summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/GNUmakefile127
-rw-r--r--src/autofsd-probe/GNUmakefile38
-rw-r--r--src/autofsd-probe/autofsd-probe.c89
-rw-r--r--src/bashrc/GNUmakefile32
-rw-r--r--src/bashrc/pcp_completion.sh68
-rw-r--r--src/collectl2pcp/GNUmakefile47
-rw-r--r--src/collectl2pcp/collectl2pcp.c268
-rw-r--r--src/collectl2pcp/cpu.c72
-rw-r--r--src/collectl2pcp/disk.c53
-rw-r--r--src/collectl2pcp/generic.c35
-rw-r--r--src/collectl2pcp/header.c143
-rw-r--r--src/collectl2pcp/load.c32
-rw-r--r--src/collectl2pcp/metrics.c1672
-rw-r--r--src/collectl2pcp/metrics.h90
-rw-r--r--src/collectl2pcp/net.c64
-rw-r--r--src/collectl2pcp/pmdesc.c84
-rw-r--r--src/collectl2pcp/proc.c191
-rw-r--r--src/collectl2pcp/timestamp.c58
-rw-r--r--src/collectl2pcp/util.c207
-rw-r--r--src/dbpmda/GNUmakefile32
-rw-r--r--src/dbpmda/src/GNUmakefile47
-rw-r--r--src/dbpmda/src/dbpmda.c437
-rw-r--r--src/dbpmda/src/dbpmda.h98
-rw-r--r--src/dbpmda/src/dso.c479
-rw-r--r--src/dbpmda/src/gram.y634
-rw-r--r--src/dbpmda/src/lex.h32
-rw-r--r--src/dbpmda/src/lex.l611
-rw-r--r--src/dbpmda/src/pmda.c840
-rw-r--r--src/dbpmda/src/util.c587
-rw-r--r--src/genpmda/GNUmakefile34
-rwxr-xr-xsrc/genpmda/genpmda1026
-rw-r--r--src/include/GNUmakefile51
-rw-r--r--src/include/builddefs.in706
-rw-r--r--src/include/buildrules192
-rw-r--r--src/include/pcp.conf.in206
-rw-r--r--src/include/pcp.env95
-rw-r--r--src/include/pcp.mingw75
-rw-r--r--src/include/pcp/GNUmakefile42
-rw-r--r--src/include/pcp/config.h.in685
-rw-r--r--src/include/pcp/config32.h23
-rw-r--r--src/include/pcp/config64.h23
-rw-r--r--src/include/pcp/configsz.h.in24
-rw-r--r--src/include/pcp/fault.h64
-rw-r--r--src/include/pcp/impl.h1488
-rw-r--r--src/include/pcp/import.h67
-rwxr-xr-xsrc/include/pcp/mk_pmdbg58
-rw-r--r--src/include/pcp/mmv_dev.h84
-rw-r--r--src/include/pcp/mmv_stats.h108
-rw-r--r--src/include/pcp/platform_defs.h.in13
-rw-r--r--src/include/pcp/pmafm.h47
-rw-r--r--src/include/pcp/pmapi.h884
-rw-r--r--src/include/pcp/pmda.h725
-rw-r--r--src/include/pcp/pmtime.h107
-rw-r--r--src/include/pcp/trace.h110
-rw-r--r--src/include/pcp/trace_dev.h95
-rw-r--r--src/iostat2pcp/GNUmakefile43
-rw-r--r--src/iostat2pcp/README123
-rwxr-xr-xsrc/iostat2pcp/iostat2pcp859
-rw-r--r--src/libpcp/GNUlocaldefs.324
-rw-r--r--src/libpcp/GNUmakefile31
-rw-r--r--src/libpcp/src/AF.c511
-rw-r--r--src/libpcp/src/GNUlocaldefs.coverage11
-rw-r--r--src/libpcp/src/GNUlocaldefs.debug8
-rw-r--r--src/libpcp/src/GNUmakefile160
-rw-r--r--src/libpcp/src/access.c1875
-rw-r--r--src/libpcp/src/accounts.c586
-rw-r--r--src/libpcp/src/auxconnect.c1389
-rw-r--r--src/libpcp/src/auxserver.c940
-rw-r--r--src/libpcp/src/avahi.c800
-rw-r--r--src/libpcp/src/avahi.h31
-rwxr-xr-xsrc/libpcp/src/check-statics502
-rw-r--r--src/libpcp/src/checksum.c37
-rw-r--r--src/libpcp/src/config.c377
-rw-r--r--src/libpcp/src/connect.c447
-rw-r--r--src/libpcp/src/connectlocal.c692
-rw-r--r--src/libpcp/src/context.c1038
-rw-r--r--src/libpcp/src/derive.c1864
-rw-r--r--src/libpcp/src/derive.h101
-rw-r--r--src/libpcp/src/derive_fetch.c1317
-rw-r--r--src/libpcp/src/desc.c87
-rw-r--r--src/libpcp/src/discovery.c323
-rw-r--r--src/libpcp/src/endian.c361
-rw-r--r--src/libpcp/src/err.c303
-rw-r--r--src/libpcp/src/events.c735
-rw-r--r--src/libpcp/src/exports472
-rw-r--r--src/libpcp/src/fault.c314
-rw-r--r--src/libpcp/src/fetch.c271
-rw-r--r--src/libpcp/src/fetchlocal.c177
-rw-r--r--src/libpcp/src/freeresult.c84
-rw-r--r--src/libpcp/src/getdate.y1274
-rw-r--r--src/libpcp/src/getopt.c1518
-rw-r--r--src/libpcp/src/hash.c202
-rw-r--r--src/libpcp/src/help.c116
-rw-r--r--src/libpcp/src/instance.c352
-rw-r--r--src/libpcp/src/internal.h277
-rw-r--r--src/libpcp/src/interp.c1714
-rw-r--r--src/libpcp/src/ipc.c275
-rw-r--r--src/libpcp/src/lock.c284
-rw-r--r--src/libpcp/src/logconnect.c472
-rw-r--r--src/libpcp/src/logcontrol.c54
-rw-r--r--src/libpcp/src/logmeta.c847
-rw-r--r--src/libpcp/src/logportmap.c454
-rw-r--r--src/libpcp/src/logutil.c2461
-rw-r--r--src/libpcp/src/loop.c871
-rw-r--r--src/libpcp/src/optfetch.c709
-rw-r--r--src/libpcp/src/p_auth.c103
-rw-r--r--src/libpcp/src/p_creds.c103
-rw-r--r--src/libpcp/src/p_desc.c118
-rw-r--r--src/libpcp/src/p_error.c168
-rw-r--r--src/libpcp/src/p_fetch.c101
-rw-r--r--src/libpcp/src/p_instance.c291
-rw-r--r--src/libpcp/src/p_lcontrol.c194
-rw-r--r--src/libpcp/src/p_lrequest.c85
-rw-r--r--src/libpcp/src/p_lstatus.c116
-rw-r--r--src/libpcp/src/p_pmns.c576
-rw-r--r--src/libpcp/src/p_profile.c223
-rw-r--r--src/libpcp/src/p_result.c652
-rw-r--r--src/libpcp/src/p_text.c150
-rw-r--r--src/libpcp/src/pdu.c574
-rw-r--r--src/libpcp/src/pdubuf.c249
-rw-r--r--src/libpcp/src/pmns.c2559
-rw-r--r--src/libpcp/src/probe.c590
-rw-r--r--src/libpcp/src/probe.h23
-rw-r--r--src/libpcp/src/profile.c385
-rw-r--r--src/libpcp/src/rtime.c761
-rw-r--r--src/libpcp/src/secureconnect.c1575
-rw-r--r--src/libpcp/src/secureserver.c719
-rw-r--r--src/libpcp/src/sortinst.c37
-rw-r--r--src/libpcp/src/spec.c1077
-rw-r--r--src/libpcp/src/store.c103
-rw-r--r--src/libpcp/src/stuffvalue.c84
-rw-r--r--src/libpcp/src/tv.c126
-rw-r--r--src/libpcp/src/tz.c541
-rw-r--r--src/libpcp/src/units.c1107
-rw-r--r--src/libpcp/src/util.c2351
-rw-r--r--src/libpcp/src/win32.c796
-rw-r--r--src/libpcp_fault/README58
-rw-r--r--src/libpcp_fault/pmfault.3353
-rw-r--r--src/libpcp_fault/src/GNUmakefile160
-rwxr-xr-xsrc/libpcp_fault/src/mk.exports14
-rw-r--r--src/libpcp_gui/GNUmakefile27
-rw-r--r--src/libpcp_gui/src/GNUmakefile77
-rw-r--r--src/libpcp_gui/src/exports19
-rw-r--r--src/libpcp_gui/src/record.c758
-rw-r--r--src/libpcp_gui/src/timeclient.c202
-rw-r--r--src/libpcp_gui/src/timestate.c307
-rw-r--r--src/libpcp_http/GNUmakefile27
-rw-r--r--src/libpcp_http/src/GNUmakefile32
-rw-r--r--src/libpcp_http/src/README78
-rw-r--r--src/libpcp_http/src/http_error_codes.c39
-rw-r--r--src/libpcp_http/src/http_error_codes.h43
-rw-r--r--src/libpcp_http/src/http_fetcher.c886
-rw-r--r--src/libpcp_http/src/http_fetcher.h168
-rw-r--r--src/libpcp_import/GNUmakefile38
-rw-r--r--src/libpcp_import/src/GNUmakefile81
-rw-r--r--src/libpcp_import/src/archive.c144
-rw-r--r--src/libpcp_import/src/exports28
-rw-r--r--src/libpcp_import/src/import.c726
-rw-r--r--src/libpcp_import/src/private.h71
-rw-r--r--src/libpcp_import/src/stuff.c178
-rw-r--r--src/libpcp_mmv/GNUmakefile35
-rw-r--r--src/libpcp_mmv/src/GNUmakefile73
-rw-r--r--src/libpcp_mmv/src/exports26
-rw-r--r--src/libpcp_mmv/src/mmv_stats.c628
-rw-r--r--src/libpcp_pmcd/GNUlocaldefs.324
-rw-r--r--src/libpcp_pmcd/GNUmakefile35
-rw-r--r--src/libpcp_pmcd/src/GNUmakefile88
-rw-r--r--src/libpcp_pmcd/src/client.c61
-rw-r--r--src/libpcp_pmcd/src/data.c43
-rw-r--r--src/libpcp_pmcd/src/pmcd.stp.in304
-rw-r--r--src/libpcp_pmcd/src/probes.d4
-rw-r--r--src/libpcp_pmcd/src/trace.c289
-rw-r--r--src/libpcp_pmda/GNUlocaldefs.324
-rw-r--r--src/libpcp_pmda/GNUmakefile31
-rw-r--r--src/libpcp_pmda/src/GNUmakefile102
-rw-r--r--src/libpcp_pmda/src/cache.c1543
-rw-r--r--src/libpcp_pmda/src/callback.c753
-rw-r--r--src/libpcp_pmda/src/context.c31
-rw-r--r--src/libpcp_pmda/src/dynamic.c217
-rw-r--r--src/libpcp_pmda/src/events.c391
-rw-r--r--src/libpcp_pmda/src/exports115
-rw-r--r--src/libpcp_pmda/src/help.c217
-rw-r--r--src/libpcp_pmda/src/libdefs.h36
-rw-r--r--src/libpcp_pmda/src/mainloop.c491
-rw-r--r--src/libpcp_pmda/src/open.c987
-rw-r--r--src/libpcp_pmda/src/queues.c610
-rw-r--r--src/libpcp_pmda/src/queues.h172
-rw-r--r--src/libpcp_pmda/src/tree.c313
-rw-r--r--src/libpcp_qed/GNUmakefile27
-rw-r--r--src/libpcp_qed/src/GNUmakefile24
-rw-r--r--src/libpcp_qed/src/libpcp_qed.pro51
-rw-r--r--src/libpcp_qed/src/qed.h28
-rw-r--r--src/libpcp_qed/src/qed_actionlist.cpp52
-rw-r--r--src/libpcp_qed/src/qed_actionlist.h40
-rw-r--r--src/libpcp_qed/src/qed_app.cpp256
-rw-r--r--src/libpcp_qed/src/qed_app.h82
-rw-r--r--src/libpcp_qed/src/qed_bar.cpp95
-rw-r--r--src/libpcp_qed/src/qed_bar.h66
-rw-r--r--src/libpcp_qed/src/qed_colorlist.cpp38
-rw-r--r--src/libpcp_qed/src/qed_colorlist.h33
-rw-r--r--src/libpcp_qed/src/qed_colorpicker.cpp306
-rw-r--r--src/libpcp_qed/src/qed_colorpicker.h170
-rw-r--r--src/libpcp_qed/src/qed_console.cpp96
-rw-r--r--src/libpcp_qed/src/qed_console.h41
-rw-r--r--src/libpcp_qed/src/qed_console.ui132
-rw-r--r--src/libpcp_qed/src/qed_fileiconprovider.cpp140
-rw-r--r--src/libpcp_qed/src/qed_fileiconprovider.h53
-rw-r--r--src/libpcp_qed/src/qed_gadget.cpp26
-rw-r--r--src/libpcp_qed/src/qed_gadget.h33
-rw-r--r--src/libpcp_qed/src/qed_groupcontrol.cpp260
-rw-r--r--src/libpcp_qed/src/qed_groupcontrol.h89
-rw-r--r--src/libpcp_qed/src/qed_label.cpp28
-rw-r--r--src/libpcp_qed/src/qed_label.h38
-rw-r--r--src/libpcp_qed/src/qed_led.cpp155
-rw-r--r--src/libpcp_qed/src/qed_led.h65
-rw-r--r--src/libpcp_qed/src/qed_legend.cpp53
-rw-r--r--src/libpcp_qed/src/qed_legend.h55
-rw-r--r--src/libpcp_qed/src/qed_line.cpp92
-rw-r--r--src/libpcp_qed/src/qed_line.h44
-rw-r--r--src/libpcp_qed/src/qed_recorddialog.cpp327
-rw-r--r--src/libpcp_qed/src/qed_recorddialog.h94
-rw-r--r--src/libpcp_qed/src/qed_recorddialog.ui540
-rw-r--r--src/libpcp_qed/src/qed_statusbar.cpp106
-rw-r--r--src/libpcp_qed/src/qed_statusbar.h62
-rw-r--r--src/libpcp_qed/src/qed_timebutton.cpp79
-rw-r--r--src/libpcp_qed/src/qed_timebutton.h61
-rw-r--r--src/libpcp_qed/src/qed_timecontrol.cpp442
-rw-r--r--src/libpcp_qed/src/qed_timecontrol.h111
-rw-r--r--src/libpcp_qed/src/qed_viewcontrol.cpp165
-rw-r--r--src/libpcp_qed/src/qed_viewcontrol.h71
-rw-r--r--src/libpcp_qmc/GNUmakefile27
-rw-r--r--src/libpcp_qmc/src/GNUmakefile24
-rw-r--r--src/libpcp_qmc/src/libpcp_qmc.pro13
-rw-r--r--src/libpcp_qmc/src/qmc.h26
-rw-r--r--src/libpcp_qmc/src/qmc_context.cpp402
-rw-r--r--src/libpcp_qmc/src/qmc_context.h115
-rw-r--r--src/libpcp_qmc/src/qmc_desc.cpp203
-rw-r--r--src/libpcp_qmc/src/qmc_desc.h49
-rw-r--r--src/libpcp_qmc/src/qmc_group.cpp521
-rw-r--r--src/libpcp_qmc/src/qmc_group.h103
-rw-r--r--src/libpcp_qmc/src/qmc_indom.cpp481
-rw-r--r--src/libpcp_qmc/src/qmc_indom.h137
-rw-r--r--src/libpcp_qmc/src/qmc_metric.cpp1248
-rw-r--r--src/libpcp_qmc/src/qmc_metric.h336
-rw-r--r--src/libpcp_qmc/src/qmc_source.cpp425
-rw-r--r--src/libpcp_qmc/src/qmc_source.h108
-rw-r--r--src/libpcp_qmc/src/qmc_time.cpp174
-rw-r--r--src/libpcp_qmc/src/qmc_time.h121
-rw-r--r--src/libpcp_qwt/GNUmakefile27
-rw-r--r--src/libpcp_qwt/src/GNUmakefile24
-rw-r--r--src/libpcp_qwt/src/libpcp_qwt.pro173
-rw-r--r--src/libpcp_qwt/src/qwt.h22
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_scale.cpp310
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_scale.h70
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_scale_draw.cpp412
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_scale_draw.h139
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_slider.cpp597
-rw-r--r--src/libpcp_qwt/src/qwt_abstract_slider.h188
-rw-r--r--src/libpcp_qwt/src/qwt_analog_clock.cpp228
-rw-r--r--src/libpcp_qwt/src/qwt_analog_clock.h96
-rw-r--r--src/libpcp_qwt/src/qwt_arrow_button.cpp332
-rw-r--r--src/libpcp_qwt/src/qwt_arrow_button.h52
-rw-r--r--src/libpcp_qwt/src/qwt_clipper.cpp486
-rw-r--r--src/libpcp_qwt/src/qwt_clipper.h37
-rw-r--r--src/libpcp_qwt/src/qwt_color_map.cpp440
-rw-r--r--src/libpcp_qwt/src/qwt_color_map.h198
-rw-r--r--src/libpcp_qwt/src/qwt_column_symbol.cpp293
-rw-r--r--src/libpcp_qwt/src/qwt_column_symbol.h161
-rw-r--r--src/libpcp_qwt/src/qwt_compass.cpp292
-rw-r--r--src/libpcp_qwt/src/qwt_compass.h65
-rw-r--r--src/libpcp_qwt/src/qwt_compass_rose.cpp265
-rw-r--r--src/libpcp_qwt/src/qwt_compass_rose.h89
-rw-r--r--src/libpcp_qwt/src/qwt_compat.h42
-rw-r--r--src/libpcp_qwt/src/qwt_counter.cpp603
-rw-r--r--src/libpcp_qwt/src/qwt_counter.h148
-rw-r--r--src/libpcp_qwt/src/qwt_curve_fitter.cpp405
-rw-r--r--src/libpcp_qwt/src/qwt_curve_fitter.h128
-rw-r--r--src/libpcp_qwt/src/qwt_dial.cpp1156
-rw-r--r--src/libpcp_qwt/src/qwt_dial.h215
-rw-r--r--src/libpcp_qwt/src/qwt_dial_needle.cpp441
-rw-r--r--src/libpcp_qwt/src/qwt_dial_needle.h190
-rw-r--r--src/libpcp_qwt/src/qwt_double_range.cpp410
-rw-r--r--src/libpcp_qwt/src/qwt_double_range.h78
-rw-r--r--src/libpcp_qwt/src/qwt_dyngrid_layout.cpp575
-rw-r--r--src/libpcp_qwt/src/qwt_dyngrid_layout.h83
-rw-r--r--src/libpcp_qwt/src/qwt_event_pattern.cpp274
-rw-r--r--src/libpcp_qwt/src/qwt_event_pattern.h225
-rw-r--r--src/libpcp_qwt/src/qwt_global.h41
-rw-r--r--src/libpcp_qwt/src/qwt_interval.cpp334
-rw-r--r--src/libpcp_qwt/src/qwt_interval.h296
-rw-r--r--src/libpcp_qwt/src/qwt_interval_symbol.cpp300
-rw-r--r--src/libpcp_qwt/src/qwt_interval_symbol.h86
-rw-r--r--src/libpcp_qwt/src/qwt_knob.cpp665
-rw-r--r--src/libpcp_qwt/src/qwt_knob.h159
-rw-r--r--src/libpcp_qwt/src/qwt_legend.cpp519
-rw-r--r--src/libpcp_qwt/src/qwt_legend.h95
-rw-r--r--src/libpcp_qwt/src/qwt_legend_item.cpp407
-rw-r--r--src/libpcp_qwt/src/qwt_legend_item.h78
-rw-r--r--src/libpcp_qwt/src/qwt_legend_itemmanager.h66
-rw-r--r--src/libpcp_qwt/src/qwt_magnifier.cpp467
-rw-r--r--src/libpcp_qwt/src/qwt_magnifier.h86
-rw-r--r--src/libpcp_qwt/src/qwt_math.cpp45
-rw-r--r--src/libpcp_qwt/src/qwt_math.h182
-rw-r--r--src/libpcp_qwt/src/qwt_matrix_raster_data.cpp270
-rw-r--r--src/libpcp_qwt/src/qwt_matrix_raster_data.h71
-rw-r--r--src/libpcp_qwt/src/qwt_null_paintdevice.cpp428
-rw-r--r--src/libpcp_qwt/src/qwt_null_paintdevice.h89
-rw-r--r--src/libpcp_qwt/src/qwt_painter.cpp765
-rw-r--r--src/libpcp_qwt/src/qwt_painter.h154
-rw-r--r--src/libpcp_qwt/src/qwt_panner.cpp537
-rw-r--r--src/libpcp_qwt/src/qwt_panner.h100
-rw-r--r--src/libpcp_qwt/src/qwt_picker.cpp1462
-rw-r--r--src/libpcp_qwt/src/qwt_picker.h327
-rw-r--r--src/libpcp_qwt/src/qwt_picker_machine.cpp455
-rw-r--r--src/libpcp_qwt/src/qwt_picker_machine.h190
-rw-r--r--src/libpcp_qwt/src/qwt_plot.cpp751
-rw-r--r--src/libpcp_qwt/src/qwt_plot.h290
-rw-r--r--src/libpcp_qwt/src/qwt_plot_axis.cpp670
-rw-r--r--src/libpcp_qwt/src/qwt_plot_canvas.cpp1095
-rw-r--r--src/libpcp_qwt/src/qwt_plot_canvas.h171
-rw-r--r--src/libpcp_qwt/src/qwt_plot_curve.cpp1127
-rw-r--r--src/libpcp_qwt/src/qwt_plot_curve.h319
-rw-r--r--src/libpcp_qwt/src/qwt_plot_dict.cpp188
-rw-r--r--src/libpcp_qwt/src/qwt_plot_dict.h58
-rw-r--r--src/libpcp_qwt/src/qwt_plot_directpainter.cpp313
-rw-r--r--src/libpcp_qwt/src/qwt_plot_directpainter.h100
-rw-r--r--src/libpcp_qwt/src/qwt_plot_grid.cpp367
-rw-r--r--src/libpcp_qwt/src/qwt_plot_grid.h84
-rw-r--r--src/libpcp_qwt/src/qwt_plot_histogram.cpp651
-rw-r--r--src/libpcp_qwt/src/qwt_plot_histogram.h134
-rw-r--r--src/libpcp_qwt/src/qwt_plot_intervalcurve.cpp548
-rw-r--r--src/libpcp_qwt/src/qwt_plot_intervalcurve.h130
-rw-r--r--src/libpcp_qwt/src/qwt_plot_item.cpp542
-rw-r--r--src/libpcp_qwt/src/qwt_plot_item.h214
-rw-r--r--src/libpcp_qwt/src/qwt_plot_layout.cpp1267
-rw-r--r--src/libpcp_qwt/src/qwt_plot_layout.h108
-rw-r--r--src/libpcp_qwt/src/qwt_plot_magnifier.cpp143
-rw-r--r--src/libpcp_qwt/src/qwt_plot_magnifier.h55
-rw-r--r--src/libpcp_qwt/src/qwt_plot_marker.cpp608
-rw-r--r--src/libpcp_qwt/src/qwt_plot_marker.h124
-rw-r--r--src/libpcp_qwt/src/qwt_plot_panner.cpp175
-rw-r--r--src/libpcp_qwt/src/qwt_plot_panner.h60
-rw-r--r--src/libpcp_qwt/src/qwt_plot_picker.cpp383
-rw-r--r--src/libpcp_qwt/src/qwt_plot_picker.h115
-rw-r--r--src/libpcp_qwt/src/qwt_plot_rasteritem.cpp904
-rw-r--r--src/libpcp_qwt/src/qwt_plot_rasteritem.h146
-rw-r--r--src/libpcp_qwt/src/qwt_plot_renderer.cpp897
-rw-r--r--src/libpcp_qwt/src/qwt_plot_renderer.h154
-rw-r--r--src/libpcp_qwt/src/qwt_plot_rescaler.cpp628
-rw-r--r--src/libpcp_qwt/src/qwt_plot_rescaler.h143
-rw-r--r--src/libpcp_qwt/src/qwt_plot_scaleitem.cpp445
-rw-r--r--src/libpcp_qwt/src/qwt_plot_scaleitem.h94
-rw-r--r--src/libpcp_qwt/src/qwt_plot_seriesitem.cpp90
-rw-r--r--src/libpcp_qwt/src/qwt_plot_seriesitem.h206
-rw-r--r--src/libpcp_qwt/src/qwt_plot_spectrocurve.cpp300
-rw-r--r--src/libpcp_qwt/src/qwt_plot_spectrocurve.h76
-rw-r--r--src/libpcp_qwt/src/qwt_plot_spectrogram.cpp663
-rw-r--r--src/libpcp_qwt/src/qwt_plot_spectrogram.h115
-rw-r--r--src/libpcp_qwt/src/qwt_plot_svgitem.cpp214
-rw-r--r--src/libpcp_qwt/src/qwt_plot_svgitem.h61
-rw-r--r--src/libpcp_qwt/src/qwt_plot_xml.cpp41
-rw-r--r--src/libpcp_qwt/src/qwt_plot_zoomer.cpp607
-rw-r--r--src/libpcp_qwt/src/qwt_plot_zoomer.h104
-rw-r--r--src/libpcp_qwt/src/qwt_point_3d.cpp22
-rw-r--r--src/libpcp_qwt/src/qwt_point_3d.h189
-rw-r--r--src/libpcp_qwt/src/qwt_point_polar.cpp114
-rw-r--r--src/libpcp_qwt/src/qwt_point_polar.h195
-rw-r--r--src/libpcp_qwt/src/qwt_raster_data.cpp390
-rw-r--r--src/libpcp_qwt/src/qwt_raster_data.h95
-rw-r--r--src/libpcp_qwt/src/qwt_round_scale_draw.cpp309
-rw-r--r--src/libpcp_qwt/src/qwt_round_scale_draw.h68
-rw-r--r--src/libpcp_qwt/src/qwt_sampling_thread.cpp106
-rw-r--r--src/libpcp_qwt/src/qwt_sampling_thread.h50
-rw-r--r--src/libpcp_qwt/src/qwt_scale_div.cpp173
-rw-r--r--src/libpcp_qwt/src/qwt_scale_div.h132
-rw-r--r--src/libpcp_qwt/src/qwt_scale_draw.cpp903
-rw-r--r--src/libpcp_qwt/src/qwt_scale_draw.h117
-rw-r--r--src/libpcp_qwt/src/qwt_scale_engine.cpp967
-rw-r--r--src/libpcp_qwt/src/qwt_scale_engine.h217
-rw-r--r--src/libpcp_qwt/src/qwt_scale_map.cpp344
-rw-r--r--src/libpcp_qwt/src/qwt_scale_map.h219
-rw-r--r--src/libpcp_qwt/src/qwt_scale_widget.cpp918
-rw-r--r--src/libpcp_qwt/src/qwt_scale_widget.h135
-rw-r--r--src/libpcp_qwt/src/qwt_series_data.cpp591
-rw-r--r--src/libpcp_qwt/src/qwt_series_data.h460
-rw-r--r--src/libpcp_qwt/src/qwt_slider.cpp805
-rw-r--r--src/libpcp_qwt/src/qwt_slider.h150
-rw-r--r--src/libpcp_qwt/src/qwt_spline.cpp380
-rw-r--r--src/libpcp_qwt/src/qwt_spline.h101
-rw-r--r--src/libpcp_qwt/src/qwt_symbol.cpp1006
-rw-r--r--src/libpcp_qwt/src/qwt_symbol.h154
-rw-r--r--src/libpcp_qwt/src/qwt_system_clock.cpp364
-rw-r--r--src/libpcp_qwt/src/qwt_system_clock.h49
-rw-r--r--src/libpcp_qwt/src/qwt_text.cpp643
-rw-r--r--src/libpcp_qwt/src/qwt_text.h216
-rw-r--r--src/libpcp_qwt/src/qwt_text_engine.cpp344
-rw-r--r--src/libpcp_qwt/src/qwt_text_engine.h172
-rw-r--r--src/libpcp_qwt/src/qwt_text_label.cpp306
-rw-r--r--src/libpcp_qwt/src/qwt_text_label.h72
-rw-r--r--src/libpcp_qwt/src/qwt_thermo.cpp1036
-rw-r--r--src/libpcp_qwt/src/qwt_thermo.h202
-rw-r--r--src/libpcp_qwt/src/qwt_wheel.cpp544
-rw-r--r--src/libpcp_qwt/src/qwt_wheel.h89
-rw-r--r--src/libpcp_trace/GNUmakefile28
-rw-r--r--src/libpcp_trace/src/GNUmakefile74
-rw-r--r--src/libpcp_trace/src/exports42
-rw-r--r--src/libpcp_trace/src/ftrace.c134
-rw-r--r--src/libpcp_trace/src/hash.c181
-rw-r--r--src/libpcp_trace/src/hash.h54
-rw-r--r--src/libpcp_trace/src/p_ack.c71
-rw-r--r--src/libpcp_trace/src/p_data.c179
-rw-r--r--src/libpcp_trace/src/pdu.c419
-rw-r--r--src/libpcp_trace/src/pdubuf.c182
-rw-r--r--src/libpcp_trace/src/trace.c950
-rw-r--r--src/mrtg2pcp/GNUmakefile43
-rw-r--r--src/mrtg2pcp/README13
-rwxr-xr-xsrc/mrtg2pcp/mrtg2pcp144
-rw-r--r--src/newhelp/GNUmakefile40
-rw-r--r--src/newhelp/chkhelp.c348
-rw-r--r--src/newhelp/newhelp.c473
-rw-r--r--src/pcp/GNUmakefile33
-rw-r--r--src/pcp/dmcache/GNUmakefile34
-rw-r--r--src/pcp/dmcache/pcp-dmcache.152
-rwxr-xr-xsrc/pcp/dmcache/pcp-dmcache.py154
-rw-r--r--src/pcp/free/GNUmakefile34
-rw-r--r--src/pcp/free/pcp-free.171
-rwxr-xr-xsrc/pcp/free/pcp-free.py216
-rw-r--r--src/pcp/numastat/GNUmakefile34
-rw-r--r--src/pcp/numastat/pcp-numastat.157
-rwxr-xr-xsrc/pcp/numastat/pcp-numastat.py157
-rwxr-xr-xsrc/pcp/pcp.sh521
-rw-r--r--src/pcp/uptime/GNUmakefile34
-rw-r--r--src/pcp/uptime/pcp-uptime.144
-rwxr-xr-xsrc/pcp/uptime/pcp-uptime.py128
-rw-r--r--src/perl/GNUmakefile49
-rw-r--r--src/perl/LogImport/Changes13
-rw-r--r--src/perl/LogImport/GNUmakefile71
-rw-r--r--src/perl/LogImport/LogImport.pm190
-rw-r--r--src/perl/LogImport/LogImport.xs129
-rw-r--r--src/perl/LogImport/MANIFEST7
-rw-r--r--src/perl/LogImport/Makefile.PL49
-rw-r--r--src/perl/LogImport/typemap24
-rw-r--r--src/perl/LogSummary/Changes8
-rw-r--r--src/perl/LogSummary/GNUmakefile62
-rw-r--r--src/perl/LogSummary/LogSummary.pm117
-rw-r--r--src/perl/LogSummary/MANIFEST20
-rw-r--r--src/perl/LogSummary/Makefile.PL9
-rw-r--r--src/perl/LogSummary/README32
-rw-r--r--src/perl/LogSummary/exceldemo.pl89
-rwxr-xr-xsrc/perl/LogSummary/extract.pl18
-rw-r--r--src/perl/LogSummary/t/GNUmakefile12
-rw-r--r--src/perl/LogSummary/t/app/20081125.0bin0 -> 483152 bytes
-rw-r--r--src/perl/LogSummary/t/app/20081125.indexbin0 -> 272 bytes
-rw-r--r--src/perl/LogSummary/t/app/20081125.metabin0 -> 379 bytes
-rw-r--r--src/perl/LogSummary/t/app/20081126.0bin0 -> 481124 bytes
-rw-r--r--src/perl/LogSummary/t/app/20081126.indexbin0 -> 252 bytes
-rw-r--r--src/perl/LogSummary/t/app/20081126.metabin0 -> 323 bytes
-rw-r--r--src/perl/LogSummary/t/app/GNUmakefile13
-rw-r--r--src/perl/LogSummary/t/db/20081125.0bin0 -> 1451420 bytes
-rw-r--r--src/perl/LogSummary/t/db/20081125.indexbin0 -> 472 bytes
-rw-r--r--src/perl/LogSummary/t/db/20081125.metabin0 -> 554 bytes
-rw-r--r--src/perl/LogSummary/t/db/20081126.0bin0 -> 1440428 bytes
-rw-r--r--src/perl/LogSummary/t/db/20081126.indexbin0 -> 492 bytes
-rw-r--r--src/perl/LogSummary/t/db/20081126.metabin0 -> 704 bytes
-rw-r--r--src/perl/LogSummary/t/db/GNUmakefile13
-rw-r--r--src/perl/LogSummary/t/test.t41
-rw-r--r--src/perl/MMV/Changes12
-rw-r--r--src/perl/MMV/GNUmakefile67
-rw-r--r--src/perl/MMV/MANIFEST9
-rw-r--r--src/perl/MMV/MMV.pm120
-rw-r--r--src/perl/MMV/MMV.xs381
-rw-r--r--src/perl/MMV/Makefile.PL49
-rwxr-xr-xsrc/perl/MMV/server.pl101
-rw-r--r--src/perl/MMV/test.pl25
-rw-r--r--src/perl/MMV/typemap10
-rw-r--r--src/perl/PMDA/Changes101
-rw-r--r--src/perl/PMDA/GNUmakefile67
-rw-r--r--src/perl/PMDA/MANIFEST11
-rw-r--r--src/perl/PMDA/Makefile.PL49
-rw-r--r--src/perl/PMDA/PMDA.pm495
-rw-r--r--src/perl/PMDA/PMDA.xs1212
-rw-r--r--src/perl/PMDA/cvalue.c155
-rw-r--r--src/perl/PMDA/local.c468
-rw-r--r--src/perl/PMDA/local.h80
-rw-r--r--src/perl/PMDA/test.pl93
-rw-r--r--src/perl/PMDA/typemap27
-rw-r--r--src/pmafm/GNUmakefile43
-rwxr-xr-xsrc/pmafm/mkaf141
-rwxr-xr-xsrc/pmafm/pmafm568
-rw-r--r--src/pmafm/pmafm.pcp23
-rw-r--r--src/pmafm/pmafm.pcp-gui5
-rw-r--r--src/pmafm/template49
-rw-r--r--src/pmatop/GNUmakefile28
-rw-r--r--src/pmatop/pmatop.py917
-rw-r--r--src/pmcd/GNUmakefile52
-rw-r--r--src/pmcd/pmcd.options36
-rw-r--r--src/pmcd/pmcd.service14
-rw-r--r--src/pmcd/pmcd.service.in14
-rw-r--r--src/pmcd/pmdaproc.sh1457
-rw-r--r--src/pmcd/rc-proc.sh394
-rw-r--r--src/pmcd/rc-proc.sh.minimal74
-rw-r--r--src/pmcd/rc_local67
-rw-r--r--src/pmcd/rc_pcp76
-rw-r--r--src/pmcd/rc_pmcd540
-rw-r--r--src/pmcd/sasl2.conf19
-rw-r--r--src/pmcd/src/GNUmakefile38
-rw-r--r--src/pmcd/src/agent.c221
-rw-r--r--src/pmcd/src/client.c265
-rw-r--r--src/pmcd/src/client.h58
-rw-r--r--src/pmcd/src/config.c2526
-rw-r--r--src/pmcd/src/dofetch.c582
-rw-r--r--src/pmcd/src/dopdus.c1057
-rw-r--r--src/pmcd/src/dostore.c312
-rw-r--r--src/pmcd/src/pmcd.c1024
-rw-r--r--src/pmcd/src/pmcd.h233
-rw-r--r--src/pmcd/src/util.c96
-rw-r--r--src/pmcd_wait/GNUmakefile31
-rw-r--r--src/pmcd_wait/pmcd_wait.c146
-rw-r--r--src/pmchart/GNUmakefile90
-rw-r--r--src/pmchart/aboutdialog.cpp29
-rw-r--r--src/pmchart/aboutdialog.h30
-rw-r--r--src/pmchart/aboutdialog.ui229
-rw-r--r--src/pmchart/chart.cpp1058
-rw-r--r--src/pmchart/chart.h334
-rw-r--r--src/pmchart/chartdialog.cpp916
-rw-r--r--src/pmchart/chartdialog.h130
-rw-r--r--src/pmchart/chartdialog.ui2249
-rw-r--r--src/pmchart/colorbutton.cpp46
-rw-r--r--src/pmchart/colorbutton.h43
-rw-r--r--src/pmchart/colorscheme.cpp95
-rw-r--r--src/pmchart/colorscheme.h57
-rw-r--r--src/pmchart/exportdialog.cpp248
-rw-r--r--src/pmchart/exportdialog.h66
-rw-r--r--src/pmchart/exportdialog.ui463
-rw-r--r--src/pmchart/gadget.cpp92
-rw-r--r--src/pmchart/gadget.h71
-rw-r--r--src/pmchart/groupcontrol.cpp675
-rw-r--r--src/pmchart/groupcontrol.h107
-rw-r--r--src/pmchart/hostdialog.cpp160
-rw-r--r--src/pmchart/hostdialog.h51
-rw-r--r--src/pmchart/hostdialog.ui320
-rw-r--r--src/pmchart/infodialog.cpp157
-rw-r--r--src/pmchart/infodialog.h54
-rw-r--r--src/pmchart/infodialog.ui181
-rw-r--r--src/pmchart/main.cpp742
-rw-r--r--src/pmchart/main.h112
-rw-r--r--src/pmchart/namespace.cpp585
-rw-r--r--src/pmchart/namespace.h110
-rw-r--r--src/pmchart/openviewdialog.cpp432
-rw-r--r--src/pmchart/openviewdialog.h73
-rw-r--r--src/pmchart/openviewdialog.ui553
-rw-r--r--src/pmchart/pmchart.cpp909
-rw-r--r--src/pmchart/pmchart.desktop8
-rw-r--r--src/pmchart/pmchart.h181
-rw-r--r--src/pmchart/pmchart.info.in18
-rw-r--r--src/pmchart/pmchart.pro45
-rw-r--r--src/pmchart/pmchart.qrc60
-rw-r--r--src/pmchart/pmchart.rc1
-rwxr-xr-xsrc/pmchart/pmchart.sh.in2
-rw-r--r--src/pmchart/pmchart.ui989
-rw-r--r--src/pmchart/recorddialog.cpp372
-rw-r--r--src/pmchart/recorddialog.h92
-rw-r--r--src/pmchart/recorddialog.ui540
-rw-r--r--src/pmchart/samplesdialog.cpp102
-rw-r--r--src/pmchart/samplesdialog.h48
-rw-r--r--src/pmchart/samplesdialog.ui353
-rw-r--r--src/pmchart/sampling.cpp880
-rw-r--r--src/pmchart/sampling.h153
-rw-r--r--src/pmchart/saveviewdialog.cpp232
-rw-r--r--src/pmchart/saveviewdialog.h65
-rw-r--r--src/pmchart/saveviewdialog.ui453
-rw-r--r--src/pmchart/searchdialog.cpp225
-rw-r--r--src/pmchart/searchdialog.h53
-rw-r--r--src/pmchart/searchdialog.ui353
-rw-r--r--src/pmchart/seealsodialog.cpp24
-rw-r--r--src/pmchart/seealsodialog.h30
-rw-r--r--src/pmchart/seealsodialog.ui235
-rw-r--r--src/pmchart/settingsdialog.cpp738
-rw-r--r--src/pmchart/settingsdialog.h132
-rw-r--r--src/pmchart/settingsdialog.ui2330
-rw-r--r--src/pmchart/statusbar.cpp121
-rw-r--r--src/pmchart/statusbar.h66
-rw-r--r--src/pmchart/tab.cpp317
-rw-r--r--src/pmchart/tab.h78
-rw-r--r--src/pmchart/tabdialog.cpp81
-rw-r--r--src/pmchart/tabdialog.h43
-rw-r--r--src/pmchart/tabdialog.ui321
-rw-r--r--src/pmchart/tabwidget.cpp47
-rw-r--r--src/pmchart/tabwidget.h46
-rw-r--r--src/pmchart/timeaxis.cpp159
-rw-r--r--src/pmchart/timeaxis.h48
-rw-r--r--src/pmchart/timecontrol.cpp447
-rw-r--r--src/pmchart/timecontrol.h106
-rw-r--r--src/pmchart/tracing.cpp720
-rw-r--r--src/pmchart/tracing.h208
-rw-r--r--src/pmchart/view.cpp1313
-rw-r--r--src/pmchart/views/Apache8
-rw-r--r--src/pmchart/views/ApacheServer38
-rwxr-xr-xsrc/pmchart/views/BusyCPU164
-rw-r--r--src/pmchart/views/CPU11
-rw-r--r--src/pmchart/views/Cisco6
-rw-r--r--src/pmchart/views/Disk6
-rw-r--r--src/pmchart/views/Diskbytes6
-rw-r--r--src/pmchart/views/ElasticsearchServer29
-rw-r--r--src/pmchart/views/Filesystem5
-rw-r--r--src/pmchart/views/GNUmakefile21
-rw-r--r--src/pmchart/views/Loadavg7
-rw-r--r--src/pmchart/views/MemAvailable6
-rw-r--r--src/pmchart/views/Memory10
-rw-r--r--src/pmchart/views/NFS26
-rw-r--r--src/pmchart/views/NFS36
-rw-r--r--src/pmchart/views/Netbytes6
-rw-r--r--src/pmchart/views/Netpackets6
-rw-r--r--src/pmchart/views/Overview32
-rw-r--r--src/pmchart/views/PMCD14
-rw-r--r--src/pmchart/views/Paging6
-rw-r--r--src/pmchart/views/Schemes6
-rw-r--r--src/pmchart/views/Sendmail10
-rw-r--r--src/pmchart/views/ShpingCPU8
-rw-r--r--src/pmchart/views/ShpingElapsed5
-rw-r--r--src/pmchart/views/Sockets7
-rw-r--r--src/pmchart/views/Swap6
-rw-r--r--src/pmchart/views/Syscalls9
-rw-r--r--src/pmchart/views/vCPU12
-rw-r--r--src/pmclient/GNUmakefile47
-rw-r--r--src/pmclient/GNUmakefile.install37
-rw-r--r--src/pmclient/README36
-rw-r--r--src/pmclient/pmclient.c357
-rw-r--r--src/pmclient/pmlogger.config16
-rw-r--r--src/pmclient/pmnsmap.spec11
-rw-r--r--src/pmcollectl/GNUmakefile28
-rwxr-xr-xsrc/pmcollectl/pmcollectl.py708
-rw-r--r--src/pmconfig/GNUmakefile31
-rw-r--r--src/pmconfig/pmconfig.c153
-rw-r--r--src/pmcpp/GNUmakefile31
-rw-r--r--src/pmcpp/pmcpp.c566
-rw-r--r--src/pmdas/GNUmakefile61
-rw-r--r--src/pmdas/aix/GNUmakefile69
-rw-r--r--src/pmdas/aix/aix.c127
-rw-r--r--src/pmdas/aix/common.h104
-rw-r--r--src/pmdas/aix/cpu.c186
-rw-r--r--src/pmdas/aix/cpu_total.c133
-rw-r--r--src/pmdas/aix/data.c435
-rw-r--r--src/pmdas/aix/disk.c189
-rw-r--r--src/pmdas/aix/disk_total.c127
-rw-r--r--src/pmdas/aix/help61
-rw-r--r--src/pmdas/aix/netif.c190
-rw-r--r--src/pmdas/aix/pmns.disk22
-rw-r--r--src/pmdas/aix/pmns.hinv6
-rw-r--r--src/pmdas/aix/pmns.kernel77
-rw-r--r--src/pmdas/aix/pmns.mem2
-rw-r--r--src/pmdas/aix/pmns.network26
-rw-r--r--src/pmdas/aix/root20
-rw-r--r--src/pmdas/apache/Apache.pmchart9
-rw-r--r--src/pmdas/apache/GNUmakefile61
-rwxr-xr-xsrc/pmdas/apache/Install54
-rw-r--r--src/pmdas/apache/README84
-rw-r--r--src/pmdas/apache/Remove25
-rw-r--r--src/pmdas/apache/apache.c538
-rw-r--r--src/pmdas/apache/help91
-rw-r--r--src/pmdas/apache/pmlogconf.processes14
-rw-r--r--src/pmdas/apache/pmlogconf.summary10
-rw-r--r--src/pmdas/apache/pmlogconf.uptime6
-rw-r--r--src/pmdas/apache/pmns43
-rw-r--r--src/pmdas/apache/root10
-rw-r--r--src/pmdas/bash/GNUmakefile62
-rw-r--r--src/pmdas/bash/Install36
-rw-r--r--src/pmdas/bash/README50
-rw-r--r--src/pmdas/bash/Remove23
-rw-r--r--src/pmdas/bash/bash.c472
-rwxr-xr-xsrc/pmdas/bash/bashproc.sh64
-rw-r--r--src/pmdas/bash/event.c434
-rw-r--r--src/pmdas/bash/event.h65
-rw-r--r--src/pmdas/bash/help48
-rwxr-xr-xsrc/pmdas/bash/pcp.sh30
-rw-r--r--src/pmdas/bash/pmns36
-rw-r--r--src/pmdas/bash/root10
-rwxr-xr-xsrc/pmdas/bash/test-child.sh39
-rwxr-xr-xsrc/pmdas/bash/test-trace.sh41
-rw-r--r--src/pmdas/bash/util.c84
-rw-r--r--src/pmdas/bonding/GNUmakefile56
-rwxr-xr-xsrc/pmdas/bonding/Install41
-rwxr-xr-xsrc/pmdas/bonding/Remove29
-rw-r--r--src/pmdas/bonding/pmdabonding.pl154
-rw-r--r--src/pmdas/cisco/Cisco.pmchart11
-rw-r--r--src/pmdas/cisco/GNUmakefile70
-rw-r--r--src/pmdas/cisco/Install156
-rw-r--r--src/pmdas/cisco/README76
-rw-r--r--src/pmdas/cisco/Remove38
-rw-r--r--src/pmdas/cisco/Samples291
-rw-r--r--src/pmdas/cisco/Tested76
-rw-r--r--src/pmdas/cisco/cisco.c265
-rw-r--r--src/pmdas/cisco/cisco.h86
-rw-r--r--src/pmdas/cisco/cisco.in_util.pmie64
-rw-r--r--src/pmdas/cisco/cisco.out_util.pmie64
-rw-r--r--src/pmdas/cisco/help76
-rw-r--r--src/pmdas/cisco/interface.c46
-rwxr-xr-xsrc/pmdas/cisco/parse.sh3
-rw-r--r--src/pmdas/cisco/pmda.c401
-rw-r--r--src/pmdas/cisco/pmns28
-rw-r--r--src/pmdas/cisco/probe.c367
-rw-r--r--src/pmdas/cisco/root10
-rw-r--r--src/pmdas/cisco/telnet.c755
-rw-r--r--src/pmdas/darwin/GNUmakefile69
-rw-r--r--src/pmdas/darwin/disk.c261
-rw-r--r--src/pmdas/darwin/disk.h55
-rw-r--r--src/pmdas/darwin/help199
-rw-r--r--src/pmdas/darwin/kernel.c195
-rw-r--r--src/pmdas/darwin/network.c185
-rw-r--r--src/pmdas/darwin/network.h57
-rw-r--r--src/pmdas/darwin/pmda.c1268
-rw-r--r--src/pmdas/darwin/pmns258
-rw-r--r--src/pmdas/darwin/root15
-rw-r--r--src/pmdas/dbping/GNUmakefile56
-rwxr-xr-xsrc/pmdas/dbping/Install36
-rwxr-xr-xsrc/pmdas/dbping/Remove29
-rw-r--r--src/pmdas/dbping/dbprobe.pl93
-rw-r--r--src/pmdas/dbping/pmdadbping.pl192
-rw-r--r--src/pmdas/dmcache/GNUmakefile37
-rw-r--r--src/pmdas/dmcache/Install35
-rw-r--r--src/pmdas/dmcache/Remove25
-rw-r--r--src/pmdas/dmcache/pmdadmcache.python264
-rw-r--r--src/pmdas/dtsrun/GNUmakefile52
-rwxr-xr-xsrc/pmdas/dtsrun/Install42
-rwxr-xr-xsrc/pmdas/dtsrun/Remove25
-rw-r--r--src/pmdas/dtsrun/pmdadtsrun.pl341
-rw-r--r--src/pmdas/elasticsearch/GNUmakefile54
-rwxr-xr-xsrc/pmdas/elasticsearch/Install38
-rwxr-xr-xsrc/pmdas/elasticsearch/Remove25
-rwxr-xr-xsrc/pmdas/elasticsearch/pmdaelasticsearch.pl882
-rw-r--r--src/pmdas/etw/GNUmakefile78
-rw-r--r--src/pmdas/etw/event.c251
-rw-r--r--src/pmdas/etw/event.h94
-rw-r--r--src/pmdas/etw/help68
-rw-r--r--src/pmdas/etw/pcp.xmlbin0 -> 107596 bytes
-rw-r--r--src/pmdas/etw/pmda.c513
-rw-r--r--src/pmdas/etw/pmns132
-rw-r--r--src/pmdas/etw/root10
-rw-r--r--src/pmdas/etw/tdhconsume.c923
-rw-r--r--src/pmdas/etw/tdhlist.c267
-rw-r--r--src/pmdas/etw/util.c190
-rw-r--r--src/pmdas/etw/util.h31
-rw-r--r--src/pmdas/freebsd/GNUmakefile69
-rw-r--r--src/pmdas/freebsd/disk.c208
-rw-r--r--src/pmdas/freebsd/freebsd.c991
-rw-r--r--src/pmdas/freebsd/freebsd.h44
-rw-r--r--src/pmdas/freebsd/help95
-rw-r--r--src/pmdas/freebsd/netif.c225
-rw-r--r--src/pmdas/freebsd/root_freebsd172
-rw-r--r--src/pmdas/gfs2/GNUmakefile60
-rw-r--r--src/pmdas/gfs2/Install27
-rw-r--r--src/pmdas/gfs2/README108
-rw-r--r--src/pmdas/gfs2/Remove24
-rw-r--r--src/pmdas/gfs2/control.c123
-rw-r--r--src/pmdas/gfs2/control.h49
-rw-r--r--src/pmdas/gfs2/ftrace.c586
-rw-r--r--src/pmdas/gfs2/ftrace.h142
-rw-r--r--src/pmdas/gfs2/glocks.c93
-rw-r--r--src/pmdas/gfs2/glocks.h36
-rw-r--r--src/pmdas/gfs2/glstats.c117
-rw-r--r--src/pmdas/gfs2/glstats.h57
-rw-r--r--src/pmdas/gfs2/help566
-rw-r--r--src/pmdas/gfs2/latency.c373
-rw-r--r--src/pmdas/gfs2/latency.h70
-rw-r--r--src/pmdas/gfs2/pmda.c1063
-rw-r--r--src/pmdas/gfs2/pmdagfs2.h57
-rw-r--r--src/pmdas/gfs2/pmns270
-rw-r--r--src/pmdas/gfs2/root16
-rw-r--r--src/pmdas/gfs2/sbstats.c264
-rw-r--r--src/pmdas/gfs2/sbstats.h56
-rw-r--r--src/pmdas/gfs2/worst_glock.c391
-rw-r--r--src/pmdas/gfs2/worst_glock.h91
-rw-r--r--src/pmdas/gluster/GNUmakefile37
-rw-r--r--src/pmdas/gluster/Install28
-rw-r--r--src/pmdas/gluster/Remove25
-rw-r--r--src/pmdas/gluster/pmdagluster.python337
-rw-r--r--src/pmdas/gpsd/GNUmakefile48
-rw-r--r--src/pmdas/gpsd/Install34
-rw-r--r--src/pmdas/gpsd/Remove25
-rw-r--r--src/pmdas/gpsd/pmdagpsd.pl310
-rw-r--r--src/pmdas/hotproc/GNUakefile52
-rw-r--r--src/pmdas/hotproc/GNUmakefile157
-rw-r--r--src/pmdas/hotproc/Install150
-rw-r--r--src/pmdas/hotproc/README141
-rw-r--r--src/pmdas/hotproc/Remove38
-rw-r--r--src/pmdas/hotproc/fixpmns.awk35
-rw-r--r--src/pmdas/hotproc/general.conf27
-rwxr-xr-xsrc/pmdas/hotproc/general.pmie29
-rwxr-xr-xsrc/pmdas/hotproc/help.fmt36
-rw-r--r--src/pmdas/hotproc/help.hotproc141
-rw-r--r--src/pmdas/hotproc/pmns.hotproc34
-rw-r--r--src/pmdas/hotproc/root10
-rw-r--r--src/pmdas/hotproc/sample.conf15
-rw-r--r--src/pmdas/hotproc/src/GNUmakefile34
-rw-r--r--src/pmdas/hotproc/src/config.c569
-rw-r--r--src/pmdas/hotproc/src/config.h79
-rw-r--r--src/pmdas/hotproc/src/ctltab.c66
-rw-r--r--src/pmdas/hotproc/src/error.c40
-rw-r--r--src/pmdas/hotproc/src/gram.y163
-rw-r--r--src/pmdas/hotproc/src/gram_node.c199
-rw-r--r--src/pmdas/hotproc/src/gram_node.h69
-rw-r--r--src/pmdas/hotproc/src/hotproc.c1555
-rw-r--r--src/pmdas/hotproc/src/hotproc.h52
-rw-r--r--src/pmdas/hotproc/src/lex.l115
-rw-r--r--src/pmdas/hotproc/src/pcpu.c100
-rw-r--r--src/pmdas/hotproc/src/pcpu.h31
-rw-r--r--src/pmdas/hotproc/src/pglobal.c93
-rw-r--r--src/pmdas/hotproc/src/pglobal.h41
-rw-r--r--src/pmdas/hotproc/src/ppred_values.c163
-rw-r--r--src/pmdas/hotproc/src/ppred_values.h39
-rw-r--r--src/pmdas/infiniband/GNUmakefile58
-rwxr-xr-xsrc/pmdas/infiniband/Install46
-rwxr-xr-xsrc/pmdas/infiniband/Remove24
-rw-r--r--src/pmdas/infiniband/help190
-rw-r--r--src/pmdas/infiniband/ib.c1050
-rw-r--r--src/pmdas/infiniband/ibpmda.h125
-rw-r--r--src/pmdas/infiniband/pmda.c418
-rw-r--r--src/pmdas/infiniband/pmns100
-rw-r--r--src/pmdas/infiniband/root9
-rw-r--r--src/pmdas/jbd2/GNUmakefile73
-rwxr-xr-xsrc/pmdas/jbd2/Install31
-rwxr-xr-xsrc/pmdas/jbd2/Remove23
-rw-r--r--src/pmdas/jbd2/convert.h30
-rw-r--r--src/pmdas/jbd2/help115
-rw-r--r--src/pmdas/jbd2/pmda.c322
-rw-r--r--src/pmdas/jbd2/proc_jbd2.c148
-rw-r--r--src/pmdas/jbd2/proc_jbd2.h38
-rw-r--r--src/pmdas/jbd2/root6
-rw-r--r--src/pmdas/jbd2/root_jbd263
-rw-r--r--src/pmdas/kvm/GNUmakefile56
-rwxr-xr-xsrc/pmdas/kvm/Install40
-rwxr-xr-xsrc/pmdas/kvm/Remove25
-rw-r--r--src/pmdas/kvm/pmdakvm.pl122
-rw-r--r--src/pmdas/linux/GNUmakefile128
-rw-r--r--src/pmdas/linux/clusters.h82
-rw-r--r--src/pmdas/linux/convert.h49
-rw-r--r--src/pmdas/linux/devmapper.c86
-rw-r--r--src/pmdas/linux/devmapper.h29
-rw-r--r--src/pmdas/linux/filesys.c122
-rw-r--r--src/pmdas/linux/filesys.h32
-rw-r--r--src/pmdas/linux/getinfo.c93
-rw-r--r--src/pmdas/linux/getinfo.h16
-rw-r--r--src/pmdas/linux/help1122
-rw-r--r--src/pmdas/linux/indom.h69
-rw-r--r--src/pmdas/linux/interrupts.c394
-rw-r--r--src/pmdas/linux/interrupts.h19
-rw-r--r--src/pmdas/linux/linux_table.c116
-rw-r--r--src/pmdas/linux/linux_table.h66
-rw-r--r--src/pmdas/linux/msg_limits.c49
-rw-r--r--src/pmdas/linux/msg_limits.h35
-rw-r--r--src/pmdas/linux/numa_meminfo.c137
-rw-r--r--src/pmdas/linux/numa_meminfo.h32
-rw-r--r--src/pmdas/linux/pmda.c5807
-rw-r--r--src/pmdas/linux/proc_cpuinfo.c246
-rw-r--r--src/pmdas/linux/proc_cpuinfo.h49
-rw-r--r--src/pmdas/linux/proc_loadavg.c45
-rw-r--r--src/pmdas/linux/proc_loadavg.h29
-rw-r--r--src/pmdas/linux/proc_meminfo.c188
-rw-r--r--src/pmdas/linux/proc_meminfo.h79
-rw-r--r--src/pmdas/linux/proc_net_dev.c444
-rw-r--r--src/pmdas/linux/proc_net_dev.h100
-rw-r--r--src/pmdas/linux/proc_net_netstat.c354
-rw-r--r--src/pmdas/linux/proc_net_netstat.h150
-rw-r--r--src/pmdas/linux/proc_net_rpc.c188
-rw-r--r--src/pmdas/linux/proc_net_rpc.h99
-rw-r--r--src/pmdas/linux/proc_net_snmp.c367
-rw-r--r--src/pmdas/linux/proc_net_snmp.h136
-rw-r--r--src/pmdas/linux/proc_net_snmp_migrate.conf8
-rw-r--r--src/pmdas/linux/proc_net_sockstat.c65
-rw-r--r--src/pmdas/linux/proc_net_sockstat.h29
-rw-r--r--src/pmdas/linux/proc_net_tcp.c71
-rw-r--r--src/pmdas/linux/proc_net_tcp.h44
-rw-r--r--src/pmdas/linux/proc_partitions.c808
-rw-r--r--src/pmdas/linux/proc_partitions.h44
-rw-r--r--src/pmdas/linux/proc_scsi.c159
-rw-r--r--src/pmdas/linux/proc_scsi.h38
-rw-r--r--src/pmdas/linux/proc_slabinfo.c237
-rw-r--r--src/pmdas/linux/proc_slabinfo.h53
-rw-r--r--src/pmdas/linux/proc_stat.c304
-rw-r--r--src/pmdas/linux/proc_stat.h65
-rw-r--r--src/pmdas/linux/proc_sys_fs.c80
-rw-r--r--src/pmdas/linux/proc_sys_fs.h32
-rw-r--r--src/pmdas/linux/proc_uptime.c47
-rw-r--r--src/pmdas/linux/proc_uptime.h29
-rw-r--r--src/pmdas/linux/proc_vmstat.c299
-rw-r--r--src/pmdas/linux/proc_vmstat.h131
-rw-r--r--src/pmdas/linux/root_linux1005
-rw-r--r--src/pmdas/linux/sem_limits.c51
-rw-r--r--src/pmdas/linux/sem_limits.h49
-rw-r--r--src/pmdas/linux/shm_limits.c43
-rw-r--r--src/pmdas/linux/shm_limits.h32
-rw-r--r--src/pmdas/linux/swapdev.c72
-rw-r--r--src/pmdas/linux/swapdev.h28
-rw-r--r--src/pmdas/linux/sysfs_kernel.c41
-rw-r--r--src/pmdas/linux/sysfs_kernel.h34
-rw-r--r--src/pmdas/linux_proc/GNUmakefile89
-rwxr-xr-xsrc/pmdas/linux_proc/Install29
-rwxr-xr-xsrc/pmdas/linux_proc/Remove23
-rw-r--r--src/pmdas/linux_proc/cgroups.c1146
-rw-r--r--src/pmdas/linux_proc/cgroups.h74
-rw-r--r--src/pmdas/linux_proc/clusters.h48
-rw-r--r--src/pmdas/linux_proc/contexts.c238
-rw-r--r--src/pmdas/linux_proc/contexts.h57
-rw-r--r--src/pmdas/linux_proc/getinfo.c55
-rw-r--r--src/pmdas/linux_proc/getinfo.h16
-rw-r--r--src/pmdas/linux_proc/help220
-rw-r--r--src/pmdas/linux_proc/indom.h52
-rw-r--r--src/pmdas/linux_proc/ksym.c564
-rw-r--r--src/pmdas/linux_proc/ksym.h41
-rw-r--r--src/pmdas/linux_proc/linux_proc_migrate.conf55
-rw-r--r--src/pmdas/linux_proc/pmda.c1896
-rw-r--r--src/pmdas/linux_proc/proc_pid.c957
-rw-r--r--src/pmdas/linux_proc/proc_pid.h289
-rw-r--r--src/pmdas/linux_proc/proc_runq.c123
-rw-r--r--src/pmdas/linux_proc/proc_runq.h35
-rw-r--r--src/pmdas/linux_proc/root6
-rw-r--r--src/pmdas/linux_proc/root_proc181
-rw-r--r--src/pmdas/linux_xfs/GNUmakefile76
-rwxr-xr-xsrc/pmdas/linux_xfs/Install29
-rwxr-xr-xsrc/pmdas/linux_xfs/Remove23
-rw-r--r--src/pmdas/linux_xfs/clusters.h31
-rw-r--r--src/pmdas/linux_xfs/filesys.c183
-rw-r--r--src/pmdas/linux_xfs/filesys.h108
-rw-r--r--src/pmdas/linux_xfs/help469
-rw-r--r--src/pmdas/linux_xfs/indom.h31
-rw-r--r--src/pmdas/linux_xfs/linux_xfs_migrate.conf16
-rw-r--r--src/pmdas/linux_xfs/pmda.c979
-rw-r--r--src/pmdas/linux_xfs/proc_fs_xfs.c278
-rw-r--r--src/pmdas/linux_xfs/proc_fs_xfs.h189
-rw-r--r--src/pmdas/linux_xfs/root6
-rw-r--r--src/pmdas/linux_xfs/root_xfs295
-rw-r--r--src/pmdas/lmsensors/GNUmakefile61
-rwxr-xr-xsrc/pmdas/lmsensors/Install35
-rw-r--r--src/pmdas/lmsensors/README69
-rwxr-xr-xsrc/pmdas/lmsensors/Remove38
-rw-r--r--src/pmdas/lmsensors/help1
-rw-r--r--src/pmdas/lmsensors/lmsensors.c953
-rw-r--r--src/pmdas/lmsensors/lmsensors.h113
-rw-r--r--src/pmdas/lmsensors/pmns106
-rw-r--r--src/pmdas/lmsensors/root12
-rw-r--r--src/pmdas/logger/GNUmakefile56
-rw-r--r--src/pmdas/logger/Install164
-rw-r--r--src/pmdas/logger/README51
-rw-r--r--src/pmdas/logger/Remove24
-rw-r--r--src/pmdas/logger/event.c493
-rw-r--r--src/pmdas/logger/event.h56
-rw-r--r--src/pmdas/logger/help37
-rw-r--r--src/pmdas/logger/logger.c587
-rw-r--r--src/pmdas/logger/pmns23
-rw-r--r--src/pmdas/logger/root10
-rw-r--r--src/pmdas/logger/util.c181
-rw-r--r--src/pmdas/logger/util.h25
-rw-r--r--src/pmdas/lustrecomm/GNUmakefile63
-rwxr-xr-xsrc/pmdas/lustrecomm/Install27
-rw-r--r--src/pmdas/lustrecomm/README60
-rwxr-xr-xsrc/pmdas/lustrecomm/Remove38
-rw-r--r--src/pmdas/lustrecomm/TODO3
-rw-r--r--src/pmdas/lustrecomm/file_indexed.c96
-rw-r--r--src/pmdas/lustrecomm/file_single.c104
-rw-r--r--src/pmdas/lustrecomm/help106
-rw-r--r--src/pmdas/lustrecomm/libreadfiles.h59
-rw-r--r--src/pmdas/lustrecomm/lustrecomm.c304
-rw-r--r--src/pmdas/lustrecomm/pmns47
-rw-r--r--src/pmdas/lustrecomm/pmns.v487
-rw-r--r--src/pmdas/lustrecomm/refresh_file.c85
-rw-r--r--src/pmdas/lustrecomm/root10
-rw-r--r--src/pmdas/lustrecomm/timespec_routines.c48
-rw-r--r--src/pmdas/mailq/GNUmakefile49
-rw-r--r--src/pmdas/mailq/Install117
-rw-r--r--src/pmdas/mailq/README48
-rw-r--r--src/pmdas/mailq/Remove38
-rw-r--r--src/pmdas/mailq/help52
-rw-r--r--src/pmdas/mailq/mailq.c401
-rw-r--r--src/pmdas/mailq/pmlogconf.summary5
-rw-r--r--src/pmdas/mailq/pmns24
-rw-r--r--src/pmdas/mailq/root10
-rw-r--r--src/pmdas/memcache/GNUmakefile54
-rwxr-xr-xsrc/pmdas/memcache/Install28
-rwxr-xr-xsrc/pmdas/memcache/Remove28
-rwxr-xr-xsrc/pmdas/memcache/client.pl24
-rw-r--r--src/pmdas/memcache/pmdamemcache.pl307
-rw-r--r--src/pmdas/mmv/GNUmakefile62
-rw-r--r--src/pmdas/mmv/Makefile.demos33
-rw-r--r--src/pmdas/mmv/README.demos26
-rw-r--r--src/pmdas/mmv/acme.c125
-rw-r--r--src/pmdas/mmv/mmvdump.c346
-rw-r--r--src/pmdas/mmv/src/GNUmakefile59
-rwxr-xr-xsrc/pmdas/mmv/src/Install36
-rwxr-xr-xsrc/pmdas/mmv/src/Remove23
-rw-r--r--src/pmdas/mmv/src/mmv.c915
-rw-r--r--src/pmdas/mmv/src/root_mmv13
-rw-r--r--src/pmdas/mounts/GNUmakefile64
-rwxr-xr-xsrc/pmdas/mounts/Install30
-rw-r--r--src/pmdas/mounts/README72
-rwxr-xr-xsrc/pmdas/mounts/Remove41
-rw-r--r--src/pmdas/mounts/help47
-rw-r--r--src/pmdas/mounts/mounts.c384
-rw-r--r--src/pmdas/mounts/mounts.conf10
-rw-r--r--src/pmdas/mounts/pmns29
-rw-r--r--src/pmdas/mounts/root15
-rw-r--r--src/pmdas/mssql/GNUmakefile53
-rw-r--r--src/pmdas/mssql/Install34
-rw-r--r--src/pmdas/mssql/Remove29
-rw-r--r--src/pmdas/mssql/pmdamssql.pl315
-rw-r--r--src/pmdas/mysql/GNUmakefile52
-rwxr-xr-xsrc/pmdas/mysql/Install34
-rw-r--r--src/pmdas/mysql/README79
-rwxr-xr-xsrc/pmdas/mysql/Remove29
-rw-r--r--src/pmdas/mysql/migrate.conf23
-rw-r--r--src/pmdas/mysql/pmdamysql.pl1911
-rw-r--r--src/pmdas/mysql/pmlogconf.summary4
-rw-r--r--src/pmdas/named/GNUmakefile48
-rwxr-xr-xsrc/pmdas/named/Install37
-rwxr-xr-xsrc/pmdas/named/Remove25
-rw-r--r--src/pmdas/named/pmdanamed.pl190
-rw-r--r--src/pmdas/netbsd/GNUmakefile68
-rw-r--r--src/pmdas/netbsd/disk.c216
-rw-r--r--src/pmdas/netbsd/help95
-rw-r--r--src/pmdas/netbsd/netbsd.c981
-rw-r--r--src/pmdas/netbsd/netbsd.h44
-rw-r--r--src/pmdas/netbsd/netif.c233
-rw-r--r--src/pmdas/netbsd/root_netbsd172
-rw-r--r--src/pmdas/netfilter/GNUmakefile55
-rwxr-xr-xsrc/pmdas/netfilter/Install33
-rwxr-xr-xsrc/pmdas/netfilter/Remove25
-rw-r--r--src/pmdas/netfilter/pmdanetfilter.pl100
-rw-r--r--src/pmdas/netfilter/pmlogconf.config5
-rw-r--r--src/pmdas/netfilter/pmlogconf.summary4
-rw-r--r--src/pmdas/news/GNUmakefile53
-rw-r--r--src/pmdas/news/Install28
-rw-r--r--src/pmdas/news/README58
-rw-r--r--src/pmdas/news/Remove29
-rw-r--r--src/pmdas/news/active12
-rw-r--r--src/pmdas/news/pmdanews.pl196
-rw-r--r--src/pmdas/nfsclient/GNUmakefile49
-rwxr-xr-xsrc/pmdas/nfsclient/Install29
-rwxr-xr-xsrc/pmdas/nfsclient/Remove24
-rw-r--r--src/pmdas/nfsclient/pmdanfsclient.pl1181
-rw-r--r--src/pmdas/nginx/GNUmakefile55
-rwxr-xr-xsrc/pmdas/nginx/Install33
-rwxr-xr-xsrc/pmdas/nginx/Remove23
-rw-r--r--src/pmdas/nginx/nginx.conf2
-rwxr-xr-xsrc/pmdas/nginx/pmdanginx.pl146
-rw-r--r--src/pmdas/nvidia/GNUmakefile50
-rwxr-xr-xsrc/pmdas/nvidia/Install28
-rwxr-xr-xsrc/pmdas/nvidia/README7
-rwxr-xr-xsrc/pmdas/nvidia/Remove38
-rw-r--r--src/pmdas/nvidia/help72
-rw-r--r--src/pmdas/nvidia/localnvml.c270
-rw-r--r--src/pmdas/nvidia/localnvml.h89
-rw-r--r--src/pmdas/nvidia/nvidia.c391
-rw-r--r--src/pmdas/nvidia/pmns30
-rw-r--r--src/pmdas/nvidia/root10
-rw-r--r--src/pmdas/oracle/GNUmakefile49
-rwxr-xr-xsrc/pmdas/oracle/Install63
-rwxr-xr-xsrc/pmdas/oracle/Remove25
-rw-r--r--src/pmdas/oracle/pmdaoracle.pl2364
-rw-r--r--src/pmdas/papi/GNUmakefile59
-rw-r--r--src/pmdas/papi/Install27
-rw-r--r--src/pmdas/papi/README54
-rwxr-xr-xsrc/pmdas/papi/Remove38
-rw-r--r--src/pmdas/papi/help74
-rw-r--r--src/pmdas/papi/papi.c1497
-rw-r--r--src/pmdas/papi/pmns139
-rw-r--r--src/pmdas/papi/root9
-rw-r--r--src/pmdas/pdns/GNUmakefile48
-rw-r--r--src/pmdas/pdns/Install28
-rw-r--r--src/pmdas/pdns/Remove25
-rw-r--r--src/pmdas/pdns/pmdapdns.pl395
-rw-r--r--src/pmdas/pmcd/GNUmakefile55
-rw-r--r--src/pmdas/pmcd/help533
-rw-r--r--src/pmdas/pmcd/root_pmcd153
-rw-r--r--src/pmdas/pmcd/src/GNUmakefile68
-rwxr-xr-xsrc/pmdas/pmcd/src/objstyle88
-rw-r--r--src/pmdas/pmcd/src/pmcd.c1869
-rw-r--r--src/pmdas/postfix/GNUmakefile48
-rw-r--r--src/pmdas/postfix/Install34
-rw-r--r--src/pmdas/postfix/Remove25
-rw-r--r--src/pmdas/postfix/pmdapostfix.pl266
-rw-r--r--src/pmdas/postgresql/GNUmakefile51
-rwxr-xr-xsrc/pmdas/postgresql/Install40
-rwxr-xr-xsrc/pmdas/postgresql/Remove25
-rw-r--r--src/pmdas/postgresql/pmdapostgresql.pl1546
-rw-r--r--src/pmdas/postgresql/pmlogconf.summary8
-rw-r--r--src/pmdas/process/GNUmakefile65
-rwxr-xr-xsrc/pmdas/process/Install28
-rw-r--r--src/pmdas/process/README74
-rwxr-xr-xsrc/pmdas/process/Remove41
-rw-r--r--src/pmdas/process/help38
-rw-r--r--src/pmdas/process/pmns26
-rw-r--r--src/pmdas/process/process.c413
-rw-r--r--src/pmdas/process/process.conf11
-rw-r--r--src/pmdas/process/root16
-rw-r--r--src/pmdas/roomtemp/GNUmakefile66
-rw-r--r--src/pmdas/roomtemp/Install55
-rw-r--r--src/pmdas/roomtemp/README74
-rw-r--r--src/pmdas/roomtemp/Remove38
-rw-r--r--src/pmdas/roomtemp/dsread.c117
-rw-r--r--src/pmdas/roomtemp/dsread.h4
-rw-r--r--src/pmdas/roomtemp/help41
-rw-r--r--src/pmdas/roomtemp/mlan/GNUmakefile45
-rw-r--r--src/pmdas/roomtemp/mlan/ds2480.h183
-rw-r--r--src/pmdas/roomtemp/mlan/ds2480ut.c206
-rw-r--r--src/pmdas/roomtemp/mlan/linuxlnk.c443
-rw-r--r--src/pmdas/roomtemp/mlan/mlan.h94
-rw-r--r--src/pmdas/roomtemp/mlan/mlanllu.c499
-rw-r--r--src/pmdas/roomtemp/mlan/mlannetu.c599
-rw-r--r--src/pmdas/roomtemp/mlan/mlansesu.c102
-rw-r--r--src/pmdas/roomtemp/mlan/mlantrnu.c579
-rw-r--r--src/pmdas/roomtemp/pmns24
-rw-r--r--src/pmdas/roomtemp/roomtemp.c211
-rw-r--r--src/pmdas/roomtemp/root10
-rw-r--r--src/pmdas/rpm/GNUmakefile67
-rw-r--r--src/pmdas/rpm/Install31
-rw-r--r--src/pmdas/rpm/Remove25
-rw-r--r--src/pmdas/rpm/help57
-rw-r--r--src/pmdas/rpm/migrate.conf8
-rw-r--r--src/pmdas/rpm/pmns38
-rw-r--r--src/pmdas/rpm/root10
-rw-r--r--src/pmdas/rpm/rpm.c704
-rw-r--r--src/pmdas/rpm/rpm.h111
-rw-r--r--src/pmdas/rpm/timer.c45
-rw-r--r--src/pmdas/rpm/timer.h24
-rw-r--r--src/pmdas/rsyslog/GNUmakefile48
-rwxr-xr-xsrc/pmdas/rsyslog/Install44
-rwxr-xr-xsrc/pmdas/rsyslog/Remove25
-rw-r--r--src/pmdas/rsyslog/pmdarsyslog.pl249
-rw-r--r--src/pmdas/samba/GNUmakefile52
-rwxr-xr-xsrc/pmdas/samba/Install43
-rwxr-xr-xsrc/pmdas/samba/Remove25
-rw-r--r--src/pmdas/samba/pmdasamba.pl198
-rw-r--r--src/pmdas/sample/GNUmakefile40
-rwxr-xr-xsrc/pmdas/sample/Install74
-rw-r--r--src/pmdas/sample/README63
-rwxr-xr-xsrc/pmdas/sample/Remove45
-rw-r--r--src/pmdas/sample/Sample.pmchart10
-rw-r--r--src/pmdas/sample/domain.h4
-rwxr-xr-xsrc/pmdas/sample/get_next_pmid48
-rw-r--r--src/pmdas/sample/help602
-rw-r--r--src/pmdas/sample/pmns260
-rw-r--r--src/pmdas/sample/root20
-rw-r--r--src/pmdas/sample/src/GNUmakefile62
-rw-r--r--src/pmdas/sample/src/GNUmakefile.install53
-rw-r--r--src/pmdas/sample/src/events.c477
-rw-r--r--src/pmdas/sample/src/events.h33
-rw-r--r--src/pmdas/sample/src/percontext.c256
-rw-r--r--src/pmdas/sample/src/percontext.h31
-rw-r--r--src/pmdas/sample/src/pmda.c126
-rw-r--r--src/pmdas/sample/src/sample.c2748
-rw-r--r--src/pmdas/sendmail/GNUmakefile58
-rw-r--r--src/pmdas/sendmail/Install27
-rw-r--r--src/pmdas/sendmail/README50
-rw-r--r--src/pmdas/sendmail/Remove38
-rw-r--r--src/pmdas/sendmail/Sendmail.pmchart10
-rw-r--r--src/pmdas/sendmail/help73
-rw-r--r--src/pmdas/sendmail/pmns39
-rw-r--r--src/pmdas/sendmail/root10
-rw-r--r--src/pmdas/sendmail/sendmail.c524
-rw-r--r--src/pmdas/shping/GNUmakefile69
-rwxr-xr-xsrc/pmdas/shping/Install222
-rw-r--r--src/pmdas/shping/README91
-rw-r--r--src/pmdas/shping/Remove38
-rw-r--r--src/pmdas/shping/help137
-rw-r--r--src/pmdas/shping/pmda.c257
-rw-r--r--src/pmdas/shping/pmlogconf.summary6
-rw-r--r--src/pmdas/shping/pmns41
-rw-r--r--src/pmdas/shping/root9
-rw-r--r--src/pmdas/shping/sample.conf46
-rw-r--r--src/pmdas/shping/shping.CPUTime.pmchart11
-rw-r--r--src/pmdas/shping/shping.RealTime.pmchart9
-rw-r--r--src/pmdas/shping/shping.c679
-rw-r--r--src/pmdas/shping/shping.h54
-rw-r--r--src/pmdas/shping/shping.response.pmie67
-rw-r--r--src/pmdas/shping/shping.status.pmie63
-rw-r--r--src/pmdas/simple/GNUmakefile46
-rw-r--r--src/pmdas/simple/GNUmakefile.install53
-rw-r--r--src/pmdas/simple/Install53
-rw-r--r--src/pmdas/simple/README67
-rw-r--r--src/pmdas/simple/Remove25
-rw-r--r--src/pmdas/simple/help82
-rw-r--r--src/pmdas/simple/pmdasimple.perl158
-rw-r--r--src/pmdas/simple/pmdasimple.python244
-rw-r--r--src/pmdas/simple/pmns27
-rw-r--r--src/pmdas/simple/root10
-rw-r--r--src/pmdas/simple/simple.c517
-rw-r--r--src/pmdas/simple/simple.conf1
-rw-r--r--src/pmdas/snmp/GNUmakefile53
-rwxr-xr-xsrc/pmdas/snmp/Install28
-rw-r--r--src/pmdas/snmp/README52
-rwxr-xr-xsrc/pmdas/snmp/Remove29
-rwxr-xr-xsrc/pmdas/snmp/pmdasnmp.pl413
-rw-r--r--src/pmdas/snmp/snmp.conf44
-rw-r--r--src/pmdas/solaris/GNUmakefile82
-rw-r--r--src/pmdas/solaris/arcstats.c55
-rw-r--r--src/pmdas/solaris/clusters.h20
-rw-r--r--src/pmdas/solaris/common.h137
-rw-r--r--src/pmdas/solaris/data.c1462
-rw-r--r--src/pmdas/solaris/disk.c420
-rw-r--r--src/pmdas/solaris/help729
-rw-r--r--src/pmdas/solaris/kvm.c59
-rw-r--r--src/pmdas/solaris/netlink.c125
-rw-r--r--src/pmdas/solaris/netmib2.c329
-rw-r--r--src/pmdas/solaris/netmib2.h54
-rw-r--r--src/pmdas/solaris/pmns.disk58
-rw-r--r--src/pmdas/solaris/pmns.hinv34
-rw-r--r--src/pmdas/solaris/pmns.kernel200
-rw-r--r--src/pmdas/solaris/pmns.mem88
-rw-r--r--src/pmdas/solaris/pmns.network302
-rw-r--r--src/pmdas/solaris/pmns.zfs60
-rw-r--r--src/pmdas/solaris/pmns.zpool31
-rw-r--r--src/pmdas/solaris/pmns.zpool_perdisk16
-rw-r--r--src/pmdas/solaris/root42
-rw-r--r--src/pmdas/solaris/solaris.c216
-rw-r--r--src/pmdas/solaris/sysinfo.c376
-rw-r--r--src/pmdas/solaris/vnops.c221
-rw-r--r--src/pmdas/solaris/zfs.c171
-rw-r--r--src/pmdas/solaris/zpool.c154
-rw-r--r--src/pmdas/solaris/zpool_perdisk.c289
-rw-r--r--src/pmdas/summary/GNUmakefile62
-rw-r--r--src/pmdas/summary/Install51
-rw-r--r--src/pmdas/summary/README255
-rw-r--r--src/pmdas/summary/Remove38
-rw-r--r--src/pmdas/summary/help56
-rw-r--r--src/pmdas/summary/mainloop.c225
-rw-r--r--src/pmdas/summary/pmda.c152
-rw-r--r--src/pmdas/summary/pmns24
-rw-r--r--src/pmdas/summary/root10
-rw-r--r--src/pmdas/summary/summary.c300
-rw-r--r--src/pmdas/summary/summary.h34
-rw-r--r--src/pmdas/summary/summary.pmie40
-rw-r--r--src/pmdas/systemd/GNUmakefile59
-rwxr-xr-xsrc/pmdas/systemd/Install29
-rw-r--r--src/pmdas/systemd/README64
-rwxr-xr-xsrc/pmdas/systemd/Remove24
-rw-r--r--src/pmdas/systemd/help52
-rw-r--r--src/pmdas/systemd/pmns35
-rw-r--r--src/pmdas/systemd/root11
-rw-r--r--src/pmdas/systemd/systemd.c791
-rw-r--r--src/pmdas/systemtap/GNUmakefile56
-rwxr-xr-xsrc/pmdas/systemtap/Install32
-rw-r--r--src/pmdas/systemtap/README59
-rwxr-xr-xsrc/pmdas/systemtap/Remove29
-rw-r--r--src/pmdas/systemtap/pmdasystemtap.pl168
-rw-r--r--src/pmdas/systemtap/probes.stp6
-rw-r--r--src/pmdas/trace/GNUmakefile109
-rw-r--r--src/pmdas/trace/GNUmakefile.stub69
-rw-r--r--src/pmdas/trace/Install275
-rw-r--r--src/pmdas/trace/Makefile.proto69
-rw-r--r--src/pmdas/trace/README62
-rw-r--r--src/pmdas/trace/README.demos71
-rw-r--r--src/pmdas/trace/Remove38
-rw-r--r--src/pmdas/trace/app1.c97
-rw-r--r--src/pmdas/trace/app2.c163
-rw-r--r--src/pmdas/trace/app3.c166
-rw-r--r--src/pmdas/trace/fapp1.f102
-rw-r--r--src/pmdas/trace/help156
-rw-r--r--src/pmdas/trace/japp1.java46
-rw-r--r--src/pmdas/trace/pmns62
-rw-r--r--src/pmdas/trace/root10
-rw-r--r--src/pmdas/trace/src/GNUmakefile57
-rw-r--r--src/pmdas/trace/src/client.c155
-rw-r--r--src/pmdas/trace/src/client.h38
-rw-r--r--src/pmdas/trace/src/comms.c289
-rw-r--r--src/pmdas/trace/src/comms.h26
-rw-r--r--src/pmdas/trace/src/data.c122
-rw-r--r--src/pmdas/trace/src/data.h68
-rw-r--r--src/pmdas/trace/src/pmda.c228
-rw-r--r--src/pmdas/trace/src/trace.c1151
-rw-r--r--src/pmdas/trace/stub.c136
-rw-r--r--src/pmdas/trivial/GNUmakefile43
-rw-r--r--src/pmdas/trivial/GNUmakefile.install51
-rw-r--r--src/pmdas/trivial/Install27
-rw-r--r--src/pmdas/trivial/README63
-rw-r--r--src/pmdas/trivial/Remove38
-rw-r--r--src/pmdas/trivial/help35
-rw-r--r--src/pmdas/trivial/pmns23
-rw-r--r--src/pmdas/trivial/root10
-rw-r--r--src/pmdas/trivial/trivial.c138
-rw-r--r--src/pmdas/txmon/GNUmakefile68
-rw-r--r--src/pmdas/txmon/GNUmakefile.install57
-rwxr-xr-xsrc/pmdas/txmon/Install56
-rw-r--r--src/pmdas/txmon/README77
-rw-r--r--src/pmdas/txmon/Remove38
-rwxr-xr-xsrc/pmdas/txmon/genload120
-rw-r--r--src/pmdas/txmon/help62
-rw-r--r--src/pmdas/txmon/pmns32
-rw-r--r--src/pmdas/txmon/root10
-rw-r--r--src/pmdas/txmon/txmon.c383
-rw-r--r--src/pmdas/txmon/txmon.h53
-rw-r--r--src/pmdas/txmon/txrecord.c108
-rw-r--r--src/pmdas/vmware/GNUmakefile54
-rwxr-xr-xsrc/pmdas/vmware/Install34
-rwxr-xr-xsrc/pmdas/vmware/Remove29
-rw-r--r--src/pmdas/vmware/pmdavmware.pl456
-rw-r--r--src/pmdas/weblog/GNUmakefile75
-rw-r--r--src/pmdas/weblog/Install694
-rw-r--r--src/pmdas/weblog/README205
-rw-r--r--src/pmdas/weblog/Remove43
-rw-r--r--src/pmdas/weblog/Web.Alarms.pmchart22
-rwxr-xr-xsrc/pmdas/weblog/Web.Allservers.pmchart90
-rwxr-xr-xsrc/pmdas/weblog/Web.Perserver.Bytes.pmchart90
-rwxr-xr-xsrc/pmdas/weblog/Web.Perserver.Requests.pmchart91
-rw-r--r--src/pmdas/weblog/Web.Requests.pmchart27
-rw-r--r--src/pmdas/weblog/Web.Volume.pmchart25
-rw-r--r--src/pmdas/weblog/check_match.c414
-rw-r--r--src/pmdas/weblog/help654
-rw-r--r--src/pmdas/weblog/pmda.c1205
-rw-r--r--src/pmdas/weblog/pmns306
-rw-r--r--src/pmdas/weblog/root10
-rwxr-xr-xsrc/pmdas/weblog/server.sh1228
-rw-r--r--src/pmdas/weblog/sproc.c39
-rw-r--r--src/pmdas/weblog/weblog.c3132
-rw-r--r--src/pmdas/weblog/weblog.h140
-rwxr-xr-xsrc/pmdas/weblog/weblogconv.sh62
-rw-r--r--src/pmdas/windows/GNUmakefile81
-rw-r--r--src/pmdas/windows/README171
-rw-r--r--src/pmdas/windows/error.c130
-rw-r--r--src/pmdas/windows/fetch.c209
-rw-r--r--src/pmdas/windows/help65
-rw-r--r--src/pmdas/windows/helptext.c84
-rw-r--r--src/pmdas/windows/hypnotoad.h104
-rw-r--r--src/pmdas/windows/instance.c376
-rw-r--r--src/pmdas/windows/open.c769
-rw-r--r--src/pmdas/windows/pdhlist.c85
-rw-r--r--src/pmdas/windows/pdhmatch.sh192
-rw-r--r--src/pmdas/windows/pmda.c1601
-rw-r--r--src/pmdas/windows/pmns.disk52
-rw-r--r--src/pmdas/windows/pmns.filesys6
-rw-r--r--src/pmdas/windows/pmns.hinv7
-rw-r--r--src/pmdas/windows/pmns.kernel48
-rw-r--r--src/pmdas/windows/pmns.mem61
-rw-r--r--src/pmdas/windows/pmns.network42
-rw-r--r--src/pmdas/windows/pmns.pmda4
-rw-r--r--src/pmdas/windows/pmns.process59
-rw-r--r--src/pmdas/windows/pmns.sqlserver147
-rw-r--r--src/pmdas/windows/root29
-rw-r--r--src/pmdas/zimbra/GNUmakefile51
-rwxr-xr-xsrc/pmdas/zimbra/Install33
-rwxr-xr-xsrc/pmdas/zimbra/Remove25
-rw-r--r--src/pmdas/zimbra/pmdazimbra.pl905
-rw-r--r--src/pmdas/zimbra/pmlogconf.all4
-rwxr-xr-xsrc/pmdas/zimbra/zimbraprobe.sh26
-rw-r--r--src/pmdas/zswap/GNUmakefile37
-rw-r--r--src/pmdas/zswap/Install28
-rw-r--r--src/pmdas/zswap/Remove25
-rw-r--r--src/pmdas/zswap/pmdazswap.python189
-rw-r--r--src/pmdate/GNUmakefile31
-rw-r--r--src/pmdate/pmdate.c152
-rw-r--r--src/pmdbg/GNUmakefile31
-rw-r--r--src/pmdbg/pmdbg.c161
-rw-r--r--src/pmdumplog/GNUmakefile31
-rw-r--r--src/pmdumplog/pmdumplog.c901
-rw-r--r--src/pmdumptext/GNUmakefile36
-rw-r--r--src/pmdumptext/pmdumptext.cpp1262
-rw-r--r--src/pmdumptext/pmdumptext.pro11
-rw-r--r--src/pmerr/GNUmakefile31
-rw-r--r--src/pmerr/pmerr.c82
-rw-r--r--src/pmevent/GNUmakefile35
-rw-r--r--src/pmevent/doargs.c517
-rw-r--r--src/pmevent/pmevent.c531
-rw-r--r--src/pmevent/pmevent.h56
-rw-r--r--src/pmfind/GNUmakefile31
-rw-r--r--src/pmfind/pmfind.c228
-rw-r--r--src/pmgadgets/GNUmakefile71
-rw-r--r--src/pmgadgets/global.h27
-rw-r--r--src/pmgadgets/lex.l133
-rw-r--r--src/pmgadgets/main.cpp56
-rw-r--r--src/pmgadgets/parse.cpp1339
-rw-r--r--src/pmgadgets/pmgadgets-args.sh381
-rw-r--r--src/pmgadgets/pmgadgets.cpp55
-rw-r--r--src/pmgadgets/pmgadgets.h40
-rw-r--r--src/pmgadgets/pmgadgets.info.in18
-rw-r--r--src/pmgadgets/pmgadgets.pro23
-rw-r--r--src/pmgadgets/pmgadgets.qrc6
-rw-r--r--src/pmgadgets/pmgadgets.sh.IN2
-rwxr-xr-xsrc/pmgadgets/pmgcisco.sh251
-rwxr-xr-xsrc/pmgadgets/pmgcluster.sh281
-rwxr-xr-xsrc/pmgadgets/pmgshping.sh242
-rwxr-xr-xsrc/pmgadgets/pmgsys.py380
-rw-r--r--src/pmgadgets/tokens.h60
-rw-r--r--src/pmgenmap/GNUmakefile34
-rwxr-xr-xsrc/pmgenmap/pmgenmap.sh103
-rw-r--r--src/pmgetopt/GNUmakefile31
-rw-r--r--src/pmgetopt/pmgetopt.c475
-rw-r--r--src/pmhostname/GNUmakefile31
-rw-r--r--src/pmhostname/pmhostname.c67
-rw-r--r--src/pmie/GNUmakefile68
-rw-r--r--src/pmie/config.default20
-rw-r--r--src/pmie/control46
-rw-r--r--src/pmie/crontab.in8
-rw-r--r--src/pmie/examples/GNUmakefile91
-rw-r--r--src/pmie/examples/README27
-rw-r--r--src/pmie/examples/cpu.009
-rw-r--r--src/pmie/examples/cpu.0115
-rw-r--r--src/pmie/examples/cpu.0210
-rw-r--r--src/pmie/examples/cpu.head12
-rw-r--r--src/pmie/examples/disk.0028
-rw-r--r--src/pmie/examples/disk.1023
-rw-r--r--src/pmie/examples/disk.2013
-rw-r--r--src/pmie/examples/disk.head11
-rw-r--r--src/pmie/examples/environ.0018
-rw-r--r--src/pmie/examples/environ.head17
-rw-r--r--src/pmie/examples/filesys.0014
-rw-r--r--src/pmie/examples/filesys.1014
-rw-r--r--src/pmie/examples/filesys.2014
-rw-r--r--src/pmie/examples/filesys.head10
-rw-r--r--src/pmie/examples/network.0010
-rw-r--r--src/pmie/examples/network.head15
-rw-r--r--src/pmie/examples/ras.0011
-rw-r--r--src/pmie/examples/ras.head5
-rw-r--r--src/pmie/examples/swap.0023
-rw-r--r--src/pmie/examples/swap.head13
-rw-r--r--src/pmie/examples/uag.005
-rw-r--r--src/pmie/examples/uag.014
-rw-r--r--src/pmie/examples/uag.028
-rw-r--r--src/pmie/examples/uag.037
-rw-r--r--src/pmie/examples/uag.0452
-rw-r--r--src/pmie/examples/uag.1032
-rw-r--r--src/pmie/examples/uag.117
-rw-r--r--src/pmie/examples/uag.1212
-rw-r--r--src/pmie/examples/uag.1333
-rw-r--r--src/pmie/examples/uag.207
-rw-r--r--src/pmie/examples/uag.219
-rw-r--r--src/pmie/examples/uag.3017
-rw-r--r--src/pmie/examples/uag.head3
-rw-r--r--src/pmie/examples/upm.006
-rw-r--r--src/pmie/examples/upm.014
-rw-r--r--src/pmie/examples/upm.0219
-rw-r--r--src/pmie/examples/upm.038
-rw-r--r--src/pmie/examples/upm.0410
-rw-r--r--src/pmie/examples/upm.059
-rw-r--r--src/pmie/examples/upm.0610
-rw-r--r--src/pmie/examples/upm.078
-rw-r--r--src/pmie/examples/upm.0813
-rw-r--r--src/pmie/examples/upm.0911
-rw-r--r--src/pmie/examples/upm.1011
-rw-r--r--src/pmie/examples/upm.head5
-rw-r--r--src/pmie/examples/webreport.008
-rw-r--r--src/pmie/examples/webreport.018
-rw-r--r--src/pmie/examples/webreport.head11
-rw-r--r--src/pmie/pmie.service.in13
-rwxr-xr-xsrc/pmie/pmie2col.sh133
-rw-r--r--src/pmie/pmie_check.sh691
-rw-r--r--src/pmie/pmie_daily.sh540
-rw-r--r--src/pmie/rc_pmie273
-rw-r--r--src/pmie/src/GNUmakefile79
-rw-r--r--src/pmie/src/README.DEBUG19
-rw-r--r--src/pmie/src/act.sk376
-rw-r--r--src/pmie/src/aggregate.sk176
-rw-r--r--src/pmie/src/andor.c457
-rw-r--r--src/pmie/src/andor.h34
-rw-r--r--src/pmie/src/binary.sk179
-rw-r--r--src/pmie/src/dstruct.c1307
-rw-r--r--src/pmie/src/dstruct.h461
-rw-r--r--src/pmie/src/eval.c808
-rw-r--r--src/pmie/src/eval.h46
-rw-r--r--src/pmie/src/fetch.sk460
-rw-r--r--src/pmie/src/fun.h124
-rw-r--r--src/pmie/src/grammar.y666
-rw-r--r--src/pmie/src/hdr.sk37
-rw-r--r--src/pmie/src/lexicon.c925
-rw-r--r--src/pmie/src/lexicon.h71
-rw-r--r--src/pmie/src/logger.h83
-rw-r--r--src/pmie/src/match_inst.c135
-rw-r--r--src/pmie/src/merge.sk103
-rwxr-xr-xsrc/pmie/src/meta299
-rw-r--r--src/pmie/src/misc.sk176
-rw-r--r--src/pmie/src/pmie.c942
-rw-r--r--src/pmie/src/pragmatics.c1226
-rw-r--r--src/pmie/src/pragmatics.h103
-rw-r--r--src/pmie/src/show.c1104
-rw-r--r--src/pmie/src/show.h32
-rw-r--r--src/pmie/src/stats.h38
-rw-r--r--src/pmie/src/stomp.c395
-rw-r--r--src/pmie/src/stomp.h26
-rw-r--r--src/pmie/src/symbol.c264
-rw-r--r--src/pmie/src/symbol.h93
-rw-r--r--src/pmie/src/syntax.c781
-rw-r--r--src/pmie/src/syntax.h97
-rw-r--r--src/pmie/src/systemlog.c198
-rw-r--r--src/pmie/src/systemlog.h1
-rw-r--r--src/pmie/src/unary.sk91
-rw-r--r--src/pmie/stomp11
-rw-r--r--src/pmieconf/GNUmakefile81
-rw-r--r--src/pmieconf/GNUmakefile.rules55
-rwxr-xr-xsrc/pmieconf/check-rules82
-rw-r--r--src/pmieconf/cpu/context_switch59
-rw-r--r--src/pmieconf/cpu/excess_fpe73
-rw-r--r--src/pmieconf/cpu/load_average73
-rw-r--r--src/pmieconf/cpu/localdefs49
-rw-r--r--src/pmieconf/cpu/low_util69
-rw-r--r--src/pmieconf/cpu/syscall69
-rw-r--r--src/pmieconf/cpu/system73
-rw-r--r--src/pmieconf/cpu/util62
-rw-r--r--src/pmieconf/filesys/buffer_cache80
-rw-r--r--src/pmieconf/filesys/dnlc_miss72
-rw-r--r--src/pmieconf/filesys/filling99
-rw-r--r--src/pmieconf/filesys/localdefs51
-rw-r--r--src/pmieconf/global/enln_actions42
-rw-r--r--src/pmieconf/global/localdefs7
-rw-r--r--src/pmieconf/global/ov_actions53
-rw-r--r--src/pmieconf/global/parameters35
-rw-r--r--src/pmieconf/global/pcp_actions89
-rw-r--r--src/pmieconf/global/tngfw_actions45
-rw-r--r--src/pmieconf/global/web_parameters35
-rw-r--r--src/pmieconf/io.c229
-rw-r--r--src/pmieconf/memory/exhausted81
-rw-r--r--src/pmieconf/memory/localdefs29
-rw-r--r--src/pmieconf/memory/swap_low78
-rw-r--r--src/pmieconf/pcp_web21
-rw-r--r--src/pmieconf/percpu/context_switch70
-rw-r--r--src/pmieconf/percpu/localdefs45
-rw-r--r--src/pmieconf/percpu/many_util85
-rw-r--r--src/pmieconf/percpu/some_util83
-rw-r--r--src/pmieconf/percpu/syscall80
-rw-r--r--src/pmieconf/percpu/system84
-rw-r--r--src/pmieconf/pernetif/collisions76
-rw-r--r--src/pmieconf/pernetif/errors72
-rw-r--r--src/pmieconf/pernetif/localdefs22
-rw-r--r--src/pmieconf/pernetif/packets83
-rw-r--r--src/pmieconf/pernetif/util75
-rwxr-xr-xsrc/pmieconf/pmie_email65
-rw-r--r--src/pmieconf/pmieconf.c852
-rw-r--r--src/pmieconf/rate-syscalls.c140
-rw-r--r--src/pmieconf/rules.c2335
-rw-r--r--src/pmieconf/rules.h154
-rw-r--r--src/pmieconf/web/errors60
-rw-r--r--src/pmieconf/web/high_requests50
-rw-r--r--src/pmieconf/web/low_requests50
-rw-r--r--src/pmieconf/webping/connect_errors50
-rw-r--r--src/pmieconf/webping/html_errors50
-rw-r--r--src/pmieconf/webping/http_errors50
-rw-r--r--src/pmieconf/webping/no_response56
-rw-r--r--src/pmieconf/webping/other_errors51
-rw-r--r--src/pmieconf/webping/slow_response56
-rwxr-xr-xsrc/pmieconf/xtractnames70
-rw-r--r--src/pmiestatus/GNUmakefile34
-rw-r--r--src/pmiestatus/pmiestatus.c64
-rw-r--r--src/pminfo/GNUmakefile31
-rw-r--r--src/pminfo/pminfo.c684
-rw-r--r--src/pmiostat/GNUmakefile29
-rwxr-xr-xsrc/pmiostat/pmiostat.py210
-rw-r--r--src/pmlc/GNUmakefile46
-rw-r--r--src/pmlc/actions.c868
-rw-r--r--src/pmlc/gram.y336
-rw-r--r--src/pmlc/lex.l268
-rw-r--r--src/pmlc/pmlc.c398
-rw-r--r--src/pmlc/pmlc.h95
-rw-r--r--src/pmlc/util.c546
-rw-r--r--src/pmlock/GNUmakefile36
-rw-r--r--src/pmlock/pmlock.c50
-rw-r--r--src/pmlogcheck/GNUmakefile31
-rw-r--r--src/pmlogcheck/RFC64
-rw-r--r--src/pmlogcheck/TODO11
-rw-r--r--src/pmlogcheck/pmlogcheck.c1117
-rw-r--r--src/pmlogconf/GNUmakefile50
-rw-r--r--src/pmlogconf/GNUmakefile.groups38
-rw-r--r--src/pmlogconf/cpu/localdefs1
-rw-r--r--src/pmlogconf/cpu/percpu4
-rw-r--r--src/pmlogconf/cpu/summary4
-rw-r--r--src/pmlogconf/disk/localdefs1
-rw-r--r--src/pmlogconf/disk/percontroller9
-rw-r--r--src/pmlogconf/disk/perdisk10
-rw-r--r--src/pmlogconf/disk/perpartition7
-rw-r--r--src/pmlogconf/disk/summary10
-rw-r--r--src/pmlogconf/filesystem/all4
-rw-r--r--src/pmlogconf/filesystem/localdefs1
-rw-r--r--src/pmlogconf/filesystem/summary8
-rw-r--r--src/pmlogconf/filesystem/xfs-all4
-rw-r--r--src/pmlogconf/filesystem/xfs-io-irix8
-rw-r--r--src/pmlogconf/filesystem/xfs-io-linux11
-rw-r--r--src/pmlogconf/kernel/bufcache-activity10
-rw-r--r--src/pmlogconf/kernel/bufcache-all4
-rw-r--r--src/pmlogconf/kernel/inode-cache7
-rw-r--r--src/pmlogconf/kernel/interrupts-irix8
-rw-r--r--src/pmlogconf/kernel/load4
-rw-r--r--src/pmlogconf/kernel/localdefs1
-rw-r--r--src/pmlogconf/kernel/memory-irix31
-rw-r--r--src/pmlogconf/kernel/memory-linux4
-rw-r--r--src/pmlogconf/kernel/queues-irix7
-rw-r--r--src/pmlogconf/kernel/read-write-data5
-rw-r--r--src/pmlogconf/kernel/summary-linux17
-rw-r--r--src/pmlogconf/kernel/summary-windows15
-rw-r--r--src/pmlogconf/kernel/syscalls-irix12
-rw-r--r--src/pmlogconf/kernel/syscalls-linux5
-rw-r--r--src/pmlogconf/kernel/syscalls-percpu-irix12
-rw-r--r--src/pmlogconf/kernel/vnodes4
-rw-r--r--src/pmlogconf/memory/localdefs1
-rw-r--r--src/pmlogconf/memory/proc-linux8
-rw-r--r--src/pmlogconf/memory/swap-activity5
-rw-r--r--src/pmlogconf/memory/swap-all4
-rw-r--r--src/pmlogconf/memory/swap-config4
-rw-r--r--src/pmlogconf/memory/tlb-irix4
-rw-r--r--src/pmlogconf/networking/interface-all4
-rw-r--r--src/pmlogconf/networking/interface-summary10
-rw-r--r--src/pmlogconf/networking/localdefs1
-rw-r--r--src/pmlogconf/networking/mbufs4
-rw-r--r--src/pmlogconf/networking/multicast4
-rw-r--r--src/pmlogconf/networking/nfs2-client4
-rw-r--r--src/pmlogconf/networking/nfs2-server4
-rw-r--r--src/pmlogconf/networking/nfs3-client4
-rw-r--r--src/pmlogconf/networking/nfs3-server4
-rw-r--r--src/pmlogconf/networking/nfs4-client4
-rw-r--r--src/pmlogconf/networking/nfs4-server4
-rw-r--r--src/pmlogconf/networking/other-protocols7
-rw-r--r--src/pmlogconf/networking/rpc4
-rw-r--r--src/pmlogconf/networking/socket-irix4
-rw-r--r--src/pmlogconf/networking/socket-linux4
-rw-r--r--src/pmlogconf/networking/streams5
-rw-r--r--src/pmlogconf/networking/tcp-activity-irix18
-rw-r--r--src/pmlogconf/networking/tcp-activity-linux7
-rw-r--r--src/pmlogconf/networking/tcp-all4
-rw-r--r--src/pmlogconf/networking/udp-all4
-rw-r--r--src/pmlogconf/networking/udp-packets-irix5
-rw-r--r--src/pmlogconf/networking/udp-packets-linux5
-rw-r--r--src/pmlogconf/platform/hinv9
-rw-r--r--src/pmlogconf/platform/linux11
-rw-r--r--src/pmlogconf/platform/localdefs1
-rwxr-xr-xsrc/pmlogconf/pmlogconf-setup.sh404
-rwxr-xr-xsrc/pmlogconf/pmlogconf.sh667
-rw-r--r--src/pmlogconf/sgi/cpu-evctr4
-rw-r--r--src/pmlogconf/sgi/craylink4
-rw-r--r--src/pmlogconf/sgi/efs4
-rw-r--r--src/pmlogconf/sgi/hub4
-rw-r--r--src/pmlogconf/sgi/kaio4
-rw-r--r--src/pmlogconf/sgi/localdefs1
-rw-r--r--src/pmlogconf/sgi/node-memory5
-rw-r--r--src/pmlogconf/sgi/numa4
-rw-r--r--src/pmlogconf/sgi/numa-summary4
-rw-r--r--src/pmlogconf/sgi/xbow4
-rw-r--r--src/pmlogconf/sgi/xlv-activity7
-rw-r--r--src/pmlogconf/sgi/xlv-stripe-io8
-rw-r--r--src/pmlogconf/sgi/xvm-all4
-rw-r--r--src/pmlogconf/sgi/xvm-ops7
-rw-r--r--src/pmlogconf/sgi/xvm-stats6
-rw-r--r--src/pmlogconf/sqlserver/localdefs1
-rw-r--r--src/pmlogconf/sqlserver/summary24
-rw-r--r--src/pmlogconf/tools/atop67
-rw-r--r--src/pmlogconf/tools/atop-proc21
-rw-r--r--src/pmlogconf/tools/atop-summary7
-rw-r--r--src/pmlogconf/tools/collectl75
-rw-r--r--src/pmlogconf/tools/collectl-summary6
-rw-r--r--src/pmlogconf/tools/dmcache13
-rw-r--r--src/pmlogconf/tools/free13
-rw-r--r--src/pmlogconf/tools/free-summary5
-rw-r--r--src/pmlogconf/tools/iostat68
-rw-r--r--src/pmlogconf/tools/ip21
-rw-r--r--src/pmlogconf/tools/localdefs23
-rw-r--r--src/pmlogconf/tools/mpstat29
-rw-r--r--src/pmlogconf/tools/numastat4
-rw-r--r--src/pmlogconf/tools/pcp-summary17
-rw-r--r--src/pmlogconf/tools/pmclient9
-rw-r--r--src/pmlogconf/tools/pmclient-summary5
-rw-r--r--src/pmlogconf/tools/pmstat17
-rw-r--r--src/pmlogconf/tools/sar64
-rw-r--r--src/pmlogconf/tools/sar-summary5
-rw-r--r--src/pmlogconf/tools/uptime6
-rw-r--r--src/pmlogconf/tools/vmstat90
-rw-r--r--src/pmlogconf/tools/vmstat-summary5
-rw-r--r--src/pmlogconf/v1.0/C24
-rw-r--r--src/pmlogconf/v1.0/C34
-rw-r--r--src/pmlogconf/v1.0/D34
-rw-r--r--src/pmlogconf/v1.0/K05
-rw-r--r--src/pmlogconf/v1.0/S04
-rw-r--r--src/pmlogconf/v1.0/S14
-rw-r--r--src/pmlogconf/v1.0/localdefs1
-rw-r--r--src/pmlogextract/GNUmakefile50
-rw-r--r--src/pmlogextract/error.c38
-rw-r--r--src/pmlogextract/gram.y381
-rw-r--r--src/pmlogextract/lex.l77
-rw-r--r--src/pmlogextract/logger.h117
-rw-r--r--src/pmlogextract/logio.c202
-rw-r--r--src/pmlogextract/metriclist.c311
-rw-r--r--src/pmlogextract/pmlogextract.c2000
-rw-r--r--src/pmlogger/GNUmakefile69
-rw-r--r--src/pmlogger/control52
-rw-r--r--src/pmlogger/crontab.in8
-rw-r--r--src/pmlogger/pmlogger.service.in13
-rwxr-xr-xsrc/pmlogger/pmlogger_check.sh867
-rwxr-xr-xsrc/pmlogger/pmlogger_daily.sh952
-rwxr-xr-xsrc/pmlogger/pmlogger_merge.sh282
-rwxr-xr-xsrc/pmlogger/pmlogmv.sh261
-rwxr-xr-xsrc/pmlogger/pmnewlog.sh702
-rw-r--r--src/pmlogger/rc_pmlogger301
-rw-r--r--src/pmlogger/src/GNUmakefile50
-rw-r--r--src/pmlogger/src/callback.c771
-rw-r--r--src/pmlogger/src/check.c164
-rw-r--r--src/pmlogger/src/dopdu.c1491
-rw-r--r--src/pmlogger/src/error.c35
-rw-r--r--src/pmlogger/src/events.c113
-rw-r--r--src/pmlogger/src/fetch.c186
-rw-r--r--src/pmlogger/src/gram.y570
-rw-r--r--src/pmlogger/src/lex.l144
-rw-r--r--src/pmlogger/src/logger.h181
-rw-r--r--src/pmlogger/src/pmlogger.c1186
-rw-r--r--src/pmlogger/src/ports.c734
-rw-r--r--src/pmlogger/src/preamble.c208
-rw-r--r--src/pmlogger/src/rewrite.c47
-rw-r--r--src/pmlogger/src/util.c81
-rw-r--r--src/pmloglabel/GNUmakefile31
-rw-r--r--src/pmloglabel/pmloglabel.c422
-rw-r--r--src/pmlogreduce/GNUmakefile43
-rw-r--r--src/pmlogreduce/dometric.c155
-rw-r--r--src/pmlogreduce/indom.c103
-rw-r--r--src/pmlogreduce/logio.c260
-rw-r--r--src/pmlogreduce/pmlogreduce.c490
-rw-r--r--src/pmlogreduce/pmlogreduce.h85
-rw-r--r--src/pmlogreduce/rewrite.c178
-rw-r--r--src/pmlogreduce/scan.c232
-rw-r--r--src/pmlogrewrite/GNUlocaldefs.coverage2
-rw-r--r--src/pmlogrewrite/GNUmakefile48
-rw-r--r--src/pmlogrewrite/gram.y986
-rw-r--r--src/pmlogrewrite/indom.c381
-rw-r--r--src/pmlogrewrite/lex.l223
-rw-r--r--src/pmlogrewrite/logger.h178
-rw-r--r--src/pmlogrewrite/logio.c175
-rw-r--r--src/pmlogrewrite/metric.c230
-rw-r--r--src/pmlogrewrite/pmlogrewrite.c1318
-rw-r--r--src/pmlogrewrite/result.c730
-rw-r--r--src/pmlogrewrite/util.c302
-rw-r--r--src/pmlogsummary/GNUmakefile40
-rwxr-xr-xsrc/pmlogsummary/pmdiff.sh316
-rw-r--r--src/pmlogsummary/pmlogcheck.c562
-rw-r--r--src/pmlogsummary/pmlogsummary.c1197
-rwxr-xr-xsrc/pmlogsummary/pmwtf.sh316
-rw-r--r--src/pmmgr/GNUmakefile63
-rw-r--r--src/pmmgr/TODO12
-rw-r--r--src/pmmgr/config/GNUmakefile40
-rw-r--r--src/pmmgr/config/README13
-rw-r--r--src/pmmgr/config/pmie0
-rw-r--r--src/pmmgr/config/pmieconf0
-rw-r--r--src/pmmgr/config/pmlogconf0
-rw-r--r--src/pmmgr/config/pmlogger0
-rw-r--r--src/pmmgr/config/pmlogmerge0
-rw-r--r--src/pmmgr/config/pmlogmerge-granular0
-rw-r--r--src/pmmgr/config/pmlogmerge-rewrite0
-rw-r--r--src/pmmgr/config/target-discovery.example-avahi1
-rw-r--r--src/pmmgr/pmmgr.cxx1285
-rw-r--r--src/pmmgr/pmmgr.h133
-rw-r--r--src/pmmgr/pmmgr.options27
-rw-r--r--src/pmmgr/pmmgr.service.in14
-rw-r--r--src/pmmgr/rc_pmmgr296
-rw-r--r--src/pmns/GNUmakefile79
-rw-r--r--src/pmns/GNUmakefile.install35
-rwxr-xr-xsrc/pmns/Make.stdpmid169
-rwxr-xr-xsrc/pmns/Rebuild395
-rw-r--r--src/pmns/ReplacePmnsSubtree184
-rw-r--r--src/pmns/lockpmns46
-rwxr-xr-xsrc/pmns/pmnsadd125
-rw-r--r--src/pmns/pmnsdel.c216
-rw-r--r--src/pmns/pmnsmerge.c322
-rw-r--r--src/pmns/pmnsutil.c99
-rw-r--r--src/pmns/pmnsutil.h21
-rw-r--r--src/pmns/stdpmid.local5
-rw-r--r--src/pmns/stdpmid.pcp128
-rw-r--r--src/pmns/unlockpmns30
-rw-r--r--src/pmnscomp/GNUmakefile31
-rw-r--r--src/pmnscomp/pmnscomp.c621
-rw-r--r--src/pmpost/GNUmakefile31
-rw-r--r--src/pmpost/pmpost.c219
-rw-r--r--src/pmprobe/GNUmakefile32
-rw-r--r--src/pmprobe/pmprobe.c361
-rw-r--r--src/pmproxy/GNUmakefile50
-rw-r--r--src/pmproxy/client.c260
-rw-r--r--src/pmproxy/pmproxy.c543
-rw-r--r--src/pmproxy/pmproxy.h48
-rw-r--r--src/pmproxy/pmproxy.options31
-rw-r--r--src/pmproxy/pmproxy.service.in14
-rw-r--r--src/pmproxy/rc_pmproxy301
-rw-r--r--src/pmproxy/util.c97
-rw-r--r--src/pmquery/GNUmakefile76
-rw-r--r--src/pmquery/main.cpp275
-rwxr-xr-xsrc/pmquery/pmconfirm.sh.in2
-rwxr-xr-xsrc/pmquery/pmmessage.sh.in2
-rw-r--r--src/pmquery/pmquery.cpp316
-rw-r--r--src/pmquery/pmquery.h105
-rw-r--r--src/pmquery/pmquery.info.in18
-rw-r--r--src/pmquery/pmquery.pro9
-rw-r--r--src/pmquery/pmquery.qrc10
-rw-r--r--src/pmquery/pmquery.sh.in2
-rw-r--r--src/pmsignal/GNUmakefile30
-rwxr-xr-xsrc/pmsignal/pmsignal.sh120
-rw-r--r--src/pmsleep/GNUmakefile32
-rw-r--r--src/pmsleep/pmsleep.c43
-rw-r--r--src/pmsnap/GNUmakefile29
-rw-r--r--src/pmsnap/Snap30
-rw-r--r--src/pmsnap/control71
-rw-r--r--src/pmsnap/crontab.IN6
-rwxr-xr-xsrc/pmsnap/pmsnap.sh457
-rw-r--r--src/pmsnap/summary.html34
-rw-r--r--src/pmsocks/GNUmakefile32
-rwxr-xr-xsrc/pmsocks/tsocks.sh56
-rw-r--r--src/pmstat/GNUmakefile34
-rw-r--r--src/pmstat/pmstat.c759
-rw-r--r--src/pmstore/GNUmakefile32
-rw-r--r--src/pmstore/pmstore.c385
-rw-r--r--src/pmtime/GNUmakefile71
-rw-r--r--src/pmtime/aboutdialog.cpp29
-rw-r--r--src/pmtime/aboutdialog.h30
-rw-r--r--src/pmtime/aboutdialog.ui229
-rw-r--r--src/pmtime/console.cpp65
-rw-r--r--src/pmtime/console.h37
-rw-r--r--src/pmtime/console.ui139
-rw-r--r--src/pmtime/main.cpp124
-rw-r--r--src/pmtime/pmtime.cpp96
-rw-r--r--src/pmtime/pmtime.h48
-rw-r--r--src/pmtime/pmtime.info.in18
-rw-r--r--src/pmtime/pmtime.pro24
-rw-r--r--src/pmtime/pmtime.qrc24
-rw-r--r--src/pmtime/pmtime.rc1
-rw-r--r--src/pmtime/pmtime.sh.in2
-rw-r--r--src/pmtime/pmtimearch.cpp704
-rw-r--r--src/pmtime/pmtimearch.h101
-rw-r--r--src/pmtime/pmtimearch.ui1262
-rw-r--r--src/pmtime/pmtimelive.cpp324
-rw-r--r--src/pmtime/pmtimelive.h72
-rw-r--r--src/pmtime/pmtimelive.ui824
-rw-r--r--src/pmtime/seealsodialog.cpp24
-rw-r--r--src/pmtime/seealsodialog.h30
-rw-r--r--src/pmtime/seealsodialog.ui235
-rw-r--r--src/pmtime/showboundsdialog.cpp237
-rw-r--r--src/pmtime/showboundsdialog.h58
-rw-r--r--src/pmtime/showboundsdialog.ui303
-rw-r--r--src/pmtime/timelord.cpp395
-rw-r--r--src/pmtime/timelord.h118
-rw-r--r--src/pmtime/timezone.h52
-rw-r--r--src/pmtop/GNUmakefile33
-rw-r--r--src/pmtop/pmtop.c1008
-rw-r--r--src/pmtrace/GNUmakefile36
-rw-r--r--src/pmtrace/pmtrace.c211
-rw-r--r--src/pmval/GNUmakefile34
-rw-r--r--src/pmval/pmval.c1141
-rw-r--r--src/pmview/GNUmakefile102
-rw-r--r--src/pmview/app-defaults207
-rw-r--r--src/pmview/barmod.cpp603
-rw-r--r--src/pmview/barmod.h133
-rw-r--r--src/pmview/barobj.cpp539
-rw-r--r--src/pmview/barobj.h125
-rw-r--r--src/pmview/baseobj.cpp125
-rw-r--r--src/pmview/baseobj.h119
-rw-r--r--src/pmview/colorlist.cpp186
-rw-r--r--src/pmview/colorlist.h77
-rw-r--r--src/pmview/colormod.cpp161
-rw-r--r--src/pmview/colormod.h59
-rw-r--r--src/pmview/colorscale.cpp158
-rw-r--r--src/pmview/colorscale.h89
-rw-r--r--src/pmview/colorscalemod.cpp190
-rw-r--r--src/pmview/colorscalemod.h65
-rw-r--r--src/pmview/defaultobj.cpp175
-rw-r--r--src/pmview/defaultobj.h152
-rw-r--r--src/pmview/error.cpp47
-rw-r--r--src/pmview/front-ends/GNUmakefile25
-rwxr-xr-xsrc/pmview/front-ends/clustervis331
-rw-r--r--src/pmview/front-ends/config.clustervis13
-rwxr-xr-xsrc/pmview/front-ends/config.dkvis14
-rwxr-xr-xsrc/pmview/front-ends/config.mpvis13
-rwxr-xr-xsrc/pmview/front-ends/config.nfsvis12
-rwxr-xr-xsrc/pmview/front-ends/config.osvis31
-rwxr-xr-xsrc/pmview/front-ends/config.weblogvis20
-rwxr-xr-xsrc/pmview/front-ends/config.webpingvis8
-rw-r--r--src/pmview/front-ends/config.webvis51
-rwxr-xr-xsrc/pmview/front-ends/dkvis572
-rwxr-xr-xsrc/pmview/front-ends/mpvis452
-rwxr-xr-xsrc/pmview/front-ends/nfsvis244
-rw-r--r--src/pmview/front-ends/osvis651
-rw-r--r--src/pmview/front-ends/pmview-args625
-rwxr-xr-xsrc/pmview/front-ends/weblogvis451
-rwxr-xr-xsrc/pmview/front-ends/weblogvis.load103
-rw-r--r--src/pmview/front-ends/weblogvis.rgbbin0 -> 206570 bytes
-rw-r--r--src/pmview/front-ends/webpingvis372
-rw-r--r--src/pmview/front-ends/webpingvis.rgbbin0 -> 365706 bytes
-rwxr-xr-xsrc/pmview/front-ends/webvis707
-rw-r--r--src/pmview/front-ends/webvis.rgbbin0 -> 502112 bytes
-rw-r--r--src/pmview/gram.y1391
-rw-r--r--src/pmview/gridobj.cpp314
-rw-r--r--src/pmview/gridobj.h107
-rw-r--r--src/pmview/iconbin0 -> 9247 bytes
-rw-r--r--src/pmview/labelobj.cpp112
-rw-r--r--src/pmview/labelobj.h99
-rw-r--r--src/pmview/launch.cpp421
-rw-r--r--src/pmview/launch.h113
-rw-r--r--src/pmview/lex.l456
-rw-r--r--src/pmview/link.cpp218
-rw-r--r--src/pmview/link.h52
-rw-r--r--src/pmview/main.cpp493
-rw-r--r--src/pmview/main.h152
-rw-r--r--src/pmview/metriclist.cpp234
-rw-r--r--src/pmview/metriclist.h84
-rw-r--r--src/pmview/modlist.cpp471
-rw-r--r--src/pmview/modlist.h134
-rw-r--r--src/pmview/modobj.h43
-rw-r--r--src/pmview/modulate.cpp134
-rw-r--r--src/pmview/modulate.h117
-rw-r--r--src/pmview/pcpcolor.cpp100
-rw-r--r--src/pmview/pcpcolor.h61
-rw-r--r--src/pmview/pipeobj.cpp187
-rw-r--r--src/pmview/pipeobj.h54
-rw-r--r--src/pmview/pmview.cpp708
-rw-r--r--src/pmview/pmview.desktop8
-rw-r--r--src/pmview/pmview.h162
-rw-r--r--src/pmview/pmview.info.in18
-rw-r--r--src/pmview/pmview.pro34
-rw-r--r--src/pmview/pmview.qrc23
-rw-r--r--src/pmview/pmview.ui386
-rw-r--r--src/pmview/scalemod.cpp183
-rw-r--r--src/pmview/scalemod.h62
-rw-r--r--src/pmview/scenefileobj.cpp127
-rw-r--r--src/pmview/scenefileobj.h53
-rw-r--r--src/pmview/scenegroup.cpp178
-rw-r--r--src/pmview/scenegroup.h62
-rw-r--r--src/pmview/stackmod.cpp594
-rw-r--r--src/pmview/stackmod.h98
-rw-r--r--src/pmview/stackobj.cpp146
-rw-r--r--src/pmview/stackobj.h76
-rw-r--r--src/pmview/text.cpp334
-rw-r--r--src/pmview/text.h83
-rw-r--r--src/pmview/togglemod.cpp106
-rw-r--r--src/pmview/togglemod.h75
-rw-r--r--src/pmview/viewobj.cpp170
-rw-r--r--src/pmview/viewobj.h135
-rw-r--r--src/pmview/xing.cpp202
-rw-r--r--src/pmview/xing.h48
-rw-r--r--src/pmview/yscalemod.cpp46
-rw-r--r--src/pmview/yscalemod.h44
-rw-r--r--src/pmwebapi/GNUmakefile63
-rw-r--r--src/pmwebapi/TODO18
-rw-r--r--src/pmwebapi/jsdemos/GNUmakefile39
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/GNUmakefile33
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/blinken_error.pngbin0 -> 7879 bytes
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/blinken_off.pngbin0 -> 6324 bytes
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/blinken_on.pngbin0 -> 6360 bytes
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/blinkenlights.css19
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/blinkenlights.js170
-rw-r--r--src/pmwebapi/jsdemos/blinkenlights/index.html129
-rw-r--r--src/pmwebapi/jsdemos/favicon.icobin0 -> 4286 bytes
-rw-r--r--src/pmwebapi/jsdemos/jquery-1.7.2.js9404
-rw-r--r--src/pmwebapi/jsdemos/jquery-ui-1.10.2.js14987
-rw-r--r--src/pmwebapi/jsdemos/jquery-ui-themes-1.10.2.tar.gzbin0 -> 985756 bytes
-rw-r--r--src/pmwebapi/jsdemos/jsbrowser/GNUmakefile27
-rw-r--r--src/pmwebapi/jsdemos/jsbrowser/index.html34
-rw-r--r--src/pmwebapi/jsdemos/jsbrowser/jsbrowser.js12
-rw-r--r--src/pmwebapi/main.c561
-rw-r--r--src/pmwebapi/pmresapi.c162
-rw-r--r--src/pmwebapi/pmwebapi.c1343
-rw-r--r--src/pmwebapi/pmwebapi.h52
-rw-r--r--src/pmwebapi/pmwebd.options32
-rw-r--r--src/pmwebapi/pmwebd.service.in14
-rw-r--r--src/pmwebapi/rc_pmwebd294
-rw-r--r--src/pmwebapi/util.c96
-rw-r--r--src/procmemstat/GNUmakefile50
-rw-r--r--src/procmemstat/GNUmakefile.install39
-rw-r--r--src/procmemstat/README19
-rw-r--r--src/procmemstat/pmnsmap.spec21
-rw-r--r--src/procmemstat/procmemstat.c138
-rw-r--r--src/python/GNUmakefile47
-rw-r--r--src/python/mmv.c92
-rw-r--r--src/python/pcp/GNUmakefile25
-rw-r--r--src/python/pcp/__init__.py1
-rw-r--r--src/python/pcp/mmv.py332
-rw-r--r--src/python/pcp/pmapi.py1866
-rw-r--r--src/python/pcp/pmcc.py620
-rw-r--r--src/python/pcp/pmda.py407
-rw-r--r--src/python/pcp/pmgui.py174
-rw-r--r--src/python/pcp/pmi.py312
-rw-r--r--src/python/pcp/pmsubsys.py355
-rw-r--r--src/python/pmapi.c1364
-rw-r--r--src/python/pmda.c1098
-rw-r--r--src/python/pmgui.c81
-rw-r--r--src/python/pmi.c115
-rw-r--r--src/python/setup.py61
-rw-r--r--src/sar2pcp/GNUmakefile43
-rw-r--r--src/sar2pcp/README81
-rwxr-xr-xsrc/sar2pcp/sar2pcp756
-rw-r--r--src/sheet2pcp/GNUmakefile43
-rwxr-xr-xsrc/sheet2pcp/sheet2pcp843
-rw-r--r--src/telnet-probe/GNUmakefile36
-rw-r--r--src/telnet-probe/telnet-probe.c184
-rw-r--r--src/win32ctl/GNUmakefile36
-rw-r--r--src/win32ctl/eventlog/GNUmakefile38
-rw-r--r--src/win32ctl/eventlog/pcp-eventlog.c144
-rw-r--r--src/win32ctl/include/GNUmakefile32
-rw-r--r--src/win32ctl/include/_mingw_unicode.h33
-rw-r--r--src/win32ctl/include/evntcons.h158
-rw-r--r--src/win32ctl/include/evntprov.h354
-rw-r--r--src/win32ctl/include/evntrace.h841
-rw-r--r--src/win32ctl/include/pdh.h605
-rw-r--r--src/win32ctl/include/pdhmsg.h101
-rw-r--r--src/win32ctl/include/pshpack8.h8
-rw-r--r--src/win32ctl/include/tdh.h329
-rw-r--r--src/win32ctl/include/tdhmsg.h39
-rw-r--r--src/win32ctl/include/winevt.h758
-rw-r--r--src/win32ctl/include/winmeta.h3
-rw-r--r--src/win32ctl/include/winperf.h193
-rw-r--r--src/win32ctl/include/wmistr.h198
-rwxr-xr-xsrc/win32ctl/lib/GNUmakefile42
-rw-r--r--src/win32ctl/lib/libpcp_pdh.def12
-rw-r--r--src/win32ctl/lib/libpcp_tdh.def10
-rwxr-xr-xsrc/win32ctl/mkaf.bat10
-rwxr-xr-xsrc/win32ctl/pcp.bat10
-rwxr-xr-xsrc/win32ctl/pmafm.bat10
-rwxr-xr-xsrc/win32ctl/pmsignal.bat10
-rw-r--r--src/win32ctl/services/GNUmakefile38
-rw-r--r--src/win32ctl/services/pcp-services.c248
-rw-r--r--src/win32ctl/setevent/GNUmakefile38
-rw-r--r--src/win32ctl/setevent/pcp-setevent.c84
2005 files changed, 434027 insertions, 0 deletions
diff --git a/src/GNUmakefile b/src/GNUmakefile
new file mode 100644
index 0000000..5993751
--- /dev/null
+++ b/src/GNUmakefile
@@ -0,0 +1,127 @@
+#
+# Copyright (c) 2012-2014 Red Hat.
+# Copyright (c) 2000,2004,2012 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+INCLUDE_SUBDIR = include
+PMNS_SUBDIR = pmns
+LIBPCP_SUBDIR = libpcp
+LIBS_SUBDIRS = \
+ libpcp_pmda \
+ libpcp_trace \
+ libpcp_http \
+ libpcp_pmcd \
+ libpcp_gui \
+ libpcp_mmv \
+ libpcp_import \
+ libpcp_qed \
+ libpcp_qmc \
+ libpcp_qwt \
+ #
+
+OTHER_SUBDIRS = \
+ pminfo \
+ pmprobe \
+ bashrc \
+ dbpmda \
+ genpmda \
+ newhelp \
+ pcp \
+ pmafm \
+ pmatop \
+ pmcollectl \
+ pmfind \
+ pmcpp \
+ pmcd \
+ pmcd_wait \
+ pmchart \
+ pmclient \
+ pmconfig \
+ pmdas \
+ pmdate \
+ pmdbg \
+ pmdumplog \
+ pmdumptext \
+ pmerr \
+ pmevent \
+ pmgenmap \
+ pmgetopt \
+ pmhostname \
+ pmie \
+ pmieconf \
+ pmiestatus \
+ pmiostat \
+ pmlc \
+ pmlock \
+ pmlogextract \
+ pmlogger \
+ pmlogreduce \
+ pmlogconf \
+ pmloglabel \
+ pmlogrewrite \
+ pmlogsummary \
+ pmmgr \
+ pmpost \
+ pmproxy \
+ pmquery \
+ pmstat \
+ pmstore \
+ pmsocks \
+ pmtrace \
+ pmsignal \
+ pmsleep \
+ pmsnap \
+ pmtop \
+ pmtime \
+ pmwebapi \
+ pmval \
+ perl \
+ python \
+ procmemstat \
+ autofsd-probe \
+ telnet-probe \
+ collectl2pcp \
+ iostat2pcp \
+ mrtg2pcp \
+ sar2pcp \
+ sheet2pcp \
+ win32ctl \
+ #
+
+SUBDIRS = \
+ $(INCLUDE_SUBDIR) \
+ $(LIBPCP_SUBDIR) \
+ $(PMNS_SUBDIR) \
+ $(LIBS_SUBDIRS) \
+ $(OTHER_SUBDIRS)
+
+default :: default_pcp
+
+include $(BUILDRULES)
+
+default_pcp : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install :: default_pcp install_pcp
+
+install_pcp : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+$(LIBPCP_SUBDIR): $(INCLUDE_SUBDIR)
+$(PMNS_SUBDIR): $(LIBPCP_SUBDIR)
+$(LIBS_SUBDIRS): $(PMNS_SUBDIR)
+$(OTHER_SUBDIRS): $(LIBS_SUBDIRS)
diff --git a/src/autofsd-probe/GNUmakefile b/src/autofsd-probe/GNUmakefile
new file mode 100644
index 0000000..38ba708
--- /dev/null
+++ b/src/autofsd-probe/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = autofsd-probe.c
+CMDTARGET = autofsd-probe
+LLDLIBS = $(PCPLIB)
+
+default: build-me
+
+include $(TOPDIR)/src/include/buildrules
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGET)
+
+install: build-me
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/autofsd-probe/autofsd-probe.c b/src/autofsd-probe/autofsd-probe.c
new file mode 100644
index 0000000..412beeb
--- /dev/null
+++ b/src/autofsd-probe/autofsd-probe.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 1998,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <rpc/rpc.h>
+
+#define AUTOFSD_PROGRAM 100099UL
+#define AUTOFSD_VERSION 1UL
+
+/*
+ * probe IRIX autofsd(1M)
+ */
+int
+main(int argc, char **argv)
+{
+ struct timeval tv = { 10, 0 };
+ CLIENT *clnt;
+ enum clnt_stat stat;
+ int c;
+ char *p;
+ char *host = "local:";
+ int errflag = 0;
+
+ __pmSetProgname(argv[0]);
+
+ while ((c = getopt(argc, argv, "h:t:?")) != EOF) {
+ switch (c) {
+
+ case 'h': /* contact autofsd on this hostname */
+ host = optarg;
+ break;
+
+ case 't': /* change timeout interval */
+ if (pmParseInterval(optarg, &tv, &p) < 0) {
+ fprintf(stderr, "%s: illegal -t argument\n", pmProgname);
+ fputs(p, stderr);
+ free(p);
+ errflag++;
+ }
+ break;
+
+ case '?':
+ default:
+ fprintf(stderr, "Usage: %s [-h host] [-t timeout]\n", pmProgname);
+ errflag++;
+ break;
+ }
+ }
+
+ if (errflag)
+ exit(4);
+
+ if ((clnt = clnt_create(host, AUTOFSD_PROGRAM, AUTOFSD_VERSION, "udp")) == NULL) {
+ clnt_pcreateerror("clnt_create");
+ exit(2);
+ }
+
+ /*
+ * take control of the timeout algorithm
+ */
+ clnt_control(clnt, CLSET_TIMEOUT, (char *)&tv);
+ clnt_control(clnt, CLSET_RETRY_TIMEOUT, (char *)&tv);
+
+ stat = clnt_call(clnt, NULLPROC, (xdrproc_t)xdr_void, (char *)0,
+ (xdrproc_t)xdr_void, (char *)0, tv);
+
+ if (stat != RPC_SUCCESS) {
+ clnt_perror(clnt, "clnt_call");
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/bashrc/GNUmakefile b/src/bashrc/GNUmakefile
new file mode 100644
index 0000000..9c4900f
--- /dev/null
+++ b/src/bashrc/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+BASHRC = pcp_completion.sh
+BASHDIR = $(PCP_ETC_DIR)/bash_completion.d
+LSRCFILES = $(BASHRC)
+
+default: $(BASHRC)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -d $(BASHDIR)
+ $(INSTALL) -m 644 $(BASHRC) $(BASHDIR)/pcp
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/bashrc/pcp_completion.sh b/src/bashrc/pcp_completion.sh
new file mode 100644
index 0000000..fe0ce82
--- /dev/null
+++ b/src/bashrc/pcp_completion.sh
@@ -0,0 +1,68 @@
+# Programmable completion for Performance Co-Pilot commands under bash.
+# Author: Roman Revyakin (Roman), rrevyakin@aconex.com
+
+_pminfo_complete ()
+{
+ local cur=${COMP_WORDS[$COMP_CWORD]}
+ local opt_regex= curpos_expand=
+
+ COMPREPLY=()
+
+ # Options that need no completion and the cursor position to start
+ # expansion from for different programs
+ case ${COMP_WORDS[0]} in
+ pminfo)
+ opt_regex="-[abhnOZ]"
+ curpos_expand=1
+ ;;
+
+ pmprobe)
+ opt_regex="-[ahnOZ]"
+ curpos_expand=1
+ ;;
+
+ pmdumptext)
+ opt_regex="-[AacdfhnOPRsStTUwZ]"
+ curpos_expand=1
+ ;;
+
+ pmdumplog)
+ opt_regex="-[nSTZ]"
+ curpos_expand=1
+ ;;
+
+ pmlogsummary)
+ opt_regex="-[BnpSTZ]"
+ curpos_expand=2
+ ;;
+
+ pmstore)
+ opt_regex="-[hin]"
+ curpos_expand=1
+ ;;
+
+ pmval)
+ opt_regex="-[AafhinOpSsTtwZ]"
+ curpos_expand=1
+ ;;
+
+ pmevent)
+ opt_regex="-[ahKOpSsTtZ]"
+ curpos_expand=1
+ ;;
+
+ esac # --- end of case ---
+
+ # We expand either straight from the cursor if it is at the position to
+ # expand or check for the preceding options whether to expand or not
+ if (( $COMP_CWORD == $curpos_expand )) || \
+ ( (( $COMP_CWORD > $curpos_expand )) \
+ && ! [[ "${COMP_WORDS[$((COMP_CWORD-1))]}" =~ $opt_regex ]]
+ )
+ then
+ COMPREPLY=(`compgen -W '$(command pminfo)' 2>/dev/null $cur`)
+ fi
+
+} # ---------- end of function _pminfo_complete ----------
+
+complete -F _pminfo_complete -o default pminfo pmprobe pmdumptext pmdumplog pmlogsummary pmstore pmval pmevent
diff --git a/src/collectl2pcp/GNUmakefile b/src/collectl2pcp/GNUmakefile
new file mode 100644
index 0000000..5d937b8
--- /dev/null
+++ b/src/collectl2pcp/GNUmakefile
@@ -0,0 +1,47 @@
+# Copyright (c) 2013 Red Hat Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+HFILES = metrics.h
+CFILES = collectl2pcp.c cpu.c disk.c net.c load.c timestamp.c util.c \
+ metrics.c header.c generic.c proc.c
+LSRCFILES = pmdesc.c
+
+CMDTARGET = collectl2pcp
+LLDLIBS = -L$(TOPDIR)/src/libpcp_import/src -lpcp_import $(PCPLIB)
+# LCFLAGS += -pg
+
+#
+# metrics.c is generated (but committed for now)
+# LDIRT = metrics.c
+LDIRT = pmdesc pmdesc.o
+
+default: pmdesc $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+# metrics.c: pmdesc
+# $(RUN_IN_BUILD_ENV) pminfo hinv kernel mem swap network disk filesys \
+# swapdev rpc nfs nfs3 nfs4 xfs pmda ipc vfs quota tmpfs sysfs proc \
+# | ./pmdesc > metrics.c
+
+pmdesc: pmdesc.o
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/collectl2pcp/collectl2pcp.c b/src/collectl2pcp/collectl2pcp.c
new file mode 100644
index 0000000..c9755af
--- /dev/null
+++ b/src/collectl2pcp/collectl2pcp.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Import collectl raw data file and create a PCP archive.
+ * Mark Goodwin <mgoodwin@redhat.com> May 2013.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include <pcp/import.h>
+#include "metrics.h"
+
+#define BUFSIZE 1048576
+
+handler_t handlers[] = {
+ { ">>>", timestamp_handler },
+
+ /* /proc/PID/... */
+ { "proc:*", proc_handler },
+
+ /* /proc/stat */
+ { "cpu*", cpu_handler },
+ { "processes", generic1_handler, "kernel.all.nprocs" },
+ { "intr", generic1_handler, "kernel.all.intr" },
+ { "ctxt", generic1_handler, "kernel.all.pswitch" },
+
+ /* /proc/diskstats */
+ { "disk", disk_handler },
+
+ /* /proc/net/dev */
+ { "Net", net_handler },
+
+ /* /proc/loadavg */
+ { "load", loadavg_handler },
+
+ /* /proc/meminfo */
+ { "MemTotal:", generic1_handler, "mem.physmem" },
+ { "MemFree:", generic1_handler, "mem.util.free" },
+ { "Buffers:", generic1_handler, "mem.util.bufmem" },
+ { "Cached:", generic1_handler, "mem.util.cached" },
+ { "SwapCached:", generic1_handler, "mem.util.swapCached" },
+ { "Active:", generic1_handler, "mem.util.active" },
+ { "Inactive:", generic1_handler, "mem.util.inactive" },
+ { "Active(anon):", generic1_handler, "mem.util.active_anon" },
+ { "Inactive(anon):", generic1_handler, "mem.util.inactive_anon" },
+ { "Active(file):", generic1_handler, "mem.util.active_file" },
+ { "Inactive(file):", generic1_handler, "mem.util.inactive_file" },
+ { "Unevictable:", generic1_handler, "mem.util.unevictable" },
+ { "Mlocked:", generic1_handler, "mem.util.mlocked" },
+ { "SwapTotal:", generic1_handler, "mem.util.swapTotal" },
+ { "SwapFree:", generic1_handler, "mem.util.swapFree" },
+ { "Dirty:", generic1_handler, "mem.util.dirty" },
+ { "Writeback:", generic1_handler, "mem.util.writeback" },
+ { "AnonPages:", generic1_handler, "mem.util.anonpages" },
+ { "Mapped:", generic1_handler, "mem.util.mapped" },
+ { "Shmem:", generic1_handler, "mem.util.shmem" },
+ { "Slab:", generic1_handler, "mem.util.slab" },
+ { "SReclaimable:", generic1_handler, "mem.util.slabReclaimable" },
+ { "SUnreclaim:", generic1_handler, "mem.util.slabUnreclaimable" },
+ { "KernelStack:", generic1_handler, "mem.util.kernelStack" },
+ { "PageTables:", generic1_handler, "mem.util.pageTables" },
+ { "NFS_Unstable:", generic1_handler, "mem.util.NFS_Unstable" },
+ { "Bounce:", generic1_handler, "mem.util.bounce" },
+ { "WritebackTmp:", generic1_handler, "unknown" },
+ { "CommitLimit:", generic1_handler, "mem.util.commitLimit" },
+ { "Committed_AS:", generic1_handler, "mem.util.committed_AS" },
+ { "VmallocTotal:", generic1_handler, "mem.util.vmallocTotal" },
+ { "VmallocUsed:", generic1_handler, "mem.util.vmallocUsed" },
+ { "VmallocChunk:", generic1_handler, "mem.util.vmallocChunk" },
+ { "HardwareCorrupted:", generic1_handler, "mem.util.corrupthardware" },
+ { "AnonHugePages:", generic1_handler, "mem.util.anonhugepages" },
+ { "HugePages_Total:", generic1_handler, "mem.util.hugepagesTotal" },
+ { "HugePages_Free:", generic1_handler, "mem.util.hugepagesFree" },
+ { "HugePages_Rsvd:", generic1_handler, "mem.util.hugepagesRsvd" },
+ { "HugePages_Surp:", generic1_handler, "mem.util.hugepagesSurp" },
+ { "Hugepagesize:", generic1_handler, "unknown" },
+ { "DirectMap4k:", generic1_handler, "mem.util.directMap4k" },
+ { "DirectMap2M:", generic1_handler, "mem.util.directMap2M" },
+
+ { NULL }
+};
+
+int indom_cnt[NUM_INDOMS];
+
+/* global options */
+int vflag;
+int Fflag;
+int kernel_all_hz;
+int utc_offset;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "force", 0, 'f', 0, "forces overwrite of 'archive' if it already exists" },
+ { "verbose", 0, 'v', 0, "enables increasingly verbose messages" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "FD:v?",
+ .long_options = longopts,
+ .short_usage = "inputfile [inputfile ...] archive\n"
+"Each 'inputfile' is a collectl archive, must be for the same host (may be gzipped).\n"
+"'archive' is the base name for the PCP archive to be created."
+};
+
+int
+main(int argc, char *argv[])
+{
+ int sts;
+ int ctx;
+ int c;
+ char *infile;
+ int nfilelist;
+ int filenum;
+ char *archive = NULL;
+ int j;
+ char *buf;
+ fields_t *f;
+ char *s;
+ int gzipped;
+ FILE *fp;
+ metric_t *m;
+ handler_t *h;
+ int unhandled_metric_cnt = 0;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'F':
+ Fflag = 1;
+ break;
+ case 'v':
+ vflag++;
+ break;
+ }
+ }
+
+ nfilelist = argc - opts.optind - 1;
+ if (nfilelist < 1)
+ opts.errors++;
+ else
+ archive = argv[argc - 1];
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if ((buf = malloc(BUFSIZE)) == NULL) {
+ perror("Error: out of memory:");
+ exit(1);
+ }
+
+ if (Fflag) {
+ snprintf(buf, BUFSIZE, "%s.meta", archive); unlink(buf);
+ snprintf(buf, BUFSIZE, "%s.index", archive); unlink(buf);
+ for (j=0;; j++) {
+ snprintf(buf, BUFSIZE, "%s.%d", archive, j);
+ if (unlink(buf) < 0)
+ break;
+ }
+ }
+
+ ctx = pmiStart(archive, 0);
+ if ((sts = pmiUseContext(ctx)) < 0) {
+ fprintf(stderr, "Error: pmiUseContext failed: %s\n", pmiErrStr(sts));
+ exit(1);
+ }
+
+ /*
+ * Define the metrics name space, see metrics.c (generated by pmdesc)
+ */
+ for (m = metrics; m->name; m++) {
+ pmDesc *d = &m->desc;
+
+ sts = pmiAddMetric(m->name, d->pmid, d->type, d->indom, d->sem, d->units);
+ if (sts < 0) {
+ fprintf(stderr, "Error: failed to add metric %s: %s\n", m->name, pmiErrStr(sts));
+ exit(1);
+ }
+ }
+
+ /*
+ * Populate special case instance domains
+ */
+ pmiAddInstance(pmInDom_build(LINUX_DOMAIN, LOADAVG_INDOM), "1 minute", 1);
+ pmiAddInstance(pmInDom_build(LINUX_DOMAIN, LOADAVG_INDOM), "5 minute", 5);
+ pmiAddInstance(pmInDom_build(LINUX_DOMAIN, LOADAVG_INDOM), "15 minute", 15);
+ indom_cnt[LOADAVG_INDOM] = 3;
+
+ for (filenum=0; filenum < nfilelist; filenum++) {
+ infile = argv[opts.optind + filenum];
+ gzipped = strstr(infile, ".gz") != NULL;
+ if (gzipped) {
+ snprintf(buf, BUFSIZE, "gzip -c -d %s", infile);
+ if ((fp = popen(buf, "r")) == NULL)
+ perror(buf);
+ }
+ else
+ if ((fp = fopen(infile, "r")) == NULL)
+ perror(infile);
+
+ if (fp == NULL) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ /*
+ * parse the header
+ */
+ sts = header_handler(fp, infile, buf, BUFSIZE);
+
+ /*
+ * Parse remaining data stream for this input file
+ */
+ while(fgets(buf, BUFSIZE, fp)) {
+ if ((s = strrchr(buf, '\n')) != NULL)
+ *s = '\0';
+ if (!buf[0])
+ continue;
+ f = fields_new(buf, strlen(buf)+1);
+ if (f->nfields > 0) {
+ if ((h = find_handler(f->fields[0])) == NULL) {
+ unhandled_metric_cnt++;
+ if (vflag > 1)
+ printf("Unhandled tag: \"%s\"\n", f->fields[0]);
+ }
+ else {
+ sts = h->handler(h, f);
+ if (sts < 0 && h->handler == timestamp_handler) {
+ fprintf(stderr, "Error: %s\n", pmiErrStr(sts));
+ exit(1);
+ }
+ }
+ }
+ fields_free(f);
+ }
+
+ /* final flush for this file */
+ if ((sts = timestamp_flush()) < 0) {
+ fprintf(stderr, "Error: failed to write final timestamp: %s\n", pmiErrStr(sts));
+ exit(1);
+ }
+
+ if (gzipped)
+ pclose(fp);
+ else
+ fclose(fp);
+ }
+
+ sts = pmiEnd();
+ if (unhandled_metric_cnt && vflag)
+ fprintf(stderr, "Warning: %d unhandled metric/values\n", unhandled_metric_cnt);
+
+ exit(0);
+}
diff --git a/src/collectl2pcp/cpu.c b/src/collectl2pcp/cpu.c
new file mode 100644
index 0000000..a5b5028
--- /dev/null
+++ b/src/collectl2pcp/cpu.c
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for kernel.{percpu,all}.cpu.*
+ *
+ * cpu 66057896 4079 13437134 575696782 4513632 1463907 1701801 0 28602 0
+ * cpu0 24489004 2578 3862841 132669978 2807446 852698 928386 0 28602 0
+ *
+ */
+
+#include "metrics.h"
+
+#define ticks_to_msec(ticks) (1000ULL * strtoull(ticks, NULL, 0) / kernel_all_hz)
+
+int
+cpu_handler(handler_t *h, fields_t *f)
+{
+ char *inst = NULL;
+
+ if (f->fieldlen[0] < 3 || f->nfields < 9)
+ return -1;
+
+ if (f->fieldlen[0] > 3 && isdigit(f->fields[0][3])) {
+ /* kernel.percpu.cpu.* */
+ pmInDom indom = pmInDom_build(LINUX_DOMAIN, CPU_INDOM);
+
+ inst = f->fields[0]; /* cpuN */
+
+ put_ull_value("kernel.percpu.cpu.user", indom, inst, ticks_to_msec(f->fields[1]));
+ put_ull_value("kernel.percpu.cpu.nice", indom, inst, ticks_to_msec(f->fields[2]));
+ put_ull_value("kernel.percpu.cpu.sys", indom, inst, ticks_to_msec(f->fields[3]));
+ put_ull_value("kernel.percpu.cpu.idle", indom, inst, ticks_to_msec(f->fields[4]));
+ put_ull_value("kernel.percpu.cpu.wait.total", indom, inst, ticks_to_msec(f->fields[5]));
+ put_ull_value("kernel.percpu.cpu.irq.hard", indom, inst, ticks_to_msec(f->fields[6]));
+ put_ull_value("kernel.percpu.cpu.irq.soft", indom, inst, ticks_to_msec(f->fields[7]));
+ put_ull_value("kernel.percpu.cpu.steal", indom, inst, ticks_to_msec(f->fields[8]));
+ if (f->nfields > 9) /* guest cpu usage is only in more recent kernels */
+ put_ull_value("kernel.percpu.cpu.guest", indom, inst, ticks_to_msec(f->fields[9]));
+
+ put_ull_value("kernel.percpu.cpu.intr", indom, inst,
+ 1000 * ((double)strtoull(f->fields[6], NULL, 0) +
+ (double)strtoull(f->fields[7], NULL, 0)) / kernel_all_hz);
+ }
+ else {
+ put_ull_value("kernel.all.cpu.user", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[1]));
+ put_ull_value("kernel.all.cpu.nice", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[2]));
+ put_ull_value("kernel.all.cpu.sys", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[3]));
+ put_ull_value("kernel.all.cpu.idle", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[4]));
+ put_ull_value("kernel.all.cpu.wait.total", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[5]));
+ put_ull_value("kernel.all.cpu.irq.hard", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[6]));
+ put_ull_value("kernel.all.cpu.irq.soft", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[7]));
+ put_ull_value("kernel.all.cpu.steal", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[8]));
+ if (f->nfields > 9)
+ put_ull_value("kernel.all.cpu.guest", PM_INDOM_NULL, NULL, ticks_to_msec(f->fields[9]));
+
+ put_ull_value("kernel.all.cpu.intr", PM_INDOM_NULL, NULL,
+ 1000 * ((double)strtoull(f->fields[6], NULL, 0) +
+ (double)strtoull(f->fields[7], NULL, 0)) / kernel_all_hz);
+ }
+
+ return 0;
+}
diff --git a/src/collectl2pcp/disk.c b/src/collectl2pcp/disk.c
new file mode 100644
index 0000000..6ea8940
--- /dev/null
+++ b/src/collectl2pcp/disk.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for disk.dev
+ *
+ * disk 8 0 sda 13911739 267073 1248592592 58081804 4912190 5603474 382020376 181762699 0 53315955 240138732
+ */
+
+#include "metrics.h"
+
+int
+disk_handler(handler_t *h, fields_t *f)
+{
+ char *inst;
+ pmInDom indom = pmInDom_build(LINUX_DOMAIN, DISK_INDOM);
+
+ if (f->nfields != 15)
+ return -1;
+
+ /* disk.dev.* */
+ inst = f->fields[3]; /* diskname */
+
+ put_str_value("disk.dev.read", indom, inst, f->fields[4]);
+ put_str_value("disk.dev.read_merge", indom, inst, f->fields[5]);
+ put_str_value("disk.dev.blkread", indom, inst, f->fields[6]);
+ /* skip read_ticks at f->fields[7] */
+ put_str_value("disk.dev.write", indom, inst, f->fields[8]);
+ put_str_value("disk.dev.write_merge", indom, inst, f->fields[9]);
+ put_str_value("disk.dev.blkwrite", indom, inst, f->fields[10]);
+ /* skip write_ticks at f->fields[11] */
+ /* skip in_flight at f->fields[12] */
+ put_str_value("disk.dev.avactive", indom, inst, f->fields[13]);
+ put_str_value("disk.dev.aveq", indom, inst, f->fields[14]);
+
+ /* derived values */
+ put_ull_value("disk.dev.write_bytes", indom, inst, strtoull(f->fields[10], NULL, 0) / 2);
+ put_ull_value("disk.dev.read_bytes", indom, inst, strtoull(f->fields[6], NULL, 0) / 2);
+ put_ull_value("disk.dev.total_bytes", indom, inst,
+ strtoull(f->fields[6], NULL, 0) + strtoull(f->fields[10], NULL, 0));
+
+ return 0;
+}
+
diff --git a/src/collectl2pcp/generic.c b/src/collectl2pcp/generic.c
new file mode 100644
index 0000000..5cfb973
--- /dev/null
+++ b/src/collectl2pcp/generic.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Generic handler for singular metrics with value in fields[1] e.g. :
+ * "processes 516779"
+ *
+ */
+
+#include "metrics.h"
+
+/* generic handler for <tag> <value> */
+int
+generic1_handler(handler_t *h, fields_t *f)
+{
+ put_str_value(h->metric_name, PM_INDOM_NULL, NULL, f->fields[1]);
+ return 0;
+}
+
+/* generic handler for <tag> <somethingelse> <value> */
+int
+generic2_handler(handler_t *h, fields_t *f)
+{
+ put_str_value(h->metric_name, PM_INDOM_NULL, NULL, f->fields[2]);
+ return 0;
+}
diff --git a/src/collectl2pcp/header.c b/src/collectl2pcp/header.c
new file mode 100644
index 0000000..2bd0129
--- /dev/null
+++ b/src/collectl2pcp/header.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Parse collectl header up to (and including) the first timestamp.
+ */
+
+#include "metrics.h"
+
+static char *hostname = NULL;
+int header_time = 0;
+
+int
+header_handler(FILE *fp, char *fname, char *buf, int maxbuf)
+{
+ int i;
+ char *s;
+ fields_t *f;
+ int sts = 0;
+
+
+ while(fgets(buf, maxbuf, fp)) {
+ if ((s = strrchr(buf, '\n')) != NULL)
+ *s = '\0';
+ if (!buf[0])
+ continue;
+
+ f = fields_new(buf, strlen(buf)+1);
+ if (f->nfields == 0) {
+ fields_free(f);
+ continue;
+ }
+
+ if (strncmp(f->fields[0], ">>>", 3) == 0) {
+ /* first timestamp: we're finished parsing the header */
+ sts = timestamp_handler(find_handler(">>>"), f);
+ fields_free(f);
+ break;
+ }
+
+ if (f->nfields > 3 && strncmp(f->fields[1], "Host:", 5) == 0) {
+ /* # Host: somehostname ... */
+ if (hostname && strcmp(hostname, f->fields[2]) != 0) {
+ fprintf(stderr, "FATAL Error: host mismatch: \"%s\" contains data for host \"%s\", not \"%s\"\n",
+ fname, f->fields[2], hostname);
+ exit(1);
+ }
+
+ if (!hostname) {
+ hostname = strdup(f->fields[2]);
+ pmiSetHostname(hostname);
+ put_str_value("kernel.uname.nodename", PM_INDOM_NULL, NULL, hostname);
+ put_str_value("kernel.uname.sysname", PM_INDOM_NULL, NULL, "Linux");
+ }
+ }
+
+ if (f->nfields > 2 && strncmp(f->fields[1], "Distro:", 7) == 0) {
+ strcpy(buf, f->fields[2]);
+ for (i=3; i < f->nfields; i++) {
+ if (strcmp(f->fields[i], "Platform:") == 0)
+ break;
+ strcat(buf, " ");
+ strcat(buf, f->fields[i]);
+ }
+ put_str_value("kernel.uname.distro", PM_INDOM_NULL, NULL, buf);
+
+#if 0 /* TODO -- add hinv.platform */
+ if (i < f->nfields) { /* found embedded platform */
+ strcpy(buf, f->fields[++i]);
+ for (; i < f->nfields; i++) {
+ strcat(buf, " ");
+ strcat(buf, f->fields[i]);
+ }
+ put_str_value("hinv.platform", PM_INDOM_NULL, NULL, buf);
+ }
+#endif
+ }
+
+ if (f->nfields == 7 && strncmp(f->fields[1], "Date:", 5) == 0) {
+ /* # Date: 20130505-170328 Secs: 1367791408 TZ: -0500 */
+ int d = strtol(f->fields[4], NULL, 0);
+
+ if (d < header_time) {
+ fprintf(stderr, "FATAL Error: input file order mismatch: \"%s\" contains data at %d, prior to %d\n",
+ fname, d, header_time);
+ exit(1);
+ }
+ header_time = d;
+ sts = pmiSetTimezone(f->fields[6]);
+ utc_offset = strtol(f->fields[6], NULL, 0);
+ sscanf(f->fields[6], "%d", &utc_offset); /* e.g. -0500 */
+ utc_offset /= 100;
+ if (vflag)
+ printf("Timezone set to \"%s\" utc_offset=%d hours, sts=%d\n", f->fields[6], utc_offset, sts);
+ }
+
+ if (f->nfields > 8 && strncmp(f->fields[1], "SubSys:", 7) == 0) {
+ /* # SubSys: bcdfijmnstYZ Options: Interval: 10:60 NumCPUs: 24 NumBud: 3 Flags: i */
+ put_str_value("hinv.ncpu", PM_INDOM_NULL, NULL, f->fields[7]);
+ }
+
+ if (f->nfields == 7 && strncmp(f->fields[1], "HZ:", 3) == 0) {
+ /* # HZ: 100 Arch: x86_64-linux-thread-multi PageSize: 4096 */
+ kernel_all_hz = strtol(f->fields[2], NULL, 0);
+ put_str_value("kernel.all.hz", PM_INDOM_NULL, NULL, f->fields[2]);
+ put_str_value("kernel.uname.machine", PM_INDOM_NULL, NULL, f->fields[4]);
+ put_str_value("hinv.pagesize", PM_INDOM_NULL, NULL, f->fields[6]);
+ }
+
+ if (f->nfields == 9 && strncmp(f->fields[1], "Kernel:", 7) == 0) {
+ /* # Kernel: 2.6.18-274.17.1.el5 Memory: 131965176 kB Swap: 134215000 kB */
+ put_str_value("kernel.uname.release", PM_INDOM_NULL, NULL, f->fields[2]);
+ put_int_value("hinv.physmem", PM_INDOM_NULL, NULL, atoi(f->fields[4])/1024);
+ put_str_value("hinv.machine", PM_INDOM_NULL, NULL, "linux");
+ }
+
+ if (f->nfields > 4 && strncmp(f->fields[1], "NumDisks:", 9) == 0) {
+ /* # NumDisks: 846 DiskNames: sda sdb .... */
+ put_str_value("hinv.ndisk", PM_INDOM_NULL, NULL, f->fields[2]);
+ }
+
+ if (f->nfields > 4 && strncmp(f->fields[1], "NumNets:", 8) == 0) {
+ /* # NumNets: 5 NetNames: em1: lo: ... */
+ put_str_value("hinv.ninterface", PM_INDOM_NULL, NULL, f->fields[2]);
+ }
+
+ fields_free(f);
+ }
+
+ if (vflag)
+ printf("Parsed header in file:\"%s\" host:\"%s\" sts=%d\n", fname, hostname, sts);
+
+ return sts;
+}
diff --git a/src/collectl2pcp/load.c b/src/collectl2pcp/load.c
new file mode 100644
index 0000000..9343415
--- /dev/null
+++ b/src/collectl2pcp/load.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for load average
+ * "load 1.07 1.18 1.30 3/658 4944"
+ *
+ */
+
+#include "metrics.h"
+
+int
+loadavg_handler(handler_t *h, fields_t *f)
+{
+ pmInDom indom = pmInDom_build(LINUX_DOMAIN, LOADAVG_INDOM);
+ if (f->nfields < 4)
+ return -1;
+ put_str_value("kernel.all.load", indom, "1 minute", f->fields[1]);
+ put_str_value("kernel.all.load", indom, "5 minute", f->fields[2]);
+ put_str_value("kernel.all.load", indom, "15 minute", f->fields[3]);
+
+ return 0;
+}
diff --git a/src/collectl2pcp/metrics.c b/src/collectl2pcp/metrics.c
new file mode 100644
index 0000000..e655cf8
--- /dev/null
+++ b/src/collectl2pcp/metrics.c
@@ -0,0 +1,1672 @@
+/* This file is automatically generated .. do not edit! */
+#include "metrics.h"
+
+metric_t metrics[] = {
+ /* 60.1.9 */ { "hinv.physmem", { 0xf000409, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=2, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.11 */ { "hinv.pagesize", { 0xf00040b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.32 */ { "hinv.ncpu", { 0xf000020, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.33 */ { "hinv.ndisk", { 0xf000021, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.0 */ { "hinv.nfilesys", { 0xf001400, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.27 */ { "hinv.ninterface", { 0xf000c1b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.7 */ { "hinv.machine", { 0xf004807, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.15.0 */ { "hinv.map.scsi", { 0xf003c00, PM_TYPE_STRING, 0xf00000b, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.6 */ { "hinv.map.cpu_num", { 0xf004806, PM_TYPE_U32, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.8 */ { "hinv.map.cpu_node", { 0xf004808, PM_TYPE_U32, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.52.0 */ { "hinv.map.lvname", { 0xf00d000, PM_TYPE_STRING, 0xf000016, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.0 */ { "hinv.cpu.clock", { 0xf004800, PM_TYPE_FLOAT, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=10, .scaleCount=0 } } },
+ /* 60.18.1 */ { "hinv.cpu.vendor", { 0xf004801, PM_TYPE_STRING, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.2 */ { "hinv.cpu.model", { 0xf004802, PM_TYPE_STRING, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.3 */ { "hinv.cpu.stepping", { 0xf004803, PM_TYPE_STRING, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.4 */ { "hinv.cpu.cache", { 0xf004804, PM_TYPE_U32, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.18.5 */ { "hinv.cpu.bogomips", { 0xf004805, PM_TYPE_FLOAT, 0xf000000, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.2.0 */ { "kernel.all.load", { 0xf000800, PM_TYPE_FLOAT, 0xf000002, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.12 */ { "kernel.all.intr", { 0xf00000c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.13 */ { "kernel.all.pswitch", { 0xf00000d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.14 */ { "kernel.all.sysfork", { 0xf00000e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.48 */ { "kernel.all.hz", { 0xf000030, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=-1, .dimCount=1, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.26.0 */ { "kernel.all.uptime", { 0xf006800, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.26.1 */ { "kernel.all.idletime", { 0xf006801, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.25.0 */ { "kernel.all.nusers", { 0xf006400, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.2.1 */ { "kernel.all.lastpid", { 0xf000801, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.2.2 */ { "kernel.all.runnable", { 0xf000802, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.2.3 */ { "kernel.all.nprocs", { 0xf000803, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.20 */ { "kernel.all.cpu.user", { 0xf000014, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.21 */ { "kernel.all.cpu.nice", { 0xf000015, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.22 */ { "kernel.all.cpu.sys", { 0xf000016, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.23 */ { "kernel.all.cpu.idle", { 0xf000017, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.34 */ { "kernel.all.cpu.intr", { 0xf000022, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.55 */ { "kernel.all.cpu.steal", { 0xf000037, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.60 */ { "kernel.all.cpu.guest", { 0xf00003c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.35 */ { "kernel.all.cpu.wait.total", { 0xf000023, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.53 */ { "kernel.all.cpu.irq.soft", { 0xf000035, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.54 */ { "kernel.all.cpu.irq.hard", { 0xf000036, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.4.3 */ { "kernel.all.interrupts.errors", { 0xf001003, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.11 */ { "kernel.percpu.interrupts.MCP", { 0xf00c80b, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.10 */ { "kernel.percpu.interrupts.MCE", { 0xf00c80a, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.9 */ { "kernel.percpu.interrupts.THR", { 0xf00c809, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.8 */ { "kernel.percpu.interrupts.TRM", { 0xf00c808, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.7 */ { "kernel.percpu.interrupts.TLB", { 0xf00c807, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.6 */ { "kernel.percpu.interrupts.CAL", { 0xf00c806, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.5 */ { "kernel.percpu.interrupts.RES", { 0xf00c805, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.4 */ { "kernel.percpu.interrupts.RTR", { 0xf00c804, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.3 */ { "kernel.percpu.interrupts.IWI", { 0xf00c803, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.2 */ { "kernel.percpu.interrupts.PMI", { 0xf00c802, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.1 */ { "kernel.percpu.interrupts.SPU", { 0xf00c801, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.50.0 */ { "kernel.percpu.interrupts.LOC", { 0xf00c800, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.14 */ { "kernel.percpu.interrupts.line46", { 0xf00c40e, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.13 */ { "kernel.percpu.interrupts.line45", { 0xf00c40d, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.12 */ { "kernel.percpu.interrupts.line44", { 0xf00c40c, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.11 */ { "kernel.percpu.interrupts.line43", { 0xf00c40b, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.10 */ { "kernel.percpu.interrupts.line42", { 0xf00c40a, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.9 */ { "kernel.percpu.interrupts.line41", { 0xf00c409, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.8 */ { "kernel.percpu.interrupts.line40", { 0xf00c408, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.7 */ { "kernel.percpu.interrupts.line23", { 0xf00c407, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.6 */ { "kernel.percpu.interrupts.line19", { 0xf00c406, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.5 */ { "kernel.percpu.interrupts.line16", { 0xf00c405, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.4 */ { "kernel.percpu.interrupts.line12", { 0xf00c404, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.3 */ { "kernel.percpu.interrupts.line9", { 0xf00c403, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.2 */ { "kernel.percpu.interrupts.line8", { 0xf00c402, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.1 */ { "kernel.percpu.interrupts.line1", { 0xf00c401, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.49.0 */ { "kernel.percpu.interrupts.line0", { 0xf00c400, PM_TYPE_U32, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.0 */ { "kernel.percpu.cpu.user", { 0xf000000, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.1 */ { "kernel.percpu.cpu.nice", { 0xf000001, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.2 */ { "kernel.percpu.cpu.sys", { 0xf000002, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.3 */ { "kernel.percpu.cpu.idle", { 0xf000003, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.31 */ { "kernel.percpu.cpu.intr", { 0xf00001f, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.58 */ { "kernel.percpu.cpu.steal", { 0xf00003a, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.61 */ { "kernel.percpu.cpu.guest", { 0xf00003d, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.30 */ { "kernel.percpu.cpu.wait.total", { 0xf00001e, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.56 */ { "kernel.percpu.cpu.irq.soft", { 0xf000038, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.57 */ { "kernel.percpu.cpu.irq.hard", { 0xf000039, PM_TYPE_U64, 0xf000000, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.62 */ { "kernel.pernode.cpu.user", { 0xf00003e, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.63 */ { "kernel.pernode.cpu.nice", { 0xf00003f, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.64 */ { "kernel.pernode.cpu.sys", { 0xf000040, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.65 */ { "kernel.pernode.cpu.idle", { 0xf000041, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.66 */ { "kernel.pernode.cpu.intr", { 0xf000042, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.67 */ { "kernel.pernode.cpu.steal", { 0xf000043, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.68 */ { "kernel.pernode.cpu.guest", { 0xf000044, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.69 */ { "kernel.pernode.cpu.wait.total", { 0xf000045, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.70 */ { "kernel.pernode.cpu.irq.soft", { 0xf000046, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.71 */ { "kernel.pernode.cpu.irq.hard", { 0xf000047, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.12.0 */ { "kernel.uname.release", { 0xf003000, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.1 */ { "kernel.uname.version", { 0xf003001, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.2 */ { "kernel.uname.sysname", { 0xf003002, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.3 */ { "kernel.uname.machine", { 0xf003003, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.4 */ { "kernel.uname.nodename", { 0xf003004, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.7 */ { "kernel.uname.distro", { 0xf003007, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.0 */ { "mem.physmem", { 0xf000400, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.10 */ { "mem.freemem", { 0xf00040a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.1 */ { "mem.util.used", { 0xf000401, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.2 */ { "mem.util.free", { 0xf000402, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.3 */ { "mem.util.shared", { 0xf000403, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.4 */ { "mem.util.bufmem", { 0xf000404, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.5 */ { "mem.util.cached", { 0xf000405, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.12 */ { "mem.util.other", { 0xf00040c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.13 */ { "mem.util.swapCached", { 0xf00040d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.14 */ { "mem.util.active", { 0xf00040e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.15 */ { "mem.util.inactive", { 0xf00040f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.16 */ { "mem.util.highTotal", { 0xf000410, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.17 */ { "mem.util.highFree", { 0xf000411, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.18 */ { "mem.util.lowTotal", { 0xf000412, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.19 */ { "mem.util.lowFree", { 0xf000413, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.20 */ { "mem.util.swapTotal", { 0xf000414, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.21 */ { "mem.util.swapFree", { 0xf000415, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.22 */ { "mem.util.dirty", { 0xf000416, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.23 */ { "mem.util.writeback", { 0xf000417, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.24 */ { "mem.util.mapped", { 0xf000418, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.25 */ { "mem.util.slab", { 0xf000419, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.26 */ { "mem.util.committed_AS", { 0xf00041a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.27 */ { "mem.util.pageTables", { 0xf00041b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.28 */ { "mem.util.reverseMaps", { 0xf00041c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.29 */ { "mem.util.cache_clean", { 0xf00041d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.30 */ { "mem.util.anonpages", { 0xf00041e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.31 */ { "mem.util.commitLimit", { 0xf00041f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.32 */ { "mem.util.bounce", { 0xf000420, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.33 */ { "mem.util.NFS_Unstable", { 0xf000421, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.34 */ { "mem.util.slabReclaimable", { 0xf000422, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.35 */ { "mem.util.slabUnreclaimable", { 0xf000423, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.36 */ { "mem.util.active_anon", { 0xf000424, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.37 */ { "mem.util.inactive_anon", { 0xf000425, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.38 */ { "mem.util.active_file", { 0xf000426, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.39 */ { "mem.util.inactive_file", { 0xf000427, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.40 */ { "mem.util.unevictable", { 0xf000428, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.41 */ { "mem.util.mlocked", { 0xf000429, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.42 */ { "mem.util.shmem", { 0xf00042a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.43 */ { "mem.util.kernelStack", { 0xf00042b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.44 */ { "mem.util.hugepagesTotal", { 0xf00042c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.45 */ { "mem.util.hugepagesFree", { 0xf00042d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.46 */ { "mem.util.hugepagesRsvd", { 0xf00042e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.47 */ { "mem.util.hugepagesSurp", { 0xf00042f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.48 */ { "mem.util.directMap4k", { 0xf000430, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.49 */ { "mem.util.directMap2M", { 0xf000431, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.50 */ { "mem.util.vmallocTotal", { 0xf000432, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.51 */ { "mem.util.vmallocUsed", { 0xf000433, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.52 */ { "mem.util.vmallocChunk", { 0xf000434, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.53 */ { "mem.util.mmap_copy", { 0xf000435, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.54 */ { "mem.util.quicklists", { 0xf000436, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.55 */ { "mem.util.corrupthardware", { 0xf000437, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.56 */ { "mem.util.anonhugepages", { 0xf000438, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.0 */ { "mem.numa.util.total", { 0xf009000, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.1 */ { "mem.numa.util.free", { 0xf009001, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.2 */ { "mem.numa.util.used", { 0xf009002, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.3 */ { "mem.numa.util.active", { 0xf009003, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.4 */ { "mem.numa.util.inactive", { 0xf009004, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.5 */ { "mem.numa.util.active_anon", { 0xf009005, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.6 */ { "mem.numa.util.inactive_anon", { 0xf009006, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.7 */ { "mem.numa.util.active_file", { 0xf009007, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.8 */ { "mem.numa.util.inactive_file", { 0xf009008, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.9 */ { "mem.numa.util.highTotal", { 0xf009009, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.10 */ { "mem.numa.util.highFree", { 0xf00900a, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.11 */ { "mem.numa.util.lowTotal", { 0xf00900b, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.12 */ { "mem.numa.util.lowFree", { 0xf00900c, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.13 */ { "mem.numa.util.unevictable", { 0xf00900d, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.14 */ { "mem.numa.util.mlocked", { 0xf00900e, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.15 */ { "mem.numa.util.dirty", { 0xf00900f, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.16 */ { "mem.numa.util.writeback", { 0xf009010, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.17 */ { "mem.numa.util.filePages", { 0xf009011, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.18 */ { "mem.numa.util.mapped", { 0xf009012, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.19 */ { "mem.numa.util.anonpages", { 0xf009013, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.20 */ { "mem.numa.util.shmem", { 0xf009014, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.21 */ { "mem.numa.util.kernelStack", { 0xf009015, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.22 */ { "mem.numa.util.pageTables", { 0xf009016, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.23 */ { "mem.numa.util.NFS_Unstable", { 0xf009017, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.24 */ { "mem.numa.util.bounce", { 0xf009018, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.25 */ { "mem.numa.util.writebackTmp", { 0xf009019, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.26 */ { "mem.numa.util.slab", { 0xf00901a, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.27 */ { "mem.numa.util.slabReclaimable", { 0xf00901b, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.28 */ { "mem.numa.util.slabUnreclaimable", { 0xf00901c, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.29 */ { "mem.numa.util.hugepagesTotal", { 0xf00901d, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.30 */ { "mem.numa.util.hugepagesFree", { 0xf00901e, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.31 */ { "mem.numa.util.hugepagesSurp", { 0xf00901f, PM_TYPE_U64, 0xf000013, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.32 */ { "mem.numa.alloc.hit", { 0xf009020, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.33 */ { "mem.numa.alloc.miss", { 0xf009021, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.34 */ { "mem.numa.alloc.foreign", { 0xf009022, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.35 */ { "mem.numa.alloc.interleave_hit", { 0xf009023, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.36 */ { "mem.numa.alloc.local_node", { 0xf009024, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.36.37 */ { "mem.numa.alloc.other_node", { 0xf009025, PM_TYPE_U64, 0xf000013, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.0 */ { "mem.slabinfo.objects.active", { 0xf005000, PM_TYPE_U64, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.1 */ { "mem.slabinfo.objects.total", { 0xf005001, PM_TYPE_U64, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.2 */ { "mem.slabinfo.objects.size", { 0xf005002, PM_TYPE_U32, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.3 */ { "mem.slabinfo.slabs.active", { 0xf005003, PM_TYPE_U32, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.4 */ { "mem.slabinfo.slabs.total", { 0xf005004, PM_TYPE_U32, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.5 */ { "mem.slabinfo.slabs.pages_per_slab", { 0xf005005, PM_TYPE_U32, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.6 */ { "mem.slabinfo.slabs.objects_per_slab", { 0xf005006, PM_TYPE_U32, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.20.7 */ { "mem.slabinfo.slabs.total_size", { 0xf005007, PM_TYPE_U64, 0xf00000c, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.35 */ { "mem.vmstat.allocstall", { 0xf007023, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.57 */ { "mem.vmstat.compact_blocks_moved", { 0xf007039, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.58 */ { "mem.vmstat.compact_fail", { 0xf00703a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.59 */ { "mem.vmstat.compact_pagemigrate_failed", { 0xf00703b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.60 */ { "mem.vmstat.compact_pages_moved", { 0xf00703c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.61 */ { "mem.vmstat.compact_stall", { 0xf00703d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.62 */ { "mem.vmstat.compact_success", { 0xf00703e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.43 */ { "mem.vmstat.htlb_buddy_alloc_fail", { 0xf00702b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.44 */ { "mem.vmstat.htlb_buddy_alloc_success", { 0xf00702c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.33 */ { "mem.vmstat.kswapd_inodesteal", { 0xf007021, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.87 */ { "mem.vmstat.kswapd_low_wmark_hit_quickly", { 0xf007057, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.88 */ { "mem.vmstat.kswapd_high_wmark_hit_quickly", { 0xf007058, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.89 */ { "mem.vmstat.kswapd_skip_congestion_wait", { 0xf007059, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.32 */ { "mem.vmstat.kswapd_steal", { 0xf007020, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.45 */ { "mem.vmstat.nr_active_anon", { 0xf00702d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.46 */ { "mem.vmstat.nr_active_file", { 0xf00702e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.39 */ { "mem.vmstat.nr_anon_pages", { 0xf007027, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.90 */ { "mem.vmstat.nr_anon_transparent_hugepages", { 0xf00705a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.40 */ { "mem.vmstat.nr_bounce", { 0xf007028, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.91 */ { "mem.vmstat.nr_dirtied", { 0xf00705b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.0 */ { "mem.vmstat.nr_dirty", { 0xf007000, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.92 */ { "mem.vmstat.nr_dirty_background_threshold", { 0xf00705c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.93 */ { "mem.vmstat.nr_dirty_threshold", { 0xf00705d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.47 */ { "mem.vmstat.nr_free_pages", { 0xf00702f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.48 */ { "mem.vmstat.nr_inactive_anon", { 0xf007030, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.49 */ { "mem.vmstat.nr_inactive_file", { 0xf007031, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.50 */ { "mem.vmstat.nr_isolated_anon", { 0xf007032, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.51 */ { "mem.vmstat.nr_isolated_file", { 0xf007033, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.52 */ { "mem.vmstat.nr_kernel_stack", { 0xf007034, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.4 */ { "mem.vmstat.nr_mapped", { 0xf007004, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.53 */ { "mem.vmstat.nr_mlock", { 0xf007035, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.3 */ { "mem.vmstat.nr_page_table_pages", { 0xf007003, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.54 */ { "mem.vmstat.nr_shmem", { 0xf007036, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.5 */ { "mem.vmstat.nr_slab", { 0xf007005, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.37 */ { "mem.vmstat.nr_slab_reclaimable", { 0xf007025, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.38 */ { "mem.vmstat.nr_slab_unreclaimable", { 0xf007026, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.55 */ { "mem.vmstat.nr_unevictable", { 0xf007037, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.2 */ { "mem.vmstat.nr_unstable", { 0xf007002, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.42 */ { "mem.vmstat.nr_vmscan_write", { 0xf00702a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.1 */ { "mem.vmstat.nr_writeback", { 0xf007001, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.56 */ { "mem.vmstat.nr_writeback_temp", { 0xf007038, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.94 */ { "mem.vmstat.nr_written", { 0xf00705e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.95 */ { "mem.vmstat.numa_foreign", { 0xf00705f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.96 */ { "mem.vmstat.numa_hit", { 0xf007060, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.97 */ { "mem.vmstat.numa_interleave", { 0xf007061, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.98 */ { "mem.vmstat.numa_local", { 0xf007062, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.99 */ { "mem.vmstat.numa_miss", { 0xf007063, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.100 */ { "mem.vmstat.numa_other", { 0xf007064, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.34 */ { "mem.vmstat.pageoutrun", { 0xf007022, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.14 */ { "mem.vmstat.pgactivate", { 0xf00700e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.12 */ { "mem.vmstat.pgalloc_dma", { 0xf00700c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.63 */ { "mem.vmstat.pgalloc_dma32", { 0xf00703f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.10 */ { "mem.vmstat.pgalloc_high", { 0xf00700a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.64 */ { "mem.vmstat.pgalloc_movable", { 0xf007040, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.11 */ { "mem.vmstat.pgalloc_normal", { 0xf00700b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.65 */ { "mem.vmstat.pgrefill_dma32", { 0xf007041, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.66 */ { "mem.vmstat.pgrefill_movable", { 0xf007042, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.15 */ { "mem.vmstat.pgdeactivate", { 0xf00700f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.16 */ { "mem.vmstat.pgfault", { 0xf007010, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.13 */ { "mem.vmstat.pgfree", { 0xf00700d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.30 */ { "mem.vmstat.pginodesteal", { 0xf00701e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.17 */ { "mem.vmstat.pgmajfault", { 0xf007011, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.6 */ { "mem.vmstat.pgpgin", { 0xf007006, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.7 */ { "mem.vmstat.pgpgout", { 0xf007007, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.20 */ { "mem.vmstat.pgrefill_dma", { 0xf007014, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.18 */ { "mem.vmstat.pgrefill_high", { 0xf007012, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.19 */ { "mem.vmstat.pgrefill_normal", { 0xf007013, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.36 */ { "mem.vmstat.pgrotated", { 0xf007024, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.29 */ { "mem.vmstat.pgscan_direct_dma", { 0xf00701d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.67 */ { "mem.vmstat.pgscan_direct_dma32", { 0xf007043, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.27 */ { "mem.vmstat.pgscan_direct_high", { 0xf00701b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.68 */ { "mem.vmstat.pgscan_direct_movable", { 0xf007044, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.28 */ { "mem.vmstat.pgscan_direct_normal", { 0xf00701c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.26 */ { "mem.vmstat.pgscan_kswapd_dma", { 0xf00701a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.69 */ { "mem.vmstat.pgscan_kswapd_dma32", { 0xf007045, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.24 */ { "mem.vmstat.pgscan_kswapd_high", { 0xf007018, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.70 */ { "mem.vmstat.pgscan_kswapd_movable", { 0xf007046, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.25 */ { "mem.vmstat.pgscan_kswapd_normal", { 0xf007019, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.23 */ { "mem.vmstat.pgsteal_dma", { 0xf007017, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.71 */ { "mem.vmstat.pgsteal_dma32", { 0xf007047, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.21 */ { "mem.vmstat.pgsteal_high", { 0xf007015, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.72 */ { "mem.vmstat.pgsteal_movable", { 0xf007048, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.22 */ { "mem.vmstat.pgsteal_normal", { 0xf007016, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.8 */ { "mem.vmstat.pswpin", { 0xf007008, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.9 */ { "mem.vmstat.pswpout", { 0xf007009, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.31 */ { "mem.vmstat.slabs_scanned", { 0xf00701f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.73 */ { "mem.vmstat.thp_fault_alloc", { 0xf007049, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.74 */ { "mem.vmstat.thp_fault_fallback", { 0xf00704a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.75 */ { "mem.vmstat.thp_collapse_alloc", { 0xf00704b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.76 */ { "mem.vmstat.thp_collapse_alloc_failed", { 0xf00704c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.77 */ { "mem.vmstat.thp_split", { 0xf00704d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.78 */ { "mem.vmstat.unevictable_pgs_cleared", { 0xf00704e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.79 */ { "mem.vmstat.unevictable_pgs_culled", { 0xf00704f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.80 */ { "mem.vmstat.unevictable_pgs_mlocked", { 0xf007050, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.81 */ { "mem.vmstat.unevictable_pgs_mlockfreed", { 0xf007051, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.82 */ { "mem.vmstat.unevictable_pgs_munlocked", { 0xf007052, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.83 */ { "mem.vmstat.unevictable_pgs_rescued", { 0xf007053, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.84 */ { "mem.vmstat.unevictable_pgs_scanned", { 0xf007054, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.85 */ { "mem.vmstat.unevictable_pgs_stranded", { 0xf007055, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.28.86 */ { "mem.vmstat.zone_reclaim_failed", { 0xf007056, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.8 */ { "swap.pagesin", { 0xf000008, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.9 */ { "swap.pagesout", { 0xf000009, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.10 */ { "swap.in", { 0xf00000a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.11 */ { "swap.out", { 0xf00000b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.8 */ { "swap.free", { 0xf000408, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.6 */ { "swap.length", { 0xf000406, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.1.7 */ { "swap.used", { 0xf000407, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.13 */ { "network.interface.collisions", { 0xf000c0d, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.21 */ { "network.interface.mtu", { 0xf000c15, PM_TYPE_U32, 0xf000003, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.22 */ { "network.interface.speed", { 0xf000c16, PM_TYPE_FLOAT, 0xf000003, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=-1, .dimCount=0, .scaleSpace=2, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.3.23 */ { "network.interface.baudrate", { 0xf000c17, PM_TYPE_U32, 0xf000003, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=-1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.3.24 */ { "network.interface.duplex", { 0xf000c18, PM_TYPE_U32, 0xf000003, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.25 */ { "network.interface.up", { 0xf000c19, PM_TYPE_U32, 0xf000003, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.26 */ { "network.interface.running", { 0xf000c1a, PM_TYPE_U32, 0xf000003, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.33.0 */ { "network.interface.inet_addr", { 0xf008400, PM_TYPE_STRING, 0xf000011, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.0 */ { "network.interface.in.bytes", { 0xf000c00, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.1 */ { "network.interface.in.packets", { 0xf000c01, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.2 */ { "network.interface.in.errors", { 0xf000c02, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.3 */ { "network.interface.in.drops", { 0xf000c03, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.4 */ { "network.interface.in.fifo", { 0xf000c04, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.5 */ { "network.interface.in.frame", { 0xf000c05, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.6 */ { "network.interface.in.compressed", { 0xf000c06, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.7 */ { "network.interface.in.mcasts", { 0xf000c07, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.8 */ { "network.interface.out.bytes", { 0xf000c08, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.9 */ { "network.interface.out.packets", { 0xf000c09, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.10 */ { "network.interface.out.errors", { 0xf000c0a, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.11 */ { "network.interface.out.drops", { 0xf000c0b, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.12 */ { "network.interface.out.fifo", { 0xf000c0c, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.14 */ { "network.interface.out.carrier", { 0xf000c0e, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.15 */ { "network.interface.out.compressed", { 0xf000c0f, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.16 */ { "network.interface.total.bytes", { 0xf000c10, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.17 */ { "network.interface.total.packets", { 0xf000c11, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.18 */ { "network.interface.total.errors", { 0xf000c12, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.19 */ { "network.interface.total.drops", { 0xf000c13, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.3.20 */ { "network.interface.total.mcasts", { 0xf000c14, PM_TYPE_U64, 0xf000003, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.0 */ { "network.sockstat.tcp.inuse", { 0xf002c00, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.1 */ { "network.sockstat.tcp.highest", { 0xf002c01, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.2 */ { "network.sockstat.tcp.util", { 0xf002c02, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.3 */ { "network.sockstat.udp.inuse", { 0xf002c03, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.4 */ { "network.sockstat.udp.highest", { 0xf002c04, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.5 */ { "network.sockstat.udp.util", { 0xf002c05, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.6 */ { "network.sockstat.raw.inuse", { 0xf002c06, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.7 */ { "network.sockstat.raw.highest", { 0xf002c07, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.11.8 */ { "network.sockstat.raw.util", { 0xf002c08, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.0 */ { "network.ip.forwarding", { 0xf003800, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.1 */ { "network.ip.defaultttl", { 0xf003801, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.2 */ { "network.ip.inreceives", { 0xf003802, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.3 */ { "network.ip.inhdrerrors", { 0xf003803, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.4 */ { "network.ip.inaddrerrors", { 0xf003804, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.5 */ { "network.ip.forwdatagrams", { 0xf003805, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.6 */ { "network.ip.inunknownprotos", { 0xf003806, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.7 */ { "network.ip.indiscards", { 0xf003807, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.8 */ { "network.ip.indelivers", { 0xf003808, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.9 */ { "network.ip.outrequests", { 0xf003809, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.10 */ { "network.ip.outdiscards", { 0xf00380a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.11 */ { "network.ip.outnoroutes", { 0xf00380b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.12 */ { "network.ip.reasmtimeout", { 0xf00380c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.13 */ { "network.ip.reasmreqds", { 0xf00380d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.14 */ { "network.ip.reasmoks", { 0xf00380e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.15 */ { "network.ip.reasmfails", { 0xf00380f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.16 */ { "network.ip.fragoks", { 0xf003810, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.17 */ { "network.ip.fragfails", { 0xf003811, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.18 */ { "network.ip.fragcreates", { 0xf003812, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.20 */ { "network.icmp.inmsgs", { 0xf003814, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.21 */ { "network.icmp.inerrors", { 0xf003815, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.22 */ { "network.icmp.indestunreachs", { 0xf003816, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.23 */ { "network.icmp.intimeexcds", { 0xf003817, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.24 */ { "network.icmp.inparmprobs", { 0xf003818, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.25 */ { "network.icmp.insrcquenchs", { 0xf003819, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.26 */ { "network.icmp.inredirects", { 0xf00381a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.27 */ { "network.icmp.inechos", { 0xf00381b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.28 */ { "network.icmp.inechoreps", { 0xf00381c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.29 */ { "network.icmp.intimestamps", { 0xf00381d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.30 */ { "network.icmp.intimestampreps", { 0xf00381e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.31 */ { "network.icmp.inaddrmasks", { 0xf00381f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.32 */ { "network.icmp.inaddrmaskreps", { 0xf003820, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.33 */ { "network.icmp.outmsgs", { 0xf003821, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.34 */ { "network.icmp.outerrors", { 0xf003822, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.35 */ { "network.icmp.outdestunreachs", { 0xf003823, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.36 */ { "network.icmp.outtimeexcds", { 0xf003824, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.37 */ { "network.icmp.outparmprobs", { 0xf003825, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.38 */ { "network.icmp.outsrcquenchs", { 0xf003826, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.39 */ { "network.icmp.outredirects", { 0xf003827, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.40 */ { "network.icmp.outechos", { 0xf003828, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.41 */ { "network.icmp.outechoreps", { 0xf003829, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.42 */ { "network.icmp.outtimestamps", { 0xf00382a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.43 */ { "network.icmp.outtimestampreps", { 0xf00382b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.44 */ { "network.icmp.outaddrmasks", { 0xf00382c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.45 */ { "network.icmp.outaddrmaskreps", { 0xf00382d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.50 */ { "network.tcp.rtoalgorithm", { 0xf003832, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.51 */ { "network.tcp.rtomin", { 0xf003833, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.52 */ { "network.tcp.rtomax", { 0xf003834, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.53 */ { "network.tcp.maxconn", { 0xf003835, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.54 */ { "network.tcp.activeopens", { 0xf003836, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.55 */ { "network.tcp.passiveopens", { 0xf003837, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.56 */ { "network.tcp.attemptfails", { 0xf003838, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.57 */ { "network.tcp.estabresets", { 0xf003839, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.58 */ { "network.tcp.currestab", { 0xf00383a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.59 */ { "network.tcp.insegs", { 0xf00383b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.60 */ { "network.tcp.outsegs", { 0xf00383c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.61 */ { "network.tcp.retranssegs", { 0xf00383d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.62 */ { "network.tcp.inerrs", { 0xf00383e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.63 */ { "network.tcp.outrsts", { 0xf00383f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.70 */ { "network.udp.indatagrams", { 0xf003846, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.71 */ { "network.udp.noports", { 0xf003847, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.72 */ { "network.udp.inerrors", { 0xf003848, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.74 */ { "network.udp.outdatagrams", { 0xf00384a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.75 */ { "network.udp.recvbuferrors", { 0xf00384b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.76 */ { "network.udp.sndbuferrors", { 0xf00384c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.77 */ { "network.udplite.indatagrams", { 0xf00384d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.78 */ { "network.udplite.noports", { 0xf00384e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.79 */ { "network.udplite.inerrors", { 0xf00384f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.80 */ { "network.udplite.outdatagrams", { 0xf003850, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.81 */ { "network.udplite.recvbuferrors", { 0xf003851, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.14.82 */ { "network.udplite.sndbuferrors", { 0xf003852, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.1 */ { "network.tcpconn.established", { 0xf004c01, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.2 */ { "network.tcpconn.syn_sent", { 0xf004c02, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.3 */ { "network.tcpconn.syn_recv", { 0xf004c03, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.4 */ { "network.tcpconn.fin_wait1", { 0xf004c04, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.5 */ { "network.tcpconn.fin_wait2", { 0xf004c05, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.6 */ { "network.tcpconn.time_wait", { 0xf004c06, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.7 */ { "network.tcpconn.close", { 0xf004c07, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.8 */ { "network.tcpconn.close_wait", { 0xf004c08, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.9 */ { "network.tcpconn.last_ack", { 0xf004c09, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.10 */ { "network.tcpconn.listen", { 0xf004c0a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.19.11 */ { "network.tcpconn.closing", { 0xf004c0b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.20 */ { "network.ib.status", { 0xf007414, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.21 */ { "network.ib.control", { 0xf007415, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.0 */ { "network.ib.in.bytes", { 0xf007400, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.1 */ { "network.ib.in.packets", { 0xf007401, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.2 */ { "network.ib.in.errors.drop", { 0xf007402, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.3 */ { "network.ib.in.errors.filter", { 0xf007403, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.4 */ { "network.ib.in.errors.local", { 0xf007404, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.5 */ { "network.ib.in.errors.remote", { 0xf007405, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.6 */ { "network.ib.out.bytes", { 0xf007406, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.7 */ { "network.ib.out.packets", { 0xf007407, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.8 */ { "network.ib.out.errors.drop", { 0xf007408, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.9 */ { "network.ib.out.errors.filter", { 0xf007409, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.16 */ { "network.ib.total.bytes", { 0xf007410, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.17 */ { "network.ib.total.packets", { 0xf007411, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.18 */ { "network.ib.total.errors.drop", { 0xf007412, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.19 */ { "network.ib.total.errors.filter", { 0xf007413, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.10 */ { "network.ib.total.errors.link", { 0xf00740a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.11 */ { "network.ib.total.errors.recover", { 0xf00740b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.12 */ { "network.ib.total.errors.integrity", { 0xf00740c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.13 */ { "network.ib.total.errors.vl15", { 0xf00740d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.14 */ { "network.ib.total.errors.overrun", { 0xf00740e, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.29.15 */ { "network.ib.total.errors.symbol", { 0xf00740f, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.4 */ { "disk.dev.read", { 0xf000004, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.5 */ { "disk.dev.write", { 0xf000005, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.28 */ { "disk.dev.total", { 0xf00001c, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.6 */ { "disk.dev.blkread", { 0xf000006, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.7 */ { "disk.dev.blkwrite", { 0xf000007, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.36 */ { "disk.dev.blktotal", { 0xf000024, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.38 */ { "disk.dev.read_bytes", { 0xf000026, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.39 */ { "disk.dev.write_bytes", { 0xf000027, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.40 */ { "disk.dev.total_bytes", { 0xf000028, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.49 */ { "disk.dev.read_merge", { 0xf000031, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.50 */ { "disk.dev.write_merge", { 0xf000032, PM_TYPE_U64, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.46 */ { "disk.dev.avactive", { 0xf00002e, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.72 */ { "disk.dev.read_rawactive", { 0xf000048, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.73 */ { "disk.dev.write_rawactive", { 0xf000049, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.47 */ { "disk.dev.aveq", { 0xf00002f, PM_TYPE_U32, 0xf000001, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.59 */ { "disk.dev.scheduler", { 0xf00003b, PM_TYPE_STRING, 0xf000001, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.24 */ { "disk.all.read", { 0xf000018, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.25 */ { "disk.all.write", { 0xf000019, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.29 */ { "disk.all.total", { 0xf00001d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.26 */ { "disk.all.blkread", { 0xf00001a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.27 */ { "disk.all.blkwrite", { 0xf00001b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.37 */ { "disk.all.blktotal", { 0xf000025, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.41 */ { "disk.all.read_bytes", { 0xf000029, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.42 */ { "disk.all.write_bytes", { 0xf00002a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.43 */ { "disk.all.total_bytes", { 0xf00002b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.51 */ { "disk.all.read_merge", { 0xf000033, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.52 */ { "disk.all.write_merge", { 0xf000034, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.0.44 */ { "disk.all.avactive", { 0xf00002c, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.74 */ { "disk.all.read_rawactive", { 0xf00004a, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.75 */ { "disk.all.write_rawactive", { 0xf00004b, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.0.45 */ { "disk.all.aveq", { 0xf00002d, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 60.10.0 */ { "disk.partitions.read", { 0xf002800, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.1 */ { "disk.partitions.write", { 0xf002801, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.2 */ { "disk.partitions.total", { 0xf002802, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.3 */ { "disk.partitions.blkread", { 0xf002803, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.4 */ { "disk.partitions.blkwrite", { 0xf002804, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.5 */ { "disk.partitions.blktotal", { 0xf002805, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.6 */ { "disk.partitions.read_bytes", { 0xf002806, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.7 */ { "disk.partitions.write_bytes", { 0xf002807, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.10.8 */ { "disk.partitions.total_bytes", { 0xf002808, PM_TYPE_U32, 0xf00000a, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.1 */ { "filesys.capacity", { 0xf001401, PM_TYPE_U64, 0xf000005, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.2 */ { "filesys.used", { 0xf001402, PM_TYPE_U64, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.3 */ { "filesys.free", { 0xf001403, PM_TYPE_U64, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.4 */ { "filesys.maxfiles", { 0xf001404, PM_TYPE_U32, 0xf000005, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.5 */ { "filesys.usedfiles", { 0xf001405, PM_TYPE_U32, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.6 */ { "filesys.freefiles", { 0xf001406, PM_TYPE_U32, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.7 */ { "filesys.mountdir", { 0xf001407, PM_TYPE_STRING, 0xf000005, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.8 */ { "filesys.full", { 0xf001408, PM_TYPE_DOUBLE, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.9 */ { "filesys.blocksize", { 0xf001409, PM_TYPE_U32, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.10 */ { "filesys.avail", { 0xf00140a, PM_TYPE_U64, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.5.11 */ { "filesys.readonly", { 0xf00140b, PM_TYPE_U32, 0xf000005, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.6.0 */ { "swapdev.free", { 0xf001800, PM_TYPE_U32, 0xf000006, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.6.1 */ { "swapdev.length", { 0xf001801, PM_TYPE_U32, 0xf000006, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.6.2 */ { "swapdev.maxswap", { 0xf001802, PM_TYPE_U32, 0xf000006, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.6.3 */ { "swapdev.vlength", { 0xf001803, PM_TYPE_U32, 0xf000006, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.6.4 */ { "swapdev.priority", { 0xf001804, PM_TYPE_32, 0xf000006, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.20 */ { "rpc.client.rpccnt", { 0xf001c14, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.21 */ { "rpc.client.rpcretrans", { 0xf001c15, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.22 */ { "rpc.client.rpcauthrefresh", { 0xf001c16, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.24 */ { "rpc.client.netcnt", { 0xf001c18, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.25 */ { "rpc.client.netudpcnt", { 0xf001c19, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.26 */ { "rpc.client.nettcpcnt", { 0xf001c1a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.27 */ { "rpc.client.nettcpconn", { 0xf001c1b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.30 */ { "rpc.server.rpccnt", { 0xf001c1e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.31 */ { "rpc.server.rpcerr", { 0xf001c1f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.32 */ { "rpc.server.rpcbadfmt", { 0xf001c20, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.33 */ { "rpc.server.rpcbadauth", { 0xf001c21, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.34 */ { "rpc.server.rpcbadclnt", { 0xf001c22, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.35 */ { "rpc.server.rchits", { 0xf001c23, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.36 */ { "rpc.server.rcmisses", { 0xf001c24, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.37 */ { "rpc.server.rcnocache", { 0xf001c25, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.38 */ { "rpc.server.fh_cached", { 0xf001c26, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.39 */ { "rpc.server.fh_valid", { 0xf001c27, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.40 */ { "rpc.server.fh_fixup", { 0xf001c28, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.41 */ { "rpc.server.fh_lookup", { 0xf001c29, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.42 */ { "rpc.server.fh_stale", { 0xf001c2a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.43 */ { "rpc.server.fh_concurrent", { 0xf001c2b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.44 */ { "rpc.server.netcnt", { 0xf001c2c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.45 */ { "rpc.server.netudpcnt", { 0xf001c2d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.46 */ { "rpc.server.nettcpcnt", { 0xf001c2e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.47 */ { "rpc.server.nettcpconn", { 0xf001c2f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.51 */ { "rpc.server.fh_anon", { 0xf001c33, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.52 */ { "rpc.server.fh_nocache_dir", { 0xf001c34, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.53 */ { "rpc.server.fh_nocache_nondir", { 0xf001c35, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.54 */ { "rpc.server.io_read", { 0xf001c36, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.55 */ { "rpc.server.io_write", { 0xf001c37, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.56 */ { "rpc.server.th_cnt", { 0xf001c38, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.57 */ { "rpc.server.th_fullcnt", { 0xf001c39, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.1 */ { "nfs.client.calls", { 0xf001c01, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.4 */ { "nfs.client.reqs", { 0xf001c04, PM_TYPE_U32, 0xf000007, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.50 */ { "nfs.server.calls", { 0xf001c32, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.12 */ { "nfs.server.reqs", { 0xf001c0c, PM_TYPE_U32, 0xf000007, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.60 */ { "nfs3.client.calls", { 0xf001c3c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.61 */ { "nfs3.client.reqs", { 0xf001c3d, PM_TYPE_U32, 0xf000008, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.62 */ { "nfs3.server.calls", { 0xf001c3e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.63 */ { "nfs3.server.reqs", { 0xf001c3f, PM_TYPE_U32, 0xf000008, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.64 */ { "nfs4.client.calls", { 0xf001c40, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.65 */ { "nfs4.client.reqs", { 0xf001c41, PM_TYPE_U32, 0xf00000e, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.66 */ { "nfs4.server.calls", { 0xf001c42, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.7.67 */ { "nfs4.server.reqs", { 0xf001c43, PM_TYPE_U32, 0xf00000f, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.51 */ { "xfs.write", { 0xf004033, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.52 */ { "xfs.write_bytes", { 0xf004034, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.53 */ { "xfs.read", { 0xf004035, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.54 */ { "xfs.read_bytes", { 0xf004036, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.67 */ { "xfs.iflush_count", { 0xf004043, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.68 */ { "xfs.icluster_flushcnt", { 0xf004044, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.69 */ { "xfs.icluster_flushinode", { 0xf004045, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.0 */ { "xfs.allocs.alloc_extent", { 0xf004000, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.1 */ { "xfs.allocs.alloc_block", { 0xf004001, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.2 */ { "xfs.allocs.free_extent", { 0xf004002, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.3 */ { "xfs.allocs.free_block", { 0xf004003, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.4 */ { "xfs.alloc_btree.lookup", { 0xf004004, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.5 */ { "xfs.alloc_btree.compare", { 0xf004005, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.6 */ { "xfs.alloc_btree.insrec", { 0xf004006, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.7 */ { "xfs.alloc_btree.delrec", { 0xf004007, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.8 */ { "xfs.block_map.read_ops", { 0xf004008, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.9 */ { "xfs.block_map.write_ops", { 0xf004009, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.10 */ { "xfs.block_map.unmap", { 0xf00400a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.11 */ { "xfs.block_map.add_exlist", { 0xf00400b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.12 */ { "xfs.block_map.del_exlist", { 0xf00400c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.13 */ { "xfs.block_map.look_exlist", { 0xf00400d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.14 */ { "xfs.block_map.cmp_exlist", { 0xf00400e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.15 */ { "xfs.bmap_btree.lookup", { 0xf00400f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.16 */ { "xfs.bmap_btree.compare", { 0xf004010, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.17 */ { "xfs.bmap_btree.insrec", { 0xf004011, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.18 */ { "xfs.bmap_btree.delrec", { 0xf004012, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.19 */ { "xfs.dir_ops.lookup", { 0xf004013, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.20 */ { "xfs.dir_ops.create", { 0xf004014, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.21 */ { "xfs.dir_ops.remove", { 0xf004015, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.22 */ { "xfs.dir_ops.getdents", { 0xf004016, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.23 */ { "xfs.transactions.sync", { 0xf004017, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.24 */ { "xfs.transactions.async", { 0xf004018, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.25 */ { "xfs.transactions.empty", { 0xf004019, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.26 */ { "xfs.inode_ops.ig_attempts", { 0xf00401a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.27 */ { "xfs.inode_ops.ig_found", { 0xf00401b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.28 */ { "xfs.inode_ops.ig_frecycle", { 0xf00401c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.29 */ { "xfs.inode_ops.ig_missed", { 0xf00401d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.30 */ { "xfs.inode_ops.ig_dup", { 0xf00401e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.31 */ { "xfs.inode_ops.ig_reclaims", { 0xf00401f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.32 */ { "xfs.inode_ops.ig_attrchg", { 0xf004020, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.33 */ { "xfs.log.writes", { 0xf004021, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.34 */ { "xfs.log.blocks", { 0xf004022, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.78 */ { "xfs.log.write_ratio", { 0xf00404e, PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.35 */ { "xfs.log.noiclogs", { 0xf004023, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.36 */ { "xfs.log.force", { 0xf004024, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.37 */ { "xfs.log.force_sleep", { 0xf004025, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.38 */ { "xfs.log_tail.try_logspace", { 0xf004026, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.39 */ { "xfs.log_tail.sleep_logspace", { 0xf004027, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.40 */ { "xfs.log_tail.push_ail.pushes", { 0xf004028, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.41 */ { "xfs.log_tail.push_ail.success", { 0xf004029, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.42 */ { "xfs.log_tail.push_ail.pushbuf", { 0xf00402a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.43 */ { "xfs.log_tail.push_ail.pinned", { 0xf00402b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.44 */ { "xfs.log_tail.push_ail.locked", { 0xf00402c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.45 */ { "xfs.log_tail.push_ail.flushing", { 0xf00402d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.46 */ { "xfs.log_tail.push_ail.restarts", { 0xf00402e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.47 */ { "xfs.log_tail.push_ail.flush", { 0xf00402f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.48 */ { "xfs.xstrat.bytes", { 0xf004030, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.49 */ { "xfs.xstrat.quick", { 0xf004031, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.50 */ { "xfs.xstrat.split", { 0xf004032, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.55 */ { "xfs.attr.get", { 0xf004037, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.56 */ { "xfs.attr.set", { 0xf004038, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.57 */ { "xfs.attr.remove", { 0xf004039, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.58 */ { "xfs.attr.list", { 0xf00403a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.59 */ { "xfs.quota.reclaims", { 0xf00403b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.60 */ { "xfs.quota.reclaim_misses", { 0xf00403c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.61 */ { "xfs.quota.dquot_dups", { 0xf00403d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.62 */ { "xfs.quota.cachemisses", { 0xf00403e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.63 */ { "xfs.quota.cachehits", { 0xf00403f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.64 */ { "xfs.quota.wants", { 0xf004040, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.65 */ { "xfs.quota.shake_reclaims", { 0xf004041, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.66 */ { "xfs.quota.inact_reclaims", { 0xf004042, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.0 */ { "xfs.buffer.get", { 0xf004400, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.1 */ { "xfs.buffer.create", { 0xf004401, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.2 */ { "xfs.buffer.get_locked", { 0xf004402, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.3 */ { "xfs.buffer.get_locked_waited", { 0xf004403, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.4 */ { "xfs.buffer.busy_locked", { 0xf004404, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.5 */ { "xfs.buffer.miss_locked", { 0xf004405, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.6 */ { "xfs.buffer.page_retries", { 0xf004406, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.7 */ { "xfs.buffer.page_found", { 0xf004407, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.17.8 */ { "xfs.buffer.get_read", { 0xf004408, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.70 */ { "xfs.vnodes.active", { 0xf004046, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.71 */ { "xfs.vnodes.alloc", { 0xf004047, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.72 */ { "xfs.vnodes.get", { 0xf004048, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.73 */ { "xfs.vnodes.hold", { 0xf004049, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.74 */ { "xfs.vnodes.rele", { 0xf00404a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.75 */ { "xfs.vnodes.reclaim", { 0xf00404b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.76 */ { "xfs.vnodes.remove", { 0xf00404c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.77 */ { "xfs.vnodes.free", { 0xf00404d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.79 */ { "xfs.control.reset", { 0xf00404f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.80 */ { "xfs.btree.alloc_blocks.lookup", { 0xf004050, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.81 */ { "xfs.btree.alloc_blocks.compare", { 0xf004051, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.82 */ { "xfs.btree.alloc_blocks.insrec", { 0xf004052, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.83 */ { "xfs.btree.alloc_blocks.delrec", { 0xf004053, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.84 */ { "xfs.btree.alloc_blocks.newroot", { 0xf004054, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.85 */ { "xfs.btree.alloc_blocks.killroot", { 0xf004055, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.86 */ { "xfs.btree.alloc_blocks.increment", { 0xf004056, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.87 */ { "xfs.btree.alloc_blocks.decrement", { 0xf004057, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.88 */ { "xfs.btree.alloc_blocks.lshift", { 0xf004058, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.89 */ { "xfs.btree.alloc_blocks.rshift", { 0xf004059, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.90 */ { "xfs.btree.alloc_blocks.split", { 0xf00405a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.91 */ { "xfs.btree.alloc_blocks.join", { 0xf00405b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.92 */ { "xfs.btree.alloc_blocks.alloc", { 0xf00405c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.93 */ { "xfs.btree.alloc_blocks.free", { 0xf00405d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.94 */ { "xfs.btree.alloc_blocks.moves", { 0xf00405e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.95 */ { "xfs.btree.alloc_contig.lookup", { 0xf00405f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.96 */ { "xfs.btree.alloc_contig.compare", { 0xf004060, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.97 */ { "xfs.btree.alloc_contig.insrec", { 0xf004061, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.98 */ { "xfs.btree.alloc_contig.delrec", { 0xf004062, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.99 */ { "xfs.btree.alloc_contig.newroot", { 0xf004063, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.100 */ { "xfs.btree.alloc_contig.killroot", { 0xf004064, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.101 */ { "xfs.btree.alloc_contig.increment", { 0xf004065, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.102 */ { "xfs.btree.alloc_contig.decrement", { 0xf004066, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.103 */ { "xfs.btree.alloc_contig.lshift", { 0xf004067, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.104 */ { "xfs.btree.alloc_contig.rshift", { 0xf004068, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.105 */ { "xfs.btree.alloc_contig.split", { 0xf004069, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.106 */ { "xfs.btree.alloc_contig.join", { 0xf00406a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.107 */ { "xfs.btree.alloc_contig.alloc", { 0xf00406b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.108 */ { "xfs.btree.alloc_contig.free", { 0xf00406c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.109 */ { "xfs.btree.alloc_contig.moves", { 0xf00406d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.110 */ { "xfs.btree.block_map.lookup", { 0xf00406e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.111 */ { "xfs.btree.block_map.compare", { 0xf00406f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.112 */ { "xfs.btree.block_map.insrec", { 0xf004070, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.113 */ { "xfs.btree.block_map.delrec", { 0xf004071, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.114 */ { "xfs.btree.block_map.newroot", { 0xf004072, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.115 */ { "xfs.btree.block_map.killroot", { 0xf004073, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.116 */ { "xfs.btree.block_map.increment", { 0xf004074, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.117 */ { "xfs.btree.block_map.decrement", { 0xf004075, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.118 */ { "xfs.btree.block_map.lshift", { 0xf004076, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.119 */ { "xfs.btree.block_map.rshift", { 0xf004077, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.120 */ { "xfs.btree.block_map.split", { 0xf004078, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.121 */ { "xfs.btree.block_map.join", { 0xf004079, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.122 */ { "xfs.btree.block_map.alloc", { 0xf00407a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.123 */ { "xfs.btree.block_map.free", { 0xf00407b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.124 */ { "xfs.btree.block_map.moves", { 0xf00407c, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.125 */ { "xfs.btree.inode.lookup", { 0xf00407d, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.126 */ { "xfs.btree.inode.compare", { 0xf00407e, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.127 */ { "xfs.btree.inode.insrec", { 0xf00407f, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.128 */ { "xfs.btree.inode.delrec", { 0xf004080, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.129 */ { "xfs.btree.inode.newroot", { 0xf004081, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.130 */ { "xfs.btree.inode.killroot", { 0xf004082, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.131 */ { "xfs.btree.inode.increment", { 0xf004083, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.132 */ { "xfs.btree.inode.decrement", { 0xf004084, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.133 */ { "xfs.btree.inode.lshift", { 0xf004085, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.134 */ { "xfs.btree.inode.rshift", { 0xf004086, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.135 */ { "xfs.btree.inode.split", { 0xf004087, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.136 */ { "xfs.btree.inode.join", { 0xf004088, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.137 */ { "xfs.btree.inode.alloc", { 0xf004089, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.138 */ { "xfs.btree.inode.free", { 0xf00408a, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.16.139 */ { "xfs.btree.inode.moves", { 0xf00408b, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.5 */ { "pmda.uname", { 0xf003005, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.12.6 */ { "pmda.version", { 0xf003006, PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.0 */ { "ipc.sem.max_semmap", { 0xf005400, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.1 */ { "ipc.sem.max_semid", { 0xf005401, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.2 */ { "ipc.sem.max_sem", { 0xf005402, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.3 */ { "ipc.sem.num_undo", { 0xf005403, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.4 */ { "ipc.sem.max_perid", { 0xf005404, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.5 */ { "ipc.sem.max_ops", { 0xf005405, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.6 */ { "ipc.sem.max_undoent", { 0xf005406, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.7 */ { "ipc.sem.sz_semundo", { 0xf005407, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.8 */ { "ipc.sem.max_semval", { 0xf005408, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.21.9 */ { "ipc.sem.max_exit", { 0xf005409, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.0 */ { "ipc.msg.sz_pool", { 0xf005800, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.1 */ { "ipc.msg.mapent", { 0xf005801, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.2 */ { "ipc.msg.max_msgsz", { 0xf005802, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.3 */ { "ipc.msg.max_defmsgq", { 0xf005803, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.4 */ { "ipc.msg.max_msgqid", { 0xf005804, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.5 */ { "ipc.msg.max_msgseg", { 0xf005805, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.6 */ { "ipc.msg.num_smsghdr", { 0xf005806, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.22.7 */ { "ipc.msg.max_seg", { 0xf005807, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.23.0 */ { "ipc.shm.max_segsz", { 0xf005c00, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.23.1 */ { "ipc.shm.min_segsz", { 0xf005c01, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.23.2 */ { "ipc.shm.max_seg", { 0xf005c02, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.23.3 */ { "ipc.shm.max_segproc", { 0xf005c03, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.23.4 */ { "ipc.shm.max_shmsys", { 0xf005c04, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.0 */ { "vfs.files.count", { 0xf006c00, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.1 */ { "vfs.files.free", { 0xf006c01, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.2 */ { "vfs.files.max", { 0xf006c02, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.3 */ { "vfs.inodes.count", { 0xf006c03, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.4 */ { "vfs.inodes.free", { 0xf006c04, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.5 */ { "vfs.dentry.count", { 0xf006c05, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.27.6 */ { "vfs.dentry.free", { 0xf006c06, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.0 */ { "quota.state.project.accounting", { 0xf007800, PM_TYPE_U32, 0xf000005, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.1 */ { "quota.state.project.enforcement", { 0xf007801, PM_TYPE_U32, 0xf000005, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.6 */ { "quota.project.space.hard", { 0xf007806, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.7 */ { "quota.project.space.soft", { 0xf007807, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.8 */ { "quota.project.space.used", { 0xf007808, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.9 */ { "quota.project.space.time_left", { 0xf007809, PM_TYPE_32, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.30.10 */ { "quota.project.files.hard", { 0xf00780a, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.11 */ { "quota.project.files.soft", { 0xf00780b, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.12 */ { "quota.project.files.used", { 0xf00780c, PM_TYPE_U64, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.30.13 */ { "quota.project.files.time_left", { 0xf00780d, PM_TYPE_32, 0xf000010, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 60.34.1 */ { "tmpfs.capacity", { 0xf008801, PM_TYPE_U64, 0xf000012, PM_SEM_DISCRETE,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.2 */ { "tmpfs.used", { 0xf008802, PM_TYPE_U64, 0xf000012, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.3 */ { "tmpfs.free", { 0xf008803, PM_TYPE_U64, 0xf000012, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.4 */ { "tmpfs.maxfiles", { 0xf008804, PM_TYPE_U32, 0xf000012, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.5 */ { "tmpfs.usedfiles", { 0xf008805, PM_TYPE_U32, 0xf000012, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.6 */ { "tmpfs.freefiles", { 0xf008806, PM_TYPE_U32, 0xf000012, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.34.7 */ { "tmpfs.full", { 0xf008807, PM_TYPE_DOUBLE, 0xf000012, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 60.35.0 */ { "sysfs.kernel.uevent_seqnum", { 0xf008c00, PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.99 */ { "proc.nprocs", { 0xc02063, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.0 */ { "proc.psinfo.pid", { 0xc02000, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.1 */ { "proc.psinfo.cmd", { 0xc02001, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.2 */ { "proc.psinfo.sname", { 0xc02002, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.3 */ { "proc.psinfo.ppid", { 0xc02003, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.4 */ { "proc.psinfo.pgrp", { 0xc02004, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.5 */ { "proc.psinfo.session", { 0xc02005, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.6 */ { "proc.psinfo.tty", { 0xc02006, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.7 */ { "proc.psinfo.tty_pgrp", { 0xc02007, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.8 */ { "proc.psinfo.flags", { 0xc02008, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.9 */ { "proc.psinfo.minflt", { 0xc02009, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.10 */ { "proc.psinfo.cmin_flt", { 0xc0200a, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.11 */ { "proc.psinfo.maj_flt", { 0xc0200b, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.12 */ { "proc.psinfo.cmaj_flt", { 0xc0200c, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.13 */ { "proc.psinfo.utime", { 0xc0200d, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 3.8.14 */ { "proc.psinfo.stime", { 0xc0200e, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 3.8.15 */ { "proc.psinfo.cutime", { 0xc0200f, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 3.8.16 */ { "proc.psinfo.cstime", { 0xc02010, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=2, .scaleCount=0 } } },
+ /* 3.8.17 */ { "proc.psinfo.priority", { 0xc02011, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.18 */ { "proc.psinfo.nice", { 0xc02012, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.20 */ { "proc.psinfo.it_real_value", { 0xc02014, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.21 */ { "proc.psinfo.start_time", { 0xc02015, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=3, .scaleCount=0 } } },
+ /* 3.8.22 */ { "proc.psinfo.vsize", { 0xc02016, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.23 */ { "proc.psinfo.rss", { 0xc02017, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.24 */ { "proc.psinfo.rss_rlim", { 0xc02018, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.25 */ { "proc.psinfo.start_code", { 0xc02019, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.26 */ { "proc.psinfo.end_code", { 0xc0201a, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.27 */ { "proc.psinfo.start_stack", { 0xc0201b, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.28 */ { "proc.psinfo.esp", { 0xc0201c, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.29 */ { "proc.psinfo.eip", { 0xc0201d, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.30 */ { "proc.psinfo.signal", { 0xc0201e, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.31 */ { "proc.psinfo.blocked", { 0xc0201f, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.32 */ { "proc.psinfo.sigignore", { 0xc02020, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.33 */ { "proc.psinfo.sigcatch", { 0xc02021, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.34 */ { "proc.psinfo.wchan", { 0xc02022, PM_TYPE_U64, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.35 */ { "proc.psinfo.nswap", { 0xc02023, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.36 */ { "proc.psinfo.cnswap", { 0xc02024, PM_TYPE_U32, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.37 */ { "proc.psinfo.exit_signal", { 0xc02025, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.38 */ { "proc.psinfo.processor", { 0xc02026, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.39 */ { "proc.psinfo.ttyname", { 0xc02027, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.40 */ { "proc.psinfo.wchan_s", { 0xc02028, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.8.41 */ { "proc.psinfo.psargs", { 0xc02029, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.16 */ { "proc.psinfo.signal_s", { 0xc06010, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.17 */ { "proc.psinfo.blocked_s", { 0xc06011, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.18 */ { "proc.psinfo.sigignore_s", { 0xc06012, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.19 */ { "proc.psinfo.sigcatch_s", { 0xc06013, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.0 */ { "proc.memory.size", { 0xc02400, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.1 */ { "proc.memory.rss", { 0xc02401, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.2 */ { "proc.memory.share", { 0xc02402, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.3 */ { "proc.memory.textrss", { 0xc02403, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.4 */ { "proc.memory.librss", { 0xc02404, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.5 */ { "proc.memory.datrss", { 0xc02405, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.6 */ { "proc.memory.dirty", { 0xc02406, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.9.7 */ { "proc.memory.maps", { 0xc02407, PM_TYPE_STRING, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.20 */ { "proc.memory.vmsize", { 0xc06014, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.21 */ { "proc.memory.vmlock", { 0xc06015, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.22 */ { "proc.memory.vmrss", { 0xc06016, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.23 */ { "proc.memory.vmdata", { 0xc06017, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.24 */ { "proc.memory.vmstack", { 0xc06018, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.25 */ { "proc.memory.vmexe", { 0xc06019, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.26 */ { "proc.memory.vmlib", { 0xc0601a, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=1, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.0 */ { "proc.runq.runnable", { 0xc03400, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.1 */ { "proc.runq.blocked", { 0xc03401, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.2 */ { "proc.runq.sleeping", { 0xc03402, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.3 */ { "proc.runq.stopped", { 0xc03403, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.4 */ { "proc.runq.swapped", { 0xc03404, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.5 */ { "proc.runq.defunct", { 0xc03405, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.6 */ { "proc.runq.unknown", { 0xc03406, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.13.7 */ { "proc.runq.kernel", { 0xc03407, PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.0 */ { "proc.id.uid", { 0xc06000, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.1 */ { "proc.id.euid", { 0xc06001, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.2 */ { "proc.id.suid", { 0xc06002, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.3 */ { "proc.id.fsuid", { 0xc06003, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.4 */ { "proc.id.gid", { 0xc06004, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.5 */ { "proc.id.egid", { 0xc06005, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.6 */ { "proc.id.sgid", { 0xc06006, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.7 */ { "proc.id.fsgid", { 0xc06007, PM_TYPE_U32, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.8 */ { "proc.id.uid_nm", { 0xc06008, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.9 */ { "proc.id.euid_nm", { 0xc06009, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.10 */ { "proc.id.suid_nm", { 0xc0600a, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.11 */ { "proc.id.fsuid_nm", { 0xc0600b, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.12 */ { "proc.id.gid_nm", { 0xc0600c, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.13 */ { "proc.id.egid_nm", { 0xc0600d, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.14 */ { "proc.id.sgid_nm", { 0xc0600e, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.24.15 */ { "proc.id.fsgid_nm", { 0xc0600f, PM_TYPE_STRING, 0xc00009, PM_SEM_DISCRETE,
+ { .dimSpace=0, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.0 */ { "proc.io.rchar", { 0xc08000, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.1 */ { "proc.io.wchar", { 0xc08001, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.2 */ { "proc.io.syscr", { 0xc08002, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.3 */ { "proc.io.syscw", { 0xc08003, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.4 */ { "proc.io.read_bytes", { 0xc08004, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.5 */ { "proc.io.write_bytes", { 0xc08005, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.32.6 */ { "proc.io.cancelled_write_bytes", { 0xc08006, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=1, .dimTime=0, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.31.0 */ { "proc.schedstat.cpu_time", { 0xc07c00, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.31.1 */ { "proc.schedstat.run_delay", { 0xc07c01, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=1, .dimCount=0, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.31.2 */ { "proc.schedstat.pcount", { 0xc07c02, PM_TYPE_U64, 0xc00009, PM_SEM_COUNTER,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ /* 3.51.0 */ { "proc.fd.count", { 0xc0cc00, PM_TYPE_U32, 0xc00009, PM_SEM_INSTANT,
+ { .dimSpace=0, .dimTime=0, .dimCount=1, .scaleSpace=0, .scaleTime=0, .scaleCount=0 } } },
+ { NULL }
+};
diff --git a/src/collectl2pcp/metrics.h b/src/collectl2pcp/metrics.h
new file mode 100644
index 0000000..c31cfc7
--- /dev/null
+++ b/src/collectl2pcp/metrics.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Mark Goodwin <mgoodwin@redhat.com> May 2013.
+ */
+
+#include <ctype.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include <pcp/import.h>
+
+/* domains from stdpmid */
+#define LINUX_DOMAIN 60
+#define PROC_DOMAIN 3
+
+/* Linux PMDA instance domain identifiers */
+#include "../../pmdas/linux/indom.h"
+
+/* PCP metric, see metrics.c */
+typedef struct {
+ char *name;
+ pmDesc desc;
+} metric_t;
+
+/* parsed input buffer, split into fields. See fields_new() et al */
+typedef struct {
+ int len;
+ char *buf;
+ int nfields;
+ char **fields;
+ int *fieldlen;
+} fields_t;
+
+/* handler to convert parsed fields into pcp metrics and emit them */
+typedef struct handler {
+ char *pattern;
+ int (*handler)(struct handler *h, fields_t *f);
+ char *metric_name;
+} handler_t;
+
+/* global options */
+extern int vflag;
+extern int kernel_all_hz;
+extern int utc_offset;
+
+/* metric table, see metrics.c (generated from pmdesc) */
+extern metric_t metrics[];
+
+/* instance domain count table - needed for dynamic instances */
+extern int indom_cnt[NUM_INDOMS];
+
+/* metric value handler table */
+extern handler_t handlers[];
+
+/* handlers */
+extern int header_handler(FILE *fp, char *fname, char *buf, int buflen);
+extern int timestamp_flush(void);
+extern int timestamp_handler(handler_t *h, fields_t *f);
+extern int cpu_handler(handler_t *h, fields_t *f);
+extern int proc_handler(handler_t *h, fields_t *f);
+extern int disk_handler(handler_t *h, fields_t *f);
+extern int net_handler(handler_t *h, fields_t *f);
+extern int loadavg_handler(handler_t *h, fields_t *f);
+extern int generic1_handler(handler_t *h, fields_t *f);
+extern int generic2_handler(handler_t *h, fields_t *f);
+
+/* various helpers, see util.c */
+extern metric_t *find_metric(char *name);
+extern handler_t *find_handler(char *buf);
+extern int put_str_instance(pmInDom indom, char *instance);
+extern int put_str_value(char *name, pmInDom indom, char *instance, char *val);
+extern int put_int_value(char *name, pmInDom indom, char *instance, int val);
+extern int put_ull_value(char *name, pmInDom indom, char *instance, unsigned long long val);
+
+/* helpers to parse and manage input buffers */
+extern int strfields(const char *s, int len, char **fields, int *fieldlen, int maxfields);
+extern fields_t *fields_new(const char *s, int len);
+extern fields_t *fields_dup(fields_t *f);
+extern void fields_free(fields_t *f);
diff --git a/src/collectl2pcp/net.c b/src/collectl2pcp/net.c
new file mode 100644
index 0000000..4b8e23b
--- /dev/null
+++ b/src/collectl2pcp/net.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for network.interface.*
+ * Net lo: 216173634 2124262 0 0 0 0 0 0 216173634 2124262 0 0 0 0 0 0
+ */
+
+
+#include "metrics.h"
+
+int
+net_handler(handler_t *h, fields_t *f)
+{
+ int n;
+ char *s;
+ char *inst;
+ pmInDom indom = pmInDom_build(LINUX_DOMAIN, NET_DEV_INDOM);
+
+ /*
+Inter-| Receive | Transmit
+ face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
+ lo: 4060748 39057 0 0 0 0 0 0 4060748 39057 0 0 0 0 0 0
+ eth0: 0 337614 0 0 0 0 0 0 0 267537 0 0 0 27346 62 0
+ */
+
+ if (f->nfields != 18)
+ return -1;
+
+ inst = f->fields[1];
+ if ((s = strchr(inst, ':')) != NULL)
+ *s = '\0';
+
+ n = 2;
+ put_str_value("network.interface.in.bytes", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.packets", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.errors", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.drops", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.fifo", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.frame", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.compressed", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.in.mcasts", indom, inst, f->fields[n++]);
+
+ put_str_value("network.interface.out.bytes", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.packets", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.errors", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.drops", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.fifo", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.collisions", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.carrier", indom, inst, f->fields[n++]);
+ put_str_value("network.interface.out.compressed", indom, inst, f->fields[n++]);
+
+ return 0;
+}
+
diff --git a/src/collectl2pcp/pmdesc.c b/src/collectl2pcp/pmdesc.c
new file mode 100644
index 0000000..72de8e7
--- /dev/null
+++ b/src/collectl2pcp/pmdesc.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ *
+ * Lookup up the metrics named on stdin and generate pmDesc descriptors.
+ * Mark Goodwin <mgoodwin@redhat.com> May 2013.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+
+static char *semStr[] = { "0", "PM_SEM_COUNTER", "2", "PM_SEM_INSTANT", "PM_SEM_DISCRETE" };
+
+static char *
+indomStr(int indom)
+{
+ static char buf[16];
+
+ if (indom == PM_INDOM_NULL)
+ strcpy(buf, "PM_INDOM_NULL");
+ else
+ sprintf(buf, "0x%04x", indom);
+
+ return buf;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ctx;
+ int sts;
+ char buf[1024];
+ char *name = buf;
+ char *p;
+ pmID pmid;
+ pmDesc desc;
+
+ ctx = pmNewContext(PM_CONTEXT_HOST, "local:");
+ if (ctx < 0) {
+ fprintf(stderr, "Error: pmNewContext %s\n", pmErrStr(ctx));
+ exit(1);
+ }
+
+ printf("/* This file is automatically generated .. do not edit! */\n");
+ printf("#include \"metrics.h\"\n\n");
+
+ printf("metric_t metrics[] = {\n");
+ while (fgets(buf, sizeof(buf), stdin)) {
+ if ((p = strrchr(buf, '\n')) != NULL)
+ *p = '\0';
+
+ if ((sts = pmLookupName(1, &name, &pmid)) < 0) {
+ fprintf(stderr, "Error: pmLookupName \"%s\": %s\n", name, pmErrStr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmLookupDesc(pmid, &desc)) < 0) {
+ fprintf(stderr, "Error: pmLookupDesc \"%s\": %s\n", name, pmErrStr(sts));
+ exit(1);
+ }
+
+ printf(" /* %-8s */ { \"%s\", { 0x%04x, PM_TYPE_%s, %s, %s,\n"
+ " { .dimSpace=%d, .dimTime=%d, .dimCount=%d, "
+ ".scaleSpace=%d, .scaleTime=%d, .scaleCount=%d } } },\n",
+ pmIDStr(desc.pmid), name, desc.pmid, pmTypeStr(desc.type),
+ indomStr(desc.indom), semStr[desc.sem], desc.units.dimSpace,
+ desc.units.dimTime, desc.units.dimCount, desc.units.scaleSpace,
+ desc.units.scaleTime, desc.units.scaleCount);
+ }
+
+ printf(" { NULL }\n};\n");
+ exit(0);
+}
diff --git a/src/collectl2pcp/proc.c b/src/collectl2pcp/proc.c
new file mode 100644
index 0000000..516087c
--- /dev/null
+++ b/src/collectl2pcp/proc.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for per-process metrics
+ *
+ * proc:28896 stat 28896 (bash) S 3574 28896 28896 34844 28896 4202496 2286 23473 1 21 4 2 486 129 20 0 1 0 125421198 119214080 85 18446744073709551615 4194304 5080360 140737040162832 140737040157848 212270400064 0 0 3686404 1266761467 18446744071582594345 0 0 17 2 0 0 0 0 0 9310744 9344396 14004224
+ *
+ */
+
+#include "metrics.h"
+
+#define ticks_to_msec(ticks) (1000ULL * strtoull(ticks, NULL, 0) / kernel_all_hz)
+
+/* /proc/PID/stat fields (starting at fields[2]) */
+#define PROC_PID_STAT_PID 0
+#define PROC_PID_STAT_CMD 1
+#define PROC_PID_STAT_STATE 2
+#define PROC_PID_STAT_PPID 3
+#define PROC_PID_STAT_PGRP 4
+#define PROC_PID_STAT_SESSION 5
+#define PROC_PID_STAT_TTY 6
+#define PROC_PID_STAT_TTY_PGRP 7
+#define PROC_PID_STAT_FLAGS 8
+#define PROC_PID_STAT_MINFLT 9
+#define PROC_PID_STAT_CMIN_FLT 10
+#define PROC_PID_STAT_MAJ_FLT 11
+#define PROC_PID_STAT_CMAJ_FLT 12
+#define PROC_PID_STAT_UTIME 13
+#define PROC_PID_STAT_STIME 14
+#define PROC_PID_STAT_CUTIME 15
+#define PROC_PID_STAT_CSTIME 16
+#define PROC_PID_STAT_PRIORITY 17
+#define PROC_PID_STAT_NICE 18
+#define PROC_PID_STAT_REMOVED 19
+#define PROC_PID_STAT_IT_REAL_VALUE 20
+#define PROC_PID_STAT_START_TIME 21
+#define PROC_PID_STAT_VSIZE 22
+#define PROC_PID_STAT_RSS 23
+#define PROC_PID_STAT_RSS_RLIM 24
+#define PROC_PID_STAT_START_CODE 25
+#define PROC_PID_STAT_END_CODE 26
+#define PROC_PID_STAT_START_STACK 27
+#define PROC_PID_STAT_ESP 28
+#define PROC_PID_STAT_EIP 29
+#define PROC_PID_STAT_SIGNAL 30
+#define PROC_PID_STAT_BLOCKED 31
+#define PROC_PID_STAT_SIGIGNORE 32
+#define PROC_PID_STAT_SIGCATCH 33
+#define PROC_PID_STAT_WCHAN 34
+#define PROC_PID_STAT_NSWAP 35
+#define PROC_PID_STAT_CNSWAP 36
+#define PROC_PID_STAT_EXIT_SIGNAL 37
+#define PROC_PID_STAT_PROCESSOR 38
+#define PROC_PID_STAT_TTYNAME 39
+#define PROC_PID_STAT_WCHAN_SYMBOL 40
+#define PROC_PID_STAT_PSARGS 41
+
+static char *inst;
+static fields_t *proc_stat;
+
+static int
+find_command_start(const char *buf, size_t len)
+{
+ int i;
+
+ /* skip over (minimal) leading "proc:N cmd " */
+ for (i = 7; i < len - 4; i++)
+ if (strncmp(&buf[i], "cmd", 4) == 0)
+ return i + 4;
+ return -1; /* wha? cannot find the "cmd" component */
+}
+
+static void
+inst_command_clean(char *command, size_t size)
+{
+ int i;
+
+ /* command contains nulls - replace 'em */
+ for (i = 0; i < size; i++) {
+ if (!isprint(command[i]))
+ command[i] = ' ';
+ }
+ /* and trailing whitespace - clean that */
+ while (--size) {
+ if (isspace(command[size]))
+ command[size] = '\0';
+ else
+ break;
+ }
+}
+
+void
+base_command_name(const char *command, char *base, size_t size)
+{
+ char *p, *start, *end;
+ int kernel = (command[0] == '('); /* kernel daemons heuristic */
+
+ /* moral equivalent of basename, dealing with args stripping too */
+ for (p = end = start = (char *)command; *p; end = ++p) {
+ if (kernel)
+ continue;
+ else if (*p == '/')
+ start = end = p+1;
+ else if (isspace(*p))
+ break;
+ }
+ size--; /* allow for a null */
+ if (size > (end - start))
+ size = (end - start);
+ memcpy(base, start, size);
+ base[size] = '\0';
+}
+
+int
+proc_handler(handler_t *h, fields_t *f)
+{
+ int pid, off, bytes;
+ char *command;
+ size_t size;
+
+ if (f->nfields < 2 || f->fieldlen[0] < 6)
+ return 0;
+
+ if (strcmp(f->fields[1], "cmd") == 0) {
+ /*
+ * e.g. :
+ * proc:27041 cmd /bin/sh /usr/prod/mts/common/bin/dblogin_gateway_reader
+ */
+ if ((off = find_command_start(f->buf, f->len)) < 0)
+ return 0;
+ size = f->len - off + 16; /* +16 for the "%06d " pid */
+ if ((inst = (char *)malloc(size)) == NULL)
+ return 0;
+ sscanf(f->buf, "proc:%d", &pid);
+ bytes = snprintf(inst, size, "%06d ", pid);
+
+ /* f->buf contains nulls - so memcpy it then replace 'em */
+ size = f->len - off - 1;
+ command = inst + bytes;
+ memcpy(command, f->buf + off, size);
+ command[size] = '\0';
+ inst_command_clean(command, size);
+ }
+
+ if (inst == NULL && strcmp(f->fields[1], "stat") == 0) {
+ /* no instance yet, so stash it for later */
+ proc_stat = fields_dup(f);
+ return 0;
+ }
+
+ if (inst) {
+ pmInDom indom = pmInDom_build(PROC_DOMAIN, PROC_PROC_INDOM);
+
+ if ((command = strchr(inst, ' ')) != NULL) {
+ char cmdname[MAXPATHLEN];
+
+ command++;
+ base_command_name(command, &cmdname[0], sizeof(cmdname));
+ put_str_value("proc.psinfo.cmd", indom, inst, cmdname);
+ put_str_value("proc.psinfo.psargs", indom, inst, command);
+ }
+
+ /* emit the stashed proc_stat fields */
+ put_ull_value("proc.psinfo.utime", indom, inst, ticks_to_msec(proc_stat->fields[PROC_PID_STAT_UTIME+2]));
+ put_ull_value("proc.psinfo.stime", indom, inst, ticks_to_msec(proc_stat->fields[PROC_PID_STAT_STIME+2]));
+ put_str_value("proc.psinfo.processor", indom, inst, proc_stat->fields[PROC_PID_STAT_PROCESSOR+2]);
+ put_str_value("proc.psinfo.rss", indom, inst, proc_stat->fields[PROC_PID_STAT_RSS+2]);
+ put_str_value("proc.psinfo.vsize", indom, inst, proc_stat->fields[PROC_PID_STAT_VSIZE+2]);
+ put_str_value("proc.psinfo.sname", indom, inst, proc_stat->fields[PROC_PID_STAT_STATE+2]);
+ /* and the rest .. */
+ fields_free(proc_stat);
+ proc_stat = NULL;
+
+ /* TODO emit other stashed stuff .. */
+
+ free(inst);
+ inst = NULL;
+ }
+
+ return 0;
+}
diff --git a/src/collectl2pcp/timestamp.c b/src/collectl2pcp/timestamp.c
new file mode 100644
index 0000000..45de17b
--- /dev/null
+++ b/src/collectl2pcp/timestamp.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Handler for timestamps
+ * >>> 1368076410.001 <<<
+ */
+
+#include "metrics.h"
+
+static int seconds = 0, mseconds = 0;
+
+int
+timestamp_flush(void)
+{
+ int sts;
+ int err = 0;
+
+ /*
+ * timstamps in collectl logs are seconds.mseconds since the epoch in utc
+ * Since we've set pmiSetTimezone to what was found in the header, we need
+ * to offset it here so the PCP archive matches the host timezone.
+ */
+ if (seconds && (sts = pmiWrite(seconds + utc_offset * 60 * 60, mseconds*1000)) < 0) {
+ if (sts != PMI_ERR_NODATA) {
+ fprintf(stderr, "Error: pmiWrite failed: error %d: %s\n", sts, pmiErrStr(sts));
+ err = sts; /* probably fatal */
+ }
+ }
+
+ return err;
+}
+
+int
+timestamp_handler(handler_t *h, fields_t *f)
+{
+
+ int sts;
+ int err = 0;
+
+ /* >>> 1368076390.001 <<< */
+ if (f->nfields != 3)
+ return -1;
+ if ((sts = timestamp_flush()) < 0)
+ err = sts;
+ sscanf(f->fields[1], "%d.%d", &seconds, &mseconds);
+
+ return err;
+}
diff --git a/src/collectl2pcp/util.c b/src/collectl2pcp/util.c
new file mode 100644
index 0000000..8d490e5
--- /dev/null
+++ b/src/collectl2pcp/util.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ */
+
+#include "metrics.h"
+
+metric_t *
+find_metric(char *name)
+{
+ metric_t *m;
+
+ for (m = metrics; m->name; m++) {
+ if (strcmp(name, m->name) == 0)
+ break;
+ }
+
+ return m->name ? m : NULL;
+}
+
+handler_t *
+find_handler(char *tag)
+{
+ handler_t *h;
+
+ for (h = handlers; h->pattern; h++) {
+ if (strchr(h->pattern, '*') != NULL) {
+ /* match tag as a prefix, e.g. cpu* matches cpu123 */
+ if (strncmp(h->pattern, tag, strlen(h->pattern)-1) == 0)
+ break;
+ }
+ else {
+ /* exact match */
+ if (strcmp(h->pattern, tag) == 0)
+ break;
+ }
+ }
+
+ return h->pattern ? h : NULL;
+}
+
+int
+put_str_instance(pmInDom indom, char *instance)
+{
+ int sts;
+ __pmInDom_int *idp = __pmindom_int(&indom);
+ int id = indom_cnt[idp->serial]++;
+
+ sts = pmiAddInstance(indom, instance, id);
+ return sts ? sts : id;
+}
+
+int
+put_str_value(char *name, pmInDom indom, char *instance, char *val)
+{
+ int sts = 0;
+
+ if (!val)
+ fprintf(stderr, "Warning: put_str_value: ignored NULL value for \"%s\"\n", name);
+ else if (indom != PM_INDOM_NULL && !instance)
+ fprintf(stderr, "Warning: put_str_value: ignored NULL instance for non-singular indom for \"%s\"\n", name);
+ else {
+ sts = pmiPutValue(name, instance, val);
+ if (sts == PM_ERR_NAME) {
+#if 0
+ if (vflag)
+ fprintf(stderr, "Warning: unknown metric name \"%s\". Check metrics.c\n", name);
+#endif
+ return sts;
+ }
+ if (indom != PM_INDOM_NULL && instance && (sts == PM_ERR_INST || sts == PM_ERR_INDOM)) {
+ /* New instance has appeared */
+ sts = put_str_instance(indom, instance);
+ if (sts < 0)
+ fprintf(stderr, "Warning: put_str_value failed to add new instance \"%s\" for indom:%d err:%d : %s\n",
+ instance, indom, sts, pmiErrStr(sts));
+ else if (vflag)
+ printf("New instance %s[%d] \"%s\"\n", name, sts, instance);
+ sts = pmiPutValue(name, instance, val);
+ }
+ if (sts < 0)
+ fprintf(stderr, "Warning: put_str_value \"%s\" inst:\"%s\" value:\"%s\" failed: err=%d %s\n",
+ name, instance ? instance : "NULL", val ? val : "NULL", sts, pmiErrStr(sts));
+ }
+
+ return sts;
+}
+
+int
+put_int_value(char *name, pmInDom indom, char *instance, int val)
+{
+ char valbuf[64];
+
+ sprintf(valbuf, "%d", val);
+ return put_str_value(name, indom, instance, valbuf);
+}
+
+int
+put_ull_value(char *name, pmInDom indom, char *instance, unsigned long long val)
+{
+ char valbuf[64];
+
+ sprintf(valbuf, "%llu", val);
+ return put_str_value(name, indom, instance, valbuf);
+}
+
+/* split a string into fields and their lengths. Free fields[0] when done. */
+int
+strfields(const char *s, int len, char **fields, int *fieldlen, int maxfields)
+{
+ int i;
+ char *p;
+ char *p_end;
+
+ if (!s || *s == '\0')
+ return 0;
+
+ if ((p = strdup(s)) == NULL)
+ return 0;
+
+ for (i=0, p_end = p+len; i < maxfields;) {
+ fields[i] = p;
+ fieldlen[i] = 0;
+ while(*p && !isspace(*p) && p < p_end) {
+ p++;
+ fieldlen[i]++;
+ }
+ i++;
+ if (!*p)
+ break;
+ *p++ ='\0';
+ while (isspace(*p))
+ p++;
+ }
+
+ return i;
+}
+
+fields_t *
+fields_new(const char *s, int len)
+{
+ int n = 1;
+ const char *p;
+ fields_t *f = (fields_t *)malloc(sizeof(fields_t));
+
+ memset(f, 0, sizeof(fields_t));
+ f->len = len;
+ for (p=s; *p && p < s+len; p++) {
+ if (isspace(*p))
+ n++;
+ }
+ /*
+ * n is an upper bound, at least 1 (separator may repeat).
+ * fields[0] is the actual buffer, allocated by strfields
+ */
+ f->fields = (char **)malloc(n * sizeof(char *));
+ f->fieldlen = (int *)malloc(n * sizeof(int));
+ f->nfields = strfields(s, len, f->fields, f->fieldlen, n);
+ f->buf = f->fields[0];
+
+ return f;
+}
+
+fields_t *
+fields_dup(fields_t *f)
+{
+ int i;
+ fields_t *copy;
+
+ copy = malloc(sizeof(fields_t));
+ memset(copy, 0, sizeof(fields_t));
+ copy->len = f->len;
+ copy->buf = (char *)malloc(f->len + 1);
+ memcpy(copy->buf, f->buf, f->len);
+
+ copy->nfields = f->nfields;
+ copy->fields = (char **)malloc(f->nfields * sizeof(char *));
+ copy->fieldlen = (int *)malloc(f->nfields * sizeof(int));
+
+ copy->fields[0] = copy->buf;
+ for (i=1; i < f->nfields; i++) {
+ copy->fieldlen[i] = f->fieldlen[i];
+ copy->fields[i] = copy->fields[0] + (f->fields[i] - f->fields[0]);
+ }
+
+ return copy;
+}
+
+void
+fields_free(fields_t *f)
+{
+ free(f->buf);
+ free(f->fields);
+ free(f->fieldlen);
+ free(f);
+}
+
diff --git a/src/dbpmda/GNUmakefile b/src/dbpmda/GNUmakefile
new file mode 100644
index 0000000..4c4ed0e
--- /dev/null
+++ b/src/dbpmda/GNUmakefile
@@ -0,0 +1,32 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/dbpmda/src/GNUmakefile b/src/dbpmda/src/GNUmakefile
new file mode 100644
index 0000000..e74eb80
--- /dev/null
+++ b/src/dbpmda/src/GNUmakefile
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = dbpmda$(EXECSUFFIX)
+CFILES = dbpmda.c dso.c util.c pmda.c
+HFILES = lex.h dbpmda.h
+LFILES = lex.l
+YFILES = gram.y
+
+LDIRT = *.log foo.* gram.h $(YFILES:%.y=%.tab.?) $(LFILES:%.l=%.c)
+LLDLIBS = $(PCPLIB) $(LIB_FOR_DLOPEN) $(LIB_FOR_READLINE) $(LIB_FOR_CURSES)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+$(CMDTARGET): $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+dbpmda.o: gram.h
+lex.o: gram.tab.h
+
+.NOTPARALLEL:
+gram.tab.h gram.tab.c: gram.y
+
+gram.h: gram.tab.h
+ rm -f $@ && $(LN_S) $< $@
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/dbpmda/src/dbpmda.c b/src/dbpmda/src/dbpmda.c
new file mode 100644
index 0000000..9af6e23
--- /dev/null
+++ b/src/dbpmda/src/dbpmda.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "dbpmda.h"
+#include "lex.h"
+#include "gram.h"
+#include <ctype.h>
+
+char *configfile;
+__pmLogCtl logctl;
+int parse_done;
+int primary; /* Non-zero for primary pmlc */
+pid_t pid = (pid_t) -1;
+char *pmnsfile = PM_NS_DEFAULT;
+char *cmd_namespace = NULL; /* namespace given from command */
+int _creds_timeout = 3; /* Timeout for agents credential PDU */
+
+int connmode = NO_CONN;
+int stmt_type;
+int eflag;
+int iflag;
+
+extern int yyparse(void);
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ PMOPT_NAMESPACE,
+ { "creds-timeout", 1, 'q', "N", "initial negotiation timeout (seconds)" },
+ { "username", 1, 'U', "USER", "run under named user account" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Input options"),
+ { "echo-input", 0, 'e', 0, "echo input" },
+ { "interactive", 0, 'i', 0, "be interactive and prompt" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_POSIX,
+ .short_options = "q:D:ein:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * called before regular exit() or as atexit() handler
+ */
+static void
+cleanup()
+{
+ if (connmode == CONN_DSO)
+ closedso();
+ else if (connmode == CONN_DAEMON)
+ closepmda();
+ connmode = NO_CONN;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts;
+ char *endnum;
+
+ iflag = isatty(0);
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'e': /* echo input */
+ eflag++;
+ break;
+
+ case 'i': /* be interactive */
+ iflag = 1;
+ break;
+
+ case 'n': /* alternative name space file */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'q':
+ sts = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || sts <= 0.0) {
+ pmprintf("%s: -q requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ _creds_timeout = sts;
+ }
+ break;
+
+ case 'U': /* run under alternate user account */
+ __pmSetProcessIdentity(opts.optarg);
+ break;
+
+ default:
+ case '?':
+ opts.errors++;
+ break;
+ }
+ }
+
+ if ((c = argc - opts.optind) > 0) {
+ if (c > 1)
+ opts.errors++;
+ else {
+ /* pid was specified */
+ if (primary) {
+ pmprintf("%s: you may not specify both -P and a pid\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else {
+ pid = (int)strtol(argv[opts.optind], &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: pid must be a numeric process id\n",
+ pmProgname);
+ opts.errors++;
+ }
+ }
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ if (pmnsfile == PM_NS_DEFAULT) {
+ fprintf(stderr, "%s: Cannot load default namespace: %s\n",
+ pmProgname, pmErrStr(sts));
+ } else {
+ fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n",
+ pmProgname, pmnsfile, pmErrStr(sts));
+ }
+ exit(1);
+ }
+
+ /* initialize the "fake context" ... */
+ setup_context();
+
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+
+#ifdef HAVE_ATEXIT
+ atexit(cleanup);
+#endif
+
+ for ( ; ; ) {
+ initmetriclist();
+ yyparse();
+ if (yywrap()) {
+ if (iflag)
+ putchar('\n');
+ break;
+ }
+
+ __pmSetInternalState(PM_STATE_PMCS);
+
+ switch (stmt_type) {
+
+ case OPEN:
+ profile_changed = 1;
+ break;
+
+ case CLOSE:
+ switch (connmode) {
+ case CONN_DSO:
+ closedso();
+ break;
+
+ case CONN_DAEMON:
+ closepmda();
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ connmode = NO_CONN;
+ break;
+
+ case DESC:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_DESC_REQ);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_DESC_REQ);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case FETCH:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_FETCH);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_FETCH);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case INSTANCE:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_INSTANCE_REQ);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_INSTANCE_REQ);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case STORE:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_RESULT);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_RESULT);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case HELP:
+ dohelp(param.number, param.pmid);
+ break;
+
+ case WATCH:
+ break;
+
+ case DBG:
+ pmDebug = param.number;
+ break;
+
+ case QUIT:
+ goto done;
+
+ case STATUS:
+ dostatus();
+ break;
+
+ case INFO:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_TEXT_REQ);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_TEXT_REQ);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+ case NAMESPACE:
+ if (cmd_namespace != NULL)
+ free(cmd_namespace);
+ cmd_namespace = strdup(param.name);
+ if (cmd_namespace == NULL) {
+ fprintf(stderr, "%s: No memory for new namespace\n",
+ pmProgname);
+ exit(1);
+ }
+ pmUnloadNameSpace();
+ strcpy(cmd_namespace, param.name);
+ if ((sts = pmLoadNameSpace(cmd_namespace)) < 0) {
+ fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n",
+ pmProgname, cmd_namespace, pmErrStr(sts));
+
+ pmUnloadNameSpace();
+ if (pmnsfile == PM_NS_DEFAULT) {
+ fprintf(stderr, "%s: Reload default namespace\n",
+ pmProgname);
+ } else {
+ fprintf(stderr, "%s: Reload namespace from \"%s\"\n",
+ pmProgname, pmnsfile);
+ }
+ if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ if (pmnsfile == PM_NS_DEFAULT) {
+ fprintf(stderr,
+ "%s: Cannot load default namespace: %s\n",
+ pmProgname, pmErrStr(sts));
+ } else {
+ fprintf(stderr,
+ "%s: Cannot load namespace from \"%s\""
+ ": %s\n",
+ pmProgname, pmnsfile, pmErrStr(sts));
+ }
+ exit(1);
+ }
+ }
+ break;
+
+ case EOL:
+ break;
+
+ case PMNS_NAME:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_PMNS_IDS);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_PMNS_IDS);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case PMNS_PMID:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_PMNS_NAMES);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_PMNS_NAMES);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case PMNS_CHILDREN:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_PMNS_CHILD);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_PMNS_CHILD);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case PMNS_TRAVERSE:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_PMNS_TRAVERSE);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_PMNS_TRAVERSE);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ case ATTR:
+ switch (connmode) {
+ case CONN_DSO:
+ dodso(PDU_AUTH);
+ break;
+
+ case CONN_DAEMON:
+ dopmda(PDU_AUTH);
+ break;
+
+ case NO_CONN:
+ yywarn("No PMDA currently opened");
+ break;
+ }
+ break;
+
+ default:
+ printf("Unexpected result (%d) from parser?\n", stmt_type);
+ break;
+ }
+ __pmSetInternalState(PM_STATE_APPL);
+ }
+
+done:
+ cleanup();
+
+ exit(0);
+}
diff --git a/src/dbpmda/src/dbpmda.h b/src/dbpmda/src/dbpmda.h
new file mode 100644
index 0000000..6e35cdf
--- /dev/null
+++ b/src/dbpmda/src/dbpmda.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+/* yacc/lex routines */
+extern char lastinput(void);
+extern void yyerror(const char *);
+extern void yywarn(char *);
+extern int yywrap(void);
+extern int yylex(void);
+extern int markpos(void);
+extern void locateError(void);
+
+/* utiltity routines */
+extern void setup_context(void);
+extern void reset_profile(void);
+extern char *strcons(char *, char *);
+extern char *strnum(int);
+extern void initmetriclist(void);
+extern void addmetriclist(pmID);
+extern void initarglist(void);
+extern void addarglist(char *);
+extern void doargs(void);
+extern void printindom(FILE *, __pmInResult *);
+extern void dohelp(int, int);
+extern void dostatus(void);
+extern int fillResult(pmResult *, int);
+extern void _dbDumpResult(FILE *, pmResult *, pmDesc *);
+
+/* pmda exerciser routines */
+extern void opendso(char *, char *, int);
+extern void closedso(void);
+extern void dodso(int);
+extern void openpmda(char *);
+extern void closepmda(void);
+extern void dopmda(int);
+extern void watch(char *);
+extern void open_unix_socket(char *);
+extern void open_inet_socket(int);
+extern void open_ipv6_socket(int);
+
+/*
+ * connection states
+ */
+#define NO_CONN -1
+#define CONN_DSO 0
+#define CONN_DAEMON 1
+extern int connmode;
+
+/* parameters for action routines ... */
+typedef struct {
+ int number;
+ char *name;
+ pmID pmid;
+ pmInDom indom;
+ int numpmid;
+ pmID *pmidlist;
+ int argc;
+ char **argv;
+} param_t;
+
+extern param_t param;
+
+/* the single profile */
+extern __pmProfile *profile;
+extern int profile_changed;
+
+/* status info */
+extern char *myPmdaName;
+
+/* help text formats */
+#define HELP_USAGE 0
+#define HELP_FULL 1
+
+/* timing information */
+extern int timer;
+
+/* get descriptor for fetch or not */
+extern int get_desc;
+
+/* namespace pathnames */
+extern char *pmnsfile;
+extern char *cmd_namespace;
diff --git a/src/dbpmda/src/dso.c b/src/dbpmda/src/dso.c
new file mode 100644
index 0000000..5dc7c16
--- /dev/null
+++ b/src/dbpmda/src/dso.c
@@ -0,0 +1,479 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include "./dbpmda.h"
+#include "pmapi.h"
+
+#ifdef HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+static char *dsoname;
+static void *handle;
+pmdaInterface dispatch;
+
+void
+opendso(char *dso, char *init, int domain)
+{
+#ifdef HAVE_DLOPEN
+ struct stat buf;
+ unsigned int challenge;
+
+ dispatch.status = -1;
+
+ if (stat(dso, &buf) < 0) {
+ fprintf(stderr, "opendso: %s: %s\n", dso, osstrerror());
+ return;
+ }
+
+ closedso();
+ /*
+ * RTLD_NOW would be better in terms of detecting unresolved symbols
+ * now, rather than taking a SEGV later ... but various combinations
+ * of dynamic and static libraries used to create the DSO PMDA,
+ * combined with hiding symbols in the DSO PMDA may result in benign
+ * unresolved symbols remaining and the dlopen() would fail under
+ * these circumstances.
+ */
+ handle = dlopen(dso, RTLD_LAZY);
+ if (handle == NULL) {
+ printf("Error attaching DSO \"%s\"\n", dso);
+ printf("%s\n\n", dlerror());
+ }
+ else {
+ void (*initp)(pmdaInterface *);
+ initp = (void (*)(pmdaInterface *))dlsym(handle, init);
+ if (initp == NULL) {
+ printf("Error: couldn't find init function \"%s\" in DSO \"%s\"\n",
+ init, dso);
+ dlclose(handle);
+ }
+ else {
+ /*
+ * the PMDA interface / PMAPI version discovery as a "challenge" ...
+ * for pmda_interface it is all the bits being set,
+ * for pmapi_version it is the complement of the one you are
+ * using now
+ */
+ challenge = 0xff;
+ dispatch.comm.pmda_interface = challenge;
+ dispatch.comm.pmapi_version = ~PMAPI_VERSION;
+ dispatch.comm.flags = 0;
+ dispatch.status = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "DSO init %s->"PRINTF_P_PFX"%p() domain=%d challenge: pmda_interface=0x%x pmapi_version=%d\n",
+ init, initp, dispatch.domain,
+ dispatch.comm.pmda_interface,
+ (~dispatch.comm.pmapi_version) & 0xff);
+#endif
+ dispatch.domain = domain;
+
+ (*initp)(&dispatch);
+
+ if (dispatch.status != 0) {
+ printf("Error: initialization routine \"%s\" failed in DSO \"%s\": %s\n",
+ init, dso, pmErrStr(dispatch.status));
+ dispatch.status = -1;
+ dlclose(handle);
+ }
+ else {
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_2 ||
+ dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) {
+
+ printf("Error: Unsupported PMDA interface version %d returned by DSO \"%s\"\n",
+ dispatch.comm.pmda_interface, dso);
+ dispatch.status = -1;
+ dlclose(handle);
+ }
+ if (dispatch.comm.pmapi_version != PMAPI_VERSION_2) {
+ printf("Error: Unsupported PMAPI version %d returned by DSO \"%s\"\n",
+ dispatch.comm.pmapi_version, dso);
+ dispatch.status = -1;
+ dlclose(handle);
+ }
+ }
+
+ if (dispatch.status == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ fprintf(stderr, "DSO has domain=%d", dispatch.domain);
+ fprintf(stderr, " pmda_interface=%d pmapi_version=%d\n",
+ dispatch.comm.pmda_interface,
+ dispatch.comm.pmapi_version);
+ }
+#endif
+ dsoname = strdup(dso);
+ connmode = CONN_DSO;
+ reset_profile();
+
+ if (myPmdaName != NULL)
+ free(myPmdaName);
+ myPmdaName = strdup(dso);
+
+ /*
+ * set here once and used by all subsequent calls into the
+ * PMDA
+ */
+ if (dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dispatch.version.four.ext->e_context = 0;
+ }
+ }
+ }
+#else /* ! HAVE_DLOPEN */
+ dispatch.status = -1;
+
+ fprintf(stderr, "opendso: %s: No dynamic DSO/DLL support on this platform\n", dso);
+#endif
+}
+
+void
+closedso(void)
+{
+ if (dsoname != NULL) {
+ if (dispatch.comm.pmda_interface >= PMDA_INTERFACE_5) {
+ if (dispatch.version.four.ext->e_endCallBack != NULL) {
+ (*(dispatch.version.four.ext->e_endCallBack))(0);
+ }
+ }
+#ifdef HAVE_DLOPEN
+ dlclose(handle);
+#endif
+ free(dsoname);
+ dsoname = NULL;
+ connmode = NO_CONN;
+ }
+}
+
+/*
+ * Do a descriptor pdu.
+ * Abstracted here for several calls.
+ */
+int
+dodso_desc(pmID pmid, pmDesc *desc)
+{
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "DSO desc()\n");
+#endif
+ sts = dispatch.version.any.desc(pmid, desc, dispatch.version.four.ext);
+
+#ifdef PCP_DEBUG
+ if (sts >= 0 && (pmDebug & DBG_TRACE_PDU))
+ __pmPrintDesc(stdout, desc);
+#endif
+
+ return sts;
+}/*dodso_desc*/
+
+
+void
+dodso(int pdu)
+{
+ int sts = 0; /* initialize to pander to gcc */
+ int length;
+ pmDesc desc;
+ pmDesc *desc_list = NULL;
+ pmResult *result;
+ __pmInResult *inresult;
+ int i;
+ int j;
+ char *buffer;
+ struct timeval start;
+ struct timeval end;
+ char name[32];
+ char **namelist;
+ int *statuslist;
+ pmID pmid;
+
+ if (timer != 0)
+ __pmtimevalNow(&start);
+
+ switch (pdu) {
+
+ case PDU_DESC_REQ:
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ if ((sts = dodso_desc(param.pmid, &desc)) >= 0)
+ __pmPrintDesc(stdout, &desc);
+ else
+ printf("Error: DSO desc() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_FETCH:
+ printf("PMID(s):");
+ for (i = 0; i < param.numpmid; i++)
+ printf(" %s", pmIDStr(param.pmidlist[i]));
+ putchar('\n');
+
+ if (get_desc) {
+ desc_list = (pmDesc *)malloc(param.numpmid * sizeof(pmDesc));
+ if (desc_list == NULL) {
+ printf("Error: DSO fetch() failed: %s\n", pmErrStr(ENOMEM));
+ return;
+ }
+ for (i = 0; i < param.numpmid; i++) {
+ if ((sts = dodso_desc(param.pmidlist[i], &desc_list[i])) < 0) {
+ printf("Error: DSO desc() failed: %s\n", pmErrStr(sts));
+ free(desc_list);
+ return;
+ }
+ }
+ }
+ sts = 0;
+ if (profile_changed) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "DSO profile()\n");
+#endif
+ sts = dispatch.version.any.profile(profile, dispatch.version.any.ext);
+ if (sts < 0)
+ printf("Error: DSO profile() failed: %s\n", pmErrStr(sts));
+ else
+ profile_changed = 0;
+ }
+ if (sts >= 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "DSO fetch()\n");
+#endif
+ sts = dispatch.version.any.fetch(param.numpmid, param.pmidlist,
+ &result, dispatch.version.any.ext);
+ if (sts >= 0) {
+ if (desc_list)
+ _dbDumpResult(stdout, result, desc_list);
+ else
+ __pmDumpResult(stdout, result);
+ /*
+ * DSO PMDA will manage the pmResult skelton, but
+ * we need to free the pmValueSets and values here
+ */
+ __pmFreeResultValues(result);
+ }
+ else {
+ printf("Error: DSO fetch() failed: %s\n", pmErrStr(sts));
+ }
+ }
+ if (desc_list)
+ free(desc_list);
+ break;
+
+ case PDU_INSTANCE_REQ:
+ printf("pmInDom: %s\n", pmInDomStr(param.indom));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "DSO instance()\n");
+#endif
+
+ sts = dispatch.version.any.instance(param.indom, param.number,
+ param.name, &inresult,
+ dispatch.version.any.ext);
+ if (sts >= 0)
+ printindom(stdout, inresult);
+ else
+ printf("Error: DSO instance() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_RESULT:
+
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ printf("Getting description...\n");
+ desc_list = &desc;
+ if ((sts = dodso_desc(param.pmid, desc_list)) < 0) {
+ printf("Error: DSO desc() failed: %s\n", pmErrStr(sts));
+ return;
+ }
+
+ if (profile_changed) {
+ printf("Sending Profile...\n");
+ sts = dispatch.version.any.profile(profile, dispatch.version.any.ext);
+ if (sts < 0) {
+ printf("Error: DSO profile() failed: %s\n", pmErrStr(sts));
+ return;
+ }
+ else
+ profile_changed = 0;
+ }
+
+ printf("Getting Result Structure...\n");
+ sts = dispatch.version.any.fetch(1, &(desc.pmid), &result,
+ dispatch.version.any.ext);
+ if (sts < 0) {
+ printf("Error: DSO fetch() failed: %s\n", pmErrStr(sts));
+ return;
+ }
+
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_FETCH)
+ _dbDumpResult(stdout, result, desc_list);
+#endif
+
+ sts = fillResult(result, desc.type);
+ if (sts < 0) {
+ pmFreeResult(result);
+ return;
+ }
+
+ sts = dispatch.version.any.store(result, dispatch.version.any.ext);
+ if (sts < 0)
+ printf("Error: DSO store() failed: %s\n", pmErrStr(sts));
+
+ break;
+
+ case PDU_TEXT_REQ:
+ if (param.number == PM_TEXT_PMID) {
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ i = param.pmid;
+ }
+ else {
+ printf("pmInDom: %s\n", pmInDomStr(param.indom));
+ i = param.indom;
+ }
+
+ for (j = 0; j < 2; j++) {
+
+ if (j == 0)
+ param.number |= PM_TEXT_ONELINE;
+ else {
+ param.number &= ~PM_TEXT_ONELINE;
+ param.number |= PM_TEXT_HELP;
+ }
+
+ sts = dispatch.version.any.text(i, param.number, &buffer, dispatch.version.any.ext);
+ if (sts >= 0) {
+ if (j == 0) {
+ if (*buffer != '\0')
+ printf("[%s]\n", buffer);
+ else
+ printf("[<no one line help text specified>]\n");
+ }
+ else if (*buffer != '\0')
+ printf("%s\n", buffer);
+ else
+ printf("<no help text specified>\n");
+ }
+ else
+ printf("Error: DSO text() failed: %s\n", pmErrStr(sts));
+ }
+ break;
+
+ case PDU_PMNS_IDS:
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_4) {
+ printf("Error: PMDA Interface %d does not support dynamic metric names\n", dispatch.comm.pmda_interface);
+ break;
+ }
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ sts = dispatch.version.four.name(param.pmid, &namelist, dispatch.version.four.ext);
+ if (sts > 0) {
+ for (i = 0; i < sts; i++) {
+ printf(" %s\n", namelist[i]);
+ }
+ free(namelist);
+ }
+ else if (sts == 0)
+ printf("Warning: DSO name() returns 0\n");
+ else
+ printf("Error: DSO name() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_NAMES:
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_4) {
+ printf("Error: PMDA Interface %d does not support dynamic metric names\n", dispatch.comm.pmda_interface);
+ break;
+ }
+ printf("Metric: %s\n", param.name);
+ sts = dispatch.version.four.pmid(param.name, &pmid, dispatch.version.four.ext);
+ if (sts >= 0)
+ printf(" %s\n", pmIDStr(pmid));
+ else
+ printf("Error: DSO pmid() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_CHILD:
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_4) {
+ printf("Error: PMDA Interface %d does not support dynamic metric names\n", dispatch.comm.pmda_interface);
+ break;
+ }
+ printf("Metric: %s\n", param.name);
+ sts = dispatch.version.four.children(param.name, 0, &namelist, &statuslist, dispatch.version.four.ext);
+ if (sts > 0) {
+ for (i = 0; i < sts; i++) {
+ printf(" %8.8s %s\n", statuslist[i] == 1 ? "non-leaf" : "leaf", namelist[i]);
+ }
+ free(namelist);
+ free(statuslist);
+ }
+ else if (sts == 0)
+ printf("Warning: DSO children() returns 0\n");
+ else
+ printf("Error: DSO children() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_TRAVERSE:
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_4) {
+ printf("Error: PMDA Interface %d does not support dynamic metric names\n", dispatch.comm.pmda_interface);
+ break;
+ }
+ printf("Metric: %s\n", param.name);
+ sts = dispatch.version.four.children(param.name, 1, &namelist, &statuslist, dispatch.version.four.ext);
+ if (sts > 0) {
+ for (i = 0; i < sts; i++) {
+ printf(" %8.8s %s\n", statuslist[i] == 1 ? "non-leaf" : "leaf", namelist[i]);
+ }
+ free(namelist);
+ free(statuslist);
+ }
+ else if (sts == 0)
+ printf("Warning: DSO children() returns 0\n");
+ else
+ printf("Error: DSO children() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_AUTH:
+ if (dispatch.comm.pmda_interface < PMDA_INTERFACE_6) {
+ printf("Error: PMDA Interface %d does not support authentication\n", dispatch.comm.pmda_interface);
+ break;
+ }
+ j = param.number; /* attribute key */
+ buffer = param.name; /* attribute value */
+ if (buffer)
+ length = strlen(buffer) + 1; /* length of value */
+ else
+ length = 0;
+ i = 0; /* client ID */
+
+ __pmAttrKeyStr_r(j, name, sizeof(name)-1);
+ name[sizeof(name)-1] = '\0';
+
+ printf("Attribute: %s=%s\n", name, buffer ? buffer : "''");
+ sts = dispatch.version.six.attribute(i, j, buffer, length, dispatch.version.six.ext);
+ if (sts >= 0)
+ printf("Success\n");
+ else
+ printf("Error: DSO attribute() failed: %s\n", pmErrStr(sts));
+ break;
+
+ default:
+ printf("Error: DSO PDU (%s) botch!\n", __pmPDUTypeStr(pdu));
+ break;
+ }
+
+ if (sts >= 0 && timer != 0) {
+ __pmtimevalNow(&end);
+ printf("Timer: %f seconds\n", __pmtimevalSub(&end, &start));
+ }
+}
diff --git a/src/dbpmda/src/gram.y b/src/dbpmda/src/gram.y
new file mode 100644
index 0000000..b9a83a6
--- /dev/null
+++ b/src/dbpmda/src/gram.y
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+#include "./dbpmda.h"
+#include "./lex.h"
+
+extern int stmt_type;
+
+static union {
+ pmID whole;
+ __pmID_int part;
+} pmid;
+
+static union {
+ pmInDom whole;
+ __pmInDom_int part;
+} indom;
+
+
+static int sts;
+static int inst;
+static char *str;
+static char warnStr[80];
+
+param_t param;
+
+/*
+ * pmidp may contain a dynamic PMID ... if so, ask the PMDA to
+ * translate name if possible
+ */
+static int
+fix_dynamic_pmid(char *name, pmID *pmidp)
+{
+ int sts;
+ __pmPDU *pb;
+ extern int outfd;
+ extern int infd;
+ extern pmdaInterface dispatch;
+
+ if (pmid_domain(*pmidp) == DYNAMIC_PMID && pmid_item(*pmidp) == 0) {
+ if (connmode == CONN_DSO) {
+ if (dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ sts = dispatch.version.four.pmid(name, pmidp, dispatch.version.four.ext);
+ if (sts < 0) return sts;
+ }
+ }
+ else if (connmode == CONN_DAEMON) {
+ sts = __pmSendNameList(outfd, FROM_ANON, 1, &name, NULL);
+ if (sts < 0) return sts;
+ sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb);
+ if (sts < 0) return sts;
+ if (sts == PDU_PMNS_IDS) {
+ int xsts;
+ sts = __pmDecodeIDList(pb, 1, pmidp, &xsts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) return sts;
+ return xsts;
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+ }
+ }
+ return 0;
+}
+
+
+%}
+
+%union {
+ char *y_str;
+ int y_num;
+ twodot_num y_2num;
+ threedot_num y_3num;
+ }
+
+%token <y_2num>
+ NUMBER2D
+
+%token <y_3num>
+ NUMBER3D
+
+%token <y_num>
+ NUMBER
+ NEGNUMBER
+ FLAG
+
+%token <y_str>
+ NAME
+ PATHNAME
+ MACRO
+ STRING
+
+%term COMMA EQUAL
+ OPEN CLOSE DESC GETDESC FETCH INSTANCE PROFILE HELP
+ WATCH DBG QUIT STATUS STORE INFO TIMER NAMESPACE WAIT
+ PMNS_NAME PMNS_PMID PMNS_CHILDREN PMNS_TRAVERSE ATTR
+ DSO PIPE SOCK UNIX INET IPV6
+ ADD DEL ALL NONE INDOM ON OFF
+ PLUS EOL
+
+%type <y_num>
+ metric
+ indom
+ optdomain
+ debug
+ raw_pmid
+ attribute
+ servport
+
+%type <y_str>
+ fname
+ arglist
+ inst
+
+%%
+
+stmt : OPEN EOL {
+ param.number = OPEN; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | OPEN DSO fname NAME optdomain EOL {
+ opendso($3, $4, $5);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | OPEN PIPE fname arglist {
+ openpmda($3);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | OPEN SOCK fname {
+ open_unix_socket($3);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | OPEN SOCK UNIX fname {
+ open_unix_socket($4);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | OPEN SOCK INET servport {
+ open_inet_socket($4);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | OPEN SOCK IPV6 servport {
+ open_ipv6_socket($4);
+ stmt_type = OPEN; YYACCEPT;
+ }
+ | CLOSE EOL {
+ stmt_type = CLOSE; YYACCEPT;
+ }
+ | DESC EOL {
+ param.number = DESC; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | DESC metric EOL {
+ param.pmid = $2;
+ stmt_type = DESC; YYACCEPT;
+ }
+ | FETCH EOL {
+ param.number = FETCH; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | FETCH metriclist EOL {
+ stmt_type = FETCH; YYACCEPT;
+ }
+ | STORE EOL {
+ param.number = STORE; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | STORE metric STRING EOL {
+ param.name = $3;
+ param.pmid = (pmID)$2;
+ stmt_type = STORE; YYACCEPT;
+ }
+ | INFO EOL {
+ param.number = INFO; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | INFO metric EOL {
+ param.number = PM_TEXT_PMID;
+ param.pmid = (pmID)$2;
+ stmt_type = INFO; YYACCEPT;
+ }
+ | INFO INDOM indom EOL {
+ param.number = PM_TEXT_INDOM;
+ param.indom = indom.whole;
+ stmt_type = INFO; YYACCEPT;
+ }
+ | INSTANCE EOL {
+ param.number = INSTANCE; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | INSTANCE indom EOL {
+ param.indom = indom.whole;
+ param.number = PM_IN_NULL;
+ param.name = NULL;
+ stmt_type = INSTANCE; YYACCEPT;
+ }
+ | INSTANCE indom NUMBER EOL {
+ param.indom = indom.whole;
+ param.number = $3;
+ param.name = NULL;
+ stmt_type = INSTANCE; YYACCEPT;
+ }
+ | INSTANCE indom inst EOL {
+ param.indom = indom.whole;
+ param.number = PM_IN_NULL;
+ param.name = $3;
+ stmt_type = INSTANCE; YYACCEPT;
+ }
+ | PROFILE EOL {
+ param.number = PROFILE; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | PROFILE indom ALL EOL {
+ sts = pmAddProfile($2, 0, NULL);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ profile_changed = 1;
+ stmt_type = EOL;
+ YYACCEPT;
+ }
+ | PROFILE indom NONE EOL {
+ sts = pmDelProfile($2, 0, NULL);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ profile_changed = 1;
+ stmt_type = EOL;
+ YYACCEPT;
+ }
+ | PROFILE indom ADD NUMBER EOL {
+ inst = $4;
+ sts = pmAddProfile($2, 1, &inst);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ profile_changed = 1;
+ stmt_type = EOL;
+ YYACCEPT;
+ }
+ | PROFILE indom DEL NUMBER EOL {
+ inst = $4;
+ sts = pmDelProfile($2, 1, &inst);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ profile_changed = 1;
+ stmt_type = EOL;
+ YYACCEPT;
+ }
+ | WATCH EOL {
+ param.number = WATCH; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | WATCH fname EOL {
+ watch($2);
+ stmt_type = WATCH; YYACCEPT;
+ }
+ | PMNS_NAME EOL {
+ param.number = PMNS_NAME; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | PMNS_NAME raw_pmid EOL {
+ param.pmid = $2;
+ stmt_type = PMNS_NAME; YYACCEPT;
+ }
+ | PMNS_PMID EOL {
+ param.number = PMNS_PMID; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | PMNS_PMID NAME EOL {
+ param.name = $2;
+ stmt_type = PMNS_PMID; YYACCEPT;
+ }
+ | PMNS_CHILDREN EOL {
+ param.number = PMNS_CHILDREN; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | PMNS_CHILDREN NAME EOL {
+ param.name = $2;
+ stmt_type = PMNS_CHILDREN; YYACCEPT;
+ }
+ | PMNS_TRAVERSE EOL {
+ param.number = PMNS_TRAVERSE; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | PMNS_TRAVERSE NAME EOL {
+ param.name = $2;
+ stmt_type = PMNS_TRAVERSE; YYACCEPT;
+ }
+ | NAMESPACE fname EOL {
+ param.name = $2;
+ stmt_type = NAMESPACE; YYACCEPT;
+ }
+ | ATTR EOL {
+ param.number = ATTR; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | ATTR attribute EOL {
+ param.number = $2;
+ param.name = NULL;
+ stmt_type = ATTR; YYACCEPT;
+ }
+ | ATTR attribute STRING EOL {
+ param.number = $2;
+ param.name = $3;
+ stmt_type = ATTR; YYACCEPT;
+ }
+
+ | HELP EOL {
+ param.number = -1; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP CLOSE EOL {
+ param.number = CLOSE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP DBG EOL {
+ param.number = DBG; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP DESC EOL {
+ param.number = DESC; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP FETCH EOL {
+ param.number = FETCH; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP GETDESC EOL {
+ param.number = GETDESC; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP INFO EOL {
+ param.number = INFO; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP INSTANCE EOL {
+ param.number = INSTANCE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP NAMESPACE EOL {
+ param.number = NAMESPACE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP OPEN EOL {
+ param.number = OPEN; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP PMNS_CHILDREN EOL {
+ param.number = PMNS_CHILDREN; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP PMNS_NAME EOL {
+ param.number = PMNS_NAME; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP PMNS_PMID EOL {
+ param.number = PMNS_PMID; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP PMNS_TRAVERSE EOL {
+ param.number = PMNS_TRAVERSE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP ATTR EOL {
+ param.number = ATTR; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP PROFILE EOL {
+ param.number = PROFILE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP QUIT EOL {
+ param.number = QUIT; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP STATUS EOL {
+ param.number = STATUS; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP STORE EOL {
+ param.number = STORE; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP TIMER EOL {
+ param.number = TIMER; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP WAIT EOL {
+ param.number = WAIT; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | HELP WATCH EOL {
+ param.number = WATCH; param.pmid = HELP_FULL;
+ stmt_type = HELP; YYACCEPT;
+ }
+
+ | QUIT EOL { stmt_type = QUIT; YYACCEPT; }
+ | DBG EOL {
+ param.number = DBG; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | DBG ALL EOL {
+ param.number = -1;
+ stmt_type = DBG; YYACCEPT;
+ }
+ | DBG NONE EOL {
+ param.number = 0;
+ stmt_type = DBG; YYACCEPT;
+ }
+ | DBG debug EOL {
+ param.number = $2;
+ stmt_type = DBG; YYACCEPT;
+ }
+ | STATUS EOL {
+ stmt_type = STATUS; YYACCEPT;
+ }
+ | TIMER EOL {
+ param.number = TIMER; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | TIMER ON EOL {
+ timer = 1; stmt_type = EOL; YYACCEPT;
+ }
+ | TIMER OFF EOL {
+ timer = 0; stmt_type = EOL; YYACCEPT;
+ }
+ | GETDESC EOL {
+ param.number = GETDESC; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | GETDESC ON EOL {
+ get_desc = 1; stmt_type = EOL; YYACCEPT;
+ }
+ | GETDESC OFF EOL {
+ get_desc = 0; stmt_type = EOL; YYACCEPT;
+ }
+ | WAIT EOL {
+ param.number = WAIT; param.pmid = HELP_USAGE;
+ stmt_type = HELP; YYACCEPT;
+ }
+ | WAIT NUMBER EOL {
+ stmt_type = EOL; sleep($2); YYACCEPT;
+ }
+ | EOL { stmt_type = EOL; YYACCEPT; }
+ | {
+ if (yywrap())
+ YYACCEPT;
+ else {
+ yyerror("Unrecognized command");
+ YYERROR;
+ }
+ }
+ ;
+
+fname : NAME { $$ = strcons("./", $1); }
+ | PATHNAME { $$ = $1; }
+ ;
+
+optdomain : NUMBER { $$ = $1; }
+ | /* nothing */ { $$ = 0; }
+ ;
+
+attribute : NUMBER {
+ sts = __pmAttrKeyStr_r($1, warnStr, sizeof(warnStr));
+ if (sts <= 0) {
+ sprintf(warnStr, "Attribute (%d) is not recognised", $1);
+ yyerror(warnStr);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ | STRING {
+ sts = __pmLookupAttrKey($1, strlen($1)+1);
+ if (sts <= 0) {
+ sprintf(warnStr, "Attribute (%s) is not recognised", $1);
+ yyerror(warnStr);
+ YYERROR;
+ }
+ $$ = sts;
+ }
+ ;
+
+servport : NUMBER { $$ = $1; }
+ | STRING {
+ struct servent *srv = getservbyname($1, NULL);
+ if (srv == NULL) {
+ sprintf(warnStr, "Failed to map (%s) to a port number", $1);
+ yyerror(warnStr);
+ YYERROR;
+ }
+ sprintf(warnStr, "Mapped %s to port number %d", $1, srv->s_port);
+ yywarn(warnStr);
+ $$ = srv->s_port;
+ }
+ ;
+
+metric : NUMBER {
+ pmid.whole = $1;
+ sts = pmNameID(pmid.whole, &str);
+ if (sts < 0) {
+ sprintf(warnStr, "PMID (%s) is not defined in the PMNS",
+ pmIDStr(pmid.whole));
+ yywarn(warnStr);
+ }
+ else
+ free(str);
+ $$ = (int)pmid.whole;
+ }
+ | NUMBER2D {
+ pmid.whole = 0;
+ pmid.part.cluster = $1.num1;
+ pmid.part.item = $1.num2;
+ sts = pmNameID(pmid.whole, &str);
+ if (sts < 0) {
+ sprintf(warnStr, "PMID (%s) is not defined in the PMNS",
+ pmIDStr(pmid.whole));
+ yywarn(warnStr);
+ }
+ else
+ free(str);
+ $$ = (int)pmid.whole;
+ }
+ | NUMBER3D {
+ pmid.whole = 0;
+ pmid.part.domain = $1.num1;
+ pmid.part.cluster = $1.num2;
+ pmid.part.item = $1.num3;
+ sts = pmNameID(pmid.whole, &str);
+ if (sts < 0) {
+ sprintf(warnStr, "PMID (%s) is not defined in the PMNS",
+ pmIDStr(pmid.whole));
+ yywarn(warnStr);
+ }
+ else
+ free(str);
+ $$ = (int)pmid.whole;
+ }
+ | NAME {
+ sts = pmLookupName(1, &$1, &pmid.whole);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ sts = fix_dynamic_pmid($1, &pmid.whole);
+ if (sts < 0) {
+ yyerror(pmErrStr(sts));
+ YYERROR;
+ }
+ $$ = (int)pmid.whole;
+ }
+ ;
+
+indom : NUMBER {
+ indom.whole = $1;
+ $$ = (int)indom.whole;
+ }
+ | NEGNUMBER {
+ indom.whole = $1;
+ $$ = (int)indom.whole;
+ }
+ | NUMBER2D {
+ indom.whole = 0;
+ indom.part.domain = $1.num1;
+ indom.part.serial = $1.num2;
+ $$ = (int)indom.whole;
+ }
+ ;
+
+raw_pmid: NUMBER3D {
+ pmid.whole = 0;
+ pmid.part.domain = $1.num1;
+ pmid.part.cluster = $1.num2;
+ pmid.part.item = $1.num3;
+ $$ = (int)pmid.whole;
+ }
+ ;
+
+metriclist : metric { addmetriclist((pmID)$1); }
+ | metriclist metric { addmetriclist((pmID)$2); }
+ | metriclist COMMA metric { addmetriclist((pmID)$3); }
+ ;
+
+
+arglist : /* nothing, a trick */ { doargs(); }
+ ;
+
+inst : STRING { $$ = $1; }
+ | NAME { $$ = $1; }
+ ;
+
+debug : NUMBER { $$ = $1; }
+ | NAME {
+ sts = __pmParseDebug($1);
+ if (sts < 0) {
+ sprintf(warnStr, "Bad debug flag (%s)", $1);
+ yywarn(warnStr);
+ YYERROR;
+ }
+ $$ = sts;
+ }
+ | NUMBER debug { $$ = $1 | $2; }
+ | NAME debug {
+ sts = __pmParseDebug($1);
+ if (sts < 0) {
+ sprintf(warnStr, "Bad debug flag (%s)", $1);
+ yywarn(warnStr);
+ YYERROR;
+ }
+ $$ = sts | $2;
+ }
+ ;
+
+%%
diff --git a/src/dbpmda/src/lex.h b/src/dbpmda/src/lex.h
new file mode 100644
index 0000000..7426571
--- /dev/null
+++ b/src/dbpmda/src/lex.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef _LEX_H
+#define _LEX_H
+
+typedef struct {
+ int num1;
+ int num2;
+}twodot_num;
+
+typedef struct {
+ int num1;
+ int num2;
+ int num3;
+}threedot_num;
+
+#endif
diff --git a/src/dbpmda/src/lex.l b/src/dbpmda/src/lex.l
new file mode 100644
index 0000000..2a9cebf
--- /dev/null
+++ b/src/dbpmda/src/lex.l
@@ -0,0 +1,611 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+#include "pmapi.h"
+#include "impl.h"
+
+static int lineno = 0;
+static int using_readline = 0;
+
+#include "./lex.h"
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#ifdef FLEX_SCANNER
+#include "./gram.tab.h"
+static int dbpmdaFlexInput (char *, int);
+#else
+#include "./gram.h"
+#endif
+
+#include "./dbpmda.h"
+
+%}
+
+%s FNAME
+%a 2200
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#undef YY_INPUT
+#define YY_INPUT(b,r,ms) (r=dbpmdaFlexInput(b, ms))
+#else
+#undef input
+#undef unput
+#undef yywrap
+#undef yyinput
+#endif
+%}
+
+%%
+
+add { return ADD; }
+all { return ALL; }
+attr { return ATTR; }
+attribute { return ATTR; }
+children { return PMNS_CHILDREN; }
+close { return CLOSE; }
+debug { return DBG; }
+delete { return DEL; }
+desc { return DESC; }
+dso { BEGIN FNAME; return DSO; }
+exit { return QUIT; }
+fetch { return FETCH; }
+getdesc { return GETDESC; }
+help { return HELP; }
+indom { return INDOM; }
+inet { return INET; }
+instance { return INSTANCE; }
+ipv6 { return IPV6; }
+name { return PMNS_NAME; }
+namespace { BEGIN FNAME; return NAMESPACE; }
+none { return NONE; }
+off { return OFF; }
+on { return ON; }
+open { return OPEN; }
+pipe { BEGIN FNAME; return PIPE; }
+pmid { return PMNS_PMID; }
+profile { return PROFILE; }
+q { return QUIT; }
+quit { return QUIT; }
+socket { BEGIN FNAME; return SOCK; }
+status { return STATUS; }
+store { return STORE; }
+text { return INFO; }
+timer { return TIMER; }
+traverse { return PMNS_TRAVERSE; }
+unix { return UNIX; }
+wait { return WAIT; }
+watch { BEGIN FNAME; return WATCH; }
+\? { return HELP; }
+\= { return EQUAL; }
+\, { return COMMA; }
+\+ { return PLUS; }
+
+[A-Za-z][A-Za-z0-9_\./:-]* {
+ yylval.y_str = (char *)malloc(yyleng+1);
+ strcpy(yylval.y_str, yytext);
+ return NAME;
+ }
+
+\$[A-Za-z][A-Za-z0-9_-]* {
+ yylval.y_str = (char *)malloc(yyleng+1);
+ strcpy(yylval.y_str, yytext);
+ return MACRO;
+ }
+
+0[xX][0-9]+ {
+ yylval.y_num = (int)strtol(&yytext[2], NULL, 16);
+ return NUMBER;
+ }
+
+[0-9]+ {
+ yylval.y_num = atoi(yytext);
+ return NUMBER;
+ }
+
+-[0-9]+ {
+ yylval.y_num = atoi(yytext);
+ return NEGNUMBER;
+ }
+
+[0-9]+\.[0-9]+ {
+ sscanf(yytext, "%d.%d", &yylval.y_2num.num1,
+ &yylval.y_2num.num2);
+ return NUMBER2D;
+ }
+
+[0-9]+\.[0-9]+\.[0-9]+ {
+ sscanf(yytext, "%d.%d.%d", &yylval.y_3num.num1,
+ &yylval.y_3num.num2, &yylval.y_3num.num3);
+ return NUMBER3D;
+ }
+
+
+\"[^\"\n][^\"\n]*\" {
+ yylval.y_str = (char *)malloc(yyleng-1);
+ strncpy(yylval.y_str, &yytext[1], yyleng-2);
+ yylval.y_str[yyleng-2] = '\0';
+ return STRING;
+ }
+
+\"[^\"\n][^\"\n]*\n {
+ yyerror("Expected \"");
+ }
+
+\#.*\n { return EOL; }
+
+[\r\t ]+ { }
+
+\n { return EOL; }
+
+<FNAME>[^\t \n]+ {
+ yylval.y_str = (char *)malloc(yyleng+1);
+ strcpy(yylval.y_str, yytext);
+ BEGIN 0;
+ return PATHNAME;
+ }
+
+
+. {
+ yyerror("Illegal character");
+ }
+%%
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+static char *prompt = "dbpmda";
+static char *line = NULL;
+static int lastc = '\n';
+static int linepos = 0;
+static int linelen = 0;
+static int mark = -1;
+
+extern char *configfile;
+
+extern int eflag;
+extern int iflag;
+
+#ifdef FLEX_SCANNER
+
+static int
+dbpmdaGetc (char * inbuf)
+{
+ int inch;
+
+#ifdef HAVE_READLINE
+ char rl_prompt_storage[64];
+ char *rl_prompt = rl_prompt_storage;
+ static char *str = NULL;
+ static int strpos = 0;
+
+ if (using_readline) {
+ if (iflag)
+ snprintf(rl_prompt_storage, 64, "%s> ", prompt);
+ else
+ rl_prompt = NULL;
+
+ rl_instream = yyin;
+
+ if (!str) {
+ do {
+ str = readline(rl_prompt);
+ if (!str) {
+ /* EOF */
+ inbuf[0] = inch = '\0';
+ goto done;
+ }
+ } while (!str[0]);
+ }
+
+ inch = str[strpos++];
+ if (inch == '\r')
+ inch = str[strpos++];
+
+ if (!inch) {
+ /* end of input line, fake out \n so parser notices */
+ inch = '\n';
+ /* and setup to call readline() next time */
+ free(str);
+ str = NULL;
+ strpos = 0;
+ }
+
+ inbuf[0] = inch;
+ goto done;
+ }
+ /* else fall through to the non-readline method */
+#endif
+
+ while ((inch = fgetc (yyin)) != EOF) {
+ inbuf[0] = inch & 0xFFU;
+ if (inbuf[0] == '\r') {
+ /* for windows, eat carriage returns */
+ continue;
+ }
+ if (eflag) {
+ putchar(inch);
+ fflush(stdout);
+ }
+ break;
+ }
+ if (inch == EOF)
+ inch = '\0';
+
+#ifdef HAVE_READLINE
+done:
+#endif
+#ifdef DESPERATE
+ fprintf(stderr, "dbpmdaGetc: yyin=%p (%d) using_readline=%d lastc=%x \"%c\" inch=%x \"%c\"\n", yyin, fileno(yyin), using_readline, lastc & 0xff, lastc, inch & 0xff, inch);
+#endif
+ return inch;
+}
+
+static int
+dbpmdaFlexInput (char * inbuf, int ms)
+{
+ static FILE * inf = NULL;
+ static int first = 1;
+ static int save_iflag, save_eflag;
+
+ if (first) {
+ first = 0;
+ if (!access (".dbpmdarc", R_OK)) {
+ inf = yyin;
+ if ((yyin = fopen (".dbpmdarc", "r")) != NULL) {
+ save_eflag = eflag;
+ save_iflag = iflag;
+ eflag = 1;
+ iflag = 1;
+ prompt = ".dbpmdarc";
+ configfile = ".dbpmdarc";
+ lineno = 0;
+ } else {
+ yyin = inf;
+ }
+ }
+#ifdef HAVE_READLINE
+ else {
+ using_readline = isatty(fileno(yyin));
+ }
+#endif
+ }
+
+ if (lastc == '\n') {
+#ifdef HAVE_READLINE
+ if (line != NULL && *line != '\0' && *line != '\n') {
+ /* line is not empty, push it into history */
+ char *newline = strchr(line, '\n');
+ if (newline != NULL) *newline = '\0';
+ add_history(line);
+ if (newline != NULL) *newline = '\n';
+ }
+#endif
+ if (iflag && !using_readline) {
+ printf ("%s> ", prompt);
+ fflush (stdout);
+ }
+ lineno++;
+ linepos = 0;
+ }
+
+ if (linepos == linelen) {
+ linelen = (linelen) ? linelen * 2 : 128;
+ if ((line = (char *)realloc(line, linelen * sizeof(char))) == NULL) {
+ fprintf(stderr, "%s: Lexer internal error\n", pmProgname);
+ exit(1);
+ }
+ }
+
+ if (ms > 0) {
+ while (1) {
+ if ((lastc = dbpmdaGetc(inbuf))) {
+ line[linepos++] = inbuf[0];
+ return (1);
+ } else {
+ /* It maybe an EOF */
+ if ((inf != NULL) && (inf != yyin)) {
+ yyin = inf;
+ lineno = 1;
+#ifdef HAVE_READLINE
+ using_readline = isatty(fileno(yyin));
+#endif
+ prompt = "dbpmda";
+ configfile = NULL;
+ iflag = save_iflag;
+ if (iflag) {
+ if (using_readline)
+ putchar('\n');
+ else
+ printf("%s> ", prompt);
+ fflush(stdout);
+ }
+ eflag = save_eflag;
+ } else {
+ return 0;
+ }
+ }
+ }
+ }
+
+ return ms;
+}
+
+#else /* AT&T Lex */
+
+static char peekc = '\0';
+
+char
+input(void)
+{
+ int get;
+ static int first = 1;
+ static int save_eflag;
+ static int save_iflag;
+ static int inrc;
+
+ if (first) {
+ if (access(".dbpmdarc", R_OK) == 0) {
+ int fd = open(".dbpmdarc", O_RDONLY);
+ if (fd >= 0) {
+ inrc = dup(0);
+ close(0);
+ dup(fd);
+ close(fd);
+ save_eflag = eflag;
+ save_iflag = iflag;
+ eflag = 1;
+ iflag = 1;
+ prompt = ".dbpmdarc";
+ configfile = ".dbpmdarc";
+ }
+ }
+ }
+
+ if (peekc) {
+ lastc = peekc;
+ peekc = '\0';
+ return lastc;
+ }
+
+ again:
+ if (lastc == '\n' || first) {
+ if (iflag) {
+ printf("%s> ", prompt);
+ fflush(stdout);
+ }
+ if (first)
+ first = 0;
+ else
+ lineno++;
+ linepos = 0;
+ }
+ else if (lastc == '\0') {
+ linepos = 0;
+ return lastc;
+ }
+
+ if (linepos == linelen) {
+ if (linelen == 0)
+ linelen = 128;
+ else
+ linelen *= 2;
+ line = (char*)realloc(line, linelen * sizeof(char));
+ }
+
+ get = getchar();
+
+ line[linepos++] = (char)get;
+
+ if (get == EOF) {
+ if (inrc) {
+ close(0);
+ dup(inrc);
+ close(inrc);
+ inrc = 0;
+ eflag = save_eflag;
+ iflag = save_iflag;
+ prompt = "dbpmda";
+ configfile = NULL;
+ putchar('\n');
+ lineno = 0;
+ lastc = '\n';
+ goto again;
+ }
+ lastc = '\0';
+ }
+ else {
+ lastc = get;
+ if (eflag) {
+ putchar(lastc);
+ fflush(stdout);
+ }
+ }
+
+ return lastc;
+}
+
+void
+unput(char c)
+{
+ peekc = c;
+}
+#endif
+
+int
+yywrap(void)
+{
+ return lastc == '\0';
+}
+
+char
+lastinput(void)
+{
+ return lastc;
+}
+
+int
+markpos(void)
+{
+ mark = linepos;
+ return mark;
+}
+
+void
+locateError(void)
+{
+ int i;
+
+ if (mark < 0) {
+ fprintf(stderr, "%s: Unrecoverable internal error in locateError()\n",
+ pmProgname);
+ exit(1);
+ }
+
+ for (i = 0; prompt[i]; i++)
+ putchar(' ');
+
+ putchar(' ');
+
+ for (i = 0; i < mark; i++) {
+ if (line[i] == '\t')
+ putchar('\t');
+ else if (line[i] == '\n' || line[i] == '\0')
+ break;
+ else
+ putchar(' ');
+ }
+
+ putchar('^');
+ printf(" at or near here\n");
+ fflush(stdout);
+}
+
+
+void
+doargs(void)
+{
+ /*
+ * a hack ... slide underneath lex/yacc to do the cmd-line args
+ */
+ char buf[256]; /* big enough for a single arg? */
+ char *p;
+ char c;
+ char delim = '\0';
+
+ initarglist();
+
+ if (lastc == '\n') {
+ addarglist(NULL);
+ return;
+ }
+
+ p = buf;
+ for ( ; ; ) {
+#ifdef FLEX_SCANNER
+ dbpmdaFlexInput (&c, 1);
+#else
+ c = input();
+#endif
+ if (delim) {
+ if (c == delim) {
+ delim = '\0';
+ continue;
+ }
+ }
+ else if (c == ' ' || c == '\t' || c == '\n' || c == '\0') {
+ if (p > buf) {
+ *p = '\0';
+ addarglist(buf);
+ p = buf;
+ }
+ if (c == '\n' || c == '\0') {
+ /*
+ * EOL removed from grammar after arglist, so no
+ * need push \n or \0 back into the input stream
+ * (which was not working well!)
+ */
+ addarglist(NULL);
+ return;
+ }
+ continue;
+ }
+ else if (c == '"' || c == '\'') {
+ delim = c;
+ continue;
+ }
+ *p++ = c;
+ }
+}
+
+void
+yywarn(char *s)
+{
+ extern int lineno;
+
+ if (configfile == NULL)
+ fprintf(stderr, "Warning: %s\n", s);
+ else
+ fprintf(stderr, "Warning [%s, line %d]\n%s\n",
+ configfile, lineno, s);
+}
+
+void
+yyerror(const char *s)
+{
+ extern int lineno;
+ extern int stmt_type;
+ char c;
+
+ markpos();
+
+ c = lastinput();
+ for ( ; ; ) {
+ if (c == '\0')
+ break;
+ if (c == '\n')
+ break;
+#ifdef FLEX_SCANNER
+ dbpmdaFlexInput (&c, 1);
+#else
+ c = input();
+#endif
+ }
+ stmt_type = EOL;
+
+ locateError();
+
+ if (configfile == NULL)
+ fprintf(stderr, "Error: %s\nType 'help' for a list of commands.\n", s);
+ else
+ fprintf(stderr,
+ "Error [%s, line %d]: %s\nType 'help' for a list of commands.\n",
+ configfile, lineno, s);
+
+
+}
diff --git a/src/dbpmda/src/pmda.c b/src/dbpmda/src/pmda.c
new file mode 100644
index 0000000..d9b454b
--- /dev/null
+++ b/src/dbpmda/src/pmda.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+
+#ifdef HAVE_VALUES_H
+#include <values.h>
+#endif
+#include <float.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+#include "./dbpmda.h"
+#include "./lex.h"
+#include "./gram.h"
+
+static __pmTimeval now = { 0, 0 };
+
+int infd;
+int outfd;
+char *myPmdaName = 0;
+
+extern int _creds_timeout;
+
+#ifndef HAVE_STRTOLL
+/*
+ * cheap hack ...won't work for large values!
+ */
+static __int64_t
+strtoll(char *p, char **endp, int base)
+{
+ return (__int64_t)strtol(p, endp, base);
+}
+#endif
+
+#ifndef HAVE_STRTOULL
+/*
+ * cheap hack ...won't work for large values!
+ */
+static __uint64_t
+strtoull(char *p, char **endp, int base)
+{
+ return (__uint64_t)strtoul(p, endp, base);
+}
+#endif
+
+/* version exchange - get a credentials PDU from 2.0 agents */
+
+static int
+agent_creds(__pmPDU *pb)
+{
+ int i;
+ int sts = 0;
+ int version = UNKNOWN_VERSION;
+ int credcount = 0;
+ int sender = 0;
+ int vflag = 0;
+ __pmCred *credlist = NULL;
+
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0)
+ return sts;
+
+ for (i = 0; i < credcount; i++) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "agent_creds: doing cred #%d from PID %d\n", i+1, sender);
+#endif
+ switch(credlist[i].c_type) {
+ case CVERSION:
+ version = credlist[i].c_vala;
+ vflag = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "agent_creds: version cred (%u)\n", version);
+#endif
+ break;
+ }
+ }
+
+ if (credlist)
+ free(credlist);
+
+ if (((sts = __pmSetVersionIPC(infd, version)) < 0) ||
+ ((sts = __pmSetVersionIPC(outfd, version)) < 0))
+ return sts;
+
+ if (vflag) { /* complete the version exchange - respond to agent */
+ __pmCred handshake[1];
+
+ handshake[0].c_type = CVERSION;
+ handshake[0].c_vala = PDU_VERSION;
+ handshake[0].c_valb = 0;
+ handshake[0].c_valc = 0;
+ if ((sts = __pmSendCreds(outfd, (int)getpid(), 1, handshake)) < 0)
+ return sts;
+ }
+
+ return 0;
+}
+
+static void
+pmdaversion(void)
+{
+ int sts;
+ __pmPDU *ack;
+ int pinpdu;
+
+ pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, _creds_timeout, &ack);
+ if (sts == PDU_CREDS) {
+ if ((sts = agent_creds(ack)) < 0) {
+ fprintf(stderr, "Warning: version exchange failed "
+ "for PMDA %s: %s\n", myPmdaName, pmErrStr(sts));
+ }
+ }
+ else {
+ if (sts < 0)
+ fprintf(stderr, "__pmGetPDU(%d): %s\n", infd, pmErrStr(sts));
+ else
+ fprintf(stderr, "pmdaversion: expecting PDU_CREDS, got PDU type %d\n", sts);
+ fprintf(stderr, "Warning: no version exchange with PMDA %s\n",
+ myPmdaName);
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(ack);
+}
+
+void
+openpmda(char *fname)
+{
+ int i;
+ struct stat buf;
+
+ if (stat(fname, &buf) < 0) {
+ fprintf(stderr, "openpmda: %s: %s\n", fname, osstrerror());
+ return;
+ }
+
+ closepmda();
+ free(param.argv[0]);
+ param.argv[0] = strdup(fname);
+ param.argc--;
+ printf("Start %s PMDA: %s", basename(param.argv[0]), fname);
+ for (i = 1; i < param.argc; i++)
+ printf(" %s", param.argv[i]);
+ putchar('\n');
+
+ if (__pmProcessCreate(param.argv, &infd, &outfd) < (pid_t)0) {
+ fprintf(stderr, "openpmda: create process: %s\n", osstrerror());
+ }
+ else {
+ connmode = CONN_DAEMON;
+ reset_profile();
+ if (myPmdaName != NULL)
+ free(myPmdaName);
+ myPmdaName = strdup(fname);
+ pmdaversion();
+ }
+}
+
+#ifdef HAVE_STRUCT_SOCKADDR_UN
+void
+open_unix_socket(char *fname)
+{
+ int fd;
+ struct stat buf;
+ struct sockaddr_un s_un;
+ int len;
+
+ if (stat(fname, &buf) < 0) {
+ fprintf(stderr, "opensocket: %s: %s\n", fname, osstrerror());
+ return;
+ }
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ fprintf(stderr, "opensocket: socket: %s\n", netstrerror());
+ return;
+ }
+
+ memset(&s_un, 0, sizeof(s_un));
+ s_un.sun_family = AF_UNIX;
+ strncpy(s_un.sun_path, fname, strlen(fname));
+ len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(s_un.sun_path);
+
+ closepmda();
+
+ if (connect(fd, (struct sockaddr *)&s_un, len) < 0) {
+ fprintf(stderr, "opensocket: connect: %s\n", netstrerror());
+ close(fd);
+ return;
+ }
+
+ infd = fd;
+ outfd = fd;
+
+ printf("Connect to PMDA on socket %s\n", fname);
+
+ connmode = CONN_DAEMON;
+ reset_profile();
+ if (myPmdaName != NULL)
+ free(myPmdaName);
+ myPmdaName = strdup(fname);
+ pmdaversion();
+}
+#else
+void
+open_unix_socket(char *fname)
+{
+ __pmNotifyErr(LOG_CRIT, "UNIX domain sockets unsupported\n");
+}
+#endif
+
+static void
+open_socket(int port, int family, const char *protocol)
+{
+ __pmSockAddr *addr;
+ int fd, sts;
+ char socket[64];
+
+ fd = (family == AF_INET) ? __pmCreateSocket() : __pmCreateIPv6Socket();
+ if (fd < 0) {
+ fprintf(stderr, "opensocket: socket: %s\n", netstrerror());
+ return;
+ }
+
+ addr = __pmLoopBackAddress(family);
+ if (addr == NULL) {
+ fprintf(stderr, "opensocket: loopback: %s\n", netstrerror());
+ __pmCloseSocket(fd);
+ return;
+ }
+
+ closepmda();
+
+ __pmSockAddrSetPort(addr, port);
+ sts = __pmConnect(fd, addr, __pmSockAddrSize());
+ __pmSockAddrFree(addr);
+
+ if (sts < 0) {
+ fprintf(stderr, "opensocket: connect: %s\n", netstrerror());
+ __pmCloseSocket(fd);
+ return;
+ }
+
+ infd = fd;
+ outfd = fd;
+
+ sprintf(socket, "%s port %d", protocol, port);
+ printf("Connect to PMDA on %s\n", socket);
+
+ connmode = CONN_DAEMON;
+ reset_profile();
+ if (myPmdaName != NULL)
+ free(myPmdaName);
+ myPmdaName = strdup(socket);
+ pmdaversion();
+}
+
+void
+open_inet_socket(int port)
+{
+ open_socket(port, AF_INET, "inet");
+}
+
+void
+open_ipv6_socket(int port)
+{
+ open_socket(port, AF_INET6, "ipv6");
+}
+
+void
+closepmda(void)
+{
+ if (connmode != NO_CONN) {
+ /* End of context logic mimics PMCD, no error checking is needed. */
+ __pmSendError(outfd, FROM_ANON, PM_ERR_NOTCONN);
+ close(outfd);
+ close(infd);
+ __pmResetIPC(infd);
+ connmode = NO_CONN;
+ if (myPmdaName != NULL) {
+ free(myPmdaName);
+ myPmdaName = NULL;
+ }
+ }
+}
+
+
+int
+dopmda_desc(pmID pmid, pmDesc *desc, int print)
+{
+ int sts;
+ __pmPDU *pb;
+ int i;
+ int pinpdu;
+
+ if ((sts = __pmSendDescReq(outfd, FROM_ANON, pmid)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_DESC) {
+ if ((sts = __pmDecodeDesc(pb, desc)) >= 0) {
+ if (print)
+ __pmPrintDesc(stdout, desc);
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_PDU)
+ __pmPrintDesc(stdout, desc);
+#endif
+ }
+ else
+ printf("Error: __pmDecodeDesc() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else if (sts == 0)
+ printf("Error: __pmGetPDU() failed: PDU empty, PMDA may have died\n");
+ else
+ printf("Error: __pmGetPDU() failed: wrong PDU (%x)\n", sts);
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendDescReq() failed: %s\n", pmErrStr(sts));
+
+ return sts;
+}
+
+void
+dopmda(int pdu)
+{
+ int sts;
+ pmDesc desc;
+ pmDesc *desc_list = NULL;
+ pmResult *result = NULL;
+ __pmInResult *inresult;
+ __pmPDU *pb;
+ int i;
+ int j;
+ int ident;
+ int length;
+ char *buffer;
+ struct timeval start;
+ struct timeval end;
+ char name[32];
+ char **namelist;
+ int *statuslist;
+ int numnames;
+ pmID pmid;
+ int pinpdu;
+
+ if (timer != 0)
+ __pmtimevalNow(&start);
+
+ switch (pdu) {
+
+ case PDU_DESC_REQ:
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ sts = dopmda_desc(param.pmid, &desc, 1);
+ break;
+
+ case PDU_FETCH:
+ printf("PMID(s):");
+ for (i = 0; i < param.numpmid; i++)
+ printf(" %s", pmIDStr(param.pmidlist[i]));
+ putchar('\n');
+
+ if (get_desc) {
+ desc_list = (pmDesc *)malloc(param.numpmid * sizeof(pmDesc));
+ if (desc_list == NULL) {
+ printf("Error: PDU fetch() failed: %s\n", pmErrStr(ENOMEM));
+ return;
+ }
+ for (i = 0; i < param.numpmid; i++) {
+ if ((sts = dopmda_desc(param.pmidlist[i], &desc_list[i], 0)) < 0) {
+ free(desc_list);
+ return;
+ }
+ }
+ }
+
+ sts = 0;
+ if (profile_changed) {
+ if ((sts = __pmSendProfile(outfd, FROM_ANON, 0, profile)) < 0)
+ printf("Error: __pmSendProfile() failed: %s\n", pmErrStr(sts));
+ else
+ profile_changed = 0;
+ }
+ if (sts >= 0) {
+ if ((sts = __pmSendFetch(outfd, FROM_ANON, 0, NULL, param.numpmid, param.pmidlist)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_RESULT) {
+ if ((sts = __pmDecodeResult(pb, &result)) >= 0) {
+ if (get_desc)
+ _dbDumpResult(stdout, result, desc_list);
+ else
+ __pmDumpResult(stdout, result);
+ pmFreeResult(result);
+ }
+ else
+ printf("Error: __pmDecodeResult() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else if (sts == 0)
+ printf("Error: __pmGetPDU() failed: PDU empty, PMDA may have died\n");
+ else
+ printf("Error: __pmGetPDU() failed: wrong PDU (%x)\n", sts);
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendFetch() failed: %s\n", pmErrStr(sts));
+ }
+ if (desc_list)
+ free(desc_list);
+ break;
+
+ case PDU_INSTANCE_REQ:
+ printf("pmInDom: %s\n", pmInDomStr(param.indom));
+ if ((sts = __pmSendInstanceReq(outfd, FROM_ANON, &now, param.indom, param.number, param.name)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_INSTANCE) {
+ if ((sts = __pmDecodeInstance(pb, &inresult)) >= 0) {
+ printindom(stdout, inresult);
+ __pmFreeInResult(inresult);
+ }
+ else
+ printf("Error: __pmDecodeInstance() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendInstanceReq() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_RESULT:
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+
+ printf("Getting description...\n");
+
+ if ((sts = dopmda_desc(param.pmid, &desc, 0)) < 0)
+ return;
+
+ if (profile_changed) {
+ printf("Sending Profile...\n");
+ if ((sts = __pmSendProfile(outfd, FROM_ANON, 0, profile)) < 0) {
+ printf("Error: __pmSendProfile() failed: %s\n", pmErrStr(sts));
+ return;
+ }
+ else
+ profile_changed = 0;
+ }
+
+ printf("Getting Result Structure...\n");
+ pinpdu = 0;
+ if ((sts = __pmSendFetch(outfd, FROM_ANON, 0, NULL,
+ 1, &(desc.pmid))) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER,
+ &pb)) == PDU_RESULT) {
+ if ((sts = __pmDecodeResult(pb, &result)) < 0)
+ printf("Error: __pmDecodeResult() failed: %s\n",
+ pmErrStr(sts));
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_FETCH)
+ __pmDumpResult(stdout, result);
+#endif
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+ }
+ else
+ printf("Error: __pmSendFetch() failed: %s\n", pmErrStr(sts));
+ /*
+ * pb is still pinned, and result may contain pointers into
+ * a second PDU buffer from __pmDecodeResult() ... need to
+ * ensure all PDU buffers are unpinned once we're done with
+ * result or giving up
+ */
+
+ if (sts < 0) {
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ return;
+ }
+
+ if ((sts = fillResult(result, desc.type)) < 0) {
+ pmFreeResult(result);
+ __pmUnpinPDUBuf(pb);
+ return;
+ }
+
+ printf("Sending Result...\n");
+ sts = __pmSendResult(outfd, FROM_ANON, result);
+ pmFreeResult(result);
+ __pmUnpinPDUBuf(pb);
+ if (sts >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER,
+ &pb)) == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0) {
+ if (sts < 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ }
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendResult() failed: %s\n", pmErrStr(sts));
+
+ break;
+
+ case PDU_TEXT_REQ:
+ if (param.number == PM_TEXT_PMID) {
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ ident = param.pmid;
+ }
+ else {
+ printf("pmInDom: %s\n", pmInDomStr(param.indom));
+ ident = param.indom;
+ }
+
+ for (j = 0; j < 2; j++) {
+
+ if (j == 0)
+ param.number |= PM_TEXT_ONELINE;
+ else {
+ param.number &= ~PM_TEXT_ONELINE;
+ param.number |= PM_TEXT_HELP;
+ }
+
+ if ((sts = __pmSendTextReq(outfd, FROM_ANON, ident, param.number)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_TEXT) {
+ if ((sts = __pmDecodeText(pb, &i, &buffer)) >= 0) {
+ if (j == 0) {
+ if (*buffer != '\0')
+ printf("[%s]\n", buffer);
+ else
+ printf("[<no one line help text specified>]\n");
+ }
+ else if (*buffer != '\0')
+ printf("%s\n", buffer);
+ else
+ printf("<no help text specified>\n");
+ free(buffer);
+ }
+ else
+ printf("Error: __pmDecodeText() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ }
+ else
+ printf("Error: __pmSendTextReq() failed: %s\n", pmErrStr(sts));
+
+ }
+ break;
+
+ case PDU_PMNS_IDS:
+ printf("PMID: %s\n", pmIDStr(param.pmid));
+ if ((sts = __pmSendIDList(outfd, FROM_ANON, 1, &param.pmid, 0)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_PMNS_NAMES) {
+ if ((sts = __pmDecodeNameList(pb, &numnames, &namelist, NULL)) >= 0) {
+ for (i = 0; i < sts; i++) {
+ printf(" %s\n", namelist[i]);
+ }
+ free(namelist);
+ }
+ else
+ printf("Error: __pmDecodeNameList() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendIDList() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_NAMES:
+ printf("Metric: %s\n", param.name);
+ if ((sts = __pmSendNameList(outfd, FROM_ANON, 1, &param.name, NULL)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_PMNS_IDS) {
+ int xsts;
+
+ if ((sts = __pmDecodeIDList(pb, 1, &pmid, &xsts)) >= 0)
+ printf(" %s\n", pmIDStr(pmid));
+ else
+ printf("Error: __pmDecodeIDList() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendIDList() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_CHILD:
+ printf("Metric: %s\n", param.name);
+ if ((sts = __pmSendChildReq(outfd, FROM_ANON, param.name, 1)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_PMNS_NAMES) {
+ if ((sts = __pmDecodeNameList(pb, &numnames, &namelist, &statuslist)) >= 0) {
+ for (i = 0; i < numnames; i++) {
+ printf(" %8.8s %s\n", statuslist[i] == 1 ? "non-leaf" : "leaf", namelist[i]);
+ }
+ free(namelist);
+ free(statuslist);
+ }
+ else
+ printf("Error: __pmDecodeNameList() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendChildReq() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_PMNS_TRAVERSE:
+ printf("Metric: %s\n", param.name);
+ if ((sts = __pmSendTraversePMNSReq(outfd, FROM_ANON, param.name)) >= 0) {
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb)) == PDU_PMNS_NAMES) {
+ if ((sts = __pmDecodeNameList(pb, &numnames, &namelist, NULL)) >= 0) {
+ for (i = 0; i < numnames; i++) {
+ printf(" %s\n", namelist[i]);
+ }
+ free(namelist);
+ }
+ else
+ printf("Error: __pmDecodeNameList() failed: %s\n", pmErrStr(sts));
+ }
+ else if (sts == PDU_ERROR) {
+ if ((i = __pmDecodeError(pb, &sts)) >= 0)
+ printf("Error PDU: %s\n", pmErrStr(sts));
+ else
+ printf("Error: __pmDecodeError() failed: %s\n", pmErrStr(i));
+ }
+ else
+ printf("Error: __pmGetPDU() failed: %s\n", pmErrStr(sts));
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ printf("Error: __pmSendTraversePMNS() failed: %s\n", pmErrStr(sts));
+ break;
+
+ case PDU_AUTH:
+ j = param.number; /* attribute key */
+ buffer = param.name; /* attribute value */
+ length = !buffer ? 0 : strlen(buffer) + 1; /* value length */
+ i = 0; /* client ID */
+
+ __pmAttrKeyStr_r(j, name, sizeof(name)-1);
+ name[sizeof(name)-1] = '\0';
+
+ printf("Attribute: %s=%s\n", name, buffer ? buffer : "''");
+ if ((sts = __pmSendAuth(outfd, 0 /* context */, j, buffer, length)) >= 0)
+ printf("Success\n");
+ else
+ printf("Error: __pmSendAuth() failed: %s\n", pmErrStr(sts));
+ break;
+
+ default:
+ printf("Error: Daemon PDU (%s) botch!\n", __pmPDUTypeStr(pdu));
+ sts = PDU_ERROR;
+ break;
+ }
+
+ if (sts >= 0 && timer != 0) {
+ __pmtimevalNow(&end);
+ printf("Timer: %f seconds\n", __pmtimevalSub(&end, &start));
+ }
+}
+
+int
+fillResult(pmResult *result, int type)
+{
+ int i;
+ int sts = 0;
+ pmAtomValue atom;
+ pmValueSet *vsp;
+ char *endbuf = NULL;
+
+ switch(type) {
+ case PM_TYPE_32:
+ atom.l = (int)strtol(param.name, &endbuf, 10);
+ break;
+ case PM_TYPE_U32:
+ atom.ul = (unsigned int)strtoul(param.name, &endbuf, 10);
+ break;
+ case PM_TYPE_64:
+ atom.ll = strtoll(param.name, &endbuf, 10);
+ break;
+ case PM_TYPE_U64:
+ atom.ull = strtoull(param.name, &endbuf, 10);
+ break;
+ case PM_TYPE_FLOAT:
+ atom.d = strtod(param.name, &endbuf);
+ if (atom.d < FLT_MIN || atom.d > FLT_MAX)
+ sts = -ERANGE;
+ else {
+ atom.f = atom.d;
+ }
+ break;
+ case PM_TYPE_DOUBLE:
+ atom.d = strtod(param.name, &endbuf);
+ break;
+ case PM_TYPE_STRING:
+ atom.cp = (char *)malloc(strlen(param.name) + 1);
+ if (atom.cp == NULL)
+ sts = -ENOMEM;
+ else {
+ strcpy(atom.cp, param.name);
+ endbuf = "";
+ }
+ break;
+ default:
+ printf("Error: dbpmda does not support storing into %s metrics\n", pmTypeStr(type));
+ sts = PM_ERR_TYPE;
+ }
+
+ if (sts < 0) {
+ if (sts != PM_ERR_TYPE)
+ printf("Error: Decoding value: %s\n", pmErrStr(sts));
+ }
+ else if (endbuf != NULL && *endbuf != '\0') {
+ printf("Error: Value \"%s\" is incompatible with metric type (PM_TYPE_%s)\n",
+ param.name, pmTypeStr(type));
+ sts = PM_ERR_VALUE;
+ }
+
+ if (sts >= 0) {
+ vsp = result->vset[0];
+
+ if (vsp->numval == 0) {
+ printf("Error: %s not available!\n", pmIDStr(param.pmid));
+ return PM_ERR_VALUE;
+ }
+
+ if (vsp->numval < 0) {
+ printf("Error: %s: %s\n", pmIDStr(param.pmid), pmErrStr(vsp->numval));
+ return vsp->numval;
+ }
+
+ for (i = 0; i < vsp->numval; i++) {
+ if (vsp->numval > 1)
+ printf("%s [%d]: ", pmIDStr(param.pmid), i);
+ else
+ printf("%s: ", pmIDStr(param.pmid));
+
+ pmPrintValue(stdout, vsp->valfmt, type, &vsp->vlist[i], 1);
+ vsp->valfmt = __pmStuffValue(&atom, &vsp->vlist[i], type);
+ printf(" -> ");
+ pmPrintValue(stdout, vsp->valfmt, type, &vsp->vlist[i], 1);
+ putchar('\n');
+ }
+ }
+
+ return sts;
+}
+
diff --git a/src/dbpmda/src/util.c b/src/dbpmda/src/util.c
new file mode 100644
index 0000000..c81832a
--- /dev/null
+++ b/src/dbpmda/src/util.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "./dbpmda.h"
+#include "./lex.h"
+#include "./gram.h"
+
+extern pmdaInterface dispatch;
+extern int infd;
+extern int outfd;
+
+__pmProfile *profile;
+int profile_changed;
+int timer;
+int get_desc;
+
+static pmID *pmidlist;
+static int numpmid;
+static __pmContext *ctxp;
+
+static char **argv;
+static int argc;
+
+/*
+ * Warning: order of these strings _must_ match bit field sequence defined
+ * in impl.h for DBG_TRACE_* macros
+ */
+static char* debugFlags[] = {
+ "pdu", "fetch", "profile", "value", "context", "indom", "pdubuf", "log",
+ "logmeta", "optfetch", "af", "appl0", "appl1", "appl2", "pmns", "libpmda",
+ "timecontrol", "pmc", "derive", "lock", "interp", "config", "loop", "fault"
+};
+
+static int numFlags = sizeof(debugFlags)/sizeof(debugFlags[0]);
+
+void
+reset_profile(void)
+{
+ if ((profile = (__pmProfile *)realloc(profile, sizeof(__pmProfile))) == NULL) {
+ __pmNoMem("reset_profile", sizeof(__pmProfile), PM_FATAL_ERR);
+ exit(1);
+ }
+ ctxp->c_instprof = profile;
+ memset(profile, 0, sizeof(__pmProfile));
+ profile->state = PM_PROFILE_INCLUDE; /* default global state */
+ profile_changed = 1;
+}
+
+void
+setup_context(void)
+{
+ int sts;
+#ifdef PM_MULTI_THREAD
+ pthread_mutex_t save_c_lock;
+#endif
+
+ if ((sts = pmNewContext(PM_CONTEXT_LOCAL, NULL)) < 0) {
+ fprintf(stderr, "setup_context: creation failed: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+
+ ctxp = __pmHandleToPtr(sts);
+ if (ctxp == NULL) {
+ fprintf(stderr, "botch: setup_context: __pmHandleToPtr(%d) returns NULL!\n", sts);
+ exit(1);
+ }
+ /*
+ * Note: ctxp->c_lock remains locked throughout ... setup_context()
+ * is only called once, and a single context is used throughout
+ * to "fake" out the connection to the current PMDA ... so
+ * there is no PM_UNLOCK(ctxp->c_lock) anywhere in the dbpmda
+ * code.
+ * This works because ctxp->c_lock is a recursive lock and
+ * dbpmda is single-threaded.
+ */
+
+#ifdef PM_MULTI_THREAD
+ /* need to be careful about the initialized lock */
+ save_c_lock = ctxp->c_lock;
+#endif
+ memset(ctxp, 0, sizeof(__pmContext));
+#ifdef PM_MULTI_THREAD
+ ctxp->c_lock = save_c_lock;
+#endif
+ ctxp->c_type = PM_CONTEXT_HOST;
+ reset_profile();
+}
+
+char *
+strcons(char *s1, char *s2)
+{
+ int i;
+ char *buf;
+
+ i = (int)strlen(s1) + (int)strlen(s2) + 1;
+
+ buf = (char *)malloc(i);
+ if (buf == NULL) {
+ fprintf(stderr, "strcons: malloc failed: %s\n", osstrerror());
+ exit(1);
+ }
+
+ strcpy(buf, s1);
+ strcat(buf, s2);
+
+ return buf;
+}
+
+char *
+strnum(int n)
+{
+ char *buf;
+
+ buf = (char *)malloc(13);
+ if (buf == NULL) {
+ fprintf(stderr, "strnum: malloc failed: %s\n", osstrerror());
+ exit(1);
+ }
+ sprintf(buf, "%d", n);
+ return buf;
+}
+
+void
+initmetriclist(void)
+{
+ param.numpmid = 0;
+ param.pmidlist = NULL;
+}
+
+void
+addmetriclist(pmID pmid)
+{
+ param.numpmid++;
+
+ if (param.numpmid >= numpmid) {
+ numpmid = param.numpmid;
+ pmidlist = (pmID *)realloc(pmidlist, numpmid * sizeof(pmidlist[0]));
+ if (pmidlist == NULL) {
+ fprintf(stderr, "addmetriclist: realloc failed: %s\n", osstrerror());
+ exit(1);
+ }
+ }
+
+ pmidlist[param.numpmid-1] = pmid;
+ param.pmidlist = pmidlist;
+}
+
+void
+initarglist(void)
+{
+ int i;
+
+ for (i = 0; i < param.argc; i++)
+ if (param.argv[i] != NULL)
+ free(param.argv[i]);
+ param.argc = 0;
+ param.argv = NULL;
+ addarglist("");
+}
+
+void
+addarglist(char *arg)
+{
+ param.argc++;
+
+ if (param.argc >= argc) {
+ argc = param.argc;
+ argv = (char **)realloc(argv, argc * sizeof(pmProgname));
+ if (argv == NULL) {
+ fprintf(stderr, "addarglist: realloc failed: %s\n", osstrerror());
+ exit(1);
+ }
+ }
+
+ if (arg != NULL)
+ argv[param.argc-1] = strdup(arg);
+ else
+ argv[param.argc-1] = arg;
+ param.argv = argv;
+}
+
+void
+watch(char *fname)
+{
+ char cmd[200];
+
+ sprintf(cmd, "xterm -hold -title \"dbpmda watch %s\" -geom 80x16 -bg dodgerblue4 -e tail -f %s &",
+ fname, fname);
+
+ if (system(cmd) != 0)
+ fprintf(stderr, "watch cmd: %s failed: %s\n", cmd, pmErrStr(-oserror()));
+}
+
+void
+printindom(FILE *f, __pmInResult *irp)
+{
+ int i;
+
+ for (i = 0; i < irp->numinst; i++) {
+ fprintf(f, "[%3d]", i);
+ if (irp->instlist != NULL)
+ fprintf(f, " inst: %d", irp->instlist[i]);
+ if (irp->namelist != NULL)
+ fprintf(f, " name: \"%s\"", irp->namelist[i]);
+ fputc('\n', f);
+ }
+}
+
+void
+dohelp(int command, int full)
+{
+ if (command < 0) {
+ puts("help [ command ]\n");
+ dohelp(ATTR, HELP_USAGE);
+ dohelp(PMNS_CHILDREN, HELP_USAGE);
+ dohelp(CLOSE, HELP_USAGE);
+ dohelp(DBG, HELP_USAGE);
+ dohelp(DESC, HELP_USAGE);
+ dohelp(FETCH, HELP_USAGE);
+ dohelp(GETDESC, HELP_USAGE);
+ dohelp(INSTANCE, HELP_USAGE);
+ dohelp(PMNS_NAME, HELP_USAGE);
+ dohelp(NAMESPACE, HELP_USAGE);
+ dohelp(OPEN, HELP_USAGE);
+ dohelp(PMNS_PMID, HELP_USAGE);
+ dohelp(PROFILE, HELP_USAGE);
+ dohelp(QUIT, HELP_USAGE);
+ dohelp(STATUS, HELP_USAGE);
+ dohelp(STORE, HELP_USAGE);
+ dohelp(INFO, HELP_USAGE);
+ dohelp(TIMER, HELP_USAGE);
+ dohelp(PMNS_TRAVERSE, HELP_USAGE);
+ dohelp(WAIT, HELP_USAGE);
+ dohelp(WATCH, HELP_USAGE);
+ putchar('\n');
+ }
+ else {
+ if (full == HELP_FULL)
+ putchar('\n');
+
+ switch (command) {
+ case ATTR:
+ puts("attr name [value]");
+ puts("attr attr# [value]");
+ break;
+ case CLOSE:
+ puts("close");
+ break;
+ case DBG:
+ puts("debug all | none");
+ puts("debug flag [ flag ... ] (flag is decimal or symbolic name)");
+ break;
+ case DESC:
+ puts("desc metric");
+ break;
+ case FETCH:
+ puts("fetch metric [ metric ... ]");
+ break;
+ case GETDESC:
+ puts("getdesc on | off");
+ break;
+ case INFO:
+ puts("text metric");
+ puts("text indom indom#");
+ break;
+ case INSTANCE:
+ puts("instance indom# [ number | name | \"name\" ]");
+ break;
+ case NAMESPACE:
+ puts("namespace fname");
+ break;
+ case OPEN:
+ puts("open dso dsoname init_routine [ domain# ]");
+ puts("open pipe execname [ arg ... ]");
+ puts("open socket unix sockname");
+ puts("open socket inet port#|service");
+ puts("open socket ipv6 port#|service");
+ break;
+ case PMNS_CHILDREN:
+ puts("children metric-name");
+ break;
+ case PMNS_NAME:
+ puts("name pmid#");
+ break;
+ case PMNS_PMID:
+ puts("pmid metric-name");
+ break;
+ case PMNS_TRAVERSE:
+ puts("traverse metric-name");
+ break;
+ case PROFILE:
+ puts("profile indom# [ all | none ]");
+ puts("profile indom# [ add | delete ] number");
+ break;
+ case QUIT:
+ puts("quit");
+ break;
+ case STATUS:
+ puts("status");
+ break;
+ case STORE:
+ puts("store metric \"value\"");
+ break;
+ case WATCH:
+ puts("watch logfilename");
+ break;
+ case TIMER:
+ puts("timer on | off");
+ break;
+ case WAIT:
+ puts("wait seconds");
+ break;
+ default:
+ fprintf(stderr, "Help for that command (%d) not supported!\n", command);
+ }
+
+ if (full == HELP_FULL) {
+ putchar('\n');
+ switch (command) {
+ case ATTR:
+ puts(
+"Set a security attribute. These set aspects of per-user authentication,\n"
+"allowing a PMDA to provide different metric views for different users.\n");
+ break;
+ case CLOSE:
+ puts(
+"Close the pipe to a daemon PMDA or dlclose(3) a DSO PMDA. dbpmda does not\n"
+"exit, allowing another PMDA to be opened.\n");
+ break;
+ case DBG:
+ puts(
+"Specify which debugging flags should be active (see pmdbg(1)). Flags may\n"
+"be specified as integers or by name, with multiple flags separated by\n"
+"white space. All flags may be selected or deselected if 'all' or 'none' is\n"
+"specified. The current setting is displayed by the status command.\n\n");
+ break;
+ case DESC:
+ puts(
+"Print out the meta data description for the 'metric'. The metric may be\n"
+"specified by name, or as a PMID of the form N, N.N or N.N.N.\n");
+ break;
+ case FETCH:
+ puts(
+"Fetch metrics from the PMDA. The metrics may be specified as a list of\n"
+"metric names, or PMIDs of the form N, N.N or N.N.N.\n");
+ break;
+ case GETDESC:
+ puts(
+"Before doing a fetch, get the descriptor so that the result of a fetch\n"
+"can be printed out correctly.\n");
+ break;
+ case INFO:
+ puts(
+"Retrieve the help text for the 'metric' or 'indom' from the PMDA. The one\n"
+"line message is shown between '[' and ']' with the long message on the next\n"
+"line. To get the help text for an instance domain requires the word\n"
+"``indom'' before the indom number\n");
+ break;
+ case INSTANCE:
+ puts(
+"List the instances in 'indom'. The list may be restricted to a specific\n"
+"instance 'name' or 'number'.\n");
+ break;
+ case NAMESPACE:
+ puts(
+"Unload the current Name Space and load up the given Name Space.\n"
+"If unsuccessful then will try to reload the previous Name Space.\n");
+ break;
+ case OPEN:
+ puts(
+"Open a PMDA as either a DSO, via a network socket (unix/inet/ipv6), or as a\n"
+"daemon (connected with a pipe). The 'dsoname' and 'execname' fields are\n"
+"the path to the PMDA shared object file or executable. The first socket PMDA\n"
+"field is the type - either unix (if supported), inet or ipv6. The 'sockname'\n"
+"argument for unix sockets is a path of a named pipe where a PMDA is listening\n"
+"for connections. The 'port' argument is a port number, 'serv' a service name\n"
+"typically defined in /etc/services (resolved to a port via getservent(3)).\n"
+"The arguments to this command are similar to a line in the pmcd.conf file.\n");
+ break;
+ case PMNS_CHILDREN:
+ puts(
+"Fetch and print the next name component of the direct decendents of\n"
+"metric-name in the PMNS, reporting for each if it is a leaf node or a\n"
+"non-leaf node.\n"
+"Most useful for PMDAs that support dynamic metrics in the PMNS.\n");
+ break;
+ case PMNS_NAME:
+ puts(
+"Print the name of the metric with PMID pmid#. The pmid# syntax follows\n"
+"the source PMNS syntax, namely 3 numbers separated by '.' to encode\n"
+"the domain, cluster and item components of the PMID, e.g.\n"
+" name 29.0.1004\n"
+"Most useful for PMDAs that support dynamic metrics in the PMNS.\n");
+ break;
+ case PMNS_PMID:
+ puts(
+"Print the PMID for the named metric\n"
+"Most useful for PMDAs that support dynamic metrics in the PMNS.\n");
+ break;
+ case PMNS_TRAVERSE:
+ puts(
+"Fetch and print all of the decendent metric names below metric-name\n"
+"in the PMNS.\n"
+"Most useful for PMDAs that support dynamic metrics in the PMNS.\n");
+ break;
+ case PROFILE:
+ puts(
+"For the instance domain specified, the profile may be changed to include\n"
+"'all' instances, no instances, add an instance or delete an instance.\n");
+ break;
+ case QUIT:
+ puts("Exit dbpmda. This also closes any open PMDAs.\n");
+ break;
+ case STATUS:
+ puts(
+"Display the state of dbpmda, including which PMDA is connected, which\n"
+"pmDebug flags are set, and the current profile.\n");
+ break;
+ case STORE:
+ puts(
+"Store the value (int, real or string) into the 'metric'. The metric may be\n"
+"specified by name or as a PMID with the format N, N.N, N.N.N. The value to\n"
+"be stored must be enclosed in quotes. Unlike the other commands, a store\n"
+"must request a metric description and fetch the metric to determine how to\n"
+"interpret the value, and to allocate the PDU for transmitting the value,\n"
+"respectively. The current profile will be used.\n");
+ break;
+ case TIMER:
+ puts(
+"Report the response time of the PMDA when sending and receiving PDUs.\n");
+ break;
+ case WATCH:
+ puts(
+"A xwsh window is opened which tails the specified log file. This window\n"
+"must be closed by the user when no longer required.\n");
+ break;
+ case WAIT:
+ puts("Sleep for this number of seconds\n");
+ break;
+ }
+ }
+ }
+}
+
+void
+dostatus(void)
+{
+ int i = 0;
+
+ putchar('\n');
+ printf("Namespace: ");
+ if (cmd_namespace != NULL)
+ printf("%s\n", cmd_namespace);
+ else {
+ if (pmnsfile == NULL)
+ printf("(default)\n");
+ else
+ printf("%s\n", pmnsfile);
+ }
+
+ if (myPmdaName == NULL || connmode == NO_CONN)
+ printf("PMDA: none\n");
+ else {
+ printf("PMDA: %s\n", myPmdaName);
+ printf("Connection: ");
+ switch (connmode) {
+ case CONN_DSO:
+ printf("dso\n");
+ printf("DSO Interface Version: %d\n", dispatch.comm.pmda_interface);
+ printf("PMDA PMAPI Version: %d\n", dispatch.comm.pmapi_version);
+ break;
+ case CONN_DAEMON:
+ printf("daemon\n");
+ printf("PMDA PMAPI Version: ");
+ i = __pmVersionIPC(infd);
+ if (i == UNKNOWN_VERSION)
+ printf("unknown!\n");
+ else
+ printf("%d\n", i);
+ break;
+ default:
+ printf("unknown!\n");
+ break;
+ }
+ }
+
+ printf("pmDebug: ");
+ if (pmDebug == (unsigned int) -1)
+ printf("-1 (all)\n");
+ else if (pmDebug == 0)
+ printf("0 (none)\n");
+ else {
+ printf("%u", pmDebug);
+ for (i = 0; i < numFlags; i++)
+ if (pmDebug & (1 << i)) {
+ printf(" ( %s", debugFlags[i]);
+ break;
+ }
+ for (i++; i < numFlags; i++)
+ if (pmDebug & (1 << i))
+ printf(" + %s", debugFlags[i]);
+ printf(" )\n");
+ }
+
+ printf("Timer: ");
+ if (timer == 0)
+ printf("off\n");
+ else
+ printf("on\n");
+
+ printf("Getdesc: ");
+ if (get_desc == 0)
+ printf("off\n");
+ else
+ printf("on\n");
+
+ putchar('\n');
+ __pmDumpProfile(stdout, PM_INDOM_NULL, profile);
+ putchar('\n');
+}
+
+
+/*
+ * Modified version of __pmDumpResult to use a descriptor list
+ * instead of calling pmLookupDesc.
+ * Notes:
+ * - desc_list should not be NULL
+ */
+
+void
+_dbDumpResult(FILE *f, pmResult *resp, pmDesc *desc_list)
+{
+ int i;
+ int j;
+ int n;
+ char *p;
+
+ fprintf(f,"pmResult dump from " PRINTF_P_PFX "%p timestamp: %d.%06d ",
+ resp, (int)resp->timestamp.tv_sec, (int)resp->timestamp.tv_usec);
+ __pmPrintStamp(f, &resp->timestamp);
+ fprintf(f, " numpmid: %d\n", resp->numpmid);
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+ n = pmNameID(vsp->pmid, &p);
+ if (n < 0)
+ fprintf(f," %s (%s):", pmIDStr(vsp->pmid), "<noname>");
+ else {
+ fprintf(f," %s (%s):", pmIDStr(vsp->pmid), p);
+ free(p);
+ }
+ if (vsp->numval == 0) {
+ fprintf(f, " No values returned!\n");
+ continue;
+ }
+ else if (vsp->numval < 0) {
+ fprintf(f, " %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+ fprintf(f, " numval: %d", vsp->numval);
+ fprintf(f, " valfmt: %d vlist[]:\n", vsp->valfmt);
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (vsp->numval > 1 || desc_list[i].indom != PM_INDOM_NULL) {
+ fprintf(f," inst [%d", vp->inst);
+ fprintf(f, " or ???]");
+ fputc(' ', f);
+ }
+ else
+ fprintf(f, " ");
+ fprintf(f, "value ");
+ pmPrintValue(f, vsp->valfmt, desc_list[i].type, vp, 1);
+ fputc('\n', f);
+ }/*for*/
+ }/*for*/
+}/*_dbDumpResult*/
diff --git a/src/genpmda/GNUmakefile b/src/genpmda/GNUmakefile
new file mode 100644
index 0000000..7526a7c
--- /dev/null
+++ b/src/genpmda/GNUmakefile
@@ -0,0 +1,34 @@
+#!gmake
+#
+# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGETS = genpmda
+LSRCFILES = genpmda
+
+default: $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 genpmda $(PCP_BIN_DIR)/genpmda
+
+default_pcp: default
+
+install_pcp: install
+
+include $(BUILDRULES)
diff --git a/src/genpmda/genpmda b/src/genpmda/genpmda
new file mode 100755
index 0000000..4650315
--- /dev/null
+++ b/src/genpmda/genpmda
@@ -0,0 +1,1026 @@
+#! /bin/sh
+#
+# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+prog=`basename $0`
+
+usage()
+{
+ cat <<EOFEOF
+Usage: $prog [-d] [-s stdpmid] [-D domain] [-t topdir] [-n pmns] [-o dir] [-v] -i IAM -c config
+Required options:
+ -c config required: input config file, see example below
+ -i IAM required: pmda name "IAM", must appear in stdpmid
+ or -D domain number may be given.
+
+Other options:
+ -D domain domain number to use (if -s is not given)
+ -d generate an Install script for a daemon PMDA (default is DSO)
+ -t topdir use "topdir" in generated GNUmakefile (default "../../..")
+ -n pmns use "pmns" as root of pmns (default matches -i flag)
+ -s stdpmid path to stdpmid (default "../../pmns/stdpmid")
+ -o dir directory for generated source (default "./generated")
+ -v verbose
+
+Example config file (for the required -c option):
+Notes:
+ EXAMPLE must appear in src/pmns/stdpmid
+ Generate the "example" pmda: genpmda -D 99 -v -i EXAMPLE -c example.conf
+
+example {
+ metric
+}
+
+example.metric {
+ ## metric string
+ ## pmid EXAMPLE:SOME_CLUSTER_NAME:0
+ ## indom PM_INDOM_NULL
+ ## type PM_TYPE_STRING
+ ## units PMDA_PMUNITS(0,0,0,0,0,0)
+ ## semantics PM_SEM_DISCRETE
+ ## briefhelptext one line help text for example.metric.string
+ ## helptext long help text for example.metric.string
+ ## helptext This is the second line of the long help text
+ ## helptext and this is the third line.
+ ## fetch function example_string_fetch_callback
+ ## code /* optional code for this metric */
+ ## code atom->cp = "hello world";
+ ## endmetric
+}
+EOFEOF
+
+ exit 1
+}
+
+dflag=false
+Dflag=""
+iflag=""
+sflag="../../pmns/stdpmid"
+tflag="../../.."
+oflag="generated"
+nflag=""
+verbose=false
+
+while getopts "c:vdD:s:t:i:n:o:" c
+do
+ case $c
+ in
+
+ D) Dflag="$OPTARG"
+ ;;
+
+ d) dflag=true
+ ;;
+
+ c) cflag="$OPTARG"
+ ;;
+
+ i) iflag="$OPTARG"
+ ;;
+
+ s) sflag="$OPTARG"
+ ;;
+
+ t) tflag="$OPTARG"
+ ;;
+
+ n) nflag="$OPTARG"
+ ;;
+
+ o) oflag="$OPTARG"
+ ;;
+
+ v) verbose=true
+ ;;
+
+ \?) usage
+ ;;
+ esac
+done
+
+[ -z "$iflag" ] && usage
+[ ! -f "$cflag" ] && echo "Error: config \"$cflag\" not found" && usage
+
+IAM=`echo $iflag | tr a-z A-Z`
+iam=`echo $IAM | tr A-Z a-z`
+[ -z "$nflag" ] && nflag="$iam"
+config="$cflag"
+
+for stdpmid in $sflag $tflag/src/pmns/stdpmid ../pmns/stdpmid ../../pmns/stdpmid
+do
+ [ -f "$stdpmid" ] && break
+done
+$verbose && [ -f "$stdpmid" ] && echo Found stdpmid in \"$stdpmid\"
+[ ! -f "$stdpmid" ] && echo Error: could not find \"stdpmid\" && usage
+domain=`awk '/^#define[ \t]*'$IAM'/ {print $3}' $stdpmid`
+if [ -z "$Dflag" ]
+then
+ [ -z "$domain" ] && echo "Error: domain for \"$IAM\" not found in ../../pmns/stdpmid, please use -D" && usage
+else
+ domain="$Dflag"
+fi
+
+[ ! -d $oflag ] && mkdir $oflag && $verbose && echo "created output directory \"$oflag\""
+
+#
+# Generate domain.h
+#
+cat <<EOFEOF >$oflag/domain.h
+/*
+ * Generated code, do not edit!
+ */
+#define $IAM $domain
+EOFEOF
+$verbose && echo Wrote $oflag/domain.h
+
+#
+# Generate (the beginning of) pmda.c
+#
+cat <<EOFEOF >$oflag/pmda.c
+/*
+ * Generated code, do not edit!
+ */
+#include <stdio.h>
+#include <limits.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "metrictab.h"
+#include "clusters.h"
+
+static int _isDSO = 1; /* =0 I am a daemon */
+
+EOFEOF
+$verbose && echo Wrote $oflag/pmda.c
+
+gnumakefile=GNUmakefile.new
+gnumakefile_g=GNUmakefile.generic.new
+install_g=install-generic.new
+
+#
+# Generate Install, Remove and Makefile.install
+#
+cat <<EOFEOF >$oflag/Install
+#! /bin/sh
+. \$PCP_DIR/etc/pcp.env
+. \$PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=$iam
+pmda_interface=3
+EOFEOF
+
+if $dflag
+then
+ cat <<EOFEOF >>$oflag/Install
+# controls for daemon PMDA installation procedures
+#
+daemon_opt=true
+dso_opt=false
+pipe_opt=true
+socket_opt=false
+
+EOFEOF
+else
+ cat <<EOFEOF >>$oflag/Install
+# controls for DSO installation procedures
+#
+daemon_opt=false
+dso_opt=true
+pipe_opt=false
+socket_opt=false
+
+EOFEOF
+fi
+
+cat <<EOFEOF >>$oflag/Install
+#
+# override "choose mode"
+__choose_mode()
+{
+ do_pmda=true
+}
+
+#
+# override "choose IPC method"
+__choose_ipc()
+{
+ ipc_type=pipe
+ type="pipe binary \$PCP_PMDAS_DIR/\$iam/pmda\$iam"
+}
+
+# Do it
+#
+pmdaSetup
+cat domain.h clusters.h pmns | \$PCP_BINADM_DIR/pmgcc -DPCP_PMNS >\$tmp/pmns
+pmns_source=\$tmp/pmns
+pmns_name=\$iam
+
+pmdaInstall
+
+exit 0
+EOFEOF
+$verbose && echo Wrote $oflag/Install
+
+cat <<EOFEOF >$oflag/Remove
+#! /bin/sh
+. \$PCP_DIR/etc/pcp.env
+. \$PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=$iam
+pmdaSetup
+pmdaRemove
+
+exit 0
+EOFEOF
+$verbose && echo Wrote $oflag/Remove
+
+cat <<"EOFEOF" >$oflag/Makefile.install
+SHELL = sh
+TARGETS =
+LDIRT = *.log *.pag *.dir
+
+default: $(TARGETS)
+
+install: default
+
+clobber:
+ rm -f $(LDIRT) $(TARGETS)
+EOFEOF
+$verbose && echo Wrote $oflag/Makefile.install
+
+#
+# Generate metrictab.[ch]
+#
+awk '
+BEGIN {
+ mt_h="'$oflag'/metrictab.h"
+ mt_c="'$oflag'/metrictab.c"
+ pmda_c="'$oflag'/pmda.c"
+ clusters_h="'$oflag'/clusters.h"
+ pmns="'$oflag'/pmns"
+ cfiles="CFILES"
+ init_c="init.c.new"
+
+ printf "/* Generated code, do not edit! */\n\n" >mt_c
+ printf "/* Generated code, do not edit! */\n\n" >mt_h
+ printf "#include \"pmapi.h\"\n" >>mt_c
+ printf "#include \"impl.h\"\n" >>mt_c
+ printf "#include \"pmda.h\"\n" >>mt_c
+ printf "#include \"metrictab.h\"\n\n" >>mt_c
+ printf "#include \"clusters.h\"\n\n" >>mt_c
+ printf "/*\n * Metric Table\n */\npmdaMetric metrictab[] = {\n" >>mt_c
+
+ printf "init.c" >cfiles
+ printf "/*\n" >init_c
+ printf " * local initialization for the %s PMDA\n", "'$IAM'" >>init_c
+ printf " * ADD CODE HERE\n" >>init_c
+ printf " */\n" >>init_c
+
+ printf "#include <stdio.h>\n" >>init_c
+ printf "#include <limits.h>\n" >>init_c
+ printf "#include <ctype.h>\n" >>init_c
+ printf "#include <sys/types.h>\n" >>init_c
+ printf "#include <sys/stat.h>\n" >>init_c
+ printf "#include <fcntl.h>\n" >>init_c
+ printf "#include \"pmapi.h\"\n" >>init_c
+ printf "#include \"impl.h\"\n" >>init_c
+ printf "#include \"pmda.h\"\n" >>init_c
+ printf "#include \"'$oflag'/domain.h\"\n" >>init_c
+ printf "#include \"'$oflag'/metrictab.h\"\n" >>init_c
+ printf "#include \"'$oflag'/clusters.h\"\n" >>init_c
+ printf "\n" >>init_c
+ printf "void\n%s_local_init(pmdaInterface *dispatch)\n", "'$iam'" >>init_c
+ printf "{\n" >>init_c
+ printf "\t/* add code for local initialization here, if required */\n" >>init_c
+ printf "}\n" >>init_c
+}
+
+/.*{$/ {
+ nonleaf = $1
+}
+
+$1 == "##" && $2 == "metric" {
+ metric = nonleaf"."$3
+ metriclist[metric] = metric
+ leafmetric[metric] = $3
+ nmetrics++
+}
+
+$1 == "##" && $2 == "pmid" {
+ pmid[metric] = $3
+ n = split(pmid[metric], id, ":")
+ current_cluster = id[2]
+ cluster[metric] = current_cluster
+ item[metric] = id[3]
+ s = metric
+ gsub("\\.", "_", s)
+ m = "METRIC_"s
+ if (seen_metric_macro[m]) {
+ printf "FATAL ERROR: cluster_metric \"%s\" is not unique,\n", m
+ printf "Conflicts with metric \"%s\"\n", seen_metric_macro[m]
+ exit 1
+ }
+ seen_metric_macro[m] = metric
+ metric_macro[metric] = m
+}
+
+$1 == "##" && $2 == "code" {
+ thiscode=$3
+ for (i=4; i <= NF; i++)
+ thiscode = thiscode " " $i
+ if (!code[metric])
+ code[metric] = "\t\t"thiscode;
+ else
+ code[metric] = code[metric]"\n\t\t"thiscode
+}
+
+$1 == "##" && $2 == "type" {
+ type[metric] = $3
+}
+
+$1 == "##" && $2 == "units" {
+ units[metric] = $3
+}
+
+$1 == "##" && $2 == "semantics" {
+ semantics[metric] = $3
+}
+
+$1 == "##" && $2 == "indom" {
+ indom[metric] = $3
+ if (indom[metric] != "PM_INDOM_NULL" && !seen_indom[indom[metric]]) {
+ printf "#define %s %d\n", indom[metric], nindoms >>mt_h
+ seen_indom[indom[metric]]++
+ nindoms++
+ indoms[nindoms] = indom[metric]
+ indom_cluster[$3] = current_cluster
+ }
+}
+
+$1 == "##" && $2 == "endmetric" {
+ printf "\t/* %s */\n", metric >>mt_c
+ printf "\t{ (void *)fetch_%s,\n", cluster[metric] >>mt_c
+ printf "\t{PMDA_PMID(%s,%s),\n", cluster[metric], metric_macro[metric] >>mt_c
+ printf "\t %s, %s, %s, %s }, },\n\n",
+ type[metric], indom[metric], semantics[metric], units[metric] >>mt_c
+}
+
+END {
+ printf "};\n" >>mt_c
+
+ if (nindoms > 0) {
+ printf "\npmdaIndom indomtab[] = {\n" >>mt_c
+ for (i=1; i <= nindoms; i++)
+ printf " { %s, 0, NULL },\n", indoms[i] >>mt_c
+ printf "};\n" >>mt_c
+ }
+
+ printf "\n#define NMETRICS %d\n", nmetrics >>mt_h
+ printf "extern pmdaMetric metrictab[NMETRICS];\n" >>mt_h
+
+ printf "\n#define NINDOMS %d\n", nindoms >>mt_h
+ if (nindoms > 0) {
+ printf "extern pmdaIndom indomtab[NINDOMS];\n" >>mt_h
+ }
+
+ #
+ # Generate clusters.h and first part of pmns
+ #
+ printf "/* Generated code, do not edit! */\n\n" >pmns
+ printf "/* Generated code, do not edit! */\n\n" >clusters_h
+ printf "#ifndef _CLUSTER_H\n" >>clusters_h
+ printf "#define _CLUSTER_H\n" >>clusters_h
+ clustercnt = 0
+ for (m in cluster) {
+ c = cluster[m]
+ if (seencluster[c])
+ continue;
+ seencluster[c] = 1
+ printf "\n/*\n" >>clusters_h
+ printf " * Metrics in the \"%s\" cluster\n", c >>clusters_h
+ printf " */\n" >>clusters_h
+ printf "#define %s\t\t\t%d\n", c, clustercnt >>clusters_h
+ for (metric in metriclist) {
+ if (cluster[metric] == c) {
+ printf "#define %s\t%s /* %s */\n", metric_macro[metric], item[metric], metric >>clusters_h
+ }
+ }
+
+ clustercnt++
+ }
+ printf "\n\n#ifndef PCP_PMNS\n" >>clusters_h
+ printf "#define NCLUSTERS\t\t\t%d\n\n", clustercnt >>clusters_h
+
+ #
+ # Generate the refresh method in pmda.c
+ #
+ for (m in cluster) {
+ c = cluster[m]
+ seencluster[c] = 0;
+ }
+ printf "\nstatic void\n%s_refresh(int *need_refresh)\n{\n", "'$iam'" >>pmda_c
+ for (m in cluster) {
+ c = cluster[m]
+ if (seencluster[c])
+ continue;
+ seencluster[c] = 1
+ printf "extern void refresh_%s(void);\n", c >>mt_h
+ printf " if (need_refresh[%s])\n\trefresh_%s();\n", c, c >>pmda_c
+ }
+ printf "}\n\n" >>pmda_c
+
+ #
+ # Generate the instance method in pmda.c
+ #
+ for (m in cluster) {
+ c = cluster[m]
+ seencluster[c] = 0;
+ }
+ printf "static int\n" >>pmda_c
+ printf "%s_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)\n", "'$iam'" >>pmda_c
+ printf "{\n" >>pmda_c
+ printf " __pmInDom_int *indomp = (__pmInDom_int *)&indom;\n" >>pmda_c
+ printf " int need_refresh[NCLUSTERS];\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " memset(need_refresh, 1, sizeof(need_refresh));\n" >>pmda_c
+ printf " /* TODO: only refresh some clusters */\n" >>pmda_c
+ printf " %s_refresh(need_refresh);\n", "'$iam'" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " return pmdaInstance(indom, inst, name, result, pmda);\n" >>pmda_c
+ printf "}\n\n" >>pmda_c
+
+ #
+ # Generate the fetch method in pmda.c
+ #
+ printf "int\n" >>pmda_c
+ printf "%s_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)\n", "'$iam'" >>pmda_c
+ printf "{\n" >>pmda_c
+ printf " int i;\n" >>pmda_c
+ printf " int need_refresh[NCLUSTERS];\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " memset(need_refresh, 0, sizeof(need_refresh));\n" >>pmda_c
+ printf " for (i=0; i < numpmid; i++) {\n" >>pmda_c
+ printf " __pmID_int *idp = (__pmID_int *)&(pmidlist[i]);\n" >>pmda_c
+ printf " if (idp->cluster >= 0 && idp->cluster < NCLUSTERS) {\n" >>pmda_c
+ printf " need_refresh[idp->cluster]++;\n" >>pmda_c
+ printf " }\n" >>pmda_c
+ printf " }\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " %s_refresh(need_refresh);\n", "'$iam'" >>pmda_c
+ printf " return pmdaFetch(numpmid, pmidlist, resp, pmda);\n" >>pmda_c
+ printf "}\n\n" >>pmda_c
+
+ #
+ # Generate the generic fetch callback in pmda.c
+ #
+ printf "static int\n" >>pmda_c
+ printf "%s_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)\n", "'$iam'" >>pmda_c
+ printf "{\n" >>pmda_c
+
+ printf "\tint (*fetchfunction)(pmdaMetric *, unsigned int, pmAtomValue *) =\n">>pmda_c
+ printf "\t\t(int (*)(pmdaMetric *, unsigned int, pmAtomValue *)) mdesc->m_user;\n">>pmda_c
+ printf "\treturn (*fetchfunction)(mdesc, inst, atom);\n" >>pmda_c
+ printf "}\n\n", pmda_c >>pmda_c
+
+ #
+ # Generate the init method in pmda.c
+ #
+ printf "void\n" >>pmda_c
+ printf "%s_init(pmdaInterface *dp)\n", "'$iam'" >>pmda_c
+ printf "{\n" >>pmda_c
+ printf " int\tneed_refresh[NCLUSTERS];\n" >>pmda_c
+ printf " extern void\t%s_local_init(pmdaInterface *);\n", "'$iam'" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " if (_isDSO) {\n" >>pmda_c
+ printf " char helppath[MAXPATHLEN];\n" >>pmda_c
+ printf " snprintf(helppath, sizeof(helppath), \"%%s/pmdas/'$iam'/help\",\n" >>pmda_c
+ printf " pmGetConfig(\"PCP_VAR_DIR\"));\n" >>pmda_c
+ printf " pmdaDSO(dp, PMDA_INTERFACE_4, \"%s DSO\", helppath);\n", "'$iam'" >>pmda_c
+ printf " }\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " if (dp->status != 0)\n" >>pmda_c
+ printf " return;\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " dp->version.two.instance = %s_instance;\n", "'$iam'" >>pmda_c
+ printf " dp->version.two.fetch = %s_fetch;\n", "'$iam'" >>pmda_c
+ printf " pmdaSetFetchCallBack(dp, %s_fetchCallBack);\n", "'$iam'" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " /* local initialization, see init.c */\n" >>pmda_c
+ printf " %s_local_init(dp);\n", "'$iam'" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " /* initially refresh all clusters */\n" >>pmda_c
+ printf " memset(need_refresh, 1, sizeof(need_refresh));\n" >>pmda_c
+ printf " %s_refresh(need_refresh);\n", "'$iam'" >>pmda_c
+ printf "\n" >>pmda_c
+ if (nindoms > 0) {
+ printf " pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,\n" >>pmda_c
+ printf " sizeof(metrictab)/sizeof(metrictab[0]));\n" >>pmda_c
+ }
+ else {
+ printf " pmdaInit(dp, NULL, 0, metrictab,\n" >>pmda_c
+ printf " sizeof(metrictab)/sizeof(metrictab[0]));\n" >>pmda_c
+ }
+ printf "}\n" >>pmda_c
+
+ #
+ # Generate usage() and main() for the daemon PMDA
+ #
+ printf "\nstatic void\n" >>pmda_c
+ printf "usage(void)\n" >>pmda_c
+ printf "{\n" >>pmda_c
+ printf " fprintf(stderr, \"Usage: %%s [options]\\n\\n\", pmProgname);\n" >>pmda_c
+ printf " fputs(\"Options:\\n\"\n" >>pmda_c
+ printf " \" -d domain use domain (numeric) for metrics domain of PMDA\\n\"\n" >>pmda_c
+ printf " \" -l logfile write log into logfile rather than using default log name\\n\",\n" >>pmda_c
+ printf " stderr);\n" >>pmda_c
+ printf " exit(1);\n" >>pmda_c
+ printf "}\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf "int\n" >>pmda_c
+ printf "main(int argc, char **argv)\n" >>pmda_c
+ printf "{\n" >>pmda_c
+ printf " int err = 0;\n" >>pmda_c
+ printf " int c = 0;\n" >>pmda_c
+ printf " pmdaInterface dispatch;\n" >>pmda_c
+ printf " char helppath[MAXPATHLEN];\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " __pmSetProgname(argv[0]);\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " _isDSO = 0;\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " snprintf(helppath, sizeof(helppath), \"%%s/pmdas/'$iam'/help\", pmGetConfig(\"PCP_VAR_DIR\"));\n" >>pmda_c
+ printf " pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, '$IAM', \"'$iam'.log\", helppath);\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " if ((c = pmdaGetOpt(argc, argv, \"D:d:l:?\", &dispatch, &err)) != EOF)\n" >>pmda_c
+ printf " err++;\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " if (err)\n" >>pmda_c
+ printf " usage();\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " pmdaOpenLog(&dispatch);\n" >>pmda_c
+ printf " '$iam'_init(&dispatch);\n" >>pmda_c
+ printf " pmdaConnect(&dispatch);\n" >>pmda_c
+ printf " pmdaMain(&dispatch);\n" >>pmda_c
+ printf "\n" >>pmda_c
+ printf " exit(0);\n" >>pmda_c
+ printf "}\n" >>pmda_c
+
+ #
+ # Generate the refresh and fetch methods for each cluster
+ #
+ for (m in cluster) {
+ c = cluster[m]
+ seencluster[c] = 0;
+ }
+ for (m in cluster) {
+ c = cluster[m]
+ if (seencluster[c])
+ continue;
+ seencluster[c] = 1
+
+ fetch_c = c".c.new"
+ printf " %s.c", c >>cfiles
+
+ printf "/*\n" >>fetch_c
+ printf " * Originally generated from \"%s\" using genpmda(1).\n","'$config'" >>fetch_c
+ printf " *\n" >>fetch_c
+ printf " * Refresh and fetch methods for the \"%s\" cluster.\n", c >>fetch_c
+ printf " */\n" >>fetch_c
+ printf "#include <stdio.h>\n" >>fetch_c
+ printf "#include <limits.h>\n" >>fetch_c
+ printf "#include <ctype.h>\n" >>fetch_c
+ printf "#include <sys/types.h>\n" >>fetch_c
+ printf "#include <sys/stat.h>\n" >>fetch_c
+ printf "#include <fcntl.h>\n" >>fetch_c
+ printf "#include \"pmapi.h\"\n" >>fetch_c
+ printf "#include \"impl.h\"\n" >>fetch_c
+ printf "#include \"pmda.h\"\n" >>fetch_c
+ printf "#include \"'$oflag'/domain.h\"\n" >>fetch_c
+ printf "#include \"'$oflag'/metrictab.h\"\n" >>fetch_c
+ printf "#include \"'$oflag'/clusters.h\"\n" >>fetch_c
+
+ printf "\nvoid\nrefresh_%s(void)\n", c >>fetch_c
+ printf "{\n" >>fetch_c
+
+ z = 0
+ if (nindoms > 0) {
+ for (i in indoms) {
+ this = indoms[i];
+ if (indom_cluster[this] == c) {
+ printf "\tpmInDom indom_%s = indomtab[%s].it_indom;\n", this, this >>fetch_c
+ z++
+ }
+ }
+ if (z > 0)
+ printf "\tstatic int first = 1;\n" >>fetch_c
+ }
+ printf "\n" >>fetch_c
+ if (z > 0) {
+ printf "\t/* initialize the instance domain cache(s) */\n" >>fetch_c
+ printf "\tif (first) {\n" >>fetch_c
+ for (i in indoms) {
+ this = indoms[i];
+ if (indom_cluster[this] == c) {
+ printf "\t\tpmdaCacheOp(indom_%s, PMDA_CACHE_LOAD);\n", this >>fetch_c
+ }
+ }
+ printf "\t\tfirst = 0;\n" >>fetch_c
+ printf "\t}\n" >>fetch_c
+ }
+
+ printf "\n" >>fetch_c
+ if (z > 0) {
+ for (i in indoms) {
+ this = indoms[i];
+ if (indom_cluster[this] == c) {
+ printf "\t/* inactivate all instances in the %s instance domain */\n", this >>fetch_c
+ printf "\tpmdaCacheOp(indom_%s, PMDA_CACHE_INACTIVE);\n", this >>fetch_c
+ printf "\n" >>fetch_c
+ printf "\t/*\n" >>fetch_c
+ printf "\t * Add code here to refresh the %s instance domain.\n", this >>fetch_c
+ printf "\t * Basically, walk your data and activate each instance, e.g.\n" >>fetch_c
+ printf "\t * inst = pmdaCacheStore(indom_%s, PMDA_CACHE_ADD, name, p);\n", this >>fetch_c
+ printf "\t * where \"name\" is a char buffer naming each instance\n" >>fetch_c
+ printf "\t * and \"p\" points to private anonymous data.\n" >>fetch_c
+ printf "\t */\n" >>fetch_c
+ printf "\t/* ADD CODE HERE */\n\n\n" >>fetch_c
+
+ printf "\n" >>fetch_c
+ printf "\t/*\n" >>fetch_c
+ printf "\t * Flush the cache for the indom_%s indom. This is\n", this >>fetch_c
+ printf "\t * only strictly needed if there are any _new_ instances.\n" >>fetch_c
+ printf "\t */\n", this >>fetch_c
+ printf "\tpmdaCacheOp(indom_%s, PMDA_CACHE_SAVE);\n", this >>fetch_c
+ }
+ }
+ }
+ else {
+ printf "\t/*\n" >>fetch_c
+ printf "\t * Add code here to refresh your data for this cluster\n" >>fetch_c
+ printf "\t * (no instance domains are defined for this cluster)\n" >>fetch_c
+ printf "\t */\n\n" >>fetch_c
+ printf "\t/* ADD CODE HERE */\n\n\n" >>fetch_c
+ }
+
+ printf "}\n" >>fetch_c
+
+ #
+ # Generate the skeletal fetch callback function for each cluster
+ # and the commented skeletal fetch code for each metric.
+ #
+ printf "extern int fetch_%s(pmdaMetric *, unsigned int, pmAtomValue *);\n", c >>clusters_h
+ printf "\nint\nfetch_%s(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)\n", c >>fetch_c
+ printf "{\n" >>fetch_c
+ printf "\t__pmID_int\t*idp = (__pmID_int *)&(mdesc->m_desc.pmid);\n" >>fetch_c
+ printf "\tvoid\t\t*p = NULL;\n" >>fetch_c
+ printf "\tint\t\tsts = 1; /* return value, success is the default */\n" >>fetch_c
+
+ printf "\n\tif (inst != PM_INDOM_NULL) {\n" >>fetch_c
+ printf "\t\tif (pmdaCacheLookup(mdesc->m_desc.indom, inst, NULL, (void **)&p) != PMDA_CACHE_ACTIVE || !p)\n" >>fetch_c
+ printf "\t\t\treturn PM_ERR_INST;\n" >>fetch_c
+ printf "\t}\n\n" >>fetch_c
+ printf "\t/*\n" >>fetch_c
+ printf "\t * p now points to the private data for this instance that was\n" >>fetch_c
+ printf "\t * previously stored in refresh_%s(), or will be NULL\n", c >>fetch_c
+ printf "\t * if this is a singular instance domain.\n" >>fetch_c
+ printf "\t */\n" >>fetch_c
+
+ printf "\n\tswitch(idp->item) {\n" >>fetch_c
+ for (metric in metriclist) {
+ if (cluster[metric] != c)
+ continue
+ printf "\tcase %s:\n", metric_macro[metric] >>fetch_c
+ printf "\t\t/*\n" >>fetch_c
+ printf "\t\t * Fetch code for metric \"" metric "\"\n" >>fetch_c
+ printf "\t\t * PMID : " pmid[metric] "\n" >>fetch_c
+ printf "\t\t * Type : " type[metric] "\n" >>fetch_c
+ printf "\t\t * Indom : " indom[metric] "\n" >>fetch_c
+ printf "\t\t * Units : " units[metric] "\n" >>fetch_c
+ printf "\t\t * Semantics: " semantics[metric] "\n" >>fetch_c
+ printf "\t\t */\n" >>fetch_c
+ if (code[metric])
+ printf "%s\n", code[metric] >>fetch_c
+ else if (type[metric] == "PM_TYPE_32")
+ printf "\t\tatom->l = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_U32")
+ printf "\t\tatom->ul = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_64")
+ printf "\t\tatom->ll = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_U64")
+ printf "\t\tatom->ull = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_FLOAT")
+ printf "\t\tatom->f = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_DOUBLE")
+ printf "\t\tatom->d = 0; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_STRING")
+ printf "\t\tatom->cp = \"string value\"; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_AGGREGATE")
+ printf "\t\tatom->vp = NULL; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_AGGREGATE_STATIC")
+ printf "\t\tatom->vp = NULL; /* ADD CODE HERE */\n" >>fetch_c
+ else if (type[metric] == "PM_TYPE_EVENT")
+ printf "\t\tatom->vp = NULL; /* ADD CODE HERE */\n" >>fetch_c
+
+ printf "\t\tbreak;\n\n" >>fetch_c
+ }
+ printf "\tdefault:\n" >>fetch_c
+ printf "\t\tsts = PM_ERR_PMID;\n" >>fetch_c
+ printf "\t\tbreak;\n" >>fetch_c
+ printf "\t}\n", c >>fetch_c
+ printf "\n", c >>fetch_c
+ printf "\t/*\n", c >>fetch_c
+ printf "\t * Return value:\n", c >>fetch_c
+ printf "\t * < 0 error\n", c >>fetch_c
+ printf "\t * = 0 no values available\n", c >>fetch_c
+ printf "\t * > 0 success\n", c >>fetch_c
+ printf "\t */\n", c >>fetch_c
+ printf "\treturn sts;\n" >>fetch_c
+ printf "}\n" >>fetch_c
+ }
+
+ printf "#endif /* PCP_PMNS */\n" >>clusters_h
+ printf "\n#endif /* _CLUSTER_H */\n" >>clusters_h
+ printf "\n" >>cfiles
+
+}' $config
+$verbose && echo Wrote $oflag/clusters.h
+$verbose && echo Wrote $oflag/metrictab.h
+$verbose && echo Wrote $oflag/metrictab.c
+
+#
+# Generate remainder of pmns
+#
+awk '
+$1 == "##" {
+ if ($2 == "metric")
+ metric = $3
+ else if ($2 == "pmid")
+ printf "\t%s\t\t%s\n", metric, $3
+ next
+}
+{
+ print
+}' <$config >>$oflag/pmns
+$verbose && echo Wrote $oflag/pmns
+
+#
+# Generate dummy root pmns
+#
+cat <<EOFEOF >$oflag/root
+/*
+ * Generated code, do not edit!
+ */
+#define PCP_PMNS
+#include "domain.h"
+#include "clusters.h"
+
+root { $nflag }
+
+#include "pmns"
+EOFEOF
+$verbose && echo Wrote $oflag/root
+
+#
+# Generate help text
+#
+awk '
+/.*{$/ {
+ nonleaf = $1
+}
+$1 == "##" {
+ if ($2 == "metric")
+ metric = $3
+ else if ($2 == "briefhelptext") {
+ printf "@ %s.%s", nonleaf, metric
+ for (i=3; i <= NF; i++)
+ printf " %s", $i
+ printf "\n"
+ }
+ else if ($2 == "helptext") {
+ for (i=3; i <= NF; i++)
+ if (i == 3)
+ printf "%s", $i
+ else
+ printf " %s", $i
+ printf "\n"
+ }
+}' <$config >$oflag/help
+$verbose && echo Wrote $oflag/help
+
+#
+# Generate generic install script
+#
+cat << EOFEOF >$install_g
+#! /bin/sh
+
+usage()
+{
+ echo "Usage: \$0 [-m mode] [-d dir] [-f srcfile -t destfile]"
+ echo "Required options: both -f and -t, or just -d"
+ exit 1
+}
+
+mdflag="755"
+mflag="644"
+
+while getopts "m:d:f:t:v" c
+do
+ case \$c
+ in
+
+ m) mflag="\$OPTARG"
+ mdflag=\$mflag
+ ;;
+
+ d) dflag="\$OPTARG"
+ ;;
+
+ f) fflag="\$OPTARG"
+ ;;
+
+ t) tflag="\$OPTARG"
+ ;;
+
+ \?) usage
+ ;;
+ esac
+done
+
+[ -z "\$fflag" -a -z "\$dflag" ] && usage
+[ -z "\$fflag" -a ! -z "\$tflag" ] && usage
+[ ! -z "\$fflag" -a -z "\$tflag" ] && usage
+[ ! -z "\$dflag" ] && mkdir \$dflag && chmod \$mdflag \$dflag
+[ ! -z "\$fflag" ] && cp \$fflag \$tflag && chmod \$mflag \$tflag
+
+exit 0
+EOFEOF
+chmod 755 $install_g
+
+#
+# Generate GNUmakefile
+#
+cat << EOFEOF >$gnumakefile
+TOPDIR = $tflag
+include \$(TOPDIR)/src/include/builddefs
+
+IAM = $iam
+CMDTARGET= pmda\$(IAM)
+LIBTARGET= pmda_\$(IAM).so
+TARGETS = \$(CMDTARGET) \$(LIBTARGET)
+
+CFILES = `cat CFILES`
+HFILES =
+
+GCFILES = $oflag/metrictab.c $oflag/pmda.c
+GHFILES = $oflag/domain.h $oflag/metrictab.h
+GSRCFILES = $oflag/help $oflag/root $oflag/pmns \
+ $oflag/Makefile.install $oflag/Install \
+ $oflag/Remove $oflag/clusters.h
+
+OBJECTS += \$(GCFILES:.c=.o)
+LCFLAGS =
+LLDLIBS = \$(PCP_PMDALIB)
+
+PMDADIR = \$(PCP_PMDAS_DIR)/\$(IAM)
+LDIRT = generated *.o \$(TARGETS) *.log *.dir *.pag pcp
+
+default: pcp help.pag help.dir \$(TARGETS)
+
+include \$(BUILDRULES)
+
+install: default
+ \$(INSTALL) -m 755 -d \$(PCP_VAR_DIR)
+ \$(INSTALL) -m 755 -d \$(PCP_VAR_DIR)/pmdas
+ \$(INSTALL) -m 755 -d \$(PMDADIR)
+ \$(INSTALL) -m 755 \$(CMDTARGET) \$(PMDADIR)/\$(CMDTARGET)
+ \$(INSTALL) -m 755 \$(LIBTARGET) \$(PMDADIR)/\$(LIBTARGET)
+ \$(INSTALL) -m 755 $oflag/Install \$(PMDADIR)/Install
+ \$(INSTALL) -m 755 $oflag/Remove \$(PMDADIR)/Remove
+ \$(INSTALL) -m 644 $oflag/Makefile.install \$(PMDADIR)/Makefile
+ \$(INSTALL) -m 644 $oflag/pmns \$(PMDADIR)/pmns
+ \$(INSTALL) -m 644 $oflag/root \$(PMDADIR)/root
+ \$(INSTALL) -m 644 $oflag/domain.h \$(PMDADIR)/domain.h
+ \$(INSTALL) -m 644 $oflag/clusters.h \$(PMDADIR)/clusters.h
+ \$(INSTALL) -m 644 $oflag/help \$(PMDADIR)/help
+ \$(INSTALL) -m 644 help.pag \$(PMDADIR)/help.pag
+ \$(INSTALL) -m 644 help.dir \$(PMDADIR)/help.dir
+
+pcp:
+ ln -s \$(TOPDIR)/src/include pcp
+
+help.dir help.pag : $oflag/help
+ \$(RUN_IN_BUILD_ENV) \$(TOPDIR)/src/newhelp/newhelp -n $oflag/root -v 2 -o help < $oflag/help
+
+\$(GCFILES) \$(GHFILES) \$(GSRCFILES) : $config
+ \$(GENPMDA) -v -i $IAM -c $config
+
+default_pro: default
+
+install_pro: install
+EOFEOF
+
+#
+# Generate GNUmakefile.generic
+#
+cat << EOFEOF >$gnumakefile_g
+IAM = $iam
+CONFIG = $config
+DOMAIN = $domain
+
+include /etc/pcp.conf
+INSTALL ?= ./install-generic
+GENPMDA ?= /usr/bin/genpmda
+CMDTARGET= pmda\$(IAM)
+LIBTARGET= pmda_\$(IAM).so
+TARGETS = \$(CMDTARGET) \$(LIBTARGET)
+
+CFILES = `cat CFILES`
+
+GCFILES = $oflag/metrictab.c $oflag/pmda.c
+GHFILES = $oflag/domain.h $oflag/metrictab.h
+GSRCFILES = $oflag/help $oflag/root $oflag/pmns \
+ $oflag/Makefile.install $oflag/Install \
+ $oflag/Remove $oflag/clusters.h
+
+OBJECTS = \$(CFILES:.c=.o) \$(GCFILES:.c=.o)
+CFLAGS = -I/usr/include/pcp
+LLDLIBS = -lpcp_pmda -lpcp
+
+PMDADIR = \$(PCP_PMDAS_DIR)/\$(IAM)
+LDIRT = generated *.o \$(TARGETS) *.log *.dir *.pag *.o
+
+default: help.pag help.dir \$(TARGETS)
+
+install: default
+ \$(INSTALL) -m 755 -d \$(PCP_VAR_DIR)
+ \$(INSTALL) -m 755 -d \$(PCP_VAR_DIR)/pmdas
+ \$(INSTALL) -m 755 -d \$(PMDADIR)
+ \$(INSTALL) -m 755 -f \$(CMDTARGET) \$(PMDADIR)/\$(CMDTARGET)
+ \$(INSTALL) -m 755 -f \$(LIBTARGET) \$(PMDADIR)/\$(LIBTARGET)
+ \$(INSTALL) -m 755 -f $oflag/Install \$(PMDADIR)/Install
+ \$(INSTALL) -m 755 -f $oflag/Remove \$(PMDADIR)/Remove
+ \$(INSTALL) -m 644 -f $oflag/Makefile.install \$(PMDADIR)/Makefile
+ \$(INSTALL) -m 644 -f $oflag/pmns \$(PMDADIR)/pmns
+ \$(INSTALL) -m 644 -f $oflag/root \$(PMDADIR)/root
+ \$(INSTALL) -m 644 -f $oflag/domain.h \$(PMDADIR)/domain.h
+ \$(INSTALL) -m 644 -f $oflag/clusters.h \$(PMDADIR)/clusters.h
+ \$(INSTALL) -m 644 -f $oflag/help \$(PMDADIR)/help
+ \$(INSTALL) -m 644 -f help.pag \$(PMDADIR)/help.pag
+ \$(INSTALL) -m 644 -f help.dir \$(PMDADIR)/help.dir
+
+help.dir help.pag : $oflag/help
+ \$(PCP_BINADM_DIR)/newhelp -n $oflag/root -v 2 -o help < $oflag/help
+
+\$(GCFILES) \$(GHFILES) \$(GSRCFILES) : \$(CONFIG)
+ \$(GENPMDA) -s \$(PCP_VAR_DIR)/pmns/stdpmid -D \$(DOMAIN) -d -v -i \$(IAM) -c \$(CONFIG)
+
+\$(CMDTARGET): \$(OBJECTS)
+ cc -o \$(CMDTARGET) \$(OBJECTS) \$(LLDLIBS)
+
+\$(LIBTARGET): \$(OBJECTS)
+ cc -shared -Wl,-soname,\$(LIBTARGET) -o \$(LIBTARGET) \$(OBJECTS) \$(LLDLIBS) -ldl
+
+clean clobber:
+ rm -rf \$(LDIRT)
+EOFEOF
+
+
+#
+# What needs to be merged?
+#
+for f in *.new
+do
+ b=`basename $f .new`
+ if [ -f "$b" ]
+ then
+ if diff "$b" "$f" >/dev/null 2>&1
+ then
+ rm -f $f
+ $verbose && echo "Unchanged $b"
+ else
+ echo "MERGE required, gdiff $b $f"
+ fi
+ else
+ mv $f $b
+ $verbose && echo "Wrote $b"
+ fi
+done
+
+rm -f CFILES
+exit 0
diff --git a/src/include/GNUmakefile b/src/include/GNUmakefile
new file mode 100644
index 0000000..a6c1ce8
--- /dev/null
+++ b/src/include/GNUmakefile
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CONFFILES = builddefs pcp.conf
+LSRCFILES = builddefs.in buildrules pcp.conf.in pcp.env pcp.mingw
+
+LDIRT = builddefs.install
+
+SUBDIRS = pcp
+
+default :: default_pcp
+
+default_pcp : $(SUBDIRS) pcp.conf builddefs.install
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+install :: default_pcp install_pcp
+
+install_pcp : $(SUBDIRS) default_pcp
+ $(INSTALL) -m 644 pcp.conf $(PCP_ETC_DIR)/pcp.conf
+ifeq "$(TARGET_OS)" "mingw"
+ $(INSTALL) -m 644 pcp.mingw $(PCP_ETC_DIR)/pcp.env
+else
+ $(INSTALL) -m 644 pcp.env $(PCP_ETC_DIR)/pcp.env
+endif
+ $(INSTALL) -m 644 buildrules $(PCP_INC_DIR)/buildrules
+ $(INSTALL) -m 644 builddefs.install $(PCP_INC_DIR)/builddefs
+ $(SUBDIRS_MAKERULE)
+
+builddefs.install : builddefs
+ sed -e 's;^include .*pcp.conf;include $$(PCP_DIR)/etc/pcp.conf;' \
+ -e 's;^BUILDRULES.*=.*buildrules;BUILDRULES = $$\(PCP_INC_DIR\)/buildrules;' \
+ -e 's;^INSTALL_SH.*=.*install-sh;INSTALL_SH = $$\(PCP_BINADM_DIR\)/install-sh;' \
+ -e 's;^GENPMDA.*=.*genpmda;GENPMDA = $$\(PCP_BIN_DIR\)/genpmda;' \
+ < builddefs > builddefs.install
diff --git a/src/include/builddefs.in b/src/include/builddefs.in
new file mode 100644
index 0000000..337b265
--- /dev/null
+++ b/src/include/builddefs.in
@@ -0,0 +1,706 @@
+#
+# Copyright (c) 2012-2014 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Common gmake macros for building
+#
+# @configure_input@
+#
+ifndef _BUILDDEFS_INCLUDED_
+_BUILDDEFS_INCLUDED_ = 1
+
+ifndef _PCP_CONF_INCLUDED_
+_PCP_CONF_INCLUDED_ = 1
+include $(TOPDIR)/src/include/pcp.conf
+endif
+
+# General package information
+PACKAGE_VERSION ?= @PACKAGE_VERSION@
+PACKAGE_MAJOR ?= @PACKAGE_MAJOR@
+PACKAGE_MINOR ?= @PACKAGE_MINOR@
+PACKAGE_REVISION ?= @PACKAGE_REVISION@
+PACKAGE_BUILD ?= @PACKAGE_BUILD@
+PACKAGE_NAME ?= pcp
+PACKAGE_CONFIGURE ?= @PACKAGE_CONFIGURE@
+PACKAGE_DISTRIBUTION ?= @PACKAGE_DISTRIBUTION@
+SGI_CHROOT_BUILD ?= 0
+SGI_ISSP_BUILD ?= 0
+
+BUILDRULES = $(TOPDIR)/src/include/buildrules
+
+# LCFLAGS, LLDFLAGS, LLDLIBS, LDIRT may be specified in local makefiles.
+
+# turn on all warnings by default
+ifeq "@cc_is_gcc@" "yes"
+WARN_OFF = -Wall
+else
+WARN_OFF =
+endif
+
+LIBPCP_ABIDIR ?= src
+PCPLIB_LDFLAGS = -L$(TOPDIR)/src/libpcp/$(LIBPCP_ABIDIR) \
+ -L$(TOPDIR)/src/libpcp_pmda/$(LIBPCP_ABIDIR)
+# backward compatibility
+PCP_LIBS = $(PCPLIB_LDFLAGS)
+
+# platform-specific CFLAGS, LDLIBS, and shared library extension
+#
+# Note:
+# Need PCFLAGS setting here to match the CFLAGS settings in
+# ../../configure.ac (likewise for PLDFLAGS and LDFLAGS).
+#
+TARGET_OS = @target_os@
+PCFLAGS = @PCFLAGS@
+PLDFLAGS = @PLDFLAGS@
+ifneq (, $(filter linux kfreebsd gnu, $(TARGET_OS)))
+DSOSUFFIX = so
+endif
+ifeq "$(TARGET_OS)" "darwin"
+DSOSUFFIX = dylib
+CASE_INSENSITIVE_FS=1
+endif
+ifeq "$(TARGET_OS)" "mingw"
+DSOSUFFIX = dll
+EXECSUFFIX = .exe
+SHELLSUFFIX = .sh
+PLDLIBS = -lwsock32
+endif
+ifeq "$(TARGET_OS)" "solaris"
+PLDFLAGS += -fPIC
+PLDLIBS = -lnsl -lsocket -lresolv -ldl -lposix4
+DSOSUFFIX = so
+endif
+ifeq "$(TARGET_OS)" "aix"
+DSOSUFFIX = so
+# -qcpluscmt
+# allow C++-style // as comment preamble
+# -brtl
+# use run-time linker
+# -bnoipath
+# do not use path to DSOs from the build, use default search path
+# rules
+# (and does not accept -Wall as a valid option)
+PLDFLAGS += -brtl -bnoipath
+WARN_OFF =
+endif
+ifeq "$(TARGET_OS)" "freebsd"
+DSOSUFFIX = so
+endif
+ifeq "$(TARGET_OS)" "netbsd"
+DSOSUFFIX = so
+endif
+
+CFLAGS_ABI = @cflags_abi@
+CFLAGS_OPT = -O2 -g
+CFLAGS += $(PCFLAGS) $(LCFLAGS) $(WARN_OFF) $(CFLAGS_OPT) \
+ -DPCP_DEBUG -DPCP_VERSION=\"$(PCP_VERSION)\" \
+ -I$(TOPDIR)/src/include -I$(TOPDIR)/src/include/pcp
+
+PIECFLAGS = @PIECFLAGS@
+PIELDFLAGS = @PIELDFLAGS@
+INVISIBILITY = @INVISIBILITY@ # hide shared library symbols
+
+NSSCFLAGS = @NSSCFLAGS@
+NSPRCFLAGS = @NSPRCFLAGS@
+SASLCFLAGS = @SASLCFLAGS@
+AVAHICFLAGS = @avahi_CFLAGS@
+
+LDFLAGS += $(PLDFLAGS) $(WARN_OFF) $(PCP_LIBS) $(LLDFLAGS)
+
+SRCFILES = GNUmakefile $(HFILES) $(CFILES) $(CXXFILES) $(MFILES) \
+ $(LSRCFILES) $(LFILES) $(YFILES) $(PYFILES)
+
+LDLIBS = $(LLDLIBS) $(PLDLIBS)
+MAKEOPTS = --no-print-directory
+
+ifdef PROJECT
+QTDIRDIRT = build debug release .obj .ui .moc .qrc *.xcodeproj *.app
+QTDIRT = *.a *.o ui_* moc_* qrc_* Info.plist Makefile* object_script.*
+endif
+DEPDIRT = dep dep.bak
+MANDIRT = *.[1-9].gz *.[1-9].bz2 *.[1-9].lzma *.[1-9].xz *.[1-9].tmp
+LIBDIRT = $(LIBTARGET) $(STATICLIBTARGET)
+CDIRT = $(OBJECTS) $(CMDTARGET) $(CXXMDTARGET)
+DIRT = $(LDIRT) $(DEPDIRT) $(MANDIRT) $(QTDIRT) $(CDIRT) $(LIBDIRT)
+DIRDIRT = $(LDIRDIRT) $(QTDIRDIRT)
+
+OBJECTS = $(ASFILES:.s=.o) \
+ $(CFILES:.c=.o) \
+ $(CXXFILES:.cxx=.o) \
+ $(FFILES:.f=.o) \
+ $(LFILES:.l=.o) \
+ $(YFILES:%.y=%.tab.o)
+
+#NB: don't override $(MAKE); gnumake sets it well, propagating -j etc.
+#MAKE = @make@
+CC = @cc@
+CXX = @cxx@
+LD = @ld@
+AWK = @awk@
+SED = @sed@
+CPP = @cpp@
+LEX = @lex@
+YACC = @yacc@
+ECHO = @echo@
+LN_S = @LN_S@
+GREP = @grep@
+GIT = @GIT@
+PYTHON = @PYTHON@
+DTRACE = @DTRACE@
+QMAKE = @qmake@
+
+INSTALL_SH = $(TOPDIR)/install-sh
+INSTALL = $(INSTALL_SH) -o $(PCP_USER_INSTALL) -g $(PCP_GROUP_INSTALL)
+
+CCF = $(CC) $(CFLAGS)
+CXXF = $(CXX) $(CFLAGS) $(CXXFLAGS)
+# NB: don't use $(MAKEF), since that suppresses gnumake's subdir parallelization
+#MAKEF = $(MAKE) $(MAKEOPTS)
+LDF = $(LD) $(LDFLAGS)
+MAKEDEPEND = @makedepend@
+
+ifeq "$(TARGET_OS)" "freebsd"
+# gmake on FreeBSD has a strange default rule that passes insufficient
+# flags to cc/ld for the link step. This change prevents errors like
+# undefined reference to `__stack_chk_fail_local'
+#
+LDFLAGS += $(CFLAGS)
+endif
+
+ZIP = @gzip@
+BZIP2 = @bzip2@
+LZMA = @lzma@
+XZ = @xz@
+TAR = @tar@
+RPMPROG = @rpmprog@
+PACKAGE_MAKER = @package_maker@
+HDIUTIL = @hdiutil@
+MKINSTALLP = @mkinstallp@
+DLLTOOL = @dlltool@
+RPMBUILD= @rpmbuild@
+RPM = @rpm@
+POD2MAN = @pod2man@
+DPKG = @dpkg@
+MAKEPKG = @makepkg@
+GENPMDA = $(TOPDIR)/src/genpmda/genpmda
+PKGMK = @pkgmk@
+MD5SUM = @md5sum@
+XMLTO = @xmlto@
+DBLATEX = @dblatex@
+PUBLICAN = @publican@
+BOOK_TOOLCHAIN = @book_toolchain@
+
+HAVE_GZIPPED_MANPAGES = @have_gzipped_manpages@
+HAVE_BZIP2ED_MANPAGES = @have_bzip2ed_manpages@
+HAVE_LZMAED_MANPAGES = @have_lzmaed_manpages@
+HAVE_XZED_MANPAGES = @have_xzed_manpages@
+PCP_MAN_DIR = @pcp_man_dir@
+PCP_BOOKS_DIR = @pcp_books_dir@
+PCP_ICONS_DIR = @pcp_icons_dir@
+PCP_DESKTOP_DIR = @pcp_desktop_dir@
+
+NEED_OLD_TBL_HEADER = @need_old_tbl_header@
+RDYNAMIC_FLAG = @rdynamic_flag@
+QT_RELEASE = @qt_release@
+
+ENABLE_SHARED = @enable_shared@
+ENABLE_SECURE = @enable_secure@
+ENABLE_PROBES = @enable_probes@
+ENABLE_AVAHI = @enable_avahi@
+ENABLE_QT = @enable_qt@
+ENABLE_SYSTEMD = @enable_systemd@
+ENABLE_PAPI = @enable_papi@
+
+# additional libraries needed for particular functions
+LIB_FOR_BASENAME = @lib_for_basename@
+LIB_FOR_DLOPEN = @lib_for_dlopen@
+LIB_FOR_REGEX = @lib_for_regex@
+LIB_FOR_MATH = @lib_for_math@
+LIB_FOR_READLINE = @lib_for_readline@
+LIB_FOR_CURSES = @lib_for_curses@
+LIB_FOR_PTHREADS = @lib_for_pthreads@
+LIB_FOR_RT = @lib_for_rt@
+LIB_FOR_NSS = @lib_for_nss@
+LIB_FOR_NSPR = @lib_for_nspr@
+LIB_FOR_SASL = @lib_for_sasl@
+LIB_FOR_SSL = @lib_for_ssl@
+LIB_FOR_AVAHI = @lib_for_avahi@
+LIB_FOR_ATOMIC = @lib_for_atomic@
+
+HAVE_LIBMICROHTTPD = @HAVE_LIBMICROHTTPD@
+HAVE_RPMLIB = @HAVE_RPMLIB@
+
+SHELL = /bin/sh
+IMAGES_DIR = $(TOPDIR)/all-images
+DIST_DIR = $(TOPDIR)/dist
+
+# env vars to be set before you can run a PCP binary in the build
+# environment ... needed for tools like newhelp
+#
+# default, then special case for different platforms
+#
+RUN_IN_BUILD_ENV = PCP_CONF=$(TOPDIR)/src/include/pcp.conf LD_LIBRARY_PATH=$(TOPDIR)/src/libpcp/src:$(TOPDIR)/src/libpcp_pmda/src:$$LD_LIBRARY_PATH HOME=`pwd` PCP_ALT_CPP=$(TOPDIR)/src/pmcpp/pmcpp
+ifeq "$(TARGET_OS)" "darwin"
+RUN_IN_BUILD_ENV = PCP_CONF=$(TOPDIR)/src/include/pcp.conf DYLD_LIBRARY_PATH=$(TOPDIR)/src/libpcp/src:$(TOPDIR)/src/libpcp_pmda/src:$$DYLD_LIBRARY_PATH HOME=`pwd` PCP_ALT_CPP=$(TOPDIR)/src/pmcpp/pmcpp
+endif
+ifeq "$(TARGET_OS)" "aix"
+RUN_IN_BUILD_ENV = PCP_CONF=$(TOPDIR)/src/include/pcp.conf LIBPATH=$(TOPDIR)/src/libpcp/src:$(TOPDIR)/src/libpcp_pmda/src:$$LIBPATH HOME=`pwd` PCP_ALT_CPP=$(TOPDIR)/src/pmcpp/pmcpp
+endif
+ifeq "$(TARGET_OS)" "mingw"
+RUN_IN_BUILD_ENV = PCP_CONF=$(TOPDIR)/src/include/pcp.conf PATH=$(TOPDIR)/src/libpcp/src:$(TOPDIR)/src/libpcp_pmda/src:$$PATH USERPROFILE=`pwd` PCP_ALT_CPP=`pwd`/$(TOPDIR)/src/pmcpp/pmcpp
+endif
+
+SUBDIRS_MAKERULE = \
+ +for d in `echo $^ `; do \
+ if test -d "$$d" -a -f "$$d/GNUmakefile"; then \
+ $(ECHO) === $$d ===; \
+ $(MAKE) $(MAKEOPTS) -C $$d $@ || exit $$?; \
+ fi; \
+ done
+
+# prepare symbols file for the GNU toolchain (linker) for DSO PMDAs
+VERSION_SCRIPT_MAKERULE = \
+ @rm -f $@; \
+ echo "{" >$@; \
+ echo " global: $(PMDAINIT);" >>$@; \
+ echo " local: *;" >>$@; \
+ echo "};" >>$@; \
+
+# prepare domain.h used during the PMDA build process for each PMDA
+DOMAIN_MAKERULE = \
+ @rm -f $@; \
+ echo "/*" >$@; \
+ echo " * built from $<" >>$@; \
+ echo " */" >>$@; \
+ $(AWK) <$< '\
+ $$1=="\#define" && $$2 == "$(DOMAIN)" {\
+ print "\#define $(DOMAIN) " $$3 >>"$@"; found++ \
+ }\
+ END {\
+ if (found == 0) { \
+ print "Botch: no define for domain $(DOMAIN) in $<"; \
+ system("rm '$@'"); \
+ exit(1) \
+ }\
+ if (found > 1) { \
+ print "Botch: multiple defines for domain $(DOMAIN) in $<";\
+ print "... see $@ for details"; \
+ system("rm '$@'"); \
+ exit(1) \
+ }\
+ exit(0) \
+ }' || ( rm -f $@ && false )
+
+DOMAIN_PERLRULE = \
+ @export perldomain=`sed -n \
+ -e '/PCP::PMDA->new/s/[^0-9]*$$//' \
+ -e '/PCP::PMDA->new/s/^[^0-9]*//p' pmda$(IAM).pl`; \
+ $(AWK) <$< '\
+ BEGIN {\
+ domain = toupper("$(IAM)") \
+ }\
+ $$1=="\#define" && $$2 == domain { \
+ pmd=$$3; found++ \
+ }\
+ END {\
+ if (found == 0) {\
+ print "Botch: no define for domain " domain " in $<"; \
+ exit(1) \
+ }\
+ if (found > 1) {\
+ print "Botch: multiple defines of domain " domain " in $<";\
+ exit(1) \
+ }\
+ if (pmd != "'"$$perldomain"'") {\
+ print "Botch: domain number in ../../pmns/stdpmid (" pmd ") does not match domain number in Perl source ("'"$$perldomain"'")"; \
+ exit(1) \
+ }\
+ exit(0) \
+ }'
+
+DOMAIN_PYTHONRULE = \
+ @export pythondomain=`sed -n \
+ -e '/PMDA(/s/[^0-9]*$$//' \
+ -e '/PMDA(/s/^[^0-9]*//p' $(PYSCRIPT)`; \
+ $(AWK) <$< '\
+ BEGIN {\
+ domain = toupper("$(IAM)") \
+ }\
+ $$1=="\#define" && $$2 == domain { \
+ pmd=$$3; found++ \
+ }\
+ END {\
+ if (found == 0) {\
+ print "Botch: no define for domain " domain " in $<"; \
+ exit(1) \
+ }\
+ if (found > 1) {\
+ print "Botch: multiple defines of domain " domain " in $<";\
+ exit(1) \
+ }\
+ if (pmd != "'"$$pythondomain"'") {\
+ print "Botch: domain number in ../../pmns/stdpmid (" pmd ") does not match domain number in Python source ("'"$$pythondomain"'")"; \
+ exit(1) \
+ }\
+ exit(0) \
+ }'
+
+POD_OPTIONS = --section=$(MAN_SECTION) --release=$(PCP_VERSION) \
+ --center="Performance Co-Pilot" --date="Performance Co-Pilot"
+POD_MAKERULE = $(POD2MAN) $(POD_OPTIONS) $^ $@
+
+ifeq "$(TARGET_OS)" "mingw"
+INSTALL_MAN =
+else
+INSTALL_MAN = \
+ test -z "$$MAN_PAGES" && MAN_PAGES="$(MAN_PAGES)"; \
+ for d in `echo $$MAN_PAGES`; do \
+ first=true; \
+ base=`echo $$d | sed -e 's/\.[0-9]//g'`; \
+ $(AWK) <$$d ' \
+BEGIN { state=0; print "'"$$base"'" } \
+$$1==".ds" { ds["\\\\\\*\\("$$2] = $$3 } \
+$$1==".SH" && $$2=="NAME" { state=1; next } \
+$$1==".SH" && state==1 { exit } \
+/^\./ { next } \
+state==1 { for (i=1;i<=NF;i++) { \
+ if ($$i=="\\-" || $$i=="-") exit; \
+ gsub ("\\\\f3", "", $$i); gsub ("\\\\f1.*", "", $$i); \
+ for ( d in ds ) sub (d, ds[d], $$i); \
+ print $$i \
+ } \
+ }' \
+ | LC_COLLATE=POSIX $(PCP_SORT_PROG) -u \
+ | while read m; do \
+ [ -z "$$m" -o "$$m" = "\\" ] && continue; \
+ t=$(MAN_DEST)/$$m.$(MAN_SECTION); \
+ if $$first; then \
+ _tfx= ;\
+ if $(NEED_OLD_TBL_HEADER) ; then \
+ $(SED) -e "1s/^'\\\\\"! tbl.*/'\\\\\" t/" $$d > $$d.tmp; _tfx=.tmp; \
+ fi; \
+ if $(HAVE_GZIPPED_MANPAGES) ; then \
+ $(ZIP) -c $$d$$_tfx > $$d.gz; _tfx=.gz; _sfx=.gz; \
+ fi; \
+ if $(HAVE_BZIP2ED_MANPAGES) ; then \
+ $(BZIP2) -c $$d$$_tfx > $$d.bz2; _tfx=.bz2; _sfx=.bz2; \
+ fi; \
+ if $(HAVE_LZMAED_MANPAGES) ; then \
+ $(LZMA) -c $$d$$_tfx > $$d.lzma; _tfx=.lzma; _sfx=.lzma; \
+ fi; \
+ if $(HAVE_XZED_MANPAGES) ; then \
+ $(XZ) -c $$d$$_tfx > $$d.xz; _tfx=.xz; _sfx=.xz; \
+ fi; \
+ u=$$m.$(MAN_SECTION)$$_sfx; \
+ echo $(INSTALL) -m 644 $${d}$$_tfx $${t}$$_sfx; \
+ $(INSTALL) -m 644 $${d}$$_tfx $${t}$$_sfx; \
+ else \
+ if test ! -z $(CASE_INSENSITIVE_FS) -a $(CASE_INSENSITIVE_FS); then \
+ if test "`echo $$u | tr 'a-z' 'A-Z'`" != "`basename $${t}$$_sfx | tr 'a-z' 'A-Z'`"; then \
+ echo $(INSTALL) -S $$u $${t}$$_sfx; \
+ $(INSTALL) -S $$u $${t}$$_sfx; \
+ fi \
+ else \
+ echo $(INSTALL) -S $$u $${t}$$_sfx; \
+ $(INSTALL) -S $$u $${t}$$_sfx; \
+ fi \
+ fi; \
+ first=false; \
+ done; \
+ done
+endif
+
+PERL_INSTALL_BASE = @perl_install_base@
+PERL_INSTALLDIRS = @perl_installdirs@
+
+# MakeMaker INSTALL_BASE needs to be unset for proper vendor_perl paths to be used for --prefix=/usr;
+ifeq "$(PERL_INSTALL_BASE)" "/usr"
+ifneq "$(TARGET_OS)" "darwin"
+MAKEMAKER_EXTRA_OPTIONS=
+else
+MAKEMAKER_EXTRA_OPTIONS=INSTALL_BASE=$(PERL_INSTALL_BASE) INSTALLBASE=$(PERL_INSTALL_BASE)
+endif
+else
+MAKEMAKER_EXTRA_OPTIONS=INSTALL_BASE=$(PERL_INSTALL_BASE) INSTALLBASE=$(PERL_INSTALL_BASE)
+endif
+
+PERL_MAKE_MAKEFILE = \
+ export PCP_TOPDIR=`cd $(TOPDIR) && pwd`; \
+ NSS_CFLAGS="$(NSS_CFLAGS)" NSPR_CFLAGS="$(NSPR_CFLAGS)" \
+ TARGET_OS="$(TARGET_OS)" CC="$(CC) $(CFLAGS_ABI)" perl Makefile.PL $(MAKEMAKER_EXTRA_OPTIONS) $(MAKEMAKER_OPTIONS)
+
+# Collect files from a Perl "make -f Makefile install" below
+# src/perl to build the PCP Perl packaging list, and also clean up
+# the installed files to remove unwanted files and make the DSO
+# executable
+#
+# Usage is $(call PERL_GET_FILELIST,listfile,base)
+# where listfile is something like $(TOPDIR)/perl-pcp-pmda.list and
+# base is the DSO basename like PMDA.
+#
+# We need different versions for the different installation and
+# packaging regimes.
+#
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+# For Debian packaging, this is not needed
+PERL_GET_FILELIST =
+else
+ifeq "$(PACKAGE_DISTRIBUTION)" "gentoo"
+# Gentoo cannot rely on the .packlist files from the install, so create
+# a temporary pack.list file
+PERL_GET_FILELIST = \
+ $(PERLMAKE) -f Makefile install DESTDIR=$$DIST_ROOT \
+ | tee pack.list; \
+ sed -n -e '/\.bs$$/d' -e 's/\.[0-9]pm$$/&.gz/' -e "s@^Installing $$DIST_ROOT@@p" <pack.list >$(1) || exit 1; \
+ if [ -s $(1) ]; then rm -f pack.list; \
+ else echo "Arrgh ... no files to include in package via $(1), see pack.list"; exit 1; \
+ fi; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name perllocal.pod -exec rm -f '{}' ';' ; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name \*.bs -exec rm -f '{}' ';' ; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name $(2).so -exec chmod 755 '{}' ';'
+else
+ifeq "$(PACKAGE_DISTRIBUTION)" "freebsd"
+# FreeBSD Perl packaging is a broken mystery at this point in time
+# 1. there is no .packlist files being created
+# 2. $(PERLMAKE) -f Makefile install DESTDIR=$$DIST_ROOT does not work
+# so disable the packaging pro tem
+PERL_GET_FILELIST =
+else
+# Everyone else ... includes the RPM-based packaging platforms
+ifeq "$(PACKAGE_DISTRIBUTION)" "mandriva"
+ man_suffix=lzma
+else
+ man_suffix=gz
+endif
+PERL_GET_FILELIST = \
+ $(PERLMAKE) -f Makefile install DESTDIR=$$DIST_ROOT; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name .packlist -exec mv '{}' $(1) ';' ; \
+ if [ -s $(1) ] ; then \
+ _sfx=.gz; \
+ $(HAVE_BZIP2ED_MANPAGES) && _sfx=.bz2; \
+ $(HAVE_LZMAED_MANPAGES) && _sfx=.lzma; \
+ $(HAVE_XZED_MANPAGES) && _sfx=.xz; \
+ sed -n -e '/\.bs$$/d' -e 's/\.[0-9]pm$$/&'"$$_sfx/" -e "s@^$$DIST_ROOT@@p" $(1) >$(1).tmp; \
+ mv $(1).tmp $1; \
+ else echo "Arrgh ... no files to include in package via $(1)"; exit 1; \
+ fi; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name perllocal.pod -exec rm -f '{}' ';' ; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name \*.bs -exec rm -f '{}' ';' ; \
+ find $$DIST_ROOT/$(PERL_INSTALL_BASE) -name $(2).so -exec chmod 755 '{}' ';'
+endif
+endif
+endif
+
+# Create perl manifest files explicitly for some distributions
+ifeq "$(shell [ '$(PACKAGE_DISTRIBUTION)' = cocoa \
+ -o '$(PACKAGE_DISTRIBUTION)' = macosx \
+ -o '$(PACKAGE_DISTRIBUTION)' = gentoo \
+ -o '$(PACKAGE_DISTRIBUTION)' = solaris \
+ -o '$(PACKAGE_DISTRIBUTION)' = freebsd \
+ ] && echo 1)" "1"
+# Gather installed Perl files before packaging
+PERL_INSTALL = \
+ if [ -n "$(DIST_MANIFEST)" ]; then \
+ if [ "`echo $(TOPDIR)/perl-pcp-*.list`" != "$(TOPDIR)/perl-pcp-*.list" ]; then \
+ cat $(TOPDIR)/perl-pcp-*.list | while read f; do \
+ bn=`basename $$f .gz`; \
+ dn=`dirname $$f`; \
+ $(INSTALL) -d $$dn || exit 1; \
+ src=`find */blib -name $$bn`; \
+ if [ -x $$src ] ; then mode=0755; else mode=0644; fi; \
+ $(INSTALL) -m $$mode $$src $$dn/$$bn || exit 1; \
+ done; \
+ fi; \
+ fi
+else
+PERL_INSTALL =
+endif
+
+SYSTEMD_CFLAGS=@SYSTEMD_CFLAGS@
+SYSTEMD_LIBS=@SYSTEMD_LIBS@
+PMDA_SYSTEMD=@PMDA_SYSTEMD@
+
+IB_LIBS=@IB_LIBS@
+PMDA_INFINIBAND=@PMDA_INFINIBAND@
+
+
+#
+# Python platform-specific installation quirks
+PYTHON_PREFIX=@python_prefix@
+SETUP_PY_BUILD_OPTIONS = --include-dirs=$(TOPDIR)/src/include
+SETUP_PY_BUILD_OPTIONS += --library-dirs=$(TOPDIR)/src/libpcp/src:$(TOPDIR)/src/libpcp_pmda/src:$(TOPDIR)/src/libpcp_gui/src:$(TOPDIR)/src/libpcp_import/src:$(TOPDIR)/src/libpcp_mmv/src
+SETUP_PY_INSTALL_OPTIONS = --skip-build
+SETUP_PY_INSTALL_OPTIONS += --root=$${DIST_ROOT-/}
+SETUP_PY_INSTALL_OPTIONS += --record=$(TOPDIR)/python-pcp.list
+ifeq "$(PYTHON_PREFIX)" "/usr"
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+SETUP_PY_INSTALL_OPTIONS += --install-layout=deb
+endif
+else
+SETUP_PY_INSTALL_OPTIONS += --prefix=$(PYTHON_PREFIX)
+endif
+# RPM byte-compiles and installs results in our DIST_ROOT, cater for this:
+ifeq "$(shell [ '$(TARGET_OS)' = linux -a '$(PACKAGE_DISTRIBUTION)' != gentoo \
+ ] && echo 1)" "1"
+# Linux and not Gentoo (which needs tarball packaging)
+PYTHON_INSTALL = \
+ $(AWK) '{print} /.pyc$$/ {sub(/.pyc$$/,".pyo"); print}' \
+ < $(TOPDIR)/python-pcp.list > $(TOPDIR)/python-pcp.list.rpm; \
+ cat $(TOPDIR)/python-pcp.list.rpm | while read f; do \
+ touch $${DIST_ROOT-/}$$f; \
+ done
+else
+ifeq "$(shell [ '$(PACKAGE_DISTRIBUTION)' = cocoa \
+ -o '$(PACKAGE_DISTRIBUTION)' = macosx \
+ -o '$(PACKAGE_DISTRIBUTION)' = gentoo \
+ -o '$(PACKAGE_DISTRIBUTION)' = solaris \
+ -o '$(PACKAGE_DISTRIBUTION)' = freebsd \
+ ] && echo 1)" "1"
+# Gather installed Python files before packaging
+# Lines in python-pcp.list are like this
+# /usr/lib/python2.7/site-packages/pcp.py
+# /usr/lib/python2.7/site-packages/pcp.pyc
+# /usr/lib/python2.7/site-packages/pmapi.so
+# /usr/lib/python2.7/site-packages/pcp-0.2-py2.7.egg-info
+#
+# Matching build artifacts are below src/python/build
+#
+PYTHON_INSTALL = \
+ if [ -n "$(DIST_MANIFEST)" ]; then \
+ cat $(TOPDIR)/python-pcp.list \
+ | while read f; do \
+ bn=`basename $$f`; \
+ dn=`dirname $$f`; \
+ $(INSTALL) -d $$dn || exit 1; \
+ src=`find $(TOPDIR)/src/python/build -name $$bn`; \
+ $(INSTALL) $$src $$f || exit 1; \
+ done; \
+ fi
+else
+PYTHON_INSTALL =
+endif
+endif
+
+# Qt magic for build/installs across all the supported platforms
+ifeq ($(PCP_PLATFORM),darwin)
+QTMAKE = $(QMAKE) -spec macx-g++ CONFIG+=$(QT_RELEASE) && make -f Makefile
+MACBUILD = build/$(QT_RELEASE)/$(COMMAND).app/Contents
+BINARY = $(MACBUILD)/MacOS/$(COMMAND)
+LNMAKE = test ! -f $(BINARY) -o -L $(COMMAND) || $(LN_S) $(BINARY) $(COMMAND)
+WINDOW = mac
+endif
+ifeq ($(PCP_PLATFORM),mingw)
+QTMAKE = $(QMAKE) CONFIG+=$(QT_RELEASE) && $(MAKE) -f Makefile
+BINARY = $(QT_RELEASE)/$(COMMAND)
+LNMAKE =
+WINDOW = win
+endif
+ifeq "$(findstring $(PCP_PLATFORM),darwin mingw)" ""
+QTMAKE = $(QMAKE) CONFIG+=$(QT_RELEASE) && $(MAKE) $(MAKEOPTS) -f Makefile
+BINARY = build/$(QT_RELEASE)/$(COMMAND)
+LNMAKE = test ! -f $(BINARY) -o -L $(COMMAND) || $(LN_S) $(BINARY) $(COMMAND)
+WINDOW = x11
+endif
+
+ifeq ($(PCP_PLATFORM),darwin)
+INSTALL_DIRECTORY_HIERARCHY=\
+ d=$(1); while [ "$$d" != "$(2)" -a "$$d" != "/" -a "$$d" != "." ] ; do \
+ echo $$d; d=`dirname $$d`; done | sort | while read id; do \
+ $(INSTALL) -m 755 -d $$id || exit 1; done
+
+INSTALL_QT_RESOURCES=\
+ printf "[Paths]\nPlugins=/Library/PCP/Plugins\n\n" > qt.conf; \
+ $(INSTALL) -m 644 qt.conf $(1)/qt.conf; \
+ rm qt.conf; \
+ find frameworks -type d -name qt_menu.nib | while read nib; do \
+ $(INSTALL) -m 755 -d $(1)/qt_menu.nib || exit 1; \
+ find $$nib -type f | while read nibs; do \
+ f=`basename $$nibs`; \
+ $(INSTALL) -m 644 $$nibs $(1)/qt_menu.nib/$$f || exit 1; \
+ done; \
+ done
+
+MAC_APPSUPPORT_DIR=/Library/PCP
+MAC_FRAMEWORKS_DIR=$(MAC_APPSUPPORT_DIR)/Frameworks
+
+# WARNING!
+# This rule modified the binary it was given, once modified the
+# binary cannot be used with this rule again.
+# If the binary is installed then it's important to call this
+# rule before calling install rule for the binary.
+INSTALL_QT_FRAMEWORKS=\
+ otool -L $(1) | awk '{if (NR != 1) {print $$1}}' |\
+ egrep 'Qt.*\.framework' | while read qt; do \
+ tdir=`dirname $$qt`; \
+ install_name_tool -change $$qt $(MAC_FRAMEWORKS_DIR)/$$qt $(1);\
+ $(call INSTALL_DIRECTORY_HIERARCHY,$(MAC_FRAMEWORKS_DIR)/$$tdir,/Library/PCP); \
+ mkdir -p frameworks/$$tdir || exit 1; \
+ fwqt="frameworks/$$qt"; \
+ cp /Library/Frameworks/$$qt frameworks/$$qt || exit 1; \
+ otool -L $$fwqt | awk '{if (NR != 1) {print $$1}}' |\
+ egrep 'Qt.*\.framework' | while read dep; do \
+ install_name_tool -change $$dep $(MAC_FRAMEWORKS_DIR)/$$dep $$fwqt;\
+ done; \
+ $(INSTALL) frameworks/$$qt $(MAC_FRAMEWORKS_DIR)/$$qt; \
+ if [ -d /Library/Frameworks/$$tdir/Resources ] ; then \
+ $(INSTALL) -d $(MAC_FRAMEWORKS_DIR)/$$tdir/Resources; \
+ (cd /Library/Frameworks/$$tdir; find Resources -type f) | \
+ while read rf; do \
+ rfpath="$$tdir/$$rf"; rfd=`dirname $$rfpath`; \
+ fwpath="frameworks/$$rfpath"; brfd=`basename $$rfd`; \
+ mkdir -p frameworks/$$rfd || exit 1; \
+ cp /Library/Frameworks/$$rfpath $$fwpath || exit 1; \
+ [ $$brfd != qt_menu.nib ] || continue; \
+ $(INSTALL) -d $(MAC_FRAMEWORKS_DIR)/$$rfd || exit 1; \
+ $(INSTALL) $$fwpath $(MAC_FRAMEWORKS_DIR)/$$rfpath;\
+ done \
+ fi; done
+endif
+
+
+# For targets that should always be rebuilt,
+# define a target that is never up-to-date.
+# Targets needing this should depend on $(_FORCE)
+_FORCE = __force_build
+
+PCP_USER = @pcp_user@
+PCP_GROUP = @pcp_group@
+PCP_USER_INSTALL = @pcp_user_install@
+PCP_GROUP_INSTALL = @pcp_group_install@
+
+PCPLIB = -lpcp
+ifneq "$(PCPLIB)" "$(LIB_FOR_BASENAME)"
+PCPLIB += $(LIB_FOR_BASENAME)
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+PCPLIB += $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS) $(LIB_FOR_DLOPEN) $(LIB_FOR_RT)
+endif
+PCP_GUILIB = -lpcp_gui $(PCPLIB)
+PCP_PMDALIB = -lpcp_pmda $(PCPLIB)
+PCP_TRACELIB = -lpcp_trace $(PCPLIB)
+
+ifdef PCP_ALTLIBS
+ifeq ($(PCP_LIB_DIR),$(PCP_LIB32_DIR))
+PCP_ALTLIBS =
+else
+ifneq "$(findstring $(MAKECMDGOALS),clean clobber)" ""
+PCP_ALTLIBS =
+endif
+endif
+endif
+
+BUILD_PMMGR=@BUILD_PMMGR@
+
+endif # _BUILDDEFS_INCLUDED_
diff --git a/src/include/buildrules b/src/include/buildrules
new file mode 100644
index 0000000..6471029
--- /dev/null
+++ b/src/include/buildrules
@@ -0,0 +1,192 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2007 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Common build rules for gmake
+#
+ifndef _BUILDRULES_INCLUDED_
+_BUILDRULES_INCLUDED_ = 1
+
+ifndef _BUILDDEFS_INCLUDED_
+include $(TOPDIR)/src/include/builddefs
+endif
+
+#
+# Standard targets
+#
+ifdef CMDTARGET
+$(CMDTARGET) : $(SUBDIRS) $(OBJECTS)
+ $(CCF) -o $(CMDTARGET) $(LDFLAGS) $(OBJECTS) $(LDLIBS)
+endif
+ifdef CXXMDTARGET
+$(CXXMDTARGET) : $(SUBDIRS) $(OBJECTS)
+ $(CXXF) -o $(CXXMDTARGET) $(LDFLAGS) $(OBJECTS) $(LDLIBS)
+endif
+
+# GNU make has a built-in recipe for .cc / .C / .cpp, but not for .cxx -> .o
+.cxx.o:
+ $(CXXF) $(CXXFLAGS) -c $< -o $@
+.SUFFIXES: .cxx
+
+ifdef LIBTARGET
+ifneq (, $(filter linux freebsd kfreebsd netbsd mingw gnu, $(TARGET_OS)))
+_SHAREDOPTS = -shared -Wl,-soname,$(LIBTARGET)
+endif
+ifeq ($(TARGET_OS), solaris)
+_SHAREDOPTS = -shared -fpic
+endif
+ifeq ($(TARGET_OS), darwin)
+# libtool doesnt understand -dynamiclib so we need both
+_SHAREDOPTS = -fPIC -dynamic -dynamiclib -flat_namespace -undefined suppress -headerpad_max_install_names -arch i386
+ifeq ($(PACKAGE_DISTRIBUTION), cocoa)
+_SHAREDOPTS += -arch x86_64
+endif
+endif
+ifeq ($(TARGET_OS), aix)
+_SHAREDOPTS = -qmkshrobj
+endif
+
+ifdef VERSION_SCRIPT
+ifneq ($(INVISIBILITY),)
+ifeq ($(TARGET_OS), darwin)
+# Mac OS X ld(1) takes a different format for the symbols file
+# _SHAREDOPTS += -Wl,-exported_symbols_list $(VERSION_SCRIPT)
+else
+_SHAREDOPTS += -Wl,--version-script=$(VERSION_SCRIPT)
+endif
+endif
+endif
+
+$(LIBTARGET) : $(SUBDIRS) $(OBJECTS)
+ $(CC) $(LDFLAGS) $(_SHAREDOPTS) -o $(LIBTARGET) $(OBJECTS) $(LDLIBS) $(LIB_FOR_DLOPEN) $(LIB_FOR_BASENAME)
+endif
+
+ifdef STATICLIBTARGET
+$(STATICLIBTARGET) : $(SUBDIRS) $(OBJECTS)
+ifeq ($(TARGET_OS), darwin)
+ libtool -static -o $(STATICLIBTARGET) $?
+else
+ $(AR) cr $(STATICLIBTARGET) $?
+endif
+endif
+
+ifdef WINDOWLINKS
+windowlinks:
+ @for l in $(WINDOWLINKS) ; do \
+ if [ ! -L $$l -a ! -f $$l ] ; then \
+ $(LN_S) $(WINDOW)_$$l $$l ; \
+ fi \
+ done
+endif
+
+# Suffix rule to support transition for YFILES to OBJECTS
+%.tab.h : %.y
+ $(YACC) -d -b `basename $< .y` $<
+
+%.tab.c : %.y
+ $(YACC) -d -b `basename $< .y` $<
+
+# Dealing with quirks of the various packaging mechanisms
+%.py: %.python
+ $(LN_S) $< $@
+
+%.pl: %.perl
+ $(LN_S) $< $@
+
+# Suffix rule to support transition for XML to PDF (books)
+%.pdf: %.xml
+ifeq ($(BOOK_TOOLCHAIN),publican)
+ $(PUBLICAN) build --langs=en-US --formats=pdf
+endif
+ifeq ($(BOOK_TOOLCHAIN),xmlto)
+ $(XMLTO) --with-fop pdf $^
+endif
+ifeq ($(BOOK_TOOLCHAIN),dblatex)
+ $(DBLATEX) --type pdf $^
+endif
+
+ifeq ($(TARGET_OS), mingw)
+#
+# .exe rule for Windows
+#
+.SUFFIXES: .exe
+.o.exe:
+ $(CCF) -o $* $(LDFLAGS) $(OBJECTS) $(LDLIBS)
+endif
+
+clean clobber :: $(SUBDIRS) $(PRO_SUBDIRS) $(SNIA_SUBDIRS)
+ rm -rf $(DIRT)
+ @rm -fr $(DIRDIRT)
+ $(SUBDIRS_MAKERULE)
+
+realclean distclean: clean
+ rm -f $(TOPDIR)/src/include/builddefs \
+ $(TOPDIR)/src/include/pcp.conf \
+ $(TOPDIR)/src/include/pcp/config.h \
+ $(TOPDIR)/src/include/pcp/configsz.h \
+ $(TOPDIR)/src/include/pcp/platform_defs.h \
+ $(TOPDIR)/src/include/pcp/pmdbg.h
+ rm -f $(TOPDIR)/build/GNUlocaldefs
+ rm -f $(TOPDIR)/pcp.lsm
+
+#
+# Never blow away subdirs
+#
+ifdef SUBDIRS
+.PRECIOUS: $(SUBDIRS)
+endif
+
+endif
+
+$(_FORCE):
+
+# The depend target does not depend on any other targets (even though it
+# actually depends on CFILES and HFILES). This is because there are several
+# places where we generate header files (e.g. domain.h is generated for each
+# subdir below src/pmdas, and domain.h in turn depends on src/pmns/stdpmid,
+# which itself a generated file ...). As a result, you can't run make
+# depend after make clobber, because the generated files will be missing.
+#
+# So running makedepend is for development use when you change a header
+# somewhere and you need to be sure everything that depends on that header
+# will be remade properly.
+
+.PHONY : depend $(_FORCE)
+
+depend : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ touch dep
+ $(MAKEDEPEND) -fdep -- $(CFLAGS) -- $(CFILES)
+
+#
+# include dep, but only if it exists
+-include dep
+
+# Support for building multiple versions of the same library
+ifneq ($(PCP_ALTLIBS),)
+$(PCP_ALTLIBS):
+ rm -rf $@
+ mkdir $@
+ cp GNUlocaldefs.$@ $@/GNUlocaldefs
+ $(MAKE) -C src SLDEST=../$@ SLSRC=../src libsrc_symlinks
+endif
+
+libsrc_symlinks:
+ test -n "$(SLDEST)" -a -d $(SLDEST)
+ for f in $(SRCFILES); do \
+ if [ -e $(SLDEST)/$$f -a ! -L $(SLDEST)/$$f ] ; then \
+ echo "$$f exists in $(SLDEST) and not a symlink"; exit 1; \
+ fi; \
+ rm -f $(SLDEST)/$$f; $(LN_S) $(SLSRC)/$$f $(SLDEST)/$$f || exit 1; \
+ done
diff --git a/src/include/pcp.conf.in b/src/include/pcp.conf.in
new file mode 100644
index 0000000..3d4feb4
--- /dev/null
+++ b/src/include/pcp.conf.in
@@ -0,0 +1,206 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# This file defines the directories and paths used by PCP
+# and is installed as the file /etc/pcp.conf for use at run-time.
+#
+# Note: the syntax of this file must allow processing to generate
+# correct variable assignments for both sh and make,
+# hence there should be no spaces immediately before or after
+# the equals in each assignment, no quoting on the right of the
+# equals although the value may contain embedded spaces.
+#
+# Shell scripts should never source this file directly, rather they
+# should use this file indirectly by sourcing /etc/pcp.env, i.e.
+# . $PCP_DIR/etc/pcp.env
+# this will handle the quoting and white space issues and set all
+# "variables" beginning with PCP_ in this file into the environment.
+# The defaults may be over-ridden by setting $PCP_CONF in the environment
+# as the full path to an alternate version of this file.
+#
+# The "standard paths" reflect the directory structure that is used by
+# default on the platform on which this file is installed.
+#
+
+# directory for _this_ file ... useful in the build
+# Standard path: /etc
+PCP_ETC_DIR=@pcp_etc_dir@
+
+# directory for PCP configuration files
+# Standard path: /etc/pcp
+PCP_SYSCONF_DIR=@pcp_sysconf_dir@
+
+# directory for rc/startup scripts
+# Standard path: /etc/init.d
+PCP_RC_DIR=@pcp_rc_dir@
+
+# directory for sysconfig controls
+# Standard path: /etc/sysconf (Redhat-specific)
+# <empty> if no sysconfig support
+PCP_SYSCONFIG_DIR=@pcp_sysconfig_dir@
+
+# directory for public binaries
+# Standard path: /usr/bin
+PCP_BIN_DIR=@pcp_bin_dir@
+
+# directory for private PCP binaries (not run directly by users)
+# Standard path is platform dependent, but generally one of
+# /usr/libexec/pcp/bin or /usr/lib/pcp/bin or /usr/share/pcp/bin
+PCP_BINADM_DIR=@pcp_binadm_dir@
+
+# directory for runtime shared libraries, libpcp.so, etc.
+# Standard path: /usr/lib
+PCP_LIB_DIR=@pcp_lib_dir@
+PCP_LIB32_DIR=@pcp_lib32_dir@
+
+# directory for shared PCP files (shareable for diskless)
+# Standard path: /usr/share/pcp
+# Subdirectories: bin lib
+PCP_SHARE_DIR=@pcp_share_dir@
+
+# directory for headers
+# Standard path: /usr/include/pcp
+PCP_INC_DIR=@pcp_inc_dir@
+
+# parent directory of man pages
+# Standard path: /usr/man
+# Subdirectories: man1 man3 man5
+PCP_MAN_DIR=@pcp_man_dir@
+
+# directory for non-shared (i.e. system local) PCP files
+# Standard path: /var/lib/pcp
+# Subdirectories: config pmns (note see $PCP_PMDAS_DIR) for pmdas
+PCP_VAR_DIR=@pcp_var_dir@
+
+# path to pmcd config file
+# Standard path: $PCP_SYSCONF_DIR/pmcd/pmcd.conf
+PCP_PMCDCONF_PATH=@pcp_pmcdconf_path@
+
+# path to pmcd options file
+# Standard path: $PCP_SYSCONF_DIR/pmcd/pmcd.options
+PCP_PMCDOPTIONS_PATH=@pcp_pmcdoptions_path@
+
+# path to site-local pmcd startup script
+# Standard path: $PCP_SYSCONF_DIR/pmcd/rc.local
+PCP_PMCDRCLOCAL_PATH=@pcp_pmcdrclocal_path@
+
+# path to pmproxy options file
+# Standard path: $PCP_SYSCONF_DIR/pmproxy/pmproxy.options
+PCP_PMPROXYOPTIONS_PATH=@pcp_pmproxyoptions_path@
+
+# path to pmwebd options file
+# Standard path: $PCP_SYSCONF_DIR/pmwebd/pmwebd.options
+PCP_PMWEBDOPTIONS_PATH=@pcp_pmwebdoptions_path@
+
+# path to pmmgr options file
+# Standard path: $PCP_SYSCONF_DIR/pmmgr/pmmgr.options
+PCP_PMMGROPTIONS_PATH=@pcp_pmmgroptions_path@
+
+# path to pmie control file
+# Standard path: $PCP_SYSCONF_DIR/pmie/control
+PCP_PMIECONTROL_PATH=@pcp_pmiecontrol_path@
+
+# path to pmsnap control file
+# Standard path: $PCP_SYSCONF_DIR/pmsnap/control
+PCP_PMSNAPCONTROL_PATH=@pcp_pmsnapcontrol_path@
+
+# path to pmlogger control file
+# Standard path: $PCP_SYSCONF_DIR/pmlogger/control
+PCP_PMLOGGERCONTROL_PATH=@pcp_pmloggercontrol_path@
+
+# directory for PCP PMDAs
+# Standard path: /var/lib/pcp/pmdas
+# Subdirectories: one per PMDA
+PCP_PMDAS_DIR=@pcp_pmdas_dir@
+
+# directory for PCP pid files
+# Standard path: /var/run/pcp
+PCP_RUN_DIR=@pcp_run_dir@
+
+# directory for PCP logs
+# Standard path: /var/log/pcp
+# Subdirectories: pmcd pmlogger pmie
+PCP_LOG_DIR=@pcp_log_dir@
+
+# directory for PCP temp status files
+# Standard path: /var/lib/pcp/tmp
+# Subdirectories: pmie pmlogger
+PCP_TMP_DIR=@pcp_tmp_dir@
+
+# directory for PCP temp work files
+# Standard path: /var/tmp
+PCP_TMPFILE_DIR=@pcp_tmpfile_dir@
+
+# directory for PCP documentation
+# Standard path: /usr/share/doc/pcp
+PCP_DOC_DIR=@pcp_doc_dir@
+PCP_HTML_DIR=@pcp_html_dir@
+
+# directory for PCP demos and examples
+# Standard path: /usr/share/pcp/demos
+PCP_DEMOS_DIR=@pcp_demos_dir@
+
+# awk
+PCP_AWK_PROG="@awk@"
+
+# unix-like sort
+PCP_SORT_PROG=@sort@
+
+# tools needed to rebuild things on the target platform
+PCP_MAKE_PROG=@make@
+PCP_CC_PROG="@cc@ @cflags_abi@"
+
+# echo
+# for lines without newline, use
+# $PCP_ECHO_PROG $PCP_ECHO_N "string""$PCP_ECHO_C"
+PCP_ECHO_PROG=@echo@
+PCP_ECHO_N=@echo_n@
+PCP_ECHO_C=@echo_c@
+
+# write to the system log
+PCP_SYSLOG_PROG=@pcp_syslog_prog@
+
+# running process list
+PCP_PS_PROG=@pcp_ps_prog@
+PCP_PS_HAVE_BSD=@pcp_ps_have_bsd@
+PCP_PS_ALL_FLAGS=@pcp_ps_all_flags@
+
+# locate executables
+PCP_WHICH_PROG=@which@
+
+# host operating system
+PCP_PLATFORM=@target_os@
+PCP_PLATFORM_PATHS=@pcp_platform_paths@
+
+# PCP version
+PCP_VERSION=@PACKAGE_VERSION@
+
+# confirmation dialog
+PCP_XCONFIRM_PROG=@ac_xconfirm_prog@
+
+# mpi
+PCP_MPI_DIRS=@pcp_mpi_dirs@
+
+# default account for running daemons (preferably unprivileged)
+# Standard user: "pcp" and group: "pcp"
+PCP_USER=@pcp_user@
+PCP_GROUP=@pcp_group@
+
+# directory for SASL configuration files
+# Standard path: /etc/sasl2
+PCP_SASLCONF_DIR=@pcp_saslconf_dir@
+
+# directory for systemd unit files
+# Standard path: /usr/lib/systemd/systemd
+PCP_SYSTEMDUNIT_DIR=@pcp_systemdunit_dir@
diff --git a/src/include/pcp.env b/src/include/pcp.env
new file mode 100644
index 0000000..d0ccab2
--- /dev/null
+++ b/src/include/pcp.env
@@ -0,0 +1,95 @@
+#
+# Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# This file is sourced by PCP scripts to set the environment
+# variables defined in the file named $PCP_CONF (or /etc/pcp.conf
+# if $PCP_CONF is not defined). Any variable already defined in
+# the environment is not changed.
+#
+# Note: any variables NOT starting with PCP_ will be ignored.
+# This is a security issue so don't change it.
+# Note also, this (variant of this) file is not used on Windows.
+#
+if [ -z "$PCP_ENV_DONE" ]
+then
+ if [ -n "$PCP_CONF" ]
+ then
+ __CONF="$PCP_CONF"
+ elif [ -n "$PCP_DIR" ]
+ then
+ __CONF="$PCP_DIR/etc/pcp.conf"
+ else
+ __CONF=/etc/pcp.conf
+ fi
+ if [ ! -f "$__CONF" ]
+ then
+ echo "pcp.env: Fatal Error: \"$__CONF\" not found" >&2
+ exit 1
+ fi
+ eval `sed -e 's/"//g' $__CONF \
+ | awk -F= '
+/^PCP_/ && NF == 2 {
+ exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+} END {
+ print "export", exports
+}'`
+ export PCP_ENV_DONE=y
+fi
+
+# Always need to set $PATH ... sudo -E leaves $PCP_ENV_DONE set, but
+# clears/resets $PATH. Note that order is important: any paths with
+# PCP-specific binaries should end up ahead of more generic paths in
+# the final $PATH to avoid conflicts on names of non-pcp binaries.
+#
+for dir in ${PCP_BIN_DIR} ${PCP_BINADM_DIR} \
+ ${PCP_SHARE_DIR}/bin ${PCP_PLATFORM_PATHS}
+do
+ if [ -d $dir ]
+ then
+ if echo ":$PATH:" | grep ":$dir:" >/dev/null 2>&1
+ then
+ :
+ else
+ PATH="$dir:$PATH"
+ fi
+ fi
+done
+export PATH
+
+_get_pids_by_name()
+{
+ if [ $# -ne 1 ]
+ then
+ echo "Usage: _get_pids_by_name process-name" >&2
+ exit 1
+ fi
+
+ # Algorithm ... all ps(1) variants have a time of the form MM:SS
+ # or HH:MM:SS or HH:MM.SS before the psargs field, so we're using
+ # this as the search anchor.
+ #
+ # Matches with $1 (process-name) occur if the first psarg is $1
+ # or ends in /$1 ... the matching uses sed's regular expressions,
+ # so passing a regex into $1 will work.
+
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS \
+ | sed -n \
+ -e 's/$/ /' \
+ -e 's/[ ][ ]*/ /g' \
+ -e 's/^ //' \
+ -e 's/^[^ ]* //' \
+ -e "/[0-9][:\.][0-9][0-9] *[^ ]*\/$1 /s/ .*//p" \
+ -e "/[0-9][:\.][0-9][0-9] *$1 /s/ .*//p"
+}
diff --git a/src/include/pcp.mingw b/src/include/pcp.mingw
new file mode 100644
index 0000000..871e16b
--- /dev/null
+++ b/src/include/pcp.mingw
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# This file is sourced by PCP scripts to set the environment
+# variables defined in the file named $PCP_CONF (or /etc/pcp.conf
+# if $PCP_CONF is not defined). Any variable already defined in
+# the environment is not changed.
+#
+# Note: any variables NOT starting with PCP_ will be ignored.
+# This is a security issue so don't change it.
+# Note also, this variant of pcp.env is *only* used on Windows.
+#
+if [ -z "$PCP_ENV_DONE" ]
+then
+ __CONF=${PCP_CONF-/etc/pcp.conf}
+ if [ ! -f "$__CONF" ]
+ then
+ echo "pcp.env: Fatal Error: \"$__CONF\" not found" >&2
+ exit 1
+ fi
+ export PCP_DIR=/c/MinGW/sys/1.0
+ export PCP_CONF=$PCP_DIR/etc/pcp.conf
+ eval export `PATH=/lib:$PATH $PCP_DIR/libexec/pcp/bin/pmconfig.exe -s`
+ export PATH="/sbin:/bin:/lib:/etc:${PCP_BIN_DIR}:${PCP_BINADM_DIR}:${PCP_SHARE_DIR}/bin:${PCP_SHARE_DIR}/lib:${PCP_PLATFORM_PATHS}"
+ export PCP_ENV_DONE=y
+fi
+
+mkaf() {
+ mkaf.sh $@
+}
+pmafm() {
+ pmafm.sh $@
+}
+pmsignal() {
+ pmsignal.sh $@
+}
+
+_get_pids_by_name()
+{
+ if [ $# -ne 1 ]
+ then
+ echo "Usage: _get_pids_by_name process-name" >&2
+ exit 1
+ fi
+
+ # Algorithm ... all ps(1) variants have a time of the form MM:SS
+ # or HH:MM:SS or HH:MM.SS before the psargs field, so we're using
+ # this as the search anchor.
+ #
+ # Matches with $1 (process-name) occur if the first psarg is $1
+ # or ends in /$1 ... the matching uses sed's regular expressions,
+ # so passing a regex into $1 will work.
+
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS \
+ | sed -n \
+ -e 's/$/ /' \
+ -e 's/[ ][ ]*/ /g' \
+ -e 's/^ //' \
+ -e 's/^[^ ]* //' \
+ -e "/[0-9][:\.][0-9][0-9] *[^ ]*\\$1\.exe /s/ .*//p" \
+ -e "/[0-9][:\.][0-9][0-9] *[^ ]*\/$1 /s/ .*//p" \
+ -e "/[0-9][:\.][0-9][0-9] *$1\.exe /s/ .*//p" \
+ -e "/[0-9][:\.][0-9][0-9] *$1 /s/ .*//p"
+}
diff --git a/src/include/pcp/GNUmakefile b/src/include/pcp/GNUmakefile
new file mode 100644
index 0000000..4aaf310
--- /dev/null
+++ b/src/include/pcp/GNUmakefile
@@ -0,0 +1,42 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+HFILES = pmapi.h impl.h pmda.h pmtime.h pmafm.h import.h \
+ trace.h trace_dev.h mmv_stats.h mmv_dev.h \
+ config32.h config64.h
+INFILES = config.h.in configsz.h.in platform_defs.h.in
+CONFFILES = config.h configsz.h platform_defs.h
+GENERATED_HFILES = pmdbg.h $(CONFFILES)
+
+LSRCFILES = mk_pmdbg fault.h $(INFILES)
+LDIRT = $(GENERATED_HFILES)
+
+default :: default_pcp
+
+default_pcp : $(HEADERS) $(GENERATED_HFILES)
+
+include $(BUILDRULES)
+
+install :: default_pcp install_pcp
+
+install_pcp : default_pcp
+ $(INSTALL) -m 644 $(HFILES) $(GENERATED_HFILES) $(PCP_INC_DIR)
+
+pmdbg.h : pmapi.h impl.h
+ ./mk_pmdbg
diff --git a/src/include/pcp/config.h.in b/src/include/pcp/config.h.in
new file mode 100644
index 0000000..ae1ccdd
--- /dev/null
+++ b/src/include/pcp/config.h.in
@@ -0,0 +1,685 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2003 Moser, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PCP_CONFIG_H
+#define _PCP_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <time.h>
+
+/*
+ * The type of timestamps in struct stat: either HAVE_STAT_TIMESPEC,
+ * HAVE_STAT_TIMESPEC_T, HAVE_STAT_TIMESTRUC or HAVE_STAT_TIME_T
+ */
+#undef HAVE_STAT_TIMESPEC_T
+#undef HAVE_STAT_TIMESTRUC
+#undef HAVE_STAT_TIMESPEC
+#undef HAVE_STAT_TIME_T
+
+/* HAVE_ST_MTIME if stat.st_mtime has a ``spec'' on the end */
+#undef HAVE_ST_MTIME_WITH_SPEC
+
+/* HAVE_ST_MTIME if stat.st_mtime has an ``e'' on the end */
+#undef HAVE_ST_MTIME_WITH_E
+
+/* if your compiler supports LL sufix on 64 bit int constants */
+#undef HAVE_CONST_LONGLONG
+
+/*
+ * HAVE_UNDERBAR_ENVIRON if extern char **_environ is defined
+ * (else extern char **environ will be used)
+ */
+#undef HAVE_UNDERBAR_ENVIRON
+
+/*
+ * If the pointer-to-function arguments to scandir()
+ * are (*scanfn)(const struct dirent *dep)
+ * Otherwise ``const'' will be omitted.
+ */
+#undef HAVE_CONST_DIRENT
+
+/*
+ * If the dirent structure contains a directory offset field.
+ */
+#undef HAVE_DIRENT_D_OFF
+
+/* Defined if printf %p -> 0x prefix for value */
+#undef HAVE_PRINTF_P_PFX
+#ifdef HAVE_PRINTF_P_PFX
+#define PRINTF_P_PFX ""
+#else
+#define PRINTF_P_PFX "0x"
+#endif
+
+/* Defined if bit fields are allocated left-to-right within a word */
+#undef HAVE_BITFIELDS_LTOR
+
+/* if compiler can cast __uint64_t to double */
+#undef HAVE_CAST_U64_DOUBLE
+
+/*
+ * long and pointer must be either 32 bit or 64 bit
+ *
+ * This is complicated by RPMs "multilib" feature, which requires there
+ * to be no differences between header files on 32 and 64 bit platforms.
+ */
+#undef HAVE_BITS_WORDSIZE_H
+#ifdef HAVE_BITS_WORDSIZE_H
+# include <bits/wordsize.h>
+# if __WORDSIZE == 32
+# include "config32.h"
+# elif __WORDSIZE == 64
+# include "config64.h"
+# else
+# error "Unknown word size"
+# endif
+#else
+# include "configsz.h"
+#endif
+
+/* Define if header file is available */
+#undef HAVE_FCNTL_H
+#undef HAVE_LIMITS_H
+#undef HAVE_MALLOC_H
+#undef HAVE_STRINGS_H
+#undef HAVE_SYSLOG_H
+#undef HAVE_UNISTD_H
+#undef HAVE_STDDEF_H
+#undef HAVE_SCHED_H
+#undef HAVE_DLFCN_H
+#undef HAVE_DL_H
+#undef HAVE_SYS_TIME_H
+#undef HAVE_SYS_TIMEB_H
+#undef HAVE_SYS_TIMES_H
+#undef HAVE_SYS_SYSINFO_H
+#undef HAVE_SYS_SYSTEMINFO_H
+#undef HAVE_SYS_RESOURCE_H
+#undef HAVE_SYS_PRCTL_H
+#undef HAVE_ENDIAN_H
+#undef HAVE_STANDARDS_H
+#undef HAVE_SYS_BYTEORDER_H
+#undef HAVE_PTHREAD_H
+#undef HAVE_LIBGEN_H
+#undef HAVE_SYS_PARAM_H
+#undef HAVE_SYS_MMAN_H
+#undef HAVE_SYS_UN_H
+#undef HAVE_STDINT_H
+#undef HAVE_VALUES_H
+#undef HAVE_IEEEFP_H
+#undef HAVE_MATH_H
+#undef HAVE_PWD_H
+#undef HAVE_GRP_H
+#undef HAVE_REGEX_H
+#undef HAVE_TERMIO_H
+#undef HAVE_TERMIOS_H
+#undef HAVE_SYS_TERMIOS_H
+#undef HAVE_SYS_IOCTL_H
+#undef HAVE_SYS_WAIT_H
+#undef HAVE_WINDOWS_H
+#undef HAVE_WINSOCK2_H
+#undef HAVE_WS2TCPIP_H
+#undef HAVE_EXECINFO_H
+#undef HAVE_IPTYPES_H
+#undef HAVE_NETDB_H
+#undef HAVE_SYS_SOCKET_H
+#undef HAVE_NETINET_IN_H
+#undef HAVE_NETINET_TCP_H
+#undef HAVE_ARPA_INET_H
+
+#undef HAVE_SYS_ENDIAN_H
+#undef HAVE_SYS_MACHINE_H
+#undef HAVE_MACHINE_ENDIAN_H
+
+#undef HAVE_AI_ADDRCONFIG
+
+#if defined(HAVE_MALLOC_H)
+#include <malloc.h>
+#endif
+#if defined(HAVE_STDDEF_H)
+#include <stddef.h>
+#endif
+#if defined(HAVE_SYSLOG_H)
+#include <syslog.h>
+#endif
+#if defined(HAVE_WINSOCK2_H)
+#include <winsock2.h>
+#endif
+#if defined(HAVE_WINDOWS_H)
+#include <windows.h>
+#endif
+#if defined(HAVE_WS2TCPIP_H)
+#include <ws2tcpip.h>
+#endif
+
+/* HAVE_NETWORK_BYTEORDER for big endian systems (not Intel) */
+#if defined(HAVE_ENDIAN_H)
+#include <endian.h>
+#elif defined(HAVE_SYS_ENDIAN_H)
+#include <sys/endian.h>
+#elif defined(HAVE_MACHINE_ENDIAN_H)
+#include <machine/endian.h>
+#endif
+#if defined(HAVE_ENDIAN_H) || defined(HAVE_SYS_ENDIAN_H) || defined(HAVE_MACHINE_ENDIAN_H)
+#if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN
+#define HAVE_NETWORK_BYTEORDER
+#endif
+#endif
+
+#if defined(HAVE_SYS_BYTEORDER_H)
+#include <sys/byteorder.h>
+#if defined(_BIG_ENDIAN)
+#define HAVE_NETWORK_BYTEORDER
+#endif
+#endif
+
+#if defined(HAVE_SYS_MACHINE_H)
+#include <sys/machine.h>
+#if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN
+#define HAVE_NETWORK_BYTEORDER
+#endif
+#endif
+
+/* define which libraries are available */
+#undef HAVE_SECURE_SOCKETS
+#undef HAVE_STATIC_PROBES
+#undef HAVE_SERVICE_DISCOVERY
+#undef HAVE_AVAHI
+#undef HAVE_LIBREGEX
+#undef HAVE_READLINE
+
+/* define which libc functions are available */
+#undef HAVE_WAIT3
+
+#undef HAVE_MKTIME
+#undef HAVE_CLOCK_GETTIME
+#undef HAVE_NANOSLEEP
+#undef HAVE_USLEEP
+#undef HAVE_UNSETENV
+
+#undef HAVE_SELECT
+#undef HAVE_SOCKET
+#undef HAVE_GETHOSTNAME
+#undef HAVE_GETPEERUCRED
+#undef HAVE_GETPEEREID
+
+#undef HAVE_UNAME
+#undef HAVE_SYSLOG
+#undef HAVE___CLONE
+#undef HAVE_PIPE2
+#undef HAVE_FCNTL
+
+#undef HAVE_PRCTL
+#undef HAVE_SETLINEBUF
+#undef HAVE_WAITPID
+#undef HAVE_ATEXIT
+#undef HAVE_KILL
+
+#undef HAVE_CHOWN
+#undef HAVE_GETCWD
+#undef HAVE_SCANDIR
+#undef HAVE_MKSTEMP
+
+#undef HAVE_GETUID
+#undef HAVE_GETGID
+#undef HAVE_GETGRENT
+#undef HAVE_GETGRENT_R
+#undef HAVE_GETGRNAM
+#undef HAVE_GETGRNAM_R
+#undef HAVE_GETGRGID
+#undef HAVE_GETGRGID_R
+#undef HAVE_GETPWENT
+#undef HAVE_GETPWENT_R
+#undef HAVE_GETPWNAM
+#undef HAVE_GETPWNAM_R
+#undef HAVE_GETPWUID
+#undef HAVE_GETPWUID_R
+
+#undef HAVE_BRK
+#undef HAVE_SBRK
+#undef HAVE_POSIX_MEMALIGN
+#undef HAVE_MEMALIGN
+#undef HAVE_VALLOC
+
+#undef HAVE_SIGNAL
+#undef HAVE_SIGHOLD
+#undef HAVE_SIGRELSE
+#undef HAVE_TCGETATTR
+#undef GWINSZ_IN_SYS_IOCTL
+
+#undef HAVE_REGEX
+#undef HAVE_REGCMP
+#undef HAVE_REGEXEC
+#undef HAVE_REGCOMP
+
+#undef HAVE_STRTOD
+#undef HAVE_STRTOL
+#undef HAVE_STRTOLL
+#undef HAVE_STRTOULL
+#undef HAVE_STRNDUP
+#undef HAVE_STRCHRNUL
+
+#undef HAVE_DLOPEN
+#undef HAVE_FPCLASSIFY
+#undef HAVE_ISNAN
+#undef HAVE_ISNANF
+#undef HAVE_FLOG10
+#undef HAVE_POW
+#undef HAVE_DIRNAME
+#undef HAVE_BASENAME
+#undef HAVE_SYSINFO
+#undef HAVE_TRACE_BACK_STACK
+#undef HAVE_BACKTRACE
+#undef HAVE_READDIR64
+
+/* if termio signals are supported */
+#ifdef HAVE_TERMIOS_H
+#ifdef HAVE_TCGETATTR
+#define HAVE_TERMIO_SIGNALS
+#endif
+#endif
+
+/* if the /proc pseudo filesystem exists */
+#undef HAVE_PROCFS
+
+/* Infiniband API version information */
+#undef HAVE_PORT_PERFORMANCE_QUERY_VIA
+#undef HAVE_PMA_QUERY_VIA
+
+#ifndef ULONGLONG_MAX
+#define ULONGLONG_MAX (__uint64_t)18446744073709551615ULL
+#endif
+
+#ifndef LONGLONG_MAX
+#define LONGLONG_MAX (__int64_t)9223372036854775807LL
+#endif
+
+/* some types only known by some compilers */
+#undef uint_t
+#undef __int32_t
+#undef __uint32_t
+#undef __int64_t
+#undef __uint64_t
+
+/* Check if __psint_t is set to something meaningful */
+#undef HAVE___PSINT_T
+#ifndef HAVE___PSINT_T
+#ifdef HAVE_32BIT_PTR
+typedef int __psint_t;
+#elif defined HAVE_64BIT_PTR
+#ifdef HAVE_64BIT_LONG
+typedef long __psint_t;
+#else
+/* This is a very strange architecture, which has 64 bit pointers but
+ * not 64 bit longs. So, I'd just punt here and assume long long is Ok */
+typedef long long __psint_t;
+#endif
+#else
+#error Unknown pointer size - not 32 and not 64
+#endif
+#endif
+
+/* Check if ptrdiff_t type is available */
+#undef HAVE_PTRDIFF_T
+#ifndef HAVE_PTRDIFF_T
+#define ptrdiff_t long
+#endif
+
+/*
+ * User and group accounts - POSIX uid_t/gid_t combo or Win32 SID
+ */
+#undef HAVE_UID_T
+#undef HAVE_GID_T
+#undef HAVE_SID
+#if defined(HAVE_UID_T) && defined(HAVE_GID_T)
+typedef uid_t __pmUserID;
+typedef gid_t __pmGroupID;
+#elif defined(HAVE_SID)
+typedef SID __pmUserID;
+typedef SID __pmGroupID;
+#else
+bozo! unclear how to represent users and groups for this platform
+#endif
+
+/*
+ * socklen_t is not always defined, so use __pmSockLen abstraction
+ */
+#undef HAVE_SOCKLEN_T
+#ifdef HAVE_SOCKLEN_T
+#include <sys/types.h>
+#include <sys/socket.h>
+typedef socklen_t __pmSockLen;
+#else
+typedef int __pmSockLen;
+#endif
+
+/*
+ * MAXNAMELEN hides in may places and may have alias names ...
+ */
+#ifndef MAXNAMELEN
+#include <stdio.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#if !defined(MAXNAMELEN) && defined(FILENAME_MAX)
+/* posix version of the bsd MAXNAMELEN macro */
+#define MAXNAMELEN FILENAME_MAX
+#endif
+#endif
+#ifndef MAXNAMELEN
+bozo! need to find where MAXNAMELEN is defined for this platform
+#endif
+
+/*
+ * MAXPATHLEN hides in many places and may have alias names ...
+ */
+#ifndef MAXPATHLEN
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#if !defined(MAXPATHLEN) && defined(PATH_MAX)
+/* posix version of the bsd MAXPATHLEN macro */
+#define MAXPATHLEN PATH_MAX
+#endif
+#endif
+#ifndef MAXPATHLEN
+/* bozo! need to find where MAXPATHLEN is defined for this platform */
+#define PATH_MAX 4096
+#define MAXPATHLEN PATH_MAX
+#endif
+
+/*
+ * MAXHOSTNAMELEN hides in many places and may also have aliases ...
+ */
+#ifndef MAXHOSTNAMELEN
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#endif
+#ifndef MAXHOSTNAMELEN
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#endif
+#ifndef MAXHOSTNAMELEN
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#endif
+#ifndef MAXHOSTNAMELEN
+#ifdef HAVE_IPTYPES_H
+#include <iptypes.h>
+#define MAXHOSTNAMELEN MAX_HOSTNAME_LEN
+#endif
+#endif
+#ifndef MAXHOSTNAMELEN
+/* bozo! need to find where MAXHOSTNAMELEN is defined for this platform*/
+#define MAXHOSTNAMELEN 4096
+#endif
+
+#ifndef HAVE_FLOG10
+#if !defined(flog10)
+#define flog10(x) (float)log10((double)x)
+#endif
+#endif
+
+#if !defined(WORD_BIT)
+#define WORD_BIT 32
+#endif
+
+#undef RETSIGTYPE
+#ifndef RETSIGTYPE
+#define RETSIGTYPE void
+#endif
+
+#undef HAVE_SIGPF
+#ifndef HAVE_SIGPF
+/* The return type of signal() */
+typedef void (*SIG_PF) (int);
+#endif
+
+#undef HAVE_SA_SIGINFO
+#undef HAVE_SIGPIPE
+#undef HAVE_SIGHUP
+
+#undef HAVE_WAIT_INCLUDES_SIGNAL
+#ifndef HAVE_WAIT_INCLUDES_SIGNAL
+#include <signal.h>
+#endif
+
+#undef HAVE_PR_TERMCHILD
+#undef HAVE_PR_SET_PDEATHSIG
+
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+
+/* thread support options */
+#undef HAVE_PTHREAD_MUTEX_T
+#undef HAVE_PTHREAD_BARRIER_T
+#undef HAVE___THREAD
+
+#undef HAVE_FNDELAY
+#if !defined(HAVE_FNDELAY)
+/* Only Solaris is known to need this so far */
+#ifndef FNDELAY
+#define FNDELAY O_NDELAY
+#endif
+#endif
+
+#undef HAVE_ALTZONE
+#undef HAVE_STRFTIME_z
+#undef HAVE_STRERROR_R_PTR
+
+#undef HAVE_STRUCT_TIMESPEC
+#undef HAVE_STRUCT_SOCKADDR_UN
+#undef HAVE_STRUCT_UCRED
+
+#ifndef HAVE_VALLOC
+#define valloc(x) malloc(x)
+#endif
+
+/* Determine if we are on a Linux box */
+#undef IS_LINUX
+
+/* Determine if we are on a Solaris box */
+#undef IS_SOLARIS
+
+/* Determine if we are on an AIX box */
+#undef IS_AIX
+
+/* Determine if we are on a FreeBSD box */
+#undef IS_FREEBSD
+
+/* Determine if we are on a GNU box */
+#undef IS_GNU
+
+/* Determine if we are on a NetBSD box */
+#undef IS_NETBSD
+
+/* Determine if we are on a Mac OS X box */
+#undef IS_DARWIN
+#ifdef IS_DARWIN
+#define DLOPEN_NO_WARN
+#define st_atim st_atimespec /* workaround */
+#define st_mtim st_mtimespec /* workaround */
+#define st_ctim st_ctimespec /* workaround */
+#endif
+
+/* Determine if we are on Windows with MinGW compiler */
+#undef IS_MINGW
+#ifdef IS_MINGW
+
+#ifdef PCP_VERSION /* used to reduce namespace pollution */
+#define EHOSTDOWN WSAEHOSTDOWN
+#define ENODATA WSANO_DATA
+extern const char *wsastrerror(int);
+
+#define HAVE_PIPE1
+#define HAVE_MKDIR2
+#define HAVE_RENAME2
+#define HAVE_DLOPEN
+#define HAVE_FNDELAY
+
+#define MAP_FAILED NULL
+#define O_NDELAY 0
+#define SIGHUP (NSIG+1)
+#define SIGUSR1 (NSIG+2)
+#define SIGBUS (NSIG+3)
+#define S_IRGRP 0
+#define S_IWGRP 0
+#define S_IROTH 0
+#define S_IWOTH 0
+#define S_IRWXG 0
+#define S_IRWXO 0
+#define S_ISVTX 0
+
+#define fcntl(f, cmd, ...) 0
+#define mkdir2(path, mode) mkdir(path)
+#define rename2(a, b) (unlink(b), rename(a,b))
+#define realpath(path, pp) strcpy(pp, path)
+#define pipe1(fds) _pipe(fds, 4096, O_BINARY)
+
+extern void setlinebuf(FILE *);
+extern char *index(const char *, int);
+extern char *rindex(const char *, int);
+extern char *strcasestr(const char *, const char *);
+extern long int lrand48(void);
+extern void srand48(long int);
+
+#ifdef HAVE_STRUCT_TIMESPEC
+/*
+ * This is a bit odd ... but for MinGW, struct timespec is not in
+ * <time.h> but _is_ in <pthread.h> ... the structure (sec, nanosec)
+ * is what we want, so include <pthread.h>
+ */
+#include <pthread.h>
+#endif
+
+extern int nanosleep(const struct timespec *, struct timespec *);
+extern unsigned int sleep(unsigned int);
+
+enum { RTLD_NOW, RTLD_LAZY };
+extern void *dlopen(const char *, int);
+extern char *dlerror(void);
+extern void *dlsym(void *, const char *);
+extern int dlclose(void *);
+
+extern void openlog(const char *, int, int);
+extern void syslog(int, const char *, ...);
+extern void closelog(void);
+#endif
+enum { LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR,
+ LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG,
+ LOG_PID, LOG_CONS, LOG_DAEMON };
+
+#ifdef LIBPCP_INTERNAL
+#define INTERN __declspec(dllexport)
+#define EXTERN
+#else
+#define INTERN
+#define EXTERN __declspec(dllimport)
+#endif
+
+#define setoserror(n) (errno = (n)) /* not SetLastError() */
+#define oserror() errno /* not GetLastError() */
+#define neterror() WSAGetLastError()
+#define hosterror() WSAGetLastError()
+#define osstrerror() strerror(GetLastError())
+#define osstrerror_r(buf, len) pmErrStr_r(-GetLastError(), buf, len)
+#define netstrerror() strerror(WSAGetLastError())
+#define netstrerror_r(buf, len) pmErrStr_r(-WSAGetLastError(), buf, len)
+#define hoststrerror() strerror(WSAGetLastError())
+
+#else /*!MINGW*/
+#define INTERN
+#define EXTERN extern
+
+#define setoserror(n) (errno = (n))
+#define oserror() errno
+#define neterror() errno
+#define hosterror() h_errno
+#define osstrerror() strerror(errno)
+#define osstrerror_r(buf, len) pmErrStr_r(-errno, buf, len)
+#define netstrerror() strerror(errno)
+#define netstrerror_r(buf, len) pmErrStr_r(-errno, buf, len)
+#define hoststrerror() hstrerror(h_errno)
+#endif
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+#ifndef FD_CLOEXEC
+#define FD_CLOEXEC 0
+#endif
+
+/*
+ * run-time environment that is in libc for most platforms, but for some
+ * we need to provide our own implementation
+ */
+#ifndef HAVE_DIRNAME
+extern char *dirname(char *);
+#endif
+#ifndef HAVE_BASENAME
+extern char *basename(char *);
+#endif
+#ifndef HAVE_STRNDUP
+extern char *strndup(const char *, size_t);
+#endif
+#ifndef HAVE_STRCHRNUL
+extern char *strchrnul(const char *, int);
+#endif
+#ifndef HAVE_STRCHRNUL
+extern char *strchrnul(const char *, int c);
+#endif
+
+#ifdef HAVE_CONST_DIRENT
+#define const_dirent const struct dirent
+#else
+#define const_dirent struct dirent
+#endif
+
+#ifndef HAVE_SCANDIR
+struct dirent;
+extern int scandir(const char *, struct dirent ***,
+ int(*filter)(const_dirent *),
+ int(*compare)(const_dirent **, const_dirent **));
+extern int alphasort(const_dirent **, const_dirent **);
+#endif
+
+#ifndef HAVE_MKDIR2
+#define mkdir2(path,mode) mkdir(path,mode)
+#endif
+
+#ifndef HAVE_RENAME2
+#define rename2(path,target) rename(path,target)
+#endif
+
+#ifndef HAVE_PIPE1
+#define pipe1(fds) pipe(fds)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PCP_CONFIG_H */
diff --git a/src/include/pcp/config32.h b/src/include/pcp/config32.h
new file mode 100644
index 0000000..64eba4c
--- /dev/null
+++ b/src/include/pcp/config32.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PCP_CONFIG32_H
+#define _PCP_CONFIG32_H
+
+/* #undef HAVE_64BIT_LONG */
+#define HAVE_32BIT_LONG 1
+#define HAVE_32BIT_PTR 1
+/* #undef HAVE_64BIT_PTR */
+
+#endif /* _PCP_CONFIG32_H */
diff --git a/src/include/pcp/config64.h b/src/include/pcp/config64.h
new file mode 100644
index 0000000..4bae2ee
--- /dev/null
+++ b/src/include/pcp/config64.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PCP_CONFIG64_H
+#define _PCP_CONFIG64_H
+
+#define HAVE_64BIT_LONG 1
+/* #undef HAVE_32BIT_LONG */
+/* #undef HAVE_32BIT_PTR */
+#define HAVE_64BIT_PTR 1
+
+#endif /* _PCP_CONFIG64_H */
diff --git a/src/include/pcp/configsz.h.in b/src/include/pcp/configsz.h.in
new file mode 100644
index 0000000..4a06e40
--- /dev/null
+++ b/src/include/pcp/configsz.h.in
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PCP_CONFIGSZ_H
+#define _PCP_CONFIGSZ_H
+
+/* long and pointer must be either 32 bit or 64 bit */
+#undef HAVE_64BIT_LONG
+#undef HAVE_32BIT_LONG
+#undef HAVE_32BIT_PTR
+#undef HAVE_64BIT_PTR
+
+#endif /* _PCP_CONFIGSZ_H */
diff --git a/src/include/pcp/fault.h b/src/include/pcp/fault.h
new file mode 100644
index 0000000..511609f
--- /dev/null
+++ b/src/include/pcp/fault.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#ifndef _FAULT_H
+#define _FAULT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Routines to support fault injection infrastructure
+ *
+ * Build libpcp with -DPM_FAULT_INJECTION to enable all of this.
+ */
+extern void __pmFaultInject(const char *, int);
+extern void __pmFaultSummary(FILE *f);
+
+#ifdef PM_FAULT_INJECTION
+extern int __pmFault_arm;
+#define PM_FAULT_POINT(ident, class) __pmFaultInject(ident, class)
+#ifdef malloc
+#undef malloc
+#endif
+#define malloc(x) __pmFault_malloc(x)
+extern void *__pmFault_malloc(size_t);
+#ifdef realloc
+#undef realloc
+#endif
+#define realloc(x,y) __pmFault_realloc(x,y)
+extern void *__pmFault_realloc(void *, size_t);
+#ifdef strdup
+#undef strdup
+#endif
+#define strdup(x) __pmFault_strdup(x)
+extern char *__pmFault_strdup(const char *);
+#define PM_FAULT_CHECK(class) if (__pmFault_arm == PM_FAULT_PMAPI) { __pmFault_arm = 0; return PM_ERR_FAULT; }
+#else
+#define PM_FAULT_POINT(ident, class)
+#define PM_FAULT_CHECK(class)
+#endif
+
+/*
+ * Classes of fault types (second arg to __pmFaultInject())
+ */
+#define PM_FAULT_ALLOC 100
+#define PM_FAULT_PMAPI 101
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FAULT_H */
diff --git a/src/include/pcp/impl.h b/src/include/pcp/impl.h
new file mode 100644
index 0000000..c139fe1
--- /dev/null
+++ b/src/include/pcp/impl.h
@@ -0,0 +1,1488 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _IMPL_H
+#define _IMPL_H
+
+#include <time.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+/*
+ * Thread-safe support ... #define to enable thread-safe protection of
+ * global data structures and mutual exclusion when required.
+ *
+ * We require pthread.h and working mutex, the rest can be faked
+ * by the libpcp itself.
+ */
+#if defined(HAVE_PTHREAD_H) && defined(HAVE_PTHREAD_MUTEX_T)
+#define PM_MULTI_THREAD 1
+#include <pthread.h>
+typedef pthread_mutex_t __pmMutex;
+#else
+typedef void * __pmMutex;
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This defines the routines, macros and data structures that are used
+ * in the Performance Metrics Collection Subsystem (PMCS) below the
+ * PMAPI.
+ */
+
+/*
+ * internal libpcp state ... PM_STATE_APPL means we are at or above the
+ * PMAPI in a state where PMAPI calls can safely be made ... PM_STATE_PMCS
+ * means we are in the PMCD, or a PMDA, or low-level PDU code, and
+ * PMAPI calls are a bad idea.
+ */
+#define PM_STATE_APPL 0
+#define PM_STATE_PMCS 1
+
+extern void __pmSetInternalState(int);
+extern int __pmGetInternalState(void);
+
+/*
+ * PMCD connections come here by default, over-ride with $PMCD_PORT in
+ * environment
+ */
+#define SERVER_PORT 44321
+#define SERVER_PROTOCOL "pcp"
+/*
+ * port that clients connect to pmproxy(1) on by default, over-ride with
+ * $PMPROXY_PORT in environment
+ */
+#define PROXY_PORT 44322
+#define PROXY_PROTOCOL "proxy"
+
+/*
+ * port that clients connect to pmwebd(1) by default
+ */
+#define PMWEBD_PORT 44323
+#define PMWEBD_PROTOCOL "http"
+
+/*
+ * Internally, this is how to decode a PMID!
+ * - flag is to denote state internally in some operations
+ * - domain is usually the unique domain number of a PMDA, but DYNAMIC_PMID
+ * (number 511) is reserved for PMIDs representing the root of a
+ * dynamic subtree in the PMNS (and in this case the real domain number
+ * is encoded in the cluster field)
+ * - cluster and item together uniquely identify a metric within a domain
+ */
+#define DYNAMIC_PMID 511
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int flag : 1;
+ unsigned int domain : 9;
+ unsigned int cluster : 12;
+ unsigned int item : 10;
+#else
+ unsigned int item : 10;
+ unsigned int cluster : 12;
+ unsigned int domain : 9;
+ unsigned int flag : 1;
+#endif
+} __pmID_int;
+
+static inline __pmID_int *
+__pmid_int(pmID *idp)
+{
+ /* avoid gcc's warning about dereferencing type-punned pointers */
+ return (__pmID_int *)idp;
+}
+
+static inline unsigned int
+pmid_item(pmID id)
+{
+ return __pmid_int(&id)->item;
+}
+
+static inline unsigned int
+pmid_cluster(pmID id)
+{
+ return __pmid_int(&id)->cluster;
+}
+
+static inline unsigned int
+pmid_domain(pmID id)
+{
+ return __pmid_int(&id)->domain;
+}
+
+static inline pmID
+pmid_build(unsigned int domain, unsigned int cluster, unsigned int item)
+{
+ pmID id;
+ __pmID_int idint;
+
+ idint.flag = 0;
+ idint.domain = domain;
+ idint.cluster = cluster;
+ idint.item = item;
+ memcpy(&id, &idint, sizeof(id));
+ return id;
+}
+
+/*
+ * Internally, this is how to decode an Instance Domain Identifier
+ * - flag is to denote state internally in some operations
+ * - domain is usually the unique domain number of a PMDA, but DYNAMIC_PMID
+ * (number 511) is reserved (see above for PMID encoding rules)
+ * - serial uniquely identifies an InDom within a domain
+ */
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ int flag : 1;
+ unsigned int domain : 9;
+ unsigned int serial : 22;
+#else
+ unsigned int serial : 22;
+ unsigned int domain : 9;
+ int flag : 1;
+#endif
+} __pmInDom_int;
+
+static inline __pmInDom_int *
+__pmindom_int(pmInDom *idp)
+{
+ /* avoid gcc's warning about dereferencing type-punned pointers */
+ return (__pmInDom_int *)idp;
+}
+
+static inline unsigned int
+pmInDom_domain(pmInDom id)
+{
+ return __pmindom_int(&id)->domain;
+}
+
+static inline unsigned int
+pmInDom_serial(pmInDom id)
+{
+ return __pmindom_int(&id)->serial;
+}
+
+static inline pmInDom
+pmInDom_build(unsigned int domain, unsigned int serial)
+{
+ pmInDom ind;
+ __pmInDom_int indint;
+
+ indint.flag = 0;
+ indint.domain = domain;
+ indint.serial = serial;
+ memcpy(&ind, &indint, sizeof(ind));
+ return ind;
+}
+
+/*
+ * internal structure of a PMNS node
+ */
+typedef struct __pmnsNode {
+ struct __pmnsNode *parent;
+ struct __pmnsNode *next;
+ struct __pmnsNode *first;
+ struct __pmnsNode *hash; /* used as "last" in build, then pmid hash synonym */
+ char *name;
+ pmID pmid;
+} __pmnsNode;
+
+/*
+ * internal structure of a PMNS tree
+ */
+typedef struct __pmnsTree {
+ __pmnsNode *root; /* root of tree structure */
+ __pmnsNode **htab; /* hash table of nodes keyed on pmid */
+ int htabsize; /* number of nodes in the table */
+ char *symbol; /* store all names contiguously */
+ int contiguous; /* is data stored contiguously ? */
+ int mark_state; /* the total mark value for trimming */
+} __pmnsTree;
+
+
+/* used by pmnsmerge... */
+extern __pmnsTree *__pmExportPMNS(void);
+
+/* for PMNS in archives */
+extern int __pmNewPMNS(__pmnsTree **);
+extern void __pmFreePMNS(__pmnsTree *);
+extern void __pmUsePMNS(__pmnsTree *); /* for debugging */
+extern int __pmFixPMNSHashTab(__pmnsTree *, int, int);
+extern int __pmAddPMNSNode(__pmnsTree *, int, const char *);
+
+
+/* return true if the named pmns file has changed */
+extern int __pmHasPMNSFileChanged(const char *);
+
+/* standard log file set up */
+extern FILE *__pmOpenLog(const char *, const char *, FILE *, int *);
+extern FILE *__pmRotateLog(const char *, const char *, FILE *, int *);
+/* make __pmNotifyErr also add entries to syslog */
+extern void __pmSyslog(int);
+/* standard error, warning and info wrapper for syslog(3C) */
+extern void __pmNotifyErr(int, const char *, ...) __PM_PRINTFLIKE(2,3);
+
+/*
+ * These are for debugging only (but are present in the shipped libpcp)
+ */
+EXTERN int pmDebug;
+#define DBG_TRACE_PDU 1 /* PDU send and receive */
+#define DBG_TRACE_FETCH 2 /* dump pmFetch results */
+#define DBG_TRACE_PROFILE 4 /* trace profile changes */
+#define DBG_TRACE_VALUE 8 /* metric value conversions */
+#define DBG_TRACE_CONTEXT 16 /* trace PMAPI context changes */
+#define DBG_TRACE_INDOM 32 /* instance domain operations */
+#define DBG_TRACE_PDUBUF 64 /* PDU buffer management */
+#define DBG_TRACE_LOG 128 /* generic archive log operations */
+#define DBG_TRACE_LOGMETA (1<<8) /* meta data in archives */
+#define DBG_TRACE_OPTFETCH (1<<9) /* optFetch tracing */
+#define DBG_TRACE_AF (1<<10) /* trace async timer events */
+#define DBG_TRACE_APPL0 (1<<11) /* reserved for applications */
+#define DBG_TRACE_APPL1 (1<<12) /* reserved for applications */
+#define DBG_TRACE_APPL2 (1<<13) /* reserved for applications */
+#define DBG_TRACE_PMNS (1<<14) /* PMNS operations */
+#define DBG_TRACE_LIBPMDA (1<<15) /* libpcp_pmda */
+#define DBG_TRACE_TIMECONTROL (1<<16) /* time control api */
+#define DBG_TRACE_PMC (1<<17) /* metrics class */
+#define DBG_TRACE_DERIVE (1<<18) /* derived metrics */
+#define DBG_TRACE_LOCK (1<<19) /* lock tracing */
+#define DBG_TRACE_INTERP (1<<20) /* interpolate mode for archives */
+#define DBG_TRACE_CONFIG (1<<21) /* configuration parameters */
+#define DBG_TRACE_LOOP (1<<22) /* pmLoop tracing */
+#define DBG_TRACE_FAULT (1<<23) /* fault injection tracing */
+#define DBG_TRACE_AUTH (1<<24) /* authentication tracing */
+#define DBG_TRACE_DISCOVERY (1<<25) /* service discovery tracing */
+/* not yet allocated, bits (1<<26) ... (1<<29) */
+#define DBG_TRACE_DESPERATE (1<<30) /* verbose/desperate level */
+
+extern int __pmParseDebug(const char *);
+extern void __pmDumpResult(FILE *, const pmResult *);
+extern void __pmDumpHighResResult(FILE *, const pmHighResResult *);
+extern void __pmPrintStamp(FILE *, const struct timeval *);
+extern void __pmPrintHighResStamp(FILE *, const struct timespec *);
+extern void __pmPrintTimespec(FILE *, const __pmTimespec *);
+extern void __pmPrintTimeval(FILE *, const __pmTimeval *);
+extern void __pmPrintDesc(FILE *, const pmDesc *);
+extern void __pmFreeResultValues(pmResult *);
+extern char *__pmPDUTypeStr_r(int, char *, int);
+extern const char *__pmPDUTypeStr(int); /* NOT thread-safe */
+extern void __pmDumpNameSpace(FILE *, int);
+extern void __pmDumpNameNode(FILE *, __pmnsNode *, int);
+extern void __pmDumpStack(FILE *);
+EXTERN int __pmLogReads;
+
+#ifdef PCP_DEBUG
+extern void __pmDumpIDList(FILE *, int, const pmID *);
+extern void __pmDumpNameList(FILE *, int, char **);
+extern void __pmDumpStatusList(FILE *, int, const int *);
+extern void __pmDumpNameAndStatusList(FILE *, int, char **, int *);
+#endif
+
+/*
+ * Logs and archives of performance metrics (not to be confused
+ * with diagnostic logs for error messages, etc.)
+ *
+ * __pmLogCtl log control
+ * __pmLogTI temporal index record
+ */
+
+/*
+ * Hashed Data Structures for the Processing of Logs and Archives
+ */
+typedef struct __pmHashNode {
+ struct __pmHashNode *next;
+ unsigned int key;
+ void *data;
+} __pmHashNode;
+
+typedef struct __pmHashCtl {
+ int nodes;
+ int hsize;
+ __pmHashNode **hash;
+ __pmHashNode *next;
+ unsigned int index;
+} __pmHashCtl;
+
+typedef enum {
+ PM_HASH_WALK_START = 0,
+ PM_HASH_WALK_NEXT,
+ PM_HASH_WALK_STOP,
+ PM_HASH_WALK_DELETE_NEXT,
+ PM_HASH_WALK_DELETE_STOP,
+} __pmHashWalkState;
+
+extern void __pmHashInit(__pmHashCtl *);
+typedef __pmHashWalkState(*__pmHashWalkCallback)(const __pmHashNode *, void *);
+extern void __pmHashWalkCB(__pmHashWalkCallback, void *, const __pmHashCtl *);
+extern __pmHashNode *__pmHashWalk(__pmHashCtl *, __pmHashWalkState);
+extern __pmHashNode *__pmHashSearch(unsigned int, __pmHashCtl *);
+extern int __pmHashAdd(unsigned int, void *, __pmHashCtl *);
+extern int __pmHashDel(unsigned int, void *, __pmHashCtl *);
+extern void __pmHashClear(__pmHashCtl *);
+
+/*
+ * External file and internal (below PMAPI) format for an archive label
+ * Note: int is OK here, because configure ensures int is a 32-bit integer
+ */
+typedef struct {
+ int ill_magic; /* PM_LOG_MAGIC | log format version no. */
+ int ill_pid; /* PID of logger */
+ __pmTimeval ill_start; /* start of this log */
+ int ill_vol; /* current log volume no. */
+ char ill_hostname[PM_LOG_MAXHOSTLEN];/* name of collection host */
+ char ill_tz[PM_TZ_MAXLEN]; /* $TZ at collection host */
+} __pmLogLabel;
+
+/*
+ * unfortunately, in this version, PCP archives are limited to no
+ * more than 2 Gbytes ...
+ */
+typedef __uint32_t __pm_off_t;
+
+/*
+ * Temporal Index Record
+ * Note: int is OK here, because configure ensures int is a 32-bit integer
+ */
+typedef struct {
+ __pmTimeval ti_stamp; /* now */
+ int ti_vol; /* current log volume no. */
+ __pm_off_t ti_meta; /* end of meta data file */
+ __pm_off_t ti_log; /* end of metrics log file */
+} __pmLogTI;
+
+/*
+ * Log/Archive Control
+ */
+typedef struct {
+ int l_refcnt; /* number of contexts using this log */
+ char *l_name; /* external log base name */
+ FILE *l_tifp; /* temporal index */
+ FILE *l_mdfp; /* meta data */
+ FILE *l_mfp; /* current metrics log */
+ int l_curvol; /* current metrics log volume no. */
+ int l_state; /* (when writing) log state */
+ __pmHashCtl l_hashpmid; /* PMID hashed access */
+ __pmHashCtl l_hashindom; /* instance domain hashed access */
+ __pmHashCtl l_hashrange; /* ptr to first and last value in log for */
+ /* each metric */
+ int l_minvol; /* (when reading) lowest known volume no. */
+ int l_maxvol; /* (when reading) highest known volume no. */
+ int l_numseen; /* (when reading) size of l_seen */
+ int *l_seen; /* (when reading) volumes opened OK */
+ __pmLogLabel l_label; /* (when reading) log label */
+ __pm_off_t l_physend; /* (when reading) offset to physical EOF */
+ /* for last volume */
+ __pmTimeval l_endtime; /* (when reading) timestamp at logical EOF */
+ int l_numti; /* (when reading) no. temporal index entries */
+ __pmLogTI *l_ti; /* (when reading) temporal index */
+ __pmnsTree *l_pmns; /* namespace from meta data */
+} __pmLogCtl;
+
+/* l_state values */
+#define PM_LOG_STATE_NEW 0
+#define PM_LOG_STATE_INIT 1
+
+/*
+ * Return the argument if it's a valid filename else return NULL
+ * (note: this function could be replaced with a call to access(),
+ * but is retained for historical reasons).
+ */
+extern const char *__pmFindPMDA(const char *);
+
+/*
+ * Internal instance profile states
+ */
+#define PM_PROFILE_INCLUDE 0 /* include all, exclude some */
+#define PM_PROFILE_EXCLUDE 1 /* exclude all, include some */
+
+/* Profile entry (per instance domain) */
+typedef struct __pmInDomProfile {
+ pmInDom indom; /* instance domain */
+ int state; /* include all or exclude all */
+ int instances_len; /* length of instances array */
+ int *instances; /* array of instances */
+} __pmInDomProfile;
+
+/* Instance profile for all domains */
+typedef struct __pmProfile {
+ int state; /* default global state */
+ int profile_len; /* length of profile array */
+ __pmInDomProfile *profile; /* array of instance profiles */
+} __pmProfile;
+
+/*
+ * Dump the instance profile, for a particular instance domain
+ * If indom == PM_INDOM_NULL, then print all instance domains
+ */
+extern void __pmDumpProfile(FILE *, int, const __pmProfile *);
+
+/*
+ * Result structure for instance domain queries
+ * Only the PMDAs and pmcd need to know about this.
+ */
+typedef struct __pmInResult {
+ pmInDom indom; /* instance domain */
+ int numinst; /* may be 0 */
+ int *instlist; /* instance ids, may be NULL */
+ char **namelist; /* instance names, may be NULL */
+} __pmInResult;
+extern void __pmDumpInResult(FILE *, const __pmInResult *);
+
+/* instance profile methods */
+extern int __pmProfileSetSent(void);
+extern void __pmFreeProfile(__pmProfile *);
+extern __pmInDomProfile *__pmFindProfile(pmInDom, const __pmProfile *);
+extern int __pmInProfile(pmInDom, const __pmProfile *, int);
+extern void __pmFreeInResult(__pmInResult *);
+
+/*
+ * Version and capabilities information for PDU exchanges
+ */
+
+#define UNKNOWN_VERSION 0
+#define PDU_VERSION2 2
+#define PDU_VERSION PDU_VERSION2
+
+#define PDU_OVERRIDE2 -1002
+
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int zero : 1; /* ensure this is zero for 1.x compatibility */
+ unsigned int version : 7; /* PDU_VERSION collector protocol preference */
+ unsigned int licensed : 8; /* ensure this is one for 2.x compatibility */
+ unsigned int features : 16; /* advertised (enabled) collector features */
+#else
+ unsigned int features : 16;
+ unsigned int licensed : 8;
+ unsigned int version : 7;
+ unsigned int zero : 1;
+#endif
+} __pmPDUInfo;
+
+/*
+ * Host specification allowing one or more pmproxy host, and port numbers
+ * within the one string, i.e. pmcd host specifications of the form:
+ * host:port,port@proxy:port,port
+ */
+typedef struct {
+ char *name; /* hostname (always valid) */
+ int *ports; /* array of host port numbers */
+ int nports; /* number of ports in host port array */
+} pmHostSpec;
+
+extern int __pmParseHostSpec(const char *, pmHostSpec **, int *, char **);
+extern int __pmUnparseHostSpec(pmHostSpec *, int, char *, size_t);
+extern void __pmFreeHostSpec(pmHostSpec *, int);
+
+typedef enum {
+ PCP_ATTR_NONE = 0,
+ PCP_ATTR_PROTOCOL = 1, /* either pcp:/pcps: protocol (libssl) */
+ PCP_ATTR_SECURE = 2, /* relaxed/enforced pcps mode (libssl) */
+ PCP_ATTR_COMPRESS = 3, /* compression flag, no value (libnss) */
+ PCP_ATTR_USERAUTH = 4, /* user auth flag, no value (libsasl) */
+ PCP_ATTR_USERNAME = 5, /* user login identity (libsasl) */
+ PCP_ATTR_AUTHNAME = 6, /* authentication name (libsasl) */
+ PCP_ATTR_PASSWORD = 7, /* passphrase-based secret (libsasl) */
+ PCP_ATTR_METHOD = 8, /* use authentication method (libsasl) */
+ PCP_ATTR_REALM = 9, /* realm to authenticate in (libsasl) */
+ PCP_ATTR_UNIXSOCK = 10, /* AF_UNIX socket + SO_PEERCRED (unix) */
+ PCP_ATTR_USERID = 11, /* uid - user identifier (posix) */
+ PCP_ATTR_GROUPID = 12, /* gid - group identifier (posix) */
+ PCP_ATTR_LOCAL = 13, /* AF_UNIX socket with localhost fallback */
+ PCP_ATTR_PROCESSID = 14, /* pid - process identifier (posix) */
+} __pmAttrKey;
+
+extern __pmAttrKey __pmLookupAttrKey(const char *, size_t);
+extern int __pmAttrKeyStr_r(__pmAttrKey, char *, size_t);
+extern int __pmAttrStr_r(__pmAttrKey, const char *, char *, size_t);
+
+extern int __pmParseHostAttrsSpec(
+ const char *, pmHostSpec **, int *, __pmHashCtl *, char **);
+extern int __pmUnparseHostAttrsSpec(
+ pmHostSpec *, int, __pmHashCtl *, char *, size_t);
+extern void __pmFreeHostAttrsSpec(pmHostSpec *, int, __pmHashCtl *);
+extern void __pmFreeAttrsSpec(__pmHashCtl *);
+
+/*
+ * Control for connection to a PMCD
+ */
+typedef struct {
+ __pmMutex pc_lock; /* mutex pmcd ipc */
+ int pc_refcnt; /* number of contexts using this socket */
+ int pc_fd; /* socket for comm with pmcd */
+ /* ... -1 means no connection */
+ pmHostSpec *pc_hosts; /* pmcd and proxy host specifications */
+ int pc_nhosts; /* number of pmHostSpec entries */
+ int pc_timeout; /* set if connect times out */
+ int pc_tout_sec; /* timeout for __pmGetPDU */
+ time_t pc_again; /* time to try again */
+} __pmPMCDCtl;
+
+extern int __pmConnectPMCD(pmHostSpec *, int, int, __pmHashCtl *);
+extern int __pmConnectLocal(__pmHashCtl *);
+extern int __pmAuxConnectPMCD(const char *);
+extern int __pmAuxConnectPMCDPort(const char *, int);
+extern int __pmAuxConnectPMCDUnixSocket(const char *);
+
+extern int __pmAddHostPorts(pmHostSpec *, int *, int);
+extern void __pmDropHostPort(pmHostSpec *);
+extern void __pmConnectGetPorts(pmHostSpec *);
+
+/*
+ * SSL/TLS/IPv6 support via NSS/NSPR.
+ */
+extern int __pmSecureServerSetup(const char *, const char *);
+extern void __pmSecureServerShutdown(void);
+extern int __pmSecureServerHandshake(int, int, __pmHashCtl *);
+extern int __pmSecureClientHandshake(int, int, const char *, __pmHashCtl *);
+
+typedef fd_set __pmFdSet;
+typedef struct __pmSockAddr __pmSockAddr;
+typedef struct __pmHostEnt __pmHostEnt;
+
+extern int __pmCreateSocket(void);
+extern int __pmCreateIPv6Socket(void);
+extern int __pmCreateUnixSocket(void);
+extern void __pmCloseSocket(int);
+
+extern int __pmSetSockOpt(int, int, int, const void *, __pmSockLen);
+extern int __pmGetSockOpt(int, int, int, void *, __pmSockLen *);
+extern int __pmConnect(int, void *, __pmSockLen);
+extern int __pmBind(int, void *, __pmSockLen);
+extern int __pmListen(int, int);
+extern int __pmAccept(int, void *, __pmSockLen *);
+extern ssize_t __pmWrite(int, const void *, size_t);
+extern ssize_t __pmRead(int, void *, size_t);
+extern ssize_t __pmSend(int, const void *, size_t, int);
+extern ssize_t __pmRecv(int, void *, size_t, int);
+extern int __pmConnectTo(int, const __pmSockAddr *, int);
+extern int __pmConnectCheckError(int);
+extern int __pmConnectRestoreFlags(int, int);
+extern int __pmSocketClosed(void);
+extern int __pmGetFileStatusFlags(int);
+extern int __pmSetFileStatusFlags(int, int);
+extern int __pmGetFileDescriptorFlags(int);
+extern int __pmSetFileDescriptorFlags(int, int);
+
+extern int __pmFD(int);
+extern void __pmFD_CLR(int, __pmFdSet *);
+extern int __pmFD_ISSET(int, __pmFdSet *);
+extern void __pmFD_SET(int, __pmFdSet *);
+extern void __pmFD_ZERO(__pmFdSet *);
+extern void __pmFD_COPY(__pmFdSet *, const __pmFdSet *);
+extern int __pmSelectRead(int, __pmFdSet *, struct timeval *);
+extern int __pmSelectWrite(int, __pmFdSet *, struct timeval *);
+
+extern __pmSockAddr *__pmSockAddrAlloc(void);
+extern void __pmSockAddrFree(__pmSockAddr *);
+extern size_t __pmSockAddrSize(void);
+extern void __pmSockAddrInit(__pmSockAddr *, int, int, int);
+extern int __pmSockAddrCompare(const __pmSockAddr *, const __pmSockAddr *);
+extern __pmSockAddr *__pmSockAddrDup(const __pmSockAddr *);
+extern __pmSockAddr *__pmSockAddrMask(__pmSockAddr *, const __pmSockAddr *);
+extern void __pmSockAddrSetFamily(__pmSockAddr *, int);
+extern int __pmSockAddrGetFamily(const __pmSockAddr *);
+extern void __pmSockAddrSetPort(__pmSockAddr *, int);
+extern int __pmSockAddrGetPort(const __pmSockAddr *);
+extern void __pmSockAddrSetScope(__pmSockAddr *, int);
+extern void __pmSockAddrSetPath(__pmSockAddr *, const char *);
+extern int __pmSockAddrIsLoopBack(const __pmSockAddr *);
+extern int __pmSockAddrIsInet(const __pmSockAddr *);
+extern int __pmSockAddrIsIPv6(const __pmSockAddr *);
+extern int __pmSockAddrIsUnix(const __pmSockAddr *);
+extern char * __pmSockAddrToString(const __pmSockAddr *);
+extern __pmSockAddr *__pmStringToSockAddr(const char *);
+extern __pmSockAddr *__pmLoopBackAddress(int);
+extern __pmSockAddr *__pmSockAddrFirstSubnetAddr(const __pmSockAddr *, int);
+extern __pmSockAddr *__pmSockAddrNextSubnetAddr(__pmSockAddr *, int);
+
+extern __pmHostEnt * __pmHostEntAlloc(void);
+extern void __pmHostEntFree(__pmHostEnt *);
+extern __pmSockAddr *__pmHostEntGetSockAddr(const __pmHostEnt *, void **);
+extern char * __pmHostEntGetName(__pmHostEnt *);
+
+extern __pmHostEnt * __pmGetAddrInfo(const char *);
+extern char * __pmGetNameInfo(__pmSockAddr *);
+
+/*
+ * Query server features - used for expressing protocol capabilities
+ */
+typedef enum {
+ PM_SERVER_FEATURE_SECURE = 0,
+ PM_SERVER_FEATURE_COMPRESS,
+ PM_SERVER_FEATURE_IPV6,
+ PM_SERVER_FEATURE_AUTH,
+ PM_SERVER_FEATURE_CREDS_REQD,
+ PM_SERVER_FEATURE_UNIX_DOMAIN,
+ PM_SERVER_FEATURE_DISCOVERY,
+ PM_SERVER_FEATURES
+} __pmServerFeature;
+
+extern int __pmServerHasFeature(__pmServerFeature);
+extern int __pmServerSetFeature(__pmServerFeature);
+extern int __pmServerClearFeature(__pmServerFeature);
+extern int __pmServerCreatePIDFile(const char *, int);
+extern int __pmServerAddPorts(const char *);
+extern int __pmServerAddInterface(const char *);
+extern void __pmServerSetLocalSocket(const char *);
+extern int __pmServerSetLocalCreds(int, __pmHashCtl *);
+extern void __pmServerSetServiceSpec(const char *);
+typedef void (*__pmServerCallback)(__pmFdSet *, int, int);
+extern void __pmServerAddNewClients(__pmFdSet *, __pmServerCallback);
+extern int __pmServerAddToClientFdSet(__pmFdSet *, int);
+extern int __pmServerOpenRequestPorts(__pmFdSet *, int);
+extern void __pmServerCloseRequestPorts(void);
+extern void __pmServerDumpRequestPorts(FILE *);
+extern char *__pmServerRequestPortString(int, char *, size_t);
+
+/* Service broadcasting, for servers. */
+typedef struct __pmServerPresence __pmServerPresence;
+extern __pmServerPresence *__pmServerAdvertisePresence(const char *, int);
+extern void __pmServerUnadvertisePresence(__pmServerPresence *);
+
+/*
+ * Per-context controls for archives and logs
+ */
+typedef struct {
+ __pmLogCtl *ac_log; /* global logging and archive control */
+ long ac_offset; /* fseek ptr for archives */
+ int ac_vol; /* volume for ac_offset */
+ int ac_serial; /* serial access pattern for archives */
+ __pmHashCtl ac_pmid_hc; /* per PMID controls for INTERP */
+ double ac_end; /* time at end of archive */
+ void *ac_want; /* used in interp.c */
+ void *ac_unbound; /* used in interp.c */
+ void *ac_cache; /* used in interp.c */
+ int ac_cache_idx; /* used in interp.c */
+} __pmArchCtl;
+
+/*
+ * PMAPI context. We keep an array of these,
+ * one for each context created by the application.
+ */
+typedef struct {
+ __pmMutex c_lock; /* mutex for multi-thread access */
+ int c_type; /* HOST, ARCHIVE, LOCAL or FREE */
+ int c_mode; /* current mode PM_MODE_* */
+ __pmPMCDCtl *c_pmcd; /* pmcd control for HOST contexts */
+ __pmArchCtl *c_archctl; /* log control for ARCHIVE contexts */
+ __pmTimeval c_origin; /* pmFetch time origin / current time */
+ int c_delta; /* for updating origin */
+ int c_sent; /* profile has been sent to pmcd */
+ __pmProfile *c_instprof; /* instance profile */
+ void *c_dm; /* derived metrics, if any */
+ int c_flags; /* ctx flags (set via type/env/attrs) */
+ __pmHashCtl c_attrs; /* various optional context attributes */
+} __pmContext;
+
+#define __PM_MODE_MASK 0xffff
+
+#define PM_CONTEXT_FREE -1 /* special type */
+
+/*
+ * Convert opaque context handle to __pmContext pointer
+ */
+extern __pmContext *__pmHandleToPtr(int);
+
+/*
+ * Dump the current context (source details + instance profile),
+ * for a particular instance domain.
+ * If indom == PM_INDOM_NULL, then print all all instance domains
+ */
+extern void __pmDumpContext(FILE *, int, pmInDom);
+
+/*
+ * pmFetch helper routines, providing hooks for derivations.
+ */
+extern int __pmPrepareFetch(__pmContext *, int, const pmID *, pmID **);
+extern int __pmFinishResult(__pmContext *, int, pmResult **);
+
+/*
+ * Protocol data unit support
+ * Note: int is OK here, because configure ensures int is a 32-bit integer
+ */
+typedef struct {
+ int len; /* length of pdu_header + PDU */
+ int type; /* PDU type */
+ int from; /* pid of PDU originator */
+} __pmPDUHdr;
+
+typedef __uint32_t __pmPDU;
+/*
+ * round a size up to the next multiple of a __pmPDU size
+ *
+ * PM_PDU_SIZE is in units of __pmPDU size
+ * PM_PDU_SIZE_BYTES is in units of bytes
+ */
+#define PM_PDU_SIZE(x) (((x)+sizeof(__pmPDU)-1)/sizeof(__pmPDU))
+#define PM_PDU_SIZE_BYTES(x) (sizeof(__pmPDU)*PM_PDU_SIZE(x))
+
+/* Types of credential PDUs (c_type) */
+#define CVERSION 0x1
+
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int c_type: 8; /* Credentials PDU type */
+ unsigned int c_vala: 8;
+ unsigned int c_valb: 8;
+ unsigned int c_valc: 8;
+#else
+ unsigned int c_valc: 8;
+ unsigned int c_valb: 8;
+ unsigned int c_vala: 8;
+ unsigned int c_type: 8;
+#endif
+} __pmCred;
+
+/* Flags for CVERSION credential PDUs, and __pmPDUInfo features */
+#define PDU_FLAG_SECURE (1U<<0)
+#define PDU_FLAG_COMPRESS (1U<<1)
+#define PDU_FLAG_AUTH (1U<<2)
+#define PDU_FLAG_CREDS_REQD (1U<<3)
+#define PDU_FLAG_SECURE_ACK (1U<<4)
+#define PDU_FLAG_NO_NSS_INIT (1U<<5)
+
+/* Credential CVERSION PDU elements look like this */
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int c_type: 8; /* Credentials PDU type */
+ unsigned int c_version: 8; /* PCP protocol version */
+ unsigned int c_flags: 16; /* All feature requests */
+#else
+ unsigned int c_flags: 16;
+ unsigned int c_version: 8;
+ unsigned int c_type: 8;
+#endif
+} __pmVersionCred;
+
+extern int __pmXmitPDU(int, __pmPDU *);
+extern int __pmGetPDU(int, int, int, __pmPDU **);
+extern int __pmGetPDUCeiling(void);
+extern int __pmSetPDUCeiling(int);
+
+EXTERN unsigned int *__pmPDUCntIn;
+EXTERN unsigned int *__pmPDUCntOut;
+extern void __pmSetPDUCntBuf(unsigned *, unsigned *);
+
+/* timeout options for PDU handling */
+#define TIMEOUT_NEVER 0
+#define TIMEOUT_DEFAULT -1
+/*#define TIMEOUT_ASYNC -2*/
+#define TIMEOUT_CONNECT -3
+
+/* mode options for __pmGetPDU */
+#define ANY_SIZE 0 /* replacement for old PDU_BINARY */
+#define LIMIT_SIZE 2 /* replacement for old PDU_CLIENT */
+
+extern __pmPDU *__pmFindPDUBuf(int);
+extern void __pmPinPDUBuf(void *);
+extern int __pmUnpinPDUBuf(void *);
+extern void __pmCountPDUBuf(int, int *, int *);
+
+#define PDU_START 0x7000
+#define PDU_ERROR 0x7000
+#define PDU_RESULT 0x7001
+#define PDU_PROFILE 0x7002
+#define PDU_FETCH 0x7003
+#define PDU_DESC_REQ 0x7004
+#define PDU_DESC 0x7005
+#define PDU_INSTANCE_REQ 0x7006
+#define PDU_INSTANCE 0x7007
+#define PDU_TEXT_REQ 0x7008
+#define PDU_TEXT 0x7009
+#define PDU_CONTROL_REQ 0x700a
+#define PDU_CREDS 0x700c
+#define PDU_PMNS_IDS 0x700d
+#define PDU_PMNS_NAMES 0x700e
+#define PDU_PMNS_CHILD 0x700f
+#define PDU_PMNS_TRAVERSE 0x7010
+#define PDU_AUTH 0x7011
+#define PDU_FINISH 0x7011
+#define PDU_MAX (PDU_FINISH - PDU_START)
+
+/*
+ * Unit of space allocation for PDU buffer.
+ */
+#define PDU_CHUNK 1024
+
+/*
+ * PDU encoding formats
+ * These have been retired ...
+ * #define PDU_BINARY 0
+ * #define PDU_ASCII 1
+ * And this has been replaced by LIMIT_SIZE for __pmGetPDU
+ * #define PDU_CLIENT 2
+ */
+
+/*
+ * Anonymous PDU sender, when context does not matter, e.g. PDUs from
+ * a PMDA sent to PMCD
+ */
+#define FROM_ANON 0
+
+extern int __pmSendError(int, int, int);
+extern int __pmDecodeError(__pmPDU *, int *);
+extern int __pmSendXtendError(int, int, int, int);
+extern int __pmDecodeXtendError(__pmPDU *, int *, int *);
+extern int __pmSendResult(int, int, const pmResult *);
+extern int __pmEncodeResult(int, const pmResult *, __pmPDU **);
+extern int __pmDecodeResult(__pmPDU *, pmResult **);
+extern int __pmSendProfile(int, int, int, __pmProfile *);
+extern int __pmDecodeProfile(__pmPDU *, int *, __pmProfile **);
+extern int __pmSendFetch(int, int, int, __pmTimeval *, int, pmID *);
+extern int __pmDecodeFetch(__pmPDU *, int *, __pmTimeval *, int *, pmID **);
+extern int __pmSendDescReq(int, int, pmID);
+extern int __pmDecodeDescReq(__pmPDU *, pmID *);
+extern int __pmSendDesc(int, int, pmDesc *);
+extern int __pmDecodeDesc(__pmPDU *, pmDesc *);
+extern int __pmSendInstanceReq(int, int, const __pmTimeval *, pmInDom, int, const char *);
+extern int __pmDecodeInstanceReq(__pmPDU *, __pmTimeval *, pmInDom *, int *, char **);
+extern int __pmSendInstance(int, int, __pmInResult *);
+extern int __pmDecodeInstance(__pmPDU *, __pmInResult **);
+extern int __pmSendTextReq(int, int, int, int);
+extern int __pmDecodeTextReq(__pmPDU *, int *, int *);
+extern int __pmSendText(int, int, int, const char *);
+extern int __pmDecodeText(__pmPDU *, int *, char **);
+extern int __pmSendCreds(int, int, int, const __pmCred *);
+extern int __pmDecodeCreds(__pmPDU *, int *, int *, __pmCred **);
+extern int __pmSendIDList(int, int, int, const pmID *, int);
+extern int __pmDecodeIDList(__pmPDU *, int, pmID *, int *);
+extern int __pmSendNameList(int, int, int, char **, const int *);
+extern int __pmDecodeNameList(__pmPDU *, int *, char ***, int **);
+extern int __pmSendChildReq(int, int, const char *, int);
+extern int __pmDecodeChildReq(__pmPDU *, char **, int *);
+extern int __pmSendTraversePMNSReq(int, int, const char *);
+extern int __pmDecodeTraversePMNSReq(__pmPDU *, char **);
+extern int __pmSendAuth(int, int, int, const char *, int);
+extern int __pmDecodeAuth(__pmPDU *, int *, char **, int *);
+
+#if defined(HAVE_64BIT_LONG)
+
+/*
+ * A pmValue contains the union of a 32-bit int and a pointer. In the world
+ * of 64-bit pointers, a pmValue is therefore larger than in the 32-bit world.
+ * The structures below are used in all PDUs containing pmResults to ensure
+ * 32-bit and 64-bit programs exchanging PDUs can communicate.
+ * Note that a pmValue can only hold a 32-bit value in situ regardless of
+ * whether the pointer size is 32 or 64 bits.
+ */
+
+typedef struct {
+ int inst; /* instance identifier */
+ union {
+ unsigned int pval; /* offset into PDU buffer for value */
+ int lval; /* 32-bit value in situ */
+ } value;
+} __pmValue_PDU;
+
+typedef struct {
+ pmID pmid; /* metric identifier */
+ int numval; /* number of values */
+ int valfmt; /* value style */
+ __pmValue_PDU vlist[1]; /* set of instances/values */
+} __pmValueSet_PDU;
+
+#elif defined(HAVE_32BIT_LONG)
+
+/* In the 32-bit world, structures may be used in PDUs as defined */
+
+typedef pmValue __pmValue_PDU;
+typedef pmValueSet __pmValueSet_PDU;
+
+#else
+bozo - unknown size of long !!!
+#endif
+
+/*
+ * For the help text PDUs, the type (PM_TEXT_ONELINE or PM_TEXT_HELP)
+ * is 'or'd with the following to encode the request for a PMID or
+ * a pmInDom ...
+ * Note the values must therefore be (a) bit fields and (b) different
+ * to the public macros PM_TEXT_* in pmapi.h
+ */
+#define PM_TEXT_PMID 4
+#define PM_TEXT_INDOM 8
+
+/*
+ * no mem today, my love has gone away ....
+ */
+extern void __pmNoMem(const char *, size_t, int);
+#define PM_FATAL_ERR 1
+#define PM_RECOV_ERR 0
+
+/*
+ * Startup handling:
+ * set program name, as used in __pmNotifyErr() ... default is "pcp"
+ * set default user for __pmSetProcessIdentity() ... default is "pcp"
+ */
+EXTERN char *pmProgname;
+extern int __pmSetProgname(const char *);
+extern int __pmGetUsername(char **);
+
+/*
+ * Cleanup handling:
+ * shutdown various components in libpcp, releasing all resources
+ * (local context PMDAs, any global NSS socket state, etc).
+ */
+extern int __pmShutdown(void);
+
+/*
+ * Map platform error values to PMAPI error codes.
+ */
+extern int __pmMapErrno(int);
+
+/*
+ * __pmLogInDom is used to hold the instance identifiers for an instance
+ * domain internally ... if multiple sets are observed over time, these
+ * are linked together in reverse chronological order
+ * -- externally we write these as
+ * timestamp
+ * indom <- note, added wrt indom_t
+ * numinst
+ * inst[0], .... inst[numinst-1]
+ * nameindex[0] .... nameindex[numinst-1]
+ * string (name) table, all null-byte terminated
+ *
+ * NOTE: 3 types of allocation
+ * (1)
+ * buf is NULL,
+ * namelist and instlist have been allocated
+ * separately and so must each be freed.
+ * (2)
+ * buf is NOT NULL, allinbuf == 1,
+ * all allocations were in the buffer and so only
+ * the buffer should be freed,
+ * (3)
+ * buf is NOT NULL, allinbuf == 0,
+ * as well as buffer allocation,
+ * the namelist has been allocated separately and so
+ * both the buf and namelist should be freed.
+ */
+typedef struct _indom_t {
+ struct _indom_t *next;
+ __pmTimeval stamp;
+ int numinst;
+ int *instlist;
+ char **namelist;
+ int *buf;
+ int allinbuf;
+} __pmLogInDom;
+
+/*
+ * record header in the metadata log file ... len (by itself) also is
+ * used as a trailer
+ */
+typedef struct {
+ int len; /* record length, includes header and trailer */
+ int type; /* see TYPE_* #defines below */
+} __pmLogHdr;
+
+#define TYPE_DESC 1 /* header, pmDesc, trailer */
+#define TYPE_INDOM 2 /* header, __pmLogInDom, trailer */
+
+extern void __pmLogPutIndex(const __pmLogCtl *, const __pmTimeval *);
+
+extern const char *__pmLogName_r(const char *, int, char *, int);
+extern const char *__pmLogName(const char *, int); /* NOT thread-safe */
+extern FILE *__pmLogNewFile(const char *, int);
+extern int __pmLogCreate(const char *, const char *, int, __pmLogCtl *);
+#define PMLOGREAD_NEXT 0
+#define PMLOGREAD_TO_EOF 1
+extern int __pmLogRead(__pmLogCtl *, int, FILE *, pmResult **, int);
+extern int __pmLogWriteLabel(FILE *, const __pmLogLabel *);
+extern int __pmLogOpen(const char *, __pmContext *);
+extern int __pmLogLoadLabel(__pmLogCtl *, const char *);
+extern int __pmLogLoadIndex(__pmLogCtl *);
+extern int __pmLogLoadMeta(__pmLogCtl *);
+extern void __pmLogClose(__pmLogCtl *);
+extern void __pmLogCacheClear(FILE *);
+
+extern int __pmLogPutDesc(__pmLogCtl *, const pmDesc *, int, char **);
+extern int __pmLogLookupDesc(__pmLogCtl *, pmID, pmDesc *);
+extern int __pmLogPutInDom(__pmLogCtl *, pmInDom, const __pmTimeval *, int, int *, char **);
+extern int __pmLogGetInDom(__pmLogCtl *, pmInDom, __pmTimeval *, int **, char ***);
+extern int __pmLogLookupInDom(__pmLogCtl *, pmInDom, __pmTimeval *, const char *);
+extern int __pmLogNameInDom(__pmLogCtl *, pmInDom, __pmTimeval *, int, char **);
+
+extern int __pmLogPutResult(__pmLogCtl *, __pmPDU *);
+extern int __pmLogPutResult2(__pmLogCtl *, __pmPDU *);
+extern int __pmLogFetch(__pmContext *, int, pmID *, pmResult **);
+extern int __pmLogFetchInterp(__pmContext *, int, pmID *, pmResult **);
+extern void __pmLogSetTime(__pmContext *);
+extern void __pmLogResetInterp(__pmContext *);
+extern void __pmFreeInterpData(__pmContext *);
+
+extern int __pmLogChangeVol(__pmLogCtl *, int);
+extern int __pmLogChkLabel(__pmLogCtl *, FILE *, __pmLogLabel *, int);
+extern int __pmGetArchiveEnd(__pmLogCtl *, struct timeval *);
+
+/* struct for maintaining information about pmlogger ports */
+typedef struct {
+ int pid; /* process id of logger */
+ int port; /* internet port for logger control */
+ char *pmcd_host; /* host pmlogger is collecting from */
+ char *archive; /* archive base pathname */
+ char *name; /* file name (minus dirname) */
+} __pmLogPort;
+
+/* Returns control port info for a pmlogger given its pid.
+ * If pid == PM_LOG_ALL_PIDS, get all pmloggers' control ports.
+ * If pid == PM_LOG_PRIMARY_PID, get primar logger's control port.
+ * Note: do NOT free any part of result returned via the parameter.
+ *
+ * __pmLogFindPort(const char *hostname, int pid, __pmLogPort **result);
+ */
+extern int __pmLogFindPort(const char *, int, __pmLogPort **);
+
+#define PM_LOG_PRIMARY_PID 0 /* symbolic pid for primary logger */
+#define PM_LOG_PRIMARY_PORT 0 /* symbolic port for primary pmlogger */
+#define PM_LOG_ALL_PIDS -1 /* symbolic pid for all pmloggers */
+#define PM_LOG_NO_PID -2 /* not a valid pid for pmlogger */
+#define PM_LOG_NO_PORT -2 /* not a valid port for pmlogger */
+
+extern const char *__pmLogLocalSocketDefault(int, char *buf, size_t bufSize);
+extern const char *__pmLogLocalSocketUser(int, char *buf, size_t bufSize);
+
+/* time utils */
+extern time_t __pmMktime(struct tm *);
+
+/* reverse ctime and time interval parsing */
+extern int __pmParseCtime(const char *, struct tm *, char **);
+extern int __pmConvertTime(struct tm *, struct timeval *, struct timeval *);
+extern int __pmParseTime(const char *, struct timeval *, struct timeval *,
+ struct timeval *, char **);
+
+/* manipulate internal timestamps */
+extern double __pmTimevalSub(const __pmTimeval *, const __pmTimeval *);
+
+/* 32-bit file checksum */
+extern __int32_t __pmCheckSum(FILE *);
+
+/* check for localhost */
+extern int __pmIsLocalhost(const char *);
+
+/*
+ * struct timeval manipulations
+ */
+extern double __pmtimevalAdd(const struct timeval *, const struct timeval *);
+extern double __pmtimevalSub(const struct timeval *, const struct timeval *);
+extern double __pmtimevalToReal(const struct timeval *);
+extern void __pmtimevalFromReal(double, struct timeval *);
+extern void __pmtimevalSleep(struct timeval);
+extern void __pmtimevalPause(struct timeval);
+extern void __pmtimevalNow(struct timeval *);
+
+typedef struct {
+ char *label; /* label to name tz */
+ char *tz; /* env $TZ */
+ int handle; /* handle from pmNewZone() */
+} pmTimeZone;
+
+/*
+ * event tracing for monitoring time between events
+ */
+extern void __pmEventTrace(const char *); /* NOT thread-safe */
+extern void __pmEventTrace_r(const char *, int *, double *, double *);
+
+/*
+ * More IPC protocol stuff
+ */
+
+typedef int (*__pmConnectHostType)(int, int);
+
+extern int __pmSetSocketIPC(int);
+extern int __pmSetVersionIPC(int, int);
+extern int __pmSetDataIPC(int, void *);
+extern int __pmDataIPCSize(void);
+extern int __pmLastVersionIPC(void);
+extern int __pmVersionIPC(int);
+extern int __pmSocketIPC(int);
+extern int __pmDataIPC(int, void *);
+extern void __pmOverrideLastFd(int);
+extern void __pmPrintIPC(void);
+extern void __pmResetIPC(int);
+
+/* safely insert an atom value into a pmValue */
+extern int __pmStuffValue(const pmAtomValue *, pmValue *, int);
+
+/*
+ * Optimized fetch bundling ("optfetch") services
+ */
+typedef struct __optreq {
+ struct __optreq *r_next; /* next request */
+ struct __fetchctl *r_fetch; /* back ptr */
+ pmDesc *r_desc; /* pmDesc for request pmID */
+ int r_numinst; /* request instances */
+ int *r_instlist; /* request instances */
+ void *r_aux; /* generic pointer to aux data */
+} optreq_t;
+
+typedef struct __pmidctl {
+ struct __pmidctl *p_next; /* next pmid control */
+ optreq_t *p_rqp; /* first request for this metric */
+ pmID p_pmid; /* my pmID */
+ int p_numinst; /* union over requests */
+ int *p_instlist; /* union over requests */
+ void *p_aux; /* generic pointer to aux data */
+} pmidctl_t;
+
+typedef struct __indomctl {
+ struct __indomctl *i_next; /* next indom control */
+ pmidctl_t *i_pmp; /* first metric, in this group */
+ pmInDom i_indom; /* my pmInDom */
+ int i_numinst; /* arg for pmAddProfile */
+ int *i_instlist; /* arg for pmAddProfile */
+ void *i_aux; /* generic pointer to aux data */
+} indomctl_t;
+
+typedef struct __fetchctl {
+ struct __fetchctl *f_next; /* next fetch control */
+ indomctl_t *f_idp; /* first indom, in this group */
+ int f_state; /* state changes during updates */
+ int f_cost; /* used internally for optimization */
+ int f_newcost; /* used internally for optimization */
+ int f_numpmid; /* arg for pmFetch() */
+ pmID *f_pmidlist; /* arg for pmFetch() */
+ void *f_aux; /* generic pointer to aux data */
+} fetchctl_t;
+
+/* states relevant to user */
+#define OPT_STATE_NEW 1 /* newly created group */
+#define OPT_STATE_PMID 2 /* list of pmids changed */
+#define OPT_STATE_PROFILE 4 /* instance profile changed */
+
+/* states used during optimization */
+#define OPT_STATE_UMASK 7 /* preserve user state bits */
+#define OPT_STATE_XREQ 8 /* things that may have changed */
+#define OPT_STATE_XPMID 16
+#define OPT_STATE_XINDOM 32
+#define OPT_STATE_XFETCH 64
+#define OPT_STATE_XPROFILE 128
+
+/*
+ * Objective function parameters
+ */
+typedef struct {
+ int c_pmid; /* cost per PMD for PMIDs in a fetch */
+ int c_indom; /* cost per PMD for indoms in a fetch */
+ int c_fetch; /* cost of a new fetch group */
+ int c_indomsize; /* expected numer of instances for an indom */
+ int c_xtrainst; /* cost of retrieving an unwanted metric inst */
+ int c_scope; /* cost opt., 0 for incremental, 1 for global */
+} optcost_t;
+
+#define OPT_COST_INFINITY 0x7fffffff
+
+extern void __pmOptFetchAdd(fetchctl_t **, optreq_t *);
+extern int __pmOptFetchDel(fetchctl_t **, optreq_t *);
+extern void __pmOptFetchRedo(fetchctl_t **);
+extern void __pmOptFetchDump(FILE *, const fetchctl_t *);
+extern void __pmOptFetchGetParams(optcost_t *);
+extern void __pmOptFetchPutParams(optcost_t *);
+
+/* work out local timezone */
+extern char *__pmTimezone(void); /* NOT thread-safe */
+extern char *__pmTimezone_r(char *, int);
+
+/*
+ */
+
+/*
+ * Generic access control routines
+ */
+extern int __pmAccAddOp(unsigned int);
+
+extern int __pmAccAddHost(const char *, unsigned int, unsigned int, int);
+extern int __pmAccAddUser(const char *, unsigned int, unsigned int, int);
+extern int __pmAccAddGroup(const char *, unsigned int, unsigned int, int);
+
+extern int __pmAccAddClient(__pmSockAddr *, unsigned int *);
+extern int __pmAccAddAccount(const char *, const char *, unsigned int *);
+extern void __pmAccDelClient(__pmSockAddr *);
+extern void __pmAccDelAccount(const char *, const char *);
+
+extern void __pmAccDumpHosts(FILE *);
+extern void __pmAccDumpUsers(FILE *);
+extern void __pmAccDumpGroups(FILE *);
+extern void __pmAccDumpLists(FILE *);
+
+extern int __pmAccSaveHosts(void);
+extern int __pmAccSaveUsers(void);
+extern int __pmAccSaveGroups(void);
+extern int __pmAccSaveLists(void);
+
+extern int __pmAccRestoreHosts(void);
+extern int __pmAccRestoreUsers(void);
+extern int __pmAccRestoreGroups(void);
+extern int __pmAccRestoreLists(void);
+
+extern void __pmAccFreeSavedHosts(void);
+extern void __pmAccFreeSavedUsers(void);
+extern void __pmAccFreeSavedGroups(void);
+extern void __pmAccFreeSavedLists(void);
+
+/*
+ * platform independent process routines
+ */
+extern int __pmProcessExists(pid_t);
+extern int __pmProcessTerminate(pid_t, int);
+extern pid_t __pmProcessCreate(char **, int *, int *);
+extern int __pmProcessDataSize(unsigned long *);
+extern int __pmProcessRunTimes(double *, double *);
+extern int __pmSetProcessIdentity(const char *);
+
+/*
+ * platform independent memory mapped file handling
+ */
+extern void *__pmMemoryMap(int, size_t, int);
+extern void __pmMemoryUnmap(void *, size_t);
+
+/*
+ * platform independent signal handling
+ */
+typedef void (*__pmSignalHandler)(int);
+extern int __pmSetSignalHandler(int, __pmSignalHandler);
+
+/*
+ * platform independent environment and filesystem path access
+ */
+typedef void (*__pmConfigCallback)(char *, char *, char *);
+EXTERN const __pmConfigCallback __pmNativeConfig;
+extern void __pmConfig(__pmConfigCallback);
+extern char *__pmNativePath(char *);
+extern int __pmAbsolutePath(char *);
+extern int __pmPathSeparator(void);
+extern int __pmMakePath(const char *, mode_t);
+
+/*
+ * discover configurable features of the shared libraries
+ */
+typedef void (*__pmAPIConfigCallback)(const char *, const char *);
+extern void __pmAPIConfig(__pmAPIConfigCallback);
+extern const char *__pmGetAPIConfig(const char *);
+
+/*
+ * internals of argument parsing for special circumstances
+ */
+extern void __pmStartOptions(pmOptions *);
+extern void __pmAddOptArchive(pmOptions *, char *);
+extern void __pmAddOptArchiveList(pmOptions *, char *);
+extern void __pmAddOptArchiveFolio(pmOptions *, char *);
+extern void __pmAddOptHost(pmOptions *, char *);
+extern void __pmAddOptHostList(pmOptions *, char *);
+extern void __pmEndOptions(pmOptions *);
+
+/*
+ * AF - general purpose asynchronous event management routines
+ */
+extern int __pmAFregister(const struct timeval *, void *, void (*)(int, void *));
+extern int __pmAFunregister(int);
+extern void __pmAFblock(void);
+extern void __pmAFunblock(void);
+extern int __pmAFisempty(void);
+
+/*
+ * private PDU protocol between pmlc and pmlogger
+ */
+#define LOG_PDU_VERSION2 2 /* private pdus & PCP 2.0 error codes */
+#define LOG_PDU_VERSION LOG_PDU_VERSION2
+
+#define LOG_REQUEST_NEWVOLUME 1
+#define LOG_REQUEST_STATUS 2
+#define LOG_REQUEST_SYNC 3
+
+typedef struct {
+ __pmTimeval ls_start; /* start time for log */
+ __pmTimeval ls_last; /* last time log written */
+ __pmTimeval ls_timenow; /* current time */
+ int ls_state; /* state of log (from __pmLogCtl) */
+ int ls_vol; /* current volume number of log */
+ __int64_t ls_size; /* size of current volume */
+ char ls_hostname[PM_LOG_MAXHOSTLEN];
+ /* name of pmcd host */
+ char ls_fqdn[PM_LOG_MAXHOSTLEN];
+ /* fully qualified domain name of pmcd host */
+ char ls_tz[PM_TZ_MAXLEN];
+ /* $TZ at collection host */
+ char ls_tzlogger[PM_TZ_MAXLEN];
+ /* $TZ at pmlogger */
+} __pmLoggerStatus;
+
+#define PDU_LOG_CONTROL 0x8000
+#define PDU_LOG_STATUS 0x8001
+#define PDU_LOG_REQUEST 0x8002
+
+extern int __pmConnectLogger(const char *, int *, int *);
+extern int __pmSendLogControl(int, const pmResult *, int, int, int);
+extern int __pmDecodeLogControl(const __pmPDU *, pmResult **, int *, int *, int *);
+extern int __pmSendLogRequest(int, int);
+extern int __pmDecodeLogRequest(const __pmPDU *, int *);
+extern int __pmSendLogStatus(int, __pmLoggerStatus *);
+extern int __pmDecodeLogStatus(__pmPDU *, __pmLoggerStatus **);
+
+/* logger timeout helper function */
+extern int __pmLoggerTimeout(void);
+
+/*
+ * other interfaces shared by pmlc and pmlogger
+ */
+
+extern int __pmControlLog(int, const pmResult *, int, int, int, pmResult **);
+
+#define PM_LOG_OFF 0 /* state */
+#define PM_LOG_MAYBE 1
+#define PM_LOG_ON 2
+
+#define PM_LOG_MANDATORY 11 /* control */
+#define PM_LOG_ADVISORY 12
+#define PM_LOG_ENQUIRE 13
+
+/* macros for logging control values from __pmControlLog() */
+#define PMLC_SET_ON(val, flag) \
+ (val) = ((val) & ~0x1) | ((flag) & 0x1)
+#define PMLC_GET_ON(val) \
+ ((val) & 0x1)
+#define PMLC_SET_MAND(val, flag) \
+ (val) = ((val) & ~0x2) | (((flag) & 0x1) << 1)
+#define PMLC_GET_MAND(val) \
+ (((val) & 0x2) >> 1)
+#define PMLC_SET_AVAIL(val, flag) \
+ (val) = ((val) & ~0x4) | (((flag) & 0x1) << 2)
+#define PMLC_GET_AVAIL(val) \
+ (((val) & 0x4) >> 2)
+#define PMLC_SET_INLOG(val, flag) \
+ (val) = ((val) & ~0x8) | (((flag) & 0x1) << 3)
+#define PMLC_GET_INLOG(val) \
+ (((val) & 0x8) >> 3)
+
+#define PMLC_SET_STATE(val, state) \
+ (val) = ((val) & ~0xf) | ((state) & 0xf)
+#define PMLC_GET_STATE(val) \
+ ((val) & 0xf)
+
+/* 28 bits of delta, 32 bits of state */
+#define PMLC_MAX_DELTA 0x0fffffff
+
+#define PMLC_SET_DELTA(val, delta) \
+ (val) = ((val) & 0xf) | ((delta) << 4)
+#define PMLC_GET_DELTA(val) \
+ ((((val) & ~0xf) >> 4) & PMLC_MAX_DELTA)
+
+/*
+ * helper functions to register client identity with pmcd for export
+ * via pmcd.client.whoami
+ */
+extern char *__pmGetClientId(int, char **);
+extern int __pmSetClientIdArgv(int, char **);
+extern int __pmSetClientId(const char *);
+
+/*
+ * Adding/deleting/clearing the list of DSO PMDAs supported for
+ * PM_CONTEXT_LOCAL contexts
+ */
+#define PM_LOCAL_ADD 1
+#define PM_LOCAL_DEL 2
+#define PM_LOCAL_CLEAR 3
+extern int __pmLocalPMDA(int, int, const char *, const char *);
+extern char *__pmSpecLocalPMDA(const char *);
+
+/*
+ * Helper methods for packed arrays of event records
+ */
+extern int __pmCheckEventRecords(pmValueSet *, int);
+extern int __pmCheckHighResEventRecords(pmValueSet *, int);
+extern void __pmDumpEventRecords(FILE *, pmValueSet *, int);
+extern void __pmDumpHighResEventRecords(FILE *, pmValueSet *, int);
+
+/* Get nanosecond precision timestamp from system clocks */
+extern int __pmGetTimespec(struct timespec *);
+
+/* Anonymous metric registration (uses derived metrics support) */
+extern int __pmRegisterAnon(const char *, int);
+
+/*
+ * Multi-thread support
+ * Use PM_MULTI_THREAD_DEBUG for lock debugging with -Dlock[,appl?...]
+ */
+extern void __pmInitLocks(void);
+extern int __pmLock(void *, const char *, int);
+extern int __pmUnlock(void *, const char *, int);
+
+/*
+ * Each of these scopes defines one or more PMAPI routines that will
+ * not allow calls from more than one thread.
+ */
+#define PM_SCOPE_DSO_PMDA 0
+#define PM_SCOPE_ACL 1
+#define PM_SCOPE_AF 2
+#define PM_SCOPE_LOGPORT 3
+#define PM_SCOPE_MAX 3
+extern int __pmMultiThreaded(int);
+
+#define PM_INIT_LOCKS() __pmInitLocks()
+#define PM_MULTIPLE_THREADS(x) __pmMultiThreaded(x)
+#define PM_LOCK(lock) __pmLock(&(lock), __FILE__, __LINE__)
+#define PM_UNLOCK(lock) __pmUnlock(&(lock), __FILE__, __LINE__)
+
+#ifdef HAVE_PTHREAD_MUTEX_T
+extern pthread_mutex_t __pmLock_libpcp; /* big libpcp lock */
+#else
+extern void *__pmLock_libpcp; /* symbol exposure */
+#endif
+
+/*
+ * Service discovery with options.
+ * The 4th argument is a pointer to a mask of flags for boolean options
+ * and status. It is set and tested using the following bits.
+ */
+#define PM_SERVICE_DISCOVERY_INTERRUPTED 0x1
+#define PM_SERVICE_DISCOVERY_RESOLVE 0x2
+
+extern int __pmDiscoverServicesWithOptions(const char *,
+ const char *,
+ const char *,
+ const volatile unsigned *,
+ char ***);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMPL_H */
diff --git a/src/include/pcp/import.h b/src/include/pcp/import.h
new file mode 100644
index 0000000..82b5be3
--- /dev/null
+++ b/src/include/pcp/import.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _IMPORT_H
+#define _IMPORT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* core libpcp_import API routines */
+extern int pmiStart(const char *, int);
+extern int pmiUseContext(int);
+extern int pmiEnd(void);
+extern int pmiSetHostname(const char *);
+extern int pmiSetTimezone(const char *);
+extern int pmiAddMetric(const char *, pmID, int, pmInDom, int, pmUnits);
+extern int pmiAddInstance(pmInDom, const char *, int);
+extern int pmiPutValue(const char *, const char *, const char *);
+extern int pmiGetHandle(const char *, const char *);
+extern int pmiPutValueHandle(int, const char *);
+extern int pmiWrite(int, int);
+extern int pmiPutResult(const pmResult *);
+
+/* helper routines */
+extern pmID pmiID(int, int, int);
+extern pmInDom pmiInDom(int, int);
+extern pmUnits pmiUnits(int, int, int, int, int, int);
+
+/* diagnostic routines */
+#define PMI_MAXERRMSGLEN 128 /* safe size to accomodate any error message */
+extern char *pmiErrStr_r(int, char *, int);
+extern const char *pmiErrStr(int); /* cannot ever be made thread-safe */
+extern void pmiDump(void);
+
+/* libpcp_import error codes */
+#define PMI_ERR_BASE 20000
+#define PMI_ERR_DUPMETRICNAME (-PMI_ERR_BASE-1) /* Metric name already defined */
+#define PMI_ERR_DUPMETRICID (-PMI_ERR_BASE-2) /* Metric pmID already defined */
+#define PMI_ERR_DUPINSTNAME (-PMI_ERR_BASE-3) /* External instance name already defined */
+#define PMI_ERR_DUPINSTID (-PMI_ERR_BASE-4) /* Internal instance identifer already defined */
+#define PMI_ERR_INSTNOTNULL (-PMI_ERR_BASE-5) /* Non-null instance expected for a singular metric */
+#define PMI_ERR_INSTNULL (-PMI_ERR_BASE-6) /* Null instance not allowed for a non-singular metric */
+#define PMI_ERR_BADHANDLE (-PMI_ERR_BASE-7) /* Illegal handle */
+#define PMI_ERR_DUPVALUE (-PMI_ERR_BASE-8) /* Value already assigned for singular metric */
+#define PMI_ERR_BADTYPE (-PMI_ERR_BASE-9) /* Illegal metric type */
+#define PMI_ERR_BADSEM (-PMI_ERR_BASE-10) /* Illegal metric semantics */
+#define PMI_ERR_NODATA (-PMI_ERR_BASE-11) /* No data to output */
+#define PMI_ERR_BADMETRICNAME (-PMI_ERR_BASE-12) /* Illegal metric name */
+#define PMI_ERR_BADTIMESTAMP (-PMI_ERR_BASE-13) /* Illegal result timestamp */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMPORT_H */
diff --git a/src/include/pcp/mk_pmdbg b/src/include/pcp/mk_pmdbg
new file mode 100755
index 0000000..a234f3b
--- /dev/null
+++ b/src/include/pcp/mk_pmdbg
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Create pmdbg.h from impl.h
+
+if [ -f ../pcp.conf ]
+then
+ # needed for $PCP_SORT_PROG
+ . ../pcp.conf
+else
+ echo "mk_pmdbg: cannot find pcp.conf"
+ exit 1
+fi
+
+if [ ! -f impl.h ]
+then
+ echo "mk_pmdbg: cannot find impl.h"
+ exit 1
+fi
+
+rm -f pmdbg.h
+cat <<End-of-File >pmdbg.h
+/*
+ * Built from impl.h by mk_pmdbg. Any modifications will be lost.
+ */
+
+typedef const struct {
+ const char *name;
+ const int bit;
+} debug_map_t;
+
+static const debug_map_t debug_map[] = {
+End-of-File
+
+sed -n <impl.h \
+ -e '/#define[ ]*DBG_TRACE_/{
+s/#define[ ]*\(DBG_TRACE_\)\([A-Z0-9_]*\).*/ { "\2", \1\2 },/p
+}' \
+| $PCP_SORT_PROG >>pmdbg.h
+
+cat <<End-of-File >>pmdbg.h
+};
+
+static const int num_debug = sizeof(debug_map) / sizeof(debug_map[0]);
+End-of-File
+
+exit 0
diff --git a/src/include/pcp/mmv_dev.h b/src/include/pcp/mmv_dev.h
new file mode 100644
index 0000000..92e3b8e
--- /dev/null
+++ b/src/include/pcp/mmv_dev.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (C) 2009 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef _MMV_DEV_H
+#define _MMV_DEV_H
+
+#define MMV_VERSION 1
+
+typedef enum {
+ MMV_TOC_INDOMS = 1, /* mmv_disk_indom_t */
+ MMV_TOC_INSTANCES = 2, /* mmv_disk_instance_t */
+ MMV_TOC_METRICS = 3, /* mmv_disk_metric_t */
+ MMV_TOC_VALUES = 4, /* mmv_disk_value_t */
+ MMV_TOC_STRINGS = 5, /* mmv_disk_string_t */
+} mmv_toc_type_t;
+
+/* The way the Table Of Contents is written into the file */
+typedef struct {
+ mmv_toc_type_t type; /* What is it? */
+ __int32_t count; /* Number of entries */
+ __uint64_t offset; /* Offset of section from file start */
+} mmv_disk_toc_t;
+
+typedef struct {
+ __uint32_t serial; /* Unique identifier */
+ __uint32_t count; /* Number of instances */
+ __uint64_t offset; /* Offset of first instance */
+ __uint64_t shorttext; /* Offset of short help text string */
+ __uint64_t helptext; /* Offset of long help text string */
+} mmv_disk_indom_t;
+
+typedef struct {
+ __uint64_t indom; /* Offset into files indom section */
+ __uint32_t padding; /* zero filled, alignment bits */
+ __int32_t internal; /* Internal instance ID */
+ char external[MMV_NAMEMAX]; /* External instance ID */
+} mmv_disk_instance_t;
+
+typedef struct {
+ char payload[MMV_STRINGMAX]; /* NULL terminated string */
+} mmv_disk_string_t;
+
+typedef struct {
+ char name[MMV_NAMEMAX];
+ __uint32_t item; /* Unique identifier */
+ mmv_metric_type_t type;
+ mmv_metric_sem_t semantics;
+ pmUnits dimension;
+ __int32_t indom; /* Instance domain number */
+ __uint32_t padding; /* zero filled, alignment bits */
+ __uint64_t shorttext; /* Offset of short help text string */
+ __uint64_t helptext; /* Offset of long help text string */
+} mmv_disk_metric_t;
+
+typedef struct {
+ pmAtomValue value; /* Union of all possible value types */
+ __int64_t extra; /* INTEGRAL(starttime)/STRING(offset) */
+ __uint64_t metric; /* Offset into the metric section */
+ __uint64_t instance; /* Offset into the instance section */
+} mmv_disk_value_t;
+
+typedef struct {
+ char magic[4]; /* MMV\0 */
+ __int32_t version; /* version */
+ __uint64_t g1; /* Generation numbers */
+ __uint64_t g2;
+ __int32_t tocs; /* Number of toc entries */
+ mmv_stats_flags_t flags;
+ __int32_t process; /* client process identifier (flags) */
+ __int32_t cluster; /* preferred PMDA cluster identifier */
+} mmv_disk_header_t;
+
+#endif /* _MMV_DEV_H */
diff --git a/src/include/pcp/mmv_stats.h b/src/include/pcp/mmv_stats.h
new file mode 100644
index 0000000..12f53d7
--- /dev/null
+++ b/src/include/pcp/mmv_stats.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2013 Red Hat.
+ * Copyright (C) 2009 Aconex. All Rights Reserved.
+ * Copyright (C) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef _MMV_STATS_H
+#define _MMV_STATS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MMV_NAMEMAX 64
+#define MMV_STRINGMAX 256
+
+typedef enum mmv_metric_type {
+ MMV_TYPE_NOSUPPORT = PM_TYPE_NOSUPPORT,
+ MMV_TYPE_I32 = PM_TYPE_32, /* 32-bit signed integer */
+ MMV_TYPE_U32 = PM_TYPE_U32, /* 32-bit unsigned integer */
+ MMV_TYPE_I64 = PM_TYPE_64, /* 64-bit signed integer */
+ MMV_TYPE_U64 = PM_TYPE_U64, /* 64-bit unsigned integer */
+ MMV_TYPE_FLOAT = PM_TYPE_FLOAT, /* 32-bit floating point */
+ MMV_TYPE_DOUBLE = PM_TYPE_DOUBLE,/* 64-bit floating point */
+ MMV_TYPE_STRING = PM_TYPE_STRING,/* NULL-terminate string */
+ MMV_TYPE_ELAPSED = 9, /* 64-bit elapsed time */
+} mmv_metric_type_t;
+
+typedef enum mmv_metric_sem {
+ MMV_SEM_COUNTER = PM_SEM_COUNTER,
+ MMV_SEM_INSTANT = PM_SEM_INSTANT,
+ MMV_SEM_DISCRETE = PM_SEM_DISCRETE,
+} mmv_metric_sem_t;
+
+typedef struct mmv_instances {
+ __int32_t internal; /* Internal instance ID */
+ char external[MMV_NAMEMAX]; /* External instance ID */
+} mmv_instances_t;
+
+typedef struct mmv_indom {
+ __uint32_t serial; /* Unique identifier */
+ __uint32_t count; /* Number of instances */
+ mmv_instances_t * instances; /* Internal/external IDs */
+ char * shorttext; /* Short help text string */
+ char * helptext; /* Long help text string */
+} mmv_indom_t;
+
+typedef struct mmv_metric {
+ char name[MMV_NAMEMAX];
+ __uint32_t item; /* Unique identifier */
+ mmv_metric_type_t type;
+ mmv_metric_sem_t semantics;
+ pmUnits dimension;
+ __uint32_t indom; /* Indom serial */
+ char * shorttext; /* Short help text string */
+ char * helptext; /* Long help text string */
+} mmv_metric_t;
+
+#ifdef HAVE_BITFIELDS_LTOR
+#define MMV_UNITS(a,b,c,d,e,f) {a,b,c,d,e,f,0}
+#else
+#define MMV_UNITS(a,b,c,d,e,f) {0,f,e,d,c,b,a}
+#endif
+
+typedef enum mmv_stats_flags {
+ MMV_FLAG_NOPREFIX = 0x1, /* Don't prefix metric names by filename */
+ MMV_FLAG_PROCESS = 0x2, /* Indicates process check on PID needed */
+} mmv_stats_flags_t;
+
+extern void * mmv_stats_init(const char *, int, mmv_stats_flags_t,
+ const mmv_metric_t *, int,
+ const mmv_indom_t *, int);
+extern void mmv_stats_stop(const char *, void *);
+
+extern pmAtomValue * mmv_lookup_value_desc(void *, const char *, const char *);
+extern void mmv_inc_value(void *, pmAtomValue *, double);
+extern void mmv_set_value(void *, pmAtomValue *, double);
+extern void mmv_set_string(void *, pmAtomValue *, const char *, int);
+
+extern void mmv_stats_add(void *, const char *, const char *, double);
+extern void mmv_stats_inc(void *, const char *, const char *);
+extern void mmv_stats_set(void *, const char *, const char *, double);
+extern void mmv_stats_add_fallback(void *, const char *, const char *,
+ const char *, double);
+extern void mmv_stats_inc_fallback(void *, const char *, const char *,
+ const char *);
+extern pmAtomValue * mmv_stats_interval_start(void *, pmAtomValue *,
+ const char *, const char *);
+extern void mmv_stats_interval_end(void *, pmAtomValue *);
+extern void mmv_stats_set_string(void *, const char *,
+ const char *, const char *);
+extern void mmv_stats_set_strlen(void *, const char *,
+ const char *, const char *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MMV_STATS_H */
diff --git a/src/include/pcp/platform_defs.h.in b/src/include/pcp/platform_defs.h.in
new file mode 100644
index 0000000..091657e
--- /dev/null
+++ b/src/include/pcp/platform_defs.h.in
@@ -0,0 +1,13 @@
+/*
+ * @configure_input@
+ */
+#ifndef _PCP_PLATFORM_DEFS_H
+#define _PCP_PLATFORM_DEFS_H
+
+#include "config.h"
+
+/* printf candy ... */
+#define FMT_PID @fmt_pid@
+#define FMT_PTHREAD @fmt_pthread@
+
+#endif /* _PCP_PLATFORM_DEFS_H */
diff --git a/src/include/pcp/pmafm.h b/src/include/pcp/pmafm.h
new file mode 100644
index 0000000..d1330e5
--- /dev/null
+++ b/src/include/pcp/pmafm.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#ifndef _PMAFM_H
+#define _PMAFM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Recording session support
+ */
+#define PM_REC_ON 40
+#define PM_REC_OFF 41
+#define PM_REC_DETACH 43
+#define PM_REC_STATUS 44
+#define PM_REC_SETARG 45
+
+typedef struct {
+ FILE *f_config; /* caller writes pmlogger configuration here */
+ int fd_ipc; /* IPC channel to pmlogger */
+ char *logfile; /* full pathname for pmlogger error logfile */
+ pid_t pid; /* process id for pmlogger */
+ int status; /* exit status, -1 if unknown */
+} pmRecordHost;
+
+extern FILE *pmRecordSetup(const char *, const char *, int);
+extern int pmRecordAddHost(const char *, int, pmRecordHost **);
+extern int pmRecordControl(pmRecordHost *, int, const char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PMAFM_H */
diff --git a/src/include/pcp/pmapi.h b/src/include/pcp/pmapi.h
new file mode 100644
index 0000000..be4b45e
--- /dev/null
+++ b/src/include/pcp/pmapi.h
@@ -0,0 +1,884 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1997,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PMAPI_H
+#define _PMAPI_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+
+/*
+ * Platform and environment customization
+ */
+#include "platform_defs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PMAPI_VERSION_2 2
+#define PMAPI_VERSION PMAPI_VERSION_2
+
+/*
+ * -------- Naming Services --------
+ */
+typedef unsigned int pmID; /* Metric Identifier */
+#define PM_ID_NULL 0xffffffff
+
+typedef unsigned int pmInDom; /* Instance-Domain */
+#define PM_INDOM_NULL 0xffffffff
+#define PM_IN_NULL 0xffffffff
+
+#define PM_NS_DEFAULT NULL /* default name */
+
+/*
+ * Encoding for the units (dimensions Time and Space) and scale
+ * for Performance Metric Values
+ *
+ * For example, a pmUnits struct of
+ * { 1, -1, 0, PM_SPACE_MBYTE, PM_TIME_SEC, 0 }
+ * represents Mbytes/sec, while
+ * { 0, 1, -1, 0, PM_TIME_HOUR, 6 }
+ * represents hours/million-events
+ */
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ signed int dimSpace : 4; /* space dimension */
+ signed int dimTime : 4; /* time dimension */
+ signed int dimCount : 4; /* event dimension */
+ unsigned int scaleSpace : 4; /* one of PM_SPACE_* below */
+ unsigned int scaleTime : 4; /* one of PM_TIME_* below */
+ signed int scaleCount : 4; /* one of PM_COUNT_* below */
+ unsigned int pad : 8;
+#else
+ unsigned int pad : 8;
+ signed int scaleCount : 4; /* one of PM_COUNT_* below */
+ unsigned int scaleTime : 4; /* one of PM_TIME_* below */
+ unsigned int scaleSpace : 4; /* one of PM_SPACE_* below */
+ signed int dimCount : 4; /* event dimension */
+ signed int dimTime : 4; /* time dimension */
+ signed int dimSpace : 4; /* space dimension */
+#endif
+} pmUnits; /* dimensional units and scale of value */
+
+/* pmUnits.scaleSpace */
+#define PM_SPACE_BYTE 0 /* bytes */
+#define PM_SPACE_KBYTE 1 /* Kilobytes (1024) */
+#define PM_SPACE_MBYTE 2 /* Megabytes (1024^2) */
+#define PM_SPACE_GBYTE 3 /* Gigabytes (1024^3) */
+#define PM_SPACE_TBYTE 4 /* Terabytes (1024^4) */
+#define PM_SPACE_PBYTE 5 /* Petabytes (1024^5) */
+#define PM_SPACE_EBYTE 6 /* Exabytes (1024^6) */
+/* pmUnits.scaleTime */
+#define PM_TIME_NSEC 0 /* nanoseconds */
+#define PM_TIME_USEC 1 /* microseconds */
+#define PM_TIME_MSEC 2 /* milliseconds */
+#define PM_TIME_SEC 3 /* seconds */
+#define PM_TIME_MIN 4 /* minutes */
+#define PM_TIME_HOUR 5 /* hours */
+/*
+ * pmUnits.scaleCount (e.g. count events, syscalls, interrupts, etc.)
+ * -- these are simply powers of 10, and not enumerated here,
+ * e.g. 6 for 10^6, or -3 for 10^-3
+ */
+#define PM_COUNT_ONE 0 /* 1 */
+
+/* Performance Metric Descriptor */
+typedef struct {
+ pmID pmid; /* unique identifier */
+ int type; /* base data type (see below) */
+ pmInDom indom; /* instance domain */
+ int sem; /* semantics of value (see below) */
+ pmUnits units; /* dimension and units */
+} pmDesc;
+
+/* pmDesc.type -- data type of metric values */
+#define PM_TYPE_NOSUPPORT -1 /* not implemented in this version */
+#define PM_TYPE_32 0 /* 32-bit signed integer */
+#define PM_TYPE_U32 1 /* 32-bit unsigned integer */
+#define PM_TYPE_64 2 /* 64-bit signed integer */
+#define PM_TYPE_U64 3 /* 64-bit unsigned integer */
+#define PM_TYPE_FLOAT 4 /* 32-bit floating point */
+#define PM_TYPE_DOUBLE 5 /* 64-bit floating point */
+#define PM_TYPE_STRING 6 /* array of char */
+#define PM_TYPE_AGGREGATE 7 /* arbitrary binary data (aggregate) */
+#define PM_TYPE_AGGREGATE_STATIC 8 /* static pointer to aggregate */
+#define PM_TYPE_EVENT 9 /* packed pmEventArray */
+#define PM_TYPE_HIGHRES_EVENT 10 /* packed pmHighResEventArray */
+#define PM_TYPE_UNKNOWN 255 /* used in pmValueBlock, not pmDesc */
+
+/* pmDesc.sem -- semantics/interpretation of metric values */
+#define PM_SEM_COUNTER 1 /* cumulative counter (monotonic increasing) */
+ /* was PM_SEM_RATE, no longer used now */
+#define PM_SEM_INSTANT 3 /* instantaneous value, continuous domain */
+#define PM_SEM_DISCRETE 4 /* instantaneous value, discrete domain */
+
+#define PM_ERR_BASE2 12345
+#define PM_ERR_BASE PM_ERR_BASE2
+
+/* PMAPI Error Conditions */
+
+#define PM_ERR_GENERIC (-PM_ERR_BASE-0) /* Generic error, already reported above */
+#define PM_ERR_PMNS (-PM_ERR_BASE-1) /* Problems parsing PMNS definitions */
+#define PM_ERR_NOPMNS (-PM_ERR_BASE-2) /* PMNS not accessible */
+#define PM_ERR_DUPPMNS (-PM_ERR_BASE-3) /* Attempt to reload the PMNS */
+#define PM_ERR_TEXT (-PM_ERR_BASE-4) /* Oneline or help text is not available */
+#define PM_ERR_APPVERSION (-PM_ERR_BASE-5) /* Metric not supported by this version of monitored application */
+#define PM_ERR_VALUE (-PM_ERR_BASE-6) /* Missing metric value(s) */
+/* retired PM_ERR_LICENSE (-PM_ERR_BASE-7) Current PCP license does not permit this operation */
+#define PM_ERR_TIMEOUT (-PM_ERR_BASE-8) /* Timeout waiting for a response from PMCD */
+#define PM_ERR_NODATA (-PM_ERR_BASE-9) /* Empty archive log file */
+#define PM_ERR_RESET (-PM_ERR_BASE-10) /* pmcd reset or configuration changed */
+/* retired PM_ERR_FILE (-PM_ERR_BASE-11) Cannot locate a file */
+#define PM_ERR_NAME (-PM_ERR_BASE-12) /* Unknown metric name */
+#define PM_ERR_PMID (-PM_ERR_BASE-13) /* Unknown or illegal metric identifier */
+#define PM_ERR_INDOM (-PM_ERR_BASE-14) /* Unknown or illegal instance domain identifier */
+#define PM_ERR_INST (-PM_ERR_BASE-15) /* Unknown or illegal instance identifier */
+#define PM_ERR_UNIT (-PM_ERR_BASE-16) /* Illegal pmUnits specification */
+#define PM_ERR_CONV (-PM_ERR_BASE-17) /* Impossible value or scale conversion */
+#define PM_ERR_TRUNC (-PM_ERR_BASE-18) /* Truncation in value conversion */
+#define PM_ERR_SIGN (-PM_ERR_BASE-19) /* Negative value in conversion to unsigned */
+#define PM_ERR_PROFILE (-PM_ERR_BASE-20) /* Explicit instance identifier(s) required */
+#define PM_ERR_IPC (-PM_ERR_BASE-21) /* IPC protocol failure */
+/* retired PM_ERR_NOASCII (-PM_ERR_BASE-22) ASCII format not supported for this PDU */
+#define PM_ERR_EOF (-PM_ERR_BASE-23) /* IPC channel closed */
+#define PM_ERR_NOTHOST (-PM_ERR_BASE-24) /* Operation requires context with host source of metrics */
+#define PM_ERR_EOL (-PM_ERR_BASE-25) /* End of PCP archive log */
+#define PM_ERR_MODE (-PM_ERR_BASE-26) /* Illegal mode specification */
+#define PM_ERR_LABEL (-PM_ERR_BASE-27) /* Illegal label record at start of a PCP archive log file */
+#define PM_ERR_LOGREC (-PM_ERR_BASE-28) /* Corrupted record in a PCP archive log */
+#define PM_ERR_NOTARCHIVE (-PM_ERR_BASE-29) /* Operation requires context with archive source of metrics */
+#define PM_ERR_LOGFILE (-PM_ERR_BASE-30) /* Missing archive file */
+#define PM_ERR_NOCONTEXT (-PM_ERR_BASE-31) /* Attempt to use an illegal context */
+#define PM_ERR_PROFILESPEC (-PM_ERR_BASE-32) /* NULL pmInDom with non-NULL instlist */
+#define PM_ERR_PMID_LOG (-PM_ERR_BASE-33) /* Metric not defined in the PCP archive log */
+#define PM_ERR_INDOM_LOG (-PM_ERR_BASE-34) /* Instance domain identifier not defined in the PCP archive log */
+#define PM_ERR_INST_LOG (-PM_ERR_BASE-35) /* Instance identifier not defined in the PCP archive log */
+#define PM_ERR_NOPROFILE (-PM_ERR_BASE-36) /* Missing profile - protocol botch */
+#define PM_ERR_NOAGENT (-PM_ERR_BASE-41) /* No pmcd agent for domain of request */
+#define PM_ERR_PERMISSION (-PM_ERR_BASE-42) /* No permission to perform requested operation */
+#define PM_ERR_CONNLIMIT (-PM_ERR_BASE-43) /* PMCD connection limit for this host exceeded */
+#define PM_ERR_AGAIN (-PM_ERR_BASE-44) /* try again. Info not currently available */
+#define PM_ERR_ISCONN (-PM_ERR_BASE-45) /* already connected */
+#define PM_ERR_NOTCONN (-PM_ERR_BASE-46) /* not connected */
+#define PM_ERR_NEEDPORT (-PM_ERR_BASE-47) /* port name required */
+/* retired PM_ERR_WANTACK (-PM_ERR_BASE-48) can not send due to pending acks */
+#define PM_ERR_NONLEAF (-PM_ERR_BASE-49) /* PMNS node is not a leaf node */
+/* retired PM_ERR_OBJSTYLE (-PM_ERR_BASE-50) user/kernel object style mismatch */
+/* retired PM_ERR_PMCDLICENSE (-PM_ERR_BASE-51) PMCD is not licensed to accept connections */
+#define PM_ERR_TYPE (-PM_ERR_BASE-52) /* Unknown or illegal metric type */
+#define PM_ERR_THREAD (-PM_ERR_BASE-53) /* Operation not supported for multi-threaded applications */
+
+/* retired PM_ERR_CTXBUSY (-PM_ERR_BASE-97) Context is busy */
+#define PM_ERR_TOOSMALL (-PM_ERR_BASE-98) /* Insufficient elements in list */
+#define PM_ERR_TOOBIG (-PM_ERR_BASE-99) /* Result size exceeded */
+#define PM_ERR_FAULT (-PM_ERR_BASE-100) /* QA fault injected */
+
+#define PM_ERR_PMDAREADY (-PM_ERR_BASE-1048) /* now ready to respond */
+#define PM_ERR_PMDANOTREADY (-PM_ERR_BASE-1049) /* not yet ready to respond */
+#define PM_ERR_NYI (-PM_ERR_BASE-8999) /* Functionality not yet implemented [end-of-range mark] */
+
+/*
+ * Report PMAPI errors messages
+ */
+extern char *pmErrStr(int); /* NOT thread-safe */
+extern char *pmErrStr_r(int, char *, int);
+/* safe size for a pmErrStr_r buffer to accommodate all error messages */
+#define PM_MAXERRMSGLEN 128
+
+/*
+ * Load a Performance Metrics Name Space
+ */
+extern int pmLoadNameSpace(const char *);
+extern int pmLoadASCIINameSpace(const char *, int);
+extern void pmUnloadNameSpace(void);
+
+/*
+ * Where is PMNS located - added for distributed PMNS.
+ */
+extern int pmGetPMNSLocation(void);
+#define PMNS_LOCAL 1
+#define PMNS_REMOTE 2
+#define PMNS_ARCHIVE 3
+
+/*
+ * Trim a name space with respect to the current context
+ * (usually from an archive, or after processing an archive)
+ */
+extern int pmTrimNameSpace(void);
+
+/*
+ * Expand a list of names to a list of metrics ids
+ */
+extern int pmLookupName(int, char **, pmID *);
+
+/*
+ * Find the names of descendent nodes in the PMNS
+ * and in the latter case get the status of each child.
+ */
+extern int pmGetChildren(const char *, char ***);
+extern int pmGetChildrenStatus(const char *, char ***, int **);
+#define PMNS_LEAF_STATUS 0 /* leaf node in PMNS tree */
+#define PMNS_NONLEAF_STATUS 1 /* non-terminal node in PMNS tree */
+
+/*
+ * Reverse Lookup: find name(s) given a metric id
+ */
+extern int pmNameID(pmID, char **); /* one */
+extern int pmNameAll(pmID, char ***); /* all */
+
+/*
+ * Handy recursive descent of the PMNS
+ */
+extern int pmTraversePMNS(const char *, void(*)(const char *));
+extern int pmTraversePMNS_r(const char *, void(*)(const char *, void *), void *);
+
+/*
+ * Given a metric, find it's descriptor (caller supplies buffer for desc),
+ * from the current context.
+ */
+extern int pmLookupDesc(pmID, pmDesc *);
+
+/*
+ * Return the internal instance identifier, from the current context,
+ * given an instance domain and the external instance name.
+ * Archive variant scans the union of the indom entries in the archive
+ * log.
+ */
+extern int pmLookupInDom(pmInDom, const char *);
+extern int pmLookupInDomArchive(pmInDom, const char *);
+
+/*
+ * Return the external instance name, from the current context,
+ * given an instance domain and the internal instance identifier.
+ * Archive variant scans the union of the indom entries in the archive
+ * log.
+ */
+extern int pmNameInDom(pmInDom, int, char **);
+extern int pmNameInDomArchive(pmInDom, int, char **);
+
+/*
+ * Return all of the internal instance identifiers (instlist) and external
+ * instance names (namelist) for the given instance domain in the current
+ * context.
+ * Archive variant returns the union of the indom entries in the archive
+ * log.
+ */
+extern int pmGetInDom(pmInDom, int **, char ***);
+extern int pmGetInDomArchive(pmInDom, int **, char ***);
+
+/*
+ * Given context ID, return the host name associated with that context,
+ * or the empty string if no name can be found
+ */
+extern const char *pmGetContextHostName(int);
+extern char *pmGetContextHostName_r(int, char *, int);
+
+/*
+ * Return the handle of the current context
+ */
+extern int pmWhichContext(void);
+
+/*
+ * Destroy a context and close its connection
+ */
+extern int pmDestroyContext(int);
+
+/*
+ * Establish a new context (source of performance data + instance profile)
+ * for the named host
+ */
+extern int pmNewContext(int, const char *);
+#define PM_CONTEXT_UNDEF -1 /* current context is undefined */
+#define PM_CONTEXT_HOST 1 /* host via pmcd */
+#define PM_CONTEXT_ARCHIVE 2 /* PCP archive */
+#define PM_CONTEXT_LOCAL 3 /* local host, no pmcd connection */
+#define PM_CONTEXT_TYPEMASK 0xff /* mask to separate types / flags */
+/* #define PM_CTXFLAG_SHALLOW (1U<<8) -- don't actually connect to host */
+/* #define PM_CTXFLAG_EXCLUSIVE (1U<<9) -- don't share socket among ctxts */
+#define PM_CTXFLAG_SECURE (1U<<10)/* encrypted socket comms channel */
+#define PM_CTXFLAG_COMPRESS (1U<<11)/* compressed socket host channel */
+#define PM_CTXFLAG_RELAXED (1U<<12)/* encrypted if possible else not */
+#define PM_CTXFLAG_AUTH (1U<<13)/* make authenticated connection */
+
+/*
+ * Duplicate current context -- returns handle to new one for pmUseContext()
+ */
+extern int pmDupContext(void);
+
+/*
+ * Restore (previously established or duplicated) context
+ */
+extern int pmUseContext(int);
+
+/*
+ * Reconnect an existing context (when pmcd dies, etc). All existing context
+ * settings are preserved and the previous context settings are restored.
+ */
+extern int pmReconnectContext(int);
+
+/*
+ * Add to instance profile.
+ * If pmInDom == PM_INDOM_NULL, then all instance domains are selected.
+ * If no inst parameters are given, then all instances are selected.
+ * e.g. to select all available instances in all domains,
+ * then use pmAddProfile(PM_INDOM_NULL, 0, NULL).
+ */
+extern int pmAddProfile(pmInDom, int, int *);
+
+/*
+ * Delete from instance profile.
+ * Similar (but negated) functional semantics to pmProfileAdd.
+ * E.g. to disable all instances in all domains then use
+ * pmDelProfile(PM_INDOM_NULL, 0, NULL).
+ */
+extern int pmDelProfile(pmInDom, int, int *);
+
+/*
+ * ---------- Collection services ----------
+ *
+ * Result from pmFetch is encoded as a timestamp and vector of pointers
+ * to pmValueSet instances (one per PMID in the result).
+ * Each pmValueSet has a PMID, a value count, a value format, and a vector of
+ * instance-value pairs. Aggregate, string and non-int values are returned
+ * via one further level of indirection using pmValueBlocks.
+ *
+ * timeStamp
+ * ->pmID
+ * value format
+ * instance, value
+ * instance, value
+ * ... etc
+ *
+ * ->pmID
+ * value format
+ * instance, value
+ * ... etc
+ *
+ *
+ * Notes on pmValueBlock
+ * 0. may be used for arbitrary binary data
+ * 1. only ever allocated dynamically, and vbuf expands to accommodate
+ * an arbitrary value (don't believe the [1] size!)
+ * 2. len is the length of the len field + the real size of vbuf
+ * (which includes the null-byte terminator if there is one)
+ */
+typedef struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int vtype : 8; /* value type */
+ unsigned int vlen : 24; /* bytes for vtype/vlen + vbuf */
+#else
+ unsigned int vlen : 24; /* bytes for vtype/vlen + vbuf */
+ unsigned int vtype : 8; /* value type */
+#endif
+ char vbuf[1]; /* the value */
+} pmValueBlock;
+
+#define PM_VAL_HDR_SIZE 4 /* bytes for the vtype/vlen header */
+#define PM_VAL_VLEN_MAX 0x00ffffff /* maximum vbuf[] size */
+
+typedef struct {
+ int inst; /* instance identifier */
+ union {
+ pmValueBlock *pval; /* pointer to value-block */
+ int lval; /* integer value insitu (lval 'cuz it WAS a long) */
+ } value;
+} pmValue;
+
+typedef struct {
+ pmID pmid; /* metric identifier */
+ int numval; /* number of values */
+ int valfmt; /* value style */
+ pmValue vlist[1]; /* set of instances/values */
+} pmValueSet;
+
+/* values for valfmt */
+#define PM_VAL_INSITU 0 /* value.lval is it */
+#define PM_VAL_DPTR 1 /* value.pval->vbuf is it, and dynamic alloc */
+#define PM_VAL_SPTR 2 /* value.pval->vbuf is it, and static alloc */
+
+
+/* Result returned by pmFetch() */
+typedef struct {
+ struct timeval timestamp; /* time stamped by collector */
+ int numpmid; /* number of PMIDs */
+ pmValueSet *vset[1]; /* set of value sets, one per PMID */
+} pmResult;
+
+/* High resolution event timer result from pmUnpackHighResEventRecords() */
+typedef struct {
+ struct timespec timestamp; /* time stamped by collector */
+ int numpmid; /* number of PMIDs */
+ pmValueSet *vset[1]; /* set of value sets, one per PMID */
+} pmHighResResult;
+
+/* Generic Union for Value-Type conversions */
+typedef union {
+ __int32_t l; /* 32-bit signed */
+ __uint32_t ul; /* 32-bit unsigned */
+ __int64_t ll; /* 64-bit signed */
+ __uint64_t ull; /* 64-bit unsigned */
+ float f; /* 32-bit floating point */
+ double d; /* 64-bit floating point */
+ char *cp; /* char ptr */
+ pmValueBlock *vbp; /* pmValueBlock ptr */
+} pmAtomValue;
+
+/*
+ * Fetch metrics. Value/instances returned depends on current instance profile.
+ * By default, all available instances for each requested metric id are
+ * returned. The metrics argument is terminated with PM_NULL_ID
+ *
+ * The value sets returned are in the same order as the metrics argument,
+ * and the number of value sets returned is guaranteed to be the same as
+ * the number of metrics in the agument.
+ */
+extern int pmFetch(int, pmID *, pmResult **);
+
+/*
+ * PMCD state changes returned as pmFetch function result for PM_CONTEXT_HOST
+ * contexts, i.e. when communicating with PMCD
+ */
+#define PMCD_NO_CHANGE 0
+#define PMCD_ADD_AGENT 1
+#define PMCD_RESTART_AGENT 2
+#define PMCD_DROP_AGENT 4
+
+/*
+ * Variant that is used to return a pmResult from an archive
+ */
+extern int pmFetchArchive(pmResult **);
+
+/*
+ * struct timeval is sometimes 2 x 64-bit ... we use a 2 x 32-bit format for
+ * PDUs, internally within libpcp and for (external) archive logs
+ */
+typedef struct {
+ __int32_t tv_sec; /* seconds since Jan. 1, 1970 */
+ __int32_t tv_usec; /* and microseconds */
+} __pmTimeval;
+
+typedef struct {
+ __int64_t tv_sec; /* seconds since Jan. 1, 1970 */
+ __int64_t tv_nsec; /* and nanoseconds */
+} __pmTimespec;
+
+/*
+ * Label Record at the start of every log file - as exported above
+ * the PMAPI ...
+ * NOTE MAXHOSTNAMELEN is a bad choice here for ll_hostname[], as
+ * it may vary on different hosts ... we use PM_LOG_MAXHOSTLEN instead, and
+ * size this to be the same as MAXHOSTNAMELEN in IRIX 5.3
+ * NOTE that the struct timeval means we have another struct (__pmLogLabel)
+ * for internal use that has a __pmTimeval in place of the struct timeval.
+ */
+#define PM_TZ_MAXLEN 40
+#define PM_LOG_MAXHOSTLEN 64
+#define PM_LOG_MAGIC 0x50052600
+#define PM_LOG_VERS02 0x2
+#define PM_LOG_VOL_TI -2 /* temporal index */
+#define PM_LOG_VOL_META -1 /* meta data */
+typedef struct {
+ int ll_magic; /* PM_LOG_MAGIC | log format version no. */
+ pid_t ll_pid; /* PID of logger */
+ struct timeval ll_start; /* start of this log */
+ char ll_hostname[PM_LOG_MAXHOSTLEN]; /* name of collection host */
+ char ll_tz[PM_TZ_MAXLEN]; /* $TZ at collection host */
+} pmLogLabel;
+
+/*
+ * Get the label record from the current archive context, and discover
+ * when the archive ends
+ */
+extern int pmGetArchiveLabel(pmLogLabel *);
+extern int pmGetArchiveEnd(struct timeval *);
+
+/* Free result buffer */
+extern void pmFreeResult(pmResult *);
+extern void pmFreeHighResResult(pmHighResResult *);
+
+/* Value extract from pmValue and type conversion */
+extern int pmExtractValue(int, const pmValue *, int, pmAtomValue *, int);
+
+/* Print single pmValue */
+extern void pmPrintValue(FILE *, int, int, const pmValue *, int);
+
+/* Scale conversion, based on value format, value type and scale */
+extern int pmConvScale(int, const pmAtomValue *, const pmUnits *, pmAtomValue *,
+ const pmUnits *);
+
+/* Sort instances for each metric within a pmResult */
+extern void pmSortInstances(pmResult *);
+
+/* Adjust collection time and/or mode for pmFetch */
+extern int pmSetMode(int, const struct timeval *, int);
+#define PM_MODE_LIVE 0
+#define PM_MODE_INTERP 1
+#define PM_MODE_FORW 2
+#define PM_MODE_BACK 3
+
+/* Modify the value of one or more metrics */
+extern int pmStore(const pmResult *);
+
+/* Get help and descriptive text */
+extern int pmLookupText(pmID, int, char **);
+extern int pmLookupInDomText(pmInDom, int, char **);
+#define PM_TEXT_ONELINE 1
+#define PM_TEXT_HELP 2
+
+/*
+ * Some handy formatting routines for messages, and other output
+ */
+extern const char *pmIDStr(pmID); /* NOT thread-safe */
+extern char *pmIDStr_r(pmID, char *, int);
+extern const char *pmInDomStr(pmInDom); /* NOT thread-safe */
+extern char *pmInDomStr_r(pmInDom, char *, int);
+extern const char *pmTypeStr(int); /* NOT thread-safe */
+extern char *pmTypeStr_r(int, char *, int);
+extern const char *pmUnitsStr(const pmUnits *); /* NOT thread-safe */
+extern char *pmUnitsStr_r(const pmUnits *, char *, int);
+extern const char *pmAtomStr(const pmAtomValue *, int); /* NOT thread-safe */
+extern char *pmAtomStr_r(const pmAtomValue *, int, char *, int);
+extern const char *pmNumberStr(double); /* NOT thread-safe */
+extern char *pmNumberStr_r(double, char *, int);
+extern const char *pmEventFlagsStr(int); /* NOT thread-safe */
+extern char *pmEventFlagsStr_r(int, char *, int);
+
+/* Extended time base definitions and macros */
+#define PM_XTB_FLAG 0x1000000
+
+#define PM_XTB_SET(type) (PM_XTB_FLAG | ((type) << 16))
+#define PM_XTB_GET(x) (((x) & PM_XTB_FLAG) ? (((x) & 0xff0000) >> 16) : -1)
+
+/* Parse -t, -S, -T, -A and -O options */
+extern int pmParseInterval(const char *, struct timeval *, char **);
+extern int pmParseTimeWindow(
+ const char *, const char *, const char *, const char *,
+ const struct timeval *, const struct timeval *,
+ struct timeval *, struct timeval *, struct timeval *, char **);
+
+/* Reporting timezone */
+extern int pmUseZone(const int);
+extern int pmNewZone(const char *);
+extern int pmNewContextZone(void);
+extern int pmWhichZone(char **);
+extern char *pmCtime(const time_t *, char *);
+extern struct tm *pmLocaltime(const time_t *, struct tm *);
+
+/* Parse host:metric[instances] or archive/metric[instances] */
+typedef struct {
+ int isarch; /* source type: 0 -> live host, 1 -> archive, 2 -> local context */
+ char *source; /* name of source host or archive */
+ char *metric; /* name of metric */
+ int ninst; /* number of instances, 0 -> all */
+ char *inst[1]; /* array of instance names */
+} pmMetricSpec;
+
+/* Parsing of host:metric[instances] or archive/metric[instances] */
+extern int pmParseMetricSpec(const char *, int, char *, pmMetricSpec **,
+ char **);
+extern void pmFreeMetricSpec(pmMetricSpec *p);
+
+/*
+ * Configurable error reporting
+ */
+#ifdef __GNUC__
+# define __PM_PRINTFLIKE(idx,cnt) __attribute__ ((format (printf, idx,cnt)))
+#else
+# define __PM_PRINTFLIKE(idx,cnt)
+#endif
+
+extern int pmprintf(const char *, ...) __PM_PRINTFLIKE(1,2);
+extern int pmflush(void);
+
+/*
+ * Wrapper for config/environment variables. Warning: this will exit() with
+ * a FATAL error if /etc/pcp.conf does not exist and $PCP_CONF is not set.
+ * Return values point to strings in the environment.
+ */
+extern char *pmGetConfig(const char *);
+
+/*
+ * Common command line argument parsing interfaces
+ */
+
+#define PMAPI_OPTIONS "A:a:D:gh:n:O:p:S:s:T:t:VZ:z?"
+#define PMAPI_OPTIONS_HEADER(s) { "", 0, '-', 0, (s) }
+#define PMAPI_OPTIONS_TEXT(s) { "", 0, '|', 0, (s) }
+#define PMAPI_OPTIONS_END { NULL, 0, 0, 0, NULL }
+
+#define PMOPT_ALIGN { "align", 1, 'A', "TIME", \
+ "align sample times on natural boundaries" }
+#define PMOPT_ARCHIVE { "archive", 1, 'a', "FILE", \
+ "metrics source is a PCP log archive" }
+#define PMOPT_DEBUG { "debug", 1, 'D', "DBG", \
+ NULL }
+#define PMOPT_GUIMODE { "guimode", 0, 'g', 0, \
+ "start in GUI mode with new time control" }
+#define PMOPT_HOST { "host", 1, 'h', "HOST", \
+ "metrics source is PMCD on host" }
+#define PMOPT_HOSTSFILE { "hostsfile", 1, 'H', "FILE", \
+ "read metric source hosts from a file" }
+#define PMOPT_SPECLOCAL { "spec-local", 1, 'K', "SPEC", \
+ "optional additional PMDA spec for local connection" }
+#define PMOPT_LOCALPMDA { "local-PMDA", 0, 'L', 0, \
+ "metrics source is local connection to a PMDA" }
+#define PMOPT_NAMESPACE { "namespace", 1, 'n', "FILE", \
+ "use an alternative PMNS" }
+#define PMOPT_DUPNAMES { "dupnames", 1, 'N', "FILE", \
+ "use an alternative PMNS (duplicate names allowed)" }
+#define PMOPT_ORIGIN { "origin", 1, 'O', "TIME", \
+ "initial sample time within the time window" }
+#define PMOPT_GUIPORT { "guiport", 1, 'p', "N", \
+ "port for connection to existing time control" }
+#define PMOPT_START { "start", 1, 'S', "TIME", \
+ "start of the time window" }
+#define PMOPT_SAMPLES { "samples", 1, 's', "N", \
+ "terminate after this many samples" }
+#define PMOPT_FINISH { "finish", 1, 'T', "TIME", \
+ "end of the time window" }
+#define PMOPT_INTERVAL { "interval", 1, 't', "DELTA", \
+ "sampling interval" }
+#define PMOPT_VERSION { "version", 0, 'V', 0, \
+ "display version number and exit" }
+#define PMOPT_TIMEZONE { "timezone", 1, 'Z', "TZ", \
+ "set reporting timezone" }
+#define PMOPT_HOSTZONE { "hostzone", 0, 'z', 0, \
+ "set reporting timezone to local time of metrics source" }
+#define PMOPT_HELP { "help", 0, '?', 0, \
+ "show this usage message and exit" }
+
+#define PMAPI_GENERAL_OPTIONS \
+ PMAPI_OPTIONS_HEADER("General options"), \
+ PMOPT_ALIGN, \
+ PMOPT_ARCHIVE, \
+ PMOPT_DEBUG, \
+ PMOPT_GUIMODE, \
+ PMOPT_HOST, \
+ PMOPT_NAMESPACE, \
+ PMOPT_ORIGIN, \
+ PMOPT_GUIPORT, \
+ PMOPT_START, \
+ PMOPT_SAMPLES, \
+ PMOPT_FINISH, \
+ PMOPT_INTERVAL, \
+ PMOPT_TIMEZONE, \
+ PMOPT_HOSTZONE, \
+ PMOPT_VERSION, \
+ PMOPT_HELP
+
+/* long-only standard options */
+#define PMLONGOPT_ARCHIVE_LIST "archive-list"
+#define PMOPT_ARCHIVE_LIST { PMLONGOPT_ARCHIVE_LIST, 1, 0, "FILES", \
+ "comma-separated list of metric source archives" }
+#define PMLONGOPT_ARCHIVE_FOLIO "archive-folio"
+#define PMOPT_ARCHIVE_FOLIO { PMLONGOPT_ARCHIVE_FOLIO, 1, 0, "FILE", \
+ "read metric source archives from a folio" }
+#define PMLONGOPT_HOST_LIST "host-list"
+#define PMOPT_HOST_LIST { PMLONGOPT_HOST_LIST, 1, 0, "HOSTS", \
+ "comma-separated list of metric source hosts" }
+
+/* pmOptions flags */
+#define PM_OPTFLAG_INIT (1<<0) /* initialisation done */
+#define PM_OPTFLAG_DONE (1<<1) /* parsing is complete */
+#define PM_OPTFLAG_MULTI (1<<2) /* allow multi-context */
+#define PM_OPTFLAG_USAGE_ERR (1<<3) /* argument parse fail */
+#define PM_OPTFLAG_RUNTIME_ERR (1<<4) /* any runtime failure */
+#define PM_OPTFLAG_EXIT (1<<5) /* tool should exit(0) */
+#define PM_OPTFLAG_POSIX (1<<6) /* POSIXLY_CORRECT set */
+#define PM_OPTFLAG_MIXED (1<<7) /* allow hosts+archives */
+#define PM_OPTFLAG_ENV_ONLY (1<<8) /* use env options only */
+#define PM_OPTFLAG_LONG_ONLY (1<<9) /* use long options only */
+#define PM_OPTFLAG_BOUNDARIES (1<<10) /* calculate time window */
+#define PM_OPTFLAG_STDOUT_TZ (1<<11) /* write timezone change */
+#define PM_OPTFLAG_NOFLUSH (1<<12) /* caller issues pmflush */
+#define PM_OPTFLAG_QUIET (1<<13) /* silence getopt errors */
+
+struct __pmOptions;
+typedef int (*pmOptionOverride)(int, struct __pmOptions *);
+
+typedef struct {
+ const char * long_opt;
+ int has_arg;
+ int short_opt;
+ const char * argname;
+ const char * message;
+} pmLongOptions;
+
+typedef struct __pmOptions {
+ int version;
+ int flags;
+
+ /* in: define set of all options */
+ const char * short_options;
+ pmLongOptions * long_options;
+ const char * short_usage;
+
+ /* in: method for general override */
+ pmOptionOverride override;
+
+ /* out: usual getopt information */
+ int index;
+ int optind;
+ int opterr;
+ int optopt;
+ char *optarg;
+
+ /* internals; do not ever access */
+ int __initialized;
+ char * __nextchar;
+ int __ordering;
+ int __posixly_correct;
+ int __first_nonopt;
+ int __last_nonopt;
+
+ /* out: error count */
+ int errors;
+
+ /* out: PMAPI options and values */
+ int context; /* PM_CONTEXT_{HOST,ARCHIVE,LOCAL} */
+ int nhosts;
+ int narchives;
+ char ** hosts;
+ char ** archives;
+ struct timeval start;
+ struct timeval finish;
+ struct timeval origin;
+ struct timeval interval;
+ char * align_optarg;
+ char * start_optarg;
+ char * finish_optarg;
+ char * origin_optarg;
+ char * guiport_optarg;
+ char * timezone;
+ int samples;
+ int guiport;
+ int padding;
+ unsigned int guiflag : 1;
+ unsigned int tzflag : 1;
+ unsigned int nsflag : 1;
+ unsigned int Lflag : 1;
+ unsigned int zeroes : 28;
+} pmOptions;
+
+extern int pmgetopt_r(int, char *const *, pmOptions *);
+extern int pmGetOptions(int, char *const *, pmOptions *);
+extern int pmGetContextOptions(int, pmOptions *);
+extern void pmUsageMessage(pmOptions *);
+extern void pmFreeOptions(pmOptions *);
+
+/*
+ * Derived Metrics support
+ */
+extern int pmLoadDerivedConfig(const char *);
+extern char *pmRegisterDerived(const char *, const char *);
+extern char *pmDerivedErrStr(void);
+
+/*
+ * Event Record support
+ */
+typedef struct {
+ pmID ep_pmid;
+ /* vtype and vlen fields the format same as for pmValueBlock */
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int ep_type : 8; /* value type */
+ unsigned int ep_len : 24; /* bytes for type/len + vbuf */
+#else
+ unsigned int ep_len : 24; /* bytes for type/len + vbuf */
+ unsigned int ep_type : 8; /* value type */
+#endif
+ /* actual value (vbuf) goes here ... */
+} pmEventParameter;
+
+typedef struct {
+ __pmTimeval er_timestamp; /* must be 2 x 32-bit format */
+ unsigned int er_flags; /* event record characteristics */
+ int er_nparams; /* number of er_param[] entries */
+ pmEventParameter er_param[1];
+} pmEventRecord;
+
+typedef struct {
+ __pmTimespec er_timestamp; /* must be 2 x 64-bit format */
+ unsigned int er_flags; /* event record characteristics */
+ int er_nparams; /* number of er_param[] entries */
+ pmEventParameter er_param[1];
+} pmHighResEventRecord;
+
+/* Potential flags bits set in er_flags (above) */
+#define PM_EVENT_FLAG_POINT (1U<<0) /* an observation, default type */
+#define PM_EVENT_FLAG_START (1U<<1) /* marking start of a new event */
+#define PM_EVENT_FLAG_END (1U<<2) /* completion of a traced event */
+#define PM_EVENT_FLAG_ID (1U<<3) /* 1st parameter is a trace ID */
+#define PM_EVENT_FLAG_PARENT (1U<<4) /* 2nd parameter is parents ID */
+#define PM_EVENT_FLAG_MISSED (1U<<31)/* nparams shows #missed events */
+
+typedef struct {
+ /* align initial declarations with start of pmValueBlock */
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int ea_type : 8; /* value type */
+ unsigned int ea_len : 24; /* bytes for type/len + vbuf */
+#else
+ unsigned int ea_len : 24; /* bytes for type/len + vbuf */
+ unsigned int ea_type : 8; /* value type */
+#endif
+ /* real event records start here */
+ int ea_nrecords; /* number of ea_record[] entries */
+ pmEventRecord ea_record[1];
+} pmEventArray;
+
+typedef struct {
+ /* align initial declarations with start of pmValueBlock */
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int ea_type : 8; /* value type */
+ unsigned int ea_len : 24; /* bytes for type/len + vbuf */
+#else
+ unsigned int ea_len : 24; /* bytes for type/len + vbuf */
+ unsigned int ea_type : 8; /* value type */
+#endif
+ /* real event records start here */
+ int ea_nrecords; /* number of ea_record[] entries */
+ pmHighResEventRecord ea_record[1];
+} pmHighResEventArray;
+
+/* Unpack a PM_TYPE_EVENT value into a set on pmResults */
+extern int pmUnpackEventRecords(pmValueSet *, int, pmResult ***);
+
+/* Free set of pmResults from pmUnpackEventRecords */
+extern void pmFreeEventResult(pmResult **);
+
+/* Unpack a PM_TYPE_HIGHRES_EVENT value into a set on pmHighResResults */
+extern int pmUnpackHighResEventRecords(pmValueSet *, int, pmHighResResult ***);
+
+/* Free set of pmHighResResults from pmUnpackEventRecords */
+extern void pmFreeHighResEventResult(pmHighResResult **);
+
+/* Service discovery, for clients. */
+#define PM_SERVER_SERVICE_SPEC "pmcd"
+#define PM_SERVER_PROXY_SPEC "pmproxy"
+#define PM_SERVER_WEBD_SPEC "pmwebd"
+
+extern int pmDiscoverServices(const char *, const char *, char ***);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PMAPI_H */
diff --git a/src/include/pcp/pmda.h b/src/include/pcp/pmda.h
new file mode 100644
index 0000000..0b6eba1
--- /dev/null
+++ b/src/include/pcp/pmda.h
@@ -0,0 +1,725 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PMDA_H
+#define _PMDA_H
+
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * libpcp_pmda interface versions
+ */
+#define PMDA_INTERFACE_2 2 /* new function arguments */
+#define PMDA_INTERFACE_3 3 /* 3-state return from fetch callback */
+#define PMDA_INTERFACE_4 4 /* dynamic pmns */
+#define PMDA_INTERFACE_5 5 /* client context in pmda and */
+ /* 4-state return from fetch callback */
+#define PMDA_INTERFACE_6 6 /* client security attributes in pmda */
+#define PMDA_INTERFACE_LATEST 6
+
+/*
+ * Type of I/O connection to PMCD (pmdaUnknown defaults to pmdaPipe)
+ */
+typedef enum {pmdaPipe, pmdaInet, pmdaUnix, pmdaUnknown, pmdaIPv6} pmdaIoType;
+
+/*
+ * Instance description: index and name
+ */
+typedef struct {
+ int i_inst; /* internal instance identifier */
+ char *i_name; /* external instance identifier */
+} pmdaInstid;
+
+/*
+ * Instance domain description: unique instance id, number of instances in
+ * this domain, and the list of instances (not null terminated).
+ */
+typedef struct {
+ pmInDom it_indom; /* indom, filled in */
+ int it_numinst; /* number of instances */
+ pmdaInstid *it_set; /* instance identifiers */
+} pmdaIndom;
+
+/*
+ * Metric description: handle for extending description, and the description.
+ */
+typedef struct {
+ void *m_user; /* for users external use */
+ pmDesc m_desc; /* metric description */
+} pmdaMetric;
+
+/*
+ * Type of function call back used by pmdaFetch.
+ */
+typedef int (*pmdaFetchCallBack)(pmdaMetric *, unsigned int, pmAtomValue *);
+
+/*
+ * return values for a pmdaFetchCallBack method
+ */
+#define PMDA_FETCH_NOVALUES 0
+#define PMDA_FETCH_STATIC 1
+#define PMDA_FETCH_DYNAMIC 2 /* free avp->vp after __pmStuffValue */
+
+/*
+ * Type of function call back used by pmdaMain to clean up a pmResult structure
+ * after a fetch.
+ */
+typedef void (*pmdaResultCallBack)(pmResult *);
+
+/*
+ * Type of function call back used by pmdaMain on receipt of each PDU to check
+ * availability, etc.
+ */
+typedef int (*pmdaCheckCallBack)(void);
+
+/*
+ * Type of function call back used by pmdaMain after each PDU has been
+ * processed.
+ */
+typedef void (*pmdaDoneCallBack)(void);
+
+/*
+ * Type of function call back used by pmdaMain when a client context is
+ * closed by PMCD.
+ */
+typedef void (*pmdaEndContextCallBack)(int);
+
+/*
+ * Forward declarations of structures so that inclusion of (internal) impl.h
+ * header file is not mandated if this header file is included.
+ */
+typedef struct __pmnsTree pmdaNameSpace;
+typedef struct __pmHashCtl pmdaHashTable;
+typedef struct __pmProfile pmdaInProfile;
+typedef struct __pmInResult pmdaInResult;
+
+/*
+ * libpcp_pmda extension structure.
+ *
+ * The fields of this structure are initialised using pmdaDaemon() or pmdaDSO()
+ * (if the agent is a daemon or a DSO respectively), pmdaGetOpt() and
+ * pmdaInit().
+ *
+ */
+typedef struct {
+
+ unsigned int e_flags; /* PMDA_EXT_FLAG_* bit field */
+ void *e_ext; /* used internally within libpcp_pmda */
+
+ char *e_sockname; /* socket name to pmcd */
+ char *e_name; /* name of this pmda */
+ char *e_logfile; /* path to log file */
+ char *e_helptext; /* path to help text */
+ int e_status; /* =0 is OK */
+ int e_infd; /* input file descriptor from pmcd */
+ int e_outfd; /* output file descriptor to pmcd */
+ int e_port; /* port to pmcd */
+ int e_singular; /* =0 for singular values */
+ int e_ordinal; /* >=0 for non-singular values */
+ int e_direct; /* =1 if pmid map to meta table */
+ int e_domain; /* metrics domain */
+ int e_nmetrics; /* number of metrics */
+ int e_nindoms; /* number of instance domains */
+ int e_help; /* help text comes via this handle */
+ pmdaInProfile *e_prof; /* last received profile */
+ pmdaIoType e_io; /* connection type to pmcd */
+ pmdaIndom *e_indoms; /* instance domain table */
+ pmdaIndom *e_idp; /* used in instance domain expansion */
+ pmdaMetric *e_metrics; /* metric description table */
+
+ pmdaResultCallBack e_resultCallBack; /* callback to clean up pmResult after fetch */
+ pmdaFetchCallBack e_fetchCallBack; /* callback to assign metric values in fetch */
+ pmdaCheckCallBack e_checkCallBack; /* callback on receipt of a PDU */
+ pmdaDoneCallBack e_doneCallBack; /* callback after PDU has been processed */
+ /* added for PMDA_INTERFACE_5 */
+ int e_context; /* client context id from pmcd */
+ pmdaEndContextCallBack e_endCallBack; /* callback after client context closed */
+} pmdaExt;
+
+#define PMDA_EXT_FLAG_DIRECT 0x01 /* direct mapped PMID metric table */
+#define PMDA_EXT_FLAG_HASHED 0x02 /* hashed PMID metric table lookup */
+#define PMDA_EXT_SETUPDONE 0x04 /* __pmdaSetup() has been called */
+#define PMDA_EXT_CONNECTED 0x08 /* pmdaConnect() done */
+#define PMDA_EXT_NOTREADY 0x10 /* pmcd connection marked NOTREADY */
+
+/*
+ * Optionally restrict symbol visibility for DSO PMDAs
+ *
+ * When compiled with -fvisibility=hidden this directive can be used
+ * to set up the init routine so that it is the only symbol exported
+ * by the DSO PMDA. This gives the compiler opportunity to generate
+ * more optimal code as well as ensuring that just the one symbol is
+ * exported (which is a good idea in itself).
+ */
+#ifdef __GNUC__
+# define __PMDA_INIT_CALL __attribute__ ((visibility ("default")))
+#else
+# define __PMDA_INIT_CALL
+#endif
+
+/*
+ * Interface Definitions for PMDA DSO Interface
+ * The new interface structure makes use of a union to manage new revisions
+ * cleanly. The structure for each new version must be backward compatible
+ * to all of the previous versions (i.e. contain earlier fields unchanged).
+ *
+ * The domain field is set by pmcd(1) in the case of a DSO PMDA, and by
+ * pmdaDaemon and pmdaGetOpt in the case of a Daemon PMDA. It should not be
+ * modified.
+ */
+typedef struct {
+ int domain; /* performance metrics domain id */
+ struct {
+ unsigned int pmda_interface : 8; /* PMDA DSO interface version */
+ unsigned int pmapi_version : 8; /* PMAPI version */
+ unsigned int flags : 16; /* optional feature flags */
+ } comm; /* set/return communication and version info */
+ int status; /* return initialization status here */
+
+ union {
+
+/*
+ * Interface Version 2 and 3 (PCP 2.0)
+ * PMDA_INTERFACE_2 and PMDA_INTERFACE_3
+ */
+
+ struct {
+ pmdaExt *ext;
+ int (*profile)(pmdaInProfile *, pmdaExt *);
+ int (*fetch)(int, pmID *, pmResult **, pmdaExt *);
+ int (*desc)(pmID, pmDesc *, pmdaExt *);
+ int (*instance)(pmInDom, int, char *, pmdaInResult **, pmdaExt *);
+ int (*text)(int, int, char **, pmdaExt *);
+ int (*store)(pmResult *, pmdaExt *);
+ } any, two, three;
+
+/*
+ * Interface Version 4 (dynamic pmns support) and Version 5 (client context
+ * in PMDA).
+ * PMDA_INTERFACE_4, PMDA_INTERFACE_5
+ */
+
+ struct {
+ pmdaExt *ext;
+ int (*profile)(pmdaInProfile *, pmdaExt *);
+ int (*fetch)(int, pmID *, pmResult **, pmdaExt *);
+ int (*desc)(pmID, pmDesc *, pmdaExt *);
+ int (*instance)(pmInDom, int, char *, pmdaInResult **, pmdaExt *);
+ int (*text)(int, int, char **, pmdaExt *);
+ int (*store)(pmResult *, pmdaExt *);
+ int (*pmid)(const char *, pmID *, pmdaExt *);
+ int (*name)(pmID, char ***, pmdaExt *);
+ int (*children)(const char *, int, char ***, int **, pmdaExt *);
+ } four, five;
+
+/*
+ * Interface Version 6 (client context security attributes in PMDA).
+ * PMDA_INTERFACE_6
+ */
+ struct {
+ pmdaExt *ext;
+ int (*profile)(pmdaInProfile *, pmdaExt *);
+ int (*fetch)(int, pmID *, pmResult **, pmdaExt *);
+ int (*desc)(pmID, pmDesc *, pmdaExt *);
+ int (*instance)(pmInDom, int, char *, pmdaInResult **, pmdaExt *);
+ int (*text)(int, int, char **, pmdaExt *);
+ int (*store)(pmResult *, pmdaExt *);
+ int (*pmid)(const char *, pmID *, pmdaExt *);
+ int (*name)(pmID, char ***, pmdaExt *);
+ int (*children)(const char *, int, char ***, int **, pmdaExt *);
+ int (*attribute)(int, int, const char *, int, pmdaExt *);
+ } six;
+
+ } version;
+
+} pmdaInterface;
+
+/*
+ * PM_CONTEXT_LOCAL support
+ */
+typedef struct {
+ int domain;
+ char *name;
+ char *init;
+ void *handle;
+ pmdaInterface dispatch;
+} __pmDSO;
+
+extern __pmDSO *__pmLookupDSO(int /*domain*/);
+
+/* Macro that can be used to create each metrics' PMID. */
+#define PMDA_PMID(x,y) (((x)<<10)|(y))
+
+/* Macro for pmUnits bitmap in a pmDesc declaration */
+#ifdef HAVE_BITFIELDS_LTOR
+#define PMDA_PMUNITS(a,b,c,d,e,f) {a,b,c,d,e,f,0}
+#else
+#define PMDA_PMUNITS(a,b,c,d,e,f) {0,f,e,d,c,b,a}
+#endif
+
+/* Command line option processing macros and data structures */
+
+#define PMDA_OPTIONS "D:d:h:i:l:pu:U:"
+#define PMDA_OPTIONS_HEADER(s) { "", 0, '-', 0, (s) }
+#define PMDA_OPTIONS_TEXT(s) { "", 0, '|', 0, (s) }
+#define PMDA_OPTIONS_END { NULL, 0, 0, 0, NULL }
+
+#define PMDAOPT_DEBUG { "debug", 1, 'D', "DBG", \
+ NULL }
+#define PMDAOPT_DOMAIN { "domain", 1, 'd', "NUM", \
+ "use domain (numeric) for metrics domain of PMDA" }
+#define PMDAOPT_HELPTEXT { "helpfile", 1, 'h', "FILE", \
+ "path to PMDA metrics help text file" }
+#define PMDAOPT_INET { "inet", 1, 'i', "PORT", \
+ "pmcd IPv4 connection service name or numeric port" }
+#define PMDAOPT_IPV6 { "ipv6", 1, '6', "PORT", \
+ "pmcd IPv6 connection service name or numeric port" }
+#define PMDAOPT_LOGFILE { "log", 1, 'l', "FILE", \
+ "write log to FILE rather than using default log name" }
+#define PMDAOPT_PIPE { "pipe", 0, 'p', 0, \
+ "use a pipe for communication with pmcd" }
+#define PMDAOPT_UNIX { "unix", 1, 'u', "FILE", \
+ "use a unix domain socket for communication with pmcd" }
+#define PMDAOPT_USERNAME { "username", 1, 'U', "USER", \
+ "run the PMDA using the named user account" }
+
+struct __pmdaOptions;
+typedef int (*pmdaOptionOverride)(int, struct __pmdaOptions *);
+
+typedef struct __pmdaOptions {
+ int version;
+ int flags;
+ const char * short_options;
+ pmLongOptions * long_options;
+ const char * short_usage;
+ pmdaOptionOverride override;
+
+ /* out: usual getopt information */
+ int index;
+ int optind;
+ int opterr;
+ int optopt;
+ char *optarg;
+
+ /* internals; do not ever access */
+ int __initialized;
+ char * __nextchar;
+ int __ordering;
+ int __posixly_correct;
+ int __first_nonopt;
+ int __last_nonopt;
+
+ /* out: error count */
+ int errors;
+
+ /* out: PMDA options (non-pmdaInterface options) */
+ char * username;
+} pmdaOptions;
+
+
+/*
+ * PMDA Setup Routines.
+ *
+ * pmdaGetOpt
+ * pmdaGetOptions
+ * pmdaUsageMessage
+ * Replacements for pmgetopt_r(3) which strip out the standard PMDA flags
+ * before returning the next command line option. The standard PMDA
+ * flags are PMDA_OPTIONS which will initialise the pmdaExt structure
+ * with the IPC details, path to the log and help file, domain number,
+ * and the user account under which the PMDA should run.
+ * An error counter will be incremented if there was an error parsing any
+ * of these options.
+ *
+ * pmdaDaemon
+ * Setup the pmdaInterface structure for a daemon and initialise
+ * the pmdaExt structure with the PMDA's name, domain and path to
+ * the log file and help file. The libpcp internal state is also
+ * initialised.
+ *
+ * pmdaDSO
+ * Setup the pmdaInterface structure for a DSO and initialise the
+ * pmdaExt structure with the PMDA's name and help file.
+ *
+ * pmdaOpenLog
+ * Redirects stderr to the logfile.
+ *
+ * pmdaSetFlags
+ * Allow behaviour flags to be set to enable features, such as to request
+ * libpcp_pmda internally use direct or hashed PMID metric table mapping.
+ * Can be called multiple times - effects are cumulative - no flag can be
+ * cleared, although libpcp_pmda may disable a flag later on if it cannot
+ * enact the requested behaviour.
+ *
+ * pmdaInit
+ * Further initialises the pmdaExt structure with the instance domain and
+ * metric structures. Unique identifiers are applied to each instance
+ * domain and metric. Also open the help text file and checks whether the
+ * metrics can be directly mapped.
+ *
+ * pmdaConnect
+ * Connect to the PMCD process using the method set in the pmdaExt e_io
+ * field.
+ *
+ * pmdaMain
+ * Loop which receives PDUs and dispatches the callbacks. Must be called
+ * by a daemon PMDA.
+ *
+ * pmdaSetResultCallBack
+ * Allows an application specific routine to be specified for cleaning up
+ * a pmResult after a fetch. Most PMDAs should not use this.
+ *
+ * pmdaSetFetchCallBack
+ * Allows an application specific routine to be specified for completing a
+ * pmAtom structure with a metrics value. This must be set if pmdaFetch is
+ * used as the fetch callback.
+ *
+ * pmdaSetCheckCallBack
+ * Allows an application specific routine to be called upon receipt of any
+ * PDU. For all PDUs except PDU_PROFILE, a result less than zero
+ * indicates an error. If set to zero (which is also the default),
+ * the callback is ignored.
+ *
+ * pmdaSetDoneCallBack
+ * Allows an application specific routine to be called after each PDU is
+ * processed. The result is ignored. If set to zero (which is also
+ * the default), the callback is ignored.
+ *
+ * pmdaSetEndContextCallBack
+ * Allows an application specific routine to be called when a
+ * PMCD context is closed, so any per-context state can be cleaned
+ * up. If set to zero (which is also the default), the callback
+ * is ignored.
+ */
+
+extern int pmdaGetOpt(int, char *const *, const char *, pmdaInterface *, int *);
+extern int pmdaGetOptions(int, char *const *, pmdaOptions *, pmdaInterface *);
+extern void pmdaUsageMessage(pmdaOptions *);
+extern void pmdaDaemon(pmdaInterface *, int, char *, int , char *, char *);
+extern void pmdaDSO(pmdaInterface *, int, char *, char *);
+extern void pmdaOpenLog(pmdaInterface *);
+extern void pmdaSetFlags(pmdaInterface *, int);
+extern void pmdaInit(pmdaInterface *, pmdaIndom *, int, pmdaMetric *, int);
+extern void pmdaConnect(pmdaInterface *);
+
+extern void pmdaMain(pmdaInterface *);
+
+extern void pmdaSetResultCallBack(pmdaInterface *, pmdaResultCallBack);
+extern void pmdaSetFetchCallBack(pmdaInterface *, pmdaFetchCallBack);
+extern void pmdaSetCheckCallBack(pmdaInterface *, pmdaCheckCallBack);
+extern void pmdaSetDoneCallBack(pmdaInterface *, pmdaDoneCallBack);
+extern void pmdaSetEndContextCallBack(pmdaInterface *, pmdaEndContextCallBack);
+
+/*
+ * Callbacks to PMCD which should be adequate for most PMDAs.
+ * NOTE: if pmdaFetch is used, e_callback must be specified in the pmdaExt
+ * structure.
+ *
+ * pmdaProfile
+ * Store the instance profile away for the next fetch.
+ *
+ * pmdaFetch
+ * Resize the pmResult and call e_callback in the pmdaExt structure
+ * for each metric instance required by the profile.
+ *
+ * pmdaInstance
+ * Return description of instances and instance domains.
+ *
+ * pmdaDesc
+ * Return the metric desciption.
+ *
+ * pmdaText
+ * Return the help text for the metric.
+ *
+ * pmdaStore
+ * Store a value into a metric. This is a no-op.
+ *
+ * pmdaPMID
+ * Return the PMID for a named metric within a dynamic subtree
+ * of the PMNS.
+ *
+ * pmdaName
+ * Given a PMID, return the names of all matching metrics within a
+ * dynamic subtree of the PMNS.
+ *
+ * pmdaChildren
+ * If traverse == 0, return the names of all the descendent children
+ * and their status, given a named metric in a dynamic subtree of
+ * the PMNS (this is the pmGetChildren or pmGetChildrenStatus variant).
+ * If traverse == 1, return the full names of all descendent metrics
+ * (this is the pmTraversePMNS variant, with the status added)
+ *
+ * pmdaAttribute
+ * Inform the agent about security aspects of a client connection,
+ * such as the authenticated userid. Passed in a client identifier,
+ * numeric PCP_ATTR, pointer to the associated value, and the length
+ * of the value.
+ */
+
+extern int pmdaProfile(pmdaInProfile *, pmdaExt *);
+extern int pmdaFetch(int, pmID *, pmResult **, pmdaExt *);
+extern int pmdaInstance(pmInDom, int, char *, pmdaInResult **, pmdaExt *);
+extern int pmdaDesc(pmID, pmDesc *, pmdaExt *);
+extern int pmdaText(int, int, char **, pmdaExt *);
+extern int pmdaStore(pmResult *, pmdaExt *);
+extern int pmdaPMID(const char *, pmID *, pmdaExt *);
+extern int pmdaName(pmID, char ***, pmdaExt *);
+extern int pmdaChildren(const char *, int, char ***, int **, pmdaExt *);
+extern int pmdaAttribute(int, int, const char *, int, pmdaExt *);
+
+/*
+ * PMDA "help" text manipulation
+ */
+extern int pmdaOpenHelp(char *);
+extern void pmdaCloseHelp(int);
+extern char *pmdaGetHelp(int, pmID, int);
+extern char *pmdaGetInDomHelp(int, pmInDom, int);
+
+/*
+ * Dynamic metric table manipulation
+ *
+ * pmdaDynamicPMNS
+ * Register a new dynamic namespace sub-tree associated with one or more
+ * PMID clusters. Callbacks are passed in to deal with PMDA-specific
+ * components (names, help text, metric duplication, and table sizing).
+ *
+ * pmdaDynamicLookupName
+ * Perform PMDA name lookup operations for the name callback, for dynamic
+ * metrics.
+ *
+ * pmdaDynamicLookupPMID
+ * Perform PMDA reverse name lookup operations for the PMID callback, for
+ * dynamic metrics.
+ *
+ * pmdaDynamicLookupText
+ * Perform PMDA help text lookup operations for dynamic metrics.
+ *
+ * pmdaDynamicMetricTable
+ * Install a new metric table for the PMDA, after changes to the set of
+ * metrics which the PMDA must export (IOW, dynamic metrics are in use).
+ *
+ * pmdaRehash
+ * Update the metrictable within the pmdaExt structure with new (dynamic)
+ * metrics and recompute the hash table used for optimised lookup. Aids
+ * PMDAs with large numbers of metrics to get closer to directly mapped
+ * PMID lookup time, rather than multiple linear table scans per fetch.
+ * [NOTE: can be used by any interface version, not only dynamic metrics]
+ */
+typedef int (*pmdaUpdatePMNS)(pmdaExt *, pmdaNameSpace **);
+typedef int (*pmdaUpdateText)(pmdaExt *, pmID, int, char **);
+typedef void (*pmdaUpdateMetric)(pmdaMetric *, pmdaMetric *, int);
+typedef void (*pmdaCountMetrics)(int *, int *);
+extern void pmdaDynamicPMNS(const char *, int *, int,
+ pmdaUpdatePMNS, pmdaUpdateText,
+ pmdaUpdateMetric, pmdaCountMetrics,
+ pmdaMetric *, int);
+extern int pmdaDynamicSetClusterMask(const char *, unsigned int);
+extern pmdaNameSpace *pmdaDynamicLookupName(pmdaExt *, const char *);
+extern pmdaNameSpace *pmdaDynamicLookupPMID(pmdaExt *, pmID);
+extern int pmdaDynamicLookupText(pmID, int, char **, pmdaExt *);
+extern void pmdaDynamicMetricTable(pmdaExt *);
+
+extern void pmdaRehash(pmdaExt *, pmdaMetric *, int);
+
+/*
+ * Dynamic names interface (version 4) helper routines.
+ *
+ * pmdaTreePMID
+ * when a pmdaNameSpace is being used, this provides
+ * an implementation for the four.pmid() interface.
+ *
+ * pmdaTreeName
+ * when a pmdaNameSpace is being used, this provides
+ * an implementation for the four.name() interface.
+ *
+ * pmdaTreeChildren
+ * when a pmdaNameSpace is being used, this provides
+ * an implementation for the four.children() interface.
+ *
+ * pmdaTreeRebuildHash
+ * iterate over a pmdaNameSpace and (re)build the hash
+ * for any subsequent PMID -> name (reverse) lookups
+ *
+ * pmdaTreeSize
+ * returns the numbers of entries in a pmdaNameSpace.
+ */
+extern int pmdaTreePMID(pmdaNameSpace *, const char *, pmID *);
+extern int pmdaTreeName(pmdaNameSpace *, pmID, char ***);
+extern int pmdaTreeChildren(pmdaNameSpace *, const char *, int, char ***, int **);
+extern void pmdaTreeRebuildHash(pmdaNameSpace *, int);
+extern int pmdaTreeSize(pmdaNameSpace *);
+
+/*
+ * PMDA instance domain cache support
+ *
+ * pmdaCacheStore
+ * add entry into the cache, or change state, assigns internal
+ * instance identifier
+ *
+ * pmdaCacheStoreKey
+ * add entry into the cache, or change state, caller provides "hint"
+ * for internal instance identifier
+ *
+ * pmdaCacheLookup
+ * fetch entry based on internal instance identifier
+ *
+ * pmdaCacheLookupName
+ * fetch entry based on external instance name
+ *
+ * pmdaCacheLookupKey
+ * fetch entry based on key as "hint", like pmdaCacheStoreKey()
+ *
+ * pmdaCacheOp
+ * service routines to load, unload, mark as write-thru, purge,
+ * count entries, etc
+ *
+ * pmdaCachePurge
+ * cull inactive entries
+ */
+extern int pmdaCacheStore(pmInDom, int, const char *, void *);
+extern int pmdaCacheStoreKey(pmInDom, int, const char *, int, const void *, void *);
+extern int pmdaCacheLookup(pmInDom, int, char **, void **);
+extern int pmdaCacheLookupName(pmInDom, const char *, int *, void **);
+extern int pmdaCacheLookupKey(pmInDom, const char *, int, const void *, char **, int *, void **);
+extern int pmdaCacheOp(pmInDom, int);
+extern int pmdaCachePurge(pmInDom, time_t);
+
+#define PMDA_CACHE_LOAD 1
+#define PMDA_CACHE_ADD 2
+#define PMDA_CACHE_HIDE 3
+#define PMDA_CACHE_CULL 4
+#define PMDA_CACHE_EMPTY 5
+#define PMDA_CACHE_SAVE 6
+#define PMDA_CACHE_STRINGS 7
+#define PMDA_CACHE_ACTIVE 8
+#define PMDA_CACHE_INACTIVE 9
+#define PMDA_CACHE_SIZE 10
+#define PMDA_CACHE_SIZE_ACTIVE 11
+#define PMDA_CACHE_SIZE_INACTIVE 12
+#define PMDA_CACHE_REUSE 13
+#define PMDA_CACHE_WALK_REWIND 14
+#define PMDA_CACHE_WALK_NEXT 15
+#define PMDA_CACHE_CHECK 16
+#define PMDA_CACHE_REORG 17
+#define PMDA_CACHE_SYNC 18
+#define PMDA_CACHE_DUMP 19
+#define PMDA_CACHE_DUMP_ALL 20
+
+/*
+ * Internal libpcp_pmda routines.
+ *
+ * __pmdaCntInst
+ * Returns the number of instances for an entry in the instance domain
+ * table.
+ *
+ * __pmdaStartInst
+ * Setup for __pmdaNextInst to iterate over an instance domain.
+ *
+ * __pmdaNextInst
+ * Step through an instance domain, returning instances one at a
+ * time.
+ *
+ * __pmdaMainPDU
+ * Use this when you need to override pmdaMain and construct
+ * your own loop.
+ * Call this function in the _body_ of your loop.
+ * See pmdaMain code for an example.
+ * Returns negative int on failure, 0 otherwise.
+ *
+ * __pmdaInFd
+ * This returns the file descriptor that is used to get the
+ * PDU from pmcd.
+ * One may use the fd to do a select call in a custom main loop.
+ * Returns negative int on failure, file descriptor otherwise.
+ *
+ * __pmdaCacheDumpAll and __pmdaCacheDump
+ * print out cache contents
+ */
+
+extern int __pmdaCntInst(pmInDom, pmdaExt *);
+extern void __pmdaStartInst(pmInDom, pmdaExt *);
+extern int __pmdaNextInst(int *, pmdaExt *);
+
+extern int __pmdaMainPDU(pmdaInterface *);
+extern int __pmdaInFd(pmdaInterface *);
+
+extern void __pmdaCacheDumpAll(FILE *, int);
+extern void __pmdaCacheDump(FILE *, pmInDom, int);
+
+/*
+ * Client Context support
+ */
+extern int pmdaGetContext(void);
+extern void __pmdaSetContext(int);
+
+/*
+ * Event Record support
+ */
+extern int pmdaEventNewArray(void);
+extern int pmdaEventResetArray(int);
+extern int pmdaEventReleaseArray(int);
+extern int pmdaEventAddRecord(int, struct timeval *, int);
+extern int pmdaEventAddMissedRecord(int, struct timeval *, int);
+extern int pmdaEventAddParam(int, pmID, int, pmAtomValue *);
+extern pmEventArray *pmdaEventGetAddr(int);
+
+/*
+ * High Resolution Timer Event Record support
+ */
+extern int pmdaEventNewHighResArray(void);
+extern int pmdaEventResetHighResArray(int);
+extern int pmdaEventReleaseHighResArray(int);
+extern int pmdaEventAddHighResRecord(int, struct timespec *, int);
+extern int pmdaEventAddHighResMissedRecord(int, struct timespec *, int);
+extern int pmdaEventHighResAddParam(int, pmID, int, pmAtomValue *);
+extern pmHighResEventArray *pmdaEventHighResGetAddr(int);
+
+/*
+ * Event Queue support
+ */
+extern int pmdaEventNewQueue(const char *, size_t);
+extern int pmdaEventNewActiveQueue(const char *, size_t, unsigned int);
+extern int pmdaEventQueueHandle(const char *);
+extern int pmdaEventQueueAppend(int, void *, size_t, struct timeval *);
+extern int pmdaEventQueueClients(int, pmAtomValue *);
+extern int pmdaEventQueueCounter(int, pmAtomValue *);
+extern int pmdaEventQueueBytes(int, pmAtomValue *);
+extern int pmdaEventQueueMemory(int, pmAtomValue *);
+
+typedef int (*pmdaEventDecodeCallBack)(int,
+ void *, size_t, struct timeval *, void *);
+extern int pmdaEventQueueRecords(int, pmAtomValue *, int,
+ pmdaEventDecodeCallBack, void *);
+
+extern int pmdaEventNewClient(int);
+extern int pmdaEventEndClient(int);
+extern int pmdaEventClients(pmAtomValue *);
+
+typedef int (*pmdaEventApplyFilterCallBack)(void *, void *, size_t);
+typedef void (*pmdaEventReleaseFilterCallBack)(void *);
+extern int pmdaEventSetFilter(int, int, void *,
+ pmdaEventApplyFilterCallBack, pmdaEventReleaseFilterCallBack);
+extern int pmdaEventSetAccess(int, int, int);
+
+extern char *__pmdaEventPrint(const char *, int, char *, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PMDA_H */
diff --git a/src/include/pcp/pmtime.h b/src/include/pcp/pmtime.h
new file mode 100644
index 0000000..86db5a3
--- /dev/null
+++ b/src/include/pcp/pmtime.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2006-2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef PMTIME_H
+#define PMTIME_H
+
+#include <sys/time.h>
+
+typedef enum {
+ PM_TCTL_SET = (1<<0), // client -> server
+ PM_TCTL_STEP = (1<<1), // server -> clients
+ PM_TCTL_TZ = (1<<2), // server -> clients
+ PM_TCTL_VCRMODE = (1<<3), // server -> clients
+ PM_TCTL_VCRMODE_DRAG= (1<<4), // server -> clients
+ PM_TCTL_GUISHOW = (1<<5), // client -> server
+ PM_TCTL_GUIHIDE = (1<<6), // client -> server
+ PM_TCTL_BOUNDS = (1<<7), // client -> server
+ PM_TCTL_ACK = (1<<8), // client -> server (except handshake)
+} pm_tctl_command;
+
+typedef enum {
+ PM_STATE_STOP = 0,
+ PM_STATE_FORWARD = 1,
+ PM_STATE_BACKWARD = 2,
+} pm_tctl_state;
+
+typedef enum {
+ PM_MODE_STEP = 0,
+ PM_MODE_NORMAL = 1,
+ PM_MODE_FAST = 2,
+} pm_tctl_mode;
+
+typedef enum {
+ PM_SOURCE_NONE = -1,
+ PM_SOURCE_HOST = 0,
+ PM_SOURCE_ARCHIVE = 1,
+} pm_tctl_source;
+
+#define PMTIME_MAGIC 0x54494D45 /* "TIME" */
+
+typedef struct {
+ unsigned int magic;
+ unsigned int length;
+ pm_tctl_command command;
+ pm_tctl_source source;
+ pm_tctl_state state;
+ pm_tctl_mode mode;
+ struct timeval delta;
+ struct timeval position;
+ struct timeval start; /* archive only */
+ struct timeval end; /* archive only */
+ char data[0]; /* arbitrary length info (e.g. $TZ) */
+} pmTime;
+
+extern int pmTimeSendAck(int, struct timeval *);
+extern int pmTimeConnect(int, pmTime *);
+extern int pmTimeShowDialog(int, int);
+extern int pmTimeRecv(int, pmTime **);
+
+/*
+ * Time state management API for simple clients
+ */
+
+typedef void (*pmTimeStateResume)(void);
+typedef void (*pmTimeStateRewind)(void);
+typedef void (*pmTimeStateExited)(void);
+typedef void (*pmTimeStateBoundary)(void);
+typedef void (*pmTimeStatePosition)(struct timeval);
+typedef void (*pmTimeStateInterval)(struct timeval);
+typedef void (*pmTimeStateStepped)(struct timeval);
+typedef void (*pmTimeStateNewZone)(char *, char *);
+
+typedef struct {
+ pmTimeStateResume resume;
+ pmTimeStateRewind rewind;
+ pmTimeStateExited exited;
+ pmTimeStateBoundary boundary;
+ pmTimeStatePosition position;
+ pmTimeStateInterval interval;
+ pmTimeStateStepped stepped;
+ pmTimeStateNewZone newzone;
+ struct timeval delta;
+ int fd;
+ int showgui;
+ int context;
+ int padding;
+} pmTimeControls;
+
+extern pmTime *pmTimeStateSetup(pmTimeControls *, int, int,
+ struct timeval, struct timeval,
+ struct timeval, struct timeval, char *, char *);
+extern void pmTimeStateAck(pmTimeControls *, pmTime *);
+extern void pmTimeStateMode(int, struct timeval, struct timeval *);
+extern int pmTimeStateVector(pmTimeControls *, pmTime *);
+extern void pmTimeStateBounds(pmTimeControls *, pmTime *);
+
+#endif /* PMTIME_H */
diff --git a/src/include/pcp/trace.h b/src/include/pcp/trace.h
new file mode 100644
index 0000000..af64d79
--- /dev/null
+++ b/src/include/pcp/trace.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _TRACE_H
+#define _TRACE_H
+
+/*
+ * Transaction monitoring PMDA (trace) public interface.
+ *
+ * An example program using this interface can be found at
+ * $PCP_DEMOS_DIR/trace/demo.c and contains further doumentation.
+ * Also refer to the pmdatrace(1) and pmdatrace(3) man pages and
+ * the Performance Co-Pilot Programmer's Guide.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Add a new entry to the table of transactions currently being monitored,
+ * or update an existing entry.
+ */
+extern int pmtracebegin(const char *);
+
+/*
+ * Make measurements recorded for the given transaction tag available
+ * through the trace PMDA.
+ */
+extern int pmtraceend(const char *);
+
+/*
+ * Cancel a transaction which began from an earlier call to pmtracebegin,
+ * without exporting new data through the trace PMDA.
+ */
+extern int pmtraceabort(const char *);
+
+/*
+ * An alternate form of measurement can be obtained using pmtracepoint.
+ * This is a count-only measurement, and will result in the trace PMDA
+ * exporting the number of times a given code point is passed (ie. the
+ * the number of times a particular label has been passed to pmtracepoint.
+ */
+extern int pmtracepoint(const char *);
+
+/*
+ * An extension to pmtracepoint is pmtraceobs, with similar semantics to
+ * pmtracepoint except allowing an arbitrary numeric value (double) to be
+ * exported up through the PMAPI.
+ */
+extern int pmtraceobs(const char *, double);
+
+/*
+ * Similar to pmtraceobs is pmtracecounter, with the only difference
+ * being the way the trace PMDA exports the given numeric value to
+ * PMAPI clients (exported with counter semantics, rather than with
+ * instantaneous semantics which is the case with pmtraceobs).
+ */
+extern int pmtracecounter(const char *, double);
+
+/*
+ * Should any of these routines return a negative value, the return value
+ * can be passed to pmtraceerrstr for the associated error message.
+ * This performs a lookup into a static error message table, so the returned
+ * pointer must not be freed.
+ */
+extern char *pmtraceerrstr(int);
+
+#define PMTRACE_ERR_BASE 12000
+#define PMTRACE_ERR_TAGNAME (-PMTRACE_ERR_BASE-0)
+#define PMTRACE_ERR_INPROGRESS (-PMTRACE_ERR_BASE-1)
+#define PMTRACE_ERR_NOPROGRESS (-PMTRACE_ERR_BASE-2)
+#define PMTRACE_ERR_NOSUCHTAG (-PMTRACE_ERR_BASE-3)
+#define PMTRACE_ERR_TAGTYPE (-PMTRACE_ERR_BASE-4)
+#define PMTRACE_ERR_TAGLENGTH (-PMTRACE_ERR_BASE-5)
+#define PMTRACE_ERR_IPC (-PMTRACE_ERR_BASE-6)
+#define PMTRACE_ERR_ENVFORMAT (-PMTRACE_ERR_BASE-7)
+#define PMTRACE_ERR_TIMEOUT (-PMTRACE_ERR_BASE-8)
+#define PMTRACE_ERR_VERSION (-PMTRACE_ERR_BASE-9)
+#define PMTRACE_ERR_PERMISSION (-PMTRACE_ERR_BASE-10)
+#define PMTRACE_ERR_CONNLIMIT (-PMTRACE_ERR_BASE-11)
+
+/*
+ * Diagnostic and state switching
+ */
+extern int pmtracestate(int code);
+
+#define PMTRACE_STATE_NONE 0 /* default: synchronous and no diagnostics */
+#define PMTRACE_STATE_API 1 /* debug: processing just below the API */
+#define PMTRACE_STATE_COMMS 2 /* debug: shows network-related activity */
+#define PMTRACE_STATE_PDU 4 /* debug: shows app<->PMDA IPC traffic */
+#define PMTRACE_STATE_PDUBUF 8 /* debug: internal IPC buffer management */
+#define PMTRACE_STATE_NOAGENT 16 /* debug: no PMDA communications at all */
+#define PMTRACE_STATE_ASYNC 32 /* control: use asynchronous PDU protocol */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TRACE_H */
diff --git a/src/include/pcp/trace_dev.h b/src/include/pcp/trace_dev.h
new file mode 100644
index 0000000..a237a97
--- /dev/null
+++ b/src/include/pcp/trace_dev.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1997-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _TRACE_DEV_H
+#define _TRACE_DEV_H
+
+#ifndef _PLATFORM_DEFS_H
+#include "platform_defs.h"
+#endif
+
+#ifdef _cplusplus
+extern "C" {
+#endif
+
+#define MAXTAGNAMELEN 256
+
+#define TRACE_ENV_HOST "PCP_TRACE_HOST"
+#define TRACE_ENV_PORT "PCP_TRACE_PORT"
+#define TRACE_ENV_TIMEOUT "PCP_TRACE_TIMEOUT"
+#define TRACE_ENV_NOAGENT "PCP_TRACE_NOAGENT"
+#define TRACE_ENV_REQTIMEOUT "PCP_TRACE_REQTIMEOUT"
+#define TRACE_ENV_RECTIMEOUT "PCP_TRACE_RECONNECT"
+#define TRACE_PORT 4323
+#define TRACE_PDU_VERSION 1
+
+#define TRACE_TYPE_TRANSACT 1
+#define TRACE_TYPE_POINT 2
+#define TRACE_TYPE_OBSERVE 3
+#define TRACE_TYPE_COUNTER 4
+#define TRACE_FIRST_TYPE TRACE_TYPE_TRANSACT
+#define TRACE_LAST_TYPE TRACE_TYPE_COUNTER
+
+/*
+ * Protocol data unit (PDU) support snarfed from impl.h, pdu.c and pdubuf.c
+ */
+typedef struct {
+ int len; /* length of pdu_header + PDU */
+ int type; /* PDU type */
+ int from; /* pid of PDU originator */
+} __pmTracePDUHdr;
+
+typedef __uint32_t __pmTracePDU;
+
+extern int __pmtracexmitPDU(int, __pmTracePDU *);
+extern int __pmtracegetPDU(int, int, __pmTracePDU **);
+
+/* for __pmtracegetPDU */
+#define TRACE_TIMEOUT_NEVER 0
+#define TRACE_TIMEOUT_DEFAULT -1
+
+/* unit of space allocation for PDU buffer. */
+#define TRACE_PDU_CHUNK 1024
+
+extern __pmTracePDU *__pmtracefindPDUbuf(int);
+extern void __pmtracepinPDUbuf(void *);
+extern int __pmtraceunpinPDUbuf(void *);
+extern int __pmtracemoreinput(int);
+extern void __pmtracenomoreinput(int);
+
+
+#define TRACE_PDU_BASE 0x7050
+#define TRACE_PDU_ACK 0x7050
+#define TRACE_PDU_DATA 0x7051
+#define TRACE_PDU_MAX 2
+
+extern int __pmtracesendack(int, int);
+extern int __pmtracedecodeack(__pmTracePDU *, int *);
+extern int __pmtracesenddata(int, char *, int, int, double);
+extern int __pmtracedecodedata(__pmTracePDU *, char **, int *, int *, int *, double *);
+
+#define TRACE_PROTOCOL_FINAL -1
+#define TRACE_PROTOCOL_QUERY 0
+#define TRACE_PROTOCOL_ASYNC 1
+#define TRACE_PROTOCOL_SYNC 2
+
+extern int __pmtraceprotocol(int);
+
+extern int __pmstate;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TRACE_DEV_H */
diff --git a/src/iostat2pcp/GNUmakefile b/src/iostat2pcp/GNUmakefile
new file mode 100644
index 0000000..a79ac5a
--- /dev/null
+++ b/src/iostat2pcp/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPT = iostat2pcp
+LDIRT = $(MAN_PAGES) $(MAN_PAGES).tmp
+LSRCFILES = $(SCRIPT) README
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = $(SCRIPT).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: $(MAN_PAGES)
+
+$(SCRIPT).$(MAN_SECTION): $(SCRIPT)
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BIN_DIR)/$(SCRIPT)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/iostat2pcp/README b/src/iostat2pcp/README
new file mode 100644
index 0000000..6fde585
--- /dev/null
+++ b/src/iostat2pcp/README
@@ -0,0 +1,123 @@
+Converting a iostat output to a PCP archive
+
+This example uses the PCP::LogImport Perl wrapper around the libpcp_import
+library to convert a sadc datafile into a PCP archive.
+
+The version of iostat that is supported here is the one from
+http://freshmeat.net/projects/sysstat and provides the following
+reporting options:
+
+-t Add timestamps like 27/07/10 12:47:34 ahead of each sample.
+ If $S_TIME_FORMAT=ISO in the environment, then the format changes
+ to 2010-07-27T12:46:07+1000
+
+ The default is to not include any timestamps.
+
+ Start date is on first line
+ Linux 2.6.32-23-generic (bozo) 27/07/10 _i686_ (1 CPU)
+
+-z supress activity for idle devices
+
+-k Disk activity in Kilobytes not blocks
+ Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
+
+-m Disk activity in megabytes not blocks
+ Device: tps MB_read/s MB_wrtn/s MB_read MB_wrtn
+
+-c CPU utilization
+ avg-cpu: %user %nice %system %iowait %steal %idle
+ 75.00 0.00 25.00 0.00 0.00 0.00
+
+-d Disk activity
+ Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
+ sda 9.27 42.38 135.10 128 408
+ sdb 83.44 182.78 1634.44 552 4936
+ scd0 0.00 0.00 0.00 0 0
+
+-n NFS report
+ Filesystem: rBlk_nor/s wBlk_nor/s rBlk_dir/s wBlk_dir/s rBlk_svr/s wBlk_svr/s rops/s wops/s
+
+-x Extended disk activity
+ Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
+ sda 0.00 10.30 0.00 4.32 0.00 116.94 27.08 0.00 0.31 0.31 0.13
+ sdb 0.00 83.39 0.33 10.63 2.66 754.82 69.09 0.05 4.36 0.85 0.93
+ scd0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
+
+-p like -d but for partitions (example below is with -z)
+ Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
+ sda 2.99 0.00 77.08 0 232
+ sda1 2.99 0.00 77.08 0 232
+ sdb 20.60 2.66 890.37 8 2680
+ sdb6 20.60 2.66 890.37 8 2680
+
+Usage: iostat2pcp infile archive
+
+The translation currently supports the following PCP metrics:
+ disk.all.read
+ disk.all.read_bytes
+ disk.all.total
+ disk.all.total_bytes
+ disk.all.write
+ disk.all.write_bytes
+ disk.dev.avactive
+ disk.dev.read_bytes
+ disk.dev.total
+ disk.dev.total_bytes
+ disk.dev.write_bytes
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.nice
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.pswitch
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.nice
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.free
+ mem.util.swapCached
+ mem.util.swapFree
+ mem.util.used
+ mem.vmstat.pgfault
+ mem.vmstat.pgfree
+ mem.vmstat.pgmajfault
+ mem.vmstat.pgpgin
+ mem.vmstat.pgpgout
+ network.interface.collisions
+ network.interface.in.bytes
+ network.interface.in.drops
+ network.interface.in.errors
+ network.interface.in.fifo
+ network.interface.in.frame
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.carrier
+ network.interface.out.drops
+ network.interface.out.errors
+ network.interface.out.fifo
+ network.interface.out.packets
+ proc.runq.runnable
+ swap.pagesin
+ swap.pagesout
+ vfs.dentry.count
+ vfs.files.count
+ vfs.inodes.count
+
+This is sufficient to support the following standard pmchart views:
+ CPU
+ Disk
+ Diskbytes
+ Loadavg
+ Memory
+ Netbytes
+ Netpackets
+ Overview (except for a lesser memory stats)
+ Paging
+
diff --git a/src/iostat2pcp/iostat2pcp b/src/iostat2pcp/iostat2pcp
new file mode 100755
index 0000000..91f1f3d
--- /dev/null
+++ b/src/iostat2pcp/iostat2pcp
@@ -0,0 +1,859 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Assumptions:
+# Except for line suppresion for idle devices with -z, output from
+# iostat is completely deterministic, with the number and order of
+# values identical for each sample interval.
+#
+# Since we don't know what sort of system the data came from
+# there is no point trying to match the PMIDs with those from
+# any particular OS PMDA. For the names we'll use the common
+# names where possible, else the Linux names (as the most likely
+# case).
+#
+
+use strict;
+use warnings;
+
+use Getopt::Std;
+use Date::Parse;
+use Date::Format;
+use PCP::LogImport;
+
+my $line = 0; # input line number
+my $basedate = undef;
+my $basetime = "00:00:00";
+my $stamp_style = 0;
+my $host = undef;
+my $zone = "UTC"; # default timezone
+ # unless -t & $S_TIME_FORMAT=ISO or -Z on command line
+my $first_tag = undef;
+my $sts;
+my %options; # for command line arguments
+my $sample = -1;
+my $interval = undef;
+my $in_dev = 0; # in Device: group
+my $dev_style; # "d" for standard -d Device: data, "x" for -x Device: data
+ # Note -x and -d are mutually exclusive to iostat
+my $in_cpu = 0; # in avg-cpu: group
+my $stamp; # timestamp from -t output lines
+my $now; # faked tv_sec of gettimeofday() for current sample
+my $next_stamp; # ready for next sample interval
+my $next_now;
+my $vflag; # -v for verbosity
+my @handle = (); # pmi* handles, one per metric-instance pair
+my $h = 0; # index into handle[]
+my $putsts = 0; # pmiPutValue() errors are only checked @ end of loop
+my $dev_thru_scale; # scale factor for disk thruput - 0.5 for blocks, 1 for
+ # kB and 1024 for MB
+my $dev_indom = pmInDom_build(PMI_DOMAIN, 0);
+my %dev_first_handle; # for each disk instance, index into handle[] for first
+ # handle, other handles for related metrics for the same
+ # instance follow consecutively in handle[]
+my %dev_prev; # previous values for each instance for disk.dev.* metrics
+my %dev_seen; # tracking disk instances seen in this sample for
+ # handling -z and missing lines for inactive devices
+my %inst_map = (); # key=indom value=last_inst_assigned, and
+ # key=indom.instance value=inst
+
+sub dodate($)
+{
+ # convert datetime formats 27/07/10 12:47:34 or
+ # 2013-07-05 09:17:28 or 2010-07-27T12:46:07+1000
+ # into ISO-8601 dates that Date::Parse
+ # seems to be able to parse correctly ... this would appear to
+ # have to be YYYY-MM-DDTHH:MM:SS.000000 and then pass the timezone
+ # as the second parameter to str2time()
+ #
+ my ($datetime) = @_;
+ my @fields;
+ my $mm;
+ my $yy;
+ my $dd;
+ my $time;
+ @fields = split(/T/, $datetime);
+ if ($#fields == 1) {
+ # ISO format - YYYY-MM-DDTHH:MM:SS[timezone]
+ $time = $fields[1];
+ $time =~ s/[+-].*//;
+ @fields = split(/-/, $fields[0]);
+ $#fields == 2 or die "dodate: bad datetime format: \"$datetime\"\n";
+ $yy = $fields[0];
+ $mm = $fields[1];
+ $dd = $fields[2];
+ }
+ else {
+ @fields = split(/\//, $datetime);
+ if ($#fields == 2) {
+ # DD/MM/YY HH:MM:SS format
+ @fields = split(/ /, $datetime);
+ $#fields == 1 or die "dodate: bad datetime format 1: \"$datetime\"\n";
+ $time = $fields[1];
+ @fields = split(/\//, $fields[0]);
+ $#fields == 2 or die "dodate: bad date format 1: \"$datetime\"\n";
+ $dd = $fields[0];
+ $mm = $fields[1];
+ $yy = $fields[2];
+ }
+ else {
+ @fields = split(/-/, $datetime);
+ if ($#fields == 2) {
+ # YYYY-MM-DD HH:MM:SS format
+ @fields = split(/ /, $datetime);
+ $#fields == 1 or die "dodate: bad datetime format 3: \"$datetime\"\n";
+ $time = $fields[1];
+ @fields = split(/-/, $fields[0]);
+ $#fields == 2 or die "dodate: bad date format 3: \"$datetime\"\n";
+ $yy = $fields[0];
+ $mm = $fields[1];
+ $dd = $fields[2];
+ }
+ }
+ }
+
+ if ($time !~ /\./) { $time .= ".000000" }
+
+ # get into canonical DD, MM and YYYY format
+ if ($dd < 10 && $dd !~ /^0/) { $dd .= "0" }; # add leading zero
+ if ($mm < 10 && $mm !~ /^0/) { $mm .= "0" }; # add leading zero
+ if ($yy < 100) {
+ # terrible Y2K hack ... will stop working in 2080
+ if ($yy <= 80) { $yy += 2000; }
+ else { $yy += 1900; }
+ }
+ return $yy . "-" . $mm . "-" . $dd . "T" . $time;
+}
+
+# for stamp_style 1 or 2 or 3, -S and -t options ignored
+# for stamp_style 2, -Z option ignored
+#
+sub check_opts()
+{
+ return if $stamp_style == 0;
+ if (exists($options{S})) {
+ print "iostat2pcp: Warning: timestamps found, -S $basetime option ignored\n";
+ }
+ if (exists($options{t})) {
+ print "iostat2pcp: Warning: timestamps found, -t $interval option ignored\n";
+ }
+ if ($stamp_style == 2 && exists($options{Z})) {
+ print "iostat2pcp: Warning: ISO timestamps found, -Z $zone option ignored\n";
+ }
+}
+
+# Handle metrics with the a singular value, calling pmiAddMetric() and
+# pmiGetHandle()
+#
+sub def_single($)
+{
+ my ($name) = @_;
+ my $sts;
+ my $units = pmiUnits(0,0,0,0,0,0);
+ my $type = PM_TYPE_FLOAT;
+ if (pmiAddMetric($name, PM_ID_NULL, $type, PM_INDOM_NULL, PM_SEM_INSTANT, $units) < 0) {
+ pmiDump();
+ die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n";
+ }
+ $sts = pmiGetHandle($name, "");
+ if ($sts < 0) {
+ pmiDump();
+ die "pmiGetHandle($name, ...): " . pmiErrStr($sts) . "\n";
+ }
+ push(@handle, $sts);
+}
+
+# Handle metrics with multiple values, calling pmiAddMetric().
+# Defer to pmiGetHandle() to def_metric_inst().
+#
+sub def_multi($$)
+{
+ my ($name,$indom) = @_;
+ my $units = pmiUnits(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE);
+ my $type = PM_TYPE_FLOAT;
+ my $sem = PM_SEM_INSTANT;
+ if ($name eq "disk.dev.avactive") {
+ $units = pmiUnits(0,0,0,0,0,0);
+ }
+ elsif ($name =~ /disk\.dev\..*bytes/) {
+ $units = pmiUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0);
+ }
+ if (pmiAddMetric($name, PM_ID_NULL, $type, $indom, $sem, $units) < 0) {
+ pmiDump();
+ die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n";
+ }
+}
+
+# Deal with metric-instance pairs.
+# If first time this instance has been seen for this indom, add it to
+# the instance domain.
+# Get a handle and add it to handle[].
+#
+sub def_metric_inst($$$)
+{
+ my ($name,$indom,$instance) = @_;
+ my $sts;
+ # inst_map{} holds the last allocated inst number with $indom as the
+ # key, and marks the instance as known with $indom . $instance as the
+ # key
+ if (!exists($inst_map{$indom . $instance})) {
+ my $inst;
+ if (exists($inst_map{$indom})) {
+ $inst_map{$indom}++;
+ $inst = $inst_map{$indom};
+ }
+ else {
+ $inst_map{$indom} = 0;
+ $inst = 0;
+ }
+ if (pmiAddInstance($indom, $instance, $inst) < 0) {
+ pmiDump();
+ die "pmiAddInstance([$name], $instance, $inst): " . pmiErrStr(-1) . "\n";
+ }
+ $inst_map{$indom . $instance} = $inst;
+ }
+ $sts = pmiGetHandle($name, $instance);
+ if ($sts < 0) {
+ pmiDump();
+ die "pmiGetHandle($name, $instance): " . pmiErrStr($sts) . "\n";
+ }
+ push(@handle, $sts);
+}
+
+# wrapper for pmiPutValueHandle(), using @handle
+#
+sub put($)
+{
+ my ($value) = @_;
+ my $sts;
+ if (!exists($handle[$h])) {
+ pmiDump();
+ die <<EOF
+put($value): No handle[] entry for index $h.
+Check Handles in dump above.
+EOF
+ }
+ $sts = pmiPutValueHandle($handle[$h], $value);
+ if ($sts < 0 && $putsts == 0) { $putsts = $sts };
+ $h++;
+}
+
+# flush log record at end of sample interval
+#
+sub sample_done()
+{
+ # if -z is in play, then the Device: stats don't have values for
+ # those instances with no activity ... need to map this onto PCP data
+ # semantics, so if this instance has been seen at all, then for counters
+ # output the previous value (no activity) and for instantaneous/discrete
+ # metrics output zero (no activity).
+ #
+ # All the metrics we have so far instantaneous ...
+ #
+ foreach my $instance (keys %dev_seen) {
+ if ($dev_seen{$instance} == 0) {
+ next if !exists($dev_prev{$instance});
+ if (exists($dev_first_handle{$instance})) {
+ $h = $dev_first_handle{$instance};
+ }
+ else {
+ pmiDump();
+ print "[$line] $_\n";
+ die "Device: no first handle for instance \"" . $instance . "\", check Handles in dump above";
+ }
+ if ($dev_style eq "d") {
+ put(0); # total
+ put(0); # read_bytes
+ put(0); # write_bytes
+ }
+ else {
+ put(0); # read_merge
+ put(0); # write_merge
+ put(0); # read
+ put(0); # write
+ put(0); # read_bytes
+ put(0); # write_bytes
+ put(0); # avactive
+ }
+ }
+ else {
+ $dev_seen{$instance} = 0;
+ }
+ }
+ if ($putsts < 0) {
+ pmiDump();
+ die "pmiPutValue: Failed @ $stamp: " . pmiErrStr($putsts) . "\n";
+ }
+ if ($vflag) {
+ print "End of sample $sample";
+ if (defined($now) && defined($stamp)) { print " @ $now $stamp"; }
+ print "\n";
+ }
+ if (pmiWrite($now, 0) < 0) {
+ pmiDump();
+ die "pmiWrite: @ $stamp: " . pmiErrStr(-1) . "\n";
+ }
+ $h = 0;
+ $putsts = 0;
+}
+
+$sts = getopts('S:t:vZ:', \%options);
+
+if (!defined($sts) || $#ARGV != 1) {
+ print "Usage: iostat2pcp [-v] [-S start] [-t interval] [-Z timezone] infile outfile\n";
+ exit(1);
+}
+
+exists($options{S}) and $basetime = $options{S};
+exists($options{t}) and $interval = $options{t};
+exists($options{v}) and $vflag = 1;
+if (exists($options{Z})) {
+ $zone = $options{Z};
+ if ($zone !~ /^[-+][0-9][0-9][0-9][0-9]$/) {
+ print "iostat2pcp: Illegal -Z value, must be +NNNN or -NNNN\n";
+ exit(1);
+ }
+}
+
+pmiStart($ARGV[1], 0);
+
+open(INFILE, "<" . $ARGV[0])
+ or die "iostat2pcp: Failed to open infile \"$ARGV[0]\"\n";
+
+while (<INFILE>) {
+ my $end_sample = 0;
+ my $header = 0;
+ chomp;
+ $line++;
+ #debug# print "[" . $line . "] {" . $sample . "} $_\n";
+ if ($line == 1) {
+ # first line ... extract baseline date in format YYYY-MM-DD
+ # from something like ...
+ # Linux 2.6.32-23-generic (bozo) 27/07/10 _i686_ (1 CPU)
+ #
+ if (/.*\s+([^\s]+)\s+[0-3][0-9]\/[0-1][0-9]\/[0-9][0-9]\s+/) {
+ my @part;
+ $basedate = $_;
+ $basedate =~ s#.*([0-3][0-9]/[0-1][0-9]/[0-9][0-9]).*#$1#;
+ @part = split(/\//, $basedate);
+ # terrible Y2K hack ... will stop working in 2080
+ if ($part[2] <= 80) { $part[2] += 2000; }
+ else { $part[2] += 1900; }
+ $basedate = $part[2] . "-" . $part[1] . "-" . $part[0];
+ }
+ # or possibly like this when $S_TIME_FORMAT=ISO is set in the
+ # environment (ISO 8601) ...
+ # Linux 2.6.32-23-generic (bozo) 2010-07-27 _i686_ (1 CPU)
+ elsif (/.*\s+([^\s]+)\s+[12][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]\s+/) {
+ $basedate = $_;
+ $basedate =~ s#.*([12][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]).*#$1#;
+ }
+ # or possibly like this ...
+ # Linux 2.6.18-194.3.1.el5 (somehost.somewhere.com) 07/27/2010
+ elsif (/.*\s+([^\s]+)\s+[0-1][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]/) {
+ my @part;
+ $basedate = $_;
+ $basedate =~ s#.*([0-1][0-9]\/[0-3][0-9]\/[12][0-9][0-9][0-9]).*#$1#;
+ @part = split(/\//, $basedate);
+ $basedate = $part[2] . "-" . $part[0] . "-" . $part[1];
+ }
+ else {
+ print "[" . $line . "] $_\n";
+ print "iostat2pcp: First line does not look like iostat ... I give up\n";
+ exit(0);
+ }
+ $host = $_;
+ $host =~ s/[^(]*\(//;
+ $host =~ s/\).*//;
+ next;
+ }
+ elsif ($line == 3 && /[0-3][0-9]\/[0-1][0-9]\/[0-9][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) {
+ # simple -t option timestamp
+ # 27/07/10 12:47:34
+ #
+ $stamp_style = 1;
+ check_opts();
+ next;
+ }
+ elsif ($line == 3 && /[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9][-+][0-9]+/) {
+
+ # -t option timestamp and $S_TIME_FORMAT=ISO set in the environment
+ # 2010-07-27T12:46:07+1000
+ #
+ $zone = $_;
+ $zone =~ s/.*([-+][0-9]+).*/$1/;
+ $stamp_style = 2;
+ check_opts();
+ next;
+ }
+ elsif ($line == 3 && /[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) {
+
+ # -t option timestamp and ??? not sure ... but visible in
+ # https://bugzilla.redhat.com/show_bug.cgi?id=981545
+ # 2013-07-05 09:17:28
+ #
+ $stamp_style = 3;
+ check_opts();
+ next;
+ }
+ elsif ($line == 3) {
+ # first group tag
+ $first_tag = $_;
+ $first_tag =~ s/([^:]+):.*/$1/;
+ }
+
+ next if /^\s*$/;
+
+ if ($stamp_style == 0 && $line > 3) {
+ my $tag = $_;
+ $tag =~ s/([^:]+):.*/$1/;
+ if ($tag eq $first_tag) {
+ if ($sample > 0) {
+ # for sample #0, time is set below in the end_sample code
+ $next_now = $now + $interval;
+ $next_stamp = ctime($next_now, $zone);
+ chomp $next_stamp;
+ }
+ $end_sample = 1;
+ }
+ }
+ elsif ($stamp_style == 1) {
+ if (/[0-3][0-9]\/[0-1][0-9]\/[0-9][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) {
+ $next_stamp = dodate($_);
+ $next_now = str2time($next_stamp, $zone);
+ $end_sample = 1;
+ }
+ }
+ elsif ($stamp_style == 2) {
+ if (/[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9][-+][0-9]+/) {
+ $next_stamp = dodate($_);
+ $next_now = str2time($next_stamp, $zone);
+ $end_sample = 1;
+ }
+ }
+ elsif ($stamp_style == 3) {
+ if (/[1-2][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9]/) {
+ $next_stamp = dodate($_);
+ $next_now = str2time($next_stamp, $zone);
+ $end_sample = 1;
+ }
+ }
+
+ if ($end_sample) {
+ if ($stamp_style == 0 && $sample == 0) {
+ print "iostat2pcp: Warning: no timestamps, assuming data starts at $basedate $basetime $zone\n";
+ $stamp = dodate($basedate . "T" . $basetime);
+ $now = str2time($stamp, $zone);
+ if (!defined($interval)) {
+ print "iostat2pcp: Warning: cannot determine sample interval, assuming 15 seconds\n";
+ $interval = 15;
+ }
+ $now += $interval;
+ $stamp = ctime($now, $zone);
+ chomp $stamp;
+ $next_now = $now + $interval;
+ $next_stamp = ctime($next_now, $zone);
+ chomp $next_stamp;
+ }
+ if ($vflag) {
+ if ($sample == 0) {
+ print "stamp_style=$stamp_style zone=$zone basedate=$basedate now=$now stamp=$stamp";
+ print " interval=$interval" if defined($interval);
+ print " first_tag=$first_tag" if defined($first_tag);
+ print "\n";
+ }
+ }
+
+ if ($sample > -1) {
+ if ($sample == 0) {
+ # Serious strangeness here ...
+ # the Perl Date::Parse and Date::Format routines appear to
+ # only work with timezones of the format +NNNN or -NNNN
+ # ("UTC" is an exception)
+ #
+ # PCP expects a $TZ style timezone in the archive label, so
+ # we have to make up a PCP-xx:xx timezone ... note this
+ # involves a sign reversal!
+ #
+ my $label_zone = $zone;
+ if ($zone =~ /^[-+][0-9][0-9][0-9][0-9]/) {
+ $label_zone =~ s/^\+/PCP-/;
+ $label_zone =~ s/^-/PCP+/;
+ $label_zone =~ s/(..)$/:$1/;
+ }
+ elsif ($zone ne "UTC") {
+ print "iostat2pcp: Warning: unexpected timezone ($zone), reverting to UTC\n";
+ $zone = "UTC";
+ $label_zone = "UTC";
+ }
+ pmiSetTimezone($label_zone) >= 0
+ or die "pmiSetTimezone($label_zone): " . pmiErrStr(-1) . "\n";
+
+ if (defined($host)) {
+ pmiSetHostname($host) >= 0
+ or die "pmiSetHostname($host): " . pmiErrStr(-1) . "\n";
+ }
+ }
+ sample_done();
+ }
+
+ $sample++;
+ $stamp = $next_stamp;
+ $now = $next_now;
+
+ # if timestamp, get onto real data in following lines
+ #
+ ($stamp_style == 1 || $stamp_style == 2 || $stamp_style == 3) and next;
+ }
+
+ if (/^Device:/) {
+ $in_cpu = 0;
+ $in_dev = 1;
+ $header = 1;
+ }
+ elsif (/^avg-cpu:/) {
+ $in_dev = 0;
+ $in_cpu = 1;
+ $header = 1;
+ }
+
+ if ($sample == -1) {
+ # first time we have the stats since boot, we need to figure
+ # out the meta data, and for $stamp_style == 0, try to compute
+ # the sample interval
+ #
+ if ($in_cpu && $header) {
+ # avg-cpu: %user %nice %system %iowait %steal %idle
+ # if present, always comes first
+ #
+ def_single("kernel.all.cpu.user");
+ def_single("kernel.all.cpu.nice");
+ def_single("kernel.all.cpu.sys");
+ def_single("kernel.all.cpu.wait.total");
+ def_single("kernel.all.cpu.steal");
+ def_single("kernel.all.cpu.idle");
+ }
+ elsif ($in_dev) {
+ if ($header) {
+ # one of ...
+ # Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
+ # Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
+ # Device: tps MB_read/s MB_wrtn/s MB_read MB_wrtn
+ # Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
+ if (/tps/) {
+ $dev_style = "d"; # -d option
+ if (/Blk_read/) { $dev_thru_scale = 0.5; }
+ elsif (/kB_read/) { $dev_thru_scale = 1; }
+ elsif (/MB_read/) { $dev_thru_scale = 1024; }
+ else {
+ print "[$line] $_\n";
+ die "Device: cannot determine thruput scale";
+ }
+ def_multi("disk.dev.total", $dev_indom);
+ def_multi("disk.dev.read_bytes", $dev_indom);
+ def_multi("disk.dev.write_bytes", $dev_indom);
+ }
+ else {
+ $dev_style = "x"; # assume -x option
+ $dev_thru_scale = 0.5; # assume 512-byte sectors
+ def_multi("disk.dev.read_merge", $dev_indom);
+ def_multi("disk.dev.write_merge", $dev_indom);
+ def_multi("disk.dev.read", $dev_indom);
+ def_multi("disk.dev.write", $dev_indom);
+ def_multi("disk.dev.read_bytes", $dev_indom);
+ def_multi("disk.dev.write_bytes", $dev_indom);
+ def_multi("disk.dev.avactive", $dev_indom);
+ }
+ }
+ # Note: instances are populated as they are found _after_ the
+ # first (from boot time) stats are processed
+ }
+ next;
+ }
+ elsif ($sample >= 0 && $in_dev && $dev_style eq "d" && $header == 0
+ && $stamp_style == 0 && !defined($interval)) {
+ # if basic Device: stats, can compute sample interval from ratio of
+ # totals to rate for reads or writes ... need to avoid divide zero
+ # counts ... must do on the second sample interval and may fail if
+ # no -d or -z and no activity
+ my @part = split(/\s+/, $_);
+ if ($#part != 5) {
+ print "[$line] $_\n";
+ die "Device: number of values? expected 6, found " . ($#part+1) . "\n";
+ }
+ if ($part[4] != 0) {
+ $interval = int(0.5 + $part[4]/$part[2]);
+ }
+ elsif ($part[5] != 0) {
+ $interval = int(0.5 + $part[5]/$part[3]);
+ }
+ if (defined($interval) && $interval <= 0) {
+ # in case we've really screwed up, better to be clueless
+ $interval = undef;
+ }
+ }
+
+ if ($header == 0 && $sample >= 0) {
+ if ($in_cpu) {
+ my @part = split(/\s+/, $_);
+ # $part[0] is empty white space before first value
+ if ($#part != 6) {
+ print "[$line] $_\n";
+ die "avg-cpu: number of values? expected 7, found " . ($#part+1) . "\n";
+ }
+ put($part[1]/100); # user
+ put($part[2]/100); # nice
+ put($part[3]/100); # system
+ put($part[4]/100); # iowait
+ put($part[5]/100); # steal
+ put($part[6]/100); # idle
+ }
+ elsif ($in_dev) {
+ # Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
+ # sda 3.34 10.70 77.59 32 232
+ # or
+ # Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
+ # sda 0.03 0.76 0.32 0.21 8.90 7.79 31.61 0.01 13.37 2.11 0.11
+ my @part = split(/\s+/, $_);
+ my @thisval;
+ my $instance;
+ if ($#part == 0) {
+ # workaround for https://bugzilla.redhat.com/show_bug.cgi?id=604637
+ # long device name followed by embedded newline ... append the next
+ # input line
+ #
+ $instance = $part[0];
+ $_ = $instance . " " . <INFILE>;
+ chomp;
+ $line++;
+ @part = split(/\s+/, $_);
+ }
+ if ($dev_style eq "d") {
+ if ($#part != 5) {
+ print "[$line] $_\n";
+ die "Device: number of values? expected 6, found " . ($#part+1) . "\n";
+ }
+ $instance = $part[0];
+ }
+ else {
+ if ($#part != 11) {
+ print "[$line] $_\n";
+ die "Device: number of values? expected 12, found " . ($#part+1) . "\n";
+ }
+ $instance = $part[0];
+ }
+ if (exists($dev_first_handle{$instance})) {
+ $h = $dev_first_handle{$instance};
+ }
+ else {
+ # first time we've seen this instance, set up indom and handles
+ #
+ if ($dev_style eq "d") {
+ def_metric_inst("disk.dev.total", $dev_indom, $instance);
+ $dev_first_handle{$instance} = $#handle;
+ def_metric_inst("disk.dev.read_bytes", $dev_indom, $instance);
+ def_metric_inst("disk.dev.write_bytes", $dev_indom, $instance);
+ }
+ else {
+ def_metric_inst("disk.dev.read_merge", $dev_indom, $instance);
+ $dev_first_handle{$instance} = $#handle;
+ def_metric_inst("disk.dev.write_merge", $dev_indom, $instance);
+ def_metric_inst("disk.dev.read", $dev_indom, $instance);
+ def_metric_inst("disk.dev.write", $dev_indom, $instance);
+ def_metric_inst("disk.dev.read_bytes", $dev_indom, $instance);
+ def_metric_inst("disk.dev.write_bytes", $dev_indom, $instance);
+ def_metric_inst("disk.dev.avactive", $dev_indom, $instance);
+ }
+ # populate dev_seen for each instance
+ $dev_seen{$instance} = 0;
+ }
+ if ($dev_style eq "d") {
+ # disk.dev.total
+ # disk.dev.read_bytes
+ # disk.dev.write_bytes
+ @thisval = ($part[1], $part[2] * $dev_thru_scale, $part[3] * $dev_thru_scale);
+ if (!exists($dev_prev{$instance})) {
+ # first time seen for this instance
+ $dev_prev{$instance} = [0,0,0];
+ }
+ for (my $i = 0; $i <= $#thisval; $i++) {
+ # all these are instantaneous
+ $dev_prev{$instance}->[$i] = $thisval[$i];
+ put($dev_prev{$instance}->[$i]);
+ }
+ $dev_seen{$instance} = 1;
+ }
+ else {
+ if (exists($dev_first_handle{$instance})) {
+ $h = $dev_first_handle{$instance};
+ }
+ else {
+ pmiDump();
+ print "[$line] $_\n";
+ die "Device: no first handle for instance \"" . $instance . "\", check Handles in dump above";
+ }
+ # disk.dev.read_merge
+ # disk.dev.write_merge
+ # disk.dev.read
+ # disk.dev.write
+ # disk.dev.read_bytes
+ # disk.dev.write_bytes
+ # disk.dev.avactive
+ @thisval = ($part[1], $part[2], $part[3], $part[4], $part[5] * $dev_thru_scale, $part[6] * $dev_thru_scale, $part[11]);
+ if (!exists($dev_prev{$instance})) {
+ # first time seen for this instance
+ $dev_prev{$instance} = [0,0,0,0,0,0,0];
+ }
+ for (my $i = 0; $i <= $#thisval; $i++) {
+ # all these are instantaneous
+ $dev_prev{$instance}->[$i] = $thisval[$i];
+ put($dev_prev{$instance}->[$i]);
+ }
+ $dev_seen{$instance} = 1;
+ }
+ }
+ }
+}
+
+# flush last sample out ... end of file is the end of the sample
+#
+sample_done();
+
+pmiEnd();
+
+exit(0);
+
+=pod
+
+=head1 NAME
+
+iostat2pcp - Import iostat data and create a PCP archive
+
+=head1 SYNOPSIS
+
+B<iostat2pcp> [B<-v>] [B<-S> I<start>] [B<-t> I<interval>] [B<-Z> I<timezone>] I<infile> I<outfile>
+
+=head1 DESCRIPTION
+
+B<iostat2pcp> reads a text file created with
+B<iostat>(1) (I<infile>) and translates this into a Performance
+Co-Pilot (PCP) archive with the basename I<outfile>.
+If I<infile> is E<quot>-E<quot> then I<iostat2pcp> reads for
+standard input, allowing easy preprocessing of the I<iostat>(1) output
+with I<sed>(1) or similar.
+
+The resultant PCP archive may be used with all the PCP client tools
+to graph subsets of the data using B<pmchart>(1),
+perform data reduction and reporting, filter with
+the PCP inference engine B<pmie>(1), etc.
+
+A series of physical files will be created with the prefix I<outfile>.
+These are I<outfile>B<.0> (the performance data),
+I<outfile>B<.meta> (the metadata that describes the performance data) and
+I<outfile>B<.index> (a temporal index to improve efficiency of replay
+operations for the archive). If any of these files exists already,
+then B<iostat2pcp> will B<not> overwrite them and will exit with an error
+message.
+
+The first output sample from I<iostat>(1) contains a statistical summary
+since boot time and is ignored by I<iostat2pcp>, so the first real data
+set is the second one in the I<iostat>(1) output.
+
+The best results are obtained when I<iostat>(1) was run with its own B<-t>
+flag, so each output sample is prefixed with a timestamp. Even better
+is B<-t> with $B<S_TIME_FORMAT=ISO> set in environment when I<iostat>(1)
+is run, in which case the timestamp includes the timezone.
+
+Note that if $B<S_TIME_FORMAT=ISO> is B<not> used with the B<-t> option
+then I<iostat>(1) may produce a timestamp controlled by B<LC_TIME> from
+the locale that is in a format I<iostat2pcp> cannot parse. The formats
+for the timestamp that I<iostat2pcp> accepts are illustrated by these
+examples:
+
+=over 4
+
+=item B<2013-07-06T21:34:39+1000>
+
+(for the $B<S_TIME_FORMAT=ISO>).
+
+=item B<2013-07-06 21:34:39>
+
+(for some of the European formats, e.g. de_AT,
+de_BE, de_LU and en_DK.utf8).
+
+=item B<06/07/13 21:34:39>
+
+(for all of the $B<LC_TIME> settings for English
+locales outside North America, e.g. en_AU, en_GB, en_IE, en_NZ,
+en_SG and en_ZA, and all the Spanish locales, e.g. es_ES, es_MX and es_AR).
+
+=back
+
+In particular, note that some common North American $B<LC_TIME> settings will
+B<not> work with I<iostat2pcp> (namely, en_US, POSIX and C) because they
+use the MM/DD format which may be incorrectly converted with the
+assumed DD/MM format. This is another reason to recommend setting
+$B<S_TIME_FORMAT=ISO>.
+
+If there are no timestamps in the input stream, I<iostat2pcp> will
+try and deduce the sample interval if basic Disk data (B<-d>
+option for I<iostat>(1)) is found. If this fails, then the B<-t> option may be
+used to specify the sample I<interval> in seconds.
+This option is ignored if timestamps are found in the input stream.
+
+The B<-S> option may be used to specify as start time for the
+first real sample in I<infile>, where I<start> must have the format
+HH:MM:SS.
+This option is ignored if timestamps are found in the input stream.
+
+The B<-Z> option may be used to specify a timezone. It must have the
+format +HHMM (for hours and minutes East of UTC) or -HHMM (for hours
+and minutes West of UTC). Note in particular that B<neither> the B<zoneinfo>
+(aka Olson) format, e.g. Europe/Paris, nor the Posix B<TZ> format, e.g.
+EST+5 is allowed for the B<-Z> option.
+This option is ignored if ISO timestamps are found in the input stream.
+If the timezone is not specified and cannot be deduced, it defaults to
+E<quot>UTCE<quot>.
+
+Some additional diagnostic output is generated with the B<-v> option.
+
+B<iostat2pcp> is a Perl script that uses the PCP::LogImport Perl wrapper
+around the PCP I<libpcp_import>
+library, and as such could be used as an example to develop new
+tools to import other types of performance data and create PCP archives.
+
+=head1 CAVEAT
+
+B<iostat2pcp> requires I<infile> to have been created by the version
+of B<iostat>(1) from
+L<http://freshmeat.net/projects/sysstat>.
+
+B<iostat2pcp> handles the B<-c> (CPU), B<-d> (Disk), B<-x> (eXtended
+Disk) and B<-p> (Partition) report formats (including their B<-k>, B<-m>,
+B<-z> and
+B<ALL> variants), but does not accommodate the B<-n> (Network Filesystem)
+report format from B<iostat>(1); this is a demand-driven limitation rather
+than a technical limitation.
+
+=head1 SEE ALSO
+
+B<Date::Format>(3pm),
+B<Date::Parse>(3pm),
+B<iostat>(1),
+B<LOGIMPORT>(3),
+B<PCP::LogImport>(3pm),
+B<pmchart>(1),
+B<pmie>(1),
+B<pmlogger>(1) and
+B<sed>(1).
diff --git a/src/libpcp/GNUlocaldefs.32 b/src/libpcp/GNUlocaldefs.32
new file mode 100644
index 0000000..38bc43e
--- /dev/null
+++ b/src/libpcp/GNUlocaldefs.32
@@ -0,0 +1,4 @@
+LCFLAGS += -m32 -march=i386
+LLDFLAGS += -m32 -march=i386
+PCP_LIB_DIR=$(PCP_LIB32_DIR)
+LIBPCP_ABIDIR=32
diff --git a/src/libpcp/GNUmakefile b/src/libpcp/GNUmakefile
new file mode 100644
index 0000000..bb890a1
--- /dev/null
+++ b/src/libpcp/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = GNUlocaldefs.32
+
+SUBDIRS = src $(PCP_ALTLIBS)
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
diff --git a/src/libpcp/src/AF.c b/src/libpcp/src/AF.c
new file mode 100644
index 0000000..b2cb34b
--- /dev/null
+++ b/src/libpcp/src/AF.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * general purpose asynchronous event management
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+#define MIN_ITIMER_USEC 100
+
+typedef struct _qelt {
+ struct _qelt *q_next;
+ int q_afid;
+ struct timeval q_when;
+ struct timeval q_delta;
+ void *q_data;
+ void (*q_func)(int afid, void *data);
+} qelt;
+
+static qelt *root;
+static int afid = 0x8000;
+static int block;
+static void onalarm(int);
+
+/*
+ * Platform dependent routines follow, Windows is very different
+ * to POSIX platforms in terms of signals and timers. Note - we
+ * attempted to use CreateTimerQueue API on Windows, but it does
+ * not behave in the way we'd like unfortunately (QA slow_af.c -
+ * shows quite different results & its un-debuggable cos its all
+ * below the Win32 API).
+ */
+#ifdef IS_MINGW
+VOID CALLBACK ontimer(LPVOID arg, DWORD lo, DWORD hi)
+{
+ onalarm(14); /* 14 == POSIX SIGALRM */
+}
+
+static HANDLE afblock; /* mutex protecting callback */
+static HANDLE aftimer; /* equivalent to ITIMER_REAL */
+static int afsetup; /* one-time-setup: done flag */
+
+static void AFsetup(void)
+{
+ PM_LOCK(__pmLock_libpcp);
+ if (afsetup) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+ }
+ afsetup = 1;
+ afblock = CreateMutex(NULL, FALSE, NULL);
+ aftimer = CreateWaitableTimer(NULL, TRUE, NULL);
+ PM_UNLOCK(__pmLock_libpcp);
+}
+static void AFhold(void)
+{
+ AFsetup();
+ WaitForSingleObject(afblock, INFINITE);
+}
+static void AFrelse(void)
+{
+ if (afsetup)
+ ReleaseMutex(afblock);
+}
+static void AFrearm(void)
+{
+ /* do nothing, callback is always "armed" (except when not setup) */
+}
+
+static void AFsetitimer(struct timeval *interval)
+{
+ LARGE_INTEGER duetime;
+ long long inc;
+
+ AFsetup();
+
+ inc = interval->tv_sec * 10000000ULL; /* sec -> 100 nsecs */
+ inc += (interval->tv_usec * 10ULL); /* used -> 100 nsecs */
+ if (inc > 0) /* negative is relative, positive absolute */
+ inc = -inc; /* we will always want this to be relative */
+ duetime.QuadPart = inc;
+ SetWaitableTimer(aftimer, &duetime, 0, ontimer, NULL, FALSE);
+}
+
+#else /* POSIX */
+static void AFsetitimer(struct timeval *interval)
+{
+ struct itimerval val;
+
+ val.it_value = *interval;
+ val.it_interval.tv_sec = val.it_interval.tv_usec = 0;
+ setitimer(ITIMER_REAL, &val, NULL);
+}
+
+#if !defined(HAVE_SIGHOLD)
+static int
+sighold(int sig)
+{
+ sigset_t s;
+
+ sigemptyset(&s);
+ sigaddset(&s, sig);
+ sigprocmask(SIG_BLOCK, &s, NULL);
+
+ return 0;
+}
+#else
+/*
+ * for Linux the prototype is hidden, even though the function is in
+ * libc
+ */
+extern int sighold(int);
+#endif
+
+#if !defined(HAVE_SIGRELSE)
+static int
+sigrelse(int sig)
+{
+ sigset_t s;
+
+ sigemptyset(&s);
+ sigaddset(&s, sig);
+ sigprocmask(SIG_UNBLOCK, &s, NULL);
+ return 0;
+}
+#else
+/*
+ * for Linux the prototype is hidden, even though the function is in
+ * libc
+ */
+extern int sigrelse(int);
+#endif
+
+static void AFhold(void) { sighold(SIGALRM); }
+static void AFrelse(void) { sigrelse(SIGALRM); }
+static void AFrearm(void) { signal(SIGALRM, onalarm); }
+
+#endif /* POSIX */
+
+
+/*
+ * Platform independent code follows
+ */
+
+#ifdef PCP_DEBUG
+static void
+printdelta(FILE *f, struct timeval *tp)
+{
+ struct tm *tmp;
+ time_t tt = (time_t)tp->tv_sec;
+
+ tmp = gmtime(&tt);
+ fprintf(stderr, "%02d:%02d:%02d.%06ld", tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (long)tp->tv_usec);
+}
+#endif
+/*
+ * a := a + b for struct timevals
+ */
+static void
+tadd(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec += b->tv_usec;
+ if (a->tv_usec > 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec++;
+ }
+ a->tv_sec += b->tv_sec;
+}
+
+/*
+ * a := a - b for struct timevals
+ */
+static void
+tsub_real(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+}
+
+/*
+ * a := a - b for struct timevals, but result is never less than zero
+ */
+static void
+tsub(struct timeval *a, struct timeval *b)
+{
+ tsub_real(a, b);
+ if (a->tv_sec < 0) {
+ /* clip negative values at zero */
+ a->tv_sec = 0;
+ a->tv_usec = 0;
+ }
+}
+
+/*
+ * a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b
+ */
+static int
+tcmp(struct timeval *a, struct timeval *b)
+{
+ int res;
+ res = (int)(a->tv_sec - b->tv_sec);
+ if (res == 0)
+ res = (int)(a->tv_usec - b->tv_usec);
+ return res;
+}
+
+static void
+enqueue(qelt *qp)
+{
+ qelt *tqp;
+ qelt *priorp;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ struct timeval now;
+
+ __pmtimevalNow(&now);
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFenqueue " PRINTF_P_PFX "%p(%d, " PRINTF_P_PFX "%p) for ",
+ qp->q_func, qp->q_afid, qp->q_data);
+ __pmPrintStamp(stderr, &qp->q_when);
+ fputc('\n', stderr);
+ }
+#endif
+
+ for (tqp = root, priorp = NULL;
+ tqp != NULL && tcmp(&qp->q_when, &tqp->q_when) >= 0;
+ tqp = tqp->q_next)
+ priorp = tqp;
+
+ if (priorp == NULL) {
+ qp->q_next = root;
+ root = qp;
+ }
+ else {
+ qp->q_next = priorp->q_next;
+ priorp->q_next = qp;
+ }
+}
+
+static void
+onalarm(int dummy)
+{
+ struct timeval now;
+ struct timeval tmp;
+ struct timeval interval;
+ qelt *qp;
+
+ if (!block)
+ AFhold();
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmtimevalNow(&now);
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFonalarm(%d)\n", dummy);
+ }
+#endif
+ if (root != NULL) {
+ /* something to do ... */
+ while (root != NULL) {
+ /* compute difference between scheduled time and now */
+ __pmtimevalNow(&now);
+ tmp = root->q_when;
+ tsub(&tmp, &now);
+ if (tmp.tv_sec == 0 && tmp.tv_usec <= 10000) {
+ /*
+ * within one 10msec tick, the time has passed for this one,
+ * execute the callback and reschedule
+ */
+
+ qp = root;
+ root = root->q_next;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFcallback " PRINTF_P_PFX "%p(%d, " PRINTF_P_PFX "%p)\n",
+ qp->q_func, qp->q_afid, qp->q_data);
+ }
+#endif
+ qp->q_func(qp->q_afid, qp->q_data);
+
+ if (qp->q_delta.tv_sec == 0 && qp->q_delta.tv_usec == 0) {
+ /*
+ * if delta is zero, this is a single-shot event,
+ * so do not reschedule it
+ */
+ free(qp);
+ }
+ else {
+ /*
+ * avoid falling too far behind
+ * if the scheduled time is more than q_delta in the
+ * past we will never catch up ... better to skip some
+ * events to catch up ...
+ *
+ * <------------ next q_when range ----------------->
+ *
+ * cannot catchup | may catchup | on schedule
+ * | |
+ * --------------------|---------------|------------> time
+ * | |
+ * | +-- now
+ * +-- now - q_delta
+ */
+ __pmtimevalNow(&now);
+ for ( ; ; ) {
+ tadd(&qp->q_when, &qp->q_delta);
+ tmp = qp->q_when;
+ tsub_real(&tmp, &now);
+ tadd(&tmp, &qp->q_delta);
+ if (tmp.tv_sec >= 0)
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFcallback event %d too slow, skip callback for ", qp->q_afid);
+ __pmPrintStamp(stderr, &qp->q_when);
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ enqueue(qp);
+ }
+ }
+ else
+ /*
+ * head of the queue (and hence all others) are too far in
+ * the future ... done for this time
+ */
+ break;
+ }
+
+ if (root == NULL) {
+ pmprintf("Warning: AF event queue is empty, nothing more will be scheduled\n");
+ pmflush();
+ }
+ else {
+ /* set itimer for head of queue */
+ interval = root->q_when;
+ __pmtimevalNow(&now);
+ tsub(&interval, &now);
+ if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC)
+ /* use minimal delay (platform dependent) */
+ interval.tv_usec = MIN_ITIMER_USEC;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFsetitimer for delta ");
+ printdelta(stderr, &interval);
+ fputc('\n', stderr);
+ }
+#endif
+ AFsetitimer(&interval);
+ }
+ }
+ if (!block) {
+ AFrearm();
+ AFrelse();
+ }
+}
+
+int
+__pmAFregister(const struct timeval *delta, void *data, void (*func)(int, void *))
+{
+ qelt *qp;
+ struct timeval now;
+ struct timeval interval;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_AF))
+ return PM_ERR_THREAD;
+
+ if (!block)
+ AFhold();
+ if (afid == 0x8000 && !block) /* first time */
+ AFrearm();
+ if ((qp = (qelt *)malloc(sizeof(qelt))) == NULL) {
+ return -oserror();
+ }
+ qp->q_afid = ++afid;
+ qp->q_data = data;
+ qp->q_delta = *delta;
+ qp->q_func = func;
+ __pmtimevalNow(&qp->q_when);
+ tadd(&qp->q_when, &qp->q_delta);
+
+ enqueue(qp);
+ if (root == qp) {
+ /* we ended up at the head of the list, set itimer */
+ interval = qp->q_when;
+ __pmtimevalNow(&now);
+ tsub(&interval, &now);
+
+ if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC)
+ /* use minimal delay (platform dependent) */
+ interval.tv_usec = MIN_ITIMER_USEC;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFsetitimer for delta ");
+ printdelta(stderr, &interval);
+ fputc('\n', stderr);
+ }
+#endif
+ AFsetitimer(&interval);
+ }
+
+ if (!block)
+ AFrelse();
+ return qp->q_afid;
+}
+
+int
+__pmAFunregister(int afid)
+{
+ qelt *qp;
+ qelt *priorp;
+ struct timeval now;
+ struct timeval interval;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_AF))
+ return PM_ERR_THREAD;
+
+ if (!block)
+ AFhold();
+ for (qp = root, priorp = NULL; qp != NULL && qp->q_afid != afid; qp = qp->q_next)
+ priorp = qp;
+
+ if (qp == NULL) {
+ if (!block)
+ AFrelse();
+ return -1;
+ }
+
+ if (priorp == NULL) {
+ root = qp->q_next;
+ if (root != NULL) {
+ /*
+ * we removed the head of the queue, set itimer for the
+ * new head of queue
+ */
+ interval = root->q_when;
+ __pmtimevalNow(&now);
+ tsub(&interval, &now);
+ if (interval.tv_sec == 0 && interval.tv_usec < MIN_ITIMER_USEC)
+ /* use minimal delay (platform dependent) */
+ interval.tv_usec = MIN_ITIMER_USEC;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_AF) {
+ __pmPrintStamp(stderr, &now);
+ fprintf(stderr, " AFsetitimer for delta ");
+ printdelta(stderr, &interval);
+ fputc('\n', stderr);
+ }
+#endif
+ AFsetitimer(&interval);
+ }
+ }
+ else
+ priorp->q_next = qp->q_next;
+
+ free(qp);
+
+ if (!block)
+ AFrelse();
+ return 0;
+}
+
+void
+__pmAFblock(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_AF))
+ return;
+ block = 1;
+ AFhold();
+}
+
+void
+__pmAFunblock(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_AF))
+ return;
+ block = 0;
+ AFrearm();
+ AFrelse();
+}
+
+int
+__pmAFisempty(void)
+{
+ return (root==NULL);
+}
diff --git a/src/libpcp/src/GNUlocaldefs.coverage b/src/libpcp/src/GNUlocaldefs.coverage
new file mode 100644
index 0000000..9df0bef
--- /dev/null
+++ b/src/libpcp/src/GNUlocaldefs.coverage
@@ -0,0 +1,11 @@
+# example QA build for libpcp ... to enable
+# $ make clean
+# $ ln GNUlocaldefs.coverage GNUlocaldefs
+# $ make
+# $ sudo make install
+# this one turns on coverage hooks for gcov and ggcov and builds
+# libpcp with fault injection enabled
+#
+CFLAGS += -fprofile-arcs -ftest-coverage -DPM_FAULT_INJECTION
+LDLIBS += -lgcov -lpcp_pmda
+LDIRT = *.gcov *.gcda *.gcno
diff --git a/src/libpcp/src/GNUlocaldefs.debug b/src/libpcp/src/GNUlocaldefs.debug
new file mode 100644
index 0000000..04004ce
--- /dev/null
+++ b/src/libpcp/src/GNUlocaldefs.debug
@@ -0,0 +1,8 @@
+# example QA build for libpcp ... to enable
+# $ make clean
+# $ ln GNUlocaldefs.debug GNUlocaldefs
+# $ make
+# $ sudo make install
+# this one turns optimization off and debug info on
+#
+CFLAGS_OPT = -O0 -g
diff --git a/src/libpcp/src/GNUmakefile b/src/libpcp/src/GNUmakefile
new file mode 100644
index 0000000..4142b03
--- /dev/null
+++ b/src/libpcp/src/GNUmakefile
@@ -0,0 +1,160 @@
+#
+# Copyright (c) 2012-2014 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = connect.c context.c desc.c err.c fetch.c freeresult.c \
+ help.c instance.c p_desc.c p_error.c p_fetch.c p_instance.c \
+ p_profile.c p_result.c p_text.c p_pmns.c p_creds.c p_auth.c \
+ pdu.c pdubuf.c pmns.c profile.c store.c units.c util.c ipc.c \
+ sortinst.c logmeta.c logportmap.c logutil.c tz.c interp.c \
+ checksum.c rtime.c tv.c spec.c fetchlocal.c optfetch.c AF.c \
+ stuffvalue.c endian.c config.c auxconnect.c auxserver.c discovery.c \
+ p_lcontrol.c p_lrequest.c p_lstatus.c logconnect.c logcontrol.c \
+ connectlocal.c derive.c derive_fetch.c events.c lock.c hash.c \
+ fault.c access.c getopt.c probe.c
+HFILES = derive.h internal.h avahi.h probe.h
+YFILES = getdate.y
+VERSION_SCRIPT = exports
+
+LSRCFILES = check-statics $(VERSION_SCRIPT)
+
+ifeq "$(ENABLE_SECURE)" "true"
+LLDLIBS += $(LIB_FOR_SSL) $(LIB_FOR_NSS) $(LIB_FOR_NSPR) $(LIB_FOR_SASL)
+LCFLAGS += $(NSSCFLAGS) $(NSPRCFLAGS) $(SASLCFLAGS)
+CFILES += secureserver.c secureconnect.c
+else
+LSRCFILES += secureserver.c secureconnect.c
+endif
+
+ifeq "$(ENABLE_AVAHI)" "true"
+LLDLIBS += $(LIB_FOR_AVAHI)
+LCFLAGS += $(AVAHICFLAGS)
+CFILES += avahi.c
+else
+LSRCFILES += avahi.c
+endif
+
+ifneq "$(TARGET_OS)" "mingw"
+CFILES += accounts.c
+LSRCFILES += win32.c
+else
+CFILES += win32.c
+LSRCFILES += accounts.c
+LLDLIBS += -lpsapi -lws2_32
+endif
+
+ifeq "$(TARGET_OS)" "solaris"
+# enables standards compliant thread-safe interfaces (accounts.c)
+LCFLAGS += -D_POSIX_PTHREAD_SEMANTICS
+endif
+
+ifeq "$(LIB_FOR_BASENAME)" "-lpcp"
+# don't need to be linked to myself in this case!
+LIB_FOR_BASENAME =
+endif
+
+LLDLIBS += $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS) $(LIB_FOR_RT)
+
+LCFLAGS += -DLIBPCP_INTERNAL '-DEXEC_SUFFIX="$(EXECSUFFIX)"' \
+ '-DDSO_SUFFIX="$(DSOSUFFIX)"'
+
+DSOVERSION = 3
+STATICLIBTARGET = libpcp.a
+LIBTARGET = libpcp.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp.$(DSOSUFFIX) libpcp.$(DSOSUFFIX).2
+
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+SYMTARGET = libpcp.$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp.$(DSOVERSION).$(DSOSUFFIX)
+SYMTARGET = libpcp.$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+STATICLIBTARGET =
+LIBTARGET = libpcp.$(DSOSUFFIX)
+SYMTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LDIRT += $(SYMTARGET) $(YFILES:%.y=%.tab.?) getdate.h check.done
+
+base default : $(LIBTARGET) check.done $(SYMTARGET) $(STATICLIBTARGET)
+
+ifneq "$(SYMTARGET)" ""
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $@
+endif
+
+include $(BUILDRULES)
+
+*.o: internal.h
+rtime.o: getdate.h
+derive.o derive_fetch.o: derive.h
+util.o: $(TOPDIR)/src/include/pcp/pmdbg.h
+
+$(OBJECTS): $(TOPDIR)/src/include/pcp/pmapi.h \
+ $(TOPDIR)/src/include/pcp/impl.h \
+ $(TOPDIR)/src/include/pcp/platform_defs.h
+
+.NOTPARALLEL:
+getdate.h getdate.tab.c: getdate.y
+ $(YACC) -d -b `basename $< .y` $< && cp `basename $@ .h`.tab.h $@
+
+ifeq "$(TARGET_OS)" "mingw"
+kernel_pmda_dso = windows
+else
+kernel_pmda_dso = $(TARGET_OS)
+endif
+
+install : default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ for tt in $(SYMTARGET); do \
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \
+ done
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(TOPDIR)/src/pmns/stdpmid:
+ cd $(@D); $(MAKE) $(@F)
+
+# The library is thread-safe ... check-statics will force a build failure
+# if there has been any change to the static variables and their disposition
+# ... refer to check-statics to add exceptions and annotations for new
+# cases
+#
+check.done: $(OBJECTS)
+ ./check-statics
+ touch check.done
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp/src/access.c b/src/libpcp/src/access.c
new file mode 100644
index 0000000..2bc99f0
--- /dev/null
+++ b/src/libpcp/src/access.c
@@ -0,0 +1,1875 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <limits.h>
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/* Host access control list */
+
+typedef struct {
+ char *hostspec; /* Host specification */
+ __pmSockAddr *hostid; /* Partial host-id to match */
+ __pmSockAddr *hostmask; /* Mask for wildcarding */
+ int level; /* Level of wildcarding */
+ unsigned int specOps; /* Mask of specified operations */
+ unsigned int denyOps; /* Mask of disallowed operations */
+ int maxcons; /* Max connections permitted (0 => no limit) */
+ int curcons; /* Current # connections from matching clients */
+} hostinfo;
+
+static hostinfo *hostlist;
+static int nhosts;
+static int szhostlist;
+
+/* User access control list */
+
+typedef struct {
+ char *username; /* User specification */
+ __pmUserID userid; /* User identifier to match */
+ unsigned int ngroups; /* Count of groups to which the user belongs */
+ __pmGroupID *groupids; /* Names of groups to which the user belongs */
+ unsigned int specOps; /* Mask of specified operations */
+ unsigned int denyOps; /* Mask of disallowed operations */
+ int maxcons; /* Max connections permitted (0 => no limit) */
+ int curcons; /* Current # connections from matching clients */
+} userinfo;
+
+static userinfo *userlist;
+static int nusers;
+static int szuserlist;
+
+/* Group access control list */
+
+typedef struct {
+ char *groupname; /* Group specification */
+ __pmGroupID groupid; /* Group identifier to match */
+ unsigned int nusers; /* Count of users in this group */
+ __pmUserID *userids; /* Names of users in this group */
+ unsigned int specOps; /* Mask of specified operations */
+ unsigned int denyOps; /* Mask of disallowed operations */
+ int maxcons; /* Max connections permitted (0 => no limit) */
+ int curcons; /* Current # connections from matching clients */
+} groupinfo;
+
+static groupinfo *grouplist;
+static int ngroups;
+static int szgrouplist;
+
+/* Mask of the operations defined by the user of the routines */
+static unsigned int all_ops; /* mask of all operations specifiable */
+
+/* This allows the set of valid operations to be specified.
+ * Operations must be powers of 2.
+ */
+int
+__pmAccAddOp(unsigned int op)
+{
+ unsigned int i, mask;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+
+ /* op must not be zero or clash with existing ops */
+ if (op == 0 || (op & all_ops))
+ return -EINVAL;
+
+ /* Find the lowest bit in op that is set (WORD_BIT from limits.h is the
+ * number of bits in an unsigned int)
+ */
+ for (i = 0; i < WORD_BIT; i++)
+ if (op & (mask = 1 << i))
+ break;
+
+ /* only one bit may be set in op */
+ if (op & ~mask)
+ return -EINVAL;
+
+ all_ops |= mask;
+ return 0;
+}
+
+/* Get the host id for this host. The host id is used when translating
+ * references to localhost into the host's real IP address during parsing of
+ * the access control section of the config file. It may also used when
+ * checking for incoming connections from localhost.
+ */
+
+static int gotmyhostid;
+static __pmHostEnt *myhostid;
+static char myhostname[MAXHOSTNAMELEN+1];
+
+/*
+ * Always called with __pmLock_libpcp already held, so accessing
+ * gotmyhostid, myhostname, myhostid and gethostname() call are all
+ * thread-safe.
+ */
+static int
+getmyhostid(void)
+{
+ if (gethostname(myhostname, MAXHOSTNAMELEN) < 0) {
+ __pmNotifyErr(LOG_ERR, "gethostname failure\n");
+ return -1;
+ }
+ myhostname[MAXHOSTNAMELEN-1] = '\0';
+
+ if ((myhostid = __pmGetAddrInfo(myhostname)) == NULL) {
+ if ((myhostid = __pmGetAddrInfo("localhost")) == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmGetAddrInfo failure for both %s and localhost\n",
+ myhostname);
+ return -1;
+ }
+ }
+ gotmyhostid = 1;
+ return 0;
+}
+
+/* Used for saving the current state of the access lists */
+
+enum { HOSTS_SAVED = 0x1, USERS_SAVED = 0x2, GROUPS_SAVED = 0x4 };
+static int saved;
+static hostinfo *oldhostlist;
+static int oldnhosts;
+static int oldszhostlist;
+static userinfo *olduserlist;
+static int oldnusers;
+static int oldszuserlist;
+static groupinfo *oldgrouplist;
+static int oldngroups;
+static int oldszgrouplist;
+
+/* Save the current access control lists.
+ * Returns 0 for success or a negative error code on error.
+ */
+int
+__pmAccSaveLists(void)
+{
+ int sts, code = 0;
+
+ if ((sts = __pmAccSaveHosts()) < 0)
+ code = sts;
+ if ((sts = __pmAccSaveUsers()) < 0)
+ code = sts;
+ if ((sts = __pmAccSaveGroups()) < 0)
+ code = sts;
+ return code;
+}
+
+int
+__pmAccSaveHosts(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (saved & HOSTS_SAVED)
+ return PM_ERR_TOOBIG;
+
+ saved |= HOSTS_SAVED;
+ oldhostlist = hostlist;
+ oldnhosts = nhosts;
+ oldszhostlist = szhostlist;
+ hostlist = NULL;
+ nhosts = 0;
+ szhostlist = 0;
+ return 0;
+}
+
+int
+__pmAccSaveUsers(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (saved & USERS_SAVED)
+ return PM_ERR_TOOBIG;
+
+ saved |= USERS_SAVED;
+ olduserlist = userlist;
+ oldnusers = nusers;
+ oldszuserlist = szuserlist;
+ userlist = NULL;
+ nusers = 0;
+ szuserlist = 0;
+ return 0;
+}
+
+int
+__pmAccSaveGroups(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (saved & GROUPS_SAVED)
+ return PM_ERR_TOOBIG;
+
+ saved |= GROUPS_SAVED;
+ oldgrouplist = grouplist;
+ oldngroups = ngroups;
+ oldszgrouplist = szgrouplist;
+ grouplist = NULL;
+ ngroups = 0;
+ szgrouplist = 0;
+ return 0;
+}
+
+/* Free the current access lists. These are done automatically by
+ * __pmAccRestoreLists so there is no need for them to be globally visible.
+ * A caller of these routines should never need to dispose of the access lists
+ * once they has been built.
+ */
+static void
+accfreehosts(void)
+{
+ int i;
+
+ if (szhostlist) {
+ for (i = 0; i < nhosts; i++)
+ if (hostlist[i].hostspec != NULL)
+ free(hostlist[i].hostspec);
+ free(hostlist);
+ }
+ hostlist = NULL;
+ nhosts = 0;
+ szhostlist = 0;
+}
+
+static void
+accfreeusers(void)
+{
+ int i;
+
+ if (szuserlist) {
+ for (i = 1; i < nusers; i++) {
+ free(userlist[i].username);
+ if (userlist[i].ngroups)
+ free(userlist[i].groupids);
+ }
+ free(userlist);
+ }
+ userlist = NULL;
+ nusers = 0;
+ szuserlist = 0;
+}
+
+static void
+accfreegroups(void)
+{
+ int i;
+
+ if (szgrouplist) {
+ for (i = 1; i < ngroups; i++) {
+ free(grouplist[i].groupname);
+ if (grouplist[i].nusers)
+ free(grouplist[i].userids);
+ }
+ free(grouplist);
+ }
+ grouplist = NULL;
+ ngroups = 0;
+ szgrouplist = 0;
+}
+
+/* Restore the previously saved access lists. Any current list is freed.
+ * Returns 0 for success or a negative error code on error.
+ */
+int
+__pmAccRestoreLists(void)
+{
+ int sts, code = 0;
+
+ if ((sts = __pmAccRestoreHosts()) < 0)
+ code = sts;
+ if ((sts = __pmAccRestoreUsers()) < 0 && !code)
+ code = sts;
+ if ((sts = __pmAccRestoreGroups()) < 0 && !code)
+ code = sts;
+ return code;
+}
+
+int
+__pmAccRestoreHosts(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (!(saved & HOSTS_SAVED))
+ return PM_ERR_TOOSMALL;
+
+ accfreehosts();
+ saved &= ~HOSTS_SAVED;
+ hostlist = oldhostlist;
+ nhosts = oldnhosts;
+ szhostlist = oldszhostlist;
+ return 0;
+}
+
+int
+__pmAccRestoreUsers(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (!(saved & USERS_SAVED))
+ return PM_ERR_TOOSMALL;
+
+ accfreeusers();
+ saved &= ~USERS_SAVED;
+ userlist = olduserlist;
+ nusers = oldnusers;
+ szuserlist = oldszuserlist;
+ return 0;
+}
+
+int
+__pmAccRestoreGroups(void)
+{
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (!(saved & GROUPS_SAVED))
+ return PM_ERR_TOOSMALL;
+
+ accfreegroups();
+ saved &= ~GROUPS_SAVED;
+ grouplist = oldgrouplist;
+ ngroups = oldngroups;
+ szgrouplist = oldszgrouplist;
+ return 0;
+}
+
+/* Free the previously saved access lists. These should be called when the saved
+ * access lists are no longer required (typically because the new ones supercede
+ * the old, have been verified as valid and correct, etc).
+ */
+void
+__pmAccFreeSavedLists(void)
+{
+ __pmAccFreeSavedHosts();
+ __pmAccFreeSavedUsers();
+ __pmAccFreeSavedGroups();
+}
+
+void
+__pmAccFreeSavedHosts(void)
+{
+ int i;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+ if (!(saved & HOSTS_SAVED))
+ return;
+
+ if (oldszhostlist) {
+ for (i = 0; i < oldnhosts; i++)
+ if (oldhostlist[i].hostspec != NULL)
+ free(oldhostlist[i].hostspec);
+ free(oldhostlist);
+ }
+ saved &= ~HOSTS_SAVED;
+}
+
+void
+__pmAccFreeSavedUsers(void)
+{
+ int i;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+ if (!(saved & USERS_SAVED))
+ return;
+
+ if (oldszuserlist) {
+ for (i = 1; i < oldnusers; i++) {
+ free(olduserlist[i].username);
+ if (olduserlist[i].ngroups)
+ free(olduserlist[i].groupids);
+ }
+ free(olduserlist);
+ }
+ saved &= ~USERS_SAVED;
+}
+
+void
+__pmAccFreeSavedGroups(void)
+{
+ int i;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+ if (!(saved & GROUPS_SAVED))
+ return;
+
+ if (oldszgrouplist) {
+ for (i = 1; i < oldngroups; i++) {
+ free(oldgrouplist[i].groupname);
+ if (oldgrouplist[i].nusers)
+ free(oldgrouplist[i].userids);
+ }
+ free(oldgrouplist);
+ }
+ saved &= ~GROUPS_SAVED;
+}
+
+/* Build up strings representing the ip address and the mask.
+ * Compute the wildcard level as we go.
+ */
+static int
+parseInetWildCard(const char *name, char *ip, char *mask)
+{
+ int level;
+ int ipIx, maskIx;
+ int i, n;
+ const char *p;
+
+ /* Accept "*" or ".*" as the inet full wild card spec. */
+ level = 4;
+ ipIx = maskIx = 0;
+ p = name;
+ if (*p == '.') {
+ ++p;
+ if (*p != '*') {
+ __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name);
+ return -EINVAL;
+ }
+ }
+ for (/**/; *p && *p != '*' ; p++) {
+ n = (int)strtol(p, (char **)&p, 10);
+ if ((*p != '.' && *p != '*') || n < 0 || n > 255) {
+ __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name);
+ return -EINVAL;
+ }
+ if (ipIx != 0) {
+ ipIx += sprintf(ip + ipIx, ".");
+ maskIx += sprintf(mask + maskIx, ".");
+ }
+ ipIx += sprintf(ip + ipIx, "%d", n);
+ maskIx += sprintf(mask + maskIx, "255");
+ --level;
+ /* Check the wildcard level, 0 is exact match, 4 is most general */
+ if (level < 1) {
+ __pmNotifyErr(LOG_ERR, "Too many dots in host pattern \"%s\"\n", name);
+ return -EINVAL;
+ }
+ }
+ /* Add zeroed components for the wildcarded levels. */
+ for (i = 0; i < level; ++i) {
+ if (ipIx != 0) {
+ ipIx += sprintf(ip + ipIx, ".");
+ maskIx += sprintf(mask + maskIx, ".");
+ }
+ ipIx += sprintf(ip + ipIx, "0");
+ maskIx += sprintf(mask + maskIx, "0");
+ }
+ return level;
+}
+
+static int
+parseIPv6WildCard(const char *name, char *ip, char *mask)
+{
+ int level;
+ int ipIx, maskIx;
+ int emptyRegion;
+ int n;
+ const char *p;
+
+ /* Accept ":*" as the IPv6 full wild card spec. Otherwise,
+ if the string starts with ':', then the second character must also be a ':'
+ which would form a region of zeroes of unspecified length. */
+ level = 8;
+ emptyRegion = 0;
+ ipIx = maskIx = 0;
+ p = name;
+ if (*p == ':') {
+ ++p;
+ if (*p != '*') {
+ if (*p != ':') {
+ __pmNotifyErr(LOG_ERR, "Bad IPv6 address wildcard, %s\n", name);
+ return -EINVAL;
+ }
+ ipIx = sprintf(ip, ":");
+ maskIx = sprintf(mask, ":");
+ /* The second colon will be detected in the loop below. */
+ }
+ }
+
+ for (/**/; *p && *p != '*' ; p++) {
+ /* Check for an empty region. There can only be one. */
+ if (*p == ':') {
+ if (emptyRegion) {
+ __pmNotifyErr(LOG_ERR, "Too many empty regions in host pattern \"%s\"\n", name);
+ return -EINVAL;
+ }
+ emptyRegion = 1;
+ ipIx += sprintf(ip + ipIx, ":");
+ maskIx += sprintf(mask + maskIx, ":");
+ }
+ else {
+ n = (int)strtol(p, (char **)&p, 16);
+ if ((*p != ':' && *p != '*') || n < 0 || n > 0xffff) {
+ __pmNotifyErr(LOG_ERR, "Bad IPv6 address wildcard, %s\n", name);
+ return -EINVAL;
+ }
+ if (ipIx != 0) {
+ ipIx += sprintf(ip + ipIx, ":");
+ maskIx += sprintf(mask + maskIx, ":");
+ }
+ ipIx += sprintf(ip + ipIx, "%x", n);
+ maskIx += sprintf(mask + maskIx, "ffff");
+ }
+ --level;
+ /* Check the wildcard level, 0 is exact match, 8 is most general */
+ if (level < 1) {
+ __pmNotifyErr(LOG_ERR, "Too many colons in host pattern \"%s\"\n", name);
+ return -EINVAL;
+ }
+ }
+ /* Add zeroed components for the wildcarded levels.
+ If the entire address is wildcarded then return the zero address. */
+ if (level == 8 || (level == 7 && emptyRegion)) {
+ /* ":*" or "::*" */
+ strcpy(ip, "::");
+ strcpy(mask, "::");
+ level = 8;
+ }
+ else if (emptyRegion) {
+ /* If there was an empty region, then we assume that the wildcard represents the final
+ segment of the spec only. */
+ sprintf(ip + ipIx, ":0");
+ sprintf(mask + maskIx, ":0");
+ }
+ else {
+ /* no empty region, so use one to finish off the address and the mask */
+ sprintf(ip + ipIx, "::");
+ sprintf(mask + maskIx, "::");
+ }
+ return level;
+}
+
+static int
+parseWildCard(const char *name, char *ip, char *mask)
+{
+ /* We need only handle inet and IPv6 wildcards here. Unix
+ * wildcards are handled separately.
+ *
+ * Names containing ':' are IPv6. The IPv6 full wildcard spec is ":*".
+ */
+ if (strchr(name, ':') != NULL)
+ return parseIPv6WildCard(name, ip, mask);
+
+ /* Names containing '.' are inet. The inet full wildcard spec ".*". */
+ if (strchr(name, '.') != NULL)
+ return parseInetWildCard(name, ip, mask);
+
+ __pmNotifyErr(LOG_ERR, "Bad IP address wildcard, %s\n", name);
+ return -EINVAL;
+}
+
+/* Information representing an access specification. */
+struct accessSpec {
+ char *name;
+ __pmSockAddr *hostid;
+ __pmSockAddr *hostmask;
+ int level;
+};
+
+static int
+setAccessSpecAddresses(struct accessSpec *spec, const char *addr, const char *mask)
+{
+ /* Now create socket addresses for the address and mask. */
+ spec->hostid = __pmStringToSockAddr(addr);
+ if (spec->hostid == NULL) {
+ __pmNotifyErr(LOG_ERR, "__pmStringToSockAddr failure\n");
+ return -ENOMEM;
+ }
+ spec->hostmask = __pmStringToSockAddr(mask);
+ if (spec->hostmask == NULL) {
+ __pmNotifyErr(LOG_ERR, "__pmStringToSockAddr failure\n");
+ __pmSockAddrFree(spec->hostid);
+ return -ENOMEM;
+ }
+ return 0; /* ok */
+}
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+/* For the Unix spec:
+ * - On input:
+ * - We're expecting 'name' to be empty or an optional series of '/' followed by
+ * and optional '*'.
+ * - On output, within the 'spec' structure:
+ * - The path of the 'hostid' will be '/'.
+ * - The 'hostmask' will be a copy of the 'hostid'.
+ * - The 'level' will be 1
+ * This sets up the spec to match the path of any unix domain socket.
+ */
+static int
+getUnixSpec(const char *name, struct accessSpec *spec)
+{
+ const char *path;
+ size_t addrSize;
+ char rootPath[2];
+ int sts;
+
+ /* Accept any number of '/', as is done by parseProtocolSpec(). */
+ for (path = name; *path == __pmPathSeparator(); ++path)
+ ;
+
+ /* Accept a final '*'. */
+ addrSize = strlen(path);
+ if (addrSize >= 1 && path[addrSize - 1] == '*')
+ --addrSize;
+
+ /* If there is anything remaining, then it is a path, which we will ignore, with a
+ * warning.
+ */
+ if (addrSize)
+ __pmNotifyErr(LOG_WARNING, "Ignoring the path in host pattern \"%s\"\n", name);
+
+ /* Set the address and mask. */
+ rootPath[0] = __pmPathSeparator();
+ rootPath[1] = '\0';
+ sts = setAccessSpecAddresses(spec, rootPath, rootPath);
+ if (sts < 0)
+ return sts;
+
+ /* Complete the rest of the spec.
+ * Do this last since a valid name indicates a valid spec.
+ */
+ spec->name = strdup("unix:");
+ if (spec->name == NULL)
+ __pmNoMem("Unix host pattern name buffer", sizeof("unix:"), PM_FATAL_ERR);
+ spec->level = 1;
+
+ return 0; /* ok */
+}
+#endif /* defined(HAVE_STRUCT_SOCKADDR_UN) */
+
+/* Construct the proper spec for the given wildcard. */
+static int
+getWildCardSpec(const char *name, struct accessSpec *spec)
+{
+ char addr[INET6_ADDRSTRLEN];
+ char mask[INET6_ADDRSTRLEN];
+ int sts;
+
+ /* Build up strings representing the ip address and the mask. Compute the wildcard
+ level as we go. */
+ spec->level = parseWildCard(name, addr, mask);
+ if (spec->level < 0)
+ return spec->level;
+
+ /* Set the address and mask. */
+ if ((sts = setAccessSpecAddresses(spec, addr, mask)) < 0)
+ return sts;
+
+ /* Do this last since a valid name indicates a valid spec. */
+ spec->name = strdup(name);
+ return sts; /* ok */
+}
+
+/* Determine all of the access specs which result from the given name. */
+static struct accessSpec *
+getHostAccessSpecs(const char *name, int *sts)
+{
+ struct accessSpec *specs;
+ size_t specSize;
+ size_t specIx;
+ size_t ix;
+ size_t need;
+ __pmSockAddr *myAddr;
+ __pmHostEnt *servInfo;
+ void *enumIx;
+ int family;
+ int isWildCard;
+ const char *realname;
+ const char *p;
+
+ /* If the general wildcard ("*") is specified, then generate individual
+ * wildcards for inet, IPv6 (if supported) and unix domain sockets
+ * (if supported). "localhost" is covered by the inet and IPv6 wildcards.
+ */
+ if (strcmp(name, "*") == 0) {
+ const char *ipv6 = __pmGetAPIConfig("ipv6");
+
+ /* Use calloc so that the final entries are zeroed, if not used. */
+ specs = calloc(4, sizeof(*specs));
+ if (specs == NULL)
+ __pmNoMem("Access Spec List", 4 * sizeof(*specs), PM_FATAL_ERR);
+
+ /* The inet general wildcard. */
+ specIx = 0;
+ getWildCardSpec(".*", &specs[specIx]); /* Guaranteed to succeed. */
+ ++specIx;
+
+ /* The IPv6 general wildcard. */
+ if (ipv6 != NULL && strcmp(ipv6, "true") == 0) {
+ getWildCardSpec(":*", &specs[specIx]); /* Guaranteed to succeed. */
+ ++specIx;
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* The unix domain socket general wildcard. */
+ getUnixSpec("*", &specs[specIx]); /* Guaranteed to succeed. */
+#endif
+
+ return specs;
+ }
+
+ /* If it is any other wildcard, make sure the '*' is at the end. */
+ if ((p = strchr(name, '*')) != NULL) {
+ if (p[1] != '\0') {
+ __pmNotifyErr(LOG_ERR,
+ "Wildcard in host pattern \"%s\" is not at the end\n",
+ name);
+ *sts = -EINVAL;
+ return NULL;
+ }
+ isWildCard = 1;
+ }
+ else
+ isWildCard = 0;
+
+ /* Initialize the specs array controls for general use. */
+ specs = NULL;
+ specSize = 0;
+ specIx = 0;
+
+ /* If a name of the form "local:[xxx]" is specified, then expand it to be
+ * "unix:[xxx]" + "localhost" in order to match the meaning of "local:[xxx]"
+ * for pmcd clients.
+ * If the spec is already "unix:[xxx] then leave it at that.
+ * Note that the above includes wildcards.
+ */
+ if (strncmp(name, "local:", 6) == 0 || strncmp(name, "unix:", 5) == 0) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* Use calloc so that the final entry is zeroed, if not used. */
+ specSize = 2;
+ specs = calloc(specSize, sizeof(*specs));
+ if (specs == NULL)
+ __pmNoMem("Access Spec List", specSize * sizeof(*specs), PM_FATAL_ERR);
+
+ /* Process the equivalent unix domain socket spec. */
+ if ((*sts = getUnixSpec(strchr(name, ':') + 1, &specs[specIx])) >= 0) {
+ /* If the spec was "unix:" then we're done. */
+ if (name[0] == 'u')
+ return specs;
+ ++specIx;
+ }
+#else
+ __pmNotifyErr(LOG_WARNING, "Host pattern \"%s\" is not supported. Using \"localhost\"\n",
+ name);
+#endif
+
+ /* Fall through to handle "localhost". */
+ name = "localhost";
+ }
+ else if (isWildCard) {
+ /* If any other wildcard is specified, then our list will contain that single item.
+ * Use calloc so that the final entry is zeroed.
+ */
+ specs = calloc(2, sizeof(*specs));
+ if (specs == NULL)
+ __pmNoMem("Access Spec List", 2 * sizeof(*specs), PM_FATAL_ERR);
+ *sts = getWildCardSpec(name, &specs[0]);
+ return specs;
+ }
+
+ /* Assume we have a host name or address. Resolve it and contruct a list containing all of the
+ resolved addresses. If the name is "localhost", then resolve using the actual host name. */
+ if (strcasecmp(name, "localhost") == 0) {
+ if (!gotmyhostid) {
+ if (getmyhostid() < 0) {
+ __pmNotifyErr(LOG_ERR, "Can't get host name/IP address, giving up\n");
+ *sts = -EHOSTDOWN;
+ if (specs)
+ free(specs);
+ return NULL; /* should never happen! */
+ }
+ }
+ realname = myhostname;
+ }
+ else
+ realname = name;
+
+ *sts = -EHOSTUNREACH;
+ if ((servInfo = __pmGetAddrInfo(realname)) != NULL) {
+ /* Collect all of the resolved addresses. Check for the end of the list within the
+ loop since we need to add an empty entry and the code to grow the list is within the
+ loop. */
+ enumIx = NULL;
+ for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ /**/;
+ myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ if (specIx == specSize) {
+ specSize = specSize == 0 ? 4 : specSize * 2;
+ need = specSize * sizeof(*specs);
+ specs = realloc(specs, need);
+ if (specs == NULL) {
+ __pmNoMem("Access Spec List", need, PM_FATAL_ERR);
+ }
+ }
+ /* No more addresses? */
+ if (myAddr == NULL) {
+ specs[specIx].name = NULL;
+ break;
+ }
+ /* Don't add any duplicate entries. It causes false permission clashes. */
+ for (ix = 0; ix < specIx; ++ix) {
+ if (__pmSockAddrCompare(myAddr, specs[ix].hostid) == 0)
+ break;
+ }
+ if (ix < specIx){
+ __pmSockAddrFree(myAddr);
+ continue;
+ }
+ /* Add the new address and its corresponding mask. AF_UNIX socket addresses
+ * will not appear here.
+ */
+ family = __pmSockAddrGetFamily(myAddr);
+ if (family == AF_INET) {
+ specs[specIx].hostmask = __pmStringToSockAddr("255.255.255.255");
+ specs[specIx].level = 0;
+ }
+ else if (family == AF_INET6) {
+ specs[specIx].hostmask = __pmStringToSockAddr("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+ specs[specIx].level = 0;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "Unsupported socket address family: %d\n", family);
+ __pmSockAddrFree(myAddr);
+ continue;
+ }
+ specs[specIx].hostid = myAddr;
+ specs[specIx].name = strdup(name);
+ *sts = 0;
+ ++specIx;
+ }
+ __pmHostEntFree(servInfo);
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "__pmGetAddrInfo(%s), %s\n",
+ realname, hoststrerror());
+ }
+
+ /* Return NULL if nothing was discovered. *sts is already set. */
+ if (specIx == 0 && specs != NULL) {
+ free(specs);
+ specs = NULL;
+ }
+ return specs;
+}
+
+/* Routine to add a group to the group access list with a specified set of
+ * permissions and a maximum connection limit.
+ * specOps is a mask. Only bits corresponding to operations specified by
+ * __pmAccAddOp have significance. A 1 bit indicates that the
+ * corresponding bit in the denyOps mask is to be used. A zero bit in
+ * specOps means the corresponding bit in denyOps should be ignored.
+ * denyOps is a mask where a 1 bit indicates that permission to perform the
+ * corresponding operation should be denied.
+ * maxcons is a maximum connection limit for individial groups. Zero means
+ * unspecified, which will allow unlimited connections or a subsequent
+ * __pmAccAddUser call with the same group to override maxcons.
+ *
+ * Returns a negated system error code on failure.
+ */
+
+int
+__pmAccAddGroup(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons)
+{
+ size_t need;
+ unsigned int nusers;
+ int i = 0, sts, wildcard, found = 0;
+ char errmsg[256];
+ char *groupname;
+ __pmUserID *userids;
+ __pmGroupID groupid;
+ groupinfo *gp;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (specOps & ~all_ops)
+ return -EINVAL;
+ if (maxcons < 0)
+ return -EINVAL;
+
+ wildcard = (strcmp(name, "*") == 0);
+ if (!wildcard) {
+ if ((sts = __pmGroupnameToID(name, &groupid)) < 0) {
+ __pmNotifyErr(LOG_ERR, "Failed to lookup group \"%s\": %s\n",
+ name, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ return -EINVAL;
+ }
+
+ /* Search for a match to this group in the groups access table */
+ for (i = 1; i < ngroups; i++) {
+ if (__pmEqualGroupIDs(groupid, grouplist[i].groupid)) {
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ /* Check and augment existing group access list entry for this groupid
+ * if a match was found, otherwise insert a new entry in list.
+ */
+ if (found) {
+ /* If the specified operations overlap, they must agree */
+ gp = &grouplist[i];
+ if ((gp->maxcons && maxcons && gp->maxcons != maxcons) ||
+ ((gp->specOps & specOps) &&
+ ((gp->specOps & gp->denyOps) ^ (specOps & denyOps)))) {
+ __pmNotifyErr(LOG_ERR,
+ "Permission clash for group %s with earlier statement\n",
+ name);
+ return -EINVAL;
+ }
+ gp->specOps |= specOps;
+ gp->denyOps |= (specOps & denyOps);
+ if (maxcons)
+ gp->maxcons = maxcons;
+ } else {
+ /* Make the group access list larger if required */
+ if (ngroups == szgrouplist) {
+ szgrouplist += 8;
+ need = szgrouplist * sizeof(groupinfo);
+ grouplist = (groupinfo *)realloc(grouplist, need);
+ if (grouplist == NULL)
+ __pmNoMem("AddGroup enlarge", need, PM_FATAL_ERR);
+ }
+ /* insert a permanent initial entry for '*' group wildcard */
+ if (ngroups == 0) {
+ gp = &grouplist[0];
+ memset(gp, 0, sizeof(*gp));
+ gp->groupname = "*";
+ gp->denyOps = gp->specOps = all_ops;
+ if (!wildcard) { /* if so, we're adding two entries */
+ i = ++ngroups;
+ }
+ }
+ if (wildcard) {
+ i = 0; /* always the first entry, setup constants */
+ gp = &grouplist[i]; /* for use when overwriting below */
+ groupname = gp->groupname;
+ groupid = gp->groupid;
+ userids = gp->userids;
+ nusers = gp->nusers;
+ } else if ((sts = __pmGroupsUserIDs(name, &userids, &nusers)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "Failed to lookup users in group \"%s\": %s\n",
+ name, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ return sts;
+ } else if ((groupname = strdup(name)) == NULL) {
+ __pmNoMem("AddGroup name", strlen(name)+1, PM_FATAL_ERR);
+ }
+ gp = &grouplist[i];
+ gp->groupname = groupname;
+ gp->groupid = groupid;
+ gp->userids = userids;
+ gp->nusers = nusers;
+ gp->specOps = specOps;
+ gp->denyOps = specOps & denyOps;
+ gp->maxcons = maxcons;
+ gp->curcons = 0;
+ ngroups++;
+ }
+
+ return 0;
+}
+
+/* Routine to add a user to the user access list with a specified set of
+ * permissions and a maximum connection limit.
+ * specOps is a mask. Only bits corresponding to operations specified by
+ * __pmAccAddOp have significance. A 1 bit indicates that the
+ * corresponding bit in the denyOps mask is to be used. A zero bit in
+ * specOps means the corresponding bit in denyOps should be ignored.
+ * denyOps is a mask where a 1 bit indicates that permission to perform the
+ * corresponding operation should be denied.
+ * maxcons is a maximum connection limit for individial users. Zero means
+ * unspecified, which will allow unlimited connections or a subsequent
+ * __pmAccAddUser call with the same user to override maxcons.
+ *
+ * Returns a negated system error code on failure.
+ */
+
+int
+__pmAccAddUser(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons)
+{
+ size_t need;
+ unsigned int ngroups;
+ int i = 0, sts, wildcard, found = 0;
+ char errmsg[256];
+ char *username;
+ __pmUserID userid;
+ __pmGroupID *groupids;
+ userinfo *up;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (specOps & ~all_ops)
+ return -EINVAL;
+ if (maxcons < 0)
+ return -EINVAL;
+
+ wildcard = (strcmp(name, "*") == 0);
+ if (!wildcard) {
+ if ((sts = __pmUsernameToID(name, &userid)) < 0) {
+ __pmNotifyErr(LOG_ERR, "Failed to lookup user \"%s\": %s\n",
+ name, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ return -EINVAL;
+ }
+
+ /* Search for a match to this user in the existing table of users. */
+ for (i = 1; i < nusers; i++) {
+ if (__pmEqualUserIDs(userid, userlist[i].userid)) {
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ /* Check and augment existing user access list entry for this userid if a
+ * match was found otherwise insert a new entry in list.
+ */
+ if (found) {
+ /* If the specified operations overlap, they must agree */
+ up = &userlist[i];
+ if ((up->maxcons && maxcons && up->maxcons != maxcons) ||
+ ((up->specOps & specOps) &&
+ ((up->specOps & up->denyOps) ^ (specOps & denyOps)))) {
+ __pmNotifyErr(LOG_ERR,
+ "Permission clash for user %s with earlier statement\n",
+ name);
+ return -EINVAL;
+ }
+ up->specOps |= specOps;
+ up->denyOps |= (specOps & denyOps);
+ if (maxcons)
+ up->maxcons = maxcons;
+ } else {
+ /* Make the user access list larger if required */
+ if (nusers == szuserlist) {
+ szuserlist += 8;
+ need = szuserlist * sizeof(userinfo);
+ userlist = (userinfo *)realloc(userlist, need);
+ if (userlist == NULL) {
+ __pmNoMem("AddUser enlarge", need, PM_FATAL_ERR);
+ }
+ }
+ /* insert a permanent initial entry for '*' user wildcard */
+ if (nusers == 0) {
+ up = &userlist[0];
+ memset(up, 0, sizeof(*up));
+ up->username = "*";
+ up->denyOps = up->specOps = all_ops;
+ if (!wildcard) /* if so, we're adding two entries */
+ i = ++nusers;
+ }
+ if (wildcard) {
+ i = 0; /* always the first entry, setup constants */
+ up = &userlist[i]; /* for use when overwriting below */
+ username = up->username;
+ userid = up->userid;
+ ngroups = up->ngroups;
+ groupids = up->groupids;
+ } else if ((sts = __pmUsersGroupIDs(name, &groupids, &ngroups)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "Failed to lookup groups for user \"%s\": %s\n",
+ name, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ return sts;
+ } else if ((username = strdup(name)) == NULL) {
+ __pmNoMem("AddUser name", strlen(name)+1, PM_FATAL_ERR);
+ }
+ up = &userlist[i];
+ up->username = username;
+ up->userid = userid;
+ up->groupids = groupids;
+ up->ngroups = ngroups;
+ up->specOps = specOps;
+ up->denyOps = specOps & denyOps;
+ up->maxcons = maxcons;
+ up->curcons = 0;
+ nusers++;
+ }
+
+ return 0;
+}
+
+/* Routine to add a host to the host access list with a specified set of
+ * permissions and a maximum connection limit.
+ * specOps is a mask. Only bits corresponding to operations specified by
+ * __pmAccAddOp have significance. A 1 bit indicates that the
+ * corresponding bit in the denyOps mask is to be used. A zero bit in
+ * specOps means the corresponding bit in denyOps should be ignored.
+ * denyOps is a mask where a 1 bit indicates that permission to perform the
+ * corresponding operation should be denied.
+ * maxcons is a maximum connection limit for clients on hosts matching the host
+ * id. Zero means unspecified, which will allow unlimited connections or
+ * a subsequent __pmAccAddHost call with the same host to override maxcons.
+ *
+ * Returns a negated system error code on failure.
+ */
+
+int
+__pmAccAddHost(const char *name, unsigned int specOps, unsigned int denyOps, int maxcons)
+{
+ size_t need;
+ int i, sts = 0;
+ struct accessSpec *specs;
+ struct accessSpec *spec;
+ hostinfo *hp;
+ int found;
+ char *prevHost;
+ char *prevName;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+ if (specOps & ~all_ops)
+ return -EINVAL;
+ if (maxcons < 0)
+ return -EINVAL;
+
+ /* The specified name may result in more than one access specification. */
+ specs = getHostAccessSpecs(name, &sts);
+ if (specs == NULL)
+ return sts;
+
+ /* Search for a match to each spec in the existing table of hosts. We will either use
+ or free the host id, mask and name of each spec as we go. */
+ prevHost = NULL;
+ prevName = NULL;
+ found = 0;
+ for (spec = specs; spec->name != NULL; ++spec) {
+ sts = 0;
+ for (i = 0; i < nhosts; i++) {
+ if (hostlist[i].level > spec->level)
+ break;
+ /* hostid AND level must match. Wildcarded IP addresses have zero in
+ * the unspecified components. Distinguish between 155.23.6.0 and
+ * 155.23.6.* or 155.23.0.0 and 155.23.* by wildcard level. IP
+ * addresses shouldn't have zero in last position but to deal with
+ * them just in case.
+ * This test also works for Unix Domain addresses and wildcards.
+ */
+ if (__pmSockAddrCompare(spec->hostid, hostlist[i].hostid) == 0 &&
+ spec->level == hostlist[i].level) {
+ sts = 1;
+ break;
+ }
+ }
+
+ /* Check and augment existing host access list entry for this host id if a
+ * match was found (sts == 1) otherwise insert a new entry in list.
+ */
+ if (sts == 1) {
+ __pmSockAddrFree(spec->hostid);
+ __pmSockAddrFree(spec->hostmask);
+
+ /* If the specified operations overlap, they must agree */
+ hp = &hostlist[i];
+ if ((hp->maxcons && maxcons && hp->maxcons != maxcons) ||
+ ((hp->specOps & specOps) &&
+ ((hp->specOps & hp->denyOps) ^ (specOps & denyOps)))) {
+ /* Suppress duplicate messages. These can occur when a host resolves to more
+ than one address. */
+ if (prevName == NULL ||
+ strcmp(prevName, spec->name) != 0 || strcmp(prevHost, hp->hostspec) != 0) {
+ __pmNotifyErr(LOG_ERR,
+ "Permission clash for %s with earlier statement for %s\n",
+ spec->name, hp->hostspec);
+ if (prevName != NULL) {
+ free(prevName);
+ free(prevHost);
+ }
+ prevName = strdup(spec->name);
+ prevHost = strdup(hp->hostspec);
+ }
+ free(spec->name);
+ continue;
+ }
+ free(spec->name);
+ hp->specOps |= specOps;
+ hp->denyOps |= (specOps & denyOps);
+ if (maxcons)
+ hp->maxcons = maxcons;
+ }
+ else {
+ /* Make the host access list larger if required */
+ if (nhosts == szhostlist) {
+ szhostlist += 8;
+ need = szhostlist * sizeof(hostinfo);
+ hostlist = (hostinfo *)realloc(hostlist, need);
+ if (hostlist == NULL) {
+ __pmNoMem("AddHost enlarge", need, PM_FATAL_ERR);
+ }
+ }
+
+ /* Move any subsequent hosts down to make room for the new entry*/
+ hp = &hostlist[i];
+ if (i < nhosts)
+ memmove(&hostlist[i+1], &hostlist[i],
+ (nhosts - i) * sizeof(hostinfo));
+ hp->hostspec = spec->name;
+ hp->hostid = spec->hostid;
+ hp->hostmask = spec->hostmask;
+ hp->level = spec->level;
+ hp->specOps = specOps;
+ hp->denyOps = specOps & denyOps;
+ hp->maxcons = maxcons;
+ hp->curcons = 0;
+ nhosts++;
+ }
+ /* Count the found hosts. */
+ ++found;
+ } /* loop over addresses */
+
+ if (prevName != NULL) {
+ free(prevName);
+ free(prevHost);
+ }
+ free(specs);
+ return found != 0 ? 0 : -EINVAL;
+}
+
+static __pmSockAddr **
+getClientIds(const __pmSockAddr *hostid, int *sts)
+{
+ __pmSockAddr **clientIds;
+ __pmSockAddr *myAddr;
+ size_t clientIx;
+ size_t clientSize;
+ size_t need;
+ void *enumIx;
+
+ *sts = 0;
+
+ /* If the address is not for "localhost", then return a list containing only
+ the given address. */
+ if (! __pmSockAddrIsLoopBack(hostid)) {
+ clientIds = calloc(2, sizeof(*clientIds));
+ if (clientIds == NULL)
+ __pmNoMem("Client Ids", 2 * sizeof(*clientIds), PM_FATAL_ERR);
+ clientIds[0] = __pmSockAddrDup(hostid);
+ return clientIds;
+ }
+
+ /* Map "localhost" to the real IP addresses. Host access statements for
+ * localhost are mapped to the "real" IP addresses so that wildcarding works
+ * consistently. First get the real host address;
+ */
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (!gotmyhostid)
+ getmyhostid();
+
+ *sts = PM_ERR_PERMISSION;
+ if (gotmyhostid <= 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ /* Now construct a list containing each address. Check for the end of the list within the
+ loop since we need to add an empty entry and the code to grow the list is within the
+ loop. */
+ clientIds = NULL;
+ clientIx = 0;
+ clientSize = 0;
+ enumIx = NULL;
+ for (myAddr = __pmHostEntGetSockAddr(myhostid, &enumIx);
+ /**/;
+ myAddr = __pmHostEntGetSockAddr(myhostid, &enumIx)) {
+ if (clientIx == clientSize) {
+ clientSize = clientSize == 0 ? 4 : clientSize * 2;
+ need = clientSize * sizeof(*clientIds);
+ clientIds = realloc(clientIds, need);
+ if (clientIds == NULL) {
+ PM_UNLOCK(__pmLock_libpcp);
+ __pmNoMem("Client Ids", need, PM_FATAL_ERR);
+ }
+ }
+ /* No more addresses? */
+ if (myAddr == NULL) {
+ clientIds[clientIx] = NULL;
+ break;
+ }
+ /* Add the new address and its corrsponding mask. */
+ clientIds[clientIx] = myAddr;
+ ++clientIx;
+ *sts = 0;
+ }
+
+ /* If no addresses were discovered, then return NULL. *sts is already set. */
+ if (clientIx == 0) {
+ free(clientIds);
+ clientIds = NULL;
+ }
+ return clientIds;
+}
+
+static void
+freeClientIds(__pmSockAddr **clientIds)
+{
+ int i;
+ for (i = 0; clientIds[i] != NULL; ++i)
+ free(clientIds[i]);
+ free(clientIds);
+}
+
+/* Called after accepting new client's connection to check that another
+ * connection from its host is permitted and to find which operations the
+ * client is permitted to perform.
+ * hostid is the address of the host that the client is running on
+ * denyOpsResult is a pointer to return the capability vector
+ */
+int
+__pmAccAddClient(__pmSockAddr *hostid, unsigned int *denyOpsResult)
+{
+ int i;
+ int sts;
+ hostinfo *hp;
+ hostinfo *lastmatch = NULL;
+ int clientIx;
+ __pmSockAddr **clientIds;
+ __pmSockAddr *clientId;
+ __pmSockAddr *matchId;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+
+ *denyOpsResult = 0; /* deny nothing == allow all */
+ if (nhosts == 0) /* No access controls => allow all */
+ return 0;
+
+ /* There could be more than one address associated with this host.*/
+ clientIds = getClientIds(hostid, &sts);
+ if (clientIds == NULL)
+ return sts;
+
+ /* Accumulate permissions for each client address. */
+ for (clientIx = 0; clientIds[clientIx] != NULL; ++clientIx) {
+ clientId = clientIds[clientIx];
+ for (i = nhosts - 1; i >= 0; i--) {
+ hp = &hostlist[i];
+ /* At a minumum, the addresses must be from the same family. */
+ if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) {
+ matchId = __pmSockAddrDup(clientId);
+ __pmSockAddrMask(matchId, hp->hostmask);
+ if (__pmSockAddrCompare(matchId, hp->hostid) == 0) {
+ /* Clobber specified ops then set. Leave unspecified ops alone. */
+ *denyOpsResult &= ~hp->specOps;
+ *denyOpsResult |= hp->denyOps;
+ lastmatch = hp;
+ }
+ __pmSockAddrFree(matchId);
+ }
+ }
+ /* no matching entry in hostlist => allow all */
+
+ /* If no operations are allowed, disallow connection */
+ if (*denyOpsResult == all_ops) {
+ freeClientIds(clientIds);
+ return PM_ERR_PERMISSION;
+ }
+
+ /* Check for connection limit */
+ if (lastmatch != NULL && lastmatch->maxcons &&
+ lastmatch->curcons >= lastmatch->maxcons) {
+
+ *denyOpsResult = all_ops;
+ freeClientIds(clientIds);
+ return PM_ERR_CONNLIMIT;
+ }
+
+ /* Increment the count of current connections for ALL host specs in the
+ * host access list that match the client's IP address. A client may
+ * contribute to several connection counts because of wildcarding.
+ */
+ for (i = 0; i < nhosts; i++) {
+ hp = &hostlist[i];
+ /* At a minumum, the addresses must be from the same family. */
+ if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) {
+ matchId = __pmSockAddrDup(clientId);
+ __pmSockAddrMask(matchId, hp->hostmask);
+ if (__pmSockAddrCompare(matchId, hp->hostid) == 0) {
+ if (hp->maxcons)
+ hp->curcons++;
+ }
+ __pmSockAddrFree(matchId);
+ }
+ }
+ }
+
+ freeClientIds(clientIds);
+ return 0;
+}
+
+void
+__pmAccDelClient(__pmSockAddr *hostid)
+{
+ int i;
+ int sts;
+ hostinfo *hp;
+ int clientIx;
+ __pmSockAddr **clientIds;
+ __pmSockAddr *clientId;
+ __pmSockAddr *matchId;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+
+ /* There could be more than one address associated with this host.*/
+ clientIds = getClientIds(hostid, &sts);
+ if (clientIds == NULL)
+ return;
+
+ /* Decrement the count of current connections for ALL host specs in the
+ * host access list that match the client's IP addresses. A client may
+ * contribute to several connection counts because of wildcarding.
+ */
+ for (clientIx = 0; clientIds[clientIx] != NULL; ++clientIx) {
+ clientId = clientIds[clientIx];
+ for (i = 0; i < nhosts; i++) {
+ hp = &hostlist[i];
+ /* At a minumum, the addresses must be from the same family. */
+ if (__pmSockAddrGetFamily(clientId) == __pmSockAddrGetFamily(hp->hostmask)) {
+ matchId = __pmSockAddrDup(clientId);
+ __pmSockAddrMask(matchId, hp->hostmask);
+ if (__pmSockAddrCompare(matchId, hp->hostid) == 0) {
+ if (hp->maxcons)
+ hp->curcons--;
+ }
+ __pmSockAddrFree(matchId);
+ }
+ }
+ }
+ freeClientIds(clientIds);
+}
+
+static int
+findGidInUsersGroups(const userinfo *up, __pmGroupID gid)
+{
+ int i;
+
+ for (i = 0; i < up->ngroups; i++)
+ if (__pmEqualGroupIDs(up->groupids[i], gid))
+ return 1;
+ return 0;
+}
+
+static int
+accessCheckUsers(__pmUserID uid, __pmGroupID gid, unsigned int *denyOpsResult)
+{
+ userinfo *up;
+ int matched = 0;
+ int i;
+
+ for (i = 1; i < nusers; i++) {
+ up = &userlist[i];
+ if ((__pmValidUserID(uid) && __pmEqualUserIDs(up->userid, uid))
+ || (__pmValidGroupID(gid) && findGidInUsersGroups(up, gid))) {
+ if (up->maxcons && up->curcons >= up->maxcons) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_CONNLIMIT;
+ }
+ *denyOpsResult |= up->denyOps;
+ matched = 1;
+ }
+ }
+
+ if (nusers && !matched) {
+ up = &userlist[0]; /* global wildcard */
+ if (up->maxcons && up->curcons >= up->maxcons) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_CONNLIMIT;
+ }
+ *denyOpsResult |= up->denyOps;
+ }
+
+ return 0;
+}
+
+static int
+findUidInGroupsUsers(const groupinfo *gp, __pmUserID uid)
+{
+ int i;
+
+ for (i = 0; i < gp->nusers; i++)
+ if (__pmEqualUserIDs(gp->userids[i], uid))
+ return 1;
+ return 0;
+}
+
+static int
+accessCheckGroups(__pmUserID uid, __pmGroupID gid, unsigned int *denyOpsResult)
+{
+ groupinfo *gp;
+ int matched = 0;
+ int i;
+
+ for (i = 1; i < ngroups; i++) {
+ gp = &grouplist[i];
+ if ((__pmValidGroupID(gid) && __pmEqualGroupIDs(gp->groupid, gid))
+ || (__pmValidUserID(uid) && findUidInGroupsUsers(gp, uid))) {
+ if (gp->maxcons && gp->curcons >= gp->maxcons) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_CONNLIMIT;
+ }
+ *denyOpsResult |= gp->denyOps;
+ matched = 1;
+ }
+ }
+
+ if (ngroups && !matched) {
+ gp = &grouplist[0]; /* global wildcard */
+ if (gp->maxcons && gp->curcons >= gp->maxcons) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_CONNLIMIT;
+ }
+ *denyOpsResult |= gp->denyOps;
+ }
+
+ return 0;
+}
+
+static void updateGroupAccountConnections(__pmGroupID, int, int);
+
+static void
+updateUserAccountConnections(__pmUserID uid, int descend, int direction)
+{
+ int i, j;
+ userinfo *up;
+
+ for (i = 1; i < nusers; i++) {
+ up = &userlist[i];
+ if (!__pmEqualUserIDs(up->userid, uid))
+ continue;
+ if (up->maxcons)
+ up->curcons += direction; /* might be negative */
+ assert(up->curcons >= 0);
+ if (!descend)
+ continue;
+ for (j = 0; j < up->ngroups; j++)
+ updateGroupAccountConnections(up->groupids[j], 0, direction);
+ }
+}
+
+static void
+updateGroupAccountConnections(__pmGroupID gid, int descend, int direction)
+{
+ int i, j;
+ groupinfo *gp;
+
+ for (i = 1; i < ngroups; i++) {
+ gp = &grouplist[i];
+ if (!__pmEqualGroupIDs(gp->groupid, gid))
+ continue;
+ if (gp->maxcons)
+ gp->curcons += direction; /* might be negative */
+ assert(gp->curcons >= 0);
+ if (!descend)
+ continue;
+ for (j = 0; j < gp->nusers; j++)
+ updateUserAccountConnections(gp->userids[j], 0, direction);
+ }
+}
+
+/* Called after authenticating a new connection to check that another
+ * connection from this account is permitted and to find which operations
+ * the account is permitted to perform.
+ * uid and gid identify the account, if not authenticated these will be
+ * negative. denyOpsResult is a pointer to return the capability vector
+ * and note that it is both input (host access) and output (merged host
+ * and account access). So, do not blindly zero or overwrite existing.
+ */
+int
+__pmAccAddAccount(const char *userid, const char *groupid, unsigned int *denyOpsResult)
+{
+ int sts;
+ __pmUserID uid;
+ __pmGroupID gid;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return PM_ERR_THREAD;
+
+ if (nusers == 0 && ngroups == 0) /* No access controls => allow all */
+ return (userid || groupid); /* Inform caller of credentials */
+
+ /* Access controls present, but no authentication information - denied */
+ if (!userid || !groupid) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_PERMISSION;
+ }
+
+ __pmUserIDFromString(userid, &uid);
+ __pmGroupIDFromString(groupid, &gid);
+
+ /* Access controls present, but invalid user/group information - denied */
+ if (!__pmValidUserID(uid) && !__pmValidGroupID(gid)) {
+ *denyOpsResult = all_ops;
+ return PM_ERR_PERMISSION;
+ }
+
+ if ((sts = accessCheckUsers(uid, gid, denyOpsResult)) < 0)
+ return sts;
+ if ((sts = accessCheckGroups(uid, gid, denyOpsResult)) < 0)
+ return sts;
+
+ /* If no operations are allowed, disallow connection */
+ if (*denyOpsResult == all_ops)
+ return PM_ERR_PERMISSION;
+
+ /* Increment the count of current connections for this user and group
+ * in the user and groups access lists. Must walk the supplementary
+ * ID lists as well as the primary ID ACLs.
+ */
+ updateUserAccountConnections(uid, 1, +1);
+ updateGroupAccountConnections(gid, 1, +1);
+
+ /* Return code indicates access controls OK and have credentials */
+ return 1;
+}
+
+void
+__pmAccDelAccount(const char *userid, const char *groupid)
+{
+ __pmUserID uid;
+ __pmGroupID gid;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+
+ __pmUserIDFromString(userid, &uid);
+ __pmGroupIDFromString(groupid, &gid);
+
+ /* Decrement the count of current connections for this user and group
+ * in the user and groups access lists. Must walk the supplementary
+ * ID lists as well as the primary ID ACLs.
+ */
+ updateUserAccountConnections(uid, 1, -1);
+ updateGroupAccountConnections(gid, 1, -1);
+}
+
+static void
+getAccMinMaxBits(int *minbitp, int *maxbitp)
+{
+ unsigned int mask = all_ops;
+ int i, minbit = -1;
+
+ for (i = 0; mask; i++) {
+ if (mask % 2)
+ if (minbit < 0)
+ minbit = i;
+ mask = mask >> 1;
+ }
+
+ *minbitp = minbit;
+ *maxbitp = i - 1;
+}
+
+#define NAME_WIDTH 39 /* sufficient for a full IPv6 address */
+#define ID_WIDTH 7 /* sufficient for large group/user ID */
+
+void
+__pmAccDumpHosts(FILE *stream)
+{
+ int h, i;
+ int minbit, maxbit;
+ char *addrid, *addrmask;
+ unsigned int mask;
+ hostinfo *hp;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+
+ if (nhosts == 0) {
+ fprintf(stream, "Host access list empty: host-based access control turned off\n");
+ return;
+ }
+
+ getAccMinMaxBits(&minbit, &maxbit);
+ fprintf(stream, "Host access list:\n");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fprintf(stream, "%02d ", i);
+ fprintf(stream, "Cur/MaxCons %-*s %-*s lvl host-name\n",
+ NAME_WIDTH, "host-spec", NAME_WIDTH, "host-mask");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fputs("== ", stream);
+ fprintf(stream, "=========== ");
+ for (i = 0; i < 2; i++) {
+ for (h = 0; h < NAME_WIDTH; h++)
+ fprintf(stream, "=");
+ fprintf(stream, " ");
+ }
+ fprintf(stream, "=== ==============\n");
+
+ for (h = 0; h < nhosts; h++) {
+ hp = &hostlist[h];
+
+ for (i = minbit; i <= maxbit; i++) {
+ mask = 1 << i;
+ if (all_ops & mask) {
+ if (hp->specOps & mask)
+ fputs((hp->denyOps & mask) ? " n " : " y ", stream);
+ else
+ fputs(" ", stream);
+ }
+ }
+ addrid = __pmSockAddrToString(hp->hostid);
+ addrmask = __pmSockAddrToString(hp->hostmask);
+ fprintf(stream, "%5d %5d %-*s %-*s %3d %s\n", hp->curcons, hp->maxcons,
+ NAME_WIDTH, addrid, NAME_WIDTH, addrmask,
+ hp->level, hp->hostspec);
+ free(addrmask);
+ free(addrid);
+ }
+}
+
+void
+__pmAccDumpUsers(FILE *stream)
+{
+ int u, i;
+ int minbit, maxbit;
+ char buf[128];
+ unsigned int mask;
+ userinfo *up;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+
+ if (nusers == 0) {
+ fprintf(stream, "User access list empty: user-based access control turned off\n");
+ return;
+ }
+
+ getAccMinMaxBits(&minbit, &maxbit);
+ fprintf(stream, "User access list:\n");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fprintf(stream, "%02d ", i);
+ fprintf(stream, "Cur/MaxCons %*s %-*s %s\n",
+ ID_WIDTH, "uid", NAME_WIDTH-ID_WIDTH-1, "user-name", "group-list");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fputs("== ", stream);
+ fprintf(stream, "=========== ");
+ for (i = 0; i < ID_WIDTH; i++) /* user-id */
+ fprintf(stream, "=");
+ fprintf(stream, " ");
+ for (i = 0; i < NAME_WIDTH-ID_WIDTH-1; i++) /* user-name */
+ fprintf(stream, "=");
+ fprintf(stream, " ");
+ for (i = 0; i < NAME_WIDTH + 19; i++) /* group-list */
+ fprintf(stream, "=");
+ fprintf(stream, "\n");
+
+ for (u = nusers-1; u >= 0; u--) {
+ up = &userlist[u];
+
+ for (i = minbit; i <= maxbit; i++) {
+ mask = 1 << i;
+ if (all_ops & mask) {
+ if (up->specOps & mask)
+ fputs((up->denyOps & mask) ? " n " : " y ", stream);
+ else
+ fputs(" ", stream);
+ }
+ }
+ fprintf(stream, "%5d %5d %*s %-*s", up->curcons, up->maxcons,
+ ID_WIDTH, u == 0 ? "*" :
+ __pmUserIDToString(up->userid, buf, sizeof(buf)),
+ NAME_WIDTH-ID_WIDTH-1, up->username);
+ for (i = 0; i < up->ngroups; i++)
+ fprintf(stream, "%c%u(%s)", i ? ',' : ' ', up->groupids[i],
+ __pmGroupnameFromID(up->groupids[i], buf, sizeof(buf)));
+ fprintf(stream, "\n");
+ }
+}
+
+void
+__pmAccDumpGroups(FILE *stream)
+{
+ int g, i;
+ int minbit, maxbit;
+ char buf[128];
+ unsigned int mask;
+ groupinfo *gp;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_ACL))
+ return;
+
+ if (ngroups == 0) {
+ fprintf(stream, "Group access list empty: group-based access control turned off\n");
+ return;
+ }
+
+ getAccMinMaxBits(&minbit, &maxbit);
+ fprintf(stream, "Group access list:\n");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fprintf(stream, "%02d ", i);
+ fprintf(stream, "Cur/MaxCons %*s %-*s %s\n",
+ ID_WIDTH, "gid", NAME_WIDTH-ID_WIDTH-1, "group-name", "user-list");
+
+ for (i = minbit; i <= maxbit; i++)
+ if (all_ops & (1 << i))
+ fputs("== ", stream);
+ fprintf(stream, "=========== ");
+ for (i = 0; i < ID_WIDTH; i++) /* group-id */
+ fprintf(stream, "=");
+ fprintf(stream, " ");
+ for (i = 0; i < NAME_WIDTH-ID_WIDTH-1; i++) /* group-name */
+ fprintf(stream, "=");
+ fprintf(stream, " ");
+ for (i = 0; i < NAME_WIDTH + 19; i++) /* user-list */
+ fprintf(stream, "=");
+ fprintf(stream, "\n");
+
+ for (g = ngroups-1; g >= 0; g--) {
+ gp = &grouplist[g];
+
+ for (i = minbit; i <= maxbit; i++) {
+ mask = 1 << i;
+ if (all_ops & mask) {
+ if (gp->specOps & mask)
+ fputs((gp->denyOps & mask) ? " n " : " y ", stream);
+ else
+ fputs(" ", stream);
+ }
+ }
+ snprintf(buf, sizeof(buf), g ? "%6d" : " *", gp->groupid);
+ fprintf(stream, "%5d %5d %*s %-*s", gp->curcons, gp->maxcons,
+ ID_WIDTH, g == 0 ? "*" :
+ __pmGroupIDToString(gp->groupid, buf, sizeof(buf)),
+ NAME_WIDTH-ID_WIDTH-1, gp->groupname);
+ for (i = 0; i < gp->nusers; i++)
+ fprintf(stream, "%c%u(%s)", i ? ',' : ' ', gp->userids[i],
+ __pmUsernameFromID(gp->userids[i], buf, sizeof(buf)));
+ fprintf(stream, "\n");
+ }
+}
+
+void
+__pmAccDumpLists(FILE *stream)
+{
+ putc('\n', stream);
+ __pmAccDumpHosts(stream);
+ __pmAccDumpUsers(stream);
+ __pmAccDumpGroups(stream);
+ putc('\n', stream);
+}
diff --git a/src/libpcp/src/accounts.c b/src/libpcp/src/accounts.c
new file mode 100644
index 0000000..99436a5
--- /dev/null
+++ b/src/libpcp/src/accounts.c
@@ -0,0 +1,586 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <limits.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#if defined(HAVE_PWD_H)
+#include <pwd.h>
+#endif
+#if defined(HAVE_GRP_H)
+#include <grp.h>
+#endif
+
+int
+__pmEqualUserIDs(__pmUserID uid1, __pmUserID uid2)
+{
+ return uid1 == uid2;
+}
+
+int
+__pmEqualGroupIDs(__pmGroupID gid1, __pmGroupID gid2)
+{
+ return gid1 == gid2;
+}
+
+int
+__pmValidUserID(__pmUserID uid)
+{
+ return uid >= 0;
+}
+
+int __pmValidGroupID(__pmGroupID gid)
+{
+ return gid >= 0;
+}
+
+void
+__pmUserIDFromString(const char *userid, __pmUserID *uid)
+{
+ *uid = atoi(userid);
+}
+
+void
+__pmGroupIDFromString(const char *groupid, __pmGroupID *gid)
+{
+ *gid = atoi(groupid);
+}
+
+char *
+__pmUserIDToString(__pmUserID uid, char *buf, size_t size)
+{
+ snprintf(buf, size, "%u", (unsigned int)uid);
+ buf[size-1] = '\0';
+ return buf;
+}
+
+char *
+__pmGroupIDToString(__pmGroupID gid, char *buf, size_t size)
+{
+ snprintf(buf, size, "%u", (unsigned int)gid);
+ buf[size-1] = '\0';
+ return buf;
+}
+
+#if defined(HAVE_GETGRGID_R)
+char *
+__pmGroupnameFromID(gid_t gid, char *buf, size_t size)
+{
+ char namebuf[1024];
+ struct group grp, *result;
+
+ getgrgid_r(gid, &grp, namebuf, sizeof(namebuf), &result);
+ snprintf(buf, size, "%s", result ? result->gr_name : "unknown");
+ buf[size-1] = '\0';
+ return buf;
+}
+#elif defined(HAVE_GETGRGID)
+char *
+__pmGroupnameFromID(gid_t gid, char *buf, size_t size)
+{
+ struct group *result;
+
+ result = getgrgid(gid);
+ snprintf(buf, size, "%s", result ? result->gr_name : "unknown");
+ buf[size-1] = '\0';
+ return buf;
+}
+#else
+!bozo!
+#endif
+
+#if defined(HAVE_GETPWUID_R)
+char *
+__pmUsernameFromID(uid_t uid, char *buf, size_t size)
+{
+ char namebuf[1024];
+ struct passwd pwd, *result;
+
+ getpwuid_r(uid, &pwd, namebuf, sizeof(namebuf), &result);
+ snprintf(buf, size, "%s", result ? result->pw_name : "unknown");
+ buf[size-1] = '\0';
+ return buf;
+}
+#elif defined(HAVE_GETPWUID)
+char *
+__pmUsernameFromID(uid_t uid, char *buf, size_t size)
+{
+ struct passwd *result;
+
+ result = getpwuid(uid);
+ snprintf(buf, size, "%s", result ? result->pw_name : "unknown");
+ buf[size-1] = '\0';
+ return buf;
+}
+#else
+!bozo!
+#endif
+
+#if defined(HAVE_GETPWNAM_R)
+int
+__pmUsernameToID(const char *name, uid_t *uid)
+{
+ char namebuf[1024];
+ struct passwd pwd, *result = NULL;
+
+ getpwnam_r(name, &pwd, namebuf, sizeof(namebuf), &result);
+ if (!result)
+ return -ENOENT;
+ *uid = result->pw_uid;
+ return 0;
+}
+#elif defined(HAVE_GETPWNAM)
+int
+__pmUsernameToID(const char *name, uid_t *uid)
+{
+ struct passwd *result;
+
+ result = getpwnam(name);
+ if (!result)
+ return -ENOENT;
+ *uid = result->pw_uid;
+ return 0;
+}
+#else
+!bozo!
+#endif
+
+#if defined(HAVE_GETGRNAM_R)
+int
+__pmGroupnameToID(const char *name, gid_t *gid)
+{
+ char namebuf[512];
+ struct group grp, *result = NULL;
+
+ getgrnam_r(name, &grp, namebuf, sizeof(namebuf), &result);
+ if (result == NULL)
+ return -ENOENT;
+ *gid = result->gr_gid;
+ return 0;
+}
+#elif defined(HAVE_GETGRNAM)
+int
+__pmGroupnameToID(const char *name, gid_t *gid)
+{
+ struct group *result;
+
+ result = getgrnam(name);
+ if (result == NULL)
+ return -ENOENT;
+ *gid = result->gr_gid;
+ return 0;
+}
+#else
+!bozo!
+#endif
+
+#if defined(HAVE_GETPWUID_R)
+char *
+__pmHomedirFromID(uid_t uid, char *buf, size_t size)
+{
+ char namebuf[1024];
+ struct passwd pwd, *result;
+ char *env;
+
+ /*
+ * Use $HOME, if it is set, otherwise get the information from
+ * getpwuid_r()
+ */
+ env = getenv("HOME");
+ if (env != NULL)
+ snprintf(buf, size, "%s", env);
+ else {
+ getpwuid_r(uid, &pwd, namebuf, sizeof(namebuf), &result);
+ if (result == NULL)
+ return NULL;
+ snprintf(buf, size, "%s", result->pw_dir);
+ }
+ buf[size-1] = '\0';
+ return buf;
+}
+#elif defined(HAVE_GETPWUID)
+char *
+__pmHomedirFromID(uid_t uid, char *buf, size_t size)
+{
+ struct passwd *result;
+
+ /*
+ * Use $HOME, if it is set, otherwise get the information from
+ * getpwuid()
+ */
+ env = getenv("HOME");
+ if (env != NULL)
+ snprintf(buf, size, "%s", env);
+ else {
+ result = getpwuid(uid);
+ if (result == NULL)
+ return NULL;
+ snprintf(buf, size, "%s", result->pw_dir);
+ }
+ buf[size-1] = '\0';
+ return buf;
+}
+#else
+!bozo!
+#endif
+
+/*
+ * Add a group ID into a group list, if it is not there already.
+ * The current group ID list and size are passed in, updated if
+ * changed, and passed back out.
+ */
+static int
+__pmAddGroupID(gid_t gid, gid_t **gidlist, unsigned int *count)
+{
+ gid_t *gids = *gidlist;
+ size_t need;
+ unsigned int i, total = *count;
+
+ for (i = 0; i < total; i++)
+ if (gids[i] == gid)
+ return 0; /* already in the list, we're done */
+
+ need = (total + 1) * sizeof(gid_t);
+ if ((gids = (gid_t *)realloc(gids, need)) == NULL)
+ return -ENOMEM;
+ gids[total++] = gid;
+ *gidlist = gids;
+ *count = total;
+ return 0;
+}
+
+#if defined(HAVE_GETPWNAM_R) && defined(HAVE_GETGRENT_R)
+int
+__pmUsersGroupIDs(const char *username, gid_t **groupids, unsigned int *ngroups)
+{
+ int i, sts;
+ unsigned int count = 0;
+ char grbuf[1024];
+ gid_t *gidlist = NULL;
+ struct passwd pwd, *result = NULL;
+ struct group gr, *grp;
+
+ getpwnam_r(username, &pwd, grbuf, sizeof(grbuf), &result);
+ if (!result)
+ return -ENOENT;
+
+ /* add the primary group in right away, before supplementary groups */
+ if ((sts = __pmAddGroupID(result->pw_gid, &gidlist, &count)) < 0)
+ return sts;
+
+ /* search for groups in which the given user is a member */
+ setgrent();
+ while (1) {
+ grp = NULL;
+#ifdef IS_SOLARIS
+ if ((grp = getgrent_r(&gr, grbuf, sizeof(grbuf))) == NULL)
+ break;
+#else
+ if (getgrent_r(&gr, grbuf, sizeof(grbuf), &grp) != 0 || grp == NULL)
+ break;
+#endif
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (strcmp(username, grp->gr_mem[i]) != 0)
+ continue;
+ if ((sts = __pmAddGroupID(grp->gr_gid, &gidlist, &count)) < 0) {
+ endgrent();
+ return sts;
+ }
+ break;
+ }
+ }
+ endgrent();
+
+ *groupids = gidlist;
+ *ngroups = count;
+ return 0;
+}
+#elif defined(HAVE_GETPWNAM) && defined(HAVE_GETGRENT)
+int
+__pmUsersGroupIDs(const char *username, gid_t **groupids, unsigned int *ngroups)
+{
+ int i, sts;
+ unsigned int count = 0;
+ gid_t *gidlist = NULL;
+ struct passwd *result;
+ struct group *grp;
+
+ result = getpwnam(username);
+ if (!result)
+ return -ENOENT;
+
+ /* add the primary group in right away, before supplementary groups */
+ if ((sts = __pmAddGroupID(result->pw_gid, &gidlist, &count)) < 0)
+ return sts;
+
+ /* search for groups in which the given user is a member */
+ setgrent();
+ while (1) {
+ grp = NULL;
+ if ((grp = getgrent()) == NULL)
+ break;
+ for (i = 0; grp->gr_mem[i]; i++) {
+ if (strcmp(username, grp->gr_mem[i]) != 0)
+ continue;
+ if ((sts = __pmAddGroupID(grp->gr_gid, &gidlist, &count)) < 0) {
+ endgrent();
+ return sts;
+ }
+ break;
+ }
+ }
+ endgrent();
+
+ *groupids = gidlist;
+ *ngroups = count;
+ return 0;
+}
+#else
+!bozo!
+#endif
+
+/*
+ * Add a user ID into a user list, if it is not there already.
+ * The current user ID list and size are passed in, updated if
+ * changed, and passed back out.
+ */
+static int
+__pmAddUserID(uid_t uid, uid_t **uidlist, unsigned int *count)
+{
+ uid_t *uids = *uidlist;
+ size_t need;
+ unsigned int i, total = *count;
+
+ for (i = 0; i < total; i++)
+ if (uids[i] == uid)
+ return 0; /* already in the list, we're done */
+
+ need = (total + 1) * sizeof(uid_t);
+ if ((uids = (uid_t *)realloc(uids, need)) == NULL)
+ return -ENOMEM;
+ uids[total++] = uid;
+ *uidlist = uids;
+ *count = total;
+ return 0;
+}
+
+#if defined(HAVE_GETGRNAM_R) && defined(HAVE_GETPWENT_R)
+int
+__pmGroupsUserIDs(const char *groupname, uid_t **userids, unsigned int *nusers)
+{
+ int sts;
+ uid_t *uidlist = NULL;
+ gid_t groupid;
+ char grbuf[1024];
+ char buf[512];
+ char **names = NULL;
+ struct group gr, *grp = NULL;
+ struct passwd pw, *pwp;
+ unsigned int i, count = 0;
+
+ /* for a given group name, find gid and user names */
+ getgrnam_r(groupname, &gr, grbuf, sizeof(grbuf), &grp);
+ if (grp == NULL)
+ return -EINVAL;
+ groupid = grp->gr_gid;
+ names = grp->gr_mem; /* supplementaries */
+
+ /* for a given list of usernames, lookup the user IDs */
+ setpwent();
+ while (1) {
+#ifdef IS_SOLARIS
+ if ((pwp = getpwent_r(&pw, buf, sizeof(buf))) == NULL)
+ break;
+#else
+ pwp = NULL;
+ if (getpwent_r(&pw, buf, sizeof(buf), &pwp) != 0 || pwp == NULL)
+ break;
+#endif
+ /* check to see if this user has given group as primary */
+ if (pwp->pw_gid == groupid &&
+ (sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) {
+ endpwent();
+ return sts;
+ }
+ /* check to see if this user is listed in groups file */
+ for (i = 0; names[i]; i++) {
+ if (strcmp(pwp->pw_name, names[i]) == 0) {
+ if ((sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) {
+ endpwent();
+ return sts;
+ }
+ break;
+ }
+ }
+ }
+ endpwent();
+
+ *userids = uidlist;
+ *nusers = count;
+ return 0;
+}
+#elif defined(HAVE_GETGRNAM) && defined(HAVE_GETPWENT)
+int
+__pmGroupsUserIDs(const char *name, uid_t **userids, unsigned int *nusers)
+{
+ int sts;
+ uid_t *uidlist = NULL;
+ gid_t groupid;
+ char **names = NULL;
+ struct group *grp = NULL;
+ struct passwd *pwp;
+ unsigned int i, count = 0;
+
+ /* for a given group name, find gid and user names */
+ if ((grp = getgrnam(name)) == NULL)
+ return -EINVAL;
+ groupid = grp->gr_gid;
+ names = grp->gr_mem;
+
+ setpwent();
+ while (1) {
+ if ((pwp = getpwent()) == NULL)
+ break;
+ /* check to see if this user has given group as primary */
+ if (pwp->pw_gid == groupid &&
+ (sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) {
+ endpwent();
+ return sts;
+ }
+ for (i = 0; names[i]; i++) {
+ if (strcmp(pwp->pw_name, names[i]) == 0) {
+ if ((sts = __pmAddUserID(pwp->pw_uid, &uidlist, &count)) < 0) {
+ endpwent();
+ return sts;
+ }
+ break;
+ }
+ }
+ }
+ endpwent();
+
+ *userids = uidlist;
+ *nusers = count;
+ return 0;
+}
+#else
+!bozo!
+#endif
+
+#if defined(HAVE_GETPWNAM_R)
+int
+__pmGetUserIdentity(const char *username, uid_t *uid, gid_t *gid, int mode)
+{
+ int sts;
+ char buf[4096];
+ struct passwd pwd, *pw;
+
+ sts = getpwnam_r(username, &pwd, buf, sizeof(buf), &pw);
+ if (pw == NULL) {
+ __pmNotifyErr(LOG_CRIT,
+ "cannot find the %s user to switch to\n", username);
+ if (mode == PM_FATAL_ERR)
+ exit(1);
+ return -ENOENT;
+ } else if (sts != 0) {
+ __pmNotifyErr(LOG_CRIT, "getpwnam_r(%s) failed: %s\n",
+ username, pmErrStr_r(sts, buf, sizeof(buf)));
+ if (mode == PM_FATAL_ERR)
+ exit(1);
+ return -ENOENT;
+ }
+ *uid = pwd.pw_uid;
+ *gid = pwd.pw_gid;
+ return 0;
+}
+#elif defined(HAVE_GETPWNAM)
+int
+__pmGetUserIdentity(const char *username, uid_t *uid, gid_t *gid, int mode)
+{
+ int sts;
+ char errmsg[128];
+ struct passwd *pw;
+
+ setoserror(0);
+ if ((pw = getpwnam(username)) == 0) {
+ __pmNotifyErr(LOG_CRIT,
+ "cannot find the %s user to switch to\n", username);
+ if (mode == PM_FATAL_ERR)
+ exit(1);
+ return -ENOENT;
+ } else if (oserror() != 0) {
+ __pmNotifyErr(LOG_CRIT, "getpwnam(%s) failed: %s\n",
+ username, pmErrStr_r(oserror(), errmsg, sizeof(errmsg)));
+ if (mode == PM_FATAL_ERR)
+ exit(1);
+ return -ENOENT;
+ }
+ *uid = pw->pw_uid;
+ *gid = pw->pw_gid;
+ return 0;
+}
+#else
+!bozo!
+#endif
+
+int
+__pmSetProcessIdentity(const char *username)
+{
+ gid_t gid;
+ uid_t uid;
+ char msg[256];
+
+ __pmGetUserIdentity(username, &uid, &gid, PM_FATAL_ERR);
+
+ if (setgid(gid) < 0) {
+ __pmNotifyErr(LOG_CRIT,
+ "setgid to gid of %s user (gid=%d): %s",
+ username, gid, osstrerror_r(msg, sizeof(msg)));
+ exit(1);
+ }
+
+ /*
+ * We must allow initgroups to fail with EPERM, as this
+ * is the behaviour when the parent process has already
+ * dropped privileges (e.g. pmcd receives SIGHUP).
+ */
+ if (initgroups(username, gid) < 0 && oserror() != EPERM) {
+ __pmNotifyErr(LOG_CRIT,
+ "initgroups with gid of %s user (gid=%d): %s",
+ username, gid, osstrerror_r(msg, sizeof(msg)));
+ exit(1);
+ }
+
+ if (setuid(uid) < 0) {
+ __pmNotifyErr(LOG_CRIT,
+ "setuid to uid of %s user (uid=%d): %s",
+ username, uid, osstrerror_r(msg, sizeof(msg)));
+ exit(1);
+ }
+
+ return 0;
+}
+
+int
+__pmGetUsername(char **username)
+{
+ char *user = pmGetConfig("PCP_USER");
+ if (user && user[0] != '\0') {
+ *username = user;
+ return 1;
+ }
+ *username = "pcp";
+ return 0;
+}
diff --git a/src/libpcp/src/auxconnect.c b/src/libpcp/src/auxconnect.c
new file mode 100644
index 0000000..bf00ae3
--- /dev/null
+++ b/src/libpcp/src/auxconnect.c
@@ -0,0 +1,1389 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2000,2004,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <net/if.h>
+#define SOCKET_INTERNAL
+#include "internal.h"
+
+/* default connect timeout is 5 seconds */
+static struct timeval canwait = { 5, 000000 };
+
+__pmHostEnt *
+__pmHostEntAlloc(void)
+{
+ return calloc(1, sizeof(__pmHostEnt));
+}
+
+void
+__pmHostEntFree(__pmHostEnt *hostent)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s:__pmHostEntFree(hostent=%p) name=%p (%s) addresses=%p\n", __FILE__, hostent, hostent->name, hostent->name, hostent-> addresses);
+#endif
+ if (hostent->name != NULL)
+ free(hostent->name);
+ if (hostent->addresses != NULL)
+ freeaddrinfo(hostent->addresses);
+ free(hostent);
+}
+
+__pmSockAddr *
+__pmSockAddrAlloc(void)
+{
+ return calloc(1, sizeof(__pmSockAddr));
+}
+
+__pmSockAddr *
+__pmSockAddrDup(const __pmSockAddr *sockaddr)
+{
+ __pmSockAddr *new = malloc(sizeof(__pmSockAddr));
+ if (new)
+ *new = *sockaddr;
+ return new;
+}
+
+size_t
+__pmSockAddrSize(void)
+{
+ return sizeof(__pmSockAddr);
+}
+
+/* Initialize a socket address. The integral address must be INADDR_ANY or
+ INADDR_LOOPBACK in host byte order. */
+void
+__pmSockAddrInit(__pmSockAddr *addr, int family, int address, int port)
+{
+ memset(addr, 0, sizeof(*addr));
+ if (family == AF_INET) {
+ addr->sockaddr.inet.sin_family = family;
+ addr->sockaddr.inet.sin_addr.s_addr = htonl(address);
+ addr->sockaddr.inet.sin_port = htons(port);
+ }
+ else if (family == AF_INET6) {
+ addr->sockaddr.ipv6.sin6_family = family;
+ addr->sockaddr.ipv6.sin6_port = htons(port);
+ if (address == INADDR_LOOPBACK)
+ addr->sockaddr.ipv6.sin6_addr.s6_addr[15] = 1;
+ }
+ else
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrInit: Invalid address family: %d\n", __FILE__, addr->sockaddr.raw.sa_family);
+}
+
+void
+__pmSockAddrSetFamily(__pmSockAddr *addr, int family)
+{
+ addr->sockaddr.raw.sa_family = family;
+}
+
+int
+__pmSockAddrGetFamily(const __pmSockAddr *addr)
+{
+ return addr->sockaddr.raw.sa_family;
+}
+
+void
+__pmSockAddrSetPort(__pmSockAddr *addr, int port)
+{
+ if (addr->sockaddr.raw.sa_family == AF_INET)
+ addr->sockaddr.inet.sin_port = htons(port);
+ else if (addr->sockaddr.raw.sa_family == AF_INET6)
+ addr->sockaddr.ipv6.sin6_port = htons(port);
+ else
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrSetPort: Invalid address family: %d\n",
+ __FILE__, addr->sockaddr.raw.sa_family);
+}
+
+int
+__pmSockAddrGetPort(const __pmSockAddr *addr)
+{
+ if (addr->sockaddr.raw.sa_family == AF_INET)
+ return ntohs(addr->sockaddr.inet.sin_port);
+ if (addr->sockaddr.raw.sa_family == AF_INET6)
+ return ntohs(addr->sockaddr.ipv6.sin6_port);
+ __pmNotifyErr(LOG_ERR,
+ "__pmSockAddrGetPort: Invalid address family: %d\n",
+ addr->sockaddr.raw.sa_family);
+ return 0; /* not set */
+}
+
+int
+__pmSockAddrIsLoopBack(const __pmSockAddr *addr)
+{
+ int rc;
+ int family;
+ __pmSockAddr *loopBackAddr;
+
+ family = __pmSockAddrGetFamily(addr);
+ loopBackAddr = __pmLoopBackAddress(family);
+ if (loopBackAddr == NULL)
+ return 0;
+ rc = __pmSockAddrCompare(addr, loopBackAddr);
+ __pmSockAddrFree(loopBackAddr);
+ return rc == 0;
+}
+
+void
+__pmSockAddrSetScope(__pmSockAddr *addr, int scope)
+{
+ if (addr->sockaddr.raw.sa_family == AF_INET6)
+ addr->sockaddr.ipv6.sin6_scope_id = scope;
+}
+
+void
+__pmSockAddrSetPath(__pmSockAddr *addr, const char *path)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (addr->sockaddr.raw.sa_family == AF_UNIX)
+ strncpy(addr->sockaddr.local.sun_path, path, sizeof(addr->sockaddr.local.sun_path));
+ else
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrSetPath: Invalid address family: %d\n",
+ __FILE__, addr->sockaddr.raw.sa_family);
+#else
+ __pmNotifyErr(LOG_ERR, "%s:__pmSockAddrSetPath: AF_UNIX is not supported\n", __FILE__);
+#endif
+}
+
+__pmSockAddr *
+__pmSockAddrMask(__pmSockAddr *addr, const __pmSockAddr *mask)
+{
+ int i;
+ if (addr->sockaddr.raw.sa_family != mask->sockaddr.raw.sa_family) {
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrMask: Address family of the address (%d) must match that of the mask (%d)\n",
+ __FILE__, addr->sockaddr.raw.sa_family, mask->sockaddr.raw.sa_family);
+ }
+ else if (addr->sockaddr.raw.sa_family == AF_INET)
+ addr->sockaddr.inet.sin_addr.s_addr &= mask->sockaddr.inet.sin_addr.s_addr;
+ else if (addr->sockaddr.raw.sa_family == AF_INET6) {
+ /* IPv6: Mask it byte by byte */
+ unsigned char *addrBytes = addr->sockaddr.ipv6.sin6_addr.s6_addr;
+ const unsigned char *maskBytes = mask->sockaddr.ipv6.sin6_addr.s6_addr;
+ for (i = 0; i < sizeof(addr->sockaddr.ipv6.sin6_addr.s6_addr); ++i)
+ addrBytes[i] &= maskBytes[i];
+ }
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ else if (addr->sockaddr.raw.sa_family == AF_UNIX) {
+ /* Simply truncate the path in the address to the length of the mask. */
+ i = strlen(mask->sockaddr.local.sun_path);
+ addr->sockaddr.local.sun_path[i] = '\0';
+ }
+#endif
+ else /* not applicable to other address families. */
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrMask: Invalid address family: %d\n", __FILE__, addr->sockaddr.raw.sa_family);
+
+ return addr;
+}
+
+int
+__pmSockAddrCompare(const __pmSockAddr *addr1, const __pmSockAddr *addr2)
+{
+ if (addr1->sockaddr.raw.sa_family != addr2->sockaddr.raw.sa_family)
+ return addr1->sockaddr.raw.sa_family - addr2->sockaddr.raw.sa_family;
+
+ if (addr1->sockaddr.raw.sa_family == AF_INET)
+ return addr1->sockaddr.inet.sin_addr.s_addr - addr2->sockaddr.inet.sin_addr.s_addr;
+
+ if (addr1->sockaddr.raw.sa_family == AF_INET6) {
+ /* IPv6: Compare it byte by byte */
+ return memcmp(&addr1->sockaddr.ipv6.sin6_addr.s6_addr, &addr2->sockaddr.ipv6.sin6_addr.s6_addr,
+ sizeof(addr1->sockaddr.ipv6.sin6_addr.s6_addr));
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (addr1->sockaddr.raw.sa_family == AF_UNIX) {
+ /* Unix Domain: Compare the paths */
+ return strncmp(addr1->sockaddr.local.sun_path, addr2->sockaddr.local.sun_path,
+ sizeof(addr1->sockaddr.local.sun_path));
+ }
+#endif
+
+ /* Unknown address family. */
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrCompare: Invalid address family: %d\n", __FILE__, addr1->sockaddr.raw.sa_family);
+ return 1; /* not equal */
+}
+
+int
+__pmSockAddrIsInet(const __pmSockAddr *addr)
+{
+ return addr->sockaddr.raw.sa_family == AF_INET;
+}
+
+int
+__pmSockAddrIsIPv6(const __pmSockAddr *addr)
+{
+ return addr->sockaddr.raw.sa_family == AF_INET6;
+}
+
+int
+__pmSockAddrIsUnix(const __pmSockAddr *addr)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ return addr->sockaddr.raw.sa_family == AF_UNIX;
+#else
+ return 0;
+#endif
+}
+
+__pmSockAddr *
+__pmStringToSockAddr(const char *cp)
+{
+ __pmSockAddr *addr = __pmSockAddrAlloc();
+ if (addr) {
+ if (cp == NULL || strcmp(cp, "INADDR_ANY") == 0) {
+ addr->sockaddr.inet.sin_addr.s_addr = INADDR_ANY;
+ /* Set the address family to 0, meaning "not set". */
+ addr->sockaddr.raw.sa_family = 0;
+ }
+ else {
+ int sts;
+ /* Determine the address family. */
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (*cp == __pmPathSeparator()) {
+ if (strlen(cp) >= sizeof(addr->sockaddr.local.sun_path))
+ sts = -1; /* too long */
+ else {
+ addr->sockaddr.raw.sa_family = AF_UNIX;
+ strcpy(addr->sockaddr.local.sun_path, cp);
+ sts = 1;
+ }
+ }
+ else
+#endif
+ if (strchr(cp, ':') != NULL) {
+ char *cp1;
+ char *scope;
+ /*
+ * inet_pton(3) does not support the "%<interface>" extension for specifying the
+ * scope of a link-local address. If one is present, then strip it out and
+ * set the scope_id manually.
+ */
+ if ((scope = strchr(cp, '%')) != NULL) {
+ size_t size = scope - cp;
+ ++scope; /* get past the '%' */
+ if ((cp1 = malloc(size + 1)) == NULL)
+ sts = -1;
+ else {
+ strncpy(cp1, cp, size);
+ cp1[size] = '\0';
+ }
+ cp = cp1;
+ }
+ if (cp != NULL) {
+ addr->sockaddr.raw.sa_family = AF_INET6;
+ sts = inet_pton(AF_INET6, cp, &addr->sockaddr.ipv6.sin6_addr);
+ if (scope != NULL) {
+ free(cp1);
+ /* Manually set the scope_id */
+ if ((addr->sockaddr.ipv6.sin6_scope_id = if_nametoindex(scope)) == 0)
+ sts = -1;
+ }
+ }
+ }
+ else {
+ addr->sockaddr.raw.sa_family = AF_INET;
+ sts = inet_pton(AF_INET, cp, &addr->sockaddr.inet.sin_addr);
+ }
+ if (sts <= 0) {
+ __pmSockAddrFree(addr);
+ addr = NULL;
+ }
+ }
+ }
+ return addr;
+}
+
+/*
+ * Convert an address to a string.
+ * The caller must free the buffer.
+ */
+char *
+__pmSockAddrToString(const __pmSockAddr *addr)
+{
+ char str[INET6_ADDRSTRLEN];
+ int family;
+ const char *sts;
+
+ sts = NULL;
+ family = addr->sockaddr.raw.sa_family;
+ if (family == AF_INET)
+ sts = inet_ntop(family, &addr->sockaddr.inet.sin_addr, str, sizeof(str));
+ else if (family == AF_INET6)
+ sts = inet_ntop(family, &addr->sockaddr.ipv6.sin6_addr, str, sizeof(str));
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ else if (family == AF_UNIX)
+ return strdup(addr->sockaddr.local.sun_path);
+#endif
+ if (sts == NULL)
+ return NULL;
+ return strdup(str);
+}
+
+__pmSockAddr *
+__pmSockAddrFirstSubnetAddr(const __pmSockAddr *netAddr, int maskBits)
+{
+ __pmSockAddr *addr;
+
+ /* Make a copy of the net address for iteration purposes. */
+ addr = __pmSockAddrDup(netAddr);
+ if (addr) {
+ /*
+ * Construct the first address in the subnet based on the given number
+ * of mask bits.
+ */
+ if (addr->sockaddr.raw.sa_family == AF_INET) {
+ /* An inet address. The ip address is in network byte order. */
+ unsigned ip = ntohl(addr->sockaddr.inet.sin_addr.s_addr);
+ ip = __pmFirstInetSubnetAddr (ip, maskBits);
+ addr->sockaddr.inet.sin_addr.s_addr = htonl(ip);
+ }
+ else if (addr->sockaddr.raw.sa_family == AF_INET6) {
+ __pmFirstIpv6SubnetAddr(addr->sockaddr.ipv6.sin6_addr.s6_addr, maskBits);
+ }
+ else {
+ /* not applicable to other address families, e.g. AF_LOCAL. */
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrFirstSubnetAddr: Unsupported address family: %d\n",
+ __FILE__, addr->sockaddr.raw.sa_family);
+ __pmSockAddrFree(addr);
+ return NULL;
+ }
+ }
+
+ return addr;
+}
+
+__pmSockAddr *
+__pmSockAddrNextSubnetAddr(__pmSockAddr *addr, int maskBits)
+{
+ if (addr) {
+ /*
+ * Construct the next address in the subnet based on the given the
+ * previous address and the given number of mask bits.
+ */
+ if (addr->sockaddr.raw.sa_family == AF_INET) {
+ /* An inet address. The ip address is in network byte order. */
+ unsigned ip = ntohl(addr->sockaddr.inet.sin_addr.s_addr);
+ unsigned newIp = __pmNextInetSubnetAddr(ip, maskBits);
+
+ /* Is this the final address? */
+ if (newIp == ip) {
+ __pmSockAddrFree(addr);
+ return NULL;
+ }
+ addr->sockaddr.inet.sin_addr.s_addr = htonl(newIp);
+ }
+ else if (addr->sockaddr.raw.sa_family == AF_INET6) {
+ unsigned char *newAddr =
+ __pmNextIpv6SubnetAddr(addr->sockaddr.ipv6.sin6_addr.s6_addr, maskBits);
+
+ if (newAddr == NULL) {
+ /* This is the final address. */
+ __pmSockAddrFree(addr);
+ return NULL;
+ }
+ }
+ else {
+ /* not applicable to other address families, e.g. AF_LOCAL. */
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmSockAddrFirstSubnetAddr: Unsupported address family: %d\n",
+ __FILE__, addr->sockaddr.raw.sa_family);
+ __pmSockAddrFree(addr);
+ return NULL;
+ }
+ }
+
+ return addr;
+}
+
+void
+__pmSockAddrFree(__pmSockAddr *sockaddr)
+{
+ free(sockaddr);
+}
+
+__pmSockAddr *
+__pmLoopBackAddress(int family)
+{
+ __pmSockAddr* addr;
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* There is no loopback address for a unix domain socket. */
+ if (family == AF_UNIX)
+ return NULL;
+#endif
+
+ addr = __pmSockAddrAlloc();
+ if (addr != NULL)
+ __pmSockAddrInit(addr, family, INADDR_LOOPBACK, 0);
+ return addr;
+}
+
+int
+__pmInitSocket(int fd, int family)
+{
+ int sts;
+ int nodelay = 1;
+ struct linger nolinger = {1, 0};
+ char errmsg[PM_MAXERRMSGLEN];
+
+ if ((sts = __pmSetSocketIPC(fd)) < 0) {
+ __pmCloseSocket(fd);
+ return sts;
+ }
+
+ /* Don't linger on close */
+ if (__pmSetSockOpt(fd, SOL_SOCKET, SO_LINGER, (char *)&nolinger,
+ (__pmSockLen)sizeof(nolinger)) < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "%s:__pmCreateSocket(%d): __pmSetSockOpt SO_LINGER: %s\n",
+ __FILE__, fd, netstrerror_r(errmsg, sizeof(errmsg)));
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (family == AF_UNIX)
+ return fd;
+#endif
+
+ /* Avoid 200 ms delay. This option is not supported for unix domain sockets. */
+ if (__pmSetSockOpt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&nodelay,
+ (__pmSockLen)sizeof(nodelay)) < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "%s:__pmCreateSocket(%d): __pmSetSockOpt TCP_NODELAY: %s\n",
+ __FILE__, fd, netstrerror_r(errmsg, sizeof(errmsg)));
+ }
+
+ return fd;
+}
+
+int
+__pmCreateSocket(void)
+{
+ int sts, fd;
+
+ if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+ return -neterror();
+ if ((sts = __pmInitSocket(fd, AF_INET)) < 0)
+ return sts;
+ return fd;
+}
+
+int
+__pmCreateIPv6Socket(void)
+{
+ int sts, fd, on;
+ __pmSockLen onlen = sizeof(on);
+
+ if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
+ return -neterror();
+
+ /*
+ * Disable IPv4-mapped connections
+ * Must explicitly check whether that worked, for ipv6.enabled=false
+ * kernels. Setting then testing is the most reliable way we've found.
+ */
+ on = 1;
+ __pmSetSockOpt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+ on = 0;
+ sts = __pmGetSockOpt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, &onlen);
+ if (sts < 0 || on != 1) {
+ __pmNotifyErr(LOG_ERR, "%s:__pmCreateIPv6Socket: IPV6 is not supported\n", __FILE__);
+ close(fd);
+ return -EOPNOTSUPP;
+ }
+
+ if ((sts = __pmInitSocket(fd, AF_INET6)) < 0)
+ return sts;
+ return fd;
+}
+
+int
+__pmCreateUnixSocket(void)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ int sts, fd;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -neterror();
+
+ if ((sts = __pmInitSocket(fd, AF_UNIX)) < 0)
+ return sts;
+
+ return fd;
+#else
+ __pmNotifyErr(LOG_ERR, "%s:__pmCreateUnixSocket: AF_UNIX is not supported\n", __FILE__);
+ return -EOPNOTSUPP;
+#endif
+}
+
+int
+__pmGetFileStatusFlags(int fd)
+{
+ return fcntl(fd, F_GETFL);
+}
+
+int
+__pmSetFileStatusFlags(int fd, int flags)
+{
+ return fcntl(fd, F_SETFL, flags);
+}
+
+int
+__pmGetFileDescriptorFlags(int fd)
+{
+ return fcntl(fd, F_GETFD);
+}
+
+int
+__pmSetFileDescriptorFlags(int fd, int flags)
+{
+ return fcntl(fd, F_SETFD, flags);
+}
+
+int
+__pmListen(int fd, int backlog)
+{
+ return listen(fd, backlog);
+}
+
+int
+__pmAccept(int fd, void *addr, __pmSockLen *addrlen)
+{
+ __pmSockAddr *sockAddr = (__pmSockAddr *)addr;
+ fd = accept(fd, &sockAddr->sockaddr.raw, addrlen);
+ __pmCheckAcceptedAddress(sockAddr);
+ return fd;
+}
+
+int
+__pmBind(int fd, void *addr, __pmSockLen addrlen)
+{
+ __pmSockAddr *sock = (__pmSockAddr *)addr;
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_CONTEXT) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "%s:__pmBind(fd=%d, family=%d, port=%d, addr=%s)\n",
+ __FILE__, fd, __pmSockAddrGetFamily(sock), __pmSockAddrGetPort(sock),
+ __pmSockAddrToString(sock));
+ }
+#endif
+ if (sock->sockaddr.raw.sa_family == AF_INET)
+ return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.inet));
+ if (sock->sockaddr.raw.sa_family == AF_INET6)
+ return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.ipv6));
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (sock->sockaddr.raw.sa_family == AF_UNIX)
+ return bind(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.local));
+#endif
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmBind: Invalid address family: %d\n", __FILE__, sock->sockaddr.raw.sa_family);
+ errno = EAFNOSUPPORT;
+ return -1; /* failure */
+}
+
+int
+__pmConnect(int fd, void *addr, __pmSockLen addrlen)
+{
+ __pmSockAddr *sock = (__pmSockAddr *)addr;
+ if (sock->sockaddr.raw.sa_family == AF_INET)
+ return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.inet));
+ if (sock->sockaddr.raw.sa_family == AF_INET6)
+ return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.ipv6));
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (sock->sockaddr.raw.sa_family == AF_UNIX)
+ return connect(fd, &sock->sockaddr.raw, sizeof(sock->sockaddr.local));
+#endif
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmConnect: Invalid address family: %d\n", __FILE__, sock->sockaddr.raw.sa_family);
+ errno = EAFNOSUPPORT;
+ return -1; /* failure */
+}
+
+int
+__pmConnectWithFNDELAY(int fd, void *addr, __pmSockLen addrlen)
+{
+ return __pmConnect(fd, addr, addrlen);
+}
+
+int
+__pmConnectTo(int fd, const __pmSockAddr *addr, int port)
+{
+ int sts, fdFlags = __pmGetFileStatusFlags(fd);
+ __pmSockAddr myAddr;
+
+ myAddr = *addr;
+ if (port >= 0)
+ __pmSockAddrSetPort(&myAddr, port);
+
+ if (__pmSetFileStatusFlags(fd, fdFlags | FNDELAY) < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+
+ __pmNotifyErr(LOG_ERR, "%s:__pmConnectTo: cannot set FNDELAY - "
+ "fcntl(%d,F_SETFL,0x%x) failed: %s\n",
+ __FILE__, fd, fdFlags|FNDELAY , osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+
+ if (__pmConnectWithFNDELAY(fd, &myAddr, sizeof(myAddr)) < 0) {
+ sts = neterror();
+ if (sts != EINPROGRESS) {
+ __pmCloseSocket(fd);
+ return -sts;
+ }
+ }
+
+ return fdFlags;
+}
+
+int
+__pmConnectCheckError(int fd)
+{
+ int so_err = 0;
+ __pmSockLen olen = sizeof(int);
+ char errmsg[PM_MAXERRMSGLEN];
+
+ if (__pmGetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void *)&so_err, &olen) < 0) {
+ so_err = neterror();
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmConnectCheckError: __pmGetSockOpt(SO_ERROR) failed: %s\n",
+ __FILE__, netstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ return so_err;
+}
+
+int
+__pmConnectRestoreFlags(int fd, int fdFlags)
+{
+ int sts;
+ char errmsg[PM_MAXERRMSGLEN];
+
+ if (__pmSetFileStatusFlags(fd, fdFlags) < 0) {
+ __pmNotifyErr(LOG_WARNING, "%s:__pmConnectRestoreFlags: cannot restore "
+ "flags fcntl(%d,F_SETFL,0x%x) failed: %s\n",
+ __FILE__, fd, fdFlags, osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+
+ if ((fdFlags = __pmGetFileDescriptorFlags(fd)) >= 0)
+ sts = __pmSetFileDescriptorFlags(fd, fdFlags | FD_CLOEXEC);
+ else
+ sts = fdFlags;
+
+ if (sts == -1) {
+ __pmNotifyErr(LOG_WARNING, "%s:__pmConnectRestoreFlags: "
+ "fcntl(%d) get/set flags failed: %s\n",
+ __FILE__, fd, osstrerror_r(errmsg, sizeof(errmsg)));
+ __pmCloseSocket(fd);
+ return sts;
+ }
+
+ return fd;
+}
+
+const struct timeval *
+__pmConnectTimeout(void)
+{
+ static int first_time = 1;
+
+ /*
+ * get optional stuff from environment ...
+ * PMCD_CONNECT_TIMEOUT
+ * PMCD_PORT
+ */
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (first_time) {
+ char *env_str;
+ first_time = 0;
+
+ if ((env_str = getenv("PMCD_CONNECT_TIMEOUT")) != NULL) {
+ char *end_ptr;
+ double timeout = strtod(env_str, &end_ptr);
+ if (*end_ptr != '\0' || timeout < 0.0)
+ __pmNotifyErr(LOG_WARNING, "%s:__pmAuxConnectPMCDPort: "
+ "ignored bad PMCD_CONNECT_TIMEOUT = '%s'\n",
+ __FILE__, env_str);
+ else {
+ canwait.tv_sec = (time_t)timeout;
+ canwait.tv_usec = (int)((timeout -
+ (double)canwait.tv_sec) * 1000000);
+ }
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return (&canwait);
+}
+
+int
+__pmFD(int fd)
+{
+ return fd;
+}
+
+void
+__pmFD_CLR(int fd, __pmFdSet *set)
+{
+ FD_CLR(fd, set);
+}
+
+int
+__pmFD_ISSET(int fd, __pmFdSet *set)
+{
+ return FD_ISSET(fd, set);
+}
+
+void
+__pmFD_SET(int fd, __pmFdSet *set)
+{
+ FD_SET(fd, set);
+}
+
+void
+__pmFD_ZERO(__pmFdSet *set)
+{
+ FD_ZERO(set);
+}
+
+void
+__pmFD_COPY(__pmFdSet *s1, const __pmFdSet *s2)
+{
+ memcpy(s1, s2, sizeof(*s1));
+}
+
+int
+__pmSelectRead(int nfds, __pmFdSet *readfds, struct timeval *timeout)
+{
+ return select(nfds, readfds, NULL, NULL, timeout);
+}
+
+int
+__pmSelectWrite(int nfds, __pmFdSet *writefds, struct timeval *timeout)
+{
+ return select(nfds, NULL, writefds, NULL, timeout);
+}
+
+/*
+ * This interface is old and mouldy (exposed via impl.h many years ago)
+ * and very much deprecated. It was replaced by __pmAuxConnectPMCDPort.
+ *
+ * The implementation here is retained for any (out-of-tree) application
+ * that might have called this interface directly ... the implementation
+ * is correct when $PMCD_PORT is unset, or set to a single numeric port
+ * number, i.e. the old semantics
+ */
+int
+__pmAuxConnectPMCD(const char *hostname)
+{
+ static int *pmcd_ports = NULL;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (pmcd_ports == NULL)
+ __pmPMCDAddPorts(&pmcd_ports, 0);
+ PM_UNLOCK(__pmLock_libpcp);
+
+ /* __pmPMCDAddPorts discovers at least one valid port, if it returns. */
+ return __pmAuxConnectPMCDPort(hostname, pmcd_ports[0]);
+}
+
+int
+__pmAuxConnectPMCDPort(const char *hostname, int pmcd_port)
+{
+ __pmSockAddr *myAddr;
+ __pmHostEnt *servInfo;
+ int fd = -1; /* Fd for socket connection to pmcd */
+ int sts;
+ int fdFlags = 0;
+ void *enumIx;
+
+ if ((servInfo = __pmGetAddrInfo(hostname)) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "%s:__pmAuxConnectPMCDPort(%s, %d) : hosterror=%d, ``%s''\n",
+ __FILE__, hostname, pmcd_port, hosterror(), hoststrerror());
+ }
+#endif
+ return -EHOSTUNREACH;
+ }
+
+ __pmConnectTimeout();
+
+ enumIx = NULL;
+ for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ myAddr != NULL;
+ myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myAddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myAddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmAuxConnectPMCDPort(%s, %d) : invalid address family %d\n",
+ __FILE__, hostname, pmcd_port, __pmSockAddrGetFamily(myAddr));
+ fd = -EINVAL;
+ }
+ if (fd < 0) {
+ __pmSockAddrFree(myAddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ fdFlags = __pmConnectTo(fd, myAddr, pmcd_port);
+ __pmSockAddrFree(myAddr);
+ if (fdFlags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address
+ */
+ fd = -ECONNREFUSED;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ struct timeval stv = canwait;
+ struct timeval *pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL;
+ __pmFdSet wfds;
+ int rc;
+
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(fd, &wfds);
+ sts = 0;
+ if ((rc = __pmSelectWrite(fd+1, &wfds, pstv)) == 1) {
+ sts = __pmConnectCheckError(fd);
+ }
+ else if (rc == 0) {
+ sts = ETIMEDOUT;
+ }
+ else {
+ sts = (rc < 0) ? neterror() : EINVAL;
+ }
+
+ /* Successful connection? */
+ if (sts == 0)
+ break;
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(fd);
+ fd = -sts;
+ }
+
+ __pmHostEntFree(servInfo);
+ if (fd < 0)
+ return fd;
+
+ /*
+ * If we're here, it means we have a valid connection; restore the
+ * flags and make sure this file descriptor is closed if exec() is
+ * called
+ */
+ return __pmConnectRestoreFlags(fd, fdFlags);
+}
+
+/*
+ * Return the path to the default PMCD local unix domain socket.
+ * Returns a pointer to a static buffer which can be used directly.
+ * Return the path regardless of whether unix domain sockets are
+ * supported by our build. Other functions can then print reasonable
+ * messages if an attempt is made to use one.
+ */
+const char *
+__pmPMCDLocalSocketDefault(void)
+{
+ static char pmcd_socket[MAXPATHLEN];
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (pmcd_socket[0] == '\0') {
+ char *envstr;
+ if ((envstr = getenv("PMCD_SOCKET")) != NULL)
+ snprintf(pmcd_socket, sizeof(pmcd_socket), "%s", envstr);
+ else
+ snprintf(pmcd_socket, sizeof(pmcd_socket), "%s%c" "pmcd.socket",
+ pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator());
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ return pmcd_socket;
+}
+
+int
+__pmAuxConnectPMCDUnixSocket(const char *sock_path)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ __pmSockAddr *myAddr;
+ int fd = -1; /* Fd for socket connection to pmcd */
+ int sts;
+ int fdFlags = 0;
+ struct timeval stv;
+ struct timeval *pstv;
+ __pmFdSet wfds;
+ int rc;
+
+ __pmConnectTimeout();
+
+ /* Initialize the socket address. */
+ myAddr = __pmSockAddrAlloc();
+ if (myAddr == NULL) {
+ __pmNotifyErr(LOG_ERR, "%s:__pmAuxConnectPMCDUnixSocket(%s): out of memory\n", __FILE__, sock_path);
+ return -1;
+ }
+ __pmSockAddrSetFamily(myAddr, AF_UNIX);
+ __pmSockAddrSetPath(myAddr, sock_path);
+
+ /* Create a socket */
+ fd = __pmCreateUnixSocket();
+ if (fd < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmAuxConnectPMCDUnixSocket(%s): unable to create socket: %s\n",
+ __FILE__, sock_path, osstrerror_r(errmsg, sizeof(errmsg)));
+ __pmSockAddrFree(myAddr);
+ return fd;
+ }
+
+ /* Attempt to connect */
+ fdFlags = __pmConnectTo(fd, myAddr, -1);
+ __pmSockAddrFree(myAddr);
+ if (fdFlags < 0)
+ return -ECONNREFUSED;
+
+ /* FNDELAY and we're in progress - wait on select */
+ stv = canwait;
+ pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(fd, &wfds);
+ sts = 0;
+ if ((rc = __pmSelectWrite(fd+1, &wfds, pstv)) == 1) {
+ sts = __pmConnectCheckError(fd);
+ }
+ else if (rc == 0) {
+ sts = ETIMEDOUT;
+ }
+ else {
+ sts = (rc < 0) ? neterror() : EINVAL;
+ }
+
+ if (sts != 0) {
+ /* Unsuccessful connection. */
+ if (sts == ENOENT)
+ sts = ECONNREFUSED;
+ __pmCloseSocket(fd);
+ fd = -sts;
+ }
+
+ if (fd < 0)
+ return fd;
+
+ /*
+ * If we're here, it means we have a valid connection; restore the
+ * flags and make sure this file descriptor is closed if exec() is
+ * called
+ */
+ return __pmConnectRestoreFlags(fd, fdFlags);
+#else
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmAuxConnectPMCDUnixSocket(%s) is not supported\n", __FILE__, sock_path);
+ return -1;
+#endif
+}
+
+char *
+__pmHostEntGetName(__pmHostEnt *he)
+{
+ __pmSockAddr *addr;
+ void *enumIx;
+
+ if (he->name == NULL) {
+ /* Try to reverse lookup the host name.
+ * Check each address until the reverse lookup succeeds.
+ */
+ enumIx = NULL;
+ for (addr = __pmHostEntGetSockAddr(he, &enumIx);
+ addr != NULL;
+ addr = __pmHostEntGetSockAddr(he, &enumIx)) {
+ he->name = __pmGetNameInfo(addr);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s:__pmHostEntGetName: __pmGetNameInfo(%s) returns %s\n", __FILE__, __pmSockAddrToString(addr), he->name);
+#endif
+ __pmSockAddrFree(addr);
+ if (he->name != NULL)
+ break;
+ }
+ if (he->name == NULL)
+ return NULL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s:__pmHostEntGetName -> %s\n", __FILE__, he->name);
+#endif
+
+ return strdup(he->name);
+}
+
+__pmSockAddr *
+__pmHostEntGetSockAddr(const __pmHostEnt *he, void **ei)
+{
+ __pmAddrInfo *ai;
+ __pmSockAddr *addr;
+
+ /* The enumerator index (*ei) is actually a pointer to the current address info. */
+ if (*ei == NULL)
+ *ei = ai = he->addresses;
+ else {
+ ai = *ei;
+ *ei = ai = ai->ai_next;
+ }
+ if (*ei == NULL)
+ return NULL; /* no (more) addresses in the chain. */
+
+ /* Now allocate a socket address and copy the data. */
+ addr = __pmSockAddrAlloc();
+ if (addr == NULL) {
+ __pmNotifyErr(LOG_ERR, "%s:__pmHostEntGetSockAddr: out of memory\n", __FILE__);
+ *ei = NULL;
+ return NULL;
+ }
+ memcpy(&addr->sockaddr.raw, ai->ai_addr, ai->ai_addrlen);
+
+ return addr;
+}
+
+char *
+__pmGetNameInfo(__pmSockAddr *address)
+{
+ int sts;
+ char buf[NI_MAXHOST];
+
+ if (address->sockaddr.raw.sa_family == AF_INET) {
+ sts = getnameinfo(&address->sockaddr.raw, sizeof(address->sockaddr.inet),
+ buf, sizeof(buf), NULL, 0, 0);
+ }
+ else if (address->sockaddr.raw.sa_family == AF_INET6) {
+ sts = getnameinfo(&address->sockaddr.raw, sizeof(address->sockaddr.ipv6),
+ buf, sizeof(buf), NULL, 0, 0);
+ }
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ else if (address->sockaddr.raw.sa_family == AF_UNIX) {
+ /* The name info is the socket path. */
+ return strdup(address->sockaddr.local.sun_path);
+ }
+#endif
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmGetNameInfo: Invalid address family: %d\n", __FILE__, address->sockaddr.raw.sa_family);
+ sts = EAI_FAMILY;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE) {
+ if (sts != 0) {
+ fprintf(stderr, "%s:__pmGetNameInfo: family=%d getnameinfo()-> %d %s\n", __FILE__, address->sockaddr.raw.sa_family, sts, gai_strerror(sts));
+ }
+ }
+#endif
+
+
+ return sts == 0 ? strdup(buf) : NULL;
+}
+
+__pmHostEnt *
+__pmGetAddrInfo(const char *hostName)
+{
+ __pmHostEnt *hostEntry;
+ struct addrinfo hints;
+ int sts;
+
+ hostEntry = __pmHostEntAlloc();
+ if (hostEntry != NULL) {
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+#ifdef HAVE_AI_ADDRCONFIG
+ hints.ai_flags = AI_ADDRCONFIG; /* Only return configured address types */
+#endif
+
+ sts = getaddrinfo(hostName, NULL, &hints, &hostEntry->addresses);
+ if (sts != 0) {
+ __pmHostEntFree(hostEntry);
+ hostEntry = NULL;
+ }
+ /* Leave the host name NULL. It will be looked up on demand in __pmHostEntGetName(). */
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE) {
+ if (hostEntry == NULL)
+ fprintf(stderr, "%s:__pmGetAddrInfo(%s) -> NULL\n", __FILE__, hostName);
+ else
+ fprintf(stderr, "%s:__pmGetAddrInfo(%s) -> %s\n", __FILE__, hostName, hostEntry->name);
+ }
+#endif
+ return hostEntry;
+}
+
+unsigned
+__pmFirstInetSubnetAddr(unsigned ip, int maskBits)
+{
+ unsigned mask = ~((1 << (32 - maskBits)) - 1);
+ return ip & mask;
+}
+
+unsigned
+__pmNextInetSubnetAddr(unsigned ip, int maskBits)
+{
+ unsigned mask = (1 << (32 - maskBits)) - 1;
+
+ /* Is this the final address? If so then return the address unchanged.*/
+ if ((ip & mask) == mask)
+ return ip;
+
+ /* Bump up the address. */
+ return ++ip;
+}
+
+unsigned char *
+__pmFirstIpv6SubnetAddr(unsigned char *addr, int maskBits)
+{
+ unsigned mask;
+ int ix;
+ /*
+ * Manipulate the ipv6 address one byte at a time. There is no
+ * host/network byte order.
+ * Mask the byte at the subnet mask boundary. Leave the higher order bytes
+ * alone and clear the lower order bytes.
+ */
+ ix = maskBits / 8;
+ if (ix < 16) {
+ maskBits %= 8;
+ mask = ~((1 << (8 - maskBits)) - 1);
+ addr[ix] &= mask;
+ for (++ix; ix < 16; ++ix)
+ addr[ix] = 0;
+ }
+
+ return addr;
+}
+
+unsigned char *
+__pmNextIpv6SubnetAddr(unsigned char *addr, int maskBits)
+{
+ unsigned mask;
+ int ix, ix1;
+ /*
+ * Manipulate the ipv6 address one byte at a time. There is no
+ * host/network byte order.
+ * First determine whether this is the final address. Do this by
+ * comparing the high order bits of the subnet against the maximum.
+ */
+ ix = maskBits / 8;
+ if (ix < 16) {
+ maskBits %= 8;
+ mask = (1 << (8 - maskBits)) - 1;
+ if ((addr[ix] & mask) == mask) {
+ /* The highest order bits are maxed out. Check the remaining bits. */
+ for (++ix; ix < 16; ++ix) {
+ if (addr[ix] != 0xff)
+ break;
+ }
+ }
+ }
+ if (ix >= 16) {
+ /* This is the final address. */
+ return NULL;
+ }
+
+ /* Bump up the address. Don't forget to carry into the higher order bits
+ when necessary. */
+ for (ix1 = 15; ix1 >= ix; --ix1) {
+ ++addr[ix1];
+ if (addr[ix1] != 0)
+ break; /* no carry */
+ }
+
+ return addr;
+}
+
+#if !defined(HAVE_SECURE_SOCKETS)
+
+void
+__pmCloseSocket(int fd)
+{
+ __pmResetIPC(fd);
+#if defined(IS_MINGW)
+ closesocket(fd);
+#else
+ close(fd);
+#endif
+}
+
+int
+__pmSetSockOpt(int socket, int level, int option_name, const void *option_value,
+ __pmSockLen option_len)
+{
+ return setsockopt(socket, level, option_name, option_value, option_len);
+}
+
+int
+__pmGetSockOpt(int socket, int level, int option_name, void *option_value,
+ __pmSockLen *option_len)
+{
+ return getsockopt(socket, level, option_name, option_value, option_len);
+}
+
+int
+__pmInitCertificates(void)
+{
+ return 0;
+}
+
+int
+__pmShutdownCertificates(void)
+{
+ return 0;
+}
+
+int
+__pmInitSecureSockets(void)
+{
+ return 0;
+}
+
+int
+__pmShutdownSecureSockets(void)
+{
+ return 0;
+}
+
+int
+__pmInitAuthClients(void)
+{
+ return 0;
+}
+
+int
+__pmDataIPCSize(void)
+{
+ return 0;
+}
+
+int
+__pmSecureClientHandshake(int fd, int flags, const char *hostname, __pmHashCtl *attrs)
+{
+ (void)fd;
+ (void)hostname;
+
+ /*
+ * We cannot handle many flags here (no support), in particular:
+ * PDU_FLAG_SECURE (NSS)
+ * PDU_FLAG_SECURE_ACK (NSS)
+ * PDU_FLAG_NO_NSS_INIT (NSS)
+ * PDU_FLAG_COMPRESS (NSS)
+ * PDU_FLAG_AUTH (SASL2)
+ *
+ * But we can still talk to a pmcd that requires credentials, provided
+ * we are using unix domain sockets (the kernel provides the auth info
+ * to pmcd in this case, with no other special sauce required).
+ */
+ if (flags == PDU_FLAG_CREDS_REQD)
+ if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL)
+ return 0;
+ return -EOPNOTSUPP;
+}
+
+int
+__pmSocketClosed(void)
+{
+ switch (oserror()) {
+ /*
+ * Treat this like end of file on input.
+ *
+ * failed as a result of pmcd exiting and the connection
+ * being reset, or as a result of the kernel ripping
+ * down the connection (most likely because the host at
+ * the other end just took a dive)
+ *
+ * from IRIX BDS kernel sources, seems like all of the
+ * following are peers here:
+ * ECONNRESET (pmcd terminated?)
+ * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH
+ * ECONNREFUSED
+ * peers for BDS but not here:
+ * ENETRESET ENONET ESHUTDOWN (cache_fs only?)
+ * ECONNABORTED (accept, user req only?)
+ * ENOTCONN (udp?)
+ * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?)
+ */
+ case ECONNRESET:
+ case EPIPE:
+ case ETIMEDOUT:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case EHOSTDOWN:
+ case EHOSTUNREACH:
+ case ECONNREFUSED:
+ return 1;
+ }
+ return 0;
+}
+
+ssize_t
+__pmWrite(int socket, const void *buffer, size_t length)
+{
+ return write(socket, buffer, length);
+}
+
+ssize_t
+__pmRead(int socket, void *buffer, size_t length)
+{
+ return read(socket, buffer, length);
+}
+
+ssize_t
+__pmSend(int socket, const void *buffer, size_t length, int flags)
+{
+ return send(socket, buffer, length, flags);
+}
+
+ssize_t
+__pmRecv(int socket, void *buffer, size_t length, int flags)
+{
+ ssize_t size;
+ size = recv(socket, buffer, length, flags);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "%s:__pmRecv(%d, ..., %d, " PRINTF_P_PFX "%x) -> %d\n",
+ __FILE__, socket, (int)length, flags, (int)size);
+ }
+#endif
+ return size;
+}
+
+int
+__pmSocketReady(int fd, struct timeval *timeout)
+{
+ __pmFdSet onefd;
+
+ FD_ZERO(&onefd);
+ FD_SET(fd, &onefd);
+ return select(fd+1, &onefd, NULL, NULL, timeout);
+}
+
+#endif /* !HAVE_SECURE_SOCKETS */
diff --git a/src/libpcp/src/auxserver.c b/src/libpcp/src/auxserver.c
new file mode 100644
index 0000000..498bac4
--- /dev/null
+++ b/src/libpcp/src/auxserver.c
@@ -0,0 +1,940 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <sys/stat.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#define SOCKET_INTERNAL
+#include "internal.h"
+#if defined(HAVE_GETPEERUCRED)
+#include <ucred.h>
+#endif
+
+#define STRINGIFY(s) #s
+#define TO_STRING(s) STRINGIFY(s)
+
+/*
+ * Info about a request port that clients may connect to a server on
+ */
+enum {
+ INET_FD = 0,
+ IPV6_FD,
+ FAMILIES
+};
+typedef struct {
+ int fds[FAMILIES]; /* Inet and IPv6 File descriptors */
+ int port; /* Listening port */
+ const char *address; /* Network address string (or NULL) */
+ __pmServerPresence *presence; /* For advertising server presence on the network. */
+} ReqPortInfo;
+
+static unsigned nReqPorts; /* number of ports */
+static unsigned szReqPorts; /* capacity of ports array */
+static ReqPortInfo *reqPorts; /* ports array */
+
+/*
+ * Interfaces we're willing to listen for clients on, from -i
+ */
+static int nintf;
+static char **intflist;
+
+/*
+ * Ports we're willing to listen for clients on, from -p (or env)
+ */
+static int nport;
+static int *portlist;
+
+/*
+ * The unix domain socket we're willing to listen for clients on,
+ * from -s (or env)
+ */
+static const char *localSocketPath;
+static int localSocketFd = -EPROTO;
+static const char *serviceSpec;
+
+int
+__pmServiceAddPorts(const char *service, int **ports, int nports)
+{
+ /*
+ * The list of ports referenced by *ports may be (re)allocated
+ * using calls to realloc(3) with a new size based on nports.
+ * For an empty list, *ports must be NULL and nports must be 0.
+ * It is the responsibility of the caller to free this memory.
+ *
+ * If -EOPNOTSUPP is not returned, then this function is
+ * guaranteed to return a list containing at least 1 element.
+ *
+ * The service is a service name (e.g. pmcd).
+ */
+ if (strcmp(service, PM_SERVER_SERVICE_SPEC) == 0)
+ nports = __pmPMCDAddPorts(ports, nports);
+ else if (strcmp(service, PM_SERVER_PROXY_SPEC) == 0)
+ nports = __pmProxyAddPorts(ports, nports);
+ else if (strcmp(service, PM_SERVER_WEBD_SPEC) == 0)
+ nports = __pmWebdAddPorts(ports, nports);
+ else
+ nports = -EOPNOTSUPP;
+
+ return nports;
+}
+
+int
+__pmPMCDAddPorts(int **ports, int nports)
+{
+ /*
+ * The list of ports referenced by *ports may be (re)allocated
+ * using calls to realloc(3) with a new size based on nports.
+ * For an empty list, *ports must be NULL and nports must be 0.
+ * It is the responsibility of the caller to free this memory.
+ *
+ * This function is guaranteed to return a list containing at least
+ * 1 element.
+ */
+ char *env;
+ int new_nports = nports;
+
+ if ((env = getenv("PMCD_PORT")) != NULL)
+ new_nports = __pmAddPorts(env, ports, nports);
+
+ /*
+ * Add the default port, if no new ports were added or if there was an
+ * error.
+ */
+ if (new_nports <= nports)
+ new_nports = __pmAddPorts(TO_STRING(SERVER_PORT), ports, nports);
+
+ return new_nports;
+}
+
+int
+__pmProxyAddPorts(int **ports, int nports)
+{
+ /*
+ * The list of ports referenced by *ports may be (re)allocated
+ * using calls to realloc(3) with a new size based on nports.
+ * For an empty list, *ports must be NULL and nports must be 0.
+ * It is the responsibility of the caller to free this memory.
+ *
+ * This function is guaranteed to return a list containing at least
+ * 1 element.
+ */
+ char *env;
+ int new_nports = nports;
+
+ if ((env = getenv("PMPROXY_PORT")) != NULL)
+ new_nports = __pmAddPorts(env, ports, nports);
+
+ /*
+ * Add the default port, if no new ports were added or if there was an
+ * error.
+ */
+ if (new_nports <= nports)
+ new_nports = __pmAddPorts(TO_STRING(PROXY_PORT), ports, nports);
+
+ return new_nports;
+}
+
+int
+__pmWebdAddPorts(int **ports, int nports)
+{
+ /*
+ * The list of ports referenced by *ports may be (re)allocated
+ * using calls to realloc(3) with a new size based on nports.
+ * For an empty list, *ports must be NULL and nports must be 0.
+ * It is the responsibility of the caller to free this memory.
+ *
+ * This function is guaranteed to return a list containing at least
+ * 1 element.
+ */
+ char *env;
+ int new_nports = nports;
+
+ if ((env = getenv("PMWEBD_PORT")) != NULL)
+ new_nports = __pmAddPorts(env, ports, nports);
+
+ /*
+ * Add the default port, if no new ports were added or if there was an
+ * error.
+ */
+ if (new_nports <= nports)
+ new_nports = __pmAddPorts(TO_STRING(PMWEBD_PORT), ports, nports);
+
+ return new_nports;
+}
+
+int
+__pmAddPorts(const char *portstr, int **ports, int nports)
+{
+ /*
+ * The list of ports referenced by *ports may be (re)allocated
+ * using calls to realloc(3) with a new size based on nports.
+ * For an empty list, *ports must be NULL and nports must be 0.
+ * It is the responsibility of the caller to free this memory.
+ *
+ * If sufficient memory cannot be allocated, then this function
+ * calls __pmNoMem() and does not return.
+ */
+ char *endptr, *p = (char *)portstr;
+ size_t size;
+
+ /*
+ * one (of possibly several) ports for client requests
+ * ... accept a comma separated list of ports here
+ */
+ for ( ; ; ) {
+ int port = (int)strtol(p, &endptr, 0);
+ if ((*endptr != '\0' && *endptr != ',') || port < 0)
+ return -EINVAL;
+
+ size = (nports + 1) * sizeof(int);
+ if ((*ports = (int *)realloc(*ports, size)) == NULL)
+ __pmNoMem("__pmAddPorts: cannot grow port list", size, PM_FATAL_ERR);
+ (*ports)[nports++] = port;
+ if (*endptr == '\0')
+ break;
+ p = &endptr[1];
+ }
+ return nports;
+}
+
+int
+__pmServerAddPorts(const char *ports)
+{
+ nport = __pmAddPorts(ports, &portlist, nport);
+ return nport;
+}
+
+int
+__pmServerAddInterface(const char *address)
+{
+ size_t size = (nintf+1) * sizeof(char *);
+ char *intf;
+
+ /* one (of possibly several) IP addresses for client requests */
+ intflist = (char **)realloc(intflist, nintf * sizeof(char *));
+ if (intflist == NULL)
+ __pmNoMem("AddInterface: cannot grow interface list", size, PM_FATAL_ERR);
+ if ((intf = strdup(address)) == NULL)
+ __pmNoMem("AddInterface: cannot strdup interface", strlen(address), PM_FATAL_ERR);
+ intflist[nintf++] = intf;
+ return nintf;
+}
+
+void
+__pmServerSetLocalSocket(const char *path)
+{
+ if (path != NULL && *path != '\0')
+ localSocketPath = strdup(path);
+ else
+ localSocketPath = __pmPMCDLocalSocketDefault();
+}
+
+void
+__pmServerSetServiceSpec(const char *spec)
+{
+ if (spec != NULL && *spec != '\0')
+ serviceSpec = strdup(spec);
+ else
+ serviceSpec = PM_SERVER_SERVICE_SPEC;
+}
+
+static void
+pidonexit(void)
+{
+ char pidpath[MAXPATHLEN];
+
+ if (serviceSpec) {
+ snprintf(pidpath, sizeof(pidpath), "%s%c%s.pid",
+ pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator(), serviceSpec);
+ unlink(pidpath);
+ }
+}
+
+int
+__pmServerCreatePIDFile(const char *spec, int verbose)
+{
+ char pidpath[MAXPATHLEN];
+ FILE *pidfile;
+
+ if (!serviceSpec)
+ __pmServerSetServiceSpec(spec);
+
+ snprintf(pidpath, sizeof(pidpath), "%s%c%s.pid",
+ pmGetConfig("PCP_RUN_DIR"), __pmPathSeparator(), spec);
+
+ if ((pidfile = fopen(pidpath, "w")) == NULL) {
+ if (verbose)
+ fprintf(stderr, "Error: cannot open PID file %s\n", pidpath);
+ return -oserror();
+ }
+ atexit(pidonexit);
+ fprintf(pidfile, "%" FMT_PID, getpid());
+ fclose(pidfile);
+ chmod(pidpath, S_IRUSR | S_IRGRP | S_IROTH);
+ return 0;
+}
+
+void
+__pmCheckAcceptedAddress(__pmSockAddr *addr)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /*
+ * accept(3) doesn't set the peer address for unix domain sockets.
+ * We need to do it ourselves. The address family
+ * is set, so we can use it to test. There is only one unix domain socket
+ * open, so we know its path.
+ */
+ if (__pmSockAddrGetFamily(addr) == AF_UNIX)
+ __pmSockAddrSetPath(addr, localSocketPath);
+#endif
+}
+
+/* Increase the capacity of the reqPorts array (maintain the contents) */
+static void
+GrowRequestPorts(void)
+{
+ size_t need;
+
+ szReqPorts += 4;
+ need = szReqPorts * sizeof(ReqPortInfo);
+ reqPorts = (ReqPortInfo*)realloc(reqPorts, need);
+ if (reqPorts == NULL) {
+ __pmNoMem("GrowRequestPorts: can't grow request port array",
+ need, PM_FATAL_ERR);
+ }
+}
+
+/* Add a request port to the reqPorts array */
+static int
+AddRequestPort(const char *address, int port)
+{
+ ReqPortInfo *rp;
+
+ if (address == NULL)
+ address = "INADDR_ANY";
+
+ if (nReqPorts == szReqPorts)
+ GrowRequestPorts();
+ rp = &reqPorts[nReqPorts];
+ rp->fds[INET_FD] = -1;
+ rp->fds[IPV6_FD] = -1;
+ rp->address = address;
+ rp->port = port;
+ rp->presence = NULL;
+ nReqPorts++;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "AddRequestPort: %s port %d\n", rp->address, rp->port);
+#endif
+
+ return nReqPorts; /* success */
+}
+
+static int
+SetupRequestPorts(void)
+{
+ int i, n;
+
+ /* check for duplicate ports, remove duplicates */
+ for (i = 0; i < nport; i++) {
+ for (n = i + 1; n < nport; n++) {
+ if (portlist[i] == portlist[n])
+ break;
+ }
+ if (n < nport) {
+ __pmNotifyErr(LOG_WARNING,
+ "%s: duplicate client request port (%d) will be ignored\n",
+ pmProgname, portlist[n]);
+ portlist[n] = -1;
+ }
+ }
+
+ if (nintf == 0) {
+ /* no interface options specified, allow connections on any address */
+ for (n = 0; n < nport; n++) {
+ if (portlist[n] != -1)
+ AddRequestPort(NULL, portlist[n]);
+ }
+ }
+ else {
+ for (i = 0; i < nintf; i++) {
+ for (n = 0; n < nport; n++) {
+ if (portlist[n] == -1 || intflist[i] == NULL)
+ continue;
+ AddRequestPort(intflist[i], portlist[n]);
+ }
+ }
+ }
+ return 0;
+}
+
+static const char *
+AddressFamily(int family)
+{
+ if (family == AF_INET)
+ return "inet";
+ if (family == AF_INET6)
+ return "ipv6";
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (family == AF_UNIX)
+ return "unix";
+#endif
+ return "unknown";
+}
+
+/*
+ * Create socket for incoming connections and bind to it an address for
+ * clients to use. Returns -1 on failure.
+ *
+ * If '*family' is AF_UNIX and unix domain sockets are supported:
+ * 'port' is ignored and 'address' is the path to the socket file in the filesystem.
+ *
+ * Otherwise:
+ * address is a string representing the Inet/IPv6 address that the port
+ * is advertised for. To allow connections to all this host's internet
+ * addresses from clients use address = "INADDR_ANY".
+ * On input, 'family' is a pointer to the address family to use (AF_INET,
+ * AF_INET6) if the address specified is empty. If the spec is not
+ * empty then family is ignored and is set to the actual address family
+ * used. 'family' must be initialized to AF_UNSPEC, in this case.
+ */
+static int
+OpenRequestSocket(int port, const char *address, int *family,
+ int backlog, __pmFdSet *fdset, int *maximum)
+{
+ int fd = -1;
+ int one, sts;
+ __pmSockAddr *myAddr;
+ int isUnix = 0;
+
+ /*
+ * Using this flag will eliminate the need for more conditional
+ * compilation below, hopefully making the code easier to read and maintain.
+ */
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (*family == AF_UNIX)
+ isUnix = 1;
+#endif
+
+ if (isUnix) {
+ if ((myAddr = __pmSockAddrAlloc()) == NULL) {
+ __pmNoMem("OpenRequestSocket: can't allocate socket address",
+ sizeof(*myAddr), PM_FATAL_ERR);
+ }
+
+ /* Initialize the address. */
+ __pmSockAddrSetFamily(myAddr, *family);
+ __pmSockAddrSetPath(myAddr, address);
+
+ /* Create the socket. */
+ fd = __pmCreateUnixSocket();
+ }
+ else {
+ if ((myAddr = __pmStringToSockAddr(address)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s) invalid address\n",
+ port, address);
+ goto fail;
+ }
+
+ /*
+ * If the address is unspecified, then use the address family we
+ * have been given, otherwise the family will be determined by
+ * __pmStringToSockAddr.
+ */
+ if (address == NULL || strcmp(address, "INADDR_ANY") == 0)
+ __pmSockAddrSetFamily(myAddr, *family);
+ else
+ *family = __pmSockAddrGetFamily(myAddr);
+ __pmSockAddrSetPort(myAddr, port);
+
+ /* Create the socket. */
+ if (*family == AF_INET)
+ fd = __pmCreateSocket();
+ else if (*family == AF_INET6)
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s) invalid address family: %d\n",
+ port, address, *family);
+ goto fail;
+ }
+ }
+
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmCreateSocket: %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ goto fail;
+ }
+
+ /* Ignore dead client connections. */
+ one = 1;
+#ifndef IS_MINGW
+ if (__pmSetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(SO_REUSEADDR): %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ goto fail;
+ }
+#else
+ if (__pmSetSockOpt(fd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(EXCLUSIVEADDRUSE): %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ goto fail;
+ }
+#endif
+
+ /* and keep alive please - bad networks eat fds */
+ if (__pmSetSockOpt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "OpenRequestSocket(%d, %s, %s) __pmSetSockOpt(SO_KEEPALIVE): %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ goto fail;
+ }
+
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+ __pmSockAddrFree(myAddr);
+ myAddr = NULL;
+ if (sts < 0) {
+ sts = neterror();
+ __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmBind: %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ if (sts == EADDRINUSE)
+ __pmNotifyErr(LOG_ERR, "%s may already be running\n", pmProgname);
+ goto fail;
+ }
+
+ if (isUnix) {
+ /*
+ * For unix domain sockets, grant rw access to the socket for all,
+ * otherwise, on linux platforms, connection will not be possible.
+ * This must be done AFTER binding the address. See Unix(7) for details.
+ */
+ sts = chmod(address, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (sts != 0) {
+ __pmNotifyErr(LOG_ERR,
+ "OpenRequestSocket(%d, %s, %s) chmod(%s): %s\n",
+ port, address, AddressFamily(*family), address, strerror(errno));
+ goto fail;
+ }
+ }
+
+ sts = __pmListen(fd, backlog); /* Max. pending connection requests */
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "OpenRequestSocket(%d, %s, %s) __pmListen: %s\n",
+ port, address, AddressFamily(*family), netstrerror());
+ goto fail;
+ }
+
+ if (fd > *maximum)
+ *maximum = fd;
+ __pmFD_SET(fd, fdset);
+ return fd;
+
+fail:
+ if (fd != -1) {
+ __pmCloseSocket(fd);
+ /* We must unlink the socket file. */
+ if (isUnix)
+ unlink(address);
+ }
+ if (myAddr)
+ __pmSockAddrFree(myAddr);
+ return -1;
+}
+
+/*
+ * Open request ports for client connections.
+ * For each request port, open an inet and IPv6 (if supported) socket
+ * for clients. Also open a local unix domain socket, if it has been
+ * specified
+ */
+static int
+OpenRequestPorts(__pmFdSet *fdset, int backlog)
+{
+ int i, fd, family, success = 0, maximum = -1;
+ int with_ipv6 = strcmp(__pmGetAPIConfig("ipv6"), "true") == 0;
+
+ for (i = 0; i < nReqPorts; i++) {
+ ReqPortInfo *rp = &reqPorts[i];
+ int portsOpened = 0;;
+
+ /*
+ * If the spec is NULL or "INADDR_ANY", then we open one socket
+ * for each address family (inet, IPv6). Otherwise, the address
+ * family will be determined by OpenRequestSocket. Reporting of
+ * all errors is left to OpenRequestSocket to avoid doubling up.
+ */
+ if (rp->address == NULL || strcmp(rp->address, "INADDR_ANY") == 0) {
+ family = AF_INET;
+ if ((fd = OpenRequestSocket(rp->port, rp->address, &family,
+ backlog, fdset, &maximum)) >= 0) {
+ rp->fds[INET_FD] = fd;
+ ++portsOpened;
+ success = 1;
+ }
+ if (with_ipv6) {
+ family = AF_INET6;
+ if ((fd = OpenRequestSocket(rp->port, rp->address, &family,
+ backlog, fdset, &maximum)) >= 0) {
+ rp->fds[IPV6_FD] = fd;
+ ++portsOpened;
+ success = 1;
+ }
+ }
+ else
+ rp->fds[IPV6_FD] = -EPROTO;
+ }
+ else {
+ family = AF_UNSPEC;
+ if ((fd = OpenRequestSocket(rp->port, rp->address, &family,
+ backlog, fdset, &maximum)) >= 0) {
+ if (family == AF_INET) {
+ rp->fds[INET_FD] = fd;
+ ++portsOpened;
+ success = 1;
+ }
+ else if (family == AF_INET6) {
+ rp->fds[IPV6_FD] = fd;
+ ++portsOpened;
+ success = 1;
+ }
+ }
+ }
+ if (portsOpened > 0) {
+ /* Advertise our presence on the network, if requested. */
+ if (serviceSpec != NULL) {
+ rp->presence = __pmServerAdvertisePresence(serviceSpec,
+ rp->port);
+ }
+ }
+ }
+
+ /* Open a local unix domain socket, if specified, and supported. */
+ if (localSocketPath != NULL) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ family = AF_UNIX;
+ if ((localSocketFd = OpenRequestSocket(0, localSocketPath, &family,
+ backlog, fdset, &maximum)) >= 0) {
+ __pmServerSetFeature(PM_SERVER_FEATURE_UNIX_DOMAIN);
+ success = 1;
+ }
+#else
+ __pmNotifyErr(LOG_ERR, "%s: unix domain sockets are not supported\n",
+ pmProgname);
+#endif
+ }
+
+ if (success)
+ return maximum;
+
+ __pmNotifyErr(LOG_ERR, "%s: can't open any request ports, exiting\n",
+ pmProgname);
+ return -1;
+}
+
+int
+__pmServerOpenRequestPorts(__pmFdSet *fdset, int backlog)
+{
+ int sts;
+
+ if ((sts = SetupRequestPorts()) < 0)
+ return sts;
+ return OpenRequestPorts(fdset, backlog);
+}
+
+void
+__pmServerCloseRequestPorts(void)
+{
+ int i, fd;
+
+ for (i = 0; i < nReqPorts; i++) {
+ /* No longer advertise our presence on the network. */
+ if (reqPorts[i].presence != NULL)
+ __pmServerUnadvertisePresence(reqPorts[i].presence);
+ if ((fd = reqPorts[i].fds[INET_FD]) >= 0)
+ __pmCloseSocket(fd);
+ if ((fd = reqPorts[i].fds[IPV6_FD]) >= 0)
+ __pmCloseSocket(fd);
+ }
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (localSocketFd >= 0) {
+ __pmCloseSocket(localSocketFd);
+ localSocketFd = -EPROTO;
+
+ /* We must remove the socket file. */
+ if (unlink(localSocketPath) != 0 && oserror() != ENOENT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR, "%s: can't unlink %s (uid=%d,euid=%d): %s",
+ pmProgname, localSocketPath, getuid(), geteuid(),
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ }
+#endif
+}
+
+/*
+ * Accept any new client connections
+ */
+void
+__pmServerAddNewClients(__pmFdSet *fdset, __pmServerCallback NewClient)
+{
+ int i, fd;
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* Check the local unix domain fd. */
+ if (localSocketFd >= 0)
+ NewClient(fdset, localSocketFd, AF_UNIX);
+#endif
+
+ for (i = 0; i < nReqPorts; i++) {
+ /* Check the inet and ipv6 fds. */
+ if ((fd = reqPorts[i].fds[INET_FD]) >= 0)
+ NewClient(fdset, fd, AF_INET);
+ if ((fd = reqPorts[i].fds[IPV6_FD]) >= 0)
+ NewClient(fdset, fd, AF_INET6);
+ }
+}
+
+static int
+SetCredentialAttrs(__pmHashCtl *attrs, unsigned int pid, unsigned int uid, unsigned int gid)
+{
+ char name[32], *namep;
+
+ snprintf(name, sizeof(name), "%u", uid);
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_USERID, namep, attrs);
+
+ snprintf(name, sizeof(name), "%u", gid);
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs);
+
+ if (!pid) /* not available on all platforms */
+ return 0;
+
+ snprintf(name, sizeof(name), "%u", pid);
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_PROCESSID, namep, attrs);
+
+ return 0;
+}
+
+/*
+ * Set local connection credentials into given hash structure
+ */
+int
+__pmServerSetLocalCreds(int fd, __pmHashCtl *attrs)
+{
+#if defined(HAVE_STRUCT_UCRED) /* Linux */
+ struct ucred ucred;
+ __pmSockLen length = sizeof(ucred);
+
+ if (__pmGetSockOpt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &length) < 0)
+ return -oserror();
+ return SetCredentialAttrs(attrs, ucred.pid, ucred.uid, ucred.gid);
+
+#elif defined(HAVE_GETPEEREID) /* MacOSX */
+ uid_t uid;
+ gid_t gid;
+
+ if (getpeereid(__pmFD(fd), &uid, &gid) < 0)
+ return -oserror();
+ return SetCredentialAttrs(attrs, 0, uid, gid);
+
+#elif defined(HAVE_GETPEERUCRED) /* Solaris */
+ unsigned int uid, gid, pid;
+ ucred_t *ucred = NULL;
+
+ if (getpeerucred(__pmFD(fd), &ucred) < 0)
+ return -oserror();
+ pid = ucred_getpid(ucred);
+ uid = ucred_geteuid(ucred);
+ gid = ucred_getegid(ucred);
+ ucred_free(ucred);
+ return SetCredentialAttrs(attrs, pid, uid, gid);
+
+#else
+ return -EOPNOTSUPP;
+#endif
+}
+
+static const char *
+RequestFamilyString(int index)
+{
+ if (index == INET_FD)
+ return "inet";
+ if (index == IPV6_FD)
+ return "ipv6";
+ return "unknown";
+}
+
+void
+__pmServerDumpRequestPorts(FILE *stream)
+{
+ int i, j;
+
+ fprintf(stream, "%s request port(s):\n"
+ " sts fd port family address\n"
+ " === ==== ===== ====== =======\n", pmProgname);
+
+ if (localSocketFd != -EPROTO)
+ fprintf(stderr, " %-3s %4d %5s %-6s %s\n",
+ (localSocketFd != -1) ? "ok" : "err",
+ localSocketFd, "", "unix",
+ localSocketPath);
+
+ for (i = 0; i < nReqPorts; i++) {
+ ReqPortInfo *rp = &reqPorts[i];
+ for (j = 0; j < FAMILIES; j++) {
+ if (rp->fds[j] != -EPROTO)
+ fprintf(stderr, " %-3s %4d %5d %-6s %s\n",
+ (rp->fds[j] != -1) ? "ok" : "err",
+ rp->fds[j], rp->port, RequestFamilyString(j),
+ rp->address ? rp->address : "(any address)");
+ }
+ }
+}
+
+char *
+__pmServerRequestPortString(int fd, char *buffer, size_t sz)
+{
+ int i, j;
+
+ if (fd == localSocketFd) {
+ snprintf(buffer, sz, "%s unix request socket %s",
+ pmProgname, localSocketPath);
+ return buffer;
+ }
+
+ for (i = 0; i < nReqPorts; i++) {
+ ReqPortInfo *rp = &reqPorts[i];
+ for (j = 0; j < FAMILIES; j++) {
+ if (fd == rp->fds[j]) {
+ snprintf(buffer, sz, "%s %s request socket %s",
+ pmProgname, RequestFamilyString(j), rp->address);
+ return buffer;
+ }
+ }
+ }
+ return NULL;
+}
+
+#if !defined(HAVE_SECURE_SOCKETS)
+
+int
+__pmSecureServerSetup(const char *db, const char *passwd)
+{
+ (void)db;
+ (void)passwd;
+ return 0;
+}
+
+int
+__pmSecureServerIPCFlags(int fd, int flags)
+{
+ (void)fd;
+ (void)flags;
+ return -EOPNOTSUPP;
+}
+
+void
+__pmSecureServerShutdown(void)
+{
+ /* nothing to do here */
+}
+
+int
+__pmSecureServerHandshake(int fd, int flags, __pmHashCtl *attrs)
+{
+ (void)fd;
+ (void)flags;
+ (void)attrs;
+ return -EOPNOTSUPP;
+}
+
+int
+__pmSecureServerHasFeature(__pmServerFeature query)
+{
+ (void)query;
+ return 0;
+}
+
+int
+__pmSecureServerSetFeature(__pmServerFeature wanted)
+{
+ (void)wanted;
+ return 0;
+}
+
+int
+__pmSecureServerClearFeature(__pmServerFeature clear)
+{
+ (void)clear;
+ return 0;
+}
+
+#endif /* !HAVE_SECURE_SOCKETS */
+
+static unsigned int server_features;
+
+int
+__pmServerClearFeature(__pmServerFeature clear)
+{
+ if (clear == PM_SERVER_FEATURE_DISCOVERY) {
+ server_features &= ~(1<<clear);
+ return 1;
+ }
+ return __pmSecureServerClearFeature(clear);
+}
+
+int
+__pmServerSetFeature(__pmServerFeature wanted)
+{
+ if (wanted == PM_SERVER_FEATURE_DISCOVERY ||
+ wanted == PM_SERVER_FEATURE_CREDS_REQD ||
+ wanted == PM_SERVER_FEATURE_UNIX_DOMAIN) {
+ server_features |= (1 << wanted);
+ return 1;
+ }
+ return __pmSecureServerSetFeature(wanted);
+}
+
+int
+__pmServerHasFeature(__pmServerFeature query)
+{
+ int sts = 0;
+
+ switch (query) {
+ case PM_SERVER_FEATURE_IPV6:
+ sts = (strcmp(__pmGetAPIConfig("ipv6"), "true") == 0);
+ break;
+ case PM_SERVER_FEATURE_DISCOVERY:
+ case PM_SERVER_FEATURE_CREDS_REQD:
+ case PM_SERVER_FEATURE_UNIX_DOMAIN:
+ if (server_features & (1 << query))
+ sts = 1;
+ break;
+ default:
+ sts = __pmSecureServerHasFeature(query);
+ break;
+ }
+ return sts;
+}
diff --git a/src/libpcp/src/avahi.c b/src/libpcp/src/avahi.c
new file mode 100644
index 0000000..26d73a5
--- /dev/null
+++ b/src/libpcp/src/avahi.c
@@ -0,0 +1,800 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <assert.h>
+#include <avahi-client/publish.h>
+#include <avahi-client/lookup.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/simple-watch.h>
+#include <avahi-common/thread-watch.h>
+#include <avahi-common/timeval.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+#include <avahi-common/domain.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include "avahi.h"
+
+/* Support for servers advertising their presence. */
+static AvahiThreadedPoll *threadedPoll;
+static AvahiClient *client;
+static AvahiEntryGroup *group;
+
+static __pmServerPresence **activeServices;
+static int nActiveServices;
+static int szActiveServices;
+
+struct __pmServerAvahiPresence {
+ char *serviceName;
+ char *serviceTag;
+ int collisions;
+};
+
+static void entryGroupCallback(AvahiEntryGroup *, AvahiEntryGroupState, void *);
+
+static int
+renameService(__pmServerPresence *s)
+{
+ /*
+ * Each service must have a unique name on the local network.
+ * When there is a collision, we try to rename the service.
+ * However, we need to limit the number of attempts, since the
+ * service namespace could be maliciously flooded with service
+ * names designed to maximize collisions.
+ * Arbitrarily choose a limit of 65535, which is the number of
+ * TCP ports.
+ */
+ ++s->avahi->collisions;
+ if (s->avahi->collisions >= 65535) {
+ __pmNotifyErr(LOG_ERR, "Too many service name collisions for Avahi service %s",
+ s->avahi->serviceTag);
+ return -EBUSY;
+ }
+
+ /*
+ * Use the avahi-supplied function to generate a new service name.
+ */
+ char *n = avahi_alternative_service_name(s->avahi->serviceName);
+
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_INFO, "Avahi service name collision, renaming service '%s' to '%s'",
+ s->avahi->serviceName, n);
+ avahi_free(s->avahi->serviceName);
+ s->avahi->serviceName = n;
+
+ return 0;
+}
+
+static int
+renameServices(void)
+{
+ int i;
+ int rc = 0;
+ __pmServerPresence *s;
+
+ for (i = 0; i < szActiveServices; ++i) {
+ s = activeServices[i];
+ if (s == NULL)
+ continue; /* empty entry */
+ if ((rc = renameService(s)) < 0)
+ break;
+ }
+
+ return rc;
+}
+
+static void
+createServices(AvahiClient *c)
+{
+ __pmServerPresence *s;
+ int ret;
+ int i;
+
+ assert(c);
+
+ /*
+ * Create a new entry group, if necessary, or reset the existing one.
+ */
+ if (group == NULL) {
+ if ((group = avahi_entry_group_new(c, entryGroupCallback, NULL)) == NULL) {
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_ERR, "avahi_entry_group_new failed: %s",
+ avahi_strerror(avahi_client_errno(c)));
+ return;
+ }
+ }
+ else
+ avahi_entry_group_reset(group);
+
+ /*
+ * We will now add our services to the entry group.
+ */
+ for (i = 0; i < szActiveServices; ++i) {
+ s = activeServices[i];
+ if (s == NULL)
+ continue; /* empty table entry */
+
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_INFO, "Adding %s Avahi service on port %d",
+ s->avahi->serviceName, s->port);
+
+ /* Loop until no collisions */
+ for (;;) {
+ ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC,
+ (AvahiPublishFlags)0,
+ s->avahi->serviceName,
+ s->avahi->serviceTag,
+ NULL, NULL, s->port, NULL);
+ if (ret == AVAHI_OK)
+ break; /* success! */
+ if (ret == AVAHI_ERR_COLLISION) {
+ /*
+ * A service name collision with a local service happened.
+ * Pick a new name. Since a service may be listening on
+ * multiple ports, this is expected to happen sometimes -
+ * do not issue warnings here.
+ */
+ if (renameService(s) < 0) {
+ /* Too many collisions. Message already issued */
+ goto fail;
+ }
+ continue; /* try again */
+ }
+
+ __pmNotifyErr(LOG_ERR, "Failed to add %s Avahi service on port %d: %s",
+ s->avahi->serviceName, s->port, avahi_strerror(ret));
+ goto fail;
+ }
+ }
+
+ /* Tell the server to register the services. */
+ if ((ret = avahi_entry_group_commit(group)) < 0) {
+ __pmNotifyErr(LOG_ERR, "Failed to commit avahi entry group: %s",
+ avahi_strerror(ret));
+ goto fail;
+ }
+
+ return;
+
+ fail:
+ avahi_entry_group_reset(group);
+}
+
+static void
+entryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *data)
+{
+ (void)data;
+
+ assert(g != NULL);
+ assert(group == NULL || group == g);
+ group = g;
+
+ /* Called whenever the entry group state changes. */
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ /* The entry group has been established successfully. */
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_INFO, "Avahi services successfully established.");
+ break;
+
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ /*
+ * A service name collision with a remote service happened.
+ * Unfortunately, we don't know which entry collided.
+ * We need to rename them all and recreate the services.
+ */
+ if (renameServices() == 0)
+ createServices(avahi_entry_group_get_client(g));
+ break;
+
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ /* Some kind of failure happened. */
+ __pmNotifyErr(LOG_ERR, "Avahi entry group failure: %s",
+ avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+}
+
+static void
+cleanupClient(void)
+{
+ /* This also frees the entry group, if any. */
+ if (client) {
+ avahi_client_free(client);
+ client = NULL;
+ group = NULL;
+ }
+}
+
+static void
+advertisingClientCallback(AvahiClient *c, AvahiClientState state, void *userData)
+{
+ assert(c);
+ (void)userData;
+
+ /* Called whenever the client or server state changes. */
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ /*
+ * The server has started successfully and registered its host
+ * name on the network, so it's time to create our services.
+ */
+ createServices(c);
+ break;
+
+ case AVAHI_CLIENT_FAILURE:
+ __pmNotifyErr(LOG_ERR, "Avahi client failure: %s",
+ avahi_strerror(avahi_client_errno(c)));
+ if (avahi_client_errno (c) == AVAHI_ERR_DISCONNECTED) {
+ int error;
+ /*
+ * The client has been disconnected; probably because the
+ * avahi daemon has been restarted. We can free the client
+ * here and try to reconnect using a new one.
+ * Passing AVAHI_CLIENT_NO_FAIL allows the new client to be
+ * created, even if the avahi daemon is not running. Our
+ * service will be advertised if/when the daemon is started.
+ */
+ cleanupClient();
+ client = avahi_client_new(avahi_threaded_poll_get(threadedPoll),
+ (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL,
+ advertisingClientCallback, NULL, & error);
+ }
+ break;
+
+ case AVAHI_CLIENT_S_COLLISION:
+ /*
+ * Drop our registered services. When the server is back
+ * in AVAHI_SERVER_RUNNING state we will register them
+ * again with the new host name.
+ * Fall through ...
+ */
+ case AVAHI_CLIENT_S_REGISTERING:
+ /*
+ * The server records are now being established. This
+ * might be caused by a host name change. We need to wait
+ * for our own records to register until the host name is
+ * properly esatblished.
+ */
+ if (group)
+ avahi_entry_group_reset (group);
+ break;
+
+ case AVAHI_CLIENT_CONNECTING:
+ /*
+ * The avahi-daemon is not currently running. Our service will be
+ * advertised if/when the daemon is started.
+ */
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_INFO,
+ "The Avahi daemon is not running. "
+ "Avahi services will be established when the daemon is started");
+ break;
+ }
+}
+
+static void
+cleanup(__pmServerAvahiPresence *s)
+{
+ if (s == NULL)
+ return;
+
+ if (s->serviceName) {
+ avahi_free(s->serviceName);
+ s->serviceName = NULL;
+ }
+ if (s->serviceTag) {
+ avahi_free(s->serviceTag);
+ s->serviceTag = NULL;
+ }
+}
+
+/* Publish a new service. */
+static void
+addService(__pmServerPresence *s)
+{
+ int i;
+ size_t size;
+
+ /* Find an empty slot in the table of active services. */
+ for (i = 0; i < szActiveServices; ++i) {
+ if (activeServices[i] == NULL)
+ break;
+ }
+
+ /* Do we need to grow the table? */
+ if (i >= szActiveServices) {
+ ++szActiveServices;
+ size = szActiveServices * sizeof(*activeServices);
+ activeServices = realloc(activeServices, size);
+ if (activeServices == NULL) {
+ __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service table",
+ size, PM_FATAL_ERR);
+ }
+ }
+
+ /* Add the service to the table. */
+ activeServices[i] = s;
+ ++nActiveServices;
+}
+
+/* Publish a new service. */
+static void
+removeService(__pmServerPresence *s)
+{
+ int i;
+
+ /*
+ * Find the service in the table of active services.
+ * We can do this by comparing the pointers directly, since
+ * that's how the services were added.
+ */
+ for (i = 0; i < szActiveServices; ++i) {
+ /*
+ * Did we find it? If so, clear the entry.
+ * We don't free it here.
+ */
+ if (activeServices[i] == s) {
+ activeServices[i] = NULL;
+ --nActiveServices;
+ return;
+ }
+ }
+}
+
+/* Publish a new service. */
+static void
+publishService(__pmServerPresence *s)
+{
+ int error;
+
+ /* Add the service to our list of active services. */
+ addService(s);
+
+ /* Is this the first service to be added? */
+ if (threadedPoll == NULL) {
+ /* Allocate main loop object. */
+ if ((threadedPoll = avahi_threaded_poll_new()) == NULL) {
+ __pmNotifyErr(LOG_ERR, "Failed to create avahi threaded poll object.");
+ goto fail;
+ }
+
+ /*
+ * Always allocate a new client. Passing AVAHI_CLIENT_NO_FAIL allows
+ * the client to be created, even if the avahi daemon is not running.
+ * Our service will be advertised if/when the daemon is started.
+ */
+ client = avahi_client_new(avahi_threaded_poll_get(threadedPoll),
+ (AvahiClientFlags)AVAHI_CLIENT_NO_FAIL,
+ advertisingClientCallback, NULL, &error);
+
+ /* Check whether creating the client object succeeded. */
+ if (! client) {
+ __pmNotifyErr(LOG_ERR, "Failed to create avahi client: %s",
+ avahi_strerror(error));
+ goto fail;
+ }
+
+ /* Start the main loop. */
+ avahi_threaded_poll_start(threadedPoll);
+ }
+ else {
+ /* Stop the main loop while we recreate the services. */
+ avahi_threaded_poll_lock(threadedPoll);
+ createServices(client);
+ avahi_threaded_poll_unlock(threadedPoll);
+ }
+
+ return;
+
+ fail:
+ cleanup(s->avahi);
+ free(s->avahi);
+}
+
+void
+__pmServerAvahiAdvertisePresence(__pmServerPresence *s)
+{
+ size_t size;
+ char host[MAXHOSTNAMELEN];
+
+ /* Allocate the avahi server presence. */
+ s->avahi = malloc(sizeof(*s->avahi));
+ if (s->avahi == NULL) {
+ __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate avahi service data",
+ sizeof(*s->avahi), PM_FATAL_ERR);
+ }
+
+ /*
+ * The service spec is simply the name of the server. Use it to
+ * construct the avahi service name and service tag.
+ * The service name cannot be longer than AVAHI_LABEL_MAX - 1.
+ */
+ gethostname(host, sizeof(host));
+ host[sizeof(host)-1] = '\0';
+
+ size = sizeof("PCP..on.") + strlen(host) +
+ strlen(s->serviceSpec); /* includes room for the nul */
+ if (size > AVAHI_LABEL_MAX)
+ size = AVAHI_LABEL_MAX;
+ if ((s->avahi->serviceName = avahi_malloc(size)) == NULL) {
+ __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service name",
+ size, PM_FATAL_ERR);
+ }
+ snprintf(s->avahi->serviceName, size, "PCP %s on %s", s->serviceSpec, host);
+ assert (avahi_is_valid_service_name(s->avahi->serviceName));
+
+ size = sizeof("_._tcp") + strlen(s->serviceSpec); /* includes room for the nul */
+ if ((s->avahi->serviceTag = avahi_malloc(size)) == NULL) {
+ __pmNoMem("__pmServerAvahiAdvertisePresence: can't allocate service tag",
+ size, PM_FATAL_ERR);
+ }
+ sprintf(s->avahi->serviceTag, "_%s._tcp", s->serviceSpec);
+ s->avahi->collisions = 0;
+
+ /* Now publish the avahi service. */
+ publishService(s);
+}
+
+void
+__pmServerAvahiUnadvertisePresence(__pmServerPresence *s)
+{
+ /* Not an avahi service? */
+ if (s->avahi == NULL)
+ return;
+
+ if (pmDebug & DBG_TRACE_DISCOVERY)
+ __pmNotifyErr(LOG_INFO, "Removing Avahi service '%s' on port %d",
+ s->avahi->serviceName , s->port);
+
+ /* Remove and cleanup the service. */
+ removeService(s);
+ cleanup(s->avahi);
+ free(s->avahi);
+ s->avahi = NULL;
+
+ /* Nothing to do if the client is not running. */
+ if (threadedPoll == NULL)
+ return;
+
+ /* Stop the main loop. */
+
+ /* If no services remain, then shut down the avahi client. */
+ if (nActiveServices == 0) {
+ /* Clean up the avahi objects. The order of freeing these is significant. */
+ avahi_threaded_poll_stop(threadedPoll);
+ cleanupClient();
+ avahi_threaded_poll_free(threadedPoll);
+ threadedPoll = NULL;
+ return;
+ }
+
+ /* Otherwise, stop the main loop while we recreate the services. */
+ avahi_threaded_poll_lock(threadedPoll);
+ createServices(client);
+ avahi_threaded_poll_unlock(threadedPoll);
+}
+
+/* Support for clients searching for services. */
+typedef struct browsingContext {
+ const __pmServiceDiscoveryOptions *discoveryOptions;
+ AvahiSimplePoll *simplePoll;
+ char ***urls;
+ int numUrls;
+ int error;
+} browsingContext;
+
+/* Called whenever a service has been resolved successfully or timed out. */
+static void
+resolveCallback(
+ AvahiServiceResolver *r,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiResolverEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ const char *hostName,
+ const AvahiAddress *address,
+ uint16_t port,
+ AvahiStringList *txt,
+ AvahiLookupResultFlags flags,
+ void *userdata
+)
+{
+ char addressString[AVAHI_ADDRESS_STR_MAX];
+ browsingContext *context = (browsingContext *)userdata;
+ char ***urls = context->urls;
+ int numUrls = context->numUrls;
+ __pmServiceInfo serviceInfo;
+
+ /* Unused arguments. */
+ (void)protocol;
+ (void)hostName;
+ (void)txt;
+ (void)flags;
+ assert(r);
+
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ context->error = avahi_client_errno(avahi_service_resolver_get_client(r));
+ break;
+
+ case AVAHI_RESOLVER_FOUND:
+ if (strcmp(type, "_" PM_SERVER_SERVICE_SPEC "._tcp") == 0) {
+ serviceInfo.spec = PM_SERVER_SERVICE_SPEC;
+ serviceInfo.protocol = SERVER_PROTOCOL;
+ }
+ else if (strcmp(type, "_" PM_SERVER_PROXY_SPEC "._tcp") == 0) {
+ serviceInfo.spec = PM_SERVER_PROXY_SPEC;
+ serviceInfo.protocol = PROXY_PROTOCOL;
+ }
+ else if (strcmp(type, "_" PM_SERVER_WEBD_SPEC "._tcp") == 0) {
+ serviceInfo.spec = PM_SERVER_WEBD_SPEC;
+ serviceInfo.protocol = PMWEBD_PROTOCOL;
+ }
+ else {
+ context->error = EINVAL;
+ break;
+ }
+
+ avahi_address_snprint(addressString, sizeof(addressString), address);
+ serviceInfo.address = __pmStringToSockAddr(addressString);
+ if (serviceInfo.address == NULL) {
+ context->error = ENOMEM;
+ break;
+ }
+ __pmSockAddrSetPort(serviceInfo.address, port);
+ __pmSockAddrSetScope(serviceInfo.address, interface);
+ context->numUrls = __pmAddDiscoveredService(&serviceInfo,
+ context->discoveryOptions,
+ numUrls, urls);
+ __pmSockAddrFree(serviceInfo.address);
+ break;
+
+ default:
+ break;
+ }
+
+ avahi_service_resolver_free(r);
+}
+
+/*
+ * Called whenever a new service becomes available on the LAN
+ * or is removed from the LAN.
+ */
+static void
+browseCallback(
+ AvahiServiceBrowser *b,
+ AvahiIfIndex interface,
+ AvahiProtocol protocol,
+ AvahiBrowserEvent event,
+ const char *name,
+ const char *type,
+ const char *domain,
+ AvahiLookupResultFlags flags,
+ void *userdata
+)
+{
+ browsingContext *context = (browsingContext *)userdata;
+ AvahiClient *c = avahi_service_browser_get_client(b);
+ AvahiSimplePoll *simplePoll = context->simplePoll;
+ assert(b);
+
+ /* Unused argument. */
+ (void)flags;
+
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+ context->error = avahi_client_errno(c);
+ avahi_simple_poll_quit(simplePoll);
+ break;
+
+ case AVAHI_BROWSER_NEW:
+ /*
+ * We ignore the returned resolver object. In the callback
+ * function we free it. If the server is terminated before
+ * the callback function is called the server will free
+ * the resolver for us.
+ */
+ if (!(avahi_service_resolver_new(c, interface, protocol,
+ name, type, domain,
+ AVAHI_PROTO_UNSPEC, (AvahiLookupFlags)0,
+ resolveCallback, context))) {
+ context->error = avahi_client_errno(c);
+ }
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ break;
+ }
+}
+
+/* Called whenever the client or server state changes. */
+static void
+browsingClientCallback(AvahiClient *c, AvahiClientState state, void *userdata)
+{
+ assert(c);
+ if (state == AVAHI_CLIENT_FAILURE) {
+ browsingContext *context = (browsingContext *)userdata;
+ AvahiSimplePoll *simplePoll = context->simplePoll;
+ context->error = avahi_client_errno(c);
+ avahi_simple_poll_quit(simplePoll);
+ }
+}
+
+static void
+timeoutCallback(AvahiTimeout *e, void *userdata)
+{
+ browsingContext *context = (browsingContext *)userdata;
+ AvahiSimplePoll *simplePoll = context->simplePoll;
+ (void)e;
+ avahi_simple_poll_quit(simplePoll);
+}
+
+
+static double
+discoveryTimeout(void)
+{
+ static int done_default = 0;
+ static double def_timeout = 0.5; /* 0.5 seconds */
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (!done_default) {
+ char *timeout_str;
+ char *end_ptr;
+ double new_timeout;
+ if ((timeout_str = getenv("AVAHI_DISCOVERY_TIMEOUT")) != NULL) {
+ new_timeout = strtod(timeout_str, &end_ptr);
+ if (*end_ptr != '\0' || def_timeout < 0.0) {
+ __pmNotifyErr(LOG_WARNING,
+ "ignored bad AVAHI_DISCOVERY_TIMEOUT = '%s'\n",
+ timeout_str);
+ }
+ else
+ def_timeout = new_timeout;
+ }
+ done_default = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return def_timeout;
+}
+
+int
+__pmAvahiDiscoverServices(const char *service,
+ const char *mechanism,
+ const __pmServiceDiscoveryOptions *options,
+ int numUrls,
+ char ***urls)
+{
+ AvahiClient *client = NULL;
+ AvahiServiceBrowser *sb = NULL;
+ AvahiSimplePoll *simplePoll;
+ struct timeval tv;
+ browsingContext context;
+ char *serviceTag;
+ size_t size;
+ const char *timeoutBegin;
+ char *timeoutEnd;
+ double timeout;
+ int sts;
+
+ /* Allocate the main loop object. */
+ if (!(simplePoll = avahi_simple_poll_new()))
+ return -ENOMEM;
+
+ context.discoveryOptions = options;
+ context.error = 0;
+ context.simplePoll = simplePoll;
+ context.urls = urls;
+ context.numUrls = numUrls;
+
+ /* Allocate a new Avahi client */
+ client = avahi_client_new(avahi_simple_poll_get(simplePoll),
+ (AvahiClientFlags)0,
+ browsingClientCallback, &context, &context.error);
+
+ /* Check whether creating the client object succeeded. */
+ if (! client)
+ goto done;
+
+ /* Create the service browser. */
+ size = sizeof("_._tcp") + strlen(service); /* includes room for the nul */
+ if ((serviceTag = malloc(size)) == NULL) {
+ context.error = ENOMEM;
+ goto done;
+ }
+ sprintf(serviceTag, "_%s._tcp", service);
+ sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, serviceTag,
+ NULL, (AvahiLookupFlags)0,
+ browseCallback, & context);
+ free(serviceTag);
+ if (sb == NULL) {
+ context.error = ENOMEM;
+ goto done;
+ }
+
+ /* Extract any ,timeout=NNN parameters. */
+ timeout = discoveryTimeout(); /* default */
+
+ timeoutBegin = strstr(mechanism ? mechanism : "", ",timeout=");
+ if (timeoutBegin) {
+ timeoutBegin += strlen(",timeout="); /* skip over it */
+ timeout = strtod (timeoutBegin, & timeoutEnd);
+ if ((*timeoutEnd != '\0' && *timeoutEnd != ',') || (timeout < 0.0)) {
+ __pmNotifyErr(LOG_WARNING,
+ "ignored bad avahi timeout = '%*s'\n",
+ (int)(timeoutEnd-timeoutBegin), timeoutBegin);
+ timeout = discoveryTimeout();
+ }
+ }
+
+ /* Set the timeout. */
+ avahi_simple_poll_get(simplePoll)->timeout_new(
+ avahi_simple_poll_get(simplePoll),
+ avahi_elapse_time(&tv, (unsigned)(timeout * 1000), 0),
+ timeoutCallback, &context);
+
+ /*
+ * This loop is based on the one in avahi_simple_poll_loop().
+ *
+ * Run the main loop one iteration at a time until it times out
+ * or until we are interrupted.
+ * The overall timeout within simplePoll will be respected and
+ * avahi_simple_poll_iterate() will return 1 if it occurs.
+ * Otherwise, avahi_simple_poll_iterate() returns -1 on error and
+ * zero on success.
+ * The discovered services will be added to 'urls' during the call back
+ * to resolveCallback
+ */
+ while (! options->timedOut &&
+ (! options->flags ||
+ (*options->flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0)) {
+ if ((sts = avahi_simple_poll_iterate(simplePoll, -1)) != 0)
+ if (sts > 0 || errno != EINTR)
+ break;
+ }
+ numUrls = context.numUrls;
+
+ done:
+ /* Cleanup. */
+ if (client) {
+ /* Also frees the service browser. */
+ avahi_client_free(client);
+ }
+ if (simplePoll)
+ avahi_simple_poll_free(simplePoll);
+
+ /*
+ * Check to see if there was an error. Make sure that the returned error
+ * code is negative.
+ */
+ if (context.error > 0)
+ return -context.error;
+ if (context.error < 0)
+ return context.error;
+
+ return numUrls;
+}
diff --git a/src/libpcp/src/avahi.h b/src/libpcp/src/avahi.h
new file mode 100644
index 0000000..bc375ff
--- /dev/null
+++ b/src/libpcp/src/avahi.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef AVAHI_H
+#define AVAHI_H
+
+#ifdef HAVE_AVAHI
+void __pmServerAvahiAdvertisePresence(__pmServerPresence *) _PCP_HIDDEN;
+void __pmServerAvahiUnadvertisePresence(__pmServerPresence *) _PCP_HIDDEN;
+int __pmAvahiDiscoverServices(const char *,
+ const char *,
+ const __pmServiceDiscoveryOptions *,
+ int,
+ char ***) _PCP_HIDDEN;
+#else
+#define __pmServerAvahiAdvertisePresence(p) do { } while (0)
+#define __pmServerAvahiUnadvertisePresence(p) do { } while (0)
+#define __pmAvahiDiscoverServices(s, m, o, n, u) 0
+#endif
+
+#endif /* AVAHI_H */
diff --git a/src/libpcp/src/check-statics b/src/libpcp/src/check-statics
new file mode 100755
index 0000000..6a69435
--- /dev/null
+++ b/src/libpcp/src/check-statics
@@ -0,0 +1,502 @@
+#!/bin/sh
+#
+# Check symbols for static variables against list of exceptions
+# that are known to be thread-safe
+#
+
+set -e # detect syntax errors or subsidiary command failures
+sts=1 # presume failure, in case of an unexpected early exit
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$sts" 0 1 2 3 15
+
+# Note
+# Really want to make this run on as many platforms as possible ...
+eval `grep PCP_PLATFORM= ../../include/pcp.conf`
+case "$PCP_PLATFORM"
+in
+ linux|darwin)
+ # only works for some architectures ... and in particular not
+ # Power PC!
+ #
+ arch=`uname -m 2>/dev/null`
+ case "$arch"
+ in
+ i?86|x86_64)
+ ;;
+ *)
+ echo "Warning: check-statics skipped for $arch architecture"
+ sts=0
+ exit
+ ;;
+ esac
+ ;;
+ freebsd|netbsd|solaris)
+ ;;
+ *)
+ echo "Warning: check-statics skipped for PCP_PLATFORM=$PCP_PLATFORM"
+ sts=0
+ exit
+ ;;
+esac
+
+obj=''
+cat <<End-of-File \
+| sed -e 's/[ ]*#.*//' \
+ -e '/^$/d' >$tmp/ctl
+# Format for the control file ...
+# All text after a # is treated as a comment
+#
+# Lines consisting of a FOO.o name are assumed to be the name of an
+# object file ... if any object file is found in the current directory
+# that is not named in the control file, this is an error. Object
+# file names beginning with '?' are optional, otherwise the object
+# file is expected to exist.
+#
+# Following the name of an object file follows zero or more lines
+# defining static data symbols from that object file that is known to
+# be thread-safe ... these lines contain the symbol's name and by
+# convention an comment explaining why the symbol is thread-safe. The
+# symbol may be preceded by a '?' character to indicate the symbol may
+# or may not be in the object file, otherwise a symbol named here that
+# is not in the object file produces a warning.
+#
+access.o
+ all_ops # single-threaded PM_SCOPE_ACL
+ gotmyhostid # single-threaded PM_SCOPE_ACL
+ grouplist # single-threaded PM_SCOPE_ACL
+ hostlist # single-threaded PM_SCOPE_ACL
+ myhostid # single-threaded PM_SCOPE_ACL
+ myhostname # single-threaded PM_SCOPE_ACL
+ nhosts # single-threaded PM_SCOPE_ACL
+ ngroups # single-threaded PM_SCOPE_ACL
+ nusers # single-threaded PM_SCOPE_ACL
+ oldgrouplist # single-threaded PM_SCOPE_ACL
+ oldhostlist # single-threaded PM_SCOPE_ACL
+ olduserlist # single-threaded PM_SCOPE_ACL
+ oldngroups # single-threaded PM_SCOPE_ACL
+ oldnhosts # single-threaded PM_SCOPE_ACL
+ oldnusers # single-threaded PM_SCOPE_ACL
+ oldszgrouplist # single-threaded PM_SCOPE_ACL
+ oldszhostlist # single-threaded PM_SCOPE_ACL
+ oldszuserlist # single-threaded PM_SCOPE_ACL
+ saved # single-threaded PM_SCOPE_ACL
+ szhostlist # single-threaded PM_SCOPE_ACL
+ szgrouplist # single-threaded PM_SCOPE_ACL
+ szuserlist # single-threaded PM_SCOPE_ACL
+ userlist # single-threaded PM_SCOPE_ACL
+accounts.o
+AF.o
+ afid # single-threaded PM_SCOPE_AF
+ block # single-threaded PM_SCOPE_AF
+ root # single-threaded PM_SCOPE_AF
+ ?afblock # guarded by __pmLock_libpcp mutex
+ ?afsetup # guarded by __pmLock_libpcp mutex
+ ?aftimer # guarded by __pmLock_libpcp mutex
+auxconnect.o
+ canwait # guarded by __pmLock_libpcp mutex
+ first_time # guarded by __pmLock_libpcp mutex
+ pmcd_ports # guarded by __pmLock_libpcp mutex
+ pmcd_socket # guarded by __pmLock_libpcp mutex
+auxserver.o
+ nport # single-threaded server scope
+ portlist # single-threaded server scope
+ nintf # single-threaded server scope
+ intflist # single-threaded server scope
+ nReqPorts # single-threaded server scope
+ szReqPorts # single-threaded server scope
+ reqPorts # single-threaded server scope
+ localSocketPath # single-threaded server scope
+ serviceSpec # single-threaded server scope
+ localSocketFd # single-threaded server scope
+ server_features # single-threaded server scope
+discovery.o
+?avahi.o
+ nActiveServices # single-threaded server scope
+ szActiveServices # single-threaded server scope
+ activeServices # single-threaded server scope
+ threadedPoll # single-threaded server scope
+ simplePoll # single-threaded server scope
+ client # single-threaded server scope
+ group # single-threaded server scope
+ done_default # guarded by __pmLock_libpcp mutex
+ def_timeout # guarded by __pmLock_libpcp mutex
+checksum.o
+config.o
+ ?__pmNativeConfig # const
+ state # guarded by __pmLock_libpcp mutex
+ ?features # const
+connectlocal.o
+ atexit_installed # guarded by __pmLock_libpcp mutex
+ buffer # assert safe, see notes in connectlocal.c
+ dsotab # assert safe, see notes in connectlocal.c
+ numdso # assert safe, see notes in connectlocal.c
+connect.o
+ global_nports # guarded by __pmLock_libpcp mutex
+ global_portlist # guarded by __pmLock_libpcp mutex
+ first_time # guarded by __pmLock_libpcp mutex
+ proxy # guarded by __pmLock_libpcp mutex
+context.o
+ _mode # const
+ def_backoff # guarded by __pmLock_libpcp mutex
+ backoff # guarded by __pmLock_libpcp mutex
+ n_backoff # guarded by __pmLock_libpcp mutex
+ contexts # guarded by __pmLock_libpcp mutex
+ contexts_len # guarded by __pmLock_libpcp mutex
+ hostbuf # single-threaded
+ ?curcontext # thread private
+ ?__emutls_t.curcontext # thread private (MinGW)
+ ?__emutls_v.curcontext # thread private (MinGW)
+derive_fetch.o
+derive.o
+ ?func # const
+ ?init # local initialize_mutex mutex
+ ?done # guarded by local initialize_mutex mutex
+ type_dbg # const
+ ?type_c # const
+ state_dbg # const
+ ?promote # const
+ ?timefactor # const
+ need_init # guarded by registered.mutex
+ tokbuf # guarded by registered.mutex
+ tokbuflen # guarded by registered.mutex
+ string # guarded by registered.mutex
+ lexpeek # guarded by registered.mutex
+ this # guarded by registered.mutex
+ ?registered # guarded by registered.mutex
+ pmid # guarded by registered.mutex
+ ?derive_errmsg # thread private
+ ?__emutls_v.derive_errmsg # thread private (MinGW)
+ ?func # const (MinGW)
+ ?promote # const (MinGW)
+ ?timefactor # const (MinGW)
+ ?type_c # const (MinGW)
+desc.o
+endian.o
+err.o
+ ?errtab # const
+ ?first # guarded by __pmLock_libpcp mutex
+ unknown # guarded by __pmLock_libpcp mutex or const (MinGW)
+ errmsg # pmErrStr deprecated by pmErrStr_r
+events.o
+ first # guarded by __pmLock_libpcp mutex
+ name_flags # guarded by __pmLock_libpcp mutex
+ name_missed # guarded by __pmLock_libpcp mutex
+ pmid_flags # no unsafe side-effects
+ pmid_missed # no unsafe side-effects
+fault.o
+fetchlocal.o
+ splitlist # single-threaded PM_SCOPE_DSO_PMDA
+ splitmax # single-threaded PM_SCOPE_DSO_PMDA
+fetch.o
+freeresult.o
+getdate.tab.o
+ dst_table # const
+ meridian_table # const
+ military_table # const
+ month_and_day_table # const
+ relative_time_table # const
+ time_units_table # const
+ time_zone_table # const
+ universal_time_zone_table # const
+ yycheck # const
+ yydefact # const
+ yydefgoto # const
+ yypact # const
+ yypgoto # const
+ yyr1 # const
+ yyr2 # const
+ ?yystos # const, may be optimized away
+ ?yyval_default # local to parser ... depends on yacc/bison version
+ yytable # const
+ yytranslate # const
+getopt.o
+hash.o
+help.o
+instance.o
+interp.o
+ dowrap # guarded by __pmLock_libpcp mutex
+ nr # diag counters, no atomic updates
+ nr_cache # diag counters, no atomic updates
+ statestr # const
+ipc.o
+ __pmIPCTable # guarded by __pmLock_libpcp mutex
+ __pmLastUsedFd # guarded by __pmLock_libpcp mutex
+ ipcentrysize # guarded by __pmLock_libpcp mutex
+ ipctablecount # guarded by __pmLock_libpcp mutex
+lock.o
+ __pmLock_libpcp # the global libpcp mutex
+ ?init # local __pmInitLocks mutex
+ ?done # guarded by local __pmInitLocks mutex
+ ?__pmTPDKey # one-trip initialization then read-only
+ ?multi_init # guarded by __pmLock_libpcp mutex
+ ?multi_seen # guarded by __pmLock_libpcp mutex
+ ?hashctl # for lock debug tracing
+ ?__pmTPDKey # if don't have __thread support
+logconnect.o
+ done_default # guarded by __pmLock_libpcp mutex
+ timeout # guarded by __pmLock_libpcp mutex
+logcontrol.o
+logmeta.o
+logportmap.o
+ nlogports # single-threaded PM_SCOPE_LOGPORT
+ szlogport # single-threaded PM_SCOPE_LOGPORT
+ logport # single-threaded PM_SCOPE_LOGPORT
+ match # single-threaded PM_SCOPE_LOGPORT
+logutil.o
+ tbuf # __pmLogName deprecated by __pmLogName_r
+ compress_ctl # const
+ ?ncompress # const
+ __pmLogReads # diag counter, no atomic updates
+ pc_hc # guarded by __pmLock_libpcp mutex
+secureserver.o
+ secure_server # guarded by __pmLock_libpcp mutex
+secureconnect.o
+ common_callbacks # const
+ initialized # single-threaded
+optfetch.o
+ optcost # guarded by __pmLock_libpcp mutex
+p_auth.o
+p_creds.o
+p_desc.o
+pdubuf.o
+ buf_free # guarded by __pmLock_libpcp mutex
+ buf_pin # guarded by __pmLock_libpcp mutex
+ buf_pin_tail # guarded by __pmLock_libpcp mutex
+pdu.o
+ done_default # guarded by __pmLock_libpcp mutex
+ def_timeout # guarded by __pmLock_libpcp mutex
+ def_wait # guarded by __pmLock_libpcp mutex
+ pmDebug # set-once in main(), read-only elsewhere
+ ceiling # no unsafe side-effects
+ ?sigpipe_done # no unsafe side-effects
+ mypid # no unsafe side-effects
+ tbuf # __pmPDUTypeStr deprecated by __pmPDUTypeStr_r
+ __pmPDUCntIn # pointer to diag counters, no atomic updates
+ __pmPDUCntOut # pointer to diag counters, no atomic updates
+ inctrs # diag counters, no atomic updates
+ outctrs # diag counters, no atomic updates
+ maxsize # guarded by __pmLock_libpcp mutex
+p_error.o
+p_profile.o
+p_result.o
+profile.o
+p_text.o
+p_fetch.o
+p_instance.o
+p_lcontrol.o
+p_lrequest.o
+p_lstatus.o
+pmns.o
+ lineno # guarded by __pmLock_libpcp mutex
+ export # guarded by __pmLock_libpcp mutex
+ fin # guarded by __pmLock_libpcp mutex
+ first # guarded by __pmLock_libpcp mutex
+ fname # guarded by __pmLock_libpcp mutex
+ havePmLoadCall # guarded by __pmLock_libpcp mutex
+ last_mtim # guarded by __pmLock_libpcp mutex
+ last_pmns_location # guarded by __pmLock_libpcp mutex
+ linebuf # guarded by __pmLock_libpcp mutex
+ linep # guarded by __pmLock_libpcp mutex
+ lp # guarded by __pmLock_libpcp mutex
+ seen # guarded by __pmLock_libpcp mutex
+ seenpmid # guarded by __pmLock_libpcp mutex
+ tokbuf # guarded by __pmLock_libpcp mutex
+ tokpmid # guarded by __pmLock_libpcp mutex
+ useExtPMNS # guarded by __pmLock_libpcp mutex
+ repname # guarded by __pmLock_libpcp mutex
+ main_pmns # guarded by __pmLock_libpcp mutex
+ curr_pmns # guarded by __pmLock_libpcp mutex
+ locerr # no unsafe side-effects, see notes in pmns.c
+p_pmns.o
+p_profile.o
+p_result.o
+probe.o
+profile.o
+p_text.o
+rtime.o
+ ?wdays # const
+ ?months # const
+ ?ampm # const
+ int_tab # const struct {...} int_tab[] = {...}
+ ?numint # const
+ ?ampm # const (MinGW)
+ ?months # const (MinGW)
+ ?wdays # const (MinGW)
+ ?startend_relative_terms # const
+sortinst.o
+spec.o
+store.o
+stuffvalue.o
+tv.o
+tz.o
+ curzone # guarded by __pmLock_libpcp mutex
+ envtz # guarded by __pmLock_libpcp mutex
+ envtzlen # guarded by __pmLock_libpcp mutex
+ zone # guarded by __pmLock_libpcp mutex
+ nzone # guarded by __pmLock_libpcp mutex
+ savetz # guarded by __pmLock_libpcp mutex
+ savetzp # guarded by __pmLock_libpcp mutex
+ tzbuffer # guarded by __pmLock_libpcp mutex
+ ?wildabbr # const (MinGW)
+units.o
+ typename # const
+ abuf # pmAtomStr deprecated by pmAtomStr_r
+ tbuf # pmTypeStr deprecated by pmTypeStr_r
+ ubuf # pmUnitsStr deprecated by pmUnitsStr_r
+util.o
+ idbuf # pmIDStr deprecated by pmIDStr_r
+ indombuf # pmInDomStr deprecated by pmInDomStr_r
+ ebuf # pmEventFlagsStr deprecated by pmEventFlagsStr_r
+ nbuf # pmNumberStr deprecated by pmNumberStr_r
+ ?unknownVal # const, variable may be optimized away by gcc
+ debug_map # const
+ ?num_debug # const
+ pmState # no unsafe side-effects, see notes in util.c
+ pmProgname # no unsafe side-effects, see notes in util.c
+ filelog # guarded by __pmLock_libpcp mutex
+ nfilelog # guarded by __pmLock_libpcp mutex
+ dosyslog # guarded by __pmLock_libpcp mutex
+ done_exit # guarded by __pmLock_libpcp mutex
+ ferr # guarded by __pmLock_libpcp mutex
+ errtype # guarded by __pmLock_libpcp mutex
+ fptr # guarded by __pmLock_libpcp mutex
+ fname # guarded by __pmLock_libpcp mutex
+ msgsize # guarded by __pmLock_libpcp mutex
+ ?base # no unsafe side-effects, see notes in util.c
+ first # __pmEventType deprecated by __pmEventType_r
+ last # __pmEventType deprecated by __pmEventType_r
+ sum # __pmEventType deprecated by __pmEventType_r
+ ?bp # const
+ ?dp_h # const
+ ?dp_l # const
+?win32.o
+END # this is magic, DO NOT DELETE THIS LINE
+End-of-File
+
+for file in *.o
+do
+ case "$file"
+ in
+ '*.o')
+ echo "Error: no object files!! Need some drive-by make action?"
+ exit 1
+ ;;
+ esac
+
+ if grep "^?*$file\$" $tmp/ctl >/dev/null 2>&1
+ then
+ :
+ else
+ echo "$file: Error: object file not mentioned in control file"
+ touch $tmp/fail
+ fi
+done
+
+skip_file=false
+
+cat $tmp/ctl \
+| while read line
+do
+ if expr $line : '.*\.o$' >/dev/null # .o file
+ then
+ if [ -n "$obj" ]
+ then
+ if [ -s $tmp/out ]
+ then
+ # extras from the last object code file
+ sed <$tmp/out \
+ -e 's/^[^ ]* //' \
+ -e "s/^\(.\) \(.*\)/$obj: \1 \2 : Error: additional symbol/"
+ touch $tmp/fail
+ fi
+ fi
+ if [ "$line" != END ]
+ then
+ if [ -f $line ] # .o file rather than symbol name
+ then
+ # Need some nm special case logic ...
+ # for darwin
+ # + const data and text symbols both appear as "S", but
+ # the latter have .eh appended to the name
+ # + static arrays and some debug (?) symbols appear as
+ # "s", but the latter have _.NNN appended, or start
+ # with LC, or have .eh appended, or start with EH_
+ # + older versions insert get_pc_thunk symbols in all
+ # object files
+ # for MinGW
+ # + strip .bss and .data lines
+ # + strip .rdata and .eh_frame lines
+ # + external symbols tend to have "C" lines
+ # for FreeBSD
+ # + strip r __func__.NNN lines
+ #
+ skip_file=false
+ nm $line \
+ | sed -n >$tmp/out \
+ -e '/ S ___i686.get_pc_thunk.[bc]x/d' \
+ -e '/ [sS] .*\.eh$/d' \
+ -e '/ s .*_\.[0-9][0-9]*$/d' \
+ -e '/ s LC[0-9][0-9]*$/d' \
+ -e '/ s EH_/d' \
+ -e '/ b \.bss/d' \
+ -e '/ d \.data/d' \
+ -e '/ r \.rdata/d' \
+ -e '/ r \.eh_frame/d' \
+ -e '/ r __PRETTY_FUNCTION__.[0-9][0-9]*$/d' \
+ -e '/ r __func__.[0-9][0-9]*$/d' \
+ -e '/ r \.LC[0-9][0-9]*$/d' \
+ -e '/ C ___pmLogReads/d' \
+ -e '/ C ___pmNativeConfig/d' \
+ -e '/ C ___pmPDUCntIn/d' \
+ -e '/ C ___pmPDUCntOut/d' \
+ -e '/ C _pmProgname/d' \
+ -e '/ [dDbBCsSrR] /p'
+ obj=$line
+ else
+ case "$line"
+ in
+ secure*.o)
+ echo "$line: Info: security object file skipped, not configured"
+ skip_file=true
+ ;;
+ \?*)
+ skip_file=true
+ ;;
+ *)
+ echo "$line: Error: object file in control file but not found"
+ touch $tmp/fail
+ esac
+ fi
+ fi
+ continue
+ fi
+ $skip_file && continue
+ opt=`echo $line | sed -n -e 's/?.*/?/p'`
+ name=`echo $line | sed -e 's/?//'`
+ #debug# echo "obj=$obj type=$line opt=$opt"
+ #
+ # We accept the given symbol name with several decorations:
+ #
+ # - in any section type (bss data, whatever; as compilers can
+ # be fickle)
+ # - with or without a _ prefix
+ # - with or without a .NNN suffix (coming from function statics
+ # or optimizations)
+ #
+ sed <$tmp/out >$tmp/tmp \
+ -e "/ [dDbBCsSrR] $name\$/d" \
+ -e "/ [dDbBCsSrR] _$name\$/d" \
+ -e "/ [dDbBCsSrR] $name\.[0-9]*\$/d" \
+ -e "/ [dDbBCsSrR] _$name\.[0-9]*\$/d"
+ if cmp -s $tmp/out $tmp/tmp
+ then
+ if [ "$opt" != "?" ]
+ then
+ echo "$obj: $name: Warning: exceptioned symbol ($line) no longer present"
+ fi
+ else
+ mv $tmp/tmp $tmp/out
+ fi
+done
+
+[ ! -f $tmp/fail ] && sts=0 # success at last
diff --git a/src/libpcp/src/checksum.c b/src/libpcp/src/checksum.c
new file mode 100644
index 0000000..d614035
--- /dev/null
+++ b/src/libpcp/src/checksum.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * __pmCheckSum(FILE *f) - algorithm stolen from sum(1), changed from 16-bit
+ * to 32-bit
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+__int32_t
+__pmCheckSum(FILE *f)
+{
+ __int32_t sum = 0x19700520;
+ int c;
+
+ while ((c = fgetc(f)) != EOF) {
+ if (sum & 1)
+ sum = (sum >> 1) + 0x80000000;
+ else
+ sum >>= 1;
+ sum += c;
+ }
+ return sum;
+}
diff --git a/src/libpcp/src/config.c b/src/libpcp/src/config.c
new file mode 100644
index 0000000..34d78b8
--- /dev/null
+++ b/src/libpcp/src/config.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+#ifdef IS_MINGW
+/*
+ * Fix up the Windows path separator quirkiness - PCP code deals
+ * typically with forward-slash separators (i.e. if not passed in
+ * on the command line, but hard-coded), but only very little now.
+ * In addition, we also need to cater for native Windows programs
+ * versus the MinimalSYStem POSIX-alike shell (which translates a
+ * drive letter into a root filesystem entry for us). Yee-hah!
+ * NB: Only single drive letters allowed (Wikipedia says so)
+ */
+char *
+dos_native_path(char *path)
+{
+ char *p = path;
+
+ if (path[0] == '/' && isalpha((int)path[1]) && path[2] == '/') {
+ p[0] = tolower(p[1]);
+ p[1] = ':';
+ p += 2;
+ }
+ for (; *p; p++)
+ if (*p == '/') *p = '\\';
+ return path;
+}
+
+static int
+dos_absolute_path(char *path)
+{
+ return (isalpha((int)path[0]) && path[1] == ':' && path[2] == '\\');
+}
+
+static char *
+msys_native_path(char *path)
+{
+ char *p = path;
+
+ /* Only single drive letters allowed (Wikipedia says so) */
+ if (isalpha((int)path[0]) && path[1] == ':') {
+ p[1] = tolower(p[0]);
+ p[0] = '/';
+ p += 2;
+ }
+ for (; *p; p++) {
+ if (*p == '\\') *p = '/';
+ else *p = tolower(*p);
+ }
+ return path;
+}
+
+static char *
+dos_rewrite_path(char *var, char *val, int msys)
+{
+ char *p = (char *)rindex(var, '_');
+
+ if (p && (strcmp(p, "_PATH") == 0 || strcmp(p, "_DIR") == 0)) {
+ if (msys)
+ return msys_native_path(val);
+ return dos_native_path(val);
+ }
+ return NULL;
+}
+
+/*
+ * For native Win32 console tools, we need to translate the paths
+ * used in scripts to native paths with PCP_DIR prefix prepended.
+ *
+ * For Win32 MSYS shell usage, we need to translate the paths
+ * used in scripts to paths with PCP_DIR prefix prepended AND
+ * drive letter path mapping done AND posix-style separators.
+ *
+ * Choose which way to go based on our environment (SHELL).
+ */
+static int posix_style(void)
+{
+ char *s;
+ int sts;
+ PM_LOCK(__pmLock_libpcp);
+ s = getenv("SHELL");
+ sts = (s && strncmp(s, "/bin/", 5) == 0);
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+static void
+dos_formatter(char *var, char *prefix, char *val)
+{
+ char envbuf[MAXPATHLEN];
+ int msys = posix_style();
+
+ if (prefix && dos_rewrite_path(var, val, msys)) {
+ char *p = msys ? msys_native_path(prefix) : prefix;
+ snprintf(envbuf, sizeof(envbuf), "%s=%s%s", var, p, val);
+ }
+ else {
+ snprintf(envbuf, sizeof(envbuf), "%s=%s", var, val);
+ }
+ PM_LOCK(__pmLock_libpcp);
+ putenv(strdup(envbuf));
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+INTERN const __pmConfigCallback __pmNativeConfig = dos_formatter;
+char *__pmNativePath(char *path) { return dos_native_path(path); }
+int __pmPathSeparator() { return posix_style() ? '/' : '\\'; }
+int __pmAbsolutePath(char *path) { return posix_style() ? path[0] == '/' : dos_absolute_path(path); }
+#else
+char *__pmNativePath(char *path) { return path; }
+int __pmAbsolutePath(char *path) { return path[0] == '/'; }
+int __pmPathSeparator() { return '/'; }
+
+static void
+posix_formatter(char *var, char *prefix, char *val)
+{
+ /* +40 bytes for max PCP env variable name */
+ char envbuf[MAXPATHLEN+40];
+ char *vp;
+ char *vend;
+
+ snprintf(envbuf, sizeof(envbuf), "%s=", var);
+ vend = &val[strlen(val)-1];
+ if (val[0] == *vend && (val[0] == '\'' || val[0] == '"')) {
+ /*
+ * have quoted value like "gawk --posix" for $PCP_AWK_PROG ...
+ * strip quotes
+ */
+ vp = &val[1];
+ vend--;
+ }
+ else
+ vp = val;
+ strncat(envbuf, vp, vend-vp+1);
+ envbuf[strlen(var)+1+vend-vp+1+1] = '\0';
+
+ PM_LOCK(__pmLock_libpcp);
+ putenv(strdup(envbuf));
+ PM_UNLOCK(__pmLock_libpcp);
+ (void)prefix;
+}
+
+INTERN const __pmConfigCallback __pmNativeConfig = posix_formatter;
+#endif
+
+void
+__pmConfig(__pmConfigCallback formatter)
+{
+ /*
+ * Scan ${PCP_CONF-$PCP_DIR/etc/pcp.conf} and put all PCP config
+ * variables found therein into the environment.
+ */
+ FILE *fp;
+ char confpath[32];
+ char dir[MAXPATHLEN];
+ char var[MAXPATHLEN];
+ char *prefix;
+ char *conf;
+ char *val;
+ char *p;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ prefix = getenv("PCP_DIR");
+ if ((conf = getenv("PCP_CONF")) == NULL) {
+ strncpy(confpath, "/etc/pcp.conf", sizeof(confpath));
+ if (prefix == NULL)
+ conf = __pmNativePath(confpath);
+ else {
+ snprintf(dir, sizeof(dir),
+ "%s%s", prefix, __pmNativePath(confpath));
+ conf = dir;
+ }
+ }
+
+ if (access((const char *)conf, R_OK) < 0 ||
+ (fp = fopen(conf, "r")) == (FILE *)NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("FATAL PCP ERROR: could not open config file \"%s\" : %s\n",
+ conf, osstrerror_r(errmsg, sizeof(errmsg)));
+ pmprintf("You may need to set PCP_CONF or PCP_DIR in your environment.\n");
+ pmflush();
+ PM_UNLOCK(__pmLock_libpcp);
+ exit(1);
+ }
+
+ while (fgets(var, sizeof(var), fp) != NULL) {
+ if (var[0] == '#' || (p = strchr(var, '=')) == NULL)
+ continue;
+ *p = '\0';
+ val = p+1;
+ if ((p = strrchr(val, '\n')) != NULL)
+ *p = '\0';
+ if ((p = getenv(var)) != NULL)
+ val = p;
+ else
+ formatter(var, prefix, val);
+
+ if (pmDebug & DBG_TRACE_CONFIG)
+ fprintf(stderr, "pmGetConfig: (init) %s=%s\n", var, val);
+ }
+ fclose(fp);
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+char *
+pmGetConfig(const char *name)
+{
+ /*
+ * state controls one-trip initialization, and recursion guard
+ * for pathological failures in initialization
+ */
+ static int state = 0;
+ char *val;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (state == 0) {
+ state = 1;
+ PM_UNLOCK(__pmLock_libpcp);
+ __pmConfig(__pmNativeConfig);
+ PM_LOCK(__pmLock_libpcp);
+ state = 2;
+ }
+ else if (state == 1) {
+ /* recursion from error in __pmConfig() ... no value is possible */
+ PM_UNLOCK(__pmLock_libpcp);
+ if (pmDebug & DBG_TRACE_CONFIG)
+ fprintf(stderr, "pmGetConfig: %s= ... recursion error\n", name);
+ val = "";
+ return val;
+ }
+
+ if ((val = getenv(name)) == NULL) {
+ val = "";
+ }
+
+ if (pmDebug & DBG_TRACE_CONFIG)
+ fprintf(stderr, "pmGetConfig: %s=%s\n", name, val);
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return val;
+}
+
+/*
+ * Details of runtime features available in the built libpcp
+ */
+
+static const char *enabled(void) { return "true"; }
+static const char *disabled(void) { return "false"; }
+
+#define STRINGIFY(s) #s
+#define TO_STRING(s) STRINGIFY(s)
+static const char *pmapi_version(void) { return TO_STRING(PMAPI_VERSION); }
+static const char *pcp_version(void) { return PCP_VERSION; }
+#if defined(HAVE_SECURE_SOCKETS)
+#include "nss.h"
+#include "nspr.h"
+#include "sasl.h"
+static const char *nspr_version(void) { return PR_VERSION; }
+static const char *nss_version(void) { return NSS_VERSION; }
+static const char *sasl_version_string(void)
+{
+ return TO_STRING(SASL_VERSION_MAJOR.SASL_VERSION_MINOR.SASL_VERSION_STEP);
+}
+#endif
+
+static const char *
+ipv6_enabled(void)
+{
+#if defined(IS_LINUX)
+ return access("/proc/net/if_inet6", F_OK) == 0 ? enabled() : disabled();
+#else
+ return enabled();
+#endif
+}
+
+#ifdef PM_MULTI_THREAD
+#define MULTI_THREAD_ENABLED enabled
+#else
+#define MULTI_THREAD_ENABLED disabled
+#endif
+#ifdef PM_FAULT_INJECTION
+#define FAULT_INJECTION_ENABLED enabled
+#else
+#define FAULT_INJECTION_ENABLED disabled
+#endif
+#if defined(HAVE_SECURE_SOCKETS)
+#define SECURE_SOCKETS_ENABLED enabled
+#define AUTHENTICATION_ENABLED enabled
+#else
+#define SECURE_SOCKETS_ENABLED disabled
+#define AUTHENTICATION_ENABLED disabled
+#endif
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+#define UNIX_DOMAIN_SOCKETS_ENABLED enabled
+#else
+#define UNIX_DOMAIN_SOCKETS_ENABLED disabled
+#endif
+#if defined(HAVE_STATIC_PROBES)
+#define STATIC_PROBES_ENABLED enabled
+#else
+#define STATIC_PROBES_ENABLED disabled
+#endif
+#if defined(HAVE_SERVICE_DISCOVERY)
+#define SERVICE_DISCOVERY_ENABLED enabled
+#else
+#define SERVICE_DISCOVERY_ENABLED disabled
+#endif
+
+typedef const char *(*feature_detector)(void);
+static struct {
+ const char *feature;
+ feature_detector detector;
+} features[] = {
+ { "pcp_version", pcp_version },
+ { "pmapi_version", pmapi_version },
+#if defined(HAVE_SECURE_SOCKETS)
+ { "nss_version", nss_version },
+ { "nspr_version", nspr_version },
+ { "sasl_version", sasl_version_string },
+#endif
+ { "multi_threaded", MULTI_THREAD_ENABLED },
+ { "fault_injection", FAULT_INJECTION_ENABLED },
+ { "secure_sockets", SECURE_SOCKETS_ENABLED }, /* from pcp-3.7.x */
+ { "ipv6", ipv6_enabled },
+ { "authentication", AUTHENTICATION_ENABLED }, /* from pcp-3.8.x */
+ { "unix_domain_sockets",UNIX_DOMAIN_SOCKETS_ENABLED }, /* from pcp-3.8.2 */
+ { "static_probes", STATIC_PROBES_ENABLED }, /* from pcp-3.8.3 */
+ { "service_discovery", SERVICE_DISCOVERY_ENABLED }, /* from pcp-3.8.6 */
+};
+
+void
+__pmAPIConfig(__pmAPIConfigCallback formatter)
+{
+ int i;
+
+ for (i = 0; i < sizeof(features)/sizeof(features[0]); i++) {
+ const char *value = features[i].detector();
+ if (pmDebug & DBG_TRACE_CONFIG)
+ fprintf(stderr, "__pmAPIConfig: %s=%s\n",
+ features[i].feature, value);
+ formatter(features[i].feature, value);
+ }
+}
+
+const char *
+__pmGetAPIConfig(const char *name)
+{
+ int i;
+
+ for (i = 0; i < sizeof(features)/sizeof(features[0]); i++)
+ if (strcasecmp(name, features[i].feature) == 0)
+ return features[i].detector();
+ return NULL;
+}
diff --git a/src/libpcp/src/connect.c b/src/libpcp/src/connect.c
new file mode 100644
index 0000000..2a025dc
--- /dev/null
+++ b/src/libpcp/src/connect.c
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * Do not need ctxp->c_pmcd->pc_lock lock around __pmSendCreds() call,
+ * as the context has not been created, so no-one else could be using
+ * the context's fd.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/* MY_BUFLEN needs to big enough to hold "hostname port" */
+#define MY_BUFLEN (MAXHOSTNAMELEN+10)
+#define MY_VERSION "pmproxy-client 1\n"
+
+static int
+negotiate_proxy(int fd, const char *hostname, int port)
+{
+ char buf[MY_BUFLEN];
+ char *bp;
+ int ok = 0;
+
+ /*
+ * version negotiation (converse to pmproxy logic)
+ * __pmSend my client version message
+ * __pmRecv server version message
+ * __pmSend hostname and port
+ */
+
+ if (__pmSend(fd, MY_VERSION, strlen(MY_VERSION), 0) != strlen(MY_VERSION)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_WARNING,
+ "__pmConnectPMCD: send version string to pmproxy failed: %s\n",
+ pmErrStr_r(-neterror(), errmsg, sizeof(errmsg)));
+ return PM_ERR_IPC;
+ }
+ for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) {
+ if (__pmRecv(fd, bp, 1, 0) != 1) {
+ *bp = '\0';
+ bp = &buf[MY_BUFLEN];
+ break;
+ }
+ if (*bp == '\n' || *bp == '\r') {
+ *bp = '\0';
+ break;
+ }
+ }
+ if (bp < &buf[MY_BUFLEN]) {
+ if (strcmp(buf, "pmproxy-server 1") == 0)
+ ok = 1;
+ }
+
+ if (!ok) {
+ __pmNotifyErr(LOG_WARNING,
+ "__pmConnectPMCD: bad version string from pmproxy: \"%s\"\n",
+ buf);
+ return PM_ERR_IPC;
+ }
+
+ snprintf(buf, sizeof(buf), "%s %d\n", hostname, port);
+ if (__pmSend(fd, buf, strlen(buf), 0) != strlen(buf)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_WARNING,
+ "__pmConnectPMCD: send hostname+port string to pmproxy failed: %s'\n",
+ pmErrStr_r(-neterror(), errmsg, sizeof(errmsg)));
+ return PM_ERR_IPC;
+ }
+
+ return ok;
+}
+
+/*
+ * client connects to pmcd handshake
+ */
+static int
+__pmConnectHandshake(int fd, const char *hostname, int ctxflags, __pmHashCtl *attrs)
+{
+ __pmPDU *pb;
+ int ok;
+ int version;
+ int challenge;
+ int sts;
+ int pinpdu;
+
+ /* Expect an error PDU back from PMCD: ACK/NACK for connection */
+ pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ if (sts == PDU_ERROR) {
+ /*
+ * See comments in pmcd ... we actually get an extended error PDU
+ * from pmcd, of the form
+ *
+ * :----------:-----------:
+ * | status | challenge |
+ * :----------:-----------:
+ *
+ * For a good connection, status is 0, else a PCP error code;
+ * challenge contains server-side info (e.g. enabled features)
+ */
+ version = __pmDecodeXtendError(pb, &sts, &challenge);
+ if (version < 0) {
+ __pmUnpinPDUBuf(pb);
+ return version;
+ }
+ if (sts < 0) {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+
+ if (version == PDU_VERSION2) {
+ __pmPDUInfo pduinfo;
+ __pmVersionCred handshake;
+ int pduflags = 0;
+
+ pduinfo = __ntohpmPDUInfo(*(__pmPDUInfo *)&challenge);
+
+ if (pduinfo.features & PDU_FLAG_CREDS_REQD)
+ /*
+ * This is a mandatory connection feature - pmcd must be
+ * sent user credential information one way or another -
+ * i.e. via SASL2 authentication, or AF_UNIX peer creds.
+ */
+ pduflags |= PDU_FLAG_CREDS_REQD;
+
+ if (ctxflags) {
+ /*
+ * If an optional connection feature (e.g. encryption) is
+ * desired, the pmcd that we're talking to must advertise
+ * support for the feature. And if it did, the client in
+ * turn must request it be enabled (now, via pduflags).
+ */
+ if (ctxflags & (PM_CTXFLAG_SECURE|PM_CTXFLAG_RELAXED)) {
+ if (pduinfo.features & PDU_FLAG_SECURE) {
+ pduflags |= PDU_FLAG_SECURE;
+ /*
+ * Determine whether the server can send an ACK for a
+ * secure connection request. We can still connect
+ * whether it does or not, but we need to know the
+ * protocol.
+ */
+ if (pduinfo.features & PDU_FLAG_SECURE_ACK)
+ pduflags |= PDU_FLAG_SECURE_ACK;
+ } else if (ctxflags & PM_CTXFLAG_SECURE) {
+ __pmUnpinPDUBuf(pb);
+ return -EOPNOTSUPP;
+ }
+ }
+ if (ctxflags & PM_CTXFLAG_COMPRESS) {
+ if (pduinfo.features & PDU_FLAG_COMPRESS)
+ pduflags |= PDU_FLAG_COMPRESS;
+ else {
+ __pmUnpinPDUBuf(pb);
+ return -EOPNOTSUPP;
+ }
+ }
+ if (ctxflags & PM_CTXFLAG_AUTH) {
+ if (pduinfo.features & PDU_FLAG_AUTH)
+ pduflags |= PDU_FLAG_AUTH;
+ else {
+ __pmUnpinPDUBuf(pb);
+ return -EOPNOTSUPP;
+ }
+ }
+ }
+
+ /*
+ * Negotiate connection version and features (via creds PDU)
+ */
+ if ((ok = __pmSetVersionIPC(fd, version)) < 0) {
+ __pmUnpinPDUBuf(pb);
+ return ok;
+ }
+
+ memset(&handshake, 0, sizeof(handshake));
+ handshake.c_type = CVERSION;
+ handshake.c_version = PDU_VERSION;
+ handshake.c_flags = pduflags;
+
+ sts = __pmSendCreds(fd, (int)getpid(), 1, (__pmCred *)&handshake);
+
+ /*
+ * At this point we know caller wants to set channel options and
+ * pmcd supports them so go ahead and update the socket now (this
+ * completes the SSL handshake in encrypting mode, authentication
+ * via SASL, and/or enabling compression in NSS).
+ */
+ if (sts >= 0 && pduflags)
+ sts = __pmSecureClientHandshake(fd, pduflags, hostname, attrs);
+ }
+ else
+ sts = PM_ERR_IPC;
+ }
+ else if (sts != PM_ERR_TIMEOUT)
+ sts = PM_ERR_IPC;
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ return sts;
+}
+
+static int global_nports;
+static int *global_portlist;
+
+static void
+load_pmcd_ports(void)
+{
+ if (global_portlist == NULL) {
+ /* __pmPMCDAddPorts discovers at least one valid port, if it returns. */
+ global_nports = __pmPMCDAddPorts(&global_portlist, global_nports);
+ }
+}
+
+static void
+load_proxy_hostspec(pmHostSpec *proxy)
+{
+ char errmsg[PM_MAXERRMSGLEN];
+ char *envstr;
+
+ if ((envstr = getenv("PMPROXY_HOST")) != NULL) {
+ proxy->name = strdup(envstr);
+ if (proxy->name == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "__pmConnectPMCD: cannot save PMPROXY_HOST: %s\n",
+ pmErrStr_r(-oserror(), errmsg, sizeof(errmsg)));
+ }
+ else {
+ /*
+ *__pmProxyAddPorts discovers at least one valid port, if it
+ * returns.
+ */
+ proxy->nports = __pmProxyAddPorts(&proxy->ports, proxy->nports);
+ }
+ }
+}
+
+void
+__pmConnectGetPorts(pmHostSpec *host)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ load_pmcd_ports();
+ if (__pmAddHostPorts(host, global_portlist, global_nports) < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "__pmConnectGetPorts: portlist dup failed, "
+ "using default PMCD_PORT (%d)\n", SERVER_PORT);
+ host->ports[0] = SERVER_PORT;
+ host->nports = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+int
+__pmConnectPMCD(pmHostSpec *hosts, int nhosts, int ctxflags, __pmHashCtl *attrs)
+{
+ int sts = -1;
+ int fd = -1; /* Fd for socket connection to pmcd */
+ int *ports;
+ int nports;
+ int portIx;
+ int version = -1;
+ int proxyport;
+ pmHostSpec *proxyhost;
+
+ static int first_time = 1;
+ static pmHostSpec proxy;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (first_time) {
+ /*
+ * One-trip check for use of pmproxy(1) in lieu of pmcd(1),
+ * and to extract the optional environment variables ...
+ * PMCD_PORT, PMPROXY_HOST and PMPROXY_PORT.
+ * We also check for the presense of a certificate database
+ * and load it up if either a user or system (global) DB is
+ * found.
+ */
+ first_time = 0;
+ load_pmcd_ports();
+ load_proxy_hostspec(&proxy);
+ }
+
+ if (hosts[0].nports == 0) {
+ nports = global_nports;
+ ports = global_portlist;
+ }
+ else {
+ nports = hosts[0].nports;
+ ports = hosts[0].ports;
+ }
+
+ if (proxy.name == NULL && nhosts == 1) {
+ const char *name = (const char *)hosts[0].name;
+
+ /*
+ * no proxy, connecting directly to pmcd
+ */
+ PM_UNLOCK(__pmLock_libpcp);
+
+ sts = -1;
+ /* Try connecting via the local unix domain socket, if requested and supported. */
+ if (nports == PM_HOST_SPEC_NPORTS_LOCAL || nports == PM_HOST_SPEC_NPORTS_UNIX) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if ((fd = __pmAuxConnectPMCDUnixSocket(name)) >= 0) {
+ if ((sts = __pmConnectHandshake(fd, name, ctxflags, attrs)) < 0) {
+ __pmCloseSocket(fd);
+ }
+ else
+ sts = fd;
+ portIx = -1; /* no port */
+ }
+#endif
+ /*
+ * If the connection failed, or is not supported, and the protocol was 'local:',
+ * then try connecting to localhost via the default port(s).
+ */
+ if (sts < 0) {
+ if (nports == PM_HOST_SPEC_NPORTS_LOCAL) {
+ name = "localhost";
+ nports = global_nports;
+ ports = global_portlist;
+ sts = -1; /* keep trying */
+ }
+ else
+ sts = -2; /* no more connection attempts. */
+ }
+ }
+
+ /* If still not connected, try via the given host name and ports, if requested. */
+ if (sts == -1) {
+ for (portIx = 0; portIx < nports; portIx++) {
+ if ((fd = __pmAuxConnectPMCDPort(name, ports[portIx])) >= 0) {
+ if ((sts = __pmConnectHandshake(fd, name, ctxflags, attrs)) < 0) {
+ __pmCloseSocket(fd);
+ }
+ else
+ /* success */
+ break;
+ }
+ else
+ sts = fd;
+ }
+ }
+
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection port=",
+ hosts[0].name);
+ for (portIx = 0; portIx < nports; portIx++) {
+ if (portIx == 0) fprintf(stderr, "%d", ports[portIx]);
+ else fprintf(stderr, ",%d", ports[portIx]);
+ }
+ fprintf(stderr, " failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ return sts;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ if (portIx >= 0) {
+ fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection port=%d fd=%d PDU version=%u\n",
+ hosts[0].name, ports[portIx], fd, __pmVersionIPC(fd));
+ }
+ else {
+ fprintf(stderr, "__pmConnectPMCD(%s): pmcd connection path=%s fd=%d PDU version=%u\n",
+ hosts[0].name, name, fd, __pmVersionIPC(fd));
+ }
+ __pmPrintIPC();
+ }
+#endif
+
+ return fd;
+ }
+
+ /*
+ * connecting to pmproxy, and then to pmcd ... not a direct
+ * connection to pmcd
+ */
+ proxyhost = (nhosts > 1) ? &hosts[1] : &proxy;
+ proxyport = (proxyhost->nports > 0) ? proxyhost->ports[0] : PROXY_PORT;
+
+ for (portIx = 0; portIx < nports; portIx++) {
+ fd = __pmAuxConnectPMCDPort(proxyhost->name, proxyport);
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectPMCD(%s): proxy to %s port=%d failed: %s \n",
+ hosts[0].name, proxyhost->name, proxyport, pmErrStr_r(-neterror(), errmsg, sizeof(errmsg)));
+ }
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return fd;
+ }
+ if ((sts = version = negotiate_proxy(fd, hosts[0].name, ports[portIx])) < 0)
+ __pmCloseSocket(fd);
+ else if ((sts = __pmConnectHandshake(fd, proxyhost->name, ctxflags, attrs)) < 0)
+ __pmCloseSocket(fd);
+ else
+ /* success */
+ break;
+ }
+
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectPMCD(%s): proxy connection to %s port=",
+ hosts[0].name, proxyhost->name);
+ for (portIx = 0; portIx < nports; portIx++) {
+ if (portIx == 0) fprintf(stderr, "%d", ports[portIx]);
+ else fprintf(stderr, ",%d", ports[portIx]);
+ }
+ fprintf(stderr, " failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "__pmConnectPMCD(%s): proxy connection host=%s port=%d fd=%d version=%d\n",
+ hosts[0].name, proxyhost->name, ports[portIx], fd, version);
+ }
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return fd;
+}
diff --git a/src/libpcp/src/connectlocal.c b/src/libpcp/src/connectlocal.c
new file mode 100644
index 0000000..24663c7
--- /dev/null
+++ b/src/libpcp/src/connectlocal.c
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * atexit_installed is protected by the __pmLock_libpcp mutex.
+ *
+ * __pmSpecLocalPMDA() uses buffer[], but this routine is only called
+ * from main() in single-threaded apps like pminfo, pmprobe, pmval
+ * and pmevent ... so we can ignore any multi-threading issues,
+ * especially as buffer[] is only used on an error handling code path.
+ *
+ * dsotab[] and numdso are obviously of interest via calls to
+ * __pmLookupDSO(), EndLocalContext(), __pmConnectLocal() or
+ * __pmLocalPMDA().
+ *
+ * Within libpcp, __pmLookupDSO() is called _only_ for PM_CONTEXT_LOCAL
+ * and it is not called from outside libpcp. Local contexts are only
+ * supported for single-threaded applications in the scope
+ * PM_SCOPE_DSO_PMDA that is enforced in pmNewContext. Multi-threaded
+ * applications are not supported for local contexts, so we do not need
+ * additional concurrency control for __pmLookupDSO().
+ *
+ * The same arguments apply to EndLocalContext() and __pmConnectLocal().
+ *
+ * __pmLocalPMDA() is a mixed bag, sharing some of the justification from
+ * __pmSpecLocalPMDA() and some from __pmConnectLocal().
+ *
+ * Because __pmConnectLocal() is not going to be used in a multi-threaded
+ * environment, the call to the thread-unsafe dlerror() is OK.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <ctype.h>
+#include <sys/stat.h>
+
+static __pmDSO *dsotab;
+static int numdso = -1;
+
+static int
+build_dsotab(void)
+{
+ /*
+ * parse pmcd's config file extracting details from dso lines
+ *
+ * very little syntactic checking here ... pmcd(1) does that job
+ * nicely and even if we get confused, the worst thing that happens
+ * is we don't include one or more of the DSO PMDAs in dsotab[]
+ *
+ * lines for DSO PMDAs generally look like this ...
+ * Name Domain Type Init Routine Path
+ * mmv 70 dso mmv_init /var/lib/pcp/pmdas/mmv/pmda_mmv.so
+ *
+ */
+ char configFileName[MAXPATHLEN];
+ FILE *configFile;
+ char *config;
+ char *p;
+ char *q;
+ struct stat sbuf;
+ int lineno = 1;
+ int domain;
+ char *init;
+ char *name;
+ char peekc;
+
+ numdso = 0;
+ dsotab = NULL;
+
+ strcpy(configFileName, pmGetConfig("PCP_PMCDCONF_PATH"));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "build_dsotab: parsing %s\n", configFileName);
+ }
+#endif
+ if (stat(configFileName, &sbuf) < 0) {
+ return -oserror();
+ }
+ configFile = fopen(configFileName, "r");
+ if (configFile == NULL) {
+ return -oserror();
+ }
+ if ((config = malloc(sbuf.st_size+1)) == NULL) {
+ __pmNoMem("build_dsotbl:", sbuf.st_size+1, PM_RECOV_ERR);
+ fclose(configFile);
+ return -oserror();
+ }
+ if (fread(config, 1, sbuf.st_size, configFile) != sbuf.st_size) {
+ fclose(configFile);
+ free(config);
+ return -oserror();
+ }
+ config[sbuf.st_size] = '\0';
+
+ p = config;
+ while (*p != '\0') {
+ /* each time through here we're at the start of a new line */
+ if (*p == '#')
+ goto eatline;
+ if (strncmp(p, "pmcd", 4) == 0) {
+ /*
+ * the pmcd PMDA is an exception ... it makes reference to
+ * symbols in pmcd, and only makes sense when attached to the
+ * pmcd process, so we skip this one
+ */
+ goto eatline;
+ }
+ /* skip the PMDA's name */
+ while (*p != '\0' && *p != '\n' && !isspace((int)*p))
+ p++;
+ while (*p != '\0' && *p != '\n' && isspace((int)*p))
+ p++;
+ /* extract domain number */
+ domain = (int)strtol(p, &q, 10);
+ p = q;
+ while (*p != '\0' && *p != '\n' && isspace((int)*p))
+ p++;
+ /* only interested if the type is "dso" */
+ if (strncmp(p, "dso", 3) != 0)
+ goto eatline;
+ p += 3;
+ while (*p != '\0' && *p != '\n' && isspace((int)*p))
+ p++;
+ /* up to the init routine name */
+ init = p;
+ while (*p != '\0' && *p != '\n' && !isspace((int)*p))
+ p++;
+ *p = '\0';
+ p++;
+ while (*p != '\0' && *p != '\n' && isspace((int)*p))
+ p++;
+ /* up to the dso pathname */
+ name = p;
+ while (*p != '\0' && *p != '\n' && !isspace((int)*p))
+ p++;
+ peekc = *p;
+ *p = '\0';
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "[%d] domain=%d, name=%s, init=%s\n", lineno, domain, name, init);
+ }
+#endif
+ /*
+ * a little bit recursive if we got here via __pmLocalPMDA(),
+ * but numdso has been set correctly, so this is OK
+ */
+ __pmLocalPMDA(PM_LOCAL_ADD, domain, name, init);
+ *p = peekc;
+
+eatline:
+ while (*p != '\0' && *p != '\n')
+ p++;
+ if (*p == '\n') {
+ lineno++;
+ p++;
+ }
+ }
+
+ fclose(configFile);
+ free(config);
+ return 0;
+}
+
+static int
+build_dsoattrs(pmdaInterface *dispatch, __pmHashCtl *attrs)
+{
+ __pmHashNode *node;
+ char name[32];
+ char *namep;
+ int sts = 0;
+
+#ifdef HAVE_GETUID
+ snprintf(name, sizeof(name), "%u", getuid());
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_USERID, namep, attrs);
+#endif
+
+#ifdef HAVE_GETGID
+ snprintf(name, sizeof(name), "%u", getgid());
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs);
+#endif
+
+ snprintf(name, sizeof(name), "%u", getpid());
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_PROCESSID, namep, attrs);
+
+ if (dispatch->version.six.attribute != NULL) {
+ for (node = __pmHashWalk(attrs, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) {
+ if ((sts = dispatch->version.six.attribute(
+ 0, node->key, node->data,
+ node->data ? strlen(node->data)+1 : 0,
+ dispatch->version.six.ext)) < 0)
+ break;
+ }
+ }
+ return sts;
+}
+
+#if defined(HAVE_DLFCN_H)
+#include <dlfcn.h>
+#endif
+
+/*
+ * As of PCP version 2.1, we're no longer searching for DSO's;
+ * pmcd's config file should have full paths to each of 'em.
+ */
+const char *
+__pmFindPMDA(const char *name)
+{
+ return (access(name, F_OK) == 0) ? name : NULL;
+}
+
+__pmDSO *
+__pmLookupDSO(int domain)
+{
+ int i;
+ for (i = 0; i < numdso; i++) {
+ if (dsotab[i].domain == domain && dsotab[i].handle != NULL)
+ return &dsotab[i];
+ }
+ return NULL;
+}
+
+static void
+EndLocalContext(void)
+{
+ int i;
+ __pmDSO *dp;
+ int ctx = pmWhichContext();
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /*
+ * Local context requires single-threaded applications
+ * ... should not really get here, so do nothing!
+ */
+ return;
+
+ for (i = 0; i < numdso; i++) {
+ dp = &dsotab[i];
+ if (dp->domain != -1 &&
+ dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 &&
+ dp->dispatch.version.four.ext->e_endCallBack != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "NotifyEndLocalContext: DSO PMDA %s (%d) notified of context %d close\n",
+ dp->name, dp->domain, ctx);
+ }
+#endif
+ (*(dp->dispatch.version.four.ext->e_endCallBack))(ctx);
+ }
+ }
+}
+
+/*
+ * Note order is significant here. Also EndLocalContext can be
+ * called from atexit handler (if previously registered), but if
+ * caller invokes shutdown beforehand, thats OK; EndLocalContext
+ * will safely do nothing on the second call.
+ */
+int
+__pmShutdownLocal(void)
+{
+ /* Call through to any PMDA termination callbacks */
+ EndLocalContext();
+
+ /* dso close and free up local memory allocations */
+ return __pmLocalPMDA(PM_LOCAL_CLEAR, 0, NULL, NULL);
+}
+
+int
+__pmConnectLocal(__pmHashCtl *attrs)
+{
+ int i;
+ __pmDSO *dp;
+ char pathbuf[MAXPATHLEN];
+ const char *path;
+#if defined(HAVE_DLOPEN)
+ unsigned int challenge;
+ void (*initp)(pmdaInterface *);
+#ifdef HAVE_ATEXIT
+ static int atexit_installed = 0;
+#endif
+#endif
+
+ if (numdso == -1) {
+ int sts;
+ sts = build_dsotab();
+ if (sts < 0) return sts;
+ }
+
+ for (i = 0; i < numdso; i++) {
+ dp = &dsotab[i];
+ if (dp->domain == -1 || dp->handle != NULL)
+ continue;
+ /*
+ * __pmLocalPMDA() means the path to the DSO may be something
+ * other than relative to $PCP_PMDAS_DIR ... need to try both
+ * options and also with and without DSO_SUFFIX (so, dll, etc)
+ */
+ snprintf(pathbuf, sizeof(pathbuf), "%s%c%s",
+ pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name);
+ if ((path = __pmFindPMDA(pathbuf)) == NULL) {
+ snprintf(pathbuf, sizeof(pathbuf), "%s%c%s.%s",
+ pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name, DSO_SUFFIX);
+ if ((path = __pmFindPMDA(pathbuf)) == NULL) {
+ if ((path = __pmFindPMDA(dp->name)) == NULL) {
+ snprintf(pathbuf, sizeof(pathbuf), "%s.%s", dp->name, DSO_SUFFIX);
+ if ((path = __pmFindPMDA(pathbuf)) == NULL) {
+ pmprintf("__pmConnectLocal: Warning: cannot find DSO at \"%s\" or \"%s\"\n",
+ pathbuf, dp->name);
+ pmflush();
+ dp->domain = -1;
+ dp->handle = NULL;
+ continue;
+ }
+ }
+ }
+ }
+#if defined(HAVE_DLOPEN)
+ dp->handle = dlopen(path, RTLD_NOW);
+ if (dp->handle == NULL) {
+ pmprintf("__pmConnectLocal: Warning: error attaching DSO "
+ "\"%s\"\n%s\n\n", path, dlerror());
+ pmflush();
+ dp->domain = -1;
+ }
+#else /* ! HAVE_DLOPEN */
+ dp->handle = NULL;
+ pmprintf("__pmConnectLocal: Warning: error attaching DSO \"%s\"\n",
+ path);
+ pmprintf("No dynamic DSO/DLL support on this platform\n\n");
+ pmflush();
+ dp->domain = -1;
+#endif
+
+ if (dp->handle == NULL)
+ continue;
+
+#if defined(HAVE_DLOPEN)
+ /*
+ * rest of this only makes sense if the dlopen() worked
+ */
+ if (dp->init == NULL)
+ initp = NULL;
+ else
+ initp = (void (*)(pmdaInterface *))dlsym(dp->handle, dp->init);
+ if (initp == NULL) {
+ pmprintf("__pmConnectLocal: Warning: couldn't find init function "
+ "\"%s\" in DSO \"%s\"\n", dp->init, path);
+ pmflush();
+ dlclose(dp->handle);
+ dp->domain = -1;
+ continue;
+ }
+
+ /*
+ * Pass in the expected domain id.
+ * The PMDA initialization routine can (a) ignore it, (b) check it
+ * is the expected value, or (c) self-adapt.
+ */
+ dp->dispatch.domain = dp->domain;
+
+ /*
+ * the PMDA interface / PMAPI version discovery as a "challenge" ...
+ * for pmda_interface it is all the bits being set,
+ * for pmapi_version it is the complement of the one you are using now
+ */
+ challenge = 0xff;
+ dp->dispatch.comm.pmda_interface = challenge;
+ dp->dispatch.comm.pmapi_version = ~PMAPI_VERSION;
+ dp->dispatch.comm.flags = 0;
+ dp->dispatch.status = 0;
+
+ (*initp)(&dp->dispatch);
+
+ if (dp->dispatch.status != 0) {
+ /* initialization failed for some reason */
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmConnectLocal: Warning: initialization "
+ "routine \"%s\" failed in DSO \"%s\": %s\n",
+ dp->init, path, pmErrStr_r(dp->dispatch.status, errmsg, sizeof(errmsg)));
+ pmflush();
+ dlclose(dp->handle);
+ dp->domain = -1;
+ }
+ else {
+ if (dp->dispatch.comm.pmda_interface < PMDA_INTERFACE_2 ||
+ dp->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) {
+ pmprintf("__pmConnectLocal: Error: Unknown PMDA interface "
+ "version %d in \"%s\" DSO\n",
+ dp->dispatch.comm.pmda_interface, path);
+ pmflush();
+ dlclose(dp->handle);
+ dp->domain = -1;
+ }
+ else if (dp->dispatch.comm.pmapi_version != PMAPI_VERSION_2) {
+ pmprintf("__pmConnectLocal: Error: Unknown PMAPI version %d "
+ "in \"%s\" DSO\n",
+ dp->dispatch.comm.pmapi_version, path);
+ pmflush();
+ dlclose(dp->handle);
+ dp->domain = -1;
+ }
+ else if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_6 &&
+ (dp->dispatch.comm.flags & PDU_FLAG_AUTH) != 0) {
+ /* Agent wants to know about connection attributes */
+ build_dsoattrs(&dp->dispatch, attrs);
+ }
+ }
+#ifdef HAVE_ATEXIT
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 &&
+ atexit_installed == 0) {
+ /* install end of local context handler */
+ atexit(EndLocalContext);
+ atexit_installed = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+#endif
+#endif /* HAVE_DLOPEN */
+ }
+
+ return 0;
+}
+
+int
+__pmLocalPMDA(int op, int domain, const char *name, const char *init)
+{
+ int sts = 0;
+ int i;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "__pmLocalPMDA(op=");
+ if (op == PM_LOCAL_ADD) fprintf(stderr, "ADD");
+ else if (op == PM_LOCAL_DEL) fprintf(stderr, "DEL");
+ else if (op == PM_LOCAL_CLEAR) fprintf(stderr, "CLEAR");
+ else fprintf(stderr, "%d ???", op);
+ fprintf(stderr, ", domain=%d, name=%s, init=%s)\n", domain, name, init);
+ }
+#endif
+
+ if (numdso == -1) {
+ if (op != PM_LOCAL_CLEAR)
+ if ((sts = build_dsotab()) < 0)
+ return sts;
+ }
+
+ switch (op) {
+ case PM_LOCAL_ADD:
+ if ((dsotab = (__pmDSO *)realloc(dsotab, (numdso+1)*sizeof(__pmDSO))) == NULL) {
+ __pmNoMem("__pmLocalPMDA realloc", (numdso+1)*sizeof(__pmDSO), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ dsotab[numdso].domain = domain;
+ if (name == NULL) {
+ /* odd, will fail later at dlopen */
+ dsotab[numdso].name = NULL;
+ }
+ else {
+ if ((dsotab[numdso].name = strdup(name)) == NULL) {
+ sts = -oserror();
+ __pmNoMem("__pmLocalPMDA name", strlen(name)+1, PM_RECOV_ERR);
+ return sts;
+ }
+ }
+ if (init == NULL) {
+ /* odd, will fail later at initialization call */
+ dsotab[numdso].init = NULL;
+ }
+ else {
+ if ((dsotab[numdso].init = strdup(init)) == NULL) {
+ sts = -oserror();
+ __pmNoMem("__pmLocalPMDA init", strlen(init)+1, PM_RECOV_ERR);
+ return sts;
+ }
+ }
+ dsotab[numdso].handle = NULL;
+ numdso++;
+ break;
+
+ case PM_LOCAL_DEL:
+ sts = PM_ERR_INDOM;
+ for (i = 0; i < numdso; i++) {
+ if ((domain != -1 && dsotab[i].domain == domain) ||
+ (name != NULL && strcmp(dsotab[i].name, name) == 0)) {
+ if (dsotab[i].handle) {
+ dlclose(dsotab[i].handle);
+ dsotab[i].handle = NULL;
+ }
+ dsotab[i].domain = -1;
+ sts = 0;
+ }
+ }
+ break;
+
+ case PM_LOCAL_CLEAR:
+ for (i = 0; i < numdso; i++) {
+ free(dsotab[i].name);
+ free(dsotab[i].init);
+ if (dsotab[i].handle)
+ dlclose(dsotab[i].handle);
+ }
+ free(dsotab);
+ dsotab = NULL;
+ numdso = 0;
+ break;
+
+ default:
+ sts = PM_ERR_CONV;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ if (sts != 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmLocalPMDA -> %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ fprintf(stderr, "Local Context PMDA Table");
+ if (numdso == 0)
+ fprintf(stderr, " ... empty");
+ fputc('\n', stderr);
+ for (i = 0; i < numdso; i++) {
+ fprintf(stderr, PRINTF_P_PFX "%p [%d] domain=%d name=%s init=%s handle=" PRINTF_P_PFX "%p\n",
+ &dsotab[i], i, dsotab[i].domain, dsotab[i].name, dsotab[i].init, dsotab[i].handle);
+ }
+ }
+#endif
+
+ return sts;
+}
+
+/*
+ * Parse a command line string that encodes arguments to __pmLocalPMDA(),
+ * then call __pmLocalPMDA().
+ *
+ * The syntax for the string is 1 to 4 fields separated by colons:
+ * - op ("add" for add, "del" for delete, "clear" for clear)
+ * - domain (PMDA's PMD)
+ * - path (path to DSO PMDA)
+ * - init (name of DSO's initialization routine)
+ */
+char *
+__pmSpecLocalPMDA(const char *spec)
+{
+ int op;
+ int domain = -1;
+ char *name = NULL;
+ char *init = NULL;
+ int sts;
+ char *arg;
+ char *sbuf;
+ char *ap;
+
+ if ((arg = sbuf = strdup(spec)) == NULL) {
+ sts = -oserror();
+ __pmNoMem("__pmSpecLocalPMDA dup spec", strlen(spec)+1, PM_RECOV_ERR);
+ return "strdup failed";
+ }
+ if (strncmp(arg, "add", 3) == 0) {
+ op = PM_LOCAL_ADD;
+ ap = &arg[3];
+ }
+ else if (strncmp(arg, "del", 3) == 0) {
+ op = PM_LOCAL_DEL;
+ ap = &arg[3];
+ }
+ else if (strncmp(arg, "clear", 5) == 0) {
+ op = PM_LOCAL_CLEAR;
+ ap = &arg[5];
+ if (*ap == '\0')
+ goto doit;
+ else {
+ free(sbuf);
+ return "unexpected text after clear op in spec";
+ }
+ }
+ else {
+ free(sbuf);
+ return "bad op in spec";
+ }
+
+ if (*ap != ',') {
+ free(sbuf);
+ return "expected , after op in spec";
+ }
+ /* ap-> , after add or del */
+ arg = ++ap;
+ if (*ap == '\0') {
+ free(sbuf);
+ return "missing domain in spec";
+ }
+ else if (*ap != ',') {
+ /* ap-> domain */
+ domain = (int)strtol(arg, &ap, 10);
+ if ((*ap != ',' && *ap != '\0') || domain < 0 || domain > 510) {
+ free(sbuf);
+ return "bad domain in spec";
+ }
+ if (*ap != '\0')
+ /* skip , after domain */
+ ap++;
+ }
+ else {
+ if (op != PM_LOCAL_DEL) {
+ /* found ,, where ,domain, expected */
+ free(sbuf);
+ return "missing domain in spec";
+ }
+ ap++;
+ }
+ /* ap -> char after , following domain */
+ if (*ap == ',') {
+ /* no path, could have init (not useful but possible!) */
+ ap++;
+ if (*ap != '\0')
+ init = ap;
+ }
+ else if (*ap != '\0') {
+ /* have path and possibly init */
+ name = ap;
+ while (*ap != ',' && *ap != '\0')
+ ap++;
+ if (*ap == ',') {
+ *ap++ = '\0';
+ if (*ap != '\0')
+ init = ap;
+ else {
+ if (op != PM_LOCAL_DEL) {
+ /* found end of string where init-routine expected */
+ free(sbuf);
+ return "missing init-routine in spec";
+ }
+ }
+ }
+ else {
+ if (op != PM_LOCAL_DEL) {
+ /* found end of string where init-routine expected */
+ free(sbuf);
+ return "missing init-routine in spec";
+ }
+ }
+ }
+ else {
+ if (op != PM_LOCAL_DEL) {
+ /* found end of string where path expected */
+ free(sbuf);
+ return "missing dso-path in spec";
+ }
+ }
+
+ if (domain == -1 && name == NULL) {
+ free(sbuf);
+ return "missing domain and dso-path in spec";
+ }
+
+doit:
+ sts = __pmLocalPMDA(op, domain, name, init);
+ if (sts < 0) {
+ /* see thread-safe note at the head of this file */
+ static char buffer[256];
+ char errmsg[PM_MAXERRMSGLEN];
+ snprintf(buffer, sizeof(buffer), "__pmLocalPMDA: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ free(sbuf);
+ return buffer;
+ }
+
+ free(sbuf);
+ return NULL;
+}
diff --git a/src/libpcp/src/context.c b/src/libpcp/src/context.c
new file mode 100644
index 0000000..3f47d8e
--- /dev/null
+++ b/src/libpcp/src/context.c
@@ -0,0 +1,1038 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2007-2008 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2002,2004,2006,2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * curcontext needs to be thread-private
+ *
+ * contexts[] et al and def_backoff[] et al are protected from changes
+ * using the libpcp lock
+ *
+ * The actual contexts (__pmContext) are protected by the (recursive)
+ * c_lock mutex which is intialized in pmNewContext() and pmDupContext(),
+ * then locked in __pmHandleToPtr() ... it is the responsibility of all
+ * __pmHandleToPtr() callers to call PM_UNLOCK(ctxp->c_lock) when they
+ * are finished with the context.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include <string.h>
+
+static __pmContext **contexts; /* array of context ptrs */
+static int contexts_len; /* number of contexts */
+
+#ifdef PM_MULTI_THREAD
+#ifdef HAVE___THREAD
+/* using a gcc construct here to make curcontext thread-private */
+static __thread int curcontext = PM_CONTEXT_UNDEF; /* current context */
+#endif
+#else
+static int curcontext = PM_CONTEXT_UNDEF; /* current context */
+#endif
+
+static int n_backoff;
+static int def_backoff[] = {5, 10, 20, 40, 80};
+static int *backoff;
+
+static void
+waitawhile(__pmPMCDCtl *ctl)
+{
+ /*
+ * after failure, compute delay before trying again ...
+ */
+ PM_LOCK(__pmLock_libpcp);
+ if (n_backoff == 0) {
+ char *q;
+ /* first time ... try for PMCD_RECONNECT_TIMEOUT from env */
+ if ((q = getenv("PMCD_RECONNECT_TIMEOUT")) != NULL) {
+ char *pend;
+ char *p;
+ int val;
+
+ for (p = q; *p != '\0'; ) {
+ val = (int)strtol(p, &pend, 10);
+ if (val <= 0 || (*pend != ',' && *pend != '\0')) {
+ __pmNotifyErr(LOG_WARNING,
+ "pmReconnectContext: ignored bad PMCD_RECONNECT_TIMEOUT = '%s'\n",
+ q);
+ n_backoff = 0;
+ if (backoff != NULL)
+ free(backoff);
+ break;
+ }
+ if ((backoff = (int *)realloc(backoff, (n_backoff+1) * sizeof(backoff[0]))) == NULL) {
+ __pmNoMem("pmReconnectContext", (n_backoff+1) * sizeof(backoff[0]), PM_FATAL_ERR);
+ }
+ backoff[n_backoff++] = val;
+ if (*pend == '\0')
+ break;
+ p = &pend[1];
+ }
+ }
+ if (n_backoff == 0) {
+ /* use default */
+ n_backoff = 5;
+ backoff = def_backoff;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ if (ctl->pc_timeout == 0)
+ ctl->pc_timeout = 1;
+ else if (ctl->pc_timeout < n_backoff)
+ ctl->pc_timeout++;
+ ctl->pc_again = time(NULL) + backoff[ctl->pc_timeout-1];
+}
+
+/*
+ * On success, context is locked and caller should unlock it
+ */
+__pmContext *
+__pmHandleToPtr(int handle)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (handle < 0 || handle >= contexts_len ||
+ contexts[handle]->c_type == PM_CONTEXT_FREE) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ else {
+ __pmContext *sts;
+ sts = contexts[handle];
+ PM_UNLOCK(__pmLock_libpcp);
+ PM_LOCK(sts->c_lock);
+ return sts;
+ }
+}
+
+int
+__pmPtrToHandle(__pmContext *ctxp)
+{
+ int i;
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ for (i = 0; i < contexts_len; i++) {
+ if (ctxp == contexts[i]) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return i;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return PM_CONTEXT_UNDEF;
+}
+
+/*
+ * Determine the hostname associated with the given context.
+ */
+char *
+pmGetContextHostName_r(int ctxid, char *buf, int buflen)
+{
+ __pmContext *ctxp;
+ char *name;
+ pmID pmid;
+ pmResult *resp;
+ int original;
+ int sts;
+
+ buf[0] = '\0';
+
+ if ((ctxp = __pmHandleToPtr(ctxid)) != NULL) {
+ switch (ctxp->c_type) {
+ case PM_CONTEXT_HOST:
+ /*
+ * Try and establish the hostname from PMCD (possibly remote).
+ * Do not nest the successive actions. That way, if any one of
+ * them fails, we take the default.
+ * Note: we must *temporarily* switch context (see pmUseContext)
+ * in the host case, then switch back afterward. We already hold
+ * locks and have validated the context pointer, so we do a mini
+ * context switch, then switch back.
+ */
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmGetContextHostName_r context(%d) -> 0\n", ctxid);
+ original = PM_TPD(curcontext);
+ PM_TPD(curcontext) = ctxid;
+
+ name = "pmcd.hostname";
+ sts = pmLookupName(1, &name, &pmid);
+ if (sts >= 0)
+ sts = pmFetch(1, &pmid, &resp);
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmGetContextHostName_r reset(%d) -> 0\n", original);
+
+ PM_TPD(curcontext) = original;
+ if (sts >= 0) {
+ if (resp->vset[0]->numval > 0) { /* pmcd.hostname present */
+ strncpy(buf, resp->vset[0]->vlist[0].value.pval->vbuf, buflen);
+ pmFreeResult(resp);
+ break;
+ }
+ pmFreeResult(resp);
+ /* FALLTHROUGH */
+ }
+
+ /*
+ * We could not get the hostname from PMCD. If the name in the
+ * context structure is a filesystem path (AF_UNIX address) or
+ * 'localhost', then use gethostname(). Otherwise, use the name
+ * from the context structure.
+ */
+ name = ctxp->c_pmcd->pc_hosts[0].name;
+ if (!name || name[0] == __pmPathSeparator() || /* AF_UNIX */
+ (strncmp(name, "localhost", 9) == 0)) /* localhost[46] */
+ gethostname(buf, buflen);
+ else
+ strncpy(buf, name, buflen-1);
+ break;
+
+ case PM_CONTEXT_LOCAL:
+ gethostname(buf, buflen);
+ break;
+
+ case PM_CONTEXT_ARCHIVE:
+ strncpy(buf, ctxp->c_archctl->ac_log->l_label.ill_hostname, buflen-1);
+ break;
+ }
+
+ buf[buflen-1] = '\0';
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return buf;
+}
+
+/*
+ * Backward-compatibility interface, non-thread-safe variant.
+ */
+const char *
+pmGetContextHostName(int ctxid)
+{
+ static char hostbuf[MAXHOSTNAMELEN];
+ return (const char *)pmGetContextHostName_r(ctxid, hostbuf, (int)sizeof(hostbuf));
+}
+
+int
+pmWhichContext(void)
+{
+ /*
+ * return curcontext, provided it is defined
+ */
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (PM_TPD(curcontext) > PM_CONTEXT_UNDEF)
+ sts = PM_TPD(curcontext);
+ else
+ sts = PM_ERR_NOCONTEXT;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmWhichContext() -> %d, cur=%d\n",
+ sts, PM_TPD(curcontext));
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmConvertTimeout(int timeo)
+{
+ int tout_msec;
+ const struct timeval *tv;
+
+ switch (timeo) {
+ case TIMEOUT_NEVER:
+ tout_msec = -1;
+ break;
+
+ case TIMEOUT_DEFAULT:
+ tv = __pmDefaultRequestTimeout();
+ tout_msec = tv->tv_sec *1000 + tv->tv_usec / 1000;
+ break;
+
+ case TIMEOUT_CONNECT:
+ tv = __pmConnectTimeout();
+ tout_msec = tv->tv_sec *1000 + tv->tv_usec / 1000;
+ break;
+
+ default:
+ tout_msec = timeo * 1000;
+ break;
+ }
+
+ return tout_msec;
+}
+
+#ifdef PM_MULTI_THREAD
+static void
+__pmInitContextLock(pthread_mutex_t *lock)
+{
+ pthread_mutexattr_t attr;
+ int sts;
+ char errmsg[PM_MAXERRMSGLEN];
+
+ /*
+ * Need context lock to be recursive as we sometimes call
+ * __pmHandleToPtr() while the current context is already
+ * locked
+ */
+ if ((sts = pthread_mutexattr_init(&attr)) != 0) {
+ pmErrStr_r(-sts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmNewContext: "
+ "context=%d lock pthread_mutexattr_init failed: %s",
+ contexts_len-1, errmsg);
+ exit(4);
+ }
+ if ((sts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) {
+ pmErrStr_r(-sts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmNewContext: "
+ "context=%d lock pthread_mutexattr_settype failed: %s",
+ contexts_len-1, errmsg);
+ exit(4);
+ }
+ if ((sts = pthread_mutex_init(lock, &attr)) != 0) {
+ pmErrStr_r(-sts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmNewContext: "
+ "context=%d lock pthread_mutex_init failed: %s",
+ contexts_len-1, errmsg);
+ exit(4);
+ }
+}
+
+static void
+__pmInitChannelLock(pthread_mutex_t *lock)
+{
+ int sts;
+ char errmsg[PM_MAXERRMSGLEN];
+
+ if ((sts = pthread_mutex_init(lock, NULL)) != 0) {
+ pmErrStr_r(-sts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmNewContext: "
+ "context=%d pmcd channel lock pthread_mutex_init failed: %s",
+ contexts_len, errmsg);
+ exit(4);
+ }
+}
+#else
+#define __pmInitContextLock(x) do { } while (1)
+#define __pmInitChannelLock(x) do { } while (1)
+#endif
+
+static int
+ctxflags(__pmHashCtl *attrs)
+{
+ int flags = 0;
+ char *secure = NULL;
+ __pmHashNode *node;
+
+ if ((node = __pmHashSearch(PCP_ATTR_PROTOCOL, attrs)) != NULL) {
+ if (strcmp((char *)node->data, "pcps") == 0) {
+ if ((node = __pmHashSearch(PCP_ATTR_SECURE, attrs)) != NULL)
+ secure = (char *)node->data;
+ else
+ secure = "enforce";
+ }
+ }
+
+ if (!secure)
+ secure = getenv("PCP_SECURE_SOCKETS");
+
+ if (secure) {
+ if (secure[0] == '\0' ||
+ (strcmp(secure, "1")) == 0 ||
+ (strcmp(secure, "enforce")) == 0) {
+ flags |= PM_CTXFLAG_SECURE;
+ } else if (strcmp(secure, "relaxed") == 0) {
+ flags |= PM_CTXFLAG_RELAXED;
+ }
+ }
+
+ if (__pmHashSearch(PCP_ATTR_COMPRESS, attrs) != NULL)
+ flags |= PM_CTXFLAG_COMPRESS;
+
+ if (__pmHashSearch(PCP_ATTR_USERAUTH, attrs) != NULL ||
+ __pmHashSearch(PCP_ATTR_USERNAME, attrs) != NULL ||
+ __pmHashSearch(PCP_ATTR_PASSWORD, attrs) != NULL ||
+ __pmHashSearch(PCP_ATTR_METHOD, attrs) != NULL ||
+ __pmHashSearch(PCP_ATTR_REALM, attrs) != NULL)
+ flags |= PM_CTXFLAG_AUTH;
+
+ return flags;
+}
+
+int
+pmNewContext(int type, const char *name)
+{
+ __pmContext *new = NULL;
+ __pmContext **list;
+ int i;
+ int sts;
+ int old_curcontext;
+ int old_contexts_len;
+
+ PM_INIT_LOCKS();
+
+ if (PM_CONTEXT_LOCAL == (type & PM_CONTEXT_TYPEMASK) &&
+ PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ return PM_ERR_THREAD;
+
+ PM_LOCK(__pmLock_libpcp);
+
+ old_curcontext = PM_TPD(curcontext);
+ old_contexts_len = contexts_len;
+
+ /* See if we can reuse a free context */
+ for (i = 0; i < contexts_len; i++) {
+ if (contexts[i]->c_type == PM_CONTEXT_FREE) {
+ PM_TPD(curcontext) = i;
+ new = contexts[i];
+ goto INIT_CONTEXT;
+ }
+ }
+
+ /* Create a new one */
+ if (contexts == NULL)
+ list = (__pmContext **)malloc(sizeof(__pmContext *));
+ else
+ list = (__pmContext **)realloc((void *)contexts, (1+contexts_len) * sizeof(__pmContext *));
+ new = (__pmContext *)malloc(sizeof(__pmContext));
+ if (list == NULL || new == NULL) {
+ /* fail : nothing changed, but new may have been allocated (in theory) */
+ if (new)
+ memset(new, 0, sizeof(__pmContext));
+ sts = -oserror();
+ goto FAILED;
+ }
+
+ contexts = list;
+ PM_TPD(curcontext) = contexts_len;
+ contexts[contexts_len] = new;
+ contexts_len++;
+
+INIT_CONTEXT:
+ /*
+ * Set up the default state
+ */
+ memset(new, 0, sizeof(__pmContext));
+ __pmInitContextLock(&new->c_lock);
+ new->c_type = (type & PM_CONTEXT_TYPEMASK);
+ new->c_flags = (type & ~PM_CONTEXT_TYPEMASK);
+ if ((new->c_instprof = (__pmProfile *)calloc(1, sizeof(__pmProfile))) == NULL) {
+ /*
+ * fail : nothing changed -- actually list is changed, but restoring
+ * contexts_len should make it ok next time through
+ */
+ sts = -oserror();
+ goto FAILED;
+ }
+ new->c_instprof->state = PM_PROFILE_INCLUDE; /* default global state */
+
+ if (new->c_type == PM_CONTEXT_HOST) {
+ __pmHashCtl *attrs = &new->c_attrs;
+ pmHostSpec *hosts;
+ int nhosts;
+ char *errmsg;
+
+ /* break down a host[:port@proxy:port][?attributes] specification */
+ __pmHashInit(attrs);
+ sts = __pmParseHostAttrsSpec(name, &hosts, &nhosts, attrs, &errmsg);
+ if (sts < 0) {
+ pmprintf("pmNewContext: bad host specification\n%s", errmsg);
+ pmflush();
+ free(errmsg);
+ goto FAILED;
+ } else if (nhosts == 0) {
+ sts = PM_ERR_NOTHOST;
+ goto FAILED;
+ } else {
+ new->c_flags |= ctxflags(attrs);
+ }
+
+ /* As an optimization, if there is already a connection to the same PMCD,
+ we try to reuse (share) it. */
+ if (nhosts == 1) { /* not proxied */
+ for (i = 0; i < contexts_len; i++) {
+ if (i == PM_TPD(curcontext))
+ continue;
+ if (contexts[i]->c_type == new->c_type &&
+ contexts[i]->c_flags == new->c_flags &&
+ strcmp(contexts[i]->c_pmcd->pc_hosts[0].name, hosts[0].name) == 0 &&
+ contexts[i]->c_pmcd->pc_hosts[0].nports == hosts[0].nports) {
+ int j;
+ int ports_same = 1;
+ for (j=0; j<hosts[0].nports; j++)
+ if (contexts[i]->c_pmcd->pc_hosts[0].ports[j] != hosts[0].ports[j])
+ ports_same = 0;
+ if (ports_same)
+ new->c_pmcd = contexts[i]->c_pmcd;
+ }
+ }
+ }
+ if (new->c_pmcd == NULL) {
+ /*
+ * Try to establish the connection.
+ * If this fails, restore the original current context
+ * and return an error.
+ */
+ sts = __pmConnectPMCD(hosts, nhosts, new->c_flags, &new->c_attrs);
+ if (sts < 0) {
+ __pmFreeHostAttrsSpec(hosts, nhosts, attrs);
+ __pmHashClear(attrs);
+ goto FAILED;
+ }
+
+ new->c_pmcd = (__pmPMCDCtl *)calloc(1,sizeof(__pmPMCDCtl));
+ if (new->c_pmcd == NULL) {
+ sts = -oserror();
+ __pmCloseSocket(sts);
+ __pmFreeHostAttrsSpec(hosts, nhosts, attrs);
+ __pmHashClear(attrs);
+ goto FAILED;
+ }
+ new->c_pmcd->pc_fd = sts;
+ new->c_pmcd->pc_hosts = hosts;
+ new->c_pmcd->pc_nhosts = nhosts;
+ new->c_pmcd->pc_tout_sec = __pmConvertTimeout(TIMEOUT_DEFAULT) / 1000;
+ __pmInitChannelLock(&new->c_pmcd->pc_lock);
+ }
+ else {
+ /* duplicate of an existing context, don't need the __pmHostSpec */
+ __pmFreeHostAttrsSpec(hosts, nhosts, attrs);
+ __pmHashClear(attrs);
+ }
+ new->c_pmcd->pc_refcnt++;
+ }
+ else if (new->c_type == PM_CONTEXT_LOCAL) {
+ if ((sts = __pmConnectLocal(&new->c_attrs)) != 0)
+ goto FAILED;
+ }
+ else if (new->c_type == PM_CONTEXT_ARCHIVE) {
+ if ((new->c_archctl = (__pmArchCtl *)malloc(sizeof(__pmArchCtl))) == NULL) {
+ sts = -oserror();
+ goto FAILED;
+ }
+ new->c_archctl->ac_log = NULL;
+ for (i = 0; i < contexts_len; i++) {
+ if (i == PM_TPD(curcontext))
+ continue;
+ if (contexts[i]->c_type == PM_CONTEXT_ARCHIVE &&
+ strcmp(name, contexts[i]->c_archctl->ac_log->l_name) == 0) {
+ new->c_archctl->ac_log = contexts[i]->c_archctl->ac_log;
+ }
+ }
+ if (new->c_archctl->ac_log == NULL) {
+ if ((new->c_archctl->ac_log = (__pmLogCtl *)malloc(sizeof(__pmLogCtl))) == NULL) {
+ free(new->c_archctl);
+ sts = -oserror();
+ goto FAILED;
+ }
+ if ((sts = __pmLogOpen(name, new)) < 0) {
+ free(new->c_archctl->ac_log);
+ free(new->c_archctl);
+ goto FAILED;
+ }
+ }
+ else {
+ /* archive already open, set default starting state as per __pmLogOpen */
+ new->c_origin.tv_sec = (__int32_t)new->c_archctl->ac_log->l_label.ill_start.tv_sec;
+ new->c_origin.tv_usec = (__int32_t)new->c_archctl->ac_log->l_label.ill_start.tv_usec;
+ new->c_mode = (new->c_mode & 0xffff0000) | PM_MODE_FORW;
+ }
+
+ /* start after header + label record + trailer */
+ new->c_archctl->ac_offset = sizeof(__pmLogLabel) + 2*sizeof(int);
+ new->c_archctl->ac_vol = new->c_archctl->ac_log->l_curvol;
+ new->c_archctl->ac_serial = 0; /* not serial access, yet */
+ new->c_archctl->ac_pmid_hc.nodes = 0; /* empty hash list */
+ new->c_archctl->ac_pmid_hc.hsize = 0;
+ new->c_archctl->ac_end = 0.0;
+ new->c_archctl->ac_want = NULL;
+ new->c_archctl->ac_unbound = NULL;
+ new->c_archctl->ac_cache = NULL;
+ new->c_archctl->ac_log->l_refcnt++;
+ }
+ else {
+ /* bad type */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "pmNewContext(%d, %s): illegal type\n",
+ type, name);
+ }
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return PM_ERR_NOCONTEXT;
+ }
+
+ /* bind defined metrics if any ... */
+ __dmopencontext(new);
+
+ /* return the handle to the new (current) context */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "pmNewContext(%d, %s) -> %d\n", type, name, PM_TPD(curcontext));
+ __pmDumpContext(stderr, PM_TPD(curcontext), PM_INDOM_NULL);
+ }
+#endif
+ sts = PM_TPD(curcontext);
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+
+FAILED:
+ if (new != NULL) {
+ if (new->c_instprof != NULL)
+ free(new->c_instprof);
+ /* only free this pointer if it was not reclaimed from old contexts */
+ for (i = 0; i < old_contexts_len; i++) {
+ if (contexts[i] != new)
+ continue;
+ new->c_type = PM_CONTEXT_FREE;
+ break;
+ }
+ if (i == old_contexts_len)
+ free(new);
+ }
+ PM_TPD(curcontext) = old_curcontext;
+ contexts_len = old_contexts_len;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmNewContext(%d, %s) -> %d, curcontext=%d\n",
+ type, name, sts, PM_TPD(curcontext));
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+pmReconnectContext(int handle)
+{
+ __pmContext *ctxp;
+ __pmPMCDCtl *ctl;
+ int i, sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (handle < 0 || handle >= contexts_len ||
+ contexts[handle]->c_type == PM_CONTEXT_FREE) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmReconnectContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT);
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return PM_ERR_NOCONTEXT;
+ }
+
+ ctxp = contexts[handle];
+ ctl = ctxp->c_pmcd;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ if (ctl->pc_timeout && time(NULL) < ctl->pc_again) {
+ /* too soon to try again */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmReconnectContext(%d) -> %d, too soon (need wait another %d secs)\n",
+ handle, (int)-ETIMEDOUT, (int)(ctl->pc_again - time(NULL)));
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return -ETIMEDOUT;
+ }
+
+ if (ctl->pc_fd >= 0) {
+ /* don't care if this fails */
+ __pmCloseSocket(ctl->pc_fd);
+ ctl->pc_fd = -1;
+ }
+
+ if ((sts = __pmConnectPMCD(ctl->pc_hosts, ctl->pc_nhosts,
+ ctxp->c_flags, &ctxp->c_attrs)) < 0) {
+ waitawhile(ctl);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmReconnectContext(%d), failed (wait %d secs before next attempt)\n",
+ handle, (int)(ctl->pc_again - time(NULL)));
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return -ETIMEDOUT;
+ }
+ else {
+ ctl->pc_fd = sts;
+ ctl->pc_timeout = 0;
+ ctxp->c_sent = 0;
+
+ /* mark profile as not sent for all contexts sharing this socket */
+ for (i = 0; i < contexts_len; i++) {
+ if (contexts[i]->c_type != PM_CONTEXT_FREE && contexts[i]->c_pmcd == ctl) {
+ contexts[i]->c_sent = 0;
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmReconnectContext(%d), done\n", handle);
+#endif
+ }
+ }
+
+ /* clear any derived metrics and re-bind */
+ __dmclosecontext(ctxp);
+ __dmopencontext(ctxp);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmReconnectContext(%d) -> %d\n", handle, handle);
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return handle;
+}
+
+int
+pmDupContext(void)
+{
+ int sts, oldtype;
+ int old, new = -1;
+ char hostspec[4096];
+ __pmContext *newcon, *oldcon;
+ __pmInDomProfile *q, *p, *p_end;
+ __pmProfile *save;
+ void *save_dm;
+#ifdef PM_MULTI_THREAD
+ pthread_mutex_t save_lock;
+#endif
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((old = pmWhichContext()) < 0) {
+ sts = old;
+ goto done;
+ }
+ oldcon = contexts[old];
+ oldtype = oldcon->c_type | oldcon->c_flags;
+ if (oldcon->c_type == PM_CONTEXT_HOST) {
+ __pmUnparseHostSpec(oldcon->c_pmcd->pc_hosts,
+ oldcon->c_pmcd->pc_nhosts, hostspec, sizeof(hostspec));
+ new = pmNewContext(oldtype, hostspec);
+ }
+ else if (oldcon->c_type == PM_CONTEXT_LOCAL)
+ new = pmNewContext(oldtype, NULL);
+ else
+ /* assume PM_CONTEXT_ARCHIVE */
+ new = pmNewContext(oldtype, oldcon->c_archctl->ac_log->l_name);
+ if (new < 0) {
+ /* failed to connect or out of memory */
+ sts = new;
+ goto done;
+ }
+ oldcon = contexts[old]; /* contexts[] may have been relocated */
+ newcon = contexts[new];
+ save = newcon->c_instprof; /* need this later */
+ save_dm = newcon->c_dm; /* need this later */
+#ifdef PM_MULTI_THREAD
+ save_lock = newcon->c_lock; /* need this later */
+#endif
+ if (newcon->c_archctl != NULL)
+ free(newcon->c_archctl); /* will allocate a new one below */
+ *newcon = *oldcon; /* struct copy */
+ newcon->c_instprof = save; /* restore saved instprof from pmNewContext */
+ newcon->c_dm = save_dm; /* restore saved derived metrics control also */
+#ifdef PM_MULTI_THREAD
+ newcon->c_lock = save_lock; /* restore saved lock with initialized state also */
+#endif
+
+ /* clone the per-domain profiles (if any) */
+ if (oldcon->c_instprof->profile_len > 0) {
+ newcon->c_instprof->profile = (__pmInDomProfile *)malloc(
+ oldcon->c_instprof->profile_len * sizeof(__pmInDomProfile));
+ if (newcon->c_instprof->profile == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ memcpy(newcon->c_instprof->profile, oldcon->c_instprof->profile,
+ oldcon->c_instprof->profile_len * sizeof(__pmInDomProfile));
+ p = oldcon->c_instprof->profile;
+ p_end = p + oldcon->c_instprof->profile_len;
+ q = newcon->c_instprof->profile;
+ for (; p < p_end; p++, q++) {
+ if (p->instances) {
+ q->instances = (int *)malloc(p->instances_len * sizeof(int));
+ if (q->instances == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ memcpy(q->instances, p->instances,
+ p->instances_len * sizeof(int));
+ }
+ }
+ }
+
+ /*
+ * The call to pmNewContext (above) should have connected to the pmcd.
+ * Make sure the new profile will be sent before the next fetch.
+ */
+ newcon->c_sent = 0;
+
+ /* clone the archive control struct, if any */
+ if (oldcon->c_archctl != NULL) {
+ if ((newcon->c_archctl = (__pmArchCtl *)malloc(sizeof(__pmArchCtl))) == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ *newcon->c_archctl = *oldcon->c_archctl; /* struct assignment */
+ /*
+ * Need to make hash list and read cache independent in case oldcon
+ * is subsequently closed via pmDestroyContext() and don't want
+ * __pmFreeInterpData() to trash our hash list and read cache.
+ * Start with an empty hash list and read cache for the dup'd context.
+ */
+ newcon->c_archctl->ac_pmid_hc.nodes = 0;
+ newcon->c_archctl->ac_pmid_hc.hsize = 0;
+ newcon->c_archctl->ac_cache = NULL;
+ }
+
+ sts = new;
+
+done:
+ /* return an error code, or the handle for the new context */
+ if (sts < 0 && new >= 0)
+ contexts[new]->c_type = PM_CONTEXT_FREE;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "pmDupContext() -> %d\n", sts);
+ if (sts >= 0)
+ __pmDumpContext(stderr, sts, PM_INDOM_NULL);
+ }
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+pmUseContext(int handle)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (handle < 0 || handle >= contexts_len ||
+ contexts[handle]->c_type == PM_CONTEXT_FREE) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmUseContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT);
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return PM_ERR_NOCONTEXT;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmUseContext(%d) -> 0\n", handle);
+#endif
+ PM_TPD(curcontext) = handle;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+int
+pmDestroyContext(int handle)
+{
+ __pmContext *ctxp;
+ struct linger dolinger = {0, 1};
+#ifdef PM_MULTI_THREAD
+ int psts;
+ char errmsg[PM_MAXERRMSGLEN];
+#endif
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (handle < 0 || handle >= contexts_len ||
+ contexts[handle]->c_type == PM_CONTEXT_FREE) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmDestroyContext(%d) -> %d\n", handle, PM_ERR_NOCONTEXT);
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return PM_ERR_NOCONTEXT;
+ }
+
+ ctxp = contexts[handle];
+ PM_LOCK(ctxp->c_lock);
+ if (ctxp->c_pmcd != NULL) {
+ if (--ctxp->c_pmcd->pc_refcnt == 0) {
+ if (ctxp->c_pmcd->pc_fd >= 0) {
+ /* before close, unsent data should be flushed */
+ __pmSetSockOpt(ctxp->c_pmcd->pc_fd, SOL_SOCKET,
+ SO_LINGER, (char *) &dolinger, (__pmSockLen)sizeof(dolinger));
+ __pmCloseSocket(ctxp->c_pmcd->pc_fd);
+ }
+ __pmFreeHostSpec(ctxp->c_pmcd->pc_hosts, ctxp->c_pmcd->pc_nhosts);
+ free(ctxp->c_pmcd);
+ }
+ }
+ if (ctxp->c_archctl != NULL) {
+ __pmFreeInterpData(ctxp);
+ if (--ctxp->c_archctl->ac_log->l_refcnt == 0) {
+ __pmLogClose(ctxp->c_archctl->ac_log);
+ free(ctxp->c_archctl->ac_log);
+ }
+ if (ctxp->c_archctl->ac_cache != NULL)
+ free(ctxp->c_archctl->ac_cache);
+ free(ctxp->c_archctl);
+ }
+ ctxp->c_type = PM_CONTEXT_FREE;
+
+ if (handle == PM_TPD(curcontext))
+ /* we have no choice */
+ PM_TPD(curcontext) = PM_CONTEXT_UNDEF;
+
+ __pmFreeProfile(ctxp->c_instprof);
+ __dmclosecontext(ctxp);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmDestroyContext(%d) -> 0, curcontext=%d\n",
+ handle, PM_TPD(curcontext));
+#endif
+
+
+ PM_UNLOCK(ctxp->c_lock);
+#ifdef PM_MULTI_THREAD
+ if ((psts = pthread_mutex_destroy(&ctxp->c_lock)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmDestroyContext(context=%d): pthread_mutex_destroy failed: %s\n", handle, errmsg);
+ /*
+ * Most likely cause is the mutex still being locked ... this is a
+ * a library bug, but potentially recoverable ...
+ */
+ while (PM_UNLOCK(ctxp->c_lock) >= 0) {
+ fprintf(stderr, "pmDestroyContext(context=%d): extra unlock?\n", handle);
+ }
+ if ((psts = pthread_mutex_destroy(&ctxp->c_lock)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "pmDestroyContext(context=%d): pthread_mutex_destroy failed second try: %s\n", handle, errmsg);
+ }
+ /* keep going, rather than exit ... */
+ }
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+static const char *_mode[] = { "LIVE", "INTERP", "FORW", "BACK" };
+
+/*
+ * dump context(s); context == -1 for all contexts, indom == PM_INDOM_NULL
+ * for all instance domains.
+ */
+void
+__pmDumpContext(FILE *f, int context, pmInDom indom)
+{
+ int i;
+ __pmContext *con;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ fprintf(f, "Dump Contexts: current context = %d\n", PM_TPD(curcontext));
+ if (PM_TPD(curcontext) < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+ }
+
+ if (indom != PM_INDOM_NULL) {
+ char strbuf[20];
+ fprintf(f, "Dump restricted to indom=%d [%s]\n",
+ indom, pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ }
+
+ for (i = 0; i < contexts_len; i++) {
+ con = contexts[i];
+ if (context == -1 || context == i) {
+ fprintf(f, "Context[%d]", i);
+ if (con->c_type == PM_CONTEXT_HOST) {
+ fprintf(f, " host %s:", con->c_pmcd->pc_hosts[0].name);
+ fprintf(f, " pmcd=%s profile=%s fd=%d refcnt=%d",
+ (con->c_pmcd->pc_fd < 0) ? "NOT CONNECTED" : "CONNECTED",
+ con->c_sent ? "SENT" : "NOT_SENT",
+ con->c_pmcd->pc_fd,
+ con->c_pmcd->pc_refcnt);
+ if (con->c_flags)
+ fprintf(f, " flags=%x", con->c_flags);
+ }
+ else if (con->c_type == PM_CONTEXT_LOCAL) {
+ fprintf(f, " standalone:");
+ fprintf(f, " profile=%s\n",
+ con->c_sent ? "SENT" : "NOT_SENT");
+ }
+ else {
+ fprintf(f, " log %s:", con->c_archctl->ac_log->l_name);
+ fprintf(f, " mode=%s", _mode[con->c_mode & __PM_MODE_MASK]);
+ fprintf(f, " profile=%s tifd=%d mdfd=%d mfd=%d\nrefcnt=%d vol=%d",
+ con->c_sent ? "SENT" : "NOT_SENT",
+ con->c_archctl->ac_log->l_tifp == NULL ? -1 : fileno(con->c_archctl->ac_log->l_tifp),
+ fileno(con->c_archctl->ac_log->l_mdfp),
+ fileno(con->c_archctl->ac_log->l_mfp),
+ con->c_archctl->ac_log->l_refcnt,
+ con->c_archctl->ac_log->l_curvol);
+ fprintf(f, " offset=%ld (vol=%d) serial=%d",
+ (long)con->c_archctl->ac_offset,
+ con->c_archctl->ac_vol,
+ con->c_archctl->ac_serial);
+ }
+ if (con->c_type != PM_CONTEXT_LOCAL) {
+ fprintf(f, " origin=%d.%06d",
+ con->c_origin.tv_sec, con->c_origin.tv_usec);
+ fprintf(f, " delta=%d\n", con->c_delta);
+ }
+ __pmDumpProfile(f, indom, con->c_instprof);
+ }
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+#ifdef PM_MULTI_THREAD
+#ifdef PM_MULTI_THREAD_DEBUG
+/*
+ * return context if lock == c_lock for a context ... no locking here
+ * to avoid recursion ad nauseum
+ */
+int
+__pmIsContextLock(void *lock)
+{
+ int i;
+ for (i = 0; i < contexts_len; i++) {
+ if ((void *)&contexts[i]->c_lock == lock)
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * return context if lock == pc_lock for a context ... no locking here
+ * to avoid recursion ad nauseum
+ */
+int
+__pmIsChannelLock(void *lock)
+{
+ int i;
+ for (i = 0; i < contexts_len; i++) {
+ if ((void *)&contexts[i]->c_pmcd->pc_lock == lock)
+ return i;
+ }
+ return -1;
+}
+#endif
+#endif
diff --git a/src/libpcp/src/derive.c b/src/libpcp/src/derive.c
new file mode 100644
index 0000000..55acf23
--- /dev/null
+++ b/src/libpcp/src/derive.c
@@ -0,0 +1,1864 @@
+/*
+ * Copyright (c) 2009,2014 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Debug Flags
+ * DERIVE - high-level diagnostics
+ * DERIVE & APPL0 - configuration and static syntax analysis
+ * DERIVE & APPL1 - expression binding, semantic analysis, PMNS ops
+ * DERIVE & APPL2 - fetch handling
+ *
+ * Caveats
+ * 0. No unary negation operator.
+ * 1. No derived metrics for pmFetchArchive() as this routine does
+ * not use a target pmidlist[]
+ * 2. Derived metrics will not work with pmRequestTraversePMNS() and
+ * pmReceiveTraversePMNS() because the by the time the list of
+ * names is received, the original name at the root of the search
+ * is no longer available.
+ * 3. pmRegisterDerived() does not apply retrospectively to any open
+ * contexts, so the normal use would be to make all calls to
+ * pmRegisterDerived() (possibly via pmLoadDerivedConfig()) and then
+ * call pmNewContext().
+ * 4. There is no pmUnregisterDerived(), so once registered a derived
+ * metric persists for the life of the application.
+ *
+ * Thread-safe notes
+ *
+ * Need to call PM_INIT_LOCKS() in pmRegisterDerived() because we may
+ * be called before a context has been created, and missed the
+ * lock initialization in pmNewContext().
+ *
+ * registered.mutex is held throughout pmRegisterDerived() and this
+ * protects all of the lexical scanner and parser state, i.e. tokbuf,
+ * tokbuflen, string, lexpeek and this. Same applies to pmid within
+ * pmRegisterDerived().
+ *
+ * The return value from pmRegisterDerived is either a NULL or a pointer
+ * back into the expr argument, so use of "this" to carry the return
+ * value in the error case is OK.
+ *
+ * All access to registered is controlled by the registered.mutex.
+ *
+ * No locking needed in init() to protect need_init and the getenv()
+ * call, as we always lock the registered.mutex before calling init().
+ *
+ * The context locking protocol ensures that when any of the routines
+ * below are called with a __pmContext * argument, that argument is
+ * not NULL and is associated with a context that is ALREADY locked
+ * via ctxp->c_lock. We should not unlock the context, that is the
+ * responsibility of our callers.
+ *
+ * derive_errmsg needs to be thread-private
+ */
+
+#include <inttypes.h>
+#include <assert.h>
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include "fault.h"
+#ifdef IS_MINGW
+extern const char *strerror_r(int, char *, size_t);
+#endif
+
+static int need_init = 1;
+static ctl_t registered = {
+#ifdef PM_MULTI_THREAD
+#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP,
+#else
+ PTHREAD_MUTEX_INITIALIZER,
+#endif
+#endif
+ 0, NULL, 0, 0 };
+
+/* parser and lexer variables */
+static char *tokbuf = NULL;
+static int tokbuflen;
+static const char *this; /* start of current lexicon */
+static int lexpeek = 0;
+static const char *string;
+
+#ifdef PM_MULTI_THREAD
+#ifdef HAVE___THREAD
+/* using a gcc construct here to make derive_errmsg thread-private */
+static __thread char *derive_errmsg;
+#endif
+#else
+static char *derive_errmsg;
+#endif
+
+static const char *type_dbg[] = {
+ "ERROR", "EOF", "UNDEF", "NUMBER", "NAME", "PLUS", "MINUS",
+ "STAR", "SLASH", "LPAREN", "RPAREN", "AVG", "COUNT", "DELTA",
+ "MAX", "MIN", "SUM", "ANON", "RATE" };
+static const char type_c[] = {
+ '\0', '\0', '\0', '\0', '\0', '+', '-', '*', '/', '(', ')', '\0' };
+
+/* function table for lexer */
+static const struct {
+ int f_type;
+ char *f_name;
+} func[] = {
+ { L_AVG, "avg" },
+ { L_COUNT, "count" },
+ { L_DELTA, "delta" },
+ { L_MAX, "max" },
+ { L_MIN, "min" },
+ { L_SUM, "sum" },
+ { L_ANON, "anon" },
+ { L_RATE, "rate" },
+ { L_UNDEF, NULL }
+};
+
+/* parser states */
+#define P_INIT 0
+#define P_LEAF 1
+#define P_LEAF_PAREN 2
+#define P_BINOP 3
+#define P_FUNC_OP 4
+#define P_FUNC_END 5
+#define P_END 99
+
+static const char *state_dbg[] = {
+ "INIT", "LEAF", "LEAF_PAREN", "BINOP", "FUNC_OP", "FUNC_END" };
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+static void
+initialize_mutex(void)
+{
+ static pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER;
+ static int done = 0;
+ int psts;
+ char errmsg[PM_MAXERRMSGLEN];
+ if ((psts = pthread_mutex_lock(&init)) != 0) {
+ strerror_r(psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "initializ_mutex: pthread_mutex_lock failed: %s", errmsg);
+ exit(4);
+ }
+ if (!done) {
+ /*
+ * Unable to initialize at compile time, need to do it here in
+ * a one trip for all threads run-time initialization.
+ */
+ pthread_mutexattr_t attr;
+
+ if ((psts = pthread_mutexattr_init(&attr)) != 0) {
+ strerror_r(psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "initialize_mutex: pthread_mutexattr_init failed: %s", errmsg);
+ exit(4);
+ }
+ if ((psts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) {
+ strerror_r(psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "initialize_mutex: pthread_mutexattr_settype failed: %s", errmsg);
+ exit(4);
+ }
+ if ((psts = pthread_mutex_init(&registered.mutex, &attr)) != 0) {
+ strerror_r(psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "initialize_mutex: pthread_mutex_init failed: %s", errmsg);
+ exit(4);
+ }
+ done = 1;
+ }
+ if ((psts = pthread_mutex_unlock(&init)) != 0) {
+ strerror_r(psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "initialize_mutex: pthread_mutex_unlock failed: %s", errmsg);
+ exit(4);
+ }
+}
+#endif
+#endif
+
+/* Register an anonymous metric */
+int
+__pmRegisterAnon(const char *name, int type)
+{
+ char *msg;
+ char buf[21]; /* anon(PM_TYPE_XXXXXX) */
+
+PM_FAULT_CHECK(PM_FAULT_PMAPI);
+ switch (type) {
+ case PM_TYPE_32:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_32)");
+ break;
+ case PM_TYPE_U32:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_U32)");
+ break;
+ case PM_TYPE_64:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_64)");
+ break;
+ case PM_TYPE_U64:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_U64)");
+ break;
+ case PM_TYPE_FLOAT:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_FLOAT)");
+ break;
+ case PM_TYPE_DOUBLE:
+ snprintf(buf, sizeof(buf), "anon(PM_TYPE_DOUBLE)");
+ break;
+ default:
+ return PM_ERR_TYPE;
+ }
+ if ((msg = pmRegisterDerived(name, buf)) != NULL) {
+ pmprintf("__pmRegisterAnon(%s, %d): @ \"%s\" Error: %s\n", name, type, msg, pmDerivedErrStr());
+ pmflush();
+ return PM_ERR_GENERIC;
+ }
+ return 0;
+}
+
+static void
+init(void)
+{
+ if (need_init) {
+ char *configpath;
+
+ if ((configpath = getenv("PCP_DERIVED_CONFIG")) != NULL) {
+ int sts;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ fprintf(stderr, "Derived metric initialization from $PCP_DERIVED_CONFIG\n");
+ }
+#endif
+ sts = pmLoadDerivedConfig(configpath);
+#ifdef PCP_DEBUG
+ if (sts < 0 && (pmDebug & DBG_TRACE_DERIVE)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "pmLoadDerivedConfig -> %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ }
+ need_init = 0;
+ }
+}
+
+static void
+unget(int c)
+{
+ lexpeek = c;
+}
+
+static int
+get()
+{
+ int c;
+ if (lexpeek != 0) {
+ c = lexpeek;
+ lexpeek = 0;
+ return c;
+ }
+ c = *string;
+ if (c == '\0') {
+ return L_EOF;
+ }
+ string++;
+ return c;
+}
+
+static int
+lex(void)
+{
+ int c;
+ char *p = tokbuf;
+ int ltype = L_UNDEF;
+ int i;
+ int firstch = 1;
+
+ for ( ; ; ) {
+ c = get();
+ if (firstch) {
+ if (isspace((int)c)) continue;
+ this = &string[-1];
+ firstch = 0;
+ }
+ if (c == L_EOF) {
+ if (ltype != L_UNDEF) {
+ /* force end of last token */
+ c = 0;
+ }
+ else {
+ /* really the end of the input */
+ return L_EOF;
+ }
+ }
+ if (p == NULL) {
+ tokbuflen = 128;
+ if ((p = tokbuf = (char *)malloc(tokbuflen)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: alloc tokbuf", tokbuflen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+ else if (p >= &tokbuf[tokbuflen]) {
+ int x = p - tokbuf;
+ tokbuflen *= 2;
+ if ((tokbuf = (char *)realloc(tokbuf, tokbuflen)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: realloc tokbuf", tokbuflen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ p = &tokbuf[x];
+ }
+
+ *p++ = (char)c;
+
+ if (ltype == L_UNDEF) {
+ if (isdigit((int)c))
+ ltype = L_NUMBER;
+ else if (isalpha((int)c))
+ ltype = L_NAME;
+ else {
+ switch (c) {
+ case '+':
+ *p = '\0';
+ return L_PLUS;
+ break;
+
+ case '-':
+ *p = '\0';
+ return L_MINUS;
+ break;
+
+ case '*':
+ *p = '\0';
+ return L_STAR;
+ break;
+
+ case '/':
+ *p = '\0';
+ return L_SLASH;
+ break;
+
+ case '(':
+ *p = '\0';
+ return L_LPAREN;
+ break;
+
+ case ')':
+ *p = '\0';
+ return L_RPAREN;
+ break;
+
+ default:
+ return L_ERROR;
+ break;
+ }
+ }
+ }
+ else {
+ if (ltype == L_NUMBER) {
+ if (!isdigit((int)c)) {
+ unget(c);
+ p[-1] = '\0';
+ return L_NUMBER;
+ }
+ }
+ else if (ltype == L_NAME) {
+ if (isalpha((int)c) || isdigit((int)c) || c == '_' || c == '.')
+ continue;
+ if (c == '(') {
+ /* check for functions ... */
+ int namelen = p - tokbuf - 1;
+ for (i = 0; func[i].f_name != NULL; i++) {
+ if (namelen == strlen(func[i].f_name) &&
+ strncmp(tokbuf, func[i].f_name, namelen) == 0) {
+ *p = '\0';
+ return func[i].f_type;
+ }
+ }
+ }
+ /* current character is end of name */
+ unget(c);
+ p[-1] = '\0';
+ return L_NAME;
+ }
+ }
+
+ }
+}
+
+static node_t *
+newnode(int type)
+{
+ node_t *np;
+ np = (node_t *)malloc(sizeof(node_t));
+ if (np == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: newnode", sizeof(node_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ np->type = type;
+ np->save_last = 0;
+ np->left = NULL;
+ np->right = NULL;
+ np->value = NULL;
+ np->info = NULL;
+ return np;
+}
+
+static void
+free_expr(node_t *np)
+{
+ if (np == NULL) return;
+ if (np->left != NULL) free_expr(np->left);
+ if (np->right != NULL) free_expr(np->right);
+ /* value is only allocated once for the static nodes */
+ if (np->info == NULL && np->value != NULL) free(np->value);
+ if (np->info != NULL) free(np->info);
+ free(np);
+}
+
+/*
+ * copy a static expression tree to make the dynamic per context
+ * expression tree and initialize the info block
+ */
+static node_t *
+bind_expr(int n, node_t *np)
+{
+ node_t *new;
+
+ assert(np != NULL);
+ new = newnode(np->type);
+ if (np->left != NULL) {
+ if ((new->left = bind_expr(n, np->left)) == NULL) {
+ /* error, reported deeper in the recursion, clean up */
+ free(new);
+ return(NULL);
+ }
+ }
+ if (np->right != NULL) {
+ if ((new->right = bind_expr(n, np->right)) == NULL) {
+ /* error, reported deeper in the recursion, clean up */
+ free(new);
+ return(NULL);
+ }
+ }
+ if ((new->info = (info_t *)malloc(sizeof(info_t))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("bind_expr: info block", sizeof(info_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ new->info->pmid = PM_ID_NULL;
+ new->info->numval = 0;
+ new->info->mul_scale = new->info->div_scale = 1;
+ new->info->ivlist = NULL;
+ new->info->stamp.tv_sec = 0;
+ new->info->stamp.tv_usec = 0;
+ new->info->time_scale = -1; /* one-trip initialization if needed */
+ new->info->last_numval = 0;
+ new->info->last_ivlist = NULL;
+ new->info->last_stamp.tv_sec = 0;
+ new->info->last_stamp.tv_usec = 0;
+
+ /* need info to be non-null to protect copy of value in free_expr */
+ new->value = np->value;
+
+ new->save_last = np->save_last;
+
+ if (new->type == L_NAME) {
+ int sts;
+
+ sts = pmLookupName(1, &new->value, &new->info->pmid);
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "bind_expr: error: derived metric %s: operand: %s: %s\n", registered.mlist[n].name, new->value, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ free(new->info);
+ free(new);
+ return NULL;
+ }
+ sts = pmLookupDesc(new->info->pmid, &new->desc);
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "bind_expr: error: derived metric %s: operand (%s [%s]): %s\n", registered.mlist[n].name, new->value, pmIDStr_r(new->info->pmid, strbuf, sizeof(strbuf)), pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ free(new->info);
+ free(new);
+ return NULL;
+ }
+ }
+ else if (new->type == L_NUMBER) {
+ new->desc = np->desc;
+ }
+
+ return new;
+}
+
+static
+void report_sem_error(char *name, node_t *np)
+{
+ pmprintf("Semantic error: derived metric %s: ", name);
+ switch (np->type) {
+ case L_PLUS:
+ case L_MINUS:
+ case L_STAR:
+ case L_SLASH:
+ if (np->left->type == L_NUMBER || np->left->type == L_NAME)
+ pmprintf("%s ", np->left->value);
+ else
+ pmprintf("<expr> ");
+ pmprintf("%c ", type_c[np->type+2]);
+ if (np->right->type == L_NUMBER || np->right->type == L_NAME)
+ pmprintf("%s", np->right->value);
+ else
+ pmprintf("<expr>");
+ break;
+ case L_AVG:
+ case L_COUNT:
+ case L_DELTA:
+ case L_RATE:
+ case L_MAX:
+ case L_MIN:
+ case L_SUM:
+ case L_ANON:
+ pmprintf("%s(%s)", type_dbg[np->type+2], np->left->value);
+ break;
+ default:
+ /* should never get here ... */
+ if (np->type+2 >= 0 && np->type+2 < sizeof(type_dbg)/sizeof(type_dbg[0]))
+ pmprintf("botch @ node type %s?", type_dbg[np->type+2]);
+ else
+ pmprintf("botch @ node type #%d?", np->type);
+ break;
+ }
+ pmprintf(": %s\n", PM_TPD(derive_errmsg));
+ pmflush();
+ PM_TPD(derive_errmsg) = NULL;
+}
+
+/* type promotion */
+static const int promote[6][6] = {
+ { PM_TYPE_32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE },
+ { PM_TYPE_U32, PM_TYPE_U32, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE },
+ { PM_TYPE_64, PM_TYPE_64, PM_TYPE_64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE },
+ { PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_U64, PM_TYPE_FLOAT, PM_TYPE_DOUBLE },
+ { PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_FLOAT, PM_TYPE_DOUBLE },
+ { PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE, PM_TYPE_DOUBLE }
+};
+
+/* time scale conversion factors */
+static const int timefactor[] = {
+ 1000, /* NSEC -> USEC */
+ 1000, /* USEC -> MSEC */
+ 1000, /* MSEC -> SEC */
+ 60, /* SEC -> MIN */
+ 60, /* MIN -> HOUR */
+};
+
+/*
+ * mapping pmUnits for the result, and refining pmDesc as we go ...
+ * we start with the pmDesc from the left operand and adjust as
+ * necessary
+ *
+ * scale conversion rules ...
+ * Count - choose larger, divide/multiply smaller by 10^(difference)
+ * Time - choose larger, divide/multiply smaller by appropriate scale
+ * Space - choose larger, divide/multiply smaller by 1024^(difference)
+ * and result is of type PM_TYPE_DOUBLE
+ *
+ * Need inverted logic to deal with numerator (dimension > 0) and
+ * denominator (dimension < 0) cases.
+ */
+static void
+map_units(node_t *np)
+{
+ pmDesc *right = &np->right->desc;
+ pmDesc *left = &np->left->desc;
+ int diff;
+ int i;
+
+ if (left->units.dimCount != 0 && right->units.dimCount != 0) {
+ diff = left->units.scaleCount - right->units.scaleCount;
+ if (diff > 0) {
+ /* use the left scaleCount, scale the right operand */
+ for (i = 0; i < diff; i++) {
+ if (right->units.dimCount > 0)
+ np->right->info->div_scale *= 10;
+ else
+ np->right->info->mul_scale *= 10;
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ else if (diff < 0) {
+ /* use the right scaleCount, scale the left operand */
+ np->desc.units.scaleCount = right->units.scaleCount;
+ for (i = diff; i < 0; i++) {
+ if (left->units.dimCount > 0)
+ np->left->info->div_scale *= 10;
+ else
+ np->left->info->mul_scale *= 10;
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ }
+ if (left->units.dimTime != 0 && right->units.dimTime != 0) {
+ diff = left->units.scaleTime - right->units.scaleTime;
+ if (diff > 0) {
+ /* use the left scaleTime, scale the right operand */
+ for (i = right->units.scaleTime; i < left->units.scaleTime; i++) {
+ if (right->units.dimTime > 0)
+ np->right->info->div_scale *= timefactor[i];
+ else
+ np->right->info->mul_scale *= timefactor[i];
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ else if (diff < 0) {
+ /* use the right scaleTime, scale the left operand */
+ np->desc.units.scaleTime = right->units.scaleTime;
+ for (i = left->units.scaleTime; i < right->units.scaleTime; i++) {
+ if (right->units.dimTime > 0)
+ np->left->info->div_scale *= timefactor[i];
+ else
+ np->left->info->mul_scale *= timefactor[i];
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ }
+ if (left->units.dimSpace != 0 && right->units.dimSpace != 0) {
+ diff = left->units.scaleSpace - right->units.scaleSpace;
+ if (diff > 0) {
+ /* use the left scaleSpace, scale the right operand */
+ for (i = 0; i < diff; i++) {
+ if (right->units.dimSpace > 0)
+ np->right->info->div_scale *= 1024;
+ else
+ np->right->info->mul_scale *= 1024;
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ else if (diff < 0) {
+ /* use the right scaleSpace, scale the left operand */
+ np->desc.units.scaleSpace = right->units.scaleSpace;
+ for (i = diff; i < 0; i++) {
+ if (right->units.dimSpace > 0)
+ np->left->info->div_scale *= 1024;
+ else
+ np->left->info->mul_scale *= 1024;
+ }
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+ }
+
+ if (np->type == L_STAR) {
+ np->desc.units.dimCount = left->units.dimCount + right->units.dimCount;
+ np->desc.units.dimTime = left->units.dimTime + right->units.dimTime;
+ np->desc.units.dimSpace = left->units.dimSpace + right->units.dimSpace;
+ }
+ else if (np->type == L_SLASH) {
+ np->desc.units.dimCount = left->units.dimCount - right->units.dimCount;
+ np->desc.units.dimTime = left->units.dimTime - right->units.dimTime;
+ np->desc.units.dimSpace = left->units.dimSpace - right->units.dimSpace;
+ }
+
+ /*
+ * for division and multiplication, dimension may have come from
+ * right operand, need to pick up scale from there also
+ */
+ if (np->desc.units.dimCount != 0 && left->units.dimCount == 0)
+ np->desc.units.scaleCount = right->units.scaleCount;
+ if (np->desc.units.dimTime != 0 && left->units.dimTime == 0)
+ np->desc.units.scaleTime = right->units.scaleTime;
+ if (np->desc.units.dimSpace != 0 && left->units.dimSpace == 0)
+ np->desc.units.scaleSpace = right->units.scaleSpace;
+
+}
+
+static int
+map_desc(int n, node_t *np)
+{
+ /*
+ * pmDesc mapping for binary operators ...
+ *
+ * semantics acceptable operators
+ * counter, counter + -
+ * non-counter, non-counter + - * /
+ * counter, non-counter * /
+ * non-counter, counter *
+ *
+ * in the non-counter and non-counter case, the semantics for the
+ * result are PM_SEM_INSTANT, unless both operands are
+ * PM_SEM_DISCRETE in which case the result is also PM_SEM_DISCRETE
+ *
+ * type promotion (similar to ANSI C)
+ * PM_TYPE_STRING, PM_TYPE_AGGREGATE, PM_TYPE_AGGREGATE_STATIC,
+ * PM_TYPE_EVENT and PM_TYPE_HIGHRES_EVENT are illegal operands
+ * except for renaming (where no operator is involved)
+ * for all operands, division => PM_TYPE_DOUBLE
+ * else PM_TYPE_DOUBLE & any type => PM_TYPE_DOUBLE
+ * else PM_TYPE_FLOAT & any type => PM_TYPE_FLOAT
+ * else PM_TYPE_U64 & any type => PM_TYPE_U64
+ * else PM_TYPE_64 & any type => PM_TYPE_64
+ * else PM_TYPE_U32 & any type => PM_TYPE_U32
+ * else PM_TYPE_32 & any type => PM_TYPE_32
+ *
+ * units mapping
+ * operator checks
+ * +, - same dimension
+ * *, / if only one is a counter, non-counter must
+ * have pmUnits of "none"
+ */
+ pmDesc *right = &np->right->desc;
+ pmDesc *left = &np->left->desc;
+
+ if (left->sem == PM_SEM_COUNTER) {
+ if (right->sem == PM_SEM_COUNTER) {
+ if (np->type != L_PLUS && np->type != L_MINUS) {
+ PM_TPD(derive_errmsg) = "Illegal operator for counters";
+ goto bad;
+ }
+ }
+ else {
+ if (np->type != L_STAR && np->type != L_SLASH) {
+ PM_TPD(derive_errmsg) = "Illegal operator for counter and non-counter";
+ goto bad;
+ }
+ }
+ }
+ else {
+ if (right->sem == PM_SEM_COUNTER) {
+ if (np->type != L_STAR) {
+ PM_TPD(derive_errmsg) = "Illegal operator for non-counter and counter";
+ goto bad;
+ }
+ }
+ else {
+ if (np->type != L_PLUS && np->type != L_MINUS &&
+ np->type != L_STAR && np->type != L_SLASH) {
+ /*
+ * this is not possible at the present since only
+ * arithmetic operators are supported and all are
+ * acceptable here ... check added for completeness
+ */
+ PM_TPD(derive_errmsg) = "Illegal operator for non-counters";
+ goto bad;
+ }
+ }
+ }
+
+ /*
+ * Choose candidate descriptor ... prefer metric or expression
+ * over constant
+ */
+ if (np->left->type != L_NUMBER)
+ np->desc = *left; /* struct copy */
+ else
+ np->desc = *right; /* struct copy */
+
+ /*
+ * most non-counter expressions produce PM_SEM_INSTANT results
+ */
+ if (left->sem != PM_SEM_COUNTER && right->sem != PM_SEM_COUNTER) {
+ if (left->sem == PM_SEM_DISCRETE && right->sem == PM_SEM_DISCRETE)
+ np->desc.sem = PM_SEM_DISCRETE;
+ else
+ np->desc.sem = PM_SEM_INSTANT;
+ }
+
+ /*
+ * type checking and promotion
+ */
+ switch (left->type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_FLOAT:
+ case PM_TYPE_DOUBLE:
+ break;
+ default:
+ PM_TPD(derive_errmsg) = "Non-arithmetic type for left operand";
+ goto bad;
+ }
+ switch (right->type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_FLOAT:
+ case PM_TYPE_DOUBLE:
+ break;
+ default:
+ PM_TPD(derive_errmsg) = "Non-arithmetic type for right operand";
+ goto bad;
+ }
+ np->desc.type = promote[left->type][right->type];
+ if (np->type == L_SLASH) {
+ /* for division result is real number */
+ np->desc.type = PM_TYPE_DOUBLE;
+ }
+
+ if (np->type == L_PLUS || np->type == L_MINUS) {
+ /*
+ * unit dimensions have to be identical
+ */
+ if (left->units.dimCount != right->units.dimCount ||
+ left->units.dimTime != right->units.dimTime ||
+ left->units.dimSpace != right->units.dimSpace) {
+ PM_TPD(derive_errmsg) = "Dimensions are not the same";
+ goto bad;
+ }
+ map_units(np);
+ }
+
+ if (np->type == L_STAR || np->type == L_SLASH) {
+ /*
+ * if multiply or divide and operands are a counter and a non-counter,
+ * then non-counter needs to be dimensionless
+ */
+ if (left->sem == PM_SEM_COUNTER && right->sem != PM_SEM_COUNTER) {
+ if (right->units.dimCount != 0 ||
+ right->units.dimTime != 0 ||
+ right->units.dimSpace != 0) {
+ PM_TPD(derive_errmsg) = "Non-counter and not dimensionless for right operand";
+ goto bad;
+ }
+ }
+ if (left->sem != PM_SEM_COUNTER && right->sem == PM_SEM_COUNTER) {
+ if (left->units.dimCount != 0 ||
+ left->units.dimTime != 0 ||
+ left->units.dimSpace != 0) {
+ PM_TPD(derive_errmsg) = "Non-counter and not dimensionless for left operand";
+ goto bad;
+ }
+ }
+ map_units(np);
+ }
+
+ /*
+ * if not both singular, then both operands must have the same
+ * instance domain
+ */
+ if (left->indom != PM_INDOM_NULL && right->indom != PM_INDOM_NULL && left->indom != right->indom) {
+ PM_TPD(derive_errmsg) = "Operands should have the same instance domain";
+ goto bad;
+ }
+
+ return 0;
+
+bad:
+ report_sem_error(registered.mlist[n].name, np);
+ return -1;
+}
+
+static int
+check_expr(int n, node_t *np)
+{
+ int sts;
+
+ assert(np != NULL);
+
+ if (np->type == L_NUMBER || np->type == L_NAME)
+ return 0;
+
+ /* otherwise, np->left is never NULL ... */
+ assert(np->left != NULL);
+
+ if ((sts = check_expr(n, np->left)) < 0)
+ return sts;
+ if (np->right != NULL) {
+ if ((sts = check_expr(n, np->right)) < 0)
+ return sts;
+ /* build pmDesc from pmDesc of both operands */
+ if ((sts = map_desc(n, np)) < 0)
+ return sts;
+ }
+ else {
+ np->desc = np->left->desc; /* struct copy */
+ /*
+ * special cases for functions ...
+ * delta() expect numeric operand, result is instantaneous
+ * rate() expect numeric operand, dimension of time must be
+ * 0 or 1, result is instantaneous
+ * aggr funcs most expect numeric operand, result is instantaneous
+ * and singular
+ */
+ if (np->type == L_AVG || np->type == L_COUNT
+ || np->type == L_DELTA || np->type == L_RATE
+ || np->type == L_MAX || np->type == L_MIN || np->type == L_SUM) {
+ if (np->type == L_COUNT) {
+ /* count() has its own type and units */
+ np->desc.type = PM_TYPE_U32;
+ memset((void *)&np->desc.units, 0, sizeof(np->desc.units));
+ np->desc.units.dimCount = 1;
+ np->desc.units.scaleCount = PM_COUNT_ONE;
+ }
+ else {
+ /* others inherit, but need arithmetic operand */
+ switch (np->left->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_FLOAT:
+ case PM_TYPE_DOUBLE:
+ break;
+ default:
+ PM_TPD(derive_errmsg) = "Non-arithmetic operand for function";
+ report_sem_error(registered.mlist[n].name, np);
+ return -1;
+ }
+ }
+ np->desc.sem = PM_SEM_INSTANT;
+ if (np->type == L_DELTA || np->type == L_RATE) {
+ /* inherit indom */
+ if (np->type == L_RATE) {
+ /*
+ * further restriction for rate() that dimension
+ * for time must be 0 (->counter/sec) or 1
+ * (->time utilization)
+ */
+ if (np->left->desc.units.dimTime != 0 && np->left->desc.units.dimTime != 1) {
+ PM_TPD(derive_errmsg) = "Incorrect time dimension for operand";
+ report_sem_error(registered.mlist[n].name, np);
+ return -1;
+ }
+ }
+ }
+ else {
+ /* all the others are aggregate funcs with a singular value */
+ np->desc.indom = PM_INDOM_NULL;
+ }
+ if (np->type == L_AVG) {
+ /* avg() returns float result */
+ np->desc.type = PM_TYPE_FLOAT;
+ }
+ if (np->type == L_RATE) {
+ /* rate() returns double result and time dimension is
+ * reduced by one ... if time dimension is then 0, set
+ * the scale to be none (this is time utilization)
+ */
+ np->desc.type = PM_TYPE_DOUBLE;
+ np->desc.units.dimTime--;
+ if (np->desc.units.dimTime == 0)
+ np->desc.units.scaleTime = 0;
+ else
+ np->desc.units.scaleTime = PM_TIME_SEC;
+ }
+ }
+ else if (np->type == L_ANON) {
+ /* do nothing, pmDesc inherited "as is" from left node */
+ ;
+ }
+ }
+ return 0;
+}
+
+static void
+dump_value(int type, pmAtomValue *avp)
+{
+ switch (type) {
+ case PM_TYPE_32:
+ fprintf(stderr, "%i", avp->l);
+ break;
+
+ case PM_TYPE_U32:
+ fprintf(stderr, "%u", avp->ul);
+ break;
+
+ case PM_TYPE_64:
+ fprintf(stderr, "%" PRId64, avp->ll);
+ break;
+
+ case PM_TYPE_U64:
+ fprintf(stderr, "%" PRIu64, avp->ull);
+ break;
+
+ case PM_TYPE_FLOAT:
+ fprintf(stderr, "%g", (double)avp->f);
+ break;
+
+ case PM_TYPE_DOUBLE:
+ fprintf(stderr, "%g", avp->d);
+ break;
+
+ case PM_TYPE_STRING:
+ fprintf(stderr, "%s", avp->cp);
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ case PM_TYPE_UNKNOWN:
+ fprintf(stderr, "[blob]");
+ break;
+
+ case PM_TYPE_NOSUPPORT:
+ fprintf(stderr, "dump_value: bogus value, metric Not Supported\n");
+ break;
+
+ default:
+ fprintf(stderr, "dump_value: unknown value type=%d\n", type);
+ }
+}
+
+void
+__dmdumpexpr(node_t *np, int level)
+{
+ char strbuf[20];
+
+ if (level == 0) fprintf(stderr, "Derived metric expr dump from " PRINTF_P_PFX "%p...\n", np);
+ if (np == NULL) return;
+ fprintf(stderr, "expr node " PRINTF_P_PFX "%p type=%s left=" PRINTF_P_PFX "%p right=" PRINTF_P_PFX "%p save_last=%d", np, type_dbg[np->type+2], np->left, np->right, np->save_last);
+ if (np->type == L_NAME || np->type == L_NUMBER)
+ fprintf(stderr, " [%s] master=%d", np->value, np->info == NULL ? 1 : 0);
+ fputc('\n', stderr);
+ if (np->info) {
+ fprintf(stderr, " PMID: %s ", pmIDStr_r(np->info->pmid, strbuf, sizeof(strbuf)));
+ fprintf(stderr, "(%s from pmDesc) numval: %d", pmIDStr_r(np->desc.pmid, strbuf, sizeof(strbuf)), np->info->numval);
+ if (np->info->div_scale != 1)
+ fprintf(stderr, " div_scale: %d", np->info->div_scale);
+ if (np->info->mul_scale != 1)
+ fprintf(stderr, " mul_scale: %d", np->info->mul_scale);
+ fputc('\n', stderr);
+ __pmPrintDesc(stderr, &np->desc);
+ if (np->info->ivlist) {
+ int j;
+ int max;
+
+ max = np->info->numval > np->info->last_numval ? np->info->numval : np->info->last_numval;
+
+ for (j = 0; j < max; j++) {
+ fprintf(stderr, "[%d]", j);
+ if (j < np->info->numval) {
+ fprintf(stderr, " inst=%d, val=", np->info->ivlist[j].inst);
+ dump_value(np->desc.type, &np->info->ivlist[j].value);
+ }
+ if (j < np->info->last_numval) {
+ fprintf(stderr, " (last inst=%d, val=", np->info->last_ivlist[j].inst);
+ dump_value(np->desc.type, &np->info->last_ivlist[j].value);
+ fputc(')', stderr);
+ }
+ fputc('\n', stderr);
+ }
+ }
+ }
+ if (np->left != NULL) __dmdumpexpr(np->left, level+1);
+ if (np->right != NULL) __dmdumpexpr(np->right, level+1);
+}
+
+/*
+ * Parser FSA
+ * state lex new state
+ * P_INIT L_NAME or P_LEAF
+ * L_NUMBER
+ * P_INIT L_<func> P_FUNC_OP
+ * P_INIT L_LPAREN if parse() != NULL then P_LEAF
+ * P_LEAF L_PLUS or P_BINOP
+ * L_MINUS or
+ * L_STAR or
+ * L_SLASH
+ * P_BINOP L_NAME or P_LEAF
+ * L_NUMBER
+ * P_BINOP L_LPAREN if parse() != NULL then P_LEAF
+ * P_BINOP L_<func> P_FUNC_OP
+ * P_LEAF_PAREN same as P_LEAF, but no precedence rules at next operator
+ * P_FUNC_OP L_NAME P_FUNC_END
+ * P_FUNC_END L_RPAREN P_LEAF
+ */
+static node_t *
+parse(int level)
+{
+ int state = P_INIT;
+ int type;
+ node_t *expr = NULL;
+ node_t *curr = NULL;
+ node_t *np;
+
+ for ( ; ; ) {
+ type = lex();
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL0)) {
+ fprintf(stderr, "parse(%d) state=P_%s type=L_%s \"%s\"\n", level, state_dbg[state], type_dbg[type+2], type == L_EOF ? "" : tokbuf);
+ }
+#endif
+ /* handle lexicons that terminate the parsing */
+ switch (type) {
+ case L_ERROR:
+ PM_TPD(derive_errmsg) = "Illegal character";
+ free_expr(expr);
+ return NULL;
+ break;
+ case L_EOF:
+ if (level == 1 && (state == P_LEAF || state == P_LEAF_PAREN))
+ return expr;
+ PM_TPD(derive_errmsg) = "End of input";
+ free_expr(expr);
+ return NULL;
+ break;
+ case L_RPAREN:
+ if (state == P_FUNC_END) {
+ state = P_LEAF;
+ continue;
+ }
+ if ((level > 1 && state == P_LEAF_PAREN) || state == P_LEAF)
+ return expr;
+ PM_TPD(derive_errmsg) = "Unexpected ')'";
+ free_expr(expr);
+ return NULL;
+ break;
+ }
+
+ switch (state) {
+ case P_INIT:
+ /*
+ * Only come here at the start of parsing an expression.
+ * The assert() is designed to stop Coverity flagging a
+ * memory leak if we should come here after expr and/or
+ * curr have already been assigned values either directly
+ * from calling newnode() or via an assignment to np that
+ * was previously assigned a value from newnode()
+ */
+ assert(expr == NULL && curr == NULL);
+
+ if (type == L_NAME || type == L_NUMBER) {
+ expr = curr = newnode(type);
+ if ((curr->value = strdup(tokbuf)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: leaf node", strlen(tokbuf)+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if (type == L_NUMBER) {
+ char *endptr;
+ __uint64_t check;
+ check = strtoull(tokbuf, &endptr, 10);
+ if (*endptr != '\0' || check > 0xffffffffUL) {
+ PM_TPD(derive_errmsg) = "Constant value too large";
+ free_expr(expr);
+ return NULL;
+ }
+ curr->desc.pmid = PM_ID_NULL;
+ curr->desc.type = PM_TYPE_U32;
+ curr->desc.indom = PM_INDOM_NULL;
+ curr->desc.sem = PM_SEM_DISCRETE;
+ memset(&curr->desc.units, 0, sizeof(pmUnits));
+ }
+ state = P_LEAF;
+ }
+ else if (type == L_LPAREN) {
+ expr = curr = parse(level+1);
+ if (expr == NULL)
+ return NULL;
+ state = P_LEAF_PAREN;
+ }
+ else if (type == L_AVG || type == L_COUNT
+ || type == L_DELTA || type == L_RATE
+ || type == L_MAX || type == L_MIN || type == L_SUM
+ || type == L_ANON) {
+ expr = curr = newnode(type);
+ state = P_FUNC_OP;
+ }
+ else {
+ free_expr(expr);
+ return NULL;
+ }
+ break;
+
+ case P_LEAF_PAREN: /* fall through */
+ case P_LEAF:
+ if (type == L_PLUS || type == L_MINUS || type == L_STAR || type == L_SLASH) {
+ np = newnode(type);
+ if (state == P_LEAF_PAREN ||
+ curr->type == L_NAME || curr->type == L_NUMBER ||
+ curr->type == L_AVG || curr->type == L_COUNT ||
+ curr->type == L_DELTA || curr->type == L_RATE ||
+ curr->type == L_MAX || curr->type == L_MIN ||
+ curr->type == L_SUM || curr->type == L_ANON ||
+ type == L_PLUS || type == L_MINUS) {
+ /*
+ * first operator or equal or lower precedence
+ * make new root of tree and push previous
+ * expr down left descendent branch
+ */
+ np->left = curr;
+ expr = curr = np;
+ }
+ else {
+ /*
+ * push previous right branch down one level
+ */
+ np->left = curr->right;
+ curr->right = np;
+ curr = np;
+ }
+ state = P_BINOP;
+ }
+ else {
+ free_expr(expr);
+ return NULL;
+ }
+ break;
+
+ case P_BINOP:
+ if (type == L_NAME || type == L_NUMBER) {
+ np = newnode(type);
+ if ((np->value = strdup(tokbuf)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: leaf node", strlen(tokbuf)+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if (type == L_NUMBER) {
+ np->desc.pmid = PM_ID_NULL;
+ np->desc.type = PM_TYPE_U32;
+ np->desc.indom = PM_INDOM_NULL;
+ np->desc.sem = PM_SEM_DISCRETE;
+ memset(&np->desc.units, 0, sizeof(pmUnits));
+ }
+ curr->right = np;
+ curr = expr;
+ state = P_LEAF;
+ }
+ else if (type == L_LPAREN) {
+ np = parse(level+1);
+ if (np == NULL)
+ return NULL;
+ curr->right = np;
+ state = P_LEAF_PAREN;
+ }
+ else if (type == L_AVG || type == L_COUNT
+ || type == L_DELTA || type == L_RATE
+ || type == L_MAX || type == L_MIN || type == L_SUM
+ || type == L_ANON) {
+ np = newnode(type);
+ curr->right = np;
+ curr = np;
+ state = P_FUNC_OP;
+ }
+ else {
+ free_expr(expr);
+ return NULL;
+ }
+ break;
+
+ case P_FUNC_OP:
+ if (type == L_NAME) {
+ np = newnode(type);
+ if ((np->value = strdup(tokbuf)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: func op node", strlen(tokbuf)+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ np->save_last = 1;
+ if (curr->type == L_ANON) {
+ /*
+ * anon(PM_TYPE_...) is a special case ... the
+ * argument defines the metric type, the remainder
+ * of the metadata is fixed and there are never any
+ * values available. So we build the pmDesc and then
+ * clobber the "left" node and prevent any attempt to
+ * contact a PMDA for metadata or values
+ */
+ if (strcmp(np->value, "PM_TYPE_32") == 0)
+ np->desc.type = PM_TYPE_32;
+ else if (strcmp(np->value, "PM_TYPE_U32") == 0)
+ np->desc.type = PM_TYPE_U32;
+ else if (strcmp(np->value, "PM_TYPE_64") == 0)
+ np->desc.type = PM_TYPE_64;
+ else if (strcmp(np->value, "PM_TYPE_U64") == 0)
+ np->desc.type = PM_TYPE_U64;
+ else if (strcmp(np->value, "PM_TYPE_FLOAT") == 0)
+ np->desc.type = PM_TYPE_FLOAT;
+ else if (strcmp(np->value, "PM_TYPE_DOUBLE") == 0)
+ np->desc.type = PM_TYPE_DOUBLE;
+ else {
+ fprintf(stderr, "Error: type=%s not allowed for anon()\n", np->value);
+ free_expr(np);
+ return NULL;
+ }
+ np->desc.pmid = PM_ID_NULL;
+ np->desc.indom = PM_INDOM_NULL;
+ np->desc.sem = PM_SEM_DISCRETE;
+ memset((void *)&np->desc.units, 0, sizeof(np->desc.units));
+ np->type = L_NUMBER;
+ }
+ curr->left = np;
+ curr = expr;
+ state = P_FUNC_END;
+ }
+ else {
+ free_expr(expr);
+ return NULL;
+ }
+ break;
+
+ default:
+ free_expr(expr);
+ return NULL;
+ }
+ }
+}
+
+static int
+checkname(char *p)
+{
+ int firstch = 1;
+
+ for ( ; *p; p++) {
+ if (firstch) {
+ firstch = 0;
+ if (isalpha((int)*p)) continue;
+ return -1;
+ }
+ else {
+ if (isalpha((int)*p) || isdigit((int)*p) || *p == '_') continue;
+ if (*p == '.') {
+ firstch = 1;
+ continue;
+ }
+ return -1;
+ }
+ }
+ return 0;
+}
+
+char *
+pmRegisterDerived(const char *name, const char *expr)
+{
+ node_t *np;
+ static __pmID_int pmid;
+ int i;
+
+ PM_INIT_LOCKS();
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL0)) {
+ fprintf(stderr, "pmRegisterDerived: name=\"%s\" expr=\"%s\"\n", name, expr);
+ }
+#endif
+
+ for (i = 0; i < registered.nmetric; i++) {
+ if (strcmp(name, registered.mlist[i].name) == 0) {
+ /* oops, duplicate name ... */
+ PM_TPD(derive_errmsg) = "Duplicate derived metric name";
+ PM_UNLOCK(registered.mutex);
+ return (char *)expr;
+ }
+ }
+
+ PM_TPD(derive_errmsg) = NULL;
+ string = expr;
+ np = parse(1);
+ if (np == NULL) {
+ /* parser error */
+ char *sts = (char *)this;
+ PM_UNLOCK(registered.mutex);
+ return sts;
+ }
+
+ registered.nmetric++;
+ registered.mlist = (dm_t *)realloc(registered.mlist, registered.nmetric*sizeof(dm_t));
+ if (registered.mlist == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmRegisterDerived: registered mlist", registered.nmetric*sizeof(dm_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if (registered.nmetric == 1) {
+ pmid.flag = 0;
+ pmid.domain = DYNAMIC_PMID;
+ pmid.cluster = 0;
+ }
+ registered.mlist[registered.nmetric-1].name = strdup(name);
+ pmid.item = registered.nmetric;
+ registered.mlist[registered.nmetric-1].pmid = *((pmID *)&pmid);
+ registered.mlist[registered.nmetric-1].expr = np;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ fprintf(stderr, "pmRegisterDerived: register metric[%d] %s = %s\n", registered.nmetric-1, name, expr);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __dmdumpexpr(np, 0);
+ }
+#endif
+
+ PM_UNLOCK(registered.mutex);
+ return NULL;
+}
+
+int
+pmLoadDerivedConfig(const char *fname)
+{
+ FILE *fp;
+ int buflen;
+ char *buf;
+ char *p;
+ int c;
+ int sts = 0;
+ int eq = -1;
+ int lineno = 1;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ fprintf(stderr, "pmLoadDerivedConfig(\"%s\")\n", fname);
+ }
+#endif
+
+ if ((fp = fopen(fname, "r")) == NULL) {
+ return -oserror();
+ }
+ buflen = 128;
+ if ((buf = (char *)malloc(buflen)) == NULL) {
+ /* registered.mutex not locked in this case */
+ __pmNoMem("pmLoadDerivedConfig: alloc buf", buflen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ p = buf;
+ while ((c = fgetc(fp)) != EOF) {
+ if (p == &buf[buflen]) {
+ if ((buf = (char *)realloc(buf, 2*buflen)) == NULL) {
+ /* registered.mutex not locked in this case */
+ __pmNoMem("pmLoadDerivedConfig: expand buf", 2*buflen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ p = &buf[buflen];
+ buflen *= 2;
+ }
+ if (c == '=' && eq == -1) {
+ /*
+ * mark first = in line ... metric name to the left and
+ * expression to the right
+ */
+ eq = p - buf;
+ }
+ if (c == '\n') {
+ if (p == buf || buf[0] == '#') {
+ /* comment or empty line, skip it ... */
+ goto next_line;
+ }
+ *p = '\0';
+ if (eq != -1) {
+ char *np; /* copy of name */
+ char *ep; /* start of expression */
+ char *q;
+ char *errp;
+ buf[eq] = '\0';
+ if ((np = strdup(buf)) == NULL) {
+ /* registered.mutex not locked in this case */
+ __pmNoMem("pmLoadDerivedConfig: dupname", strlen(buf), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ /* trim white space from tail of metric name */
+ q = &np[eq-1];
+ while (q >= np && isspace((int)*q))
+ *q-- = '\0';
+ /* trim white space from head of metric name */
+ q = np;
+ while (*q && isspace((int)*q))
+ q++;
+ if (*q == '\0') {
+ buf[eq] = '=';
+ pmprintf("[%s:%d] Error: pmLoadDerivedConfig: derived metric name missing\n%s\n", fname, lineno, buf);
+ pmflush();
+ free(np);
+ goto next_line;
+ }
+ if (checkname(q) < 0) {
+ pmprintf("[%s:%d] Error: pmLoadDerivedConfig: illegal derived metric name (%s)\n", fname, lineno, q);
+ pmflush();
+ free(np);
+ goto next_line;
+ }
+ ep = &buf[eq+1];
+ while (*ep != '\0' && isspace((int)*ep))
+ ep++;
+ if (*ep == '\0') {
+ buf[eq] = '=';
+ pmprintf("[%s:%d] Error: pmLoadDerivedConfig: expression missing\n%s\n", fname, lineno, buf);
+ pmflush();
+ free(np);
+ goto next_line;
+ }
+ errp = pmRegisterDerived(q, ep);
+ if (errp != NULL) {
+ pmprintf("[%s:%d] Error: pmRegisterDerived(%s, ...) syntax error\n", fname, lineno, q);
+ pmprintf("%s\n", &buf[eq+1]);
+ for (q = &buf[eq+1]; *q; q++) {
+ if (q == errp) *q = '^';
+ else if (!isspace((int)*q)) *q = ' ';
+ }
+ pmprintf("%s\n", &buf[eq+1]);
+ q = pmDerivedErrStr();
+ if (q != NULL) pmprintf("%s\n", q);
+ pmflush();
+ }
+ else
+ sts++;
+ free(np);
+ }
+ else {
+ /*
+ * error ... no = in the line, so no derived metric name
+ */
+ pmprintf("[%s:%d] Error: pmLoadDerivedConfig: missing ``='' after derived metric name\n%s\n", fname, lineno, buf);
+ pmflush();
+ }
+next_line:
+ lineno++;
+ p = buf;
+ eq = -1;
+ }
+ else
+ *p++ = c;
+ }
+ fclose(fp);
+ free(buf);
+ return sts;
+}
+
+char *
+pmDerivedErrStr(void)
+{
+ PM_INIT_LOCKS();
+ return PM_TPD(derive_errmsg);
+}
+
+/*
+ * callbacks
+ */
+
+int
+__dmtraverse(const char *name, char ***namelist)
+{
+ int sts = 0;
+ int i;
+ char **list = NULL;
+ int matchlen = strlen(name);
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+ init();
+
+ for (i = 0; i < registered.nmetric; i++) {
+ /*
+ * prefix match ... if name is "", then all names match
+ */
+ if (matchlen == 0 ||
+ (strncmp(name, registered.mlist[i].name, matchlen) == 0 &&
+ (registered.mlist[i].name[matchlen] == '.' ||
+ registered.mlist[i].name[matchlen] == '\0'))) {
+ sts++;
+ if ((list = (char **)realloc(list, sts*sizeof(list[0]))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("__dmtraverse: list", sts*sizeof(list[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ list[sts-1] = registered.mlist[i].name;
+ }
+ }
+ *namelist = list;
+
+ PM_UNLOCK(registered.mutex);
+ return sts;
+}
+
+int
+__dmchildren(const char *name, char ***offspring, int **statuslist)
+{
+ int sts = 0;
+ int i;
+ int j;
+ char **children = NULL;
+ int *status = NULL;
+ int matchlen = strlen(name);
+ int start;
+ int len;
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+ init();
+
+ for (i = 0; i < registered.nmetric; i++) {
+ /*
+ * prefix match ... pick off the unique next level names on match
+ */
+ if (name[0] == '\0' ||
+ (strncmp(name, registered.mlist[i].name, matchlen) == 0 &&
+ (registered.mlist[i].name[matchlen] == '.' ||
+ registered.mlist[i].name[matchlen] == '\0'))) {
+ if (registered.mlist[i].name[matchlen] == '\0') {
+ /*
+ * leaf node
+ * assert is for coverity, name uniqueness means we
+ * should only ever come here after zero passes through
+ * the block below where sts is incremented and children[]
+ * and status[] are realloc'd
+ */
+ assert(sts == 0 && children == NULL && status == NULL);
+ PM_UNLOCK(registered.mutex);
+ return 0;
+ }
+ start = matchlen > 0 ? matchlen + 1 : 0;
+ for (j = 0; j < sts; j++) {
+ len = strlen(children[j]);
+ if (strncmp(&registered.mlist[i].name[start], children[j], len) == 0 &&
+ registered.mlist[i].name[start+len] == '.')
+ break;
+ }
+ if (j == sts) {
+ /* first time for this one */
+ sts++;
+ if ((children = (char **)realloc(children, sts*sizeof(children[0]))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("__dmchildren: children", sts*sizeof(children[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (len = 0; registered.mlist[i].name[start+len] != '\0' && registered.mlist[i].name[start+len] != '.'; len++)
+ ;
+ if ((children[sts-1] = (char *)malloc(len+1)) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("__dmchildren: name", len+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ strncpy(children[sts-1], &registered.mlist[i].name[start], len);
+ children[sts-1][len] = '\0';
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) {
+ fprintf(stderr, "__dmchildren: offspring[%d] %s", sts-1, children[sts-1]);
+ }
+#endif
+
+ if (statuslist != NULL) {
+ if ((status = (int *)realloc(status, sts*sizeof(status[0]))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("__dmchildren: statrus", sts*sizeof(status[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ status[sts-1] = registered.mlist[i].name[start+len] == '\0' ? PMNS_LEAF_STATUS : PMNS_NONLEAF_STATUS;
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) {
+ fprintf(stderr, " (status=%d)", status[sts-1]);
+ }
+#endif
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) {
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ }
+ }
+
+ if (sts == 0) {
+ PM_UNLOCK(registered.mutex);
+ return PM_ERR_NAME;
+ }
+
+ *offspring = children;
+ if (statuslist != NULL)
+ *statuslist = status;
+
+ PM_UNLOCK(registered.mutex);
+ return sts;
+}
+
+int
+__dmgetpmid(const char *name, pmID *dp)
+{
+ int i;
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+ init();
+
+ for (i = 0; i < registered.nmetric; i++) {
+ if (strcmp(name, registered.mlist[i].name) == 0) {
+ *dp = registered.mlist[i].pmid;
+ PM_UNLOCK(registered.mutex);
+ return 0;
+ }
+ }
+ PM_UNLOCK(registered.mutex);
+ return PM_ERR_NAME;
+}
+
+int
+__dmgetname(pmID pmid, char ** name)
+{
+ int i;
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+ init();
+
+ for (i = 0; i < registered.nmetric; i++) {
+ if (pmid == registered.mlist[i].pmid) {
+ *name = strdup(registered.mlist[i].name);
+ if (*name == NULL) {
+ PM_UNLOCK(registered.mutex);
+ return -oserror();
+ }
+ else {
+ PM_UNLOCK(registered.mutex);
+ return 0;
+ }
+ }
+ }
+ PM_UNLOCK(registered.mutex);
+ return PM_ERR_PMID;
+}
+
+void
+__dmopencontext(__pmContext *ctxp)
+{
+ int i;
+ int sts;
+ ctl_t *cp;
+
+#ifdef PM_MULTI_THREAD
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ initialize_mutex();
+#endif
+#endif
+ PM_LOCK(registered.mutex);
+ init();
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL1)) {
+ fprintf(stderr, "__dmopencontext(->ctx %d) called\n", __pmPtrToHandle(ctxp));
+ }
+#endif
+ if (registered.nmetric == 0) {
+ ctxp->c_dm = NULL;
+ PM_UNLOCK(registered.mutex);
+ return;
+ }
+ if ((cp = (void *)malloc(sizeof(ctl_t))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmNewContext: derived metrics (ctl)", sizeof(ctl_t), PM_FATAL_ERR);
+ /* NOTREACHED */
+ }
+ ctxp->c_dm = (void *)cp;
+ cp->nmetric = registered.nmetric;
+ if ((cp->mlist = (dm_t *)malloc(cp->nmetric*sizeof(dm_t))) == NULL) {
+ PM_UNLOCK(registered.mutex);
+ __pmNoMem("pmNewContext: derived metrics (mlist)", cp->nmetric*sizeof(dm_t), PM_FATAL_ERR);
+ /* NOTREACHED */
+ }
+ for (i = 0; i < cp->nmetric; i++) {
+ cp->mlist[i].name = registered.mlist[i].name;
+ cp->mlist[i].pmid = registered.mlist[i].pmid;
+ assert(registered.mlist[i].expr != NULL);
+ /* failures must be reported in bind_expr() or below */
+ cp->mlist[i].expr = bind_expr(i, registered.mlist[i].expr);
+ if (cp->mlist[i].expr != NULL) {
+ /* failures must be reported in check_expr() or below */
+ sts = check_expr(i, cp->mlist[i].expr);
+ if (sts < 0) {
+ free_expr(cp->mlist[i].expr);
+ cp->mlist[i].expr = NULL;
+ }
+ else {
+ /* set correct PMID in pmDesc at the top level */
+ cp->mlist[i].expr->desc.pmid = cp->mlist[i].pmid;
+ }
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && cp->mlist[i].expr != NULL) {
+ fprintf(stderr, "__dmopencontext: bind metric[%d] %s\n", i, registered.mlist[i].name);
+ if (pmDebug & DBG_TRACE_APPL1)
+ __dmdumpexpr(cp->mlist[i].expr, 0);
+ }
+#endif
+ }
+ PM_UNLOCK(registered.mutex);
+}
+
+void
+__dmclosecontext(__pmContext *ctxp)
+{
+ int i;
+ ctl_t *cp = (ctl_t *)ctxp->c_dm;
+
+ /* if needed, init() called in __dmopencontext beforehand */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ fprintf(stderr, "__dmclosecontext(->ctx %d) called dm->" PRINTF_P_PFX "%p %d metrics\n", __pmPtrToHandle(ctxp), cp, cp == NULL ? -1 : cp->nmetric);
+ }
+#endif
+ if (cp == NULL) return;
+ for (i = 0; i < cp->nmetric; i++) {
+ free_expr(cp->mlist[i].expr);
+ }
+ free(cp->mlist);
+ free(cp);
+ ctxp->c_dm = NULL;
+}
+
+int
+__dmdesc(__pmContext *ctxp, pmID pmid, pmDesc *desc)
+{
+ int i;
+ ctl_t *cp = (ctl_t *)ctxp->c_dm;
+
+ /* if needed, init() called in __dmopencontext beforehand */
+
+ if (cp == NULL) return PM_ERR_PMID;
+
+ for (i = 0; i < cp->nmetric; i++) {
+ if (cp->mlist[i].pmid == pmid) {
+ if (cp->mlist[i].expr == NULL)
+ /* bind failed for some reason, reported earlier */
+ return PM_ERR_NAME;
+ *desc = cp->mlist[i].expr->desc;
+ return 0;
+ }
+ }
+ return PM_ERR_PMID;
+}
+
+#ifdef PM_MULTI_THREAD
+#ifdef PM_MULTI_THREAD_DEBUG
+/*
+ * return true if lock == registered.mutex ... no locking here to avoid
+ * recursion ad nauseum
+ */
+int
+__pmIsDeriveLock(void *lock)
+{
+ return lock == (void *)&registered.mutex;
+}
+#endif
+#endif
diff --git a/src/libpcp/src/derive.h b/src/libpcp/src/derive.h
new file mode 100644
index 0000000..56a0196
--- /dev/null
+++ b/src/libpcp/src/derive.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2009 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef _DERIVE_H
+#define _DERIVE_H
+
+/*
+ * Derived Metrics support
+ */
+
+typedef struct { /* one value in the expression tree */
+ int inst;
+ pmAtomValue value;
+ int vlen; /* from vlen of pmValueBlock for string and aggregates */
+} val_t;
+
+typedef struct { /* dynamic information for an expression node */
+ pmID pmid;
+ int numval; /* length of ivlist[] */
+ int mul_scale; /* scale multiplier */
+ int div_scale; /* scale divisor */
+ val_t *ivlist; /* instance-value pairs */
+ struct timeval stamp; /* timestamp from current fetch */
+ double time_scale; /* time utilization scaling for rate() */
+ int last_numval; /* length of last_ivlist[] */
+ val_t *last_ivlist; /* values from previous fetch for delta() or rate() */
+ struct timeval last_stamp; /* timestamp from previous fetch for rate() */
+} info_t;
+
+typedef struct node { /* expression tree node */
+ int type;
+ pmDesc desc;
+ int save_last;
+ struct node *left;
+ struct node *right;
+ char *value;
+ info_t *info;
+} node_t;
+
+typedef struct { /* one derived metric */
+ char *name;
+ pmID pmid;
+ node_t *expr;
+} dm_t;
+
+/*
+ * Control structure for a set of derived metrics.
+ * This is used for the static definitions (registered) and the dynamic
+ * tree of expressions maintained per context.
+ */
+typedef struct {
+ __pmMutex mutex;
+ int nmetric; /* derived metrics */
+ dm_t *mlist;
+ int fetch_has_dm; /* ==1 if pmResult rewrite needed */
+ int numpmid; /* from pmFetch before rewrite */
+} ctl_t;
+
+/* lexical types */
+#define L_ERROR -2
+#define L_EOF -1
+#define L_UNDEF 0
+#define L_NUMBER 1
+#define L_NAME 2
+#define L_PLUS 3
+#define L_MINUS 4
+#define L_STAR 5
+#define L_SLASH 6
+#define L_LPAREN 7
+#define L_RPAREN 8
+#define L_AVG 9
+#define L_COUNT 10
+#define L_DELTA 11
+#define L_MAX 12
+#define L_MIN 13
+#define L_SUM 14
+#define L_ANON 15
+#define L_RATE 16
+
+extern int __dmtraverse(const char *, char ***) _PCP_HIDDEN;
+extern int __dmchildren(const char *, char ***, int **) _PCP_HIDDEN;
+extern int __dmgetpmid(const char *, pmID *) _PCP_HIDDEN;
+extern int __dmgetname(pmID, char **) _PCP_HIDDEN;
+extern void __dmopencontext(__pmContext *) _PCP_HIDDEN;
+extern void __dmclosecontext(__pmContext *) _PCP_HIDDEN;
+extern int __dmdesc(__pmContext *, pmID, pmDesc *) _PCP_HIDDEN;
+extern int __dmprefetch(__pmContext *, int, const pmID *, pmID **) _PCP_HIDDEN;
+extern void __dmpostfetch(__pmContext *, pmResult **) _PCP_HIDDEN;
+extern void __dmdumpexpr(node_t *, int) _PCP_HIDDEN;
+
+#endif /* _DERIVE_H */
diff --git a/src/libpcp/src/derive_fetch.c b/src/libpcp/src/derive_fetch.c
new file mode 100644
index 0000000..061c67a
--- /dev/null
+++ b/src/libpcp/src/derive_fetch.c
@@ -0,0 +1,1317 @@
+/*
+ * Copyright (c) 2009,2014 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Debug Flags
+ * DERIVE - high-level diagnostics
+ * DERIVE & APPL0 - configuration and static syntax analysis
+ * DERIVE & APPL1 - expression binding and semantic analysis
+ * DERIVE & APPL2 - fetch handling
+ */
+
+#include <inttypes.h>
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+static void
+get_pmids(node_t *np, int *cnt, pmID **list)
+{
+ assert(np != NULL);
+ if (np->left != NULL) get_pmids(np->left, cnt, list);
+ if (np->right != NULL) get_pmids(np->right, cnt, list);
+ if (np->type == L_NAME) {
+ (*cnt)++;
+ if ((*list = (pmID *)realloc(*list, (*cnt)*sizeof(pmID))) == NULL) {
+ __pmNoMem("__dmprefetch: realloc xtralist", (*cnt)*sizeof(pmID), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ (*list)[*cnt-1] = np->info->pmid;
+ }
+}
+
+/*
+ * Walk the pmidlist[] from pmFetch.
+ * For each derived metric found in the list add all the operand metrics,
+ * and build a combined pmID list (newlist).
+ *
+ * Return 0 if no derived metrics in the list, else the number of pmIDs
+ * in the combined list.
+ *
+ * The derived metric pmIDs are left in the combined list (they will
+ * return PM_ERR_NOAGENT from the fetch) to simplify the post-processing
+ * of the pmResult in __dmpostfetch()
+ */
+int
+__dmprefetch(__pmContext *ctxp, int numpmid, const pmID *pmidlist, pmID **newlist)
+{
+ int i;
+ int j;
+ int m;
+ int xtracnt = 0;
+ pmID *xtralist = NULL;
+ pmID *list;
+ ctl_t *cp = (ctl_t *)ctxp->c_dm;
+
+ /* if needed, init() called in __dmopencontext beforehand */
+
+ if (cp == NULL) return 0;
+
+ /*
+ * save numpmid to be used in __dmpostfetch() ... works because calls
+ * to pmFetch cannot be nested (at all, but certainly for the same
+ * context).
+ * Ditto for the fast path flag (fetch_has_dm).
+ */
+ cp->numpmid = numpmid;
+ cp->fetch_has_dm = 0;
+
+ for (m = 0; m < numpmid; m++) {
+ if (pmid_domain(pmidlist[m]) != DYNAMIC_PMID ||
+ pmid_item(pmidlist[m]) == 0)
+ continue;
+ for (i = 0; i < cp->nmetric; i++) {
+ if (pmidlist[m] == cp->mlist[i].pmid) {
+ if (cp->mlist[i].expr != NULL) {
+ get_pmids(cp->mlist[i].expr, &xtracnt, &xtralist);
+ cp->fetch_has_dm = 1;
+ }
+ break;
+ }
+ }
+ }
+ if (xtracnt == 0) {
+ if (cp->fetch_has_dm)
+ return numpmid;
+ else
+ return 0;
+ }
+
+ /*
+ * Some of the "extra" ones, may already be in the caller's pmFetch
+ * list, or repeated in xtralist[] (if the same metric operand appears
+ * more than once as a leaf node in the expression tree.
+ * Remove these duplicates
+ */
+ j = 0;
+ for (i = 0; i < xtracnt; i++) {
+ for (m = 0; m < numpmid; m++) {
+ if (xtralist[i] == pmidlist[m])
+ /* already in pmFetch list */
+ break;
+ }
+ if (m < numpmid) continue;
+ for (m = 0; m < j; m++) {
+ if (xtralist[i] == xtralist[m])
+ /* already in xtralist[] */
+ break;
+ }
+ if (m == j)
+ xtralist[j++] = xtralist[i];
+ }
+ xtracnt = j;
+ if (xtracnt == 0) {
+ free(xtralist);
+ return numpmid;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ char strbuf[20];
+ fprintf(stderr, "derived metrics prefetch added %d metrics:", xtracnt);
+ for (i = 0; i < xtracnt; i++)
+ fprintf(stderr, " %s", pmIDStr_r(xtralist[i], strbuf, sizeof(strbuf)));
+ fputc('\n', stderr);
+ }
+#endif
+ if ((list = (pmID *)malloc((numpmid+xtracnt)*sizeof(pmID))) == NULL) {
+ __pmNoMem("__dmprefetch: alloc list", (numpmid+xtracnt)*sizeof(pmID), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (m = 0; m < numpmid; m++) {
+ list[m] = pmidlist[m];
+ }
+ for (i = 0; i < xtracnt; i++) {
+ list[m++] = xtralist[i];
+ }
+ free(xtralist);
+ *newlist = list;
+
+ return m;
+}
+
+/*
+ * Free the old ivlist[] (if any) ... may need to walk the list because
+ * the pmAtomValues may have buffers attached in the type STRING,
+ * type AGGREGATE* and type EVENT cases.
+ * Includes logic to save one history sample (for delta() and rate()).
+ */
+static void
+free_ivlist(node_t *np)
+{
+ int i;
+
+ assert(np->info != NULL);
+
+ if (np->save_last) {
+ /*
+ * saving history for delta() or rate() ... release previous
+ * sample, and save this sample
+ */
+ if (np->info->last_ivlist != NULL) {
+ /*
+ * no STRING, AGGREGATE or EVENT types for delta() or rate()
+ * so simple free()
+ */
+ free(np->info->last_ivlist);
+ }
+ np->info->last_numval = np->info->numval;
+ np->info->last_ivlist = np->info->ivlist;
+ np->info->ivlist = NULL;
+ }
+ else {
+ /* no history */
+ if (np->info->ivlist != NULL) {
+ if (np->desc.type == PM_TYPE_STRING) {
+ for (i = 0; i < np->info->numval; i++) {
+ if (np->info->ivlist[i].value.cp != NULL)
+ free(np->info->ivlist[i].value.cp);
+ }
+ }
+ else if (np->desc.type == PM_TYPE_AGGREGATE ||
+ np->desc.type == PM_TYPE_AGGREGATE_STATIC ||
+ np->desc.type == PM_TYPE_EVENT ||
+ np->desc.type == PM_TYPE_HIGHRES_EVENT) {
+ for (i = 0; i < np->info->numval; i++) {
+ if (np->info->ivlist[i].value.vbp != NULL)
+ free(np->info->ivlist[i].value.vbp);
+ }
+ }
+ }
+ free(np->info->ivlist);
+ np->info->numval = 0;
+ np->info->ivlist = NULL;
+ }
+}
+
+/*
+ * Binary arithmetic.
+ *
+ * result = <a> <op> <b>
+ * ltype, rtype and type are the types of <a>, <b> and the result
+ * respectively
+ *
+ * If type is PM_TYPE_DOUBLE then lmul, ldiv, rmul and rdiv are
+ * the scale factors for units scale conversion of <a> and <b>
+ * respectively, so lmul*<a>/ldiv ... all are 1 in the common cases.
+ */
+static pmAtomValue
+bin_op(int type, int op, pmAtomValue a, int ltype, int lmul, int ldiv, pmAtomValue b, int rtype, int rmul, int rdiv)
+{
+ pmAtomValue res;
+ pmAtomValue l;
+ pmAtomValue r;
+
+ l = a; /* struct assignments */
+ r = b;
+
+ /*
+ * Promote each operand to the type of the result ... there are limited
+ * cases to be considered here, see promote[][] and map_desc().
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ /* do nothing */
+ break;
+ case PM_TYPE_64:
+ switch (ltype) {
+ case PM_TYPE_32:
+ l.ll = a.l;
+ break;
+ case PM_TYPE_U32:
+ l.ll = a.ul;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ /* do nothing */
+ break;
+ }
+ switch (rtype) {
+ case PM_TYPE_32:
+ r.ll = b.l;
+ break;
+ case PM_TYPE_U32:
+ r.ll = b.ul;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ /* do nothing */
+ break;
+ }
+ break;
+ case PM_TYPE_U64:
+ switch (ltype) {
+ case PM_TYPE_32:
+ l.ull = a.l;
+ break;
+ case PM_TYPE_U32:
+ l.ull = a.ul;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ /* do nothing */
+ break;
+ }
+ switch (rtype) {
+ case PM_TYPE_32:
+ r.ull = b.l;
+ break;
+ case PM_TYPE_U32:
+ r.ull = b.ul;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ /* do nothing */
+ break;
+ }
+ break;
+ case PM_TYPE_FLOAT:
+ switch (ltype) {
+ case PM_TYPE_32:
+ l.f = a.l;
+ break;
+ case PM_TYPE_U32:
+ l.f = a.ul;
+ break;
+ case PM_TYPE_64:
+ l.f = a.ll;
+ break;
+ case PM_TYPE_U64:
+ l.f = a.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ /* do nothing */
+ break;
+ }
+ switch (rtype) {
+ case PM_TYPE_32:
+ r.f = b.l;
+ break;
+ case PM_TYPE_U32:
+ r.f = b.ul;
+ break;
+ case PM_TYPE_64:
+ r.f = b.ll;
+ break;
+ case PM_TYPE_U64:
+ r.f = b.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ /* do nothing */
+ break;
+ }
+ break;
+ case PM_TYPE_DOUBLE:
+ switch (ltype) {
+ case PM_TYPE_32:
+ l.d = a.l;
+ break;
+ case PM_TYPE_U32:
+ l.d = a.ul;
+ break;
+ case PM_TYPE_64:
+ l.d = a.ll;
+ break;
+ case PM_TYPE_U64:
+ l.d = a.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ l.d = a.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ /* do nothing */
+ break;
+ }
+ l.d = (l.d / ldiv) * lmul;
+ switch (rtype) {
+ case PM_TYPE_32:
+ r.d = b.l;
+ break;
+ case PM_TYPE_U32:
+ r.d = b.ul;
+ break;
+ case PM_TYPE_64:
+ r.d = b.ll;
+ break;
+ case PM_TYPE_U64:
+ r.d = b.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ r.d = b.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ /* do nothing */
+ break;
+ }
+ r.d = (r.d / rdiv) * rmul;
+ break;
+ }
+
+ /*
+ * Do the aritmetic ... messy!
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ switch (op) {
+ case L_PLUS:
+ res.l = l.l + r.l;
+ break;
+ case L_MINUS:
+ res.l = l.l - r.l;
+ break;
+ case L_STAR:
+ res.l = l.l * r.l;
+ break;
+ /* semantics enforce no L_SLASH for integer results */
+ }
+ break;
+ case PM_TYPE_U32:
+ switch (op) {
+ case L_PLUS:
+ res.ul = l.ul + r.ul;
+ break;
+ case L_MINUS:
+ res.ul = l.ul - r.ul;
+ break;
+ case L_STAR:
+ res.ul = l.ul * r.ul;
+ break;
+ /* semantics enforce no L_SLASH for integer results */
+ }
+ break;
+ case PM_TYPE_64:
+ switch (op) {
+ case L_PLUS:
+ res.ll = l.ll + r.ll;
+ break;
+ case L_MINUS:
+ res.ll = l.ll - r.ll;
+ break;
+ case L_STAR:
+ res.ll = l.ll * r.ll;
+ break;
+ /* semantics enforce no L_SLASH for integer results */
+ }
+ break;
+ case PM_TYPE_U64:
+ switch (op) {
+ case L_PLUS:
+ res.ull = l.ull + r.ull;
+ break;
+ case L_MINUS:
+ res.ull = l.ull - r.ull;
+ break;
+ case L_STAR:
+ res.ull = l.ull * r.ull;
+ break;
+ /* semantics enforce no L_SLASH for integer results */
+ }
+ break;
+ case PM_TYPE_FLOAT:
+ switch (op) {
+ case L_PLUS:
+ res.f = l.f + r.f;
+ break;
+ case L_MINUS:
+ res.f = l.f - r.f;
+ break;
+ case L_STAR:
+ res.f = l.f * r.f;
+ break;
+ /* semantics enforce no L_SLASH for float results */
+ }
+ break;
+ case PM_TYPE_DOUBLE:
+ switch (op) {
+ case L_PLUS:
+ res.d = l.d + r.d;
+ break;
+ case L_MINUS:
+ res.d = l.d - r.d;
+ break;
+ case L_STAR:
+ res.d = l.d * r.d;
+ break;
+ case L_SLASH:
+ if (l.d == 0)
+ res.d = 0;
+ else
+ res.d = l.d / r.d;
+ break;
+ }
+ break;
+ }
+
+ return res;
+}
+
+
+/*
+ * Walk an expression tree, filling in operand values from the
+ * pmResult at the leaf nodes and propagating the computed values
+ * towards the root node of the tree.
+ */
+static int
+eval_expr(node_t *np, pmResult *rp, int level)
+{
+ int sts;
+ int i;
+ int j;
+ int k;
+ size_t need;
+
+ assert(np != NULL);
+ if (np->left != NULL) {
+ sts = eval_expr(np->left, rp, level+1);
+ if (sts < 0) return sts;
+ }
+ if (np->right != NULL) {
+ sts = eval_expr(np->right, rp, level+1);
+ if (sts < 0) return sts;
+ }
+
+ /* mostly, np->left is not NULL ... */
+ assert (np->type == L_NUMBER || np->type == L_NAME || np->left != NULL);
+
+ switch (np->type) {
+
+ case L_NUMBER:
+ if (np->info->numval == 0) {
+ /* initialize ivlist[] for singular instance first time through */
+ np->info->numval = 1;
+ if ((np->info->ivlist = (val_t *)malloc(sizeof(val_t))) == NULL) {
+ __pmNoMem("eval_expr: number ivlist", sizeof(val_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ np->info->ivlist[0].inst = PM_INDOM_NULL;
+ /* don't need error checking, done in the lexical scanner */
+ np->info->ivlist[0].value.l = atoi(np->value);
+ }
+ return 1;
+ break;
+
+ case L_DELTA:
+ case L_RATE:
+ /*
+ * this and the last values are in the left expr
+ */
+ np->info->last_stamp = np->info->stamp;
+ np->info->stamp = rp->timestamp;
+ free_ivlist(np);
+ np->info->numval = np->left->info->numval <= np->left->info->last_numval ? np->left->info->numval : np->left->info->last_numval;
+ if (np->info->numval <= 0)
+ return np->info->numval;
+ if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) {
+ __pmNoMem("eval_expr: delta()/rate() ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ /*
+ * delta()
+ * ivlist[k] = left->ivlist[i] - left->last_ivlist[j]
+ * rate()
+ * ivlist[k] = (left->ivlist[i] - left->last_ivlist[j]) /
+ * (timestamp - left->last_stamp)
+ */
+ for (i = k = 0; i < np->left->info->numval; i++) {
+ j = i;
+ if (j >= np->left->info->last_numval)
+ j = 0;
+ if (np->left->info->ivlist[i].inst != np->left->info->last_ivlist[j].inst) {
+ /* current ith inst != last jth inst ... search in last */
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ fprintf(stderr, "eval_expr: inst[%d] mismatch left [%d]=%d last [%d]=%d\n", k, i, np->left->info->ivlist[i].inst, j, np->left->info->last_ivlist[j].inst);
+ }
+#endif
+ for (j = 0; j < np->left->info->last_numval; j++) {
+ if (np->left->info->ivlist[i].inst == np->left->info->last_ivlist[j].inst)
+ break;
+ }
+ if (j == np->left->info->last_numval) {
+ /* no match, skip this instance from this result */
+ continue;
+ }
+#ifdef PCP_DEBUG
+ else {
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ fprintf(stderr, "eval_expr: recover @ last [%d]=%d\n", j, np->left->info->last_ivlist[j].inst);
+ }
+ }
+#endif
+ }
+ np->info->ivlist[k].inst = np->left->info->ivlist[i].inst;
+ if (np->type == L_DELTA) {
+ /* for delta() result type == operand type */
+ switch (np->left->desc.type) {
+ case PM_TYPE_32:
+ np->info->ivlist[k].value.l = np->left->info->ivlist[i].value.l - np->left->info->last_ivlist[j].value.l;
+ break;
+ case PM_TYPE_U32:
+ np->info->ivlist[k].value.ul = np->left->info->ivlist[i].value.ul - np->left->info->last_ivlist[j].value.ul;
+ break;
+ case PM_TYPE_64:
+ np->info->ivlist[k].value.ll = np->left->info->ivlist[i].value.ll - np->left->info->last_ivlist[j].value.ll;
+ break;
+ case PM_TYPE_U64:
+ np->info->ivlist[k].value.ull = np->left->info->ivlist[i].value.ull - np->left->info->last_ivlist[j].value.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ np->info->ivlist[k].value.f = np->left->info->ivlist[i].value.f - np->left->info->last_ivlist[j].value.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ np->info->ivlist[k].value.d = np->left->info->ivlist[i].value.d - np->left->info->last_ivlist[j].value.d;
+ break;
+ default:
+ /*
+ * Nothing should end up here as check_expr() checks
+ * for numeric data type at bind time
+ */
+ return PM_ERR_CONV;
+ }
+ }
+ else {
+ /* rate() conversion, type will be DOUBLE */
+ struct timeval stampdiff;
+ stampdiff.tv_sec = np->info->stamp.tv_sec - np->info->last_stamp.tv_sec;
+ stampdiff.tv_usec = np->info->stamp.tv_usec - np->info->last_stamp.tv_usec;
+ if (stampdiff.tv_usec < 0) {
+ stampdiff.tv_usec += 1000000;
+ stampdiff.tv_sec--;
+ }
+ switch (np->left->desc.type) {
+ case PM_TYPE_32:
+ np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.l - np->left->info->last_ivlist[j].value.l);
+ break;
+ case PM_TYPE_U32:
+ np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ul - np->left->info->last_ivlist[j].value.ul);
+ break;
+ case PM_TYPE_64:
+ np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ll - np->left->info->last_ivlist[j].value.ll);
+ break;
+ case PM_TYPE_U64:
+ np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.ull - np->left->info->last_ivlist[j].value.ull);
+ break;
+ case PM_TYPE_FLOAT:
+ np->info->ivlist[k].value.d = (double)(np->left->info->ivlist[i].value.f - np->left->info->last_ivlist[j].value.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ np->info->ivlist[k].value.d = np->left->info->ivlist[i].value.d - np->left->info->last_ivlist[j].value.d;
+ break;
+ default:
+ /*
+ * Nothing should end up here as check_expr() checks
+ * for numeric data type at bind time
+ */
+ return PM_ERR_CONV;
+ }
+ np->info->ivlist[k].value.d /= stampdiff.tv_sec + (double)stampdiff.tv_usec/1000000;
+ /*
+ * check_expr() ensures dimTime is 0 or 1 at bind time
+ */
+ if (np->left->desc.units.dimTime == 1) {
+ /* scale rate(time counter) -> time utilization */
+ if (np->info->time_scale < 0) {
+ /*
+ * one trip initialization for time utilization
+ * scaling factor (to scale metric from counter
+ * units into seconds)
+ */
+ int i;
+ np->info->time_scale = 1;
+ if (np->left->desc.units.scaleTime > PM_TIME_SEC) {
+
+ for (i = PM_TIME_SEC; i < np->left->desc.units.scaleTime; i++)
+
+ np->info->time_scale *= 60;
+ }
+ else {
+ for (i = np->left->desc.units.scaleTime; i < PM_TIME_SEC; i++)
+ np->info->time_scale /= 1000;
+ }
+ }
+ np->info->ivlist[k].value.d *= np->info->time_scale;
+ }
+ }
+ k++;
+ }
+ np->info->numval = k;
+ return np->info->numval;
+ break;
+
+ case L_AVG:
+ case L_COUNT:
+ case L_SUM:
+ case L_MAX:
+ case L_MIN:
+ if (np->info->ivlist == NULL) {
+ /* initialize ivlist[] for singular instance first time through */
+ if ((np->info->ivlist = (val_t *)malloc(sizeof(val_t))) == NULL) {
+ __pmNoMem("eval_expr: aggr ivlist", sizeof(val_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ np->info->ivlist[0].inst = PM_IN_NULL;
+ }
+ /*
+ * values are in the left expr
+ */
+ if (np->type == L_COUNT) {
+ np->info->numval = 1;
+ np->info->ivlist[0].value.l = np->left->info->numval;
+ }
+ else {
+ np->info->numval = 1;
+ if (np->type == L_AVG)
+ np->info->ivlist[0].value.f = 0;
+ else if (np->type == L_SUM) {
+ switch (np->desc.type) {
+ case PM_TYPE_32:
+ np->info->ivlist[0].value.l = 0;
+ break;
+ case PM_TYPE_U32:
+ np->info->ivlist[0].value.ul = 0;
+ break;
+ case PM_TYPE_64:
+ np->info->ivlist[0].value.ll = 0;
+ break;
+ case PM_TYPE_U64:
+ np->info->ivlist[0].value.ull = 0;
+ break;
+ case PM_TYPE_FLOAT:
+ np->info->ivlist[0].value.f = 0;
+ break;
+ case PM_TYPE_DOUBLE:
+ np->info->ivlist[0].value.d = 0;
+ break;
+ }
+ }
+ for (i = 0; i < np->left->info->numval; i++) {
+ switch (np->type) {
+
+ case L_AVG:
+ switch (np->left->desc.type) {
+ case PM_TYPE_32:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.l / np->left->info->numval;
+ break;
+ case PM_TYPE_U32:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ul / np->left->info->numval;
+ break;
+ case PM_TYPE_64:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ll / np->left->info->numval;
+ break;
+ case PM_TYPE_U64:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.ull / np->left->info->numval;
+ break;
+ case PM_TYPE_FLOAT:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.f / np->left->info->numval;
+ break;
+ case PM_TYPE_DOUBLE:
+ np->info->ivlist[0].value.f += (float)np->left->info->ivlist[i].value.d / np->left->info->numval;
+ break;
+ default:
+ /*
+ * check_expr() checks for numeric data
+ * type at bind time ... if here, botch!
+ */
+ return PM_ERR_CONV;
+ }
+ break;
+
+ case L_MAX:
+ switch (np->desc.type) {
+ case PM_TYPE_32:
+ if (i == 0 ||
+ np->info->ivlist[0].value.l < np->left->info->ivlist[i].value.l)
+ np->info->ivlist[0].value.l = np->left->info->ivlist[i].value.l;
+ break;
+ case PM_TYPE_U32:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ul < np->left->info->ivlist[i].value.ul)
+ np->info->ivlist[0].value.ul = np->left->info->ivlist[i].value.ul;
+ break;
+ case PM_TYPE_64:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ll < np->left->info->ivlist[i].value.ll)
+ np->info->ivlist[0].value.ll = np->left->info->ivlist[i].value.ll;
+ break;
+ case PM_TYPE_U64:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ull < np->left->info->ivlist[i].value.ull)
+ np->info->ivlist[0].value.ull = np->left->info->ivlist[i].value.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ if (i == 0 ||
+ np->info->ivlist[0].value.f < np->left->info->ivlist[i].value.f)
+ np->info->ivlist[0].value.f = np->left->info->ivlist[i].value.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ if (i == 0 ||
+ np->info->ivlist[0].value.d < np->left->info->ivlist[i].value.d)
+ np->info->ivlist[0].value.d = np->left->info->ivlist[i].value.d;
+ break;
+ default:
+ /*
+ * check_expr() checks for numeric data
+ * type at bind time ... if here, botch!
+ */
+ return PM_ERR_CONV;
+ }
+ break;
+
+ case L_MIN:
+ switch (np->desc.type) {
+ case PM_TYPE_32:
+ if (i == 0 ||
+ np->info->ivlist[0].value.l > np->left->info->ivlist[i].value.l)
+ np->info->ivlist[0].value.l = np->left->info->ivlist[i].value.l;
+ break;
+ case PM_TYPE_U32:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ul > np->left->info->ivlist[i].value.ul)
+ np->info->ivlist[0].value.ul = np->left->info->ivlist[i].value.ul;
+ break;
+ case PM_TYPE_64:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ll > np->left->info->ivlist[i].value.ll)
+ np->info->ivlist[0].value.ll = np->left->info->ivlist[i].value.ll;
+ break;
+ case PM_TYPE_U64:
+ if (i == 0 ||
+ np->info->ivlist[0].value.ull > np->left->info->ivlist[i].value.ull)
+ np->info->ivlist[0].value.ull = np->left->info->ivlist[i].value.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ if (i == 0 ||
+ np->info->ivlist[0].value.f > np->left->info->ivlist[i].value.f)
+ np->info->ivlist[0].value.f = np->left->info->ivlist[i].value.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ if (i == 0 ||
+ np->info->ivlist[0].value.d > np->left->info->ivlist[i].value.d)
+ np->info->ivlist[0].value.d = np->left->info->ivlist[i].value.d;
+ break;
+ default:
+ /*
+ * check_expr() checks for numeric data
+ * type at bind time ... if here, botch!
+ */
+ return PM_ERR_CONV;
+ }
+ break;
+
+ case L_SUM:
+ switch (np->desc.type) {
+ case PM_TYPE_32:
+ np->info->ivlist[0].value.l += np->left->info->ivlist[i].value.l;
+ break;
+ case PM_TYPE_U32:
+ np->info->ivlist[0].value.ul += np->left->info->ivlist[i].value.ul;
+ break;
+ case PM_TYPE_64:
+ np->info->ivlist[0].value.ll += np->left->info->ivlist[i].value.ll;
+ break;
+ case PM_TYPE_U64:
+ np->info->ivlist[0].value.ull += np->left->info->ivlist[i].value.ull;
+ break;
+ case PM_TYPE_FLOAT:
+ np->info->ivlist[0].value.f += np->left->info->ivlist[i].value.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ np->info->ivlist[0].value.d += np->left->info->ivlist[i].value.d;
+ break;
+ default:
+ /*
+ * check_expr() checks for numeric data
+ * type at bind time ... if here, botch!
+ */
+ return PM_ERR_CONV;
+ }
+ break;
+
+ }
+ }
+ }
+ return np->info->numval;
+ break;
+
+ case L_NAME:
+ /*
+ * Extract instance-values from pmResult and store them in
+ * ivlist[] as <int, pmAtomValue> pairs
+ */
+ for (j = 0; j < rp->numpmid; j++) {
+ if (np->info->pmid == rp->vset[j]->pmid) {
+ free_ivlist(np);
+ np->info->numval = rp->vset[j]->numval;
+ if (np->info->numval <= 0)
+ return np->info->numval;
+ if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) {
+ __pmNoMem("eval_expr: metric ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (i = 0; i < np->info->numval; i++) {
+ np->info->ivlist[i].inst = rp->vset[j]->vlist[i].inst;
+ switch (np->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ np->info->ivlist[i].value.l = rp->vset[j]->vlist[i].value.lval;
+ break;
+
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ memcpy((void *)&np->info->ivlist[i].value.ll, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(__int64_t));
+ break;
+
+ case PM_TYPE_FLOAT:
+ if (rp->vset[j]->valfmt == PM_VAL_INSITU) {
+ /* old style insitu float */
+ np->info->ivlist[i].value.l = rp->vset[j]->vlist[i].value.lval;
+ }
+ else {
+ assert(rp->vset[j]->vlist[i].value.pval->vtype == PM_TYPE_FLOAT);
+ memcpy((void *)&np->info->ivlist[i].value.f, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(float));
+ }
+ break;
+
+ case PM_TYPE_DOUBLE:
+ memcpy((void *)&np->info->ivlist[i].value.d, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, sizeof(double));
+ break;
+
+ case PM_TYPE_STRING:
+ need = rp->vset[j]->vlist[i].value.pval->vlen-PM_VAL_HDR_SIZE;
+ if ((np->info->ivlist[i].value.cp = (char *)malloc(need)) == NULL) {
+ __pmNoMem("eval_expr: string value", rp->vset[j]->vlist[i].value.pval->vlen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy((void *)np->info->ivlist[i].value.cp, (void *)rp->vset[j]->vlist[i].value.pval->vbuf, need);
+ np->info->ivlist[i].vlen = need;
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ if ((np->info->ivlist[i].value.vbp = (pmValueBlock *)malloc(rp->vset[j]->vlist[i].value.pval->vlen)) == NULL) {
+ __pmNoMem("eval_expr: aggregate value", rp->vset[j]->vlist[i].value.pval->vlen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy(np->info->ivlist[i].value.vbp, (void *)rp->vset[j]->vlist[i].value.pval, rp->vset[j]->vlist[i].value.pval->vlen);
+ np->info->ivlist[i].vlen = rp->vset[j]->vlist[i].value.pval->vlen;
+ break;
+
+ default:
+ /*
+ * really only PM_TYPE_NOSUPPORT should
+ * end up here
+ */
+ return PM_ERR_TYPE;
+ }
+ }
+ return np->info->numval;
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char strbuf[20];
+ fprintf(stderr, "eval_expr: botch: operand %s not in the extended pmResult\n", pmIDStr_r(np->info->pmid, strbuf, sizeof(strbuf)));
+ __pmDumpResult(stderr, rp);
+ }
+#endif
+ return PM_ERR_PMID;
+
+ case L_ANON:
+ /* no values available for anonymous metrics */
+ return 0;
+
+ default:
+ /*
+ * binary operator cases ... always have a left and right
+ * operand and no errors (these are caught earlier when the
+ * recursive call on each of the operands would may have
+ * returned an error
+ */
+ assert(np->left != NULL);
+ assert(np->right != NULL);
+
+ free_ivlist(np);
+ /*
+ * empty result cases first
+ */
+ if (np->left->info->numval == 0) {
+ np->info->numval = 0;
+ return np->info->numval;
+ }
+ if (np->right->info->numval == 0) {
+ np->info->numval = 0;
+ return np->info->numval;
+ }
+ /*
+ * really got some work to do ...
+ */
+ if (np->left->desc.indom == PM_INDOM_NULL)
+ np->info->numval = np->right->info->numval;
+ else if (np->right->desc.indom == PM_INDOM_NULL)
+ np->info->numval = np->left->info->numval;
+ else {
+ /*
+ * Generally have the same number of instances because
+ * both operands are over the same instance domain,
+ * fetched with the same profile. When not the case,
+ * the result can contain no more instances than in
+ * the smaller of the operands.
+ */
+ if (np->left->info->numval <= np->right->info->numval)
+ np->info->numval = np->left->info->numval;
+ else
+ np->info->numval = np->right->info->numval;
+ }
+ if ((np->info->ivlist = (val_t *)malloc(np->info->numval*sizeof(val_t))) == NULL) {
+ __pmNoMem("eval_expr: expr ivlist", np->info->numval*sizeof(val_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ /*
+ * ivlist[k] = left-ivlist[i] <op> right-ivlist[j]
+ */
+ for (i = j = k = 0; k < np->info->numval; ) {
+ if (i >= np->left->info->numval || j >= np->right->info->numval) {
+ /* run out of operand instances, quit */
+ np->info->numval = k;
+ break;
+ }
+ if (np->left->desc.indom != PM_INDOM_NULL &&
+ np->right->desc.indom != PM_INDOM_NULL) {
+ if (np->left->info->ivlist[i].inst != np->right->info->ivlist[j].inst) {
+ /* left ith inst != right jth inst ... search in right */
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ fprintf(stderr, "eval_expr: inst[%d] mismatch left [%d]=%d right [%d]=%d\n", k, i, np->left->info->ivlist[i].inst, j, np->right->info->ivlist[j].inst);
+ }
+#endif
+ for (j = 0; j < np->right->info->numval; j++) {
+ if (np->left->info->ivlist[i].inst == np->right->info->ivlist[j].inst)
+ break;
+ }
+ if (j == np->right->info->numval) {
+ /*
+ * no match, so next instance on left operand,
+ * and reset to start from first instance of
+ * right operand
+ */
+ i++;
+ j = 0;
+ continue;
+ }
+#ifdef PCP_DEBUG
+ else {
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ fprintf(stderr, "eval_expr: recover @ right [%d]=%d\n", j, np->right->info->ivlist[j].inst);
+ }
+ }
+#endif
+ }
+ }
+ np->info->ivlist[k].value =
+ bin_op(np->desc.type, np->type,
+ np->left->info->ivlist[i].value, np->left->desc.type, np->left->info->mul_scale, np->left->info->div_scale,
+ np->right->info->ivlist[j].value, np->right->desc.type, np->right->info->mul_scale, np->right->info->div_scale);
+ if (np->left->desc.indom != PM_INDOM_NULL)
+ np->info->ivlist[k].inst = np->left->info->ivlist[i].inst;
+ else
+ np->info->ivlist[k].inst = np->right->info->ivlist[j].inst;
+ k++;
+ if (np->left->desc.indom != PM_INDOM_NULL) {
+ i++;
+ if (np->right->desc.indom != PM_INDOM_NULL) {
+ j++;
+ if (j >= np->right->info->numval) {
+ /* rescan if need be */
+ j = 0;
+ }
+ }
+ }
+ else if (np->right->desc.indom != PM_INDOM_NULL) {
+ j++;
+ }
+ }
+ return np->info->numval;
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * Algorithm here is complicated by trying to re-write the pmResult.
+ *
+ * On entry the pmResult is likely to be built over a pinned PDU buffer,
+ * which means individual pmValueSets cannot be selectively replaced
+ * (this would come to tears badly in pmFreeResult() where as soon as
+ * one pmValueSet is found to be in a pinned PDU buffer it is assumed
+ * they are all so ... leaving a memory leak for any ones we'd modified
+ * here).
+ *
+ * So the only option is to COPY the pmResult, selectively replacing
+ * the pmValueSets for the derived metrics, and then calling
+ * pmFreeResult() to free the input structure and return the new one.
+ *
+ * In making the COPY it is critical that we reverse the algorithm
+ * used in pmFreeResult() so that a later call to pmFreeResult() will
+ * not cause a memory leak.
+ * This means ...
+ * - malloc() the pmResult (padded out to the right number of vset[]
+ * entries)
+ * - if valfmt is not PM_VAL_INSITU use PM_VAL_DPTR (not PM_VAL_SPTR),
+ * so anything we point to is going to be released when our caller
+ * calls pmFreeResult()
+ * - use one malloc() for each pmValueSet with vlist[] sized to be 0
+ * if numval < 0 else numval
+ * - pmValueBlocks are from malloc()
+ *
+ * For reference, the same logic appears in __pmLogFetchInterp() to
+ * sythesize a pmResult there.
+ */
+void
+__dmpostfetch(__pmContext *ctxp, pmResult **result)
+{
+ int i;
+ int j;
+ int m;
+ int numval;
+ int valfmt;
+ size_t need;
+ int rewrite;
+ ctl_t *cp = (ctl_t *)ctxp->c_dm;
+ pmResult *rp = *result;
+ pmResult *newrp;
+
+ /* if needed, init() called in __dmopencontext beforehand */
+
+ if (cp == NULL || cp->fetch_has_dm == 0) return;
+
+ newrp = (pmResult *)malloc(sizeof(pmResult)+(cp->numpmid-1)*sizeof(pmValueSet *));
+ if (newrp == NULL) {
+ __pmNoMem("__dmpostfetch: newrp", sizeof(pmResult)+(cp->numpmid-1)*sizeof(pmValueSet *), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ newrp->timestamp = rp->timestamp;
+ newrp->numpmid = cp->numpmid;
+
+ for (j = 0; j < newrp->numpmid; j++) {
+ numval = rp->vset[j]->numval;
+ valfmt = rp->vset[j]->valfmt;
+ rewrite = 0;
+ /*
+ * pandering to gcc ... m is not used unless rewrite == 1 in
+ * which case m is well-defined
+ */
+ m = 0;
+ if (pmid_domain(rp->vset[j]->pmid) == DYNAMIC_PMID &&
+ pmid_item(rp->vset[j]->pmid) != 0) {
+ for (m = 0; m < cp->nmetric; m++) {
+ if (rp->vset[j]->pmid == cp->mlist[m].pmid) {
+ if (cp->mlist[m].expr == NULL) {
+ numval = PM_ERR_PMID;
+ }
+ else {
+ rewrite = 1;
+ if (cp->mlist[m].expr->desc.type == PM_TYPE_32 ||
+ cp->mlist[m].expr->desc.type == PM_TYPE_U32)
+ valfmt = PM_VAL_INSITU;
+ else
+ valfmt = PM_VAL_DPTR;
+ numval = eval_expr(cp->mlist[m].expr, rp, 1);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_DERIVE) && (pmDebug & DBG_TRACE_APPL2)) {
+ int k;
+ char strbuf[20];
+
+ fprintf(stderr, "__dmpostfetch: [%d] root node %s: numval=%d", j, pmIDStr_r(rp->vset[j]->pmid, strbuf, sizeof(strbuf)), numval);
+ for (k = 0; k < numval; k++) {
+ fprintf(stderr, " vset[%d]: inst=%d", k, cp->mlist[m].expr->info->ivlist[k].inst);
+ if (cp->mlist[m].expr->desc.type == PM_TYPE_32)
+ fprintf(stderr, " l=%d", cp->mlist[m].expr->info->ivlist[k].value.l);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_U32)
+ fprintf(stderr, " u=%u", cp->mlist[m].expr->info->ivlist[k].value.ul);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_64)
+ fprintf(stderr, " ll=%"PRIi64, cp->mlist[m].expr->info->ivlist[k].value.ll);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_U64)
+ fprintf(stderr, " ul=%"PRIu64, cp->mlist[m].expr->info->ivlist[k].value.ull);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_FLOAT)
+ fprintf(stderr, " f=%f", (double)cp->mlist[m].expr->info->ivlist[k].value.f);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_DOUBLE)
+ fprintf(stderr, " d=%f", cp->mlist[m].expr->info->ivlist[k].value.d);
+ else if (cp->mlist[m].expr->desc.type == PM_TYPE_STRING) {
+ fprintf(stderr, " cp=%s (len=%d)", cp->mlist[m].expr->info->ivlist[k].value.cp, cp->mlist[m].expr->info->ivlist[k].vlen);
+ }
+ else {
+ fprintf(stderr, " vbp=" PRINTF_P_PFX "%p (len=%d)", cp->mlist[m].expr->info->ivlist[k].value.vbp, cp->mlist[m].expr->info->ivlist[k].vlen);
+ }
+ }
+ fputc('\n', stderr);
+ if (cp->mlist[m].expr->info != NULL)
+ __dmdumpexpr(cp->mlist[m].expr, 1);
+ }
+#endif
+ }
+ break;
+ }
+ }
+ }
+
+ if (numval <= 0) {
+ /* only need pmid and numval */
+ need = sizeof(pmValueSet) - sizeof(pmValue);
+ }
+ else {
+ /* already one pmValue in a pmValueSet */
+ need = sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue);
+ }
+ if (need > 0) {
+ if ((newrp->vset[j] = (pmValueSet *)malloc(need)) == NULL) {
+ __pmNoMem("__dmpostfetch: vset", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+ newrp->vset[j]->pmid = rp->vset[j]->pmid;
+ newrp->vset[j]->numval = numval;
+ newrp->vset[j]->valfmt = valfmt;
+ if (numval < 0)
+ continue;
+
+ for (i = 0; i < numval; i++) {
+ pmValueBlock *vp;
+
+ if (!rewrite) {
+ newrp->vset[j]->vlist[i].inst = rp->vset[j]->vlist[i].inst;
+ if (newrp->vset[j]->valfmt == PM_VAL_INSITU) {
+ newrp->vset[j]->vlist[i].value.lval = rp->vset[j]->vlist[i].value.lval;
+ }
+ else {
+ need = rp->vset[j]->vlist[i].value.pval->vlen;
+ vp = (pmValueBlock *)malloc(need);
+ if (vp == NULL) {
+ __pmNoMem("__dmpostfetch: copy value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy((void *)vp, (void *)rp->vset[j]->vlist[i].value.pval, need);
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ }
+ continue;
+ }
+
+ /*
+ * the rewrite case ...
+ */
+ newrp->vset[j]->vlist[i].inst = cp->mlist[m].expr->info->ivlist[i].inst;
+ switch (cp->mlist[m].expr->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ newrp->vset[j]->vlist[i].value.lval = cp->mlist[m].expr->info->ivlist[i].value.l;
+ break;
+
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ need = PM_VAL_HDR_SIZE + sizeof(__int64_t);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ __pmNoMem("__dmpostfetch: 64-bit int value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ vp->vlen = need;
+ vp->vtype = cp->mlist[m].expr->desc.type;
+ memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.ll, sizeof(__int64_t));
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ break;
+
+ case PM_TYPE_FLOAT:
+ need = PM_VAL_HDR_SIZE + sizeof(float);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ __pmNoMem("__dmpostfetch: float value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ vp->vlen = need;
+ vp->vtype = PM_TYPE_FLOAT;
+ memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.f, sizeof(float));
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ break;
+
+ case PM_TYPE_DOUBLE:
+ need = PM_VAL_HDR_SIZE + sizeof(double);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ __pmNoMem("__dmpostfetch: double value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ vp->vlen = need;
+ vp->vtype = PM_TYPE_DOUBLE;
+ memcpy((void *)vp->vbuf, (void *)&cp->mlist[m].expr->info->ivlist[i].value.f, sizeof(double));
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ break;
+
+ case PM_TYPE_STRING:
+ need = PM_VAL_HDR_SIZE + cp->mlist[m].expr->info->ivlist[i].vlen;
+ vp = (pmValueBlock *)malloc(need);
+ if (vp == NULL) {
+ __pmNoMem("__dmpostfetch: string value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ vp->vlen = need;
+ vp->vtype = cp->mlist[m].expr->desc.type;
+ memcpy((void *)vp->vbuf, cp->mlist[m].expr->info->ivlist[i].value.cp, cp->mlist[m].expr->info->ivlist[i].vlen);
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ need = cp->mlist[m].expr->info->ivlist[i].vlen;
+ vp = (pmValueBlock *)malloc(need);
+ if (vp == NULL) {
+ __pmNoMem("__dmpostfetch: aggregate or event value", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy((void *)vp, cp->mlist[m].expr->info->ivlist[i].value.vbp, cp->mlist[m].expr->info->ivlist[i].vlen);
+ newrp->vset[j]->vlist[i].value.pval = vp;
+ break;
+
+ default:
+ /*
+ * really nothing should end up here ...
+ * do nothing as numval should have been < 0
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char strbuf[20];
+ fprintf(stderr, "__dmpostfetch: botch: drived metric[%d]: operand %s has odd type (%d)\n", m, pmIDStr_r(rp->vset[j]->pmid, strbuf, sizeof(strbuf)), cp->mlist[m].expr->desc.type);
+ }
+#endif
+ break;
+ }
+ }
+ }
+
+ /*
+ * cull the original pmResult and return the rewritten one
+ */
+ pmFreeResult(rp);
+ *result = newrp;
+
+ return;
+}
diff --git a/src/libpcp/src/desc.c b/src/libpcp/src/desc.c
new file mode 100644
index 0000000..d1dd624
--- /dev/null
+++ b/src/libpcp/src/desc.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+
+int
+pmLookupDesc(pmID pmid, pmDesc *desc)
+{
+ int n;
+ __pmContext *ctxp;
+ __pmPDU *pb;
+
+ if ((n = pmWhichContext()) < 0)
+ goto done;
+ if ((ctxp = __pmHandleToPtr(n)) == NULL) {
+ n = PM_ERR_NOCONTEXT;
+ goto done;
+ }
+
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ if ((n = __pmSendDescReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), pmid)) < 0)
+ n = __pmMapErrno(n);
+ else {
+ int pinpdu;
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_DESC)
+ n = __pmDecodeDesc(pb, desc);
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ int ctx = n;
+ __pmDSO *dp;
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ else if ((dp = __pmLookupDSO(((__pmID_int *)&pmid)->domain)) == NULL)
+ n = PM_ERR_NOAGENT;
+ else {
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ n = dp->dispatch.version.any.desc(pmid, desc, dp->dispatch.version.any.ext);
+ }
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ n = __pmLogLookupDesc(ctxp->c_archctl->ac_log, pmid, desc);
+ }
+
+ if (n == PM_ERR_PMID || n == PM_ERR_PMID_LOG || n == PM_ERR_NOAGENT) {
+ int sts;
+ /*
+ * check for derived metric ... keep error status from above
+ * unless we have success with the derived metrics
+ */
+ sts = __dmdesc(ctxp, pmid, desc);
+ if (sts >= 0)
+ n = sts;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+
+done:
+ return n;
+}
+
diff --git a/src/libpcp/src/discovery.c b/src/libpcp/src/discovery.c
new file mode 100644
index 0000000..ec8c359
--- /dev/null
+++ b/src/libpcp/src/discovery.c
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include "avahi.h"
+#include "probe.h"
+
+/*
+ * Advertise the given service using all available means. The implementation
+ * must support adding and removing individual service specs on the fly.
+ * e.g. "pmcd" on port 1234
+ */
+__pmServerPresence *
+__pmServerAdvertisePresence(const char *serviceSpec, int port)
+{
+ __pmServerPresence *s;
+
+ /* Allocate a server presence and copy the given data. */
+ if ((s = malloc(sizeof(*s))) == NULL) {
+ __pmNoMem("__pmServerAdvertisePresence: can't allocate __pmServerPresence",
+ sizeof(*s), PM_FATAL_ERR);
+ }
+ s->serviceSpec = strdup(serviceSpec);
+ if (s->serviceSpec == NULL) {
+ __pmNoMem("__pmServerAdvertisePresence: can't allocate service spec",
+ strlen(serviceSpec) + 1, PM_FATAL_ERR);
+ }
+ s->port = port;
+
+ /* Now advertise our presence using all available means. If a particular
+ * method is not available or not configured, then the respective call
+ * will have no effect. Currently, only Avahi is supported.
+ */
+ __pmServerAvahiAdvertisePresence(s);
+ return s;
+}
+
+/*
+ * Unadvertise the given service using all available means. The implementation
+ * must support removing individual service specs on the fly.
+ * e.g. "pmcd" on port 1234
+ */
+void
+__pmServerUnadvertisePresence(__pmServerPresence *s)
+{
+ /* Unadvertise our presence for all available means. If a particular
+ * method is not active, then the respective call will have no effect.
+ */
+ __pmServerAvahiUnadvertisePresence(s);
+ free(s->serviceSpec);
+ free(s);
+}
+
+/*
+ * Service discovery API entry points.
+ */
+char *
+__pmServiceDiscoveryParseTimeout (const char *s, struct timeval *timeout)
+{
+ double seconds;
+ char *end;
+
+ /*
+ * The string is a floating point number representing the number of seconds
+ * to wait. Possibly followed by a comma, to separate the next option.
+ */
+ seconds = strtod(s, &end);
+ if (*end != '\0' && *end != ',') {
+ __pmNotifyErr(LOG_ERR, "the timeout argument '%s' is not valid", s);
+ return strchrnul(s, ',');
+ }
+
+ /* Set the specified timeout. */
+ timeout->tv_sec = (long)seconds;
+ timeout->tv_usec = (long)((seconds - timeout->tv_sec) * 1000000);
+
+ return end;
+}
+
+static int
+parseOptions(const char *optionsString, __pmServiceDiscoveryOptions *options)
+{
+ if (optionsString == NULL)
+ return 0; /* no options to parse */
+
+ /* Now interpret the options string. */
+ while (*optionsString != '\0') {
+ if (strncmp(optionsString, "resolve", sizeof("resolve") - 1) == 0)
+ options->resolve = 1;
+ else if (strncmp(optionsString, "timeout=", sizeof("timeout=") - 1) == 0) {
+#if ! PM_MULTI_THREAD
+ __pmNotifyErr(LOG_ERR, "__pmDiscoverServicesWithOptions: Service discovery global timeout is not supported");
+ return -EOPNOTSUPP;
+#else
+ optionsString += sizeof("timeout=") - 1;
+ optionsString = __pmServiceDiscoveryParseTimeout(optionsString,
+ &options->timeout);
+#endif
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "__pmDiscoverServicesWithOptions: unrecognized option at '%s'", optionsString);
+ return -EINVAL;
+ }
+ /* Locate the start of the next option. */
+ optionsString = strchrnul(optionsString, ',');
+ }
+
+ return 0; /* ok */
+}
+
+#if PM_MULTI_THREAD
+static void *
+timeoutSleep(void *arg)
+{
+ __pmServiceDiscoveryOptions *options = arg;
+ int old;
+
+ /*
+ * Make sure that this thread is cancellable.
+ * We don't need the previous state, but pthread_setcancelstate(3) says that
+ * passing in NULL as the second argument is not portable.
+ */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old);
+
+ /*
+ * Sleep for the specified amount of time. Our thread will either be
+ * cancelled by the calling thread or we will wake up on our own.
+ */
+ __pmtimevalSleep(options->timeout);
+
+ /*
+ * Service discovery has timed out. It's ok to set this unconditionally
+ * since the object exists in the calling thread's memory space and it
+ * waits to join with our thread before finishing.
+ */
+ options->timedOut = 1;
+ return NULL;
+}
+#endif
+
+int
+pmDiscoverServices(const char *service,
+ const char *mechanism,
+ char ***urls)
+{
+ return __pmDiscoverServicesWithOptions(service, mechanism, NULL, NULL, urls);
+}
+
+int
+__pmDiscoverServicesWithOptions(const char *service,
+ const char *mechanism,
+ const char *optionsString,
+ const volatile unsigned *flags,
+ char ***urls)
+{
+ __pmServiceDiscoveryOptions options;
+ int numUrls;
+ int sts;
+#if PM_MULTI_THREAD
+ pthread_t timeoutThread;
+ pthread_attr_t threadAttr;
+ int timeoutSet = 0;
+#endif
+
+ /* Interpret the options string. Initialize first. */
+ memset(&options, 0, sizeof(options));
+ sts = parseOptions(optionsString, &options);
+ if (sts < 0)
+ return sts;
+ options.flags = flags;
+
+#if PM_MULTI_THREAD
+ /*
+ * If a global timeout has been specified, then start a thread which will
+ * sleep for the specified length of time. When it wakes up, it will
+ * interrupt the discovery process. If discovery finishes before the
+ * timeout period, then the thread will be cancelled.
+ * We want the thread to be joinable.
+ */
+ if (options.timeout.tv_sec || options.timeout.tv_usec) {
+ pthread_attr_init(&threadAttr);
+ pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_JOINABLE);
+ sts = pthread_create(&timeoutThread, &threadAttr,
+ timeoutSleep, &options);
+ pthread_attr_destroy(&threadAttr);
+ if (sts != 0) {
+ sts = oserror();
+ __pmNotifyErr(LOG_ERR, "Service discovery global timeout could not be set: %s",
+ strerror(sts));
+ return -sts;
+ }
+ timeoutSet = 1;
+ }
+#endif
+
+ /*
+ * Attempt to discover the requested service(s) using the requested or
+ * all available means.
+ * If a particular method is not available or not configured, then the
+ * respective call will have no effect.
+ */
+ *urls = NULL;
+ numUrls = 0;
+ if (mechanism == NULL) {
+ /*
+ * Accumulate discovered services using all available mechanisms.
+ * Ensure that the return value from each mechanism is not an error
+ * code before adding it to numUrls.
+ */
+ sts = __pmAvahiDiscoverServices(service, mechanism, &options,
+ numUrls, urls);
+ if (sts < 0) {
+ numUrls = sts;
+ goto done;
+ }
+ numUrls += sts;
+ if (! flags || (*flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0) {
+ sts = __pmProbeDiscoverServices(service, mechanism, &options,
+ numUrls, urls);
+ if (sts < 0) {
+ numUrls = sts;
+ goto done;
+ }
+ numUrls += sts;
+ }
+ }
+ else if (strncmp(mechanism, "avahi", 5) == 0) {
+ numUrls = __pmAvahiDiscoverServices(service, mechanism, &options,
+ numUrls, urls);
+ }
+ else if (strncmp(mechanism, "probe", 5) == 0) {
+ numUrls = __pmProbeDiscoverServices(service, mechanism, &options,
+ numUrls, urls);
+ }
+ else
+ numUrls = -EOPNOTSUPP;
+
+ done:
+#if PM_MULTI_THREAD
+ if (timeoutSet) {
+ /* Cancel the timeout thread and then wait for it to join. */
+ pthread_cancel(timeoutThread);
+ pthread_join(timeoutThread, NULL);
+ }
+#endif
+
+ return numUrls;
+}
+
+/* For manually adding a service. Also used by pmDiscoverServices(). */
+int
+__pmAddDiscoveredService(__pmServiceInfo *info,
+ const __pmServiceDiscoveryOptions *options,
+ int numUrls,
+ char ***urls)
+{
+ const char *protocol = info->protocol;
+ char *host = NULL;
+ char *url;
+ size_t size;
+ int isIPv6;
+ int port;
+
+ /* If address resolution was requested, then do attempt it. */
+ if (options->resolve ||
+ (options->flags && (*options->flags & PM_SERVICE_DISCOVERY_RESOLVE) != 0))
+ host = __pmGetNameInfo(info->address);
+
+ /*
+ * If address resolution was not requested, or if it failed, then
+ * just use the address.
+ */
+ if (host == NULL) {
+ host = __pmSockAddrToString(info->address);
+ if (host == NULL) {
+ __pmNoMem("__pmAddDiscoveredService: can't allocate host buffer",
+ 0, PM_FATAL_ERR);
+ }
+ }
+
+ /*
+ * Allocate the new entry. We need room for the URL prefix, the
+ * address/host and the port. IPv6 addresses require a set of []
+ * surrounding the address in order to distinguish the port.
+ */
+ port = __pmSockAddrGetPort(info->address);
+ size = strlen(protocol) + sizeof("://");
+ size += strlen(host) + sizeof(":65535");
+ if ((isIPv6 = (strchr(host, ':') != NULL)))
+ size += 2;
+ url = malloc(size);
+ if (url == NULL) {
+ __pmNoMem("__pmAddDiscoveredService: can't allocate new entry",
+ size, PM_FATAL_ERR);
+ }
+ if (isIPv6)
+ snprintf(url, size, "%s://[%s]:%u", protocol, host, port);
+ else
+ snprintf(url, size, "%s://%s:%u", protocol, host, port);
+ free(host);
+
+ /*
+ * Now search the current list for the new entry.
+ * Add it if not found. We don't want any duplicates.
+ */
+ if (__pmStringListFind(url, numUrls, *urls) == NULL)
+ numUrls = __pmStringListAdd(url, numUrls, urls);
+
+ free(url);
+ return numUrls;
+}
diff --git a/src/libpcp/src/endian.c b/src/libpcp/src/endian.c
new file mode 100644
index 0000000..a5782b6
--- /dev/null
+++ b/src/libpcp/src/endian.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * Bit field typedefs for endian translations to support little endian
+ * hosts.
+ *
+ * For a typedef __X_foo, the big endian version will be "foo" or
+ * The only structures that appear here are ones that
+ * (a) may be encoded within a PDU, and/or
+ * (b) may appear in a PCP archive
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+#ifndef __htonpmUnits
+pmUnits
+__htonpmUnits(pmUnits units)
+{
+ unsigned int x;
+
+ x = htonl(*(unsigned int *)&units);
+ units = *(pmUnits *)&x;
+
+ return units;
+}
+#endif
+
+#ifndef __ntohpmUnits
+pmUnits
+__ntohpmUnits(pmUnits units)
+{
+ unsigned int x;
+
+ x = ntohl(*(unsigned int *)&units);
+ units = *(pmUnits *)&x;
+
+ return units;
+}
+#endif
+
+#ifndef __htonpmValueBlock
+static void
+htonEventArray(pmValueBlock * const vb, int highres)
+{
+ size_t size;
+ char *base;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int nrecords;
+ int nparams;
+ int vtype;
+ int vlen;
+ __uint32_t *tp; /* points to int holding vtype/vlen */
+
+ /* ea_type and ea_len handled via *ip below */
+ if (highres) {
+ pmHighResEventArray *hreap = (pmHighResEventArray *)vb;
+ base = (char *)&hreap->ea_record[0];
+ nrecords = hreap->ea_nrecords;
+ hreap->ea_nrecords = htonl(nrecords);
+ }
+ else {
+ pmEventArray *eap = (pmEventArray *)vb;
+ base = (char *)&eap->ea_record[0];
+ nrecords = eap->ea_nrecords;
+ eap->ea_nrecords = htonl(nrecords);
+ }
+
+ /* walk packed event record array */
+ for (r = 0; r < nrecords; r++) {
+ if (highres) {
+ pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base;
+ size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) +
+ sizeof(hrerp->er_nparams);
+ if (hrerp->er_flags & PM_EVENT_FLAG_MISSED)
+ nparams = 0;
+ else
+ nparams = hrerp->er_nparams;
+ hrerp->er_nparams = htonl(nparams);
+ hrerp->er_flags = htonl(hrerp->er_flags);
+ hrerp->er_timestamp.tv_sec = htonl(hrerp->er_timestamp.tv_sec);
+ hrerp->er_timestamp.tv_nsec = htonl(hrerp->er_timestamp.tv_nsec);
+ }
+ else {
+ pmEventRecord *erp = (pmEventRecord *)base;
+ size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) +
+ sizeof(erp->er_nparams);
+ if (erp->er_flags & PM_EVENT_FLAG_MISSED)
+ nparams = 0;
+ else
+ nparams = erp->er_nparams;
+ erp->er_nparams = htonl(erp->er_nparams);
+ erp->er_flags = htonl(erp->er_flags);
+ erp->er_timestamp.tv_sec = htonl(erp->er_timestamp.tv_sec);
+ erp->er_timestamp.tv_usec = htonl(erp->er_timestamp.tv_usec);
+ }
+ base += size;
+
+ for (p = 0; p < nparams; p++) {
+ pmEventParameter *epp = (pmEventParameter *)base;
+
+ epp->ep_pmid = __htonpmID(epp->ep_pmid);
+ vtype = epp->ep_type;
+ vlen = epp->ep_len;
+ tp = (__uint32_t *)&epp->ep_pmid;
+ tp++; /* now points to ep_type/ep_len */
+ *tp = htonl(*tp);
+ tp++; /* now points to vbuf */
+ /* convert the types we're able to ... */
+ switch (vtype) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ *tp = htonl(*tp);
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ __htonll((void *)tp);
+ break;
+ case PM_TYPE_DOUBLE:
+ __htond((void *)tp);
+ break;
+ case PM_TYPE_FLOAT:
+ __htonf((void *)tp);
+ break;
+ }
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(vlen);
+ }
+ }
+}
+
+void
+__htonpmValueBlock(pmValueBlock * const vb)
+{
+ unsigned int *ip = (unsigned int *)vb;
+
+ if (vb->vtype == PM_TYPE_U64 || vb->vtype == PM_TYPE_64)
+ __htonll(vb->vbuf);
+ else if (vb->vtype == PM_TYPE_DOUBLE)
+ __htond(vb->vbuf);
+ else if (vb->vtype == PM_TYPE_FLOAT)
+ __htonf(vb->vbuf);
+ else if (vb->vtype == PM_TYPE_EVENT)
+ htonEventArray(vb, 0);
+ else if (vb->vtype == PM_TYPE_HIGHRES_EVENT)
+ htonEventArray(vb, 1);
+
+ *ip = htonl(*ip); /* vtype/vlen */
+}
+#endif
+
+#ifndef __ntohpmValueBlock
+static void
+ntohEventArray(pmValueBlock * const vb, int highres)
+{
+ char *base;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int nrecords;
+ int nparams;
+
+ /* ea_type and ea_len handled via *ip above */
+ if (highres) {
+ pmHighResEventArray *hreap = (pmHighResEventArray *)vb;
+ base = (char *)&hreap->ea_record[0];
+ nrecords = hreap->ea_nrecords = ntohl(hreap->ea_nrecords);
+ }
+ else {
+ pmEventArray *eap = (pmEventArray *)vb;
+ base = (char *)&eap->ea_record[0];
+ nrecords = eap->ea_nrecords = ntohl(eap->ea_nrecords);
+ }
+
+ /* walk packed event record array */
+ for (r = 0; r < nrecords; r++) {
+ unsigned int flags;
+ size_t size;
+
+ if (highres) {
+ pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base;
+ size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) +
+ sizeof(hrerp->er_nparams);
+ hrerp->er_timestamp.tv_sec = ntohl(hrerp->er_timestamp.tv_sec);
+ hrerp->er_timestamp.tv_nsec = ntohl(hrerp->er_timestamp.tv_nsec);
+ nparams = hrerp->er_nparams = ntohl(hrerp->er_nparams);
+ flags = hrerp->er_flags = ntohl(hrerp->er_flags);
+ }
+ else {
+ pmEventRecord *erp = (pmEventRecord *)base;
+ size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) +
+ sizeof(erp->er_nparams);
+ erp->er_timestamp.tv_sec = ntohl(erp->er_timestamp.tv_sec);
+ erp->er_timestamp.tv_usec = ntohl(erp->er_timestamp.tv_usec);
+ nparams = erp->er_nparams = ntohl(erp->er_nparams);
+ flags = erp->er_flags = ntohl(erp->er_flags);
+ }
+
+ if (flags & PM_EVENT_FLAG_MISSED)
+ nparams = 0;
+
+ base += size;
+ for (p = 0; p < nparams; p++) {
+ __uint32_t *tp; /* points to int holding vtype/vlen */
+ pmEventParameter *epp = (pmEventParameter *)base;
+
+ epp->ep_pmid = __ntohpmID(epp->ep_pmid);
+ tp = (__uint32_t *)&epp->ep_pmid;
+ tp++; /* now points to ep_type/ep_len */
+ *tp = ntohl(*tp);
+ tp++; /* now points to vbuf */
+ /* convert the types we're able to ... */
+ switch (epp->ep_type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ *tp = ntohl(*tp);
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ __ntohll((void *)tp);
+ break;
+ case PM_TYPE_DOUBLE:
+ __ntohd((void *)tp);
+ break;
+ case PM_TYPE_FLOAT:
+ __ntohf((void *)tp);
+ break;
+ }
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ }
+ }
+}
+
+void
+__ntohpmValueBlock(pmValueBlock * const vb)
+{
+ unsigned int *ip = (unsigned int *)vb;
+
+ /* Swab the first word, which contain vtype and vlen */
+ *ip = ntohl(*ip);
+
+ switch (vb->vtype) {
+ case PM_TYPE_U64:
+ case PM_TYPE_64:
+ __ntohll(vb->vbuf);
+ break;
+
+ case PM_TYPE_DOUBLE:
+ __ntohd(vb->vbuf);
+ break;
+
+ case PM_TYPE_FLOAT:
+ __ntohf(vb->vbuf);
+ break;
+
+ case PM_TYPE_EVENT:
+ ntohEventArray(vb, 0);
+ break;
+
+ case PM_TYPE_HIGHRES_EVENT:
+ ntohEventArray(vb, 1);
+ break;
+ }
+}
+#endif
+
+#ifndef __htonpmPDUInfo
+__pmPDUInfo
+__htonpmPDUInfo(__pmPDUInfo info)
+{
+ unsigned int x;
+
+ x = htonl(*(unsigned int *)&info);
+ info = *(__pmPDUInfo *)&x;
+
+ return info;
+}
+#endif
+
+#ifndef __ntohpmPDUInfo
+__pmPDUInfo
+__ntohpmPDUInfo(__pmPDUInfo info)
+{
+ unsigned int x;
+
+ x = ntohl(*(unsigned int *)&info);
+ info = *(__pmPDUInfo *)&x;
+
+ return info;
+}
+#endif
+
+#ifndef __htonpmCred
+__pmCred
+__htonpmCred(__pmCred cred)
+{
+ unsigned int x;
+
+ x = htonl(*(unsigned int *)&cred);
+ cred = *(__pmCred *)&x;
+
+ return cred;
+}
+#endif
+
+#ifndef __ntohpmCred
+__pmCred
+__ntohpmCred(__pmCred cred)
+{
+ unsigned int x;
+
+ x = ntohl(*(unsigned int *)&cred);
+ cred = *(__pmCred *)&x;
+
+ return cred;
+}
+#endif
+
+
+#ifndef __htonf
+void
+__htonf(char *p)
+{
+ char c;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ c = p[i];
+ p[i] = p[3-i];
+ p[3-i] = c;
+ }
+}
+#endif
+
+#ifndef __htonll
+void
+__htonll(char *p)
+{
+ char c;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ c = p[i];
+ p[i] = p[7-i];
+ p[7-i] = c;
+ }
+}
+#endif
diff --git a/src/libpcp/src/err.c b/src/libpcp/src/err.c
new file mode 100644
index 0000000..614837d
--- /dev/null
+++ b/src/libpcp/src/err.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "fault.h"
+#include <ctype.h>
+#ifdef HAVE_SECURE_SOCKETS
+#include <prerror.h>
+#include <secerr.h>
+#include <sslerr.h>
+#include <sasl.h>
+#endif
+#ifdef IS_MINGW
+extern const char *strerror_r(int, char *, size_t);
+#endif
+
+/*
+ * if you modify this table at all, be sure to remake qa/006
+ */
+static const struct {
+ int err;
+ char *symb;
+ char *errmess;
+} errtab[] = {
+ { PM_ERR_GENERIC, "PM_ERR_GENERIC",
+ "Generic error, already reported above" },
+ { PM_ERR_PMNS, "PM_ERR_PMNS",
+ "Problems parsing PMNS definitions" },
+ { PM_ERR_NOPMNS, "PM_ERR_NOPMNS",
+ "PMNS not accessible" },
+ { PM_ERR_DUPPMNS, "PM_ERR_DUPPMNS",
+ "Attempt to reload the PMNS" },
+ { PM_ERR_TEXT, "PM_ERR_TEXT",
+ "One-line or help text is not available" },
+ { PM_ERR_APPVERSION, "PM_ERR_APPVERSION",
+ "Metric not supported by this version of monitored application" },
+ { PM_ERR_VALUE, "PM_ERR_VALUE",
+ "Missing metric value(s)" },
+ { PM_ERR_TIMEOUT, "PM_ERR_TIMEOUT",
+ "Timeout waiting for a response from PMCD" },
+ { PM_ERR_NODATA, "PM_ERR_NODATA",
+ "Empty archive log file" },
+ { PM_ERR_RESET, "PM_ERR_RESET",
+ "PMCD reset or configuration change" },
+ { PM_ERR_NAME, "PM_ERR_NAME",
+ "Unknown metric name" },
+ { PM_ERR_PMID, "PM_ERR_PMID",
+ "Unknown or illegal metric identifier" },
+ { PM_ERR_INDOM, "PM_ERR_INDOM",
+ "Unknown or illegal instance domain identifier" },
+ { PM_ERR_INST, "PM_ERR_INST",
+ "Unknown or illegal instance identifier" },
+ { PM_ERR_TYPE, "PM_ERR_TYPE",
+ "Unknown or illegal metric type" },
+ { PM_ERR_UNIT, "PM_ERR_UNIT",
+ "Illegal pmUnits specification" },
+ { PM_ERR_CONV, "PM_ERR_CONV",
+ "Impossible value or scale conversion" },
+ { PM_ERR_TRUNC, "PM_ERR_TRUNC",
+ "Truncation in value conversion" },
+ { PM_ERR_SIGN, "PM_ERR_SIGN",
+ "Negative value in conversion to unsigned" },
+ { PM_ERR_PROFILE, "PM_ERR_PROFILE",
+ "Explicit instance identifier(s) required" },
+ { PM_ERR_IPC, "PM_ERR_IPC",
+ "IPC protocol failure" },
+ { PM_ERR_EOF, "PM_ERR_EOF",
+ "IPC channel closed" },
+ { PM_ERR_NOTHOST, "PM_ERR_NOTHOST",
+ "Operation requires context with host source of metrics" },
+ { PM_ERR_EOL, "PM_ERR_EOL",
+ "End of PCP archive log" },
+ { PM_ERR_MODE, "PM_ERR_MODE",
+ "Illegal mode specification" },
+ { PM_ERR_LABEL, "PM_ERR_LABEL",
+ "Illegal label record at start of a PCP archive log file" },
+ { PM_ERR_LOGREC, "PM_ERR_LOGREC",
+ "Corrupted record in a PCP archive log" },
+ { PM_ERR_LOGFILE, "PM_ERR_LOGFILE",
+ "Missing PCP archive log file" },
+ { PM_ERR_NOTARCHIVE, "PM_ERR_NOTARCHIVE",
+ "Operation requires context with archive source of metrics" },
+ { PM_ERR_NOCONTEXT, "PM_ERR_NOCONTEXT",
+ "Attempt to use an illegal context" },
+ { PM_ERR_PROFILESPEC, "PM_ERR_PROFILESPEC",
+ "NULL pmInDom with non-NULL instlist" },
+ { PM_ERR_PMID_LOG, "PM_ERR_PMID_LOG",
+ "Metric not defined in the PCP archive log" },
+ { PM_ERR_INDOM_LOG, "PM_ERR_INDOM_LOG",
+ "Instance domain identifier not defined in the PCP archive log" },
+ { PM_ERR_INST_LOG, "PM_ERR_INST_LOG",
+ "Instance identifier not defined in the PCP archive log" },
+ { PM_ERR_NOPROFILE, "PM_ERR_NOPROFILE",
+ "Missing profile - protocol botch" },
+ { PM_ERR_NOAGENT, "PM_ERR_NOAGENT",
+ "No PMCD agent for domain of request" },
+ { PM_ERR_PERMISSION, "PM_ERR_PERMISSION",
+ "No permission to perform requested operation" },
+ { PM_ERR_CONNLIMIT, "PM_ERR_CONNLIMIT",
+ "PMCD connection limit for this host exceeded" },
+ { PM_ERR_AGAIN, "PM_ERR_AGAIN",
+ "Try again. Information not currently available" },
+ { PM_ERR_ISCONN, "PM_ERR_ISCONN",
+ "Already Connected" },
+ { PM_ERR_NOTCONN, "PM_ERR_NOTCONN",
+ "Not Connected" },
+ { PM_ERR_NEEDPORT, "PM_ERR_NEEDPORT",
+ "A non-null port name is required" },
+ { PM_ERR_NONLEAF, "PM_ERR_NONLEAF",
+ "Metric name is not a leaf in PMNS" },
+ { PM_ERR_PMDANOTREADY, "PM_ERR_PMDANOTREADY",
+ "PMDA is not yet ready to respond to requests" },
+ { PM_ERR_PMDAREADY, "PM_ERR_PMDAREADY",
+ "PMDA is now responsive to requests" },
+ { PM_ERR_TOOSMALL, "PM_ERR_TOOSMALL",
+ "Insufficient elements in list" },
+ { PM_ERR_TOOBIG, "PM_ERR_TOOBIG",
+ "Result size exceeded" },
+ { PM_ERR_FAULT, "PM_ERR_FAULT",
+ "QA fault injected" },
+ { PM_ERR_THREAD, "PM_ERR_THREAD",
+ "Operation not supported for multi-threaded applications" },
+ /* insert new libpcp error codes here */
+ { PM_ERR_NYI, "PM_ERR_NYI",
+ "Functionality not yet implemented" },
+ /* do not use values smaller than NYI */
+ { 0, "",
+ "" }
+};
+
+#define BADCODE "No such PMAPI error code (%d)"
+
+#ifndef IS_MINGW
+/*
+ * handle non-determinism in the GNU implementation of strerror_r()
+ */
+static void
+strerror_x(int code, char *buf, int buflen)
+{
+#ifdef HAVE_STRERROR_R_PTR
+ char *p;
+ p = strerror_r(code, buf, buflen);
+ if (p != buf)
+ strncpy(buf, p, buflen);
+#else
+ /*
+ * the more normal POSIX and XSI compliant variants always fill buf[]
+ */
+ strerror_r(code, buf, buflen);
+#endif
+}
+#endif
+
+char *
+pmErrStr_r(int code, char *buf, int buflen)
+{
+ int i;
+#ifndef IS_MINGW
+ static int first = 1;
+ static char *unknown = NULL;
+#else
+ static char unknown[] = "Unknown error";
+#endif
+
+ if (code == 0) {
+ strncpy(buf, "No error", buflen);
+ return buf;
+ }
+
+ /*
+ * Is the code from a library wrapped by libpcp? (e.g. NSS/SSL/SASL)
+ * By good fortune, these libraries are using error codes that do not
+ * overlap - by design for NSS/SSL/NSPR, and by sheer luck with SASL.
+ */
+ if (code < PM_ERR_NYI) {
+#ifdef HAVE_SECURE_SOCKETS
+#define DECODE_SECURE_SOCKETS_ERROR(c) ((c) - PM_ERR_NYI) /* negative */
+#define DECODE_SASL_SPECIFIC_ERROR(c) ((c) < -1000 ? 0 : (c))
+
+ int error = DECODE_SECURE_SOCKETS_ERROR(code);
+ if (DECODE_SASL_SPECIFIC_ERROR(error))
+ snprintf(buf, buflen, "Authentication - %s", sasl_errstring(error, NULL, NULL));
+ else
+ strncpy(buf, PR_ErrorToString(error, PR_LANGUAGE_EN), buflen);
+ buf[buflen-1] = '\0';
+ return buf;
+#endif
+ }
+
+#ifndef IS_MINGW
+ if (first) {
+ /*
+ * reference message for an unrecognized error code.
+ * For IRIX, strerror() returns NULL in this case.
+ */
+ strerror_x(-1, buf, buflen);
+ if (buf[0] != '\0') {
+ /*
+ * For Linux et al, strip the last word, expected to be the
+ * error number as in ...
+ * Unknown error -1
+ * or
+ * Unknown error 4294967295
+ */
+ char *sp = strrchr(buf, ' ');
+ char *p;
+
+ if (sp != NULL) {
+ sp++;
+ if (*sp == '-') sp++;
+ for (p = sp; *p != '\0'; p++) {
+ if (!isdigit((int)*p)) break;
+ }
+
+ if (*p == '\0') {
+PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC);
+ *sp = '\0';
+ if ((unknown = strdup(buf)) != NULL)
+ unknown[sp - buf] = '\0';
+ }
+ }
+ }
+ first = 0;
+ }
+ if (code < 0 && code > -PM_ERR_BASE) {
+ /* intro(2) / errno(3) errors, maybe */
+ strerror_x(-code, buf, buflen);
+ if (unknown == NULL) {
+ if (buf[0] != '\0')
+ return buf;
+ }
+ else {
+ /* The intention here is to catch variants of "Unknown
+ * error XXX" - in this case we're going to fail the
+ * stncmp() below, fall through and return a pcp error
+ * message, otherwise return the system error message
+ */
+ if (strncmp(buf, unknown, strlen(unknown)) != 0)
+ return buf;
+ }
+ }
+#else /* WIN32 */
+ if (code > -PM_ERR_BASE || code < -PM_ERR_NYI) {
+ const char *bp;
+ if ((bp = wsastrerror(-code)) != NULL)
+ strncpy(buf, bp, buflen);
+ else {
+ /* No strerror_r in MinGW, so need to lock */
+ char *tbp;
+ PM_LOCK(__pmLock_libpcp);
+ tbp = strerror(-code);
+ strncpy(buf, tbp, buflen);
+ PM_UNLOCK(__pmLock_libpcp);
+ }
+
+ if (strncmp(buf, unknown, strlen(unknown)) != 0)
+ return buf;
+ }
+#endif
+
+ for (i = 0; errtab[i].err; i++) {
+ if (errtab[i].err == code) {
+ strncpy(buf, errtab[i].errmess, buflen);
+ return buf;
+ }
+ }
+
+ /* failure */
+ snprintf(buf, buflen, BADCODE, code);
+ return buf;
+}
+
+char *
+pmErrStr(int code)
+{
+ static char errmsg[PM_MAXERRMSGLEN];
+ pmErrStr_r(code, errmsg, sizeof(errmsg));
+ return errmsg;
+}
+
+void
+__pmDumpErrTab(FILE *f)
+{
+ int i;
+
+ fprintf(f, " Code Symbolic Name Message\n");
+ for (i = 0; errtab[i].err; i++)
+ fprintf(f, "%6d %-20s %s\n",
+ errtab[i].err, errtab[i].symb, errtab[i].errmess);
+}
diff --git a/src/libpcp/src/events.c b/src/libpcp/src/events.c
new file mode 100644
index 0000000..c1049f8
--- /dev/null
+++ b/src/libpcp/src/events.c
@@ -0,0 +1,735 @@
+/*
+ * Unpack an array of event records
+ * Free space from unpack
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * The initialization of pmid_flags and pmid_missed both have a potential
+ * race, but there are no side-effects and the end result will be the
+ * same, so no locking is required.
+ *
+ */
+#include <inttypes.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "fault.h"
+
+static void
+dump_count(FILE *f, size_t length, int nrecords)
+{
+ if (length < PM_VAL_HDR_SIZE + sizeof(int)) {
+ fprintf(f, "Error: bad len (smaller than minimum size %lu)\n",
+ (unsigned long)PM_VAL_HDR_SIZE + sizeof(int));
+ return;
+ }
+ fprintf(f, "nrecords: %d\n", nrecords);
+ if (nrecords < 0) {
+ fprintf(f, "Error: bad nrecords\n");
+ return;
+ }
+ if (nrecords == 0) {
+ fprintf(f, "Warning: no event records\n");
+ return;
+ }
+}
+
+static int
+dump_flags(FILE *f, unsigned int flags, int nparams)
+{
+ if (flags != 0)
+ fprintf(f, " flags=%x", flags);
+ if (flags & PM_EVENT_FLAG_MISSED) {
+ fprintf(f, "\n ==> %d missed records", nparams);
+ if (flags != PM_EVENT_FLAG_MISSED)
+ fprintf(f, " (Warning: extra flags %x ignored)",
+ flags & (~PM_EVENT_FLAG_MISSED));
+ fputc('\n', f);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+dump_parameter(FILE *f, pmEventParameter *epp)
+{
+ pmAtomValue atom;
+ char strbuf[20];
+ char *vbuf;
+ char *name;
+
+ if (pmNameID(epp->ep_pmid, &name) == 0) {
+ fprintf(f, " %s", name);
+ free(name);
+ } else {
+ fprintf(f, " %s", pmIDStr_r(epp->ep_pmid, strbuf, sizeof(strbuf)));
+ }
+
+ vbuf = (char *)epp + sizeof(epp->ep_pmid) + sizeof(int);
+ switch (epp->ep_type) {
+ case PM_TYPE_32:
+ fprintf(f, " = %i", *((__int32_t *)vbuf));
+ break;
+ case PM_TYPE_U32:
+ fprintf(f, " = %u", *((__uint32_t *)vbuf));
+ break;
+ case PM_TYPE_64:
+ memcpy((void *)&atom.ll, (void *)vbuf, sizeof(atom.ll));
+ fprintf(f, " = %"PRIi64, atom.ll);
+ break;
+ case PM_TYPE_U64:
+ memcpy((void *)&atom.ull, (void *)vbuf, sizeof(atom.ull));
+ fprintf(f, " = %"PRIu64, atom.ull);
+ break;
+ case PM_TYPE_FLOAT:
+ memcpy((void *)&atom.f, (void *)vbuf, sizeof(atom.f));
+ fprintf(f, " = %.8g", (double)atom.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ memcpy((void *)&atom.d, (void *)vbuf, sizeof(atom.d));
+ fprintf(f, " = %.16g", atom.d);
+ break;
+ case PM_TYPE_STRING:
+ fprintf(f, " = \"%*.*s\"", epp->ep_len-PM_VAL_HDR_SIZE,
+ epp->ep_len-PM_VAL_HDR_SIZE, vbuf);
+ break;
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ fprintf(f, " = [%08x...]", ((__uint32_t *)vbuf)[0]);
+ break;
+ default:
+ fprintf(f, " : bad type %s",
+ pmTypeStr_r(epp->ep_type, strbuf, sizeof(strbuf)));
+ }
+ fputc('\n', f);
+}
+
+/*
+ * Dump a packed array of event records ... need to be paranoid
+ * with checking here, because typically called after
+ * __pmCheck[HighRes]EventRecords() finds an error.
+ * Process the idx'th instance.
+ */
+void
+dump_event_records(FILE *f, pmValueSet *vsp, int idx, int highres)
+{
+ char *base;
+ char *valend; /* end of the value */
+ char strbuf[20];
+ size_t length;
+ int nrecords;
+ int nparams;
+ int r; /* records index */
+ int p; /* parameters in a record ... */
+
+ fprintf(f, "Event Records Dump ...\n");
+ fprintf(f, "PMID: %s numval: %d",
+ pmIDStr_r(vsp->pmid, strbuf, sizeof(strbuf)), vsp->numval);
+ if (vsp->numval <= 0) {
+ fprintf(f, "\nError: bad numval\n");
+ return;
+ }
+ fprintf(f, " valfmt: %d", vsp->valfmt);
+ if (vsp->valfmt != PM_VAL_DPTR && vsp->valfmt != PM_VAL_SPTR) {
+ fprintf(f, "\nError: bad valfmt\n");
+ return;
+ }
+ if (vsp->vlist[idx].inst != PM_IN_NULL)
+ fprintf(f, " inst: %d", vsp->vlist[idx].inst);
+
+ if (highres) {
+ pmHighResEventArray *hreap;
+
+ hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval;
+ fprintf(f, " vtype: %s vlen: %d\n",
+ pmTypeStr_r(hreap->ea_type, strbuf, sizeof(strbuf)),
+ hreap->ea_len);
+ if (hreap->ea_type != PM_TYPE_HIGHRES_EVENT) {
+ fprintf(f, "Error: bad highres vtype\n");
+ return;
+ }
+ length = hreap->ea_len;
+ nrecords = hreap->ea_nrecords;
+ valend = &((char *)hreap)[length];
+ base = (char *)&hreap->ea_record[0];
+ }
+ else {
+ pmEventArray *eap;
+
+ eap = (pmEventArray *)vsp->vlist[idx].value.pval;
+ fprintf(f, " vtype: %s vlen: %d\n",
+ pmTypeStr_r(eap->ea_type, strbuf, sizeof(strbuf)), eap->ea_len);
+ if (eap->ea_type != PM_TYPE_EVENT) {
+ fprintf(f, "Error: bad vtype\n");
+ return;
+ }
+ length = eap->ea_len;
+ nrecords = eap->ea_nrecords;
+ valend = &((char *)eap)[length];
+ base = (char *)&eap->ea_record[0];
+ }
+ dump_count(f, length, nrecords);
+
+ for (r = 0; r < nrecords; r++) {
+ pmEventParameter *epp;
+ unsigned int flags;
+ size_t size;
+
+ fprintf(f, "Event Record [%d]", r);
+
+ if (highres) {
+ pmHighResEventRecord *herp = (pmHighResEventRecord *)base;
+
+ size = sizeof(herp->er_timestamp) + sizeof(herp->er_flags) +
+ sizeof(herp->er_nparams);
+ if (base + size > valend) {
+ fprintf(f, " Error: buffer overflow\n");
+ return;
+ }
+ flags = herp->er_flags;
+ nparams = herp->er_nparams;
+ } else {
+ pmEventRecord *erp = (pmEventRecord *)base;
+
+ size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) +
+ sizeof(erp->er_nparams);
+ if (base + size > valend) {
+ fprintf(f, " Error: buffer overflow\n");
+ return;
+ }
+ flags = erp->er_flags;
+ nparams = erp->er_nparams;
+ }
+ base += size;
+
+ if (dump_flags(f, flags, nparams) != 0)
+ continue;
+
+ fprintf(f, " with %d parameters\n", nparams);
+ for (p = 0; p < nparams; p++) {
+ fprintf(f, " Parameter [%d]:", p);
+ if (base + sizeof(pmEventParameter) > valend) {
+ fprintf(f, " Error: buffer overflow\n");
+ return;
+ }
+ epp = (pmEventParameter *)base;
+ size = sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ if (base + size > valend) {
+ fprintf(f, " Error: buffer overflow\n");
+ return;
+ }
+ dump_parameter(f, epp);
+ base += size;
+ }
+ }
+}
+
+void
+__pmDumpEventRecords(FILE *f, pmValueSet *vsp, int idx)
+{
+ dump_event_records(f, vsp, idx, 0);
+}
+
+void
+__pmDumpHighResEventRecords(FILE *f, pmValueSet *vsp, int idx)
+{
+ dump_event_records(f, vsp, idx, 1);
+}
+
+/*
+ * Integrity checker for a packed array of event records, check
+ * the idx'th instance.
+ */
+int
+check_event_records(pmValueSet *vsp, int idx, int highres)
+{
+ char *base;
+ char *valend; /* end of the value */
+ pmEventParameter *epp;
+ int nrecords;
+ int nparams;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+
+ if (vsp->numval < 1)
+ return vsp->numval;
+ if (vsp->valfmt != PM_VAL_DPTR && vsp->valfmt != PM_VAL_SPTR)
+ return PM_ERR_CONV;
+
+ if (highres) {
+ pmHighResEventArray *hreap;
+
+ hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval;
+ if (hreap->ea_type != PM_TYPE_HIGHRES_EVENT)
+ return PM_ERR_TYPE;
+ if (hreap->ea_len < PM_VAL_HDR_SIZE + sizeof(int))
+ return PM_ERR_TOOSMALL;
+ nrecords = hreap->ea_nrecords;
+ base = (char *)&hreap->ea_record[0];
+ valend = &((char *)hreap)[hreap->ea_len];
+ }
+ else {
+ pmEventArray *eap;
+
+ eap = (pmEventArray *)vsp->vlist[idx].value.pval;
+ if (eap->ea_type != PM_TYPE_EVENT)
+ return PM_ERR_TYPE;
+ if (eap->ea_len < PM_VAL_HDR_SIZE + sizeof(eap->ea_nrecords))
+ return PM_ERR_TOOSMALL;
+ nrecords = eap->ea_nrecords;
+ base = (char *)&eap->ea_record[0];
+ valend = &((char *)eap)[eap->ea_len];
+ }
+ if (nrecords < 0)
+ return PM_ERR_TOOSMALL;
+
+ /* header seems OK, onto each event record */
+ for (r = 0; r < nrecords; r++) {
+ unsigned int flags;
+ size_t size;
+
+ if (highres) {
+ pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base;
+
+ size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) +
+ sizeof(hrerp->er_nparams);
+ if (base + size > valend)
+ return PM_ERR_TOOBIG;
+ flags = hrerp->er_flags;
+ nparams = hrerp->er_nparams;
+ }
+ else {
+ pmEventRecord *erp = (pmEventRecord *)base;
+
+ size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) +
+ sizeof(erp->er_nparams);
+ if (base + size > valend)
+ return PM_ERR_TOOBIG;
+ flags = erp->er_flags;
+ nparams = erp->er_nparams;
+ }
+ base += size;
+
+ if (flags & PM_EVENT_FLAG_MISSED) {
+ if (flags == PM_EVENT_FLAG_MISSED)
+ nparams = 0;
+ else {
+ /*
+ * not legal to have other flag bits set when
+ * PM_EVENT_FLAG_MISSED is set
+ */
+ return PM_ERR_CONV;
+ }
+ }
+
+ for (p = 0; p < nparams; p++) {
+ if (base + sizeof(pmEventParameter) > valend)
+ return PM_ERR_TOOBIG;
+ epp = (pmEventParameter *)base;
+ size = sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ if (base + size > valend)
+ return PM_ERR_TOOBIG;
+ base += size;
+ }
+ }
+ return 0;
+}
+
+int
+__pmCheckEventRecords(pmValueSet *vsp, int idx)
+{
+ return check_event_records(vsp, idx, 0);
+}
+
+int
+__pmCheckHighResEventRecords(pmValueSet *vsp, int idx)
+{
+ return check_event_records(vsp, idx, 1);
+}
+
+ static char *name_flags = "event.flags";
+ static char *name_missed = "event.missed";
+
+static int
+register_event_metrics(const char *caller)
+{
+ static int first = 1;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (first) {
+ int sts;
+
+PM_FAULT_POINT("libpcp/" __FILE__ ":5", PM_FAULT_PMAPI);
+ if (first == 1) {
+ sts = __pmRegisterAnon(name_flags, PM_TYPE_U32);
+ if (sts < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s: Warning: failed to register %s: %s\n",
+ caller, name_flags, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ first = 2;
+ }
+
+PM_FAULT_POINT("libpcp/" __FILE__ ":6", PM_FAULT_PMAPI);
+ sts = __pmRegisterAnon(name_missed, PM_TYPE_U32);
+ if (sts < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s: Warning: failed to register %s: %s\n",
+ caller, name_missed, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ first = 0;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+/*
+ * flags is optionally unpacked into an extra anon events.flags metric
+ * before all the event record parameters, and for PM_EVENT_FLAG_MISSED
+ * nparams is a count of the missed records.
+ */
+static int
+count_event_parameters(unsigned int flags, int nparams)
+{
+ if (flags == 0)
+ return nparams;
+ else if (flags & PM_EVENT_FLAG_MISSED)
+ return 2;
+ return nparams + 1;
+}
+
+static int
+add_event_parameter(const char *caller, pmEventParameter *epp, int idx,
+ unsigned int flags, int nparams, pmValueSet **vsetp)
+{
+ pmValueSet *vset;
+ char *vbuf;
+ char errmsg[PM_MAXERRMSGLEN];
+ int sts;
+ int need;
+ int want;
+ int vsize;
+
+ /* always have numval == 1 */
+PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC);
+ if ((vset = (pmValueSet *)malloc(sizeof(pmValueSet))) == NULL)
+ return -oserror();
+
+ if (idx == 0 && flags != 0) {
+ /* rewrite non-zero er_flags as the anon event.flags metric */
+ static pmID pmid_flags = 0;
+
+ if (pmid_flags == 0) {
+ if ((sts = pmLookupName(1, &name_flags, &pmid_flags)) < 0) {
+ fprintf(stderr, "%s: Warning: failed to get PMID for %s: %s\n",
+ caller, name_flags, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ __pmid_int(&pmid_flags)->item = 1;
+ }
+ }
+ vset->pmid = pmid_flags;
+ vset->numval = 1;
+ vset->vlist[0].inst = PM_IN_NULL;
+ vset->valfmt = PM_VAL_INSITU;
+ vset->vlist[0].value.lval = flags;
+ *vsetp = vset;
+ return 1;
+ }
+ if (idx == 1 && flags & PM_EVENT_FLAG_MISSED) {
+ /* rewrite missed count as the anon event.missed metric */
+ static pmID pmid_missed = 0;
+
+ if (pmid_missed == 0) {
+ if ((sts = pmLookupName(1, &name_missed, &pmid_missed)) < 0) {
+ fprintf(stderr, "%s: Warning: failed to get PMID for %s: %s\n",
+ caller, name_missed, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ __pmid_int(&pmid_missed)->item = 1;
+ }
+ }
+ vset->pmid = pmid_missed;
+ vset->numval = 1;
+ vset->vlist[0].inst = PM_IN_NULL;
+ vset->valfmt = PM_VAL_INSITU;
+ vset->vlist[0].value.lval = nparams;
+ *vsetp = vset;
+ return 2;
+ }
+
+ vset->pmid = epp->ep_pmid;
+ vset->numval = 1;
+ vset->vlist[0].inst = PM_IN_NULL;
+ vbuf = (char *)epp + sizeof(epp->ep_pmid) + sizeof(int);
+ switch (epp->ep_type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ vset->valfmt = PM_VAL_INSITU;
+ memcpy((void *)&vset->vlist[0].value.lval, (void *)vbuf, sizeof(__int32_t));
+ *vsetp = vset;
+ return 0;
+
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ vsize = sizeof(__int64_t);
+ break;
+ case PM_TYPE_FLOAT:
+ vsize = sizeof(float);
+ break;
+ case PM_TYPE_DOUBLE:
+ vsize = sizeof(double);
+ break;
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_STRING:
+ case PM_TYPE_AGGREGATE_STATIC:
+ vsize = epp->ep_len - PM_VAL_HDR_SIZE;
+ break;
+ case PM_TYPE_EVENT: /* no nesting! */
+ case PM_TYPE_HIGHRES_EVENT:
+ default:
+ free(vset);
+ return PM_ERR_TYPE;
+ }
+ need = vsize + PM_VAL_HDR_SIZE;
+ want = need;
+ if (want < sizeof(pmValueBlock))
+ want = sizeof(pmValueBlock);
+PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC);
+ vset->vlist[0].value.pval = (pmValueBlock *)malloc(want);
+ if (vset->vlist[0].value.pval == NULL) {
+ vset->valfmt = PM_VAL_INSITU;
+ return -oserror();
+ }
+ vset->vlist[0].value.pval->vlen = need;
+ vset->vlist[0].value.pval->vtype = epp->ep_type;
+ memcpy((void *)vset->vlist[0].value.pval->vbuf, (void *)vbuf, vsize);
+ vset->valfmt = PM_VAL_DPTR;
+ *vsetp = vset;
+ return 0;
+}
+
+/*
+ * Process the idx'th instance of an event record metric value
+ * and unpack the array of event records into a pmResult.
+ */
+int
+pmUnpackEventRecords(pmValueSet *vsp, int idx, pmResult ***rap)
+{
+ pmEventArray *eap;
+ const char caller[] = "pmUnpackEventRecords";
+ char *base;
+ size_t need;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int numpmid; /* metrics in a pmResult */
+ int sts;
+
+ if ((sts = register_event_metrics(caller)) < 0)
+ return sts;
+
+ if ((sts = __pmCheckEventRecords(vsp, idx)) < 0) {
+ __pmDumpEventRecords(stderr, vsp, idx);
+ return sts;
+ }
+
+ eap = (pmEventArray *)vsp->vlist[idx].value.pval;
+ if (eap->ea_nrecords == 0) {
+ *rap = NULL;
+ return 0;
+ }
+
+ /*
+ * allocate one more than needed as a NULL sentinel to be used
+ * in pmFreeEventResult
+ */
+PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC);
+ need = (eap->ea_nrecords + 1) * sizeof(pmResult *);
+ if ((*rap = (pmResult **)malloc(need)) == NULL)
+ return -oserror();
+
+ base = (char *)&eap->ea_record[0];
+ /* walk packed event record array */
+ for (r = 0; r < eap->ea_nrecords; r++) {
+ pmEventRecord *erp = (pmEventRecord *)base;
+ pmResult *rp;
+
+ numpmid = count_event_parameters(erp->er_flags, erp->er_nparams);
+ need = sizeof(pmResult) + (numpmid-1)*sizeof(pmValueSet *);
+PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC);
+ if ((rp = (pmResult *)malloc(need)) == NULL) {
+ sts = -oserror();
+ r--;
+ goto bail;
+ }
+ (*rap)[r] = rp;
+ rp->timestamp.tv_sec = erp->er_timestamp.tv_sec;
+ rp->timestamp.tv_usec = erp->er_timestamp.tv_usec;
+ rp->numpmid = numpmid;
+ base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams);
+ for (p = 0; p < numpmid; p++) {
+ pmEventParameter *epp = (pmEventParameter *)base;
+
+ if ((sts = add_event_parameter(caller, epp, p,
+ erp->er_flags, erp->er_nparams,
+ &rp->vset[p])) < 0) {
+ rp->numpmid = p;
+ goto bail;
+ }
+ if (sts == 0)
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ }
+ }
+ (*rap)[r] = NULL; /* sentinel */
+
+ if (pmDebug & DBG_TRACE_FETCH) {
+ fprintf(stderr, "pmUnpackEventRecords returns ...\n");
+ for (r = 0; r < eap->ea_nrecords; r++) {
+ fprintf(stderr, "pmResult[%d]\n", r);
+ __pmDumpResult(stderr, (*rap)[r]);
+ }
+ }
+
+ return eap->ea_nrecords;
+
+bail:
+ while (r >= 0) {
+ if ((*rap)[r] != NULL)
+ pmFreeResult((*rap)[r]);
+ r--;
+ }
+ free(*rap);
+ *rap = NULL;
+ return sts;
+}
+
+/*
+ * Process the idx'th instance of a highres event record metric value
+ * and unpack the array of event records into a pmHighResResult.
+ */
+int
+pmUnpackHighResEventRecords(pmValueSet *vsp, int idx, pmHighResResult ***rap)
+{
+ pmHighResEventArray *hreap;
+ const char caller[] = "pmUnpackHighResEventRecords";
+ char *base;
+ size_t need;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int numpmid; /* metrics in a pmResult */
+ int sts;
+
+ if ((sts = register_event_metrics(caller)) < 0)
+ return sts;
+
+ if ((sts = __pmCheckHighResEventRecords(vsp, idx)) < 0) {
+ __pmDumpHighResEventRecords(stderr, vsp, idx);
+ return sts;
+ }
+
+ hreap = (pmHighResEventArray *)vsp->vlist[idx].value.pval;
+ if (hreap->ea_nrecords == 0) {
+ *rap = NULL;
+ return 0;
+ }
+
+ /*
+ * allocate one more than needed as a NULL sentinel to be used
+ * in pmFreeHighResEventResult
+ */
+PM_FAULT_POINT("libpcp/" __FILE__ ":7", PM_FAULT_ALLOC);
+ need = (hreap->ea_nrecords + 1) * sizeof(pmHighResResult *);
+ if ((*rap = (pmHighResResult **)malloc(need)) == NULL)
+ return -oserror();
+
+ base = (char *)&hreap->ea_record[0];
+ /* walk packed event record array */
+ for (r = 0; r < hreap->ea_nrecords; r++) {
+ pmHighResEventRecord *erp = (pmHighResEventRecord *)base;
+ pmHighResResult *rp;
+
+ numpmid = count_event_parameters(erp->er_flags, erp->er_nparams);
+ need = sizeof(pmHighResResult) + (numpmid-1)*sizeof(pmValueSet *);
+PM_FAULT_POINT("libpcp/" __FILE__ ":8", PM_FAULT_ALLOC);
+ if ((rp = (pmHighResResult *)malloc(need)) == NULL) {
+ sts = -oserror();
+ r--;
+ goto bail;
+ }
+ (*rap)[r] = rp;
+ rp->timestamp.tv_sec = erp->er_timestamp.tv_sec;
+ rp->timestamp.tv_nsec = erp->er_timestamp.tv_nsec;
+ rp->numpmid = numpmid;
+ base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams);
+ for (p = 0; p < numpmid; p++) {
+ pmEventParameter *epp = (pmEventParameter *)base;
+
+ if ((sts = add_event_parameter(caller, epp, p,
+ erp->er_flags, erp->er_nparams,
+ &rp->vset[p])) < 0) {
+ rp->numpmid = p;
+ goto bail;
+ }
+ if (sts == 0)
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ }
+ }
+ (*rap)[r] = NULL; /* sentinel */
+
+ if (pmDebug & DBG_TRACE_FETCH) {
+ fprintf(stderr, "%s returns ...\n", caller);
+ for (r = 0; r < hreap->ea_nrecords; r++) {
+ fprintf(stderr, "pmHighResResult[%d]\n", r);
+ __pmDumpHighResResult(stderr, (*rap)[r]);
+ }
+ }
+
+ return hreap->ea_nrecords;
+
+bail:
+ while (r >= 0) {
+ if ((*rap)[r] != NULL)
+ pmFreeHighResResult((*rap)[r]);
+ r--;
+ }
+ free(*rap);
+ *rap = NULL;
+ return sts;
+}
+
+void
+pmFreeEventResult(pmResult **rset)
+{
+ int r;
+
+ if (rset == NULL)
+ return;
+ for (r = 0; rset[r] != NULL; r++)
+ pmFreeResult(rset[r]);
+ free(rset);
+}
+
+void
+pmFreeHighResEventResult(pmHighResResult **rset)
+{
+ int r;
+
+ if (rset == NULL)
+ return;
+ for (r = 0; rset[r] != NULL; r++)
+ pmFreeHighResResult(rset[r]);
+ free(rset);
+}
diff --git a/src/libpcp/src/exports b/src/libpcp/src/exports
new file mode 100644
index 0000000..545a9b3
--- /dev/null
+++ b/src/libpcp/src/exports
@@ -0,0 +1,472 @@
+PCP_3.0 {
+ global:
+ pmAddProfile;
+ pmAtomStr;
+ pmAtomStr_r;
+ pmConvScale;
+ pmCtime;
+ pmDebug;
+ pmDelProfile;
+ pmDerivedErrStr;
+ pmDestroyContext;
+ pmDupContext;
+ pmErrStr;
+ pmErrStr_r;
+ pmEventFlagsStr;
+ pmEventFlagsStr_r;
+ pmExtractValue;
+ pmFetch;
+ pmFetchArchive;
+ pmflush;
+ pmFreeEventResult;
+ pmFreeMetricSpec;
+ pmFreeResult;
+ pmGetArchiveEnd;
+ pmGetArchiveLabel;
+ pmGetChildren;
+ pmGetChildrenStatus;
+ pmGetConfig;
+ pmGetContextHostName;
+ pmGetInDom;
+ pmGetInDomArchive;
+ pmGetPMNSLocation;
+ pmIDStr;
+ pmIDStr_r;
+ pmInDomStr;
+ pmInDomStr_r;
+ pmLoadASCIINameSpace;
+ pmLoadDerivedConfig;
+ pmLoadNameSpace;
+ pmLocaltime;
+ pmLookupDesc;
+ pmLookupInDom;
+ pmLookupInDomArchive;
+ pmLookupInDomText;
+ pmLookupName;
+ pmLookupText;
+ pmNameAll;
+ pmNameID;
+ pmNameInDom;
+ pmNameInDomArchive;
+ pmNewContext;
+ pmNewContextZone;
+ pmNewZone;
+ pmNumberStr;
+ pmNumberStr_r;
+ pmParseInterval;
+ pmParseMetricSpec;
+ pmParseTimeWindow;
+ pmprintf;
+ pmPrintValue;
+ pmProgname;
+ pmReconnectContext;
+ pmRegisterDerived;
+ pmSetMode;
+ pmSortInstances;
+ pmStore;
+ pmTraversePMNS;
+ pmTraversePMNS_r;
+ pmTrimNameSpace;
+ pmTypeStr;
+ pmTypeStr_r;
+ pmUnitsStr;
+ pmUnitsStr_r;
+ pmUnloadNameSpace;
+ pmUnpackEventRecords;
+ pmUseContext;
+ pmUseZone;
+ pmWhichContext;
+ pmWhichZone;
+
+ __pmAbsolutePath;
+ __pmAccAddAccount;
+ __pmAccAddClient;
+ __pmAccAddGroup;
+ __pmAccAddHost;
+ __pmAccAddOp;
+ __pmAccAddUser;
+ __pmAccDelAccount;
+ __pmAccDelClient;
+ __pmAccDumpGroups;
+ __pmAccDumpHosts;
+ __pmAccDumpLists;
+ __pmAccDumpUsers;
+ __pmAccept;
+ __pmAccFreeSavedGroups;
+ __pmAccFreeSavedHosts;
+ __pmAccFreeSavedLists;
+ __pmAccFreeSavedUsers;
+ __pmAccRestoreGroups;
+ __pmAccRestoreHosts;
+ __pmAccRestoreLists;
+ __pmAccRestoreUsers;
+ __pmAccSaveGroups;
+ __pmAccSaveHosts;
+ __pmAccSaveLists;
+ __pmAccSaveUsers;
+ __pmAddHostPorts;
+ __pmAddPMNSNode;
+ __pmAFblock;
+ __pmAFisempty;
+ __pmAFregister;
+ __pmAFunblock;
+ __pmAFunregister;
+ __pmAPIConfig;
+ __pmAttrKeyStr_r;
+ __pmAttrStr_r;
+ __pmAuxConnectPMCD;
+ __pmAuxConnectPMCDPort;
+ __pmAuxConnectPMCDUnixSocket;
+ __pmBind;
+ __pmCheckEventRecords;
+ __pmCheckSum;
+ __pmCloseSocket;
+ __pmConfig;
+ __pmConnect;
+ __pmConnectGetPorts;
+ __pmConnectLocal;
+ __pmConnectLogger;
+ __pmConnectPMCD;
+ __pmConnectTo;
+ __pmControlLog;
+ __pmConvertTime;
+ __pmCountPDUBuf;
+ __pmCreateIPv6Socket;
+ __pmCreateSocket;
+ __pmCreateUnixSocket;
+ __pmDataIPC;
+ __pmDataIPCSize;
+ __pmDecodeAuth;
+ __pmDecodeChildReq;
+ __pmDecodeCreds;
+ __pmDecodeDesc;
+ __pmDecodeDescReq;
+ __pmDecodeError;
+ __pmDecodeFetch;
+ __pmDecodeIDList;
+ __pmDecodeInstance;
+ __pmDecodeInstanceReq;
+ __pmDecodeLogControl;
+ __pmDecodeLogRequest;
+ __pmDecodeLogStatus;
+ __pmDecodeNameList;
+ __pmDecodeProfile;
+ __pmDecodeResult;
+ __pmDecodeText;
+ __pmDecodeTextReq;
+ __pmDecodeTraversePMNSReq;
+ __pmDecodeXtendError;
+ __pmDropHostPort;
+ __pmDumpContext;
+ __pmDumpErrTab;
+ __pmDumpEventRecords;
+ __pmDumpIDList;
+ __pmDumpInResult;
+ __pmDumpNameAndStatusList;
+ __pmDumpNameList;
+ __pmDumpNameSpace;
+ __pmDumpProfile;
+ __pmDumpResult;
+ __pmDumpStatusList;
+ __pmEncodeResult;
+ __pmEventTrace;
+ __pmEventTrace_r;
+ __pmExportPMNS;
+ __pmFaultInject;
+ __pmFaultSummary;
+ __pmFD;
+ __pmFD_CLR;
+ __pmFD_COPY;
+ __pmFD_ISSET;
+ __pmFD_SET;
+ __pmFD_ZERO;
+ __pmFindPDUBuf;
+ __pmFindPMDA;
+ __pmFindProfile;
+ __pmFinishResult;
+ __pmFixPMNSHashTab;
+ __pmFreeAttrsSpec;
+ __pmFreeHostAttrsSpec;
+ __pmFreeHostSpec;
+ __pmFreeInResult;
+ __pmFreePMNS;
+ __pmFreeProfile;
+ __pmFreeResultValues;
+ __pmGetAddrInfo;
+ __pmGetAPIConfig;
+ __pmGetArchiveEnd;
+ __pmGetClientId;
+ __pmGetInternalState;
+ __pmGetNameInfo;
+ __pmGetPDU;
+ __pmGetPDUCeiling;
+ __pmGetSockOpt;
+ __pmGetUsername;
+ __pmHandleToPtr;
+ __pmHashAdd;
+ __pmHashClear;
+ __pmHashDel;
+ __pmHashInit;
+ __pmHashSearch;
+ __pmHashWalk;
+ __pmHashWalkCB;
+ __pmHasPMNSFileChanged;
+ __pmHostEntAlloc;
+ __pmHostEntFree;
+ __pmHostEntGetName;
+ __pmHostEntGetSockAddr;
+ __pmInitLocks;
+ __pmInProfile;
+ __pmIsLocalhost;
+ __pmLastVersionIPC;
+ __pmListen;
+ __pmLocalPMDA;
+ __pmLock;
+ __pmLock_libpcp;
+ __pmLogCacheClear;
+ __pmLogChangeVol;
+ __pmLogChkLabel;
+ __pmLogClose;
+ __pmLogCreate;
+ __pmLogFetch;
+ __pmLogFetchInterp;
+ __pmLogFindLocalPorts;
+ __pmLogFindPort;
+ __pmLoggerTimeout;
+ __pmLogGetInDom;
+ __pmLogLoadIndex;
+ __pmLogLoadLabel;
+ __pmLogLoadMeta;
+ __pmLogLookupDesc;
+ __pmLogLookupInDom;
+ __pmLogName;
+ __pmLogNameInDom;
+ __pmLogName_r;
+ __pmLogNewFile;
+ __pmLogOpen;
+ __pmLogPutDesc;
+ __pmLogPutIndex;
+ __pmLogPutInDom;
+ __pmLogPutResult;
+ __pmLogRead;
+ __pmLogReads;
+ __pmLogResetInterp;
+ __pmLogSetTime;
+ __pmLogWriteLabel;
+ __pmLookupAttrKey;
+ __pmLookupDSO;
+ __pmLoopBackAddress;
+ __pmMapErrno;
+ __pmMemoryMap;
+ __pmMemoryUnmap;
+ __pmMktime;
+ __pmMultiThreaded;
+ __pmNativeConfig;
+ __pmNativePath;
+ __pmNewPMNS;
+ __pmNoMem;
+ __pmNotifyErr;
+ __pmOpenLog;
+ __pmOptFetchAdd;
+ __pmOptFetchDel;
+ __pmOptFetchDump;
+ __pmOptFetchGetParams;
+ __pmOptFetchPutParams;
+ __pmOptFetchRedo;
+ __pmOverrideLastFd;
+ __pmParseCtime;
+ __pmParseDebug;
+ __pmParseHostAttrsSpec;
+ __pmParseHostSpec;
+ __pmParseTime;
+ __pmPathSeparator;
+ __pmPDUCntIn;
+ __pmPDUCntOut;
+ __pmPDUTypeStr;
+ __pmPDUTypeStr_r;
+ __pmPinPDUBuf;
+ __pmPrepareFetch;
+ __pmPrintDesc;
+ __pmPrintIPC;
+ __pmPrintStamp;
+ __pmPrintTimeval;
+ __pmProcessCreate;
+ __pmProcessDataSize;
+ __pmProcessExists;
+ __pmProcessRunTimes;
+ __pmProcessTerminate;
+ __pmRead;
+ __pmRecv;
+ __pmRegisterAnon;
+ __pmResetIPC;
+ __pmRotateLog;
+ __pmSecureClientHandshake;
+ __pmSecureServerHandshake;
+ __pmSecureServerSetup;
+ __pmSecureServerShutdown;
+ __pmSelectRead;
+ __pmSelectWrite;
+ __pmSend;
+ __pmSendAuth;
+ __pmSendChildReq;
+ __pmSendCreds;
+ __pmSendDesc;
+ __pmSendDescReq;
+ __pmSendError;
+ __pmSendFetch;
+ __pmSendIDList;
+ __pmSendInstance;
+ __pmSendInstanceReq;
+ __pmSendLogControl;
+ __pmSendLogRequest;
+ __pmSendLogStatus;
+ __pmSendNameList;
+ __pmSendProfile;
+ __pmSendResult;
+ __pmSendText;
+ __pmSendTextReq;
+ __pmSendTraversePMNSReq;
+ __pmSendXtendError;
+ __pmServerAddInterface;
+ __pmServerAddNewClients;
+ __pmServerAddPorts;
+ __pmServerAdvertisePresence;
+ __pmServerClearFeature;
+ __pmServerCloseRequestPorts;
+ __pmServerDumpRequestPorts;
+ __pmServerHasFeature;
+ __pmServerOpenRequestPorts;
+ __pmServerRequestPortString;
+ __pmServerSetFeature;
+ __pmServerSetLocalCreds;
+ __pmServerSetLocalSocket;
+ __pmServerSetServiceSpec;
+ __pmServerUnadvertisePresence;
+ __pmSetClientId;
+ __pmSetClientIdArgv;
+ __pmSetDataIPC;
+ __pmSetInternalState;
+ __pmSetPDUCeiling;
+ __pmSetPDUCntBuf;
+ __pmSetProcessIdentity;
+ __pmSetProgname;
+ __pmSetSignalHandler;
+ __pmSetSocketIPC;
+ __pmSetSockOpt;
+ __pmSetVersionIPC;
+ __pmShutdown;
+ __pmSockAddrAlloc;
+ __pmSockAddrCompare;
+ __pmSockAddrDup;
+ __pmSockAddrFree;
+ __pmSockAddrGetFamily;
+ __pmSockAddrGetPort;
+ __pmSockAddrInit;
+ __pmSockAddrIsInet;
+ __pmSockAddrIsIPv6;
+ __pmSockAddrIsLoopBack;
+ __pmSockAddrIsUnix;
+ __pmSockAddrMask;
+ __pmSockAddrSetFamily;
+ __pmSockAddrSetPath;
+ __pmSockAddrSetPort;
+ __pmSockAddrSetScope;
+ __pmSockAddrSize;
+ __pmSockAddrToString;
+ __pmSocketIPC;
+ __pmSpecLocalPMDA;
+ __pmStringToSockAddr;
+ __pmStuffValue;
+ __pmSyslog;
+ __pmtimevalAdd;
+ __pmtimevalFromReal;
+ __pmtimevalNow;
+ __pmtimevalPause;
+ __pmtimevalSleep;
+ __pmtimevalSub;
+ __pmTimevalSub;
+ __pmtimevalToReal;
+ __pmTimezone;
+ __pmTimezone_r;
+ __pmUnlock;
+ __pmUnparseHostAttrsSpec;
+ __pmUnparseHostSpec;
+ __pmUnpinPDUBuf;
+ __pmUsePMNS;
+ __pmVersionIPC;
+ __pmWrite;
+ __pmXmitPDU;
+
+ local: *;
+};
+
+PCP_3.1 {
+ global:
+ pmDiscoverServices;
+} PCP_3.0;
+
+PCP_3.2 {
+ global:
+ pmGetContextHostName_r;
+ pmGetContextOptions;
+ pmGetOptions;
+ pmFreeOptions;
+ pmUsageMessage;
+
+ __pmStartOptions;
+ __pmAddOptArchive;
+ __pmAddOptArchiveList;
+ __pmAddOptHost;
+ __pmAddOptHostList;
+ __pmEndOptions;
+} PCP_3.1;
+
+PCP_3.3 {
+ global:
+ pmgetopt_r;
+
+ __pmLogLocalSocketDefault;
+ __pmLogLocalSocketUser;
+ __pmMakePath;
+} PCP_3.2;
+
+PCP_3.4 {
+ global:
+ __pmConnectCheckError;
+ __pmConnectRestoreFlags;
+ __pmFdOpen;
+ __pmGetFileStatusFlags;
+ __pmSetFileStatusFlags;
+ __pmGetFileDescriptorFlags;
+ __pmSetFileDescriptorFlags;
+ __pmSocketClosed;
+ __pmLogPutResult2;
+} PCP_3.3;
+
+PCP_3.5 {
+ global:
+ __pmDumpStack;
+ __pmServerCreatePIDFile;
+} PCP_3.4;
+
+PCP_3.6 {
+ global:
+ __pmDiscoverServicesWithOptions;
+ __pmDumpNameNode;
+ __pmFreeInterpData;
+ __pmAddOptArchiveFolio;
+} PCP_3.5;
+
+PCP_3.7 {
+ global:
+ pmFreeHighResResult;
+ pmFreeHighResEventResult;
+ pmUnpackHighResEventRecords;
+
+ __pmCheckHighResEventRecords;
+ __pmDumpHighResEventRecords;
+ __pmDumpHighResResult;
+ __pmPrintHighResStamp;
+ __pmPrintTimespec;
+ __pmGetTimespec;
+} PCP_3.6;
diff --git a/src/libpcp/src/fault.c b/src/libpcp/src/fault.c
new file mode 100644
index 0000000..5ba8bf0
--- /dev/null
+++ b/src/libpcp/src/fault.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "fault.h"
+/* need pmda.h and libpcp_pmda for the pmdaCache* routines */
+#include "pmda.h"
+
+#include <ctype.h>
+
+/*
+ * Fault Injection - run-time control structure
+ */
+typedef struct {
+ int ntrip;
+ int op;
+ int thres;
+ int nfault;
+} control_t;
+
+#define PM_FAULT_LT 0
+#define PM_FAULT_LE 1
+#define PM_FAULT_EQ 2
+#define PM_FAULT_GE 3
+#define PM_FAULT_GT 4
+#define PM_FAULT_NE 5
+#define PM_FAULT_MOD 6
+
+#ifdef PM_FAULT_INJECTION
+
+int __pmFault_arm;
+
+#define FAULT_INDOM pmInDom_build(DYNAMIC_PMID, 1024)
+
+static void
+__pmFaultAtExit(void)
+{
+ __pmFaultSummary(stderr);
+}
+
+void
+__pmFaultInject(const char *ident, int class)
+{
+ static int first = 1;
+ int sts;
+ control_t *cp;
+
+ if (first) {
+ char *fname = getenv("PM_FAULT_CONTROL");
+ if (fname != NULL) {
+ FILE *f;
+ if ((f = fopen(fname, "r")) == NULL) {
+ char msgbuf[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmFaultInject: cannot open \"%s\": %s\n", fname, pmErrStr_r(-errno, msgbuf, sizeof(msgbuf)));
+ }
+ else {
+ char line[128];
+ int lineno = 0;
+ /*
+ * control line format
+ * ident - start of line to first white space
+ * guard - optional, consists of <op> and threshold
+ * <op> is one of <, <=, ==, >=, >=, != or %
+ * threshold is an integer value ...
+ * fault will be injected when
+ * tripcount <op> threshold == 1
+ * default guard is ">0", i.e. fault on every trip
+ * leading # => comment
+ */
+ pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_CULL);
+ while (fgets(line, sizeof(line), f) != NULL) {
+ char *lp = line;
+ char *sp;
+ char *ep;
+ int op;
+ int thres;
+ lineno++;
+ while (*lp) {
+ if (*lp == '\n') {
+ *lp = '\0';
+ break;
+ }
+ lp++;
+ }
+ lp = line;
+ while (*lp && isspace((int)*lp)) lp++;
+ /* comment? */
+ if (*lp == '#')
+ continue;
+ sp = lp;
+ while (*lp && !isspace((int)*lp)) lp++;
+ /* empty line? */
+ if (lp == sp)
+ continue;
+ ep = lp;
+ while (*lp && isspace((int)*lp)) lp++;
+ if (*lp == '\0') {
+ op = PM_FAULT_GT;
+ thres = 0;
+ }
+ else {
+ if (strncmp(lp, "<=", 2) == 0) {
+ op = PM_FAULT_LE;
+ lp +=2;
+ }
+ else if (strncmp(lp, ">=", 2) == 0) {
+ op = PM_FAULT_GE;
+ lp +=2;
+ }
+ else if (strncmp(lp, "!=", 2) == 0) {
+ op = PM_FAULT_NE;
+ lp +=2;
+ }
+ else if (strncmp(lp, "==", 2) == 0) {
+ op = PM_FAULT_EQ;
+ lp +=2;
+ }
+ else if (*lp == '<') {
+ op = PM_FAULT_LT;
+ lp++;
+ }
+ else if (*lp == '>') {
+ op = PM_FAULT_GT;
+ lp++;
+ }
+ else if (*lp == '%') {
+ op = PM_FAULT_MOD;
+ lp++;
+ }
+ else {
+ fprintf(stderr, "Ignoring: %s[%d]: illegal operator: %s\n", fname, lineno, line);
+ continue;
+ }
+ }
+ while (*lp && isspace((int)*lp)) lp++;
+ thres = (int)strtol(lp, &lp, 10);
+ while (*lp && isspace((int)*lp)) lp++;
+ if (*lp != '\0') {
+ fprintf(stderr, "Ignoring: %s[%d]: non-numeric threshold: %s\n", fname, lineno, line);
+ continue;
+ }
+ cp = (control_t *)malloc(sizeof(control_t));
+ if (cp == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmFaultInject: malloc failed: %s\n", pmErrStr_r(-errno, errmsg, sizeof(errmsg)));
+ break;
+ }
+ *ep = '\0';
+ cp->ntrip = cp->nfault = 0;
+ cp->op = op;
+ cp->thres = thres;
+ sts = pmdaCacheStore(FAULT_INDOM, PMDA_CACHE_ADD, sp, cp);
+ if (sts < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s[%d]: %s\n", fname, lineno, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ }
+ fclose(f);
+ }
+ }
+#ifdef HAVE_ATEXIT
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FAULT)
+ atexit(__pmFaultAtExit);
+#endif
+#endif
+ first = 0;
+ }
+
+ sts = pmdaCacheLookupName(FAULT_INDOM, ident, NULL, (void **)&cp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ cp->ntrip++;
+ __pmFault_arm = 0;
+ switch (cp->op) {
+ case PM_FAULT_LT:
+ __pmFault_arm = (cp->ntrip < cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_LE:
+ __pmFault_arm = (cp->ntrip <= cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_EQ:
+ __pmFault_arm = (cp->ntrip == cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_GE:
+ __pmFault_arm = (cp->ntrip >= cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_GT:
+ __pmFault_arm = (cp->ntrip > cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_NE:
+ __pmFault_arm = (cp->ntrip != cp->thres) ? class : 0;
+ break;
+ case PM_FAULT_MOD:
+ __pmFault_arm = ((cp->ntrip % cp->thres) == 1) ? class : 0;
+ break;
+ }
+ if (__pmFault_arm != 0)
+ cp->nfault++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FAULT)
+ fprintf(stderr, "__pmFaultInject(%s) ntrip=%d %s\n", ident, cp->ntrip, __pmFault_arm == 0 ? "SKIP" : "INJECT");
+#endif
+ }
+ else if (sts == PM_ERR_INST) {
+ /*
+ * expected for injection points that are compiled in the code
+ * but not registered via the control file
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FAULT)
+ fprintf(stderr, "__pmFaultInject(%s) not registered\n", ident);
+#endif
+ ;
+ }
+ else {
+ /* oops, this is serious */
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmFaultInject(%s): %s\n", ident, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+
+}
+
+void
+__pmFaultSummary(FILE *f)
+{
+ int inst;
+ char *ident;
+ control_t *cp;
+ int sts;
+ static char *opstr[] = { "<", "<=", "==", ">=", ">", "!=", "%" };
+
+ pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_WALK_REWIND);
+
+ fprintf(f, "=== Fault Injection Summary Report ===\n");
+ while ((inst = pmdaCacheOp(FAULT_INDOM, PMDA_CACHE_WALK_NEXT)) != -1) {
+ sts = pmdaCacheLookup(FAULT_INDOM, inst, &ident, (void **)&cp);
+ if (sts < 0) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(f, "pmdaCacheLookup(%s, %d, %s, ..): %s\n", pmInDomStr_r(FAULT_INDOM, strbuf, sizeof(strbuf)), inst, ident, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ else
+ fprintf(f, "%s: guard trip%s%d, %d trips, %d faults\n", ident, opstr[cp->op], cp->thres, cp->ntrip, cp->nfault);
+
+ }
+}
+
+void
+*__pmFault_malloc(size_t size)
+{
+ if (__pmFault_arm == PM_FAULT_ALLOC) {
+ __pmFault_arm = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ else
+#undef malloc
+ return malloc(size);
+}
+
+void
+*__pmFault_realloc(void *ptr, size_t size)
+{
+ if (__pmFault_arm == PM_FAULT_ALLOC) {
+ __pmFault_arm = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ else
+#undef realloc
+ return realloc(ptr, size);
+}
+
+char *
+__pmFault_strdup(const char *s)
+{
+ if (__pmFault_arm == PM_FAULT_ALLOC) {
+ __pmFault_arm = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ else
+#undef strdup
+ return strdup(s);
+}
+
+#else
+void
+__pmFaultInject(const char *ident, int class)
+{
+ fprintf(stderr, "__pmFaultInject() called but library not compiled with -DPM_FAULT_INJECTION\n");
+ exit(1);
+}
+
+void
+__pmFaultSummary(FILE *f)
+{
+ fprintf(f, "__pmFaultSummary() called but library not compiled with -DPM_FAULT_INJECTION\n");
+ exit(1);
+
+}
+#endif
diff --git a/src/libpcp/src/fetch.c b/src/libpcp/src/fetch.c
new file mode 100644
index 0000000..de684af
--- /dev/null
+++ b/src/libpcp/src/fetch.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 1995-2006,2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+static int
+request_fetch(int ctxid, __pmContext *ctxp, int numpmid, pmID pmidlist[])
+{
+ int n;
+
+ if (ctxp->c_sent == 0) {
+ /*
+ * current profile is _not_ already cached at other end of
+ * IPC, so send get current profile
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PROFILE) {
+ fprintf(stderr, "pmFetch: calling __pmSendProfile, context: %d\n",
+ ctxid);
+ __pmDumpProfile(stderr, PM_INDOM_NULL, ctxp->c_instprof);
+ }
+#endif
+ if ((n = __pmSendProfile(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ ctxid, ctxp->c_instprof)) < 0)
+ return (__pmMapErrno(n));
+ else
+ ctxp->c_sent = 1;
+ }
+
+ n = __pmSendFetch(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), ctxid,
+ &ctxp->c_origin, numpmid, pmidlist);
+ if (n < 0) {
+ n = __pmMapErrno(n);
+ }
+ return n;
+}
+
+int
+__pmPrepareFetch(__pmContext *ctxp, int numpmid, const pmID *ids, pmID **newids)
+{
+ return __dmprefetch(ctxp, numpmid, ids, newids);
+}
+
+int
+__pmFinishResult(__pmContext *ctxp, int count, pmResult **resultp)
+{
+ if (count >= 0)
+ __dmpostfetch(ctxp, resultp);
+ return count;
+}
+
+int
+pmFetch(int numpmid, pmID pmidlist[], pmResult **result)
+{
+ int n;
+
+ if (numpmid < 1) {
+ n = PM_ERR_TOOSMALL;
+ goto done;
+ }
+
+ if ((n = pmWhichContext()) >= 0) {
+ __pmContext *ctxp = __pmHandleToPtr(n);
+ int newcnt;
+ pmID *newlist = NULL;
+ int have_dm;
+
+ if (ctxp == NULL) {
+ n = PM_ERR_NOCONTEXT;
+ goto done;
+ }
+ if (ctxp->c_type == PM_CONTEXT_LOCAL && PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) {
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ PM_UNLOCK(ctxp->c_lock);
+ goto done;
+ }
+
+ /* for derived metrics, may need to rewrite the pmidlist */
+ have_dm = newcnt = __pmPrepareFetch(ctxp, numpmid, pmidlist, &newlist);
+ if (newcnt > numpmid) {
+ /* replace args passed into pmFetch */
+ numpmid = newcnt;
+ pmidlist = newlist;
+ }
+
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ /*
+ * Thread-safe note
+ *
+ * Need to be careful here, because the PMCD changed protocol
+ * may mean several PDUs are returned, but __pmDecodeResult()
+ * may request more info from PMCD if pmDebug is set.
+ *
+ * So unlock ctxp->c_pmcd->pc_lock as soon as possible.
+ */
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ if ((n = request_fetch(n, ctxp, numpmid, pmidlist)) >= 0) {
+ int changed = 0;
+ do {
+ __pmPDU *pb;
+ int pinpdu;
+
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_RESULT) {
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ n = __pmDecodeResult(pb, result);
+ }
+ else if (n == PDU_ERROR) {
+ __pmDecodeError(pb, &n);
+ if (n > 0)
+ /* PMCD state change protocol */
+ changed = n;
+ else
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else {
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ } while (n > 0);
+
+ if (n == 0)
+ n |= changed;
+ }
+ else
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ n = __pmFetchLocal(ctxp, numpmid, pmidlist, result);
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ n = __pmLogFetch(ctxp, numpmid, pmidlist, result);
+ if (n >= 0 && (ctxp->c_mode & __PM_MODE_MASK) != PM_MODE_INTERP) {
+ ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ }
+ }
+
+ /* process derived metrics, if any */
+ if (have_dm) {
+ __pmFinishResult(ctxp, n, result);
+ if (newlist != NULL)
+ free(newlist);
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+done:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FETCH) {
+ fprintf(stderr, "pmFetch returns ...\n");
+ if (n > 0) {
+ fprintf(stderr, "PMCD state changes: agent(s)");
+ if (n & PMCD_ADD_AGENT) fprintf(stderr, " added");
+ if (n & PMCD_RESTART_AGENT) fprintf(stderr, " restarted");
+ if (n & PMCD_DROP_AGENT) fprintf(stderr, " dropped");
+ fputc('\n', stderr);
+ }
+ if (n >= 0)
+ __pmDumpResult(stderr, *result);
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "Error: %s\n", pmErrStr_r(n, errmsg, sizeof(errmsg)));
+ }
+ }
+#endif
+
+ return n;
+}
+
+int
+pmFetchArchive(pmResult **result)
+{
+ int n;
+ __pmContext *ctxp;
+ int ctxp_mode;
+
+ if ((n = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(n);
+ if (ctxp == NULL)
+ n = PM_ERR_NOCONTEXT;
+ else {
+ ctxp_mode = (ctxp->c_mode & __PM_MODE_MASK);
+ if (ctxp->c_type != PM_CONTEXT_ARCHIVE)
+ n = PM_ERR_NOTARCHIVE;
+ else if (ctxp_mode == PM_MODE_INTERP)
+ /* makes no sense! */
+ n = PM_ERR_MODE;
+ else {
+ /* assume PM_CONTEXT_ARCHIVE and BACK or FORW */
+ n = __pmLogFetch(ctxp, 0, NULL, result);
+ if (n >= 0) {
+ ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ }
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+ }
+
+ return n;
+}
+
+int
+pmSetMode(int mode, const struct timeval *when, int delta)
+{
+ int n;
+ __pmContext *ctxp;
+ int l_mode = (mode & __PM_MODE_MASK);
+
+ if ((n = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(n);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ if (l_mode != PM_MODE_LIVE)
+ n = PM_ERR_MODE;
+ else {
+ ctxp->c_origin.tv_sec = ctxp->c_origin.tv_usec = 0;
+ ctxp->c_mode = mode;
+ ctxp->c_delta = delta;
+ n = 0;
+ }
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ n = PM_ERR_MODE;
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ if (l_mode == PM_MODE_INTERP ||
+ l_mode == PM_MODE_FORW || l_mode == PM_MODE_BACK) {
+ if (when != NULL) {
+ /*
+ * special case of NULL for timestamp
+ * => do not update notion of "current" time
+ */
+ ctxp->c_origin.tv_sec = (__int32_t)when->tv_sec;
+ ctxp->c_origin.tv_usec = (__int32_t)when->tv_usec;
+ }
+ ctxp->c_mode = mode;
+ ctxp->c_delta = delta;
+ __pmLogSetTime(ctxp);
+ __pmLogResetInterp(ctxp);
+ n = 0;
+ }
+ else
+ n = PM_ERR_MODE;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+ return n;
+}
diff --git a/src/libpcp/src/fetchlocal.c b/src/libpcp/src/fetchlocal.c
new file mode 100644
index 0000000..d7944a6
--- /dev/null
+++ b/src/libpcp/src/fetchlocal.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <stdio.h>
+#include <sys/time.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+
+/*
+ * Called with valid context locked ...
+ */
+int
+__pmFetchLocal(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result)
+{
+ int sts;
+ int ctx;
+ int j;
+ int k;
+ int n;
+ pmResult *ans;
+ pmResult *tmp_ans;
+ __pmDSO *dp;
+ int need;
+
+ static pmID * splitlist=NULL;
+ static int splitmax=0;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ return PM_ERR_THREAD;
+ if (numpmid < 1)
+ return PM_ERR_TOOSMALL;
+
+ ctx = __pmPtrToHandle(ctxp);
+
+ /*
+ * this is very ugly ... the DSOs have a high-water mark
+ * allocation algorithm for the result skeleton, but the
+ * code that calls us assumes it has freedom to retain
+ * this result structure for as long as it wishes, and
+ * then to call pmFreeResult
+ *
+ * we make another skeleton, selectively copy and return that
+ *
+ * (numpmid - 1) because there's room for one valueSet
+ * in a pmResult
+ */
+ need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *);
+ if ((ans = (pmResult *)malloc(need)) == NULL)
+ return -oserror();
+
+ /*
+ * Check if we have enough space to accomodate "best" case scenario -
+ * all pmids are from the same domain
+ */
+ if (splitmax < numpmid) {
+ splitmax = numpmid;
+ pmID *tmp_list = (pmID *)realloc(splitlist, sizeof(pmID)*splitmax);
+ if (tmp_list == NULL) {
+ free(splitlist);
+ splitmax = 0;
+ free(ans);
+ return -oserror();
+ }
+ splitlist = tmp_list;
+ }
+
+ ans->numpmid = numpmid;
+ __pmtimevalNow(&ans->timestamp);
+ for (j = 0; j < numpmid; j++)
+ ans->vset[j] = NULL;
+
+ for (j = 0; j < numpmid; j++) {
+ int cnt;
+
+ if (ans->vset[j] != NULL)
+ /* picked up in a previous fetch */
+ continue;
+
+ sts = 0;
+ if ((dp = __pmLookupDSO(((__pmID_int *)&pmidlist[j])->domain)) == NULL)
+ /* based on domain, unknown PMDA */
+ sts = PM_ERR_NOAGENT;
+ else {
+ if (ctxp->c_sent != dp->domain) {
+ /*
+ * current profile is _not_ already cached at other end of
+ * IPC, so send get current profile ...
+ * Note: trickier than the non-local case, as no per-PMDA
+ * caching at the PMCD end, so need to remember the
+ * last domain to receive a profile
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FETCH)
+ fprintf(stderr,
+ "__pmFetchLocal: calling ???_profile(domain: %d), "
+ "context: %d\n", dp->domain, ctx);
+#endif
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ sts = dp->dispatch.version.any.profile(ctxp->c_instprof,
+ dp->dispatch.version.any.ext);
+ if (sts >= 0)
+ ctxp->c_sent = dp->domain;
+ }
+ }
+
+ /* Copy all pmID for the current domain into the temp. list */
+ for (cnt=0, k=j; k < numpmid; k++ ) {
+ if (((__pmID_int*)(pmidlist+k))->domain ==
+ ((__pmID_int*)(pmidlist+j))->domain)
+ splitlist[cnt++] = pmidlist[k];
+ }
+
+ if (sts >= 0) {
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ sts = dp->dispatch.version.any.fetch(cnt, splitlist, &tmp_ans,
+ dp->dispatch.version.any.ext);
+ }
+
+ /* Copy results back
+ *
+ * Note: We DO NOT have to free tmp_ans since DSO PMDA would
+ * ALWAYS return a pointer to the static area.
+ */
+ for (n = 0, k = j; k < numpmid && n < cnt; k++) {
+ if (pmidlist[k] == splitlist[n]) {
+ if (sts < 0) {
+ ans->vset[k] = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (ans->vset[k] == NULL) {
+ /* cleanup all partial allocations for ans->vset[] */
+ for (k--; k >=0; k--)
+ free(ans->vset[k]);
+ free(ans);
+ return -oserror();
+ }
+ ans->vset[k]->numval = sts;
+ ans->vset[k]->pmid = pmidlist[k];
+ }
+ else {
+ ans->vset[k] = tmp_ans->vset[n];
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_FETCH) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmFetchLocal: [%d] PMID=%s nval=",
+ k, pmIDStr_r(pmidlist[k], strbuf, sizeof(strbuf)));
+ if (ans->vset[k]->numval < 0)
+ fprintf(stderr, "%s\n",
+ pmErrStr_r(ans->vset[k]->numval, errmsg, sizeof(errmsg)));
+ else
+ fprintf(stderr, "%d\n", ans->vset[k]->numval);
+ }
+#endif
+ n++;
+ }
+ }
+ }
+ *result = ans;
+
+ return 0;
+}
diff --git a/src/libpcp/src/freeresult.c b/src/libpcp/src/freeresult.c
new file mode 100644
index 0000000..b7e3d52
--- /dev/null
+++ b/src/libpcp/src/freeresult.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+/* Free result buffer routines */
+
+static void
+__pmFreeResultValueSets(pmValueSet **ppvstart, pmValueSet **ppvsend)
+{
+ pmValueSet *pvs;
+ pmValueSet **ppvs;
+ char strbuf[20];
+ int j;
+
+ /* if _any_ vset[] -> an address within a pdubuf, we are done */
+ for (ppvs = ppvstart; ppvs < ppvsend; ppvs++) {
+ if (__pmUnpinPDUBuf((void *)*ppvs))
+ return;
+ }
+
+ /* not created from a pdubuf, really free the memory */
+ for (ppvs = ppvstart; ppvs < ppvsend; ppvs++) {
+ pvs = *ppvs;
+ if (pvs->numval > 0 && pvs->valfmt == PM_VAL_DPTR) {
+ /* pmValueBlocks may be malloc'd as well */
+ for (j = 0; j < pvs->numval; j++) {
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "free"
+ "(" PRINTF_P_PFX "%p) pmValueBlock pmid=%s inst=%d\n",
+ pvs->vlist[j].value.pval,
+ pmIDStr_r(pvs->pmid, strbuf, sizeof(strbuf)),
+ pvs->vlist[j].inst);
+ free(pvs->vlist[j].value.pval);
+ }
+ }
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "free(" PRINTF_P_PFX "%p) vset pmid=%s\n",
+ pvs, pmIDStr_r(pvs->pmid, strbuf, sizeof(strbuf)));
+ free(pvs);
+ }
+}
+
+void
+__pmFreeResultValues(pmResult *result)
+{
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "__pmFreeResultValues(" PRINTF_P_PFX "%p) numpmid=%d\n",
+ result, result->numpmid);
+ if (result->numpmid)
+ __pmFreeResultValueSets(result->vset, &result->vset[result->numpmid]);
+}
+
+void
+pmFreeResult(pmResult *result)
+{
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "pmFreeResult(" PRINTF_P_PFX "%p)\n", result);
+ __pmFreeResultValues(result);
+ free(result);
+}
+
+void
+pmFreeHighResResult(pmHighResResult *result)
+{
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "pmFreeHighResResult(" PRINTF_P_PFX "%p)\n", result);
+ if (result->numpmid)
+ __pmFreeResultValueSets(result->vset, &result->vset[result->numpmid]);
+ free(result);
+}
diff --git a/src/libpcp/src/getdate.y b/src/libpcp/src/getdate.y
new file mode 100644
index 0000000..b01d015
--- /dev/null
+++ b/src/libpcp/src/getdate.y
@@ -0,0 +1,1274 @@
+ /* *INDENT-OFF* *//* indent -linux -nce -i4 */
+%{
+/* Parse a string into an internal time stamp.
+ *
+ * Copyright (C) 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007 Free Software
+ * Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* There's no need to extend the stack, so there's no need to involve
+ alloca. */
+#define YYSTACK_USE_ALLOCA 0
+
+/* Tell Bison how much stack space is needed. 20 should be plenty for
+ this grammar, which is not right recursive. Beware setting it too
+ high, since that might cause problems on machines whose
+ implementations have lame stack-overflow checking. */
+#define YYMAXDEPTH 20
+#define YYINITDEPTH YYMAXDEPTH
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdbool.h>
+#include "pmapi.h"
+#include "impl.h"
+
+
+/* ISDIGIT differs from isdigit, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char
+ or EOF.
+ - It's typically faster.
+ POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
+ isdigit unless it's important to use the locale's definition
+ of `digit' even when the host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
+
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
+# define __attribute__(x)
+# endif
+#endif
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+/* Shift A right by B bits portably, by dividing A by 2**B and
+ truncating towards minus infinity. A and B should be free of side
+ effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
+ INT_BITS is the number of useful bits in an int. GNU code can
+ assume that INT_BITS is at least 32.
+
+ ISO C99 says that A >> B is implementation-defined if A < 0. Some
+ implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
+ right in the usual way when A < 0, so SHR falls back on division if
+ ordinary A >> B doesn't seem to be the usual signed shift. */
+#define SHR(a, b) \
+ (-1 >> 1 == -1 \
+ ? (a) >> (b) \
+ : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
+
+#define EPOCH_YEAR 1970
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+/* An integer value, and the number of digits in its textual
+ representation. */
+typedef struct
+{
+ bool negative;
+ long int value;
+ size_t digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct
+{
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+enum { BILLION = 1000000000, LOG10_BILLION = 9 };
+
+/* Relative times. */
+typedef struct
+{
+ /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+ long int year;
+ long int month;
+ long int day;
+ long int hour;
+ long int minutes;
+ long int seconds;
+ long int ns;
+} relative_time;
+
+# define RELATIVE_TIME_0 ((relative_time) { 0, 0, 0, 0, 0, 0, 0 })
+
+/* Information passed to and from the parser. */
+typedef struct
+{
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ long int day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ long int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
+ textint year;
+ long int month;
+ long int day;
+ long int hour;
+ long int minutes;
+ struct timespec seconds; /* includes nanoseconds */
+
+ /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
+ relative_time rel;
+
+ /* Presence or counts of nonterminals of various flavors parsed so far. */
+ bool timespec_seen;
+ bool rels_seen;
+ size_t dates_seen;
+ size_t days_seen;
+ size_t local_zones_seen;
+ size_t dsts_seen;
+ size_t times_seen;
+ size_t zones_seen;
+
+ /* Table of local time zone abbrevations, terminated by a null entry. */
+ table local_time_zone_table[3];
+} parser_control;
+
+union YYSTYPE;
+static int yylex (union YYSTYPE *, parser_control *);
+static int yyerror (parser_control const *, char const *);
+static long int time_zone_hhmm (textint, long int);
+
+%}
+
+/* We want a reentrant parser, even if the TZ manipulation and the calls to
+ localtime and gmtime are not reentrant. */
+%pure-parser
+%parse-param { parser_control *pc }
+%lex-param { parser_control *pc }
+
+/* This grammar has 20 shift/reduce conflicts. */
+%expect 20
+
+%union
+{
+ long int intval;
+ textint textintval;
+ struct timespec timespec;
+ relative_time rel;
+}
+
+%token tAGO tDST
+
+%token tYEAR_UNIT tMONTH_UNIT tHOUR_UNIT tMINUTE_UNIT tSEC_UNIT
+%token <intval> tDAY_UNIT
+
+%token <intval> tDAY tDAYZONE tLOCAL_ZONE tMERIDIAN
+%token <intval> tMONTH tORDINAL tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+%token <timespec> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
+
+%type <intval> o_colon_minutes o_merid
+%type <timespec> seconds signed_seconds unsigned_seconds
+
+%type <rel> relunit relunit_snumber
+
+%%
+
+spec:
+ timespec
+ | items
+ ;
+
+timespec:
+ '@' seconds
+ {
+ pc->seconds = $2;
+ pc->timespec_seen = true;
+ }
+ ;
+
+items:
+ /* empty */
+ | items item
+ ;
+
+item:
+ time
+ { pc->times_seen++; }
+ | local_zone
+ { pc->local_zones_seen++; }
+ | zone
+ { pc->zones_seen++; }
+ | date
+ { pc->dates_seen++; }
+ | day
+ { pc->days_seen++; }
+ | rel
+ { pc->rels_seen = true; }
+ | number
+ ;
+
+time:
+ tUNUMBER tMERIDIAN
+ {
+ pc->hour = $1.value;
+ pc->minutes = 0;
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER o_colon_minutes
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = time_zone_hhmm ($4, $5);
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds o_merid
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5;
+ pc->meridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' unsigned_seconds tSNUMBER o_colon_minutes
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = time_zone_hhmm ($6, $7);
+ }
+ ;
+
+local_zone:
+ tLOCAL_ZONE
+ {
+ pc->local_isdst = $1;
+ pc->dsts_seen += (0 < $1);
+ }
+ | tLOCAL_ZONE tDST
+ {
+ pc->local_isdst = 1;
+ pc->dsts_seen += (0 < $1) + 1;
+ }
+ ;
+
+zone:
+ tZONE
+ { pc->time_zone = $1; }
+ | tZONE relunit_snumber
+ { pc->time_zone = $1;
+ pc->rel.ns += $2.ns;
+ pc->rel.seconds += $2.seconds;
+ pc->rel.minutes += $2.minutes;
+ pc->rel.hour += $2.hour;
+ pc->rel.day += $2.day;
+ pc->rel.month += $2.month;
+ pc->rel.year += $2.year;
+ pc->rels_seen = true; }
+ | tZONE tSNUMBER o_colon_minutes
+ { pc->time_zone = $1 + time_zone_hhmm ($2, $3); }
+ | tDAYZONE
+ { pc->time_zone = $1 + 60; }
+ | tZONE tDST
+ { pc->time_zone = $1 + 60; }
+ ;
+
+day:
+ tDAY
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = $1;
+ }
+ | tDAY ','
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = $1;
+ }
+ | tORDINAL tDAY
+ {
+ pc->day_ordinal = $1;
+ pc->day_number = $2;
+ }
+ | tUNUMBER tDAY
+ {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+ ;
+
+date:
+ tUNUMBER '/' tUNUMBER
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER
+/* *INDENT-ON* */
+{
+ /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if (4 <= $1.digits) {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ }
+ else {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ pc->year = $5;
+ }
+}
+/* *INDENT-OFF* */
+ | tUNUMBER tSNUMBER tSNUMBER
+ {
+ /* ISO 8601 format. YYYY-MM-DD. */
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
+ }
+ | tUNUMBER tMONTH tSNUMBER
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tSNUMBER tSNUMBER
+ {
+ /* e.g. JUN-17-1992. */
+ pc->month = $1;
+ pc->day = -$2.value;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ ;
+
+rel:
+ relunit tAGO
+ {
+ pc->rel.ns -= $1.ns;
+ pc->rel.seconds -= $1.seconds;
+ pc->rel.minutes -= $1.minutes;
+ pc->rel.hour -= $1.hour;
+ pc->rel.day -= $1.day;
+ pc->rel.month -= $1.month;
+ pc->rel.year -= $1.year;
+ }
+ | relunit
+ {
+ pc->rel.ns += $1.ns;
+ pc->rel.seconds += $1.seconds;
+ pc->rel.minutes += $1.minutes;
+ pc->rel.hour += $1.hour;
+ pc->rel.day += $1.day;
+ pc->rel.month += $1.month;
+ pc->rel.year += $1.year;
+ }
+ ;
+
+relunit:
+ tORDINAL tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1; }
+ | tUNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = 1; }
+ | tORDINAL tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1; }
+ | tUNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = 1; }
+ | tORDINAL tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1 * $2; }
+ | tUNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1; }
+ | tORDINAL tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1; }
+ | tUNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = 1; }
+ | tORDINAL tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1; }
+ | tUNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = 1; }
+ | tORDINAL tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1; }
+ | tUNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+ | tSDECIMAL_NUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
+ | tUDECIMAL_NUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.tv_sec; $$.ns = $1.tv_nsec; }
+ | tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = 1; }
+ | relunit_snumber
+ ;
+
+relunit_snumber:
+ tSNUMBER tYEAR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.year = $1.value; }
+ | tSNUMBER tMONTH_UNIT
+ { $$ = RELATIVE_TIME_0; $$.month = $1.value; }
+ | tSNUMBER tDAY_UNIT
+ { $$ = RELATIVE_TIME_0; $$.day = $1.value * $2; }
+ | tSNUMBER tHOUR_UNIT
+ { $$ = RELATIVE_TIME_0; $$.hour = $1.value; }
+ | tSNUMBER tMINUTE_UNIT
+ { $$ = RELATIVE_TIME_0; $$.minutes = $1.value; }
+ | tSNUMBER tSEC_UNIT
+ { $$ = RELATIVE_TIME_0; $$.seconds = $1.value; }
+ ;
+
+seconds: signed_seconds | unsigned_seconds;
+
+signed_seconds:
+ tSDECIMAL_NUMBER
+ | tSNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+ ;
+
+unsigned_seconds:
+ tUDECIMAL_NUMBER
+ | tUNUMBER
+ { $$.tv_sec = $1.value; $$.tv_nsec = 0; }
+ ;
+
+number:
+ tUNUMBER
+/* *INDENT-ON* */
+
+{
+ if (pc->dates_seen && !pc->year.digits
+ && !pc->rels_seen && (pc->times_seen || 2 < $1.digits))
+ pc->year = $1;
+ else {
+ if (4 < $1.digits) {
+ pc->dates_seen++;
+ pc->day = $1.value % 100;
+ pc->month = ($1.value / 100) % 100;
+ pc->year.value = $1.value / 10000;
+ pc->year.digits = $1.digits - 4;
+ }
+ else {
+ pc->times_seen++;
+ if ($1.digits <= 2) {
+ pc->hour = $1.value;
+ pc->minutes = 0;
+ }
+ else {
+ pc->hour = $1.value / 100;
+ pc->minutes = $1.value % 100;
+ }
+ pc->seconds.tv_sec = 0;
+ pc->seconds.tv_nsec = 0;
+ pc->meridian = MER24;
+ }
+ }
+}
+
+;
+/* *INDENT-OFF* */
+
+o_colon_minutes:
+ /* empty */
+ { $$ = -1; }
+ | ':' tUNUMBER
+ { $$ = $2.value; }
+ ;
+
+o_merid:
+ /* empty */
+ { $$ = MER24; }
+ | tMERIDIAN
+ { $$ = $1; }
+ ;
+
+%%
+
+static table const meridian_table[] =
+{
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { NULL, 0, 0 }
+};
+
+static table const dst_table[] =
+{
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] =
+{
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { NULL, 0, 0 }
+};
+
+static table const time_units_table[] =
+{
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { NULL, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] =
+{
+ { "TOMORROW", tDAY_UNIT, 1 },
+ { "YESTERDAY",tDAY_UNIT, -1 },
+ { "TODAY", tDAY_UNIT, 0 },
+ { "NOW", tDAY_UNIT, 0 },
+ { "LAST", tORDINAL, -1 },
+ { "THIS", tORDINAL, 0 },
+ { "NEXT", tORDINAL, 1 },
+ { "FIRST", tORDINAL, 1 },
+/*{ "SECOND", tORDINAL, 2 }, */
+ { "THIRD", tORDINAL, 3 },
+ { "FOURTH", tORDINAL, 4 },
+ { "FIFTH", tORDINAL, 5 },
+ { "SIXTH", tORDINAL, 6 },
+ { "SEVENTH", tORDINAL, 7 },
+ { "EIGHTH", tORDINAL, 8 },
+ { "NINTH", tORDINAL, 9 },
+ { "TENTH", tORDINAL, 10 },
+ { "ELEVENTH", tORDINAL, 11 },
+ { "TWELFTH", tORDINAL, 12 },
+ { "AGO", tAGO, 1 },
+ { NULL, 0, 0 }
+};
+
+/* The universal time zone table. These labels can be used even for
+ time stamps that would not otherwise be valid, e.g., GMT time
+ stamps in London during summer. */
+static table const universal_time_zone_table[] =
+{
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/* The time zone table. This table is necessarily incomplete, as time
+ zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ as Eastern time in Australia, not as US Eastern Standard Time.
+ You cannot rely on getdate to handle arbitrary time zone
+ abbreviations; use numeric abbreviations like `-0500' instead. */
+static table const time_zone_table[] =
+{
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { NULL, 0, 0 }
+};
+
+/* Military time zone table. */
+static table const military_table[] =
+{
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", tZONE, HOUR ( 7) },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { NULL, 0, 0 }
+};
+
+/* *INDENT-ON* */
+
+/* Convert a time zone expressed as HH:MM into an integer count of
+ minutes. If MM is negative, then S is of the form HHMM and needs
+ to be picked apart; otherwise, S is of the form HH. */
+
+static long int time_zone_hhmm(textint s, long int mm)
+{
+ if (mm < 0)
+ return (s.value / 100) * 60 + s.value % 100;
+ else
+ return s.value * 60 + (s.negative ? -mm : mm);
+}
+
+static int to_hour(long int hours, int meridian)
+{
+ switch (meridian) {
+ default: /* Pacify GCC. */
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ }
+}
+
+static long int to_year(textint textyear)
+{
+ long int year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /* XPG4 suggests that years 00-68 map to 2000-2068, and
+ years 69-99 map to 1969-1999. */
+ else if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const *lookup_zone(parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ for (tp = universal_time_zone_table; tp->name; tp++)
+ if (strcmp(name, tp->name) == 0)
+ return tp;
+
+ /* Try local zone abbreviations before those in time_zone_table, as
+ the local ones are more likely to be right. */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp(name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp(name, tp->name) == 0)
+ return tp;
+
+ return NULL;
+}
+
+/* Yield the difference between *A and *B,
+ measured in seconds, ignoring leap seconds.
+ The body of this function is taken directly from the GNU C Library;
+ see src/strftime.c. */
+static long int tm_diff(struct tm const *a, struct tm const *b)
+{
+ /* Compute intervening leap days correctly even if year is negative.
+ Take care to avoid int overflow in leap day calculations. */
+ int a4 = SHR(a->tm_year, 2) + SHR(TM_YEAR_BASE, 2) - !(a->tm_year & 3);
+ int b4 = SHR(b->tm_year, 2) + SHR(TM_YEAR_BASE, 2) - !(b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = SHR(a100, 2);
+ int b400 = SHR(b100, 2);
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ long int ayear = a->tm_year;
+ long int years = ayear - b->tm_year;
+ long int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+
+static table const *lookup_word(parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ bool period_found;
+ bool abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++) {
+ unsigned char ch = *p;
+ *p = toupper(ch);
+ }
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp(word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen(word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp(word, tp->name, 3) : strcmp(word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone(pc, word)))
+ return tp;
+
+ if (strcmp(word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp(word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S') {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp(word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp(word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (period_found = false, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ period_found = true;
+ else
+ p++;
+ if (period_found && (tp = lookup_zone(pc, word)))
+ return tp;
+
+ return NULL;
+}
+
+static int yylex(union YYSTYPE * lvalp, parser_control * pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;) {
+ while (c = *pc->input, isspace(c))
+ pc->input++;
+
+ if (ISDIGIT(c) || c == '-' || c == '+') {
+ char const *p;
+ int sign;
+ unsigned long int value;
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ while (c = *++pc->input, isspace(c))
+ continue;
+ if (!ISDIGIT(c))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ p = pc->input;
+ for (value = 0;; value *= 10) {
+ unsigned long int value1 = value + (c - '0');
+ if (value1 < value)
+ return '?';
+ value = value1;
+ c = *++p;
+ if (!ISDIGIT(c))
+ break;
+ if (ULONG_MAX / 10 < value)
+ return '?';
+ }
+ if ((c == '.' || c == ',') && ISDIGIT(p[1])) {
+ time_t s;
+ int ns;
+ int digits;
+ unsigned long int value1;
+
+ /* Check for overflow when converting value to time_t. */
+ if (sign < 0) {
+ s = -value;
+ if (0 < s)
+ return '?';
+ value1 = -s;
+ }
+ else {
+ s = value;
+ if (s < 0)
+ return '?';
+ value1 = s;
+ }
+ if (value != value1)
+ return '?';
+
+ /* Accumulate fraction, to ns precision. */
+ p++;
+ ns = *p++ - '0';
+ for (digits = 2; digits <= LOG10_BILLION; digits++) {
+ ns *= 10;
+ if (ISDIGIT(*p))
+ ns += *p++ - '0';
+ }
+
+ /* Skip excess digits, truncating toward -Infinity. */
+ if (sign < 0)
+ for (; ISDIGIT(*p); p++)
+ if (*p != '0') {
+ ns++;
+ break;
+ }
+ while (ISDIGIT(*p))
+ p++;
+
+ /* Adjust to the timespec convention, which is that
+ tv_nsec is always a positive offset even if tv_sec is
+ negative. */
+ if (sign < 0 && ns) {
+ s--;
+ if (!(s < 0))
+ return '?';
+ ns = BILLION - ns;
+ }
+
+ lvalp->timespec.tv_sec = s;
+ lvalp->timespec.tv_nsec = ns;
+ pc->input = p;
+ return sign ? tSDECIMAL_NUMBER : tUDECIMAL_NUMBER;
+ }
+ else {
+ lvalp->textintval.negative = sign < 0;
+ if (sign < 0) {
+ lvalp->textintval.value = -value;
+ if (0 < lvalp->textintval.value)
+ return '?';
+ }
+ else {
+ lvalp->textintval.value = value;
+ if (lvalp->textintval.value < 0)
+ return '?';
+ }
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+ }
+
+ if (isalpha(c)) {
+ char buff[20];
+ char *p = buff;
+ table const *tp;
+
+ do {
+ if (p < buff + sizeof buff - 1)
+ *p++ = c;
+ c = *++pc->input;
+ }
+ while (isalpha(c) || c == '.');
+
+ *p = '\0';
+ tp = lookup_word(pc, buff);
+ if (!tp)
+ return '?';
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return *pc->input++;
+ count = 0;
+ do {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count != 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int
+yyerror(parser_control const *pc ATTRIBUTE_UNUSED,
+ char const *s ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
+/* If *TM0 is the old and *TM1 is the new value of a struct tm after
+ passing it to mktime, return true if it's OK that mktime returned T.
+ It's not OK if *TM0 has out-of-range members. */
+
+static bool mktime_ok(struct tm const *tm0, struct tm const *tm1, time_t t)
+{
+ if (t == (time_t) - 1) {
+ /* Guard against falsely reporting an error when parsing a time
+ stamp that happens to equal (time_t) -1, on a host that
+ supports such a time stamp. */
+ tm1 = pmLocaltime(&t, (struct tm *)&tm1);
+ if (!tm1)
+ return false;
+ }
+
+ return !((tm0->tm_sec ^ tm1->tm_sec)
+ | (tm0->tm_min ^ tm1->tm_min)
+ | (tm0->tm_hour ^ tm1->tm_hour)
+ | (tm0->tm_mday ^ tm1->tm_mday)
+ | (tm0->tm_mon ^ tm1->tm_mon)
+ | (tm0->tm_year ^ tm1->tm_year));
+}
+
+/* A reasonable upper bound for the size of ordinary TZ strings.
+ Use heap allocation if TZ's length exceeds this. */
+enum { TZBUFSIZE = 100 };
+
+/* Parse a date/time string, storing the resulting time value into *RESULT.
+ The string itself is pointed to by P. Return true if successful.
+ P can be an incomplete or relative time specification; if so, use
+ *NOW as the basis for the returned time. */
+int
+__pmGlibGetDate(struct timespec *result, char const *p,
+ struct timespec const *now)
+{
+ time_t Start;
+ long int Start_ns;
+ struct tm tmpbuf;
+ struct tm const *tmp = &tmpbuf;
+ struct tm tm;
+ struct tm tm0;
+ parser_control pc;
+ struct timespec gettime_buffer;
+ unsigned char c;
+ int ok = 0;
+
+ if (!now) {
+ __pmGetTimespec(&gettime_buffer);
+ now = &gettime_buffer;
+ }
+
+ Start = now->tv_sec;
+ Start_ns = now->tv_nsec;
+
+ tmp = pmLocaltime(&now->tv_sec, (struct tm *)tmp);
+ if (!tmp)
+ return -1;
+
+ while (c = *p, isspace(c))
+ p++;
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year;
+ pc.year.value += TM_YEAR_BASE;
+ pc.year.digits = 0;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds.tv_sec = tmp->tm_sec;
+ pc.seconds.tv_nsec = Start_ns;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel = RELATIVE_TIME_0;
+ pc.timespec_seen = false;
+ pc.rels_seen = false;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.dsts_seen = 0;
+ pc.zones_seen = 0;
+ pc.local_time_zone_table[0].name = NULL;
+
+ pc.local_time_zone_table[0].name = NULL;
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && !strcmp(pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name)) {
+ /* This locale uses the same abbrevation for standard and
+ daylight times. So if we see that abbreviation, we don't
+ know whether it's daylight time. */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = NULL;
+ }
+
+ if (yyparse(&pc) != 0)
+ goto fail;
+
+ if (pc.timespec_seen)
+ *result = pc.seconds;
+ else {
+ if (1 < (pc.times_seen | pc.dates_seen | pc.days_seen | pc.dsts_seen
+ | (pc.local_zones_seen + pc.zones_seen)))
+ goto fail;
+
+ tm.tm_year = to_year(pc.year) - TM_YEAR_BASE;
+ tm.tm_mon = pc.month - 1;
+ tm.tm_mday = pc.day;
+ if (pc.times_seen || (pc.rels_seen && !pc.dates_seen && !pc.days_seen)) {
+ tm.tm_hour = to_hour(pc.hour, pc.meridian);
+ if (tm.tm_hour < 0)
+ goto fail;
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds.tv_sec;
+ }
+ else {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ pc.seconds.tv_nsec = 0;
+ }
+
+ /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen)
+ tm.tm_isdst = -1;
+
+ /* But if the input explicitly specifies local time with or without
+ DST, give mktime that information. */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = __pmMktime(&tm);
+
+ if (!mktime_ok(&tm0, &tm, Start))
+ goto fail;
+
+ if (pc.days_seen && !pc.dates_seen) {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
+ + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+ tm.tm_isdst = -1;
+ Start = __pmMktime(&tm);
+ if (Start == (time_t) - 1)
+ goto fail;
+ }
+
+ if (pc.zones_seen) {
+ long int delta = pc.time_zone * 60;
+ time_t t1;
+ time_t t = Start;
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ struct tm *gmt = NULL;
+ gmt = gmtime(&t);
+ PM_UNLOCK(__pmLock_libpcp);
+ if (!gmt)
+ goto fail;
+ delta -= tm_diff(&tm, gmt);
+ t1 = Start - delta;
+ if ((Start < t1) != (delta < 0))
+ goto fail; /* time_t overflow */
+ Start = t1;
+ }
+
+ /* Add relative date. */
+ if (pc.rel.year | pc.rel.month | pc.rel.day) {
+ int year = tm.tm_year + pc.rel.year;
+ int month = tm.tm_mon + pc.rel.month;
+ int day = tm.tm_mday + pc.rel.day;
+ if (((year < tm.tm_year) ^ (pc.rel.year < 0))
+ | ((month < tm.tm_mon) ^ (pc.rel.month < 0))
+ | ((day < tm.tm_mday) ^ (pc.rel.day < 0)))
+ goto fail;
+ tm.tm_year = year;
+ tm.tm_mon = month;
+ tm.tm_mday = day;
+ tm.tm_hour = tm0.tm_hour;
+ tm.tm_min = tm0.tm_min;
+ tm.tm_sec = tm0.tm_sec;
+ tm.tm_isdst = tm0.tm_isdst;
+ Start = __pmMktime(&tm);
+ if (Start == (time_t) - 1)
+ goto fail;
+ }
+
+ /* Add relative hours, minutes, and seconds. On hosts that support
+ leap seconds, ignore the possibility of leap seconds; e.g.,
+ "+ 10 minutes" adds 600 seconds, even if one of them is a
+ leap second. Typically this is not what the user wants, but it's
+ too hard to do it the other way, because the time zone indicator
+ must be applied before relative times, and if mktime is applied
+ again the time zone will be lost. */
+ {
+ long int sum_ns = pc.seconds.tv_nsec + pc.rel.ns;
+ long int normalized_ns = (sum_ns % BILLION + BILLION) % BILLION;
+ time_t t0 = Start;
+ long int d1 = 60 * 60 * pc.rel.hour;
+ time_t t1 = t0 + d1;
+ long int d2 = 60 * pc.rel.minutes;
+ time_t t2 = t1 + d2;
+ long int d3 = pc.rel.seconds;
+ time_t t3 = t2 + d3;
+ long int d4 = (sum_ns - normalized_ns) / BILLION;
+ time_t t4 = t3 + d4;
+
+ if ((d1 / (60 * 60) ^ pc.rel.hour)
+ | (d2 / 60 ^ pc.rel.minutes)
+ | ((t1 < t0) ^ (d1 < 0))
+ | ((t2 < t1) ^ (d2 < 0))
+ | ((t3 < t2) ^ (d3 < 0))
+ | ((t4 < t3) ^ (d4 < 0)))
+ goto fail;
+
+ result->tv_sec = t4;
+ result->tv_nsec = normalized_ns;
+ }
+ }
+
+ goto done;
+
+ fail:
+ ok = -1;
+ done:
+ return ok;
+}
diff --git a/src/libpcp/src/getopt.c b/src/libpcp/src/getopt.c
new file mode 100644
index 0000000..a7e9558
--- /dev/null
+++ b/src/libpcp/src/getopt.c
@@ -0,0 +1,1518 @@
+/*
+ * Common argument parsing for all PMAPI client tools.
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (C) 1987-2014 Free Software Foundation, Inc.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include <ctype.h>
+
+#if !defined(HAVE_UNDERBAR_ENVIRON)
+#define _environ environ
+#endif
+
+enum {
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+};
+
+/*
+ * Using the current archive context, extract start and end
+ * times and adjust the time window boundaries accordingly.
+ */
+static int
+__pmUpdateBounds(pmOptions *opts, int index, struct timeval *begin, struct timeval *end)
+{
+ struct timeval logend;
+ pmLogLabel label;
+ int sts;
+
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ pmprintf("%s: Cannot get archive %s label record: %s\n",
+ pmProgname, opts->archives[index], pmErrStr(sts));
+ return sts;
+ }
+ if ((sts = pmGetArchiveEnd(&logend)) < 0) {
+ logend.tv_sec = INT_MAX;
+ logend.tv_usec = 0;
+ fflush(stdout);
+ fprintf(stderr, "%s: Cannot locate end of archive %s: %s\n",
+ pmProgname, opts->archives[index], pmErrStr(sts));
+ fprintf(stderr, "\nWARNING: "
+ "This archive is sufficiently damaged that it may not be possible to\n");
+ fprintf(stderr, " "
+ "produce complete information. Continuing and hoping for the best.\n\n");
+ fflush(stderr);
+ }
+
+ if (index == 0) {
+ /* the first archive in the set forms the initial boundaries */
+ *begin = label.ll_start;
+ *end = logend;
+ } else {
+ /* must now check if this archive pre- or post- dates others */
+ if (__pmtimevalSub(begin, &label.ll_start) > 0.0)
+ *begin = label.ll_start;
+ if (__pmtimevalSub(end, &logend) < 0.0)
+ *end = logend;
+ }
+ return 0;
+}
+
+/*
+ * Calculate time window boundaries depending on context type.
+ * In multi-archive context, this means opening all of them and
+ * defining the boundary as being from the start of the earliest
+ * through to the end of the last-written archive.
+ *
+ * Note - called with an active context via pmGetContextOptions.
+ */
+static int
+__pmBoundaryOptions(pmOptions *opts, struct timeval *begin, struct timeval *end)
+{
+ int i, ctx, sts = 0;
+
+ if (opts->context != PM_CONTEXT_ARCHIVE) {
+ /* live/local context, open ended - start now, never end */
+ __pmtimevalNow(begin);
+ end->tv_sec = INT_MAX;
+ end->tv_usec = 0;
+ } else if (opts->narchives == 1) {
+ /* singular archive context, make use of current context */
+ sts = __pmUpdateBounds(opts, 0, begin, end);
+ } else {
+ /* multiple archives - figure out combined start and end */
+ for (i = 0; i < opts->narchives; i++) {
+ sts = pmNewContext(PM_CONTEXT_ARCHIVE, opts->archives[i]);
+ if (sts < 0) {
+ pmprintf("%s: Cannot open archive %s: %s\n",
+ pmProgname, opts->archives[i], pmErrStr(sts));
+ break;
+ }
+ ctx = sts;
+ sts = __pmUpdateBounds(opts, i, begin, end);
+ pmDestroyContext(ctx);
+ if (sts < 0)
+ break;
+ }
+ }
+ return sts;
+}
+
+/*
+ * Final stages of argument parsing, anything that needs to wait
+ * until after we have a context - e.g. timezones, time windows.
+ */
+int
+pmGetContextOptions(int ctxid, pmOptions *opts)
+{
+ int window = (opts->start_optarg || opts->finish_optarg ||
+ opts->align_optarg || opts->origin_optarg) ||
+ (opts->flags & PM_OPTFLAG_BOUNDARIES);
+ int tzh;
+
+ /* timezone setup */
+ if (opts->tzflag) {
+ char hostname[MAXHOSTNAMELEN];
+
+ pmGetContextHostName_r(ctxid, hostname, MAXHOSTNAMELEN);
+ if ((tzh = pmNewContextZone()) < 0) {
+ pmprintf("%s: Cannot set context timezone: %s\n",
+ pmProgname, pmErrStr(tzh));
+ opts->errors++;
+ }
+ else if (opts->flags & PM_OPTFLAG_STDOUT_TZ) {
+ printf("Note: timezone set to local timezone of host \"%s\"%s\n\n",
+ hostname,
+ opts->context != PM_CONTEXT_ARCHIVE ? "" : " from archive");
+ }
+ }
+ else if (opts->timezone) {
+ if ((tzh = pmNewZone(opts->timezone)) < 0) {
+ pmprintf("%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, opts->timezone, pmErrStr(tzh));
+ opts->errors++;
+ }
+ else if (opts->flags & PM_OPTFLAG_STDOUT_TZ) {
+ printf("Note: timezone set to \"TZ=%s\"\n\n", opts->timezone);
+ }
+ }
+
+ /* time window setup */
+ if (!opts->errors && window) {
+ struct timeval first_boundary, last_boundary;
+ char *msg;
+
+ if (__pmBoundaryOptions(opts, &first_boundary, &last_boundary) < 0)
+ opts->errors++;
+ else if (pmParseTimeWindow(
+ opts->start_optarg, opts->finish_optarg,
+ opts->align_optarg, opts->origin_optarg,
+ &first_boundary, &last_boundary,
+ &opts->start, &opts->finish, &opts->origin,
+ &msg) < 0) {
+ pmprintf("%s: invalid time window: %s\n", pmProgname, msg);
+ opts->errors++;
+ free(msg);
+ }
+ }
+
+ if (opts->errors) {
+ if (!(opts->flags & PM_OPTFLAG_USAGE_ERR))
+ opts->flags |= PM_OPTFLAG_RUNTIME_ERR;
+ return PM_ERR_GENERIC;
+ }
+
+ return 0;
+}
+
+/*
+ * All arguments have been parsed at this point (both internal and external).
+ * We can now perform any final processing that could not be done earlier.
+ *
+ * Note that some end processing requires a context (in particular, the
+ * "time window" processing, which may require timezone setup, and so on).
+ * Such processing is deferred to pmGetContextOptions().
+ */
+void
+__pmEndOptions(pmOptions *opts)
+{
+ if (opts->flags & PM_OPTFLAG_DONE)
+ return;
+
+ /* inform caller of the struct version used */
+ if (opts->version != PMAPI_VERSION_2)
+ opts->version = PMAPI_VERSION_2;
+
+ if (!opts->context) {
+ if (opts->Lflag)
+ opts->context = PM_CONTEXT_LOCAL;
+ else if (opts->nhosts && !opts->narchives)
+ opts->context = PM_CONTEXT_HOST;
+ else if (opts->narchives && !opts->nhosts)
+ opts->context = PM_CONTEXT_ARCHIVE;
+ }
+
+ if ((opts->start_optarg || opts->align_optarg || opts->origin_optarg) &&
+ opts->context != PM_CONTEXT_ARCHIVE) {
+ pmprintf("%s: time window options are supported for archives only\n",
+ pmProgname);
+ opts->errors++;
+ }
+
+ if (opts->tzflag && opts->context != PM_CONTEXT_ARCHIVE &&
+ opts->context != PM_CONTEXT_HOST) {
+ pmprintf("%s: use of timezone from metric source requires a source\n",
+ pmProgname);
+ opts->errors++;
+ }
+
+ if (opts->errors && !(opts->flags & PM_OPTFLAG_RUNTIME_ERR))
+ opts->flags |= PM_OPTFLAG_USAGE_ERR;
+ opts->flags |= PM_OPTFLAG_DONE;
+}
+
+static void
+__pmSetAlignment(pmOptions *opts, char *arg)
+{
+ opts->align_optarg = arg;
+}
+
+static void
+__pmSetOrigin(pmOptions *opts, char *arg)
+{
+ opts->origin_optarg = arg;
+}
+
+static void
+__pmSetStartTime(pmOptions *opts, char *arg)
+{
+ opts->start_optarg = arg;
+}
+
+static void
+__pmSetDebugFlag(pmOptions *opts, char *arg)
+{
+ int sts;
+
+ if ((sts = __pmParseDebug(arg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, arg);
+ opts->errors++;
+ }
+ else {
+ pmDebug |= sts;
+ }
+}
+
+static void
+__pmSetGuiModeFlag(pmOptions *opts)
+{
+ if (opts->guiport_optarg) {
+ pmprintf("%s: at most one of -g and -p allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->guiflag = 1;
+ }
+}
+
+static void
+__pmSetGuiPort(pmOptions *opts, char *arg)
+{
+ char *endnum;
+
+ if (opts->guiflag) {
+ pmprintf("%s: at most one of -g and -p allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->guiport_optarg = arg;
+ opts->guiport = (int)strtol(arg, &endnum, 10);
+ if (*endnum != '\0' || opts->guiport < 0)
+ opts->guiport = 0;
+ }
+}
+
+void
+__pmAddOptArchive(pmOptions *opts, char *arg)
+{
+ char **archives = opts->archives;
+ size_t size = sizeof(char *) * (opts->narchives + 1);
+
+ if (opts->narchives && !(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: too many archives requested: %s\n", pmProgname, arg);
+ opts->errors++;
+ } else if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one host or archive allowed\n", pmProgname);
+ opts->errors++;
+ } else if ((archives = realloc(archives, size)) != NULL) {
+ archives[opts->narchives] = arg;
+ opts->archives = archives;
+ opts->narchives++;
+ } else {
+ __pmNoMem("pmGetOptions(archive)", size, PM_FATAL_ERR);
+ }
+}
+
+static char *
+comma_or_end(const char *start)
+{
+ char *end;
+
+ if ((end = strchr(start, ',')) != NULL)
+ return end;
+ if (*start == '\0')
+ return NULL;
+ end = (char *)start + strlen(start);
+ return end;
+}
+
+void
+__pmAddOptArchiveList(pmOptions *opts, char *arg)
+{
+ char *start = arg, *end;
+
+ if (!(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: too many archives requested: %s\n", pmProgname, arg);
+ opts->errors++;
+ } else if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one of hosts or archives allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ while ((end = comma_or_end(start)) != NULL) {
+ size_t size = sizeof(char *) * (opts->narchives + 1);
+ size_t length = end - start;
+ char **archives = opts->archives;
+ char *archive;
+
+ if (length == 0)
+ goto next;
+
+ if ((archives = realloc(archives, size)) != NULL) {
+ if ((archive = strndup(start, length)) != NULL) {
+ archives[opts->narchives] = archive;
+ opts->archives = archives;
+ opts->narchives++;
+ } else {
+ __pmNoMem("pmGetOptions(archive)", length, PM_FATAL_ERR);
+ }
+ } else {
+ __pmNoMem("pmGetOptions(archives)", size, PM_FATAL_ERR);
+ }
+ next:
+ start = (*end == '\0') ? end : end + 1;
+ }
+ }
+}
+
+void
+__pmAddOptHost(pmOptions *opts, char *arg)
+{
+ char **hosts = opts->hosts;
+ size_t size = sizeof(char *) * (opts->nhosts + 1);
+
+ if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg);
+ opts->errors++;
+ } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one host or archive allowed\n", pmProgname);
+ opts->errors++;
+ } else if ((hosts = realloc(hosts, size)) != NULL) {
+ hosts[opts->nhosts] = arg;
+ opts->hosts = hosts;
+ opts->nhosts++;
+ } else {
+ __pmNoMem("pmGetOptions(host)", size, PM_FATAL_ERR);
+ }
+}
+
+static inline char *
+skip_whitespace(char *p)
+{
+ while (*p && isspace((int)*p) && *p != '\n')
+ p++;
+ return p;
+}
+
+static inline char *
+skip_nonwhitespace(char *p)
+{
+ while (*p && !isspace((int)*p))
+ p++;
+ return p;
+}
+
+void
+__pmAddOptArchiveFolio(pmOptions *opts, char *arg)
+{
+ char buffer[MAXPATHLEN];
+ FILE *fp;
+
+#define FOLIO_MAGIC "PCPFolio"
+#define FOLIO_VERSION "Version: 1"
+
+ if (opts->nhosts && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one of hosts or archives allowed\n", pmProgname);
+ opts->errors++;
+ } else if ((fp = fopen(arg, "r")) == NULL) {
+ pmprintf("%s: cannot open archive folio %s: %s\n", pmProgname,
+ arg, pmErrStr_r(-oserror(), buffer, sizeof(buffer)));
+ opts->flags |= PM_OPTFLAG_RUNTIME_ERR;
+ opts->errors++;
+ } else {
+ size_t length;
+ char *p, *log, *dir;
+ int line, sep = __pmPathSeparator();
+
+ if (fgets(buffer, sizeof(buffer)-1, fp) == NULL) {
+ pmprintf("%s: archive folio %s has no header\n", pmProgname, arg);
+ goto badfolio;
+ }
+ if (strncmp(buffer, FOLIO_MAGIC, sizeof(FOLIO_MAGIC)-1) != 0) {
+ pmprintf("%s: archive folio %s has bad magic\n", pmProgname, arg);
+ goto badfolio;
+ }
+ if (fgets(buffer, sizeof(buffer)-1, fp) == NULL) {
+ pmprintf("%s: archive folio %s has no version\n", pmProgname, arg);
+ goto badfolio;
+ }
+ if (strncmp(buffer, FOLIO_VERSION, sizeof(FOLIO_VERSION)-1) != 0) {
+ pmprintf("%s: unknown version archive folio %s\n", pmProgname, arg);
+ goto badfolio;
+ }
+
+ line = 2;
+ dir = dirname(arg);
+
+ while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
+ line++;
+ p = buffer;
+
+ if (strncmp(p, "Archive:", sizeof("Archive:")-1) != 0)
+ continue;
+ p = skip_nonwhitespace(p);
+ p = skip_whitespace(p);
+ if (*p == '\n') {
+ pmprintf("%s: missing host on archive folio line %d\n",
+ pmProgname, line);
+ goto badfolio;
+ }
+ p = skip_nonwhitespace(p);
+ p = skip_whitespace(p);
+ if (*p == '\n') {
+ pmprintf("%s: missing path on archive folio line %d\n",
+ pmProgname, line);
+ goto badfolio;
+ }
+
+ log = p;
+ p = skip_nonwhitespace(p);
+ *p = '\0';
+
+ length = strlen(dir) + 1 + strlen(log) + 1;
+ if ((p = (char *)malloc(length)) == NULL)
+ __pmNoMem("pmGetOptions(archive)", length, PM_FATAL_ERR);
+ snprintf(p, length, "%s%c%s", dir, sep, log);
+ __pmAddOptArchive(opts, p);
+ }
+
+ fclose(fp);
+ }
+ return;
+
+badfolio:
+ fclose(fp);
+ opts->flags |= PM_OPTFLAG_RUNTIME_ERR;
+ opts->errors++;
+}
+
+static void
+__pmAddOptHostFile(pmOptions *opts, char *arg)
+{
+ if (!(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg);
+ opts->errors++;
+ } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one of hosts or archives allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ FILE *fp = fopen(arg, "r");
+
+ if (fp) {
+ char buffer[MAXHOSTNAMELEN];
+
+ while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
+ size_t size = sizeof(char *) * (opts->nhosts + 1);
+ char **hosts = opts->hosts;
+ char *host, *p = buffer;
+ size_t length;
+
+ while (isspace((int)*p) && *p != '\n')
+ p++;
+ if (*p == '\n' || *p == '#')
+ continue;
+ host = p;
+ length = 0;
+ while (*p != '\n' && *p != '#' && !isspace((int)*p))
+ length++;
+ p += length;
+ *p = '\0';
+ if ((hosts = realloc(hosts, size)) != NULL) {
+ if ((host = strndup(host, length)) != NULL) {
+ hosts[opts->nhosts] = host;
+ opts->hosts = hosts;
+ opts->nhosts++;
+ } else {
+ __pmNoMem("pmGetOptions(host)", length, PM_FATAL_ERR);
+ }
+ } else {
+ __pmNoMem("pmGetOptions(hosts)", size, PM_FATAL_ERR);
+ }
+ }
+
+ fclose(fp);
+ } else {
+ char errmsg[PM_MAXERRMSGLEN];
+
+ pmprintf("%s: cannot open hosts file %s: %s\n", pmProgname, arg,
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ opts->flags |= PM_OPTFLAG_RUNTIME_ERR;
+ opts->errors++;
+ }
+ }
+}
+
+void
+__pmAddOptHostList(pmOptions *opts, char *arg)
+{
+ if (!(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: too many hosts requested: %s\n", pmProgname, arg);
+ opts->errors++;
+ } else if (opts->narchives && !(opts->flags & PM_OPTFLAG_MIXED)) {
+ pmprintf("%s: only one of hosts or archives allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ char *start = arg, *end;
+
+ while ((end = comma_or_end(start)) != NULL) {
+ size_t size = sizeof(char *) * (opts->nhosts + 1);
+ size_t length = end - start;
+ char **hosts = opts->hosts;
+ char *host;
+
+ if (length == 0)
+ goto next;
+
+ if ((hosts = realloc(hosts, size)) != NULL) {
+ if ((host = strndup(start, length)) != NULL) {
+ hosts[opts->nhosts] = host;
+ opts->hosts = hosts;
+ opts->nhosts++;
+ } else {
+ __pmNoMem("pmGetOptions(host)", length, PM_FATAL_ERR);
+ }
+ } else {
+ __pmNoMem("pmGetOptions(hosts)", size, PM_FATAL_ERR);
+ }
+ next:
+ start = (*end == '\0') ? end : end + 1;
+ }
+ }
+}
+
+static void
+__pmSetLocalContextTable(pmOptions *opts, char *arg)
+{
+ char *errmsg;
+
+ if ((errmsg = __pmSpecLocalPMDA(arg)) != NULL) {
+ pmprintf("%s: __pmSpecLocalPMDA failed: %s\n", pmProgname, errmsg);
+ opts->errors++;
+ }
+}
+
+static void
+__pmSetLocalContextFlag(pmOptions *opts)
+{
+ if (opts->context && !(opts->flags & PM_OPTFLAG_MULTI)) {
+ pmprintf("%s: at most one of -a, -h and -L allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->Lflag = 1;
+ }
+}
+
+static void
+__pmSetNameSpace(pmOptions *opts, char *arg, int dupok)
+{
+ int sts;
+
+ if ((sts = pmLoadASCIINameSpace(arg, dupok)) < 0) {
+ pmprintf("%s: Cannot load namespace from \"%s\": %s\n",
+ pmProgname, arg, pmErrStr(sts));
+ opts->flags |= PM_OPTFLAG_RUNTIME_ERR;
+ opts->errors++;
+ } else {
+ opts->nsflag = 1;
+ }
+}
+
+static void
+__pmSetSampleCount(pmOptions *opts, char *arg)
+{
+ char *endnum;
+
+ if (opts->finish_optarg) {
+ pmprintf("%s: at most one of -T and -s allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->samples = (int)strtol(arg, &endnum, 10);
+ if (*endnum != '\0' || opts->samples < 0) {
+ pmprintf("%s: -s requires numeric argument\n", pmProgname);
+ opts->errors++;
+ }
+ }
+}
+
+static void
+__pmSetFinishTime(pmOptions *opts, char *arg)
+{
+ if (opts->samples) {
+ pmprintf("%s: at most one of -T and -s allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->finish_optarg = arg;
+ }
+}
+
+static void
+__pmSetSampleInterval(pmOptions *opts, char *arg)
+{
+ char *endnum;
+
+ if (pmParseInterval(arg, &opts->interval, &endnum) < 0) {
+ pmprintf("%s: -t argument not in pmParseInterval(3) format:\n",
+ pmProgname);
+ pmprintf("%s\n", endnum);
+ opts->errors++;
+ free(endnum);
+ }
+}
+
+static void
+__pmSetTimeZone(pmOptions *opts, char *arg)
+{
+ if (opts->tzflag) {
+ pmprintf("%s: at most one of -Z and -z allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->timezone = arg;
+ }
+}
+
+static void
+__pmSetHostZone(pmOptions *opts)
+{
+ if (opts->timezone) {
+ pmprintf("%s: at most one of -Z and -z allowed\n", pmProgname);
+ opts->errors++;
+ } else {
+ opts->tzflag = 1;
+ }
+}
+
+/*
+ * Called once at the start of option processing, before any getopt calls.
+ * For our needs, we can set default values at this point based on values
+ * we find set in the processes environment.
+ */
+void
+__pmStartOptions(pmOptions *opts)
+{
+ extern char **_environ;
+ char **p, *s, *value = NULL;
+
+ if (opts->flags & PM_OPTFLAG_INIT)
+ return;
+
+ for (p = _environ; *p != NULL; p++) {
+ s = *p;
+ if (strncmp(s, "PCP_", 4) != 0)
+ continue; /* short circuit if not PCP-prefixed */
+ s += 4;
+ if ((value = strchr(s, '=')) != NULL) {
+ *value = '\0';
+ value++; /* skip over the equals sign */
+ }
+
+ if (strcmp(s, "ALIGN_TIME") == 0)
+ __pmSetAlignment(opts, value);
+ else if (strcmp(s, "ARCHIVE") == 0)
+ __pmAddOptArchive(opts, value);
+ else if (strcmp(s, "ARCHIVE_LIST") == 0)
+ __pmAddOptArchiveList(opts, value);
+ else if (strcmp(s, "DEBUG") == 0)
+ __pmSetDebugFlag(opts, value);
+ else if (strcmp(s, "FOLIO") == 0)
+ __pmAddOptArchiveFolio(opts, value);
+ else if (strcmp(s, "GUIMODE") == 0)
+ __pmSetGuiModeFlag(opts);
+ else if (strcmp(s, "HOST") == 0)
+ __pmAddOptHost(opts, value);
+ else if (strcmp(s, "HOST_LIST") == 0)
+ __pmAddOptHostList(opts, value);
+ else if (strcmp(s, "LOCALMODE") == 0)
+ __pmSetLocalContextFlag(opts);
+ else if (strcmp(s, "NAMESPACE") == 0)
+ __pmSetNameSpace(opts, value, 0);
+ else if (strcmp(s, "ORIGIN") == 0 ||
+ strcmp(s, "ORIGIN_TIME") == 0)
+ __pmSetOrigin(opts, value);
+ else if (strcmp(s, "GUIPORT") == 0)
+ __pmSetGuiPort(opts, value);
+ else if (strcmp(s, "START_TIME") == 0)
+ __pmSetStartTime(opts, value);
+ else if (strcmp(s, "SAMPLES") == 0)
+ __pmSetSampleCount(opts, value);
+ else if (strcmp(s, "FINISH_TIME") == 0)
+ __pmSetFinishTime(opts, value);
+ else if (strcmp(s, "INTERVAL") == 0)
+ __pmSetSampleInterval(opts, value);
+ else if (strcmp(s, "TIMEZONE") == 0)
+ __pmSetTimeZone(opts, value);
+ else if (strcmp(s, "HOSTZONE") == 0)
+ __pmSetHostZone(opts);
+
+ if (value) /* reset the environment */
+ *(value-1) = '=';
+ }
+
+ opts->flags |= PM_OPTFLAG_INIT;
+}
+
+int
+pmGetOptions(int argc, char *const *argv, pmOptions *opts)
+{
+ pmLongOptions *opt;
+ int flag = 0;
+ int c = EOF;
+
+ if (!(opts->flags & PM_OPTFLAG_INIT)) {
+ __pmSetProgname(argv[0]);
+ opts->__initialized = 1;
+ __pmStartOptions(opts);
+ }
+
+ /* environment has been checked at this stage, leave opt* well alone */
+ if (opts->flags & PM_OPTFLAG_ENV_ONLY) {
+ __pmEndOptions(opts);
+ return EOF;
+ }
+
+ while (!flag) {
+ c = pmgetopt_r(argc, argv, opts);
+
+ /* provide opportunity for overriding the general set of options */
+ if (c != EOF && opts->override && opts->override(c, opts))
+ break;
+
+ switch (c) {
+ case 'A':
+ __pmSetAlignment(opts, opts->optarg);
+ break;
+ case 'a':
+ __pmAddOptArchive(opts, opts->optarg);
+ break;
+ case 'D':
+ __pmSetDebugFlag(opts, opts->optarg);
+ break;
+ case 'g':
+ __pmSetGuiModeFlag(opts);
+ break;
+ case 'H':
+ __pmAddOptHostFile(opts, opts->optarg);
+ break;
+ case 'h':
+ __pmAddOptHost(opts, opts->optarg);
+ break;
+ case 'K':
+ __pmSetLocalContextTable(opts, opts->optarg);
+ break;
+ case 'L':
+ __pmSetLocalContextFlag(opts);
+ break;
+ case 'N':
+ __pmSetNameSpace(opts, opts->optarg, 1);
+ break;
+ case 'n':
+ __pmSetNameSpace(opts, opts->optarg, 0);
+ break;
+ case 'O':
+ __pmSetOrigin(opts, opts->optarg);
+ break;
+ case 'p':
+ __pmSetGuiPort(opts, opts->optarg);
+ break;
+ case 'S':
+ __pmSetStartTime(opts, opts->optarg);
+ break;
+ case 's':
+ __pmSetSampleCount(opts, opts->optarg);
+ break;
+ case 'T':
+ __pmSetFinishTime(opts, opts->optarg);
+ break;
+ case 't':
+ __pmSetSampleInterval(opts, opts->optarg);
+ break;
+ case 'V':
+ opts->flags |= PM_OPTFLAG_EXIT;
+ pmprintf("%s version %s\n", pmProgname, PCP_VERSION);
+ break;
+ case 'Z':
+ __pmSetTimeZone(opts, opts->optarg);
+ break;
+ case 'z':
+ __pmSetHostZone(opts);
+ break;
+ case '?':
+ opts->errors++;
+ break;
+ case 0:
+ /* long-option-only standard argument handling */
+ opt = &opts->long_options[opts->index];
+ if (strcmp(opt->long_opt, PMLONGOPT_HOST_LIST) == 0)
+ __pmAddOptHostList(opts, opts->optarg);
+ else if (strcmp(opt->long_opt, PMLONGOPT_ARCHIVE_LIST) == 0)
+ __pmAddOptArchiveList(opts, opts->optarg);
+ else if (strcmp(opt->long_opt, PMLONGOPT_ARCHIVE_FOLIO) == 0)
+ __pmAddOptArchiveFolio(opts, opts->optarg);
+ else
+ flag = 1;
+ break;
+ default: /* pass back out to caller */
+ flag = 1;
+ }
+ }
+
+ /* end of arguments - process everything we can now */
+ if (c == EOF)
+ __pmEndOptions(opts);
+ return c;
+}
+
+void
+pmFreeOptions(pmOptions *opts)
+{
+ if (opts->narchives)
+ free(opts->archives);
+ if (opts->nhosts)
+ free(opts->hosts);
+}
+
+void
+pmUsageMessage(pmOptions *opts)
+{
+ pmLongOptions *option;
+ const char *message;
+ int bytes;
+
+ if (opts->flags & (PM_OPTFLAG_RUNTIME_ERR|PM_OPTFLAG_EXIT))
+ goto flush;
+
+ message = opts->short_usage ? opts->short_usage : "[options]";
+ pmprintf("Usage: %s %s\n", pmProgname, message);
+
+ for (option = opts->long_options; option; option++) {
+ if (!option->long_opt) /* sentinel */
+ break;
+ if (!option->message) /* undocumented option */
+ continue;
+ if (option->short_opt == '-') { /* section header */
+ pmprintf("\n%s:\n", option->message);
+ continue;
+ }
+ if (option->short_opt == '|') { /* descriptive text */
+ pmprintf("%s\n", option->message);
+ continue;
+ }
+
+ message = option->argname ? option->argname : "?";
+ if (option->long_opt && option->long_opt[0] != '\0') {
+ if (option->short_opt && option->has_arg)
+ bytes = pmprintf(" -%c %s, --%s=%s", option->short_opt,
+ message, option->long_opt, message);
+ else if (option->short_opt)
+ bytes = pmprintf(" -%c, --%s", option->short_opt,
+ option->long_opt);
+ else if (option->has_arg)
+ bytes = pmprintf(" --%s=%s", option->long_opt, message);
+ else
+ bytes = pmprintf(" --%s", option->long_opt);
+ } else { /* short option with no long option */
+ if (option->has_arg)
+ bytes = pmprintf(" -%c %s", option->short_opt, message);
+ else
+ bytes = pmprintf(" -%c", option->short_opt);
+ }
+
+ if (bytes < 24) /* message will fit here */
+ pmprintf("%*s%s\n", 24 - bytes, "", option->message);
+ else /* message on next line */
+ pmprintf("\n%24s%s\n", "", option->message);
+ }
+flush:
+ if (!(opts->flags & PM_OPTFLAG_NOFLUSH))
+ pmflush();
+}
+
+/*
+ * Exchange two adjacent subsequences of ARGV.
+ * One subsequence is elements [first_nonopt,last_nonopt)
+ * which contains all the non-options that have been skipped so far.
+ * The other is elements [last_nonopt,optind), which contains all
+ * the options processed since those non-options were skipped.
+
+ * `first_nonopt' and `last_nonopt' are relocated so that they describe
+ * the new indices of the non-options in ARGV after they are moved.
+ */
+static void
+__pmgetopt_exchange(char **argv, pmOptions *d)
+{
+ int bottom = d->__first_nonopt;
+ int middle = d->__last_nonopt;
+ int top = d->optind;
+ char *tem;
+
+ /*
+ * Exchange the shorter segment with the far end of the longer segment.
+ * That puts the shorter segment into the right place.
+ * It leaves the longer segment in the right place overall,
+ * but it consists of two parts that need to be swapped next.
+ */
+ while (top > middle && middle > bottom) {
+ if (top - middle > middle - bottom) {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++) {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+ d->__first_nonopt += (d->optind - d->__last_nonopt);
+ d->__last_nonopt = d->optind;
+}
+
+/*
+ * Initialize the internal data when the first getopt call is made.
+ */
+static const char *
+__pmgetopt_initialize(int argc, char *const *argv, pmOptions *d)
+{
+ const char *optstring = d->short_options;
+ int posixly_correct = !!(d->flags & PM_OPTFLAG_POSIX);
+
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ * is the program name); the sequence of previously skipped
+ * non-option ARGV-elements is empty.
+ */
+ d->__first_nonopt = d->__last_nonopt = d->optind;
+ d->__nextchar = NULL;
+ d->__posixly_correct = posixly_correct | !!getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+ if (optstring[0] == '-') {
+ d->__ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+') {
+ d->__ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (d->__posixly_correct)
+ d->__ordering = REQUIRE_ORDER;
+ else
+ d->__ordering = PERMUTE;
+
+ return optstring;
+}
+
+/*
+ * Scan elements of ARGV (whose length is ARGC) for option characters
+ * given in OPTSTRING.
+ *
+ * If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ * then it is an option element. The characters of this element
+ * (aside from the initial '-') are option characters. If `getopt'
+ * is called repeatedly, it returns successively each of the option characters
+ * from each of the option elements.
+ *
+ * If `getopt' finds another option character, it returns that character,
+ * updating `optind' and `nextchar' so that the next call to `getopt' can
+ * resume the scan with the following option character or ARGV-element.
+ *
+ * If there are no more option characters, `getopt' returns -1.
+ * Then `optind' is the index in ARGV of the first ARGV-element
+ * that is not an option. (The ARGV-elements have been permuted
+ * so that those that are not options now come last.)
+ *
+ * OPTSTRING is a string containing the legitimate option characters.
+ * If an option character is seen that is not listed in OPTSTRING,
+ * return '?' after printing an error message. If you set `opterr' to
+ * zero, the error message is suppressed but we still return '?'.
+ *
+ * If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ * so the following text in the same ARGV-element, or the text of the following
+ * ARGV-element, is returned in `optarg'. Two colons mean an option that
+ * wants an optional arg; if there is text in the current ARGV-element,
+ * it is returned in `optarg', otherwise `optarg' is set to zero.
+ *
+ * If OPTSTRING starts with `-' or `+', it requests different methods of
+ * handling the non-option ARGV-elements.
+ * See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+ *
+ * Long-named options begin with `--' instead of `-'.
+ * Their names may be abbreviated as long as the abbreviation is unique
+ * or is an exact match for some defined option. If they have an
+ * argument, it follows the option name in the same ARGV-element, separated
+ * from the option name by a `=', or else the in next ARGV-element.
+ * When `getopt' finds a long-named option, it returns 0 if that option's
+ * `flag' field is nonzero, the value of the option's `val' field
+ * if the `flag' field is zero.
+ *
+ * The elements of ARGV aren't really const, because we permute them.
+ * But we pretend they're const in the prototype to be compatible
+ * with other systems.
+ *
+ * LONGOPTS is a vector of `pmOptions' terminated by an
+ * element containing a name which is zero.
+ *
+ * LONGIND returns the index in LONGOPT of the long-named option found.
+ * It is only valid when a long-named option has been found by the most
+ * recent call.
+ *
+ * If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ * long-named options.
+ */
+
+typedef struct pmOptList {
+ pmLongOptions * p;
+ struct pmOptList * next;
+} pmOptionsList;
+
+int
+pmgetopt_r(int argc, char *const *argv, pmOptions *d)
+{
+ const char *optstring = d->short_options;
+ pmLongOptions *longopts = d->long_options;
+ int *longind = &d->index;
+ int long_only = (d->flags & PM_OPTFLAG_LONG_ONLY);
+ int quiet = (d->flags & PM_OPTFLAG_QUIET);
+ int print_errors = d->opterr || !quiet;
+
+ if (argc < 1 || !optstring)
+ return -1;
+
+ d->optarg = NULL;
+
+ if (d->optind == 0 || d->__initialized <= 1) {
+ if (d->optind == 0)
+ d->optind = 1; /* Don't scan ARGV[0], the program name. */
+ if (!d->__initialized)
+ __pmSetProgname(argv[0]);
+ optstring = __pmgetopt_initialize(argc, argv, d);
+ d->__initialized = 2;
+ }
+ else if (optstring[0] == '-' || optstring[0] == '+')
+ optstring++;
+ if (optstring[0] == ':')
+ print_errors = 0;
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ * Either it does not have option syntax, or there is an environment flag
+ * from the shell indicating it is not an option. The later information
+ * is only used when the used in the GNU libc.
+ */
+
+ if (d->__nextchar == NULL || *d->__nextchar == '\0') {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ * moved back by the user (who may also have changed the arguments).
+ */
+ if (d->__last_nonopt > d->optind)
+ d->__last_nonopt = d->optind;
+ if (d->__first_nonopt > d->optind)
+ d->__first_nonopt = d->optind;
+
+ if (d->__ordering == PERMUTE) {
+ /* If we have just processed options following some non-options,
+ * exchange them so that the options come first.
+ */
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ __pmgetopt_exchange((char **) argv, d);
+ else if (d->__last_nonopt != d->optind)
+ d->__first_nonopt = d->optind;
+
+ /* Skip any additional non-options and
+ * extend the range of non-options previously skipped.
+ */
+ while (d->optind < argc && \
+ (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
+ d->optind++;
+ d->__last_nonopt = d->optind;
+ }
+
+ /*
+ * The special ARGV-element `--' means premature end of options.
+ * Skip it like a null option,
+ * then exchange with previous non-options as if it were an option,
+ * then skip everything else like a non-option.
+ */
+ if (d->optind != argc && !strcmp(argv[d->optind], "--")) {
+ d->optind++;
+
+ if (d->__first_nonopt != d->__last_nonopt
+ && d->__last_nonopt != d->optind)
+ __pmgetopt_exchange((char **) argv, d);
+ else if (d->__first_nonopt == d->__last_nonopt)
+ d->__first_nonopt = d->optind;
+ d->__last_nonopt = argc;
+ d->optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ * and back over any non-options that we skipped and permuted.
+ */
+ if (d->optind == argc) {
+ /* Set the next-arg-index to point at the non-options
+ * that we previously skipped, so the caller will digest them.
+ */
+ if (d->__first_nonopt != d->__last_nonopt)
+ d->optind = d->__first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ * either stop the scan or describe it to the caller and pass it by.o
+ */
+ if (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') {
+ if (d->__ordering == REQUIRE_ORDER)
+ return -1;
+ d->optarg = argv[d->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ * Skip the initial punctuation.
+ */
+ d->__nextchar = (argv[d->optind] + 1
+ + (longopts != NULL && argv[d->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /*
+ * Check whether the ARGV-element is a long option.
+ *
+ * If long_only and the ARGV-element has the form "-f", where f is
+ * a valid short option, don't consider it an abbreviated form of
+ * a long option that starts with f. Otherwise there would be no
+ * way to give the -f short option.
+ *
+ * On the other hand, if there's a long option "fubar" and
+ * the ARGV-element is "-fu", do consider that an abbreviation of
+ * the long option, just like "--fu", and not "-f" with arg "u".
+ *
+ * This distinction seems to be the most useful approach.
+ */
+ if (longopts != NULL
+ && (argv[d->optind][1] == '-'
+ || (long_only && (argv[d->optind][2]
+ || !strchr(optstring, argv[d->optind][1]))))) {
+ char *nameend;
+ unsigned int namelen;
+ pmLongOptions *p;
+ pmLongOptions *pfound = NULL;
+ pmOptionsList *ambig_list = NULL;
+ int exact = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+ namelen = nameend - d->__nextchar;
+
+ /* Test all long options for either exact match
+ * or abbreviated matches.
+ */
+ for (p = longopts, option_index = 0; p->long_opt; p++, option_index++) {
+ if (!strncmp(p->long_opt, d->__nextchar, namelen)) {
+ if (namelen == (unsigned int) strlen(p->long_opt)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL) {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->short_opt != p->short_opt) {
+ /* Second or later nonexact match found. */
+ pmOptionsList *newp = malloc(sizeof(*newp));
+ newp->p = p;
+ newp->next = ambig_list;
+ ambig_list = newp;
+ }
+ }
+ }
+
+ if (ambig_list != NULL && !exact) {
+ if (print_errors) {
+ pmOptionsList first;
+ first.p = pfound;
+ first.next = ambig_list;
+ ambig_list = &first;
+
+ pmprintf("%s: option '%s' is ambiguous; possibilities:",
+ pmProgname, argv[d->optind]);
+ do {
+ pmprintf(" '--%s'", ambig_list->p->long_opt);
+ ambig_list = ambig_list->next;
+ } while (ambig_list != NULL);
+ pmprintf("\n");
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ d->optind++;
+ d->optopt = 0;
+ free(ambig_list);
+ return '?';
+ }
+ else if (ambig_list != NULL) {
+ free(ambig_list);
+ }
+
+ if (pfound != NULL) {
+ option_index = indfound;
+ d->optind++;
+ if (*nameend) {
+ if (pfound->has_arg) {
+ d->optarg = nameend + 1;
+ } else {
+ if (print_errors) {
+ if (argv[d->optind - 1][1] == '-') {
+ /* --option */
+ pmprintf("%s: option '--%s' doesn't allow an argument\n",
+ pmProgname, pfound->long_opt);
+ } else {
+ /* +option or -option */
+ pmprintf("%s: option '%c%s' doesn't allow an argument\n",
+ pmProgname, argv[d->optind - 1][0],
+ pfound->long_opt);
+ }
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ d->optopt = pfound->short_opt;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1) {
+ if (d->optind < argc) {
+ d->optarg = argv[d->optind++];
+ } else {
+ if (print_errors) {
+ pmprintf("%s: option '--%s' requires an argument\n",
+ pmProgname, pfound->long_opt);
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ d->optopt = pfound->short_opt;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ return pfound->short_opt;
+ }
+
+ /* Can't find it as a long option. If this is not a long-only form,
+ * or the option starts with '--', or is not a valid short option,
+ * then it's an error.
+ * Otherwise interpret it as a short option.
+ */
+ if (!long_only || argv[d->optind][1] == '-'
+ || strchr (optstring, *d->__nextchar) == NULL) {
+ if (print_errors) {
+ if (argv[d->optind][1] == '-') {
+ /* --option */
+ pmprintf("%s: unrecognized option '--%s'\n",
+ pmProgname, d->__nextchar);
+ } else {
+ /* +option or -option */
+ pmprintf("%s: unrecognized option '%c%s'\n",
+ pmProgname, argv[d->optind][0], d->__nextchar);
+ }
+ }
+ d->__nextchar = (char *) "";
+ d->optind++;
+ d->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *d->__nextchar++;
+ char *temp = strchr(optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*d->__nextchar == '\0')
+ ++d->optind;
+
+ if (temp == NULL || c == ':' || c == ';') {
+ if (print_errors) {
+ pmprintf("%s: invalid option -- '%c'\n", pmProgname, c);
+ }
+ d->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';') {
+ if (longopts == NULL)
+ goto no_longs;
+
+ char *nameend;
+ pmLongOptions *p;
+ pmLongOptions *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0') {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ * we must advance to the next element now.
+ */
+ d->optind++;
+ }
+ else if (d->optind == argc) {
+ if (print_errors) {
+ pmprintf("%s: option requires an argument -- '%c'\n",
+ pmProgname, c);
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else {
+ /* We already incremented `d->optind' once;
+ * increment it again when taking next ARGV-elt as argument.
+ */
+ d->optarg = argv[d->optind++];
+ }
+
+ /* optarg is now the argument, see if it's in the table of longopts. */
+
+ for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '=';
+ nameend++)
+ /* Do nothing */ ;
+
+ /* Test all long options for either exact or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->long_opt; p++, option_index++)
+ if (!strncmp(p->long_opt, d->__nextchar, nameend - d->__nextchar)) {
+ if ((unsigned int)(nameend - d->__nextchar) == strlen(p->long_opt)) {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL) {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else if (long_only
+ || pfound->has_arg != p->has_arg
+ || pfound->short_opt != p->short_opt) {
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ }
+ if (ambig && !exact) {
+ if (print_errors) {
+ pmprintf("%s: option '-W %s' is ambiguous\n",
+ pmProgname, d->optarg);
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ d->optind++;
+ return '?';
+ }
+ if (pfound != NULL) {
+ option_index = indfound;
+ if (*nameend) {
+ if (pfound->has_arg) {
+ d->optarg = nameend + 1;
+ } else {
+ if (print_errors) {
+ pmprintf("%s: option '-W %s' doesn't allow an argument\n",
+ pmProgname, pfound->long_opt);
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1) {
+ if (d->optind < argc) {
+ d->optarg = argv[d->optind++];
+ } else {
+ if (print_errors) {
+ pmprintf("%s: option '-W %s' requires an argument\n",
+ pmProgname, pfound->long_opt);
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ else {
+ d->optarg = NULL;
+ }
+ d->__nextchar += strlen(d->__nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ return pfound->short_opt;
+ }
+
+ no_longs:
+ d->__nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':') {
+ if (temp[2] == ':') {
+ /* This is an option that accepts an argument optionally. */
+ if (*d->__nextchar != '\0') {
+ d->optarg = d->__nextchar;
+ d->optind++;
+ } else {
+ d->optarg = NULL;
+ }
+ d->__nextchar = NULL;
+ }
+ else {
+ /* This is an option that requires an argument. */
+ if (*d->__nextchar != '\0') {
+ d->optarg = d->__nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ * we must advance to the next element now.
+ */
+ d->optind++;
+ }
+ else if (d->optind == argc) {
+ if (print_errors) {
+ pmprintf("%s: option requires an argument -- '%c'\n",
+ pmProgname, c);
+ }
+ d->optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else {
+ /* We already incremented `optind' once;
+ * increment it again when taking next ARGV-elt as argument.
+ */
+ d->optarg = argv[d->optind++];
+ }
+ d->__nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
diff --git a/src/libpcp/src/hash.c b/src/libpcp/src/hash.c
new file mode 100644
index 0000000..9bdf97f
--- /dev/null
+++ b/src/libpcp/src/hash.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <stddef.h>
+
+void
+__pmHashInit(__pmHashCtl *hcp)
+{
+ memset(hcp, 0, sizeof(*hcp));
+}
+
+__pmHashNode *
+__pmHashSearch(unsigned int key, __pmHashCtl *hcp)
+{
+ __pmHashNode *hp;
+
+ if (hcp->hsize == 0)
+ return NULL;
+
+ for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) {
+ if (hp->key == key)
+ return hp;
+ }
+ return NULL;
+}
+
+int
+__pmHashAdd(unsigned int key, void *data, __pmHashCtl *hcp)
+{
+ __pmHashNode *hp;
+ int k;
+
+ hcp->nodes++;
+
+ if (hcp->hsize == 0) {
+ hcp->hsize = 1; /* arbitrary number */
+ if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) {
+ hcp->hsize = 0;
+ return -oserror();
+ }
+ }
+ else if (hcp->nodes / 4 > hcp->hsize) {
+ __pmHashNode *tp;
+ __pmHashNode **old = hcp->hash;
+ int oldsize = hcp->hsize;
+
+ hcp->hsize *= 2;
+ if (hcp->hsize % 2) hcp->hsize++;
+ if (hcp->hsize % 3) hcp->hsize += 2;
+ if (hcp->hsize % 5) hcp->hsize += 2;
+ if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) {
+ hcp->hsize = oldsize;
+ hcp->hash = old;
+ return -oserror();
+ }
+ /*
+ * re-link chains
+ */
+ while (oldsize) {
+ for (hp = old[--oldsize]; hp != NULL; ) {
+ tp = hp;
+ hp = hp->next;
+ k = tp->key % hcp->hsize;
+ tp->next = hcp->hash[k];
+ hcp->hash[k] = tp;
+ }
+ }
+ free(old);
+ }
+
+ if ((hp = (__pmHashNode *)malloc(sizeof(__pmHashNode))) == NULL)
+ return -oserror();
+
+ k = key % hcp->hsize;
+ hp->key = key;
+ hp->data = data;
+ hp->next = hcp->hash[k];
+ hcp->hash[k] = hp;
+
+ return 1;
+}
+
+int
+__pmHashDel(unsigned int key, void *data, __pmHashCtl *hcp)
+{
+ __pmHashNode *hp;
+ __pmHashNode *lhp = NULL;
+
+ if (hcp->hsize == 0)
+ return 0;
+
+ for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) {
+ if (hp->key == key && hp->data == data) {
+ if (lhp == NULL)
+ hcp->hash[key % hcp->hsize] = hp->next;
+ else
+ lhp->next = hp->next;
+ free(hp);
+ return 1;
+ }
+ lhp = hp;
+ }
+
+ return 0;
+}
+
+void
+__pmHashClear(__pmHashCtl *hcp)
+{
+ if (hcp->hsize != 0) {
+ free(hcp->hash);
+ hcp->hsize = 0;
+ }
+}
+
+/*
+ * Iterate over the entire hash table. For each entry, call *cb,
+ * passing *cdata and the current key/value pair. The function's
+ * return value decides how to continue or abort iteration. The
+ * callback function must not modify the hash table.
+ */
+void
+__pmHashWalkCB(__pmHashWalkCallback cb, void *cdata, const __pmHashCtl *hcp)
+{
+ int n;
+
+ for (n = 0; n < hcp->hsize; n++) {
+ __pmHashNode *tp = hcp->hash[n];
+ __pmHashNode **tpp = & hcp->hash[n];
+
+ while (tp != NULL) {
+ __pmHashWalkState state = (*cb)(tp, cdata);
+
+ switch (state) {
+ case PM_HASH_WALK_DELETE_STOP:
+ *tpp = tp->next; /* unlink */
+ free(tp); /* delete */
+ return; /* & stop */
+
+ case PM_HASH_WALK_NEXT:
+ tpp = &tp->next;
+ tp = *tpp;
+ break;
+
+ case PM_HASH_WALK_DELETE_NEXT:
+ *tpp = tp->next; /* unlink */
+ /* NB: do not change tpp. It will still point at the previous
+ * node's "next" pointer. Consider consecutive CONTINUE_DELETEs.
+ */
+ free(tp); /* delete */
+ tp = *tpp; /* == tp->next, except that tp is already freed. */
+ break; /* & next */
+
+ case PM_HASH_WALK_STOP:
+ default:
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * Walk a hash table; state flow is START ... NEXT ... NEXT ...
+ */
+__pmHashNode *
+__pmHashWalk(__pmHashCtl *hcp, __pmHashWalkState state)
+{
+ __pmHashNode *node;
+
+ if (hcp->hsize == 0)
+ return NULL;
+
+ if (state == PM_HASH_WALK_START) {
+ hcp->index = 0;
+ hcp->next = hcp->hash[0];
+ }
+
+ while (hcp->next == NULL) {
+ hcp->index++;
+ if (hcp->index >= hcp->hsize)
+ return NULL;
+ hcp->next = hcp->hash[hcp->index];
+ }
+
+ node = hcp->next;
+ hcp->next = node->next;
+ return node;
+}
diff --git a/src/libpcp/src/help.c b/src/libpcp/src/help.c
new file mode 100644
index 0000000..ddc4b0d
--- /dev/null
+++ b/src/libpcp/src/help.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+
+static int
+lookuptext(int ident, int type, char **buffer)
+{
+ int n;
+ __pmContext *ctxp;
+ __pmDSO *dp;
+
+
+ if ((n = pmWhichContext()) >= 0) {
+ int ctx = n;
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+again:
+ n = __pmSendTextReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), ident, type);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_TEXT) {
+ int x_ident;
+ n = __pmDecodeText(pb, &x_ident, buffer);
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ /*
+ * Note: __pmDecodeText does not swab ident because it
+ * does not know whether it's a pmID or a pmInDom.
+ */
+
+ if (n == 0 && (*buffer)[0] == '\0' && (type & PM_TEXT_HELP)) {
+ /* fall back to one-line, if possible */
+ free(*buffer);
+ type &= ~PM_TEXT_HELP;
+ type |= PM_TEXT_ONELINE;
+ goto again;
+ }
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ else if ((dp = __pmLookupDSO(((__pmID_int *)&ident)->domain)) == NULL)
+ n = PM_ERR_NOAGENT;
+ else {
+again_local:
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ n = dp->dispatch.version.any.text(ident, type, buffer, dp->dispatch.version.any.ext);
+ if (n == 0 && (*buffer)[0] == '\0' && (type & PM_TEXT_HELP)) {
+ /* fall back to one-line, if possible */
+ type &= ~PM_TEXT_HELP;
+ type |= PM_TEXT_ONELINE;
+ goto again_local;
+ }
+ if (n == 0) {
+ /*
+ * PMDAs don't malloc the buffer but the caller will
+ * free it, so malloc and copy
+ */
+ *buffer = strdup(*buffer);
+ }
+ }
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE -- this is an error */
+ n = PM_ERR_NOTHOST;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
+
+int
+pmLookupText(pmID pmid, int level, char **buffer)
+{
+ return lookuptext((int)pmid, level | PM_TEXT_PMID, buffer);
+}
+
+int
+pmLookupInDomText(pmInDom indom, int level, char **buffer)
+{
+ return lookuptext((int)indom, level | PM_TEXT_INDOM, buffer);
+}
diff --git a/src/libpcp/src/instance.c b/src/libpcp/src/instance.c
new file mode 100644
index 0000000..07fad87
--- /dev/null
+++ b/src/libpcp/src/instance.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2006 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+
+int
+pmLookupInDom(pmInDom indom, const char *name)
+{
+ int n;
+ __pmInResult *result;
+ __pmContext *ctxp;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+ if ((n = pmWhichContext()) >= 0) {
+ int ctx = n;
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ &ctxp->c_origin, indom, PM_IN_NULL, name);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_INSTANCE) {
+ __pmInResult *result;
+ if ((n = __pmDecodeInstance(pb, &result)) >= 0) {
+ n = result->instlist[0];
+ __pmFreeInResult(result);
+ }
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ __pmDSO *dp;
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL)
+ n = PM_ERR_NOAGENT;
+ else {
+ /* We can safely cast away const here */
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ n = dp->dispatch.version.any.instance(indom, PM_IN_NULL,
+ (char *)name, &result,
+ dp->dispatch.version.any.ext);
+ }
+ if (n >= 0) {
+ n = result->instlist[0];
+ __pmFreeInResult(result);
+ }
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ n = __pmLogLookupInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, name);
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
+
+int
+pmNameInDom(pmInDom indom, int inst, char **name)
+{
+ int n;
+ __pmInResult *result;
+ __pmContext *ctxp;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+ if ((n = pmWhichContext()) >= 0) {
+ int ctx = n;
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ &ctxp->c_origin, indom, inst, NULL);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_INSTANCE) {
+ __pmInResult *result;
+ if ((n = __pmDecodeInstance(pb, &result)) >= 0) {
+ if ((*name = strdup(result->namelist[0])) == NULL)
+ n = -oserror();
+ __pmFreeInResult(result);
+ }
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ __pmDSO *dp;
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL)
+ n = PM_ERR_NOAGENT;
+ else {
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ n = dp->dispatch.version.any.instance(indom, inst, NULL, &result, dp->dispatch.version.any.ext);
+ }
+ if (n >= 0) {
+ if ((*name = strdup(result->namelist[0])) == NULL)
+ n = -oserror();
+ __pmFreeInResult(result);
+ }
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ char *tmp;
+ if ((n = __pmLogNameInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, inst, &tmp)) >= 0) {
+ if ((*name = strdup(tmp)) == NULL)
+ n = -oserror();
+ }
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
+
+static int
+inresult_to_lists(__pmInResult *result, int **instlist, char ***namelist)
+{
+ int n, i, sts, need;
+ char *p;
+ int *ilist;
+ char **nlist;
+
+ if (result->numinst == 0) {
+ __pmFreeInResult(result);
+ return 0;
+ }
+ need = 0;
+ for (i = 0; i < result->numinst; i++) {
+ need += sizeof(**namelist) + strlen(result->namelist[i]) + 1;
+ }
+ ilist = (int *)malloc(result->numinst * sizeof(result->instlist[0]));
+ if (ilist == NULL) {
+ sts = -oserror();
+ __pmFreeInResult(result);
+ return sts;
+ }
+ if ((nlist = (char **)malloc(need)) == NULL) {
+ sts = -oserror();
+ free(ilist);
+ __pmFreeInResult(result);
+ return sts;
+ }
+
+ *instlist = ilist;
+ *namelist = nlist;
+ p = (char *)&nlist[result->numinst];
+ for (i = 0; i < result->numinst; i++) {
+ ilist[i] = result->instlist[i];
+ strcpy(p, result->namelist[i]);
+ nlist[i] = p;
+ p += strlen(result->namelist[i]) + 1;
+ }
+ n = result->numinst;
+ __pmFreeInResult(result);
+ return n;
+}
+
+int
+pmGetInDom(pmInDom indom, int **instlist, char ***namelist)
+{
+ int n;
+ int i;
+ __pmInResult *result;
+ __pmContext *ctxp;
+ char *p;
+ int need;
+ int *ilist;
+ char **nlist;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((n = pmWhichContext()) >= 0) {
+ int ctx = n;
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ n = __pmSendInstanceReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ &ctxp->c_origin, indom, PM_IN_NULL, NULL);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_INSTANCE) {
+ __pmInResult *result;
+ if ((n = __pmDecodeInstance(pb, &result)) < 0) {
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ return n;
+ }
+ n = inresult_to_lists(result, instlist, namelist);
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ __pmDSO *dp;
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ n = PM_ERR_THREAD;
+ else if ((dp = __pmLookupDSO(((__pmInDom_int *)&indom)->domain)) == NULL)
+ n = PM_ERR_NOAGENT;
+ else {
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ n = dp->dispatch.version.any.instance(indom, PM_IN_NULL, NULL,
+ &result,
+ dp->dispatch.version.any.ext);
+ }
+ if (n >= 0)
+ n = inresult_to_lists(result, instlist, namelist);
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE */
+ int *insttmp;
+ char **nametmp;
+ if ((n = __pmLogGetInDom(ctxp->c_archctl->ac_log, indom, &ctxp->c_origin, &insttmp, &nametmp)) >= 0) {
+ need = 0;
+ for (i = 0; i < n; i++)
+ need += sizeof(char *) + strlen(nametmp[i]) + 1;
+ if ((ilist = (int *)malloc(n * sizeof(insttmp[0]))) == NULL) {
+ PM_UNLOCK(ctxp->c_lock);
+ return -oserror();
+ }
+ if ((nlist = (char **)malloc(need)) == NULL) {
+ free(ilist);
+ PM_UNLOCK(ctxp->c_lock);
+ return -oserror();
+ }
+ *instlist = ilist;
+ *namelist = nlist;
+ p = (char *)&nlist[n];
+ for (i = 0; i < n; i++) {
+ ilist[i] = insttmp[i];
+ strcpy(p, nametmp[i]);
+ nlist[i] = p;
+ p += strlen(nametmp[i]) + 1;
+ }
+ }
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ if (n == 0) {
+ /* avoid ambiguity when no instances or errors */
+ *instlist = NULL;
+ *namelist = NULL;
+ }
+
+ return n;
+}
+
+#ifdef PCP_DEBUG
+void
+__pmDumpInResult(FILE *f, const __pmInResult *irp)
+{
+ int i;
+ char strbuf[20];
+ fprintf(f,"pmInResult dump from " PRINTF_P_PFX "%p for InDom %s (0x%x), numinst=%d\n",
+ irp, pmInDomStr_r(irp->indom, strbuf, sizeof(strbuf)), irp->indom, irp->numinst);
+ for (i = 0; i < irp->numinst; i++) {
+ fprintf(f, " [%d]", i);
+ if (irp->instlist != NULL)
+ fprintf(f, " inst=%d", irp->instlist[i]);
+ if (irp->namelist != NULL)
+ fprintf(f, " name=\"%s\"", irp->namelist[i]);
+ fputc('\n', f);
+ }
+ return;
+}
+#endif
+
+void
+__pmFreeInResult(__pmInResult *res)
+{
+ int i;
+
+ if (res->namelist != NULL) {
+ for (i = 0; i < res->numinst; i++) {
+ if (res->namelist[i] != NULL) {
+ free(res->namelist[i]);
+ }
+ }
+ free(res->namelist);
+ }
+ if (res->instlist != NULL)
+ free(res->instlist);
+ free(res);
+}
diff --git a/src/libpcp/src/internal.h b/src/libpcp/src/internal.h
new file mode 100644
index 0000000..b9f4300
--- /dev/null
+++ b/src/libpcp/src/internal.h
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef _INTERNAL_H
+#define _INTERNAL_H
+
+/*
+ * Routines and data structures used within libpcp source files,
+ * but which we do not want to expose via impl.h or pmapi.h.
+ */
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+# define _PCP_HIDDEN __attribute__ ((visibility ("hidden")))
+#else
+# define _PCP_HIDDEN
+#endif
+
+#include "derive.h"
+
+extern int __pmConvertTimeout(int) _PCP_HIDDEN;
+extern const struct timeval *__pmConnectTimeout(void) _PCP_HIDDEN;
+extern const struct timeval * __pmDefaultRequestTimeout(void) _PCP_HIDDEN;
+extern int __pmConnectWithFNDELAY(int, void *, __pmSockLen) _PCP_HIDDEN;
+
+extern int __pmPtrToHandle(__pmContext *) _PCP_HIDDEN;
+extern int __pmFetchLocal(__pmContext *, int, pmID *, pmResult **) _PCP_HIDDEN;
+
+extern int __pmGlibGetDate (struct timespec *, char const *, struct timespec const *) _PCP_HIDDEN;
+
+#ifdef HAVE_NETWORK_BYTEORDER
+/*
+ * no-ops if already in network byte order but
+ * the value may be used in an expression.
+ */
+#define __htonpmUnits(a) (a)
+#define __ntohpmUnits(a) (a)
+#define __htonpmID(a) (a)
+#define __ntohpmID(a) (a)
+#define __htonpmInDom(a) (a)
+#define __ntohpmInDom(a) (a)
+#define __htonpmPDUInfo(a) (a)
+#define __ntohpmPDUInfo(a) (a)
+#define __htonpmCred(a) (a)
+#define __ntohpmCred(a) (a)
+
+/*
+ * For network byte order, the following are noops,
+ * but otherwise the function is void, so they are
+ * defined as comments to catch code that tries to
+ * use them in an expression or assignment.
+ */
+#define __htonpmValueBlock(a) /* noop */
+#define __ntohpmValueBlock(a) /* noop */
+#define __htonf(a) /* noop */
+#define __ntohf(a) /* noop */
+#define __htond(a) /* noop */
+#define __ntohd(a) /* noop */
+#define __htonll(a) /* noop */
+#define __ntohll(a) /* noop */
+
+#else
+/*
+ * Functions to convert to/from network byte order
+ * for little-endian platforms (e.g. Intel).
+ */
+#define __htonpmID(a) htonl(a)
+#define __ntohpmID(a) ntohl(a)
+#define __htonpmInDom(a) htonl(a)
+#define __ntohpmInDom(a) ntohl(a)
+
+extern pmUnits __htonpmUnits(pmUnits) _PCP_HIDDEN;
+extern pmUnits __ntohpmUnits(pmUnits) _PCP_HIDDEN;
+extern __pmPDUInfo __htonpmPDUInfo(__pmPDUInfo) _PCP_HIDDEN;
+extern __pmPDUInfo __ntohpmPDUInfo(__pmPDUInfo) _PCP_HIDDEN;
+extern __pmCred __htonpmCred(__pmCred) _PCP_HIDDEN;
+extern __pmCred __ntohpmCred(__pmCred) _PCP_HIDDEN;
+
+/* insitu swab for these */
+extern void __htonpmValueBlock(pmValueBlock * const) _PCP_HIDDEN;
+extern void __ntohpmValueBlock(pmValueBlock * const) _PCP_HIDDEN;
+extern void __htonf(char *) _PCP_HIDDEN; /* float */
+#define __ntohf(v) __htonf(v)
+#define __htond(v) __htonll(v) /* double */
+#define __ntohd(v) __ntohll(v)
+extern void __htonll(char *) _PCP_HIDDEN; /* 64bit int */
+#define __ntohll(v) __htonll(v)
+
+#endif /* HAVE_NETWORK_BYTEORDER */
+
+#ifdef PM_MULTI_THREAD
+#ifdef HAVE___THREAD
+/*
+ * C compiler is probably gcc and supports __thread declarations
+ */
+#define PM_TPD(x) x
+#else
+/*
+ * Roll-your-own Thread Private Data support
+ */
+extern pthread_key_t __pmTPDKey _PCP_HIDDEN;
+
+typedef struct {
+ int curcontext; /* current context */
+ char *derive_errmsg; /* derived metric parser error message */
+} __pmTPD;
+
+static inline __pmTPD *
+__pmTPDGet(void)
+{
+ return (__pmTPD *)pthread_getspecific(__pmTPDKey);
+}
+
+#define PM_TPD(x) __pmTPDGet()->x
+#endif
+#else /* !PM_MULTI_THREAD */
+/* No threads - just access global variables as-is */
+#define PM_TPD(x) x
+#endif
+
+#ifdef PM_MULTI_THREAD_DEBUG
+extern void __pmDebugLock(int, void *, const char *, int) _PCP_HIDDEN;
+extern int __pmIsContextLock(void *) _PCP_HIDDEN;
+extern int __pmIsChannelLock(void *) _PCP_HIDDEN;
+extern int __pmIsDeriveLock(void *) _PCP_HIDDEN;
+#endif
+
+/* AF_UNIX socket family internals */
+#define PM_HOST_SPEC_NPORTS_LOCAL (-1)
+#define PM_HOST_SPEC_NPORTS_UNIX (-2)
+extern const char *__pmPMCDLocalSocketDefault(void) _PCP_HIDDEN;
+extern void __pmCheckAcceptedAddress(__pmSockAddr *) _PCP_HIDDEN;
+
+#ifdef SOCKET_INTERNAL
+#ifdef HAVE_SECURE_SOCKETS
+#include <nss.h>
+#include <ssl.h>
+#include <nspr.h>
+#include <prerror.h>
+#include <private/pprio.h>
+#include <sasl.h>
+
+#define SECURE_SERVER_CERTIFICATE "PCP Collector certificate"
+#define SECURE_USERDB_DEFAULT_KEY "\n"
+
+/* internal NSS/NSPR/SSL/SASL implementation details */
+extern int __pmSecureSocketsError(int) _PCP_HIDDEN;
+#endif
+
+#if defined(HAVE_SYS_UN_H)
+#include <sys/un.h>
+#endif
+
+struct __pmSockAddr {
+ union {
+ struct sockaddr raw;
+ struct sockaddr_in inet;
+ struct sockaddr_in6 ipv6;
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ struct sockaddr_un local;
+#endif
+ } sockaddr;
+};
+
+typedef struct addrinfo __pmAddrInfo;
+
+struct __pmHostEnt {
+ char *name;
+ __pmAddrInfo *addresses;
+};
+#endif
+
+extern unsigned __pmFirstInetSubnetAddr(unsigned, int) _PCP_HIDDEN;
+extern unsigned __pmNextInetSubnetAddr(unsigned, int) _PCP_HIDDEN;
+extern unsigned char *__pmFirstIpv6SubnetAddr(unsigned char *, int maskBits) _PCP_HIDDEN;
+extern unsigned char *__pmNextIpv6SubnetAddr(unsigned char *, int maskBits) _PCP_HIDDEN;
+
+extern int __pmInitSecureSockets(void) _PCP_HIDDEN;
+extern int __pmInitCertificates(void) _PCP_HIDDEN;
+extern int __pmInitSocket(int, int) _PCP_HIDDEN;
+extern int __pmSocketReady(int, struct timeval *) _PCP_HIDDEN;
+extern void *__pmGetSecureSocket(int) _PCP_HIDDEN;
+extern void *__pmGetUserAuthData(int) _PCP_HIDDEN;
+extern int __pmSecureServerInit(void) _PCP_HIDDEN;
+extern int __pmSecureServerIPCFlags(int, int) _PCP_HIDDEN;
+extern int __pmSecureServerHasFeature(__pmServerFeature) _PCP_HIDDEN;
+extern int __pmSecureServerSetFeature(__pmServerFeature) _PCP_HIDDEN;
+extern int __pmSecureServerClearFeature(__pmServerFeature) _PCP_HIDDEN;
+
+extern int __pmShutdownLocal(void) _PCP_HIDDEN;
+extern int __pmShutdownCertificates(void) _PCP_HIDDEN;
+extern int __pmShutdownSecureSockets(void) _PCP_HIDDEN;
+
+#define SECURE_SERVER_SASL_SERVICE "PCP Collector"
+#define LIMIT_AUTH_PDU 2048 /* maximum size of a SASL transfer (in bytes) */
+#define LIMIT_CLIENT_CALLBACKS 8 /* maximum size of callback array */
+#define DEFAULT_SECURITY_STRENGTH 0 /* SASL security strength factor */
+
+typedef int (*sasl_callback_func)(void);
+extern int __pmInitAuthClients(void) _PCP_HIDDEN;
+extern int __pmInitAuthServer(void) _PCP_HIDDEN;
+
+/*
+ * Platform independent user/group account manipulation
+ */
+extern int __pmValidUserID(__pmUserID) _PCP_HIDDEN;
+extern int __pmValidGroupID(__pmGroupID) _PCP_HIDDEN;
+extern int __pmEqualUserIDs(__pmUserID, __pmUserID) _PCP_HIDDEN;
+extern int __pmEqualGroupIDs(__pmGroupID, __pmGroupID) _PCP_HIDDEN;
+extern void __pmUserIDFromString(const char *, __pmUserID *) _PCP_HIDDEN;
+extern void __pmGroupIDFromString(const char *, __pmGroupID *) _PCP_HIDDEN;
+extern char *__pmUserIDToString(__pmUserID, char *, size_t) _PCP_HIDDEN;
+extern char *__pmGroupIDToString(__pmGroupID, char *, size_t) _PCP_HIDDEN;
+extern int __pmUsernameToID(const char *, __pmUserID *) _PCP_HIDDEN;
+extern int __pmGroupnameToID(const char *, __pmGroupID *) _PCP_HIDDEN;
+extern char *__pmUsernameFromID(__pmUserID, char *, size_t) _PCP_HIDDEN;
+extern char *__pmGroupnameFromID(__pmGroupID, char *, size_t) _PCP_HIDDEN;
+extern char *__pmHomedirFromID(__pmUserID, char *, size_t) _PCP_HIDDEN;
+extern int __pmUsersGroupIDs(const char *, __pmGroupID **, unsigned int *) _PCP_HIDDEN;
+extern int __pmGroupsUserIDs(const char *, __pmUserID **, unsigned int *) _PCP_HIDDEN;
+extern int __pmGetUserIdentity(const char *, __pmUserID *, __pmGroupID *, int) _PCP_HIDDEN;
+
+extern int __pmStringListAdd(char *, int, char ***) _PCP_HIDDEN;
+extern char *__pmStringListFind(const char *, int, char **) _PCP_HIDDEN;
+
+/*
+ * Representations of server presence on the network.
+ */
+typedef struct __pmServerAvahiPresence __pmServerAvahiPresence;
+
+struct __pmServerPresence {
+ /* Common data. */
+ char *serviceSpec;
+ int port;
+ /* API-specific data. */
+ __pmServerAvahiPresence *avahi;
+};
+
+/* Service discovery internals. */
+typedef struct {
+ const char *spec;
+ __pmSockAddr *address;
+ const char *protocol;
+} __pmServiceInfo;
+
+typedef struct {
+ const volatile unsigned *flags; /* Service discovery flags */
+ struct timeval timeout; /* Global timeout period */
+ volatile int timedOut; /* Global timeout occurred */
+ int resolve; /* Resolve discovered addresses */
+} __pmServiceDiscoveryOptions;
+
+extern int __pmAddDiscoveredService(__pmServiceInfo *,
+ const __pmServiceDiscoveryOptions *,
+ int,
+ char ***) _PCP_HIDDEN;
+extern char *__pmServiceDiscoveryParseTimeout (const char *s,
+ struct timeval *timeout)
+ _PCP_HIDDEN;
+
+extern int __pmServiceAddPorts(const char *, int **, int) _PCP_HIDDEN;
+extern int __pmPMCDAddPorts(int **, int) _PCP_HIDDEN;
+extern int __pmProxyAddPorts(int **, int) _PCP_HIDDEN;
+extern int __pmWebdAddPorts(int **, int) _PCP_HIDDEN;
+extern int __pmAddPorts(const char *, int **, int) _PCP_HIDDEN;
+
+#endif /* _INTERNAL_H */
diff --git a/src/libpcp/src/interp.c b/src/libpcp/src/interp.c
new file mode 100644
index 0000000..ec49387
--- /dev/null
+++ b/src/libpcp/src/interp.c
@@ -0,0 +1,1714 @@
+/*
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes:
+ *
+ * nr[] and nr_cache[] are diagnostic counters that are maintained with
+ * non-atomic updates ... we've decided that it is acceptable for their
+ * values to be subject to possible (but unlikely) missed updates
+ */
+
+/*
+ * Note: _FORTIFY_SOURCE cannot be set here because the calls to memcpy()
+ * with vp->vbuf as the destination raise a warning that is not
+ * correct as the allocation for a pmValueBlock always extends
+ * vbuf[] to be the correct size, not the [1] as per the declaration
+ * in pmapi.h
+ */
+#ifdef _FORTIFY_SOURCE
+#undef _FORTIFY_SOURCE
+#define _FORTIFY_SOURCE 0
+#endif
+
+#include <limits.h>
+#include <inttypes.h>
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+
+#define UPD_MARK_NONE 0
+#define UPD_MARK_FORW 1
+#define UPD_MARK_BACK 2
+
+#if defined(HAVE_CONST_LONGLONG)
+#define SIGN_64_MASK 0x8000000000000000LL
+#else
+#define SIGN_64_MASK 0x8000000000000000
+#endif
+
+typedef union { /* value from pmResult */
+ pmValueBlock *pval;
+ int lval;
+} value;
+
+/*
+ * state values for s_prior and s_next
+ */
+#define S_UNDEFINED 0 /* no searching done yet */
+#define S_NOTFOUND 1 /* searched to end or start of archive and
+ did not find a value or a <mark> */
+#define S_MARK 2 /* searched and found <mark> before value
+ at t_prior or t_mext */
+#define S_VALUE 3 /* searched and found value at t_prior or
+ t_next */
+
+static const char *statestr[] = { " <undefined>", " <notfound>", " <mark>", "" };
+
+
+typedef struct instcntl { /* metric-instance control */
+ struct instcntl *next; /* next for this metric control */
+ struct instcntl *want; /* ones of interest */
+ struct instcntl *unbound; /* not yet bound above [or below] */
+ int search; /* looking for found this one? */
+ int inst; /* instance identifier */
+ int inresult; /* will be in this result */
+ double t_prior;
+ int s_prior; /* state at t_prior */
+ value v_prior;
+ double t_next;
+ int s_next; /* state at t_next */
+ value v_next;
+ double t_first; /* no records before this */
+ double t_last; /* no records after this */
+ struct pmidcntl *metric; /* back to metric control */
+} instcntl_t;
+
+typedef struct pmidcntl { /* metric control */
+ pmDesc desc;
+ int valfmt; /* used to build result */
+ int numval; /* number of instances in this result */
+ struct instcntl *first; /* first metric-instace control */
+} pmidcntl_t;
+
+typedef struct {
+ pmResult *rp; /* cached pmResult from __pmLogRead */
+ int sts; /* from __pmLogRead */
+ FILE *mfp; /* log stream */
+ int vol; /* log volume */
+ long head_posn; /* posn in file before forwards __pmLogRead */
+ long tail_posn; /* posn in file after forwards __pmLogRead */
+ int mode; /* PM_MODE_FORW or PM_MODE_BACK */
+ int used; /* used count for LFU replacement */
+} cache_t;
+
+#define NUMCACHE 4
+
+/*
+ * diagnostic counters ... indexed by PM_MODE_FORW (2) and
+ * PM_MODE_BACK (3), hence 4 elts for cached and non-cached reads
+ */
+static long nr_cache[PM_MODE_BACK+1];
+static long nr[PM_MODE_BACK+1];
+
+/*
+ * called with the context lock held
+ */
+static int
+cache_read(__pmArchCtl *acp, int mode, pmResult **rp)
+{
+ long posn;
+ cache_t *cp;
+ cache_t *lfup;
+ cache_t *cache;
+ int save_curvol;
+
+ if (acp->ac_vol == acp->ac_log->l_curvol) {
+ posn = ftell(acp->ac_log->l_mfp);
+ assert(posn >= 0);
+ }
+ else
+ posn = 0;
+
+ if (acp->ac_cache == NULL) {
+ /* cache initialization */
+ acp->ac_cache = cache = (cache_t *)malloc(NUMCACHE*sizeof(cache_t));
+ // TODO error check
+ for (cp = cache; cp < &cache[NUMCACHE]; cp++) {
+ cp->rp = NULL;
+ cp->mfp = NULL;
+ }
+ acp->ac_cache_idx = 0;
+ }
+ else
+ cache = acp->ac_cache;
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) {
+ fprintf(stderr, "cache_read: fd=%d mode=%s vol=%d (curvol=%d) %s_posn=%ld ",
+ fileno(acp->ac_log->l_mfp),
+ mode == PM_MODE_FORW ? "forw" : "back",
+ acp->ac_vol, acp->ac_log->l_curvol,
+ mode == PM_MODE_FORW ? "head" : "tail",
+ (long)posn);
+ }
+#endif
+
+ acp->ac_cache_idx = (acp->ac_cache_idx + 1) % NUMCACHE;
+ lfup = &cache[acp->ac_cache_idx];
+ for (cp = cache; cp < &cache[NUMCACHE]; cp++) {
+ if (cp->mfp == acp->ac_log->l_mfp && cp->vol == acp->ac_vol &&
+ ((mode == PM_MODE_FORW && cp->head_posn == posn) ||
+ (mode == PM_MODE_BACK && cp->tail_posn == posn)) &&
+ cp->rp != NULL) {
+ int sts;
+ *rp = cp->rp;
+ cp->used++;
+ if (mode == PM_MODE_FORW)
+ fseek(acp->ac_log->l_mfp, cp->tail_posn, SEEK_SET);
+ else
+ fseek(acp->ac_log->l_mfp, cp->head_posn, SEEK_SET);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) {
+ __pmTimeval tmp;
+ double t_this;
+ tmp.tv_sec = (__int32_t)cp->rp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)cp->rp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &acp->ac_log->l_label.ill_start);
+ fprintf(stderr, "hit cache[%d] t=%.6f\n",
+ (int)(cp - cache), t_this);
+ nr_cache[mode]++;
+ }
+#endif
+ sts = cp->sts;
+ return sts;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP))
+ fprintf(stderr, "miss\n");
+ nr[mode]++;
+#endif
+
+ if (lfup->rp != NULL)
+ pmFreeResult(lfup->rp);
+
+ save_curvol = acp->ac_log->l_curvol;
+
+ lfup->sts = __pmLogRead(acp->ac_log, mode, NULL, &lfup->rp, PMLOGREAD_NEXT);
+ if (lfup->sts < 0)
+ lfup->rp = NULL;
+ *rp = lfup->rp;
+
+ if (posn == 0 || save_curvol != acp->ac_log->l_curvol) {
+ /*
+ * vol switch since last time, or vol switch in __pmLogRead() ...
+ * new vol, stdio stream and we don't know where we started from
+ * ... don't cache
+ */
+ lfup->mfp = NULL;
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP))
+ fprintf(stderr, "cache_read: reload vol switch, mark cache[%d] unused\n",
+ (int)(lfup - cache));
+#endif
+ }
+ else {
+ lfup->mode = mode;
+ lfup->vol = acp->ac_vol;
+ lfup->mfp = acp->ac_log->l_mfp;
+ lfup->used = 1;
+ if (mode == PM_MODE_FORW) {
+ lfup->head_posn = posn;
+ lfup->tail_posn = ftell(acp->ac_log->l_mfp);
+ assert(lfup->tail_posn >= 0);
+ }
+ else {
+ lfup->tail_posn = posn;
+ lfup->head_posn = ftell(acp->ac_log->l_mfp);
+ assert(lfup->head_posn >= 0);
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) {
+ fprintf(stderr, "cache_read: reload cache[%d] vol=%d (curvol=%d) head=%ld tail=%ld ",
+ (int)(lfup - cache), lfup->vol, acp->ac_log->l_curvol,
+ (long)lfup->head_posn, (long)lfup->tail_posn);
+ if (lfup->sts == 0)
+ fprintf(stderr, "sts=%d\n", lfup->sts);
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "sts=%s\n", pmErrStr_r(lfup->sts, errmsg, sizeof(errmsg)));
+ }
+ }
+#endif
+ }
+
+ return lfup->sts;
+}
+
+void
+__pmLogCacheClear(FILE *mfp)
+{
+ /* retired ... functionality moved to __pmFreeInterpData() */
+ return;
+}
+
+#ifdef PCP_DEBUG
+/*
+ * prior == 1 for ?_prior fields, else use ?_next fields
+ */
+static void
+dumpval(FILE *f, int type, int valfmt, int prior, instcntl_t *icp)
+{
+ int mark;
+ value *vp;
+ if (prior) {
+ if (icp->t_prior == -1) {
+ fprintf(stderr, " <undefined>");
+ return;
+ }
+ mark = icp->s_prior == S_MARK;
+ vp = &icp->v_prior;
+ }
+ else {
+ if (icp->t_next == -1) {
+ fprintf(stderr, " <undefined>");
+ return;
+ }
+ mark = icp->s_next == S_MARK;
+ vp = &icp->v_next;
+ }
+ if (mark) {
+ fprintf(f, " <mark>");
+ return;
+ }
+ if (type == PM_TYPE_32 || type == PM_TYPE_U32)
+ fprintf(f, " v=%d", vp->lval);
+ else if (type == PM_TYPE_FLOAT && valfmt == PM_VAL_INSITU) {
+ float tmp;
+ memcpy((void *)&tmp, (void *)&vp->lval, sizeof(tmp));
+ fprintf(f, " v=%f", (double)tmp);
+ }
+ else if (type == PM_TYPE_64) {
+ __int64_t tmp;
+ memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp));
+ fprintf(f, " v=%"PRIi64, tmp);
+ }
+ else if (type == PM_TYPE_U64) {
+ __uint64_t tmp;
+ memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp));
+ fprintf(f, " v=%"PRIu64, tmp);
+ }
+ else if (type == PM_TYPE_FLOAT) {
+ float tmp;
+ memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp));
+ fprintf(f, " v=%f", (double)tmp);
+ }
+ else if (type == PM_TYPE_DOUBLE) {
+ double tmp;
+ memcpy((void *)&tmp, (void *)vp->pval->vbuf, sizeof(tmp));
+ fprintf(f, " v=%f", tmp);
+ }
+ else
+ fprintf(f, "v=??? (lval=%d)", vp->lval);
+}
+#endif
+
+static void
+update_bounds(__pmContext *ctxp, double t_req, pmResult *logrp, int do_mark, int *done_prior, int *done_next)
+{
+ /*
+ * for every metric in the result from the log
+ * for every instance in the result from the log
+ * if we have ever asked for this metric and instance, update the
+ * range bounds, if necessary
+ */
+ int k;
+ int i;
+ __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc;
+ __pmHashNode *hp;
+ pmidcntl_t *pcp;
+ instcntl_t *icp;
+ double t_this;
+ __pmTimeval tmp;
+ int changed;
+
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+
+ if (logrp->numpmid == 0 && do_mark != UPD_MARK_NONE) {
+ /* mark record, discontinuity in log */
+ for (icp = (instcntl_t *)ctxp->c_archctl->ac_want; icp != NULL; icp = icp->want) {
+ if (t_this <= t_req &&
+ (t_this >= icp->t_prior || icp->t_prior > t_req)) {
+ /* <mark> is closer than best lower bound to date */
+ icp->t_prior = t_this;
+ icp->s_prior = S_MARK;
+ if (icp->metric->valfmt != PM_VAL_INSITU) {
+ if (icp->v_prior.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_prior.pval);
+ icp->v_prior.pval = NULL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "pmid %s inst %d <mark> t_prior=%.6f t_first=%.6f t_last=%.6f\n",
+ pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_prior, icp->t_first, icp->t_last);
+ }
+#endif
+ if (icp->search && done_prior != NULL) {
+ icp->search = 0;
+ (*done_prior)++;
+ }
+ }
+ if (t_this >= t_req &&
+ ((t_this <= icp->t_next || icp->t_next < 0) ||
+ icp->t_next < t_req)) {
+ /* <mark> is closer than best upper bound to date */
+ icp->t_next = t_this;
+ icp->s_next = S_MARK;
+ if (icp->metric->valfmt != PM_VAL_INSITU) {
+ if (icp->v_next.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_next.pval);
+ icp->v_next.pval = NULL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "pmid %s inst %d <mark> t_next=%.6f t_first=%.6f t_last=%.6f\n",
+ pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_next, icp->t_first, icp->t_last);
+ }
+#endif
+ if (icp->search && done_next != NULL) {
+ icp->search = 0;
+ (*done_next)++;
+ }
+ }
+ }
+ return;
+ }
+
+ changed = 0;
+ for (k = 0; k < logrp->numpmid; k++) {
+ hp = __pmHashSearch((int)logrp->vset[k]->pmid, hcp);
+ if (hp == NULL)
+ continue;
+ pcp = (pmidcntl_t *)hp->data;
+ if (pcp->valfmt == -1 && logrp->vset[k]->numval > 0)
+ pcp->valfmt = logrp->vset[k]->valfmt;
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ for (i = 0; i < logrp->vset[k]->numval; i++) {
+ if (logrp->vset[k]->vlist[i].inst == icp->inst ||
+ icp->inst == PM_IN_NULL) {
+ /* matched on instance */
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ char strbuf[20];
+ fprintf(stderr, "update: match pmid %s inst %d t_this=%.6f t_prior=%.6f t_next=%.6f t_first=%.6f t_last=%.6f\n",
+ pmIDStr_r(logrp->vset[k]->pmid, strbuf, sizeof(strbuf)), icp->inst,
+ t_this, icp->t_prior, icp->t_next,
+ icp->t_first, icp->t_last);
+ }
+#endif
+ if (t_this <= t_req &&
+ (icp->t_prior > t_req || t_this >= icp->t_prior)) {
+ /*
+ * at or before the requested time, and this is the
+ * closest-to-date lower bound
+ */
+ changed = 1;
+ if (icp->t_prior < icp->t_next && icp->t_prior >= t_req) {
+ /* shuffle prior to next */
+ icp->t_next = icp->t_prior;
+ if (pcp->valfmt == PM_VAL_INSITU)
+ icp->v_next.lval = icp->v_prior.lval;
+ else {
+ if (icp->v_next.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_next.pval);
+ icp->v_next.pval = icp->v_prior.pval;
+ }
+ }
+ icp->t_prior = t_this;
+ icp->s_prior = S_VALUE;
+ if (pcp->valfmt == PM_VAL_INSITU)
+ icp->v_prior.lval = logrp->vset[k]->vlist[i].value.lval;
+ else {
+ if (icp->v_prior.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_prior.pval);
+ icp->v_prior.pval = logrp->vset[k]->vlist[i].value.pval;
+ __pmPinPDUBuf((void *)icp->v_prior.pval);
+ }
+ if (icp->search && done_prior != NULL) {
+ /* one we were looking for */
+ changed |= 2;
+ icp->search = 0;
+ (*done_prior)++;
+ }
+ }
+ if (t_this >= t_req &&
+ (icp->t_next < t_req || t_this <= icp->t_next)) {
+ /*
+ * at or after the requested time, and this is the
+ * closest-to-date upper bound
+ */
+ changed |= 1;
+ if (icp->t_prior < icp->t_next && icp->t_next <= t_req) {
+ /* shuffle next to prior */
+ icp->t_prior = icp->t_next;
+ icp->s_prior = icp->s_next;
+ if (pcp->valfmt == PM_VAL_INSITU)
+ icp->v_prior.lval = icp->v_next.lval;
+ else {
+ if (icp->v_prior.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_prior.pval);
+ icp->v_prior.pval = icp->v_next.pval;
+ }
+ }
+ icp->t_next = t_this;
+ icp->s_next = S_VALUE;
+ if (pcp->valfmt == PM_VAL_INSITU)
+ icp->v_next.lval = logrp->vset[k]->vlist[i].value.lval;
+ else {
+ if (icp->v_next.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_next.pval);
+ icp->v_next.pval = logrp->vset[k]->vlist[i].value.pval;
+ __pmPinPDUBuf((void *)icp->v_next.pval);
+ }
+ if (icp->search && done_next != NULL) {
+ /* one we were looking for */
+ changed |= 2;
+ icp->search = 0;
+ (*done_next)++;
+ }
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_INTERP) && changed) {
+ char strbuf[20];
+ fprintf(stderr, "update%s pmid %s inst %d prior: t=%.6f",
+ changed & 2 ? "+search" : "",
+ pmIDStr_r(logrp->vset[k]->pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_prior);
+ dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 1, icp);
+ fprintf(stderr, " next: t=%.6f", icp->t_next);
+ dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 0, icp);
+ fprintf(stderr, " t_first=%.6f t_last=%.6f\n",
+ icp->t_first, icp->t_last);
+ }
+#endif
+ goto next_inst;
+ }
+ }
+next_inst:
+ ;
+ }
+ }
+
+ return;
+}
+
+static void
+do_roll(__pmContext *ctxp, double t_req)
+{
+ pmResult *logrp;
+ __pmTimeval tmp;
+ double t_this;
+
+ /*
+ * now roll forwards in the direction of log reading
+ * to make sure we are up to t_req
+ */
+ if (ctxp->c_delta > 0) {
+ while (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) >= 0) {
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (t_this > t_req)
+ break;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP)
+ fprintf(stderr, "roll forw to t=%.6f%s\n",
+ t_this, logrp->numpmid == 0 ? " <mark>" : "");
+#endif
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_FORW, NULL, NULL);
+ }
+ }
+ else {
+ while (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) >= 0) {
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (t_this < t_req)
+ break;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP)
+ fprintf(stderr, "roll back to t=%.6f%s\n",
+ t_this, logrp->numpmid == 0 ? " <mark>" : "");
+#endif
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_BACK, NULL, NULL);
+ }
+ }
+}
+
+#define pmXTBdeltaToTimeval(d, m, t) { \
+ (t)->tv_sec = 0; \
+ (t)->tv_usec = (long)0; \
+ switch(PM_XTB_GET(m)) { \
+ case PM_TIME_NSEC: (t)->tv_usec = (long)((d) / 1000); break; \
+ case PM_TIME_USEC: (t)->tv_usec = (long)(d); break; \
+ case PM_TIME_MSEC: (t)->tv_sec = (d) / 1000; (t)->tv_usec = (long)(1000 * ((d) % 1000)); break; \
+ case PM_TIME_SEC: (t)->tv_sec = (d); break; \
+ case PM_TIME_MIN: (t)->tv_sec = (d) * 60; break; \
+ case PM_TIME_HOUR: (t)->tv_sec = (d) * 360; break; \
+ default: (t)->tv_sec = (d) / 1000; (t)->tv_usec = (long)(1000 * ((d) % 1000)); break; \
+ } \
+}
+
+int
+__pmLogFetchInterp(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result)
+{
+ int i;
+ int j;
+ int sts;
+ double t_req;
+ double t_this;
+ pmResult *rp;
+ pmResult *logrp;
+ __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc;
+ __pmHashNode *hp;
+ pmidcntl_t *pcp = NULL; /* initialize to pander to gcc */
+ instcntl_t *icp;
+ int back = 0;
+ int forw = 0;
+ int done;
+ int done_roll;
+ static int dowrap = -1;
+ __pmTimeval tmp;
+ struct timeval delta_tv;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (dowrap == -1) {
+ /* PCP_COUNTER_WRAP in environment enables "counter wrap" logic */
+ if (getenv("PCP_COUNTER_WRAP") == NULL)
+ dowrap = 0;
+ else
+ dowrap = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ t_req = __pmTimevalSub(&ctxp->c_origin, &ctxp->c_archctl->ac_log->l_label.ill_start);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ fprintf(stderr, "__pmLogFetchInterp @ ");
+ __pmPrintTimeval(stderr, &ctxp->c_origin);
+ fprintf(stderr, " t_req=%.6f curvol=%d posn=%ld (vol=%d) serial=%d\n",
+ t_req, ctxp->c_archctl->ac_log->l_curvol,
+ (long)ctxp->c_archctl->ac_offset, ctxp->c_archctl->ac_vol,
+ ctxp->c_archctl->ac_serial);
+ nr_cache[PM_MODE_FORW] = nr[PM_MODE_FORW] = 0;
+ nr_cache[PM_MODE_BACK] = nr[PM_MODE_BACK] = 0;
+ }
+#endif
+
+ /*
+ * the 0.001 is magic slop for 1 msec, which is about as accurate
+ * as we can expect any of this timing stuff to be ...
+ */
+ if (t_req < -0.001) {
+ sts = PM_ERR_EOL;
+ goto all_done;
+ }
+
+ if (t_req > ctxp->c_archctl->ac_end + 0.001) {
+ struct timeval end;
+ __pmTimeval tmp;
+
+ /*
+ * Past end of archive ... see if it has grown since we last looked.
+ */
+ if (pmGetArchiveEnd(&end) >= 0) {
+ tmp.tv_sec = (__int32_t)end.tv_sec;
+ tmp.tv_usec = (__int32_t)end.tv_usec;
+ ctxp->c_archctl->ac_end = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ }
+ if (t_req > ctxp->c_archctl->ac_end) {
+ sts = PM_ERR_EOL;
+ goto all_done;
+ }
+ }
+
+ if ((rp = (pmResult *)malloc(sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *))) == NULL)
+ return -oserror();
+
+ rp->timestamp.tv_sec = ctxp->c_origin.tv_sec;
+ rp->timestamp.tv_usec = ctxp->c_origin.tv_usec;
+ rp->numpmid = numpmid;
+
+ /* zeroth pass ... clear search and inresult flags */
+ for (j = 0; j < hcp->hsize; j++) {
+ for (hp = hcp->hash[j]; hp != NULL; hp = hp->next) {
+ pcp = (pmidcntl_t *)hp->data;
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ icp->search = icp->inresult = 0;
+ icp->unbound = icp->want = NULL;
+ }
+ }
+ }
+
+ /*
+ * first pass ... scan all metrics, establish which ones are in
+ * the log, and which instances are being requested ... also build
+ * the skeletal pmResult
+ */
+ ctxp->c_archctl->ac_want = NULL;
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == PM_ID_NULL)
+ continue;
+ hp = __pmHashSearch((int)pmidlist[j], hcp);
+ if (hp == NULL) {
+ /* first time we've been asked for this one in this context */
+ if ((pcp = (pmidcntl_t *)malloc(sizeof(pmidcntl_t))) == NULL) {
+ __pmNoMem("__pmLogFetchInterp.pmidcntl_t", sizeof(pmidcntl_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ pcp->valfmt = -1;
+ pcp->first = NULL;
+ sts = __pmHashAdd((int)pmidlist[j], (void *)pcp, hcp);
+ if (sts < 0) {
+ rp->numpmid = j;
+ pmFreeResult(rp);
+ free(pcp);
+ return sts;
+ }
+ sts = __pmLogLookupDesc(ctxp->c_archctl->ac_log, pmidlist[j], &pcp->desc);
+ if (sts < 0)
+ /* not in the archive log */
+ pcp->desc.type = -1;
+ else {
+ /* enumerate all the instances from the domain underneath */
+ int *instlist = NULL;
+ char **namelist = NULL;
+ instcntl_t *lcp;
+ if (pcp->desc.indom == PM_INDOM_NULL) {
+ sts = 1;
+ if ((instlist = (int *)malloc(sizeof(int))) == NULL) {
+ __pmNoMem("__pmLogFetchInterp.instlist", sizeof(int), PM_FATAL_ERR);
+ }
+ instlist[0] = PM_IN_NULL;
+ }
+ else {
+ sts = pmGetInDomArchive(pcp->desc.indom, &instlist, &namelist);
+ }
+ lcp = NULL;
+ for (i = 0; i < sts; i++) {
+ if ((icp = (instcntl_t *)malloc(sizeof(instcntl_t))) == NULL) {
+ __pmNoMem("__pmLogFetchInterp.instcntl_t", sizeof(instcntl_t), PM_FATAL_ERR);
+ }
+ if (lcp)
+ lcp->next = icp;
+ else
+ pcp->first = icp;
+ lcp = icp;
+ icp->metric = pcp;
+ icp->inresult = icp->search = 0;
+ icp->next = icp->want = icp->unbound = NULL;
+ icp->inst = instlist[i];
+ icp->t_prior = icp->t_next = icp->t_first = icp->t_last = -1;
+ icp->s_prior = icp->s_next = S_MARK;
+ icp->v_prior.pval = icp->v_next.pval = NULL;
+ }
+ if (instlist != NULL)
+ free(instlist);
+ if (namelist != NULL)
+ free(namelist);
+ }
+ }
+ else
+ /* seen this one before */
+ pcp = (pmidcntl_t *)hp->data;
+
+ pcp->numval = 0;
+ if (pcp->desc.type == -1) {
+ pcp->numval = PM_ERR_PMID_LOG;
+ }
+ else if (pcp->desc.indom != PM_INDOM_NULL) {
+ /* use the profile to filter the instances to be returned */
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (__pmInProfile(pcp->desc.indom, ctxp->c_instprof, icp->inst)) {
+ icp->inresult = 1;
+ icp->want = (instcntl_t *)ctxp->c_archctl->ac_want;
+ ctxp->c_archctl->ac_want = icp;
+ pcp->numval++;
+ }
+ else
+ icp->inresult = 0;
+ }
+ }
+ else {
+ pcp->first->inresult = 1;
+ pcp->first->want = (instcntl_t *)ctxp->c_archctl->ac_want;
+ ctxp->c_archctl->ac_want = pcp->first;
+ pcp->numval = 1;
+ }
+ }
+
+ if (ctxp->c_archctl->ac_serial == 0) {
+ /* need gross positioning from temporal index */
+ __pmLogSetTime(ctxp);
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+
+ /*
+ * and now fine-tuning ...
+ * back-up (relative to the direction we are reading the log)
+ * to make sure
+ */
+ if (ctxp->c_delta > 0) {
+ while (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) >= 0) {
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (t_this <= t_req) {
+ break;
+ }
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_NONE, NULL, NULL);
+ }
+ }
+ else {
+ while (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) >= 0) {
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (t_this > t_req) {
+ break;
+ }
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_NONE, NULL, NULL);
+ }
+ }
+ ctxp->c_archctl->ac_serial = 1;
+ }
+
+ /* get to the last remembered place */
+ __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol);
+ fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET);
+
+ /*
+ * optimization to supress roll forwards unless really needed ...
+ * if the sample interval is much shorter than the time between log
+ * records, then do not roll forwards unless some scanning is
+ * required ... and if scanning is required in the "forwards"
+ * direction, no need to roll forwards
+ */
+ done_roll = 0;
+
+ /*
+ * second pass ... see which metrics are not currently bounded below
+ */
+ ctxp->c_archctl->ac_unbound = NULL;
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == PM_ID_NULL)
+ continue;
+ hp = __pmHashSearch((int)pmidlist[j], hcp);
+ assert(hp != NULL);
+ pcp = (pmidcntl_t *)hp->data;
+ if (pcp->numval > 0) {
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (!icp->inresult)
+ continue;
+ if (icp->t_first >= 0 && t_req < icp->t_first)
+ /* before earliest, don't bother */
+ continue;
+retry_back:
+ /*
+ * At this stage there _may_ be a value earlier in the
+ * archive of interest ...
+ * t_prior = -1 => have not explored in this direction,
+ * so need to go back
+ * t_prior > t_req => need to push t_prior to be <= t_req
+ * if possible, so go back
+ * t_next is valid and a mark and t_next > t_req => need
+ * to search back also
+ */
+ if (icp->t_prior < 0 || icp->t_prior > t_req ||
+ (icp->t_next >= 0 && icp->s_next == S_MARK && icp->t_next > t_req)) {
+ if (back == 0 && !done_roll) {
+ done_roll = 1;
+ if (ctxp->c_delta > 0) {
+ /* forwards before scanning back */
+ do_roll(ctxp, t_req);
+ goto retry_back;
+ }
+ }
+ back++;
+ icp->search = 1;
+ icp->unbound = (instcntl_t *)ctxp->c_archctl->ac_unbound;
+ ctxp->c_archctl->ac_unbound = icp;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "search back for inst %d and pmid %s (t_first=%.6f t_prior=%.6f%s t_next=%.6f%s t_last=%.6f)\n",
+ icp->inst, pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->t_first,
+ icp->t_prior, statestr[icp->s_prior],
+ icp->t_next, statestr[icp->s_next],
+ icp->t_last);
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ if (back) {
+ /*
+ * at least one metric requires a bound from earlier in the log ...
+ * position ourselves, ... and search
+ */
+ __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol);
+ fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET);
+ done = 0;
+
+ while (done < back) {
+ if (cache_read(ctxp->c_archctl, PM_MODE_BACK, &logrp) < 0) {
+ /* ran into start of log */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ fprintf(stderr, "Start of Log, %d metric-inst not found\n",
+ back - done);
+ }
+#endif
+ break;
+ }
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (ctxp->c_delta < 0 && t_this >= t_req) {
+ /* going backwards, and not up to t_req yet */
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ }
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_BACK, &done, NULL);
+
+ /*
+ * forget about those that can never be found from here
+ * in this direction
+ */
+ for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) {
+ if (icp->search && t_this <= icp->t_first) {
+ icp->search = 0;
+ done++;
+ }
+ }
+ }
+ /* end of search, trim t_first as required */
+ for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) {
+ if ((icp->t_prior == -1 || icp->t_prior > t_req) &&
+ icp->t_first < t_req) {
+ icp->t_first = t_req;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "pmid %s inst %d no values before t_first=%.6f\n",
+ pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_first);
+ }
+#endif
+ }
+ icp->search = 0;
+ }
+ }
+
+ /*
+ * third pass ... see which metrics are not currently bounded above
+ */
+ ctxp->c_archctl->ac_unbound = NULL;
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == PM_ID_NULL)
+ continue;
+ hp = __pmHashSearch((int)pmidlist[j], hcp);
+ assert(hp != NULL);
+ pcp = (pmidcntl_t *)hp->data;
+ if (pcp->numval > 0) {
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (!icp->inresult)
+ continue;
+ if (icp->t_last >= 0 && t_req > icp->t_last)
+ /* after latest, don't bother */
+ continue;
+retry_forw:
+ /*
+ * At this stage there _may_ be a value later in the
+ * archive of interest ...
+ * t_next = -1 => have not explored in this direction,
+ * so need to go forward
+ * t_next < t_req => need to push t_next to be >= t_req
+ * if possible, so go forward
+ * t_prior is valid and a mark and t_prior < t_req => need
+ * to search forward also
+ */
+ if (icp->t_next < 0 || icp->t_next < t_req ||
+ (icp->t_prior >= 0 && icp->s_prior == S_MARK && icp->t_prior < t_req)) {
+ if (forw == 0 && !done_roll) {
+ done_roll = 1;
+ if (ctxp->c_delta < 0) {
+ /* backwards before scanning forwards */
+ do_roll(ctxp, t_req);
+ goto retry_forw;
+ }
+ }
+ forw++;
+ icp->search = 1;
+ icp->unbound = (instcntl_t *)ctxp->c_archctl->ac_unbound;
+ ctxp->c_archctl->ac_unbound = icp;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "search forw for inst %d and pmid %s (t_first=%.6f t_prior=%.6f%s t_next=%.6f%s t_last=%.6f)\n",
+ icp->inst, pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->t_first,
+ icp->t_prior, statestr[icp->s_prior],
+ icp->t_next, statestr[icp->s_next],
+ icp->t_last);
+ }
+#endif
+ }
+ }
+ }
+ }
+
+ if (forw) {
+ /*
+ * at least one metric requires a bound from later in the log ...
+ * position ourselves ... and search
+ */
+ __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol);
+ fseek(ctxp->c_archctl->ac_log->l_mfp, ctxp->c_archctl->ac_offset, SEEK_SET);
+ done = 0;
+
+ while (done < forw) {
+ if (cache_read(ctxp->c_archctl, PM_MODE_FORW, &logrp) < 0) {
+ /* ran into end of log */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ fprintf(stderr, "End of Log, %d metric-inst not found\n",
+ forw - done);
+ }
+#endif
+ break;
+ }
+ tmp.tv_sec = (__int32_t)logrp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)logrp->timestamp.tv_usec;
+ t_this = __pmTimevalSub(&tmp, &ctxp->c_archctl->ac_log->l_label.ill_start);
+ if (ctxp->c_delta > 0 && t_this <= t_req) {
+ /* going forwards, and not up to t_req yet */
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+ }
+ update_bounds(ctxp, t_req, logrp, UPD_MARK_FORW, NULL, &done);
+
+ /*
+ * forget about those that can never be found from here
+ * in this direction
+ */
+ for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) {
+ if (icp->search && icp->t_last >= 0 && t_this >= icp->t_last) {
+ icp->search = 0;
+ done++;
+ }
+ }
+ }
+ /* end of search, trim t_last as required */
+ for (icp = (instcntl_t *)ctxp->c_archctl->ac_unbound; icp != NULL; icp = icp->unbound) {
+ if (icp->t_next < t_req &&
+ (icp->t_last < 0 || t_req < icp->t_last)) {
+ icp->t_last = t_req;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ char strbuf[20];
+ fprintf(stderr, "pmid %s inst %d no values after t_last=%.6f\n",
+ pmIDStr_r(icp->metric->desc.pmid, strbuf, sizeof(strbuf)), icp->inst, icp->t_last);
+ }
+#endif
+ }
+ icp->search = 0;
+ }
+ }
+
+ /*
+ * check to see how many qualifying values there are really going to be
+ */
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == PM_ID_NULL)
+ continue;
+ hp = __pmHashSearch((int)pmidlist[j], hcp);
+ assert(hp != NULL);
+ pcp = (pmidcntl_t *)hp->data;
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (!icp->inresult)
+ continue;
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->s_prior == S_MARK || icp->t_prior == -1 ||
+ icp->t_prior > t_req) {
+ /* no earlier value, so no value */
+ pcp->numval--;
+ icp->inresult = 0;
+ }
+ }
+ else {
+ /* assume COUNTER or INSTANT */
+ if (icp->s_prior == S_MARK || icp->t_prior == -1 ||
+ icp->t_prior > t_req ||
+ icp->s_next == S_MARK || icp->t_next == -1 || icp->t_next < t_req) {
+ /* in mid-range, and no bound, so no value */
+ pcp->numval--;
+ icp->inresult = 0;
+ }
+ else if (pcp->desc.sem == PM_SEM_COUNTER) {
+ /*
+ * for counters, has to be arithmetic also,
+ * else cannot interpolate ...
+ */
+ if (pcp->desc.type != PM_TYPE_32 &&
+ pcp->desc.type != PM_TYPE_U32 &&
+ pcp->desc.type != PM_TYPE_64 &&
+ pcp->desc.type != PM_TYPE_U64 &&
+ pcp->desc.type != PM_TYPE_FLOAT &&
+ pcp->desc.type != PM_TYPE_DOUBLE)
+ pcp->numval = PM_ERR_TYPE;
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == PM_ID_NULL) {
+ rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+ }
+ else {
+ hp = __pmHashSearch((int)pmidlist[j], hcp);
+ assert(hp != NULL);
+ pcp = (pmidcntl_t *)hp->data;
+
+ if (pcp->numval >= 1)
+ rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ (pcp->numval - 1)*sizeof(pmValue));
+ else
+ rp->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+ }
+
+ if (rp->vset[j] == NULL) {
+ __pmNoMem("__pmLogFetchInterp.vset", sizeof(pmValueSet), PM_FATAL_ERR);
+ }
+
+ rp->vset[j]->pmid = pmidlist[j];
+ if (pmidlist[j] == PM_ID_NULL) {
+ rp->vset[j]->numval = 0;
+ continue;
+ }
+ rp->vset[j]->numval = pcp->numval;
+ rp->vset[j]->valfmt = pcp->valfmt;
+
+ i = 0;
+ if (pcp->numval > 0) {
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (!icp->inresult)
+ continue;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP && done_roll) {
+ char strbuf[20];
+ fprintf(stderr, "pmid %s inst %d prior: t=%.6f",
+ pmIDStr_r(pmidlist[j], strbuf, sizeof(strbuf)), icp->inst, icp->t_prior);
+ dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 1, icp);
+ fprintf(stderr, " next: t=%.6f", icp->t_next);
+ dumpval(stderr, pcp->desc.type, icp->metric->valfmt, 0, icp);
+ fprintf(stderr, " t_first=%.6f t_last=%.6f\n",
+ icp->t_first, icp->t_last);
+ }
+#endif
+ rp->vset[j]->vlist[i].inst = icp->inst;
+ if (pcp->desc.type == PM_TYPE_32 || pcp->desc.type == PM_TYPE_U32) {
+ if (icp->t_prior == t_req)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ else if (icp->t_next == t_req)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_next.lval;
+ else {
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->t_prior >= 0)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ }
+ else if (pcp->desc.sem == PM_SEM_INSTANT) {
+ if (icp->t_prior >= 0 && icp->t_next >= 0)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ }
+ else {
+ /* assume COUNTER */
+ if (icp->t_prior >= 0 && icp->t_next >= 0) {
+ if (pcp->desc.type == PM_TYPE_32) {
+ if (icp->v_next.lval >= icp->v_prior.lval ||
+ dowrap == 0) {
+ rp->vset[j]->vlist[i++].value.lval = 0.5 +
+ icp->v_prior.lval + (t_req - icp->t_prior) *
+ (icp->v_next.lval - icp->v_prior.lval) /
+ (icp->t_next - icp->t_prior);
+ }
+ else {
+ /* not monotonic increasing and want wrap */
+ rp->vset[j]->vlist[i++].value.lval = 0.5 +
+ (t_req - icp->t_prior) *
+ (__int32_t)(UINT_MAX - icp->v_prior.lval + 1 + icp->v_next.lval) /
+ (icp->t_next - icp->t_prior);
+ rp->vset[j]->vlist[i].value.lval += icp->v_prior.lval;
+ }
+ }
+ else {
+ pmAtomValue av;
+ pmAtomValue *avp_prior = (pmAtomValue *)&icp->v_prior.lval;
+ pmAtomValue *avp_next = (pmAtomValue *)&icp->v_next.lval;
+ if (avp_next->ul >= avp_prior->ul) {
+ av.ul = 0.5 + avp_prior->ul +
+ (t_req - icp->t_prior) *
+ (avp_next->ul - avp_prior->ul) /
+ (icp->t_next - icp->t_prior);
+ }
+ else {
+ /* not monotonic increasing */
+ if (dowrap) {
+ av.ul = 0.5 +
+ (t_req - icp->t_prior) *
+ (__uint32_t)(UINT_MAX - avp_prior->ul + 1 + avp_next->ul ) /
+ (icp->t_next - icp->t_prior);
+ av.ul += avp_prior->ul;
+ }
+ else {
+ __uint32_t tmp;
+ tmp = avp_prior->ul - avp_next->ul;
+ av.ul = 0.5 + avp_prior->ul -
+ (t_req - icp->t_prior) * tmp /
+ (icp->t_next - icp->t_prior);
+ }
+ }
+ rp->vset[j]->vlist[i++].value.lval = av.ul;
+ }
+ }
+ }
+ }
+ }
+ else if (pcp->desc.type == PM_TYPE_FLOAT && icp->metric->valfmt == PM_VAL_INSITU) {
+ /* OLD style FLOAT insitu */
+ if (icp->t_prior == t_req)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ else if (icp->t_next == t_req)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_next.lval;
+ else {
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->t_prior >= 0)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ }
+ else if (pcp->desc.sem == PM_SEM_INSTANT) {
+ if (icp->t_prior >= 0 && icp->t_next >= 0)
+ rp->vset[j]->vlist[i++].value.lval = icp->v_prior.lval;
+ }
+ else {
+ /* assume COUNTER */
+ pmAtomValue av;
+ pmAtomValue *avp_prior = (pmAtomValue *)&icp->v_prior.lval;
+ pmAtomValue *avp_next = (pmAtomValue *)&icp->v_next.lval;
+ if (icp->t_prior >= 0 && icp->t_next >= 0) {
+ av.f = avp_prior->f + (t_req - icp->t_prior) *
+ (avp_next->f - avp_prior->f) /
+ (icp->t_next - icp->t_prior);
+ /* yes this IS correct ... */
+ rp->vset[j]->vlist[i++].value.lval = av.l;
+ }
+ }
+ }
+ }
+ else if (pcp->desc.type == PM_TYPE_FLOAT) {
+ /* NEW style FLOAT in pmValueBlock */
+ int need;
+ pmValueBlock *vp;
+ int ok = 1;
+
+ need = PM_VAL_HDR_SIZE + sizeof(float);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ sts = -oserror();
+ goto bad_alloc;
+ }
+ vp->vlen = need;
+ vp->vtype = PM_TYPE_FLOAT;
+ rp->vset[j]->valfmt = PM_VAL_DPTR;
+ rp->vset[j]->vlist[i++].value.pval = vp;
+ if (icp->t_prior == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float));
+ else if (icp->t_next == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(float));
+ else {
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->t_prior >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float));
+ else
+ ok = 0;
+ }
+ else if (pcp->desc.sem == PM_SEM_INSTANT) {
+ if (icp->t_prior >= 0 && icp->t_next >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(float));
+ else
+ ok = 0;
+ }
+ else {
+ /* assume COUNTER */
+ if (icp->t_prior >= 0 && icp->t_next >= 0) {
+ pmAtomValue av;
+ void *avp_prior = icp->v_prior.pval->vbuf;
+ void *avp_next = icp->v_next.pval->vbuf;
+ float f_prior;
+ float f_next;
+
+ memcpy((void *)&av.f, avp_prior, sizeof(av.f));
+ f_prior = av.f;
+ memcpy((void *)&av.f, avp_next, sizeof(av.f));
+ f_next = av.f;
+
+ av.f = f_prior + (t_req - icp->t_prior) *
+ (f_next - f_prior) /
+ (icp->t_next - icp->t_prior);
+ memcpy((void *)vp->vbuf, (void *)&av.f, sizeof(av.f));
+ }
+ else
+ ok = 0;
+ }
+ }
+ if (!ok) {
+ i--;
+ free(vp);
+ }
+ }
+ else if (pcp->desc.type == PM_TYPE_64 || pcp->desc.type == PM_TYPE_U64) {
+ int need;
+ pmValueBlock *vp;
+ int ok = 1;
+
+ need = PM_VAL_HDR_SIZE + sizeof(__int64_t);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ sts = -oserror();
+ goto bad_alloc;
+ }
+ vp->vlen = need;
+ if (pcp->desc.type == PM_TYPE_64)
+ vp->vtype = PM_TYPE_64;
+ else
+ vp->vtype = PM_TYPE_U64;
+ rp->vset[j]->valfmt = PM_VAL_DPTR;
+ rp->vset[j]->vlist[i++].value.pval = vp;
+ if (icp->t_prior == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t));
+ else if (icp->t_next == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(__int64_t));
+ else {
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->t_prior >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t));
+ else
+ ok = 0;
+ }
+ else if (pcp->desc.sem == PM_SEM_INSTANT) {
+ if (icp->t_prior >= 0 && icp->t_next >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(__int64_t));
+ else
+ ok = 0;
+ }
+ else {
+ /* assume COUNTER */
+ if (icp->t_prior >= 0 && icp->t_next >= 0) {
+ pmAtomValue av;
+ void *avp_prior = (void *)icp->v_prior.pval->vbuf;
+ void *avp_next = (void *)icp->v_next.pval->vbuf;
+ if (pcp->desc.type == PM_TYPE_64) {
+ __int64_t ll_prior;
+ __int64_t ll_next;
+ memcpy((void *)&av.ll, avp_prior, sizeof(av.ll));
+ ll_prior = av.ll;
+ memcpy((void *)&av.ll, avp_next, sizeof(av.ll));
+ ll_next = av.ll;
+ if (ll_next >= ll_prior || dowrap == 0)
+ av.ll = ll_next - ll_prior;
+ else
+ /* not monotonic increasing and want wrap */
+ av.ll = (__int64_t)(ULONGLONG_MAX - ll_prior + 1 + ll_next);
+ av.ll = (__int64_t)(0.5 + (double)ll_prior +
+ (t_req - icp->t_prior) * (double)av.ll / (icp->t_next - icp->t_prior));
+ memcpy((void *)vp->vbuf, (void *)&av.ll, sizeof(av.ll));
+ }
+ else {
+ __int64_t ull_prior;
+ __int64_t ull_next;
+ memcpy((void *)&av.ull, avp_prior, sizeof(av.ull));
+ ull_prior = av.ull;
+ memcpy((void *)&av.ull, avp_next, sizeof(av.ull));
+ ull_next = av.ull;
+ if (ull_next >= ull_prior) {
+ av.ull = ull_next - ull_prior;
+#if !defined(HAVE_CAST_U64_DOUBLE)
+ {
+ double tmp;
+
+ if (SIGN_64_MASK & av.ull)
+ tmp = (double)(__int64_t)(av.ull & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK;
+ else
+ tmp = (double)(__int64_t)av.ull;
+
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior +
+ (t_req - icp->t_prior) * tmp /
+ (icp->t_next - icp->t_prior));
+ }
+#else
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior +
+ (t_req - icp->t_prior) * (double)av.ull /
+ (icp->t_next - icp->t_prior));
+#endif
+ }
+ else {
+ /* not monotonic increasing */
+ if (dowrap) {
+ av.ull = ULONGLONG_MAX - ull_prior + 1 +
+ ull_next;
+#if !defined(HAVE_CAST_U64_DOUBLE)
+ {
+ double tmp;
+
+ if (SIGN_64_MASK & av.ull)
+ tmp = (double)(__int64_t)(av.ull & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK;
+ else
+ tmp = (double)(__int64_t)av.ull;
+
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior +
+ (t_req - icp->t_prior) * tmp /
+ (icp->t_next - icp->t_prior));
+ }
+#else
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior +
+ (t_req - icp->t_prior) * (double)av.ull /
+ (icp->t_next - icp->t_prior));
+#endif
+ }
+ else {
+ __uint64_t tmp;
+ tmp = ull_prior - ull_next;
+#if !defined(HAVE_CAST_U64_DOUBLE)
+ {
+ double xtmp;
+
+ if (SIGN_64_MASK & av.ull)
+ xtmp = (double)(__int64_t)(tmp & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK;
+ else
+ xtmp = (double)(__int64_t)tmp;
+
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior -
+ (t_req - icp->t_prior) * xtmp /
+ (icp->t_next - icp->t_prior));
+ }
+#else
+ av.ull = (__uint64_t)(0.5 + (double)ull_prior -
+ (t_req - icp->t_prior) * (double)tmp /
+ (icp->t_next - icp->t_prior));
+#endif
+ }
+ }
+ memcpy((void *)vp->vbuf, (void *)&av.ull, sizeof(av.ull));
+ }
+ }
+ else
+ ok = 0;
+ }
+ }
+ if (!ok) {
+ i--;
+ free(vp);
+ }
+ }
+ else if (pcp->desc.type == PM_TYPE_DOUBLE) {
+ int need;
+ pmValueBlock *vp;
+ int ok = 1;
+
+ need = PM_VAL_HDR_SIZE + sizeof(double);
+ if ((vp = (pmValueBlock *)malloc(need)) == NULL) {
+ sts = -oserror();
+ goto bad_alloc;
+ }
+ vp->vlen = need;
+ vp->vtype = PM_TYPE_DOUBLE;
+ rp->vset[j]->valfmt = PM_VAL_DPTR;
+ rp->vset[j]->vlist[i++].value.pval = vp;
+ if (icp->t_prior == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double));
+ else if (icp->t_next == t_req)
+ memcpy((void *)vp->vbuf, (void *)icp->v_next.pval->vbuf, sizeof(double));
+ else {
+ if (pcp->desc.sem == PM_SEM_DISCRETE) {
+ if (icp->t_prior >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double));
+ else
+ ok = 0;
+ }
+ else if (pcp->desc.sem == PM_SEM_INSTANT) {
+ if (icp->t_prior >= 0 && icp->t_next >= 0)
+ memcpy((void *)vp->vbuf, (void *)icp->v_prior.pval->vbuf, sizeof(double));
+ else
+ ok = 0;
+ }
+ else {
+ /* assume COUNTER */
+ if (icp->t_prior >= 0 && icp->t_next >= 0) {
+ pmAtomValue av;
+ void *avp_prior = (void *)icp->v_prior.pval->vbuf;
+ void *avp_next = (void *)icp->v_next.pval->vbuf;
+ double d_prior;
+ double d_next;
+ memcpy((void *)&av.d, avp_prior, sizeof(av.d));
+ d_prior = av.d;
+ memcpy((void *)&av.d, avp_next, sizeof(av.d));
+ d_next = av.d;
+ av.d = d_prior + (t_req - icp->t_prior) *
+ (d_next - d_prior) /
+ (icp->t_next - icp->t_prior);
+ memcpy((void *)vp->vbuf, (void *)&av.d, sizeof(av.d));
+ }
+ else
+ ok = 0;
+ }
+ }
+ if (!ok) {
+ i--;
+ free(vp);
+ }
+ }
+ else if ((pcp->desc.type == PM_TYPE_AGGREGATE ||
+ pcp->desc.type == PM_TYPE_EVENT ||
+ pcp->desc.type == PM_TYPE_HIGHRES_EVENT ||
+ pcp->desc.type == PM_TYPE_STRING) &&
+ icp->t_prior >= 0) {
+ int need;
+ pmValueBlock *vp;
+
+ need = icp->v_prior.pval->vlen;
+
+ vp = (pmValueBlock *)malloc(need);
+ if (vp == NULL) {
+ sts = -oserror();
+ goto bad_alloc;
+ }
+ rp->vset[j]->valfmt = PM_VAL_DPTR;
+ rp->vset[j]->vlist[i++].value.pval = vp;
+ memcpy((void *)vp, icp->v_prior.pval, need);
+ }
+ else {
+ /* unknown type - skip it, else junk in result */
+ i--;
+ }
+ }
+ }
+ }
+
+ *result = rp;
+ sts = 0;
+
+all_done:
+ pmXTBdeltaToTimeval(ctxp->c_delta, ctxp->c_mode, &delta_tv);
+ ctxp->c_origin.tv_sec += delta_tv.tv_sec;
+ ctxp->c_origin.tv_usec += delta_tv.tv_usec;
+ while (ctxp->c_origin.tv_usec > 1000000) {
+ ctxp->c_origin.tv_sec++;
+ ctxp->c_origin.tv_usec -= 1000000;
+ }
+ while (ctxp->c_origin.tv_usec < 0) {
+ ctxp->c_origin.tv_sec--;
+ ctxp->c_origin.tv_usec += 1000000;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INTERP) {
+ fprintf(stderr, "__pmLogFetchInterp: log reads: forward %ld",
+ nr[PM_MODE_FORW]);
+ if (nr_cache[PM_MODE_FORW])
+ fprintf(stderr, " (+%ld cached)", nr_cache[PM_MODE_FORW]);
+ fprintf(stderr, " backwards %ld",
+ nr[PM_MODE_BACK]);
+ if (nr_cache[PM_MODE_BACK])
+ fprintf(stderr, " (+%ld cached)", nr_cache[PM_MODE_BACK]);
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ return sts;
+
+bad_alloc:
+ /*
+ * leaks a little (vlist[] stuff) ... but does not really matter at
+ * this point, chance of anything good happening from here on are
+ * pretty remote
+ */
+ rp->vset[j]->numval = i;
+ while (++j < numpmid)
+ rp->vset[j]->numval = 0;
+ pmFreeResult(rp);
+
+ return sts;
+
+}
+
+void
+__pmLogResetInterp(__pmContext *ctxp)
+{
+ __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc;
+ double t_req;
+ __pmHashNode *hp;
+ int k;
+ pmidcntl_t *pcp;
+ instcntl_t *icp;
+
+ if (hcp->hsize == 0)
+ return;
+
+ t_req = __pmTimevalSub(&ctxp->c_origin, &ctxp->c_archctl->ac_log->l_label.ill_start);
+
+ for (k = 0; k < hcp->hsize; k++) {
+ for (hp = hcp->hash[k]; hp != NULL; hp = hp->next) {
+ pcp = (pmidcntl_t *)hp->data;
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (icp->t_prior > t_req || icp->t_next < t_req) {
+ icp->t_prior = icp->t_next = -1;
+ if (pcp->valfmt != PM_VAL_INSITU) {
+ if (icp->v_prior.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_prior.pval);
+ if (icp->v_next.pval != NULL)
+ __pmUnpinPDUBuf((void *)icp->v_next.pval);
+ }
+ icp->v_prior.pval = icp->v_next.pval = NULL;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Free interp data when context is closed ...
+ * - pinned PDU buffers holding values used for interpolation
+ * - hash structures for finding metrics and instances
+ * - read_cache contents
+ *
+ * Called with ctxp->c_lock held.
+ */
+void
+__pmFreeInterpData(__pmContext *ctxp)
+{
+ if (ctxp->c_archctl->ac_pmid_hc.hsize > 0) {
+ /* we have done some interpolation ... */
+ __pmHashCtl *hcp = &ctxp->c_archctl->ac_pmid_hc;
+ __pmHashNode *hp;
+ pmidcntl_t *pcp;
+ instcntl_t *icp;
+ int j;
+
+ for (j = 0; j < hcp->hsize; j++) {
+ __pmHashNode *last_hp = NULL;
+ /*
+ * Don't free __pmHashNode until hp->next has been traversed,
+ * hence free lags one node in the chain (last_hp used for free).
+ * Same for linked list of instcntl_t structs (use last_icp
+ * for free in this case).
+ */
+ for (hp = hcp->hash[j]; hp != NULL; hp = hp->next) {
+ instcntl_t *last_icp = NULL;
+ pcp = (pmidcntl_t *)hp->data;
+ for (icp = pcp->first; icp != NULL; icp = icp->next) {
+ if (pcp->valfmt != PM_VAL_INSITU) {
+ /*
+ * Held values may be in PDU buffers, unpin the PDU
+ * buffers just in case (__pmUnpinPDUBuf is a NOP if
+ * the value is not in a PDU buffer)
+ */
+ if (icp->v_prior.pval != NULL) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ char strbuf[20];
+ fprintf(stderr, "release pmid %s inst %d prior\n",
+ pmIDStr_r(pcp->desc.pmid, strbuf, sizeof(strbuf)), icp->inst);
+ }
+#endif
+ __pmUnpinPDUBuf((void *)icp->v_prior.pval);
+ }
+ if (icp->v_next.pval != NULL) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_INTERP) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ char strbuf[20];
+ fprintf(stderr, "release pmid %s inst %d next\n",
+ pmIDStr_r(pcp->desc.pmid, strbuf, sizeof(strbuf)), icp->inst);
+ }
+#endif
+ __pmUnpinPDUBuf((void *)icp->v_next.pval);
+ }
+ }
+ if (last_icp != NULL)
+ free(last_icp);
+ last_icp = icp;
+ }
+ if (last_icp != NULL)
+ free(last_icp);
+ if (last_hp != NULL) {
+ if (last_hp->data != NULL)
+ free(last_hp->data);
+ free(last_hp);
+ }
+ last_hp = hp;
+ }
+ if (last_hp != NULL) {
+ if (last_hp->data != NULL)
+ free(last_hp->data);
+ free(last_hp);
+ }
+ free(hcp->hash);
+ /* just being paranoid here */
+ hcp->hash = NULL;
+ hcp->hsize = 0;
+ }
+ }
+
+ if (ctxp->c_archctl->ac_cache != NULL) {
+ /* read cache allocated, work to be done */
+ cache_t *cache = (cache_t *)ctxp->c_archctl->ac_cache;
+ cache_t *cp;
+
+ for (cp = cache; cp < &cache[NUMCACHE]; cp++) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_LOG) && (pmDebug & DBG_TRACE_INTERP)) {
+ fprintf(stderr, "read cache entry " PRINTF_P_PFX "%p: mfp=" PRINTF_P_PFX "%p rp=" PRINTF_P_PFX "%p\n", cp, cp->mfp, cp->rp);
+ }
+#endif
+ if (cp->mfp == ctxp->c_archctl->ac_log->l_mfp) {
+ if (cp->rp != NULL)
+ pmFreeResult(cp->rp);
+ cp->rp = NULL;
+ cp->mfp = NULL;
+ cp->used = 0;
+ }
+ }
+ }
+}
diff --git a/src/libpcp/src/ipc.c b/src/libpcp/src/ipc.c
new file mode 100644
index 0000000..a9c7538
--- /dev/null
+++ b/src/libpcp/src/ipc.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#ifdef HAVE_VALUES_H
+#include <values.h>
+#endif
+#ifdef HAVE_SYS_CONFIG_H
+#include <sys/config.h>
+#endif
+
+/*
+ * We keep a table of connection state for each interesting file descriptor here.
+ * The version field holds the version of the software at the other end of the
+ * connection end point (0 is unknown, 1 or 2 are also valid).
+ * The socket field is used to tell whether this is a socket or pipe (or a file)
+ * connection, which is most important for the Windows port, as socket interfaces
+ * are "special" and do not use the usual file descriptor read/write/close calls,
+ * but must rather use recv/send/closesocket.
+ *
+ * The table entries are of fixed length, but the actual size depends on compile
+ * time options used (in particular, the secure sockets setting requires further
+ * space allocated to hold the additional security metadata for each socket).
+ */
+typedef struct {
+ int version; /* one or two */
+ int socket; /* true or false */
+ char data[0]; /* opaque data (optional) */
+} __pmIPC;
+
+static int __pmLastUsedFd = -INT_MAX;
+static __pmIPC *__pmIPCTable;
+static int ipctablecount;
+static int ipcentrysize;
+
+static inline __pmIPC *
+__pmIPCTablePtr(int fd)
+{
+ char *entry = (char *)__pmIPCTable;
+ return (__pmIPC *)(entry + fd * ipcentrysize);
+}
+
+/*
+ * always called with __pmLock_libpcp held
+ */
+static int
+__pmResizeIPC(int fd)
+{
+ size_t size;
+ int oldcount;
+
+ if (__pmIPCTable == NULL || fd >= ipctablecount) {
+ if (ipcentrysize == 0)
+ ipcentrysize = sizeof(__pmIPC) + __pmDataIPCSize();
+ oldcount = ipctablecount;
+ if (ipctablecount == 0)
+ ipctablecount = 4;
+ while (fd >= ipctablecount)
+ ipctablecount *= 2;
+ size = ipcentrysize * ipctablecount;
+ __pmIPCTable = (__pmIPC *)realloc(__pmIPCTable, size);
+ if (__pmIPCTable == NULL)
+ return -oserror();
+ size -= ipcentrysize * oldcount;
+ memset(__pmIPCTablePtr(oldcount), 0, size);
+ }
+ return 0;
+}
+
+int
+__pmSetVersionIPC(int fd, int version)
+{
+ int sts;
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmSetVersionIPC: fd=%d version=%d\n", fd, version);
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((sts = __pmResizeIPC(fd)) < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+
+ __pmIPCTablePtr(fd)->version = version;
+ __pmLastUsedFd = fd;
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ __pmPrintIPC();
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmSetSocketIPC(int fd)
+{
+ int sts;
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmSetSocketIPC: fd=%d\n", fd);
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((sts = __pmResizeIPC(fd)) < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+
+ __pmIPCTablePtr(fd)->socket = 1;
+ __pmLastUsedFd = fd;
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ __pmPrintIPC();
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmVersionIPC(int fd)
+{
+ int sts;
+
+ if (fd == PDU_OVERRIDE2)
+ return PDU_VERSION2;
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (__pmIPCTable == NULL || fd < 0 || fd >= ipctablecount) {
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr,
+ "IPC protocol botch: table->" PRINTF_P_PFX "%p fd=%d sz=%d\n",
+ __pmIPCTable, fd, ipctablecount);
+ PM_UNLOCK(__pmLock_libpcp);
+ return UNKNOWN_VERSION;
+ }
+ sts = __pmIPCTablePtr(fd)->version;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmLastVersionIPC()
+{
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ sts = __pmVersionIPC(__pmLastUsedFd);
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmSocketIPC(int fd)
+{
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (__pmIPCTable == NULL || fd < 0 || fd >= ipctablecount) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+ }
+ sts = __pmIPCTablePtr(fd)->socket;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmSetDataIPC(int fd, void *data)
+{
+ char *dest;
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((sts = __pmResizeIPC(fd)) < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmSetDataIPC: fd=%d data=%p(sz=%d)\n",
+ fd, data, (int)(ipcentrysize - sizeof(__pmIPC)));
+
+ dest = ((char *)__pmIPCTablePtr(fd)) + sizeof(__pmIPC);
+ memcpy(dest, data, ipcentrysize - sizeof(__pmIPC));
+ __pmLastUsedFd = fd;
+
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ __pmPrintIPC();
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+__pmDataIPC(int fd, void *data)
+{
+ char *source;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (fd < 0 || fd >= ipctablecount || __pmIPCTable == NULL ||
+ ipcentrysize == sizeof(__pmIPC)) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return -ESRCH;
+ }
+ source = ((char *)__pmIPCTablePtr(fd)) + sizeof(__pmIPC);
+ if ((pmDebug & DBG_TRACE_CONTEXT) && (pmDebug & DBG_TRACE_DESPERATE))
+ fprintf(stderr, "__pmDataIPC: fd=%d, data=%p(sz=%d)\n",
+ fd, source, (int)(ipcentrysize - sizeof(__pmIPC)));
+ memcpy(data, source, ipcentrysize - sizeof(__pmIPC));
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+/*
+ * Called by log readers who need version info for result decode,
+ * but don't have a socket fd (have a FILE* & fileno though).
+ * Also at start of version exchange before version is known
+ * (when __pmDecodeError is called before knowing version).
+ */
+void
+__pmOverrideLastFd(int fd)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ __pmLastUsedFd = fd;
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+void
+__pmResetIPC(int fd)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (__pmIPCTable && fd >= 0 && fd < ipctablecount)
+ memset(__pmIPCTablePtr(fd), 0, ipcentrysize);
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+void
+__pmPrintIPC(void)
+{
+ int i;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ fprintf(stderr, "IPC table fd(PDU version):");
+ for (i = 0; i < ipctablecount; i++) {
+ if (__pmIPCTablePtr(i)->version != UNKNOWN_VERSION)
+ fprintf(stderr, " %d(%d,%d)", i, __pmIPCTablePtr(i)->version,
+ __pmIPCTablePtr(i)->socket);
+ }
+ fputc('\n', stderr);
+ PM_UNLOCK(__pmLock_libpcp);
+}
diff --git a/src/libpcp/src/lock.c b/src/libpcp/src/lock.c
new file mode 100644
index 0000000..0bcf2a1
--- /dev/null
+++ b/src/libpcp/src/lock.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+
+#ifdef PM_MULTI_THREAD
+typedef struct {
+ void *lock;
+ int count;
+} lockdbg_t;
+
+#define PM_LOCK_OP 1
+#define PM_UNLOCK_OP 2
+
+static int multi_init[PM_SCOPE_MAX+1];
+static pthread_t multi_seen[PM_SCOPE_MAX+1];
+
+/* the big libpcp lock */
+#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+pthread_mutex_t __pmLock_libpcp = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
+#else
+pthread_mutex_t __pmLock_libpcp = PTHREAD_MUTEX_INITIALIZER;
+
+#ifndef HAVE___THREAD
+pthread_key_t __pmTPDKey = 0;
+
+static void
+__pmTPD__destroy(void *addr)
+{
+ free(addr);
+}
+#endif
+#endif
+
+void
+__pmInitLocks(void)
+{
+ static pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER;
+ static int done = 0;
+ int psts;
+ char errmsg[PM_MAXERRMSGLEN];
+ if ((psts = pthread_mutex_lock(&init)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_mutex_lock failed: %s", errmsg);
+ exit(4);
+ }
+ if (!done) {
+#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
+ /*
+ * Unable to initialize at compile time, need to do it here in
+ * a one trip for all threads run-time initialization.
+ */
+ pthread_mutexattr_t attr;
+
+ if ((psts = pthread_mutexattr_init(&attr)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_mutexattr_init failed: %s", errmsg);
+ exit(4);
+ }
+ if ((psts = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_mutexattr_settype failed: %s", errmsg);
+ exit(4);
+ }
+ if ((psts = pthread_mutex_init(&__pmLock_libpcp, &attr)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_mutex_init failed: %s", errmsg);
+ exit(4);
+ }
+#endif
+#ifndef HAVE___THREAD
+ /* first thread here creates the thread private data key */
+ if ((psts = pthread_key_create(&__pmTPDKey, __pmTPD__destroy)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_key_create failed: %s", errmsg);
+ exit(4);
+ }
+#endif
+ done = 1;
+ }
+ if ((psts = pthread_mutex_unlock(&init)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_mutex_unlock failed: %s", errmsg);
+ exit(4);
+ }
+#ifndef HAVE___THREAD
+ if (pthread_getspecific(__pmTPDKey) == NULL) {
+ __pmTPD *tpd = (__pmTPD *)malloc(sizeof(__pmTPD));
+ if (tpd == NULL) {
+ __pmNoMem("__pmInitLocks: __pmTPD", sizeof(__pmTPD), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if ((psts = pthread_setspecific(__pmTPDKey, tpd)) != 0) {
+ pmErrStr_r(-psts, errmsg, sizeof(errmsg));
+ fprintf(stderr, "__pmInitLocks: pthread_setspecific failed: %s", errmsg);
+ exit(4);
+ }
+ tpd->curcontext = PM_CONTEXT_UNDEF;
+ }
+#endif
+}
+
+int
+__pmMultiThreaded(int scope)
+{
+ int sts = 0;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (!multi_init[scope]) {
+ multi_init[scope] = 1;
+ multi_seen[scope] = pthread_self();
+ }
+ else {
+ if (!pthread_equal(multi_seen[scope], pthread_self()))
+ sts = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+#ifdef PM_MULTI_THREAD_DEBUG
+void
+__pmDebugLock(int op, void *lock, const char *file, int line)
+{
+ int report = 0;
+ int ctx;
+ static __pmHashCtl hashctl = { 0, 0, NULL };
+ __pmHashNode *hp = NULL;
+ lockdbg_t *ldp;
+ int try;
+ int sts;
+
+ if (lock == (void *)&__pmLock_libpcp) {
+ if ((pmDebug & DBG_TRACE_APPL0) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0))
+ report = DBG_TRACE_APPL0;
+ }
+ else if ((ctx = __pmIsContextLock(lock)) >= 0) {
+ if ((pmDebug & DBG_TRACE_APPL1) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0))
+ report = DBG_TRACE_APPL1;
+ }
+ else {
+ if ((pmDebug & DBG_TRACE_APPL2) | ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1 | DBG_TRACE_APPL2)) == 0))
+ report = DBG_TRACE_APPL2;
+ }
+
+ if (report) {
+ __psint_t key = (__psint_t)lock;
+ fprintf(stderr, "%s:%d %s", file, line, op == PM_LOCK_OP ? "lock" : "unlock");
+ try = 0;
+again:
+ hp = __pmHashSearch((unsigned int)key, &hashctl);
+ while (hp != NULL) {
+ ldp = (lockdbg_t *)hp->data;
+ if (ldp->lock == lock)
+ break;
+ hp = hp->next;
+ }
+ if (hp == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ ldp = (lockdbg_t *)malloc(sizeof(lockdbg_t));
+ ldp->lock = lock;
+ ldp->count = 0;
+ sts = __pmHashAdd((unsigned int)key, (void *)ldp, &hashctl);
+ if (sts == 1) {
+ try++;
+ if (try == 1)
+ goto again;
+ }
+ hp = NULL;
+ fprintf(stderr, " hash control failure: %s\n", pmErrStr_r(-sts, errmsg, sizeof(errmsg)));
+ }
+ }
+
+ if (report == DBG_TRACE_APPL0) {
+ fprintf(stderr, "(global_libpcp)");
+ }
+ else if (report == DBG_TRACE_APPL1) {
+ fprintf(stderr, "(ctx %d)", ctx);
+ }
+ else if (report == DBG_TRACE_APPL2) {
+ if ((ctx = __pmIsChannelLock(lock)) >= 0)
+ fprintf(stderr, "(ctx %d ipc channel)", ctx);
+ else if (__pmIsDeriveLock(lock))
+ fprintf(stderr, "(derived_metric)");
+ else
+ fprintf(stderr, "(" PRINTF_P_PFX "%p)", lock);
+ }
+ if (report) {
+ if (hp != NULL) {
+ ldp = (lockdbg_t *)hp->data;
+ if (op == PM_LOCK_OP) {
+ if (ldp->count != 0)
+ fprintf(stderr, " [count=%d]", ldp->count);
+ ldp->count++;
+ }
+ else {
+ if (ldp->count != 1)
+ fprintf(stderr, " [count=%d]", ldp->count);
+ ldp->count--;
+ }
+ }
+ fputc('\n', stderr);
+#ifdef HAVE_BACKTRACE
+#define MAX_TRACE_DEPTH 32
+ {
+ void *backaddr[MAX_TRACE_DEPTH];
+ sts = backtrace(backaddr, MAX_TRACE_DEPTH);
+ if (sts > 0) {
+ char **symbols;
+ symbols = backtrace_symbols(backaddr, MAX_TRACE_DEPTH);
+ if (symbols != NULL) {
+ int i;
+ fprintf(stderr, "backtrace:\n");
+ for (i = 0; i < sts; i++)
+ fprintf(stderr, " %s\n", symbols[i]);
+ free(symbols);
+ }
+ }
+ }
+
+#endif
+ }
+}
+#else
+#define __pmDebugLock(op, lock, file, line) do { } while (0)
+#endif
+
+int
+__pmLock(void *lock, const char *file, int line)
+{
+ int sts;
+
+ if (pmDebug & DBG_TRACE_LOCK)
+ __pmDebugLock(PM_LOCK_OP, lock, file, line);
+
+ if ((sts = pthread_mutex_lock(lock)) != 0) {
+ sts = -sts;
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s:%d: lock failed: %s\n", file, line, pmErrStr(sts));
+ }
+ return sts;
+}
+
+int
+__pmUnlock(void *lock, const char *file, int line)
+{
+ int sts;
+
+ if (pmDebug & DBG_TRACE_LOCK)
+ __pmDebugLock(PM_UNLOCK_OP, lock, file, line);
+
+ if ((sts = pthread_mutex_unlock(lock)) != 0) {
+ sts = -sts;
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s:%d: unlock failed: %s\n", file, line, pmErrStr(sts));
+ }
+ return sts;
+}
+
+#else /* !PM_MULTI_THREAD - symbols exposed at the shlib ABI level */
+void *__pmLock_libpcp;
+void __pmInitLocks(void) { }
+int __pmMultiThreaded(int scope) { (void)scope; return 0; }
+int __pmLock(void *l, const char *f, int n) { (void)l, (void)f, (void)n; return 0; }
+int __pmUnlock(void *l, const char *f, int n) { (void)l, (void)f, (void)n; return 0; }
+#endif
diff --git a/src/libpcp/src/logconnect.c b/src/libpcp/src/logconnect.c
new file mode 100644
index 0000000..8a2365e
--- /dev/null
+++ b/src/libpcp/src/logconnect.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * Do not need ctxp->c_pmcd->pc_lock lock around __pmSendCreds() call,
+ * as the connection to pmlogger has not been created, so no-one else
+ * could be using the fd.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+#include <limits.h>
+#include <sys/stat.h>
+
+/*
+ * Return timeout (in seconds) to be used when pmlc is communicating
+ * with pmlogger ... used externally from pmlc and internally from
+ * __pmConnectLogger() and __pmControlLogger()
+ */
+int
+__pmLoggerTimeout(void)
+{
+ static int timeout = TIMEOUT_NEVER;
+ static int done_default = 0;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (!done_default) {
+ char *timeout_str;
+ char *end_ptr;
+ if ((timeout_str = getenv("PMLOGGER_REQUEST_TIMEOUT")) != NULL) {
+ /*
+ * Only a positive integer (the unit is seconds) is OK
+ */
+ timeout = strtol(timeout_str, &end_ptr, 10);
+ if (*end_ptr != '\0' || timeout < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "ignored bad PMLOGGER_REQUEST_TIMEOUT = '%s'\n",
+ timeout_str);
+ timeout = TIMEOUT_NEVER;
+ }
+ }
+ done_default = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ return timeout;
+}
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+/*
+ * Return the path to the default PMLOGGER local unix domain socket.
+ * in the buffer propvided.
+ * Return the path regardless of whether unix domain sockets are
+ * supported by our build. Other functions can then print reasonable
+ * messages if an attempt is made to use one.
+ */
+const char *
+__pmLogLocalSocketDefault(int pid, char *buf, size_t bufSize)
+{
+ /* snprintf guarantees a terminating nul, even if the output is truncated. */
+ if (pid == PM_LOG_PRIMARY_PID) { /* primary */
+ snprintf(buf, bufSize, "%s/pmlogger.primary.socket",
+ pmGetConfig("PCP_RUN_DIR"));
+ }
+ else {
+ snprintf(buf, bufSize, "%s/pmlogger.%d.socket",
+ pmGetConfig("PCP_RUN_DIR"), pid);
+ }
+
+ return buf;
+}
+
+/*
+ * Return the path to the user's own PMLOGGER local unix domain socket
+ * in the buffer provided.
+ * Return the path regardless of whether unix domain sockets are
+ * supported by our build. Other functions can then print reasonable
+ * messages if an attempt is made to use one.
+ */
+const char *
+__pmLogLocalSocketUser(int pid, char *buf, size_t bufSize)
+{
+ char home[MAXPATHLEN];
+ char *homeResult;
+
+ homeResult = __pmHomedirFromID(getuid(), home, sizeof(home));
+ if (homeResult == NULL)
+ return NULL;
+
+ /* snprintf guarantees a terminating nul, even if the output is truncated. */
+ snprintf(buf, bufSize, "%s/.pcp/run/pmlogger.%d.socket",
+ homeResult, pid);
+
+ return buf;
+}
+#endif
+
+/*
+ * Common function for attempting connections to pmlogger.
+ */
+static int
+connectLogger(int fd, __pmSockAddr *myAddr)
+{
+ /* Attempt the connection. */
+ int sts = __pmConnect(fd, myAddr, __pmSockAddrSize());
+
+ /* Successful connection? */
+ if (sts >= 0)
+ return sts;
+
+ sts = neterror();
+ if (sts == EINPROGRESS) {
+ /* We're in progress - wait on select. */
+ struct timeval stv = { 0, 000000 };
+ struct timeval *pstv;
+ __pmFdSet rfds;
+ int rc;
+ stv.tv_sec = __pmLoggerTimeout();
+ pstv = stv.tv_sec ? &stv : NULL;
+
+ __pmFD_ZERO(&rfds);
+ __pmFD_SET(fd, &rfds);
+ if ((rc = __pmSelectRead(fd+1, &rfds, pstv)) == 1) {
+ sts = __pmConnectCheckError(fd);
+ }
+ else if (rc == 0) {
+ sts = ETIMEDOUT;
+ }
+ else {
+ sts = (rc < 0) ? neterror() : EINVAL;
+ }
+ }
+ sts = -sts;
+
+ /* Successful connection? */
+ if (sts >= 0)
+ return sts;
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(fd);
+ return sts;
+}
+
+/*
+ * Attempt connection to pmlogger via a local socket.
+ */
+static int
+connectLoggerLocal(const char *local_socket)
+{
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ char socket_path[MAXPATHLEN];
+ int fd;
+ int sts;
+ __pmSockAddr *myAddr;
+
+ /* Create a socket */
+ fd = __pmCreateUnixSocket();
+ if (fd < 0)
+ return -ECONNREFUSED;
+
+ /* Set up the socket address. */
+ myAddr = __pmSockAddrAlloc();
+ if (myAddr == NULL) {
+ __pmNotifyErr(LOG_ERR, "__pmConnectLogger: out of memory\n");
+ __pmCloseSocket(fd);
+ return -ENOMEM;
+ }
+ __pmSockAddrSetFamily(myAddr, AF_UNIX);
+
+ /*
+ * Set the socket path. All socket paths are absolute, but strip off any redundant
+ * initial path separators.
+ * snprintf is guaranteed to add a nul byte.
+ */
+ while (*local_socket == __pmPathSeparator())
+ ++local_socket;
+ snprintf(socket_path, sizeof(socket_path), "%c%s", __pmPathSeparator(), local_socket);
+ __pmSockAddrSetPath(myAddr, socket_path);
+
+ /* Attempt to connect */
+ sts = connectLogger(fd, myAddr);
+ __pmSockAddrFree(myAddr);
+
+ if (sts < 0) {
+ __pmCloseSocket(fd);
+ return sts;
+ }
+
+ return fd;
+#else
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: local_socket == %s is not supported\n",
+ local_socket);
+#endif
+ return -ECONNREFUSED;
+#endif
+}
+
+/*
+ * Determine how to connect based on connectionSpec, pid and port:
+ *
+ * If hostname is "local:[//][path]", then try the socket at
+ * /path, if specified and the socket at PCP_RUN_DIR/pmlogger.<pid>.socket otherwise,
+ * where <pid> is "primary" if pid is PM_LOG_PRIMARY_PID.
+ * If this fails then set connectionSpec to "localhost" and then
+ *
+ * ConnectionSpec is a host name.
+ * If port is set, use hostname:port, otherwise
+ *
+ * Use hostname+pid to find port, assuming pmcd is running there
+ */
+int
+__pmConnectLogger(const char *connectionSpec, int *pid, int *port)
+{
+ int n, sts = 0;
+ __pmLogPort *lpp;
+ int fd; /* Fd for socket connection to pmcd */
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ int pinpdu;
+ __pmHostEnt *servInfo;
+ __pmSockAddr *myAddr;
+ void *enumIx;
+ const char *prefix_end;
+ size_t prefix_len;
+ char path[MAXPATHLEN];
+ int originalPid;
+ int wasLocal;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger(host=%s, pid=%d, port=%d)\n",
+ connectionSpec, *pid, *port);
+#endif
+
+ if (*pid == PM_LOG_NO_PID && *port == PM_LOG_PRIMARY_PORT) {
+ /*
+ * __pmLogFindPort and __pmLogLocalSocketDefault can only lookup
+ * based on pid, so xlate the request
+ */
+ *pid = PM_LOG_PRIMARY_PID;
+ *port = PM_LOG_NO_PORT;
+ }
+
+ /*
+ * If the prefix is "local:[path]", we may try the connection more than once using
+ * "unix:[path]" followed by "localhost".
+ */
+ for (originalPid = *pid; /**/; *pid = originalPid) {
+ fd = -1;
+ /* Look for a "local:" or a "unix:" prefix. */
+ wasLocal = 0;
+ prefix_end = strchr(connectionSpec, ':');
+ if (prefix_end != NULL) {
+ prefix_len = prefix_end - connectionSpec + 1;
+ if ((wasLocal = (prefix_len == 6 && strncmp(connectionSpec, "local:", prefix_len) == 0)) ||
+ (prefix_len == 5 && strncmp(connectionSpec, "unix:", prefix_len) == 0)) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (connectionSpec[prefix_len] != '\0') {
+ /* Try the specified local socket directly. */
+ fd = connectLoggerLocal(connectionSpec + prefix_len);
+ }
+ else if (*pid != PM_LOG_NO_PID) {
+ /* Try the socket indicated by the pid. */
+ connectionSpec = __pmLogLocalSocketDefault(*pid, path, sizeof(path));
+ fd = connectLoggerLocal(connectionSpec);
+ if (fd < 0) {
+ /* Try the socket in the user's home directory. */
+ connectionSpec = __pmLogLocalSocketUser(*pid, path, sizeof(path));
+ if (connectionSpec != NULL)
+ fd = connectLoggerLocal(connectionSpec);
+ }
+ }
+#endif
+ }
+ if (fd >= 0)
+ sts = 0;
+ else
+ sts = fd;
+ }
+ else {
+ /*
+ * If not a url, then connectionSpec is a host name.
+ *
+ * Catch pid == PM_LOG_ALL_PIDS ... this tells __pmLogFindPort
+ * to get all ports
+ */
+ if (*pid == PM_LOG_ALL_PIDS) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: pid == PM_LOG_ALL_PIDS makes no sense here\n");
+#endif
+ return -ECONNREFUSED;
+ }
+
+ if (*port == PM_LOG_NO_PORT) {
+ if ((n = __pmLogFindPort(connectionSpec, *pid, &lpp)) < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectLogger: __pmLogFindPort: %s\n", pmErrStr_r(n, errmsg, sizeof(errmsg)));
+ }
+#endif
+ return n;
+ }
+ else if (n != 1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: __pmLogFindPort -> 1, cannot contact pmlogger\n");
+#endif
+ return -ECONNREFUSED;
+ }
+ *port = lpp->port;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: __pmLogFindPort -> pid = %d\n", lpp->port);
+#endif
+ }
+
+ if ((servInfo = __pmGetAddrInfo(connectionSpec)) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: __pmGetAddrInfo: %s\n",
+ hoststrerror());
+#endif
+ return -EHOSTUNREACH;
+ }
+
+ /*
+ * Loop over the addresses resolved for this host name until one of them
+ * connects.
+ */
+ enumIx = NULL;
+ for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ myAddr != NULL;
+ myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myAddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myAddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "__pmConnectLogger : invalid address family %d\n",
+ __pmSockAddrGetFamily(myAddr));
+ fd = -1;
+ }
+ if (fd < 0) {
+ __pmSockAddrFree(myAddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ __pmSockAddrSetPort(myAddr, *port);
+ sts = connectLogger(fd, myAddr);
+ __pmSockAddrFree(myAddr);
+
+ /* Successful connection? */
+ if (sts >= 0)
+ break;
+ }
+ __pmHostEntFree(servInfo);
+ }
+
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectLogger: connect: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ }
+ else {
+ /* Expect an error PDU back: ACK/NACK for connection */
+ pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, __pmLoggerTimeout(), &pb);
+ if (sts == PDU_ERROR) {
+ __pmOverrideLastFd(PDU_OVERRIDE2); /* don't dink with the value */
+ __pmDecodeError(pb, &sts);
+ php = (__pmPDUHdr *)pb;
+ if (*pid != PM_LOG_NO_PID && *pid != PM_LOG_PRIMARY_PID && php->from != *pid) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: ACK response from pid %d, expected pid %d\n",
+ php->from, *pid);
+#endif
+ sts = -ECONNREFUSED;
+ }
+ *pid = php->from;
+ }
+ else if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ if (sts == PM_ERR_TIMEOUT)
+ fprintf(stderr, "__pmConnectLogger: timeout (after %d secs)\n", __pmLoggerTimeout());
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmConnectLogger: Error: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ }
+#endif
+ ; /* fall through */
+ }
+ else {
+ /* wrong PDU type! */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: ACK botch PDU type=%d not PDU_ERROR?\n", sts);
+#endif
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (sts >= 0) {
+ if (sts == LOG_PDU_VERSION2) {
+ __pmCred handshake[1];
+
+ __pmSetVersionIPC(fd, sts);
+ handshake[0].c_type = CVERSION;
+ handshake[0].c_vala = LOG_PDU_VERSION;
+ handshake[0].c_valb = 0;
+ handshake[0].c_valc = 0;
+ sts = __pmSendCreds(fd, (int)getpid(), 1, handshake);
+ }
+ else
+ sts = PM_ERR_IPC;
+ if (sts >= 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmConnectLogger: PDU version=%d fd=%d\n",
+ __pmVersionIPC(fd), fd);
+#endif
+ return fd;
+ }
+ }
+
+ /* Error if we get here */
+ __pmCloseSocket(fd);
+ }
+
+ /*
+ * If the prefix was "local:" and we have a port or a pid, try the
+ * connection as "localhost". Otherwise, we can't connect.
+ */
+ if (wasLocal && (*port != PM_LOG_NO_PORT || *pid != PM_LOG_NO_PID)) {
+ connectionSpec = "localhost";
+ continue;
+ }
+
+ /* No more ways to connect. */
+ break;
+ } /* Loop over connect specs. */
+
+ return sts;
+}
diff --git a/src/libpcp/src/logcontrol.c b/src/libpcp/src/logcontrol.c
new file mode 100644
index 0000000..2dc5c17
--- /dev/null
+++ b/src/libpcp/src/logcontrol.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe note
+ *
+ * This routine is not thread-safe as there is no serialization on the
+ * use of the fd between the __pmSendLogControl() and the reading of
+ * the reply PDU. It is assumed that the caller is single-threaded,
+ * which is true for the only current user of this routine, pmlc(1).
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+int
+__pmControlLog(int fd, const pmResult *request, int control, int state, int delta, pmResult **status)
+{
+ int n;
+ __pmPDU *pb;
+
+ if (request->numpmid < 1)
+ return PM_ERR_TOOSMALL;
+
+ /* send a PCP 2.0 log control request */
+ n = __pmSendLogControl(fd, request, control, state, delta);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ int pinpdu;
+ /* get the reply */
+ pinpdu = n = __pmGetPDU(fd, ANY_SIZE, __pmLoggerTimeout(), &pb);
+ if (n == PDU_RESULT) {
+ n = __pmDecodeResult(pb, status);
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC; /* unknown reply type */
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+
+ return n;
+}
diff --git a/src/libpcp/src/logmeta.c b/src/libpcp/src/logmeta.c
new file mode 100644
index 0000000..9cf893e
--- /dev/null
+++ b/src/libpcp/src/logmeta.c
@@ -0,0 +1,847 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "fault.h"
+#include "internal.h"
+#include <stddef.h>
+
+/* bytes for a length field in a header/trailer, or a string length field */
+#define LENSIZE 4
+
+#ifdef PCP_DEBUG
+static void
+StrTimeval(__pmTimeval *tp)
+{
+ if (tp == NULL)
+ fprintf(stderr, "<null timeval>");
+ else
+ __pmPrintTimeval(stderr, tp);
+}
+#endif
+
+static int
+addindom(__pmLogCtl *lcp, pmInDom indom, const __pmTimeval *tp, int numinst,
+ int *instlist, char **namelist, int *indom_buf, int allinbuf)
+{
+ __pmLogInDom *idp;
+ __pmHashNode *hp;
+ int sts;
+
+PM_FAULT_POINT("libpcp/" __FILE__ ":1", PM_FAULT_ALLOC);
+ if ((idp = (__pmLogInDom *)malloc(sizeof(__pmLogInDom))) == NULL)
+ return -oserror();
+ idp->stamp = *tp; /* struct assignment */
+ idp->numinst = numinst;
+ idp->instlist = instlist;
+ idp->namelist = namelist;
+ idp->buf = indom_buf;
+ idp->allinbuf = allinbuf;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ char strbuf[20];
+ fprintf(stderr, "addindom( ..., %s, ", pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ StrTimeval((__pmTimeval *)tp);
+ fprintf(stderr, ", numinst=%d)\n", numinst);
+ }
+#endif
+
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL) {
+ idp->next = NULL;
+ sts = __pmHashAdd((unsigned int)indom, (void *)idp, &lcp->l_hashindom);
+ }
+ else {
+ idp->next = (__pmLogInDom *)hp->data;
+ hp->data = (void *)idp;
+ sts = 0;
+ }
+ return sts;
+}
+
+/*
+ * Load _all_ of the hashed pmDesc and __pmLogInDom structures from the metadata
+ * log file -- used at the initialization (NewContext) of an archive.
+ * Also load all the metric names from the metadata log file and create l_pmns.
+ */
+int
+__pmLogLoadMeta(__pmLogCtl *lcp)
+{
+ int rlen;
+ int check;
+ pmDesc *dp;
+ int sts = 0;
+ __pmLogHdr h;
+ FILE *f = lcp->l_mdfp;
+ int numpmid = 0;
+ int n;
+
+ if ((sts = __pmNewPMNS(&(lcp->l_pmns))) < 0)
+ goto end;
+
+ fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET);
+ for ( ; ; ) {
+ n = (int)fread(&h, 1, sizeof(__pmLogHdr), f);
+
+ /* swab hdr */
+ h.len = ntohl(h.len);
+ h.type = ntohl(h.type);
+
+ if (n != sizeof(__pmLogHdr) || h.len <= 0) {
+ if (feof(f)) {
+ clearerr(f);
+ sts = 0;
+ goto end;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: header read -> %d: expected: %d or len=%d\n",
+ n, (int)sizeof(__pmLogHdr), h.len);
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ goto end;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: record len=%d, type=%d @ offset=%d\n",
+ h.len, h.type, (int)(ftell(f) - sizeof(__pmLogHdr)));
+ }
+#endif
+ rlen = h.len - (int)sizeof(__pmLogHdr) - (int)sizeof(int);
+ if (h.type == TYPE_DESC) {
+ numpmid++;
+PM_FAULT_POINT("libpcp/" __FILE__ ":2", PM_FAULT_ALLOC);
+ if ((dp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL) {
+ sts = -oserror();
+ goto end;
+ }
+ if ((n = (int)fread(dp, 1, sizeof(pmDesc), f)) != sizeof(pmDesc)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: pmDesc read -> %d: expected: %d\n",
+ n, (int)sizeof(pmDesc));
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ free(dp);
+ goto end;
+ }
+ else {
+ /* swab desc */
+ dp->type = ntohl(dp->type);
+ dp->sem = ntohl(dp->sem);
+ dp->indom = __ntohpmInDom(dp->indom);
+ dp->units = __ntohpmUnits(dp->units);
+ dp->pmid = __ntohpmID(dp->pmid);
+ }
+
+ if ((sts = __pmHashAdd((int)dp->pmid, (void *)dp, &lcp->l_hashpmid)) < 0) {
+ free(dp);
+ goto end;
+ }
+
+ else {
+ char name[MAXPATHLEN];
+ int numnames;
+ int i;
+ int len;
+
+ /* read in the names & store in PMNS tree ... */
+ if ((n = (int)fread(&numnames, 1, sizeof(numnames), f)) !=
+ sizeof(numnames)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: numnames read -> %d: expected: %d\n",
+ n, (int)sizeof(numnames));
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ goto end;
+ }
+ else {
+ /* swab numnames */
+ numnames = ntohl(numnames);
+ }
+
+ for (i = 0; i < numnames; i++) {
+ if ((n = (int)fread(&len, 1, sizeof(len), f)) !=
+ sizeof(len)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: len name[%d] read -> %d: expected: %d\n",
+ i, n, (int)sizeof(len));
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ goto end;
+ }
+ else {
+ /* swab len */
+ len = ntohl(len);
+ }
+
+ if ((n = (int)fread(name, 1, len, f)) != len) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: name[%d] read -> %d: expected: %d\n",
+ i, n, len);
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ goto end;
+ }
+ name[len] = '\0';
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ char strbuf[20];
+ fprintf(stderr, "__pmLogLoadMeta: PMID: %s name: %s\n",
+ pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)), name);
+ }
+#endif
+
+ if ((sts = __pmAddPMNSNode(lcp->l_pmns, dp->pmid, name)) < 0) {
+ /*
+ * If we see a duplicate PMID, its a recoverable error.
+ * We wont be able to see all of the data in the log, but
+ * its better to provide access to some rather than none,
+ * esp. when only one or two metric IDs may be corrupted
+ * in this way (which we may not be interested in anyway).
+ */
+ if (sts != PM_ERR_PMID)
+ goto end;
+ sts = 0;
+ }
+ }/*for*/
+ }
+ }
+ else if (h.type == TYPE_INDOM) {
+ int *tbuf;
+ pmInDom indom;
+ __pmTimeval *when;
+ int numinst;
+ int *instlist;
+ char **namelist;
+ char *namebase;
+ int *stridx;
+ int i;
+ int k;
+ int allinbuf = 0;
+
+PM_FAULT_POINT("libpcp/" __FILE__ ":3", PM_FAULT_ALLOC);
+ if ((tbuf = (int *)malloc(rlen)) == NULL) {
+ sts = -oserror();
+ goto end;
+ }
+ if ((n = (int)fread(tbuf, 1, rlen, f)) != rlen) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: indom read -> %d: expected: %d\n",
+ n, rlen);
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ free(tbuf);
+ goto end;
+ }
+
+ k = 0;
+ when = (__pmTimeval *)&tbuf[k];
+ when->tv_sec = ntohl(when->tv_sec);
+ when->tv_usec = ntohl(when->tv_usec);
+ k += sizeof(*when)/sizeof(int);
+ indom = __ntohpmInDom((unsigned int)tbuf[k++]);
+ numinst = ntohl(tbuf[k++]);
+ if (numinst > 0) {
+ instlist = &tbuf[k];
+ k += numinst;
+ stridx = &tbuf[k];
+#if defined(HAVE_32BIT_PTR)
+ namelist = (char **)stridx;
+ allinbuf = 1; /* allocation is all in tbuf */
+#else
+ allinbuf = 0; /* allocation for namelist + tbuf */
+ /* need to allocate to hold the pointers */
+PM_FAULT_POINT("libpcp/" __FILE__ ":4", PM_FAULT_ALLOC);
+ namelist = (char **)malloc(numinst*sizeof(char*));
+ if (namelist == NULL) {
+ sts = -oserror();
+ free(tbuf);
+ goto end;
+ }
+#endif
+ k += numinst;
+ namebase = (char *)&tbuf[k];
+ for (i = 0; i < numinst; i++) {
+ instlist[i] = ntohl(instlist[i]);
+ namelist[i] = &namebase[ntohl(stridx[i])];
+ }
+ }
+ else {
+ /* no instances, or an error */
+ instlist = NULL;
+ namelist = NULL;
+ }
+ if ((sts = addindom(lcp, indom, when, numinst, instlist, namelist, tbuf, allinbuf)) < 0) {
+ free(tbuf);
+ if (allinbuf == 0)
+ free(namelist);
+ goto end;
+ }
+ }
+ else
+ fseek(f, (long)rlen, SEEK_CUR);
+ n = (int)fread(&check, 1, sizeof(check), f);
+ check = ntohl(check);
+ if (n != sizeof(check) || h.len != check) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: trailer read -> %d or len=%d: expected %d @ offset=%d\n",
+ n, check, h.len, (int)(ftell(f) - sizeof(check)));
+ }
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ }
+ else
+ sts = PM_ERR_LOGREC;
+ goto end;
+ }
+ }/*for*/
+end:
+
+ fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET);
+
+ if (sts == 0) {
+ if (numpmid == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "__pmLogLoadMeta: no metrics found?\n");
+ }
+#endif
+ sts = PM_ERR_LOGREC;
+ }
+ else
+ __pmFixPMNSHashTab(lcp->l_pmns, numpmid, 1);
+ }
+ return sts;
+}
+
+/*
+ * scan the hashed data structures to find a pmDesc, given a pmid
+ */
+int
+__pmLogLookupDesc(__pmLogCtl *lcp, pmID pmid, pmDesc *dp)
+{
+ __pmHashNode *hp;
+ pmDesc *tp;
+
+ if ((hp = __pmHashSearch((unsigned int)pmid, &lcp->l_hashpmid)) == NULL)
+ return PM_ERR_PMID_LOG;
+
+ tp = (pmDesc *)hp->data;
+ *dp = *tp; /* struct assignment */
+ return 0;
+}
+
+/*
+ * Add a new pmDesc into the metadata log, and to the hashed data structures
+ * If numnames is positive, then write out any associated PMNS names.
+ */
+int
+__pmLogPutDesc(__pmLogCtl *lcp, const pmDesc *dp, int numnames, char **names)
+{
+ FILE *f = lcp->l_mdfp;
+ pmDesc *tdp;
+ int olen; /* length to write out */
+ int i;
+ int sts;
+ int len;
+ typedef struct { /* skeletal external record */
+ __pmLogHdr hdr;
+ pmDesc desc;
+ int numnames; /* not present if numnames == 0 */
+ char data[0]; /* will be expanded */
+ } ext_t;
+ ext_t *out;
+
+ len = sizeof(__pmLogHdr) + sizeof(pmDesc) + LENSIZE;
+ if (numnames > 0) {
+ len += sizeof(numnames);
+ for (i = 0; i < numnames; i++)
+ len += LENSIZE + (int)strlen(names[i]);
+ }
+PM_FAULT_POINT("libpcp/" __FILE__ ":10", PM_FAULT_ALLOC);
+ if ((out = (ext_t *)malloc(len)) == NULL)
+ return -oserror();
+
+ out->hdr.len = htonl(len);
+ out->hdr.type = htonl(TYPE_DESC);
+ out->desc.type = htonl(dp->type);
+ out->desc.sem = htonl(dp->sem);
+ out->desc.indom = __htonpmInDom(dp->indom);
+ out->desc.units = __htonpmUnits(dp->units);
+ out->desc.pmid = __htonpmID(dp->pmid);
+
+ if (numnames > 0) {
+ char *op = (char *)&out->data;
+
+ out->numnames = htonl(numnames);
+
+ /* copy the names and length prefix */
+ for (i = 0; i < numnames; i++) {
+ int slen = (int)strlen(names[i]);
+ olen = htonl(slen);
+ memmove((void *)op, &olen, sizeof(olen));
+ op += sizeof(olen);
+ memmove((void *)op, names[i], slen);
+ op += slen;
+ }
+ /* trailer length */
+ memmove((void *)op, &out->hdr.len, sizeof(out->hdr.len));
+ }
+ else {
+ /* no names, trailer length lands on numnames in ext_t */
+ out->numnames = out->hdr.len;
+ }
+
+ if ((sts = fwrite(out, 1, len, f)) != len) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutDesc(...,pmid=%s,name=%s): write failed: returned %d expecting %d: %s\n",
+ pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)),
+ numnames > 0 ? names[0] : "<none>", len, sts,
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ free(out);
+ return -oserror();
+ }
+
+ free(out);
+
+ /*
+ * need to make a copy of the pmDesc, and add this, since caller
+ * may re-use *dp
+ */
+PM_FAULT_POINT("libpcp/" __FILE__ ":5", PM_FAULT_ALLOC);
+ if ((tdp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL)
+ return -oserror();
+ *tdp = *dp; /* struct assignment */
+ return __pmHashAdd((int)dp->pmid, (void *)tdp, &lcp->l_hashpmid);
+}
+
+static __pmLogInDom *
+searchindom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp)
+{
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ char strbuf[20];
+ fprintf(stderr, "searchindom( ..., %s, ", pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ StrTimeval(tp);
+ fprintf(stderr, ")\n");
+ }
+#endif
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL)
+ return NULL;
+
+ idp = (__pmLogInDom *)hp->data;
+ if (tp != NULL) {
+ for ( ; idp != NULL; idp = idp->next) {
+ /*
+ * need first one at or earlier than the requested time
+ */
+ if (__pmTimevalSub(&idp->stamp, tp) <= 0)
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "request @ ");
+ StrTimeval(tp);
+ fprintf(stderr, " is too early for indom @ ");
+ StrTimeval(&idp->stamp);
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ if (idp == NULL)
+ return NULL;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ fprintf(stderr, "success for indom @ ");
+ StrTimeval(&idp->stamp);
+ fputc('\n', stderr);
+ }
+#endif
+ return idp;
+}
+
+/*
+ * for the given indom retrieve the instance domain that is correct
+ * as of the latest time (tp == NULL) or at a designated
+ * time
+ */
+int
+__pmLogGetInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int **instlist, char ***namelist)
+{
+ __pmLogInDom *idp = searchindom(lcp, indom, tp);
+
+ if (idp == NULL)
+ return PM_ERR_INDOM_LOG;
+
+ *instlist = idp->instlist;
+ *namelist = idp->namelist;
+
+ return idp->numinst;
+}
+
+int
+__pmLogLookupInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp,
+ const char *name)
+{
+ __pmLogInDom *idp = searchindom(lcp, indom, tp);
+ int i;
+
+ if (idp == NULL)
+ return PM_ERR_INDOM_LOG;
+
+ if (idp->numinst < 0)
+ return idp->numinst;
+
+ /* full match */
+ for (i = 0; i < idp->numinst; i++) {
+ if (strcmp(name, idp->namelist[i]) == 0)
+ return idp->instlist[i];
+ }
+
+ /* half-baked match to first space */
+ for (i = 0; i < idp->numinst; i++) {
+ char *p = idp->namelist[i];
+ while (*p && *p != ' ')
+ p++;
+ if (*p == ' ') {
+ if (strncmp(name, idp->namelist[i], p - idp->namelist[i]) == 0)
+ return idp->instlist[i];
+ }
+ }
+
+ return PM_ERR_INST_LOG;
+}
+
+int
+__pmLogNameInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int inst, char **name)
+{
+ __pmLogInDom *idp = searchindom(lcp, indom, tp);
+ int i;
+
+ if (idp == NULL)
+ return PM_ERR_INDOM_LOG;
+
+ if (idp->numinst < 0)
+ return idp->numinst;
+
+ for (i = 0; i < idp->numinst; i++) {
+ if (inst == idp->instlist[i]) {
+ *name = idp->namelist[i];
+ return 0;
+ }
+ }
+
+ return PM_ERR_INST_LOG;
+}
+
+int
+__pmLogPutInDom(__pmLogCtl *lcp, pmInDom indom, const __pmTimeval *tp,
+ int numinst, int *instlist, char **namelist)
+{
+ int sts = 0;
+ int i;
+ int *inst;
+ int *stridx;
+ char *str;
+ int len;
+ typedef struct { /* skeletal external record */
+ __pmLogHdr hdr;
+ __pmTimeval stamp;
+ pmInDom indom;
+ int numinst;
+ char data[0]; /* inst[] then stridx[] then strings */
+ /* will be expanded if numinst > 0 */
+ } ext_t;
+ ext_t *out;
+
+ len = (int)sizeof(ext_t)
+ + (numinst > 0 ? numinst : 0) * ((int)sizeof(instlist[0]) + (int)sizeof(stridx[0]))
+ + LENSIZE;
+ for (i = 0; i < numinst; i++) {
+ len += (int)strlen(namelist[i]) + 1;
+ }
+
+PM_FAULT_POINT("libpcp/" __FILE__ ":6", PM_FAULT_ALLOC);
+ if ((out = (ext_t *)malloc(len)) == NULL)
+ return -oserror();
+
+ /* swab all output fields */
+ out->hdr.len = htonl(len);
+ out->hdr.type = htonl(TYPE_INDOM);
+ out->stamp.tv_sec = htonl(tp->tv_sec);
+ out->stamp.tv_usec = htonl(tp->tv_usec);
+ out->indom = __htonpmInDom(indom);
+ out->numinst = htonl(numinst);
+
+ inst = (int *)&out->data;
+ stridx = (int *)&inst[numinst];
+ str = (char *)&stridx[numinst];
+ for (i = 0; i < numinst; i++) {
+ int slen = strlen(namelist[i])+1;
+ inst[i] = htonl(instlist[i]);
+ memmove((void *)str, (void *)namelist[i], slen);
+ stridx[i] = htonl((int)((ptrdiff_t)str - (ptrdiff_t)&stridx[numinst]));
+ str += slen;
+ }
+ /* trailer length */
+ memmove((void *)str, &out->hdr.len, sizeof(out->hdr.len));
+
+ if ((sts = fwrite(out, 1, len, lcp->l_mdfp)) != len) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutInDom(...,indom=%s,numinst=%d): write failed: returned %d expecting %d: %s\n",
+ pmInDomStr_r(indom, strbuf, sizeof(strbuf)), numinst, len, sts,
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ free(out);
+ return -oserror();
+ }
+ free(out);
+
+ sts = addindom(lcp, indom, tp, numinst, instlist, namelist, NULL, 0);
+
+ return sts;
+}
+
+int
+pmLookupInDomArchive(pmInDom indom, const char *name)
+{
+ int n;
+ int j;
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+ __pmContext *ctxp;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((n = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(n);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type != PM_CONTEXT_ARCHIVE) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_NOTARCHIVE;
+ }
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_INDOM_LOG;
+ }
+
+ for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) {
+ /* full match */
+ for (j = 0; j < idp->numinst; j++) {
+ if (strcmp(name, idp->namelist[j]) == 0) {
+ PM_UNLOCK(ctxp->c_lock);
+ return idp->instlist[j];
+ }
+ }
+ /* half-baked match to first space */
+ for (j = 0; j < idp->numinst; j++) {
+ char *p = idp->namelist[j];
+ while (*p && *p != ' ')
+ p++;
+ if (*p == ' ') {
+ if (strncmp(name, idp->namelist[j], p - idp->namelist[j]) == 0) {
+ PM_UNLOCK(ctxp->c_lock);
+ return idp->instlist[j];
+ }
+ }
+ }
+ }
+ n = PM_ERR_INST_LOG;
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
+
+int
+pmNameInDomArchive(pmInDom indom, int inst, char **name)
+{
+ int n;
+ int j;
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+ __pmContext *ctxp;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((n = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(n);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type != PM_CONTEXT_ARCHIVE) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_NOTARCHIVE;
+ }
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_INDOM_LOG;
+ }
+
+ for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) {
+ for (j = 0; j < idp->numinst; j++) {
+ if (idp->instlist[j] == inst) {
+ if ((*name = strdup(idp->namelist[j])) == NULL)
+ n = -oserror();
+ else
+ n = 0;
+ PM_UNLOCK(ctxp->c_lock);
+ return n;
+ }
+ }
+ }
+ n = PM_ERR_INST_LOG;
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
+
+int
+pmGetInDomArchive(pmInDom indom, int **instlist, char ***namelist)
+{
+ int n;
+ int i;
+ int j;
+ char *p;
+ __pmContext *ctxp;
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+ int numinst = 0;
+ int strsize = 0;
+ int *ilist = NULL;
+ char **nlist = NULL;
+ char **olist;
+
+ /* avoid ambiguity when no instances or errors */
+ *instlist = NULL;
+ *namelist = NULL;
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((n = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(n);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type != PM_CONTEXT_ARCHIVE) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_NOTARCHIVE;
+ }
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &ctxp->c_archctl->ac_log->l_hashindom)) == NULL) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_INDOM_LOG;
+ }
+
+ for (idp = (__pmLogInDom *)hp->data; idp != NULL; idp = idp->next) {
+ for (j = 0; j < idp->numinst; j++) {
+ for (i = 0; i < numinst; i++) {
+ if (idp->instlist[j] == ilist[i])
+ break;
+ }
+ if (i == numinst) {
+ numinst++;
+PM_FAULT_POINT("libpcp/" __FILE__ ":7", PM_FAULT_ALLOC);
+ if ((ilist = (int *)realloc(ilist, numinst*sizeof(ilist[0]))) == NULL) {
+ __pmNoMem("pmGetInDomArchive: ilist", numinst*sizeof(ilist[0]), PM_FATAL_ERR);
+ }
+PM_FAULT_POINT("libpcp/" __FILE__ ":8", PM_FAULT_ALLOC);
+ if ((nlist = (char **)realloc(nlist, numinst*sizeof(nlist[0]))) == NULL) {
+ __pmNoMem("pmGetInDomArchive: nlist", numinst*sizeof(nlist[0]), PM_FATAL_ERR);
+ }
+ ilist[numinst-1] = idp->instlist[j];
+ nlist[numinst-1] = idp->namelist[j];
+ strsize += strlen(idp->namelist[j])+1;
+ }
+ }
+ }
+PM_FAULT_POINT("libpcp/" __FILE__ ":9", PM_FAULT_ALLOC);
+ if ((olist = (char **)malloc(numinst*sizeof(olist[0]) + strsize)) == NULL) {
+ __pmNoMem("pmGetInDomArchive: olist", numinst*sizeof(olist[0]) + strsize, PM_FATAL_ERR);
+ }
+ p = (char *)olist;
+ p += numinst * sizeof(olist[0]);
+ for (i = 0; i < numinst; i++) {
+ olist[i] = p;
+ strcpy(p, nlist[i]);
+ p += strlen(nlist[i]) + 1;
+ }
+ free(nlist);
+ *instlist = ilist;
+ *namelist = olist;
+ n = numinst;
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return n;
+}
diff --git a/src/libpcp/src/logportmap.c b/src/libpcp/src/logportmap.c
new file mode 100644
index 0000000..9319bc4
--- /dev/null
+++ b/src/libpcp/src/logportmap.c
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <ctype.h>
+
+static __pmLogPort *logport;
+ /* array of all known pmlogger ports */
+static int nlogports; /* no. of elements used in logports array */
+static int szlogport; /* size of logport array */
+
+/* Make sure the logports array is large enough to hold newsize entries. Free
+ * any currently allocated names and zero the first newsize entries.
+ */
+static int
+resize_logports(int newsize)
+{
+ int i;
+ int need;
+
+ if (nlogports) {
+ for (i = 0; i < nlogports; i++) {
+ if (logport[i].pmcd_host != NULL)
+ free(logport[i].pmcd_host);
+ if (logport[i].archive != NULL)
+ free(logport[i].archive);
+ if (logport[i].name != NULL)
+ free(logport[i].name);
+ }
+ memset(logport, 0, nlogports * sizeof(__pmLogPort));
+ }
+ nlogports = 0;
+ if (szlogport >= newsize)
+ return 0;
+ free(logport);
+ need = newsize * (int)sizeof(__pmLogPort);
+ if ((logport = (__pmLogPort *)malloc(need)) == NULL) {
+ szlogport = 0;
+ return -1;
+ }
+ memset(logport, 0, need);
+ szlogport = newsize;
+ return 0;
+}
+
+/* Used by scandir to determine which files are pmlogger port files. The valid
+ * files are numbers (pids) or PM_LOG_PRIMARY_LINK for the primary logger.
+ */
+static int
+is_portfile(const_dirent *dep)
+{
+ char *endp;
+ pid_t pid;
+
+ pid = (pid_t)strtol(dep->d_name, &endp, 10);
+ if (pid > (pid_t)1)
+ return __pmProcessExists(pid);
+ return strcmp(dep->d_name, "primary") == 0;
+}
+
+/* The following function is used for selecting particular port files rather
+ * than all valid files. snprintf the pid of the pmlogger process or the
+ * special constant PM_LOG_PRIMARY_LINK into the match array first.
+ */
+#define PROCFS_ENTRY_SIZE 40 /* encompass any size of entry for pid */
+static char match[PROCFS_ENTRY_SIZE];
+
+static int
+is_match(const_dirent *dep)
+{
+ return strcmp(match, dep->d_name) == 0;
+}
+
+/* Return (in result) a list of active pmlogger ports on the local machine.
+ * The return value of the function is the number of elements in the array.
+ * The caller must NOT free any part of the result stucture, it's storage is
+ * managed here. Subsequent calls will overwrite the data so the caller should
+ * copy it if persistence is required.
+ */
+int
+__pmLogFindLocalPorts(int pid, __pmLogPort **result)
+{
+ char dir[MAXPATHLEN];
+ int lendir;
+ int i, j, n;
+ int nf; /* number of port files found */
+ struct dirent **files = NULL; /* array of port file dirents */
+ char *p;
+ int len;
+ char namebuf[MAXPATHLEN];
+ int (*scanfn)(const_dirent *dep);
+ FILE *pfile;
+ char buf[MAXPATHLEN];
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_LOGPORT))
+ return PM_ERR_THREAD;
+
+ if (result == NULL)
+ return -EINVAL;
+
+ lendir = snprintf(dir, sizeof(dir), "%s%cpmlogger",
+ pmGetConfig("PCP_TMP_DIR"), __pmPathSeparator());
+
+ /* Set up the appropriate function to select files from the control port
+ * directory. Anticipate that this will usually be an exact match for
+ * the primary logger control port.
+ */
+ scanfn = is_match;
+ switch (pid) {
+ case PM_LOG_PRIMARY_PID: /* primary logger control (single) */
+ strcpy(match, "primary");
+ break;
+
+ case PM_LOG_ALL_PIDS: /* find all ports */
+ scanfn = is_portfile;
+ break;
+
+ default: /* a specific pid (single) */
+ if (!__pmProcessExists((pid_t)pid)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "__pmLogFindLocalPorts() -> 0, "
+ "pid(%d) doesn't exist\n", pid);
+ }
+#endif
+ *result = NULL;
+ return 0;
+ }
+ snprintf(match, sizeof(match), "%d", pid);
+ break;
+ }
+
+ nf = scandir(dir, &files, scanfn, alphasort);
+#ifdef PCP_DEBUG
+ if (nf < 1 && (pmDebug & DBG_TRACE_LOG)) {
+ fprintf(stderr, "__pmLogFindLocalPorts: scandir() -> %d %s\n",
+ nf, pmErrStr(oserror()));
+ }
+#endif
+ if (nf == -1 && oserror() == ENOENT)
+ nf = 0;
+ else if (nf == -1) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogFindLocalPorts: scandir: %s\n", osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ return -oserror();
+ }
+ if (resize_logports(nf) < 0) {
+ for (i=0; i < nf; i++)
+ free(files[i]);
+ free(files);
+ return -oserror();
+ }
+ if (nf == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "__pmLogFindLocalPorts() -> 0, "
+ "num files = 0\n");
+ }
+#endif
+ *result = NULL;
+ free(files);
+ return 0;
+ }
+
+ /* make a buffer for the longest complete pathname found */
+ len = (int)strlen(files[0]->d_name);
+ for (i = 1; i < nf; i++)
+ if ((j = (int)strlen(files[i]->d_name)) > len)
+ len = j;
+ /* +1 for trailing path separator, +1 for null termination */
+ len += lendir + 2;
+
+ /* namebuf is the complete pathname, p points to the trailing filename
+ * within namebuf.
+ */
+ strcpy(namebuf, dir);
+ p = namebuf + lendir;
+ *p++ = __pmPathSeparator();
+
+ /* open the file, try to read the port number and add the port to the
+ * logport array if successful.
+ */
+ for (i = 0; i < nf; i++) {
+ char *fname = files[i]->d_name;
+ int err = 0;
+ __pmLogPort *lpp = &logport[nlogports];
+
+ strcpy(p, fname);
+ if ((pfile = fopen(namebuf, "r")) == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: %s\n",
+ namebuf, osstrerror_r(errmsg, sizeof(errmsg)));
+ free(files[i]);
+ pmflush();
+ continue;
+ }
+ if (!err && fgets(buf, MAXPATHLEN, pfile) == NULL) {
+ if (feof(pfile)) {
+ clearerr(pfile);
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s empty!\n",
+ namebuf);
+ }
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: %s\n",
+ namebuf, osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ err = 1;
+ }
+ else {
+ char *endp;
+
+ lpp->port = (int)strtol(buf, &endp, 10);
+ if (*endp != '\n') {
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no port number\n",
+ namebuf);
+ err = 1;
+ }
+ else {
+ lpp->pid = (int)strtol(fname, &endp, 10);
+ if (*endp != '\0') {
+ if (strcmp(fname, "primary") == 0)
+ lpp->pid = PM_LOG_PRIMARY_PORT;
+ else {
+ pmprintf("__pmLogFindLocalPorts: unrecognised pmlogger port file %s\n",
+ namebuf);
+ err = 1;
+ }
+ }
+ }
+ }
+ if (err) {
+ pmflush();
+ fclose(pfile);
+ }
+ else {
+ if (fgets(buf, MAXPATHLEN, pfile) == NULL) {
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no PMCD host name\n",
+ namebuf);
+ pmflush();
+ }
+ else {
+ char *q = strchr(buf, '\n');
+ if (q != NULL)
+ *q = '\0';
+ lpp->pmcd_host = strdup(buf);
+ if (fgets(buf, MAXPATHLEN, pfile) == NULL) {
+ pmprintf("__pmLogFindLocalPorts: pmlogger port file %s: no archive base pathname\n",
+ namebuf);
+ pmflush();
+ }
+ else {
+ char *q = strchr(buf, '\n');
+ if (q != NULL)
+ *q = '\0';
+ lpp->archive = strdup(buf);
+ }
+ }
+ fclose(pfile);
+ if ((lpp->name = strdup(fname)) != NULL)
+ nlogports++;
+ else {
+ if (lpp->pmcd_host != NULL) {
+ free(lpp->pmcd_host);
+ lpp->pmcd_host = NULL;
+ }
+ if (lpp->archive != NULL) {
+ free(lpp->archive);
+ lpp->archive = NULL;
+ }
+ break;
+ }
+ }
+ free(files[i]);
+ }
+
+ if (i == nf) { /* all went well */
+ n = nlogports;
+ *result = logport;
+ }
+ else { /* strdup error on fname, clean up */
+ *result = NULL;
+ for (j = i; j < nf; j++)
+ free(files[j]);
+ n = -oserror();
+ }
+ free(files);
+ return n;
+}
+
+/*
+ * Return 1 if hostname corresponds to the current host, 0 if not and < 0 for
+ * an error.
+ */
+int
+__pmIsLocalhost(const char *hostname)
+{
+ int sts = 0;
+
+ if (strcasecmp(hostname, "localhost") == 0 ||
+ strncmp(hostname, "local:", 6) == 0 ||
+ strncmp(hostname, "unix:", 5) == 0)
+ return 1;
+ else {
+ char lhost[MAXHOSTNAMELEN+1];
+ __pmHostEnt *servInfo1;
+
+ if (gethostname(lhost, MAXHOSTNAMELEN) < 0)
+ return -oserror();
+
+ if ((servInfo1 = __pmGetAddrInfo(lhost)) != NULL) {
+ __pmHostEnt *servInfo2;
+ __pmSockAddr *addr1, *addr2;
+ void *enumIx1, *enumIx2;
+
+ if ((servInfo2 = __pmGetAddrInfo(hostname)) == NULL) {
+ __pmHostEntFree(servInfo1);
+ return -EHOSTUNREACH;
+ }
+ enumIx1 = NULL;
+ for (addr1 = __pmHostEntGetSockAddr(servInfo1, &enumIx1);
+ addr1 != NULL;
+ addr1 = __pmHostEntGetSockAddr(servInfo1, &enumIx1)) {
+ enumIx2 = NULL;
+ for (addr2 = __pmHostEntGetSockAddr(servInfo2, &enumIx2);
+ addr2 != NULL;
+ addr2 = __pmHostEntGetSockAddr(servInfo2, &enumIx2)) {
+ if (__pmSockAddrCompare(addr1, addr2) == 0) {
+ __pmHostEntFree(servInfo1);
+ __pmHostEntFree(servInfo2);
+ return 1;
+ }
+ }
+ }
+ __pmHostEntFree(servInfo1);
+ __pmHostEntFree(servInfo2);
+ }
+ }
+
+ return sts;
+}
+
+/* Return (in result) a list of active pmlogger ports on the specified machine.
+ * The return value of the function is the number of elements in the array.
+ * The caller must NOT free any part of the result stucture, it's storage is
+ * managed here. Subsequent calls will overwrite the data so the caller should
+ * copy it if persistence is required.
+ */
+int
+__pmLogFindPort(const char *host, int pid, __pmLogPort **lpp)
+{
+ int ctx, oldctx;
+ char *ctxhost;
+ int sts, numval;
+ int i, j;
+ int findone = pid != PM_LOG_ALL_PIDS;
+ int localcon = 0; /* > 0 for local connection */
+ pmDesc desc;
+ pmResult *res;
+ char *namelist[] = {"pmcd.pmlogger.port"};
+ pmID pmid;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_LOGPORT))
+ return PM_ERR_THREAD;
+
+ *lpp = NULL; /* pass null back in event of error */
+ localcon = __pmIsLocalhost(host);
+ if (localcon > 0)
+ /* do the work here instead of making PMCD do it */
+ return __pmLogFindLocalPorts(pid, lpp);
+ else if (localcon < 0)
+ return localcon;
+
+ /* note: there may not be a current context */
+ ctx = 0;
+ oldctx = pmWhichContext();
+
+ /*
+ * Enclose ctxhost in [] in case it is an ipv6 address. This prevents
+ * the first colon from being taken as a port separator by pmNewContext
+ * and does no harm otherwise.
+ */
+ ctxhost = malloc(strlen(host) + 2 + 1);
+ if (ctxhost == NULL) {
+ sts = -ENOMEM;
+ goto ctxErr;
+ }
+ sprintf(ctxhost, "[%s]", host);
+ ctx = pmNewContext(PM_CONTEXT_HOST, ctxhost);
+ free(ctxhost);
+ if (ctx < 0)
+ return ctx;
+ if ((sts = pmLookupName(1, namelist, &pmid)) < 0)
+ goto ctxErr;
+
+ if ((sts = pmLookupDesc(pmid, &desc)) < 0)
+ goto ctxErr;
+ if ((sts = pmFetch(1, &pmid, &res) < 0))
+ goto ctxErr;
+ if ((sts = numval = res->vset[0]->numval) < 0)
+ goto resErr;
+ j = 0;
+ if (numval) {
+ if (resize_logports(findone ? 1 : numval) < 0) {
+ sts = -oserror();
+ goto resErr;
+ }
+ /* scan the pmResult, copying matching pid(s) to logport */
+ for (i = j = 0; i < numval; i++) {
+ __pmLogPort *p = &logport[j];
+ pmValue *vp = &res->vset[0]->vlist[i];
+
+ if (vp->inst == 1) /* old vcr instance (pseudo-init) */
+ continue;
+ if (findone && vp->inst != pid)
+ continue;
+ p->pid = vp->inst;
+ p->port = vp->value.lval;
+ sts = pmNameInDom(desc.indom, p->pid, &p->name);
+ if (sts < 0) {
+ p->name = NULL;
+ goto resErr;
+ }
+ j++;
+ if (findone) /* found one, stop searching */
+ break;
+ }
+ *lpp = logport;
+ }
+ sts = j; /* the number actually added */
+
+resErr:
+ pmFreeResult(res);
+ctxErr:
+ if (oldctx >= 0)
+ pmUseContext(oldctx);
+ if (ctx >= 0)
+ pmDestroyContext(ctx);
+ return sts;
+}
diff --git a/src/libpcp/src/logutil.c b/src/libpcp/src/logutil.c
new file mode 100644
index 0000000..9b577cb
--- /dev/null
+++ b/src/libpcp/src/logutil.c
@@ -0,0 +1,2461 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes:
+ *
+ * __pmLogReads is a diagnostic counter that is maintained with
+ * non-atomic updates ... we've decided that it is acceptable for the
+ * value to be subject to possible (but unlikely) missed updates
+ */
+
+#include <inttypes.h>
+#include <assert.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+
+INTERN int __pmLogReads;
+
+/*
+ * Suffixes and associated compresssion application for compressed filenames.
+ * These can appear _after_ the volume number in the name of a file for an
+ * archive metric log file, e.g. /var/log/pmlogger/myhost/20101219.0.bz2
+ */
+#define USE_NONE 0
+#define USE_BZIP2 1
+#define USE_GZIP 2
+#define USE_XZ 3
+static const struct {
+ const char *suff;
+ const int appl;
+} compress_ctl[] = {
+ { ".bz2", USE_BZIP2 },
+ { ".bz", USE_BZIP2 },
+ { ".gz", USE_GZIP },
+ { ".Z", USE_GZIP },
+ { ".z", USE_GZIP },
+ { ".lzma", USE_XZ },
+ { ".xz", USE_XZ },
+};
+static const int ncompress = sizeof(compress_ctl) / sizeof(compress_ctl[0]);
+
+/*
+ * first two fields are made to look like a pmValueSet when no values are
+ * present ... used to populate the pmValueSet in a pmResult when values
+ * for particular metrics are not available from this log record.
+ */
+typedef struct {
+ pmID pc_pmid;
+ int pc_numval; /* MUST be 0 */
+ /* value control for interpolation */
+} pmid_ctl;
+
+/*
+ * Hash control for requested metrics, used to construct 'No values'
+ * result when the corresponding metric is requested but there is
+ * no values available in the pmResult
+ *
+ * Note, this hash table is global across all contexts.
+ */
+static __pmHashCtl pc_hc;
+
+#ifdef PCP_DEBUG
+static void
+dumpbuf(int nch, __pmPDU *pb)
+{
+ int i, j;
+
+ nch /= sizeof(__pmPDU);
+ fprintf(stderr, "%03d: ", 0);
+ for (j = 0, i = 0; j < nch; j++) {
+ if (i == 8) {
+ fprintf(stderr, "\n%03d: ", j);
+ i = 0;
+ }
+ fprintf(stderr, "%8x ", pb[j]);
+ i++;
+ }
+ fputc('\n', stderr);
+}
+#endif
+
+int
+__pmLogChkLabel(__pmLogCtl *lcp, FILE *f, __pmLogLabel *lp, int vol)
+{
+ int len;
+ int version = UNKNOWN_VERSION;
+ int xpectlen = sizeof(__pmLogLabel) + 2 * sizeof(len);
+ int n;
+
+ if (vol >= 0 && vol < lcp->l_numseen && lcp->l_seen[vol]) {
+ /* FastPath, cached result of previous check for this volume */
+ fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET);
+ return 0;
+ }
+
+ if (vol >= 0 && vol >= lcp->l_numseen) {
+ lcp->l_seen = (int *)realloc(lcp->l_seen, (vol+1)*(int)sizeof(lcp->l_seen[0]));
+ if (lcp->l_seen == NULL)
+ lcp->l_numseen = 0;
+ else {
+ int i;
+ for (i = lcp->l_numseen; i < vol; i++)
+ lcp->l_seen[i] = 0;
+ lcp->l_numseen = vol+1;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogChkLabel: fd=%d vol=%d", fileno(f), vol);
+#endif
+
+ fseek(f, (long)0, SEEK_SET);
+ n = (int)fread(&len, 1, sizeof(len), f);
+ len = ntohl(len);
+ if (n != sizeof(len) || len != xpectlen) {
+ if (feof(f)) {
+ clearerr(f);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " file is empty\n");
+#endif
+ return PM_ERR_NODATA;
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " header read -> %d (expect %d) or bad header len=%d (expected %d)\n",
+ n, (int)sizeof(len), len, xpectlen);
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ return -oserror();
+ }
+ else
+ return PM_ERR_LABEL;
+ }
+ }
+
+ if ((n = (int)fread(lp, 1, sizeof(__pmLogLabel), f)) != sizeof(__pmLogLabel)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " bad label len=%d: expected %d\n",
+ n, (int)sizeof(__pmLogLabel));
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ return -oserror();
+ }
+ else
+ return PM_ERR_LABEL;
+ }
+ else {
+ /* swab internal log label */
+ lp->ill_magic = ntohl(lp->ill_magic);
+ lp->ill_pid = ntohl(lp->ill_pid);
+ lp->ill_start.tv_sec = ntohl(lp->ill_start.tv_sec);
+ lp->ill_start.tv_usec = ntohl(lp->ill_start.tv_usec);
+ lp->ill_vol = ntohl(lp->ill_vol);
+ }
+
+ n = (int)fread(&len, 1, sizeof(len), f);
+ len = ntohl(len);
+ if (n != sizeof(len) || len != xpectlen) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " trailer read -> %d (expect %d) or bad trailer len=%d (expected %d)\n",
+ n, (int)sizeof(len), len, xpectlen);
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ return -oserror();
+ }
+ else
+ return PM_ERR_LABEL;
+ }
+
+ version = lp->ill_magic & 0xff;
+ if ((lp->ill_magic & 0xffffff00) != PM_LOG_MAGIC ||
+ (version != PM_LOG_VERS02) || lp->ill_vol != vol) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if ((lp->ill_magic & 0xffffff00) != PM_LOG_MAGIC)
+ fprintf(stderr, " label magic 0x%x not 0x%x as expected", (lp->ill_magic & 0xffffff00), PM_LOG_MAGIC);
+ if (version != PM_LOG_VERS02)
+ fprintf(stderr, " label version %d not supported", version);
+ if (lp->ill_vol != vol)
+ fprintf(stderr, " label volume %d not %d as expected", lp->ill_vol, vol);
+ fputc('\n', stderr);
+ }
+#endif
+ return PM_ERR_LABEL;
+ }
+ else {
+ if (__pmSetVersionIPC(fileno(f), version) < 0)
+ return -oserror();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " [magic=%8x version=%d vol=%d pid=%d host=%s]\n",
+ lp->ill_magic, version, lp->ill_vol, lp->ill_pid, lp->ill_hostname);
+#endif
+ }
+
+ if (vol >= 0 && vol < lcp->l_numseen)
+ lcp->l_seen[vol] = 1;
+
+ return version;
+}
+
+static int
+popen_uncompress(const char *cmd, const char *fname, const char *suffix, int fd)
+{
+ char pipecmd[2*MAXPATHLEN+2];
+ char buffer[4096];
+ FILE *finp;
+ ssize_t bytes;
+ int sts, infd;
+
+ snprintf(pipecmd, sizeof(pipecmd), "%s %s%s", cmd, fname, suffix);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogOpen: uncompress using: %s\n", pipecmd);
+#endif
+
+ if ((finp = popen(pipecmd, "r")) == NULL)
+ return -1;
+ infd = fileno(finp);
+
+ while ((bytes = read(infd, buffer, sizeof(buffer))) > 0) {
+ if (write(fd, buffer, bytes) != bytes) {
+ bytes = -1;
+ break;
+ }
+ }
+
+ if ((sts = pclose(finp)) != 0)
+ return sts;
+ return (bytes == 0) ? 0 : -1;
+}
+
+static FILE *
+fopen_compress(const char *fname)
+{
+ int sts;
+ int fd;
+ int i;
+ char *cmd;
+ char *msg;
+ FILE *fp;
+ char tmpname[MAXPATHLEN];
+ mode_t cur_umask;
+
+ for (i = 0; i < ncompress; i++) {
+ snprintf(tmpname, sizeof(tmpname), "%s%s", fname, compress_ctl[i].suff);
+ if (access(tmpname, R_OK) == 0) {
+ break;
+ }
+ }
+ if (i == ncompress) {
+ /* end up here if it does not look like a compressed file */
+ return NULL;
+ }
+ if (compress_ctl[i].appl == USE_BZIP2)
+ cmd = "bzip2 -dc";
+ else if (compress_ctl[i].appl == USE_GZIP)
+ cmd = "gzip -dc";
+ else if (compress_ctl[i].appl == USE_XZ)
+ cmd = "xz -dc";
+ else {
+ /* botch in compress_ctl[] ... should not happen */
+ return NULL;
+ }
+
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+#if HAVE_MKSTEMP
+ snprintf(tmpname, sizeof(tmpname),
+ "%s/XXXXXX", pmGetConfig("PCP_TMPFILE_DIR"));
+ msg = tmpname;
+ fd = mkstemp(tmpname);
+#else
+ if ((msg = tmpnam(NULL)) == NULL) {
+ umask(cur_umask);
+ return NULL;
+ }
+ fd = open(msg, O_RDWR|O_CREAT|O_EXCL, 0600);
+#endif
+ /*
+ * unlink temporary file to avoid namespace pollution and allow O/S
+ * space cleanup on last close
+ */
+ unlink(msg);
+ umask(cur_umask);
+
+ if (fd < 0) {
+ sts = oserror();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogOpen: temp file create failed: %s\n", osstrerror());
+#endif
+ setoserror(sts);
+ return NULL;
+ }
+
+ sts = popen_uncompress(cmd, fname, compress_ctl[i].suff, fd);
+ if (sts == -1) {
+ sts = oserror();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmLogOpen: uncompress command failed: %s\n", osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+#endif
+ close(fd);
+ setoserror(sts);
+ return NULL;
+ }
+ if (sts != 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+#if defined(HAVE_SYS_WAIT_H)
+ if (WIFEXITED(sts))
+ fprintf(stderr, "__pmLogOpen: uncompress failed, exit status: %d\n", WEXITSTATUS(sts));
+ else if (WIFSIGNALED(sts))
+ fprintf(stderr, "__pmLogOpen: uncompress failed, signal: %d\n", WTERMSIG(sts));
+ else
+#endif
+ fprintf(stderr, "__pmLogOpen: uncompress failed, popen() returns: %d\n", sts);
+ }
+#endif
+ close(fd);
+ /* not a great error code, but the best we can do */
+ setoserror(-PM_ERR_LOGREC);
+ return NULL;
+ }
+ if ((fp = fdopen(fd, "r")) == NULL) {
+ sts = oserror();
+ close(fd);
+ setoserror(sts);
+ return NULL;
+ }
+ /* success */
+ return fp;
+}
+
+static FILE *
+_logpeek(__pmLogCtl *lcp, int vol)
+{
+ int sts;
+ FILE *f;
+ __pmLogLabel label;
+ char fname[MAXPATHLEN];
+
+ snprintf(fname, sizeof(fname), "%s.%d", lcp->l_name, vol);
+ if ((f = fopen(fname, "r")) == NULL) {
+ if ((f = fopen_compress(fname)) == NULL)
+ return f;
+ }
+
+ if ((sts = __pmLogChkLabel(lcp, f, &label, vol)) < 0) {
+ fclose(f);
+ setoserror(sts);
+ return NULL;
+ }
+
+ return f;
+}
+
+int
+__pmLogChangeVol(__pmLogCtl *lcp, int vol)
+{
+ char name[MAXPATHLEN];
+ int sts;
+
+ if (lcp->l_curvol == vol)
+ return 0;
+
+ if (lcp->l_mfp != NULL) {
+ __pmResetIPC(fileno(lcp->l_mfp));
+ fclose(lcp->l_mfp);
+ }
+ snprintf(name, sizeof(name), "%s.%d", lcp->l_name, vol);
+ if ((lcp->l_mfp = fopen(name, "r")) == NULL) {
+ /* try for a compressed file */
+ if ((lcp->l_mfp = fopen_compress(name)) == NULL)
+ return -oserror();
+ }
+
+ if ((sts = __pmLogChkLabel(lcp, lcp->l_mfp, &lcp->l_label, vol)) < 0)
+ return sts;
+
+ lcp->l_curvol = vol;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogChangeVol: change to volume %d\n", vol);
+#endif
+ return sts;
+}
+
+int
+__pmLogLoadIndex(__pmLogCtl *lcp)
+{
+ int sts = 0;
+ FILE *f = lcp->l_tifp;
+ int n;
+ __pmLogTI *tip;
+
+ lcp->l_numti = 0;
+ lcp->l_ti = NULL;
+
+ if (lcp->l_tifp != NULL) {
+ fseek(f, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET);
+ for ( ; ; ) {
+ lcp->l_ti = (__pmLogTI *)realloc(lcp->l_ti, (1 + lcp->l_numti) * sizeof(__pmLogTI));
+ if (lcp->l_ti == NULL) {
+ sts = -oserror();
+ break;
+ }
+ tip = &lcp->l_ti[lcp->l_numti];
+ n = (int)fread(tip, 1, sizeof(__pmLogTI), f);
+
+ if (n != sizeof(__pmLogTI)) {
+ if (feof(f)) {
+ clearerr(f);
+ sts = 0;
+ break;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogLoadIndex: bad TI entry len=%d: expected %d\n",
+ n, (int)sizeof(__pmLogTI));
+#endif
+ if (ferror(f)) {
+ clearerr(f);
+ sts = -oserror();
+ break;
+ }
+ else {
+ sts = PM_ERR_LOGREC;
+ break;
+ }
+ }
+ else {
+ /* swab the temporal index record */
+ tip->ti_stamp.tv_sec = ntohl(tip->ti_stamp.tv_sec);
+ tip->ti_stamp.tv_usec = ntohl(tip->ti_stamp.tv_usec);
+ tip->ti_vol = ntohl(tip->ti_vol);
+ tip->ti_meta = ntohl(tip->ti_meta);
+ tip->ti_log = ntohl(tip->ti_log);
+ }
+
+ lcp->l_numti++;
+ }/*for*/
+ }/*not null*/
+
+ return sts;
+}
+
+const char *
+__pmLogName_r(const char *base, int vol, char *buf, int buflen)
+{
+ switch (vol) {
+ case PM_LOG_VOL_TI:
+ snprintf(buf, buflen, "%s.index", base);
+ break;
+
+ case PM_LOG_VOL_META:
+ snprintf(buf, buflen, "%s.meta", base);
+ break;
+
+ default:
+ snprintf(buf, buflen, "%s.%d", base, vol);
+ break;
+ }
+
+ return buf;
+}
+
+const char *
+__pmLogName(const char *base, int vol)
+{
+ static char tbuf[MAXPATHLEN];
+
+ return __pmLogName_r(base, vol, tbuf, sizeof(tbuf));
+}
+
+FILE *
+__pmLogNewFile(const char *base, int vol)
+{
+ char fname[MAXPATHLEN];
+ FILE *f;
+ int save_error;
+
+ __pmLogName_r(base, vol, fname, sizeof(fname));
+
+ if (access(fname, R_OK) != -1) {
+ /* exists and readable ... */
+ pmprintf("__pmLogNewFile: \"%s\" already exists, not over-written\n", fname);
+ pmflush();
+ setoserror(EEXIST);
+ return NULL;
+ }
+
+ if ((f = fopen(fname, "w")) == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ save_error = oserror();
+ pmprintf("__pmLogNewFile: failed to create \"%s\": %s\n", fname, osstrerror_r(errmsg, sizeof(errmsg)));
+
+ pmflush();
+ setoserror(save_error);
+ return NULL;
+ }
+ /*
+ * Want unbuffered I/O for all files of the archive, so a single
+ * fwrite() maps to one logical record for each of the metadata
+ * records, the index records and the data (pmResult) records.
+ */
+ setvbuf(f, NULL, _IONBF, 0);
+
+ if ((save_error = __pmSetVersionIPC(fileno(f), PDU_VERSION)) < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogNewFile: failed to setup \"%s\": %s\n", fname, osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ fclose(f);
+ setoserror(save_error);
+ return NULL;
+ }
+
+ return f;
+}
+
+int
+__pmLogWriteLabel(FILE *f, const __pmLogLabel *lp)
+{
+ int sts = 0;
+ struct { /* skeletal external record */
+ int header;
+ __pmLogLabel label;
+ int trailer;
+ } out;
+
+ out.header = out.trailer = htonl((int)sizeof(out));
+
+ /* swab */
+ out.label.ill_magic = htonl(lp->ill_magic);
+ out.label.ill_pid = htonl(lp->ill_pid);
+ out.label.ill_start.tv_sec = htonl(lp->ill_start.tv_sec);
+ out.label.ill_start.tv_usec = htonl(lp->ill_start.tv_usec);
+ out.label.ill_vol = htonl(lp->ill_vol);
+ memmove((void *)out.label.ill_hostname, (void *)lp->ill_hostname, sizeof(lp->ill_hostname));
+ memmove((void *)out.label.ill_tz, (void *)lp->ill_tz, sizeof(lp->ill_tz));
+
+ if ((sts = fwrite(&out, 1, sizeof(out), f)) != sizeof(out)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogWriteLabel: write failed: returns %d expecting %d: %s\n",
+ sts, (int)sizeof(out), osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ sts = -oserror();
+ }
+ else
+ sts = 0;
+
+ return sts;
+}
+
+int
+__pmLogCreate(const char *host, const char *base, int log_version,
+ __pmLogCtl *lcp)
+{
+ int save_error = 0;
+ char fname[MAXPATHLEN];
+
+ lcp->l_minvol = lcp->l_maxvol = lcp->l_curvol = 0;
+ lcp->l_hashpmid.nodes = lcp->l_hashpmid.hsize = 0;
+ lcp->l_hashindom.nodes = lcp->l_hashindom.hsize = 0;
+ lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL;
+
+ if ((lcp->l_tifp = __pmLogNewFile(base, PM_LOG_VOL_TI)) != NULL) {
+ if ((lcp->l_mdfp = __pmLogNewFile(base, PM_LOG_VOL_META)) != NULL) {
+ if ((lcp->l_mfp = __pmLogNewFile(base, 0)) != NULL) {
+ char tzbuf[PM_TZ_MAXLEN];
+ char *tz;
+ int sts;
+
+ tz = __pmTimezone_r(tzbuf, sizeof(tzbuf));
+
+ lcp->l_label.ill_magic = PM_LOG_MAGIC | log_version;
+ /*
+ * Warning ill_hostname may be truncated, but we
+ * guarantee it will be null-byte terminated
+ */
+ strncpy(lcp->l_label.ill_hostname, host, PM_LOG_MAXHOSTLEN-1);
+ lcp->l_label.ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0';
+ lcp->l_label.ill_pid = (int)getpid();
+ /*
+ * hack - how do you get the TZ for a remote host?
+ */
+ strcpy(lcp->l_label.ill_tz, tz ? tz : "");
+ lcp->l_state = PM_LOG_STATE_NEW;
+
+ /*
+ * __pmLogNewFile sets the IPC version to PDU_VERSION
+ * we want log_version instead
+ */
+ sts = __pmSetVersionIPC(fileno(lcp->l_tifp), log_version);
+ if (sts < 0)
+ return sts;
+ sts = __pmSetVersionIPC(fileno(lcp->l_mdfp), log_version);
+ if (sts < 0)
+ return sts;
+ sts = __pmSetVersionIPC(fileno(lcp->l_mfp), log_version);
+ return sts;
+ }
+ else {
+ save_error = oserror();
+ unlink(__pmLogName_r(base, PM_LOG_VOL_TI, fname, sizeof(fname)));
+ unlink(__pmLogName_r(base, PM_LOG_VOL_META, fname, sizeof(fname)));
+ setoserror(save_error);
+ }
+ }
+ else {
+ save_error = oserror();
+ unlink(__pmLogName_r(base, PM_LOG_VOL_TI, fname, sizeof(fname)));
+ setoserror(save_error);
+ }
+ }
+
+ lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL;
+ return oserror() ? -oserror() : -EPERM;
+}
+
+/*
+ * Close the log files.
+ * Free up the space used by __pmLogCtl.
+ */
+
+void
+__pmLogClose(__pmLogCtl *lcp)
+{
+ if (lcp->l_tifp != NULL) {
+ __pmResetIPC(fileno(lcp->l_tifp));
+ fclose(lcp->l_tifp);
+ lcp->l_tifp = NULL;
+ }
+ if (lcp->l_mdfp != NULL) {
+ __pmResetIPC(fileno(lcp->l_mdfp));
+ fclose(lcp->l_mdfp);
+ lcp->l_mdfp = NULL;
+ }
+ if (lcp->l_mfp != NULL) {
+ __pmResetIPC(fileno(lcp->l_mfp));
+ fclose(lcp->l_mfp);
+ lcp->l_mfp = NULL;
+ }
+ if (lcp->l_name != NULL) {
+ free(lcp->l_name);
+ lcp->l_name = NULL;
+ }
+ if (lcp->l_seen != NULL) {
+ free(lcp->l_seen);
+ lcp->l_seen = NULL;
+ lcp->l_numseen = 0;
+ }
+ if (lcp->l_pmns != NULL) {
+ __pmFreePMNS(lcp->l_pmns);
+ lcp->l_pmns = NULL;
+ }
+
+ if (lcp->l_ti != NULL)
+ free(lcp->l_ti);
+
+ if (lcp->l_hashpmid.hsize != 0) {
+ __pmHashCtl *hcp = &lcp->l_hashpmid;
+ __pmHashNode *hp;
+ __pmHashNode *prior_hp;
+ int i;
+
+ for (i = 0; i < hcp->hsize; i++) {
+ for (hp = hcp->hash[i], prior_hp = NULL; hp != NULL; hp = hp->next) {
+ if (hp->data != NULL)
+ free(hp->data);
+ if (prior_hp != NULL)
+ free(prior_hp);
+ prior_hp = hp;
+ }
+ if (prior_hp != NULL)
+ free(prior_hp);
+ }
+ free(hcp->hash);
+ }
+
+ if (lcp->l_hashindom.hsize != 0) {
+ __pmHashCtl *hcp = &lcp->l_hashindom;
+ __pmHashNode *hp;
+ __pmHashNode *prior_hp;
+ __pmLogInDom *idp;
+ __pmLogInDom *prior_idp;
+ int i;
+
+ for (i = 0; i < hcp->hsize; i++) {
+ for (hp = hcp->hash[i], prior_hp = NULL; hp != NULL; hp = hp->next) {
+ for (idp = (__pmLogInDom *)hp->data, prior_idp = NULL;
+ idp != NULL; idp = idp->next) {
+ if (idp->buf != NULL)
+ free(idp->buf);
+ if (idp->allinbuf == 0 && idp->namelist != NULL)
+ free(idp->namelist);
+ if (prior_idp != NULL)
+ free(prior_idp);
+ prior_idp = idp;
+ }
+ if (prior_idp != NULL)
+ free(prior_idp);
+ if (prior_hp != NULL)
+ free(prior_hp);
+ prior_hp = hp;
+ }
+ if (prior_hp != NULL)
+ free(prior_hp);
+ }
+ free(hcp->hash);
+ }
+
+}
+
+int
+__pmLogLoadLabel(__pmLogCtl *lcp, const char *name)
+{
+ int sts;
+ int blen;
+ int exists = 0;
+ int i;
+ int sep = __pmPathSeparator();
+ char *q;
+ char *base;
+ char *tbuf;
+ char *tp;
+ char *dir;
+ DIR *dirp = NULL;
+ char filename[MAXPATHLEN];
+#if defined(HAVE_READDIR64)
+ struct dirent64 *direntp;
+#else
+ struct dirent *direntp;
+#endif
+
+ /*
+ * find directory name component ... copy as dirname() may clobber
+ * the string
+ */
+ if ((tbuf = strdup(name)) == NULL)
+ return -oserror();
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ dir = dirname(tbuf);
+
+ /*
+ * find file name component
+ */
+ strncpy(filename, name, MAXPATHLEN);
+ filename[MAXPATHLEN-1] = '\0';
+ if ((base = strdup(basename(filename))) == NULL) {
+ sts = -oserror();
+ free(tbuf);
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ if (access(name, R_OK) == 0) {
+ /*
+ * file exists and is readable ... if name contains '.' and
+ * suffix is "index", "meta" or a string of digits or a string
+ * of digits followed by one of the compression suffixes,
+ * strip the suffix
+ */
+ int strip = 0;
+ if ((q = strrchr(base, '.')) != NULL) {
+ if (strcmp(q, ".index") == 0) {
+ strip = 1;
+ goto done;
+ }
+ if (strcmp(q, ".meta") == 0) {
+ strip = 1;
+ goto done;
+ }
+ for (i = 0; i < ncompress; i++) {
+ if (strcmp(q, compress_ctl[i].suff) == 0) {
+ char *q2;
+ /*
+ * name ends with one of the supported compressed file
+ * suffixes, check for a string of digits before that,
+ * e.g. if base is initially "foo.0.bz2", we want it
+ * stripped to "foo"
+ */
+ *q = '\0';
+ if ((q2 = strrchr(base, '.')) == NULL) {
+ /* no . to the left of the suffix */
+ *q = '.';
+ goto done;
+ }
+ q = q2;
+ break;
+ }
+ }
+ if (q[1] != '\0') {
+ char *end;
+ /*
+ * Below we don't care about the value from strtol(),
+ * we're interested in updating the pointer "end".
+ * The messiness is thanks to gcc and glibc ... strtol()
+ * is marked __attribute__((warn_unused_result)) ...
+ * to avoid warnings on all platforms, assign to a
+ * dummy variable that is explicitly marked unused.
+ */
+ long tmpl __attribute__((unused));
+ tmpl = strtol(q+1, &end, 10);
+ if (*end == '\0') strip = 1;
+ }
+ }
+done:
+ if (strip) {
+ *q = '\0';
+ }
+ }
+
+ snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, base);
+ if ((lcp->l_name = strdup(filename)) == NULL) {
+ sts = -oserror();
+ free(tbuf);
+ free(base);
+ return sts;
+ }
+
+ lcp->l_minvol = -1;
+ lcp->l_tifp = lcp->l_mdfp = lcp->l_mfp = NULL;
+ lcp->l_ti = NULL;
+ lcp->l_hashpmid.nodes = lcp->l_hashpmid.hsize = 0;
+ lcp->l_hashindom.nodes = lcp->l_hashindom.hsize = 0;
+ lcp->l_numseen = 0; lcp->l_seen = NULL;
+ lcp->l_pmns = NULL;
+
+ blen = (int)strlen(base);
+ PM_LOCK(__pmLock_libpcp);
+ if ((dirp = opendir(dir)) != NULL) {
+#if defined(HAVE_READDIR64)
+ while ((direntp = readdir64(dirp)) != NULL)
+#else
+ while ((direntp = readdir(dirp)) != NULL)
+#endif
+ {
+ if (strncmp(base, direntp->d_name, blen) != 0)
+ continue;
+ if (direntp->d_name[blen] != '.')
+ continue;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name);
+ fprintf(stderr, "__pmLogOpen: inspect file \"%s\"\n", filename);
+ }
+#endif
+ tp = &direntp->d_name[blen+1];
+ if (strcmp(tp, "index") == 0) {
+ exists = 1;
+ snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name);
+ if ((lcp->l_tifp = fopen(filename, "r")) == NULL) {
+ sts = -oserror();
+ PM_UNLOCK(__pmLock_libpcp);
+ goto cleanup;
+ }
+ }
+ else if (strcmp(tp, "meta") == 0) {
+ exists = 1;
+ snprintf(filename, sizeof(filename), "%s%c%s", dir, sep, direntp->d_name);
+ if ((lcp->l_mdfp = fopen(filename, "r")) == NULL) {
+ sts = -oserror();
+ PM_UNLOCK(__pmLock_libpcp);
+ goto cleanup;
+ }
+ }
+ else {
+ char *q;
+ int vol;
+ vol = (int)strtol(tp, &q, 10);
+ if (*q != '0') {
+ /* may have one of the trailing compressed file suffixes */
+ int i;
+ for (i = 0; i < ncompress; i++) {
+ if (strcmp(q, compress_ctl[i].suff) == 0) {
+ /* match */
+ *q = '\0';
+ break;
+ }
+ }
+ }
+ if (*q == '\0') {
+ exists = 1;
+ if (lcp->l_minvol == -1) {
+ lcp->l_minvol = vol;
+ lcp->l_maxvol = vol;
+ }
+ else {
+ if (vol < lcp->l_minvol)
+ lcp->l_minvol = vol;
+ if (vol > lcp->l_maxvol)
+ lcp->l_maxvol = vol;
+ }
+ }
+ }
+ }
+ closedir(dirp);
+ dirp = NULL;
+ }
+ else {
+#ifdef PCP_DEBUG
+ sts = -oserror();
+ if (pmDebug & DBG_TRACE_LOG) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmLogOpen: cannot scan directory \"%s\": %s\n", dir, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ goto cleanup;
+
+#endif
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ if (lcp->l_minvol == -1 || lcp->l_mdfp == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (lcp->l_minvol == -1)
+ fprintf(stderr, "__pmLogOpen: Not found: data file \"%s.0\" (or similar)\n", base);
+ if (lcp->l_mdfp == NULL)
+ fprintf(stderr, "__pmLogOpen: Not found: metadata file \"%s.meta\"\n", base);
+ }
+#endif
+ if (exists)
+ sts = PM_ERR_LOGFILE;
+ else
+ sts = -ENOENT;
+ goto cleanup;
+ }
+ free(tbuf);
+ free(base);
+ return 0;
+
+cleanup:
+ if (dirp != NULL)
+ closedir(dirp);
+ __pmLogClose(lcp);
+ free(tbuf);
+ free(base);
+ return sts;
+}
+
+int
+__pmLogOpen(const char *name, __pmContext *ctxp)
+{
+ __pmLogCtl *lcp = ctxp->c_archctl->ac_log;
+ __pmLogLabel label;
+ int version;
+ int sts;
+
+ if ((sts = __pmLogLoadLabel(lcp, name)) < 0)
+ return sts;
+
+ lcp->l_curvol = -1;
+ if ((sts = __pmLogChangeVol(lcp, lcp->l_minvol)) < 0)
+ goto cleanup;
+ else
+ version = sts;
+
+ ctxp->c_origin = lcp->l_label.ill_start;
+
+ if (lcp->l_tifp) {
+ sts = __pmLogChkLabel(lcp, lcp->l_tifp, &label, PM_LOG_VOL_TI);
+ if (sts < 0)
+ goto cleanup;
+ else if (sts != version) {
+ /* mismatch between meta & actual data versions! */
+ sts = PM_ERR_LABEL;
+ goto cleanup;
+ }
+
+ if (lcp->l_label.ill_pid != label.ill_pid ||
+ strcmp(lcp->l_label.ill_hostname, label.ill_hostname) != 0) {
+ sts = PM_ERR_LABEL;
+ goto cleanup;
+ }
+ }
+
+ if ((sts = __pmLogChkLabel(lcp, lcp->l_mdfp, &label, PM_LOG_VOL_META)) < 0)
+ goto cleanup;
+ else if (sts != version) { /* version mismatch between meta & ti */
+ sts = PM_ERR_LABEL;
+ goto cleanup;
+ }
+
+ if ((sts = __pmLogLoadMeta(lcp)) < 0)
+ goto cleanup;
+
+ if ((sts = __pmLogLoadIndex(lcp)) < 0)
+ goto cleanup;
+
+ if (lcp->l_label.ill_pid != label.ill_pid ||
+ strcmp(lcp->l_label.ill_hostname, label.ill_hostname) != 0) {
+ sts = PM_ERR_LABEL;
+ goto cleanup;
+ }
+
+ lcp->l_refcnt = 0;
+ lcp->l_physend = -1;
+
+ ctxp->c_mode = (ctxp->c_mode & 0xffff0000) | PM_MODE_FORW;
+
+ return 0;
+
+cleanup:
+ __pmLogClose(lcp);
+ return sts;
+}
+
+void
+__pmLogPutIndex(const __pmLogCtl *lcp, const __pmTimeval *tp)
+{
+ __pmLogTI ti;
+ __pmLogTI oti;
+ int sts;
+
+ if (lcp->l_tifp == NULL || lcp->l_mdfp == NULL || lcp->l_mfp == NULL) {
+ /*
+ * archive not really created (failed in __pmLogCreate) ...
+ * nothing to be done
+ */
+ return;
+ }
+
+ if (tp == NULL) {
+ struct timeval tmp;
+
+ __pmtimevalNow(&tmp);
+ ti.ti_stamp.tv_sec = (__int32_t)tmp.tv_sec;
+ ti.ti_stamp.tv_usec = (__int32_t)tmp.tv_usec;
+ }
+ else
+ ti.ti_stamp = *tp; /* struct assignment */
+ ti.ti_vol = lcp->l_curvol;
+ fflush(lcp->l_mdfp);
+ fflush(lcp->l_mfp);
+
+ if (sizeof(off_t) > sizeof(__pm_off_t)) {
+ /* check for overflow of the offset ... */
+ off_t tmp;
+
+ tmp = ftell(lcp->l_mdfp);
+ assert(tmp >= 0);
+ ti.ti_meta = (__pm_off_t)tmp;
+ if (tmp != ti.ti_meta) {
+ __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive file (meta) too big\n");
+ return;
+ }
+ tmp = ftell(lcp->l_mfp);
+ assert(tmp >= 0);
+ ti.ti_log = (__pm_off_t)tmp;
+ if (tmp != ti.ti_log) {
+ __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive file (data) too big\n");
+ return;
+ }
+ }
+ else {
+ ti.ti_meta = (__pm_off_t)ftell(lcp->l_mdfp);
+ ti.ti_log = (__pm_off_t)ftell(lcp->l_mfp);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "__pmLogPutIndex: timestamp=%d.06%d vol=%d meta posn=%ld log posn=%ld\n",
+ (int)ti.ti_stamp.tv_sec, (int)ti.ti_stamp.tv_usec,
+ ti.ti_vol, (long)ti.ti_meta, (long)ti.ti_log);
+ }
+#endif
+
+ oti.ti_stamp.tv_sec = htonl(ti.ti_stamp.tv_sec);
+ oti.ti_stamp.tv_usec = htonl(ti.ti_stamp.tv_usec);
+ oti.ti_vol = htonl(ti.ti_vol);
+ oti.ti_meta = htonl(ti.ti_meta);
+ oti.ti_log = htonl(ti.ti_log);
+ if ((sts = fwrite(&oti, 1, sizeof(oti), lcp->l_tifp)) != sizeof(oti)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutIndex: write failed: returns %d expecting %d: %s\n",
+ sts, (int)sizeof(oti), osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ }
+ if (fflush(lcp->l_tifp) != 0)
+ __pmNotifyErr(LOG_ERR, "__pmLogPutIndex: PCP archive temporal index flush failed\n");
+}
+
+static int
+logputresult(int version,__pmLogCtl *lcp, __pmPDU *pb)
+{
+ /*
+ * This is a bit tricky ...
+ *
+ * Input
+ * :---------:----------:----------:---------------- .........:---------:
+ * | int len | int type | int from | timestamp, .... pmResult | unused |
+ * :---------:----------:----------:---------------- .........:---------:
+ * ^
+ * |
+ * pb
+ *
+ * Output
+ * :---------:----------:----------:---------------- .........:---------:
+ * | unused | unused | int len | timestamp, .... pmResult | int len |
+ * :---------:----------:----------:---------------- .........:---------:
+ * ^
+ * |
+ * start
+ *
+ * If version == 1, pb[] does not have room for trailer len.
+ * If version == 2, pb[] does have room for trailer len.
+ */
+ int sz;
+ int sts = 0;
+ int save_from;
+ __pmPDU *start = &pb[2];
+
+ if (lcp->l_state == PM_LOG_STATE_NEW) {
+ int i;
+ __pmTimeval *tvp;
+ /*
+ * first result, do the label record
+ */
+ i = sizeof(__pmPDUHdr) / sizeof(__pmPDU);
+ tvp = (__pmTimeval *)&pb[i];
+ lcp->l_label.ill_start.tv_sec = ntohl(tvp->tv_sec);
+ lcp->l_label.ill_start.tv_usec = ntohl(tvp->tv_usec);
+ lcp->l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(lcp->l_tifp, &lcp->l_label);
+ lcp->l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(lcp->l_mdfp, &lcp->l_label);
+ lcp->l_label.ill_vol = 0;
+ __pmLogWriteLabel(lcp->l_mfp, &lcp->l_label);
+ lcp->l_state = PM_LOG_STATE_INIT;
+ }
+
+ sz = pb[0] - (int)sizeof(__pmPDUHdr) + 2 * (int)sizeof(int);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "logputresult: pdubuf=" PRINTF_P_PFX "%p input len=%d output len=%d posn=%ld\n", pb, pb[0], sz, (long)ftell(lcp->l_mfp));
+ }
+#endif
+
+ save_from = start[0];
+ start[0] = htonl(sz); /* swab */
+
+ if (version == 1) {
+ if ((sts = fwrite(start, 1, sz-sizeof(int), lcp->l_mfp)) != sz-sizeof(int)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutResult: write failed: returns %d expecting %d: %s\n",
+ sts, (int)(sz-sizeof(int)), osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ sts = -oserror();
+ }
+ else {
+ if ((sts = fwrite(start, 1, sizeof(int), lcp->l_mfp)) != sizeof(int)) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutResult: trailer write failed: returns %d expecting %d: %s\n",
+ sts, (int)sizeof(int), osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ sts = -oserror();
+ }
+ }
+ }
+ else {
+ /* assume version == 2 */
+ start[(sz-1)/sizeof(__pmPDU)] = start[0];
+ if ((sts = fwrite(start, 1, sz, lcp->l_mfp)) != sz) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("__pmLogPutResult2: write failed: returns %d expecting %d: %s\n",
+ sts, sz, osstrerror_r(errmsg, sizeof(errmsg)));
+ pmflush();
+ sts = -oserror();
+ }
+ }
+
+ /* restore and unswab */
+ start[0] = save_from;
+
+ return sts;
+}
+
+/*
+ * original routine, pb[] does not have room for trailer, so 2 writes
+ * needed
+ */
+int
+__pmLogPutResult(__pmLogCtl *lcp, __pmPDU *pb)
+{
+ return logputresult(1, lcp, pb);
+}
+
+/*
+ * new routine, pb[] does have room for trailer, so only 1 write
+ * needed
+ */
+int
+__pmLogPutResult2(__pmLogCtl *lcp, __pmPDU *pb)
+{
+ return logputresult(2, lcp, pb);
+}
+
+/*
+ * check if PDU buffer seems even half-way reasonable ...
+ * only used when trying to locate end of archive.
+ * return 0 for OK, -1 for bad.
+ */
+static int
+paranoidCheck(int len, __pmPDU *pb)
+{
+ int numpmid;
+ size_t hdrsz; /* bytes for the PDU head+tail */
+ int i;
+ int j;
+ int vsize; /* size of vlist_t's in PDU buffer */
+ int vbsize; /* size of pmValueBlocks */
+ int numval; /* number of values */
+ int valfmt;
+
+ struct result_t { /* from p_result.c */
+ __pmPDUHdr hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* no. of PMIDs to follow */
+ __pmPDU data[1]; /* zero or more */
+ } *pp;
+ struct vlist_t { /* from p_result.c */
+ pmID pmid;
+ int numval; /* no. of vlist els to follow, or error */
+ int valfmt; /* insitu or pointer */
+ __pmValue_PDU vlist[1]; /* zero or more */
+ } *vlp;
+
+ /*
+ * to start with, need space for result_t with no data (__pmPDU)
+ * ... this is the external size, which consists of
+ * <header len>
+ * <timestamp> (2 words)
+ * <numpmid>
+ * <trailer len>
+ *
+ * it is confusing because *pb and result_t include the fake
+ * __pmPDUHdr which is not really in the external file
+ */
+ hdrsz = 5 * sizeof(__pmPDU);
+
+ if (len < hdrsz) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: len=%d, min len=%d\n",
+ len, (int)hdrsz);
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+
+ pp = (struct result_t *)pb;
+ numpmid = ntohl(pp->numpmid);
+
+ /*
+ * This is a re-implementation of much of __pmDecodeResult()
+ */
+
+ if (numpmid < 1) {
+ if (len != hdrsz) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: numpmid=%d len=%d, expected len=%d\n",
+ numpmid, len, (int)hdrsz);
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+ }
+
+ /*
+ * Calculate vsize and vbsize from the original PDU buffer ...
+ * :---------:-----------:----------------:--------------------:
+ * : numpmid : timestamp : ... vlists ... : .. pmValueBocks .. :
+ * :---------:-----------:----------------:--------------------:
+ * <--- vsize ---> <--- vbsize --->
+ * bytes bytes
+ */
+
+ vsize = vbsize = 0;
+ for (i = 0; i < numpmid; i++) {
+ vlp = (struct vlist_t *)&pp->data[vsize/sizeof(__pmPDU)];
+ vsize += sizeof(vlp->pmid) + sizeof(vlp->numval);
+ if (len < hdrsz + vsize + vbsize) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: vset[%d] len=%d, need len>=%d (%d+%d+%d)\n",
+ i, len, (int)(hdrsz + vsize + vbsize), (int)hdrsz, vsize, vbsize);
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+ numval = ntohl(vlp->numval);
+ if (numval > 0) {
+#ifdef DESPERATE
+ pmID pmid;
+#endif
+ valfmt = ntohl(vlp->valfmt);
+ if (valfmt != PM_VAL_INSITU &&
+ valfmt != PM_VAL_DPTR &&
+ valfmt != PM_VAL_SPTR) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: vset[%d] bad valfmt=%d\n",
+ i, valfmt);
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+#ifdef DESPERATE
+ {
+ char strbuf[20];
+ if (i == 0) fputc('\n', stderr);
+ pmid = __ntohpmID(vlp->pmid);
+ fprintf(stderr, "vlist[%d] pmid: %s numval: %d valfmt: %d\n",
+ i, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), numval, valfmt);
+ }
+#endif
+ vsize += sizeof(vlp->valfmt) + numval * sizeof(__pmValue_PDU);
+ if (valfmt != PM_VAL_INSITU) {
+ for (j = 0; j < numval; j++) {
+ int index = (int)ntohl((long)vlp->vlist[j].value.pval);
+ pmValueBlock *pduvbp;
+ int vlen;
+
+ if (index < 0 || index * sizeof(__pmPDU) > len) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: vset[%d] val[%d], bad pval index=%d not in range 0..%d\n",
+ i, j, index, (int)(len / sizeof(__pmPDU)));
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+ pduvbp = (pmValueBlock *)&pb[index];
+ __ntohpmValueBlock(pduvbp);
+ vlen = pduvbp->vlen;
+ __htonpmValueBlock(pduvbp); /* restore pdubuf! */
+ if (vlen < sizeof(__pmPDU)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "\nparanoidCheck: vset[%d] val[%d], bad vlen=%d\n",
+ i, j, vlen);
+ dumpbuf(len, &pb[3]); /* skip first 3 words, start @ timestamp */
+ }
+#endif
+ return -1;
+ }
+ vbsize += PM_PDU_SIZE_BYTES(vlen);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+paranoidLogRead(__pmLogCtl *lcp, int mode, FILE *peekf, pmResult **result)
+{
+ return __pmLogRead(lcp, mode, peekf, result, PMLOGREAD_TO_EOF);
+}
+
+/*
+ * read next forward or backward from the log
+ *
+ * by default (peekf == NULL) use lcp->l_mfp and roll volume
+ * at end of file if another volume is available
+ *
+ * if peekf != NULL, use this stream, and do not roll volume
+ */
+int
+__pmLogRead(__pmLogCtl *lcp, int mode, FILE *peekf, pmResult **result, int option)
+{
+ int head;
+ int rlen;
+ int trail;
+ int sts;
+ long offset;
+ __pmPDU *pb;
+ FILE *f;
+ int n;
+
+ /*
+ * Strip any XTB data from mode, its not used here
+ */
+ mode &= __PM_MODE_MASK;
+
+ if (peekf != NULL)
+ f = peekf;
+ else
+ f = lcp->l_mfp;
+
+ offset = ftell(f);
+ assert(offset >= 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "__pmLogRead: fd=%d%s mode=%s vol=%d posn=%ld ",
+ fileno(f), peekf == NULL ? "" : " (peek)",
+ mode == PM_MODE_FORW ? "forw" : "back",
+ lcp->l_curvol, (long)offset);
+ }
+#endif
+
+ if (mode == PM_MODE_BACK) {
+ for ( ; ; ) {
+ if (offset <= sizeof(__pmLogLabel) + 2 * sizeof(int)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "BEFORE start\n");
+#endif
+ if (peekf == NULL) {
+ int vol = lcp->l_curvol-1;
+ while (vol >= lcp->l_minvol) {
+ if (__pmLogChangeVol(lcp, vol) >= 0) {
+ f = lcp->l_mfp;
+ fseek(f, 0L, SEEK_END);
+ offset = ftell(f);
+ assert(offset >= 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "vol=%d posn=%ld ",
+ lcp->l_curvol, (long)offset);
+ }
+#endif
+ break;
+ }
+ vol--;
+ }
+ if (vol < lcp->l_minvol)
+ return PM_ERR_EOL;
+ }
+ else
+ return PM_ERR_EOL;
+ }
+ else {
+ fseek(f, -(long)sizeof(head), SEEK_CUR);
+ break;
+ }
+ }
+ }
+
+again:
+ n = (int)fread(&head, 1, sizeof(head), f);
+ head = ntohl(head); /* swab head */
+ if (n != sizeof(head)) {
+ if (feof(f)) {
+ /* no more data ... looks like End of Archive volume */
+ clearerr(f);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "AFTER end\n");
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (peekf == NULL) {
+ int vol = lcp->l_curvol+1;
+ while (vol <= lcp->l_maxvol) {
+ if (__pmLogChangeVol(lcp, vol) >= 0) {
+ f = lcp->l_mfp;
+ goto again;
+ }
+ vol++;
+ }
+ }
+ return PM_ERR_EOL;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "\nError: header fread got %d expected %d\n", n, (int)sizeof(head));
+#endif
+ if (ferror(f)) {
+ /* I/O error */
+ clearerr(f);
+ return -oserror();
+ }
+ else
+ /* corrupted archive */
+ return PM_ERR_LOGREC;
+ }
+
+ /*
+ * This is pretty ugly (forward case shown backwards is similar) ...
+ *
+ * Input
+ * head <--- rlen bytes -- ...---> tail
+ * :---------:---------:---------:---------------- .........:---------:
+ * | ??? | ??? | int len | timestamp, .... pmResult | int len |
+ * :---------:---------:---------:---------------- .........:---------:
+ * ^ ^
+ * | |
+ * pb read into here
+ *
+ * Decode
+ * <---- __pmPDUHdr ----------->
+ * :---------:---------:---------:---------------- .........:---------:
+ * | length | pdutype | anon | timestamp, .... pmResult | int len |
+ * :---------:---------:---------:---------------- .........:---------:
+ * ^
+ * |
+ * pb
+ *
+ * Note: cannot volume switch in the middle of a log record
+ */
+
+ rlen = head - 2 * (int)sizeof(head);
+ if (rlen < 0 || (mode == PM_MODE_BACK && rlen > offset)) {
+ /*
+ * corrupted! usually means a truncated log ...
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "\nError: truncated log? rlen=%d (offset %d)\n",
+ rlen, (int)offset);
+#endif
+ return PM_ERR_LOGREC;
+ }
+ /*
+ * need to add int at end for trailer in case buffer is used
+ * subsequently by __pmLogPutResult2()
+ */
+ if ((pb = __pmFindPDUBuf(rlen + (int)sizeof(__pmPDUHdr) + (int)sizeof(int))) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "\nError: __pmFindPDUBuf(%d) %s\n",
+ (int)(rlen + sizeof(__pmPDUHdr)),
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+#endif
+ fseek(f, offset, SEEK_SET);
+ return -oserror();
+ }
+
+ if (mode == PM_MODE_BACK)
+ fseek(f, -(long)(sizeof(head) + rlen), SEEK_CUR);
+
+ if ((n = (int)fread(&pb[3], 1, rlen, f)) != rlen) {
+ /* data read failed */
+ __pmUnpinPDUBuf(pb);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "\nError: data fread got %d expected %d\n", n, rlen);
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (ferror(f)) {
+ /* I/O error */
+ clearerr(f);
+ return -oserror();
+ }
+ clearerr(f);
+
+ /* corrupted archive */
+ return PM_ERR_LOGREC;
+ }
+ else {
+ __pmPDUHdr *header = (__pmPDUHdr *)pb;
+ header->len = sizeof(*header) + rlen;
+ header->type = PDU_RESULT;
+ header->from = FROM_ANON;
+ /* swab pdu buffer - done later in __pmDecodeResult */
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ int j;
+ char *p;
+ int jend = PM_PDU_SIZE(header->len);
+
+ /* for Purify ... */
+ p = (char *)pb + header->len;
+ while (p < (char *)pb + jend*sizeof(__pmPDU))
+ *p++ = '~'; /* buffer end */
+
+ fprintf(stderr, "__pmLogRead: PDU buffer\n");
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0 && j > 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", pb[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+ }
+
+ if (mode == PM_MODE_BACK)
+ fseek(f, -(long)(rlen + sizeof(head)), SEEK_CUR);
+
+ if ((n = (int)fread(&trail, 1, sizeof(trail), f)) != sizeof(trail)) {
+ __pmUnpinPDUBuf(pb);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "\nError: trailer fread got %d expected %d\n", n, (int)sizeof(trail));
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (ferror(f)) {
+ /* I/O error */
+ clearerr(f);
+ return -oserror();
+ }
+ clearerr(f);
+
+ /* corrupted archive */
+ return PM_ERR_LOGREC;
+ }
+ else {
+ /* swab trail */
+ trail = ntohl(trail);
+ }
+
+ if (trail != head) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "\nError: record length mismatch: header (%d) != trailer (%d)\n", head, trail);
+#endif
+ __pmUnpinPDUBuf(pb);
+ return PM_ERR_LOGREC;
+ }
+
+ if (option == PMLOGREAD_TO_EOF && paranoidCheck(head, pb) == -1) {
+ __pmUnpinPDUBuf(pb);
+ return PM_ERR_LOGREC;
+ }
+
+ if (mode == PM_MODE_BACK)
+ fseek(f, -(long)sizeof(trail), SEEK_CUR);
+
+ __pmOverrideLastFd(fileno(f));
+ sts = __pmDecodeResult(pb, result); /* also swabs the result */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ head -= sizeof(head) + sizeof(trail);
+ if (sts >= 0) {
+ __pmTimeval tmp;
+ fprintf(stderr, "@");
+ __pmPrintStamp(stderr, &(*result)->timestamp);
+ tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ fprintf(stderr, " (t=%.6f)", __pmTimevalSub(&tmp, &lcp->l_label.ill_start));
+ }
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmLogRead: __pmDecodeResult failed: %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ fprintf(stderr, "@unknown time");
+ }
+ fprintf(stderr, " len=header+%d+trailer\n", head);
+ }
+#endif
+
+ /* exported to indicate how efficient we are ... */
+ __pmLogReads++;
+
+ if (sts < 0) {
+ __pmUnpinPDUBuf(pb);
+ return PM_ERR_LOGREC;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ fprintf(stderr, "__pmLogRead timestamp=");
+ __pmPrintStamp(stderr, &(*result)->timestamp);
+ fprintf(stderr, " " PRINTF_P_PFX "%p ... " PRINTF_P_PFX "%p", &pb[3], &pb[head/sizeof(__pmPDU)+3]);
+ fputc('\n', stderr);
+ dumpbuf(rlen, &pb[3]); /* see above to explain "3" */
+ }
+#endif
+
+ __pmUnpinPDUBuf(pb);
+
+ return 0;
+}
+
+static int
+check_all_derived(int numpmid, pmID pmidlist[])
+{
+ int i;
+
+ /*
+ * Special case ... if we ONLY have derived metrics in the input
+ * pmidlist then all the derived metrics must be constant
+ * expressions, so skip all the processing.
+ * Derived metrics have domain == DYNAMIC_PMID and item != 0.
+ * This rare, but avoids reading to the end of an archive
+ * for no good reason.
+ */
+
+ for (i = 0; i < numpmid; i++) {
+ if (pmid_domain(pmidlist[i]) != DYNAMIC_PMID ||
+ pmid_item(pmidlist[i]) == 0)
+ return 0;
+ }
+ return 1;
+}
+
+int
+__pmLogFetch(__pmContext *ctxp, int numpmid, pmID pmidlist[], pmResult **result)
+{
+ int i;
+ int j;
+ int u;
+ int all_derived;
+ int sts = 0;
+ int found;
+ double tdiff;
+ pmResult *newres;
+ pmDesc desc;
+ int kval;
+ __pmHashNode *hp;
+ pmid_ctl *pcp;
+ int nskip;
+ __pmTimeval tmp;
+ int ctxp_mode = ctxp->c_mode & __PM_MODE_MASK;
+
+ if (ctxp_mode == PM_MODE_INTERP) {
+ return __pmLogFetchInterp(ctxp, numpmid, pmidlist, result);
+ }
+
+ all_derived = check_all_derived(numpmid, pmidlist);
+
+ /* re-establish position */
+ __pmLogChangeVol(ctxp->c_archctl->ac_log, ctxp->c_archctl->ac_vol);
+ fseek(ctxp->c_archctl->ac_log->l_mfp,
+ (long)ctxp->c_archctl->ac_offset, SEEK_SET);
+
+more:
+
+ found = 0;
+ nskip = 0;
+ *result = NULL;
+ while (!found) {
+ if (ctxp->c_archctl->ac_serial == 0) {
+ /*
+ * no serial access, so need to make sure we are
+ * starting in the correct place
+ */
+ int tmp_mode;
+ nskip = 0;
+ if (ctxp_mode == PM_MODE_FORW)
+ tmp_mode = PM_MODE_BACK;
+ else
+ tmp_mode = PM_MODE_FORW;
+ while (__pmLogRead(ctxp->c_archctl->ac_log, tmp_mode, NULL, result, PMLOGREAD_NEXT) >= 0) {
+ nskip++;
+ tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ tdiff = __pmTimevalSub(&tmp, &ctxp->c_origin);
+ if ((tdiff < 0 && ctxp_mode == PM_MODE_FORW) ||
+ (tdiff > 0 && ctxp_mode == PM_MODE_BACK)) {
+ pmFreeResult(*result);
+ *result = NULL;
+ break;
+ }
+ else if (tdiff == 0) {
+ /* exactly the one we wanted */
+ found = 1;
+ break;
+ }
+ pmFreeResult(*result);
+ *result = NULL;
+ }
+ ctxp->c_archctl->ac_serial = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (nskip) {
+ fprintf(stderr, "__pmLogFetch: ctx=%d skip reverse %d to ",
+ pmWhichContext(), nskip);
+ if (*result != NULL)
+ __pmPrintStamp(stderr, &(*result)->timestamp);
+ else
+ fprintf(stderr, "unknown time");
+ fprintf(stderr, ", found=%d\n", found);
+ }
+#ifdef DESPERATE
+ else
+ fprintf(stderr, "__pmLogFetch: ctx=%d no skip reverse\n",
+ pmWhichContext());
+#endif
+ }
+#endif
+ nskip = 0;
+ }
+ if (found)
+ break;
+ if ((sts = __pmLogRead(ctxp->c_archctl->ac_log, ctxp->c_mode, NULL, result, PMLOGREAD_NEXT)) < 0)
+ break;
+ tmp.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ tdiff = __pmTimevalSub(&tmp, &ctxp->c_origin);
+ if ((tdiff < 0 && ctxp_mode == PM_MODE_FORW) ||
+ (tdiff > 0 && ctxp_mode == PM_MODE_BACK)) {
+ nskip++;
+ pmFreeResult(*result);
+ *result = NULL;
+ continue;
+ }
+ found = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (nskip) {
+ fprintf(stderr, "__pmLogFetch: ctx=%d skip %d to ",
+ pmWhichContext(), nskip);
+ __pmPrintStamp(stderr, &(*result)->timestamp);
+ fputc('\n', stderr);
+ }
+#ifdef DESPERATE
+ else
+ fprintf(stderr, "__pmLogFetch: ctx=%d no skip\n",
+ pmWhichContext());
+#endif
+ }
+#endif
+ }
+ if (found) {
+ ctxp->c_origin.tv_sec = (__int32_t)(*result)->timestamp.tv_sec;
+ ctxp->c_origin.tv_usec = (__int32_t)(*result)->timestamp.tv_usec;
+ }
+
+ if (*result != NULL && (*result)->numpmid == 0) {
+ /*
+ * mark record, and not interpolating ...
+ * if pmFetchArchive(), return it
+ * otherwise keep searching
+ */
+ if (numpmid == 0)
+ newres = *result;
+ else {
+ pmFreeResult(*result);
+ goto more;
+ }
+ }
+ else if (found) {
+ if (numpmid > 0) {
+ /*
+ * not necesssarily after them all, so cherry-pick the metrics
+ * we wanted ..
+ * there are two tricks here ...
+ * (1) pmValueSets for metrics requested, but not in the pmResult
+ * from the log are assigned using the first two fields in the
+ * pmid_ctl struct -- since these are allocated once as
+ * needed, and never free'd, we have to make sure pmFreeResult
+ * finds a pmValueSet in a pinned pdu buffer ... this means
+ * we must find at least one real value from the log to go
+ * with any "unavailable" results
+ * (2) real pmValueSets can be ignored, they are in a pdubuf
+ * and will be reclaimed when the buffer is unpinned in
+ * pmFreeResult
+ */
+
+ i = (int)sizeof(pmResult) + numpmid * (int)sizeof(pmValueSet *);
+ if ((newres = (pmResult *)malloc(i)) == NULL) {
+ __pmNoMem("__pmLogFetch.newres", i, PM_FATAL_ERR);
+ }
+ newres->numpmid = numpmid;
+ newres->timestamp = (*result)->timestamp;
+ u = 0;
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ for (j = 0; j < numpmid; j++) {
+ hp = __pmHashSearch((int)pmidlist[j], &pc_hc);
+ if (hp == NULL) {
+ /* first time we've been asked for this one */
+ if ((pcp = (pmid_ctl *)malloc(sizeof(pmid_ctl))) == NULL) {
+ __pmNoMem("__pmLogFetch.pmid_ctl", sizeof(pmid_ctl), PM_FATAL_ERR);
+ }
+ pcp->pc_pmid = pmidlist[j];
+ pcp->pc_numval = 0;
+ sts = __pmHashAdd((int)pmidlist[j], (void *)pcp, &pc_hc);
+ if (sts < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ }
+ else
+ pcp = (pmid_ctl *)hp->data;
+ for (i = 0; i < (*result)->numpmid; i++) {
+ if (pmidlist[j] == (*result)->vset[i]->pmid) {
+ /* match */
+ newres->vset[j] = (*result)->vset[i];
+ u++;
+ break;
+ }
+ }
+ if (i == (*result)->numpmid) {
+ /*
+ * requested metric not returned from the log, construct
+ * a "no values available" pmValueSet from the pmid_ctl
+ */
+ newres->vset[j] = (pmValueSet *)pcp;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ if (u == 0 && !all_derived) {
+ /*
+ * not one of our pmids was in the log record, try
+ * another log record ...
+ */
+ pmFreeResult(*result);
+ free(newres);
+ goto more;
+ }
+ /*
+ * *result malloc'd in __pmLogRead, but vset[]'s are either in
+ * pdubuf or the pmid_ctl struct
+ */
+ free(*result);
+ *result = newres;
+ }
+ else
+ /* numpmid == 0, pmFetchArchive() call */
+ newres = *result;
+ /*
+ * Apply instance profile filtering ...
+ * Note. This is a little strange, as in the numpmid == 0,
+ * pmFetchArchive() case, this for-loop is not executed ...
+ * this is correct, the instance profile is ignored for
+ * pmFetchArchive()
+ */
+ for (i = 0; i < numpmid; i++) {
+ if (newres->vset[i]->numval <= 0) {
+ /*
+ * no need to xlate numval for an error ... already done
+ * below __pmLogRead() in __pmDecodeResult() ... also xlate
+ * here would have been skipped in the pmFetchArchive() case
+ */
+ continue;
+ }
+ sts = __pmLogLookupDesc(ctxp->c_archctl->ac_log, newres->vset[i]->pmid, &desc);
+ if (sts < 0) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_WARNING, "__pmLogFetch: missing pmDesc for pmID %s: %s",
+ pmIDStr_r(desc.pmid, strbuf, sizeof(strbuf)), pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ pmFreeResult(newres);
+ break;
+ }
+ if (desc.indom == PM_INDOM_NULL)
+ /* no instance filtering to be done for these ones */
+ continue;
+
+ /*
+ * scan instances, keeping those "in" the instance profile
+ *
+ * WARNING
+ * This compresses the pmValueSet INSITU, and since
+ * these are in a pdu buffer, it trashes the the
+ * pdu buffer and means there is no clever way of
+ * re-using the pdu buffer to satisfy multiple
+ * pmFetch requests
+ * Fortunately, stdio buffering means copying to
+ * make additional pdu buffers is not too expensive.
+ */
+ kval = 0;
+ for (j = 0; j < newres->vset[i]->numval; j++) {
+ if (__pmInProfile(desc.indom, ctxp->c_instprof, newres->vset[i]->vlist[j].inst)) {
+ if (kval != j)
+ /* struct assignment */
+ newres->vset[i]->vlist[kval] = newres->vset[i]->vlist[j];
+ kval++;
+ }
+ }
+ newres->vset[i]->numval = kval;
+ }
+ }
+
+ /* remember your position in this context */
+ ctxp->c_archctl->ac_offset = ftell(ctxp->c_archctl->ac_log->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+
+ return sts;
+}
+
+/*
+ * error handling wrapper around __pmLogChangeVol() to deal with
+ * missing volumes ... return lcp->l_ti[] index for entry matching
+ * success
+ */
+static int
+VolSkip(__pmLogCtl *lcp, int mode, int j)
+{
+ int vol = lcp->l_ti[j].ti_vol;
+
+ while (lcp->l_minvol <= vol && vol <= lcp->l_maxvol) {
+ if (__pmLogChangeVol(lcp, vol) >= 0)
+ return j;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "VolSkip: Skip missing vol %d\n", vol);
+ }
+#endif
+ if (mode == PM_MODE_FORW) {
+ for (j++; j < lcp->l_numti; j++)
+ if (lcp->l_ti[j].ti_vol != vol)
+ break;
+ if (j == lcp->l_numti)
+ return PM_ERR_EOL;
+ vol = lcp->l_ti[j].ti_vol;
+ }
+ else {
+ for (j--; j >= 0; j--)
+ if (lcp->l_ti[j].ti_vol != vol)
+ break;
+ if (j < 0)
+ return PM_ERR_EOL;
+ vol = lcp->l_ti[j].ti_vol;
+ }
+ }
+ return PM_ERR_EOL;
+}
+
+void
+__pmLogSetTime(__pmContext *ctxp)
+{
+ __pmLogCtl *lcp = ctxp->c_archctl->ac_log;
+ int mode;
+
+ mode = ctxp->c_mode & __PM_MODE_MASK; /* strip XTB data */
+
+ if (mode == PM_MODE_INTERP)
+ mode = ctxp->c_delta > 0 ? PM_MODE_FORW : PM_MODE_BACK;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "__pmLogSetTime(%d) ", pmWhichContext());
+ __pmPrintTimeval(stderr, &ctxp->c_origin);
+ fprintf(stderr, " delta=%d", ctxp->c_delta);
+ }
+#endif
+
+ if (lcp->l_numti) {
+ /* we have a temporal index, use it! */
+ int i;
+ int j = -1;
+ int toobig = 0;
+ int match = 0;
+ int numti = lcp->l_numti;
+ __pmLogTI *tip = lcp->l_ti;
+ double t_hi;
+ double t_lo;
+ struct stat sbuf;
+
+ sbuf.st_size = -1;
+
+ for (i = 0; i < numti; i++, tip++) {
+ if (tip->ti_vol < lcp->l_minvol)
+ /* skip missing preliminary volumes */
+ continue;
+ if (tip->ti_vol == lcp->l_maxvol) {
+ /* truncated check for last volume */
+ if (sbuf.st_size < 0) {
+ FILE *f = _logpeek(lcp, lcp->l_maxvol);
+
+ sbuf.st_size = 0;
+ if (f != NULL) {
+ fstat(fileno(f), &sbuf);
+ fclose(f);
+ }
+ }
+ if (tip->ti_log > sbuf.st_size) {
+ j = i;
+ toobig++;
+ break;
+ }
+ }
+ t_hi = __pmTimevalSub(&tip->ti_stamp, &ctxp->c_origin);
+ if (t_hi > 0) {
+ j = i;
+ break;
+ }
+ else if (t_hi == 0) {
+ j = i;
+ match = 1;
+ break;
+ }
+ }
+ if (i == numti)
+ j = numti;
+
+ ctxp->c_archctl->ac_serial = 1;
+
+ if (match) {
+ j = VolSkip(lcp, mode, j);
+ if (j < 0)
+ return;
+ fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET);
+ if (mode == PM_MODE_BACK)
+ ctxp->c_archctl->ac_serial = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, " at ti[%d]@", j);
+ __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp);
+ }
+#endif
+ }
+ else if (j < 1) {
+ j = VolSkip(lcp, PM_MODE_FORW, 0);
+ if (j < 0)
+ return;
+ fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, " before start ti@");
+ __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp);
+ }
+#endif
+ }
+ else if (j == numti) {
+ j = VolSkip(lcp, PM_MODE_BACK, numti-1);
+ if (j < 0)
+ return;
+ fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET);
+ if (mode == PM_MODE_BACK)
+ ctxp->c_archctl->ac_serial = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, " after end ti@");
+ __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp);
+ }
+#endif
+ }
+ else {
+ /*
+ * [j-1] [origin] [j]
+ * <----- t_lo -------><----- t_hi ---->
+ *
+ * choose closest index point. if toobig, [j] is not
+ * really valid (log truncated or incomplete)
+ */
+ t_hi = __pmTimevalSub(&lcp->l_ti[j].ti_stamp, &ctxp->c_origin);
+ t_lo = __pmTimevalSub(&ctxp->c_origin, &lcp->l_ti[j-1].ti_stamp);
+ if (t_hi <= t_lo && !toobig) {
+ j = VolSkip(lcp, mode, j);
+ if (j < 0)
+ return;
+ fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET);
+ if (mode == PM_MODE_FORW)
+ ctxp->c_archctl->ac_serial = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, " before ti[%d]@", j);
+ __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp);
+ }
+#endif
+ }
+ else {
+ j = VolSkip(lcp, mode, j-1);
+ if (j < 0)
+ return;
+ fseek(lcp->l_mfp, (long)lcp->l_ti[j].ti_log, SEEK_SET);
+ if (mode == PM_MODE_BACK)
+ ctxp->c_archctl->ac_serial = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, " after ti[%d]@", j);
+ __pmPrintTimeval(stderr, &lcp->l_ti[j].ti_stamp);
+ }
+#endif
+ }
+ if (ctxp->c_archctl->ac_serial && mode == PM_MODE_FORW) {
+ /*
+ * back up one record ...
+ * index points to the END of the record!
+ */
+ pmResult *result;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " back up ...\n");
+#endif
+ if (__pmLogRead(lcp, PM_MODE_BACK, NULL, &result, PMLOGREAD_NEXT) >= 0)
+ pmFreeResult(result);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "...");
+#endif
+ }
+ }
+ }
+ else {
+ /* index either not available, or not useful */
+ if (mode == PM_MODE_FORW) {
+ __pmLogChangeVol(lcp, lcp->l_minvol);
+ fseek(lcp->l_mfp, (long)(sizeof(__pmLogLabel) + 2*sizeof(int)), SEEK_SET);
+ }
+ else if (mode == PM_MODE_BACK) {
+ __pmLogChangeVol(lcp, lcp->l_maxvol);
+ fseek(lcp->l_mfp, (long)0, SEEK_END);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " index not useful\n");
+#endif
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, " vol=%d posn=%ld serial=%d\n",
+ lcp->l_curvol, (long)ftell(lcp->l_mfp), ctxp->c_archctl->ac_serial);
+#endif
+
+ /* remember your position in this context */
+ ctxp->c_archctl->ac_offset = ftell(lcp->l_mfp);
+ assert(ctxp->c_archctl->ac_offset >= 0);
+ ctxp->c_archctl->ac_vol = ctxp->c_archctl->ac_log->l_curvol;
+}
+
+int
+pmGetArchiveLabel(pmLogLabel *lp)
+{
+ __pmContext *ctxp;
+ ctxp = __pmHandleToPtr(pmWhichContext());
+ if (ctxp == NULL || ctxp->c_type != PM_CONTEXT_ARCHIVE)
+ return PM_ERR_NOCONTEXT;
+ else {
+ __pmLogLabel *rlp;
+ /*
+ * we have to copy the structure to hide the differences
+ * between the internal __pmTimeval and the external struct timeval
+ */
+ rlp = &ctxp->c_archctl->ac_log->l_label;
+ lp->ll_magic = rlp->ill_magic;
+ lp->ll_pid = (pid_t)rlp->ill_pid;
+ lp->ll_start.tv_sec = rlp->ill_start.tv_sec;
+ lp->ll_start.tv_usec = rlp->ill_start.tv_usec;
+ memcpy(lp->ll_hostname, rlp->ill_hostname, PM_LOG_MAXHOSTLEN);
+ memcpy(lp->ll_tz, rlp->ill_tz, sizeof(lp->ll_tz));
+ PM_UNLOCK(ctxp->c_lock);
+ return 0;
+ }
+}
+
+int
+pmGetArchiveEnd(struct timeval *tp)
+{
+ /*
+ * set l_physend and l_endtime
+ * at the end of ... ctxp->c_archctl->ac_log
+ */
+ __pmContext *ctxp;
+ int sts;
+
+ ctxp = __pmHandleToPtr(pmWhichContext());
+ if (ctxp == NULL || ctxp->c_type != PM_CONTEXT_ARCHIVE)
+ return PM_ERR_NOCONTEXT;
+ sts = __pmGetArchiveEnd(ctxp->c_archctl->ac_log, tp);
+ PM_UNLOCK(ctxp->c_lock);
+ return sts;
+}
+
+int
+__pmGetArchiveEnd(__pmLogCtl *lcp, struct timeval *tp)
+{
+ struct stat sbuf;
+ FILE *f;
+ long save = 0;
+ pmResult *rp = NULL;
+ pmResult *nrp;
+ int i;
+ int sts;
+ int found;
+ int head;
+ long offset;
+ int vol;
+ __pm_off_t logend;
+ __pm_off_t physend = 0;
+
+ /*
+ * expect things to be stable, so l_maxvol is not empty, and
+ * l_physend does not change for l_maxvol ... the ugliness is
+ * to handle situations where these expectations are not met
+ */
+ found = 0;
+ sts = PM_ERR_LOGREC; /* default error condition */
+ f = NULL;
+ for (vol = lcp->l_maxvol; vol >= lcp->l_minvol; vol--) {
+ if (lcp->l_curvol == vol) {
+ f = lcp->l_mfp;
+ save = ftell(f);
+ assert(save >= 0);
+ }
+ else if ((f = _logpeek(lcp, vol)) == NULL) {
+ sts = -oserror();
+ break;
+ }
+
+ if (fstat(fileno(f), &sbuf) < 0) {
+ sts = -oserror();
+ break;
+ }
+
+ if (vol == lcp->l_maxvol && sbuf.st_size == lcp->l_physend) {
+ /* nothing changed, return cached stuff */
+ tp->tv_sec = lcp->l_endtime.tv_sec;
+ tp->tv_usec = lcp->l_endtime.tv_usec;
+ sts = 0;
+ break;
+ }
+
+ /* if this volume is empty, try previous volume */
+ if (sbuf.st_size <= (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int)) {
+ if (f != lcp->l_mfp) {
+ fclose(f);
+ f = NULL;
+ }
+ continue;
+ }
+
+ physend = (__pm_off_t)sbuf.st_size;
+ if (sizeof(off_t) > sizeof(__pm_off_t)) {
+ if (physend != sbuf.st_size) {
+ __pmNotifyErr(LOG_ERR, "pmGetArchiveEnd: PCP archive file"
+ " (meta) too big (%"PRIi64" bytes)\n",
+ (uint64_t)sbuf.st_size);
+ sts = PM_ERR_TOOBIG;
+ break;
+ }
+ }
+
+ /* try to read backwards for the last physical record ... */
+ fseek(f, (long)physend, SEEK_SET);
+ if (paranoidLogRead(lcp, PM_MODE_BACK, f, &rp) >= 0) {
+ /* success, we are done! */
+ found = 1;
+ break;
+ }
+
+ /*
+ * failure at the physical end of file may be related to a truncted
+ * block flush for a growing archive. Scan temporal index, and use
+ * last entry at or before end of physical file for this volume
+ */
+ logend = (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int);
+ for (i = lcp->l_numti - 1; i >= 0; i--) {
+ if (lcp->l_ti[i].ti_vol != vol) {
+ if (f != lcp->l_mfp) {
+ fclose(f);
+ f = NULL;
+ }
+ continue;
+ }
+ if (lcp->l_ti[i].ti_log <= physend) {
+ logend = lcp->l_ti[i].ti_log;
+ break;
+ }
+ }
+
+ /*
+ * Now chase it forwards from the last index entry ...
+ *
+ * BUG 357003 - pmchart can't read archive file
+ * turns out the index may point to the _end_ of the last
+ * valid record, so if not at start of volume, back up one
+ * record, then scan forwards.
+ */
+ fseek(f, (long)logend, SEEK_SET);
+ if (logend > (int)sizeof(__pmLogLabel) + 2*(int)sizeof(int)) {
+ if (paranoidLogRead(lcp, PM_MODE_BACK, f, &rp) < 0) {
+ /* this is badly damaged! */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "pmGetArchiveEnd: "
+ "Error reading record ending at posn=%d ti[%d]@",
+ logend, i);
+ __pmPrintTimeval(stderr, &lcp->l_ti[i].ti_stamp);
+ fputc('\n', stderr);
+ }
+#endif
+ break;
+ }
+ }
+
+ /* Keep reading records from "logend" until can do so no more... */
+ for ( ; ; ) {
+ offset = ftell(f);
+ assert(offset >= 0);
+ if ((int)fread(&head, 1, sizeof(head), f) != sizeof(head))
+ /* cannot read header for log record !!?? */
+ break;
+ head = ntohl(head);
+ if (offset + head > physend)
+ /* last record is incomplete */
+ break;
+ fseek(f, offset, SEEK_SET);
+ if (paranoidLogRead(lcp, PM_MODE_FORW, f, &nrp) < 0)
+ /* this record is truncated, or bad, we lose! */
+ break;
+ /* this one is ok, remember it as it may be the last one */
+ found = 1;
+ if (rp != NULL)
+ pmFreeResult(rp);
+ rp = nrp;
+ }
+ if (found)
+ break;
+
+ /*
+ * this probably means this volume contains no useful records,
+ * try the previous volume
+ */
+ }/*for*/
+
+ if (f == lcp->l_mfp)
+ fseek(f, save, SEEK_SET); /* restore file pointer in current vol */
+ else if (f != NULL)
+ /* temporary FILE * from _logpeek() */
+ fclose(f);
+
+ if (found) {
+ tp->tv_sec = (time_t)rp->timestamp.tv_sec;
+ tp->tv_usec = (int)rp->timestamp.tv_usec;
+ if (vol == lcp->l_maxvol) {
+ lcp->l_endtime.tv_sec = (__int32_t)rp->timestamp.tv_sec;
+ lcp->l_endtime.tv_usec = (__int32_t)rp->timestamp.tv_usec;
+ lcp->l_physend = physend;
+ }
+ sts = 0;
+ }
+ if (rp != NULL) {
+ /*
+ * rp is not NULL from found==1 path _or_ from error break
+ * after an initial paranoidLogRead() success
+ */
+ pmFreeResult(rp);
+ }
+
+ return sts;
+}
diff --git a/src/libpcp/src/loop.c b/src/libpcp/src/loop.c
new file mode 100644
index 0000000..353e008
--- /dev/null
+++ b/src/libpcp/src/loop.c
@@ -0,0 +1,871 @@
+/*
+ * Copyright (c) 2004-2006 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#ifdef ASYNC_API
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+#include <signal.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#ifndef SIGMAX
+/* I would use SIGRTMAX...except it's not constant on Linux */
+#define SIGMAX 64
+#endif
+
+typedef struct loop_input_s loop_input_t;
+typedef struct loop_signal_s loop_signal_t;
+typedef struct loop_timeout_s loop_timeout_t;
+typedef struct loop_child_s loop_child_t;
+typedef struct loop_idle_s loop_idle_t;
+typedef struct loop_main_s loop_main_t;
+
+struct loop_input_s
+{
+ loop_input_t *next;
+ int tag;
+ int fd;
+ int flags;
+ int (*callback)(int fd, int flags, void *closure);
+ void *closure;
+ int priority;
+};
+
+struct loop_signal_s
+{
+ loop_signal_t *next;
+ int tag;
+ int (*callback)(int sig, void *closure);
+ void *closure;
+};
+
+struct loop_timeout_s
+{
+ loop_timeout_t *next;
+ int tag;
+ int delay;
+ int tout_msec;
+ int (*callback)(void *closure);
+ void *closure;
+};
+
+struct loop_child_s
+{
+ loop_child_t *next;
+ int tag;
+ pid_t pid;
+ int (*callback)(pid_t pid, int status, const struct rusage *, void *closure);
+ void *closure;
+};
+
+struct loop_idle_s
+{
+ loop_idle_t *next;
+ int tag;
+ int (*callback)(void *closure);
+ void *closure;
+};
+
+/*
+* per-loop state, kept in an implicit stack
+* by the main loop and subsidiary loops.
+*/
+struct loop_main_s
+{
+ loop_main_t *next;
+ int running;
+ loop_timeout_t *current_timeout;
+ loop_child_t *current_child;
+};
+
+#define pmLoopDebug ((pmDebug & DBG_TRACE_LOOP) != 0)
+
+static int num_inputs;
+static loop_input_t *input_list;
+static struct pollfd *pfd;
+static loop_input_t **inputs;
+static int next_tag = 1;
+static int inputs_dirty = 1;
+static loop_signal_t *signals[SIGMAX];
+static volatile int signals_pending[SIGMAX];
+static struct timeval poll_start;
+static loop_timeout_t *timeout_list;
+static loop_main_t *main_stack;
+static loop_child_t *child_list;
+static int child_pending;
+static int sigchld_tag;
+static loop_idle_t *idle_list;
+
+static int
+tv_sub(const struct timeval *a, const struct timeval *b)
+{
+ struct timeval t;
+
+ t.tv_sec = a->tv_sec - b->tv_sec;
+ if (a->tv_usec >= b->tv_usec) {
+ t.tv_usec = a->tv_usec - b->tv_usec;
+ } else {
+ t.tv_sec--;
+ t.tv_usec = 1000000 + a->tv_usec - b->tv_usec;
+ }
+ return (t.tv_sec * 1000 + t.tv_usec / 1000);
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+int
+pmLoopRegisterInput(
+ int fd,
+ int flags,
+ int (*callback)(int fd, int flags, void *closure),
+ void *closure,
+ int priority)
+{
+ loop_input_t *ii;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,
+ "loop_register_input: fd=%d flags=0x%x "
+ "callback=%p closure=%p priority=%d",
+ fd, flags, callback, closure, priority);
+
+ if ((ii = (loop_input_t *)malloc(sizeof(loop_input_t))) == NULL) {
+ return (-ENOMEM);
+ }
+
+ ii->tag = next_tag++;
+ ii->fd = fd;
+ ii->flags = flags;
+ ii->callback = callback;
+ ii->closure = closure;
+
+ ii->next = input_list;
+ input_list = ii;
+ num_inputs++;
+
+ inputs_dirty = 1;
+
+ return ii->tag;
+}
+
+void
+pmLoopUnregisterInput(int tag)
+{
+ loop_input_t *ii, *previi;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG, "loop_unregister_input: tag=%d", tag);
+
+ for (ii = input_list, previi = NULL ;
+ ii != NULL && ii->tag != tag ;
+ previi = ii, ii = ii->next)
+ ;
+ if (ii == NULL)
+ return;
+
+ if (previi == NULL)
+ input_list = ii->next;
+ else
+ previi->next = ii->next;
+ num_inputs--;
+ free(ii);
+
+ inputs_dirty = 1;
+}
+
+static int
+loop_compare_by_priority(const void *av, const void *bv)
+{
+ const loop_input_t *a = *(const loop_input_t **)av;
+ const loop_input_t *b = *(const loop_input_t **)bv;
+
+ return a->priority - b->priority;
+}
+
+static int
+loop_setup_inputs(void)
+{
+ loop_input_t *ii;
+ int i;
+
+ if (num_inputs <= 0) {
+ return (0);
+ }
+
+ if (inputs_dirty) {
+ pfd = (struct pollfd *)realloc(pfd,
+ num_inputs * sizeof(struct pollfd));
+ inputs = (loop_input_t **)realloc(inputs,
+ num_inputs * sizeof(loop_input_t *));
+ if ((pfd == NULL) || (inputs == NULL)) {
+ return (-ENOMEM);
+ }
+ inputs_dirty = 0;
+ }
+
+ for (ii = input_list, i = 0; ii != NULL ; ii = ii->next, i++)
+ inputs[i] = ii;
+ qsort(inputs, num_inputs, sizeof(loop_input_t *), loop_compare_by_priority);
+
+ for (i = 0 ; i < num_inputs ; i++)
+ {
+ ii = inputs[i];
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,
+ "loop_setup_inputs: inputs[%d] = (fd=%d "
+ "callback=%p closure=%p)",
+ i, ii->fd, ii->callback, ii->closure);
+
+ pfd[i].fd = ii->fd;
+ pfd[i].events = ii->flags;
+ pfd[i].revents = 0;
+ }
+ return (num_inputs);
+}
+
+static void
+loop_dispatch_inputs(void)
+{
+ int i;
+ loop_input_t *ii;
+ int n = num_inputs; /* because num_inputs can change inside the loop */
+
+ for (i = 0 ; i < n; i++) {
+ ii = inputs[i];
+
+ if ((pfd[i].revents & POLLNVAL)) {
+ /* invalid fd... */
+ pmLoopUnregisterInput(ii->tag);
+ continue;
+ }
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,
+ "loop_dispatch_inputs: pfd[%i]=(fd=%d "
+ "events=0x%x revents=0x%x)",
+ i, pfd[i].fd, pfd[i].events, pfd[i].revents);
+
+ if ((pfd[i].revents & (ii->flags | POLLHUP | POLLERR))) {
+ if ((*ii->callback)(ii->fd, pfd[i].revents, ii->closure)) {
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,
+ "loop_dispatch_inputs: deregistering "
+ "input with tag %d\n",
+ ii->tag);
+
+ pmLoopUnregisterInput(ii->tag);
+ }
+ }
+ }
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+static void
+loop_sig_handler(int sig)
+{
+ signals_pending[sig] = 1;
+}
+
+int
+pmLoopRegisterSignal(
+ int sig,
+ int (*callback)(int sig, void *closure),
+ void *closure)
+{
+ loop_signal_t *ss;
+ int doinstall;
+
+ if (sig < 0 || sig >= SIGMAX)
+ return -EINVAL;
+
+ if ((ss = (loop_signal_t *)malloc(sizeof(loop_signal_t))) == NULL)
+ return -ENOMEM;
+
+ ss->tag = next_tag++;
+ ss->callback = callback;
+ ss->closure = closure;
+
+ doinstall = (signals[sig] == NULL);
+ ss->next = signals[sig];
+ signals[sig] = ss;
+
+ if (doinstall) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = 0;
+ sa.sa_handler = loop_sig_handler;
+ if (sigaction(sig, &sa, NULL) < 0) {
+ int ee = oserror();
+
+ __pmNotifyErr(LOG_WARNING,
+ "sigaction failed - %s", osstrerror());
+ return -ee;
+ }
+ }
+
+ return ss->tag;
+}
+
+void
+pmLoopUnregisterSignal(int tag)
+{
+ int sig;
+ loop_signal_t *ss, *prevss;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_unregister_signal: tag=%d", tag);
+
+ for (sig = 0 ; sig < SIGMAX ; sig++)
+ {
+ for (ss = signals[sig], prevss = NULL ;
+ ss != NULL && ss->tag != tag ;
+ prevss = ss, ss = ss->next)
+ ;
+ if (ss == NULL)
+ continue;
+
+ if (prevss == NULL)
+ signals[sig] = ss->next;
+ else
+ prevss->next = ss->next;
+ free(ss);
+
+ if (signals[sig] == NULL)
+ {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = 0;
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(sig, &sa, NULL) < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "sigaction failed - %s", osstrerror());
+ return;
+ }
+ }
+ break;
+ }
+}
+
+static void
+loop_dispatch_signals(void)
+{
+ int sig;
+ loop_signal_t *ss, *nextss;
+
+ for (sig = 0 ; sig < SIGMAX ; sig++) {
+ if (signals_pending[sig]) {
+ signals_pending[sig] = 0;
+
+ for (ss = signals[sig]; ss != NULL; ss = nextss) {
+ nextss = ss->next;
+ if ((*ss->callback)(sig, ss->closure)) {
+ pmLoopUnregisterSignal(ss->tag);
+ }
+ }
+ }
+ }
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+/*
+ * A few words about the timeout data structure. This is the classic
+ * (i.e. simple and not scalable) data structure for multiplexing
+ * multiple virtual timeouts onto one real one, as described in any
+ * OS internals book.
+ *
+ * A singly-linked list of loop_timeout_t structures is kept, the head
+ * is pointed to by timeout_list and threaded by tt->next. Each entry
+ * stores in tt->delay a timeout in millisecons which expresses when
+ * the entry is scheduled to fire as the elapsed time after the
+ * previous entry is scheduled to fire. Thus the delay field in the
+ * head of the list is logically the amount of time from now until the
+ * first timeout is scheduled, and so is used directly as the timeout
+ * for poll().
+ *
+ * From this data structure we can derive the insert algorithm.
+ * The algorithm walks down the list from the head, keeping a running
+ * relative delay from the last entry iterated over by subtracting
+ * from it the tt->delay of each entry skipped. When iterating to the
+ * next entry would cause this running relative delay to go negative,
+ * we know we've arrived at the right place to insert the new entry.
+ * Note that the check is for negative, not negative or zero: this
+ * ensures that multiple entries for the same scheduled time are
+ * stored in the same order that they were inserted, which is the
+ * most intuitive behaviour for the application programmer.
+ *
+ * The remove algorithm is simpler, it just scans the list trying
+ * to match the unique tag.
+ *
+ * There are some more hairy parts as well. It is possible for poll()
+ * to return before the timeout expires, for example if input becomes
+ * available on a file descriptor. The poll() call does not give any
+ * indication of how much time remained until the timeout would have
+ * fired (on some operating systems, the select(2) does this). So a
+ * sample of the system time is taken before and after every call
+ * to poll(), the elapsed time in the poll() is calculated, and the
+ * tt->delay in the head of the timeout_list is adjusted to account
+ * for the elapsed time. This is necessary to avoid restarting the
+ * poll() with too long a timeout. An example of the resulting bug
+ * would be a timeout registered for 10 seconds from now, but every
+ * 1 second input becomes available on some file descriptor; if the
+ * poll() timeout were not adjusted the timeout callback would never
+ * be called and would always be 10 seconds in the future.
+ */
+
+static void
+loop_dump_timeouts(void)
+{
+ loop_timeout_t *tt;
+
+ __pmNotifyErr(LOG_DEBUG,"timeout_list {");
+ for (tt = timeout_list ; tt != NULL ; tt = tt->next) {
+ __pmNotifyErr(LOG_DEBUG," %dms %p %p",
+ tt->delay, tt->callback, tt->closure);
+ }
+ __pmNotifyErr(LOG_DEBUG,"}");
+}
+
+static void
+loop_insert_timeout(loop_timeout_t *tt)
+{
+ loop_timeout_t *next, *prev;
+ int delay = tt->delay;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG, "loop_insert_timeout: %d %p %p",
+ tt->delay, tt->callback, tt->closure);
+
+ for (next = timeout_list, prev = NULL ;
+ (next != NULL) && ((delay - next->delay) >= 0);
+ prev = next, next = next->next)
+ delay -= next->delay;
+
+ if (prev == NULL)
+ timeout_list = tt;
+ else
+ prev->next = tt;
+ tt->next = next;
+
+ if (next != NULL)
+ next->delay -= delay;
+
+ tt->delay = delay;
+
+ if (pmLoopDebug)
+ loop_dump_timeouts();
+}
+
+int
+pmLoopRegisterTimeout(
+ int tout_msec,
+ int (*callback)(void *closure),
+ void *closure)
+{
+ loop_timeout_t *tt;
+
+ if (tout_msec < 0) {
+ return (-EINVAL);
+ }
+
+ if ((tt = (loop_timeout_t *)malloc(sizeof(loop_timeout_t))) == NULL) {
+ return (-ENOMEM);
+ }
+
+ tt->tag = next_tag++;
+ tt->delay = tt->tout_msec = tout_msec;
+ tt->callback = callback;
+ tt->closure = closure;
+
+ loop_insert_timeout(tt);
+
+ return tt->tag;
+}
+
+void
+pmLoopUnregisterTimeout(int tag)
+{
+ loop_main_t *lm;
+ loop_timeout_t *tt, *prevtt;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_unregister_timeout: tag=%d", tag);
+
+ /*
+ * Because the timeout object is detached from the
+ * global timeout list while its being dispatched
+ * (and yes there are good reasons for this), we
+ * have to search for the timeout tag in all the
+ * currently stacked loops.
+ */
+ for (lm = main_stack ; lm != NULL ; lm = lm->next)
+ {
+ if (lm->current_timeout != NULL &&
+ lm->current_timeout->tag == tag)
+ {
+ free(lm->current_timeout);
+ lm->current_timeout = NULL;
+ return;
+ }
+ }
+
+ for (tt = timeout_list, prevtt = NULL;
+ tt != NULL && tt->tag != tag ;
+ prevtt = tt, tt = tt->next)
+ ;
+
+ if (tt == NULL)
+ return;
+
+ if (prevtt == NULL)
+ timeout_list = tt->next;
+ else
+ prevtt->next = tt->next;
+
+ if (tt->next != NULL)
+ tt->next->delay += tt->delay;
+
+ free(tt);
+}
+
+/* returns milliseconds */
+static int
+loop_setup_timeouts(void)
+{
+ __pmtimevalNow(&poll_start);
+
+ if (idle_list != NULL)
+ return 0; /* poll() returns immediately */
+ if (timeout_list == NULL)
+ return -1; /* poll() waits forever */
+ return (timeout_list->delay);
+}
+
+static void
+loop_dispatch_timeouts(void)
+{
+ if (timeout_list == NULL)
+ return;
+
+ timeout_list->delay = 0;
+ while (timeout_list != NULL && (timeout_list->delay == 0)) {
+ loop_main_t *lm = main_stack;
+ int isdone;
+ assert(lm != NULL);
+ assert(lm->current_timeout == NULL);
+ lm->current_timeout = timeout_list;
+ timeout_list = timeout_list->next;
+
+ isdone = (*lm->current_timeout->callback)(lm->current_timeout->closure);
+
+ assert(lm == main_stack);
+
+ if (!isdone && (lm->current_timeout != NULL)) {
+ lm->current_timeout->delay = lm->current_timeout->tout_msec;
+ loop_insert_timeout(lm->current_timeout);
+ } else {
+ pmLoopUnregisterTimeout(lm->current_timeout->tag);
+ }
+ lm->current_timeout = NULL;
+ }
+}
+
+static void
+loop_adjust_timeout(void)
+{
+ struct timeval now;
+ int spent;
+
+ if (timeout_list == NULL)
+ return;
+
+ __pmtimevalNow(&now);
+ spent = tv_sub(&now, &poll_start);
+ if (spent >= timeout_list->delay) {
+ timeout_list->delay = 0;
+ loop_dispatch_timeouts();
+ } else {
+ timeout_list->delay -= spent;
+ }
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+static int
+loop_sigchld_handler(int sig, void *closure)
+{
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_sigchld_handler");
+ child_pending = 1;
+ return (0);
+}
+
+
+int
+pmLoopRegisterChild(
+ pid_t pid,
+ int (*callback)(pid_t pid, int status, const struct rusage *, void *closure),
+ void *closure)
+{
+ loop_child_t *cc;
+ int doinstall;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_register_child: pid=%" FMT_PID " callback=%p closure=%p",
+ pid, callback, closure);
+
+ if (pid <= (pid_t)0)
+ return -1;
+ if ((cc = (loop_child_t *)malloc(sizeof(loop_child_t))) == NULL) {
+ return (-ENOMEM);
+ }
+
+ cc->tag = next_tag++;
+ cc->pid = pid;
+ cc->callback = callback;
+ cc->closure = closure;
+
+ doinstall = (child_list == NULL);
+ cc->next = child_list;
+ child_list = cc;
+
+ if (doinstall)
+ sigchld_tag = pmLoopRegisterSignal(SIGCHLD,
+ loop_sigchld_handler,
+ NULL);
+
+ return cc->tag;
+}
+
+void
+pmLoopUnregisterChild(int tag)
+{
+ loop_main_t *lm;
+ loop_child_t *cc, *prevcc;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_unregister_child: tag=%d", tag);
+
+ for (cc = child_list, prevcc = NULL ;
+ cc != NULL && cc->tag != tag ;
+ prevcc = cc, cc = cc->next)
+ ;
+ if (cc == NULL)
+ return;
+
+ if (prevcc == NULL)
+ child_list = cc->next;
+ else
+ prevcc->next = cc->next;
+
+ for (lm = main_stack ; lm != NULL ; lm = lm->next)
+ {
+ if (cc == lm->current_child)
+ lm->current_child = NULL;
+ }
+ free(cc);
+
+ if (child_list == NULL)
+ {
+ pmLoopUnregisterSignal(sigchld_tag);
+ sigchld_tag = -1;
+ }
+}
+
+static void
+loop_dispatch_children(void)
+{
+ loop_child_t *cc, *nextcc;
+ int status;
+ int r;
+ struct rusage rusage;
+
+ memset (&rusage, 0, sizeof(rusage));
+
+ child_pending = 0;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children");
+
+ /* We don't support callback on process groups. Sorry */
+ while ((r = wait3(&status, WNOHANG, &rusage)) > 0)
+ {
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_dispatch_children: r=%d", r);
+
+ for (cc = child_list ; cc != NULL ; cc = nextcc)
+ {
+ nextcc = cc->next;
+
+ if (r == (int)cc->pid) {
+ loop_main_t *lm = main_stack;
+ int isdone;
+
+ assert(lm != NULL);
+ lm->current_child = cc;
+ isdone = (*cc->callback)((pid_t)r, status, &rusage,
+ cc->closure);
+
+ if (isdone ||
+ (lm->current_child != NULL &&
+ (WIFEXITED(status) || WIFSIGNALED(status)))) {
+ /*
+ * This pid won't be coming back or we were told
+ * that callback has fulfilled its purpose, so
+ * unregister.
+ */
+ pmLoopUnregisterChild(cc->tag);
+ }
+ assert(lm == main_stack);
+ assert(lm->current_child == NULL || lm->current_child == cc);
+ lm->current_child = NULL;
+ break;
+ }
+ }
+ }
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+int
+pmLoopRegisterIdle(
+ int (*callback)(void *closure),
+ void *closure)
+{
+ loop_idle_t *ii;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG,"loop_register_idle: callback=%p closure=%p",
+ callback, closure);
+
+ if ((ii = (loop_idle_t *)malloc(sizeof(loop_idle_t))) == NULL) {
+ return (-ENOMEM);
+ }
+
+ ii->tag = next_tag++;
+ ii->callback = callback;
+ ii->closure = closure;
+
+ ii->next = idle_list;
+ idle_list = ii;
+
+ return ii->tag;
+}
+
+void
+pmLoopUnregisterIdle(int tag)
+{
+ loop_idle_t *ii, *previi;
+
+ if (pmLoopDebug)
+ __pmNotifyErr(LOG_DEBUG, "loop_unregister_idle: tag=%d", tag);
+
+ for (ii = idle_list, previi = NULL ;
+ ii != NULL && ii->tag != tag ;
+ previi = ii, ii = ii->next)
+ ;
+ if (ii == NULL)
+ return;
+
+ if (previi == NULL)
+ idle_list = ii->next;
+ else
+ previi->next = ii->next;
+
+ free(ii);
+}
+
+static void
+loop_dispatch_idle(void)
+{
+ loop_idle_t *ii, *nextii;
+
+ for (ii = idle_list ; ii != NULL ; ii = nextii) {
+ nextii = ii->next;
+
+ if ((*ii->callback)(ii->closure)) {
+ pmLoopUnregisterIdle(ii->tag);
+ }
+ }
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+
+void
+pmLoopStop(void)
+{
+ if (main_stack != NULL)
+ main_stack->running = 0;
+}
+
+int
+pmLoopMain(void)
+{
+ int r;
+ int timeout;
+ loop_main_t lmain;
+
+ memset(&lmain, 0, sizeof(lmain));
+ lmain.next = main_stack;
+ main_stack = &lmain;
+
+ lmain.running = 1;
+ while (lmain.running) {
+ int ee;
+
+ if ((ee = loop_setup_inputs()) < 0)
+ return ee;
+ timeout = loop_setup_timeouts();
+ loop_dispatch_idle();
+
+ if ((ee == 0) && (timeout == -1) && (idle_list == NULL))
+ return 0;
+
+ r = poll(pfd, num_inputs, timeout);
+ if (r < 0) {
+ if (oserror() == EINTR) {
+ loop_dispatch_signals();
+ if (child_pending)
+ loop_dispatch_children();
+ continue;
+ }
+ __pmNotifyErr(LOG_ERR, "pmLoopMain: poll failed - %s",
+ osstrerror());
+ break;
+ } else if (r == 0) {
+ if (timeout > 0)
+ loop_dispatch_timeouts();
+ else
+ loop_adjust_timeout();
+ } else {
+ loop_dispatch_inputs();
+ loop_adjust_timeout();
+ }
+ }
+
+ assert(main_stack == &lmain);
+ assert(lmain.current_child == NULL);
+ assert(lmain.current_timeout == NULL);
+ main_stack = lmain.next;
+ return 0;
+}
+
+/*-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/
+#endif /*ASYNC_API*/
diff --git a/src/libpcp/src/optfetch.c b/src/libpcp/src/optfetch.c
new file mode 100644
index 0000000..f2ca9ae
--- /dev/null
+++ b/src/libpcp/src/optfetch.c
@@ -0,0 +1,709 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Generic routines to provide the "optimized" pmFetch bundling
+ * services ... the optimization is driven by the crude heuristics
+ * weights defined in optcost below.
+ *
+ * Thread-safe notes
+ *
+ * lrand48() is not thread-safe, but we don't really care here.
+ */
+
+/* if DESPERATE, we need DEBUG */
+#if defined(DESPERATE) && !defined(PCP_DEBUG)
+#define DEBUG
+#endif
+
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+
+/*
+ * elements of optcost are
+ *
+ * c_pmid cost per PMD for PMIDs in a fetch
+ * c_indom cost per PMD for indoms in a fetch
+ * c_fetch cost of a new fetch group (should be less than
+ * c_indomsize * c_xtrainst, else all fetches will go
+ * into a single group, unless the scope is global)
+ * c_indomsize expected numer of instances for an indom
+ * c_xtrainst cost of retrieving an unwanted metric inst
+ * c_scope cost opt., 0 for incremental, 1 for global
+ */
+
+/* default costs */
+static optcost_t optcost = { 4, 1, 15, 10, 2, 0 };
+
+static int
+addpmid(fetchctl_t *fp, pmID pmid)
+{
+ int i;
+ int j;
+
+ for (i = 0; i < fp->f_numpmid; i++) {
+ if (pmid == fp->f_pmidlist[i])
+ return 0;
+ if (pmid > fp->f_pmidlist[i])
+ break;
+ }
+ fp->f_numpmid++;
+ fp->f_pmidlist = (pmID *)realloc(fp->f_pmidlist, fp->f_numpmid*sizeof(pmID));
+ if (fp->f_pmidlist == NULL) {
+ __pmNoMem("addpmid", fp->f_numpmid*sizeof(pmID), PM_FATAL_ERR);
+ }
+
+ for (j = fp->f_numpmid-1; j > i; j--)
+ fp->f_pmidlist[j] = fp->f_pmidlist[j-1];
+ fp->f_pmidlist[i] = pmid;
+
+ return 1;
+}
+
+static int
+addinst(int *numinst, int **instlist, optreq_t *new)
+{
+ int i;
+ int j;
+ int k;
+ int numi;
+ int *ilist;
+ int match;
+
+ if (*numinst == 0)
+ return 0;
+ if (new->r_numinst == 0) {
+ *numinst = 0;
+ if (*instlist != NULL) {
+ free(*instlist);
+ *instlist = NULL;
+ }
+ return 1;
+ }
+ numi = *numinst;
+ if (numi == -1)
+ numi = 0;
+
+ ilist = (int *)realloc(*instlist, (numi + new->r_numinst)*sizeof(int));
+ if (ilist == NULL) {
+ __pmNoMem("addinst.up", (numi + new->r_numinst)*sizeof(int), PM_FATAL_ERR);
+ }
+
+ for (j = 0; j < new->r_numinst; j++) {
+ match = 0;
+ for (i = 0; i < numi; i++) {
+ if (ilist[i] == new->r_instlist[j]) {
+ match = 1;
+ break;
+ }
+ if (ilist[i] > new->r_instlist[j])
+ break;
+ }
+ if (match)
+ continue;
+ for (k = numi; k > i; k--)
+ ilist[k] = ilist[k-1];
+ ilist[i] = new->r_instlist[j];
+ numi++;
+ }
+
+ ilist = (int *)realloc(ilist, numi*sizeof(int));
+ if (ilist == NULL) {
+ __pmNoMem("addinst.down", numi*sizeof(int), PM_FATAL_ERR);
+ }
+
+ *numinst = numi;
+ *instlist = ilist;
+
+ return 1;
+}
+
+/*
+ * if we retrieve the instances identified by numa and lista[], how much larger
+ * is this than the set of instances identified by numb and listb[]?
+ */
+static int
+missinst(int numa, int *lista, int numb, int *listb)
+{
+ int xtra = 0;
+ int i;
+ int j;
+
+ PM_LOCK(__pmLock_libpcp);
+ /* count in lista[] but _not_ in listb[] */
+ if (numa == 0) {
+ /* special case for all instances in lista[] */
+ if (numb != 0 && numb < optcost.c_indomsize)
+ xtra += optcost.c_indomsize - numb;
+ }
+ else {
+ /* not all instances for both lista[] and listb[] */
+ i = 0;
+ j = 0;
+ while (i < numa && j < numb) {
+ if (lista[i] == listb[j]) {
+ i++;
+ j++;
+ }
+ else if (lista[i] < listb[j]) {
+ i++;
+ xtra++;
+ }
+ else {
+ j++;
+ xtra++;
+ }
+ }
+ xtra += (numa - i) + (numb - j);
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return xtra;
+}
+
+static void
+redoinst(fetchctl_t *fp)
+{
+ indomctl_t *idp;
+ pmidctl_t *pmp;
+ optreq_t *rqp;
+
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ idp->i_numinst = -1;
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ pmp->p_numinst = -1;
+ for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) {
+ addinst(&pmp->p_numinst, &pmp->p_instlist, rqp);
+ addinst(&idp->i_numinst, &idp->i_instlist, rqp);
+ }
+ }
+ }
+}
+
+static void
+redopmid(fetchctl_t *fp)
+{
+ indomctl_t *idp;
+ pmidctl_t *pmp;
+
+ fp->f_numpmid = 0;
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ addpmid(fp, pmp->p_pmid);
+ }
+ }
+}
+
+static int
+optCost(fetchctl_t *fp)
+{
+ indomctl_t *idp;
+ indomctl_t *xidp;
+ pmidctl_t *pmp;
+ pmidctl_t *xpmp;
+ __pmID_int *pmidp;
+ __pmInDom_int *indomp;
+ int pmd;
+ int cost = 0;
+ int done;
+
+ PM_LOCK(__pmLock_libpcp);
+ /*
+ * cost per PMD for the pmids in this fetch
+ */
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ pmidp = (__pmID_int *)&pmp->p_pmid;
+ pmd = pmidp->domain;
+ done = 0;
+ for (xidp = fp->f_idp; xidp != NULL; xidp = xidp->i_next) {
+ for (xpmp = xidp->i_pmp; xpmp != NULL; xpmp = xpmp->p_next) {
+ pmidp = (__pmID_int *)&xpmp->p_pmid;
+ if (xpmp != pmp && pmd == pmidp->domain) {
+ done = 1;
+ break;
+ }
+ if (xpmp == pmp) {
+ cost += optcost.c_pmid;
+ done = 1;
+ break;
+ }
+ }
+ if (done || xidp == idp)
+ break;
+ }
+ }
+ }
+
+ /*
+ * cost per PMD for the indoms in this fetch
+ */
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ indomp = (__pmInDom_int *)&idp->i_indom;
+ pmd = indomp->domain;
+ for (xidp = fp->f_idp; xidp != idp; xidp = xidp->i_next) {
+ indomp = (__pmInDom_int *)&xidp->i_indom;
+ if (pmd == indomp->domain)
+ break;
+ }
+ if (xidp == idp)
+ cost += optcost.c_indom;
+ }
+
+ /*
+ * cost for extra retrievals due to multiple metrics over the same indom
+ */
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ cost += optcost.c_xtrainst * missinst(idp->i_numinst, idp->i_instlist, pmp->p_numinst, pmp->p_instlist);
+ }
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return cost;
+}
+
+#ifdef PCP_DEBUG
+static char *
+statestr(int state, char *sbuf)
+{
+ sbuf[0] = '\0';
+ if (state & OPT_STATE_NEW) strcat(sbuf, "NEW ");
+ if (state & OPT_STATE_PMID) strcat(sbuf, "PMID ");
+ if (state & OPT_STATE_PROFILE) strcat(sbuf, "PROFILE ");
+ if (state & OPT_STATE_XREQ) strcat(sbuf, "XREQ ");
+ if (state & OPT_STATE_XPMID) strcat(sbuf, "XPMID ");
+ if (state & OPT_STATE_XINDOM) strcat(sbuf, "XINDOM ");
+ if (state & OPT_STATE_XFETCH) strcat(sbuf, "XFETCH ");
+ if (state & OPT_STATE_XPROFILE) strcat(sbuf, "XPROFILE ");
+
+ return sbuf;
+}
+
+static void
+dumplist(FILE *f, int style, char *tag, int numi, int *ilist)
+{
+ char strbuf[20];
+
+ fprintf(f, "%s: [%d]", tag, numi);
+ if (ilist == NULL)
+ fprintf(f, " (nil)\n");
+ else {
+ int i;
+ for (i = 0; i < numi; i++) {
+ if (style == 1)
+ fprintf(f, " %s", pmIDStr_r((pmID)ilist[i], strbuf, sizeof(strbuf)));
+ else
+ fprintf(f, " %d", ilist[i]);
+ }
+ fputc('\n', f);
+ }
+}
+
+static void
+___pmOptFetchDump(FILE *f, const fetchctl_t *fp)
+{
+ indomctl_t *idp;
+ pmidctl_t *pmp;
+ optreq_t *rqp;
+ char strbuf[100];
+
+ fflush(stderr);
+ fflush(stdout);
+ fprintf(f, "Dump optfetch structures from " PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p\n", fp, fp->f_next);
+ fprintf(f, "Fetch Control @ " PRINTF_P_PFX "%p: cost=%d state=%s\n", fp, fp->f_cost, statestr(fp->f_state, strbuf));
+ dumplist(f, 1, "PMIDs", fp->f_numpmid, (int *)fp->f_pmidlist);
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ fprintf(f, " InDom %s Control @ " PRINTF_P_PFX "%p:\n", pmInDomStr_r(idp->i_indom, strbuf, sizeof(strbuf)), idp);
+ dumplist(f, 0, " instances", idp->i_numinst, idp->i_instlist);
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ fprintf(f, " PMID %s Control @ " PRINTF_P_PFX "%p:\n", pmIDStr_r(pmp->p_pmid, strbuf, sizeof(strbuf)), pmp);
+ dumplist(f, 0, " instances", pmp->p_numinst, pmp->p_instlist);
+ for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) {
+ fprintf(f, " Request @ " PRINTF_P_PFX "%p:\n", rqp);
+ dumplist(f, 0, " instances", rqp->r_numinst, rqp->r_instlist);
+ }
+ }
+ }
+ fputc('\n', f);
+ fflush(f);
+}
+
+void
+__pmOptFetchDump(FILE *f, const fetchctl_t *root)
+{
+ const fetchctl_t *fp;
+
+ for (fp = root; fp != NULL; fp = fp->f_next)
+ ___pmOptFetchDump(f, fp);
+}
+#endif /* DEBUG */
+
+/*
+ * add a new request into a group of fetches ...
+ * only failure is from calloc() and this is fatal
+ */
+void
+__pmOptFetchAdd(fetchctl_t **root, optreq_t *new)
+{
+ fetchctl_t *fp;
+ fetchctl_t *tfp;
+ indomctl_t *idp;
+ pmidctl_t *pmp = NULL;
+ int mincost;
+ int change;
+ pmInDom indom = new->r_desc->indom;
+ pmID pmid = new->r_desc->pmid;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ /* add new fetch as first option ... will be reclaimed later if not used */
+ if ((fp = (fetchctl_t *)calloc(1, sizeof(fetchctl_t))) == NULL) {
+ __pmNoMem("optAddFetch.fetch", sizeof(fetchctl_t), PM_FATAL_ERR);
+ }
+ fp->f_next = *root;
+ *root = fp;
+
+ for (fp = *root; fp != NULL; fp = fp->f_next) {
+ fp->f_cost = optCost(fp);
+
+ change = OPT_STATE_XINDOM | OPT_STATE_XPMID | OPT_STATE_XREQ;
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ if (idp->i_indom != indom)
+ continue;
+ change = OPT_STATE_XPMID | OPT_STATE_XREQ;
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ if (pmp->p_pmid == pmid) {
+ change = OPT_STATE_XREQ;
+ break;
+ }
+ }
+ break;
+ }
+ if (fp == *root)
+ change |= OPT_STATE_XFETCH;
+ fp->f_state = (fp->f_state & OPT_STATE_UMASK) | change;
+
+ if (change & OPT_STATE_XINDOM) {
+ if ((idp = (indomctl_t *)calloc(1, sizeof(indomctl_t))) == NULL) {
+ __pmNoMem("optAddFetch.indomctl", sizeof(indomctl_t), PM_FATAL_ERR);
+ }
+ idp->i_indom = indom;
+ idp->i_next = fp->f_idp;
+ idp->i_numinst = -1;
+ fp->f_idp = idp;
+ }
+ if (change & OPT_STATE_XPMID) {
+ if ((pmp = (pmidctl_t *)calloc(1, sizeof(pmidctl_t))) == NULL) {
+ __pmNoMem("optAddFetch.pmidctl", sizeof(pmidctl_t), PM_FATAL_ERR);
+ }
+ pmp->p_next = idp->i_pmp;
+ idp->i_pmp = pmp;
+ pmp->p_pmid = pmid;
+ pmp->p_numinst = -1;
+ }
+ addinst(&pmp->p_numinst, &pmp->p_instlist, new);
+ if (addinst(&idp->i_numinst, &idp->i_instlist, new))
+ fp->f_state |= OPT_STATE_XPROFILE;
+
+ fp->f_newcost = optCost(fp);
+ if (fp == *root)
+ fp->f_newcost += optcost.c_fetch;
+#ifdef DESPERATE
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ char strbuf[100];
+ fprintf(stderr, "optFetch: cost=");
+ if (fp->f_cost == OPT_COST_INFINITY)
+ fprintf(stderr, "INFINITY");
+ else
+ fprintf(stderr, "%d", fp->f_cost);
+ fprintf(stderr, ", newcost=");
+ if (fp->f_newcost == OPT_COST_INFINITY)
+ fprintf(stderr, "INFINITY");
+ else
+ fprintf(stderr, "%d", fp->f_newcost);
+ fprintf(stderr, ", for %s @ grp 0x%x,",
+ pmIDStr_r(pmid, strbuf, sizeof(strbuf)), fp);
+ fprintf(stderr, " state %s\n",
+ statestr(fp->f_state, strbuf));
+ }
+#endif
+ }
+
+ tfp = NULL;
+ mincost = OPT_COST_INFINITY;
+ for (fp = *root; fp != NULL; fp = fp->f_next) {
+ int cost;
+ if (optcost.c_scope)
+ /* global */
+ cost = fp->f_newcost;
+ else
+ /* local */
+ cost = fp->f_newcost - fp->f_cost;
+ if (cost < mincost) {
+ mincost = cost;
+ tfp = fp;
+ }
+ }
+#ifdef DESPERATE
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ char strbuf[100];
+ fprintf(stderr, "optFetch: chose %s cost=%d for %s @ grp 0x%x,",
+ optcost.c_scope ? "global" : "incremental",
+ mincost, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), tfp);
+ fprintf(stderr, " change %s\n", statestr(tfp->f_state, strbuf));
+ }
+#endif
+
+ /*
+ * Warning! Traversal of the list is a bit tricky, because the
+ * current element (fp) may be freed, making fp->fp_next
+ * a bad idea at the end of each iteration ...
+ */
+ for (fp = *root; fp != NULL; ) {
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ if (idp->i_indom != indom)
+ continue;
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ if (pmp->p_pmid == pmid)
+ break;
+ }
+ break;
+ }
+ assert(idp != NULL && pmp != NULL);
+ if (fp == tfp) {
+ /*
+ * The chosen one ...
+ */
+ if (fp->f_state & OPT_STATE_XFETCH)
+ fp->f_state |= OPT_STATE_NEW;
+ if (addpmid(tfp, pmid))
+ fp->f_state |= OPT_STATE_PMID;
+ if (fp->f_state & OPT_STATE_XPROFILE)
+ fp->f_state |= OPT_STATE_PROFILE;
+ new->r_next = pmp->p_rqp;
+ new->r_fetch = tfp;
+ pmp->p_rqp = new;
+ fp->f_cost = fp->f_newcost;
+ fp->f_state &= OPT_STATE_UMASK;
+ fp = fp->f_next;
+ }
+ else {
+ /*
+ * Otherwise, need to undo changes made to data structures.
+ * Note. if new structures added, they will be at the start of
+ * their respective lists.
+ */
+ if (fp->f_state & OPT_STATE_XPMID) {
+ idp->i_pmp = pmp->p_next;
+ free(pmp);
+ }
+ if (fp->f_state & OPT_STATE_XINDOM) {
+ fp->f_idp = idp->i_next;
+ free(idp);
+ }
+ if (fp->f_state & OPT_STATE_XFETCH) {
+ *root = fp->f_next;
+ free(fp);
+ fp = *root;
+ }
+ else {
+ redoinst(fp);
+ fp->f_state &= OPT_STATE_UMASK;
+ fp = fp->f_next;
+ }
+ }
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+}
+
+/*
+ * remove a request from a group of fetches
+ */
+int
+__pmOptFetchDel(fetchctl_t **root, optreq_t *new)
+{
+ fetchctl_t *fp;
+ fetchctl_t *p_fp;
+ indomctl_t *idp;
+ indomctl_t *p_idp;
+ pmidctl_t *pmp;
+ pmidctl_t *p_pmp;
+ optreq_t *rqp;
+ optreq_t *p_rqp;
+
+ p_fp = NULL;
+ for (fp = *root; fp != NULL; fp = fp->f_next) {
+ p_idp = NULL;
+ for (idp = fp->f_idp; idp != NULL; idp = idp->i_next) {
+ p_pmp = NULL;
+ for (pmp = idp->i_pmp; pmp != NULL; pmp = pmp->p_next) {
+ p_rqp = NULL;
+ for (rqp = pmp->p_rqp; rqp != NULL; rqp = rqp->r_next) {
+ if (rqp == new) {
+ if (p_rqp != NULL)
+ /* not first request for this metric */
+ p_rqp->r_next = rqp->r_next;
+ else if (rqp->r_next != NULL)
+ /* first of several requests for this metric */
+ pmp->p_rqp = rqp->r_next;
+ else {
+ /* only request for this metric */
+ if (p_pmp != NULL)
+ /* not first metric for this indom */
+ p_pmp->p_next = pmp->p_next;
+ else if (pmp->p_next != NULL)
+ /* first of several metrics for this indom */
+ idp->i_pmp = pmp->p_next;
+ else {
+ /* only metric for this indom */
+ if (p_idp != NULL)
+ /* not first indom for this fetch */
+ p_idp->i_next = idp->i_next;
+ else if (idp->i_next != NULL)
+ /* first of several idoms for this fetch */
+ fp->f_idp = idp->i_next;
+ else {
+ /* only indom for this fetch */
+ if (p_fp != NULL)
+ /* not first fetch for the group */
+ p_fp->f_next = fp->f_next;
+ else
+ /* first of fetch for the group */
+ *root = fp->f_next;
+ free(fp);
+ fp = NULL;
+ }
+ free(idp);
+ }
+ free(pmp);
+ }
+ /* data structures repaired, now redo lists */
+ if (fp != NULL) {
+ redoinst(fp);
+ redopmid(fp);
+ fp->f_state = OPT_STATE_PMID | OPT_STATE_PROFILE;
+ fp->f_cost = optCost(fp);
+ }
+ return 0;
+ }
+ p_rqp = rqp;
+ }
+ p_pmp = pmp;
+ }
+ p_idp = idp;
+ }
+ p_fp = fp;
+ }
+ return -1;
+}
+
+void
+__pmOptFetchRedo(fetchctl_t **root)
+{
+ fetchctl_t *newroot = NULL;
+ fetchctl_t *fp;
+ fetchctl_t *t_fp;
+ indomctl_t *idp;
+ indomctl_t *t_idp;
+ pmidctl_t *pmp;
+ pmidctl_t *t_pmp;
+ optreq_t *rqp;
+ optreq_t *t_rqp;
+ optreq_t *p_rqp;
+ optreq_t *rlist;
+ int numreq;
+
+ rlist = NULL;
+ numreq = 0;
+ /*
+ * collect all of the requests first
+ */
+ for (fp = *root; fp != NULL; ) {
+ for (idp = fp->f_idp; idp != NULL; ) {
+ for (pmp = idp->i_pmp; pmp != NULL; ) {
+ for (rqp = pmp->p_rqp; rqp != NULL; ) {
+ t_rqp = rqp->r_next;
+ rqp->r_next = rlist;
+ rlist = rqp;
+ rqp = t_rqp;
+ numreq++;
+ }
+ t_pmp = pmp;
+ pmp = pmp->p_next;
+ free(t_pmp);
+ }
+ t_idp = idp;
+ idp = idp->i_next;
+ free(t_idp);
+ }
+ t_fp = fp;
+ fp = fp->f_next;
+ free(t_fp);
+ }
+
+ if (numreq) {
+ /* something to do, randomly cut and splice the list of requests */
+ numreq = (int)lrand48() % numreq;
+ t_rqp = rlist;
+ p_rqp = NULL;
+ for (rqp = rlist; rqp != NULL; rqp = rqp->r_next) {
+ if (numreq == 0)
+ t_rqp = rqp;
+ numreq--;
+ p_rqp = rqp;
+ }
+ if (p_rqp == NULL || t_rqp == rlist)
+ /* do nothing */
+ ;
+ else {
+ /* p_rqp is end of list, t_rqp is new head */
+ p_rqp->r_next = rlist;
+ rlist = t_rqp->r_next;
+ t_rqp->r_next = NULL;
+ }
+
+ /* now add them all back again */
+ for (rqp = rlist; rqp != NULL; ) {
+ /* warning, rqp->r_next may change */
+ t_rqp = rqp->r_next;
+ __pmOptFetchAdd(&newroot, rqp);
+ rqp = t_rqp;
+ }
+ }
+
+ *root = newroot;
+ return;
+}
+
+void
+__pmOptFetchGetParams(optcost_t *ocp)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ *ocp = optcost;
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+}
+
+void
+__pmOptFetchPutParams(optcost_t *ocp)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ optcost = *ocp;
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+}
diff --git a/src/libpcp/src/p_auth.c b/src/libpcp/src/p_auth.c
new file mode 100644
index 0000000..d2077e7
--- /dev/null
+++ b/src/libpcp/src/p_auth.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include <ctype.h>
+
+/*
+ * PDU for per-user authentication (PDU_AUTH)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int attr; /* PCP_ATTR code (optional, can be zero) */
+ char value[sizeof(int)];
+} auth_t;
+
+int
+__pmSendAuth(int fd, int from, int attr, const char *value, int length)
+{
+ size_t need;
+ auth_t *pp;
+ int i;
+ int sts;
+
+ if (length < 0 || length >= LIMIT_AUTH_PDU)
+ return PM_ERR_IPC;
+
+ need = (sizeof(*pp) - sizeof(pp->value)) + length;
+ if ((pp = (auth_t *)__pmFindPDUBuf((int)need)) == NULL)
+ return -oserror();
+ pp->hdr.len = (int)need;
+ pp->hdr.type = PDU_AUTH;
+ pp->hdr.from = from;
+ pp->attr = htonl(attr);
+ memcpy(&pp->value, value, length);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char buffer[LIMIT_AUTH_PDU];
+ for (i = 0; i < length; i++)
+ buffer[i] = isprint((int)value[i]) ? value[i] : '.';
+ buffer[length] = buffer[LIMIT_AUTH_PDU-1] = '\0';
+ if (attr)
+ fprintf(stderr, "__pmSendAuth [len=%d]: attr=%x value=\"%s\"\n",
+ length, attr, buffer);
+ else
+ fprintf(stderr, "__pmSendAuth [len=%d]: payload=\"%s\"\n",
+ length, buffer);
+ }
+#endif
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeAuth(__pmPDU *pdubuf, int *attr, char **value, int *vlen)
+{
+ auth_t *pp;
+ int i;
+ int pdulen;
+ int length;
+
+ pp = (auth_t *)pdubuf;
+ pdulen = pp->hdr.len; /* ntohl() converted already in __pmGetPDU() */
+ length = pdulen - (sizeof(*pp) - sizeof(pp->value));
+ if (length < 0 || length >= LIMIT_AUTH_PDU)
+ return PM_ERR_IPC;
+
+ *attr = ntohl(pp->attr);
+ *value = length ? pp->value : NULL;
+ *vlen = length;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char buffer[LIMIT_AUTH_PDU];
+ for (i = 0; i < length; i++)
+ buffer[i] = isprint((int)pp->value[i]) ? pp->value[i] : '.';
+ buffer[length] = buffer[LIMIT_AUTH_PDU-1] = '\0';
+ if (*attr)
+ fprintf(stderr, "__pmDecodeAuth [len=%d]: attr=%x value=\"%s\"\n",
+ length, *attr, buffer);
+ else
+ fprintf(stderr, "__pmDecodeAuth [len=%d]: payload=\"%s\"\n",
+ length, buffer);
+ }
+#endif
+
+ return 0;
+}
diff --git a/src/libpcp/src/p_creds.c b/src/libpcp/src/p_creds.c
new file mode 100644
index 0000000..98aa996
--- /dev/null
+++ b/src/libpcp/src/p_creds.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+#define LIMIT_CREDS 1024
+
+/*
+ * PDU for process credentials (PDU_CREDS)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int numcreds;
+ __pmCred credlist[1];
+} creds_t;
+
+int
+__pmSendCreds(int fd, int from, int credcount, const __pmCred *credlist)
+{
+ size_t need;
+ creds_t *pp;
+ int i;
+ int sts;
+
+ if (credcount <= 0 || credcount > LIMIT_CREDS || credlist == NULL)
+ return PM_ERR_IPC;
+
+ need = sizeof(creds_t) + ((credcount-1) * sizeof(__pmCred));
+ if ((pp = (creds_t *)__pmFindPDUBuf((int)need)) == NULL)
+ return -oserror();
+ pp->hdr.len = (int)need;
+ pp->hdr.type = PDU_CREDS;
+ pp->hdr.from = from;
+ pp->numcreds = htonl(credcount);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ for (i = 0; i < credcount; i++)
+ fprintf(stderr, "__pmSendCreds: #%d = %x\n", i, *(unsigned int*)&(credlist[i]));
+#endif
+ /* swab and fix bitfield order */
+ for (i = 0; i < credcount; i++)
+ pp->credlist[i] = __htonpmCred(credlist[i]);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeCreds(__pmPDU *pdubuf, int *sender, int *credcount, __pmCred **credlist)
+{
+ creds_t *pp;
+ int i;
+ int len;
+ int need;
+ int numcred;
+ __pmCred *list;
+
+ pp = (creds_t *)pdubuf;
+ len = pp->hdr.len; /* ntohl() converted already in __pmGetPDU() */
+ numcred = ntohl(pp->numcreds);
+ if (numcred < 0 || numcred > LIMIT_CREDS)
+ return PM_ERR_IPC;
+ need = sizeof(creds_t) + ((numcred-1) * sizeof(__pmCred));
+ if (need != len)
+ return PM_ERR_IPC;
+
+ *sender = pp->hdr.from; /* ntohl() converted already in __pmGetPDU() */
+ if ((list = (__pmCred *)malloc(sizeof(__pmCred) * numcred)) == NULL)
+ return -oserror();
+
+ /* swab and fix bitfield order */
+ for (i = 0; i < numcred; i++) {
+ list[i] = __ntohpmCred(pp->credlist[i]);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ for (i = 0; i < numcred; i++)
+ fprintf(stderr, "__pmDecodeCreds: #%d = { type=0x%x a=0x%x b=0x%x c=0x%x }\n",
+ i, list[i].c_type, list[i].c_vala,
+ list[i].c_valb, list[i].c_valc);
+#endif
+
+ *credlist = list;
+ *credcount = numcred;
+
+ return 0;
+}
diff --git a/src/libpcp/src/p_desc.c b/src/libpcp/src/p_desc.c
new file mode 100644
index 0000000..46a8a66
--- /dev/null
+++ b/src/libpcp/src/p_desc.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for pmLookupDesc request (PDU_DESC_REQ)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ pmID pmid;
+} desc_req_t;
+
+int
+__pmSendDescReq(int fd, int from, pmID pmid)
+{
+ desc_req_t *pp;
+ int sts;
+
+ if ((pp = (desc_req_t *)__pmFindPDUBuf(sizeof(desc_req_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(desc_req_t);
+ pp->hdr.type = PDU_DESC_REQ;
+ pp->hdr.from = from;
+ pp->pmid = __htonpmID(pmid);
+
+#ifdef DESPERATE
+ {
+ char strbuf[20];
+ fprintf(stderr, "__pmSendDescReq: converted 0x%08x (%s) to 0x%08x\n", pmid, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), pp->pmid);
+ }
+#endif
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeDescReq(__pmPDU *pdubuf, pmID *pmid)
+{
+ desc_req_t *pp;
+ char *pduend;
+
+ pp = (desc_req_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp != sizeof(desc_req_t))
+ return PM_ERR_IPC;
+
+ *pmid = __ntohpmID(pp->pmid);
+ return 0;
+}
+
+/*
+ * PDU for pmLookupDesc result (PDU_DESC)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ pmDesc desc;
+} desc_t;
+
+int
+__pmSendDesc(int fd, int ctx, pmDesc *desc)
+{
+ desc_t *pp;
+ int sts;
+
+ if ((pp = (desc_t *)__pmFindPDUBuf(sizeof(desc_t))) == NULL)
+ return -oserror();
+
+ pp->hdr.len = sizeof(desc_t);
+ pp->hdr.type = PDU_DESC;
+ pp->hdr.from = ctx;
+ pp->desc.type = htonl(desc->type);
+ pp->desc.sem = htonl(desc->sem);
+ pp->desc.indom = __htonpmInDom(desc->indom);
+ pp->desc.units = __htonpmUnits(desc->units);
+ pp->desc.pmid = __htonpmID(desc->pmid);
+
+ sts =__pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeDesc(__pmPDU *pdubuf, pmDesc *desc)
+{
+ desc_t *pp;
+ char *pduend;
+
+ pp = (desc_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp != sizeof(desc_t))
+ return PM_ERR_IPC;
+
+ desc->type = ntohl(pp->desc.type);
+ desc->sem = ntohl(pp->desc.sem);
+ desc->indom = __ntohpmInDom(pp->desc.indom);
+ desc->units = __ntohpmUnits(pp->desc.units);
+ desc->pmid = __ntohpmID(pp->desc.pmid);
+ return 0;
+}
diff --git a/src/libpcp/src/p_error.c b/src/libpcp/src/p_error.c
new file mode 100644
index 0000000..819757e
--- /dev/null
+++ b/src/libpcp/src/p_error.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include <ctype.h>
+
+/*
+ * Old V1 error codes are only used in 2 places now:
+ * 1) embedded in pmResults of V1 archives, and
+ * 2) as part of the client/pmcd connection challenge where all versions
+ * if pmcd return the status as a V1 error code as a legacy of
+ * migration from V1 to V2 protocols that we're stuck with (not
+ * really an issue, as the error code is normally 0)
+ *
+ * These macros were removed from the more public pmapi.h and impl.h
+ * headers in PCP 3.6
+ */
+#define PM_ERR_BASE1 1000
+#define PM_ERR_V1(e) (e)+PM_ERR_BASE2-PM_ERR_BASE1
+#define XLATE_ERR_1TO2(e) \
+ ((e) <= -PM_ERR_BASE1 ? (e)+PM_ERR_BASE1-PM_ERR_BASE2 : (e))
+#define XLATE_ERR_2TO1(e) \
+ ((e) <= -PM_ERR_BASE2 ? PM_ERR_V1(e) : (e))
+
+/*
+ * PDU for general error reporting (PDU_ERROR)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int code; /* error code */
+} p_error_t;
+
+/*
+ * and the extended variant, with a second datum word
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int code; /* error code */
+ int datum; /* additional information */
+} x_error_t;
+
+int
+__pmSendError(int fd, int from, int code)
+{
+ p_error_t *pp;
+ int sts;
+
+ if ((pp = (p_error_t *)__pmFindPDUBuf(sizeof(p_error_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(p_error_t);
+ pp->hdr.type = PDU_ERROR;
+ pp->hdr.from = from;
+
+ pp->code = code;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr,
+ "__pmSendError: sending error PDU (code=%d, toversion=%d)\n",
+ pp->code, __pmVersionIPC(fd));
+#endif
+
+ pp->code = htonl(pp->code);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmSendXtendError(int fd, int from, int code, int datum)
+{
+ x_error_t *pp;
+ int sts;
+
+ if ((pp = (x_error_t *)__pmFindPDUBuf(sizeof(x_error_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(x_error_t);
+ pp->hdr.type = PDU_ERROR;
+ pp->hdr.from = from;
+
+ /*
+ * It is ALWAYS a PCP 1.x error code here ... this was required
+ * to support migration from the V1 to V2 protocols when a V2 pmcd
+ * (who is the sole user of this routine) supported connections
+ * from both V1 and V2 PMAPI clients ... for the same reason we
+ * cannot retire this translation, even when the V1 protocols are
+ * no longer supported in all other IPC cases.
+ *
+ * For most common cases, code is 0 so it makes no difference.
+ */
+ pp->code = htonl(XLATE_ERR_2TO1(code));
+
+ pp->datum = datum; /* NOTE: caller must swab this */
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeError(__pmPDU *pdubuf, int *code)
+{
+ p_error_t *pp;
+ int sts;
+
+ pp = (p_error_t *)pdubuf;
+ if (pp->hdr.len != sizeof(p_error_t) && pp->hdr.len != sizeof(x_error_t)) {
+ sts = *code = PM_ERR_IPC;
+ } else {
+ *code = ntohl(pp->code);
+ sts = 0;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr,
+ "__pmDecodeError: got error PDU (code=%d, fromversion=%d)\n",
+ *code, __pmLastVersionIPC());
+#endif
+ return sts;
+}
+
+int
+__pmDecodeXtendError(__pmPDU *pdubuf, int *code, int *datum)
+{
+ x_error_t *pp = (x_error_t *)pdubuf;
+ int sts;
+
+ if (pp->hdr.len != sizeof(p_error_t) && pp->hdr.len != sizeof(x_error_t)) {
+ *code = PM_ERR_IPC;
+ } else {
+ /*
+ * It is ALWAYS a PCP 1.x error code here ... see note above
+ * in __pmSendXtendError()
+ */
+ *code = XLATE_ERR_1TO2((int)ntohl(pp->code));
+ }
+ if (pp->hdr.len == sizeof(x_error_t)) {
+ /* really version 2 extended error PDU */
+ sts = PDU_VERSION2;
+ *datum = pp->datum; /* NOTE: caller must swab this */
+ }
+ else {
+ sts = PM_ERR_IPC;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "__pmDecodeXtendError: "
+ "got error PDU (code=%d, datum=%d, version=%d)\n",
+ *code, *datum, sts);
+#endif
+
+ return sts;
+}
diff --git a/src/libpcp/src/p_fetch.c b/src/libpcp/src/p_fetch.c
new file mode 100644
index 0000000..db29687
--- /dev/null
+++ b/src/libpcp/src/p_fetch.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for pmFetch request (PDU_FETCH)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int ctxnum; /* context no. */
+ __pmTimeval when; /* desired time */
+ int numpmid; /* no. PMIDs to follow */
+ pmID pmidlist[1]; /* one or more */
+} fetch_t;
+
+int
+__pmSendFetch(int fd, int from, int ctxnum, __pmTimeval *when, int numpmid, pmID *pmidlist)
+{
+ size_t need;
+ fetch_t *pp;
+ int j;
+ int sts;
+
+ need = sizeof(fetch_t) + (numpmid-1) * sizeof(pmID);
+ if ((pp = (fetch_t *)__pmFindPDUBuf((int)need)) == NULL)
+ return -oserror();
+ pp->hdr.len = (int)need;
+ pp->hdr.type = PDU_FETCH;
+ /*
+ * note: context id may be sent twice due to protocol evolution and
+ * backwards compatibility issues
+ */
+ pp->hdr.from = from;
+ pp->ctxnum = htonl(ctxnum);
+ if (when == NULL)
+ memset((void *)&pp->when, 0, sizeof(pp->when));
+ else {
+#if defined(HAVE_32BIT_LONG)
+ pp->when.tv_sec = htonl(when->tv_sec);
+ pp->when.tv_usec = htonl(when->tv_usec);
+#else
+ pp->when.tv_sec = htonl((__int32_t)when->tv_sec);
+ pp->when.tv_usec = htonl((__int32_t)when->tv_usec);
+#endif
+ }
+ pp->numpmid = htonl(numpmid);
+ for (j = 0; j < numpmid; j++)
+ pp->pmidlist[j] = __htonpmID(pmidlist[j]);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeFetch(__pmPDU *pdubuf, int *ctxnum, __pmTimeval *when, int *numpmidp, pmID **pmidlist)
+{
+ fetch_t *pp;
+ char *pduend;
+ int numpmid;
+ int j;
+
+ pp = (fetch_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp < sizeof(fetch_t))
+ return PM_ERR_IPC;
+ numpmid = ntohl(pp->numpmid);
+ if (numpmid <= 0 || numpmid > pp->hdr.len)
+ return PM_ERR_IPC;
+ if (numpmid >= (INT_MAX - sizeof(fetch_t)) / sizeof(pmID))
+ return PM_ERR_IPC;
+ if ((pduend - (char*)pp) != sizeof(fetch_t) + ((sizeof(pmID)) * (numpmid-1)))
+ return PM_ERR_IPC;
+
+ for (j = 0; j < numpmid; j++)
+ pp->pmidlist[j] = __ntohpmID(pp->pmidlist[j]);
+
+ *ctxnum = ntohl(pp->ctxnum);
+ when->tv_sec = ntohl(pp->when.tv_sec);
+ when->tv_usec = ntohl(pp->when.tv_usec);
+ *numpmidp = numpmid;
+ *pmidlist = pp->pmidlist;
+ __pmPinPDUBuf((void *)pdubuf);
+ return 0;
+}
diff --git a/src/libpcp/src/p_instance.c b/src/libpcp/src/p_instance.c
new file mode 100644
index 0000000..3b5474d
--- /dev/null
+++ b/src/libpcp/src/p_instance.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for pm*InDom request (PDU_INSTANCE_REQ)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ pmInDom indom;
+ __pmTimeval when; /* desired time */
+ int inst; /* may be PM_IN_NULL */
+ int namelen; /* chars in name[], may be 0 */
+ char name[sizeof(int)]; /* may be missing */
+} instance_req_t;
+
+int
+__pmSendInstanceReq(int fd, int from, const __pmTimeval *when, pmInDom indom,
+ int inst, const char *name)
+{
+ instance_req_t *pp;
+ int need;
+ int sts;
+
+ need = sizeof(instance_req_t) - sizeof(int);
+ if (name != NULL)
+ need += PM_PDU_SIZE_BYTES(strlen(name));
+ if ((pp = (instance_req_t *)__pmFindPDUBuf(sizeof(need))) == NULL)
+ return -oserror();
+ pp->hdr.len = need;
+ pp->hdr.type = PDU_INSTANCE_REQ;
+ pp->hdr.from = from;
+ pp->when.tv_sec = htonl((__int32_t)when->tv_sec);
+ pp->when.tv_usec = htonl((__int32_t)when->tv_usec);
+ pp->indom = __htonpmInDom(indom);
+ pp->inst = htonl(inst);
+ if (name == NULL)
+ pp->namelen = 0;
+ else {
+ pp->namelen = (int)strlen(name);
+ memcpy((void *)pp->name, (void *)name, pp->namelen);
+#ifdef PCP_DEBUG
+ if ((pp->namelen % sizeof(__pmPDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = pp->name + pp->namelen;
+
+ for (pad = sizeof(__pmPDU) - 1; pad >= (pp->namelen % sizeof(__pmPDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+ pp->namelen = htonl(pp->namelen);
+ }
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeInstanceReq(__pmPDU *pdubuf, __pmTimeval *when, pmInDom *indom, int *inst, char **name)
+{
+ instance_req_t *pp;
+ char *pdu_end;
+ int namelen;
+
+ pp = (instance_req_t *)pdubuf;
+ pdu_end = (char *)pdubuf + pp->hdr.len;
+
+ if (pdu_end - (char *)pp < sizeof(instance_req_t) - sizeof(pp->name))
+ return PM_ERR_IPC;
+
+ when->tv_sec = ntohl(pp->when.tv_sec);
+ when->tv_usec = ntohl(pp->when.tv_usec);
+ *indom = __ntohpmInDom(pp->indom);
+ *inst = ntohl(pp->inst);
+ namelen = ntohl(pp->namelen);
+ if (namelen > 0) {
+ if (namelen >= INT_MAX - 1 || namelen > pp->hdr.len)
+ return PM_ERR_IPC;
+ if (pdu_end - (char *)pp < sizeof(instance_req_t) - sizeof(pp->name) + namelen)
+ return PM_ERR_IPC;
+ if ((*name = (char *)malloc(namelen+1)) == NULL)
+ return -oserror();
+ strncpy(*name, pp->name, namelen);
+ (*name)[namelen] = '\0';
+ }
+ else if (namelen < 0) {
+ return PM_ERR_IPC;
+ } else {
+ *name = NULL;
+ }
+ return 0;
+}
+
+/*
+ * PDU for pm*InDom result (PDU_INSTANCE)
+ */
+typedef struct {
+ int inst; /* internal instance id */
+ int namelen; /* chars in name[], may be 0 */
+ char name[sizeof(int)]; /* may be missing */
+} instlist_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ pmInDom indom;
+ int numinst; /* no. of elts to follow */
+ __pmPDU rest[1]; /* array of instlist_t */
+} instance_t;
+
+int
+__pmSendInstance(int fd, int from, __pmInResult *result)
+{
+ instance_t *rp;
+ instlist_t *ip;
+ int need;
+ int i;
+ int j;
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM)
+ __pmDumpInResult(stderr, result);
+#endif
+
+ need = sizeof(*rp) - sizeof(rp->rest);
+ /* instlist_t + name rounded up to a __pmPDU boundary */
+ for (i = 0; i < result->numinst; i++) {
+ need += sizeof(*ip) - sizeof(ip->name);
+ if (result->namelist != NULL)
+ need += PM_PDU_SIZE_BYTES(strlen(result->namelist[i]));
+ }
+
+ if ((rp = (instance_t *)__pmFindPDUBuf(need)) == NULL)
+ return -oserror();
+ rp->hdr.len = need;
+ rp->hdr.type = PDU_INSTANCE;
+ rp->hdr.from = from;
+ rp->indom = __htonpmInDom(result->indom);
+ rp->numinst = htonl(result->numinst);
+
+ for (i = j = 0; i < result->numinst; i++) {
+ ip = (instlist_t *)&rp->rest[j/sizeof(__pmPDU)];
+ if (result->instlist != NULL)
+ ip->inst = htonl(result->instlist[i]);
+ else
+ /* weird, but this is going to be ignored at the other end */
+ ip->inst = htonl(PM_IN_NULL);
+ if (result->namelist != NULL) {
+ ip->namelen = (int)strlen(result->namelist[i]);
+ memcpy((void *)ip->name, (void *)result->namelist[i], ip->namelen);
+#ifdef PCP_DEBUG
+ if ((ip->namelen % sizeof(__pmPDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = ip->name + ip->namelen;
+ for (pad = sizeof(__pmPDU) - 1; pad >= (ip->namelen % sizeof(__pmPDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+ j += sizeof(*ip) - sizeof(ip->name) + PM_PDU_SIZE_BYTES(ip->namelen);
+ ip->namelen = htonl(ip->namelen);
+ }
+ else {
+ ip->namelen = 0;
+ j += sizeof(*ip) - sizeof(ip->name);
+ }
+ }
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)rp);
+ __pmUnpinPDUBuf(rp);
+ return sts;
+}
+
+int
+__pmDecodeInstance(__pmPDU *pdubuf, __pmInResult **result)
+{
+ int i;
+ int j;
+ instance_t *rp;
+ instlist_t *ip;
+ __pmInResult *res;
+ int sts;
+ char *p;
+ char *pdu_end;
+ int keep_instlist;
+ int keep_namelist;
+
+ rp = (instance_t *)pdubuf;
+ pdu_end = (char *)pdubuf + rp->hdr.len;
+
+ if (pdu_end - (char *)pdubuf < sizeof(instance_t) - sizeof(__pmPDU))
+ return PM_ERR_IPC;
+
+ if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL)
+ return -oserror();
+ res->instlist = NULL;
+ res->namelist = NULL;
+ res->indom = __ntohpmInDom(rp->indom);
+ res->numinst = ntohl(rp->numinst);
+
+ if (res->numinst >= (INT_MAX / sizeof(res->instlist[0])) ||
+ res->numinst >= (INT_MAX / sizeof(res->namelist[0])) ||
+ res->numinst >= rp->hdr.len) {
+ sts = PM_ERR_IPC;
+ goto badsts;
+ }
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ sts = -oserror();
+ goto badsts;
+ }
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ sts = -oserror();
+ goto badsts;
+ }
+ /* required for __pmFreeInResult() in the event of a later error */
+ memset(res->namelist, 0, res->numinst * sizeof(res->namelist[0]));
+
+ if (res->numinst == 1)
+ keep_instlist = keep_namelist = 0;
+ else
+ keep_instlist = keep_namelist = 1;
+
+ for (i = j = 0; i < res->numinst; i++) {
+ ip = (instlist_t *)&rp->rest[j/sizeof(__pmPDU)];
+ if (sizeof(instlist_t) - sizeof(ip->name) > (size_t)(pdu_end - (char *)ip)) {
+ sts = PM_ERR_IPC;
+ goto badsts;
+ }
+
+ res->instlist[i] = ntohl(ip->inst);
+ if (res->instlist[i] != PM_IN_NULL)
+ keep_instlist = 1;
+ ip->namelen = ntohl(ip->namelen);
+ if (ip->namelen > 0)
+ keep_namelist = 1;
+ if (ip->namelen < 0) {
+ sts = PM_ERR_IPC;
+ goto badsts;
+ }
+ if (sizeof(instlist_t) - sizeof(int) + ip->namelen > (size_t)(pdu_end - (char *)ip)) {
+ sts = PM_ERR_IPC;
+ goto badsts;
+ }
+ if ((p = (char *)malloc(ip->namelen + 1)) == NULL) {
+ sts = -oserror();
+ goto badsts;
+ }
+ memcpy((void *)p, (void *)ip->name, ip->namelen);
+ p[ip->namelen] = '\0';
+ res->namelist[i] = p;
+ j += sizeof(*ip) - sizeof(ip->name) + PM_PDU_SIZE_BYTES(ip->namelen);
+ }
+ if (keep_instlist == 0) {
+ free(res->instlist);
+ res->instlist = NULL;
+ }
+ if (keep_namelist == 0) {
+ free(res->namelist[0]);
+ free(res->namelist);
+ res->namelist = NULL;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM)
+ __pmDumpInResult(stderr, res);
+#endif
+ *result = res;
+ return 0;
+
+badsts:
+ __pmFreeInResult(res);
+ return sts;
+}
diff --git a/src/libpcp/src/p_lcontrol.c b/src/libpcp/src/p_lcontrol.c
new file mode 100644
index 0000000..9a57214
--- /dev/null
+++ b/src/libpcp/src/p_lcontrol.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for __pmControlLogging request (PDU_LOG_CONTROL)
+ */
+
+typedef struct {
+ pmID v_pmid;
+ int v_numval; /* no. of vlist els to follow */
+ __pmValue_PDU v_list[1]; /* one or more */
+} vlist_t;
+
+typedef struct {
+ __pmPDUHdr c_hdr;
+ int c_control; /* mandatory or advisory */
+ int c_state; /* off, maybe or on */
+ int c_delta; /* requested logging interval (msec) */
+ int c_numpmid; /* no. of vlist_ts to follow */
+ __pmPDU c_data[1]; /* one or more */
+} control_req_t;
+
+int
+__pmSendLogControl(int fd, const pmResult *request, int control, int state, int delta)
+{
+ pmValueSet *vsp;
+ int i;
+ int j;
+ control_req_t *pp;
+ int need;
+ vlist_t *vp;
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ __pmDumpResult(stderr, request);
+#endif
+
+ /* advisory+maybe logging and retrospective logging (delta < 0) are not
+ *permitted
+ */
+ if (delta < 0 ||
+ (control == PM_LOG_ADVISORY && state == PM_LOG_MAYBE))
+ return -EINVAL;
+
+ /* PDU header, control, state and count of metrics */
+ need = sizeof(control_req_t) - sizeof(pp->c_data);
+ for (i = 0; i < request->numpmid; i++) {
+ /* plus PMID and count of values */
+ if (request->vset[i]->numval > 0)
+ need += sizeof(vlist_t) + (request->vset[i]->numval - 1)*sizeof(__pmValue_PDU);
+ else
+ need += sizeof(vlist_t) - sizeof(__pmValue_PDU);
+ }
+ if ((pp = (control_req_t *)__pmFindPDUBuf(need)) == NULL)
+ return -oserror();
+ pp->c_hdr.len = need;
+ pp->c_hdr.type = PDU_LOG_CONTROL;
+ pp->c_hdr.from = FROM_ANON; /* context does not matter here */
+ pp->c_control = htonl(control);
+ pp->c_state = htonl(state);
+ pp->c_delta = htonl(delta);
+ pp->c_numpmid = htonl(request->numpmid);
+ vp = (vlist_t *)pp->c_data;
+ for (i = 0; i < request->numpmid; i++) {
+ vsp = request->vset[i];
+ vp->v_pmid = __htonpmID(vsp->pmid);
+ vp->v_numval = htonl(vsp->numval);
+ /*
+ * Note: spec says only PM_VAL_INSITU can be used ... we don't
+ * check vsp->valfmt -- this is OK, because the "value" is never
+ * looked at!
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ vp->v_list[j].inst = htonl(vsp->vlist[j].inst);
+ vp->v_list[j].value.lval = htonl(vsp->vlist[j].value.lval);
+ }
+ if (vsp->numval > 0)
+ vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) + (vsp->numval-1)*sizeof(__pmValue_PDU));
+ else
+ vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) - sizeof(__pmValue_PDU));
+ }
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeLogControl(const __pmPDU *pdubuf, pmResult **request, int *control, int *state, int *delta)
+{
+ int sts;
+ int i;
+ int j;
+ int nv;
+ const control_req_t *pp;
+ char *pduend;
+ int numpmid;
+ size_t need;
+ pmResult *req;
+ pmValueSet *vsp;
+ vlist_t *vp;
+
+ pp = (const control_req_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->c_hdr.len;
+ if (pduend - (char *)pdubuf < sizeof(control_req_t))
+ return PM_ERR_IPC;
+
+ *control = ntohl(pp->c_control);
+ *state = ntohl(pp->c_state);
+ *delta = ntohl(pp->c_delta);
+ numpmid = ntohl(pp->c_numpmid);
+ vp = (vlist_t *)pp->c_data;
+
+ if (numpmid < 0 || numpmid > pp->c_hdr.len)
+ return PM_ERR_IPC;
+ if (numpmid >= (INT_MAX - sizeof(pmResult)) / sizeof(pmValueSet *))
+ return PM_ERR_IPC;
+ need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *);
+ if ((req = (pmResult *)malloc(need)) == NULL) {
+ __pmNoMem("__pmDecodeLogControl.req", need, PM_RECOV_ERR);
+ return -oserror();
+ }
+ req->numpmid = numpmid;
+
+ sts = PM_ERR_IPC;
+ for (i = 0; i < numpmid; i++) {
+ /* check that numval field is within the input buffer */
+ if (pduend - (char *)vp < sizeof(vlist_t) - sizeof(__pmValue_PDU))
+ goto corrupt;
+ nv = (int)ntohl(vp->v_numval);
+ if (nv > pp->c_hdr.len)
+ goto corrupt;
+ if (nv <= 0) {
+ need = sizeof(pmValueSet) - sizeof(pmValue);
+ /* check that pointer cannot move beyond input buffer end */
+ if (pduend - (char *)vp < sizeof(vlist_t) - sizeof(__pmValue_PDU))
+ goto corrupt;
+ } else {
+ /* check that dynamic allocation argument will not wrap */
+ if (nv >= (INT_MAX - sizeof(pmValueSet)) / sizeof(pmValue))
+ goto corrupt;
+ need = sizeof(pmValueSet) + ((nv - 1) * sizeof(pmValue));
+ /* check that pointer cannot move beyond input buffer end */
+ if (nv >= (INT_MAX - sizeof(vlist_t)) / sizeof(__pmValue_PDU))
+ goto corrupt;
+ if (pduend - (char *)vp < sizeof(vlist_t) + ((nv - 1) * sizeof(__pmValue_PDU)))
+ goto corrupt;
+ }
+ if ((vsp = (pmValueSet *)malloc(need)) == NULL) {
+ __pmNoMem("__pmDecodeLogControl.vsp", need, PM_RECOV_ERR);
+ sts = -oserror();
+ i--;
+ goto corrupt;
+ }
+ req->vset[i] = vsp;
+ vsp->pmid = __ntohpmID(vp->v_pmid);
+ vsp->valfmt = PM_VAL_INSITU;
+ vsp->numval = nv;
+ for (j = 0; j < nv; j++) {
+ vsp->vlist[j].inst = ntohl(vp->v_list[j].inst);
+ vsp->vlist[j].value.lval = ntohl(vp->v_list[j].value.lval);
+ }
+ if (nv > 0)
+ vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) + (nv - 1)*sizeof(__pmValue_PDU));
+ else
+ vp = (vlist_t *)((__psint_t)vp + sizeof(*vp) - sizeof(__pmValue_PDU));
+ }
+
+ *request = req;
+ return 0;
+
+corrupt:
+ while (i)
+ free(req->vset[i--]);
+ free(req);
+ return sts;
+}
diff --git a/src/libpcp/src/p_lrequest.c b/src/libpcp/src/p_lrequest.c
new file mode 100644
index 0000000..bbe9696
--- /dev/null
+++ b/src/libpcp/src/p_lrequest.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe note
+ *
+ * Unlike most of the other __pmSend*() routines, there is no wrapper
+ * routine in libpcp for __pmSendLogRequest() so there is no place in
+ * the library to enforce serialization between the sending of the
+ * PDU in __pmSendLogRequest() and reading the result PDU.
+ *
+ * It is assumed that the caller of __pmSendLogRequest() either manages
+ * this serialization or is single-threaded, which is true for
+ * the only current user of this routine, pmlc(1).
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+
+/*
+ * PDU for general pmlogger notification (PDU_LOG_REQUEST)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int type; /* notification type */
+} notify_t;
+
+int
+__pmSendLogRequest(int fd, int type)
+{
+ notify_t *pp;
+ int sts;
+
+ if ((pp = (notify_t *)__pmFindPDUBuf(sizeof(notify_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(notify_t);
+ pp->hdr.type = PDU_LOG_REQUEST;
+ pp->hdr.from = FROM_ANON; /* context does not matter here */
+ pp->type = htonl(type);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int version = __pmVersionIPC(fd);
+ fprintf(stderr, "_pmSendRequest: sending PDU (type=%d, version=%d)\n",
+ pp->type, version==UNKNOWN_VERSION? LOG_PDU_VERSION : version);
+ }
+#endif
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeLogRequest(const __pmPDU *pdubuf, int *type)
+{
+ const notify_t *pp;
+ const char *pduend;
+
+ pp = (const notify_t *)pdubuf;
+ pduend = (const char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp < sizeof(notify_t))
+ return PM_ERR_IPC;
+
+ *type = ntohl(pp->type);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int version = __pmLastVersionIPC();
+ fprintf(stderr, "__pmDecodeLogRequest: got PDU (type=%d, version=%d)\n",
+ *type, version==UNKNOWN_VERSION? LOG_PDU_VERSION : version);
+ }
+#endif
+ return 0;
+}
diff --git a/src/libpcp/src/p_lstatus.c b/src/libpcp/src/p_lstatus.c
new file mode 100644
index 0000000..5b61b10
--- /dev/null
+++ b/src/libpcp/src/p_lstatus.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe note
+ *
+ * Unlike most of the other __pmSend*() routines, there is no wrapper
+ * routine in libpcp for __pmSendLogStatus() so there is no place in
+ * the library to enforce serialization between the receiving the
+ * LOG_REQUEST_STATUS PDU and calling __pmSendLogStatus().
+ *
+ * It is assumed that the caller of __pmSendLogStatus() either manages
+ * this serialization or is single-threaded, which is true for
+ * the only current user of this routine, pmlogger(1).
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for logger status information transfer (PDU_LOG_STATUS)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int pad; /* force status to be double word aligned */
+ __pmLoggerStatus status;
+} logstatus_t;
+
+int
+__pmSendLogStatus(int fd, __pmLoggerStatus *status)
+{
+ logstatus_t *pp;
+ int sts;
+
+ if ((pp = (logstatus_t *)__pmFindPDUBuf(sizeof(logstatus_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(logstatus_t);
+ pp->hdr.type = PDU_LOG_STATUS;
+ pp->hdr.from = FROM_ANON; /* context does not matter here */
+ memcpy(&pp->status, status, sizeof(__pmLoggerStatus));
+
+ /* Conditional convertion from host to network byteorder HAVE to be
+ * unconditional if one cares about endianess compatibiltity at all!
+ */
+ pp->status.ls_state = htonl(pp->status.ls_state);
+ pp->status.ls_vol = htonl(pp->status.ls_vol);
+ __htonll((char *)&pp->status.ls_size);
+ pp->status.ls_start.tv_sec = htonl(pp->status.ls_start.tv_sec);
+ pp->status.ls_start.tv_usec = htonl(pp->status.ls_start.tv_usec);
+ pp->status.ls_last.tv_sec = htonl(pp->status.ls_last.tv_sec);
+ pp->status.ls_last.tv_usec = htonl(pp->status.ls_last.tv_usec);
+ pp->status.ls_timenow.tv_sec = htonl(pp->status.ls_timenow.tv_sec);
+ pp->status.ls_timenow.tv_usec = htonl(pp->status.ls_timenow.tv_usec);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int version = __pmVersionIPC(fd);
+ fprintf(stderr, "__pmSendLogStatus: sending PDU (toversion=%d)\n",
+ version == UNKNOWN_VERSION ? LOG_PDU_VERSION : version);
+ }
+#endif
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeLogStatus(__pmPDU *pdubuf, __pmLoggerStatus **status)
+{
+ logstatus_t *pp;
+ char *pduend;
+
+ pp = (logstatus_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if ((pduend - (char*)pp) != sizeof(logstatus_t))
+ return PM_ERR_IPC;
+
+ /* Conditional convertion from host to network byteorder HAVE to be
+ * unconditional if one cares about endianess compatibiltity at all!
+ */
+ pp->status.ls_state = ntohl(pp->status.ls_state);
+ pp->status.ls_vol = ntohl(pp->status.ls_vol);
+ __ntohll((char *)&pp->status.ls_size);
+ pp->status.ls_start.tv_sec = ntohl(pp->status.ls_start.tv_sec);
+ pp->status.ls_start.tv_usec = ntohl(pp->status.ls_start.tv_usec);
+ pp->status.ls_last.tv_sec = ntohl(pp->status.ls_last.tv_sec);
+ pp->status.ls_last.tv_usec = ntohl(pp->status.ls_last.tv_usec);
+ pp->status.ls_timenow.tv_sec = ntohl(pp->status.ls_timenow.tv_sec);
+ pp->status.ls_timenow.tv_usec = ntohl(pp->status.ls_timenow.tv_usec);
+
+ *status = &pp->status;
+ __pmPinPDUBuf(pdubuf);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int version = __pmLastVersionIPC();
+ fprintf(stderr, "__pmDecodeLogStatus: got PDU (fromversion=%d)\n",
+ version == UNKNOWN_VERSION ? LOG_PDU_VERSION : version);
+ }
+#endif
+ return 0;
+}
diff --git a/src/libpcp/src/p_pmns.c b/src/libpcp/src/p_pmns.c
new file mode 100644
index 0000000..8a90753
--- /dev/null
+++ b/src/libpcp/src/p_pmns.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for id list (PDU_PMNS_IDS)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int sts; /* to encode status of pmns op */
+ int numids;
+ pmID idlist[1];
+} idlist_t;
+
+#ifdef PCP_DEBUG
+void
+__pmDumpIDList(FILE *f, int numids, const pmID idlist[])
+{
+ int i;
+ char strbuf[20];
+
+ fprintf(f, "IDlist dump: numids = %d\n", numids);
+ for (i = 0; i < numids; i++)
+ fprintf(f, " PMID[%d]: 0x%08x %s\n", i, idlist[i], pmIDStr_r(idlist[i], strbuf, sizeof(strbuf)));
+}
+#endif
+
+/*
+ * Send a PDU_PMNS_IDS across socket.
+ */
+int
+__pmSendIDList(int fd, int from, int numids, const pmID idlist[], int sts)
+{
+ idlist_t *ip;
+ int need;
+ int j;
+ int lsts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "__pmSendIDList\n");
+ __pmDumpIDList(stderr, numids, idlist);
+ }
+#endif
+
+ need = (int)(sizeof(idlist_t) + (numids-1) * sizeof(idlist[0]));
+
+ if ((ip = (idlist_t *)__pmFindPDUBuf(need)) == NULL)
+ return -oserror();
+ ip->hdr.len = need;
+ ip->hdr.type = PDU_PMNS_IDS;
+ ip->hdr.from = from;
+ ip->sts = htonl(sts);
+ ip->numids = htonl(numids);
+ for (j = 0; j < numids; j++) {
+ ip->idlist[j] = __htonpmID(idlist[j]);
+ }
+
+ lsts = __pmXmitPDU(fd, (__pmPDU *)ip);
+ __pmUnpinPDUBuf(ip);
+ return lsts;
+}
+
+/*
+ * Decode a PDU_PMNS_IDS
+ * Assumes that we have preallocated idlist prior to this call
+ * (i.e. we know how many should definitely be coming over)
+ * Returns 0 on success.
+ */
+int
+__pmDecodeIDList(__pmPDU *pdubuf, int numids, pmID idlist[], int *sts)
+{
+ idlist_t *idlist_pdu;
+ char *pdu_end;
+ int nids;
+ int j;
+
+ idlist_pdu = (idlist_t *)pdubuf;
+ pdu_end = (char *)pdubuf + idlist_pdu->hdr.len;
+
+ if (pdu_end - (char *)pdubuf < sizeof(idlist_t) - sizeof(pmID))
+ return PM_ERR_IPC;
+ *sts = ntohl(idlist_pdu->sts);
+ nids = ntohl(idlist_pdu->numids);
+ if (nids <= 0 || nids != numids || nids > idlist_pdu->hdr.len)
+ return PM_ERR_IPC;
+ if (nids >= (INT_MAX - sizeof(idlist_t)) / sizeof(pmID))
+ return PM_ERR_IPC;
+ if (sizeof(idlist_t) + (sizeof(pmID) * (nids-1)) > (size_t)(pdu_end - (char *)pdubuf))
+ return PM_ERR_IPC;
+
+ for (j = 0; j < numids; j++)
+ idlist[j] = __ntohpmID(idlist_pdu->idlist[j]);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "__pmDecodeIDList\n");
+ __pmDumpIDList(stderr, numids, idlist);
+ }
+#endif
+
+ return 0;
+}
+
+/*********************************************************************/
+
+/*
+ * PDU for name list (PDU_PMNS_NAMES)
+ */
+
+typedef struct {
+ int namelen;
+ char name[sizeof(__pmPDU)]; /* variable length */
+} name_t;
+
+typedef struct {
+ int status;
+ int namelen;
+ char name[sizeof(__pmPDU)]; /* variable length */
+} name_status_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ int nstrbytes; /* number of str bytes including null terminators */
+ int numstatus; /* = 0 if there is no status to be encoded */
+ int numnames;
+ __pmPDU names[1]; /* list of variable length name_t or name_status_t */
+} namelist_t;
+
+/*
+ * NOTES:
+ *
+ * 1.
+ * name_t's are padded to a __pmPDU boundary (if needed)
+ * so that direct accesses of a following record
+ * can be made on a word boundary.
+ * i.e. the following "namelen" field will be accessible.
+ *
+ * 2.
+ * Names are sent length prefixed as opposed to null terminated.
+ * This can make copying at the decoding end simpler
+ * (and possibly more efficient using memcpy).
+ *
+ * 3.
+ * nstrbytes is used by the decoding function to know how many
+ * bytes to allocate.
+ *
+ * 4.
+ * name_status_t was added for pmGetChildrenStatus.
+ * It is a variant of the names pdu which encompasses status
+ * data as well.
+ */
+
+#ifdef PCP_DEBUG
+void
+__pmDumpNameList(FILE *f, int numnames, char *namelist[])
+{
+ int i;
+
+ fprintf(f, "namelist dump: numnames = %d\n", numnames);
+ for (i = 0; i < numnames; i++)
+ fprintf(f, " name[%d]: \"%s\"\n", i, namelist[i]);
+}
+
+void
+__pmDumpStatusList(FILE *f, int numstatus, const int statuslist[])
+{
+ int i;
+
+ fprintf(f, "statuslist dump: numstatus = %d\n", numstatus);
+ for (i = 0; i < numstatus; i++)
+ fprintf(f, " status[%d]: %d\n", i, statuslist[i]);
+}
+
+void
+__pmDumpNameAndStatusList(FILE *f, int numnames, char *namelist[], int statuslist[])
+{
+ int i;
+
+ fprintf(f, "namelist & statuslist dump: numnames = %d\n", numnames);
+ for (i = 0; i < numnames; i++)
+ fprintf(f, " name[%d]: \"%s\" (%s)\n", i, namelist[i],
+ statuslist[i] == PMNS_LEAF_STATUS ? "leaf" : "non-leaf");
+}
+#endif
+
+/*
+ * Send a PDU_PMNS_NAMES across socket.
+ */
+int
+__pmSendNameList(int fd, int from, int numnames, char *namelist[],
+ const int statuslist[])
+{
+ namelist_t *nlistp;
+ int need;
+ int nstrbytes=0;
+ int i;
+ name_t *nt;
+ name_status_t *nst;
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "__pmSendNameList\n");
+ __pmDumpNameList(stderr, numnames, namelist);
+ if (statuslist != NULL)
+ __pmDumpStatusList(stderr, numnames, statuslist);
+ }
+#endif
+
+ /* namelist_t + names rounded up to a __pmPDU boundary */
+ need = sizeof(*nlistp) - sizeof(nlistp->names);
+ for (i = 0; i < numnames; i++) {
+ int len = (int)strlen(namelist[i]);
+ nstrbytes += len+1;
+ need += PM_PDU_SIZE_BYTES(len);
+ if (statuslist == NULL)
+ need += sizeof(*nt) - sizeof(nt->name);
+ else
+ need += sizeof(*nst) - sizeof(nst->name);
+ }
+
+ if ((nlistp = (namelist_t *)__pmFindPDUBuf(need)) == NULL)
+ return -oserror();
+ nlistp->hdr.len = need;
+ nlistp->hdr.type = PDU_PMNS_NAMES;
+ nlistp->hdr.from = from;
+ nlistp->nstrbytes = htonl(nstrbytes);
+ nlistp->numnames = htonl(numnames);
+
+ if (statuslist == NULL) {
+ int i, j = 0, namelen;
+ nlistp->numstatus = htonl(0);
+ for(i=0; i<numnames; i++) {
+ nt = (name_t*)&nlistp->names[j/sizeof(__pmPDU)];
+ namelen = (int)strlen(namelist[i]);
+ memcpy(nt->name, namelist[i], namelen);
+#ifdef PCP_DEBUG
+ if ((namelen % sizeof(__pmPDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = nt->name + namelen;
+ for (pad = sizeof(__pmPDU) - 1; pad >= (namelen % sizeof(__pmPDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+ j += sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen);
+ nt->namelen = htonl(namelen);
+ }
+ }
+ else { /* include the status fields */
+ int i, j = 0, namelen;
+ nlistp->numstatus = htonl(numnames);
+ for(i=0; i<numnames; i++) {
+ nst = (name_status_t*)&nlistp->names[j/sizeof(__pmPDU)];
+ nst->status = htonl(statuslist[i]);
+ namelen = (int)strlen(namelist[i]);
+ memcpy(nst->name, namelist[i], namelen);
+#ifdef PCP_DEBUG
+ if ((namelen % sizeof(__pmPDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = nst->name + namelen;
+ for (pad = sizeof(__pmPDU) - 1; pad >= (namelen % sizeof(__pmPDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+ j += sizeof(nst->status) + sizeof(namelen) +
+ PM_PDU_SIZE_BYTES(namelen);
+ nst->namelen = htonl(namelen);
+ }
+ }
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)nlistp);
+ __pmUnpinPDUBuf(nlistp);
+ return sts;
+}
+
+/*
+ * Decode a PDU_PMNS_NAMES
+ *
+ * statuslist is optional ... if NULL, no status values will be returned
+ */
+int
+__pmDecodeNameList(__pmPDU *pdubuf, int *numnamesp,
+ char*** namelist, int** statuslist)
+{
+ namelist_t *namelist_pdu;
+ char *pdu_end;
+ char **names;
+ char *dest, *dest_end;
+ int *status = NULL;
+ int namesize, numnames;
+ int statussize, numstatus;
+ int nstrbytes;
+ int namelen;
+ int i, j;
+
+ namelist_pdu = (namelist_t *)pdubuf;
+ pdu_end = (char *)pdubuf + namelist_pdu->hdr.len;
+
+ *namelist = NULL;
+ if (statuslist != NULL)
+ *statuslist = NULL;
+
+ if (pdu_end - (char*)namelist_pdu < sizeof(namelist_t) - sizeof(__pmPDU))
+ return PM_ERR_IPC;
+
+ numnames = ntohl(namelist_pdu->numnames);
+ numstatus = ntohl(namelist_pdu->numstatus);
+ nstrbytes = ntohl(namelist_pdu->nstrbytes);
+
+ if (numnames == 0) {
+ *numnamesp = 0;
+ return 0;
+ }
+
+ /* validity checks - none of these conditions should happen */
+ if (numnames < 0 || nstrbytes < 0)
+ return PM_ERR_IPC;
+ /* anti-DOS measure - limiting allowable memory allocations */
+ if (numnames > namelist_pdu->hdr.len || nstrbytes > namelist_pdu->hdr.len)
+ return PM_ERR_IPC;
+ /* numstatus must be one (and only one) of zero or numnames */
+ if (numstatus != 0 && numstatus != numnames)
+ return PM_ERR_IPC;
+
+ /* need space for name ptrs and the name characters */
+ if (numnames >= (INT_MAX - nstrbytes) / (int)sizeof(char *))
+ return PM_ERR_IPC;
+ namesize = numnames * ((int)sizeof(char*)) + nstrbytes;
+ if ((names = (char**)malloc(namesize)) == NULL)
+ return -oserror();
+
+ /* need space for status values */
+ if (statuslist != NULL && numstatus > 0) {
+ if (numstatus >= INT_MAX / (int)sizeof(int))
+ goto corrupt;
+ statussize = numstatus * (int)sizeof(int);
+ if ((status = (int*)malloc(statussize)) == NULL) {
+ free(names);
+ return -oserror();
+ }
+ }
+
+ dest = (char*)&names[numnames];
+ dest_end = (char*)names + namesize;
+
+ /* copy over ptrs and characters */
+ if (numstatus == 0) {
+ name_t *np;
+
+ for (i = j = 0; i < numnames; i++) {
+ np = (name_t*)&namelist_pdu->names[j/sizeof(__pmPDU)];
+ names[i] = dest;
+
+ if (sizeof(name_t) > (size_t)(pdu_end - (char *)np))
+ goto corrupt;
+ namelen = ntohl(np->namelen);
+ /* ensure source buffer contains everything that we copy over */
+ if (sizeof(np->namelen) + namelen > (size_t)(pdu_end - (char *)np))
+ goto corrupt;
+ /* ensure space in destination; note null-terminator is added */
+ if (namelen < 0 || (namelen + 1) > (dest_end - dest))
+ goto corrupt;
+
+ memcpy(dest, np->name, namelen);
+ *(dest + namelen) = '\0';
+ dest += namelen + 1;
+
+ j += sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen);
+ }
+ }
+ else { /* status fields included in the PDU */
+ name_status_t *np;
+
+ for (i = j = 0; i < numnames; i++) {
+ np = (name_status_t*)&namelist_pdu->names[j/sizeof(__pmPDU)];
+ names[i] = dest;
+
+ if (sizeof(name_status_t) > (size_t)(pdu_end - (char *)np))
+ goto corrupt;
+ namelen = ntohl(np->namelen);
+ /* ensure source buffer contains everything that we copy over */
+ if (sizeof(np->namelen) + sizeof(np->status) + namelen > (size_t)(pdu_end - (char *)np))
+ goto corrupt;
+ /* ensure space for null-terminated name in destination buffer */
+ if (namelen < 0 || (namelen + 1) > (dest_end - dest))
+ goto corrupt;
+
+ if (status != NULL)
+ status[i] = ntohl(np->status);
+
+ memcpy(dest, np->name, namelen);
+ *(dest + namelen) = '\0';
+ dest += namelen + 1;
+
+ j += sizeof(np->status) + sizeof(namelen) + PM_PDU_SIZE_BYTES(namelen);
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "__pmDecodeNameList\n");
+ __pmDumpNameList(stderr, numnames, names);
+ if (status != NULL)
+ __pmDumpStatusList(stderr, numstatus, status);
+ }
+#endif
+
+ *namelist = names;
+ if (statuslist != NULL)
+ *statuslist = status;
+ *numnamesp = numnames;
+ return numnames;
+
+corrupt:
+ if (status != NULL)
+ free(status);
+ free(names);
+ return PM_ERR_IPC;
+}
+
+
+/*********************************************************************/
+
+/*
+ * name request
+ */
+
+typedef struct {
+ __pmPDUHdr hdr;
+ int subtype;
+ int namelen;
+ char name[sizeof(int)];
+} namereq_t;
+
+/*
+ * Send a PDU_PMNS_CHILD_REQ across socket.
+ */
+static int
+SendNameReq(int fd, int from, const char *name, int pdu_type, int subtype)
+{
+ namereq_t *nreq;
+ int need;
+ int namelen;
+ int alloc_len; /* length allocated for name */
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ char strbuf[20];
+ fprintf(stderr, "SendNameReq: from=%d name=\"%s\" pdu=%s subtype=%d\n",
+ from, name, __pmPDUTypeStr_r(pdu_type, strbuf, sizeof(strbuf)), subtype);
+ }
+#endif
+
+ namelen = (int)strlen(name);
+ alloc_len = (int)(sizeof(int)*((namelen-1 + sizeof(int))/sizeof(int)));
+ need = (int)(sizeof(*nreq) - sizeof(nreq->name) + alloc_len);
+
+ if ((nreq = (namereq_t *)__pmFindPDUBuf(need)) == NULL)
+ return -oserror();
+ nreq->hdr.len = need;
+ nreq->hdr.type = pdu_type;
+ nreq->hdr.from = from;
+ nreq->subtype = htonl(subtype);
+ nreq->namelen = htonl(namelen);
+ memcpy(&nreq->name[0], name, namelen);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)nreq);
+ __pmUnpinPDUBuf(nreq);
+ return sts;
+}
+
+/*
+ * Decode a name request
+ */
+static int
+DecodeNameReq(__pmPDU *pdubuf, char **name_p, int *subtype)
+{
+ namereq_t *namereq_pdu;
+ char *pdu_end;
+ char *name;
+ int namelen;
+
+ namereq_pdu = (namereq_t *)pdubuf;
+ pdu_end = (char *)pdubuf + namereq_pdu->hdr.len;
+
+ if (pdu_end - (char *)namereq_pdu < sizeof(namereq_t) - sizeof(int))
+ return PM_ERR_IPC;
+
+ /* only set it if you want it */
+ if (subtype != NULL)
+ *subtype = ntohl(namereq_pdu->subtype);
+ namelen = ntohl(namereq_pdu->namelen);
+
+ if (namelen < 0 || namelen > namereq_pdu->hdr.len)
+ return PM_ERR_IPC;
+ if (sizeof(namereq_t) - sizeof(int) + namelen > (size_t)(pdu_end - (char *)namereq_pdu))
+ return PM_ERR_IPC;
+
+ name = malloc(namelen+1);
+ if (name == NULL)
+ return -oserror();
+ memcpy(name, namereq_pdu->name, namelen);
+ name[namelen] = '\0';
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS)
+ fprintf(stderr, "DecodeNameReq: name=\"%s\"\n", name);
+#endif
+
+ *name_p = name;
+ return 0;
+}
+
+/*********************************************************************/
+
+/*
+ * Send a PDU_PMNS_CHILD
+ */
+int
+__pmSendChildReq(int fd, int from, const char *name, int subtype)
+{
+ return SendNameReq(fd, from, name, PDU_PMNS_CHILD, subtype);
+}
+
+
+/*
+ * Decode a PDU_PMNS_CHILD
+ */
+int
+__pmDecodeChildReq(__pmPDU *pdubuf, char **name_p, int *subtype)
+{
+ return DecodeNameReq(pdubuf, name_p, subtype);
+}
+
+/*********************************************************************/
+
+/*
+ * Send a PDU_PMNS_TRAVERSE
+ */
+int
+__pmSendTraversePMNSReq(int fd, int from, const char *name)
+{
+ return SendNameReq(fd, from, name, PDU_PMNS_TRAVERSE, 0);
+}
+
+
+/*
+ * Decode a PDU_PMNS_TRAVERSE
+ */
+int
+__pmDecodeTraversePMNSReq(__pmPDU *pdubuf, char **name_p)
+{
+ return DecodeNameReq(pdubuf, name_p, 0);
+}
+
+/*********************************************************************/
diff --git a/src/libpcp/src/p_profile.c b/src/libpcp/src/p_profile.c
new file mode 100644
index 0000000..63acbfa
--- /dev/null
+++ b/src/libpcp/src/p_profile.c
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+#define LIMIT_CTXNUM 2048
+
+/*
+ * PDU used to transmit __pmProfile prior to pmFetch (PDU_PROFILE)
+ */
+typedef struct {
+ pmInDom indom;
+ int state; /* include/exclude */
+ int numinst; /* no. of instances to follow */
+ int pad; /* protocol backward compatibility */
+} instprof_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ int ctxnum;
+ int g_state; /* global include/exclude */
+ int numprof; /* no. of elts to follow */
+ int pad; /* protocol backward compatibility */
+} profile_t;
+
+int
+__pmSendProfile(int fd, int from, int ctxnum, __pmProfile *instprof)
+{
+ __pmInDomProfile *prof, *p_end;
+ profile_t *pduProfile;
+ instprof_t *pduInstProf;
+ __pmPDU *p;
+ size_t need;
+ __pmPDU *pdubuf;
+ int sts;
+
+ /* work out how much space we need and then alloc a pdu buf */
+ need = sizeof(profile_t) + instprof->profile_len * sizeof(instprof_t);
+ for (prof = instprof->profile, p_end = prof + instprof->profile_len;
+ prof < p_end;
+ prof++)
+ need += prof->instances_len * sizeof(int);
+
+ if ((pdubuf = __pmFindPDUBuf((int)need)) == NULL)
+ return -oserror();
+
+ p = (__pmPDU *)pdubuf;
+
+ /* First the profile itself */
+ pduProfile = (profile_t *)p;
+ pduProfile->hdr.len = (int)need;
+ pduProfile->hdr.type = PDU_PROFILE;
+ /*
+ * note: context id may be sent twice due to protocol evolution and
+ * backwards compatibility issues
+ */
+ pduProfile->hdr.from = from;
+ pduProfile->ctxnum = htonl(ctxnum);
+ pduProfile->g_state = htonl(instprof->state);
+ pduProfile->numprof = htonl(instprof->profile_len);
+ pduProfile->pad = 0;
+
+ p += sizeof(profile_t) / sizeof(__pmPDU);
+
+ if (instprof->profile_len) {
+ /* Next all the profile entries (if any) in one block */
+ for (prof = instprof->profile, p_end = prof + instprof->profile_len;
+ prof < p_end;
+ prof++) {
+ pduInstProf = (instprof_t *)p;
+ pduInstProf->indom = __htonpmInDom(prof->indom);
+ pduInstProf->state = htonl(prof->state);
+ pduInstProf->numinst = htonl(prof->instances_len);
+ pduInstProf->pad = 0;
+ p += sizeof(instprof_t) / sizeof(__pmPDU);
+ }
+
+ /* and then all the instances */
+ for (prof = instprof->profile, p_end = prof+instprof->profile_len;
+ prof < p_end;
+ prof++) {
+ int j;
+
+ /* and then the instances themselves (if any) */
+ for (j = 0; j < prof->instances_len; j++, p++)
+ *p = htonl(prof->instances[j]);
+ }
+ }
+ sts = __pmXmitPDU(fd, pdubuf);
+ __pmUnpinPDUBuf(pdubuf);
+ return sts;
+}
+
+int
+__pmDecodeProfile(__pmPDU *pdubuf, int *ctxnump, __pmProfile **resultp)
+{
+ __pmProfile *instprof;
+ __pmInDomProfile *prof, *p_end;
+ profile_t *pduProfile;
+ instprof_t *pduInstProf;
+ __pmPDU *p = (__pmPDU *)pdubuf;
+ char *pdu_end;
+ int ctxnum;
+ int sts = 0;
+
+ /* First the profile */
+ pduProfile = (profile_t *)pdubuf;
+ pdu_end = (char*)pdubuf + pduProfile->hdr.len;
+ if (pdu_end - (char*)pdubuf < sizeof(profile_t))
+ return PM_ERR_IPC;
+
+ ctxnum = ntohl(pduProfile->ctxnum);
+ if (ctxnum < 0 || ctxnum > LIMIT_CTXNUM)
+ return PM_ERR_IPC;
+ if ((instprof = (__pmProfile *)malloc(sizeof(__pmProfile))) == NULL)
+ return -oserror();
+ instprof->state = ntohl(pduProfile->g_state);
+ instprof->profile = NULL;
+ instprof->profile_len = ntohl(pduProfile->numprof);
+ if (instprof->profile_len < 0) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ }
+
+ p += sizeof(profile_t) / sizeof(__pmPDU);
+
+ if (instprof->profile_len > 0) {
+ if (instprof->profile_len >= INT_MAX / sizeof(__pmInDomProfile) ||
+ instprof->profile_len >= pduProfile->hdr.len) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ }
+ if ((instprof->profile = (__pmInDomProfile *)calloc(
+ instprof->profile_len, sizeof(__pmInDomProfile))) == NULL) {
+ sts = -oserror();
+ goto fail;
+ }
+
+ /* Next the profiles (if any) all together */
+ for (prof = instprof->profile, p_end = prof + instprof->profile_len;
+ prof < p_end;
+ prof++) {
+ if ((char *)p >= pdu_end) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ }
+ pduInstProf = (instprof_t *)p;
+ prof->indom = __ntohpmInDom(pduInstProf->indom);
+ prof->state = ntohl(pduInstProf->state);
+ prof->instances = NULL;
+ prof->instances_len = ntohl(pduInstProf->numinst);
+ p += sizeof(instprof_t) / sizeof(__pmPDU);
+ }
+
+ /* Finally, all the instances for all profiles (if any) together */
+ for (prof = instprof->profile, p_end = prof+instprof->profile_len;
+ prof < p_end;
+ prof++) {
+ int j;
+
+ if (prof->instances_len > 0) {
+ if (prof->instances_len >= INT_MAX / sizeof(int) ||
+ prof->instances_len >= pduProfile->hdr.len) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ }
+ prof->instances = (int *)calloc(prof->instances_len, sizeof(int));
+ if (prof->instances == NULL) {
+ sts = -oserror();
+ goto fail;
+ }
+ for (j = 0; j < prof->instances_len; j++, p++) {
+ if ((char *)p >= pdu_end) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ }
+ prof->instances[j] = ntohl(*p);
+ }
+ } else if (prof->instances_len < 0) {
+ sts = PM_ERR_IPC;
+ goto fail;
+ } else {
+ prof->instances = NULL;
+ }
+ }
+ }
+ else {
+ instprof->profile = NULL;
+ }
+
+ *resultp = instprof;
+ *ctxnump = ctxnum;
+ return 0;
+
+fail:
+ if (instprof != NULL) {
+ if (instprof->profile != NULL) {
+ for (prof = instprof->profile, p_end = prof+instprof->profile_len;
+ prof < p_end;
+ prof++) {
+ if (prof->instances != NULL)
+ free(prof->instances);
+ }
+ free(instprof->profile);
+ }
+ free(instprof);
+ }
+ return sts;
+}
diff --git a/src/libpcp/src/p_result.c b/src/libpcp/src/p_result.c
new file mode 100644
index 0000000..50d4850
--- /dev/null
+++ b/src/libpcp/src/p_result.c
@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe note
+ *
+ * Because __pmFindPDUBuf() returns with a pinned pdu buffer, the
+ * buffer passed back from __pmEncodeResult() must also remain pinned
+ * (otherwise another thread could clobber the buffer after returning
+ * from __pmEncodeResult()) ... it is the caller of __pmEncodeResult()
+ * who is responsible for (a) not pinning the buffer again, and (b)
+ * ensuring _someone_ will unpin the buffer when it is safe to do so.
+ *
+ * Similarly, __pmDecodeResult() accepts a pinned buffer and returns
+ * a pmResult that (on 64-bit pointer platforms) may contain pointers
+ * into a second underlying pinned buffer. The input buffer remains
+ * pinned, the second buffer will be pinned if it is used. The caller
+ * will typically call pmFreeResult(), but also needs to call
+ * __pmUnpinPDUBuf() for the input PDU buffer. When the result contains
+ * pointers back into the input PDU buffer, this will be pinned _twice_
+ * so the pmFreeResult() and __pmUnpinPDUBuf() calls will still be
+ * required.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for pmResult (PDU_RESULT)
+ */
+
+typedef struct {
+ pmID pmid;
+ int numval; /* no. of vlist els to follow, or error */
+ int valfmt; /* insitu or pointer */
+ __pmValue_PDU vlist[1]; /* zero or more */
+} vlist_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* no. of PMIDs to follow */
+ __pmPDU data[1]; /* zero or more */
+} result_t;
+
+int
+__pmEncodeResult(int targetfd, const pmResult *result, __pmPDU **pdubuf)
+{
+ int i;
+ int j;
+ size_t need; /* bytes for the PDU */
+ size_t vneed; /* additional bytes for the pmValueBlocks on the end */
+ __pmPDU *_pdubuf;
+ __pmPDU *vbp;
+ result_t *pp;
+ vlist_t *vlp;
+
+ need = sizeof(result_t) - sizeof(__pmPDU);
+ vneed = 0;
+ /* now add space for each vlist_t (data in result_t) */
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+ /* need space for PMID and count of values (defer valfmt until
+ * we know numval > 0, which means there should be a valfmt)
+ */
+ need += sizeof(pmID) + sizeof(int);
+ for (j = 0; j < vsp->numval; j++) {
+ /* plus value, instance pair */
+ need += sizeof(__pmValue_PDU);
+ if (vsp->valfmt != PM_VAL_INSITU) {
+ /* plus pmValueBlock */
+ vneed += PM_PDU_SIZE_BYTES(vsp->vlist[j].value.pval->vlen);
+ }
+ }
+ if (j)
+ /* optional value format, if any values present */
+ need += sizeof(int);
+ }
+ /*
+ * Need to reserve additonal space for trailer (an int) in case the
+ * PDU buffer is used by __pmLogPutResult2()
+ */
+ if ((_pdubuf = __pmFindPDUBuf((int)(need+vneed+sizeof(int)))) == NULL)
+ return -oserror();
+ pp = (result_t *)_pdubuf;
+ pp->hdr.len = (int)(need+vneed);
+ pp->hdr.type = PDU_RESULT;
+ pp->timestamp.tv_sec = htonl((__int32_t)(result->timestamp.tv_sec));
+ pp->timestamp.tv_usec = htonl((__int32_t)(result->timestamp.tv_usec));
+ pp->numpmid = htonl(result->numpmid);
+ vlp = (vlist_t *)pp->data;
+ /*
+ * Note: vbp, and hence offset in sent PDU is in units of __pmPDU
+ */
+ vbp = _pdubuf + need/sizeof(__pmPDU);
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+ vlp->pmid = __htonpmID(vsp->pmid);
+ if (vsp->numval > 0)
+ vlp->valfmt = htonl(vsp->valfmt);
+ for (j = 0; j < vsp->numval; j++) {
+ vlp->vlist[j].inst = htonl(vsp->vlist[j].inst);
+ if (vsp->valfmt == PM_VAL_INSITU)
+ vlp->vlist[j].value.lval = htonl(vsp->vlist[j].value.lval);
+ else {
+ /*
+ * pmValueBlocks are harder!
+ * -- need to copy the len field (len) + len bytes (vbuf)
+ */
+ int nb;
+ nb = vsp->vlist[j].value.pval->vlen;
+ memcpy((void *)vbp, (void *)vsp->vlist[j].value.pval, nb);
+#ifdef PCP_DEBUG
+ if ((nb % sizeof(__pmPDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = (char *)vbp + nb;
+ for (pad = sizeof(__pmPDU) - 1; pad >= (nb % sizeof(__pmPDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+ __htonpmValueBlock((pmValueBlock *)vbp);
+ /* point to the value block at the end of the PDU */
+ vlp->vlist[j].value.lval = htonl((int)(vbp - _pdubuf));
+ vbp += PM_PDU_SIZE(nb);
+ }
+ }
+ vlp->numval = htonl(vsp->numval);
+ if (j > 0)
+ vlp = (vlist_t *)((__psint_t)vlp + sizeof(*vlp) + (j-1)*sizeof(vlp->vlist[0]));
+ else
+ vlp = (vlist_t *)((__psint_t)vlp + sizeof(vlp->pmid) + sizeof(vlp->numval));
+ }
+ *pdubuf = _pdubuf;
+
+ /* Note _pdubuf remains pinned ... see thread-safe comments above */
+ return 0;
+}
+
+int
+__pmSendResult(int fd, int from, const pmResult *result)
+{
+ int sts;
+ __pmPDU *pdubuf;
+ result_t *pp;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ __pmDumpResult(stderr, result);
+#endif
+ if ((sts = __pmEncodeResult(fd, result, &pdubuf)) < 0)
+ return sts;
+ pp = (result_t *)pdubuf;
+ pp->hdr.from = from;
+ sts = __pmXmitPDU(fd, pdubuf);
+ __pmUnpinPDUBuf(pdubuf);
+ return sts;
+}
+
+/*
+ * enter here with pdubuf already pinned ... result may point into
+ * _another_ pdu buffer that is pinned on exit
+ */
+int
+__pmDecodeResult(__pmPDU *pdubuf, pmResult **result)
+{
+ int numpmid; /* number of metrics */
+ int i; /* range of metrics */
+ int j; /* range over values */
+ int index;
+ int vsize; /* size of vlist_t's in PDU buffer */
+ char *pduend; /* end pointer for incoming buffer */
+ char *vsplit; /* vlist/valueblock division point */
+ result_t *pp;
+ vlist_t *vlp;
+ pmResult *pr;
+#if defined(HAVE_64BIT_PTR)
+ char *newbuf;
+ int valfmt;
+ int numval;
+ int need;
+/*
+ * Note: all sizes are in units of bytes ... beware that pp->data is in
+ * units of __pmPDU
+ */
+ int nvsize; /* size of pmValue's after decode */
+ int offset; /* differences in sizes */
+ int vbsize; /* size of pmValueBlocks */
+ pmValueSet *nvsp;
+#elif defined(HAVE_32BIT_PTR)
+ pmValueSet *vsp; /* vlist_t == pmValueSet */
+#else
+ Bozo - unexpected sizeof pointer!!
+#endif
+
+ pp = (result_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+ if (pduend - (char *)pdubuf < sizeof(result_t) - sizeof(__pmPDU)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: len=%d smaller than min %d\n", pp->hdr.len, (int)(sizeof(result_t) - sizeof(__pmPDU)));
+ }
+#endif
+ return PM_ERR_IPC;
+ }
+
+ numpmid = ntohl(pp->numpmid);
+ if (numpmid < 0 || numpmid > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: numpmid=%d negative or not smaller than PDU len %d\n", numpmid, pp->hdr.len);
+ }
+#endif
+ return PM_ERR_IPC;
+ }
+ if (numpmid >= (INT_MAX - sizeof(pmResult)) / sizeof(pmValueSet *)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: numpmid=%d larger than max %ld\n", numpmid, (long)(INT_MAX - sizeof(pmResult) / sizeof(pmValueSet *)));
+ }
+#endif
+ return PM_ERR_IPC;
+ }
+ if ((pr = (pmResult *)malloc(sizeof(pmResult) +
+ (numpmid - 1) * sizeof(pmValueSet *))) == NULL) {
+ return -oserror();
+ }
+ pr->numpmid = numpmid;
+ pr->timestamp.tv_sec = ntohl(pp->timestamp.tv_sec);
+ pr->timestamp.tv_usec = ntohl(pp->timestamp.tv_usec);
+
+#if defined(HAVE_64BIT_PTR)
+ vsplit = pduend; /* smallest observed value block pointer */
+ nvsize = vsize = vbsize = 0;
+ for (i = 0; i < numpmid; i++) {
+ vlp = (vlist_t *)&pp->data[vsize/sizeof(__pmPDU)];
+
+ if (sizeof(*vlp) - sizeof(vlp->vlist) - sizeof(int) > (pduend - (char *)vlp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] outer vlp past end of PDU buffer\n", i);
+ }
+#endif
+ goto corrupt;
+ }
+
+ vsize += sizeof(vlp->pmid) + sizeof(vlp->numval);
+ nvsize += sizeof(pmValueSet);
+ numval = ntohl(vlp->numval);
+ valfmt = ntohl(vlp->valfmt);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ pmID pmid = __ntohpmID(vlp->pmid);
+ char strbuf[20];
+ fprintf(stderr, "vlist[%d] pmid: %s numval: %d",
+ i, pmIDStr_r(pmid, strbuf, sizeof(strbuf)), numval);
+ }
+#endif
+ /* numval may be negative - it holds an error code in that case */
+ if (numval > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > len=%d\n", i, numval, pp->hdr.len);
+ }
+#endif
+ goto corrupt;
+ }
+ if (numval > 0) {
+ if (sizeof(*vlp) - sizeof(vlp->vlist) > (pduend - (char *)vlp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] inner vlp past end of PDU buffer\n", i);
+ }
+#endif
+ goto corrupt;
+ }
+ if (numval >= (INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > max=%ld\n", i, numval, (long)((INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)));
+ }
+#endif
+ goto corrupt;
+ }
+ vsize += sizeof(vlp->valfmt) + numval * sizeof(__pmValue_PDU);
+ nvsize += (numval - 1) * sizeof(pmValue);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, " valfmt: %s",
+ valfmt == PM_VAL_INSITU ? "insitu" : "ptr");
+ }
+#endif
+ if (valfmt != PM_VAL_INSITU) {
+ for (j = 0; j < numval; j++) {
+ __pmValue_PDU *pduvp;
+ pmValueBlock *pduvbp;
+
+ pduvp = &vlp->vlist[j];
+ if (sizeof(__pmValue_PDU) > (size_t)(pduend - (char *)pduvp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] intial pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+ index = ntohl(pduvp->value.lval);
+ if (index < 0 || index > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] index=%d\n", i, j, index);
+ }
+#endif
+ goto corrupt;
+ }
+ pduvbp = (pmValueBlock *)&pdubuf[index];
+ if (vsplit > (char *)pduvbp)
+ vsplit = (char *)pduvbp;
+ if (sizeof(unsigned int) > (size_t)(pduend - (char *)pduvbp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] second pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+ __ntohpmValueBlock(pduvbp);
+ if (pduvbp->vlen < PM_VAL_HDR_SIZE || pduvbp->vlen > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] vlen=%d\n", i, j, pduvbp->vlen);
+ }
+#endif
+ goto corrupt;
+ }
+ if (pduvbp->vlen > (size_t)(pduend - (char *)pduvbp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] third pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+ vbsize += PM_PDU_SIZE_BYTES(pduvbp->vlen);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, " len: %d type: %d",
+ pduvbp->vlen - PM_VAL_HDR_SIZE, pduvbp->vtype);
+ }
+#endif
+ }
+ }
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fputc('\n', stderr);
+ }
+#endif
+ }
+
+ need = nvsize + vbsize;
+ offset = sizeof(result_t) - sizeof(__pmPDU) + vsize;
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "need: %d vsize: %d nvsize: %d vbsize: %d offset: %d hdr.len: %d pduend: %p vsplit: %p (diff %d) pdubuf: %p (diff %d)\n", need, vsize, nvsize, vbsize, offset, pp->hdr.len, pduend, vsplit, (int)(pduend-vsplit), pdubuf, (int)(pduend-(char *)pdubuf));
+ }
+#endif
+
+ if (need < 0 ||
+ vsize > INT_MAX / sizeof(__pmPDU) ||
+ vbsize > INT_MAX / sizeof(pmValueBlock) ||
+ offset != pp->hdr.len - (pduend - vsplit) ||
+ offset + vbsize != pduend - (char *)pdubuf) {
+ goto corrupt;
+ }
+
+ /* the original pdubuf is already pinned so we won't allocate that again */
+ if ((newbuf = (char *)__pmFindPDUBuf(need)) == NULL) {
+ free(pr);
+ return -oserror();
+ }
+
+ /*
+ * At this point, we have verified the contents of the incoming PDU and
+ * the following is set up ...
+ *
+ * From the original PDU buffer ...
+ * :-----:---------:-----------:----------------:---------------------:
+ * : Hdr :timestamp: numpmid : ... vlists ... : .. pmValueBlocks .. :
+ * :-----:---------:-----------:----------------:---------------------:
+ * <--- vsize ---> <---- vbsize ---->
+ * bytes bytes
+ *
+ * and in the new PDU buffer we are going to build ...
+ * :---------------------:---------------------:
+ * : ... pmValueSets ... : .. pmValueBlocks .. :
+ * :---------------------:---------------------:
+ * <--- nvsize ---> <---- vbsize ---->
+ * bytes bytes
+ */
+
+ if (vbsize) {
+ /* pmValueBlocks (if any) are copied across "as is" */
+ index = vsize / sizeof(__pmPDU);
+ memcpy((void *)&newbuf[nvsize], (void *)&pp->data[index], vbsize);
+ }
+
+ /*
+ * offset is a bit tricky ... _add_ the expansion due to the
+ * different sizes of the vlist_t and pmValueSet, and _subtract_
+ * the PDU header and pmResult fields ...
+ */
+ offset = nvsize - vsize
+ - (int)sizeof(pp->hdr) - (int)sizeof(pp->timestamp)
+ - (int)sizeof(pp->numpmid);
+ nvsize = vsize = 0;
+ for (i = 0; i < numpmid; i++) {
+ vlp = (vlist_t *)&pp->data[vsize/sizeof(__pmPDU)];
+ nvsp = (pmValueSet *)&newbuf[nvsize];
+ pr->vset[i] = nvsp;
+ nvsp->pmid = __ntohpmID(vlp->pmid);
+ nvsp->numval = ntohl(vlp->numval);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ char strbuf[20];
+ fprintf(stderr, "new vlist[%d] pmid: %s numval: %d",
+ i, pmIDStr_r(nvsp->pmid, strbuf, sizeof(strbuf)), nvsp->numval);
+ }
+#endif
+
+ vsize += sizeof(nvsp->pmid) + sizeof(nvsp->numval);
+ nvsize += sizeof(pmValueSet);
+ if (nvsp->numval > 0) {
+ nvsp->valfmt = ntohl(vlp->valfmt);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, " valfmt: %s",
+ nvsp->valfmt == PM_VAL_INSITU ? "insitu" : "ptr");
+ }
+#endif
+ vsize += sizeof(nvsp->valfmt) + nvsp->numval * sizeof(__pmValue_PDU);
+ nvsize += (nvsp->numval - 1) * sizeof(pmValue);
+ for (j = 0; j < nvsp->numval; j++) {
+ __pmValue_PDU *vp = &vlp->vlist[j];
+ pmValue *nvp = &nvsp->vlist[j];
+
+ nvp->inst = ntohl(vp->inst);
+ if (nvsp->valfmt == PM_VAL_INSITU) {
+ nvp->value.lval = ntohl(vp->value.lval);
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, " value: %d", nvp->value.lval);
+ }
+#endif
+ }
+ else {
+ /*
+ * in the input PDU buffer, pval is an index to the
+ * start of the pmValueBlock, in units of __pmPDU
+ */
+ index = sizeof(__pmPDU) * ntohl(vp->value.pval) + offset;
+ nvp->value.pval = (pmValueBlock *)&newbuf[index];
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ int k, len;
+ len = nvp->value.pval->vlen - PM_VAL_HDR_SIZE;
+ fprintf(stderr, " len: %d type: %d value: 0x", len,
+ nvp->value.pval->vtype);
+ for (k = 0; k < len; k++)
+ fprintf(stderr, "%02x", nvp->value.pval->vbuf[k]);
+ }
+#endif
+ }
+ }
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ if (numpmid == 0)
+ __pmUnpinPDUBuf(newbuf);
+
+#elif defined(HAVE_32BIT_PTR)
+
+ pr->timestamp.tv_sec = ntohl(pp->timestamp.tv_sec);
+ pr->timestamp.tv_usec = ntohl(pp->timestamp.tv_usec);
+ vlp = (vlist_t *)pp->data;
+ vsplit = pduend;
+ vsize = 0;
+
+ /*
+ * Now fix up any pointers in pmValueBlocks (currently offsets into
+ * the PDU buffer) by adding the base address of the PDU buffer.
+ */
+ for (i = 0; i < numpmid; i++) {
+ if (sizeof(*vlp) - sizeof(vlp->vlist) - sizeof(int) > (pduend - (char *)vlp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] outer vlp past end of PDU buffer\n", i);
+ }
+#endif
+ goto corrupt;
+ }
+
+ vsp = pr->vset[i] = (pmValueSet *)vlp;
+ vsp->pmid = __ntohpmID(vsp->pmid);
+ vsp->numval = ntohl(vsp->numval);
+ /* numval may be negative - it holds an error code in that case */
+ if (vsp->numval > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > len=%d\n", i, vsp->numval, pp->hdr.len);
+ }
+#endif
+ goto corrupt;
+ }
+
+ vsize += sizeof(vsp->pmid) + sizeof(vsp->numval);
+ if (vsp->numval > 0) {
+ if (sizeof(*vlp) - sizeof(vlp->vlist) > (pduend - (char *)vlp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] inner vlp past end of PDU buffer\n", i);
+ }
+#endif
+ goto corrupt;
+ }
+ if (vsp->numval >= (INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] numval=%d > max=%ld\n", i, vsp->numval, (long)((INT_MAX - sizeof(*vlp)) / sizeof(__pmValue_PDU)));
+ }
+#endif
+ goto corrupt;
+ }
+ vsp->valfmt = ntohl(vsp->valfmt);
+ vsize += sizeof(vsp->valfmt) + vsp->numval * sizeof(__pmValue_PDU);
+ for (j = 0; j < vsp->numval; j++) {
+ __pmValue_PDU *pduvp;
+ pmValueBlock *pduvbp;
+
+ pduvp = &vsp->vlist[j];
+ if (sizeof(__pmValue_PDU) > (size_t)(pduend - (char *)pduvp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] initial pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+
+ pduvp->inst = ntohl(pduvp->inst);
+ if (vsp->valfmt == PM_VAL_INSITU) {
+ pduvp->value.lval = ntohl(pduvp->value.lval);
+ } else {
+ /* salvage pmValueBlocks from end of PDU */
+ index = ntohl(pduvp->value.lval);
+ if (index < 0 || index > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] index=%d\n", i, j, index);
+ }
+#endif
+ goto corrupt;
+ }
+ pduvbp = (pmValueBlock *)&pdubuf[index];
+ if (vsplit > (char *)pduvbp)
+ vsplit = (char *)pduvbp;
+ if (sizeof(unsigned int) > (size_t)(pduend - (char *)pduvbp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] second pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+ __ntohpmValueBlock(pduvbp);
+ if (pduvbp->vlen < PM_VAL_HDR_SIZE || pduvbp->vlen > pp->hdr.len) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] vlen=%d\n", i, j, pduvbp->vlen);
+ }
+#endif
+ goto corrupt;
+ }
+ if (pduvbp->vlen > (size_t)(pduend - (char *)pduvbp)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: pmid[%d] value[%d] third pduvp past end of PDU buffer\n", i, j);
+ }
+#endif
+ goto corrupt;
+ }
+ pduvp->value.pval = pduvbp;
+ }
+ }
+ vlp = (vlist_t *)((__psint_t)vlp + sizeof(*vlp) + (vsp->numval-1)*sizeof(vlp->vlist[0]));
+ }
+ else {
+ vlp = (vlist_t *)((__psint_t)vlp + sizeof(vlp->pmid) + sizeof(vlp->numval));
+ }
+ }
+ if (numpmid > 0) {
+ if (sizeof(result_t) - sizeof(__pmPDU) + vsize != pp->hdr.len - (pduend - vsplit)) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "__pmDecodeResult: Bad: vsplit past end of PDU buffer\n");
+ }
+#endif
+ goto corrupt;
+ }
+ /*
+ * PDU buffer already pinned on entry, pin again so that
+ * the caller can safely call _both_ pmFreeResult() and
+ * __pmUnpinPDUBuf() ... refer to thread-safe notes above.
+ */
+ __pmPinPDUBuf(pdubuf);
+ }
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ __pmDumpResult(stderr, pr);
+#endif
+
+ /*
+ * Note we return with the input buffer (pdubuf) still pinned and
+ * for the 64-bit pointer case the new buffer (newbuf) also pinned -
+ * if numpmid != 0 see the thread-safe comments above
+ */
+ *result = pr;
+ return 0;
+
+corrupt:
+ free(pr);
+ return PM_ERR_IPC;
+}
diff --git a/src/libpcp/src/p_text.c b/src/libpcp/src/p_text.c
new file mode 100644
index 0000000..739ef5a
--- /dev/null
+++ b/src/libpcp/src/p_text.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+/*
+ * PDU for pmLookupText request (PDU_TEXT_REQ)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int ident;
+ int type; /* one line or help, PMID or InDom */
+} text_req_t;
+
+int
+__pmSendTextReq(int fd, int from, int ident, int type)
+{
+ text_req_t *pp;
+ int sts;
+
+ if ((pp = (text_req_t *)__pmFindPDUBuf(sizeof(text_req_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(text_req_t);
+ pp->hdr.type = PDU_TEXT_REQ;
+ pp->hdr.from = from;
+
+ if (type & PM_TEXT_PMID)
+ pp->ident = __htonpmID((pmID)ident);
+ else if (type & PM_TEXT_INDOM)
+ pp->ident = __htonpmInDom((pmInDom)ident);
+ else
+ pp->ident = __htonpmInDom((pmInDom)ident);
+
+ pp->type = htonl(type);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeTextReq(__pmPDU *pdubuf, int *ident, int *type)
+{
+ text_req_t *pp;
+ char *pduend;
+
+ pp = (text_req_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp < sizeof(text_req_t))
+ return PM_ERR_IPC;
+
+ *type = ntohl(pp->type);
+ if ((*type) & PM_TEXT_PMID)
+ *ident = __ntohpmID(pp->ident);
+ else if ((*type) & PM_TEXT_INDOM)
+ *ident = __ntohpmInDom(pp->ident);
+ else
+ *ident = PM_INDOM_NULL;
+
+ return 0;
+}
+
+/*
+ * PDU for pmLookupText result (PDU_TEXT)
+ */
+typedef struct {
+ __pmPDUHdr hdr;
+ int ident;
+ int buflen; /* no. of chars following */
+ char buffer[sizeof(int)]; /* desired text */
+} text_t;
+
+int
+__pmSendText(int fd, int ctx, int ident, const char *buffer)
+{
+ text_t *pp;
+ size_t need;
+ int sts;
+
+ need = sizeof(text_t) - sizeof(pp->buffer) + PM_PDU_SIZE_BYTES(strlen(buffer));
+ if ((pp = (text_t *)__pmFindPDUBuf((int)need)) == NULL)
+ return -oserror();
+ pp->hdr.len = (int)need;
+ pp->hdr.type = PDU_TEXT;
+ pp->hdr.from = ctx;
+ /*
+ * Note: ident argument must already be in network byte order.
+ * The caller has to do this because the type of ident is not
+ * part of the transmitted PDU_TEXT pdu; ident may be either
+ * a pmID or pmInDom, and so the caller must use either
+ * __htonpmID or __htonpmInDom (respectfully).
+ */
+ pp->ident = ident;
+
+ pp->buflen = (int)strlen(buffer);
+ memcpy((void *)pp->buffer, (void *)buffer, pp->buflen);
+ pp->buflen = htonl(pp->buflen);
+
+ sts = __pmXmitPDU(fd, (__pmPDU *)pp);
+ __pmUnpinPDUBuf(pp);
+ return sts;
+}
+
+int
+__pmDecodeText(__pmPDU *pdubuf, int *ident, char **buffer)
+{
+ text_t *pp;
+ char *pduend;
+ int buflen;
+
+ pp = (text_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp < sizeof(text_t) - sizeof(int))
+ return PM_ERR_IPC;
+
+ /*
+ * Note: ident argument is returned in network byte order.
+ * The caller has to convert it to host byte order because
+ * the type of ident is not part of the transmitted PDU_TEXT
+ * pdu (ident may be either a pmID or a pmInDom. The caller
+ * must use either __ntohpmID() or __ntohpmInDom(), respectfully.
+ */
+ *ident = pp->ident;
+ buflen = ntohl(pp->buflen);
+ if (buflen < 0 || buflen >= INT_MAX - 1 || buflen > pp->hdr.len)
+ return PM_ERR_IPC;
+ if (pduend - (char *)pp < sizeof(text_t) - sizeof(pp->buffer) + buflen)
+ return PM_ERR_IPC;
+ if ((*buffer = (char *)malloc(buflen+1)) == NULL)
+ return -oserror();
+ strncpy(*buffer, pp->buffer, buflen);
+ (*buffer)[buflen] = '\0';
+ return 0;
+}
diff --git a/src/libpcp/src/pdu.c b/src/libpcp/src/pdu.c
new file mode 100644
index 0000000..3036976
--- /dev/null
+++ b/src/libpcp/src/pdu.c
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes:
+ *
+ * maxsize - monotonic increasing and rarely changes, so use global
+ * mutex to protect updates, but allow reads without locking
+ * as seeing an unexpected newly updated value is benign
+ *
+ * On success, the result parameter from __pmGetPDU() points into a PDU
+ * buffer that is pinned from the call to __pmFindPDUBuf(). It is the
+ * responsibility of the __pmGetPDU() caller to unpin the buffer when
+ * it is safe to do so.
+ *
+ * __pmPDUCntIn[] and __pmPDUCntOut[] are diagnostic counters that are
+ * maintained with non-atomic updates ... we've decided that it is
+ * acceptable for their values to be subject to possible (but unlikely)
+ * missed updates
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+INTERN int pmDebug; /* the real McCoy */
+
+/*
+ * Performance Instrumentation
+ * ... counts binary PDUs received and sent by low 4 bits of PDU type
+ */
+
+static unsigned int inctrs[PDU_MAX+1];
+static unsigned int outctrs[PDU_MAX+1];
+INTERN unsigned int *__pmPDUCntIn = inctrs;
+INTERN unsigned int *__pmPDUCntOut = outctrs;
+
+#ifdef PCP_DEBUG
+static int mypid = -1;
+#endif
+static int ceiling = PDU_CHUNK * 64;
+
+static struct timeval def_wait = { 10, 0 };
+static double def_timeout = 10.0;
+
+#define HEADER -1
+#define BODY 0
+
+const struct timeval *
+__pmDefaultRequestTimeout(void)
+{
+ static int done_default = 0;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (!done_default) {
+ char *timeout_str;
+ char *end_ptr;
+ if ((timeout_str = getenv("PMCD_REQUEST_TIMEOUT")) != NULL) {
+ def_timeout = strtod(timeout_str, &end_ptr);
+ if (*end_ptr != '\0' || def_timeout < 0.0) {
+ __pmNotifyErr(LOG_WARNING,
+ "ignored bad PMCD_REQUEST_TIMEOUT = '%s'\n",
+ timeout_str);
+ }
+ else {
+ def_wait.tv_sec = (int)def_timeout; /* truncate -> secs */
+ if (def_timeout > (double)def_wait.tv_sec)
+ def_wait.tv_usec = (long)((def_timeout - (double)def_wait.tv_sec) * 1000000);
+ else
+ def_wait.tv_usec = 0;
+ }
+ }
+ done_default = 1;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return (&def_wait);
+}
+
+static int
+pduread(int fd, char *buf, int len, int part, int timeout)
+{
+ int socketipc = __pmSocketIPC(fd);
+ int status = 0;
+ int have = 0;
+ int onetrip = 1;
+ struct timeval dead_hand;
+ struct timeval now;
+
+ if (timeout == -2 /*TIMEOUT_ASYNC*/)
+ return -EOPNOTSUPP;
+
+ /*
+ * Handle short reads that may split a PDU ...
+ *
+ * The original logic here assumed that recv() would only split a
+ * PDU at a word (__pmPDU) boundary ... with the introduction of
+ * secure connections, SSL and possibly compression all lurking
+ * below the socket covers, this is no longer a safe assumption.
+ *
+ * So, we keep nibbling at the input stream until we have all that
+ * we have requested, or we timeout, or error.
+ */
+ while (len) {
+ struct timeval wait;
+
+#if defined(IS_MINGW) /* cannot select on a pipe on Win32 - yay! */
+ if (!__pmSocketIPC(fd)) {
+ COMMTIMEOUTS cwait = { 0 };
+
+ if (timeout != TIMEOUT_NEVER)
+ cwait.ReadTotalTimeoutConstant = timeout * 1000.0;
+ else
+ cwait.ReadTotalTimeoutConstant = def_timeout * 1000.0;
+ SetCommTimeouts((HANDLE)_get_osfhandle(fd), &cwait);
+ }
+ else
+#endif
+
+ /*
+ * either never timeout (i.e. block forever), or timeout
+ */
+ if (timeout != TIMEOUT_NEVER) {
+ if (timeout > 0) {
+ wait.tv_sec = timeout;
+ wait.tv_usec = 0;
+ }
+ else
+ wait = def_wait;
+ if (onetrip) {
+ /*
+ * Need all parts of the PDU to be received by dead_hand
+ * This enforces a low overall timeout for the whole PDU
+ * (as opposed to just a timeout for individual calls to
+ * recv). A more invasive alternative (better) approach
+ * would see all I/O performed in the main event loop,
+ * and I/O routines transformed to continuation-passing
+ * style.
+ */
+ gettimeofday(&dead_hand, NULL);
+ dead_hand.tv_sec += wait.tv_sec;
+ dead_hand.tv_usec += wait.tv_usec;
+ while (dead_hand.tv_usec >= 1000000) {
+ dead_hand.tv_usec -= 1000000;
+ dead_hand.tv_sec++;
+ }
+ onetrip = 0;
+ }
+
+ status = __pmSocketReady(fd, &wait);
+ if (status > 0) {
+ gettimeofday(&now, NULL);
+ if (now.tv_sec > dead_hand.tv_sec ||
+ (now.tv_sec == dead_hand.tv_sec &&
+ now.tv_usec > dead_hand.tv_usec))
+ status = 0;
+ }
+ if (status == 0) {
+ if (__pmGetInternalState() != PM_STATE_APPL) {
+ /* special for PMCD and friends
+ * Note, on Linux select would return 'time remaining'
+ * in timeout value, so report the expected timeout
+ */
+ int tosec, tomsec;
+
+ if ( timeout != TIMEOUT_NEVER && timeout > 0 ) {
+ tosec = (int)timeout;
+ tomsec = 0;
+ } else {
+ tosec = (int)def_wait.tv_sec;
+ tomsec = 1000*(int)def_wait.tv_usec;
+ }
+
+ __pmNotifyErr(LOG_WARNING,
+ "pduread: timeout (after %d.%03d "
+ "sec) while attempting to read %d "
+ "bytes out of %d in %s on fd=%d",
+ tosec, tomsec, len - have, len,
+ part == HEADER ? "HDR" : "BODY", fd);
+ }
+ return PM_ERR_TIMEOUT;
+ }
+ else if (status < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR, "pduread: select() on fd=%d: %s",
+ fd, netstrerror_r(errmsg, sizeof(errmsg)));
+ setoserror(neterror());
+ return status;
+ }
+ }
+ if (socketipc) {
+ status = __pmRecv(fd, buf, len, 0);
+ setoserror(neterror());
+ } else {
+ status = read(fd, buf, len);
+ }
+ __pmOverrideLastFd(fd);
+ if (status < 0)
+ /* error */
+ return status;
+ else if (status == 0)
+ /* return what we have, or nothing */
+ break;
+
+ have += status;
+ buf += status;
+ len -= status;
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "pduread(%d, ...): have %d, last read %d, still need %d\n",
+ fd, have, status, len);
+ }
+#endif
+ }
+
+ return have;
+}
+
+char *
+__pmPDUTypeStr_r(int type, char *buf, int buflen)
+{
+ char *res = NULL;
+ if (type == PDU_ERROR) res = "ERROR";
+ else if (type == PDU_RESULT) res = "RESULT";
+ else if (type == PDU_PROFILE) res = "PROFILE";
+ else if (type == PDU_FETCH) res = "FETCH";
+ else if (type == PDU_DESC_REQ) res = "DESC_REQ";
+ else if (type == PDU_DESC) res = "DESC";
+ else if (type == PDU_INSTANCE_REQ) res = "INSTANCE_REQ";
+ else if (type == PDU_INSTANCE) res = "INSTANCE";
+ else if (type == PDU_TEXT_REQ) res = "TEXT_REQ";
+ else if (type == PDU_TEXT) res = "TEXT";
+ else if (type == PDU_CONTROL_REQ) res = "CONTROL_REQ";
+ else if (type == PDU_CREDS) res = "CREDS";
+ else if (type == PDU_PMNS_IDS) res = "PMNS_IDS";
+ else if (type == PDU_PMNS_NAMES) res = "PMNS_NAMES";
+ else if (type == PDU_PMNS_CHILD) res = "PMNS_CHILD";
+ else if (type == PDU_PMNS_TRAVERSE) res = "PMNS_TRAVERSE";
+ else if (type == PDU_LOG_CONTROL) res = "LOG_CONTROL";
+ else if (type == PDU_LOG_STATUS) res = "LOG_STATUS";
+ else if (type == PDU_LOG_REQUEST) res = "LOG_REQUEST";
+ else if (type == PDU_AUTH) res = "AUTH";
+ if (res == NULL)
+ snprintf(buf, buflen, "TYPE-%d?", type);
+ else
+ snprintf(buf, buflen, "%s", res);
+
+ return buf;
+}
+
+const char *
+__pmPDUTypeStr(int type)
+{
+ static char tbuf[20];
+ __pmPDUTypeStr_r(type, tbuf, sizeof(tbuf));
+ return tbuf;
+}
+
+#if defined(HAVE_SIGPIPE)
+/*
+ * Because the default handler for SIGPIPE is to exit, we always want a handler
+ * installed to override that so that the write() just returns an error. The
+ * problem is that the user might have installed one prior to the first write()
+ * or may install one at some later stage. This doesn't matter. As long as a
+ * handler other than SIG_DFL is there, all will be well. The first time that
+ * __pmXmitPDU is called, install SIG_IGN as the handler for SIGPIPE. If the
+ * user had already changed the handler from SIG_DFL, put back what was there
+ * before.
+ */
+static int sigpipe_done = 0; /* First time check for installation of
+ non-default SIGPIPE handler */
+static void setup_sigpipe()
+{
+ if (!sigpipe_done) { /* Make sure SIGPIPE is handled */
+ SIG_PF user_onpipe;
+ user_onpipe = signal(SIGPIPE, SIG_IGN);
+ if (user_onpipe != SIG_DFL) /* Put user handler back */
+ signal(SIGPIPE, user_onpipe);
+ sigpipe_done = 1;
+ }
+}
+#else
+static void setup_sigpipe() { }
+#endif
+
+int
+__pmXmitPDU(int fd, __pmPDU *pdubuf)
+{
+ int socketipc = __pmSocketIPC(fd);
+ int off = 0;
+ int len;
+ __pmPDUHdr *php = (__pmPDUHdr *)pdubuf;
+
+ setup_sigpipe();
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int j;
+ char *p;
+ int jend = PM_PDU_SIZE(php->len);
+ char strbuf[20];
+
+ /* for Purify ... */
+ p = (char *)pdubuf + php->len;
+ while (p < (char *)pdubuf + jend*sizeof(__pmPDU))
+ *p++ = '~'; /* buffer end */
+
+ if (mypid == -1)
+ mypid = (int)getpid();
+ fprintf(stderr, "[%d]pmXmitPDU: %s fd=%d len=%d",
+ mypid, __pmPDUTypeStr_r(php->type, strbuf, sizeof(strbuf)), fd, php->len);
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", pdubuf[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+ len = php->len;
+
+ php->len = htonl(php->len);
+ php->from = htonl(php->from);
+ php->type = htonl(php->type);
+ while (off < len) {
+ char *p = (char *)pdubuf;
+ int n;
+
+ p += off;
+
+ n = socketipc ? __pmSend(fd, p, len-off, 0) : write(fd, p, len-off);
+ if (n < 0)
+ break;
+ off += n;
+ }
+ php->len = ntohl(php->len);
+ php->from = ntohl(php->from);
+ php->type = ntohl(php->type);
+
+ if (off != len) {
+ if (socketipc) {
+ if (__pmSocketClosed())
+ return PM_ERR_IPC;
+ return neterror() ? -neterror() : PM_ERR_IPC;
+ }
+ return oserror() ? -oserror() : PM_ERR_IPC;
+ }
+
+ __pmOverrideLastFd(fd);
+ if (php->type >= PDU_START && php->type <= PDU_FINISH)
+ __pmPDUCntOut[php->type-PDU_START]++;
+
+ return off;
+}
+
+/* result is pinned on successful return */
+int
+__pmGetPDU(int fd, int mode, int timeout, __pmPDU **result)
+{
+ int need;
+ int len;
+ static int maxsize = PDU_CHUNK;
+ char *handle;
+ __pmPDU *pdubuf;
+ __pmPDU *pdubuf_prev;
+ __pmPDUHdr *php;
+
+ if ((pdubuf = __pmFindPDUBuf(maxsize)) == NULL)
+ return -oserror();
+
+ /* First read - try to read the header */
+ len = pduread(fd, (void *)pdubuf, sizeof(__pmPDUHdr), HEADER, timeout);
+ php = (__pmPDUHdr *)pdubuf;
+
+ if (len < (int)sizeof(__pmPDUHdr)) {
+ if (len == -1) {
+ if (__pmSocketClosed()) {
+ len = 0;
+ } else {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg)));
+ }
+ }
+ else if (len >= (int)sizeof(php->len)) {
+ /*
+ * Have part of a PDU header. Enough for the "len"
+ * field to be valid, but not yet all of it - save
+ * what we have received and try to read some more.
+ * Note this can only happen once per PDU, so the
+ * ntohl() below will _only_ be done once per PDU.
+ */
+ goto check_read_len; /* continue, do not return */
+ }
+ else if (len == PM_ERR_TIMEOUT) {
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_TIMEOUT;
+ }
+ else if (len < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: len=%d: %s", fd, len, pmErrStr_r(len, errmsg, sizeof(errmsg)));
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_IPC;
+ }
+ else if (len > 0) {
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d hdr read: bad len=%d", fd, len);
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_IPC;
+ }
+
+ /*
+ * end-of-file with no data
+ */
+ __pmUnpinPDUBuf(pdubuf);
+ return 0;
+ }
+
+check_read_len:
+ php->len = ntohl(php->len);
+ if (php->len < (int)sizeof(__pmPDUHdr)) {
+ /*
+ * PDU length indicates insufficient bytes for a PDU header
+ * ... looks like DOS attack like PV 935490
+ */
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU len=%d in hdr", fd, php->len);
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_IPC;
+ }
+ else if (mode == LIMIT_SIZE && php->len > ceiling) {
+ /*
+ * Guard against denial of service attack ... don't accept PDUs
+ * from clients that are larger than 64 Kbytes (ceiling)
+ * (note, pmcd and pmdas have to be able to _send_ large PDUs,
+ * e.g. for a pmResult or instance domain enquiry)
+ */
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d bad PDU len=%d in hdr exceeds maximum client PDU size (%d)",
+ fd, php->len, ceiling);
+
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_TOOBIG;
+ }
+
+ if (len < php->len) {
+ /*
+ * need to read more ...
+ */
+ int tmpsize;
+ int have = len;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (php->len > maxsize) {
+ tmpsize = PDU_CHUNK * ( 1 + php->len / PDU_CHUNK);
+ maxsize = tmpsize;
+ }
+ else
+ tmpsize = maxsize;
+ PM_UNLOCK(__pmLock_libpcp);
+
+ pdubuf_prev = pdubuf;
+ if ((pdubuf = __pmFindPDUBuf(tmpsize)) == NULL) {
+ __pmUnpinPDUBuf(pdubuf_prev);
+ return -oserror();
+ }
+
+ memmove((void *)pdubuf, (void *)php, len);
+ __pmUnpinPDUBuf(pdubuf_prev);
+
+ php = (__pmPDUHdr *)pdubuf;
+ need = php->len - have;
+ handle = (char *)pdubuf;
+ /* block until all of the PDU is received this time */
+ len = pduread(fd, (void *)&handle[len], need, BODY, timeout);
+ if (len != need) {
+ if (len == PM_ERR_TIMEOUT) {
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_TIMEOUT;
+ }
+ else if (len < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: len=%d: %s", fd, len, pmErrStr_r(-oserror(), errmsg, sizeof(errmsg)));
+ }
+ else
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d data read: have %d, want %d, got %d", fd, have, need, len);
+ /*
+ * only report header fields if you've read enough bytes
+ */
+ if (len > 0)
+ have += len;
+ if (have >= (int)(sizeof(php->len)+sizeof(php->type)+sizeof(php->from)))
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x from=0x%x", php->len, (unsigned)ntohl(php->type), (unsigned)ntohl(php->from));
+ else if (have >= (int)(sizeof(php->len)+sizeof(php->type)))
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: PDU hdr: len=0x%x type=0x%x", php->len, (unsigned)ntohl(php->type));
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_IPC;
+ }
+ }
+
+ *result = (__pmPDU *)php;
+ php->type = ntohl((unsigned int)php->type);
+ if (php->type < 0) {
+ /*
+ * PDU type is bad ... could be a possible mem leak attack like
+ * https://bugzilla.redhat.com/show_bug.cgi?id=841319
+ */
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: fd=%d illegal PDU type=%d in hdr", fd, php->type);
+ __pmUnpinPDUBuf(pdubuf);
+ return PM_ERR_IPC;
+ }
+ php->from = ntohl((unsigned int)php->from);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int j;
+ char *p;
+ int jend = PM_PDU_SIZE(php->len);
+ char strbuf[20];
+
+ /* for Purify ... */
+ p = (char *)*result + php->len;
+ while (p < (char *)*result + jend*sizeof(__pmPDU))
+ *p++ = '~'; /* buffer end */
+
+ if (mypid == -1)
+ mypid = (int)getpid();
+ fprintf(stderr, "[%d]pmGetPDU: %s fd=%d len=%d from=%d",
+ mypid, __pmPDUTypeStr_r(php->type, strbuf, sizeof(strbuf)), fd, php->len, php->from);
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", (*result)[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+ if (php->type >= PDU_START && php->type <= PDU_FINISH)
+ __pmPDUCntIn[php->type-PDU_START]++;
+
+ /*
+ * Note php points into the PDU buffer pdubuf that remains pinned
+ * and php is returned via the result parameter ... see the
+ * thread-safe comments above
+ */
+ return php->type;
+}
+
+int
+__pmGetPDUCeiling(void)
+{
+ return ceiling;
+}
+
+int
+__pmSetPDUCeiling(int newceiling)
+{
+ if (newceiling > 0)
+ return (ceiling = newceiling);
+ return ceiling;
+}
+
+void
+__pmSetPDUCntBuf(unsigned *in, unsigned *out)
+{
+ __pmPDUCntIn = in;
+ __pmPDUCntOut = out;
+}
diff --git a/src/libpcp/src/pdubuf.c b/src/libpcp/src/pdubuf.c
new file mode 100644
index 0000000..ac522e0
--- /dev/null
+++ b/src/libpcp/src/pdubuf.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * To avoid buffer trampling, on success __pmFindPDUBuf() now returns
+ * a pinned PDU buffer. It is the caller's responsibility to unpin the
+ * PDU buffer when safe to do so.
+ *
+ * TODO now that buffers always pinned on return, we can do away
+ * with buf_pin and buf_pin_tail and maintain one list?
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+
+#define PDU_CHUNK 1024 /* unit of space allocation for PDU buffer */
+
+typedef struct bufctl {
+ struct bufctl *bc_next;
+ int bc_size;
+ int bc_pincnt;
+ char *bc_buf;
+ char *bc_bufend;
+} bufctl_t;
+
+static bufctl_t *buf_free;
+static bufctl_t *buf_pin;
+static bufctl_t *buf_pin_tail;
+
+#ifdef PCP_DEBUG
+static void
+pdubufdump(void)
+{
+ bufctl_t *pcp;
+
+ PM_LOCK(__pmLock_libpcp);
+ if (buf_free != NULL) {
+ fprintf(stderr, " free pdubuf[size]:");
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next)
+ fprintf(stderr, " " PRINTF_P_PFX "%p[%d]", pcp->bc_buf, pcp->bc_size);
+ fputc('\n', stderr);
+ }
+
+ if (buf_pin != NULL) {
+ fprintf(stderr, " pinned pdubuf[size](pincnt):");
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next)
+ fprintf(stderr, " " PRINTF_P_PFX "%p...%p[%d](%d)", pcp->bc_buf, &pcp->bc_buf[pcp->bc_size-1], pcp->bc_size, pcp->bc_pincnt);
+ fputc('\n', stderr);
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+}
+#endif
+
+__pmPDU *
+__pmFindPDUBuf(int need)
+{
+ bufctl_t *pcp;
+ __pmPDU *sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (need < 0) {
+ /* special diagnostic case ... dump buffer state */
+#ifdef PCP_DEBUG
+ fprintf(stderr, "__pmFindPDUBuf(DEBUG)\n");
+ pdubufdump();
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_size >= need)
+ break;
+ }
+ if (pcp == NULL) {
+ if ((pcp = (bufctl_t *)malloc(sizeof(*pcp))) == NULL) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ pcp->bc_pincnt = 0;
+ pcp->bc_size = PDU_CHUNK * (1 + need/PDU_CHUNK);
+ if ((pcp->bc_buf = (char *)valloc(pcp->bc_size)) == NULL) {
+ free(pcp);
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ pcp->bc_next = buf_free;
+ pcp->bc_bufend = &pcp->bc_buf[pcp->bc_size];
+ buf_free = pcp;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDUBUF) {
+ fprintf(stderr, "__pmFindPDUBuf(%d) -> " PRINTF_P_PFX "%p\n", need, pcp->bc_buf);
+ pdubufdump();
+ }
+#endif
+
+ __pmPinPDUBuf(pcp->bc_buf);
+ sts = (__pmPDU *)pcp->bc_buf;
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+void
+__pmPinPDUBuf(void *handle)
+{
+ bufctl_t *pcp;
+ bufctl_t *prior = NULL;
+
+ assert(((__psint_t)handle % sizeof(int)) == 0);
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend)
+ break;
+ prior = pcp;
+ }
+
+ if (pcp != NULL) {
+ /* first pin for this buffer, move between lists */
+ if (prior == NULL)
+ buf_free = pcp->bc_next;
+ else
+ prior->bc_next = pcp->bc_next;
+ pcp->bc_next = NULL;
+ if (buf_pin_tail != NULL)
+ buf_pin_tail->bc_next = pcp;
+ buf_pin_tail = pcp;
+ if (buf_pin == NULL)
+ buf_pin = pcp;
+ pcp->bc_pincnt = 1;
+ }
+ else {
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend)
+ break;
+ }
+ if (pcp != NULL)
+ pcp->bc_pincnt++;
+ else {
+ __pmNotifyErr(LOG_WARNING, "__pmPinPDUBuf: 0x%lx not in pool!",
+ (unsigned long)handle);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ pdubufdump();
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "__pmPinPDUBuf(" PRINTF_P_PFX "%p) -> pdubuf=" PRINTF_P_PFX "%p, pincnt=%d\n",
+ handle, pcp->bc_buf, pcp->bc_pincnt);
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+}
+
+int
+__pmUnpinPDUBuf(void *handle)
+{
+ bufctl_t *pcp;
+ bufctl_t *prior = NULL;
+
+ assert(((__psint_t)handle % sizeof(int)) == 0);
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < &pcp->bc_buf[pcp->bc_size])
+ break;
+ prior = pcp;
+ }
+ if (pcp == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDUBUF) {
+ fprintf(stderr, "__pmUnpinPDUBuf(" PRINTF_P_PFX "%p) -> fails\n", handle);
+ pdubufdump();
+ }
+#endif
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+ }
+
+ if (--pcp->bc_pincnt == 0) {
+ if (prior == NULL)
+ buf_pin = pcp->bc_next;
+ else
+ prior->bc_next = pcp->bc_next;
+ if (buf_pin_tail == pcp)
+ buf_pin_tail = prior;
+
+ pcp->bc_next = buf_free;
+ buf_free = pcp;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDUBUF)
+ fprintf(stderr, "__pmUnpinPDUBuf(" PRINTF_P_PFX "%p) -> pdubuf=" PRINTF_P_PFX "%p, pincnt=%d\n",
+ handle, pcp->bc_buf, pcp->bc_pincnt);
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 1;
+}
+
+void
+__pmCountPDUBuf(int need, int *alloc, int *free)
+{
+ bufctl_t *pcp;
+ int count;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ count = 0;
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_size >= need)
+ count++;
+ }
+ *alloc = count;
+
+ count = 0;
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_size >= need)
+ count++;
+ }
+ *free = count;
+ *alloc += count;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+}
diff --git a/src/libpcp/src/pmns.c b/src/libpcp/src/pmns.c
new file mode 100644
index 0000000..7602922
--- /dev/null
+++ b/src/libpcp/src/pmns.c
@@ -0,0 +1,2559 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * locerr - no serious side-effects, most unlikely to be used, and
+ * repeated calls are likely to produce the same result, so don't bother
+ * to make thread-safe
+ */
+
+#include <sys/stat.h>
+#include <stddef.h>
+#include <assert.h>
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+/* token types */
+#define NAME 1
+#define PATH 2
+#define PMID 3
+#define LBRACE 4
+#define RBRACE 5
+#define BOGUS 10
+
+#define UNKNOWN_MARK_STATE -1 /* tree not all marked the same way */
+/*
+ * Note: bit masks below are designed to clear and set the "flag" field
+ * of a __pmID_int (i.e. a PMID)
+ */
+#define PMID_MASK 0x7fffffff /* 31 bits of PMID */
+#define MARK_BIT 0x80000000 /* mark bit */
+
+
+static int lineno;
+static char linebuf[256];
+static char *linep;
+static char fname[256];
+static char tokbuf[256];
+static pmID tokpmid;
+static int seenpmid;
+
+static __pmnsNode *seen; /* list of pass-1 subtree nodes */
+
+/* Last modification time for loading main_pmns file. */
+#if defined(HAVE_STAT_TIMESTRUC)
+static timestruc_t last_mtim;
+#elif defined(HAVE_STAT_TIMESPEC)
+static struct timespec last_mtim;
+#elif defined(HAVE_STAT_TIMESPEC_T)
+static timespec_t last_mtim;
+#elif defined(HAVE_STAT_TIME_T)
+static time_t last_mtim;
+#else
+!bozo!
+#endif
+
+/* The curr_pmns points to PMNS to use for API ops.
+ * Curr_pmns will point to either the main_pmns or
+ * a pmns from a version 2 archive context.
+ */
+static __pmnsTree *curr_pmns;
+
+/* The main_pmns points to the loaded PMNS (not from archive). */
+static __pmnsTree *main_pmns;
+
+
+/* == 1 if PMNS loaded and __pmExportPMNS has been called */
+static int export;
+
+static int havePmLoadCall;
+static int useExtPMNS; /* set by __pmUsePMNS() */
+
+static int load(const char *filename, int dupok);
+static __pmnsNode *locate(const char *name, __pmnsNode *root);
+
+
+/*
+ * Set current pmns to an externally supplied PMNS.
+ * Useful for testing the API routines during debugging.
+ */
+void
+__pmUsePMNS(__pmnsTree *t)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ useExtPMNS = 1;
+ curr_pmns = t;
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+static char *
+pmPMNSLocationStr(int location)
+{
+ if (location < 0) {
+ /* see thread-safe note above */
+ static char locerr[PM_MAXERRMSGLEN];
+ return pmErrStr_r(location, locerr, sizeof(locerr));
+ }
+
+ switch(location) {
+ case PMNS_LOCAL: return "Local";
+ case PMNS_REMOTE: return "Remote";
+ case PMNS_ARCHIVE: return "Archive";
+ }
+ return "Internal Error";
+}
+
+
+static int
+LoadDefault(char *reason_msg)
+{
+ if (main_pmns == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr,
+ "pmGetPMNSLocation: Loading local PMNS for %s PMAPI context\n",
+ reason_msg);
+ }
+#endif
+ if (load(PM_NS_DEFAULT, 0) < 0)
+ return PM_ERR_NOPMNS;
+ else
+ return PMNS_LOCAL;
+ }
+ return PMNS_LOCAL;
+}
+
+/*
+ * Return the pmns_location. Possibly load the default PMNS.
+ */
+int
+pmGetPMNSLocation(void)
+{
+ int pmns_location = PM_ERR_NOPMNS;
+ int n;
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (useExtPMNS) {
+ PM_UNLOCK(__pmLock_libpcp);
+ pmns_location = PMNS_LOCAL;
+ goto done;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ /*
+ * Determine if we are to use PDUs or local PMNS file.
+ * Load PMNS if necessary.
+ */
+ if (!havePmLoadCall) {
+ __pmContext *ctxp;
+ int version;
+
+ if ((n = pmWhichContext()) >= 0 && (ctxp = __pmHandleToPtr(n)) != NULL) {
+ switch(ctxp->c_type) {
+ case PM_CONTEXT_HOST:
+ if (ctxp->c_pmcd->pc_fd == -1) {
+ pmns_location = PM_ERR_IPC;
+ goto done;
+ }
+ if ((sts = version = __pmVersionIPC(ctxp->c_pmcd->pc_fd)) < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(LOG_ERR,
+ "pmGetPMNSLocation: version lookup failed "
+ "(context=%d, fd=%d): %s",
+ n, ctxp->c_pmcd->pc_fd, pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ pmns_location = PM_ERR_NOPMNS;
+ }
+ else if (version == PDU_VERSION2) {
+ pmns_location = PMNS_REMOTE;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "pmGetPMNSLocation: bad host PDU version "
+ "(context=%d, fd=%d, ver=%d)",
+ n, ctxp->c_pmcd->pc_fd, version);
+ pmns_location = PM_ERR_NOPMNS;
+ }
+ break;
+
+ case PM_CONTEXT_LOCAL:
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA))
+ /* Local context requires single-threaded applications */
+ pmns_location = PM_ERR_THREAD;
+ else
+ pmns_location = LoadDefault("local");
+ break;
+
+ case PM_CONTEXT_ARCHIVE:
+ version = ctxp->c_archctl->ac_log->l_label.ill_magic & 0xff;
+ if (version == PM_LOG_VERS02) {
+ pmns_location = PMNS_ARCHIVE;
+ PM_LOCK(__pmLock_libpcp);
+ curr_pmns = ctxp->c_archctl->ac_log->l_pmns;
+ PM_UNLOCK(__pmLock_libpcp);
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "pmGetPMNSLocation: bad archive "
+ "version (context=%d, fd=%d, ver=%d)",
+ n, ctxp->c_pmcd->pc_fd, version);
+ pmns_location = PM_ERR_NOPMNS;
+ }
+ break;
+
+ default:
+ __pmNotifyErr(LOG_ERR, "pmGetPMNSLocation: bogus context "
+ "type: %d", ctxp->c_type);
+ pmns_location = PM_ERR_NOPMNS;
+ break;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+ else {
+ pmns_location = PM_ERR_NOPMNS; /* no context for client */
+ }
+ }
+ else { /* have explicit external load call */
+ if (main_pmns == NULL)
+ pmns_location = PM_ERR_NOPMNS;
+ else
+ pmns_location = PMNS_LOCAL;
+ }
+
+ PM_LOCK(__pmLock_libpcp);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ static int last_pmns_location = -1;
+
+ if (pmns_location != last_pmns_location) {
+ fprintf(stderr, "pmGetPMNSLocation() -> %s\n",
+ pmPMNSLocationStr(pmns_location));
+ last_pmns_location = pmns_location;
+ }
+ }
+#endif
+
+ /* fix up curr_pmns for API ops */
+ if (pmns_location == PMNS_LOCAL)
+ curr_pmns = main_pmns;
+ PM_UNLOCK(__pmLock_libpcp);
+
+done:
+ return pmns_location;
+}
+
+/*
+ * Our own PMNS locator. Don't distinguish between ARCHIVE or LOCAL.
+ */
+static int
+GetLocation(void)
+{
+ int loc = pmGetPMNSLocation();
+
+ if (loc == PMNS_ARCHIVE)
+ return PMNS_LOCAL;
+ return loc;
+}
+
+/*
+ * For debugging, call via __pmDumpNameSpace() or __pmDumpNameNode()
+ *
+ * verbosity is 0 (name), 1 (names and pmids) or 2 (names, pmids and
+ * linked-list structures)
+ */
+static void
+dumptree(FILE *f, int level, __pmnsNode *rp, int verbosity)
+{
+ int i;
+ __pmID_int *pp;
+
+ if (rp != NULL) {
+ if (verbosity > 1)
+ fprintf(f, "" PRINTF_P_PFX "%p", rp);
+ for (i = 0; i < level; i++) {
+ fprintf(f, " ");
+ }
+ fprintf(f, " %-16.16s", rp->name);
+ pp = (__pmID_int *)&rp->pmid;
+ if (verbosity > 0 && rp->first == NULL)
+ fprintf(f, " %d %d.%d.%d 0x%08x", rp->pmid,
+ pp->domain, pp->cluster, pp->item,
+ rp->pmid);
+ if (verbosity > 1) {
+ fprintf(f, "\t[first: ");
+ if (rp->first) fprintf(f, "" PRINTF_P_PFX "%p", rp->first);
+ else fprintf(f, "<null>");
+ fprintf(f, " next: ");
+ if (rp->next) fprintf(f, "" PRINTF_P_PFX "%p", rp->next);
+ else fprintf(f, "<null>");
+ fprintf(f, " parent: ");
+ if (rp->parent) fprintf(f, "" PRINTF_P_PFX "%p", rp->parent);
+ else fprintf(f, "<null>");
+ fprintf(f, " hash: ");
+ if (rp->hash) fprintf(f, "" PRINTF_P_PFX "%p", rp->hash);
+ else fprintf(f, "<null>");
+ }
+ fputc('\n', f);
+ dumptree(f, level+1, rp->first, verbosity);
+ dumptree(f, level, rp->next, verbosity);
+ }
+}
+
+static void
+err(char *s)
+{
+ if (lineno > 0)
+ pmprintf("[%s:%d] ", fname, lineno);
+ pmprintf("Error Parsing ASCII PMNS: %s\n", s);
+ if (lineno > 0) {
+ char *p;
+ pmprintf(" %s", linebuf);
+ for (p = linebuf; *p; p++)
+ ;
+ if (p[-1] != '\n')
+ pmprintf("\n");
+ if (linep) {
+ p = linebuf;
+ for (p = linebuf; p < linep; p++) {
+ if (!isspace((int)*p))
+ *p = ' ';
+ }
+ *p++ = '^';
+ *p++ = '\n';
+ *p = '\0';
+ pmprintf(" %s", linebuf);
+ }
+ }
+ pmflush();
+}
+
+/*
+ * lexical analyser for loading the ASCII pmns
+ */
+static int
+lex(int reset)
+{
+ static int first = 1;
+ static FILE *fin;
+ static char *lp;
+ char *tp;
+ int colon;
+ int type;
+ int d, c, i;
+ __pmID_int pmid_int;
+
+ if (reset) {
+ /* reset! */
+ linep = NULL;
+ first = 1;
+ return 0;
+ }
+
+ if (first) {
+ char *alt;
+ char cmd[80+MAXPATHLEN];
+
+ first = 0;
+ if ((alt = getenv("PCP_ALT_CPP")) != NULL) {
+ /* $PCP_ALT_CPP used in the build before pmcpp installed */
+ snprintf(cmd, sizeof(cmd), "%s %s", alt, fname);
+ }
+ else {
+ /* the normal case ... */
+ int sep = __pmPathSeparator();
+ char *bin_dir = pmGetConfig("PCP_BINADM_DIR");
+ snprintf(cmd, sizeof(cmd), "%s%c%s %s", bin_dir, sep, "pmcpp" EXEC_SUFFIX, fname);
+ }
+
+ fin = popen(cmd, "r");
+ if (fin == NULL)
+ return -oserror();
+
+ lp = linebuf;
+ *lp = '\0';
+ }
+
+ while (*lp && isspace((int)*lp)) lp++;
+
+ while (*lp == '\0') {
+ for ( ; ; ) {
+ char *p;
+ char *q;
+ int inspace = 0;
+
+ if (fgets(linebuf, sizeof(linebuf), fin) == NULL) {
+ if (pclose(fin) != 0) {
+ lineno = -1; /* We're outside of line counting range now */
+ err("pmcpp returned non-zero exit status");
+ return PM_ERR_PMNS;
+ } else {
+ return 0;
+ }
+ }
+ for (q = p = linebuf; *p; p++) {
+ if (isspace((int)*p)) {
+ if (!inspace) {
+ if (q > linebuf && q[-1] != ':')
+ *q++ = *p;
+ inspace = 1;
+ }
+ }
+ else if (*p == ':') {
+ if (inspace) {
+ q--;
+ inspace = 0;
+ }
+ *q++ = *p;
+ }
+ else {
+ *q++ = *p;
+ inspace = 0;
+ }
+ }
+ if (p[-1] != '\n') {
+ err("Absurdly long line, cannot recover");
+ return PM_ERR_PMNS;
+ }
+ *q = '\0';
+ if (linebuf[0] == '#') {
+ /* pmcpp line number control line */
+ if (sscanf(linebuf, "# %d \"%s", &lineno, fname) != 2) {
+ err("Illegal line number control number");
+ return PM_ERR_PMNS;
+ }
+ --lineno;
+ for (p = fname; *p; p++)
+ ;
+ *--p = '\0';
+ continue;
+ }
+ else
+ lineno++;
+ lp = linebuf;
+ while (*lp && isspace((int)*lp)) lp++;
+ break;
+ }
+ }
+
+ linep = lp;
+ tp = tokbuf;
+ while (!isspace((int)*lp))
+ *tp++ = *lp++;
+ *tp = '\0';
+
+ if (tokbuf[0] == '{' && tokbuf[1] == '\0') return LBRACE;
+ else if (tokbuf[0] == '}' && tokbuf[1] == '\0') return RBRACE;
+ else if (isalpha((int)tokbuf[0])) {
+ type = NAME;
+ for (tp = &tokbuf[1]; *tp; tp++) {
+ if (*tp == '.')
+ type = PATH;
+ else if (!isalpha((int)*tp) && !isdigit((int)*tp) && *tp != '_')
+ break;
+ }
+ if (*tp == '\0') return type;
+ }
+ colon = 0;
+ for (tp = tokbuf; *tp; tp++) {
+ if (*tp == ':') {
+ if (++colon > 3) return BOGUS;
+ }
+ else if (!isdigit((int)*tp) && *tp != '*') return BOGUS;
+ }
+
+ /*
+ * Internal PMID format
+ * domain 9 bits
+ * cluster 12 bits
+ * item 10 bits
+ */
+ if (sscanf(tokbuf, "%d:%d:%d", &d, &c, &i) == 3) {
+ if (d > 510) {
+ err("Illegal domain field in PMID");
+ return BOGUS;
+ }
+ else if (c > 4095) {
+ err("Illegal cluster field in PMID");
+ return BOGUS;
+ }
+ else if (i > 1023) {
+ err("Illegal item field in PMID");
+ return BOGUS;
+ }
+ pmid_int.flag = 0;
+ pmid_int.domain = d;
+ pmid_int.cluster = c;
+ pmid_int.item = i;
+ }
+ else {
+ for (tp = tokbuf; *tp; tp++) {
+ if (*tp == ':') {
+ if (strcmp("*:*", ++tp) != 0) {
+ err("Illegal PMID");
+ return BOGUS;
+ }
+ break;
+ }
+ }
+ if (sscanf(tokbuf, "%d:", &d) != 1) {
+ err("Illegal PMID");
+ return BOGUS;
+ }
+ if (d > 510) {
+ err("Illegal domain field in dynamic PMID");
+ return BOGUS;
+ }
+ else {
+ /*
+ * this node is the base of a dynamic subtree in the PMNS
+ * ... identified by setting the domain field to the reserved
+ * value DYNAMIC_PMID and storing the real domain of the PMDA
+ * that can enumerate the subtree in the cluster field, while
+ * the item field is not used (and set to zero)
+ */
+ pmid_int.flag = 0;
+ pmid_int.domain = DYNAMIC_PMID;
+ pmid_int.cluster = d;
+ pmid_int.item = 0;
+ }
+ }
+ tokpmid = *(pmID *)&pmid_int;
+
+ return PMID;
+}
+
+/*
+ * Remove the named node from the seen list and return it.
+ * The seen-list is a list of subtrees from pass 1.
+ */
+
+static __pmnsNode *
+findseen(char *name)
+{
+ __pmnsNode *np;
+ __pmnsNode *lnp; /* last np */
+
+ for (np = seen, lnp = NULL; np != NULL; lnp = np, np = np->next) {
+ if (strcmp(np->name, name) == 0) {
+ if (np == seen)
+ seen = np->next;
+ else
+ lnp->next = np->next;
+ np->next = NULL;
+ return np;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Attach the subtrees from pass-1 to form a whole
+ * connected tree.
+ */
+static int
+attach(char *base, __pmnsNode *rp)
+{
+ int i;
+ __pmnsNode *np;
+ __pmnsNode *xp;
+ char *path;
+
+ if (rp != NULL) {
+ for (np = rp->first; np != NULL; np = np->next) {
+ if (np->pmid == PM_ID_NULL) {
+ /* non-terminal node ... */
+ if (*base == '\0') {
+ if ((path = (char *)malloc(strlen(np->name)+1)) == NULL)
+ return -oserror();
+ strcpy(path, np->name);
+ }
+ else {
+ if ((path = (char *)malloc(strlen(base)+strlen(np->name)+2)) == NULL)
+ return -oserror();
+ strcpy(path, base);
+ strcat(path, ".");
+ strcat(path, np->name);
+ }
+ if ((xp = findseen(path)) == NULL) {
+ snprintf(linebuf, sizeof(linebuf), "Cannot find definition for non-terminal node \"%s\" in name space",
+ path);
+ err(linebuf);
+ free(path);
+ return PM_ERR_PMNS;
+ }
+ np->first = xp->first;
+ /* node xp and name no longer needed */
+ free(xp->name);
+ free(xp);
+ seenpmid--;
+ i = attach(path, np);
+ free(path);
+ if (i != 0)
+ return i;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Create a fullpath name by walking from the current
+ * tree node up to the root.
+ */
+static int
+backname(__pmnsNode *np, char **name)
+{
+ __pmnsNode *xp;
+ char *p;
+ int nch;
+
+ nch = 0;
+ xp = np;
+ while (xp->parent != NULL) {
+ nch += (int)strlen(xp->name)+1;
+ xp = xp->parent;
+ }
+
+ if ((p = (char *)malloc(nch)) == NULL)
+ return -oserror();
+
+ p[--nch] = '\0';
+ xp = np;
+ while (xp->parent != NULL) {
+ int xl;
+
+ xl = (int)strlen(xp->name);
+ nch -= xl;
+ strncpy(&p[nch], xp->name, xl);
+ xp = xp->parent;
+ if (xp->parent == NULL)
+ break;
+ else
+ p[--nch] = '.';
+ }
+ *name = p;
+
+ return 0;
+}
+
+/*
+ * Fixup the parent pointers of the tree.
+ * Fill in the hash table with nodes from the tree.
+ * Hashing is done on pmid.
+ */
+static int
+backlink(__pmnsTree *tree, __pmnsNode *root, int dupok)
+{
+ __pmnsNode *np;
+ int status;
+
+ for (np = root->first; np != NULL; np = np->next) {
+ np->parent = root;
+ if (np->pmid != PM_ID_NULL) {
+ int i;
+ __pmnsNode *xp;
+ i = np->pmid % tree->htabsize;
+ for (xp = tree->htab[i]; xp != NULL; xp = xp->hash) {
+ if (xp->pmid == np->pmid && !dupok &&
+ (pmid_domain(np->pmid) != DYNAMIC_PMID || pmid_item(np->pmid) != 0)) {
+ char *nn, *xn;
+ char strbuf[20];
+ backname(np, &nn);
+ backname(xp, &xn);
+ snprintf(linebuf, sizeof(linebuf), "Duplicate metric id (%s) in name space for metrics \"%s\" and \"%s\"\n",
+ pmIDStr_r(np->pmid, strbuf, sizeof(strbuf)), nn, xn);
+ err(linebuf);
+ free(nn);
+ free(xn);
+ return PM_ERR_PMNS;
+ }
+ }
+ np->hash = tree->htab[i];
+ tree->htab[i] = np;
+ }
+ if ((status = backlink(tree, np, dupok)))
+ return status;
+ }
+ return 0;
+}
+
+/*
+ * Build up the whole tree by attaching the subtrees
+ * from the seen list.
+ * Create the hash table keyed on pmid.
+ *
+ */
+static int
+pass2(int dupok)
+{
+ __pmnsNode *np;
+ int status;
+
+ lineno = -1;
+
+ main_pmns = (__pmnsTree*)malloc(sizeof(*main_pmns));
+ if (main_pmns == NULL) {
+ return -oserror();
+ }
+
+ /* Get the root subtree out of the seen list */
+ if ((main_pmns->root = findseen("root")) == NULL) {
+ err("No name space entry for \"root\"");
+ return PM_ERR_PMNS;
+ }
+
+ if (findseen("root") != NULL) {
+ err("Multiple name space entries for \"root\"");
+ return PM_ERR_PMNS;
+ }
+
+ /* Build up main tree from subtrees in seen-list */
+ if ((status = attach("", main_pmns->root)))
+ return status;
+
+ /* Make sure all subtrees have been used in the main tree */
+ for (np = seen; np != NULL; np = np->next) {
+ snprintf(linebuf, sizeof(linebuf), "Disconnected subtree (\"%s\") in name space", np->name);
+ err(linebuf);
+ status = PM_ERR_PMNS;
+ }
+ if (status)
+ return status;
+
+ main_pmns->symbol = NULL;
+ main_pmns->contiguous = 0;
+ main_pmns->mark_state = UNKNOWN_MARK_STATE;
+
+ return __pmFixPMNSHashTab(main_pmns, seenpmid, dupok);
+}
+
+
+/*
+ * clear/set the "mark" bit used by pmTrimNameSpace, for all pmids
+ */
+static void
+mark_all(__pmnsTree *pmns, int bit)
+{
+ int i;
+ __pmnsNode *np;
+ __pmnsNode *pp;
+
+ if (pmns->mark_state == bit)
+ return;
+
+ pmns->mark_state = bit;
+ for (i = 0; i < pmns->htabsize; i++) {
+ for (np = pmns->htab[i]; np != NULL; np = np->hash) {
+ for (pp = np ; pp != NULL; pp = pp->parent) {
+ if (bit)
+ pp->pmid |= MARK_BIT;
+ else
+ pp->pmid &= ~MARK_BIT;
+ }
+ }
+ }
+}
+
+/*
+ * clear/set the "mark" bit used by pmTrimNameSpace, for one pmid, and
+ * for all parent nodes on the path to the root of the PMNS
+ */
+static void
+mark_one(__pmnsTree *pmns, pmID pmid, int bit)
+{
+ __pmnsNode *np;
+
+ if (pmns->mark_state == bit)
+ return;
+
+ pmns->mark_state = UNKNOWN_MARK_STATE;
+ for (np = pmns->htab[pmid % pmns->htabsize]; np != NULL; np = np->hash) {
+ if ((np->pmid & PMID_MASK) == (pmid & PMID_MASK)) {
+ for ( ; np != NULL; np = np->parent) {
+ if (bit)
+ np->pmid |= MARK_BIT;
+ else
+ np->pmid &= ~MARK_BIT;
+ }
+ return;
+ }
+ }
+}
+
+
+/*
+ * Create a new empty PMNS for Adding nodes to.
+ * Use with __pmAddPMNSNode() and __pmFixPMNSHashTab()
+ */
+int
+__pmNewPMNS(__pmnsTree **pmns)
+{
+ __pmnsTree *t = NULL;
+ __pmnsNode *np = NULL;
+
+ t = (__pmnsTree*)malloc(sizeof(*main_pmns));
+ if (t == NULL)
+ return -oserror();
+
+ /* Insert the "root" node first */
+ if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL) {
+ free(t);
+ return -oserror();
+ }
+ np->pmid = PM_ID_NULL;
+ np->parent = np->first = np->hash = np->next = NULL;
+ np->name = strdup("root");
+ if (np->name == NULL) {
+ free(t);
+ free(np);
+ return -oserror();
+ }
+
+ t->root = np;
+ t->htab = NULL;
+ t->htabsize = 0;
+ t->symbol = NULL;
+ t->contiguous = 0;
+ t->mark_state = UNKNOWN_MARK_STATE;
+
+ *pmns = t;
+ return 0;
+}
+
+/*
+ * Go through the tree and build a hash table.
+ * Fix up parent links while we're there.
+ * Unmark all nodes.
+ */
+int
+__pmFixPMNSHashTab(__pmnsTree *tree, int numpmid, int dupok)
+{
+ int sts;
+ int htabsize = numpmid/5;
+
+ /*
+ * make the average hash list no longer than 5, and the number
+ * of hash table entries not a multiple of 2, 3 or 5
+ */
+ if (htabsize % 2 == 0) htabsize++;
+ if (htabsize % 3 == 0) htabsize += 2;
+ if (htabsize % 5 == 0) htabsize += 2;
+ tree->htabsize = htabsize;
+ tree->htab = (__pmnsNode **)calloc(htabsize, sizeof(__pmnsNode *));
+ if (tree->htab == NULL)
+ return -oserror();
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((sts = backlink(tree, tree->root, dupok)) < 0) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ mark_all(tree, 0);
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+
+/*
+ * Add a new node for fullpath, name, with pmid.
+ * Does NOT update the hash table;
+ * need to call __pmFixPMNSHashTab() for that.
+ * Recursive routine.
+ */
+
+static int
+AddPMNSNode(__pmnsNode *root, int pmid, const char *name)
+{
+ __pmnsNode *np = NULL;
+ const char *tail;
+ int nch;
+
+ /* Traverse until '.' or '\0' */
+ for (tail = name; *tail && *tail != '.'; tail++)
+ ;
+
+ nch = (int)(tail - name);
+
+ /* Compare name with all the child nodes */
+ for (np = root->first; np != NULL; np = np->next) {
+ if (strncmp(name, np->name, (int)nch) == 0 && np->name[(int)nch] == '\0')
+ break;
+ }
+
+ if (np == NULL) { /* no match with child */
+ __pmnsNode *parent_np = root;
+ const char *name_p = name;
+ int is_first = 1;
+
+ /* create nodes until reach leaf */
+
+ for ( ; ; ) {
+ if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL)
+ return -oserror();
+
+ /* fixup name */
+ if ((np->name = (char *)malloc(nch+1)) == NULL) {
+ free(np);
+ return -oserror();
+ }
+ strncpy(np->name, name_p, nch);
+ np->name[nch] = '\0';
+
+ /* fixup some links */
+ np->first = np->hash = np->next = NULL;
+ np->parent = parent_np;
+ if (is_first) {
+ is_first = 0;
+ if (root->first != NULL) {
+ /* chuck new node at front of list */
+ np->next = root->first;
+ }
+ }
+ parent_np->first = np;
+
+ /* at this stage, assume np is a non-leaf */
+ np->pmid = PM_ID_NULL;
+
+ parent_np = np;
+ if (*tail == '\0')
+ break;
+ name_p += nch+1; /* skip over node + dot */
+ for (tail = name_p; *tail && *tail != '.'; tail++)
+ ;
+ nch = (int)(tail - name_p);
+ }
+
+ np->pmid = pmid; /* set pmid of leaf node */
+ return 0;
+ }
+ else if (*tail == '\0') { /* matched with whole path */
+ if (np->pmid != pmid)
+ return PM_ERR_PMID;
+ else
+ return 0;
+ }
+ else {
+ return AddPMNSNode(np, pmid, tail+1); /* try matching with rest of pathname */
+ }
+
+}
+
+
+/*
+ * Add a new node for fullpath, name, with pmid.
+ * NOTE: Need to call __pmFixPMNSHashTab() to update hash table
+ * when have finished adding nodes.
+ */
+int
+__pmAddPMNSNode(__pmnsTree *tree, int pmid, const char *name)
+{
+ if (tree->contiguous) {
+ /* Cannot add node to contiguously allocated tree! */
+ return -EINVAL;
+ }
+
+ return AddPMNSNode(tree->root, pmid, name);
+}
+
+/*
+ * fsa for parser
+ *
+ * old token new
+ * 0 NAME 1
+ * 0 PATH 1
+ * 1 LBRACE 2
+ * 2 NAME 3
+ * 2 RBRACE 0
+ * 3 NAME 3
+ * 3 PMID 2
+ * 3 RBRACE 0
+ */
+static int
+loadascii(int dupok)
+{
+ int state = 0;
+ int type;
+ __pmnsNode *np = NULL; /* pander to gcc */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS)
+ fprintf(stderr, "loadascii(file=%s)\n", fname);
+#endif
+
+
+ /* do some resets */
+ lex(1); /* reset analyzer */
+ seen = NULL; /* make seen-list empty */
+ seenpmid = 0;
+
+
+ if (access(fname, R_OK) == -1) {
+ snprintf(linebuf, sizeof(linebuf), "Cannot open \"%s\"", fname);
+ err(linebuf);
+ return -oserror();
+ }
+ lineno = 1;
+
+ while ((type = lex(0)) > 0) {
+ switch (state) {
+
+ case 0:
+ if (type != NAME && type != PATH) {
+ err("Expected NAME or PATH");
+ return PM_ERR_PMNS;
+ }
+ state = 1;
+ break;
+
+ case 1:
+ if (type != LBRACE) {
+ err("{ expected");
+ return PM_ERR_PMNS;
+ }
+ state = 2;
+ break;
+
+ case 2:
+ if (type == NAME) {
+ state = 3;
+ }
+ else if (type == RBRACE) {
+ state = 0;
+ }
+ else {
+ err("Expected NAME or }");
+ return PM_ERR_PMNS;
+ }
+ break;
+
+ case 3:
+ if (type == NAME) {
+ state = 3;
+ }
+ else if (type == PMID) {
+ np->pmid = tokpmid;
+ state = 2;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ char strbuf[20];
+ fprintf(stderr, "pmLoadNameSpace: %s -> %s\n",
+ np->name, pmIDStr_r(np->pmid, strbuf, sizeof(strbuf)));
+ }
+#endif
+ }
+ else if (type == RBRACE) {
+ state = 0;
+ }
+ else {
+ err("Expected NAME, PMID or }");
+ return PM_ERR_PMNS;
+ }
+ break;
+
+ }
+
+ if (state == 1 || state == 3) {
+ if ((np = (__pmnsNode *)malloc(sizeof(*np))) == NULL)
+ return -oserror();
+ seenpmid++;
+ if ((np->name = (char *)malloc(strlen(tokbuf)+1)) == NULL) {
+ free(np);
+ return -oserror();
+ }
+ strcpy(np->name, tokbuf);
+ np->first = np->hash = np->next = np->parent = NULL;
+ np->pmid = PM_ID_NULL;
+ if (state == 1) {
+ np->next = seen;
+ seen = np;
+ }
+ else {
+ if (seen->hash)
+ seen->hash->next = np;
+ else
+ seen->first = np;
+ seen->hash = np;
+ }
+ }
+ else if (state == 0) {
+ if (seen) {
+ __pmnsNode *xp;
+
+ for (np = seen->first; np != NULL; np = np->next) {
+ for (xp = np->next; xp != NULL; xp = xp->next) {
+ if (strcmp(xp->name, np->name) == 0) {
+ snprintf(linebuf, sizeof(linebuf), "Duplicate name \"%s\" in subtree for \"%s\"\n",
+ np->name, seen->name);
+ err(linebuf);
+ return PM_ERR_PMNS;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (type == 0)
+ type = pass2(dupok);
+
+
+ if (type == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS)
+ fprintf(stderr, "Loaded ASCII PMNS\n");
+#endif
+ }
+
+ return type;
+}
+
+static const char *
+getfname(const char *filename)
+{
+ /*
+ * 0xffffffff is there to maintain backwards compatibility with PCP 1.0
+ */
+ if (filename == PM_NS_DEFAULT || (__psint_t)filename == 0xffffffff) {
+ char *def_pmns;
+
+ def_pmns = getenv("PMNS_DEFAULT");
+ if (def_pmns != NULL) {
+ /* get default PMNS name from environment */
+ return def_pmns;
+ }
+ else {
+ static char repname[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(repname, sizeof(repname), "%s%c" "pmns" "%c" "root",
+ pmGetConfig("PCP_VAR_DIR"), sep, sep);
+ return repname;
+ }
+ }
+ return filename;
+}
+
+int
+__pmHasPMNSFileChanged(const char *filename)
+{
+ const char *f;
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ f = getfname(filename);
+ if (f == NULL) {
+ /* error encountered -> must have changed :) */
+ sts = 1;
+ goto done;
+ }
+
+ /* if still using same filename ... */
+ if (strcmp(f, fname) == 0) {
+ struct stat statbuf;
+
+ if (stat(f, &statbuf) == 0) {
+ /* If the modification times have changed */
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr,
+ "__pmHasPMNSFileChanged(%s) -> %s last=%d now=%d\n",
+ filename == PM_NS_DEFAULT ||
+ (__psint_t)filename == 0xffffffff ?
+ "PM_NS_DEFAULT" : filename,
+ f, (int)last_mtim, (int)statbuf.st_mtime);
+ }
+#endif
+ sts = (statbuf.st_mtime == last_mtim) ? 0 : 1;
+ goto done;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr,
+ "__pmHasPMNSFileChanged(%s) -> %s last=%d.%09ld now=%d.%09ld\n",
+ filename == PM_NS_DEFAULT ||
+ (__psint_t)filename == 0xffffffff ?
+ "PM_NS_DEFAULT" : filename,
+ f, (int)last_mtim.tv_sec, last_mtim.tv_nsec,
+ (int)statbuf.st_mtimespec.tv_sec,
+ statbuf.st_mtimespec.tv_nsec);
+ }
+#endif
+ sts = (statbuf.st_mtimespec.tv_sec == last_mtim.tv_sec &&
+ statbuf.st_mtimespec.tv_nsec == last_mtim.tv_nsec) ? 0 : 1;
+ goto done;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr,
+ "__pmHasPMNSFileChanged(%s) -> %s last=%d.%09ld now=%d.%09ld\n",
+ filename == PM_NS_DEFAULT ||
+ (__psint_t)filename == 0xffffffff ?
+ "PM_NS_DEFAULT" : filename,
+ f, (int)last_mtim.tv_sec, last_mtim.tv_nsec,
+ (int)statbuf.st_mtim.tv_sec, statbuf.st_mtim.tv_nsec);
+ }
+#endif
+ sts = (statbuf.st_mtim.tv_sec == last_mtim.tv_sec &&
+ (statbuf.st_mtim.tv_nsec == last_mtim.tv_nsec)) ? 0 : 1;
+ goto done;
+#else
+!bozo!
+#endif
+ }
+ else {
+ /* error encountered -> must have changed */
+ sts = 1;
+ goto done;
+ }
+ }
+ /* different filenames at least */
+ sts = 1;
+
+done:
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+static int
+load(const char *filename, int dupok)
+{
+ int i = 0;
+
+ if (main_pmns != NULL) {
+ if (export) {
+ export = 0;
+
+ /*
+ * drop the loaded PMNS ... huge memory leak, but it is
+ * assumed the caller has saved the previous PMNS after calling
+ * __pmExportPMNS() ... only user of this service is pmnsmerge
+ */
+ main_pmns = NULL;
+ }
+ else {
+ return PM_ERR_DUPPMNS;
+ }
+ }
+
+ strcpy(fname, getfname(filename));
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS)
+ fprintf(stderr, "load(name=%s, dupok=%d) lic case=%d fname=%s\n",
+ filename, dupok, i, fname);
+#endif
+
+ /* Note modification time of pmns file */
+ {
+ struct stat statbuf;
+
+ if (stat(fname, &statbuf) == 0) {
+#if defined(HAVE_ST_MTIME_WITH_E)
+ last_mtim = statbuf.st_mtime; /* possible struct assignment */
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ last_mtim = statbuf.st_mtimespec; /* possible struct assignment */
+#else
+ last_mtim = statbuf.st_mtim; /* possible struct assignment */
+#endif
+ }
+ }
+
+ /*
+ * load ASCII PMNS
+ */
+ return loadascii(dupok);
+}
+
+/*
+ * just for pmnsmerge to use
+ */
+__pmnsTree*
+__pmExportPMNS(void)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ export = 1;
+ PM_UNLOCK(__pmLock_libpcp);
+
+ /*
+ * Warning: this is _not_ thread-safe, and cannot be guarded/protected
+ */
+ return main_pmns;
+}
+
+/*
+ * Find and return the named node in the tree, root.
+ */
+static __pmnsNode *
+locate(const char *name, __pmnsNode *root)
+{
+ const char *tail;
+ ptrdiff_t nch;
+ __pmnsNode *np;
+
+ /* Traverse until '.' or '\0' */
+ for (tail = name; *tail && *tail != '.'; tail++)
+ ;
+
+ nch = tail - name;
+
+ /* Compare name with all the child nodes */
+ for (np = root->first; np != NULL; np = np->next) {
+ if (strncmp(name, np->name, (int)nch) == 0 && np->name[(int)nch] == '\0' &&
+ (np->pmid & MARK_BIT) == 0)
+ break;
+ }
+
+ if (np == NULL) /* no match with child */
+ return NULL;
+ else if (*tail == '\0') /* matched with whole path */
+ return np;
+ else
+ return locate(tail+1, np); /* try matching with rest of pathname */
+}
+
+/*
+ * PMAPI routines from here down
+ */
+
+/*
+ * As of PCP 3.6, there is _only_ the ASCII version of the PMNS
+ */
+int
+pmLoadNameSpace(const char *filename)
+{
+ return pmLoadASCIINameSpace(filename, 0);
+}
+
+int
+pmLoadASCIINameSpace(const char *filename, int dupok)
+{
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ havePmLoadCall = 1;
+ sts = load(filename, dupok);
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+/*
+ * Assume that each node has been malloc'ed separately.
+ * This is the case for an ASCII loaded PMNS.
+ * Traverse entire tree and free each node.
+ */
+static void
+FreeTraversePMNS(__pmnsNode *this)
+{
+ __pmnsNode *np, *next;
+
+ if (this == NULL)
+ return;
+
+ /* Free child sub-trees */
+ for (np = this->first; np != NULL; np = next) {
+ next = np->next;
+ FreeTraversePMNS(np);
+ }
+
+ free(this->name);
+ free(this);
+}
+
+void
+__pmFreePMNS(__pmnsTree *pmns)
+{
+ if (pmns != NULL) {
+ if (pmns->contiguous) {
+ free(pmns->root);
+ free(pmns->htab);
+ free(pmns->symbol);
+ }
+ else {
+ free(pmns->htab);
+ FreeTraversePMNS(pmns->root);
+ }
+
+ free(pmns);
+ }
+}
+
+void
+pmUnloadNameSpace(void)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ havePmLoadCall = 0;
+ __pmFreePMNS(main_pmns);
+ main_pmns = NULL;
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+int
+pmLookupName(int numpmid, char *namelist[], pmID pmidlist[])
+{
+ int pmns_location = GetLocation();
+ int sts = 0;
+ __pmContext *ctxp;
+ int c_type;
+ int lsts;
+ int ctx;
+ int i;
+ int nfail = 0;
+
+ if (numpmid < 1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "pmLookupName(%d, ...) bad numpmid!\n", numpmid);
+ }
+#endif
+ return PM_ERR_TOOSMALL;
+ }
+
+ PM_INIT_LOCKS();
+
+ ctx = lsts = pmWhichContext();
+ if (lsts >= 0) {
+ ctxp = __pmHandleToPtr(ctx);
+ c_type = ctxp->c_type;
+ }
+ else {
+ ctxp = NULL;
+ /*
+ * set c_type to be NONE of PM_CONTEXT_HOST, PM_CONTEXT_ARCHIVE
+ * nor PM_CONTEXT_LOCAL
+ */
+ c_type = 0;
+ }
+ if (ctxp != NULL && c_type == PM_CONTEXT_LOCAL && PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) {
+ /* Local context requires single-threaded applications */
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_THREAD;
+ }
+
+ /*
+ * Guarantee that derived metrics preparation is done, for all possible
+ * paths through this routine which might end at "Try derived metrics.."
+ * below.
+ */
+ memset(pmidlist, PM_ID_NULL, numpmid * sizeof(pmID));
+
+ if (pmns_location < 0) {
+ if (ctxp != NULL)
+ PM_UNLOCK(ctxp->c_lock);
+ sts = pmns_location;
+ /* only hope is derived metrics ... set up for this */
+ nfail += numpmid;
+ }
+ else if (pmns_location == PMNS_LOCAL) {
+ char *xname;
+ char *xp;
+ __pmnsNode *np;
+
+ if (ctxp != NULL)
+ PM_UNLOCK(ctxp->c_lock);
+ for (i = 0; i < numpmid; i++) {
+ /*
+ * if we locate the name and it is a leaf in the PMNS
+ * this is good
+ */
+ PM_LOCK(__pmLock_libpcp);
+ np = locate(namelist[i], curr_pmns->root);
+ PM_UNLOCK(__pmLock_libpcp);
+ if (np != NULL ) {
+ if (np->first == NULL)
+ pmidlist[i] = np->pmid;
+ else {
+ sts = PM_ERR_NONLEAF;
+ nfail++;
+ }
+ continue;
+ }
+ nfail++;
+ /*
+ * did not match name in PMNS ... try for prefix matching
+ * the name to the root of a dynamic subtree of the PMNS,
+ * or possibly we're using a local context and then we may
+ * be able to ship request to PMDA
+ */
+ xname = strdup(namelist[i]);
+ if (xname == NULL) {
+ __pmNoMem("pmLookupName", strlen(namelist[i])+1, PM_RECOV_ERR);
+ sts = -oserror();
+ continue;
+ }
+ while ((xp = rindex(xname, '.')) != NULL) {
+ *xp = '\0';
+ lsts = 0;
+ PM_LOCK(__pmLock_libpcp);
+ np = locate(xname, curr_pmns->root);
+ PM_UNLOCK(__pmLock_libpcp);
+ if (np != NULL && np->first == NULL &&
+ pmid_domain(np->pmid) == DYNAMIC_PMID &&
+ pmid_item(np->pmid) == 0) {
+ /* root of dynamic subtree */
+ if (c_type == PM_CONTEXT_LOCAL) {
+ /* have PM_CONTEXT_LOCAL ... ship request to PMDA */
+ int domain = ((__pmID_int *)&np->pmid)->cluster;
+ __pmDSO *dp;
+ if ((dp = __pmLookupDSO(domain)) == NULL) {
+ if (sts >= 0) sts = PM_ERR_NOAGENT;
+ break;
+ }
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ lsts = dp->dispatch.version.four.pmid(namelist[i], &pmidlist[i], dp->dispatch.version.four.ext);
+ if (lsts >= 0)
+ nfail--;
+
+ break;
+ }
+ }
+ else {
+ /* No PM_LOCAL_CONTEXT, use PMID from PMNS */
+ pmidlist[i] = np->pmid;
+ nfail--;
+ break;
+ }
+ }
+ }
+ free(xname);
+ }
+
+ sts = (sts == 0 ? numpmid - nfail : sts);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ int i;
+ char strbuf[20];
+ fprintf(stderr, "pmLookupName(%d, ...) using local PMNS returns %d and ...\n",
+ numpmid, sts);
+ for (i = 0; i < numpmid; i++) {
+ fprintf(stderr, " name[%d]: \"%s\"", i, namelist[i]);
+ if (sts >= 0)
+ fprintf(stderr, " PMID: 0x%x %s",
+ pmidlist[i], pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf)));
+ fputc('\n', stderr);
+ }
+ }
+#endif
+ }
+ else {
+ /*
+ * PMNS_REMOTE so there must be a current host context
+ */
+ assert(c_type == PM_CONTEXT_HOST);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "pmLookupName: request_names ->");
+ for (i = 0; i < numpmid; i++)
+ fprintf(stderr, " [%d] %s", i, namelist[i]);
+ fputc('\n', stderr);
+ }
+#endif
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ sts = __pmSendNameList(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ numpmid, namelist, NULL);
+ if (sts < 0)
+ sts = __pmMapErrno(sts);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+
+ pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (sts == PDU_PMNS_IDS) {
+ /* Note:
+ * pmLookupName may return an error even though
+ * it has a valid list of ids.
+ * This is why we need op_status.
+ */
+ int op_status;
+ sts = __pmDecodeIDList(pb, numpmid, pmidlist, &op_status);
+ if (sts >= 0)
+ sts = op_status;
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ }
+ else if (sts != PM_ERR_TIMEOUT) {
+ sts = PM_ERR_IPC;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts >= 0)
+ nfail = numpmid - sts;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "pmLookupName: receive_names <-");
+ if (sts >= 0) {
+ for (i = 0; i < numpmid; i++)
+ fprintf(stderr, " [%d] %s", i, pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf)));
+ fputc('\n', stderr);
+ }
+ else
+ fprintf(stderr, " %s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+#endif
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ if (sts < 0 || nfail > 0) {
+ /*
+ * Try derived metrics for any remaining unknown pmids.
+ * The return status is a little tricky ... prefer the status
+ * from above unless all of the remaining unknown PMIDs are
+ * resolved by __dmgetpmid() in which case success (numpmid)
+ * is the right return status
+ */
+ nfail = 0;
+ for (i = 0; i < numpmid; i++) {
+ if (pmidlist[i] == PM_ID_NULL) {
+ lsts = __dmgetpmid(namelist[i], &pmidlist[i]);
+ if (lsts < 0) {
+ nfail++;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char strbuf[20];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__dmgetpmid: metric \"%s\" -> ", namelist[i]);
+ if (lsts < 0)
+ fprintf(stderr, "%s\n", pmErrStr_r(lsts, errmsg, sizeof(errmsg)));
+ else
+ fprintf(stderr, "PMID %s\n", pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf)));
+ }
+#endif
+ }
+ }
+ if (nfail == 0)
+ sts = numpmid;
+ }
+
+ /*
+ * special case for a single metric, PM_ERR_NAME is more helpful than
+ * returning 0 and having one PM_ID_NULL pmid
+ */
+ if (sts == 0 && numpmid == 1)
+ sts = PM_ERR_NAME;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "pmLookupName(%d, ...) -> ", numpmid);
+ if (sts < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s\n", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ }
+ else
+ fprintf(stderr, "%d\n", sts);
+ }
+#endif
+
+ return sts;
+}
+
+static int
+GetChildrenStatusRemote(__pmContext *ctxp, const char *name,
+ char ***offspring, int **statuslist)
+{
+ int n;
+
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ n = __pmSendChildReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp),
+ name, statuslist == NULL ? 0 : 1);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (n == PDU_PMNS_NAMES) {
+ int numnames;
+ n = __pmDecodeNameList(pb, &numnames, offspring, statuslist);
+ if (n >= 0)
+ n = numnames;
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+
+ return n;
+}
+
+static void
+stitch_list(int *num, char ***offspring, int **statuslist, int x_num, char **x_offspring, int *x_statuslist)
+{
+ /*
+ * so this gets tricky ... need to stitch the additional metrics
+ * (derived metrics or dynamic metrics) at the end of the existing
+ * metrics (if any) after removing any duplicates (!) ... and honour
+ * the bizarre pmGetChildren contract in terms of malloc'ing the
+ * result arrays
+ */
+ int n_num;
+ char **n_offspring;
+ int *n_statuslist = NULL;
+ int i;
+ int j;
+ char *q;
+ size_t need;
+
+ if (x_num == 0) {
+ /* nothing to do */
+ return;
+ }
+ if (*num > 0) {
+ /* appending */
+ n_num = *num + x_num;
+ }
+ else {
+ /* initializing */
+ n_num = x_num;
+ }
+
+ for (i = 0; i < x_num; i++) {
+ for (j = 0; j < *num; j++) {
+ if (strcmp(x_offspring[i], (*offspring)[j]) == 0) {
+ /* duplicate ... bugger */
+ n_num--;
+ free(x_offspring[i]);
+ x_offspring[i] = NULL;
+ break;
+ }
+ }
+ }
+
+ need = n_num*sizeof(char *);
+ for (j = 0; j < *num; j++) {
+ need += strlen((*offspring)[j]) + 1;
+ }
+ for (i = 0; i < x_num; i++) {
+ if (x_offspring[i] != NULL) {
+ need += strlen(x_offspring[i]) + 1;
+ }
+ }
+ if ((n_offspring = (char **)malloc(need)) == NULL) {
+ __pmNoMem("pmGetChildrenStatus: n_offspring", need, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if (statuslist != NULL) {
+ if ((n_statuslist = (int *)malloc(n_num*sizeof(n_statuslist[0]))) == NULL) {
+ __pmNoMem("pmGetChildrenStatus: n_statuslist", n_num*sizeof(n_statuslist[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+ q = (char *)&n_offspring[n_num];
+ for (j = 0; j < *num; j++) {
+ n_offspring[j] = q;
+ strcpy(q, (*offspring)[j]);
+ q += strlen(n_offspring[j]) + 1;
+ if (statuslist != NULL)
+ n_statuslist[j] = (*statuslist)[j];
+ }
+ for (i = 0; i < x_num; i++) {
+ if (x_offspring[i] != NULL) {
+ n_offspring[j] = q;
+ strcpy(q, x_offspring[i]);
+ q += strlen(n_offspring[j]) + 1;
+ if (statuslist != NULL)
+ n_statuslist[j] = x_statuslist[i];
+ j++;
+ }
+ }
+ if (*num > 0) {
+ free(*offspring);
+ if (statuslist != NULL)
+ free(*statuslist);
+ }
+ *num = n_num;
+ if (statuslist != NULL)
+ *statuslist = n_statuslist;
+ *offspring = n_offspring;
+}
+
+/*
+ * It is allowable to pass in a statuslist arg of NULL. It is therefore
+ * important to check that this is not NULL before accessing it.
+ */
+int
+pmGetChildrenStatus(const char *name, char ***offspring, int **statuslist)
+{
+ int *status = NULL;
+ int pmns_location = GetLocation();
+ int num;
+ int dm_num;
+ char **dm_offspring;
+ int *dm_statuslist;
+ int sts;
+ int ctx;
+ __pmContext *ctxp;
+
+ if (pmns_location < 0)
+ return pmns_location;
+
+ if (name == NULL)
+ return PM_ERR_NAME;
+
+ PM_INIT_LOCKS();
+
+ ctx = sts = pmWhichContext();
+ if (sts >= 0)
+ ctxp = __pmHandleToPtr(sts);
+ else
+ ctxp = NULL;
+
+ if (pmns_location == PMNS_LOCAL) {
+ __pmnsNode *np;
+ __pmnsNode *tnp;
+ int i;
+ int need;
+ char **result;
+ char *p;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "pmGetChildren(name=\"%s\") [local]\n", name);
+ }
+#endif
+
+ /* avoids ambiguity, for errors and leaf nodes */
+ *offspring = NULL;
+ num = 0;
+ if (statuslist)
+ *statuslist = NULL;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (*name == '\0')
+ np = curr_pmns->root; /* use "" to name the root of the PMNS */
+ else
+ np = locate(name, curr_pmns->root);
+ if (np == NULL) {
+ if (ctxp != NULL && ctxp->c_type == PM_CONTEXT_LOCAL) {
+ /*
+ * No match in PMNS and using PM_CONTEXT_LOCAL so for
+ * dynamic metrics, need to consider prefix matches back to
+ * the root on the PMNS to find a possible root of a dynamic
+ * subtree, and hence the domain of the responsible PMDA
+ */
+ char *xname = strdup(name);
+ char *xp;
+ if (xname == NULL) {
+ __pmNoMem("pmGetChildrenStatus", strlen(name)+1, PM_RECOV_ERR);
+ num = -oserror();
+ PM_UNLOCK(__pmLock_libpcp);
+ goto report;
+ }
+ while ((xp = rindex(xname, '.')) != NULL) {
+ *xp = '\0';
+ np = locate(xname, curr_pmns->root);
+ if (np != NULL && np->first == NULL &&
+ pmid_domain(np->pmid) == DYNAMIC_PMID &&
+ pmid_item(np->pmid) == 0) {
+ int domain = ((__pmID_int *)&np->pmid)->cluster;
+ __pmDSO *dp;
+ if ((dp = __pmLookupDSO(domain)) == NULL) {
+ num = PM_ERR_NOAGENT;
+ free(xname);
+ PM_UNLOCK(__pmLock_libpcp);
+ goto check;
+ }
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ char **x_offspring = NULL;
+ int *x_statuslist = NULL;
+ int x_num;
+ x_num = dp->dispatch.version.four.children(
+ name, 0, &x_offspring, &x_statuslist,
+ dp->dispatch.version.four.ext);
+ if (x_num < 0)
+ num = x_num;
+ else if (x_num > 0) {
+ stitch_list(&num, offspring, statuslist,
+ x_num, x_offspring, x_statuslist);
+ free(x_offspring);
+ free(x_statuslist);
+ }
+ free(xname);
+ PM_UNLOCK(__pmLock_libpcp);
+ goto check;
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ num = PM_ERR_NAME;
+ free(xname);
+ PM_UNLOCK(__pmLock_libpcp);
+ goto check;
+ }
+ }
+ }
+ free(xname);
+ }
+ num = PM_ERR_NAME;
+ PM_UNLOCK(__pmLock_libpcp);
+ goto check;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ if (np != NULL && np->first == NULL) {
+ /*
+ * this is a leaf node ... if it is the root of a dynamic
+ * subtree of the PMNS and we have an existing context
+ * of type PM_CONTEXT_LOCAL than we should chase the
+ * relevant PMDA to provide the details
+ */
+ if (pmid_domain(np->pmid) == DYNAMIC_PMID &&
+ pmid_item(np->pmid) == 0) {
+ if (ctxp != NULL && ctxp->c_type == PM_CONTEXT_LOCAL) {
+ int domain = ((__pmID_int *)&np->pmid)->cluster;
+ __pmDSO *dp;
+ if ((dp = __pmLookupDSO(domain)) == NULL) {
+ num = PM_ERR_NOAGENT;
+ goto check;
+ }
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ char **x_offspring = NULL;
+ int *x_statuslist = NULL;
+ int x_num;
+ x_num = dp->dispatch.version.four.children(name, 0,
+ &x_offspring, &x_statuslist,
+ dp->dispatch.version.four.ext);
+ if (x_num < 0)
+ num = x_num;
+ else if (x_num > 0) {
+ stitch_list(&num, offspring, statuslist,
+ x_num, x_offspring, x_statuslist);
+ free(x_offspring);
+ free(x_statuslist);
+ }
+ goto check;
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ num = PM_ERR_NAME;
+ goto check;
+ }
+ }
+ }
+ num = 0;
+ goto check;
+ }
+
+ need = 0;
+ num = 0;
+
+ if (np != NULL) {
+ for (i = 0, tnp = np->first; tnp != NULL; tnp = tnp->next, i++) {
+ if ((tnp->pmid & MARK_BIT) == 0) {
+ num++;
+ need += sizeof(**offspring) + strlen(tnp->name) + 1;
+ }
+ }
+ }
+
+ if ((result = (char **)malloc(need)) == NULL) {
+ num = -oserror();
+ goto report;
+ }
+
+ if (statuslist != NULL) {
+ if ((status = (int *)malloc(num*sizeof(int))) == NULL) {
+ num = -oserror();
+ free(result);
+ goto report;
+ }
+ }
+
+ p = (char *)&result[num];
+
+ if (np != NULL) {
+ for (i = 0, tnp = np->first; tnp != NULL; tnp = tnp->next) {
+ if ((tnp->pmid & MARK_BIT) == 0) {
+ result[i] = p;
+ /*
+ * a name at the root of a dynamic metrics subtree
+ * needs some special handling ... they will have a
+ * "special" PMID, but need the status set to indicate
+ * they are not a leaf node of the PMNS
+ */
+ if (statuslist != NULL) {
+ if (pmid_domain(tnp->pmid) == DYNAMIC_PMID &&
+ pmid_item(tnp->pmid) == 0) {
+ status[i] = PMNS_NONLEAF_STATUS;
+ }
+ else
+ /* node has children? */
+ status[i] = (tnp->first == NULL ? PMNS_LEAF_STATUS : PMNS_NONLEAF_STATUS);
+ }
+ strcpy(result[i], tnp->name);
+ p += strlen(tnp->name) + 1;
+ i++;
+ }
+ }
+ }
+
+ *offspring = result;
+ if (statuslist != NULL)
+ *statuslist = status;
+ }
+ else {
+ /*
+ * PMNS_REMOTE so there must be a current host context
+ */
+ assert(ctxp != NULL && ctxp->c_type == PM_CONTEXT_HOST);
+ num = GetChildrenStatusRemote(ctxp, name, offspring, statuslist);
+ }
+
+check:
+ if (ctxp != NULL)
+ PM_UNLOCK(ctxp->c_lock);
+ /*
+ * see if there are derived metrics that qualify
+ */
+ dm_num = __dmchildren(name, &dm_offspring, &dm_statuslist);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DERIVE) {
+ char errmsg[PM_MAXERRMSGLEN];
+ if (num < 0)
+ fprintf(stderr, "pmGetChildren(name=\"%s\") no regular children (%s)", name, pmErrStr_r(num, errmsg, sizeof(errmsg)));
+ else
+ fprintf(stderr, "pmGetChildren(name=\"%s\") %d regular children", name, num);
+ if (dm_num < 0)
+ fprintf(stderr, ", no derived children (%s)\n", pmErrStr_r(dm_num, errmsg, sizeof(errmsg)));
+ else if (dm_num == 0)
+ fprintf(stderr, ", derived leaf\n");
+ else
+ fprintf(stderr, ", %d derived children\n", dm_num);
+ }
+#endif
+ if (dm_num > 0) {
+ stitch_list(&num, offspring, statuslist, dm_num, dm_offspring, dm_statuslist);
+ free(dm_offspring);
+ free(dm_statuslist);
+ }
+ else if (dm_num == 0 && num < 0) {
+ /* leaf node and derived metric */
+ num = 0;
+ }
+
+report:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "pmGetChildren(name=\"%s\") -> ", name);
+ if (num == 0)
+ fprintf(stderr, "leaf\n");
+ else if (num > 0) {
+ if (statuslist != NULL)
+ __pmDumpNameAndStatusList(stderr, num, *offspring, *statuslist);
+ else
+ __pmDumpNameList(stderr, num, *offspring);
+ }
+ else {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s\n", pmErrStr_r(num, errmsg, sizeof(errmsg)));
+ }
+ }
+#endif
+
+ return num;
+}
+
+int
+pmGetChildren(const char *name, char ***offspring)
+{
+ return pmGetChildrenStatus(name, offspring, NULL);
+}
+
+static int
+request_namebypmid(__pmContext *ctxp, pmID pmid)
+{
+ int n;
+
+ n = __pmSendIDList(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), 1, &pmid, 0);
+ if (n < 0)
+ n = __pmMapErrno(n);
+ return n;
+}
+
+static int
+receive_namesbyid(__pmContext *ctxp, char ***namelist)
+{
+ int n;
+ __pmPDU *pb;
+ int pinpdu;
+
+ pinpdu = n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+
+ if (n == PDU_PMNS_NAMES) {
+ int numnames;
+
+ n = __pmDecodeNameList(pb, &numnames, namelist, NULL);
+ if (n >= 0)
+ n = numnames;
+ }
+ else if (n == PDU_ERROR)
+ __pmDecodeError(pb, &n);
+ else if (n != PM_ERR_TIMEOUT)
+ n = PM_ERR_IPC;
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ return n;
+}
+
+static int
+receive_a_name(__pmContext *ctxp, char **name)
+{
+ int n;
+ char **namelist;
+
+ if ((n = receive_namesbyid(ctxp, &namelist)) >= 0) {
+ char *newname = strdup(namelist[0]);
+ free(namelist);
+ if (newname == NULL) {
+ n = -oserror();
+ } else {
+ *name = newname;
+ n = 0;
+ }
+ }
+
+ return n;
+}
+
+int
+pmNameID(pmID pmid, char **name)
+{
+ int pmns_location = GetLocation();
+
+ if (pmns_location < 0)
+ return pmns_location;
+
+ PM_INIT_LOCKS();
+
+ if (pmns_location == PMNS_LOCAL) {
+ __pmnsNode *np;
+ PM_LOCK(__pmLock_libpcp);
+ for (np = curr_pmns->htab[pmid % curr_pmns->htabsize]; np != NULL; np = np->hash) {
+ if (np->pmid == pmid) {
+ int sts;
+ if (pmid_domain(np->pmid) != DYNAMIC_PMID ||
+ pmid_item(np->pmid) != 0)
+ sts = backname(np, name);
+ else
+ sts = PM_ERR_PMID;
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ /* not found so far, try derived metrics ... */
+ return __dmgetname(pmid, name);
+ }
+
+ else {
+ /* assume PMNS_REMOTE */
+ int n;
+ __pmContext *ctxp;
+
+ /* As we have PMNS_REMOTE there must be a current host context */
+ if ((n = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(n)) == NULL)
+ return PM_ERR_NOCONTEXT;
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ if ((n = request_namebypmid(ctxp, pmid)) >= 0) {
+ n = receive_a_name(ctxp, name);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ PM_UNLOCK(ctxp->c_lock);
+ if (n >= 0) return n;
+ return __dmgetname(pmid, name);
+ }
+}
+
+int
+pmNameAll(pmID pmid, char ***namelist)
+{
+ int pmns_location = GetLocation();
+ char **tmp = NULL;
+ int n = 0;
+ int len = 0;
+ char *sp;
+
+ if (pmns_location < 0)
+ return pmns_location;
+
+ PM_INIT_LOCKS();
+
+ if (pmns_location == PMNS_LOCAL) {
+ __pmnsNode *np;
+ int sts = 0;
+ int i;
+
+ if (pmid_domain(pmid) == DYNAMIC_PMID && pmid_item(pmid) == 0) {
+ /*
+ * pmid is for the root of a dynamic subtree in the PMNS ...
+ * there is no matching leaf name
+ */
+ return PM_ERR_PMID;
+ }
+ PM_LOCK(__pmLock_libpcp);
+ for (np = curr_pmns->htab[pmid % curr_pmns->htabsize]; np != NULL; np = np->hash) {
+ if (np->pmid == pmid) {
+ n++;
+ if ((tmp = (char **)realloc(tmp, n * sizeof(tmp[0]))) == NULL) {
+ sts = -oserror();
+ break;
+ }
+ if ((sts = backname(np, &tmp[n-1])) < 0) {
+ /* error, ... free any partial allocations */
+ for (i = n-2; i >= 0; i--)
+ free(tmp[i]);
+ free(tmp);
+ break;
+ }
+ len += strlen(tmp[n-1])+1;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ if (sts < 0)
+ return sts;
+
+ if (n == 0)
+ goto try_derive;
+
+ len += n * sizeof(tmp[0]);
+ if ((tmp = (char **)realloc(tmp, len)) == NULL)
+ return -oserror();
+
+ sp = (char *)&tmp[n];
+ for (i = 0; i < n; i++) {
+ strcpy(sp, tmp[i]);
+ free(tmp[i]);
+ tmp[i] = sp;
+ sp += strlen(sp)+1;
+ }
+
+ *namelist = tmp;
+ return n;
+ }
+
+ else {
+ /* assume PMNS_REMOTE */
+ int n;
+ __pmContext *ctxp;
+
+ /* As we have PMNS_REMOTE there must be a current host context */
+ if ((n = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(n)) == NULL)
+ return PM_ERR_NOCONTEXT;
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ if ((n = request_namebypmid (ctxp, pmid)) >= 0) {
+ n = receive_namesbyid (ctxp, namelist);
+ }
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ PM_UNLOCK(ctxp->c_lock);
+ if (n == 0)
+ goto try_derive;
+ return n;
+ }
+
+try_derive:
+ if ((tmp = (char **)malloc(sizeof(tmp[0]))) == NULL)
+ return -oserror();
+ n = __dmgetname(pmid, tmp);
+ if (n < 0) {
+ free(tmp);
+ return n;
+ }
+ len = sizeof(tmp[0]) + strlen(tmp[0])+1;
+ if ((tmp = (char **)realloc(tmp, len)) == NULL)
+ return -oserror();
+ sp = (char *)&tmp[1];
+ strcpy(sp, tmp[0]);
+ free(tmp[0]);
+ tmp[0] = sp;
+ *namelist = tmp;
+ return 1;
+}
+
+
+/*
+ * generic depth-first recursive descent of the PMNS
+ */
+static int
+TraversePMNS_local(const char *name, void(*func)(const char *), void(*func_r)(const char *, void *), void *closure)
+{
+ int sts = 0;
+ int nchildren;
+ char **enfants;
+
+ if ((nchildren = pmGetChildren(name, &enfants)) < 0)
+ return nchildren;
+
+ if (nchildren > 0) {
+ int j;
+ char *newname;
+
+ for (j = 0; j < nchildren; j++) {
+ size_t size = strlen(name) + 1 + strlen(enfants[j]) + 1;
+ if ((newname = (char *)malloc(size)) == NULL)
+ __pmNoMem("pmTraversePMNS", size, PM_FATAL_ERR);
+ if (*name == '\0')
+ strcpy(newname, enfants[j]);
+ else {
+ strcpy(newname, name);
+ strcat(newname, ".");
+ strcat(newname, enfants[j]);
+ }
+ sts = TraversePMNS_local(newname, func, func_r, closure);
+ free(newname);
+ if (sts < 0)
+ break;
+ }
+ free(enfants);
+ }
+ else {
+ /* leaf node, name is full name of a metric */
+ if (func_r == NULL)
+ (*func)(name);
+ else
+ (*func_r)(name, closure);
+ }
+
+ return sts;
+}
+
+static int
+TraversePMNS(const char *name, void(*func)(const char *), void(*func_r)(const char *, void *), void *closure)
+{
+ int sts;
+ int pmns_location = GetLocation();
+
+ if (pmns_location < 0)
+ return pmns_location;
+
+ if (name == NULL)
+ return PM_ERR_NAME;
+
+ PM_INIT_LOCKS();
+
+ if (pmns_location == PMNS_LOCAL) {
+ PM_LOCK(__pmLock_libpcp);
+ sts = TraversePMNS_local(name, func, func_r, closure);
+ PM_UNLOCK(__pmLock_libpcp);
+ }
+ else {
+ __pmPDU *pb;
+ __pmContext *ctxp;
+
+ /* As we have PMNS_REMOTE there must be a current host context */
+ if ((sts = pmWhichContext()) < 0 || (ctxp = __pmHandleToPtr(sts)) == NULL)
+ return PM_ERR_NOCONTEXT;
+ PM_LOCK(ctxp->c_pmcd->pc_lock);
+ sts = __pmSendTraversePMNSReq(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), name);
+ if (sts < 0) {
+ sts = __pmMapErrno(sts);
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ PM_UNLOCK(ctxp->c_lock);
+ }
+ else {
+ int numnames;
+ int i;
+ int xtra;
+ char **namelist;
+ int pinpdu;
+
+ pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ TIMEOUT_DEFAULT, &pb);
+ PM_UNLOCK(ctxp->c_pmcd->pc_lock);
+ PM_UNLOCK(ctxp->c_lock);
+ if (sts == PDU_PMNS_NAMES) {
+ sts = __pmDecodeNameList(pb, &numnames,
+ &namelist, NULL);
+ if (sts > 0) {
+ for (i=0; i<numnames; i++) {
+ /*
+ * Do not process anonymous metrics here, we'll
+ * pick them up with the derived metrics later on
+ */
+ if (strncmp(namelist[i], "anon.", 5) != 0) {
+ if (func_r == NULL)
+ (*func)(namelist[i]);
+ else
+ (*func_r)(namelist[i], closure);
+ }
+ }
+ numnames = sts;
+ free(namelist);
+ }
+ else {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ if (sts != PM_ERR_NAME) {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+ numnames = 0;
+ }
+ else {
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ return (sts == PM_ERR_TIMEOUT) ? sts : PM_ERR_IPC;
+ }
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ /*
+ * add any derived metrics that have "name" as
+ * their prefix
+ */
+ xtra = __dmtraverse(name, &namelist);
+ if (xtra > 0) {
+ sts = 0;
+ for (i=0; i<xtra; i++) {
+ if (func_r == NULL)
+ (*func)(namelist[i]);
+ else
+ (*func_r)(namelist[i], closure);
+ }
+ numnames += xtra;
+ free(namelist);
+ }
+
+ if (sts > 0)
+ return numnames;
+ }
+ }
+ return sts;
+}
+
+int
+pmTraversePMNS(const char *name, void(*func)(const char *))
+{
+ return TraversePMNS(name, func, NULL, NULL);
+}
+
+int
+pmTraversePMNS_r(const char *name, void(*func)(const char *, void *), void *closure)
+{
+ return TraversePMNS(name, NULL, func, closure);
+}
+
+int
+pmTrimNameSpace(void)
+{
+ int i;
+ __pmContext *ctxp;
+ __pmHashCtl *hcp;
+ __pmHashNode *hp;
+ int pmns_location = GetLocation();
+
+ if (pmns_location < 0)
+ return pmns_location;
+ else if (pmns_location == PMNS_REMOTE)
+ return 0;
+
+ /* for PMNS_LOCAL ... */
+ PM_INIT_LOCKS();
+
+ if ((ctxp = __pmHandleToPtr(pmWhichContext())) == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ if (ctxp->c_type != PM_CONTEXT_ARCHIVE) {
+ /* unset all of the marks */
+ PM_LOCK(__pmLock_libpcp);
+ mark_all(curr_pmns, 0);
+ PM_UNLOCK(__pmLock_libpcp);
+ PM_UNLOCK(ctxp->c_lock);
+ return 0;
+ }
+
+ /* Don't do any trimming for archives.
+ * Exception: if an explicit load PMNS call was made.
+ */
+ PM_LOCK(__pmLock_libpcp);
+ if (havePmLoadCall) {
+ /*
+ * (1) set all of the marks, and
+ * (2) clear the marks for those metrics defined in the archive
+ */
+ mark_all(curr_pmns, 1);
+ hcp = &ctxp->c_archctl->ac_log->l_hashpmid;
+
+ for (i = 0; i < hcp->hsize; i++) {
+ for (hp = hcp->hash[i]; hp != NULL; hp = hp->next) {
+ mark_one(curr_pmns, (pmID)hp->key, 0);
+ }
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ PM_UNLOCK(ctxp->c_lock);
+
+ return 0;
+}
+
+void
+__pmDumpNameSpace(FILE *f, int verbosity)
+{
+ int pmns_location = GetLocation();
+
+ if (pmns_location < 0)
+ fprintf(f, "__pmDumpNameSpace: Unable to determine PMNS location\n");
+ else if (pmns_location == PMNS_REMOTE)
+ fprintf(f, "__pmDumpNameSpace: Name Space is remote !\n");
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ dumptree(f, 0, curr_pmns->root, verbosity);
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+void
+__pmDumpNameNode(FILE *f, __pmnsNode *node, int verbosity)
+{
+ dumptree(f, 0, node, verbosity);
+}
diff --git a/src/libpcp/src/probe.c b/src/libpcp/src/probe.c
new file mode 100644
index 0000000..ee67576
--- /dev/null
+++ b/src/libpcp/src/probe.c
@@ -0,0 +1,590 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#include "probe.h"
+
+#if !defined(PTHREAD_STACK_MIN)
+#if defined(IS_SOLARIS)
+#define PTHREAD_STACK_MIN ((size_t)_sysconf(_SC_THREAD_STACK_MIN))
+#else
+#define PTHREAD_STACK_MIN 16384
+#endif
+#endif
+
+/*
+ * Service discovery by active probing. The given subnet is probed for the
+ * requested service(s).
+ */
+typedef struct connectionOptions {
+ __pmSockAddr *netAddress; /* Address of the subnet */
+ int maskBits; /* Number of bits in the subnet */
+ unsigned maxThreads; /* Max number of threads to use. */
+ struct timeval timeout; /* Connection timeout */
+ const __pmServiceDiscoveryOptions *globalOptions; /* Global discover options */
+} connectionOptions;
+
+/* Context for each thread. */
+typedef struct connectionContext {
+ const char *service; /* Service spec */
+ __pmSockAddr *nextAddress; /* Next available address */
+ int nports; /* Number of ports per address */
+ int portIx; /* Index of next available port */
+ const int *ports; /* The actual ports */
+ int *numUrls; /* Size of the results */
+ char ***urls; /* The results */
+ const connectionOptions *options; /* Connection options */
+#if PM_MULTI_THREAD
+ __pmMutex addrLock; /* lock for the above address/port */
+ __pmMutex urlLock; /* lock for the above results */
+#endif
+} connectionContext;
+
+#if ! PM_MULTI_THREAD
+/* Make these disappear. */
+#undef PM_LOCK
+#undef PM_UNLOCK
+#define PM_LOCK(lock) do { } while (0)
+#define PM_UNLOCK(lock) do { } while (0)
+#endif
+
+/*
+ * Attempt connection based on the given context until there are no more
+ * addresses+ports to try.
+ */
+static void *
+attemptConnections(void *arg)
+{
+ int s;
+ int flags;
+ int sts;
+ __pmFdSet wfds;
+ __pmServiceInfo serviceInfo;
+ __pmSockAddr *addr;
+ const __pmServiceDiscoveryOptions *globalOptions;
+ int port;
+ int attempt;
+ struct timeval againWait = {0, 100000}; /* 0.1 seconds */
+ connectionContext *context = arg;
+
+ /*
+ * Keep trying to secure an address+port until there are no more
+ * or until we are interrupted.
+ */
+ globalOptions = context->options->globalOptions;
+ while (! globalOptions->timedOut &&
+ (! globalOptions->flags ||
+ (*globalOptions->flags & PM_SERVICE_DISCOVERY_INTERRUPTED) == 0)) {
+ /* Obtain the address lock while securing the next address, if any. */
+ PM_LOCK(context->addrLock);
+ if (context->nextAddress == NULL) {
+ /* No more addresses. */
+ PM_UNLOCK(context->addrLock);
+ break;
+ }
+
+ /*
+ * There is an address+port remaining. Secure them. If we cannot
+ * obtain our own copy of the address, then give up the lock and
+ * try again. Another thread will try this address+port.
+ */
+ addr = __pmSockAddrDup(context->nextAddress);
+ if (addr == NULL) {
+ PM_UNLOCK(context->addrLock);
+ continue;
+ }
+ port = context->ports[context->portIx];
+ __pmSockAddrSetPort(addr, port);
+
+ /*
+ * Advance the port index for the next thread. If we took the
+ * final port, then advance the address and reset the port index.
+ * The address may become NULL which is the signal for all
+ * threads to exit.
+ */
+ ++context->portIx;
+ if (context->portIx == context->nports) {
+ context->portIx = 0;
+ context->nextAddress =
+ __pmSockAddrNextSubnetAddr(context->nextAddress,
+ context->options->maskBits);
+ }
+ PM_UNLOCK(context->addrLock);
+
+ /*
+ * Create a socket. There is a limit on open fds, not just from
+ * the OS, but also in the IPC table. If we get EAGAIN,
+ * then wait 0.1 seconds and try again. We must have a limit in case
+ * something goes wrong. Make it 5 seconds (50 * 100,000 usecs).
+ */
+ for (attempt = 0; attempt < 50; ++attempt) {
+ if (__pmSockAddrIsInet(addr))
+ s = __pmCreateSocket();
+ else /* address family already checked */
+ s = __pmCreateIPv6Socket();
+ if (s != -EAGAIN)
+ break;
+ __pmtimevalSleep(againWait);
+ }
+ if (pmDebug & DBG_TRACE_DISCOVERY) {
+ if (attempt > 0) {
+ __pmNotifyErr(LOG_INFO,
+ "Waited for %d attempts for an available fd\n",
+ attempt);
+ }
+ }
+ if (s < 0) {
+ char *addrString = __pmSockAddrToString(addr);
+ __pmNotifyErr(LOG_WARNING,
+ "__pmProbeDiscoverServices: Unable to create socket for address %s",
+ addrString);
+ free(addrString);
+ __pmSockAddrFree(addr);
+ continue;
+ }
+
+ /*
+ * Attempt to connect. If flags comes back as less than zero, then the
+ * socket has already been closed by __pmConnectTo().
+ */
+ sts = -1;
+ flags = __pmConnectTo(s, addr, port);
+ if (flags >= 0) {
+ /*
+ * FNDELAY and we're in progress - wait on __pmSelectWrite.
+ * __pmSelectWrite may alter the contents of the timeout so make a
+ * copy.
+ */
+ struct timeval timeout = context->options->timeout;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(s, &wfds);
+ sts = __pmSelectWrite(s+1, &wfds, &timeout);
+
+ /* Was the connection successful? */
+ if (sts == 0)
+ sts = -1; /* Timed out */
+ else if (sts > 0)
+ sts = __pmConnectCheckError(s);
+
+ __pmCloseSocket(s);
+ }
+
+ /* If connection was successful, add this service to the list. */
+ if (sts == 0) {
+ serviceInfo.spec = context->service;
+ serviceInfo.address = addr;
+ if (strcmp(context->service, PM_SERVER_SERVICE_SPEC) == 0)
+ serviceInfo.protocol = SERVER_PROTOCOL;
+ else if (strcmp(context->service, PM_SERVER_PROXY_SPEC) == 0)
+ serviceInfo.protocol = PROXY_PROTOCOL;
+ else if (strcmp(context->service, PM_SERVER_WEBD_SPEC) == 0)
+ serviceInfo.protocol = PMWEBD_PROTOCOL;
+
+ PM_LOCK(context->urlLock);
+ *context->numUrls =
+ __pmAddDiscoveredService(&serviceInfo, globalOptions,
+ *context->numUrls, context->urls);
+ PM_UNLOCK(context->urlLock);
+ }
+
+ __pmSockAddrFree(addr);
+ } /* Loop over connection attempts. */
+
+ return NULL;
+}
+
+static int
+probeForServices(
+ const char *service,
+ const connectionOptions *options,
+ int numUrls,
+ char ***urls
+)
+{
+ int *ports = NULL;
+ int nports;
+ int prevNumUrls = numUrls;
+ connectionContext context;
+#if PM_MULTI_THREAD
+ int sts;
+ pthread_t *threads = NULL;
+ unsigned threadIx;
+ unsigned nThreads;
+ pthread_attr_t threadAttr;
+#endif
+
+ /* Determine the port numbers for this service. */
+ ports = NULL;
+ nports = 0;
+ nports = __pmServiceAddPorts(service, &ports, nports);
+ if (nports <= 0) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: could not find ports for service '%s'",
+ service);
+ return 0;
+ }
+
+ /*
+ * Initialize the shared probing context. This will be shared among all of
+ * the worker threads.
+ */
+ context.service = service;
+ context.ports = ports;
+ context.nports = nports;
+ context.numUrls = &numUrls;
+ context.urls = urls;
+ context.portIx = 0;
+ context.options = options;
+
+ /*
+ * Initialize the first address of the subnet. This pointer will become
+ * NULL and the mempry freed by __pmSockAddrNextSubnetAddr() when the
+ * final address+port has been probed.
+ */
+ context.nextAddress =
+ __pmSockAddrFirstSubnetAddr(options->netAddress, options->maskBits);
+ if (context.nextAddress == NULL) {
+ char *addrString = __pmSockAddrToString(options->netAddress);
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: unable to determine the first address of the subnet: %s/%d",
+ addrString, options->maskBits);
+ free(addrString);
+ goto done;
+ }
+
+#if PM_MULTI_THREAD
+ /*
+ * Set up the concurrrency controls. These locks will be tested
+ * even if we fail to allocate/use the thread table below.
+ */
+ pthread_mutex_init(&context.addrLock, NULL);
+ pthread_mutex_init(&context.urlLock, NULL);
+
+ if (options->maxThreads > 0) {
+ /*
+ * Allocate the thread table. We have a maximum for the number of
+ * threads, so that will be the size.
+ */
+ threads = malloc(options->maxThreads * sizeof(*threads));
+ if (threads == NULL) {
+ /*
+ * Unable to allocate the thread table, however, We can still do the
+ * probing on the main thread.
+ */
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: unable to allocate %u threads",
+ options->maxThreads);
+ }
+ else {
+ /* We want our worker threads to be joinable. */
+ pthread_attr_init(&threadAttr);
+ pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_JOINABLE);
+
+ /*
+ * Our worker threads don't need much stack. PTHREAD_STACK_MIN is
+ * enough except when resolving addresses, where twice that much is
+ * sufficient.
+ */
+ if (options->globalOptions->resolve ||
+ (options->globalOptions->flags &&
+ (*options->globalOptions->flags & PM_SERVICE_DISCOVERY_RESOLVE)
+ != 0))
+ pthread_attr_setstacksize(&threadAttr, 2 * PTHREAD_STACK_MIN);
+ else
+ pthread_attr_setstacksize(&threadAttr, PTHREAD_STACK_MIN);
+
+ /* Dispatch the threads. */
+ for (nThreads = 0; nThreads < options->maxThreads; ++nThreads) {
+ sts = pthread_create(&threads[nThreads], &threadAttr,
+ attemptConnections, &context);
+ /*
+ * If we failed to create a thread, then we've reached the OS
+ * limit.
+ */
+ if (sts != 0)
+ break;
+ }
+
+ /* We no longer need this. */
+ pthread_attr_destroy(&threadAttr);
+ }
+ }
+#endif
+
+ /*
+ * In addition to any threads we've dispatched, this thread can also
+ * participate in the probing.
+ */
+ attemptConnections(&context);
+
+#if PM_MULTI_THREAD
+ if (threads) {
+ /* Wait for all the connection attempts to finish. */
+ for (threadIx = 0; threadIx < nThreads; ++threadIx)
+ pthread_join(threads[threadIx], NULL);
+ }
+
+ /* These must not be destroyed until all of the threads have finished. */
+ pthread_mutex_destroy(&context.addrLock);
+ pthread_mutex_destroy(&context.urlLock);
+#endif
+
+ done:
+ free(ports);
+ if (context.nextAddress)
+ __pmSockAddrFree(context.nextAddress);
+#if PM_MULTI_THREAD
+ if (threads)
+ free(threads);
+#endif
+
+ /* Return the number of new urls. */
+ return numUrls - prevNumUrls;
+}
+
+/*
+ * Parse the mechanism string for options. The first option will be of the form
+ *
+ * probe=<net-address>/<maskSize>
+ *
+ * Subsequent options, if any, will be separated by commas. Currently supported:
+ *
+ * maxThreads=<integer> -- specifies a hard limit on the number of active
+ * threads.
+ */
+static int
+parseOptions(const char *mechanism, connectionOptions *options)
+{
+ const char *addressString;
+ const char *maskString;
+ size_t len;
+ char *buf;
+ char *end;
+ const char *option;
+ int family;
+ int sts;
+ long longVal;
+ unsigned subnetBits;
+ unsigned subnetSize;
+
+ /* Nothing to probe? */
+ if (mechanism == NULL)
+ return -1;
+
+ /* First extract the subnet argument, parse it and check it. */
+ addressString = strchr(mechanism, '=');
+ if (addressString == NULL || addressString[1] == '\0') {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: No argument provided");
+ return -1;
+ }
+ ++addressString;
+ maskString = strchr(addressString, '/');
+ if (maskString == NULL || maskString[1] == '\0') {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: No subnet mask provided");
+ return -1;
+ }
+ ++maskString;
+
+ /* Convert the address string to a socket address. */
+ len = maskString - addressString; /* enough space for the nul */
+ buf = malloc(len);
+ memcpy(buf, addressString, len - 1);
+ buf[len - 1] = '\0';
+ options->netAddress = __pmStringToSockAddr(buf);
+ if (options->netAddress == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: Address '%s' is not valid",
+ buf);
+ free(buf);
+ return -1;
+ }
+ free(buf);
+
+ /* Convert the mask string to an integer */
+ options->maskBits = strtol(maskString, &end, 0);
+ if (*end != '\0' && *end != ',') {
+ __pmNotifyErr(LOG_ERR, "__pmProbeDiscoverServices: Subnet mask '%s' is not valid",
+ maskString);
+ return -1;
+ }
+
+ /* Check the number of bits in the mask against the address family. */
+ if (options->maskBits < 0) {
+ __pmNotifyErr(LOG_ERR, "__pmProbeDiscoverServices: Inet subnet mask must be >= 0 bits");
+ return -1;
+ }
+ family = __pmSockAddrGetFamily(options->netAddress);
+ switch (family) {
+ case AF_INET:
+ if (options->maskBits > 32) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: Inet subnet mask must be <= 32 bits");
+ return -1;
+ }
+ break;
+ case AF_INET6:
+ if (options->maskBits > 128) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: Inet subnet mask must be <= 128 bits");
+ return -1;
+ }
+ break;
+ default:
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: Unsupported address family, %d",
+ family);
+ return -1;
+ }
+
+ /*
+ * Parse any remaining options.
+ * Initialize to defaults first.
+ *
+ * FD_SETSIZE is the most open fds that __pmFD*()
+ * and __pmSelect() can deal with, so it's a decent default for maxThreads.
+ * The main thread also participates, so subtract 1.
+ */
+ options->maxThreads = FD_SETSIZE - 1;
+
+ /*
+ * Set a default for the connection timeout. 20ms allows us to scan 50
+ * addresses per second per thread.
+ */
+ options->timeout.tv_sec = 0;
+ options->timeout.tv_usec = 20 * 1000;
+
+ /* Now parse the options. */
+ sts = 0;
+ for (option = end; *option != '\0'; /**/) {
+ /*
+ * All additional options begin with a separating comma.
+ * Make sure something has been specified.
+ */
+ ++option;
+ if (*option == '\0') {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: Missing option after ','");
+ return -1;
+ }
+
+ /* Examine the option. */
+ if (strncmp(option, "maxThreads=", sizeof("maxThreads=") - 1) == 0) {
+ option += sizeof("maxThreads=") - 1;
+ longVal = strtol(option, &end, 0);
+ if (*end != '\0' && *end != ',') {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: maxThreads value '%s' is not valid",
+ option);
+ sts = -1;
+ }
+ else {
+ option = end;
+ /*
+ * Make sure the value is positive. Make sure that the given
+ * value does not exceed the existing value which is also the
+ * hard limit.
+ */
+ if (longVal > options->maxThreads) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: maxThreads value %ld must not exceed %u",
+ longVal, options->maxThreads);
+ sts = -1;
+ }
+ else if (longVal <= 0) {
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: maxThreads value %ld must be positive",
+ longVal);
+ sts = -1;
+ }
+ else {
+#if PM_MULTI_THREAD
+ /* The main thread participates, so reduce this by one. */
+ options->maxThreads = longVal - 1;
+#else
+ __pmNotifyErr(LOG_WARNING,
+ "__pmProbeDiscoverServices: no thread support. Ignoring maxThreads value %ld",
+ longVal);
+#endif
+ }
+ }
+ }
+ else if (strncmp(option, "timeout=", sizeof("timeout=") - 1) == 0) {
+ option += sizeof("timeout=") - 1;
+ option = __pmServiceDiscoveryParseTimeout(option, &options->timeout);
+ }
+ else {
+ /* An invalid option. Skip it. */
+ __pmNotifyErr(LOG_ERR,
+ "__pmProbeDiscoverServices: option '%s' is not valid",
+ option);
+ sts = -1;
+ ++option;
+ }
+ /* Locate the next option, if any. */
+ for (/**/; *option != '\0' && *option != ','; ++option)
+ ;
+ } /* Parse additional options */
+
+ /*
+ * We now have a maximum for the number of threads
+ * but there's no point in creating more threads than the number of
+ * addresses in the subnet (less 1 for the main thread).
+ *
+ * We already know that the address is inet or ipv6 and that the
+ * number of mask bits is appropriate.
+ *
+ * Beware of overflow!!! If the calculation would have overflowed,
+ * then it means that the subnet is extremely large and therefore
+ * much larger than maxThreads anyway.
+ */
+ if (__pmSockAddrIsInet(options->netAddress))
+ subnetBits = 32 - options->maskBits;
+ else
+ subnetBits = 128 - options->maskBits;
+ if (subnetBits < sizeof(subnetSize) * 8) {
+ subnetSize = 1 << subnetBits;
+ if (subnetSize - 1 < options->maxThreads)
+ options->maxThreads = subnetSize - 1;
+ }
+
+ return sts;
+}
+
+int
+__pmProbeDiscoverServices(const char *service,
+ const char *mechanism,
+ const __pmServiceDiscoveryOptions *globalOptions,
+ int numUrls,
+ char ***urls)
+{
+ connectionOptions options;
+ int sts;
+
+ /* Interpret the mechanism string. */
+ sts = parseOptions(mechanism, &options);
+ if (sts != 0)
+ return 0;
+ options.globalOptions = globalOptions;
+
+ /* Everything checks out. Now do the actual probing. */
+ numUrls = probeForServices(service, &options, numUrls, urls);
+
+ /* Clean up */
+ __pmSockAddrFree(options.netAddress);
+
+ return numUrls;
+}
diff --git a/src/libpcp/src/probe.h b/src/libpcp/src/probe.h
new file mode 100644
index 0000000..fd04f56
--- /dev/null
+++ b/src/libpcp/src/probe.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PROBE_H
+#define PROBE_H
+
+int __pmProbeDiscoverServices(const char *,
+ const char *,
+ const __pmServiceDiscoveryOptions *,
+ int,
+ char ***) _PCP_HIDDEN;
+
+#endif /* PROBE_H */
diff --git a/src/libpcp/src/profile.c b/src/libpcp/src/profile.c
new file mode 100644
index 0000000..0127ea3
--- /dev/null
+++ b/src/libpcp/src/profile.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+static int *
+_subtract(int *list, int *list_len, int *arg, int arg_len)
+{
+ int *new;
+ int len = *list_len;
+ int new_len = 0;
+ int i, j;
+
+ if (list == NULL)
+ /* noop */
+ return NULL;
+
+ new = (int *)malloc(len * sizeof(int));
+ if (new == NULL)
+ return NULL;
+
+ for (i=0; i < len; i++) {
+ for (j=0; j < arg_len; j++)
+ if (list[i] == arg[j])
+ break;
+ if (j == arg_len)
+ /* this instance survived */
+ new[new_len++] = list[i];
+ }
+ free(list);
+ *list_len = new_len;
+ return new;
+}
+
+static int *
+_union(int *list, int *list_len, int *arg, int arg_len)
+{
+ int *new;
+ int len = *list_len;
+ int new_len = *list_len;
+ int i, j;
+
+ if (list == NULL) {
+ list = (int *)malloc(arg_len * sizeof(int));
+ memcpy((void *)list, (void *)arg, arg_len * sizeof(int));
+ *list_len = arg_len;
+ return list;
+ }
+
+ new = (int *)realloc((void *)list, (len + arg_len) * sizeof(int));
+ if (new == NULL)
+ return NULL;
+
+ for (i=0; i < arg_len; i++) {
+ for (j=0; j < new_len; j++) {
+ if (arg[i] == new[j])
+ break;
+ }
+ if (j == new_len)
+ /* instance is not already in the list */
+ new[new_len++] = arg[i];
+ }
+ *list_len = new_len;
+ return new;
+}
+
+static void
+_setGlobalState(__pmContext *ctxp, int state)
+{
+ __pmInDomProfile *p, *p_end;
+
+ /* free everything and set the global state */
+ if (ctxp->c_instprof->profile) {
+ for (p=ctxp->c_instprof->profile, p_end = p + ctxp->c_instprof->profile_len;
+ p < p_end; p++) {
+ if (p->instances)
+ free(p->instances);
+ p->instances_len = 0;
+ }
+
+ free(ctxp->c_instprof->profile);
+ ctxp->c_instprof->profile = NULL;
+ ctxp->c_instprof->profile_len = 0;
+ }
+
+ ctxp->c_instprof->state = state;
+ ctxp->c_sent = 0;
+}
+
+static __pmInDomProfile *
+_newprof(pmInDom indom, __pmContext *ctxp)
+{
+ __pmInDomProfile *p;
+
+ if (ctxp->c_instprof->profile == NULL) {
+ /* create a new profile for this inDom in the default state */
+ p = ctxp->c_instprof->profile = (__pmInDomProfile *)malloc(
+ sizeof(__pmInDomProfile));
+ if (p == NULL)
+ /* fail, no changes */
+ return NULL;
+ ctxp->c_instprof->profile_len = 1;
+ }
+ else {
+ /* append a new profile to the end of the list */
+ ctxp->c_instprof->profile_len++;
+ p = (__pmInDomProfile *)realloc((void *)ctxp->c_instprof->profile,
+ ctxp->c_instprof->profile_len * sizeof(__pmInDomProfile));
+ if (p == NULL)
+ /* fail, no changes */
+ return NULL;
+ ctxp->c_instprof->profile = p;
+ p = ctxp->c_instprof->profile + ctxp->c_instprof->profile_len - 1;
+ }
+
+ /* initialise a new profile entry : default = include all instances */
+ p->indom = indom;
+ p->instances = NULL;
+ p->instances_len = 0;
+ p->state = PM_PROFILE_INCLUDE;
+ return p;
+}
+
+__pmInDomProfile *
+__pmFindProfile(pmInDom indom, const __pmProfile *prof)
+{
+ __pmInDomProfile *p, *p_end;
+
+ if (prof != NULL && prof->profile_len > 0)
+ /* search for the profile entry for this instance domain */
+ for (p=prof->profile, p_end=p+prof->profile_len; p < p_end; p++) {
+ if (p->indom == indom)
+ /* found : an entry for this instance domain already exists */
+ return p;
+ }
+
+ /* not found */
+ return NULL;
+}
+
+int
+__pmInProfile(pmInDom indom, const __pmProfile *prof, int inst)
+{
+ __pmInDomProfile *p;
+ int *in, *in_end;
+
+ if (prof == NULL)
+ /* default if no profile for any instance domains */
+ return 1;
+
+ if ((p = __pmFindProfile(indom, prof)) == NULL)
+ /* no profile for this indom => use global default */
+ return (prof->state == PM_PROFILE_INCLUDE) ? 1 : 0;
+
+ for (in=p->instances, in_end=in+p->instances_len; in < in_end; in++)
+ if (*in == inst)
+ /* present in the list => inverse of default for this indom */
+ return (p->state == PM_PROFILE_INCLUDE) ? 0 : 1;
+
+ /* not in the list => use default for this indom */
+ return (p->state == PM_PROFILE_INCLUDE) ? 1 : 0;
+}
+
+void
+__pmFreeProfile(__pmProfile *prof)
+{
+ __pmInDomProfile *p, *p_end;
+
+ if (prof != NULL) {
+ if (prof->profile != NULL) {
+ for (p=prof->profile, p_end = p+prof->profile_len; p < p_end; p++) {
+ if (p->instances)
+ free(p->instances);
+ }
+ if (prof->profile_len)
+ free(prof->profile);
+ }
+ free(prof);
+ }
+}
+
+int
+pmAddProfile(pmInDom indom, int instlist_len, int instlist[])
+{
+ int sts;
+ __pmContext *ctxp;
+ __pmInDomProfile *prof;
+
+ if (indom == PM_INDOM_NULL && instlist != NULL)
+ /* semantic disconnect! */
+ return PM_ERR_PROFILESPEC;
+
+ if ((sts = pmWhichContext()) < 0)
+ return sts;
+ ctxp = __pmHandleToPtr(sts);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ if (indom == PM_INDOM_NULL && instlist_len == 0) {
+ _setGlobalState(ctxp, PM_PROFILE_INCLUDE);
+ goto SUCCESS;
+ }
+
+ if ((prof = __pmFindProfile(indom, ctxp->c_instprof)) == NULL) {
+ if ((prof = _newprof(indom, ctxp)) == NULL) {
+ /* fail */
+ PM_UNLOCK(ctxp->c_lock);
+ return -oserror();
+ }
+ else {
+ /* starting state: exclude all except the supplied list */
+ prof->state = PM_PROFILE_EXCLUDE;
+ }
+ }
+
+ /* include all instances? */
+ if (instlist_len == 0 || instlist == NULL) {
+ /* include all instances in this domain */
+ if (prof->instances)
+ free(prof->instances);
+ prof->instances = NULL;
+ prof->instances_len = 0;
+ prof->state = PM_PROFILE_INCLUDE;
+ goto SUCCESS;
+ }
+
+ switch (prof->state) {
+ case PM_PROFILE_INCLUDE:
+ /*
+ * prof->instances is an exclusion list (all else included)
+ * => traverse and remove the specified instances (if present)
+ */
+ prof->instances = _subtract(
+ prof->instances, &prof->instances_len,
+ instlist, instlist_len);
+ break;
+
+ case PM_PROFILE_EXCLUDE:
+ /*
+ * prof->instances is an inclusion list (all else excluded)
+ * => traverse and add the specified instances (if not already present)
+ */
+ prof->instances = _union(
+ prof->instances, &prof->instances_len,
+ instlist, instlist_len);
+ break;
+ }
+
+SUCCESS:
+ ctxp->c_sent = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PROFILE) {
+ char strbuf[20];
+ fprintf(stderr, "pmAddProfile() indom: %s\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ __pmDumpProfile(stderr, indom, ctxp->c_instprof);
+ }
+#endif
+ PM_UNLOCK(ctxp->c_lock);
+ return 0;
+}
+
+int
+pmDelProfile(pmInDom indom, int instlist_len, int instlist[])
+{
+ int sts;
+ __pmContext *ctxp;
+ __pmInDomProfile *prof;
+
+ if (indom == PM_INDOM_NULL && instlist != NULL)
+ /* semantic disconnect! */
+ return PM_ERR_PROFILESPEC;
+
+ if ((sts = pmWhichContext()) < 0)
+ return sts;
+ ctxp = __pmHandleToPtr(sts);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ if (indom == PM_INDOM_NULL && instlist_len == 0) {
+ _setGlobalState(ctxp, PM_PROFILE_EXCLUDE);
+ goto SUCCESS;
+ }
+
+ if ((prof = __pmFindProfile(indom, ctxp->c_instprof)) == NULL) {
+ if ((prof = _newprof(indom, ctxp)) == NULL) {
+ /* fail */
+ PM_UNLOCK(ctxp->c_lock);
+ return -oserror();
+ }
+ else {
+ /* starting state: include all except the supplied list */
+ prof->state = PM_PROFILE_EXCLUDE;
+ }
+ }
+
+ /* include all instances? */
+ if (instlist_len == 0 || instlist == NULL) {
+ /* include all instances in this domain */
+ if (prof->instances)
+ free(prof->instances);
+ prof->instances = NULL;
+ prof->instances_len = 0;
+ prof->state = PM_PROFILE_EXCLUDE;
+ goto SUCCESS;
+ }
+
+ switch (prof->state) {
+ case PM_PROFILE_INCLUDE:
+ /*
+ * prof->instances is an exclusion list (all else included)
+ * => traverse and add the specified instances (if not already present)
+ */
+ prof->instances = _union(
+ prof->instances, &prof->instances_len,
+ instlist, instlist_len);
+ break;
+
+ case PM_PROFILE_EXCLUDE:
+ /*
+ * prof->instances is an inclusion list (all else excluded)
+ * => traverse and remove the specified instances (if present)
+ */
+ prof->instances = _subtract(
+ prof->instances, &prof->instances_len,
+ instlist, instlist_len);
+ break;
+ }
+
+SUCCESS:
+ ctxp->c_sent = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PROFILE) {
+ char strbuf[20];
+ fprintf(stderr, "pmDelProfile() indom: %s\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ __pmDumpProfile(stderr, indom, ctxp->c_instprof);
+ }
+#endif
+ PM_UNLOCK(ctxp->c_lock);
+ return 0;
+}
+
+void
+__pmDumpProfile(FILE *f, int indom, const __pmProfile *pp)
+{
+ int j;
+ int k;
+ __pmInDomProfile *prof;
+ char strbuf[20];
+
+ fprintf(f, "Dump Instance Profile state=%s, %d profiles",
+ pp->state == PM_PROFILE_INCLUDE ? "INCLUDE" : "EXCLUDE",
+ pp->profile_len);
+ if (indom != PM_INDOM_NULL)
+ fprintf(f, ", dump restricted to indom=%d [%s]",
+ indom, pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ fprintf(f, "\n");
+
+ for (prof=pp->profile, j=0; j < pp->profile_len; j++, prof++) {
+ if (indom != PM_INDOM_NULL && indom != prof->indom)
+ continue;
+ fprintf(f, "\tProfile [%d] indom=%d [%s] state=%s %d instances\n",
+ j, prof->indom, pmInDomStr_r(prof->indom, strbuf, sizeof(strbuf)),
+ (prof->state == PM_PROFILE_INCLUDE) ? "INCLUDE" : "EXCLUDE",
+ prof->instances_len);
+
+ if (prof->instances_len) {
+ fprintf(f, "\t\tInstances:");
+ for (k=0; k < prof->instances_len; k++)
+ fprintf(f, " [%d]", prof->instances[k]);
+ fprintf(f, "\n");
+ }
+ }
+}
diff --git a/src/libpcp/src/rtime.c b/src/libpcp/src/rtime.c
new file mode 100644
index 0000000..b62cd9c
--- /dev/null
+++ b/src/libpcp/src/rtime.c
@@ -0,0 +1,761 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <limits.h>
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+
+
+#define CODE3(a,b,c) ((__uint32_t)(a)|((__uint32_t)(b)<<8)|((__uint32_t)(c)<<16))
+#define whatmsg "unexpected value"
+#define moremsg "more information expected"
+#define alignmsg "alignment specified by -A switch could not be applied"
+
+#define N_WDAYS 7
+static const __uint32_t wdays[N_WDAYS] = {
+ CODE3('s', 'u', 'n'),
+ CODE3('m', 'o', 'n'),
+ CODE3('t', 'u', 'e'),
+ CODE3('w', 'e', 'd'),
+ CODE3('t', 'h', 'u'),
+ CODE3('f', 'r', 'i'),
+ CODE3('s', 'a', 't')
+};
+
+#define N_MONTHS 12
+static const __uint32_t months[N_MONTHS] = {
+ CODE3('j', 'a', 'n'),
+ CODE3('f', 'e', 'b'),
+ CODE3('m', 'a', 'r'),
+ CODE3('a', 'p', 'r'),
+ CODE3('m', 'a', 'y'),
+ CODE3('j', 'u', 'n'),
+ CODE3('j', 'u', 'l'),
+ CODE3('a', 'u', 'g'),
+ CODE3('s', 'e', 'p'),
+ CODE3('o', 'c', 't'),
+ CODE3('n', 'o', 'v'),
+ CODE3('d', 'e', 'c')
+};
+
+#define N_AMPM 2
+static const __uint32_t ampm[N_AMPM] = {
+ CODE3('a', 'm', 0 ),
+ CODE3('p', 'm', 0 )
+};
+
+static const struct {
+ char *name; /* pmParseInterval scale name */
+ int len; /* length of scale name */
+ int scale; /* <0 -divisor, else multiplier */
+} int_tab[] = {
+ { "millisecond", 11, -1000 },
+ { "second", 6, 1 },
+ { "minute", 6, 60 },
+ { "hour", 4, 3600 },
+ { "day", 3, 86400 },
+ { "msec", 4, -1000 },
+ { "sec", 3, 1 },
+ { "min", 3, 60 },
+ { "hr", 2, 3600 },
+ { "s", 1, 1 },
+ { "m", 1, 60 },
+ { "h", 1, 3600 },
+ { "d", 1, 86400 },
+};
+static const int numint = sizeof(int_tab) / sizeof(int_tab[0]);
+
+#define NO_OFFSET 0
+#define PLUS_OFFSET 1
+#define NEG_OFFSET 2
+
+
+/* Compare struct timevals */
+static int /* 0 -> equal, -1 -> tv1 < tv2, 1 -> tv1 > tv2 */
+tvcmp(struct timeval tv1, struct timeval tv2)
+{
+ if (tv1.tv_sec < tv2.tv_sec)
+ return -1;
+ if (tv1.tv_sec > tv2.tv_sec)
+ return 1;
+ if (tv1.tv_usec < tv2.tv_usec)
+ return -1;
+ if (tv1.tv_usec > tv2.tv_usec)
+ return 1;
+ return 0;
+}
+
+/* Recognise three character string as one of the given codes.
+ return: 1 == ok, 0 <= *rslt <= ncodes-1, *spec points to following char
+ 0 == not found, *spec updated to strip blanks */
+static int
+parse3char(const char **spec, const __uint32_t *codes, int ncodes, int *rslt)
+{
+ const char *scan = *spec;
+ __uint32_t code = 0;
+ int i;
+
+ while (isspace((int)*scan))
+ scan++;
+ *spec = scan;
+ if (! isalpha((int)*scan))
+ return 0;
+ for (i = 0; i <= 16; i += 8) {
+ code |= (tolower((int)*scan) << i);
+ scan++;
+ if (! isalpha((int)*scan))
+ break;
+ }
+ for (i = 0; i < ncodes; i++) {
+ if (code == codes[i]) {
+ *spec = scan;
+ *rslt = i;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Recognise single char (with optional leading space) */
+static int
+parseChar(const char **spec, char this)
+{
+ const char *scan = *spec;
+
+ while (isspace((int)*scan))
+ scan++;
+ *spec = scan;
+ if (*scan != this)
+ return 0;
+ *spec = scan + 1;
+ return 1;
+}
+
+/* Recognise integer in range min..max
+ return: 1 == ok, min <= *rslt <= max, *spec points to following char
+ 0 == not found, *spec updated to strip blanks */
+static int
+parseInt(const char **spec, int min, int max, int *rslt)
+{
+ const char *scan = *spec;
+ char *tmp;
+ long r;
+
+ while (isspace((int)*scan))
+ scan++;
+ tmp = (char *)scan;
+ r = strtol(scan, &tmp, 10);
+ *spec = tmp;
+ if (scan == *spec || r < min || r > max) {
+ *spec = scan;
+ return 0;
+ }
+ *rslt = (int)r;
+ return 1;
+}
+
+/* Recognise double precision float in the range min..max
+ return: 1 == ok, min <= *rslt <= max, *spec points to following char
+ 0 == not found, *spec updated to strip blanks */
+static double
+parseDouble(const char **spec, double min, double max, double *rslt)
+{
+ const char *scan = *spec;
+ char *tmp;
+ double r;
+
+ while (isspace((int)*scan))
+ scan++;
+ tmp = (char *)scan;
+ r = strtod(scan, &tmp);
+ *spec = tmp;
+ if (scan == *spec || r < min || r > max) {
+ *spec = scan;
+ return 0;
+ }
+ *rslt = r;
+ return 1;
+}
+
+/* Construct error message buffer for syntactic error */
+static void
+parseError(const char *spec, const char *point, char *msg, char **rslt)
+{
+ int need = 2 * (int)strlen(spec) + (int)strlen(msg) + 8;
+ const char *p;
+ char *q;
+
+ if ((*rslt = malloc(need)) == NULL)
+ __pmNoMem("__pmParseTime", need, PM_FATAL_ERR);
+ q = *rslt;
+
+ for (p = spec; *p != '\0'; p++)
+ *q++ = *p;
+ *q++ = '\n';
+ for (p = spec; p != point; p++)
+ *q++ = isgraph((int)*p) ? ' ' : *p;
+ sprintf(q, "^ -- ");
+ q += 5;
+ for (p = msg; *p != '\0'; p++)
+ *q++ = *p;
+ *q++ = '\n';
+ *q = '\0';
+}
+
+
+int /* 0 -> ok, -1 -> error */
+pmParseInterval(
+ const char *spec, /* interval to parse */
+ struct timeval *rslt, /* result stored here */
+ char **errmsg) /* error message */
+{
+ const char *scan = spec;
+ double d;
+ double sec = 0.0;
+ int i;
+ const char *p;
+ int len;
+
+ if (scan == NULL || *scan == '\0') {
+ const char *empty = "";
+ parseError(empty, empty, "Null or empty specification", errmsg);
+ return -1;
+ }
+
+ /* parse components of spec */
+ while (*scan != '\0') {
+ if (! parseDouble(&scan, 0.0, (double)INT_MAX, &d))
+ break;
+ while (*scan != '\0' && isspace((int)*scan))
+ scan++;
+ if (*scan == '\0') {
+ /* no scale, seconds is the default */
+ sec += d;
+ break;
+ }
+ for (p = scan; *p && isalpha((int)*p); p++)
+ ;
+ len = (int)(p - scan);
+ if (len == 0)
+ /* no scale, seconds is the default */
+ sec += d;
+ else {
+ if (len > 1 && (p[-1] == 's' || p[-1] == 'S'))
+ /* ignore any trailing 's' */
+ len--;
+ for (i = 0; i < numint; i++) {
+ if (len != int_tab[i].len)
+ continue;
+ if (strncasecmp(scan, int_tab[i].name, len) == 0)
+ break;
+ }
+ if (i == numint)
+ break;
+ if (int_tab[i].scale < 0)
+ sec += d / (-int_tab[i].scale);
+ else
+ sec += d * int_tab[i].scale;
+ }
+ scan = p;
+ }
+
+ /* error detection */
+ if (*scan != '\0') {
+ parseError(spec, scan, whatmsg, errmsg);
+ return -1;
+ }
+
+ /* convert into seconds and microseconds */
+ rslt->tv_sec = (time_t)sec;
+ rslt->tv_usec = (int)(1000000.0 * (sec - (double)rslt->tv_sec));
+ return 0;
+}
+
+
+int /* 0 -> ok, -1 -> error */
+__pmParseCtime(
+ const char *spec, /* ctime string to parse */
+ struct tm *rslt, /* result stored here */
+ char **errmsg) /* error message */
+{
+ struct tm tm = {-1, -1, -1, -1, -1, -1, NO_OFFSET, -1, -1};
+ double d;
+ const char *scan = spec;
+ int pm = -1;
+ int ignored = -1;
+ int dflt;
+
+ /* parse time spec */
+ parse3char(&scan, wdays, N_WDAYS, &ignored);
+ parse3char(&scan, months, N_MONTHS, &tm.tm_mon);
+
+ parseInt(&scan, 0, 31, &tm.tm_mday);
+ parseInt(&scan, 0, 23, &tm.tm_hour);
+ if (tm.tm_mday == 0 && tm.tm_hour != -1) {
+ tm.tm_mday = -1;
+ }
+ if (tm.tm_hour == -1 && tm.tm_mday >= 0 && tm.tm_mday <= 23 &&
+ (tm.tm_mon == -1 || *scan == ':')) {
+ tm.tm_hour = tm.tm_mday;
+ tm.tm_mday = -1;
+ }
+ if (parseChar(&scan, ':')) {
+ if (tm.tm_hour == -1)
+ tm.tm_hour = 0;
+ tm.tm_min = 0; /* for moreError checking */
+ parseInt(&scan, 0, 59, &tm.tm_min);
+ if (parseChar(&scan, ':')) {
+ if (parseDouble(&scan, 0.0, 61.0, &d)) {
+ tm.tm_sec = (time_t)d;
+ tm.tm_yday = (int)(1000000.0 * (d - tm.tm_sec));
+ }
+ }
+ }
+ if (parse3char(&scan, ampm, N_AMPM, &pm)) {
+ if (tm.tm_hour > 12 || tm.tm_hour == -1)
+ scan -= 2;
+ else {
+ if (pm) {
+ if (tm.tm_hour < 12)
+ tm.tm_hour += 12;
+ }
+ else {
+ if (tm.tm_hour == 12)
+ tm.tm_hour = 0;
+ }
+ }
+ }
+ /*
+ * parse range forces tm_year to be >= 1900, so this is Y2K safe
+ */
+ if (parseInt(&scan, 1900, 9999, &tm.tm_year))
+ tm.tm_year -= 1900;
+
+ /*
+ * error detection and reporting
+ *
+ * in the code below, tm_year is either years since 1900 or
+ * -1 (a sentinel), so this is is Y2K safe
+ */
+ while (isspace((int)*scan))
+ scan++;
+ if (*scan != '\0') {
+ parseError(spec, scan, whatmsg, errmsg);
+ return -1;
+ }
+ if ((ignored != -1 && tm.tm_mon == -1 && tm.tm_mday == -1) ||
+ (tm.tm_hour != -1 && tm.tm_min == -1 && tm.tm_mday == -1 &&
+ tm.tm_mon == -1 && tm.tm_year == -1)) {
+ parseError(spec, scan, moremsg, errmsg);
+ return -1;
+ }
+
+ /* fill in elements of tm from spec */
+ dflt = (tm.tm_year != -1);
+ if (tm.tm_mon != -1)
+ dflt = 1;
+ else if (dflt)
+ tm.tm_mon = 0;
+ if (tm.tm_mday != -1)
+ dflt = 1;
+ else if (dflt)
+ tm.tm_mday = 1;
+ if (tm.tm_hour != -1)
+ dflt = 1;
+ else if (dflt)
+ tm.tm_hour = 0;
+ if (tm.tm_min != -1)
+ dflt = 1;
+ else if (dflt)
+ tm.tm_min = 0;
+ if (tm.tm_sec == -1 && dflt) {
+ tm.tm_sec = 0;
+ tm.tm_yday = 0;
+ }
+
+ *rslt = tm;
+ return 0;
+}
+
+
+int /* 0 ok, -1 error */
+__pmConvertTime(
+ struct tm *tmin, /* absolute or +ve or -ve offset time */
+ struct timeval *origin, /* defaults and origin for offset */
+ struct timeval *rslt) /* result stored here */
+{
+ time_t t;
+ struct timeval tval = *origin;
+ struct tm tm;
+
+ /* positive offset interval */
+ if (tmin->tm_wday == PLUS_OFFSET) {
+ tval.tv_usec += tmin->tm_yday;
+ if (tval.tv_usec > 1000000) {
+ tval.tv_usec -= 1000000;
+ tval.tv_sec++;
+ }
+ tval.tv_sec += tmin->tm_sec;
+ }
+
+ /* negative offset interval */
+ else if (tmin->tm_wday == NEG_OFFSET) {
+ if (tval.tv_usec < tmin->tm_yday) {
+ tval.tv_usec += 1000000;
+ tval.tv_sec--;
+ }
+ tval.tv_usec -= tmin->tm_yday;
+ tval.tv_sec -= tmin->tm_sec;
+ }
+
+ /* absolute time */
+ else {
+ /* tmin completely specified */
+ if (tmin->tm_year != -1) {
+ tm = *tmin;
+ tval.tv_usec = tmin->tm_yday;
+ }
+
+ /* tmin partially specified */
+ else {
+ t = (time_t)tval.tv_sec;
+ pmLocaltime(&t, &tm);
+ tm.tm_isdst = -1;
+
+ /* fill in elements of tm from spec */
+ if (tmin->tm_mon != -1) {
+ if (tmin->tm_mon < tm.tm_mon)
+ /*
+ * tm_year is years since 1900 and the tm_year++ is
+ * adjusting for the specified month being before the
+ * current month, so this is Y2K safe
+ */
+ tm.tm_year++;
+ tm.tm_mon = tmin->tm_mon;
+ tm.tm_mday = tmin->tm_mday;
+ tm.tm_hour = tmin->tm_hour;
+ tm.tm_min = tmin->tm_min;
+ tm.tm_sec = tmin->tm_sec;
+ tval.tv_usec = tmin->tm_yday;
+ }
+ else if (tmin->tm_mday != -1) {
+ if (tmin->tm_mday < tm.tm_mday)
+ tm.tm_mon++;
+ tm.tm_mday = tmin->tm_mday;
+ tm.tm_hour = tmin->tm_hour;
+ tm.tm_min = tmin->tm_min;
+ tm.tm_sec = tmin->tm_sec;
+ tval.tv_usec = tmin->tm_yday;
+ }
+ else if (tmin->tm_hour != -1) {
+ if (tmin->tm_hour < tm.tm_hour)
+ tm.tm_mday++;
+ tm.tm_hour = tmin->tm_hour;
+ tm.tm_min = tmin->tm_min;
+ tm.tm_sec = tmin->tm_sec;
+ tval.tv_usec = tmin->tm_yday;
+ }
+ else if (tmin->tm_min != -1) {
+ if (tmin->tm_min < tm.tm_min)
+ tm.tm_hour++;
+ tm.tm_min = tmin->tm_min;
+ tm.tm_sec = tmin->tm_sec;
+ tval.tv_usec = tmin->tm_yday;
+ }
+ else if (tmin->tm_sec != -1) {
+ if (tmin->tm_sec < tm.tm_sec)
+ tm.tm_min++;
+ tm.tm_sec = tmin->tm_sec;
+ tval.tv_usec = tmin->tm_yday;
+ }
+ }
+ tval.tv_sec = __pmMktime(&tm);
+ }
+
+ *rslt = tval;
+ return 0;
+}
+
+
+/*
+ * Use heuristics to determine the presence of a relative date time
+ * and its direction
+ */
+static int
+glib_relative_date(const char *date_string)
+{
+ /*
+ * Time terms most commonly used with an adjective modifier are
+ * relative to the start/end time
+ * e.g. last year, 2 year ago, next hour, -1 minute
+ */
+ char * const startend_relative_terms[] = {
+ " YEAR",
+ " MONTH",
+ " FORTNIGHT",
+ " WEEK",
+ " DAY",
+ " HOUR",
+ " MINUTE",
+ " MIN",
+ " SECOND",
+ " SEC"
+ };
+
+ /*
+ * Time terms for a specific day are relative to the current time
+ * TOMORROW, YESTERDAY, TODAY, NOW, MONDAY-SUNDAY
+ */
+ int rtu_bound = sizeof(startend_relative_terms) / sizeof(void *);
+ int rtu_idx;
+
+ while (isspace((int)*date_string))
+ date_string++;
+ for (rtu_idx = 0; rtu_idx < rtu_bound; rtu_idx++)
+ if (strcasestr(date_string, startend_relative_terms[rtu_idx]) != NULL)
+ break;
+ if (rtu_idx < rtu_bound) {
+ if (strcasestr(date_string, "last") != NULL ||
+ strcasestr(date_string, "ago") != NULL ||
+ date_string[0] == '-')
+ return NEG_OFFSET;
+ else
+ return PLUS_OFFSET;
+ }
+ return NO_OFFSET;
+}
+
+/*
+ * Helper interface to wrap calls to the __pmGlibGetDate interface
+ */
+static int
+glib_get_date(
+ const char *scan,
+ struct timeval *start,
+ struct timeval *end,
+ struct timeval *rslt)
+{
+ int sts;
+ int rel_type;
+ struct timespec tsrslt;
+
+ rel_type = glib_relative_date(scan);
+
+ if (rel_type == NO_OFFSET)
+ sts = __pmGlibGetDate(&tsrslt, scan, NULL);
+ else if (rel_type == NEG_OFFSET && end->tv_sec < INT_MAX) {
+ struct timespec tsend;
+ tsend.tv_sec = end->tv_sec;
+ tsend.tv_nsec = end->tv_usec * 1000;
+ sts = __pmGlibGetDate(&tsrslt, scan, &tsend);
+ }
+ else {
+ struct timespec tsstart;
+ tsstart.tv_sec = start->tv_sec;
+ tsstart.tv_nsec = start->tv_usec * 1000;
+ sts = __pmGlibGetDate(&tsrslt, scan, &tsstart);
+ }
+ if (sts < 0)
+ return sts;
+
+ rslt->tv_sec = tsrslt.tv_sec;
+ rslt->tv_usec = tsrslt.tv_nsec / 1000;
+ return 0;
+}
+
+int /* 0 -> ok, -1 -> error */
+__pmParseTime(
+ const char *string, /* string to be parsed */
+ struct timeval *logStart, /* start of log or current time */
+ struct timeval *logEnd, /* end of log or tv_sec == INT_MAX */
+ /* assumes sizeof(t_time) == sizeof(int) */
+ struct timeval *rslt, /* if parsed ok, result filled in */
+ char **errMsg) /* error message, please free */
+{
+ struct tm tm;
+ const char *scan;
+ struct timeval start;
+ struct timeval end;
+ struct timeval tval;
+
+ *errMsg = NULL;
+ start = *logStart;
+ end = *logEnd;
+ if (end.tv_sec == INT_MAX)
+ end.tv_usec = 999999;
+ scan = string;
+
+ /* ctime string */
+ if (parseChar(&scan, '@')) {
+ if (__pmParseCtime(scan, &tm, errMsg) >= 0) {
+ tm.tm_wday = NO_OFFSET;
+ __pmConvertTime(&tm, &start, rslt);
+ return 0;
+ }
+ }
+
+ /* relative to end of archive */
+ else if (end.tv_sec < INT_MAX && parseChar(&scan, '-')) {
+ if (pmParseInterval(scan, &tval, errMsg) >= 0) {
+ tm.tm_wday = NEG_OFFSET;
+ tm.tm_sec = (int)tval.tv_sec;
+ tm.tm_yday = (int)tval.tv_usec;
+ __pmConvertTime(&tm, &end, rslt);
+ return 0;
+ }
+ }
+
+ /* relative to start of archive or current time */
+ else {
+ parseChar(&scan, '+');
+ if (pmParseInterval(scan, &tval, errMsg) >= 0) {
+ tm.tm_wday = PLUS_OFFSET;
+ tm.tm_sec = (int)tval.tv_sec;
+ tm.tm_yday = (int)tval.tv_usec;
+ __pmConvertTime(&tm, &start, rslt);
+ return 0;
+ }
+ }
+
+ /* datetime is not recognised, try the glib_get_date method */
+ parseChar(&scan, '@'); /* ignore; glib_relative_date determines type */
+ if (glib_get_date(scan, &start, &end, rslt) < 0)
+ return -1;
+
+ if (*errMsg)
+ free(*errMsg);
+ return 0;
+}
+
+
+/* This function is designed to encapsulate the interpretation of
+ the -S, -T, -A and -O command line switches for use by the PCP
+ client tools. */
+int /* 1 -> ok, 0 -> warning, -1 -> error */
+pmParseTimeWindow(
+ const char *swStart, /* argument of -S switch, may be NULL */
+ const char *swEnd, /* argument of -T switch, may be NULL */
+ const char *swAlign, /* argument of -A switch, may be NULL */
+ const char *swOffset, /* argument of -O switch, may be NULL */
+ const struct timeval *logStart, /* start of log or current time */
+ const struct timeval *logEnd, /* end of log or tv_sec == INT_MAX */
+ struct timeval *rsltStart, /* start time returned here */
+ struct timeval *rsltEnd, /* end time returned here */
+ struct timeval *rsltOffset,/* offset time returned here */
+ char **errMsg) /* error message, please free */
+{
+ struct timeval astart;
+ struct timeval start;
+ struct timeval end;
+ struct timeval offset;
+ struct timeval aoffset;
+ struct timeval tval;
+ const char *scan;
+ __int64_t delta = 0; /* initialize to pander to gcc */
+ __int64_t align;
+ __int64_t blign;
+ int sts = 1;
+
+ /* default values for start and end */
+ start = *logStart;
+ end = *logEnd;
+ if (end.tv_sec == INT_MAX)
+ end.tv_usec = 999999;
+
+ /* parse -S argument and adjust start accordingly */
+ if (swStart) {
+ if (__pmParseTime(swStart, &start, &end, &start, errMsg) < 0)
+ return -1;
+ }
+
+ /* sanity check -S */
+ if (tvcmp(start, *logStart) < 0)
+ /* move start forwards to the beginning of the archive */
+ start = *logStart;
+
+ /* parse -A argument and adjust start accordingly */
+ if (swAlign) {
+ scan = swAlign;
+ if (pmParseInterval(scan, &tval, errMsg) < 0)
+ return -1;
+ delta = tval.tv_usec + 1000000 * (__int64_t)tval.tv_sec;
+ align = start.tv_usec + 1000000 * (__int64_t)start.tv_sec;
+ blign = (align / delta) * delta;
+ if (blign < align)
+ blign += delta;
+ astart.tv_sec = (time_t)(blign / 1000000);
+ astart.tv_usec = (int)(blign % 1000000);
+
+ /* sanity check -S after alignment */
+ if (tvcmp(astart, *logStart) >= 0 && tvcmp(astart, *logEnd) <= 0)
+ start = astart;
+ else {
+ parseError(swAlign, swAlign, alignmsg, errMsg);
+ sts = 0;
+ }
+ }
+
+ /* parse -T argument and adjust end accordingly */
+ if (swEnd) {
+ if (__pmParseTime(swEnd, &start, &end, &end, errMsg) < 0)
+ return -1;
+ }
+
+ /* sanity check -T */
+ if (tvcmp(end, *logEnd) > 0)
+ /* move end backwards to the end of the archive */
+ end = *logEnd;
+
+ /* parse -O argument and align if required */
+ offset = start;
+ if (swOffset) {
+ if (__pmParseTime(swOffset, &start, &end, &offset, errMsg) < 0)
+ return -1;
+
+ /* sanity check -O */
+ if (tvcmp(offset, start) < 0)
+ offset = start;
+ else if (tvcmp(offset, end) > 0)
+ offset = end;
+
+ if (swAlign) {
+ align = offset.tv_usec + 1000000 * (__int64_t)offset.tv_sec;
+ blign = (align / delta) * delta;
+ if (blign < align)
+ blign += delta;
+ align = end.tv_usec + 1000000 * (__int64_t)end.tv_sec;
+ if (blign > align)
+ blign -= delta;
+ aoffset.tv_sec = (time_t)(blign / 1000000);
+ aoffset.tv_usec = (int)(blign % 1000000);
+
+ /* sanity check -O after alignment */
+ if (tvcmp(aoffset, start) >= 0 && tvcmp(aoffset, end) <= 0)
+ offset = aoffset;
+ else {
+ parseError(swAlign, swAlign, alignmsg, errMsg);
+ sts = 0;
+ }
+ }
+ }
+
+ /* return results */
+ *rsltStart = start;
+ *rsltEnd = end;
+ *rsltOffset = offset;
+ return sts;
+}
diff --git a/src/libpcp/src/secureconnect.c b/src/libpcp/src/secureconnect.c
new file mode 100644
index 0000000..3391329
--- /dev/null
+++ b/src/libpcp/src/secureconnect.c
@@ -0,0 +1,1575 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Security and Authentication (NSS and SASL) support. Client side.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#define SOCKET_INTERNAL
+#include "internal.h"
+#include <assert.h>
+#include <hasht.h>
+#include <certdb.h>
+#include <secerr.h>
+#include <sslerr.h>
+#include <pk11pub.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TERMIOS_H
+#include <sys/termios.h>
+#endif
+
+/*
+ * We shift NSS/NSPR/SSL/SASL errors below the valid range for other
+ * PCP error codes, in order to avoid conflicts. pmErrStr can then
+ * detect and decode. PM_ERR_NYI is the PCP error code sentinel.
+ */
+int
+__pmSecureSocketsError(int code)
+{
+ int sts = (PM_ERR_NYI + code); /* encode, negative value */
+ setoserror(-sts);
+ return sts;
+}
+
+int
+__pmSocketClosed(void)
+{
+ int error = oserror();
+
+ if (PM_ERR_NYI > -error)
+ error = -(error + PM_ERR_NYI);
+
+ switch (error) {
+ /*
+ * Treat this like end of file on input.
+ *
+ * failed as a result of pmcd exiting and the connection
+ * being reset, or as a result of the kernel ripping
+ * down the connection (most likely because the host at
+ * the other end just took a dive)
+ *
+ * from IRIX BDS kernel sources, seems like all of the
+ * following are peers here:
+ * ECONNRESET (pmcd terminated?)
+ * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH
+ * ECONNREFUSED
+ * peers for BDS but not here:
+ * ENETRESET ENONET ESHUTDOWN (cache_fs only?)
+ * ECONNABORTED (accept, user req only?)
+ * ENOTCONN (udp?)
+ * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?)
+ */
+ case ECONNRESET:
+ case EPIPE:
+ case ETIMEDOUT:
+ case ENETDOWN:
+ case ENETUNREACH:
+ case EHOSTDOWN:
+ case EHOSTUNREACH:
+ case ECONNREFUSED:
+ case PR_IO_TIMEOUT_ERROR:
+ case PR_NETWORK_UNREACHABLE_ERROR:
+ case PR_CONNECT_TIMEOUT_ERROR:
+ case PR_NOT_CONNECTED_ERROR:
+ case PR_CONNECT_RESET_ERROR:
+ case PR_PIPE_ERROR:
+ case PR_NETWORK_DOWN_ERROR:
+ case PR_SOCKET_SHUTDOWN_ERROR:
+ case PR_HOST_UNREACHABLE_ERROR:
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * For every connection when operating under secure socket mode, we need
+ * the following auxillary structure associated with the socket. It holds
+ * critical information that each piece of the security pie can make use
+ * of (NSS/SSL/NSPR/SASL). This is allocated once a connection is upgraded
+ * from insecure to secure.
+ */
+typedef struct {
+ PRFileDesc *nsprFd;
+ PRFileDesc *sslFd;
+ sasl_conn_t *saslConn;
+ sasl_callback_t *saslCB;
+} __pmSecureSocket;
+
+int
+__pmDataIPCSize(void)
+{
+ return sizeof(__pmSecureSocket);
+}
+
+int
+__pmInitSecureSockets(void)
+{
+ /* Make sure that NSPR has been initialized */
+ if (PR_Initialized() != PR_TRUE)
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+ return 0;
+}
+
+int
+__pmShutdownSecureSockets(void)
+{
+ if (PR_Initialized() == PR_TRUE)
+ PR_Cleanup();
+ return 0;
+}
+
+static int
+__pmSetupSecureSocket(int fd, __pmSecureSocket *socket)
+{
+ /* Is this socket already set up? */
+ if (socket->nsprFd)
+ return 0;
+
+ /* Import the fd into NSPR. */
+ socket->nsprFd = PR_ImportTCPSocket(fd);
+ if (! socket->nsprFd)
+ return -1;
+
+ return 0;
+}
+
+void
+__pmCloseSocket(int fd)
+{
+ __pmSecureSocket socket;
+ int sts;
+
+ sts = __pmDataIPC(fd, (void *)&socket);
+ __pmResetIPC(fd);
+
+ if (sts == 0) {
+ if (socket.saslConn) {
+ sasl_dispose(&socket.saslConn);
+ socket.saslConn = NULL;
+ }
+ if (socket.saslCB) {
+ free(socket.saslCB);
+ socket.saslCB = NULL;
+ }
+ if (socket.nsprFd) {
+ PR_Close(socket.nsprFd);
+ socket.nsprFd = NULL;
+ socket.sslFd = NULL;
+ fd = -1;
+ }
+ }
+
+ if (fd != -1) {
+#if defined(IS_MINGW)
+ closesocket(fd);
+#else
+ close(fd);
+#endif
+ }
+}
+
+static char *
+dbpath(char *path, size_t size, char *db_method)
+{
+ int sep = __pmPathSeparator();
+ const char *empty_homedir = "";
+ char *homedir = getenv("HOME");
+ char *nss_method = getenv("PCP_SECURE_DB_METHOD");
+
+ if (homedir == NULL)
+ homedir = (char *)empty_homedir;
+ if (nss_method == NULL)
+ nss_method = db_method;
+
+ /*
+ * Fill in a buffer with the users NSS database specification.
+ * Return a pointer to the filesystem path component - without
+ * the <method>:-prefix - for other routines to work with.
+ */
+ snprintf(path, size, "%s%s" "%c" ".pki" "%c" "nssdb",
+ nss_method, homedir, sep, sep);
+ return path + strlen(nss_method);
+}
+
+static char *
+dbphrase(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ (void)arg;
+ if (retry)
+ return NULL;
+ assert(PK11_IsInternal(slot));
+ return strdup(SECURE_USERDB_DEFAULT_KEY);
+}
+
+int
+__pmInitCertificates(void)
+{
+ char nssdb[MAXPATHLEN];
+ PK11SlotInfo *slot;
+ SECStatus secsts;
+ static int initialized;
+
+ /* Only attempt this once. */
+ if (initialized)
+ return 0;
+ initialized = 1;
+
+ PK11_SetPasswordFunc(dbphrase);
+
+ /*
+ * Check for client certificate databases. We enforce use
+ * of the per-user shared NSS database at $HOME/.pki/nssdb
+ * For simplicity, we create this directory if we need to.
+ * If we cannot, we silently bail out so that users who're
+ * not using secure connections (initially everyone) don't
+ * have to diagnose / put up with spurious errors.
+ */
+ if (__pmMakePath(dbpath(nssdb, sizeof(nssdb), "sql:"), 0700) < 0)
+ return 0;
+ secsts = NSS_InitReadWrite(nssdb);
+
+ if (secsts != SECSuccess) {
+ /* fallback, older versions of NSS do not support sql: */
+ dbpath(nssdb, sizeof(nssdb), "");
+ secsts = NSS_InitReadWrite(nssdb);
+ }
+
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ if ((slot = PK11_GetInternalKeySlot()) != NULL) {
+ if (PK11_NeedUserInit(slot))
+ PK11_InitPin(slot, NULL, SECURE_USERDB_DEFAULT_KEY);
+ else if (PK11_NeedLogin(slot))
+ PK11_Authenticate(slot, PR_FALSE, NULL);
+ PK11_FreeSlot(slot);
+ }
+
+ /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */
+ do {
+ const PRUint16 *cipher;
+ for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher)
+ SSL_CipherPolicySet(*cipher, SSL_ALLOWED);
+ } while (0);
+ SSL_ClearSessionCache();
+
+ return 0;
+}
+
+int
+__pmShutdownCertificates(void)
+{
+ if (NSS_Shutdown() != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ return 0;
+}
+
+static void
+saveUserCertificate(CERTCertificate *cert)
+{
+ SECStatus secsts;
+ PK11SlotInfo *slot = PK11_GetInternalKeySlot();
+ CERTCertTrust *trust = NULL;
+
+ secsts = PK11_ImportCert(slot, cert, CK_INVALID_HANDLE,
+ SECURE_SERVER_CERTIFICATE, PR_FALSE);
+ if (secsts != SECSuccess)
+ goto done;
+
+ secsts = SECFailure;
+ trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust));
+ if (!trust)
+ goto done;
+
+ secsts = CERT_DecodeTrustString(trust, "P,P,P");
+ if (secsts != SECSuccess)
+ goto done;
+
+ secsts = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, trust);
+
+done:
+ if (slot)
+ PK11_FreeSlot(slot);
+ if (trust)
+ PORT_Free(trust);
+
+ /*
+ * Issue a warning only, but continue, if we fail to save certificate
+ * (this is not a fatal condition on setting up the secure socket).
+ */
+ if (secsts != SECSuccess) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmprintf("WARNING: Failed to save certificate locally: %s\n",
+ pmErrStr_r(__pmSecureSocketsError(PR_GetError()),
+ errmsg, sizeof(errmsg)));
+ pmflush();
+ }
+}
+
+static int
+rejectUserCertificate(const char *message)
+{
+ pmprintf("%s? (no)\n", message);
+ pmflush();
+ return 0;
+}
+
+#ifdef HAVE_SYS_TERMIOS_H
+static int
+queryCertificateOK(const char *message)
+{
+ int c, fd, sts = 0, count = 0;
+
+ fd = fileno(stdin);
+ /* if we cannot interact, simply assume the answer to be "no". */
+ if (!isatty(fd))
+ return rejectUserCertificate(message);
+
+ do {
+ struct termios saved, raw;
+
+ pmprintf("%s (y/n)? ", message);
+ pmflush();
+
+ /* save terminal state and temporarily enter raw terminal mode */
+ if (tcgetattr(fd, &saved) < 0)
+ return 0;
+ cfmakeraw(&raw);
+ if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
+ return 0;
+
+ c = getchar();
+ if (c == 'y' || c == 'Y')
+ sts = 1; /* yes */
+ else if (c == 'n' || c == 'N')
+ sts = 0; /* no */
+ else
+ sts = -1; /* dunno, try again (3x) */
+ tcsetattr(fd, TCSAFLUSH, &saved);
+ pmprintf("\n");
+ } while (sts == -1 && ++count < 3);
+ pmflush();
+
+ return sts;
+}
+#else
+static int
+queryCertificateOK(const char *message)
+{
+ /* no way implemented to interact to query the user, so decline */
+ return rejectUserCertificate(message);
+}
+#endif
+
+static void
+reportFingerprint(SECItem *item)
+{
+ unsigned char fingerprint[SHA1_LENGTH] = { 0 };
+ SECItem fitem;
+ char *fstring;
+
+ PK11_HashBuf(SEC_OID_SHA1, fingerprint, item->data, item->len);
+ fitem.data = fingerprint;
+ fitem.len = SHA1_LENGTH;
+ fstring = CERT_Hexify(&fitem, 1);
+ pmprintf("SHA1 fingerprint is %s\n", fstring);
+ PORT_Free(fstring);
+}
+
+static SECStatus
+queryCertificateAuthority(PRFileDesc *sslsocket)
+{
+ int sts;
+ int secsts = SECFailure;
+ char *result;
+ CERTCertificate *servercert;
+
+ result = SSL_RevealURL(sslsocket);
+ pmprintf("WARNING: "
+ "issuer of certificate received from host %s is not trusted.\n",
+ result);
+ PORT_Free(result);
+
+ servercert = SSL_PeerCertificate(sslsocket);
+ if (servercert) {
+ reportFingerprint(&servercert->derCert);
+ sts = queryCertificateOK("Do you want to accept and save this certificate locally anyway");
+ if (sts == 1) {
+ saveUserCertificate(servercert);
+ secsts = SECSuccess;
+ }
+ CERT_DestroyCertificate(servercert);
+ } else {
+ pmflush();
+ }
+ return secsts;
+}
+
+static SECStatus
+queryCertificateDomain(PRFileDesc *sslsocket)
+{
+ int sts;
+ char *result;
+ SECItem secitem = { 0 };
+ SECStatus secstatus = SECFailure;
+ PRArenaPool *arena = NULL;
+ CERTCertificate *servercert = NULL;
+
+ /*
+ * Propagate a warning through to the client. Show the expected
+ * host, then list the DNS names from the server certificate.
+ */
+ result = SSL_RevealURL(sslsocket);
+ pmprintf("WARNING: "
+"The domain name %s does not match the DNS name(s) on the server certificate:\n",
+ result);
+ PORT_Free(result);
+
+ servercert = SSL_PeerCertificate(sslsocket);
+ secstatus = CERT_FindCertExtension(servercert,
+ SEC_OID_X509_SUBJECT_ALT_NAME, &secitem);
+ if (secstatus != SECSuccess || !secitem.data) {
+ pmprintf("Unable to find alt name extension on the server certificate\n");
+ } else if ((arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE)) == NULL) {
+ pmprintf("Out of memory while generating name list\n");
+ SECITEM_FreeItem(&secitem, PR_FALSE);
+ } else {
+ CERTGeneralName *namelist, *n;
+
+ namelist = n = CERT_DecodeAltNameExtension(arena, &secitem);
+ SECITEM_FreeItem(&secitem, PR_FALSE);
+ if (!namelist) {
+ pmprintf("Unable to decode alt name extension on server certificate\n");
+ } else {
+ do {
+ if (n->type == certDNSName)
+ pmprintf(" %.*s\n", (int)n->name.other.len, n->name.other.data);
+ n = CERT_GetNextGeneralName(n);
+ } while (n != namelist);
+ }
+ }
+ if (arena)
+ PORT_FreeArena(arena, PR_FALSE);
+ if (servercert)
+ CERT_DestroyCertificate(servercert);
+
+ sts = queryCertificateOK("Do you want to accept this certificate anyway");
+ return (sts == 1) ? SECSuccess : SECFailure;
+}
+
+static SECStatus
+badCertificate(void *arg, PRFileDesc *sslsocket)
+{
+ (void)arg;
+ switch (PR_GetError()) {
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ return queryCertificateDomain(sslsocket);
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ return queryCertificateAuthority(sslsocket);
+ default:
+ break;
+ }
+ return SECFailure;
+}
+
+static int
+__pmAuthLogCB(void *context, int priority, const char *message)
+{
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthLogCB enter ctx=%p pri=%d\n", __FILE__, context, priority);
+
+ if (!message)
+ return SASL_BADPARAM;
+ switch (priority) {
+ case SASL_LOG_NONE:
+ return SASL_OK;
+ case SASL_LOG_ERR:
+ priority = LOG_ERR;
+ break;
+ case SASL_LOG_FAIL:
+ priority = LOG_ALERT;
+ break;
+ case SASL_LOG_WARN:
+ priority = LOG_WARNING;
+ break;
+ case SASL_LOG_NOTE:
+ priority = LOG_NOTICE;
+ break;
+ case SASL_LOG_DEBUG:
+ case SASL_LOG_TRACE:
+ case SASL_LOG_PASS:
+ if (pmDebug & DBG_TRACE_AUTH)
+ priority = LOG_DEBUG;
+ else
+ return SASL_OK;
+ break;
+ default:
+ priority = LOG_INFO;
+ break;
+ }
+ __pmNotifyErr(priority, "%s", message);
+ return SASL_OK;
+}
+
+#if !defined(IS_MINGW)
+static void echoOff(int fd)
+{
+ if (isatty(fd)) {
+ struct termios tio;
+ tcgetattr(fd, &tio);
+ tio.c_lflag &= ~ECHO;
+ tcsetattr(fd, TCSAFLUSH, &tio);
+ }
+}
+
+static void echoOn(int fd)
+{
+ if (isatty(fd)) {
+ struct termios tio;
+ tcgetattr(fd, &tio);
+ tio.c_lflag |= ECHO;
+ tcsetattr(fd, TCSAFLUSH, &tio);
+ }
+}
+#define fgetsQuietly(buf,length,input) fgets(buf,length,input)
+#define consoleName "/dev/tty"
+#else
+#define echoOn(fd) do { } while (0)
+#define echoOff(fd) do { } while (0)
+#define consoleName "CON:"
+static char *fgetsQuietly(char *buf, int length, FILE *input)
+{
+ int c;
+ char *end = buf;
+
+ if (!isatty(fileno(input)))
+ return fgets(buf, length, input);
+ do {
+ c = getch();
+ if (c == '\b') {
+ if (end > buf)
+ end--;
+ }
+ else if (--length > 0)
+ *end++ = c;
+ if (!c || c == '\n' || c == '\r')
+ break;
+ } while (1);
+
+ return buf;
+}
+#endif
+
+static char *fgetsPrompt(FILE *in, FILE *out, const char *prompt, int secret)
+{
+ size_t length;
+ int infd = fileno(in);
+ int isTTY = isatty(infd);
+ char *value, phrase[256];
+
+ if (isTTY) {
+ fprintf(out, "%s", prompt);
+ fflush(out);
+ if (secret)
+ echoOff(infd);
+ }
+
+ memset(phrase, 0, sizeof(phrase));
+ value = fgetsQuietly(phrase, sizeof(phrase)-1, in);
+ if (!value)
+ return strdup("");
+ length = strlen(value) - 1;
+ while (length && (value[length] == '\n' || value[length] == '\r'))
+ value[length] = '\0';
+
+ if (isTTY && secret) {
+ fprintf(out, "\n");
+ echoOn(infd);
+ }
+
+ return strdup(value);
+}
+
+/*
+ * SASL is calling us looking for the value for a specific attribute;
+ * we must respond as best we can:
+ * - if user specified it on the command line, its in the given hash
+ * - if we can interact, we can ask the user for it (taking care for
+ * sensitive info like passwords, not to echo back to the user)
+ * Also take care to handle non-console interactive modes, like the
+ * pmchart case. Further, we should consider a mode where we extract
+ * these values from a per-user config file too (ala. libvirt).
+ *
+ * Return value is a dynamically allocated string, caller must free.
+ */
+
+static char *
+__pmGetAttrConsole(const char *prompt, int secret)
+{
+ FILE *input, *output;
+ char *value, *console;
+
+ /*
+ * Interactive mode: open terminal and discuss with user
+ * For graphical tools, we do not want to ever be here.
+ * For testing, we want to just error out of here ASAP.
+ */
+ console = getenv("PCP_CONSOLE");
+ if (console) {
+ if (strcmp(console, "none") == 0)
+ return NULL;
+ } else {
+ console = consoleName;
+ }
+
+ input = fopen(console, "r");
+ if (input == NULL) {
+ __pmNotifyErr(LOG_ERR, "opening input terminal for read\n");
+ return NULL;
+ }
+ output = fopen(console, "w");
+ if (output == NULL) {
+ __pmNotifyErr(LOG_ERR, "opening output terminal for write\n");
+ fclose(input);
+ return NULL;
+ }
+
+ value = fgetsPrompt(input, output, prompt, secret);
+
+ fclose(input);
+ fclose(output);
+
+ return value;
+}
+
+static char *
+__pmGetAttrValue(__pmAttrKey key, __pmHashCtl *attrs, const char *prompt)
+{
+ __pmHashNode *node;
+ char *value;
+
+ if ((node = __pmHashSearch(key, attrs)) != NULL)
+ return (char *)node->data;
+ value = __pmGetAttrConsole(prompt, key == PCP_ATTR_PASSWORD);
+ if (value) /* must track all our own memory use in SASL */
+ __pmHashAdd(key, value, attrs);
+ return value;
+}
+
+
+static int
+__pmAuthRealmCB(void *context, int id, const char **realms, const char **result)
+{
+ __pmHashCtl *attrs = (__pmHashCtl *)context;
+ char *value = NULL;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthRealmCB enter ctx=%p id=%#x\n", __FILE__, context, id);
+
+ if (id != SASL_CB_GETREALM)
+ return SASL_FAIL;
+
+ value = __pmGetAttrValue(PCP_ATTR_REALM, attrs, "Realm: ");
+ *result = (const char *)value;
+
+ if (pmDebug & DBG_TRACE_AUTH) {
+ fprintf(stderr, "%s:__pmAuthRealmCB ctx=%p, id=%#x, realms=(", __FILE__, context, id);
+ if (realms) {
+ if (*realms)
+ fprintf(stderr, "%s", *realms);
+ for (value = (char *) *(realms + 1); value && *value; value++)
+ fprintf(stderr, " %s", value);
+ }
+ fprintf(stderr, ") -> rslt=%s\n", *result ? *result : "(none)");
+ }
+ return SASL_OK;
+}
+
+static int
+__pmAuthSimpleCB(void *context, int id, const char **result, unsigned *len)
+{
+ __pmHashCtl *attrs = (__pmHashCtl *)context;
+ char *value = NULL;
+ int sts;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthSimpleCB enter ctx=%p id=%#x\n", __FILE__, context, id);
+
+ if (!result)
+ return SASL_BADPARAM;
+
+ sts = SASL_OK;
+ switch (id) {
+ case SASL_CB_USER:
+ case SASL_CB_AUTHNAME:
+ value = __pmGetAttrValue(PCP_ATTR_USERNAME, attrs, "Username: ");
+ break;
+ case SASL_CB_LANGUAGE:
+ break;
+ default:
+ sts = SASL_BADPARAM;
+ break;
+ }
+
+ if (len)
+ *len = value ? strlen(value) : 0;
+ *result = value;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthSimpleCB ctx=%p id=%#x -> sts=%d rslt=%p len=%d\n",
+ __FILE__, context, id, sts, *result, len ? *len : -1);
+ return sts;
+}
+
+static int
+__pmAuthSecretCB(sasl_conn_t *saslconn, void *context, int id, sasl_secret_t **secret)
+{
+ __pmHashCtl *attrs = (__pmHashCtl *)context;
+ size_t length = 0;
+ char *password;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthSecretCB enter ctx=%p id=%#x\n", __FILE__, context, id);
+
+ if (saslconn == NULL || secret == NULL || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ password = __pmGetAttrValue(PCP_ATTR_PASSWORD, attrs, "Password: ");
+ length = password ? strlen(password) : 0;
+
+ *secret = (sasl_secret_t *) calloc(1, sizeof(sasl_secret_t) + length + 1);
+ if (!*secret) {
+ free(password);
+ return SASL_NOMEM;
+ }
+
+ if (password) {
+ (*secret)->len = length;
+ strcpy((char *)(*secret)->data, password);
+ }
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthSecretCB ctx=%p id=%#x -> data=%s len=%u\n",
+ __FILE__, context, id, password, (unsigned)length);
+ free(password);
+
+ return SASL_OK;
+}
+
+static int
+__pmAuthPromptCB(void *context, int id, const char *challenge, const char *prompt,
+ const char *defaultresult, const char **result, unsigned *length)
+{
+ char *value, message[512];
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthPromptCB enter ctx=%p id=%#x\n", __FILE__, context, id);
+
+ if (id != SASL_CB_ECHOPROMPT && id != SASL_CB_NOECHOPROMPT)
+ return SASL_BADPARAM;
+ if (!prompt || !result || !length)
+ return SASL_BADPARAM;
+ if (defaultresult == NULL)
+ defaultresult = "";
+
+ if (!challenge)
+ snprintf(message, sizeof(message), "%s [%s]: ", prompt, defaultresult);
+ else
+ snprintf(message, sizeof(message), "%s [challenge: %s] [%s]: ",
+ prompt, challenge, defaultresult);
+ message[sizeof(message)-1] = '\0';
+
+ if (id == SASL_CB_ECHOPROMPT) {
+ value = __pmGetAttrConsole(message, 0);
+ if (value && value[0] != '\0') {
+ *result = value;
+ } else {
+ free(value);
+ *result = defaultresult;
+ }
+ } else {
+ if (fgets(message, sizeof(message), stdin) == NULL || message[0])
+ *result = strdup(message);
+ else
+ *result = defaultresult;
+ }
+ if (!*result)
+ return SASL_NOMEM;
+
+ *length = (unsigned) strlen(*result);
+ return SASL_OK;
+}
+
+static int
+__pmSecureClientInit(int flags)
+{
+ int sts;
+
+ /* Ensure correct security lib initialisation order */
+ __pmInitSecureSockets();
+
+ /*
+ * If secure sockets functionality available, iterate over the set of
+ * known locations for certificate databases and attempt to initialise
+ * one of them for our use.
+ */
+ sts = 0;
+ if ((flags & PDU_FLAG_NO_NSS_INIT) == 0) {
+ sts = __pmInitCertificates();
+ if (sts < 0)
+ __pmNotifyErr(LOG_WARNING, "__pmConnectPMCD: "
+ "certificate database exists, but failed initialization");
+ }
+ return sts;
+}
+
+static int
+__pmSecureClientIPCFlags(int fd, int flags, const char *hostname, __pmHashCtl *attrs)
+{
+ __pmSecureSocket socket;
+ sasl_callback_t *cb;
+ SECStatus secsts;
+ int sts;
+
+ if (__pmDataIPC(fd, &socket) < 0)
+ return -EOPNOTSUPP;
+
+ if ((flags & PDU_FLAG_SECURE) != 0) {
+ sts = __pmSecureClientInit(flags);
+ if (sts < 0)
+ return sts;
+ sts = __pmSetupSecureSocket(fd, &socket);
+ if (sts < 0)
+ return __pmSecureSocketsError(PR_GetError());
+ if ((socket.sslFd = SSL_ImportFD(NULL, socket.nsprFd)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "SecureClientIPCFlags: importing socket into SSL");
+ return PM_ERR_IPC;
+ }
+ socket.nsprFd = socket.sslFd;
+
+ secsts = SSL_OptionSet(socket.sslFd, SSL_SECURITY, PR_TRUE);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ secsts = SSL_OptionSet(socket.sslFd, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ secsts = SSL_SetURL(socket.sslFd, hostname);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ secsts = SSL_BadCertHook(socket.sslFd,
+ (SSLBadCertHandler)badCertificate, NULL);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ }
+
+ if ((flags & PDU_FLAG_COMPRESS) != 0) {
+ /*
+ * The current implementation of compression requires an SSL/TLS
+ * connection.
+ */
+ if (socket.sslFd == NULL)
+ return -EOPNOTSUPP;
+ secsts = SSL_OptionSet(socket.sslFd, SSL_ENABLE_DEFLATE, PR_TRUE);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ }
+
+ if ((flags & PDU_FLAG_AUTH) != 0) {
+ __pmInitAuthClients();
+ socket.saslCB = calloc(LIMIT_CLIENT_CALLBACKS, sizeof(sasl_callback_t));
+ if ((cb = socket.saslCB) == NULL)
+ return -ENOMEM;
+ cb->id = SASL_CB_USER;
+ cb->proc = (sasl_callback_func)&__pmAuthSimpleCB;
+ cb->context = (void *)attrs;
+ cb++;
+ cb->id = SASL_CB_AUTHNAME;
+ cb->proc = (sasl_callback_func)&__pmAuthSimpleCB;
+ cb->context = (void *)attrs;
+ cb++;
+ cb->id = SASL_CB_LANGUAGE;
+ cb->proc = (sasl_callback_func)&__pmAuthSimpleCB;
+ cb++;
+ cb->id = SASL_CB_GETREALM;
+ cb->proc = (sasl_callback_func)&__pmAuthRealmCB;
+ cb->context = (void *)attrs;
+ cb++;
+ cb->id = SASL_CB_PASS;
+ cb->proc = (sasl_callback_func)&__pmAuthSecretCB;
+ cb->context = (void *)attrs;
+ cb++;
+ cb->id = SASL_CB_ECHOPROMPT;
+ cb->proc = (sasl_callback_func)&__pmAuthPromptCB;
+ cb++;
+ cb->id = SASL_CB_NOECHOPROMPT;
+ cb->proc = (sasl_callback_func)&__pmAuthPromptCB;
+ cb++;
+ cb->id = SASL_CB_LIST_END;
+ cb++;
+ assert(cb - socket.saslCB <= LIMIT_CLIENT_CALLBACKS);
+
+ sts = sasl_client_new(SECURE_SERVER_SASL_SERVICE,
+ hostname,
+ NULL, NULL, /*iplocal,ipremote*/
+ socket.saslCB,
+ 0, &socket.saslConn);
+ if (sts != SASL_OK && sts != SASL_CONTINUE)
+ return __pmSecureSocketsError(sts);
+ }
+
+ /* save changes back into the IPC table (updates client sslFd) */
+ return __pmSetDataIPC(fd, (void *)&socket);
+}
+
+static int
+__pmSecureClientNegotiation(int fd, int *strength)
+{
+ PRIntervalTime timer;
+ PRFileDesc *sslsocket;
+ SECStatus secsts;
+ int enabled, keysize;
+ int msec;
+
+ sslsocket = (PRFileDesc *)__pmGetSecureSocket(fd);
+ if (!sslsocket)
+ return -EINVAL;
+
+ secsts = SSL_ResetHandshake(sslsocket, PR_FALSE /*client*/);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ msec = __pmConvertTimeout(TIMEOUT_DEFAULT);
+ timer = PR_MillisecondsToInterval(msec);
+ secsts = SSL_ForceHandshakeWithTimeout(sslsocket, timer);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ secsts = SSL_SecurityStatus(sslsocket, &enabled, NULL, &keysize, NULL, NULL, NULL);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ *strength = (enabled > 0) ? keysize : DEFAULT_SECURITY_STRENGTH;
+ return 0;
+}
+
+static void
+__pmInitAuthPaths(void)
+{
+ char *path;
+
+ if ((path = getenv("PCP_SASL2_PLUGIN_PATH")) != NULL)
+ sasl_set_path(SASL_PATH_TYPE_PLUGIN, path);
+ if ((path = getenv("PCP_SASL2_CONFIG_PATH")) != NULL)
+ sasl_set_path(SASL_PATH_TYPE_CONFIG, path);
+}
+
+static sasl_callback_t common_callbacks[] = { \
+ { .id = SASL_CB_LOG, .proc = (sasl_callback_func)&__pmAuthLogCB },
+ { .id = SASL_CB_LIST_END }};
+
+int
+__pmInitAuthClients(void)
+{
+ __pmInitAuthPaths();
+ if (sasl_client_init(common_callbacks) != SASL_OK)
+ return -EINVAL;
+ return 0;
+}
+
+int
+__pmInitAuthServer(void)
+{
+ __pmInitAuthPaths();
+ if (sasl_server_init(common_callbacks, pmProgname) != SASL_OK) {
+ __pmNotifyErr(LOG_ERR, "Failed to start authenticating server");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+__pmAuthClientSetProperties(sasl_conn_t *saslconn, int ssf)
+{
+ int sts;
+ sasl_security_properties_t props;
+
+ /* set external security strength factor */
+ if ((sts = sasl_setprop(saslconn, SASL_SSF_EXTERNAL, &ssf)) != SASL_OK)
+ return __pmSecureSocketsError(sts);
+
+ /* set general security properties */
+ memset(&props, 0, sizeof(props));
+ props.maxbufsize = LIMIT_AUTH_PDU;
+ props.max_ssf = UINT_MAX;
+ if ((sts = sasl_setprop(saslconn, SASL_SEC_PROPS, &props)) != SASL_OK)
+ return __pmSecureSocketsError(sts);
+
+ return 0;
+}
+
+static int
+__pmAuthClientNegotiation(int fd, int ssf, const char *hostname, __pmHashCtl *attrs)
+{
+ int sts, zero, saslsts = SASL_FAIL;
+ int pinned, length, method_length;
+ char *payload, buffer[LIMIT_AUTH_PDU];
+ const char *method = NULL;
+ sasl_conn_t *saslconn;
+ __pmHashNode *node;
+ __pmPDU *pb;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthClientNegotiation(fd=%d, ssf=%d, host=%s)\n",
+ __FILE__, fd, ssf, hostname);
+
+ if ((saslconn = (sasl_conn_t *)__pmGetUserAuthData(fd)) == NULL)
+ return -EINVAL;
+
+ /* setup all the security properties for this connection */
+ if ((sts = __pmAuthClientSetProperties(saslconn, ssf)) < 0)
+ return sts;
+
+ /* lookup users preferred connection method, if specified */
+ if ((node = __pmHashSearch(PCP_ATTR_METHOD, attrs)) != NULL)
+ method = (const char *)node->data;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthClientNegotiation requesting \"%s\" method\n",
+ __FILE__, method ? method : "default");
+
+ /* get security mechanism list */
+ sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ if (sts == PDU_AUTH) {
+ sts = __pmDecodeAuth(pb, &zero, &payload, &length);
+ if (sts >= 0) {
+ strncpy(buffer, payload, length);
+ buffer[length] = '\0';
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthClientNegotiation got methods: "
+ "\"%s\" (%d)\n", __FILE__, buffer, length);
+ /*
+ * buffer now contains the list of server mechanisms -
+ * override using users preference (if any) and proceed.
+ */
+ if (method) {
+ strncpy(buffer, method, sizeof(buffer));
+ buffer[sizeof(buffer) - 1] = '\0';
+ length = strlen(buffer);
+ }
+
+ payload = NULL;
+ saslsts = sasl_client_start(saslconn, buffer, NULL,
+ (const char **)&payload,
+ (unsigned int *)&length, &method);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ sts = __pmSecureSocketsError(saslsts);
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_client_start failed: %d (%s)\n",
+ saslsts, pmErrStr(sts));
+ }
+ }
+ } else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ } else if (sts != PM_ERR_TIMEOUT) {
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinned)
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0)
+ return sts;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_client_start chose \"%s\" method, saslsts=%s\n",
+ method, saslsts == SASL_CONTINUE ? "continue" : "ok");
+
+ /* tell server we've made a decision and are ready to move on */
+ strncpy(buffer, method, sizeof(buffer));
+ buffer[sizeof(buffer) - 1] = '\0';
+ method_length = strlen(buffer);
+ if (payload) {
+ if (LIMIT_AUTH_PDU - method_length - 1 < length)
+ return -E2BIG;
+ memcpy(buffer + method_length + 1, payload, length);
+ length += method_length + 1;
+ } else {
+ length = method_length + 1;
+ }
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_client_start sending (%d bytes) \"%s\"\n",
+ length, buffer);
+
+ if ((sts = __pmSendAuth(fd, FROM_ANON, 0, buffer, length)) < 0)
+ return sts;
+
+ while (saslsts == SASL_CONTINUE) {
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmAuthClientNegotiation awaiting server reply\n", __FILE__);
+
+ sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ if (sts == PDU_AUTH) {
+ sts = __pmDecodeAuth(pb, &zero, &payload, &length);
+ if (sts >= 0) {
+ saslsts = sasl_client_step(saslconn, payload, length, NULL,
+ (const char **)&buffer,
+ (unsigned int *)&length);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ sts = __pmSecureSocketsError(saslsts);
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_client_step failed: %d (%s)\n",
+ saslsts, pmErrStr(sts));
+ break;
+ }
+ if (pmDebug & DBG_TRACE_AUTH) {
+ fprintf(stderr, "%s:__pmAuthClientNegotiation"
+ " step recv (%d bytes)", __FILE__, length);
+ }
+ }
+ } else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ } else if (sts != PM_ERR_TIMEOUT) {
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinned)
+ __pmUnpinPDUBuf(pb);
+ if (sts >= 0)
+ sts = __pmSendAuth(fd, FROM_ANON, 0, length ? buffer : "", length);
+ if (sts < 0)
+ break;
+ }
+
+ if (pmDebug & DBG_TRACE_AUTH) {
+ if (sts < 0)
+ fprintf(stderr, "%s:__pmAuthClientNegotiation loop failed\n", __FILE__);
+ else {
+ saslsts = sasl_getprop(saslconn, SASL_USERNAME, (const void **)&payload);
+ fprintf(stderr, "%s:__pmAuthClientNegotiation success, username=%s\n",
+ __FILE__, saslsts != SASL_OK ? "?" : payload);
+ }
+ }
+
+ return sts;
+}
+
+int
+__pmSecureClientHandshake(int fd, int flags, const char *hostname, __pmHashCtl *attrs)
+{
+ int sts, ssf = DEFAULT_SECURITY_STRENGTH;
+
+ /*
+ * If the server uses the secure-ack protocol, then expect an error
+ * pdu here containing the server's secure status. If the status is zero,
+ * then all is ok, otherwise, return the status to the caller.
+ */
+ if (flags & PDU_FLAG_SECURE_ACK) {
+ __pmPDU *rpdu;
+ int pinpdu;
+ int serverSts;
+ pinpdu = sts = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &rpdu);
+ if (sts != PDU_ERROR) {
+ if (pinpdu)
+ __pmUnpinPDUBuf(&rpdu);
+ return -PM_ERR_IPC;
+ }
+ sts = __pmDecodeError (rpdu, &serverSts);
+ if (pinpdu)
+ __pmUnpinPDUBuf(&rpdu);
+ if (sts < 0)
+ return sts;
+ if (serverSts < 0)
+ return serverSts;
+ }
+
+ if (flags & PDU_FLAG_CREDS_REQD) {
+ if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL)
+ return 0;
+ flags |= PDU_FLAG_AUTH; /* force the use of SASL authentication */
+ }
+ if ((sts = __pmSecureClientIPCFlags(fd, flags, hostname, attrs)) < 0)
+ return sts;
+ if (((flags & PDU_FLAG_SECURE) != 0) &&
+ ((sts = __pmSecureClientNegotiation(fd, &ssf)) < 0))
+ return sts;
+ if (((flags & PDU_FLAG_AUTH) != 0) &&
+ ((sts = __pmAuthClientNegotiation(fd, ssf, hostname, attrs)) < 0))
+ return sts;
+ return 0;
+}
+
+void *
+__pmGetSecureSocket(int fd)
+{
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) < 0)
+ return NULL;
+ return (void *)socket.sslFd;
+}
+
+void *
+__pmGetUserAuthData(int fd)
+{
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) < 0)
+ return NULL;
+ return (void *)socket.saslConn;
+}
+
+static void
+sendSecureAck(int fd, int flags, int sts) {
+ /*
+ * At this point we've attempted some required initialization for secure
+ * sockets. If the client wants a secure-ack then send an error pdu
+ * containing our status. The client will then know whether or not to
+ * proceed with the secure handshake.
+ */
+ if (flags & PDU_FLAG_SECURE_ACK)
+ __pmSendError (fd, FROM_ANON, sts);
+}
+
+int
+__pmSecureServerIPCFlags(int fd, int flags)
+{
+ __pmSecureSocket socket;
+ SECStatus secsts;
+ int saslsts;
+ int sts;
+
+ if (__pmDataIPC(fd, &socket) < 0)
+ return -EOPNOTSUPP;
+
+ if ((flags & PDU_FLAG_SECURE) != 0) {
+ sts = __pmSecureServerInit();
+ if (sts < 0) {
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ sts = __pmSetupSecureSocket(fd, &socket);
+ if (sts < 0) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ if ((socket.sslFd = SSL_ImportFD(NULL, socket.nsprFd)) == NULL) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ socket.nsprFd = socket.sslFd;
+
+ secsts = SSL_OptionSet(socket.sslFd, SSL_NO_LOCKS, PR_TRUE);
+ if (secsts != SECSuccess) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ secsts = SSL_OptionSet(socket.sslFd, SSL_SECURITY, PR_TRUE);
+ if (secsts != SECSuccess) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ secsts = SSL_OptionSet(socket.sslFd, SSL_HANDSHAKE_AS_SERVER, PR_TRUE);
+ if (secsts != SECSuccess) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ secsts = SSL_OptionSet(socket.sslFd, SSL_REQUEST_CERTIFICATE, PR_FALSE);
+ if (secsts != SECSuccess) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ secsts = SSL_OptionSet(socket.sslFd, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+ if (secsts != SECSuccess) {
+ sts = __pmSecureSocketsError(PR_GetError());
+ sendSecureAck(fd, flags, sts);
+ return sts;
+ }
+ sendSecureAck(fd, flags, sts);
+ }
+
+ if ((flags & PDU_FLAG_COMPRESS) != 0) {
+ /*
+ * The current implementation of compression requires an SSL/TLS
+ * connection.
+ */
+ if (socket.sslFd == NULL)
+ return -EOPNOTSUPP;
+ secsts = SSL_OptionSet(socket.sslFd, SSL_ENABLE_DEFLATE, PR_TRUE);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+ }
+
+ if ((flags & PDU_FLAG_AUTH) != 0) {
+ sts = __pmInitAuthServer();
+ if (sts < 0)
+ return sts;
+ saslsts = sasl_server_new(SECURE_SERVER_SASL_SERVICE,
+ NULL, NULL, /*localdomain,userdomain*/
+ NULL, NULL, NULL, /*iplocal,ipremote,callbacks*/
+ 0, &socket.saslConn);
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "%s:__pmSecureServerIPCFlags SASL server: %d\n", __FILE__, saslsts);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE)
+ return __pmSecureSocketsError(saslsts);
+ }
+
+ /* save changes back into the IPC table */
+ return __pmSetDataIPC(fd, (void *)&socket);
+}
+
+static int
+sockOptValue(const void *option_value, __pmSockLen option_len)
+{
+ switch(option_len) {
+ case sizeof(int):
+ return *(int *)option_value;
+ default:
+ __pmNotifyErr(LOG_ERR, "sockOptValue: invalid option length: %d\n", option_len);
+ break;
+ }
+ return 0;
+}
+
+int
+__pmSetSockOpt(int fd, int level, int option_name, const void *option_value,
+ __pmSockLen option_len)
+{
+ /* Map the request to the NSPR equivalent, if possible. */
+ PRSocketOptionData option_data;
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+ switch(level) {
+ case SOL_SOCKET:
+ switch(option_name) {
+ /*
+ * These options are not related. They are just both options for which
+ * NSPR has no direct mapping.
+ */
+#ifdef IS_MINGW
+ case SO_EXCLUSIVEADDRUSE: /* Only exists on MINGW */
+#endif
+ {
+ /*
+ * There is no direct mapping of this option in NSPR.
+ * The best we can do is to use the native handle and
+ * call setsockopt on that handle.
+ */
+ fd = PR_FileDesc2NativeHandle(socket.nsprFd);
+ return setsockopt(fd, level, option_name, option_value, option_len);
+ }
+ case SO_KEEPALIVE:
+ option_data.option = PR_SockOpt_Keepalive;
+ option_data.value.keep_alive = sockOptValue(option_value, option_len);
+ break;
+ case SO_LINGER: {
+ struct linger *linger = (struct linger *)option_value;
+ option_data.option = PR_SockOpt_Linger;
+ option_data.value.linger.polarity = linger->l_onoff;
+ option_data.value.linger.linger = linger->l_linger;
+ break;
+ }
+ case SO_REUSEADDR:
+ option_data.option = PR_SockOpt_Reuseaddr;
+ option_data.value.reuse_addr = sockOptValue(option_value, option_len);
+ break;
+ default:
+ __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for SOL_SOCKET: %d\n",
+ __FILE__, option_name);
+ return -1;
+ }
+ break;
+ case IPPROTO_TCP:
+ if (option_name == TCP_NODELAY) {
+ option_data.option = PR_SockOpt_NoDelay;
+ option_data.value.no_delay = sockOptValue(option_value, option_len);
+ break;
+ }
+ __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for IPPROTO_TCP: %d\n",
+ __FILE__, option_name);
+ return -1;
+ case IPPROTO_IPV6:
+ if (option_name == IPV6_V6ONLY) {
+ /*
+ * There is no direct mapping of this option in NSPR.
+ * The best we can do is to use the native handle and
+ * call setsockopt on that handle.
+ */
+ fd = PR_FileDesc2NativeHandle(socket.nsprFd);
+ return setsockopt(fd, level, option_name, option_value, option_len);
+ }
+ __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented option_name for IPPROTO_IPV6: %d\n",
+ __FILE__, option_name);
+ return -1;
+ default:
+ __pmNotifyErr(LOG_ERR, "%s:__pmSetSockOpt: unimplemented level: %d\n", __FILE__, level);
+ return -1;
+ }
+
+ return (PR_SetSocketOption(socket.nsprFd, &option_data)
+ == PR_SUCCESS) ? 0 : -1;
+ }
+
+ /* We have a native socket. */
+ return setsockopt(fd, level, option_name, option_value, option_len);
+}
+
+int
+__pmGetSockOpt(int fd, int level, int option_name, void *option_value,
+ __pmSockLen *option_len)
+{
+ __pmSecureSocket socket;
+
+ /* Map the request to the NSPR equivalent, if possible. */
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+ switch (level) {
+ case SOL_SOCKET:
+ switch(option_name) {
+
+#if defined(HAVE_STRUCT_UCRED)
+ case SO_PEERCRED:
+#endif
+ case SO_ERROR: {
+ /*
+ * There is no direct mapping of this option in NSPR.
+ * Best we can do is call getsockopt on the native fd.
+ */
+ fd = PR_FileDesc2NativeHandle(socket.nsprFd);
+ return getsockopt(fd, level, option_name, option_value, option_len);
+ }
+ default:
+ __pmNotifyErr(LOG_ERR,
+ "%s:__pmGetSockOpt: unimplemented option_name for SOL_SOCKET: %d\n",
+ __FILE__, option_name);
+ return -1;
+ }
+ break;
+
+ default:
+ __pmNotifyErr(LOG_ERR, "%s:__pmGetSockOpt: unimplemented level: %d\n", __FILE__, level);
+ break;
+ }
+ return -1;
+ }
+
+ /* We have a native socket. */
+ return getsockopt(fd, level, option_name, option_value, option_len);
+}
+
+ssize_t
+__pmWrite(int fd, const void *buffer, size_t length)
+{
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+ ssize_t size = PR_Write(socket.nsprFd, buffer, length);
+ if (size < 0)
+ __pmSecureSocketsError(PR_GetError());
+ return size;
+ }
+ return write(fd, buffer, length);
+}
+
+ssize_t
+__pmRead(int fd, void *buffer, size_t length)
+{
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+ ssize_t size = PR_Read(socket.nsprFd, buffer, length);
+ if (size < 0)
+ __pmSecureSocketsError(PR_GetError());
+ return size;
+ }
+ return read(fd, buffer, length);
+}
+
+ssize_t
+__pmSend(int fd, const void *buffer, size_t length, int flags)
+{
+ __pmSecureSocket socket;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+ ssize_t size = PR_Write(socket.nsprFd, buffer, length);
+ if (size < 0)
+ __pmSecureSocketsError(PR_GetError());
+ return size;
+ }
+ return send(fd, buffer, length, flags);
+}
+
+ssize_t
+__pmRecv(int fd, void *buffer, size_t length, int flags)
+{
+ __pmSecureSocket socket;
+ ssize_t size;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.nsprFd) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "%s:__pmRecv[secure](", __FILE__);
+ }
+#endif
+ size = PR_Read(socket.nsprFd, buffer, length);
+ if (size < 0)
+ __pmSecureSocketsError(PR_GetError());
+ }
+ else {
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "%s:__pmRecv(", __FILE__);
+ }
+#endif
+ size = recv(fd, buffer, length, flags);
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_PDU) && (pmDebug & DBG_TRACE_DESPERATE)) {
+ fprintf(stderr, "%d, ..., %d, " PRINTF_P_PFX "%x) -> %d\n",
+ fd, (int)length, flags, (int)size);
+ }
+#endif
+ return size;
+}
+
+/*
+ * In certain situations, we need to allow access to previously-read
+ * data on a socket. This is because, for example, the SSL protocol
+ * buffering may have already consumed data that we are now expecting
+ * (in this case, its buffered internally and a socket read will give
+ * up that data).
+ *
+ * PR_Poll does not seem to play well here and so we need to use the
+ * native select-based mechanism to block and/or query the state of
+ * pending data.
+ */
+int
+__pmSocketReady(int fd, struct timeval *timeout)
+{
+ __pmSecureSocket socket;
+ __pmFdSet onefd;
+
+ if (__pmDataIPC(fd, &socket) == 0 && socket.sslFd)
+ if (SSL_DataPending(socket.sslFd))
+ return 1; /* proceed without blocking */
+
+ FD_ZERO(&onefd);
+ FD_SET(fd, &onefd);
+ return select(fd+1, &onefd, NULL, NULL, timeout);
+}
diff --git a/src/libpcp/src/secureserver.c b/src/libpcp/src/secureserver.c
new file mode 100644
index 0000000..a8c0629
--- /dev/null
+++ b/src/libpcp/src/secureserver.c
@@ -0,0 +1,719 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ *
+ * Server side security features - via Network Security Services (NSS) and
+ * the Simple Authentication and Security Layer (SASL).
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#define SOCKET_INTERNAL
+#include "internal.h"
+#include <keyhi.h>
+#include <secder.h>
+#include <pk11pub.h>
+#include <sys/stat.h>
+
+#define MAX_NSSDB_PASSWORD_LENGTH 256
+
+static struct {
+ /* NSS certificate management */
+ CERTCertificate *certificate;
+ SECKEYPrivateKey *private_key;
+ const char *password_file;
+ SSLKEAType certificate_KEA;
+ char database_path[MAXPATHLEN];
+
+ /* status flags (bitfields) */
+ unsigned int initialized : 1;
+ unsigned int init_failed : 1;
+ unsigned int certificate_verified : 1; /* NSS */
+ unsigned int ssl_session_cache_setup : 1; /* NSS */
+} secure_server;
+
+int
+__pmSecureServerSetFeature(__pmServerFeature wanted)
+{
+ (void)wanted;
+ return 0; /* nothing dynamically enabled at this stage */
+}
+
+int
+__pmSecureServerClearFeature(__pmServerFeature clear)
+{
+ (void)clear;
+ return 0; /* nothing dynamically disabled at this stage */
+}
+
+int
+__pmSecureServerHasFeature(__pmServerFeature query)
+{
+ int sts = 0;
+
+ switch (query) {
+ case PM_SERVER_FEATURE_SECURE:
+ return ! secure_server.init_failed;
+ case PM_SERVER_FEATURE_COMPRESS:
+ case PM_SERVER_FEATURE_AUTH:
+ sts = 1;
+ break;
+ default:
+ break;
+ }
+ return sts;
+}
+
+static int
+secure_file_contents(const char *filename, char **passwd, size_t *length)
+{
+ struct stat stat;
+ size_t size = *length;
+ char *pass = NULL;
+ FILE *file = NULL;
+ int sts;
+
+ if ((file = fopen(filename, "r")) == NULL)
+ goto fail;
+ if (fstat(fileno(file), &stat) < 0)
+ goto fail;
+ if (stat.st_size > size) {
+ setoserror(E2BIG);
+ goto fail;
+ }
+ if ((pass = (char *)PORT_Alloc(stat.st_size)) == NULL) {
+ setoserror(ENOMEM);
+ goto fail;
+ }
+ sts = fread(pass, 1, stat.st_size, file);
+ if (sts < 1) {
+ setoserror(EINVAL);
+ goto fail;
+ }
+ while (sts > 0 && (pass[sts-1] == '\r' || pass[sts-1] == '\n'))
+ pass[--sts] = '\0';
+ *passwd = pass;
+ *length = sts;
+ fclose(file);
+ return 0;
+
+fail:
+ sts = -oserror();
+ if (file)
+ fclose(file);
+ if (pass)
+ PORT_Free(pass);
+ return sts;
+}
+
+static char *
+certificate_database_password(PK11SlotInfo *info, PRBool retry, void *arg)
+{
+ size_t length = MAX_NSSDB_PASSWORD_LENGTH;
+ char *password = NULL;
+ char passfile[MAXPATHLEN];
+ int sts;
+
+ (void)arg;
+ (void)info;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ passfile[0] = '\0';
+ if (secure_server.password_file)
+ strncpy(passfile, secure_server.password_file, MAXPATHLEN-1);
+ passfile[MAXPATHLEN-1] = '\0';
+ PM_UNLOCK(__pmLock_libpcp);
+
+ if (passfile[0] == '\0') {
+ __pmNotifyErr(LOG_ERR, "Password sought but no password file given");
+ return NULL;
+ }
+ if (retry) {
+ __pmNotifyErr(LOG_ERR, "Retry attempted during password extraction");
+ return NULL; /* no soup^Wretries for you */
+ }
+
+ sts = secure_file_contents(passfile, &password, &length);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot read password file \"%s\": %s",
+ passfile, pmErrStr(sts));
+ return NULL;
+ }
+ return password;
+}
+
+static int
+__pmCertificateTimestamp(SECItem *vtime, char *buffer, size_t size)
+{
+ PRExplodedTime exploded;
+ SECStatus secsts;
+ int64 itime;
+
+ switch (vtime->type) {
+ case siUTCTime:
+ secsts = DER_UTCTimeToTime(&itime, vtime);
+ break;
+ case siGeneralizedTime:
+ secsts = DER_GeneralizedTimeToTime(&itime, vtime);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ /* Convert to local time */
+ PR_ExplodeTime(itime, PR_GMTParameters, &exploded);
+ if (!PR_FormatTime(buffer, size, "%a %b %d %H:%M:%S %Y", &exploded))
+ return __pmSecureSocketsError(PR_GetError());
+ return 0;
+}
+
+static void
+__pmDumpCertificate(FILE *fp, const char *nickname, CERTCertificate *cert)
+{
+ CERTValidity *valid = &cert->validity;
+ char tbuf[256];
+
+ fprintf(fp, "Certificate: %s", nickname);
+ if (__pmCertificateTimestamp(&valid->notBefore, tbuf, sizeof(tbuf)) == 0)
+ fprintf(fp, " Not Valid Before: %s UTC", tbuf);
+ if (__pmCertificateTimestamp(&valid->notAfter, tbuf, sizeof(tbuf)) == 0)
+ fprintf(fp, " Not Valid After: %s UTC", tbuf);
+}
+
+static int
+__pmValidCertificate(CERTCertDBHandle *db, CERTCertificate *cert, PRTime stamp)
+{
+ SECCertificateUsage usage = certificateUsageSSLServer;
+ SECStatus secsts = CERT_VerifyCertificate(db, cert, PR_TRUE, usage,
+ stamp, NULL, NULL, &usage);
+ return (secsts == SECSuccess);
+}
+
+static char *
+serverdb(char *path, size_t size, char *db_method)
+{
+ int sep = __pmPathSeparator();
+ char *nss_method = getenv("PCP_SECURE_DB_METHOD");
+
+ if (nss_method == NULL)
+ nss_method = db_method;
+
+ /*
+ * Fill in a buffer with the server NSS database specification.
+ * Return a pointer to the filesystem path component - without
+ * the <method>:-prefix - for other routines to work with.
+ */
+ snprintf(path, size, "%s" "%c" "etc" "%c" "pki" "%c" "nssdb",
+ nss_method, sep, sep, sep);
+ return path + strlen(nss_method);
+}
+
+int
+__pmSecureServerSetup(const char *db, const char *passwd)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ /* Configure optional (cmdline) password file in case DB locked */
+ secure_server.password_file = passwd;
+
+ /*
+ * Configure location of the NSS database with a sane default.
+ * For servers, we default to the shared (sql) system-wide database.
+ * If command line db specified, pass it directly through - allowing
+ * any old database format, at the users discretion.
+ */
+ if (db) {
+ /* shortened-buffer-size (-2) guarantees null-termination */
+ strncpy(secure_server.database_path, db, MAXPATHLEN-2);
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+int
+__pmSecureServerInit(void)
+{
+ const char *nickname = SECURE_SERVER_CERTIFICATE;
+ SECStatus secsts;
+ int pathSpecified;
+ int sts = 0;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ /* Only attempt this once. */
+ if (secure_server.initialized)
+ goto done;
+ secure_server.initialized = 1;
+
+ if (PR_Initialized() != PR_TRUE)
+ PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
+
+ /* Configure optional (cmdline) password file in case DB locked */
+ PK11_SetPasswordFunc(certificate_database_password);
+
+ /*
+ * Configure location of the NSS database with a sane default.
+ * For servers, we default to the shared (sql) system-wide database.
+ * If command line db specified, pass it directly through - allowing
+ * any old database format, at the users discretion.
+ */
+ if (!secure_server.database_path[0]) {
+ const char *path;
+ pathSpecified = 0;
+ path = serverdb(secure_server.database_path, MAXPATHLEN, "sql:");
+
+ /* this is the default case on some platforms, so no log spam */
+ if (access(path, R_OK|X_OK) < 0) {
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ __pmNotifyErr(LOG_INFO,
+ "Cannot access system security database: %s",
+ secure_server.database_path);
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ }
+ }
+ else
+ pathSpecified = 1;
+
+ secsts = NSS_Init(secure_server.database_path);
+ if (secsts != SECSuccess && !pathSpecified) {
+ /* fallback, older versions of NSS do not support sql: */
+ serverdb(secure_server.database_path, MAXPATHLEN, "");
+ secsts = NSS_Init(secure_server.database_path);
+ }
+
+ if (secsts != SECSuccess) {
+ __pmNotifyErr(LOG_ERR, "Cannot setup certificate DB (%s): %s",
+ secure_server.database_path,
+ pmErrStr(__pmSecureSocketsError(PR_GetError())));
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ }
+
+ /* Some NSS versions don't do this correctly in NSS_SetDomesticPolicy. */
+ do {
+ const PRUint16 *cipher;
+ for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher)
+ SSL_CipherPolicySet(*cipher, SSL_ALLOWED);
+ } while (0);
+
+ /* Configure SSL session cache for multi-process server, using defaults */
+ secsts = SSL_ConfigMPServerSIDCache(1, 0, 0, NULL);
+ if (secsts != SECSuccess) {
+ __pmNotifyErr(LOG_ERR, "Unable to configure SSL session ID cache: %s",
+ pmErrStr(__pmSecureSocketsError(PR_GetError())));
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ } else {
+ secure_server.ssl_session_cache_setup = 1;
+ }
+
+ /*
+ * Iterate over any/all PCP Collector nickname certificates,
+ * seeking one valid certificate. No-such-nickname is not an
+ * error (not configured by admin at all) but anything else is.
+ */
+ CERTCertList *certlist;
+ CERTCertDBHandle *nssdb = CERT_GetDefaultCertDB();
+ CERTCertificate *dbcert = PK11_FindCertFromNickname(nickname, NULL);
+
+ if (dbcert) {
+ PRTime now = PR_Now();
+ SECItem *name = &dbcert->derSubject;
+ CERTCertListNode *node;
+
+ certlist = CERT_CreateSubjectCertList(NULL, nssdb, name, now, PR_FALSE);
+ if (certlist) {
+ for (node = CERT_LIST_HEAD(certlist);
+ !CERT_LIST_END(node, certlist);
+ node = CERT_LIST_NEXT (node)) {
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ __pmDumpCertificate(stderr, nickname, node->cert);
+ if (!__pmValidCertificate(nssdb, node->cert, now))
+ continue;
+ secure_server.certificate_verified = 1;
+ break;
+ }
+ CERT_DestroyCertList(certlist);
+ }
+
+ if (secure_server.certificate_verified) {
+ secure_server.certificate_KEA = NSS_FindCertKEAType(dbcert);
+ secure_server.private_key = PK11_FindKeyByAnyCert(dbcert, NULL);
+ if (!secure_server.private_key) {
+ __pmNotifyErr(LOG_ERR, "Unable to extract %s private key",
+ nickname);
+ CERT_DestroyCertificate(dbcert);
+ secure_server.certificate_verified = 0;
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "Unable to find a valid %s", nickname);
+ CERT_DestroyCertificate(dbcert);
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ }
+ }
+
+ if (! secure_server.certificate_verified) {
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ __pmNotifyErr(LOG_INFO, "No valid %s in security database: %s",
+ nickname, secure_server.database_path);
+ }
+ sts = -EOPNOTSUPP; /* not fatal - just no secure connections */
+ secure_server.init_failed = 1;
+ goto done;
+ }
+
+ secure_server.certificate = dbcert;
+ secure_server.init_failed = 0;
+ sts = 0;
+
+done:
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+void
+__pmSecureServerShutdown(void)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (secure_server.certificate) {
+ CERT_DestroyCertificate(secure_server.certificate);
+ secure_server.certificate = NULL;
+ }
+ if (secure_server.private_key) {
+ SECKEY_DestroyPrivateKey(secure_server.private_key);
+ secure_server.private_key = NULL;
+ }
+ if (secure_server.ssl_session_cache_setup) {
+ SSL_ShutdownServerSessionIDCache();
+ secure_server.ssl_session_cache_setup = 0;
+ }
+ if (secure_server.initialized) {
+ NSS_Shutdown();
+ secure_server.initialized = 0;
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+static int
+__pmSecureServerNegotiation(int fd, int *strength)
+{
+ PRIntervalTime timer;
+ PRFileDesc *sslsocket;
+ SECStatus secsts;
+ int enabled, keysize;
+ int msec;
+
+ sslsocket = (PRFileDesc *)__pmGetSecureSocket(fd);
+ if (!sslsocket)
+ return PM_ERR_IPC;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ secsts = SSL_ConfigSecureServer(sslsocket,
+ secure_server.certificate,
+ secure_server.private_key,
+ secure_server.certificate_KEA);
+ PM_UNLOCK(__pmLock_libpcp);
+
+ if (secsts != SECSuccess) {
+ __pmNotifyErr(LOG_ERR, "Unable to configure secure server: %s",
+ pmErrStr(__pmSecureSocketsError(PR_GetError())));
+ return PM_ERR_IPC;
+ }
+
+ secsts = SSL_ResetHandshake(sslsocket, PR_TRUE /*server*/);
+ if (secsts != SECSuccess) {
+ __pmNotifyErr(LOG_ERR, "Unable to reset secure handshake: %s",
+ pmErrStr(__pmSecureSocketsError(PR_GetError())));
+ return PM_ERR_IPC;
+ }
+
+ /* Server initiates handshake now to get early visibility of errors */
+ msec = __pmConvertTimeout(TIMEOUT_DEFAULT);
+ timer = PR_MillisecondsToInterval(msec);
+ secsts = SSL_ForceHandshakeWithTimeout(sslsocket, timer);
+ if (secsts != SECSuccess) {
+ __pmNotifyErr(LOG_ERR, "Unable to force secure handshake: %s",
+ pmErrStr(__pmSecureSocketsError(PR_GetError())));
+ return PM_ERR_IPC;
+ }
+
+ secsts = SSL_SecurityStatus(sslsocket, &enabled, NULL, &keysize, NULL, NULL, NULL);
+ if (secsts != SECSuccess)
+ return __pmSecureSocketsError(PR_GetError());
+
+ *strength = (enabled > 0) ? keysize : DEFAULT_SECURITY_STRENGTH;
+ return 0;
+}
+
+static int
+__pmSetUserGroupAttributes(const char *username, __pmHashCtl *attrs)
+{
+ char name[32];
+ char *namep;
+ uid_t uid;
+ gid_t gid;
+
+ if (__pmGetUserIdentity(username, &uid, &gid, PM_RECOV_ERR) == 0) {
+ snprintf(name, sizeof(name), "%u", uid);
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_USERID, namep, attrs);
+ else
+ return -ENOMEM;
+
+ snprintf(name, sizeof(name), "%u", gid);
+ name[sizeof(name)-1] = '\0';
+ if ((namep = strdup(name)) != NULL)
+ __pmHashAdd(PCP_ATTR_GROUPID, namep, attrs);
+ else
+ return -ENOMEM;
+ return 0;
+ }
+ __pmNotifyErr(LOG_ERR, "Authenticated user %s not found\n", username);
+ return -ESRCH;
+}
+
+static int
+__pmAuthServerSetAttributes(sasl_conn_t *conn, __pmHashCtl *attrs)
+{
+ const void *property = NULL;
+ char *username;
+ int sts;
+
+ sts = sasl_getprop(conn, SASL_USERNAME, &property);
+ username = (char *)property;
+ if (sts == SASL_OK && username) {
+ __pmNotifyErr(LOG_INFO,
+ "Successful authentication for user \"%s\"\n",
+ username);
+ if ((username = strdup(username)) == NULL) {
+ __pmNoMem("__pmAuthServerSetAttributes",
+ strlen(username), PM_RECOV_ERR);
+ return -ENOMEM;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR,
+ "Authentication complete, but no username\n");
+ return -ESRCH;
+ }
+
+ if ((sts = __pmHashAdd(PCP_ATTR_USERNAME, username, attrs)) < 0)
+ return sts;
+ return __pmSetUserGroupAttributes(username, attrs);
+}
+
+static int
+__pmAuthServerSetProperties(sasl_conn_t *conn, int ssf)
+{
+ int saslsts;
+ sasl_security_properties_t props;
+
+ /* set external security strength factor */
+ saslsts = sasl_setprop(conn, SASL_SSF_EXTERNAL, &ssf);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ __pmNotifyErr(LOG_ERR, "SASL setting external SSF to %d: %s",
+ ssf, sasl_errstring(saslsts, NULL, NULL));
+ return __pmSecureSocketsError(saslsts);
+ }
+
+ /* set general security properties */
+ memset(&props, 0, sizeof(props));
+ props.maxbufsize = LIMIT_AUTH_PDU;
+ props.max_ssf = UINT_MAX;
+ saslsts = sasl_setprop(conn, SASL_SEC_PROPS, &props);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ __pmNotifyErr(LOG_ERR, "SASL setting security properties: %s",
+ sasl_errstring(saslsts, NULL, NULL));
+ return __pmSecureSocketsError(saslsts);
+ }
+
+ return 0;
+}
+
+static int
+__pmAuthServerNegotiation(int fd, int ssf, __pmHashCtl *attrs)
+{
+ int sts, saslsts;
+ int pinned, length, count;
+ char *payload, *offset;
+ sasl_conn_t *sasl_conn;
+ __pmPDU *pb;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation(fd=%d, ssf=%d)\n",
+ fd, ssf);
+
+ if ((sasl_conn = (sasl_conn_t *)__pmGetUserAuthData(fd)) == NULL)
+ return -EINVAL;
+
+ /* setup all the security properties for this connection */
+ if ((sts = __pmAuthServerSetProperties(sasl_conn, ssf)) < 0)
+ return sts;
+
+ saslsts = sasl_listmech(sasl_conn,
+ NULL, NULL, " ", NULL,
+ (const char **)&payload,
+ (unsigned int *)&length,
+ &count);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ __pmNotifyErr(LOG_ERR, "Generating client mechanism list: %s",
+ sasl_errstring(saslsts, NULL, NULL));
+ return __pmSecureSocketsError(saslsts);
+ }
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation - sending mechanism list "
+ "(%d items, %d bytes): \"%s\"\n", count, length, payload);
+
+ if ((sts = __pmSendAuth(fd, FROM_ANON, 0, payload, length)) < 0)
+ return sts;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation - wait for mechanism\n");
+
+ sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ if (sts == PDU_AUTH) {
+ sts = __pmDecodeAuth(pb, &count, &payload, &length);
+ if (sts >= 0) {
+ for (count = 0; count < length; count++) {
+ if (payload[count] == '\0')
+ break;
+ }
+ if (count < length) { /* found an initial response */
+ length = length - count - 1;
+ offset = payload + count + 1;
+ } else {
+ length = 0;
+ offset = NULL;
+ }
+
+ saslsts = sasl_server_start(sasl_conn, payload,
+ offset, length,
+ (const char **)&payload,
+ (unsigned int *)&length);
+ if (saslsts != SASL_OK && saslsts != SASL_CONTINUE) {
+ sts = __pmSecureSocketsError(saslsts);
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_server_start failed: %d (%s)\n",
+ saslsts, pmErrStr(sts));
+ } else {
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "sasl_server_start success: sts=%s\n",
+ saslsts == SASL_CONTINUE ? "continue" : "ok");
+ }
+ }
+ } else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ } else if (sts != PM_ERR_TIMEOUT) {
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinned)
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0)
+ return sts;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation method negotiated\n");
+
+ while (saslsts == SASL_CONTINUE) {
+ if (!payload) {
+ __pmNotifyErr(LOG_ERR, "No SASL data to send");
+ sts = -EINVAL;
+ break;
+ }
+ if ((sts = __pmSendAuth(fd, FROM_ANON, 0, payload, length)) < 0)
+ break;
+
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation awaiting response\n");
+
+ sts = pinned = __pmGetPDU(fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ if (sts == PDU_AUTH) {
+ sts = __pmDecodeAuth(pb, &count, &payload, &length);
+ if (sts >= 0) {
+ sts = saslsts = sasl_server_step(sasl_conn, payload, length,
+ (const char **)&payload,
+ (unsigned int *)&length);
+ if (sts != SASL_OK && sts != SASL_CONTINUE) {
+ sts = __pmSecureSocketsError(sts);
+ break;
+ }
+ if (pmDebug & DBG_TRACE_AUTH) {
+ fprintf(stderr, "__pmAuthServerNegotiation"
+ " step recv (%d bytes)\n", length);
+ }
+ }
+ } else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ } else if (sts != PM_ERR_TIMEOUT) {
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinned)
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0)
+ break;
+ }
+
+ if (sts < 0) {
+ if (pmDebug & DBG_TRACE_AUTH)
+ fprintf(stderr, "__pmAuthServerNegotiation loop failed: %d\n", sts);
+ return sts;
+ }
+
+ return __pmAuthServerSetAttributes(sasl_conn, attrs);
+}
+
+int
+__pmSecureServerHandshake(int fd, int flags, __pmHashCtl *attrs)
+{
+ int sts, ssf = DEFAULT_SECURITY_STRENGTH;
+
+ /* protect from unsupported requests from future/oddball clients */
+ if ((flags & ~(PDU_FLAG_SECURE | PDU_FLAG_SECURE_ACK | PDU_FLAG_COMPRESS
+ | PDU_FLAG_AUTH | PDU_FLAG_CREDS_REQD)) != 0)
+ return PM_ERR_IPC;
+
+ if (flags & PDU_FLAG_CREDS_REQD) {
+ if (__pmHashSearch(PCP_ATTR_USERID, attrs) != NULL)
+ return 0; /* unix domain socket */
+ else
+ flags |= PDU_FLAG_AUTH; /* force authentication */
+ }
+
+ if ((sts = __pmSecureServerIPCFlags(fd, flags)) < 0)
+ return sts;
+ if (((flags & PDU_FLAG_SECURE) != 0) &&
+ ((sts = __pmSecureServerNegotiation(fd, &ssf)) < 0))
+ return sts;
+ if (((flags & PDU_FLAG_AUTH) != 0) &&
+ ((sts = __pmAuthServerNegotiation(fd, ssf, attrs)) < 0))
+ return sts;
+ return 0;
+}
diff --git a/src/libpcp/src/sortinst.c b/src/libpcp/src/sortinst.c
new file mode 100644
index 0000000..3d557f4
--- /dev/null
+++ b/src/libpcp/src/sortinst.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <stdlib.h>
+#include "pmapi.h"
+
+static int
+comp(const void *a, const void *b)
+{
+ pmValue *ap = (pmValue *)a;
+ pmValue *bp = (pmValue *)b;
+
+ return ap->inst - bp->inst;
+}
+
+void
+pmSortInstances(pmResult *rp)
+{
+ int i;
+
+ for (i = 0; i < rp->numpmid; i++) {
+ if (rp->vset[i]->numval > 1) {
+ qsort(rp->vset[i]->vlist, rp->vset[i]->numval, sizeof(pmValue), comp);
+ }
+ }
+}
diff --git a/src/libpcp/src/spec.c b/src/libpcp/src/spec.c
new file mode 100644
index 0000000..8a7f299
--- /dev/null
+++ b/src/libpcp/src/spec.c
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * Parse uniform metric and host specification syntax
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "internal.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+
+static void *
+parseAlloc(const char *func, size_t need)
+{
+ void *tmp;
+
+ if ((tmp = malloc(need)) == NULL)
+ __pmNoMem(func, need, PM_FATAL_ERR);
+ return tmp;
+}
+
+static void
+parseError(const char *func, const char *spec, const char *point, char *msg, char **rslt)
+{
+ int need;
+ const char *p;
+ char *q;
+
+ if (rslt == NULL)
+ return;
+
+ need = 2 * (int)strlen(spec) + 1 + 6 + (int)strlen(msg) + 2;
+ *rslt = q = (char *)parseAlloc(func, need);
+ for (p = spec; *p != '\0'; p++)
+ *q++ = *p;
+ *q++ = '\n';
+ for (p = spec; p != point; p++)
+ *q++ = isgraph((int)*p) ? ' ' : *p;
+ sprintf(q, "^ -- %s\n", msg); /* safe */
+}
+
+static void *
+metricAlloc(size_t need)
+{
+ return parseAlloc("pmParseMetricSpec", need);
+}
+
+static void
+metricError(const char *spec, const char *point, char *msg, char **rslt)
+{
+ parseError("pmParseMetricSpec", spec, point, msg, rslt);
+}
+
+int /* 0 -> ok, PM_ERR_GENERIC -> error */
+pmParseMetricSpec(
+ const char *spec, /* parse this string */
+ int isarch, /* default source: 0 -> host, 1 -> archive */
+ char *source, /* name of default host or archive */
+ pmMetricSpec **rslt, /* result allocated and returned here */
+ char **errmsg) /* error message */
+{
+ pmMetricSpec *msp = NULL;
+ const char *scan;
+ const char *mark;
+ const char *h_start = NULL; /* host name */
+ const char *h_end = NULL;
+ const char *a_start = NULL; /* archive name */
+ const char *a_end = NULL;
+ const char *m_start = NULL; /* metric name */
+ const char *m_end = NULL;
+ const char *i_start = NULL; /* instance names */
+ const char *i_end = NULL;
+ char *i_str = NULL; /* temporary instance names */
+ char *i_scan;
+ int ninst; /* number of instance names */
+ char *push;
+ const char *pull;
+ int length;
+ int i;
+ int inquote = 0; /* true if within quotes */
+
+ scan = spec;
+ while (isspace((int)*scan))
+ scan++;
+
+ /*
+ * Options here are ...
+ * [host:]metric[[instance list]]
+ * special case for PM_CONTEXT_LOCAL [@:]metric[[instance list]]
+ * [archive/]metric[[instance list]]
+ *
+ * Find end of metric name first ([ or end of string) then scan
+ * backwards for first ':' or '/'
+ */
+ mark = index(scan, (int)'[');
+ if (mark == NULL) mark = &scan[strlen(scan)-1];
+ while (mark >= scan) {
+ if (*mark == ':') {
+ h_start = scan;
+ h_end = mark-1;
+ while (h_end >= scan && isspace((int)*h_end)) h_end--;
+ if (h_end < h_start) {
+ metricError(spec, h_start, "host name expected", errmsg);
+ return PM_ERR_GENERIC;
+ }
+ h_end++;
+ scan = mark+1;
+ break;
+ }
+ else if (*mark == '/') {
+ a_start = scan;
+ a_end = mark-1;
+ while (a_end >= scan && isspace((int)*a_end)) a_end--;
+ if (a_end < a_start) {
+ metricError(spec, a_start, "archive name expected", errmsg);
+ return PM_ERR_GENERIC;
+ }
+ a_end++;
+ scan = mark+1;
+ break;
+ }
+ mark--;
+ }
+
+ while (isspace((int)*scan))
+ scan++;
+ mark = scan;
+
+ /* delimit metric name */
+ m_start = scan;
+ while (! isspace((int)*scan) && *scan != '\0' && *scan != '[') {
+ if (*scan == ']' || *scan == ',') {
+ metricError(spec, scan, "unexpected character in metric name", errmsg);
+ return PM_ERR_GENERIC;
+ }
+ if (*scan == '\\' && *(scan+1) != '\0')
+ scan++;
+ scan++;
+ }
+ m_end = scan;
+ if (m_start == m_end) {
+ metricError(spec, m_start, "performance metric name expected", errmsg);
+ return PM_ERR_GENERIC;
+ }
+
+ while (isspace((int)*scan))
+ scan++;
+
+ /* delimit instance names */
+ if (*scan == '[') {
+ scan++;
+ while (isspace((int)*scan))
+ scan++;
+ i_start = scan;
+ for ( ; ; ) {
+ if (*scan == '\0') {
+ if (inquote)
+ metricError(spec, scan, "closing \" and ] expected", errmsg);
+ else
+ metricError(spec, scan, "closing ] expected", errmsg);
+ return PM_ERR_GENERIC;
+ }
+ if (*scan == '\\' && *(scan+1) != '\0')
+ scan++;
+ else if (*scan == '"')
+ inquote = 1 - inquote;
+ else if (!inquote && *scan == ']')
+ break;
+ scan++;
+ }
+ i_end = scan;
+ scan++;
+ }
+
+ /* check for rubbish at end of string */
+ while (isspace((int)*scan))
+ scan++;
+ if (*scan != '\0') {
+ metricError(spec, scan, "unexpected extra characters", errmsg);
+ return PM_ERR_GENERIC;
+ }
+
+ /* count instance names and make temporary copy */
+ ninst = 0;
+ if (i_start != NULL) {
+ i_str = (char *) metricAlloc(i_end - i_start + 1);
+
+ /* count and copy instance names */
+ scan = i_start;
+ i_scan = i_str;
+ while (scan < i_end) {
+
+ /* copy single instance name */
+ ninst++;
+ if (*scan == '"') {
+ scan++;
+ for (;;) {
+ if (scan >= i_end) {
+ metricError(spec, scan, "closing \" expected (pmParseMetricSpec botch?)", errmsg);
+ if (i_str)
+ free(i_str);
+ return PM_ERR_GENERIC;
+ }
+ if (*scan == '\\')
+ scan++;
+ else if (*scan == '"')
+ break;
+ *i_scan++ = *scan++;
+ }
+ scan++;
+ }
+ else {
+ while (! isspace((int)*scan) && *scan != ',' && scan < i_end) {
+ if (*scan == '\\')
+ scan++;
+ *i_scan++ = *scan++;
+ }
+ }
+ *i_scan++ = '\0';
+
+ /* skip delimiters */
+ while ((isspace((int)*scan) || *scan == ',') && scan < i_end)
+ scan++;
+ }
+ i_start = i_str;
+ i_end = i_scan;
+ }
+
+ /* single memory allocation for result structure */
+ length = (int)(sizeof(pmMetricSpec) +
+ ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0) +
+ ((h_start) ? h_end - h_start + 1 : 0) +
+ ((a_start) ? a_end - a_start + 1 : 0) +
+ ((m_start) ? m_end - m_start + 1 : 0) +
+ ((i_start) ? i_end - i_start + 1 : 0));
+ msp = (pmMetricSpec *)metricAlloc(length);
+
+ /* strings follow pmMetricSpec proper */
+ push = ((char *) msp) +
+ sizeof(pmMetricSpec) +
+ ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0);
+
+ /* copy metric name */
+ msp->metric = push;
+ pull = m_start;
+ while (pull < m_end) {
+ if (*pull == '\\' && (pull+1) < m_end)
+ pull++;
+ *push++ = *pull++;
+ }
+ *push++ = '\0';
+
+ /* copy host name */
+ if (h_start != NULL) {
+ if (h_end - h_start == 1 && *h_start == '@') {
+ /* PM_CONTEXT_LOCAL special case */
+ msp->isarch = 2;
+ }
+ else {
+ /* PM_CONTEXT_HOST */
+ msp->isarch = 0;
+ }
+ msp->source = push;
+ pull = h_start;
+ while (pull < h_end) {
+ if (*pull == '\\' && (pull+1) < h_end)
+ pull++;
+ *push++ = *pull++;
+ }
+ *push++ = '\0';
+ }
+
+ /* copy archive name */
+ else if (a_start != NULL) {
+ msp->isarch = 1;
+ msp->source = push;
+ pull = a_start;
+ while (pull < a_end) {
+ if (*pull == '\\' && (pull+1) < a_end)
+ pull++;
+ *push++ = *pull++;
+ }
+ *push++ = '\0';
+ }
+
+ /* take default host or archive */
+ else {
+ msp->isarch = isarch;
+ msp->source = source;
+ }
+
+ /* instance names */
+ msp->ninst = ninst;
+ pull = i_start;
+ for (i = 0; i < ninst; i++) {
+ msp->inst[i] = push;
+ do
+ *push++ = *pull;
+ while (*pull++ != '\0');
+ }
+
+ if (i_str)
+ free(i_str);
+ *rslt = msp;
+ return 0;
+}
+
+void
+pmFreeMetricSpec(pmMetricSpec *spec)
+{
+ free(spec);
+}
+
+
+static void
+hostError(const char *spec, const char *point, char *msg, char **rslt)
+{
+ parseError("pmParseHostSpec", spec, point, msg, rslt);
+}
+
+static char *
+hostStrndup(const char *name, int namelen)
+{
+ char *s = malloc(namelen + 1);
+ strncpy(s, name, namelen);
+ s[namelen] = '\0';
+ return s;
+}
+
+static pmHostSpec *
+hostAdd(pmHostSpec *specp, int *count, const char *name, int namelen)
+{
+ int n = *count;
+ char *host;
+
+ host = hostStrndup(name, namelen);
+ if (!host || (specp = realloc(specp, sizeof(pmHostSpec) * (n+1))) == NULL) {
+ if (host != NULL)
+ free(host);
+ *count = 0;
+ return NULL;
+ }
+ specp[n].name = host;
+ specp[n].ports = NULL;
+ specp[n].nports = 0;
+
+ *count = n + 1;
+ return specp;
+}
+
+int
+__pmAddHostPorts(pmHostSpec *specp, int *ports, int nports)
+{
+ int *portlist;
+
+ if ((portlist = malloc(sizeof(int) * (specp->nports + nports))) == NULL)
+ return -ENOMEM;
+ if (specp->nports > 0) {
+ memcpy(portlist, specp->ports, sizeof(int) * specp->nports);
+ free(specp->ports);
+ }
+ memcpy(&portlist[specp->nports], ports, sizeof(int) * nports);
+ specp->ports = portlist;
+ specp->nports = specp->nports + nports;
+ return 0;
+}
+
+void
+__pmDropHostPort(pmHostSpec *specp)
+{
+ specp->nports--;
+ memmove(&specp->ports[0], &specp->ports[1], specp->nports*sizeof(int));
+}
+
+/*
+ * Parse a host specification, with optional ports and proxy host(s).
+ * Examples:
+ * pcp -h app1.aconex.com:44321,4321@firewall.aconex.com:44322
+ * pcp -h app1.aconex.com:44321@firewall.aconex.com:44322
+ * pcp -h app1.aconex.com:44321@firewall.aconex.com
+ * pcp -h app1.aconex.com@firewall.aconex.com
+ * pcp -h app1.aconex.com:44321
+ * pcp -h 192.168.122.1:44321
+ * pcp -h [fe80::5eff:35ff:fe07:55ca]:44321,4321@[fe80::5eff:35ff:fe07:55cc]:44322
+ * pcp -h [fe80::5eff:35ff:fe07:55ca]:44321
+ *
+ * Basic algorithm:
+ * look for first colon, @ or null; preceding text is hostname
+ * if colon, look for comma, @ or null, preceding text is port
+ * while comma, look for comma, @ or null, preceding text is next port
+ * if @, start following host specification at the following character,
+ * by returning to the start and repeating the above for the next chunk.
+ * Note:
+ * IPv6 addresses contain colons and, so, must be separated from the
+ * rest of the spec somehow. A common notation among ipv6-enabled
+ * applications is to enclose the address within brackets, as in
+ * [fe80::5eff:35ff:fe07:55ca]:44321. We keep it simple, however,
+ * and allow any host spec to be enclosed in brackets.
+ * Note:
+ * Currently only two hosts are useful, but ability to handle more than
+ * one optional proxy host is there (i.e. proxy ->proxy ->... ->pmcd),
+ * in case someone implements the pmproxy->pmproxy protocol extension.
+ */
+static int /* 0 -> ok, PM_ERR_GENERIC -> error message is set */
+parseHostSpec(
+ const char *spec,
+ char **position, /* parse this string, return end char */
+ pmHostSpec **rslt, /* result allocated and returned here */
+ int *count,
+ char **errmsg) /* error message */
+{
+ pmHostSpec *hsp = NULL;
+ const char *s, *start, *next;
+ int nhosts = 0, sts = 0;
+
+ for (s = start = *position; s != NULL; s++) {
+ /* Allow the host spec to be enclosed in brackets. */
+ if (s == start && *s == '[') {
+ for (s++; *s != ']' && *s != '\0'; s++)
+ ;
+ if (*s != ']') {
+ hostError(spec, s, "missing closing ']' for host spec", errmsg);
+ sts = PM_ERR_GENERIC;
+ goto fail;
+ }
+ next = s + 1; /* past the trailing ']' */
+ if (*next != ':' && *next != '@' && *next != '\0' && *next != '/' && *next != '?') {
+ hostError(spec, next, "extra characters after host spec", errmsg);
+ sts = PM_ERR_GENERIC;
+ goto fail;
+ }
+ start++; /* past the initial '[' */
+ }
+ else
+ next = s;
+ if (*next == ':' || *next == '@' || *next == '\0' || *next == '/' || *next == '?') {
+ if (s == *position)
+ break;
+ else if (s == start)
+ continue;
+ hsp = hostAdd(hsp, &nhosts, start, s - start);
+ if (hsp == NULL) {
+ sts = -ENOMEM;
+ goto fail;
+ }
+ s = next;
+ if (*s == ':') {
+ for (++s, start = s; s != NULL; s++) {
+ if (*s == ',' || *s == '@' || *s == '\0' || *s == '/' || *s == '?') {
+ if (s - start < 1) {
+ hostError(spec, s, "missing port", errmsg);
+ sts = PM_ERR_GENERIC;
+ goto fail;
+ }
+ int port = atoi(start);
+ sts = __pmAddHostPorts(&hsp[nhosts-1], &port, 1);
+ if (sts < 0)
+ goto fail;
+ start = s + 1;
+ if (*s == '@' || *s == '\0' || *s == '/' || *s == '?')
+ break;
+ continue;
+ }
+ if (isdigit((int)*s))
+ continue;
+ hostError(spec, s, "non-numeric port", errmsg);
+ sts = PM_ERR_GENERIC;
+ goto fail;
+ }
+ }
+ if (*s == '@') {
+ start = s+1;
+ continue;
+ }
+ break;
+ }
+ }
+ *position = (char *)s;
+ *count = nhosts;
+ *rslt = hsp;
+ return 0;
+
+fail:
+ __pmFreeHostSpec(hsp, nhosts);
+ *rslt = NULL;
+ *count = 0;
+ return sts;
+}
+
+/*
+ * Parse a socket path.
+ * Accept anything up to, but not including the first ':', or the end of the spec.
+ * We use ':' as the delimeter even though a '?' could be the start of the attibutes because
+ * '?' is a valid character in a socket path. It is also consistent with things like $PATH.
+ */
+static int /* 0 -> ok, PM_ERR_GENERIC -> error message is set */
+parseSocketPath(
+ const char *spec,
+ char **position, /* parse this string, return end char */
+ pmHostSpec **rslt) /* result allocated and returned here */
+{
+ pmHostSpec *hsp = NULL;
+ const char *s, *start, *path;
+ char absolute_path[MAXPATHLEN];
+ size_t len;
+ int nhosts = 0;
+
+ /* Scan to the end of the string or to the first ':'. */
+ for (s = start = *position; s != NULL; s++) {
+ if (*s == '\0')
+ break;
+ if (*s == ':') {
+ ++s;
+ break;
+ }
+ }
+
+ /* If the path is empty, then provide the default. */
+ if (s == start) {
+ path = __pmPMCDLocalSocketDefault();
+ len = strlen(path);
+ }
+ else {
+ path = start;
+ len = s - start;
+ }
+
+ /*
+ * Make sure that the path is absolute. parseProtocolSpec() removes the
+ * (optional) "//" from "local://some/path".
+ */
+ if (*path != __pmPathSeparator()) {
+ len = snprintf (absolute_path, sizeof(absolute_path), "%c%s",
+ __pmPathSeparator(), path);
+ path = absolute_path;
+ }
+
+ /* Add the path as the only member of the host list. */
+ hsp = hostAdd(hsp, &nhosts, path, len);
+ if (hsp == NULL) {
+ __pmFreeHostSpec(hsp, nhosts);
+ *rslt = NULL;
+ return -ENOMEM;
+ }
+
+ *position = (char *)s;
+ *rslt = hsp;
+ return 0;
+}
+
+int
+__pmParseHostSpec(
+ const char *spec, /* parse this string */
+ pmHostSpec **rslt, /* result allocated and returned here */
+ int *count, /* number of host specs returned here */
+ char **errmsg) /* error message */
+{
+ char *s = (char *)spec;
+ int sts;
+
+ if ((sts = parseHostSpec(spec, &s, rslt, count, errmsg)) < 0)
+ return sts;
+
+ if (*s == '\0')
+ return 0;
+
+ hostError(spec, s, "unexpected terminal character", errmsg);
+ __pmFreeHostSpec(*rslt, *count);
+ *rslt = NULL;
+ *count = 0;
+ return PM_ERR_GENERIC;
+}
+
+static int
+unparseHostSpec(pmHostSpec *hostp, int count, char *string, size_t size, int prefix)
+{
+ int off = 0, len = size; /* offset in string and space remaining */
+ int i, j, sts;
+
+ for (i = 0; i < count; i++) {
+ if (i > 0) {
+ if ((sts = snprintf(string + off, len, "@")) >= size) {
+ off = -E2BIG;
+ goto done;
+ }
+ len -= sts; off += sts;
+ }
+
+ if (prefix && hostp[i].nports == PM_HOST_SPEC_NPORTS_LOCAL) {
+ if ((sts = snprintf(string + off, len, "local:/%s", hostp[i].name + 1)) >= size) {
+ off = -E2BIG;
+ goto done;
+ }
+ }
+ else if (prefix && hostp[i].nports == PM_HOST_SPEC_NPORTS_UNIX) {
+ if ((sts = snprintf(string + off, len, "unix:/%s", hostp[i].name + 1)) >= size) {
+ off = -E2BIG;
+ goto done;
+ }
+ }
+ else {
+ if ((sts = snprintf(string + off, len, "%s", hostp[i].name)) >= size) {
+ off = -E2BIG;
+ goto done;
+ }
+ }
+ len -= sts; off += sts;
+
+ for (j = 0; j < hostp[i].nports; j++) {
+ if ((sts = snprintf(string + off, len,
+ "%c%u", (j == 0) ? ':' : ',',
+ hostp[i].ports[j])) >= size) {
+ off = -E2BIG;
+ goto done;
+ }
+ len -= sts; off += sts;
+ }
+ }
+
+done:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "__pmUnparseHostSpec([name=%s ports=%p nport=%d], count=%d, ...) -> ", hostp->name, hostp->ports, hostp->nports, count);
+ if (off < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ pmErrStr_r(off, errmsg, sizeof(errmsg));
+ fprintf(stderr, "%s\n", errmsg);
+ }
+ else
+ fprintf(stderr, "%d \"%s\"\n", off, string);
+ }
+#endif
+ return off;
+}
+
+int
+__pmUnparseHostSpec(pmHostSpec *hostp, int count, char *string, size_t size)
+{
+ return unparseHostSpec(hostp, count, string, size, 1);
+}
+
+void
+__pmFreeHostSpec(pmHostSpec *specp, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ free(specp[i].name);
+ specp[i].name = NULL;
+ if (specp[i].nports > 0)
+ free(specp[i].ports);
+ specp[i].ports = NULL;
+ specp[i].nports = 0;
+ }
+ if (specp && count)
+ free(specp);
+}
+
+static __pmHashWalkState
+attrHashNodeDel(const __pmHashNode *tp, void *cp)
+{
+ (void)cp;
+ if (tp->data)
+ free(tp->data);
+ return PM_HASH_WALK_DELETE_NEXT;
+}
+
+void
+__pmFreeAttrsSpec(__pmHashCtl *attrs)
+{
+ __pmHashWalkCB(attrHashNodeDel, NULL, attrs);
+}
+
+void
+__pmFreeHostAttrsSpec(pmHostSpec *hosts, int count, __pmHashCtl *attrs)
+{
+ __pmFreeHostSpec(hosts, count);
+ __pmFreeAttrsSpec(attrs);
+}
+
+#define PCP_PROTOCOL_NAME "pcp"
+#define PCP_PROTOCOL_PREFIX PCP_PROTOCOL_NAME ":"
+#define PCP_PROTOCOL_SIZE (sizeof(PCP_PROTOCOL_NAME)-1)
+#define PCP_PROTOCOL_PREFIXSZ (sizeof(PCP_PROTOCOL_PREFIX)-1)
+#define PCPS_PROTOCOL_NAME "pcps"
+#define PCPS_PROTOCOL_PREFIX PCPS_PROTOCOL_NAME ":"
+#define PCPS_PROTOCOL_SIZE (sizeof(PCPS_PROTOCOL_NAME)-1)
+#define PCPS_PROTOCOL_PREFIXSZ (sizeof(PCPS_PROTOCOL_PREFIX)-1)
+#define LOCAL_PROTOCOL_NAME "local"
+#define LOCAL_PROTOCOL_PREFIX LOCAL_PROTOCOL_NAME ":"
+#define LOCAL_PROTOCOL_SIZE (sizeof(LOCAL_PROTOCOL_NAME)-1)
+#define LOCAL_PROTOCOL_PREFIXSZ (sizeof(LOCAL_PROTOCOL_PREFIX)-1)
+#define UNIX_PROTOCOL_NAME "unix"
+#define UNIX_PROTOCOL_PREFIX UNIX_PROTOCOL_NAME ":"
+#define UNIX_PROTOCOL_SIZE (sizeof(UNIX_PROTOCOL_NAME)-1)
+#define UNIX_PROTOCOL_PREFIXSZ (sizeof(UNIX_PROTOCOL_PREFIX)-1)
+
+static int
+parseProtocolSpec(
+ const char *spec, /* the original, complete string to parse */
+ char **position,
+ int *attribute,
+ char **value,
+ char **errmsg)
+{
+ char *protocol = NULL;
+ char *s = *position;
+
+ /* optionally extract protocol specifier */
+ if (strncmp(s, PCP_PROTOCOL_PREFIX, PCP_PROTOCOL_PREFIXSZ) == 0) {
+ protocol = PCP_PROTOCOL_NAME;
+ s += PCP_PROTOCOL_PREFIXSZ;
+ *attribute = PCP_ATTR_PROTOCOL;
+ } else if (strncmp(s, PCPS_PROTOCOL_PREFIX, PCPS_PROTOCOL_PREFIXSZ) == 0) {
+ protocol = PCPS_PROTOCOL_NAME;
+ s += PCPS_PROTOCOL_PREFIXSZ;
+ *attribute = PCP_ATTR_PROTOCOL;
+ } else if (strncmp(s, LOCAL_PROTOCOL_PREFIX, LOCAL_PROTOCOL_PREFIXSZ) == 0) {
+ protocol = LOCAL_PROTOCOL_NAME;
+ s += LOCAL_PROTOCOL_PREFIXSZ;
+ *attribute = PCP_ATTR_LOCAL;
+ } else if (strncmp(s, UNIX_PROTOCOL_PREFIX, UNIX_PROTOCOL_PREFIXSZ) == 0) {
+ protocol = UNIX_PROTOCOL_NAME;
+ s += UNIX_PROTOCOL_PREFIXSZ;
+ *attribute = PCP_ATTR_UNIXSOCK;
+ }
+
+ /* optionally skip over slash-delimiters */
+ if (protocol) {
+ while (*s == '/')
+ s++;
+ if ((*value = strdup(protocol)) == NULL)
+ return -ENOMEM;
+ } else {
+ *value = NULL;
+ *attribute = PCP_ATTR_NONE;
+ }
+
+ *position = s;
+ return 0;
+}
+
+__pmAttrKey
+__pmLookupAttrKey(const char *attribute, size_t size)
+{
+ if (size == sizeof("compress") &&
+ strncmp(attribute, "compress", size) == 0)
+ return PCP_ATTR_COMPRESS;
+ if ((size == sizeof("userauth") &&
+ strncmp(attribute, "userauth", size) == 0) ||
+ (size == sizeof("authorise") &&
+ (strncmp(attribute, "authorise", size) == 0 ||
+ strncmp(attribute, "authorize", size) == 0)))
+ return PCP_ATTR_USERAUTH;
+ if ((size == sizeof("user") &&
+ strncmp(attribute, "user", size) == 0) ||
+ (size == sizeof("username") &&
+ strncmp(attribute, "username", size) == 0))
+ return PCP_ATTR_USERNAME;
+ if (size == sizeof("realm") &&
+ strncmp(attribute, "realm", size) == 0)
+ return PCP_ATTR_REALM;
+ if ((size == sizeof("authmeth") &&
+ strncmp(attribute, "authmeth", size) == 0) ||
+ (size == sizeof("method") &&
+ strncmp(attribute, "method", size) == 0))
+ return PCP_ATTR_METHOD;
+ if ((size == sizeof("pass") &&
+ strncmp(attribute, "pass", size) == 0) ||
+ (size == sizeof("password") &&
+ strncmp(attribute, "password", size) == 0))
+ return PCP_ATTR_PASSWORD;
+ if ((size == sizeof("unix") &&
+ strncmp(attribute, "unix", size) == 0) ||
+ (size == sizeof("unixsock") &&
+ strncmp(attribute, "unixsock", size) == 0))
+ return PCP_ATTR_UNIXSOCK;
+ if ((size == sizeof("local") &&
+ strncmp(attribute, "local", size) == 0))
+ return PCP_ATTR_LOCAL;
+ if ((size == sizeof("uid") &&
+ strncmp(attribute, "uid", size) == 0) ||
+ (size == sizeof("userid") &&
+ strncmp(attribute, "userid", size) == 0))
+ return PCP_ATTR_USERID;
+ if ((size == sizeof("gid") &&
+ strncmp(attribute, "gid", size) == 0) ||
+ (size == sizeof("groupid") &&
+ strncmp(attribute, "groupid", size) == 0))
+ return PCP_ATTR_GROUPID;
+ if ((size == sizeof("pid") &&
+ strncmp(attribute, "pid", size) == 0) ||
+ (size == sizeof("processid") &&
+ strncmp(attribute, "processid", size) == 0))
+ return PCP_ATTR_PROCESSID;
+ if (size == sizeof("secure") &&
+ strncmp(attribute, "secure", size) == 0)
+ return PCP_ATTR_SECURE;
+ return PCP_ATTR_NONE;
+}
+
+/*
+ * Parse the attributes component of a PCP connection string.
+ * Optionally, an initial attribute:value pair can be passed
+ * in as well to add to the parsed set.
+ */
+static int
+parseAttributeSpec(
+ const char *spec, /* the original, complete string to parse */
+ char **position, /* parse from here onward and update at end */
+ int attribute,
+ char *value,
+ __pmHashCtl *attributes,
+ char **errmsg)
+{
+ char *s, *start, *v = NULL;
+ char buffer[32]; /* must be large enough to hold largest attr name */
+ int buflen, attr, len, sts;
+
+ if (attribute != PCP_ATTR_NONE)
+ if ((sts = __pmHashAdd(attribute, (void *)value, attributes)) < 0)
+ return sts;
+
+ for (s = start = *position; s != NULL; s++) {
+ /* parse: foo=bar&moo&goo=blah ... go! */
+ if (*s == '\0' || *s == '/' || *s == '&') {
+ if ((*s == '\0' || *s == '/') && s == start)
+ break;
+ len = v ? (v - start - 1) : (s - start);
+ buflen = (len < sizeof(buffer)-1) ? len : sizeof(buffer)-1;
+ strncpy(buffer, start, buflen);
+ buffer[buflen] = '\0';
+ attr = __pmLookupAttrKey(buffer, buflen+1);
+ if (attr != PCP_ATTR_NONE) {
+ char *val = NULL;
+
+ if (v && (val = strndup(v, s - v)) == NULL) {
+ sts = -ENOMEM;
+ goto fail;
+ }
+ if ((sts = __pmHashAdd(attr, (void *)val, attributes)) < 0) {
+ free(val);
+ goto fail;
+ }
+ }
+ v = NULL;
+ if (*s == '\0' || *s == '/')
+ break;
+ start = s + 1; /* start of attribute name */
+ continue;
+ }
+ if (*s == '=') {
+ v = s + 1; /* start of attribute value */
+ }
+ }
+
+ *position = s;
+ return 0;
+
+fail:
+ if (attribute != PCP_ATTR_NONE) /* avoid double free in caller */
+ __pmHashDel(attribute, (void *)value, attributes);
+ __pmFreeAttrsSpec(attributes);
+ return sts;
+}
+
+/*
+ * Finally, bring it all together to handle parsing full connection URLs:
+ *
+ * pcp://oss.sgi.com:45892?user=otto&pass=blotto&compress=true
+ * pcps://oss.sgi.com@proxy.org:45893?user=jimbo&pass=jones&compress=true
+ * local://path/to/socket:?user=jimbo&pass=jones
+ * unix://path/to/socket
+ */
+int
+__pmParseHostAttrsSpec(
+ const char *spec, /* the original, complete string to parse */
+ pmHostSpec **host, /* hosts result allocated and returned here */
+ int *count,
+ __pmHashCtl *attributes,
+ char **errmsg) /* error message */
+{
+ char *value = NULL, *s = (char *)spec;
+ int sts, attr;
+
+ *count = 0; /* ensure this initialised for fail: code */
+
+ /* parse optional protocol section */
+ if ((sts = parseProtocolSpec(spec, &s, &attr, &value, errmsg)) < 0)
+ return sts;
+
+ if (attr == PCP_ATTR_LOCAL || attr == PCP_ATTR_UNIXSOCK) {
+ /* We are looking for a socket path. */
+ if ((sts = parseSocketPath(spec, &s, host)) < 0)
+ goto fail;
+ *count = 1;
+ host[0]->nports = (attr == PCP_ATTR_LOCAL) ?
+ PM_HOST_SPEC_NPORTS_LOCAL : PM_HOST_SPEC_NPORTS_UNIX;
+ }
+ else {
+ /* We are looking for a host spec. */
+ if ((sts = parseHostSpec(spec, &s, host, count, errmsg)) < 0)
+ goto fail;
+ }
+
+ /* skip over an attributes delimiter */
+ if (*s == '?') {
+ s++; /* optionally skip over the question mark */
+ } else if (*s != '\0' && *s != '/') {
+ hostError(spec, s, "unexpected terminal character", errmsg);
+ sts = PM_ERR_GENERIC;
+ goto fail;
+ }
+
+ /* parse optional attributes section */
+ if ((sts = parseAttributeSpec(spec, &s, attr, value, attributes, errmsg)) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ if (value)
+ free(value);
+ if (*count)
+ __pmFreeHostSpec(*host, *count);
+ *count = 0;
+ *host = NULL;
+ return sts;
+}
+
+static int
+unparseAttribute(__pmHashNode *node, char *string, size_t size)
+{
+ return __pmAttrStr_r(node->key, node->data, string, size);
+}
+
+int
+__pmAttrKeyStr_r(__pmAttrKey key, char *string, size_t size)
+{
+ switch (key) {
+ case PCP_ATTR_PROTOCOL:
+ return snprintf(string, size, "protocol");
+ case PCP_ATTR_COMPRESS:
+ return snprintf(string, size, "compress");
+ case PCP_ATTR_USERAUTH:
+ return snprintf(string, size, "userauth");
+ case PCP_ATTR_USERNAME:
+ return snprintf(string, size, "username");
+ case PCP_ATTR_AUTHNAME:
+ return snprintf(string, size, "authname");
+ case PCP_ATTR_PASSWORD:
+ return snprintf(string, size, "password");
+ case PCP_ATTR_METHOD:
+ return snprintf(string, size, "method");
+ case PCP_ATTR_REALM:
+ return snprintf(string, size, "realm");
+ case PCP_ATTR_SECURE:
+ return snprintf(string, size, "secure");
+ case PCP_ATTR_UNIXSOCK:
+ return snprintf(string, size, "unixsock");
+ case PCP_ATTR_LOCAL:
+ return snprintf(string, size, "local");
+ case PCP_ATTR_USERID:
+ return snprintf(string, size, "userid");
+ case PCP_ATTR_GROUPID:
+ return snprintf(string, size, "groupid");
+ case PCP_ATTR_PROCESSID:
+ return snprintf(string, size, "processid");
+ case PCP_ATTR_NONE:
+ default:
+ break;
+ }
+ return 0;
+}
+
+int
+__pmAttrStr_r(__pmAttrKey key, const char *data, char *string, size_t size)
+{
+ char name[16]; /* must be sufficient to hold any key name (above) */
+ int sts;
+
+ if ((sts = __pmAttrKeyStr_r(key, name, sizeof(name))) <= 0)
+ return sts;
+
+ switch (key) {
+ case PCP_ATTR_PROTOCOL:
+ case PCP_ATTR_USERNAME:
+ case PCP_ATTR_PASSWORD:
+ case PCP_ATTR_METHOD:
+ case PCP_ATTR_REALM:
+ case PCP_ATTR_SECURE:
+ case PCP_ATTR_USERID:
+ case PCP_ATTR_GROUPID:
+ case PCP_ATTR_PROCESSID:
+ return snprintf(string, size, "%s=%s", name, data ? data : "");
+
+ case PCP_ATTR_UNIXSOCK:
+ case PCP_ATTR_LOCAL:
+ case PCP_ATTR_COMPRESS:
+ case PCP_ATTR_USERAUTH:
+ return snprintf(string, size, "%s", name);
+
+ case PCP_ATTR_NONE:
+ default:
+ break;
+ }
+ return 0;
+}
+
+int
+__pmUnparseHostAttrsSpec(
+ pmHostSpec *hosts,
+ int count,
+ __pmHashCtl *attrs,
+ char *string,
+ size_t size)
+{
+ __pmHashNode *node;
+ int off = 0, len = size; /* offset in string and space remaining */
+ int sts, first;
+
+ if ((node = __pmHashSearch(PCP_ATTR_PROTOCOL, attrs)) != NULL) {
+ if ((sts = snprintf(string, len, "%s://", (char *)node->data)) >= len)
+ return -E2BIG;
+ len -= sts; off += sts;
+ }
+ else if (__pmHashSearch(PCP_ATTR_UNIXSOCK, attrs) != NULL) {
+ if ((sts = snprintf(string, len, "unix:/")) >= len)
+ return -E2BIG;
+ len -= sts; off += sts;
+ }
+ else if (__pmHashSearch(PCP_ATTR_LOCAL, attrs) != NULL) {
+ if ((sts = snprintf(string, len, "local:/")) >= len)
+ return -E2BIG;
+ len -= sts; off += sts;
+ }
+
+ if ((sts = unparseHostSpec(hosts, count, string + off, len, 0)) >= len)
+ return sts;
+ len -= sts; off += sts;
+
+ first = 1;
+ for (node = __pmHashWalk(attrs, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) {
+ if (node->key == PCP_ATTR_PROTOCOL ||
+ node->key == PCP_ATTR_UNIXSOCK || node->key == PCP_ATTR_LOCAL)
+ continue;
+ if ((sts = snprintf(string + off, len, "%c", first ? '?' : '&')) >= len)
+ return -E2BIG;
+ len -= sts; off += sts;
+ first = 0;
+
+ if ((sts = unparseAttribute(node, string + off, len)) >= len)
+ return -E2BIG;
+ len -= sts; off += sts;
+ }
+
+ return off;
+}
diff --git a/src/libpcp/src/store.c b/src/libpcp/src/store.c
new file mode 100644
index 0000000..2d16ef3
--- /dev/null
+++ b/src/libpcp/src/store.c
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "internal.h"
+
+int
+pmStore(const pmResult *result)
+{
+ int n;
+ int sts;
+ __pmContext *ctxp;
+ __pmDSO *dp;
+
+ if (result->numpmid < 1)
+ return PM_ERR_TOOSMALL;
+
+ for (n = 0; n < result->numpmid; n++) {
+ if (result->vset[n]->numval < 1) {
+ return PM_ERR_VALUE;
+ }
+ }
+
+ if ((sts = pmWhichContext()) >= 0) {
+ int ctx = sts;
+
+ ctxp = __pmHandleToPtr(sts);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type == PM_CONTEXT_HOST) {
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ sts = __pmSendResult(ctxp->c_pmcd->pc_fd, __pmPtrToHandle(ctxp), result);
+ if (sts < 0)
+ sts = __pmMapErrno(sts);
+ else {
+ __pmPDU *pb;
+ int pinpdu;
+
+ pinpdu = sts = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE,
+ ctxp->c_pmcd->pc_tout_sec, &pb);
+ if (sts == PDU_ERROR)
+ __pmDecodeError(pb, &sts);
+ else if (sts != PM_ERR_TIMEOUT)
+ sts = PM_ERR_IPC;
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ /*
+ * have to do them one at a time in case different DSOs
+ * involved ... need to copy each result->vset[n]
+ */
+ pmResult tmp;
+ pmValueSet tmpvset;
+
+ if (PM_MULTIPLE_THREADS(PM_SCOPE_DSO_PMDA)) {
+ /* Local context requires single-threaded applications */
+ sts = PM_ERR_THREAD;
+ } else {
+ sts = 0;
+ for (n = 0; sts == 0 && n < result->numpmid; n++) {
+ if ((dp = __pmLookupDSO(((__pmID_int *)&result->vset[n]->pmid)->domain)) == NULL)
+ sts = PM_ERR_NOAGENT;
+ else {
+ tmp.numpmid = 1;
+ tmp.vset[0] = &tmpvset;
+ tmpvset.numval = 1;
+ tmpvset.pmid = result->vset[n]->pmid;
+ tmpvset.valfmt = result->vset[n]->valfmt;
+ tmpvset.vlist[0] = result->vset[n]->vlist[0];
+ if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ dp->dispatch.version.four.ext->e_context = ctx;
+ sts = dp->dispatch.version.any.store(&tmp, dp->dispatch.version.any.ext);
+ }
+ }
+ }
+ }
+ else {
+ /* assume PM_CONTEXT_ARCHIVE -- this is an error */
+ sts = PM_ERR_NOTHOST;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ return sts;
+}
diff --git a/src/libpcp/src/stuffvalue.c b/src/libpcp/src/stuffvalue.c
new file mode 100644
index 0000000..23e9cc1
--- /dev/null
+++ b/src/libpcp/src/stuffvalue.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+int
+__pmStuffValue(const pmAtomValue *avp, pmValue *vp, int type)
+{
+ void *src;
+ size_t need, body;
+
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ vp->value.lval = avp->ul;
+ return PM_VAL_INSITU;
+
+ case PM_TYPE_FLOAT:
+ body = sizeof(float);
+ src = (void *)&avp->f;
+ break;
+
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_DOUBLE:
+ body = sizeof(__int64_t);
+ src = (void *)&avp->ull;
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ /*
+ * vbp field of pmAtomValue points to a dynamically allocated
+ * pmValueBlock ... the vlen and vtype fields MUST have been
+ * already set up.
+ * A new pmValueBlock header will be allocated below, so adjust
+ * the length here (PM_VAL_HDR_SIZE will be added back later).
+ */
+ body = avp->vbp->vlen - PM_VAL_HDR_SIZE;
+ src = avp->vbp->vbuf;
+ break;
+
+ case PM_TYPE_STRING:
+ body = strlen(avp->cp) + 1;
+ src = (void *)avp->cp;
+ break;
+
+ case PM_TYPE_AGGREGATE_STATIC:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ /*
+ * vbp field of pmAtomValue points to a statically allocated
+ * pmValueBlock ... the vlen and vtype fields MUST have been
+ * already set up and are not modified here
+ *
+ * DO NOT make a copy of the value in this case
+ */
+ vp->value.pval = avp->vbp;
+ return PM_VAL_SPTR;
+
+ default:
+ return PM_ERR_TYPE;
+ }
+ need = body + PM_VAL_HDR_SIZE;
+ vp->value.pval = (pmValueBlock *)malloc(
+ (need < sizeof(pmValueBlock)) ? sizeof(pmValueBlock) : need);
+ if (vp->value.pval == NULL)
+ return -oserror();
+ vp->value.pval->vlen = (int)need;
+ vp->value.pval->vtype = type;
+ memcpy((void *)vp->value.pval->vbuf, (void *)src, body);
+ return PM_VAL_DPTR;
+}
diff --git a/src/libpcp/src/tv.c b/src/libpcp/src/tv.c
new file mode 100644
index 0000000..cabdf75
--- /dev/null
+++ b/src/libpcp/src/tv.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <sys/time.h>
+
+/*
+ * real additive time, *ap plus *bp
+ */
+double
+__pmtimevalAdd(const struct timeval *ap, const struct timeval *bp)
+{
+ return (double)(ap->tv_sec + bp->tv_sec) + (double)(ap->tv_usec + bp->tv_usec)/1000000.0;
+}
+
+/*
+ * real time difference, *ap minus *bp
+ */
+double
+__pmtimevalSub(const struct timeval *ap, const struct timeval *bp)
+{
+ return (double)(ap->tv_sec - bp->tv_sec) + (double)(ap->tv_usec - bp->tv_usec)/1000000.0;
+}
+
+/*
+ * convert a timeval to a double (units = seconds)
+ */
+double
+__pmtimevalToReal(const struct timeval *val)
+{
+ double dbl = (double)(val->tv_sec);
+ dbl += (double)val->tv_usec / 1000000.0;
+ return dbl;
+}
+
+/*
+ * convert double to a timeval
+ */
+void
+__pmtimevalFromReal(double dbl, struct timeval *val)
+{
+ val->tv_sec = (time_t)dbl;
+ val->tv_usec = (long)(((dbl - (double)val->tv_sec) * 1000000.0));
+}
+
+/*
+ * Sleep for a specified amount of time
+ */
+void
+__pmtimevalSleep(struct timeval interval)
+{
+ struct timespec delay;
+ struct timespec left;
+ int sts;
+
+ delay.tv_sec = interval.tv_sec;
+ delay.tv_nsec = interval.tv_usec * 1000;
+
+ for (;;) { /* loop to catch early wakeup by nanosleep */
+ sts = nanosleep(&delay, &left);
+ if (sts == 0 || (sts < 0 && oserror() != EINTR))
+ break;
+ delay = left;
+ }
+}
+
+/* subtract timevals */
+static struct timeval
+tsub(struct timeval t1, struct timeval t2)
+{
+ t1.tv_usec -= t2.tv_usec;
+ if (t1.tv_usec < 0) {
+ t1.tv_usec += 1000000;
+ t1.tv_sec--;
+ }
+ t1.tv_sec -= t2.tv_sec;
+ return t1;
+}
+
+/* convert timeval to timespec */
+static struct timespec *
+tospec(struct timeval tv, struct timespec *ts)
+{
+ ts->tv_nsec = tv.tv_usec * 1000;
+ ts->tv_sec = tv.tv_sec;
+ return ts;
+}
+
+#if !defined(IS_MINGW)
+void
+__pmtimevalNow(struct timeval *tv)
+{
+ gettimeofday(tv, NULL);
+}
+#endif
+
+/* sleep until given timeval */
+void
+__pmtimevalPause(struct timeval sched)
+{
+ int sts;
+ struct timeval curr; /* current time */
+ struct timespec delay; /* interval to sleep */
+ struct timespec left; /* remaining sleep time */
+
+ __pmtimevalNow(&curr);
+ tospec(tsub(sched, curr), &delay);
+ for (;;) { /* loop to catch early wakeup by nanosleep */
+ sts = nanosleep(&delay, &left);
+ if (sts == 0 || (sts < 0 && oserror() != EINTR))
+ break;
+ delay = left;
+ }
+}
diff --git a/src/libpcp/src/tz.c b/src/libpcp/src/tz.c
new file mode 100644
index 0000000..1157ad6
--- /dev/null
+++ b/src/libpcp/src/tz.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 1995-2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * These routines manipulate the environment and call lots of routines
+ * like localtime(), asctime(), gmtime(), getenv(), putenv() ... all of
+ * which are not thread-safe.
+ *
+ * We use the big lock to prevent concurrent execution.
+ *
+ * Need to call PM_INIT_LOCKS() in all the exposed routines because we
+ * may be called before a context has been created, and missed the
+ * lock initialization in pmNewContext().
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+static char *envtz; /* buffer in env */
+static int envtzlen;
+
+static char *savetz; /* real $TZ from env */
+static char **savetzp;
+
+static int nzone; /* table of zones */
+static int curzone = -1;
+static char **zone;
+
+#if !defined(HAVE_UNDERBAR_ENVIRON)
+#define _environ environ
+#endif
+
+extern char **_environ;
+
+static void
+_pushTZ(void)
+{
+ char **p;
+
+ savetzp = NULL;
+ for (p = _environ; *p != NULL; p++) {
+ if (strncmp(*p, "TZ=", 3) == 0) {
+ savetz = *p;
+ *p = envtz;
+ savetzp = p;
+ break;
+ }
+ }
+ if (*p == NULL)
+ putenv(envtz);
+ tzset();
+}
+
+static void
+_popTZ(void)
+{
+ if (savetzp != NULL)
+ *savetzp = savetz;
+ else
+ putenv("TZ=");
+ tzset();
+}
+
+/*
+ * Construct TZ=... subject to the constraint that the length of the
+ * timezone part is not more than PM_TZ_MAXLEN bytes
+ * Assumes TZ= is in the start of tzbuffer and this is not touched.
+ * And finally set TZ in the environment.
+ */
+static void
+__pmSquashTZ(char *tzbuffer)
+{
+ time_t now = time(NULL);
+ struct tm *t;
+ char *tzn;
+#ifndef IS_MINGW
+ time_t offset;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ tzset();
+ t = localtime(&now);
+
+#ifdef HAVE_ALTZONE
+ offset = (t->tm_isdst > 0) ? altzone : timezone;
+#elif defined HAVE_STRFTIME_z
+ {
+ char tzoffset[6]; /* +1200\0 */
+
+ strftime (tzoffset, sizeof (tzoffset), "%z", t);
+ offset = -strtol (tzoffset, NULL, 10);
+ offset = ((offset/100) * 3600) + ((offset%100) * 60);
+ }
+#else
+ {
+ struct tm *gmt = gmtime(&now);
+ offset = (gmt->tm_hour - t->tm_hour) * 3600 +
+ (gmt->tm_min - t->tm_min) * 60;
+ }
+#endif
+
+ tzn = tzname[(t->tm_isdst > 0)];
+
+ if (offset != 0) {
+ int hours = offset / 3600;
+ int mins = abs ((offset % 3600) / 60);
+ int len = (int) strlen(tzn);
+
+ if (mins == 0) {
+ /* -3 for +HH in worst case */
+ if (len > PM_TZ_MAXLEN-3) len = PM_TZ_MAXLEN-3;
+ snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%*.*s%+d", len, len, tzn, hours);
+ }
+ else {
+ /* -6 for +HH:MM in worst case */
+ if (len > PM_TZ_MAXLEN-6) len = PM_TZ_MAXLEN-6;
+ snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%*.*s%+d:%02d", len, len, tzn, hours, mins);
+ }
+ }
+ else {
+ strncpy(tzbuffer+3, tzn, PM_TZ_MAXLEN);
+ tzbuffer[PM_TZ_MAXLEN+4-1] = '\0';
+ }
+ putenv(tzbuffer);
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+
+#else /* IS_MINGW */
+ /*
+ * Use the native Win32 API to extract the timezone. This is
+ * a Windows timezone, we want the POSIX style but there's no
+ * API, really. What we've found works, is the same approach
+ * the MSYS dll takes - we set TZ their way (below) and then
+ * use tzset, then extract. Note that the %Z and %z strftime
+ * parameters do not contain abbreviated names/offsets (they
+ * both contain Windows timezone, and both are the same with
+ * no TZ). Note also that putting the Windows name into the
+ * environment as TZ does not do anything good (see the tzset
+ * MSDN docs).
+ */
+#define is_upper(c) ((unsigned)(c) - 'A' <= 26)
+
+ TIME_ZONE_INFORMATION tz;
+ static const char wildabbr[] = "GMT";
+ char tzbuf[256], tzoff[64];
+ char *cp, *dst, *off;
+ wchar_t *src;
+ div_t d;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ GetTimeZoneInformation(&tz);
+ dst = cp = tzbuf;
+ off = tzoff;
+ for (src = tz.StandardName; *src; src++)
+ if (is_upper(*src)) *dst++ = *src;
+ if (cp == dst) {
+ /* In Asian Windows, tz.StandardName may not contain
+ the timezone name. */
+ strcpy(cp, wildabbr);
+ cp += strlen(wildabbr);
+ }
+ else
+ cp = dst;
+ d = div(tz.Bias+tz.StandardBias, 60);
+ sprintf(cp, "%d", d.quot);
+ sprintf(off, "%d", d.quot);
+ if (d.rem) {
+ sprintf(cp=strchr(cp, 0), ":%d", abs(d.rem));
+ sprintf(off=strchr(off, 0), ":%d", abs(d.rem));
+ }
+ if (tz.StandardDate.wMonth) {
+ cp = strchr(cp, 0);
+ dst = cp;
+ for (src = tz.DaylightName; *src; src++)
+ if (is_upper(*src)) *dst++ = *src;
+ if (cp == dst) {
+ /* In Asian Windows, tz.StandardName may not contain
+ the daylight name. */
+ strcpy(tzbuf, wildabbr);
+ cp += strlen(wildabbr);
+ }
+ else
+ cp = dst;
+ d = div(tz.Bias+tz.DaylightBias, 60);
+ sprintf(cp, "%d", d.quot);
+ if (d.rem)
+ sprintf(cp=strchr(cp, 0), ":%d", abs(d.rem));
+ cp = strchr(cp, 0);
+ sprintf(cp=strchr(cp, 0), ",M%d.%d.%d/%d",
+ tz.DaylightDate.wMonth,
+ tz.DaylightDate.wDay,
+ tz.DaylightDate.wDayOfWeek,
+ tz.DaylightDate.wHour);
+ if (tz.DaylightDate.wMinute || tz.DaylightDate.wSecond)
+ sprintf(cp=strchr(cp, 0), ":%d", tz.DaylightDate.wMinute);
+ if (tz.DaylightDate.wSecond)
+ sprintf(cp=strchr(cp, 0), ":%d", tz.DaylightDate.wSecond);
+ cp = strchr(cp, 0);
+ sprintf(cp=strchr(cp, 0), ",M%d.%d.%d/%d",
+ tz.StandardDate.wMonth,
+ tz.StandardDate.wDay,
+ tz.StandardDate.wDayOfWeek,
+ tz.StandardDate.wHour);
+ if (tz.StandardDate.wMinute || tz.StandardDate.wSecond)
+ sprintf(cp=strchr(cp, 0), ":%d", tz.StandardDate.wMinute);
+ if (tz.StandardDate.wSecond)
+ sprintf(cp=strchr(cp, 0), ":%d", tz.StandardDate.wSecond);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_TIMECONTROL)
+ fprintf(stderr, "Win32 TZ=%s\n", tzbuf);
+#endif
+
+ snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%s", tzbuf);
+ putenv(tzbuffer);
+
+ tzset();
+ t = localtime(&now);
+ tzn = tzname[(t->tm_isdst > 0)];
+
+ snprintf(tzbuffer+3, PM_TZ_MAXLEN, "%s%s", tzn, tzoff);
+ putenv(tzbuffer);
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+#endif
+}
+
+/*
+ * __pmTimezone: work out local timezone
+ */
+char *
+__pmTimezone(void)
+{
+ static char *tzbuffer = NULL;
+ char *tz;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ tz = getenv("TZ");
+
+ if (tzbuffer == NULL) {
+ /*
+ * size is PM_TZ_MAXLEN + length of "TZ=" + null byte terminator
+ */
+ tzbuffer = (char *)malloc(PM_TZ_MAXLEN+4);
+ if (tzbuffer == NULL) {
+ /* not much we can do here ... */
+ PM_UNLOCK(__pmLock_libpcp);
+ return NULL;
+ }
+ strcpy(tzbuffer, "TZ=");
+ }
+
+ if (tz == NULL || tz[0] == ':') {
+ /* NO TZ in the environment - invent one. If TZ starts with a colon,
+ * it's an Olson-style TZ and it does not supported on all IRIXes, so
+ * squash it into a simple one (pv#788431). */
+ __pmSquashTZ(tzbuffer);
+ tz = &tzbuffer[3];
+ } else if (strlen(tz) > PM_TZ_MAXLEN) {
+ /* TZ is too long to fit into the internal PCP timezone structs
+ * let's try to sqash it a bit */
+ char *tb;
+
+ if ((tb = strdup(tz)) == NULL) {
+ /* sorry state of affairs, go squash w/out copying buffer */
+ __pmSquashTZ(tzbuffer);
+ tz = &tzbuffer[3];
+ }
+ else {
+ char *ptz = tz;
+ char *zeros;
+ char *end = tb;
+
+ while ((zeros = strstr(ptz, ":00")) != NULL) {
+ strncpy(end, ptz, zeros-ptz);
+ end += zeros-ptz;
+ *end = '\0';
+ ptz = zeros+3;
+ }
+
+ if (strlen(tb) > PM_TZ_MAXLEN) {
+ /* Still too long - let's pretend it's Olson */
+ __pmSquashTZ(tzbuffer);
+ tz = &tzbuffer[3];
+ } else {
+ strcpy(tzbuffer+3, tb);
+ putenv(tzbuffer);
+ tz = tzbuffer+3;
+ }
+
+ free(tb);
+ }
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return tz;
+}
+
+/*
+ * buffer should be at least PM_TZ_MAXLEN bytes long
+ */
+char *
+__pmTimezone_r(char *buf, int buflen)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ strcpy(buf, __pmTimezone());
+ PM_UNLOCK(__pmLock_libpcp);
+ return buf;
+}
+
+int
+pmUseZone(const int tz_handle)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ if (tz_handle < 0 || tz_handle >= nzone) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return -1;
+ }
+
+ curzone = tz_handle;
+ strcpy(&envtz[3], zone[curzone]);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmUseZone(%d) tz=%s\n", curzone, zone[curzone]);
+#endif
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return 0;
+}
+
+int
+pmNewZone(const char *tz)
+{
+ int len;
+ int hack = 0;
+ int sts;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ len = (int)strlen(tz);
+ if (len == 3) {
+ /*
+ * things like TZ=GMT may be broken in libc, particularly
+ * in _ltzset() of time_comm.c, where changes to TZ are
+ * sometimes not properly reflected.
+ * TZ=GMT+0 avoids the problem.
+ */
+ len += 2;
+ hack = 1;
+ }
+
+ if (len+4 > envtzlen) {
+ /* expand buffer for env */
+ if (envtz != NULL)
+ free(envtz);
+ envtzlen = len+4;
+ envtz = (char *)malloc(envtzlen);
+ strcpy(envtz, "TZ=");
+ }
+ strcpy(&envtz[3], tz);
+ if (hack)
+ /* see above */
+ strcpy(&envtz[6], "+0");
+
+ curzone = nzone++;
+ zone = (char **)realloc(zone, nzone * sizeof(char *));
+ if (zone == NULL) {
+ __pmNoMem("pmNewZone", nzone * sizeof(char *), PM_FATAL_ERR);
+ }
+ zone[curzone] = strdup(&envtz[3]);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmNewZone(%s) -> %d\n", zone[curzone], curzone);
+#endif
+ sts = curzone;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+int
+pmNewContextZone(void)
+{
+ __pmContext *ctxp;
+ int sts;
+
+ if ((ctxp = __pmHandleToPtr(pmWhichContext())) == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ if (ctxp->c_type == PM_CONTEXT_ARCHIVE) {
+ sts = pmNewZone(ctxp->c_archctl->ac_log->l_label.ill_tz);
+ PM_UNLOCK(ctxp->c_lock);
+ }
+ else if (ctxp->c_type == PM_CONTEXT_LOCAL) {
+ char tzbuf[PM_TZ_MAXLEN];
+ /* from env, not PMCD */
+ PM_UNLOCK(ctxp->c_lock);
+ __pmTimezone_r(tzbuf, sizeof(tzbuf));
+ sts = pmNewZone(tzbuf);
+ }
+ else {
+ /* assume PM_CONTEXT_HOST */
+ char *name = "pmcd.timezone";
+ pmID pmid;
+ pmResult *rp;
+
+ PM_UNLOCK(ctxp->c_lock);
+ if ((sts = pmLookupName(1, &name, &pmid)) < 0)
+ return sts;
+ if ((sts = pmFetch(1, &pmid, &rp)) >= 0) {
+ if (rp->vset[0]->numval == 1 &&
+ (rp->vset[0]->valfmt == PM_VAL_DPTR || rp->vset[0]->valfmt == PM_VAL_SPTR))
+ sts = pmNewZone((char *)rp->vset[0]->vlist[0].value.pval->vbuf);
+ else
+ sts = PM_ERR_VALUE;
+ pmFreeResult(rp);
+ }
+ }
+
+ return sts;
+}
+
+char *
+pmCtime(const time_t *clock, char *buf)
+{
+#if !defined(IS_SOLARIS) && !defined(IS_MINGW)
+ struct tm tbuf;
+#endif
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ if (curzone >= 0) {
+ _pushTZ();
+#if defined(IS_SOLARIS) || defined(IS_MINGW)
+ strcpy(buf, asctime(localtime(clock)));
+#else
+ asctime_r(localtime_r(clock, &tbuf), buf);
+#endif
+ _popTZ();
+ }
+ else {
+#if defined(IS_SOLARIS) || defined(IS_MINGW)
+ strcpy(buf, asctime(localtime(clock)));
+#else
+ asctime_r(localtime_r(clock, &tbuf), buf);
+#endif
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return buf;
+}
+
+struct tm *
+pmLocaltime(const time_t *clock, struct tm *result)
+{
+#if defined(IS_SOLARIS) || defined(IS_MINGW)
+ struct tm *tmp;
+#endif
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ if (curzone >= 0) {
+ _pushTZ();
+#if defined(IS_SOLARIS) || defined(IS_MINGW)
+ tmp = localtime(clock);
+ memcpy(result, tmp, sizeof(*result));
+#else
+ localtime_r(clock, result);
+#endif
+ _popTZ();
+ }
+ else {
+#if defined(IS_SOLARIS) || defined(IS_MINGW)
+ tmp = localtime(clock);
+ memcpy(result, tmp, sizeof(*result));
+#else
+ localtime_r(clock, result);
+#endif
+ }
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return result;
+}
+
+time_t
+__pmMktime(struct tm *timeptr)
+{
+ time_t ans;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+
+ if (curzone >= 0) {
+ _pushTZ();
+ ans = mktime(timeptr);
+ _popTZ();
+ }
+ else
+ ans = mktime(timeptr);
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return ans;
+}
+
+int
+pmWhichZone(char **tz)
+{
+ int sts;
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (curzone >= 0)
+ *tz = zone[curzone];
+ sts = curzone;
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
diff --git a/src/libpcp/src/units.c b/src/libpcp/src/units.c
new file mode 100644
index 0000000..8827868
--- /dev/null
+++ b/src/libpcp/src/units.c
@@ -0,0 +1,1107 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <inttypes.h>
+
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+
+#if !defined(ABS)
+#define ABS(a) ((a) < 0 ? -(a) : (a))
+#endif
+
+#if defined(HAVE_CONST_LONGLONG)
+#define SIGN_64_MASK 0x8000000000000000LL
+#else
+#define SIGN_64_MASK 0x8000000000000000
+#endif
+
+/*
+ * pmAtomValue -> string, max length is 80 bytes
+ *
+ * To avoid alignment problems, avp _must_ be aligned appropriately
+ * for a pmAtomValue pointer by the caller.
+ */
+char *
+pmAtomStr_r(const pmAtomValue *avp, int type, char *buf, int buflen)
+{
+ int i;
+ int vlen;
+ char strbuf[40];
+
+ switch (type) {
+ case PM_TYPE_32:
+ snprintf(buf, buflen, "%d", avp->l);
+ break;
+ case PM_TYPE_U32:
+ snprintf(buf, buflen, "%u", avp->ul);
+ break;
+ case PM_TYPE_64:
+ snprintf(buf, buflen, "%"PRIi64, avp->ll);
+ break;
+ case PM_TYPE_U64:
+ snprintf(buf, buflen, "%"PRIu64, avp->ull);
+ break;
+ case PM_TYPE_FLOAT:
+ snprintf(buf, buflen, "%e", (double)avp->f);
+ break;
+ case PM_TYPE_DOUBLE:
+ snprintf(buf, buflen, "%e", avp->d);
+ break;
+ case PM_TYPE_STRING:
+ if (avp->cp == NULL)
+ snprintf(buf, buflen, "<null>");
+ else {
+ i = (int)strlen(avp->cp);
+ if (i < 38)
+ snprintf(buf, buflen, "\"%s\"", avp->cp);
+ else
+ snprintf(buf, buflen, "\"%34.34s...\"", avp->cp);
+ }
+ break;
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ if (avp->vbp == NULL) {
+ snprintf(buf, buflen, "<null>");
+ break;
+ }
+ vlen = avp->vbp->vlen - PM_VAL_HDR_SIZE;
+ if (vlen == 0)
+ snprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen);
+ else {
+ char *cp;
+ char *bp;
+ snprintf(buf, buflen, "[type=%s len=%d]", pmTypeStr_r(avp->vbp->vtype, strbuf, sizeof(strbuf)), vlen);
+ cp = (char *)avp->vbp->vbuf;
+ for (i = 0; i < vlen && i < 12; i++) {
+ bp = &buf[strlen(buf)];
+ if ((i % 4) == 0)
+ snprintf(bp, sizeof(buf) - (bp-buf), " %02x", *cp & 0xff);
+ else
+ snprintf(bp, sizeof(buf) - (bp-buf), "%02x", *cp & 0xff);
+ cp++;
+ }
+ if (vlen > 12) {
+ bp = &buf[strlen(buf)];
+ snprintf(bp, sizeof(buf) - (bp-buf), " ...");
+ }
+ }
+ break;
+ case PM_TYPE_EVENT: {
+ /* have to assume alignment is OK in this case */
+ pmEventArray *eap = (pmEventArray *)avp->vbp;
+ if (eap->ea_nrecords == 1)
+ snprintf(buf, buflen, "[1 event record]");
+ else
+ snprintf(buf, buflen, "[%d event records]", eap->ea_nrecords);
+ break;
+ }
+ case PM_TYPE_HIGHRES_EVENT: {
+ /* have to assume alignment is OK in this case */
+ pmHighResEventArray *hreap = (pmHighResEventArray *)avp->vbp;
+ if (hreap->ea_nrecords == 1)
+ snprintf(buf, buflen, "[1 event record]");
+ else
+ snprintf(buf, buflen, "[%d event records]", hreap->ea_nrecords);
+ break;
+ }
+ default:
+ snprintf(buf, buflen, "Error: unexpected type: %s", pmTypeStr_r(type, strbuf, sizeof(strbuf)));
+ }
+ return buf;
+}
+
+/*
+ * To avoid alignment problems, avp _must_ be aligned appropriately
+ * for a pmAtomValue pointer by the caller.
+ */
+const char *
+pmAtomStr(const pmAtomValue *avp, int type)
+{
+ static char abuf[80];
+ pmAtomStr_r(avp, type, abuf, sizeof(abuf));
+ return abuf;
+}
+
+/*
+ * must be in agreement with ordinal values for PM_TYPE_* #defines
+ */
+static const char *typename[] = {
+ "32", "U32", "64", "U64", "FLOAT", "DOUBLE", "STRING", "AGGREGATE", "AGGREGATE_STATIC", "EVENT", "HIGHRES_EVENT"
+};
+
+/* PM_TYPE_* -> string, max length is 20 bytes */
+char *
+pmTypeStr_r(int type, char *buf, int buflen)
+{
+ if (type >= 0 && type < sizeof(typename)/sizeof(typename[0]))
+ snprintf(buf, buflen, "%s", typename[type]);
+ else if (type == PM_TYPE_NOSUPPORT)
+ snprintf(buf, buflen, "%s", "Not Supported");
+ else if (type == PM_TYPE_UNKNOWN)
+ snprintf(buf, buflen, "%s", "Unknown");
+ else
+ snprintf(buf, buflen, "Illegal type=%d", type);
+
+ return buf;
+}
+
+const char *
+pmTypeStr(int type)
+{
+ static char tbuf[20];
+ pmTypeStr_r(type, tbuf, sizeof(tbuf));
+ return tbuf;
+}
+
+/* scale+units -> string, max length is 60 bytes */
+char *
+pmUnitsStr_r(const pmUnits *pu, char *buf, int buflen)
+{
+ char *spacestr = NULL;
+ char *timestr = NULL;
+ char *countstr = NULL;
+ char *p;
+ char sbuf[20];
+ char tbuf[20];
+ char cbuf[20];
+
+ buf[0] = '\0';
+
+ if (pu->dimSpace) {
+ switch (pu->scaleSpace) {
+ case PM_SPACE_BYTE:
+ spacestr = "byte";
+ break;
+ case PM_SPACE_KBYTE:
+ spacestr = "Kbyte";
+ break;
+ case PM_SPACE_MBYTE:
+ spacestr = "Mbyte";
+ break;
+ case PM_SPACE_GBYTE:
+ spacestr = "Gbyte";
+ break;
+ case PM_SPACE_TBYTE:
+ spacestr = "Tbyte";
+ break;
+ case PM_SPACE_PBYTE:
+ spacestr = "Pbyte";
+ break;
+ case PM_SPACE_EBYTE:
+ spacestr = "Ebyte";
+ break;
+ default:
+ snprintf(sbuf, sizeof(sbuf), "space-%d", pu->scaleSpace);
+ spacestr = sbuf;
+ break;
+ }
+ }
+ if (pu->dimTime) {
+ switch (pu->scaleTime) {
+ case PM_TIME_NSEC:
+ timestr = "nanosec";
+ break;
+ case PM_TIME_USEC:
+ timestr = "microsec";
+ break;
+ case PM_TIME_MSEC:
+ timestr = "millisec";
+ break;
+ case PM_TIME_SEC:
+ timestr = "sec";
+ break;
+ case PM_TIME_MIN:
+ timestr = "min";
+ break;
+ case PM_TIME_HOUR:
+ timestr = "hour";
+ break;
+ default:
+ snprintf(tbuf, sizeof(tbuf), "time-%d", pu->scaleTime);
+ timestr = tbuf;
+ break;
+ }
+ }
+ if (pu->dimCount) {
+ switch (pu->scaleCount) {
+ case 0:
+ countstr = "count";
+ break;
+ case 1:
+ snprintf(cbuf, sizeof(cbuf), "count x 10");
+ countstr = cbuf;
+ break;
+ default:
+ snprintf(cbuf, sizeof(cbuf), "count x 10^%d", pu->scaleCount);
+ countstr = cbuf;
+ break;
+ }
+ }
+
+ p = buf;
+
+ if (pu->dimSpace > 0) {
+ if (pu->dimSpace == 1)
+ snprintf(p, buflen, "%s", spacestr);
+ else
+ snprintf(p, buflen, "%s^%d", spacestr, pu->dimSpace);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ if (pu->dimTime > 0) {
+ if (pu->dimTime == 1)
+ snprintf(p, buflen - (p - buf), "%s", timestr);
+ else
+ snprintf(p, buflen - (p - buf), "%s^%d", timestr, pu->dimTime);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ if (pu->dimCount > 0) {
+ if (pu->dimCount == 1)
+ snprintf(p, buflen - (p - buf), "%s", countstr);
+ else
+ snprintf(p, buflen - (p - buf), "%s^%d", countstr, pu->dimCount);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ if (pu->dimSpace < 0 || pu->dimTime < 0 || pu->dimCount < 0) {
+ *p++ = '/';
+ *p++ = ' ';
+ if (pu->dimSpace < 0) {
+ if (pu->dimSpace == -1)
+ snprintf(p, buflen - (p - buf), "%s", spacestr);
+ else
+ snprintf(p, buflen - (p - buf), "%s^%d", spacestr, -pu->dimSpace);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ if (pu->dimTime < 0) {
+ if (pu->dimTime == -1)
+ snprintf(p, buflen - (p - buf), "%s", timestr);
+ else
+ snprintf(p, buflen - (p - buf), "%s^%d", timestr, -pu->dimTime);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ if (pu->dimCount < 0) {
+ if (pu->dimCount == -1)
+ snprintf(p, buflen - (p - buf), "%s", countstr);
+ else
+ snprintf(p, buflen - (p - buf), "%s^%d", countstr, -pu->dimCount);
+ while (*p) p++;
+ *p++ = ' ';
+ }
+ }
+
+ if (buf[0] == '\0') {
+ /*
+ * dimension is all 0, but scale maybe specified ... small
+ * anomaly here as we would expect dimCount to be 1 not
+ * 0 for these cases, but support maintained for historical
+ * behaviour
+ */
+ if (pu->scaleCount == 1)
+ snprintf(buf, buflen, "x 10");
+ else if (pu->scaleCount != 0)
+ snprintf(buf, buflen, "x 10^%d", pu->scaleCount);
+ }
+ else {
+ p--;
+ *p = '\0';
+ }
+
+ return buf;
+}
+
+const char *
+pmUnitsStr(const pmUnits *pu)
+{
+ static char ubuf[60];
+ pmUnitsStr_r(pu, ubuf, sizeof(ubuf));
+ return ubuf;
+}
+
+/* Scale conversion, based on value format, value type and scale */
+int
+pmConvScale(int type, const pmAtomValue *ival, const pmUnits *iunit,
+ pmAtomValue *oval, const pmUnits *ounit)
+{
+ int sts;
+ int k;
+ __int64_t div, mult;
+ __int64_t d, m;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ fprintf(stderr, "pmConvScale: %s", pmAtomStr_r(ival, type, strbuf, sizeof(strbuf)));
+ fprintf(stderr, " [%s]", pmUnitsStr_r(iunit, strbuf, sizeof(strbuf)));
+ }
+#endif
+
+ if (iunit->dimSpace != ounit->dimSpace ||
+ iunit->dimTime != ounit->dimTime ||
+ iunit->dimCount != ounit->dimCount) {
+ sts = PM_ERR_CONV;
+ goto bad;
+ }
+
+ div = mult = 1;
+
+ if (iunit->dimSpace) {
+ d = 1;
+ m = 1;
+ switch (iunit->scaleSpace) {
+ case PM_SPACE_BYTE:
+ d = 1024 * 1024;
+ break;
+ case PM_SPACE_KBYTE:
+ d = 1024;
+ break;
+ case PM_SPACE_MBYTE:
+ /* the canonical unit */
+ break;
+ case PM_SPACE_GBYTE:
+ m = 1024;
+ break;
+ case PM_SPACE_TBYTE:
+ m = 1024 * 1024;
+ break;
+ case PM_SPACE_PBYTE:
+ m = 1024 * 1024 * 1024;
+ break;
+ case PM_SPACE_EBYTE:
+ m = (__int64_t)1024 * 1024 * 1024 * 1024;
+ break;
+ default:
+ sts = PM_ERR_UNIT;
+ goto bad;
+ }
+ switch (ounit->scaleSpace) {
+ case PM_SPACE_BYTE:
+ m *= 1024 * 1024;
+ break;
+ case PM_SPACE_KBYTE:
+ m *= 1024;
+ break;
+ case PM_SPACE_MBYTE:
+ /* the canonical unit */
+ break;
+ case PM_SPACE_GBYTE:
+ d *= 1024;
+ break;
+ case PM_SPACE_TBYTE:
+ d *= 1024 * 1024;
+ break;
+ case PM_SPACE_PBYTE:
+ d *= 1024 * 1024 * 1024;
+ break;
+ case PM_SPACE_EBYTE:
+ d *= (__int64_t)1024 * 1024 * 1024 * 1024;
+ break;
+ default:
+ sts = PM_ERR_UNIT;
+ goto bad;
+ }
+ if (iunit->dimSpace > 0) {
+ for (k = 0; k < iunit->dimSpace; k++) {
+ div *= d;
+ mult *= m;
+ }
+ }
+ else {
+ for (k = iunit->dimSpace; k < 0; k++) {
+ mult *= d;
+ div *= m;
+ }
+ }
+ }
+
+ if (iunit->dimTime) {
+ d = 1;
+ m = 1;
+ switch (iunit->scaleTime) {
+ case PM_TIME_NSEC:
+ d = 1000000000;
+ break;
+ case PM_TIME_USEC:
+ d = 1000000;
+ break;
+ case PM_TIME_MSEC:
+ d = 1000;
+ break;
+ case PM_TIME_SEC:
+ /* the canonical unit */
+ break;
+ case PM_TIME_MIN:
+ m = 60;
+ break;
+ case PM_TIME_HOUR:
+ m = 3600;
+ break;
+ default:
+ sts = PM_ERR_UNIT;
+ goto bad;
+ }
+ switch (ounit->scaleTime) {
+ case PM_TIME_NSEC:
+ m *= 1000000000;
+ break;
+ case PM_TIME_USEC:
+ m *= 1000000;
+ break;
+ case PM_TIME_MSEC:
+ m *= 1000;
+ break;
+ case PM_TIME_SEC:
+ /* the canonical unit */
+ break;
+ case PM_TIME_MIN:
+ d *= 60;
+ break;
+ case PM_TIME_HOUR:
+ d *= 3600;
+ break;
+ default:
+ sts = PM_ERR_UNIT;
+ goto bad;
+ }
+ if (iunit->dimTime > 0) {
+ for (k = 0; k < iunit->dimTime; k++) {
+ div *= d;
+ mult *= m;
+ }
+ }
+ else {
+ for (k = iunit->dimTime; k < 0; k++) {
+ mult *= d;
+ div *= m;
+ }
+ }
+ }
+
+ if (iunit->dimCount ||
+ (iunit->dimSpace == 0 && iunit->dimTime == 0)) {
+ d = 1;
+ m = 1;
+ if (iunit->scaleCount < 0) {
+ for (k = iunit->scaleCount; k < 0; k++)
+ d *= 10;
+ }
+ else if (iunit->scaleCount > 0) {
+ for (k = 0; k < iunit->scaleCount; k++)
+ m *= 10;
+ }
+ if (ounit->scaleCount < 0) {
+ for (k = ounit->scaleCount; k < 0; k++)
+ m *= 10;
+ }
+ else if (ounit->scaleCount > 0) {
+ for (k = 0; k < ounit->scaleCount; k++)
+ d *= 10;
+ }
+ if (iunit->dimCount > 0) {
+ for (k = 0; k < iunit->dimCount; k++) {
+ div *= d;
+ mult *= m;
+ }
+ }
+ else if (iunit->dimCount < 0) {
+ for (k = iunit->dimCount; k < 0; k++) {
+ mult *= d;
+ div *= m;
+ }
+ }
+ else {
+ mult = m;
+ div = d;
+ }
+ }
+
+ if (mult % div == 0) {
+ mult /= div;
+ div = 1;
+ }
+
+ switch (type) {
+ case PM_TYPE_32:
+ oval->l = (__int32_t)((ival->l * mult + div/2) / div);
+ break;
+ case PM_TYPE_U32:
+ oval->ul = (__uint32_t)((ival->ul * mult + div/2) / div);
+ break;
+ case PM_TYPE_64:
+ oval->ll = (ival->ll * mult + div/2) / div;
+ break;
+ case PM_TYPE_U64:
+ oval->ull = (ival->ull * mult + div/2) / div;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = ival->f * ((float)mult / (float)div);
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = ival->d * ((double)mult / (double)div);
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ goto bad;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ fprintf(stderr, " -> %s", pmAtomStr_r(oval, type, strbuf, sizeof(strbuf)));
+ fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf)));
+ }
+#endif
+ return 0;
+
+bad:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[60];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ fprintf(stderr, " [%s]\n", pmUnitsStr_r(ounit, strbuf, sizeof(strbuf)));
+ }
+#endif
+ return sts;
+}
+
+/* Value extract from pmValue and type conversion */
+int
+pmExtractValue(int valfmt, const pmValue *ival, int itype,
+ pmAtomValue *oval, int otype)
+{
+ void *avp;
+ pmAtomValue av;
+ int sts = 0;
+ int len;
+ const char *vp;
+ char buf[80];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ fprintf(stderr, "pmExtractValue: ");
+ vp = "???";
+ }
+#endif
+
+ oval->ll = 0;
+ if (valfmt == PM_VAL_INSITU) {
+ av.l = ival->value.lval;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
+ }
+#endif
+ switch (itype) {
+
+ case PM_TYPE_32:
+ case PM_TYPE_UNKNOWN:
+ switch (otype) {
+ case PM_TYPE_32:
+ oval->l = av.l;
+ break;
+ case PM_TYPE_U32:
+ if (av.l < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ul = (__uint32_t)av.l;
+ break;
+ case PM_TYPE_64:
+ oval->ll = (__int64_t)av.l;
+ break;
+ case PM_TYPE_U64:
+ if (av.l < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ull = (__uint64_t)av.l;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = (float)av.l;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = (double)av.l;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_U32:
+ switch (otype) {
+ case PM_TYPE_32:
+ if (av.ul > 0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)av.ul;
+ break;
+ case PM_TYPE_U32:
+ oval->ul = (__uint32_t)av.ul;
+ break;
+ case PM_TYPE_64:
+ oval->ll = (__int64_t)av.ul;
+ break;
+ case PM_TYPE_U64:
+ oval->ull = (__uint64_t)av.ul;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = (float)av.ul;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = (double)av.ul;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ /*
+ * Notes on conversion to FLOAT ... because of the limited
+ * precision of the mantissa, more than one integer value
+ * maps to the same floating point value ... hence the
+ * >= (float)max-int-value style of tests
+ */
+ case PM_TYPE_FLOAT: /* old style insitu encoding */
+ switch (otype) {
+ case PM_TYPE_32:
+ if ((float)ABS(av.f) >= (float)0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)av.f;
+ break;
+ case PM_TYPE_U32:
+ if (av.f >= (float)((unsigned)0xffffffff))
+ sts = PM_ERR_TRUNC;
+ else if (av.f < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ul = (__uint32_t)av.f;
+ break;
+ case PM_TYPE_64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (av.f >= (float)0x7fffffffffffffffLL)
+ sts = PM_ERR_TRUNC;
+#else
+ if (av.f >= (float)0x7fffffffffffffff)
+ sts = PM_ERR_TRUNC;
+#endif
+ else
+ oval->ll = (__int64_t)av.f;
+ break;
+ case PM_TYPE_U64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (av.f >= (float)((__uint64_t)0xffffffffffffffffLL))
+ sts = PM_ERR_TRUNC;
+#else
+ if (av.f >= (float)((__uint64_t)0xffffffffffffffff))
+ sts = PM_ERR_TRUNC;
+#endif
+ else if (av.f < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ull = (__uint64_t)av.f;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = av.f;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = (double)av.f;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_DOUBLE:
+ case PM_TYPE_STRING:
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ default:
+ sts = PM_ERR_CONV;
+ }
+ }
+ else if (valfmt == PM_VAL_DPTR || valfmt == PM_VAL_SPTR) {
+ __int64_t src;
+ __uint64_t usrc;
+ double dsrc;
+ float fsrc;
+ switch (itype) {
+
+ case PM_TYPE_64:
+ if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__int64_t) ||
+ (ival->value.pval->vtype != PM_TYPE_64 &&
+ ival->value.pval->vtype != 0)) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ avp = (void *)&ival->value.pval->vbuf;
+ memcpy((void *)&av.ll, avp, sizeof(av.ll));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
+ }
+#endif
+ src = av.ll;
+ switch (otype) {
+ case PM_TYPE_32:
+ if (src > 0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)src;
+ break;
+ case PM_TYPE_U32:
+ if (src > (unsigned)0xffffffff)
+ sts = PM_ERR_TRUNC;
+ else if (src < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ul = (__uint32_t)src;
+ break;
+ case PM_TYPE_64:
+ oval->ll = src;
+ break;
+ case PM_TYPE_U64:
+ if (src < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ull = (__uint64_t)src;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = (float)src;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = (double)src;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_U64:
+ if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(__uint64_t) ||
+ (ival->value.pval->vtype != PM_TYPE_U64 &&
+ ival->value.pval->vtype != 0)) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ avp = (void *)&ival->value.pval->vbuf;
+ memcpy((void *)&av.ull, avp, sizeof(av.ull));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
+ }
+#endif
+ usrc = av.ull;
+ switch (otype) {
+ case PM_TYPE_32:
+ if (usrc > 0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)usrc;
+ break;
+ case PM_TYPE_U32:
+ if (usrc > (unsigned)0xffffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->ul = (__uint32_t)usrc;
+ break;
+ case PM_TYPE_64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (usrc > (__int64_t)0x7fffffffffffffffLL)
+ sts = PM_ERR_TRUNC;
+#else
+ if (usrc > (__int64_t)0x7fffffffffffffff)
+ sts = PM_ERR_TRUNC;
+#endif
+ else
+ oval->ll = (__int64_t)usrc;
+ break;
+ case PM_TYPE_U64:
+ oval->ull = usrc;
+ break;
+ case PM_TYPE_FLOAT:
+#if !defined(HAVE_CAST_U64_DOUBLE)
+ if (SIGN_64_MASK & usrc)
+ oval->f = (float)(__int64_t)(usrc & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK;
+ else
+ oval->f = (float)(__int64_t)usrc;
+#else
+ oval->f = (float)usrc;
+#endif
+ break;
+ case PM_TYPE_DOUBLE:
+#if !defined(HAVE_CAST_U64_DOUBLE)
+ if (SIGN_64_MASK & usrc)
+ oval->d = (double)(__int64_t)(usrc & (~SIGN_64_MASK)) + (__uint64_t)SIGN_64_MASK;
+ else
+ oval->d = (double)(__int64_t)usrc;
+#else
+ oval->d = (double)usrc;
+#endif
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_DOUBLE:
+ if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(double) ||
+ (ival->value.pval->vtype != PM_TYPE_DOUBLE &&
+ ival->value.pval->vtype != 0)) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ avp = (void *)&ival->value.pval->vbuf;
+ memcpy((void *)&av.d, avp, sizeof(av.d));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
+ }
+#endif
+ dsrc = av.d;
+ switch (otype) {
+ case PM_TYPE_32:
+ if (ABS(dsrc) >= (double)0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)dsrc;
+ break;
+ case PM_TYPE_U32:
+ if (dsrc >= (double)((unsigned)0xffffffff))
+ sts = PM_ERR_TRUNC;
+ else if (dsrc < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ul = (__uint32_t)dsrc;
+ break;
+ case PM_TYPE_64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (dsrc >= (double)0x7fffffffffffffffLL)
+ sts = PM_ERR_TRUNC;
+#else
+ if (dsrc >= (double)0x7fffffffffffffff)
+ sts = PM_ERR_TRUNC;
+#endif
+ else
+ oval->ll = (__int64_t)dsrc;
+ break;
+ case PM_TYPE_U64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (dsrc >= (double)((__uint64_t)0xffffffffffffffffLL))
+ sts = PM_ERR_TRUNC;
+#else
+ if (dsrc >= (double)((__uint64_t)0xffffffffffffffff))
+ sts = PM_ERR_TRUNC;
+#endif
+ else if (dsrc < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ull = (__uint64_t)dsrc;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = (float)dsrc;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = dsrc;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_FLOAT: /* new style pmValueBlock encoding */
+ if (ival->value.pval->vlen != PM_VAL_HDR_SIZE + sizeof(float) ||
+ (ival->value.pval->vtype != PM_TYPE_FLOAT &&
+ ival->value.pval->vtype != 0)) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ avp = (void *)&ival->value.pval->vbuf;
+ memcpy((void *)&av.f, avp, sizeof(av.f));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ vp = pmAtomStr_r(&av, itype, strbuf, sizeof(strbuf));
+ }
+#endif
+ fsrc = av.f;
+ switch (otype) {
+ case PM_TYPE_32:
+ if ((float)ABS(fsrc) >= (float)0x7fffffff)
+ sts = PM_ERR_TRUNC;
+ else
+ oval->l = (__int32_t)fsrc;
+ break;
+ case PM_TYPE_U32:
+ if (fsrc >= (float)((unsigned)0xffffffff))
+ sts = PM_ERR_TRUNC;
+ else if (fsrc < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ul = (__uint32_t)fsrc;
+ break;
+ case PM_TYPE_64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (fsrc >= (float)0x7fffffffffffffffLL)
+ sts = PM_ERR_TRUNC;
+#else
+ if (fsrc >= (float)0x7fffffffffffffff)
+ sts = PM_ERR_TRUNC;
+#endif
+ else
+ oval->ll = (__int64_t)fsrc;
+ break;
+ case PM_TYPE_U64:
+#if defined(HAVE_CONST_LONGLONG)
+ if (fsrc >= (float)((__uint64_t)0xffffffffffffffffLL))
+ sts = PM_ERR_TRUNC;
+#else
+ if (fsrc >= (float)((__uint64_t)0xffffffffffffffff))
+ sts = PM_ERR_TRUNC;
+#endif
+ else if (fsrc < 0)
+ sts = PM_ERR_SIGN;
+ else
+ oval->ull = (__uint64_t)fsrc;
+ break;
+ case PM_TYPE_FLOAT:
+ oval->f = fsrc;
+ break;
+ case PM_TYPE_DOUBLE:
+ oval->d = (float)fsrc;
+ break;
+ default:
+ sts = PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_STRING:
+ if (ival->value.pval->vtype != PM_TYPE_STRING &&
+ ival->value.pval->vtype != 0) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ len = ival->value.pval->vlen - PM_VAL_HDR_SIZE;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ if (ival->value.pval->vbuf[0] == '\0')
+ vp = "<null>";
+ else {
+ int i;
+ i = (int)strlen(ival->value.pval->vbuf);
+ if (i < 38)
+ snprintf(buf, sizeof(buf), "\"%s\"", ival->value.pval->vbuf);
+ else
+ snprintf(buf, sizeof(buf), "\"%34.34s...\"", ival->value.pval->vbuf);
+ vp = buf;
+ }
+ }
+#endif
+ if (otype != PM_TYPE_STRING) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ if ((oval->cp = (char *)malloc(len + 1)) == NULL) {
+ __pmNoMem("pmExtractValue.string", len + 1, PM_FATAL_ERR);
+ }
+ memcpy(oval->cp, ival->value.pval->vbuf, len);
+ oval->cp[len] = '\0';
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ if (ival->value.pval->vtype != PM_TYPE_AGGREGATE &&
+ ival->value.pval->vtype != 0) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ len = ival->value.pval->vlen;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ int vlen;
+ int i;
+ vlen = ival->value.pval->vlen - PM_VAL_HDR_SIZE;
+ if (vlen == 0)
+ snprintf(buf, sizeof(buf), "[len=%d]", vlen);
+ else {
+ char *cp;
+ char *bp;
+ snprintf(buf, sizeof(buf), "[len=%d]", vlen);
+ cp = (char *)ival->value.pval->vbuf;
+ for (i = 0; i < vlen && i < 12; i++) {
+ bp = &buf[strlen(buf)];
+ if ((i % 4) == 0)
+ snprintf(bp, sizeof(buf) - (bp-buf), " %02x", *cp & 0xff);
+ else
+ snprintf(bp, sizeof(buf) - (bp-buf), "%02x", *cp & 0xff);
+ cp++;
+ }
+ if (vlen > 12) {
+ bp = &buf[strlen(buf)];
+ snprintf(bp, sizeof(buf) - (bp-buf), " ...");
+ }
+ }
+ vp = buf;
+ }
+#endif
+ if (otype != PM_TYPE_AGGREGATE) {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ if ((oval->vbp = (pmValueBlock *)malloc(len)) == NULL) {
+ __pmNoMem("pmExtractValue.aggr", len, PM_FATAL_ERR);
+ }
+ memcpy(oval->vbp, ival->value.pval, len);
+ break;
+
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ default:
+ sts = PM_ERR_CONV;
+ }
+ }
+ else
+ sts = PM_ERR_CONV;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_VALUE) {
+ char strbuf[80];
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, " %s", vp);
+ fprintf(stderr, " [%s]", pmTypeStr_r(itype, strbuf, sizeof(strbuf)));
+ if (sts == 0)
+ fprintf(stderr, " -> %s", pmAtomStr_r(oval, otype, strbuf, sizeof(strbuf)));
+ else
+ fprintf(stderr, " -> Error: %s", pmErrStr_r(sts, errmsg, sizeof(errmsg)));
+ fprintf(stderr, " [%s]\n", pmTypeStr_r(otype, strbuf, sizeof(strbuf)));
+ }
+#endif
+
+ return sts;
+}
diff --git a/src/libpcp/src/util.c b/src/libpcp/src/util.c
new file mode 100644
index 0000000..f0e212a
--- /dev/null
+++ b/src/libpcp/src/util.c
@@ -0,0 +1,2351 @@
+/*
+ * General Utility Routines
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Thread-safe notes
+ *
+ * pmState - no side-effects, don't bother locking
+ *
+ * pmProgname - most likely set in main(), not worth protecting here
+ * and impossible to capture all the read uses in other places
+ *
+ * base (in __pmProcessDataSize) - no real side-effects, don't bother
+ * locking
+ */
+
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmdbg.h"
+#include "internal.h"
+
+#if defined(HAVE_SYS_TIMES_H)
+#include <sys/times.h>
+#endif
+#if defined(HAVE_SYS_MMAN_H)
+#include <sys/mman.h>
+#endif
+#if defined(HAVE_IEEEFP_H)
+#include <ieeefp.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#if defined(IS_DARWIN)
+#include <sys/sysctl.h>
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
+static FILE **filelog;
+static int nfilelog;
+static int dosyslog;
+static int pmState = PM_STATE_APPL;
+static int done_exit;
+
+INTERN char *pmProgname = "pcp"; /* the real McCoy */
+
+static int vpmprintf(const char *, va_list);
+
+/*
+ * if onoff == 1, logging is to syslog and stderr, else logging is
+ * just to stderr (this is the default)
+ */
+void
+__pmSyslog(int onoff)
+{
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ dosyslog = onoff;
+ if (dosyslog)
+ openlog("pcp", LOG_PID, LOG_DAEMON);
+ else
+ closelog();
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+/*
+ * This is a wrapper around syslog(3C) that writes similar messages to stderr,
+ * but if __pmSyslog(1) is called, the messages will really go to syslog
+ */
+void
+__pmNotifyErr(int priority, const char *message, ...)
+{
+ va_list arg;
+ char *p;
+ char *level;
+ time_t now;
+
+ va_start(arg, message);
+
+ time(&now);
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (dosyslog) {
+ char syslogmsg[2048];
+
+ vsnprintf(syslogmsg, sizeof(syslogmsg), message, arg);
+ va_end(arg);
+ va_start(arg, message);
+ syslog(priority, "%s", syslogmsg);
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+
+ /*
+ * do the stderr equivalent
+ */
+
+ switch (priority) {
+ case LOG_EMERG :
+ level = "Emergency";
+ break;
+ case LOG_ALERT :
+ level = "Alert";
+ break;
+ case LOG_CRIT :
+ level = "Critical";
+ break;
+ case LOG_ERR :
+ level = "Error";
+ break;
+ case LOG_WARNING :
+ level = "Warning";
+ break;
+ case LOG_NOTICE :
+ level = "Notice";
+ break;
+ case LOG_INFO :
+ level = "Info";
+ break;
+ case LOG_DEBUG :
+ level = "Debug";
+ break;
+ default:
+ level = "???";
+ break;
+ }
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ pmprintf("[%.19s] %s(%" FMT_PID ") %s: ", ctime(&now), pmProgname, getpid(), level);
+ PM_UNLOCK(__pmLock_libpcp);
+ vpmprintf(message, arg);
+ va_end(arg);
+ /* trailing \n if needed */
+ for (p = (char *)message; *p; p++)
+ ;
+ if (p == message || p[-1] != '\n')
+ pmprintf("\n");
+ pmflush();
+}
+
+static void
+logheader(const char *progname, FILE *log, const char *act)
+{
+ time_t now;
+ char host[MAXHOSTNAMELEN];
+
+ setlinebuf(log); /* line buffering for log files */
+ gethostname(host, MAXHOSTNAMELEN);
+ host[MAXHOSTNAMELEN-1] = '\0';
+ time(&now);
+ PM_LOCK(__pmLock_libpcp);
+ fprintf(log, "Log for %s on %s %s %s\n", progname, host, act, ctime(&now));
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+static void
+logfooter(FILE *log, const char *act)
+{
+ time_t now;
+
+ time(&now);
+ PM_LOCK(__pmLock_libpcp);
+ fprintf(log, "\nLog %s %s", act, ctime(&now));
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+static void
+logonexit(void)
+{
+ int i;
+
+ PM_LOCK(__pmLock_libpcp);
+ if (++done_exit != 1) {
+ PM_UNLOCK(__pmLock_libpcp);
+ return;
+ }
+
+ for (i = 0; i < nfilelog; i++)
+ logfooter(filelog[i], "finished");
+
+ PM_UNLOCK(__pmLock_libpcp);
+}
+
+/* common code shared by __pmRotateLog and __pmOpenLog */
+static FILE *
+logreopen(const char *progname, const char *logname, FILE *oldstream,
+ int *status)
+{
+ int oldfd;
+ int dupoldfd;
+ FILE *dupoldstream = oldstream;
+
+ /*
+ * Do our own version of freopen() because the standard one closes the
+ * original stream BEFORE trying to open the new one. Once it's gone,
+ * there's no way to get the closed stream back if the open fails.
+ */
+
+ fflush(oldstream);
+ *status = 0; /* set to one if all this works ... */
+ oldfd = fileno(oldstream);
+ if ((dupoldfd = dup(oldfd)) >= 0) {
+ /*
+ * try to remove the file first ... don't bother if this fails,
+ * but if it succeeds, we at least get a chance to define the
+ * owner and mode, rather than inheriting this from an existing
+ * writeable file ... really only a problem when called as with
+ * uid == 0, e.g. from pmcd(1).
+ */
+ unlink(logname);
+
+ oldstream = freopen(logname, "w", oldstream);
+ if (oldstream == NULL) {
+ int save_error = oserror(); /* need for error message */
+
+ close(oldfd);
+ if (dup(dupoldfd) != oldfd) {
+ /* fd juggling failed! */
+ oldstream = NULL;
+ }
+ else {
+ /* oldfd now re-instated as at entry */
+ oldstream = fdopen(oldfd, "w");
+ }
+ if (oldstream == NULL) {
+ /* serious trouble ... choose least obnoxious alternative */
+ if (dupoldstream == stderr)
+ oldstream = fdopen(fileno(stdout), "w");
+ else
+ oldstream = fdopen(fileno(stderr), "w");
+ }
+ if (oldstream != NULL) {
+ /*
+ * oldstream was NULL, but recovered so now fixup
+ * input oldstream ... this is potentially dangerous,
+ * but we're relying on
+ * (a) fflush(oldstream) on entry flushes buffers
+ * (b) fdopen() leaves new oldstream initialized
+ * (c) caller knows nothing about "new" oldstream
+ * and is never going to fclose() it, so only
+ * fclose() will come at exit() and should be
+ * benign (except possibly for a free() of an
+ * already free()'d buffer)
+ */
+ *dupoldstream = *oldstream; /* struct copy */
+ /* put oldstream back for return value */
+ oldstream = dupoldstream;
+ }
+ pmprintf("%s: cannot open log \"%s\" for writing : %s\n",
+ progname, logname, strerror(save_error));
+ pmflush();
+ }
+ else {
+ /* yippee */
+ *status = 1;
+ }
+ close(dupoldfd);
+ }
+ else {
+ pmprintf("%s: cannot redirect log output to \"%s\": %s\n",
+ progname, logname, strerror(errno));
+ pmflush();
+ }
+ return oldstream;
+}
+
+FILE *
+__pmOpenLog(const char *progname, const char *logname, FILE *oldstream,
+ int *status)
+{
+ oldstream = logreopen(progname, logname, oldstream, status);
+ PM_INIT_LOCKS();
+ logheader(progname, oldstream, "started");
+
+ PM_LOCK(__pmLock_libpcp);
+ nfilelog++;
+ if (nfilelog == 1)
+ atexit(logonexit);
+
+ filelog = (FILE **)realloc(filelog, nfilelog * sizeof(FILE *));
+ if (filelog == NULL) {
+ __pmNoMem("__pmOpenLog", nfilelog * sizeof(FILE *), PM_FATAL_ERR);
+ }
+ filelog[nfilelog-1] = oldstream;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return oldstream;
+}
+
+FILE *
+__pmRotateLog(const char *progname, const char *logname, FILE *oldstream,
+ int *status)
+{
+ int i;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ for (i = 0; i < nfilelog; i++) {
+ if (oldstream == filelog[i]) {
+ logfooter(oldstream, "rotated"); /* old */
+ oldstream = logreopen(progname, logname, oldstream, status);
+ logheader(progname, oldstream, "rotated"); /* new */
+ filelog[i] = oldstream;
+ break;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return oldstream;
+}
+
+/* pmID -> string, max length is 20 bytes */
+char *
+pmIDStr_r(pmID pmid, char *buf, int buflen)
+{
+ __pmID_int* p = (__pmID_int*)&pmid;
+ if (pmid == PM_ID_NULL)
+ snprintf(buf, buflen, "%s", "PM_ID_NULL");
+ else if (p->domain == DYNAMIC_PMID && p->item == 0)
+ /*
+ * this PMID represents the base of a dynamic subtree in the PMNS
+ * ... identified by setting the domain field to the reserved
+ * value DYNAMIC_PMID and storing the real domain of the PMDA
+ * that can enumerate the subtree in the cluster field, while
+ * the item field is not used
+ */
+ snprintf(buf, buflen, "%d.*.*", p->cluster);
+ else
+ snprintf(buf, buflen, "%d.%d.%d", p->domain, p->cluster, p->item);
+ return buf;
+}
+
+const char *
+pmIDStr(pmID pmid)
+{
+ static char idbuf[20];
+ pmIDStr_r(pmid, idbuf, sizeof(idbuf));
+ return idbuf;
+}
+
+/* pmInDom -> string, max length is 20 bytes */
+char *
+pmInDomStr_r(pmInDom indom, char *buf, int buflen)
+{
+ __pmInDom_int* p = (__pmInDom_int*)&indom;
+ if (indom == PM_INDOM_NULL)
+ snprintf(buf, buflen, "%s", "PM_INDOM_NULL");
+ else
+ snprintf(buf, buflen, "%d.%d", p->domain, p->serial);
+ return buf;
+}
+
+const char *
+pmInDomStr(pmInDom indom)
+{
+ static char indombuf[20];
+ pmInDomStr_r(indom, indombuf, sizeof(indombuf));
+ return indombuf;
+}
+
+/* double -> string, max length is 8 bytes */
+char *
+pmNumberStr_r(double value, char *buf, int buflen)
+{
+ if (value >= 0.0) {
+ if (value >= 999995000000000.0)
+ snprintf(buf, buflen, " inf? ");
+ else if (value >= 999995000000.0)
+ snprintf(buf, buflen, "%6.2fT", value / 1000000000000.0);
+ else if (value >= 999995000.0)
+ snprintf(buf, buflen, "%6.2fG", value / 1000000000.0);
+ else if (value >= 999995.0)
+ snprintf(buf, buflen, "%6.2fM", value / 1000000.0);
+ else if (value >= 999.995)
+ snprintf(buf, buflen, "%6.2fK", value / 1000.0);
+ else if (value >= 0.005)
+ snprintf(buf, buflen, "%6.2f ", value);
+ else
+ snprintf(buf, buflen, "%6.2f ", 0.0);
+ }
+ else {
+ if (value <= -99995000000000.0)
+ snprintf(buf, buflen, "-inf? ");
+ else if (value <= -99995000000.0)
+ snprintf(buf, buflen, "%6.2fT", value / 1000000000000.0);
+ else if (value <= -99995000.0)
+ snprintf(buf, buflen, "%6.2fG", value / 1000000000.0);
+ else if (value <= -99995.0)
+ snprintf(buf, buflen, "%6.2fM", value / 1000000.0);
+ else if (value <= -99.995)
+ snprintf(buf, buflen, "%6.2fK", value / 1000.0);
+ else if (value <= -0.005)
+ snprintf(buf, buflen, "%6.2f ", value);
+ else
+ snprintf(buf, buflen, "%6.2f ", 0.0);
+ }
+ return buf;
+}
+
+const char *
+pmNumberStr(double value)
+{
+ static char nbuf[8];
+ pmNumberStr_r(value, nbuf, sizeof(nbuf));
+ return nbuf;
+}
+
+/* flags -> string, max length is 64 bytes */
+char *
+pmEventFlagsStr_r(int flags, char *buf, int buflen)
+{
+ /*
+ * buffer needs to be long enough to hold each flag name
+ * (excluding missed) plus the separation commas, so
+ * point,start,end,id,parent (even though it is unlikely that
+ * both start and end would be set for the one event record)
+ */
+ int started = 0;
+
+ if (flags & PM_EVENT_FLAG_MISSED)
+ return strcpy(buf, "missed");
+
+ buf[0] = '\0';
+ if (flags & PM_EVENT_FLAG_POINT) {
+ if (started++) strcat(buf, ",");
+ strcat(buf, "point");
+ }
+ if (flags & PM_EVENT_FLAG_START) {
+ if (started++) strcat(buf, ",");
+ strcat(buf, "start");
+ }
+ if (flags & PM_EVENT_FLAG_END) {
+ if (started++) strcat(buf, ",");
+ strcat(buf, "end");
+ }
+ if (flags & PM_EVENT_FLAG_ID) {
+ if (started++) strcat(buf, ",");
+ strcat(buf, "id");
+ }
+ if (flags & PM_EVENT_FLAG_PARENT) {
+ if (started++) strcat(buf, ",");
+ strcat(buf, "parent");
+ }
+ return buf;
+}
+
+const char *
+pmEventFlagsStr(int flags)
+{
+ static char ebuf[64];
+ pmEventFlagsStr_r(flags, ebuf, sizeof(ebuf));
+ return ebuf;
+}
+
+/*
+ * Several PMAPI interfaces allocate a list of strings into a buffer
+ * pointed to by (char **) which can be safely freed simply by
+ * freeing the pointer to the buffer.
+ *
+ * Here we provide some functions for manipulating these lists.
+ */
+
+/* Add the given item to the list, which may be empty. */
+int
+__pmStringListAdd(char *item, int numElements, char ***list)
+{
+ size_t ptrSize;
+ size_t dataSize;
+ size_t newSize;
+ char *initialString;
+ char *finalString;
+ char **newList;
+ int i;
+
+ /* Compute the sizes of the pointers and data for the current list. */
+ if (*list != NULL) {
+ ptrSize = numElements * sizeof(**list);
+ initialString = **list;
+ finalString = (*list)[numElements - 1];
+ dataSize = (finalString + strlen(finalString) + 1) - initialString;
+ }
+ else {
+ ptrSize = 0;
+ dataSize = 0;
+ }
+
+ /*
+ * Now allocate a new buffer for the expanded list.
+ * We need room for a new pointer and for the new item.
+ */
+ newSize = ptrSize + sizeof(**list) + dataSize + strlen(item) + 1;
+ newList = realloc(*list, newSize);
+ if (newList == NULL) {
+ __pmNoMem("__pmStringListAdd", newSize, PM_FATAL_ERR);
+ }
+
+ /*
+ * Shift the existing data to make room for the new pointer and
+ * recompute each existing pointer.
+ */
+ finalString = (char *)(newList + numElements + 1);
+ if (dataSize != 0) {
+ initialString = (char *)(newList + numElements);
+ memmove(finalString, initialString, dataSize);
+ for (i = 0; i < numElements; ++i) {
+ newList[i] = finalString;
+ finalString += strlen(finalString) + 1;
+ }
+ }
+
+ /* Now add the new item. */
+ newList[numElements] = finalString;
+ strcpy(finalString, item);
+
+ *list = newList;
+ return numElements + 1;
+}
+
+/* Search for the given string in the given string list. */
+char *
+__pmStringListFind(const char *item, int numElements, char **list)
+{
+ int e;
+
+ if (list == NULL)
+ return NULL; /* no list to search */
+
+ for (e = 0; e < numElements; ++e) {
+ if (strcmp(item, list[e]) == 0)
+ return list[e];
+ }
+
+ /* Not found. */
+ return NULL;
+}
+
+/*
+ * Save/restore global debugging flag, without locking.
+ * Needed since tracing PDUs really messes __pmDump*() routines
+ * up when pmNameInDom is called internally.
+ */
+static int
+save_debug(void)
+{
+ int saved = pmDebug;
+ pmDebug = 0;
+ return saved;
+}
+
+static void
+restore_debug(int saved)
+{
+ pmDebug = saved;
+}
+
+static void
+dump_valueset(FILE *f, pmValueSet *vsp)
+{
+ pmDesc desc;
+ char errmsg[PM_MAXERRMSGLEN];
+ char strbuf[20];
+ char *pmid, *p;
+ int have_desc = 1;
+ int n, j;
+
+ pmid = pmIDStr_r(vsp->pmid, strbuf, sizeof(strbuf));
+ if ((n = pmNameID(vsp->pmid, &p)) < 0)
+ fprintf(f," %s (%s):", pmid, "<noname>");
+ else {
+ fprintf(f," %s (%s):", pmid, p);
+ free(p);
+ }
+ if (vsp->numval == 0) {
+ fprintf(f, " No values returned!\n");
+ return;
+ }
+ if (vsp->numval < 0) {
+ fprintf(f, " %s\n", pmErrStr_r(vsp->numval, errmsg, sizeof(errmsg)));
+ return;
+ }
+ if (__pmGetInternalState() == PM_STATE_PMCS ||
+ pmLookupDesc(vsp->pmid, &desc) < 0) {
+ /* don't know, so punt on the most common cases */
+ desc.indom = PM_INDOM_NULL;
+ have_desc = 0;
+ }
+
+ fprintf(f, " numval: %d", vsp->numval);
+ fprintf(f, " valfmt: %d vlist[]:\n", vsp->valfmt);
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) {
+ fprintf(f," inst [%d", vp->inst);
+ if (have_desc &&
+ pmNameInDom(desc.indom, vp->inst, &p) >= 0) {
+ fprintf(f, " or \"%s\"]", p);
+ free(p);
+ }
+ else {
+ fprintf(f, " or ???]");
+ }
+ fputc(' ', f);
+ }
+ else
+ fprintf(f, " ");
+ fprintf(f, "value ");
+
+ if (have_desc)
+ pmPrintValue(f, vsp->valfmt, desc.type, vp, 1);
+ else {
+ if (vsp->valfmt == PM_VAL_INSITU)
+ pmPrintValue(f, vsp->valfmt, PM_TYPE_UNKNOWN, vp, 1);
+ else
+ pmPrintValue(f, vsp->valfmt, (int)vp->value.pval->vtype, vp, 1);
+ }
+ fputc('\n', f);
+ }
+}
+
+void
+__pmDumpResult(FILE *f, const pmResult *resp)
+{
+ int i, saved;
+
+ saved = save_debug();
+ fprintf(f, "pmResult dump from " PRINTF_P_PFX "%p timestamp: %d.%06d ",
+ resp, (int)resp->timestamp.tv_sec, (int)resp->timestamp.tv_usec);
+ __pmPrintStamp(f, &resp->timestamp);
+ fprintf(f, " numpmid: %d\n", resp->numpmid);
+ for (i = 0; i < resp->numpmid; i++)
+ dump_valueset(f, resp->vset[i]);
+ restore_debug(saved);
+}
+
+void
+__pmDumpHighResResult(FILE *f, const pmHighResResult *hresp)
+{
+ int i, saved;
+
+ saved = save_debug();
+ fprintf(f, "pmHighResResult dump from " PRINTF_P_PFX "%p timestamp: %d.%09d ",
+ hresp, (int)hresp->timestamp.tv_sec, (int)hresp->timestamp.tv_nsec);
+ __pmPrintHighResStamp(f, &hresp->timestamp);
+ fprintf(f, " numpmid: %d\n", hresp->numpmid);
+ for (i = 0; i < hresp->numpmid; i++)
+ dump_valueset(f, hresp->vset[i]);
+ restore_debug(saved);
+}
+
+static void
+print_event_summary(FILE *f, const pmValue *val, int highres)
+{
+ struct timespec tsstamp;
+ struct timeval tvstamp;
+ __pmTimespec *tsp;
+ __pmTimeval *tvp;
+ unsigned int flags;
+ size_t size;
+ char *base;
+ int nparams;
+ int nrecords;
+ int nmissed = 0;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+
+ if (highres) {
+ pmHighResEventArray *hreap = (pmHighResEventArray *)val->value.pval;
+ nrecords = hreap->ea_nrecords;
+ base = (char *)&hreap->ea_record[0];
+ tsp = (__pmTimespec *)base;
+ tsstamp.tv_sec = tsp->tv_sec;
+ tsstamp.tv_nsec = tsp->tv_nsec;
+ }
+ else {
+ pmEventArray *eap = (pmEventArray *)val->value.pval;
+ nrecords = eap->ea_nrecords;
+ base = (char *)&eap->ea_record[0];
+ tvp = (__pmTimeval *)base;
+ tvstamp.tv_sec = tvp->tv_sec;
+ tvstamp.tv_usec = tvp->tv_usec;
+ }
+
+ /* walk packed event record array */
+ for (r = 0; r < nrecords-1; r++) {
+ if (highres) {
+ pmHighResEventRecord *hrerp = (pmHighResEventRecord *)base;
+ size = sizeof(hrerp->er_timestamp) + sizeof(hrerp->er_flags) +
+ sizeof(hrerp->er_nparams);
+ flags = hrerp->er_flags;
+ nparams = hrerp->er_nparams;
+ }
+ else {
+ pmEventRecord *erp = (pmEventRecord *)base;
+ size = sizeof(erp->er_timestamp) + sizeof(erp->er_flags) +
+ sizeof(erp->er_nparams);
+ flags = erp->er_flags;
+ nparams = erp->er_nparams;
+ }
+
+ if (flags & PM_EVENT_FLAG_MISSED) {
+ nmissed += nparams;
+ continue;
+ }
+
+ base += size;
+ for (p = 0; p < nparams; p++) {
+ pmEventParameter *epp = (pmEventParameter *)base;
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ }
+ }
+ fprintf(f, "[%d event record", nrecords);
+ if (nrecords != 1)
+ fputc('s', f);
+ if (nmissed > 0)
+ fprintf(f, " (%d missed)", nmissed);
+ if (nrecords > 0) {
+ fprintf(f, " timestamp");
+ if (nrecords > 1)
+ fputc('s', f);
+ fputc(' ', f);
+
+ if (highres)
+ __pmPrintHighResStamp(f, &tsstamp);
+ else
+ __pmPrintStamp(f, &tvstamp);
+
+ if (nrecords > 1) {
+ fprintf(f, "...");
+ if (highres) {
+ tsp = (__pmTimespec *)base;
+ tsstamp.tv_sec = tsp->tv_sec;
+ tsstamp.tv_nsec = tsp->tv_nsec;
+ __pmPrintHighResStamp(f, &tsstamp);
+ }
+ else {
+ tvp = (__pmTimeval *)base;
+ tvstamp.tv_sec = tvp->tv_sec;
+ tvstamp.tv_usec = tvp->tv_usec;
+ __pmPrintStamp(f, &tvstamp);
+ }
+ }
+ }
+ fputc(']', f);
+}
+
+/* Print single pmValue. */
+void
+pmPrintValue(FILE *f, /* output stream */
+ int valfmt, /* from pmValueSet */
+ int type, /* from pmDesc */
+ const pmValue *val, /* value to print */
+ int minwidth) /* output is at least this wide */
+{
+ pmAtomValue a;
+ int i;
+ int n;
+ char *p;
+ int sts;
+
+ if (type != PM_TYPE_UNKNOWN &&
+ type != PM_TYPE_EVENT &&
+ type != PM_TYPE_HIGHRES_EVENT) {
+ sts = pmExtractValue(valfmt, val, type, &a, type);
+ if (sts < 0)
+ type = PM_TYPE_UNKNOWN;
+ }
+
+ switch (type) {
+ case PM_TYPE_32:
+ fprintf(f, "%*i", minwidth, a.l);
+ break;
+
+ case PM_TYPE_U32:
+ fprintf(f, "%*u", minwidth, a.ul);
+ break;
+
+ case PM_TYPE_64:
+ fprintf(f, "%*"PRIi64, minwidth, a.ll);
+ break;
+
+ case PM_TYPE_U64:
+ fprintf(f, "%*"PRIu64, minwidth, a.ull);
+ break;
+
+ case PM_TYPE_FLOAT:
+ fprintf(f, "%*.8g", minwidth, (double)a.f);
+ break;
+
+ case PM_TYPE_DOUBLE:
+ fprintf(f, "%*.16g", minwidth, a.d);
+ break;
+
+ case PM_TYPE_STRING:
+ n = (int)strlen(a.cp) + 2;
+ while (n < minwidth) {
+ fputc(' ', f);
+ n++;
+ }
+ fprintf(f, "\"%s\"", a.cp);
+ free(a.cp);
+ break;
+
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_UNKNOWN:
+ if (valfmt == PM_VAL_INSITU) {
+ float *fp = (float *)&val->value.lval;
+ __uint32_t *ip = (__uint32_t *)&val->value.lval;
+ int fp_bad = 0;
+ fprintf(f, "%*u", minwidth, *ip);
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(*fp) == FP_NAN;
+#else
+#ifdef HAVE_ISNANF
+ fp_bad = isnanf(*fp);
+#endif
+#endif
+ if (!fp_bad)
+ fprintf(f, " %*.8g", minwidth, (double)*fp);
+ if (minwidth > 2)
+ minwidth -= 2;
+ fprintf(f, " 0x%*x", minwidth, val->value.lval);
+ }
+ else {
+ int string;
+ int done = 0;
+ if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(__uint64_t)) {
+ __uint64_t tmp;
+ memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp));
+ fprintf(f, "%*"PRIu64, minwidth, tmp);
+ done = 1;
+ }
+ if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(double)) {
+ double tmp;
+ int fp_bad = 0;
+ memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp));
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(tmp) == FP_NAN;
+#else
+#ifdef HAVE_ISNAN
+ fp_bad = isnan(tmp);
+#endif
+#endif
+ if (!fp_bad) {
+ if (done) fputc(' ', f);
+ fprintf(f, "%*.16g", minwidth, tmp);
+ done = 1;
+ }
+ }
+ if (val->value.pval->vlen == PM_VAL_HDR_SIZE + sizeof(float)) {
+ float tmp;
+ int fp_bad = 0;
+ memcpy((void *)&tmp, (void *)val->value.pval->vbuf, sizeof(tmp));
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(tmp) == FP_NAN;
+#else
+#ifdef HAVE_ISNANF
+ fp_bad = isnanf(tmp);
+#endif
+#endif
+ if (!fp_bad) {
+ if (done) fputc(' ', f);
+ fprintf(f, "%*.8g", minwidth, (double)tmp);
+ done = 1;
+ }
+ }
+ if (val->value.pval->vlen < PM_VAL_HDR_SIZE)
+ fprintf(f, "pmPrintValue: negative length (%d) for aggregate value?",
+ (int)val->value.pval->vlen - PM_VAL_HDR_SIZE);
+ else {
+ string = 1;
+ for (n = 0; n < val->value.pval->vlen - PM_VAL_HDR_SIZE; n++) {
+ if (!isprint((int)val->value.pval->vbuf[n])) {
+ string = 0;
+ break;
+ }
+ }
+ if (string) {
+ if (done) fputc(' ', f);
+ n = (int)val->value.pval->vlen - PM_VAL_HDR_SIZE + 2;
+ while (n < minwidth) {
+ fputc(' ', f);
+ n++;
+ }
+ n = (int)val->value.pval->vlen - PM_VAL_HDR_SIZE;
+ fprintf(f, "\"%*.*s\"", n, n, val->value.pval->vbuf);
+ done = 1;
+ }
+ n = 2 * (val->value.pval->vlen - PM_VAL_HDR_SIZE) + 2;
+ while (n < minwidth) {
+ fputc(' ', f);
+ n++;
+ }
+ if (done) fputc(' ', f);
+ fputc('[', f);
+ p = &val->value.pval->vbuf[0];
+ for (i = 0; i < val->value.pval->vlen - PM_VAL_HDR_SIZE; i++) {
+ fprintf(f, "%02x", *p & 0xff);
+ p++;
+ }
+ fputc(']', f);
+ }
+ }
+ if (type != PM_TYPE_UNKNOWN)
+ free(a.vbp);
+ break;
+
+ case PM_TYPE_EVENT: /* not much we can do about minwidth */
+ case PM_TYPE_HIGHRES_EVENT:
+ if (valfmt == PM_VAL_INSITU) {
+ /*
+ * Special case for pmlc/pmlogger where PMLC_SET_*() macros
+ * used to set control requests / state in the lval field
+ * and the pval does not really contain a valid event record
+ * Code here comes from PrintState() in actions.c from pmlc.
+ */
+ fputs("[pmlc control ", f);
+ fputs(PMLC_GET_MAND(val->value.lval) ? "mand " : "adv ", f);
+ fputs(PMLC_GET_ON(val->value.lval) ? "on " : "off ", f);
+ if (PMLC_GET_INLOG(val->value.lval))
+ fputs(PMLC_GET_AVAIL(val->value.lval) ? " " : "na ", f);
+ else
+ fputs("nl ", f);
+ fprintf(f, "%d]", PMLC_GET_DELTA(val->value.lval));
+ }
+ else
+ print_event_summary(f, val, type != PM_TYPE_EVENT);
+ break;
+
+ case PM_TYPE_NOSUPPORT:
+ fprintf(f, "pmPrintValue: bogus value, metric Not Supported\n");
+ break;
+
+ default:
+ fprintf(f, "pmPrintValue: unknown value type=%d\n", type);
+ }
+}
+
+void
+__pmNoMem(const char *where, size_t size, int fatal)
+{
+ char errmsg[PM_MAXERRMSGLEN];
+ __pmNotifyErr(fatal ? LOG_ERR : LOG_WARNING,
+ "%s: malloc(%d) failed: %s",
+ where, (int)size, osstrerror_r(errmsg, sizeof(errmsg)));
+ if (fatal)
+ exit(1);
+}
+
+/*
+ * this one is used just below the PMAPI to convert platform errors
+ * into more appropriate PMAPI error codes
+ */
+int
+__pmMapErrno(int sts)
+{
+ if (sts == -EBADF || sts == -EPIPE)
+ sts = PM_ERR_IPC;
+#ifdef IS_MINGW
+ else if (sts == -EINVAL)
+ sts = PM_ERR_IPC;
+#endif
+ return sts;
+}
+
+int
+__pmGetTimespec(struct timespec *ts)
+{
+#if defined(IS_DARWIN)
+ clock_serv_t cclock;
+ mach_timespec_t mts;
+
+ host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
+ clock_get_time(cclock, &mts);
+ mach_port_deallocate(mach_task_self(), cclock);
+ ts->tv_sec = mts.tv_sec;
+ ts->tv_nsec = mts.tv_nsec;
+ return 0;
+#elif defined(HAVE_CLOCK_GETTIME)
+ return clock_gettime(CLOCK_REALTIME, ts);
+#else
+#warning "No high resolution timestamp support on this platform"
+ struct timeval tv;
+ int sts;
+
+ if ((sts = gettimeofday(&tv, NULL)) == 0) {
+ ts->tv_sec = tv.tv_sec;
+ ts->tv_nsec = tv.tv_usec * 1000;
+ }
+ return sts;
+#endif
+}
+
+/*
+ * difference for two on the internal timestamps
+ */
+double
+__pmTimevalSub(const __pmTimeval *ap, const __pmTimeval *bp)
+{
+ return ap->tv_sec - bp->tv_sec + (double)(ap->tv_usec - bp->tv_usec)/1000000.0;
+}
+
+/*
+ * print timeval timestamp in HH:MM:SS.XXX format
+ */
+void
+__pmPrintStamp(FILE *f, const struct timeval *tp)
+{
+ struct tm tmp;
+ time_t now;
+
+ now = (time_t)tp->tv_sec;
+ pmLocaltime(&now, &tmp);
+ fprintf(f, "%02d:%02d:%02d.%03d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(tp->tv_usec/1000));
+}
+
+/*
+ * print high resolution timestamp in HH:MM:SS.XXXXXXXXX format
+ */
+void
+__pmPrintHighResStamp(FILE *f, const struct timespec *tp)
+{
+ struct tm tmp;
+ time_t now;
+
+ now = (time_t)tp->tv_sec;
+ pmLocaltime(&now, &tmp);
+ fprintf(f, "%02d:%02d:%02d.%09d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(tp->tv_nsec));
+}
+
+/*
+ * print __pmTimeval timestamp in HH:MM:SS.XXX format
+ * (__pmTimeval variant used in PDUs, archives and internally)
+ */
+void
+__pmPrintTimeval(FILE *f, const __pmTimeval *tp)
+{
+ struct tm tmp;
+ time_t now;
+
+ now = (time_t)tp->tv_sec;
+ pmLocaltime(&now, &tmp);
+ fprintf(f, "%02d:%02d:%02d.%03d", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, tp->tv_usec/1000);
+}
+
+/*
+ * print __pmTimespec timestamp in HH:MM:SS.XXXXXXXXX format
+ * (__pmTimespec variant used in events, archives and internally)
+ */
+void
+__pmPrintTimespec(FILE *f, const __pmTimespec *tp)
+{
+ struct tm tmp;
+ time_t now;
+
+ now = (time_t)tp->tv_sec;
+ pmLocaltime(&now, &tmp);
+ fprintf(f, "%02d:%02d:%02d.%09ld", tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (long)tp->tv_nsec);
+}
+
+/*
+ * descriptor
+ */
+void
+__pmPrintDesc(FILE *f, const pmDesc *desc)
+{
+ const char *type;
+ const char *sem;
+ static const char *unknownVal = "???";
+ const char *units;
+ char strbuf[60];
+
+ if (desc->type == PM_TYPE_NOSUPPORT) {
+ fprintf(f, " Data Type: Not Supported\n");
+ return;
+ }
+
+ switch (desc->type) {
+ case PM_TYPE_32:
+ type = "32-bit int";
+ break;
+ case PM_TYPE_U32:
+ type = "32-bit unsigned int";
+ break;
+ case PM_TYPE_64:
+ type = "64-bit int";
+ break;
+ case PM_TYPE_U64:
+ type = "64-bit unsigned int";
+ break;
+ case PM_TYPE_FLOAT:
+ type = "float";
+ break;
+ case PM_TYPE_DOUBLE:
+ type = "double";
+ break;
+ case PM_TYPE_STRING:
+ type = "string";
+ break;
+ case PM_TYPE_AGGREGATE:
+ type = "aggregate";
+ break;
+ case PM_TYPE_AGGREGATE_STATIC:
+ type = "static aggregate";
+ break;
+ case PM_TYPE_EVENT:
+ type = "event record array";
+ break;
+ case PM_TYPE_HIGHRES_EVENT:
+ type = "highres event record array";
+ break;
+ default:
+ type = unknownVal;
+ break;
+ }
+ fprintf(f, " Data Type: %s", type);
+ if (type == unknownVal)
+ fprintf(f, " (%d)", desc->type);
+
+ fprintf(f," InDom: %s 0x%x\n", pmInDomStr_r(desc->indom, strbuf, sizeof(strbuf)), desc->indom);
+
+ switch (desc->sem) {
+ case PM_SEM_COUNTER:
+ sem = "counter";
+ break;
+ case PM_SEM_INSTANT:
+ sem = "instant";
+ break;
+ case PM_SEM_DISCRETE:
+ sem = "discrete";
+ break;
+ default:
+ sem = unknownVal;
+ break;
+ }
+
+ fprintf(f, " Semantics: %s", sem);
+ if (sem == unknownVal)
+ fprintf(f, " (%d)", desc->sem);
+
+ fprintf(f, " Units: ");
+ units = pmUnitsStr_r(&desc->units, strbuf, sizeof(strbuf));
+ if (*units == '\0')
+ fprintf(f, "none\n");
+ else
+ fprintf(f, "%s\n", units);
+}
+
+/*
+ * print times between events
+ */
+void
+__pmEventTrace_r(const char *event, int *first, double *sum, double *last)
+{
+#ifdef PCP_DEBUG
+ struct timeval tv;
+ double now;
+
+ __pmtimevalNow(&tv);
+ now = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
+ if (*first) {
+ *first = 0;
+ *sum = 0;
+ *last = now;
+ }
+ *sum += now - *last;
+ fprintf(stderr, "%s: +%4.2f = %4.2f -> %s\n",
+ pmProgname, now-*last, *sum, event);
+ *last = now;
+#endif
+}
+
+void
+__pmEventTrace(const char *event)
+{
+#ifdef PCP_DEBUG
+ static double last;
+ static double sum;
+ static int first = 1;
+
+ __pmEventTrace_r(event, &first, &sum, &last);
+#endif
+}
+
+int
+__pmParseDebug(const char *spec)
+{
+#ifdef PCP_DEBUG
+ int val = 0;
+ int tmp;
+ const char *p;
+ char *pend;
+ int i;
+
+ for (p = spec; *p; ) {
+ tmp = (int)strtol(p, &pend, 10);
+ if (tmp == -1)
+ /* special case ... -1 really means set all the bits! */
+ tmp = INT_MAX;
+ if (*pend == '\0') {
+ val |= tmp;
+ break;
+ }
+ else if (*pend == ',') {
+ val |= tmp;
+ p = pend + 1;
+ }
+ else {
+ pend = strchr(p, ',');
+ if (pend != NULL)
+ *pend = '\0';
+
+ if (strcasecmp(p, "ALL") == 0) {
+ val |= INT_MAX;
+ if (pend != NULL) {
+ *pend = ',';
+ p = pend + 1;
+ }
+ else
+ p = ""; /* force termination of outer loop */
+ break;
+ }
+
+ for (i = 0; i < num_debug; i++) {
+ if (strcasecmp(p, debug_map[i].name) == 0) {
+ val |= debug_map[i].bit;
+ if (pend != NULL) {
+ *pend = ',';
+ p = pend + 1;
+ }
+ else
+ p = ""; /* force termination of outer loop */
+ break;
+ }
+ }
+
+ if (i == num_debug) {
+ if (pend != NULL)
+ *pend = ',';
+ return PM_ERR_CONV;
+ }
+ }
+ }
+
+ return val;
+#else
+ return PM_ERR_NYI;
+#endif
+}
+
+int
+__pmGetInternalState(void)
+{
+ return pmState;
+}
+
+void
+__pmSetInternalState(int state)
+{
+ pmState = state;
+}
+
+
+/*
+ * GUI output option
+ */
+
+#define MSGBUFLEN 256
+static FILE *fptr = NULL;
+static int msgsize = 0;
+static char *fname; /* temporary file name for buffering errors */
+static char *ferr; /* error output filename from PCP_STDERR */
+
+#define PM_QUERYERR -1
+#define PM_USEDIALOG 0
+#define PM_USESTDERR 1
+#define PM_USEFILE 2
+
+static int
+pmfstate(int state)
+{
+ static int errtype = -1;
+
+ if (state > PM_QUERYERR)
+ errtype = state;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (errtype == PM_QUERYERR) {
+ errtype = PM_USESTDERR;
+ if ((ferr = getenv("PCP_STDERR")) != NULL) {
+ if (strcasecmp(ferr, "DISPLAY") == 0) {
+ char * xconfirm = pmGetConfig("PCP_XCONFIRM_PROG");
+ if (access(__pmNativePath(xconfirm), X_OK) < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s: using stderr - cannot access %s: %s\n",
+ pmProgname, xconfirm, osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ else
+ errtype = PM_USEDIALOG;
+ }
+ else if (strcmp(ferr, "") != 0)
+ errtype = PM_USEFILE;
+ }
+ }
+ PM_UNLOCK(__pmLock_libpcp);
+ return errtype;
+}
+
+static int
+vpmprintf(const char *msg, va_list arg)
+{
+ int lsize = 0;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (fptr == NULL && msgsize == 0) { /* create scratch file */
+ int fd = -1;
+ char *tmpdir = pmGetConfig("PCP_TMPFILE_DIR");
+
+ if (tmpdir[0] != '\0') {
+ mode_t cur_umask;
+
+ /*
+ * PCP_TMPFILE_DIR found in the configuration/environment,
+ * otherwise fall through to the stderr case
+ */
+
+#if HAVE_MKSTEMP
+ fname = (char *)malloc(MAXPATHLEN+1);
+ if (fname == NULL) goto fail;
+ snprintf(fname, MAXPATHLEN, "%s/pcp-XXXXXX", tmpdir);
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ fd = mkstemp(fname);
+ umask(cur_umask);
+#else
+ fname = tempnam(tmpdir, "pcp-");
+ if (fname == NULL) goto fail;
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ fd = open(fname, O_RDWR|O_APPEND|O_CREAT|O_EXCL, 0600);
+ umask(cur_umask);
+#endif /* HAVE_MKSTEMP */
+
+ if (fd < 0) goto fail;
+ if ((fptr = fdopen(fd, "a")) == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+fail:
+ if (fname != NULL) {
+ fprintf(stderr, "%s: vpmprintf: failed to create \"%s\": %s\n",
+ pmProgname, fname, osstrerror_r(errmsg, sizeof(errmsg)));
+ unlink(fname);
+ free(fname);
+ }
+ else {
+ fprintf(stderr, "%s: vpmprintf: failed to create temporary file: %s\n",
+ pmProgname, osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ fprintf(stderr, "vpmprintf msg:\n");
+ if (fd >= 0)
+ close(fd);
+ msgsize = -1;
+ fptr = NULL;
+ }
+ }
+ else
+ msgsize = -1;
+ }
+
+ if (msgsize < 0) {
+ vfprintf(stderr, msg, arg);
+ fflush(stderr);
+ lsize = 0;
+ }
+ else
+ msgsize += (lsize = vfprintf(fptr, msg, arg));
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return lsize;
+}
+
+int
+pmprintf(const char *msg, ...)
+{
+ va_list arg;
+ int lsize;
+
+ va_start(arg, msg);
+ lsize = vpmprintf(msg, arg);
+ va_end(arg);
+ return lsize;
+}
+
+int
+pmflush(void)
+{
+ int sts = 0;
+ int len;
+ int state;
+ FILE *eptr = NULL;
+ char outbuf[MSGBUFLEN];
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if (fptr != NULL && msgsize > 0) {
+ fflush(fptr);
+ state = pmfstate(PM_QUERYERR);
+ if (state == PM_USEFILE) {
+ if ((eptr = fopen(ferr, "a")) == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "pmflush: cannot append to file '%s' (from "
+ "$PCP_STDERR): %s\n", ferr, osstrerror_r(errmsg, sizeof(errmsg)));
+ state = PM_USESTDERR;
+ }
+ }
+ switch (state) {
+ case PM_USESTDERR:
+ rewind(fptr);
+ while ((len = (int)read(fileno(fptr), outbuf, MSGBUFLEN)) > 0) {
+ sts = write(fileno(stderr), outbuf, len);
+ if (sts != len) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "pmflush: write() failed: %s\n",
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ sts = 0;
+ }
+ break;
+ case PM_USEDIALOG:
+ /* If we're here, it means xconfirm has passed access test */
+ snprintf(outbuf, sizeof(outbuf), "%s -file %s -c -B OK -icon info"
+ " %s -header 'PCP Information' >/dev/null",
+ __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG")), fname,
+ (msgsize > 80 ? "-useslider" : ""));
+ if (system(outbuf) < 0) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "%s: system failed: %s\n", pmProgname,
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ sts = -oserror();
+ }
+ break;
+ case PM_USEFILE:
+ rewind(fptr);
+ while ((len = (int)read(fileno(fptr), outbuf, MSGBUFLEN)) > 0) {
+ sts = write(fileno(eptr), outbuf, len);
+ if (sts != len) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "pmflush: write() failed: %s\n",
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ sts = 0;
+ }
+ fclose(eptr);
+ break;
+ }
+ fclose(fptr);
+ fptr = NULL;
+ unlink(fname);
+ free(fname);
+ if (sts >= 0)
+ sts = msgsize;
+ }
+
+ msgsize = 0;
+
+ PM_UNLOCK(__pmLock_libpcp);
+ return sts;
+}
+
+/*
+ * Set the pmcd client identity as exported by pmcd.client.whoami
+ *
+ * Identity is of the form
+ * hostname (ipaddr) <id>
+ *
+ * Assumes you already have a current host context.
+ */
+int
+__pmSetClientId(const char *id)
+{
+ char *name = "pmcd.client.whoami";
+ pmID pmid;
+ int sts;
+ pmResult store = { .numpmid = 1 };
+ pmValueSet pmvs;
+ pmValueBlock *pmvb;
+ char host[MAXHOSTNAMELEN];
+ char *ipaddr = NULL;
+ __pmHostEnt *servInfo;
+ int vblen;
+
+ if ((sts = pmLookupName(1, &name, &pmid)) < 0)
+ return sts;
+
+ /*
+ * Try to obtain the address and the actual host name.
+ * Compute the vblen as we go.
+ */
+ vblen = 0;
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ if ((servInfo = __pmGetAddrInfo(host)) != NULL) {
+ __pmSockAddr *addr;
+ void *enumIx = NULL;
+ char *servInfoName = NULL;
+ for (addr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ addr != NULL;
+ addr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ servInfoName = __pmGetNameInfo(addr);
+ if (servInfoName != NULL)
+ break;
+ __pmSockAddrFree(addr);
+ }
+ __pmHostEntFree(servInfo);
+
+ /* Did we get a name? */
+ if (servInfoName == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmSetClientId: __pmGetNameInfo() failed: %s\n",
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ else {
+ strncpy(host, servInfoName, sizeof(host));
+ host[sizeof(host) - 1] = '\0';
+ free(servInfoName);
+ }
+ vblen = strlen(host) + strlen(id) + 2;
+
+ /* Did we get an address? */
+ if (addr != NULL) {
+ ipaddr = __pmSockAddrToString(addr);
+ __pmSockAddrFree(addr);
+ if (ipaddr == NULL) {
+ char errmsg[PM_MAXERRMSGLEN];
+ fprintf(stderr, "__pmSetClientId: __pmSockAddrToString() failed: %s\n",
+ osstrerror_r(errmsg, sizeof(errmsg)));
+ }
+ else
+ vblen += strlen(ipaddr) + 3;
+ }
+ }
+ vblen += strlen(host) + strlen(id) + 2;
+
+ /* build pmResult for pmStore() */
+ pmvb = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE+vblen);
+ if (pmvb == NULL) {
+ __pmNoMem("__pmSetClientId", PM_VAL_HDR_SIZE+vblen, PM_RECOV_ERR);
+ return -ENOMEM;
+ }
+ pmvb->vtype = PM_TYPE_STRING;
+ pmvb->vlen = PM_VAL_HDR_SIZE+vblen;
+ strcpy(pmvb->vbuf, host);
+ strcat(pmvb->vbuf, " ");
+ if (ipaddr != NULL) {
+ strcat(pmvb->vbuf, "(");
+ strcat(pmvb->vbuf, ipaddr);
+ strcat(pmvb->vbuf, ") ");
+ free(ipaddr);
+ }
+ strcat(pmvb->vbuf, id);
+
+ pmvs.pmid = pmid;
+ pmvs.numval = 1;
+ pmvs.valfmt = PM_VAL_SPTR;
+ pmvs.vlist[0].value.pval = pmvb;
+ pmvs.vlist[0].inst = PM_IN_NULL;
+
+ store.vset[0] = &pmvs;
+ sts = pmStore(&store);
+ free(pmvb);
+ return sts;
+}
+
+char *
+__pmGetClientId(int argc, char **argv)
+{
+ char *clientID;
+ int a, need = 0;
+
+ for (a = 0; a < argc; a++)
+ need += strlen(argv[a]) + 1;
+ clientID = (char *)malloc(need);
+ if (clientID) {
+ clientID[0] = '\0';
+ for (a = 0; a < argc; a++) {
+ strcat(clientID, argv[a]);
+ if (a < argc - 1)
+ strcat(clientID, " ");
+ }
+ }
+ return clientID;
+}
+
+int
+__pmSetClientIdArgv(int argc, char **argv)
+{
+ char *id = __pmGetClientId(argc, argv);
+ int sts;
+
+ if (id) {
+ sts = __pmSetClientId(id);
+ free(id);
+ return sts;
+ }
+ return -ENOMEM;
+}
+
+/*
+ * Support for C environments that have lame libc implementations.
+ * All of these developed from first principles, so no 3rd party
+ * copyright or licensing issues, else used under a licence that
+ * is compatible with the PCP licence.
+ */
+
+#ifndef HAVE_BASENAME
+char *
+basename(char *name)
+{
+ char *p = strrchr(name, '/');
+
+ if (p == NULL)
+ return(name);
+ else
+ return(p+1);
+}
+#endif /* HAVE_BASENAME */
+
+#ifndef HAVE_DIRNAME
+char *
+dirname(char *name)
+{
+ char *p = strrchr(name, '/');
+
+ if (p == NULL)
+ return(".");
+ else {
+ *p = '\0';
+ return(name);
+ }
+}
+#endif /* HAVE_DIRNAME */
+
+/*
+ * Create a directory, including all of its path components.
+ */
+int
+__pmMakePath(const char *dir, mode_t mode)
+{
+ char path[MAXPATHLEN], *p;
+ int sts;
+
+ sts = access(dir, R_OK|W_OK|X_OK);
+ if (sts == 0)
+ return 0;
+ if (sts < 0 && oserror() != ENOENT)
+ return -1;
+
+ strncpy(path, dir, sizeof(path));
+ path[sizeof(path)-1] = '\0';
+
+ for (p = path+1; *p != '\0'; p++) {
+ if (*p == __pmPathSeparator()) {
+ *p = '\0';
+ mkdir2(path, mode);
+ *p = __pmPathSeparator();
+ }
+ }
+ return mkdir2(path, mode);
+}
+
+#ifndef HAVE_STRNDUP
+char *
+strndup(const char *s, size_t n)
+{
+ char *buf;
+
+ if ((buf = malloc(n + 1)) != NULL) {
+ strncpy(buf, s, n);
+ buf[n] = '\0';
+ }
+ return buf;
+}
+#endif /* HAVE_STRNDUP */
+
+#ifndef HAVE_STRCHRNUL
+char *
+strchrnul(const char *s, int c)
+{
+ char *result;
+
+ if ((result = strchr(s, c)) == NULL)
+ result = strchr(s, '\0');
+ return result;
+}
+#endif /* HAVE_STRCHRNUL */
+
+#ifndef HAVE_SCANDIR
+/*
+ * Scan the directory dirname, building an array of pointers to
+ * dirent entries using malloc(3C). select() and compare() are
+ * used to optionally filter and sort directory entries.
+ */
+int
+scandir(const char *dirname, struct dirent ***namelist,
+ int(*select)(const_dirent *),
+ int(*compare)(const_dirent **, const_dirent **))
+{
+ DIR *dirp;
+ int n = 0;
+ struct dirent **names = NULL;
+ struct dirent *dp;
+ struct dirent *tp;
+
+ PM_INIT_LOCKS();
+ PM_LOCK(__pmLock_libpcp);
+ if ((dirp = opendir(dirname)) == NULL)
+ return -1;
+
+ while ((dp = readdir(dirp)) != NULL) {
+ if (select && (*select)(dp) == 0)
+ continue;
+
+ n++;
+ if ((names = (struct dirent **)realloc(names, n * sizeof(dp))) == NULL) {
+ PM_UNLOCK(__pmLock_libpcp);
+ closedir(dirp);
+ return -1;
+ }
+
+ if ((names[n-1] = tp = (struct dirent *)malloc(
+ sizeof(*dp)-sizeof(dp->d_name)+strlen(dp->d_name)+1)) == NULL) {
+ PM_UNLOCK(__pmLock_libpcp);
+ closedir(dirp);
+ return -1;
+ }
+
+ tp->d_ino = dp->d_ino;
+#if defined(HAVE_DIRENT_D_OFF)
+ tp->d_off = dp->d_off;
+#else
+ tp->d_reclen = dp->d_reclen;
+#endif
+ memcpy(tp->d_name, dp->d_name, strlen(dp->d_name)+1);
+ }
+ closedir(dirp);
+ PM_UNLOCK(__pmLock_libpcp);
+ *namelist = names;
+
+ if (n && compare)
+ qsort(names, n, sizeof(names[0]),
+ (int(*)(const void *, const void *))compare);
+ return n;
+}
+
+/*
+ * Alphabetical sort for default use
+ */
+int
+alphasort(const_dirent **p, const_dirent **q)
+{
+ return strcmp((*p)->d_name, (*q)->d_name);
+}
+#endif /* HAVE_SCANDIR */
+
+#ifndef HAVE_POW
+/*
+ * For PCP we have not found a platform yet that needs this, but just
+ * in case, this implementation comes from
+ * http://www.netlib.org/fdlibm/e_pow.c
+ *
+ * ====================================================
+ * Copyright (C) 2003 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice
+ * is preserved.
+ * ====================================================
+ */
+
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#else
+#ifdef HAVE_SYS_ENDIAN_H
+#include <sys/endian.h>
+#else
+bozo!
+#endif
+#endif
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define __HI(x) *(1+(int*)&x)
+#define __LO(x) *(int*)&x
+#define __HIp(x) *(1+(int*)x)
+#define __LOp(x) *(int*)x
+#else
+#define __HI(x) *(int*)&x
+#define __LO(x) *(1+(int*)&x)
+#define __HIp(x) *(int*)x
+#define __LOp(x) *(1+(int*)x)
+#endif
+
+/* _pow(x,y) return x**y
+ *
+ * n
+ * Method: Let x = 2 * (1+f)
+ * 1. Compute and return log2(x) in two pieces:
+ * log2(x) = w1 + w2,
+ * where w1 has 53-24 = 29 bit trailing zeros.
+ * 2. Perform y*log2(x) = n+y' by simulating muti-precision
+ * arithmetic, where |y'|<=0.5.
+ * 3. Return x**y = 2**n*exp(y'*log2)
+ *
+ * Special cases:
+ * 1. (anything) ** 0 is 1
+ * 2. (anything) ** 1 is itself
+ * 3. (anything) ** NAN is NAN
+ * 4. NAN ** (anything except 0) is NAN
+ * 5. +-(|x| > 1) ** +INF is +INF
+ * 6. +-(|x| > 1) ** -INF is +0
+ * 7. +-(|x| < 1) ** +INF is +0
+ * 8. +-(|x| < 1) ** -INF is +INF
+ * 9. +-1 ** +-INF is NAN
+ * 10. +0 ** (+anything except 0, NAN) is +0
+ * 11. -0 ** (+anything except 0, NAN, odd integer) is +0
+ * 12. +0 ** (-anything except 0, NAN) is +INF
+ * 13. -0 ** (-anything except 0, NAN, odd integer) is +INF
+ * 14. -0 ** (odd integer) = -( +0 ** (odd integer) )
+ * 15. +INF ** (+anything except 0,NAN) is +INF
+ * 16. +INF ** (-anything except 0,NAN) is +0
+ * 17. -INF ** (anything) = -0 ** (-anything)
+ * 18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer)
+ * 19. (-anything except 0 and inf) ** (non-integer) is NAN
+ *
+ * Accuracy:
+ * pow(x,y) returns x**y nearly rounded. In particular
+ * pow(integer,integer)
+ * always returns the correct integer provided it is
+ * representable.
+ *
+ * Constants :
+ * The hexadecimal values are the intended ones for the following
+ * constants. The decimal values may be used, provided that the
+ * compiler will convert from decimal to binary accurately enough
+ * to produce the hexadecimal values shown.
+ */
+
+static const double
+bp[] = {1.0, 1.5,},
+dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */
+dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */
+zero = 0.0,
+one = 1.0,
+two = 2.0,
+two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */
+huge = 1.0e300,
+tiny = 1.0e-300,
+ /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */
+L1 = 5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */
+L2 = 4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */
+L3 = 3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */
+L4 = 2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */
+L5 = 2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */
+L6 = 2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */
+P1 = 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */
+P2 = -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */
+P3 = 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */
+P4 = -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */
+P5 = 4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */
+lg2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */
+lg2_h = 6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */
+lg2_l = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */
+ovt = 8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */
+cp = 9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */
+cp_h = 9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */
+cp_l = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/
+ivln2 = 1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */
+ivln2_h = 1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/
+ivln2_l = 1.92596299112661746887e-08; /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/
+
+double
+pow(double x, double y)
+{
+ double z,ax,z_h,z_l,p_h,p_l;
+ double y1,t1,t2,r,s,t,u,v,w;
+ int i,j,k,yisint,n;
+ int hx,hy,ix,iy;
+ unsigned lx,ly;
+
+ hx = __HI(x); lx = __LO(x);
+ hy = __HI(y); ly = __LO(y);
+ ix = hx&0x7fffffff; iy = hy&0x7fffffff;
+
+ /* y==zero: x**0 = 1 */
+ if((iy|ly)==0) return one;
+
+ /* +-NaN return x+y */
+ if(ix > 0x7ff00000 || ((ix==0x7ff00000)&&(lx!=0)) ||
+ iy > 0x7ff00000 || ((iy==0x7ff00000)&&(ly!=0)))
+ return x+y;
+
+ /* determine if y is an odd int when x < 0
+ * yisint = 0 ... y is not an integer
+ * yisint = 1 ... y is an odd int
+ * yisint = 2 ... y is an even int
+ */
+ yisint = 0;
+ if(hx<0) {
+ if(iy>=0x43400000) yisint = 2; /* even integer y */
+ else if(iy>=0x3ff00000) {
+ k = (iy>>20)-0x3ff; /* exponent */
+ if(k>20) {
+ j = ly>>(52-k);
+ if((j<<(52-k))==ly) yisint = 2-(j&1);
+ } else if(ly==0) {
+ j = iy>>(20-k);
+ if((j<<(20-k))==iy) yisint = 2-(j&1);
+ }
+ }
+ }
+
+ /* special value of y */
+ if(ly==0) {
+ if (iy==0x7ff00000) { /* y is +-inf */
+ if(((ix-0x3ff00000)|lx)==0)
+ return y - y; /* inf**+-1 is NaN */
+ else if (ix >= 0x3ff00000)/* (|x|>1)**+-inf = inf,0 */
+ return (hy>=0)? y: zero;
+ else /* (|x|<1)**-,+inf = inf,0 */
+ return (hy<0)?-y: zero;
+ }
+ if(iy==0x3ff00000) { /* y is +-1 */
+ if(hy<0) return one/x; else return x;
+ }
+ if(hy==0x40000000) return x*x; /* y is 2 */
+ if(hy==0x3fe00000) { /* y is 0.5 */
+ if(hx>=0) /* x >= +0 */
+ return sqrt(x);
+ }
+ }
+
+ ax = fabs(x);
+ /* special value of x */
+ if(lx==0) {
+ if(ix==0x7ff00000||ix==0||ix==0x3ff00000){
+ z = ax; /*x is +-0,+-inf,+-1*/
+ if(hy<0) z = one/z; /* z = (1/|x|) */
+ if(hx<0) {
+ if(((ix-0x3ff00000)|yisint)==0) {
+ z = (z-z)/(z-z); /* (-1)**non-int is NaN */
+ } else if(yisint==1)
+ z = -z; /* (x<0)**odd = -(|x|**odd) */
+ }
+ return z;
+ }
+ }
+
+ n = (hx>>31)+1;
+
+ /* (x<0)**(non-int) is NaN */
+ if((n|yisint)==0) return (x-x)/(x-x);
+
+ s = one; /* s (sign of result -ve**odd) = -1 else = 1 */
+ if((n|(yisint-1))==0) s = -one;/* (-ve)**(odd int) */
+
+ /* |y| is huge */
+ if(iy>0x41e00000) { /* if |y| > 2**31 */
+ if(iy>0x43f00000){ /* if |y| > 2**64, must o/uflow */
+ if(ix<=0x3fefffff) return (hy<0)? huge*huge:tiny*tiny;
+ if(ix>=0x3ff00000) return (hy>0)? huge*huge:tiny*tiny;
+ }
+ /* over/underflow if x is not close to one */
+ if(ix<0x3fefffff) return (hy<0)? s*huge*huge:s*tiny*tiny;
+ if(ix>0x3ff00000) return (hy>0)? s*huge*huge:s*tiny*tiny;
+ /* now |1-x| is tiny <= 2**-20, suffice to compute
+ log(x) by x-x^2/2+x^3/3-x^4/4 */
+ t = ax-one; /* t has 20 trailing zeros */
+ w = (t*t)*(0.5-t*(0.3333333333333333333333-t*0.25));
+ u = ivln2_h*t; /* ivln2_h has 21 sig. bits */
+ v = t*ivln2_l-w*ivln2;
+ t1 = u+v;
+ __LO(t1) = 0;
+ t2 = v-(t1-u);
+ } else {
+ double ss,s2,s_h,s_l,t_h,t_l;
+ n = 0;
+ /* take care subnormal number */
+ if(ix<0x00100000)
+ {ax *= two53; n -= 53; ix = __HI(ax); }
+ n += ((ix)>>20)-0x3ff;
+ j = ix&0x000fffff;
+ /* determine interval */
+ ix = j|0x3ff00000; /* normalize ix */
+ if(j<=0x3988E) k=0; /* |x|<sqrt(3/2) */
+ else if(j<0xBB67A) k=1; /* |x|<sqrt(3) */
+ else {k=0;n+=1;ix -= 0x00100000;}
+ __HI(ax) = ix;
+
+ /* compute ss = s_h+s_l = (x-1)/(x+1) or (x-1.5)/(x+1.5) */
+ u = ax-bp[k]; /* bp[0]=1.0, bp[1]=1.5 */
+ v = one/(ax+bp[k]);
+ ss = u*v;
+ s_h = ss;
+ __LO(s_h) = 0;
+ /* t_h=ax+bp[k] High */
+ t_h = zero;
+ __HI(t_h)=((ix>>1)|0x20000000)+0x00080000+(k<<18);
+ t_l = ax - (t_h-bp[k]);
+ s_l = v*((u-s_h*t_h)-s_h*t_l);
+ /* compute log(ax) */
+ s2 = ss*ss;
+ r = s2*s2*(L1+s2*(L2+s2*(L3+s2*(L4+s2*(L5+s2*L6)))));
+ r += s_l*(s_h+ss);
+ s2 = s_h*s_h;
+ t_h = 3.0+s2+r;
+ __LO(t_h) = 0;
+ t_l = r-((t_h-3.0)-s2);
+ /* u+v = ss*(1+...) */
+ u = s_h*t_h;
+ v = s_l*t_h+t_l*ss;
+ /* 2/(3log2)*(ss+...) */
+ p_h = u+v;
+ __LO(p_h) = 0;
+ p_l = v-(p_h-u);
+ z_h = cp_h*p_h; /* cp_h+cp_l = 2/(3*log2) */
+ z_l = cp_l*p_h+p_l*cp+dp_l[k];
+ /* log2(ax) = (ss+..)*2/(3*log2) = n + dp_h + z_h + z_l */
+ t = (double)n;
+ t1 = (((z_h+z_l)+dp_h[k])+t);
+ __LO(t1) = 0;
+ t2 = z_l-(((t1-t)-dp_h[k])-z_h);
+ }
+
+ /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */
+ y1 = y;
+ __LO(y1) = 0;
+ p_l = (y-y1)*t1+y*t2;
+ p_h = y1*t1;
+ z = p_l+p_h;
+ j = __HI(z);
+ i = __LO(z);
+ if (j>=0x40900000) { /* z >= 1024 */
+ if(((j-0x40900000)|i)!=0) /* if z > 1024 */
+ return s*huge*huge; /* overflow */
+ else {
+ if(p_l+ovt>z-p_h) return s*huge*huge; /* overflow */
+ }
+ } else if((j&0x7fffffff)>=0x4090cc00 ) { /* z <= -1075 */
+ if(((j-0xc090cc00)|i)!=0) /* z < -1075 */
+ return s*tiny*tiny; /* underflow */
+ else {
+ if(p_l<=z-p_h) return s*tiny*tiny; /* underflow */
+ }
+ }
+ /*
+ * compute 2**(p_h+p_l)
+ */
+ i = j&0x7fffffff;
+ k = (i>>20)-0x3ff;
+ n = 0;
+ if(i>0x3fe00000) { /* if |z| > 0.5, set n = [z+0.5] */
+ n = j+(0x00100000>>(k+1));
+ k = ((n&0x7fffffff)>>20)-0x3ff; /* new k for n */
+ t = zero;
+ __HI(t) = (n&~(0x000fffff>>k));
+ n = ((n&0x000fffff)|0x00100000)>>(20-k);
+ if(j<0) n = -n;
+ p_h -= t;
+ }
+ t = p_l+p_h;
+ __LO(t) = 0;
+ u = t*lg2_h;
+ v = (p_l-(t-p_h))*lg2+t*lg2_l;
+ z = u+v;
+ w = v-(z-u);
+ t = z*z;
+ t1 = z - t*(P1+t*(P2+t*(P3+t*(P4+t*P5))));
+ r = (z*t1)/(t1-two)-(w+z*w);
+ z = one-(r-z);
+ j = __HI(z);
+ j += (n<<20);
+ if((j>>20)<=0) z = scalbn(z,n); /* subnormal output */
+ else __HI(z) += (n<<20);
+ return s*z;
+}
+#endif /* HAVE_POW */
+
+#define PROCFS_ENTRY_SIZE 40 /* encompass any size of entry for pid */
+
+#if defined(IS_DARWIN) /* No procfs on Mac OS X */
+int
+__pmProcessExists(pid_t pid)
+{
+ struct kinfo_proc kp;
+ size_t len = sizeof(kp);
+ int mib[4];
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = pid;
+ if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1)
+ return 0;
+ return (len > 0);
+}
+#elif defined(IS_FREEBSD)
+int
+__pmProcessExists(pid_t pid)
+{
+ /*
+ * kill(.., 0) returns -1 if the process exists.
+ */
+ if (kill(pid, 0) == -1)
+ return 1;
+ else
+ return 0;
+}
+#elif defined(HAVE_PROCFS)
+#define PROCFS "/proc"
+#define PROCFS_PATH_SIZE (sizeof(PROCFS)+PROCFS_ENTRY_SIZE)
+int
+__pmProcessExists(pid_t pid)
+{
+ char proc_buf[PROCFS_PATH_SIZE];
+ snprintf(proc_buf, sizeof(proc_buf), "%s/%" FMT_PID, PROCFS, pid);
+ return (access(proc_buf, F_OK) == 0);
+}
+#elif !defined(IS_MINGW)
+!bozo!
+#endif
+
+#if defined(HAVE_KILL)
+int
+__pmProcessTerminate(pid_t pid, int force)
+{
+ return kill(pid, force ? SIGKILL : SIGTERM);
+}
+#elif !defined(IS_MINGW)
+!bozo!
+#endif
+
+#if defined(HAVE_SBRK)
+int
+__pmProcessDataSize(unsigned long *size)
+{
+ static void *base;
+
+ if (size && base)
+ *size = (sbrk(0) - base) / 1024;
+ else {
+ base = sbrk(0);
+ if (size)
+ *size = 0;
+ }
+ return 0;
+}
+#elif !defined(IS_MINGW)
+#warning "Platform does not define a process datasize interface?"
+int __pmProcessDataSize(unsigned long *) { return -1; }
+#endif
+
+#if !defined(IS_MINGW)
+int
+__pmProcessRunTimes(double *usr, double *sys)
+{
+ struct tms tms;
+ double ticks = (double)sysconf(_SC_CLK_TCK);
+
+ if (times(&tms) == (clock_t)-1) {
+ *usr = *sys = 0.0;
+ return -1;
+ }
+ *usr = (double)tms.tms_utime / ticks;
+ *sys = (double)tms.tms_stime / ticks;
+ return 0;
+}
+#endif
+
+#if !defined(IS_MINGW)
+pid_t
+__pmProcessCreate(char **argv, int *infd, int *outfd)
+{
+ int in[2];
+ int out[2];
+ pid_t pid;
+
+ if (pipe1(in) < 0)
+ return -oserror();
+ if (pipe1(out) < 0)
+ return -oserror();
+
+ pid = fork();
+ if (pid < 0) {
+ return -1;
+ }
+ else if (pid) {
+ /* parent */
+ close(in[0]);
+ close(out[1]);
+ *infd = out[0];
+ *outfd = in[1];
+ }
+ else {
+ /* child */
+ char errmsg[PM_MAXERRMSGLEN];
+ close(in[1]);
+ close(out[0]);
+ if (in[0] != 0) {
+ close(0);
+ dup2(in[0], 0);
+ close(in[0]);
+ }
+ if (out[1] != 1) {
+ close(1);
+ dup2(out[1], 1);
+ close(out[1]);
+ }
+ execvp(argv[0], argv);
+ fprintf(stderr, "execvp: %s\n", osstrerror_r(errmsg, sizeof(errmsg)));
+ exit(1);
+ }
+ return pid;
+}
+
+int
+__pmSetSignalHandler(int sig, __pmSignalHandler func)
+{
+ signal(sig, func);
+ return 0;
+}
+
+int
+__pmSetProgname(const char *program)
+{
+ char *p;
+
+ /* Trim command name of leading directory components */
+ if (program)
+ pmProgname = (char *)program;
+ for (p = pmProgname; pmProgname && *p; p++) {
+ if (*p == '/')
+ pmProgname = p+1;
+ }
+ return 0;
+}
+
+int
+__pmShutdown(void)
+{
+ int code = 0, sts;
+
+ if ((sts = __pmShutdownLocal()) < 0 && !code)
+ code = sts;
+ if ((sts = __pmShutdownCertificates()) < 0 && !code)
+ code = sts;
+ if ((sts = __pmShutdownSecureSockets()) < 0 && !code)
+ code = sts;
+ return code;
+}
+
+void *
+__pmMemoryMap(int fd, size_t sz, int writable)
+{
+ int mflags = writable ? (PROT_READ | PROT_WRITE) : PROT_READ;
+ void *addr = mmap(NULL, sz, mflags, MAP_SHARED, fd, 0);
+ if (addr == MAP_FAILED)
+ return NULL;
+ return addr;
+}
+
+void
+__pmMemoryUnmap(void *addr, size_t sz)
+{
+ munmap(addr, sz);
+}
+
+#if HAVE_TRACE_BACK_STACK
+#include <libexc.h>
+#define MAX_DEPTH 30 /* max callback procedure depth */
+#define MAX_SIZE 48 /* max function name length */
+
+void
+__pmDumpStack(FILE *f)
+{
+ __uint64_t call_addr[MAX_DEPTH];
+ char *call_fn[MAX_DEPTH];
+ char names[MAX_DEPTH][MAX_SIZE];
+ int res;
+ int i;
+
+ for (i = 0; i < MAX_DEPTH; i++)
+ call_fn[i] = names[i];
+ res = trace_back_stack(MAX_DEPTH, call_addr, call_fn, MAX_DEPTH, MAX_SIZE);
+ for (i = 1; i < res; i++) {
+#if defined(HAVE_64BIT_PTR)
+ fprintf(f, " 0x%016llx [%s]\n", call_addr[i], call_fn[i]);
+#else
+ fprintf(f, " 0x%08lx [%s]\n", (__uint32_t)call_addr[i], call_fn[i]);
+#endif
+ }
+}
+
+#elif HAVE_BACKTRACE
+#include <execinfo.h>
+#define MAX_DEPTH 30 /* max callback procedure depth */
+
+void
+__pmDumpStack(FILE *f)
+{
+ int nframe;
+ void *buf[MAX_DEPTH];
+ char **symbols;
+ int i;
+
+ nframe = backtrace(buf, MAX_DEPTH);
+ if (nframe < 1) {
+ fprintf(f, "backtrace -> %d frames?\n", nframe);
+ return;
+ }
+ symbols = backtrace_symbols(buf, nframe);
+ if (symbols == NULL) {
+ fprintf(f, "backtrace_symbols failed!\n");
+ return;
+ }
+ for (i = 1; i < nframe; i++)
+ fprintf(f, " " PRINTF_P_PFX "%p [%s]\n", buf[i], symbols[i]);
+}
+#else /* no known mechanism, provide a stub (called unconditionally) */
+void
+__pmDumpStack(FILE *f)
+{
+ fprintf(f, "[No backtrace support available]\n");
+}
+#endif /* HAVE_BACKTRACE */
+
+#endif /* !IS_MINGW */
diff --git a/src/libpcp/src/win32.c b/src/libpcp/src/win32.c
new file mode 100644
index 0000000..387b2f5
--- /dev/null
+++ b/src/libpcp/src/win32.c
@@ -0,0 +1,796 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * For the MinGW headers and library to work correctly, we need
+ * something newer than the default Windows 95 versions of the Win32
+ * APIs. 0x0500 is the magic sauce to select the Windows 2000 version
+ * of the Win32 APIs, which is the minimal version needed for PCP.
+ *
+ * WINVER needs to be set before any of the MinGW headers are processed
+ * and we include <windows.h> from pmapi.h via platform_defs.h.
+ *
+ * Thanks to "Earnie" on the mingw-users@lists.sourceforge.net mailing
+ * list for this tip.
+ */
+#define WINVER 0x0500
+
+#include "pmapi.h"
+#include "impl.h"
+#include <winbase.h>
+#include <psapi.h>
+
+#define FILETIME_1970 116444736000000000ull /* 1/1/1601-1/1/1970 */
+#define HECTONANOSEC_PER_SEC 10000000ull
+#define MILLISEC_PER_SEC 1000
+#define NANOSEC_PER_MILLISEC 1000000ull
+#define NANOSEC_BOUND (1000000000ull - 1)
+#define MAX_SIGNALS 3 /* HUP, USR1, TERM */
+
+static struct {
+ int signal;
+ HANDLE eventhandle;
+ HANDLE waithandle;
+ __pmSignalHandler callback;
+} signals[MAX_SIGNALS];
+
+VOID CALLBACK
+SignalCallback(PVOID param, BOOLEAN timerorwait)
+{
+ int index = (int)param;
+
+ if (index >= 0 && index < MAX_SIGNALS)
+ signals[index].callback(signals[index].signal);
+ else
+ fprintf(stderr, "SignalCallback: bad signal index (%d)\n", index);
+}
+
+static char *
+MapSignals(int sig, int *index)
+{
+ static char name[8];
+
+ switch (sig) {
+ case SIGHUP:
+ *index = 0;
+ strcpy(name, "SIGHUP");
+ break;
+ case SIGUSR1:
+ *index = 1;
+ strcpy(name, "SIGUSR1");
+ break;
+ case SIGTERM:
+ *index = 2;
+ strcpy(name, "SIGTERM");
+ break;
+ default:
+ return NULL;
+ }
+ return name;
+}
+
+int
+__pmSetSignalHandler(int sig, __pmSignalHandler func)
+{
+ int sts, index;
+ char *signame, evname[64];
+ HANDLE eventhdl, waithdl;
+
+ if ((signame = MapSignals(sig, &index)) < 0)
+ return index;
+
+ if (signals[index].callback) { /* remove old handler */
+ UnregisterWait(signals[index].waithandle);
+ CloseHandle(signals[index].eventhandle);
+ signals[index].callback = NULL;
+ signals[index].signal = -1;
+ }
+
+ if (func == SIG_IGN)
+ return 0;
+
+ sts = 0;
+ snprintf(evname, sizeof(evname), "PCP/%" FMT_PID "/%s", getpid(), signame);
+ if (!(eventhdl = CreateEvent(NULL, FALSE, FALSE, TEXT(evname)))) {
+ sts = GetLastError();
+ fprintf(stderr, "CreateEvent::%s failed (%d)\n", signame, sts);
+ }
+ else if (!RegisterWaitForSingleObject(&waithdl, eventhdl,
+ SignalCallback, (PVOID)index, INFINITE, 0)) {
+ sts = GetLastError();
+ fprintf(stderr, "RegisterWait::%s failed (%d)\n", signame, sts);
+ }
+ else {
+ signals[index].eventhandle = eventhdl;
+ signals[index].waithandle = waithdl;
+ signals[index].callback = func;
+ signals[index].signal = sig;
+ }
+ return sts;
+}
+
+static void
+sigterm_callback(int sig)
+{
+ exit(0); /* give atexit(3) handlers a look-in */
+}
+
+int
+__pmSetProcessIdentity(const char *username)
+{
+ (void)username;
+ return 0; /* Not Yet Implemented */
+}
+
+int
+__pmSetProgname(const char *program)
+{
+ int sts1, sts2;
+ char *p, *suffix = NULL;
+ WORD wVersionRequested = MAKEWORD(2, 2);
+ WSADATA wsaData;
+
+ /* Trim command name of leading directory components and ".exe" suffix */
+ if (program)
+ pmProgname = (char *)program;
+ for (p = pmProgname; pmProgname && *p; p++) {
+ if (*p == '\\' || *p == '/')
+ pmProgname = p + 1;
+ if (*p == '.')
+ suffix = p;
+ }
+ if (suffix && strcmp(suffix, ".exe") == 0)
+ *suffix = '\0';
+
+ /* Deal with all files in binary mode - no EOL futzing */
+ _fmode = O_BINARY;
+
+ /*
+ * If Windows networking is not setup, all networking calls fail;
+ * this even includes gethostname(2), if you can believe that. :[
+ */
+ sts1 = WSAStartup(wVersionRequested, &wsaData);
+
+ /*
+ * Here we are emulating POSIX signals using Event objects.
+ * For all processes we want a SIGTERM handler, which allows
+ * us an opportunity to cleanly shutdown: atexit(1) handlers
+ * get a look-in, IOW. Other signals (HUP/USR1) are handled
+ * in a similar way, but only by processes that need them.
+ */
+ sts2 = __pmSetSignalHandler(SIGTERM, sigterm_callback);
+
+ return sts1 | sts2;
+}
+
+void *
+__pmMemoryMap(int fd, size_t sz, int writable)
+{
+ void *addr = NULL;
+ int cflags = writable ? PAGE_READWRITE : PAGE_READONLY;
+
+ HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(fd),
+ NULL, cflags, 0, sz, NULL);
+ if (handle != NULL) {
+ int mflags = writable ? FILE_MAP_ALL_ACCESS : FILE_MAP_READ;
+ addr = MapViewOfFile(handle, mflags, 0, 0, sz);
+ CloseHandle(handle);
+ if (addr == MAP_FAILED)
+ return NULL;
+ }
+ return addr;
+}
+
+void
+__pmMemoryUnmap(void *addr, size_t sz)
+{
+ (void)sz;
+ UnmapViewOfFile(addr);
+}
+
+int
+__pmProcessExists(pid_t pid)
+{
+ HANDLE ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
+ if (ph == NULL)
+ return 0;
+ CloseHandle(ph);
+ return 1;
+}
+
+int
+__pmProcessTerminate(pid_t pid, int force)
+{
+ HANDLE ph = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ if (ph != NULL) {
+ TerminateProcess(ph, 0);
+ CloseHandle(ph);
+ return 0;
+ }
+ return -ESRCH;
+}
+
+pid_t
+__pmProcessCreate(char **argv, int *infd, int *outfd)
+{
+ HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr;
+ PROCESS_INFORMATION piProcInfo;
+ SECURITY_ATTRIBUTES saAttr;
+ STARTUPINFO siStartInfo;
+ LPTSTR cmdline = NULL;
+ char *command;
+ int i, sz = 0;
+
+ ZeroMemory(&saAttr, sizeof(SECURITY_ATTRIBUTES));
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE; /* pipe handles are inherited. */
+ saAttr.lpSecurityDescriptor = NULL;
+
+ /*
+ * Create a pipe for communication with the child process.
+ * Ensure that the read handle to the child process's pipe for
+ * STDOUT is not inherited.
+ */
+ if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
+ return -1;
+ SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0);
+
+ /*
+ * Create a pipe for the child process's STDIN.
+ * Ensure that the write handle to the child process's pipe for
+ * STDIN is not inherited.
+ */
+ if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
+ CloseHandle(hChildStdoutRd);
+ CloseHandle(hChildStdoutWr);
+ return -1;
+ }
+ SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0);
+
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+ siStartInfo.cb = sizeof(STARTUPINFO);
+ siStartInfo.hStdOutput = hChildStdoutWr;
+ siStartInfo.hStdInput = hChildStdinRd;
+ siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ /* Flatten the argv array for the Windows CreateProcess API */
+
+ for (command = argv[0], i = 0; command && *command; command = argv[++i]) {
+ int length = strlen(command);
+ cmdline = realloc(cmdline, sz + length + 1); /* 1space or 1null */
+ strcpy(&cmdline[sz], command);
+ cmdline[sz + length] = ' ';
+ sz += length + 1;
+ }
+ cmdline[sz - 1] = '\0';
+
+ if (0 == CreateProcess(NULL,
+ cmdline, /* command line */
+ NULL, /* process security attributes */
+ NULL, /* primary thread security attributes */
+ TRUE, /* handles are inherited */
+ 0, /* creation flags */
+ NULL, /* use parent's environment */
+ NULL, /* use parent's current directory */
+ &siStartInfo, /* STARTUPINFO pointer */
+ &piProcInfo)) /* receives PROCESS_INFORMATION */
+ {
+ CloseHandle(hChildStdinRd);
+ CloseHandle(hChildStdinWr);
+ CloseHandle(hChildStdoutRd);
+ CloseHandle(hChildStdoutWr);
+ return -1;
+ }
+ else {
+ CloseHandle(piProcInfo.hProcess);
+ CloseHandle(piProcInfo.hThread);
+ }
+
+ *infd = _open_osfhandle((intptr_t)hChildStdoutRd, _O_RDONLY);
+ *outfd = _open_osfhandle((intptr_t)hChildStdinWr, _O_WRONLY);
+ return piProcInfo.dwProcessId;
+}
+
+pid_t
+__pmProcessWait(pid_t pid, int nowait, int *code, int *signal)
+{
+ HANDLE ph;
+ DWORD status;
+
+ if (pid == (pid_t)-1 || pid == (pid_t)-2)
+ return -1;
+ if ((ph = OpenProcess(SYNCHRONIZE, FALSE, pid)) == NULL)
+ return -1;
+ if (WaitForSingleObject(ph, (DWORD)(-1L)) == WAIT_FAILED) {
+ CloseHandle(ph);
+ return -1;
+ }
+ if (GetExitCodeProcess(ph, &status)) {
+ CloseHandle(ph);
+ return -1;
+ }
+ if (code)
+ *code = status;
+ CloseHandle(ph);
+ *signal = -1;
+ return pid;
+}
+
+int
+__pmProcessDataSize(unsigned long *datasize)
+{
+ PROCESS_MEMORY_COUNTERS pmc;
+ HANDLE ph;
+ int sts = -1;
+
+ if (!datasize)
+ return 0;
+ *datasize = 0UL;
+ ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
+ if (ph == NULL)
+ return sts;
+ else if (GetProcessMemoryInfo(ph, &pmc, sizeof(pmc))) {
+ *datasize = pmc.WorkingSetSize / 1024;
+ sts = 0;
+ }
+ CloseHandle(ph);
+ return sts;
+}
+
+int
+__pmProcessRunTimes(double *usr, double *sys)
+{
+ ULARGE_INTEGER ul;
+ FILETIME times[4];
+ HANDLE ph;
+ int sts = -1;
+
+ *usr = *sys = 0.0;
+ ph = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
+ if (ph == NULL)
+ return sts;
+ else if (GetProcessTimes(ph, &times[0], &times[1], &times[2], &times[3])) {
+ ul.LowPart = times[2].dwLowDateTime;
+ ul.HighPart = times[2].dwHighDateTime;
+ *sys = ul.QuadPart / 10000000.0;
+ ul.LowPart = times[3].dwLowDateTime;
+ ul.HighPart = times[3].dwHighDateTime;
+ *usr = ul.QuadPart / 10000000.0;
+ sts = 0;
+ }
+ CloseHandle(ph);
+ return sts;
+}
+
+void
+__pmDumpStack(FILE *f)
+{
+ /* TODO: StackWalk64 API */
+}
+
+void
+__pmtimevalNow(struct timeval *tv)
+{
+ struct timespec ts;
+ union {
+ unsigned long long ns100; /*time since 1 Jan 1601 in 100ns units */
+ FILETIME ft;
+ } now;
+
+ GetSystemTimeAsFileTime(&now.ft);
+ now.ns100 -= FILETIME_1970;
+ ts.tv_sec = now.ns100 / HECTONANOSEC_PER_SEC;
+ ts.tv_nsec = (long)(now.ns100 % HECTONANOSEC_PER_SEC) * 100;
+ tv->tv_sec = ts.tv_sec;
+ tv->tv_usec = (ts.tv_nsec / 1000);
+}
+
+int
+nanosleep(const struct timespec *req, struct timespec *rem)
+{
+ DWORD milliseconds;
+
+ if (req->tv_sec < 0 || req->tv_nsec < 0 || req->tv_nsec > NANOSEC_BOUND) {
+ SetLastError(EINVAL);
+ return -1;
+ }
+ milliseconds = req->tv_sec * MILLISEC_PER_SEC
+ + req->tv_nsec / NANOSEC_PER_MILLISEC;
+ SleepEx(milliseconds, TRUE);
+ if (rem)
+ memset(rem, 0, sizeof(*rem));
+ return 0;
+}
+
+unsigned int
+sleep(unsigned int seconds)
+{
+ SleepEx(seconds * 1000, TRUE);
+ return 0;
+}
+
+void setlinebuf(FILE *stream)
+{
+ setvbuf(stream, NULL, _IONBF, 0); /* no line buffering in Win32 */
+}
+
+long int
+lrand48(void)
+{
+ return rand();
+}
+
+void
+srand48(long int seed)
+{
+ srand(seed);
+}
+
+char *
+index(const char *string, int marker)
+{
+ char *p;
+ for (p = (char *)string; *p != '\0'; p++)
+ if (*p == marker)
+ return p;
+ return NULL;
+}
+
+char *
+rindex(const char *string, int marker)
+{
+ char *p;
+ for (p = (char *)string; *p != '\0'; p++)
+ ;
+ if (p == string)
+ return NULL;
+ for (--p; p != string; p--)
+ if (*p == marker)
+ return p;
+ return NULL;
+}
+
+char *
+strcasestr(const char *string, const char *substr)
+{
+ int i, j;
+ int sublen = strlen(substr);
+ int length = strlen(string) - sublen + 1;
+
+ for (i = 0; i < length; i++) {
+ for (j = 0; j < sublen; j++)
+ if (toupper(string[i+j]) != toupper(substr[j]))
+ goto outerloop;
+ return (char *) substr + i;
+ outerloop:
+ continue;
+ }
+ return NULL;
+}
+
+int
+inet_pton(int family, const char *src, void *dest)
+{
+ struct sockaddr_storage ss = { 0 };
+ char src_copy[INET6_ADDRSTRLEN + 1];
+ int size;
+
+ strncpy(src_copy, src, sizeof(src_copy));
+ src_copy[sizeof(src_copy)-1] = '\0';
+ size = sizeof(ss);
+ if (WSAStringToAddress(src_copy, family, NULL, (struct sockaddr *)&ss, &size) != 0)
+ return 0;
+ switch(family) {
+ case AF_INET:
+ *(struct in_addr *)dest = ((struct sockaddr_in *)&ss)->sin_addr;
+ return 1;
+ case AF_INET6:
+ *(struct in6_addr *)dest = ((struct sockaddr_in6 *)&ss)->sin6_addr;
+ return 1;
+ }
+ return 0;
+}
+
+const char *
+inet_ntop(int family, const void *src, char *dest, socklen_t size)
+{
+ struct sockaddr_storage ss = { .ss_family = family };
+ unsigned long sz = size;
+
+ switch(family) {
+ case AF_INET:
+ ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src;
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src;
+ break;
+ default:
+ return NULL;
+ }
+ if (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dest, &sz) != 0)
+ return NULL;
+ return dest;
+}
+
+void *
+dlopen(const char *filename, int flag)
+{
+ return LoadLibrary(filename);
+}
+
+void *
+dlsym(void *handle, const char *symbol)
+{
+ return GetProcAddress(handle, symbol);
+}
+
+int
+dlclose(void *handle)
+{
+ return FreeLibrary(handle);
+}
+
+char *
+dlerror(void)
+{
+ return strerror(GetLastError());
+}
+
+static HANDLE eventlog;
+static char *eventlogPrefix;
+
+void
+openlog(const char *ident, int option, int facility)
+{
+ if (eventlog)
+ closelog();
+ eventlog = RegisterEventSource(NULL, "Application");
+ if (ident)
+ eventlogPrefix = strdup(ident);
+}
+
+void
+syslog(int priority, const char *format, ...)
+{
+ va_list arg;
+ LPCSTR msgptr;
+ char logmsg[2048];
+ char *p = logmsg;
+ int offset = 0;
+ DWORD eventlogPriority;
+
+ va_start(arg, format);
+
+ if (!eventlog)
+ openlog(NULL, 0, 0);
+
+ if (eventlogPrefix)
+ offset = snprintf(p, sizeof(logmsg), "%s: ", eventlogPrefix);
+
+ switch (priority) {
+ case LOG_EMERG:
+ case LOG_CRIT:
+ case LOG_ERR:
+ eventlogPriority = EVENTLOG_ERROR_TYPE;
+ break;
+ case LOG_WARNING:
+ case LOG_ALERT:
+ eventlogPriority = EVENTLOG_WARNING_TYPE;
+ break;
+ case LOG_NOTICE:
+ case LOG_DEBUG:
+ case LOG_INFO:
+ default:
+ eventlogPriority = EVENTLOG_INFORMATION_TYPE;
+ break;
+ }
+ msgptr = logmsg;
+ snprintf(p + offset, sizeof(logmsg) - offset, format, arg);
+ ReportEvent(eventlog, eventlogPriority, 0, 0, NULL, 1, 0, &msgptr, NULL);
+ va_end(arg);
+}
+
+void
+closelog(void)
+{
+ if (eventlog) {
+ DeregisterEventSource(eventlog);
+ if (eventlogPrefix)
+ free(eventlogPrefix);
+ eventlogPrefix = NULL;
+ }
+ eventlog = NULL;
+}
+
+const char *
+strerror_r(int errnum, char *buf, size_t buflen)
+{
+ /* strerror_s is missing from the MinGW string.h */
+ /* we need to wait for it until we can do this: */
+ /* return strerror_s(buf, buflen, errnum); */
+ return strerror(errnum);
+}
+
+/* Windows socket error codes - what a nightmare! */
+static const struct {
+ int err;
+ char *errmess;
+} wsatab[] = {
+/*10004*/ { WSAEINTR, "Interrupted function call" },
+/*10009*/ { WSAEBADF, "File handle is not valid" },
+/*10013*/ { WSAEACCES, "Permission denied" },
+/*10014*/ { WSAEFAULT, "Bad address" },
+/*10022*/ { WSAEINVAL, "Invalid argument" },
+/*10024*/ { WSAEMFILE, "Too many open files" },
+/*10035*/ { WSAEWOULDBLOCK, "Resource temporarily unavailable" },
+/*10036*/ { WSAEINPROGRESS, "Operation now in progress" },
+/*10037*/ { WSAEALREADY, "Operation already in progress" },
+/*10038*/ { WSAENOTSOCK, "Socket operation on nonsocket" },
+/*10039*/ { WSAEDESTADDRREQ, "Destination address required" },
+/*10040*/ { WSAEMSGSIZE, "Message too long" },
+/*10041*/ { WSAEPROTOTYPE, "Protocol wrong type for socket" },
+/*10042*/ { WSAENOPROTOOPT, "Bad protocol option" },
+/*10043*/ { WSAEPROTONOSUPPORT, "Protocol not supported" },
+/*10044*/ { WSAESOCKTNOSUPPORT, "Socket type not supported" },
+/*10045*/ { WSAEOPNOTSUPP, "Operation not supported" },
+/*10046*/ { WSAEPFNOSUPPORT, "Protocol family not supported" },
+/*10047*/ { WSAEAFNOSUPPORT, "Address family not supported by protocol family"},
+/*10048*/ { WSAEADDRINUSE, "Address already in use" },
+/*10049*/ { WSAEADDRNOTAVAIL, "Cannot assign requested address" },
+/*10050*/ { WSAENETDOWN, "Network is down" },
+/*10051*/ { WSAENETUNREACH, "Network is unreachable" },
+/*10052*/ { WSAENETRESET, "Network dropped connection on reset" },
+/*10053*/ { WSAECONNABORTED, "Software caused connection abort" },
+/*10054*/ { WSAECONNRESET, "Connection reset by peer" },
+/*10055*/ { WSAENOBUFS, "No buffer space available" },
+/*10056*/ { WSAEISCONN, "Socket is already connected" },
+/*10057*/ { WSAENOTCONN, "Socket is not connected" },
+/*10058*/ { WSAESHUTDOWN, "Cannot send after socket shutdown" },
+/*10059*/ { WSAETOOMANYREFS, "Too many references" },
+/*10060*/ { WSAETIMEDOUT, "Connection timed out" },
+/*10061*/ { WSAECONNREFUSED, "Connection refused" },
+/*10062*/ { WSAELOOP, "Cannot translate name" },
+/*10063*/ { WSAENAMETOOLONG, "Name too long" },
+/*10064*/ { WSAEHOSTDOWN, "Host is down" },
+/*10065*/ { WSAEHOSTUNREACH, "No route to host" },
+/*10066*/ { WSAENOTEMPTY, "Directory not empty" },
+/*10067*/ { WSAEPROCLIM, "Too many processes" },
+/*10070*/ { WSAESTALE, "Stale file handle reference" },
+/*10091*/ { WSASYSNOTREADY, "Network subsystem is unavailable" },
+/*10092*/ { WSAVERNOTSUPPORTED, "Winsock.dll version out of range" },
+/*10093*/ { WSANOTINITIALISED, "Successful WSAStartup not yet performed" },
+/*10101*/ { WSAEDISCON, "Graceful shutdown in progress" },
+/*10102*/ { WSAENOMORE, "No more results" },
+/*10103*/ { WSAECANCELLED, "Call has been canceled" },
+/*10104*/ { WSAEINVALIDPROCTABLE, "Procedure call table is invalid" },
+/*10105*/ { WSAEINVALIDPROVIDER, "Service provider is invalid" },
+/*10106*/ { WSAEPROVIDERFAILEDINIT, "Service provider failed to initialize" },
+/*10107*/ { WSASYSCALLFAILURE, "System call failure" },
+/*10108*/ { WSASERVICE_NOT_FOUND, "Service not found" },
+/*10109*/ { WSATYPE_NOT_FOUND, "Class type not found" },
+/*10110*/ { WSA_E_NO_MORE, "No more results" },
+/*10111*/ { WSA_E_CANCELLED, "Call was canceled" },
+/*10112*/ { WSAEREFUSED, "Database query was refused" },
+/*11001*/ { WSAHOST_NOT_FOUND, "Host not found" },
+/*11002*/ { WSATRY_AGAIN, "Nonauthoritative host not found" },
+/*11003*/ { WSANO_RECOVERY, "This is a nonrecoverable error" },
+/*11004*/ { WSANO_DATA, "Valid name, no data record of requested type" },
+ { 0,"" }
+};
+
+const char *
+wsastrerror(int code)
+{
+ int i;
+
+ for (i = 0; wsatab[i].err; i++)
+ if (wsatab[i].err == code)
+ return wsatab[i].errmess;
+ return NULL;
+}
+
+/*
+ * User and group account management using Security IDs (SIDs)
+ */
+int
+__pmValidUserID(__pmUserID sid)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmValidGroupID(__pmGroupID sid)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmEqualUserIDs(__pmUserID sid1, __pmUserID sid2)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmEqualGroupIDs(__pmGroupID sid1, __pmGroupID sid2)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+void
+__pmUserIDFromString(const char *username, __pmUserID *sid)
+{
+ /* NYI */
+}
+
+void
+__pmGroupIDFromString(const char *groupname, __pmGroupID *sid)
+{
+ /* NYI */
+}
+
+char *
+__pmUserIDToString(__pmUserID sid, char *buffer, size_t size)
+{
+ return NULL; /* NYI */
+}
+
+char *
+__pmGroupIDToString(__pmGroupID gid, char *buffer, size_t size)
+{
+ return NULL; /* NYI */
+}
+
+int
+__pmUsernameToID(const char *username, __pmUserID *uidp)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmGroupnameToID(const char *groupname, __pmGroupID *gidp)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+char *
+__pmGroupnameFromID(__pmGroupID gid, char *buf, size_t size)
+{
+ return NULL; /* NYI */
+}
+
+char *
+__pmUsernameFromID(__pmUserID uid, char *buf, size_t size)
+{
+ return NULL; /* NYI */
+}
+
+int
+__pmUsersGroupIDs(const char *username, __pmGroupID **groupids, unsigned int *ngroups)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmGroupsUserIDs(const char *groupname, __pmUserID **userids, unsigned int *nusers)
+{
+ return -ENOTSUP; /* NYI */
+}
+
+int
+__pmGetUserIdentity(const char *username, __pmUserID *uid, __pmGroupID *gid, int mode)
+{
+ return -ENOTSUP; /* NYI */
+}
diff --git a/src/libpcp_fault/README b/src/libpcp_fault/README
new file mode 100644
index 0000000..b6f3f18
--- /dev/null
+++ b/src/libpcp_fault/README
@@ -0,0 +1,58 @@
+libpcp_fault is a version of libpcp that has fault injection
+capabilities added and lock debug instrumentation.
+
+This library is NOT intended to be packaged or shipped,
+rather it is intended for use with the PCP QA suite.
+
+Normal builds do not descend into this directory.
+
+To build and use the library, cd into here, then
+
+ $ cd src
+ $ make
+ $ sudo make install
+ $ make clean
+
+To use the library in a test application,
+
+ #include <pcp/fault.h>
+
+ ...
+
+Then compile the code with -DPM_FAULT_INJECTION=1 and link with
+-lpcp_fault instead of -lpcp
+
+To use with an executable that is already linked, set $LD_PRELOAD
+in the environment to the full pathname of the installed
+libpcp_fault, e.g.
+
+ $ LD_PRELOAD=/usr/lib/libpcp_fault.so pminfo ...
+
+The mechanisms for defining fault injection points and controlling
+the triggering of fault injection is described in the brief "man"
+page found in the this directory. Review with
+
+ $ man ./pmfault.3
+
+Lock debugging is activated with -D options on the command line for
+standard PCP applications:
+
+ -D lock enables lock debug tracing, by default all lock and
+ unlock requests are traced with the source:lineno of
+ the caller and some symbolic reference to the lock
+ type ... lock recursion is reported by a [count=N]
+ note if the lock count is not 0 for a lock() and 1
+ for an unlock()
+
+ -D lock,appl0
+ restrict the tracing to the global libpcp lock
+
+ -D lock,appl1
+ restrict tracing to the per-context locks
+
+ -D lock,appl2
+ restrict tracing to the ipc channel lock for each
+ context
+
+One or more appl? flags may be specified. -D lock is equivalent to
+-D lock,appl0,appl1,appl2
diff --git a/src/libpcp_fault/pmfault.3 b/src/libpcp_fault/pmfault.3
new file mode 100644
index 0000000..5eef089
--- /dev/null
+++ b/src/libpcp_fault/pmfault.3
@@ -0,0 +1,353 @@
+'\"macro stdmacro
+.\"
+.\" Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+.\"
+.\" This program is free software; you can redistribute it and/or modify it
+.\" under the terms of the GNU General Public License as published by the
+.\" Free Software Foundation; either version 2 of the License, or (at your
+.\" option) any later version.
+.\"
+.\" This program is distributed in the hope that it will be useful, but
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+.\" or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+.\" for more details.
+.\"
+.\"
+.TH PMFAULT 3 "" "Performance Co-Pilot"
+.SH NAME
+\f3__pmFaultInject\f1,
+\f3PM_FAULT_POINT\f1,
+\f3PM_FAULT_CHECK\f1,
+\f3__pmFaultSummary\f1 \- Fault Injection Infrastracture for QA
+.SH "C SYNOPSIS"
+.ft 3
+#include <pcp/pmapi.h>
+.br
+#include <pcp/impl.h>
+.br
+#include <pcp/fault.h>
+.sp
+void __pmFaultInject(const char *\fIident\fP, int \fIclass\fP);
+.br
+void __pmFaultSummary(FILE *\fIf\fP);
+.sp
+PM_FAULT_POINT(\fIident\fP, \fIclass\fP);
+.br
+PM_FAULT_CHECK(\fIclass\fP);
+.sp
+cc \-DPM_FAULT_INJECTION=1 ... \-lpcp_fault
+.ft 1
+.SH DESCRIPTION
+.PP
+As part of the coverage-driven changes to QA in PCP 3.6, it became
+apparent that we needed someway to exercise the ``uncommon''
+code paths associated with error detection and recovery.
+.PP
+The facilities described below provide
+a basic fault injection infrastructure (for
+.I libpcp
+only at this stage, alhough the mechanism is far more general and could
+easily be extended).
+.PP
+A special build is required to create
+.I libpcp_fault
+and the associated
+.I <pcp/fault.h>
+header file.
+Once this has been done, new QA applications may be built with
+.B \-DPM_FAULT_INJECTION=1
+and/or existing applications can be exercised in presence of
+fault injection by forcing
+.I libpcp_fault
+to be used in preference to
+.I libpcp
+as described below.
+.PP
+In the code to be tested,
+.B __pmFaultInject
+defines a fault point at which a fault of type
+.I class
+may be injected.
+.I ident
+is a string to uniquely identify the fault point across all
+of the PCP source code, so something
+like "libpcp/" __FILE__ ":<number>" works just fine.
+The
+.I ident
+string also determines if a fault will be injected at run-time or not
+\- refer to the
+.B "RUN-TIME CONTROL"
+section below.
+.I class
+selects a failure type, using one of the following defined
+values (this list may well grow over time):
+.TP
+.B PM_FAULT_ALLOC
+Will cause the
+.B next
+call to
+.BR malloc (3),
+.BR realloc (3)
+or
+.BR strdup (3)
+to fail, returning NULL and setting
+.I errno
+to
+.BR ENOMEM .
+We could extend the coverage to all of the malloc-related routines,
+but these three are sufficient to cover the vast majority of the uses
+within
+.IR libpcp .
+.TP
+.B PM_FAULT_PMAPI
+Will cause the
+.B next
+call to a PMAPI routine
+to fail by returning the (new) PCP error code
+.BR PM_ERR_FAULT .
+At the
+this stage, only
+.BR __pmRegisterAnon (3)
+has been instrumented as a proof of concept for this part of the
+facility.
+.PP
+To allow fault injection to co-exist within the production source
+code,
+.B PM_FAULT_POINT
+is a macro that emits no code by default, but when
+.B PM_FAULT_INJECTION
+is defined this becomes a call to
+.BR __pmFaultInject .
+Throughout
+.I libpcp
+we use
+.B PM_FAULT_POINT
+and
+.B not
+.B __pmFaultInject
+so that both
+.I libpcp
+and
+.I libpcp_fault
+can be built from the same source code.
+.PP
+Similarly, the macro
+.B PM_FAULT_CHECK
+emits no code unless
+.B PM_FAULT_INJECTION
+is defined, in which case if a fault of type
+.I class
+has been armed with
+.B __pmFaultInject
+then, the enclosing
+routine will trigger the associated error behaviour.
+For the moment, this only works for the
+.I class
+of
+.B PM_FAULT_PMAPI
+where the enclosing routine will return immediately with the value
+.B PM_ERR_FAULT
+\- this assumes the enclosing routine is of type
+.B "int foo(...)"
+like all of the PMAPI routines.
+.PP
+A summary of fault points seen and faults injected is produced
+on stdio stream
+.I f
+by
+.BR __pmFaultSummary .
+.PP
+Additional tracing (via
+.B \-Dfault
+and
+.BR DBG_TRACE_FAULT )
+and a new
+PMAPI error code (\c
+.BR PM_ERR_FAULT )
+are also defined, although
+these will only ever be seen or used in
+.IR libpcp_fault .
+If
+.B DBG_TRACE_FAULT
+is set the first time
+.B __pmFaultInject
+is called, then
+.B __pmFaultSummary
+will be called automatically to report on
+.I stderr
+when the application exits (via
+.BR atexit (3)).
+.PP
+Fault injection cannot be nested. Each call to
+.B __pmFaultInject
+clears any previous fault injection that has been armed, but not yet
+executed.
+.PP
+The fault injection infrastructure is
+.B not
+thread-safe and should only be used with applications that are
+known to be single-threaded.
+
+.SH RUN-TIME CONTROL
+.PP
+By default, no fault injection is enabled at run-time, even when
+.B __pmFaultInject
+is called.
+.PP
+Faults are selectively enabled using a control file, identified by the environment
+variable
+.BR $PM_FAULT_CONTROL ;
+if this is not set, no faults are enabled.
+.PP
+The control file (if it exists) is read the first time
+.B __pmFaultInject
+is called, and
+contains lines of the form:
+.ti +8n
+.I ident
+.I op
+.I number
+.br
+that define fault injection guards.
+.PP
+.I ident
+is a fault point string (as defined by a call to
+.BR __pmFaultInject ,
+or more usually the
+.B PM_FAULT_POINT
+macro). So one needs access to the
+.I libpcp
+source code to determine the available
+.I ident
+strings and their semantics.
+.PP
+.I op
+is one of the C-style operators
+.BR >= ,
+.BR > ,
+.BR == ,
+.BR < ,
+.BR <= ,
+.B !=
+or
+.BR %
+and
+.I number
+is an unsigned integer.
+.I op
+.I number
+is optional and the default is
+.BR ">0"
+.PP
+The semantics of the fault injection guards are that each time
+.B __pmFaultInject
+is called for a particular
+.IR ident ,
+a trip count is incremented (the first
+trip is 1); if the C-style expression
+.I tripcount
+.I op
+.I number
+has the
+value 1 (so
+.B true
+for most
+.IR op s,
+or the remainder equals 1 for the
+.B %
+.IR op ),
+then
+a fault of the
+.I class
+defined for the fault point associated with
+.I ident
+will be armed, and executed as soon as possible.
+.PP
+Within the control file, blank lines are ignored and lines
+beginning with # are treated as comments.
+.PP
+For an existing application linked with
+.I libpcp
+fault injection may still be used by forcing
+.I libpcp_fault
+to be used in the place of
+.IR libpcp .
+The following example shows how this might be done.
+.ft CW
+.nf
+$ export PM_FAULT_CONTROL=/tmp/control
+$ cat $PM_FAULT_CONTROL
+# ok for 2 trips, then inject errors
+libpcp/events.c:1 >2
+
+$ export LD_PRELOAD=/usr/lib/libpcp_fault.so
+$ pmevent -Dfault -s 3 sample.event.records
+host: localhost
+samples: 3
+interval: 1.00 sec
+sample.event.records[fungus]: 0 event records
+__pmFaultInject(libpcp/events.c:1) ntrip=1 SKIP
+sample.event.records[bogus]: 2 event records
+ 10:46:12.413 --- event record [0] flags 0x1 (point) ---
+ sample.event.param_string "fetch #0"
+ 10:46:12.413 --- event record [1] flags 0x1 (point) ---
+ sample.event.param_string "bingo!"
+__pmFaultInject(libpcp/events.c:1) ntrip=2 SKIP
+sample.event.records[fungus]: 1 event records
+ 10:46:03.416 --- event record [0] flags 0x1 (point) ---
+__pmFaultInject(libpcp/events.c:1) ntrip=3 INJECT
+sample.event.records[bogus]: pmUnpackEventRecords: Cannot allocate memory
+__pmFaultInject(libpcp/events.c:1) ntrip=4 INJECT
+sample.event.records[fungus]: pmUnpackEventRecords: Cannot allocate memory
+__pmFaultInject(libpcp/events.c:1) ntrip=5 INJECT
+sample.event.records[bogus]: pmUnpackEventRecords: Cannot allocate memory
+=== Fault Injection Summary Report ===
+libpcp/events.c:1: guard trip>2, 5 trips, 3 faults
+.fi
+.ft
+
+.SH EXAMPLES
+Refer to the PCP and PCP QA source code.
+.PP
+.I src/libpcp/src/derive.c
+uses
+.BR PM_FAULT_CHECK .
+.PP
+.I src/libpcp/src/err.c
+and
+.I src/libpcp/src/events.c
+use
+.BR PM_FAULT_POINT .
+.PP
+.I src/libpcp/src/fault.c
+contains all of the the underlying implementation.
+.PP
+.I src/libpcp_fault
+contains the recipe and Makefile for creating and
+installing
+.I libpcp_fault
+and
+.IR <pcp/fault.h> .
+.PP
+.I QA/477
+and
+.I QA/478
+show examples of control file use.
+
+.SH ENVIRONMENT
+.TP
+.B PM_FAULT_CONTROL
+Full path to the fault injection control file.
+.TP
+.B LD_PRELOAD
+Force
+.I libpcp_fault
+to be used in preference to
+.IR libpcp .
+
+.SH SEE ALSO
+.BR PMAPI (3)
+.SH DIAGNOSTICS
+.PP
+Some non-recoverable errors are reported on
+.IR stderr .
diff --git a/src/libpcp_fault/src/GNUmakefile b/src/libpcp_fault/src/GNUmakefile
new file mode 100644
index 0000000..0876060
--- /dev/null
+++ b/src/libpcp_fault/src/GNUmakefile
@@ -0,0 +1,160 @@
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+LCFLAGS += -DPM_FAULT_INJECTION=1 -DPM_MULTI_THREAD_DEBUG=1
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = connect.c context.c desc.c err.c fetch.c freeresult.c \
+ help.c instance.c p_desc.c p_error.c p_fetch.c p_instance.c \
+ p_profile.c p_result.c p_text.c p_pmns.c p_creds.c p_auth.c \
+ pdu.c pdubuf.c pmns.c profile.c store.c units.c util.c ipc.c \
+ sortinst.c logmeta.c logportmap.c logutil.c tz.c interp.c \
+ checksum.c rtime.c tv.c spec.c fetchlocal.c optfetch.c AF.c \
+ stuffvalue.c endian.c config.c auxconnect.c auxserver.c discovery.c \
+ p_lcontrol.c p_lrequest.c p_lstatus.c logconnect.c logcontrol.c \
+ connectlocal.c derive.c derive_fetch.c events.c lock.c hash.c \
+ fault.c access.c probe.c
+HFILES = derive.h internal.h avahi.h probe.h
+VERSION_SCRIPT = exports
+
+LSRCFILES = check-statics $(VERSION_SCRIPT)
+
+LLDLIBS += $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS) -lpcp_pmda
+
+ifeq "$(ENABLE_SECURE)" "true"
+LLDLIBS += $(LIB_FOR_SSL) $(LIB_FOR_NSS) $(LIB_FOR_NSPR) $(LIB_FOR_SASL)
+LCFLAGS += $(NSSCFLAGS) $(NSPRCFLAGS) $(SASLCFLAGS)
+CFILES += secureserver.c secureconnect.c
+else
+LSRCFILES += secureserver.c secureconnect.c
+endif
+
+ifeq "$(ENABLE_AVAHI)" "true"
+LLDLIBS += $(LIB_FOR_AVAHI)
+LCFLAGS += $(AVAHICFLAGS)
+CFILES += avahi.c
+else
+LSRCFILES += avahi.c
+endif
+
+ifneq "$(TARGET_OS)" "mingw"
+CFILES += accounts.c
+LSRCFILES += win32.c
+else
+CFILES += win32.c
+LSRCFILES += accounts.c
+LLDLIBS += -lpsapi
+endif
+
+ifeq "$(TARGET_OS)" "solaris"
+# enables standards compliant thread-safe interfaces (accounts.c)
+LCFLAGS += -D_POSIX_PTHREAD_SEMANTICS
+endif
+
+ifeq "$(LIB_FOR_BASENAME)" "-lpcp"
+# don't need to be linked to myself in this case!
+LIB_FOR_BASENAME =
+endif
+
+LLDLIBS += $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+
+LCFLAGS += -DLIBPCP_INTERNAL '-DEXEC_SUFFIX="$(EXECSUFFIX)"' \
+ '-DDSO_SUFFIX="$(DSOSUFFIX)"'
+
+DSOVERSION = 3
+STATICLIBTARGET = libpcp_fault.a
+LIBTARGET = libpcp_fault.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp_fault.$(DSOSUFFIX) libpcp_fault.$(DSOSUFFIX).2
+
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+SYMTARGET = libpcp_fault.$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp_fault.$(DSOVERSION).$(DSOSUFFIX)
+SYMTARGET = libpcp_fault.$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+STATICLIBTARGET =
+LIBTARGET = libpcp_fault.$(DSOSUFFIX)
+SYMTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LDIRT += $(SYMTARGET) $(LIBTARGET) $(STATICLIBTARGET) $(VERSION_SCRIPT) $(HFILES) $(CFILES)
+
+base default : $(HFILES) $(CFILES) $(VERSION_SCRIPT) $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+
+libpcp.so: $(SYMTARGET)
+ ln -s $(SYMTARGET) libpcp.so
+
+ifneq "$(SYMTARGET)" ""
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $@
+endif
+
+include $(BUILDRULES)
+
+*.o: internal.h
+derive.o derive_fetch.o: derive.h
+util.o: $(TOPDIR)/src/include/pcp/pmdbg.h
+fault.o: $(TOPDIR)/src/include/pcp/fault.h
+
+$(OBJECTS): $(TOPDIR)/src/include/pcp/pmapi.h \
+ $(TOPDIR)/src/include/pcp/impl.h \
+ $(TOPDIR)/src/include/pcp/platform_defs.h
+
+ifeq "$(TARGET_OS)" "mingw"
+kernel_pmda_dso = windows
+else
+kernel_pmda_dso = $(TARGET_OS)
+endif
+
+install : default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ for tt in $(SYMTARGET); do \
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \
+ done
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+ $(INSTALL) -m 644 $(TOPDIR)/src/include/pcp/fault.h $(PCP_INC_DIR)/fault.h
+
+$(HFILES) $(CFILES):
+ ln -s ../../libpcp/src/$@ .
+
+$(VERSION_SCRIPT): ../../libpcp/src/$(VERSION_SCRIPT) mk.exports
+ ./mk.exports
+
+default_pcp : default
+
+install_pcp : install
+
+$(TOPDIR)/src/pmns/stdpmid:
+ cd $(@D); $(MAKE) $(@F)
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp_fault/src/mk.exports b/src/libpcp_fault/src/mk.exports
new file mode 100755
index 0000000..d732539
--- /dev/null
+++ b/src/libpcp_fault/src/mk.exports
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Make exports for libpcp_fault from the exports file for libpcp
+#
+
+rm -f exports
+sed <../../libpcp/src/exports >exports \
+ -e '/ local: \*;/i\
+/* added for libpcp_fault */\
+ __pmFault_malloc;\
+ __pmFault_realloc;\
+ __pmFault_strdup;\
+
+'
diff --git a/src/libpcp_gui/GNUmakefile b/src/libpcp_gui/GNUmakefile
new file mode 100644
index 0000000..830d2c9
--- /dev/null
+++ b/src/libpcp_gui/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2006 Aconex. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_gui/src/GNUmakefile b/src/libpcp_gui/src/GNUmakefile
new file mode 100644
index 0000000..91589f7
--- /dev/null
+++ b/src/libpcp_gui/src/GNUmakefile
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2006 Aconex. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = timeclient.c timestate.c record.c
+LLDLIBS = -lpcp
+
+STATICLIBTARGET = libpcp_gui.a
+
+DSOVERSION = 2
+LIBTARGET = libpcp_gui.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp_gui.$(DSOSUFFIX) libpcp_gui.$(DSOSUFFIX).1
+
+VERSION_SCRIPT = exports
+
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+SYMTARGET = libpcp_gui.$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp_gui.$(DSOVERSION).$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+LIBTARGET = libpcp_gui.$(DSOSUFFIX)
+STATICLIBTARGET =
+SYMTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LDIRT = $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+LSRCFILES = $(VERSION_SCRIPT)
+
+default: $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+
+ifneq ($(SYMTARGET),)
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $@
+endif
+
+include $(BUILDRULES)
+
+install: default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ for tt in $(SYMTARGET); do \
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \
+ done
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp_gui/src/exports b/src/libpcp_gui/src/exports
new file mode 100644
index 0000000..c7d2eef
--- /dev/null
+++ b/src/libpcp_gui/src/exports
@@ -0,0 +1,19 @@
+PCP_GUI_2.0 {
+ global:
+ pmRecordSetup;
+ pmRecordControl;
+ pmRecordAddHost;
+
+ pmTimeConnect;
+ pmTimeDisconnect;
+ pmTimeRecv;
+ pmTimeSendAck;
+ pmTimeShowDialog;
+ pmTimeStateAck;
+ pmTimeStateBounds;
+ pmTimeStateMode;
+ pmTimeStateSetup;
+ pmTimeStateVector;
+
+ local: *;
+};
diff --git a/src/libpcp_gui/src/record.c b/src/libpcp_gui/src/record.c
new file mode 100644
index 0000000..3a388e8
--- /dev/null
+++ b/src/libpcp_gui/src/record.c
@@ -0,0 +1,758 @@
+/*
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#include "pmapi.h"
+#include "pmafm.h"
+#include "impl.h"
+#include <sys/stat.h>
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+/*
+ * some extended state, make sure these values are different to
+ * the PM_REC_* macros in <pmapi.h>
+ */
+#define PM_REC_BEGIN 81
+#define PM_REC_HOST 82
+
+static int state = PM_REC_OFF; /* where you are up to ... */
+static char *dir; /* directory containing Archive Folio files */
+static char *base; /* unique basename */
+static FILE *f_folio; /* Archive Folio goes here */
+static FILE *f_replay; /* current replay config file */
+static int _replay; /* can replay? */
+static char *_folio; /* remember the folio name */
+static char *_creator; /* remember the creator name */
+static int _seendefault; /* seen default host? */
+
+typedef struct _record {
+ struct _record *next;
+ int state;
+ char *host; /* host name */
+ int isdefault; /* is this the default host? */
+ pmRecordHost public; /* exposed to the caller */
+ char *base; /* archive base */
+ char *logfile; /* for -l ... not to be confused */
+ /* with public.logfile which is the */
+ /* full pathname */
+ char *config; /* for -c */
+ int argc;
+ char **argv;
+} record_t;
+
+static record_t *record;
+static int n_record;
+#ifndef IS_MINGW /* not yet ported */
+static int n_alive;
+#endif
+static char tbuf[MAXPATHLEN]; /* used for mktemp(), messages, ... */
+
+/*
+ * initialize, and return stdio stream for writing replay config
+ * (if any)
+ */
+FILE *
+pmRecordSetup(const char *folio, const char *creator, int replay)
+{
+ char *p;
+ char c;
+ time_t now;
+ int sts;
+ int fd = -1;
+ static char host[MAXHOSTNAMELEN];
+ char foliopath[MAXPATHLEN];
+ char *temp = NULL; /* for unlink() */
+ record_t *rp;
+ mode_t cur_umask;
+
+ if (state != PM_REC_OFF) {
+ /* already begun w/out end */
+ setoserror(EINVAL);
+ return NULL;
+ }
+
+ if (access(folio, F_OK) == 0) {
+ setoserror(EEXIST);
+ return NULL;
+ }
+
+ if (_folio != NULL)
+ free(_folio);
+ if ((_folio = strdup(folio)) == NULL)
+ return NULL;
+
+ if (_creator != NULL)
+ free(_creator);
+ if ((_creator = strdup(creator)) == NULL)
+ return NULL;
+
+ if ((f_folio = fopen(folio, "w")) == NULL)
+ return NULL;
+
+ dir = NULL;
+ base = NULL;
+
+ /*
+ * have folio file created ... get unique base string
+ */
+ tbuf[0] = '\0';
+ strcpy(foliopath, folio);
+ if ((p = strrchr(foliopath, '/')) != NULL) {
+ /* folio name contains a slash */
+ p++;
+ c = *p;
+ *p = '\0';
+ if (strcmp(foliopath, "./") != 0) {
+ if (dir != NULL)
+ free(dir);
+ if ((dir = strdup(foliopath)) == NULL)
+ goto failed;
+ strcpy(tbuf, dir);
+ strcat(tbuf, "/");
+ }
+ *p = c;
+ }
+ strcat(tbuf, "XXXXXX");
+#if HAVE_MKSTEMP
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ if ((fd = mkstemp(tbuf)) < 0) {
+ umask(cur_umask);
+ goto failed;
+ }
+ umask(cur_umask);
+#else
+ if (mktemp(tbuf) == NULL)
+ goto failed;
+
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ if ((fd = open(tbuf, O_CREAT | O_EXCL | O_RDWR, 0600)) < 0) {
+ umask(cur_umask);
+ goto failed;
+ }
+ umask(cur_umask);
+#endif
+ /*
+ * file named tbuf is never used, it is the basename for the real
+ * files we create. remember it so we can cleanup.
+ */
+ temp = strdup(tbuf);
+
+ if (dir == NULL)
+ p = tbuf;
+ else
+ p = strrchr(tbuf, '/') + 1;
+
+ if ((base = strdup(p)) == NULL)
+ goto failed;
+
+ /*
+ * folio preamble ...
+ */
+ fprintf(f_folio, "PCPFolio\nVersion: 1\n");
+ fprintf(f_folio, "# use pmafm(1) to process this PCP Archive Folio\n#\n");
+ time(&now);
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ host[MAXHOSTNAMELEN-1] = '\0';
+ fprintf(f_folio, "Created: on %s at %s", host, ctime(&now));
+ fprintf(f_folio, "Creator: %s", creator);
+ if (replay)
+ fprintf(f_folio, " %s", base);
+ fprintf(f_folio, "\n# Host Basename\n#\n");
+
+ _replay = replay;
+ if (_replay) {
+ f_replay = fdopen(fd, "w");
+ if (f_replay == NULL)
+ goto failed;
+ }
+ else
+ f_replay = fopen("/dev/null", "r");
+
+ n_record = 0;
+ for (rp = record; rp != NULL; rp = rp->next) {
+ rp->state = PM_REC_BEGIN;
+ rp->isdefault = 0;
+ if (rp->host != NULL) {
+ free(rp->host);
+ rp->host = NULL;
+ }
+ if (rp->base != NULL) {
+ free(rp->base);
+ rp->base = NULL;
+ }
+ if (rp->logfile != NULL) {
+ free(rp->logfile);
+ rp->logfile = NULL;
+ }
+ if (rp->config != NULL) {
+ free(rp->config);
+ rp->config = NULL;
+ }
+ if (rp->argv != NULL) {
+ free(rp->argv);
+ rp->argv = NULL;
+ }
+ rp->argc = 0;
+ if (rp->public.f_config != NULL) {
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ }
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ if (rp->public.logfile != NULL) {
+ free(rp->public.logfile);
+ rp->public.logfile = NULL;
+ }
+ rp->public.pid = (pid_t)0;
+ rp->public.status = -1;
+ }
+
+ state = PM_REC_BEGIN;
+ _seendefault = 0;
+
+ if (temp) {
+ unlink(temp);
+ free(temp);
+ }
+ return f_replay;
+
+failed:
+ sts = oserror();
+ if (dir != NULL)
+ free(dir);
+ if (base != NULL)
+ free(base);
+ unlink(folio);
+ fclose(f_folio);
+ f_folio = NULL;
+ if (fd >= 0)
+ close(fd);
+ setoserror(sts);
+ if (temp) {
+ unlink(temp);
+ free(temp);
+ }
+ return NULL;
+}
+
+/*
+ * need to log another host in this folio ... must come here
+ * at least once, but may be more than once before the recording
+ * commences
+ */
+int
+pmRecordAddHost(const char *host, int isdefault, pmRecordHost **rhp)
+{
+ char *p;
+ int c;
+ int sts;
+ record_t *rp;
+
+ *rhp = NULL; /* in case of errors */
+
+ if (state != PM_REC_BEGIN && state != PM_REC_HOST)
+ /* botched order of calls ... */
+ return -EINVAL;
+
+ if (isdefault && _seendefault)
+ /* only one default allowed per session! */
+ return -EINVAL;
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state == PM_REC_BEGIN)
+ break;
+ }
+ if (rp == NULL) {
+ /* need another one */
+ if ((rp = (record_t *)malloc(sizeof(record_t))) == NULL)
+ return -oserror();
+ rp->next = record;
+ record = rp;
+ rp->isdefault = 0;
+ rp->host = NULL;
+ rp->base = NULL;
+ rp->logfile = NULL;
+ rp->config = NULL;
+ rp->argv = NULL;
+ rp->argc = 0;
+ rp->public.f_config = NULL;
+ rp->public.fd_ipc = -1;
+ rp->public.logfile = NULL;
+ rp->public.pid = (pid_t)0;
+ rp->public.status = -1;
+ }
+
+ rp->isdefault = isdefault;
+
+ if (dir != NULL)
+ strcpy(tbuf, dir);
+ else
+ tbuf[0] = '\0';
+ strcat(tbuf, base);
+ p = &tbuf[strlen(tbuf)];
+ strcat(tbuf, ".");
+ strcat(tbuf, host);
+ strcat(tbuf, ".config");
+ c = '\0';
+ if (access(tbuf, F_OK) == 0) {
+ p[0] = 'a';
+ p[1] = '.';
+ p[2] = '\0';
+ strcat(p, host);
+ strcat(p, ".config");
+ while (p[0] <= 'z') {
+ if (access(tbuf, F_OK) != 0) {
+ c = p[0];
+ break;
+ }
+ p[0]++;
+ }
+ if (c == '\0') {
+ setoserror(EEXIST);
+ goto failed;
+ }
+ }
+
+ if ((rp->host = strdup(host)) == NULL)
+ goto failed;
+
+ if ((rp->public.f_config = fopen(tbuf, "w")) == NULL)
+ goto failed;
+
+ if ((rp->base = malloc(strlen(base)+1+strlen(host)+3)) == NULL)
+ goto failed;
+ strcpy(rp->base, base);
+ p = &rp->base[strlen(rp->base)];
+ if (c != '\0') {
+ *p++ = c;
+ }
+ *p++ = '.';
+ *p = '\0';
+ strcat(p, host);
+
+ if ((rp->logfile = malloc(strlen(rp->base)+5)) == NULL)
+ goto failed;
+ strcpy(rp->logfile, rp->base);
+ strcat(rp->logfile, ".log");
+
+ if ((rp->config = malloc(strlen(rp->base)+8)) == NULL)
+ goto failed;
+ strcpy(rp->config, rp->base);
+ strcat(rp->config, ".config");
+
+ /* construct full pathname */
+ rp->public.logfile = malloc(MAXPATHLEN);
+ if (rp->public.logfile != NULL) {
+ int sep = __pmPathSeparator();
+ if (dir != NULL && __pmAbsolutePath(dir))
+ strcpy(rp->public.logfile, dir);
+ else {
+ if (getcwd(rp->public.logfile, MAXPATHLEN) == NULL)
+ goto failed;
+ }
+
+ sts = strlen(rp->public.logfile);
+ if (rp->public.logfile[sts - 1] != sep) {
+ rp->public.logfile[sts] = sep;
+ rp->public.logfile[sts+1] = '\0';
+ }
+ strcat(rp->public.logfile, rp->logfile);
+ }
+ else {
+ /* malloc failure ... */
+ goto failed;
+ }
+
+ n_record++;
+ state = rp->state = PM_REC_HOST;
+ *rhp = &rp->public;
+
+ return 0;
+
+failed:
+ sts = -oserror();
+ if (rp->public.f_config != NULL) {
+ unlink(tbuf);
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ }
+ if (rp->host != NULL)
+ free(rp->host);
+ if (rp->base != NULL)
+ free(rp->base);
+ if (rp->logfile != NULL)
+ free(rp->logfile);
+ if (rp->config != NULL)
+ free(rp->config);
+ *rhp = NULL;
+ return sts;
+}
+
+#ifndef IS_MINGW /* not yet ported */
+/*
+ * simple control protocol between here and pmlogger
+ * - only write from app to pmlogger
+ * - no ack
+ * - commands are
+ * V<number>\n - version
+ * F<folio>\n - folio name
+ * P<name>\n - launcher's name
+ * R[<msg>]\n - launcher knows how to replay
+ * D[<msg>]\n - detach from launcher
+ * Q[<msg>]\n - quit pmlogger
+ * ?[<msg>]\n - display session status
+ */
+static int
+xmit_to_logger(int fd, char tag, const char *msg)
+{
+ int sts;
+#ifdef HAVE_SIGPIPE
+ SIG_PF user_onpipe;
+#endif
+
+ if (fd < 0)
+ return PM_ERR_IPC;
+
+#ifdef HAVE_SIGPIPE
+ user_onpipe = signal(SIGPIPE, SIG_IGN);
+#endif
+ sts = (int)write(fd, &tag, 1);
+ if (sts != 1)
+ goto fail;
+
+ if (msg != NULL) {
+ int len = (int)strlen(msg);
+ sts = (int)write(fd, msg, len);
+ if (sts != len)
+ goto fail;
+ }
+
+ sts = (int)write(fd, "\n", 1);
+ if (sts != 1)
+ goto fail;
+
+#ifdef HAVE_SIGPIPE
+ signal(SIGPIPE, user_onpipe);
+#endif
+ return 0;
+
+fail:
+ if (oserror() == EPIPE)
+ sts = PM_ERR_IPC;
+ else
+ sts = -oserror();
+#ifdef HAVE_SIGPIPE
+ signal(SIGPIPE, user_onpipe);
+#endif
+ return sts;
+}
+#endif
+
+int
+pmRecordControl(pmRecordHost *rhp, int request, const char *msg)
+{
+#ifndef IS_MINGW /* not yet ported */
+ pid_t pid;
+ record_t *rp;
+ int sts;
+ int ok;
+ int cmd;
+ int mypipe[2];
+ static int maxseenfd = -1;
+
+ /*
+ * harvest old and smelly pmlogger instances
+ */
+ while ((pid = waitpid(-1, &sts, WNOHANG)) > 0) {
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (pid == rp->public.pid) {
+ rp->public.status = sts;
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ break;
+ }
+ }
+ }
+
+ sts = 0;
+
+ switch (request) {
+
+ case PM_REC_ON:
+ if (state != PM_REC_HOST || rhp != NULL) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST)
+ continue;
+ if (rp->isdefault) {
+ fprintf(f_folio, "%-15s %-23s %s\n", "Archive:", rp->host, rp->base);
+ break;
+ }
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST || rp->isdefault)
+ continue;
+ fprintf(f_folio, "%-15s %-23s %s\n", "Archive:", rp->host, rp->base);
+ }
+
+ fflush(stderr);
+ fflush(stdout);
+ if (_replay)
+ fflush(f_replay);
+ fflush(f_folio);
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->state != PM_REC_HOST)
+ continue;
+ fclose(rp->public.f_config);
+ rp->public.f_config = NULL;
+ if (pipe1(mypipe) < 0) {
+ sts = -oserror();
+ break;
+ }
+ if (mypipe[1] > maxseenfd)
+ maxseenfd = mypipe[1];
+ if (mypipe[0] > maxseenfd)
+ maxseenfd = mypipe[0];
+
+ if ((rp->public.pid = fork()) == 0) {
+ /* pmlogger */
+ char loggerpath[MAXPATHLEN];
+ char fdnum[4];
+ int fd;
+ int i;
+
+ close(mypipe[1]);
+ snprintf(fdnum, sizeof(fdnum), "%d", mypipe[0]);
+ if (dir != NULL) {
+ /* trim trailing separator */
+ dir[strlen(dir)-1] = '\0';
+ if (chdir(dir) < 0) {
+ /* not good! */
+ exit(1);
+ }
+ }
+ /*
+ * leave stdin, tie stdout and stderr together, leave
+ * the ipc fd and close all other fds
+ */
+ dup2(2, 1); /* stdout -> stderr */
+ for (fd = 3; fd <= maxseenfd; fd++) {
+ if (fd != mypipe[0])
+ close(fd);
+ }
+ /*
+ * have:
+ * argv[0] ... argv[argc-1] from PM_REC_SETARG
+ * want:
+ * argv[0] "pmlogger"
+ * argv[1] ... argv[argc] from PM_REC_SETARG
+ * argv[argc+1], argv[argc+2] "-x" fdnum
+ * argv[argc+3], argv[argc+4] "-h" host
+ * argv[argc+5], argv[argc+6] "-l" log
+ * argv[argc+7], argv[argc+8] "-c" config
+ * argv[argc+9] basename
+ * argv[argc+10] NULL
+ */
+ rp->argv = (char **)realloc(rp->argv, (rp->argc+11)*sizeof(rp->argv[0]));
+ if (rp->argv == NULL) {
+ __pmNoMem("pmRecordControl: argv[]", (rp->argc+11)*sizeof(rp->argv[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (i = rp->argc; i > 0; i--)
+ rp->argv[i] = rp->argv[i-1];
+ rp->argv[0] = "pmlogger";
+ i = rp->argc+1;
+ rp->argv[i++] = "-x";
+ rp->argv[i++] = fdnum;
+ rp->argv[i++] = "-h";
+ rp->argv[i++] = rp->host;
+ rp->argv[i++] = "-l";
+ rp->argv[i++] = rp->logfile;
+ rp->argv[i++] = "-c";
+ rp->argv[i++] = rp->config;
+ rp->argv[i++] = rp->base;
+ rp->argv[i++] = NULL;
+#if DESPERATE
+fprintf(stderr, "Launching pmlogger:");
+for (i = 0; i < rp->argc+11; i++) fprintf(stderr, " %s", rp->argv[i]);
+fputc('\n', stderr);
+#endif
+ snprintf(loggerpath, sizeof(loggerpath), "%s%cpmlogger",
+ pmGetConfig("PCP_BINADM_DIR"), __pmPathSeparator());
+ execv(loggerpath, rp->argv);
+
+ /* this is really bad! */
+ exit(1);
+ }
+ else if (rp->public.pid < (pid_t)0) {
+ sts = -oserror();
+ break;
+ }
+ else {
+ /* the application launching pmlogger */
+ close(mypipe[0]);
+ rp->public.fd_ipc = mypipe[1];
+ /* send the protocol version */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'V', "0");
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* send the folio name */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'F', _folio);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* send the my name */
+ ok = xmit_to_logger(rp->public.fd_ipc, 'P', _creator);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ /* do we know how to replay? */
+ if (_replay) {
+ ok = xmit_to_logger(rp->public.fd_ipc, 'R', NULL);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ continue;
+ }
+ }
+ }
+ rp->state = PM_REC_ON;
+ }
+
+ if (sts < 0) {
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rp->public.fd_ipc >= 0) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ }
+ }
+ else {
+ if (_replay) {
+ fclose(f_replay);
+ f_replay = NULL;
+ }
+ fclose(f_folio);
+ f_folio = NULL;
+ state = PM_REC_ON;
+ n_alive = n_record;
+ }
+ break;
+
+ case PM_REC_STATUS:
+ cmd = '?';
+ goto broadcast;
+
+ case PM_REC_OFF:
+ cmd = 'Q';
+ goto broadcast;
+
+ case PM_REC_DETACH:
+ cmd = 'D';
+
+broadcast:
+ if (state != PM_REC_ON) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rhp != NULL && rhp != &rp->public)
+ continue;
+ if (rp->state != PM_REC_ON) {
+ if (rhp != NULL)
+ /* explicit pmlogger, should be "on" */
+ sts = -EINVAL;
+ continue;
+ }
+ if (rp->public.fd_ipc >= 0) {
+ ok = xmit_to_logger(rp->public.fd_ipc, cmd, msg);
+ if (ok < 0) {
+ /* remember last failure */
+ sts = ok;
+ rp->public.fd_ipc = -1;
+ }
+ }
+ else
+ sts = PM_ERR_IPC;
+
+ if (cmd != '?') {
+ n_alive--;
+ rp->state = PM_REC_OFF;
+ if (rp->public.fd_ipc != -1) {
+ close(rp->public.fd_ipc);
+ rp->public.fd_ipc = -1;
+ }
+ }
+ }
+
+ if (n_alive <= 0)
+ state = PM_REC_OFF;
+
+ break;
+
+ case PM_REC_SETARG:
+ if (state != PM_REC_HOST) {
+ sts = -EINVAL;
+ break;
+ }
+
+ for (rp = record; rp != NULL; rp = rp->next) {
+ if (rhp != NULL && rhp != &rp->public)
+ continue;
+ rp->argv = (char **)realloc(rp->argv, (rp->argc+1)*sizeof(rp->argv[0]));
+ if (rp->argv == NULL) {
+ sts = -oserror();
+ rp->argc = 0;
+ }
+ else {
+ rp->argv[rp->argc] = strdup(msg);
+ if (rp->argv[rp->argc] == NULL)
+ sts = -oserror();
+ else
+ rp->argc++;
+ }
+ }
+ break;
+
+ default:
+ sts = -EINVAL;
+ break;
+ }
+
+ return sts;
+#else
+ return -EINVAL;
+#endif
+}
diff --git a/src/libpcp_gui/src/timeclient.c b/src/libpcp_gui/src/timeclient.c
new file mode 100644
index 0000000..1046e3f
--- /dev/null
+++ b/src/libpcp_gui/src/timeclient.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2006 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "pmtime.h"
+
+static int
+pmServerExec(int fd, int livemode)
+{
+ char portname[32];
+ int port, in, out;
+ char *argv[] = { "pmtime", NULL, NULL };
+
+ if (livemode)
+ argv[1] = "-h"; /* -h for live hosts */
+ else
+ argv[1] = "-a"; /* -a for archives */
+
+ if (__pmProcessCreate(argv, &in, &out) < (pid_t)0) {
+ __pmCloseSocket(fd);
+ return -1;
+ }
+
+ if (read(in, &portname, sizeof(portname)) < 0)
+ port = -1;
+ else if (sscanf(portname, "port=%d", &port) != 1) {
+ setoserror(EPROTO);
+ port = -1;
+ }
+ close(in);
+ close(out);
+ if (port == -1)
+ __pmCloseSocket(fd);
+ return port;
+}
+
+static int
+pmConnectHandshake(int fd, int port, pmTime *pkt)
+{
+ __pmSockAddr *myaddr;
+ char buffer[4096];
+ pmTime *ack;
+ int sts;
+
+ /*
+ * Connect to pmtime - pmtime guaranteed started by now, due to the
+ * port number read(2) earlier, or -p option (so no race there).
+ */
+ if ((myaddr = __pmSockAddrAlloc()) == NULL) {
+ setoserror(ENOMEM);
+ goto error;
+ }
+
+ __pmSockAddrInit(myaddr, AF_INET, INADDR_LOOPBACK, port);
+ if ((sts = __pmConnect(fd, (void *)myaddr, __pmSockAddrSize())) < 0) {
+ setoserror(neterror());
+ goto error;
+ }
+ __pmSockAddrFree(myaddr);
+ myaddr = NULL;
+
+ /*
+ * Write the packet, then wait for an ACK.
+ */
+ sts = __pmSend(fd, (const void *)pkt, pkt->length, 0);
+ if (sts < 0) {
+ setoserror(neterror());
+ goto error;
+ } else if (sts != pkt->length) {
+ setoserror(EMSGSIZE);
+ goto error;
+ }
+ ack = (pmTime *)buffer;
+ sts = __pmRecv(fd, buffer, sizeof(buffer), 0);
+ if (sts < 0) {
+ setoserror(neterror());
+ goto error;
+ } else if (sts != ack->length) {
+ setoserror(EMSGSIZE);
+ goto error;
+ } else if (ack->command != PM_TCTL_ACK) {
+ setoserror(EPROTO);
+ goto error;
+ } else if (ack->source != pkt->source) {
+ setoserror(ENOSYS);
+ goto error;
+ }
+ return 0;
+
+error:
+ if (myaddr)
+ __pmSockAddrFree(myaddr);
+ __pmCloseSocket(fd);
+ return -1;
+}
+
+int
+pmTimeConnect(int port, pmTime *pkt)
+{
+ int fd;
+
+ if ((fd = __pmCreateSocket()) < 0)
+ return -1;
+ if (port < 0) {
+ if ((port = pmServerExec(fd, pkt->source != PM_SOURCE_ARCHIVE)) < 0)
+ return -2;
+ if (pmConnectHandshake(fd, port, pkt) < 0)
+ return -3;
+ } else { /* attempt to connect to the given port (once) */
+ if (pmConnectHandshake(fd, port, pkt) < 0)
+ return -4;
+ }
+ return fd;
+}
+
+int
+pmTimeDisconnect(int fd)
+{
+ if (fd < 0) {
+ setoserror(EINVAL);
+ return -1;
+ }
+ __pmCloseSocket(fd);
+ return 0;
+}
+
+int
+pmTimeSendAck(int fd, struct timeval *tv)
+{
+ pmTime data;
+ int sts;
+
+ memset(&data, 0, sizeof(data));
+ data.magic = PMTIME_MAGIC;
+ data.length = sizeof(data);
+ data.command = PM_TCTL_ACK;
+ data.position = *tv;
+ sts = __pmSend(fd, (const void *)&data, sizeof(data), 0);
+ if (sts < 0)
+ setoserror(neterror());
+ return sts;
+}
+
+int
+pmTimeShowDialog(int fd, int show)
+{
+ pmTime data;
+ int sts;
+
+ memset(&data, 0, sizeof(data));
+ data.magic = PMTIME_MAGIC;
+ data.length = sizeof(data);
+ data.command = show ? PM_TCTL_GUISHOW : PM_TCTL_GUIHIDE;
+ sts = __pmSend(fd, (const void *)&data, sizeof(data), 0);
+ if (sts >= 0 && sts != sizeof(data)) {
+ setoserror(EMSGSIZE);
+ sts = -1;
+ } else if (sts < 0)
+ setoserror(neterror());
+ return sts;
+}
+
+int
+pmTimeRecv(int fd, pmTime **datap)
+{
+ pmTime *k = *datap;
+ int sts, remains;
+
+ memset(k, 0, sizeof(pmTime));
+ sts = __pmRecv(fd, (void *)k, sizeof(pmTime), 0);
+ if (sts >= 0 && sts != sizeof(pmTime)) {
+ setoserror(EMSGSIZE);
+ sts = -1;
+ } else if (sts < 0) {
+ setoserror(neterror());
+ } else if (k->length > sizeof(pmTime)) { /* double dipping */
+ remains = k->length - sizeof(pmTime);
+ *datap = k = realloc(k, k->length);
+ sts = __pmRecv(fd, (char *)k + sizeof(pmTime), remains, 0);
+ if (sts >= 0 && sts != remains) {
+ setoserror(E2BIG);
+ sts = -1;
+ } else if (sts < 0) {
+ setoserror(neterror());
+ }
+ }
+ if (sts < 0)
+ return sts;
+ return k->command;
+}
diff --git a/src/libpcp_gui/src/timestate.c b/src/libpcp_gui/src/timestate.c
new file mode 100644
index 0000000..7314183
--- /dev/null
+++ b/src/libpcp_gui/src/timestate.c
@@ -0,0 +1,307 @@
+/*
+ * Time control functions for pmval
+ *
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmtime.h"
+#include "pmapi.h"
+#include "impl.h"
+
+static enum {
+ START = -1,
+ STANDBY = 0,
+ FORW = 1,
+ BACK = 2,
+ MOVING = 3,
+ RESTART = 4,
+ ENDLOG = 5,
+} state;
+
+/*
+ * a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b
+ */
+static int
+__pmtimevalCmp(struct timeval *a, struct timeval *b)
+{
+ int res = (int)(a->tv_sec - b->tv_sec);
+
+ if (res == 0)
+ res = (int)(a->tv_usec - b->tv_usec);
+
+ return res;
+}
+
+static void timeControlExited()
+{
+ fprintf(stderr, "\n%s: Time Control dialog exited, goodbye.\n", pmProgname);
+}
+
+static void timeControlRewind()
+{
+ printf("\n[Time Control] Rewind/Reverse ...\n");
+}
+
+static void timeControlPosition(struct timeval position)
+{
+ printf("\n[Time Control] Repositioned in archive ...\n");
+}
+
+static void timeControlInterval(struct timeval delta)
+{
+ printf("new interval: %1.2f sec\n", __pmtimevalToReal(&delta));
+}
+
+static void timeControlResume()
+{
+ printf("\n[Time Control] Resume ...\n");
+}
+
+static void timeControlBounds()
+{
+ printf("\n[Time Control] End of Archive ...\n");
+}
+
+static void timeControlStepped(struct timeval delta)
+{
+}
+
+static void timeControlNewZone(char *timezone, char *label)
+{
+ int sts = pmNewZone(timezone);
+
+ if (sts < 0)
+ fprintf(stderr, "%s: Warning: cannot set timezone to \"%s\": %s\n",
+ pmProgname, timezone, pmErrStr(sts));
+ else
+ printf("new timezone: %s (%s)\n", timezone, label);
+}
+
+/*
+ * Get Extended Time Base interval and Units from a timeval
+ * in order to set archive mode.
+ */
+void
+pmTimeStateMode(int mode, struct timeval delta, struct timeval *position)
+{
+ const int SECS_IN_24_DAYS = 2073600;
+ int step, sts;
+
+ if (delta.tv_sec > SECS_IN_24_DAYS) {
+ step = delta.tv_sec;
+ mode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ step = delta.tv_sec * 1e3 + delta.tv_usec / 1e3;
+ mode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ if ((sts = pmSetMode(mode, position, step)) < 0) {
+ fprintf(stderr, "%s: pmSetMode: %s\n", pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+}
+
+pmTime *
+pmTimeStateSetup(
+ pmTimeControls *timecontrols, int ctxt, int port,
+ struct timeval delta, struct timeval position,
+ struct timeval first, struct timeval last, char *tz, char *tz_label)
+{
+ pmTime *pmtime = malloc(sizeof(pmTime));
+ int fd, sts, tzlen;
+
+ pmtime->magic = PMTIME_MAGIC;
+ pmtime->length = sizeof(pmTime);
+ pmtime->command = PM_TCTL_SET;
+ pmtime->delta = delta;
+
+ if (ctxt == PM_CONTEXT_ARCHIVE) {
+ pmtime->source = PM_SOURCE_ARCHIVE;
+ pmtime->position = position;
+ pmtime->start = first;
+ pmtime->end = last;
+ } else {
+ pmtime->source = PM_SOURCE_HOST;
+ __pmtimevalNow(&pmtime->position);
+ }
+ if (tz == NULL) {
+ char tzbuf[PM_TZ_MAXLEN];
+ tz = __pmTimezone_r(tzbuf, sizeof(tzbuf));
+ if (ctxt == PM_CONTEXT_ARCHIVE) {
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, tz, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ tzlen = strlen(tz) + 1;
+ if (tz_label == NULL)
+ tz_label = "localhost";
+ pmtime->length += tzlen + strlen(tz_label) + 1;
+ pmtime = realloc(pmtime, pmtime->length);
+ if (!pmtime) {
+ fprintf(stderr, "%s: realloc: %s\n", pmProgname, osstrerror());
+ exit(EXIT_FAILURE);
+ }
+ strcpy(pmtime->data, tz);
+ strcpy(pmtime->data + tzlen, tz_label);
+ if ((fd = pmTimeConnect(port, pmtime)) < 0) {
+ fprintf(stderr, "%s: pmTimeConnect: %s\n", pmProgname, pmErrStr(fd));
+ exit(EXIT_FAILURE);
+ }
+
+ pmtime->length = sizeof(pmTime); /* reduce size to header only */
+ pmtime = realloc(pmtime, pmtime->length); /* cannot fail! */
+
+ /* setup default vectors */
+ timecontrols->resume = timeControlResume;
+ timecontrols->exited = timeControlExited;
+ timecontrols->rewind = timeControlRewind;
+ timecontrols->boundary = timeControlBounds;
+ timecontrols->position = timeControlPosition;
+ timecontrols->interval = timeControlInterval;
+ timecontrols->stepped = timeControlStepped;
+ timecontrols->newzone = timeControlNewZone;
+ timecontrols->context = ctxt;
+ timecontrols->showgui = 1;
+ timecontrols->delta = delta;
+ timecontrols->padding = 0;
+ timecontrols->fd = fd;
+ state = START;
+
+ return pmtime;
+}
+
+void
+pmTimeStateBounds(pmTimeControls *control, pmTime *pmtime)
+{
+ pmTimeStateAck(control, pmtime);
+ if (state != ENDLOG)
+ control->boundary();
+ state = ENDLOG;
+}
+
+void
+pmTimeStateAck(pmTimeControls *control, pmTime *pmtime)
+{
+ int sts = pmTimeSendAck(control->fd, &pmtime->position);
+
+ if (sts < 0) {
+ if (sts == -EPIPE)
+ control->exited();
+ else
+ fprintf(stderr, "\n%s: pmTimeSendAck: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+pmTimeStateVector(pmTimeControls *control, pmTime *pmtime)
+{
+ int cmd, fetch = 0;
+
+ if (control->showgui) {
+ pmTimeShowDialog(control->fd, 1);
+ control->showgui = 0;
+ }
+
+ do {
+ cmd = pmTimeRecv(control->fd, &pmtime);
+ if (cmd < 0) {
+ control->exited();
+ exit(EXIT_FAILURE);
+ }
+
+ switch (pmtime->command) {
+ case PM_TCTL_SET:
+ if (state == ENDLOG)
+ state = STANDBY;
+ else if (state == FORW)
+ state = RESTART;
+ break;
+
+ case PM_TCTL_STEP:
+ if (pmtime->state == PM_STATE_BACKWARD) {
+ if (state != BACK) {
+ control->rewind();
+ state = BACK;
+ }
+ }
+ else if (state != FORW && state != ENDLOG) {
+ if (control->context == PM_CONTEXT_ARCHIVE) {
+ if (state == STANDBY)
+ control->resume();
+ else if (state != START)
+ control->position(pmtime->position);
+ }
+ else if (state != START)
+ control->resume();
+ if (state != START &&
+ __pmtimevalCmp(&pmtime->delta, &control->delta) != 0)
+ control->interval(pmtime->delta);
+
+ if (control->context == PM_CONTEXT_ARCHIVE)
+ pmTimeStateMode(PM_MODE_INTERP,
+ pmtime->delta, &pmtime->position);
+ control->delta = pmtime->delta;
+ control->stepped(pmtime->delta);
+ state = FORW;
+ }
+
+ if (state == BACK || state == ENDLOG) {
+ /*
+ * for EOL and reverse travel, no fetch, so ACK here
+ */
+ pmTimeStateAck(control, pmtime);
+ break;
+ }
+ fetch = 1;
+ break;
+
+ case PM_TCTL_TZ:
+ control->newzone(pmtime->data,
+ pmtime->data + strlen(pmtime->data) + 1);
+ break;
+
+ case PM_TCTL_VCRMODE:
+ case PM_TCTL_VCRMODE_DRAG:
+ /* something has changed ... suppress reporting */
+ if (pmtime->command == PM_TCTL_VCRMODE_DRAG)
+ state = MOVING;
+ else if (pmtime->state == PM_STATE_STOP)
+ state = STANDBY;
+ else
+ state = RESTART;
+ break;
+
+ /*
+ * safely and silently ignore these
+ */
+ case PM_TCTL_GUISHOW:
+ case PM_TCTL_GUIHIDE:
+ case PM_TCTL_BOUNDS:
+ case PM_TCTL_ACK:
+ break;
+
+ default:
+ if (pmDebug & DBG_TRACE_TIMECONTROL)
+ fprintf(stderr, "pmTimeRecv: cmd %x?\n", cmd);
+ break;
+ }
+ } while (!fetch);
+
+ return 0;
+}
diff --git a/src/libpcp_http/GNUmakefile b/src/libpcp_http/GNUmakefile
new file mode 100644
index 0000000..552118e
--- /dev/null
+++ b/src/libpcp_http/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_http/src/GNUmakefile b/src/libpcp_http/src/GNUmakefile
new file mode 100644
index 0000000..e09dd62
--- /dev/null
+++ b/src/libpcp_http/src/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+STATICLIBTARGET = libpcp_http.a
+LCFLAGS = -DVERSION=\"1.1.0\"
+CFILES = http_error_codes.c http_fetcher.c
+HFILES = http_error_codes.h http_fetcher.h
+LSRCFILES = README
+
+base default : $(STATICLIBTARGET)
+
+include $(BUILDRULES)
+
+install : default
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_http/src/README b/src/libpcp_http/src/README
new file mode 100644
index 0000000..fa4cd6c
--- /dev/null
+++ b/src/libpcp_http/src/README
@@ -0,0 +1,78 @@
+HTTP Fetcher
+Lyle Hanson (lhanson@users.sourceforge.net) (C) 2001, 2003, 2004
+http://http-fetcher.sourceforge.net
+===============================================================================
+
+
+ABOUT
+=====
+HTTP Fetcher is a small library that downloads files via HTTP. I developed
+it for use within another project, and because I didn't find any pre-existing
+software that did exactly what I wanted without hassle. Hopefully you'll find
+it useful and avoid writing similar code yourself.
+
+It supports the GET method. Anything further would involve more than
+fetching, now wouldn't it? If you need more than GET, there are other
+libaries out ther that would be better suited to your needs (try http-tiny,
+find it at freshmeat.net).
+
+HTTP Fetcher is meant to be small, fast, and flexible at what it does.
+It's very robust, in my opinion. It's easy to use; using one function,
+it can download any kind of file via HTTP. It also offers further
+sophistication, allowing you control over what (if any) User-Agent or
+Referrer you wish to show to the web server. Which is neat stuff, depending
+on your use (testing and stealth/deception are two that come to mind).
+
+
+DEPENDENCIES
+============
+HTTP Fetcher should run on most unices that support BSD-type sockets and have
+a network connection. Developed and tested on x86 machines running recent
+versions of RedHat.
+
+
+INSTALLATION
+============
+Read the INSTALL file for info on building and installing the library
+
+
+USAGE
+=====
+Read the manpages in the docs directory, see the 'testfetch' example included,
+check out 'fetch' (http://fetch.sourceforge.net), a complete HTTP file download utility.
+
+
+LICENCE
+=======
+HTTP Fetcher is licenced under the GNU Lesser General Public License (LGPL)
+version 2 or newer. See the LICENSE file for full info. Feel free to contact
+me if you have any questions/concerns over licensing issues.
+
+
+FEEDBACK
+========
+If you use this library for anything, drop me a line (lhanson@users.sourceforge.net).
+I'd love to know that formally releasing this stuff was worth it. I'm open
+to any input (bad or good).
+
+
+TODO:
+====
+Further development is focused on the 2.x series of http-fetcher. Only bugfixes
+and minor improvements will be added to 1.x releases.
+
+
+BUGS:
+=====
+This version introduces transparent redirects, but the 1.x series still does
+not allow the client program access to the HTTP return code. So while you can
+now follow redirects, your program won't know anything about it. You can,
+however, turn off or configure the number of redirects to follow.
+
+A better interface is planned for 2.x.x.
+
+
+CREDITS:
+========
+Steve Augart (steve@augart.com) has been a huge contributor to the project.
+Thanks, Steve!
diff --git a/src/libpcp_http/src/http_error_codes.c b/src/libpcp_http/src/http_error_codes.c
new file mode 100644
index 0000000..3b1fb2c
--- /dev/null
+++ b/src/libpcp_http/src/http_error_codes.c
@@ -0,0 +1,39 @@
+/* http_error_codes.c - Error code declarations
+
+ HTTP Fetcher
+ Copyright (C) 2001, 2003, 2004 Lyle Hanson (lhanson@users.sourceforge.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ See LICENSE file for details
+ */
+
+
+ /* Note that '%d' cannot be escaped at this time */
+const char *http_errlist[] =
+ {
+ "Success", /* HF_SUCCESS */
+ "Internal Error. What the hell?!", /* HF_METAERROR */
+ "Got NULL url", /* HF_NULLURL */
+ "Timed out, no metadata for %d seconds", /* HF_HEADTIMEOUT */
+ "Timed out, no data for %d seconds", /* HF_DATATIMEOUT */
+ "Couldn't find return code in HTTP response", /* HF_FRETURNCODE */
+ "Couldn't convert return code in HTTP response",/* HF_CRETURNCODE */
+ "Request returned a status code of %d", /* HF_STATUSCODE */
+ "Couldn't convert Content-Length to integer", /* HF_CONTENTLEN */
+ "Network error (description unavailable)", /* HF_HERROR */
+ "Status code of %d but no Location: field", /* HF_CANTREDIRECT */
+ "Followed the maximum number of redirects (%d)" /* HF_MAXREDIRECTS */
+ };
+
+ /* Used to copy in messages from http_errlist[] and replace %d's with
+ * the value of errorInt. Then we can pass the pointer to THIS */
+char convertedError[128];
diff --git a/src/libpcp_http/src/http_error_codes.h b/src/libpcp_http/src/http_error_codes.h
new file mode 100644
index 0000000..f1d9c51
--- /dev/null
+++ b/src/libpcp_http/src/http_error_codes.h
@@ -0,0 +1,43 @@
+/* http_error_codes.h - Error code definitions
+
+ HTTP Fetcher
+ Copyright (C) 2001, 2003, 2004 Lyle Hanson (lhanson@users.sourceforge.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ See LICENSE file for details
+
+ */
+
+#ifndef HTTP_ERROR_CODES_H
+#define HTTP_ERROR_CODES_H
+
+/* Error sources */
+#define FETCHER_ERROR 0
+#define ERRNO 1
+#define H_ERRNO 2
+
+/* HTTP Fetcher error codes */
+#define HF_SUCCESS 0
+#define HF_METAERROR 1
+#define HF_NULLURL 2
+#define HF_HEADTIMEOUT 3
+#define HF_DATATIMEOUT 4
+#define HF_FRETURNCODE 5
+#define HF_CRETURNCODE 6
+#define HF_STATUSCODE 7
+#define HF_CONTENTLEN 8
+#define HF_HERROR 9
+#define HF_CANTREDIRECT 10
+#define HF_MAXREDIRECTS 11
+#define HF_CONNECTTIMEOUT 12
+
+#endif
diff --git a/src/libpcp_http/src/http_fetcher.c b/src/libpcp_http/src/http_fetcher.c
new file mode 100644
index 0000000..4d01919
--- /dev/null
+++ b/src/libpcp_http/src/http_fetcher.c
@@ -0,0 +1,886 @@
+/* http_fetcher.c - HTTP handling functions
+
+ HTTP Fetcher
+ Copyright (c) 2014 Red Hat.
+ Copyright (C) 2001, 2003, 2004 Lyle Hanson (lhanson@users.sourceforge.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ See LICENSE file for details
+
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include "http_fetcher.h"
+
+/* Globals */
+int timeout = DEFAULT_READ_TIMEOUT;
+char *userAgent = NULL;
+char *referer = NULL;
+int hideUserAgent = 0;
+int hideReferer = 1;
+static int followRedirects = DEFAULT_REDIRECTS; /* # of redirects to follow */
+extern const char *http_errlist[]; /* Array of HTTP Fetcher error messages */
+extern char convertedError[128]; /* Buffer to used when errors contain %d */
+static int errorSource = 0;
+static int http_errno = 0;
+static int errorInt = 0; /* When the error message has a %d in it,
+ * this variable is inserted */
+
+
+ /*
+ * Actually downloads the page, registering a hit (donation)
+ * If the fileBuf passed in is NULL, the url is downloaded and then
+ * freed; otherwise the necessary space is allocated for fileBuf.
+ * Returns size of download on success, -1 on error is set,
+ */
+int http_fetch(const char *url_tmp, char **fileBuf)
+ {
+ fd_set rfds;
+ struct timeval tv;
+ char headerBuf[HEADER_BUF_SIZE];
+ char *tmp, *url, *pageBuf, *requestBuf = NULL, *host, *charIndex;
+ int sock, bytesRead = 0, contentLength = -1, bufsize = REQUEST_BUF_SIZE;
+ int i,
+ ret = -1,
+ tempSize,
+ selectRet,
+ found = 0, /* For redirects */
+ redirectsFollowed = 0;
+
+
+ if(url_tmp == NULL)
+ {
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_NULLURL;
+ return -1;
+ }
+
+ /* Copy the url passed in into a buffer we can work with, change, etc. */
+ url = malloc(strlen(url_tmp)+1);
+ if(url == NULL)
+ {
+ errorSource = ERRNO;
+ return -1;
+ }
+ strncpy(url, url_tmp, strlen(url_tmp) + 1);
+
+ /* This loop allows us to follow redirects if need be. An afterthought,
+ * added to provide this basic functionality. Will hopefully be designed
+ * better in 2.x.x ;) */
+/* while(!found &&
+ (followRedirects < 0 || redirectsFollowed < followRedirects) )
+ */ do
+ {
+ /* Seek to the file path portion of the url */
+ charIndex = strstr(url, "://");
+ if(charIndex != NULL)
+ {
+ /* url contains a protocol field */
+ charIndex += strlen("://");
+ host = charIndex;
+ charIndex = strchr(charIndex, '/');
+ }
+ else
+ {
+ host = (char *)url;
+ charIndex = strchr(url, '/');
+ }
+
+ /* Compose a request string */
+ requestBuf = malloc(bufsize);
+ if(requestBuf == NULL)
+ {
+ free(url);
+ errorSource = ERRNO;
+ return -1;
+ }
+ requestBuf[0] = 0;
+
+ if(charIndex == NULL)
+ {
+ /* The url has no '/' in it, assume the user is making a root-level
+ * request */
+ tempSize = strlen("GET /") + strlen(HTTP_VERSION) + 2;
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize) ||
+ snprintf(requestBuf, bufsize, "GET / %s\r\n", HTTP_VERSION) < 0)
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ }
+ else
+ {
+ tempSize = strlen("GET ") + strlen(charIndex) +
+ strlen(HTTP_VERSION) + 4;
+ /* + 4 is for ' ', '\r', '\n', and NULL */
+
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize) ||
+ snprintf(requestBuf, bufsize, "GET %s %s\r\n",
+ charIndex, HTTP_VERSION) < 0)
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ }
+
+ /* Null out the end of the hostname if need be */
+ if(charIndex != NULL)
+ *charIndex = 0;
+
+ /* Use Host: even though 1.0 doesn't specify it. Some servers
+ * won't play nice if we don't send Host, and it shouldn't
+ * hurt anything */
+ ret = bufsize - strlen(requestBuf); /* Space left in buffer */
+ tempSize = (int)strlen("Host: ") + (int)strlen(host) + 3;
+ /* +3 for "\r\n\0" */
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize + 128))
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ strcat(requestBuf, "Host: ");
+ strcat(requestBuf, host);
+ strcat(requestBuf, "\r\n");
+
+ if(!hideReferer && referer != NULL) /* NO default referer */
+ {
+ tempSize = (int)strlen("Referer: ") + (int)strlen(referer) + 3;
+ /* + 3 is for '\r', '\n', and NULL */
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize))
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ strcat(requestBuf, "Referer: ");
+ strcat(requestBuf, referer);
+ strcat(requestBuf, "\r\n");
+ }
+
+ if(!hideUserAgent && userAgent == NULL)
+ {
+ tempSize = (int)strlen("User-Agent: ") +
+ (int)strlen(DEFAULT_USER_AGENT) + (int)strlen(VERSION) + 4;
+ /* + 4 is for '\', '\r', '\n', and NULL */
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize))
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ strcat(requestBuf, "User-Agent: ");
+ strcat(requestBuf, DEFAULT_USER_AGENT);
+ strcat(requestBuf, "/");
+ strcat(requestBuf, VERSION);
+ strcat(requestBuf, "\r\n");
+ }
+ else if(!hideUserAgent)
+ {
+ tempSize = (int)strlen("User-Agent: ") + (int)strlen(userAgent) + 3;
+ /* + 3 is for '\r', '\n', and NULL */
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize))
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ strcat(requestBuf, "User-Agent: ");
+ strcat(requestBuf, userAgent);
+ strcat(requestBuf, "\r\n");
+ }
+
+ tempSize = (int)strlen("Connection: Close\r\n\r\n");
+ if(_checkBufSize(&requestBuf, &bufsize, tempSize))
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ strcat(requestBuf, "Connection: Close\r\n\r\n");
+
+ /* Now free any excess memory allocated to the buffer */
+ tmp = realloc(requestBuf, strlen(requestBuf) + 1);
+ if(tmp == NULL)
+ {
+ free(url);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ requestBuf = tmp;
+
+ sock = makeSocket(host); /* errorSource set within makeSocket */
+ if(sock == -1) { free(url); free(requestBuf); return -1;}
+
+ free(url);
+ url = NULL;
+
+ if(write(sock, requestBuf, strlen(requestBuf)) == -1)
+ {
+ close(sock);
+ free(requestBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+
+ free(requestBuf);
+ requestBuf = NULL;
+
+ /* Grab enough of the response to get the metadata */
+ ret = _http_read_header(sock, headerBuf); /* errorSource set within */
+ if(ret < 0) { close(sock); return -1; }
+
+ /* Get the return code */
+ charIndex = strstr(headerBuf, "HTTP/");
+ if(charIndex == NULL)
+ {
+ close(sock);
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_FRETURNCODE;
+ return -1;
+ }
+ while(*charIndex != ' ')
+ charIndex++;
+ charIndex++;
+
+ ret = sscanf(charIndex, "%d", &i);
+ if(ret != 1)
+ {
+ close(sock);
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_CRETURNCODE;
+ return -1;
+ }
+ if(i<200 || i>307)
+ {
+ close(sock);
+ errorInt = i; /* Status code, to be inserted in error string */
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_STATUSCODE;
+ return -1;
+ }
+
+ /* If a redirect, repeat operation until final URL is found or we
+ * redirect followRedirects times. Note the case sensitive "Location",
+ * should probably be made more robust in the future (without relying
+ * on the non-standard strcasecmp()).
+ * This bit mostly by Dean Wilder, tweaked by me */
+ if(i >= 300)
+ {
+ redirectsFollowed++;
+
+ /* Pick up redirect URL, allocate new url, and repeat process */
+ charIndex = strstr(headerBuf, "Location:");
+ if(!charIndex)
+ {
+ close(sock);
+ errorInt = i; /* Status code, to be inserted in error string */
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_CANTREDIRECT;
+ return -1;
+ }
+ charIndex += strlen("Location:");
+ /* Skip any whitespace... */
+ while(*charIndex != '\0' && isspace((int)*charIndex))
+ charIndex++;
+ if(*charIndex == '\0')
+ {
+ close(sock);
+ errorInt = i; /* Status code, to be inserted in error string */
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_CANTREDIRECT;
+ return -1;
+ }
+
+ i = strcspn(charIndex, " \r\n");
+ if(i > 0)
+ {
+ url = (char *)malloc(i + 1);
+ strncpy(url, charIndex, i);
+ url[i] = '\0';
+ }
+ else
+ /* Found 'Location:' but contains no URL! We'll handle it as
+ * 'found', hopefully the resulting document will give the user
+ * a hint as to what happened. */
+ found = 1;
+ }
+ else
+ found = 1;
+ } while(!found &&
+ (followRedirects < 0 || redirectsFollowed <= followRedirects) );
+
+ if(url) /* Redirection code may malloc this, then exceed followRedirects */
+ {
+ free(url);
+ url = NULL;
+ }
+
+ if(redirectsFollowed >= followRedirects && !found)
+ {
+ close(sock);
+ errorInt = followRedirects; /* To be inserted in error string */
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_MAXREDIRECTS;
+ return -1;
+ }
+
+ /*
+ * Parse out about how big the data segment is.
+ * Note that under current HTTP standards (1.1 and prior), the
+ * Content-Length field is not guaranteed to be accurate or even present.
+ * I just use it here so I can allocate a ballpark amount of memory.
+ *
+ * Note that some servers use different capitalization
+ */
+ charIndex = strstr(headerBuf, "Content-Length:");
+ if(charIndex == NULL)
+ charIndex = strstr(headerBuf, "Content-length:");
+
+ if(charIndex != NULL)
+ {
+ ret = sscanf(charIndex + strlen("content-length: "), "%d",
+ &contentLength);
+ if(ret < 1)
+ {
+ close(sock);
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_CONTENTLEN;
+ return -1;
+ }
+ }
+
+ /* Allocate enough memory to hold the page */
+ if(contentLength == -1)
+ contentLength = DEFAULT_PAGE_BUF_SIZE;
+
+ pageBuf = (char *)malloc(contentLength);
+ if(pageBuf == NULL)
+ {
+ close(sock);
+ errorSource = ERRNO;
+ return -1;
+ }
+
+ /* Begin reading the body of the file */
+ while(ret > 0)
+ {
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ if(timeout >= 0)
+ selectRet = select(sock+1, &rfds, NULL, NULL, &tv);
+ else /* No timeout, can block indefinately */
+ selectRet = select(sock+1, &rfds, NULL, NULL, NULL);
+
+ if(selectRet == 0)
+ {
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_DATATIMEOUT;
+ errorInt = timeout;
+ close(sock);
+ free(pageBuf);
+ return -1;
+ }
+ else if(selectRet == -1)
+ {
+ setoserror(neterror());
+ close(sock);
+ free(pageBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+
+ ret = recv(sock, pageBuf + bytesRead, contentLength, 0);
+ if(ret == -1)
+ {
+ setoserror(neterror());
+ close(sock);
+ free(pageBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+
+ bytesRead += ret;
+
+ if(ret > 0)
+ {
+ /* To be tolerant of inaccurate Content-Length fields, we'll
+ * allocate another read-sized chunk to make sure we have
+ * enough room.
+ */
+ tmp = (char *)realloc(pageBuf, bytesRead + contentLength);
+ if(tmp == NULL)
+ {
+ close(sock);
+ free(pageBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ pageBuf = tmp;
+ }
+ }
+
+ /*
+ * The download buffer is too large. Trim off the safety padding.
+ * Note that we add one NULL byte to the end of the data, as it may not
+ * already be NULL terminated and we can't be sure what type of data it
+ * is or what the caller will do with it.
+ */
+ tmp = (char *)realloc(pageBuf, bytesRead + 1);
+ /* tmp shouldn't be null, since we're _shrinking_ the buffer,
+ * and if it DID fail, we could go on with the too-large buffer,
+ * but something would DEFINATELY be wrong, so we'll just give
+ * an error message */
+ if(tmp == NULL)
+ {
+ close(sock);
+ free(pageBuf);
+ errorSource = ERRNO;
+ return -1;
+ }
+ pageBuf = tmp;
+ pageBuf[bytesRead] = '\0'; /* NULL terminate the data */
+
+ if(fileBuf == NULL) /* They just wanted us to "hit" the url */
+ free(pageBuf);
+ else
+ *fileBuf = pageBuf;
+
+ close(sock);
+ return bytesRead;
+ }
+
+
+
+ /*
+ * Changes the User Agent. Returns 0 on success, -1 on error.
+ */
+int http_setUserAgent(const char *newAgent)
+ {
+ static int freeOldAgent = 0; /* Indicates previous malloc's */
+ char *tmp;
+
+ if(newAgent == NULL)
+ {
+ if(freeOldAgent) free(userAgent);
+ userAgent = NULL;
+ hideUserAgent = 1;
+ }
+ else
+ {
+ tmp = (char *)malloc(strlen(newAgent)+1);
+ if(tmp == NULL) { errorSource = ERRNO; return -1; }
+ if(freeOldAgent) free(userAgent);
+ userAgent = tmp;
+ strcpy(userAgent, newAgent);
+ freeOldAgent = 1;
+ hideUserAgent = 0;
+ }
+
+ return 0;
+ }
+
+
+
+ /*
+ * Changes the Referer. Returns 0 on success, -1 on error
+ */
+int http_setReferer(const char *newReferer)
+ {
+ static int freeOldReferer = 0; /* Indicated previous malloc's */
+ char *tmp;
+
+ if(newReferer == NULL)
+ {
+ if(freeOldReferer) free(referer);
+ referer = NULL;
+ hideReferer = 1;
+ }
+ else
+ {
+ tmp = (char *)malloc(strlen(newReferer)+1);
+ if(tmp == NULL) { errorSource = ERRNO; return -1; }
+ if(freeOldReferer) free(referer);
+ referer = tmp;
+ strcpy(referer, newReferer);
+ freeOldReferer = 1;
+ hideReferer = 0;
+ }
+
+ return 0;
+ }
+
+
+
+ /*
+ * Changes the amount of time that HTTP Fetcher will wait for data
+ * before timing out on reads
+ */
+void http_setTimeout(int seconds) { timeout = seconds; }
+
+
+
+ /*
+ * Changes the number of HTTP redirects HTTP Fetcher will automatically
+ * follow. If a request returns a status code of 3XX and contains
+ * a "Location:" field, the library will transparently follow up to
+ * the specified number of redirects. With this implementation
+ * (which is just a stopgap, really) the caller won't be aware of any
+ * redirection and will assume the returned document came from the original
+ * URL.
+ * To disable redirects, pass a 0. To follow unlimited redirects (probably
+ * unwise), pass a negative value. The default is to follow 3 redirects.
+ */
+void http_setRedirects(int redirects) { followRedirects = redirects; }
+
+
+
+ /*
+ * Puts the filename portion of the url into 'filename'.
+ * Returns:
+ * 0 on success
+ * 1 when url contains no end filename (i.e., 'www.foo.com/'),
+ * and **filename should not be assumed to be valid
+ * -1 on error
+ */
+int http_parseFilename(const char *url, char **filename)
+ {
+ char *ptr;
+
+ if(url == NULL)
+ {
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_NULLURL;
+ return -1;
+ }
+
+ ptr = (char *)rindex(url, '/');
+ if(ptr == NULL)
+ /* Root level request, apparently */
+ return 1;
+
+ ptr++;
+ if(*ptr == '\0') return 1;
+
+ *filename = (char *)malloc(strlen(ptr)+1);
+ if(*filename == NULL) { errorSource = ERRNO; return -1; }
+ strcpy(*filename, ptr);
+
+ return 0;
+ }
+
+
+
+ /* Depending on the source of error, calls either perror() or prints
+ * an HTTP Fetcher error message to stdout */
+void http_perror(const char *string)
+ {
+ if(errorSource == ERRNO)
+ fprintf(stderr, "%s: %s\n", string, osstrerror());
+ else if(errorSource == H_ERRNO)
+ fprintf(stderr, "%s: %s\n", string, hoststrerror());
+ else if(errorSource == FETCHER_ERROR)
+ {
+ const char *stringIndex;
+
+ if(strstr(http_errlist[http_errno], "%d") == NULL)
+ {
+ fputs(string, stderr);
+ fputs(": ", stderr);
+ fputs(http_errlist[http_errno], stderr);
+ fputs("\n", stderr);
+ }
+ else
+ {
+ /* The error string has a %d in it, we need to insert errorInt */
+ stringIndex = http_errlist[http_errno];
+ while(*stringIndex != '%') /* Print up to the %d */
+ {
+ fputc(*stringIndex, stderr);
+ stringIndex++;
+ }
+ fprintf(stderr, "%d", errorInt); /* Print the number */
+ stringIndex += 2; /* Skip past the %d */
+ while(*stringIndex != 0) /* Print up to the end NULL */
+ {
+ fputc(*stringIndex, stderr);
+ stringIndex++;
+ }
+ fputs("\n", stderr);
+ }
+ }
+ }
+
+
+ /*
+ * Returns true/false (1/0) if a timeout occurred on last request.
+ */
+int http_getTimeoutError()
+ {
+ if(errorSource == FETCHER_ERROR)
+ return (http_errno == HF_DATATIMEOUT || http_errno == HF_HEADTIMEOUT || http_errno == HF_CONNECTTIMEOUT);
+ return 0;
+ }
+
+
+ /*
+ * Returns a pointer to the current error description message. The
+ * message pointed to is only good until the next call to http_strerror(),
+ * so if you need to hold on to the message for a while you should make
+ * a copy of it
+ */
+const char *http_strerror()
+ {
+ if(errorSource == ERRNO)
+ return osstrerror();
+ else if(errorSource == H_ERRNO)
+ return hoststrerror();
+ else if(errorSource == FETCHER_ERROR)
+ {
+ if(strstr(http_errlist[http_errno], "%d") == NULL)
+ return http_errlist[http_errno];
+ else
+ {
+ /* The error string has a %d in it, we need to insert errorInt.
+ * convertedError[128] has been declared for that purpose */
+ char *stringIndex, *originalError;
+
+ originalError = (char *)http_errlist[http_errno];
+ convertedError[0] = 0; /* Start off with NULL */
+ stringIndex = strstr(originalError, "%d");
+ strncat(convertedError, originalError, /* Copy up to %d */
+ abs(stringIndex - originalError));
+ sprintf(&convertedError[strlen(convertedError)],"%d",errorInt);
+ stringIndex += 2; /* Skip past the %d */
+ strcat(convertedError, stringIndex);
+
+ return convertedError;
+ }
+ }
+
+ return http_errlist[HF_METAERROR]; /* Should NEVER happen */
+ }
+
+
+ /*
+ * Reads the metadata of an HTTP response.
+ * Perhaps a little inefficient, as it reads 1 byte at a time, but
+ * I don't think it's that much of a loss (most headers aren't HUGE).
+ * Returns:
+ * # of bytes read on success, or
+ * -1 on error
+ */
+int _http_read_header(int sock, char *headerPtr)
+ {
+ fd_set rfds;
+ struct timeval tv;
+ int bytesRead = 0, newlines = 0, ret, selectRet;
+
+ while(newlines != 2 && bytesRead != HEADER_BUF_SIZE)
+ {
+ FD_ZERO(&rfds);
+ FD_SET(sock, &rfds);
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ if(timeout >= 0)
+ selectRet = select(sock+1, &rfds, NULL, NULL, &tv);
+ else /* No timeout, can block indefinately */
+ selectRet = select(sock+1, &rfds, NULL, NULL, NULL);
+
+ if(selectRet == 0)
+ {
+ errorSource = FETCHER_ERROR;
+ http_errno = HF_HEADTIMEOUT;
+ errorInt = timeout;
+ return -1;
+ }
+ else if(selectRet == -1)
+ {
+ setoserror(neterror());
+ errorSource = ERRNO;
+ return -1;
+ }
+
+ ret = recv(sock, headerPtr, 1, 0);
+ if(ret == -1)
+ {
+ setoserror(neterror());
+ errorSource = ERRNO;
+ return -1;
+ }
+ bytesRead++;
+
+ if(*headerPtr == '\r') /* Ignore CR */
+ {
+ /* Basically do nothing special, just don't set newlines
+ * to 0 */
+ headerPtr++;
+ continue;
+ }
+ else if(*headerPtr == '\n') /* LF is the separator */
+ newlines++;
+ else
+ newlines = 0;
+
+ headerPtr++;
+ }
+
+ headerPtr -= 3; /* Snip the trailing LF's */
+ *headerPtr = '\0';
+ return bytesRead;
+ }
+
+
+
+ /*
+ * Opens a TCP socket and returns the descriptor
+ * Returns:
+ * socket descriptor, or
+ * -1 on error
+ */
+int makeSocket(const char *host)
+ {
+ int sock; /* Socket descriptor */
+ int ret;
+ int port;
+ char *p;
+ __pmFdSet wfds;
+ struct timeval tv;
+ struct timeval *ptv;
+ __pmSockAddr *myaddr;
+ __pmHostEnt *servInfo;
+ void *enumIx;
+ int flags = 0;
+
+ /* Check for port number specified in URL */
+ p = strchr(host, ':');
+ if(p)
+ {
+ port = atoi(p + 1);
+ *p = '\0';
+ }
+ else
+ port = PORT_NUMBER;
+
+ servInfo = __pmGetAddrInfo(host);
+ if(servInfo == NULL) { errorSource = H_ERRNO; return -1; }
+
+ sock = -1;
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myaddr))
+ sock = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ sock = __pmCreateIPv6Socket();
+ else
+ continue;
+ if (sock < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ flags = __pmConnectTo(sock, myaddr, port);
+ __pmSockAddrFree(myaddr);
+
+ if (flags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address. sock has been closed in __pmConnectTo().
+ */
+ sock = -1;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ ptv = (tv.tv_sec || tv.tv_usec) ? &tv : NULL;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(sock, &wfds);
+ ret = __pmSelectWrite(sock+1, &wfds, ptv);
+
+ /* Was the connection successful? */
+ if (ret < 0) {
+ if (oserror() == EINTR)
+ return _makeSocketErr(sock, FETCHER_ERROR, HF_CONNECTTIMEOUT);
+ return _makeSocketErr(sock, ERRNO, 0);
+ }
+ ret = __pmConnectCheckError(sock);
+ if (ret == 0)
+ break;
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(sock);
+ sock = -1;
+ } /* loop over addresses */
+
+ __pmHostEntFree(servInfo);
+
+ if(sock == -1) { errorSource = ERRNO; return -1; }
+
+ sock = __pmConnectRestoreFlags(sock, flags);
+ if(sock < 0) { errorSource = ERRNO; return -1; }
+
+ return sock;
+ }
+
+int _makeSocketErr(int sock, int this_errorSource, int this_http_errno){
+ errorSource = this_errorSource;
+ http_errno = this_http_errno;
+ close(sock);
+ return -1;
+ }
+
+ /*
+ * Determines if the given NULL-terminated buffer is large enough to
+ * concatenate the given number of characters. If not, it attempts to
+ * grow the buffer to fit.
+ * Returns:
+ * 0 on success, or
+ * -1 on error (original buffer is unchanged).
+ */
+int _checkBufSize(char **buf, int *bufsize, int more)
+ {
+ char *tmp;
+ int roomLeft = *bufsize - (strlen(*buf) + 1);
+ if(roomLeft > more)
+ return 0;
+ tmp = realloc(*buf, *bufsize + more + 1);
+ if(tmp == NULL)
+ return -1;
+ *buf = tmp;
+ *bufsize += more + 1;
+ return 0;
+ }
diff --git a/src/libpcp_http/src/http_fetcher.h b/src/libpcp_http/src/http_fetcher.h
new file mode 100644
index 0000000..62a39dd
--- /dev/null
+++ b/src/libpcp_http/src/http_fetcher.h
@@ -0,0 +1,168 @@
+/* http_fetcher.h - HTTP handling functions
+
+ HTTP Fetcher
+ Copyright (C) 2001, 2003, 2004 Lyle Hanson (lhanson@users.sourceforge.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ See LICENSE file for details
+
+ */
+
+#ifndef HTTP_FETCHER_H
+#define HTTP_FETCHER_H
+
+#include "http_error_codes.h"
+
+#define PORT_NUMBER 80
+#define HTTP_VERSION "HTTP/1.0"
+#define DEFAULT_USER_AGENT "HTTP Fetcher"
+#define DEFAULT_READ_TIMEOUT 30 /* Seconds to wait before giving up
+ * when no data is arriving */
+
+#define REQUEST_BUF_SIZE 1024
+#define HEADER_BUF_SIZE 1024
+#define DEFAULT_PAGE_BUF_SIZE 1024 * 200 /* 200K should hold most things */
+#define DEFAULT_REDIRECTS 3 /* Number of HTTP redirects to follow */
+
+
+
+/******************************************************************************/
+/**************** Function declarations and descriptions **********************/
+/******************************************************************************/
+
+/*
+ * [!!! NOTE !!!] All HTTP Fetcher functions return -1 on error. You can
+ * then either call http_perror to print the error message or call
+ * http_strerror to get a pointer to it
+ */
+
+
+ /*
+ * Download the page, registering a hit. If you pass it a NULL for fileBuf,
+ * 'url' will be requested but will not remain in memory (useful for
+ * simply registering a hit). Otherwise necessary space will be allocated
+ * and will be pointed to by fileBuf. Note that a NULL byte is added to
+ * the data, so the actual buffer will be the file size + 1.
+ * Returns:
+ * # of bytes downloaded, or
+ * -1 on error
+ */
+int http_fetch(const char *url, char **fileBuf);
+
+ /*
+ * Changes the User Agent (shown to the web server with each request)
+ * Send it NULL to avoid telling the server a User Agent
+ * By default, the User Agent is sent (The default one unless changed)
+ * Returns:
+ * 0 on success, or
+ * -1 on error (previous value for agent remains unchanged)
+ */
+int http_setUserAgent(const char *newAgent);
+
+ /*
+ * Changes the Referer (shown to the web server with each request)
+ * Send it NULL to avoid thelling the server a Referer
+ * By default, no Referer is sent
+ * Returns:
+ * 0 on success, or
+ * -1 on error
+ */
+int http_setReferer(const char *newReferer);
+
+ /*
+ * Changes the maximum amount of time that HTTP Fetcher will wait on
+ * data. If this many seconds elapses without more data from the
+ * server, http_fetch will return with an error.
+ * If you pass a value less than 0, reads will not time out, potentially
+ * waiting forever (or until data shows up, whichever comes first)
+ */
+void http_setTimeout(int seconds);
+
+ /*
+ * Changes the number of HTTP redirects HTTP Fetcher will automatically
+ * follow. If a request returns a status code of 3XX and contains
+ * a "Location:" field, the library will transparently follow up to
+ * the specified number of redirects. With this implementation
+ * (which is just a stopgap, really) the caller won't be aware of any
+ * redirection and will assume the returned document came from the original
+ * URL.
+ * To disable redirects, pass a 0. To follow unlimited redirects (probably
+ * unwise), pass a negative value. The default is to follow 3 redirects.
+ */
+void http_setRedirects(int redirects);
+
+ /*
+ * Takes a url and puts the filename portion of it into 'filename'.
+ * Returns:
+ * 0 on success, or
+ * 1 when url contains no end filename (i.e., "www.foo.com/")
+ * and **filename should not be assumed to point to anything), or
+ * -1 on error
+ */
+int http_parseFilename(const char *url, char **filename);
+
+ /*
+ * Works like perror. If an HTTP Fetcher function ever returns an
+ * error (-1), this will print a descriptive message to standard output
+ */
+void http_perror(const char *string);
+
+ /*
+ * Returns true or false (1/0) if last request timed out.
+ */
+int http_getTimeoutError();
+
+ /*
+ * Returns a pointer to the current error description message. The
+ * message pointed to is only good until the next call to http_strerror(),
+ * so if you need to hold on to the message for a while you should make
+ * a copy of it.
+ */
+const char *http_strerror();
+
+
+
+/******************************************************************************/
+/**** The following functions are used INTERNALLY by http_fetcher *************/
+/******************************************************************************/
+
+ /*
+ * Reads the metadata of an HTTP response. On success returns the number
+ * Returns:
+ * # of bytes read on success, or
+ * -1 on error
+ */
+int _http_read_header(int sock, char *headerPtr);
+
+ /*
+ * Opens a TCP socket and returns the descriptor
+ * Returns:
+ * socket descriptor, or
+ * -1 on error
+ */
+int makeSocket(const char *host);
+
+ /* Handles error conditions with creating sockets
+ */
+int _makeSocketErr(int sock, int this_errorSource, int this_http_errno);
+
+ /*
+ * Determines if the given NULL-terminated buffer is large enough to
+ * concatenate the given number of characters. If not, it attempts to
+ * grow the buffer to fit.
+ * Returns:
+ * 0 on success, or
+ * -1 on error (original buffer is unchanged).
+ */
+int _checkBufSize(char **buf, int *bufsize, int more);
+
+#endif
diff --git a/src/libpcp_import/GNUmakefile b/src/libpcp_import/GNUmakefile
new file mode 100644
index 0000000..0cb0f4a
--- /dev/null
+++ b/src/libpcp_import/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+# libpcp_import.so - import performance data and create PCP archives
+#
+
+TOPDIR = ../..
+
+include $(TOPDIR)/src/include/builddefs
+
+BASE = libpcp_import.a
+
+SUBDIRS = src
+
+default install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/libpcp_import/src/GNUmakefile b/src/libpcp_import/src/GNUmakefile
new file mode 100644
index 0000000..6c3c2aa
--- /dev/null
+++ b/src/libpcp_import/src/GNUmakefile
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = import.c stuff.c archive.c
+HFILES = private.h
+VERSION_SCRIPT = exports
+
+STATICLIBTARGET = libpcp_import.a
+DSOVERSION = 1
+LIBTARGET = libpcp_import.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp_import.$(DSOSUFFIX)
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp_import.$(DSOVERSION).$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+LIBTARGET = libpcp_import.$(DSOSUFFIX)
+SYMTARGET =
+STATICLIBTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LCFLAGS =
+LLDLIBS = -lpcp
+LSRCFILES = $(VERSION_SCRIPT)
+LDIRT = $(SYMTARGET) domain.h
+
+DOMAIN = PMI_DOMAIN
+
+default: domain.h $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+
+$(OBJECTS): $(HFILES)
+
+include $(BUILDRULES)
+
+install: default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ for tt in $(SYMTARGET); do \
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \
+ done
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+ifneq ($(SYMTARGET),)
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $@
+endif
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
+
+domain.h: $(TOPDIR)/src/pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/libpcp_import/src/archive.c b/src/libpcp_import/src/archive.c
new file mode 100644
index 0000000..4c1a0e3
--- /dev/null
+++ b/src/libpcp_import/src/archive.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "import.h"
+#include "private.h"
+
+static __pmTimeval stamp;
+
+int
+_pmi_put_result(pmi_context *current, pmResult *result)
+{
+ int sts;
+ char *host;
+ char myname[MAXHOSTNAMELEN];
+ __pmPDU *pb;
+ __pmLogCtl *lcp = &current->logctl;
+ int k;
+ int i;
+ int m;
+ int needti;
+
+ /*
+ * some front-end tools use lazy discovery of instances and/or process
+ * data in non-deterministic order ... it is simpler for everyone if
+ * we sort the values into ascending instance order.
+ */
+ pmSortInstances(result);
+
+ stamp.tv_sec = result->timestamp.tv_sec;
+ stamp.tv_usec = result->timestamp.tv_usec;
+
+ if (current->state == CONTEXT_START) {
+ if (current->hostname == NULL) {
+ (void)gethostname(myname, MAXHOSTNAMELEN);
+ myname[MAXHOSTNAMELEN-1] = '\0';
+ host = myname;
+ }
+ else
+ host = current->hostname;
+
+ sts = __pmLogCreate(host, current->archive, PM_LOG_VERS02, lcp);
+ if (sts < 0)
+ return sts;
+
+ if (current->timezone == NULL) {
+ char tzbuf[PM_TZ_MAXLEN];
+ strcpy(lcp->l_label.ill_tz, __pmTimezone_r(tzbuf, sizeof(tzbuf)));
+ }
+ else
+ strcpy(lcp->l_label.ill_tz, current->timezone);
+ pmNewZone(lcp->l_label.ill_tz);
+ current->state = CONTEXT_ACTIVE;
+
+ /*
+ * do the label records (it is too late when __pmLogPutResult
+ * or __pmLogPutResult2 is called as we've already output some
+ * metadata) ... this code is stolen from logputresult() in
+ * libpcp
+ */
+ lcp->l_label.ill_start.tv_sec = stamp.tv_sec;
+ lcp->l_label.ill_start.tv_usec = stamp.tv_usec;
+ lcp->l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(lcp->l_tifp, &lcp->l_label);
+ lcp->l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(lcp->l_mdfp, &lcp->l_label);
+ lcp->l_label.ill_vol = 0;
+ __pmLogWriteLabel(lcp->l_mfp, &lcp->l_label);
+ lcp->l_state = PM_LOG_STATE_INIT;
+ __pmLogPutIndex(&current->logctl, &stamp);
+ }
+
+ __pmOverrideLastFd(fileno(lcp->l_mfp));
+ if ((sts = __pmEncodeResult(fileno(lcp->l_mfp), result, &pb)) < 0)
+ return sts;
+
+ needti = 0;
+ for (k = 0; k < result->numpmid; k++) {
+ for (m = 0; m < current->nmetric; m++) {
+ if (result->vset[k]->pmid != current->metric[m].pmid)
+ continue;
+ if (current->metric[m].meta_done == 0) {
+ char **namelist = &current->metric[m].name;
+
+ if ((sts = __pmLogPutDesc(lcp, &current->metric[m].desc, 1, namelist)) < 0) {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+ current->metric[m].meta_done = 1;
+ needti = 1;
+ }
+ if (current->metric[m].desc.indom != PM_INDOM_NULL) {
+ for (i = 0; i < current->nindom; i++) {
+ if (current->metric[m].desc.indom == current->indom[i].indom) {
+ if (current->indom[i].meta_done == 0) {
+ if ((sts = __pmLogPutInDom(lcp, current->indom[i].indom, &stamp, current->indom[i].ninstance, current->indom[i].inst, current->indom[i].name)) < 0) {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+ current->indom[i].meta_done = 1;
+ needti = 1;
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (needti) {
+ __pmLogPutIndex(lcp, &stamp);
+ }
+
+ if ((sts = __pmLogPutResult2(lcp, pb)) < 0) {
+ __pmUnpinPDUBuf(pb);
+ return sts;
+ }
+
+ __pmUnpinPDUBuf(pb);
+ return 0;
+}
+
+int
+_pmi_end(pmi_context *current)
+{
+ /* Final temporal index update to finish the archive
+ * ... same logic here as in run_done() for pmlogger
+ */
+ __pmLogPutIndex(&current->logctl, &stamp);
+
+ current->state = CONTEXT_END;
+ return 0;
+}
diff --git a/src/libpcp_import/src/exports b/src/libpcp_import/src/exports
new file mode 100644
index 0000000..a544262
--- /dev/null
+++ b/src/libpcp_import/src/exports
@@ -0,0 +1,28 @@
+PCP_IMPORT_1.0 {
+ global:
+ pmiStart;
+ pmiSetHostname;
+ pmiSetTimezone;
+ pmiUseContext;
+ pmiGetHandle;
+ pmiDump;
+ pmiEnd;
+
+ pmiAddInstance;
+ pmiAddMetric;
+
+ pmiErrStr;
+ pmiErrStr_r;
+
+ pmiID;
+ pmiInDom;
+ pmiUnits;
+
+ pmiPutResult;
+ pmiPutValue;
+ pmiPutValueHandle;
+
+ pmiWrite;
+
+ local: *;
+};
diff --git a/src/libpcp_import/src/import.c b/src/libpcp_import/src/import.c
new file mode 100644
index 0000000..71b8c7f
--- /dev/null
+++ b/src/libpcp_import/src/import.c
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "import.h"
+#include "domain.h"
+#include "private.h"
+#include <ctype.h>
+
+static pmi_context *context_tab;
+static int ncontext;
+static pmi_context *current;
+
+static void
+printstamp(FILE *f, const struct timeval *tp)
+{
+ struct tm tmp;
+ time_t now;
+
+ now = (time_t)tp->tv_sec;
+ pmLocaltime(&now, &tmp);
+ fprintf(f, "%4d-%02d-%02d %02d:%02d:%02d.%06d", 1900+tmp.tm_year, tmp.tm_mon, tmp.tm_mday, tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(tp->tv_usec));
+}
+
+
+void
+pmiDump(void)
+{
+ FILE *f = stderr;
+
+ fprintf(f, "pmiDump: context %ld of %d",
+ (long)(current - context_tab), ncontext);
+ if (current == NULL) {
+ fprintf(f, " Error: current context is not defined.\n");
+ return;
+ }
+ else {
+ fprintf(f, " archive: %s\n",
+ current->archive == NULL ? "<undefined>" : current->archive);
+ }
+ fprintf(f, " state: %d ", current->state);
+ switch (current->state) {
+ case CONTEXT_START:
+ fprintf(f, "(start)");
+ break;
+ case CONTEXT_ACTIVE:
+ fprintf(f, "(active)");
+ break;
+ case CONTEXT_END:
+ fprintf(f, "(end)");
+ break;
+ default:
+ fprintf(f, "(BAD)");
+ break;
+ }
+ fprintf(f, " hostname: %s timezone: %s\n",
+ current->hostname == NULL ? "<undefined>" : current->hostname,
+ current->timezone == NULL ? "<undefined>" : current->timezone);
+ if (current->nmetric == 0)
+ fprintf(f, " No metrics.\n");
+ else {
+ int m;
+ char strbuf[20];
+ for (m = 0; m < current->nmetric; m++) {
+ fprintf(f, " metric[%d] name=%s pmid=%s\n",
+ m, current->metric[m].name,
+ pmIDStr_r(current->metric[m].pmid, strbuf, sizeof(strbuf)));
+ __pmPrintDesc(f, &current->metric[m].desc);
+ }
+ }
+ if (current->nindom == 0)
+ fprintf(f, " No indoms.\n");
+ else {
+ int i;
+ char strbuf[20];
+ for (i = 0; i < current->nindom; i++) {
+ fprintf(f, " indom[%d] indom=%s",
+ i, pmInDomStr_r(current->indom[i].indom, strbuf, sizeof(strbuf)));
+ if (current->indom[i].ninstance == 0) {
+ fprintf(f, " No instances.\n");
+ }
+ else {
+ int j;
+ fputc('\n', f);
+ for (j = 0; j < current->indom[i].ninstance; j++) {
+ fprintf(f, " instance[%d] %s (%d)\n",
+ j, current->indom[i].name[j],
+ current->indom[i].inst[j]);
+ }
+ }
+ }
+ }
+ if (current->nhandle == 0)
+ fprintf(f, " No handles.\n");
+ else {
+ int h;
+ char strbuf[20];
+ for (h = 0; h < current->nhandle; h++) {
+ fprintf(f, " handle[%d] metric=%s (%s) instance=%d\n",
+ h, current->metric[current->handle[h].midx].name,
+ pmIDStr_r(current->metric[current->handle[h].midx].pmid, strbuf, sizeof(strbuf)),
+ current->handle[h].inst);
+ }
+ }
+ if (current->result == NULL)
+ fprintf(f, " No pmResult.\n");
+ else
+ __pmDumpResult(f, current->result);
+}
+
+pmUnits
+pmiUnits(int dimSpace, int dimTime, int dimCount, int scaleSpace, int scaleTime, int scaleCount)
+{
+ static pmUnits units;
+ units.dimSpace = dimSpace;
+ units.dimTime = dimTime;
+ units.dimCount = dimCount;
+ units.scaleSpace = scaleSpace;
+ units.scaleTime = scaleTime;
+ units.scaleCount = scaleCount;
+
+ return units;
+}
+
+pmID
+pmiID(int domain, int cluster, int item)
+{
+ return pmid_build(domain, cluster, item);
+}
+
+pmInDom
+pmiInDom(int domain, int serial)
+{
+ return pmInDom_build(domain, serial);
+}
+
+const char *
+pmiErrStr(int sts)
+{
+ static char errmsg[PMI_MAXERRMSGLEN];
+ pmiErrStr_r(sts, errmsg, sizeof(errmsg));
+ return errmsg;
+}
+
+char *
+pmiErrStr_r(int code, char *buf, int buflen)
+{
+ const char *msg;
+
+ if (code == -1 && current != NULL) code = current->last_sts;
+ switch (code) {
+ case PMI_ERR_DUPMETRICNAME:
+ msg = "Metric name already defined";
+ break;
+ case PMI_ERR_DUPMETRICID:
+ msg = "Metric pmID already defined";
+ break;
+ case PMI_ERR_DUPINSTNAME:
+ msg = "External instance name already defined";
+ break;
+ case PMI_ERR_DUPINSTID:
+ msg = "Internal instance identifer already defined";
+ break;
+ case PMI_ERR_INSTNOTNULL:
+ msg = "Null instance expected for a singular metric";
+ break;
+ case PMI_ERR_INSTNULL:
+ msg = "Null instance not allowed for a non-singular metric";
+ break;
+ case PMI_ERR_BADHANDLE:
+ msg = "Illegal handle";
+ break;
+ case PMI_ERR_DUPVALUE:
+ msg = "Value already assigned for this metric-instance";
+ break;
+ case PMI_ERR_BADTYPE:
+ msg = "Illegal metric type";
+ break;
+ case PMI_ERR_BADSEM:
+ msg = "Illegal metric semantics";
+ break;
+ case PMI_ERR_NODATA:
+ msg = "No data to output";
+ break;
+ case PMI_ERR_BADMETRICNAME:
+ msg = "Illegal metric name";
+ break;
+ case PMI_ERR_BADTIMESTAMP:
+ msg = "Illegal result timestamp";
+ break;
+ default:
+ return pmErrStr_r(code, buf, buflen);
+ }
+ strncpy(buf, msg, buflen);
+ buf[buflen-1] = '\0';
+ return buf;
+}
+
+int
+pmiStart(const char *archive, int inherit)
+{
+ pmi_context *old_current;
+ char *np;
+ int c = current - context_tab;
+
+ ncontext++;
+ context_tab = (pmi_context *)realloc(context_tab, ncontext*sizeof(context_tab[0]));
+ if (context_tab == NULL) {
+ __pmNoMem("pmiStart: context_tab", ncontext*sizeof(context_tab[0]), PM_FATAL_ERR);
+ }
+ old_current = &context_tab[c];
+ current = &context_tab[ncontext-1];
+
+ current->state = CONTEXT_START;
+ current->archive = strdup(archive);
+ if (current->archive == NULL) {
+ __pmNoMem("pmiStart", strlen(archive)+1, PM_FATAL_ERR);
+ }
+ current->hostname = NULL;
+ current->timezone = NULL;
+ current->result = NULL;
+ memset((void *)&current->logctl, 0, sizeof(current->logctl));
+ if (inherit && old_current != NULL) {
+ current->nmetric = old_current->nmetric;
+ if (old_current->metric != NULL) {
+ int m;
+ current->metric = (pmi_metric *)malloc(current->nmetric*sizeof(pmi_metric));
+ if (current->metric == NULL) {
+ __pmNoMem("pmiStart: pmi_metric", current->nmetric*sizeof(pmi_metric), PM_FATAL_ERR);
+ }
+ for (m = 0; m < current->nmetric; m++) {
+ current->metric[m].name = old_current->metric[m].name;
+ current->metric[m].pmid = old_current->metric[m].pmid;
+ current->metric[m].desc = old_current->metric[m].desc;
+ current->metric[m].meta_done = 0;
+ }
+ }
+ else
+ current->metric = NULL;
+ current->nindom = old_current->nindom;
+ if (old_current->indom != NULL) {
+ int i;
+ current->indom = (pmi_indom *)malloc(current->nindom*sizeof(pmi_indom));
+ if (current->indom == NULL) {
+ __pmNoMem("pmiStart: pmi_indom", current->nindom*sizeof(pmi_indom), PM_FATAL_ERR);
+ }
+ for (i = 0; i < current->nindom; i++) {
+ int j;
+ current->indom[i].indom = old_current->indom[i].indom;
+ current->indom[i].ninstance = old_current->indom[i].ninstance;
+ current->indom[i].meta_done = 0;
+ if (old_current->indom[i].ninstance > 0) {
+ current->indom[i].name = (char **)malloc(current->indom[i].ninstance*sizeof(char *));
+ if (current->indom[i].name == NULL) {
+ __pmNoMem("pmiStart: name", current->indom[i].ninstance*sizeof(char *), PM_FATAL_ERR);
+ }
+ current->indom[i].inst = (int *)malloc(current->indom[i].ninstance*sizeof(int));
+ if (current->indom[i].inst == NULL) {
+ __pmNoMem("pmiStart: inst", current->indom[i].ninstance*sizeof(int), PM_FATAL_ERR);
+ }
+ current->indom[i].namebuflen = old_current->indom[i].namebuflen;
+ current->indom[i].namebuf = (char *)malloc(old_current->indom[i].namebuflen);
+ if (current->indom[i].namebuf == NULL) {
+ __pmNoMem("pmiStart: namebuf", old_current->indom[i].namebuflen, PM_FATAL_ERR);
+ }
+ np = current->indom[i].namebuf;
+ for (j = 0; j < current->indom[i].ninstance; j++) {
+ strcpy(np, old_current->indom[i].name[j]);
+ current->indom[i].name[j] = np;
+ np += strlen(np)+1;
+ current->indom[i].inst[j] = old_current->indom[i].inst[j];
+ }
+ }
+ else {
+ current->indom[i].name = NULL;
+ current->indom[i].inst = NULL;
+ current->indom[i].namebuflen = 0;
+ current->indom[i].namebuf = NULL;
+ }
+ }
+ }
+ else
+ current->indom = NULL;
+ current->nhandle = old_current->nhandle;
+ if (old_current->handle != NULL) {
+ int h;
+ current->handle = (pmi_handle *)malloc(current->nhandle*sizeof(pmi_handle));
+ if (current->handle == NULL) {
+ __pmNoMem("pmiStart: pmi_handle", current->nhandle*sizeof(pmi_handle), PM_FATAL_ERR);
+ }
+ for (h = 0; h < current->nhandle; h++) {
+ current->handle[h].midx = old_current->handle[h].midx;
+ current->handle[h].inst = old_current->handle[h].inst;
+ }
+ }
+ else
+ current->handle = NULL;
+ current->last_stamp = old_current->last_stamp;
+ }
+ else {
+ current->nmetric = 0;
+ current->metric = NULL;
+ current->nindom = 0;
+ current->indom = NULL;
+ current->nhandle = 0;
+ current->handle = NULL;
+ current->last_stamp.tv_sec = current->last_stamp.tv_usec = 0;
+ }
+ return ncontext;
+}
+
+int
+pmiUseContext(int context)
+{
+ if (context < 1 || context > ncontext) {
+ if (current != NULL) current->last_sts = PM_ERR_NOCONTEXT;
+ return PM_ERR_NOCONTEXT;
+ }
+ current = &context_tab[context-1];
+ return current->last_sts = 0;
+}
+
+int
+pmiEnd(void)
+{
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ return current->last_sts = _pmi_end(current);
+}
+
+int
+pmiSetHostname(const char *value)
+{
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+ current->hostname = strdup(value);
+ if (current->hostname == NULL) {
+ __pmNoMem("pmiSetHostname", strlen(value)+1, PM_FATAL_ERR);
+ }
+ return current->last_sts = 0;
+}
+
+int
+pmiSetTimezone(const char *value)
+{
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+ current->timezone = strdup(value);
+ if (current->timezone == NULL) {
+ __pmNoMem("pmiSetTimezone", strlen(value)+1, PM_FATAL_ERR);
+ }
+ return current->last_sts = 0;
+}
+
+static int
+valid_pmns_name(const char *name)
+{
+ const char *previous;
+
+ /*
+ * Ensure requested metric name conforms to the PMNS rules:
+ * Should start with an alphabetic, then any combination of
+ * alphanumerics or underscore. Dot separators are OK, but
+ * ensure only one (no repeats).
+ */
+ if (name == NULL)
+ return 0;
+ if (!isalpha((int)*name))
+ return 0;
+ for (previous = name++; *name != '\0'; name++) {
+ if (!isalnum((int)*name) && *name != '_' && *name != '.')
+ return 0;
+ if (*previous == '.') {
+ if (!isalpha((int)*name)) /* non-alphabetic first */
+ return 0;
+ if (*name == *previous) /* repeated . separator */
+ return 0;
+ }
+ previous = name;
+ }
+ if (*previous == '.') /* shouldn't end with separator */
+ return 0;
+ return 1;
+}
+
+int
+pmiAddMetric(const char *name, pmID pmid, int type, pmInDom indom, int sem, pmUnits units)
+{
+ int m;
+ int item;
+ int cluster;
+ size_t size;
+ pmi_metric *mp;
+
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ if (valid_pmns_name(name) == 0)
+ return current->last_sts = PMI_ERR_BADMETRICNAME;
+
+ for (m = 0; m < current->nmetric; m++) {
+ if (strcmp(name, current->metric[m].name) == 0) {
+ /* duplicate metric name is not good */
+ return current->last_sts = PMI_ERR_DUPMETRICNAME;
+ }
+ if (pmid == current->metric[m].pmid) {
+ /* duplicate metric pmID is not good */
+ return current->last_sts = PMI_ERR_DUPMETRICID;
+ }
+ }
+
+ /*
+ * basic sanity check of metadata ... we do not check later so this
+ * needs to be robust
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_FLOAT:
+ case PM_TYPE_DOUBLE:
+ case PM_TYPE_STRING:
+ break;
+ default:
+ return current->last_sts = PMI_ERR_BADTYPE;
+ }
+ switch (sem) {
+ case PM_SEM_INSTANT:
+ case PM_SEM_COUNTER:
+ case PM_SEM_DISCRETE:
+ break;
+ default:
+ return current->last_sts = PMI_ERR_BADSEM;
+ }
+
+ current->nmetric++;
+ size = current->nmetric * sizeof(pmi_metric);
+ current->metric = (pmi_metric *)realloc(current->metric, size);
+ if (current->metric == NULL) {
+ __pmNoMem("pmiAddMetric: pmi_metric", size, PM_FATAL_ERR);
+ }
+ mp = &current->metric[current->nmetric-1];
+ if (pmid != PM_ID_NULL) {
+ mp->pmid = pmid;
+ } else {
+ /* choose a PMID on behalf of the caller - check boundaries first */
+ item = cluster = current->nmetric;
+ if (item >= (1<<22)) { /* enough room for unique item:cluster? */
+ current->nmetric--;
+ return current->last_sts = PMI_ERR_DUPMETRICID; /* wrap */
+ }
+ item %= (1<<10);
+ cluster >>= 10;
+ mp->pmid = pmid_build(PMI_DOMAIN, cluster, item);
+ }
+ mp->name = strdup(name);
+ if (mp->name == NULL) {
+ __pmNoMem("pmiAddMetric: name", strlen(name)+1, PM_FATAL_ERR);
+ }
+ mp->desc.pmid = mp->pmid;
+ mp->desc.type = type;
+ mp->desc.indom = indom;
+ mp->desc.sem = sem;
+ mp->desc.units = units;
+ mp->meta_done = 0;
+
+ return current->last_sts = 0;
+}
+
+int
+pmiAddInstance(pmInDom indom, const char *instance, int inst)
+{
+ pmi_indom *idp;
+ const char *p;
+ char *np;
+ int spaced;
+ int i;
+ int j;
+
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ for (i = 0; i < current->nindom; i++) {
+ if (current->indom[i].indom == indom)
+ break;
+ }
+ if (i == current->nindom) {
+ /* extend indom table */
+ current->nindom++;
+ current->indom = (pmi_indom *)realloc(current->indom, current->nindom*sizeof(pmi_indom));
+ if (current->indom == NULL) {
+ __pmNoMem("pmiAddInstance: pmi_indom", current->nindom*sizeof(pmi_indom), PM_FATAL_ERR);
+ }
+ current->indom[i].indom = indom;
+ current->indom[i].ninstance = 0;
+ current->indom[i].name = NULL;
+ current->indom[i].inst = NULL;
+ current->indom[i].namebuflen = 0;
+ current->indom[i].namebuf = NULL;
+ }
+ idp = &current->indom[i];
+ /*
+ * duplicate external instance identifier would be bad, but need
+ * to honour unique to first space rule ...
+ * duplicate instance internal identifier is also not allowed
+ */
+ for (p = instance; *p && *p != ' '; p++)
+ ;
+ spaced = (*p == ' ') ? p - instance + 1: 0; /* +1 => *must* compare the space too */
+ for (j = 0; j < idp->ninstance; j++) {
+ if (spaced) {
+ if (strncmp(instance, idp->name[j], spaced) == 0)
+ return current->last_sts = PMI_ERR_DUPINSTNAME;
+ } else {
+ if (strcmp(instance, idp->name[j]) == 0)
+ return current->last_sts = PMI_ERR_DUPINSTNAME;
+ }
+ if (inst == idp->inst[j]) {
+ return current->last_sts = PMI_ERR_DUPINSTID;
+ }
+ }
+ /* add instance marks whole indom as needing to be written */
+ idp->meta_done = 0;
+ idp->ninstance++;
+ idp->name = (char **)realloc(idp->name, idp->ninstance*sizeof(char *));
+ if (idp->name == NULL) {
+ __pmNoMem("pmiAddInstance: name", idp->ninstance*sizeof(char *), PM_FATAL_ERR);
+ }
+ idp->inst = (int *)realloc(idp->inst, idp->ninstance*sizeof(int));
+ if (idp->inst == NULL) {
+ __pmNoMem("pmiAddInstance: inst", idp->ninstance*sizeof(int), PM_FATAL_ERR);
+ }
+ idp->namebuf = (char *)realloc(idp->namebuf, idp->namebuflen+strlen(instance)+1);
+ if (idp->namebuf == NULL) {
+ __pmNoMem("pmiAddInstance: namebuf", idp->namebuflen+strlen(instance)+1, PM_FATAL_ERR);
+ }
+ strcpy(&idp->namebuf[idp->namebuflen], instance);
+ idp->namebuflen += strlen(instance)+1;
+ idp->inst[idp->ninstance-1] = inst;
+ /* in case namebuf moves, need to redo name[] pointers */
+ np = idp->namebuf;
+ for (j = 0; j < current->indom[i].ninstance; j++) {
+ idp->name[j] = np;
+ np += strlen(np)+1;
+ }
+
+ return current->last_sts = 0;
+}
+
+static int
+make_handle(const char *name, const char *instance, pmi_handle *hp)
+{
+ int m;
+ int i;
+ int j;
+ int spaced;
+ const char *p;
+ pmi_indom *idp;
+
+ if (instance != NULL && instance[0] == '\0')
+ /* map "" to NULL to help Perl callers */
+ instance = NULL;
+
+ for (m = 0; m < current->nmetric; m++) {
+ if (strcmp(name, current->metric[m].name) == 0)
+ break;
+ }
+ if (m == current->nmetric)
+ return current->last_sts = PM_ERR_NAME;
+ hp->midx = m;
+
+ if (current->metric[hp->midx].desc.indom == PM_INDOM_NULL) {
+ if (instance != NULL) {
+ /* expect "instance" to be NULL */
+ return current->last_sts = PMI_ERR_INSTNOTNULL;
+ }
+ hp->inst = PM_IN_NULL;
+ }
+ else {
+ if (instance == NULL)
+ /* don't expect "instance" to be NULL */
+ return current->last_sts = PMI_ERR_INSTNULL;
+ for (i = 0; i < current->nindom; i++) {
+ if (current->metric[hp->midx].desc.indom == current->indom[i].indom)
+ break;
+ }
+ if (i == current->nindom)
+ return current->last_sts = PM_ERR_INDOM;
+ idp = &current->indom[i];
+
+ /* match to first space rule */
+
+ for (p = instance; *p && *p != ' '; p++)
+ ;
+ spaced = (*p == ' ') ? p - instance + 1: 0; /* +1 => *must* compare the space too */
+ for (j = 0; j < idp->ninstance; j++) {
+ if (spaced) {
+ if (strncmp(instance, idp->name[j], spaced) == 0)
+ break;
+ } else {
+ if (strcmp(instance, idp->name[j]) == 0)
+ break;
+ }
+ }
+ if (j == idp->ninstance)
+ return current->last_sts = PM_ERR_INST;
+ hp->inst = idp->inst[j];
+ }
+
+ return current->last_sts = 0;
+}
+
+int
+pmiPutValue(const char *name, const char *instance, const char *value)
+{
+ pmi_handle tmp;
+ int sts;
+
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ sts = make_handle(name, instance, &tmp);
+ if (sts != 0)
+ return current->last_sts = sts;
+
+ return current->last_sts = _pmi_stuff_value(current, &tmp, value);
+}
+
+int
+pmiGetHandle(const char *name, const char *instance)
+{
+ int sts;
+ pmi_handle tmp;
+ pmi_handle *hp;
+
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ sts = make_handle(name, instance, &tmp);
+ if (sts != 0)
+ return current->last_sts = sts;
+
+ current->nhandle++;
+ current->handle = (pmi_handle *)realloc(current->handle, current->nhandle*sizeof(pmi_handle));
+ if (current->handle == NULL) {
+ __pmNoMem("pmiGetHandle: pmi_handle", current->nhandle*sizeof(pmi_handle), PM_FATAL_ERR);
+ }
+ hp = &current->handle[current->nhandle-1];
+ hp->midx = tmp.midx;
+ hp->inst = tmp.inst;
+
+ return current->last_sts = current->nhandle;
+}
+
+int
+pmiPutValueHandle(int handle, const char *value)
+{
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (handle <= 0 || handle > current->nhandle)
+ return current->last_sts = PMI_ERR_BADHANDLE;
+
+ return current->last_sts = _pmi_stuff_value(current, &current->handle[handle-1], value);
+}
+
+int
+pmiWrite(int sec, int usec)
+{
+ int sts;
+
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (current->result == NULL)
+ return current->last_sts = PMI_ERR_NODATA;
+
+ if (sec < 0) {
+ __pmtimevalNow(&current->result->timestamp);
+ }
+ else {
+ current->result->timestamp.tv_sec = sec;
+ current->result->timestamp.tv_usec = usec;
+ }
+ if (current->result->timestamp.tv_sec < current->last_stamp.tv_sec ||
+ (current->result->timestamp.tv_sec == current->last_stamp.tv_sec &&
+ current->result->timestamp.tv_usec < current->last_stamp.tv_usec)) {
+ fprintf(stderr, "Fatal Error: timestamp ");
+ printstamp(stderr, &current->result->timestamp);
+ fprintf(stderr, " not greater than previous valid timestamp ");
+ printstamp(stderr, &current->last_stamp);
+ fputc('\n', stderr);
+ sts = PMI_ERR_BADTIMESTAMP;
+ }
+ else {
+ sts = _pmi_put_result(current, current->result);
+ current->last_stamp = current->result->timestamp;
+ }
+
+ pmFreeResult(current->result);
+ current->result = NULL;
+
+ return current->last_sts = sts;
+}
+
+int
+pmiPutResult(const pmResult *result)
+{
+ if (current == NULL)
+ return PM_ERR_NOCONTEXT;
+
+ return current->last_sts = _pmi_put_result(current, current->result);
+}
diff --git a/src/libpcp_import/src/private.h b/src/libpcp_import/src/private.h
new file mode 100644
index 0000000..735d35c
--- /dev/null
+++ b/src/libpcp_import/src/private.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef _PRIVATE_H
+#define _PRIVATE_H
+
+typedef struct {
+ char *name;
+ pmID pmid;
+ pmDesc desc;
+ int meta_done;
+} pmi_metric;
+
+typedef struct {
+ pmInDom indom;
+ int ninstance;
+ char **name; // list of external instance names
+ int *inst; // list of internal instance identifiers
+ int namebuflen; // names are packed in namebuf[] as
+ char *namebuf; // required by __pmLogPutInDom()
+ int meta_done;
+} pmi_indom;
+
+typedef struct {
+ int midx; // index into metric[]
+ int inst; // internal instance identifier
+} pmi_handle;
+
+typedef struct {
+ int state;
+ char *archive;
+ char *hostname;
+ char *timezone;
+ __pmLogCtl logctl;
+ pmResult *result;
+ int nmetric;
+ pmi_metric *metric;
+ int nindom;
+ pmi_indom *indom;
+ int nhandle;
+ pmi_handle *handle;
+ int last_sts;
+ struct timeval last_stamp;
+} pmi_context;
+
+#define CONTEXT_START 1
+#define CONTEXT_ACTIVE 2
+#define CONTEXT_END 3
+
+#if defined(__GNUC__) && (__GNUC__ >= 4)
+# define _PMI_HIDDEN __attribute__ ((visibility ("hidden")))
+#else
+# define _PMI_HIDDEN
+#endif
+
+extern int _pmi_stuff_value(pmi_context *, pmi_handle *, const char *) _PMI_HIDDEN;
+extern int _pmi_put_result(pmi_context *, pmResult *) _PMI_HIDDEN;
+extern int _pmi_end(pmi_context *) _PMI_HIDDEN;
+
+#endif /* _PRIVATE_H */
diff --git a/src/libpcp_import/src/stuff.c b/src/libpcp_import/src/stuff.c
new file mode 100644
index 0000000..bfb4935
--- /dev/null
+++ b/src/libpcp_import/src/stuff.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "import.h"
+#include "private.h"
+
+int
+_pmi_stuff_value(pmi_context *current, pmi_handle *hp, const char *value)
+{
+ pmResult *rp;
+ int i;
+ pmID pmid;
+ pmValueSet *vsp;
+ pmValue *vp;
+ pmi_metric *mp;
+ char *end;
+ int dsize;
+ void *data;
+ __int64_t ll;
+ __uint64_t ull;
+ float f;
+ double d;
+
+ mp = &current->metric[hp->midx];
+
+ if (current->result == NULL) {
+ /* first time */
+ current->result = (pmResult *)malloc(sizeof(pmResult));
+ if (current->result == NULL) {
+ __pmNoMem("_pmi_stuff_value: result malloc:", sizeof(pmResult), PM_FATAL_ERR);
+ }
+ current->result->numpmid = 0;
+ current->result->timestamp.tv_sec = 0;
+ current->result->timestamp.tv_usec = 0;
+ }
+ rp = current->result;
+
+ pmid = current->metric[hp->midx].pmid;
+ for (i = 0; i < rp->numpmid; i++) {
+ if (pmid == rp->vset[i]->pmid) {
+ if (mp->desc.indom == PM_INDOM_NULL)
+ /* singular metric, cannot have more than one value */
+ return PMI_ERR_DUPVALUE;
+ break;
+ }
+ }
+ if (i == rp->numpmid) {
+ rp->numpmid++;
+ rp = current->result = (pmResult *)realloc(current->result, sizeof(pmResult) + (rp->numpmid - 1)*sizeof(pmValueSet *));
+ if (current->result == NULL) {
+ __pmNoMem("_pmi_stuff_value: result realloc:", sizeof(pmResult) + (rp->numpmid - 1)*sizeof(pmValueSet *), PM_FATAL_ERR);
+ }
+ rp->vset[rp->numpmid-1] = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (rp->vset[rp->numpmid-1] == NULL) {
+ __pmNoMem("_pmi_stuff_value: vset alloc:", sizeof(pmValueSet), PM_FATAL_ERR);
+ }
+ vsp = rp->vset[rp->numpmid-1];
+ vsp->pmid = pmid;
+ vsp->numval = 1;
+ }
+ else {
+ int j;
+ for (j = 0; j < rp->vset[i]->numval; j++) {
+ if (rp->vset[i]->vlist[j].inst == hp->inst)
+ /* each metric-instance can appear at most once per pmResult */
+ return PMI_ERR_DUPVALUE;
+ }
+ rp->vset[i]->numval++;
+ vsp = rp->vset[i] = (pmValueSet *)realloc(rp->vset[i], sizeof(pmValueSet) + (rp->vset[i]->numval-1)*sizeof(pmValue));
+ if (rp->vset[i] == NULL) {
+ __pmNoMem("_pmi_stuff_value: vset realloc:", sizeof(pmValueSet) + (rp->vset[i]->numval-1)*sizeof(pmValue), PM_FATAL_ERR);
+ }
+ }
+ vp = &vsp->vlist[vsp->numval-1];
+ vp->inst = hp->inst;
+ dsize = -1;
+ switch (mp->desc.type) {
+ case PM_TYPE_32:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_INSITU;
+ vp->value.lval = strtol(value, &end, 10);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_U32:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_INSITU;
+ vp->value.lval = strtoul(value, &end, 10);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ break;
+
+ case PM_TYPE_64:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_DPTR;
+ ll = strtoll(value, &end, 10);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ dsize = sizeof(ll);
+ data = (void *)&ll;
+ break;
+
+ case PM_TYPE_U64:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_DPTR;
+ ull = strtoull(value, &end, 10);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ dsize = sizeof(ull);
+ data = (void *)&ull;
+ break;
+
+ case PM_TYPE_FLOAT:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_DPTR;
+ f = strtof(value, &end);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ dsize = sizeof(f);
+ data = (void *)&f;
+ break;
+
+ case PM_TYPE_DOUBLE:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_DPTR;
+ d = strtod(value, &end);
+ if (*end != '\0') {
+ vsp->numval = PM_ERR_CONV;
+ return PM_ERR_CONV;
+ }
+ dsize = sizeof(d);
+ data = (void *)&d;
+ break;
+
+ case PM_TYPE_STRING:
+ if (vsp->numval == 1) vsp->valfmt = PM_VAL_DPTR;
+ dsize = strlen(value)+1;
+ data = (void *)value;
+ break;
+
+ default:
+ vsp->numval = PM_ERR_TYPE;
+ return PM_ERR_TYPE;
+ }
+
+ if (dsize != -1) {
+ /* logic copied from stuffvalue.c in libpcp */
+ int need = dsize + PM_VAL_HDR_SIZE;
+
+ vp->value.pval = (pmValueBlock *)malloc(need < sizeof(pmValueBlock) ? sizeof(pmValueBlock) : need);
+ if (vp->value.pval == NULL) {
+ __pmNoMem("_pmi_stuff_value: pmValueBlock:", need < sizeof(pmValueBlock) ? sizeof(pmValueBlock) : need, PM_FATAL_ERR);
+ }
+ vp->value.pval->vlen = (int)need;
+ vp->value.pval->vtype = mp->desc.type;
+ memcpy((void *)vp->value.pval->vbuf, data, dsize);
+ }
+
+ return 0;
+}
diff --git a/src/libpcp_mmv/GNUmakefile b/src/libpcp_mmv/GNUmakefile
new file mode 100644
index 0000000..ef4485e
--- /dev/null
+++ b/src/libpcp_mmv/GNUmakefile
@@ -0,0 +1,35 @@
+#
+# Copyright (C) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+
+#
+# libpcp_mmv.so - performance data export through shared memory
+# (used in conjunction with the mmv pmda)
+#
+
+TOPDIR = ../..
+
+include $(TOPDIR)/src/include/builddefs
+
+BASE = libpcp_mmv.a
+
+SUBDIRS = src
+
+default install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/libpcp_mmv/src/GNUmakefile b/src/libpcp_mmv/src/GNUmakefile
new file mode 100644
index 0000000..a60029c
--- /dev/null
+++ b/src/libpcp_mmv/src/GNUmakefile
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = mmv_stats.c
+VERSION_SCRIPT = exports
+LSRCFILES = $(VERSION_SCRIPT)
+
+STATICLIBTARGET = libpcp_mmv.a
+DSOVERSION = 1
+LIBTARGET = libpcp_mmv.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp_mmv.$(DSOSUFFIX)
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp_mmv.$(DSOVERSION).$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+LIBTARGET = libpcp_mmv.$(DSOSUFFIX)
+SYMTARGET =
+STATICLIBTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LCFLAGS = -I.
+LLDLIBS = -lpcp
+LDIRT = $(SYMTARGET)
+
+default: $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ for tt in $(SYMTARGET); do \
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$$tt || exit 1; \
+ done
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+ifneq ($(SYMTARGET),)
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $@
+endif
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp_mmv/src/exports b/src/libpcp_mmv/src/exports
new file mode 100644
index 0000000..00b4fc6
--- /dev/null
+++ b/src/libpcp_mmv/src/exports
@@ -0,0 +1,26 @@
+PCP_MMV_1.0 {
+ global:
+ mmv_stats_init;
+ mmv_stats_stop;
+
+ mmv_inc_value;
+ mmv_set_value;
+ mmv_set_string;
+
+ mmv_lookup_value_desc;
+
+ mmv_stats_add;
+ mmv_stats_add_fallback;
+
+ mmv_stats_inc;
+ mmv_stats_inc_fallback;
+
+ mmv_stats_interval_end;
+ mmv_stats_interval_start;
+
+ mmv_stats_set;
+ mmv_stats_set_string;
+ mmv_stats_set_strlen;
+
+ local: *;
+};
diff --git a/src/libpcp_mmv/src/mmv_stats.c b/src/libpcp_mmv/src/mmv_stats.c
new file mode 100644
index 0000000..a66d06f
--- /dev/null
+++ b/src/libpcp_mmv/src/mmv_stats.c
@@ -0,0 +1,628 @@
+/*
+ * Memory Mapped Values PMDA Client API
+ *
+ * Copyright (C) 2001,2009 Silicon Graphics, Inc. All rights reserved.
+ * Copyright (C) 2009 Aconex. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ */
+#include "pmapi.h"
+#include <sys/stat.h>
+#include "mmv_stats.h"
+#include "mmv_dev.h"
+#include "impl.h"
+
+static void
+mmv_stats_path(const char *fname, char *fullpath, size_t pathlen)
+{
+ int sep = __pmPathSeparator();
+
+ snprintf(fullpath, pathlen, "%s%c" "mmv" "%c%s",
+ pmGetConfig("PCP_TMP_DIR"), sep, sep, fname);
+}
+
+static void *
+mmv_mapping_init(const char *fname, size_t size)
+{
+ char path[MAXPATHLEN];
+ void *addr = NULL;
+ mode_t cur_umask;
+ int fd, sts = 0;
+
+ /* unlink+creat will cause the pmda to reload on next fetch */
+ mmv_stats_path(fname, path, sizeof(path));
+ unlink(path);
+ cur_umask = umask(S_IWGRP | S_IWOTH);
+ fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0644);
+ umask(cur_umask);
+ if (fd < 0)
+ return NULL;
+
+ if (ftruncate(fd, size) != -1)
+ addr = __pmMemoryMap(fd, size, 1);
+ else
+ sts = oserror();
+
+ close(fd);
+ setoserror(sts);
+ return addr;
+}
+
+static int
+mmv_singular(__int32_t indom)
+{
+ return (indom == 0 || indom == PM_INDOM_NULL);
+}
+
+static mmv_disk_indom_t *
+mmv_lookup_disk_indom(__int32_t indom, mmv_disk_indom_t *in, int nindoms)
+{
+ int i;
+
+ for (i = 0; i < nindoms; i++)
+ if (in[i].serial == indom)
+ return &in[i];
+ return NULL;
+}
+
+static const mmv_indom_t *
+mmv_lookup_indom(__int32_t indom, const mmv_indom_t *in, int nindoms)
+{
+ int i;
+
+ for (i = 0; i < nindoms; i++)
+ if (in[i].serial == indom)
+ return &in[i];
+ return NULL;
+}
+
+static __uint64_t
+mmv_generation(void)
+{
+ struct timeval now;
+ __uint32_t gen1, gen2;
+
+ __pmtimevalNow(&now);
+ gen1 = now.tv_sec;
+ gen2 = now.tv_usec;
+ return (((__uint64_t)gen1 << 32) | (__uint64_t)gen2);
+}
+
+void *
+mmv_stats_init(const char *fname,
+ int cluster, mmv_stats_flags_t fl,
+ const mmv_metric_t *st, int nmetrics,
+ const mmv_indom_t *in, int nindoms)
+{
+ mmv_disk_instance_t *inlist;
+ mmv_disk_indom_t *domlist;
+ mmv_disk_metric_t *mlist;
+ mmv_disk_string_t *slist;
+ mmv_disk_value_t *vlist;
+ mmv_disk_header_t *hdr;
+ mmv_disk_toc_t *toc;
+ __uint64_t indoms_offset; /* anchor start of indoms section */
+ __uint64_t instances_offset; /* anchor start of instances section */
+ __uint64_t metrics_offset; /* anchor start of metrics section */
+ __uint64_t values_offset; /* anchor start of values section */
+ __uint64_t strings_offset; /* anchor start of any/all strings */
+ void *addr;
+ size_t size;
+ int i, j, k, tocidx, stridx;
+ int ninstances = 0;
+ int nstrings = 0;
+ int nvalues = 0;
+
+ for (i = 0; i < nindoms; i++) {
+ if (mmv_singular(in[i].serial)) {
+ setoserror(ESRCH);
+ return NULL;
+ }
+ ninstances += in[i].count;
+ if (in[i].shorttext)
+ nstrings++;
+ if (in[i].helptext)
+ nstrings++;
+ }
+
+ for (i = 0; i < nmetrics; i++) {
+ if ((st[i].type < MMV_TYPE_NOSUPPORT) ||
+ (st[i].type > MMV_TYPE_ELAPSED) || strlen(st[i].name) == 0) {
+ setoserror(EINVAL);
+ return NULL;
+ }
+
+ if (st[i].helptext)
+ nstrings++;
+ if (st[i].shorttext)
+ nstrings++;
+
+ if (!mmv_singular(st[i].indom)) {
+ const mmv_indom_t * mi;
+
+ if ((mi = mmv_lookup_indom(st[i].indom, in, nindoms)) == NULL) {
+ setoserror(ESRCH);
+ return NULL;
+ }
+ if (st[i].type == MMV_TYPE_STRING)
+ nstrings += mi->count;
+ nvalues += mi->count;
+ } else {
+ if (st[i].type == MMV_TYPE_STRING)
+ nstrings++;
+ nvalues++;
+ }
+ }
+
+ /* TOC follows header, with enough entries to hold */
+ /* indoms, instances, metrics, values, and strings */
+ size = sizeof(mmv_disk_toc_t) * 2;
+ if (nindoms)
+ size += sizeof(mmv_disk_toc_t) * 2;
+ if (nstrings)
+ size += sizeof(mmv_disk_toc_t) * 1;
+ indoms_offset = sizeof(mmv_disk_header_t) + size;
+
+ /* Following the indom definitions are the actual instances */
+ size = sizeof(mmv_disk_indom_t) * nindoms;
+ instances_offset = indoms_offset + size;
+
+ /* Following the instances are the metric definitions */
+ for (size = 0, i = 0; i < nindoms; i++)
+ size += in[i].count * sizeof(mmv_disk_instance_t);
+ metrics_offset = instances_offset + size;
+
+ /* Following metric definitions are the actual values */
+ size = nmetrics * sizeof(mmv_disk_metric_t);
+ values_offset = metrics_offset + size;
+
+ /* Following the values are the string values and/or help text */
+ size = nvalues * sizeof(mmv_disk_value_t);
+ strings_offset = values_offset + size;
+
+ /* End of file follows all of the actual strings */
+ size = strings_offset + nstrings * sizeof(mmv_disk_string_t);
+
+ if ((addr = mmv_mapping_init(fname, size)) == NULL)
+ return NULL;
+
+ /*
+ * We unconditionally clobber the stats file on each restart;
+ * easier this way and the clients deal with counter wraps.
+ * We also write from the start going forward through the file
+ * with an occassional step back to (re)write the TOC page -
+ * this gives the kernel a decent shot at laying out the file
+ * contiguously ondisk (hopefully we dont do much disk I/O on
+ * this file, but it will be written to disk at times so lets
+ * try to minimise that I/O traffic, eh?).
+ */
+
+ hdr = (mmv_disk_header_t *) addr;
+ strncpy(hdr->magic, "MMV", 4);
+ hdr->version = MMV_VERSION;
+ hdr->g1 = mmv_generation();
+ hdr->g2 = 0;
+ hdr->tocs = 2;
+ if (nindoms)
+ hdr->tocs += 2;
+ if (nstrings)
+ hdr->tocs += 1;
+ hdr->flags = fl;
+ hdr->cluster = cluster;
+ hdr->process = (__int32_t)getpid();
+
+ toc = (mmv_disk_toc_t *)((char *)addr + sizeof(mmv_disk_header_t));
+ tocidx = 0;
+
+ if (nindoms) {
+ toc[tocidx].type = MMV_TOC_INDOMS;
+ toc[tocidx].count = nindoms;
+ toc[tocidx].offset = indoms_offset;
+ tocidx++;
+ toc[tocidx].type = MMV_TOC_INSTANCES;
+ toc[tocidx].count = ninstances;
+ toc[tocidx].offset = instances_offset;
+ tocidx++;
+ }
+ toc[tocidx].type = MMV_TOC_METRICS;
+ toc[tocidx].count = nmetrics;
+ toc[tocidx].offset = metrics_offset;
+ tocidx++;
+ toc[tocidx].type = MMV_TOC_VALUES;
+ toc[tocidx].count = nvalues;
+ toc[tocidx].offset = values_offset;
+ tocidx++;
+ if (nstrings) {
+ toc[tocidx].type = MMV_TOC_STRINGS;
+ toc[tocidx].count = nstrings;
+ toc[tocidx].offset = strings_offset;
+ tocidx++;
+ }
+
+ /* Indom section */
+ domlist = (mmv_disk_indom_t *)((char *)addr + indoms_offset);
+ for (i = 0; i < nindoms; i++) {
+ domlist[i].serial = in[i].serial;
+ domlist[i].count = in[i].count;
+ domlist[i].offset = 0; /* filled in below */
+ domlist[i].shorttext = 0; /* filled in later */
+ domlist[i].helptext = 0; /* filled in later */
+ }
+
+ /* Instances section */
+ inlist = (mmv_disk_instance_t *)((char *)addr + instances_offset);
+ for (i = 0; i < nindoms; i++) {
+ mmv_instances_t *insts = in[i].instances;
+ domlist[i].offset = ((char *)inlist - (char *)addr);
+ for (j = 0; j < domlist[i].count; j++) {
+ inlist->indom = indoms_offset + (i * sizeof(mmv_disk_indom_t));
+ inlist->padding = 0;
+ inlist->internal = insts[j].internal;
+ strncpy(inlist->external, insts[j].external, MMV_NAMEMAX);
+ inlist->external[MMV_NAMEMAX-1] = '\0';
+ inlist++;
+ }
+ }
+
+ /* Metrics section */
+ mlist = (mmv_disk_metric_t *)((char *)addr + metrics_offset);
+ for (i = 0; i < nmetrics; i++) {
+ strncpy(mlist[i].name, st[i].name, MMV_NAMEMAX);
+ mlist[i].name[MMV_NAMEMAX-1] = '\0';
+ mlist[i].item = st[i].item;
+ mlist[i].type = st[i].type;
+ mlist[i].indom = st[i].indom;
+ mlist[i].dimension = st[i].dimension;
+ mlist[i].semantics = st[i].semantics;
+ mlist[i].shorttext = 0; /* filled in later */
+ mlist[i].helptext = 0; /* filled in later */
+ mlist[i].padding = 0;
+ }
+
+ /* Values section */
+ vlist = (mmv_disk_value_t *)((char *)addr + values_offset);
+ for (i = j = 0; i < nmetrics; i++) {
+ __uint64_t off = metrics_offset + i * sizeof(mmv_disk_metric_t);
+
+ if (mmv_singular(st[i].indom)) {
+ memset(&vlist[j], 0, sizeof(mmv_disk_value_t));
+ vlist[j].metric = off;
+ j++;
+ } else {
+ __uint64_t ioff;
+ mmv_disk_indom_t *indom;
+
+ indom = mmv_lookup_disk_indom(st[i].indom, domlist, nindoms);
+ for (k = 0; k < indom->count; k++) {
+ ioff = indom->offset + sizeof(mmv_disk_instance_t) * k;
+ memset(&vlist[j], 0, sizeof(mmv_disk_value_t));
+ vlist[j].metric = off;
+ vlist[j].instance = ioff;
+ j++;
+ }
+ }
+ }
+
+ /* Strings section */
+ slist = (mmv_disk_string_t *)((char *)addr + strings_offset);
+ stridx = 0;
+
+ /*
+ * 3 phases: all string values, any metric help, any indom help.
+ */
+ for (i = 0; i < nvalues; i++) {
+ mmv_disk_metric_t * metric = (mmv_disk_metric_t *)
+ ((char *)(addr + vlist[i].metric));
+ if (metric->type == MMV_TYPE_STRING) {
+ vlist[i].extra = strings_offset +
+ (stridx * sizeof(mmv_disk_string_t));
+ stridx++;
+ }
+ }
+ for (i = 0; i < nmetrics; i++) {
+ if (st[i].shorttext) {
+ mlist[i].shorttext = strings_offset +
+ (stridx * sizeof(mmv_disk_string_t));
+ strncpy(slist[stridx].payload, st[i].shorttext, MMV_STRINGMAX);
+ slist[stridx].payload[MMV_STRINGMAX-1] = '\0';
+ stridx++;
+ }
+ if (st[i].helptext) {
+ mlist[i].helptext = strings_offset +
+ (stridx * sizeof(mmv_disk_string_t));
+ strncpy(slist[stridx].payload, st[i].helptext, MMV_STRINGMAX);
+ slist[stridx].payload[MMV_STRINGMAX-1] = '\0';
+ stridx++;
+ }
+ }
+ for (i = 0; i < nindoms; i++) {
+ if (in[i].shorttext) {
+ domlist[i].shorttext = strings_offset +
+ (stridx * sizeof(mmv_disk_string_t));
+ strncpy(slist[stridx].payload, in[i].shorttext, MMV_STRINGMAX);
+ slist[stridx].payload[MMV_STRINGMAX-1] = '\0';
+ stridx++;
+ }
+ if (in[i].helptext) {
+ domlist[i].helptext = strings_offset +
+ (stridx * sizeof(mmv_disk_string_t));
+ strncpy(slist[stridx].payload, in[i].helptext, MMV_STRINGMAX);
+ slist[stridx].payload[MMV_STRINGMAX-1] = '\0';
+ stridx++;
+ }
+ }
+
+ /* Complete - unlock the header, PMDA can read now */
+ hdr->g2 = hdr->g1;
+
+ return addr;
+}
+
+void
+mmv_stats_stop(const char *fname, void *addr)
+{
+ mmv_disk_header_t *hdr = (mmv_disk_header_t *)addr;
+ char path[MAXPATHLEN];
+ struct stat sbuf;
+
+ mmv_stats_path(fname, path, sizeof(path));
+ if (stat(path, &sbuf) < 0)
+ sbuf.st_size = (size_t)-1;
+ else if (hdr->flags & MMV_FLAG_PROCESS)
+ unlink(path);
+ __pmMemoryUnmap(addr, sbuf.st_size);
+}
+
+pmAtomValue *
+mmv_lookup_value_desc(void *addr, const char *metric, const char *inst)
+{
+ if (addr != NULL && metric != NULL) {
+ int i;
+ mmv_disk_header_t *hdr = (mmv_disk_header_t *)addr;
+ mmv_disk_toc_t *toc = (mmv_disk_toc_t *)
+ ((char *)addr + sizeof(mmv_disk_header_t));
+
+ for (i = 0; i < hdr->tocs; i++) {
+ if (toc[i].type == MMV_TOC_VALUES) {
+ int j;
+ mmv_disk_value_t *v = (mmv_disk_value_t *)
+ ((char *)addr + toc[i].offset);
+
+ for (j = 0; j < toc[i].count; j++) {
+ mmv_disk_metric_t *m = (mmv_disk_metric_t *)
+ ((char *)addr + v[j].metric);
+ if (strcmp(m->name, metric) == 0) {
+ if (mmv_singular(m->indom)) { /* Singular metric */
+ return &v[j].value;
+ } else {
+ if (inst == NULL) {
+ /* Metric has multiple instances, but
+ * we don't know which one to return,
+ * so return an error
+ */
+ return NULL;
+ } else {
+ mmv_disk_instance_t * in =
+ (mmv_disk_instance_t *)
+ ((char *)addr + v[j].instance);
+ if (strcmp(in->external, inst) == 0)
+ return &v[j].value;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void
+mmv_inc_value(void *addr, pmAtomValue *av, double inc)
+{
+ if (av != NULL && addr != NULL) {
+ mmv_disk_value_t * v = (mmv_disk_value_t *) av;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + v->metric);
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ v->value.l += (__int32_t)inc;
+ break;
+ case MMV_TYPE_U32:
+ v->value.ul += (__uint32_t)inc;
+ break;
+ case MMV_TYPE_I64:
+ v->value.ll += (__int64_t)inc;
+ break;
+ case MMV_TYPE_U64:
+ v->value.ull += (__uint64_t)inc;
+ break;
+ case MMV_TYPE_FLOAT:
+ v->value.f += (float)inc;
+ break;
+ case MMV_TYPE_DOUBLE:
+ v->value.d += inc;
+ break;
+ case MMV_TYPE_ELAPSED:
+ if (inc < 0)
+ v->extra = (__int64_t)inc;
+ else {
+ v->value.ll += v->extra + (__int64_t)inc;
+ v->extra = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+mmv_set_value(void *addr, pmAtomValue *av, double val)
+{
+ if (av != NULL && addr != NULL) {
+ mmv_disk_value_t * v = (mmv_disk_value_t *) av;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + v->metric);
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ v->value.l = (__int32_t)val;
+ break;
+ case MMV_TYPE_U32:
+ v->value.ul = (__uint32_t)val;
+ break;
+ case MMV_TYPE_I64:
+ v->value.ll = (__int64_t)val;
+ break;
+ case MMV_TYPE_U64:
+ v->value.ull = (__uint64_t)val;
+ break;
+ case MMV_TYPE_FLOAT:
+ v->value.f = (float)val;
+ break;
+ case MMV_TYPE_DOUBLE:
+ v->value.d = val;
+ break;
+ case MMV_TYPE_ELAPSED:
+ v->value.ll = (__int64_t)val;
+ v->extra = 0;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+mmv_set_string(void *addr, pmAtomValue *av, const char *string, int size)
+{
+ if (av != NULL && addr != NULL && string != NULL) {
+ mmv_disk_value_t * v = (mmv_disk_value_t *) av;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + v->metric);
+
+ if (m->type == MMV_TYPE_STRING &&
+ (size >= 0 && size < MMV_STRINGMAX - 1)) {
+ __uint64_t soffset = v->extra;
+ mmv_disk_string_t * s;
+
+ s = (mmv_disk_string_t *)((char *)addr + soffset);
+ strncpy(s->payload, string, size);
+ s->payload[size] = '\0';
+ v->value.l = size;
+ }
+ }
+}
+
+/*
+ * Simple wrapper routines
+ */
+
+void
+mmv_stats_add(void *addr,
+ const char *metric, const char *instance, double count)
+{
+ if (addr) {
+ pmAtomValue * mmv_metric;
+ mmv_metric = mmv_lookup_value_desc(addr, metric, instance);
+ if (mmv_metric)
+ mmv_inc_value(addr, mmv_metric, count);
+ }
+}
+
+void
+mmv_stats_inc(void *addr, const char *metric, const char *instance)
+{
+ mmv_stats_add(addr, metric, instance, 1);
+}
+
+void
+mmv_stats_set(void *addr,
+ const char *metric, const char *instance, double value)
+{
+ if (addr) {
+ pmAtomValue * mmv_metric;
+ mmv_metric = mmv_lookup_value_desc(addr, metric, instance);
+ if (mmv_metric)
+ mmv_set_value(addr, mmv_metric, value);
+ }
+}
+
+void
+mmv_stats_add_fallback(void *addr, const char *metric,
+ const char *instance, const char *instance2, double count)
+{
+ if (addr) {
+ pmAtomValue * mmv_metric;
+ mmv_metric = mmv_lookup_value_desc(addr, metric, instance);
+ if (mmv_metric == NULL)
+ mmv_metric = mmv_lookup_value_desc(addr,metric,instance2);
+ if (mmv_metric)
+ mmv_inc_value(addr, mmv_metric, count);
+ }
+}
+
+void
+mmv_stats_inc_fallback(void *addr, const char *metric,
+ const char *instance, const char *instance2)
+{
+ mmv_stats_add_fallback(addr, metric, instance, instance2, 1);
+}
+
+pmAtomValue *
+mmv_stats_interval_start(void *addr, pmAtomValue *value,
+ const char *metric, const char *instance)
+{
+ if (addr) {
+ if (value == NULL)
+ value = mmv_lookup_value_desc(addr, metric, instance);
+ if (value) {
+ struct timeval tv;
+ __pmtimevalNow(&tv);
+ mmv_inc_value(addr, value, -(tv.tv_sec*1e6 + tv.tv_usec));
+ }
+ }
+ return value;
+}
+
+void
+mmv_stats_interval_end(void *addr, pmAtomValue *value)
+{
+ if (value && addr) {
+ struct timeval tv;
+ __pmtimevalNow(&tv);
+ mmv_inc_value(addr, value, (tv.tv_sec*1e6 + tv.tv_usec));
+ }
+}
+
+void
+mmv_stats_set_string(void *addr, const char *metric,
+ const char *instance, const char *string)
+{
+ if (addr) {
+ size_t len = strlen(string);
+ pmAtomValue *mmv_metric;
+ mmv_metric = mmv_lookup_value_desc(addr, metric, instance);
+ mmv_set_string(addr, mmv_metric, string, len);
+ }
+}
+
+void
+mmv_stats_set_strlen(void *addr, const char *metric,
+ const char *instance, const char *string, size_t len)
+{
+ if (addr) {
+ pmAtomValue *mmv_metric;
+ mmv_metric = mmv_lookup_value_desc(addr, metric, instance);
+ mmv_set_string(addr, mmv_metric, string, len);
+ }
+}
diff --git a/src/libpcp_pmcd/GNUlocaldefs.32 b/src/libpcp_pmcd/GNUlocaldefs.32
new file mode 100644
index 0000000..38bc43e
--- /dev/null
+++ b/src/libpcp_pmcd/GNUlocaldefs.32
@@ -0,0 +1,4 @@
+LCFLAGS += -m32 -march=i386
+LLDFLAGS += -m32 -march=i386
+PCP_LIB_DIR=$(PCP_LIB32_DIR)
+LIBPCP_ABIDIR=32
diff --git a/src/libpcp_pmcd/GNUmakefile b/src/libpcp_pmcd/GNUmakefile
new file mode 100644
index 0000000..936948f
--- /dev/null
+++ b/src/libpcp_pmcd/GNUmakefile
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+TOPDIR = ../..
+
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = GNUlocaldefs.32
+
+SUBDIRS = src $(PCP_ALTLIBS)
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
diff --git a/src/libpcp_pmcd/src/GNUmakefile b/src/libpcp_pmcd/src/GNUmakefile
new file mode 100644
index 0000000..1242269
--- /dev/null
+++ b/src/libpcp_pmcd/src/GNUmakefile
@@ -0,0 +1,88 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = data.c trace.c client.c
+LSRCFILES = pmcd.stp.in probes.d
+
+LCFLAGS = -I$(TOPDIR)/src/pmcd/src -I$(TOPDIR)/src/libpcp/src -DPMCD_INTERNAL
+LLDLIBS = -lpcp
+
+ifeq "$(ENABLE_SECURE)" "true"
+LCFLAGS += $(NSSCFLAGS) $(NSPRCFLAGS)
+endif
+
+ifeq "$(ENABLE_PROBES)" "true"
+ifeq "$(TARGET_OS)" "linux"
+OBJECTS += probes.o
+endif
+LDIRT += probes.h pmcd.stp
+endif
+
+ifeq "$(TARGET_OS)" "mingw"
+LIBTARGET = libpcp_pmcd.$(DSOSUFFIX)
+LDIRT += libpcp_pmcd.a
+else
+STATICLIBTARGET = libpcp_pmcd.a
+LDIRT += libpcp_pmcd.$(DSOSUFFIX)
+endif
+
+default : $(LIBTARGET) $(STATICLIBTARGET)
+
+# Static probing for Linux, Mac OS X and Solaris.
+ifeq "$(ENABLE_PROBES)" "true"
+ifneq "$(findstring $(TARGET_OS),linux darwin freebsd)" ""
+trace.o: probes.h
+probes.h: probes.d pmcd.stp
+ $(DTRACE) -h -s $< -o $@
+probes.o: probes.d
+ $(DTRACE) -G -s $< -o $@
+endif
+ifeq "$(TARGET_OS)" "solaris"
+$(STATICLIBTARGET): rewrite
+trace.o: probes.h
+probes.h: probes.d
+ $(DTRACE) -h -s $< -o $@
+rewrite: trace.o
+ $(DTRACE) -G -s probes.d trace.o
+ touch rewrite
+LDIRT += rewrite
+endif
+endif
+
+pmcd.stp : pmcd.stp.in
+ $(SED) -e 's;@path@;'$(PCP_BINADM_DIR)/pmcd';' $< > $@
+
+include $(BUILDRULES)
+
+install : default
+ifeq "$(TARGET_OS)" "mingw"
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+
+ifeq "$(ENABLE_PROBES)" "true"
+ifeq "$(TARGET_OS)" "linux"
+ $(INSTALL) -m 755 -d $(PCP_SHARE_DIR)/../systemtap/tapset
+ $(INSTALL) -m 444 pmcd.stp $(PCP_SHARE_DIR)/../systemtap/tapset/pmcd.stp
+endif
+endif
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_pmcd/src/client.c b/src/libpcp_pmcd/src/client.c
new file mode 100644
index 0000000..be941a2
--- /dev/null
+++ b/src/libpcp_pmcd/src/client.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmcd.h"
+
+PMCD_INTERN ClientInfo *client;
+PMCD_INTERN int nClients; /* Number in array, (not all in use) */
+PMCD_INTERN int this_client_id;
+
+void
+ShowClients(FILE *f)
+{
+ int i;
+ char *sbuf;
+ char *hostName;
+
+ fprintf(f, " fd client connection from ipc ver operations denied\n");
+ fprintf(f, " == ======================================== ======= =================\n");
+ for (i = 0; i < nClients; i++) {
+ if (client[i].status.connected == 0)
+ continue;
+
+ fprintf(f, " %3d ", client[i].fd);
+
+ hostName = __pmGetNameInfo(client[i].addr);
+ if (hostName == NULL) {
+ sbuf = __pmSockAddrToString(client[i].addr);
+ fprintf(f, "%s", sbuf);
+ free(sbuf);
+ } else {
+ fprintf(f, "%-40.40s", hostName);
+ free(hostName);
+ }
+ fprintf(f, " %7d", __pmVersionIPC(client[i].fd));
+
+ if (client[i].denyOps != 0) {
+ fprintf(f, " ");
+ if (client[i].denyOps & PMCD_OP_FETCH)
+ fprintf(f, "fetch ");
+ if (client[i].denyOps & PMCD_OP_STORE)
+ fprintf(f, "store ");
+ }
+
+ fputc('\n', f);
+ }
+ fputc('\n', f);
+}
diff --git a/src/libpcp_pmcd/src/data.c b/src/libpcp_pmcd/src/data.c
new file mode 100644
index 0000000..ca257b0
--- /dev/null
+++ b/src/libpcp_pmcd/src/data.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmcd.h"
+
+/*
+ * Global data shared by pmcd and the pmcd PMDA DSO must reside
+ * in a DSO as well, due to linkage oddities with Windows DLLs.
+ */
+
+PMCD_INTERN int pmcd_hi_openfds = -1; /* Highest open pmcd file descriptor */
+PMCD_INTERN int _pmcd_done; /* flag from pmcd pmda */
+PMCD_INTERN int _pmcd_timeout = 5; /* Timeout for hung agents */
+
+PMCD_INTERN int nAgents; /* Number of active agents */
+PMCD_INTERN AgentInfo *agent; /* Array of agent info structs */
+
+PMCD_INTERN char *_pmcd_hostname; /* Explicitly requested hostname */
+/*
+ * File descriptors are used as an internal index with the advent
+ * of NSPR in libpcp. We (may) need to first decode the index to
+ * an internal representation and lookup the real file descriptor.
+ * Note the use of on-stack fd overwrite, avoiding local variable.
+ */
+
+void
+pmcd_openfds_sethi(int fd)
+{
+ if ((fd = __pmFD(fd)) > pmcd_hi_openfds)
+ pmcd_hi_openfds = fd;
+}
diff --git a/src/libpcp_pmcd/src/pmcd.stp.in b/src/libpcp_pmcd/src/pmcd.stp.in
new file mode 100644
index 0000000..83864b3
--- /dev/null
+++ b/src/libpcp_pmcd/src/pmcd.stp.in
@@ -0,0 +1,304 @@
+// Performance CoPilot pmcd tapset
+// Copyright (C) 2013 Red Hat Inc.
+//
+// This file is part of SystemTap, and is free software. You can
+// redistribute it and/or modify it under the terms of the GNU General
+// Public License (GPL); either version 2, or (at your option) any
+// later version.
+
+
+global pmid_domain[2]
+global pmid_cluster[2]
+global pmid_item[2]
+global pmindom_domain[2]
+global pmindom_serial[2]
+global pdu_types[25]
+
+# from impl.h
+global PDU_ERROR = 0x7000
+global PDU_RESULT = 0x7001
+global PDU_PROFILE = 0x7002
+global PDU_FETCH = 0x7003
+global PDU_DESC_REQ = 0x7004
+global PDU_DESC = 0x7005
+global PDU_INSTANCE_REQ = 0x7006
+global PDU_INSTANCE = 0x7007
+global PDU_TEXT_REQ = 0x7008
+global PDU_TEXT = 0x7009
+global PDU_CONTROL_REQ = 0x700a
+global PDU_CREDS = 0x700c
+global PDU_PMNS_IDS = 0x700d
+global PDU_PMNS_NAMES = 0x700e
+global PDU_PMNS_CHILD = 0x700f
+global PDU_PMNS_TRAVERSE = 0x7010
+global PDU_AUTH = 0x7011
+global PDU_LOG_CONTROL = 0x8000
+global PDU_LOG_STATUS = 0x8001
+global PDU_LOG_REQUEST = 0x8002
+global DYNAMIC_PMID = 511
+global PM_INDOM_NULL = 0xffffffff
+global PM_ID_NULL = 0xffffffff
+
+# from pmcd.h
+global TR_ADD_CLIENT = 1
+global TR_DEL_CLIENT = 2
+global TR_ADD_AGENT = 3
+global TR_DEL_AGENT = 4
+global TR_EOF = 5
+global TR_XMIT_PDU = 7
+global TR_RECV_PDU = 8
+global TR_WRONG_PDU = 9
+global TR_XMIT_ERR = 10
+global TR_RECV_TIMEOUT = 11
+global TR_RECV_ERR = 12
+
+# Use the same naming scheme as trace.c::tracebuf
+# We sadly eschew stap 2.0+ feature to make $argN more symbolic:
+# @define t_type %( $arg1 %)
+# @define t_who %( $arg2 %)
+# @define t_p1 %( $arg3 %)
+# @define t_p2 %( $arg4 %)
+
+
+probe begin {
+ // [0]=bit start position [1]=bit length
+ pmid_item[0] = 0
+ pmid_item[1] = 10
+ pmid_cluster[0] = 10
+ pmid_cluster[1] = 12
+ pmid_domain[0] = 22
+ pmid_domain[1] = 9
+ pmindom_serial[0] = 0
+ pmindom_serial[1] = 22
+ pmindom_domain[0] = 22
+ pmindom_domain[1] = 9
+
+ pdu_types[PDU_ERROR] = "ERROR";
+ pdu_types[PDU_RESULT] = "RESULT";
+ pdu_types[PDU_PROFILE] = "PROFILE";
+ pdu_types[PDU_FETCH] = "FETCH";
+ pdu_types[PDU_DESC_REQ] = "DESC_REQ";
+ pdu_types[PDU_DESC] = "DESC";
+ pdu_types[PDU_INSTANCE_REQ] = "INSTANCE_REQ";
+ pdu_types[PDU_INSTANCE] = "INSTANCE";
+ pdu_types[PDU_TEXT_REQ] = "TEXT_REQ";
+ pdu_types[PDU_TEXT] = "TEXT";
+ pdu_types[PDU_CONTROL_REQ] = "CONTROL_REQ";
+ pdu_types[PDU_CREDS] = "CREDS";
+ pdu_types[PDU_PMNS_IDS] = "PMNS_IDS";
+ pdu_types[PDU_PMNS_NAMES] = "PMNS_NAMES";
+ pdu_types[PDU_PMNS_CHILD] = "PMNS_CHILD";
+ pdu_types[PDU_PMNS_TRAVERSE] = "PMNS_TRAVERSE";
+ pdu_types[PDU_LOG_CONTROL] = "LOG_CONTROL";
+ pdu_types[PDU_LOG_STATUS] = "LOG_STATUS";
+ pdu_types[PDU_LOG_REQUEST] = "LOG_REQUEST";
+ pdu_types[PDU_AUTH] = "AUTH";
+ pdu_types[-1] = "NO";
+}
+
+
+function bitextract (bits, start, length)
+{
+ // HAVE_BITFIELDS_LTOR ?
+ %( arch != "i386" && arch != "x86_64" %?
+ // HAVE_BITFIELDS_RTOL
+ start = 32 - start - length;
+ %)
+ return ((bits >> start) & ((1 << length) - 1));
+}
+
+function pdu_type:string (type:long)
+{
+ if (type in pdu_types)
+ return pdu_types[type]
+ else
+ return sprint ("TYPE-",type,"?");
+}
+
+
+/**
+ * probe pmcd.* - Indicates the type of the pmcd trace
+ *
+ * @t_desc: Description of the trace
+ * @t_type: Type of tracing that occurred
+ * @t_who: File descriptor
+ * @t_p1: Trace argument 1
+ * @t_p2: Trace argument 2
+ */
+
+
+probe pmcd.add_client = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_ADD_CLIENT)
+ next
+ t_desc = sprintf("New client: [%d]", $arg2)
+}
+
+
+probe pmcd.del_client = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_DEL_CLIENT)
+ next
+ t_desc = sprintf("End client: fd=%d err=%d", $arg2, $arg3)
+}
+
+
+probe pmcd.add_agent = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_ADD_AGENT)
+ next
+ t_desc = sprintf("Add PMDA: domain=%d ", $arg2)
+ if ($arg3 == -1 && $arg4 == -1)
+ t_desc = t_desc . "DSO"
+ else
+ t_desc = t_desc . sprintf("infd=%d, outfd=%d", $arg3, $arg4)
+}
+
+
+probe pmcd.del_agent = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_DEL_AGENT)
+ next
+
+ t_desc = sprintf("Drop PMDA: domain=%d ", $arg2)
+ if ($arg3 == -1 && $arg4 == -1)
+ t_desc = t_desc . "DSO"
+ else
+ t_desc = t_desc . sprintf("infd=%d, outfd=%d", $arg3, $arg4)
+}
+
+
+probe pmcd.eof = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_EOF)
+ next;
+
+ t_desc = sprintf("Premature EOF: expecting %s PDU, fd=%d", pdu_type($arg3), $arg2)
+}
+
+
+probe pmcd.wrong_pdu = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_WRONG_PDU)
+ next
+
+ t_desc = sprintf("Wrong PDU type: expecting %s PDU, fd=%d, ", pdu_type($arg3), $arg2)
+ if ($arg3 > 0)
+ t_desc = t_desc . sprintf("got %s PDU", pdu_type($arg4))
+ else if ($arg3 == 0)
+ t_desc = t_desc . "got EOF"
+ else
+ t_desc = t_desc . sprintf("got err=%d", $arg4)
+}
+
+
+probe pmcd.xmit_err = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_XMIT_ERR)
+ next
+
+ t_desc = sprintf("Send %s PDU failed: fd=%d, err=%d", pdu_type($arg3), $arg2, $arg4)
+}
+
+
+probe pmcd.recv_timeout = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_RECV_TIMEOUT)
+ next
+
+ t_desc = sprintf("Recv timeout: expecting %s PDU, fd=%d", pdu_type($arg3), $arg2)
+}
+
+
+probe pmcd.recv_err = process("@path@").mark("PMCD")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_RECV_ERR)
+ next;
+
+ t_desc = sprintf("Recv error: expecting %s PDU, fd=%d, err=%d", pdu_type($arg3), $arg2, $arg4)
+}
+
+
+probe pmcd.pdu.xmit = process("@path@").mark("PMCD_PDU")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_XMIT_PDU)
+ next
+
+ t_desc = sprintf("Xmit: %s PDU, fd=%d, ", pdu_type($arg3), $arg2)
+ if ($arg3 == PDU_ERROR)
+ t_desc = t_desc . sprintf(" err=%d", $arg4)
+ else if ($arg3 == PDU_RESULT)
+ t_desc = t_desc . sprintf("numpmid=%d", $arg4)
+ else if ($arg3 == PDU_TEXT || $arg3 == PDU_TEXT_REQ)
+ t_desc = t_desc . sprintf("id=%#lx", $arg4)
+ else if ($arg3 == PDU_DESC || $arg3 == PDU_DESC_REQ)
+ {
+ domain = bitextract ($arg4, pmid_domain[0],pmid_domain[1])
+ cluster = bitextract ($arg4, pmid_cluster[0],pmid_cluster[1])
+ item = bitextract ($arg4, pmid_item[0],pmid_item[1])
+ if ($arg4 == PM_ID_NULL)
+ t_desc = t_desc . sprintf("pmid=PM_ID_NULL")
+ else if (domain == DYNAMIC_PMID && item == 0)
+ t_desc = t_desc . sprintf("pmid=%d.*.*", $arg4)
+ else
+ t_desc = t_desc . sprintf("pmid=%d.%d.%d", domain, cluster, item)
+ }
+ else if ($arg3 == PDU_INSTANCE_REQ || $arg3 == PDU_INSTANCE)
+ {
+ domain = bitextract ($arg4, pmindom_domain[0],pmindom_domain[1])
+ serial = bitextract ($arg4, pmindom_serial[0],pmindom_serial[1])
+ if ($arg4 == PM_INDOM_NULL)
+ t_desc = t_desc . sprintf("pmid=PM_INDOM_NULL")
+ else
+ t_desc = t_desc . sprintf("indom=%d.%d", domain, serial)
+ }
+ else if ($arg2 == PDU_PMNS_NAMES)
+ t_desc = t_desc . sprintf("numpmid=%d", $arg4)
+ else if ($arg2 == PDU_PMNS_IDS)
+ t_desc = t_desc . sprintf("numpmid=%d", $arg4)
+ else if ($arg2 == PDU_CREDS)
+ t_desc = t_desc . sprintf("numcreds=%d", $arg4)
+}
+
+
+probe pmcd.pdu.recv = process("@path@").mark("PMCD_PDU")
+{
+ t_desc = ""
+
+ if ($arg1 != TR_RECV_PDU)
+ next;
+
+ t_desc = sprintf("Recv: %s PDU, fd=%d, pdubuf=%#x", pdu_type($arg3), $arg2, $arg4)
+}
+
+
+// Example Use
+# probe pmcd.*
+# {
+# println(t_desc)
+# }
+
+# probe pmcd.pdu.*
+# {
+# println(t_desc)
+# }
diff --git a/src/libpcp_pmcd/src/probes.d b/src/libpcp_pmcd/src/probes.d
new file mode 100644
index 0000000..97bdae8
--- /dev/null
+++ b/src/libpcp_pmcd/src/probes.d
@@ -0,0 +1,4 @@
+provider pcp_probe {
+ probe PMCD_PDU(int, int, int, int);
+ probe PMCD(int, int, int, int);
+};
diff --git a/src/libpcp_pmcd/src/trace.c b/src/libpcp_pmcd/src/trace.c
new file mode 100644
index 0000000..ed0ebaf
--- /dev/null
+++ b/src/libpcp_pmcd/src/trace.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <time.h>
+#include "pmcd.h"
+#include "config.h"
+#if HAVE_STATIC_PROBES
+#include "probes.h"
+#else
+#define PCP_PROBE_PMCD_PDU(type,who,p1,p2)
+#define PCP_PROBE_PMCD(type,who,p1,p2)
+#endif
+
+/*
+ * Diagnostic event tracing support
+ */
+
+typedef struct {
+ time_t t_stamp; /* timestamp */
+ int t_type; /* event type */
+ int t_who; /* originator or principal identifier */
+ int t_p1; /* optional event parameters */
+ int t_p2;
+} tracebuf;
+
+static tracebuf *trace;
+static unsigned int next;
+
+/*
+ * by default, circular buffer last 20 events -- change by modify
+ * pmcd.control.tracebufs
+ * by default, tracing is disabled -- change by setting the following
+ * to 1, pmcd.control.traceconn (trace connections) and/or
+ * pmcd.control.tracepdu (trace PDUs)
+ */
+PMCD_INTERN int _pmcd_trace_nbufs = 20;
+PMCD_INTERN int _pmcd_trace_mask;
+
+void
+pmcd_init_trace(int n)
+{
+ if (trace != NULL)
+ free(trace);
+ if ((trace = (tracebuf *)malloc(n * sizeof(tracebuf))) == NULL) {
+ __pmNoMem("pmcd_init_trace", n * sizeof(tracebuf), PM_RECOV_ERR);
+ return;
+ }
+ _pmcd_trace_nbufs = n;
+ next = 0;
+}
+
+void
+pmcd_trace(int type, int who, int p1, int p2)
+{
+ int p;
+
+ switch (type) {
+ case TR_XMIT_PDU:
+ case TR_RECV_PDU:
+ PCP_PROBE_PMCD_PDU(type, who, p1, p2);
+ if ((_pmcd_trace_mask & TR_MASK_PDU) == 0)
+ return;
+ break;
+ default:
+ PCP_PROBE_PMCD(type, who, p1, p2);
+ if ((_pmcd_trace_mask & TR_MASK_CONN) == 0)
+ return;
+ break;
+ }
+
+ if (trace == NULL) {
+ pmcd_init_trace(_pmcd_trace_nbufs);
+ if (trace == NULL)
+ return;
+ }
+
+ p = (next++) % _pmcd_trace_nbufs;
+
+ time(&trace[p].t_stamp);
+ trace[p].t_type = type;
+ trace[p].t_who = who;
+ trace[p].t_p1 = p1;
+ trace[p].t_p2 = p2;
+
+ if (_pmcd_trace_mask & TR_MASK_NOBUF)
+ /* unbuffered, dump it now */
+ pmcd_dump_trace(stderr);
+}
+
+void
+pmcd_dump_trace(FILE *f)
+{
+ int i;
+ int p;
+ struct tm last = { 0, 0 };
+ struct tm *this;
+ char strbuf[20];
+
+ if ((_pmcd_trace_mask & TR_MASK_NOBUF) == 0)
+ fprintf(f, "\n->PMCD event trace: ");
+ if (trace != NULL && next != 0) {
+ if (next < _pmcd_trace_nbufs)
+ i = 0;
+ else
+ i = next - _pmcd_trace_nbufs;
+ if ((_pmcd_trace_mask & TR_MASK_NOBUF) == 0) {
+ fprintf(f, "starting at %s", ctime(&trace[i % _pmcd_trace_nbufs].t_stamp));
+ last.tm_hour = -1;
+ }
+ else
+ last.tm_hour = -2;
+
+ for ( ; i < next; i++) {
+ fprintf(f, "->");
+ p = i % _pmcd_trace_nbufs;
+ this = localtime(&trace[p].t_stamp);
+ if (this->tm_hour != last.tm_hour ||
+ this->tm_min != last.tm_min ||
+ this->tm_sec != last.tm_sec) {
+ if (last.tm_hour == -1)
+ fprintf(f, " ");
+ else
+ fprintf(f, "%02d:%02d:%02d ", this->tm_hour, this->tm_min, this->tm_sec);
+ last = *this; /* struct assignment */
+ }
+ else
+ fprintf(f, " ");
+
+ switch (trace[p].t_type) {
+
+ case TR_ADD_CLIENT:
+ {
+ ClientInfo *cip;
+
+ fprintf(f, "New client: [%d] ", trace[p].t_who);
+ cip = GetClient(trace[p].t_who);
+ if (cip == NULL) {
+ fprintf(f, "-- unknown?\n");
+ }
+ else {
+ __pmSockAddr *saddr = (__pmSockAddr *)cip->addr;
+ char *addrbuf;
+
+ addrbuf = __pmSockAddrToString(saddr);
+ if (addrbuf == NULL)
+ fprintf(f, "invalid socket address");
+ else {
+ fprintf(f, "addr=%s", addrbuf);
+ free(addrbuf);
+ }
+ fprintf(f, ", fd=%d, seq=%u\n", cip->fd, cip->seq);
+ }
+ }
+ break;
+
+ case TR_DEL_CLIENT:
+ fprintf(f, "End client: fd=%d", trace[p].t_who);
+ if (trace[p].t_p1 != 0)
+ fprintf(f, ", err=%d: %s", trace[p].t_p1, pmErrStr(trace[p].t_p1));
+ fputc('\n', f);
+ break;
+
+ case TR_ADD_AGENT:
+ fprintf(f, "Add PMDA: domain=%d, ", trace[p].t_who);
+ if (trace[p].t_p1 == -1 && trace[p].t_p2 == -1)
+ fprintf(f, "DSO\n");
+ else
+ fprintf(f, "infd=%d, outfd=%d\n", trace[p].t_p1, trace[p].t_p2);
+ break;
+
+ case TR_DEL_AGENT:
+ fprintf(f, "Drop PMDA: domain=%d, ", trace[p].t_who);
+ if (trace[p].t_p1 == -1 && trace[p].t_p2 == -1)
+ fprintf(f, "DSO\n");
+ else
+ fprintf(f, "infd=%d, outfd=%d\n", trace[p].t_p1, trace[p].t_p2);
+ break;
+
+ case TR_EOF:
+ fprintf(f, "Premature EOF: expecting %s PDU, fd=%d\n",
+ trace[p].t_p1 == -1 ? "NO" : __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)),
+ trace[p].t_who);
+ break;
+
+ case TR_WRONG_PDU:
+ if (trace[p].t_p2 > 0) {
+ fprintf(f, "Wrong PDU type: expecting %s PDU, fd=%d, got %s PDU\n",
+ trace[p].t_p1 == -1 ? "NO" : __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)),
+ trace[p].t_who,
+ trace[p].t_p2 == -1 ? "NO" : __pmPDUTypeStr_r(trace[p].t_p2, strbuf, sizeof(strbuf)));
+ }
+ else if (trace[p].t_p2 == 0) {
+ fprintf(f, "Wrong PDU type: expecting %s PDU, fd=%d, got EOF\n",
+ trace[p].t_p1 == -1 ? "NO" : __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)),
+ trace[p].t_who);
+ }
+ else {
+ fprintf(f, "Wrong PDU type: expecting %s PDU, fd=%d, got err=%d: %s\n",
+ trace[p].t_p1 == -1 ? "NO" : __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)),
+ trace[p].t_who,
+ trace[p].t_p2, pmErrStr(trace[p].t_p2));
+
+ }
+ break;
+
+ case TR_XMIT_ERR:
+ fprintf(f, "Send %s PDU failed: fd=%d, err=%d: %s\n",
+ __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)), trace[p].t_who,
+ trace[p].t_p2, pmErrStr(trace[p].t_p2));
+ break;
+
+ case TR_RECV_TIMEOUT:
+ fprintf(f, "Recv timeout: expecting %s PDU, fd=%d\n",
+ __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)), trace[p].t_who);
+ break;
+
+ case TR_RECV_ERR:
+ fprintf(f, "Recv error: expecting %s PDU, fd=%d, err=%d: %s\n",
+ __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)), trace[p].t_who,
+ trace[p].t_p2, pmErrStr(trace[p].t_p2));
+ break;
+
+ case TR_XMIT_PDU:
+ fprintf(f, "Xmit: %s PDU, fd=%d",
+ __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)), trace[p].t_who);
+ if (trace[p].t_p1 == PDU_ERROR)
+ fprintf(f, ", err=%d: %s",
+ trace[p].t_p2, pmErrStr(trace[p].t_p2));
+ else if (trace[p].t_p1 == PDU_RESULT)
+ fprintf(f, ", numpmid=%d", trace[p].t_p2);
+ else if (trace[p].t_p1 == PDU_TEXT_REQ ||
+ trace[p].t_p1 == PDU_TEXT)
+ fprintf(f, ", id=0x%x", trace[p].t_p2);
+ else if (trace[p].t_p1 == PDU_DESC_REQ ||
+ trace[p].t_p1 == PDU_DESC)
+ fprintf(f, ", pmid=%s", pmIDStr_r((pmID)trace[p].t_p2, strbuf, sizeof(strbuf)));
+ else if (trace[p].t_p1 == PDU_INSTANCE_REQ ||
+ trace[p].t_p1 == PDU_INSTANCE)
+ fprintf(f, ", indom=%s", pmInDomStr_r((pmInDom)trace[p].t_p2, strbuf, sizeof(strbuf)));
+ else if (trace[p].t_p1 == PDU_PMNS_NAMES)
+ fprintf(f, ", numpmid=%d", trace[p].t_p2);
+ else if (trace[p].t_p1 == PDU_PMNS_IDS)
+ fprintf(f, ", numpmid=%d", trace[p].t_p2);
+ else if (trace[p].t_p1 == PDU_CREDS)
+ fprintf(f, ", numcreds=%d", trace[p].t_p2);
+ fputc('\n', f);
+ break;
+
+ case TR_RECV_PDU:
+ fprintf(f, "Recv: %s PDU, fd=%d, pdubuf=0x",
+ __pmPDUTypeStr_r(trace[p].t_p1, strbuf, sizeof(strbuf)), trace[p].t_who);
+ /* This will only work if sizeof (int) == sizeof (ptr).
+ * On MIPS int is always 32 bits regardless the ABI,
+ * and on Linux we're checking in configure if an int is
+ * anything else but 32 bits, so if pointer is not
+ * 32 bit, then .... */
+#ifndef HAVE_32BIT_PTR
+ fprintf(f, "...");
+#endif
+ fprintf(f, "%x\n", trace[p].t_p2);
+ break;
+
+ default:
+ fprintf(f, "Type=%d who=%d p1=%d p2=%d\n",
+ trace[p].t_type, trace[p].t_who, trace[p].t_p1,
+ trace[p].t_p2);
+ break;
+ }
+ }
+ }
+ else
+ fprintf(f, "<empty>\n");
+
+ if ((_pmcd_trace_mask & TR_MASK_NOBUF) == 0)
+ fputc('\n', f);
+ next = 0; /* empty the circular buffer */
+}
diff --git a/src/libpcp_pmda/GNUlocaldefs.32 b/src/libpcp_pmda/GNUlocaldefs.32
new file mode 100644
index 0000000..38bc43e
--- /dev/null
+++ b/src/libpcp_pmda/GNUlocaldefs.32
@@ -0,0 +1,4 @@
+LCFLAGS += -m32 -march=i386
+LLDFLAGS += -m32 -march=i386
+PCP_LIB_DIR=$(PCP_LIB32_DIR)
+LIBPCP_ABIDIR=32
diff --git a/src/libpcp_pmda/GNUmakefile b/src/libpcp_pmda/GNUmakefile
new file mode 100644
index 0000000..982d0ad
--- /dev/null
+++ b/src/libpcp_pmda/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = GNUlocaldefs.32
+
+SUBDIRS = src
+
+default install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
diff --git a/src/libpcp_pmda/src/GNUmakefile b/src/libpcp_pmda/src/GNUmakefile
new file mode 100644
index 0000000..5812b73
--- /dev/null
+++ b/src/libpcp_pmda/src/GNUmakefile
@@ -0,0 +1,102 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2009,2011 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = callback.c open.c mainloop.c help.c cache.c tree.c context.c \
+ events.c queues.c dynamic.c
+HFILES = libdefs.h queues.h
+LLDLIBS = -lpcp
+
+STATICLIBTARGET = libpcp_pmda.a
+
+#
+# libpcp_pmda.so -> libpcp_pmda.so.3
+# libpcp_pmda.so.2 -> libpcp_pmda.so.3
+#
+DSOVERSION_V2 = 2
+DSOVERSION_V3 = 3
+VERSION_SCRIPT = exports
+
+ifneq "$(TARGET_OS)" "darwin"
+LIBTARGET_V1 = libpcp_pmda.$(DSOSUFFIX)
+LIBTARGET_V2 = libpcp_pmda.$(DSOSUFFIX).$(DSOVERSION_V2)
+LIBTARGET_V3 = libpcp_pmda.$(DSOSUFFIX).$(DSOVERSION_V3)
+else
+LIBTARGET_V1 = libpcp_pmda.$(DSOSUFFIX)
+LIBTARGET_V2 =
+LIBTARGET_V3 = libpcp_pmda.$(DSOVERSION_V3).$(DSOSUFFIX)
+endif
+ifeq "$(PACKAGE_DISTRIBUTION)" "debian"
+LIBTARGET_V2 =
+endif
+LIBTARGET = $(LIBTARGET_V3)
+
+ifeq "$(TARGET_OS)" "mingw"
+LIBTARGET = libpcp_pmda.$(DSOSUFFIX)
+STATICLIBTARGET =
+LIBTARGET_V1 =
+LIBTARGET_V2 =
+LIBTARGET_V3 =
+endif
+
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+LIBTARGET_V1 =
+LIBTARGET_V2 =
+LIBTARGET_V3 =
+endif
+
+LSRCFILES = $(VERSION_SCRIPT)
+LDIRT = $(LIBTARGET_V1) $(LIBTARGET_V2) $(LIBTARGET_V3)
+
+default: $(LIBTARGET_V1) $(LIBTARGET_V2) $(LIBTARGET) $(STATICLIBTARGET)
+
+ifneq ($(LIBTARGET_V1),)
+$(LIBTARGET_V1): $(LIBTARGET_V3)
+ $(LN_S) -f $(LIBTARGET_V3) $(LIBTARGET_V1)
+endif
+ifneq ($(LIBTARGET_V2),)
+$(LIBTARGET_V2): $(LIBTARGET_V3)
+ $(LN_S) -f $(LIBTARGET_V3) $(LIBTARGET_V2)
+endif
+
+callback.o mainloop.o open.o : libdefs.h
+
+include $(BUILDRULES)
+
+install : default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(LIBTARGET_V1),)
+ $(INSTALL) -S $(LIBTARGET_V3) $(PCP_LIB_DIR)/$(LIBTARGET_V1)
+endif
+ifneq ($(LIBTARGET_V2),)
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET_V2)
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp_pmda/src/cache.c b/src/libpcp_pmda/src/cache.c
new file mode 100644
index 0000000..dd74b3f
--- /dev/null
+++ b/src/libpcp_pmda/src/cache.c
@@ -0,0 +1,1543 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+static __uint32_t hash(const char *, int, __uint32_t);
+
+/*
+ * simple linked list for each cache at this stage
+ */
+typedef struct entry {
+ struct entry *next; /* in inst identifier order */
+ struct entry *h_inst; /* inst hash chain */
+ struct entry *h_name; /* name hash chain */
+ int inst;
+ char *name;
+ int hashlen; /* smaller of strlen(name) and chars to first space */
+ int keylen; /* > 0 if have key from pmdaCacheStoreKey() */
+ void *key; /* != NULL if have key from pmdaCacheStoreKey() */
+ int state;
+ void *private;
+ time_t stamp;
+} entry_t;
+
+#define VERSION 1 /* version of external file format */
+#define MAX_HASH_TRY 10
+
+/*
+ * linked list of cache headers
+ */
+typedef struct hdr {
+ struct hdr *next; /* linked list of indoms */
+ entry_t *first; /* in inst order */
+ entry_t *last; /* in inst order */
+ entry_t *save; /* used in cache_walk() */
+ entry_t **ctl_inst; /* hash by inst chains */
+ entry_t **ctl_name; /* hash by name chains */
+ pmInDom indom;
+ int hsize;
+ int hbits;
+ int nentry; /* number of entries */
+ int ins_mode; /* see insert_cache() */
+ int hstate; /* dirty/clean/string state */
+ int keyhash_cnt[MAX_HASH_TRY];
+} hdr_t;
+
+/* bitfields for hstate */
+#define DIRTY_INSTANCE 0x1
+#define DIRTY_STAMP 0x2
+#define CACHE_STRINGS 0x4
+
+static hdr_t *base; /* start of cache headers */
+static char filename[MAXPATHLEN];
+ /* for load/save ops */
+static char *vdp; /* first trip mkdir for load/save */
+
+/*
+ * Count character to end of string or first space, whichever comes
+ * first. In the special case of string caches, spaces are allowed.
+ */
+static int
+get_hashlen(hdr_t *h, const char *str)
+{
+ const char *q = str;
+
+ while (*q && (*q != ' ' || (h->hstate & CACHE_STRINGS) != 0))
+ q++;
+ return (int)(q-str);
+}
+
+static unsigned int
+hash_str(const char *str, int len)
+{
+ return hash(str, len, 0);
+}
+
+static void
+KeyStr(FILE *f, int keylen, const char *key)
+{
+ int i;
+ if (keylen > 0) {
+ fprintf(f, "[key=0x");
+ for (i = 0; i < keylen; i++, key++)
+ fprintf(f, "%02x", (*key & 0xff));
+ fputc(']', f);
+ }
+ else
+ fprintf(f, "[no key]");
+}
+
+/*
+ * The magic "match up to a space" instance name matching ...
+ *
+ * e->name e->hashlen name hashlen result
+ * foo... >3 foo 3 0
+ * foo 3 foo... >3 0
+ * foo 3 foo 3 1
+ * foo bar 3 foo 3 1
+ * foo bar 3 foo bar 3 1
+ * foo 3 foo bar 3 -1 bad
+ * foo blah 3 foo bar 3 -1 bad
+ *
+ */
+static int
+name_eq(entry_t *e, const char *name, int hashlen)
+{
+ if (e->hashlen != hashlen)
+ return 0;
+ if (strncmp(e->name, name, hashlen) != 0)
+ return 0;
+ if (name[hashlen] == '\0')
+ return 1;
+ if (e->name[hashlen] == '\0')
+ return -1;
+ if (strcmp(&e->name[hashlen+1], &name[hashlen+1]) == 0)
+ return 1;
+ return -1;
+}
+
+static int
+key_eq(entry_t *e, int keylen, const char *key)
+{
+ const char *ekp;
+ const char *kp;
+ int i;
+
+ if (e->keylen != keylen)
+ return 0;
+
+ ekp = (const char *)e->key;
+ kp = (const char *)key;
+ for (i = 0; i < keylen; i++) {
+ if (*ekp != *kp)
+ return 0;
+ ekp++;
+ kp++;
+ }
+
+ return 1;
+}
+
+static hdr_t *
+find_cache(pmInDom indom, int *sts)
+{
+ hdr_t *h;
+ int i;
+
+ for (h = base; h != NULL; h = h->next) {
+ if (h->indom == indom)
+ return h;
+ }
+
+ if ((h = (hdr_t *)malloc(sizeof(hdr_t))) == NULL) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "find_cache: indom %s: unable to allocate memory for hdr_t",
+ pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ *sts = PM_ERR_GENERIC;
+ return NULL;
+ }
+ h->next = base;
+ base = h;
+ h->first = NULL;
+ h->last = NULL;
+ h->hsize = 16;
+ h->hbits = 0xf;
+ h->ctl_inst = (entry_t **)calloc(h->hsize, sizeof(entry_t *));
+ h->ctl_name = (entry_t **)calloc(h->hsize, sizeof(entry_t *));
+ h->indom = indom;
+ h->nentry = 0;
+ h->ins_mode = 0;
+ h->hstate = 0;
+ for (i = 0; i < MAX_HASH_TRY; i++)
+ h->keyhash_cnt[i] = 0;
+ return h;
+}
+
+/*
+ * Traverse the cache in ascending inst order
+ */
+static entry_t *
+walk_cache(hdr_t *h, int op)
+{
+ entry_t *e;
+
+ if (op == PMDA_CACHE_WALK_REWIND) {
+ h->save = h->first;
+ return NULL;
+ }
+ e = h->save;
+ if (e != NULL)
+ h->save = e->next;
+ return e;
+}
+
+/*
+ * inst_or_name is 0 for inst hash list, 1 for name hash list
+ */
+static void
+dump_hash_list(FILE *fp, hdr_t *h, int inst_or_name, int i)
+{
+ entry_t *e;
+
+ fprintf(fp, " [%03d]", i);
+ if (inst_or_name == 1) {
+ for (e = h->ctl_name[i]; e != NULL; e = e->h_name) {
+ fprintf(fp, " -> %d", e->inst);
+ if (e->state == PMDA_CACHE_EMPTY)
+ fputc('E', fp);
+ else if (e->state == PMDA_CACHE_INACTIVE)
+ fputc('I', fp);
+ }
+ fputc('\n', fp);
+ }
+ else {
+ for (e = h->ctl_inst[i]; e != NULL; e = e->h_inst) {
+ fprintf(fp, " -> %d", e->inst);
+ if (e->state == PMDA_CACHE_EMPTY)
+ fputc('E', fp);
+ else if (e->state == PMDA_CACHE_INACTIVE)
+ fputc('I', fp);
+ }
+ fputc('\n', fp);
+ }
+}
+
+static void
+dump(FILE *fp, hdr_t *h, int do_hash)
+{
+ entry_t *e;
+ char strbuf[20];
+ int i;
+
+ fprintf(fp, "pmdaCacheDump: indom %s: nentry=%d ins_mode=%d hstate=%d hsize=%d\n",
+ pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)), h->nentry, h->ins_mode, h->hstate, h->hsize);
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_EMPTY) {
+ fprintf(fp, "(%10d) %8s\n", e->inst, "empty");
+ }
+ else {
+ fprintf(fp, " %10d %8s " PRINTF_P_PFX "%p %s",
+ e->inst, e->state == PMDA_CACHE_ACTIVE ? "active" : "inactive",
+ e->private, e->name);
+ if (strlen(e->name) > e->hashlen)
+ fprintf(fp, " [match len=%d]", e->hashlen);
+ if (e->keylen > 0) {
+ fputc(' ', fp);
+ KeyStr(fp, e->keylen, (const char *)e->key);
+ }
+ fputc('\n', fp);
+ }
+ }
+
+ if (do_hash == 0)
+ return;
+
+ for (i = 0; i < MAX_HASH_TRY; i++) {
+ if (h->keyhash_cnt[i])
+ break;
+ }
+
+ if (i < MAX_HASH_TRY) {
+ fprintf(fp, "pmdaCacheStoreKey hash stats ...\n");
+ for (i = 0; i < MAX_HASH_TRY; i++) {
+ if (h->keyhash_cnt[i] != 0) {
+ if (i == 0)
+ fprintf(fp, "hash once: %d times\n", h->keyhash_cnt[i]);
+ else
+ fprintf(fp, "%d hash attempts: %d times\n", i+1, h->keyhash_cnt[i]);
+ }
+ }
+ }
+
+ if (h->ctl_inst != NULL) {
+ int i;
+ fprintf(fp, "inst hash\n");
+ for (i = 0; i < h->hsize; i++) {
+ dump_hash_list(fp, h, 0, i);
+ }
+ }
+ if (h->ctl_name != NULL) {
+ int i;
+ fprintf(fp, "name hash\n");
+ for (i = 0; i < h->hsize; i++) {
+ dump_hash_list(fp, h, 1, i);
+ }
+ }
+}
+
+static entry_t *
+find_name(hdr_t *h, const char *name, int *sts)
+{
+ entry_t *e;
+ int hashlen = get_hashlen(h, name);
+
+ *sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state != PMDA_CACHE_EMPTY) {
+ if ((*sts = name_eq(e, name, hashlen)))
+ break;
+ }
+ }
+ return e;
+}
+
+static entry_t *
+find_inst(hdr_t *h, int inst)
+{
+ entry_t *e;
+
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->inst == inst && e->state != PMDA_CACHE_EMPTY)
+ break;
+ }
+ return e;
+}
+
+/*
+ * supports find by instance identifier (name == NULL) else
+ * find by instance name
+ */
+static entry_t *
+find_entry(hdr_t *h, const char *name, int inst, int *sts)
+{
+ entry_t *e;
+
+ *sts = 0;
+ if (name == NULL) {
+ /*
+ * search by instance identifier (inst)
+ */
+ if (h->ctl_inst == NULL)
+ /* no hash, use linear search */
+ return find_inst(h, inst);
+ for (e = h->ctl_inst[inst & h->hbits]; e != NULL; e = e->h_inst) {
+ if (e->inst == inst && e->state != PMDA_CACHE_EMPTY)
+ return e;
+ }
+ }
+ else {
+ /*
+ * search by instance name
+ */
+ int hashlen = get_hashlen(h, name);
+
+ if (h->ctl_name == NULL)
+ /* no hash, use linear search */
+ return find_name(h, name, sts);
+ for (e = h->ctl_name[hash_str(name, hashlen) & h->hbits]; e != NULL; e = e->h_name) {
+ if (e->state != PMDA_CACHE_EMPTY) {
+ if ((*sts = name_eq(e, name, hashlen)))
+ return e;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * optionally resize the hash table first (if resize == 1)
+ *
+ * then re-order each hash chain so that active entires are
+ * before inactive entries, and culled entries dropped
+ *
+ * applies to _both_ the inst and name hashes
+ */
+static void
+redo_hash(hdr_t *h, int resize)
+{
+ entry_t *e;
+ entry_t *last_e = NULL;
+ entry_t *t;
+ int i;
+ entry_t *last_active;
+ entry_t *inactive;
+ entry_t *last_inactive;
+
+ if (resize) {
+ entry_t **old_inst;
+ entry_t **old_name;
+ int oldsize;
+ int oldi;
+
+ old_inst = h->ctl_inst;
+ old_name = h->ctl_name;
+ oldsize = h->hsize;
+ h->hsize <<= 1;
+ h->ctl_inst = (entry_t **)calloc(h->hsize, sizeof(entry_t *));
+ if (h->ctl_inst == NULL) {
+ h->ctl_inst = old_inst;
+ h->hsize = oldsize;
+ goto reorder;
+ }
+ h->ctl_name = (entry_t **)calloc(h->hsize, sizeof(entry_t *));
+ if (h->ctl_name == NULL) {
+ free(h->ctl_inst);
+ h->ctl_inst = old_inst;
+ h->ctl_name = old_name;
+ h->hsize = oldsize;
+ goto reorder;
+ }
+ h->hbits = (h->hbits << 1) | 1;
+ for (oldi = 0; oldi < oldsize; oldi++) {
+ for (e = old_inst[oldi]; e != NULL; ) {
+ t = e;
+ e = e->h_inst;
+ i = t->inst & h->hbits;
+ t->h_inst = h->ctl_inst[i];
+ h->ctl_inst[i] = t;
+ }
+ }
+ for (oldi = 0; oldi < oldsize; oldi++) {
+ for (e = old_name[oldi]; e != NULL; ) {
+ t = e;
+ e = e->h_name;
+ i = hash_str(t->name, t->hashlen) & h->hbits;
+ t->h_name = h->ctl_name[i];
+ h->ctl_name[i] = t;
+ }
+ }
+ free(old_inst);
+ free(old_name);
+ }
+reorder:
+
+ /*
+ * first the inst hash list, moving active entries before inactive ones,
+ * and unlinking any empty ones
+ */
+ for (i = 0; i < h->hsize; i++) {
+ last_active = NULL;
+ inactive = NULL;
+ last_inactive = NULL;
+ e = h->ctl_inst[i];
+ h->ctl_inst[i] = NULL;
+ while (e != NULL) {
+ t = e;
+ e = e->h_inst;
+ t->h_inst = NULL;
+ if (t->state == PMDA_CACHE_ACTIVE) {
+ if (last_active == NULL)
+ h->ctl_inst[i] = t;
+ else
+ last_active->h_inst = t;
+ last_active = t;
+ }
+ else if (t->state == PMDA_CACHE_INACTIVE) {
+ if (last_inactive == NULL)
+ inactive = t;
+ else
+ last_inactive->h_inst = t;
+ last_inactive = t;
+ }
+ }
+ if (last_active == NULL)
+ h->ctl_inst[i] = inactive;
+ else
+ last_active->h_inst = inactive;
+ }
+
+ /*
+ * and now the name hash list, doing the same thing
+ */
+ for (i = 0; i < h->hsize; i++) {
+ last_active = NULL;
+ inactive = NULL;
+ last_inactive = NULL;
+ e = h->ctl_name[i];
+ h->ctl_name[i] = NULL;
+ while (e != NULL) {
+ t = e;
+ e = e->h_name;
+ t->h_name = NULL;
+ if (t->state == PMDA_CACHE_ACTIVE) {
+ if (last_active == NULL)
+ h->ctl_name[i] = t;
+ else
+ last_active->h_name = t;
+ last_active = t;
+ }
+ else if (t->state == PMDA_CACHE_INACTIVE) {
+ if (last_inactive == NULL)
+ inactive = t;
+ else
+ last_inactive->h_name = t;
+ last_inactive = t;
+ }
+ }
+ if (last_active == NULL)
+ h->ctl_name[i] = inactive;
+ else
+ last_active->h_name = inactive;
+ }
+
+ /*
+ * now walk the instance list, removing any culled entries and
+ * rebuilding the linked list
+ */
+ e = h->first;
+ while (e != NULL) {
+ t = e;
+ e = e->next;
+ if (t->state == PMDA_CACHE_EMPTY) {
+ if (last_e == NULL)
+ h->first = e;
+ else
+ last_e->next = e;
+ if (t->name)
+ free(t->name);
+ free(t);
+ }
+ else
+ last_e = t;
+ }
+
+}
+
+/*
+ * We need to keep the instances in ascending inst order.
+ * If inst _is_ PM_IN_NULL, then we need to choose a value ...
+ * The default mode is appending to use the last value+1 (this is
+ * ins_mode == 0). If we wrap the instance identifier range, or
+ * PMDA_CACHE_REUSE has been used, then ins_mode == 1 and we walk
+ * the list starting from the beginning, looking for the first
+ * unused inst value.
+ *
+ * If inst is _not_ PM_IN_NULL, we're being called from load_cache
+ * or pmdaCacheStoreKey() and the inst is known ... so we need to
+ * check for possible duplicate entries.
+ */
+static entry_t *
+insert_cache(hdr_t *h, const char *name, int inst, int *sts)
+{
+ entry_t *e;
+ entry_t *last_e = NULL;
+ char *dup;
+ int i;
+ int hashlen = get_hashlen(h, name);
+
+ *sts = 0;
+
+ if (inst != PM_IN_NULL) {
+ /*
+ * Check if instance id or instance name already in cache
+ * ... if id and name are the the same, keep the existing one
+ * and ignore the new one (in particular state is not reset to
+ * inactive).
+ * If one matches but the other is different, keep the
+ * matching entry, but return an error as a warning.
+ * If both fail to match, we're OK to insert the new entry,
+ * although we need to run down the list quickly to find the
+ * correct place to insert the new one.
+ */
+ e = find_entry(h, NULL, inst, sts);
+ if (e != NULL) {
+ if (name_eq(e, name, hashlen) != 1) {
+ /* instance id the same, different name */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCache: store: indom %s: instance %d ", pmInDomStr(h->indom), e->inst);
+ fprintf(stderr, " in cache, name \"%s\" does not match new entry \"%s\"\n", e->name, name);
+ }
+#endif
+ *sts = PM_ERR_INST;
+ }
+ return e;
+ }
+ e = find_entry(h, name, PM_IN_NULL, sts);
+ if (e != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\"", pmInDomStr(h->indom), e->name);
+ fprintf(stderr, " in cache, id %d does not match new entry %d\n", e->inst, inst);
+ }
+#endif
+ *sts = PM_ERR_INST;
+ return e;
+ }
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->inst < inst)
+ last_e = e;
+ else if (e->inst > inst)
+ break;
+ }
+ }
+
+ if ((dup = strdup(name)) == NULL) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "insert_cache: indom %s: unable to allocate %d bytes for name: %s\n",
+ pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)), (int)strlen(name), name);
+ *sts = PM_ERR_GENERIC;
+ return NULL;
+ }
+
+ if (inst == PM_IN_NULL) {
+ if (h->ins_mode == 0) {
+ last_e = h->last;
+ if (last_e == NULL)
+ inst = 0;
+ else {
+ if (last_e->inst == 0x7fffffff) {
+ /*
+ * overflowed inst identifier, need to shift to
+ * ins_mode == 1
+ */
+ h->ins_mode = 1;
+ last_e = NULL;
+ goto retry;
+ }
+ inst = last_e->inst+1;
+ }
+ }
+ else {
+retry:
+ inst = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (inst < e->inst)
+ break;
+ if (inst == 0x7fffffff) {
+ /*
+ * 2^32-1 is the maximum number of instances we can have
+ */
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "insert_cache: indom %s: too many instances",
+ pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)));
+ *sts = PM_ERR_GENERIC;
+ free(dup);
+ return NULL;
+ }
+ inst++;
+ last_e = e;
+ }
+ }
+ }
+
+ if ((e = (entry_t *)malloc(sizeof(entry_t))) == NULL) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "insert_cache: indom %s: unable to allocate memory for entry_t",
+ pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)));
+ *sts = PM_ERR_GENERIC;
+ free(dup);
+ return NULL;
+ }
+
+ if (last_e == NULL) {
+ /* head of list */
+ e->next = h->first;
+ h->first = e;
+ }
+ else {
+ /* middle of list */
+ e->next = last_e->next;
+ last_e->next = e;
+ }
+ e->inst = inst;
+ e->name = dup;
+ e->hashlen = get_hashlen(h, dup);
+ e->key = NULL;
+ e->state = PMDA_CACHE_INACTIVE;
+ e->private = NULL;
+ e->stamp = 0;
+ if (h->last == NULL || h->last->inst < inst)
+ h->last = e;
+ h->nentry++;
+
+ if (h->hsize > 0 && h->hsize < 1024 && h->nentry > 4 * h->hsize)
+ redo_hash(h, 1);
+
+ /* link into the inst hash list, if any */
+ if (h->ctl_inst != NULL) {
+ i = inst & h->hbits;
+ e->h_inst = h->ctl_inst[i];
+ h->ctl_inst[i] = e;
+ }
+ else
+ e->h_inst = NULL;
+
+ /* link into the name hash list, if any */
+ if (h->ctl_name != NULL) {
+ i = hash_str(e->name, e->hashlen) & h->hbits;
+ e->h_name = h->ctl_name[i];
+ h->ctl_name[i] = e;
+ }
+ else
+ e->h_name = NULL;
+
+ return e;
+}
+
+static int
+load_cache(hdr_t *h)
+{
+ FILE *fp;
+ entry_t *e;
+ int cnt;
+ int x;
+ int inst;
+ int keylen = 0;
+ void *key = NULL;
+ int s;
+ char buf[1024]; /* input line buffer, is this big enough? */
+ char *p;
+ int sts;
+ int sep = __pmPathSeparator();
+ char strbuf[20];
+
+ if (vdp == NULL) {
+ vdp = pmGetConfig("PCP_VAR_DIR");
+ snprintf(filename, sizeof(filename),
+ "%s%c" "config" "%c" "pmda", vdp, sep, sep);
+ mkdir2(filename, 0755);
+ }
+
+ snprintf(filename, sizeof(filename), "%s%cconfig%cpmda%c%s",
+ vdp, sep, sep, sep, pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)));
+ if ((fp = fopen(filename, "r")) == NULL)
+ return -oserror();
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaCacheOp: %s: empty file?", filename);
+ fclose(fp);
+ return PM_ERR_GENERIC;
+ }
+ s = sscanf(buf, "%d %d", &x, &h->ins_mode);
+ if (s != 2 || x != 1 || h->ins_mode < 0 || h->ins_mode > 1) {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaCacheOp: %s: illegal first record: %s",
+ filename, buf);
+ fclose(fp);
+ return PM_ERR_GENERIC;
+ }
+
+ for (cnt = 0; ; cnt++) {
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ break;
+ if ((p = strchr(buf, '\n')) != NULL)
+ *p = '\0';
+ p = buf;
+ while (*p && isascii((int)*p) && isspace((int)*p))
+ p++;
+ if (*p == '\0') goto bad;
+ inst = 0;
+ while (*p && isascii((int)*p) && isdigit((int)*p)) {
+ inst = inst*10 + (*p-'0');
+ p++;
+ }
+ while (*p && isascii((int)*p) && isspace((int)*p))
+ p++;
+ if (inst < 0 || *p == '\0') goto bad;
+ x = 0;
+ while (*p && isascii((int)*p) && isdigit((int)*p)) {
+ x = x*10 + (*p-'0');
+ p++;
+ }
+ while (*p && isascii((int)*p) && isspace((int)*p))
+ p++;
+ if (*p == '[') {
+ char *pend;
+ char *q;
+ int i;
+ int tmp;
+ p++;
+ pend = p;
+ while (*pend && *pend != ']')
+ pend++;
+ if (*pend != ']')
+ goto bad;
+ /*
+ * convert key in place ...
+ */
+ keylen = (pend - p) / 2;
+ if ((key = malloc(keylen)) == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "load_cache: indom %s: unable to allocate memory for keylen=%d",
+ pmInDomStr(h->indom), keylen);
+ fclose(fp);
+ return PM_ERR_GENERIC;
+ }
+ q = key;
+ for (i = 0; i < keylen; i++) {
+ sscanf(p, "%2x", &tmp);
+ *q++ = (tmp & 0xff);
+ p += 2;
+ }
+ p += 2;
+ while (*p && isascii((int)*p) && isspace((int)*p))
+ p++;
+ }
+ else {
+ keylen = 0;
+ key = NULL;
+ }
+ if (*p == '\0') {
+bad:
+ __pmNotifyErr(LOG_ERR,
+ "pmdaCacheOp: %s: illegal record: %s",
+ filename, buf);
+ if (key) free(key);
+ fclose(fp);
+ return PM_ERR_GENERIC;
+ }
+ e = insert_cache(h, p, inst, &sts);
+ if (e == NULL) {
+ if (key) free(key);
+ fclose(fp);
+ return sts;
+ }
+ if (sts != 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "pmdaCacheOp: %s: loading instance %d (\"%s\") ignored, already in cache as %d (\"%s\")",
+ filename, inst, p, e->inst, e->name);
+ }
+ e->keylen = keylen;
+ e->key = key;
+ e->stamp = x;
+ }
+ fclose(fp);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "After PMDA_CACHE_LOAD\n");
+ dump(stderr, h, 0);
+ }
+#endif
+
+ return cnt;
+}
+
+static int
+save_cache(hdr_t *h, int hstate)
+{
+ FILE *fp;
+ entry_t *e;
+ int cnt;
+ time_t now;
+ int sep = __pmPathSeparator();
+ char strbuf[20];
+
+ if ((h->hstate & hstate) == 0 || (h->hstate & CACHE_STRINGS) != 0) {
+ /* nothing to be done */
+ return 0;
+ }
+
+ if (vdp == NULL) {
+ vdp = pmGetConfig("PCP_VAR_DIR");
+ snprintf(filename, sizeof(filename),
+ "%s%c" "config" "%c" "pmda", vdp, sep, sep);
+ mkdir2(filename, 0755);
+ }
+
+ snprintf(filename, sizeof(filename), "%s%cconfig%cpmda%c%s",
+ vdp, sep, sep, sep, pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)));
+ if ((fp = fopen(filename, "w")) == NULL)
+ return -oserror();
+ fprintf(fp, "%d %d\n", VERSION, h->ins_mode);
+
+ now = time(NULL);
+ cnt = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_EMPTY)
+ continue;
+ if (e->stamp == 0)
+ e->stamp = now;
+ fprintf(fp, "%d %d", e->inst, (int)e->stamp);
+ if (e->keylen > 0) {
+ char *p = (char *)e->key;
+ int i;
+ fprintf(fp, " [");
+ for (i = 0; i < e->keylen; i++, p++)
+ fprintf(fp, "%02x", (*p & 0xff));
+ fputc(']', fp);
+ }
+ fprintf(fp, " %s\n", e->name);
+ cnt++;
+ }
+ fclose(fp);
+ h->hstate = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "After cache_save hstate={");
+ if (hstate & DIRTY_INSTANCE) fprintf(stderr, "DIRTY_INSTANCE");
+ if (hstate & DIRTY_STAMP) fprintf(stderr, "DIRTY_STAMP");
+ fprintf(stderr, "}\n");
+ dump(stderr, h, 0);
+ }
+#endif
+
+ return cnt;
+}
+
+void
+__pmdaCacheDumpAll(FILE *fp, int do_hash)
+{
+ hdr_t *h;
+
+ for (h = base; h != NULL; h = h->next) {
+ dump(fp, h, do_hash);
+ }
+}
+
+void
+__pmdaCacheDump(FILE *fp, pmInDom indom, int do_hash)
+{
+ hdr_t *h;
+
+ for (h = base; h != NULL; h = h->next) {
+ if (h->indom == indom) {
+ dump(fp, h, do_hash);
+ }
+ }
+}
+
+static int
+store(pmInDom indom, int flags, const char *name, pmInDom inst, int keylen, const char *key, void *private)
+{
+ hdr_t *h;
+ entry_t *e;
+ int sts;
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ if ((e = find_entry(h, name, inst, &sts)) == NULL) {
+
+ if (flags != PMDA_CACHE_ADD) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCache store: indom %s: instance \"%s\"", pmInDomStr(indom), name);
+ if (inst != PM_IN_NULL)
+ fprintf(stderr, " (%d)", inst);
+ fprintf(stderr, " not in cache: flags=%d not allowed\n", flags);
+ }
+#endif
+ return PM_ERR_INST;
+ }
+
+ if ((e = insert_cache(h, name, inst, &sts)) == NULL)
+ return sts;
+ h->hstate |= DIRTY_INSTANCE; /* added a new entry */
+ }
+ else {
+ if (sts == -1)
+ /*
+ * name contains space, match up to space (short name) but
+ * mismatch after that ... cannot store name in the cache,
+ * the PMDA would have to cull the entry that matches on
+ * the short name first
+ */
+ return -EINVAL;
+ }
+
+ switch (flags) {
+ case PMDA_CACHE_ADD:
+ e->keylen = keylen;
+ if (keylen > 0) {
+ if ((e->key = malloc(keylen)) == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "store: indom %s: unable to allocate memory for keylen=%d",
+ pmInDomStr(indom), keylen);
+ return PM_ERR_GENERIC;
+ }
+ memcpy(e->key, key, keylen);
+ }
+ else
+ e->key = NULL;
+ e->state = PMDA_CACHE_ACTIVE;
+ e->private = private;
+ e->stamp = 0; /* flag, updated at next cache_save() */
+ h->hstate |= DIRTY_STAMP; /* timestamp needs updating */
+ break;
+
+ case PMDA_CACHE_HIDE:
+ e->state = PMDA_CACHE_INACTIVE;
+ break;
+
+ case PMDA_CACHE_CULL:
+ e->state = PMDA_CACHE_EMPTY;
+ /*
+ * we don't clean anything up, which may be a problem in the
+ * presence of lots of culling ... see redo_hash() for how
+ * the culled entries can be reclaimed
+ */
+ h->hstate |= DIRTY_INSTANCE; /* entry will not be saved */
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return e->inst;
+}
+
+int
+pmdaCacheStore(pmInDom indom, int flags, const char *name, void *private)
+{
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ return store(indom, flags, name, PM_IN_NULL, 0, NULL, private);
+}
+
+/*
+ * Generate a new 31-bit (positive) instance number from a key provided
+ * as a ``hint'' via key[] (first keylen bytes) or name[] if keylen < 1
+ * or key == NULL ... useful for compressing natural 64-bit or larger
+ * instance identifiers into the 31-bits required for the PCP APIs
+ * and PDUs.
+ */
+int
+pmdaCacheStoreKey(pmInDom indom, int flags, const char *name, int keylen, const void *key, void *private)
+{
+ int inst;
+ int sts;
+ int i;
+ __uint32_t try = 0;
+ hdr_t *h;
+ entry_t *e;
+ const char *mykey;
+ int mykeylen;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if (flags != PMDA_CACHE_ADD)
+ return store(indom, flags, name, PM_IN_NULL, 0, NULL, private);
+
+ /*
+ * This is the PMDA_CACHE_ADD case, so need to find an instance id
+ */
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ if (keylen < 1 || key == NULL) {
+ /* use name[] instead of keybuf[] */
+ mykey = (const char *)name;
+ mykeylen = strlen(name);
+ }
+ else {
+ mykey = key;
+ mykeylen = keylen;
+ }
+
+ if ((e = find_entry(h, name, PM_IN_NULL, &sts)) != NULL) {
+ /*
+ * cache entry already exists for this name ...
+ * if keys are not equal => failure
+ */
+ if (key_eq(e, mykeylen, mykey) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\" (%d) in cache ", pmInDomStr(indom), e->name, e->inst);
+ KeyStr(stderr, e->keylen, (const char *)e->key);
+ fprintf(stderr, " does not match new entry ");
+ KeyStr(stderr, mykeylen, mykey);
+ fputc('\n', stderr);
+ }
+#endif
+ return PM_ERR_INST;
+ }
+ /* keys the same, use inst from existing entry */
+ inst = e->inst;
+ }
+ else {
+ /* we're in the inst guessing game ... */
+ for (i = 0; i < MAX_HASH_TRY; i++) {
+ try = hash(mykey, mykeylen, try);
+ /* strip top bit ... instance id must be positive */
+ inst = try & ~(1 << (8*sizeof(__uint32_t)-1));
+ e = find_entry(h, NULL, inst, &sts);
+ if (e == NULL) {
+ h->keyhash_cnt[i]++;
+ break;
+ }
+ /*
+ * Found matching entry using the guessed inst ...
+ *
+ * If the key[]s are the same and the name[]s are the same
+ * then the matching entry is already in the cache, so use
+ * this instance identifier.
+ *
+ * If the key[]s match, but the name[]s are different, this
+ * is an error (duplicate instance name).
+ *
+ * If the names[] match, but the key[]s are different, this
+ * is an error (duplicate key).
+ *
+ * Otherwise instance id is in use for another instance, so
+ * keep trying by rehashing.
+ */
+ if (strcmp(e->name, name) == 0) {
+ if (key_eq(e, mykeylen, mykey) == 1)
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\" (%d) in cache, ", pmInDomStr(indom), e->name, e->inst);
+ KeyStr(stderr, e->keylen, (const char *)e->key);
+ fprintf(stderr, " does not match new entry ");
+ KeyStr(stderr, mykeylen, mykey);
+ fputc('\n', stderr);
+ }
+#endif
+ return PM_ERR_INST;
+ }
+ else if (key_eq(e, mykeylen, mykey) == 1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance %d ", pmInDomStr(indom), e->inst);
+ KeyStr(stderr, e->keylen, (const char *)e->key);
+ fprintf(stderr, " in cache, name \"%s\" does not match new entry \"%s\"\n", e->name, name);
+ }
+#endif
+ return PM_ERR_INST;
+ }
+ }
+ if (i == MAX_HASH_TRY) {
+ /* failed after MAX_HASH_TRY rehash attempts ... */
+ __pmNotifyErr(LOG_ERR,
+ "pmdaCacheStoreKey: indom %s: unable allocate a new id for instance \"%s\" based on a key of %d bytes\n",
+ pmInDomStr(h->indom), name, keylen);
+ return PM_ERR_GENERIC;
+ }
+
+ /*
+ * when using key[] or name[] as a hint, we permanently change
+ * to PMDA_CACHE_REUSE mode
+ */
+ h->ins_mode = 1;
+ }
+
+ return store(indom, flags, name, inst, mykeylen, mykey, private);
+}
+
+int pmdaCacheOp(pmInDom indom, int op)
+{
+ hdr_t *h;
+ entry_t *e;
+ int sts;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if (op == PMDA_CACHE_CHECK) {
+ /* is there a cache for this one? */
+ for (h = base; h != NULL; h = h->next) {
+ if (h->indom == indom)
+ return 1;
+ }
+ return 0;
+ }
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ switch (op) {
+ case PMDA_CACHE_LOAD:
+ return load_cache(h);
+
+ case PMDA_CACHE_SAVE:
+ return save_cache(h, DIRTY_INSTANCE);
+
+ case PMDA_CACHE_SYNC:
+ return save_cache(h, DIRTY_INSTANCE|DIRTY_STAMP);
+
+ case PMDA_CACHE_STRINGS:
+ /* must be set before any cache entries are added */
+ if (h->nentry > 0)
+ return -E2BIG;
+ h->hstate |= CACHE_STRINGS;
+ return 0;
+
+ case PMDA_CACHE_ACTIVE:
+ sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_INACTIVE) {
+ e->state = PMDA_CACHE_ACTIVE;
+ sts++;
+ }
+ }
+ /* no instances added or deleted, so no need to save */
+ return sts;
+
+ case PMDA_CACHE_INACTIVE:
+ sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_ACTIVE) {
+ e->state = PMDA_CACHE_INACTIVE;
+ sts++;
+ }
+ }
+ /* no instances added or deleted, so no need to save */
+ return sts;
+
+ case PMDA_CACHE_CULL:
+ sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state != PMDA_CACHE_EMPTY) {
+ e->state = PMDA_CACHE_EMPTY;
+ sts++;
+ }
+ }
+ if (sts > 0)
+ h->hstate |= DIRTY_INSTANCE; /* entries culled */
+ return sts;
+
+ case PMDA_CACHE_SIZE:
+ return h->nentry;
+
+ case PMDA_CACHE_SIZE_ACTIVE:
+ sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_ACTIVE)
+ sts++;
+ }
+ return sts;
+
+ case PMDA_CACHE_SIZE_INACTIVE:
+ sts = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ if (e->state == PMDA_CACHE_INACTIVE)
+ sts++;
+ }
+ return sts;
+
+ case PMDA_CACHE_REUSE:
+ h->ins_mode = 1;
+ return 0;
+
+ case PMDA_CACHE_REORG:
+ redo_hash(h, 0);
+ return 0;
+
+ case PMDA_CACHE_WALK_REWIND:
+ walk_cache(h, PMDA_CACHE_WALK_REWIND);
+ return 0;
+
+ case PMDA_CACHE_WALK_NEXT:
+ while ((e = walk_cache(h, PMDA_CACHE_WALK_NEXT)) != NULL) {
+ if (e->state == PMDA_CACHE_ACTIVE)
+ return e->inst;
+ }
+ return -1;
+
+ case PMDA_CACHE_DUMP:
+ dump(stderr, h, 0);
+ return 0;
+
+ case PMDA_CACHE_DUMP_ALL:
+ dump(stderr, h, 1);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+int pmdaCacheLookupName(pmInDom indom, const char *name, int *inst, void **private)
+{
+ hdr_t *h;
+ entry_t *e;
+ int sts;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ if ((e = find_entry(h, name, PM_IN_NULL, &sts)) == NULL) {
+ if (sts == 0) sts = PM_ERR_INST;
+ return sts;
+ }
+
+ if (private != NULL)
+ *private = e->private;
+
+ if (inst != NULL)
+ *inst = e->inst;
+
+ return e->state;
+}
+
+int pmdaCacheLookup(pmInDom indom, int inst, char **name, void **private)
+{
+ hdr_t *h;
+ entry_t *e;
+ int sts;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ if ((e = find_entry(h, NULL, inst, &sts)) == NULL) {
+ if (sts == 0) sts = PM_ERR_INST;
+ return sts;
+ }
+
+ if (name != NULL)
+ *name = e->name;
+ if (private != NULL)
+ *private = e->private;
+
+ return e->state;
+}
+
+int pmdaCacheLookupKey(pmInDom indom, const char *name, int keylen, const void *key, char **oname, int *inst, void **private)
+{
+ hdr_t *h;
+ entry_t *e;
+ int sts;
+ const char *mykey;
+ int mykeylen;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ if (keylen < 1 || key == NULL) {
+ /* use name[] instead of keybuf[] */
+ mykey = (const char *)name;
+ mykeylen = strlen(name);
+ }
+ else {
+ mykey = key;
+ mykeylen = keylen;
+ }
+
+ /*
+ * No hash list for key[]s ... have to walk the cache.
+ * pmdaCacheStoreKey() ensures the key[]s are unique, so first match
+ * wins.
+ */
+ walk_cache(h, PMDA_CACHE_WALK_REWIND);
+ while ((e = walk_cache(h, PMDA_CACHE_WALK_NEXT)) != NULL) {
+ if (e->state == PMDA_CACHE_EMPTY)
+ continue;
+ if (key_eq(e, mykeylen, mykey) == 1) {
+ if (oname != NULL)
+ *oname = e->name;
+ if (inst != NULL)
+ *inst = e->inst;
+ if (private != NULL)
+ *private = e->private;
+ return e->state;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ fprintf(stderr, "pmdaCacheLookupKey: indom %s: ", pmInDomStr(h->indom));
+ KeyStr(stderr, mykeylen, mykey);
+ fprintf(stderr, ": no matching key in cache\n");
+ }
+#endif
+ return PM_ERR_INST;
+}
+
+int pmdaCachePurge(pmInDom indom, time_t recent)
+{
+ hdr_t *h;
+ entry_t *e;
+ time_t epoch = time(NULL) - recent;
+ int cnt;
+ int sts;
+
+ if (indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((h = find_cache(indom, &sts)) == NULL)
+ return sts;
+
+ cnt = 0;
+ for (e = h->first; e != NULL; e = e->next) {
+ /*
+ * e->stamp == 0 => recently ACTIVE and no subsequent SAVE ...
+ * keep these ones
+ */
+ if (e->stamp != 0 && e->stamp < epoch) {
+ e->state = PMDA_CACHE_EMPTY;
+ cnt++;
+ }
+ }
+ if (cnt > 0)
+ h->hstate |= DIRTY_INSTANCE; /* entries marked empty */
+
+ return cnt;
+}
+
+/*
+--------------------------------------------------------------------
+lookup2.c, by Bob Jenkins, December 1996, Public Domain.
+hash(), hash2(), hash3, and mix() are externally useful functions.
+Routines to test the hash are included if SELF_TEST is defined.
+You can use this free for any purpose. It has no warranty.
+--------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+typedef unsigned long int ub4; /* unsigned 4-byte quantities */
+typedef unsigned char ub1;
+
+#define hashsize(n) ((ub4)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+
+/*
+--------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+For every delta with one or two bit set, and the deltas of all three
+ high bits or all three low bits, whether the original value of a,b,c
+ is almost all zero or is uniformly distributed,
+* If mix() is run forward or backward, at least 32 bits in a,b,c
+ have at least 1/4 probability of changing.
+* If mix() is run forward, every bit of c will change between 1/3 and
+ 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.)
+mix() was built out of 36 single-cycle latency instructions in a
+ structure that could supported 2x parallelism, like so:
+ a -= b;
+ a -= c; x = (c>>13);
+ b -= c; a ^= x;
+ b -= a; x = (a<<8);
+ c -= a; b ^= x;
+ c -= b; x = (b>>13);
+ ...
+ Unfortunately, superscalar Pentiums and Sparcs can't take advantage
+ of that parallelism. They've also turned some of those single-cycle
+ latency instructions into multi-cycle latency instructions. Still,
+ this is the fastest good hash I could find. There were about 2^^68
+ to choose from. I only looked at a billion or so.
+--------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= b; a -= c; a ^= (c>>13); \
+ b -= c; b -= a; b ^= (a<<8); \
+ c -= a; c -= b; c ^= (b>>13); \
+ a -= b; a -= c; a ^= (c>>12); \
+ b -= c; b -= a; b ^= (a<<16); \
+ c -= a; c -= b; c ^= (b>>5); \
+ a -= b; a -= c; a ^= (c>>3); \
+ b -= c; b -= a; b ^= (a<<10); \
+ c -= a; c -= b; c ^= (b>>15); \
+}
+
+/*
+--------------------------------------------------------------------
+hash() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ len : the length of the key, counting by bytes
+ level : can be any 4-byte value
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Every 1-bit and 2-bit delta achieves avalanche.
+About 36+6len instructions.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (ub1 **)k, do it like this:
+ for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);
+
+By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this
+code any way you wish, private, educational, or commercial. It's free.
+
+See http://burlteburtle.net/bob/hash/evahash.html
+Use for hash table lookup, or anything where one collision in 2^32 is
+acceptable. Do NOT use for cryptographic purposes.
+--------------------------------------------------------------------
+*/
+
+static __uint32_t
+hash(const char *k, int length, __uint32_t initval)
+{
+ __uint32_t a,b,c,len;
+
+ /* Set up the internal state */
+ len = length;
+ a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
+ c = initval; /* the previous hash value */
+
+ /*---------------------------------------- handle most of the key */
+ while (len >= 12)
+ {
+ a += (k[0] +((__uint32_t)k[1]<<8) +((__uint32_t)k[2]<<16)
+ +((__uint32_t)k[3]<<24));
+ b += (k[4] +((__uint32_t)k[5]<<8) +((__uint32_t)k[6]<<16)
+ +((__uint32_t)k[7]<<24));
+ c += (k[8] +((__uint32_t)k[9]<<8)
+ +((__uint32_t)k[10]<<16)+((__uint32_t)k[11]<<24));
+ mix(a,b,c);
+ k += 12; len -= 12;
+ }
+
+ /*------------------------------------- handle the last 11 bytes */
+ c += length;
+ switch(len) /* all the case statements fall through */
+ {
+ case 11: c+=((__uint32_t)k[10]<<24);
+ case 10: c+=((__uint32_t)k[9]<<16);
+ case 9 : c+=((__uint32_t)k[8]<<8);
+ /* the first byte of c is reserved for the length */
+ case 8 : b+=((__uint32_t)k[7]<<24);
+ case 7 : b+=((__uint32_t)k[6]<<16);
+ case 6 : b+=((__uint32_t)k[5]<<8);
+ case 5 : b+=k[4];
+ case 4 : a+=((__uint32_t)k[3]<<24);
+ case 3 : a+=((__uint32_t)k[2]<<16);
+ case 2 : a+=((__uint32_t)k[1]<<8);
+ case 1 : a+=k[0];
+ /* case 0: nothing left to add */
+ }
+ mix(a,b,c);
+ /*-------------------------------------------- report the result */
+ return c;
+}
diff --git a/src/libpcp_pmda/src/callback.c b/src/libpcp_pmda/src/callback.c
new file mode 100644
index 0000000..c1a1d86
--- /dev/null
+++ b/src/libpcp_pmda/src/callback.c
@@ -0,0 +1,753 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "libdefs.h"
+
+/*
+ * count the number of instances in an instance domain
+ */
+
+int
+__pmdaCntInst(pmInDom indom, pmdaExt *pmda)
+{
+ int i;
+ int sts = 0;
+
+ if (indom == PM_INDOM_NULL)
+ return 1;
+ if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) {
+ sts = pmdaCacheOp(indom, PMDA_CACHE_SIZE_ACTIVE);
+ }
+ else {
+ for (i = 0; i < pmda->e_nindoms; i++) {
+ if (pmda->e_indoms[i].it_indom == indom) {
+ sts = pmda->e_indoms[i].it_numinst;
+ break;
+ }
+ }
+ if (i == pmda->e_nindoms) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_WARNING, "cntinst: unknown indom %s", pmInDomStr_r(indom, strbuf, sizeof(strbuf)));
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ char strbuf[20];
+ fprintf(stderr, "__pmdaCntInst(indom=%s) -> %d\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf)), sts);
+ }
+#endif
+
+ return sts;
+}
+
+/*
+ * commence a new round of instance selection
+ */
+
+static pmdaIndom last;
+
+/*
+ * State between here and __pmdaNextInst is a little strange
+ *
+ * for the classical method,
+ * - pmda->e_idp is set here (points into indomtab[]) and
+ * pmda->e_idp->it_indom is used in __pmdaNextInst
+ *
+ * for the cache method
+ * - pmda->e_idp is set here (points into last) which is also set
+ * up with the it_indom field (other fields in last are not used),
+ * and pmda->e_idp->it_indom in __pmdaNextInst
+ *
+ * In both cases, pmda->e_ordinal and pmda->e_singular are set here
+ * and updated in __pmdaNextInst.
+ *
+ * As in most other places, this is not thread-safe and we assume we
+ * call __pmdaStartInst and then repeatedly call __pmdaNextInst all
+ * for the same indom, before calling __pmdaStartInst again.
+ *
+ * If we could do this again, adding an indom argument to __pmdaNextInst
+ * would be a better design, but the API to __pmdaNextInst has escaped.
+ */
+void
+__pmdaStartInst(pmInDom indom, pmdaExt *pmda)
+{
+ int i;
+
+ pmda->e_ordinal = pmda->e_singular = -1;
+ if (indom == PM_INDOM_NULL) {
+ /* singular value */
+ pmda->e_singular = 0;
+ }
+ else {
+ if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) {
+ pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND);
+ last.it_indom = indom;
+ pmda->e_idp = &last;
+ pmda->e_ordinal = 0;
+ }
+ else {
+ for (i = 0; i < pmda->e_nindoms; i++) {
+ if (pmda->e_indoms[i].it_indom == indom) {
+ /* multiple values are possible */
+ pmda->e_idp = &pmda->e_indoms[i];
+ pmda->e_ordinal = 0;
+ break;
+ }
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ char strbuf[20];
+ fprintf(stderr, "__pmdaStartInst(indom=%s) e_ordinal=%d\n",
+ pmInDomStr_r(indom, strbuf, sizeof(strbuf)), pmda->e_ordinal);
+ }
+#endif
+ return;
+}
+
+/*
+ * select the next instance
+ */
+
+int
+__pmdaNextInst(int *inst, pmdaExt *pmda)
+{
+ int j;
+ int myinst;
+
+ if (pmda->e_singular == 0) {
+ /* PM_INDOM_NULL ... just the one value */
+ *inst = 0;
+ pmda->e_singular = -1;
+ return 1;
+ }
+ if (pmda->e_ordinal >= 0) {
+ /* scan for next value in the profile */
+ if (pmda->e_idp == &last) {
+ /* cache-driven */
+ while ((myinst = pmdaCacheOp(pmda->e_idp->it_indom, PMDA_CACHE_WALK_NEXT)) != -1) {
+ pmda->e_ordinal++;
+ if (__pmInProfile(pmda->e_idp->it_indom, pmda->e_prof, myinst)) {
+ *inst = myinst;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ char strbuf[20];
+ fprintf(stderr, "__pmdaNextInst(indom=%s) -> %d e_ordinal=%d (cache)\n",
+ pmInDomStr_r(pmda->e_idp->it_indom, strbuf, sizeof(strbuf)), myinst, pmda->e_ordinal);
+ }
+#endif
+ return 1;
+ }
+ }
+ }
+ else {
+ /* indomtab[]-driven */
+ for (j = pmda->e_ordinal; j < pmda->e_idp->it_numinst; j++) {
+ if (__pmInProfile(pmda->e_idp->it_indom,
+ pmda->e_prof,
+ pmda->e_idp->it_set[j].i_inst)) {
+ *inst = pmda->e_idp->it_set[j].i_inst;
+ pmda->e_ordinal = j+1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_INDOM) {
+ char strbuf[20];
+ fprintf(stderr, "__pmdaNextInst(indom=%s) -> %d e_ordinal=%d\n",
+ pmInDomStr_r(pmda->e_idp->it_indom, strbuf, sizeof(strbuf)), *inst, pmda->e_ordinal);
+ }
+#endif
+ return 1;
+ }
+ }
+ }
+ pmda->e_ordinal = -1;
+ }
+ return 0;
+}
+
+
+/*
+ * Helper routines for performing metric table searches.
+ *
+ * There are currently three ways - using the PMID hash that hangs off
+ * of the e_ext structure, using the direct mapping mechanism (require
+ * PMIDs be allocated one after-the-other, all in one cluster), or via
+ * a linear search of the metric table array.
+ */
+
+static pmdaMetric *
+__pmdaHashedSearch(pmID pmid, __pmHashCtl *hash)
+{
+ __pmHashNode *node;
+
+ if ((node = __pmHashSearch(pmid, hash)) == NULL)
+ return NULL;
+ return (pmdaMetric *)node->data;
+}
+
+static pmdaMetric *
+__pmdaDirectSearch(pmID pmid, pmdaExt *pmda)
+{
+ __pmID_int *pmidp = (__pmID_int *)&pmid;
+
+ /*
+ * pmidp->domain is correct ... PMCD guarantees this, but
+ * pmda->e_direct only works for a single cluster
+ */
+ if (pmidp->item < pmda->e_nmetrics &&
+ pmidp->cluster ==
+ ((__pmID_int *)&pmda->e_metrics[pmidp->item].m_desc.pmid)->cluster) {
+ /* pmidp->item is unsigned, so must be >= 0 */
+ return &pmda->e_metrics[pmidp->item];
+ }
+ return NULL;
+}
+
+static pmdaMetric *
+__pmdaLinearSearch(pmID pmid, pmdaExt *pmda)
+{
+ int i;
+
+ for (i = 0; i < pmda->e_nmetrics; i++) {
+ if (pmda->e_metrics[i].m_desc.pmid == pmid) {
+ /* found the hard way */
+ return &pmda->e_metrics[i];
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Save the profile away for use in __pmdaNextInst() during subsequent
+ * fetches ... it is the _caller_ of pmdaProfile()'s responsibility to
+ * ensure that the profile is not freed while it is being used here.
+ *
+ * For DSO pmdas, the profiles are managed per client in DoProfile() and
+ * DeleteClient() but sent to the pmda in SendFetch()
+ *
+ * For daemon pmdas, the profile is received from pmcd in __pmdaMainPDU
+ * and the last received profile is held there
+ */
+
+int
+pmdaProfile(__pmProfile *prof, pmdaExt *pmda)
+{
+ pmda->e_prof = prof;
+ return 0;
+}
+
+/*
+ * return desciption of an instance or instance domain
+ */
+
+int
+pmdaInstance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ int i;
+ int namelen;
+ int err = 0;
+ __pmInResult *res;
+ pmdaIndom *idp = NULL; /* initialize to pander to gcc */
+ int have_cache = 0;
+ int myinst;
+ char *np;
+
+ if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) {
+ have_cache = 1;
+ }
+ else {
+ /*
+ * check this is an instance domain we know about -- code below
+ * assumes this test is complete
+ */
+ for (i = 0; i < pmda->e_nindoms; i++) {
+ if (pmda->e_indoms[i].it_indom == indom)
+ break;
+ }
+ if (i >= pmda->e_nindoms)
+ return PM_ERR_INDOM;
+ idp = &pmda->e_indoms[i];
+ }
+
+ if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL)
+ return -oserror();
+ res->indom = indom;
+
+ if (name == NULL && inst == PM_IN_NULL)
+ res->numinst = __pmdaCntInst(indom, pmda);
+ else
+ res->numinst = 1;
+
+ if (inst == PM_IN_NULL) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ free(res);
+ return -oserror();
+ }
+ }
+ else
+ res->instlist = NULL;
+
+ if (name == NULL) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (i = 0; i < res->numinst; i++)
+ res->namelist[0] = NULL;
+ }
+ else
+ res->namelist = NULL;
+
+ if (name == NULL && inst == PM_IN_NULL) {
+ /* return inst and name for everything */
+ if (have_cache) {
+ pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND);
+ i = 0;
+ while (i < res->numinst && (myinst = pmdaCacheOp(indom, PMDA_CACHE_WALK_NEXT)) != -1) {
+ if (pmdaCacheLookup(indom, myinst, &np, NULL) != PMDA_CACHE_ACTIVE)
+ continue;
+
+ res->instlist[i] = myinst;
+ if ((res->namelist[i++] = strdup(np)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ }
+ else {
+ for (i = 0; i < res->numinst; i++) {
+ res->instlist[i] = idp->it_set[i].i_inst;
+ if ((res->namelist[i] = strdup(idp->it_set[i].i_name)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ }
+ }
+ else if (name == NULL) {
+ /* given an inst, return the name */
+ if (have_cache) {
+ if (pmdaCacheLookup(indom, inst, &np, NULL) == PMDA_CACHE_ACTIVE) {
+ if ((res->namelist[0] = strdup(np)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ else
+ err = 1;
+ }
+ else {
+ for (i = 0; i < idp->it_numinst; i++) {
+ if (inst == idp->it_set[i].i_inst) {
+ if ((res->namelist[0] = strdup(idp->it_set[i].i_name)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ break;
+ }
+ }
+ if (i == idp->it_numinst)
+ err = 1;
+ }
+ }
+ else if (inst == PM_IN_NULL && (namelen = (int)strlen(name)) > 0) {
+ if (have_cache) {
+ if (pmdaCacheLookupName(indom, name, &myinst, NULL) == PMDA_CACHE_ACTIVE)
+ res->instlist[0] = myinst;
+ else
+ err = 1;
+ }
+ else {
+ /* given a name, return an inst. If the name contains spaces,
+ * only exact matches are good enough for us, otherwise, we're
+ * prepared to accept a match upto the first space in the
+ * instance name on the assumption that pmdas will play by the
+ * rules and guarantee the first "word" in the instance name
+ * is unique. That allows for the things like "1 5 15" to match
+ * instances for kernel.all.load["1 minute","5 minute","15 minutes"]
+ */
+ char * nspace = strchr (name, ' ');
+
+ for (i = 0; i < idp->it_numinst; i++) {
+ char *instname = idp->it_set[i].i_name;
+ if (strcmp(name, instname) == 0) {
+ /* accept an exact match */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr,
+ "pmdaInstance: exact match name=%s id=%d\n",
+ name, idp->it_set[i].i_inst);
+ }
+#endif
+ res->instlist[0] = idp->it_set[i].i_inst;
+ break;
+ }
+ else if (nspace == NULL) {
+ /* all of name must match instname up to the the first
+ * space in instname. */
+ char *p = strchr(instname, ' ');
+ if (p != NULL) {
+ int len = (int)(p - instname);
+ if (namelen == len && strncmp(name, instname, len) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "pmdaInstance: matched argument name=\"%s\" with indom id=%d name=\"%s\" len=%d\n",
+ name, idp->it_set[i].i_inst, instname, len);
+ }
+#endif
+ res->instlist[0] = idp->it_set[i].i_inst;
+ break;
+ }
+ }
+ }
+ }
+ if (i == idp->it_numinst)
+ err = 1;
+ }
+ }
+ else
+ err = 1;
+ if (err == 1) {
+ /* bogus arguments or instance id/name */
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+
+ *result = res;
+ return 0;
+}
+
+/*
+ * resize the pmResult and call the e_callback for each metric instance
+ * required in the profile.
+ */
+
+int
+pmdaFetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i; /* over pmidlist[] */
+ int j; /* over metatab and vset->vlist[] */
+ int sts;
+ int need;
+ int inst;
+ int numval;
+ pmValueSet *vset;
+ pmDesc *dp;
+ pmdaMetric *metap;
+ pmAtomValue atom;
+ int type;
+ e_ext_t *extp = (e_ext_t *)pmda->e_ext;
+
+ if (extp->pmda_interface >= PMDA_INTERFACE_5)
+ __pmdaSetContext(pmda->e_context);
+
+ if (numpmid > extp->maxnpmids) {
+ if (extp->res != NULL)
+ free(extp->res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *);
+ if ((extp->res = (pmResult *) malloc(need)) == NULL)
+ return -oserror();
+ extp->maxnpmids = numpmid;
+ }
+
+ extp->res->timestamp.tv_sec = 0;
+ extp->res->timestamp.tv_usec = 0;
+ extp->res->numpmid = numpmid;
+
+ for (i = 0; i < numpmid; i++) {
+ if (pmda->e_flags & PMDA_EXT_FLAG_HASHED)
+ metap = __pmdaHashedSearch(pmidlist[i], &extp->hashpmids);
+ else if (pmda->e_direct)
+ metap = __pmdaDirectSearch(pmidlist[i], pmda);
+ else
+ metap = __pmdaLinearSearch(pmidlist[i], pmda);
+
+ if (metap) {
+ dp = &(metap->m_desc);
+ if (dp->indom != PM_INDOM_NULL) {
+ /* count instances in the profile */
+ numval = 0;
+ /* count instances in indom */
+ __pmdaStartInst(dp->indom, pmda);
+ while (__pmdaNextInst(&inst, pmda)) {
+ numval++;
+ }
+ }
+ else {
+ /* singular instance domains */
+ numval = 1;
+ }
+ }
+ else {
+ dp = NULL;
+ /* dynamic name metrics may often vanish, avoid log spam */
+ if (extp->pmda_interface < PMDA_INTERFACE_4) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: Requested metric %s is not defined",
+ pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf)));
+ }
+ numval = PM_ERR_PMID;
+ }
+
+
+ /* Must use individual malloc()s because of pmFreeResult() */
+ if (numval >= 1)
+ extp->res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ (numval - 1)*sizeof(pmValue));
+ else
+ extp->res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+ if (vset == NULL) {
+ sts = -oserror();
+ goto error;
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = numval;
+ vset->valfmt = PM_VAL_INSITU;
+ if (vset->numval <= 0)
+ continue;
+
+ if (dp->indom == PM_INDOM_NULL)
+ inst = PM_IN_NULL;
+ else {
+ __pmdaStartInst(dp->indom, pmda);
+ __pmdaNextInst(&inst, pmda);
+ }
+ type = dp->type;
+ j = 0;
+ do {
+ if (j == numval) {
+ /* more instances than expected! */
+ numval++;
+ extp->res->vset[i] = vset = (pmValueSet *)realloc(vset,
+ sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue));
+ if (vset == NULL) {
+ sts = -oserror();
+ goto error;
+ }
+ }
+ vset->vlist[j].inst = inst;
+
+ if ((sts = (*(pmda->e_fetchCallBack))(metap, inst, &atom)) < 0) {
+ char strbuf[20];
+
+ pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf));
+ if (sts == PM_ERR_PMID) {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: PMID %s not handled by fetch callback\n",
+ strbuf);
+ }
+ else if (sts == PM_ERR_INST) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: Instance %d of PMID %s not handled by fetch callback\n",
+ inst, strbuf);
+ }
+#endif
+ }
+ else if (sts == PM_ERR_APPVERSION ||
+ sts == PM_ERR_PERMISSION ||
+ sts == PM_ERR_AGAIN ||
+ sts == PM_ERR_NYI) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: Unavailable metric PMID %s[%d]\n",
+ strbuf, inst);
+ }
+#endif
+ }
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: Fetch callback error from metric PMID %s[%d]: %s\n",
+ strbuf, inst, pmErrStr(sts));
+ }
+ }
+ else {
+ /*
+ * PMDA_INTERFACE_2
+ * >= 0 => OK
+ * PMDA_INTERFACE_3 or PMDA_INTERFACE_4
+ * == 0 => no values
+ * > 0 => OK
+ * PMDA_INTERFACE_5 or later
+ * == 0 (PMDA_FETCH_NOVALUES) => no values
+ * == 1 (PMDA_FETCH_STATIC) or > 2 => OK
+ * == 2 (PMDA_FETCH_DYNAMIC) => OK and free(atom.vp)
+ * after __pmStuffValue() called
+ */
+ if (extp->pmda_interface == PMDA_INTERFACE_2 ||
+ (extp->pmda_interface >= PMDA_INTERFACE_3 && sts > 0)) {
+ int lsts;
+
+ if ((lsts = __pmStuffValue(&atom, &vset->vlist[j], type)) == PM_ERR_TYPE) {
+ char strbuf[20];
+ char st2buf[20];
+ __pmNotifyErr(LOG_ERR,
+ "pmdaFetch: Descriptor type (%s) for metric %s is bad",
+ pmTypeStr_r(type, strbuf, sizeof(strbuf)),
+ pmIDStr_r(dp->pmid, st2buf, sizeof(st2buf)));
+ }
+ else if (lsts >= 0) {
+ vset->valfmt = lsts;
+ j++;
+ }
+ if (extp->pmda_interface >= PMDA_INTERFACE_5 && sts == PMDA_FETCH_DYNAMIC) {
+ if (type == PM_TYPE_STRING)
+ free(atom.cp);
+ else if (type == PM_TYPE_AGGREGATE)
+ free(atom.vbp);
+ else {
+ char strbuf[20];
+ char st2buf[20];
+ __pmNotifyErr(LOG_WARNING,
+ "pmdaFetch: Attempt to free value for metric %s of wrong type %s\n",
+ pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)),
+ pmTypeStr_r(type, st2buf, sizeof(st2buf)));
+ }
+ }
+ if (lsts < 0)
+ sts = lsts;
+ }
+ }
+ } while (dp->indom != PM_INDOM_NULL && __pmdaNextInst(&inst, pmda));
+
+ if (j == 0)
+ vset->numval = sts;
+ else
+ vset->numval = j;
+
+ }
+ *resp = extp->res;
+ return 0;
+
+ error:
+
+ if (i) {
+ extp->res->numpmid = i;
+ __pmFreeResultValues(extp->res);
+ }
+ return sts;
+}
+
+/*
+ * Return the metric description
+ */
+
+int
+pmdaDesc(pmID pmid, pmDesc *desc, pmdaExt *pmda)
+{
+ e_ext_t *extp = (e_ext_t *)pmda->e_ext;
+ pmdaMetric *metric;
+ char strbuf[32];
+
+ if (extp->pmda_interface >= PMDA_INTERFACE_5)
+ __pmdaSetContext(pmda->e_context);
+
+ if (pmda->e_flags & PMDA_EXT_FLAG_HASHED)
+ metric = __pmdaHashedSearch(pmid, &extp->hashpmids);
+ else if (pmda->e_direct)
+ metric = __pmdaDirectSearch(pmid, pmda);
+ else
+ metric = __pmdaLinearSearch(pmid, pmda);
+
+ if (metric) {
+ *desc = metric->m_desc;
+ return 0;
+ }
+
+ __pmNotifyErr(LOG_ERR, "Requested metric %s is not defined",
+ pmIDStr_r(pmid, strbuf, sizeof(strbuf)));
+ return PM_ERR_PMID;
+}
+
+/*
+ * Return the help text for a metric
+ */
+
+int
+pmdaText(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ e_ext_t *extp = (e_ext_t *)pmda->e_ext;
+
+ if (extp->pmda_interface >= PMDA_INTERFACE_5)
+ __pmdaSetContext(pmda->e_context);
+
+ if (pmda->e_help >= 0) {
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID)
+ *buffer = pmdaGetHelp(pmda->e_help, (pmID)ident, type);
+ else
+ *buffer = pmdaGetInDomHelp(pmda->e_help, (pmInDom)ident, type);
+ }
+ else
+ *buffer = NULL;
+
+ return (*buffer == NULL) ? PM_ERR_TEXT : 0;
+}
+
+/*
+ * Tell PMCD there is nothing to store
+ */
+
+int
+pmdaStore(pmResult *result, pmdaExt *pmda)
+{
+ return PM_ERR_PERMISSION;
+}
+
+/*
+ * Expect routines pmdaPMID(), pmdaName() and pmdaChildren() below
+ * to be overridden with real routines for any PMDA that is
+ * using PMDA_INTERFACE_4 or later and supporting dynamic metrics.
+ *
+ * Expect the pmdaAttribute() routine to be overridden with a real
+ * routine for a PMDA using PMDA_INTERFACE_6 or later supporting
+ * metrics whose behaviour depends on clients being authenticated.
+ *
+ * These implementations are stubs that return appropriate errors
+ * if they are ever called.
+ */
+
+int
+pmdaPMID(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ return PM_ERR_NAME;
+}
+
+int
+pmdaName(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ return PM_ERR_PMID;
+}
+
+int
+pmdaChildren(const char *name, int traverse, char ***offspring, int **status, pmdaExt *pmda)
+{
+ return PM_ERR_NAME;
+}
+
+int
+pmdaAttribute(int context, int attribute, const char *value, int size, pmdaExt *pmda)
+{
+ return 0; /* simply ignore everything by default */
+}
diff --git a/src/libpcp_pmda/src/context.c b/src/libpcp_pmda/src/context.c
new file mode 100644
index 0000000..fb0f0e2
--- /dev/null
+++ b/src/libpcp_pmda/src/context.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+static int last_ctx = -1; /* not thread safe! */
+
+void
+__pmdaSetContext(int ctx)
+{
+ last_ctx = ctx;
+}
+
+int
+pmdaGetContext(void)
+{
+ return last_ctx;
+}
diff --git a/src/libpcp_pmda/src/dynamic.c b/src/libpcp_pmda/src/dynamic.c
new file mode 100644
index 0000000..3d6e73d
--- /dev/null
+++ b/src/libpcp_pmda/src/dynamic.c
@@ -0,0 +1,217 @@
+/*
+ * Dynamic namespace metrics, PMDA helper routines.
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+static struct dynamic {
+ const char *prefix;
+ int prefixlen;
+ int mtabcount; /* internal use only */
+ int extratrees; /* internal use only */
+ int nclusters;
+ int *clusters;
+ pmdaUpdatePMNS pmnsupdate;
+ pmdaUpdateText textupdate;
+ pmdaUpdateMetric mtabupdate;
+ pmdaCountMetrics mtabcounts;
+ pmdaNameSpace *pmns;
+ pmdaMetric *metrics; /* original fixed table */
+ int nmetrics; /* fixed metrics number */
+ unsigned int clustermask; /* mask out parts of PMID cluster */
+} *dynamic;
+
+static int dynamic_count;
+
+static inline unsigned int
+dynamic_pmid_cluster(int index, pmID pmid)
+{
+ if (dynamic[index].clustermask)
+ return pmid_cluster(pmid) & dynamic[index].clustermask;
+ return pmid_cluster(pmid);
+}
+
+int
+pmdaDynamicSetClusterMask(const char *prefix, unsigned int mask)
+{
+ int i;
+
+ for (i = 0; i < dynamic_count; i++) {
+ if (strcmp(prefix, dynamic[i].prefix) == 0)
+ continue;
+ dynamic[i].clustermask = mask;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+void
+pmdaDynamicPMNS(const char *prefix,
+ int *clusters, int nclusters,
+ pmdaUpdatePMNS pmnsupdate, pmdaUpdateText textupdate,
+ pmdaUpdateMetric mtabupdate, pmdaCountMetrics mtabcounts,
+ pmdaMetric *metrics, int nmetrics)
+{
+ int size = (dynamic_count+1) * sizeof(struct dynamic);
+ int *ctab;
+ size_t ctabsz;
+
+ if ((dynamic = (struct dynamic *)realloc(dynamic, size)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "out-of-memory registering dynamic metrics");
+ return;
+ }
+ ctabsz = sizeof(int) * nclusters;
+ if ((ctab = (int *)malloc(ctabsz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "out-of-memory registering dynamic clusters");
+ free(dynamic);
+ return;
+ }
+ dynamic[dynamic_count].prefix = prefix;
+ dynamic[dynamic_count].prefixlen = strlen(prefix);
+ dynamic[dynamic_count].nclusters = nclusters;
+ dynamic[dynamic_count].clusters = ctab;
+ memcpy(dynamic[dynamic_count].clusters, clusters, ctabsz);
+ dynamic[dynamic_count].pmnsupdate = pmnsupdate;
+ dynamic[dynamic_count].textupdate = textupdate;
+ dynamic[dynamic_count].mtabupdate = mtabupdate;
+ dynamic[dynamic_count].mtabcounts = mtabcounts;
+ dynamic[dynamic_count].pmns = NULL;
+ dynamic[dynamic_count].metrics = metrics;
+ dynamic[dynamic_count].nmetrics = nmetrics;
+ dynamic[dynamic_count].clustermask = 0;
+ dynamic_count++;
+}
+
+pmdaNameSpace *
+pmdaDynamicLookupName(pmdaExt *pmda, const char *name)
+{
+ int i;
+
+ for (i = 0; i < dynamic_count; i++) {
+ if (strncmp(name, dynamic[i].prefix, dynamic[i].prefixlen) == 0) {
+ if (dynamic[i].pmnsupdate(pmda, &dynamic[i].pmns))
+ pmdaDynamicMetricTable(pmda);
+ return dynamic[i].pmns;
+ }
+ }
+ return NULL;
+}
+
+pmdaNameSpace *
+pmdaDynamicLookupPMID(pmdaExt *pmda, pmID pmid)
+{
+ int i, j, cluster;
+
+ for (i = 0; i < dynamic_count; i++) {
+ cluster = dynamic_pmid_cluster(i, pmid);
+ for (j = 0; j < dynamic[i].nclusters; j++) {
+ if (cluster == dynamic[i].clusters[j]) {
+ if (dynamic[i].pmnsupdate(pmda, &dynamic[i].pmns))
+ pmdaDynamicMetricTable(pmda);
+ return dynamic[i].pmns;
+ }
+ }
+ }
+ return NULL;
+}
+
+int
+pmdaDynamicLookupText(pmID pmid, int type, char **buf, pmdaExt *pmda)
+{
+ int i, j;
+
+ for (i = 0; i < dynamic_count; i++) {
+ int cluster = dynamic_pmid_cluster(i, pmid);
+ for (j = 0; j < dynamic[i].nclusters; j++)
+ if (cluster == dynamic[i].clusters[j])
+ return dynamic[i].textupdate(pmda, pmid, type, buf);
+ }
+ return -ENOENT;
+}
+
+/*
+ * Call the update function for each new metric we're adding.
+ * We pass in the original metric, and the new (uninit'd) slot
+ * which needs to be filled in. All a bit obscure, really.
+ */
+static pmdaMetric *
+dynamic_metric_table(int index, pmdaMetric *offset)
+{
+ struct dynamic *dp = &dynamic[index];
+ int m, tree_count = dp->extratrees;
+
+ for (m = 0; m < dp->nmetrics; m++) {
+ pmdaMetric *mp = &dp->metrics[m];
+ int cluster = dynamic_pmid_cluster(index, mp->m_desc.pmid);
+ int c, gid;
+
+ for (c = 0; c < dp->nclusters; c++)
+ if (dp->clusters[c] == cluster)
+ break;
+ if (c < dp->nclusters)
+ for (gid = 0; gid < tree_count; gid++)
+ dp->mtabupdate(mp, offset++, gid + 1);
+ }
+ return offset;
+}
+
+/*
+ * Iterate through the dynamic table working out how many additional metric
+ * table entries are needed. Then allocate a new metric table, if needed,
+ * and run through the dynamic table once again to fill in the additional
+ * entries. Finally, we update the metric table pointer within the pmdaExt
+ * for libpcp_pmda callback routines subsequent use.
+ */
+void
+pmdaDynamicMetricTable(pmdaExt *pmda)
+{
+ int i, trees, total, resize = 0;
+ pmdaMetric *mtab, *fixed, *offset;
+
+ for (i = 0; i < dynamic_count; i++)
+ dynamic[i].mtabcount = dynamic[i].extratrees = 0;
+
+ for (i = 0; i < dynamic_count; i++) {
+ dynamic[i].mtabcounts(&total, &trees);
+ dynamic[i].mtabcount += total;
+ dynamic[i].extratrees += trees;
+ resize += (total * trees);
+ }
+
+ fixed = dynamic[0].metrics; /* fixed metrics */
+ total = dynamic[0].nmetrics; /* and the count */
+
+ if (resize == 0) {
+ /* Fits into the default metric table - reset it to original values */
+fallback:
+ if (pmda->e_metrics != fixed)
+ free(pmda->e_metrics);
+ pmdaRehash(pmda, fixed, total);
+ } else {
+ resize += total;
+ if ((mtab = calloc(resize, sizeof(pmdaMetric))) == NULL)
+ goto fallback;
+ memcpy(mtab, fixed, total * sizeof(pmdaMetric));
+ offset = mtab + total;
+ for (i = 0; i < dynamic_count; i++)
+ offset = dynamic_metric_table(i, offset);
+ if (pmda->e_metrics != fixed)
+ free(pmda->e_metrics);
+ pmdaRehash(pmda, mtab, resize);
+ }
+}
diff --git a/src/libpcp_pmda/src/events.c b/src/libpcp_pmda/src/events.c
new file mode 100644
index 0000000..09abb8d
--- /dev/null
+++ b/src/libpcp_pmda/src/events.c
@@ -0,0 +1,391 @@
+/*
+ * Service routines for managing a packed array of event records
+ *
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+typedef struct {
+ char *baddr; /* base address of the buffer */
+ char *bptr; /* next location to be filled in the buffer */
+ void *berp; /* current pmEvent[HighRes]Record in buffer */
+ int blen; /* buffer size */
+ int bstate;
+} bufctl_t;
+
+#define B_FREE 0
+#define B_INUSE 1
+
+static int nbuf;
+static bufctl_t *bufs;
+
+static int
+check_buf(bufctl_t *bp, int need)
+{
+ int offset = bp->bptr - bp->baddr;
+ int er_offset = (char *)bp->berp - bp->baddr;
+
+ while (bp->blen == 0 || &bp->bptr[need] >= &bp->baddr[bp->blen-1]) {
+ if (bp->blen == 0)
+ /* first time, punt on a 512 byte buffer */
+ bp->blen = 512;
+ else
+ bp->blen *= 2;
+ if ((bp->baddr = (char *)realloc(bp->baddr, bp->blen)) == NULL)
+ return -oserror();
+ bp->bptr = &bp->baddr[offset];
+ bp->berp = (void *)&bp->baddr[er_offset];
+ }
+ return 0;
+}
+
+static int
+event_array(void)
+{
+ int i;
+
+ for (i = 0; i < nbuf; i++) {
+ if (bufs[i].bstate == B_FREE)
+ break;
+ }
+
+ if (i == nbuf) {
+ nbuf++;
+ bufs = (bufctl_t *)realloc(bufs, nbuf*sizeof(bufs[0]));
+ if (bufs == NULL) {
+ nbuf = 0;
+ return -oserror();
+ }
+ }
+
+ bufs[i].bptr = bufs[i].baddr = NULL;
+ bufs[i].blen = 0;
+ bufs[i].bstate = B_INUSE;
+ return i;
+}
+
+int
+pmdaEventNewArray(void)
+{
+ int sts = event_array();
+
+ if (sts >= 0)
+ pmdaEventResetArray(sts);
+ return sts;
+}
+
+int
+pmdaEventNewHighResArray(void)
+{
+ int sts = event_array();
+
+ if (sts >= 0)
+ pmdaEventResetHighResArray(sts);
+ return sts;
+}
+
+/* prepare to reuse an array */
+int
+pmdaEventResetArray(int idx)
+{
+ bufctl_t *bp;
+ pmEventArray *eap;
+ int sts;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+ if ((sts = check_buf(bp, sizeof(pmEventArray) - sizeof(pmEventRecord))) < 0)
+ return sts;
+
+ eap = (pmEventArray *)bp->baddr;
+ eap->ea_nrecords = 0;
+ bp->bptr = bp->baddr + sizeof(pmEventArray) - sizeof(pmEventRecord);
+ return 0;
+}
+
+int
+pmdaEventResetHighResArray(int idx)
+{
+ bufctl_t *bp;
+ pmHighResEventArray *hreap;
+ int sts;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+ if ((sts = check_buf(bp, sizeof(*hreap) - sizeof(pmHighResEventRecord))) < 0)
+ return sts;
+
+ hreap = (pmHighResEventArray *)bp->baddr;
+ hreap->ea_nrecords = 0;
+ bp->bptr = bp->baddr + sizeof(*hreap) - sizeof(pmHighResEventRecord);
+ return 0;
+}
+
+/* release buffer space associated with a packed event array */
+int
+pmdaEventReleaseArray(int idx)
+{
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+
+ free(bufs[idx].baddr);
+ bufs[idx].bstate = B_FREE;
+ return 0;
+}
+
+int
+pmdaEventReleaseHighResArray(int idx)
+{
+ return pmdaEventReleaseArray(idx);
+}
+
+int
+pmdaEventAddRecord(int idx, struct timeval *tp, int flags)
+{
+ int sts;
+ bufctl_t *bp;
+ pmEventArray *eap;
+ pmEventRecord *erp;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+
+ /* use pmdaEventAddMissedRecord for missed records ... */
+ if (flags & PM_EVENT_FLAG_MISSED)
+ return PM_ERR_CONV;
+
+ if ((sts = check_buf(bp, sizeof(*erp) - sizeof(pmEventParameter))) < 0)
+ return sts;
+ eap = (pmEventArray *)bp->baddr;
+ eap->ea_nrecords++;
+ erp = (pmEventRecord *)bp->bptr;
+ erp->er_timestamp.tv_sec = (__int32_t)tp->tv_sec;
+ erp->er_timestamp.tv_usec = (__int32_t)tp->tv_usec;
+ erp->er_nparams = 0;
+ erp->er_flags = flags;
+ bp->berp = (void *)erp;
+ bp->bptr += sizeof(pmEventRecord) - sizeof(pmEventParameter);
+ return 0;
+}
+
+int
+pmdaEventAddHighResRecord(int idx, struct timespec *ts, int flags)
+{
+ int sts;
+ bufctl_t *bp;
+ pmHighResEventArray *hreap;
+ pmHighResEventRecord *hrerp;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+
+ /* use pmdaEventAddMissedRecord for missed records ... */
+ if (flags & PM_EVENT_FLAG_MISSED)
+ return PM_ERR_CONV;
+
+ if ((sts = check_buf(bp, sizeof(*hrerp) - sizeof(pmEventParameter))) < 0)
+ return sts;
+ hreap = (pmHighResEventArray *)bp->baddr;
+ hreap->ea_nrecords++;
+ hrerp = (pmHighResEventRecord *)bp->bptr;
+ hrerp->er_timestamp.tv_sec = (__int64_t)ts->tv_sec;
+ hrerp->er_timestamp.tv_nsec = (__int64_t)ts->tv_nsec;
+ hrerp->er_nparams = 0;
+ hrerp->er_flags = flags;
+ bp->berp = (void *)hrerp;
+ bp->bptr += sizeof(pmHighResEventRecord) - sizeof(pmEventParameter);
+ return 0;
+}
+
+int
+pmdaEventAddMissedRecord(int idx, struct timeval *tp, int missed)
+{
+ int sts;
+ bufctl_t *bp;
+ pmEventArray *eap;
+ pmEventRecord *erp;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+
+ if ((sts = check_buf(bp, sizeof(*erp) - sizeof(pmEventParameter))) < 0)
+ return sts;
+ eap = (pmEventArray *)bp->baddr;
+ eap->ea_nrecords++;
+ erp = (pmEventRecord *)bp->bptr;
+ erp->er_timestamp.tv_sec = (__int32_t)tp->tv_sec;
+ erp->er_timestamp.tv_usec = (__int32_t)tp->tv_usec;
+ erp->er_nparams = missed;
+ erp->er_flags = PM_EVENT_FLAG_MISSED;
+ bp->berp = (void *)erp;
+ bp->bptr += sizeof(pmEventRecord) - sizeof(pmEventParameter);
+ return 0;
+}
+
+int
+pmdaEventAddHighResMissedRecord(int idx, struct timespec *ts, int missed)
+{
+ int sts;
+ bufctl_t *bp;
+ pmHighResEventArray *hreap;
+ pmHighResEventRecord *hrerp;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+
+ if ((sts = check_buf(bp, sizeof(*hrerp) - sizeof(pmEventParameter))) < 0)
+ return sts;
+ hreap = (pmHighResEventArray *)bp->baddr;
+ hreap->ea_nrecords++;
+ hrerp = (pmHighResEventRecord *)bp->bptr;
+ hrerp->er_timestamp.tv_sec = (__int32_t)ts->tv_sec;
+ hrerp->er_timestamp.tv_nsec = (__int32_t)ts->tv_nsec;
+ hrerp->er_nparams = missed;
+ hrerp->er_flags = PM_EVENT_FLAG_MISSED;
+ bp->berp = (void *)hrerp;
+ bp->bptr += sizeof(pmHighResEventRecord) - sizeof(pmEventParameter);
+ return 0;
+}
+
+int
+add_param(int idx, pmID pmid, int type, pmAtomValue *avp, bufctl_t **bpp)
+{
+ int sts;
+ int need; /* bytes in the buffer */
+ int vlen; /* value only length */
+ void *src;
+ pmEventParameter *epp;
+ bufctl_t *bp;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return PM_ERR_NOCONTEXT;
+ bp = &bufs[idx];
+
+ need = sizeof(pmEventParameter);
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ vlen = sizeof(avp->l);
+ need += vlen;
+ src = &avp->l;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ vlen = sizeof(avp->ll);
+ need += vlen;
+ src = &avp->ll;
+ break;
+ case PM_TYPE_FLOAT:
+ vlen = sizeof(avp->f);
+ need += vlen;
+ src = &avp->f;
+ break;
+ case PM_TYPE_DOUBLE:
+ vlen = sizeof(avp->d);
+ need += vlen;
+ src = &avp->d;
+ break;
+ case PM_TYPE_STRING:
+ vlen = strlen(avp->cp);
+ need += PM_PDU_SIZE_BYTES(vlen);
+ src = avp->cp;
+ break;
+ case PM_TYPE_AGGREGATE:
+ /* PM_VAL_HDR_SIZE is added back in below */
+ vlen = avp->vbp->vlen - PM_VAL_HDR_SIZE;
+ need += PM_PDU_SIZE_BYTES(vlen);
+ src = avp->vbp->vbuf;
+ break;
+ default:
+ return PM_ERR_TYPE;
+ }
+ if ((sts = check_buf(bp, need)) < 0)
+ return sts;
+ epp = (pmEventParameter *)bp->bptr;
+ epp->ep_pmid = pmid;
+ epp->ep_len = PM_VAL_HDR_SIZE + vlen;
+ epp->ep_type = type;
+ memcpy((void *)(bp->bptr + sizeof(pmEventParameter)), src, vlen);
+ bp->bptr += need;
+ *bpp = bp;
+ return 0;
+}
+
+int
+pmdaEventAddParam(int idx, pmID pmid, int type, pmAtomValue *avp)
+{
+ int sts;
+ bufctl_t *bp;
+ pmEventRecord *erp;
+
+ if ((sts = add_param(idx, pmid, type, avp, &bp)) >= 0) {
+ erp = (pmEventRecord *)bp->berp;
+ erp->er_nparams++;
+ }
+ return sts;
+}
+
+int
+pmdaEventHighResAddParam(int idx, pmID pmid, int type, pmAtomValue *avp)
+{
+ int sts;
+ bufctl_t *bp;
+ pmHighResEventRecord *hrerp;
+
+ if ((sts = add_param(idx, pmid, type, avp, &bp)) >= 0) {
+ hrerp = (pmHighResEventRecord *)bp->berp;
+ hrerp->er_nparams++;
+ }
+ return sts;
+}
+
+/*
+ * fill in the vlen/vtype header and return the address of the whole
+ * structure
+ */
+pmEventArray *
+pmdaEventGetAddr(int idx)
+{
+ pmEventArray *eap;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return NULL;
+
+ eap = (pmEventArray *)bufs[idx].baddr;
+ eap->ea_type = PM_TYPE_EVENT;
+ eap->ea_len = bufs[idx].bptr - bufs[idx].baddr;
+ return eap;
+}
+
+pmHighResEventArray *
+pmdaEventHighResGetAddr(int idx)
+{
+ pmHighResEventArray *hreap;
+
+ if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE)
+ return NULL;
+
+ hreap = (pmHighResEventArray *)bufs[idx].baddr;
+ hreap->ea_type = PM_TYPE_HIGHRES_EVENT;
+ hreap->ea_len = bufs[idx].bptr - bufs[idx].baddr;
+ return hreap;
+}
diff --git a/src/libpcp_pmda/src/exports b/src/libpcp_pmda/src/exports
new file mode 100644
index 0000000..ed8c313
--- /dev/null
+++ b/src/libpcp_pmda/src/exports
@@ -0,0 +1,115 @@
+PCP_PMDA_3.0 {
+ global:
+ pmdaInit;
+ pmdaMain;
+ pmdaGetOpt;
+ pmdaConnect;
+ pmdaOpenLog;
+ pmdaOpenHelp;
+ pmdaSetFlags;
+ pmdaGetContext;
+ pmdaGetInDomHelp;
+
+ pmdaDaemon;
+ pmdaDSO;
+
+ __pmdaInFd;
+ __pmdaMainPDU;
+ __pmdaSetContext;
+
+ pmdaPMID;
+ pmdaRehash;
+
+ pmdaSetCheckCallBack;
+ pmdaSetDoneCallBack;
+ pmdaSetEndContextCallBack;
+ pmdaSetFetchCallBack;
+ pmdaSetResultCallBack;
+
+ pmdaAttribute;
+ pmdaDesc;
+ pmdaFetch;
+ pmdaInstance;
+ pmdaProfile;
+ pmdaStore;
+ pmdaText;
+ pmdaName;
+ pmdaChildren;
+
+ pmdaCacheLookup;
+ pmdaCacheLookupKey;
+ pmdaCacheLookupName;
+ pmdaCacheOp;
+ pmdaCachePurge;
+ pmdaCacheStore;
+ pmdaCacheStoreKey;
+
+ __pmdaCacheDump;
+ __pmdaCacheDumpAll;
+ __pmdaStartInst;
+ __pmdaNextInst;
+ __pmdaCntInst;
+
+ pmdaCloseHelp;
+ pmdaGetHelp;
+ __pmdaHelpTab;
+
+ pmdaTreeChildren;
+ pmdaTreeName;
+ pmdaTreePMID;
+ pmdaTreeRebuildHash;
+ pmdaTreeSize;
+
+ pmdaEventAddMissedRecord;
+ pmdaEventAddParam;
+ pmdaEventAddRecord;
+ pmdaEventClients;
+ pmdaEventEndClient;
+ pmdaEventGetAddr;
+ pmdaEventNewActiveQueue;
+ pmdaEventNewArray;
+ pmdaEventNewClient;
+ pmdaEventNewQueue;
+ pmdaEventQueueAppend;
+ pmdaEventQueueBytes;
+ pmdaEventQueueClients;
+ pmdaEventQueueCounter;
+ pmdaEventQueueHandle;
+ pmdaEventQueueMemory;
+ pmdaEventQueueRecords;
+ pmdaEventReleaseArray;
+ pmdaEventResetArray;
+ pmdaEventSetAccess;
+ pmdaEventSetFilter;
+ __pmdaEventPrint;
+
+ pmdaDynamicLookupName;
+ pmdaDynamicLookupPMID;
+ pmdaDynamicLookupText;
+ pmdaDynamicMetricTable;
+ pmdaDynamicPMNS;
+
+ local: *;
+};
+
+PCP_PMDA_3.1 {
+ global:
+ pmdaGetOptions;
+ pmdaUsageMessage;
+} PCP_PMDA_3.0;
+
+PCP_PMDA_3.2 {
+ global:
+ pmdaDynamicSetClusterMask;
+} PCP_PMDA_3.1;
+
+PCP_PMDA_3.3 {
+ global:
+ pmdaEventNewHighResArray;
+ pmdaEventResetHighResArray;
+ pmdaEventReleaseHighResArray;
+ pmdaEventAddHighResRecord;
+ pmdaEventAddHighResMissedRecord;
+ pmdaEventHighResAddParam;
+ pmdaEventHighResGetAddr;
+} PCP_PMDA_3.2;
diff --git a/src/libpcp_pmda/src/help.c b/src/libpcp_pmda/src/help.c
new file mode 100644
index 0000000..d9d17b8
--- /dev/null
+++ b/src/libpcp_pmda/src/help.c
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+/*
+ * Get help text from files built using newhelp
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <sys/stat.h>
+
+typedef struct { /* beware: this data structure mirrored in chkhelp */
+ pmID pmid;
+ __uint32_t off_oneline;
+ __uint32_t off_text;
+} help_idx_t;
+
+typedef struct { /* beware: this data structure mirrored in chkhelp */
+ int dir_fd;
+ int pag_fd;
+ int numidx;
+ help_idx_t *index;
+ char *text;
+ int textlen;
+} help_t;
+
+static help_t *tab = NULL;
+static int numhelp = 0;
+
+/*
+ * open the help text files and return a handle on success
+ */
+int
+pmdaOpenHelp(char *fname)
+{
+ char pathname[MAXPATHLEN];
+ int sts, size;
+ help_idx_t hdr;
+ help_t *hp;
+ struct stat sbuf;
+
+ for (sts = 0; sts < numhelp; sts++) {
+ if (tab[sts].dir_fd == -1)
+ break;
+ }
+ if (sts == numhelp) {
+ sts = numhelp++;
+ tab = (help_t *)realloc(tab, numhelp * sizeof(tab[0]));
+ if (tab == NULL) {
+ __pmNoMem("pmdaOpenHelp", numhelp * sizeof(tab[0]), PM_RECOV_ERR);
+ numhelp = 0;
+ return -oserror();
+ }
+ }
+ hp = &tab[sts];
+ memset(hp, 0, sizeof(*hp));
+ hp->dir_fd = -1;
+ hp->pag_fd = -1;
+
+ snprintf(pathname, sizeof(pathname), "%s.dir", fname);
+ hp->dir_fd = open(pathname, O_RDONLY);
+ if (hp->dir_fd < 0) {
+ sts = -oserror();
+ goto failed;
+ }
+
+ if (read(hp->dir_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+ sts = -EINVAL;
+ goto failed;
+ }
+
+ if (hdr.pmid != 0x50635068 ||
+ (hdr.off_oneline & 0xffff0000) != 0x31320000) {
+ sts = -EINVAL;
+ goto failed;
+ }
+
+ hp->numidx = hdr.off_text;
+ size = (hp->numidx + 1) * sizeof(help_idx_t);
+
+ hp->index = (help_idx_t *)__pmMemoryMap(hp->dir_fd, size, 0);
+ if (hp->index == NULL) {
+ sts = -oserror();
+ goto failed;
+ }
+
+ snprintf(pathname, sizeof(pathname), "%s.pag", fname);
+ hp->pag_fd = open(pathname, O_RDONLY);
+ if (hp->pag_fd < 0) {
+ sts = -oserror();
+ goto failed;
+ }
+ if (fstat(hp->pag_fd, &sbuf) < 0) {
+ sts = -oserror();
+ goto failed;
+ }
+ hp->textlen = (int)sbuf.st_size;
+ hp->text = (char *)__pmMemoryMap(hp->pag_fd, hp->textlen, 0);
+ if (hp->text == NULL) {
+ sts = -oserror();
+ goto failed;
+ }
+ return numhelp - 1;
+
+failed:
+ pmdaCloseHelp(numhelp-1);
+ return sts;
+}
+
+/*
+ * retrieve pmID help text, ...
+ *
+ */
+char *
+pmdaGetHelp(int handle, pmID pmid, int type)
+{
+ int i;
+ help_t *hp;
+
+ if (handle < 0 || handle >= numhelp)
+ return NULL;
+ hp = &tab[handle];
+
+ /* search forwards -- could use binary chop */
+ for (i = 1; i <= hp->numidx; i++) {
+ if (hp->index[i].pmid == pmid) {
+ if (type & PM_TEXT_ONELINE)
+ return &hp->text[hp->index[i].off_oneline];
+ else
+ return &hp->text[hp->index[i].off_text];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * retrieve pmInDom help text, ...
+ *
+ */
+char *
+pmdaGetInDomHelp(int handle, pmInDom indom, int type)
+{
+ int i;
+ help_t *hp;
+ pmID pmid;
+ __pmID_int *pip = (__pmID_int *)&pmid;
+
+ if (handle < 0 || handle >= numhelp)
+ return NULL;
+ hp = &tab[handle];
+
+ *pip = *((__pmID_int *)&indom);
+ /*
+ * set a bit here to disambiguate pmInDom from pmID
+ * -- this "hack" is shared between here and newhelp/newhelp.c
+ */
+ pip->flag = 1;
+
+ /* search backwards ... pmInDom entries are at the end */
+ for (i = hp->numidx; i >= 1; i--) {
+ if (hp->index[i].pmid == pmid) {
+ if (type & PM_TEXT_ONELINE)
+ return &hp->text[hp->index[i].off_oneline];
+ else
+ return &hp->text[hp->index[i].off_text];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * this is only here for chkhelp(1) ... export the control data strcuture
+ */
+void *
+__pmdaHelpTab(void)
+{
+ return (void *)tab;
+}
+
+void
+pmdaCloseHelp(int handle)
+{
+ help_t *hp;
+
+ if (handle < 0 || handle >= numhelp)
+ return;
+ hp = &tab[handle];
+
+ if (hp->dir_fd != -1)
+ close(hp->dir_fd);
+ if (hp->pag_fd != -1)
+ close(hp->pag_fd);
+ if (hp->index != NULL)
+ __pmMemoryUnmap((void *)hp->index, (hp->numidx+1) * sizeof(help_idx_t));
+ if (hp->text != NULL)
+ __pmMemoryUnmap(hp->text, hp->textlen);
+
+ hp->textlen = 0;
+ hp->dir_fd = -1;
+ hp->pag_fd = -1;
+ hp->numidx = 0;
+ hp->index = NULL;
+ hp->text = NULL;
+}
diff --git a/src/libpcp_pmda/src/libdefs.h b/src/libpcp_pmda/src/libdefs.h
new file mode 100644
index 0000000..1d8b1c6
--- /dev/null
+++ b/src/libpcp_pmda/src/libdefs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1999-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef LIBDEFS_H
+#define LIBDEFS_H
+
+#define HAVE_V_TWO(interface) ((interface) >= PMDA_INTERFACE_2)
+#define HAVE_V_FOUR(interface) ((interface) >= PMDA_INTERFACE_4)
+#define HAVE_V_FIVE(interface) ((interface) >= PMDA_INTERFACE_5)
+#define HAVE_V_SIX(interface) ((interface) >= PMDA_INTERFACE_6)
+#define HAVE_ANY(interface) ((interface) <= PMDA_INTERFACE_6 && HAVE_V_TWO(interface))
+
+/*
+ * Auxilliary structure used to save data from pmdaDSO or pmdaDaemon and
+ * make it available to the other methods, also as private per PMDA data
+ * when multiple DSO PMDAs are in use
+ */
+typedef struct {
+ int pmda_interface;
+ pmResult *res; /* high-water allocation for */
+ int maxnpmids; /* pmResult for each PMDA */
+ __pmHashCtl hashpmids; /* hashed metrictab lookups */
+} e_ext_t;
+
+#endif /* LIBDEFS_H */
diff --git a/src/libpcp_pmda/src/mainloop.c b/src/libpcp_pmda/src/mainloop.c
new file mode 100644
index 0000000..7b6b776
--- /dev/null
+++ b/src/libpcp_pmda/src/mainloop.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "libdefs.h"
+
+int
+__pmdaInFd(pmdaInterface *dispatch)
+{
+ if (HAVE_ANY(dispatch->comm.pmda_interface))
+ return dispatch->version.any.ext->e_infd;
+
+ __pmNotifyErr(LOG_CRIT, "PMDA interface version %d not supported",
+ dispatch->comm.pmda_interface);
+ return -1;
+}
+
+int
+__pmdaMainPDU(pmdaInterface *dispatch)
+{
+ __pmPDU *pb;
+ int sts;
+ int op_sts;
+ pmID pmid;
+ pmDesc desc;
+ int npmids;
+ pmID *pmidlist;
+ char **namelist = NULL;
+ char *name;
+ char **offspring = NULL;
+ int *statuslist = NULL;
+ int subtype;
+ pmResult *result;
+ int ctxnum;
+ int length;
+ __pmTimeval when;
+ int ident;
+ int type;
+ pmInDom indom;
+ int inst;
+ char *iname;
+ __pmInResult *inres;
+ char *buffer;
+ __pmProfile *new_profile;
+ static __pmProfile *profile = NULL;
+ static int first_time = 1;
+ static pmdaExt *pmda = NULL;
+ int pinpdu;
+
+ /* Initial version checks */
+ if (first_time) {
+ if (dispatch->status != 0) {
+ __pmNotifyErr(LOG_ERR, "PMDA Initialisation Failed");
+ return -1;
+ }
+ if (!HAVE_ANY(dispatch->comm.pmda_interface)) {
+ __pmNotifyErr(LOG_CRIT, "PMDA interface version %d not supported",
+ dispatch->comm.pmda_interface);
+ return -1;
+ }
+ pmda = dispatch->version.any.ext;
+ dispatch->comm.pmapi_version = PMAPI_VERSION;
+ first_time = 0;
+ }
+
+ pinpdu = sts = __pmGetPDU(pmda->e_infd, ANY_SIZE, TIMEOUT_NEVER, &pb);
+ if (sts == 0)
+ return PM_ERR_EOF;
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "IPC Error: %s\n", pmErrStr(sts));
+ return sts;
+ }
+
+ if (HAVE_V_FIVE(dispatch->comm.pmda_interface)) {
+ /* set up sender context */
+ __pmPDUHdr *php = (__pmPDUHdr *)pb;
+ /* ntohl() converted already in __pmGetPDU() */
+ dispatch->version.four.ext->e_context = php->from;
+ }
+
+ /*
+ * if defined, callback once per PDU to check availability, etc.
+ */
+ if (pmda->e_checkCallBack) {
+ op_sts = (*(pmda->e_checkCallBack))();
+ if (op_sts < 0) {
+ if (sts != PDU_PROFILE)
+ /* all other PDUs expect an ACK */
+ __pmSendError(pmda->e_outfd, FROM_ANON, op_sts);
+ __pmUnpinPDUBuf(pb);
+ return 0;
+ }
+ }
+
+ switch (sts) {
+ case PDU_ERROR:
+ /*
+ * If __pmDecodeError() fails, just ignore it as no response PDU
+ * is required nor expected.
+ * Expect PM_ERR_NOTCONN to mark client context being closed.
+ */
+ if (__pmDecodeError(pb, &op_sts) >= 0) {
+ if (op_sts == PM_ERR_NOTCONN) {
+ if (HAVE_V_FIVE(dispatch->comm.pmda_interface)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_ERROR (end context %d)\n", dispatch->version.four.ext->e_context);
+ }
+#endif
+ if (pmda->e_endCallBack != NULL) {
+ (*(pmda->e_endCallBack))(dispatch->version.four.ext->e_context);
+ }
+ }
+ }
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "%s: unexpected error pdu from pmcd: %s?\n",
+ pmda->e_name, pmErrStr(op_sts));
+ }
+ }
+ break;
+
+ case PDU_PROFILE:
+ /*
+ * can ignore ctxnum, since pmcd has already used this to send
+ * the correct profile, if required
+ */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_PROFILE\n");
+ }
+#endif
+
+ /*
+ * free last profile received (if any)
+ * Note error responses are not sent for PDU_PROFILE
+ */
+ if (__pmDecodeProfile(pb, &ctxnum, &new_profile) < 0)
+ break;
+
+ sts = dispatch->version.any.profile(new_profile, pmda);
+ if (sts < 0) {
+ __pmFreeProfile(new_profile);
+ } else {
+ __pmFreeProfile(profile);
+ profile = new_profile;
+ }
+ break;
+
+ case PDU_FETCH:
+ /*
+ * can ignore ctxnum, since pmcd has already used this to send
+ * the correct profile, if required
+ */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_FETCH\n");
+ }
+#endif
+
+ sts = __pmDecodeFetch(pb, &ctxnum, &when, &npmids, &pmidlist);
+ if (sts >= 0) {
+ sts = dispatch->version.any.fetch(npmids, pmidlist, &result, pmda);
+ __pmUnpinPDUBuf(pmidlist);
+ }
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else {
+ /* this is for PURIFY to prevent a UMR in __pmXmitPDU */
+ result->timestamp.tv_sec = 0;
+ result->timestamp.tv_usec = 0;
+ __pmSendResult(pmda->e_outfd, FROM_ANON, result);
+ (pmda->e_resultCallBack)(result);
+ }
+ break;
+
+ case PDU_PMNS_NAMES:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_NAMES\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeNameList(pb, &npmids, &namelist, NULL)) >= 0) {
+ if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) {
+ if (npmids != 1)
+ /*
+ * expect only one name at a time to be sent to the
+ * pmda from pmcd
+ */
+ sts = PM_ERR_IPC;
+ else
+ sts = dispatch->version.four.pmid(namelist[0], &pmid, pmda);
+ }
+ else {
+ /* Not INTERFACE_4 or later */
+ sts = PM_ERR_NAME;
+ }
+ __pmUnpinPDUBuf(namelist);
+ }
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else
+ __pmSendIDList(pmda->e_outfd, FROM_ANON, 1, &pmid, sts);
+ break;
+
+ case PDU_PMNS_CHILD:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_CHILD\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeChildReq(pb, &name, &subtype)) >= 0) {
+ if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) {
+ sts = dispatch->version.four.children(name, 0, &offspring, &statuslist, pmda);
+ if (sts >= 0) {
+ if (subtype == 0) {
+ if (statuslist) free(statuslist);
+ statuslist = NULL;
+ }
+ }
+ }
+ else {
+ /* Not INTERFACE_4 */
+ sts = PM_ERR_NAME;
+ }
+ }
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else
+ __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, offspring, statuslist);
+ if (offspring) free(offspring);
+ if (statuslist) free(statuslist);
+ break;
+
+ case PDU_PMNS_TRAVERSE:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_TRAVERSE\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeTraversePMNSReq(pb, &name)) >= 0) {
+ if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) {
+ sts = dispatch->version.four.children(name, 1, &offspring, &statuslist, pmda);
+ if (sts >= 0) {
+ if (statuslist) free(statuslist);
+ statuslist = NULL;
+ }
+ }
+ else {
+ /* Not INTERFACE_4 */
+ sts = PM_ERR_NAME;
+ }
+ free(name);
+ }
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else
+ __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, offspring, NULL);
+ if (offspring) free(offspring);
+ break;
+
+ case PDU_PMNS_IDS:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_IDS\n");
+ }
+#endif
+
+ sts = __pmDecodeIDList(pb, 1, &pmid, &op_sts);
+ if (sts >= 0)
+ sts = op_sts;
+ if (sts >= 0) {
+ if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) {
+ sts = dispatch->version.four.name(pmid, &namelist, pmda);
+ }
+ else {
+ /* Not INTERFACE_4 */
+ sts = PM_ERR_PMID;
+ }
+ }
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else
+ __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, namelist, NULL);
+ if (namelist) free(namelist);
+ break;
+
+ case PDU_DESC_REQ:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_DESC_REQ\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeDescReq(pb, &pmid)) >= 0)
+ sts = dispatch->version.any.desc(pmid, &desc, pmda);
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else
+ __pmSendDesc(pmda->e_outfd, FROM_ANON, &desc);
+ break;
+
+ case PDU_INSTANCE_REQ:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_INSTANCE_REQ\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeInstanceReq(pb, &when, &indom, &inst, &iname)) >= 0)
+ sts = dispatch->version.any.instance(indom, inst, iname, &inres, pmda);
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else {
+ __pmSendInstance(pmda->e_outfd, FROM_ANON, inres);
+ __pmFreeInResult(inres);
+ }
+ if (iname)
+ free(iname);
+ break;
+
+ case PDU_TEXT_REQ:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_TEXT_REQ\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeTextReq(pb, &ident, &type)) >= 0)
+ sts = dispatch->version.any.text(ident, type, &buffer, pmda);
+ if (sts < 0)
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ else {
+ __pmSendText(pmda->e_outfd, FROM_ANON, ident, buffer);
+ }
+ break;
+
+ case PDU_RESULT:
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_RESULT\n");
+ }
+#endif
+
+ if ((sts = __pmDecodeResult(pb, &result)) >= 0)
+ sts = dispatch->version.any.store(result, pmda);
+ __pmSendError(pmda->e_outfd, FROM_ANON, sts);
+ pmFreeResult(result);
+ break;
+
+ case PDU_CONTROL_REQ:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_CONTROL_REQ\n");
+ }
+#endif
+ break;
+
+ case PDU_AUTH:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_DEBUG, "Received PDU_AUTH\n");
+#endif
+ if (__pmDecodeAuth(pb, &subtype, &buffer, &length) < 0)
+ break;
+ if (HAVE_V_SIX(dispatch->comm.pmda_interface)) {
+ ctxnum = dispatch->version.six.ext->e_context;
+ sts = dispatch->version.six.attribute(ctxnum, subtype, buffer, length, pmda);
+ } else {
+ sts = PM_ERR_GENERIC;
+ }
+ break;
+
+ default: {
+ char strbuf[20];
+ __pmNotifyErr(LOG_ERR,
+ "%s: Unrecognised pdu type: %s?\n",
+ pmda->e_name, __pmPDUTypeStr_r(sts, strbuf, sizeof(strbuf)));
+ }
+ break;
+ }
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ /*
+ * if defined, callback once per PDU to do termination checks,
+ * stats, etc
+ */
+ if (pmda->e_doneCallBack)
+ (*(pmda->e_doneCallBack))();
+
+ return 0;
+}
+
+
+void
+pmdaMain(pmdaInterface *dispatch)
+{
+ for ( ; ; ) {
+ if (__pmdaMainPDU(dispatch) < 0)
+ break;
+ }
+}
+
+void
+pmdaSetResultCallBack(pmdaInterface *dispatch, pmdaResultCallBack callback)
+{
+ if (HAVE_ANY(dispatch->comm.pmda_interface))
+ dispatch->version.any.ext->e_resultCallBack = callback;
+ else {
+ __pmNotifyErr(LOG_CRIT, "Unable to set result callback for PMDA interface version %d.",
+ dispatch->comm.pmda_interface);
+ dispatch->status = PM_ERR_GENERIC;
+ }
+}
+
+void
+pmdaSetEndContextCallBack(pmdaInterface *dispatch, pmdaEndContextCallBack callback)
+{
+ if (HAVE_V_FIVE(dispatch->comm.pmda_interface) || callback == NULL)
+ dispatch->version.four.ext->e_endCallBack = callback;
+ else {
+ __pmNotifyErr(LOG_CRIT, "Unable to set end context callback for PMDA interface version %d.",
+ dispatch->comm.pmda_interface);
+ dispatch->status = PM_ERR_GENERIC;
+ }
+}
+
+void
+pmdaSetFetchCallBack(pmdaInterface *dispatch, pmdaFetchCallBack callback)
+{
+ if (HAVE_ANY(dispatch->comm.pmda_interface))
+ dispatch->version.any.ext->e_fetchCallBack = callback;
+ else {
+ __pmNotifyErr(LOG_CRIT, "Unable to set fetch callback for PMDA interface version %d.",
+ dispatch->comm.pmda_interface);
+ dispatch->status = PM_ERR_GENERIC;
+ }
+}
+
+void
+pmdaSetCheckCallBack(pmdaInterface *dispatch, pmdaCheckCallBack callback)
+{
+ if (HAVE_ANY(dispatch->comm.pmda_interface))
+ dispatch->version.any.ext->e_checkCallBack = callback;
+ else {
+ __pmNotifyErr(LOG_CRIT, "Unable to set check callback for PMDA interface version %d.",
+ dispatch->comm.pmda_interface);
+ dispatch->status = PM_ERR_GENERIC;
+ }
+}
+
+void
+pmdaSetDoneCallBack(pmdaInterface *dispatch, pmdaDoneCallBack callback)
+{
+ if (HAVE_ANY(dispatch->comm.pmda_interface))
+ dispatch->version.any.ext->e_doneCallBack = callback;
+ else {
+ __pmNotifyErr(LOG_CRIT, "Unable to set done callback for PMDA interface version %d.",
+ dispatch->comm.pmda_interface);
+ dispatch->status = PM_ERR_GENERIC;
+ }
+}
diff --git a/src/libpcp_pmda/src/open.c b/src/libpcp_pmda/src/open.c
new file mode 100644
index 0000000..3150c20
--- /dev/null
+++ b/src/libpcp_pmda/src/open.c
@@ -0,0 +1,987 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "libdefs.h"
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+
+/*
+ * Open an inet port to PMCD
+ */
+
+static void
+__pmdaOpenSocket(char *sockname, int port, int family, int *infd, int *outfd)
+{
+ int sts;
+ int sfd;
+ struct servent *service;
+ __pmSockAddr *myaddr;
+ __pmSockLen addrlen;
+ __pmFdSet rfds;
+ int one = 1;
+
+ if (sockname != NULL) { /* Translate port name to port num */
+ service = getservbyname(sockname, NULL);
+ if (service == NULL) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: getservbyname(%s): %s\n",
+ sockname, netstrerror());
+ exit(1);
+ }
+ port = service->s_port;
+ }
+
+ if (family != AF_INET6) {
+ sfd = __pmCreateSocket();
+ if (sfd < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: inet socket: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ }
+ else {
+ sfd = __pmCreateIPv6Socket();
+ if (sfd < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: ipv6 socket: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ }
+#ifndef IS_MINGW
+ /*
+ * allow port to be quickly re-used, e.g. when Install and PMDA already
+ * installed, this becomes terminate and restart in a hurry ...
+ */
+ if (__pmSetSockOpt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: __pmSetSockOpt(reuseaddr): %s\n",
+ netstrerror());
+ exit(1);
+ }
+#else
+ /* see MSDN tech note: "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" */
+ if (__pmSetSockOpt(sfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: __pmSetSockOpt(excladdruse): %s\n",
+ netstrerror());
+ exit(1);
+ }
+#endif
+
+ if ((myaddr =__pmSockAddrAlloc()) == NULL) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: sock addr alloc failed\n");
+ exit(1);
+ }
+ __pmSockAddrInit(myaddr, family, INADDR_LOOPBACK, port);
+ sts = __pmBind(sfd, (void *)myaddr, __pmSockAddrSize());
+ if (sts < 0) {
+ __pmSockAddrFree(myaddr);
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: bind: %s\n",
+ netstrerror());
+ exit(1);
+ }
+
+ sts = __pmListen(sfd, 1); /* Max. 1 pending connection request (pmcd) */
+ if (sts == -1) {
+ __pmSockAddrFree(myaddr);
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: listen: %s\n",
+ netstrerror());
+ exit(1);
+ }
+
+ /* block here indefinitely, waiting for a connection */
+ __pmFD_ZERO(&rfds);
+ __pmFD_SET(sfd, &rfds);
+ if ((sts = __pmSelectRead(sfd+1, &rfds, NULL)) != 1) {
+ sts = (sts < 0) ? -neterror() : -EINVAL;
+ }
+ if (sts < 0) {
+ __pmSockAddrFree(myaddr);
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: select: %s\n",
+ pmErrStr(sts));
+ exit(1);
+ }
+
+ addrlen = __pmSockAddrSize();
+ if ((*infd = __pmAccept(sfd, myaddr, &addrlen)) < 0) {
+ __pmSockAddrFree(myaddr);
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: accept: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ __pmCloseSocket(sfd);
+ __pmSetSocketIPC(*infd);
+ __pmSockAddrFree(myaddr);
+
+ *outfd = *infd;
+}
+
+static void
+__pmdaOpenInet(char *sockname, int port, int *infd, int *outfd)
+{
+ __pmdaOpenSocket(sockname, port, AF_INET, infd, outfd);
+}
+
+static void
+__pmdaOpenIPv6(char *sockname, int port, int *infd, int *outfd)
+{
+ __pmdaOpenSocket(sockname, port, AF_INET6, infd, outfd);
+}
+
+#ifdef HAVE_STRUCT_SOCKADDR_UN
+/*
+ * Open a unix port to PMCD
+ */
+static void
+__pmdaOpenUnix(char *sockname, int *infd, int *outfd)
+{
+ int sts;
+ int sfd;
+ int len;
+ __pmSockLen addrlen;
+ struct sockaddr_un myaddr;
+ struct sockaddr_un from;
+
+ sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: Unix domain socket: %s",
+ netstrerror());
+ exit(1);
+ }
+ /* Sockets in the Unix domain are named pipes in the file system.
+ * In case the socket is still hanging around, try to remove it. If it
+ * belonged to someone and is still open, they will still keep the
+ * connection because the reference count is non-zero.
+ */
+ if ((sts = unlink(sockname)) == 0)
+ __pmNotifyErr(LOG_WARNING, "__pmdaOpenUnix: Unix domain socket '%s' existed, unlinked it\n",
+ sockname);
+ else if (sts < 0 && oserror() != ENOENT) {
+ /* If can't unlink socket, give up. We might end up with an
+ * unwanted connection to some other socket (from outer space)
+ */
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: Unlinking Unix domain socket '%s': %s\n",
+ sockname, osstrerror());
+ exit(1);
+ }
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sun_family = AF_UNIX;
+ strcpy(myaddr.sun_path, sockname);
+ len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(myaddr.sun_path);
+ sts = bind(sfd, (struct sockaddr*) &myaddr, len);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix bind: %s\n",
+ netstrerror());
+ exit(1);
+ }
+
+ sts = listen(sfd, 5); /* Max. of 5 pending connection requests */
+ if (sts == -1) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix listen: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ addrlen = sizeof(from);
+ /* block here, waiting for a connection */
+ if ((*infd = accept(sfd, (struct sockaddr *)&from, &addrlen)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix accept: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ close(sfd);
+ *outfd = *infd;
+}
+#else
+static void
+__pmdaOpenUnix(char *sockname, int *infd, int *outfd)
+{
+ __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: UNIX domain sockets unsupported\n");
+ exit(1);
+}
+#endif
+
+/*
+ * Capture PMDA args using pmgetopts_r
+ */
+
+static char
+pmdaIoTypeToOption(pmdaIoType io)
+{
+ switch(io) {
+ case pmdaPipe:
+ return 'p';
+ case pmdaInet:
+ return 'i';
+ case pmdaIPv6:
+ return '6';
+ case pmdaUnix:
+ return 'u';
+ case pmdaUnknown:
+ default:
+ break;
+ }
+ return '?';
+}
+
+/*
+ * Backwards compatibility interface, short option support only.
+ * We override username (-U) to preserve backward-compatibility
+ * with the original pmdaGetOpt interface, which does not know
+ * about that option (and uses of -U have been observed in the
+ * wild). Happily, pmdaGetOptions gives us a much more flexible
+ * route forward.
+ */
+
+static int
+username_override(int opt, pmdaOptions *opts)
+{
+ (void)opts;
+ return opt == 'U';
+}
+
+int
+pmdaGetOpt(int argc, char *const *argv, const char *optstring, pmdaInterface *dispatch, int *err)
+{
+ int sts;
+ static pmdaOptions opts;
+
+ opts.flags |= PM_OPTFLAG_POSIX;
+ opts.short_options = optstring;
+ opts.override = username_override;
+ opts.errors = 0;
+
+ sts = pmdaGetOptions(argc, argv, &opts, dispatch);
+
+ optind = opts.optind;
+ opterr = opts.opterr;
+ optopt = opts.optopt;
+ optarg = opts.optarg;
+ *err += opts.errors;
+ return sts;
+}
+
+/*
+ * New, prefered interface - supports long and short options, allows
+ * caller to select whether POSIX style options are required. Also,
+ * handles the common -U,--username option setting automatically.
+ */
+int
+pmdaGetOptions(int argc, char *const *argv, pmdaOptions *opts, pmdaInterface *dispatch)
+{
+ int c = EOF;
+ int flag = 0;
+ char *endnum = NULL;
+ pmdaExt *pmda = NULL;
+
+ if (dispatch->status != 0) {
+ opts->errors++;
+ return EOF;
+ }
+
+ if (!HAVE_ANY(dispatch->comm.pmda_interface)) {
+ __pmNotifyErr(LOG_CRIT, "pmdaGetOptions: "
+ "PMDA interface version %d not supported (domain=%d)",
+ dispatch->comm.pmda_interface, dispatch->domain);
+ opts->errors++;
+ return EOF;
+ }
+
+ pmda = dispatch->version.any.ext;
+
+ /*
+ * We only support one version of pmdaOptions structure so far - tell
+ * the caller the struct size/fields we will be using (version zero),
+ * in case they are of later version (newer PMDA, older library).
+ *
+ * The pmOptions and pmdaOptions structures share initial pmgetopts_r
+ * fields, hence the cast below (and sharing of pmgetopt_r itself).
+ *
+ * So far, the only PMDA-specific field is from --username although,
+ * of course, more may be added in the future (bumping version as we
+ * go of course, and observing the version number the PMDA passes in).
+ */
+ opts->version = 0;
+
+ while (!flag && ((c = pmgetopt_r(argc, argv, (pmOptions *)opts)) != EOF)) {
+ int sts;
+
+ /* provide opportunity for overriding the general set of options */
+ if (opts->override && opts->override(c, opts))
+ break;
+
+ switch (c) {
+ case 'd':
+ dispatch->domain = (int)strtol(opts->optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: -d requires numeric domain number\n",
+ pmda->e_name);
+ opts->errors++;
+ }
+ pmda->e_domain = dispatch->domain;
+ break;
+
+ case 'D':
+ if ((sts = __pmParseDebug(opts->optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmda->e_name, opts->optarg);
+ opts->errors++;
+ } else {
+ pmDebug |= sts;
+ }
+ break;
+
+ case 'h': /* over-ride default help file */
+ pmda->e_helptext = opts->optarg;
+ break;
+
+ case 'i':
+ if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaInet) {
+ pmprintf("%s: -i option clashes with -%c option\n",
+ pmda->e_name, pmdaIoTypeToOption(pmda->e_io));
+ opts->errors++;
+ } else {
+ pmda->e_io = pmdaInet;
+ pmda->e_port = (int)strtol(opts->optarg, &endnum, 10);
+ if (*endnum != '\0')
+ pmda->e_sockname = opts->optarg;
+ }
+ break;
+
+ case 'l': /* over-ride default log file */
+ pmda->e_logfile = opts->optarg;
+ break;
+
+ case 'p':
+ if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaPipe) {
+ pmprintf("%s: -p option clashes with -%c option\n",
+ pmda->e_name, pmdaIoTypeToOption(pmda->e_io));
+ opts->errors++;
+ } else {
+ pmda->e_io = pmdaPipe;
+ }
+ break;
+
+ case 'u':
+ if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaUnix) {
+ pmprintf("%s: -u option clashes with -%c option\n",
+ pmda->e_name, pmdaIoTypeToOption(pmda->e_io));
+ opts->errors++;
+ } else {
+ pmda->e_io = pmdaUnix;
+ pmda->e_sockname = opts->optarg;
+ }
+ break;
+
+ case '6':
+ if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaIPv6) {
+ pmprintf("%s: -6 option clashes with -%c option\n",
+ pmda->e_name, pmdaIoTypeToOption(pmda->e_io));
+ opts->errors++;
+ } else {
+ pmda->e_io = pmdaIPv6;
+ pmda->e_port = (int)strtol(opts->optarg, &endnum, 10);
+ if (*endnum != '\0')
+ pmda->e_sockname = opts->optarg;
+ }
+ break;
+
+ case 'U':
+ opts->username = opts->optarg;
+ break;
+
+ case '?':
+ opts->errors++;
+ break;
+
+ default:
+ flag = 1;
+ }
+ }
+
+ return c;
+}
+
+void
+pmdaUsageMessage(pmdaOptions *opts)
+{
+ pmUsageMessage((pmOptions *)opts);
+}
+
+static __pmHashWalkState
+pmdaHashNodeDelete(const __pmHashNode *tp, void *cp)
+{
+ (void)tp;
+ (void)cp;
+ return PM_HASH_WALK_DELETE_NEXT;
+}
+
+static void
+pmdaHashDelete(__pmHashCtl *hashp)
+{
+ __pmHashWalkCB(pmdaHashNodeDelete, NULL, hashp);
+ __pmHashClear(hashp);
+}
+
+/*
+ * Recompute the hash table which maps metric PMIDs to metric table
+ * offsets. Provides an optimised lookup alternative when a direct
+ * mapping is inappropriate or impossible.
+ */
+void
+pmdaRehash(pmdaExt *pmda, pmdaMetric *metrics, int nmetrics)
+{
+ e_ext_t *extp = (e_ext_t *)pmda->e_ext;
+ __pmHashCtl *hashp = &extp->hashpmids;
+ pmdaMetric *metric;
+ char buf[32];
+ int m;
+
+ pmda->e_direct = 0;
+ pmda->e_metrics = metrics;
+ pmda->e_nmetrics = nmetrics;
+
+ pmdaHashDelete(hashp);
+ for (m = 0; m < pmda->e_nmetrics; m++) {
+ metric = &pmda->e_metrics[m];
+
+ if (__pmHashAdd(metric->m_desc.pmid, metric, hashp) < 0) {
+ __pmNotifyErr(LOG_WARNING, "pmdaRehash: PMDA %s: "
+ "Hashed mapping for metrics disabled @ metric[%d] %s\n",
+ pmda->e_name, m,
+ pmIDStr_r(metric->m_desc.pmid, buf, sizeof(buf)));
+ break;
+ }
+ }
+ if (m == pmda->e_nmetrics)
+ pmda->e_flags |= PMDA_EXT_FLAG_HASHED;
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_DEBUG, "pmdaRehash: PMDA %s: successful rebuild\n",
+ pmda->e_name);
+ else {
+ pmda->e_flags &= ~PMDA_EXT_FLAG_HASHED;
+ pmdaHashDelete(hashp);
+ }
+}
+
+static void
+pmdaDirect(pmdaExt *pmda, pmdaMetric *metrics, int nmetrics)
+{
+ __pmID_int *pmidp;
+ char buf[20];
+ int m;
+
+ pmda->e_direct = 1;
+ for (m = 0; m < pmda->e_nmetrics; m++) {
+ pmidp = (__pmID_int *)&pmda->e_metrics[m].m_desc.pmid;
+
+ if (pmidp->item == m)
+ continue;
+
+ pmda->e_direct = 0;
+ if ((pmda->e_flags & PMDA_EXT_FLAG_DIRECT) ||
+ (pmDebug & DBG_TRACE_LIBPMDA))
+ __pmNotifyErr(LOG_WARNING, "pmdaDirect: PMDA %s: "
+ "Direct mapping for metrics disabled @ metrics[%d] %s\n",
+ pmda->e_name, m,
+ pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, buf, sizeof(buf)));
+ break;
+ }
+}
+
+void
+pmdaSetFlags(pmdaInterface *dispatch, int flags)
+{
+ pmdaExt *pmda;
+
+ if (!HAVE_ANY(dispatch->comm.pmda_interface)) {
+ __pmNotifyErr(LOG_CRIT, "pmdaSetFlags: PMDA interface version %d not supported (domain=%d)",
+ dispatch->comm.pmda_interface, dispatch->domain);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ pmda = dispatch->version.any.ext;
+ pmda->e_flags |= flags;
+}
+
+/*
+ * Open the help text file, check for direct mapping into the metric table
+ * and whether a hash mapping has been requested.
+ */
+
+void
+pmdaInit(pmdaInterface *dispatch, pmdaIndom *indoms, int nindoms,
+ pmdaMetric *metrics, int nmetrics)
+{
+ int m = 0;
+ int i = 0;
+ __pmInDom_int *indomp = NULL;
+ __pmInDom_int *mindomp = NULL;
+ __pmID_int *pmidp = NULL;
+ pmdaExt *pmda = NULL;
+
+ if (!HAVE_ANY(dispatch->comm.pmda_interface)) {
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA interface version %d not supported (domain=%d)",
+ dispatch->comm.pmda_interface, dispatch->domain);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ pmda = dispatch->version.any.ext;
+
+ if (dispatch->version.any.fetch == pmdaFetch &&
+ pmda->e_fetchCallBack == (pmdaFetchCallBack)0) {
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: using pmdaFetch() but fetch call back not set", pmda->e_name);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ /* parameter sanity checks */
+ if (nmetrics < 0) {
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: nmetrics (%d) should be non-negative", pmda->e_name, nmetrics);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ if (nindoms < 0) {
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: nindoms (%d) should be non-negative", pmda->e_name, nindoms);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ if ((nmetrics == 0 && metrics != NULL) ||
+ (nmetrics != 0 && metrics == NULL)){
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: metrics not consistent with nmetrics", pmda->e_name);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ if ((nindoms == 0 && indoms != NULL) ||
+ (nindoms != 0 && indoms == NULL)){
+ __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: indoms not consistent with nindoms", pmda->e_name);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ pmda->e_indoms = indoms;
+ pmda->e_nindoms = nindoms;
+ pmda->e_metrics = metrics;
+ pmda->e_nmetrics = nmetrics;
+
+ /* fix bit fields in indom for all instance domains */
+ for (i = 0; i < pmda->e_nindoms; i++) {
+ unsigned int domain = dispatch->domain;
+ unsigned int serial = pmda->e_indoms[i].it_indom;
+ pmda->e_indoms[i].it_indom = pmInDom_build(domain, serial);
+ }
+
+ /* fix bit fields in indom for all metrics */
+ for (i = 0; i < pmda->e_nmetrics; i++) {
+ if (pmda->e_metrics[i].m_desc.indom != PM_INDOM_NULL) {
+ unsigned int domain = dispatch->domain;
+ unsigned int serial = pmda->e_metrics[i].m_desc.indom;
+ pmda->e_metrics[i].m_desc.indom = pmInDom_build(domain, serial);
+ }
+ }
+
+ /*
+ * For each metric, check the instance domain serial number is valid
+ */
+ for (m = 0; m < pmda->e_nmetrics; m++) {
+ if (pmda->e_metrics[m].m_desc.indom != PM_INDOM_NULL) {
+ mindomp = (__pmInDom_int *)&(pmda->e_metrics[m].m_desc.indom);
+ if (pmda->e_nindoms > 0) {
+ for (i = 0; i < pmda->e_nindoms; i++) {
+ indomp = (__pmInDom_int *)&(pmda->e_indoms[i].it_indom);
+ if (indomp->serial == mindomp->serial) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ char strbuf[20];
+ char st2buf[20];
+ __pmNotifyErr(LOG_DEBUG,
+ "pmdaInit: PMDA %s: Metric %s(%d) matched to indom %s(%d)\n",
+ pmda->e_name,
+ pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, strbuf, sizeof(strbuf)), m,
+ pmInDomStr_r(pmda->e_indoms[i].it_indom, st2buf, sizeof(st2buf)), i);
+ }
+#endif
+ break;
+ }
+ }
+ if (i == pmda->e_nindoms) {
+ char strbuf[20];
+ __pmNotifyErr(LOG_CRIT,
+ "pmdaInit: PMDA %s: Undefined instance domain serial (%d) specified in metric %s(%d)\n",
+ pmda->e_name, mindomp->serial,
+ pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, strbuf, sizeof(strbuf)), m);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ }
+ }
+ }
+
+ if (pmda->e_helptext != NULL) {
+ pmda->e_help = pmdaOpenHelp(pmda->e_helptext);
+ if (pmda->e_help < 0) {
+ __pmNotifyErr(LOG_WARNING, "pmdaInit: PMDA %s: Unable to open help text file(s) from \"%s\": %s\n",
+ pmda->e_name, pmda->e_helptext, pmErrStr(pmda->e_help));
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "pmdaInit: PMDA %s: help file %s opened\n", pmda->e_name, pmda->e_helptext);
+ }
+#endif
+ }
+ else {
+ if (dispatch->version.two.text == pmdaText)
+ __pmNotifyErr(LOG_WARNING, "pmdaInit: PMDA %s: No help text file specified", pmda->e_name);
+#ifdef PCP_DEBUG
+ else
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_DEBUG, "pmdaInit: PMDA %s: No help text path specified", pmda->e_name);
+#endif
+ }
+
+ /*
+ * Stamp the correct domain number in each of the PMIDs
+ */
+ for (m = 0; m < pmda->e_nmetrics; m++) {
+ pmidp = (__pmID_int *)&pmda->e_metrics[m].m_desc.pmid;
+ pmidp->domain = dispatch->domain;
+ }
+
+ if (pmda->e_flags & PMDA_EXT_FLAG_HASHED)
+ pmdaRehash(pmda, metrics, nmetrics);
+ else
+ pmdaDirect(pmda, metrics, nmetrics);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_INFO, "name = %s\n", pmda->e_name);
+ __pmNotifyErr(LOG_INFO, "domain = %d\n", dispatch->domain);
+ if (dispatch->comm.flags)
+ __pmNotifyErr(LOG_INFO, "comm flags = %x\n", dispatch->comm.flags);
+ __pmNotifyErr(LOG_INFO, "ext flags = %x\n", pmda->e_flags);
+ __pmNotifyErr(LOG_INFO, "num metrics = %d\n", pmda->e_nmetrics);
+ __pmNotifyErr(LOG_INFO, "num indom = %d\n", pmda->e_nindoms);
+ __pmNotifyErr(LOG_INFO, "metric map = %s\n",
+ (pmda->e_flags & PMDA_EXT_FLAG_HASHED) ? "hashed" :
+ (pmda->e_direct ? "direct" : "linear"));
+ }
+#endif
+
+ dispatch->status = pmda->e_status;
+}
+
+/*
+ * version exchange with pmcd via credentials PDU
+ */
+
+static int
+__pmdaSetupPDU(int infd, int outfd, int flags, char *agentname)
+{
+ __pmVersionCred handshake;
+ __pmCred *credlist = NULL;
+ __pmPDU *pb;
+ int i, sts, pinpdu, vflag = 0;
+ int version = UNKNOWN_VERSION, credcount = 0, sender = 0;
+
+ handshake.c_type = CVERSION;
+ handshake.c_version = PDU_VERSION;
+ handshake.c_flags = flags;
+ if ((sts = __pmSendCreds(outfd, (int)getpid(), 1, (__pmCred *)&handshake)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s send creds: %s\n", agentname, pmErrStr(sts));
+ return -1;
+ }
+
+ if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s getting creds: %s\n", agentname, pmErrStr(sts));
+ return -1;
+ }
+
+ if (sts == PDU_CREDS) {
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s decode creds: %s\n", agentname, pmErrStr(sts));
+ __pmUnpinPDUBuf(pb);
+ return -1;
+ }
+
+ for (i = 0; i < credcount; i++) {
+ switch (credlist[i].c_type) {
+ case CVERSION:
+ version = credlist[i].c_vala;
+ vflag = 1;
+ break;
+ default:
+ __pmNotifyErr(LOG_WARNING, "__pmdaSetupPDU: PMDA %s: unexpected creds PDU\n", agentname);
+ }
+ }
+ if (vflag) {
+ __pmSetVersionIPC(infd, version);
+ __pmSetVersionIPC(outfd, version);
+ }
+ if (credlist != NULL)
+ free(credlist);
+ }
+ else
+ __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s: version exchange failure\n", agentname);
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ return version;
+}
+
+/*
+ * set up connection to PMCD
+ */
+
+void
+pmdaConnect(pmdaInterface *dispatch)
+{
+ pmdaExt *pmda = NULL;
+ int sts, flags = dispatch->comm.flags;
+
+ if (dispatch->version.any.ext == NULL ||
+ (dispatch->version.any.ext->e_flags & PMDA_EXT_SETUPDONE) != PMDA_EXT_SETUPDONE) {
+ __pmNotifyErr(LOG_CRIT, "pmdaConnect: need to call pmdaDaemon() or pmdaDSO() first");
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ if ((dispatch->version.any.ext->e_flags & PMDA_EXT_CONNECTED) == PMDA_EXT_CONNECTED) {
+ __pmNotifyErr(LOG_CRIT, "pmdaConnect: called more than once");
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ if (!HAVE_ANY(dispatch->comm.pmda_interface)) {
+ __pmNotifyErr(LOG_CRIT, "pmdaConnect: PMDA interface version %d not supported (domain=%d)",
+ dispatch->comm.pmda_interface, dispatch->domain);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ pmda = dispatch->version.any.ext;
+
+ switch (pmda->e_io) {
+ case pmdaPipe:
+ case pmdaUnknown: /* Default */
+
+ pmda->e_infd = fileno(stdin);
+ pmda->e_outfd = fileno(stdout);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened pipe to pmcd, infd = %d, outfd = %d\n",
+ pmda->e_name, pmda->e_infd, pmda->e_outfd);
+ }
+#endif
+ break;
+
+ case pmdaInet:
+
+ __pmdaOpenInet(pmda->e_sockname, pmda->e_port, &(pmda->e_infd),
+ &(pmda->e_outfd));
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened inet connection, infd = %d, outfd = %d\n",
+ pmda->e_name, pmda->e_infd, pmda->e_outfd);
+ }
+#endif
+
+ break;
+
+ case pmdaIPv6:
+
+ __pmdaOpenIPv6(pmda->e_sockname, pmda->e_port, &(pmda->e_infd),
+ &(pmda->e_outfd));
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened ipv6 connection, infd = %d, outfd = %d\n",
+ pmda->e_name, pmda->e_infd, pmda->e_outfd);
+ }
+#endif
+
+ break;
+
+ case pmdaUnix:
+
+ __pmdaOpenUnix(pmda->e_sockname, &(pmda->e_infd), &(pmda->e_outfd));
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: Opened unix connection, infd = %d, outfd = %d\n",
+ pmda->e_name, pmda->e_infd, pmda->e_outfd);
+ }
+#endif
+
+ break;
+ default:
+ __pmNotifyErr(LOG_CRIT, "pmdaConnect: PMDA %s: Illegal iotype: %d\n", pmda->e_name, pmda->e_io);
+ exit(1);
+ }
+
+ sts = __pmdaSetupPDU(pmda->e_infd, pmda->e_outfd, flags, pmda->e_name);
+ if (sts < 0)
+ dispatch->status = sts;
+ else {
+ dispatch->comm.pmapi_version = (unsigned int)sts;
+ pmda->e_flags |= PMDA_EXT_CONNECTED;
+ }
+}
+
+/*
+ * initialise the pmdaExt and pmdaInterface structures for a daemon or DSO PMDA.
+ */
+
+static void
+__pmdaSetup(pmdaInterface *dispatch, int version, char *name)
+{
+ pmdaExt *pmda = NULL;
+ e_ext_t *extp;
+
+ if (!HAVE_ANY(version)) {
+ __pmNotifyErr(LOG_CRIT, "__pmdaSetup: %s PMDA: interface version %d not supported (domain=%d)",
+ name, version, dispatch->domain);
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ pmda = (pmdaExt *)calloc(1, sizeof(pmdaExt));
+ if (pmda == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "%s: Unable to allocate memory for pmdaExt structure (%d bytes)",
+ name, (int)sizeof(pmdaExt));
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ dispatch->status = 0;
+
+ dispatch->comm.pmda_interface = version;
+ dispatch->comm.pmapi_version = PMAPI_VERSION;
+ dispatch->comm.flags = 0;
+
+ if (HAVE_V_SIX(version)) {
+ dispatch->version.six.attribute = pmdaAttribute;
+ }
+ if (HAVE_V_FOUR(version)) {
+ dispatch->version.four.pmid = pmdaPMID;
+ dispatch->version.four.name = pmdaName;
+ dispatch->version.four.children = pmdaChildren;
+ }
+ dispatch->version.any.profile = pmdaProfile;
+ dispatch->version.any.fetch = pmdaFetch;
+ dispatch->version.any.desc = pmdaDesc;
+ dispatch->version.any.instance = pmdaInstance;
+ dispatch->version.any.text = pmdaText;
+ dispatch->version.any.store = pmdaStore;
+ dispatch->version.any.ext = pmda;
+
+ pmda->e_name = name;
+ pmda->e_infd = -1;
+ pmda->e_outfd = -1;
+ pmda->e_port = -1;
+ pmda->e_singular = -1;
+ pmda->e_ordinal = -1;
+ pmda->e_domain = dispatch->domain;
+ pmda->e_help = -1;
+ pmda->e_io = pmdaUnknown;
+ pmda->e_ext = (void *)dispatch;
+ pmda->e_flags |= PMDA_EXT_SETUPDONE;
+
+ extp = (e_ext_t *)calloc(1, sizeof(*extp));
+ if (extp == NULL) {
+ __pmNotifyErr(LOG_ERR,
+ "%s: Unable to allocate memory for e_ext_t structure (%d bytes)",
+ name, (int)sizeof(*extp));
+ free(pmda);
+ dispatch->version.any.ext = NULL;
+ dispatch->status = PM_ERR_GENERIC;
+ return;
+ }
+ extp->pmda_interface = version;
+ pmda->e_ext = (void *)extp;
+
+ pmdaSetResultCallBack(dispatch, __pmFreeResultValues);
+ pmdaSetFetchCallBack(dispatch, (pmdaFetchCallBack)0);
+ pmdaSetCheckCallBack(dispatch, (pmdaCheckCallBack)0);
+ pmdaSetDoneCallBack(dispatch, (pmdaDoneCallBack)0);
+ pmdaSetEndContextCallBack(dispatch, (pmdaEndContextCallBack)0);
+}
+
+/*
+ * initialise the pmdaExt and pmdaInterface structures for a daemon
+ * also set some globals
+ */
+
+void
+pmdaDaemon(pmdaInterface *dispatch, int version, char *name, int domain,
+ char *logfile, char *helptext)
+{
+ pmdaExt *pmda;
+
+ dispatch->domain = domain;
+ __pmdaSetup(dispatch, version, name);
+
+ if (dispatch->status < 0)
+ return;
+
+ pmda = dispatch->version.any.ext;
+ pmda->e_logfile = logfile;
+ pmda->e_helptext = helptext;
+
+ __pmSetInternalState(PM_STATE_PMCS);
+}
+
+/*
+ * initialise the pmdaExt structure for a DSO
+ * also set some globals
+ */
+
+void
+pmdaDSO(pmdaInterface *dispatch, int version, char *name, char *helptext)
+{
+ __pmdaSetup(dispatch, version, name);
+
+ if (dispatch->status < 0)
+ return;
+
+ dispatch->version.any.ext->e_helptext = helptext;
+}
+
+/*
+ * Redirect stderr to the log file
+ */
+
+void
+pmdaOpenLog(pmdaInterface *dispatch)
+{
+ int c;
+
+ if (dispatch->status < 0)
+ return;
+
+ __pmOpenLog(dispatch->version.any.ext->e_name,
+ dispatch->version.any.ext->e_logfile, stderr, &c);
+}
diff --git a/src/libpcp_pmda/src/queues.c b/src/libpcp_pmda/src/queues.c
new file mode 100644
index 0000000..ef01b59
--- /dev/null
+++ b/src/libpcp_pmda/src/queues.c
@@ -0,0 +1,610 @@
+/*
+ * Generic event queue support for PMDAs
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Nathan Scott. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "queues.h"
+#include <ctype.h>
+
+static event_queue_t *queues;
+static int numqueues;
+
+static event_client_t *clients;
+static int numclients;
+static event_client_t *client_lookup(int context);
+
+typedef void (*clientVisitCallBack)(event_clientq_t *, event_queue_t *, void *);
+static void client_iterate(clientVisitCallBack, int, event_queue_t *, void *);
+
+static event_queue_t *
+queue_lookup(int handle)
+{
+ if (handle >= numqueues || handle < 0)
+ return NULL;
+ if (queues[handle].inuse)
+ return &queues[handle];
+ return NULL;
+}
+
+/*
+ * Drop an event after it has been queued (i.e. client was too slow)
+ */
+static void
+queue_drop(event_clientq_t *clientq, event_queue_t *queue, void *data)
+{
+ event_t *event = (event_t *)data;
+
+ if (clientq->last != NULL && clientq->last == event) {
+ clientq->last = TAILQ_NEXT(event, events);
+ clientq->missed++;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Client missed queue %s event %p",
+ queue->name, event);
+ }
+}
+
+static void
+queue_drop_bytes(int handle, event_queue_t *queue, size_t bytes)
+{
+ event_t *event, *next;
+
+ event = TAILQ_FIRST(&queue->tailq);
+ while (event) {
+ if (bytes <= queue->maxmemory - queue->qsize)
+ break;
+ next = TAILQ_NEXT(event, events);
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Dropping %s: e=%p sz=%d max=%d qsz=%d",
+ queue->name, event, (int)event->size,
+ (int)queue->maxmemory, (int)queue->qsize);
+
+ /* Walk clients - if event last seen, drop it and bump missed count */
+ client_iterate(queue_drop, handle, queue, event);
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Removing %s event %p (%d bytes)",
+ queue->name, event, (int)event->size);
+
+ TAILQ_REMOVE(&queue->tailq, event, events);
+ queue->qsize -= event->size;
+ free(event);
+ event = next;
+ }
+}
+
+int
+pmdaEventNewActiveQueue(const char *name, size_t maxmemory, unsigned int numclients)
+{
+ event_queue_t *queue;
+ size_t size;
+ int i;
+
+ if (name == NULL || maxmemory <= 0)
+ return -EINVAL;
+
+ for (i = 0; i < numqueues; i++)
+ if (queues[i].inuse && strcmp(queues[i].name, name) == 0)
+ return -EEXIST;
+
+ for (i = 0; i < numqueues; i++)
+ if (queues[i].inuse == 0)
+ break;
+ if (i == numqueues) {
+ /*
+ * No free slots - extend the available set.
+ * realloc() potential moves "queues" address, fix up
+ * must tear down existing queues which may have back
+ * references and then re-initialise them afterward.
+ */
+ for (i = 0; i < numqueues; i++)
+ queue_drop_bytes(i, &queues[i], INT_MAX);
+ size = (numqueues + 1) * sizeof(event_queue_t);
+ queues = realloc(queues, size);
+ if (!queues)
+ __pmNoMem("pmdaEventNewQueue", size, PM_FATAL_ERR);
+ /* realloc moves tailq tqh_last pointer - reset 'em */
+ for (i = 0; i < numqueues; i++)
+ TAILQ_INIT(&queues[i].tailq);
+ numqueues++;
+ }
+
+ /* "i" now indexes into a free slot */
+ queue = &queues[i];
+ memset(queue, 0, sizeof(*queue));
+ TAILQ_INIT(&queue->tailq);
+ queue->eventarray = pmdaEventNewArray();
+ queue->numclients = numclients;
+ queue->maxmemory = maxmemory;
+ queue->inuse = 1;
+ queue->name = name;
+ return i;
+}
+
+int
+pmdaEventNewQueue(const char *name, size_t maxmemory)
+{
+ return pmdaEventNewActiveQueue(name, maxmemory, 0);
+}
+
+int
+pmdaEventQueueHandle(const char *name)
+{
+ int i;
+
+ for (i = 0; i < numqueues; i++)
+ if (queues[i].inuse && strcmp(queues[i].name, name) == 0)
+ return i;
+ return -ESRCH;
+}
+
+int
+pmdaEventQueueCounter(int handle, pmAtomValue *atom)
+{
+ event_queue_t *queue = queue_lookup(handle);
+
+ if (!queue)
+ return -EINVAL;
+ atom->ul = queue->count;
+ return PMDA_FETCH_STATIC;
+}
+
+int
+pmdaEventQueueClients(int handle, pmAtomValue *atom)
+{
+ event_queue_t *queue = queue_lookup(handle);
+
+ if (!queue)
+ return -EINVAL;
+ atom->ul = queue->numclients;
+ return PMDA_FETCH_STATIC;
+}
+
+int
+pmdaEventQueueMemory(int handle, pmAtomValue *atom)
+{
+ event_queue_t *queue = queue_lookup(handle);
+
+ if (!queue)
+ return -EINVAL;
+ atom->ull = queue->qsize;
+ return PMDA_FETCH_STATIC;
+}
+
+int
+pmdaEventQueueBytes(int handle, pmAtomValue *atom)
+{
+ event_queue_t *queue = queue_lookup(handle);
+
+ if (!queue)
+ return -EINVAL;
+ atom->ull = queue->bytes;
+ return PMDA_FETCH_STATIC;
+}
+
+int
+pmdaEventQueueAppend(int handle, void *data, size_t bytes, struct timeval *tv)
+{
+ event_queue_t *queue = queue_lookup(handle);
+ event_t *event;
+
+ if (!queue)
+ return -EINVAL;
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Appending event: queue#%d \"%s\" (%ld bytes)",
+ handle, queue->name, (long)bytes);
+ if (bytes > queue->maxmemory) {
+ __pmNotifyErr(LOG_WARNING, "Event too large for queue %s (%ld > %ld)",
+ queue->name, (long)bytes, (long)queue->maxmemory);
+ goto done;
+ }
+
+ /*
+ * We may need to make room in the event queue. If so, start at the head
+ * and madly drop events until sufficient space exists or all are freed.
+ * Bump the missed counter for each client who missed an event we had to
+ * throw away.
+ */
+ queue_drop_bytes(handle, queue, bytes);
+ if (queue->numclients == 0)
+ goto done;
+
+ if ((event = malloc(sizeof(event_t) + bytes + 1)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "event allocation failure: %ld bytes",
+ (long)(bytes + 1));
+ return -ENOMEM;
+ }
+
+ /* Track the actual event data */
+ event->count = queue->numclients;
+ memcpy(event->buffer, data, bytes);
+ memcpy(&event->time, tv, sizeof(*tv));
+ event->size = bytes;
+
+ /* Finally, store the event in the queue */
+ TAILQ_INSERT_TAIL(&queue->tailq, event, events);
+ queue->qsize += bytes;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO,
+ "Inserted %s event %p (%ld bytes) clients = %d.",
+ queue->name, event, (long)event->size, event->count);
+
+done:
+ /* Update event queue tracking stats (even for no-clients case) */
+ queue->bytes += bytes;
+ queue->count++;
+ return 0;
+}
+
+static int
+queue_filter(event_clientq_t *clientq, void *data, size_t size)
+{
+ /* Note: having a filter implies access (optionally) checked there */
+ if (clientq->filter) {
+ int sts = clientq->apply(clientq->filter, data, size);
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Clientq filter applied (%d)\n", sts);
+ return sts;
+ }
+ else if (!clientq->access) {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Clientq access denied\n");
+ return -PM_ERR_PERMISSION;
+ }
+ return 0;
+}
+
+static int
+queue_fetch(event_queue_t *queue, event_clientq_t *clientq, pmAtomValue *atom,
+ pmdaEventDecodeCallBack queue_decoder, void *data)
+{
+ event_t *event, *next;
+ int records, key, sts;
+
+ /*
+ * Ensure the way we keep track of which clients are interested
+ * in which queues is up to date.
+ */
+ if (clientq->active == 0) {
+ clientq->active = 1;
+ queue->numclients++;
+ }
+ if (clientq->last == NULL)
+ clientq->last = TAILQ_FIRST(&queue->tailq);
+ event = clientq->last;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "queue_fetch start, last event=%p\n", event);
+
+ sts = records = 0;
+ key = queue->eventarray;
+ pmdaEventResetArray(key);
+
+ while (event != NULL) {
+ char message[64];
+
+ if (queue_filter(clientq, event->buffer, event->size)) {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Culling event (sz=%ld): \"%s\"",
+ (long)event->size,
+ __pmdaEventPrint(event->buffer, event->size,
+ message, sizeof(message)));
+ } else {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Adding event (sz=%ld): \"%s\"",
+ (long)event->size,
+ __pmdaEventPrint(event->buffer, event->size,
+ message, sizeof(message)));
+ if ((sts = queue_decoder(key,
+ event->buffer, event->size, &event->time, data)) < 0)
+ break;
+ records += sts;
+ sts = 0;
+ }
+
+ next = TAILQ_NEXT(event, events);
+
+ /* Remove the current one (if its use count hits zero) */
+ if (--event->count <= 0) {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Removing %s event %p in fetch",
+ queue->name, event);
+ TAILQ_REMOVE(&queue->tailq, event, events);
+ queue->qsize -= event->size;
+ free(event);
+ }
+
+ /* Go on to the next event. */
+ event = next;
+ }
+
+ /*
+ * Did this client miss any events? The "extra" one is the last previously
+ * observed event (pointed at by per-context last pointer) - so, event only
+ * missed once we move past *more* than just that last observed event.
+ */
+ if (sts == 0) {
+ sts = clientq->missed - 1;
+ clientq->missed = 0;
+ if (sts > 0) {
+ struct timeval timestamp;
+ gettimeofday(&timestamp, NULL);
+ sts = pmdaEventAddMissedRecord(key, &timestamp, sts);
+ records++;
+ } else {
+ sts = 0;
+ }
+ }
+
+ /* Update queue tail pointer for this client. */
+ clientq->last = NULL;
+
+ atom->vbp = records ? (pmValueBlock *)pmdaEventGetAddr(key) : NULL;
+ return sts;
+}
+
+static event_clientq_t *
+client_queue_lookup(int context, int handle, int accessq)
+{
+ event_client_t *client = client_lookup(context);
+ event_queue_t *queue = queue_lookup(handle);
+ size_t size;
+
+ /*
+ * If context doesn't exist, bail out. But, if per-client
+ * queue information doesn't exist for that context yet, it
+ * is create iff an indication of interest was shown by the
+ * caller (i.e. "accessq" was set).
+ */
+ if (!client || !queue)
+ return NULL;
+ if (handle < client->nclientq)
+ return &client->clientq[handle];
+ if (!accessq)
+ return NULL;
+
+ /* allocate (possibly multiple) queue slots for this client */
+ size = (handle + 1) * sizeof(struct event_clientq);
+ client->clientq = realloc(client->clientq, size);
+ if (!client->clientq)
+ __pmNoMem("client_queue_lookup", size, PM_FATAL_ERR);
+
+ /* ensure any new clientq's up to this one are initialised */
+ size -= client->nclientq * sizeof(struct event_clientq);
+ memset(client->clientq + client->nclientq, 0, size);
+ client->nclientq = handle + 1;
+ return &client->clientq[handle];
+}
+
+int
+pmdaEventQueueRecords(int handle, pmAtomValue *atom, int context,
+ pmdaEventDecodeCallBack queue_decoder, void *data)
+{
+ event_clientq_t *clientq = client_queue_lookup(context, handle, 1);
+ event_queue_t *queue = queue_lookup(handle);
+ int sts;
+
+ if (!queue || !clientq)
+ return -EINVAL;
+
+ sts = queue_fetch(queue, clientq, atom, queue_decoder, data);
+ if (sts != 0)
+ return sts;
+ return (atom->vbp == NULL) ? PMDA_FETCH_NOVALUES : PMDA_FETCH_STATIC;
+}
+
+/*
+ * We've lost a client (disconnected).
+ * Cleanup any filter and any back references across the queues.
+ */
+static void
+queue_cleanup(int handle, event_clientq_t *clientq)
+{
+ event_queue_t *queue = queue_lookup(handle);
+ event_t *event, *next;
+
+ if (clientq->release)
+ clientq->release(clientq->filter);
+
+ if (!queue || !clientq->active)
+ return;
+
+ event = clientq->last;
+ while (event) {
+ next = TAILQ_NEXT(event, events);
+
+ /* Remove the current event (if use count hits zero) */
+ if (--event->count <= 0) {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "Removing %s event %p",
+ queue->name, event);
+ TAILQ_REMOVE(&queue->tailq, event, events);
+ queue->qsize -= event->size;
+ free(event);
+ }
+ event = next;
+ }
+
+ queue->numclients--;
+}
+
+char *
+__pmdaEventPrint(const char *buffer, int bufsize, char *msg, int msgsize)
+{
+ int minsize = msgsize < bufsize ? msgsize : bufsize;
+ int i;
+
+ if (msgsize < 4)
+ return NULL;
+ memcpy(msg, buffer, minsize);
+ memset(msg + minsize, '.', msgsize - minsize);
+ msg[minsize - 1] = '\0';
+ for (i = 0; i < minsize - 1; i++) {
+ if (isspace((int)msg[i]))
+ msg[i] = ' ';
+ else if (!isprint((int)msg[i]))
+ msg[i] = '.';
+ }
+ return msg;
+}
+
+int
+pmdaEventNewClient(int context)
+{
+ event_client_t *client;
+ int size, i;
+
+ for (i = 0; i < numclients; i++) {
+ if (clients[i].context == context && clients[i].inuse)
+ return i;
+ }
+ for (i = 0; i < numclients; i++) {
+ if (clients[i].inuse == 0)
+ break;
+ }
+ if (i == numclients) {
+ /* no free slots, extend the available set */
+ size = (numclients + 1) * sizeof(event_client_t);
+ clients = realloc(clients, size);
+ if (!clients)
+ __pmNoMem("pmdaEventNewClient", size, PM_FATAL_ERR);
+ numclients++;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_INFO, "%s: new client, slot=%d (total=%d)\n",
+ __FUNCTION__, i, numclients);
+ }
+
+ /* "i" now indexes into a free slot */
+ client = &clients[i];
+ memset(client, 0, sizeof(*client));
+ client->context = context;
+ client->inuse = 1;
+ return i;
+}
+
+static event_client_t *
+client_lookup(int context)
+{
+ int i;
+
+ for (i = 0; i < numclients; i++)
+ if (clients[i].context == context && clients[i].inuse)
+ return &clients[i];
+ return NULL;
+}
+
+/*
+ * Visit each active context and run a supplied callback routine
+ */
+static void
+client_iterate(clientVisitCallBack visit,
+ int handle, event_queue_t *queue, void *data)
+{
+ event_clientq_t *clientq;
+ int i;
+
+ for (i = 0; i < numclients; i++) {
+ if (!clients[i].inuse)
+ continue;
+ clientq = client_queue_lookup(clients[i].context, handle, 0);
+ if (clientq && clientq->active)
+ visit(clientq, queue, data);
+ }
+}
+
+int
+pmdaEventEndClient(int context)
+{
+ event_client_t *client = client_lookup(context);
+ int i;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmdaEventEndClient: ctx=%d slot=%d\n",
+ context, client ? (int)(client - clients) : 0);
+
+ if (!client) {
+ /*
+ * This is expected ... when a context is closed in pmcd
+ * (or for a local context or for dbpmda or ...) all the
+ * PMDAs with a registered pmdaEndContextCallBack will be
+ * called and some of the PMDAs may not have not serviced
+ * any previous requests for that context.
+ */
+ return 0;
+ }
+
+ for (i = 0; i < client->nclientq; i++)
+ queue_cleanup(i, &client->clientq[i]);
+ if (client->clientq)
+ free(client->clientq);
+
+ memset(client, 0, sizeof(*client)); /* sets !inuse */
+ return 0;
+}
+
+int
+pmdaEventClients(pmAtomValue *atom)
+{
+ __uint32_t i, c = 0;
+
+ for (i = 0; i < numclients; i++)
+ if (clients[i].inuse)
+ c++;
+ atom->ul = c;
+ return PMDA_FETCH_STATIC;
+}
+
+/*
+ * Marks context as having been pmStore'd into (access allowed),
+ * adds optional filtering data for the current client context.
+ */
+int
+pmdaEventSetFilter(int context, int handle, void *filter,
+ pmdaEventApplyFilterCallBack apply,
+ pmdaEventReleaseFilterCallBack release)
+{
+ event_clientq_t *clientq = client_queue_lookup(context, handle, 1);
+
+ if (!clientq)
+ return -EINVAL;
+
+ /* first, free up any existing filter */
+ if (clientq->filter)
+ clientq->release(clientq->filter);
+
+ clientq->apply = apply;
+ clientq->filter = filter;
+ clientq->release = release;
+ clientq->access = 1;
+ return 0;
+}
+
+int
+pmdaEventSetAccess(int context, int handle, int allow)
+{
+ event_clientq_t *clientq = client_queue_lookup(context, handle, 1);
+
+ if (!clientq)
+ return -EINVAL;
+
+ clientq->access = allow;
+ return 0;
+}
diff --git a/src/libpcp_pmda/src/queues.h b/src/libpcp_pmda/src/queues.h
new file mode 100644
index 0000000..607655e
--- /dev/null
+++ b/src/libpcp_pmda/src/queues.h
@@ -0,0 +1,172 @@
+/*
+ * Event queue support for PMDAs
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Nathan Scott. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Portions Copyright (c) 1991, 1993
+ *
+ * The Regents of the University of California. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _QUEUES_H
+#define _QUEUES_H
+
+/*
+ * Extracts of TAILQ implementation from <sys/queue.h> included directly
+ * for platforms without support (Win32, older Linux variants).
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#define _TAILQ_HEAD(name, type, qual) \
+struct name { \
+ qual type *tqh_first; /* first element */ \
+ qual type *qual *tqh_last; /* addr of last next element */ \
+}
+#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,)
+
+#define _TAILQ_ENTRY(type, qual) \
+struct { \
+ qual type *tqe_next; /* next element */ \
+ qual type *qual *tqe_prev; /* address of previous next element */\
+}
+#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,)
+
+#define TAILQ_INIT(head) do { \
+ (head)->tqh_first = NULL; \
+ (head)->tqh_last = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \
+ (head)->tqh_first->field.tqe_prev = \
+ &(elm)->field.tqe_next; \
+ else \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+ (head)->tqh_first = (elm); \
+ (elm)->field.tqe_prev = &(head)->tqh_first; \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ (elm)->field.tqe_next = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &(elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if (((elm)->field.tqe_next) != NULL) \
+ (elm)->field.tqe_next->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+
+/*
+ * Data structures used in the PMDA event queue implementation
+ * Every event is timestamped and linked into one (tail) queue.
+ * Events know nothing about the clients accessing them.
+ */
+
+typedef struct event {
+ TAILQ_ENTRY(event) events; /* link into queue of events */
+ struct timeval time; /* timestamp for this event */
+ int count; /* events reference count */
+ size_t size; /* buffer size in bytes */
+ char buffer[];
+} event_t;
+
+TAILQ_HEAD(tailqueue, event);
+
+typedef struct event_queue {
+ const char *name; /* callers identifier for this queue */
+ size_t maxmemory; /* max data bytes that can be queued */
+ int inuse; /* is this queue in use or free */
+ int eventarray; /* event records for this queue */
+ __uint32_t numclients; /* export: number of active clients */
+ __uint32_t count; /* exported: event counter */
+ __uint64_t bytes; /* exported: data throughput */
+ __uint64_t qsize; /* data in the queue (<= maxmem) */
+ struct tailqueue tailq; /* queue of events for clients */
+} event_queue_t;
+
+/*
+ * Data structures used in the PMDA event client implementation
+ * Each client is one PCP tool invocation (e.g. pmevent) and has
+ * a link back to those queues which it has fetched/stored into
+ * at some point in the past. The "last" event pointer, gives a
+ * pointer to the last observed event for that client, which is
+ * used as the starting point for a subsequent fetch request (or
+ * when dropping events, should the client not be keeping up).
+ */
+
+typedef struct event_clientq {
+ int active; /* client interest in this queue */
+ int missed; /* count of events missed on queue */
+ int access; /* is access restricted/permitted */
+ event_t *last; /* last event seen on this queue */
+ void *filter; /* filter data for the event queue */
+ pmdaEventApplyFilterCallBack apply; /* actual filter callback */
+ pmdaEventReleaseFilterCallBack release; /* remove filter callback */
+} event_clientq_t;
+
+typedef struct event_client {
+ int context; /* client context identifier */
+ int inuse; /* is this table slot in use */
+ int nclientq; /* allocated size of clientq */
+ event_clientq_t *clientq; /* per-queue client state */
+} event_client_t;
+
+#endif /* _QUEUES_H */
diff --git a/src/libpcp_pmda/src/tree.c b/src/libpcp_pmda/src/tree.c
new file mode 100644
index 0000000..ab24504
--- /dev/null
+++ b/src/libpcp_pmda/src/tree.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#define NONLEAF(node) ((node)->pmid == PM_ID_NULL)
+
+/*
+ * Fixup the parent pointers of the tree.
+ * Fill in the hash table with nodes from the tree.
+ * Hashing is done on pmid.
+ */
+static void
+__pmdaTreeReindexHash(__pmnsTree *tree, __pmnsNode *root)
+{
+ __pmnsNode *np;
+
+ for (np = root->first; np != NULL; np = np->next) {
+ np->parent = root;
+ if (np->pmid != PM_ID_NULL) {
+ int i = np->pmid % tree->htabsize;
+ np->hash = tree->htab[i];
+ tree->htab[i] = np;
+ }
+ __pmdaTreeReindexHash(tree, np);
+ }
+}
+
+/*
+ * "Make the average hash list no longer than 5, and the number
+ * of hash table entries not a multiple of 2, 3 or 5."
+ * [From __pmFixPMNSHashTab; without mark_all, dinks with pmids]
+ */
+void
+pmdaTreeRebuildHash(__pmnsTree *tree, int numpmid)
+{
+ if (tree) {
+ int htabsize = numpmid / 5;
+
+ if (htabsize % 2 == 0) htabsize++;
+ if (htabsize % 3 == 0) htabsize += 2;
+ if (htabsize % 5 == 0) htabsize += 2;
+ tree->htabsize = htabsize;
+ tree->htab = (__pmnsNode **)calloc(htabsize, sizeof(__pmnsNode *));
+ if (tree->htab) {
+ __pmdaTreeReindexHash(tree, tree->root);
+ } else {
+ __pmNoMem("pmdaTreeRebuildHash",
+ htabsize * sizeof(__pmnsNode *), PM_RECOV_ERR);
+ tree->htabsize = 0;
+ }
+ }
+}
+
+static int
+__pmdaNodeCount(__pmnsNode *parent)
+{
+ __pmnsNode *np;
+ int count;
+
+ count = 0;
+ for (np = parent->first; np != NULL; np = np->next) {
+ if (np->pmid != PM_ID_NULL)
+ count++;
+ else
+ count += __pmdaNodeCount(np);
+ }
+ return count;
+}
+
+int
+pmdaTreeSize(__pmnsTree *pmns)
+{
+ if (pmns && pmns->root)
+ return __pmdaNodeCount(pmns->root);
+ return 0;
+}
+
+static __pmnsNode *
+__pmdaNodeLookup(__pmnsNode *node, const char *name)
+{
+ while (node != NULL) {
+ size_t length = strlen(node->name);
+ if (strncmp(name, node->name, length) == 0) {
+ if (name[length] == '\0')
+ return node;
+ if (name[length] == '.' && NONLEAF(node))
+ return __pmdaNodeLookup(node->first, name + length + 1);
+ }
+ node = node->next;
+ }
+ return NULL;
+}
+
+int
+pmdaTreePMID(__pmnsTree *pmns, const char *name, pmID *pmid)
+{
+ if (pmns && pmns->root) {
+ __pmnsNode *node;
+
+ if ((node = __pmdaNodeLookup(pmns->root->first, name)) == NULL)
+ return PM_ERR_NAME;
+ if (NONLEAF(node))
+ return PM_ERR_NAME;
+ *pmid = node->pmid;
+ return 0;
+ }
+ return PM_ERR_NAME;
+}
+
+static char *
+__pmdaNodeAbsoluteName(__pmnsNode *node, char *buffer)
+{
+ if (node && node->parent) {
+ buffer = __pmdaNodeAbsoluteName(node->parent, buffer);
+ strcpy(buffer, node->name);
+ buffer += strlen(node->name);
+ *buffer++ = '.';
+ }
+ return buffer;
+}
+
+int
+pmdaTreeName(__pmnsTree *pmns, pmID pmid, char ***nameset)
+{
+ __pmnsNode *hashchain, *node, *parent;
+ int nmatch = 0, length = 0;
+ char *p, **list;
+
+ if (!pmns)
+ return PM_ERR_PMID;
+
+ hashchain = pmns->htab[pmid % pmns->htabsize];
+ for (node = hashchain; node != NULL; node = node->hash) {
+ if (node->pmid == pmid) {
+ for (parent = node; parent->parent; parent = parent->parent)
+ length += strlen(parent->name) + 1;
+ nmatch++;
+ }
+ }
+
+ if (nmatch == 0)
+ return PM_ERR_PMID;
+
+ length += nmatch * sizeof(char *); /* pointers to names */
+
+ if ((list = (char **)malloc(length)) == NULL)
+ return -oserror();
+
+ p = (char *)&list[nmatch];
+ nmatch = 0;
+ for (node = hashchain; node != NULL; node = node->hash) {
+ if (node->pmid == pmid) {
+ list[nmatch++] = p;
+ p = __pmdaNodeAbsoluteName(node, p);
+ *(p-1) = '\0'; /* overwrite final '.' */
+ }
+ }
+
+ *nameset = list;
+ return nmatch;
+}
+
+static int
+__pmdaNodeRelativeChildren(__pmnsNode *base, char ***offspring, int **status)
+{
+ __pmnsNode *node;
+ char **list, *p;
+ int *leaf, length = 0, nmatch = 0;
+
+ for (node = base; node != NULL; node = node->next, nmatch++)
+ length += strlen(node->name) + 1;
+ if (nmatch == 0) {
+ /*
+ * no need to allocate zero sized arrays for offspring[]
+ * and status[]
+ */
+ return 0;
+ }
+ length += nmatch * sizeof(char *); /* pointers to names */
+ if ((list = (char **)malloc(length)) == NULL)
+ return -oserror();
+ if ((leaf = (int *)malloc(nmatch * sizeof(int))) == NULL) {
+ free(list);
+ return -oserror();
+ }
+ p = (char *)&list[nmatch];
+ nmatch = 0;
+ for (node = base; node != NULL; node = node->next, nmatch++) {
+ leaf[nmatch] = NONLEAF(node) ? PMNS_NONLEAF_STATUS : PMNS_LEAF_STATUS;
+ list[nmatch] = p;
+ strcpy(p, node->name);
+ p += strlen(node->name);
+ *p++ = '\0';
+ }
+
+ *offspring = list;
+ *status = leaf;
+ return nmatch;
+}
+
+static void
+__pmdaNodeChildrenGetSize(__pmnsNode *base, int kids, int *length, int *nmetrics)
+{
+ __pmnsNode *node, *parent;
+
+ /* walk to every leaf & then add its (absolute name) length */
+ for (node = base; node != NULL; node = node->next) {
+ if (NONLEAF(node)) {
+ __pmdaNodeChildrenGetSize(node->first, 1, length, nmetrics);
+ continue;
+ }
+ for (parent = node; parent->parent; parent = parent->parent)
+ *length += strlen(parent->name) + 1;
+ (*nmetrics)++;
+ if (!kids)
+ break;
+ }
+}
+
+/*
+ * Fill the pmdaChildren buffers - names and leaf status. Called recursively
+ * to descend down to all leaf nodes. Offset parameter is the current offset
+ * into the name list buffer, and its also returned at the end of each call -
+ * it keeps track of where the next name is to start in (list) output buffer.
+ */
+static char *
+__pmdaNodeChildrenGetList(__pmnsNode *base, int kids, int *nmetrics, char *p, char **list, int *leaf)
+{
+ __pmnsNode *node;
+ int count = *nmetrics;
+ char *start = p;
+
+ for (node = base; node != NULL; node = node->next) {
+ if (NONLEAF(node)) {
+ p = __pmdaNodeChildrenGetList(node->first, 1, &count, p, list, leaf);
+ start = p;
+ continue;
+ }
+ leaf[count] = PMNS_LEAF_STATUS;
+ list[count] = start;
+ p = __pmdaNodeAbsoluteName(node, p);
+ *(p-1) = '\0'; /* overwrite final '.' */
+ start = p;
+ count++;
+ if (!kids)
+ break;
+ }
+ *nmetrics = count;
+ return p;
+}
+
+static int
+__pmdaNodeAbsoluteChildren(__pmnsNode *node, char ***offspring, int **status)
+{
+ char *p, **list;
+ int *leaf, descend = 0, length = 0, nmetrics = 0;
+
+ if (NONLEAF(node)) {
+ node = node->first;
+ descend = 1;
+ }
+ __pmdaNodeChildrenGetSize(node, descend, &length, &nmetrics);
+
+ length += nmetrics * sizeof(char *); /* pointers to names */
+ if ((list = (char **)malloc(length)) == NULL)
+ return -oserror();
+ if ((leaf = (int *)malloc(nmetrics * sizeof(int))) == NULL) {
+ free(list);
+ return -oserror();
+ }
+
+ p = (char *)&list[nmetrics];
+ nmetrics = 0; /* start at the start */
+ __pmdaNodeChildrenGetList(node, descend, &nmetrics, p, list, leaf);
+
+ *offspring = list;
+ *status = leaf;
+ return nmetrics;
+}
+
+int
+pmdaTreeChildren(__pmnsTree *pmns, const char *name, int traverse, char ***offspring, int **status)
+{
+ __pmnsNode *node;
+ int sts;
+
+ if (!pmns)
+ return PM_ERR_NAME;
+
+ if ((node = __pmdaNodeLookup(pmns->root->first, name)) == NULL)
+ return PM_ERR_NAME;
+
+ if (traverse == 0)
+ sts = __pmdaNodeRelativeChildren(node->first, offspring, status);
+ else
+ sts = __pmdaNodeAbsoluteChildren(node, offspring, status);
+ return sts;
+}
diff --git a/src/libpcp_qed/GNUmakefile b/src/libpcp_qed/GNUmakefile
new file mode 100644
index 0000000..552118e
--- /dev/null
+++ b/src/libpcp_qed/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_qed/src/GNUmakefile b/src/libpcp_qed/src/GNUmakefile
new file mode 100644
index 0000000..e11a2cf
--- /dev/null
+++ b/src/libpcp_qed/src/GNUmakefile
@@ -0,0 +1,24 @@
+TOPDIR = ../../..
+LIBRARY = libpcp_qed
+PROJECT = $(LIBRARY).pro
+include $(TOPDIR)/src/include/builddefs
+
+HEADERS = $(shell echo *.h)
+SOURCES = $(shell echo *.cpp)
+
+default: build-me
+
+ifeq "$(ENABLE_QT)" "true"
+build-me: $(PROJECT)
+ $(QTMAKE)
+else
+build-me:
+endif
+
+include $(BUILDRULES)
+
+install: default
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/libpcp_qed/src/libpcp_qed.pro b/src/libpcp_qed/src/libpcp_qed.pro
new file mode 100644
index 0000000..294a66a
--- /dev/null
+++ b/src/libpcp_qed/src/libpcp_qed.pro
@@ -0,0 +1,51 @@
+TARGET = pcp_qed
+TEMPLATE = lib
+VERSION = 1.0.0
+CONFIG += qt staticlib warn_on
+INCLUDEPATH += ../../include ../../libpcp_qmc/src
+QT = core gui network svg
+
+HEADERS = qed.h \
+ qed_actionlist.h \
+ qed_app.h \
+ qed_bar.h \
+ qed_colorlist.h \
+ qed_colorpicker.h \
+ qed_console.h \
+ qed_fileiconprovider.h \
+ qed_gadget.h \
+ qed_groupcontrol.h \
+ qed_label.h \
+ qed_led.h \
+ qed_legend.h \
+ qed_line.h \
+ qed_recorddialog.h \
+ qed_statusbar.h \
+ qed_timebutton.h \
+ qed_timecontrol.h \
+ qed_viewcontrol.h \
+
+SOURCES = \
+ qed_actionlist.cpp \
+ qed_app.cpp \
+ qed_bar.cpp \
+ qed_colorlist.cpp \
+ qed_colorpicker.cpp \
+ qed_console.cpp \
+ qed_fileiconprovider.cpp \
+ qed_gadget.cpp \
+ qed_groupcontrol.cpp \
+ qed_label.cpp \
+ qed_led.cpp \
+ qed_legend.cpp \
+ qed_line.cpp \
+ qed_recorddialog.cpp \
+ qed_statusbar.cpp \
+ qed_timebutton.cpp \
+ qed_timecontrol.cpp \
+ qed_viewcontrol.cpp \
+
+FORMS = \
+ qed_console.ui \
+ qed_recorddialog.ui \
+
diff --git a/src/libpcp_qed/src/qed.h b/src/libpcp_qed/src/qed.h
new file mode 100644
index 0000000..36a3155
--- /dev/null
+++ b/src/libpcp_qed/src/qed.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QED_H
+#define QED_H
+
+//
+// Qt Extensions and Doodads Library
+// doodad: An un-namable gadget of some sort, possibly highly technical.
+//
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+class QedLED;
+class QedLine;
+class QedLabel;
+class QedLegend;
+class QedGadget;
+class QedColorList;
+class QedActionList;
+
+#endif // QED_H
diff --git a/src/libpcp_qed/src/qed_actionlist.cpp b/src/libpcp_qed/src/qed_actionlist.cpp
new file mode 100644
index 0000000..a371b64
--- /dev/null
+++ b/src/libpcp_qed/src/qed_actionlist.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qed_actionlist.h"
+
+QedActionList::QedActionList(const char *id) : QString(id)
+{
+ my.defaultPos = -1;
+}
+
+const char *
+QedActionList::identity(void) const
+{
+ return this->toAscii();
+}
+
+void
+QedActionList::addName(const char *name)
+{
+ my.names << QString(name);
+}
+
+void
+QedActionList::addAction(const char *act)
+{
+ my.actions << QString(act);
+}
+
+// QMenu &QedActionList::menu() { }
+
+int
+QedActionList::defaultPos(void)
+{
+ return my.defaultPos;
+}
+
+void
+QedActionList::setDefaultPos(unsigned int pos)
+{
+ my.defaultPos = (int)pos;
+}
diff --git a/src/libpcp_qed/src/qed_actionlist.h b/src/libpcp_qed/src/qed_actionlist.h
new file mode 100644
index 0000000..fb78cbe
--- /dev/null
+++ b/src/libpcp_qed/src/qed_actionlist.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_ACTIONLIST_H
+#define QED_ACTIONLIST_H
+
+#include <QtGui>
+
+class QedActionList : public QString
+{
+public:
+ QedActionList(const char *id);
+ const char *identity() const;
+
+ void addName(const char *name);
+ void addAction(const char *act);
+ // QMenu &menu() { /* TODO: construct a real QMenu */ }
+
+ int defaultPos(void);
+ void setDefaultPos(unsigned int pos);
+
+private:
+ struct {
+ QStringList names; // menu names
+ QStringList actions; // commands to enact
+ int defaultPos; // position of default action in list
+ } my;
+};
+
+#endif // QED_ACTIONLIST_H
diff --git a/src/libpcp_qed/src/qed_app.cpp b/src/libpcp_qed/src/qed_app.cpp
new file mode 100644
index 0000000..7aa3268
--- /dev/null
+++ b/src/libpcp_qed/src/qed_app.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "qed_app.h"
+#include "qed_console.h"
+
+QedApp::QedApp(int &argc, char **argv) : QApplication(argc, argv)
+{
+ // TODO: rewrite with pmOptions
+ __pmSetProgname(argv[0]);
+ my.argc = argc;
+ my.argv = argv;
+ my.pmnsfile = NULL;
+ my.Lflag = 0;
+ my.Sflag = NULL;
+ my.Tflag = NULL;
+ my.Aflag = NULL;
+ my.Oflag = NULL;
+ my.zflag = 0;
+ my.tz = NULL;
+ my.port = -1;
+
+ QCoreApplication::setOrganizationName("PCP");
+ QCoreApplication::setApplicationName(pmProgname);
+ QCoreApplication::setApplicationVersion(pmGetConfig("PCP_VERSION"));
+ QString confirm = pmGetConfig("PCP_BIN_DIR");
+ confirm.prepend("PCP_XCONFIRM_PROG=");
+ confirm.append("/pmquery");
+ putenv(strdup((const char *)confirm.toAscii()));
+ if (getenv("PCP_STDERR") == NULL) // do not overwrite, for QA
+ putenv(strdup("PCP_STDERR=DISPLAY"));
+}
+
+QFont *QedApp::globalFont()
+{
+ static QFont *globalFont;
+ if (!globalFont)
+ globalFont = new QFont("Sans Serif", QedApp::globalFontSize());
+ return globalFont;
+}
+
+int QedApp::globalFontSize()
+{
+#ifdef IS_DARWIN
+ return 9;
+#else
+ return 7;
+#endif
+}
+
+int QedApp::getopts(const char *options)
+{
+ int unknown = 0;
+ int c, sts, errflg = 0;
+ char *endnum, *msg;
+
+ do {
+ switch ((c = getopt(my.argc, my.argv, options))) {
+
+ case 'A': /* sample alignment */
+ my.Aflag = optarg;
+ continue;
+
+ case 'a':
+ my.archives.append(optarg);
+ break;
+
+ case 'D':
+ sts = __pmParseDebug(optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, optarg);
+ errflg++;
+ } else
+ pmDebug |= sts;
+ break;
+
+ case 'h':
+ my.hosts.append(optarg);
+ break;
+
+ case 'L': /* local context */
+ my.Lflag = 1;
+ break;
+
+ case 'n': /* alternative PMNS */
+ my.pmnsfile = optarg;
+ break;
+
+ case 'O': /* sample offset */
+ my.Oflag = optarg;
+ break;
+
+ case 'p': /* existing pmtime port */
+ my.port = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0' || c < 0) {
+ pmprintf("%s: -p requires a numeric argument\n", pmProgname);
+ errflg++;
+ }
+ break;
+
+ case 'S': /* start run time */
+ my.Sflag = optarg;
+ break;
+
+ case 't': /* sampling interval */
+ if (pmParseInterval(optarg, &my.delta, &msg) < 0) {
+ pmprintf("%s: cannot parse interval\n%s", pmProgname, msg);
+ free(msg);
+ errflg++;
+ }
+ continue;
+
+ case 'T': /* run time */
+ my.Tflag = optarg;
+ break;
+
+ case 'V': /* version */
+ printf("%s %s\n", pmProgname, pmGetConfig("PCP_VERSION"));
+ exit(0);
+
+ case 'z': /* timezone from host */
+ if (my.tz != NULL) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ errflg++;
+ }
+ my.zflag++;
+ break;
+
+ case 'Z': /* $TZ timezone */
+ if (my.zflag) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ errflg++;
+ }
+ my.tz = optarg;
+ break;
+
+ default:
+ unknown = 1;
+ break;
+ }
+ } while (!unknown);
+
+ return c;
+}
+
+// a := a + b for struct timevals
+void QedApp::timevalAdd(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec += b->tv_usec;
+ if (a->tv_usec > 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec++;
+ }
+ a->tv_sec += b->tv_sec;
+}
+
+//
+// a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b
+//
+int QedApp::timevalCmp(struct timeval *a, struct timeval *b)
+{
+ int res = (int)(a->tv_sec - b->tv_sec);
+ if (res == 0)
+ res = (int)(a->tv_usec - b->tv_usec);
+ return res;
+}
+
+// convert timeval to seconds
+double QedApp::timevalToSeconds(struct timeval t)
+{
+ return t.tv_sec + (t.tv_usec / 1000000.0);
+}
+
+// conversion from seconds (double precision) to struct timeval
+void QedApp::timevalFromSeconds(double value, struct timeval *tv)
+{
+ tv->tv_sec = (time_t)value;
+ tv->tv_usec = (long)(((value - (double)tv->tv_sec) * 1000000.0));
+}
+
+// debugging, display seconds-since-epoch in human readable format
+char *QedApp::timeString(double seconds)
+{
+ static char string[32];
+ time_t secs = (time_t)seconds;
+ char *s;
+
+ s = pmCtime(&secs, string);
+ s[strlen(s)-1] = '\0';
+ return s;
+}
+
+// return a string containing hour and milliseconds
+char *QedApp::timeHiResString(double time)
+{
+ static char s[16];
+ char m[8];
+ time_t secs = (time_t)time;
+ struct tm t;
+
+ sprintf(m, "%.3f", time - floor(time));
+ pmLocaltime(&secs, &t);
+ sprintf(s, "%02d:%02d:%02d.%s", t.tm_hour, t.tm_min, t.tm_sec, m+2);
+ s[strlen(s)-1] = '\0';
+ return s;
+}
+
+void QedApp::nomem(void)
+{
+ // no point trying to report anything ... dump core is the best bet
+ abort();
+}
+
+QPixmap QedApp::cached(const QString &image)
+{
+ if (QPixmap *p = QPixmapCache::find(image))
+ return *p;
+
+ QPixmap pm;
+ pm = QPixmap::fromImage(QImage(image),
+ Qt::OrderedDither | Qt::OrderedAlphaDither);
+ if (pm.isNull())
+ return QPixmap();
+
+ QPixmapCache::insert(image, pm);
+ return pm;
+}
+
+// call startconsole() after command line args processed so pmDebug
+// has a chance to be set
+//
+void QedApp::startconsole(void)
+{
+ struct timeval origin;
+
+ gettimeofday(&origin, NULL);
+ console = new QedConsole(origin);
+}
diff --git a/src/libpcp_qed/src/qed_app.h b/src/libpcp_qed/src/qed_app.h
new file mode 100644
index 0000000..bb3859b
--- /dev/null
+++ b/src/libpcp_qed/src/qed_app.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_APP_H
+#define QED_APP_H
+
+#include <QtGui/QApplication>
+#include <QtGui/QPixmapCache>
+#include <QtGui/QFont>
+
+class QedApp : public QApplication
+{
+ Q_OBJECT
+
+public:
+ typedef enum {
+ DebugApp = 0x1,
+ DebugUi = 0x1,
+ DebugProtocol = 0x2,
+ DebugView = 0x4,
+ DebugTimeless = 0x8,
+ DebugForce = 0x10,
+ } DebugOptions;
+
+ QedApp(int &, char **);
+ virtual ~QedApp() { }
+ int getopts(const char *options);
+ void startconsole(void);
+
+ static QFont *globalFont();
+ static int globalFontSize();
+
+ static void nomem(void);
+ static QPixmap cached(const QString &);
+
+ static void timevalAdd(struct timeval *, struct timeval *);
+ static int timevalCmp(struct timeval *, struct timeval *);
+ static double timevalToSeconds(struct timeval);
+ static void timevalFromSeconds(double, struct timeval *);
+ static char *timeString(double);
+ static char *timeHiResString(double);
+
+ struct {
+ int argc;
+ char **argv;
+
+ char *pmnsfile; /* local namespace file */
+ int Lflag; /* local context mode */
+ char *Sflag; /* argument of -S flag */
+ char *Tflag; /* argument of -T flag */
+ char *Aflag; /* argument of -A flag */
+ char *Oflag; /* argument of -O flag */
+ int zflag; /* for -z (source zone) */
+ char *tz; /* for -Z timezone */
+ int port; /* pmtime port number */
+
+ struct timeval delta;
+ struct timeval logStartTime;
+ struct timeval logEndTime;
+ struct timeval realStartTime;
+ struct timeval realEndTime;
+ struct timeval position;
+
+ QStringList hosts;
+ QStringList archives;
+ QString tzLabel;
+ QString tzString;
+ } my;
+};
+
+#endif // QED_APP_H
diff --git a/src/libpcp_qed/src/qed_bar.cpp b/src/libpcp_qed/src/qed_bar.cpp
new file mode 100644
index 0000000..72b7a96
--- /dev/null
+++ b/src/libpcp_qed/src/qed_bar.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "qed_bar.h"
+
+QedBar::QedBar(QWidget *parent, int x, int y, int w, int h) : QedGadget(parent)
+{
+ (void)x;
+ (void)y;
+ (void)w;
+ (void)h;
+}
+
+void
+QedBar::setMinimum(double minimum)
+{
+ (void)minimum;
+}
+
+void
+QedBar::setMaximum(double maximum)
+{
+ (void)maximum;
+}
+
+void
+QedBar::setColor(const char *color)
+{
+ (void)color;
+}
+
+void
+QedBar::setOrientation(Qt::Orientation o)
+{
+ (void)o;
+}
+
+void
+QedBar::setScaleRange(int range)
+{
+ (void)range;
+}
+
+void
+QedBar::paintEvent(QPaintEvent *event)
+{
+ (void)event;
+}
+
+void
+QedBar::resizeEvent(QResizeEvent *event)
+{
+ (void)event;
+}
+
+QedMultiBar::QedMultiBar(QWidget *parent, int x, int y, int w, int h,
+ QedColorList *l, int history) : QedBar(parent, x, y, w, h)
+{
+ (void)history;
+ (void)l;
+}
+
+void
+QedMultiBar::setOutline(bool on)
+{
+ (void)on;
+}
+
+void
+QedMultiBar::setMaximum(double maximum, bool on)
+{
+ (void)maximum;
+ (void)on;
+}
+
+QedBarGraph::QedBarGraph(QWidget *parent, int x, int y, int w, int h, int history)
+ : QedBar(parent, x, y, w, h)
+{
+ (void)history;
+}
+
+void
+QedBarGraph::clipRange(void)
+{
+}
diff --git a/src/libpcp_qed/src/qed_bar.h b/src/libpcp_qed/src/qed_bar.h
new file mode 100644
index 0000000..8aafe36
--- /dev/null
+++ b/src/libpcp_qed/src/qed_bar.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_BAR_H
+#define QED_BAR_H
+
+#include <QtGui>
+#include "qed_gadget.h"
+#include "qed_colorlist.h"
+
+class QedBar : public QedGadget
+{
+ Q_OBJECT
+
+public:
+ QedBar(QWidget *parent, int x, int y, int w, int h);
+ void setMinimum(double min);
+ void setMaximum(double max);
+ void setColor(const char *color);
+ void setOrientation(Qt::Orientation);
+ void setScaleRange(int range);
+
+protected:
+ virtual void paintEvent(QPaintEvent *);
+ virtual void resizeEvent(QResizeEvent *);
+
+ struct {
+ QColor color;
+ Qt::Orientation oriented;
+ int scaleRange;
+ double minimum;
+ double maximum;
+ } my;
+};
+
+class QedMultiBar : public QedBar
+{
+ Q_OBJECT
+
+public:
+ QedMultiBar(QWidget *parent, int x, int y, int w, int h,
+ QedColorList *l, int history);
+ void setOutline(bool);
+ void setMaximum(double, bool);
+};
+
+class QedBarGraph : public QedBar
+{
+ Q_OBJECT
+
+public:
+ QedBarGraph(QWidget *parent, int x, int y, int w, int h, int history);
+ void clipRange();
+};
+
+#endif // QED_BAR_H
diff --git a/src/libpcp_qed/src/qed_colorlist.cpp b/src/libpcp_qed/src/qed_colorlist.cpp
new file mode 100644
index 0000000..cb008f0
--- /dev/null
+++ b/src/libpcp_qed/src/qed_colorlist.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qed_colorlist.h"
+
+QedColorList::QedColorList(const char *id) : QString(id)
+{
+ // TODO
+}
+
+const char *
+QedColorList::identity(void) const
+{
+ return this->toAscii();
+}
+
+void
+QedColorList::addColor(const char *name)
+{
+ my.names << QString(name);
+}
+
+unsigned int
+QedColorList::length(void)
+{
+ return my.names.length();
+}
diff --git a/src/libpcp_qed/src/qed_colorlist.h b/src/libpcp_qed/src/qed_colorlist.h
new file mode 100644
index 0000000..b918773
--- /dev/null
+++ b/src/libpcp_qed/src/qed_colorlist.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_COLORLIST_H
+#define QED_COLORLIST_H
+
+#include <QtGui>
+
+class QedColorList : public QString
+{
+public:
+ QedColorList(const char *id);
+ void addColor(const char *name);
+ const char *identity() const;
+ unsigned int length();
+
+private:
+ struct {
+ QStringList names; // color names
+ } my;
+};
+
+#endif // QED_COLORLIST_H
diff --git a/src/libpcp_qed/src/qed_colorpicker.cpp b/src/libpcp_qed/src/qed_colorpicker.cpp
new file mode 100644
index 0000000..f4a5405
--- /dev/null
+++ b/src/libpcp_qed/src/qed_colorpicker.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2014 Red Hat.
+ * Copyright (C) 1999-2005 Trolltech AS. All rights reserved.
+ *
+ * This file was derived from the QT QColorDialog class
+ *
+ * This file may be distributed under the terms of the Q Public License
+ * as defined by Trolltech AS of Norway and appearing in the file
+ * LICENSE.QPL included in the packaging of this file.
+ *
+ * This file may be distributed and/or modified under the terms of the
+ * GNU General Public License version 2 as published by the Free Software
+ * Foundation and appearing in the file LICENSE.GPL included in the
+ * packaging of this file.
+ *
+ * Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
+ * licenses may use this file in accordance with the Qt Commercial License
+ * Agreement provided with the Software.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <QApplication>
+#include <QPainter>
+#include "qed_colorpicker.h"
+
+static int pWidth = 172;
+static int pHeight = 172;
+
+int QedColorLuminancePicker::y2val(int y)
+{
+ int d = height() - 2*coff - 1;
+ return 255 - (y - coff)*255/d;
+}
+
+int QedColorLuminancePicker::val2y(int v)
+{
+ int d = height() - 2*coff - 1;
+ return coff + (255-v)*d/255;
+}
+
+QedColorLuminancePicker::QedColorLuminancePicker(QWidget* parent)
+ :QWidget(parent)
+{
+ hue = 100; val = 100; sat = 100;
+ pix = 0;
+ // setAtribute(QA_NoErase, true);
+}
+
+QedColorLuminancePicker::~QedColorLuminancePicker()
+{
+ delete pix;
+}
+
+void QedColorLuminancePicker::mouseMoveEvent(QMouseEvent *m)
+{
+ setVal(y2val(m->y()));
+}
+void QedColorLuminancePicker::mousePressEvent( QMouseEvent *m )
+{
+ setVal(y2val(m->y()));
+}
+
+void QedColorLuminancePicker::setVal(int v)
+{
+ if (val == v)
+ return;
+ val = qMax(0, qMin(v,255));
+ delete pix; pix=0;
+ repaint();
+ Q_EMIT newHsv(hue, sat, val);
+}
+
+//receives from a hue,sat chooser and relays.
+void QedColorLuminancePicker::setCol(int h, int s)
+{
+ setCol(h, s, val);
+ Q_EMIT newHsv(h, s, val);
+}
+
+void QedColorLuminancePicker::paintEvent(QPaintEvent *)
+{
+ int w = width() - 5;
+
+ QRect r(0, foff, w, height() - 2*foff);
+ int wi = r.width() - 2;
+ int hi = r.height() - 2;
+ if (!pix || pix->height() != hi || pix->width() != wi) {
+ delete pix;
+ QImage img(wi, hi, QImage::Format_RGB32);
+ int y;
+ for (y = 0; y < hi; y++) {
+ QColor c;
+ c.setHsv(hue, sat, y2val(y+coff));
+ QRgb r = c.rgb();
+ int x;
+ for (x = 0; x < wi; x++)
+ img.setPixel(x, y, r);
+ }
+ pix = new QPixmap(QPixmap::fromImage(img));
+ }
+ QPainter p(this);
+ p.drawPixmap(1, coff, *pix);
+ const QPalette &g = palette();
+ qDrawShadePanel(&p, r, g, true);
+ p.setPen(g.foreground().color());
+ p.setBrush(g.foreground());
+ QPolygon a;
+ int y = val2y(val);
+ a.setPoints(3, w, y, w+5, y+5, w+5, y-5);
+ p.eraseRect(w, 0, 5, height());
+ p.drawPolygon(a);
+}
+
+void QedColorLuminancePicker::setCol( int h, int s , int v )
+{
+ val = v;
+ hue = h;
+ sat = s;
+ delete pix; pix=0;
+ repaint();
+}
+
+QPoint QedColorPicker::colPt()
+{ return QPoint((360-hue)*(pWidth-1)/360, (255-sat)*(pHeight-1)/255); }
+
+int QedColorPicker::huePt(const QPoint &pt)
+{ return 360 - pt.x()*360/(pWidth-1); }
+
+int QedColorPicker::satPt(const QPoint &pt)
+{ return 255 - pt.y()*255/(pHeight-1) ; }
+
+void QedColorPicker::setCol(const QPoint &pt)
+{ setCol(huePt(pt), satPt(pt)); }
+
+QedColorPicker::QedColorPicker(QWidget* parent)
+ : QFrame(parent)
+{
+ hue = 0; sat = 0;
+ setCol(150, 255);
+
+ QImage img(pWidth, pHeight, QImage::Format_RGB32);
+ int x,y;
+ for (y = 0; y < pHeight; y++)
+ for (x = 0; x < pWidth; x++) {
+ QPoint p(x, y);
+ QColor c;
+ c.setHsv(huePt(p), satPt(p), 200);
+ img.setPixel(x, y, c.rgb());
+ }
+ pix = new QPixmap(QPixmap::fromImage(img));
+ setAttribute(Qt::WA_NoSystemBackground);
+ setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) );
+}
+
+QedColorPicker::~QedColorPicker()
+{
+ delete pix;
+}
+
+QSize QedColorPicker::sizeHint() const
+{
+ return QSize(pWidth + 2*frameWidth(), pHeight + 2*frameWidth());
+}
+
+void QedColorPicker::setCol(int h, int s)
+{
+ int nhue = qMin(qMax(0,h), 359);
+ int nsat = qMin(qMax(0,s), 255);
+ if (nhue == hue && nsat == sat)
+ return;
+ QRect r(colPt(), QSize(20,20));
+ hue = nhue; sat = nsat;
+ r = r.unite(QRect(colPt(), QSize(20,20)));
+ r.translate(contentsRect().x()-9, contentsRect().y()-9);
+ // update(r);
+ repaint(r);
+}
+
+void QedColorPicker::mouseMoveEvent(QMouseEvent *m)
+{
+ QPoint p = m->pos() - contentsRect().topLeft();
+ setCol(p);
+ Q_EMIT newCol(hue, sat);
+}
+
+void QedColorPicker::mousePressEvent(QMouseEvent *m)
+{
+ QPoint p = m->pos() - contentsRect().topLeft();
+ setCol(p);
+ Q_EMIT newCol(hue, sat);
+}
+
+void QedColorPicker::paintEvent(QPaintEvent *e)
+{
+ QFrame::paintEvent(e);
+ QPainter p(this);
+ QRect r = contentsRect();
+
+ p.drawPixmap(r.topLeft(), *pix);
+ QPoint pt = colPt() + r.topLeft();
+ p.setPen(Qt::black);
+
+ p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black);
+ p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black);
+}
+
+void QedColorShowLabel::paintEvent(QPaintEvent *e)
+{
+ QFrame::paintEvent(e);
+ QPainter p(this);
+ p.fillRect(contentsRect()&e->rect(), col);
+}
+
+void QedColorShowLabel::mousePressEvent(QMouseEvent *e)
+{
+ mousePressed = true;
+ pressPos = e->pos();
+}
+
+void QedColorShowLabel::mouseMoveEvent(QMouseEvent *e)
+{
+#ifdef QT_NO_DRAGANDDROP
+ Q_UNUSED(e);
+#else
+ if (!mousePressed)
+ return;
+ if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) {
+ QMimeData *mime = new QMimeData;
+ mime->setColorData(col);
+ QPixmap pix(30, 20);
+ pix.fill(col);
+ QPainter p(&pix);
+ p.drawRect(0, 0, pix.width(), pix.height());
+ p.end();
+ QDrag *drg = new QDrag(this);
+ drg->setMimeData(mime);
+ drg->setPixmap(pix);
+ mousePressed = false;
+ drg->start();
+ }
+#endif
+}
+
+#ifndef QT_NO_DRAGANDDROP
+void QedColorShowLabel::dragEnterEvent(QDragEnterEvent *e)
+{
+ if (qvariant_cast<QColor>(e->mimeData()->colorData()).isValid())
+ e->accept();
+ else
+ e->ignore();
+}
+
+void QedColorShowLabel::dragLeaveEvent(QDragLeaveEvent *)
+{
+}
+
+void QedColorShowLabel::dropEvent(QDropEvent *e)
+{
+ QColor color = qvariant_cast<QColor>(e->mimeData()->colorData());
+ if (color.isValid()) {
+ col = color;
+ repaint();
+ Q_EMIT colorDropped(col.rgb());
+ e->accept();
+ } else {
+ e->ignore();
+ }
+}
+#endif // QT_NO_DRAGANDDROP
+
+void QedColorShowLabel::mouseReleaseEvent( QMouseEvent * )
+{
+ if (!mousePressed)
+ return;
+ mousePressed = false;
+}
+
+QedColLineEdit::QedColLineEdit(QWidget *parent) : QLineEdit(parent)
+{
+ connect(this, SIGNAL(textEdited(const QString&)),
+ this, SLOT(textEdited(const QString&)));
+}
+
+void QedColLineEdit::textEdited(const QString &text)
+{
+ QColor c;
+ c.setNamedColor(text);
+ if (c.isValid()) {
+ setColor(c);
+ Q_EMIT newColor(c);
+ }
+}
+
+void QedColLineEdit::setColor(QColor c)
+{
+ col = c;
+ setText(col.name());
+}
+
+void QedColLineEdit::setCol(int h, int s, int v)
+{
+ col.setHsv(h, s, v);
+ setText(col.name());
+}
diff --git a/src/libpcp_qed/src/qed_colorpicker.h b/src/libpcp_qed/src/qed_colorpicker.h
new file mode 100644
index 0000000..2b804b1
--- /dev/null
+++ b/src/libpcp_qed/src/qed_colorpicker.h
@@ -0,0 +1,170 @@
+/*
+** Copyright (C) 2014 Red Hat.
+** Copyright (C) 1999-2007 Trolltech AS. All rights reserved.
+**
+** This file is derived from part of the QtGui module of the Qt Toolkit.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**/
+#ifndef QED_COLORPICKER_H
+#define QED_COLORPICKER_H
+
+#include <QFrame>
+#include <QLabel>
+#include <QSpinBox>
+#include <QLineEdit>
+#include <QValidator>
+#include <QDragEnterEvent>
+#include <QDropEvent>
+#include <QMouseEvent>
+#include <QPaintEvent>
+#include <QDragLeaveEvent>
+
+static inline void rgb2hsv(QRgb rgb, int &h, int &s, int &v)
+{
+ QColor c;
+ c.setRgb(rgb);
+ c.getHsv(&h, &s, &v);
+}
+
+class QedColorPicker : public QFrame
+{
+ Q_OBJECT
+public:
+ QedColorPicker(QWidget* parent);
+ ~QedColorPicker();
+
+public slots:
+ void setCol(int h, int s);
+
+Q_SIGNALS:
+ void newCol(int h, int s);
+
+protected:
+ QSize sizeHint() const;
+ void paintEvent(QPaintEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void mousePressEvent(QMouseEvent *);
+
+private:
+ int hue;
+ int sat;
+
+ QPoint colPt();
+ int huePt(const QPoint &pt);
+ int satPt(const QPoint &pt);
+ void setCol(const QPoint &pt);
+
+ QPixmap *pix;
+};
+
+class QedColorLuminancePicker : public QWidget
+{
+ Q_OBJECT
+public:
+ QedColorLuminancePicker(QWidget* parent);
+ ~QedColorLuminancePicker();
+
+public slots:
+ void setCol(int h, int s, int v);
+ void setCol(int h, int s);
+
+Q_SIGNALS:
+ void newHsv(int h, int s, int v);
+
+protected:
+ void paintEvent(QPaintEvent *);
+ void mouseMoveEvent(QMouseEvent *);
+ void mousePressEvent(QMouseEvent *);
+
+private:
+ enum { foff = 3, coff = 4 }; //frame and contents offset
+ int val;
+ int hue;
+ int sat;
+
+ int y2val(int y);
+ int val2y(int val);
+ void setVal(int v);
+
+ QPixmap *pix;
+};
+
+class QedColSpinBox : public QSpinBox
+{
+public:
+ QedColSpinBox(QWidget *parent)
+ : QSpinBox(parent) { setRange(0, 255); }
+ void setValue(int i) {
+ bool block = signalsBlocked();
+ blockSignals(true);
+ QSpinBox::setValue(i);
+ blockSignals(block);
+ }
+};
+
+class QedColLineEdit : public QLineEdit
+{
+ Q_OBJECT
+
+public:
+ QedColLineEdit(QWidget *parent);
+
+public slots:
+ void setColor(QColor c);
+ void setCol(int h, int s, int v);
+ void textEdited(const QString &text);
+
+Q_SIGNALS:
+ void newColor(QColor c);
+
+private:
+ QColor col;
+};
+
+class QedColorShowLabel : public QFrame
+{
+ Q_OBJECT
+
+public:
+ QedColorShowLabel(QWidget *parent) : QFrame(parent) {
+ setFrameStyle(QFrame::Panel|QFrame::Sunken);
+ setAcceptDrops(true);
+ mousePressed = false;
+ }
+ void setColor(QColor c) { col = c; update(); }
+
+Q_SIGNALS:
+ void colorDropped(QRgb);
+
+protected:
+ void paintEvent(QPaintEvent *e);
+ void mousePressEvent(QMouseEvent *e);
+ void mouseMoveEvent(QMouseEvent *e);
+ void mouseReleaseEvent(QMouseEvent *e);
+#ifndef QT_NO_DRAGANDDROP
+ void dragEnterEvent(QDragEnterEvent *e);
+ void dragLeaveEvent(QDragLeaveEvent *e);
+ void dropEvent(QDropEvent *e);
+#endif
+
+private:
+ QColor col;
+ bool mousePressed;
+ QPoint pressPos;
+};
+
+#endif /* QED_COLORPICKER_H */
diff --git a/src/libpcp_qed/src/qed_console.cpp b/src/libpcp_qed/src/qed_console.cpp
new file mode 100644
index 0000000..30492aa
--- /dev/null
+++ b/src/libpcp_qed/src/qed_console.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <stdarg.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "qed_console.h"
+
+QedConsole *console;
+
+QedConsole::QedConsole(struct timeval origin) : QDialog()
+{
+ my.level = 0;
+ if (pmDebug & DBG_TRACE_APPL0) {
+ my.level |= QedApp::DebugApp; // general and UI tracing
+ my.level |= QedApp::DebugUi;
+ }
+ if (pmDebug & DBG_TRACE_APPL1)
+ my.level |= QedApp::DebugProtocol; // trace time protocol
+ if (pmDebug & DBG_TRACE_APPL2) {
+ my.level |= QedApp::DebugView; // config files, for QA
+ my.level |= QedApp::DebugTimeless;
+ }
+ setupUi(this);
+
+ my.origin = QedApp::timevalToSeconds(origin);
+ post("Console available");
+}
+
+void QedConsole::post(const char *fmt, ...)
+{
+ static char buffer[4096];
+ struct timeval now;
+ va_list ap;
+ int offset = 0;
+
+ if (!(my.level & QedApp::DebugApp))
+ return;
+
+ if (!(my.level & QedApp::DebugTimeless)) {
+ gettimeofday(&now, NULL);
+ sprintf(buffer, "%6.2f: ", QedApp::timevalToSeconds(now) - my.origin);
+ offset = 8;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(buffer+offset, sizeof(buffer)-offset, fmt, ap);
+ va_end(ap);
+
+ fputs(buffer, stderr);
+ fputc('\n', stderr);
+ text->append(QString(buffer));
+}
+
+bool QedConsole::logLevel(int level)
+{
+ if (!(my.level & level))
+ return false;
+ return true;
+}
+
+void QedConsole::post(int level, const char *fmt, ...)
+{
+ static char buffer[4096];
+ struct timeval now;
+ va_list ap;
+ int offset = 0;
+
+ if (!(my.level & level) && !(level & QedApp::DebugForce))
+ return;
+
+ if (!(my.level & QedApp::DebugTimeless)) {
+ gettimeofday(&now, NULL);
+ sprintf(buffer, "%6.2f: ", QedApp::timevalToSeconds(now) - my.origin);
+ offset = 8;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(buffer+offset, sizeof(buffer)-offset, fmt, ap);
+ va_end(ap);
+
+ fputs(buffer, stderr);
+ fputc('\n', stderr);
+ text->append(QString(buffer));
+}
diff --git a/src/libpcp_qed/src/qed_console.h b/src/libpcp_qed/src/qed_console.h
new file mode 100644
index 0000000..f1425e5
--- /dev/null
+++ b/src/libpcp_qed/src/qed_console.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_CONSOLE_H
+#define QED_CONSOLE_H
+
+#include "ui_qed_console.h"
+#include "qed_app.h"
+
+class QedConsole : public QDialog, public Ui::QedConsole
+{
+ Q_OBJECT
+
+public:
+ QedConsole(struct timeval);
+ void post(const char *p, ...);
+ void post(int level, const char *p, ...);
+ bool logLevel(int level = QedApp::DebugApp);
+
+private:
+ struct {
+ int level;
+ double origin;
+ } my;
+};
+
+extern QedConsole *console;
+
+#endif // QED_CONSOLE_H
diff --git a/src/libpcp_qed/src/qed_console.ui b/src/libpcp_qed/src/qed_console.ui
new file mode 100644
index 0000000..b6d571f
--- /dev/null
+++ b/src/libpcp_qed/src/qed_console.ui
@@ -0,0 +1,132 @@
+<ui version="4.0" >
+ <class>QedConsole</class>
+ <widget class="QDialog" name="QedConsole" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>493</width>
+ <height>326</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>PCP GUI Console</string>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QTextEdit" name="text" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFormatting" >
+ <set>QTextEdit::AutoNone</set>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonHide" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>85</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>85</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>&amp;Hide</string>
+ </property>
+ <property name="shortcut" >
+ <string>Alt+H</string>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <connections>
+ <connection>
+ <sender>buttonHide</sender>
+ <signal>clicked()</signal>
+ <receiver>QedConsole</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/libpcp_qed/src/qed_fileiconprovider.cpp b/src/libpcp_qed/src/qed_fileiconprovider.cpp
new file mode 100644
index 0000000..7c3d939
--- /dev/null
+++ b/src/libpcp_qed/src/qed_fileiconprovider.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "qed_fileiconprovider.h"
+#include "qed_console.h"
+
+#define DESPERATE 0
+
+QedFileIconProvider *fileIconProvider;
+
+QedFileIconProvider::QedFileIconProvider() : QFileIconProvider()
+{
+ // generic Qt QFileIconProvider types
+ my.file = QIcon(":/images/filegeneric.png");
+ my.folder = QIcon(":/images/filefolder.png");
+ my.computer = QIcon(":/images/computer.png");
+
+ // PCP GUI specific images
+ my.fileView = QIcon(":/images/fileview.png");
+ my.fileFolio = QIcon(":/images/filefolio.png");
+ my.fileArchive = QIcon(":/images/filearchive.png");
+
+ // images for several other common file types
+ my.fileHtml = QIcon(":/images/filehtml.png");
+ my.fileImage = QIcon(":/images/fileimage.png");
+ my.filePackage = QIcon(":/images/filepackage.png");
+ my.fileSpreadSheet = QIcon(":/images/filespreadsheet.png");
+ my.fileWordProcessor = QIcon(":/images/filewordprocessor.png");
+}
+
+QIcon QedFileIconProvider::icon(FileIconType type) const
+{
+ console->post("QedFileIconProvider::icon extended types");
+ switch (type) {
+ case View:
+ return my.fileView;
+ case Folio:
+ return my.fileFolio;
+ case Archive:
+ return my.fileArchive;
+ case Html:
+ return my.fileHtml;
+ case Image:
+ return my.fileImage;
+ case Package:
+ return my.filePackage;
+ case SpreadSheet:
+ return my.fileSpreadSheet;
+ case WordProcessor:
+ return my.fileWordProcessor;
+ default:
+ break;
+ }
+ return my.file;
+}
+
+QIcon QedFileIconProvider::icon(IconType type) const
+{
+ console->post("QedFileIconProvider::icon type");
+ switch (type) {
+ case File:
+ return my.file;
+ case Folder:
+ return my.folder;
+ case Computer:
+ return my.computer;
+ default:
+ break;
+ }
+ return QFileIconProvider::icon(type);
+}
+
+QString QedFileIconProvider::type(const QFileInfo &fi) const
+{
+ console->post("QedFileIconProvider::type string");
+ return QFileIconProvider::type(fi);
+}
+
+QIcon QedFileIconProvider::icon(const QFileInfo &fi) const
+{
+#if DESPERATE
+ console->post("QedFileIconProvider::icon - %s",
+ (const char *)fi.filePath().toAscii());
+#endif
+
+ if (fi.isFile()) {
+ QFile file(fi.filePath());
+ file.open(QIODevice::ReadOnly);
+ char block[9];
+ int count = file.read(block, sizeof(block)-1);
+ if (count == sizeof(block)-1) {
+ static const char *viewmagic[] = { "#kmchart", "#pmchart" };
+ static char foliomagic[] = "PCPFolio";
+ static char archmagic[] = "\0\0\0\204\120\5\46\2"; //PM_LOG_MAGIC|V2
+
+ if (memcmp(viewmagic[0], block, sizeof(block)-1) == 0)
+ return my.fileView;
+ if (memcmp(viewmagic[1], block, sizeof(block)-1) == 0)
+ return my.fileView;
+ if (memcmp(foliomagic, block, sizeof(block)-1) == 0)
+ return my.fileFolio;
+ if (memcmp(archmagic, block, sizeof(block)-1) == 0)
+ return my.fileArchive;
+ }
+#if DESPERATE
+ console->post(" Got %d bytes from %s: \"%c%c%c%c%c%c%c%c\"", count,
+ (const char *) fi.filePath().toAscii(), block[0], block[1],
+ block[2], block[3], block[4], block[5], block[6], block[7]);
+#endif
+ QString ext = fi.suffix();
+ if (ext == "htm" || ext == "html")
+ return my.fileHtml;
+ if (ext == "svg" || ext == "gif" || ext == "jpg" || ext == "jpeg" ||
+ ext == "png" || ext == "xpm" || ext == "odg" /* ... */ )
+ return my.fileImage;
+ if (ext == "tar" || ext == "tgz" || ext == "deb" || ext == "rpm" ||
+ ext == "zip" || ext == "bz2" || ext == "gz" || ext == "xz")
+ return my.filePackage;
+ if (ext == "ods" || ext == "xls")
+ return my.fileSpreadSheet;
+ if (ext == "odp" || ext == "doc")
+ return my.fileWordProcessor;
+ return my.file; // catch-all for every other regular file
+ }
+ else if (fi.isDir()) {
+ return my.folder;
+ }
+ return QFileIconProvider::icon(fi);
+}
diff --git a/src/libpcp_qed/src/qed_fileiconprovider.h b/src/libpcp_qed/src/qed_fileiconprovider.h
new file mode 100644
index 0000000..1fc7e41
--- /dev/null
+++ b/src/libpcp_qed/src/qed_fileiconprovider.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_FILEICONPROVIDER_H
+#define QED_FILEICONPROVIDER_H
+
+#include <QtGui/QApplication>
+#include <QtGui/QFileIconProvider>
+
+class QedFileIconProvider : public QFileIconProvider
+{
+public:
+ QedFileIconProvider();
+
+ typedef enum { View, Folio, Archive, Html, Image, // IconType++
+ Package, SpreadSheet, WordProcessor } FileIconType;
+ QIcon icon(FileIconType type) const;
+
+ QIcon icon(IconType type) const;
+ QIcon icon(const QFileInfo &info) const;
+ QString type(const QFileInfo &info) const;
+
+private:
+ struct {
+ QIcon file;
+ QIcon folder;
+ QIcon computer;
+
+ QIcon fileView; // pmchart view
+ QIcon fileFolio; // PCP folio
+ QIcon fileArchive; // PCP archive
+ QIcon fileHtml;
+ QIcon fileImage;
+ QIcon filePackage;
+ QIcon fileSpreadSheet;
+ QIcon fileWordProcessor;
+ } my;
+};
+
+extern QedFileIconProvider *fileIconProvider;
+
+#endif // QED_FILEICONPROVIDER_H
diff --git a/src/libpcp_qed/src/qed_gadget.cpp b/src/libpcp_qed/src/qed_gadget.cpp
new file mode 100644
index 0000000..beee4b9
--- /dev/null
+++ b/src/libpcp_qed/src/qed_gadget.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qed_gadget.h"
+
+QedGadget::QedGadget(QWidget *parent) : QWidget(parent)
+{
+ my.depth = -1;
+}
+
+void
+QedGadget::dump(FILE *f)
+{
+ fprintf(f, " depth=%d\n", my.depth);
+}
diff --git a/src/libpcp_qed/src/qed_gadget.h b/src/libpcp_qed/src/qed_gadget.h
new file mode 100644
index 0000000..4b3e66b
--- /dev/null
+++ b/src/libpcp_qed/src/qed_gadget.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_GADGET_H
+#define QED_GADGET_H
+
+#include <QtGui>
+
+class QedGadget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ QedGadget(QWidget *parent);
+ void dump(FILE *f);
+
+private:
+ struct {
+ int depth; // z-axis setting for rendering
+ } my;
+};
+
+#endif // QED_GADGET_H
diff --git a/src/libpcp_qed/src/qed_groupcontrol.cpp b/src/libpcp_qed/src/qed_groupcontrol.cpp
new file mode 100644
index 0000000..c42e83c
--- /dev/null
+++ b/src/libpcp_qed/src/qed_groupcontrol.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "qed_groupcontrol.h"
+#include "qed_console.h"
+#include "qed_app.h"
+
+QedGroupControl::QedGroupControl()
+{
+ my.realDelta = 0;
+ my.realPosition = 0;
+ my.timeState = StartState;
+ my.buttonState = QedTimeButton::Timeless;
+ my.pmtimeState = QmcTime::StoppedState;
+ memset(&my.delta, 0, sizeof(struct timeval));
+ memset(&my.position, 0, sizeof(struct timeval));
+}
+
+void QedGroupControl::init(struct timeval *interval, struct timeval *position)
+{
+ if (isArchiveSource()) {
+ my.pmtimeState = QmcTime::StoppedState;
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else {
+ my.pmtimeState = QmcTime::ForwardState;
+ my.buttonState = QedTimeButton::ForwardLive;
+ }
+ my.delta = *interval;
+ my.position = *position;
+ my.realDelta = QedApp::timevalToSeconds(*interval);
+ my.realPosition = QedApp::timevalToSeconds(*position);
+}
+
+QmcTime::State QedGroupControl::pmtimeState(void)
+{
+ return my.pmtimeState;
+}
+
+char *QedGroupControl::timeState()
+{
+ static char buf[16];
+
+ switch (my.timeState) {
+ case StartState: strcpy(buf, "Start"); break;
+ case ForwardState: strcpy(buf, "Forward"); break;
+ case BackwardState: strcpy(buf, "Backward"); break;
+ case EndLogState: strcpy(buf, "EndLog"); break;
+ case StandbyState: strcpy(buf, "Standby"); break;
+ default: strcpy(buf, "Dodgey"); break;
+ }
+ return buf;
+}
+
+//
+// Setup the initial data needed after opening a view.
+// All of the work is in archive mode; in live mode we have
+// not yet got any historical data that we can display...
+//
+void QedGroupControl::setupWorldView(struct timeval *interval,
+ struct timeval *position, struct timeval *start, struct timeval *end)
+{
+ if (isArchiveSource() == false)
+ return;
+
+ QmcTime::Packet packet;
+ packet.source = QmcTime::ArchiveSource;
+ packet.state = QmcTime::ForwardState;
+ packet.mode = QmcTime::NormalMode;
+ memcpy(&packet.delta, interval, sizeof(packet.delta));
+ memcpy(&packet.position, position, sizeof(packet.position));
+ memcpy(&packet.start, start, sizeof(packet.start));
+ memcpy(&packet.end, end, sizeof(packet.end));
+ adjustWorldView(&packet, true);
+}
+
+//
+// Received a Set or a VCRMode requiring us to adjust our state
+// and possibly rethink everything. This can result from a time
+// control position change, delta change, direction change, etc.
+//
+void QedGroupControl::adjustWorldView(QmcTime::Packet *packet, bool vcrMode)
+{
+ my.delta = packet->delta;
+ my.position = packet->position;
+ my.realDelta = QedApp::timevalToSeconds(packet->delta);
+ my.realPosition = QedApp::timevalToSeconds(packet->position);
+
+ console->post("QedGroupControl::adjustWorldView: "
+ "delta=%.2f position=%.2f (%s) state=%s",
+ my.realDelta, my.realPosition,
+ QedApp::timeString(my.realPosition), timeState());
+
+ QmcTime::State state = packet->state;
+ if (isArchiveSource()) {
+ if (packet->state == QmcTime::ForwardState)
+ adjustArchiveWorldViewForward(packet, vcrMode);
+ else if (packet->state == QmcTime::BackwardState)
+ adjustArchiveWorldViewBackward(packet, vcrMode);
+ else
+ adjustArchiveWorldViewStopped(packet, vcrMode);
+ }
+ else if (state != QmcTime::StoppedState)
+ adjustLiveWorldViewForward(packet);
+ else
+ adjustLiveWorldViewStopped(packet);
+}
+
+void QedGroupControl::adjustLiveWorldViewStopped(QmcTime::Packet *packet)
+{
+ if (isActive(packet)) {
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+ updateTimeButton();
+ }
+}
+
+void QedGroupControl::adjustLiveWorldViewForward(QmcTime::Packet *packet)
+{
+ console->post("QedGroupControl::adjustLiveWorldViewForward");
+
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+}
+
+void QedGroupControl::adjustArchiveWorldViewForward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("QedGroupControl::adjustArchiveWorldViewForward");
+
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+}
+
+void QedGroupControl::adjustArchiveWorldViewBackward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("QedGroupControl::adjustArchiveWorldViewBackward");
+
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+}
+
+void QedGroupControl::adjustArchiveWorldViewStopped(QmcTime::Packet *packet, bool needFetch)
+{
+ if (needFetch) { // stopped, but VCR reposition event occurred
+ adjustArchiveWorldViewForward(packet, needFetch);
+ return;
+ }
+ my.timeState = StandbyState;
+ packet->state = QmcTime::StoppedState;
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+ updateTimeButton();
+}
+
+bool QedGroupControl::fuzzyTimeMatch(double a, double b, double tolerance)
+{
+ // a matches b if the difference is within 1% of the delta (tolerance)
+ return (a == b ||
+ (b > a && a + tolerance > b) ||
+ (b < a && a - tolerance < b));
+}
+
+//
+// Catch the situation where we get a larger than expected increase
+// in position. This happens when we restart after a stop in live
+// mode (both with and without a change in the delta).
+//
+static bool sideStep(double n, double o, double interval)
+{
+ // tolerance set to 5% of the sample interval:
+ return QedGroupControl::fuzzyTimeMatch(o + interval, n, interval/20.0) == false;
+}
+
+void QedGroupControl::step(QmcTime::Packet *packet)
+{
+ double stepPosition = QedApp::timevalToSeconds(packet->position);
+
+ console->post(QedApp::DebugProtocol,
+ "GroupControl::step: stepping to time %.2f, delta=%.2f, state=%s",
+ stepPosition, my.realDelta, timeState());
+
+ if ((packet->source == QmcTime::ArchiveSource &&
+ ((packet->state == QmcTime::ForwardState &&
+ my.timeState != ForwardState) ||
+ (packet->state == QmcTime::BackwardState &&
+ my.timeState != BackwardState))) ||
+ sideStep(stepPosition, my.realPosition, my.realDelta))
+ return adjustWorldView(packet, false);
+
+ my.pmtimeState = packet->state;
+ my.position = packet->position;
+ my.realPosition = stepPosition;
+
+ adjustStep(packet);
+ fetch();
+
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, isRecording(packet));
+}
+
+void QedGroupControl::VCRMode(QmcTime::Packet *packet, bool dragMode)
+{
+ if (!dragMode)
+ adjustWorldView(packet, true);
+}
+
+void QedGroupControl::setTimezone(QmcTime::Packet *packet, char *tz)
+{
+ console->post(QedApp::DebugProtocol, "GroupControl::setTimezone %s", tz);
+ useTZ(QString(tz));
+ (void)packet;
+}
+
+void QedGroupControl::newButtonState(QmcTime::State s, QmcTime::Mode m, bool record)
+{
+ if (isArchiveSource() == false) {
+ if (s == QmcTime::StoppedState)
+ my.buttonState = record ?
+ QedTimeButton::StoppedRecord : QedTimeButton::StoppedLive;
+ else
+ my.buttonState = record ?
+ QedTimeButton::ForwardRecord : QedTimeButton::ForwardLive;
+ }
+ else if (m == QmcTime::StepMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::StepForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::StepBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (m == QmcTime::FastMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::FastForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::FastBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::ForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::BackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+}
diff --git a/src/libpcp_qed/src/qed_groupcontrol.h b/src/libpcp_qed/src/qed_groupcontrol.h
new file mode 100644
index 0000000..c9825bf
--- /dev/null
+++ b/src/libpcp_qed/src/qed_groupcontrol.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2009, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_GROUPCONTROL_H
+#define QED_GROUPCONTROL_H
+
+#include <QtCore/QList>
+#include <qmc_group.h>
+#include <qmc_time.h>
+#include "qed_timebutton.h"
+
+class QedGroupControl : public QObject, public QmcGroup
+{
+ Q_OBJECT
+
+public:
+ QedGroupControl();
+ void init(struct timeval *, struct timeval *);
+
+ virtual bool isArchiveSource() = 0;
+
+ double timeInterval() const { return my.realDelta; }
+ double timePosition() const { return my.realPosition; }
+
+ virtual void step(QmcTime::Packet *);
+ virtual void VCRMode(QmcTime::Packet *, bool);
+ virtual void setTimezone(QmcTime::Packet *, char *);
+
+ virtual void adjustStep(QmcTime::Packet *) = 0;
+ virtual void updateTimeButton() = 0;
+ virtual void updateTimeAxis(void) = 0;
+
+ virtual void setupWorldView(struct timeval *, struct timeval *,
+ struct timeval *, struct timeval *);
+ static bool fuzzyTimeMatch(double, double, double);
+
+ QedTimeButton::State buttonState() { return my.buttonState; }
+ QmcTime::State pmtimeState();
+ void newButtonState(QmcTime::State, QmcTime::Mode, bool);
+ bool isStateBackward() { return my.timeState == BackwardState; }
+
+protected:
+ typedef enum {
+ StartState,
+ ForwardState,
+ BackwardState,
+ EndLogState,
+ StandbyState,
+ } QedTimeState;
+
+ char *timeState();
+ void setTimeState(QedTimeState state) { my.timeState = state; }
+ virtual void setButtonState(QedTimeButton::State) = 0;
+
+ virtual bool isActive(QmcTime::Packet *) = 0;
+ virtual bool isRecording(QmcTime::Packet *) = 0;
+ virtual void adjustWorldView(QmcTime::Packet *, bool);
+ virtual void adjustLiveWorldViewForward(QmcTime::Packet *);
+ virtual void adjustLiveWorldViewStopped(QmcTime::Packet *);
+ virtual void adjustArchiveWorldViewForward(QmcTime::Packet *, bool);
+ virtual void adjustArchiveWorldViewStopped(QmcTime::Packet *, bool);
+ virtual void adjustArchiveWorldViewBackward(QmcTime::Packet *, bool);
+
+ struct {
+ double realDelta; // current update interval
+ double realPosition; // current time position
+ struct timeval delta;
+ struct timeval position;
+
+ QedTimeButton::State buttonState;
+ QmcTime::Source pmtimeSource; // reliable archive/host test
+ QmcTime::State pmtimeState;
+ QedTimeState timeState;
+ } my;
+};
+
+#endif // QED_GROUPCONTROL_H
diff --git a/src/libpcp_qed/src/qed_label.cpp b/src/libpcp_qed/src/qed_label.cpp
new file mode 100644
index 0000000..874b607
--- /dev/null
+++ b/src/libpcp_qed/src/qed_label.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qed_label.h"
+
+QedLabel::QedLabel(QWidget *parent) : QedGadget(parent)
+{
+}
+
+QedLabel::QedLabel(QWidget *parent, int x, int y, const char *t, const char *font) : QedGadget(parent)
+{
+ (void)x;
+ (void)y;
+ (void)t;
+ (void)font;
+}
+
diff --git a/src/libpcp_qed/src/qed_label.h b/src/libpcp_qed/src/qed_label.h
new file mode 100644
index 0000000..b26b5aa
--- /dev/null
+++ b/src/libpcp_qed/src/qed_label.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_LABEL_H
+#define QED_LABEL_H
+
+#include <QtGui>
+#include "qed_gadget.h"
+
+class QedLabel : public QedGadget
+{
+ Q_OBJECT
+
+public:
+ QedLabel(QWidget *parent);
+ QedLabel(QWidget *parent, int x, int y, const char *t, const char *font);
+
+ void setOrientation(Qt::Orientation o) { my.oriented = o; }
+
+private:
+ struct {
+ QString text;
+ QString font;
+ Qt::Orientation oriented;
+ } my;
+};
+
+#endif // QED_LABEL_H
diff --git a/src/libpcp_qed/src/qed_led.cpp b/src/libpcp_qed/src/qed_led.cpp
new file mode 100644
index 0000000..9beb47a
--- /dev/null
+++ b/src/libpcp_qed/src/qed_led.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QWidget>
+#include "qed_led.h"
+
+QedLED::QedLED(QWidget *parent, QColor color) : QedGadget(parent)
+{
+ const qreal baseBound = 32.0;
+ const int iBaseBound = (int)(baseBound+0.5);
+
+ my.color = color;
+ my.bound = baseBound;
+ setMinimumSize(iBaseBound, iBaseBound);
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+}
+
+QedLED::QedLED(QWidget *parent, int x, int y, int w, int h, QedLegend *color) : QedGadget(parent)
+{
+ // TODO
+ (void)x;
+ (void)y;
+ (void)w;
+ (void)h;
+ (void)color;
+}
+
+void QedLED::resizeEvent(QResizeEvent *event)
+{
+ QSize size = event->size();
+ my.bound = qMin(size.height(), size.width());
+}
+
+void QedRoundLED::paintEvent(QPaintEvent *event)
+{
+ qreal lowerInset, upperInset;
+ (void)event;
+
+ QPainterPath baseCirclePath;
+ QRectF baseBox(0.0, 0.0, my.bound, my.bound);
+ baseCirclePath.arcTo(baseBox, 0.0, 360.0);
+ baseCirclePath.closeSubpath();
+ QLinearGradient baseGradient(0, 0, 0, 1.33846 * my.bound);
+ baseGradient.setColorAt(0.0, Qt::lightGray);
+ baseGradient.setColorAt(1.0, Qt::white);
+
+ QPainterPath seatCirclePath;
+ lowerInset = 0.06666 * my.bound;
+ upperInset = my.bound - (lowerInset * 2.0);
+ QRectF seatBox(lowerInset, lowerInset, upperInset, upperInset);
+ seatCirclePath.arcTo(seatBox, 0.0, 360.0);
+ seatCirclePath.closeSubpath();
+ QLinearGradient seatGradient(0, 0, 0, 0.7692 * my.bound);
+ seatGradient.setColorAt(0.0, Qt::lightGray);
+ seatGradient.setColorAt(1.0, Qt::darkGray);
+
+ QPainterPath mainCirclePath;
+ lowerInset = 0.1 * my.bound;
+ upperInset = my.bound - (lowerInset * 2.0);
+ QRectF mainBox(lowerInset, lowerInset, upperInset, upperInset);
+ mainCirclePath.arcTo(mainBox, 0.0, 360.0);
+ mainCirclePath.closeSubpath();
+ QLinearGradient mainGradient(0, 0, 0, 1.23076 * my.bound);
+ mainGradient.setColorAt(0.0, my.color);
+ mainGradient.setColorAt(1.0, Qt::white);
+
+ QPainterPath reflectCirclePath;
+ QRectF reflectBox(0.18538 * my.bound, 0.12969 * my.bound,
+ 0.61384 * my.bound, 0.48615 * my.bound);
+ reflectCirclePath.arcTo(reflectBox, 0.0, 360.0);
+ reflectCirclePath.closeSubpath();
+ QLinearGradient reflectGradient(0, 0, 0, 1.22953 * my.bound);
+ reflectGradient.setColorAt(0.0, Qt::white);
+ reflectGradient.setColorAt(1.0, my.color);
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(Qt::NoPen);
+
+ painter.setBrush(baseGradient);
+ painter.drawPath(baseCirclePath);
+ painter.setBrush(seatGradient);
+ painter.drawPath(seatCirclePath);
+ painter.setBrush(mainGradient);
+ painter.drawPath(mainCirclePath);
+ painter.setBrush(reflectGradient);
+ painter.drawPath(reflectCirclePath);
+}
+
+void QedSquareLED::paintEvent(QPaintEvent *event)
+{
+ qreal lowerInset, upperInset, radius = my.bound / 16.0;
+ (void)event;
+
+ QPainterPath baseSquarePath;
+ QRectF baseBox(0.0, 0.0, my.bound, my.bound);
+ baseSquarePath.addRoundedRect(baseBox, radius, radius);
+ baseSquarePath.closeSubpath();
+ QLinearGradient baseGradient(0, 0, 0, 1.33846 * my.bound);
+ baseGradient.setColorAt(0.0, Qt::lightGray);
+ baseGradient.setColorAt(1.0, Qt::white);
+
+ QPainterPath seatSquarePath;
+ lowerInset = 0.06666 * my.bound;
+ upperInset = my.bound - (lowerInset * 2.0);
+ QRectF seatBox(lowerInset, lowerInset, upperInset, upperInset);
+ seatSquarePath.addRoundedRect(seatBox, radius, radius);
+ seatSquarePath.closeSubpath();
+ QLinearGradient seatGradient(0, 0, 0, 0.7692 * my.bound);
+ seatGradient.setColorAt(0.0, Qt::lightGray);
+ seatGradient.setColorAt(1.0, Qt::darkGray);
+
+ QPainterPath mainSquarePath;
+ lowerInset = 0.1 * my.bound;
+ upperInset = my.bound - (lowerInset * 2.0);
+ QRectF mainBox(lowerInset, lowerInset, upperInset, upperInset);
+ mainSquarePath.addRoundedRect(mainBox, radius, radius);
+ mainSquarePath.closeSubpath();
+ QLinearGradient mainGradient(0, 0, 0, 1.23076 * my.bound);
+ mainGradient.setColorAt(0.0, my.color);
+ mainGradient.setColorAt(1.0, Qt::white);
+
+ QPainterPath reflectSquarePath;
+ QRectF reflectBox(0.13538 * my.bound, 0.12969 * my.bound,
+ 0.73084 * my.bound, 0.48615 * my.bound);
+ reflectSquarePath.addRoundedRect(reflectBox, radius, radius);
+ reflectSquarePath.closeSubpath();
+ QLinearGradient reflectGradient(0, 0, 0, 1.22953 * my.bound);
+ reflectGradient.setColorAt(0.0, Qt::white);
+ reflectGradient.setColorAt(1.0, my.color);
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(Qt::NoPen);
+
+ painter.setBrush(baseGradient);
+ painter.drawPath(baseSquarePath);
+ painter.setBrush(seatGradient);
+ painter.drawPath(seatSquarePath);
+ painter.setBrush(mainGradient);
+ painter.drawPath(mainSquarePath);
+ painter.setBrush(reflectGradient);
+ painter.drawPath(reflectSquarePath);
+}
diff --git a/src/libpcp_qed/src/qed_led.h b/src/libpcp_qed/src/qed_led.h
new file mode 100644
index 0000000..067f92b
--- /dev/null
+++ b/src/libpcp_qed/src/qed_led.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_LED_H
+#define QED_LED_H
+
+#include <QtGui>
+#include "qed_gadget.h"
+#include "qed_legend.h"
+
+class QedLED : public QedGadget
+{
+ Q_OBJECT
+
+public:
+ QedLED(QWidget *parent, QColor color);
+ QedLED(QWidget *parent, int x, int y, int w, int h, QedLegend *color);
+
+protected:
+ virtual void paintEvent(QPaintEvent *) = 0;
+ virtual void resizeEvent(QResizeEvent *);
+
+ struct {
+ QColor color;
+ qreal bound;
+ } my;
+};
+
+class QedRoundLED : public QedLED
+{
+ Q_OBJECT
+
+public:
+ QedRoundLED(QWidget *parent, QColor color) : QedLED(parent, color) { }
+ QedRoundLED(QWidget *parent, int x, int y, int w, int h, QedLegend *l)
+ : QedLED(parent, x, y, w, h, l) { }
+
+private:
+ void paintEvent(QPaintEvent *);
+};
+
+class QedSquareLED : public QedLED
+{
+ Q_OBJECT
+
+public:
+ QedSquareLED(QWidget *parent, QColor color) : QedLED(parent, color) { }
+ QedSquareLED(QWidget *parent, int x, int y, int w, int h, QedLegend *l)
+ : QedLED(parent, x, y, w, h, l) { }
+
+private:
+ void paintEvent(QPaintEvent *);
+};
+
+#endif // QED_LED_H
diff --git a/src/libpcp_qed/src/qed_legend.cpp b/src/libpcp_qed/src/qed_legend.cpp
new file mode 100644
index 0000000..a885b73
--- /dev/null
+++ b/src/libpcp_qed/src/qed_legend.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "qed_legend.h"
+
+QedLegend::QedLegend()
+{
+ // default is white when no _default appears in the legend ...
+ my.defaultColor = Qt::white;
+}
+
+QedLegend::QedLegend(const char *name) : QString(name)
+{
+ QedLegend();
+}
+
+const char *
+QedLegend::identity(void) const
+{
+ return this->toAscii();
+}
+
+void
+QedLegend::setDefaultColor(const char *color)
+{
+ my.defaultColor = QColor(color);
+}
+
+void
+QedLegend::setThresholds(QStringList &tl)
+{
+ // TODO: iterate over tl and build my.thresholds
+ (void)tl;
+}
+
+void
+QedLegend::addThreshold(double value, const char *color)
+{
+ // TODO: append to my.thresholds
+ (void)value;
+ (void)color;
+}
diff --git a/src/libpcp_qed/src/qed_legend.h b/src/libpcp_qed/src/qed_legend.h
new file mode 100644
index 0000000..c70e872
--- /dev/null
+++ b/src/libpcp_qed/src/qed_legend.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_LEGEND_H
+#define QED_LEGEND_H
+
+#include <QtGui>
+
+class QedThreshold
+{
+public:
+ QedThreshold(double value, const QColor &color)
+ { my.value = value; my.color = color; }
+
+ double value() const { return my.value; }
+ QColor color() const { return my.color; }
+
+private:
+ struct {
+ double value; // maximum value for range
+ QColor color;
+ } my;
+};
+
+// Simple legend - a named list of value ranges,
+// with colors associated with each value range.
+//
+class QedLegend : public QString
+{
+public:
+ QedLegend();
+ QedLegend(const char *legend);
+ const char *identity() const;
+ void setDefaultColor(const char *color);
+ void addThreshold(double value, const char *color);
+ void setThresholds(QStringList &tl);
+
+private:
+ struct {
+ QColor defaultColor;
+ QList<QedThreshold> thresholds;
+ } my;
+};
+
+#endif // QED_LEGEND_H
diff --git a/src/libpcp_qed/src/qed_line.cpp b/src/libpcp_qed/src/qed_line.cpp
new file mode 100644
index 0000000..51e8573
--- /dev/null
+++ b/src/libpcp_qed/src/qed_line.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QWidget>
+#include "qed_line.h"
+
+QedLine::QedLine(QWidget *parent, qreal length) : QedGadget(parent)
+{
+ setup();
+ my.box = QRect(0, 0, length, 1);
+ my.length = length * 1.0;
+}
+
+QedLine::QedLine(QWidget *parent, int x, int y, int w, int h) : QedGadget(parent)
+{
+ setup();
+ my.box = QRect(x, y, w, h);
+ my.length = QLineF(QLine(x, y, w, h)).length();
+}
+
+QedLine::QedLine(QWidget *parent, int length, Qt::Orientation oriented) : QedGadget(parent)
+{
+ setup();
+ my.oriented = oriented;
+ my.box = QRect(0, 0, length, 1);
+ my.length = 1.0 * length;
+}
+
+void
+QedLine::setup(void)
+{
+ const qreal baseBound = 32.0;
+ const int iBaseBound = (int)(baseBound+0.5);
+ my.oriented = Qt::Horizontal;
+ my.bound = baseBound;
+ setMinimumSize(iBaseBound, iBaseBound);
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+}
+
+void
+QedLine::resizeEvent(QResizeEvent *event)
+{
+ QSize size = event->size();
+ my.bound = qMin(size.height(), size.width());
+}
+
+void
+QedLine::paintEvent(QPaintEvent *event)
+{
+ qreal lowerInset, upperInset, radius = my.bound / 16.0;
+ (void)event;
+
+ QPainterPath mainSquarePath;
+ lowerInset = 0.3 * my.bound;
+ upperInset = (my.bound * my.length) - (lowerInset * 2.0);
+ QRectF mainBox(lowerInset, lowerInset,
+ upperInset, my.bound - (lowerInset * 2.0));
+ mainSquarePath.addRoundedRect(mainBox, radius, radius);
+ mainSquarePath.closeSubpath();
+ QLinearGradient mainGradient(0, 0, 0, 1.0 * my.bound);
+ mainGradient.setColorAt(0.0, Qt::lightGray);
+ mainGradient.setColorAt(1.0, Qt::darkGray);
+
+ QPainterPath reflectSquarePath;
+ QRectF reflectBox(0.13538 * my.bound, 0.12969 * my.bound,
+ 0.73084 * my.bound, 0.48615 * my.bound);
+ reflectSquarePath.addRoundedRect(reflectBox, radius, radius);
+ reflectSquarePath.closeSubpath();
+ QLinearGradient reflectGradient(0, 0, 0, 1.22953 * my.bound);
+ reflectGradient.setColorAt(0.0, Qt::white);
+ reflectGradient.setColorAt(1.0, Qt::gray);
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(Qt::NoPen);
+
+ painter.setBrush(mainGradient);
+ painter.drawPath(mainSquarePath);
+ painter.setBrush(reflectGradient);
+ painter.drawPath(reflectSquarePath);
+}
diff --git a/src/libpcp_qed/src/qed_line.h b/src/libpcp_qed/src/qed_line.h
new file mode 100644
index 0000000..3444c9e
--- /dev/null
+++ b/src/libpcp_qed/src/qed_line.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_LINE_H
+#define QED_LINE_H
+
+#include <QtGui>
+#include "qed_gadget.h"
+
+class QedLine : public QedGadget
+{
+ Q_OBJECT
+
+public:
+ QedLine(QWidget *parent, qreal length);
+ QedLine(QWidget *parent, int length, Qt::Orientation o);
+ QedLine(QWidget *parent, int x, int y, int w, int h);
+
+ void setOrientation(Qt::Orientation o) { my.oriented = o; }
+
+private:
+ void setup(void);
+ virtual void paintEvent(QPaintEvent *);
+ virtual void resizeEvent(QResizeEvent *);
+
+ struct {
+ QRect box;
+ qreal bound;
+ qreal length;
+ enum Qt::Orientation oriented;
+ } my;
+};
+
+#endif // QED_LINE_H
diff --git a/src/libpcp_qed/src/qed_recorddialog.cpp b/src/libpcp_qed/src/qed_recorddialog.cpp
new file mode 100644
index 0000000..24f977b
--- /dev/null
+++ b/src/libpcp_qed/src/qed_recorddialog.cpp
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "qed_recorddialog.h"
+#include <QtCore/QDateTime>
+#include <QtCore/QTextStream>
+#include <QtGui/QMessageBox>
+#include <QtGui/QDoubleValidator>
+
+#include "qed_app.h"
+#include "qed_console.h"
+#include "qed_viewcontrol.h"
+#include "qed_fileiconprovider.h"
+
+QedRecordDialog::QedRecordDialog() : QDialog()
+{
+ setupUi(this);
+ deltaLineEdit->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 3, deltaLineEdit));
+}
+
+void QedRecordDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void QedRecordDialog::init(QedViewControl *view, double userDelta)
+{
+ QString pmlogger = "~/.pcp/pmlogger/";
+ QString viewName, folioName, archiveName;
+
+ viewName = folioName = archiveName = pmlogger;
+
+ viewName.append(tr("[date].view"));
+ viewLineEdit->setText(viewName);
+ folioName.append(tr("[date].folio"));
+ folioLineEdit->setText(folioName);
+ archiveName.append(tr("[host]/[date]"));
+ archiveLineEdit->setText(archiveName);
+
+ my.view = view;
+ my.userDelta = userDelta;
+ my.units = QmcTime::Seconds;
+ deltaLineEdit->setText(QmcTime::deltaString(userDelta, my.units));
+
+ selectedRadioButton->setChecked(false);
+ allGadgetsRadioButton->setChecked(true);
+}
+
+void QedRecordDialog::selectedRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(true);
+ allGadgetsRadioButton->setChecked(false);
+}
+
+void QedRecordDialog::allGadgetsRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(false);
+ allGadgetsRadioButton->setChecked(true);
+}
+
+void QedRecordDialog::deltaUnitsComboBox_activated(int value)
+{
+ my.userDelta = QmcTime::deltaValue(deltaLineEdit->text(), my.units);
+ my.units = (QmcTime::DeltaUnits)value;
+ deltaLineEdit->setText(QmcTime::deltaString(my.userDelta, my.units));
+}
+
+void QedRecordDialog::viewPushButton_clicked()
+{
+ QedRecordFileDialog view(this);
+
+ view.setDirectory(QDir::homePath().append("/.pcp/pmlogger/"));
+ if (view.exec() == QDialog::Accepted)
+ viewLineEdit->setText(view.selectedFiles().at(0));
+}
+
+void QedRecordDialog::folioPushButton_clicked()
+{
+ QedRecordFileDialog folio(this);
+
+ folio.setDirectory(QDir::homePath().append("/.pcp/pmlogger/"));
+ if (folio.exec() == QDialog::Accepted)
+ folioLineEdit->setText(folio.selectedFiles().at(0));
+}
+
+void QedRecordDialog::archivePushButton_clicked()
+{
+ QedRecordFileDialog archive(this);
+
+ archive.setDirectory(QDir::homePath().append("/.pcp/pmlogger/"));
+ if (archive.exec() == QDialog::Accepted)
+ archiveLineEdit->setText(archive.selectedFiles().at(0));
+}
+
+bool QedRecordDialog::saveFolio(QString folioName, QString viewName)
+{
+ QFile folio(folioName);
+
+ if (!folio.open(QIODevice::WriteOnly)) {
+ QString msg = tr("Cannot open file: ");
+ msg.append(folioName);
+ msg.append("\n");
+ msg.append(folio.errorString());
+ QMessageBox::warning(this, pmProgname, msg);
+ return false;
+ }
+
+ QTextStream stream(&folio);
+ QString datetime;
+
+ datetime = QDateTime::currentDateTime().toString("ddd MMM d hh:mm:ss yyyy");
+ stream << "PCPFolio\n";
+ stream << "Version: 1\n";
+ stream << "# use pmafm(1) to process this PCP archive folio\n" << "#\n";
+ stream << "Created: on " << QmcSource::localHost;
+ stream << " at " << datetime << "\n";
+ stream << "Creator: pmchart " << viewName << "\n";
+ stream << "#\t\tHost\t\tBasename\n";
+
+ for (int i = 0; i < my.hosts.size(); i++) {
+ QString host = my.hosts.at(i);
+ QString archive = my.archives.at(i);
+ QFileInfo logFile(archive);
+ QDir logDir = logFile.dir();
+ logDir.mkpath(logDir.absolutePath());
+ stream << "Archive:\t" << my.hosts.at(i) << "\t\t" << archive << "\n";
+ }
+ return true;
+}
+
+bool QedRecordDialog::saveConfig(QString configfile, QString configdata)
+{
+ QFile config(configfile);
+
+ if (!config.open(QIODevice::WriteOnly)) {
+ QString msg = tr("Cannot open file: ");
+ msg.append(configfile);
+ msg.append("\n");
+ msg.append(config.errorString());
+ QMessageBox::warning(this, pmProgname, msg);
+ return false;
+ }
+
+ QTextStream stream(&config);
+ stream << configdata;
+ return true;
+}
+
+PmLogger::PmLogger(QObject *parent) : QProcess(parent)
+{
+ connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
+ this, SLOT(finished(int, QProcess::ExitStatus)));
+}
+
+void PmLogger::init(QedViewControl *view, QString host, QString logfile)
+{
+ my.view = view;
+ my.host = host;
+ my.logfile = logfile;
+ my.terminating = false;
+}
+
+void PmLogger::terminate()
+{
+ my.terminating = true;
+}
+
+void PmLogger::finished(int, QProcess::ExitStatus)
+{
+ QString msg;
+
+ if (my.terminating == false) {
+ my.terminating = true;
+ if (my.view->stopRecording(msg) == 0) {
+ msg = "Recording process (pmlogger) exited unexpectedly\n";
+ msg.append("for host ");
+ msg.append(my.host);
+ msg.append(".\n\n");
+ msg.append("Additional diagnostics may be available in the log:\n");
+ msg.append(my.logfile);
+ }
+ QMessageBox::warning(NULL, pmProgname, msg);
+ }
+}
+
+void QedRecordDialog::buttonOk_clicked()
+{
+ if (deltaLineEdit->isModified()) {
+ // convert to seconds, make sure its still in range 0.001-INT_MAX
+ double input = QmcTime::deltaValue(deltaLineEdit->text(), my.units);
+ if (input < 0.001 || input > INT_MAX) {
+ QString msg = tr("Record Sampling Interval is invalid.\n");
+ msg.append(deltaLineEdit->text());
+ msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+ }
+
+ QString today = QDateTime::currentDateTime().toString("yyyyMMdd.hh.mm.ss");
+
+ QString viewName = viewLineEdit->text().trimmed();
+ viewName.replace(QRegExp("^~"), QDir::homePath());
+ viewName.replace(QRegExp("\\[date\\]"), today);
+ viewName.replace(QRegExp("\\[host\\]"), QmcSource::localHost);
+ QFileInfo viewFile(viewName);
+ QDir viewDir = viewFile.dir();
+ if (viewDir.mkpath(viewDir.absolutePath()) == false) {
+ QString msg = tr("Failed to create path for view:\n");
+ msg.append(viewName);
+ msg.append("\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+
+ QString folioName = folioLineEdit->text().trimmed();
+ folioName.replace(QRegExp("^~"), QDir::homePath());
+ folioName.replace(QRegExp("\\[date\\]"), today);
+ folioName.replace(QRegExp("\\[host\\]"), QmcSource::localHost);
+ QFileInfo folioFile(folioName);
+ QDir folioDir = folioFile.dir();
+ if (folioDir.mkpath(folioDir.absolutePath()) == false) {
+ QString msg = tr("Failed to create path for folio:\n");
+ msg.append(folioName);
+ msg.append("\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+
+ console->post("RecordDialog verifying paths view=%s folio=%s",
+ (const char *)folioName.toAscii(), (const char *)viewName.toAscii());
+
+ my.viewName = viewName;
+ my.folioName = folioName;
+ my.delta.setNum(QmcTime::deltaValue(deltaLineEdit->text(), my.units), 'f');
+
+ my.hosts = my.view->hostList(selectedRadioButton->isChecked());
+ for (int h = 0; h < my.hosts.count(); h++) {
+ QString archive = archiveLineEdit->text().trimmed();
+ archive.replace(QRegExp("^~"), QDir::homePath());
+ archive.replace(QRegExp("\\[host\\]"), my.hosts.at(h));
+ archive.replace(QRegExp("\\[date\\]"), today);
+ my.archives.append(archive);
+ }
+
+ if (my.view->saveConfig(viewName, false, false, false, true) == false)
+ return;
+ if (saveFolio(folioName, viewName) == false)
+ return;
+ QDialog::accept();
+}
+
+//
+// write pmlogger, pmchart and pmafm configs, then start pmloggers.
+//
+void QedRecordDialog::startLoggers()
+{
+ QString pmlogger = pmGetConfig("PCP_BINADM_DIR");
+ pmlogger.append("/pmlogger");
+
+ QString regex = "^";
+ regex.append(QDir::homePath());
+ my.folioName.replace(QRegExp(regex), "~");
+
+ my.view->addFolio(my.folioName, my.viewName);
+
+ for (int i = 0; i < my.hosts.size(); i++) {
+ PmLogger *process = new PmLogger(this);
+ QString archive = my.archives.at(i);
+ QString host = my.hosts.at(i);
+ QString logfile, configfile;
+
+ configfile = archive;
+ configfile.append(".config");
+ logfile = archive;
+ logfile.append(".log");
+
+ process->init(my.view, host, logfile);
+
+ QStringList arguments;
+ arguments << "-r" << "-c" << configfile << "-h" << host << "-x0";
+ arguments << "-l" << logfile << "-t" << my.delta << archive;
+
+ QString data("#pmlogger Version 1\n\n"); // header for file(1)
+ data.append(my.view->pmloggerSyntax(selectedRadioButton->isChecked()));
+ saveConfig(configfile, data);
+
+ process->start(pmlogger, arguments);
+ my.view->addLogger(process, archive);
+
+ // Send initial control messages to pmlogger
+ QStringList control;
+ control << "V0\n";
+ control << "F" << my.folioName << "\n";
+ control << "Ppmchart\n" << "R\n";
+ for (int i = 0; i < control.size(); i++)
+ process->write(control.at(i).toAscii());
+ }
+}
+
+// RecordFileDialog is the one which is displayed when you click
+// on one of the file selection push buttons (view/logfile/folio).
+
+QedRecordFileDialog::QedRecordFileDialog(QWidget *parent) : QFileDialog(parent)
+{
+ setAcceptMode(QFileDialog::AcceptSave);
+ setFileMode(QFileDialog::AnyFile);
+ setIconProvider(fileIconProvider);
+ setConfirmOverwrite(true);
+}
+
+void QedRecordFileDialog::setFileName(QString path)
+{
+ selectFile(path);
+}
diff --git a/src/libpcp_qed/src/qed_recorddialog.h b/src/libpcp_qed/src/qed_recorddialog.h
new file mode 100644
index 0000000..b76db6b
--- /dev/null
+++ b/src/libpcp_qed/src/qed_recorddialog.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_RECORDDIALOG_H
+#define QED_RECORDDIALOG_H
+
+#include "ui_qed_recorddialog.h"
+#include <QtCore/QProcess>
+#include <QtGui/QFileDialog>
+#include "qmc_time.h"
+
+class QedViewControl;
+
+class QedRecordDialog : public QDialog, public Ui::QedRecordDialog
+{
+ Q_OBJECT
+
+public:
+ QedRecordDialog();
+
+ virtual void init(QedViewControl *, double);
+ virtual bool saveFolio(QString, QString);
+ virtual bool saveConfig(QString, QString);
+ virtual void startLoggers();
+
+public slots:
+ virtual void deltaUnitsComboBox_activated(int);
+ virtual void selectedRadioButton_clicked();
+ virtual void allGadgetsRadioButton_clicked();
+ virtual void viewPushButton_clicked();
+ virtual void folioPushButton_clicked();
+ virtual void archivePushButton_clicked();
+ virtual void buttonOk_clicked();
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ struct {
+ QedViewControl *view;
+ QString delta;
+ double userDelta;
+ QmcTime::DeltaUnits units;
+
+ QString viewName;
+ QString folioName;
+ QStringList hosts;
+ QStringList archives;
+ } my;
+};
+
+class PmLogger : public QProcess
+{
+ Q_OBJECT
+
+public:
+ PmLogger(QObject *);
+ void init(QedViewControl *view, QString host, QString log);
+ QString host() { return my.host; }
+
+public slots:
+ void terminate();
+ void finished(int, QProcess::ExitStatus);
+
+private:
+ struct {
+ QedViewControl *view;
+ QString host;
+ QString logfile;
+ bool terminating;
+ } my;
+};
+
+class QedRecordFileDialog : public QFileDialog, public Ui::QedRecordDialog
+{
+ Q_OBJECT
+
+public:
+ QedRecordFileDialog(QWidget* parent);
+ void setFileName(QString);
+};
+
+#endif // QED_RECORDDIALOG_H
diff --git a/src/libpcp_qed/src/qed_recorddialog.ui b/src/libpcp_qed/src/qed_recorddialog.ui
new file mode 100644
index 0000000..627381e
--- /dev/null
+++ b/src/libpcp_qed/src/qed_recorddialog.ui
@@ -0,0 +1,540 @@
+<ui version="4.0" >
+ <class>QedRecordDialog</class>
+ <widget class="QDialog" name="QedRecordDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>410</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>410</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>16777215</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>Record</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="libapp.qrc" >:/images/archive.png</iconset>
+ </property>
+ <property name="toolTip" >
+ <string/>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="allGadgetsRadioButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>All Charts in Tab</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selectedRadioButton" >
+ <property name="text" >
+ <string>Selected Chart Only</string>
+ </property>
+ <property name="checked" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="deltaLabel" >
+ <property name="text" >
+ <string>Logging Interval:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="deltaLineEdit" >
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="deltaUnitsComboBox" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex" >
+ <number>1</number>
+ </property>
+ <property name="maxCount" >
+ <number>6</number>
+ </property>
+ <property name="insertPolicy" >
+ <enum>QComboBox::NoInsert</enum>
+ </property>
+ <property name="duplicatesEnabled" >
+ <bool>false</bool>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="folioTextLabel" >
+ <property name="text" >
+ <string>Folio</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="folioLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="folioPushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="libapp.qrc" >:/images/folio.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="viewTextLabel" >
+ <property name="text" >
+ <string>View</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="viewLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewPushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="libapp.qrc" >:/images/view.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="archiveTextLabel" >
+ <property name="text" >
+ <string>Archive</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="archiveLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="archivePushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="libapp.qrc" >:/images/archive.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="libapp.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>buttonOk_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>selectedRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>selectedRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>allGadgetsRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>allGadgetsRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>deltaUnitsComboBox</sender>
+ <signal>activated(int)</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>deltaUnitsComboBox_activated(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>viewPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>viewPushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>folioPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>folioPushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>archivePushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>QedRecordDialog</receiver>
+ <slot>archivePushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/libpcp_qed/src/qed_statusbar.cpp b/src/libpcp_qed/src/qed_statusbar.cpp
new file mode 100644
index 0000000..2d669cc
--- /dev/null
+++ b/src/libpcp_qed/src/qed_statusbar.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui>
+#include <QHBoxLayout>
+#include "qed_statusbar.h"
+#include "qed_app.h"
+
+QedStatusBar::QedStatusBar()
+{
+ QFont *font = QedApp::globalFont();
+
+ setFont(*font);
+ setFixedHeight(buttonSize());
+ setSizeGripEnabled(false);
+
+ my.timeButton = new QedTimeButton(this);
+ my.timeButton->setFixedSize(QSize(buttonSize(), buttonSize()));
+ my.timeButton->setWhatsThis(QApplication::translate("PmChart",
+ "VCR state button, also used to display the time control window.",
+ 0, QApplication::UnicodeUTF8));
+ my.timeFrame = new QToolButton(this);
+ my.timeFrame->setMinimumSize(QSize(buttonSize(), buttonSize()));
+ my.timeFrame->setSizePolicy(
+ QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+ my.timeFrame->setWhatsThis(QApplication::translate("PmChart",
+ "Unified time axis, displaying the current time position at the "
+ "rightmost point, and either status information or the timeframe "
+ "covering all Visible Points to the left",
+ 0, QApplication::UnicodeUTF8));
+
+ delete layout();
+ QHBoxLayout *box = new QHBoxLayout;
+ box->setMargin(0);
+ box->setSpacing(1);
+ box->addWidget(my.timeButton);
+ box->addWidget(my.timeFrame);
+ setLayout(box);
+
+ my.gadgetLabel = new QLabel(my.timeFrame);
+ my.gadgetLabel->setFont(*font);
+ my.gadgetLabel->hide(); // shown with gadget Views
+
+ my.dateLabel = new QLabel(my.timeFrame);
+ my.dateLabel->setIndent(8);
+ my.dateLabel->setFont(*font);
+ my.dateLabel->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+
+ my.labelSpacer = new QSpacerItem(10, 0,
+ QSizePolicy::Fixed, QSizePolicy::Minimum);
+ my.rightSpacer = new QSpacerItem(0, 0,
+ QSizePolicy::Fixed, QSizePolicy::Minimum);
+
+ my.valueLabel = new QLabel(my.timeFrame);
+ my.valueLabel->setIndent(8);
+ my.valueLabel->setFont(*font);
+ my.valueLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+
+ my.grid = new QGridLayout; // Grid of [5 x 3] cells
+ my.grid->setMargin(0);
+ my.grid->setSpacing(0);
+ my.grid->addWidget(my.gadgetLabel, 0, 0, 1, 3);
+ my.grid->addWidget(my.dateLabel, 2, 2, 1, 1); // bottom row, last two cols
+ my.grid->addItem(my.labelSpacer, 2, 1, 1, 1); // bottom row, second column
+ my.grid->addWidget(my.valueLabel, 2, 0, 1, 1); // bottom row, first column.
+ my.grid->addItem(my.rightSpacer, 0, 4, 2, 1); // all rows, in final column
+ my.timeFrame->setLayout(my.grid);
+}
+
+void QedStatusBar::init()
+{
+}
+
+bool QedStatusBar::event(QEvent *e)
+{
+ if (e->type() == QEvent::Show)
+ my.grid->update();
+ return QStatusBar::event(e);
+}
+
+void QedStatusBar::resizeEvent(QResizeEvent *e)
+{
+ my.timeFrame->resize(e->size().width()-1 - buttonSize(), buttonSize());
+}
+
+void QedStatusBar::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ QStyleOption opt(0);
+
+ opt.rect.setRect(buttonSize()+2, 0, width()-buttonSize()-2, buttonSize());
+ opt.palette = palette();
+ opt.state = QStyle::State_None;
+ style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, my.timeFrame);
+}
diff --git a/src/libpcp_qed/src/qed_statusbar.h b/src/libpcp_qed/src/qed_statusbar.h
new file mode 100644
index 0000000..6ec070f
--- /dev/null
+++ b/src/libpcp_qed/src/qed_statusbar.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_STATUSBAR_H
+#define QED_STATUSBAR_H
+
+#include <QtGui/QLabel>
+#include <QtGui/QStatusBar>
+#include <QtGui/QGridLayout>
+#include "qed_timebutton.h"
+
+class QedStatusBar : public QStatusBar
+{
+ Q_OBJECT
+
+public:
+ QedStatusBar();
+ virtual ~QedStatusBar() { }
+ virtual void init();
+
+ static int buttonSize() { return 56; } // pixels
+
+ QLabel *dateLabel() { return my.dateLabel; }
+ QToolButton *timeFrame() { return my.timeFrame; }
+ QedTimeButton *timeButton() { return my.timeButton; }
+
+ QString dateText() { return my.dateLabel->text(); }
+ void setDateText(QString &s) { my.dateLabel->setText(s); }
+ void setValueText(QString &s) { my.valueLabel->setText(s); }
+ void clearValueText() { my.valueLabel->clear(); }
+
+ void setTimeAxisRightAlignment(int w);
+
+protected:
+ bool event(QEvent *);
+ void paintEvent(QPaintEvent *);
+ void resizeEvent(QResizeEvent *);
+
+ struct {
+ QGridLayout *grid;
+ QSpacerItem *labelSpacer; // spacer between date/value labels
+ QSpacerItem *rightSpacer; // spacer at right edge for toolbar
+ QToolButton *timeFrame;
+ QedTimeButton *timeButton;
+ QLabel *gadgetLabel;
+ QLabel *valueLabel;
+ QLabel *dateLabel;
+ } my;
+};
+
+#endif // QED_STATUSBAR_H
diff --git a/src/libpcp_qed/src/qed_timebutton.cpp b/src/libpcp_qed/src/qed_timebutton.cpp
new file mode 100644
index 0000000..0d50af6
--- /dev/null
+++ b/src/libpcp_qed/src/qed_timebutton.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "qed_timebutton.h"
+#include "qed_console.h"
+
+QedTimeButton::QedTimeButton(QWidget *parent) : QToolButton(parent)
+{
+ my.state = Timeless;
+ setIconSize(QSize(52, 52));
+ setFocusPolicy(Qt::NoFocus);
+ my.forwardLiveIcon = QIcon(":/images/play_live.png");
+ my.stoppedLiveIcon = QIcon(":/images/stop_live.png");
+ my.forwardRecordIcon = QIcon(":/images/play_record.png");
+ my.stoppedRecordIcon = QIcon(":/images/stop_record.png");
+ my.forwardArchiveIcon = QIcon(":/images/play_archive.png");
+ my.stoppedArchiveIcon = QIcon(":/images/stop_archive.png");
+ my.backwardArchiveIcon = QIcon(":/images/back_archive.png");
+ my.stepForwardArchiveIcon = QIcon(":/images/stepfwd_archive.png");
+ my.stepBackwardArchiveIcon = QIcon(":/images/stepback_archive.png");
+ my.fastForwardArchiveIcon = QIcon(":/images/fastfwd_archive.png");
+ my.fastBackwardArchiveIcon = QIcon(":/images/fastback_archive.png");
+ console->post(QedApp::DebugUi, "Time button resources loaded");
+}
+
+void QedTimeButton::setButtonState(QedTimeButton::State state)
+{
+ if (my.state == state)
+ return;
+ switch (state) {
+ case QedTimeButton::ForwardLive:
+ setIcon(my.forwardLiveIcon);
+ break;
+ case QedTimeButton::StoppedLive:
+ setIcon(my.stoppedLiveIcon);
+ break;
+ case QedTimeButton::ForwardRecord:
+ setIcon(my.forwardRecordIcon);
+ break;
+ case QedTimeButton::StoppedRecord:
+ setIcon(my.stoppedRecordIcon);
+ break;
+ case QedTimeButton::ForwardArchive:
+ setIcon(my.forwardArchiveIcon);
+ break;
+ case QedTimeButton::StoppedArchive:
+ setIcon(my.stoppedArchiveIcon);
+ break;
+ case QedTimeButton::BackwardArchive:
+ setIcon(my.backwardArchiveIcon);
+ break;
+ case QedTimeButton::StepForwardArchive:
+ setIcon(my.stepForwardArchiveIcon);
+ break;
+ case QedTimeButton::StepBackwardArchive:
+ setIcon(my.stepBackwardArchiveIcon);
+ break;
+ case QedTimeButton::FastForwardArchive:
+ setIcon(my.fastForwardArchiveIcon);
+ break;
+ case QedTimeButton::FastBackwardArchive:
+ setIcon(my.fastBackwardArchiveIcon);
+ break;
+ default:
+ abort();
+ }
+ my.state = state;
+}
diff --git a/src/libpcp_qed/src/qed_timebutton.h b/src/libpcp_qed/src/qed_timebutton.h
new file mode 100644
index 0000000..8b37f8d
--- /dev/null
+++ b/src/libpcp_qed/src/qed_timebutton.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_TIMEBUTTON_H
+#define QED_TIMEBUTTON_H
+
+#include <QtGui/QIcon>
+#include <QtGui/QToolButton>
+
+class QedTimeButton : public QToolButton
+{
+ Q_OBJECT
+
+public:
+ typedef enum {
+ Timeless = 1,
+ ForwardLive = 2,
+ StoppedLive = 3,
+ ForwardRecord = 4,
+ StoppedRecord = 5,
+ ForwardArchive = 6,
+ StoppedArchive = 7,
+ BackwardArchive = 8,
+ StepForwardArchive = 9,
+ StepBackwardArchive = 10,
+ FastForwardArchive = 11,
+ FastBackwardArchive = 12,
+ } State;
+
+ QedTimeButton(QWidget *);
+ void setButtonState(State state);
+
+private:
+ struct {
+ State state;
+ QIcon forwardLiveIcon;
+ QIcon stoppedLiveIcon;
+ QIcon forwardRecordIcon;
+ QIcon stoppedRecordIcon;
+ QIcon forwardArchiveIcon;
+ QIcon stoppedArchiveIcon;
+ QIcon backwardArchiveIcon;
+ QIcon stepForwardArchiveIcon;
+ QIcon stepBackwardArchiveIcon;
+ QIcon fastForwardArchiveIcon;
+ QIcon fastBackwardArchiveIcon;
+ } my;
+};
+
+#endif // QED_TIMEBUTTON_H
diff --git a/src/libpcp_qed/src/qed_timecontrol.cpp b/src/libpcp_qed/src/qed_timecontrol.cpp
new file mode 100644
index 0000000..a3de69e
--- /dev/null
+++ b/src/libpcp_qed/src/qed_timecontrol.cpp
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui/QMessageBox>
+#include <QtGui/QApplication>
+#include <QtNetwork/QHostAddress>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+#include "qed_timecontrol.h"
+#include "qed_console.h"
+#include "qed_app.h"
+
+QedTimeControl::QedTimeControl() : QProcess(NULL)
+{
+ my.tcpPort = -1;
+ my.tzLength = 0;
+ my.tzData = NULL;
+ my.bufferLength = sizeof(QmcTime::Packet);
+
+ console->post("TimeControl::TimeControl: created");
+ my.buffer = (char *)malloc(my.bufferLength);
+ my.livePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ my.archivePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ if (!my.buffer || !my.livePacket || !my.archivePacket)
+ QedApp::nomem();
+ my.livePacket->magic = QmcTime::Magic;
+ my.livePacket->source = QmcTime::HostSource;
+ my.liveState = QedTimeControl::Disconnected;
+ my.liveSocket = new QTcpSocket(this);
+ connect(my.liveSocket, SIGNAL(connected()),
+ SLOT(liveSocketConnected()));
+ connect(my.liveSocket, SIGNAL(disconnected()),
+ SLOT(liveCloseConnection()));
+ connect(my.liveSocket, SIGNAL(readyRead()),
+ SLOT(liveProtocolMessage()));
+
+ my.archivePacket->magic = QmcTime::Magic;
+ my.archivePacket->source = QmcTime::ArchiveSource;
+ my.archiveState = QedTimeControl::Disconnected;
+ my.archiveSocket = new QTcpSocket(this);
+ connect(my.archiveSocket, SIGNAL(connected()),
+ SLOT(archiveSocketConnected()));
+ connect(my.archiveSocket, SIGNAL(disconnected()),
+ SLOT(archiveCloseConnection()));
+ connect(my.archiveSocket, SIGNAL(readyRead()),
+ SLOT(archiveProtocolMessage()));
+}
+
+void QedTimeControl::quit()
+{
+ disconnect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ if (my.liveSocket) {
+ my.liveSocket->close();
+ my.liveSocket = NULL;
+ }
+ if (my.archiveSocket) {
+ my.archiveSocket->close();
+ my.archiveSocket = NULL;
+ }
+ terminate();
+}
+
+void QedTimeControl::init(int port, bool live,
+ struct timeval *interval, struct timeval *position,
+ struct timeval *starttime, struct timeval *endtime,
+ QString tzstring, QString tzlabel)
+{
+ struct timeval now;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+
+ my.tzLength = tzlen+1 + lablen+1;
+ my.tzData = (char *)realloc(my.tzData, my.tzLength);
+ if (!my.tzData)
+ QedApp::nomem();
+
+ my.livePacket->length = my.archivePacket->length =
+ sizeof(QmcTime::Packet) + my.tzLength;
+ my.livePacket->command = my.archivePacket->command = QmcTime::Set;
+ my.livePacket->delta = my.archivePacket->delta = *interval;
+ if (live) {
+ my.livePacket->position = *position;
+ my.livePacket->start = *starttime;
+ my.livePacket->end = *endtime;
+ memset(&my.archivePacket->position, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->start, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->end, 0, sizeof(struct timeval));
+ } else {
+ gettimeofday(&now, NULL);
+ my.archivePacket->position = *position;
+ my.archivePacket->start = *starttime;
+ my.archivePacket->end = *endtime;
+ my.livePacket->position = now;
+ my.livePacket->start = now;
+ my.livePacket->end = now;
+ }
+ strncpy(my.tzData, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy(my.tzData + tzlen+1, (const char *)tzlabel.toAscii(), lablen+1);
+
+ if (port < 0) {
+ startTimeServer();
+ } else {
+ my.tcpPort = port;
+ liveConnect();
+ archiveConnect();
+ }
+}
+
+void QedTimeControl::addArchive(
+ struct timeval starttime, struct timeval endtime,
+ QString tzstring, QString tzlabel, bool atEnd)
+{
+ QmcTime::Packet *message;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+ int sz = sizeof(QmcTime::Packet) + tzlen + 1 + lablen + 1;
+
+ if (my.archivePacket->position.tv_sec == 0) { // first archive
+ my.archivePacket->position = atEnd ? endtime : starttime;
+ my.archivePacket->start = starttime;
+ my.archivePacket->end = endtime;
+ }
+
+ if ((message = (QmcTime::Packet *)malloc(sz)) == NULL)
+ QedApp::nomem();
+ *message = *my.archivePacket;
+ message->command = QmcTime::Bounds;
+ message->length = sz;
+ message->start = starttime;
+ message->end = endtime;
+ strncpy((char *)message->data, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy((char *)message->data + tzlen+1,
+ (const char *)tzlabel.toAscii(), lablen+1);
+ if (my.archiveSocket->write((const char *)message, sz) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot update pmtime boundaries."),
+ QApplication::tr("Quit") );
+ free(message);
+}
+
+void QedTimeControl::liveConnect()
+{
+ console->post("Connecting to pmtime, live source");
+ my.liveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void QedTimeControl::archiveConnect()
+{
+ console->post("Connecting to pmtime, archive source");
+ my.archiveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void QedTimeControl::showLiveTimeControl(void)
+{
+ my.livePacket->command = QmcTime::GUIShow;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void QedTimeControl::showArchiveTimeControl(void)
+{
+ my.archivePacket->command = QmcTime::GUIShow;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void QedTimeControl::hideLiveTimeControl()
+{
+ my.livePacket->command = QmcTime::GUIHide;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void QedTimeControl::hideArchiveTimeControl()
+{
+ my.archivePacket->command = QmcTime::GUIHide;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void QedTimeControl::endTimeControl(void)
+{
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Time Control process pmtime has exited."),
+ QApplication::tr("Quit") );
+ exit(-1);
+}
+
+void QedTimeControl::liveCloseConnection()
+{
+ my.liveSocket->close();
+ my.liveSocket = NULL;
+ emit done();
+ exit(0);
+}
+
+void QedTimeControl::archiveCloseConnection()
+{
+ my.archiveSocket->close();
+ my.archiveSocket = NULL;
+ emit done();
+ exit(0);
+}
+
+void QedTimeControl::liveSocketConnected()
+{
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in live pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.liveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed to send timezone in live pmtime negotiation."),
+ QApplication::tr("Quit"));
+ exit(1);
+ }
+ my.liveState = QedTimeControl::AwaitingACK;
+}
+
+void QedTimeControl::archiveSocketConnected()
+{
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.archiveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed timezone send in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ my.archiveState = QedTimeControl::AwaitingACK;
+}
+
+//
+// Start a shiny new pmtime process.
+// The one process serves time for all (live and archive) tabs.
+// We do have to specify which form will be used first, however.
+//
+void QedTimeControl::startTimeServer()
+{
+ QStringList arguments;
+
+ if (pmDebug & DBG_TRACE_TIMECONTROL)
+ arguments << "-D" << "all";
+ connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ connect(this, SIGNAL(readyReadStandardOutput()), this,
+ SLOT(readPortFromStdout()));
+ start("pmtime", arguments);
+}
+
+//
+// When pmtime starts in "port probe" mode, port# is written to
+// stdout. We can only complete negotiation once we have that...
+//
+void QedTimeControl::readPortFromStdout(void)
+{
+ bool ok;
+ QString data = readAllStandardOutput();
+
+ my.tcpPort = data.remove("port=").toInt(&ok, 10);
+ if (!ok) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad port number from pmtime program."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+
+ liveConnect();
+ archiveConnect();
+}
+
+void QedTimeControl::protocolMessage(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, QedProtocolState *state)
+{
+ int sts, need = sizeof(QmcTime::Packet), offset = 0;
+ QmcTime::Packet *msg;
+
+ // Read one pmtime packet, handling both small reads and large packets
+ for (;;) {
+ sts = socket->read(my.buffer + offset, need);
+ if (sts < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed socket read in pmtime transfer."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ else if (sts != need) {
+ need -= sts;
+ offset += sts;
+ continue;
+ }
+
+ msg = (QmcTime::Packet *)my.buffer;
+ if (msg->magic != QmcTime::Magic) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad client message magic number."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->length > my.bufferLength) {
+ my.bufferLength = msg->length;
+ my.buffer = (char *)realloc(my.buffer, my.bufferLength);
+ if (!my.buffer)
+ QedApp::nomem();
+ msg = (QmcTime::Packet *)my.buffer;
+ }
+ if (msg->length > (uint)offset + sts) {
+ offset += sts;
+ need = msg->length - offset;
+ continue;
+ }
+ break;
+ }
+
+#if DESPERATE
+ console->post(QedConsole::DebugProtocol,
+ "QedTimeControl::protocolMessage: recv pos=%s state=%d",
+ timeString(tosec(packet->position)), *state);
+#endif
+
+ switch (*state) {
+ case QedTimeControl::AwaitingACK:
+ if (msg->command != QmcTime::ACK) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Initial ACK not received from pmtime."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->source != packet->source) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("pmtime not serving same metric source."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ *state = QedTimeControl::ClientReady;
+ if (msg->length > packet->length) {
+ packet = (QmcTime::Packet *)realloc(packet, msg->length);
+ if (!packet)
+ QedApp::nomem();
+ }
+ //
+ // Note: we drive the local state from the time control values,
+ // and _not_ from the values that we initially sent to it.
+ //
+ memcpy(packet, msg, msg->length);
+ emit VCRMode(live, msg, true);
+ break;
+
+ case QedTimeControl::ClientReady:
+ if (msg->command == QmcTime::Step) {
+ emit step(live, msg);
+ msg->command = QmcTime::ACK;
+ msg->length = sizeof(QmcTime::Packet);
+ sts = socket->write((const char *)msg, msg->length);
+ if (sts < 0 || sts != (int)msg->length) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed pmtime write for STEP ACK."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ } else if (msg->command == QmcTime::VCRMode ||
+ msg->command == QmcTime::VCRModeDrag ||
+ msg->command == QmcTime::Bounds) {
+ emit VCRMode(live, msg, msg->command == QmcTime::VCRModeDrag);
+ } else if (msg->command == QmcTime::TZ) {
+ emit timeZone(live, msg, (char *)msg->data);
+ }
+ break;
+
+ default:
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Protocol error with pmtime."),
+ QApplication::tr("Quit") );
+ // fall through
+ case QedTimeControl::Disconnected:
+ exit(1);
+ }
+}
+
+void QedTimeControl::protocolMessageLoop(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, QedProtocolState *state)
+{
+ do {
+ protocolMessage(live, packet, socket, state);
+ } while (socket->bytesAvailable() >= (int)sizeof(QmcTime::Packet));
+}
diff --git a/src/libpcp_qed/src/qed_timecontrol.h b/src/libpcp_qed/src/qed_timecontrol.h
new file mode 100644
index 0000000..599ec88
--- /dev/null
+++ b/src/libpcp_qed/src/qed_timecontrol.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_TIMECONTROL_H
+#define QED_TIMECONTROL_H
+
+#include <QtCore/QString>
+#include <QtCore/QProcess>
+#include <QtNetwork/QTcpSocket>
+#include <qmc_time.h>
+
+class QedTimeControl : public QProcess
+{
+ Q_OBJECT
+
+public:
+ QedTimeControl();
+
+ void init(int port, bool livemode,
+ struct timeval *interval, struct timeval *position,
+ struct timeval *starttime, struct timeval *endtime,
+ QString tzstring, QString tzlabel);
+ void quit();
+
+ void addArchive(struct timeval starttime, struct timeval endtime,
+ QString tzstring, QString tzlabel, bool atEnd);
+
+ void liveConnect();
+ void archiveConnect();
+
+ int port() { return my.tcpPort; }
+ struct timeval *liveInterval() { return &my.livePacket->delta; }
+ struct timeval *livePosition() { return &my.livePacket->position; }
+ struct timeval *archiveInterval() { return &my.archivePacket->delta; }
+ struct timeval *archivePosition() { return &my.archivePacket->position; }
+ struct timeval *archiveStart() { return &my.archivePacket->start; }
+ struct timeval *archiveEnd() { return &my.archivePacket->end; }
+
+signals:
+ void done();
+ void step(bool, QmcTime::Packet *);
+ void VCRMode(bool, QmcTime::Packet *, bool);
+ void timeZone(bool, QmcTime::Packet *, char *);
+
+public slots:
+ void showLiveTimeControl();
+ void hideLiveTimeControl();
+ void showArchiveTimeControl();
+ void hideArchiveTimeControl();
+ void endTimeControl();
+
+ void readPortFromStdout();
+
+ void liveCloseConnection();
+ void liveSocketConnected();
+ void liveProtocolMessage()
+ {
+ protocolMessageLoop(true, my.livePacket, my.liveSocket, &my.liveState);
+ }
+
+ void archiveCloseConnection();
+ void archiveSocketConnected();
+ void archiveProtocolMessage()
+ {
+ protocolMessageLoop(false, my.archivePacket, my.archiveSocket,
+ &my.archiveState);
+ }
+
+private:
+ typedef enum {
+ Disconnected = 1,
+ AwaitingACK = 2,
+ ClientReady = 3,
+ } QedProtocolState;
+
+ void startTimeServer();
+ void protocolMessage(bool live, QmcTime::Packet *pmtime,
+ QTcpSocket *socket, QedProtocolState *state);
+ void protocolMessageLoop(bool live, QmcTime::Packet *pmtime,
+ QTcpSocket *socket, QedProtocolState *state);
+
+ struct {
+ int tcpPort;
+ int tzLength;
+ char *tzData;
+
+ unsigned int bufferLength;
+ char *buffer;
+
+ QTcpSocket *liveSocket;
+ QmcTime::Packet *livePacket;
+ QedProtocolState liveState;
+
+ QTcpSocket *archiveSocket;
+ QmcTime::Packet *archivePacket;
+ QedProtocolState archiveState;
+ } my;
+};
+
+#endif // QED_TIMECONTROL_H
diff --git a/src/libpcp_qed/src/qed_viewcontrol.cpp b/src/libpcp_qed/src/qed_viewcontrol.cpp
new file mode 100644
index 0000000..3f5ff17
--- /dev/null
+++ b/src/libpcp_qed/src/qed_viewcontrol.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui/QMenu>
+#include <QtGui/QMessageBox>
+#include "qed_console.h"
+#include "qed_viewcontrol.h"
+#include "qed_recorddialog.h"
+
+QedViewControl::QedViewControl()
+{
+ my.recording = false;
+ my.action = NULL;
+ my.group = NULL;
+}
+
+QedViewControl::~QedViewControl()
+{
+ if (my.action)
+ delete my.action;
+}
+
+void QedViewControl::init(QedGroupControl *group, QMenu *menu, QString title, double delta)
+{
+ my.delta = delta;
+ my.title = title;
+ my.group = group;
+ my.action = new QAction(title, menu);
+}
+
+bool QedViewControl::isArchiveSource(void)
+{
+ return my.group->isArchiveSource();
+}
+
+bool QedViewControl::isRecording(void)
+{
+ return my.recording;
+}
+
+void QedViewControl::addFolio(QString folio, QString view)
+{
+ my.view = view;
+ my.folio = folio;
+}
+
+void QedViewControl::addLogger(PmLogger *pmlogger, QString archive)
+{
+ my.loggerList.append(pmlogger);
+ my.archiveList.append(archive);
+}
+
+bool QedViewControl::startRecording(void)
+{
+ QedRecordDialog record;
+
+ console->post("QedView::startRecording");
+ record.init(this, my.delta);
+ if (record.exec() != QDialog::Accepted)
+ my.recording = false;
+ else { // write pmlogger/pmchart/pmafm configs and start up loggers.
+ console->post("QedView::startRecording starting loggers");
+ record.startLoggers();
+ my.recording = true;
+ }
+ return my.recording;
+}
+
+bool QedViewControl::stopRecording(QString &errmsg)
+{
+ QString msg = "Q\n";
+ int count = my.loggerList.size();
+ bool error = false;
+
+ console->post("QedView::stopRecording stopping %d logger(s)", count);
+ for (int i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(QApplication::tr(
+ "Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error = true;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ my.loggerList.at(i)->terminate();
+ }
+ }
+ return error;
+}
+
+void QedViewControl::cleanupRecording(void)
+{
+ my.recording = false;
+ my.loggerList.clear();
+ my.archiveList.clear();
+ my.view = QString::null;
+ my.folio = QString::null;
+}
+
+bool QedViewControl::queryRecording(QString &errmsg)
+{
+ QString msg = "?\n";
+ bool error = false;
+ int i, count = my.loggerList.size();
+
+ console->post("QedView::stopRecording querying %d logger(s)", count);
+ for (i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(QApplication::tr(
+ "Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error = true;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ }
+ }
+
+ if (error) {
+ msg = "Q\n"; // if one fails, we shut down all loggers
+ for (i = 0; i < count; i++)
+ my.loggerList.at(i)->write(msg.toAscii());
+ cleanupRecording();
+ }
+
+ return error;
+}
+
+bool QedViewControl::detachLoggers(QString &errmsg)
+{
+ QString msg = "D\n";
+ bool error = false;
+ int count = my.loggerList.size();
+
+ console->post("QedView::detachLoggers detaching %d logger(s)", count);
+ for (int i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(QApplication::tr(
+ "Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error = true;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ }
+ }
+ if (error)
+ cleanupRecording();
+ return error;
+}
diff --git a/src/libpcp_qed/src/qed_viewcontrol.h b/src/libpcp_qed/src/qed_viewcontrol.h
new file mode 100644
index 0000000..4d84cb4
--- /dev/null
+++ b/src/libpcp_qed/src/qed_viewcontrol.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2009, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QED_VIEWCONTROL_H
+#define QED_VIEWCONTROL_H
+
+#include <QtCore/QList>
+#include <QtGui/QAction>
+#include "qed_groupcontrol.h"
+
+class PmLogger;
+
+class QedViewControl
+{
+public:
+ QedViewControl();
+ virtual ~QedViewControl();
+
+ void init(QedGroupControl *, QMenu *, QString, double);
+ QedGroupControl *group() const { return my.group; }
+ bool isArchiveSource(); // query if tab is for archives
+
+ QAction *action() const { return my.action; }
+
+ QString view() const { return my.view; }
+ QString title() const { return my.title; }
+ void setTitle(QString &text) { my.title = text; my.action->setText(text); }
+
+ void addFolio(QString, QString);
+ void addLogger(PmLogger *, QString);
+ QStringList &archiveList() { return my.archiveList; }
+
+ virtual QStringList hostList(bool) = 0;
+ virtual QString pmloggerSyntax(bool) = 0;
+ virtual bool saveConfig(QString, bool, bool, bool, bool) = 0;
+
+ bool isRecording();
+ bool startRecording();
+ void cleanupRecording();
+ bool queryRecording(QString &);
+ bool stopRecording(QString &);
+ bool detachLoggers(QString &);
+
+private:
+ struct {
+ double delta; // default recording interval
+ QString title;
+ QAction *action;
+ QedGroupControl *group;
+
+ bool recording; // running any pmlogger's?
+ QString view;
+ QString folio;
+ QStringList archiveList; // list of archive names
+ QList<PmLogger*> loggerList; // list of pmloggers for our View
+ } my;
+};
+
+#endif // QED_VIEWCONTROL_H
diff --git a/src/libpcp_qmc/GNUmakefile b/src/libpcp_qmc/GNUmakefile
new file mode 100644
index 0000000..552118e
--- /dev/null
+++ b/src/libpcp_qmc/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_qmc/src/GNUmakefile b/src/libpcp_qmc/src/GNUmakefile
new file mode 100644
index 0000000..3c8d8f8
--- /dev/null
+++ b/src/libpcp_qmc/src/GNUmakefile
@@ -0,0 +1,24 @@
+TOPDIR = ../../..
+LIBRARY = libpcp_qmc
+PROJECT = $(LIBRARY).pro
+include $(TOPDIR)/src/include/builddefs
+
+HEADERS = $(shell echo *.h)
+SOURCES = $(shell echo *.cpp)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me: $(PROJECT)
+ $(QTMAKE)
+else
+build-me:
+endif
+
+install: default
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/libpcp_qmc/src/libpcp_qmc.pro b/src/libpcp_qmc/src/libpcp_qmc.pro
new file mode 100644
index 0000000..2be408f
--- /dev/null
+++ b/src/libpcp_qmc/src/libpcp_qmc.pro
@@ -0,0 +1,13 @@
+TARGET = pcp_qmc
+TEMPLATE = lib
+VERSION = 1.0.0
+CONFIG += qt staticlib warn_on
+INCLUDEPATH += ../../include
+
+HEADERS = qmc_context.h qmc_desc.h qmc_group.h \
+ qmc_indom.h qmc_metric.h qmc_source.h \
+ qmc_time.h
+
+SOURCES = qmc_context.cpp qmc_desc.cpp qmc_group.cpp \
+ qmc_indom.cpp qmc_metric.cpp qmc_source.cpp \
+ qmc_time.cpp
diff --git a/src/libpcp_qmc/src/qmc.h b/src/libpcp_qmc/src/qmc.h
new file mode 100644
index 0000000..cdd0f99
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_H
+#define QMC_H
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+//
+// Classes
+//
+class QmcContext;
+class QmcDesc;
+class QmcGroup;
+class QmcIndom;
+class QmcMetric;
+class QmcSource;
+
+#endif // QMC_H
diff --git a/src/libpcp_qmc/src/qmc_context.cpp b/src/libpcp_qmc/src/qmc_context.cpp
new file mode 100644
index 0000000..0a45a61
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_context.cpp
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2007-2008 Aconex. All Rights Reserved.
+ * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+
+#include "qmc_context.h"
+#include "qmc_metric.h"
+#include <limits.h>
+#include <QVector>
+#include <QStringList>
+#include <QHashIterator>
+
+QStringList *QmcContext::theStringList;
+
+QmcContext::QmcContext(QmcSource* source)
+{
+ my.delta = 0.0;
+ my.context = -1;
+ my.source = source;
+ my.needReconnect = false;
+
+ if (my.source->status() >= 0)
+ my.context = my.source->dupContext();
+ else
+ my.context = my.source->status();
+}
+
+QmcContext::~QmcContext()
+{
+ while (my.metrics.isEmpty() == false) {
+ delete my.metrics.takeFirst();
+ }
+ QHashIterator<pmID, QmcDesc*> descs(my.descCache);
+ while (descs.hasNext()) {
+ descs.next();
+ delete descs.value();
+ }
+ while (my.indoms.isEmpty() == false) {
+ delete my.indoms.takeFirst();
+ }
+ if (my.context >= 0)
+ my.source->delContext(my.context);
+}
+
+int
+QmcContext::lookupName(pmID pmid, QString **name)
+{
+ char *value;
+ int sts = 0;
+
+ if ((sts = pmUseContext(my.context)) < 0)
+ return sts;
+
+ if (my.pmidCache.contains(pmid) == false) {
+ if ((sts = pmNameID(pmid, &value)) >= 0) {
+ *name = new QString(value);
+ my.pmidCache.insert(pmid, *name);
+ free(value);
+ }
+ } else {
+ QString *np = my.pmidCache.value(pmid);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupName: Matched id "
+ << pmIDStr(pmid) << " to \"" << *np << "\"" << endl;
+ }
+ *name = np;
+ }
+ return sts;
+}
+
+int
+QmcContext::lookupPMID(const char *name, pmID& id)
+{
+ QString key = name;
+ int sts;
+
+ if ((sts = pmUseContext(my.context)) < 0)
+ return sts;
+
+ if (my.nameCache.contains(key) == false) {
+ if ((sts = pmLookupName(1, (char **)(&name), &id)) >= 0)
+ my.nameCache.insert(key, id);
+ } else {
+ id = my.nameCache.value(key);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupPMID: Matched \"" << name
+ << "\" to id " << pmIDStr(id) << endl;
+ }
+ sts = 1;
+ }
+ return sts;
+}
+
+int
+QmcContext::lookupInDom(const char *name, uint_t& indom)
+{
+ pmID pmid;
+ int sts = lookupPMID(name, pmid);
+ if (sts < 0)
+ return sts;
+ return lookupInDom(pmid, indom);
+}
+
+int
+QmcContext::lookupDesc(pmID pmid, QmcDesc **descriptor)
+{
+ int sts;
+ QmcDesc *descPtr;
+
+ if ((sts = pmUseContext(my.context)) < 0)
+ return sts;
+
+ if (my.descCache.contains(pmid) == false) {
+ descPtr = new QmcDesc(pmid);
+ if (descPtr->status() < 0) {
+ sts = descPtr->status();
+ delete descPtr;
+ return sts;
+ }
+ my.descCache.insert(pmid, descPtr);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupDesc: Add descriptor for "
+ << pmIDStr(descPtr->id()) << endl;
+ }
+ }
+ else {
+ descPtr = my.descCache.value(pmid);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupDesc: Reusing descriptor "
+ << pmIDStr(descPtr->id()) << endl;
+ }
+ }
+ *descriptor = descPtr;
+ return 0;
+}
+
+int
+QmcContext::lookup(pmID pmid, QString **namePtr, QmcDesc **descPtr, QmcIndom **indomPtr)
+{
+ uint_t indom;
+ int sts;
+
+ if ((sts = lookupName(pmid, namePtr)) < 0)
+ return sts;
+ if ((sts = lookupDesc(pmid, descPtr)) < 0)
+ return sts;
+ if ((sts = lookupInDom(pmid, indom)) < 0)
+ return sts;
+ *indomPtr = (indom == UINT_MAX) ? NULL : my.indoms[indom];
+ return 0;
+}
+
+int
+QmcContext::lookupInDom(pmID pmid, uint_t& indom)
+{
+ QmcDesc *descPtr;
+ int sts;
+
+ if ((sts = lookupDesc(pmid, &descPtr)) < 0)
+ return sts;
+ return lookupInDom(descPtr, indom);
+}
+
+int
+QmcContext::lookupInDom(QmcDesc *descPtr, uint_t& indom)
+{
+ int i, sts;
+ QmcIndom *indomPtr;
+
+ if ((sts = pmUseContext(my.context)) < 0)
+ return sts;
+
+ indom = UINT_MAX;
+ if (descPtr->desc().indom != PM_INDOM_NULL) {
+ for (i = 0; i < my.indoms.size(); i++)
+ if (my.indoms[i]->id() == (int)descPtr->desc().indom)
+ break;
+ if (i == my.indoms.size()) {
+ indomPtr = new QmcIndom(my.source->type(), *descPtr);
+ if (indomPtr->status() < 0) {
+ sts = indomPtr->status();
+ delete indomPtr;
+ return sts;
+ }
+ my.indoms.append(indomPtr);
+ indom = my.indoms.size() - 1;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupInDom: Add indom for "
+ << pmInDomStr(indomPtr->id()) << endl;
+ }
+ }
+ else {
+ indomPtr = my.indoms[i];
+ indom = i;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::lookupInDom: Reusing indom "
+ << pmInDomStr(indomPtr->id()) << endl;
+ }
+ }
+ }
+ return 0;
+}
+
+int
+QmcContext::useTZ()
+{
+ if (my.source->tzHandle() >= 0)
+ return pmUseZone(my.source->tzHandle());
+ return 0;
+}
+
+QTextStream&
+operator<<(QTextStream &stream, const QmcContext &context)
+{
+ stream << context.source().desc() << " has "
+ << context.numMetrics() << " metrics";
+ return stream;
+}
+
+void
+QmcContext::dump(QTextStream &stream)
+{
+ stream << "Context " << my.context << " has " << my.nameCache.size()
+ << " metric names for source:" << endl;
+ my.source->dump(stream);
+}
+
+void
+QmcContext::dumpMetrics(QTextStream &stream)
+{
+ for (int i = 0; i < my.metrics.size(); i++)
+ stream << " [" << i << "] "
+ << my.metrics[i]->spec(false, true) << endl;
+}
+
+void
+QmcContext::addMetric(QmcMetric *metric)
+{
+ pmID pmid;
+ int i;
+
+ my.metrics.append(metric);
+ if (metric->status() >= 0) {
+ pmid = metric->desc().desc().pmid;
+ for (i = 0; i < my.pmids.size(); i++)
+ if (my.pmids[i] == pmid)
+ break;
+ if (i == my.pmids.size())
+ my.pmids.append(pmid);
+ metric->setIdIndex(i);
+ }
+}
+
+int
+QmcContext::fetch(bool update)
+{
+ int i, sts;
+ pmResult *result;
+
+ for (i = 0; i < my.metrics.size(); i++) {
+ QmcMetric *metric = my.metrics[i];
+ if (metric->status() < 0)
+ continue;
+ metric->shiftValues();
+ }
+
+ // Inform each indom that we are about to do a new fetch so any
+ // indom changes are now irrelevant
+ for (i = 0; i < my.indoms.size(); i++)
+ my.indoms[i]->newFetch();
+
+ sts = pmUseContext(my.context);
+ if (sts >= 0) {
+ for (i = 0; i < my.indoms.size(); i++) {
+ if (my.indoms[i]->diffProfile())
+ sts = my.indoms[i]->genProfile();
+ }
+ }
+ else if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: Unable to switch to this context: "
+ << pmErrStr(sts) << endl;
+ }
+
+ if (sts >= 0 && my.needReconnect) {
+ sts = pmReconnectContext(my.context);
+ if (sts >= 0) {
+ my.needReconnect = false;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: Reconnected context \""
+ << *my.source << endl;
+ }
+ }
+ else if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: Reconnect failed: "
+ << pmErrStr(sts) << endl;
+ }
+ }
+
+ if (sts >= 0 && my.pmids.size()) {
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: fetching context " << *this << endl;
+ }
+
+ sts = pmFetch(my.pmids.size(),
+ (pmID *)(my.pmids.toVector().data()), &result);
+ if (sts >= 0) {
+ my.previousTime = my.currentTime;
+ my.currentTime = result->timestamp;
+ my.delta = __pmtimevalSub(&my.currentTime, &my.previousTime);
+ for (i = 0; i < my.metrics.size(); i++) {
+ QmcMetric *metric = my.metrics[i];
+ if (metric->status() < 0)
+ continue;
+ Q_ASSERT((int)metric->idIndex() < result->numpmid);
+ metric->extractValues(result->vset[metric->idIndex()]);
+ }
+ pmFreeResult(result);
+ }
+ else {
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: pmFetch: " << pmErrStr(sts) << endl;
+ }
+ for (i = 0; i < my.metrics.size(); i++) {
+ QmcMetric *metric = my.metrics[i];
+ if (metric->status() < 0)
+ continue;
+ metric->setError(sts);
+ }
+ if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT)
+ my.needReconnect = true;
+ }
+
+ if (update) {
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: Updating metrics" << endl;
+ }
+ for (i = 0; i < my.metrics.size(); i++) {
+ QmcMetric *metric = my.metrics[i];
+ if (metric->status() < 0)
+ continue;
+ metric->update();
+ }
+ }
+ }
+ else if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcContext::fetch: nothing to fetch" << endl;
+ }
+
+ return sts;
+}
+
+void
+QmcContext::dometric(const char *name)
+{
+ theStringList->append(name);
+}
+
+int
+QmcContext::traverse(const char *name, QStringList &list)
+{
+ int sts;
+
+ theStringList = &list;
+ theStringList->clear();
+
+ if ((sts = pmUseContext(my.context)) < 0)
+ return sts;
+
+ sts = pmTraversePMNS(name, QmcContext::dometric);
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ if (sts >= 0) {
+ cerr << "QmcContext::traverse: Found " << list.size()
+ << " names from " << name << endl;
+ }
+ else
+ cerr << "QmcContext::traverse: Failed: " << pmErrStr(sts)
+ << endl;
+ }
+
+ return sts;
+}
diff --git a/src/libpcp_qmc/src/qmc_context.h b/src/libpcp_qmc/src/qmc_context.h
new file mode 100644
index 0000000..8751597
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_context.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_CONTEXT_H
+#define QMC_CONTEXT_H
+
+#include "qmc.h"
+#include "qmc_desc.h"
+#include "qmc_indom.h"
+#include "qmc_source.h"
+
+#include <qhash.h>
+#include <qlist.h>
+#include <qstring.h>
+#include <qtextstream.h>
+
+class QmcContext
+{
+public:
+ QmcContext(QmcSource *source);
+ ~QmcContext();
+
+ int status() const // Is this a valid context?
+ { return (my.context < 0) ? my.context : 0; }
+
+ int handle() const // The PMAPI context handle
+ { return my.context; }
+
+ QmcSource const& source() const // Source description
+ { return *my.source; }
+
+ unsigned int numIDs() const // Number of unique pmIDs in use
+ { return my.pmids.size(); }
+
+ pmID id(unsigned int index) const // Access to each unique pmID
+ { return my.pmids[index]; }
+
+ QmcDesc const& desc(pmID pmid) const
+ { return *(my.descCache.value(pmid)); }
+ QmcDesc& desc(pmID pmid) // Access to each descriptor
+ { return *(my.descCache.value(pmid)); }
+
+ unsigned int numIndoms() const // Number of indom descriptors
+ { return my.indoms.size(); } // requested from this context
+
+ QmcIndom const& indom(unsigned int index) const
+ { return *(my.indoms[index]); }
+ QmcIndom& indom(unsigned int index) // Access to each indom
+ { return *(my.indoms[index]); }
+
+ // Lookup the pmid or indom (implies descriptor) for metric <name>|<id>
+ int lookupPMID(const char *name, pmID& id);
+ int lookupInDom(const char *name, unsigned int& indom);
+ int lookupInDom(QmcDesc *desc, uint_t& indom);
+
+ // Lookup various structures using the pmid
+ int lookupInDom(pmID pmid, unsigned int& indom);
+ int lookupName(pmID pmid, QString **name);
+ int lookupDesc(pmID pmid, QmcDesc **desc);
+ int lookup(pmID pmid, QString **name, QmcDesc **desc, QmcIndom **indom);
+
+ int useTZ(); // Use this timezone
+
+ unsigned int numMetrics() const // Number of metrics using this context
+ { return my.metrics.size(); }
+
+ QmcMetric const& metric(unsigned int index) const
+ { return *(my.metrics[index]); }
+ QmcMetric& metric(unsigned int index) // Get a handle to a metric
+ { return *(my.metrics[index]); }
+
+ void addMetric(QmcMetric* metric); // Add a metric using this context
+
+ int fetch(bool update); // Fetch metrics using this context
+
+ struct timeval const& timeStamp() const
+ { return my.currentTime; }
+
+ double timeDelta() const // Time since previous fetch
+ { return my.delta; }
+
+ int traverse(const char *name, QStringList &list); // Walk the namespace
+
+ friend QTextStream &operator<<(QTextStream &stream, const QmcContext &rhs);
+ void dump(QTextStream &stream); // Dump debugging information
+ void dumpMetrics(QTextStream &stream); // Dump list of metrics
+
+private:
+ struct {
+ int context; // PMAPI Context handle
+ bool needReconnect; // Need to reconnect the context
+ QmcSource *source; // Handle to the source description
+ QHash<QString, pmID> nameCache; // Reverse map from names to PMIDs
+ QHash<pmID, QString*> pmidCache;// Mapping between PMIDs and names
+ QHash<pmID, QmcDesc*> descCache;// Mapping between PMIDs and descs
+ QList<pmID> pmids; // List of valid PMIDs to be fetched
+ QList<QmcIndom*> indoms; // List of requested indoms
+ QList<QmcMetric*> metrics; // List of metrics using this context
+ struct timeval currentTime; // Time of current fetch
+ struct timeval previousTime; // Time of previous fetch
+ double delta; // Time between fetches
+ } my;
+
+ static QStringList *theStringList; // List of metric names in traversal
+ static void dometric(const char *);
+};
+
+#endif // QMC_CONTEXT_H
diff --git a/src/libpcp_qmc/src/qmc_desc.cpp b/src/libpcp_qmc/src/qmc_desc.cpp
new file mode 100644
index 0000000..af496d8
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_desc.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#include "qmc_desc.h"
+#include <QTextStream>
+
+QmcDesc::QmcDesc(pmID pmid)
+{
+ my.pmid = pmid;
+ my.scaleFlag = false;
+ my.status = pmLookupDesc(my.pmid, &my.desc);
+ if (my.status >= 0) {
+ my.scaleUnits = my.desc.units;
+ setUnitStrings();
+ }
+ else if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcDesc::QmcDesc: unable to lookup "
+ << pmIDStr(my.pmid) << ": " << pmErrStr(my.status) << endl;
+ }
+}
+
+void
+QmcDesc::setUnitStrings()
+{
+ const char *units = pmUnitsStr(&my.scaleUnits);
+ const char *shortUnits = shortUnitsString(&my.scaleUnits);
+
+ if (my.desc.sem == PM_SEM_COUNTER) {
+ if (my.scaleFlag &&
+ my.scaleUnits.dimTime == 1 &&
+ my.scaleUnits.dimSpace == 0 &&
+ my.scaleUnits.dimCount == 0) {
+ my.units = "Time Utilization";
+ my.shortUnits = "util";
+ }
+ else {
+ my.units = units;
+ my.units.append(" / second");
+ my.shortUnits = shortUnits;
+ my.shortUnits.append("/s");
+ }
+ }
+ else {
+ if (units[0] == '\0')
+ my.units = "none";
+ else
+ my.units = units;
+ if (shortUnits[0] == '\0')
+ my.shortUnits = "none";
+ else
+ my.shortUnits = shortUnits;
+ }
+}
+
+void
+QmcDesc::setScaleUnits(const pmUnits &units)
+{
+ my.scaleUnits = units;
+ my.scaleFlag = true;
+ setUnitStrings();
+}
+
+const char *
+QmcDesc::shortUnitsString(pmUnits *pu)
+{
+ const char *spaceString, *timeString, *countString;
+ char sbuf[20], tbuf[20], cbuf[20];
+ static char buf[60];
+ char *p;
+
+ spaceString = timeString = countString = NULL;
+ buf[0] = '\0';
+
+ if (pu->dimSpace) {
+ switch (pu->scaleSpace) {
+ case PM_SPACE_BYTE:
+ spaceString = "b";
+ break;
+ case PM_SPACE_KBYTE:
+ spaceString = "Kb";
+ break;
+ case PM_SPACE_MBYTE:
+ spaceString = "Mb";
+ break;
+ case PM_SPACE_GBYTE:
+ spaceString = "Gb";
+ break;
+ case PM_SPACE_TBYTE:
+ spaceString = "Tb";
+ break;
+ default:
+ sprintf(sbuf, "space-%d", pu->scaleSpace);
+ spaceString = sbuf;
+ break;
+ }
+ }
+ if (pu->dimTime) {
+ switch (pu->scaleTime) {
+ case PM_TIME_NSEC:
+ timeString = "ns";
+ break;
+ case PM_TIME_USEC:
+ timeString = "us";
+ break;
+ case PM_TIME_MSEC:
+ timeString = "msec";
+ break;
+ case PM_TIME_SEC:
+ timeString = "s";
+ break;
+ case PM_TIME_MIN:
+ timeString = "m";
+ break;
+ case PM_TIME_HOUR:
+ timeString = "h";
+ break;
+ default:
+ sprintf(tbuf, "time-%d", pu->scaleTime);
+ timeString = tbuf;
+ break;
+ }
+ }
+ if (pu->dimCount) {
+ switch (pu->scaleCount) {
+ case 0:
+ countString = "c";
+ break;
+ case 1:
+ countString = "cx10";
+ break;
+ default:
+ sprintf(cbuf, "cx10^%d", pu->scaleCount);
+ countString = cbuf;
+ break;
+ }
+ }
+
+ p = buf;
+
+ if (pu->dimSpace > 0) {
+ if (pu->dimSpace == 1)
+ sprintf(p, "%s", spaceString);
+ else
+ sprintf(p, "%s^%d", spaceString, pu->dimSpace);
+ while (*p) p++;
+ }
+ if (pu->dimTime > 0) {
+ if (pu->dimTime == 1)
+ sprintf(p, "%s", timeString);
+ else
+ sprintf(p, "%s^%d", timeString, pu->dimTime);
+ while (*p) p++;
+ }
+ if (pu->dimCount > 0) {
+ if (pu->dimCount == 1)
+ sprintf(p, "%s", countString);
+ else
+ sprintf(p, "%s^%d", countString, pu->dimCount);
+ while (*p) p++;
+ }
+ if (pu->dimSpace < 0 || pu->dimTime < 0 || pu->dimCount < 0) {
+ *p++ = '/';
+ if (pu->dimSpace < 0) {
+ if (pu->dimSpace == -1)
+ sprintf(p, "%s", spaceString);
+ else
+ sprintf(p, "%s^%d", spaceString, -pu->dimSpace);
+ while (*p) p++;
+ }
+ if (pu->dimTime < 0) {
+ if (pu->dimTime == -1)
+ sprintf(p, "%s", timeString);
+ else
+ sprintf(p, "%s^%d", timeString, -pu->dimTime);
+ while (*p) p++;
+ }
+ if (pu->dimCount < 0) {
+ if (pu->dimCount == -1)
+ sprintf(p, "%s", countString);
+ else
+ sprintf(p, "%s^%d", countString, -pu->dimCount);
+ while (*p) p++;
+ }
+ }
+
+ if (buf[0] == '\0') {
+ if (pu->scaleCount == 1)
+ sprintf(buf, "x10");
+ else if (pu->scaleCount != 0)
+ sprintf(buf, "x10^%d", pu->scaleCount);
+ }
+ else
+ *p = '\0';
+
+ return buf;
+}
diff --git a/src/libpcp_qmc/src/qmc_desc.h b/src/libpcp_qmc/src/qmc_desc.h
new file mode 100644
index 0000000..1f269ff
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_desc.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_DESC_H
+#define QMC_DESC_H
+
+#include "qmc.h"
+#include <qstring.h>
+
+class QmcDesc
+{
+public:
+ QmcDesc(pmID pmid);
+
+ int status() const { return my.status; }
+ pmID id() const { return my.pmid; }
+ pmDesc desc() const { return my.desc; }
+ const pmDesc *descPtr() const { return &my.desc; }
+ const QString units() const { return my.units; }
+ const QString shortUnits() const { return my.shortUnits; }
+ const pmUnits &scaleUnits() const { return my.scaleUnits; }
+
+ void setScaleUnits(const pmUnits &units);
+
+ // Are we using scaled units provided by a call to setScaleUnits?
+ bool useScaleUnits() const { return my.scaleFlag; }
+
+private:
+ struct {
+ int status;
+ pmID pmid;
+ pmDesc desc;
+ QString units;
+ QString shortUnits;
+ pmUnits scaleUnits;
+ bool scaleFlag;
+ } my;
+
+ void setUnitStrings();
+ static const char *shortUnitsString(pmUnits *pu);
+};
+
+#endif // QMC_DESC_H
diff --git a/src/libpcp_qmc/src/qmc_group.cpp b/src/libpcp_qmc/src/qmc_group.cpp
new file mode 100644
index 0000000..d2ccb72
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_group.cpp
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1997-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <limits.h>
+#include <float.h>
+#include <math.h>
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "qmc_group.h"
+#include "qmc_source.h"
+#include "qmc_context.h"
+#include "qmc_metric.h"
+
+int QmcGroup::tzLocal = -1;
+bool QmcGroup::tzLocalInit = false;
+QString QmcGroup::tzLocalString;
+QString QmcGroup::localHost;
+
+QmcGroup::QmcGroup(bool restrictArchives)
+{
+ my.restrictArchives = restrictArchives;
+ my.mode = PM_CONTEXT_HOST;
+ my.use = -1;
+ my.localSource = 0;
+ my.tzFlag = unknownTZ;
+ my.tzDefault = -1;
+ my.tzUser = -1;
+ my.tzGroupIndex = 0;
+ my.timeEndReal = 0.0;
+
+ // Get timezone from environment
+ if (tzLocalInit == false) {
+ char buf[MAXHOSTNAMELEN];
+ gethostname(buf, MAXHOSTNAMELEN);
+ buf[MAXHOSTNAMELEN-1] = '\0';
+ localHost = buf;
+
+ char *tz = __pmTimezone();
+ if (tz == NULL)
+ pmprintf("%s: Warning: Unable to get timezone from environment\n",
+ pmProgname);
+ else {
+ tzLocal = pmNewZone(tz);
+ if (tzLocal < 0)
+ pmprintf("%s: Warning: Timezone for localhost: %s\n",
+ pmProgname, pmErrStr(tzLocal));
+ else {
+ tzLocalString = tz;
+ my.tzDefault = tzLocal;
+ my.tzFlag = localTZ;
+ }
+ }
+ tzLocalInit = true;
+ }
+}
+
+QmcGroup::~QmcGroup()
+{
+ for (int i = 0; i < my.contexts.size(); i++)
+ if (my.contexts[i])
+ delete my.contexts[i];
+}
+
+int
+QmcGroup::use(int type, const QString &theSource, int flags)
+{
+ int sts = 0;
+ unsigned int i;
+ QString source(theSource);
+
+ if (type == PM_CONTEXT_LOCAL) {
+ for (i = 0; i < numContexts(); i++)
+ if (my.contexts[i]->source().type() == type)
+ break;
+ }
+ else if (type == PM_CONTEXT_ARCHIVE) {
+ if (source == QString::null) {
+ pmprintf("%s: Error: Archive context requires archive path\n",
+ pmProgname);
+ return PM_ERR_NOCONTEXT;
+ }
+ // This doesn't take into account {.N,.meta,.index,} ... but
+ // then again, nor does pmNewContext. More work ... useful?
+ for (i = 0; i < numContexts(); i++) {
+ if (source == my.contexts[i]->source().source())
+ break;
+ }
+ }
+ else {
+ if (source == QString::null) {
+ if (!defaultDefined()) {
+ createLocalContext();
+ if (!defaultDefined()) {
+ pmprintf("%s: Error: "
+ "Cannot connect to PMCD on localhost: %s\n",
+ pmProgname,
+ pmErrStr(my.localSource->status()));
+ return my.localSource->status();
+ }
+ }
+ source = my.contexts[0]->source().source();
+ }
+
+ for (i = 0; i < numContexts(); i++) {
+ if (source == my.contexts[i]->source().source())
+ break;
+ }
+ }
+
+ if (i == numContexts()) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::use: No direct match for context \"" << source
+ << "\" (type " << type << ")." << endl;
+ }
+
+ // Determine live or archive mode by the first source
+ if (i == 0)
+ my.mode = type;
+
+ // If the assumed mode differs from the requested context type
+ // we may need to map the host to an archive
+ if (my.mode != type) {
+
+ if (my.mode == PM_CONTEXT_HOST && type == PM_CONTEXT_ARCHIVE) {
+ pmprintf("%s: Error: Archive \"%s\" requested "
+ "after live mode was assumed.\n", pmProgname,
+ (const char *)source.toAscii());
+ return PM_ERR_NOCONTEXT;
+ }
+
+ // If we are in archive mode, map hosts to archives of same host
+ if (my.mode == PM_CONTEXT_ARCHIVE && type == PM_CONTEXT_HOST) {
+ QString chop1 = source.remove(PM_LOG_MAXHOSTLEN-1, INT_MAX);
+ for (i = 0; i < numContexts(); i++) {
+ QString chop2 = my.contexts[i]->source().host();
+ chop2.remove(PM_LOG_MAXHOSTLEN-1, INT_MAX);
+ if (chop1 == chop2)
+ break;
+ }
+
+ if (i == numContexts()) {
+ pmprintf("%s: Error: No archives were specified "
+ "for host \"%s\"\n", pmProgname,
+ (const char *)source.toAscii());
+ return PM_ERR_NOTARCHIVE;
+ }
+ }
+ }
+ }
+
+ if (i == numContexts()) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::use: Creating new context for \"" << source
+ << '\"' << endl;
+ }
+
+ QmcSource *src = QmcSource::getSource(type, source, flags, false);
+ if (src == NULL) {
+ pmprintf("%s: Error: No archives were specified for host \"%s\"\n",
+ pmProgname, (const char *)source.toAscii());
+ return PM_ERR_NOTARCHIVE;
+ }
+
+ QmcContext *newContext = new QmcContext(src);
+ if (newContext->handle() < 0) {
+ sts = newContext->handle();
+ pmprintf("%s: Error: %s: %s\n", pmProgname,
+ (const char *)source.toAscii(), pmErrStr(sts));
+ delete newContext;
+ return sts;
+ }
+
+ // If we are in archive mode and are adding an archive,
+ // make sure another archive for the same host does not exist
+ if (my.restrictArchives && type == PM_CONTEXT_ARCHIVE) {
+ for (i = 0; i < numContexts(); i++)
+ // No need to restrict comparison here, both are from
+ // log labels.
+ if (my.contexts[i]->source().host() ==
+ newContext->source().host()) {
+ pmprintf("%s: Error: Archives \"%s\" and \"%s\" are from "
+ "the same host \"%s\"\n", pmProgname,
+ my.contexts[i]->source().sourceAscii(),
+ newContext->source().sourceAscii(),
+ my.contexts[i]->source().hostAscii());
+ delete newContext;
+ return PM_ERR_NOCONTEXT;
+ }
+ }
+
+ my.contexts.append(newContext);
+ my.use = my.contexts.size() - 1;
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::use: Added context " << my.use << ": "
+ << *newContext << endl;
+ }
+ }
+
+ // We found a match, do we need to use a different context?
+ else if (i != (unsigned int)my.use) {
+ my.use = i;
+ sts = useContext();
+ if (sts < 0) {
+ pmprintf("%s: Error: Unable to use context to %s: %s\n", pmProgname,
+ context()->source().sourceAscii(), pmErrStr(sts));
+ return sts;
+ }
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::use: Using existing context " << my.use
+ << " for " << context()->source().desc() << endl;
+ }
+ }
+ else if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::use: Using current context " << my.use
+ << " (handle = " << context()->handle() << ") for "
+ << context()->source().desc() << endl;
+ }
+
+ return context()->handle();
+}
+
+int
+QmcGroup::useTZ()
+{
+ int sts = context()->useTZ();
+
+ if (sts >= 0) {
+ my.tzDefault = context()->source().tzHandle();
+ my.tzFlag = groupTZ;
+ my.tzGroupIndex = my.use;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::useTZ: Using timezone of "
+ << context()->source().desc()
+ << " (" << my.tzGroupIndex << ')' << endl;
+ }
+ }
+ return sts;
+}
+
+int
+QmcGroup::useTZ(const QString &tz)
+{
+ int sts = pmNewZone(tz.toAscii());
+
+ if (sts >= 0) {
+ my.tzUser = sts;
+ my.tzUserString = tz;
+ my.tzFlag = userTZ;
+ my.tzDefault = sts;
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::useTZ: Switching timezones to \"" << tz
+ << "\" (" << my.tzUserString << ')' << endl;
+ }
+ }
+ return sts;
+}
+
+int
+QmcGroup::useLocalTZ()
+{
+ if (tzLocal >= 0) {
+ int sts = pmUseZone(tzLocal);
+ if (sts > 0) {
+ my.tzFlag = localTZ;
+ my.tzDefault = tzLocal;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::useTZ: Using timezone of host \"localhost\""
+ << endl;
+ }
+ }
+ return sts;
+ }
+ return tzLocal;
+}
+
+void
+QmcGroup::defaultTZ(QString &label, QString &tz)
+{
+ if (my.tzFlag == userTZ) {
+ label = my.tzUserString;
+ tz = my.tzUserString;
+ }
+ else if (my.tzFlag == localTZ) {
+ label = localHost;
+ tz = tzLocalString;
+ }
+ else {
+ label = my.contexts[my.tzGroupIndex]->source().host();
+ tz = my.contexts[my.tzGroupIndex]->source().timezone();
+ }
+}
+
+int
+QmcGroup::useDefaultTZ()
+{
+ if (my.tzFlag == unknownTZ)
+ return -1;
+ return pmUseZone(my.tzDefault);
+}
+
+int
+QmcGroup::useDefault()
+{
+ if (numContexts() == 0)
+ createLocalContext();
+ if (numContexts() == 0)
+ return my.localSource->status();
+ my.use = 0;
+ return pmUseContext(context()->handle());
+}
+
+void
+QmcGroup::createLocalContext()
+{
+ if (numContexts() == 0) {
+ QTextStream cerr(stderr);
+ QmcSource *localSource = QmcSource::getSource(PM_CONTEXT_HOST,
+ localHost, 0, false);
+ if (localSource->status() < 0 && pmDebug & DBG_TRACE_PMC)
+ cerr << "QmcGroup::createLocalContext: Default context to "
+ << localSource->desc() << " failed: "
+ << pmErrStr(localSource->status()) << endl;
+ else if (pmDebug & DBG_TRACE_PMC)
+ cerr << "QmcGroup::createLocalContext: Default context to "
+ << localSource->desc() << endl;
+
+ QmcContext *newContext = new QmcContext(localSource);
+ if (newContext->handle() < 0) {
+ pmprintf("%s: Error: %s: %s\n", pmProgname,
+ (const char *)localHost.toAscii(), pmErrStr(newContext->handle()));
+ }
+ my.contexts.append(newContext);
+ my.use = my.contexts.size() - 1;
+ }
+}
+
+void
+QmcGroup::updateBounds()
+{
+ double newStart = DBL_MAX;
+ double newEnd = 0.0;
+ double startReal;
+ double endReal;
+ struct timeval startTv;
+ struct timeval endTv;
+
+ my.timeStart.tv_sec = 0;
+ my.timeStart.tv_usec = 0;
+ my.timeEnd = my.timeStart;
+
+ for (unsigned int i = 0; i < numContexts(); i++) {
+ if (my.contexts[i]->handle() >= 0 &&
+ my.contexts[i]->source().type() == PM_CONTEXT_ARCHIVE) {
+ startTv = my.contexts[i]->source().start();
+ endTv = my.contexts[i]->source().end();
+ startReal = __pmtimevalToReal(&startTv);
+ endReal = __pmtimevalToReal(&endTv);
+ if (startReal < newStart)
+ newStart = startReal;
+ if (endReal > newEnd)
+ newEnd = endReal;
+ }
+ }
+
+ __pmtimevalFromReal(newStart, &my.timeStart);
+ __pmtimevalFromReal(newEnd, &my.timeEnd);
+ my.timeEndReal = newEnd;
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::updateBounds: start = " << my.timeStart.tv_sec
+ << '.' << my.timeStart.tv_usec << ", end = "
+ << my.timeEnd.tv_sec << '.' << my.timeEnd.tv_usec << endl;
+ }
+}
+
+void
+QmcGroup::dump(QTextStream &stream)
+{
+ stream << "mode: ";
+ switch(my.mode) {
+ case PM_CONTEXT_LOCAL:
+ stream << "local";
+ break;
+ case PM_CONTEXT_HOST:
+ stream << "live host";
+ break;
+ case PM_CONTEXT_ARCHIVE:
+ stream << "archive";
+ break;
+ }
+
+ stream << ", timezone: ";
+ switch(my.tzFlag) {
+ case QmcGroup::localTZ:
+ stream << "local = \"" << tzLocalString;
+ break;
+ case QmcGroup::userTZ:
+ stream << "user = \"" << my.tzUserString;
+ break;
+ case QmcGroup::groupTZ:
+ stream << "group = \""
+ << my.contexts[my.tzGroupIndex]->source().timezone();
+ break;
+ case QmcGroup::unknownTZ:
+ stream << "unknown = \"???";
+ break;
+ }
+ stream << "\": " << endl;
+
+ stream << " " << numContexts() << " contexts:" << endl;
+ for (unsigned int i = 0; i < numContexts(); i++) {
+ stream << " [" << i << "] " << *(my.contexts[i]) << endl;
+ my.contexts[i]->dumpMetrics(stream);
+ }
+}
+
+int
+QmcGroup::useContext()
+{
+ int sts = 0;
+
+ if ((context()->status() == 0) &&
+ (sts = pmUseContext(context()->handle())) < 0)
+ pmprintf("%s: Error: Unable to reuse context to %s: %s\n",
+ pmProgname, context()->source().sourceAscii(), pmErrStr(sts));
+ return sts;
+}
+
+QmcMetric *
+QmcGroup::addMetric(char const *string, double theScale, bool active)
+{
+ QmcMetric *metric = new QmcMetric(this, string, theScale, active);
+ if (metric->status() >= 0)
+ metric->context()->addMetric(metric);
+ return metric;
+}
+
+QmcMetric *
+QmcGroup::addMetric(pmMetricSpec *theMetric, double theScale, bool active)
+{
+ QmcMetric *metric = new QmcMetric(this, theMetric, theScale, active);
+ if (metric->status() >= 0)
+ metric->context()->addMetric(metric);
+ return metric;
+}
+
+int
+QmcGroup::fetch(bool update)
+{
+ int sts = 0;
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::fetch: " << numContexts() << " contexts" << endl;
+ }
+
+ for (unsigned int i = 0; i < numContexts(); i++)
+ my.contexts[i]->fetch(update);
+
+ if (numContexts())
+ sts = useContext();
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcGroup::fetch: Done" << endl;
+ }
+
+ return sts;
+}
+
+int
+QmcGroup::setArchiveMode(int mode, const struct timeval *when, int interval)
+{
+ int sts, result = 0;
+
+ for (unsigned int i = 0; i < numContexts(); i++) {
+ if (my.contexts[i]->source().type() != PM_CONTEXT_ARCHIVE)
+ continue;
+
+ sts = pmUseContext(my.contexts[i]->handle());
+ if (sts < 0) {
+ pmprintf("%s: Error: Unable to switch to context for %s: %s\n",
+ pmProgname, my.contexts[i]->source().sourceAscii(),
+ pmErrStr(sts));
+ result = sts;
+ continue;
+ }
+ sts = pmSetMode(mode, when, interval);
+ if (sts < 0) {
+ pmprintf("%s: Error: Unable to set context mode for %s: %s\n",
+ pmProgname, my.contexts[i]->source().sourceAscii(),
+ pmErrStr(sts));
+ result = sts;
+ }
+ }
+ sts = useContext();
+ if (sts < 0)
+ result = sts;
+ return result;
+}
diff --git a/src/libpcp_qmc/src/qmc_group.h b/src/libpcp_qmc/src/qmc_group.h
new file mode 100644
index 0000000..05edadf
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_group.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_GROUP_H
+#define QMC_GROUP_H
+
+#include "qmc.h"
+#include "qmc_context.h"
+
+#include <qlist.h>
+#include <qstring.h>
+#include <qtextstream.h>
+
+class QmcGroup
+{
+public:
+ enum TimeZoneFlag { localTZ, userTZ, groupTZ, unknownTZ };
+
+public:
+ QmcGroup(bool restrictArchives = false);
+ ~QmcGroup();
+
+ int mode() const { return my.mode; }
+
+ unsigned int numContexts() const { return my.contexts.size(); }
+
+ // Return a handle to the contexts
+ QmcContext* context() const { return my.contexts[my.use]; }
+ QmcContext* context(unsigned int index) const { return my.contexts[index]; }
+
+ // Index to the active context
+ unsigned int contextIndex() const { return my.use; }
+
+ int use(int type, const QString &source, int flags = 0);
+ int use(unsigned int index) { my.use = index; return useContext(); }
+ bool defaultDefined() const { return (numContexts() > 0); }
+ int useDefault();
+
+ void createLocalContext();
+
+ // Add a new metric to the group
+ QmcMetric* addMetric(char const* str, double theScale = 0.0,
+ bool active = false);
+ QmcMetric* addMetric(pmMetricSpec* theMetric, double theScale = 0.0,
+ bool active = false);
+
+ // Fetch all the metrics in this group
+ // By default, do all rate conversions and counter wraps
+ int fetch(bool update = true);
+
+ // Set the archive position and mode
+ int setArchiveMode(int mode, const struct timeval *when, int interval);
+
+ int useTZ(); // Use TZ of current context as default
+ int useTZ(const QString &tz); // Use this TZ as default
+ int useLocalTZ(); // Use local TZ as default
+ void defaultTZ(QString &label, QString &tz);
+
+ TimeZoneFlag defaultTZ() const { return my.tzFlag; }
+ int useDefaultTZ();
+
+ struct timeval const& logStart() const { return my.timeStart; }
+ struct timeval const& logEnd() const { return my.timeEnd; }
+ void updateBounds(); // Determine the archive start and finish times
+
+ void dump(QTextStream &os);
+
+private:
+ struct {
+ QList<QmcContext*> contexts; // List of all contexts in this group
+ bool restrictArchives; // Only one archive per host
+ int mode; // Default context type
+ int use; // Context in use
+ QmcSource *localSource; // Localhost source desc
+
+ TimeZoneFlag tzFlag; // default TZ type
+ int tzDefault; // handle to default TZ
+ int tzUser; // handle to user defined TZ
+ QString tzUserString; // user defined TZ;
+ int tzGroupIndex; // index to group context used for
+ // current timezone
+ struct timeval timeStart; // Start of first archive
+ struct timeval timeEnd; // End of last archive
+ double timeEndReal; // End of last archive
+ } my;
+
+ // Timezone for localhost from environment
+ static bool tzLocalInit; // got TZ from environment
+ static int tzLocal; // handle to environment TZ
+ static QString tzLocalString; // environment TZ string
+ static QString localHost; // name of localhost
+
+ int useContext();
+};
+
+#endif // QMC_GROUP_H
diff --git a/src/libpcp_qmc/src/qmc_indom.cpp b/src/libpcp_qmc/src/qmc_indom.cpp
new file mode 100644
index 0000000..67dd260
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_indom.cpp
@@ -0,0 +1,481 @@
+/*
+ * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "qmc_indom.h"
+#include "qmc_desc.h"
+#include <ctype.h>
+#include <QVector>
+#include <QStringList>
+
+QmcInstance::QmcInstance()
+{
+ my.inst = PM_IN_NULL;
+ my.refCount = 0;
+ my.index = -1;
+ my.active = false;
+}
+
+QmcInstance::QmcInstance(int id, const char* name)
+{
+ my.inst = id;
+ my.name = name;
+ my.refCount = 0;
+ my.index = -1;
+ my.active = true;
+}
+
+void
+QmcInstance::deactivate(int nullIndex)
+{
+ my.inst = PM_IN_NULL;
+ my.name = "";
+ my.refCount = 0;
+ my.index = nullIndex;
+ my.active = false;
+}
+
+QmcInstance const&
+QmcInstance::operator=(QmcInstance const& rhs)
+{
+ if (this != &rhs) {
+ my.inst = rhs.inst();
+ my.name = rhs.name();
+ my.refCount = rhs.refCount();
+ my.index = rhs.index();
+ my.active = rhs.active();
+ }
+ return *this;
+}
+
+QmcIndom::QmcIndom(int type, QmcDesc &desc)
+{
+ int *instList;
+ char **nameList;
+
+ my.type = type;
+ my.id = desc.desc().indom;
+ my.profile = false;
+ my.changed = false;
+ my.updated = true;
+ my.count = 0;
+ my.nullCount = 0;
+ my.nullIndex = UINT_MAX;
+ my.numActive = 0;
+ my.numActiveRef = 0;
+
+ if (my.id == PM_INDOM_NULL)
+ my.status = PM_ERR_INDOM;
+ else if (my.type == PM_CONTEXT_HOST || my.type == PM_CONTEXT_LOCAL)
+ my.status = pmGetInDom(my.id, &instList, &nameList);
+ else if (my.type == PM_CONTEXT_ARCHIVE)
+ my.status = pmGetInDomArchive(my.id, &instList, &nameList);
+ else
+ my.status = PM_ERR_NOCONTEXT;
+
+ if (my.status > 0) {
+ for (int i = 0; i < my.status; i++)
+ my.instances.append(QmcInstance(instList[i], nameList[i]));
+ my.numActive = my.status;
+ free(instList);
+ free(nameList);
+
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::QmcIndom: indom ";
+ }
+ }
+ else if (my.status < 0 && pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::QmcIndom: unable to lookup "
+ << pmInDomStr(my.id) << " from "
+ << (my.type == PM_CONTEXT_ARCHIVE ? "archive" : "host/local")
+ << " source: " << pmErrStr(my.status) << endl;
+ }
+}
+
+int
+QmcIndom::lookup(QString const &name)
+{
+ int i;
+ bool ok;
+ QStringList list;
+
+ for (i = 0; i < my.instances.size(); i++) {
+ if (my.instances[i].null())
+ continue;
+ if (my.instances[i].name().compare(name) == 0) {
+ if (my.instances[i].refCount() == 0) {
+ my.profile = true;
+ my.count++;
+ if (my.instances[i].active())
+ my.numActiveRef++;
+ }
+ my.instances[i].refCountInc();
+ return i;
+ }
+ }
+
+ // Match up to the first space
+ // Need this for proc and similiar agents
+
+ for (i = 0; i < my.instances.size(); i++) {
+ if (my.instances[i].null())
+ continue;
+ list = my.instances[i].name().split(QChar(' '));
+ if (list.size() <= 1)
+ continue;
+ if (name.compare(list.at(0)) == 0) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::lookup: inst \"" << name << "\"(" << i
+ << ") matched to \"" << my.instances[i].name() << "\"("
+ << i << ')' << endl;
+ }
+ if (my.instances[i].refCount() == 0) {
+ my.profile = true;
+ my.count++;
+ if (my.instances[i].active())
+ my.numActiveRef++;
+ }
+ my.instances[i].refCountInc();
+ return i;
+ }
+ }
+
+ // If the instance requested is numeric, then ignore leading
+ // zeros in the instance up to the first space
+ int nameNumber = name.toInt(&ok);
+
+ // The requested instance is numeric
+ if (ok) {
+ for (i = 0; i < my.instances.size(); i++) {
+ if (my.instances[i].null())
+ continue;
+
+ list = my.instances[i].name().split(QChar(' '));
+ if (list.size() <= 1)
+ continue;
+ int instNumber = list.at(0).toInt(&ok);
+ if (!ok)
+ continue;
+ if (instNumber == nameNumber) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::lookup: numerical inst \""
+ << name << " matched to \"" << my.instances[i].name()
+ << "\"(" << i << ')' << endl;
+ }
+ if (my.instances[i].refCount() == 0) {
+ my.profile = true;
+ my.count++;
+ if (my.instances[i].active())
+ my.numActiveRef++;
+ }
+ my.instances[i].refCountInc();
+ return i;
+ }
+ }
+ }
+
+ return -1; // we don't know about that instance
+}
+
+void
+QmcIndom::refAll(bool active)
+{
+ my.numActiveRef = 0;
+
+ for (int i = 0; i < my.instances.size(); i++) {
+ if (my.instances[i].null() || (active && !my.instances[i].active()))
+ continue;
+
+ if (my.instances[i].refCount() == 0)
+ my.profile = true;
+ if (my.instances[i].active())
+ my.numActiveRef++;
+
+ my.instances[i].refCountInc();
+ }
+ my.count = my.instances.size() - my.nullCount;
+}
+
+void
+QmcIndom::removeRef(uint index)
+{
+ Q_ASSERT(my.instances[index].refCount());
+
+ my.instances[index].refCountDec();
+ if (my.instances[index].refCount() == 0) {
+ my.profile = true;
+ my.count--;
+ if (my.instances[index].active())
+ my.numActiveRef--;
+ }
+}
+
+int
+QmcIndom::genProfile()
+{
+ int i, j;
+ int sts = 0;
+ int *ptr = NULL;
+ QVector<int> list;
+ const char *action = NULL;
+
+ // If all instances are referenced or there are no instances
+ // then request all instances
+ if (my.numActiveRef == my.numActive || my.numActive == 0) {
+ sts = pmAddProfile(my.id, 0, NULL);
+ action = "ALL";
+ }
+ // If the number of referenced instances is less than the number
+ // of unreferenced active instances, then the smallest profile
+ // is to add all the referenced instances
+ else if (my.count < (my.numActive - my.numActiveRef)) {
+ action = "ADD";
+ sts = pmDelProfile(my.id, 0, NULL);
+ if (sts >= 0) {
+ list.resize(my.count);
+ for (i = 0, j = 0; i < my.instances.size(); i++)
+ if (!my.instances[i].null() && my.instances[i].refCount())
+ list[j++] = my.instances[i].inst();
+ ptr = list.data();
+ sts = pmAddProfile(my.id, list.size(), ptr);
+ }
+ }
+ // Delete those active instances that are not referenced
+ else {
+ action = "DELETE";
+ sts = pmAddProfile(my.id, 0, NULL);
+ if (sts >= 0) {
+ list.resize(my.instances.size() - my.count);
+ for (i = 0, j = 0; i < my.instances.size(); i++)
+ if (!my.instances[i].null() &&
+ my.instances[i].refCount() == 0 &&
+ my.instances[i].active())
+ list[j++] = my.instances[i].inst();
+ ptr = list.data();
+ sts = pmDelProfile(my.id, list.size(), ptr);
+ }
+ }
+
+ if (pmDebug & (DBG_TRACE_PMC | DBG_TRACE_INDOM | DBG_TRACE_PROFILE)) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::genProfile: id = " << my.id << ", count = "
+ << my.count << ", numInsts = " << numInsts() << ", active = "
+ << my.numActive << ", activeRef = " << my.numActiveRef
+ << ": " << action << " ptr = " << ptr;
+ if (sts < 0)
+ cerr << ", sts = " << sts << ": " << pmErrStr(sts);
+ cerr << endl;
+ }
+
+ if (sts >= 0)
+ my.profile = false;
+ return sts;
+}
+
+void
+QmcIndom::dump(QTextStream &os) const
+{
+ os << pmInDomStr(my.id) << ": " << numInsts() << " instances ("
+ << my.nullCount << " NULL)" << endl;
+ for (int i = 0; i < my.instances.size(); i++)
+ if (!my.instances[i].null())
+ os << " [" << my.instances[i].inst() << "] = \""
+ << my.instances[i].name() << "\" ("
+ << my.instances[i].refCount() << " refs) "
+ << (my.instances[i].active() ? "active" : "inactive") << endl;
+ else
+ os << " NULL -> " << my.instances[i].index() << endl;
+}
+
+int
+QmcIndom::update()
+{
+ int *instList;
+ char **nameList;
+ int i, j, count;
+ int oldLen = my.instances.size();
+ uint oldNullCount = my.nullCount;
+ int sts = 0;
+
+ // If the indom has already been updated, just check that all instances
+ // are referenced and remove any that have gone away.
+ if (!my.changed || my.updated) {
+ for (i = 0; i < oldLen; i++) {
+ QmcInstance &inst = my.instances[i];
+ if (inst.refCount() || inst.null() || inst.active())
+ continue;
+ inst.deactivate(my.nullIndex);
+ my.nullIndex = i;
+ my.nullCount++;
+ my.profile = true;
+ }
+ if (pmDebug & DBG_TRACE_INDOM && my.nullCount != oldNullCount) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::update: Cleaning indom " << pmInDomStr(my.id)
+ << ": Removed " << my.nullCount - oldNullCount
+ << " instances" << endl;
+ }
+ return 0;
+ }
+
+ my.updated = true;
+
+ if (my.type == PM_CONTEXT_ARCHIVE)
+ return 0;
+
+ if (my.type == PM_CONTEXT_HOST || my.type == PM_CONTEXT_LOCAL)
+ sts = pmGetInDom(my.id, &instList, &nameList);
+
+ my.numActive = 0;
+ my.numActiveRef = 0;
+
+ if (sts > 0) {
+ count = sts;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::update: Updating indom " << pmInDomStr(my.id)
+ << ": Got " << count << " instances (vs " << numInsts()
+ << ")" << endl;
+ }
+
+ // Any instances which are not in the new indom AND are not
+ // referenced can be removed
+ for (i = 0; i < oldLen; i++) {
+ QmcInstance &inst = my.instances[i];
+ inst.setActive(false);
+
+ if (inst.refCount() || inst.null())
+ continue;
+ j = 0;
+ if (i < count && inst.inst() == instList[i]) {
+ if (inst.name().compare(nameList[i]) == 0)
+ continue;
+ else
+ j = count;
+ }
+ for (; j < count; j++) {
+ if (inst.inst() == instList[j]) {
+ if (inst.name().compare(nameList[j]) == 0)
+ break;
+ else
+ j = count;
+ }
+ }
+
+ // If j >= count, then instance i has either changed or gone away
+ if (j >= count) {
+ inst.deactivate(my.nullIndex);
+ my.nullIndex = i;
+ my.nullCount++;
+ my.profile = true;
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ // Quick check to see if they are the same
+ if (i < my.instances.size() &&
+ my.instances[i].inst() == instList[i] &&
+ my.instances[i].name().compare(nameList[i]) == 0) {
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::update: Unchanged \"" << nameList[i]
+ << "\"(" << instList[i] << ')' << endl;
+ }
+ my.instances[i].setActive(true);
+ my.numActive++;
+ if (my.instances[i].refCount())
+ my.numActiveRef++;
+ continue;
+ }
+
+ for (j = 0; j < oldLen; j++) {
+ if (my.instances[j].null())
+ continue;
+
+ if (my.instances[j].inst() == instList[i]) {
+ // Same instance and same external name but different
+ // order, mark as active. If it has a different
+ // external name just ignore it
+ if (my.instances[j].name().compare(nameList[i]) == 0) {
+ my.instances[j].setActive(true);
+ my.numActive++;
+ if (my.instances[j].refCount())
+ my.numActiveRef++;
+ }
+ else if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::update: Ignoring \""
+ << nameList[i]
+ << "\" with identical internal identifier ("
+ << instList[i] << ")" << endl;
+ }
+ break;
+ }
+ }
+
+ if (j == oldLen) {
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ cerr << "QmcIndom::update: Adding \"" << nameList[i]
+ << "\"(" << instList[i] << ")" << endl;
+ }
+ if (my.nullCount) {
+ uint newindex = my.instances[my.nullIndex].index();
+ my.instances[my.nullIndex] = QmcInstance(instList[i],
+ nameList[i]);
+ my.nullIndex = newindex;
+ my.nullCount--;
+ }
+ else
+ my.instances.append(QmcInstance(instList[i], nameList[i]));
+ my.profile = true;
+ my.numActive++;
+ }
+ }
+
+ free(instList);
+ free(nameList);
+
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ if (my.instances.size() == oldLen && my.nullCount == oldNullCount)
+ cerr << "QmcIndom::update: indom size unchanged" << endl;
+ else {
+ cerr << "QmcIndom::update: indom changed from "
+ << oldLen - oldNullCount << " to " << numInsts() << endl;
+ dump(cerr);
+ }
+ }
+ }
+ else {
+ for (i = 0; i < my.instances.size(); i++)
+ my.instances[i].setActive(false);
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ if (sts == 0)
+ cerr << "QmcIndom::update: indom empty!" << endl;
+ else
+ cerr << "QmcIndom::update: unable to lookup "
+ << pmInDomStr(my.id) << " from host/local source: "
+ << pmErrStr(sts) << endl;
+ }
+ }
+
+ return sts;
+}
diff --git a/src/libpcp_qmc/src/qmc_indom.h b/src/libpcp_qmc/src/qmc_indom.h
new file mode 100644
index 0000000..265f3ad
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_indom.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1997-2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+#ifndef QMC_INDOM_H
+#define QMC_INDOM_H
+
+#include "qmc.h"
+
+#include <qlist.h>
+#include <qstring.h>
+#include <qtextstream.h>
+
+class QmcInstance
+{
+public:
+ QmcInstance();
+ QmcInstance(int id, const char* name);
+ QmcInstance const& operator=(QmcInstance const&);
+
+ void deactivate(int index);
+ int inst() const { return my.inst; }
+ bool null() const { return (my.inst == (int)PM_IN_NULL); }
+ QString name() const { return my.name; }
+ int refCount() const { return my.refCount; }
+ int refCountInc() { return ++my.refCount; }
+ int refCountDec() { return --my.refCount; }
+ bool active() const { return my.active; }
+ void setActive(bool active) { my.active = active; }
+ int index() const { return my.index; }
+ void setIndex(int index) { my.index = index; }
+
+private:
+ struct {
+ int inst; // Instance internal id
+ QString name; // Instance external id
+ int refCount;
+ int index; // Index into pmResult of last fetch
+ // May also be used to index the next NULL inst
+ bool active; // Instance was listed in last indom lookup
+ } my;
+};
+
+class QmcIndom
+{
+public:
+ QmcIndom(int type, QmcDesc &desc);
+
+ int status() const { return my.status; }
+ int id() const { return my.id; }
+
+ int numInsts() const { return my.instances.size() - my.nullCount; }
+ int numActiveInsts() const { return my.numActive; }
+
+ // Length of instance list - some of the instances may be NULL hence
+ // this may be larger than numInsts()
+ int listLen() const { return my.instances.size(); }
+
+ // Internal instance id for instance <index>
+ int inst(uint index) const { return my.instances[index].inst(); }
+
+ // External instance name for instance <index>
+ const QString name(uint index) const { return my.instances[index].name(); }
+
+ bool nullInst(uint index) const { return my.instances[index].null(); }
+
+ // Was this instance listed in the last update?
+ bool activeInst(uint index) const { return my.instances[index].active(); }
+
+ // Is this instance referenced by any metrics?
+ bool refInst(uint index) const { return my.instances[index].refCount()>0; }
+
+ // Return index into table for instance with external <name>
+ // Also adds a reference to this instance for the profile
+ int lookup(QString const& name);
+
+ // Add a reference to all instances in this indom
+ // Id <active> is set, only reference active instances
+ void refAll(bool active = false);
+
+ // Remove a reference to an instance
+ void removeRef(uint index);
+
+ // Number of instances referenced
+ uint refCount() const { return my.count; }
+
+ // Was the indom different on the last fetch?
+ bool changed() const { return my.changed; }
+
+ // About to fetch, so mark this indom as unchanged
+ void newFetch() { my.changed = false; my.updated = true; }
+
+ // Mark that the indom was different in a previous fetch
+ void hasChanged() { my.changed = true; my.updated = false; }
+
+ int update(); // Update indom with latest instances
+
+ // Has profile changed since last call to genProfile
+ bool diffProfile() const { return my.profile; }
+
+ int genProfile(); // Generate profile for current context
+
+ // Likely index into pmResult for instance <inst>
+ int index(uint inst) const { return my.instances[inst].index(); }
+ void setIndex(uint inst, int index) { my.instances[inst].setIndex(index); }
+
+ // Dump some debugging output for this indom
+ void dump(QTextStream &os) const;
+
+private:
+ struct {
+ int status;
+ int type;
+ pmInDom id;
+ QList<QmcInstance> instances; // Sparse list of instances
+ bool profile; // Does the profile need to be updated
+ bool changed; // Did indom change in the last fetch?
+ bool updated; // Has the indom been updated?
+ uint count; // Number of referenced instances
+ uint nullCount; // Count of NULL instances
+ uint nullIndex; // Index to first NULL instance
+ uint numActive; // Number of active instances
+ uint numActiveRef; // Number of active referenced insts
+ } my;
+};
+
+#endif // QMC_INDOM_H
diff --git a/src/libpcp_qmc/src/qmc_metric.cpp b/src/libpcp_qmc/src/qmc_metric.cpp
new file mode 100644
index 0000000..85d5016
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_metric.cpp
@@ -0,0 +1,1248 @@
+/*
+ * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <strings.h>
+#include "qmc_metric.h"
+#include "qmc_group.h"
+
+QmcMetricValue::QmcMetricValue()
+{
+ my.value = 0.0;
+ my.currentValue = 0.0;
+ my.previousValue = 0.0;
+ my.error = PM_ERR_VALUE;
+ my.currentError = PM_ERR_VALUE;
+ my.previousError = PM_ERR_VALUE;
+ my.instance = PM_ERR_INST;
+}
+
+QmcMetricValue const&
+QmcMetricValue::operator=(QmcMetricValue const& rhs)
+{
+ if (this != &rhs) {
+ my.instance = rhs.my.instance;
+ my.value = rhs.my.value;
+ my.currentValue = rhs.my.currentValue;
+ my.previousValue = rhs.my.previousValue;
+ my.stringValue = rhs.my.stringValue;
+ my.error = rhs.my.error;
+ my.currentError = rhs.my.currentError;
+ my.previousError = rhs.my.previousError;
+ }
+ return *this;
+}
+
+QmcMetric::QmcMetric(QmcGroup *group, const char *string,
+ double scale, bool active)
+{
+ pmMetricSpec *metricSpec;
+ char *msg;
+
+ my.status = 0;
+ my.group = group;
+ my.scale = scale;
+ my.idIndex = UINT_MAX;
+ my.indomIndex = UINT_MAX;
+ my.contextIndex = UINT_MAX;
+ my.explicitInst = false;
+ my.active = active;
+
+ my.status = pmParseMetricSpec(string, 0, NULL, &metricSpec, &msg);
+ if (my.status < 0) {
+ pmprintf("%s: Error: Unable to parse metric spec:\n%s\n",
+ pmProgname, msg);
+ my.name = QString::null;
+ free(msg);
+ }
+ else {
+ my.name = QString(metricSpec->metric);
+ setup(group, metricSpec);
+ free(metricSpec);
+ }
+}
+
+QmcMetric::QmcMetric(QmcGroup *group, pmMetricSpec *metricSpec,
+ double scale, bool active)
+{
+ my.pmid = PM_ID_NULL;
+ my.status = 0;
+ my.name = QString(metricSpec->metric);
+ my.group = group;
+ my.scale = scale;
+ my.contextIndex = UINT_MAX;
+ my.idIndex = 0;
+ my.indomIndex = UINT_MAX;
+ my.explicitInst = false;
+ my.active = active;
+ setup(group, metricSpec);
+}
+
+void
+QmcMetric::setup(QmcGroup* group, pmMetricSpec *metricSpec)
+{
+ if (my.status >= 0)
+ setupDesc(group, metricSpec);
+ if (my.status >= 0)
+ setupIndom(metricSpec);
+ if (my.status < 0)
+ return;
+ if (pmDebug & DBG_TRACE_PMC)
+ dumpAll();
+}
+
+QmcMetric::~QmcMetric()
+{
+ if (hasInstances())
+ for (int i = 0; i < my.values.size(); i++)
+ indom()->removeRef(my.values[i].instance());
+}
+
+void
+QmcMetric::setupDesc(QmcGroup* group, pmMetricSpec *metricSpec)
+{
+ int contextType = PM_CONTEXT_HOST;
+ int descType;
+ char *src = NULL;
+ char *name = NULL;
+
+ if (metricSpec->isarch == 1)
+ contextType = PM_CONTEXT_ARCHIVE;
+ else if (metricSpec->isarch == 2)
+ contextType = PM_CONTEXT_LOCAL;
+
+ QString source = QString(metricSpec->source);
+ my.status = group->use(contextType, source);
+
+ if (my.status >= 0) {
+ my.contextIndex = group->contextIndex();
+ contextType = context()->source().type();
+ my.status = context()->lookupPMID(metricSpec->metric, my.pmid);
+ if (my.status >= 0)
+ my.status = context()->lookupInDom(my.pmid, my.indomIndex);
+ if (my.status < 0) {
+ name = strdup(nameAscii());
+ src = strdup(context()->source().sourceAscii());
+ pmprintf("%s: Error: %s%c%s: %s\n",
+ pmProgname,
+ contextType == PM_CONTEXT_LOCAL ? "@" : src,
+ contextType == PM_CONTEXT_ARCHIVE ? '/' : ':',
+ name, pmErrStr(my.status));
+ }
+ }
+ else {
+ // do nothing, error already reported via pmprintf from
+ // QmcGroup::use()
+ ;
+ }
+
+ if (my.status >= 0) {
+ descType = desc().desc().type;
+ if (descType == PM_TYPE_NOSUPPORT) {
+ my.status = PM_ERR_CONV;
+ name = strdup(nameAscii());
+ src = strdup(context()->source().sourceAscii());
+ pmprintf("%s: Error: %s%c%s is not supported on %s\n",
+ pmProgname, contextType == PM_CONTEXT_LOCAL ? "@" : src,
+ (contextType == PM_CONTEXT_ARCHIVE ? '/' : ':'),
+ name, context()->source().hostAscii());
+ }
+ else if (descType == PM_TYPE_AGGREGATE ||
+ descType == PM_TYPE_AGGREGATE_STATIC ||
+ descType == PM_TYPE_UNKNOWN) {
+ my.status = PM_ERR_CONV;
+ name = strdup(nameAscii());
+ src = strdup(context()->source().sourceAscii());
+ pmprintf("%s: Error: %s%c%s has type \"%s\","
+ " which is not a number or a string\n",
+ pmProgname, contextType == PM_CONTEXT_LOCAL ? "@" : src,
+ (contextType == PM_CONTEXT_ARCHIVE ? '/' : ':'),
+ name, pmTypeStr(descType));
+ }
+ }
+
+ if (name)
+ free(name);
+ if (src)
+ free(src);
+}
+
+void
+QmcMetric::setupIndom(pmMetricSpec *metricSpec)
+{
+ int i, j;
+ QmcIndom *indomPtr = indom();
+
+ if (!hasIndom()) {
+ if (metricSpec->ninst > 0) {
+ my.status = PM_ERR_INST;
+ dumpErr(metricSpec->inst[0]);
+ }
+ else
+ setupValues(1);
+ }
+ else if (metricSpec->ninst) {
+ Q_ASSERT(hasInstances());
+ setupValues(metricSpec->ninst);
+
+ for (i = 0 ; i < metricSpec->ninst && my.status >= 0; i++) {
+ j = indomPtr->lookup(metricSpec->inst[i]);
+ if (j >= 0)
+ my.values[i].setInstance(j);
+ else {
+ my.status = PM_ERR_INST;
+ my.values[i].setInstance(PM_ERR_INST);
+ dumpErr(metricSpec->inst[i]);
+ }
+ }
+ my.explicitInst = true;
+ }
+ else {
+ Q_ASSERT(hasInstances());
+
+ if (my.active) {
+ setupValues(indomPtr->numActiveInsts());
+ indomPtr->refAll(my.active);
+
+ for (i = 0, j = 0; i < indomPtr->listLen(); i++)
+ if (!indomPtr->nullInst(i) && indomPtr->activeInst(i))
+ my.values[j++].setInstance(i);
+ }
+ else {
+ setupValues(indomPtr->numInsts());
+ indomPtr->refAll(my.active);
+
+ for (i = 0, j = 0; i < indomPtr->listLen(); i++)
+ if (!indomPtr->nullInst(i))
+ my.values[j++].setInstance(i);
+ }
+ }
+}
+
+void
+QmcMetric::setupValues(int num)
+{
+ int i, oldLen = my.values.size();
+
+ if (num == 0)
+ my.values.clear();
+ else {
+ if (my.values.size() > num)
+ for (i = num; i < my.values.size(); i++)
+ my.values.removeAt(i);
+ for (i = oldLen; i < num; i++)
+ my.values.append(QmcMetricValue());
+ }
+}
+
+QString
+QmcMetric::spec(bool srcFlag, bool instFlag, uint instance) const
+{
+ QString str;
+ int i, len = 4;
+
+ if (srcFlag)
+ len += context()->source().source().size();
+ len += name().size();
+ if (hasInstances() && instFlag) {
+ if (instance != UINT_MAX)
+ len += instName(instance).size() + 2;
+ else
+ for (i = 0; i < numInst(); i++)
+ len += instName(i).size() + 4;
+ }
+
+ if (srcFlag) {
+ str.append(context()->source().source());
+ if (context()->source().type() == PM_CONTEXT_ARCHIVE)
+ str.append(QChar('/'));
+ else
+ str.append(QChar(':'));
+ }
+ str.append(name());
+ if (hasInstances() && instFlag) {
+ str.append(QChar('['));
+ str.append(QChar('\"'));
+ if (instance != UINT_MAX)
+ str.append(instName(instance));
+ else if (numInst()) {
+ str.append(instName(0));
+ for (i = 1; i < numInst(); i++) {
+ str.append("\", \"");
+ str.append(instName(i));
+ }
+ }
+ str.append("\"]");
+ }
+
+ return str;
+}
+
+void
+QmcMetric::dumpSource(QTextStream &os) const
+{
+ switch(context()->source().type()) {
+ case PM_CONTEXT_LOCAL:
+ os << "@:";
+ break;
+ case PM_CONTEXT_HOST:
+ os << context()->source().source() << ':';
+ break;
+ case PM_CONTEXT_ARCHIVE:
+ os << context()->source().source() << '/';
+ break;
+ }
+}
+
+void
+QmcMetric::dumpValue(QTextStream &stream, uint inst) const
+{
+ if (error(inst) < 0)
+ stream << pmErrStr(error(inst));
+ else if (!real())
+ stream << stringValue(inst);
+ else if (!event())
+ stream << value(inst) << " " << desc().units();
+}
+
+void
+QmcMetric::dump(QTextStream &stream, bool srcFlag, uint instance) const
+{
+ if (event())
+ dumpEventMetric(stream, srcFlag, instance);
+ else
+ dumpSampledMetric(stream, srcFlag, instance);
+}
+
+void
+QmcMetric::dumpSampledMetric(QTextStream &stream, bool srcFlag, uint instance) const
+{
+ Q_ASSERT(!event());
+
+ stream << name();
+
+ if (srcFlag == true)
+ dumpSource(stream);
+
+ if (my.status < 0)
+ stream << ": " << pmErrStr(my.status) << endl;
+ else if (hasInstances()) {
+ if (instance == UINT_MAX) {
+ if (numInst() == 1)
+ stream << ": 1 instance";
+ else
+ stream << ": " << numInst() << " instances";
+ if (indom()->changed())
+ stream << " (indom has changed)";
+ stream << endl;
+
+ for (int i = 0; i < numInst(); i++) {
+ stream << " [" << instID(i) << " or \"" << instName(i)
+ << "\" (" << my.values[i].instance() << ")] = ";
+ dumpValue(stream, i);
+ stream << endl;
+ }
+ }
+ else {
+ stream << '[' << instID(instance) << " or \"" << instName(instance)
+ << "\" (" << my.values[instance].instance() << ")] = ";
+ dumpValue(stream, instance);
+ stream << endl;
+ }
+ }
+ else {
+ stream << " = ";
+ dumpValue(stream, 0);
+ stream << endl;
+ }
+}
+
+QTextStream&
+operator<<(QTextStream &stream, const QmcMetric &metric)
+{
+ metric.dumpSource(stream);
+ stream << metric.name();
+ if (metric.numInst()) {
+ stream << "[\"" << metric.instName(0);
+ for (int i = 1; i < metric.numValues(); i++)
+ stream << "\", \"" << metric.instName(i);
+ stream << "\"]";
+ }
+ return stream;
+}
+
+void
+QmcMetric::setScaleUnits(pmUnits const& units)
+{
+ QmcContext *context = my.group->context(my.contextIndex);
+ QmcDesc &desc = context->desc(my.pmid);
+ desc.setScaleUnits(units);
+}
+
+int
+QmcMetric::update()
+{
+ uint i, err = 0;
+ uint num = numValues();
+ int sts;
+ pmAtomValue ival, oval;
+ double delta = context()->timeDelta();
+ static int wrap = -1;
+
+ if (num == 0 || my.status < 0)
+ return my.status;
+
+ // PCP_COUNTER_WRAP in environment enables "counter wrap" logic
+ if (wrap == -1)
+ wrap = (getenv("PCP_COUNTER_WRAP") != NULL);
+
+ for (i = 0; i < num; i++) {
+ my.values[i].setError(my.values[i].currentError());
+ if (my.values[i].error() < 0)
+ err++;
+ if (pmDebug & DBG_TRACE_VALUE) {
+ QTextStream cerr(stderr);
+ if (my.values[i].error() < 0)
+ cerr << "QmcMetric::update: " << spec(true, true, i)
+ << ": " << pmErrStr(my.values[i].error()) << endl;
+ }
+ }
+
+ if (!real())
+ return err;
+
+ if (desc().desc().sem == PM_SEM_COUNTER) {
+ for (i = 0; i < num; i++) {
+ QmcMetricValue& value = my.values[i];
+
+ if (value.error() < 0) { // we already know we
+ value.setValue(0.0); // don't have this value
+ continue;
+ }
+ if (value.previousError() < 0) { // we need two values
+ value.setValue(0.0); // for a rate
+ value.setError(value.previousError());
+ err++;
+ if (pmDebug & DBG_TRACE_VALUE) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::update: Previous: "
+ << spec(true, true, i) << ": "
+ << pmErrStr(value.error()) << endl;
+ }
+ continue;
+ }
+
+ value.setValue(value.currentValue() - value.previousValue());
+
+ // wrapped going forwards
+ if (value.value() < 0 && delta > 0) {
+ if (wrap) {
+ switch(desc().desc().type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ value.addValue((double)UINT_MAX+1);
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ value.addValue((double)ULONGLONG_MAX+1);
+ break;
+ }
+ }
+ else { // counter not monotonic
+ value.setValue(0.0); // increasing
+ value.setError(PM_ERR_VALUE);
+ err++;
+ continue;
+ }
+ }
+ // wrapped going backwards
+ else if (value.value() > 0 && delta < 0) {
+ if (wrap) {
+ switch(desc().desc().type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ value.subValue((double)UINT_MAX+1);
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ value.subValue((double)ULONGLONG_MAX+1);
+ break;
+ }
+ }
+ else { // counter not monotonic
+ value.setValue(0.0); // increasing
+ value.setError(PM_ERR_VALUE);
+ err++;
+ continue;
+ }
+ }
+
+ if (delta != 0) // sign of delta and v
+ value.divValue(delta); // should be the same
+ else
+ value.setValue(0.0); // nothing can have happened
+ }
+ }
+ else {
+ for (i = 0; i < num; i++) {
+ QmcMetricValue& value = my.values[i];
+ if (value.error() < 0)
+ value.setValue(0.0);
+ else
+ value.setValue(value.currentValue());
+ }
+ }
+
+ if (my.scale != 0.0) {
+ for (i = 0; i < num; i++) {
+ if (my.values[i].error() >= 0)
+ my.values[i].divValue(my.scale);
+ }
+ }
+
+ if (desc().useScaleUnits()) {
+ for (i = 0; i < num; i++) {
+ if (my.values[i].error() < 0)
+ continue;
+ ival.d = my.values[i].value();
+ pmUnits units = desc().desc().units;
+ sts = pmConvScale(PM_TYPE_DOUBLE, &ival, &units,
+ &oval, (pmUnits *)&(desc().scaleUnits()));
+ if (sts < 0)
+ my.values[i].setError(sts);
+ else {
+ my.values[i].setValue(oval.d);
+ if (pmDebug & DBG_TRACE_VALUE) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::update: scaled " << my.name
+ << " from " << ival.d << " to " << oval.d
+ << endl;
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+void
+QmcMetric::dumpAll() const
+{
+ QTextStream cerr(stderr);
+ cerr << *this << " from " << context()->source().desc()
+ << " with scale = " << my.scale << " and units = " << desc().units()
+ << endl;
+}
+
+void
+QmcMetric::dumpErr() const
+{
+ pmprintf("%s: Error: %s: %s\n", pmProgname,
+ (const char *)spec(true).toAscii(), pmErrStr(my.status));
+}
+
+// Instance list may not be valid, so pass inst as a string rather than
+// as an index
+
+void
+QmcMetric::dumpErr(const char *inst) const
+{
+ pmprintf("%s: Error: %s[%s]: %s\n", pmProgname,
+ (const char *)spec(true).toAscii(), inst, pmErrStr(my.status));
+}
+
+const char *
+QmcMetric::formatNumber(double value)
+{
+ static char buf[8];
+
+ if (value >= 0.0) {
+ if (value > 99950000000000.0)
+ strcpy(buf, " inf?");
+ else if (value > 99950000000.0)
+ sprintf(buf, "%5.2fT", value / 1000000000000.0);
+ else if (value > 99950000.0)
+ sprintf(buf, "%5.2fG", value / 1000000000.0);
+ else if (value > 99950.0)
+ sprintf(buf, "%5.2fM", value / 1000000.0);
+ else if (value > 99.95)
+ sprintf(buf, "%5.2fK", value / 1000.0);
+ else if (value > 0.005)
+ sprintf(buf, "%5.2f ", value);
+ else
+ strcpy(buf, " 0.00 ");
+ }
+ else {
+ if (value < -9995000000000.0)
+ strcpy(buf, " -inf?");
+ else if (value < -9995000000.0)
+ sprintf(buf, "%.2fT", value / 1000000000000.0);
+ else if (value < -9995000.0)
+ sprintf(buf, "%.2fG", value / 1000000000.0);
+ else if (value < -9995.0)
+ sprintf(buf, "%.2fM", value / 1000000.0);
+ else if (value < -9.995)
+ sprintf(buf, "%.2fK", value / 1000.0);
+ else if (value < -0.005)
+ sprintf(buf, "%.2f ", value);
+ else
+ strcpy(buf, " 0.00 ");
+ }
+
+ return buf;
+}
+
+void
+QmcMetric::shiftValues()
+{
+ for (int i = 0; i < my.values.size(); i++)
+ my.values[i].shiftValues();
+}
+
+void
+QmcMetric::setError(int sts)
+{
+ for (int i = 0; i < numValues(); i++) {
+ QmcMetricValue &value = my.values[i];
+ value.setCurrentError(sts);
+ }
+}
+
+void
+QmcMetric::extractValues(pmValueSet const* set)
+{
+ int i, j, index, inst;
+ pmValue const *value = NULL;
+ bool found;
+ QmcIndom *indomPtr = indom();
+
+ Q_ASSERT(set->pmid == desc().id());
+
+ if (set->numval > 0) {
+ if (hasIndom()) {
+ // If the number of instances are not the expected number
+ // then mark the indom as changed
+ if (!my.explicitInst && (my.values.size() != set->numval)) {
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::extractValues: implicit indom "
+ << pmInDomStr(indomPtr->id()) << " changed ("
+ << set->numval << " != " << my.values.size() << ')'
+ << endl;
+ }
+ indomPtr->hasChanged();
+ updateIndom();
+ }
+
+ for (i = 0; i < numInst(); i++) {
+ QmcMetricValue &valueRef = my.values[i];
+ inst = my.values[i].instance();
+ index = indomPtr->index(inst);
+ found = false;
+
+ // If the index is within range, try it first
+ if (index >= 0 && index < set->numval) {
+ value = &(set->vlist[index]);
+
+ // Found it in the same spot as last time
+ if (value->inst == indomPtr->inst(inst))
+ found = true;
+ }
+
+ // Search for it from the top
+ for (j = 0; found == false && j < set->numval; j++) {
+ if (set->vlist[j].inst == indomPtr->inst(inst)) {
+ index = j;
+ value = &(set->vlist[j]);
+ indomPtr->setIndex(inst, j);
+ found = true;
+ }
+ }
+
+ if (found) {
+ if (real())
+ extractNumericMetric(set, value, valueRef);
+ else if (!event())
+ extractArrayMetric(set, value, valueRef);
+ else
+ extractEventMetric(set, index, valueRef);
+ }
+ else { // Cannot find it
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::extractValues: "
+ << spec(true, true, i) << ": "
+ << pmErrStr(PM_ERR_VALUE) << endl;
+ }
+
+ if (valueRef.previousError() != PM_ERR_VALUE)
+ indomPtr->hasChanged();
+
+ valueRef.setCurrentError(PM_ERR_VALUE);
+ }
+ }
+ }
+ else if (set->numval == 1) {
+ // We have no instances at this point in time
+ if (my.values.size() == 0 && hasInstances())
+ indomPtr->hasChanged();
+ else {
+ QmcMetricValue &valueRef = my.values[0];
+ value = &(set->vlist[0]);
+
+ if (real())
+ extractNumericMetric(set, value, valueRef);
+ else if (!event())
+ extractArrayMetric(set, value, valueRef);
+ else
+ extractEventMetric(set, 0, valueRef);
+ }
+ }
+ else { // Did not expect any instances
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::extractValues: " << spec(true)
+ << " is a singular metric but result contained "
+ << set->numval << " values" << endl;
+ }
+ setError(PM_ERR_VALUE);
+ }
+ }
+ else if (set->numval == 0) {
+ if (!(hasInstances() && numInst() == 0)) {
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::extractValues: numval == 0: "
+ << spec(true, false) << ": " << pmErrStr(PM_ERR_VALUE)
+ << endl;
+ }
+ setError(PM_ERR_VALUE);
+ if (hasInstances())
+ indomPtr->hasChanged();
+ }
+ }
+ else {
+ if (pmDebug & DBG_TRACE_OPTFETCH) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::extractValues: numval < 0: "
+ << spec(true, false)
+ << ": " << pmErrStr(set->numval) << endl;
+ }
+ setError(set->numval);
+ if (hasInstances())
+ indomPtr->hasChanged();
+ }
+}
+
+/*
+ * Display-able aggregate (for diagnostics) - up to
+ * first 16 characters displayed. Uses a similar
+ * approach to that taken in pmPrintValue().
+ */
+void
+QmcMetric::aggregateAsString(pmValue const *vp, char *buffer, int buflen)
+{
+ char *p = &vp->value.pval->vbuf[0];
+
+ memset(buffer, '.', buflen);
+ for (int i = 0; i < (buflen/2)-1; i++, p++) {
+ if (i < vp->value.pval->vlen - PM_VAL_HDR_SIZE)
+ sprintf(buffer + (i*2), "%02x", *p & 0xff);
+ }
+ buffer[buflen-1] = '\0';
+}
+
+void
+QmcMetric::extractArrayMetric(pmValueSet const *set, pmValue const *vp, QmcMetricValue &valueRef)
+{
+ pmAtomValue result;
+ int sts, type = desc().desc().type;
+
+ if (aggregate(type)) {
+ char buffer[32];
+ aggregateAsString(vp, buffer, sizeof(buffer));
+ valueRef.setStringValue(buffer);
+ }
+ else if ((sts = pmExtractValue(set->valfmt, vp,
+ type, &result, PM_TYPE_STRING)) >= 0) {
+ valueRef.setStringValue(result.cp);
+ if (result.cp)
+ free(result.cp);
+ }
+ else {
+ valueRef.setCurrentError(sts);
+ }
+}
+
+void
+QmcMetric::extractNumericMetric(pmValueSet const *set, pmValue const *value, QmcMetricValue &valueRef)
+{
+ pmAtomValue result;
+ int sts;
+
+ if ((sts = pmExtractValue(set->valfmt, value,
+ desc().desc().type, &result, PM_TYPE_DOUBLE)) >= 0)
+ valueRef.setCurrentValue(result.d);
+ else
+ valueRef.setCurrentError(sts);
+}
+
+void
+QmcMetricValue::extractEventRecords(QmcContext *context, int recordCount, pmResult **result)
+{
+ pmID parameterID;
+ pmID missedID = QmcEventRecord::eventMissed();
+ pmID flagsID = QmcEventRecord::eventFlags();
+ int i, p, r, parameterCount;
+
+ my.eventRecords.resize(recordCount);
+
+ for (r = 0; r < recordCount; r++) {
+ QmcEventRecord &record = my.eventRecords[r];
+
+ // count is the size of my.parameter vector (less flags/missed)
+ parameterCount = 0;
+ for (i = 0; i < result[r]->numpmid; i++) {
+ parameterID = result[r]->vset[i]->pmid;
+ if (parameterID != flagsID && parameterID != missedID)
+ parameterCount++;
+ }
+
+ // initialise this record
+ record.setTimestamp(&result[r]->timestamp);
+ record.setParameterCount(parameterCount);
+ record.setMissed(0);
+ record.setFlags(0);
+
+ // i indexes into result[r], p indexes into my.parameter vector
+ for (i = p = 0; i < result[r]->numpmid; i++) {
+ pmValueSet *valueSet = result[r]->vset[i];
+ parameterID = valueSet->pmid;
+ if (parameterID == flagsID)
+ record.setFlags(valueSet->vlist[0].value.lval);
+ else if (parameterID == missedID)
+ record.setMissed(valueSet->vlist[0].value.lval);
+ else
+ record.setParameter(p++, parameterID, context, valueSet);
+ }
+
+ if (pmDebug & DBG_TRACE_VALUE) {
+ QTextStream cerr(stderr);
+ pmValueSet *valueSet = result[r]->vset[0];
+ record.dump(cerr, valueSet->vlist[0].inst, r);
+ }
+ }
+}
+
+void
+QmcMetric::extractEventMetric(pmValueSet const *valueSet, int index, QmcMetricValue &valueRef)
+{
+ pmValueSet *values = (pmValueSet *)valueSet;
+ pmResult **result;
+ int sts;
+
+ if ((sts = pmUnpackEventRecords(values, index, &result)) >= 0) {
+ valueRef.extractEventRecords(context(), sts, result);
+ pmFreeEventResult(result);
+ }
+ else {
+ valueRef.setCurrentError(sts);
+ }
+}
+
+int
+QmcEventRecord::setParameter(int index, pmID pmid, QmcContext *context, pmValueSet const *vsp)
+{
+ QString *name;
+ QmcDesc *desc;
+ QmcIndom *indom;
+ int sts, type;
+
+ if (vsp->numval <= 0) // no value or an error
+ return vsp->numval;
+
+ if ((sts = context->lookup(pmid, &name, &desc, &indom)) < 0)
+ return sts;
+
+ QmcEventParameter &parameter = my.parameters[index];
+ parameter.setPMID(pmid);
+ parameter.setNamePtr(name);
+ parameter.setDescPtr(desc);
+ parameter.setIndomPtr(indom);
+ parameter.setValueCount(vsp->numval);
+
+ type = desc->desc().type;
+ for (int i = 0; i < vsp->numval; i++) {
+ pmAtomValue result;
+ const pmValue *vp = &vsp->vlist[i];
+ QmcMetricValue *value = parameter.valuePtr(i);
+
+ sts = PM_ERR_TYPE; // no nesting events
+ if (QmcMetric::real(type) == true) {
+ if ((sts = pmExtractValue(vsp->valfmt, vp,
+ type, &result, PM_TYPE_DOUBLE)) >= 0)
+ value->setCurrentValue(result.d);
+ } else if (QmcMetric::aggregate(type) == true) {
+ char buffer[32];
+ QmcMetric::aggregateAsString(vp, buffer, sizeof(buffer));
+ value->setStringValue(buffer);
+ } else if (QmcMetric::event(type) == false) {
+ if ((sts = pmExtractValue(vsp->valfmt, vp,
+ type, &result, PM_TYPE_STRING)) >= 0) {
+ value->setStringValue(result.cp);
+ free(result.cp);
+ }
+ }
+ value->setInstance(vp->inst);
+ if (sts < 0)
+ value->setCurrentError(sts);
+ }
+ return 0;
+}
+
+QString
+QmcEventRecord::parameterAsString(int index) const
+{
+ QString identifier;
+
+ if (index >= my.parameters.size())
+ return QString::null;
+
+ int type = my.parameters[index].type();
+ if (QmcMetric::real(type))
+ return QString::number(my.parameters.at(index).value(0));
+ if (QmcMetric::event(type))
+ return QString::null;
+ return my.parameters.at(index).stringValue(0);
+}
+
+QString
+QmcEventRecord::identifier() const
+{
+ //
+ // If the ID flag is set, the identifier is always the
+ // first parameter.
+ //
+ if (my.flags & PM_EVENT_FLAG_ID)
+ return parameterAsString(0);
+ return QString::null;
+}
+
+QString
+QmcEventRecord::parent() const
+{
+ //
+ // If PARENT flag is set, then parent identifier is either
+ // the first parameter (if no ID, bit wierd) or the second
+ // (thats expected typical usage anyway).
+ //
+ if (my.flags & PM_EVENT_FLAG_PARENT)
+ return parameterAsString((my.flags & PM_EVENT_FLAG_ID) != 0);
+ return QString::null;
+}
+
+void
+QmcEventRecord::parameterSummary(QString &os, int instID) const
+{
+ for (int i = 0; i < my.parameters.size(); i++)
+ my.parameters.at(i).summary(os, instID);
+}
+
+void
+QmcEventParameter::summary(QString &os, int instID) const
+{
+ pmDesc desc = my.desc->desc();
+
+ for (int i = 0; i < my.values.size(); i++) {
+ QmcMetricValue const &value = my.values.at(i);
+
+ os.append(" ").append(*my.name);
+ if (desc.indom != PM_INDOM_NULL) {
+ if (my.values.size() > 1)
+ os.append("\n").append(" ");
+ QString name = my.indom->name(instID);
+ if (name == QString::null)
+ os.append("[").append(instID).append("]");
+ else
+ os.append("[\"").append(name).append("\"]");
+ }
+ os.append(" ");
+
+ if (QmcMetric::real(desc.type))
+ os.append(QString::number(value.currentValue()));
+ else if (QmcMetric::aggregate(desc.type))
+ os.append("[").append(value.stringValue()).append("]");
+ else if (QmcMetric::event(desc.type) == false)
+ os.append("\"").append(value.stringValue()).append("\"");
+ os.append("\n");
+ }
+}
+
+void
+QmcEventParameter::setValueCount(int numInst)
+{
+ my.values.resize(numInst);
+}
+
+QmcMetricValue *
+QmcEventParameter::valuePtr(int inst)
+{
+ return &my.values[inst];
+}
+
+int
+QmcEventParameter::type() const
+{
+ return my.desc->desc().type;
+}
+
+double
+QmcEventParameter::value(int inst) const
+{
+ return my.values[inst].currentValue();
+}
+
+QString
+QmcEventParameter::stringValue(int inst) const
+{
+ return my.values[inst].stringValue();
+}
+
+void
+QmcEventParameter::dump(QTextStream &os, int instID) const
+{
+ pmDesc desc = my.desc->desc();
+
+ for (int i = 0; i < my.values.size(); i++) {
+ QmcMetricValue const &value = my.values.at(i);
+
+ os << " " << *my.name;
+ if (desc.indom != PM_INDOM_NULL) {
+ if (my.values.size() > 1)
+ os << endl << " ";
+ QString name = my.indom->name(instID);
+ if (name == QString::null)
+ os << "[" << instID << "]";
+ else
+ os << "[\"" << name << "\"]";
+ }
+ os << " ";
+
+ if (QmcMetric::real(desc.type))
+ os << value.currentValue();
+ else if (QmcMetric::aggregate(desc.type))
+ os << "[" << value.stringValue() << "]";
+ else if (QmcMetric::event(desc.type) == false)
+ os << "\"" << value.stringValue() << "\"";
+ os << endl;
+ }
+}
+
+void
+QmcEventRecord::dump(QTextStream &os, int instID, uint recordID) const
+{
+ os << " " << QmcSource::timeStringBrief(&my.timestamp);
+ os << " --- event record [" << recordID << "]";
+ if (my.flags) {
+ os.setIntegerBase(16);
+ os << " flags 0x" << (uint)my.flags << " (" << pmEventFlagsStr(my.flags) << ")";
+ os.setIntegerBase(10);
+ }
+ os << " ---" << endl;
+ if (my.flags & PM_EVENT_FLAG_MISSED)
+ os << " ==> " << my.missed << " missed event records" << endl;
+ for (int i = 0; i < my.parameters.size(); i++)
+ my.parameters.at(i).dump(os, instID);
+}
+
+void
+QmcMetricValue::dumpEventRecords(QTextStream &os, int instID) const
+{
+ os << my.eventRecords.size() << " event records" << endl;
+ for (int i = 0; i < my.eventRecords.size(); i++)
+ my.eventRecords.at(i).dump(os, instID, i);
+}
+
+void
+QmcMetric::dumpEventMetric(QTextStream &os, bool srcFlag, uint instance) const
+{
+ Q_ASSERT(event());
+
+ for (int i = 0; i < my.values.size(); i++) {
+ int instID;
+
+ if (srcFlag == true)
+ dumpSource(os);
+ os << name();
+
+ instID = PM_IN_NULL;
+ if (hasInstances()) {
+ if (instance != UINT_MAX)
+ instID = (int)instance;
+ else
+ instID = my.values[i].instance();
+
+ QString inst = instName(instID);
+ if (inst == QString::null)
+ os << "[" << instID << "]";
+ else
+ os << "[\"" << inst << "\"]";
+ }
+ os << ": ";
+ my.values[i].dumpEventRecords(os, instID);
+ }
+}
+
+pmID
+QmcEventRecord::eventMissed(void)
+{
+ static pmID eventMissed = PM_IN_NULL;
+ static const char *nameMissed[] = { "event.missed" };
+
+ if (eventMissed == PM_ID_NULL)
+ if (pmLookupName(1, (char **)nameMissed, &eventMissed) < 0)
+ eventMissed = PM_ID_NULL;
+ return eventMissed;
+}
+
+pmID
+QmcEventRecord::eventFlags(void)
+{
+ static pmID eventFlags = PM_IN_NULL;
+ static const char *nameFlags[] = { "event.flags" };
+
+ if (eventFlags == PM_ID_NULL)
+ if (pmLookupName(1, (char **)nameFlags, &eventFlags) < 0)
+ eventFlags = PM_ID_NULL;
+ return eventFlags;
+}
+
+bool
+QmcMetric::updateIndom(void)
+{
+ int i = 0, j, oldNum = numInst(), newNum, newInst;
+ QmcIndom *indomPtr = indom();
+
+ if (status() < 0 || !hasIndom())
+ return false;
+
+ if (indomPtr->changed())
+ indomPtr->update();
+
+ my.explicitInst = false;
+
+ newNum = my.active ? indomPtr->numActiveInsts() : indomPtr->numInsts();
+
+ // If the number of instances are the same then we know that no
+ // modifications to the metric instance list is required as these
+ // instances are all referenced in the indom
+ //
+ // If the instance list is only active instances, then we need to
+ // check all the instances as the number may be the same
+ //
+ if (newNum == oldNum) {
+ if (my.active) {
+ for (i = 0; i < my.values.size(); i++)
+ if (!indomPtr->activeInst(my.values[i].instance()))
+ break;
+ }
+ if (!my.active || i == my.values.size()) {
+ if (pmDebug & DBG_TRACE_INDOM) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::updateIndom: No change required" << endl;
+ }
+ return false;
+ }
+ }
+
+ // Duplicate the current values
+ // Replace the old index into the indom instance list with the
+ // internal instance identifiers so that these can be correlated
+ // if the order of instances changes
+ QList<QmcMetricValue> oldValues = my.values;
+ for (i = 0; i < oldNum; i++) {
+ oldValues[i].setInstance(indomPtr->inst(my.values[i].instance()));
+ indomPtr->removeRef(my.values[i].instance());
+ }
+
+ setupValues(newNum);
+ indomPtr->refAll(my.active);
+
+ if (my.active) {
+ for (i = 0, j = 0; i < indomPtr->listLen(); i++)
+ if (!indomPtr->nullInst(i) && indomPtr->activeInst(i))
+ my.values[j++].setInstance(i);
+ }
+ else {
+ for (i = 0, j = 0; i < indomPtr->listLen(); i++)
+ if (!indomPtr->nullInst(i))
+ my.values[j++].setInstance(i);
+ }
+
+ // Copy values of instances that have not gone away
+ // Note that their position may have changed
+ for (i = 0; i < my.values.size(); i++) {
+ if (i < oldValues.size() &&
+ indomPtr->inst(my.values[i].instance()) ==
+ oldValues[i].instance()) {
+ newInst = my.values[i].instance();
+ my.values[i] = oldValues[i];
+ my.values[i].setInstance(newInst);
+ continue;
+ }
+ for (j = 0; j < oldValues.size(); j++)
+ if (indomPtr->inst(my.values[i].instance()) ==
+ oldValues[j].instance()) {
+ newInst = my.values[i].instance();
+ my.values[i] = oldValues[j];
+ my.values[i].setInstance(newInst);
+ break;
+ }
+
+ // Need to set all error flags to avoid problems with rate conversion
+ if (j == oldValues.size())
+ my.values[i].setAllErrors(PM_ERR_VALUE);
+ }
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcMetric::updateIndom: " << spec(true) << ": Had "
+ << oldNum << " instances, now have " << numInst() << endl;
+ }
+
+ indomPtr->update();
+
+ return true;
+}
+
+int
+QmcMetric::addInst(QString const& name)
+{
+ if (my.status < 0)
+ return my.status;
+
+ if (!hasInstances())
+ return PM_ERR_INDOM;
+
+ int i = indom()->lookup(name);
+ if (i >= 0) {
+ setupValues(my.values.size() + 1);
+ my.values.last().setInstance(i);
+ }
+
+ return i;
+}
+
+void
+QmcMetric::removeInst(uint index)
+{
+ Q_ASSERT(hasInstances());
+ indom()->removeRef(my.values[index].instance());
+ my.values.removeAt(index);
+}
diff --git a/src/libpcp_qmc/src/qmc_metric.h b/src/libpcp_qmc/src/qmc_metric.h
new file mode 100644
index 0000000..7bca55b
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_metric.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat, Inc.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1998-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_METRIC_H
+#define QMC_METRIC_H
+
+#include "qmc.h"
+#include "qmc_desc.h"
+#include "qmc_group.h"
+#include "qmc_context.h"
+
+#include <qlist.h>
+#include <qvector.h>
+#include <qstring.h>
+#include <qtextstream.h>
+
+class QmcMetricValue;
+
+class QmcEventParameter
+{
+public:
+ QmcEventParameter() { my.pmid = PM_ID_NULL; }
+
+ void setPMID(pmID pmid) { my.pmid = pmid; }
+ void setNamePtr(QString *name) { my.name = name; }
+ void setDescPtr(QmcDesc *desc) { my.desc = desc; }
+ void setIndomPtr(QmcIndom *indom) { my.indom = indom; }
+
+ void setValueCount(int numInst);
+ QmcMetricValue *valuePtr(int inst);
+
+ int type() const;
+ double value(int inst) const;
+ QString stringValue(int inst) const;
+
+ void summary(QString &os, int instID) const;
+ void dump(QTextStream &os, int instID) const;
+
+private:
+ struct {
+ pmID pmid; // pmid for the parameter to an event
+ QString *name; // direct pointer into external cache
+ QmcDesc *desc; // direct pointer into external cache
+ QmcIndom *indom; // direct pointer into external cache
+ QVector<QmcMetricValue> values;
+ } my;
+};
+
+class QmcEventRecord
+{
+public:
+ QmcEventRecord() { my.missed = my.flags = 0; }
+
+ const struct timeval *timestamp() const { return &my.timestamp; }
+ void setTimestamp(struct timeval *tv) { my.timestamp = *tv; }
+
+ int flags() const { return my.flags; }
+ void setFlags(int flags) { my.flags = flags; }
+
+ int missed() const { return my.missed; }
+ void setMissed(int missed) { my.missed = missed; }
+
+ void setParameterCount(int numParams)
+ { my.parameters.resize(numParams); }
+ int setParameter(int n, pmID pmid, QmcContext *cp, pmValueSet const *vp);
+
+ QString parent() const;
+ QString identifier() const;
+
+ void parameterSummary(QString &os, int instID) const;
+ void dump(QTextStream &os, int instID, uint recordID) const;
+
+ static pmID eventFlags();
+ static pmID eventMissed();
+
+private:
+ QString parameterAsString(int index) const;
+
+ struct {
+ struct timeval timestamp;
+ int missed;
+ int flags;
+ QVector<QmcEventParameter> parameters;
+ } my;
+};
+
+class QmcMetricValue
+{
+public:
+ QmcMetricValue();
+ QmcMetricValue const& operator=(QmcMetricValue const& rhs);
+
+ int instance() const { return my.instance; }
+ void setInstance(int instance) { my.instance = instance; }
+
+ int error() const { return my.error; }
+ void setError(int error) { my.error = error; }
+ void setAllErrors(int error)
+ { my.error = my.currentError = my.previousError = error; }
+
+ double value() const { return my.value; }
+ void setValue(double value) { my.value = value; }
+ void divValue(double value) { my.value /= value; }
+ void addValue(double value) { my.value += value; }
+ void subValue(double value) { my.value -= value; }
+
+ QString stringValue() const { return my.stringValue; }
+ void setStringValue(const char *s) { my.stringValue = s; }
+
+ int currentError() const { return my.currentError; }
+ void setCurrentError(int error)
+ { my.currentError = error; resetCurrentValue(); }
+ double currentValue() const { return my.currentValue; }
+ void setCurrentValue(double value) { my.currentValue = value; }
+
+ int previousError() const { return my.previousError; }
+ double previousValue() const { return my.previousValue; }
+ void shiftValues() { my.previousValue = my.currentValue;
+ my.previousError = my.currentError;
+ my.currentError = 0; }
+
+ QVector<QmcEventRecord> const &eventRecords() const { return my.eventRecords; }
+ void extractEventRecords(QmcContext *context, int recordCount, pmResult **result);
+ void dumpEventRecords(QTextStream &os, int instid) const;
+
+private:
+ void resetCurrentValue()
+ { my.currentValue = 0.0; my.stringValue = QString::null; my.eventRecords.clear(); }
+
+ struct {
+ int instance;
+ int error;
+ double value;
+ double previousValue;
+ double currentValue;
+ int currentError;
+ int previousError;
+ QString stringValue;
+ QVector<QmcEventRecord> eventRecords;
+ } my;
+};
+
+class QmcMetric
+{
+ friend class QmcGroup;
+ friend class QmcContext;
+
+public:
+ QmcMetric(QmcGroup *group, const char *str, double theScale = 0.0,
+ bool active = false);
+ QmcMetric(QmcGroup *group, pmMetricSpec *theMetric, double theScale = 0.0,
+ bool active = false);
+ ~QmcMetric();
+
+ int status() const { return my.status; }
+ pmID metricID() const { return my.pmid; }
+ const QString name() const { return my.name; }
+ char *nameAscii() const { return strdup((const char *)my.name.toAscii()); }
+ QmcContext *context() const
+ { return my.group->context(my.contextIndex); }
+ const QmcDesc &desc() const
+ { return context()->desc(my.pmid); }
+ bool hasIndom() const
+ { return desc().desc().indom != PM_INDOM_NULL; }
+ bool hasInstances() const
+ { return (my.status >= 0 && my.indomIndex < UINT_MAX); }
+
+ // Were the instances explicitly listed?
+ bool explicitInsts() const { return my.explicitInst; }
+
+ // Are only active instances referenced
+ bool activeInsts() const { return my.active; }
+
+ int numInst() const
+ { return (my.status >= 0 && my.indomIndex < UINT_MAX) ?
+ my.values.size() : 0; }
+
+ // How many values does it have (will not equal number of instances
+ // if singular)
+ int numValues() const { return (my.status >= 0) ? my.values.size() : 0; }
+
+ // The metric indom
+ QmcIndom *indom() const
+ { return (my.indomIndex == UINT_MAX) ? NULL :
+ &(my.group->context(my.contextIndex)->indom(my.indomIndex)); }
+
+ // Internal instance id for instance <index>
+ int instID(int index) const
+ { return my.group->context(my.contextIndex)->indom(my.indomIndex).inst(my.values[index].instance()); }
+
+ // External instance name for instance <index>
+ const QString instName(int index) const
+ { return my.group->context(my.contextIndex)->indom(my.indomIndex).name(my.values[index].instance()); }
+
+ // Return the index for the instance in the indom list
+ int instIndex(uint index) const { return my.values[index].instance(); }
+
+ // Update the metric to include new instances
+ // Returns true if the instance list changed
+ // Metrics with implicit instances will be extended to include those
+ // new instances. The position of instances may change.
+ // If <active> is set, only those instances in the latest indom will
+ // be listed, other instances will be removed
+ bool updateIndom();
+
+ int addInst(QString const& name);
+ void removeInst(uint index);
+
+ // Scaling modifier applied to metric values
+ double scale() const { return my.scale; }
+
+ // Metric has event records (as opposed to real/string/aggregate values)
+ bool event() const { return event(desc().desc().type); }
+ static bool event(int type)
+ { return type == PM_TYPE_EVENT || type == PM_TYPE_HIGHRES_EVENT; }
+
+ bool aggregate() const { return aggregate(desc().desc().type); }
+ static void aggregateAsString(pmValue const *, char *, int);
+ static bool aggregate(int type)
+ { return type == PM_TYPE_AGGREGATE || type == PM_TYPE_AGGREGATE_STATIC; }
+
+ // Metric has real values (as opposed to event/string/aggregate values)
+ bool real() const { return real(desc().desc().type); }
+ static bool real(int type)
+ { return type > PM_TYPE_NOSUPPORT && type < PM_TYPE_STRING; }
+
+ // Current rate-converted and scaled real value
+ double value(int index) const { return my.values[index].value(); }
+
+ double realValue(int index) const // Current rate-converted value
+ { return my.values[index].value() * my.scale; }
+
+ double currentValue(int index) const // Current raw value
+ { return my.values[index].currentValue(); }
+
+ QString stringValue(int index) const // Current string value
+ { return my.values[index].stringValue(); }
+
+ QVector<QmcEventRecord> const &eventRecords(int index) const
+ { return my.values[index].eventRecords(); }
+
+ int error(int index) const // Current error code (after rate-conversion)
+ { return my.values[index].error(); }
+
+ int currentError(int index) const // Current raw error code
+ { return my.values[index].currentError(); }
+
+ void shiftValues(); // Shift values in preparation for next fetch
+
+ void setError(int sts); // Set error code for all instances
+
+ void extractValues(pmValueSet const* set); // Extract values after a fetch
+
+ uint contextIndex() const // Index for context in group list
+ { return my.contextIndex; }
+
+ // Index for metric into pmResult
+ uint idIndex() const { return my.idIndex; }
+
+ // Index for indom in context list
+ uint indomIndex() const { return my.indomIndex; }
+
+ // Set the canonical units
+ void setScaleUnits(pmUnits const& units);
+
+ // Generate a metric spec
+ QString spec(bool srcFlag = false,
+ bool instFlag = false,
+ uint instance = UINT_MAX) const;
+
+ // Dump out the metric and its current value(s)
+ void dump(QTextStream &os, bool srcFlag = false,
+ uint instance = UINT_MAX) const;
+
+ // Dump out the current value
+ void dumpValue(QTextStream &os, uint instance) const;
+
+ // Dump out the metric source
+ void dumpSource(QTextStream &os) const;
+
+ // Format a value into a fixed width format
+ static const char *formatNumber(double value);
+
+ // Determine the current errors and rate-converted scaled values
+ int update();
+
+ friend QTextStream &operator<<(QTextStream &os, const QmcMetric &metric);
+
+private:
+ struct {
+ pmID pmid;
+ int status;
+ QString name;
+ QmcGroup *group;
+ QList<QmcMetricValue> values;
+ double scale;
+
+ uint contextIndex; // Index into the context list for the group
+ uint idIndex; // Index into the pmid list for the context.
+ uint indomIndex; // Index into the indom list for the context.
+
+ bool explicitInst; // Instances explicitly specified
+ bool active; // Use only active implicit insts
+ } my;
+
+ void setup(QmcGroup *group, pmMetricSpec *theMetric);
+ void setupDesc(QmcGroup *group, pmMetricSpec *theMetric);
+ void setupIndom(pmMetricSpec *theMetric);
+ void setupValues(int num);
+
+ void extractNumericMetric(pmValueSet const *vset, pmValue const *v, QmcMetricValue &vref);
+ void extractArrayMetric(pmValueSet const *vset, pmValue const *v, QmcMetricValue &vref);
+ void extractEventMetric(pmValueSet const *vset, int index, QmcMetricValue &vref);
+
+ void setIdIndex(uint index) { my.idIndex = index; }
+
+ // Dump error messages
+ void dumpAll() const;
+ void dumpErr() const;
+ void dumpErr(const char *inst) const;
+
+ // Dump out different metric flavours and their current value(s)
+ void dumpEventMetric(QTextStream &os, bool srcFlag = false,
+ uint instance = UINT_MAX) const;
+ void dumpSampledMetric(QTextStream &os, bool srcFlag = false,
+ uint instance = UINT_MAX) const;
+};
+
+#endif // QMC_METRIC_H
diff --git a/src/libpcp_qmc/src/qmc_source.cpp b/src/libpcp_qmc/src/qmc_source.cpp
new file mode 100644
index 0000000..3935217
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_source.cpp
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#include "qmc_source.h"
+
+QString QmcSource::localHost;
+QList<QmcSource*> QmcSource::sourceList;
+
+QmcSource::QmcSource(int type, QString &source, int flags)
+{
+ my.status = -1;
+ my.flags = flags;
+ my.type = type;
+ my.tz = 0;
+ my.dupFlag = false;
+
+ if (localHost.length() == 0) {
+ char buf[MAXHOSTNAMELEN];
+ gethostname(buf, MAXHOSTNAMELEN);
+ buf[MAXHOSTNAMELEN-1] = '\0';
+ localHost = buf;
+ }
+
+ this->retryConnect(type, source);
+}
+
+void
+QmcSource::retryConnect(int type, QString &source)
+{
+ int oldTZ;
+ int oldContext;
+ int offset;
+ int sts;
+ char *tzs;
+ QString hostSpec;
+
+ my.attrs = QString::null;
+ switch(type) {
+ case PM_CONTEXT_LOCAL:
+ my.desc = "Local context";
+ my.host = my.source = localHost;
+ my.proxy = "";
+ break;
+
+ case PM_CONTEXT_HOST:
+ my.desc = "host \"";
+ my.desc.append(source);
+ my.desc.append(QChar('\"'));
+ my.host = source;
+ my.proxy = getenv("PMPROXY_HOST");
+ if ((offset = my.host.indexOf('?')) >= 0) {
+ my.attrs = my.host;
+ my.attrs.remove(0, offset+1);
+ my.host.truncate(offset);
+ }
+ if ((offset = my.host.indexOf('@')) >= 0) {
+ my.proxy = my.host;
+ my.proxy.remove(0, offset+1);
+ }
+ my.source = my.host;
+ break;
+
+ case PM_CONTEXT_ARCHIVE:
+ my.desc = "archive \"";
+ my.desc.append(source);
+ my.desc.append(QChar('\"'));
+ my.source = source;
+ my.proxy = "";
+ break;
+ }
+
+ oldContext = pmWhichContext();
+
+ hostSpec = source;
+ if (my.attrs != QString::null)
+ hostSpec.append("?").append(my.attrs);
+
+ my.status = pmNewContext(type | my.flags, (const char *)hostSpec.toAscii());
+ if (my.status >= 0) {
+ my.handles.append(my.status);
+
+ // Fetch the server-side host name for this context, properly as of pcp 3.8.3+.
+ my.context_hostname = pmGetContextHostName (my.status); // NB: may leak memory
+ if (my.context_hostname == "") // may be returned for errors or PM_CONTEXT_LOCAL
+ my.context_hostname = localHost;
+
+ if (my.type == PM_CONTEXT_ARCHIVE) {
+ pmLogLabel lp;
+ sts = pmGetArchiveLabel(&lp);
+ if (sts < 0) {
+ pmprintf("%s: Unable to obtain log label for \"%s\": %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ my.host = "unknown?";
+ my.status = sts;
+ goto done;
+ }
+ else {
+ my.host = lp.ll_hostname;
+ my.start = lp.ll_start;
+ }
+ sts = pmGetArchiveEnd(&my.end);
+ if (sts < 0) {
+ pmprintf("%s: Unable to determine end of \"%s\": %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ my.status = sts;
+ goto done;
+ }
+ }
+ else {
+ gettimeofday(&my.start, NULL);
+ my.end = my.start;
+ }
+
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::QmcSource: Created context "
+ << my.handles.last() << " to " << my.desc << endl;
+ }
+
+ oldTZ = pmWhichZone(&tzs);
+ my.tz = pmNewContextZone();
+ if (my.tz < 0)
+ pmprintf("%s: Warning: Unable to obtain timezone for %s: %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(my.tz));
+ else {
+ sts = pmWhichZone(&tzs);
+ if (sts >= 0)
+ my.timezone = tzs;
+ else
+ pmprintf("%s: Warning: Unable to obtain timezone for %s: %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+
+ if (oldTZ >= 0) {
+ sts = pmUseZone(oldTZ);
+ if (sts < 0) {
+ pmprintf("%s: Warning: Unable to switch timezones."
+ " Using timezone for %s: %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+ }
+ }
+ else if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::QmcSource: Context to " << source
+ << " failed: " << pmErrStr(my.status) << endl;
+ }
+
+ done:
+ sourceList.append(this);
+
+ if (oldContext >= 0) {
+ sts = pmUseContext(oldContext);
+ if (sts < 0) {
+ pmprintf("%s: Warning: Unable to switch contexts."
+ " Using context to %s: %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+ }
+}
+
+QmcSource::~QmcSource()
+{
+ int i;
+
+ for (i = 0; i < sourceList.size(); i++)
+ if (sourceList[i] == this)
+ break;
+ if (i < sourceList.size())
+ sourceList.removeAt(i);
+}
+
+QString
+QmcSource::timeString(const struct timeval *timeval)
+{
+ QString timestring;
+ char timebuf[32], *ddmm, *year;
+ struct tm tmp;
+ time_t secs = (time_t)timeval->tv_sec;
+
+ ddmm = pmCtime(&secs, timebuf);
+ ddmm[10] = '\0';
+ year = &ddmm[20];
+ year[4] = '\0';
+ pmLocaltime(&secs, &tmp);
+
+ timestring.sprintf("%02d:%02d:%02d.%03d",
+ tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(timeval->tv_usec/1000));
+ timestring.prepend(" ");
+ timestring.prepend(ddmm);
+ timestring.append(" ");
+ timestring.append(year);
+ return timestring;
+}
+
+QString
+QmcSource::timeStringBrief(const struct timeval *timeval)
+{
+ QString timestring;
+ struct tm tmp;
+ time_t secs = (time_t)timeval->tv_sec;
+
+ pmLocaltime(&secs, &tmp);
+ timestring.sprintf("%02d:%02d:%02d.%03d",
+ tmp.tm_hour, tmp.tm_min, tmp.tm_sec, (int)(timeval->tv_usec/1000));
+ return timestring;
+}
+
+bool
+QmcSource::compare(int type, QString &source, int flags)
+{
+ if (this->type() != type)
+ return false;
+ if (this->flags() != flags)
+ return false;
+ return this->source() == source;
+}
+
+QmcSource*
+QmcSource::getSource(int type, QString &source, int flags, bool matchHosts)
+{
+ int i;
+ QmcSource *src = NULL;
+
+ for (i = 0; i < sourceList.size(); i++) {
+ src = sourceList[i];
+ if (matchHosts && type == PM_CONTEXT_HOST) {
+ if (src->type() == PM_CONTEXT_ARCHIVE && src->host() == source) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::getSource: Matched host "
+ << source << " to archive " << src->source()
+ << " (source " << i << ")" << endl;
+ }
+ break;
+ }
+ }
+ else if (src->compare(type, source, flags)) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::getSource: Matched " << source
+ << " to source " << i << endl;
+ }
+ if (src->status() < 0)
+ src->retryConnect(type, source);
+ break;
+ }
+ }
+
+ if (i == sourceList.size() &&
+ !(matchHosts == true && type == PM_CONTEXT_HOST)) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ if (type != PM_CONTEXT_LOCAL)
+ cerr << "QmcSource::getSource: Creating new source for "
+ << source << endl;
+ else
+ cerr << "QmcSource::getSource: Creating new local context"
+ << endl;
+ }
+ src = new QmcSource(type, source, flags);
+ }
+
+ if (src == NULL && pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::getSource: Unable to map host "
+ << source << " to an arch context" << endl;
+ }
+
+ return src;
+}
+
+int
+QmcSource::dupContext()
+{
+ int sts = 0;
+
+ if (my.status < 0)
+ return my.status;
+
+ if (my.dupFlag == false && my.handles.size() == 1) {
+ sts = pmUseContext(my.handles[0]);
+ if (sts >= 0) {
+ sts = my.handles[0];
+ my.dupFlag = true;
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::dupContext: Using original context for "
+ << my.desc << endl;
+ }
+ }
+ else
+ pmprintf("%s: Error: Unable to switch to context for \"%s\": %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+ else if (my.handles.size()) {
+ sts = pmUseContext(my.handles[0]);
+ if (sts >= 0) {
+ sts = pmDupContext();
+ if (sts >= 0) {
+ my.handles.append(sts);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::dupContext: " << my.desc
+ << " duplicated, handle[" << my.handles.size() - 1
+ << "] = " << sts << endl;
+ }
+ }
+ else
+ pmprintf("%s: Error: "
+ "Unable to duplicate context to \"%s\": %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+ else
+ pmprintf("%s: Error: Unable to switch to context for \"%s\": %s\n",
+ pmProgname, (const char *)my.desc.toAscii(),
+ pmErrStr(sts));
+ }
+ // No active contexts, create a new context
+ else {
+ sts = pmNewContext(my.type, sourceAscii());
+ if (sts >= 0) {
+ my.handles.append(sts);
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::dupContext: new context to " << my.desc
+ << " created, handle = " << sts << endl;
+ }
+ }
+ }
+
+ if (sts < 0 && pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::dupContext: context to " << my.desc
+ << " failed: " << pmErrStr(my.status) << endl;
+ }
+
+ return sts;
+}
+
+int
+QmcSource::delContext(int handle)
+{
+ int i;
+ int sts;
+
+ for (i = 0; i < my.handles.size(); i++)
+ if (my.handles[i] == handle)
+ break;
+
+ if (i == my.handles.size()) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::delContext: Attempt to delete " << handle
+ << " from list for " << my.desc << ", but it is not listed"
+ << endl;
+ }
+ return PM_ERR_NOCONTEXT;
+ }
+
+ sts = pmDestroyContext(my.handles[i]);
+ my.handles.removeAt(i);
+
+ // If this is a valid source, but no more contexts remain,
+ // then we should delete ourselves
+ if (my.handles.size() == 0 && my.status >= 0) {
+ if (pmDebug & DBG_TRACE_PMC) {
+ QTextStream cerr(stderr);
+ cerr << "QmcSource::delContext: No contexts remain, removing "
+ << my.desc << endl;
+ }
+ delete this;
+ }
+
+ return sts;
+}
+
+QTextStream&
+operator<<(QTextStream &stream, const QmcSource &rhs)
+{
+ stream << rhs.my.desc;
+ return stream;
+}
+
+void
+QmcSource::dump(QTextStream &stream)
+{
+ stream << " sts = " << my.status << ", type = " << my.type
+ << ", source = " << my.source << endl
+ << " host = " << my.host << ", timezone = " << my.timezone
+ << ", tz hndl = " << my.tz << endl;
+ if (my.status >= 0)
+ stream << " start = " << timeString(&my.start) << ", end = "
+ << timeString(&my.end) << ", dupFlag = "
+ << (my.dupFlag == true ? "true" : "false") << endl << " "
+ << my.handles.size() << " contexts: ";
+ for (int i = 0; i < my.handles.size(); i++)
+ stream << my.handles[i] << ' ';
+ stream << endl;
+}
+
+void
+QmcSource::dumpList(QTextStream &stream)
+{
+ stream << sourceList.size() << " sources:" << endl;
+ for (int i = 0; i < sourceList.size(); i++) {
+ stream << '[' << i << "] " << *(sourceList[i]) << endl;
+ sourceList[i]->dump(stream);
+ }
+}
diff --git a/src/libpcp_qmc/src/qmc_source.h b/src/libpcp_qmc/src/qmc_source.h
new file mode 100644
index 0000000..02b8f12
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_source.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ * Copyright (c) 1998,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef QMC_SOURCE_H
+#define QMC_SOURCE_H
+
+#include "qmc.h"
+
+#include <qlist.h>
+#include <qstring.h>
+#include <qtextstream.h>
+
+class QmcSource
+{
+public:
+ QmcSource(int type, QString &source, int flags = 0);
+ ~QmcSource();
+
+ // Get the source description by searching the list of existing sources
+ // and returning a new source only if required.
+ // If matchHosts is true, then it will attempt to map a live context
+ // to an archive source. If no matching archive context is found,
+ // a NULL pointer is returned.
+ static QmcSource* getSource(int type, QString &source, int flags = 0,
+ bool matchHosts = false);
+
+ int status() const { return my.status; }
+ int flags() const { return my.flags; }
+ int type() const { return my.type; }
+ bool isArchive() const { return my.type == PM_CONTEXT_ARCHIVE; }
+ QString source() const { return my.source; }
+ char *sourceAscii() const { return strdup((const char*)my.source.toAscii()); }
+ QString host() const { return my.host; }
+ char *hostAscii() const { return strdup((const char *)my.host.toAscii()); }
+ QString proxy() const { return my.proxy; }
+ char *proxyAscii() const { return strdup((const char *)my.proxy.toAscii()); }
+ int tzHandle() const { return my.tz; }
+ QString timezone() const { return my.timezone; }
+ struct timeval start() const { return my.start; }
+ QString startTime() { return timeString(&my.start); }
+ struct timeval end() const { return my.end; }
+ QString endTime() { return timeString(&my.end); }
+ QString desc() const { return my.desc; }
+ char *descAscii() const { return strdup((const char *)my.desc.toAscii()); }
+ QString context_hostname() const { return my.context_hostname; }
+
+ // Number of active contexts to this source
+ uint numContexts() const { return my.handles.size(); }
+
+ // Create a new context to this source
+ int dupContext();
+
+ // Delete context to this source
+ int delContext(int handle);
+
+ // Output the source
+ friend QTextStream &operator<<(QTextStream &os, const QmcSource &rhs);
+
+ // Dump all info about a source
+ void dump(QTextStream &os);
+
+ // Dump list of known sources
+ static void dumpList(QTextStream &os);
+
+ // Local host name (from gethostname(2))
+ static QString localHost;
+
+ // Convert a time to a string (long and short forms)
+ static QString timeString(const struct timeval *timeval);
+ static QString timeStringBrief(const struct timeval *timeval);
+
+protected:
+ // retry context/connection (e.g. if it failed in the constructor)
+ void retryConnect(int type, QString &source);
+
+ // compare two sources - static so getSource() can make use of it
+ bool compare(int type, QString &source, int flags);
+
+private:
+ struct {
+ int status;
+ int type;
+ QString source;
+ QString proxy;
+ QString attrs;
+ QString host;
+ QString context_hostname; // from pmcd/archive, not from -h/-a argument
+ QString desc;
+ QString timezone;
+ QList<int> handles; // Contexts created for this source
+ struct timeval start;
+ struct timeval end;
+ int tz;
+ bool dupFlag; // Dup has been called and 1st context is in use
+ int flags;
+ } my;
+
+ static QList<QmcSource*> sourceList;
+};
+
+#endif // QMC_SOURCE_H
diff --git a/src/libpcp_qmc/src/qmc_time.cpp b/src/libpcp_qmc/src/qmc_time.cpp
new file mode 100644
index 0000000..b59db0d
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_time.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui/QIcon>
+#include "qmc_time.h"
+
+//
+// Map icon type name to QIcon
+//
+extern QIcon *QmcTime::icon(QmcTime::Icon type)
+{
+ static QIcon icons[QmcTime::IconCount];
+ static int setup;
+
+ if (!setup) {
+ setup = 1;
+ icons[QmcTime::ForwardOn] = QIcon(":images/play_on.png");
+ icons[QmcTime::ForwardOff] = QIcon(":images/play_off.png");
+ icons[QmcTime::StoppedOn] = QIcon(":images/stop_on.png");
+ icons[QmcTime::StoppedOff] = QIcon(":images/stop_off.png");
+ icons[QmcTime::BackwardOn] = QIcon(":images/back_on.png");
+ icons[QmcTime::BackwardOff] = QIcon(":images/back_off.png");
+ icons[QmcTime::FastForwardOn] = QIcon(":images/fastfwd_on.png");
+ icons[QmcTime::FastForwardOff] = QIcon(":images/fastfwd_off.png");
+ icons[QmcTime::FastBackwardOn] = QIcon(":images/fastback_on.png");
+ icons[QmcTime::FastBackwardOff] = QIcon(":images/fastback_off.png");
+ icons[QmcTime::StepForwardOn] = QIcon(":images/stepfwd_on.png");
+ icons[QmcTime::StepForwardOff] = QIcon(":images/stepfwd_off.png");
+ icons[QmcTime::StepBackwardOn] = QIcon(":images/stepback_on.png");
+ icons[QmcTime::StepBackwardOff] = QIcon(":images/stepback_off.png");
+ }
+ return &icons[type];
+}
+
+//
+// Test for not-zeroed timeval
+//
+int QmcTime::timevalNonZero(struct timeval *a)
+{
+ return (a->tv_sec != 0 || a->tv_usec != 0);
+}
+
+//
+// a := a + b for struct timevals
+//
+void QmcTime::timevalAdd(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec += b->tv_usec;
+ if (a->tv_usec > 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec++;
+ }
+ a->tv_sec += b->tv_sec;
+}
+
+//
+// a := a - b for struct timevals, result is never less than zero
+//
+void QmcTime::timevalSub(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+ if (a->tv_sec < 0) {
+ /* clip negative values at zero */
+ a->tv_sec = 0;
+ a->tv_usec = 0;
+ }
+}
+
+//
+// a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b
+//
+int QmcTime::timevalCompare(struct timeval *a, struct timeval *b)
+{
+ int res = (int)(a->tv_sec - b->tv_sec);
+ if (res == 0)
+ res = (int)(a->tv_usec - b->tv_usec);
+ return res;
+}
+
+//
+// Conversion from seconds (double precision) to struct timeval
+//
+void QmcTime::secondsToTimeval(double value, struct timeval *tv)
+{
+ tv->tv_sec = (time_t)value;
+ tv->tv_usec = (long)(((value - (double)tv->tv_sec) * 1000000.0));
+}
+
+//
+// Conversion from struct timeval to seconds (double precision)
+//
+double QmcTime::secondsFromTimeval(struct timeval *tv)
+{
+ return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0);
+}
+
+//
+// Conversion from other time units into seconds
+//
+double QmcTime::unitsToSeconds(double value, QmcTime::DeltaUnits units)
+{
+ if (units == QmcTime::Milliseconds)
+ return value / 1000.0;
+ else if (units == QmcTime::Minutes)
+ return value * 60.0;
+ else if (units == QmcTime::Hours)
+ return value * (60.0 * 60.0);
+ else if (units == QmcTime::Days)
+ return value * (60.0 * 60.0 * 24.0);
+ else if (units == QmcTime::Weeks)
+ return value * (60.0 * 60.0 * 24.0 * 7.0);
+ return value;
+}
+
+//
+// Conversion from seconds into other time units
+//
+double QmcTime::secondsToUnits(double value, QmcTime::DeltaUnits units)
+{
+ switch (units) {
+ case Milliseconds:
+ value = value * 1000.0;
+ break;
+ case Minutes:
+ value = value / 60.0;
+ break;
+ case Hours:
+ value = value / (60.0 * 60.0);
+ break;
+ case Days:
+ value = value / (60.0 * 60.0 * 24.0);
+ break;
+ case Weeks:
+ value = value / (60.0 * 60.0 * 24.0 * 7.0);
+ break;
+ case Seconds:
+ default:
+ break;
+ }
+ return value;
+}
+
+double QmcTime::deltaValue(QString delta, QmcTime::DeltaUnits units)
+{
+ return QmcTime::secondsToUnits(delta.trimmed().toDouble(), units);
+}
+
+QString QmcTime::deltaString(double value, QmcTime::DeltaUnits units)
+{
+ QString delta;
+
+ value = QmcTime::secondsToUnits(value, units);
+ if ((double)(int)value == value)
+ delta.sprintf("%.2f", value);
+ else
+ delta.sprintf("%.6f", value);
+ return delta;
+}
diff --git a/src/libpcp_qmc/src/qmc_time.h b/src/libpcp_qmc/src/qmc_time.h
new file mode 100644
index 0000000..0baf59d
--- /dev/null
+++ b/src/libpcp_qmc/src/qmc_time.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2006-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef QMC_TIME_H
+#define QMC_TIME_H
+
+#include <sys/time.h>
+
+class QIcon;
+
+class QmcTime
+{
+public:
+ typedef enum {
+ StoppedState = 0,
+ ForwardState = 1,
+ BackwardState = 2,
+ } State;
+
+ typedef enum {
+ StepMode = 0,
+ NormalMode = 1,
+ FastMode = 2,
+ } Mode;
+
+ typedef enum {
+ NoSource = -1,
+ HostSource = 0,
+ ArchiveSource = 1,
+ } Source;
+
+ typedef enum {
+ Set = (1<<0), // client -> server
+ Step = (1<<1), // server -> clients
+ TZ = (1<<2), // server -> clients
+ VCRMode = (1<<3), // server -> clients
+ VCRModeDrag = (1<<4), // server -> clients
+ GUIShow = (1<<5), // client -> server
+ GUIHide = (1<<6), // client -> server
+ Bounds = (1<<7), // client -> server
+ ACK = (1<<8), // client -> server (except handshake)
+ } Command;
+
+ static const unsigned int Magic = 0x54494D45; // "TIME"
+
+ typedef struct {
+ unsigned int magic;
+ unsigned int length;
+ QmcTime::Command command;
+ QmcTime::Source source;
+ QmcTime::State state;
+ QmcTime::Mode mode;
+ struct timeval delta;
+ struct timeval position;
+ struct timeval start; // archive only
+ struct timeval end; // archive only
+ unsigned char data[0]; // arbitrary length (e.g. $TZ)
+ } Packet;
+
+ typedef enum {
+ ForwardOn, ForwardOff,
+ StoppedOn, StoppedOff,
+ BackwardOn, BackwardOff,
+ FastForwardOn, FastForwardOff,
+ FastBackwardOn, FastBackwardOff,
+ StepForwardOn, StepForwardOff,
+ StepBackwardOn, StepBackwardOff,
+ IconCount
+ } Icon;
+
+ typedef enum {
+ Milliseconds,
+ Seconds,
+ Minutes,
+ Hours,
+ Days,
+ Weeks,
+ } DeltaUnits;
+
+ typedef enum {
+ DebugApp = 0x1,
+ DebugProtocol = 0x2,
+ } DebugOptions;
+
+ static const int BasePort = 43334;
+ static const int FastModeDelay = 100; // milliseconds
+ static const int DefaultDelta = 2; // seconds
+
+ static QIcon *icon(QmcTime::Icon);
+ static double defaultSpeed(double delta)
+ { return 2.0 * delta; } // num deltas per second
+ static double minimumSpeed(double delta)
+ { return 0.1 * delta; } // min deltas per second
+ static double maximumSpeed(double delta)
+ { return 1000.0 * delta; } // max deltas per second
+
+ static void timevalAdd(struct timeval *a, struct timeval *b);
+ static void timevalSub(struct timeval *a, struct timeval *b);
+ static int timevalNonZero(struct timeval *a);
+ static int timevalCompare(struct timeval *a, struct timeval *b);
+
+ static void secondsToTimeval(double value, struct timeval *tv);
+ static double secondsFromTimeval(struct timeval *tv);
+
+ static double unitsToSeconds(double value, DeltaUnits units);
+ static double secondsToUnits(double value, DeltaUnits units);
+ static QString deltaString(double value, DeltaUnits units);
+ static double deltaValue(QString delta, DeltaUnits units);
+};
+
+#endif // QMC_TIME_H
diff --git a/src/libpcp_qwt/GNUmakefile b/src/libpcp_qwt/GNUmakefile
new file mode 100644
index 0000000..552118e
--- /dev/null
+++ b/src/libpcp_qwt/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_qwt/src/GNUmakefile b/src/libpcp_qwt/src/GNUmakefile
new file mode 100644
index 0000000..d1c2139
--- /dev/null
+++ b/src/libpcp_qwt/src/GNUmakefile
@@ -0,0 +1,24 @@
+TOPDIR = ../../..
+LIBRARY = libpcp_qwt
+PROJECT = $(LIBRARY).pro
+include $(TOPDIR)/src/include/builddefs
+
+HEADERS = $(shell echo *.h)
+SOURCES = $(shell echo *.cpp)
+
+default: build-me
+
+ifeq "$(ENABLE_QT)" "true"
+build-me: $(PROJECT)
+ $(QTMAKE)
+else
+build-me:
+endif
+
+include $(BUILDRULES)
+
+install: default
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/libpcp_qwt/src/libpcp_qwt.pro b/src/libpcp_qwt/src/libpcp_qwt.pro
new file mode 100644
index 0000000..9b64ab5
--- /dev/null
+++ b/src/libpcp_qwt/src/libpcp_qwt.pro
@@ -0,0 +1,173 @@
+TARGET = pcp_qwt
+TEMPLATE = lib
+VERSION = 6.0.2
+CONFIG += qt staticlib warn_on
+QT = core gui network svg
+
+HEADERS += \
+ qwt.h \
+ qwt_abstract_scale_draw.h \
+ qwt_interval_symbol.h \
+ qwt_clipper.h \
+ qwt_color_map.h \
+ qwt_compat.h \
+ qwt_column_symbol.h \
+ qwt_interval.h \
+ qwt_dyngrid_layout.h \
+ qwt_global.h \
+ qwt_math.h \
+ qwt_magnifier.h \
+ qwt_null_paintdevice.h \
+ qwt_painter.h \
+ qwt_panner.h \
+ qwt_picker.h \
+ qwt_picker_machine.h \
+ qwt_point_3d.h \
+ qwt_point_polar.h \
+ qwt_round_scale_draw.h \
+ qwt_scale_div.h \
+ qwt_scale_draw.h \
+ qwt_scale_engine.h \
+ qwt_scale_map.h \
+ qwt_spline.h \
+ qwt_symbol.h \
+ qwt_system_clock.h \
+ qwt_text_engine.h \
+ qwt_text_label.h \
+ qwt_text.h
+
+SOURCES += \
+ qwt_abstract_scale_draw.cpp \
+ qwt_interval_symbol.cpp \
+ qwt_clipper.cpp \
+ qwt_color_map.cpp \
+ qwt_column_symbol.cpp \
+ qwt_interval.cpp \
+ qwt_dyngrid_layout.cpp \
+ qwt_math.cpp \
+ qwt_magnifier.cpp \
+ qwt_panner.cpp \
+ qwt_null_paintdevice.cpp \
+ qwt_painter.cpp \
+ qwt_picker.cpp \
+ qwt_round_scale_draw.cpp \
+ qwt_scale_div.cpp \
+ qwt_scale_draw.cpp \
+ qwt_scale_map.cpp \
+ qwt_spline.cpp \
+ qwt_text_engine.cpp \
+ qwt_text_label.cpp \
+ qwt_text.cpp \
+ qwt_event_pattern.cpp \
+ qwt_picker_machine.cpp \
+ qwt_point_3d.cpp \
+ qwt_point_polar.cpp \
+ qwt_scale_engine.cpp \
+ qwt_symbol.cpp \
+ qwt_system_clock.cpp
+
+# qwt plot
+HEADERS += \
+ qwt_curve_fitter.h \
+ qwt_event_pattern.h \
+ qwt_legend.h \
+ qwt_legend_item.h \
+ qwt_legend_itemmanager.h \
+ qwt_plot.h \
+ qwt_plot_renderer.h \
+ qwt_plot_curve.h \
+ qwt_plot_dict.h \
+ qwt_plot_directpainter.h \
+ qwt_plot_grid.h \
+ qwt_plot_histogram.h \
+ qwt_plot_item.h \
+ qwt_plot_intervalcurve.h \
+ qwt_plot_layout.h \
+ qwt_plot_marker.h \
+ qwt_plot_rasteritem.h \
+ qwt_plot_spectrogram.h \
+ qwt_plot_spectrocurve.h \
+ qwt_plot_scaleitem.h \
+ qwt_plot_seriesitem.h \
+ qwt_plot_canvas.h \
+ qwt_plot_panner.h \
+ qwt_plot_picker.h \
+ qwt_plot_zoomer.h \
+ qwt_plot_magnifier.h \
+ qwt_plot_rescaler.h \
+ qwt_raster_data.h \
+ qwt_matrix_raster_data.h \
+ qwt_sampling_thread.h \
+ qwt_series_data.h \
+ qwt_scale_widget.h
+
+SOURCES += \
+ qwt_curve_fitter.cpp \
+ qwt_legend.cpp \
+ qwt_legend_item.cpp \
+ qwt_plot.cpp \
+ qwt_plot_renderer.cpp \
+ qwt_plot_xml.cpp \
+ qwt_plot_axis.cpp \
+ qwt_plot_curve.cpp \
+ qwt_plot_dict.cpp \
+ qwt_plot_directpainter.cpp \
+ qwt_plot_grid.cpp \
+ qwt_plot_histogram.cpp \
+ qwt_plot_item.cpp \
+ qwt_plot_intervalcurve.cpp \
+ qwt_plot_spectrogram.cpp \
+ qwt_plot_spectrocurve.cpp \
+ qwt_plot_scaleitem.cpp \
+ qwt_plot_seriesitem.cpp \
+ qwt_plot_marker.cpp \
+ qwt_plot_layout.cpp \
+ qwt_plot_canvas.cpp \
+ qwt_plot_panner.cpp \
+ qwt_plot_rasteritem.cpp \
+ qwt_plot_picker.cpp \
+ qwt_plot_zoomer.cpp \
+ qwt_plot_magnifier.cpp \
+ qwt_plot_rescaler.cpp \
+ qwt_raster_data.cpp \
+ qwt_matrix_raster_data.cpp \
+ qwt_sampling_thread.cpp \
+ qwt_series_data.cpp \
+ qwt_scale_widget.cpp
+
+# svg
+HEADERS += qwt_plot_svgitem.h
+SOURCES += qwt_plot_svgitem.cpp
+
+# widgets
+HEADERS += \
+ qwt_abstract_slider.h \
+ qwt_abstract_scale.h \
+ qwt_arrow_button.h \
+ qwt_analog_clock.h \
+ qwt_compass.h \
+ qwt_compass_rose.h \
+ qwt_counter.h \
+ qwt_dial.h \
+ qwt_dial_needle.h \
+ qwt_double_range.h \
+ qwt_knob.h \
+ qwt_slider.h \
+ qwt_thermo.h \
+ qwt_wheel.h
+
+SOURCES += \
+ qwt_abstract_slider.cpp \
+ qwt_abstract_scale.cpp \
+ qwt_arrow_button.cpp \
+ qwt_analog_clock.cpp \
+ qwt_compass.cpp \
+ qwt_compass_rose.cpp \
+ qwt_counter.cpp \
+ qwt_dial.cpp \
+ qwt_dial_needle.cpp \
+ qwt_double_range.cpp \
+ qwt_knob.cpp \
+ qwt_slider.cpp \
+ qwt_thermo.cpp \
+ qwt_wheel.cpp
diff --git a/src/libpcp_qwt/src/qwt.h b/src/libpcp_qwt/src/qwt.h
new file mode 100644
index 0000000..3749493
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt.h
@@ -0,0 +1,22 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_H
+#define QWT_H
+
+#include "qwt_global.h"
+
+/*!
+ Some constants for use within Qwt.
+*/
+namespace Qwt
+{
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_abstract_scale.cpp b/src/libpcp_qwt/src/qwt_abstract_scale.cpp
new file mode 100644
index 0000000..e44776e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_scale.cpp
@@ -0,0 +1,310 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_abstract_scale.h"
+#include "qwt_scale_engine.h"
+#include "qwt_scale_draw.h"
+#include "qwt_scale_div.h"
+#include "qwt_scale_map.h"
+#include "qwt_interval.h"
+
+class QwtAbstractScale::PrivateData
+{
+public:
+ PrivateData():
+ maxMajor( 5 ),
+ maxMinor( 3 ),
+ stepSize( 0.0 ),
+ autoScale( true )
+ {
+ scaleEngine = new QwtLinearScaleEngine;
+ scaleDraw = new QwtScaleDraw();
+ }
+
+ ~PrivateData()
+ {
+ delete scaleEngine;
+ delete scaleDraw;
+ }
+
+ QwtScaleEngine *scaleEngine;
+ QwtAbstractScaleDraw *scaleDraw;
+
+ int maxMajor;
+ int maxMinor;
+ double stepSize;
+
+ bool autoScale;
+};
+
+/*!
+ Constructor
+
+ Creates a default QwtScaleDraw and a QwtLinearScaleEngine.
+ Autoscaling is enabled, and the stepSize is initialized by 0.0.
+*/
+
+QwtAbstractScale::QwtAbstractScale()
+{
+ d_data = new PrivateData;
+ rescale( 0.0, 100.0 );
+}
+
+//! Destructor
+QwtAbstractScale::~QwtAbstractScale()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Specify a scale.
+
+ Disable autoscaling and define a scale by an interval and a step size
+
+ \param vmin lower limit of the scale interval
+ \param vmax upper limit of the scale interval
+ \param stepSize major step size
+ \sa setAutoScale()
+*/
+void QwtAbstractScale::setScale( double vmin, double vmax, double stepSize )
+{
+ d_data->autoScale = false;
+ d_data->stepSize = stepSize;
+
+ rescale( vmin, vmax, stepSize );
+}
+
+/*!
+ \brief Specify a scale.
+
+ Disable autoscaling and define a scale by an interval and a step size
+
+ \param interval Interval
+ \param stepSize major step size
+ \sa setAutoScale()
+*/
+void QwtAbstractScale::setScale( const QwtInterval &interval, double stepSize )
+{
+ setScale( interval.minValue(), interval.maxValue(), stepSize );
+}
+
+
+/*!
+ \brief Specify a scale.
+
+ Disable autoscaling and define a scale by a scale division
+
+ \param scaleDiv Scale division
+ \sa setAutoScale()
+*/
+void QwtAbstractScale::setScale( const QwtScaleDiv &scaleDiv )
+{
+ d_data->autoScale = false;
+
+ if ( scaleDiv != d_data->scaleDraw->scaleDiv() )
+ {
+ d_data->scaleDraw->setScaleDiv( scaleDiv );
+ scaleChange();
+ }
+}
+
+/*!
+ Recalculate the scale division and update the scale draw.
+
+ \param vmin Lower limit of the scale interval
+ \param vmax Upper limit of the scale interval
+ \param stepSize Major step size
+
+ \sa scaleChange()
+*/
+void QwtAbstractScale::rescale( double vmin, double vmax, double stepSize )
+{
+ const QwtScaleDiv scaleDiv = d_data->scaleEngine->divideScale(
+ vmin, vmax, d_data->maxMajor, d_data->maxMinor, stepSize );
+
+ if ( scaleDiv != d_data->scaleDraw->scaleDiv() )
+ {
+ d_data->scaleDraw->setTransformation(
+ d_data->scaleEngine->transformation() );
+ d_data->scaleDraw->setScaleDiv( scaleDiv );
+ scaleChange();
+ }
+}
+
+/*!
+ \brief Advise the widget to control the scale range internally.
+
+ Autoscaling is on by default.
+ \sa setScale(), autoScale()
+*/
+void QwtAbstractScale::setAutoScale()
+{
+ if ( !d_data->autoScale )
+ {
+ d_data->autoScale = true;
+ scaleChange();
+ }
+}
+
+/*!
+ \return \c true if autoscaling is enabled
+*/
+bool QwtAbstractScale::autoScale() const
+{
+ return d_data->autoScale;
+}
+
+/*!
+ \brief Set the maximum number of major tick intervals.
+
+ The scale's major ticks are calculated automatically such that
+ the number of major intervals does not exceed ticks.
+ The default value is 5.
+ \param ticks maximal number of major ticks.
+ \sa QwtAbstractScaleDraw
+*/
+void QwtAbstractScale::setScaleMaxMajor( int ticks )
+{
+ if ( ticks != d_data->maxMajor )
+ {
+ d_data->maxMajor = ticks;
+ updateScaleDraw();
+ }
+}
+
+/*!
+ \brief Set the maximum number of minor tick intervals
+
+ The scale's minor ticks are calculated automatically such that
+ the number of minor intervals does not exceed ticks.
+ The default value is 3.
+ \param ticks
+ \sa QwtAbstractScaleDraw
+*/
+void QwtAbstractScale::setScaleMaxMinor( int ticks )
+{
+ if ( ticks != d_data->maxMinor )
+ {
+ d_data->maxMinor = ticks;
+ updateScaleDraw();
+ }
+}
+
+/*!
+ \return Max. number of minor tick intervals
+ The default value is 3.
+*/
+int QwtAbstractScale::scaleMaxMinor() const
+{
+ return d_data->maxMinor;
+}
+
+/*!
+ \return Max. number of major tick intervals
+ The default value is 5.
+*/
+int QwtAbstractScale::scaleMaxMajor() const
+{
+ return d_data->maxMajor;
+}
+
+/*!
+ \brief Set a scale draw
+
+ scaleDraw has to be created with new and will be deleted in
+ ~QwtAbstractScale or the next call of setAbstractScaleDraw.
+*/
+void QwtAbstractScale::setAbstractScaleDraw( QwtAbstractScaleDraw *scaleDraw )
+{
+ if ( scaleDraw == NULL || scaleDraw == d_data->scaleDraw )
+ return;
+
+ if ( d_data->scaleDraw != NULL )
+ scaleDraw->setScaleDiv( d_data->scaleDraw->scaleDiv() );
+
+ delete d_data->scaleDraw;
+ d_data->scaleDraw = scaleDraw;
+}
+
+/*!
+ \return Scale draw
+ \sa setAbstractScaleDraw()
+*/
+QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw()
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ \return Scale draw
+ \sa setAbstractScaleDraw()
+*/
+const QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw() const
+{
+ return d_data->scaleDraw;
+}
+
+void QwtAbstractScale::updateScaleDraw()
+{
+ rescale( d_data->scaleDraw->scaleDiv().lowerBound(),
+ d_data->scaleDraw->scaleDiv().upperBound(), d_data->stepSize );
+}
+
+/*!
+ \brief Set a scale engine
+
+ The scale engine is responsible for calculating the scale division,
+ and in case of auto scaling how to align the scale.
+
+ scaleEngine has to be created with new and will be deleted in
+ ~QwtAbstractScale or the next call of setScaleEngine.
+*/
+void QwtAbstractScale::setScaleEngine( QwtScaleEngine *scaleEngine )
+{
+ if ( scaleEngine != NULL && scaleEngine != d_data->scaleEngine )
+ {
+ delete d_data->scaleEngine;
+ d_data->scaleEngine = scaleEngine;
+ }
+}
+
+/*!
+ \return Scale engine
+ \sa setScaleEngine()
+*/
+const QwtScaleEngine *QwtAbstractScale::scaleEngine() const
+{
+ return d_data->scaleEngine;
+}
+
+/*!
+ \return Scale engine
+ \sa setScaleEngine()
+*/
+QwtScaleEngine *QwtAbstractScale::scaleEngine()
+{
+ return d_data->scaleEngine;
+}
+
+/*!
+ \brief Notify changed scale
+
+ Dummy empty implementation, intended to be overloaded by derived classes
+*/
+void QwtAbstractScale::scaleChange()
+{
+}
+
+/*!
+ \return abstractScaleDraw()->scaleMap()
+*/
+const QwtScaleMap &QwtAbstractScale::scaleMap() const
+{
+ return d_data->scaleDraw->scaleMap();
+}
diff --git a/src/libpcp_qwt/src/qwt_abstract_scale.h b/src/libpcp_qwt/src/qwt_abstract_scale.h
new file mode 100644
index 0000000..670d228
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_scale.h
@@ -0,0 +1,70 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ABSTRACT_SCALE_H
+#define QWT_ABSTRACT_SCALE_H
+
+#include "qwt_global.h"
+
+class QwtScaleEngine;
+class QwtAbstractScaleDraw;
+class QwtScaleDiv;
+class QwtScaleMap;
+class QwtInterval;
+
+/*!
+ \brief An abstract base class for classes containing a scale
+
+ QwtAbstractScale is used to provide classes with a QwtScaleDraw,
+ and a QwtScaleDiv. The QwtScaleDiv might be set explicitely
+ or calculated by a QwtScaleEngine.
+*/
+
+class QWT_EXPORT QwtAbstractScale
+{
+public:
+ QwtAbstractScale();
+ virtual ~QwtAbstractScale();
+
+ void setScale( double vmin, double vmax, double step = 0.0 );
+ void setScale( const QwtInterval &, double step = 0.0 );
+ void setScale( const QwtScaleDiv & );
+
+ void setAutoScale();
+ bool autoScale() const;
+
+ void setScaleMaxMajor( int ticks );
+ int scaleMaxMinor() const;
+
+ void setScaleMaxMinor( int ticks );
+ int scaleMaxMajor() const;
+
+ void setScaleEngine( QwtScaleEngine * );
+ const QwtScaleEngine *scaleEngine() const;
+ QwtScaleEngine *scaleEngine();
+
+ const QwtScaleMap &scaleMap() const;
+
+protected:
+ void rescale( double vmin, double vmax, double step = 0.0 );
+
+ void setAbstractScaleDraw( QwtAbstractScaleDraw * );
+ const QwtAbstractScaleDraw *abstractScaleDraw() const;
+ QwtAbstractScaleDraw *abstractScaleDraw();
+
+ virtual void scaleChange();
+
+private:
+ void updateScaleDraw();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_abstract_scale_draw.cpp b/src/libpcp_qwt/src/qwt_abstract_scale_draw.cpp
new file mode 100644
index 0000000..49230b7
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_scale_draw.cpp
@@ -0,0 +1,412 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_abstract_scale_draw.h"
+#include "qwt_math.h"
+#include "qwt_text.h"
+#include "qwt_painter.h"
+#include "qwt_scale_map.h"
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qmap.h>
+#include <qlocale.h>
+
+class QwtAbstractScaleDraw::PrivateData
+{
+public:
+ PrivateData():
+ spacing( 4.0 ),
+ penWidth( 0 ),
+ minExtent( 0.0 )
+ {
+ components = QwtAbstractScaleDraw::Backbone
+ | QwtAbstractScaleDraw::Ticks
+ | QwtAbstractScaleDraw::Labels;
+
+ tickLength[QwtScaleDiv::MinorTick] = 4.0;
+ tickLength[QwtScaleDiv::MediumTick] = 6.0;
+ tickLength[QwtScaleDiv::MajorTick] = 8.0;
+ }
+
+ ScaleComponents components;
+
+ QwtScaleMap map;
+ QwtScaleDiv scldiv;
+
+ double spacing;
+ double tickLength[QwtScaleDiv::NTickTypes];
+ int penWidth;
+
+ double minExtent;
+
+ QMap<double, QwtText> labelCache;
+};
+
+/*!
+ \brief Constructor
+
+ The range of the scale is initialized to [0, 100],
+ The spacing (distance between ticks and labels) is
+ set to 4, the tick lengths are set to 4,6 and 8 pixels
+*/
+QwtAbstractScaleDraw::QwtAbstractScaleDraw()
+{
+ d_data = new QwtAbstractScaleDraw::PrivateData;
+}
+
+//! Destructor
+QwtAbstractScaleDraw::~QwtAbstractScaleDraw()
+{
+ delete d_data;
+}
+
+/*!
+ En/Disable a component of the scale
+
+ \param component Scale component
+ \param enable On/Off
+
+ \sa hasComponent()
+*/
+void QwtAbstractScaleDraw::enableComponent(
+ ScaleComponent component, bool enable )
+{
+ if ( enable )
+ d_data->components |= component;
+ else
+ d_data->components &= ~component;
+}
+
+/*!
+ Check if a component is enabled
+ \sa enableComponent()
+*/
+bool QwtAbstractScaleDraw::hasComponent( ScaleComponent component ) const
+{
+ return ( d_data->components & component );
+}
+
+/*!
+ Change the scale division
+ \param sd New scale division
+*/
+void QwtAbstractScaleDraw::setScaleDiv( const QwtScaleDiv &sd )
+{
+ d_data->scldiv = sd;
+ d_data->map.setScaleInterval( sd.lowerBound(), sd.upperBound() );
+ d_data->labelCache.clear();
+}
+
+/*!
+ Change the transformation of the scale
+ \param transformation New scale transformation
+*/
+void QwtAbstractScaleDraw::setTransformation(
+ QwtScaleTransformation *transformation )
+{
+ d_data->map.setTransformation( transformation );
+}
+
+//! \return Map how to translate between scale and pixel values
+const QwtScaleMap &QwtAbstractScaleDraw::scaleMap() const
+{
+ return d_data->map;
+}
+
+//! \return Map how to translate between scale and pixel values
+QwtScaleMap &QwtAbstractScaleDraw::scaleMap()
+{
+ return d_data->map;
+}
+
+//! \return scale division
+const QwtScaleDiv& QwtAbstractScaleDraw::scaleDiv() const
+{
+ return d_data->scldiv;
+}
+
+/*!
+ \brief Specify the width of the scale pen
+ \param width Pen width
+ \sa penWidth()
+*/
+void QwtAbstractScaleDraw::setPenWidth( int width )
+{
+ if ( width < 0 )
+ width = 0;
+
+ if ( width != d_data->penWidth )
+ d_data->penWidth = width;
+}
+
+/*!
+ \return Scale pen width
+ \sa setPenWidth()
+*/
+int QwtAbstractScaleDraw::penWidth() const
+{
+ return d_data->penWidth;
+}
+
+/*!
+ \brief Draw the scale
+
+ \param painter The painter
+
+ \param palette Palette, text color is used for the labels,
+ foreground color for ticks and backbone
+*/
+void QwtAbstractScaleDraw::draw( QPainter *painter,
+ const QPalette& palette ) const
+{
+ painter->save();
+
+ QPen pen = painter->pen();
+ pen.setWidth( d_data->penWidth );
+ pen.setCosmetic( false );
+ painter->setPen( pen );
+
+ if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
+ {
+ painter->save();
+ painter->setPen( palette.color( QPalette::Text ) ); // ignore pen style
+
+ const QList<double> &majorTicks =
+ d_data->scldiv.ticks( QwtScaleDiv::MajorTick );
+
+ for ( int i = 0; i < majorTicks.count(); i++ )
+ {
+ const double v = majorTicks[i];
+ if ( d_data->scldiv.contains( v ) )
+ drawLabel( painter, v );
+ }
+
+ painter->restore();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ {
+ painter->save();
+
+ QPen pen = painter->pen();
+ pen.setColor( palette.color( QPalette::WindowText ) );
+ pen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( pen );
+
+ for ( int tickType = QwtScaleDiv::MinorTick;
+ tickType < QwtScaleDiv::NTickTypes; tickType++ )
+ {
+ const QList<double> &ticks = d_data->scldiv.ticks( tickType );
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ const double v = ticks[i];
+ if ( d_data->scldiv.contains( v ) )
+ drawTick( painter, v, d_data->tickLength[tickType] );
+ }
+ }
+
+ painter->restore();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
+ {
+ painter->save();
+
+ QPen pen = painter->pen();
+ pen.setColor( palette.color( QPalette::WindowText ) );
+ pen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( pen );
+
+ drawBackbone( painter );
+
+ painter->restore();
+ }
+
+ painter->restore();
+}
+
+/*!
+ \brief Set the spacing between tick and labels
+
+ The spacing is the distance between ticks and labels.
+ The default spacing is 4 pixels.
+
+ \param spacing Spacing
+
+ \sa spacing()
+*/
+void QwtAbstractScaleDraw::setSpacing( double spacing )
+{
+ if ( spacing < 0 )
+ spacing = 0;
+
+ d_data->spacing = spacing;
+}
+
+/*!
+ \brief Get the spacing
+
+ The spacing is the distance between ticks and labels.
+ The default spacing is 4 pixels.
+
+ \sa setSpacing()
+*/
+double QwtAbstractScaleDraw::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ \brief Set a minimum for the extent
+
+ The extent is calculated from the coomponents of the
+ scale draw. In situations, where the labels are
+ changing and the layout depends on the extent (f.e scrolling
+ a scale), setting an upper limit as minimum extent will
+ avoid jumps of the layout.
+
+ \param minExtent Minimum extent
+
+ \sa extent(), minimumExtent()
+*/
+void QwtAbstractScaleDraw::setMinimumExtent( double minExtent )
+{
+ if ( minExtent < 0.0 )
+ minExtent = 0.0;
+
+ d_data->minExtent = minExtent;
+}
+
+/*!
+ Get the minimum extent
+ \sa extent(), setMinimumExtent()
+*/
+double QwtAbstractScaleDraw::minimumExtent() const
+{
+ return d_data->minExtent;
+}
+
+/*!
+ Set the length of the ticks
+
+ \param tickType Tick type
+ \param length New length
+
+ \warning the length is limited to [0..1000]
+*/
+void QwtAbstractScaleDraw::setTickLength(
+ QwtScaleDiv::TickType tickType, double length )
+{
+ if ( tickType < QwtScaleDiv::MinorTick ||
+ tickType > QwtScaleDiv::MajorTick )
+ {
+ return;
+ }
+
+ if ( length < 0.0 )
+ length = 0.0;
+
+ const double maxTickLen = 1000.0;
+ if ( length > maxTickLen )
+ length = maxTickLen;
+
+ d_data->tickLength[tickType] = length;
+}
+
+/*!
+ Return the length of the ticks
+
+ \sa setTickLength(), maxTickLength()
+*/
+double QwtAbstractScaleDraw::tickLength( QwtScaleDiv::TickType tickType ) const
+{
+ if ( tickType < QwtScaleDiv::MinorTick ||
+ tickType > QwtScaleDiv::MajorTick )
+ {
+ return 0;
+ }
+
+ return d_data->tickLength[tickType];
+}
+
+/*!
+ \return Length of the longest tick
+
+ Useful for layout calculations
+ \sa tickLength(), setTickLength()
+*/
+double QwtAbstractScaleDraw::maxTickLength() const
+{
+ double length = 0.0;
+ for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
+ length = qMax( length, d_data->tickLength[i] );
+
+ return length;
+}
+
+/*!
+ \brief Convert a value into its representing label
+
+ The value is converted to a plain text using
+ QLocale::system().toString(value).
+ This method is often overloaded by applications to have individual
+ labels.
+
+ \param value Value
+ \return Label string.
+*/
+QwtText QwtAbstractScaleDraw::label( double value ) const
+{
+ return QLocale().toString( value );
+}
+
+/*!
+ \brief Convert a value into its representing label and cache it.
+
+ The conversion between value and label is called very often
+ in the layout and painting code. Unfortunately the
+ calculation of the label sizes might be slow (really slow
+ for rich text in Qt4), so it's necessary to cache the labels.
+
+ \param font Font
+ \param value Value
+
+ \return Tick label
+*/
+const QwtText &QwtAbstractScaleDraw::tickLabel(
+ const QFont &font, double value ) const
+{
+ QMap<double, QwtText>::const_iterator it = d_data->labelCache.find( value );
+ if ( it == d_data->labelCache.end() )
+ {
+ QwtText lbl = label( value );
+ lbl.setRenderFlags( 0 );
+ lbl.setLayoutAttribute( QwtText::MinimumLayout );
+
+ ( void )lbl.textSize( font ); // initialize the internal cache
+
+ it = d_data->labelCache.insert( value, lbl );
+ }
+
+ return ( *it );
+}
+
+/*!
+ Invalidate the cache used by QwtAbstractScaleDraw::tickLabel
+
+ The cache is invalidated, when a new QwtScaleDiv is set. If
+ the labels need to be changed. while the same QwtScaleDiv is set,
+ invalidateCache() needs to be called manually.
+*/
+void QwtAbstractScaleDraw::invalidateCache()
+{
+ d_data->labelCache.clear();
+}
diff --git a/src/libpcp_qwt/src/qwt_abstract_scale_draw.h b/src/libpcp_qwt/src/qwt_abstract_scale_draw.h
new file mode 100644
index 0000000..1dd64c0
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_scale_draw.h
@@ -0,0 +1,139 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ABSTRACT_SCALE_DRAW_H
+#define QWT_ABSTRACT_SCALE_DRAW_H
+
+#include "qwt_global.h"
+#include "qwt_scale_div.h"
+#include "qwt_text.h"
+
+class QPalette;
+class QPainter;
+class QFont;
+class QwtScaleTransformation;
+class QwtScaleMap;
+
+/*!
+ \brief A abstract base class for drawing scales
+
+ QwtAbstractScaleDraw can be used to draw linear or logarithmic scales.
+
+ After a scale division has been specified as a QwtScaleDiv object
+ using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s),
+ the scale can be drawn with the QwtAbstractScaleDraw::draw() member.
+*/
+class QWT_EXPORT QwtAbstractScaleDraw
+{
+public:
+
+ /*!
+ Components of a scale
+ \sa enableComponent(), hasComponent
+ */
+ enum ScaleComponent
+ {
+ //! Backbone = the line where the ticks are located
+ Backbone = 0x01,
+
+ //! Ticks
+ Ticks = 0x02,
+
+ //! Labels
+ Labels = 0x04
+ };
+
+ //! Scale components
+ typedef QFlags<ScaleComponent> ScaleComponents;
+
+ QwtAbstractScaleDraw();
+ virtual ~QwtAbstractScaleDraw();
+
+ void setScaleDiv( const QwtScaleDiv &s );
+ const QwtScaleDiv& scaleDiv() const;
+
+ void setTransformation( QwtScaleTransformation * );
+ const QwtScaleMap &scaleMap() const;
+ QwtScaleMap &scaleMap();
+
+ void enableComponent( ScaleComponent, bool enable = true );
+ bool hasComponent( ScaleComponent ) const;
+
+ void setTickLength( QwtScaleDiv::TickType, double length );
+ double tickLength( QwtScaleDiv::TickType ) const;
+ double maxTickLength() const;
+
+ void setSpacing( double margin );
+ double spacing() const;
+
+ void setPenWidth( int width );
+ int penWidth() const;
+
+ virtual void draw( QPainter *, const QPalette & ) const;
+
+ virtual QwtText label( double ) const;
+
+ /*!
+ Calculate the extent
+
+ The extent is the distcance from the baseline to the outermost
+ pixel of the scale draw in opposite to its orientation.
+ It is at least minimumExtent() pixels.
+
+ \sa setMinimumExtent(), minimumExtent()
+ */
+ virtual double extent( const QFont & ) const = 0;
+
+ void setMinimumExtent( double );
+ double minimumExtent() const;
+
+protected:
+ /*!
+ Draw a tick
+
+ \param painter Painter
+ \param value Value of the tick
+ \param len Lenght of the tick
+
+ \sa drawBackbone(), drawLabel()
+ */
+ virtual void drawTick( QPainter *painter, double value, double len ) const = 0;
+
+ /*!
+ Draws the baseline of the scale
+ \param painter Painter
+
+ \sa drawTick(), drawLabel()
+ */
+ virtual void drawBackbone( QPainter *painter ) const = 0;
+
+ /*!
+ Draws the label for a major scale tick
+
+ \param painter Painter
+ \param value Value
+
+ \sa drawTick(), drawBackbone()
+ */
+ virtual void drawLabel( QPainter *painter, double value ) const = 0;
+
+ void invalidateCache();
+ const QwtText &tickLabel( const QFont &, double value ) const;
+
+private:
+ QwtAbstractScaleDraw( const QwtAbstractScaleDraw & );
+ QwtAbstractScaleDraw &operator=( const QwtAbstractScaleDraw & );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtAbstractScaleDraw::ScaleComponents )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_abstract_slider.cpp b/src/libpcp_qwt/src/qwt_abstract_slider.cpp
new file mode 100644
index 0000000..e2dbcff
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_slider.cpp
@@ -0,0 +1,597 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_abstract_slider.h"
+#include "qwt_math.h"
+#include <qevent.h>
+#include <qdatetime.h>
+
+#if QT_VERSION < 0x040601
+#define qFabs(x) ::fabs(x)
+#define qExp(x) ::exp(x)
+#endif
+
+class QwtAbstractSlider::PrivateData
+{
+public:
+ PrivateData():
+ scrollMode( QwtAbstractSlider::ScrNone ),
+ mouseOffset( 0.0 ),
+ tracking( true ),
+ tmrID( 0 ),
+ updTime( 150 ),
+ mass( 0.0 ),
+ readOnly( false )
+ {
+ }
+
+ QwtAbstractSlider::ScrollMode scrollMode;
+ double mouseOffset;
+ int direction;
+ int tracking;
+
+ int tmrID;
+ int updTime;
+ int timerTick;
+ QTime time;
+ double speed;
+ double mass;
+ Qt::Orientation orientation;
+ bool readOnly;
+};
+
+/*!
+ \brief Constructor
+
+ \param orientation Orientation
+ \param parent Parent widget
+*/
+QwtAbstractSlider::QwtAbstractSlider(
+ Qt::Orientation orientation, QWidget *parent ):
+ QWidget( parent, NULL )
+{
+ d_data = new QwtAbstractSlider::PrivateData;
+ d_data->orientation = orientation;
+
+ setFocusPolicy( Qt::TabFocus );
+}
+
+//! Destructor
+QwtAbstractSlider::~QwtAbstractSlider()
+{
+ if ( d_data->tmrID )
+ killTimer( d_data->tmrID );
+
+ delete d_data;
+}
+
+/*!
+ En/Disable read only mode
+
+ In read only mode the slider can't be controlled by mouse
+ or keyboard.
+
+ \param readOnly Enables in case of true
+ \sa isReadOnly()
+*/
+void QwtAbstractSlider::setReadOnly( bool readOnly )
+{
+ d_data->readOnly = readOnly;
+ update();
+}
+
+/*!
+ In read only mode the slider can't be controlled by mouse
+ or keyboard.
+
+ \return true if read only
+ \sa setReadOnly()
+*/
+bool QwtAbstractSlider::isReadOnly() const
+{
+ return d_data->readOnly;
+}
+
+/*!
+ \brief Set the orientation.
+ \param o Orientation. Allowed values are
+ Qt::Horizontal and Qt::Vertical.
+*/
+void QwtAbstractSlider::setOrientation( Qt::Orientation o )
+{
+ d_data->orientation = o;
+}
+
+/*!
+ \return Orientation
+ \sa setOrientation()
+*/
+Qt::Orientation QwtAbstractSlider::orientation() const
+{
+ return d_data->orientation;
+}
+
+//! Stop updating if automatic scrolling is active
+
+void QwtAbstractSlider::stopMoving()
+{
+ if ( d_data->tmrID )
+ {
+ killTimer( d_data->tmrID );
+ d_data->tmrID = 0;
+ }
+}
+
+/*!
+ \brief Specify the update interval for automatic scrolling
+ \param t update interval in milliseconds
+ \sa getScrollMode()
+*/
+void QwtAbstractSlider::setUpdateTime( int t )
+{
+ if ( t < 50 )
+ t = 50;
+ d_data->updTime = t;
+}
+
+
+/*!
+ Mouse press event handler
+ \param e Mouse event
+*/
+void QwtAbstractSlider::mousePressEvent( QMouseEvent *e )
+{
+ if ( isReadOnly() )
+ {
+ e->ignore();
+ return;
+ }
+ if ( !isValid() )
+ return;
+
+ const QPoint &p = e->pos();
+
+ d_data->timerTick = 0;
+
+ getScrollMode( p, d_data->scrollMode, d_data->direction );
+ stopMoving();
+
+ switch ( d_data->scrollMode )
+ {
+ case ScrPage:
+ case ScrTimer:
+ d_data->mouseOffset = 0;
+ d_data->tmrID = startTimer( qMax( 250, 2 * d_data->updTime ) );
+ break;
+
+ case ScrMouse:
+ d_data->time.start();
+ d_data->speed = 0;
+ d_data->mouseOffset = getValue( p ) - value();
+ Q_EMIT sliderPressed();
+ break;
+
+ default:
+ d_data->mouseOffset = 0;
+ d_data->direction = 0;
+ break;
+ }
+}
+
+
+//! Emits a valueChanged() signal if necessary
+void QwtAbstractSlider::buttonReleased()
+{
+ if ( ( !d_data->tracking ) || ( value() != prevValue() ) )
+ Q_EMIT valueChanged( value() );
+}
+
+
+/*!
+ Mouse Release Event handler
+ \param e Mouse event
+*/
+void QwtAbstractSlider::mouseReleaseEvent( QMouseEvent *e )
+{
+ if ( isReadOnly() )
+ {
+ e->ignore();
+ return;
+ }
+ if ( !isValid() )
+ return;
+
+ const double inc = step();
+
+ switch ( d_data->scrollMode )
+ {
+ case ScrMouse:
+ {
+ setPosition( e->pos() );
+ d_data->direction = 0;
+ d_data->mouseOffset = 0;
+ if ( d_data->mass > 0.0 )
+ {
+ const int ms = d_data->time.elapsed();
+ if ( ( qFabs( d_data->speed ) > 0.0 ) && ( ms < 50 ) )
+ d_data->tmrID = startTimer( d_data->updTime );
+ }
+ else
+ {
+ d_data->scrollMode = ScrNone;
+ buttonReleased();
+ }
+ Q_EMIT sliderReleased();
+
+ break;
+ }
+
+ case ScrDirect:
+ {
+ setPosition( e->pos() );
+ d_data->direction = 0;
+ d_data->mouseOffset = 0;
+ d_data->scrollMode = ScrNone;
+ buttonReleased();
+ break;
+ }
+
+ case ScrPage:
+ {
+ stopMoving();
+ if ( !d_data->timerTick )
+ QwtDoubleRange::incPages( d_data->direction );
+ d_data->timerTick = 0;
+ buttonReleased();
+ d_data->scrollMode = ScrNone;
+ break;
+ }
+
+ case ScrTimer:
+ {
+ stopMoving();
+ if ( !d_data->timerTick )
+ QwtDoubleRange::fitValue( value() + double( d_data->direction ) * inc );
+ d_data->timerTick = 0;
+ buttonReleased();
+ d_data->scrollMode = ScrNone;
+ break;
+ }
+
+ default:
+ {
+ d_data->scrollMode = ScrNone;
+ buttonReleased();
+ }
+ }
+}
+
+
+/*!
+ Move the slider to a specified point, adjust the value
+ and emit signals if necessary.
+*/
+void QwtAbstractSlider::setPosition( const QPoint &p )
+{
+ QwtDoubleRange::fitValue( getValue( p ) - d_data->mouseOffset );
+}
+
+
+/*!
+ \brief Enables or disables tracking.
+
+ If tracking is enabled, the slider emits a
+ valueChanged() signal whenever its value
+ changes (the default behaviour). If tracking
+ is disabled, the value changed() signal will only
+ be emitted if:<ul>
+ <li>the user releases the mouse
+ button and the value has changed or
+ <li>at the end of automatic scrolling.</ul>
+ Tracking is enabled by default.
+ \param enable \c true (enable) or \c false (disable) tracking.
+*/
+void QwtAbstractSlider::setTracking( bool enable )
+{
+ d_data->tracking = enable;
+}
+
+/*!
+ Mouse Move Event handler
+ \param e Mouse event
+*/
+void QwtAbstractSlider::mouseMoveEvent( QMouseEvent *e )
+{
+ if ( isReadOnly() )
+ {
+ e->ignore();
+ return;
+ }
+
+ if ( !isValid() )
+ return;
+
+ if ( d_data->scrollMode == ScrMouse )
+ {
+ setPosition( e->pos() );
+ if ( d_data->mass > 0.0 )
+ {
+ double ms = double( d_data->time.elapsed() );
+ if ( ms < 1.0 )
+ ms = 1.0;
+ d_data->speed = ( exactValue() - exactPrevValue() ) / ms;
+ d_data->time.start();
+ }
+ if ( value() != prevValue() )
+ Q_EMIT sliderMoved( value() );
+ }
+}
+
+/*!
+ Wheel Event handler
+ \param e Whell event
+*/
+void QwtAbstractSlider::wheelEvent( QWheelEvent *e )
+{
+ if ( isReadOnly() )
+ {
+ e->ignore();
+ return;
+ }
+
+ if ( !isValid() )
+ return;
+
+ QwtAbstractSlider::ScrollMode mode = ScrNone;
+ int direction = 0;
+
+ // Give derived classes a chance to say ScrNone
+ getScrollMode( e->pos(), mode, direction );
+ if ( mode != QwtAbstractSlider::ScrNone )
+ {
+ // Most mouse types work in steps of 15 degrees, in which case
+ // the delta value is a multiple of 120
+
+ const int inc = e->delta() / 120;
+ QwtDoubleRange::incPages( inc );
+ if ( value() != prevValue() )
+ Q_EMIT sliderMoved( value() );
+ }
+}
+
+/*!
+ Handles key events
+
+ - Key_Down, KeyLeft\n
+ Decrement by 1
+ - Key_Up, Key_Right\n
+ Increment by 1
+
+ \param e Key event
+ \sa isReadOnly()
+*/
+void QwtAbstractSlider::keyPressEvent( QKeyEvent *e )
+{
+ if ( isReadOnly() )
+ {
+ e->ignore();
+ return;
+ }
+
+ if ( !isValid() )
+ return;
+
+ int increment = 0;
+ switch ( e->key() )
+ {
+ case Qt::Key_Down:
+ if ( orientation() == Qt::Vertical )
+ increment = -1;
+ break;
+ case Qt::Key_Up:
+ if ( orientation() == Qt::Vertical )
+ increment = 1;
+ break;
+ case Qt::Key_Left:
+ if ( orientation() == Qt::Horizontal )
+ increment = -1;
+ break;
+ case Qt::Key_Right:
+ if ( orientation() == Qt::Horizontal )
+ increment = 1;
+ break;
+ default:;
+ e->ignore();
+ }
+
+ if ( increment != 0 )
+ {
+ QwtDoubleRange::incValue( increment );
+ if ( value() != prevValue() )
+ Q_EMIT sliderMoved( value() );
+ }
+}
+
+/*!
+ Qt timer event
+ \param e Timer event
+*/
+void QwtAbstractSlider::timerEvent( QTimerEvent * )
+{
+ const double inc = step();
+
+ switch ( d_data->scrollMode )
+ {
+ case ScrMouse:
+ {
+ if ( d_data->mass > 0.0 )
+ {
+ d_data->speed *= qExp( - double( d_data->updTime ) * 0.001 / d_data->mass );
+ const double newval =
+ exactValue() + d_data->speed * double( d_data->updTime );
+ QwtDoubleRange::fitValue( newval );
+ // stop if d_data->speed < one step per second
+ if ( qFabs( d_data->speed ) < 0.001 * qFabs( step() ) )
+ {
+ d_data->speed = 0;
+ stopMoving();
+ buttonReleased();
+ }
+
+ }
+ else
+ stopMoving();
+ break;
+ }
+
+ case ScrPage:
+ {
+ QwtDoubleRange::incPages( d_data->direction );
+ if ( !d_data->timerTick )
+ {
+ killTimer( d_data->tmrID );
+ d_data->tmrID = startTimer( d_data->updTime );
+ }
+ break;
+ }
+ case ScrTimer:
+ {
+ QwtDoubleRange::fitValue( value() + double( d_data->direction ) * inc );
+ if ( !d_data->timerTick )
+ {
+ killTimer( d_data->tmrID );
+ d_data->tmrID = startTimer( d_data->updTime );
+ }
+ break;
+ }
+ default:
+ {
+ stopMoving();
+ break;
+ }
+ }
+
+ d_data->timerTick = 1;
+}
+
+
+/*!
+ Notify change of value
+
+ This function can be reimplemented by derived classes
+ in order to keep track of changes, i.e. repaint the widget.
+ The default implementation emits a valueChanged() signal
+ if tracking is enabled.
+*/
+void QwtAbstractSlider::valueChange()
+{
+ if ( d_data->tracking )
+ Q_EMIT valueChanged( value() );
+}
+
+/*!
+ \brief Set the slider's mass for flywheel effect.
+
+ If the slider's mass is greater then 0, it will continue
+ to move after the mouse button has been released. Its speed
+ decreases with time at a rate depending on the slider's mass.
+ A large mass means that it will continue to move for a
+ long time.
+
+ Derived widgets may overload this function to make it public.
+
+ \param val New mass in kg
+
+ \bug If the mass is smaller than 1g, it is set to zero.
+ The maximal mass is limited to 100kg.
+ \sa mass()
+*/
+void QwtAbstractSlider::setMass( double val )
+{
+ if ( val < 0.001 )
+ d_data->mass = 0.0;
+ else if ( val > 100.0 )
+ d_data->mass = 100.0;
+ else
+ d_data->mass = val;
+}
+
+/*!
+ \return mass
+ \sa setMass()
+*/
+double QwtAbstractSlider::mass() const
+{
+ return d_data->mass;
+}
+
+
+/*!
+ \brief Move the slider to a specified value
+
+ This function can be used to move the slider to a value
+ which is not an integer multiple of the step size.
+ \param val new value
+ \sa fitValue()
+*/
+void QwtAbstractSlider::setValue( double val )
+{
+ if ( d_data->scrollMode == ScrMouse )
+ stopMoving();
+ QwtDoubleRange::setValue( val );
+}
+
+
+/*!
+ \brief Set the slider's value to the nearest integer multiple
+ of the step size.
+
+ \param value Value
+ \sa setValue(), incValue()
+*/
+void QwtAbstractSlider::fitValue( double value )
+{
+ if ( d_data->scrollMode == ScrMouse )
+ stopMoving();
+ QwtDoubleRange::fitValue( value );
+}
+
+/*!
+ \brief Increment the value by a specified number of steps
+ \param steps number of steps
+ \sa setValue()
+*/
+void QwtAbstractSlider::incValue( int steps )
+{
+ if ( d_data->scrollMode == ScrMouse )
+ stopMoving();
+ QwtDoubleRange::incValue( steps );
+}
+
+/*!
+ \sa mouseOffset()
+*/
+void QwtAbstractSlider::setMouseOffset( double offset )
+{
+ d_data->mouseOffset = offset;
+}
+
+/*!
+ \sa setMouseOffset()
+*/
+double QwtAbstractSlider::mouseOffset() const
+{
+ return d_data->mouseOffset;
+}
+
+//! sa ScrollMode
+int QwtAbstractSlider::scrollMode() const
+{
+ return d_data->scrollMode;
+}
diff --git a/src/libpcp_qwt/src/qwt_abstract_slider.h b/src/libpcp_qwt/src/qwt_abstract_slider.h
new file mode 100644
index 0000000..d9facbb
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_abstract_slider.h
@@ -0,0 +1,188 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ABSTRACT_SLIDER_H
+#define QWT_ABSTRACT_SLIDER_H
+
+#include "qwt_global.h"
+#include "qwt_double_range.h"
+#include <qwidget.h>
+
+/*!
+ \brief An abstract base class for slider widgets
+
+ QwtAbstractSlider is a base class for
+ slider widgets. It handles mouse events
+ and updates the slider's value accordingly. Derived classes
+ only have to implement the getValue() and
+ getScrollMode() members, and should react to a
+ valueChange(), which normally requires repainting.
+*/
+
+class QWT_EXPORT QwtAbstractSlider : public QWidget, public QwtDoubleRange
+{
+ Q_OBJECT
+ Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly )
+ Q_PROPERTY( bool valid READ isValid WRITE setValid )
+ Q_PROPERTY( double mass READ mass WRITE setMass )
+ Q_PROPERTY( Qt::Orientation orientation
+ READ orientation WRITE setOrientation )
+
+public:
+ /*!
+ Scroll mode
+ \sa getScrollMode()
+ */
+ enum ScrollMode
+ {
+ //! Scrolling switched off. Don't change the value.
+ ScrNone,
+
+ /*!
+ Change the value while the user keeps the
+ button pressed and moves the mouse.
+ */
+ ScrMouse,
+
+ /*!
+ Automatic scrolling. Increment the value in the specified direction
+ as long as the user keeps the button pressed.
+ */
+ ScrTimer,
+
+ ScrDirect,
+
+ //! Automatic scrolling. Same as ScrTimer, but increment by page size.
+ ScrPage
+ };
+
+ explicit QwtAbstractSlider( Qt::Orientation, QWidget *parent = NULL );
+ virtual ~QwtAbstractSlider();
+
+ void setUpdateTime( int t );
+ void stopMoving();
+ void setTracking( bool enable );
+
+ virtual void setMass( double val );
+ virtual double mass() const;
+
+ virtual void setOrientation( Qt::Orientation o );
+ Qt::Orientation orientation() const;
+
+ bool isReadOnly() const;
+
+ /*
+ Wrappers for QwtDblRange::isValid/QwtDblRange::setValid made
+ to be available as Q_PROPERTY in the designer.
+ */
+
+ /*!
+ \sa QwtDblRange::isValid()
+ */
+ bool isValid() const
+ {
+ return QwtDoubleRange::isValid();
+ }
+
+ /*!
+ \param valid true/false
+ \sa QwtDblRange::isValid()
+ */
+ void setValid( bool valid )
+ {
+ QwtDoubleRange::setValid( valid );
+ }
+
+public Q_SLOTS:
+ virtual void setValue( double val );
+ virtual void fitValue( double val );
+ virtual void incValue( int steps );
+
+ virtual void setReadOnly( bool );
+
+Q_SIGNALS:
+
+ /*!
+ \brief Notify a change of value.
+
+ In the default setting
+ (tracking enabled), this signal will be emitted every
+ time the value changes ( see setTracking() ).
+ \param value new value
+ */
+ void valueChanged( double value );
+
+ /*!
+ This signal is emitted when the user presses the
+ movable part of the slider (start ScrMouse Mode).
+ */
+ void sliderPressed();
+
+ /*!
+ This signal is emitted when the user releases the
+ movable part of the slider.
+ */
+
+ void sliderReleased();
+ /*!
+ This signal is emitted when the user moves the
+ slider with the mouse.
+ \param value new value
+ */
+ void sliderMoved( double value );
+
+protected:
+ virtual void setPosition( const QPoint & );
+ virtual void valueChange();
+
+ virtual void timerEvent( QTimerEvent *e );
+ virtual void mousePressEvent( QMouseEvent *e );
+ virtual void mouseReleaseEvent( QMouseEvent *e );
+ virtual void mouseMoveEvent( QMouseEvent *e );
+ virtual void keyPressEvent( QKeyEvent *e );
+ virtual void wheelEvent( QWheelEvent *e );
+
+ /*!
+ \brief Determine the value corresponding to a specified poind
+
+ This is an abstract virtual function which is called when
+ the user presses or releases a mouse button or moves the
+ mouse. It has to be implemented by the derived class.
+ \param p point
+ */
+ virtual double getValue( const QPoint & p ) = 0;
+
+ /*!
+ \brief Determine what to do when the user presses a mouse button.
+
+ This function is abstract and has to be implemented by derived classes.
+ It is called on a mousePress event. The derived class can determine
+ what should happen next in dependence of the position where the mouse
+ was pressed by returning scrolling mode and direction.
+
+ \param pos point where the mouse was pressed
+ \retval scrollMode The scrolling mode
+ \retval direction direction: 1, 0, or -1.
+ */
+ virtual void getScrollMode( const QPoint &pos,
+ ScrollMode &scrollMode, int &direction ) const = 0;
+
+ void setMouseOffset( double );
+ double mouseOffset() const;
+
+ int scrollMode() const;
+
+private:
+ void buttonReleased();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_analog_clock.cpp b/src/libpcp_qwt/src/qwt_analog_clock.cpp
new file mode 100644
index 0000000..fe723d1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_analog_clock.cpp
@@ -0,0 +1,228 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_analog_clock.h"
+#include <qmath.h>
+
+/*!
+ Constructor
+ \param parent Parent widget
+*/
+QwtAnalogClock::QwtAnalogClock( QWidget *parent ):
+ QwtDial( parent )
+{
+ initClock();
+}
+
+void QwtAnalogClock::initClock()
+{
+ setWrapping( true );
+ setReadOnly( true );
+
+ setOrigin( 270.0 );
+ setRange( 0.0, 60.0 * 60.0 * 12.0 ); // seconds
+ setScale( -1, 5, 60.0 * 60.0 );
+
+ setScaleComponents(
+ QwtAbstractScaleDraw::Ticks | QwtAbstractScaleDraw::Labels );
+ setScaleTicks( 1, 0, 8 );
+ scaleDraw()->setSpacing( 8 );
+
+ QColor knobColor = palette().color( QPalette::Active, QPalette::Text );
+ knobColor = knobColor.dark( 120 );
+
+ QColor handColor;
+ int width;
+
+ for ( int i = 0; i < NHands; i++ )
+ {
+ if ( i == SecondHand )
+ {
+ width = 2;
+ handColor = knobColor.dark( 120 );
+ }
+ else
+ {
+ width = 8;
+ handColor = knobColor;
+ }
+
+ QwtDialSimpleNeedle *hand = new QwtDialSimpleNeedle(
+ QwtDialSimpleNeedle::Arrow, true, handColor, knobColor );
+ hand->setWidth( width );
+
+ d_hand[i] = NULL;
+ setHand( ( Hand )i, hand );
+ }
+}
+
+//! Destructor
+QwtAnalogClock::~QwtAnalogClock()
+{
+ for ( int i = 0; i < NHands; i++ )
+ delete d_hand[i];
+}
+
+/*!
+ Nop method, use setHand instead
+ \sa setHand()
+*/
+void QwtAnalogClock::setNeedle( QwtDialNeedle * )
+{
+ // no op
+ return;
+}
+
+/*!
+ Set a clockhand
+ \param hand Specifies the type of hand
+ \param needle Hand
+ \sa hand()
+*/
+void QwtAnalogClock::setHand( Hand hand, QwtDialNeedle *needle )
+{
+ if ( hand >= 0 || hand < NHands )
+ {
+ delete d_hand[hand];
+ d_hand[hand] = needle;
+ }
+}
+
+/*!
+ \return Clock hand
+ \param hd Specifies the type of hand
+ \sa setHand()
+*/
+QwtDialNeedle *QwtAnalogClock::hand( Hand hd )
+{
+ if ( hd < 0 || hd >= NHands )
+ return NULL;
+
+ return d_hand[hd];
+}
+
+/*!
+ \return Clock hand
+ \param hd Specifies the type of hand
+ \sa setHand()
+*/
+const QwtDialNeedle *QwtAnalogClock::hand( Hand hd ) const
+{
+ return const_cast<QwtAnalogClock *>( this )->hand( hd );
+}
+
+/*!
+ \brief Set the current time
+
+ This is the same as QwtAnalogClock::setTime(), but Qt < 3.0
+ can't handle default parameters for slots.
+*/
+void QwtAnalogClock::setCurrentTime()
+{
+ setTime( QTime::currentTime() );
+}
+
+/*!
+ Set a time
+ \param time Time to display
+*/
+void QwtAnalogClock::setTime( const QTime &time )
+{
+ if ( time.isValid() )
+ {
+ setValue( ( time.hour() % 12 ) * 60.0 * 60.0
+ + time.minute() * 60.0 + time.second() );
+ }
+ else
+ setValid( false );
+}
+
+/*!
+ Find the scale label for a given value
+
+ \param value Value
+ \return Label
+*/
+QwtText QwtAnalogClock::scaleLabel( double value ) const
+{
+ if ( qFuzzyCompare( value + 1.0, 1.0 ) )
+ value = 60.0 * 60.0 * 12.0;
+
+ return QString::number( qRound( value / ( 60.0 * 60.0 ) ) );
+}
+
+/*!
+ \brief Draw the needle
+
+ A clock has no single needle but three hands instead. drawNeedle
+ translates value() into directions for the hands and calls
+ drawHand().
+
+ \param painter Painter
+ \param center Center of the clock
+ \param radius Maximum length for the hands
+ \param dir Dummy, not used.
+ \param colorGroup ColorGroup
+
+ \sa drawHand()
+*/
+void QwtAnalogClock::drawNeedle( QPainter *painter, const QPointF &center,
+ double radius, double dir, QPalette::ColorGroup colorGroup ) const
+{
+ Q_UNUSED( dir );
+
+ if ( isValid() )
+ {
+ const double hours = value() / ( 60.0 * 60.0 );
+ const double minutes =
+ ( value() - qFloor(hours) * 60.0 * 60.0 ) / 60.0;
+ const double seconds = value() - qFloor(hours) * 60.0 * 60.0
+ - qFloor(minutes) * 60.0;
+
+ double angle[NHands];
+ angle[HourHand] = 360.0 * hours / 12.0;
+ angle[MinuteHand] = 360.0 * minutes / 60.0;
+ angle[SecondHand] = 360.0 * seconds / 60.0;
+
+ for ( int hand = 0; hand < NHands; hand++ )
+ {
+ double d = angle[hand];
+ if ( direction() == Clockwise )
+ d = 360.0 - d;
+
+ d -= origin();
+
+ drawHand( painter, ( Hand )hand, center, radius, d, colorGroup );
+ }
+ }
+}
+
+/*!
+ Draw a clock hand
+
+ \param painter Painter
+ \param hd Specify the type of hand
+ \param center Center of the clock
+ \param radius Maximum length for the hands
+ \param direction Direction of the hand in degrees, counter clockwise
+ \param cg ColorGroup
+*/
+void QwtAnalogClock::drawHand( QPainter *painter, Hand hd,
+ const QPointF &center, double radius, double direction,
+ QPalette::ColorGroup cg ) const
+{
+ const QwtDialNeedle *needle = hand( hd );
+ if ( needle )
+ {
+ if ( hd == HourHand )
+ radius = qRound( 0.8 * radius );
+
+ needle->draw( painter, center, radius, direction, cg );
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_analog_clock.h b/src/libpcp_qwt/src/qwt_analog_clock.h
new file mode 100644
index 0000000..f20c3f1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_analog_clock.h
@@ -0,0 +1,96 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ANALOG_CLOCK_H
+#define QWT_ANALOG_CLOCK_H
+
+#include "qwt_global.h"
+#include "qwt_dial.h"
+#include "qwt_dial_needle.h"
+#include <qdatetime.h>
+
+/*!
+ \brief An analog clock
+
+ \image html analogclock.png
+
+ \par Example
+ \verbatim #include <qwt_analog_clock.h>
+
+ QwtAnalogClock *clock = new QwtAnalogClock(...);
+ clock->scaleDraw()->setPenWidth(3);
+ clock->setLineWidth(6);
+ clock->setFrameShadow(QwtDial::Sunken);
+ clock->setTime();
+
+ // update the clock every second
+ QTimer *timer = new QTimer(clock);
+ timer->connect(timer, SIGNAL(timeout()), clock, SLOT(setCurrentTime()));
+ timer->start(1000);
+
+ \endverbatim
+
+ Qwt is missing a set of good looking hands.
+ Contributions are very welcome.
+
+ \note The examples/dials example shows how to use QwtAnalogClock.
+*/
+
+class QWT_EXPORT QwtAnalogClock: public QwtDial
+{
+ Q_OBJECT
+
+public:
+ /*!
+ Hand type
+ \sa setHand(), hand()
+ */
+ enum Hand
+ {
+ //! Needle displaying the seconds
+ SecondHand,
+
+ //! Needle displaying the minutes
+ MinuteHand,
+
+ //! Needle displaying the hours
+ HourHand,
+
+ //! Number of needles
+ NHands
+ };
+
+ explicit QwtAnalogClock( QWidget* parent = NULL );
+ virtual ~QwtAnalogClock();
+
+ virtual void setHand( Hand, QwtDialNeedle * );
+ const QwtDialNeedle *hand( Hand ) const;
+ QwtDialNeedle *hand( Hand );
+
+public Q_SLOTS:
+ void setCurrentTime();
+ void setTime( const QTime & = QTime::currentTime() );
+
+protected:
+ virtual QwtText scaleLabel( double ) const;
+
+ virtual void drawNeedle( QPainter *, const QPointF &,
+ double radius, double direction, QPalette::ColorGroup ) const;
+
+ virtual void drawHand( QPainter *, Hand, const QPointF &,
+ double radius, double direction, QPalette::ColorGroup ) const;
+
+private:
+ virtual void setNeedle( QwtDialNeedle * );
+ void initClock();
+
+ QwtDialNeedle *d_hand[NHands];
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_arrow_button.cpp b/src/libpcp_qwt/src/qwt_arrow_button.cpp
new file mode 100644
index 0000000..f451b44
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_arrow_button.cpp
@@ -0,0 +1,332 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_arrow_button.h"
+#include "qwt_math.h"
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qevent.h>
+#include <qapplication.h>
+
+static const int MaxNum = 3;
+static const int Margin = 2;
+static const int Spacing = 1;
+
+class QwtArrowButton::PrivateData
+{
+public:
+ int num;
+ Qt::ArrowType arrowType;
+};
+
+static QStyleOptionButton styleOpt( const QwtArrowButton* btn )
+{
+ QStyleOptionButton option;
+ option.init( btn );
+ option.features = QStyleOptionButton::None;
+ if ( btn->isFlat() )
+ option.features |= QStyleOptionButton::Flat;
+ if ( btn->menu() )
+ option.features |= QStyleOptionButton::HasMenu;
+ if ( btn->autoDefault() || btn->isDefault() )
+ option.features |= QStyleOptionButton::AutoDefaultButton;
+ if ( btn->isDefault() )
+ option.features |= QStyleOptionButton::DefaultButton;
+ if ( btn->isDown() )
+ option.state |= QStyle::State_Sunken;
+ if ( !btn->isFlat() && !btn->isDown() )
+ option.state |= QStyle::State_Raised;
+
+ return option;
+}
+
+/*!
+ \param num Number of arrows
+ \param arrowType see Qt::ArowType in the Qt docs.
+ \param parent Parent widget
+*/
+QwtArrowButton::QwtArrowButton( int num,
+ Qt::ArrowType arrowType, QWidget *parent ):
+ QPushButton( parent )
+{
+ d_data = new PrivateData;
+ d_data->num = qBound( 1, num, MaxNum );
+ d_data->arrowType = arrowType;
+
+ setAutoRepeat( true );
+ setAutoDefault( false );
+
+ switch ( d_data->arrowType )
+ {
+ case Qt::LeftArrow:
+ case Qt::RightArrow:
+ setSizePolicy( QSizePolicy::Expanding,
+ QSizePolicy::Fixed );
+ break;
+ default:
+ setSizePolicy( QSizePolicy::Fixed,
+ QSizePolicy::Expanding );
+ }
+}
+
+//! Destructor
+QwtArrowButton::~QwtArrowButton()
+{
+ delete d_data;
+ d_data = NULL;
+}
+
+/*!
+ \brief The direction of the arrows
+*/
+Qt::ArrowType QwtArrowButton::arrowType() const
+{
+ return d_data->arrowType;
+}
+
+/*!
+ \brief The number of arrows
+*/
+int QwtArrowButton::num() const
+{
+ return d_data->num;
+}
+
+/*!
+ \return the bounding rect for the label
+*/
+QRect QwtArrowButton::labelRect() const
+{
+ const int m = Margin;
+
+ QRect r = rect();
+ r.setRect( r.x() + m, r.y() + m,
+ r.width() - 2 * m, r.height() - 2 * m );
+
+ if ( isDown() )
+ {
+ QStyleOptionButton option = styleOpt( this );
+ const int ph = style()->pixelMetric(
+ QStyle::PM_ButtonShiftHorizontal, &option, this );
+ const int pv = style()->pixelMetric(
+ QStyle::PM_ButtonShiftVertical, &option, this );
+
+ r.translate( ph, pv );
+ }
+
+ return r;
+}
+
+/*!
+ Paint event handler
+ \param event Paint event
+*/
+void QwtArrowButton::paintEvent( QPaintEvent *event )
+{
+ QPushButton::paintEvent( event );
+ QPainter painter( this );
+ drawButtonLabel( &painter );
+}
+
+/*!
+ \brief Draw the button label
+
+ \param painter Painter
+ \sa The Qt Manual on QPushButton
+*/
+void QwtArrowButton::drawButtonLabel( QPainter *painter )
+{
+ const bool isVertical = d_data->arrowType == Qt::UpArrow ||
+ d_data->arrowType == Qt::DownArrow;
+
+ const QRect r = labelRect();
+ QSize boundingSize = labelRect().size();
+ if ( isVertical )
+ boundingSize.transpose();
+
+ const int w =
+ ( boundingSize.width() - ( MaxNum - 1 ) * Spacing ) / MaxNum;
+
+ QSize arrow = arrowSize( Qt::RightArrow,
+ QSize( w, boundingSize.height() ) );
+
+ if ( isVertical )
+ arrow.transpose();
+
+ QRect contentsSize; // aligned rect where to paint all arrows
+ if ( d_data->arrowType == Qt::LeftArrow || d_data->arrowType == Qt::RightArrow )
+ {
+ contentsSize.setWidth( d_data->num * arrow.width()
+ + ( d_data->num - 1 ) * Spacing );
+ contentsSize.setHeight( arrow.height() );
+ }
+ else
+ {
+ contentsSize.setWidth( arrow.width() );
+ contentsSize.setHeight( d_data->num * arrow.height()
+ + ( d_data->num - 1 ) * Spacing );
+ }
+
+ QRect arrowRect( contentsSize );
+ arrowRect.moveCenter( r.center() );
+ arrowRect.setSize( arrow );
+
+ painter->save();
+ for ( int i = 0; i < d_data->num; i++ )
+ {
+ drawArrow( painter, arrowRect, d_data->arrowType );
+
+ int dx = 0;
+ int dy = 0;
+
+ if ( isVertical )
+ dy = arrow.height() + Spacing;
+ else
+ dx = arrow.width() + Spacing;
+
+ arrowRect.translate( dx, dy );
+ }
+ painter->restore();
+
+ if ( hasFocus() )
+ {
+ QStyleOptionFocusRect option;
+ option.init( this );
+ option.backgroundColor = palette().color( QPalette::Window );
+
+ style()->drawPrimitive( QStyle::PE_FrameFocusRect,
+ &option, painter, this );
+ }
+}
+
+/*!
+ Draw an arrow int a bounding rect
+
+ \param painter Painter
+ \param r Rectangle where to paint the arrow
+ \param arrowType Arrow type
+*/
+void QwtArrowButton::drawArrow( QPainter *painter,
+ const QRect &r, Qt::ArrowType arrowType ) const
+{
+ QPolygon pa( 3 );
+
+ switch ( arrowType )
+ {
+ case Qt::UpArrow:
+ pa.setPoint( 0, r.bottomLeft() );
+ pa.setPoint( 1, r.bottomRight() );
+ pa.setPoint( 2, r.center().x(), r.top() );
+ break;
+ case Qt::DownArrow:
+ pa.setPoint( 0, r.topLeft() );
+ pa.setPoint( 1, r.topRight() );
+ pa.setPoint( 2, r.center().x(), r.bottom() );
+ break;
+ case Qt::RightArrow:
+ pa.setPoint( 0, r.topLeft() );
+ pa.setPoint( 1, r.bottomLeft() );
+ pa.setPoint( 2, r.right(), r.center().y() );
+ break;
+ case Qt::LeftArrow:
+ pa.setPoint( 0, r.topRight() );
+ pa.setPoint( 1, r.bottomRight() );
+ pa.setPoint( 2, r.left(), r.center().y() );
+ break;
+ default:
+ break;
+ }
+
+ painter->save();
+
+ painter->setPen( palette().color( QPalette::ButtonText ) );
+ painter->setBrush( palette().brush( QPalette::ButtonText ) );
+ painter->drawPolygon( pa );
+
+ painter->restore();
+}
+
+/*!
+ \return a size hint
+*/
+QSize QwtArrowButton::sizeHint() const
+{
+ const QSize hint = minimumSizeHint();
+ return hint.expandedTo( QApplication::globalStrut() );
+}
+
+/*!
+ \brief Return a minimum size hint
+*/
+QSize QwtArrowButton::minimumSizeHint() const
+{
+ const QSize asz = arrowSize( Qt::RightArrow, QSize() );
+
+ QSize sz(
+ 2 * Margin + ( MaxNum - 1 ) * Spacing + MaxNum * asz.width(),
+ 2 * Margin + asz.height()
+ );
+
+ if ( d_data->arrowType == Qt::UpArrow || d_data->arrowType == Qt::DownArrow )
+ sz.transpose();
+
+ QStyleOption styleOption;
+ styleOption.init( this );
+
+ sz = style()->sizeFromContents( QStyle::CT_PushButton,
+ &styleOption, sz, this );
+
+ return sz;
+}
+
+/*!
+ Calculate the size for a arrow that fits into a rect of a given size
+
+ \param arrowType Arrow type
+ \param boundingSize Bounding size
+ \return Size of the arrow
+*/
+QSize QwtArrowButton::arrowSize( Qt::ArrowType arrowType,
+ const QSize &boundingSize ) const
+{
+ QSize bs = boundingSize;
+ if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow )
+ bs.transpose();
+
+ const int MinLen = 2;
+ const QSize sz = bs.expandedTo(
+ QSize( MinLen, 2 * MinLen - 1 ) ); // minimum
+
+ int w = sz.width();
+ int h = 2 * w - 1;
+
+ if ( h > sz.height() )
+ {
+ h = sz.height();
+ w = ( h + 1 ) / 2;
+ }
+
+ QSize arrSize( w, h );
+ if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow )
+ arrSize.transpose();
+
+ return arrSize;
+}
+
+/*!
+ \brief autoRepeat for the space keys
+*/
+void QwtArrowButton::keyPressEvent( QKeyEvent *event )
+{
+ if ( event->isAutoRepeat() && event->key() == Qt::Key_Space )
+ Q_EMIT clicked();
+
+ QPushButton::keyPressEvent( event );
+}
diff --git a/src/libpcp_qwt/src/qwt_arrow_button.h b/src/libpcp_qwt/src/qwt_arrow_button.h
new file mode 100644
index 0000000..ae436fe
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_arrow_button.h
@@ -0,0 +1,52 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ARROW_BUTTON_H
+#define QWT_ARROW_BUTTON_H
+
+#include "qwt_global.h"
+#include <qpushbutton.h>
+
+/*!
+ \brief Arrow Button
+
+ A push button with one or more filled triangles on its front.
+ An Arrow button can have 1 to 3 arrows in a row, pointing
+ up, down, left or right.
+*/
+class QWT_EXPORT QwtArrowButton : public QPushButton
+{
+public:
+ explicit QwtArrowButton ( int num, Qt::ArrowType, QWidget *parent = NULL );
+ virtual ~QwtArrowButton();
+
+ Qt::ArrowType arrowType() const;
+ int num() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+protected:
+ virtual void paintEvent( QPaintEvent *event );
+
+ virtual void drawButtonLabel( QPainter *p );
+ virtual void drawArrow( QPainter *,
+ const QRect &, Qt::ArrowType ) const;
+ virtual QRect labelRect() const;
+ virtual QSize arrowSize( Qt::ArrowType,
+ const QSize &boundingSize ) const;
+
+ virtual void keyPressEvent( QKeyEvent * );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_clipper.cpp b/src/libpcp_qwt/src/qwt_clipper.cpp
new file mode 100644
index 0000000..fea3fd4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_clipper.cpp
@@ -0,0 +1,486 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_clipper.h"
+#include "qwt_point_polar.h"
+#include <qrect.h>
+
+#if QT_VERSION < 0x040601
+#define qAtan(x) ::atan(x)
+#endif
+
+namespace QwtClip
+{
+ // some templates used for inlining
+ template <class Point, typename T> class LeftEdge;
+ template <class Point, typename T> class RightEdge;
+ template <class Point, typename T> class TopEdge;
+ template <class Point, typename T> class BottomEdge;
+
+ template <class Point> class PointBuffer;
+}
+
+template <class Point, typename Value>
+class QwtClip::LeftEdge
+{
+public:
+ inline LeftEdge( Value x1, Value, Value, Value ):
+ d_x1( x1 )
+ {
+ }
+
+ inline bool isInside( const Point &p ) const
+ {
+ return p.x() >= d_x1;
+ }
+
+ inline Point intersection( const Point &p1, const Point &p2 ) const
+ {
+ double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() );
+ return Point( d_x1, ( Value ) ( p2.y() + ( d_x1 - p2.x() ) * dy ) );
+ }
+private:
+ const Value d_x1;
+};
+
+template <class Point, typename Value>
+class QwtClip::RightEdge
+{
+public:
+ inline RightEdge( Value, Value x2, Value, Value ):
+ d_x2( x2 )
+ {
+ }
+
+ inline bool isInside( const Point &p ) const
+ {
+ return p.x() <= d_x2;
+ }
+
+ inline Point intersection( const Point &p1, const Point &p2 ) const
+ {
+ double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() );
+ return Point( d_x2, ( Value ) ( p2.y() + ( d_x2 - p2.x() ) * dy ) );
+ }
+
+private:
+ const Value d_x2;
+};
+
+template <class Point, typename Value>
+class QwtClip::TopEdge
+{
+public:
+ inline TopEdge( Value, Value, Value y1, Value ):
+ d_y1( y1 )
+ {
+ }
+
+ inline bool isInside( const Point &p ) const
+ {
+ return p.y() >= d_y1;
+ }
+
+ inline Point intersection( const Point &p1, const Point &p2 ) const
+ {
+ double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() );
+ return Point( ( Value )( p2.x() + ( d_y1 - p2.y() ) * dx ), d_y1 );
+ }
+
+private:
+ const Value d_y1;
+};
+
+template <class Point, typename Value>
+class QwtClip::BottomEdge
+{
+public:
+ inline BottomEdge( Value, Value, Value, Value y2 ):
+ d_y2( y2 )
+ {
+ }
+
+ inline bool isInside( const Point &p ) const
+ {
+ return p.y() <= d_y2;
+ }
+
+ inline Point intersection( const Point &p1, const Point &p2 ) const
+ {
+ double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() );
+ return Point( ( Value )( p2.x() + ( d_y2 - p2.y() ) * dx ), d_y2 );
+ }
+
+private:
+ const Value d_y2;
+};
+
+template<class Point>
+class QwtClip::PointBuffer
+{
+public:
+ PointBuffer( int capacity = 0 ):
+ m_capacity( 0 ),
+ m_size( 0 ),
+ m_buffer( NULL )
+ {
+ if ( capacity > 0 )
+ reserve( capacity );
+ }
+
+ ~PointBuffer()
+ {
+ if ( m_buffer )
+ qFree( m_buffer );
+ }
+
+ inline void setPoints( int numPoints, const Point *points )
+ {
+ reserve( numPoints );
+
+ m_size = numPoints;
+ qMemCopy( m_buffer, points, m_size * sizeof( Point ) );
+ }
+
+ inline void reset()
+ {
+ m_size = 0;
+ }
+
+ inline int size() const
+ {
+ return m_size;
+ }
+
+ inline Point *data() const
+ {
+ return m_buffer;
+ }
+
+ inline Point &operator[]( int i )
+ {
+ return m_buffer[i];
+ }
+
+ inline const Point &operator[]( int i ) const
+ {
+ return m_buffer[i];
+ }
+
+ inline void add( const Point &point )
+ {
+ if ( m_capacity <= m_size )
+ reserve( m_size + 1 );
+
+ m_buffer[m_size++] = point;
+ }
+
+private:
+ inline void reserve( int size )
+ {
+ if ( m_capacity == 0 )
+ m_capacity = 1;
+
+ while ( m_capacity < size )
+ m_capacity *= 2;
+
+ m_buffer = ( Point * ) qRealloc(
+ m_buffer, m_capacity * sizeof( Point ) );
+ }
+
+ int m_capacity;
+ int m_size;
+ Point *m_buffer;
+};
+
+using namespace QwtClip;
+
+template <class Polygon, class Rect, class Point, typename T>
+class QwtPolygonClipper
+{
+public:
+ QwtPolygonClipper( const Rect &clipRect ):
+ d_clipRect( clipRect )
+ {
+ }
+
+ Polygon clipPolygon( const Polygon &polygon, bool closePolygon ) const
+ {
+#if 0
+ if ( d_clipRect.contains( polygon.boundingRect() ) )
+ return polygon;
+#endif
+
+ PointBuffer<Point> points1;
+ PointBuffer<Point> points2( qMin( 256, polygon.size() ) );
+
+ points1.setPoints( polygon.size(), polygon.data() );
+
+ clipEdge< LeftEdge<Point, T> >( closePolygon, points1, points2 );
+ clipEdge< RightEdge<Point, T> >( closePolygon, points2, points1 );
+ clipEdge< TopEdge<Point, T> >( closePolygon, points1, points2 );
+ clipEdge< BottomEdge<Point, T> >( closePolygon, points2, points1 );
+
+ Polygon p;
+ p.resize( points1.size() );
+ qMemCopy( p.data(), points1.data(), points1.size() * sizeof( Point ) );
+
+ return p;
+ }
+
+private:
+ template <class Edge>
+ inline void clipEdge( bool closePolygon,
+ PointBuffer<Point> &points, PointBuffer<Point> &clippedPoints ) const
+ {
+ clippedPoints.reset();
+
+ if ( points.size() < 2 )
+ {
+ if ( points.size() == 1 )
+ clippedPoints.add( points[0] );
+ return;
+ }
+
+ const Edge edge( d_clipRect.x(), d_clipRect.x() + d_clipRect.width(),
+ d_clipRect.y(), d_clipRect.y() + d_clipRect.height() );
+
+ int lastPos, start;
+ if ( closePolygon )
+ {
+ start = 0;
+ lastPos = points.size() - 1;
+ }
+ else
+ {
+ start = 1;
+ lastPos = 0;
+
+ if ( edge.isInside( points[0] ) )
+ clippedPoints.add( points[0] );
+ }
+
+ const uint nPoints = points.size();
+ for ( uint i = start; i < nPoints; i++ )
+ {
+ const Point &p1 = points[i];
+ const Point &p2 = points[lastPos];
+
+ if ( edge.isInside( p1 ) )
+ {
+ if ( edge.isInside( p2 ) )
+ {
+ clippedPoints.add( p1 );
+ }
+ else
+ {
+ clippedPoints.add( edge.intersection( p1, p2 ) );
+ clippedPoints.add( p1 );
+ }
+ }
+ else
+ {
+ if ( edge.isInside( p2 ) )
+ {
+ clippedPoints.add( edge.intersection( p1, p2 ) );
+ }
+ }
+ lastPos = i;
+ }
+ }
+
+ const Rect d_clipRect;
+};
+
+class QwtCircleClipper
+{
+public:
+ QwtCircleClipper( const QRectF &r );
+ QVector<QwtInterval> clipCircle( const QPointF &, double radius ) const;
+
+private:
+ enum Edge
+ {
+ Left,
+ Top,
+ Right,
+ Bottom,
+
+ NEdges
+ };
+
+ QList<QPointF> cuttingPoints(
+ Edge, const QPointF &pos, double radius ) const;
+
+ double toAngle( const QPointF &, const QPointF & ) const;
+
+ const QRectF d_rect;
+};
+
+
+QwtCircleClipper::QwtCircleClipper( const QRectF &r ):
+ d_rect( r )
+{
+}
+
+QVector<QwtInterval> QwtCircleClipper::clipCircle(
+ const QPointF &pos, double radius ) const
+{
+ QList<QPointF> points;
+ for ( int edge = 0; edge < NEdges; edge++ )
+ points += cuttingPoints( ( Edge )edge, pos, radius );
+
+ QVector<QwtInterval> intv;
+ if ( points.size() <= 0 )
+ {
+ QRectF cRect( 0, 0, 2 * radius, 2 * radius );
+ cRect.moveCenter( pos );
+ if ( d_rect.contains( cRect ) )
+ intv += QwtInterval( 0.0, 2 * M_PI );
+ }
+ else
+ {
+ QList<double> angles;
+ for ( int i = 0; i < points.size(); i++ )
+ angles += toAngle( pos, points[i] );
+ qSort( angles );
+
+ const int in = d_rect.contains( qwtPolar2Pos( pos, radius,
+ angles[0] + ( angles[1] - angles[0] ) / 2 ) );
+
+ if ( in )
+ {
+ for ( int i = 0; i < angles.size() - 1; i += 2 )
+ intv += QwtInterval( angles[i], angles[i+1] );
+ }
+ else
+ {
+ for ( int i = 1; i < angles.size() - 1; i += 2 )
+ intv += QwtInterval( angles[i], angles[i+1] );
+ intv += QwtInterval( angles.last(), angles.first() );
+ }
+ }
+
+ return intv;
+}
+
+double QwtCircleClipper::toAngle(
+ const QPointF &from, const QPointF &to ) const
+{
+ if ( from.x() == to.x() )
+ return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0;
+
+ const double m = qAbs( ( to.y() - from.y() ) / ( to.x() - from.x() ) );
+
+ double angle = qAtan( m );
+ if ( to.x() > from.x() )
+ {
+ if ( to.y() > from.y() )
+ angle = 2 * M_PI - angle;
+ }
+ else
+ {
+ if ( to.y() > from.y() )
+ angle = M_PI + angle;
+ else
+ angle = M_PI - angle;
+ }
+
+ return angle;
+}
+
+QList<QPointF> QwtCircleClipper::cuttingPoints(
+ Edge edge, const QPointF &pos, double radius ) const
+{
+ QList<QPointF> points;
+
+ if ( edge == Left || edge == Right )
+ {
+ const double x = ( edge == Left ) ? d_rect.left() : d_rect.right();
+ if ( qAbs( pos.x() - x ) < radius )
+ {
+ const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) );
+ const double m_y1 = pos.y() + off;
+ if ( m_y1 >= d_rect.top() && m_y1 <= d_rect.bottom() )
+ points += QPointF( x, m_y1 );
+
+ const double m_y2 = pos.y() - off;
+ if ( m_y2 >= d_rect.top() && m_y2 <= d_rect.bottom() )
+ points += QPointF( x, m_y2 );
+ }
+ }
+ else
+ {
+ const double y = ( edge == Top ) ? d_rect.top() : d_rect.bottom();
+ if ( qAbs( pos.y() - y ) < radius )
+ {
+ const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) );
+ const double x1 = pos.x() + off;
+ if ( x1 >= d_rect.left() && x1 <= d_rect.right() )
+ points += QPointF( x1, y );
+
+ const double m_x2 = pos.x() - off;
+ if ( m_x2 >= d_rect.left() && m_x2 <= d_rect.right() )
+ points += QPointF( m_x2, y );
+ }
+ }
+ return points;
+}
+
+/*!
+ Sutherland-Hodgman polygon clipping
+
+ \param clipRect Clip rectangle
+ \param polygon Polygon
+ \param closePolygon True, when the polygon is closed
+
+ \return Clipped polygon
+*/
+QPolygon QwtClipper::clipPolygon(
+ const QRect &clipRect, const QPolygon &polygon, bool closePolygon )
+{
+ QwtPolygonClipper<QPolygon, QRect, QPoint, int> clipper( clipRect );
+ return clipper.clipPolygon( polygon, closePolygon );
+}
+
+/*!
+ Sutherland-Hodgman polygon clipping
+
+ \param clipRect Clip rectangle
+ \param polygon Polygon
+ \param closePolygon True, when the polygon is closed
+
+ \return Clipped polygon
+*/
+QPolygonF QwtClipper::clipPolygonF(
+ const QRectF &clipRect, const QPolygonF &polygon, bool closePolygon )
+{
+ QwtPolygonClipper<QPolygonF, QRectF, QPointF, double> clipper( clipRect );
+ return clipper.clipPolygon( polygon, closePolygon );
+}
+
+/*!
+ Circle clipping
+
+ clipCircle() devides a circle into intervals of angles representing arcs
+ of the circle. When the circle is completely inside the clip rectangle
+ an interval [0.0, 2 * M_PI] is returned.
+
+ \param clipRect Clip rectangle
+ \param center Center of the circle
+ \param radius Radius of the circle
+
+ \return Arcs of the circle
+*/
+QVector<QwtInterval> QwtClipper::clipCircle( const QRectF &clipRect,
+ const QPointF &center, double radius )
+{
+ QwtCircleClipper clipper( clipRect );
+ return clipper.clipCircle( center, radius );
+}
diff --git a/src/libpcp_qwt/src/qwt_clipper.h b/src/libpcp_qwt/src/qwt_clipper.h
new file mode 100644
index 0000000..98316d3
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_clipper.h
@@ -0,0 +1,37 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_CLIPPER_H
+#define QWT_CLIPPER_H
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include <qpolygon.h>
+#include <qvector.h>
+
+class QRect;
+class QRectF;
+
+/*!
+ \brief Some clipping algos
+*/
+
+class QWT_EXPORT QwtClipper
+{
+public:
+ static QPolygon clipPolygon( const QRect &,
+ const QPolygon &, bool closePolygon = false );
+ static QPolygonF clipPolygonF( const QRectF &,
+ const QPolygonF &, bool closePolygon = false );
+
+ static QVector<QwtInterval> clipCircle(
+ const QRectF &, const QPointF &, double radius );
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_color_map.cpp b/src/libpcp_qwt/src/qwt_color_map.cpp
new file mode 100644
index 0000000..4318e5a
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_color_map.cpp
@@ -0,0 +1,440 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_color_map.h"
+#include "qwt_math.h"
+#include "qwt_interval.h"
+#include <qnumeric.h>
+
+class QwtLinearColorMap::ColorStops
+{
+public:
+ ColorStops()
+ {
+ _stops.reserve( 256 );
+ }
+
+ void insert( double pos, const QColor &color );
+ QRgb rgb( QwtLinearColorMap::Mode, double pos ) const;
+
+ QVector<double> stops() const;
+
+private:
+
+ class ColorStop
+ {
+ public:
+ ColorStop():
+ pos( 0.0 ),
+ rgb( 0 )
+ {
+ };
+
+ ColorStop( double p, const QColor &c ):
+ pos( p ),
+ rgb( c.rgb() )
+ {
+ r = qRed( rgb );
+ g = qGreen( rgb );
+ b = qBlue( rgb );
+ }
+
+ double pos;
+ QRgb rgb;
+ int r, g, b;
+ };
+
+ inline int findUpper( double pos ) const;
+ QVector<ColorStop> _stops;
+};
+
+void QwtLinearColorMap::ColorStops::insert( double pos, const QColor &color )
+{
+ // Lookups need to be very fast, insertions are not so important.
+ // Anyway, a balanced tree is what we need here. TODO ...
+
+ if ( pos < 0.0 || pos > 1.0 )
+ return;
+
+ int index;
+ if ( _stops.size() == 0 )
+ {
+ index = 0;
+ _stops.resize( 1 );
+ }
+ else
+ {
+ index = findUpper( pos );
+ if ( index == _stops.size() ||
+ qAbs( _stops[index].pos - pos ) >= 0.001 )
+ {
+ _stops.resize( _stops.size() + 1 );
+ for ( int i = _stops.size() - 1; i > index; i-- )
+ _stops[i] = _stops[i-1];
+ }
+ }
+
+ _stops[index] = ColorStop( pos, color );
+}
+
+inline QVector<double> QwtLinearColorMap::ColorStops::stops() const
+{
+ QVector<double> positions( _stops.size() );
+ for ( int i = 0; i < _stops.size(); i++ )
+ positions[i] = _stops[i].pos;
+ return positions;
+}
+
+inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const
+{
+ int index = 0;
+ int n = _stops.size();
+
+ const ColorStop *stops = _stops.data();
+
+ while ( n > 0 )
+ {
+ const int half = n >> 1;
+ const int middle = index + half;
+
+ if ( stops[middle].pos <= pos )
+ {
+ index = middle + 1;
+ n -= half + 1;
+ }
+ else
+ n = half;
+ }
+
+ return index;
+}
+
+inline QRgb QwtLinearColorMap::ColorStops::rgb(
+ QwtLinearColorMap::Mode mode, double pos ) const
+{
+ if ( pos <= 0.0 )
+ return _stops[0].rgb;
+ if ( pos >= 1.0 )
+ return _stops[ _stops.size() - 1 ].rgb;
+
+ const int index = findUpper( pos );
+ if ( mode == FixedColors )
+ {
+ return _stops[index-1].rgb;
+ }
+ else
+ {
+ const ColorStop &s1 = _stops[index-1];
+ const ColorStop &s2 = _stops[index];
+
+ const double ratio = ( pos - s1.pos ) / ( s2.pos - s1.pos );
+
+ const int r = s1.r + qRound( ratio * ( s2.r - s1.r ) );
+ const int g = s1.g + qRound( ratio * ( s2.g - s1.g ) );
+ const int b = s1.b + qRound( ratio * ( s2.b - s1.b ) );
+
+ return qRgb( r, g, b );
+ }
+}
+
+//! Constructor
+QwtColorMap::QwtColorMap( Format format ):
+ d_format( format )
+{
+}
+
+//! Destructor
+QwtColorMap::~QwtColorMap()
+{
+}
+
+/*!
+ Build and return a color map of 256 colors
+
+ The color table is needed for rendering indexed images in combination
+ with using colorIndex().
+
+ \param interval Range for the values
+ \return A color table, that can be used for a QImage
+*/
+QVector<QRgb> QwtColorMap::colorTable( const QwtInterval &interval ) const
+{
+ QVector<QRgb> table( 256 );
+
+ if ( interval.isValid() )
+ {
+ const double step = interval.width() / ( table.size() - 1 );
+ for ( int i = 0; i < table.size(); i++ )
+ table[i] = rgb( interval, interval.minValue() + step * i );
+ }
+
+ return table;
+}
+
+class QwtLinearColorMap::PrivateData
+{
+public:
+ ColorStops colorStops;
+ QwtLinearColorMap::Mode mode;
+};
+
+/*!
+ Build a color map with two stops at 0.0 and 1.0. The color
+ at 0.0 is Qt::blue, at 1.0 it is Qt::yellow.
+
+ \param format Preferred format of the color map
+*/
+QwtLinearColorMap::QwtLinearColorMap( QwtColorMap::Format format ):
+ QwtColorMap( format )
+{
+ d_data = new PrivateData;
+ d_data->mode = ScaledColors;
+
+ setColorInterval( Qt::blue, Qt::yellow );
+}
+
+/*!
+ Build a color map with two stops at 0.0 and 1.0.
+
+ \param color1 Color used for the minimum value of the value interval
+ \param color2 Color used for the maximum value of the value interval
+ \param format Preferred format of the coor map
+*/
+QwtLinearColorMap::QwtLinearColorMap( const QColor &color1,
+ const QColor &color2, QwtColorMap::Format format ):
+ QwtColorMap( format )
+{
+ d_data = new PrivateData;
+ d_data->mode = ScaledColors;
+ setColorInterval( color1, color2 );
+}
+
+//! Destructor
+QwtLinearColorMap::~QwtLinearColorMap()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Set the mode of the color map
+
+ FixedColors means the color is calculated from the next lower
+ color stop. ScaledColors means the color is calculated
+ by interpolating the colors of the adjacent stops.
+
+ \sa mode()
+*/
+void QwtLinearColorMap::setMode( Mode mode )
+{
+ d_data->mode = mode;
+}
+
+/*!
+ \return Mode of the color map
+ \sa setMode()
+*/
+QwtLinearColorMap::Mode QwtLinearColorMap::mode() const
+{
+ return d_data->mode;
+}
+
+/*!
+ Set the color range
+
+ Add stops at 0.0 and 1.0.
+
+ \param color1 Color used for the minimum value of the value interval
+ \param color2 Color used for the maximum value of the value interval
+
+ \sa color1(), color2()
+*/
+void QwtLinearColorMap::setColorInterval(
+ const QColor &color1, const QColor &color2 )
+{
+ d_data->colorStops = ColorStops();
+ d_data->colorStops.insert( 0.0, color1 );
+ d_data->colorStops.insert( 1.0, color2 );
+}
+
+/*!
+ Add a color stop
+
+ The value has to be in the range [0.0, 1.0].
+ F.e. a stop at position 17.0 for a range [10.0,20.0] must be
+ passed as: (17.0 - 10.0) / (20.0 - 10.0)
+
+ \param value Value between [0.0, 1.0]
+ \param color Color stop
+*/
+void QwtLinearColorMap::addColorStop( double value, const QColor& color )
+{
+ if ( value >= 0.0 && value <= 1.0 )
+ d_data->colorStops.insert( value, color );
+}
+
+/*!
+ Return all positions of color stops in increasing order
+*/
+QVector<double> QwtLinearColorMap::colorStops() const
+{
+ return d_data->colorStops.stops();
+}
+
+/*!
+ \return the first color of the color range
+ \sa setColorInterval()
+*/
+QColor QwtLinearColorMap::color1() const
+{
+ return QColor( d_data->colorStops.rgb( d_data->mode, 0.0 ) );
+}
+
+/*!
+ \return the second color of the color range
+ \sa setColorInterval()
+*/
+QColor QwtLinearColorMap::color2() const
+{
+ return QColor( d_data->colorStops.rgb( d_data->mode, 1.0 ) );
+}
+
+/*!
+ Map a value of a given interval into a rgb value
+
+ \param interval Range for all values
+ \param value Value to map into a rgb value
+*/
+QRgb QwtLinearColorMap::rgb(
+ const QwtInterval &interval, double value ) const
+{
+ if ( qIsNaN(value) )
+ return qRgba(0, 0, 0, 0);
+
+ const double width = interval.width();
+
+ double ratio = 0.0;
+ if ( width > 0.0 )
+ ratio = ( value - interval.minValue() ) / width;
+
+ return d_data->colorStops.rgb( d_data->mode, ratio );
+}
+
+/*!
+ Map a value of a given interval into a color index, between 0 and 255
+
+ \param interval Range for all values
+ \param value Value to map into a color index
+*/
+unsigned char QwtLinearColorMap::colorIndex(
+ const QwtInterval &interval, double value ) const
+{
+ const double width = interval.width();
+
+ if ( qIsNaN(value) || width <= 0.0 || value <= interval.minValue() )
+ return 0;
+
+ if ( value >= interval.maxValue() )
+ return ( unsigned char )255;
+
+ const double ratio = ( value - interval.minValue() ) / width;
+
+ unsigned char index;
+ if ( d_data->mode == FixedColors )
+ index = ( unsigned char )( ratio * 255 ); // always floor
+ else
+ index = ( unsigned char )qRound( ratio * 255 );
+
+ return index;
+}
+
+class QwtAlphaColorMap::PrivateData
+{
+public:
+ QColor color;
+ QRgb rgb;
+};
+
+
+/*!
+ Constructor
+ \param color Color of the map
+*/
+QwtAlphaColorMap::QwtAlphaColorMap( const QColor &color ):
+ QwtColorMap( QwtColorMap::RGB )
+{
+ d_data = new PrivateData;
+ d_data->color = color;
+ d_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 );
+}
+
+//! Destructor
+QwtAlphaColorMap::~QwtAlphaColorMap()
+{
+ delete d_data;
+}
+
+/*!
+ Set the color
+
+ \param color Color
+ \sa color()
+*/
+void QwtAlphaColorMap::setColor( const QColor &color )
+{
+ d_data->color = color;
+ d_data->rgb = color.rgb();
+}
+
+/*!
+ \return the color
+ \sa setColor()
+*/
+QColor QwtAlphaColorMap::color() const
+{
+ return d_data->color;
+}
+
+/*!
+ \brief Map a value of a given interval into a alpha value
+
+ alpha := (value - interval.minValue()) / interval.width();
+
+ \param interval Range for all values
+ \param value Value to map into a rgb value
+ \return rgb value, with an alpha value
+*/
+QRgb QwtAlphaColorMap::rgb( const QwtInterval &interval, double value ) const
+{
+ const double width = interval.width();
+ if ( !qIsNaN(value) && width >= 0.0 )
+ {
+ const double ratio = ( value - interval.minValue() ) / width;
+ int alpha = qRound( 255 * ratio );
+ if ( alpha < 0 )
+ alpha = 0;
+ if ( alpha > 255 )
+ alpha = 255;
+
+ return d_data->rgb | ( alpha << 24 );
+ }
+ return d_data->rgb;
+}
+
+/*!
+ Dummy function, needed to be implemented as it is pure virtual
+ in QwtColorMap. Color indices make no sense in combination with
+ an alpha channel.
+
+ \return Always 0
+*/
+unsigned char QwtAlphaColorMap::colorIndex(
+ const QwtInterval &, double ) const
+{
+ return 0;
+}
diff --git a/src/libpcp_qwt/src/qwt_color_map.h b/src/libpcp_qwt/src/qwt_color_map.h
new file mode 100644
index 0000000..e754830
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_color_map.h
@@ -0,0 +1,198 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_COLOR_MAP_H
+#define QWT_COLOR_MAP_H
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include <qcolor.h>
+#include <qvector.h>
+
+/*!
+ \brief QwtColorMap is used to map values into colors.
+
+ For displaying 3D data on a 2D plane the 3rd dimension is often
+ displayed using colors, like f.e in a spectrogram.
+
+ Each color map is optimized to return colors for only one of the
+ following image formats:
+
+ - QImage::Format_Indexed8\n
+ - QImage::Format_ARGB32\n
+
+ \sa QwtPlotSpectrogram, QwtScaleWidget
+*/
+
+class QWT_EXPORT QwtColorMap
+{
+public:
+ /*!
+ Format for color mapping
+ \sa rgb(), colorIndex(), colorTable()
+ */
+
+ enum Format
+ {
+ //! The map is intended to map into QRgb values.
+ RGB,
+
+ /*!
+ The map is intended to map into 8 bit values, that
+ are indices into the color table.
+ */
+ Indexed
+ };
+
+ QwtColorMap( Format = QwtColorMap::RGB );
+ virtual ~QwtColorMap();
+
+ Format format() const;
+
+ /*!
+ Map a value of a given interval into a rgb value.
+ \param interval Range for the values
+ \param value Value
+ \return rgb value, corresponding to value
+ */
+ virtual QRgb rgb( const QwtInterval &interval,
+ double value ) const = 0;
+
+ /*!
+ Map a value of a given interval into a color index
+ \param interval Range for the values
+ \param value Value
+ \return color index, corresponding to value
+ */
+ virtual unsigned char colorIndex(
+ const QwtInterval &interval, double value ) const = 0;
+
+ QColor color( const QwtInterval &, double value ) const;
+ virtual QVector<QRgb> colorTable( const QwtInterval & ) const;
+
+private:
+ Format d_format;
+};
+
+/*!
+ \brief QwtLinearColorMap builds a color map from color stops.
+
+ A color stop is a color at a specific position. The valid
+ range for the positions is [0.0, 1.0]. When mapping a value
+ into a color it is translated into this interval according to mode().
+*/
+class QWT_EXPORT QwtLinearColorMap: public QwtColorMap
+{
+public:
+ /*!
+ Mode of color map
+ \sa setMode(), mode()
+ */
+ enum Mode
+ {
+ //! Return the color from the next lower color stop
+ FixedColors,
+
+ //! Interpolating the colors of the adjacent stops.
+ ScaledColors
+ };
+
+ QwtLinearColorMap( QwtColorMap::Format = QwtColorMap::RGB );
+ QwtLinearColorMap( const QColor &from, const QColor &to,
+ QwtColorMap::Format = QwtColorMap::RGB );
+
+ virtual ~QwtLinearColorMap();
+
+ void setMode( Mode );
+ Mode mode() const;
+
+ void setColorInterval( const QColor &color1, const QColor &color2 );
+ void addColorStop( double value, const QColor& );
+ QVector<double> colorStops() const;
+
+ QColor color1() const;
+ QColor color2() const;
+
+ virtual QRgb rgb( const QwtInterval &, double value ) const;
+ virtual unsigned char colorIndex(
+ const QwtInterval &, double value ) const;
+
+ class ColorStops;
+
+private:
+ // Disabled copy constructor and operator=
+ QwtLinearColorMap( const QwtLinearColorMap & );
+ QwtLinearColorMap &operator=( const QwtLinearColorMap & );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ \brief QwtAlphaColorMap variies the alpha value of a color
+*/
+class QWT_EXPORT QwtAlphaColorMap: public QwtColorMap
+{
+public:
+ QwtAlphaColorMap( const QColor & = QColor( Qt::gray ) );
+ virtual ~QwtAlphaColorMap();
+
+ void setColor( const QColor & );
+ QColor color() const;
+
+ virtual QRgb rgb( const QwtInterval &, double value ) const;
+
+private:
+ QwtAlphaColorMap( const QwtAlphaColorMap & );
+ QwtAlphaColorMap &operator=( const QwtAlphaColorMap & );
+
+ virtual unsigned char colorIndex(
+ const QwtInterval &, double value ) const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+
+/*!
+ Map a value into a color
+
+ \param interval Valid interval for values
+ \param value Value
+
+ \return Color corresponding to value
+
+ \warning This method is slow for Indexed color maps. If it is
+ necessary to map many values, its better to get the
+ color table once and find the color using colorIndex().
+*/
+inline QColor QwtColorMap::color(
+ const QwtInterval &interval, double value ) const
+{
+ if ( d_format == RGB )
+ {
+ return QColor( rgb( interval, value ) );
+ }
+ else
+ {
+ const unsigned int index = colorIndex( interval, value );
+ return colorTable( interval )[index]; // slow
+ }
+}
+
+/*!
+ \return Intended format of the color map
+ \sa Format
+*/
+inline QwtColorMap::Format QwtColorMap::format() const
+{
+ return d_format;
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_column_symbol.cpp b/src/libpcp_qwt/src/qwt_column_symbol.cpp
new file mode 100644
index 0000000..ecbcd9a
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_column_symbol.cpp
@@ -0,0 +1,293 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_column_symbol.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qpalette.h>
+
+static void drawBox( QPainter *p, const QRectF &rect,
+ const QPalette &pal, double lw )
+{
+ if ( lw > 0.0 )
+ {
+ if ( rect.width() == 0.0 )
+ {
+ p->setPen( pal.dark().color() );
+ p->drawLine( rect.topLeft(), rect.bottomLeft() );
+ return;
+ }
+
+ if ( rect.height() == 0.0 )
+ {
+ p->setPen( pal.dark().color() );
+ p->drawLine( rect.topLeft(), rect.topRight() );
+ return;
+ }
+
+ lw = qMin( lw, rect.height() / 2.0 - 1.0 );
+ lw = qMin( lw, rect.width() / 2.0 - 1.0 );
+
+ const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 );
+ QPolygonF polygon( outerRect );
+
+ if ( outerRect.width() > 2 * lw &&
+ outerRect.height() > 2 * lw )
+ {
+ const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw );
+ polygon = polygon.subtracted( innerRect );
+ }
+
+ p->setPen( Qt::NoPen );
+
+ p->setBrush( pal.dark() );
+ p->drawPolygon( polygon );
+ }
+
+ const QRectF windowRect = rect.adjusted( lw, lw, -lw + 1, -lw + 1 );
+ if ( windowRect.isValid() )
+ p->fillRect( windowRect, pal.window() );
+}
+
+static void drawPanel( QPainter *painter, const QRectF &rect,
+ const QPalette &pal, double lw )
+{
+ if ( lw > 0.0 )
+ {
+ if ( rect.width() == 0.0 )
+ {
+ painter->setPen( pal.window().color() );
+ painter->drawLine( rect.topLeft(), rect.bottomLeft() );
+ return;
+ }
+
+ if ( rect.height() == 0.0 )
+ {
+ painter->setPen( pal.window().color() );
+ painter->drawLine( rect.topLeft(), rect.topRight() );
+ return;
+ }
+
+ lw = qMin( lw, rect.height() / 2.0 - 1.0 );
+ lw = qMin( lw, rect.width() / 2.0 - 1.0 );
+
+ const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 );
+ const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw );
+
+ QPolygonF lines[2];
+
+ lines[0] += outerRect.bottomLeft();
+ lines[0] += outerRect.topLeft();
+ lines[0] += outerRect.topRight();
+ lines[0] += innerRect.topRight();
+ lines[0] += innerRect.topLeft();
+ lines[0] += innerRect.bottomLeft();
+
+ lines[1] += outerRect.topRight();
+ lines[1] += outerRect.bottomRight();
+ lines[1] += outerRect.bottomLeft();
+ lines[1] += innerRect.bottomLeft();
+ lines[1] += innerRect.bottomRight();
+ lines[1] += innerRect.topRight();
+
+ painter->setPen( Qt::NoPen );
+
+ painter->setBrush( pal.light() );
+ painter->drawPolygon( lines[0] );
+ painter->setBrush( pal.dark() );
+ painter->drawPolygon( lines[1] );
+ }
+
+ painter->fillRect( rect.adjusted( lw, lw, -lw + 1, -lw + 1 ), pal.window() );
+}
+
+class QwtColumnSymbol::PrivateData
+{
+public:
+ PrivateData():
+ style( QwtColumnSymbol::Box ),
+ frameStyle( QwtColumnSymbol::Raised ),
+ lineWidth( 2 )
+ {
+ palette = QPalette( Qt::gray );
+ }
+
+ QwtColumnSymbol::Style style;
+ QwtColumnSymbol::FrameStyle frameStyle;
+
+ QPalette palette;
+ int lineWidth;
+};
+
+/*!
+ Constructor
+
+ \param style Style of the symbol
+ \sa setStyle(), style(), Style
+*/
+QwtColumnSymbol::QwtColumnSymbol( Style style )
+{
+ d_data = new PrivateData();
+ d_data->style = style;
+}
+
+//! Destructor
+QwtColumnSymbol::~QwtColumnSymbol()
+{
+ delete d_data;
+}
+
+/*!
+ Specify the symbol style
+
+ \param style Style
+ \sa style(), setPalette()
+*/
+void QwtColumnSymbol::setStyle( Style style )
+{
+ d_data->style = style;
+}
+
+/*!
+ \return Current symbol style
+ \sa setStyle()
+*/
+QwtColumnSymbol::Style QwtColumnSymbol::style() const
+{
+ return d_data->style;
+}
+
+/*!
+ Assign a palette for the symbol
+
+ \param palette Palette
+ \sa palette(), setStyle()
+*/
+void QwtColumnSymbol::setPalette( const QPalette &palette )
+{
+ d_data->palette = palette;
+}
+
+/*!
+ \return Current palette
+ \sa setPalette()
+*/
+const QPalette& QwtColumnSymbol::palette() const
+{
+ return d_data->palette;
+}
+
+/*!
+ Set the frame, that is used for the Box style.
+
+ \param frameStyle Frame style
+ \sa frameStyle(), setLineWidth(), setStyle()
+*/
+void QwtColumnSymbol::setFrameStyle( FrameStyle frameStyle )
+{
+ d_data->frameStyle = frameStyle;
+}
+
+/*!
+ \return Current frame style, that is used for the Box style.
+ \sa setFrameStyle(), lineWidth(), setStyle()
+*/
+QwtColumnSymbol::FrameStyle QwtColumnSymbol::frameStyle() const
+{
+ return d_data->frameStyle;
+}
+
+/*!
+ Set the line width of the frame, that is used for the Box style.
+
+ \param width Width
+ \sa lineWidth(), setFrameStyle()
+*/
+void QwtColumnSymbol::setLineWidth( int width )
+{
+ if ( width < 0 )
+ width = 0;
+
+ d_data->lineWidth = width;
+}
+
+/*!
+ \return Line width of the frame, that is used for the Box style.
+ \sa setLineWidth(), frameStyle(), setStyle()
+*/
+int QwtColumnSymbol::lineWidth() const
+{
+ return d_data->lineWidth;
+}
+
+/*!
+ Draw the symbol depending on its style.
+
+ \param painter Painter
+ \param rect Directed rectangle
+
+ \sa drawBox()
+*/
+void QwtColumnSymbol::draw( QPainter *painter,
+ const QwtColumnRect &rect ) const
+{
+ painter->save();
+
+ switch ( d_data->style )
+ {
+ case QwtColumnSymbol::Box:
+ {
+ drawBox( painter, rect );
+ break;
+ }
+ default:;
+ }
+
+ painter->restore();
+}
+
+/*!
+ Draw the symbol when it is in Box style.
+
+ \param painter Painter
+ \param rect Directed rectangle
+
+ \sa draw()
+*/
+void QwtColumnSymbol::drawBox( QPainter *painter,
+ const QwtColumnRect &rect ) const
+{
+ QRectF r = rect.toRect();
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ r.setLeft( qRound( r.left() ) );
+ r.setRight( qRound( r.right() ) );
+ r.setTop( qRound( r.top() ) );
+ r.setBottom( qRound( r.bottom() ) );
+ }
+
+ switch ( d_data->frameStyle )
+ {
+ case QwtColumnSymbol::Raised:
+ {
+ ::drawPanel( painter, r, d_data->palette, d_data->lineWidth );
+ break;
+ }
+ case QwtColumnSymbol::Plain:
+ {
+ ::drawBox( painter, r, d_data->palette, d_data->lineWidth );
+ break;
+ }
+ default:
+ {
+ painter->fillRect( r, d_data->palette.window() );
+ }
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_column_symbol.h b/src/libpcp_qwt/src/qwt_column_symbol.h
new file mode 100644
index 0000000..3c278f1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_column_symbol.h
@@ -0,0 +1,161 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_COLUMN_SYMBOL_H
+#define QWT_COLUMN_SYMBOL_H
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include <qpen.h>
+#include <qsize.h>
+#include <qrect.h>
+
+class QPainter;
+class QPalette;
+class QRect;
+class QwtText;
+
+/*!
+ \brief Directed rectangle representing bounding rectangle und orientation
+ of a column.
+*/
+class QWT_EXPORT QwtColumnRect
+{
+public:
+ //! Direction of the column
+ enum Direction
+ {
+ //! From left to right
+ LeftToRight,
+
+ //! From right to left
+ RightToLeft,
+
+ //! From bottom to top
+ BottomToTop,
+
+ //! From top to bottom
+ TopToBottom
+ };
+
+ //! Build an rectangle with invalid intervals directed BottomToTop.
+ QwtColumnRect():
+ direction( BottomToTop )
+ {
+ }
+
+ //! \return A normalized QRect built from the intervals
+ QRectF toRect() const
+ {
+ QRectF r( hInterval.minValue(), vInterval.minValue(),
+ hInterval.maxValue() - hInterval.minValue(),
+ vInterval.maxValue() - vInterval.minValue() );
+ r = r.normalized();
+
+ if ( hInterval.borderFlags() & QwtInterval::ExcludeMinimum )
+ r.adjust( 1, 0, 0, 0 );
+ if ( hInterval.borderFlags() & QwtInterval::ExcludeMaximum )
+ r.adjust( 0, 0, -1, 0 );
+ if ( vInterval.borderFlags() & QwtInterval::ExcludeMinimum )
+ r.adjust( 0, 1, 0, 0 );
+ if ( vInterval.borderFlags() & QwtInterval::ExcludeMaximum )
+ r.adjust( 0, 0, 0, -1 );
+
+ return r;
+ }
+
+ //! \return Orientation
+ Qt::Orientation orientation() const
+ {
+ if ( direction == LeftToRight || direction == RightToLeft )
+ return Qt::Horizontal;
+
+ return Qt::Vertical;
+ }
+
+ //! Interval for the horizontal coordinates
+ QwtInterval hInterval;
+
+ //! Interval for the vertical coordinates
+ QwtInterval vInterval;
+
+ //! Direction
+ Direction direction;
+};
+
+//! A drawing primitive for columns
+class QWT_EXPORT QwtColumnSymbol
+{
+public:
+ /*!
+ Style
+ \sa setStyle(), style()
+ */
+ enum Style
+ {
+ //! No Style, the symbol draws nothing
+ NoStyle = -1,
+
+ /*!
+ The column is painted with a frame depending on the frameStyle()
+ and lineWidth() using the palette().
+ */
+ Box,
+
+ /*!
+ Styles >= QwtColumnSymbol::UserStyle are reserved for derived
+ classes of QwtColumnSymbol that overload draw() with
+ additional application specific symbol types.
+ */
+ UserStyle = 1000
+ };
+
+ /*!
+ Frame Style used in Box style().
+ \sa Style, setFrameStyle(), frameStyle(), setStyle(), setPalette()
+ */
+ enum FrameStyle
+ {
+ //! No frame
+ NoFrame,
+
+ //! A plain frame style
+ Plain,
+
+ //! A raised frame style
+ Raised
+ };
+
+public:
+ QwtColumnSymbol( Style = NoStyle );
+ virtual ~QwtColumnSymbol();
+
+ void setFrameStyle( FrameStyle style );
+ FrameStyle frameStyle() const;
+
+ void setLineWidth( int width );
+ int lineWidth() const;
+
+ void setPalette( const QPalette & );
+ const QPalette &palette() const;
+
+ void setStyle( Style );
+ Style style() const;
+
+ virtual void draw( QPainter *, const QwtColumnRect & ) const;
+
+protected:
+ void drawBox( QPainter *, const QwtColumnRect & ) const;
+
+private:
+ class PrivateData;
+ PrivateData* d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_compass.cpp b/src/libpcp_qwt/src/qwt_compass.cpp
new file mode 100644
index 0000000..5ee3e4e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_compass.cpp
@@ -0,0 +1,292 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_compass.h"
+#include "qwt_compass_rose.h"
+#include "qwt_math.h"
+#include "qwt_scale_draw.h"
+#include "qwt_painter.h"
+#include "qwt_dial_needle.h"
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qevent.h>
+
+class QwtCompass::PrivateData
+{
+public:
+ PrivateData():
+ rose( NULL )
+ {
+ }
+
+ ~PrivateData()
+ {
+ delete rose;
+ }
+
+ QwtCompassRose *rose;
+ QMap<double, QString> labelMap;
+};
+
+/*!
+ \brief Constructor
+ \param parent Parent widget
+
+ Create a compass widget with a scale, no needle and no rose.
+ The default origin is 270.0 with no valid value. It accepts
+ mouse and keyboard inputs and has no step size. The default mode
+ is QwtDial::RotateNeedle.
+*/
+QwtCompass::QwtCompass( QWidget* parent ):
+ QwtDial( parent )
+{
+ initCompass();
+}
+
+//! Destructor
+QwtCompass::~QwtCompass()
+{
+ delete d_data;
+}
+
+void QwtCompass::initCompass()
+{
+ d_data = new PrivateData;
+
+ // Only labels, no backbone, no ticks
+ setScaleComponents( QwtAbstractScaleDraw::Labels );
+
+ setOrigin( 270.0 );
+ setWrapping( true );
+
+
+ d_data->labelMap.insert( 0.0, QString::fromLatin1( "N" ) );
+ d_data->labelMap.insert( 45.0, QString::fromLatin1( "NE" ) );
+ d_data->labelMap.insert( 90.0, QString::fromLatin1( "E" ) );
+ d_data->labelMap.insert( 135.0, QString::fromLatin1( "SE" ) );
+ d_data->labelMap.insert( 180.0, QString::fromLatin1( "S" ) );
+ d_data->labelMap.insert( 225.0, QString::fromLatin1( "SW" ) );
+ d_data->labelMap.insert( 270.0, QString::fromLatin1( "W" ) );
+ d_data->labelMap.insert( 315.0, QString::fromLatin1( "NW" ) );
+
+#if 0
+ d_data->labelMap.insert( 22.5, QString::fromLatin1( "NNE" ) );
+ d_data->labelMap.insert( 67.5, QString::fromLatin1( "NEE" ) );
+ d_data->labelMap.insert( 112.5, QString::fromLatin1( "SEE" ) );
+ d_data->labelMap.insert( 157.5, QString::fromLatin1( "SSE" ) );
+ d_data->labelMap.insert( 202.5, QString::fromLatin1( "SSW" ) );
+ d_data->labelMap.insert( 247.5, QString::fromLatin1( "SWW" ) );
+ d_data->labelMap.insert( 292.5, QString::fromLatin1( "NWW" ) );
+ d_data->labelMap.insert( 337.5, QString::fromLatin1( "NNW" ) );
+#endif
+}
+
+/*!
+ Draw the contents of the scale
+
+ \param painter Painter
+ \param center Center of the content circle
+ \param radius Radius of the content circle
+*/
+void QwtCompass::drawScaleContents( QPainter *painter,
+ const QPointF &center, double radius ) const
+{
+ QPalette::ColorGroup cg;
+ if ( isEnabled() )
+ cg = hasFocus() ? QPalette::Active : QPalette::Inactive;
+ else
+ cg = QPalette::Disabled;
+
+ double north = origin();
+ if ( isValid() )
+ {
+ if ( mode() == RotateScale )
+ north -= value();
+ }
+
+ const int margin = 4;
+ drawRose( painter, center, radius - margin, 360.0 - north, cg );
+}
+
+/*!
+ Draw the compass rose
+
+ \param painter Painter
+ \param center Center of the compass
+ \param radius of the circle, where to paint the rose
+ \param north Direction pointing north, in degrees counter clockwise
+ \param cg Color group
+*/
+void QwtCompass::drawRose( QPainter *painter, const QPointF &center,
+ double radius, double north, QPalette::ColorGroup cg ) const
+{
+ if ( d_data->rose )
+ d_data->rose->draw( painter, center, radius, north, cg );
+}
+
+/*!
+ Set a rose for the compass
+ \param rose Compass rose
+ \warning The rose will be deleted, when a different rose is
+ set or in ~QwtCompass
+ \sa rose()
+*/
+void QwtCompass::setRose( QwtCompassRose *rose )
+{
+ if ( rose != d_data->rose )
+ {
+ if ( d_data->rose )
+ delete d_data->rose;
+
+ d_data->rose = rose;
+ update();
+ }
+}
+
+/*!
+ \return rose
+ \sa setRose()
+*/
+const QwtCompassRose *QwtCompass::rose() const
+{
+ return d_data->rose;
+}
+
+/*!
+ \return rose
+ \sa setRose()
+*/
+QwtCompassRose *QwtCompass::rose()
+{
+ return d_data->rose;
+}
+
+/*!
+ Handles key events
+
+ Beside the keys described in QwtDial::keyPressEvent numbers
+ from 1-9 (without 5) set the direction according to their
+ position on the num pad.
+
+ \sa isReadOnly()
+*/
+void QwtCompass::keyPressEvent( QKeyEvent *kev )
+{
+ if ( isReadOnly() )
+ return;
+
+#if 0
+ if ( kev->key() == Key_5 )
+ {
+ invalidate(); // signal ???
+ return;
+ }
+#endif
+
+ double newValue = value();
+
+ if ( kev->key() >= Qt::Key_1 && kev->key() <= Qt::Key_9 )
+ {
+ if ( mode() != RotateNeedle || kev->key() == Qt::Key_5 )
+ return;
+
+ switch ( kev->key() )
+ {
+ case Qt::Key_6:
+ newValue = 180.0 * 0.0;
+ break;
+ case Qt::Key_3:
+ newValue = 180.0 * 0.25;
+ break;
+ case Qt::Key_2:
+ newValue = 180.0 * 0.5;
+ break;
+ case Qt::Key_1:
+ newValue = 180.0 * 0.75;
+ break;
+ case Qt::Key_4:
+ newValue = 180.0 * 1.0;
+ break;
+ case Qt::Key_7:
+ newValue = 180.0 * 1.25;
+ break;
+ case Qt::Key_8:
+ newValue = 180.0 * 1.5;
+ break;
+ case Qt::Key_9:
+ newValue = 180.0 * 1.75;
+ break;
+ }
+ newValue -= origin();
+ setValue( newValue );
+ }
+ else
+ {
+ QwtDial::keyPressEvent( kev );
+ }
+}
+
+/*!
+ \return map, mapping values to labels
+ \sa setLabelMap()
+*/
+const QMap<double, QString> &QwtCompass::labelMap() const
+{
+ return d_data->labelMap;
+}
+
+/*!
+ \return map, mapping values to labels
+ \sa setLabelMap()
+*/
+QMap<double, QString> &QwtCompass::labelMap()
+{
+ return d_data->labelMap;
+}
+
+/*!
+ \brief Set a map, mapping values to labels
+ \param map value to label map
+
+ The values of the major ticks are found by looking into this
+ map. The default map consists of the labels N, NE, E, SE, S, SW, W, NW.
+
+ \warning The map will have no effect for values that are no major
+ tick values. Major ticks can be changed by QwtScaleDraw::setScale
+
+ \sa labelMap(), scaleDraw(), setScale()
+*/
+void QwtCompass::setLabelMap( const QMap<double, QString> &map )
+{
+ d_data->labelMap = map;
+}
+
+/*!
+ Map a value to a corresponding label
+ \param value Value that will be mapped
+ \return Label, or QString::null
+
+ label() looks in a map for a corresponding label for value
+ or return an null text.
+ \sa labelMap(), setLabelMap()
+*/
+
+QwtText QwtCompass::scaleLabel( double value ) const
+{
+ if ( qFuzzyCompare( value + 1.0, 1.0 ) )
+ value = 0.0;
+
+ if ( value < 0.0 )
+ value += 360.0;
+
+ if ( d_data->labelMap.contains( value ) )
+ return d_data->labelMap[value];
+
+ return QwtText();
+}
diff --git a/src/libpcp_qwt/src/qwt_compass.h b/src/libpcp_qwt/src/qwt_compass.h
new file mode 100644
index 0000000..a1044b7
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_compass.h
@@ -0,0 +1,65 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_COMPASS_H
+#define QWT_COMPASS_H 1
+
+#include "qwt_global.h"
+#include "qwt_dial.h"
+#include <qstring.h>
+#include <qmap.h>
+
+class QwtCompassRose;
+
+/*!
+ \brief A Compass Widget
+
+ QwtCompass is a widget to display and enter directions. It consists
+ of a scale, an optional needle and rose.
+
+ \image html dials1.png
+
+ \note The examples/dials example shows how to use QwtCompass.
+*/
+
+class QWT_EXPORT QwtCompass: public QwtDial
+{
+ Q_OBJECT
+
+public:
+ explicit QwtCompass( QWidget* parent = NULL );
+ virtual ~QwtCompass();
+
+ void setRose( QwtCompassRose *rose );
+ const QwtCompassRose *rose() const;
+ QwtCompassRose *rose();
+
+ const QMap<double, QString> &labelMap() const;
+ QMap<double, QString> &labelMap();
+ void setLabelMap( const QMap<double, QString> &map );
+
+protected:
+ virtual QwtText scaleLabel( double value ) const;
+
+ virtual void drawRose( QPainter *, const QPointF &center,
+ double radius, double north, QPalette::ColorGroup ) const;
+
+ virtual void drawScaleContents( QPainter *,
+ const QPointF &center, double radius ) const;
+
+ virtual void keyPressEvent( QKeyEvent * );
+
+private:
+ void initCompass();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_compass_rose.cpp b/src/libpcp_qwt/src/qwt_compass_rose.cpp
new file mode 100644
index 0000000..375a5bd
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_compass_rose.cpp
@@ -0,0 +1,265 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_compass_rose.h"
+#include "qwt_point_polar.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+
+static QPointF qwtIntersection(
+ QPointF p11, QPointF p12, QPointF p21, QPointF p22 )
+{
+ const QLineF line1( p11, p12 );
+ const QLineF line2( p21, p22 );
+
+ QPointF pos;
+ if ( line1.intersect( line2, &pos ) == QLineF::NoIntersection )
+ return QPointF();
+
+ return pos;
+}
+
+class QwtSimpleCompassRose::PrivateData
+{
+public:
+ PrivateData():
+ width( 0.2 ),
+ numThorns( 8 ),
+ numThornLevels( -1 ),
+ shrinkFactor( 0.9 )
+ {
+ }
+
+ double width;
+ int numThorns;
+ int numThornLevels;
+ double shrinkFactor;
+};
+
+/*!
+ Constructor
+
+ \param numThorns Number of thorns
+ \param numThornLevels Number of thorn levels
+*/
+QwtSimpleCompassRose::QwtSimpleCompassRose(
+ int numThorns, int numThornLevels )
+{
+ d_data = new PrivateData();
+ d_data->numThorns = numThorns;
+ d_data->numThornLevels = numThornLevels;
+
+ const QColor dark( 128, 128, 255 );
+ const QColor light( 192, 255, 255 );
+
+ QPalette palette;
+ palette.setColor( QPalette::Dark, dark );
+ palette.setColor( QPalette::Light, light );
+
+ setPalette( palette );
+}
+
+//! Destructor
+QwtSimpleCompassRose::~QwtSimpleCompassRose()
+{
+ delete d_data;
+}
+
+/*!
+ Set the Factor how to shrink the thorns with each level
+ The default value is 0.9.
+
+ \sa shrinkFactor()
+*/
+void QwtSimpleCompassRose::setShrinkFactor( double factor )
+{
+ d_data->shrinkFactor = factor;
+}
+
+/*!
+ \return Factor how to shrink the thorns with each level
+ \sa setShrinkFactor()
+*/
+double QwtSimpleCompassRose::shrinkFactor() const
+{
+ return d_data->shrinkFactor;
+}
+
+/*!
+ Draw the rose
+
+ \param painter Painter
+ \param center Center point
+ \param radius Radius of the rose
+ \param north Position
+ \param cg Color group
+*/
+void QwtSimpleCompassRose::draw( QPainter *painter, const QPointF &center,
+ double radius, double north, QPalette::ColorGroup cg ) const
+{
+ QPalette pal = palette();
+ pal.setCurrentColorGroup( cg );
+
+ drawRose( painter, pal, center, radius, north, d_data->width,
+ d_data->numThorns, d_data->numThornLevels, d_data->shrinkFactor );
+}
+
+/*!
+ Draw the rose
+
+ \param painter Painter
+ \param palette Palette
+ \param center Center of the rose
+ \param radius Radius of the rose
+ \param north Position pointing to north
+ \param width Width of the rose
+ \param numThorns Number of thorns
+ \param numThornLevels Number of thorn levels
+ \param shrinkFactor Factor to shrink the thorns with each level
+*/
+void QwtSimpleCompassRose::drawRose(
+ QPainter *painter,
+ const QPalette &palette,
+ const QPointF &center, double radius, double north, double width,
+ int numThorns, int numThornLevels, double shrinkFactor )
+{
+ if ( numThorns < 4 )
+ numThorns = 4;
+
+ if ( numThorns % 4 )
+ numThorns += 4 - numThorns % 4;
+
+ if ( numThornLevels <= 0 )
+ numThornLevels = numThorns / 4;
+
+ if ( shrinkFactor >= 1.0 )
+ shrinkFactor = 1.0;
+
+ if ( shrinkFactor <= 0.5 )
+ shrinkFactor = 0.5;
+
+ painter->save();
+
+ painter->setPen( Qt::NoPen );
+
+ for ( int j = 1; j <= numThornLevels; j++ )
+ {
+ double step = qPow( 2.0, j ) * M_PI / numThorns;
+ if ( step > M_PI_2 )
+ break;
+
+ double r = radius;
+ for ( int k = 0; k < 3; k++ )
+ {
+ if ( j + k < numThornLevels )
+ r *= shrinkFactor;
+ }
+
+ double leafWidth = r * width;
+ if ( 2.0 * M_PI / step > 32 )
+ leafWidth = 16;
+
+ const double origin = north / 180.0 * M_PI;
+ for ( double angle = origin;
+ angle < 2.0 * M_PI + origin; angle += step )
+ {
+ const QPointF p = qwtPolar2Pos( center, r, angle );
+ const QPointF p1 = qwtPolar2Pos( center, leafWidth, angle + M_PI_2 );
+ const QPointF p2 = qwtPolar2Pos( center, leafWidth, angle - M_PI_2 );
+ const QPointF p3 = qwtPolar2Pos( center, r, angle + step / 2.0 );
+ const QPointF p4 = qwtPolar2Pos( center, r, angle - step / 2.0 );
+
+ QPainterPath darkPath;
+ darkPath.moveTo( center );
+ darkPath.lineTo( p );
+ darkPath.lineTo( qwtIntersection( center, p3, p1, p ) );
+
+ painter->setBrush( palette.brush( QPalette::Dark ) );
+ painter->drawPath( darkPath );
+
+ QPainterPath lightPath;
+ lightPath.moveTo( center );
+ lightPath.lineTo( p );
+ lightPath.lineTo( qwtIntersection( center, p4, p2, p ) );
+
+ painter->setBrush( palette.brush( QPalette::Light ) );
+ painter->drawPath( lightPath );
+ }
+ }
+ painter->restore();
+}
+
+/*!
+ Set the width of the rose heads. Lower value make thinner heads.
+ The range is limited from 0.03 to 0.4.
+
+ \param width Width
+*/
+void QwtSimpleCompassRose::setWidth( double width )
+{
+ d_data->width = width;
+ if ( d_data->width < 0.03 )
+ d_data->width = 0.03;
+
+ if ( d_data->width > 0.4 )
+ d_data->width = 0.4;
+}
+
+//! \sa setWidth()
+double QwtSimpleCompassRose::width() const
+{
+ return d_data->width;
+}
+
+/*!
+ Set the number of thorns on one level
+ The number is aligned to a multiple of 4, with a minimum of 4
+
+ \param numThorns Number of thorns
+ \sa numThorns(), setNumThornLevels()
+*/
+void QwtSimpleCompassRose::setNumThorns( int numThorns )
+{
+ if ( numThorns < 4 )
+ numThorns = 4;
+
+ if ( numThorns % 4 )
+ numThorns += 4 - numThorns % 4;
+
+ d_data->numThorns = numThorns;
+}
+
+/*!
+ \return Number of thorns
+ \sa setNumThorns(), setNumThornLevels()
+*/
+int QwtSimpleCompassRose::numThorns() const
+{
+ return d_data->numThorns;
+}
+
+/*!
+ Set the of thorns levels
+
+ \param numThornLevels Number of thorns levels
+ \sa setNumThorns(), numThornLevels()
+*/
+void QwtSimpleCompassRose::setNumThornLevels( int numThornLevels )
+{
+ d_data->numThornLevels = numThornLevels;
+}
+
+/*!
+ \return Number of thorn levels
+ \sa setNumThorns(), setNumThornLevels()
+*/
+int QwtSimpleCompassRose::numThornLevels() const
+{
+ return d_data->numThornLevels;
+}
diff --git a/src/libpcp_qwt/src/qwt_compass_rose.h b/src/libpcp_qwt/src/qwt_compass_rose.h
new file mode 100644
index 0000000..9b715df
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_compass_rose.h
@@ -0,0 +1,89 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_COMPASS_ROSE_H
+#define QWT_COMPASS_ROSE_H 1
+
+#include "qwt_global.h"
+#include <qpalette.h>
+
+class QPainter;
+
+/*!
+ \brief Abstract base class for a compass rose
+*/
+class QWT_EXPORT QwtCompassRose
+{
+public:
+ //! Destructor
+ virtual ~QwtCompassRose() {};
+
+ //! Assign a palette
+ virtual void setPalette( const QPalette &p )
+ {
+ d_palette = p;
+ }
+
+ //! \return Current palette
+ const QPalette &palette() const
+ {
+ return d_palette;
+ }
+
+ /*!
+ Draw the rose
+
+ \param painter Painter
+ \param center Center point
+ \param radius Radius of the rose
+ \param north Position
+ \param colorGroup Color group
+ */
+ virtual void draw( QPainter *painter,
+ const QPointF &center, double radius, double north,
+ QPalette::ColorGroup colorGroup = QPalette::Active ) const = 0;
+
+private:
+ QPalette d_palette;
+};
+
+/*!
+ \brief A simple rose for QwtCompass
+*/
+class QWT_EXPORT QwtSimpleCompassRose: public QwtCompassRose
+{
+public:
+ QwtSimpleCompassRose( int numThorns = 8, int numThornLevels = -1 );
+ virtual ~QwtSimpleCompassRose();
+
+ void setWidth( double w );
+ double width() const;
+
+ void setNumThorns( int count );
+ int numThorns() const;
+
+ void setNumThornLevels( int count );
+ int numThornLevels() const;
+
+ void setShrinkFactor( double factor );
+ double shrinkFactor() const;
+
+ virtual void draw( QPainter *, const QPointF &center, double radius,
+ double north, QPalette::ColorGroup = QPalette::Active ) const;
+
+ static void drawRose( QPainter *, const QPalette &,
+ const QPointF &center, double radius, double origin, double width,
+ int numThorns, int numThornLevels, double shrinkFactor );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_compat.h b/src/libpcp_qwt/src/qwt_compat.h
new file mode 100644
index 0000000..c97cf6b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_compat.h
@@ -0,0 +1,42 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef _QWT_COMPAT_H_
+#define _QWT_COMPAT_H_
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include "qwt_point_3d.h"
+#include <qlist.h>
+#include <qvector.h>
+#include <qpoint.h>
+#include <qsize.h>
+#include <qrect.h>
+#include <qpolygon.h>
+
+// A couple of definition for Qwt5 compatibility
+
+#define qwtMax qMax
+#define qwtMin qMin
+#define qwtAbs qAbs
+#define qwtRound qRound
+
+#define QwtArray QVector
+
+typedef QList<double> QwtValueList;
+typedef QPointF QwtDoublePoint;
+typedef QSizeF QwtDoubleSize;
+typedef QRectF QwtDoubleRect;
+
+typedef QPolygon QwtPolygon;
+typedef QPolygonF QwtPolygonF;
+typedef QwtInterval QwtDoubleInterval;
+typedef QwtPoint3D QwtDoublePoint3D;
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_counter.cpp b/src/libpcp_qwt/src/qwt_counter.cpp
new file mode 100644
index 0000000..2c7a895
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_counter.cpp
@@ -0,0 +1,603 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_arrow_button.h"
+#include "qwt_math.h"
+#include "qwt_counter.h"
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qvalidator.h>
+#include <qevent.h>
+#include <qstyle.h>
+
+class QwtCounter::PrivateData
+{
+public:
+ PrivateData():
+ editable( true )
+ {
+ increment[Button1] = 1;
+ increment[Button2] = 10;
+ increment[Button3] = 100;
+ }
+
+ QwtArrowButton *buttonDown[ButtonCnt];
+ QwtArrowButton *buttonUp[ButtonCnt];
+ QLineEdit *valueEdit;
+
+ int increment[ButtonCnt];
+ int numButtons;
+
+ bool editable;
+};
+
+/*!
+ The default number of buttons is set to 2. The default increments are:
+ \li Button 1: 1 step
+ \li Button 2: 10 steps
+ \li Button 3: 100 steps
+
+ \param parent
+ */
+QwtCounter::QwtCounter( QWidget *parent ):
+ QWidget( parent )
+{
+ initCounter();
+}
+
+void QwtCounter::initCounter()
+{
+ d_data = new PrivateData;
+
+ QHBoxLayout *layout = new QHBoxLayout( this );
+ layout->setSpacing( 0 );
+ layout->setMargin( 0 );
+
+ for ( int i = ButtonCnt - 1; i >= 0; i-- )
+ {
+ QwtArrowButton *btn =
+ new QwtArrowButton( i + 1, Qt::DownArrow, this );
+ btn->setFocusPolicy( Qt::NoFocus );
+ btn->installEventFilter( this );
+ layout->addWidget( btn );
+
+ connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) );
+ connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) );
+
+ d_data->buttonDown[i] = btn;
+ }
+
+ d_data->valueEdit = new QLineEdit( this );
+ d_data->valueEdit->setReadOnly( false );
+ d_data->valueEdit->setValidator( new QDoubleValidator( d_data->valueEdit ) );
+ layout->addWidget( d_data->valueEdit );
+
+ connect( d_data->valueEdit, SIGNAL( editingFinished() ),
+ SLOT( textChanged() ) );
+
+ layout->setStretchFactor( d_data->valueEdit, 10 );
+
+ for ( int i = 0; i < ButtonCnt; i++ )
+ {
+ QwtArrowButton *btn =
+ new QwtArrowButton( i + 1, Qt::UpArrow, this );
+ btn->setFocusPolicy( Qt::NoFocus );
+ btn->installEventFilter( this );
+ layout->addWidget( btn );
+
+ connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) );
+ connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) );
+
+ d_data->buttonUp[i] = btn;
+ }
+
+ setNumButtons( 2 );
+ setRange( 0.0, 1.0, 0.001 );
+ setValue( 0.0 );
+
+ setSizePolicy(
+ QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) );
+
+ setFocusProxy( d_data->valueEdit );
+ setFocusPolicy( Qt::StrongFocus );
+}
+
+//! Destructor
+QwtCounter::~QwtCounter()
+{
+ delete d_data;
+}
+
+//! Set from lineedit
+void QwtCounter::textChanged()
+{
+ if ( !d_data->editable )
+ return;
+
+ bool converted = false;
+
+ const double value = d_data->valueEdit->text().toDouble( &converted );
+ if ( converted )
+ setValue( value );
+}
+
+/**
+ \brief Allow/disallow the user to manually edit the value
+
+ \param editable true enables editing
+ \sa editable()
+*/
+void QwtCounter::setEditable( bool editable )
+{
+ if ( editable == d_data->editable )
+ return;
+
+ d_data->editable = editable;
+ d_data->valueEdit->setReadOnly( !editable );
+}
+
+//! returns whether the line edit is edatble. (default is yes)
+bool QwtCounter::editable() const
+{
+ return d_data->editable;
+}
+
+/*!
+ Handle PolishRequest events
+ \param event Event
+*/
+bool QwtCounter::event( QEvent *event )
+{
+ if ( event->type() == QEvent::PolishRequest )
+ {
+ const int w = d_data->valueEdit->fontMetrics().width( "W" ) + 8;
+ for ( int i = 0; i < ButtonCnt; i++ )
+ {
+ d_data->buttonDown[i]->setMinimumWidth( w );
+ d_data->buttonUp[i]->setMinimumWidth( w );
+ }
+ }
+
+ return QWidget::event( event );
+}
+
+/*!
+ Handle key events
+
+ - Ctrl + Qt::Key_Home\n
+ Step to minValue()
+ - Ctrl + Qt::Key_End\n
+ Step to maxValue()
+ - Qt::Key_Up\n
+ Increment by incSteps(QwtCounter::Button1)
+ - Qt::Key_Down\n
+ Decrement by incSteps(QwtCounter::Button1)
+ - Qt::Key_PageUp\n
+ Increment by incSteps(QwtCounter::Button2)
+ - Qt::Key_PageDown\n
+ Decrement by incSteps(QwtCounter::Button2)
+ - Shift + Qt::Key_PageUp\n
+ Increment by incSteps(QwtCounter::Button3)
+ - Shift + Qt::Key_PageDown\n
+ Decrement by incSteps(QwtCounter::Button3)
+
+ \param event Key event
+*/
+void QwtCounter::keyPressEvent ( QKeyEvent *event )
+{
+ bool accepted = true;
+
+ switch ( event->key() )
+ {
+ case Qt::Key_Home:
+ {
+ if ( event->modifiers() & Qt::ControlModifier )
+ setValue( minValue() );
+ else
+ accepted = false;
+ break;
+ }
+ case Qt::Key_End:
+ {
+ if ( event->modifiers() & Qt::ControlModifier )
+ setValue( maxValue() );
+ else
+ accepted = false;
+ break;
+ }
+ case Qt::Key_Up:
+ {
+ incValue( d_data->increment[0] );
+ break;
+ }
+ case Qt::Key_Down:
+ {
+ incValue( -d_data->increment[0] );
+ break;
+ }
+ case Qt::Key_PageUp:
+ case Qt::Key_PageDown:
+ {
+ int increment = d_data->increment[0];
+ if ( d_data->numButtons >= 2 )
+ increment = d_data->increment[1];
+ if ( d_data->numButtons >= 3 )
+ {
+ if ( event->modifiers() & Qt::ShiftModifier )
+ increment = d_data->increment[2];
+ }
+ if ( event->key() == Qt::Key_PageDown )
+ increment = -increment;
+ incValue( increment );
+ break;
+ }
+ default:
+ {
+ accepted = false;
+ }
+ }
+
+ if ( accepted )
+ {
+ event->accept();
+ return;
+ }
+
+ QWidget::keyPressEvent ( event );
+}
+
+/*!
+ Handle wheel events
+ \param event Wheel event
+*/
+void QwtCounter::wheelEvent( QWheelEvent *event )
+{
+ event->accept();
+
+ if ( d_data->numButtons <= 0 )
+ return;
+
+ int increment = d_data->increment[0];
+ if ( d_data->numButtons >= 2 )
+ {
+ if ( event->modifiers() & Qt::ControlModifier )
+ increment = d_data->increment[1];
+ }
+ if ( d_data->numButtons >= 3 )
+ {
+ if ( event->modifiers() & Qt::ShiftModifier )
+ increment = d_data->increment[2];
+ }
+
+ for ( int i = 0; i < d_data->numButtons; i++ )
+ {
+ if ( d_data->buttonDown[i]->geometry().contains( event->pos() ) ||
+ d_data->buttonUp[i]->geometry().contains( event->pos() ) )
+ {
+ increment = d_data->increment[i];
+ }
+ }
+
+ const int wheel_delta = 120;
+
+ int delta = event->delta();
+ if ( delta >= 2 * wheel_delta )
+ delta /= 2; // Never saw an abs(delta) < 240
+
+ incValue( delta / wheel_delta * increment );
+}
+
+/*!
+ Specify the number of steps by which the value
+ is incremented or decremented when a specified button
+ is pushed.
+
+ \param button Button index
+ \param nSteps Number of steps
+
+ \sa incSteps()
+*/
+void QwtCounter::setIncSteps( QwtCounter::Button button, int nSteps )
+{
+ if ( button >= 0 && button < ButtonCnt )
+ d_data->increment[button] = nSteps;
+}
+
+/*!
+ \return the number of steps by which a specified button increments the value
+ or 0 if the button is invalid.
+ \param button Button index
+
+ \sa setIncSteps()
+*/
+int QwtCounter::incSteps( QwtCounter::Button button ) const
+{
+ if ( button >= 0 && button < ButtonCnt )
+ return d_data->increment[button];
+
+ return 0;
+}
+
+/*!
+ \brief Set a new value
+
+ Calls QwtDoubleRange::setValue and does all visual updates.
+
+ \param value New value
+ \sa QwtDoubleRange::setValue()
+*/
+
+void QwtCounter::setValue( double value )
+{
+ QwtDoubleRange::setValue( value );
+
+ showNum( this->value() );
+ updateButtons();
+}
+
+/*!
+ \brief Notify a change of value
+*/
+void QwtCounter::valueChange()
+{
+ if ( isValid() )
+ showNum( value() );
+ else
+ d_data->valueEdit->setText( QString::null );
+
+ updateButtons();
+
+ if ( isValid() )
+ Q_EMIT valueChanged( value() );
+}
+
+/*!
+ \brief Update buttons according to the current value
+
+ When the QwtCounter under- or over-flows, the focus is set to the smallest
+ up- or down-button and counting is disabled.
+
+ Counting is re-enabled on a button release event (mouse or space bar).
+*/
+void QwtCounter::updateButtons()
+{
+ if ( isValid() )
+ {
+ // 1. save enabled state of the smallest down- and up-button
+ // 2. change enabled state on under- or over-flow
+
+ for ( int i = 0; i < QwtCounter::ButtonCnt; i++ )
+ {
+ d_data->buttonDown[i]->setEnabled( value() > minValue() );
+ d_data->buttonUp[i]->setEnabled( value() < maxValue() );
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < QwtCounter::ButtonCnt; i++ )
+ {
+ d_data->buttonDown[i]->setEnabled( false );
+ d_data->buttonUp[i]->setEnabled( false );
+ }
+ }
+}
+
+/*!
+ \brief Specify the number of buttons on each side of the label
+ \param numButtons Number of buttons
+*/
+void QwtCounter::setNumButtons( int numButtons )
+{
+ if ( numButtons < 0 || numButtons > QwtCounter::ButtonCnt )
+ return;
+
+ for ( int i = 0; i < QwtCounter::ButtonCnt; i++ )
+ {
+ if ( i < numButtons )
+ {
+ d_data->buttonDown[i]->show();
+ d_data->buttonUp[i]->show();
+ }
+ else
+ {
+ d_data->buttonDown[i]->hide();
+ d_data->buttonUp[i]->hide();
+ }
+ }
+
+ d_data->numButtons = numButtons;
+}
+
+/*!
+ \return The number of buttons on each side of the widget.
+*/
+int QwtCounter::numButtons() const
+{
+ return d_data->numButtons;
+}
+
+/*!
+ Display number string
+
+ \param number Number
+*/
+void QwtCounter::showNum( double number )
+{
+ QString text;
+ text.setNum( number );
+
+ const int cursorPos = d_data->valueEdit->cursorPosition();
+ d_data->valueEdit->setText( text );
+ d_data->valueEdit->setCursorPosition( cursorPos );
+}
+
+//! Button clicked
+void QwtCounter::btnClicked()
+{
+ for ( int i = 0; i < ButtonCnt; i++ )
+ {
+ if ( d_data->buttonUp[i] == sender() )
+ incValue( d_data->increment[i] );
+
+ if ( d_data->buttonDown[i] == sender() )
+ incValue( -d_data->increment[i] );
+ }
+}
+
+//! Button released
+void QwtCounter::btnReleased()
+{
+ Q_EMIT buttonReleased( value() );
+}
+
+/*!
+ \brief Notify change of range
+
+ This function updates the enabled property of
+ all buttons contained in QwtCounter.
+*/
+void QwtCounter::rangeChange()
+{
+ updateButtons();
+}
+
+//! A size hint
+QSize QwtCounter::sizeHint() const
+{
+ QString tmp;
+
+ int w = tmp.setNum( minValue() ).length();
+ int w1 = tmp.setNum( maxValue() ).length();
+ if ( w1 > w )
+ w = w1;
+ w1 = tmp.setNum( minValue() + step() ).length();
+ if ( w1 > w )
+ w = w1;
+ w1 = tmp.setNum( maxValue() - step() ).length();
+ if ( w1 > w )
+ w = w1;
+
+ tmp.fill( '9', w );
+
+ QFontMetrics fm( d_data->valueEdit->font() );
+ w = fm.width( tmp ) + 2;
+ if ( d_data->valueEdit->hasFrame() )
+ w += 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
+
+ // Now we replace default sizeHint contribution of d_data->valueEdit by
+ // what we really need.
+
+ w += QWidget::sizeHint().width() - d_data->valueEdit->sizeHint().width();
+
+ const int h = qMin( QWidget::sizeHint().height(),
+ d_data->valueEdit->minimumSizeHint().height() );
+ return QSize( w, h );
+}
+
+//! returns the step size
+double QwtCounter::step() const
+{
+ return QwtDoubleRange::step();
+}
+
+/*!
+ Set the step size
+ \param stepSize Step size
+ \sa QwtDoubleRange::setStep()
+*/
+void QwtCounter::setStep( double stepSize )
+{
+ QwtDoubleRange::setStep( stepSize );
+}
+
+//! returns the minimum value of the range
+double QwtCounter::minValue() const
+{
+ return QwtDoubleRange::minValue();
+}
+
+/*!
+ Set the minimum value of the range
+
+ \param value Minimum value
+ \sa setMaxValue(), minValue()
+*/
+void QwtCounter::setMinValue( double value )
+{
+ setRange( value, maxValue(), step() );
+}
+
+//! returns the maximum value of the range
+double QwtCounter::maxValue() const
+{
+ return QwtDoubleRange::maxValue();
+}
+
+/*!
+ Set the maximum value of the range
+
+ \param value Maximum value
+ \sa setMinValue(), maxVal()
+*/
+void QwtCounter::setMaxValue( double value )
+{
+ setRange( minValue(), value, step() );
+}
+
+/*!
+ Set the number of increment steps for button 1
+ \param nSteps Number of steps
+*/
+void QwtCounter::setStepButton1( int nSteps )
+{
+ setIncSteps( Button1, nSteps );
+}
+
+//! returns the number of increment steps for button 1
+int QwtCounter::stepButton1() const
+{
+ return incSteps( Button1 );
+}
+
+/*!
+ Set the number of increment steps for button 2
+ \param nSteps Number of steps
+*/
+void QwtCounter::setStepButton2( int nSteps )
+{
+ setIncSteps( Button2, nSteps );
+}
+
+//! returns the number of increment steps for button 2
+int QwtCounter::stepButton2() const
+{
+ return incSteps( Button2 );
+}
+
+/*!
+ Set the number of increment steps for button 3
+ \param nSteps Number of steps
+*/
+void QwtCounter::setStepButton3( int nSteps )
+{
+ setIncSteps( Button3, nSteps );
+}
+
+//! returns the number of increment steps for button 3
+int QwtCounter::stepButton3() const
+{
+ return incSteps( Button3 );
+}
+
+//! \return Current value
+double QwtCounter::value() const
+{
+ return QwtDoubleRange::value();
+}
+
diff --git a/src/libpcp_qwt/src/qwt_counter.h b/src/libpcp_qwt/src/qwt_counter.h
new file mode 100644
index 0000000..459fdf9
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_counter.h
@@ -0,0 +1,148 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_COUNTER_H
+#define QWT_COUNTER_H
+
+#include "qwt_global.h"
+#include "qwt_double_range.h"
+#include <qwidget.h>
+
+/*!
+ \brief The Counter Widget
+
+ A Counter consists of a lineEdit displaying a number and
+ one ore more (up to three) push buttons on each side
+ of the lineEdit which can be used to increment or decrement
+ the counter's value.
+
+ A Counter has a range from a minimum value to a maximum value
+ and a step size. The range can be specified using setRange().
+ The number of steps by which a button increments or decrements
+ the value can be specified using setIncSteps().
+ The number of buttons can be changed with setNumButtons().
+
+ Example:
+\code
+#include <qwt_counter.h>
+
+QwtCounter *counter = new QwtCounter(parent);
+
+counter->setRange(0.0, 100.0, 1.0); // From 0.0 to 100, step 1.0
+counter->setNumButtons(2); // Two buttons each side
+counter->setIncSteps(QwtCounter::Button1, 1); // Button 1 increments 1 step
+counter->setIncSteps(QwtCounter::Button2, 20); // Button 2 increments 20 steps
+
+connect(counter, SIGNAL(valueChanged(double)), my_class, SLOT(newValue(double)));
+\endcode
+ */
+
+class QWT_EXPORT QwtCounter : public QWidget, public QwtDoubleRange
+{
+ Q_OBJECT
+
+ Q_PROPERTY( int numButtons READ numButtons WRITE setNumButtons )
+ Q_PROPERTY( double basicstep READ step WRITE setStep )
+ Q_PROPERTY( double minValue READ minValue WRITE setMinValue )
+ Q_PROPERTY( double maxValue READ maxValue WRITE setMaxValue )
+ Q_PROPERTY( int stepButton1 READ stepButton1 WRITE setStepButton1 )
+ Q_PROPERTY( int stepButton2 READ stepButton2 WRITE setStepButton2 )
+ Q_PROPERTY( int stepButton3 READ stepButton3 WRITE setStepButton3 )
+ Q_PROPERTY( double value READ value WRITE setValue )
+ Q_PROPERTY( bool editable READ editable WRITE setEditable )
+
+public:
+ //! Button index
+ enum Button
+ {
+ //! Button intended for minor steps
+ Button1,
+
+ //! Button intended for medium steps
+ Button2,
+
+ //! Button intended for large steps
+ Button3,
+
+ //! Number of buttons
+ ButtonCnt
+ };
+
+ explicit QwtCounter( QWidget *parent = NULL );
+ virtual ~QwtCounter();
+
+ bool editable() const;
+ void setEditable( bool );
+
+ void setNumButtons( int n );
+ int numButtons() const;
+
+ void setIncSteps( QwtCounter::Button btn, int nSteps );
+ int incSteps( QwtCounter::Button btn ) const;
+
+ virtual void setValue( double );
+ virtual QSize sizeHint() const;
+
+ // a set of dummies to help the designer
+
+ double step() const;
+ void setStep( double s );
+
+ double minValue() const;
+ void setMinValue( double m );
+
+ double maxValue() const;
+ void setMaxValue( double m );
+
+ void setStepButton1( int nSteps );
+ int stepButton1() const;
+
+ void setStepButton2( int nSteps );
+ int stepButton2() const;
+
+ void setStepButton3( int nSteps );
+ int stepButton3() const;
+
+ virtual double value() const;
+
+Q_SIGNALS:
+ /*!
+ This signal is emitted when a button has been released
+ \param value The new value
+ */
+ void buttonReleased ( double value );
+
+ /*!
+ This signal is emitted when the counter's value has changed
+ \param value The new value
+ */
+ void valueChanged ( double value );
+
+protected:
+ virtual bool event( QEvent * );
+ virtual void wheelEvent( QWheelEvent * );
+ virtual void keyPressEvent( QKeyEvent * );
+ virtual void rangeChange();
+
+private Q_SLOTS:
+ void btnReleased();
+ void btnClicked();
+ void textChanged();
+
+private:
+ void initCounter();
+ void updateButtons();
+ void showNum( double );
+ virtual void valueChange();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_curve_fitter.cpp b/src/libpcp_qwt/src/qwt_curve_fitter.cpp
new file mode 100644
index 0000000..639a80d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_curve_fitter.cpp
@@ -0,0 +1,405 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_curve_fitter.h"
+#include "qwt_math.h"
+#include "qwt_spline.h"
+#include <qstack.h>
+#include <qvector.h>
+
+#if QT_VERSION < 0x040601
+#define qFabs(x) ::fabs(x)
+#endif
+
+//! Constructor
+QwtCurveFitter::QwtCurveFitter()
+{
+}
+
+//! Destructor
+QwtCurveFitter::~QwtCurveFitter()
+{
+}
+
+class QwtSplineCurveFitter::PrivateData
+{
+public:
+ PrivateData():
+ fitMode( QwtSplineCurveFitter::Auto ),
+ splineSize( 250 )
+ {
+ }
+
+ QwtSpline spline;
+ QwtSplineCurveFitter::FitMode fitMode;
+ int splineSize;
+};
+
+//! Constructor
+QwtSplineCurveFitter::QwtSplineCurveFitter()
+{
+ d_data = new PrivateData;
+}
+
+//! Destructor
+QwtSplineCurveFitter::~QwtSplineCurveFitter()
+{
+ delete d_data;
+}
+
+/*!
+ Select the algorithm used for building the spline
+
+ \param mode Mode representing a spline algorithm
+ \sa fitMode()
+*/
+void QwtSplineCurveFitter::setFitMode( FitMode mode )
+{
+ d_data->fitMode = mode;
+}
+
+/*!
+ \return Mode representing a spline algorithm
+ \sa setFitMode()
+*/
+QwtSplineCurveFitter::FitMode QwtSplineCurveFitter::fitMode() const
+{
+ return d_data->fitMode;
+}
+
+/*!
+ Assign a spline
+
+ \param spline Spline
+ \sa spline()
+*/
+void QwtSplineCurveFitter::setSpline( const QwtSpline &spline )
+{
+ d_data->spline = spline;
+ d_data->spline.reset();
+}
+
+/*!
+ \return Spline
+ \sa setSpline()
+*/
+const QwtSpline &QwtSplineCurveFitter::spline() const
+{
+ return d_data->spline;
+}
+
+/*!
+ \return Spline
+ \sa setSpline()
+*/
+QwtSpline &QwtSplineCurveFitter::spline()
+{
+ return d_data->spline;
+}
+
+/*!
+ Assign a spline size ( has to be at least 10 points )
+
+ \param splineSize Spline size
+ \sa splineSize()
+*/
+void QwtSplineCurveFitter::setSplineSize( int splineSize )
+{
+ d_data->splineSize = qMax( splineSize, 10 );
+}
+
+/*!
+ \return Spline size
+ \sa setSplineSize()
+*/
+int QwtSplineCurveFitter::splineSize() const
+{
+ return d_data->splineSize;
+}
+
+/*!
+ Find a curve which has the best fit to a series of data points
+
+ \param points Series of data points
+ \return Curve points
+*/
+QPolygonF QwtSplineCurveFitter::fitCurve( const QPolygonF &points ) const
+{
+ const int size = points.size();
+ if ( size <= 2 )
+ return points;
+
+ FitMode fitMode = d_data->fitMode;
+ if ( fitMode == Auto )
+ {
+ fitMode = Spline;
+
+ const QPointF *p = points.data();
+ for ( int i = 1; i < size; i++ )
+ {
+ if ( p[i].x() <= p[i-1].x() )
+ {
+ fitMode = ParametricSpline;
+ break;
+ }
+ };
+ }
+
+ if ( fitMode == ParametricSpline )
+ return fitParametric( points );
+ else
+ return fitSpline( points );
+}
+
+QPolygonF QwtSplineCurveFitter::fitSpline( const QPolygonF &points ) const
+{
+ d_data->spline.setPoints( points );
+ if ( !d_data->spline.isValid() )
+ return points;
+
+ QPolygonF fittedPoints( d_data->splineSize );
+
+ const double x1 = points[0].x();
+ const double x2 = points[int( points.size() - 1 )].x();
+ const double dx = x2 - x1;
+ const double delta = dx / ( d_data->splineSize - 1 );
+
+ for ( int i = 0; i < d_data->splineSize; i++ )
+ {
+ QPointF &p = fittedPoints[i];
+
+ const double v = x1 + i * delta;
+ const double sv = d_data->spline.value( v );
+
+ p.setX( v );
+ p.setY( sv );
+ }
+ d_data->spline.reset();
+
+ return fittedPoints;
+}
+
+QPolygonF QwtSplineCurveFitter::fitParametric( const QPolygonF &points ) const
+{
+ int i;
+ const int size = points.size();
+
+ QPolygonF fittedPoints( d_data->splineSize );
+ QPolygonF splinePointsX( size );
+ QPolygonF splinePointsY( size );
+
+ const QPointF *p = points.data();
+ QPointF *spX = splinePointsX.data();
+ QPointF *spY = splinePointsY.data();
+
+ double param = 0.0;
+ for ( i = 0; i < size; i++ )
+ {
+ const double x = p[i].x();
+ const double y = p[i].y();
+ if ( i > 0 )
+ {
+ const double delta = qSqrt( qwtSqr( x - spX[i-1].y() )
+ + qwtSqr( y - spY[i-1].y() ) );
+ param += qMax( delta, 1.0 );
+ }
+ spX[i].setX( param );
+ spX[i].setY( x );
+ spY[i].setX( param );
+ spY[i].setY( y );
+ }
+
+ d_data->spline.setPoints( splinePointsX );
+ if ( !d_data->spline.isValid() )
+ return points;
+
+ const double deltaX =
+ splinePointsX[size - 1].x() / ( d_data->splineSize - 1 );
+ for ( i = 0; i < d_data->splineSize; i++ )
+ {
+ const double dtmp = i * deltaX;
+ fittedPoints[i].setX( d_data->spline.value( dtmp ) );
+ }
+
+ d_data->spline.setPoints( splinePointsY );
+ if ( !d_data->spline.isValid() )
+ return points;
+
+ const double deltaY =
+ splinePointsY[size - 1].x() / ( d_data->splineSize - 1 );
+ for ( i = 0; i < d_data->splineSize; i++ )
+ {
+ const double dtmp = i * deltaY;
+ fittedPoints[i].setY( d_data->spline.value( dtmp ) );
+ }
+
+ return fittedPoints;
+}
+
+class QwtWeedingCurveFitter::PrivateData
+{
+public:
+ PrivateData():
+ tolerance( 1.0 )
+ {
+ }
+
+ double tolerance;
+};
+
+class QwtWeedingCurveFitter::Line
+{
+public:
+ Line( int i1 = 0, int i2 = 0 ):
+ from( i1 ),
+ to( i2 )
+ {
+ }
+
+ int from;
+ int to;
+};
+
+/*!
+ Constructor
+
+ \param tolerance Tolerance
+ \sa setTolerance(), tolerance()
+*/
+QwtWeedingCurveFitter::QwtWeedingCurveFitter( double tolerance )
+{
+ d_data = new PrivateData;
+ setTolerance( tolerance );
+}
+
+//! Destructor
+QwtWeedingCurveFitter::~QwtWeedingCurveFitter()
+{
+ delete d_data;
+}
+
+/*!
+ Assign the tolerance
+
+ The tolerance is the maximum distance, that is accaptable
+ between the original curve and the smoothed curve.
+
+ Increasing the tolerance will reduce the number of the
+ resulting points.
+
+ \param tolerance Tolerance
+
+ \sa tolerance()
+*/
+void QwtWeedingCurveFitter::setTolerance( double tolerance )
+{
+ d_data->tolerance = qMax( tolerance, 0.0 );
+}
+
+/*!
+ \return Tolerance
+ \sa setTolerance()
+*/
+double QwtWeedingCurveFitter::tolerance() const
+{
+ return d_data->tolerance;
+}
+
+/*!
+ \param points Series of data points
+ \return Curve points
+*/
+QPolygonF QwtWeedingCurveFitter::fitCurve( const QPolygonF &points ) const
+{
+ QStack<Line> stack;
+ stack.reserve( 500 );
+
+ const QPointF *p = points.data();
+ const int nPoints = points.size();
+
+ QVector<bool> usePoint( nPoints, false );
+
+ double distToSegment;
+
+ stack.push( Line( 0, nPoints - 1 ) );
+
+ while ( !stack.isEmpty() )
+ {
+ const Line r = stack.pop();
+
+ // initialize line segment
+ const double vecX = p[r.to].x() - p[r.from].x();
+ const double vecY = p[r.to].y() - p[r.from].y();
+
+ const double vecLength = qSqrt( vecX * vecX + vecY * vecY );
+
+ const double unitVecX = ( vecLength != 0.0 ) ? vecX / vecLength : 0.0;
+ const double unitVecY = ( vecLength != 0.0 ) ? vecY / vecLength : 0.0;
+
+ double maxDist = 0.0;
+ int nVertexIndexMaxDistance = r.from + 1;
+ for ( int i = r.from + 1; i < r.to; i++ )
+ {
+ //compare to anchor
+ const double fromVecX = p[i].x() - p[r.from].x();
+ const double fromVecY = p[i].y() - p[r.from].y();
+ const double fromVecLength =
+ qSqrt( fromVecX * fromVecX + fromVecY * fromVecY );
+
+ if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 )
+ {
+ distToSegment = fromVecLength;
+ }
+ if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 )
+ {
+ distToSegment = fromVecLength;
+ }
+ else
+ {
+ const double toVecX = p[i].x() - p[r.to].x();
+ const double toVecY = p[i].y() - p[r.to].y();
+ const double toVecLength = qSqrt( toVecX * toVecX + toVecY * toVecY );
+ const double s = toVecX * ( -unitVecX ) + toVecY * ( -unitVecY );
+ if ( s < 0.0 )
+ distToSegment = toVecLength;
+ else
+ {
+ distToSegment = qSqrt( qFabs( toVecLength * toVecLength - s * s ) );
+ }
+ }
+
+ if ( maxDist < distToSegment )
+ {
+ maxDist = distToSegment;
+ nVertexIndexMaxDistance = i;
+ }
+ }
+ if ( maxDist <= d_data->tolerance )
+ {
+ usePoint[r.from] = true;
+ usePoint[r.to] = true;
+ }
+ else
+ {
+ stack.push( Line( r.from, nVertexIndexMaxDistance ) );
+ stack.push( Line( nVertexIndexMaxDistance, r.to ) );
+ }
+ }
+
+ int cnt = 0;
+
+ QPolygonF stripped( nPoints );
+ for ( int i = 0; i < nPoints; i++ )
+ {
+ if ( usePoint[i] )
+ stripped[cnt++] = p[i];
+ }
+ stripped.resize( cnt );
+ return stripped;
+}
diff --git a/src/libpcp_qwt/src/qwt_curve_fitter.h b/src/libpcp_qwt/src/qwt_curve_fitter.h
new file mode 100644
index 0000000..c9ae603
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_curve_fitter.h
@@ -0,0 +1,128 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_CURVE_FITTER_H
+#define QWT_CURVE_FITTER_H
+
+#include "qwt_global.h"
+#include <qpolygon.h>
+#include <qrect.h>
+
+class QwtSpline;
+
+/*!
+ \brief Abstract base class for a curve fitter
+*/
+class QWT_EXPORT QwtCurveFitter
+{
+public:
+ virtual ~QwtCurveFitter();
+
+ /*!
+ Find a curve which has the best fit to a series of data points
+
+ \param polygon Series of data points
+ \return Curve points
+ */
+ virtual QPolygonF fitCurve( const QPolygonF &polygon ) const = 0;
+
+protected:
+ QwtCurveFitter();
+
+private:
+ QwtCurveFitter( const QwtCurveFitter & );
+ QwtCurveFitter &operator=( const QwtCurveFitter & );
+};
+
+/*!
+ \brief A curve fitter using cubic splines
+*/
+class QWT_EXPORT QwtSplineCurveFitter: public QwtCurveFitter
+{
+public:
+ /*!
+ Spline type
+ The default setting is Auto
+ \sa setFitMode(), FitMode()
+ */
+ enum FitMode
+ {
+ /*!
+ Use the default spline algorithm for polygons with
+ increasing x values ( p[i-1] < p[i] ), otherwise use
+ a parametric spline algorithm.
+ */
+ Auto,
+
+ //! Use a default spline algorithm
+ Spline,
+
+ //! Use a parametric spline algorithm
+ ParametricSpline
+ };
+
+ QwtSplineCurveFitter();
+ virtual ~QwtSplineCurveFitter();
+
+ void setFitMode( FitMode );
+ FitMode fitMode() const;
+
+ void setSpline( const QwtSpline& );
+ const QwtSpline &spline() const;
+ QwtSpline &spline();
+
+ void setSplineSize( int size );
+ int splineSize() const;
+
+ virtual QPolygonF fitCurve( const QPolygonF & ) const;
+
+private:
+ QPolygonF fitSpline( const QPolygonF & ) const;
+ QPolygonF fitParametric( const QPolygonF & ) const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ \brief A curve fitter implementing Douglas and Peucker algorithm
+
+ The purpose of the Douglas and Peucker algorithm is that given a 'curve'
+ composed of line segments to find a curve not too dissimilar but that
+ has fewer points. The algorithm defines 'too dissimilar' based on the
+ maximum distance (tolerance) between the original curve and the
+ smoothed curve.
+
+ The smoothed curve consists of a subset of the points that defined the
+ original curve.
+
+ In opposite to QwtSplineCurveFitter the Douglas and Peucker algorithm reduces
+ the number of points. By adjusting the tolerance parameter according to the
+ axis scales QwtSplineCurveFitter can be used to implement different
+ level of details to speed up painting of curves of many points.
+*/
+class QWT_EXPORT QwtWeedingCurveFitter: public QwtCurveFitter
+{
+public:
+ QwtWeedingCurveFitter( double tolerance = 1.0 );
+ virtual ~QwtWeedingCurveFitter();
+
+ void setTolerance( double );
+ double tolerance() const;
+
+ virtual QPolygonF fitCurve( const QPolygonF & ) const;
+
+private:
+ class Line;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_dial.cpp b/src/libpcp_qwt/src/qwt_dial.cpp
new file mode 100644
index 0000000..cf05561
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dial.cpp
@@ -0,0 +1,1156 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_dial.h"
+#include "qwt_dial_needle.h"
+#include "qwt_math.h"
+#include "qwt_scale_engine.h"
+#include "qwt_scale_map.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qbitmap.h>
+#include <qpalette.h>
+#include <qpixmap.h>
+#include <qevent.h>
+#include <qalgorithms.h>
+#include <qmath.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qapplication.h>
+
+#if QT_VERSION < 0x040601
+#define qAtan(x) ::atan(x)
+#endif
+
+class QwtDial::PrivateData
+{
+public:
+ PrivateData():
+ frameShadow( Sunken ),
+ lineWidth( 0 ),
+ mode( RotateNeedle ),
+ direction( Clockwise ),
+ origin( 90.0 ),
+ minScaleArc( 0.0 ),
+ maxScaleArc( 0.0 ),
+ scaleDraw( 0 ),
+ maxMajIntv( 36 ),
+ maxMinIntv( 10 ),
+ scaleStep( 0.0 ),
+ needle( 0 )
+ {
+ }
+
+ ~PrivateData()
+ {
+ delete scaleDraw;
+ delete needle;
+ }
+ Shadow frameShadow;
+ int lineWidth;
+
+ QwtDial::Mode mode;
+ QwtDial::Direction direction;
+
+ double origin;
+ double minScaleArc;
+ double maxScaleArc;
+
+ QwtDialScaleDraw *scaleDraw;
+ int maxMajIntv;
+ int maxMinIntv;
+ double scaleStep;
+
+ QwtDialNeedle *needle;
+
+ static double previousDir;
+};
+
+double QwtDial::PrivateData::previousDir = -1.0;
+
+/*!
+ Constructor
+
+ \param parent Parent dial widget
+*/
+QwtDialScaleDraw::QwtDialScaleDraw( QwtDial *parent ):
+ d_parent( parent ),
+ d_penWidth( 1.0 )
+{
+}
+
+/*!
+ Set the pen width used for painting the scale
+
+ \param penWidth Pen width
+ \sa penWidth(), QwtDial::drawScale()
+*/
+
+void QwtDialScaleDraw::setPenWidth( double penWidth )
+{
+ d_penWidth = qMax( penWidth, 0.0 );
+}
+
+/*!
+ \return Pen width used for painting the scale
+ \sa setPenWidth, QwtDial::drawScale()
+*/
+double QwtDialScaleDraw::penWidth() const
+{
+ return d_penWidth;
+}
+
+/*!
+ Call QwtDial::scaleLabel of the parent dial widget.
+
+ \param value Value to display
+
+ \sa QwtDial::scaleLabel()
+*/
+QwtText QwtDialScaleDraw::label( double value ) const
+{
+ if ( d_parent == NULL )
+ return QwtRoundScaleDraw::label( value );
+
+ return d_parent->scaleLabel( value );
+}
+
+/*!
+ \brief Constructor
+ \param parent Parent widget
+
+ Create a dial widget with no scale and no needle.
+ The default origin is 90.0 with no valid value. It accepts
+ mouse and keyboard inputs and has no step size. The default mode
+ is QwtDial::RotateNeedle.
+*/
+QwtDial::QwtDial( QWidget* parent ):
+ QwtAbstractSlider( Qt::Horizontal, parent )
+{
+ initDial();
+}
+
+void QwtDial::initDial()
+{
+ d_data = new PrivateData;
+
+ setFocusPolicy( Qt::TabFocus );
+
+ QPalette p = palette();
+ for ( int i = 0; i < QPalette::NColorGroups; i++ )
+ {
+ const QPalette::ColorGroup colorGroup = ( QPalette::ColorGroup )i;
+
+ // Base: background color of the circle inside the frame.
+ // WindowText: background color of the circle inside the scale
+
+ p.setColor( colorGroup, QPalette::WindowText,
+ p.color( colorGroup, QPalette::Base ) );
+ }
+ setPalette( p );
+
+ d_data->scaleDraw = new QwtDialScaleDraw( this );
+ d_data->scaleDraw->setRadius( 0 );
+
+ setScaleArc( 0.0, 360.0 ); // scale as a full circle
+ setRange( 0.0, 360.0, 1.0, 10 ); // degrees as default
+}
+
+//! Destructor
+QwtDial::~QwtDial()
+{
+ delete d_data;
+}
+
+/*!
+ Sets the frame shadow value from the frame style.
+ \param shadow Frame shadow
+ \sa setLineWidth(), QFrame::setFrameShadow()
+*/
+void QwtDial::setFrameShadow( Shadow shadow )
+{
+ if ( shadow != d_data->frameShadow )
+ {
+ d_data->frameShadow = shadow;
+ if ( lineWidth() > 0 )
+ update();
+ }
+}
+
+/*!
+ \return Frame shadow
+ /sa setFrameShadow(), lineWidth(), QFrame::frameShadow
+*/
+QwtDial::Shadow QwtDial::frameShadow() const
+{
+ return d_data->frameShadow;
+}
+
+/*!
+ Sets the line width
+
+ \param lineWidth Line width
+ \sa setFrameShadow()
+*/
+void QwtDial::setLineWidth( int lineWidth )
+{
+ if ( lineWidth < 0 )
+ lineWidth = 0;
+
+ if ( d_data->lineWidth != lineWidth )
+ {
+ d_data->lineWidth = lineWidth;
+ update();
+ }
+}
+
+/*!
+ \return Line width of the frame
+ \sa setLineWidth(), frameShadow(), lineWidth()
+*/
+int QwtDial::lineWidth() const
+{
+ return d_data->lineWidth;
+}
+
+/*!
+ \return bounding rect of the circle inside the frame
+ \sa setLineWidth(), scaleInnerRect(), boundingRect()
+*/
+QRectF QwtDial::innerRect() const
+{
+ const double lw = lineWidth();
+ return boundingRect().adjusted( lw, lw, -lw, -lw );
+}
+
+/*!
+ \return bounding rect of the dial including the frame
+ \sa setLineWidth(), scaleInnerRect(), innerRect()
+*/
+QRectF QwtDial::boundingRect() const
+{
+ const QRectF cr = contentsRect();
+
+ const double dim = qMin( cr.width(), cr.height() );
+
+ QRectF inner( 0, 0, dim, dim );
+ inner.moveCenter( cr.center() );
+
+ return inner;
+}
+
+/*!
+ \return rect inside the scale
+ \sa setLineWidth(), boundingRect(), innerRect()
+*/
+QRectF QwtDial::scaleInnerRect() const
+{
+ QRectF rect = innerRect();
+
+ if ( d_data->scaleDraw )
+ {
+ double scaleDist = qCeil( d_data->scaleDraw->extent( font() ) );
+ scaleDist++; // margin
+
+ rect.adjust( scaleDist, scaleDist, -scaleDist, -scaleDist );
+ }
+
+ return rect;
+}
+
+/*!
+ \brief Change the mode of the meter.
+ \param mode New mode
+
+ The value of the meter is indicated by the difference
+ between north of the scale and the direction of the needle.
+ In case of QwtDial::RotateNeedle north is pointing
+ to the origin() and the needle is rotating, in case of
+ QwtDial::RotateScale, the needle points to origin()
+ and the scale is rotating.
+
+ The default mode is QwtDial::RotateNeedle.
+
+ \sa mode(), setValue(), setOrigin()
+*/
+void QwtDial::setMode( Mode mode )
+{
+ if ( mode != d_data->mode )
+ {
+ d_data->mode = mode;
+ update();
+ }
+}
+
+/*!
+ \return mode of the dial.
+
+ The value of the dial is indicated by the difference
+ between the origin and the direction of the needle.
+ In case of QwtDial::RotateNeedle the scale arc is fixed
+ to the origin() and the needle is rotating, in case of
+ QwtDial::RotateScale, the needle points to origin()
+ and the scale is rotating.
+
+ The default mode is QwtDial::RotateNeedle.
+
+ \sa setMode(), origin(), setScaleArc(), value()
+*/
+QwtDial::Mode QwtDial::mode() const
+{
+ return d_data->mode;
+}
+
+/*!
+ Sets whether it is possible to step the value from the highest value to
+ the lowest value and vice versa to on.
+
+ \param wrapping en/disables wrapping
+
+ \sa wrapping(), QwtDoubleRange::periodic()
+ \note The meaning of wrapping is like the wrapping property of QSpinBox,
+ but not like it is used in QDial.
+*/
+void QwtDial::setWrapping( bool wrapping )
+{
+ setPeriodic( wrapping );
+}
+
+/*!
+ wrapping() holds whether it is possible to step the value from the
+ highest value to the lowest value and vice versa.
+
+ \sa setWrapping(), QwtDoubleRange::setPeriodic()
+ \note The meaning of wrapping is like the wrapping property of QSpinBox,
+ but not like it is used in QDial.
+*/
+bool QwtDial::wrapping() const
+{
+ return periodic();
+}
+
+/*!
+ Set the direction of the dial (clockwise/counterclockwise)
+
+ \param direction Direction
+ \sa direction()
+*/
+void QwtDial::setDirection( Direction direction )
+{
+ if ( direction != d_data->direction )
+ {
+ d_data->direction = direction;
+ update();
+ }
+}
+
+/*!
+ \return Direction of the dial
+
+ The default direction of a dial is QwtDial::Clockwise
+
+ \sa setDirection()
+*/
+QwtDial::Direction QwtDial::direction() const
+{
+ return d_data->direction;
+}
+
+/*!
+ Paint the dial
+ \param event Paint event
+*/
+void QwtDial::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ painter.setRenderHint( QPainter::Antialiasing, true );
+
+ painter.save();
+ drawContents( &painter );
+ painter.restore();
+
+ painter.save();
+ drawFrame( &painter );
+ painter.restore();
+
+ if ( hasFocus() )
+ drawFocusIndicator( &painter );
+}
+
+/*!
+ Draw a dotted round circle, if !isReadOnly()
+
+ \param painter Painter
+*/
+void QwtDial::drawFocusIndicator( QPainter *painter ) const
+{
+ if ( !isReadOnly() )
+ {
+ QRectF focusRect = innerRect();
+
+ const int margin = 2;
+ focusRect.adjust( margin, margin, -margin, -margin );
+
+ QColor color = palette().color( QPalette::Base );
+ if ( color.isValid() )
+ {
+ const QColor gray( Qt::gray );
+
+ int h, s, v;
+ color.getHsv( &h, &s, &v );
+ color = ( v > 128 ) ? gray.dark( 120 ) : gray.light( 120 );
+ }
+ else
+ color = Qt::darkGray;
+
+ painter->save();
+ painter->setBrush( Qt::NoBrush );
+ painter->setPen( QPen( color, 0, Qt::DotLine ) );
+ painter->drawEllipse( focusRect );
+ painter->restore();
+ }
+}
+
+/*!
+ Draw the frame around the dial
+
+ \param painter Painter
+ \sa lineWidth(), frameShadow()
+*/
+void QwtDial::drawFrame( QPainter *painter )
+{
+ if ( lineWidth() <= 0 )
+ return;
+
+ const double lw2 = 0.5 * lineWidth();
+
+ QRectF r = boundingRect();
+ r.adjust( lw2, lw2, -lw2, -lw2 );
+
+ QPen pen;
+
+ switch ( d_data->frameShadow )
+ {
+ case QwtDial::Raised:
+ case QwtDial::Sunken:
+ {
+ QColor c1 = palette().color( QPalette::Light );
+ QColor c2 = palette().color( QPalette::Dark );
+
+ if ( d_data->frameShadow == QwtDial::Sunken )
+ qSwap( c1, c2 );
+
+ QLinearGradient gradient( r.topLeft(), r.bottomRight() );
+ gradient.setColorAt( 0.0, c1 );
+#if 0
+ gradient.setColorAt( 0.3, c1 );
+ gradient.setColorAt( 0.7, c2 );
+#endif
+ gradient.setColorAt( 1.0, c2 );
+
+ pen = QPen( gradient, lineWidth() );
+ break;
+ }
+ default: // Plain
+ {
+ pen = QPen( palette().brush( QPalette::Dark ), lineWidth() );
+ }
+ }
+
+ painter->save();
+
+ painter->setPen( pen );
+ painter->setBrush( Qt::NoBrush );
+ painter->drawEllipse( r );
+
+ painter->restore();
+}
+
+/*!
+ \brief Draw the contents inside the frame
+
+ QPalette::Window is the background color outside of the frame.
+ QPalette::Base is the background color inside the frame.
+ QPalette::WindowText is the background color inside the scale.
+
+ \param painter Painter
+ \sa boundingRect(), innerRect(),
+ scaleInnerRect(), QWidget::setPalette()
+*/
+void QwtDial::drawContents( QPainter *painter ) const
+{
+ if ( testAttribute( Qt::WA_NoSystemBackground ) ||
+ palette().brush( QPalette::Base ) !=
+ palette().brush( QPalette::Window ) )
+ {
+ const QRectF br = boundingRect();
+
+ painter->save();
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette().brush( QPalette::Base ) );
+ painter->drawEllipse( br );
+ painter->restore();
+ }
+
+
+ const QRectF insideScaleRect = scaleInnerRect();
+ if ( palette().brush( QPalette::WindowText ) !=
+ palette().brush( QPalette::Base ) )
+ {
+ painter->save();
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette().brush( QPalette::WindowText ) );
+ painter->drawEllipse( insideScaleRect );
+ painter->restore();
+ }
+
+ const QPointF center = insideScaleRect.center();
+ const double radius = 0.5 * insideScaleRect.width();
+
+ double direction = d_data->origin;
+
+ if ( isValid() )
+ {
+ direction = d_data->minScaleArc;
+ if ( maxValue() > minValue() &&
+ d_data->maxScaleArc > d_data->minScaleArc )
+ {
+ const double ratio =
+ ( value() - minValue() ) / ( maxValue() - minValue() );
+ direction += ratio * ( d_data->maxScaleArc - d_data->minScaleArc );
+ }
+
+ if ( d_data->direction == QwtDial::CounterClockwise )
+ direction = d_data->maxScaleArc - ( direction - d_data->minScaleArc );
+
+ direction += d_data->origin;
+ if ( direction >= 360.0 )
+ direction -= 360.0;
+ else if ( direction < 0.0 )
+ direction += 360.0;
+ }
+
+ double origin = d_data->origin;
+ if ( mode() == RotateScale )
+ {
+ origin -= direction - d_data->origin;
+ direction = d_data->origin;
+ }
+
+ painter->save();
+ drawScale( painter, center, radius, origin,
+ d_data->minScaleArc, d_data->maxScaleArc );
+ painter->restore();
+
+ painter->save();
+ drawScaleContents( painter, center, radius );
+ painter->restore();
+
+ if ( isValid() )
+ {
+ QPalette::ColorGroup cg;
+ if ( isEnabled() )
+ cg = hasFocus() ? QPalette::Active : QPalette::Inactive;
+ else
+ cg = QPalette::Disabled;
+
+ painter->save();
+ drawNeedle( painter, center, radius, direction, cg );
+ painter->restore();
+ }
+}
+
+/*!
+ Draw the needle
+
+ \param painter Painter
+ \param center Center of the dial
+ \param radius Length for the needle
+ \param direction Direction of the needle in degrees, counter clockwise
+ \param cg ColorGroup
+*/
+void QwtDial::drawNeedle( QPainter *painter, const QPointF &center,
+ double radius, double direction, QPalette::ColorGroup cg ) const
+{
+ if ( d_data->needle )
+ {
+ direction = 360.0 - direction; // counter clockwise
+ d_data->needle->draw( painter, center, radius, direction, cg );
+ }
+}
+
+/*!
+ Draw the scale
+
+ \param painter Painter
+ \param center Center of the dial
+ \param radius Radius of the scale
+ \param origin Origin of the scale
+ \param minArc Minimum of the arc
+ \param maxArc Minimum of the arc
+
+ \sa QwtRoundScaleDraw::setAngleRange()
+*/
+void QwtDial::drawScale( QPainter *painter, const QPointF &center,
+ double radius, double origin, double minArc, double maxArc ) const
+{
+ if ( d_data->scaleDraw == NULL )
+ return;
+
+ origin -= 270.0; // hardcoded origin of QwtScaleDraw
+
+ double angle = maxArc - minArc;
+ if ( angle > 360.0 )
+ angle = ::fmod( angle, 360.0 );
+
+ minArc += origin;
+ if ( minArc < -360.0 )
+ minArc = ::fmod( minArc, 360.0 );
+
+ maxArc = minArc + angle;
+ if ( maxArc > 360.0 )
+ {
+ // QwtRoundScaleDraw::setAngleRange accepts only values
+ // in the range [-360.0..360.0]
+ minArc -= 360.0;
+ maxArc -= 360.0;
+ }
+
+ if ( d_data->direction == QwtDial::CounterClockwise )
+ qSwap( minArc, maxArc );
+
+ painter->setFont( font() );
+
+ d_data->scaleDraw->setAngleRange( minArc, maxArc );
+ d_data->scaleDraw->setRadius( qFloor( radius ) );
+ d_data->scaleDraw->moveCenter( center );
+
+ QPalette pal = palette();
+
+ const QColor textColor = pal.color( QPalette::Text );
+ pal.setColor( QPalette::WindowText, textColor ); //ticks, backbone
+
+ painter->setPen( QPen( textColor, d_data->scaleDraw->penWidth() ) );
+
+ painter->setBrush( Qt::red );
+ d_data->scaleDraw->draw( painter, pal );
+}
+
+/*!
+ Draw the contents inside the scale
+
+ Paints nothing.
+
+ \param painter Painter
+ \param center Center of the contents circle
+ \param radius Radius of the contents circle
+*/
+
+void QwtDial::drawScaleContents( QPainter *painter,
+ const QPointF &center, double radius ) const
+{
+ Q_UNUSED(painter);
+ Q_UNUSED(center);
+ Q_UNUSED(radius);
+}
+
+/*!
+ Set a needle for the dial
+
+ Qwt is missing a set of good looking needles.
+ Contributions are very welcome.
+
+ \param needle Needle
+ \warning The needle will be deleted, when a different needle is
+ set or in ~QwtDial()
+*/
+void QwtDial::setNeedle( QwtDialNeedle *needle )
+{
+ if ( needle != d_data->needle )
+ {
+ if ( d_data->needle )
+ delete d_data->needle;
+
+ d_data->needle = needle;
+ update();
+ }
+}
+
+/*!
+ \return needle
+ \sa setNeedle()
+*/
+const QwtDialNeedle *QwtDial::needle() const
+{
+ return d_data->needle;
+}
+
+/*!
+ \return needle
+ \sa setNeedle()
+*/
+QwtDialNeedle *QwtDial::needle()
+{
+ return d_data->needle;
+}
+
+//! QwtDoubleRange update hook
+void QwtDial::rangeChange()
+{
+ updateScale();
+}
+
+/*!
+ Update the scale with the current attributes
+ \sa setScale()
+*/
+void QwtDial::updateScale()
+{
+ if ( d_data->scaleDraw )
+ {
+ QwtLinearScaleEngine scaleEngine;
+
+ const QwtScaleDiv scaleDiv = scaleEngine.divideScale(
+ minValue(), maxValue(),
+ d_data->maxMajIntv, d_data->maxMinIntv, d_data->scaleStep );
+
+ d_data->scaleDraw->setTransformation( scaleEngine.transformation() );
+ d_data->scaleDraw->setScaleDiv( scaleDiv );
+ }
+}
+
+//! Return the scale draw
+QwtDialScaleDraw *QwtDial::scaleDraw()
+{
+ return d_data->scaleDraw;
+}
+
+//! Return the scale draw
+const QwtDialScaleDraw *QwtDial::scaleDraw() const
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ Set an individual scale draw
+
+ \param scaleDraw Scale draw
+ \warning The previous scale draw is deleted
+*/
+void QwtDial::setScaleDraw( QwtDialScaleDraw *scaleDraw )
+{
+ if ( scaleDraw != d_data->scaleDraw )
+ {
+ if ( d_data->scaleDraw )
+ delete d_data->scaleDraw;
+
+ d_data->scaleDraw = scaleDraw;
+ updateScale();
+ update();
+ }
+}
+
+/*!
+ Change the intervals of the scale
+
+ \param maxMajIntv Maximum for the number of major steps
+ \param maxMinIntv Maximum number of minor steps
+ \param step Step size
+
+ \sa QwtScaleEngine::divideScale()
+*/
+void QwtDial::setScale( int maxMajIntv, int maxMinIntv, double step )
+{
+ d_data->maxMajIntv = maxMajIntv;
+ d_data->maxMinIntv = maxMinIntv;
+ d_data->scaleStep = step;
+
+ updateScale();
+}
+
+/*!
+ A wrapper method for accessing the scale draw.
+
+ \param components Scale components
+ \sa QwtAbstractScaleDraw::enableComponent()
+*/
+void QwtDial::setScaleComponents(
+ QwtAbstractScaleDraw::ScaleComponents components )
+{
+ if ( components == 0 )
+ setScaleDraw( NULL );
+
+ QwtDialScaleDraw *sd = d_data->scaleDraw;
+ if ( sd == NULL )
+ return;
+
+ sd->enableComponent( QwtAbstractScaleDraw::Backbone,
+ components & QwtAbstractScaleDraw::Backbone );
+
+ sd->enableComponent( QwtAbstractScaleDraw::Ticks,
+ components & QwtAbstractScaleDraw::Ticks );
+
+ sd->enableComponent( QwtAbstractScaleDraw::Labels,
+ components & QwtAbstractScaleDraw::Labels );
+}
+
+/*!
+ Assign length and width of the ticks
+
+ \param minLen Length of the minor ticks
+ \param medLen Length of the medium ticks
+ \param majLen Length of the major ticks
+ \param penWidth Width of the pen for all ticks
+
+ \sa QwtAbstractScaleDraw::setTickLength(), QwtDialScaleDraw::setPenWidth()
+*/
+void QwtDial::setScaleTicks( int minLen, int medLen,
+ int majLen, int penWidth )
+{
+ QwtDialScaleDraw *sd = d_data->scaleDraw;
+ if ( sd )
+ {
+ sd->setTickLength( QwtScaleDiv::MinorTick, minLen );
+ sd->setTickLength( QwtScaleDiv::MediumTick, medLen );
+ sd->setTickLength( QwtScaleDiv::MajorTick, majLen );
+ sd->setPenWidth( penWidth );
+ }
+}
+
+/*!
+ Find the label for a value
+
+ \param value Value
+ \return label
+*/
+QwtText QwtDial::scaleLabel( double value ) const
+{
+ if ( value == -0.0 )
+ value = 0.0;
+
+ return QString::number( value );
+}
+
+//! \return Lower limit of the scale arc
+double QwtDial::minScaleArc() const
+{
+ return d_data->minScaleArc;
+}
+
+//! \return Upper limit of the scale arc
+double QwtDial::maxScaleArc() const
+{
+ return d_data->maxScaleArc;
+}
+
+/*!
+ \brief Change the origin
+
+ The origin is the angle where scale and needle is relative to.
+
+ \param origin New origin
+ \sa origin()
+*/
+void QwtDial::setOrigin( double origin )
+{
+ d_data->origin = origin;
+ update();
+}
+
+/*!
+ The origin is the angle where scale and needle is relative to.
+
+ \return Origin of the dial
+ \sa setOrigin()
+*/
+double QwtDial::origin() const
+{
+ return d_data->origin;
+}
+
+/*!
+ Change the arc of the scale
+
+ \param minArc Lower limit
+ \param maxArc Upper limit
+*/
+void QwtDial::setScaleArc( double minArc, double maxArc )
+{
+ if ( minArc != 360.0 && minArc != -360.0 )
+ minArc = ::fmod( minArc, 360.0 );
+ if ( maxArc != 360.0 && maxArc != -360.0 )
+ maxArc = ::fmod( maxArc, 360.0 );
+
+ d_data->minScaleArc = qMin( minArc, maxArc );
+ d_data->maxScaleArc = qMax( minArc, maxArc );
+ if ( d_data->maxScaleArc - d_data->minScaleArc > 360.0 )
+ d_data->maxScaleArc = d_data->minScaleArc + 360.0;
+
+ update();
+}
+
+//! QwtDoubleRange update hook
+void QwtDial::valueChange()
+{
+ update();
+ QwtAbstractSlider::valueChange();
+}
+
+/*!
+ \return Size hint
+*/
+QSize QwtDial::sizeHint() const
+{
+ int sh = 0;
+ if ( d_data->scaleDraw )
+ sh = qCeil( d_data->scaleDraw->extent( font() ) );
+
+ const int d = 6 * sh + 2 * lineWidth();
+
+ QSize hint( d, d );
+ if ( !isReadOnly() )
+ hint = hint.expandedTo( QApplication::globalStrut() );
+
+ return hint;
+}
+
+/*!
+ \brief Return a minimum size hint
+ \warning The return value of QwtDial::minimumSizeHint() depends on the
+ font and the scale.
+*/
+QSize QwtDial::minimumSizeHint() const
+{
+ int sh = 0;
+ if ( d_data->scaleDraw )
+ sh = qCeil( d_data->scaleDraw->extent( font() ) );
+
+ const int d = 3 * sh + 2 * lineWidth();
+
+ return QSize( d, d );
+}
+
+static double line2Degrees( const QPointF &p1, const QPointF &p2 )
+{
+ const QPointF p = p2 - p1;
+
+ double angle;
+ if ( p.x() == 0.0 )
+ {
+ angle = ( p.y() <= 0.0 ) ? M_PI_2 : 3 * M_PI_2;
+ }
+ else
+ {
+ angle = qAtan( double( -p.y() ) / double( p.x() ) );
+ if ( p.x() < 0.0 )
+ angle += M_PI;
+ if ( angle < 0.0 )
+ angle += 2 * M_PI;
+ }
+ return 360.0 - angle * 180.0 / M_PI;
+}
+
+/*!
+ Find the value for a given position
+
+ \param pos Position
+ \return Value
+*/
+double QwtDial::getValue( const QPoint &pos )
+{
+ if ( d_data->maxScaleArc == d_data->minScaleArc || maxValue() == minValue() )
+ return minValue();
+
+ double dir = line2Degrees( innerRect().center(), pos ) - d_data->origin;
+ if ( dir < 0.0 )
+ dir += 360.0;
+
+ if ( mode() == RotateScale )
+ dir = 360.0 - dir;
+
+ // The position might be in the area that is outside the scale arc.
+ // We need the range of the scale if it was a complete circle.
+
+ const double completeCircle = 360.0 / ( d_data->maxScaleArc - d_data->minScaleArc )
+ * ( maxValue() - minValue() );
+
+ double posValue = minValue() + completeCircle * dir / 360.0;
+
+ if ( scrollMode() == ScrMouse )
+ {
+ if ( d_data->previousDir >= 0.0 ) // valid direction
+ {
+ // We have to find out whether the mouse is moving
+ // clock or counter clockwise
+
+ bool clockWise = false;
+
+ const double angle = dir - d_data->previousDir;
+ if ( ( angle >= 0.0 && angle <= 180.0 ) || angle < -180.0 )
+ clockWise = true;
+
+ if ( clockWise )
+ {
+ if ( dir < d_data->previousDir && mouseOffset() > 0.0 )
+ {
+ // We passed 360 -> 0
+ setMouseOffset( mouseOffset() - completeCircle );
+ }
+
+ if ( wrapping() )
+ {
+ if ( posValue - mouseOffset() > maxValue() )
+ {
+ // We passed maxValue and the value will be set
+ // to minValue. We have to adjust the mouseOffset.
+
+ setMouseOffset( posValue - minValue() );
+ }
+ }
+ else
+ {
+ if ( posValue - mouseOffset() > maxValue() ||
+ value() == maxValue() )
+ {
+ // We fix the value at maxValue by adjusting
+ // the mouse offset.
+
+ setMouseOffset( posValue - maxValue() );
+ }
+ }
+ }
+ else
+ {
+ if ( dir > d_data->previousDir && mouseOffset() < 0.0 )
+ {
+ // We passed 0 -> 360
+ setMouseOffset( mouseOffset() + completeCircle );
+ }
+
+ if ( wrapping() )
+ {
+ if ( posValue - mouseOffset() < minValue() )
+ {
+ // We passed minValue and the value will be set
+ // to maxValue. We have to adjust the mouseOffset.
+
+ setMouseOffset( posValue - maxValue() );
+ }
+ }
+ else
+ {
+ if ( posValue - mouseOffset() < minValue() ||
+ value() == minValue() )
+ {
+ // We fix the value at minValue by adjusting
+ // the mouse offset.
+
+ setMouseOffset( posValue - minValue() );
+ }
+ }
+ }
+ }
+ d_data->previousDir = dir;
+ }
+
+ return posValue;
+}
+
+/*!
+ See QwtAbstractSlider::getScrollMode()
+
+ \param pos point where the mouse was pressed
+ \retval scrollMode The scrolling mode
+ \retval direction direction: 1, 0, or -1.
+
+ \sa QwtAbstractSlider::getScrollMode()
+*/
+void QwtDial::getScrollMode( const QPoint &pos,
+ QwtAbstractSlider::ScrollMode &scrollMode, int &direction ) const
+{
+ direction = 0;
+ scrollMode = QwtAbstractSlider::ScrNone;
+
+ const QRegion region( innerRect().toRect(), QRegion::Ellipse );
+ if ( region.contains( pos ) && pos != innerRect().center() )
+ {
+ scrollMode = QwtAbstractSlider::ScrMouse;
+ d_data->previousDir = -1.0;
+ }
+}
+
+/*!
+ Handles key events
+
+ - Key_Down, KeyLeft\n
+ Decrement by 1
+ - Key_Prior\n
+ Decrement by pageSize()
+ - Key_Home\n
+ Set the value to minValue()
+
+ - Key_Up, KeyRight\n
+ Increment by 1
+ - Key_Next\n
+ Increment by pageSize()
+ - Key_End\n
+ Set the value to maxValue()
+
+ \param event Key event
+ \sa isReadOnly()
+*/
+void QwtDial::keyPressEvent( QKeyEvent *event )
+{
+ if ( isReadOnly() )
+ {
+ event->ignore();
+ return;
+ }
+
+ if ( !isValid() )
+ return;
+
+ const double previousValue = value();
+
+ switch ( event->key() )
+ {
+ case Qt::Key_Down:
+ case Qt::Key_Left:
+ QwtDoubleRange::incValue( -1 );
+ break;
+ case Qt::Key_PageUp:
+ QwtDoubleRange::incValue( -pageSize() );
+ break;
+ case Qt::Key_Home:
+ setValue( minValue() );
+ break;
+
+ case Qt::Key_Up:
+ case Qt::Key_Right:
+ QwtDoubleRange::incValue( 1 );
+ break;
+ case Qt::Key_PageDown:
+ QwtDoubleRange::incValue( pageSize() );
+ break;
+ case Qt::Key_End:
+ setValue( maxValue() );
+ break;
+ default:;
+ event->ignore();
+ }
+
+ if ( value() != previousValue )
+ Q_EMIT sliderMoved( value() );
+}
diff --git a/src/libpcp_qwt/src/qwt_dial.h b/src/libpcp_qwt/src/qwt_dial.h
new file mode 100644
index 0000000..128ceda
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dial.h
@@ -0,0 +1,215 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_DIAL_H
+#define QWT_DIAL_H 1
+
+#include "qwt_global.h"
+#include "qwt_abstract_slider.h"
+#include "qwt_round_scale_draw.h"
+#include <qframe.h>
+#include <qpalette.h>
+
+class QwtDialNeedle;
+class QwtDial;
+
+/*!
+ \brief A special scale draw made for QwtDial
+
+ \sa QwtDial, QwtCompass
+*/
+class QWT_EXPORT QwtDialScaleDraw: public QwtRoundScaleDraw
+{
+public:
+ explicit QwtDialScaleDraw( QwtDial * );
+
+ virtual QwtText label( double value ) const;
+
+ void setPenWidth( double );
+ double penWidth() const;
+
+private:
+ QwtDial *d_parent;
+ double d_penWidth;
+};
+
+/*!
+ \brief QwtDial class provides a rounded range control.
+
+ QwtDial is intended as base class for dial widgets like
+ speedometers, compass widgets, clocks ...
+
+ \image html dials2.png
+
+ A dial contains a scale and a needle indicating the current value
+ of the dial. Depending on Mode one of them is fixed and the
+ other is rotating. If not isReadOnly() the
+ dial can be rotated by dragging the mouse or using keyboard inputs
+ (see keyPressEvent()). A dial might be wrapping, what means
+ a rotation below/above one limit continues on the other limit (f.e compass).
+ The scale might cover any arc of the dial, its values are related to
+ the origin() of the dial.
+
+ Qwt is missing a set of good looking needles (QwtDialNeedle).
+ Contributions are very welcome.
+
+ \sa QwtCompass, QwtAnalogClock, QwtDialNeedle
+ \note The examples/dials example shows different types of dials.
+*/
+
+class QWT_EXPORT QwtDial: public QwtAbstractSlider
+{
+ Q_OBJECT
+
+ Q_ENUMS( Shadow )
+ Q_ENUMS( Mode )
+ Q_ENUMS( Direction )
+
+ Q_PROPERTY( int lineWidth READ lineWidth WRITE setLineWidth )
+ Q_PROPERTY( Shadow frameShadow READ frameShadow WRITE setFrameShadow )
+ Q_PROPERTY( Mode mode READ mode WRITE setMode )
+ Q_PROPERTY( double origin READ origin WRITE setOrigin )
+ Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping )
+ Q_PROPERTY( Direction direction READ direction WRITE setDirection )
+
+ friend class QwtDialScaleDraw;
+public:
+
+ /*!
+ \brief Frame shadow
+
+ Unfortunately it is not possible to use QFrame::Shadow
+ as a property of a widget that is not derived from QFrame.
+ The following enum is made for the designer only. It is safe
+ to use QFrame::Shadow instead.
+ */
+ enum Shadow
+ {
+ //! QFrame::Plain
+ Plain = QFrame::Plain,
+
+ //! QFrame::Raised
+ Raised = QFrame::Raised,
+
+ //! QFrame::Sunken
+ Sunken = QFrame::Sunken
+ };
+
+ //! Mode controlling wether the needle or the scale is rotating
+ enum Mode
+ {
+ //! The needle is rotating
+ RotateNeedle,
+
+ //! The needle is fixed, the scales are rotating
+ RotateScale
+ };
+
+ //! Direction of the dial
+ enum Direction
+ {
+ //! Clockwise
+ Clockwise,
+
+ //! Counter clockwise
+ CounterClockwise
+ };
+
+ explicit QwtDial( QWidget *parent = NULL );
+ virtual ~QwtDial();
+
+ void setFrameShadow( Shadow );
+ Shadow frameShadow() const;
+
+ void setLineWidth( int );
+ int lineWidth() const;
+
+ void setMode( Mode );
+ Mode mode() const;
+
+ virtual void setWrapping( bool );
+ bool wrapping() const;
+
+ virtual void setScale( int maxMajIntv, int maxMinIntv, double step = 0.0 );
+
+ void setScaleArc( double min, double max );
+ void setScaleComponents( QwtAbstractScaleDraw::ScaleComponents );
+ void setScaleTicks( int minLen, int medLen, int majLen, int penWidth = 1 );
+
+ double minScaleArc() const;
+ double maxScaleArc() const;
+
+ virtual void setOrigin( double );
+ double origin() const;
+
+ void setDirection( Direction );
+ Direction direction() const;
+
+ virtual void setNeedle( QwtDialNeedle * );
+ const QwtDialNeedle *needle() const;
+ QwtDialNeedle *needle();
+
+ QRectF boundingRect() const;
+ QRectF innerRect() const;
+ virtual QRectF scaleInnerRect() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ virtual void setScaleDraw( QwtDialScaleDraw * );
+
+ QwtDialScaleDraw *scaleDraw();
+ const QwtDialScaleDraw *scaleDraw() const;
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void keyPressEvent( QKeyEvent * );
+
+ virtual void drawFrame( QPainter *p );
+ virtual void drawContents( QPainter * ) const;
+ virtual void drawFocusIndicator( QPainter * ) const;
+
+ virtual void drawScale(
+ QPainter *, const QPointF &center,
+ double radius, double origin,
+ double arcMin, double arcMax ) const;
+
+ /*!
+ Draw the contents inside the scale
+
+ Paints nothing.
+
+ \param painter Painter
+ \param center Center of the contents circle
+ \param radius Radius of the contents circle
+ */
+ virtual void drawScaleContents( QPainter *painter,
+ const QPointF &center, double radius ) const;
+
+ virtual void drawNeedle( QPainter *, const QPointF &,
+ double radius, double direction, QPalette::ColorGroup ) const;
+
+ virtual QwtText scaleLabel( double ) const;
+ void updateScale();
+
+ virtual void rangeChange();
+ virtual void valueChange();
+
+ virtual double getValue( const QPoint & );
+ virtual void getScrollMode( const QPoint &,
+ QwtAbstractSlider::ScrollMode &, int &direction ) const;
+
+private:
+ void initDial();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_dial_needle.cpp b/src/libpcp_qwt/src/qwt_dial_needle.cpp
new file mode 100644
index 0000000..3888c11
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dial_needle.cpp
@@ -0,0 +1,441 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_dial_needle.h"
+#include "qwt_global.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qapplication.h>
+#include <qpainter.h>
+
+#if QT_VERSION < 0x040601
+#define qFastSin(x) qSin(x)
+#define qFastCos(x) qCos(x)
+#endif
+
+static void qwtDrawStyle1Needle( QPainter *painter,
+ const QPalette &palette, QPalette::ColorGroup colorGroup,
+ double length )
+{
+ const double r[] = { 0.4, 0.3, 1, 0.8, 1, 0.3, 0.4 };
+ const double a[] = { -45, -20, -15, 0, 15, 20, 45 };
+
+ QPainterPath path;
+ for ( int i = 0; i < 7; i++ )
+ {
+ const double angle = a[i] / 180.0 * M_PI;
+ const double radius = r[i] * length;
+
+ const double x = radius * qFastCos( angle );
+ const double y = radius * qFastSin( angle );
+
+ path.lineTo( x, -y );
+ }
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette.brush( colorGroup, QPalette::Light ) );
+ painter->drawPath( path );
+}
+
+static void qwtDrawStyle2Needle( QPainter *painter,
+ const QPalette &palette, QPalette::ColorGroup colorGroup, double length )
+{
+ const double ratioX = 0.7;
+ const double ratioY = 0.3;
+
+ QPainterPath path1;
+ path1.lineTo( ratioX * length, 0.0 );
+ path1.lineTo( length, ratioY * length );
+
+ QPainterPath path2;
+ path2.lineTo( ratioX * length, 0.0 );
+ path2.lineTo( length, -ratioY * length );
+
+ painter->setPen( Qt::NoPen );
+
+ painter->setBrush( palette.brush( colorGroup, QPalette::Light ) );
+ painter->drawPath( path1 );
+
+ painter->setBrush( palette.brush( colorGroup, QPalette::Dark ) );
+ painter->drawPath( path2 );
+}
+
+static void qwtDrawShadedPointer( QPainter *painter,
+ const QColor &lightColor, const QColor &darkColor,
+ double length, double width )
+{
+ const double peak = qMax( length / 10.0, 5.0 );
+
+ const double knobWidth = width + 8;
+ QRectF knobRect( 0, 0, knobWidth, knobWidth );
+ knobRect.moveCenter( QPointF(0, 0) );
+
+ QPainterPath path1;
+ path1.lineTo( 0.0, 0.5 * width );
+ path1.lineTo( length - peak, 0.5 * width );
+ path1.lineTo( length, 0.0 );
+ path1.lineTo( 0.0, 0.0 );
+
+ QPainterPath arcPath1;
+ arcPath1.arcTo( knobRect, 0.0, -90.0 );
+
+ path1 = path1.united( arcPath1 );
+
+ QPainterPath path2;
+ path2.lineTo( 0.0, -0.5 * width );
+ path2.lineTo( length - peak, -0.5 * width );
+ path2.lineTo( length, 0.0 );
+ path2.lineTo( 0.0, 0.0 );
+
+ QPainterPath arcPath2;
+ arcPath2.arcTo( knobRect, 0.0, 90.0 );
+
+ path2 = path2.united( arcPath2 );
+
+ painter->setPen( Qt::NoPen );
+
+ painter->setBrush( lightColor );
+ painter->drawPath( path1 );
+
+ painter->setBrush( darkColor );
+ painter->drawPath( path2 );
+}
+
+static void qwtDrawArrowNeedle( QPainter *painter,
+ const QPalette &palette, QPalette::ColorGroup colorGroup,
+ double length, double width )
+{
+ if ( width <= 0 )
+ width = qMax( length * 0.06, 9.0 );
+
+ const double peak = qMax( 2.0, 0.4 * width );
+
+ QPainterPath path;
+ path.moveTo( 0.0, 0.5 * width );
+ path.lineTo( length - peak, 0.3 * width );
+ path.lineTo( length, 0.0 );
+ path.lineTo( length - peak, -0.3 * width );
+ path.lineTo( 0.0, -0.5 * width );
+
+ QRectF br = path.boundingRect();
+
+ QPalette pal( palette.color( QPalette::Mid ) );
+ QColor c1 = pal.color( QPalette::Light );
+ QColor c2 = pal.color( QPalette::Dark );
+
+ QLinearGradient gradient( br.topLeft(), br.bottomLeft() );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 0.5, c1 );
+ gradient.setColorAt( 0.5001, c2 );
+ gradient.setColorAt( 1.0, c2 );
+
+ QPen pen( gradient, 1 );
+ pen.setJoinStyle( Qt::MiterJoin );
+
+ painter->setPen( pen );
+ painter->setBrush( palette.brush( colorGroup, QPalette::Mid ) );
+
+ painter->drawPath( path );
+}
+
+static void qwtDrawTriangleNeedle( QPainter *painter,
+ const QPalette &palette, QPalette::ColorGroup colorGroup,
+ double length )
+{
+ const double width = qRound( length / 3.0 );
+
+ QPainterPath path[4];
+
+ path[0].lineTo( length, 0.0 );
+ path[0].lineTo( 0.0, width / 2 );
+
+ path[1].lineTo( length, 0.0 );
+ path[1].lineTo( 0.0, -width / 2 );
+
+ path[2].lineTo( -length, 0.0 );
+ path[2].lineTo( 0.0, width / 2 );
+
+ path[3].lineTo( -length, 0.0 );
+ path[3].lineTo( 0.0, -width / 2 );
+
+
+ const int colorOffset = 10;
+ const QColor darkColor = palette.color( colorGroup, QPalette::Dark );
+ const QColor lightColor = palette.color( colorGroup, QPalette::Light );
+
+ QColor color[4];
+ color[0] = darkColor.light( 100 + colorOffset );
+ color[1] = darkColor.dark( 100 + colorOffset );
+ color[2] = lightColor.light( 100 + colorOffset );
+ color[3] = lightColor.dark( 100 + colorOffset );
+
+ painter->setPen( Qt::NoPen );
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ painter->setBrush( color[i] );
+ painter->drawPath( path[i] );
+ }
+}
+
+//! Constructor
+QwtDialNeedle::QwtDialNeedle():
+ d_palette( QApplication::palette() )
+{
+}
+
+//! Destructor
+QwtDialNeedle::~QwtDialNeedle()
+{
+}
+
+/*!
+ Sets the palette for the needle.
+
+ \param palette New Palette
+*/
+void QwtDialNeedle::setPalette( const QPalette &palette )
+{
+ d_palette = palette;
+}
+
+/*!
+ \return the palette of the needle.
+*/
+const QPalette &QwtDialNeedle::palette() const
+{
+ return d_palette;
+}
+
+/*!
+ Draw the needle
+
+ \param painter Painter
+ \param center Center of the dial, start position for the needle
+ \param length Length of the needle
+ \param direction Direction of the needle, in degrees counter clockwise
+ \param colorGroup Color group, used for painting
+*/
+void QwtDialNeedle::draw( QPainter *painter,
+ const QPointF &center, double length, double direction,
+ QPalette::ColorGroup colorGroup ) const
+{
+ painter->save();
+
+ painter->translate( center );
+ painter->rotate( -direction );
+
+ drawNeedle( painter, length, colorGroup );
+
+ painter->restore();
+}
+
+//! Draw the knob
+void QwtDialNeedle::drawKnob( QPainter *painter,
+ double width, const QBrush &brush, bool sunken ) const
+{
+ QPalette palette( brush.color() );
+
+ QColor c1 = palette.color( QPalette::Light );
+ QColor c2 = palette.color( QPalette::Dark );
+
+ if ( sunken )
+ qSwap( c1, c2 );
+
+ QRectF rect( 0.0, 0.0, width, width );
+ rect.moveCenter( painter->combinedTransform().map( QPointF() ) );
+
+ QLinearGradient gradient( rect.topLeft(), rect.bottomRight() );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 0.3, c1 );
+ gradient.setColorAt( 0.7, c2 );
+ gradient.setColorAt( 1.0, c2 );
+
+ painter->save();
+
+ painter->resetTransform();
+
+ painter->setPen( QPen( gradient, 1 ) );
+ painter->setBrush( brush );
+ painter->drawEllipse( rect );
+
+ painter->restore();
+}
+
+/*!
+ Constructor
+
+ \param style Style
+ \param hasKnob With/Without knob
+ \param mid Middle color
+ \param base Base color
+*/
+QwtDialSimpleNeedle::QwtDialSimpleNeedle( Style style, bool hasKnob,
+ const QColor &mid, const QColor &base ):
+ d_style( style ),
+ d_hasKnob( hasKnob ),
+ d_width( -1 )
+{
+ QPalette palette;
+ palette.setColor( QPalette::Mid, mid );
+ palette.setColor( QPalette::Base, base );
+
+ setPalette( palette );
+}
+
+/*!
+ Set the width of the needle
+ \param width Width
+ \sa width()
+*/
+void QwtDialSimpleNeedle::setWidth( double width )
+{
+ d_width = width;
+}
+
+/*!
+ \return the width of the needle
+ \sa setWidth()
+*/
+double QwtDialSimpleNeedle::width() const
+{
+ return d_width;
+}
+
+/*!
+ Draw the needle
+
+ \param painter Painter
+ \param length Length of the needle
+ \param colorGroup Color group, used for painting
+*/
+void QwtDialSimpleNeedle::drawNeedle( QPainter *painter,
+ double length, QPalette::ColorGroup colorGroup ) const
+{
+ double knobWidth = 0.0;
+ double width = d_width;
+
+ if ( d_style == Arrow )
+ {
+ if ( width <= 0.0 )
+ width = qMax(length * 0.06, 6.0);
+
+ qwtDrawArrowNeedle( painter,
+ palette(), colorGroup, length, width );
+
+ knobWidth = qMin( width * 2.0, 0.2 * length );
+ }
+ else
+ {
+ if ( width <= 0.0 )
+ width = 5.0;
+
+ QPen pen ( palette().brush( colorGroup, QPalette::Mid ), width );
+ pen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( pen );
+ painter->drawLine( QPointF( 0.0, 0.0 ), QPointF( length, 0.0 ) );
+
+ knobWidth = qMax( width * 3.0, 5.0 );
+ }
+
+ if ( d_hasKnob && knobWidth > 0.0 )
+ {
+ drawKnob( painter, knobWidth,
+ palette().brush( colorGroup, QPalette::Base ), false );
+ }
+}
+
+//! Constructor
+
+QwtCompassMagnetNeedle::QwtCompassMagnetNeedle( Style style,
+ const QColor &light, const QColor &dark ):
+ d_style( style )
+{
+ QPalette palette;
+ palette.setColor( QPalette::Light, light );
+ palette.setColor( QPalette::Dark, dark );
+ palette.setColor( QPalette::Base, Qt::gray );
+
+ setPalette( palette );
+}
+
+/*!
+ Draw the needle
+
+ \param painter Painter
+ \param length Length of the needle
+ \param colorGroup Color group, used for painting
+*/
+void QwtCompassMagnetNeedle::drawNeedle( QPainter *painter,
+ double length, QPalette::ColorGroup colorGroup ) const
+{
+ if ( d_style == ThinStyle )
+ {
+ const double width = qMax( length / 6.0, 3.0 );
+
+ const int colorOffset = 10;
+
+ const QColor light = palette().color( colorGroup, QPalette::Light );
+ const QColor dark = palette().color( colorGroup, QPalette::Dark );
+
+ qwtDrawShadedPointer( painter,
+ dark.light( 100 + colorOffset ),
+ dark.dark( 100 + colorOffset ),
+ length, width );
+
+ painter->rotate( 180.0 );
+
+ qwtDrawShadedPointer( painter,
+ light.light( 100 + colorOffset ),
+ light.dark( 100 + colorOffset ),
+ length, width );
+
+ const QBrush baseBrush = palette().brush( colorGroup, QPalette::Base );
+ drawKnob( painter, width, baseBrush, true );
+ }
+ else
+ {
+ qwtDrawTriangleNeedle( painter, palette(), colorGroup, length );
+ }
+}
+
+/*!
+ Constructor
+
+ \param style Arrow style
+ \param light Light color
+ \param dark Dark color
+*/
+QwtCompassWindArrow::QwtCompassWindArrow( Style style,
+ const QColor &light, const QColor &dark ):
+ d_style( style )
+{
+ QPalette palette;
+ palette.setColor( QPalette::Light, light );
+ palette.setColor( QPalette::Dark, dark );
+
+ setPalette( palette );
+}
+
+/*!
+ Draw the needle
+
+ \param painter Painter
+ \param length Length of the needle
+ \param colorGroup Color group, used for painting
+*/
+void QwtCompassWindArrow::drawNeedle( QPainter *painter,
+ double length, QPalette::ColorGroup colorGroup ) const
+{
+ if ( d_style == Style1 )
+ qwtDrawStyle1Needle( painter, palette(), colorGroup, length );
+ else
+ qwtDrawStyle2Needle( painter, palette(), colorGroup, length );
+}
diff --git a/src/libpcp_qwt/src/qwt_dial_needle.h b/src/libpcp_qwt/src/qwt_dial_needle.h
new file mode 100644
index 0000000..a02a590
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dial_needle.h
@@ -0,0 +1,190 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_DIAL_NEEDLE_H
+#define QWT_DIAL_NEEDLE_H 1
+
+#include "qwt_global.h"
+#include <qpalette.h>
+
+class QPainter;
+class QPoint;
+
+/*!
+ \brief Base class for needles that can be used in a QwtDial.
+
+ QwtDialNeedle is a pointer that indicates a value by pointing
+ to a specific direction.
+
+ Qwt is missing a set of good looking needles.
+ Contributions are very welcome.
+
+ \sa QwtDial, QwtCompass
+*/
+
+class QWT_EXPORT QwtDialNeedle
+{
+public:
+ QwtDialNeedle();
+ virtual ~QwtDialNeedle();
+
+ virtual void setPalette( const QPalette & );
+ const QPalette &palette() const;
+
+ virtual void draw( QPainter *painter, const QPointF &center,
+ double length, double direction,
+ QPalette::ColorGroup = QPalette::Active ) const;
+
+protected:
+ /*!
+ \brief Draw the needle
+
+ The origin of the needle is at position (0.0, 0.0 )
+ pointing in direction 0.0 ( = east ).
+
+ The painter is already initilaized with translation and
+ rotation.
+
+ \param painter Painter
+ \param length Length of the needle
+ \param colorGroup Color group, used for painting
+
+ \sa setPalette(), palette()
+ */
+ virtual void drawNeedle( QPainter *painter,
+ double length, QPalette::ColorGroup colorGroup ) const = 0;
+
+ virtual void drawKnob( QPainter *, double width,
+ const QBrush &, bool sunken ) const;
+
+private:
+ QPalette d_palette;
+};
+
+/*!
+ \brief A needle for dial widgets
+
+ The following colors are used:
+
+ - QPalette::Mid\n
+ Pointer
+ - QPalette::Base\n
+ Knob
+
+ \sa QwtDial, QwtCompass
+*/
+
+class QWT_EXPORT QwtDialSimpleNeedle: public QwtDialNeedle
+{
+public:
+ //! Style of the needle
+ enum Style
+ {
+ //! Arrow
+ Arrow,
+
+ //! A straight line from the center
+ Ray
+ };
+
+ QwtDialSimpleNeedle( Style, bool hasKnob = true,
+ const QColor &mid = Qt::gray, const QColor &base = Qt::darkGray );
+
+ void setWidth( double width );
+ double width() const;
+
+protected:
+ virtual void drawNeedle( QPainter *, double length,
+ QPalette::ColorGroup ) const;
+
+private:
+ Style d_style;
+ bool d_hasKnob;
+ double d_width;
+};
+
+/*!
+ \brief A magnet needle for compass widgets
+
+ A magnet needle points to two opposite directions indicating
+ north and south.
+
+ The following colors are used:
+ - QPalette::Light\n
+ Used for pointing south
+ - QPalette::Dark\n
+ Used for pointing north
+ - QPalette::Base\n
+ Knob (ThinStyle only)
+
+ \sa QwtDial, QwtCompass
+*/
+
+class QWT_EXPORT QwtCompassMagnetNeedle: public QwtDialNeedle
+{
+public:
+ //! Style of the needle
+ enum Style
+ {
+ //! A needle with a triangular shape
+ TriangleStyle,
+
+ //! A thin needle
+ ThinStyle
+ };
+
+ QwtCompassMagnetNeedle( Style = TriangleStyle,
+ const QColor &light = Qt::white, const QColor &dark = Qt::red );
+
+protected:
+ virtual void drawNeedle( QPainter *,
+ double length, QPalette::ColorGroup ) const;
+
+private:
+ Style d_style;
+};
+
+/*!
+ \brief An indicator for the wind direction
+
+ QwtCompassWindArrow shows the direction where the wind comes from.
+
+ - QPalette::Light\n
+ Used for Style1, or the light half of Style2
+ - QPalette::Dark\n
+ Used for the dark half of Style2
+
+ \sa QwtDial, QwtCompass
+*/
+
+class QWT_EXPORT QwtCompassWindArrow: public QwtDialNeedle
+{
+public:
+ //! Style of the arrow
+ enum Style
+ {
+ //! A needle pointing to the center
+ Style1,
+
+ //! A needle pointing to the center
+ Style2
+ };
+
+ QwtCompassWindArrow( Style, const QColor &light = Qt::white,
+ const QColor &dark = Qt::gray );
+
+protected:
+ virtual void drawNeedle( QPainter *,
+ double length, QPalette::ColorGroup ) const;
+
+private:
+ Style d_style;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_double_range.cpp b/src/libpcp_qwt/src/qwt_double_range.cpp
new file mode 100644
index 0000000..b073eab
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_double_range.cpp
@@ -0,0 +1,410 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_double_range.h"
+#include "qwt_math.h"
+
+#if QT_VERSION < 0x040601
+#define qFabs(x) ::fabs(x)
+#endif
+
+class QwtDoubleRange::PrivateData
+{
+public:
+ PrivateData():
+ minValue( 0.0 ),
+ maxValue( 0.0 ),
+ step( 1.0 ),
+ pageSize( 1 ),
+ isValid( false ),
+ value( 0.0 ),
+ exactValue( 0.0 ),
+ exactPrevValue( 0.0 ),
+ prevValue( 0.0 ),
+ periodic( false )
+ {
+ }
+
+ double minValue;
+ double maxValue;
+ double step;
+ int pageSize;
+
+ bool isValid;
+ double value;
+ double exactValue;
+ double exactPrevValue;
+ double prevValue;
+
+ bool periodic;
+};
+
+/*!
+ The range is initialized to [0.0, 100.0], the
+ step size to 1.0, and the value to 0.0.
+*/
+QwtDoubleRange::QwtDoubleRange()
+{
+ d_data = new PrivateData();
+}
+
+//! Destroys the QwtDoubleRange
+QwtDoubleRange::~QwtDoubleRange()
+{
+ delete d_data;
+}
+
+//! Set the value to be valid/invalid
+void QwtDoubleRange::setValid( bool isValid )
+{
+ if ( isValid != d_data->isValid )
+ {
+ d_data->isValid = isValid;
+ valueChange();
+ }
+}
+
+//! Indicates if the value is valid
+bool QwtDoubleRange::isValid() const
+{
+ return d_data->isValid;
+}
+
+void QwtDoubleRange::setNewValue( double value, bool align )
+{
+ d_data->prevValue = d_data->value;
+
+ const double vmin = qMin( d_data->minValue, d_data->maxValue );
+ const double vmax = qMax( d_data->minValue, d_data->maxValue );
+
+ if ( value < vmin )
+ {
+ if ( d_data->periodic && vmin != vmax )
+ {
+ d_data->value = value +
+ ::ceil( ( vmin - value ) / ( vmax - vmin ) ) * ( vmax - vmin );
+ }
+ else
+ d_data->value = vmin;
+ }
+ else if ( value > vmax )
+ {
+ if ( ( d_data->periodic ) && ( vmin != vmax ) )
+ {
+ d_data->value = value -
+ ::ceil( ( value - vmax ) / ( vmax - vmin ) ) * ( vmax - vmin );
+ }
+ else
+ d_data->value = vmax;
+ }
+ else
+ {
+ d_data->value = value;
+ }
+
+ d_data->exactPrevValue = d_data->exactValue;
+ d_data->exactValue = d_data->value;
+
+ if ( align )
+ {
+ if ( d_data->step != 0.0 )
+ {
+ d_data->value = d_data->minValue +
+ qRound( ( d_data->value - d_data->minValue ) / d_data->step ) * d_data->step;
+ }
+ else
+ d_data->value = d_data->minValue;
+
+ const double minEps = 1.0e-10;
+ // correct rounding error at the border
+ if ( qFabs( d_data->value - d_data->maxValue ) < minEps * qAbs( d_data->step ) )
+ d_data->value = d_data->maxValue;
+
+ // correct rounding error if value = 0
+ if ( qFabs( d_data->value ) < minEps * qAbs( d_data->step ) )
+ d_data->value = 0.0;
+ }
+
+ if ( !d_data->isValid || d_data->prevValue != d_data->value )
+ {
+ d_data->isValid = true;
+ valueChange();
+ }
+}
+
+/*!
+ \brief Adjust the value to the closest point in the step raster.
+ \param x value
+ \warning The value is clipped when it lies outside the range.
+ When the range is QwtDoubleRange::periodic, it will
+ be mapped to a point in the interval such that
+ \verbatim new value := x + n * (max. value - min. value)\endverbatim
+ with an integer number n.
+*/
+void QwtDoubleRange::fitValue( double x )
+{
+ setNewValue( x, true );
+}
+
+
+/*!
+ \brief Set a new value without adjusting to the step raster
+ \param x new value
+ \warning The value is clipped when it lies outside the range.
+ When the range is QwtDoubleRange::periodic, it will
+ be mapped to a point in the interval such that
+ \verbatim new value := x + n * (max. value - min. value)\endverbatim
+ with an integer number n.
+*/
+void QwtDoubleRange::setValue( double x )
+{
+ setNewValue( x, false );
+}
+
+/*!
+ \brief Specify range and step size
+
+ \param vmin lower boundary of the interval
+ \param vmax higher boundary of the interval
+ \param vstep step width
+ \param pageSize page size in steps
+ \warning
+ \li A change of the range changes the value if it lies outside the
+ new range. The current value
+ will *not* be adjusted to the new step raster.
+ \li vmax < vmin is allowed.
+ \li If the step size is left out or set to zero, it will be
+ set to 1/100 of the interval length.
+ \li If the step size has an absurd value, it will be corrected
+ to a better one.
+*/
+void QwtDoubleRange::setRange(
+ double vmin, double vmax, double vstep, int pageSize )
+{
+ const bool rchg = ( d_data->maxValue != vmax || d_data->minValue != vmin );
+
+ if ( rchg )
+ {
+ d_data->minValue = vmin;
+ d_data->maxValue = vmax;
+ }
+
+ // look if the step width has an acceptable
+ // value or otherwise change it.
+ setStep( vstep );
+
+ // limit page size
+ const int max =
+ int( qAbs( ( d_data->maxValue - d_data->minValue ) / d_data->step ) );
+ d_data->pageSize = qBound( 0, pageSize, max );
+
+ // If the value lies out of the range, it
+ // will be changed. Note that it will not be adjusted to
+ // the new step width.
+ setNewValue( d_data->value, false );
+
+ // call notifier after the step width has been
+ // adjusted.
+ if ( rchg )
+ rangeChange();
+}
+
+/*!
+ \brief Change the step raster
+ \param vstep new step width
+ \warning The value will \e not be adjusted to the new step raster.
+*/
+void QwtDoubleRange::setStep( double vstep )
+{
+ const double intv = d_data->maxValue - d_data->minValue;
+
+ double newStep;
+ if ( vstep == 0.0 )
+ {
+ const double defaultRelStep = 1.0e-2;
+ newStep = intv * defaultRelStep;
+ }
+ else
+ {
+ if ( ( intv > 0.0 && vstep < 0.0 ) || ( intv < 0.0 && vstep > 0.0 ) )
+ newStep = -vstep;
+ else
+ newStep = vstep;
+
+ const double minRelStep = 1.0e-10;
+ if ( qFabs( newStep ) < qFabs( minRelStep * intv ) )
+ newStep = minRelStep * intv;
+ }
+
+ if ( newStep != d_data->step )
+ {
+ d_data->step = newStep;
+ stepChange();
+ }
+}
+
+
+/*!
+ \brief Make the range periodic
+
+ When the range is periodic, the value will be set to a point
+ inside the interval such that
+
+ \verbatim point = value + n * width \endverbatim
+
+ if the user tries to set a new value which is outside the range.
+ If the range is nonperiodic (the default), values outside the
+ range will be clipped.
+
+ \param tf true for a periodic range
+*/
+void QwtDoubleRange::setPeriodic( bool tf )
+{
+ d_data->periodic = tf;
+}
+
+/*!
+ \brief Increment the value by a specified number of steps
+ \param nSteps Number of steps to increment
+ \warning As a result of this operation, the new value will always be
+ adjusted to the step raster.
+*/
+void QwtDoubleRange::incValue( int nSteps )
+{
+ if ( isValid() )
+ setNewValue( d_data->value + double( nSteps ) * d_data->step, true );
+}
+
+/*!
+ \brief Increment the value by a specified number of pages
+ \param nPages Number of pages to increment.
+ A negative number decrements the value.
+ \warning The Page size is specified in the constructor.
+*/
+void QwtDoubleRange::incPages( int nPages )
+{
+ if ( isValid() )
+ {
+ const double off = d_data->step * d_data->pageSize * nPages;
+ setNewValue( d_data->value + off, true );
+ }
+}
+
+/*!
+ \brief Notify a change of value
+
+ This virtual function is called whenever the value changes.
+ The default implementation does nothing.
+*/
+void QwtDoubleRange::valueChange()
+{
+}
+
+
+/*!
+ \brief Notify a change of the range
+
+ This virtual function is called whenever the range changes.
+ The default implementation does nothing.
+*/
+void QwtDoubleRange::rangeChange()
+{
+}
+
+
+/*!
+ \brief Notify a change of the step size
+
+ This virtual function is called whenever the step size changes.
+ The default implementation does nothing.
+*/
+void QwtDoubleRange::stepChange()
+{
+}
+
+/*!
+ \return the step size
+ \sa setStep(), setRange()
+*/
+double QwtDoubleRange::step() const
+{
+ return qAbs( d_data->step );
+}
+
+/*!
+ \brief Returns the value of the second border of the range
+
+ maxValue returns the value which has been specified
+ as the second parameter in QwtDoubleRange::setRange.
+
+ \sa setRange()
+*/
+double QwtDoubleRange::maxValue() const
+{
+ return d_data->maxValue;
+}
+
+/*!
+ \brief Returns the value at the first border of the range
+
+ minValue returns the value which has been specified
+ as the first parameter in setRange().
+
+ \sa setRange()
+*/
+double QwtDoubleRange::minValue() const
+{
+ return d_data->minValue;
+}
+
+/*!
+ \brief Returns true if the range is periodic
+ \sa setPeriodic()
+*/
+bool QwtDoubleRange::periodic() const
+{
+ return d_data->periodic;
+}
+
+//! Returns the page size in steps.
+int QwtDoubleRange::pageSize() const
+{
+ return d_data->pageSize;
+}
+
+//! Returns the current value.
+double QwtDoubleRange::value() const
+{
+ return d_data->value;
+}
+
+/*!
+ \brief Returns the exact value
+
+ The exact value is the value which QwtDoubleRange::value would return
+ if the value were not adjusted to the step raster. It differs from
+ the current value only if fitValue() or incValue() have been used before.
+ This function is intended for internal use in derived classes.
+*/
+double QwtDoubleRange::exactValue() const
+{
+ return d_data->exactValue;
+}
+
+//! Returns the exact previous value
+double QwtDoubleRange::exactPrevValue() const
+{
+ return d_data->exactPrevValue;
+}
+
+//! Returns the previous value
+double QwtDoubleRange::prevValue() const
+{
+ return d_data->prevValue;
+}
diff --git a/src/libpcp_qwt/src/qwt_double_range.h b/src/libpcp_qwt/src/qwt_double_range.h
new file mode 100644
index 0000000..29f2f66
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_double_range.h
@@ -0,0 +1,78 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_DOUBLE_RANGE_H
+#define QWT_DOUBLE_RANGE_H
+
+#include "qwt_global.h"
+
+/*!
+ \brief A class which controls a value within an interval
+
+ This class is useful as a base class or a member for sliders.
+ It represents an interval of type double within which a value can
+ be moved. The value can be either an arbitrary point inside
+ the interval (see QwtDoubleRange::setValue), or it can be fitted
+ into a step raster (see QwtDoubleRange::fitValue and
+ QwtDoubleRange::incValue).
+
+ As a special case, a QwtDoubleRange can be periodic, which means that
+ a value outside the interval will be mapped to a value inside the
+ interval when QwtDoubleRange::setValue(), QwtDoubleRange::fitValue(),
+ QwtDoubleRange::incValue() or QwtDoubleRange::incPages() are called.
+*/
+
+class QWT_EXPORT QwtDoubleRange
+{
+public:
+ QwtDoubleRange();
+ virtual ~QwtDoubleRange();
+
+ void setRange( double vmin, double vmax,
+ double vstep = 0.0, int pagesize = 1 );
+
+ void setValid( bool );
+ bool isValid() const;
+
+ virtual void setValue( double );
+ double value() const;
+
+ void setPeriodic( bool tf );
+ bool periodic() const;
+
+ void setStep( double );
+ double step() const;
+
+ double maxValue() const;
+ double minValue() const;
+
+ int pageSize() const;
+
+ virtual void incValue( int );
+ virtual void incPages( int );
+ virtual void fitValue( double );
+
+protected:
+
+ double exactValue() const;
+ double exactPrevValue() const;
+ double prevValue() const;
+
+ virtual void valueChange();
+ virtual void stepChange();
+ virtual void rangeChange();
+
+private:
+ void setNewValue( double value, bool align = false );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_dyngrid_layout.cpp b/src/libpcp_qwt/src/qwt_dyngrid_layout.cpp
new file mode 100644
index 0000000..e6968ae
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dyngrid_layout.cpp
@@ -0,0 +1,575 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_dyngrid_layout.h"
+#include "qwt_math.h"
+#include <qvector.h>
+#include <qlist.h>
+
+class QwtDynGridLayout::PrivateData
+{
+public:
+ PrivateData():
+ isDirty( true )
+ {
+ }
+
+ void updateLayoutCache();
+
+ mutable QList<QLayoutItem*> itemList;
+
+ uint maxCols;
+ uint numRows;
+ uint numCols;
+
+ Qt::Orientations expanding;
+
+ bool isDirty;
+ QVector<QSize> itemSizeHints;
+};
+
+void QwtDynGridLayout::PrivateData::updateLayoutCache()
+{
+ itemSizeHints.resize( itemList.count() );
+
+ int index = 0;
+
+ for ( QList<QLayoutItem*>::iterator it = itemList.begin();
+ it != itemList.end(); ++it, index++ )
+ {
+ itemSizeHints[ index ] = ( *it )->sizeHint();
+ }
+
+ isDirty = false;
+}
+
+/*!
+ \param parent Parent widget
+ \param margin Margin
+ \param spacing Spacing
+*/
+
+QwtDynGridLayout::QwtDynGridLayout( QWidget *parent,
+ int margin, int spacing ):
+ QLayout( parent )
+{
+ init();
+
+ setSpacing( spacing );
+ setMargin( margin );
+}
+
+/*!
+ \param spacing Spacing
+*/
+
+QwtDynGridLayout::QwtDynGridLayout( int spacing )
+{
+ init();
+ setSpacing( spacing );
+}
+
+/*!
+ Initialize the layout with default values.
+*/
+void QwtDynGridLayout::init()
+{
+ d_data = new QwtDynGridLayout::PrivateData;
+ d_data->maxCols = d_data->numRows = d_data->numCols = 0;
+ d_data->expanding = 0;
+}
+
+//! Destructor
+
+QwtDynGridLayout::~QwtDynGridLayout()
+{
+ for ( int i = 0; i < d_data->itemList.size(); i++ )
+ delete d_data->itemList[i];
+
+ delete d_data;
+}
+
+//! Invalidate all internal caches
+void QwtDynGridLayout::invalidate()
+{
+ d_data->isDirty = true;
+ QLayout::invalidate();
+}
+
+/*!
+ Limit the number of columns.
+ \param maxCols upper limit, 0 means unlimited
+ \sa maxCols()
+*/
+void QwtDynGridLayout::setMaxCols( uint maxCols )
+{
+ d_data->maxCols = maxCols;
+}
+
+/*!
+ Return the upper limit for the number of columns.
+ 0 means unlimited, what is the default.
+ \sa setMaxCols()
+*/
+
+uint QwtDynGridLayout::maxCols() const
+{
+ return d_data->maxCols;
+}
+
+//! Adds item to the next free position.
+
+void QwtDynGridLayout::addItem( QLayoutItem *item )
+{
+ d_data->itemList.append( item );
+ invalidate();
+}
+
+/*!
+ \return true if this layout is empty.
+*/
+
+bool QwtDynGridLayout::isEmpty() const
+{
+ return d_data->itemList.isEmpty();
+}
+
+/*!
+ \return number of layout items
+*/
+
+uint QwtDynGridLayout::itemCount() const
+{
+ return d_data->itemList.count();
+}
+
+/*!
+ Find the item at a spcific index
+
+ \param index Index
+ \sa takeAt()
+*/
+QLayoutItem *QwtDynGridLayout::itemAt( int index ) const
+{
+ if ( index < 0 || index >= d_data->itemList.count() )
+ return NULL;
+
+ return d_data->itemList.at( index );
+}
+
+/*!
+ Find the item at a spcific index and remove it from the layout
+
+ \param index Index
+ \sa itemAt()
+*/
+QLayoutItem *QwtDynGridLayout::takeAt( int index )
+{
+ if ( index < 0 || index >= d_data->itemList.count() )
+ return NULL;
+
+ d_data->isDirty = true;
+ return d_data->itemList.takeAt( index );
+}
+
+//! \return Number of items in the layout
+int QwtDynGridLayout::count() const
+{
+ return d_data->itemList.count();
+}
+
+/*!
+ Set whether this layout can make use of more space than sizeHint().
+ A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only
+ one dimension, while Qt::Vertical | Qt::Horizontal means that it wants
+ to grow in both dimensions. The default value is 0.
+
+ \param expanding Or'd orientations
+ \sa expandingDirections()
+*/
+void QwtDynGridLayout::setExpandingDirections( Qt::Orientations expanding )
+{
+ d_data->expanding = expanding;
+}
+
+/*!
+ Returns whether this layout can make use of more space than sizeHint().
+ A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only
+ one dimension, while Qt::Vertical | Qt::Horizontal means that it wants
+ to grow in both dimensions.
+ \sa setExpandingDirections()
+*/
+Qt::Orientations QwtDynGridLayout::expandingDirections() const
+{
+ return d_data->expanding;
+}
+
+/*!
+ Reorganizes columns and rows and resizes managed items within
+ the rectangle rect.
+
+ \param rect Layout geometry
+*/
+void QwtDynGridLayout::setGeometry( const QRect &rect )
+{
+ QLayout::setGeometry( rect );
+
+ if ( isEmpty() )
+ return;
+
+ d_data->numCols = columnsForWidth( rect.width() );
+ d_data->numRows = itemCount() / d_data->numCols;
+ if ( itemCount() % d_data->numCols )
+ d_data->numRows++;
+
+ QList<QRect> itemGeometries = layoutItems( rect, d_data->numCols );
+
+ int index = 0;
+ for ( QList<QLayoutItem*>::iterator it = d_data->itemList.begin();
+ it != d_data->itemList.end(); ++it )
+ {
+ ( *it )->setGeometry( itemGeometries[index] );
+ index++;
+ }
+}
+
+/*!
+ Calculate the number of columns for a given width. It tries to
+ use as many columns as possible (limited by maxCols())
+
+ \param width Available width for all columns
+ \sa maxCols(), setMaxCols()
+*/
+
+uint QwtDynGridLayout::columnsForWidth( int width ) const
+{
+ if ( isEmpty() )
+ return 0;
+
+ uint maxCols = itemCount();
+ if ( d_data->maxCols > 0 )
+ maxCols = qMin( d_data->maxCols, maxCols );
+
+ if ( maxRowWidth( maxCols ) <= width )
+ return maxCols;
+
+ for ( uint numCols = 2; numCols <= maxCols; numCols++ )
+ {
+ const int rowWidth = maxRowWidth( numCols );
+ if ( rowWidth > width )
+ return numCols - 1;
+ }
+
+ return 1; // At least 1 column
+}
+
+/*!
+ Calculate the width of a layout for a given number of
+ columns.
+
+ \param numCols Given number of columns
+ \param itemWidth Array of the width hints for all items
+*/
+int QwtDynGridLayout::maxRowWidth( int numCols ) const
+{
+ int col;
+
+ QVector<int> colWidth( numCols );
+ for ( col = 0; col < numCols; col++ )
+ colWidth[col] = 0;
+
+ if ( d_data->isDirty )
+ d_data->updateLayoutCache();
+
+ for ( int index = 0;
+ index < d_data->itemSizeHints.count(); index++ )
+ {
+ col = index % numCols;
+ colWidth[col] = qMax( colWidth[col],
+ d_data->itemSizeHints[int( index )].width() );
+ }
+
+ int rowWidth = 2 * margin() + ( numCols - 1 ) * spacing();
+ for ( col = 0; col < numCols; col++ )
+ rowWidth += colWidth[col];
+
+ return rowWidth;
+}
+
+/*!
+ \return the maximum width of all layout items
+*/
+int QwtDynGridLayout::maxItemWidth() const
+{
+ if ( isEmpty() )
+ return 0;
+
+ if ( d_data->isDirty )
+ d_data->updateLayoutCache();
+
+ int w = 0;
+ for ( int i = 0; i < d_data->itemSizeHints.count(); i++ )
+ {
+ const int itemW = d_data->itemSizeHints[i].width();
+ if ( itemW > w )
+ w = itemW;
+ }
+
+ return w;
+}
+
+/*!
+ Calculate the geometries of the layout items for a layout
+ with numCols columns and a given rect.
+
+ \param rect Rect where to place the items
+ \param numCols Number of columns
+ \return item geometries
+*/
+
+QList<QRect> QwtDynGridLayout::layoutItems( const QRect &rect,
+ uint numCols ) const
+{
+ QList<QRect> itemGeometries;
+ if ( numCols == 0 || isEmpty() )
+ return itemGeometries;
+
+ uint numRows = itemCount() / numCols;
+ if ( numCols % itemCount() )
+ numRows++;
+
+ if ( numRows == 0 )
+ return itemGeometries;
+
+ QVector<int> rowHeight( numRows );
+ QVector<int> colWidth( numCols );
+
+ layoutGrid( numCols, rowHeight, colWidth );
+
+ bool expandH, expandV;
+ expandH = expandingDirections() & Qt::Horizontal;
+ expandV = expandingDirections() & Qt::Vertical;
+
+ if ( expandH || expandV )
+ stretchGrid( rect, numCols, rowHeight, colWidth );
+
+ const int maxCols = d_data->maxCols;
+ d_data->maxCols = numCols;
+ const QRect alignedRect = alignmentRect( rect );
+ d_data->maxCols = maxCols;
+
+ const int xOffset = expandH ? 0 : alignedRect.x();
+ const int yOffset = expandV ? 0 : alignedRect.y();
+
+ QVector<int> colX( numCols );
+ QVector<int> rowY( numRows );
+
+ const int xySpace = spacing();
+
+ rowY[0] = yOffset + margin();
+ for ( uint r = 1; r < numRows; r++ )
+ rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace;
+
+ colX[0] = xOffset + margin();
+ for ( uint c = 1; c < numCols; c++ )
+ colX[c] = colX[c-1] + colWidth[c-1] + xySpace;
+
+ const int itemCount = d_data->itemList.size();
+ for ( int i = 0; i < itemCount; i++ )
+ {
+ const int row = i / numCols;
+ const int col = i % numCols;
+
+ QRect itemGeometry( colX[col], rowY[row],
+ colWidth[col], rowHeight[row] );
+ itemGeometries.append( itemGeometry );
+ }
+
+ return itemGeometries;
+}
+
+
+/*!
+ Calculate the dimensions for the columns and rows for a grid
+ of numCols columns.
+
+ \param numCols Number of columns.
+ \param rowHeight Array where to fill in the calculated row heights.
+ \param colWidth Array where to fill in the calculated column widths.
+*/
+
+void QwtDynGridLayout::layoutGrid( uint numCols,
+ QVector<int>& rowHeight, QVector<int>& colWidth ) const
+{
+ if ( numCols <= 0 )
+ return;
+
+ if ( d_data->isDirty )
+ d_data->updateLayoutCache();
+
+ for ( int index = 0; index < d_data->itemSizeHints.count(); index++ )
+ {
+ const int row = index / numCols;
+ const int col = index % numCols;
+
+ const QSize &size = d_data->itemSizeHints[int( index )];
+
+ rowHeight[row] = ( col == 0 )
+ ? size.height() : qMax( rowHeight[row], size.height() );
+ colWidth[col] = ( row == 0 )
+ ? size.width() : qMax( colWidth[col], size.width() );
+ }
+}
+
+/*!
+ \return true: QwtDynGridLayout implements heightForWidth.
+ \sa heightForWidth()
+*/
+bool QwtDynGridLayout::hasHeightForWidth() const
+{
+ return true;
+}
+
+/*!
+ \return The preferred height for this layout, given the width w.
+ \sa hasHeightForWidth()
+*/
+int QwtDynGridLayout::heightForWidth( int width ) const
+{
+ if ( isEmpty() )
+ return 0;
+
+ const uint numCols = columnsForWidth( width );
+ uint numRows = itemCount() / numCols;
+ if ( itemCount() % numCols )
+ numRows++;
+
+ QVector<int> rowHeight( numRows );
+ QVector<int> colWidth( numCols );
+
+ layoutGrid( numCols, rowHeight, colWidth );
+
+ int h = 2 * margin() + ( numRows - 1 ) * spacing();
+ for ( uint row = 0; row < numRows; row++ )
+ h += rowHeight[row];
+
+ return h;
+}
+
+/*!
+ Stretch columns in case of expanding() & QSizePolicy::Horizontal and
+ rows in case of expanding() & QSizePolicy::Vertical to fill the entire
+ rect. Rows and columns are stretched with the same factor.
+
+ \sa setExpanding(), expanding()
+*/
+void QwtDynGridLayout::stretchGrid( const QRect &rect,
+ uint numCols, QVector<int>& rowHeight, QVector<int>& colWidth ) const
+{
+ if ( numCols == 0 || isEmpty() )
+ return;
+
+ bool expandH, expandV;
+ expandH = expandingDirections() & Qt::Horizontal;
+ expandV = expandingDirections() & Qt::Vertical;
+
+ if ( expandH )
+ {
+ int xDelta = rect.width() - 2 * margin() - ( numCols - 1 ) * spacing();
+ for ( uint col = 0; col < numCols; col++ )
+ xDelta -= colWidth[col];
+
+ if ( xDelta > 0 )
+ {
+ for ( uint col = 0; col < numCols; col++ )
+ {
+ const int space = xDelta / ( numCols - col );
+ colWidth[col] += space;
+ xDelta -= space;
+ }
+ }
+ }
+
+ if ( expandV )
+ {
+ uint numRows = itemCount() / numCols;
+ if ( itemCount() % numCols )
+ numRows++;
+
+ int yDelta = rect.height() - 2 * margin() - ( numRows - 1 ) * spacing();
+ for ( uint row = 0; row < numRows; row++ )
+ yDelta -= rowHeight[row];
+
+ if ( yDelta > 0 )
+ {
+ for ( uint row = 0; row < numRows; row++ )
+ {
+ const int space = yDelta / ( numRows - row );
+ rowHeight[row] += space;
+ yDelta -= space;
+ }
+ }
+ }
+}
+
+/*!
+ Return the size hint. If maxCols() > 0 it is the size for
+ a grid with maxCols() columns, otherwise it is the size for
+ a grid with only one row.
+
+ \sa maxCols(), setMaxCols()
+*/
+QSize QwtDynGridLayout::sizeHint() const
+{
+ if ( isEmpty() )
+ return QSize();
+
+ uint numCols = itemCount();
+ if ( d_data->maxCols > 0 )
+ numCols = qMin( d_data->maxCols, numCols );
+
+ uint numRows = itemCount() / numCols;
+ if ( itemCount() % numCols )
+ numRows++;
+
+ QVector<int> rowHeight( numRows );
+ QVector<int> colWidth( numCols );
+
+ layoutGrid( numCols, rowHeight, colWidth );
+
+ int h = 2 * margin() + ( numRows - 1 ) * spacing();
+ for ( uint row = 0; row < numRows; row++ )
+ h += rowHeight[row];
+
+ int w = 2 * margin() + ( numCols - 1 ) * spacing();
+ for ( uint col = 0; col < numCols; col++ )
+ w += colWidth[col];
+
+ return QSize( w, h );
+}
+
+/*!
+ \return Number of rows of the current layout.
+ \sa numCols()
+ \warning The number of rows might change whenever the geometry changes
+*/
+uint QwtDynGridLayout::numRows() const
+{
+ return d_data->numRows;
+}
+
+/*!
+ \return Number of columns of the current layout.
+ \sa numRows()
+ \warning The number of columns might change whenever the geometry changes
+*/
+uint QwtDynGridLayout::numCols() const
+{
+ return d_data->numCols;
+}
diff --git a/src/libpcp_qwt/src/qwt_dyngrid_layout.h b/src/libpcp_qwt/src/qwt_dyngrid_layout.h
new file mode 100644
index 0000000..0b835f0
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_dyngrid_layout.h
@@ -0,0 +1,83 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_DYNGRID_LAYOUT_H
+#define QWT_DYNGRID_LAYOUT_H
+
+#include "qwt_global.h"
+#include <qlayout.h>
+#include <qsize.h>
+#include <qlist.h>
+
+/*!
+ \brief The QwtDynGridLayout class lays out widgets in a grid,
+ adjusting the number of columns and rows to the current size.
+
+ QwtDynGridLayout takes the space it gets, divides it up into rows and
+ columns, and puts each of the widgets it manages into the correct cell(s).
+ It lays out as many number of columns as possible (limited by maxCols()).
+*/
+
+class QWT_EXPORT QwtDynGridLayout : public QLayout
+{
+ Q_OBJECT
+public:
+ explicit QwtDynGridLayout( QWidget *, int margin = 0, int space = -1 );
+ explicit QwtDynGridLayout( int space = -1 );
+
+ virtual ~QwtDynGridLayout();
+
+ virtual void invalidate();
+
+ void setMaxCols( uint maxCols );
+ uint maxCols() const;
+
+ uint numRows () const;
+ uint numCols () const;
+
+ virtual void addItem( QLayoutItem * );
+
+ virtual QLayoutItem *itemAt( int index ) const;
+ virtual QLayoutItem *takeAt( int index );
+ virtual int count() const;
+
+ void setExpandingDirections( Qt::Orientations );
+ virtual Qt::Orientations expandingDirections() const;
+ QList<QRect> layoutItems( const QRect &, uint numCols ) const;
+
+ virtual int maxItemWidth() const;
+
+ virtual void setGeometry( const QRect &rect );
+
+ virtual bool hasHeightForWidth() const;
+ virtual int heightForWidth( int ) const;
+
+ virtual QSize sizeHint() const;
+
+ virtual bool isEmpty() const;
+ uint itemCount() const;
+
+ virtual uint columnsForWidth( int width ) const;
+
+protected:
+
+ void layoutGrid( uint numCols,
+ QVector<int>& rowHeight, QVector<int>& colWidth ) const;
+ void stretchGrid( const QRect &rect, uint numCols,
+ QVector<int>& rowHeight, QVector<int>& colWidth ) const;
+
+private:
+ void init();
+ int maxRowWidth( int numCols ) const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_event_pattern.cpp b/src/libpcp_qwt/src/qwt_event_pattern.cpp
new file mode 100644
index 0000000..24bcbbc
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_event_pattern.cpp
@@ -0,0 +1,274 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_event_pattern.h"
+#include <qevent.h>
+
+/*!
+ Constructor
+
+ \sa MousePatternCode, KeyPatternCode
+*/
+
+QwtEventPattern::QwtEventPattern():
+ d_mousePattern( MousePatternCount ),
+ d_keyPattern( KeyPatternCount )
+{
+ initKeyPattern();
+ initMousePattern( 3 );
+}
+
+//! Destructor
+QwtEventPattern::~QwtEventPattern()
+{
+}
+
+/*!
+ Set default mouse patterns, depending on the number of mouse buttons
+
+ \param numButtons Number of mouse buttons ( <= 3 )
+ \sa MousePatternCode
+*/
+void QwtEventPattern::initMousePattern( int numButtons )
+{
+ const int altButton = Qt::AltModifier;
+ const int controlButton = Qt::ControlModifier;
+ const int shiftButton = Qt::ShiftModifier;
+
+ d_mousePattern.resize( MousePatternCount );
+
+ switch ( numButtons )
+ {
+ case 1:
+ {
+ setMousePattern( MouseSelect1, Qt::LeftButton );
+ setMousePattern( MouseSelect2, Qt::LeftButton, controlButton );
+ setMousePattern( MouseSelect3, Qt::LeftButton, altButton );
+ break;
+ }
+ case 2:
+ {
+ setMousePattern( MouseSelect1, Qt::LeftButton );
+ setMousePattern( MouseSelect2, Qt::RightButton );
+ setMousePattern( MouseSelect3, Qt::LeftButton, altButton );
+ break;
+ }
+ default:
+ {
+ setMousePattern( MouseSelect1, Qt::LeftButton );
+ setMousePattern( MouseSelect2, Qt::RightButton );
+ setMousePattern( MouseSelect3, Qt::MidButton );
+ }
+ }
+ for ( int i = 0; i < 3; i++ )
+ {
+ setMousePattern( MouseSelect4 + i,
+ d_mousePattern[MouseSelect1 + i].button,
+ d_mousePattern[MouseSelect1 + i].state | shiftButton );
+ }
+}
+
+/*!
+ Set default mouse patterns.
+
+ \sa KeyPatternCode
+*/
+void QwtEventPattern::initKeyPattern()
+{
+ d_keyPattern.resize( KeyPatternCount );
+
+ setKeyPattern( KeySelect1, Qt::Key_Return );
+ setKeyPattern( KeySelect2, Qt::Key_Space );
+ setKeyPattern( KeyAbort, Qt::Key_Escape );
+
+ setKeyPattern( KeyLeft, Qt::Key_Left );
+ setKeyPattern( KeyRight, Qt::Key_Right );
+ setKeyPattern( KeyUp, Qt::Key_Up );
+ setKeyPattern( KeyDown, Qt::Key_Down );
+
+ setKeyPattern( KeyRedo, Qt::Key_Plus );
+ setKeyPattern( KeyUndo, Qt::Key_Minus );
+ setKeyPattern( KeyHome, Qt::Key_Escape );
+}
+
+/*!
+ Change one mouse pattern
+
+ \param pattern Index of the pattern
+ \param button Button
+ \param state State
+
+ \sa QMouseEvent
+*/
+void QwtEventPattern::setMousePattern( uint pattern, int button, int state )
+{
+ if ( pattern < ( uint )d_mousePattern.count() )
+ {
+ d_mousePattern[int( pattern )].button = button;
+ d_mousePattern[int( pattern )].state = state;
+ }
+}
+
+/*!
+ Change one key pattern
+
+ \param pattern Index of the pattern
+ \param key Key
+ \param state State
+
+ \sa QKeyEvent
+*/
+void QwtEventPattern::setKeyPattern( uint pattern, int key, int state )
+{
+ if ( pattern < ( uint )d_keyPattern.count() )
+ {
+ d_keyPattern[int( pattern )].key = key;
+ d_keyPattern[int( pattern )].state = state;
+ }
+}
+
+//! Change the mouse event patterns
+void QwtEventPattern::setMousePattern( const QVector<MousePattern> &pattern )
+{
+ d_mousePattern = pattern;
+}
+
+//! Change the key event patterns
+void QwtEventPattern::setKeyPattern( const QVector<KeyPattern> &pattern )
+{
+ d_keyPattern = pattern;
+}
+
+//! Return mouse patterns
+const QVector<QwtEventPattern::MousePattern> &
+QwtEventPattern::mousePattern() const
+{
+ return d_mousePattern;
+}
+
+//! Return key patterns
+const QVector<QwtEventPattern::KeyPattern> &
+QwtEventPattern::keyPattern() const
+{
+ return d_keyPattern;
+}
+
+//! Return ,ouse patterns
+QVector<QwtEventPattern::MousePattern> &QwtEventPattern::mousePattern()
+{
+ return d_mousePattern;
+}
+
+//! Return Key patterns
+QVector<QwtEventPattern::KeyPattern> &QwtEventPattern::keyPattern()
+{
+ return d_keyPattern;
+}
+
+/*!
+ \brief Compare a mouse event with an event pattern.
+
+ A mouse event matches the pattern when both have the same button
+ value and in the state value the same key flags(Qt::KeyButtonMask)
+ are set.
+
+ \param pattern Index of the event pattern
+ \param event Mouse event
+ \return true if matches
+
+ \sa keyMatch()
+*/
+bool QwtEventPattern::mouseMatch( uint pattern,
+ const QMouseEvent *event ) const
+{
+ bool ok = false;
+
+ if ( event && pattern < ( uint )d_mousePattern.count() )
+ ok = mouseMatch( d_mousePattern[int( pattern )], event );
+
+ return ok;
+}
+
+/*!
+ \brief Compare a mouse event with an event pattern.
+
+ A mouse event matches the pattern when both have the same button
+ value and in the state value the same key flags(Qt::KeyButtonMask)
+ are set.
+
+ \param pattern Mouse event pattern
+ \param event Mouse event
+ \return true if matches
+
+ \sa keyMatch()
+*/
+
+bool QwtEventPattern::mouseMatch( const MousePattern &pattern,
+ const QMouseEvent *event ) const
+{
+ if ( event->button() != pattern.button )
+ return false;
+
+ const bool matched =
+ ( event->modifiers() & Qt::KeyboardModifierMask ) ==
+ ( int )( pattern.state & Qt::KeyboardModifierMask );
+
+ return matched;
+}
+
+/*!
+ \brief Compare a key event with an event pattern.
+
+ A key event matches the pattern when both have the same key
+ value and in the state value the same key flags (Qt::KeyButtonMask)
+ are set.
+
+ \param pattern Index of the event pattern
+ \param event Key event
+ \return true if matches
+
+ \sa mouseMatch()
+*/
+bool QwtEventPattern::keyMatch( uint pattern,
+ const QKeyEvent *event ) const
+{
+ bool ok = false;
+
+ if ( event && pattern < ( uint )d_keyPattern.count() )
+ ok = keyMatch( d_keyPattern[int( pattern )], event );
+
+ return ok;
+}
+
+/*!
+ \brief Compare a key event with an event pattern.
+
+ A key event matches the pattern when both have the same key
+ value and in the state value the same key flags (Qt::KeyButtonMask)
+ are set.
+
+ \param pattern Key event pattern
+ \param event Key event
+ \return true if matches
+
+ \sa mouseMatch()
+*/
+
+bool QwtEventPattern::keyMatch(
+ const KeyPattern &pattern, const QKeyEvent *event ) const
+{
+ if ( event->key() != pattern.key )
+ return false;
+
+ const bool matched =
+ ( event->modifiers() & Qt::KeyboardModifierMask ) ==
+ ( int )( pattern.state & Qt::KeyboardModifierMask );
+
+ return matched;
+}
diff --git a/src/libpcp_qwt/src/qwt_event_pattern.h b/src/libpcp_qwt/src/qwt_event_pattern.h
new file mode 100644
index 0000000..a88f5f4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_event_pattern.h
@@ -0,0 +1,225 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_EVENT_PATTERN
+#define QWT_EVENT_PATTERN 1
+
+#include "qwt_global.h"
+#include <qnamespace.h>
+#include <qvector.h>
+
+class QMouseEvent;
+class QKeyEvent;
+
+/*!
+ \brief A collection of event patterns
+
+ QwtEventPattern introduces an level of indirection for mouse and
+ keyboard inputs. Those are represented by symbolic names, so
+ the application code can be configured by individual mappings.
+
+ \sa QwtPicker, QwtPickerMachine, QwtPlotZoomer
+*/
+class QWT_EXPORT QwtEventPattern
+{
+public:
+ /*!
+ \brief Symbolic mouse input codes
+
+ The default initialization for 3 button mice is:
+ - MouseSelect1\n
+ Qt::LeftButton
+ - MouseSelect2\n
+ Qt::RightButton
+ - MouseSelect3\n
+ Qt::MidButton
+ - MouseSelect4\n
+ Qt::LeftButton + Qt::ShiftButton
+ - MouseSelect5\n
+ Qt::RightButton + Qt::ShiftButton
+ - MouseSelect6\n
+ Qt::MidButton + Qt::ShiftButton
+
+ The default initialization for 2 button mice is:
+ - MouseSelect1\n
+ Qt::LeftButton
+ - MouseSelect2\n
+ Qt::RightButton
+ - MouseSelect3\n
+ Qt::LeftButton + Qt::AltButton
+ - MouseSelect4\n
+ Qt::LeftButton + Qt::ShiftButton
+ - MouseSelect5\n
+ Qt::RightButton + Qt::ShiftButton
+ - MouseSelect6\n
+ Qt::LeftButton + Qt::AltButton + Qt::ShiftButton
+
+ The default initialization for 1 button mice is:
+ - MouseSelect1\n
+ Qt::LeftButton
+ - MouseSelect2\n
+ Qt::LeftButton + Qt::ControlButton
+ - MouseSelect3\n
+ Qt::LeftButton + Qt::AltButton
+ - MouseSelect4\n
+ Qt::LeftButton + Qt::ShiftButton
+ - MouseSelect5\n
+ Qt::LeftButton + Qt::ControlButton + Qt::ShiftButton
+ - MouseSelect6\n
+ Qt::LeftButton + Qt::AltButton + Qt::ShiftButton
+
+ \sa initMousePattern()
+ */
+
+ enum MousePatternCode
+ {
+ MouseSelect1,
+ MouseSelect2,
+ MouseSelect3,
+ MouseSelect4,
+ MouseSelect5,
+ MouseSelect6,
+
+ MousePatternCount
+ };
+
+ /*!
+ \brief Symbolic keyboard input codes
+
+ Default initialization:
+ - KeySelect1\n
+ Qt::Key_Return
+ - KeySelect2\n
+ Qt::Key_Space
+ - KeyAbort\n
+ Qt::Key_Escape
+
+ - KeyLeft\n
+ Qt::Key_Left
+ - KeyRight\n
+ Qt::Key_Right
+ - KeyUp\n
+ Qt::Key_Up
+ - KeyDown\n
+ Qt::Key_Down
+
+ - KeyUndo\n
+ Qt::Key_Minus
+ - KeyRedo\n
+ Qt::Key_Plus
+ - KeyHome\n
+ Qt::Key_Escape
+ */
+ enum KeyPatternCode
+ {
+ KeySelect1,
+ KeySelect2,
+ KeyAbort,
+
+ KeyLeft,
+ KeyRight,
+ KeyUp,
+ KeyDown,
+
+ KeyRedo,
+ KeyUndo,
+ KeyHome,
+
+ KeyPatternCount
+ };
+
+ //! A pattern for mouse events
+ class MousePattern
+ {
+ public:
+ //! Constructor
+ MousePattern( int btn = Qt::NoButton, int st = Qt::NoButton )
+ {
+ button = btn;
+ state = st;
+ }
+
+ //! Button code
+ int button;
+
+ //! State
+ int state;
+ };
+
+ //! A pattern for key events
+ class KeyPattern
+ {
+ public:
+ //! Constructor
+ KeyPattern( int k = 0, int st = Qt::NoButton )
+ {
+ key = k;
+ state = st;
+ }
+
+ //! Key code
+ int key;
+
+ //! State
+ int state;
+ };
+
+ QwtEventPattern();
+ virtual ~QwtEventPattern();
+
+ void initMousePattern( int numButtons );
+ void initKeyPattern();
+
+ void setMousePattern( uint pattern, int button, int state = Qt::NoButton );
+ void setKeyPattern( uint pattern, int key, int state = Qt::NoButton );
+
+ void setMousePattern( const QVector<MousePattern> & );
+ void setKeyPattern( const QVector<KeyPattern> & );
+
+ const QVector<MousePattern> &mousePattern() const;
+ const QVector<KeyPattern> &keyPattern() const;
+
+ QVector<MousePattern> &mousePattern();
+ QVector<KeyPattern> &keyPattern();
+
+ bool mouseMatch( uint pattern, const QMouseEvent * ) const;
+ bool keyMatch( uint pattern, const QKeyEvent * ) const;
+
+protected:
+ virtual bool mouseMatch( const MousePattern &, const QMouseEvent * ) const;
+ virtual bool keyMatch( const KeyPattern &, const QKeyEvent * ) const;
+
+private:
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable: 4251)
+#endif
+ QVector<MousePattern> d_mousePattern;
+ QVector<KeyPattern> d_keyPattern;
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+};
+
+//! Compare operator
+inline bool operator==( QwtEventPattern::MousePattern b1,
+ QwtEventPattern::MousePattern b2 )
+{
+ return b1.button == b2.button && b1.state == b2.state;
+}
+
+//! Compare operator
+inline bool operator==( QwtEventPattern::KeyPattern b1,
+ QwtEventPattern::KeyPattern b2 )
+{
+ return b1.key == b2.key && b1.state == b2.state;
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_global.h b/src/libpcp_qwt/src/qwt_global.h
new file mode 100644
index 0000000..6b3f830
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_global.h
@@ -0,0 +1,41 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_GLOBAL_H
+#define QWT_GLOBAL_H
+
+#include <qglobal.h>
+
+// QWT_VERSION is (major << 16) + (minor << 8) + patch.
+
+#define QWT_VERSION 0x060002
+#define QWT_VERSION_STR "6.0.2"
+
+#if defined(_MSC_VER) /* MSVC Compiler */
+/* template-class specialization 'identifier' is already instantiated */
+#pragma warning(disable: 4660)
+#endif // _MSC_VER
+
+#ifdef QWT_DLL
+
+#if defined(QWT_MAKEDLL) // create a Qwt DLL library
+#define QWT_EXPORT Q_DECL_EXPORT
+#else // use a Qwt DLL library
+#define QWT_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // QWT_DLL
+
+#ifndef QWT_EXPORT
+#define QWT_EXPORT
+#endif
+
+// #define QWT_NO_COMPAT 1 // disable withdrawn functionality
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_interval.cpp b/src/libpcp_qwt/src/qwt_interval.cpp
new file mode 100644
index 0000000..f835567
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_interval.cpp
@@ -0,0 +1,334 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_interval.h"
+#include "qwt_math.h"
+#include <qalgorithms.h>
+
+/*!
+ \brief Normalize the limits of the interval
+
+ If maxValue() < minValue() the limits will be inverted.
+ \return Normalized interval
+
+ \sa isValid(), inverted()
+*/
+QwtInterval QwtInterval::normalized() const
+{
+ if ( d_minValue > d_maxValue )
+ {
+ return inverted();
+ }
+ if ( d_minValue == d_maxValue && d_borderFlags == ExcludeMinimum )
+ {
+ return inverted();
+ }
+
+ return *this;
+}
+
+/*!
+ Invert the limits of the interval
+ \return Inverted interval
+ \sa normalized()
+*/
+QwtInterval QwtInterval::inverted() const
+{
+ BorderFlags borderFlags = IncludeBorders;
+ if ( d_borderFlags & ExcludeMinimum )
+ borderFlags |= ExcludeMaximum;
+ if ( d_borderFlags & ExcludeMaximum )
+ borderFlags |= ExcludeMinimum;
+
+ return QwtInterval( d_maxValue, d_minValue, borderFlags );
+}
+
+/*!
+ Test if a value is inside an interval
+
+ \param value Value
+ \return true, if value >= minValue() && value <= maxValue()
+*/
+bool QwtInterval::contains( double value ) const
+{
+ if ( !isValid() )
+ return false;
+
+ if ( value < d_minValue || value > d_maxValue )
+ return false;
+
+ if ( value == d_minValue && d_borderFlags & ExcludeMinimum )
+ return false;
+
+ if ( value == d_maxValue && d_borderFlags & ExcludeMaximum )
+ return false;
+
+ return true;
+}
+
+//! Unite 2 intervals
+QwtInterval QwtInterval::unite( const QwtInterval &other ) const
+{
+ /*
+ If one of the intervals is invalid return the other one.
+ If both are invalid return an invalid default interval
+ */
+ if ( !isValid() )
+ {
+ if ( !other.isValid() )
+ return QwtInterval();
+ else
+ return other;
+ }
+ if ( !other.isValid() )
+ return *this;
+
+ QwtInterval united;
+ BorderFlags flags = IncludeBorders;
+
+ // minimum
+ if ( d_minValue < other.minValue() )
+ {
+ united.setMinValue( d_minValue );
+ flags &= d_borderFlags & ExcludeMinimum;
+ }
+ else if ( other.minValue() < d_minValue )
+ {
+ united.setMinValue( other.minValue() );
+ flags &= other.borderFlags() & ExcludeMinimum;
+ }
+ else // d_minValue == other.minValue()
+ {
+ united.setMinValue( d_minValue );
+ flags &= ( d_borderFlags & other.borderFlags() ) & ExcludeMinimum;
+ }
+
+ // maximum
+ if ( d_maxValue > other.maxValue() )
+ {
+ united.setMaxValue( d_maxValue );
+ flags &= d_borderFlags & ExcludeMaximum;
+ }
+ else if ( other.maxValue() > d_maxValue )
+ {
+ united.setMaxValue( other.maxValue() );
+ flags &= other.borderFlags() & ExcludeMaximum;
+ }
+ else // d_maxValue == other.maxValue() )
+ {
+ united.setMaxValue( d_maxValue );
+ flags &= d_borderFlags & other.borderFlags() & ExcludeMaximum;
+ }
+
+ united.setBorderFlags( flags );
+ return united;
+}
+
+//! Intersect 2 intervals
+QwtInterval QwtInterval::intersect( const QwtInterval &other ) const
+{
+ if ( !other.isValid() || !isValid() )
+ return QwtInterval();
+
+ QwtInterval i1 = *this;
+ QwtInterval i2 = other;
+
+ // swap i1/i2, so that the minimum of i1
+ // is smaller then the minimum of i2
+
+ if ( i1.minValue() > i2.minValue() )
+ {
+ qSwap( i1, i2 );
+ }
+ else if ( i1.minValue() == i2.minValue() )
+ {
+ if ( i1.borderFlags() & ExcludeMinimum )
+ qSwap( i1, i2 );
+ }
+
+ if ( i1.maxValue() < i2.minValue() )
+ {
+ return QwtInterval();
+ }
+
+ if ( i1.maxValue() == i2.minValue() )
+ {
+ if ( i1.borderFlags() & ExcludeMaximum ||
+ i2.borderFlags() & ExcludeMinimum )
+ {
+ return QwtInterval();
+ }
+ }
+
+ QwtInterval intersected;
+ BorderFlags flags = IncludeBorders;
+
+ intersected.setMinValue( i2.minValue() );
+ flags |= i2.borderFlags() & ExcludeMinimum;
+
+ if ( i1.maxValue() < i2.maxValue() )
+ {
+ intersected.setMaxValue( i1.maxValue() );
+ flags |= i1.borderFlags() & ExcludeMaximum;
+ }
+ else if ( i2.maxValue() < i1.maxValue() )
+ {
+ intersected.setMaxValue( i2.maxValue() );
+ flags |= i2.borderFlags() & ExcludeMaximum;
+ }
+ else // i1.maxValue() == i2.maxValue()
+ {
+ intersected.setMaxValue( i1.maxValue() );
+ flags |= i1.borderFlags() & i2.borderFlags() & ExcludeMaximum;
+ }
+
+ intersected.setBorderFlags( flags );
+ return intersected;
+}
+
+//! Unites this interval with the given interval.
+QwtInterval& QwtInterval::operator|=( const QwtInterval & interval )
+{
+ *this = *this | interval;
+ return *this;
+}
+
+//! Intersects this interval with the given interval.
+QwtInterval& QwtInterval::operator&=( const QwtInterval & interval )
+{
+ *this = *this & interval;
+ return *this;
+}
+
+/*!
+ Test if two intervals overlap
+*/
+bool QwtInterval::intersects( const QwtInterval &other ) const
+{
+ if ( !isValid() || !other.isValid() )
+ return false;
+
+ QwtInterval i1 = *this;
+ QwtInterval i2 = other;
+
+ // swap i1/i2, so that the minimum of i1
+ // is smaller then the minimum of i2
+
+ if ( i1.minValue() > i2.minValue() )
+ {
+ qSwap( i1, i2 );
+ }
+ else if ( i1.minValue() == i2.minValue() &&
+ i1.borderFlags() & ExcludeMinimum )
+ {
+ qSwap( i1, i2 );
+ }
+
+ if ( i1.maxValue() > i2.minValue() )
+ {
+ return true;
+ }
+ if ( i1.maxValue() == i2.minValue() )
+ {
+ return !( ( i1.borderFlags() & ExcludeMaximum ) ||
+ ( i2.borderFlags() & ExcludeMinimum ) );
+ }
+ return false;
+}
+
+/*!
+ Adjust the limit that is closer to value, so that value becomes
+ the center of the interval.
+
+ \param value Center
+ \return Interval with value as center
+*/
+QwtInterval QwtInterval::symmetrize( double value ) const
+{
+ if ( !isValid() )
+ return *this;
+
+ const double delta =
+ qMax( qAbs( value - d_maxValue ), qAbs( value - d_minValue ) );
+
+ return QwtInterval( value - delta, value + delta );
+}
+
+/*!
+ Limit the interval, keeping the border modes
+
+ \param lowerBound Lower limit
+ \param upperBound Upper limit
+
+ \return Limited interval
+*/
+QwtInterval QwtInterval::limited( double lowerBound, double upperBound ) const
+{
+ if ( !isValid() || lowerBound > upperBound )
+ return QwtInterval();
+
+ double minValue = qMax( d_minValue, lowerBound );
+ minValue = qMin( minValue, upperBound );
+
+ double maxValue = qMax( d_maxValue, lowerBound );
+ maxValue = qMin( maxValue, upperBound );
+
+ return QwtInterval( minValue, maxValue, d_borderFlags );
+}
+
+/*!
+ Extend the interval
+
+ If value is below minValue, value becomes the lower limit.
+ If value is above maxValue, value becomes the upper limit.
+
+ extend has no effect for invalid intervals
+
+ \param value Value
+ \sa isValid()
+*/
+QwtInterval QwtInterval::extend( double value ) const
+{
+ if ( !isValid() )
+ return *this;
+
+ return QwtInterval( qMin( value, d_minValue ),
+ qMax( value, d_maxValue ), d_borderFlags );
+}
+
+/*!
+ Extend an interval
+
+ \param value Value
+ \return Reference of the extended interval
+
+ \sa extend()
+*/
+QwtInterval& QwtInterval::operator|=( double value )
+{
+ *this = *this | value;
+ return *this;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+
+QDebug operator<<( QDebug debug, const QwtInterval &interval )
+{
+ const int flags = interval.borderFlags();
+
+ debug.nospace() << "QwtInterval("
+ << ( ( flags & QwtInterval::ExcludeMinimum ) ? "]" : "[" )
+ << interval.minValue() << "," << interval.maxValue()
+ << ( ( flags & QwtInterval::ExcludeMaximum ) ? "[" : "]" )
+ << ")";
+
+ return debug.space();
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_interval.h b/src/libpcp_qwt/src/qwt_interval.h
new file mode 100644
index 0000000..6e30920
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_interval.h
@@ -0,0 +1,296 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_INTERVAL_H
+#define QWT_INTERVAL_H
+
+#include "qwt_global.h"
+#ifndef QT_NO_DEBUG_STREAM
+#include <qdebug.h>
+#endif
+
+/*!
+ \brief A class representing an interval
+
+ The interval is represented by 2 doubles, the lower and the upper limit.
+*/
+
+class QWT_EXPORT QwtInterval
+{
+public:
+ /*!
+ Flag indicating if a border is included or excluded
+ \sa setBorderFlags(), borderFlags()
+ */
+ enum BorderFlag
+ {
+ //! Min/Max values are inside the interval
+ IncludeBorders = 0x00,
+
+ //! Min value is not included in the interval
+ ExcludeMinimum = 0x01,
+
+ //! Max value is not included in the interval
+ ExcludeMaximum = 0x02,
+
+ //! Min/Max values are not included in the interval
+ ExcludeBorders = ExcludeMinimum | ExcludeMaximum
+ };
+
+ //! Border flags
+ typedef QFlags<BorderFlag> BorderFlags;
+
+ QwtInterval();
+ QwtInterval( double minValue, double maxValue,
+ BorderFlags = IncludeBorders );
+
+ void setInterval( double minValue, double maxValue,
+ BorderFlags = IncludeBorders );
+
+ QwtInterval normalized() const;
+ QwtInterval inverted() const;
+ QwtInterval limited( double minValue, double maxValue ) const;
+
+ bool operator==( const QwtInterval & ) const;
+ bool operator!=( const QwtInterval & ) const;
+
+ void setBorderFlags( BorderFlags );
+ BorderFlags borderFlags() const;
+
+ double minValue() const;
+ double maxValue() const;
+
+ double width() const;
+
+ void setMinValue( double );
+ void setMaxValue( double );
+
+ bool contains( double value ) const;
+
+ bool intersects( const QwtInterval & ) const;
+ QwtInterval intersect( const QwtInterval & ) const;
+ QwtInterval unite( const QwtInterval & ) const;
+
+ QwtInterval operator|( const QwtInterval & ) const;
+ QwtInterval operator&( const QwtInterval & ) const;
+
+ QwtInterval &operator|=( const QwtInterval & );
+ QwtInterval &operator&=( const QwtInterval & );
+
+ QwtInterval extend( double value ) const;
+ QwtInterval operator|( double ) const;
+ QwtInterval &operator|=( double );
+
+ bool isValid() const;
+ bool isNull() const;
+ void invalidate();
+
+ QwtInterval symmetrize( double value ) const;
+
+private:
+ double d_minValue;
+ double d_maxValue;
+ BorderFlags d_borderFlags;
+};
+
+Q_DECLARE_TYPEINFO(QwtInterval, Q_MOVABLE_TYPE);
+
+/*!
+ \brief Default Constructor
+
+ Creates an invalid interval [0.0, -1.0]
+ \sa setInterval(), isValid()
+*/
+inline QwtInterval::QwtInterval():
+ d_minValue( 0.0 ),
+ d_maxValue( -1.0 ),
+ d_borderFlags( IncludeBorders )
+{
+}
+
+/*!
+ Constructor
+
+ Build an interval with from min/max values
+
+ \param minValue Minimum value
+ \param maxValue Maximum value
+ \param borderFlags Include/Exclude borders
+*/
+inline QwtInterval::QwtInterval(
+ double minValue, double maxValue, BorderFlags borderFlags ):
+ d_minValue( minValue ),
+ d_maxValue( maxValue ),
+ d_borderFlags( borderFlags )
+{
+}
+
+/*!
+ Assign the limits of the interval
+
+ \param minValue Minimum value
+ \param maxValue Maximum value
+ \param borderFlags Include/Exclude borders
+*/
+inline void QwtInterval::setInterval(
+ double minValue, double maxValue, BorderFlags borderFlags )
+{
+ d_minValue = minValue;
+ d_maxValue = maxValue;
+ d_borderFlags = borderFlags;
+}
+
+/*!
+ Change the border flags
+
+ \param borderFlags Or'd BorderMode flags
+ \sa borderFlags()
+*/
+inline void QwtInterval::setBorderFlags( BorderFlags borderFlags )
+{
+ d_borderFlags = borderFlags;
+}
+
+/*!
+ \return Border flags
+ \sa setBorderFlags()
+*/
+inline QwtInterval::BorderFlags QwtInterval::borderFlags() const
+{
+ return d_borderFlags;
+}
+
+/*!
+ Assign the lower limit of the interval
+
+ \param minValue Minimum value
+*/
+inline void QwtInterval::setMinValue( double minValue )
+{
+ d_minValue = minValue;
+}
+
+/*!
+ Assign the upper limit of the interval
+
+ \param maxValue Maximum value
+*/
+inline void QwtInterval::setMaxValue( double maxValue )
+{
+ d_maxValue = maxValue;
+}
+
+//! \return Lower limit of the interval
+inline double QwtInterval::minValue() const
+{
+ return d_minValue;
+}
+
+//! \return Upper limit of the interval
+inline double QwtInterval::maxValue() const
+{
+ return d_maxValue;
+}
+
+/*!
+ A interval is valid when minValue() <= maxValue().
+ In case of QwtInterval::ExcludeBorders it is true
+ when minValue() < maxValue()
+*/
+inline bool QwtInterval::isValid() const
+{
+ if ( ( d_borderFlags & ExcludeBorders ) == 0 )
+ return d_minValue <= d_maxValue;
+ else
+ return d_minValue < d_maxValue;
+}
+
+/*!
+ Return the width of an interval
+ The width of invalid intervals is 0.0, otherwise the result is
+ maxValue() - minValue().
+
+ \sa isValid()
+*/
+inline double QwtInterval::width() const
+{
+ return isValid() ? ( d_maxValue - d_minValue ) : 0.0;
+}
+
+/*!
+ Intersection of two intervals
+ \sa intersect()
+*/
+inline QwtInterval QwtInterval::operator&(
+ const QwtInterval &interval ) const
+{
+ return intersect( interval );
+}
+
+/*!
+ Union of two intervals
+ \sa unite()
+*/
+inline QwtInterval QwtInterval::operator|(
+ const QwtInterval &interval ) const
+{
+ return unite( interval );
+}
+
+//! Compare two intervals
+inline bool QwtInterval::operator==( const QwtInterval &other ) const
+{
+ return ( d_minValue == other.d_minValue ) &&
+ ( d_maxValue == other.d_maxValue ) &&
+ ( d_borderFlags == other.d_borderFlags );
+}
+
+//! Compare two intervals
+inline bool QwtInterval::operator!=( const QwtInterval &other ) const
+{
+ return ( !( *this == other ) );
+}
+
+/*!
+ Extend an interval
+
+ \param value Value
+ \return Extended interval
+ \sa extend()
+*/
+inline QwtInterval QwtInterval::operator|( double value ) const
+{
+ return extend( value );
+}
+
+//! \return true, if isValid() && (minValue() >= maxValue())
+inline bool QwtInterval::isNull() const
+{
+ return isValid() && d_minValue >= d_maxValue;
+}
+
+/*!
+ Invalidate the interval
+
+ The limits are set to interval [0.0, -1.0]
+ \sa isValid()
+*/
+inline void QwtInterval::invalidate()
+{
+ d_minValue = 0.0;
+ d_maxValue = -1.0;
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtInterval::BorderFlags )
+
+#ifndef QT_NO_DEBUG_STREAM
+QWT_EXPORT QDebug operator<<( QDebug, const QwtInterval & );
+#endif
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_interval_symbol.cpp b/src/libpcp_qwt/src/qwt_interval_symbol.cpp
new file mode 100644
index 0000000..e3cef16
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_interval_symbol.cpp
@@ -0,0 +1,300 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_interval_symbol.h"
+#include "qwt_painter.h"
+#include "qwt_math.h"
+#include <qpainter.h>
+
+#if QT_VERSION < 0x040601
+#define qAtan2(y, x) ::atan2(y, x)
+#define qFastSin(x) qSin(x)
+#define qFastCos(x) qCos(x)
+#endif
+
+class QwtIntervalSymbol::PrivateData
+{
+public:
+ PrivateData():
+ style( QwtIntervalSymbol::NoSymbol ),
+ width( 6 )
+ {
+ }
+
+ bool operator==( const PrivateData &other ) const
+ {
+ return ( style == other.style )
+ && ( width == other.width )
+ && ( brush == other.brush )
+ && ( pen == other.pen );
+ }
+
+ QwtIntervalSymbol::Style style;
+ int width;
+
+ QPen pen;
+ QBrush brush;
+};
+
+/*!
+ Constructor
+
+ \param style Style of the symbol
+ \sa setStyle(), style(), Style
+*/
+QwtIntervalSymbol::QwtIntervalSymbol( Style style )
+{
+ d_data = new PrivateData();
+ d_data->style = style;
+}
+
+//! Copy constructor
+QwtIntervalSymbol::QwtIntervalSymbol( const QwtIntervalSymbol &other )
+{
+ d_data = new PrivateData();
+ *d_data = *other.d_data;
+}
+
+//! Destructor
+QwtIntervalSymbol::~QwtIntervalSymbol()
+{
+ delete d_data;
+}
+
+//! \brief Assignment operator
+QwtIntervalSymbol &QwtIntervalSymbol::operator=(
+ const QwtIntervalSymbol &other )
+{
+ *d_data = *other.d_data;
+ return *this;
+}
+
+//! \brief Compare two symbols
+bool QwtIntervalSymbol::operator==(
+ const QwtIntervalSymbol &other ) const
+{
+ return *d_data == *other.d_data;
+}
+
+//! \brief Compare two symbols
+bool QwtIntervalSymbol::operator!=(
+ const QwtIntervalSymbol &other ) const
+{
+ return !( *d_data == *other.d_data );
+}
+
+/*!
+ Specify the symbol style
+
+ \param style Style
+ \sa style(), Style
+*/
+void QwtIntervalSymbol::setStyle( Style style )
+{
+ d_data->style = style;
+}
+
+/*!
+ \return Current symbol style
+ \sa setStyle()
+*/
+QwtIntervalSymbol::Style QwtIntervalSymbol::style() const
+{
+ return d_data->style;
+}
+
+/*!
+ Specify the width of the symbol
+ It is used depending on the style.
+
+ \param width Width
+ \sa width(), setStyle()
+*/
+void QwtIntervalSymbol::setWidth( int width )
+{
+ d_data->width = width;
+}
+
+/*!
+ \return Width of the symbol.
+ \sa setWidth(), setStyle()
+*/
+int QwtIntervalSymbol::width() const
+{
+ return d_data->width;
+}
+
+/*!
+ \brief Assign a brush
+
+ The brush is used for the Box style.
+
+ \param brush Brush
+ \sa brush()
+*/
+void QwtIntervalSymbol::setBrush( const QBrush &brush )
+{
+ d_data->brush = brush;
+}
+
+/*!
+ \return Brush
+ \sa setBrush()
+*/
+const QBrush& QwtIntervalSymbol::brush() const
+{
+ return d_data->brush;
+}
+
+/*!
+ Assign a pen
+
+ \param pen Pen
+ \sa pen(), setBrush()
+*/
+void QwtIntervalSymbol::setPen( const QPen &pen )
+{
+ d_data->pen = pen;
+}
+
+/*!
+ \return Pen
+ \sa setPen(), brush()
+*/
+const QPen& QwtIntervalSymbol::pen() const
+{
+ return d_data->pen;
+}
+
+/*!
+ Draw a symbol depending on its style
+
+ \param painter Painter
+ \param orientation Orientation
+ \param from Start point of the interval in target device coordinates
+ \param to End point of the interval in target device coordinates
+
+ \sa setStyle()
+*/
+void QwtIntervalSymbol::draw( QPainter *painter, Qt::Orientation orientation,
+ const QPointF &from, const QPointF &to ) const
+{
+ const qreal pw = qMax( painter->pen().widthF(), qreal( 1.0 ) );
+
+ QPointF p1 = from;
+ QPointF p2 = to;
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ p1 = p1.toPoint();
+ p2 = p2.toPoint();
+ }
+
+ switch ( d_data->style )
+ {
+ case QwtIntervalSymbol::Bar:
+ {
+ QwtPainter::drawLine( painter, p1, p2 );
+ if ( d_data->width > pw )
+ {
+ if ( ( orientation == Qt::Horizontal )
+ && ( p1.y() == p2.y() ) )
+ {
+ const double sw = d_data->width;
+
+ const double y = p1.y() - sw / 2;
+ QwtPainter::drawLine( painter,
+ p1.x(), y, p1.x(), y + sw );
+ QwtPainter::drawLine( painter,
+ p2.x(), y, p2.x(), y + sw );
+ }
+ else if ( ( orientation == Qt::Vertical )
+ && ( p1.x() == p2.x() ) )
+ {
+ const double sw = d_data->width;
+
+ const double x = p1.x() - sw / 2;
+ QwtPainter::drawLine( painter,
+ x, p1.y(), x + sw, p1.y() );
+ QwtPainter::drawLine( painter,
+ x, p2.y(), x + sw, p2.y() );
+ }
+ else
+ {
+ const double sw = d_data->width;
+
+ const double dx = p2.x() - p1.x();
+ const double dy = p2.y() - p1.y();
+ const double angle = qAtan2( dy, dx ) + M_PI_2;
+ double dw2 = sw / 2.0;
+
+ const double cx = qFastCos( angle ) * dw2;
+ const double sy = qFastSin( angle ) * dw2;
+
+ QwtPainter::drawLine( painter,
+ p1.x() - cx, p1.y() - sy,
+ p1.x() + cx, p1.y() + sy );
+ QwtPainter::drawLine( painter,
+ p2.x() - cx, p2.y() - sy,
+ p2.x() + cx, p2.y() + sy );
+ }
+ }
+ break;
+ }
+ case QwtIntervalSymbol::Box:
+ {
+ if ( d_data->width <= pw )
+ {
+ QwtPainter::drawLine( painter, p1, p2 );
+ }
+ else
+ {
+ if ( ( orientation == Qt::Horizontal )
+ && ( p1.y() == p2.y() ) )
+ {
+ const double sw = d_data->width;
+
+ const double y = p1.y() - d_data->width / 2;
+ QwtPainter::drawRect( painter,
+ p1.x(), y, p2.x() - p1.x(), sw );
+ }
+ else if ( ( orientation == Qt::Vertical )
+ && ( p1.x() == p2.x() ) )
+ {
+ const double sw = d_data->width;
+
+ const double x = p1.x() - d_data->width / 2;
+ QwtPainter::drawRect( painter,
+ x, p1.y(), sw, p2.y() - p1.y() );
+ }
+ else
+ {
+ const double sw = d_data->width;
+
+ const double dx = p2.x() - p1.x();
+ const double dy = p2.y() - p1.y();
+ const double angle = qAtan2( dy, dx ) + M_PI_2;
+ double dw2 = sw / 2.0;
+
+ const double cx = qFastCos( angle ) * dw2;
+ const double sy = qFastSin( angle ) * dw2;
+
+ QPolygonF polygon;
+ polygon += QPointF( p1.x() - cx, p1.y() - sy );
+ polygon += QPointF( p1.x() + cx, p1.y() + sy );
+ polygon += QPointF( p2.x() + cx, p2.y() + sy );
+ polygon += QPointF( p2.x() - cx, p2.y() - sy );
+
+ QwtPainter::drawPolygon( painter, polygon );
+ }
+ }
+ break;
+ }
+ default:;
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_interval_symbol.h b/src/libpcp_qwt/src/qwt_interval_symbol.h
new file mode 100644
index 0000000..3ea4fe4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_interval_symbol.h
@@ -0,0 +1,86 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_INTERVAL_SYMBOL_H
+#define QWT_INTERVAL_SYMBOL_H
+
+#include "qwt_global.h"
+#include <qpen.h>
+#include <qsize.h>
+
+class QPainter;
+class QRect;
+class QPointF;
+
+/*!
+ \brief A drawing primitive for displaying an interval like an error bar
+
+ \sa QwtPlotIntervalCurve
+*/
+class QWT_EXPORT QwtIntervalSymbol
+{
+public:
+ //! Symbol style
+ enum Style
+ {
+ //! No Style. The symbol cannot be drawn.
+ NoSymbol = -1,
+
+ /*!
+ The symbol displays a line with caps at the beginning/end.
+ The size of the caps depends on the symbol width().
+ */
+ Bar,
+
+ /*!
+ The symbol displays a plain rectangle using pen() and brush().
+ The size of the rectangle depends on the translated interval and
+ the width(),
+ */
+ Box,
+
+ /*!
+ Styles >= UserSymbol are reserved for derived
+ classes of QwtIntervalSymbol that overload draw() with
+ additional application specific symbol types.
+ */
+ UserSymbol = 1000
+ };
+
+public:
+ QwtIntervalSymbol( Style = NoSymbol );
+ QwtIntervalSymbol( const QwtIntervalSymbol & );
+ virtual ~QwtIntervalSymbol();
+
+ QwtIntervalSymbol &operator=( const QwtIntervalSymbol & );
+ bool operator==( const QwtIntervalSymbol & ) const;
+ bool operator!=( const QwtIntervalSymbol & ) const;
+
+ void setWidth( int );
+ int width() const;
+
+ void setBrush( const QBrush& b );
+ const QBrush& brush() const;
+
+ void setPen( const QPen & );
+ const QPen& pen() const;
+
+ void setStyle( Style );
+ Style style() const;
+
+ virtual void draw( QPainter *, Qt::Orientation,
+ const QPointF& from, const QPointF& to ) const;
+
+private:
+
+ class PrivateData;
+ PrivateData* d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_knob.cpp b/src/libpcp_qwt/src/qwt_knob.cpp
new file mode 100644
index 0000000..2e41879
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_knob.cpp
@@ -0,0 +1,665 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_knob.h"
+#include "qwt_round_scale_draw.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qevent.h>
+#include <qmath.h>
+#include <qapplication.h>
+
+#if QT_VERSION < 0x040601
+#define qAtan2(y, x) ::atan2(y, x)
+#define qFabs(x) ::fabs(x)
+#define qFastCos(x) qCos(x)
+#define qFastSin(x) qSin(x)
+#endif
+
+class QwtKnob::PrivateData
+{
+public:
+ PrivateData()
+ {
+ angle = 0.0;
+ nTurns = 0.0;
+ borderWidth = 2;
+ borderDist = 4;
+ totalAngle = 270.0;
+ scaleDist = 4;
+ markerStyle = QwtKnob::Notch;
+ maxScaleTicks = 11;
+ knobStyle = QwtKnob::Raised;
+ knobWidth = 50;
+ markerSize = 8;
+ }
+
+ QwtKnob::KnobStyle knobStyle;
+ QwtKnob::MarkerStyle markerStyle;
+
+ int borderWidth;
+ int borderDist;
+ int scaleDist;
+ int maxScaleTicks;
+ int knobWidth;
+ int markerSize;
+
+ double angle;
+ double totalAngle;
+ double nTurns;
+
+ mutable QRectF knobRect; // bounding rect of the knob without scale
+};
+
+/*!
+ Constructor
+ \param parent Parent widget
+*/
+QwtKnob::QwtKnob( QWidget* parent ):
+ QwtAbstractSlider( Qt::Horizontal, parent )
+{
+ initKnob();
+}
+
+void QwtKnob::initKnob()
+{
+ d_data = new PrivateData;
+
+ setScaleDraw( new QwtRoundScaleDraw() );
+
+ setUpdateTime( 50 );
+ setTotalAngle( 270.0 );
+ recalcAngle();
+ setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
+
+ setRange( 0.0, 10.0, 1.0 );
+ setValue( 0.0 );
+}
+
+//! Destructor
+QwtKnob::~QwtKnob()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Set the knob type
+
+ \param knobStyle Knob type
+ \sa knobStyle(), setBorderWidth()
+*/
+void QwtKnob::setKnobStyle( KnobStyle knobStyle )
+{
+ if ( d_data->knobStyle != knobStyle )
+ {
+ d_data->knobStyle = knobStyle;
+ update();
+ }
+}
+
+/*!
+ \return Marker type of the knob
+ \sa setKnobStyle(), setBorderWidth()
+*/
+QwtKnob::KnobStyle QwtKnob::knobStyle() const
+{
+ return d_data->knobStyle;
+}
+
+/*!
+ \brief Set the marker type of the knob
+
+ \param markerStyle Marker type
+ \sa markerStyle(), setMarkerSize()
+*/
+void QwtKnob::setMarkerStyle( MarkerStyle markerStyle )
+{
+ if ( d_data->markerStyle != markerStyle )
+ {
+ d_data->markerStyle = markerStyle;
+ update();
+ }
+}
+
+/*!
+ \return Marker type of the knob
+ \sa setMarkerStyle(), setMarkerSize()
+*/
+QwtKnob::MarkerStyle QwtKnob::markerStyle() const
+{
+ return d_data->markerStyle;
+}
+
+/*!
+ \brief Set the total angle by which the knob can be turned
+ \param angle Angle in degrees.
+
+ The default angle is 270 degrees. It is possible to specify
+ an angle of more than 360 degrees so that the knob can be
+ turned several times around its axis.
+*/
+void QwtKnob::setTotalAngle ( double angle )
+{
+ if ( angle < 10.0 )
+ d_data->totalAngle = 10.0;
+ else
+ d_data->totalAngle = angle;
+
+ scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle,
+ 0.5 * d_data->totalAngle );
+ layoutKnob( true );
+}
+
+//! Return the total angle
+double QwtKnob::totalAngle() const
+{
+ return d_data->totalAngle;
+}
+
+/*!
+ Change the scale draw of the knob
+
+ For changing the labels of the scales, it
+ is necessary to derive from QwtRoundScaleDraw and
+ overload QwtRoundScaleDraw::label().
+
+ \sa scaleDraw()
+*/
+void QwtKnob::setScaleDraw( QwtRoundScaleDraw *scaleDraw )
+{
+ setAbstractScaleDraw( scaleDraw );
+ setTotalAngle( d_data->totalAngle );
+}
+
+/*!
+ \return the scale draw of the knob
+ \sa setScaleDraw()
+*/
+const QwtRoundScaleDraw *QwtKnob::scaleDraw() const
+{
+ return static_cast<const QwtRoundScaleDraw *>( abstractScaleDraw() );
+}
+
+/*!
+ \return the scale draw of the knob
+ \sa setScaleDraw()
+*/
+QwtRoundScaleDraw *QwtKnob::scaleDraw()
+{
+ return static_cast<QwtRoundScaleDraw *>( abstractScaleDraw() );
+}
+
+/*!
+ \brief Notify change of value
+
+ Sets the knob's value to the nearest multiple
+ of the step size.
+*/
+void QwtKnob::valueChange()
+{
+ recalcAngle();
+ update();
+ QwtAbstractSlider::valueChange();
+}
+
+/*!
+ \brief Determine the value corresponding to a specified position
+
+ Called by QwtAbstractSlider
+ \param pos point
+*/
+double QwtKnob::getValue( const QPoint &pos )
+{
+ const double dx = rect().center().x() - pos.x();
+ const double dy = rect().center().y() - pos.y();
+
+ const double arc = qAtan2( -dx, dy ) * 180.0 / M_PI;
+
+ double newValue = 0.5 * ( minValue() + maxValue() )
+ + ( arc + d_data->nTurns * 360.0 ) * ( maxValue() - minValue() )
+ / d_data->totalAngle;
+
+ const double oneTurn = qFabs( maxValue() - minValue() ) * 360.0 / d_data->totalAngle;
+ const double eqValue = value() + mouseOffset();
+
+ if ( qFabs( newValue - eqValue ) > 0.5 * oneTurn )
+ {
+ if ( newValue < eqValue )
+ newValue += oneTurn;
+ else
+ newValue -= oneTurn;
+ }
+
+ return newValue;
+}
+
+/*!
+ \brief Set the scrolling mode and direction
+
+ Called by QwtAbstractSlider
+ \param pos Point in question
+ \param scrollMode Scrolling mode
+ \param direction Direction
+*/
+void QwtKnob::getScrollMode( const QPoint &pos,
+ QwtAbstractSlider::ScrollMode &scrollMode, int &direction ) const
+{
+ const double r = 0.5 * d_data->knobRect.width();
+ const double dx = d_data->knobRect.x() + r - pos.x();
+ const double dy = d_data->knobRect.y() + r - pos.y();
+
+ if ( qwtSqr( dx ) + qwtSqr( dy ) <= qwtSqr( r ) )
+ {
+ // point is inside the knob
+
+ scrollMode = QwtAbstractSlider::ScrMouse;
+ direction = 0;
+ }
+ else // point lies outside
+ {
+ scrollMode = QwtAbstractSlider::ScrTimer;
+
+ double arc = qAtan2( double( -dx ), double( dy ) ) * 180.0 / M_PI;
+ if ( arc < d_data->angle )
+ direction = -1;
+ else if ( arc > d_data->angle )
+ direction = 1;
+ else
+ direction = 0;
+ }
+}
+
+/*!
+ \brief Notify a change of the range
+
+ Called by QwtAbstractSlider
+*/
+void QwtKnob::rangeChange()
+{
+ if ( autoScale() )
+ rescale( minValue(), maxValue() );
+
+ layoutKnob( true );
+ recalcAngle();
+}
+
+/*!
+ Qt Resize Event
+ \param event Resize event
+*/
+void QwtKnob::resizeEvent( QResizeEvent *event )
+{
+ Q_UNUSED( event );
+ layoutKnob( false );
+}
+
+/*!
+ Handle QEvent::StyleChange and QEvent::FontChange;
+ \param event Change event
+*/
+void QwtKnob::changeEvent( QEvent *event )
+{
+ switch( event->type() )
+ {
+ case QEvent::StyleChange:
+ case QEvent::FontChange:
+ layoutKnob( true );
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ Recalculate the knob's geometry and layout based on
+ the current rect and fonts.
+
+ \param update_geometry notify the layout system and call update
+ to redraw the scale
+*/
+void QwtKnob::layoutKnob( bool update_geometry )
+{
+ const double d = d_data->knobWidth;
+
+ d_data->knobRect.setWidth( d );
+ d_data->knobRect.setHeight( d );
+ d_data->knobRect.moveCenter( rect().center() );
+
+ scaleDraw()->setRadius( 0.5 * d + d_data->scaleDist );
+ scaleDraw()->moveCenter( rect().center() );
+
+ if ( update_geometry )
+ {
+ updateGeometry();
+ update();
+ }
+}
+
+/*!
+ Repaint the knob
+ \param event Paint event
+*/
+void QwtKnob::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ painter.setRenderHint( QPainter::Antialiasing, true );
+
+ if ( !d_data->knobRect.contains( event->region().boundingRect() ) )
+ scaleDraw()->draw( &painter, palette() );
+
+ drawKnob( &painter, d_data->knobRect );
+ drawMarker( &painter, d_data->knobRect, d_data->angle );
+
+ painter.setRenderHint( QPainter::Antialiasing, false );
+
+ if ( hasFocus() )
+ QwtPainter::drawFocusRect( &painter, this );
+}
+
+/*!
+ \brief Draw the knob
+ \param painter painter
+ \param knobRect Bounding rectangle of the knob (without scale)
+*/
+void QwtKnob::drawKnob( QPainter *painter, const QRectF &knobRect ) const
+{
+ double dim = qMin( knobRect.width(), knobRect.height() );
+ dim -= d_data->borderWidth * 0.5;
+
+ QRectF aRect( 0, 0, dim, dim );
+ aRect.moveCenter( knobRect.center() );
+
+ QPen pen( Qt::NoPen );
+ if ( d_data->borderWidth > 0 )
+ {
+ QColor c1 = palette().color( QPalette::Light );
+ QColor c2 = palette().color( QPalette::Dark );
+
+ QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 0.3, c1 );
+ gradient.setColorAt( 0.7, c2 );
+ gradient.setColorAt( 1.0, c2 );
+
+ pen = QPen( gradient, d_data->borderWidth );
+ }
+
+ QBrush brush;
+ switch( d_data->knobStyle )
+ {
+ case QwtKnob::Raised:
+ {
+ double off = 0.3 * knobRect.width();
+ QRadialGradient gradient( knobRect.center(),
+ knobRect.width(), knobRect.topLeft() + QPointF( off, off ) );
+
+ gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) );
+ gradient.setColorAt( 1.0, palette().color( QPalette::Button ) );
+
+ brush = QBrush( gradient );
+
+ break;
+ }
+ case QwtKnob::Sunken:
+ {
+ QLinearGradient gradient(
+ knobRect.topLeft(), knobRect.bottomRight() );
+ gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) );
+ gradient.setColorAt( 0.5, palette().color( QPalette::Button ) );
+ gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) );
+ brush = QBrush( gradient );
+
+ break;
+ }
+ default:
+ brush = palette().brush( QPalette::Button );
+ }
+
+ painter->setPen( pen );
+ painter->setBrush( brush );
+ painter->drawEllipse( aRect );
+}
+
+
+/*!
+ \brief Draw the marker at the knob's front
+ \param painter Painter
+ \param rect Bounding rectangle of the knob without scale
+ \param angle Angle of the marker in degrees
+*/
+void QwtKnob::drawMarker( QPainter *painter,
+ const QRectF &rect, double angle ) const
+{
+ if ( d_data->markerStyle == NoMarker || !isValid() )
+ return;
+
+ const double radians = angle * M_PI / 180.0;
+ const double sinA = -qFastSin( radians );
+ const double cosA = qFastCos( radians );
+
+ const double xm = rect.center().x();
+ const double ym = rect.center().y();
+ const double margin = 4.0;
+
+ double radius = 0.5 * ( rect.width() - d_data->borderWidth ) - margin;
+ if ( radius < 1.0 )
+ radius = 1.0;
+
+ switch ( d_data->markerStyle )
+ {
+ case Notch:
+ case Nub:
+ {
+ const double dotWidth =
+ qMin( double( d_data->markerSize ), radius);
+
+ const double dotCenterDist = radius - 0.5 * dotWidth;
+ if ( dotCenterDist > 0.0 )
+ {
+ const QPointF center( xm - sinA * dotCenterDist,
+ ym - cosA * dotCenterDist );
+
+ QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
+ ellipse.moveCenter( center );
+
+ QColor c1 = palette().color( QPalette::Light );
+ QColor c2 = palette().color( QPalette::Mid );
+
+ if ( d_data->markerStyle == Notch )
+ qSwap( c1, c2 );
+
+ QLinearGradient gradient(
+ ellipse.topLeft(), ellipse.bottomRight() );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 1.0, c2 );
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( gradient );
+
+ painter->drawEllipse( ellipse );
+ }
+ break;
+ }
+ case Dot:
+ {
+ const double dotWidth =
+ qMin( double( d_data->markerSize ), radius);
+
+ const double dotCenterDist = radius - 0.5 * dotWidth;
+ if ( dotCenterDist > 0.0 )
+ {
+ const QPointF center( xm - sinA * dotCenterDist,
+ ym - cosA * dotCenterDist );
+
+ QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth );
+ ellipse.moveCenter( center );
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette().color( QPalette::ButtonText ) );
+ painter->drawEllipse( ellipse );
+ }
+
+ break;
+ }
+ case Tick:
+ {
+ const double rb = qMax( radius - d_data->markerSize, 1.0 );
+ const double re = radius;
+
+ const QLineF line( xm - sinA * rb, ym - cosA * rb,
+ xm - sinA * re, ym - cosA * re );
+
+ QPen pen( palette().color( QPalette::ButtonText ), 0 );
+ pen.setCapStyle( Qt::FlatCap );
+ painter->setPen( pen );
+ painter->drawLine ( line );
+
+ break;
+ }
+#if 0
+ case Triangle:
+ {
+ const double rb = qMax( radius - d_data->markerSize, 1.0 );
+ const double re = radius;
+
+ painter->translate( rect.center() );
+ painter->rotate( angle - 90.0 );
+
+ QPolygonF polygon;
+ polygon += QPointF( re, 0.0 );
+ polygon += QPointF( rb, 0.5 * ( re - rb ) );
+ polygon += QPointF( rb, -0.5 * ( re - rb ) );
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette().color( QPalette::Text ) );
+ painter->drawPolygon( polygon );
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+}
+
+/*!
+ \brief Change the knob's width.
+
+ The specified width must be >= 5, or it will be clipped.
+ \param width New width
+*/
+void QwtKnob::setKnobWidth( int width )
+{
+ d_data->knobWidth = qMax( width, 5 );
+ layoutKnob( true );
+}
+
+//! Return the width of the knob
+int QwtKnob::knobWidth() const
+{
+ return d_data->knobWidth;
+}
+
+/*!
+ \brief Set the knob's border width
+ \param borderWidth new border width
+*/
+void QwtKnob::setBorderWidth( int borderWidth )
+{
+ d_data->borderWidth = qMax( borderWidth, 0 );
+ layoutKnob( true );
+}
+
+//! Return the border width
+int QwtKnob::borderWidth() const
+{
+ return d_data->borderWidth;
+}
+
+/*!
+ \brief Set the size of the marker
+ \sa markerSize(), markerStyle()
+*/
+void QwtKnob::setMarkerSize( int size )
+{
+ if ( d_data->markerSize != size )
+ {
+ d_data->markerSize = size;
+ update();
+ }
+}
+
+//! Return the marker size
+int QwtKnob::markerSize() const
+{
+ return d_data->markerSize;
+}
+
+/*!
+ \brief Recalculate the marker angle corresponding to the
+ current value
+*/
+void QwtKnob::recalcAngle()
+{
+ // calculate the angle corresponding to the value
+ if ( maxValue() == minValue() )
+ {
+ d_data->angle = 0;
+ d_data->nTurns = 0;
+ }
+ else
+ {
+ d_data->angle = ( value() - 0.5 * ( minValue() + maxValue() ) )
+ / ( maxValue() - minValue() ) * d_data->totalAngle;
+ d_data->nTurns = qFloor( ( d_data->angle + 180.0 ) / 360.0 );
+ d_data->angle = d_data->angle - d_data->nTurns * 360.0;
+ }
+}
+
+
+/*!
+ Recalculates the layout
+ \sa layoutKnob()
+*/
+void QwtKnob::scaleChange()
+{
+ layoutKnob( true );
+}
+
+/*!
+ \return minimumSizeHint()
+*/
+QSize QwtKnob::sizeHint() const
+{
+ const QSize hint = minimumSizeHint();
+ return hint.expandedTo( QApplication::globalStrut() );
+}
+
+/*!
+ \brief Return a minimum size hint
+ \warning The return value of QwtKnob::minimumSizeHint() depends on the
+ font and the scale.
+*/
+QSize QwtKnob::minimumSizeHint() const
+{
+ // Add the scale radial thickness to the knobWidth
+ const int sh = qCeil( scaleDraw()->extent( font() ) );
+ const int d = 2 * sh + 2 * d_data->scaleDist + d_data->knobWidth;
+
+ return QSize( d, d );
+}
diff --git a/src/libpcp_qwt/src/qwt_knob.h b/src/libpcp_qwt/src/qwt_knob.h
new file mode 100644
index 0000000..355b4c5
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_knob.h
@@ -0,0 +1,159 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_KNOB_H
+#define QWT_KNOB_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_slider.h"
+#include "qwt_abstract_scale.h"
+
+class QwtRoundScaleDraw;
+
+/*!
+ \brief The Knob Widget
+
+ The QwtKnob widget imitates look and behaviour of a volume knob on a radio.
+ It contains a scale around the knob which is set up automatically or can
+ be configured manually (see QwtAbstractScale).
+ Automatic scrolling is enabled when the user presses a mouse
+ button on the scale. For a description of signals, slots and other
+ members, see QwtAbstractSlider.
+
+ \image html knob.png
+ \sa QwtAbstractSlider and QwtAbstractScale for the descriptions
+ of the inherited members.
+*/
+
+class QWT_EXPORT QwtKnob : public QwtAbstractSlider, public QwtAbstractScale
+{
+ Q_OBJECT
+
+ Q_ENUMS ( KnobStyle )
+ Q_ENUMS ( MarkerStyle )
+
+ Q_PROPERTY( KnobStyle knobStyle READ knobStyle WRITE setKnobStyle )
+ Q_PROPERTY( MarkerStyle markerStyle READ markerStyle WRITE setMarkerStyle )
+ Q_PROPERTY( int knobWidth READ knobWidth WRITE setKnobWidth )
+ Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth )
+ Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle )
+ Q_PROPERTY( int markerSize READ markerSize WRITE setMarkerSize )
+ Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth )
+
+public:
+ /*!
+ \brief Style of the knob surface
+
+ Depending on the KnobStyle the surface of the knob is
+ filled from the brushes of the widget palette().
+
+ \sa setKnobStyle(), knobStyle()
+ */
+ enum KnobStyle
+ {
+ //! Fill the knob with a brush from QPalette::Button.
+ NoStyle = -1,
+
+ //! Build a gradient from QPalette::Midlight and QPalette::Button
+ Raised,
+
+ /*!
+ Build a gradient from QPalette::Midlight, QPalette::Button
+ and QPalette::Midlight
+ */
+ Sunken
+ };
+
+ /*!
+ \brief Marker type
+
+ The marker indicates the current value on the knob
+ The default setting is a Notch marker.
+
+ \sa setMarkerStyle(), setMarkerSize()
+ */
+ enum MarkerStyle
+ {
+ //! Don't paint any marker
+ NoMarker = -1,
+
+ //! Paint a single tick in QPalette::ButtonText color
+ Tick,
+
+ //! Paint a circle in QPalette::ButtonText color
+ Dot,
+
+ /*!
+ Draw a raised ellipse with a gradient build from
+ QPalette::Light and QPalette::Mid
+ */
+ Nub,
+
+ /*!
+ Draw a sunken ellipse with a gradient build from
+ QPalette::Light and QPalette::Mid
+ */
+ Notch
+ };
+
+ explicit QwtKnob( QWidget* parent = NULL );
+ virtual ~QwtKnob();
+
+ void setKnobWidth( int w );
+ int knobWidth() const;
+
+ void setTotalAngle ( double angle );
+ double totalAngle() const;
+
+ void setKnobStyle( KnobStyle );
+ KnobStyle knobStyle() const;
+
+ void setBorderWidth( int bw );
+ int borderWidth() const;
+
+ void setMarkerStyle( MarkerStyle );
+ MarkerStyle markerStyle() const;
+
+ void setMarkerSize( int );
+ int markerSize() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setScaleDraw( QwtRoundScaleDraw * );
+ const QwtRoundScaleDraw *scaleDraw() const;
+ QwtRoundScaleDraw *scaleDraw();
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+ virtual void changeEvent( QEvent * );
+
+ virtual void drawKnob( QPainter *, const QRectF & ) const;
+ virtual void drawMarker( QPainter *,
+ const QRectF &, double arc ) const;
+
+ virtual double getValue( const QPoint &p );
+ virtual void getScrollMode( const QPoint &,
+ QwtAbstractSlider::ScrollMode &, int &direction ) const;
+
+private:
+ void initKnob();
+ void layoutKnob( bool update );
+ void recalcAngle();
+
+ virtual void valueChange();
+ virtual void rangeChange();
+ virtual void scaleChange();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_legend.cpp b/src/libpcp_qwt/src/qwt_legend.cpp
new file mode 100644
index 0000000..f4797e6
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_legend.cpp
@@ -0,0 +1,519 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_legend.h"
+#include "qwt_legend_itemmanager.h"
+#include "qwt_legend_item.h"
+#include "qwt_dyngrid_layout.h"
+#include "qwt_math.h"
+#include <qapplication.h>
+#include <qmap.h>
+#include <qscrollbar.h>
+#include <qscrollarea.h>
+
+class QwtLegend::PrivateData
+{
+public:
+ class LegendMap
+ {
+ public:
+ void insert( const QwtLegendItemManager *, QWidget * );
+
+ void remove( const QwtLegendItemManager * );
+ void remove( QWidget * );
+
+ void clear();
+
+ uint count() const;
+
+ inline const QWidget *find( const QwtLegendItemManager * ) const;
+ inline QWidget *find( const QwtLegendItemManager * );
+
+ inline const QwtLegendItemManager *find( const QWidget * ) const;
+ inline QwtLegendItemManager *find( const QWidget * );
+
+ const QMap<QWidget *, const QwtLegendItemManager *> &widgetMap() const;
+ QMap<QWidget *, const QwtLegendItemManager *> &widgetMap();
+
+ private:
+ QMap<QWidget *, const QwtLegendItemManager *> d_widgetMap;
+ QMap<const QwtLegendItemManager *, QWidget *> d_itemMap;
+ };
+
+ QwtLegend::LegendItemMode itemMode;
+
+ LegendMap map;
+
+ class LegendView;
+ LegendView *view;
+};
+
+class QwtLegend::PrivateData::LegendView: public QScrollArea
+{
+public:
+ LegendView( QWidget *parent ):
+ QScrollArea( parent )
+ {
+ setFocusPolicy( Qt::NoFocus );
+
+ contentsWidget = new QWidget( this );
+ contentsWidget->setObjectName( "QwtLegendViewContents" );
+
+ setWidget( contentsWidget );
+ setWidgetResizable( false );
+
+ viewport()->setObjectName( "QwtLegendViewport" );
+
+ // QScrollArea::setWidget internally sets autoFillBackground to true
+ // But we don't want a background.
+ contentsWidget->setAutoFillBackground( false );
+ viewport()->setAutoFillBackground( false );
+ }
+
+ virtual bool viewportEvent( QEvent *e )
+ {
+ bool ok = QScrollArea::viewportEvent( e );
+
+ if ( e->type() == QEvent::Resize )
+ {
+ QEvent event( QEvent::LayoutRequest );
+ QApplication::sendEvent( contentsWidget, &event );
+ }
+ return ok;
+ }
+
+ QSize viewportSize( int w, int h ) const
+ {
+ const int sbHeight = horizontalScrollBar()->sizeHint().height();
+ const int sbWidth = verticalScrollBar()->sizeHint().width();
+
+ const int cw = contentsRect().width();
+ const int ch = contentsRect().height();
+
+ int vw = cw;
+ int vh = ch;
+
+ if ( w > vw )
+ vh -= sbHeight;
+
+ if ( h > vh )
+ {
+ vw -= sbWidth;
+ if ( w > vw && vh == ch )
+ vh -= sbHeight;
+ }
+ return QSize( vw, vh );
+ }
+
+ QWidget *contentsWidget;
+};
+
+void QwtLegend::PrivateData::LegendMap::insert(
+ const QwtLegendItemManager *item, QWidget *widget )
+{
+ d_itemMap.insert( item, widget );
+ d_widgetMap.insert( widget, item );
+}
+
+void QwtLegend::PrivateData::LegendMap::remove( const QwtLegendItemManager *item )
+{
+ QWidget *widget = d_itemMap[item];
+ d_itemMap.remove( item );
+ d_widgetMap.remove( widget );
+}
+
+void QwtLegend::PrivateData::LegendMap::remove( QWidget *widget )
+{
+ const QwtLegendItemManager *item = d_widgetMap[widget];
+ d_itemMap.remove( item );
+ d_widgetMap.remove( widget );
+}
+
+void QwtLegend::PrivateData::LegendMap::clear()
+{
+
+ /*
+ We can't delete the widgets in the following loop, because
+ we would get ChildRemoved events, changing d_itemMap, while
+ we are iterating.
+ */
+
+ QList<const QWidget *> widgets;
+
+ QMap<const QwtLegendItemManager *, QWidget *>::const_iterator it;
+ for ( it = d_itemMap.begin(); it != d_itemMap.end(); ++it )
+ widgets.append( it.value() );
+
+ d_itemMap.clear();
+ d_widgetMap.clear();
+
+ for ( int i = 0; i < widgets.size(); i++ )
+ delete widgets[i];
+}
+
+uint QwtLegend::PrivateData::LegendMap::count() const
+{
+ return d_itemMap.count();
+}
+
+inline const QWidget *QwtLegend::PrivateData::LegendMap::find(
+ const QwtLegendItemManager *item ) const
+{
+ if ( !d_itemMap.contains( item ) )
+ return NULL;
+
+ return d_itemMap[item];
+}
+
+inline QWidget *QwtLegend::PrivateData::LegendMap::find(
+ const QwtLegendItemManager *item )
+{
+ if ( !d_itemMap.contains( item ) )
+ return NULL;
+
+ return d_itemMap[item];
+}
+
+inline const QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find(
+ const QWidget *widget ) const
+{
+ QWidget *w = const_cast<QWidget *>( widget );
+ if ( !d_widgetMap.contains( w ) )
+ return NULL;
+
+ return d_widgetMap[w];
+}
+
+inline QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find(
+ const QWidget *widget )
+{
+ QWidget *w = const_cast<QWidget *>( widget );
+ if ( !d_widgetMap.contains( w ) )
+ return NULL;
+
+ return const_cast<QwtLegendItemManager *>( d_widgetMap[w] );
+}
+
+inline const QMap<QWidget *, const QwtLegendItemManager *> &
+QwtLegend::PrivateData::LegendMap::widgetMap() const
+{
+ return d_widgetMap;
+}
+
+inline QMap<QWidget *, const QwtLegendItemManager *> &
+QwtLegend::PrivateData::LegendMap::widgetMap()
+{
+ return d_widgetMap;
+}
+
+/*!
+ Constructor
+
+ \param parent Parent widget
+*/
+QwtLegend::QwtLegend( QWidget *parent ):
+ QFrame( parent )
+{
+ setFrameStyle( NoFrame );
+
+ d_data = new QwtLegend::PrivateData;
+ d_data->itemMode = QwtLegend::ReadOnlyItem;
+
+ d_data->view = new QwtLegend::PrivateData::LegendView( this );
+ d_data->view->setObjectName( "QwtLegendView" );
+ d_data->view->setFrameStyle( NoFrame );
+
+ QwtDynGridLayout *gridLayout = new QwtDynGridLayout(
+ d_data->view->contentsWidget );
+ gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop );
+
+ d_data->view->contentsWidget->installEventFilter( this );
+
+ QVBoxLayout *layout = new QVBoxLayout( this );
+ layout->setContentsMargins( 0, 0, 0, 0 );
+ layout->addWidget( d_data->view );
+}
+
+//! Destructor
+QwtLegend::~QwtLegend()
+{
+ delete d_data;
+}
+
+//! \sa LegendItemMode
+void QwtLegend::setItemMode( LegendItemMode mode )
+{
+ d_data->itemMode = mode;
+}
+
+//! \sa LegendItemMode
+QwtLegend::LegendItemMode QwtLegend::itemMode() const
+{
+ return d_data->itemMode;
+}
+
+/*!
+ The contents widget is the only child of the viewport of
+ the internal QScrollArea and the parent widget of all legend items.
+
+ \return Container widget of the legend items
+*/
+QWidget *QwtLegend::contentsWidget()
+{
+ return d_data->view->contentsWidget;
+}
+
+/*!
+ \return Horizontal scrollbar
+ \sa verticalScrollBar()
+*/
+QScrollBar *QwtLegend::horizontalScrollBar() const
+{
+ return d_data->view->horizontalScrollBar();
+}
+
+/*!
+ \return Vertical scrollbar
+ \sa horizontalScrollBar()
+*/
+QScrollBar *QwtLegend::verticalScrollBar() const
+{
+ return d_data->view->verticalScrollBar();
+}
+
+/*!
+ The contents widget is the only child of the viewport of
+ the internal QScrollArea and the parent widget of all legend items.
+
+ \return Container widget of the legend items
+
+*/
+const QWidget *QwtLegend::contentsWidget() const
+{
+ return d_data->view->contentsWidget;
+}
+
+/*!
+ Insert a new item for a plot item
+ \param plotItem Plot item
+ \param legendItem New legend item
+ \note The parent of item will be changed to contentsWidget()
+*/
+void QwtLegend::insert( const QwtLegendItemManager *plotItem, QWidget *legendItem )
+{
+ if ( legendItem == NULL || plotItem == NULL )
+ return;
+
+ QWidget *contentsWidget = d_data->view->contentsWidget;
+
+ if ( legendItem->parent() != contentsWidget )
+ legendItem->setParent( contentsWidget );
+
+ legendItem->show();
+
+ d_data->map.insert( plotItem, legendItem );
+
+ layoutContents();
+
+ if ( contentsWidget->layout() )
+ {
+ contentsWidget->layout()->addWidget( legendItem );
+
+ // set tab focus chain
+
+ QWidget *w = NULL;
+
+ for ( int i = 0; i < contentsWidget->layout()->count(); i++ )
+ {
+ QLayoutItem *item = contentsWidget->layout()->itemAt( i );
+ if ( w && item->widget() )
+ QWidget::setTabOrder( w, item->widget() );
+
+ w = item->widget();
+ }
+ }
+ if ( parentWidget() && parentWidget()->layout() == NULL )
+ {
+ /*
+ updateGeometry() doesn't post LayoutRequest in certain
+ situations, like when we are hidden. But we want the
+ parent widget notified, so it can show/hide the legend
+ depending on its items.
+ */
+ QApplication::postEvent( parentWidget(),
+ new QEvent( QEvent::LayoutRequest ) );
+ }
+}
+
+/*!
+ Find the widget that represents a plot item
+
+ \param plotItem Plot item
+ \return Widget on the legend, or NULL
+*/
+QWidget *QwtLegend::find( const QwtLegendItemManager *plotItem ) const
+{
+ return d_data->map.find( plotItem );
+}
+
+/*!
+ Find the widget that represents a plot item
+
+ \param legendItem Legend item
+ \return Widget on the legend, or NULL
+*/
+QwtLegendItemManager *QwtLegend::find( const QWidget *legendItem ) const
+{
+ return d_data->map.find( legendItem );
+}
+
+/*!
+ Find the corresponding item for a plotItem and remove it
+ from the item list.
+
+ \param plotItem Plot item
+*/
+void QwtLegend::remove( const QwtLegendItemManager *plotItem )
+{
+ QWidget *legendItem = d_data->map.find( plotItem );
+ d_data->map.remove( legendItem );
+ delete legendItem;
+}
+
+//! Remove all items.
+void QwtLegend::clear()
+{
+ bool doUpdate = updatesEnabled();
+ if ( doUpdate )
+ setUpdatesEnabled( false );
+
+ d_data->map.clear();
+
+ if ( doUpdate )
+ setUpdatesEnabled( true );
+
+ update();
+}
+
+//! Return a size hint.
+QSize QwtLegend::sizeHint() const
+{
+ QSize hint = d_data->view->contentsWidget->sizeHint();
+ hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
+
+ return hint;
+}
+
+/*!
+ \return The preferred height, for the width w.
+ \param width Width
+*/
+int QwtLegend::heightForWidth( int width ) const
+{
+ width -= 2 * frameWidth();
+
+ int h = d_data->view->contentsWidget->heightForWidth( width );
+ if ( h >= 0 )
+ h += 2 * frameWidth();
+
+ return h;
+}
+
+/*!
+ Adjust contents widget and item layout to the size of the viewport().
+*/
+void QwtLegend::layoutContents()
+{
+ const QSize visibleSize =
+ d_data->view->viewport()->contentsRect().size();
+
+ const QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
+ d_data->view->contentsWidget->layout() );
+ if ( tl )
+ {
+ const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin();
+
+ int w = qMax( visibleSize.width(), minW );
+ int h = qMax( tl->heightForWidth( w ), visibleSize.height() );
+
+ const int vpWidth = d_data->view->viewportSize( w, h ).width();
+ if ( w > vpWidth )
+ {
+ w = qMax( vpWidth, minW );
+ h = qMax( tl->heightForWidth( w ), visibleSize.height() );
+ }
+
+ d_data->view->contentsWidget->resize( w, h );
+ }
+}
+
+/*!
+ Handle QEvent::ChildRemoved andQEvent::LayoutRequest events
+ for the contentsWidget().
+
+ \param object Object to be filtered
+ \param event Event
+*/
+bool QwtLegend::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object == d_data->view->contentsWidget )
+ {
+ switch ( event->type() )
+ {
+ case QEvent::ChildRemoved:
+ {
+ const QChildEvent *ce =
+ static_cast<const QChildEvent *>(event);
+ if ( ce->child()->isWidgetType() )
+ {
+ QWidget *w = static_cast< QWidget * >( ce->child() );
+ d_data->map.remove( w );
+ }
+ break;
+ }
+ case QEvent::LayoutRequest:
+ {
+ layoutContents();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ return QFrame::eventFilter( object, event );
+}
+
+
+//! Return true, if there are no legend items.
+bool QwtLegend::isEmpty() const
+{
+ return d_data->map.count() == 0;
+}
+
+//! Return the number of legend items.
+uint QwtLegend::itemCount() const
+{
+ return d_data->map.count();
+}
+
+//! Return a list of all legend items
+QList<QWidget *> QwtLegend::legendItems() const
+{
+ const QMap<QWidget *, const QwtLegendItemManager *> &map =
+ d_data->map.widgetMap();
+
+ QList<QWidget *> list;
+
+ QMap<QWidget *, const QwtLegendItemManager *>::const_iterator it;
+ for ( it = map.begin(); it != map.end(); ++it )
+ list += it.key();
+
+ return list;
+} \ No newline at end of file
diff --git a/src/libpcp_qwt/src/qwt_legend.h b/src/libpcp_qwt/src/qwt_legend.h
new file mode 100644
index 0000000..e241783
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_legend.h
@@ -0,0 +1,95 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_LEGEND_H
+#define QWT_LEGEND_H
+
+#include "qwt_global.h"
+#include <qframe.h>
+#include <qlist.h>
+
+class QScrollBar;
+class QwtLegendItemManager;
+
+/*!
+ \brief The legend widget
+
+ The QwtLegend widget is a tabular arrangement of legend items. Legend
+ items might be any type of widget, but in general they will be
+ a QwtLegendItem.
+
+ \sa QwtLegendItem, QwtLegendItemManager QwtPlot
+*/
+
+class QWT_EXPORT QwtLegend : public QFrame
+{
+ Q_OBJECT
+
+public:
+ /*!
+ \brief Interaction mode for the legend items
+
+ The default is QwtLegend::ReadOnlyItem.
+
+ \sa setItemMode(), itemMode(), QwtLegendItem::IdentifierMode
+ QwtLegendItem::clicked(), QwtLegendItem::checked(),
+ QwtPlot::legendClicked(), QwtPlot::legendChecked()
+ */
+
+ enum LegendItemMode
+ {
+ //! The legend item is not interactive, like a label
+ ReadOnlyItem,
+
+ //! The legend item is clickable, like a push button
+ ClickableItem,
+
+ //! The legend item is checkable, like a checkable button
+ CheckableItem
+ };
+
+ explicit QwtLegend( QWidget *parent = NULL );
+ virtual ~QwtLegend();
+
+ void setItemMode( LegendItemMode );
+ LegendItemMode itemMode() const;
+
+ QWidget *contentsWidget();
+ const QWidget *contentsWidget() const;
+
+ void insert( const QwtLegendItemManager *, QWidget * );
+ void remove( const QwtLegendItemManager * );
+
+ QWidget *find( const QwtLegendItemManager * ) const;
+ QwtLegendItemManager *find( const QWidget * ) const;
+
+ virtual QList<QWidget *> legendItems() const;
+
+ void clear();
+
+ bool isEmpty() const;
+ uint itemCount() const;
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+ virtual QSize sizeHint() const;
+ virtual int heightForWidth( int w ) const;
+
+ QScrollBar *horizontalScrollBar() const;
+ QScrollBar *verticalScrollBar() const;
+
+protected:
+ virtual void layoutContents();
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_legend_item.cpp b/src/libpcp_qwt/src/qwt_legend_item.cpp
new file mode 100644
index 0000000..5d59af4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_legend_item.cpp
@@ -0,0 +1,407 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_legend_item.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include "qwt_symbol.h"
+#include <qpainter.h>
+#include <qdrawutil.h>
+#include <qstyle.h>
+#include <qpen.h>
+#include <qevent.h>
+#include <qstyleoption.h>
+#include <qapplication.h>
+
+static const int PixmapHeight = 10;
+static const int PixmapWidth = 15;
+static const int ButtonFrame = 2;
+static const int Margin = 2;
+
+static QSize buttonShift( const QwtLegendItem *w )
+{
+ QStyleOption option;
+ option.init( w );
+
+ const int ph = w->style()->pixelMetric(
+ QStyle::PM_ButtonShiftHorizontal, &option, w );
+ const int pv = w->style()->pixelMetric(
+ QStyle::PM_ButtonShiftVertical, &option, w );
+ return QSize( ph, pv );
+}
+
+class QwtLegendItem::PrivateData
+{
+public:
+ PrivateData():
+ itemMode( QwtLegend::ReadOnlyItem ),
+ isDown( false ),
+ identifierSize( PixmapWidth, PixmapHeight ),
+ spacing( Margin )
+ {
+ }
+
+ QwtLegend::LegendItemMode itemMode;
+ bool isDown;
+
+ QSize identifierSize;
+ QPixmap identifier;
+
+ int spacing;
+};
+
+/*!
+ \param parent Parent widget
+*/
+QwtLegendItem::QwtLegendItem( QWidget *parent ):
+ QwtTextLabel( parent )
+{
+ d_data = new PrivateData;
+ setMargin( Margin );
+ setIndent( Margin + d_data->identifierSize.width() + 2 * d_data->spacing );
+}
+
+//! Destructor
+QwtLegendItem::~QwtLegendItem()
+{
+ delete d_data;
+ d_data = NULL;
+}
+
+/*!
+ Set the text to the legend item
+
+ \param text Text label
+ \sa QwtTextLabel::text()
+*/
+void QwtLegendItem::setText( const QwtText &text )
+{
+ const int flags = Qt::AlignLeft | Qt::AlignVCenter
+ | Qt::TextExpandTabs | Qt::TextWordWrap;
+
+ QwtText txt = text;
+ txt.setRenderFlags( flags );
+
+ QwtTextLabel::setText( txt );
+}
+
+/*!
+ Set the item mode
+ The default is QwtLegend::ReadOnlyItem
+
+ \param mode Item mode
+ \sa itemMode()
+*/
+void QwtLegendItem::setItemMode( QwtLegend::LegendItemMode mode )
+{
+ if ( mode != d_data->itemMode )
+ {
+ d_data->itemMode = mode;
+ d_data->isDown = false;
+
+ setFocusPolicy( mode != QwtLegend::ReadOnlyItem ? Qt::TabFocus : Qt::NoFocus );
+ setMargin( ButtonFrame + Margin );
+
+ updateGeometry();
+ }
+}
+
+/*!
+ Return the item mode
+
+ \sa setItemMode()
+*/
+QwtLegend::LegendItemMode QwtLegendItem::itemMode() const
+{
+ return d_data->itemMode;
+}
+
+/*!
+ Assign the identifier
+ The identifier needs to be created according to the identifierWidth()
+
+ \param identifier Pixmap representing a plot item
+
+ \sa identifier(), identifierWidth()
+*/
+void QwtLegendItem::setIdentifier( const QPixmap &identifier )
+{
+ d_data->identifier = identifier;
+ update();
+}
+
+/*!
+ \return pixmap representing a plot item
+ \sa setIdentifier()
+*/
+QPixmap QwtLegendItem::identifier() const
+{
+ return d_data->identifier;
+}
+
+/*!
+ Set the size for the identifier
+ Default is PixmapWidth x PixmapHeight pixels
+
+ \param size New size
+
+ \sa identifierSize()
+*/
+void QwtLegendItem::setIdentifierSize( const QSize &size )
+{
+ QSize sz = size.expandedTo( QSize( 0, 0 ) );
+ if ( sz != d_data->identifierSize )
+ {
+ d_data->identifierSize = sz;
+ setIndent( margin() + d_data->identifierSize.width()
+ + 2 * d_data->spacing );
+ updateGeometry();
+ }
+}
+/*!
+ Return the width of the identifier
+
+ \sa setIdentifierSize()
+*/
+QSize QwtLegendItem::identifierSize() const
+{
+ return d_data->identifierSize;
+}
+
+/*!
+ Change the spacing
+ \param spacing Spacing
+ \sa spacing(), identifierWidth(), QwtTextLabel::margin()
+*/
+void QwtLegendItem::setSpacing( int spacing )
+{
+ spacing = qMax( spacing, 0 );
+ if ( spacing != d_data->spacing )
+ {
+ d_data->spacing = spacing;
+ setIndent( margin() + d_data->identifierSize.width()
+ + 2 * d_data->spacing );
+ }
+}
+
+/*!
+ Return the spacing
+ \sa setSpacing(), identifierWidth(), QwtTextLabel::margin()
+*/
+int QwtLegendItem::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ Check/Uncheck a the item
+
+ \param on check/uncheck
+ \sa setItemMode()
+*/
+void QwtLegendItem::setChecked( bool on )
+{
+ if ( d_data->itemMode == QwtLegend::CheckableItem )
+ {
+ const bool isBlocked = signalsBlocked();
+ blockSignals( true );
+
+ setDown( on );
+
+ blockSignals( isBlocked );
+ }
+}
+
+//! Return true, if the item is checked
+bool QwtLegendItem::isChecked() const
+{
+ return d_data->itemMode == QwtLegend::CheckableItem && isDown();
+}
+
+//! Set the item being down
+void QwtLegendItem::setDown( bool down )
+{
+ if ( down == d_data->isDown )
+ return;
+
+ d_data->isDown = down;
+ update();
+
+ if ( d_data->itemMode == QwtLegend::ClickableItem )
+ {
+ if ( d_data->isDown )
+ Q_EMIT pressed();
+ else
+ {
+ Q_EMIT released();
+ Q_EMIT clicked();
+ }
+ }
+
+ if ( d_data->itemMode == QwtLegend::CheckableItem )
+ Q_EMIT checked( d_data->isDown );
+}
+
+//! Return true, if the item is down
+bool QwtLegendItem::isDown() const
+{
+ return d_data->isDown;
+}
+
+//! Return a size hint
+QSize QwtLegendItem::sizeHint() const
+{
+ QSize sz = QwtTextLabel::sizeHint();
+ sz.setHeight( qMax( sz.height(), d_data->identifier.height() + 4 ) );
+
+ if ( d_data->itemMode != QwtLegend::ReadOnlyItem )
+ {
+ sz += buttonShift( this );
+ sz = sz.expandedTo( QApplication::globalStrut() );
+ }
+
+ return sz;
+}
+
+//! Paint event
+void QwtLegendItem::paintEvent( QPaintEvent *e )
+{
+ const QRect cr = contentsRect();
+
+ QPainter painter( this );
+ painter.setClipRegion( e->region() );
+
+ if ( d_data->isDown )
+ {
+ qDrawWinButton( &painter, 0, 0, width(), height(),
+ palette(), true );
+ }
+
+ painter.save();
+
+ if ( d_data->isDown )
+ {
+ const QSize shiftSize = buttonShift( this );
+ painter.translate( shiftSize.width(), shiftSize.height() );
+ }
+
+ painter.setClipRect( cr );
+
+ drawContents( &painter );
+
+ if ( !d_data->identifier.isNull() )
+ {
+ QRect identRect = cr;
+ identRect.setX( identRect.x() + margin() );
+ if ( d_data->itemMode != QwtLegend::ReadOnlyItem )
+ identRect.setX( identRect.x() + ButtonFrame );
+
+ identRect.setSize( d_data->identifier.size() );
+ identRect.moveCenter( QPoint( identRect.center().x(), cr.center().y() ) );
+
+ painter.drawPixmap( identRect, d_data->identifier );
+ }
+
+ painter.restore();
+}
+
+//! Handle mouse press events
+void QwtLegendItem::mousePressEvent( QMouseEvent *e )
+{
+ if ( e->button() == Qt::LeftButton )
+ {
+ switch ( d_data->itemMode )
+ {
+ case QwtLegend::ClickableItem:
+ {
+ setDown( true );
+ return;
+ }
+ case QwtLegend::CheckableItem:
+ {
+ setDown( !isDown() );
+ return;
+ }
+ default:;
+ }
+ }
+ QwtTextLabel::mousePressEvent( e );
+}
+
+//! Handle mouse release events
+void QwtLegendItem::mouseReleaseEvent( QMouseEvent *e )
+{
+ if ( e->button() == Qt::LeftButton )
+ {
+ switch ( d_data->itemMode )
+ {
+ case QwtLegend::ClickableItem:
+ {
+ setDown( false );
+ return;
+ }
+ case QwtLegend::CheckableItem:
+ {
+ return; // do nothing, but accept
+ }
+ default:;
+ }
+ }
+ QwtTextLabel::mouseReleaseEvent( e );
+}
+
+//! Handle key press events
+void QwtLegendItem::keyPressEvent( QKeyEvent *e )
+{
+ if ( e->key() == Qt::Key_Space )
+ {
+ switch ( d_data->itemMode )
+ {
+ case QwtLegend::ClickableItem:
+ {
+ if ( !e->isAutoRepeat() )
+ setDown( true );
+ return;
+ }
+ case QwtLegend::CheckableItem:
+ {
+ if ( !e->isAutoRepeat() )
+ setDown( !isDown() );
+ return;
+ }
+ default:;
+ }
+ }
+
+ QwtTextLabel::keyPressEvent( e );
+}
+
+//! Handle key release events
+void QwtLegendItem::keyReleaseEvent( QKeyEvent *e )
+{
+ if ( e->key() == Qt::Key_Space )
+ {
+ switch ( d_data->itemMode )
+ {
+ case QwtLegend::ClickableItem:
+ {
+ if ( !e->isAutoRepeat() )
+ setDown( false );
+ return;
+ }
+ case QwtLegend::CheckableItem:
+ {
+ return; // do nothing, but accept
+ }
+ default:;
+ }
+ }
+
+ QwtTextLabel::keyReleaseEvent( e );
+}
diff --git a/src/libpcp_qwt/src/qwt_legend_item.h b/src/libpcp_qwt/src/qwt_legend_item.h
new file mode 100644
index 0000000..1d315f6
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_legend_item.h
@@ -0,0 +1,78 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_LEGEND_ITEM_H
+#define QWT_LEGEND_ITEM_H
+
+#include "qwt_global.h"
+#include "qwt_legend.h"
+#include "qwt_text.h"
+#include "qwt_text_label.h"
+#include <qpixmap.h>
+
+/*!
+ \brief A widget representing something on a QwtLegend().
+*/
+class QWT_EXPORT QwtLegendItem: public QwtTextLabel
+{
+ Q_OBJECT
+public:
+ explicit QwtLegendItem( QWidget *parent = 0 );
+ virtual ~QwtLegendItem();
+
+ void setItemMode( QwtLegend::LegendItemMode );
+ QwtLegend::LegendItemMode itemMode() const;
+
+ void setSpacing( int spacing );
+ int spacing() const;
+
+ virtual void setText( const QwtText & );
+
+ void setIdentifier( const QPixmap & );
+ QPixmap identifier() const;
+
+ void setIdentifierSize( const QSize & );
+ QSize identifierSize() const;
+
+ virtual QSize sizeHint() const;
+
+ bool isChecked() const;
+
+public Q_SLOTS:
+ void setChecked( bool on );
+
+Q_SIGNALS:
+ //! Signal, when the legend item has been clicked
+ void clicked();
+
+ //! Signal, when the legend item has been pressed
+ void pressed();
+
+ //! Signal, when the legend item has been relased
+ void released();
+
+ //! Signal, when the legend item has been toggled
+ void checked( bool );
+
+protected:
+ void setDown( bool );
+ bool isDown() const;
+
+ virtual void paintEvent( QPaintEvent * );
+ virtual void mousePressEvent( QMouseEvent * );
+ virtual void mouseReleaseEvent( QMouseEvent * );
+ virtual void keyPressEvent( QKeyEvent * );
+ virtual void keyReleaseEvent( QKeyEvent * );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_legend_itemmanager.h b/src/libpcp_qwt/src/qwt_legend_itemmanager.h
new file mode 100644
index 0000000..ccea820
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_legend_itemmanager.h
@@ -0,0 +1,66 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_LEGEND_ITEM_MANAGER_H
+#define QWT_LEGEND_ITEM_MANAGER_H
+
+#include "qwt_global.h"
+
+class QwtLegend;
+class QWidget;
+class QRectF;
+class QPainter;
+
+/*!
+ \brief Abstract API to bind plot items to the legend
+*/
+
+class QWT_EXPORT QwtLegendItemManager
+{
+public:
+ //! Constructor
+ QwtLegendItemManager()
+ {
+ }
+
+ //! Destructor
+ virtual ~QwtLegendItemManager()
+ {
+ }
+
+ /*!
+ Update the widget that represents the item on the legend
+ \param legend Legend
+ \sa legendItem()
+ */
+ virtual void updateLegend( QwtLegend *legend ) const = 0;
+
+ /*!
+ Allocate the widget that represents the item on the legend
+ \return Allocated widget
+ \sa updateLegend() QwtLegend()
+ */
+
+ virtual QWidget *legendItem() const = 0;
+
+ /*!
+ QwtLegendItem can display an icon-identifier followed
+ by a text. The icon helps to identify a plot item on
+ the plot canvas and depends on the type of information,
+ that is displayed.
+
+ The default implementation paints nothing.
+ */
+ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const
+ {
+ }
+};
+
+#endif
+
diff --git a/src/libpcp_qwt/src/qwt_magnifier.cpp b/src/libpcp_qwt/src/qwt_magnifier.cpp
new file mode 100644
index 0000000..5e8ffb0
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_magnifier.cpp
@@ -0,0 +1,467 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_magnifier.h"
+#include "qwt_math.h"
+#include <qevent.h>
+#include <qwidget.h>
+
+class QwtMagnifier::PrivateData
+{
+public:
+ PrivateData():
+ isEnabled( false ),
+ wheelFactor( 0.9 ),
+ wheelButtonState( Qt::NoButton ),
+ mouseFactor( 0.95 ),
+ mouseButton( Qt::RightButton ),
+ mouseButtonState( Qt::NoButton ),
+ keyFactor( 0.9 ),
+ zoomInKey( Qt::Key_Plus ),
+ zoomOutKey( Qt::Key_Minus ),
+ zoomInKeyModifiers( Qt::NoModifier ),
+ zoomOutKeyModifiers( Qt::NoModifier ),
+ mousePressed( false )
+ {
+ }
+
+ bool isEnabled;
+
+ double wheelFactor;
+ int wheelButtonState;
+
+ double mouseFactor;
+ int mouseButton;
+ int mouseButtonState;
+
+ double keyFactor;
+ int zoomInKey;
+ int zoomOutKey;
+ int zoomInKeyModifiers;
+ int zoomOutKeyModifiers;
+
+ bool mousePressed;
+ bool hasMouseTracking;
+ QPoint mousePos;
+};
+
+/*!
+ Constructor
+ \param parent Widget to be magnified
+*/
+QwtMagnifier::QwtMagnifier( QWidget *parent ):
+ QObject( parent )
+{
+ d_data = new PrivateData();
+ setEnabled( true );
+}
+
+//! Destructor
+QwtMagnifier::~QwtMagnifier()
+{
+ delete d_data;
+}
+
+/*!
+ \brief En/disable the magnifier
+
+ When enabled is true an event filter is installed for
+ the observed widget, otherwise the event filter is removed.
+
+ \param on true or false
+ \sa isEnabled(), eventFilter()
+*/
+void QwtMagnifier::setEnabled( bool on )
+{
+ if ( d_data->isEnabled != on )
+ {
+ d_data->isEnabled = on;
+
+ QObject *o = parent();
+ if ( o )
+ {
+ if ( d_data->isEnabled )
+ o->installEventFilter( this );
+ else
+ o->removeEventFilter( this );
+ }
+ }
+}
+
+/*!
+ \return true when enabled, false otherwise
+ \sa setEnabled(), eventFilter()
+*/
+bool QwtMagnifier::isEnabled() const
+{
+ return d_data->isEnabled;
+}
+
+/*!
+ \brief Change the wheel factor
+
+ The wheel factor defines the ratio between the current range
+ on the parent widget and the zoomed range for each step of the wheel.
+ The default value is 0.9.
+
+ \param factor Wheel factor
+ \sa wheelFactor(), setWheelButtonState(),
+ setMouseFactor(), setKeyFactor()
+*/
+void QwtMagnifier::setWheelFactor( double factor )
+{
+ d_data->wheelFactor = factor;
+}
+
+/*!
+ \return Wheel factor
+ \sa setWheelFactor()
+*/
+double QwtMagnifier::wheelFactor() const
+{
+ return d_data->wheelFactor;
+}
+
+/*!
+ Assign a mandatory button state for zooming in/out using the wheel.
+ The default button state is Qt::NoButton.
+
+ \param buttonState Button state
+ \sa wheelButtonState()
+*/
+void QwtMagnifier::setWheelButtonState( int buttonState )
+{
+ d_data->wheelButtonState = buttonState;
+}
+
+/*!
+ \return Wheel button state
+ \sa setWheelButtonState()
+*/
+int QwtMagnifier::wheelButtonState() const
+{
+ return d_data->wheelButtonState;
+}
+
+/*!
+ \brief Change the mouse factor
+
+ The mouse factor defines the ratio between the current range
+ on the parent widget and the zoomed range for each vertical mouse movement.
+ The default value is 0.95.
+
+ \param factor Wheel factor
+ \sa mouseFactor(), setMouseButton(), setWheelFactor(), setKeyFactor()
+*/
+void QwtMagnifier::setMouseFactor( double factor )
+{
+ d_data->mouseFactor = factor;
+}
+
+/*!
+ \return Mouse factor
+ \sa setMouseFactor()
+*/
+double QwtMagnifier::mouseFactor() const
+{
+ return d_data->mouseFactor;
+}
+
+/*!
+ Assign the mouse button, that is used for zooming in/out.
+ The default value is Qt::RightButton.
+
+ \param button Button
+ \param buttonState Button state
+ \sa getMouseButton()
+*/
+void QwtMagnifier::setMouseButton( int button, int buttonState )
+{
+ d_data->mouseButton = button;
+ d_data->mouseButtonState = buttonState;
+}
+
+//! \sa setMouseButton()
+void QwtMagnifier::getMouseButton(
+ int &button, int &buttonState ) const
+{
+ button = d_data->mouseButton;
+ buttonState = d_data->mouseButtonState;
+}
+
+/*!
+ \brief Change the key factor
+
+ The key factor defines the ratio between the current range
+ on the parent widget and the zoomed range for each key press of
+ the zoom in/out keys. The default value is 0.9.
+
+ \param factor Key factor
+ \sa keyFactor(), setZoomInKey(), setZoomOutKey(),
+ setWheelFactor, setMouseFactor()
+*/
+void QwtMagnifier::setKeyFactor( double factor )
+{
+ d_data->keyFactor = factor;
+}
+
+/*!
+ \return Key factor
+ \sa setKeyFactor()
+*/
+double QwtMagnifier::keyFactor() const
+{
+ return d_data->keyFactor;
+}
+
+/*!
+ Assign the key, that is used for zooming in.
+ The default combination is Qt::Key_Plus + Qt::NoModifier.
+
+ \param key
+ \param modifiers
+ \sa getZoomInKey(), setZoomOutKey()
+*/
+void QwtMagnifier::setZoomInKey( int key, int modifiers )
+{
+ d_data->zoomInKey = key;
+ d_data->zoomInKeyModifiers = modifiers;
+}
+
+//! \sa setZoomInKey()
+void QwtMagnifier::getZoomInKey( int &key, int &modifiers ) const
+{
+ key = d_data->zoomInKey;
+ modifiers = d_data->zoomInKeyModifiers;
+}
+
+/*!
+ Assign the key, that is used for zooming out.
+ The default combination is Qt::Key_Minus + Qt::NoModifier.
+
+ \param key
+ \param modifiers
+ \sa getZoomOutKey(), setZoomOutKey()
+*/
+void QwtMagnifier::setZoomOutKey( int key, int modifiers )
+{
+ d_data->zoomOutKey = key;
+ d_data->zoomOutKeyModifiers = modifiers;
+}
+
+//! \sa setZoomOutKey()
+void QwtMagnifier::getZoomOutKey( int &key, int &modifiers ) const
+{
+ key = d_data->zoomOutKey;
+ modifiers = d_data->zoomOutKeyModifiers;
+}
+
+/*!
+ \brief Event filter
+
+ When isEnabled() the mouse events of the observed widget are filtered.
+
+ \param object Object to be filtered
+ \param event Event
+
+ \sa widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent()
+ widgetKeyReleaseEvent()
+*/
+bool QwtMagnifier::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object && object == parent() )
+ {
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ widgetMousePressEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseMove:
+ {
+ widgetMouseMoveEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ widgetMouseReleaseEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::Wheel:
+ {
+ widgetWheelEvent( ( QWheelEvent * )event );
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ widgetKeyPressEvent( ( QKeyEvent * )event );
+ break;
+ }
+ case QEvent::KeyRelease:
+ {
+ widgetKeyReleaseEvent( ( QKeyEvent * )event );
+ break;
+ }
+ default:;
+ }
+ }
+ return QObject::eventFilter( object, event );
+}
+
+/*!
+ Handle a mouse press event for the observed widget.
+
+ \param mouseEvent Mouse event
+ \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent()
+*/
+void QwtMagnifier::widgetMousePressEvent( QMouseEvent *mouseEvent )
+{
+ if ( ( mouseEvent->button() != d_data->mouseButton)
+ || parentWidget() == NULL )
+ {
+ return;
+ }
+
+ if ( ( mouseEvent->modifiers() & Qt::KeyboardModifierMask ) !=
+ ( int )( d_data->mouseButtonState & Qt::KeyboardModifierMask ) )
+ {
+ return;
+ }
+
+ d_data->hasMouseTracking = parentWidget()->hasMouseTracking();
+ parentWidget()->setMouseTracking( true );
+ d_data->mousePos = mouseEvent->pos();
+ d_data->mousePressed = true;
+}
+
+/*!
+ Handle a mouse release event for the observed widget.
+
+ \param mouseEvent Mouse event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(),
+*/
+void QwtMagnifier::widgetMouseReleaseEvent( QMouseEvent *mouseEvent )
+{
+ Q_UNUSED( mouseEvent );
+
+ if ( d_data->mousePressed && parentWidget() )
+ {
+ d_data->mousePressed = false;
+ parentWidget()->setMouseTracking( d_data->hasMouseTracking );
+ }
+}
+
+/*!
+ Handle a mouse move event for the observed widget.
+
+ \param mouseEvent Mouse event
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+*/
+void QwtMagnifier::widgetMouseMoveEvent( QMouseEvent *mouseEvent )
+{
+ if ( !d_data->mousePressed )
+ return;
+
+ const int dy = mouseEvent->pos().y() - d_data->mousePos.y();
+ if ( dy != 0 )
+ {
+ double f = d_data->mouseFactor;
+ if ( dy < 0 )
+ f = 1 / f;
+
+ rescale( f );
+ }
+
+ d_data->mousePos = mouseEvent->pos();
+}
+
+/*!
+ Handle a wheel event for the observed widget.
+
+ \param wheelEvent Wheel event
+ \sa eventFilter()
+*/
+void QwtMagnifier::widgetWheelEvent( QWheelEvent *wheelEvent )
+{
+ if ( ( wheelEvent->modifiers() & Qt::KeyboardModifierMask ) !=
+ ( int )( d_data->wheelButtonState & Qt::KeyboardModifierMask ) )
+ {
+ return;
+ }
+
+ if ( d_data->wheelFactor != 0.0 )
+ {
+ /*
+ A positive delta indicates that the wheel was
+ rotated forwards away from the user; a negative
+ value indicates that the wheel was rotated
+ backwards toward the user.
+ Most mouse types work in steps of 15 degrees,
+ in which case the delta value is a multiple
+ of 120 (== 15 * 8).
+ */
+ double f = qPow( d_data->wheelFactor,
+ qAbs( wheelEvent->delta() / 120.0 ) );
+
+ if ( wheelEvent->delta() > 0 )
+ f = 1 / f;
+
+ rescale( f );
+ }
+}
+
+/*!
+ Handle a key press event for the observed widget.
+
+ \param keyEvent Key event
+ \sa eventFilter(), widgetKeyReleaseEvent()
+*/
+void QwtMagnifier::widgetKeyPressEvent( QKeyEvent *keyEvent )
+{
+ const int key = keyEvent->key();
+ const int state = keyEvent->modifiers();
+
+ if ( key == d_data->zoomInKey &&
+ state == d_data->zoomInKeyModifiers )
+ {
+ rescale( d_data->keyFactor );
+ }
+ else if ( key == d_data->zoomOutKey &&
+ state == d_data->zoomOutKeyModifiers )
+ {
+ rescale( 1.0 / d_data->keyFactor );
+ }
+}
+
+/*!
+ Handle a key release event for the observed widget.
+
+ \param keyEvent Key event
+ \sa eventFilter(), widgetKeyReleaseEvent()
+*/
+void QwtMagnifier::widgetKeyReleaseEvent( QKeyEvent *keyEvent )
+{
+ Q_UNUSED( keyEvent );
+}
+
+//! \return Parent widget, where the rescaling happens
+QWidget *QwtMagnifier::parentWidget()
+{
+ return qobject_cast<QWidget *>( parent() );
+}
+
+//! \return Parent widget, where the rescaling happens
+const QWidget *QwtMagnifier::parentWidget() const
+{
+ return qobject_cast<const QWidget *>( parent() );
+}
+
diff --git a/src/libpcp_qwt/src/qwt_magnifier.h b/src/libpcp_qwt/src/qwt_magnifier.h
new file mode 100644
index 0000000..f2f4bbd
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_magnifier.h
@@ -0,0 +1,86 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_MAGNIFIER_H
+#define QWT_MAGNIFIER_H 1
+
+#include "qwt_global.h"
+#include <qobject.h>
+
+class QWidget;
+class QMouseEvent;
+class QWheelEvent;
+class QKeyEvent;
+
+/*!
+ \brief QwtMagnifier provides zooming, by magnifying in steps.
+
+ Using QwtMagnifier a plot can be zoomed in/out in steps using
+ keys, the mouse wheel or moving a mouse button in vertical direction.
+*/
+class QWT_EXPORT QwtMagnifier: public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit QwtMagnifier( QWidget * );
+ virtual ~QwtMagnifier();
+
+ QWidget *parentWidget();
+ const QWidget *parentWidget() const;
+
+ void setEnabled( bool );
+ bool isEnabled() const;
+
+ // mouse
+ void setMouseFactor( double );
+ double mouseFactor() const;
+
+ void setMouseButton( int button, int buttonState = Qt::NoButton );
+ void getMouseButton( int &button, int &buttonState ) const;
+
+ // mouse wheel
+ void setWheelFactor( double );
+ double wheelFactor() const;
+
+ void setWheelButtonState( int buttonState );
+ int wheelButtonState() const;
+
+ // keyboard
+ void setKeyFactor( double );
+ double keyFactor() const;
+
+ void setZoomInKey( int key, int modifiers );
+ void getZoomInKey( int &key, int &modifiers ) const;
+
+ void setZoomOutKey( int key, int modifiers );
+ void getZoomOutKey( int &key, int &modifiers ) const;
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+protected:
+ /*!
+ Rescale the parent widget
+ \param factor Scale factor
+ */
+ virtual void rescale( double factor ) = 0;
+
+ virtual void widgetMousePressEvent( QMouseEvent * );
+ virtual void widgetMouseReleaseEvent( QMouseEvent * );
+ virtual void widgetMouseMoveEvent( QMouseEvent * );
+ virtual void widgetWheelEvent( QWheelEvent * );
+ virtual void widgetKeyPressEvent( QKeyEvent * );
+ virtual void widgetKeyReleaseEvent( QKeyEvent * );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_math.cpp b/src/libpcp_qwt/src/qwt_math.cpp
new file mode 100644
index 0000000..06a039a
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_math.cpp
@@ -0,0 +1,45 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_math.h"
+
+/*!
+ \brief Find the smallest value in an array
+ \param array Pointer to an array
+ \param size Array size
+*/
+double qwtGetMin( const double *array, int size )
+{
+ if ( size <= 0 )
+ return 0.0;
+
+ double rv = array[0];
+ for ( int i = 1; i < size; i++ )
+ rv = qMin( rv, array[i] );
+
+ return rv;
+}
+
+
+/*!
+ \brief Find the largest value in an array
+ \param array Pointer to an array
+ \param size Array size
+*/
+double qwtGetMax( const double *array, int size )
+{
+ if ( size <= 0 )
+ return 0.0;
+
+ double rv = array[0];
+ for ( int i = 1; i < size; i++ )
+ rv = qMax( rv, array[i] );
+
+ return rv;
+}
diff --git a/src/libpcp_qwt/src/qwt_math.h b/src/libpcp_qwt/src/qwt_math.h
new file mode 100644
index 0000000..fa8a476
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_math.h
@@ -0,0 +1,182 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_MATH_H
+#define QWT_MATH_H
+
+#include "qwt_global.h"
+
+#if defined(_MSC_VER)
+/*
+ Microsoft says:
+
+ Define _USE_MATH_DEFINES before including math.h to expose these macro
+ definitions for common math constants. These are placed under an #ifdef
+ since these commonly-defined names are not part of the C/C++ standards.
+*/
+#define _USE_MATH_DEFINES 1
+#endif
+
+#include <qpoint.h>
+#include <qmath.h>
+#include "qwt_global.h"
+
+#ifndef LOG10_2
+#define LOG10_2 0.30102999566398119802 /* log10(2) */
+#endif
+
+#ifndef LOG10_3
+#define LOG10_3 0.47712125471966243540 /* log10(3) */
+#endif
+
+#ifndef LOG10_5
+#define LOG10_5 0.69897000433601885749 /* log10(5) */
+#endif
+
+#ifndef M_2PI
+#define M_2PI 6.28318530717958623200 /* 2 pi */
+#endif
+
+#ifndef LOG_MIN
+//! Mininum value for logarithmic scales
+#define LOG_MIN 1.0e-100
+#endif
+
+#ifndef LOG_MAX
+//! Maximum value for logarithmic scales
+#define LOG_MAX 1.0e100
+#endif
+
+#ifndef M_E
+#define M_E 2.7182818284590452354 /* e */
+#endif
+
+#ifndef M_LOG2E
+#define M_LOG2E 1.4426950408889634074 /* log_2 e */
+#endif
+
+#ifndef M_LOG10E
+#define M_LOG10E 0.43429448190325182765 /* log_10 e */
+#endif
+
+#ifndef M_LN2
+#define M_LN2 0.69314718055994530942 /* log_e 2 */
+#endif
+
+#ifndef M_LN10
+#define M_LN10 2.30258509299404568402 /* log_e 10 */
+#endif
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+#ifndef M_PI_2
+#define M_PI_2 1.57079632679489661923 /* pi/2 */
+#endif
+
+#ifndef M_PI_4
+#define M_PI_4 0.78539816339744830962 /* pi/4 */
+#endif
+
+#ifndef M_1_PI
+#define M_1_PI 0.31830988618379067154 /* 1/pi */
+#endif
+
+#ifndef M_2_PI
+#define M_2_PI 0.63661977236758134308 /* 2/pi */
+#endif
+
+#ifndef M_2_SQRTPI
+#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */
+#endif
+
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
+#endif
+
+#ifndef M_SQRT1_2
+#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
+#endif
+
+QWT_EXPORT double qwtGetMin( const double *array, int size );
+QWT_EXPORT double qwtGetMax( const double *array, int size );
+
+/*!
+ \brief Compare 2 values, relative to an interval
+
+ Values are "equal", when :
+ \f$\cdot value2 - value1 <= abs(intervalSize * 10e^{-6})\f$
+
+ \param value1 First value to compare
+ \param value2 Second value to compare
+ \param intervalSize interval size
+
+ \return 0: if equal, -1: if value2 > value1, 1: if value1 > value2
+*/
+inline int qwtFuzzyCompare( double value1, double value2, double intervalSize )
+{
+ const double eps = qAbs( 1.0e-6 * intervalSize );
+
+ if ( value2 - value1 > eps )
+ return -1;
+
+ if ( value1 - value2 > eps )
+ return 1;
+
+ return 0;
+}
+
+
+inline bool qwtFuzzyGreaterOrEqual( double d1, double d2 )
+{
+ return ( d1 >= d2 ) || qFuzzyCompare( d1, d2 );
+}
+
+inline bool qwtFuzzyLessOrEqual( double d1, double d2 )
+{
+ return ( d1 <= d2 ) || qFuzzyCompare( d1, d2 );
+}
+
+//! Return the sign
+inline int qwtSign( double x )
+{
+ if ( x > 0.0 )
+ return 1;
+ else if ( x < 0.0 )
+ return ( -1 );
+ else
+ return 0;
+}
+
+//! Return the square of a number
+inline double qwtSqr( double x )
+{
+ return x * x;
+}
+
+//! Like qRound, but without converting the result to an int
+inline double qwtRoundF(double d)
+{
+ return ::floor( d + 0.5 );
+}
+
+//! Like qFloor, but without converting the result to an int
+inline double qwtFloorF(double d)
+{
+ return ::floor( d );
+}
+
+//! Like qCeil, but without converting the result to an int
+inline double qwtCeilF(double d)
+{
+ return ::ceil( d );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_matrix_raster_data.cpp b/src/libpcp_qwt/src/qwt_matrix_raster_data.cpp
new file mode 100644
index 0000000..2176ec9
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_matrix_raster_data.cpp
@@ -0,0 +1,270 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_matrix_raster_data.h"
+#include <qnumeric.h>
+#include <qmath.h>
+
+class QwtMatrixRasterData::PrivateData
+{
+public:
+ PrivateData():
+ resampleMode(QwtMatrixRasterData::NearestNeighbour),
+ numColumns(0)
+ {
+ }
+
+ inline double value(size_t row, size_t col) const
+ {
+ return values.data()[ row * numColumns + col ];
+ }
+
+ QwtMatrixRasterData::ResampleMode resampleMode;
+
+ QVector<double> values;
+ size_t numColumns;
+ size_t numRows;
+
+ double dx;
+ double dy;
+};
+
+//! Constructor
+QwtMatrixRasterData::QwtMatrixRasterData()
+{
+ d_data = new PrivateData();
+ update();
+}
+
+//! Destructor
+QwtMatrixRasterData::~QwtMatrixRasterData()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Set the resampling algorithm
+
+ \param mode Resampling mode
+ \sa resampleMode(), value()
+*/
+void QwtMatrixRasterData::setResampleMode(ResampleMode mode)
+{
+ d_data->resampleMode = mode;
+}
+
+/*!
+ \return resampling algorithm
+ \sa setResampleMode(), value()
+*/
+QwtMatrixRasterData::ResampleMode QwtMatrixRasterData::resampleMode() const
+{
+ return d_data->resampleMode;
+}
+
+/*!
+ \brief Assign the bounding interval for an axis
+
+ Setting the bounding intervals for the X/Y axis is mandatory
+ to define the positions for the values of the value matrix.
+ The interval in Z direction defines the possible range for
+ the values in the matrix, what is f.e used by QwtPlotSpectrogram
+ to map values to colors. The Z-interval might be the bounding
+ interval of the values in the matrix, but usually it isn't.
+ ( f.e a interval of 0.0-100.0 for values in percentage )
+
+ \param axis X, Y or Z axis
+ \param interval Interval
+
+ \sa QwtRasterData::interval(), setValueMatrix()
+*/
+void QwtMatrixRasterData::setInterval(
+ Qt::Axis axis, const QwtInterval &interval )
+{
+ QwtRasterData::setInterval( axis, interval );
+ update();
+}
+
+/*!
+ \brief Assign a value matrix
+
+ The positions of the values are calculated by dividing
+ the bounding rectangle of the X/Y intervals into equidistant
+ rectangles ( pixels ). Each value corresponds to the center of
+ a pixel.
+
+ \param values Vector of values
+ \param numColumns Number of columns
+
+ \sa valueMatrix(), numColumns(), numRows(), setInterval()()
+*/
+void QwtMatrixRasterData::setValueMatrix(
+ const QVector<double> &values, size_t numColumns )
+{
+ d_data->values = values;
+ d_data->numColumns = numColumns;
+ update();
+}
+
+/*!
+ \return Value matrix
+ \sa setValueMatrix(), numColumns(), numRows(), setInterval()
+*/
+const QVector<double> QwtMatrixRasterData::valueMatrix() const
+{
+ return d_data->values;
+}
+
+/*!
+ \return Number of columns of the value matrix
+ \sa valueMatrix(), numRows(), setValueMatrix()
+*/
+size_t QwtMatrixRasterData::numColumns() const
+{
+ return d_data->numColumns;
+}
+
+/*!
+ \return Number of rows of the value matrix
+ \sa valueMatrix(), numColumns(), setValueMatrix()
+*/
+size_t QwtMatrixRasterData::numRows() const
+{
+ return d_data->numRows;
+}
+
+/*!
+ \brief Pixel hint
+
+ - NearestNeighbour\n
+ pixelHint() returns the surrounding pixel of the top left value
+ in the matrix.
+
+ - BilinearInterpolation\n
+ Returns an empty rectangle recommending
+ to render in target device ( f.e. screen ) resolution.
+
+ \sa ResampleMode, setMatrix(), setInterval()
+*/
+QRectF QwtMatrixRasterData::pixelHint( const QRectF & ) const
+{
+ QRectF rect;
+ if ( d_data->resampleMode == NearestNeighbour )
+ {
+ const QwtInterval intervalX = interval( Qt::XAxis );
+ const QwtInterval intervalY = interval( Qt::YAxis );
+ if ( intervalX.isValid() && intervalY.isValid() )
+ {
+ rect = QRectF( intervalX.minValue(), intervalY.minValue(),
+ d_data->dx, d_data->dy );
+ }
+ }
+
+ return rect;
+}
+
+/*!
+ \return the value at a raster position
+
+ \param x X value in plot coordinates
+ \param y Y value in plot coordinates
+
+ \sa ResampleMode
+*/
+double QwtMatrixRasterData::value( double x, double y ) const
+{
+ const QwtInterval xInterval = interval( Qt::XAxis );
+ const QwtInterval yInterval = interval( Qt::YAxis );
+
+ if ( !( xInterval.contains(x) && yInterval.contains(y) ) )
+ return qQNaN();
+
+ double value;
+
+ switch( d_data->resampleMode )
+ {
+ case BilinearInterpolation:
+ {
+ int col1 = qRound( (x - xInterval.minValue() ) / d_data->dx ) - 1;
+ int row1 = qRound( (y - yInterval.minValue() ) / d_data->dy ) - 1;
+ int col2 = col1 + 1;
+ int row2 = row1 + 1;
+
+ if ( col1 < 0 )
+ col1 = col2;
+ else if ( col2 >= (int)d_data->numColumns )
+ col2 = col1;
+
+ if ( row1 < 0 )
+ row1 = row2;
+ else if ( row2 >= (int)d_data->numRows )
+ row2 = row1;
+
+ const double v11 = d_data->value( row1, col1 );
+ const double v21 = d_data->value( row1, col2 );
+ const double v12 = d_data->value( row2, col1 );
+ const double v22 = d_data->value( row2, col2 );
+
+ const double x2 = xInterval.minValue() +
+ ( col2 + 0.5 ) * d_data->dx;
+ const double y2 = yInterval.minValue() +
+ ( row2 + 0.5 ) * d_data->dy;
+
+ const double rx = ( x2 - x ) / d_data->dx;
+ const double ry = ( y2 - y ) / d_data->dy;
+
+ const double vr1 = rx * v11 + ( 1.0 - rx ) * v21;
+ const double vr2 = rx * v12 + ( 1.0 - rx ) * v22;
+
+ value = ry * vr1 + ( 1.0 - ry ) * vr2;
+
+ break;
+ }
+ case NearestNeighbour:
+ default:
+ {
+ uint row = uint( (y - yInterval.minValue() ) / d_data->dy );
+ uint col = uint( (x - xInterval.minValue() ) / d_data->dx );
+
+ // In case of intervals, where the maximum is included
+ // we get out of bound for row/col, when the value for the
+ // maximum is requested. Instead we return the value
+ // from the last row/col
+
+ if ( row >= d_data->numRows )
+ row = d_data->numRows - 1;
+
+ if ( col >= d_data->numColumns )
+ col = d_data->numColumns - 1;
+
+ value = d_data->value( row, col );
+ }
+ }
+
+ return value;
+}
+
+void QwtMatrixRasterData::update()
+{
+ d_data->numRows = 0;
+ d_data->dx = 0.0;
+ d_data->dy = 0.0;
+
+ if ( d_data->numColumns > 0 )
+ {
+ d_data->numRows = d_data->values.size() / d_data->numColumns;
+
+ const QwtInterval xInterval = interval( Qt::XAxis );
+ const QwtInterval yInterval = interval( Qt::YAxis );
+ if ( xInterval.isValid() )
+ d_data->dx = xInterval.width() / d_data->numColumns;
+ if ( yInterval.isValid() )
+ d_data->dy = yInterval.width() / d_data->numRows;
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_matrix_raster_data.h b/src/libpcp_qwt/src/qwt_matrix_raster_data.h
new file mode 100644
index 0000000..a8940f8
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_matrix_raster_data.h
@@ -0,0 +1,71 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_MATRIX_RASTER_DATA_H
+#define QWT_MATRIX_RASTER_DATA_H 1
+
+#include "qwt_global.h"
+#include "qwt_raster_data.h"
+#include <qvector.h>
+
+/*!
+ \brief A class representing a matrix of values as raster data
+
+ QwtMatrixRasterData implements an interface for a matrix of
+ equidistant values, that can be used by a QwtPlotRasterItem.
+ It implements a couple of resampling algorithms, to provide
+ values for positions, that or not on the value matrix.
+*/
+class QWT_EXPORT QwtMatrixRasterData: public QwtRasterData
+{
+public:
+ /*!
+ \brief Resampling algorithm
+ The default setting is NearestNeighbour;
+ */
+ enum ResampleMode
+ {
+ /*!
+ Return the value from the matrix, that is nearest to the
+ the requested position.
+ */
+ NearestNeighbour,
+
+ /*!
+ Interpolate the value from the distances and values of the
+ 4 surrounding values in the matrix,
+ */
+ BilinearInterpolation
+ };
+
+ QwtMatrixRasterData();
+ virtual ~QwtMatrixRasterData();
+
+ void setResampleMode(ResampleMode mode);
+ ResampleMode resampleMode() const;
+
+ virtual void setInterval( Qt::Axis, const QwtInterval & );
+ void setValueMatrix( const QVector<double> &values, size_t numColumns );
+
+ const QVector<double> valueMatrix() const;
+ size_t numColumns() const;
+ size_t numRows() const;
+
+ virtual QRectF pixelHint( const QRectF & ) const;
+
+ virtual double value( double x, double y ) const;
+
+private:
+ void update();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_null_paintdevice.cpp b/src/libpcp_qwt/src/qwt_null_paintdevice.cpp
new file mode 100644
index 0000000..cdab2a6
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_null_paintdevice.cpp
@@ -0,0 +1,428 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_null_paintdevice.h"
+#include <qpaintengine.h>
+#include <qpixmap.h>
+
+class QwtNullPaintDevice::PrivateData
+{
+public:
+ PrivateData():
+ size( 0, 0 )
+ {
+ }
+
+ QSize size;
+};
+
+class QwtNullPaintDevice::PaintEngine: public QPaintEngine
+{
+public:
+ PaintEngine( QPaintEngine::PaintEngineFeatures );
+
+ virtual bool begin( QPaintDevice * );
+ virtual bool end();
+
+ virtual Type type () const;
+ virtual void updateState(const QPaintEngineState &);
+
+ virtual void drawRects(const QRect *, int );
+ virtual void drawRects(const QRectF *, int );
+
+ virtual void drawLines(const QLine *, int );
+ virtual void drawLines(const QLineF *, int );
+
+ virtual void drawEllipse(const QRectF &);
+ virtual void drawEllipse(const QRect &);
+
+ virtual void drawPath(const QPainterPath &);
+
+ virtual void drawPoints(const QPointF *, int );
+ virtual void drawPoints(const QPoint *, int );
+
+ virtual void drawPolygon(const QPointF *, int , PolygonDrawMode );
+ virtual void drawPolygon(const QPoint *, int , PolygonDrawMode );
+
+ virtual void drawPixmap(const QRectF &,
+ const QPixmap &, const QRectF &);
+
+ virtual void drawTextItem(const QPointF &, const QTextItem &);
+ virtual void drawTiledPixmap(const QRectF &,
+ const QPixmap &, const QPointF &s);
+ virtual void drawImage(const QRectF &,
+ const QImage &, const QRectF &, Qt::ImageConversionFlags );
+
+private:
+ QwtNullPaintDevice *d_device;
+};
+
+QwtNullPaintDevice::PaintEngine::PaintEngine(
+ QPaintEngine::PaintEngineFeatures features ):
+ QPaintEngine( features ),
+ d_device(NULL)
+{
+}
+
+bool QwtNullPaintDevice::PaintEngine::begin(
+ QPaintDevice *device )
+{
+ d_device = static_cast<QwtNullPaintDevice *>( device );
+ return true;
+}
+
+bool QwtNullPaintDevice::PaintEngine::end()
+{
+ d_device = NULL;
+ return true;
+}
+
+QPaintEngine::Type
+QwtNullPaintDevice::PaintEngine::type () const
+{
+ return QPaintEngine::User;
+}
+
+void QwtNullPaintDevice::PaintEngine::drawRects(
+ const QRect *rects, int rectCount)
+{
+ if ( d_device )
+ d_device->drawRects( rects, rectCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawRects(
+ const QRectF *rects, int rectCount)
+{
+ if ( d_device )
+ d_device->drawRects( rects, rectCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawLines(
+ const QLine *lines, int lineCount)
+{
+ if ( d_device )
+ d_device->drawLines( lines, lineCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawLines(
+ const QLineF *lines, int lineCount)
+{
+ if ( d_device )
+ d_device->drawLines( lines, lineCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawEllipse(
+ const QRectF &rect)
+{
+ if ( d_device )
+ d_device->drawEllipse( rect );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawEllipse(
+ const QRect &rect)
+{
+ if ( d_device )
+ d_device->drawEllipse( rect );
+}
+
+
+void QwtNullPaintDevice::PaintEngine::drawPath(
+ const QPainterPath &path)
+{
+ if ( d_device )
+ d_device->drawPath( path );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawPoints(
+ const QPointF *points, int pointCount)
+{
+ if ( d_device )
+ d_device->drawPoints( points, pointCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawPoints(
+ const QPoint *points, int pointCount)
+{
+ if ( d_device )
+ d_device->drawPoints( points, pointCount );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawPolygon(
+ const QPointF *points, int pointCount, PolygonDrawMode mode)
+{
+ if ( d_device )
+ d_device->drawPolygon( points, pointCount, mode );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawPolygon(
+ const QPoint *points, int pointCount, PolygonDrawMode mode)
+{
+ if ( d_device )
+ d_device->drawPolygon( points, pointCount, mode );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawPixmap(
+ const QRectF &rect, const QPixmap &pm, const QRectF &subRect )
+{
+ if ( d_device )
+ d_device->drawPixmap( rect, pm, subRect );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawTextItem(
+ const QPointF &pos, const QTextItem &textItem)
+{
+ if ( d_device )
+ d_device->drawTextItem( pos, textItem );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawTiledPixmap(
+ const QRectF &rect, const QPixmap &pixmap,
+ const QPointF &subRect)
+{
+ if ( d_device )
+ d_device->drawTiledPixmap( rect, pixmap, subRect );
+}
+
+void QwtNullPaintDevice::PaintEngine::drawImage(
+ const QRectF &rect, const QImage &image,
+ const QRectF &subRect, Qt::ImageConversionFlags flags)
+{
+ if ( d_device )
+ d_device->drawImage( rect, image, subRect, flags );
+}
+
+void QwtNullPaintDevice::PaintEngine::updateState(
+ const QPaintEngineState &state)
+{
+ if ( d_device )
+ d_device->updateState( state );
+}
+
+//! Constructor
+QwtNullPaintDevice::QwtNullPaintDevice(
+ QPaintEngine::PaintEngineFeatures features )
+{
+ init( features );
+}
+
+//! Constructor
+QwtNullPaintDevice::QwtNullPaintDevice( const QSize &size,
+ QPaintEngine::PaintEngineFeatures features )
+{
+ init( features );
+ d_data->size = size;
+}
+
+void QwtNullPaintDevice::init(
+ QPaintEngine::PaintEngineFeatures features )
+{
+ d_engine = new PaintEngine( features );
+ d_data = new PrivateData;
+}
+
+//! Destructor
+QwtNullPaintDevice::~QwtNullPaintDevice()
+{
+ delete d_engine;
+ delete d_data;
+}
+
+/*!
+ Set the size of the paint device
+
+ \param size Size
+ \sa size()
+*/
+void QwtNullPaintDevice::setSize( const QSize & size )
+{
+ d_data->size = size;
+}
+
+/*!
+ \return Size of the paint device
+ \sa setSize()
+*/
+QSize QwtNullPaintDevice::size() const
+{
+ return d_data->size;
+}
+
+//! See QPaintDevice::paintEngine()
+QPaintEngine *QwtNullPaintDevice::paintEngine() const
+{
+ return d_engine;
+}
+
+/*!
+ See QPaintDevice::metric()
+ \sa setSize()
+*/
+int QwtNullPaintDevice::metric( PaintDeviceMetric metric ) const
+{
+ static QPixmap pm;
+
+ int value;
+
+ switch ( metric )
+ {
+ case PdmWidth:
+ value = qMax( d_data->size.width(), 0 );
+ break;
+ case PdmHeight:
+ value = qMax( d_data->size.height(), 0 );
+ break;
+ case PdmNumColors:
+ value = 16777216;
+ break;
+ case PdmDepth:
+ value = 24;
+ break;
+ case PdmPhysicalDpiX:
+ case PdmDpiY:
+ case PdmPhysicalDpiY:
+ case PdmWidthMM:
+ case PdmHeightMM:
+ case PdmDpiX:
+ default:
+ value = 0;
+ }
+ return value;
+
+}
+
+//! See QPaintEngine::drawRects()
+void QwtNullPaintDevice::drawRects(
+ const QRect *rects, int rectCount)
+{
+ Q_UNUSED(rects);
+ Q_UNUSED(rectCount);
+}
+
+//! See QPaintEngine::drawRects()
+void QwtNullPaintDevice::drawRects(
+ const QRectF *rects, int rectCount)
+{
+ Q_UNUSED(rects);
+ Q_UNUSED(rectCount);
+}
+
+//! See QPaintEngine::drawLines()
+void QwtNullPaintDevice::drawLines(
+ const QLine *lines, int lineCount)
+{
+ Q_UNUSED(lines);
+ Q_UNUSED(lineCount);
+}
+
+//! See QPaintEngine::drawLines()
+void QwtNullPaintDevice::drawLines(
+ const QLineF *lines, int lineCount)
+{
+ Q_UNUSED(lines);
+ Q_UNUSED(lineCount);
+}
+
+//! See QPaintEngine::drawEllipse()
+void QwtNullPaintDevice::drawEllipse( const QRectF &rect )
+{
+ Q_UNUSED(rect);
+}
+
+//! See QPaintEngine::drawEllipse()
+void QwtNullPaintDevice::drawEllipse( const QRect &rect )
+{
+ Q_UNUSED(rect);
+}
+
+//! See QPaintEngine::drawPath()
+void QwtNullPaintDevice::drawPath( const QPainterPath &path )
+{
+ Q_UNUSED(path);
+}
+
+//! See QPaintEngine::drawPoints()
+void QwtNullPaintDevice::drawPoints(
+ const QPointF *points, int pointCount)
+{
+ Q_UNUSED(points);
+ Q_UNUSED(pointCount);
+}
+
+//! See QPaintEngine::drawPoints()
+void QwtNullPaintDevice::drawPoints(
+ const QPoint *points, int pointCount)
+{
+ Q_UNUSED(points);
+ Q_UNUSED(pointCount);
+}
+
+//! See QPaintEngine::drawPolygon()
+void QwtNullPaintDevice::drawPolygon(
+ const QPointF *points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode)
+{
+ Q_UNUSED(points);
+ Q_UNUSED(pointCount);
+ Q_UNUSED(mode);
+}
+
+//! See QPaintEngine::drawPolygon()
+void QwtNullPaintDevice::drawPolygon(
+ const QPoint *points, int pointCount,
+ QPaintEngine::PolygonDrawMode mode)
+{
+ Q_UNUSED(points);
+ Q_UNUSED(pointCount);
+ Q_UNUSED(mode);
+}
+
+//! See QPaintEngine::drawPixmap()
+void QwtNullPaintDevice::drawPixmap( const QRectF &rect,
+ const QPixmap &pm, const QRectF &subRect )
+{
+ Q_UNUSED(rect);
+ Q_UNUSED(pm);
+ Q_UNUSED(subRect);
+}
+
+//! See QPaintEngine::drawTextItem()
+void QwtNullPaintDevice::drawTextItem(
+ const QPointF &pos, const QTextItem &textItem)
+{
+ Q_UNUSED(pos);
+ Q_UNUSED(textItem);
+}
+
+//! See QPaintEngine::drawTiledPixmap()
+void QwtNullPaintDevice::drawTiledPixmap(
+ const QRectF &rect, const QPixmap &pixmap,
+ const QPointF &subRect)
+{
+ Q_UNUSED(rect);
+ Q_UNUSED(pixmap);
+ Q_UNUSED(subRect);
+}
+
+//! See QPaintEngine::drawImage()
+void QwtNullPaintDevice::drawImage(
+ const QRectF &rect, const QImage &image,
+ const QRectF &subRect, Qt::ImageConversionFlags flags)
+{
+ Q_UNUSED(rect);
+ Q_UNUSED(image);
+ Q_UNUSED(subRect);
+ Q_UNUSED(flags);
+}
+
+//! See QPaintEngine::updateState()
+void QwtNullPaintDevice::updateState(
+ const QPaintEngineState &state )
+{
+ Q_UNUSED(state);
+}
diff --git a/src/libpcp_qwt/src/qwt_null_paintdevice.h b/src/libpcp_qwt/src/qwt_null_paintdevice.h
new file mode 100644
index 0000000..aae9c2d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_null_paintdevice.h
@@ -0,0 +1,89 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_NULL_PAINT_DEVICE_H
+#define QWT_NULL_PAINT_DEVICE_H 1
+
+#include "qwt_global.h"
+#include <qpaintdevice.h>
+#include <qpaintengine.h>
+
+/*!
+ \brief A null paint device doing nothing
+
+ Sometimes important layout/rendering geometries are not
+ available or changable from the public Qt class interface.
+ ( f.e hidden in the style implementation ).
+
+ QwtNullPaintDevice can be used to manipulate or filter out
+ these informations by analyzing the stream of paint primitives.
+
+ F.e. QwtNullPaintDevice is used by QwtPlotCanvas to identify
+ styled backgrounds with rounded corners.
+*/
+
+class QWT_EXPORT QwtNullPaintDevice: public QPaintDevice
+{
+public:
+ QwtNullPaintDevice( QPaintEngine::PaintEngineFeatures );
+ QwtNullPaintDevice( const QSize &size,
+ QPaintEngine::PaintEngineFeatures );
+
+ virtual ~QwtNullPaintDevice();
+
+ void setSize( const QSize &);
+ QSize size() const;
+
+ virtual QPaintEngine *paintEngine() const;
+ virtual int metric( PaintDeviceMetric metric ) const;
+
+ virtual void drawRects(const QRect *, int );
+ virtual void drawRects(const QRectF *, int );
+
+ virtual void drawLines(const QLine *, int );
+ virtual void drawLines(const QLineF *, int );
+
+ virtual void drawEllipse(const QRectF &);
+ virtual void drawEllipse(const QRect &);
+
+ virtual void drawPath(const QPainterPath &);
+
+ virtual void drawPoints(const QPointF *, int );
+ virtual void drawPoints(const QPoint *, int );
+
+ virtual void drawPolygon(
+ const QPointF *, int , QPaintEngine::PolygonDrawMode );
+
+ virtual void drawPolygon(
+ const QPoint *, int , QPaintEngine::PolygonDrawMode );
+
+ virtual void drawPixmap(const QRectF &,
+ const QPixmap &, const QRectF &);
+
+ virtual void drawTextItem(const QPointF &, const QTextItem &);
+
+ virtual void drawTiledPixmap(const QRectF &,
+ const QPixmap &, const QPointF &s);
+
+ virtual void drawImage(const QRectF &,
+ const QImage &, const QRectF &, Qt::ImageConversionFlags );
+
+ virtual void updateState( const QPaintEngineState &state );
+
+private:
+ void init( QPaintEngine::PaintEngineFeatures );
+
+ class PaintEngine;
+ PaintEngine *d_engine;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_painter.cpp b/src/libpcp_qwt/src/qwt_painter.cpp
new file mode 100644
index 0000000..adc0859
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_painter.cpp
@@ -0,0 +1,765 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_painter.h"
+#include "qwt_math.h"
+#include "qwt_clipper.h"
+#include "qwt_color_map.h"
+#include "qwt_scale_map.h"
+#include <qwindowdefs.h>
+#include <qwidget.h>
+#include <qframe.h>
+#include <qrect.h>
+#include <qpainter.h>
+#include <qpalette.h>
+#include <qpaintdevice.h>
+#include <qpixmap.h>
+#include <qstyle.h>
+#include <qtextdocument.h>
+#include <qabstracttextdocumentlayout.h>
+#include <qstyleoption.h>
+#include <qpaintengine.h>
+#include <qapplication.h>
+#include <qdesktopwidget.h>
+
+bool QwtPainter::d_polylineSplitting = true;
+bool QwtPainter::d_roundingAlignment = true;
+
+static inline bool isClippingNeeded( const QPainter *painter, QRectF &clipRect )
+{
+ bool doClipping = false;
+ const QPaintEngine *pe = painter->paintEngine();
+ if ( pe && pe->type() == QPaintEngine::SVG )
+ {
+ // The SVG paint engine ignores any clipping,
+
+ if ( painter->hasClipping() )
+ {
+ doClipping = true;
+ clipRect = painter->clipRegion().boundingRect();
+ }
+ }
+
+ return doClipping;
+}
+
+static inline void drawPolyline( QPainter *painter,
+ const QPointF *points, int pointCount, bool polylineSplitting )
+{
+ bool doSplit = false;
+ if ( polylineSplitting )
+ {
+ const QPaintEngine *pe = painter->paintEngine();
+ if ( pe && pe->type() == QPaintEngine::Raster )
+ {
+ /*
+ The raster paint engine seems to use some algo with O(n*n).
+ ( Qt 4.3 is better than Qt 4.2, but remains unacceptable)
+ To work around this problem, we have to split the polygon into
+ smaller pieces.
+ */
+ doSplit = true;
+ }
+ }
+
+ if ( doSplit )
+ {
+ const int splitSize = 20;
+ for ( int i = 0; i < pointCount; i += splitSize )
+ {
+ const int n = qMin( splitSize + 1, pointCount - i );
+ painter->drawPolyline( points + i, n );
+ }
+ }
+ else
+ painter->drawPolyline( points, pointCount );
+}
+
+static inline void unscaleFont( QPainter *painter )
+{
+ if ( painter->font().pixelSize() >= 0 )
+ return;
+
+ static QSize screenResolution;
+ if ( !screenResolution.isValid() )
+ {
+ QDesktopWidget *desktop = QApplication::desktop();
+ if ( desktop )
+ {
+ screenResolution.setWidth( desktop->logicalDpiX() );
+ screenResolution.setHeight( desktop->logicalDpiY() );
+ }
+ }
+
+ const QPaintDevice *pd = painter->device();
+ if ( pd->logicalDpiX() != screenResolution.width() ||
+ pd->logicalDpiY() != screenResolution.height() )
+ {
+ QFont pixelFont( painter->font(), QApplication::desktop() );
+ pixelFont.setPixelSize( QFontInfo( pixelFont ).pixelSize() );
+
+ painter->setFont( pixelFont );
+ }
+}
+
+/*!
+ Check if the painter is using a paint engine, that aligns
+ coordinates to integers. Today these are all paint engines
+ beside QPaintEngine::Pdf and QPaintEngine::SVG.
+
+ \param painter Painter
+ \return true, when the paint engine is aligning
+
+ \sa setRoundingAlignment()
+*/
+bool QwtPainter::isAligning( QPainter *painter )
+{
+ if ( painter && painter->isActive() )
+ {
+ switch ( painter->paintEngine()->type() )
+ {
+ case QPaintEngine::Pdf:
+ case QPaintEngine::SVG:
+ return false;
+
+ default:;
+ }
+ }
+
+ return true;
+}
+
+/*!
+ Enable whether coordinates should be rounded, before they are painted
+ to a paint engine that floors to integer values. For other paint engines
+ this ( Pdf, SVG ), this flag has no effect.
+ QwtPainter stores this flag only, the rounding itsself is done in
+ the painting code ( f.e the plot items ).
+
+ The default setting is true.
+
+ \sa roundingAlignment(), isAligning()
+*/
+void QwtPainter::setRoundingAlignment( bool enable )
+{
+ d_roundingAlignment = enable;
+}
+
+/*!
+ \brief En/Disable line splitting for the raster paint engine
+
+ The raster paint engine paints polylines of many points
+ much faster when they are splitted in smaller chunks.
+
+ \sa polylineSplitting()
+*/
+void QwtPainter::setPolylineSplitting( bool enable )
+{
+ d_polylineSplitting = enable;
+}
+
+//! Wrapper for QPainter::drawPath()
+void QwtPainter::drawPath( QPainter *painter, const QPainterPath &path )
+{
+ painter->drawPath( path );
+}
+
+//! Wrapper for QPainter::drawRect()
+void QwtPainter::drawRect( QPainter *painter, double x, double y, double w, double h )
+{
+ drawRect( painter, QRectF( x, y, w, h ) );
+}
+
+//! Wrapper for QPainter::drawRect()
+void QwtPainter::drawRect( QPainter *painter, const QRectF &rect )
+{
+ const QRectF r = rect;
+
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping )
+ {
+ if ( !clipRect.intersects( r ) )
+ return;
+
+ if ( !clipRect.contains( r ) )
+ {
+ fillRect( painter, r & clipRect, painter->brush() );
+
+ painter->save();
+ painter->setBrush( Qt::NoBrush );
+ drawPolyline( painter, QPolygonF( r ) );
+ painter->restore();
+
+ return;
+ }
+ }
+
+ painter->drawRect( r );
+}
+
+//! Wrapper for QPainter::fillRect()
+void QwtPainter::fillRect( QPainter *painter,
+ const QRectF &rect, const QBrush &brush )
+{
+ if ( !rect.isValid() )
+ return;
+
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ /*
+ Performance of Qt4 is horrible for non trivial brushs. Without
+ clipping expect minutes or hours for repainting large rects
+ (might result from zooming)
+ */
+
+ if ( deviceClipping )
+ clipRect &= painter->window();
+ else
+ clipRect = painter->window();
+
+ if ( painter->hasClipping() )
+ clipRect &= painter->clipRegion().boundingRect();
+
+ QRectF r = rect;
+ if ( deviceClipping )
+ r = r.intersect( clipRect );
+
+ if ( r.isValid() )
+ painter->fillRect( r, brush );
+}
+
+//! Wrapper for QPainter::drawPie()
+void QwtPainter::drawPie( QPainter *painter, const QRectF &rect,
+ int a, int alen )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+ if ( deviceClipping && !clipRect.contains( rect ) )
+ return;
+
+ painter->drawPie( rect, a, alen );
+}
+
+//! Wrapper for QPainter::drawEllipse()
+void QwtPainter::drawEllipse( QPainter *painter, const QRectF &rect )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping && !clipRect.contains( rect ) )
+ return;
+
+ painter->drawEllipse( rect );
+}
+
+//! Wrapper for QPainter::drawText()
+void QwtPainter::drawText( QPainter *painter, double x, double y,
+ const QString &text )
+{
+ drawText( painter, QPointF( x, y ), text );
+}
+
+//! Wrapper for QPainter::drawText()
+void QwtPainter::drawText( QPainter *painter, const QPointF &pos,
+ const QString &text )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping && !clipRect.contains( pos ) )
+ return;
+
+
+ painter->save();
+ unscaleFont( painter );
+ painter->drawText( pos, text );
+ painter->restore();
+}
+
+//! Wrapper for QPainter::drawText()
+void QwtPainter::drawText( QPainter *painter,
+ double x, double y, double w, double h,
+ int flags, const QString &text )
+{
+ drawText( painter, QRectF( x, y, w, h ), flags, text );
+}
+
+//! Wrapper for QPainter::drawText()
+void QwtPainter::drawText( QPainter *painter, const QRectF &rect,
+ int flags, const QString &text )
+{
+ painter->save();
+ unscaleFont( painter );
+ painter->drawText( rect, flags, text );
+ painter->restore();
+}
+
+#ifndef QT_NO_RICHTEXT
+
+/*!
+ Draw a text document into a rectangle
+
+ \param painter Painter
+ \param rect Traget rectangle
+ \param flags Alignments/Text flags, see QPainter::drawText()
+ \param text Text document
+*/
+void QwtPainter::drawSimpleRichText( QPainter *painter, const QRectF &rect,
+ int flags, const QTextDocument &text )
+{
+ QTextDocument *txt = text.clone();
+
+ painter->save();
+
+ painter->setFont( txt->defaultFont() );
+ unscaleFont( painter );
+
+ txt->setDefaultFont( painter->font() );
+ txt->setPageSize( QSizeF( rect.width(), QWIDGETSIZE_MAX ) );
+
+ QAbstractTextDocumentLayout* layout = txt->documentLayout();
+
+ const double height = layout->documentSize().height();
+ double y = rect.y();
+ if ( flags & Qt::AlignBottom )
+ y += ( rect.height() - height );
+ else if ( flags & Qt::AlignVCenter )
+ y += ( rect.height() - height ) / 2;
+
+ QAbstractTextDocumentLayout::PaintContext context;
+ context.palette.setColor( QPalette::Text, painter->pen().color() );
+
+ painter->translate( rect.x(), y );
+ layout->draw( painter, context );
+
+ painter->restore();
+ delete txt;
+}
+
+#endif // !QT_NO_RICHTEXT
+
+
+//! Wrapper for QPainter::drawLine()
+void QwtPainter::drawLine( QPainter *painter,
+ const QPointF &p1, const QPointF &p2 )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping &&
+ !( clipRect.contains( p1 ) && clipRect.contains( p2 ) ) )
+ {
+ QPolygonF polygon;
+ polygon += p1;
+ polygon += p2;
+ drawPolyline( painter, polygon );
+ return;
+ }
+
+ painter->drawLine( p1, p2 );
+}
+
+//! Wrapper for QPainter::drawPolygon()
+void QwtPainter::drawPolygon( QPainter *painter, const QPolygonF &polygon )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ QPolygonF cpa = polygon;
+ if ( deviceClipping )
+ cpa = QwtClipper::clipPolygonF( clipRect, polygon );
+
+ painter->drawPolygon( cpa );
+}
+
+//! Wrapper for QPainter::drawPolyline()
+void QwtPainter::drawPolyline( QPainter *painter, const QPolygonF &polygon )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ QPolygonF cpa = polygon;
+ if ( deviceClipping )
+ cpa = QwtClipper::clipPolygonF( clipRect, cpa );
+
+ ::drawPolyline( painter,
+ cpa.constData(), cpa.size(), d_polylineSplitting );
+}
+
+//! Wrapper for QPainter::drawPolyline()
+void QwtPainter::drawPolyline( QPainter *painter,
+ const QPointF *points, int pointCount )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping )
+ {
+ QPolygonF polygon( pointCount );
+ qMemCopy( polygon.data(), points, pointCount * sizeof( QPointF ) );
+
+ polygon = QwtClipper::clipPolygonF( clipRect, polygon );
+ ::drawPolyline( painter,
+ polygon.constData(), polygon.size(), d_polylineSplitting );
+ }
+ else
+ ::drawPolyline( painter, points, pointCount, d_polylineSplitting );
+}
+
+//! Wrapper for QPainter::drawPoint()
+void QwtPainter::drawPoint( QPainter *painter, const QPointF &pos )
+{
+ QRectF clipRect;
+ const bool deviceClipping = isClippingNeeded( painter, clipRect );
+
+ if ( deviceClipping && !clipRect.contains( pos ) )
+ return;
+
+ painter->drawPoint( pos );
+}
+
+//! Wrapper for QPainter::drawImage()
+void QwtPainter::drawImage( QPainter *painter,
+ const QRectF &rect, const QImage &image )
+{
+ const QRect alignedRect = rect.toAlignedRect();
+
+ if ( alignedRect != rect )
+ {
+ const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 );
+
+ painter->save();
+ painter->setClipRect( clipRect, Qt::IntersectClip );
+ painter->drawImage( alignedRect, image );
+ painter->restore();
+ }
+ else
+ {
+ painter->drawImage( alignedRect, image );
+ }
+}
+
+//! Wrapper for QPainter::drawPixmap()
+void QwtPainter::drawPixmap( QPainter *painter,
+ const QRectF &rect, const QPixmap &pixmap )
+{
+ const QRect alignedRect = rect.toAlignedRect();
+
+ if ( alignedRect != rect )
+ {
+ const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 );
+
+ painter->save();
+ painter->setClipRect( clipRect, Qt::IntersectClip );
+ painter->drawPixmap( alignedRect, pixmap );
+ painter->restore();
+ }
+ else
+ {
+ painter->drawPixmap( alignedRect, pixmap );
+ }
+}
+
+//! Draw a focus rectangle on a widget using its style.
+void QwtPainter::drawFocusRect( QPainter *painter, QWidget *widget )
+{
+ drawFocusRect( painter, widget, widget->rect() );
+}
+
+//! Draw a focus rectangle on a widget using its style.
+void QwtPainter::drawFocusRect( QPainter *painter, QWidget *widget,
+ const QRect &rect )
+{
+ QStyleOptionFocusRect opt;
+ opt.init( widget );
+ opt.rect = rect;
+ opt.state |= QStyle::State_HasFocus;
+
+ widget->style()->drawPrimitive( QStyle::PE_FrameFocusRect,
+ &opt, painter, widget );
+}
+
+/*!
+ Draw a frame with rounded borders
+
+ \param painter Painter
+ \param rect Frame rectangle
+ \param xRadius x-radius of the ellipses defining the corners
+ \param yRadius y-radius of the ellipses defining the corners
+ \param palette QPalette::WindowText is used for plain borders
+ QPalette::Dark and QPalette::Light for raised
+ or sunken borders
+ \param lineWidth Line width
+ \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow
+*/
+
+void QwtPainter::drawRoundedFrame( QPainter *painter,
+ const QRectF &rect, double xRadius, double yRadius,
+ const QPalette &palette, int lineWidth, int frameStyle )
+{
+ painter->save();
+ painter->setRenderHint( QPainter::Antialiasing, true );
+ painter->setBrush( Qt::NoBrush );
+
+ double lw2 = lineWidth * 0.5;
+ QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 );
+
+ QPainterPath path;
+ path.addRoundedRect( r, xRadius, yRadius );
+
+ enum Style
+ {
+ Plain,
+ Sunken,
+ Raised
+ };
+
+ Style style = Plain;
+ if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken )
+ style = Sunken;
+ else if ( (frameStyle & QFrame::Raised) == QFrame::Raised )
+ style = Raised;
+
+ if ( style != Plain && path.elementCount() == 17 )
+ {
+ // move + 4 * ( cubicTo + lineTo )
+ QPainterPath pathList[8];
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ const int j = i * 4 + 1;
+
+ pathList[ 2 * i ].moveTo(
+ path.elementAt(j - 1).x, path.elementAt( j - 1 ).y
+ );
+
+ pathList[ 2 * i ].cubicTo(
+ path.elementAt(j + 0).x, path.elementAt(j + 0).y,
+ path.elementAt(j + 1).x, path.elementAt(j + 1).y,
+ path.elementAt(j + 2).x, path.elementAt(j + 2).y );
+
+ pathList[ 2 * i + 1 ].moveTo(
+ path.elementAt(j + 2).x, path.elementAt(j + 2).y
+ );
+ pathList[ 2 * i + 1 ].lineTo(
+ path.elementAt(j + 3).x, path.elementAt(j + 3).y
+ );
+ }
+
+ QColor c1( palette.color( QPalette::Dark ) );
+ QColor c2( palette.color( QPalette::Light ) );
+
+ if ( style == Raised )
+ qSwap( c1, c2 );
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ QRectF r = pathList[2 * i].controlPointRect();
+
+ QPen arcPen;
+ arcPen.setWidth( lineWidth );
+
+ QPen linePen;
+ linePen.setWidth( lineWidth );
+
+ switch( i )
+ {
+ case 0:
+ {
+ arcPen.setColor( c1 );
+ linePen.setColor( c1 );
+ break;
+ }
+ case 1:
+ {
+ QLinearGradient gradient;
+ gradient.setStart( r.topLeft() );
+ gradient.setFinalStop( r.bottomRight() );
+ gradient.setColorAt( 0.0, c1 );
+ gradient.setColorAt( 1.0, c2 );
+
+ arcPen.setBrush( gradient );
+ linePen.setColor( c2 );
+ break;
+ }
+ case 2:
+ {
+ arcPen.setColor( c2 );
+ linePen.setColor( c2 );
+ break;
+ }
+ case 3:
+ {
+ QLinearGradient gradient;
+
+ gradient.setStart( r.bottomRight() );
+ gradient.setFinalStop( r.topLeft() );
+ gradient.setColorAt( 0.0, c2 );
+ gradient.setColorAt( 1.0, c1 );
+
+ arcPen.setBrush( gradient );
+ linePen.setColor( c1 );
+ break;
+ }
+ }
+
+
+ painter->setPen( arcPen );
+ painter->drawPath( pathList[ 2 * i] );
+
+ painter->setPen( linePen );
+ painter->drawPath( pathList[ 2 * i + 1] );
+ }
+ }
+ else
+ {
+ QPen pen( palette.color( QPalette::WindowText ), lineWidth );
+ painter->setPen( pen );
+ painter->drawPath( path );
+ }
+
+ painter->restore();
+}
+
+/*!
+ Draw a color bar into a rectangle
+
+ \param painter Painter
+ \param colorMap Color map
+ \param interval Value range
+ \param scaleMap Scale map
+ \param orientation Orientation
+ \param rect Traget rectangle
+*/
+void QwtPainter::drawColorBar( QPainter *painter,
+ const QwtColorMap &colorMap, const QwtInterval &interval,
+ const QwtScaleMap &scaleMap, Qt::Orientation orientation,
+ const QRectF &rect )
+{
+ QVector<QRgb> colorTable;
+ if ( colorMap.format() == QwtColorMap::Indexed )
+ colorTable = colorMap.colorTable( interval );
+
+ QColor c;
+
+ const QRect devRect = rect.toAlignedRect();
+
+ /*
+ We paint to a pixmap first to have something scalable for printing
+ ( f.e. in a Pdf document )
+ */
+
+ QPixmap pixmap( devRect.size() );
+ QPainter pmPainter( &pixmap );
+ pmPainter.translate( -devRect.x(), -devRect.y() );
+
+ if ( orientation == Qt::Horizontal )
+ {
+ QwtScaleMap sMap = scaleMap;
+ sMap.setPaintInterval( rect.left(), rect.right() );
+
+ for ( int x = devRect.left(); x <= devRect.right(); x++ )
+ {
+ const double value = sMap.invTransform( x );
+
+ if ( colorMap.format() == QwtColorMap::RGB )
+ c.setRgb( colorMap.rgb( interval, value ) );
+ else
+ c = colorTable[colorMap.colorIndex( interval, value )];
+
+ pmPainter.setPen( c );
+ pmPainter.drawLine( x, devRect.top(), x, devRect.bottom() );
+ }
+ }
+ else // Vertical
+ {
+ QwtScaleMap sMap = scaleMap;
+ sMap.setPaintInterval( rect.bottom(), rect.top() );
+
+ for ( int y = devRect.top(); y <= devRect.bottom(); y++ )
+ {
+ const double value = sMap.invTransform( y );
+
+ if ( colorMap.format() == QwtColorMap::RGB )
+ c.setRgb( colorMap.rgb( interval, value ) );
+ else
+ c = colorTable[colorMap.colorIndex( interval, value )];
+
+ pmPainter.setPen( c );
+ pmPainter.drawLine( devRect.left(), y, devRect.right(), y );
+ }
+ }
+ pmPainter.end();
+
+ drawPixmap( painter, rect, pixmap );
+}
+
+#if QT_VERSION >= 0x050000
+
+static inline void qwtFillRect(QPainter *painter, const QRect &rect, const QBrush &brush)
+{
+ if ( brush.style() == Qt::TexturePattern )
+ {
+ painter->setClipRect( rect );
+ painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft());
+ }
+ else if (brush.gradient()
+ && brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode)
+ {
+ painter->save();
+ painter->setClipRect( rect );
+ painter->fillRect(0, 0, painter->device()->width(),
+ painter->device()->height(), brush);
+ painter->restore();
+ }
+ else
+ {
+ painter->fillRect(rect, brush);
+ }
+}
+
+void QwtPainter::fillPixmap( const QWidget *widget,
+ QPixmap &pixmap, const QPoint &offset )
+{
+ // Qwt 5.0.0 Alpha offers an empty dummy implementation
+ // of QPixmap::fill, that does nothing helpful beside converting
+ // a compiler into a runtime error
+
+ const QRect rect( offset, pixmap.size() );
+
+ QPainter painter( &pixmap );
+ painter.translate( -offset );
+
+ const QBrush autoFillBrush =
+ widget->palette().brush( widget->backgroundRole() );
+
+ if ( !( widget->autoFillBackground() && autoFillBrush.isOpaque() ) )
+ {
+ const QBrush bg = widget->palette().brush( QPalette::Window );
+ qwtFillRect( &painter, rect, bg);
+ }
+
+ if ( widget->autoFillBackground() )
+ qwtFillRect( &painter, rect, autoFillBrush);
+
+ if ( widget->testAttribute(Qt::WA_StyledBackground) )
+ {
+ painter.setClipRegion( rect );
+
+ QStyleOption opt;
+ opt.initFrom( widget );
+ widget->style()->drawPrimitive( QStyle::PE_Widget,
+ &opt, &painter, widget );
+ }
+}
+
+#endif // QT_VERSION >= 0x050000
diff --git a/src/libpcp_qwt/src/qwt_painter.h b/src/libpcp_qwt/src/qwt_painter.h
new file mode 100644
index 0000000..3235099
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_painter.h
@@ -0,0 +1,154 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PAINTER_H
+#define QWT_PAINTER_H
+
+#include "qwt_global.h"
+
+#include <qpoint.h>
+#include <qrect.h>
+#include <qpen.h>
+#include <qline.h>
+
+class QPainter;
+class QBrush;
+class QColor;
+class QWidget;
+class QPolygonF;
+class QRectF;
+class QImage;
+class QPixmap;
+class QwtScaleMap;
+class QwtColorMap;
+class QwtInterval;
+
+class QPalette;
+class QTextDocument;
+class QPainterPath;
+
+/*!
+ \brief A collection of QPainter workarounds
+*/
+class QWT_EXPORT QwtPainter
+{
+public:
+ static void setPolylineSplitting( bool );
+ static bool polylineSplitting();
+
+ static void setRoundingAlignment( bool );
+ static bool roundingAlignment();
+ static bool roundingAlignment(QPainter *);
+
+ static void drawText( QPainter *, double x, double y, const QString & );
+ static void drawText( QPainter *, const QPointF &, const QString & );
+ static void drawText( QPainter *, double x, double y, double w, double h,
+ int flags, const QString & );
+ static void drawText( QPainter *, const QRectF &,
+ int flags, const QString & );
+
+#ifndef QT_NO_RICHTEXT
+ static void drawSimpleRichText( QPainter *, const QRectF &,
+ int flags, const QTextDocument & );
+#endif
+
+ static void drawRect( QPainter *, double x, double y, double w, double h );
+ static void drawRect( QPainter *, const QRectF &rect );
+ static void fillRect( QPainter *, const QRectF &, const QBrush & );
+
+ static void drawEllipse( QPainter *, const QRectF & );
+ static void drawPie( QPainter *, const QRectF & r, int a, int alen );
+
+ static void drawLine( QPainter *, double x1, double y1, double x2, double y2 );
+ static void drawLine( QPainter *, const QPointF &p1, const QPointF &p2 );
+ static void drawLine( QPainter *, const QLineF & );
+
+ static void drawPolygon( QPainter *, const QPolygonF &pa );
+ static void drawPolyline( QPainter *, const QPolygonF &pa );
+ static void drawPolyline( QPainter *, const QPointF *, int pointCount );
+
+ static void drawPoint( QPainter *, double x, double y );
+ static void drawPoint( QPainter *, const QPointF & );
+
+ static void drawPath( QPainter *, const QPainterPath & );
+ static void drawImage( QPainter *, const QRectF &, const QImage & );
+ static void drawPixmap( QPainter *, const QRectF &, const QPixmap & );
+
+ static void drawRoundedFrame( QPainter *,
+ const QRectF &, double xRadius, double yRadius,
+ const QPalette &, int lineWidth, int frameStyle );
+
+ static void drawFocusRect( QPainter *, QWidget * );
+ static void drawFocusRect( QPainter *, QWidget *, const QRect & );
+
+ static void drawColorBar( QPainter *painter,
+ const QwtColorMap &, const QwtInterval &,
+ const QwtScaleMap &, Qt::Orientation, const QRectF & );
+
+ static bool isAligning( QPainter *painter );
+
+#if QT_VERSION >= 0x050000
+ static void fillPixmap( const QWidget *,
+ QPixmap &, const QPoint &offset = QPoint() );
+#endif
+
+private:
+ static bool d_polylineSplitting;
+ static bool d_roundingAlignment;
+};
+
+//! Wrapper for QPainter::drawPoint()
+inline void QwtPainter::drawPoint( QPainter *painter, double x, double y )
+{
+ QwtPainter::drawPoint( painter, QPointF( x, y ) );
+}
+
+//! Wrapper for QPainter::drawLine()
+inline void QwtPainter::drawLine( QPainter *painter,
+ double x1, double y1, double x2, double y2 )
+{
+ QwtPainter::drawLine( painter, QPointF( x1, y1 ), QPointF( x2, y2 ) );
+}
+
+//! Wrapper for QPainter::drawLine()
+inline void QwtPainter::drawLine( QPainter *painter, const QLineF &line )
+{
+ QwtPainter::drawLine( painter, line.p1(), line.p2() );
+}
+
+/*!
+ Returns whether line splitting for the raster paint engine is enabled.
+ \sa setPolylineSplitting()
+*/
+inline bool QwtPainter::polylineSplitting()
+{
+ return d_polylineSplitting;
+}
+
+/*!
+ Returns whether coordinates should be rounded, before they are painted
+ to a paint engine that floors to integer values. For other paint engines
+ this ( Pdf, SVG ), this flag has no effect.
+
+ \sa setRoundingAlignment(), isAligning()
+*/
+inline bool QwtPainter::roundingAlignment()
+{
+ return d_roundingAlignment;
+}
+
+/*!
+ \return roundingAlignment() && isAligning(painter);
+ \param painter Painter
+*/
+inline bool QwtPainter::roundingAlignment(QPainter *painter)
+{
+ return d_roundingAlignment && isAligning(painter);
+}
+#endif
diff --git a/src/libpcp_qwt/src/qwt_panner.cpp b/src/libpcp_qwt/src/qwt_panner.cpp
new file mode 100644
index 0000000..709a2ea
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_panner.cpp
@@ -0,0 +1,537 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_panner.h"
+#include "qwt_picker.h"
+#if QT_VERSION >= 0x050000
+#include "qwt_painter.h"
+#endif
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qevent.h>
+#include <qcursor.h>
+#include <qbitmap.h>
+
+static QVector<QwtPicker *> qwtActivePickers( QWidget *w )
+{
+ QVector<QwtPicker *> pickers;
+
+ QObjectList children = w->children();
+ for ( int i = 0; i < children.size(); i++ )
+ {
+ QwtPicker *picker = qobject_cast<QwtPicker *>( children[i] );
+ if ( picker && picker->isEnabled() )
+ pickers += picker;
+ }
+
+ return pickers;
+}
+
+class QwtPanner::PrivateData
+{
+public:
+ PrivateData():
+ button( Qt::LeftButton ),
+ buttonState( Qt::NoButton ),
+ abortKey( Qt::Key_Escape ),
+ abortKeyState( Qt::NoButton ),
+#ifndef QT_NO_CURSOR
+ cursor( NULL ),
+ restoreCursor( NULL ),
+ hasCursor( false ),
+#endif
+ isEnabled( false )
+ {
+ orientations = Qt::Vertical | Qt::Horizontal;
+ }
+
+ ~PrivateData()
+ {
+#ifndef QT_NO_CURSOR
+ delete cursor;
+ delete restoreCursor;
+#endif
+ }
+
+ int button;
+ int buttonState;
+ int abortKey;
+ int abortKeyState;
+
+ QPoint initialPos;
+ QPoint pos;
+
+ QPixmap pixmap;
+ QBitmap contentsMask;
+
+#ifndef QT_NO_CURSOR
+ QCursor *cursor;
+ QCursor *restoreCursor;
+ bool hasCursor;
+#endif
+ bool isEnabled;
+ Qt::Orientations orientations;
+};
+
+/*!
+ Creates an panner that is enabled for the left mouse button.
+
+ \param parent Parent widget to be panned
+*/
+QwtPanner::QwtPanner( QWidget *parent ):
+ QWidget( parent )
+{
+ d_data = new PrivateData();
+
+ setAttribute( Qt::WA_TransparentForMouseEvents );
+ setAttribute( Qt::WA_NoSystemBackground );
+ setFocusPolicy( Qt::NoFocus );
+ hide();
+
+ setEnabled( true );
+}
+
+//! Destructor
+QwtPanner::~QwtPanner()
+{
+ delete d_data;
+}
+
+/*!
+ Change the mouse button
+ The defaults are Qt::LeftButton and Qt::NoButton
+*/
+void QwtPanner::setMouseButton( int button, int buttonState )
+{
+ d_data->button = button;
+ d_data->buttonState = buttonState;
+}
+
+//! Get the mouse button
+void QwtPanner::getMouseButton( int &button, int &buttonState ) const
+{
+ button = d_data->button;
+ buttonState = d_data->buttonState;
+}
+
+/*!
+ Change the abort key
+ The defaults are Qt::Key_Escape and Qt::NoButton
+
+ \param key Key ( See Qt::Keycode )
+ \param state State
+*/
+void QwtPanner::setAbortKey( int key, int state )
+{
+ d_data->abortKey = key;
+ d_data->abortKeyState = state;
+}
+
+//! Get the abort key
+void QwtPanner::getAbortKey( int &key, int &state ) const
+{
+ key = d_data->abortKey;
+ state = d_data->abortKeyState;
+}
+
+/*!
+ Change the cursor, that is active while panning
+ The default is the cursor of the parent widget.
+
+ \param cursor New cursor
+
+ \sa setCursor()
+*/
+#ifndef QT_NO_CURSOR
+void QwtPanner::setCursor( const QCursor &cursor )
+{
+ d_data->cursor = new QCursor( cursor );
+}
+#endif
+
+/*!
+ \return Cursor that is active while panning
+ \sa setCursor()
+*/
+#ifndef QT_NO_CURSOR
+const QCursor QwtPanner::cursor() const
+{
+ if ( d_data->cursor )
+ return *d_data->cursor;
+
+ if ( parentWidget() )
+ return parentWidget()->cursor();
+
+ return QCursor();
+}
+#endif
+
+/*!
+ \brief En/disable the panner
+
+ When enabled is true an event filter is installed for
+ the observed widget, otherwise the event filter is removed.
+
+ \param on true or false
+ \sa isEnabled(), eventFilter()
+*/
+void QwtPanner::setEnabled( bool on )
+{
+ if ( d_data->isEnabled != on )
+ {
+ d_data->isEnabled = on;
+
+ QWidget *w = parentWidget();
+ if ( w )
+ {
+ if ( d_data->isEnabled )
+ {
+ w->installEventFilter( this );
+ }
+ else
+ {
+ w->removeEventFilter( this );
+ hide();
+ }
+ }
+ }
+}
+
+/*!
+ Set the orientations, where panning is enabled
+ The default value is in both directions: Qt::Horizontal | Qt::Vertical
+
+ /param o Orientation
+*/
+void QwtPanner::setOrientations( Qt::Orientations o )
+{
+ d_data->orientations = o;
+}
+
+//! Return the orientation, where paning is enabled
+Qt::Orientations QwtPanner::orientations() const
+{
+ return d_data->orientations;
+}
+
+/*!
+ Return true if a orientatio is enabled
+ \sa orientations(), setOrientations()
+*/
+bool QwtPanner::isOrientationEnabled( Qt::Orientation o ) const
+{
+ return d_data->orientations & o;
+}
+
+/*!
+ \return true when enabled, false otherwise
+ \sa setEnabled, eventFilter()
+*/
+bool QwtPanner::isEnabled() const
+{
+ return d_data->isEnabled;
+}
+
+/*!
+ \brief Paint event
+
+ Repaint the grabbed pixmap on its current position and
+ fill the empty spaces by the background of the parent widget.
+
+ \param pe Paint event
+*/
+void QwtPanner::paintEvent( QPaintEvent *pe )
+{
+ int dx = d_data->pos.x() - d_data->initialPos.x();
+ int dy = d_data->pos.y() - d_data->initialPos.y();
+
+ QRect r( 0, 0, d_data->pixmap.width(), d_data->pixmap.height() );
+ r.moveCenter( QPoint( r.center().x() + dx, r.center().y() + dy ) );
+
+ QPixmap pm( size() );
+#if QT_VERSION >= 0x050000
+ QwtPainter::fillPixmap( parentWidget(), pm );
+#else
+ pm.fill( parentWidget(), 0, 0 );
+#endif
+
+ QPainter painter( &pm );
+
+ if ( !d_data->contentsMask.isNull() )
+ {
+ QPixmap masked = d_data->pixmap;
+ masked.setMask( d_data->contentsMask );
+ painter.drawPixmap( r, masked );
+ }
+ else
+ {
+ painter.drawPixmap( r, d_data->pixmap );
+ }
+
+ painter.end();
+
+ if ( !d_data->contentsMask.isNull() )
+ pm.setMask( d_data->contentsMask );
+
+ painter.begin( this );
+ painter.setClipRegion( pe->region() );
+ painter.drawPixmap( 0, 0, pm );
+}
+
+/*!
+ \brief Calculate a mask for the contents of the panned widget
+
+ Sometimes only parts of the contents of a widget should be
+ panned. F.e. for a widget with a styled background with rounded borders
+ only the area inside of the border should be panned.
+
+ \return An empty bitmap, indicating no mask
+*/
+QBitmap QwtPanner::contentsMask() const
+{
+ return QBitmap();
+}
+
+/*!
+ Grab the widget into a pixmap.
+*/
+QPixmap QwtPanner::grab() const
+{
+ return QPixmap::grabWidget( parentWidget() );
+}
+
+/*!
+ \brief Event filter
+
+ When isEnabled() the mouse events of the observed widget are filtered.
+
+ \param object Object to be filtered
+ \param event Event
+
+ \sa widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseMoveEvent()
+*/
+bool QwtPanner::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object == NULL || object != parentWidget() )
+ return false;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ widgetMousePressEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseMove:
+ {
+ widgetMouseMoveEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ widgetMouseReleaseEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ widgetKeyPressEvent( ( QKeyEvent * )event );
+ break;
+ }
+ case QEvent::KeyRelease:
+ {
+ widgetKeyReleaseEvent( ( QKeyEvent * )event );
+ break;
+ }
+ case QEvent::Paint:
+ {
+ if ( isVisible() )
+ return true;
+ break;
+ }
+ default:;
+ }
+
+ return false;
+}
+
+/*!
+ Handle a mouse press event for the observed widget.
+
+ \param mouseEvent Mouse event
+ \sa eventFilter(), widgetMouseReleaseEvent(),
+ widgetMouseMoveEvent(),
+*/
+void QwtPanner::widgetMousePressEvent( QMouseEvent *mouseEvent )
+{
+ if ( mouseEvent->button() != d_data->button )
+ return;
+
+ QWidget *w = parentWidget();
+ if ( w == NULL )
+ return;
+
+ if ( ( mouseEvent->modifiers() & Qt::KeyboardModifierMask ) !=
+ ( int )( d_data->buttonState & Qt::KeyboardModifierMask ) )
+ {
+ return;
+ }
+
+#ifndef QT_NO_CURSOR
+ showCursor( true );
+#endif
+
+ d_data->initialPos = d_data->pos = mouseEvent->pos();
+
+ setGeometry( parentWidget()->rect() );
+
+ // We don't want to grab the picker !
+ QVector<QwtPicker *> pickers = qwtActivePickers( parentWidget() );
+ for ( int i = 0; i < pickers.size(); i++ )
+ pickers[i]->setEnabled( false );
+
+ d_data->pixmap = grab();
+ d_data->contentsMask = contentsMask();
+
+ for ( int i = 0; i < pickers.size(); i++ )
+ pickers[i]->setEnabled( true );
+
+ show();
+}
+
+/*!
+ Handle a mouse move event for the observed widget.
+
+ \param mouseEvent Mouse event
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent()
+*/
+void QwtPanner::widgetMouseMoveEvent( QMouseEvent *mouseEvent )
+{
+ if ( !isVisible() )
+ return;
+
+ QPoint pos = mouseEvent->pos();
+ if ( !isOrientationEnabled( Qt::Horizontal ) )
+ pos.setX( d_data->initialPos.x() );
+ if ( !isOrientationEnabled( Qt::Vertical ) )
+ pos.setY( d_data->initialPos.y() );
+
+ if ( pos != d_data->pos && rect().contains( pos ) )
+ {
+ d_data->pos = pos;
+ update();
+
+ Q_EMIT moved( d_data->pos.x() - d_data->initialPos.x(),
+ d_data->pos.y() - d_data->initialPos.y() );
+ }
+}
+
+/*!
+ Handle a mouse release event for the observed widget.
+
+ \param mouseEvent Mouse event
+ \sa eventFilter(), widgetMousePressEvent(),
+ widgetMouseMoveEvent(),
+*/
+void QwtPanner::widgetMouseReleaseEvent( QMouseEvent *mouseEvent )
+{
+ if ( isVisible() )
+ {
+ hide();
+#ifndef QT_NO_CURSOR
+ showCursor( false );
+#endif
+
+ QPoint pos = mouseEvent->pos();
+ if ( !isOrientationEnabled( Qt::Horizontal ) )
+ pos.setX( d_data->initialPos.x() );
+ if ( !isOrientationEnabled( Qt::Vertical ) )
+ pos.setY( d_data->initialPos.y() );
+
+ d_data->pixmap = QPixmap();
+ d_data->contentsMask = QBitmap();
+ d_data->pos = pos;
+
+ if ( d_data->pos != d_data->initialPos )
+ {
+ Q_EMIT panned( d_data->pos.x() - d_data->initialPos.x(),
+ d_data->pos.y() - d_data->initialPos.y() );
+ }
+ }
+}
+
+/*!
+ Handle a key press event for the observed widget.
+
+ \param keyEvent Key event
+ \sa eventFilter(), widgetKeyReleaseEvent()
+*/
+void QwtPanner::widgetKeyPressEvent( QKeyEvent *keyEvent )
+{
+ if ( keyEvent->key() == d_data->abortKey )
+ {
+ const bool matched =
+ ( keyEvent->modifiers() & Qt::KeyboardModifierMask ) ==
+ ( int )( d_data->abortKeyState & Qt::KeyboardModifierMask );
+ if ( matched )
+ {
+ hide();
+#ifndef QT_NO_CURSOR
+ showCursor( false );
+#endif
+ d_data->pixmap = QPixmap();
+ }
+ }
+}
+
+/*!
+ Handle a key release event for the observed widget.
+
+ \param keyEvent Key event
+ \sa eventFilter(), widgetKeyReleaseEvent()
+*/
+void QwtPanner::widgetKeyReleaseEvent( QKeyEvent *keyEvent )
+{
+ Q_UNUSED( keyEvent );
+}
+
+#ifndef QT_NO_CURSOR
+void QwtPanner::showCursor( bool on )
+{
+ if ( on == d_data->hasCursor )
+ return;
+
+ QWidget *w = parentWidget();
+ if ( w == NULL || d_data->cursor == NULL )
+ return;
+
+ d_data->hasCursor = on;
+
+ if ( on )
+ {
+ if ( w->testAttribute( Qt::WA_SetCursor ) )
+ {
+ delete d_data->restoreCursor;
+ d_data->restoreCursor = new QCursor( w->cursor() );
+ }
+ w->setCursor( *d_data->cursor );
+ }
+ else
+ {
+ if ( d_data->restoreCursor )
+ {
+ w->setCursor( *d_data->restoreCursor );
+ delete d_data->restoreCursor;
+ d_data->restoreCursor = NULL;
+ }
+ else
+ w->unsetCursor();
+ }
+}
+#endif
diff --git a/src/libpcp_qwt/src/qwt_panner.h b/src/libpcp_qwt/src/qwt_panner.h
new file mode 100644
index 0000000..247d4a3
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_panner.h
@@ -0,0 +1,100 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PANNER_H
+#define QWT_PANNER_H 1
+
+#include "qwt_global.h"
+#include <qwidget.h>
+#include <qpixmap.h>
+
+class QCursor;
+
+/*!
+ \brief QwtPanner provides panning of a widget
+
+ QwtPanner grabs the contents of a widget, that can be dragged
+ in all directions. The offset between the start and the end position
+ is emitted by the panned signal.
+
+ QwtPanner grabs the content of the widget into a pixmap and moves
+ the pixmap around, without initiating any repaint events for the widget.
+ Areas, that are not part of content are not painted while panning.
+ This makes panning fast enough for widgets, where
+ repaints are too slow for mouse movements.
+
+ For widgets, where repaints are very fast it might be better to
+ implement panning manually by mapping mouse events into paint events.
+*/
+class QWT_EXPORT QwtPanner: public QWidget
+{
+ Q_OBJECT
+
+public:
+ QwtPanner( QWidget* parent );
+ virtual ~QwtPanner();
+
+ void setEnabled( bool );
+ bool isEnabled() const;
+
+ void setMouseButton( int button, int buttonState = Qt::NoButton );
+ void getMouseButton( int &button, int &buttonState ) const;
+ void setAbortKey( int key, int state = Qt::NoButton );
+ void getAbortKey( int &key, int &state ) const;
+
+ void setCursor( const QCursor & );
+ const QCursor cursor() const;
+
+ void setOrientations( Qt::Orientations );
+ Qt::Orientations orientations() const;
+
+ bool isOrientationEnabled( Qt::Orientation ) const;
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+Q_SIGNALS:
+ /*!
+ Signal emitted, when panning is done
+
+ \param dx Offset in horizontal direction
+ \param dy Offset in vertical direction
+ */
+ void panned( int dx, int dy );
+
+ /*!
+ Signal emitted, while the widget moved, but panning
+ is not finished.
+
+ \param dx Offset in horizontal direction
+ \param dy Offset in vertical direction
+ */
+ void moved( int dx, int dy );
+
+protected:
+ virtual void widgetMousePressEvent( QMouseEvent * );
+ virtual void widgetMouseReleaseEvent( QMouseEvent * );
+ virtual void widgetMouseMoveEvent( QMouseEvent * );
+ virtual void widgetKeyPressEvent( QKeyEvent * );
+ virtual void widgetKeyReleaseEvent( QKeyEvent * );
+
+ virtual void paintEvent( QPaintEvent * );
+
+ virtual QBitmap contentsMask() const;
+ virtual QPixmap grab() const;
+
+private:
+#ifndef QT_NO_CURSOR
+ void showCursor( bool );
+#endif
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_picker.cpp b/src/libpcp_qwt/src/qwt_picker.cpp
new file mode 100644
index 0000000..2b7afa1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_picker.cpp
@@ -0,0 +1,1462 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_picker.h"
+#include "qwt_picker_machine.h"
+#include "qwt_painter.h"
+#include "qwt_math.h"
+#include <qapplication.h>
+#include <qevent.h>
+#include <qpainter.h>
+#include <qframe.h>
+#include <qcursor.h>
+#include <qbitmap.h>
+#include <qpointer.h>
+#include <qpaintengine.h>
+#include <qmath.h>
+
+class QwtPicker::PickerWidget: public QWidget
+{
+public:
+ enum Type
+ {
+ RubberBand,
+ Text
+ };
+
+ PickerWidget( QwtPicker *, QWidget *, Type );
+ void updateMask();
+
+ /*
+ For a tracker text with a background we can use the background
+ rect as mask. Also for "regular" Qt widgets >= 4.3.0 we
+ don't need to mask the text anymore.
+ */
+ bool d_hasTextMask;
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+
+ QwtPicker *d_picker;
+ Type d_type;
+};
+
+class QwtPicker::PrivateData
+{
+public:
+ bool enabled;
+
+ QwtPickerMachine *stateMachine;
+
+ QwtPicker::ResizeMode resizeMode;
+
+ QwtPicker::RubberBand rubberBand;
+ QPen rubberBandPen;
+
+ QwtPicker::DisplayMode trackerMode;
+ QPen trackerPen;
+ QFont trackerFont;
+
+ QPolygon pickedPoints;
+ bool isActive;
+ QPoint trackerPosition;
+
+ bool mouseTracking; // used to save previous value
+
+ /*
+ On X11 the widget below the picker widgets gets paint events
+ with a region that is the bounding rect of the mask, if it is complex.
+ In case of (f.e) a CrossRubberBand and a text this creates complete
+ repaints of the widget. So we better use two different widgets.
+ */
+
+ QPointer<PickerWidget> rubberBandWidget;
+ QPointer<PickerWidget> trackerWidget;
+};
+
+QwtPicker::PickerWidget::PickerWidget(
+ QwtPicker *picker, QWidget *parent, Type type ):
+ QWidget( parent ),
+ d_hasTextMask( false ),
+ d_picker( picker ),
+ d_type( type )
+{
+ setAttribute( Qt::WA_TransparentForMouseEvents );
+ setAttribute( Qt::WA_NoSystemBackground );
+ setFocusPolicy( Qt::NoFocus );
+}
+
+void QwtPicker::PickerWidget::updateMask()
+{
+ QRegion mask;
+
+ if ( d_type == RubberBand )
+ {
+ QBitmap bm( width(), height() );
+ bm.fill( Qt::color0 );
+
+ QPainter painter( &bm );
+ QPen pen = d_picker->rubberBandPen();
+ pen.setColor( Qt::color1 );
+ painter.setPen( pen );
+
+ d_picker->drawRubberBand( &painter );
+
+ mask = QRegion( bm );
+ }
+ if ( d_type == Text )
+ {
+ d_hasTextMask = parentWidget()->testAttribute( Qt::WA_PaintOnScreen );
+
+ if ( d_hasTextMask )
+ {
+ const QwtText label = d_picker->trackerText(
+ d_picker->trackerPosition() );
+
+ if ( label.testPaintAttribute( QwtText::PaintBackground )
+ && label.backgroundBrush().style() != Qt::NoBrush )
+ {
+ if ( label.backgroundBrush().color().alpha() > 0 )
+ {
+ // We don't need a text mask, when we have a background
+ d_hasTextMask = false;
+ }
+ }
+ }
+
+ if ( d_hasTextMask )
+ {
+ QBitmap bm( width(), height() );
+ bm.fill( Qt::color0 );
+
+ QPainter painter( &bm );
+ painter.setFont( font() );
+
+ QPen pen = d_picker->trackerPen();
+ pen.setColor( Qt::color1 );
+ painter.setPen( pen );
+
+ d_picker->drawTracker( &painter );
+
+ mask = QRegion( bm );
+ }
+ else
+ {
+ mask = d_picker->trackerRect( font() );
+ }
+ }
+
+ QWidget *w = parentWidget();
+ if ( w && !w->testAttribute( Qt::WA_PaintOnScreen ) )
+ {
+ // The parent widget gets an update for its complete rectangle
+ // when the mask is changed in visible state.
+ // With this hide/show we only get an update for the
+ // previous mask.
+
+ hide();
+ }
+ setMask( mask );
+ setVisible( !mask.isEmpty() );
+}
+
+void QwtPicker::PickerWidget::paintEvent( QPaintEvent *e )
+{
+ QPainter painter( this );
+ painter.setClipRegion( e->region() );
+
+ if ( d_type == RubberBand )
+ {
+ painter.setPen( d_picker->rubberBandPen() );
+ d_picker->drawRubberBand( &painter );
+ }
+
+ if ( d_type == Text )
+ {
+ /*
+ If we have a text mask we simply fill the region of
+ the mask. This gives better results for antialiased fonts.
+ */
+ if ( d_hasTextMask )
+ {
+ painter.fillRect( e->rect(),
+ QBrush( d_picker->trackerPen().color() ) );
+ }
+ else
+ {
+ painter.setPen( d_picker->trackerPen() );
+ d_picker->drawTracker( &painter );
+ }
+ }
+}
+
+void QwtPicker::PickerWidget::resizeEvent( QResizeEvent *event )
+{
+ QWidget::resizeEvent( event );
+ if ( isVisible() )
+ updateMask();
+}
+
+/*!
+ Constructor
+
+ Creates an picker that is enabled, but without a state machine.
+ rubberband and tracker are disabled.
+
+ \param parent Parent widget, that will be observed
+ */
+
+QwtPicker::QwtPicker( QWidget *parent ):
+ QObject( parent )
+{
+ init( parent, NoRubberBand, AlwaysOff );
+}
+
+/*!
+ Constructor
+
+ \param rubberBand Rubberband style
+ \param trackerMode Tracker mode
+ \param parent Parent widget, that will be observed
+ */
+QwtPicker::QwtPicker( RubberBand rubberBand,
+ DisplayMode trackerMode, QWidget *parent ):
+ QObject( parent )
+{
+ init( parent, rubberBand, trackerMode );
+}
+
+//! Destructor
+QwtPicker::~QwtPicker()
+{
+ setMouseTracking( false );
+ delete d_data->stateMachine;
+ delete d_data->rubberBandWidget;
+ delete d_data->trackerWidget;
+ delete d_data;
+}
+
+//! Init the picker, used by the constructors
+void QwtPicker::init( QWidget *parent,
+ RubberBand rubberBand, DisplayMode trackerMode )
+{
+ d_data = new PrivateData;
+
+ d_data->rubberBandWidget = NULL;
+ d_data->trackerWidget = NULL;
+
+ d_data->rubberBand = rubberBand;
+ d_data->enabled = false;
+ d_data->resizeMode = Stretch;
+ d_data->trackerMode = AlwaysOff;
+ d_data->isActive = false;
+ d_data->trackerPosition = QPoint( -1, -1 );
+ d_data->mouseTracking = false;
+
+ d_data->stateMachine = NULL;
+
+ if ( parent )
+ {
+ if ( parent->focusPolicy() == Qt::NoFocus )
+ parent->setFocusPolicy( Qt::WheelFocus );
+
+ d_data->trackerFont = parent->font();
+ d_data->mouseTracking = parent->hasMouseTracking();
+ setEnabled( true );
+ }
+ setTrackerMode( trackerMode );
+}
+
+/*!
+ Set a state machine and delete the previous one
+
+ \param stateMachine State machine
+ \sa stateMachine()
+*/
+void QwtPicker::setStateMachine( QwtPickerMachine *stateMachine )
+{
+ if ( d_data->stateMachine != stateMachine )
+ {
+ reset();
+
+ delete d_data->stateMachine;
+ d_data->stateMachine = stateMachine;
+
+ if ( d_data->stateMachine )
+ d_data->stateMachine->reset();
+ }
+}
+
+/*!
+ \return Assigned state machine
+ \sa setStateMachine()
+*/
+QwtPickerMachine *QwtPicker::stateMachine()
+{
+ return d_data->stateMachine;
+}
+
+/*!
+ \return Assigned state machine
+ \sa setStateMachine()
+*/
+const QwtPickerMachine *QwtPicker::stateMachine() const
+{
+ return d_data->stateMachine;
+}
+
+//! Return the parent widget, where the selection happens
+QWidget *QwtPicker::parentWidget()
+{
+ QObject *obj = parent();
+ if ( obj && obj->isWidgetType() )
+ return static_cast<QWidget *>( obj );
+
+ return NULL;
+}
+
+//! Return the parent widget, where the selection happens
+const QWidget *QwtPicker::parentWidget() const
+{
+ QObject *obj = parent();
+ if ( obj && obj->isWidgetType() )
+ return static_cast< const QWidget *>( obj );
+
+ return NULL;
+}
+
+/*!
+ Set the rubberband style
+
+ \param rubberBand Rubberband style
+ The default value is NoRubberBand.
+
+ \sa rubberBand(), RubberBand, setRubberBandPen()
+*/
+void QwtPicker::setRubberBand( RubberBand rubberBand )
+{
+ d_data->rubberBand = rubberBand;
+}
+
+/*!
+ \return Rubberband style
+ \sa setRubberBand(), RubberBand, rubberBandPen()
+*/
+QwtPicker::RubberBand QwtPicker::rubberBand() const
+{
+ return d_data->rubberBand;
+}
+
+/*!
+ \brief Set the display mode of the tracker.
+
+ A tracker displays information about current position of
+ the cursor as a string. The display mode controls
+ if the tracker has to be displayed whenever the observed
+ widget has focus and cursor (AlwaysOn), never (AlwaysOff), or
+ only when the selection is active (ActiveOnly).
+
+ \param mode Tracker display mode
+
+ \warning In case of AlwaysOn, mouseTracking will be enabled
+ for the observed widget.
+ \sa trackerMode(), DisplayMode
+*/
+
+void QwtPicker::setTrackerMode( DisplayMode mode )
+{
+ if ( d_data->trackerMode != mode )
+ {
+ d_data->trackerMode = mode;
+ setMouseTracking( d_data->trackerMode == AlwaysOn );
+ }
+}
+
+/*!
+ \return Tracker display mode
+ \sa setTrackerMode(), DisplayMode
+*/
+QwtPicker::DisplayMode QwtPicker::trackerMode() const
+{
+ return d_data->trackerMode;
+}
+
+/*!
+ \brief Set the resize mode.
+
+ The resize mode controls what to do with the selected points of an active
+ selection when the observed widget is resized.
+
+ Stretch means the points are scaled according to the new
+ size, KeepSize means the points remain unchanged.
+
+ The default mode is Stretch.
+
+ \param mode Resize mode
+ \sa resizeMode(), ResizeMode
+*/
+void QwtPicker::setResizeMode( ResizeMode mode )
+{
+ d_data->resizeMode = mode;
+}
+
+/*!
+ \return Resize mode
+ \sa setResizeMode(), ResizeMode
+*/
+
+QwtPicker::ResizeMode QwtPicker::resizeMode() const
+{
+ return d_data->resizeMode;
+}
+
+/*!
+ \brief En/disable the picker
+
+ When enabled is true an event filter is installed for
+ the observed widget, otherwise the event filter is removed.
+
+ \param enabled true or false
+ \sa isEnabled(), eventFilter()
+*/
+void QwtPicker::setEnabled( bool enabled )
+{
+ if ( d_data->enabled != enabled )
+ {
+ d_data->enabled = enabled;
+
+ QWidget *w = parentWidget();
+ if ( w )
+ {
+ if ( enabled )
+ w->installEventFilter( this );
+ else
+ w->removeEventFilter( this );
+ }
+
+ updateDisplay();
+ }
+}
+
+/*!
+ \return true when enabled, false otherwise
+ \sa setEnabled(), eventFilter()
+*/
+
+bool QwtPicker::isEnabled() const
+{
+ return d_data->enabled;
+}
+
+/*!
+ Set the font for the tracker
+
+ \param font Tracker font
+ \sa trackerFont(), setTrackerMode(), setTrackerPen()
+*/
+void QwtPicker::setTrackerFont( const QFont &font )
+{
+ if ( font != d_data->trackerFont )
+ {
+ d_data->trackerFont = font;
+ updateDisplay();
+ }
+}
+
+/*!
+ \return Tracker font
+ \sa setTrackerFont(), trackerMode(), trackerPen()
+*/
+
+QFont QwtPicker::trackerFont() const
+{
+ return d_data->trackerFont;
+}
+
+/*!
+ Set the pen for the tracker
+
+ \param pen Tracker pen
+ \sa trackerPen(), setTrackerMode(), setTrackerFont()
+*/
+void QwtPicker::setTrackerPen( const QPen &pen )
+{
+ if ( pen != d_data->trackerPen )
+ {
+ d_data->trackerPen = pen;
+ updateDisplay();
+ }
+}
+
+/*!
+ \return Tracker pen
+ \sa setTrackerPen(), trackerMode(), trackerFont()
+*/
+QPen QwtPicker::trackerPen() const
+{
+ return d_data->trackerPen;
+}
+
+/*!
+ Set the pen for the rubberband
+
+ \param pen Rubberband pen
+ \sa rubberBandPen(), setRubberBand()
+*/
+void QwtPicker::setRubberBandPen( const QPen &pen )
+{
+ if ( pen != d_data->rubberBandPen )
+ {
+ d_data->rubberBandPen = pen;
+ updateDisplay();
+ }
+}
+
+/*!
+ \return Rubberband pen
+ \sa setRubberBandPen(), rubberBand()
+*/
+QPen QwtPicker::rubberBandPen() const
+{
+ return d_data->rubberBandPen;
+}
+
+/*!
+ \brief Return the label for a position
+
+ In case of HLineRubberBand the label is the value of the
+ y position, in case of VLineRubberBand the value of the x position.
+ Otherwise the label contains x and y position separated by a ',' .
+
+ The format for the string conversion is "%d".
+
+ \param pos Position
+ \return Converted position as string
+*/
+
+QwtText QwtPicker::trackerText( const QPoint &pos ) const
+{
+ QString label;
+
+ switch ( rubberBand() )
+ {
+ case HLineRubberBand:
+ label.sprintf( "%d", pos.y() );
+ break;
+ case VLineRubberBand:
+ label.sprintf( "%d", pos.x() );
+ break;
+ default:
+ label.sprintf( "%d, %d", pos.x(), pos.y() );
+ }
+ return label;
+}
+
+/*!
+ Draw a rubberband, depending on rubberBand()
+
+ \param painter Painter, initialized with clip rect
+
+ \sa rubberBand(), RubberBand
+*/
+
+void QwtPicker::drawRubberBand( QPainter *painter ) const
+{
+ if ( !isActive() || rubberBand() == NoRubberBand ||
+ rubberBandPen().style() == Qt::NoPen )
+ {
+ return;
+ }
+
+ const QRect &pRect = pickRect();
+ const QPolygon pa = adjustedPoints( d_data->pickedPoints );
+
+ QwtPickerMachine::SelectionType selectionType =
+ QwtPickerMachine::NoSelection;
+
+ if ( d_data->stateMachine )
+ selectionType = d_data->stateMachine->selectionType();
+
+ switch ( selectionType )
+ {
+ case QwtPickerMachine::NoSelection:
+ case QwtPickerMachine::PointSelection:
+ {
+ if ( pa.count() < 1 )
+ return;
+
+ const QPoint pos = pa[0];
+
+ switch ( rubberBand() )
+ {
+ case VLineRubberBand:
+ QwtPainter::drawLine( painter, pos.x(),
+ pRect.top(), pos.x(), pRect.bottom() );
+ break;
+
+ case HLineRubberBand:
+ QwtPainter::drawLine( painter, pRect.left(),
+ pos.y(), pRect.right(), pos.y() );
+ break;
+
+ case CrossRubberBand:
+ QwtPainter::drawLine( painter, pos.x(),
+ pRect.top(), pos.x(), pRect.bottom() );
+ QwtPainter::drawLine( painter, pRect.left(),
+ pos.y(), pRect.right(), pos.y() );
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case QwtPickerMachine::RectSelection:
+ {
+ if ( pa.count() < 2 )
+ return;
+
+ const QPoint p1 = pa[0];
+ const QPoint p2 = pa[int( pa.count() - 1 )];
+
+ const QRect rect = QRect( p1, p2 ).normalized();
+ switch ( rubberBand() )
+ {
+ case EllipseRubberBand:
+ QwtPainter::drawEllipse( painter, rect );
+ break;
+ case RectRubberBand:
+ QwtPainter::drawRect( painter, rect );
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ case QwtPickerMachine::PolygonSelection:
+ {
+ if ( rubberBand() == PolygonRubberBand )
+ painter->drawPolyline( pa );
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/*!
+ Draw the tracker
+
+ \param painter Painter
+ \sa trackerRect(), trackerText()
+*/
+
+void QwtPicker::drawTracker( QPainter *painter ) const
+{
+ const QRect textRect = trackerRect( painter->font() );
+ if ( !textRect.isEmpty() )
+ {
+ const QwtText label = trackerText( d_data->trackerPosition );
+ if ( !label.isEmpty() )
+ label.draw( painter, textRect );
+ }
+}
+
+/*!
+ \brief Map the pickedPoints() into a selection()
+
+ adjustedPoints() maps the points, that have been collected on
+ the parentWidget() into a selection(). The default implementation
+ simply returns the points unmodified.
+
+ The reason, why a selection() differs from the picked points
+ depends on the application requirements. F.e. :
+
+ - A rectangular selection might need to have a specific aspect ratio only.\n
+ - A selection could accept non intersecting polygons only.\n
+ - ...\n
+
+ The example below is for a rectangular selection, where the first
+ point is the center of the selected rectangle.
+ \par Example
+ \verbatim QPolygon MyPicker::adjustedPoints(const QPolygon &points) const
+{
+ QPolygon adjusted;
+ if ( points.size() == 2 )
+ {
+ const int width = qAbs(points[1].x() - points[0].x());
+ const int height = qAbs(points[1].y() - points[0].y());
+
+ QRect rect(0, 0, 2 * width, 2 * height);
+ rect.moveCenter(points[0]);
+
+ adjusted += rect.topLeft();
+ adjusted += rect.bottomRight();
+ }
+ return adjusted;
+}\endverbatim\n
+*/
+QPolygon QwtPicker::adjustedPoints( const QPolygon &points ) const
+{
+ return points;
+}
+
+/*!
+ \return Selected points
+ \sa pickedPoints(), adjustedPoints()
+*/
+QPolygon QwtPicker::selection() const
+{
+ return adjustedPoints( d_data->pickedPoints );
+}
+
+//! \return Current position of the tracker
+QPoint QwtPicker::trackerPosition() const
+{
+ return d_data->trackerPosition;
+}
+
+/*!
+ Calculate the bounding rectangle for the tracker text
+ from the current position of the tracker
+
+ \param font Font of the tracker text
+ \return Bounding rectangle of the tracker text
+
+ \sa trackerPosition()
+*/
+QRect QwtPicker::trackerRect( const QFont &font ) const
+{
+ if ( trackerMode() == AlwaysOff ||
+ ( trackerMode() == ActiveOnly && !isActive() ) )
+ {
+ return QRect();
+ }
+
+ if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 )
+ return QRect();
+
+ QwtText text = trackerText( d_data->trackerPosition );
+ if ( text.isEmpty() )
+ return QRect();
+
+ const QSizeF textSize = text.textSize( font );
+ QRect textRect( 0, 0, qCeil( textSize.width() ), qCeil( textSize.height() ) );
+
+ const QPoint &pos = d_data->trackerPosition;
+
+ int alignment = 0;
+ if ( isActive() && d_data->pickedPoints.count() > 1
+ && rubberBand() != NoRubberBand )
+ {
+ const QPoint last =
+ d_data->pickedPoints[int( d_data->pickedPoints.count() ) - 2];
+
+ alignment |= ( pos.x() >= last.x() ) ? Qt::AlignRight : Qt::AlignLeft;
+ alignment |= ( pos.y() > last.y() ) ? Qt::AlignBottom : Qt::AlignTop;
+ }
+ else
+ alignment = Qt::AlignTop | Qt::AlignRight;
+
+ const int margin = 5;
+
+ int x = pos.x();
+ if ( alignment & Qt::AlignLeft )
+ x -= textRect.width() + margin;
+ else if ( alignment & Qt::AlignRight )
+ x += margin;
+
+ int y = pos.y();
+ if ( alignment & Qt::AlignBottom )
+ y += margin;
+ else if ( alignment & Qt::AlignTop )
+ y -= textRect.height() + margin;
+
+ textRect.moveTopLeft( QPoint( x, y ) );
+
+ int right = qMin( textRect.right(), pickRect().right() - margin );
+ int bottom = qMin( textRect.bottom(), pickRect().bottom() - margin );
+ textRect.moveBottomRight( QPoint( right, bottom ) );
+
+ int left = qMax( textRect.left(), pickRect().left() + margin );
+ int top = qMax( textRect.top(), pickRect().top() + margin );
+ textRect.moveTopLeft( QPoint( left, top ) );
+
+ return textRect;
+}
+
+/*!
+ \brief Event filter
+
+ When isEnabled() == true all events of the observed widget are filtered.
+ Mouse and keyboard events are translated into widgetMouse- and widgetKey-
+ and widgetWheel-events. Paint and Resize events are handled to keep
+ rubberband and tracker up to date.
+
+ \param object Object to be filtered
+ \param event Event
+
+ \sa widgetEnterEvent(), widgetLeaveEvent(),
+ widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent(),
+ QObject::installEventFilter(), QObject::event()
+*/
+bool QwtPicker::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object && object == parentWidget() )
+ {
+ switch ( event->type() )
+ {
+ case QEvent::Resize:
+ {
+ const QResizeEvent *re = ( QResizeEvent * )event;
+ if ( d_data->resizeMode == Stretch )
+ stretchSelection( re->oldSize(), re->size() );
+
+ if ( d_data->rubberBandWidget )
+ d_data->rubberBandWidget->resize( re->size() );
+
+ if ( d_data->trackerWidget )
+ d_data->trackerWidget->resize( re->size() );
+ break;
+ }
+ case QEvent::Enter:
+ {
+ widgetEnterEvent( event );
+ break;
+ }
+ case QEvent::Leave:
+ {
+ widgetLeaveEvent( event );
+ break;
+ }
+ case QEvent::MouseButtonPress:
+ {
+ widgetMousePressEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ widgetMouseReleaseEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseButtonDblClick:
+ {
+ widgetMouseDoubleClickEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::MouseMove:
+ {
+ widgetMouseMoveEvent( ( QMouseEvent * )event );
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ widgetKeyPressEvent( ( QKeyEvent * )event );
+ break;
+ }
+ case QEvent::KeyRelease:
+ {
+ widgetKeyReleaseEvent( ( QKeyEvent * )event );
+ break;
+ }
+ case QEvent::Wheel:
+ {
+ widgetWheelEvent( ( QWheelEvent * )event );
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+/*!
+ Handle a mouse press event for the observed widget.
+
+ \param mouseEvent Mouse event
+
+ \sa eventFilter(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetMousePressEvent( QMouseEvent *mouseEvent )
+{
+ transition( mouseEvent );
+}
+
+/*!
+ Handle a mouse move event for the observed widget.
+
+ \param mouseEvent Mouse event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetMouseMoveEvent( QMouseEvent *mouseEvent )
+{
+ if ( pickRect().contains( mouseEvent->pos() ) )
+ d_data->trackerPosition = mouseEvent->pos();
+ else
+ d_data->trackerPosition = QPoint( -1, -1 );
+
+ if ( !isActive() )
+ updateDisplay();
+
+ transition( mouseEvent );
+}
+
+/*!
+ Handle a enter event for the observed widget.
+
+ \param event Qt event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetEnterEvent( QEvent *event )
+{
+ transition( event );
+}
+
+/*!
+ Handle a leave event for the observed widget.
+
+ \param event Qt event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetLeaveEvent( QEvent *event )
+{
+ transition( event );
+
+ d_data->trackerPosition = QPoint( -1, -1 );
+ if ( !isActive() )
+ updateDisplay();
+}
+
+/*!
+ Handle a mouse relase event for the observed widget.
+
+ \param mouseEvent Mouse event
+
+ \sa eventFilter(), widgetMousePressEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetMouseReleaseEvent( QMouseEvent *mouseEvent )
+{
+ transition( mouseEvent );
+}
+
+/*!
+ Handle mouse double click event for the observed widget.
+
+ \param mouseEvent Mouse event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetMouseDoubleClickEvent( QMouseEvent *mouseEvent )
+{
+ transition( mouseEvent );
+}
+
+
+/*!
+ Handle a wheel event for the observed widget.
+
+ Move the last point of the selection in case of isActive() == true
+
+ \param wheelEvent Wheel event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetKeyPressEvent(), widgetKeyReleaseEvent()
+*/
+void QwtPicker::widgetWheelEvent( QWheelEvent *wheelEvent )
+{
+ if ( pickRect().contains( wheelEvent->pos() ) )
+ d_data->trackerPosition = wheelEvent->pos();
+ else
+ d_data->trackerPosition = QPoint( -1, -1 );
+
+ updateDisplay();
+
+ transition( wheelEvent );
+}
+
+/*!
+ Handle a key press event for the observed widget.
+
+ Selections can be completely done by the keyboard. The arrow keys
+ move the cursor, the abort key aborts a selection. All other keys
+ are handled by the current state machine.
+
+ \param keyEvent Key event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyReleaseEvent(), stateMachine(),
+ QwtEventPattern::KeyPatternCode
+*/
+void QwtPicker::widgetKeyPressEvent( QKeyEvent *keyEvent )
+{
+ int dx = 0;
+ int dy = 0;
+
+ int offset = 1;
+ if ( keyEvent->isAutoRepeat() )
+ offset = 5;
+
+ if ( keyMatch( KeyLeft, keyEvent ) )
+ dx = -offset;
+ else if ( keyMatch( KeyRight, keyEvent ) )
+ dx = offset;
+ else if ( keyMatch( KeyUp, keyEvent ) )
+ dy = -offset;
+ else if ( keyMatch( KeyDown, keyEvent ) )
+ dy = offset;
+ else if ( keyMatch( KeyAbort, keyEvent ) )
+ {
+ reset();
+ }
+ else
+ transition( keyEvent );
+
+ if ( dx != 0 || dy != 0 )
+ {
+ const QRect rect = pickRect();
+ const QPoint pos = parentWidget()->mapFromGlobal( QCursor::pos() );
+
+ int x = pos.x() + dx;
+ x = qMax( rect.left(), x );
+ x = qMin( rect.right(), x );
+
+ int y = pos.y() + dy;
+ y = qMax( rect.top(), y );
+ y = qMin( rect.bottom(), y );
+
+ QCursor::setPos( parentWidget()->mapToGlobal( QPoint( x, y ) ) );
+ }
+}
+
+/*!
+ Handle a key release event for the observed widget.
+
+ Passes the event to the state machine.
+
+ \param keyEvent Key event
+
+ \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(),
+ widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(),
+ widgetWheelEvent(), widgetKeyPressEvent(), stateMachine()
+*/
+void QwtPicker::widgetKeyReleaseEvent( QKeyEvent *keyEvent )
+{
+ transition( keyEvent );
+}
+
+/*!
+ Passes an event to the state machine and executes the resulting
+ commands. Append and Move commands use the current position
+ of the cursor (QCursor::pos()).
+
+ \param event Event
+*/
+void QwtPicker::transition( const QEvent *event )
+{
+ if ( !d_data->stateMachine )
+ return;
+
+ const QList<QwtPickerMachine::Command> commandList =
+ d_data->stateMachine->transition( *this, event );
+
+ QPoint pos;
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonRelease:
+ case QEvent::MouseMove:
+ {
+ const QMouseEvent *me =
+ static_cast< const QMouseEvent * >( event );
+ pos = me->pos();
+ break;
+ }
+ default:
+ pos = parentWidget()->mapFromGlobal( QCursor::pos() );
+ }
+
+ for ( int i = 0; i < commandList.count(); i++ )
+ {
+ switch ( commandList[i] )
+ {
+ case QwtPickerMachine::Begin:
+ {
+ begin();
+ break;
+ }
+ case QwtPickerMachine::Append:
+ {
+ append( pos );
+ break;
+ }
+ case QwtPickerMachine::Move:
+ {
+ move( pos );
+ break;
+ }
+ case QwtPickerMachine::Remove:
+ {
+ remove();
+ break;
+ }
+ case QwtPickerMachine::End:
+ {
+ end();
+ break;
+ }
+ }
+ }
+}
+
+/*!
+ Open a selection setting the state to active
+
+ \sa isActive(), end(), append(), move()
+*/
+void QwtPicker::begin()
+{
+ if ( d_data->isActive )
+ return;
+
+ d_data->pickedPoints.resize( 0 );
+ d_data->isActive = true;
+ Q_EMIT activated( true );
+
+ if ( trackerMode() != AlwaysOff )
+ {
+ if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 )
+ {
+ QWidget *w = parentWidget();
+ if ( w )
+ d_data->trackerPosition = w->mapFromGlobal( QCursor::pos() );
+ }
+ }
+
+ updateDisplay();
+ setMouseTracking( true );
+}
+
+/*!
+ \brief Close a selection setting the state to inactive.
+
+ The selection is validated and maybe fixed by accept().
+
+ \param ok If true, complete the selection and emit a selected signal
+ otherwise discard the selection.
+ \return true if the selection is accepted, false otherwise
+ \sa isActive(), begin(), append(), move(), selected(), accept()
+*/
+bool QwtPicker::end( bool ok )
+{
+ if ( d_data->isActive )
+ {
+ setMouseTracking( false );
+
+ d_data->isActive = false;
+ Q_EMIT activated( false );
+
+ if ( trackerMode() == ActiveOnly )
+ d_data->trackerPosition = QPoint( -1, -1 );
+
+ if ( ok )
+ ok = accept( d_data->pickedPoints );
+
+ if ( ok )
+ Q_EMIT selected( d_data->pickedPoints );
+ else
+ d_data->pickedPoints.resize( 0 );
+
+ updateDisplay();
+ }
+ else
+ ok = false;
+
+ return ok;
+}
+
+/*!
+ Reset the state machine and terminate (end(false)) the selection
+*/
+void QwtPicker::reset()
+{
+ if ( d_data->stateMachine )
+ d_data->stateMachine->reset();
+
+ if ( isActive() )
+ end( false );
+}
+
+/*!
+ Append a point to the selection and update rubberband and tracker.
+ The appended() signal is emitted.
+
+ \param pos Additional point
+
+ \sa isActive(), begin(), end(), move(), appended()
+*/
+void QwtPicker::append( const QPoint &pos )
+{
+ if ( d_data->isActive )
+ {
+ const int idx = d_data->pickedPoints.count();
+ d_data->pickedPoints.resize( idx + 1 );
+ d_data->pickedPoints[idx] = pos;
+
+ updateDisplay();
+ Q_EMIT appended( pos );
+ }
+}
+
+/*!
+ Move the last point of the selection
+ The moved() signal is emitted.
+
+ \param pos New position
+ \sa isActive(), begin(), end(), append()
+*/
+void QwtPicker::move( const QPoint &pos )
+{
+ if ( d_data->isActive )
+ {
+ const int idx = d_data->pickedPoints.count() - 1;
+ if ( idx >= 0 )
+ {
+ if ( d_data->pickedPoints[idx] != pos )
+ {
+ d_data->pickedPoints[idx] = pos;
+
+ updateDisplay();
+ Q_EMIT moved( pos );
+ }
+ }
+ }
+}
+
+/*!
+ Remove the last point of the selection
+ The removed() signal is emitted.
+
+ \sa isActive(), begin(), end(), append(), move()
+*/
+void QwtPicker::remove()
+{
+ if ( d_data->isActive )
+ {
+ const int idx = d_data->pickedPoints.count() - 1;
+ if ( idx > 0 )
+ {
+ const int idx = d_data->pickedPoints.count();
+
+ const QPoint pos = d_data->pickedPoints[idx - 1];
+ d_data->pickedPoints.resize( idx - 1 );
+
+ updateDisplay();
+ Q_EMIT removed( pos );
+ }
+ }
+}
+
+/*!
+ \brief Validate and fixup the selection
+
+ Accepts all selections unmodified
+
+ \param selection Selection to validate and fixup
+ \return true, when accepted, false otherwise
+*/
+bool QwtPicker::accept( QPolygon &selection ) const
+{
+ Q_UNUSED( selection );
+ return true;
+}
+
+/*!
+ A picker is active between begin() and end().
+ \return true if the selection is active.
+*/
+bool QwtPicker::isActive() const
+{
+ return d_data->isActive;
+}
+
+/*!
+ Return the points, that have been collected so far. The selection()
+ is calculated from the pickedPoints() in adjustedPoints().
+ \return Picked points
+*/
+const QPolygon &QwtPicker::pickedPoints() const
+{
+ return d_data->pickedPoints;
+}
+
+/*!
+ Scale the selection by the ratios of oldSize and newSize
+ The changed() signal is emitted.
+
+ \param oldSize Previous size
+ \param newSize Current size
+
+ \sa ResizeMode, setResizeMode(), resizeMode()
+*/
+void QwtPicker::stretchSelection( const QSize &oldSize, const QSize &newSize )
+{
+ if ( oldSize.isEmpty() )
+ {
+ // avoid division by zero. But scaling for small sizes also
+ // doesn't make much sense, because of rounding losses. TODO ...
+ return;
+ }
+
+ const double xRatio =
+ double( newSize.width() ) / double( oldSize.width() );
+ const double yRatio =
+ double( newSize.height() ) / double( oldSize.height() );
+
+ for ( int i = 0; i < int( d_data->pickedPoints.count() ); i++ )
+ {
+ QPoint &p = d_data->pickedPoints[i];
+ p.setX( qRound( p.x() * xRatio ) );
+ p.setY( qRound( p.y() * yRatio ) );
+
+ Q_EMIT changed( d_data->pickedPoints );
+ }
+}
+
+/*!
+ Set mouse tracking for the observed widget.
+
+ In case of enable is true, the previous value
+ is saved, that is restored when enable is false.
+
+ \warning Even when enable is false, mouse tracking might be restored
+ to true. When mouseTracking for the observed widget
+ has been changed directly by QWidget::setMouseTracking
+ while mouse tracking has been set to true, this value can't
+ be restored.
+*/
+
+void QwtPicker::setMouseTracking( bool enable )
+{
+ QWidget *widget = parentWidget();
+ if ( !widget )
+ return;
+
+ if ( enable )
+ {
+ d_data->mouseTracking = widget->hasMouseTracking();
+ widget->setMouseTracking( true );
+ }
+ else
+ {
+ widget->setMouseTracking( d_data->mouseTracking );
+ }
+}
+
+/*!
+ Find the area of the observed widget, where selection might happen.
+
+ \return parentWidget()->contentsRect()
+*/
+QRect QwtPicker::pickRect() const
+{
+ const QWidget *widget = parentWidget();
+ if ( widget )
+ return widget->contentsRect();
+
+ return QRect();
+}
+
+//! Update the state of rubberband and tracker label
+void QwtPicker::updateDisplay()
+{
+ QWidget *w = parentWidget();
+
+ bool showRubberband = false;
+ bool showTracker = false;
+ if ( w && w->isVisible() && d_data->enabled )
+ {
+ if ( rubberBand() != NoRubberBand && isActive() &&
+ rubberBandPen().style() != Qt::NoPen )
+ {
+ showRubberband = true;
+ }
+
+ if ( trackerMode() == AlwaysOn ||
+ ( trackerMode() == ActiveOnly && isActive() ) )
+ {
+ if ( trackerPen() != Qt::NoPen )
+ showTracker = true;
+ }
+ }
+
+ QPointer<PickerWidget> &rw = d_data->rubberBandWidget;
+ if ( showRubberband )
+ {
+ if ( rw.isNull() )
+ {
+ rw = new PickerWidget( this, w, PickerWidget::RubberBand );
+ rw->setObjectName( "PickerRubberBand" );
+ rw->resize( w->size() );
+ }
+ rw->updateMask();
+ rw->update(); // Needed, when the mask doesn't change
+ }
+ else
+ delete rw;
+
+ QPointer<PickerWidget> &tw = d_data->trackerWidget;
+ if ( showTracker )
+ {
+ if ( tw.isNull() )
+ {
+ tw = new PickerWidget( this, w, PickerWidget::Text );
+ tw->setObjectName( "PickerTracker" );
+ tw->resize( w->size() );
+ }
+ tw->setFont( d_data->trackerFont );
+ tw->updateMask();
+ tw->update(); // Needed, when the mask doesn't change
+ }
+ else
+ delete tw;
+}
+
+//! \return Widget displaying the rubberband
+const QWidget *QwtPicker::rubberBandWidget() const
+{
+ return d_data->rubberBandWidget;
+}
+
+//! \return Widget displaying the tracker text
+const QWidget *QwtPicker::trackerWidget() const
+{
+ return d_data->trackerWidget;
+}
+
diff --git a/src/libpcp_qwt/src/qwt_picker.h b/src/libpcp_qwt/src/qwt_picker.h
new file mode 100644
index 0000000..c0b22dc
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_picker.h
@@ -0,0 +1,327 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PICKER
+#define QWT_PICKER 1
+
+#include "qwt_global.h"
+#include "qwt_text.h"
+#include "qwt_event_pattern.h"
+#include <qobject.h>
+#include <qpen.h>
+#include <qfont.h>
+#include <qrect.h>
+
+class QWidget;
+class QMouseEvent;
+class QWheelEvent;
+class QKeyEvent;
+class QwtPickerMachine;
+
+/*!
+ \brief QwtPicker provides selections on a widget
+
+ QwtPicker filters all enter, leave, mouse and keyboard events of a widget
+ and translates them into an array of selected points.
+
+ The way how the points are collected depends on type of state machine
+ that is connected to the picker. Qwt offers a couple of predefined
+ state machines for selecting:
+
+ - Nothing\n
+ QwtPickerTrackerMachine
+ - Single points\n
+ QwtPickerClickPointMachine, QwtPickerDragPointMachine
+ - Rectangles\n
+ QwtPickerClickRectMachine, QwtPickerDragRectMachine
+ - Polygons\n
+ QwtPickerPolygonMachine
+
+ While these state machines cover the most common ways to collect points
+ it is also possible to implement individual machines as well.
+
+ QwtPicker translates the picked points into a selection using the
+ adjustedPoints method. adjustedPoints is intended to be reimplemented
+ to fixup the selection according to application specific requirements.
+ (F.e. when an application accepts rectangles of a fixed aspect ratio only.)
+
+ Optionally QwtPicker support the process of collecting points by a
+ rubberband and tracker displaying a text for the current mouse
+ position.
+
+ \par Example
+ \verbatim #include <qwt_picker.h>
+#include <qwt_picker_machine.h>
+
+QwtPicker *picker = new QwtPicker(widget);
+picker->setStateMachine(new QwtPickerDragRectMachine);
+picker->setTrackerMode(QwtPicker::ActiveOnly);
+picker->setRubberBand(QwtPicker::RectRubberBand); \endverbatim\n
+
+ The state machine triggers the following commands:
+
+ - begin()\n
+ Activate/Initialize the selection.
+ - append()\n
+ Add a new point
+ - move() \n
+ Change the position of the last point.
+ - remove()\n
+ Remove the last point.
+ - end()\n
+ Terminate the selection and call accept to validate the picked points.
+
+ The picker is active (isActive()), between begin() and end().
+ In active state the rubberband is displayed, and the tracker is visible
+ in case of trackerMode is ActiveOnly or AlwaysOn.
+
+ The cursor can be moved using the arrow keys. All selections can be aborted
+ using the abort key. (QwtEventPattern::KeyPatternCode)
+
+ \warning In case of QWidget::NoFocus the focus policy of the observed
+ widget is set to QWidget::WheelFocus and mouse tracking
+ will be manipulated while the picker is active,
+ or if trackerMode() is AlwayOn.
+*/
+
+class QWT_EXPORT QwtPicker: public QObject, public QwtEventPattern
+{
+ Q_OBJECT
+
+ Q_ENUMS( RubberBand )
+ Q_ENUMS( DisplayMode )
+ Q_ENUMS( ResizeMode )
+
+ Q_PROPERTY( bool isEnabled READ isEnabled WRITE setEnabled )
+ Q_PROPERTY( ResizeMode resizeMode READ resizeMode WRITE setResizeMode )
+
+ Q_PROPERTY( DisplayMode trackerMode READ trackerMode WRITE setTrackerMode )
+ Q_PROPERTY( QPen trackerPen READ trackerPen WRITE setTrackerPen )
+ Q_PROPERTY( QFont trackerFont READ trackerFont WRITE setTrackerFont )
+
+ Q_PROPERTY( RubberBand rubberBand READ rubberBand WRITE setRubberBand )
+ Q_PROPERTY( QPen rubberBandPen READ rubberBandPen WRITE setRubberBandPen )
+
+public:
+ /*!
+ Rubberband style
+
+ The default value is QwtPicker::NoRubberBand.
+ \sa setRubberBand(), rubberBand()
+ */
+
+ enum RubberBand
+ {
+ //! No rubberband.
+ NoRubberBand = 0,
+
+ //! A horizontal line ( only for QwtPicker::PointSelection )
+ HLineRubberBand,
+
+ //! A vertical line ( only for QwtPicker::PointSelection )
+ VLineRubberBand,
+
+ //! A crosshair ( only for QwtPicker::PointSelection )
+ CrossRubberBand,
+
+ //! A rectangle ( only for QwtPicker::RectSelection )
+ RectRubberBand,
+
+ //! An ellipse ( only for QwtPicker::RectSelection )
+ EllipseRubberBand,
+
+ //! A polygon ( only for QwtPicker::&PolygonSelection )
+ PolygonRubberBand,
+
+ /*!
+ Values >= UserRubberBand can be used to define additional
+ rubber bands.
+ */
+ UserRubberBand = 100
+ };
+
+ /*!
+ \brief Display mode
+ \sa setTrackerMode(), trackerMode(), isActive()
+ */
+ enum DisplayMode
+ {
+ //! Display never
+ AlwaysOff,
+
+ //! Display always
+ AlwaysOn,
+
+ //! Display only when the selection is active
+ ActiveOnly
+ };
+
+ /*!
+ Controls what to do with the selected points of an active
+ selection when the observed widget is resized.
+
+ The default value is QwtPicker::Stretch.
+ \sa setResizeMode()
+ */
+
+ enum ResizeMode
+ {
+ //! All points are scaled according to the new size,
+ Stretch,
+
+ //! All points remain unchanged.
+ KeepSize
+ };
+
+ explicit QwtPicker( QWidget *parent );
+ explicit QwtPicker( RubberBand rubberBand,
+ DisplayMode trackerMode, QWidget * );
+
+ virtual ~QwtPicker();
+
+ void setStateMachine( QwtPickerMachine * );
+ const QwtPickerMachine *stateMachine() const;
+ QwtPickerMachine *stateMachine();
+
+ void setRubberBand( RubberBand );
+ RubberBand rubberBand() const;
+
+ void setTrackerMode( DisplayMode );
+ DisplayMode trackerMode() const;
+
+ void setResizeMode( ResizeMode );
+ ResizeMode resizeMode() const;
+
+ void setRubberBandPen( const QPen & );
+ QPen rubberBandPen() const;
+
+ void setTrackerPen( const QPen & );
+ QPen trackerPen() const;
+
+ void setTrackerFont( const QFont & );
+ QFont trackerFont() const;
+
+ bool isEnabled() const;
+ bool isActive() const;
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+ QWidget *parentWidget();
+ const QWidget *parentWidget() const;
+
+ virtual QRect pickRect() const;
+
+ virtual void drawRubberBand( QPainter * ) const;
+ virtual void drawTracker( QPainter * ) const;
+
+ virtual QwtText trackerText( const QPoint &pos ) const;
+ QPoint trackerPosition() const;
+ virtual QRect trackerRect( const QFont & ) const;
+
+ QPolygon selection() const;
+
+public Q_SLOTS:
+ void setEnabled( bool );
+
+Q_SIGNALS:
+ /*!
+ A signal indicating, when the picker has been activated.
+ Together with setEnabled() it can be used to implement
+ selections with more than one picker.
+
+ \param on True, when the picker has been activated
+ */
+ void activated( bool on );
+
+ /*!
+ A signal emitting the selected points,
+ at the end of a selection.
+
+ \param polygon Selected points
+ */
+ void selected( const QPolygon &polygon );
+
+ /*!
+ A signal emitted when a point has been appended to the selection
+
+ \param pos Position of the appended point.
+ \sa append(). moved()
+ */
+ void appended( const QPoint &pos );
+
+ /*!
+ A signal emitted whenever the last appended point of the
+ selection has been moved.
+
+ \param pos Position of the moved last point of the selection.
+ \sa move(), appended()
+ */
+ void moved( const QPoint &pos );
+
+ /*!
+ A signal emitted whenever the last appended point of the
+ selection has been removed.
+
+ \sa remove(), appended()
+ */
+ void removed( const QPoint &pos );
+ /*!
+ A signal emitted when the active selection has been changed.
+ This might happen when the observed widget is resized.
+
+ \param selection Changed selection
+ \sa stretchSelection()
+ */
+ void changed( const QPolygon &selection );
+
+protected:
+ virtual QPolygon adjustedPoints( const QPolygon & ) const;
+
+ virtual void transition( const QEvent * );
+
+ virtual void begin();
+ virtual void append( const QPoint & );
+ virtual void move( const QPoint & );
+ virtual void remove();
+ virtual bool end( bool ok = true );
+
+ virtual bool accept( QPolygon & ) const;
+ virtual void reset();
+
+ virtual void widgetMousePressEvent( QMouseEvent * );
+ virtual void widgetMouseReleaseEvent( QMouseEvent * );
+ virtual void widgetMouseDoubleClickEvent( QMouseEvent * );
+ virtual void widgetMouseMoveEvent( QMouseEvent * );
+ virtual void widgetWheelEvent( QWheelEvent * );
+ virtual void widgetKeyPressEvent( QKeyEvent * );
+ virtual void widgetKeyReleaseEvent( QKeyEvent * );
+ virtual void widgetEnterEvent( QEvent * );
+ virtual void widgetLeaveEvent( QEvent * );
+
+ virtual void stretchSelection( const QSize &oldSize,
+ const QSize &newSize );
+
+ virtual void updateDisplay();
+
+ const QWidget *rubberBandWidget() const;
+ const QWidget *trackerWidget() const;
+
+ const QPolygon &pickedPoints() const;
+
+private:
+ void init( QWidget *, RubberBand rubberBand, DisplayMode trackerMode );
+
+ void setMouseTracking( bool );
+
+ class PickerWidget;
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_picker_machine.cpp b/src/libpcp_qwt/src/qwt_picker_machine.cpp
new file mode 100644
index 0000000..d43cc88
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_picker_machine.cpp
@@ -0,0 +1,455 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_picker_machine.h"
+#include "qwt_event_pattern.h"
+#include <qevent.h>
+
+//! Constructor
+QwtPickerMachine::QwtPickerMachine( SelectionType type ):
+ d_selectionType( type ),
+ d_state( 0 )
+{
+}
+
+//! Destructor
+QwtPickerMachine::~QwtPickerMachine()
+{
+}
+
+//! Return the selection type
+QwtPickerMachine::SelectionType QwtPickerMachine::selectionType() const
+{
+ return d_selectionType;
+}
+
+//! Return the current state
+int QwtPickerMachine::state() const
+{
+ return d_state;
+}
+
+//! Change the current state
+void QwtPickerMachine::setState( int state )
+{
+ d_state = state;
+}
+
+//! Set the current state to 0.
+void QwtPickerMachine::reset()
+{
+ setState( 0 );
+}
+
+//! Constructor
+QwtPickerTrackerMachine::QwtPickerTrackerMachine():
+ QwtPickerMachine( NoSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerTrackerMachine::transition(
+ const QwtEventPattern &, const QEvent *e )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( e->type() )
+ {
+ case QEvent::Enter:
+ case QEvent::MouseMove:
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ setState( 1 );
+ }
+ else
+ {
+ cmdList += Move;
+ }
+ break;
+ }
+ case QEvent::Leave:
+ {
+ cmdList += Remove;
+ cmdList += End;
+ setState( 0 );
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
+
+//! Constructor
+QwtPickerClickPointMachine::QwtPickerClickPointMachine():
+ QwtPickerMachine( PointSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerClickPointMachine::transition(
+ const QwtEventPattern &eventPattern, const QEvent *event )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += End;
+ }
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect1, ( const QKeyEvent * )event ) )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += End;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
+
+//! Constructor
+QwtPickerDragPointMachine::QwtPickerDragPointMachine():
+ QwtPickerMachine( PointSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerDragPointMachine::transition(
+ const QwtEventPattern &eventPattern, const QEvent *event )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ setState( 1 );
+ }
+ }
+ break;
+ }
+ case QEvent::MouseMove:
+ case QEvent::Wheel:
+ {
+ if ( state() != 0 )
+ cmdList += Move;
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ if ( state() != 0 )
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect1, ( const QKeyEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ setState( 1 );
+ }
+ else
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
+
+//! Constructor
+QwtPickerClickRectMachine::QwtPickerClickRectMachine():
+ QwtPickerMachine( RectSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerClickRectMachine::transition(
+ const QwtEventPattern &eventPattern, const QEvent *event )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ switch ( state() )
+ {
+ case 0:
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ setState( 1 );
+ break;
+ }
+ case 1:
+ {
+ // Uh, strange we missed the MouseButtonRelease
+ break;
+ }
+ default:
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ }
+ break;
+ }
+ case QEvent::MouseMove:
+ case QEvent::Wheel:
+ {
+ if ( state() != 0 )
+ cmdList += Move;
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ if ( state() == 1 )
+ {
+ cmdList += Append;
+ setState( 2 );
+ }
+ }
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect1, ( const QKeyEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ setState( 1 );
+ }
+ else
+ {
+ if ( state() == 1 )
+ {
+ cmdList += Append;
+ setState( 2 );
+ }
+ else if ( state() == 2 )
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
+
+//! Constructor
+QwtPickerDragRectMachine::QwtPickerDragRectMachine():
+ QwtPickerMachine( RectSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerDragRectMachine::transition(
+ const QwtEventPattern &eventPattern, const QEvent *event )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += Append;
+ setState( 2 );
+ }
+ }
+ break;
+ }
+ case QEvent::MouseMove:
+ case QEvent::Wheel:
+ {
+ if ( state() != 0 )
+ cmdList += Move;
+ break;
+ }
+ case QEvent::MouseButtonRelease:
+ {
+ if ( state() == 2 )
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect1, ( const QKeyEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += Append;
+ setState( 2 );
+ }
+ else
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
+
+//! Constructor
+QwtPickerPolygonMachine::QwtPickerPolygonMachine():
+ QwtPickerMachine( PolygonSelection )
+{
+}
+
+//! Transition
+QList<QwtPickerMachine::Command> QwtPickerPolygonMachine::transition(
+ const QwtEventPattern &eventPattern, const QEvent *event )
+{
+ QList<QwtPickerMachine::Command> cmdList;
+
+ switch ( event->type() )
+ {
+ case QEvent::MouseButtonPress:
+ {
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect1, ( const QMouseEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += Append;
+ setState( 1 );
+ }
+ else
+ {
+ cmdList += Append;
+ }
+ }
+ if ( eventPattern.mouseMatch(
+ QwtEventPattern::MouseSelect2, ( const QMouseEvent * )event ) )
+ {
+ if ( state() == 1 )
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ break;
+ }
+ case QEvent::MouseMove:
+ case QEvent::Wheel:
+ {
+ if ( state() != 0 )
+ cmdList += Move;
+ break;
+ }
+ case QEvent::KeyPress:
+ {
+ if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect1, ( const QKeyEvent * )event ) )
+ {
+ if ( state() == 0 )
+ {
+ cmdList += Begin;
+ cmdList += Append;
+ cmdList += Append;
+ setState( 1 );
+ }
+ else
+ {
+ cmdList += Append;
+ }
+ }
+ else if ( eventPattern.keyMatch(
+ QwtEventPattern::KeySelect2, ( const QKeyEvent * )event ) )
+ {
+ if ( state() == 1 )
+ {
+ cmdList += End;
+ setState( 0 );
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cmdList;
+}
diff --git a/src/libpcp_qwt/src/qwt_picker_machine.h b/src/libpcp_qwt/src/qwt_picker_machine.h
new file mode 100644
index 0000000..8527115
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_picker_machine.h
@@ -0,0 +1,190 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PICKER_MACHINE
+#define QWT_PICKER_MACHINE 1
+
+#include "qwt_global.h"
+#include <qlist.h>
+
+class QEvent;
+class QwtEventPattern;
+
+/*!
+ \brief A state machine for QwtPicker selections
+
+ QwtPickerMachine accepts key and mouse events and translates them
+ into selection commands.
+
+ \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode
+*/
+
+class QWT_EXPORT QwtPickerMachine
+{
+public:
+ /*!
+ Type of a selection.
+ \sa selectionType()
+ */
+ enum SelectionType
+ {
+ //! The state machine not usable for any type of selection.
+ NoSelection = -1,
+
+ //! The state machine is for selecting a single point.
+ PointSelection,
+
+ //! The state machine is for selecting a rectangle (2 points).
+ RectSelection,
+
+ //! The state machine is for selecting a polygon (many points).
+ PolygonSelection
+ };
+
+ //! Commands - the output of a state machine
+ enum Command
+ {
+ Begin,
+ Append,
+ Move,
+ Remove,
+ End
+ };
+
+ QwtPickerMachine( SelectionType );
+ virtual ~QwtPickerMachine();
+
+ //! Transition
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * ) = 0;
+ void reset();
+
+ int state() const;
+ void setState( int );
+
+ SelectionType selectionType() const;
+
+private:
+ const SelectionType d_selectionType;
+ int d_state;
+};
+
+/*!
+ \brief A state machine for indicating mouse movements
+
+ QwtPickerTrackerMachine supports displaying information
+ corresponding to mouse movements, but is not intended for
+ selecting anything. Begin/End are related to Enter/Leave events.
+*/
+class QWT_EXPORT QwtPickerTrackerMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerTrackerMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+/*!
+ \brief A state machine for point selections
+
+ Pressing QwtEventPattern::MouseSelect1 or
+ QwtEventPattern::KeySelect1 selects a point.
+
+ \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode
+*/
+class QWT_EXPORT QwtPickerClickPointMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerClickPointMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+/*!
+ \brief A state machine for point selections
+
+ Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1
+ starts the selection, releasing QwtEventPattern::MouseSelect1 or
+ a second press of QwtEventPattern::KeySelect1 terminates it.
+*/
+class QWT_EXPORT QwtPickerDragPointMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerDragPointMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+/*!
+ \brief A state machine for rectangle selections
+
+ Pressing QwtEventPattern::MouseSelect1 starts
+ the selection, releasing it selects the first point. Pressing it
+ again selects the second point and terminates the selection.
+ Pressing QwtEventPattern::KeySelect1 also starts the
+ selection, a second press selects the first point. A third one selects
+ the second point and terminates the selection.
+
+ \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode
+*/
+
+class QWT_EXPORT QwtPickerClickRectMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerClickRectMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+/*!
+ \brief A state machine for rectangle selections
+
+ Pressing QwtEventPattern::MouseSelect1 selects
+ the first point, releasing it the second point.
+ Pressing QwtEventPattern::KeySelect1 also selects the
+ first point, a second press selects the second point and terminates
+ the selection.
+
+ \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode
+*/
+
+class QWT_EXPORT QwtPickerDragRectMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerDragRectMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+/*!
+ \brief A state machine for polygon selections
+
+ Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1
+ starts the selection and selects the first point, or appends a point.
+ Pressing QwtEventPattern::MouseSelect2 or QwtEventPattern::KeySelect2
+ appends the last point and terminates the selection.
+
+ \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode
+*/
+
+class QWT_EXPORT QwtPickerPolygonMachine: public QwtPickerMachine
+{
+public:
+ QwtPickerPolygonMachine();
+
+ virtual QList<Command> transition(
+ const QwtEventPattern &, const QEvent * );
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot.cpp b/src/libpcp_qwt/src/qwt_plot.cpp
new file mode 100644
index 0000000..1e2fb3e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot.cpp
@@ -0,0 +1,751 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot.h"
+#include "qwt_plot_dict.h"
+#include "qwt_plot_layout.h"
+#include "qwt_scale_widget.h"
+#include "qwt_scale_engine.h"
+#include "qwt_text_label.h"
+#include "qwt_legend.h"
+#include "qwt_dyngrid_layout.h"
+#include "qwt_plot_canvas.h"
+#include <qpainter.h>
+#include <qpointer.h>
+#include <qpaintengine.h>
+#include <qapplication.h>
+#include <qevent.h>
+
+class QwtPlot::PrivateData
+{
+public:
+ QPointer<QwtTextLabel> titleLabel;
+ QPointer<QwtPlotCanvas> canvas;
+ QPointer<QwtLegend> legend;
+ QwtPlotLayout *layout;
+
+ bool autoReplot;
+};
+
+/*!
+ \brief Constructor
+ \param parent Parent widget
+ */
+QwtPlot::QwtPlot( QWidget *parent ):
+ QFrame( parent )
+{
+ initPlot( QwtText() );
+}
+
+/*!
+ \brief Constructor
+ \param title Title text
+ \param parent Parent widget
+ */
+QwtPlot::QwtPlot( const QwtText &title, QWidget *parent ):
+ QFrame( parent )
+{
+ initPlot( title );
+}
+
+//! Destructor
+QwtPlot::~QwtPlot()
+{
+ detachItems( QwtPlotItem::Rtti_PlotItem, autoDelete() );
+
+ delete d_data->layout;
+ deleteAxesData();
+ delete d_data;
+}
+
+/*!
+ \brief Initializes a QwtPlot instance
+ \param title Title text
+ */
+void QwtPlot::initPlot( const QwtText &title )
+{
+ d_data = new PrivateData;
+
+ d_data->layout = new QwtPlotLayout;
+ d_data->autoReplot = false;
+
+ // title
+ d_data->titleLabel = new QwtTextLabel( this );
+ d_data->titleLabel->setObjectName( "QwtPlotTitle" );
+ d_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) );
+
+ QwtText text( title );
+ text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap );
+ d_data->titleLabel->setText( text );
+
+ // legend
+ d_data->legend = NULL;
+
+ // axis
+ initAxesData();
+
+ // canvas
+ d_data->canvas = new QwtPlotCanvas( this );
+ d_data->canvas->setObjectName( "QwtPlotCanvas" );
+ d_data->canvas->setFrameStyle( QFrame::Panel | QFrame::Sunken );
+ d_data->canvas->setLineWidth( 2 );
+
+ updateTabOrder();
+
+ setSizePolicy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::MinimumExpanding );
+
+ resize( 200, 200 );
+}
+
+/*!
+ \brief Adds handling of layout requests
+ \param event Event
+*/
+bool QwtPlot::event( QEvent *event )
+{
+ bool ok = QFrame::event( event );
+ switch ( event->type() )
+ {
+ case QEvent::LayoutRequest:
+ updateLayout();
+ break;
+ case QEvent::PolishRequest:
+ replot();
+ break;
+ default:;
+ }
+ return ok;
+}
+
+//! Replots the plot if autoReplot() is \c true.
+void QwtPlot::autoRefresh()
+{
+ if ( d_data->autoReplot )
+ replot();
+}
+
+/*!
+ \brief Set or reset the autoReplot option
+
+ If the autoReplot option is set, the plot will be
+ updated implicitly by manipulating member functions.
+ Since this may be time-consuming, it is recommended
+ to leave this option switched off and call replot()
+ explicitly if necessary.
+
+ The autoReplot option is set to false by default, which
+ means that the user has to call replot() in order to make
+ changes visible.
+ \param tf \c true or \c false. Defaults to \c true.
+ \sa replot()
+*/
+void QwtPlot::setAutoReplot( bool tf )
+{
+ d_data->autoReplot = tf;
+}
+
+/*!
+ \return true if the autoReplot option is set.
+ \sa setAutoReplot()
+*/
+bool QwtPlot::autoReplot() const
+{
+ return d_data->autoReplot;
+}
+
+/*!
+ Change the plot's title
+ \param title New title
+*/
+void QwtPlot::setTitle( const QString &title )
+{
+ if ( title != d_data->titleLabel->text().text() )
+ {
+ d_data->titleLabel->setText( title );
+ updateLayout();
+ }
+}
+
+/*!
+ Change the plot's title
+ \param title New title
+*/
+void QwtPlot::setTitle( const QwtText &title )
+{
+ if ( title != d_data->titleLabel->text() )
+ {
+ d_data->titleLabel->setText( title );
+ updateLayout();
+ }
+}
+
+//! \return Title of the plot
+QwtText QwtPlot::title() const
+{
+ return d_data->titleLabel->text();
+}
+
+//! \return the plot's title
+QwtPlotLayout *QwtPlot::plotLayout()
+{
+ return d_data->layout;
+}
+
+//! \return the plot's layout
+const QwtPlotLayout *QwtPlot::plotLayout() const
+{
+ return d_data->layout;
+}
+
+//! \return the plot's titel label.
+QwtTextLabel *QwtPlot::titleLabel()
+{
+ return d_data->titleLabel;
+}
+
+/*!
+ \return the plot's titel label.
+*/
+const QwtTextLabel *QwtPlot::titleLabel() const
+{
+ return d_data->titleLabel;
+}
+
+/*!
+ \return the plot's legend
+ \sa insertLegend()
+*/
+QwtLegend *QwtPlot::legend()
+{
+ return d_data->legend;
+}
+
+/*!
+ \return the plot's legend
+ \sa insertLegend()
+*/
+const QwtLegend *QwtPlot::legend() const
+{
+ return d_data->legend;
+}
+
+
+/*!
+ \return the plot's canvas
+*/
+QwtPlotCanvas *QwtPlot::canvas()
+{
+ return d_data->canvas;
+}
+
+/*!
+ \return the plot's canvas
+*/
+const QwtPlotCanvas *QwtPlot::canvas() const
+{
+ return d_data->canvas;
+}
+
+/*!
+ Return sizeHint
+ \sa minimumSizeHint()
+*/
+
+QSize QwtPlot::sizeHint() const
+{
+ int dw = 0;
+ int dh = 0;
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ {
+ if ( axisEnabled( axisId ) )
+ {
+ const int niceDist = 40;
+ const QwtScaleWidget *scaleWidget = axisWidget( axisId );
+ const QwtScaleDiv &scaleDiv = scaleWidget->scaleDraw()->scaleDiv();
+ const int majCnt = scaleDiv.ticks( QwtScaleDiv::MajorTick ).count();
+
+ if ( axisId == yLeft || axisId == yRight )
+ {
+ int hDiff = ( majCnt - 1 ) * niceDist
+ - scaleWidget->minimumSizeHint().height();
+ if ( hDiff > dh )
+ dh = hDiff;
+ }
+ else
+ {
+ int wDiff = ( majCnt - 1 ) * niceDist
+ - scaleWidget->minimumSizeHint().width();
+ if ( wDiff > dw )
+ dw = wDiff;
+ }
+ }
+ }
+ return minimumSizeHint() + QSize( dw, dh );
+}
+
+/*!
+ \brief Return a minimum size hint
+*/
+QSize QwtPlot::minimumSizeHint() const
+{
+ QSize hint = d_data->layout->minimumSizeHint( this );
+ hint += QSize( 2 * frameWidth(), 2 * frameWidth() );
+
+ return hint;
+}
+
+/*!
+ Resize and update internal layout
+ \param e Resize event
+*/
+void QwtPlot::resizeEvent( QResizeEvent *e )
+{
+ QFrame::resizeEvent( e );
+ updateLayout();
+}
+
+/*!
+ \brief Redraw the plot
+
+ If the autoReplot option is not set (which is the default)
+ or if any curves are attached to raw data, the plot has to
+ be refreshed explicitly in order to make changes visible.
+
+ \sa setAutoReplot()
+ \warning Calls canvas()->repaint, take care of infinite recursions
+*/
+void QwtPlot::replot()
+{
+ bool doAutoReplot = autoReplot();
+ setAutoReplot( false );
+
+ updateAxes();
+
+ /*
+ Maybe the layout needs to be updated, because of changed
+ axes labels. We need to process them here before painting
+ to avoid that scales and canvas get out of sync.
+ */
+ QApplication::sendPostedEvents( this, QEvent::LayoutRequest );
+
+ d_data->canvas->replot();
+
+ setAutoReplot( doAutoReplot );
+}
+
+/*!
+ \brief Adjust plot content to its current size.
+ \sa resizeEvent()
+*/
+void QwtPlot::updateLayout()
+{
+ d_data->layout->activate( this, contentsRect() );
+
+ QRect titleRect = d_data->layout->titleRect().toRect();
+ QRect scaleRect[QwtPlot::axisCnt];
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ scaleRect[axisId] = d_data->layout->scaleRect( axisId ).toRect();
+ QRect legendRect = d_data->layout->legendRect().toRect();
+ QRect canvasRect = d_data->layout->canvasRect().toRect();
+
+ // resize and show the visible widgets
+ if ( !d_data->titleLabel->text().isEmpty() )
+ {
+ d_data->titleLabel->setGeometry( titleRect );
+ if ( !d_data->titleLabel->isVisibleTo( this ) )
+ d_data->titleLabel->show();
+ }
+ else
+ d_data->titleLabel->hide();
+
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ {
+ if ( axisEnabled( axisId ) )
+ {
+ axisWidget( axisId )->setGeometry( scaleRect[axisId] );
+
+#if 1
+ if ( axisId == xBottom || axisId == xTop )
+ {
+ // do we need this code any longer ???
+
+ QRegion r( scaleRect[axisId] );
+ if ( axisEnabled( yLeft ) )
+ r = r.subtract( QRegion( scaleRect[yLeft] ) );
+ if ( axisEnabled( yRight ) )
+ r = r.subtract( QRegion( scaleRect[yRight] ) );
+ r.translate( -scaleRect[ axisId ].x(),
+ -scaleRect[axisId].y() );
+
+ axisWidget( axisId )->setMask( r );
+ }
+#endif
+ if ( !axisWidget( axisId )->isVisibleTo( this ) )
+ axisWidget( axisId )->show();
+ }
+ else
+ axisWidget( axisId )->hide();
+ }
+
+ if ( d_data->legend &&
+ d_data->layout->legendPosition() != ExternalLegend )
+ {
+ if ( d_data->legend->itemCount() > 0 )
+ {
+ d_data->legend->setGeometry( legendRect );
+ d_data->legend->show();
+ }
+ else
+ d_data->legend->hide();
+ }
+
+ d_data->canvas->setGeometry( canvasRect );
+}
+
+/*!
+ Update the focus tab order
+
+ The order is changed so that the canvas will be in front of the
+ first legend item, or behind the last legend item - depending
+ on the position of the legend.
+*/
+
+void QwtPlot::updateTabOrder()
+{
+ if ( d_data->canvas->focusPolicy() == Qt::NoFocus )
+ return;
+ if ( d_data->legend.isNull()
+ || d_data->layout->legendPosition() == ExternalLegend
+ || d_data->legend->legendItems().count() == 0 )
+ {
+ return;
+ }
+
+ // Depending on the position of the legend the
+ // tab order will be changed that the canvas is
+ // next to the last legend item, or before
+ // the first one.
+
+ const bool canvasFirst =
+ d_data->layout->legendPosition() == QwtPlot::BottomLegend ||
+ d_data->layout->legendPosition() == QwtPlot::RightLegend;
+
+ QWidget *previous = NULL;
+
+ QWidget *w = d_data->canvas;
+ while ( ( w = w->nextInFocusChain() ) != d_data->canvas )
+ {
+ bool isLegendItem = false;
+ if ( w->focusPolicy() != Qt::NoFocus
+ && w->parent() && w->parent() == d_data->legend->contentsWidget() )
+ {
+ isLegendItem = true;
+ }
+
+ if ( canvasFirst )
+ {
+ if ( isLegendItem )
+ break;
+
+ previous = w;
+ }
+ else
+ {
+ if ( isLegendItem )
+ previous = w;
+ else
+ {
+ if ( previous )
+ break;
+ }
+ }
+ }
+
+ if ( previous && previous != d_data->canvas )
+ setTabOrder( previous, d_data->canvas );
+}
+
+/*!
+ Redraw the canvas.
+ \param painter Painter used for drawing
+
+ \warning drawCanvas calls drawItems what is also used
+ for printing. Applications that like to add individual
+ plot items better overload drawItems()
+ \sa drawItems()
+*/
+void QwtPlot::drawCanvas( QPainter *painter )
+{
+ QwtScaleMap maps[axisCnt];
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ maps[axisId] = canvasMap( axisId );
+
+ drawItems( painter, d_data->canvas->contentsRect(), maps );
+}
+
+/*!
+ Redraw the canvas items.
+ \param painter Painter used for drawing
+ \param canvasRect Bounding rectangle where to paint
+ \param map QwtPlot::axisCnt maps, mapping between plot and paint device coordinates
+*/
+
+void QwtPlot::drawItems( QPainter *painter, const QRectF &canvasRect,
+ const QwtScaleMap map[axisCnt] ) const
+{
+ const QwtPlotItemList& itmList = itemList();
+ for ( QwtPlotItemIterator it = itmList.begin();
+ it != itmList.end(); ++it )
+ {
+ QwtPlotItem *item = *it;
+ if ( item && item->isVisible() )
+ {
+ painter->save();
+
+ painter->setRenderHint( QPainter::Antialiasing,
+ item->testRenderHint( QwtPlotItem::RenderAntialiased ) );
+
+ item->draw( painter,
+ map[item->xAxis()], map[item->yAxis()],
+ canvasRect );
+
+ painter->restore();
+ }
+ }
+}
+
+/*!
+ \param axisId Axis
+ \return Map for the axis on the canvas. With this map pixel coordinates can
+ translated to plot coordinates and vice versa.
+ \sa QwtScaleMap, transform(), invTransform()
+
+*/
+QwtScaleMap QwtPlot::canvasMap( int axisId ) const
+{
+ QwtScaleMap map;
+ if ( !d_data->canvas )
+ return map;
+
+ map.setTransformation( axisScaleEngine( axisId )->transformation() );
+
+ const QwtScaleDiv *sd = axisScaleDiv( axisId );
+ map.setScaleInterval( sd->lowerBound(), sd->upperBound() );
+
+ if ( axisEnabled( axisId ) )
+ {
+ const QwtScaleWidget *s = axisWidget( axisId );
+ if ( axisId == yLeft || axisId == yRight )
+ {
+ double y = s->y() + s->startBorderDist() - d_data->canvas->y();
+ double h = s->height() - s->startBorderDist() - s->endBorderDist();
+ map.setPaintInterval( y + h, y );
+ }
+ else
+ {
+ double x = s->x() + s->startBorderDist() - d_data->canvas->x();
+ double w = s->width() - s->startBorderDist() - s->endBorderDist();
+ map.setPaintInterval( x, x + w );
+ }
+ }
+ else
+ {
+ int margin = 0;
+ if ( !plotLayout()->alignCanvasToScales() )
+ margin = plotLayout()->canvasMargin( axisId );
+
+ const QRect &canvasRect = d_data->canvas->contentsRect();
+ if ( axisId == yLeft || axisId == yRight )
+ {
+ map.setPaintInterval( canvasRect.bottom() - margin,
+ canvasRect.top() + margin );
+ }
+ else
+ {
+ map.setPaintInterval( canvasRect.left() + margin,
+ canvasRect.right() - margin );
+ }
+ }
+ return map;
+}
+
+/*!
+ \brief Change the background of the plotting area
+
+ Sets brush to QPalette::Window of all colorgroups of
+ the palette of the canvas. Using canvas()->setPalette()
+ is a more powerful way to set these colors.
+
+ \param brush New background brush
+ \sa canvasBackground()
+*/
+void QwtPlot::setCanvasBackground( const QBrush &brush )
+{
+ QPalette pal = d_data->canvas->palette();
+ pal.setBrush( QPalette::Window, brush );
+
+ canvas()->setPalette( pal );
+}
+
+/*!
+ Nothing else than: canvas()->palette().brush(
+ QPalette::Normal, QPalette::Window);
+
+ \return Background brush of the plotting area.
+ \sa setCanvasBackground()
+*/
+QBrush QwtPlot::canvasBackground() const
+{
+ return canvas()->palette().brush(
+ QPalette::Normal, QPalette::Window );
+}
+
+/*!
+ \brief Change the border width of the plotting area
+
+ Nothing else than canvas()->setLineWidth(w),
+ left for compatibility only.
+
+ \param width New border width
+*/
+void QwtPlot::setCanvasLineWidth( int width )
+{
+ canvas()->setLineWidth( width );
+ updateLayout();
+}
+
+/*!
+ Nothing else than: canvas()->lineWidth(),
+ left for compatibility only.
+
+ \return the border width of the plotting area
+*/
+int QwtPlot::canvasLineWidth() const
+{
+ return canvas()->lineWidth();
+}
+
+/*!
+ \return \c true if the specified axis exists, otherwise \c false
+ \param axisId axis index
+ */
+bool QwtPlot::axisValid( int axisId )
+{
+ return ( ( axisId >= QwtPlot::yLeft ) && ( axisId < QwtPlot::axisCnt ) );
+}
+
+/*!
+ Called internally when the legend has been clicked on.
+ Emits a legendClicked() signal.
+*/
+void QwtPlot::legendItemClicked()
+{
+ if ( d_data->legend && sender()->isWidgetType() )
+ {
+ QwtPlotItem *plotItem =
+ ( QwtPlotItem* )d_data->legend->find( ( QWidget * )sender() );
+ if ( plotItem )
+ Q_EMIT legendClicked( plotItem );
+ }
+}
+
+/*!
+ Called internally when the legend has been checked
+ Emits a legendClicked() signal.
+*/
+void QwtPlot::legendItemChecked( bool on )
+{
+ if ( d_data->legend && sender()->isWidgetType() )
+ {
+ QwtPlotItem *plotItem =
+ ( QwtPlotItem* )d_data->legend->find( ( QWidget * )sender() );
+ if ( plotItem )
+ Q_EMIT legendChecked( plotItem, on );
+ }
+}
+
+/*!
+ \brief Insert a legend
+
+ If the position legend is \c QwtPlot::LeftLegend or \c QwtPlot::RightLegend
+ the legend will be organized in one column from top to down.
+ Otherwise the legend items will be placed in a table
+ with a best fit number of columns from left to right.
+
+ If pos != QwtPlot::ExternalLegend the plot widget will become
+ parent of the legend. It will be deleted when the plot is deleted,
+ or another legend is set with insertLegend().
+
+ \param legend Legend
+ \param pos The legend's position. For top/left position the number
+ of colums will be limited to 1, otherwise it will be set to
+ unlimited.
+
+ \param ratio Ratio between legend and the bounding rect
+ of title, canvas and axes. The legend will be shrinked
+ if it would need more space than the given ratio.
+ The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0
+ it will be reset to the default ratio.
+ The default vertical/horizontal ratio is 0.33/0.5.
+
+ \sa legend(), QwtPlotLayout::legendPosition(),
+ QwtPlotLayout::setLegendPosition()
+*/
+void QwtPlot::insertLegend( QwtLegend *legend,
+ QwtPlot::LegendPosition pos, double ratio )
+{
+ d_data->layout->setLegendPosition( pos, ratio );
+
+ if ( legend != d_data->legend )
+ {
+ if ( d_data->legend && d_data->legend->parent() == this )
+ delete d_data->legend;
+
+ d_data->legend = legend;
+
+ if ( d_data->legend )
+ {
+ if ( pos != ExternalLegend )
+ {
+ if ( d_data->legend->parent() != this )
+ d_data->legend->setParent( this );
+ }
+
+ const QwtPlotItemList& itmList = itemList();
+ for ( QwtPlotItemIterator it = itmList.begin();
+ it != itmList.end(); ++it )
+ {
+ ( *it )->updateLegend( d_data->legend );
+ }
+
+ QwtDynGridLayout *tl = qobject_cast<QwtDynGridLayout *>(
+ d_data->legend->contentsWidget()->layout() );
+ if ( tl )
+ {
+ switch ( d_data->layout->legendPosition() )
+ {
+ case LeftLegend:
+ case RightLegend:
+ tl->setMaxCols( 1 ); // 1 column: align vertical
+ break;
+ case TopLegend:
+ case BottomLegend:
+ tl->setMaxCols( 0 ); // unlimited
+ break;
+ case ExternalLegend:
+ break;
+ }
+ }
+ }
+ updateTabOrder();
+ }
+
+ updateLayout();
+}
diff --git a/src/libpcp_qwt/src/qwt_plot.h b/src/libpcp_qwt/src/qwt_plot.h
new file mode 100644
index 0000000..8a81f9b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot.h
@@ -0,0 +1,290 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_H
+#define QWT_PLOT_H
+
+#include "qwt_global.h"
+#include "qwt_text.h"
+#include "qwt_plot_dict.h"
+#include "qwt_scale_map.h"
+#include "qwt_interval.h"
+#include <qframe.h>
+
+class QwtPlotLayout;
+class QwtLegend;
+class QwtScaleWidget;
+class QwtScaleEngine;
+class QwtScaleDiv;
+class QwtScaleDraw;
+class QwtTextLabel;
+class QwtPlotCanvas;
+
+/*!
+ \brief A 2-D plotting widget
+
+ QwtPlot is a widget for plotting two-dimensional graphs.
+ An unlimited number of plot items can be displayed on
+ its canvas. Plot items might be curves (QwtPlotCurve), markers
+ (QwtPlotMarker), the grid (QwtPlotGrid), or anything else derived
+ from QwtPlotItem.
+ A plot can have up to four axes, with each plot item attached to an x- and
+ a y axis. The scales at the axes can be explicitely set (QwtScaleDiv), or
+ are calculated from the plot items, using algorithms (QwtScaleEngine) which
+ can be configured separately for each axis.
+
+ \image html plot.png
+
+ \par Example
+ The following example shows (schematically) the most simple
+ way to use QwtPlot. By default, only the left and bottom axes are
+ visible and their scales are computed automatically.
+ \verbatim
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+
+QwtPlot *myPlot = new QwtPlot("Two Curves", parent);
+
+// add curves
+QwtPlotCurve *curve1 = new QwtPlotCurve("Curve 1");
+QwtPlotCurve *curve2 = new QwtPlotCurve("Curve 2");
+
+// connect or copy the data to the curves
+curve1->setData(...);
+curve2->setData(...);
+
+curve1->attach(myPlot);
+curve2->attach(myPlot);
+
+// finally, refresh the plot
+myPlot->replot();
+\endverbatim
+*/
+
+class QWT_EXPORT QwtPlot: public QFrame, public QwtPlotDict
+{
+ Q_OBJECT
+ Q_PROPERTY( QString propertiesDocument
+ READ grabProperties WRITE applyProperties )
+
+public:
+ //! \brief Axis index
+ enum Axis
+ {
+ //! Y axis left of the canvas
+ yLeft,
+
+ //! Y axis right of the canvas
+ yRight,
+
+ //! X axis below the canvas
+ xBottom,
+
+ //! X axis above the canvas
+ xTop,
+
+ //! Number of axes
+ axisCnt
+ };
+
+ /*!
+ Position of the legend, relative to the canvas.
+
+ \sa insertLegend()
+ \note In case of ExternalLegend, the legend is not
+ handled by QwtPlotRenderer
+ */
+ enum LegendPosition
+ {
+ //! The legend will be left from the QwtPlot::yLeft axis.
+ LeftLegend,
+
+ //! The legend will be right from the QwtPlot::yRight axis.
+ RightLegend,
+
+ //! The legend will be below QwtPlot::xBottom axis.
+ BottomLegend,
+
+ //! The legend will be between QwtPlot::xTop axis and the title.
+ TopLegend,
+
+ /*!
+ External means that only the content of the legend
+ will be handled by QwtPlot, but not its geometry.
+ This type can be used to have a legend in an
+ external window ( or on the canvas ).
+ */
+ ExternalLegend
+ };
+
+ explicit QwtPlot( QWidget * = NULL );
+ explicit QwtPlot( const QwtText &title, QWidget *p = NULL );
+
+ virtual ~QwtPlot();
+
+ void applyProperties( const QString & );
+ QString grabProperties() const;
+
+ void setAutoReplot( bool tf = true );
+ bool autoReplot() const;
+
+ // Layout
+
+ QwtPlotLayout *plotLayout();
+ const QwtPlotLayout *plotLayout() const;
+
+ // Title
+
+ void setTitle( const QString & );
+ void setTitle( const QwtText &t );
+ QwtText title() const;
+
+ QwtTextLabel *titleLabel();
+ const QwtTextLabel *titleLabel() const;
+
+ // Canvas
+
+ QwtPlotCanvas *canvas();
+ const QwtPlotCanvas *canvas() const;
+
+ void setCanvasBackground( const QBrush & );
+ QBrush canvasBackground() const;
+
+ void setCanvasLineWidth( int w );
+ int canvasLineWidth() const;
+
+ virtual QwtScaleMap canvasMap( int axisId ) const;
+
+ double invTransform( int axisId, int pos ) const;
+ double transform( int axisId, double value ) const;
+
+ // Axes
+
+ QwtScaleEngine *axisScaleEngine( int axisId );
+ const QwtScaleEngine *axisScaleEngine( int axisId ) const;
+ void setAxisScaleEngine( int axisId, QwtScaleEngine * );
+
+ void setAxisAutoScale( int axisId, bool on = true );
+ bool axisAutoScale( int axisId ) const;
+
+ void enableAxis( int axisId, bool tf = true );
+ bool axisEnabled( int axisId ) const;
+
+ void setAxisFont( int axisId, const QFont &f );
+ QFont axisFont( int axisId ) const;
+
+ void setAxisScale( int axisId, double min, double max, double step = 0 );
+ void setAxisScaleDiv( int axisId, const QwtScaleDiv & );
+ void setAxisScaleDraw( int axisId, QwtScaleDraw * );
+
+ double axisStepSize( int axisId ) const;
+ QwtInterval axisInterval( int axisId ) const;
+
+ const QwtScaleDiv *axisScaleDiv( int axisId ) const;
+ QwtScaleDiv *axisScaleDiv( int axisId );
+
+ const QwtScaleDraw *axisScaleDraw( int axisId ) const;
+ QwtScaleDraw *axisScaleDraw( int axisId );
+
+ const QwtScaleWidget *axisWidget( int axisId ) const;
+ QwtScaleWidget *axisWidget( int axisId );
+
+ void setAxisLabelAlignment( int axisId, Qt::Alignment );
+ void setAxisLabelRotation( int axisId, double rotation );
+
+ void setAxisTitle( int axisId, const QString & );
+ void setAxisTitle( int axisId, const QwtText & );
+ QwtText axisTitle( int axisId ) const;
+
+ void setAxisMaxMinor( int axisId, int maxMinor );
+ int axisMaxMinor( int axisId ) const;
+
+ void setAxisMaxMajor( int axisId, int maxMajor );
+ int axisMaxMajor( int axisId ) const;
+
+ // Legend
+
+ void insertLegend( QwtLegend *, LegendPosition = QwtPlot::RightLegend,
+ double ratio = -1.0 );
+
+ QwtLegend *legend();
+ const QwtLegend *legend() const;
+
+ // Misc
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ virtual void updateLayout();
+ virtual void drawCanvas( QPainter * );
+
+ void updateAxes();
+
+ virtual bool event( QEvent * );
+
+ virtual void drawItems( QPainter *, const QRectF &,
+ const QwtScaleMap maps[axisCnt] ) const;
+
+Q_SIGNALS:
+ /*!
+ A signal which is emitted when the user has clicked on
+ a legend item, which is in QwtLegend::ClickableItem mode.
+
+ \param plotItem Corresponding plot item of the
+ selected legend item
+
+ \note clicks are disabled as default
+ \sa QwtLegend::setItemMode(), QwtLegend::itemMode()
+ */
+ void legendClicked( QwtPlotItem *plotItem );
+
+ /*!
+ A signal which is emitted when the user has clicked on
+ a legend item, which is in QwtLegend::CheckableItem mode
+
+ \param plotItem Corresponding plot item of the
+ selected legend item
+ \param on True when the legen item is checked
+
+ \note clicks are disabled as default
+ \sa QwtLegend::setItemMode(), QwtLegend::itemMode()
+ */
+
+ void legendChecked( QwtPlotItem *plotItem, bool on );
+
+public Q_SLOTS:
+ virtual void replot();
+ void autoRefresh();
+
+protected Q_SLOTS:
+ virtual void legendItemClicked();
+ virtual void legendItemChecked( bool );
+
+protected:
+ static bool axisValid( int axisId );
+
+ virtual void updateTabOrder();
+
+ virtual void resizeEvent( QResizeEvent *e );
+
+private:
+ void initAxesData();
+ void deleteAxesData();
+ void updateScaleDiv();
+
+ void initPlot( const QwtText &title );
+
+ class AxisData;
+ AxisData *d_axisData[axisCnt];
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_axis.cpp b/src/libpcp_qwt/src/qwt_plot_axis.cpp
new file mode 100644
index 0000000..66f2fa1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_axis.cpp
@@ -0,0 +1,670 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot.h"
+#include "qwt_math.h"
+#include "qwt_scale_widget.h"
+#include "qwt_scale_div.h"
+#include "qwt_scale_engine.h"
+
+class QwtPlot::AxisData
+{
+public:
+ bool isEnabled;
+ bool doAutoScale;
+
+ double minValue;
+ double maxValue;
+ double stepSize;
+
+ int maxMajor;
+ int maxMinor;
+
+ QwtScaleDiv scaleDiv;
+ QwtScaleEngine *scaleEngine;
+ QwtScaleWidget *scaleWidget;
+};
+
+//! Initialize axes
+void QwtPlot::initAxesData()
+{
+ int axisId;
+
+ for ( axisId = 0; axisId < axisCnt; axisId++ )
+ d_axisData[axisId] = new AxisData;
+
+ d_axisData[yLeft]->scaleWidget =
+ new QwtScaleWidget( QwtScaleDraw::LeftScale, this );
+ d_axisData[yRight]->scaleWidget =
+ new QwtScaleWidget( QwtScaleDraw::RightScale, this );
+ d_axisData[xTop]->scaleWidget =
+ new QwtScaleWidget( QwtScaleDraw::TopScale, this );
+ d_axisData[xBottom]->scaleWidget =
+ new QwtScaleWidget( QwtScaleDraw::BottomScale, this );
+
+ d_axisData[yLeft]->scaleWidget->setObjectName( "QwtPlotAxisYLeft" );
+ d_axisData[yRight]->scaleWidget->setObjectName( "QwtPlotAxisYRight" );
+ d_axisData[xTop]->scaleWidget->setObjectName( "QwtPlotAxisXTop" );
+ d_axisData[xBottom]->scaleWidget->setObjectName( "QwtPlotAxisXBottom" );
+
+ QFont fscl( fontInfo().family(), 10 );
+ QFont fttl( fontInfo().family(), 12, QFont::Bold );
+
+ for ( axisId = 0; axisId < axisCnt; axisId++ )
+ {
+ AxisData &d = *d_axisData[axisId];
+
+ d.scaleWidget->setFont( fscl );
+ d.scaleWidget->setMargin( 2 );
+
+ QwtText text = d.scaleWidget->title();
+ text.setFont( fttl );
+ d.scaleWidget->setTitle( text );
+
+ d.doAutoScale = true;
+
+ d.minValue = 0.0;
+ d.maxValue = 1000.0;
+ d.stepSize = 0.0;
+
+ d.maxMinor = 5;
+ d.maxMajor = 8;
+
+ d.scaleEngine = new QwtLinearScaleEngine;
+
+ d.scaleDiv.invalidate();
+ }
+
+ d_axisData[yLeft]->isEnabled = true;
+ d_axisData[yRight]->isEnabled = false;
+ d_axisData[xBottom]->isEnabled = true;
+ d_axisData[xTop]->isEnabled = false;
+}
+
+void QwtPlot::deleteAxesData()
+{
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ {
+ delete d_axisData[axisId]->scaleEngine;
+ delete d_axisData[axisId];
+ d_axisData[axisId] = NULL;
+ }
+}
+
+/*!
+ \return specified axis, or NULL if axisId is invalid.
+ \param axisId axis index
+*/
+const QwtScaleWidget *QwtPlot::axisWidget( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->scaleWidget;
+
+ return NULL;
+}
+
+/*!
+ \return specified axis, or NULL if axisId is invalid.
+ \param axisId axis index
+*/
+QwtScaleWidget *QwtPlot::axisWidget( int axisId )
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->scaleWidget;
+
+ return NULL;
+}
+
+/*!
+ Change the scale engine for an axis
+
+ \param axisId axis index
+ \param scaleEngine Scale engine
+
+ \sa axisScaleEngine()
+*/
+void QwtPlot::setAxisScaleEngine( int axisId, QwtScaleEngine *scaleEngine )
+{
+ if ( axisValid( axisId ) && scaleEngine != NULL )
+ {
+ AxisData &d = *d_axisData[axisId];
+
+ delete d.scaleEngine;
+ d.scaleEngine = scaleEngine;
+
+ d.scaleDiv.invalidate();
+
+ autoRefresh();
+ }
+}
+
+/*!
+ \param axisId axis index
+ \return Scale engine for a specific axis
+*/
+QwtScaleEngine *QwtPlot::axisScaleEngine( int axisId )
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->scaleEngine;
+ else
+ return NULL;
+}
+
+/*!
+ \param axisId axis index
+ \return Scale engine for a specific axis
+*/
+const QwtScaleEngine *QwtPlot::axisScaleEngine( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->scaleEngine;
+ else
+ return NULL;
+}
+/*!
+ \return \c true if autoscaling is enabled
+ \param axisId axis index
+*/
+bool QwtPlot::axisAutoScale( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->doAutoScale;
+ else
+ return false;
+
+}
+
+/*!
+ \return \c true if a specified axis is enabled
+ \param axisId axis index
+*/
+bool QwtPlot::axisEnabled( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->isEnabled;
+ else
+ return false;
+}
+
+/*!
+ \return the font of the scale labels for a specified axis
+ \param axisId axis index
+*/
+QFont QwtPlot::axisFont( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return axisWidget( axisId )->font();
+ else
+ return QFont();
+
+}
+
+/*!
+ \return the maximum number of major ticks for a specified axis
+ \param axisId axis index
+ \sa setAxisMaxMajor()
+*/
+int QwtPlot::axisMaxMajor( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->maxMajor;
+ else
+ return 0;
+}
+
+/*!
+ \return the maximum number of minor ticks for a specified axis
+ \param axisId axis index
+ \sa setAxisMaxMinor()
+*/
+int QwtPlot::axisMaxMinor( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return d_axisData[axisId]->maxMinor;
+ else
+ return 0;
+}
+
+/*!
+ \brief Return the scale division of a specified axis
+
+ axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound()
+ are the current limits of the axis scale.
+
+ \param axisId axis index
+ \return Scale division
+
+ \sa QwtScaleDiv, setAxisScaleDiv()
+*/
+const QwtScaleDiv *QwtPlot::axisScaleDiv( int axisId ) const
+{
+ if ( !axisValid( axisId ) )
+ return NULL;
+
+ return &d_axisData[axisId]->scaleDiv;
+}
+
+/*!
+ \brief Return the scale division of a specified axis
+
+ axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound()
+ are the current limits of the axis scale.
+
+ \param axisId axis index
+ \return Scale division
+
+ \sa QwtScaleDiv, setAxisScaleDiv()
+*/
+QwtScaleDiv *QwtPlot::axisScaleDiv( int axisId )
+{
+ if ( !axisValid( axisId ) )
+ return NULL;
+
+ return &d_axisData[axisId]->scaleDiv;
+}
+
+/*!
+ \returns the scale draw of a specified axis
+ \param axisId axis index
+ \return specified scaleDraw for axis, or NULL if axis is invalid.
+ \sa QwtScaleDraw
+*/
+const QwtScaleDraw *QwtPlot::axisScaleDraw( int axisId ) const
+{
+ if ( !axisValid( axisId ) )
+ return NULL;
+
+ return axisWidget( axisId )->scaleDraw();
+}
+
+/*!
+ \returns the scale draw of a specified axis
+ \param axisId axis index
+ \return specified scaleDraw for axis, or NULL if axis is invalid.
+ \sa QwtScaleDraw
+*/
+QwtScaleDraw *QwtPlot::axisScaleDraw( int axisId )
+{
+ if ( !axisValid( axisId ) )
+ return NULL;
+
+ return axisWidget( axisId )->scaleDraw();
+}
+
+/*!
+ Return the step size parameter, that has been set
+ in setAxisScale. This doesn't need to be the step size
+ of the current scale.
+
+ \param axisId axis index
+ \return step size parameter value
+
+ \sa setAxisScale()
+*/
+double QwtPlot::axisStepSize( int axisId ) const
+{
+ if ( !axisValid( axisId ) )
+ return 0;
+
+ return d_axisData[axisId]->stepSize;
+}
+
+/*!
+ \brief Return the current interval of the specified axis
+
+ This is only a convenience function for axisScaleDiv( axisId )->interval();
+
+ \param axisId axis index
+ \return Scale interval
+
+ \sa QwtScaleDiv, axisScaleDiv()
+*/
+QwtInterval QwtPlot::axisInterval( int axisId ) const
+{
+ if ( !axisValid( axisId ) )
+ return QwtInterval();
+
+ return d_axisData[axisId]->scaleDiv.interval();
+}
+
+/*!
+ \return the title of a specified axis
+ \param axisId axis index
+*/
+QwtText QwtPlot::axisTitle( int axisId ) const
+{
+ if ( axisValid( axisId ) )
+ return axisWidget( axisId )->title();
+ else
+ return QwtText();
+}
+
+/*!
+ \brief Enable or disable a specified axis
+
+ When an axis is disabled, this only means that it is not
+ visible on the screen. Curves, markers and can be attached
+ to disabled axes, and transformation of screen coordinates
+ into values works as normal.
+
+ Only xBottom and yLeft are enabled by default.
+ \param axisId axis index
+ \param tf \c true (enabled) or \c false (disabled)
+*/
+void QwtPlot::enableAxis( int axisId, bool tf )
+{
+ if ( axisValid( axisId ) && tf != d_axisData[axisId]->isEnabled )
+ {
+ d_axisData[axisId]->isEnabled = tf;
+ updateLayout();
+ }
+}
+
+/*!
+ Transform the x or y coordinate of a position in the
+ drawing region into a value.
+ \param axisId axis index
+ \param pos position
+ \warning The position can be an x or a y coordinate,
+ depending on the specified axis.
+*/
+double QwtPlot::invTransform( int axisId, int pos ) const
+{
+ if ( axisValid( axisId ) )
+ return( canvasMap( axisId ).invTransform( pos ) );
+ else
+ return 0.0;
+}
+
+
+/*!
+ \brief Transform a value into a coordinate in the plotting region
+ \param axisId axis index
+ \param value value
+ \return X or y coordinate in the plotting region corresponding
+ to the value.
+*/
+double QwtPlot::transform( int axisId, double value ) const
+{
+ if ( axisValid( axisId ) )
+ return( canvasMap( axisId ).transform( value ) );
+ else
+ return 0.0;
+}
+
+/*!
+ \brief Change the font of an axis
+ \param axisId axis index
+ \param f font
+ \warning This function changes the font of the tick labels,
+ not of the axis title.
+*/
+void QwtPlot::setAxisFont( int axisId, const QFont &f )
+{
+ if ( axisValid( axisId ) )
+ axisWidget( axisId )->setFont( f );
+}
+
+/*!
+ \brief Enable autoscaling for a specified axis
+
+ This member function is used to switch back to autoscaling mode
+ after a fixed scale has been set. Autoscaling is enabled by default.
+
+ \param axisId axis index
+ \param on On/Off
+ \sa setAxisScale(), setAxisScaleDiv(), updateAxes()
+
+ \note The autoscaling flag has no effect until updateAxes() is executed
+ ( called by replot() ).
+*/
+void QwtPlot::setAxisAutoScale( int axisId, bool on )
+{
+ if ( axisValid( axisId ) && ( d_axisData[axisId]->doAutoScale != on ) )
+ {
+ d_axisData[axisId]->doAutoScale = on;
+ autoRefresh();
+ }
+}
+
+/*!
+ \brief Disable autoscaling and specify a fixed scale for a selected axis.
+ \param axisId axis index
+ \param min
+ \param max minimum and maximum of the scale
+ \param stepSize Major step size. If <code>step == 0</code>, the step size is
+ calculated automatically using the maxMajor setting.
+ \sa setAxisMaxMajor(), setAxisAutoScale(), axisStepSize()
+*/
+void QwtPlot::setAxisScale( int axisId, double min, double max, double stepSize )
+{
+ if ( axisValid( axisId ) )
+ {
+ AxisData &d = *d_axisData[axisId];
+
+ d.doAutoScale = false;
+ d.scaleDiv.invalidate();
+
+ d.minValue = min;
+ d.maxValue = max;
+ d.stepSize = stepSize;
+
+ autoRefresh();
+ }
+}
+
+/*!
+ \brief Disable autoscaling and specify a fixed scale for a selected axis.
+ \param axisId axis index
+ \param scaleDiv Scale division
+ \sa setAxisScale(), setAxisAutoScale()
+*/
+void QwtPlot::setAxisScaleDiv( int axisId, const QwtScaleDiv &scaleDiv )
+{
+ if ( axisValid( axisId ) )
+ {
+ AxisData &d = *d_axisData[axisId];
+
+ d.doAutoScale = false;
+ d.scaleDiv = scaleDiv;
+
+ autoRefresh();
+ }
+}
+
+/*!
+ \brief Set a scale draw
+ \param axisId axis index
+ \param scaleDraw object responsible for drawing scales.
+
+ By passing scaleDraw it is possible to extend QwtScaleDraw
+ functionality and let it take place in QwtPlot. Please note
+ that scaleDraw has to be created with new and will be deleted
+ by the corresponding QwtScale member ( like a child object ).
+
+ \sa QwtScaleDraw, QwtScaleWidget
+ \warning The attributes of scaleDraw will be overwritten by those of the
+ previous QwtScaleDraw.
+*/
+
+void QwtPlot::setAxisScaleDraw( int axisId, QwtScaleDraw *scaleDraw )
+{
+ if ( axisValid( axisId ) )
+ {
+ axisWidget( axisId )->setScaleDraw( scaleDraw );
+ autoRefresh();
+ }
+}
+
+/*!
+ Change the alignment of the tick labels
+ \param axisId axis index
+ \param alignment Or'd Qt::AlignmentFlags see <qnamespace.h>
+ \sa QwtScaleDraw::setLabelAlignment()
+*/
+void QwtPlot::setAxisLabelAlignment( int axisId, Qt::Alignment alignment )
+{
+ if ( axisValid( axisId ) )
+ axisWidget( axisId )->setLabelAlignment( alignment );
+}
+
+/*!
+ Rotate all tick labels
+ \param axisId axis index
+ \param rotation Angle in degrees. When changing the label rotation,
+ the label alignment might be adjusted too.
+ \sa QwtScaleDraw::setLabelRotation(), setAxisLabelAlignment()
+*/
+void QwtPlot::setAxisLabelRotation( int axisId, double rotation )
+{
+ if ( axisValid( axisId ) )
+ axisWidget( axisId )->setLabelRotation( rotation );
+}
+
+/*!
+ Set the maximum number of minor scale intervals for a specified axis
+
+ \param axisId axis index
+ \param maxMinor maximum number of minor steps
+ \sa axisMaxMinor()
+*/
+void QwtPlot::setAxisMaxMinor( int axisId, int maxMinor )
+{
+ if ( axisValid( axisId ) )
+ {
+ maxMinor = qBound( 0, maxMinor, 100 );
+
+ AxisData &d = *d_axisData[axisId];
+ if ( maxMinor != d.maxMinor )
+ {
+ d.maxMinor = maxMinor;
+ d.scaleDiv.invalidate();
+ autoRefresh();
+ }
+ }
+}
+
+/*!
+ Set the maximum number of major scale intervals for a specified axis
+
+ \param axisId axis index
+ \param maxMajor maximum number of major steps
+ \sa axisMaxMajor()
+*/
+void QwtPlot::setAxisMaxMajor( int axisId, int maxMajor )
+{
+ if ( axisValid( axisId ) )
+ {
+ maxMajor = qBound( 1, maxMajor, 10000 );
+
+ AxisData &d = *d_axisData[axisId];
+ if ( maxMajor != d.maxMajor )
+ {
+ d.maxMajor = maxMajor;
+ d.scaleDiv.invalidate();
+ autoRefresh();
+ }
+ }
+}
+
+/*!
+ \brief Change the title of a specified axis
+ \param axisId axis index
+ \param title axis title
+*/
+void QwtPlot::setAxisTitle( int axisId, const QString &title )
+{
+ if ( axisValid( axisId ) )
+ axisWidget( axisId )->setTitle( title );
+}
+
+/*!
+ \brief Change the title of a specified axis
+ \param axisId axis index
+ \param title axis title
+*/
+void QwtPlot::setAxisTitle( int axisId, const QwtText &title )
+{
+ if ( axisValid( axisId ) )
+ axisWidget( axisId )->setTitle( title );
+}
+
+//! Rebuild the scales
+void QwtPlot::updateAxes()
+{
+ // Find bounding interval of the item data
+ // for all axes, where autoscaling is enabled
+
+ QwtInterval intv[axisCnt];
+
+ const QwtPlotItemList& itmList = itemList();
+
+ QwtPlotItemIterator it;
+ for ( it = itmList.begin(); it != itmList.end(); ++it )
+ {
+ const QwtPlotItem *item = *it;
+
+ if ( !item->testItemAttribute( QwtPlotItem::AutoScale ) )
+ continue;
+
+ if ( !item->isVisible() )
+ continue;
+
+ if ( axisAutoScale( item->xAxis() ) || axisAutoScale( item->yAxis() ) )
+ {
+ const QRectF rect = item->boundingRect();
+ intv[item->xAxis()] |= QwtInterval( rect.left(), rect.right() );
+ intv[item->yAxis()] |= QwtInterval( rect.top(), rect.bottom() );
+ }
+ }
+
+ // Adjust scales
+
+ for ( int axisId = 0; axisId < axisCnt; axisId++ )
+ {
+ AxisData &d = *d_axisData[axisId];
+
+ double minValue = d.minValue;
+ double maxValue = d.maxValue;
+ double stepSize = d.stepSize;
+
+ if ( d.doAutoScale && intv[axisId].isValid() )
+ {
+ d.scaleDiv.invalidate();
+
+ minValue = intv[axisId].minValue();
+ maxValue = intv[axisId].maxValue();
+
+ d.scaleEngine->autoScale( d.maxMajor,
+ minValue, maxValue, stepSize );
+ }
+ if ( !d.scaleDiv.isValid() )
+ {
+ d.scaleDiv = d.scaleEngine->divideScale(
+ minValue, maxValue,
+ d.maxMajor, d.maxMinor, stepSize );
+ }
+
+ QwtScaleWidget *scaleWidget = axisWidget( axisId );
+ scaleWidget->setScaleDiv(
+ d.scaleEngine->transformation(), d.scaleDiv );
+
+ int startDist, endDist;
+ scaleWidget->getBorderDistHint( startDist, endDist );
+ scaleWidget->setBorderDist( startDist, endDist );
+ }
+
+ for ( it = itmList.begin(); it != itmList.end(); ++it )
+ {
+ QwtPlotItem *item = *it;
+ item->updateScaleDiv( *axisScaleDiv( item->xAxis() ),
+ *axisScaleDiv( item->yAxis() ) );
+ }
+}
+
diff --git a/src/libpcp_qwt/src/qwt_plot_canvas.cpp b/src/libpcp_qwt/src/qwt_plot_canvas.cpp
new file mode 100644
index 0000000..69669d2
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_canvas.cpp
@@ -0,0 +1,1095 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_canvas.h"
+#include "qwt_painter.h"
+#include "qwt_null_paintdevice.h"
+#include "qwt_math.h"
+#include "qwt_plot.h"
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qpaintengine.h>
+#include <qevent.h>
+#include <qbitmap.h>
+#ifdef Q_WS_X11
+#include <qx11info_x11.h>
+#endif
+
+class QwtStyleSheetRecorder: public QwtNullPaintDevice
+{
+public:
+ QwtStyleSheetRecorder( const QSize &size ):
+ QwtNullPaintDevice( QPaintEngine::AllFeatures )
+ {
+ setSize( size );
+ }
+
+ virtual void updateState( const QPaintEngineState &state )
+ {
+ if ( state.state() & QPaintEngine::DirtyPen )
+ {
+ d_pen = state.pen();
+ }
+ if ( state.state() & QPaintEngine::DirtyBrush )
+ {
+ d_brush = state.brush();
+ }
+ if ( state.state() & QPaintEngine::DirtyBrushOrigin )
+ {
+ d_origin = state.brushOrigin();
+ }
+ }
+
+ virtual void drawRects(const QRectF *rects, int count )
+ {
+ for ( int i = 0; i < count; i++ )
+ border.rectList += rects[i];
+ }
+
+ virtual void drawPath( const QPainterPath &path )
+ {
+ const QRectF rect( QPointF( 0.0, 0.0 ) , size() );
+ if ( path.controlPointRect().contains( rect.center() ) )
+ {
+ setCornerRects( path );
+ alignCornerRects( rect );
+
+ background.path = path;
+ background.brush = d_brush;
+ background.origin = d_origin;
+ }
+ else
+ {
+ border.pathList += path;
+ }
+ }
+
+ void setCornerRects( const QPainterPath &path )
+ {
+ QPointF pos( 0.0, 0.0 );
+
+ for ( int i = 0; i < path.elementCount(); i++ )
+ {
+ QPainterPath::Element el = path.elementAt(i);
+ switch( el.type )
+ {
+ case QPainterPath::MoveToElement:
+ case QPainterPath::LineToElement:
+ {
+ pos.setX( el.x );
+ pos.setY( el.y );
+ break;
+ }
+ case QPainterPath::CurveToElement:
+ {
+ QRectF r( pos, QPointF( el.x, el.y ) );
+ clipRects += r.normalized();
+
+ pos.setX( el.x );
+ pos.setY( el.y );
+
+ break;
+ }
+ case QPainterPath::CurveToDataElement:
+ {
+ if ( clipRects.size() > 0 )
+ {
+ QRectF r = clipRects.last();
+ r.setCoords(
+ qMin( r.left(), el.x ),
+ qMin( r.top(), el.y ),
+ qMax( r.right(), el.x ),
+ qMax( r.bottom(), el.y )
+ );
+ clipRects.last() = r.normalized();
+ }
+ break;
+ }
+ }
+ }
+ }
+
+private:
+ void alignCornerRects( const QRectF &rect )
+ {
+ for ( int i = 0; i < clipRects.size(); i++ )
+ {
+ QRectF &r = clipRects[i];
+ if ( r.center().x() < rect.center().x() )
+ r.setLeft( rect.left() );
+ else
+ r.setRight( rect.right() );
+
+ if ( r.center().y() < rect.center().y() )
+ r.setTop( rect.top() );
+ else
+ r.setBottom( rect.bottom() );
+ }
+ }
+
+
+public:
+ QVector<QRectF> clipRects;
+
+ struct Border
+ {
+ QList<QPainterPath> pathList;
+ QList<QRectF> rectList;
+ QRegion clipRegion;
+ } border;
+
+ struct Background
+ {
+ QPainterPath path;
+ QBrush brush;
+ QPointF origin;
+ } background;
+
+private:
+ QPen d_pen;
+ QBrush d_brush;
+ QPointF d_origin;
+};
+
+static void qwtDrawBackground( QPainter *painter, QWidget *widget )
+{
+ const QBrush &brush =
+ widget->palette().brush( widget->backgroundRole() );
+
+ if ( brush.style() == Qt::TexturePattern )
+ {
+ QPixmap pm( widget->size() );
+#if QT_VERSION >= 0x050000
+ QwtPainter::fillPixmap( widget, pm );
+#else
+ pm.fill( widget, 0, 0 );
+#endif
+ painter->drawPixmap( 0, 0, pm );
+ }
+ else if ( brush.gradient() )
+ {
+ QVector<QRect> rects;
+
+ if ( brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode )
+ {
+ rects += widget->rect();
+ }
+ else
+ {
+ rects = painter->clipRegion().rects();
+ }
+
+#if 1
+ bool useRaster = false;
+
+ if ( painter->paintEngine()->type() == QPaintEngine::X11 )
+ {
+ // Qt 4.7.1: gradients on X11 are broken ( subrects +
+ // QGradient::StretchToDeviceMode ) and horrible slow.
+ // As workaround we have to use the raster paintengine.
+ // Even if the QImage -> QPixmap translation is slow
+ // it is three times faster, than using X11 directly
+
+ useRaster = true;
+ }
+#endif
+ if ( useRaster )
+ {
+ QImage::Format format = QImage::Format_RGB32;
+
+ const QGradientStops stops = brush.gradient()->stops();
+ for ( int i = 0; i < stops.size(); i++ )
+ {
+ if ( stops[i].second.alpha() != 255 )
+ {
+ // don't use Format_ARGB32_Premultiplied. It's
+ // recommended by the Qt docs, but QPainter::drawImage()
+ // is horrible slow on X11.
+
+ format = QImage::Format_ARGB32;
+ break;
+ }
+ }
+
+ QImage image( widget->size(), format );
+
+ QPainter p( &image );
+ p.setPen( Qt::NoPen );
+ p.setBrush( brush );
+
+ p.drawRects( rects );
+
+ p.end();
+
+ painter->drawImage( 0, 0, image );
+ }
+ else
+ {
+ painter->save();
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( brush );
+
+ painter->drawRects( rects );
+
+ painter->restore();
+ }
+ }
+ else
+ {
+ painter->save();
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( brush );
+
+ painter->drawRects( painter->clipRegion().rects() );
+
+ painter->restore();
+ }
+}
+
+static inline void qwtRevertPath( QPainterPath &path )
+{
+ if ( path.elementCount() == 4 )
+ {
+ QPainterPath::Element el0 = path.elementAt(0);
+ QPainterPath::Element el3 = path.elementAt(3);
+
+ path.setElementPositionAt( 0, el3.x, el3.y );
+ path.setElementPositionAt( 3, el0.x, el0.y );
+ }
+}
+
+static QPainterPath qwtCombinePathList( const QRectF &rect,
+ const QList<QPainterPath> &pathList )
+{
+ if ( pathList.isEmpty() )
+ return QPainterPath();
+
+ QPainterPath ordered[8]; // starting top left
+
+ for ( int i = 0; i < pathList.size(); i++ )
+ {
+ int index = -1;
+ QPainterPath subPath = pathList[i];
+
+ const QRectF br = pathList[i].controlPointRect();
+ if ( br.center().x() < rect.center().x() )
+ {
+ if ( br.center().y() < rect.center().y() )
+ {
+ if ( qAbs( br.top() - rect.top() ) <
+ qAbs( br.left() - rect.left() ) )
+ {
+ index = 1;
+ }
+ else
+ {
+ index = 0;
+ }
+ }
+ else
+ {
+ if ( qAbs( br.bottom() - rect.bottom() ) <
+ qAbs( br.left() - rect.left() ) )
+ {
+ index = 6;
+ }
+ else
+ {
+ index = 7;
+ }
+ }
+
+ if ( subPath.currentPosition().y() > br.center().y() )
+ qwtRevertPath( subPath );
+ }
+ else
+ {
+ if ( br.center().y() < rect.center().y() )
+ {
+ if ( qAbs( br.top() - rect.top() ) <
+ qAbs( br.right() - rect.right() ) )
+ {
+ index = 2;
+ }
+ else
+ {
+ index = 3;
+ }
+ }
+ else
+ {
+ if ( qAbs( br.bottom() - rect.bottom() ) <
+ qAbs( br.right() - rect.right() ) )
+ {
+ index = 5;
+ }
+ else
+ {
+ index = 4;
+ }
+ }
+ if ( subPath.currentPosition().y() < br.center().y() )
+ qwtRevertPath( subPath );
+ }
+ ordered[index] = subPath;
+ }
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ if ( ordered[ 2 * i].isEmpty() != ordered[2 * i + 1].isEmpty() )
+ {
+ // we don't accept incomplete rounded borders
+ return QPainterPath();
+ }
+ }
+
+
+ const QPolygonF corners( rect );
+
+ QPainterPath path;
+ //path.moveTo( rect.topLeft() );
+
+ for ( int i = 0; i < 4; i++ )
+ {
+ if ( ordered[2 * i].isEmpty() )
+ {
+ path.lineTo( corners[i] );
+ }
+ else
+ {
+ path.connectPath( ordered[2 * i] );
+ path.connectPath( ordered[2 * i + 1] );
+ }
+ }
+
+ path.closeSubpath();
+
+#if 0
+ return path.simplified();
+#else
+ return path;
+#endif
+}
+
+static inline void qwtDrawStyledBackground(
+ QWidget *w, QPainter *painter )
+{
+ QStyleOption opt;
+ opt.initFrom(w);
+ w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w);
+}
+
+static QWidget *qwtBackgroundWidget( QWidget *w )
+{
+ if ( w->parentWidget() == NULL )
+ return w;
+
+ if ( w->autoFillBackground() )
+ {
+ const QBrush brush = w->palette().brush( w->backgroundRole() );
+ if ( brush.color().alpha() > 0 )
+ return w;
+ }
+
+ if ( w->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ QImage image( 1, 1, QImage::Format_ARGB32 );
+ image.fill( Qt::transparent );
+
+ QPainter painter( &image );
+ painter.translate( -w->rect().center() );
+ qwtDrawStyledBackground( w, &painter );
+ painter.end();
+
+ if ( qAlpha( image.pixel( 0, 0 ) ) != 0 )
+ return w;
+ }
+
+ return qwtBackgroundWidget( w->parentWidget() );
+}
+
+static void qwtFillBackground( QPainter *painter,
+ QWidget *widget, const QVector<QRectF> &fillRects )
+{
+ if ( fillRects.isEmpty() )
+ return;
+
+ QRegion clipRegion;
+ if ( painter->hasClipping() )
+ clipRegion = painter->transform().map( painter->clipRegion() );
+ else
+ clipRegion = widget->contentsRect();
+
+ // Try to find out which widget fills
+ // the unfilled areas of the styled background
+
+ QWidget *bgWidget = qwtBackgroundWidget( widget->parentWidget() );
+
+ for ( int i = 0; i < fillRects.size(); i++ )
+ {
+ const QRect rect = fillRects[i].toAlignedRect();
+ if ( clipRegion.intersects( rect ) )
+ {
+ const QPoint topLeft = widget->mapTo( bgWidget, rect.topLeft() );
+
+ QPixmap pm( rect.size() );
+#if QT_VERSION >= 0x050000
+ QwtPainter::fillPixmap( bgWidget, pm, topLeft );
+#else
+ pm.fill( bgWidget, topLeft );
+#endif
+ painter->drawPixmap( rect, pm );
+ }
+ }
+}
+
+static void qwtFillBackground( QPainter *painter, QwtPlotCanvas *canvas )
+{
+ QVector<QRectF> rects;
+
+ if ( canvas->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ QwtStyleSheetRecorder recorder( canvas->size() );
+
+ QPainter p( &recorder );
+ qwtDrawStyledBackground( canvas, &p );
+ p.end();
+
+ if ( recorder.background.brush.isOpaque() )
+ rects = recorder.clipRects;
+ else
+ rects += canvas->rect();
+ }
+ else
+ {
+ const QRectF r = canvas->rect();
+ const double radius = canvas->borderRadius();
+ if ( radius > 0.0 )
+ {
+ QSizeF sz( radius, radius );
+
+ rects += QRectF( r.topLeft(), sz );
+ rects += QRectF( r.topRight() - QPointF( radius, 0 ), sz );
+ rects += QRectF( r.bottomRight() - QPointF( radius, radius ), sz );
+ rects += QRectF( r.bottomLeft() - QPointF( 0, radius ), sz );
+ }
+ }
+
+ qwtFillBackground( painter, canvas, rects);
+}
+
+
+class QwtPlotCanvas::PrivateData
+{
+public:
+ PrivateData():
+ focusIndicator( NoFocusIndicator ),
+ borderRadius( 0 ),
+ paintAttributes( 0 ),
+ backingStore( NULL )
+ {
+ styleSheet.hasBorder = false;
+ }
+
+ ~PrivateData()
+ {
+ delete backingStore;
+ }
+
+ FocusIndicator focusIndicator;
+ double borderRadius;
+ QwtPlotCanvas::PaintAttributes paintAttributes;
+ QPixmap *backingStore;
+
+ struct StyleSheet
+ {
+ bool hasBorder;
+ QPainterPath borderPath;
+ QVector<QRectF> cornerRects;
+
+ struct StyleSheetBackground
+ {
+ QBrush brush;
+ QPointF origin;
+ } background;
+
+ } styleSheet;
+
+};
+
+//! Sets a cross cursor, enables QwtPlotCanvas::BackingStore
+
+QwtPlotCanvas::QwtPlotCanvas( QwtPlot *plot ):
+ QFrame( plot )
+{
+ d_data = new PrivateData;
+
+#ifndef QT_NO_CURSOR
+ setCursor( Qt::CrossCursor );
+#endif
+
+ setAutoFillBackground( true );
+ setPaintAttribute( QwtPlotCanvas::BackingStore, true );
+ setPaintAttribute( QwtPlotCanvas::Opaque, true );
+ setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true );
+}
+
+//! Destructor
+QwtPlotCanvas::~QwtPlotCanvas()
+{
+ delete d_data;
+}
+
+//! Return parent plot widget
+QwtPlot *QwtPlotCanvas::plot()
+{
+ return qobject_cast<QwtPlot *>( parent() );
+}
+
+//! Return parent plot widget
+const QwtPlot *QwtPlotCanvas::plot() const
+{
+ return qobject_cast<const QwtPlot *>( parent() );
+}
+
+/*!
+ \brief Changing the paint attributes
+
+ \param attribute Paint attribute
+ \param on On/Off
+
+ \sa testPaintAttribute(), backingStore()
+*/
+void QwtPlotCanvas::setPaintAttribute( PaintAttribute attribute, bool on )
+{
+ if ( bool( d_data->paintAttributes & attribute ) == on )
+ return;
+
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+
+ switch ( attribute )
+ {
+ case BackingStore:
+ {
+ if ( on )
+ {
+ if ( d_data->backingStore == NULL )
+ d_data->backingStore = new QPixmap();
+
+ if ( isVisible() )
+ {
+#if QT_VERSION >= 0x050000
+ *d_data->backingStore = grab( rect() );
+#else
+ *d_data->backingStore =
+ QPixmap::grabWidget( this, rect() );
+#endif
+ }
+ }
+ else
+ {
+ delete d_data->backingStore;
+ d_data->backingStore = NULL;
+ }
+ break;
+ }
+ case Opaque:
+ {
+ if ( on )
+ setAttribute( Qt::WA_OpaquePaintEvent, true );
+
+ break;
+ }
+ case HackStyledBackground:
+ case ImmediatePaint:
+ {
+ break;
+ }
+ }
+}
+
+/*!
+ Test wether a paint attribute is enabled
+
+ \param attribute Paint attribute
+ \return true if the attribute is enabled
+ \sa setPaintAttribute()
+*/
+bool QwtPlotCanvas::testPaintAttribute( PaintAttribute attribute ) const
+{
+ return d_data->paintAttributes & attribute;
+}
+
+//! \return Backing store, might be null
+const QPixmap *QwtPlotCanvas::backingStore() const
+{
+ return d_data->backingStore;
+}
+
+//! Invalidate the internal backing store
+void QwtPlotCanvas::invalidateBackingStore()
+{
+ if ( d_data->backingStore )
+ *d_data->backingStore = QPixmap();
+}
+
+/*!
+ Set the focus indicator
+
+ \sa FocusIndicator, focusIndicator()
+*/
+void QwtPlotCanvas::setFocusIndicator( FocusIndicator focusIndicator )
+{
+ d_data->focusIndicator = focusIndicator;
+}
+
+/*!
+ \return Focus indicator
+
+ \sa FocusIndicator, setFocusIndicator()
+*/
+QwtPlotCanvas::FocusIndicator QwtPlotCanvas::focusIndicator() const
+{
+ return d_data->focusIndicator;
+}
+
+/*!
+ Set the radius for the corners of the border frame
+
+ \param radius Radius of a rounded corner
+ \sa borderRadius()
+*/
+void QwtPlotCanvas::setBorderRadius( double radius )
+{
+ d_data->borderRadius = qMax( 0.0, radius );
+}
+
+/*!
+ \return Radius for the corners of the border frame
+ \sa setBorderRadius()
+*/
+double QwtPlotCanvas::borderRadius() const
+{
+ return d_data->borderRadius;
+}
+
+/*!
+ Qt event handler for QEvent::PolishRequest and QEvent::StyleChange
+ \param event Qt Event
+*/
+bool QwtPlotCanvas::event( QEvent *event )
+{
+ if ( event->type() == QEvent::PolishRequest )
+ {
+ if ( testPaintAttribute( QwtPlotCanvas::Opaque ) )
+ {
+ // Setting a style sheet changes the
+ // Qt::WA_OpaquePaintEvent attribute, but we insist
+ // on painting the background.
+
+ setAttribute( Qt::WA_OpaquePaintEvent, true );
+ }
+ }
+
+ if ( event->type() == QEvent::PolishRequest ||
+ event->type() == QEvent::StyleChange )
+ {
+ updateStyleSheetInfo();
+ }
+
+ return QFrame::event( event );
+}
+
+/*!
+ Paint event
+ \param event Paint event
+*/
+void QwtPlotCanvas::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) &&
+ d_data->backingStore != NULL )
+ {
+ QPixmap &bs = *d_data->backingStore;
+ if ( bs.size() != size() )
+ {
+ bs = QPixmap( size() );
+
+#ifdef Q_WS_X11
+ if ( bs.x11Info().screen() != x11Info().screen() )
+ bs.x11SetScreen( x11Info().screen() );
+#endif
+
+ if ( testAttribute(Qt::WA_StyledBackground) )
+ {
+ QPainter p( &bs );
+ qwtFillBackground( &p, this );
+ drawCanvas( &p, true );
+ }
+ else
+ {
+ QPainter p;
+ if ( d_data->borderRadius <= 0.0 )
+ {
+#if QT_VERSION >= 0x050000
+ QwtPainter::fillPixmap( this, bs );
+#else
+ bs.fill( this, 0, 0 );
+#endif
+ p.begin( &bs );
+ drawCanvas( &p, false );
+ }
+ else
+ {
+ p.begin( &bs );
+ qwtFillBackground( &p, this );
+ drawCanvas( &p, true );
+ }
+
+ if ( frameWidth() > 0 )
+ drawBorder( &p );
+ }
+ }
+
+ painter.drawPixmap( 0, 0, *d_data->backingStore );
+ }
+ else
+ {
+ if ( testAttribute(Qt::WA_StyledBackground ) )
+ {
+ if ( testAttribute( Qt::WA_OpaquePaintEvent ) )
+ {
+ qwtFillBackground( &painter, this );
+ drawCanvas( &painter, true );
+ }
+ else
+ {
+ drawCanvas( &painter, false );
+ }
+ }
+ else
+ {
+ if ( testAttribute( Qt::WA_OpaquePaintEvent ) )
+ {
+ if ( autoFillBackground() )
+ qwtDrawBackground( &painter, this );
+ }
+
+ drawCanvas( &painter, false );
+
+ if ( frameWidth() > 0 )
+ drawBorder( &painter );
+ }
+ }
+
+ if ( hasFocus() && focusIndicator() == CanvasFocusIndicator )
+ drawFocusIndicator( &painter );
+}
+
+void QwtPlotCanvas::drawCanvas( QPainter *painter, bool withBackground )
+{
+ bool hackStyledBackground = false;
+
+ if ( withBackground && testAttribute( Qt::WA_StyledBackground )
+ && testPaintAttribute( HackStyledBackground ) )
+ {
+ // Antialiasing rounded borders is done by
+ // inserting pixels with colors between the
+ // border color and the color on the canvas,
+ // When the border is painted before the plot items
+ // these colors are interpolated for the canvas
+ // and the plot items need to be clipped excluding
+ // the anialiased pixels. In situations, where
+ // the plot items fill the area at the rounded
+ // borders this is noticeable.
+ // The only way to avoid these annoying "artefacts"
+ // is to paint the border on top of the plot items.
+
+ if ( d_data->styleSheet.hasBorder &&
+ !d_data->styleSheet.borderPath.isEmpty() )
+ {
+ // We have a border with at least one rounded corner
+ hackStyledBackground = true;
+ }
+ }
+
+ if ( withBackground )
+ {
+ painter->save();
+
+ if ( testAttribute( Qt::WA_StyledBackground ) )
+ {
+ if ( hackStyledBackground )
+ {
+ // paint background without border
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( d_data->styleSheet.background.brush );
+ painter->setBrushOrigin( d_data->styleSheet.background.origin );
+ painter->setClipPath( d_data->styleSheet.borderPath );
+ painter->drawRect( contentsRect() );
+ }
+ else
+ {
+ qwtDrawStyledBackground( this, painter );
+ }
+ }
+ else if ( autoFillBackground() )
+ {
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( palette().brush( backgroundRole() ) );
+
+ if ( d_data->borderRadius > 0.0 )
+ {
+ if ( frameWidth() > 0 )
+ {
+ painter->setClipPath( borderPath( rect() ) );
+ painter->drawRect( rect() );
+ }
+ else
+ {
+ painter->setRenderHint( QPainter::Antialiasing, true );
+ painter->drawPath( borderPath( rect() ) );
+ }
+ }
+ else
+ {
+ painter->drawRect( contentsRect() );
+ }
+ }
+
+ painter->restore();
+ }
+
+ painter->save();
+
+ if ( !d_data->styleSheet.borderPath.isEmpty() )
+ {
+ painter->setClipPath(
+ d_data->styleSheet.borderPath, Qt::IntersectClip );
+ }
+ else
+ {
+ if ( d_data->borderRadius > 0.0 )
+ painter->setClipPath( borderPath( rect() ), Qt::IntersectClip );
+ else
+ painter->setClipRect( contentsRect(), Qt::IntersectClip );
+ }
+
+ plot()->drawCanvas( painter );
+
+ painter->restore();
+
+ if ( withBackground && hackStyledBackground )
+ {
+ // Now paint the border on top
+ QStyleOptionFrame opt;
+ opt.initFrom(this);
+ style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, this);
+ }
+}
+
+/*!
+ Draw the border of the plot canvas
+
+ \param painter Painter
+ \sa setBorderRadius(), QFrame::drawFrame()
+*/
+void QwtPlotCanvas::drawBorder( QPainter *painter )
+{
+ if ( d_data->borderRadius > 0 )
+ {
+ if ( frameWidth() > 0 )
+ {
+ QwtPainter::drawRoundedFrame( painter, QRectF( rect() ),
+ d_data->borderRadius, d_data->borderRadius,
+ palette(), frameWidth(), frameStyle() );
+ }
+ }
+ else
+ {
+ drawFrame( painter );
+ }
+}
+
+/*!
+ Resize event
+ \param event Resize event
+*/
+void QwtPlotCanvas::resizeEvent( QResizeEvent *event )
+{
+ QFrame::resizeEvent( event );
+ updateStyleSheetInfo();
+}
+
+/*!
+ Draw the focus indication
+ \param painter Painter
+*/
+void QwtPlotCanvas::drawFocusIndicator( QPainter *painter )
+{
+ const int margin = 1;
+
+ QRect focusRect = contentsRect();
+ focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin,
+ focusRect.width() - 2 * margin, focusRect.height() - 2 * margin );
+
+ QwtPainter::drawFocusRect( painter, this, focusRect );
+}
+
+/*!
+ Invalidate the paint cache and repaint the canvas
+ \sa invalidatePaintCache()
+*/
+void QwtPlotCanvas::replot()
+{
+ invalidateBackingStore();
+
+ if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) )
+ repaint( contentsRect() );
+ else
+ update( contentsRect() );
+}
+
+//! Update the cached informations about the current style sheet
+void QwtPlotCanvas::updateStyleSheetInfo()
+{
+ if ( !testAttribute(Qt::WA_StyledBackground ) )
+ return;
+
+ QwtStyleSheetRecorder recorder( size() );
+
+ QPainter painter( &recorder );
+
+ QStyleOption opt;
+ opt.initFrom(this);
+ style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this);
+
+ painter.end();
+
+ d_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty();
+ d_data->styleSheet.cornerRects = recorder.clipRects;
+
+ if ( recorder.background.path.isEmpty() )
+ {
+ if ( !recorder.border.rectList.isEmpty() )
+ {
+ d_data->styleSheet.borderPath =
+ qwtCombinePathList( rect(), recorder.border.pathList );
+ }
+ }
+ else
+ {
+ d_data->styleSheet.borderPath = recorder.background.path;
+ d_data->styleSheet.background.brush = recorder.background.brush;
+ d_data->styleSheet.background.origin = recorder.background.origin;
+ }
+}
+
+/*!
+ Calculate the painter path for a styled or rounded border
+
+ When the canvas has no styled background or rounded borders
+ the painter path is empty.
+
+ \param rect Bounding rectangle of the canvas
+ \return Painter path, that can be used for clipping
+*/
+QPainterPath QwtPlotCanvas::borderPath( const QRect &rect ) const
+{
+ if ( testAttribute(Qt::WA_StyledBackground ) )
+ {
+ QwtStyleSheetRecorder recorder( rect.size() );
+
+ QPainter painter( &recorder );
+
+ QStyleOption opt;
+ opt.initFrom(this);
+ opt.rect = rect;
+ style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this);
+
+ painter.end();
+
+ if ( !recorder.background.path.isEmpty() )
+ return recorder.background.path;
+
+ if ( !recorder.border.rectList.isEmpty() )
+ return qwtCombinePathList( rect, recorder.border.pathList );
+ }
+ else if ( d_data->borderRadius > 0.0 )
+ {
+ double fw2 = frameWidth() * 0.5;
+ QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 );
+
+ QPainterPath path;
+ path.addRoundedRect( r, d_data->borderRadius, d_data->borderRadius );
+ return path;
+ }
+
+ return QPainterPath();
+}
+
+/*!
+ Calculate a mask, that can be used to clip away the border frame
+
+ \param size Size including the frame
+*/
+QBitmap QwtPlotCanvas::borderMask( const QSize &size ) const
+{
+ const QRect r( 0, 0, size.width(), size.height() );
+
+ const QPainterPath path = borderPath( r );
+ if ( path.isEmpty() )
+ return QBitmap();
+
+ QImage image( size, QImage::Format_ARGB32_Premultiplied );
+ image.fill( Qt::color0 );
+
+ QPainter painter( &image );
+ painter.setClipPath( path );
+ painter.fillRect( r, Qt::color1 );
+
+ // now erase the frame
+
+ painter.setCompositionMode( QPainter::CompositionMode_DestinationOut );
+
+ if ( testAttribute(Qt::WA_StyledBackground ) )
+ {
+ QStyleOptionFrame opt;
+ opt.initFrom(this);
+ opt.rect = r;
+ style()->drawPrimitive( QStyle::PE_Frame, &opt, &painter, this );
+ }
+ else
+ {
+ if ( d_data->borderRadius > 0 && frameWidth() > 0 )
+ {
+ painter.setPen( QPen( Qt::color1, frameWidth() ) );
+ painter.setBrush( Qt::NoBrush );
+ painter.setRenderHint( QPainter::Antialiasing, true );
+
+ painter.drawPath( path );
+ }
+ }
+
+ painter.end();
+
+ const QImage mask = image.createMaskFromColor(
+ QColor( Qt::color1 ).rgb(), Qt::MaskOutColor );
+
+ return QBitmap::fromImage( mask );
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_canvas.h b/src/libpcp_qwt/src/qwt_plot_canvas.h
new file mode 100644
index 0000000..2f4c163
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_canvas.h
@@ -0,0 +1,171 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_CANVAS_H
+#define QWT_PLOT_CANVAS_H
+
+#include "qwt_global.h"
+#include <qframe.h>
+#include <qpen.h>
+#include <qpainterpath.h>
+#include <qbitmap.h>
+
+class QwtPlot;
+class QPixmap;
+
+/*!
+ \brief Canvas of a QwtPlot.
+
+ Canvas is the widget where all plot items are displayed
+
+ \sa QwtPlot
+*/
+class QWT_EXPORT QwtPlotCanvas : public QFrame
+{
+ Q_OBJECT
+
+public:
+
+ /*!
+ \brief Paint attributes
+
+ The default setting enables BackingStore and Opaque.
+
+ \sa setPaintAttribute(), testPaintAttribute()
+ */
+ enum PaintAttribute
+ {
+ /*!
+ \brief Paint double buffered reusing the content
+ of the pixmap buffer when possible.
+
+ Using a backing store might improve the performance
+ significantly, when workin with widget overlays ( like rubberbands ).
+ Disabling the cache might improve the performance for
+ incremental paints (using QwtPlotDirectPainter ).
+
+ \sa backingStore(), invalidateBackingStore()
+ */
+ BackingStore = 1,
+
+ /*!
+ \brief Try to fill the complete contents rectangle
+ of the plot canvas
+
+ When using styled backgrounds Qt assumes, that the
+ canvas doesn't fill its area completely
+ ( f.e because of rounded borders ) and fills the area
+ below the canvas. When this is done with gradients it might
+ result in a serious performance bottleneck - depending on the size.
+
+ When the Opaque attribute is enabled the canvas tries to
+ identify the gaps with some heuristics and to fill those only.
+
+ \warning Will not work for semitransparent backgrounds
+ */
+ Opaque = 2,
+
+ /*!
+ \brief Try to improve painting of styled backgrounds
+
+ QwtPlotCanvas supports the box model attributes for
+ customizing the layout with style sheets. Unfortunately
+ the design of Qt style sheets has no concept how to
+ handle backgrounds with rounded corners - beside of padding.
+
+ When HackStyledBackground is enabled the plot canvas tries
+ to seperate the background from the background border
+ by reverse engeneering to paint the background before and
+ the border after the plot items. In this order the border
+ gets prefectly antialiased and you can avoid some pixel
+ artifacts in the corners.
+ */
+ HackStyledBackground = 4,
+
+ /*!
+ When ImmediatePaint is set replot() calls repaint()
+ instead of update().
+
+ \sa replot(), QWidget::repaint(), QWidget::update()
+ */
+ ImmediatePaint = 8
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ /*!
+ \brief Focus indicator
+ The default setting is NoFocusIndicator
+ \sa setFocusIndicator(), focusIndicator(), paintFocus()
+ */
+
+ enum FocusIndicator
+ {
+ //! Don't paint a focus indicator
+ NoFocusIndicator,
+
+ /*!
+ The focus is related to the complete canvas.
+ Paint the focus indicator using paintFocus()
+ */
+ CanvasFocusIndicator,
+
+ /*!
+ The focus is related to an item (curve, point, ...) on
+ the canvas. It is up to the application to display a
+ focus indication using f.e. highlighting.
+ */
+ ItemFocusIndicator
+ };
+
+ explicit QwtPlotCanvas( QwtPlot * );
+ virtual ~QwtPlotCanvas();
+
+ QwtPlot *plot();
+ const QwtPlot *plot() const;
+
+ void setFocusIndicator( FocusIndicator );
+ FocusIndicator focusIndicator() const;
+
+ void setBorderRadius( double );
+ double borderRadius() const;
+
+ QPainterPath borderPath( const QRect &rect ) const;
+ QBitmap borderMask( const QSize & ) const;
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ const QPixmap *backingStore() const;
+ void invalidateBackingStore();
+
+ void replot();
+
+ virtual bool event( QEvent * );
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+
+ virtual void drawFocusIndicator( QPainter * );
+ virtual void drawBorder( QPainter * );
+
+ void updateStyleSheetInfo();
+
+private:
+ void drawCanvas( QPainter *, bool withBackground );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCanvas::PaintAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_curve.cpp b/src/libpcp_qwt/src/qwt_plot_curve.cpp
new file mode 100644
index 0000000..f5cc449
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_curve.cpp
@@ -0,0 +1,1127 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_curve.h"
+#include "qwt_math.h"
+#include "qwt_clipper.h"
+#include "qwt_painter.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_scale_map.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_curve_fitter.h"
+#include "qwt_symbol.h"
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qalgorithms.h>
+#include <qmath.h>
+
+static int verifyRange( int size, int &i1, int &i2 )
+{
+ if ( size < 1 )
+ return 0;
+
+ i1 = qBound( 0, i1, size - 1 );
+ i2 = qBound( 0, i2, size - 1 );
+
+ if ( i1 > i2 )
+ qSwap( i1, i2 );
+
+ return ( i2 - i1 + 1 );
+}
+
+class QwtPlotCurve::PrivateData
+{
+public:
+ PrivateData():
+ style( QwtPlotCurve::Lines ),
+ baseline( 0.0 ),
+ symbol( NULL ),
+ attributes( 0 ),
+ paintAttributes( QwtPlotCurve::ClipPolygons ),
+ legendAttributes( 0 )
+ {
+ pen = QPen( Qt::black );
+ legendPen = Qt::NoPen;
+ curveFitter = new QwtSplineCurveFitter;
+ }
+
+ ~PrivateData()
+ {
+ delete symbol;
+ delete curveFitter;
+ }
+
+ QwtPlotCurve::CurveStyle style;
+ double baseline;
+
+ const QwtSymbol *symbol;
+ QwtCurveFitter *curveFitter;
+
+ QPen pen;
+ QPen legendPen;
+ QBrush brush;
+
+ QwtPlotCurve::CurveAttributes attributes;
+ QwtPlotCurve::PaintAttributes paintAttributes;
+
+ QwtPlotCurve::LegendAttributes legendAttributes;
+};
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotCurve::QwtPlotCurve( const QwtText &title ):
+ QwtPlotSeriesItem<QPointF>( title )
+{
+ init();
+}
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotCurve::QwtPlotCurve( const QString &title ):
+ QwtPlotSeriesItem<QPointF>( QwtText( title ) )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotCurve::~QwtPlotCurve()
+{
+ delete d_data;
+}
+
+//! Initialize internal members
+void QwtPlotCurve::init()
+{
+ setItemAttribute( QwtPlotItem::Legend );
+ setItemAttribute( QwtPlotItem::AutoScale );
+
+ d_data = new PrivateData;
+ d_series = new QwtPointSeriesData();
+
+ setZ( 20.0 );
+}
+
+//! \return QwtPlotItem::Rtti_PlotCurve
+int QwtPlotCurve::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotCurve;
+}
+
+/*!
+ Specify an attribute how to draw the curve
+
+ \param attribute Paint attribute
+ \param on On/Off
+ \sa testPaintAttribute()
+*/
+void QwtPlotCurve::setPaintAttribute( PaintAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+}
+
+/*!
+ \brief Return the current paint attributes
+ \sa setPaintAttribute()
+*/
+bool QwtPlotCurve::testPaintAttribute( PaintAttribute attribute ) const
+{
+ return ( d_data->paintAttributes & attribute );
+}
+
+/*!
+ Specify an attribute how to draw the legend identifier
+
+ \param attribute Attribute
+ \param on On/Off
+ /sa testLegendAttribute()
+*/
+void QwtPlotCurve::setLegendAttribute( LegendAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->legendAttributes |= attribute;
+ else
+ d_data->legendAttributes &= ~attribute;
+}
+
+/*!
+ \brief Return the current paint attributes
+ \sa setLegendAttribute()
+*/
+bool QwtPlotCurve::testLegendAttribute( LegendAttribute attribute ) const
+{
+ return ( d_data->legendAttributes & attribute );
+}
+
+/*!
+ Set the curve's drawing style
+
+ \param style Curve style
+ \sa style()
+*/
+void QwtPlotCurve::setStyle( CurveStyle style )
+{
+ if ( style != d_data->style )
+ {
+ d_data->style = style;
+ itemChanged();
+ }
+}
+
+/*!
+ Return the current style
+ \sa setStyle()
+*/
+QwtPlotCurve::CurveStyle QwtPlotCurve::style() const
+{
+ return d_data->style;
+}
+
+/*!
+ Assign a symbol
+
+ The curve will take the ownership of the symbol, hence the previously
+ set symbol will be delete by setting a new one. If \p symbol is
+ \c NULL no symbol will be drawn.
+
+ \param symbol Symbol
+ \sa symbol()
+*/
+void QwtPlotCurve::setSymbol( const QwtSymbol *symbol )
+{
+ if ( symbol != d_data->symbol )
+ {
+ delete d_data->symbol;
+ d_data->symbol = symbol;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Current symbol or NULL, when no symbol has been assigned
+ \sa setSymbol()
+*/
+const QwtSymbol *QwtPlotCurve::symbol() const
+{
+ return d_data->symbol;
+}
+
+/*!
+ Assign a pen
+
+ \param pen New pen
+ \sa pen(), brush()
+*/
+void QwtPlotCurve::setPen( const QPen &pen )
+{
+ if ( pen != d_data->pen )
+ {
+ d_data->pen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Pen used to draw the lines
+ \sa setPen(), brush()
+*/
+const QPen& QwtPlotCurve::pen() const
+{
+ return d_data->pen;
+}
+
+/*!
+ \brief Assign a brush.
+
+ In case of brush.style() != QBrush::NoBrush
+ and style() != QwtPlotCurve::Sticks
+ the area between the curve and the baseline will be filled.
+
+ In case !brush.color().isValid() the area will be filled by
+ pen.color(). The fill algorithm simply connects the first and the
+ last curve point to the baseline. So the curve data has to be sorted
+ (ascending or descending).
+
+ \param brush New brush
+ \sa brush(), setBaseline(), baseline()
+*/
+void QwtPlotCurve::setBrush( const QBrush &brush )
+{
+ if ( brush != d_data->brush )
+ {
+ d_data->brush = brush;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Brush used to fill the area between lines and the baseline
+ \sa setBrush(), setBaseline(), baseline()
+*/
+const QBrush& QwtPlotCurve::brush() const
+{
+ return d_data->brush;
+}
+
+/*!
+ Draw an interval of the curve
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first point to be painted
+ \param to Index of the last point to be painted. If to < 0 the
+ curve will be painted to its last point.
+
+ \sa drawCurve(), drawSymbols(),
+*/
+void QwtPlotCurve::drawSeries( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ if ( !painter || dataSize() <= 0 )
+ return;
+
+ if ( to < 0 )
+ to = dataSize() - 1;
+
+ if ( verifyRange( dataSize(), from, to ) > 0 )
+ {
+ painter->save();
+ painter->setPen( d_data->pen );
+
+ /*
+ Qt 4.0.0 is slow when drawing lines, but it's even
+ slower when the painter has a brush. So we don't
+ set the brush before we really need it.
+ */
+
+ drawCurve( painter, d_data->style, xMap, yMap, canvasRect, from, to );
+ painter->restore();
+
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtSymbol::NoSymbol ) )
+ {
+ painter->save();
+ drawSymbols( painter, *d_data->symbol,
+ xMap, yMap, canvasRect, from, to );
+ painter->restore();
+ }
+ }
+}
+
+/*!
+ \brief Draw the line part (without symbols) of a curve interval.
+ \param painter Painter
+ \param style curve style, see QwtPlotCurve::CurveStyle
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from index of the first point to be painted
+ \param to index of the last point to be painted
+ \sa draw(), drawDots(), drawLines(), drawSteps(), drawSticks()
+*/
+void QwtPlotCurve::drawCurve( QPainter *painter, int style,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ switch ( style )
+ {
+ case Lines:
+ if ( testCurveAttribute( Fitted ) )
+ {
+ // we always need the complete
+ // curve for fitting
+ from = 0;
+ to = dataSize() - 1;
+ }
+ drawLines( painter, xMap, yMap, canvasRect, from, to );
+ break;
+ case Sticks:
+ drawSticks( painter, xMap, yMap, canvasRect, from, to );
+ break;
+ case Steps:
+ drawSteps( painter, xMap, yMap, canvasRect, from, to );
+ break;
+ case Dots:
+ drawDots( painter, xMap, yMap, canvasRect, from, to );
+ break;
+ case NoCurve:
+ default:
+ break;
+ }
+}
+
+/*!
+ \brief Draw lines
+
+ If the CurveAttribute Fitted is enabled a QwtCurveFitter tries
+ to interpolate/smooth the curve, before it is painted.
+
+ \param painter Painter
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from index of the first point to be painted
+ \param to index of the last point to be painted
+
+ \sa setCurveAttribute(), setCurveFitter(), draw(),
+ drawLines(), drawDots(), drawSteps(), drawSticks()
+*/
+void QwtPlotCurve::drawLines( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ int size = to - from + 1;
+ if ( size <= 0 )
+ return;
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ QPolygonF polyline( size );
+
+ QPointF *points = polyline.data();
+ for ( int i = from; i <= to; i++ )
+ {
+ const QPointF sample = d_series->sample( i );
+
+ double x = xMap.transform( sample.x() );
+ double y = yMap.transform( sample.y() );
+ if ( doAlign )
+ {
+ x = qRound( x );
+ y = qRound( y );
+ }
+
+ points[i - from].rx() = x;
+ points[i - from].ry() = y;
+ }
+
+ if ( ( d_data->attributes & Fitted ) && d_data->curveFitter )
+ polyline = d_data->curveFitter->fitCurve( polyline );
+
+ if ( d_data->paintAttributes & ClipPolygons )
+ {
+ qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF());
+ const QPolygonF clipped = QwtClipper::clipPolygonF(
+ canvasRect.adjusted(-pw, -pw, pw, pw), polyline, false );
+
+ QwtPainter::drawPolyline( painter, clipped );
+ }
+ else
+ {
+ QwtPainter::drawPolyline( painter, polyline );
+ }
+
+ if ( d_data->brush.style() != Qt::NoBrush )
+ fillCurve( painter, xMap, yMap, canvasRect, polyline );
+}
+
+/*!
+ Draw sticks
+
+ \param painter Painter
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from index of the first point to be painted
+ \param to index of the last point to be painted
+
+ \sa draw(), drawCurve(), drawDots(), drawLines(), drawSteps()
+*/
+void QwtPlotCurve::drawSticks( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &, int from, int to ) const
+{
+ painter->save();
+ painter->setRenderHint( QPainter::Antialiasing, false );
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ double x0 = xMap.transform( d_data->baseline );
+ double y0 = yMap.transform( d_data->baseline );
+ if ( doAlign )
+ {
+ x0 = qRound( x0 );
+ y0 = qRound( y0 );
+ }
+
+ const Qt::Orientation o = orientation();
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QPointF sample = d_series->sample( i );
+ double xi = xMap.transform( sample.x() );
+ double yi = yMap.transform( sample.y() );
+ if ( doAlign )
+ {
+ xi = qRound( xi );
+ yi = qRound( yi );
+ }
+
+ if ( o == Qt::Horizontal )
+ QwtPainter::drawLine( painter, x0, yi, xi, yi );
+ else
+ QwtPainter::drawLine( painter, xi, y0, xi, yi );
+ }
+
+ painter->restore();
+}
+
+/*!
+ Draw dots
+
+ \param painter Painter
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from index of the first point to be painted
+ \param to index of the last point to be painted
+
+ \sa draw(), drawCurve(), drawSticks(), drawLines(), drawSteps()
+*/
+void QwtPlotCurve::drawDots( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ const bool doFill = d_data->brush.style() != Qt::NoBrush;
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ QPolygonF polyline;
+ if ( doFill )
+ polyline.resize( to - from + 1 );
+
+ QPointF *points = polyline.data();
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QPointF sample = d_series->sample( i );
+ double xi = xMap.transform( sample.x() );
+ double yi = yMap.transform( sample.y() );
+ if ( doAlign )
+ {
+ xi = qRound( xi );
+ yi = qRound( yi );
+ }
+
+ QwtPainter::drawPoint( painter, QPointF( xi, yi ) );
+
+ if ( doFill )
+ {
+ points[i - from].rx() = xi;
+ points[i - from].ry() = yi;
+ }
+ }
+
+ if ( doFill )
+ fillCurve( painter, xMap, yMap, canvasRect, polyline );
+}
+
+/*!
+ Draw step function
+
+ The direction of the steps depends on Inverted attribute.
+
+ \param painter Painter
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from index of the first point to be painted
+ \param to index of the last point to be painted
+
+ \sa CurveAttribute, setCurveAttribute(),
+ draw(), drawCurve(), drawDots(), drawLines(), drawSticks()
+*/
+void QwtPlotCurve::drawSteps( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ QPolygonF polygon( 2 * ( to - from ) + 1 );
+ QPointF *points = polygon.data();
+
+ bool inverted = orientation() == Qt::Vertical;
+ if ( d_data->attributes & Inverted )
+ inverted = !inverted;
+
+ int i, ip;
+ for ( i = from, ip = 0; i <= to; i++, ip += 2 )
+ {
+ const QPointF sample = d_series->sample( i );
+ double xi = xMap.transform( sample.x() );
+ double yi = yMap.transform( sample.y() );
+ if ( doAlign )
+ {
+ xi = qRound( xi );
+ yi = qRound( yi );
+ }
+
+ if ( ip > 0 )
+ {
+ const QPointF &p0 = points[ip - 2];
+ QPointF &p = points[ip - 1];
+
+ if ( inverted )
+ {
+ p.rx() = p0.x();
+ p.ry() = yi;
+ }
+ else
+ {
+ p.rx() = xi;
+ p.ry() = p0.y();
+ }
+ }
+
+ points[ip].rx() = xi;
+ points[ip].ry() = yi;
+ }
+
+ if ( d_data->paintAttributes & ClipPolygons )
+ {
+ const QPolygonF clipped = QwtClipper::clipPolygonF(
+ canvasRect, polygon, false );
+
+ QwtPainter::drawPolyline( painter, clipped );
+ }
+ else
+ {
+ QwtPainter::drawPolyline( painter, polygon );
+ }
+
+ if ( d_data->brush.style() != Qt::NoBrush )
+ fillCurve( painter, xMap, yMap, canvasRect, polygon );
+}
+
+
+/*!
+ Specify an attribute for drawing the curve
+
+ \param attribute Curve attribute
+ \param on On/Off
+
+ /sa testCurveAttribute(), setCurveFitter()
+*/
+void QwtPlotCurve::setCurveAttribute( CurveAttribute attribute, bool on )
+{
+ if ( bool( d_data->attributes & attribute ) == on )
+ return;
+
+ if ( on )
+ d_data->attributes |= attribute;
+ else
+ d_data->attributes &= ~attribute;
+
+ itemChanged();
+}
+
+/*!
+ \return true, if attribute is enabled
+ \sa setCurveAttribute()
+*/
+bool QwtPlotCurve::testCurveAttribute( CurveAttribute attribute ) const
+{
+ return d_data->attributes & attribute;
+}
+
+/*!
+ Assign a curve fitter
+
+ The curve fitter "smooths" the curve points, when the Fitted
+ CurveAttribute is set. setCurveFitter(NULL) also disables curve fitting.
+
+ The curve fitter operates on the translated points ( = widget coordinates)
+ to be functional for logarithmic scales. Obviously this is less performant
+ for fitting algorithms, that reduce the number of points.
+
+ For situations, where curve fitting is used to improve the performance
+ of painting huge series of points it might be better to execute the fitter
+ on the curve points once and to cache the result in the QwtSeriesData object.
+
+ \param curveFitter() Curve fitter
+ \sa Fitted
+*/
+void QwtPlotCurve::setCurveFitter( QwtCurveFitter *curveFitter )
+{
+ delete d_data->curveFitter;
+ d_data->curveFitter = curveFitter;
+
+ itemChanged();
+}
+
+/*!
+ Get the curve fitter. If curve fitting is disabled NULL is returned.
+
+ \return Curve fitter
+ \sa setCurveFitter(), Fitted
+*/
+QwtCurveFitter *QwtPlotCurve::curveFitter() const
+{
+ return d_data->curveFitter;
+}
+
+/*!
+ Fill the area between the curve and the baseline with
+ the curve brush
+
+ \param painter Painter
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param polygon Polygon - will be modified !
+
+ \sa setBrush(), setBaseline(), setStyle()
+*/
+void QwtPlotCurve::fillCurve( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, QPolygonF &polygon ) const
+{
+ if ( d_data->brush.style() == Qt::NoBrush )
+ return;
+
+ closePolyline( painter, xMap, yMap, polygon );
+ if ( polygon.count() <= 2 ) // a line can't be filled
+ return;
+
+ QBrush brush = d_data->brush;
+ if ( !brush.color().isValid() )
+ brush.setColor( d_data->pen.color() );
+
+ if ( d_data->paintAttributes & ClipPolygons )
+ polygon = QwtClipper::clipPolygonF( canvasRect, polygon, true );
+
+ painter->save();
+
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( brush );
+
+ QwtPainter::drawPolygon( painter, polygon );
+
+ painter->restore();
+}
+
+/*!
+ \brief Complete a polygon to be a closed polygon including the
+ area between the original polygon and the baseline.
+
+ \param painter Painter
+ \param xMap X map
+ \param yMap Y map
+ \param polygon Polygon to be completed
+*/
+void QwtPlotCurve::closePolyline( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ QPolygonF &polygon ) const
+{
+ if ( polygon.size() < 2 )
+ return;
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ double baseline = d_data->baseline;
+
+ if ( orientation() == Qt::Vertical )
+ {
+ if ( yMap.transformation()->type() == QwtScaleTransformation::Log10 )
+ {
+ if ( baseline < QwtScaleMap::LogMin )
+ baseline = QwtScaleMap::LogMin;
+ }
+
+ double refY = yMap.transform( baseline );
+ if ( doAlign )
+ refY = qRound( refY );
+
+ polygon += QPointF( polygon.last().x(), refY );
+ polygon += QPointF( polygon.first().x(), refY );
+ }
+ else
+ {
+ if ( xMap.transformation()->type() == QwtScaleTransformation::Log10 )
+ {
+ if ( baseline < QwtScaleMap::LogMin )
+ baseline = QwtScaleMap::LogMin;
+ }
+
+ double refX = xMap.transform( baseline );
+ if ( doAlign )
+ refX = qRound( refX );
+
+ polygon += QPointF( refX, polygon.last().y() );
+ polygon += QPointF( refX, polygon.first().y() );
+ }
+}
+
+/*!
+ Draw symbols
+
+ \param painter Painter
+ \param symbol Curve symbol
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first point to be painted
+ \param to Index of the last point to be painted
+
+ \sa setSymbol(), drawSeries(), drawCurve()
+*/
+void QwtPlotCurve::drawSymbols( QPainter *painter, const QwtSymbol &symbol,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ bool usePixmap = testPaintAttribute( CacheSymbols );
+ if ( usePixmap && !doAlign )
+ {
+ // Don't use the pixmap, when the paint device
+ // could generate scalable vectors
+
+ usePixmap = false;
+ }
+
+ if ( usePixmap )
+ {
+ const QSize sz = ( 2 * symbol.boundingSize() + QSize( 1, 1 ) ) / 2;
+ const int w2 = sz.width() / 2;
+ const int h2 = sz.height() / 2;
+
+ QPixmap pm( sz );
+ pm.fill( Qt::transparent );
+
+ QPainter p( &pm );
+ p.setRenderHints( painter->renderHints() );
+ symbol.drawSymbol( &p, QPointF( w2, h2 ) );
+ p.end();
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QPointF sample = d_series->sample( i );
+
+ const double xi = xMap.transform( sample.x() );
+ const double yi = yMap.transform( sample.y() );
+
+ if ( canvasRect.contains( xi, yi ) )
+ {
+ const int left = qRound( xi ) - w2;
+ const int top = qRound( yi ) - h2;
+
+ painter->drawPixmap( left, top, pm );
+ }
+ }
+ }
+ else
+ {
+ const int chunkSize = 500;
+
+ for ( int i = from; i <= to; i += chunkSize )
+ {
+ const int n = qMin( chunkSize, to - i + 1 );
+
+ QPolygonF points;
+ for ( int j = 0; j < n; j++ )
+ {
+ const QPointF sample = d_series->sample( i + j );
+
+ const double xi = xMap.transform( sample.x() );
+ const double yi = yMap.transform( sample.y() );
+
+ if ( canvasRect.contains( xi, yi ) )
+ points += QPointF( xi, yi );
+ }
+
+ if ( points.size() > 0 )
+ symbol.drawSymbols( painter, points );
+ }
+ }
+}
+
+/*!
+ \brief Set the value of the baseline
+
+ The baseline is needed for filling the curve with a brush or
+ the Sticks drawing style.
+
+ The interpretation of the baseline depends on the orientation().
+ With Qt::Horizontal, the baseline is interpreted as a horizontal line
+ at y = baseline(), with Qt::Vertical, it is interpreted as a vertical
+ line at x = baseline().
+
+ The default value is 0.0.
+
+ \param value Value of the baseline
+ \sa baseline(), setBrush(), setStyle(), QwtPlotAbstractSeriesItem::orientation()
+*/
+void QwtPlotCurve::setBaseline( double value )
+{
+ if ( d_data->baseline != value )
+ {
+ d_data->baseline = value;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Value of the baseline
+ \sa setBaseline()
+*/
+double QwtPlotCurve::baseline() const
+{
+ return d_data->baseline;
+}
+
+/*!
+ Find the closest curve point for a specific position
+
+ \param pos Position, where to look for the closest curve point
+ \param dist If dist != NULL, closestPoint() returns the distance between
+ the position and the clostest curve point
+ \return Index of the closest curve point, or -1 if none can be found
+ ( f.e when the curve has no points )
+ \note closestPoint() implements a dumb algorithm, that iterates
+ over all points
+*/
+int QwtPlotCurve::closestPoint( const QPoint &pos, double *dist ) const
+{
+ if ( plot() == NULL || dataSize() <= 0 )
+ return -1;
+
+ const QwtScaleMap xMap = plot()->canvasMap( xAxis() );
+ const QwtScaleMap yMap = plot()->canvasMap( yAxis() );
+
+ int index = -1;
+ double dmin = 1.0e10;
+
+ for ( uint i = 0; i < dataSize(); i++ )
+ {
+ const QPointF sample = d_series->sample( i );
+
+ const double cx = xMap.transform( sample.x() ) - pos.x();
+ const double cy = yMap.transform( sample.y() ) - pos.y();
+
+ const double f = qwtSqr( cx ) + qwtSqr( cy );
+ if ( f < dmin )
+ {
+ index = i;
+ dmin = f;
+ }
+ }
+ if ( dist )
+ *dist = qSqrt( dmin );
+
+ return index;
+}
+
+/*!
+ \brief Update the widget that represents the item on the legend
+
+ \param legend Legend
+ \sa drawLegendIdentifier(), legendItem(), QwtPlotItem::Legend
+*/
+void QwtPlotCurve::updateLegend( QwtLegend *legend ) const
+{
+ if ( legend && testItemAttribute( QwtPlotItem::Legend )
+ && ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol )
+ && d_data->symbol
+ && d_data->symbol->style() != QwtSymbol::NoSymbol )
+ {
+ QWidget *lgdItem = legend->find( this );
+ if ( lgdItem == NULL )
+ {
+ lgdItem = legendItem();
+ if ( lgdItem )
+ legend->insert( this, lgdItem );
+ }
+
+ QwtLegendItem *l = qobject_cast<QwtLegendItem *>( lgdItem );
+ if ( l )
+ {
+ QSize sz = d_data->symbol->boundingSize();
+ sz += QSize( 2, 2 ); // margin
+
+ if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine )
+ {
+ // Avoid, that the line is completely covered by the symbol
+
+ int w = qCeil( 1.5 * sz.width() );
+ if ( w % 2 )
+ w++;
+
+ sz.setWidth( qMax( 8, w ) );
+ }
+
+ l->setIdentifierSize( sz );
+ }
+ }
+
+ QwtPlotItem::updateLegend( legend );
+}
+
+/*!
+ \brief Draw the identifier representing the curve on the legend
+
+ \param painter Painter
+ \param rect Bounding rectangle for the identifier
+
+ \sa setLegendAttribute(), QwtPlotItem::Legend
+*/
+void QwtPlotCurve::drawLegendIdentifier(
+ QPainter *painter, const QRectF &rect ) const
+{
+ if ( rect.isEmpty() )
+ return;
+
+ const double dim = qMin( rect.width(), rect.height() );
+
+ QSizeF size( dim, dim );
+
+ QRectF r( 0, 0, size.width(), size.height() );
+ r.moveCenter( rect.center() );
+
+ if ( d_data->legendAttributes == 0 )
+ {
+ QBrush brush = d_data->brush;
+ if ( brush.style() == Qt::NoBrush )
+ {
+ if ( style() != QwtPlotCurve::NoCurve )
+ brush = QBrush( pen().color() );
+ else if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtSymbol::NoSymbol ) )
+ {
+ brush = QBrush( d_data->symbol->pen().color() );
+ }
+ }
+ if ( brush.style() != Qt::NoBrush )
+ painter->fillRect( r, brush );
+ }
+ if ( d_data->legendAttributes & QwtPlotCurve::LegendShowBrush )
+ {
+ if ( d_data->brush.style() != Qt::NoBrush )
+ painter->fillRect( r, d_data->brush );
+ }
+ if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine )
+ {
+ if ( pen() != Qt::NoPen )
+ {
+ painter->setPen( pen() );
+ QwtPainter::drawLine( painter, rect.left(), rect.center().y(),
+ rect.right() - 1.0, rect.center().y() );
+ }
+ }
+ if ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol )
+ {
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtSymbol::NoSymbol ) )
+ {
+ QSize symbolSize = d_data->symbol->boundingSize();
+ symbolSize -= QSize( 2, 2 );
+
+ // scale the symbol size down if it doesn't fit into rect.
+
+ double xRatio = 1.0;
+ if ( rect.width() < symbolSize.width() )
+ xRatio = rect.width() / symbolSize.width();
+ double yRatio = 1.0;
+ if ( rect.height() < symbolSize.height() )
+ yRatio = rect.height() / symbolSize.height();
+
+ const double ratio = qMin( xRatio, yRatio );
+
+ painter->save();
+ painter->scale( ratio, ratio );
+
+ d_data->symbol->drawSymbol( painter, rect.center() / ratio );
+
+ painter->restore();
+ }
+ }
+}
+
+/*!
+ Initialize data with an array of points (explicitly shared).
+
+ \param samples Vector of points
+*/
+void QwtPlotCurve::setSamples( const QVector<QPointF> &samples )
+{
+ delete d_series;
+ d_series = new QwtPointSeriesData( samples );
+ itemChanged();
+}
+
+#ifndef QWT_NO_COMPAT
+
+/*!
+ \brief Initialize the data by pointing to memory blocks which
+ are not managed by QwtPlotCurve.
+
+ setRawSamples is provided for efficiency.
+ It is important to keep the pointers
+ during the lifetime of the underlying QwtCPointerData class.
+
+ \param xData pointer to x data
+ \param yData pointer to y data
+ \param size size of x and y
+
+ \sa QwtCPointerData
+*/
+void QwtPlotCurve::setRawSamples(
+ const double *xData, const double *yData, int size )
+{
+ delete d_series;
+ d_series = new QwtCPointerData( xData, yData, size );
+ itemChanged();
+}
+
+/*!
+ Set data by copying x- and y-values from specified memory blocks.
+ Contrary to setRawSamples(), this function makes a 'deep copy' of
+ the data.
+
+ \param xData pointer to x values
+ \param yData pointer to y values
+ \param size size of xData and yData
+
+ \sa QwtPointArrayData
+*/
+void QwtPlotCurve::setSamples(
+ const double *xData, const double *yData, int size )
+{
+ delete d_series;
+ d_series = new QwtPointArrayData( xData, yData, size );
+ itemChanged();
+}
+
+/*!
+ \brief Initialize data with x- and y-arrays (explicitly shared)
+
+ \param xData x data
+ \param yData y data
+
+ \sa QwtPointArrayData
+*/
+void QwtPlotCurve::setSamples( const QVector<double> &xData,
+ const QVector<double> &yData )
+{
+ delete d_series;
+ d_series = new QwtPointArrayData( xData, yData );
+ itemChanged();
+}
+#endif // !QWT_NO_COMPAT
+
diff --git a/src/libpcp_qwt/src/qwt_plot_curve.h b/src/libpcp_qwt/src/qwt_plot_curve.h
new file mode 100644
index 0000000..a957c4b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_curve.h
@@ -0,0 +1,319 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_CURVE_H
+#define QWT_PLOT_CURVE_H
+
+#include "qwt_global.h"
+#include "qwt_plot_seriesitem.h"
+#include "qwt_series_data.h"
+#include "qwt_text.h"
+#include <qpen.h>
+#include <qstring.h>
+
+class QPainter;
+class QPolygonF;
+class QwtScaleMap;
+class QwtSymbol;
+class QwtCurveFitter;
+
+/*!
+ \brief A plot item, that represents a series of points
+
+ A curve is the representation of a series of points in the x-y plane.
+ It supports different display styles, interpolation ( f.e. spline )
+ and symbols.
+
+ \par Usage
+ <dl><dt>a) Assign curve properties</dt>
+ <dd>When a curve is created, it is configured to draw black solid lines
+ with in QwtPlotCurve::Lines style and no symbols.
+ You can change this by calling
+ setPen(), setStyle() and setSymbol().</dd>
+ <dt>b) Connect/Assign data.</dt>
+ <dd>QwtPlotCurve gets its points using a QwtSeriesData object offering
+ a bridge to the real storage of the points ( like QAbstractItemModel ).
+ There are several convenience classes derived from QwtSeriesData, that also store
+ the points inside ( like QStandardItemModel ). QwtPlotCurve also offers
+ a couple of variations of setSamples(), that build QwtSeriesData objects from
+ arrays internally.</dd>
+ <dt>c) Attach the curve to a plot</dt>
+ <dd>See QwtPlotItem::attach()
+ </dd></dl>
+
+ \par Example:
+ see examples/bode
+
+ \sa QwtPointSeriesData, QwtSymbol, QwtScaleMap
+*/
+class QWT_EXPORT QwtPlotCurve: public QwtPlotSeriesItem<QPointF>
+{
+public:
+ /*!
+ Curve styles.
+ \sa setStyle(), style()
+ */
+ enum CurveStyle
+ {
+ /*!
+ Don't draw a curve. Note: This doesn't affect the symbols.
+ */
+ NoCurve = -1,
+
+ /*!
+ Connect the points with straight lines. The lines might
+ be interpolated depending on the 'Fitted' attribute. Curve
+ fitting can be configured using setCurveFitter().
+ */
+ Lines,
+
+ /*!
+ Draw vertical or horizontal sticks ( depending on the
+ orientation() ) from a baseline which is defined by setBaseline().
+ */
+ Sticks,
+
+ /*!
+ Connect the points with a step function. The step function
+ is drawn from the left to the right or vice versa,
+ depending on the QwtPlotCurve::Inverted attribute.
+ */
+ Steps,
+
+ /*!
+ Draw dots at the locations of the data points. Note:
+ This is different from a dotted line (see setPen()), and faster
+ as a curve in QwtPlotCurve::NoStyle style and a symbol
+ painting a point.
+ */
+ Dots,
+
+ /*!
+ Styles >= QwtPlotCurve::UserCurve are reserved for derived
+ classes of QwtPlotCurve that overload drawCurve() with
+ additional application specific curve types.
+ */
+ UserCurve = 100
+ };
+
+ /*!
+ Attribute for drawing the curve
+ \sa setCurveAttribute(), testCurveAttribute(), curveFitter()
+ */
+ enum CurveAttribute
+ {
+ /*!
+ For QwtPlotCurve::Steps only.
+ Draws a step function from the right to the left.
+ */
+ Inverted = 0x01,
+
+ /*!
+ Only in combination with QwtPlotCurve::Lines
+ A QwtCurveFitter tries to
+ interpolate/smooth the curve, before it is painted.
+
+ \note Curve fitting requires temorary memory
+ for calculating coefficients and additional points.
+ If painting in QwtPlotCurve::Fitted mode is slow it might be better
+ to fit the points, before they are passed to QwtPlotCurve.
+ */
+ Fitted = 0x02
+ };
+
+ //! Curve attributes
+ typedef QFlags<CurveAttribute> CurveAttributes;
+
+ /*!
+ Attributes how to represent the curve on the legend
+
+ \sa setLegendAttribute(), testLegendAttribute(),
+ drawLegendIdentifier()
+ */
+
+ enum LegendAttribute
+ {
+ /*!
+ QwtPlotCurve tries to find a color representing the curve
+ and paints a rectangle with it.
+ */
+ LegendNoAttribute = 0x00,
+
+ /*!
+ If the style() is not QwtPlotCurve::NoCurve a line
+ is painted with the curve pen().
+ */
+ LegendShowLine = 0x01,
+
+ /*!
+ If the curve has a valid symbol it is painted.
+ */
+ LegendShowSymbol = 0x02,
+
+ /*!
+ If the curve has a brush a rectangle filled with the
+ curve brush() is painted.
+ */
+ LegendShowBrush = 0x04
+ };
+
+ //! Legend attributes
+ typedef QFlags<LegendAttribute> LegendAttributes;
+
+ /*!
+ Attributes to modify the drawing algorithm.
+ The default setting enables ClipPolygons
+
+ \sa setPaintAttribute(), testPaintAttribute()
+ */
+ enum PaintAttribute
+ {
+ /*!
+ Clip polygons before painting them. In situations, where points
+ are far outside the visible area (f.e when zooming deep) this
+ might be a substantial improvement for the painting performance
+ */
+ ClipPolygons = 0x01,
+
+ /*!
+ Paint the symbol to a QPixmap and paint the pixmap
+ instead rendering the symbol for each point. The flag has
+ no effect, when the curve is not painted to the canvas
+ ( f.e when exporting the plot to a PDF document ).
+ */
+ CacheSymbols = 0x02
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ explicit QwtPlotCurve( const QString &title = QString::null );
+ explicit QwtPlotCurve( const QwtText &title );
+
+ virtual ~QwtPlotCurve();
+
+ virtual int rtti() const;
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ void setLegendAttribute( LegendAttribute, bool on = true );
+ bool testLegendAttribute( LegendAttribute ) const;
+
+#ifndef QWT_NO_COMPAT
+ void setRawSamples( const double *xData, const double *yData, int size );
+ void setSamples( const double *xData, const double *yData, int size );
+ void setSamples( const QVector<double> &xData, const QVector<double> &yData );
+#endif
+ void setSamples( const QVector<QPointF> & );
+
+ int closestPoint( const QPoint &pos, double *dist = NULL ) const;
+
+ double minXValue() const;
+ double maxXValue() const;
+ double minYValue() const;
+ double maxYValue() const;
+
+ void setCurveAttribute( CurveAttribute, bool on = true );
+ bool testCurveAttribute( CurveAttribute ) const;
+
+ void setPen( const QPen & );
+ const QPen &pen() const;
+
+ void setBrush( const QBrush & );
+ const QBrush &brush() const;
+
+ void setBaseline( double ref );
+ double baseline() const;
+
+ void setStyle( CurveStyle style );
+ CurveStyle style() const;
+
+ void setSymbol( const QwtSymbol *s );
+ const QwtSymbol *symbol() const;
+
+ void setCurveFitter( QwtCurveFitter * );
+ QwtCurveFitter *curveFitter() const;
+
+ virtual void drawSeries( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual void updateLegend( QwtLegend * ) const;
+ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const;
+
+protected:
+
+ void init();
+
+ virtual void drawCurve( QPainter *, int style,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual void drawSymbols( QPainter *, const QwtSymbol &,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ void drawLines( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ void drawSticks( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ void drawDots( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ void drawSteps( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual void fillCurve( QPainter *,
+ const QwtScaleMap &, const QwtScaleMap &,
+ const QRectF &canvasRect, QPolygonF & ) const;
+
+ void closePolyline( QPainter *,
+ const QwtScaleMap &, const QwtScaleMap &, QPolygonF & ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+//! boundingRect().left()
+inline double QwtPlotCurve::minXValue() const
+{
+ return boundingRect().left();
+}
+
+//! boundingRect().right()
+inline double QwtPlotCurve::maxXValue() const
+{
+ return boundingRect().right();
+}
+
+//! boundingRect().top()
+inline double QwtPlotCurve::minYValue() const
+{
+ return boundingRect().top();
+}
+
+//! boundingRect().bottom()
+inline double QwtPlotCurve::maxYValue() const
+{
+ return boundingRect().bottom();
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::PaintAttributes )
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::LegendAttributes )
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::CurveAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_dict.cpp b/src/libpcp_qwt/src/qwt_plot_dict.cpp
new file mode 100644
index 0000000..4125137
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_dict.cpp
@@ -0,0 +1,188 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_dict.h"
+
+class QwtPlotDict::PrivateData
+{
+public:
+
+ class ItemList: public QList<QwtPlotItem *>
+ {
+ public:
+ void insertItem( QwtPlotItem *item )
+ {
+ if ( item == NULL )
+ return;
+
+ QList<QwtPlotItem *>::iterator it =
+ qUpperBound( begin(), end(), item, LessZThan() );
+ insert( it, item );
+ }
+
+ void removeItem( QwtPlotItem *item )
+ {
+ if ( item == NULL )
+ return;
+
+ QList<QwtPlotItem *>::iterator it =
+ qLowerBound( begin(), end(), item, LessZThan() );
+
+ for ( ; it != end(); ++it )
+ {
+ if ( item == *it )
+ {
+ erase( it );
+ break;
+ }
+ }
+ }
+ private:
+ class LessZThan
+ {
+ public:
+ inline bool operator()( const QwtPlotItem *item1,
+ const QwtPlotItem *item2 ) const
+ {
+ return item1->z() < item2->z();
+ }
+ };
+ };
+
+ ItemList itemList;
+ bool autoDelete;
+};
+
+/*!
+ Constructor
+
+ Auto deletion is enabled.
+ \sa setAutoDelete(), attachItem()
+*/
+QwtPlotDict::QwtPlotDict()
+{
+ d_data = new QwtPlotDict::PrivateData;
+ d_data->autoDelete = true;
+}
+
+/*!
+ Destructor
+
+ If autoDelete is on, all attached items will be deleted
+ \sa setAutoDelete(), autoDelete(), attachItem()
+*/
+QwtPlotDict::~QwtPlotDict()
+{
+ detachItems( QwtPlotItem::Rtti_PlotItem, d_data->autoDelete );
+ delete d_data;
+}
+
+/*!
+ En/Disable Auto deletion
+
+ If Auto deletion is on all attached plot items will be deleted
+ in the destructor of QwtPlotDict. The default value is on.
+
+ \sa autoDelete(), attachItem()
+*/
+void QwtPlotDict::setAutoDelete( bool autoDelete )
+{
+ d_data->autoDelete = autoDelete;
+}
+
+/*!
+ \return true if auto deletion is enabled
+ \sa setAutoDelete(), attachItem()
+*/
+bool QwtPlotDict::autoDelete() const
+{
+ return d_data->autoDelete;
+}
+
+/*!
+ Attach/Detach a plot item
+
+ Attached items will be deleted in the destructor,
+ if auto deletion is enabled (default). Manually detached
+ items are not deleted.
+
+ \param item Plot item to attach/detach
+ \ on If true attach, else detach the item
+
+ \sa setAutoDelete(), ~QwtPlotDict()
+*/
+void QwtPlotDict::attachItem( QwtPlotItem *item, bool on )
+{
+ if ( on )
+ d_data->itemList.insertItem( item );
+ else
+ d_data->itemList.removeItem( item );
+}
+
+/*!
+ Detach items from the dictionary
+
+ \param rtti In case of QwtPlotItem::Rtti_PlotItem detach all items
+ otherwise only those items of the type rtti.
+ \param autoDelete If true, delete all detached items
+*/
+void QwtPlotDict::detachItems( int rtti, bool autoDelete )
+{
+ PrivateData::ItemList list = d_data->itemList;
+ QwtPlotItemIterator it = list.begin();
+ while ( it != list.end() )
+ {
+ QwtPlotItem *item = *it;
+
+ ++it; // increment before removing item from the list
+
+ if ( rtti == QwtPlotItem::Rtti_PlotItem || item->rtti() == rtti )
+ {
+ item->attach( NULL );
+ if ( autoDelete )
+ delete item;
+ }
+ }
+}
+
+/*!
+ \brief A QwtPlotItemList of all attached plot items.
+
+ Use caution when iterating these lists, as removing/detaching an item will
+ invalidate the iterator. Instead you can place pointers to objects to be
+ removed in a removal list, and traverse that list later.
+
+ \return List of all attached plot items.
+*/
+const QwtPlotItemList &QwtPlotDict::itemList() const
+{
+ return d_data->itemList;
+}
+
+/*!
+ \return List of all attached plot items of a specific type.
+ \sa QwtPlotItem::rtti()
+*/
+QwtPlotItemList QwtPlotDict::itemList( int rtti ) const
+{
+ if ( rtti == QwtPlotItem::Rtti_PlotItem )
+ return d_data->itemList;
+
+ QwtPlotItemList items;
+
+ PrivateData::ItemList list = d_data->itemList;
+ for ( QwtPlotItemIterator it = list.begin(); it != list.end(); ++it )
+ {
+ QwtPlotItem *item = *it;
+ if ( item->rtti() == rtti )
+ items += item;
+ }
+
+ return items;
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_dict.h b/src/libpcp_qwt/src/qwt_plot_dict.h
new file mode 100644
index 0000000..0882d28
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_dict.h
@@ -0,0 +1,58 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+/*! \file !*/
+#ifndef QWT_PLOT_DICT
+#define QWT_PLOT_DICT
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include <qlist.h>
+
+/// \var typedef QList< QwtPlotItem *> QwtPlotItemList
+/// \brief See QT 4.x assistant documentation for QList
+typedef QList<QwtPlotItem *> QwtPlotItemList;
+typedef QList<QwtPlotItem *>::ConstIterator QwtPlotItemIterator;
+
+/*!
+ \brief A dictionary for plot items
+
+ QwtPlotDict organizes plot items in increasing z-order.
+ If autoDelete() is enabled, all attached items will be deleted
+ in the destructor of the dictionary.
+ QwtPlotDict can be used to get access to all QwtPlotItem items - or all
+ items of a specific type - that are currently on the plot.
+
+ \sa QwtPlotItem::attach(), QwtPlotItem::detach(), QwtPlotItem::z()
+*/
+class QWT_EXPORT QwtPlotDict
+{
+public:
+ explicit QwtPlotDict();
+ virtual ~QwtPlotDict();
+
+ void setAutoDelete( bool );
+ bool autoDelete() const;
+
+ const QwtPlotItemList& itemList() const;
+ QwtPlotItemList itemList( int rtti ) const;
+
+ void detachItems( int rtti = QwtPlotItem::Rtti_PlotItem,
+ bool autoDelete = true );
+
+private:
+ friend class QwtPlotItem;
+
+ void attachItem( QwtPlotItem *, bool );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_directpainter.cpp b/src/libpcp_qwt/src/qwt_plot_directpainter.cpp
new file mode 100644
index 0000000..28682aa
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_directpainter.cpp
@@ -0,0 +1,313 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_directpainter.h"
+#include "qwt_scale_map.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_plot_seriesitem.h"
+#include <qpainter.h>
+#include <qevent.h>
+#include <qapplication.h>
+#include <qpixmap.h>
+
+static inline void renderItem(
+ QPainter *painter, const QRect &canvasRect,
+ QwtPlotAbstractSeriesItem *seriesItem, int from, int to )
+{
+ // A minor performance improvement is possible
+ // with caching the maps. TODO ...
+
+ QwtPlot *plot = seriesItem->plot();
+ const QwtScaleMap xMap = plot->canvasMap( seriesItem->xAxis() );
+ const QwtScaleMap yMap = plot->canvasMap( seriesItem->yAxis() );
+
+ painter->setRenderHint( QPainter::Antialiasing,
+ seriesItem->testRenderHint( QwtPlotItem::RenderAntialiased ) );
+ seriesItem->drawSeries( painter, xMap, yMap, canvasRect, from, to );
+}
+
+class QwtPlotDirectPainter::PrivateData
+{
+public:
+ PrivateData():
+ attributes( 0 ),
+ hasClipping(false),
+ seriesItem( NULL )
+ {
+ }
+
+ QwtPlotDirectPainter::Attributes attributes;
+
+ bool hasClipping;
+ QRegion clipRegion;
+
+ QPainter painter;
+
+ QwtPlotAbstractSeriesItem *seriesItem;
+ int from;
+ int to;
+};
+
+//! Constructor
+QwtPlotDirectPainter::QwtPlotDirectPainter( QObject *parent ):
+ QObject( parent )
+{
+ d_data = new PrivateData;
+}
+
+//! Destructor
+QwtPlotDirectPainter::~QwtPlotDirectPainter()
+{
+ delete d_data;
+}
+
+/*!
+ Change an attribute
+
+ \param attribute Attribute to change
+ \param on On/Off
+
+ \sa Attribute, testAttribute()
+*/
+void QwtPlotDirectPainter::setAttribute( Attribute attribute, bool on )
+{
+ if ( bool( d_data->attributes & attribute ) != on )
+ {
+ if ( on )
+ d_data->attributes |= attribute;
+ else
+ d_data->attributes &= ~attribute;
+
+ if ( ( attribute == AtomicPainter ) && on )
+ reset();
+ }
+}
+
+/*!
+ Check if a attribute is set.
+
+ \param attribute Attribute to be tested
+ \sa Attribute, setAttribute()
+*/
+bool QwtPlotDirectPainter::testAttribute( Attribute attribute ) const
+{
+ return d_data->attributes & attribute;
+}
+
+/*!
+ En/Disables clipping
+
+ \param enable Enables clipping is true, disable it otherwise
+ \sa hasClipping(), clipRegion(), setClipRegion()
+*/
+void QwtPlotDirectPainter::setClipping( bool enable )
+{
+ d_data->hasClipping = enable;
+}
+
+/*!
+ \return true, when clipping is enabled
+ \sa setClipping(), clipRegion(), setClipRegion()
+*/
+bool QwtPlotDirectPainter::hasClipping() const
+{
+ return d_data->hasClipping;
+}
+
+/*!
+ \brief Assign a clip region and enable clipping
+
+ Depending on the environment setting a proper clip region might improve
+ the performance heavily. F.e. on Qt embedded only the clipped part of
+ the backing store will be copied to a ( maybe unaccelerated ) frame buffer
+ device.
+
+ \param region Clip region
+ \sa clipRegion(), hasClipping(), setClipping()
+*/
+void QwtPlotDirectPainter::setClipRegion( const QRegion &region )
+{
+ d_data->clipRegion = region;
+ d_data->hasClipping = true;
+}
+
+/*!
+ \return Currently set clip region.
+ \sa setClipRegion(), setClipping(), hasClipping()
+*/
+QRegion QwtPlotDirectPainter::clipRegion() const
+{
+ return d_data->clipRegion;
+}
+
+/*!
+ \brief Draw a set of points of a seriesItem.
+
+ When observing an measurement while it is running, new points have to be
+ added to an existing seriesItem. drawSeries can be used to display them avoiding
+ a complete redraw of the canvas.
+
+ Setting plot()->canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true);
+ will result in faster painting, if the paint engine of the canvas widget
+ supports this feature.
+
+ \param seriesItem Item to be painted
+ \param from Index of the first point to be painted
+ \param to Index of the last point to be painted. If to < 0 the
+ series will be painted to its last point.
+*/
+void QwtPlotDirectPainter::drawSeries(
+ QwtPlotAbstractSeriesItem *seriesItem, int from, int to )
+{
+ if ( seriesItem == NULL || seriesItem->plot() == NULL )
+ return;
+
+ QwtPlotCanvas *canvas = seriesItem->plot()->canvas();
+ const QRect canvasRect = canvas->contentsRect();
+
+ const bool hasBackingStore =
+ canvas->testPaintAttribute( QwtPlotCanvas::BackingStore )
+ && canvas->backingStore() && !canvas->backingStore()->isNull();
+
+ if ( hasBackingStore )
+ {
+ QPainter painter( const_cast<QPixmap *>( canvas->backingStore() ) );
+
+ if ( d_data->hasClipping )
+ painter.setClipRegion( d_data->clipRegion );
+
+ renderItem( &painter, canvasRect, seriesItem, from, to );
+
+ if ( testAttribute( QwtPlotDirectPainter::FullRepaint ) )
+ {
+ canvas->repaint();
+ return;
+ }
+ }
+
+ bool immediatePaint = true;
+ if ( !canvas->testAttribute( Qt::WA_WState_InPaintEvent ) )
+ {
+#if QT_VERSION < 0x050000
+ if ( !canvas->testAttribute( Qt::WA_PaintOutsidePaintEvent ) )
+#endif
+ immediatePaint = false;
+ }
+
+ if ( immediatePaint )
+ {
+ if ( !d_data->painter.isActive() )
+ {
+ reset();
+
+ d_data->painter.begin( canvas );
+ canvas->installEventFilter( this );
+ }
+
+ if ( d_data->hasClipping )
+ {
+ d_data->painter.setClipRegion(
+ QRegion( canvasRect ) & d_data->clipRegion );
+ }
+ else
+ {
+ if ( !d_data->painter.hasClipping() )
+ d_data->painter.setClipRect( canvasRect );
+ }
+
+ renderItem( &d_data->painter, canvasRect, seriesItem, from, to );
+
+ if ( d_data->attributes & QwtPlotDirectPainter::AtomicPainter )
+ {
+ reset();
+ }
+ else
+ {
+ if ( d_data->hasClipping )
+ d_data->painter.setClipping( false );
+ }
+ }
+ else
+ {
+ reset();
+
+ d_data->seriesItem = seriesItem;
+ d_data->from = from;
+ d_data->to = to;
+
+ QRegion clipRegion = canvasRect;
+ if ( d_data->hasClipping )
+ clipRegion &= d_data->clipRegion;
+
+ canvas->installEventFilter( this );
+ canvas->repaint(clipRegion);
+ canvas->removeEventFilter( this );
+
+ d_data->seriesItem = NULL;
+ }
+}
+
+//! Close the internal QPainter
+void QwtPlotDirectPainter::reset()
+{
+ if ( d_data->painter.isActive() )
+ {
+ QWidget *w = ( QWidget * )d_data->painter.device();
+ if ( w )
+ w->removeEventFilter( this );
+
+ d_data->painter.end();
+ }
+}
+
+//! Event filter
+bool QwtPlotDirectPainter::eventFilter( QObject *, QEvent *event )
+{
+ if ( event->type() == QEvent::Paint )
+ {
+ reset();
+
+ if ( d_data->seriesItem )
+ {
+ const QPaintEvent *pe = static_cast< QPaintEvent *>( event );
+
+ QwtPlotCanvas *canvas = d_data->seriesItem->plot()->canvas();
+
+ QPainter painter( canvas );
+ painter.setClipRegion( pe->region() );
+
+ bool copyCache = testAttribute( CopyBackingStore )
+ && canvas->testPaintAttribute( QwtPlotCanvas::BackingStore );
+
+ if ( copyCache )
+ {
+ // is something valid in the cache ?
+ copyCache = ( canvas->backingStore() != NULL )
+ && !canvas->backingStore()->isNull();
+ }
+
+ if ( copyCache )
+ {
+ painter.drawPixmap(
+ canvas->contentsRect().topLeft(),
+ *canvas->backingStore() );
+ }
+ else
+ {
+ renderItem( &painter, canvas->contentsRect(),
+ d_data->seriesItem, d_data->from, d_data->to );
+ }
+
+ return true; // don't call QwtPlotCanvas::paintEvent()
+ }
+ }
+
+ return false;
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_directpainter.h b/src/libpcp_qwt/src/qwt_plot_directpainter.h
new file mode 100644
index 0000000..ca7dbf9
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_directpainter.h
@@ -0,0 +1,100 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_DIRECT_PAINTER_H
+#define QWT_PLOT_DIRECT_PAINTER_H
+
+#include "qwt_global.h"
+#include <qobject.h>
+
+class QRegion;
+class QwtPlotAbstractSeriesItem;
+
+/*!
+ \brief Painter object trying to paint incrementally
+
+ Often applications want to display samples while they are
+ collected. When there are too many samples complete replots
+ will be expensive to be processed in a collection cycle.
+
+ QwtPlotDirectPainter offers an API to paint
+ subsets ( f.e all additions points ) without erasing/repainting
+ the plot canvas.
+
+ On certain environments it might be important to calculate a proper
+ clip region before painting. F.e. for Qt Embedded only the clipped part
+ of the backing store will be copied to a ( maybe unaccelerated )
+ frame buffer.
+
+ \warning Incremental painting will only help when no replot is triggered
+ by another operation ( like changing scales ) and nothing needs
+ to be erased.
+*/
+class QWT_EXPORT QwtPlotDirectPainter: public QObject
+{
+public:
+ /*!
+ \brief Paint attributes
+ \sa setAttribute(), testAttribute(), drawSeries()
+ */
+ enum Attribute
+ {
+ /*!
+ Initializing a QPainter is an expensive operation.
+ When AtomicPainter is set each call of drawSeries() opens/closes
+ a temporary QPainter. Otherwise QwtPlotDirectPainter tries to
+ use the same QPainter as long as possible.
+ */
+ AtomicPainter = 0x01,
+
+ /*!
+ When FullRepaint is set the plot canvas is explicitely repainted
+ after the samples have been rendered.
+ */
+ FullRepaint = 0x02,
+
+ /*!
+ When QwtPlotCanvas::BackingStore is enabled the painter
+ has to paint to the backing store and the widget. In certain
+ situations/environments it might be faster to paint to
+ the backing store only and then copy the backingstore to the canvas.
+ This flag can also be useful for settings, where Qt fills the
+ the clip region with the widget background.
+ */
+ CopyBackingStore = 0x04
+ };
+
+ //! Paint attributes
+ typedef QFlags<Attribute> Attributes;
+
+ QwtPlotDirectPainter( QObject *parent = NULL );
+ virtual ~QwtPlotDirectPainter();
+
+ void setAttribute( Attribute, bool on );
+ bool testAttribute( Attribute ) const;
+
+ void setClipping( bool );
+ bool hasClipping() const;
+
+ void setClipRegion( const QRegion & );
+ QRegion clipRegion() const;
+
+ void drawSeries( QwtPlotAbstractSeriesItem *, int from, int to );
+ void reset();
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotDirectPainter::Attributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_grid.cpp b/src/libpcp_qwt/src/qwt_plot_grid.cpp
new file mode 100644
index 0000000..8e25aeb
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_grid.cpp
@@ -0,0 +1,367 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_grid.h"
+#include "qwt_painter.h"
+#include "qwt_text.h"
+#include "qwt_scale_map.h"
+#include "qwt_scale_div.h"
+#include "qwt_math.h"
+#include <qpainter.h>
+#include <qpen.h>
+
+class QwtPlotGrid::PrivateData
+{
+public:
+ PrivateData():
+ xEnabled( true ),
+ yEnabled( true ),
+ xMinEnabled( false ),
+ yMinEnabled( false )
+ {
+ }
+
+ bool xEnabled;
+ bool yEnabled;
+ bool xMinEnabled;
+ bool yMinEnabled;
+
+ QwtScaleDiv xScaleDiv;
+ QwtScaleDiv yScaleDiv;
+
+ QPen majPen;
+ QPen minPen;
+};
+
+//! Enables major grid, disables minor grid
+QwtPlotGrid::QwtPlotGrid():
+ QwtPlotItem( QwtText( "Grid" ) )
+{
+ d_data = new PrivateData;
+ setZ( 10.0 );
+}
+
+//! Destructor
+QwtPlotGrid::~QwtPlotGrid()
+{
+ delete d_data;
+}
+
+//! \return QwtPlotItem::Rtti_PlotGrid
+int QwtPlotGrid::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotGrid;
+}
+
+/*!
+ \brief Enable or disable vertical gridlines
+ \param tf Enable (true) or disable
+
+ \sa Minor gridlines can be enabled or disabled with
+ enableXMin()
+*/
+void QwtPlotGrid::enableX( bool tf )
+{
+ if ( d_data->xEnabled != tf )
+ {
+ d_data->xEnabled = tf;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Enable or disable horizontal gridlines
+ \param tf Enable (true) or disable
+ \sa Minor gridlines can be enabled or disabled with enableYMin()
+*/
+void QwtPlotGrid::enableY( bool tf )
+{
+ if ( d_data->yEnabled != tf )
+ {
+ d_data->yEnabled = tf;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Enable or disable minor vertical gridlines.
+ \param tf Enable (true) or disable
+ \sa enableX()
+*/
+void QwtPlotGrid::enableXMin( bool tf )
+{
+ if ( d_data->xMinEnabled != tf )
+ {
+ d_data->xMinEnabled = tf;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Enable or disable minor horizontal gridlines
+ \param tf Enable (true) or disable
+ \sa enableY()
+*/
+void QwtPlotGrid::enableYMin( bool tf )
+{
+ if ( d_data->yMinEnabled != tf )
+ {
+ d_data->yMinEnabled = tf;
+ itemChanged();
+ }
+}
+
+/*!
+ Assign an x axis scale division
+
+ \param scaleDiv Scale division
+*/
+void QwtPlotGrid::setXDiv( const QwtScaleDiv &scaleDiv )
+{
+ if ( d_data->xScaleDiv != scaleDiv )
+ {
+ d_data->xScaleDiv = scaleDiv;
+ itemChanged();
+ }
+}
+
+/*!
+ Assign a y axis division
+
+ \param scaleDiv Scale division
+*/
+void QwtPlotGrid::setYDiv( const QwtScaleDiv &scaleDiv )
+{
+ if ( d_data->yScaleDiv != scaleDiv )
+ {
+ d_data->yScaleDiv = scaleDiv;
+ itemChanged();
+ }
+}
+
+/*!
+ Assign a pen for both major and minor gridlines
+
+ \param pen Pen
+ \sa setMajPen(), setMinPen()
+*/
+void QwtPlotGrid::setPen( const QPen &pen )
+{
+ if ( d_data->majPen != pen || d_data->minPen != pen )
+ {
+ d_data->majPen = pen;
+ d_data->minPen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ Assign a pen for the major gridlines
+
+ \param pen Pen
+ \sa majPen(), setMinPen(), setPen()
+*/
+void QwtPlotGrid::setMajPen( const QPen &pen )
+{
+ if ( d_data->majPen != pen )
+ {
+ d_data->majPen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ Assign a pen for the minor gridlines
+
+ \param pen Pen
+ \sa minPen(), setMajPen(), setPen()
+*/
+void QwtPlotGrid::setMinPen( const QPen &pen )
+{
+ if ( d_data->minPen != pen )
+ {
+ d_data->minPen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Draw the grid
+
+ The grid is drawn into the bounding rectangle such that
+ gridlines begin and end at the rectangle's borders. The X and Y
+ maps are used to map the scale divisions into the drawing region
+ screen.
+ \param painter Painter
+ \param xMap X axis map
+ \param yMap Y axis
+ \param canvasRect Contents rect of the plot canvas
+*/
+void QwtPlotGrid::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ // draw minor gridlines
+ QPen minPen = d_data->minPen;
+ minPen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( minPen );
+
+ if ( d_data->xEnabled && d_data->xMinEnabled )
+ {
+ drawLines( painter, canvasRect, Qt::Vertical, xMap,
+ d_data->xScaleDiv.ticks( QwtScaleDiv::MinorTick ) );
+ drawLines( painter, canvasRect, Qt::Vertical, xMap,
+ d_data->xScaleDiv.ticks( QwtScaleDiv::MediumTick ) );
+ }
+
+ if ( d_data->yEnabled && d_data->yMinEnabled )
+ {
+ drawLines( painter, canvasRect, Qt::Horizontal, yMap,
+ d_data->yScaleDiv.ticks( QwtScaleDiv::MinorTick ) );
+ drawLines( painter, canvasRect, Qt::Horizontal, yMap,
+ d_data->yScaleDiv.ticks( QwtScaleDiv::MediumTick ) );
+ }
+
+ // draw major gridlines
+ QPen majPen = d_data->majPen;
+ majPen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( majPen );
+
+ if ( d_data->xEnabled )
+ {
+ drawLines( painter, canvasRect, Qt::Vertical, xMap,
+ d_data->xScaleDiv.ticks( QwtScaleDiv::MajorTick ) );
+ }
+
+ if ( d_data->yEnabled )
+ {
+ drawLines( painter, canvasRect, Qt::Horizontal, yMap,
+ d_data->yScaleDiv.ticks( QwtScaleDiv::MajorTick ) );
+ }
+}
+
+void QwtPlotGrid::drawLines( QPainter *painter, const QRectF &canvasRect,
+ Qt::Orientation orientation, const QwtScaleMap &scaleMap,
+ const QList<double> &values ) const
+{
+ const double x1 = canvasRect.left();
+ const double x2 = canvasRect.right() - 1.0;
+ const double y1 = canvasRect.top();
+ const double y2 = canvasRect.bottom() - 1.0;
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ for ( int i = 0; i < values.count(); i++ )
+ {
+ double value = scaleMap.transform( values[i] );
+ if ( doAlign )
+ value = qRound( value );
+
+ if ( orientation == Qt::Horizontal )
+ {
+ if ( qwtFuzzyGreaterOrEqual( value, y1 ) &&
+ qwtFuzzyLessOrEqual( value, y2 ) )
+ {
+ QwtPainter::drawLine( painter, x1, value, x2, value );
+ }
+ }
+ else
+ {
+ if ( qwtFuzzyGreaterOrEqual( value, x1 ) &&
+ qwtFuzzyLessOrEqual( value, x2 ) )
+ {
+ QwtPainter::drawLine( painter, value, y1, value, y2 );
+ }
+ }
+ }
+}
+
+/*!
+ \return the pen for the major gridlines
+ \sa setMajPen(), setMinPen(), setPen()
+*/
+const QPen &QwtPlotGrid::majPen() const
+{
+ return d_data->majPen;
+}
+
+/*!
+ \return the pen for the minor gridlines
+ \sa setMinPen(), setMajPen(), setPen()
+*/
+const QPen &QwtPlotGrid::minPen() const
+{
+ return d_data->minPen;
+}
+
+/*!
+ \return true if vertical gridlines are enabled
+ \sa enableX()
+*/
+bool QwtPlotGrid::xEnabled() const
+{
+ return d_data->xEnabled;
+}
+
+/*!
+ \return true if minor vertical gridlines are enabled
+ \sa enableXMin()
+*/
+bool QwtPlotGrid::xMinEnabled() const
+{
+ return d_data->xMinEnabled;
+}
+
+/*!
+ \return true if horizontal gridlines are enabled
+ \sa enableY()
+*/
+bool QwtPlotGrid::yEnabled() const
+{
+ return d_data->yEnabled;
+}
+
+/*!
+ \return true if minor horizontal gridlines are enabled
+ \sa enableYMin()
+*/
+bool QwtPlotGrid::yMinEnabled() const
+{
+ return d_data->yMinEnabled;
+}
+
+
+/*! \return the scale division of the x axis */
+const QwtScaleDiv &QwtPlotGrid::xScaleDiv() const
+{
+ return d_data->xScaleDiv;
+}
+
+/*! \return the scale division of the y axis */
+const QwtScaleDiv &QwtPlotGrid::yScaleDiv() const
+{
+ return d_data->yScaleDiv;
+}
+
+/*!
+ Update the grid to changes of the axes scale division
+
+ \param xScaleDiv Scale division of the x-axis
+ \param yScaleDiv Scale division of the y-axis
+
+ \sa QwtPlot::updateAxes()
+*/
+void QwtPlotGrid::updateScaleDiv( const QwtScaleDiv& xScaleDiv,
+ const QwtScaleDiv& yScaleDiv )
+{
+ setXDiv( xScaleDiv );
+ setYDiv( yScaleDiv );
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_grid.h b/src/libpcp_qwt/src/qwt_plot_grid.h
new file mode 100644
index 0000000..361ec81
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_grid.h
@@ -0,0 +1,84 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_GRID_H
+#define QWT_PLOT_GRID_H
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include "qwt_scale_div.h"
+
+class QPainter;
+class QPen;
+class QwtScaleMap;
+class QwtScaleDiv;
+
+/*!
+ \brief A class which draws a coordinate grid
+
+ The QwtPlotGrid class can be used to draw a coordinate grid.
+ A coordinate grid consists of major and minor vertical
+ and horizontal gridlines. The locations of the gridlines
+ are determined by the X and Y scale divisions which can
+ be assigned with setXDiv() and setYDiv().
+ The draw() member draws the grid within a bounding
+ rectangle.
+*/
+
+class QWT_EXPORT QwtPlotGrid: public QwtPlotItem
+{
+public:
+ explicit QwtPlotGrid();
+ virtual ~QwtPlotGrid();
+
+ virtual int rtti() const;
+
+ void enableX( bool tf );
+ bool xEnabled() const;
+
+ void enableY( bool tf );
+ bool yEnabled() const;
+
+ void enableXMin( bool tf );
+ bool xMinEnabled() const;
+
+ void enableYMin( bool tf );
+ bool yMinEnabled() const;
+
+ void setXDiv( const QwtScaleDiv &sx );
+ const QwtScaleDiv &xScaleDiv() const;
+
+ void setYDiv( const QwtScaleDiv &sy );
+ const QwtScaleDiv &yScaleDiv() const;
+
+ void setPen( const QPen &p );
+
+ void setMajPen( const QPen &p );
+ const QPen& majPen() const;
+
+ void setMinPen( const QPen &p );
+ const QPen& minPen() const;
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &rect ) const;
+
+ virtual void updateScaleDiv(
+ const QwtScaleDiv &xMap, const QwtScaleDiv &yMap );
+
+private:
+ void drawLines( QPainter *painter, const QRectF &,
+ Qt::Orientation orientation, const QwtScaleMap &,
+ const QList<double> & ) const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_histogram.cpp b/src/libpcp_qwt/src/qwt_plot_histogram.cpp
new file mode 100644
index 0000000..53d0551
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_histogram.cpp
@@ -0,0 +1,651 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_histogram.h"
+#include "qwt_plot.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_painter.h"
+#include "qwt_column_symbol.h"
+#include "qwt_scale_map.h"
+#include <qstring.h>
+#include <qpainter.h>
+
+static inline bool isCombinable( const QwtInterval &d1,
+ const QwtInterval &d2 )
+{
+ if ( d1.isValid() && d2.isValid() )
+ {
+ if ( d1.maxValue() == d2.minValue() )
+ {
+ if ( !( d1.borderFlags() & QwtInterval::ExcludeMaximum
+ && d2.borderFlags() & QwtInterval::ExcludeMinimum ) )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+class QwtPlotHistogram::PrivateData
+{
+public:
+ PrivateData():
+ baseline( 0.0 ),
+ style( Columns ),
+ symbol( NULL )
+ {
+ }
+
+ ~PrivateData()
+ {
+ delete symbol;
+ }
+
+ double baseline;
+
+ QPen pen;
+ QBrush brush;
+ QwtPlotHistogram::HistogramStyle style;
+ const QwtColumnSymbol *symbol;
+};
+
+/*!
+ Constructor
+ \param title Title of the histogram.
+*/
+
+QwtPlotHistogram::QwtPlotHistogram( const QwtText &title ):
+ QwtPlotSeriesItem<QwtIntervalSample>( title )
+{
+ init();
+}
+
+/*!
+ Constructor
+ \param title Title of the histogram.
+*/
+QwtPlotHistogram::QwtPlotHistogram( const QString &title ):
+ QwtPlotSeriesItem<QwtIntervalSample>( title )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotHistogram::~QwtPlotHistogram()
+{
+ delete d_data;
+}
+
+//! Initialize data members
+void QwtPlotHistogram::init()
+{
+ d_data = new PrivateData();
+ d_series = new QwtIntervalSeriesData();
+
+ setItemAttribute( QwtPlotItem::AutoScale, true );
+ setItemAttribute( QwtPlotItem::Legend, true );
+
+ setZ( 20.0 );
+}
+
+/*!
+ Set the histogram's drawing style
+
+ \param style Histogram style
+ \sa HistogramStyle, style()
+*/
+void QwtPlotHistogram::setStyle( HistogramStyle style )
+{
+ if ( style != d_data->style )
+ {
+ d_data->style = style;
+ itemChanged();
+ }
+}
+
+/*!
+ Return the current style
+ \sa HistogramStyle, setStyle()
+*/
+QwtPlotHistogram::HistogramStyle QwtPlotHistogram::style() const
+{
+ return d_data->style;
+}
+
+/*!
+ Assign a pen, that is used in a style() depending way.
+
+ \param pen New pen
+ \sa pen(), brush()
+*/
+void QwtPlotHistogram::setPen( const QPen &pen )
+{
+ if ( pen != d_data->pen )
+ {
+ d_data->pen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Pen used in a style() depending way.
+ \sa setPen(), brush()
+*/
+const QPen &QwtPlotHistogram::pen() const
+{
+ return d_data->pen;
+}
+
+/*!
+ Assign a brush, that is used in a style() depending way.
+
+ \param brush New brush
+ \sa pen(), brush()
+*/
+void QwtPlotHistogram::setBrush( const QBrush &brush )
+{
+ if ( brush != d_data->brush )
+ {
+ d_data->brush = brush;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Brush used in a style() depending way.
+ \sa setPen(), brush()
+*/
+const QBrush &QwtPlotHistogram::brush() const
+{
+ return d_data->brush;
+}
+
+/*!
+ \brief Assign a symbol
+
+ In Column style an optional symbol can be assigned, that is responsible
+ for displaying the rectangle that is defined by the interval and
+ the distance between baseline() and value. When no symbol has been
+ defined the area is displayed as plain rectangle using pen() and brush().
+
+ \sa style(), symbol(), drawColumn(), pen(), brush()
+
+ \note In applications, where different intervals need to be displayed
+ in a different way ( f.e different colors or even using differnt symbols)
+ it is recommended to overload drawColumn().
+*/
+void QwtPlotHistogram::setSymbol( const QwtColumnSymbol *symbol )
+{
+ if ( symbol != d_data->symbol )
+ {
+ delete d_data->symbol;
+ d_data->symbol = symbol;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Current symbol or NULL, when no symbol has been assigned
+ \sa setSymbol()
+*/
+const QwtColumnSymbol *QwtPlotHistogram::symbol() const
+{
+ return d_data->symbol;
+}
+
+/*!
+ \brief Set the value of the baseline
+
+ Each column representing an QwtIntervalSample is defined by its
+ interval and the interval between baseline and the value of the sample.
+
+ The default value of the baseline is 0.0.
+
+ \param value Value of the baseline
+ \sa baseline()
+*/
+void QwtPlotHistogram::setBaseline( double value )
+{
+ if ( d_data->baseline != value )
+ {
+ d_data->baseline = value;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Value of the baseline
+ \sa setBaseline()
+*/
+double QwtPlotHistogram::baseline() const
+{
+ return d_data->baseline;
+}
+
+/*!
+ \return Bounding rectangle of all samples.
+ For an empty series the rectangle is invalid.
+*/
+QRectF QwtPlotHistogram::boundingRect() const
+{
+ QRectF rect = d_series->boundingRect();
+ if ( !rect.isValid() )
+ return rect;
+
+ if ( orientation() == Qt::Horizontal )
+ {
+ rect = QRectF( rect.y(), rect.x(),
+ rect.height(), rect.width() );
+
+ if ( rect.left() > d_data->baseline )
+ rect.setLeft( d_data->baseline );
+ else if ( rect.right() < d_data->baseline )
+ rect.setRight( d_data->baseline );
+ }
+ else
+ {
+ if ( rect.bottom() < d_data->baseline )
+ rect.setBottom( d_data->baseline );
+ else if ( rect.top() > d_data->baseline )
+ rect.setTop( d_data->baseline );
+ }
+
+ return rect;
+}
+
+//! \return QwtPlotItem::Rtti_PlotHistogram
+int QwtPlotHistogram::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotHistogram;
+}
+
+/*!
+ Initialize data with an array of samples.
+ \param samples Vector of points
+*/
+void QwtPlotHistogram::setSamples(
+ const QVector<QwtIntervalSample> &samples )
+{
+ delete d_series;
+ d_series = new QwtIntervalSeriesData( samples );
+ itemChanged();
+}
+
+/*!
+ Draw a subset of the histogram samples
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ series will be painted to its last sample.
+
+ \sa drawOutline(), drawLines(), drawColumns
+*/
+void QwtPlotHistogram::drawSeries( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &, int from, int to ) const
+{
+ if ( !painter || dataSize() <= 0 )
+ return;
+
+ if ( to < 0 )
+ to = dataSize() - 1;
+
+ switch ( d_data->style )
+ {
+ case Outline:
+ drawOutline( painter, xMap, yMap, from, to );
+ break;
+ case Lines:
+ drawLines( painter, xMap, yMap, from, to );
+ break;
+ case Columns:
+ drawColumns( painter, xMap, yMap, from, to );
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ Draw a histogram in Outline style()
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ histogram will be painted to its last point.
+
+ \sa setStyle(), style()
+ \warning The outline style requires, that the intervals are in increasing
+ order and not overlapping.
+*/
+void QwtPlotHistogram::drawOutline( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ double v0 = ( orientation() == Qt::Horizontal ) ?
+ xMap.transform( baseline() ) : yMap.transform( baseline() );
+ if ( doAlign )
+ v0 = qRound( v0 );
+
+ QwtIntervalSample previous;
+
+ QPolygonF polygon;
+ for ( int i = from; i <= to; i++ )
+ {
+ const QwtIntervalSample sample = d_series->sample( i );
+
+ if ( !sample.interval.isValid() )
+ {
+ flushPolygon( painter, v0, polygon );
+ previous = sample;
+ continue;
+ }
+
+ if ( previous.interval.isValid() )
+ {
+ if ( !isCombinable( previous.interval, sample.interval ) )
+ flushPolygon( painter, v0, polygon );
+ }
+
+ if ( orientation() == Qt::Vertical )
+ {
+ double x1 = xMap.transform( sample.interval.minValue() );
+ double x2 = xMap.transform( sample.interval.maxValue() );
+ double y = yMap.transform( sample.value );
+ if ( doAlign )
+ {
+ x1 = qRound( x1 );
+ x2 = qRound( x2 );
+ y = qRound( y );
+ }
+
+ if ( polygon.size() == 0 )
+ polygon += QPointF( x1, v0 );
+
+ polygon += QPointF( x1, y );
+ polygon += QPointF( x2, y );
+ }
+ else
+ {
+ double y1 = yMap.transform( sample.interval.minValue() );
+ double y2 = yMap.transform( sample.interval.maxValue() );
+ double x = xMap.transform( sample.value );
+ if ( doAlign )
+ {
+ y1 = qRound( y1 );
+ y2 = qRound( y2 );
+ x = qRound( x );
+ }
+
+ if ( polygon.size() == 0 )
+ polygon += QPointF( v0, y1 );
+
+ polygon += QPointF( x, y1 );
+ polygon += QPointF( x, y2 );
+ }
+ previous = sample;
+ }
+
+ flushPolygon( painter, v0, polygon );
+}
+
+/*!
+ Draw a histogram in Columns style()
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ histogram will be painted to its last point.
+
+ \sa setStyle(), style(), setSymbol(), drawColumn()
+*/
+void QwtPlotHistogram::drawColumns( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const
+{
+ painter->setPen( d_data->pen );
+ painter->setBrush( d_data->brush );
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QwtIntervalSample sample = d_series->sample( i );
+ if ( !sample.interval.isNull() )
+ {
+ const QwtColumnRect rect = columnRect( sample, xMap, yMap );
+ drawColumn( painter, rect, sample );
+ }
+ }
+}
+
+/*!
+ Draw a histogram in Lines style()
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ histogram will be painted to its last point.
+
+ \sa setStyle(), style(), setPen()
+*/
+void QwtPlotHistogram::drawLines( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ painter->setPen( d_data->pen );
+ painter->setBrush( Qt::NoBrush );
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QwtIntervalSample sample = d_series->sample( i );
+ if ( !sample.interval.isNull() )
+ {
+ const QwtColumnRect rect = columnRect( sample, xMap, yMap );
+
+ QRectF r = rect.toRect();
+ if ( doAlign )
+ {
+ r.setLeft( qRound( r.left() ) );
+ r.setRight( qRound( r.right() ) );
+ r.setTop( qRound( r.top() ) );
+ r.setBottom( qRound( r.bottom() ) );
+ }
+
+ switch ( rect.direction )
+ {
+ case QwtColumnRect::LeftToRight:
+ {
+ QwtPainter::drawLine( painter,
+ r.topRight(), r.bottomRight() );
+ break;
+ }
+ case QwtColumnRect::RightToLeft:
+ {
+ QwtPainter::drawLine( painter,
+ r.topLeft(), r.bottomLeft() );
+ break;
+ }
+ case QwtColumnRect::TopToBottom:
+ {
+ QwtPainter::drawLine( painter,
+ r.bottomRight(), r.bottomLeft() );
+ break;
+ }
+ case QwtColumnRect::BottomToTop:
+ {
+ QwtPainter::drawLine( painter,
+ r.topRight(), r.topLeft() );
+ break;
+ }
+ }
+ }
+ }
+}
+
+//! Internal, used by the Outline style.
+void QwtPlotHistogram::flushPolygon( QPainter *painter,
+ double baseLine, QPolygonF &polygon ) const
+{
+ if ( polygon.size() == 0 )
+ return;
+
+ if ( orientation() == Qt::Horizontal )
+ polygon += QPointF( baseLine, polygon.last().y() );
+ else
+ polygon += QPointF( polygon.last().x(), baseLine );
+
+ if ( d_data->brush.style() != Qt::NoBrush )
+ {
+ painter->setPen( Qt::NoPen );
+ painter->setBrush( d_data->brush );
+
+ if ( orientation() == Qt::Horizontal )
+ {
+ polygon += QPointF( polygon.last().x(), baseLine );
+ polygon += QPointF( polygon.first().x(), baseLine );
+ }
+ else
+ {
+ polygon += QPointF( baseLine, polygon.last().y() );
+ polygon += QPointF( baseLine, polygon.first().y() );
+ }
+ QwtPainter::drawPolygon( painter, polygon );
+ int resize = polygon.size();
+ if ( resize > 1 )
+ resize -= 2;
+ polygon.resize( resize );
+ }
+ if ( d_data->pen.style() != Qt::NoPen )
+ {
+ painter->setBrush( Qt::NoBrush );
+ painter->setPen( d_data->pen );
+ QwtPainter::drawPolyline( painter, polygon );
+ }
+ polygon.clear();
+}
+
+/*!
+ Calculate the area that is covered by a sample
+
+ \param sample Sample
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+
+ \return Rectangle, that is covered by a sample
+*/
+QwtColumnRect QwtPlotHistogram::columnRect( const QwtIntervalSample &sample,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const
+{
+ QwtColumnRect rect;
+
+ const QwtInterval &iv = sample.interval;
+ if ( !iv.isValid() )
+ return rect;
+
+ if ( orientation() == Qt::Horizontal )
+ {
+ const double x0 = xMap.transform( baseline() );
+ const double x = xMap.transform( sample.value );
+ const double y1 = yMap.transform( iv.minValue() );
+ const double y2 = yMap.transform( iv.maxValue() );
+
+ rect.hInterval.setInterval( x0, x );
+ rect.vInterval.setInterval( y1, y2, iv.borderFlags() );
+ rect.direction = ( x < x0 ) ? QwtColumnRect::RightToLeft :
+ QwtColumnRect::LeftToRight;
+ }
+ else
+ {
+ const double x1 = xMap.transform( iv.minValue() );
+ const double x2 = xMap.transform( iv.maxValue() );
+ const double y0 = yMap.transform( baseline() );
+ const double y = yMap.transform( sample.value );
+
+ rect.hInterval.setInterval( x1, x2, iv.borderFlags() );
+ rect.vInterval.setInterval( y0, y );
+ rect.direction = ( y < y0 ) ? QwtColumnRect::BottomToTop :
+ QwtColumnRect::TopToBottom;
+ }
+
+ return rect;
+}
+
+/*!
+ Draw a column for a sample in Columns style().
+
+ When a symbol() has been set the symbol is used otherwise the
+ column is displayed as plain rectangle using pen() and brush().
+
+ \param painter Painter
+ \param rect Rectangle where to paint the column in paint device coordinates
+ \param sample Sample to be displayed
+
+ \note In applications, where different intervals need to be displayed
+ in a different way ( f.e different colors or even using differnt symbols)
+ it is recommended to overload drawColumn().
+*/
+void QwtPlotHistogram::drawColumn( QPainter *painter,
+ const QwtColumnRect &rect, const QwtIntervalSample &sample ) const
+{
+ Q_UNUSED( sample );
+
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtColumnSymbol::NoStyle ) )
+ {
+ d_data->symbol->draw( painter, rect );
+ }
+ else
+ {
+ QRectF r = rect.toRect();
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ r.setLeft( qRound( r.left() ) );
+ r.setRight( qRound( r.right() ) );
+ r.setTop( qRound( r.top() ) );
+ r.setBottom( qRound( r.bottom() ) );
+ }
+
+ QwtPainter::drawRect( painter, r );
+ }
+}
+
+/*!
+ Draw a plain rectangle without pen using the brush() as identifier
+
+ \param painter Painter
+ \param rect Bounding rectangle for the identifier
+*/
+void QwtPlotHistogram::drawLegendIdentifier(
+ QPainter *painter, const QRectF &rect ) const
+{
+ const double dim = qMin( rect.width(), rect.height() );
+
+ QSizeF size( dim, dim );
+
+ QRectF r( 0, 0, size.width(), size.height() );
+ r.moveCenter( rect.center() );
+
+ painter->fillRect( r, d_data->brush );
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_histogram.h b/src/libpcp_qwt/src/qwt_plot_histogram.h
new file mode 100644
index 0000000..3e40c45
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_histogram.h
@@ -0,0 +1,134 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_HISTOGRAM_H
+#define QWT_PLOT_HISTOGRAM_H
+
+#include "qwt_global.h"
+#include "qwt_plot_seriesitem.h"
+#include "qwt_column_symbol.h"
+#include <qcolor.h>
+#include <qvector.h>
+
+class QwtIntervalData;
+class QString;
+class QPolygonF;
+
+/*!
+ \brief QwtPlotHistogram represents a series of samples, where an interval
+ is associated with a value ( \f$y = f([x1,x2])\f$ ).
+
+ The representation depends on the style() and an optional symbol()
+ that is displayed for each interval.
+
+ \note The term "histogram" is used in a different way in the areas of
+ digital image processing and statistics. Wikipedia introduces the
+ terms "image histogram" and "color histogram" to avoid confusions.
+ While "image histograms" can be displayed by a QwtPlotCurve there
+ is no applicable plot item for a "color histogram" yet.
+*/
+
+class QWT_EXPORT QwtPlotHistogram: public QwtPlotSeriesItem<QwtIntervalSample>
+{
+public:
+ /*!
+ Histogram styles.
+ The default style is QwtPlotHistogram::Columns.
+
+ \sa setStyle(), style(), setSymbol(), symbol(), setBaseline()
+ */
+ enum HistogramStyle
+ {
+ /*!
+ Draw an outline around the area, that is build by all intervals
+ using the pen() and fill it with the brush(). The outline style
+ requires, that the intervals are in increasing order and
+ not overlapping.
+ */
+ Outline,
+
+ /*!
+ Draw a column for each interval. When a symbol() has been set
+ the symbol is used otherwise the column is displayed as
+ plain rectangle using pen() and brush().
+ */
+ Columns,
+
+ /*!
+ Draw a simple line using the pen() for each interval.
+ */
+ Lines,
+
+ /*!
+ Styles >= UserStyle are reserved for derived
+ classes that overload drawSeries() with
+ additional application specific ways to display a histogram.
+ */
+ UserStyle = 100
+ };
+
+ explicit QwtPlotHistogram( const QString &title = QString::null );
+ explicit QwtPlotHistogram( const QwtText &title );
+ virtual ~QwtPlotHistogram();
+
+ virtual int rtti() const;
+
+ void setPen( const QPen & );
+ const QPen &pen() const;
+
+ void setBrush( const QBrush & );
+ const QBrush &brush() const;
+
+ void setSamples( const QVector<QwtIntervalSample> & );
+
+ void setBaseline( double reference );
+ double baseline() const;
+
+ void setStyle( HistogramStyle style );
+ HistogramStyle style() const;
+
+ void setSymbol( const QwtColumnSymbol * );
+ const QwtColumnSymbol *symbol() const;
+
+ virtual void drawSeries( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual QRectF boundingRect() const;
+
+ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const;
+
+protected:
+ virtual QwtColumnRect columnRect( const QwtIntervalSample &,
+ const QwtScaleMap &, const QwtScaleMap & ) const;
+
+ virtual void drawColumn( QPainter *, const QwtColumnRect &,
+ const QwtIntervalSample & ) const;
+
+ void drawColumns( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const;
+
+ void drawOutline( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const;
+
+ void drawLines( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ int from, int to ) const;
+
+private:
+ void init();
+ void flushPolygon( QPainter *, double baseLine, QPolygonF & ) const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_intervalcurve.cpp b/src/libpcp_qwt/src/qwt_plot_intervalcurve.cpp
new file mode 100644
index 0000000..1edf0f1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_intervalcurve.cpp
@@ -0,0 +1,548 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_intervalcurve.h"
+#include "qwt_interval_symbol.h"
+#include "qwt_scale_map.h"
+#include "qwt_clipper.h"
+#include "qwt_painter.h"
+
+#include <qpainter.h>
+
+static inline bool qwtIsHSampleInside( const QwtIntervalSample &sample,
+ double xMin, double xMax, double yMin, double yMax )
+{
+ const double y = sample.value;
+ const double x1 = sample.interval.minValue();
+ const double x2 = sample.interval.maxValue();
+
+ const bool isOffScreen = ( y < yMin ) || ( y > yMax )
+ || ( x1 < xMin && x2 < xMin ) || ( x1 > xMax && x2 > xMax );
+
+ return !isOffScreen;
+}
+
+static inline bool qwtIsVSampleInside( const QwtIntervalSample &sample,
+ double xMin, double xMax, double yMin, double yMax )
+{
+ const double x = sample.value;
+ const double y1 = sample.interval.minValue();
+ const double y2 = sample.interval.maxValue();
+
+ const bool isOffScreen = ( x < xMin ) || ( x > xMax )
+ || ( y1 < yMin && y2 < yMin ) || ( y1 > yMax && y2 > yMax );
+
+ return !isOffScreen;
+}
+
+class QwtPlotIntervalCurve::PrivateData
+{
+public:
+ PrivateData():
+ style( QwtPlotIntervalCurve::Tube ),
+ symbol( NULL ),
+ pen( Qt::black ),
+ brush( Qt::white )
+ {
+ paintAttributes = QwtPlotIntervalCurve::ClipPolygons;
+ paintAttributes |= QwtPlotIntervalCurve::ClipSymbol;
+
+ pen.setCapStyle( Qt::FlatCap );
+ }
+
+ ~PrivateData()
+ {
+ delete symbol;
+ }
+
+ QwtPlotIntervalCurve::CurveStyle style;
+ const QwtIntervalSymbol *symbol;
+
+ QPen pen;
+ QBrush brush;
+
+ QwtPlotIntervalCurve::PaintAttributes paintAttributes;
+};
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QwtText &title ):
+ QwtPlotSeriesItem<QwtIntervalSample>( title )
+{
+ init();
+}
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QString &title ):
+ QwtPlotSeriesItem<QwtIntervalSample>( QwtText( title ) )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotIntervalCurve::~QwtPlotIntervalCurve()
+{
+ delete d_data;
+}
+
+//! Initialize internal members
+void QwtPlotIntervalCurve::init()
+{
+ setItemAttribute( QwtPlotItem::Legend, true );
+ setItemAttribute( QwtPlotItem::AutoScale, true );
+
+ d_data = new PrivateData;
+ d_series = new QwtIntervalSeriesData();
+
+ setZ( 19.0 );
+}
+
+//! \return QwtPlotItem::Rtti_PlotIntervalCurve
+int QwtPlotIntervalCurve::rtti() const
+{
+ return QwtPlotIntervalCurve::Rtti_PlotIntervalCurve;
+}
+
+/*!
+ Specify an attribute how to draw the curve
+
+ \param attribute Paint attribute
+ \param on On/Off
+ \sa testPaintAttribute()
+*/
+void QwtPlotIntervalCurve::setPaintAttribute(
+ PaintAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+}
+
+/*!
+ \brief Return the current paint attributes
+ \sa PaintAttribute, setPaintAttribute()
+*/
+bool QwtPlotIntervalCurve::testPaintAttribute(
+ PaintAttribute attribute ) const
+{
+ return ( d_data->paintAttributes & attribute );
+}
+
+/*!
+ Initialize data with an array of samples.
+ \param samples Vector of samples
+*/
+void QwtPlotIntervalCurve::setSamples(
+ const QVector<QwtIntervalSample> &samples )
+{
+ delete d_series;
+ d_series = new QwtIntervalSeriesData( samples );
+ itemChanged();
+}
+
+/*!
+ Set the curve's drawing style
+
+ \param style Curve style
+ \sa CurveStyle, style()
+*/
+void QwtPlotIntervalCurve::setStyle( CurveStyle style )
+{
+ if ( style != d_data->style )
+ {
+ d_data->style = style;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Return the current style
+ \sa setStyle()
+*/
+QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const
+{
+ return d_data->style;
+}
+
+/*!
+ Assign a symbol.
+
+ \param symbol Symbol
+ \sa symbol()
+*/
+void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol *symbol )
+{
+ if ( symbol != d_data->symbol )
+ {
+ delete d_data->symbol;
+ d_data->symbol = symbol;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Current symbol or NULL, when no symbol has been assigned
+ \sa setSymbol()
+*/
+const QwtIntervalSymbol *QwtPlotIntervalCurve::symbol() const
+{
+ return d_data->symbol;
+}
+
+/*!
+ \brief Assign a pen
+ \param pen New pen
+ \sa pen(), brush()
+*/
+void QwtPlotIntervalCurve::setPen( const QPen &pen )
+{
+ if ( pen != d_data->pen )
+ {
+ d_data->pen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Return the pen used to draw the lines
+ \sa setPen(), brush()
+*/
+const QPen& QwtPlotIntervalCurve::pen() const
+{
+ return d_data->pen;
+}
+
+/*!
+ Assign a brush.
+
+ The brush is used to fill the area in Tube style().
+
+ \param brush Brush
+ \sa brush(), pen(), setStyle(), CurveStyle
+*/
+void QwtPlotIntervalCurve::setBrush( const QBrush &brush )
+{
+ if ( brush != d_data->brush )
+ {
+ d_data->brush = brush;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Brush used to fill the area in Tube style()
+ \sa setBrush(), setStyle(), CurveStyle
+*/
+const QBrush& QwtPlotIntervalCurve::brush() const
+{
+ return d_data->brush;
+}
+
+/*!
+ \return Bounding rectangle of all samples.
+ For an empty series the rectangle is invalid.
+*/
+QRectF QwtPlotIntervalCurve::boundingRect() const
+{
+ QRectF rect = QwtPlotSeriesItem<QwtIntervalSample>::boundingRect();
+ if ( rect.isValid() && orientation() == Qt::Vertical )
+ rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() );
+
+ return rect;
+}
+
+/*!
+ Draw a subset of the samples
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ series will be painted to its last sample.
+
+ \sa drawTube(), drawSymbols()
+*/
+void QwtPlotIntervalCurve::drawSeries( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ if ( to < 0 )
+ to = dataSize() - 1;
+
+ if ( from < 0 )
+ from = 0;
+
+ if ( from > to )
+ return;
+
+ switch ( d_data->style )
+ {
+ case Tube:
+ drawTube( painter, xMap, yMap, canvasRect, from, to );
+ break;
+
+ case NoCurve:
+ default:
+ break;
+ }
+
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) )
+ {
+ drawSymbols( painter, *d_data->symbol,
+ xMap, yMap, canvasRect, from, to );
+ }
+}
+
+/*!
+ Draw a tube
+
+ Builds 2 curves from the upper and lower limits of the intervals
+ and draws them with the pen(). The area between the curves is
+ filled with the brush().
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ series will be painted to its last sample.
+
+ \sa drawSeries(), drawSymbols()
+*/
+void QwtPlotIntervalCurve::drawTube( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ painter->save();
+
+ const size_t size = to - from + 1;
+ QPolygonF polygon( 2 * size );
+ QPointF *points = polygon.data();
+
+ for ( uint i = 0; i < size; i++ )
+ {
+ QPointF &minValue = points[i];
+ QPointF &maxValue = points[2 * size - 1 - i];
+
+ const QwtIntervalSample intervalSample = sample( from + i );
+ if ( orientation() == Qt::Vertical )
+ {
+ double x = xMap.transform( intervalSample.value );
+ double y1 = yMap.transform( intervalSample.interval.minValue() );
+ double y2 = yMap.transform( intervalSample.interval.maxValue() );
+ if ( doAlign )
+ {
+ x = qRound( x );
+ y1 = qRound( y1 );
+ y2 = qRound( y2 );
+ }
+
+ minValue.rx() = x;
+ minValue.ry() = y1;
+ maxValue.rx() = x;
+ maxValue.ry() = y2;
+ }
+ else
+ {
+ double y = yMap.transform( intervalSample.value );
+ double x1 = xMap.transform( intervalSample.interval.minValue() );
+ double x2 = xMap.transform( intervalSample.interval.maxValue() );
+ if ( doAlign )
+ {
+ y = qRound( y );
+ x1 = qRound( x1 );
+ x2 = qRound( x2 );
+ }
+
+ minValue.rx() = x1;
+ minValue.ry() = y;
+ maxValue.rx() = x2;
+ maxValue.ry() = y;
+ }
+ }
+
+ if ( d_data->brush.style() != Qt::NoBrush )
+ {
+ painter->setPen( QPen( Qt::NoPen ) );
+ painter->setBrush( d_data->brush );
+
+ if ( d_data->paintAttributes & ClipPolygons )
+ {
+ const qreal m = 1.0;
+ const QPolygonF p = QwtClipper::clipPolygonF(
+ canvasRect.adjusted( -m, -m, m, m ), polygon, true );
+
+ QwtPainter::drawPolygon( painter, p );
+ }
+ else
+ {
+ QwtPainter::drawPolygon( painter, polygon );
+ }
+ }
+
+ if ( d_data->pen.style() != Qt::NoPen )
+ {
+ painter->setPen( d_data->pen );
+ painter->setBrush( Qt::NoBrush );
+
+ if ( d_data->paintAttributes & ClipPolygons )
+ {
+ qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF() );
+ const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw );
+
+ QPolygonF p;
+
+ p.resize( size );
+ qMemCopy( p.data(), points, size * sizeof( QPointF ) );
+ p = QwtClipper::clipPolygonF( clipRect, p );
+ QwtPainter::drawPolyline( painter, p );
+
+ p.resize( size );
+ qMemCopy( p.data(), points + size, size * sizeof( QPointF ) );
+ p = QwtClipper::clipPolygonF( clipRect, p );
+ QwtPainter::drawPolyline( painter, p );
+ }
+ else
+ {
+ QwtPainter::drawPolyline( painter, points, size );
+ QwtPainter::drawPolyline( painter, points + size, size );
+ }
+ }
+
+ painter->restore();
+}
+
+/*!
+ Draw symbols for a subset of the samples
+
+ \param painter Painter
+ \param symbol Interval symbol
+ \param xMap x map
+ \param yMap y map
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted
+
+ \sa setSymbol(), drawSeries(), drawTube()
+*/
+void QwtPlotIntervalCurve::drawSymbols(
+ QPainter *painter, const QwtIntervalSymbol &symbol,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ painter->save();
+
+ QPen pen = symbol.pen();
+ pen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( pen );
+ painter->setBrush( symbol.brush() );
+
+ const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect );
+
+ const double xMin = tr.left();
+ const double xMax = tr.right();
+ const double yMin = tr.top();
+ const double yMax = tr.bottom();
+
+ const bool doClip = d_data->paintAttributes & ClipSymbol;
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QwtIntervalSample s = sample( i );
+
+ if ( orientation() == Qt::Vertical )
+ {
+ if ( !doClip || qwtIsVSampleInside( s, xMin, xMax, yMin, yMax ) )
+ {
+ const double x = xMap.transform( s.value );
+ const double y1 = yMap.transform( s.interval.minValue() );
+ const double y2 = yMap.transform( s.interval.maxValue() );
+
+ symbol.draw( painter, orientation(),
+ QPointF( x, y1 ), QPointF( x, y2 ) );
+ }
+ }
+ else
+ {
+ if ( !doClip || qwtIsHSampleInside( s, xMin, xMax, yMin, yMax ) )
+ {
+ const double y = yMap.transform( s.value );
+ const double x1 = xMap.transform( s.interval.minValue() );
+ const double x2 = xMap.transform( s.interval.maxValue() );
+
+ symbol.draw( painter, orientation(),
+ QPointF( x1, y ), QPointF( x2, y ) );
+ }
+ }
+ }
+
+ painter->restore();
+}
+
+/*!
+ \brief Draw the identifier for the legend
+
+ In case of Tube style() a plain rectangle filled with the brush() is painted.
+ If a symbol is assigned it is painted centered into rect.
+
+ \param painter Painter
+ \param rect Bounding rectangle for the identifier
+*/
+void QwtPlotIntervalCurve::drawLegendIdentifier(
+ QPainter *painter, const QRectF &rect ) const
+{
+ const double dim = qMin( rect.width(), rect.height() );
+
+ QSizeF size( dim, dim );
+
+ QRectF r( 0, 0, size.width(), size.height() );
+ r.moveCenter( rect.center() );
+
+ if ( d_data->style == Tube )
+ {
+ painter->fillRect( r, d_data->brush );
+ }
+
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) )
+ {
+ QPen pen = d_data->symbol->pen();
+ pen.setWidthF( pen.widthF() );
+ pen.setCapStyle( Qt::FlatCap );
+
+ painter->setPen( pen );
+ painter->setBrush( d_data->symbol->brush() );
+
+ if ( orientation() == Qt::Vertical )
+ {
+ d_data->symbol->draw( painter, orientation(),
+ QPointF( r.center().x(), r.top() ),
+ QPointF( r.center().x(), r.bottom() - 1 ) );
+ }
+ else
+ {
+ d_data->symbol->draw( painter, orientation(),
+ QPointF( r.left(), r.center().y() ),
+ QPointF( r.right() - 1, r.center().y() ) );
+ }
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_intervalcurve.h b/src/libpcp_qwt/src/qwt_plot_intervalcurve.h
new file mode 100644
index 0000000..b26586e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_intervalcurve.h
@@ -0,0 +1,130 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_INTERVAL_CURVE_H
+#define QWT_PLOT_INTERVAL_CURVE_H
+
+#include "qwt_global.h"
+#include "qwt_plot_seriesitem.h"
+#include "qwt_series_data.h"
+
+class QwtIntervalSymbol;
+
+/*!
+ \brief QwtPlotIntervalCurve represents a series of samples, where each value
+ is associated with an interval ( \f$[y1,y2] = f(x)\f$ ).
+
+ The representation depends on the style() and an optional symbol()
+ that is displayed for each interval. QwtPlotIntervalCurve might be used
+ to disply error bars or the area between 2 curves.
+*/
+
+class QWT_EXPORT QwtPlotIntervalCurve: public QwtPlotSeriesItem<QwtIntervalSample>
+{
+public:
+ /*!
+ \brief Curve styles.
+ The default setting is QwtPlotIntervalCurve::Tube.
+
+ \sa setStyle(), style()
+ */
+
+ enum CurveStyle
+ {
+ /*!
+ Don't draw a curve. Note: This doesn't affect the symbols.
+ */
+ NoCurve,
+
+ /*!
+ Build 2 curves from the upper and lower limits of the intervals
+ and draw them with the pen(). The area between the curves is
+ filled with the brush().
+ */
+ Tube,
+
+ /*!
+ Styles >= QwtPlotIntervalCurve::UserCurve are reserved for derived
+ classes that overload drawSeries() with
+ additional application specific curve types.
+ */
+ UserCurve = 100
+ };
+
+ /*!
+ Attributes to modify the drawing algorithm.
+ \sa setPaintAttribute(), testPaintAttribute()
+ */
+ enum PaintAttribute
+ {
+ /*!
+ Clip polygons before painting them. In situations, where points
+ are far outside the visible area (f.e when zooming deep) this
+ might be a substantial improvement for the painting performance.
+ */
+ ClipPolygons = 0x01,
+
+ //! Check if a symbol is on the plot canvas before painting it.
+ ClipSymbol = 0x02
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ explicit QwtPlotIntervalCurve( const QString &title = QString::null );
+ explicit QwtPlotIntervalCurve( const QwtText &title );
+
+ virtual ~QwtPlotIntervalCurve();
+
+ virtual int rtti() const;
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ void setSamples( const QVector<QwtIntervalSample> & );
+
+ void setPen( const QPen & );
+ const QPen &pen() const;
+
+ void setBrush( const QBrush & );
+ const QBrush &brush() const;
+
+ void setStyle( CurveStyle style );
+ CurveStyle style() const;
+
+ void setSymbol( const QwtIntervalSymbol * );
+ const QwtIntervalSymbol *symbol() const;
+
+ virtual void drawSeries( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual QRectF boundingRect() const;
+ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const;
+
+protected:
+
+ void init();
+
+ virtual void drawTube( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ virtual void drawSymbols( QPainter *, const QwtIntervalSymbol &,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotIntervalCurve::PaintAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_item.cpp b/src/libpcp_qwt/src/qwt_plot_item.cpp
new file mode 100644
index 0000000..5ed0d40
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_item.cpp
@@ -0,0 +1,542 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_item.h"
+#include "qwt_text.h"
+#include "qwt_plot.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_scale_div.h"
+#include <qpainter.h>
+
+class QwtPlotItem::PrivateData
+{
+public:
+ PrivateData():
+ plot( NULL ),
+ isVisible( true ),
+ attributes( 0 ),
+ renderHints( 0 ),
+ z( 0.0 ),
+ xAxis( QwtPlot::xBottom ),
+ yAxis( QwtPlot::yLeft )
+ {
+ }
+
+ mutable QwtPlot *plot;
+
+ bool isVisible;
+ QwtPlotItem::ItemAttributes attributes;
+ QwtPlotItem::RenderHints renderHints;
+ double z;
+
+ int xAxis;
+ int yAxis;
+
+ QwtText title;
+};
+
+/*!
+ Constructor
+ \param title Title of the item
+*/
+QwtPlotItem::QwtPlotItem( const QwtText &title )
+{
+ d_data = new PrivateData;
+ d_data->title = title;
+}
+
+//! Destroy the QwtPlotItem
+QwtPlotItem::~QwtPlotItem()
+{
+ attach( NULL );
+ delete d_data;
+}
+
+/*!
+ \brief Attach the item to a plot.
+
+ This method will attach a QwtPlotItem to the QwtPlot argument. It will first
+ detach the QwtPlotItem from any plot from a previous call to attach (if
+ necessary). If a NULL argument is passed, it will detach from any QwtPlot it
+ was attached to.
+
+ \param plot Plot widget
+ \sa detach()
+*/
+void QwtPlotItem::attach( QwtPlot *plot )
+{
+ if ( plot == d_data->plot )
+ return;
+
+ // remove the item from the previous plot
+
+ if ( d_data->plot )
+ {
+ if ( d_data->plot->legend() )
+ d_data->plot->legend()->remove( this );
+
+ d_data->plot->attachItem( this, false );
+
+ if ( d_data->plot->autoReplot() )
+ d_data->plot->update();
+ }
+
+ d_data->plot = plot;
+
+ if ( d_data->plot )
+ {
+ // insert the item into the current plot
+
+ d_data->plot->attachItem( this, true );
+ itemChanged();
+ }
+}
+
+/*!
+ \brief This method detaches a QwtPlotItem from any
+ QwtPlot it has been associated with.
+
+ detach() is equivalent to calling attach( NULL )
+ \sa attach()
+*/
+void QwtPlotItem::detach()
+{
+ attach( NULL );
+}
+
+/*!
+ Return rtti for the specific class represented. QwtPlotItem is simply
+ a virtual interface class, and base classes will implement this method
+ with specific rtti values so a user can differentiate them.
+
+ The rtti value is useful for environments, where the
+ runtime type information is disabled and it is not possible
+ to do a dynamic_cast<...>.
+
+ \return rtti value
+ \sa RttiValues
+*/
+int QwtPlotItem::rtti() const
+{
+ return Rtti_PlotItem;
+}
+
+//! Return attached plot
+QwtPlot *QwtPlotItem::plot() const
+{
+ return d_data->plot;
+}
+
+/*!
+ Plot items are painted in increasing z-order.
+
+ \return setZ(), QwtPlotDict::itemList()
+*/
+double QwtPlotItem::z() const
+{
+ return d_data->z;
+}
+
+/*!
+ \brief Set the z value
+
+ Plot items are painted in increasing z-order.
+
+ \param z Z-value
+ \sa z(), QwtPlotDict::itemList()
+*/
+void QwtPlotItem::setZ( double z )
+{
+ if ( d_data->z != z )
+ {
+ if ( d_data->plot ) // update the z order
+ d_data->plot->attachItem( this, false );
+
+ d_data->z = z;
+
+ if ( d_data->plot )
+ d_data->plot->attachItem( this, true );
+
+ itemChanged();
+ }
+}
+
+/*!
+ Set a new title
+
+ \param title Title
+ \sa title()
+*/
+void QwtPlotItem::setTitle( const QString &title )
+{
+ setTitle( QwtText( title ) );
+}
+
+/*!
+ Set a new title
+
+ \param title Title
+ \sa title()
+*/
+void QwtPlotItem::setTitle( const QwtText &title )
+{
+ if ( d_data->title != title )
+ {
+ d_data->title = title;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Title of the item
+ \sa setTitle()
+*/
+const QwtText &QwtPlotItem::title() const
+{
+ return d_data->title;
+}
+
+/*!
+ Toggle an item attribute
+
+ \param attribute Attribute type
+ \param on true/false
+
+ \sa testItemAttribute(), ItemAttribute
+*/
+void QwtPlotItem::setItemAttribute( ItemAttribute attribute, bool on )
+{
+ if ( bool( d_data->attributes & attribute ) != on )
+ {
+ if ( on )
+ d_data->attributes |= attribute;
+ else
+ d_data->attributes &= ~attribute;
+
+ itemChanged();
+ }
+}
+
+/*!
+ Test an item attribute
+
+ \param attribute Attribute type
+ \return true/false
+ \sa setItemAttribute(), ItemAttribute
+*/
+bool QwtPlotItem::testItemAttribute( ItemAttribute attribute ) const
+{
+ return ( d_data->attributes & attribute );
+}
+
+/*!
+ Toggle an render hint
+
+ \param hint Render hint
+ \param on true/false
+
+ \sa testRenderHint(), RenderHint
+*/
+void QwtPlotItem::setRenderHint( RenderHint hint, bool on )
+{
+ if ( ( ( d_data->renderHints & hint ) != 0 ) != on )
+ {
+ if ( on )
+ d_data->renderHints |= hint;
+ else
+ d_data->renderHints &= ~hint;
+
+ itemChanged();
+ }
+}
+
+/*!
+ Test a render hint
+
+ \param hint Render hint
+ \return true/false
+ \sa setRenderHint(), RenderHint
+*/
+bool QwtPlotItem::testRenderHint( RenderHint hint ) const
+{
+ return ( d_data->renderHints & hint );
+}
+
+//! Show the item
+void QwtPlotItem::show()
+{
+ setVisible( true );
+}
+
+//! Hide the item
+void QwtPlotItem::hide()
+{
+ setVisible( false );
+}
+
+/*!
+ Show/Hide the item
+
+ \param on Show if true, otherwise hide
+ \sa isVisible(), show(), hide()
+*/
+void QwtPlotItem::setVisible( bool on )
+{
+ if ( on != d_data->isVisible )
+ {
+ d_data->isVisible = on;
+ itemChanged();
+ }
+}
+
+/*!
+ \return true if visible
+ \sa setVisible(), show(), hide()
+*/
+bool QwtPlotItem::isVisible() const
+{
+ return d_data->isVisible;
+}
+
+/*!
+ Update the legend and call QwtPlot::autoRefresh for the
+ parent plot.
+
+ \sa updateLegend()
+*/
+void QwtPlotItem::itemChanged()
+{
+ if ( d_data->plot )
+ {
+ if ( d_data->plot->legend() )
+ updateLegend( d_data->plot->legend() );
+
+ d_data->plot->autoRefresh();
+ }
+}
+
+/*!
+ Set X and Y axis
+
+ The item will painted according to the coordinates its Axes.
+
+ \param xAxis X Axis
+ \param yAxis Y Axis
+
+ \sa setXAxis(), setYAxis(), xAxis(), yAxis()
+*/
+void QwtPlotItem::setAxes( int xAxis, int yAxis )
+{
+ if ( xAxis == QwtPlot::xBottom || xAxis == QwtPlot::xTop )
+ d_data->xAxis = xAxis;
+
+ if ( yAxis == QwtPlot::yLeft || yAxis == QwtPlot::yRight )
+ d_data->yAxis = yAxis;
+
+ itemChanged();
+}
+
+/*!
+ Set the X axis
+
+ The item will painted according to the coordinates its Axes.
+
+ \param axis X Axis
+ \sa setAxes(), setYAxis(), xAxis()
+*/
+void QwtPlotItem::setXAxis( int axis )
+{
+ if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop )
+ {
+ d_data->xAxis = axis;
+ itemChanged();
+ }
+}
+
+/*!
+ Set the Y axis
+
+ The item will painted according to the coordinates its Axes.
+
+ \param axis Y Axis
+ \sa setAxes(), setXAxis(), yAxis()
+*/
+void QwtPlotItem::setYAxis( int axis )
+{
+ if ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight )
+ {
+ d_data->yAxis = axis;
+ itemChanged();
+ }
+}
+
+//! Return xAxis
+int QwtPlotItem::xAxis() const
+{
+ return d_data->xAxis;
+}
+
+//! Return yAxis
+int QwtPlotItem::yAxis() const
+{
+ return d_data->yAxis;
+}
+
+/*!
+ \return An invalid bounding rect: QRectF(1.0, 1.0, -2.0, -2.0)
+*/
+QRectF QwtPlotItem::boundingRect() const
+{
+ return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid
+}
+
+/*!
+ \brief Allocate the widget that represents the item on the legend
+
+ The default implementation returns a QwtLegendItem(), but an item
+ could be represented by any type of widget,
+ by overloading legendItem() and updateLegend().
+
+ \return QwtLegendItem()
+ \sa updateLegend() QwtLegend()
+*/
+QWidget *QwtPlotItem::legendItem() const
+{
+ QwtLegendItem *item = new QwtLegendItem;
+ if ( d_data->plot )
+ {
+ QObject::connect( item, SIGNAL( clicked() ),
+ d_data->plot, SLOT( legendItemClicked() ) );
+ QObject::connect( item, SIGNAL( checked( bool ) ),
+ d_data->plot, SLOT( legendItemChecked( bool ) ) );
+ }
+ return item;
+}
+
+/*!
+ \brief Update the widget that represents the item on the legend
+
+ updateLegend() is called from itemChanged() to adopt the widget
+ representing the item on the legend to its new configuration.
+
+ The default implementation updates a QwtLegendItem(),
+ but an item could be represented by any type of widget,
+ by overloading legendItem() and updateLegend().
+
+ \param legend Legend
+
+ \sa legendItem(), itemChanged(), QwtLegend()
+*/
+void QwtPlotItem::updateLegend( QwtLegend *legend ) const
+{
+ if ( legend == NULL )
+ return;
+
+ QWidget *lgdItem = legend->find( this );
+ if ( testItemAttribute( QwtPlotItem::Legend ) )
+ {
+ if ( lgdItem == NULL )
+ {
+ lgdItem = legendItem();
+ if ( lgdItem )
+ legend->insert( this, lgdItem );
+ }
+
+ QwtLegendItem *label = qobject_cast<QwtLegendItem *>( lgdItem );
+ if ( label )
+ {
+ // paint the identifier
+ const QSize sz = label->identifierSize();
+
+ QPixmap identifier( sz.width(), sz.height() );
+ identifier.fill( Qt::transparent );
+
+ QPainter painter( &identifier );
+ painter.setRenderHint( QPainter::Antialiasing,
+ testRenderHint( QwtPlotItem::RenderAntialiased ) );
+ drawLegendIdentifier( &painter,
+ QRect( 0, 0, sz.width(), sz.height() ) );
+ painter.end();
+
+ const bool doUpdate = label->updatesEnabled();
+ if ( doUpdate )
+ label->setUpdatesEnabled( false );
+
+ label->setText( title() );
+ label->setIdentifier( identifier );
+ label->setItemMode( legend->itemMode() );
+
+ if ( doUpdate )
+ label->setUpdatesEnabled( true );
+
+ label->update();
+ }
+ }
+ else
+ {
+ if ( lgdItem )
+ {
+ lgdItem->hide();
+ lgdItem->deleteLater();
+ }
+ }
+}
+
+/*!
+ \brief Update the item to changes of the axes scale division
+
+ Update the item, when the axes of plot have changed.
+ The default implementation does nothing, but items that depend
+ on the scale division (like QwtPlotGrid()) have to reimplement
+ updateScaleDiv()
+
+ \param xScaleDiv Scale division of the x-axis
+ \param yScaleDiv Scale division of the y-axis
+
+ \sa QwtPlot::updateAxes()
+*/
+void QwtPlotItem::updateScaleDiv( const QwtScaleDiv &xScaleDiv,
+ const QwtScaleDiv &yScaleDiv )
+{
+ Q_UNUSED( xScaleDiv );
+ Q_UNUSED( yScaleDiv );
+}
+
+/*!
+ \brief Calculate the bounding scale rect of 2 maps
+
+ \param xMap X map
+ \param yMap Y map
+
+ \return Bounding scale rect of the scale maps, not normalized
+*/
+QRectF QwtPlotItem::scaleRect( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap ) const
+{
+ return QRectF( xMap.s1(), yMap.s1(),
+ xMap.sDist(), yMap.sDist() );
+}
+
+/*!
+ \brief Calculate the bounding paint rect of 2 maps
+
+ \param xMap X map
+ \param yMap Y map
+
+ \return Bounding paint rect of the scale maps, not normalized
+*/
+QRectF QwtPlotItem::paintRect( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap ) const
+{
+ const QRectF rect( xMap.p1(), yMap.p1(),
+ xMap.pDist(), yMap.pDist() );
+
+ return rect;
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_item.h b/src/libpcp_qwt/src/qwt_plot_item.h
new file mode 100644
index 0000000..5000fff
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_item.h
@@ -0,0 +1,214 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_ITEM_H
+#define QWT_PLOT_ITEM_H
+
+#include "qwt_global.h"
+#include "qwt_legend_itemmanager.h"
+#include "qwt_text.h"
+#include <qrect.h>
+
+class QString;
+class QPainter;
+class QWidget;
+class QwtPlot;
+class QwtLegend;
+class QwtScaleMap;
+class QwtScaleDiv;
+
+/*!
+ \brief Base class for items on the plot canvas
+
+ A plot item is "something", that can be painted on the plot canvas,
+ or only affects the scales of the plot widget. They can be categorized as:
+
+ - Representator\n
+ A "Representator" is an item that represents some sort of data
+ on the plot canvas. The different representator classes are organized
+ according to the characteristics of the data:
+ - QwtPlotMarker
+ Represents a point or a horizontal/vertical coordinate
+ - QwtPlotCurve
+ Represents a series of points
+ - QwtPlotSpectrogram ( QwtPlotRasterItem )
+ Represents raster data
+ - ...
+
+ - Decorators\n
+ A "Decorator" is an item, that displays additional information, that
+ is not related to any data:
+ - QwtPlotGrid
+ - QwtPlotScaleItem
+ - QwtPlotSvgItem
+ - ...
+
+ Depending on the QwtPlotItem::ItemAttribute flags, an item is included
+ into autoscaling or has an entry on the legnd.
+
+ Before misusing the existing item classes it might be better to
+ implement a new type of plot item
+ ( don't implement a watermark as spectrogram ).
+ Deriving a new type of QwtPlotItem primarily means to implement
+ the YourPlotItem::draw() method.
+
+ \sa The cpuplot example shows the implementation of additional plot items.
+*/
+
+class QWT_EXPORT QwtPlotItem: public QwtLegendItemManager
+{
+public:
+ /*!
+ \brief Runtime type information
+
+ RttiValues is used to cast plot items, without
+ having to enable runtime type information of the compiler.
+ */
+ enum RttiValues
+ {
+ //! Unspecific value, that can be used, when it doesn't matter
+ Rtti_PlotItem = 0,
+
+ //! For QwtPlotGrid
+ Rtti_PlotGrid,
+
+ //! For QwtPlotScaleItem
+ Rtti_PlotScale,
+
+ //! For QwtPlotMarker
+ Rtti_PlotMarker,
+
+ //! For QwtPlotCurve
+ Rtti_PlotCurve,
+
+ //! For QwtPlotSpectroCurve
+ Rtti_PlotSpectroCurve,
+
+ //! For QwtPlotIntervalCurve
+ Rtti_PlotIntervalCurve,
+
+ //! For QwtPlotHistogram
+ Rtti_PlotHistogram,
+
+ //! For QwtPlotSpectrogram
+ Rtti_PlotSpectrogram,
+
+ //! For QwtPlotSvgItem
+ Rtti_PlotSVG,
+
+ /*!
+ Values >= Rtti_PlotUserItem are reserved for plot items
+ not implemented in the Qwt library.
+ */
+ Rtti_PlotUserItem = 1000
+ };
+
+ /*!
+ Plot Item Attributes
+ \sa setItemAttribute(), testItemAttribute()
+ */
+ enum ItemAttribute
+ {
+ //! The item is represented on the legend.
+ Legend = 0x01,
+
+ /*!
+ The boundingRect() of the item is included in the
+ autoscaling calculation.
+ */
+ AutoScale = 0x02
+ };
+
+ //! Plot Item Attributes
+ typedef QFlags<ItemAttribute> ItemAttributes;
+
+ //! Render hints
+ enum RenderHint
+ {
+ //! Enable antialiasing
+ RenderAntialiased = 1
+ };
+
+ //! Render hints
+ typedef QFlags<RenderHint> RenderHints;
+
+ explicit QwtPlotItem( const QwtText &title = QwtText() );
+ virtual ~QwtPlotItem();
+
+ void attach( QwtPlot *plot );
+ void detach();
+
+ QwtPlot *plot() const;
+
+ void setTitle( const QString &title );
+ void setTitle( const QwtText &title );
+ const QwtText &title() const;
+
+ virtual int rtti() const;
+
+ void setItemAttribute( ItemAttribute, bool on = true );
+ bool testItemAttribute( ItemAttribute ) const;
+
+ void setRenderHint( RenderHint, bool on = true );
+ bool testRenderHint( RenderHint ) const;
+
+ double z() const;
+ void setZ( double z );
+
+ void show();
+ void hide();
+ virtual void setVisible( bool );
+ bool isVisible () const;
+
+ void setAxes( int xAxis, int yAxis );
+
+ void setXAxis( int axis );
+ int xAxis() const;
+
+ void setYAxis( int axis );
+ int yAxis() const;
+
+ virtual void itemChanged();
+
+ /*!
+ \brief Draw the item
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas in painter coordinates
+ */
+ virtual void draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const = 0;
+
+ virtual QRectF boundingRect() const;
+
+ virtual void updateLegend( QwtLegend * ) const;
+ virtual void updateScaleDiv(
+ const QwtScaleDiv&, const QwtScaleDiv& );
+
+ virtual QWidget *legendItem() const;
+
+ QRectF scaleRect( const QwtScaleMap &, const QwtScaleMap & ) const;
+ QRectF paintRect( const QwtScaleMap &, const QwtScaleMap & ) const;
+
+private:
+ // Disabled copy constructor and operator=
+ QwtPlotItem( const QwtPlotItem & );
+ QwtPlotItem &operator=( const QwtPlotItem & );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemAttributes )
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::RenderHints )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_layout.cpp b/src/libpcp_qwt/src/qwt_plot_layout.cpp
new file mode 100644
index 0000000..0e55a88
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_layout.cpp
@@ -0,0 +1,1267 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_layout.h"
+#include "qwt_text.h"
+#include "qwt_text_label.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_widget.h"
+#include "qwt_legend.h"
+#include <qscrollbar.h>
+#include <qmath.h>
+
+class QwtPlotLayout::LayoutData
+{
+public:
+ void init( const QwtPlot *, const QRectF &rect );
+
+ struct t_legendData
+ {
+ int frameWidth;
+ int vScrollBarWidth;
+ int hScrollBarHeight;
+ QSize hint;
+ } legend;
+
+ struct t_titleData
+ {
+ QwtText text;
+ int frameWidth;
+ } title;
+
+ struct t_scaleData
+ {
+ bool isEnabled;
+ const QwtScaleWidget *scaleWidget;
+ QFont scaleFont;
+ int start;
+ int end;
+ int baseLineOffset;
+ double tickOffset;
+ int dimWithoutTitle;
+ } scale[QwtPlot::axisCnt];
+
+ struct t_canvasData
+ {
+ int frameWidth;
+ } canvas;
+};
+
+/*
+ Extract all layout relevant data from the plot components
+*/
+
+void QwtPlotLayout::LayoutData::init( const QwtPlot *plot, const QRectF &rect )
+{
+ // legend
+
+ if ( plot->plotLayout()->legendPosition() != QwtPlot::ExternalLegend
+ && plot->legend() )
+ {
+ legend.frameWidth = plot->legend()->frameWidth();
+ legend.vScrollBarWidth =
+ plot->legend()->verticalScrollBar()->sizeHint().width();
+ legend.hScrollBarHeight =
+ plot->legend()->horizontalScrollBar()->sizeHint().height();
+
+ const QSize hint = plot->legend()->sizeHint();
+
+ int w = qMin( hint.width(), qFloor( rect.width() ) );
+ int h = plot->legend()->heightForWidth( w );
+ if ( h == 0 )
+ h = hint.height();
+
+ if ( h > rect.height() )
+ w += legend.vScrollBarWidth;
+
+ legend.hint = QSize( w, h );
+ }
+
+ // title
+
+ title.frameWidth = 0;
+ title.text = QwtText();
+
+ if ( plot->titleLabel() )
+ {
+ const QwtTextLabel *label = plot->titleLabel();
+ title.text = label->text();
+ if ( !( title.text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) )
+ title.text.setFont( label->font() );
+
+ title.frameWidth = plot->titleLabel()->frameWidth();
+ }
+
+ // scales
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( plot->axisEnabled( axis ) )
+ {
+ const QwtScaleWidget *scaleWidget = plot->axisWidget( axis );
+
+ scale[axis].isEnabled = true;
+
+ scale[axis].scaleWidget = scaleWidget;
+
+ scale[axis].scaleFont = scaleWidget->font();
+
+ scale[axis].start = scaleWidget->startBorderDist();
+ scale[axis].end = scaleWidget->endBorderDist();
+
+ scale[axis].baseLineOffset = scaleWidget->margin();
+ scale[axis].tickOffset = scaleWidget->margin();
+ if ( scaleWidget->scaleDraw()->hasComponent(
+ QwtAbstractScaleDraw::Ticks ) )
+ {
+ scale[axis].tickOffset +=
+ scaleWidget->scaleDraw()->maxTickLength();
+ }
+
+ scale[axis].dimWithoutTitle = scaleWidget->dimForLength(
+ QWIDGETSIZE_MAX, scale[axis].scaleFont );
+
+ if ( !scaleWidget->title().isEmpty() )
+ {
+ scale[axis].dimWithoutTitle -=
+ scaleWidget->titleHeightForWidth( QWIDGETSIZE_MAX );
+ }
+ }
+ else
+ {
+ scale[axis].isEnabled = false;
+ scale[axis].start = 0;
+ scale[axis].end = 0;
+ scale[axis].baseLineOffset = 0;
+ scale[axis].tickOffset = 0.0;
+ scale[axis].dimWithoutTitle = 0;
+ }
+ }
+
+ // canvas
+
+ canvas.frameWidth = plot->canvas()->frameWidth();
+}
+
+class QwtPlotLayout::PrivateData
+{
+public:
+ PrivateData():
+ spacing( 5 ),
+ alignCanvasToScales( false )
+ {
+ }
+
+ QRectF titleRect;
+ QRectF legendRect;
+ QRectF scaleRect[QwtPlot::axisCnt];
+ QRectF canvasRect;
+
+ QwtPlotLayout::LayoutData layoutData;
+
+ QwtPlot::LegendPosition legendPos;
+ double legendRatio;
+ unsigned int spacing;
+ unsigned int fixedOffset[QwtPlot::axisCnt];
+ unsigned int canvasMargin[QwtPlot::axisCnt];
+ bool alignCanvasToScales;
+};
+
+/*!
+ \brief Constructor
+ */
+
+QwtPlotLayout::QwtPlotLayout()
+{
+ d_data = new PrivateData;
+
+ setLegendPosition( QwtPlot::BottomLegend );
+ setFixedAxisOffset(0);
+ setCanvasMargin( 4 );
+
+ invalidate();
+}
+
+//! Destructor
+QwtPlotLayout::~QwtPlotLayout()
+{
+ delete d_data;
+}
+
+/*!
+ Change a margin of the canvas. The margin is the space
+ above/below the scale ticks. A negative margin will
+ be set to -1, excluding the borders of the scales.
+
+ \param margin New margin
+ \param axis One of QwtPlot::Axis. Specifies where the position of the margin.
+ -1 means margin at all borders.
+ \sa canvasMargin()
+
+ \warning The margin will have no effect when alignCanvasToScales is true
+*/
+
+void QwtPlotLayout::setCanvasMargin( int margin, int axis )
+{
+ if ( margin < -1 )
+ margin = -1;
+
+ if ( axis == -1 )
+ {
+ for ( axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ d_data->canvasMargin[axis] = margin;
+ }
+ else if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->canvasMargin[axis] = margin;
+}
+
+/*!
+ \return Margin around the scale tick borders
+ \sa setCanvasMargin()
+*/
+int QwtPlotLayout::canvasMargin( int axis ) const
+{
+ if ( axis < 0 || axis >= QwtPlot::axisCnt )
+ return 0;
+
+ return d_data->canvasMargin[axis];
+}
+
+/*!
+ * Set a fixed offset for the given axis scale. The offset is the space
+ * between an outer edge of the plot widget and the scale backbone.
+ *
+ * \param offset New offset
+ * \param axis One of QwtPlot::Axis. Specifies which axis to make fixed offset.
+ * -1 means margin at all borders.
+ * \sa fixedAxisOffset()
+ * */
+void QwtPlotLayout::setFixedAxisOffset(int offset, int axis)
+{
+ if ( offset < 0 )
+ offset = 0;
+
+ if ( axis == -1 )
+ {
+ for (axis = 0; axis < QwtPlot::axisCnt; axis++)
+ d_data->fixedOffset[axis] = offset;
+ }
+ else if ( axis >= 0 || axis < QwtPlot::axisCnt )
+ d_data->fixedOffset[axis] = offset;
+}
+
+/*!
+ * \return Fixed offset, if any, for a given axis scale
+ * \sa setFixedAxisOffset()
+ * */
+int QwtPlotLayout::fixedAxisOffset(int axis) const
+{
+ if ( axis < 0 || axis >= QwtPlot::axisCnt )
+ return 0;
+
+ return d_data->fixedOffset[axis];
+}
+
+/*!
+ Change the align-canvas-to-axis-scales setting. The canvas may:
+ - extend beyond the axis scale ends to maximize its size,
+ - align with the axis scale ends to control its size.
+
+ \param alignCanvasToScales New align-canvas-to-axis-scales setting
+
+ \sa setCanvasMargin()
+ \note In this context the term 'scale' means the backbone of a scale.
+ \warning In case of alignCanvasToScales == true canvasMargin will have
+ no effect
+*/
+void QwtPlotLayout::setAlignCanvasToScales( bool alignCanvasToScales )
+{
+ d_data->alignCanvasToScales = alignCanvasToScales;
+}
+
+/*!
+ Return the align-canvas-to-axis-scales setting. The canvas may:
+ - extend beyond the axis scale ends to maximize its size
+ - align with the axis scale ends to control its size.
+
+ \return align-canvas-to-axis-scales setting
+ \sa setAlignCanvasToScales, setCanvasMargin()
+ \note In this context the term 'scale' means the backbone of a scale.
+*/
+bool QwtPlotLayout::alignCanvasToScales() const
+{
+ return d_data->alignCanvasToScales;
+}
+
+/*!
+ Change the spacing of the plot. The spacing is the distance
+ between the plot components.
+
+ \param spacing new spacing
+ \sa setMargin(), spacing()
+*/
+void QwtPlotLayout::setSpacing( int spacing )
+{
+ d_data->spacing = qMax( 0, spacing );
+}
+
+/*!
+ \return spacing
+ \sa margin(), setSpacing()
+*/
+int QwtPlotLayout::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ \brief Specify the position of the legend
+ \param pos The legend's position.
+ \param ratio Ratio between legend and the bounding rect
+ of title, canvas and axes. The legend will be shrinked
+ if it would need more space than the given ratio.
+ The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0
+ it will be reset to the default ratio.
+ The default vertical/horizontal ratio is 0.33/0.5.
+
+ \sa QwtPlot::setLegendPosition()
+*/
+
+void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio )
+{
+ if ( ratio > 1.0 )
+ ratio = 1.0;
+
+ switch ( pos )
+ {
+ case QwtPlot::TopLegend:
+ case QwtPlot::BottomLegend:
+ if ( ratio <= 0.0 )
+ ratio = 0.33;
+ d_data->legendRatio = ratio;
+ d_data->legendPos = pos;
+ break;
+ case QwtPlot::LeftLegend:
+ case QwtPlot::RightLegend:
+ if ( ratio <= 0.0 )
+ ratio = 0.5;
+ d_data->legendRatio = ratio;
+ d_data->legendPos = pos;
+ break;
+ case QwtPlot::ExternalLegend:
+ d_data->legendRatio = ratio; // meaningless
+ d_data->legendPos = pos;
+ default:
+ break;
+ }
+}
+
+/*!
+ \brief Specify the position of the legend
+ \param pos The legend's position. Valid values are
+ \c QwtPlot::LeftLegend, \c QwtPlot::RightLegend,
+ \c QwtPlot::TopLegend, \c QwtPlot::BottomLegend.
+
+ \sa QwtPlot::setLegendPosition()
+*/
+void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos )
+{
+ setLegendPosition( pos, 0.0 );
+}
+
+/*!
+ \return Position of the legend
+ \sa setLegendPosition(), QwtPlot::setLegendPosition(),
+ QwtPlot::legendPosition()
+*/
+QwtPlot::LegendPosition QwtPlotLayout::legendPosition() const
+{
+ return d_data->legendPos;
+}
+
+/*!
+ Specify the relative size of the legend in the plot
+ \param ratio Ratio between legend and the bounding rect
+ of title, canvas and axes. The legend will be shrinked
+ if it would need more space than the given ratio.
+ The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0
+ it will be reset to the default ratio.
+ The default vertical/horizontal ratio is 0.33/0.5.
+*/
+void QwtPlotLayout::setLegendRatio( double ratio )
+{
+ setLegendPosition( legendPosition(), ratio );
+}
+
+/*!
+ \return The relative size of the legend in the plot.
+ \sa setLegendPosition()
+*/
+double QwtPlotLayout::legendRatio() const
+{
+ return d_data->legendRatio;
+}
+
+/*!
+ \return Geometry for the title
+ \sa activate(), invalidate()
+*/
+const QRectF &QwtPlotLayout::titleRect() const
+{
+ return d_data->titleRect;
+}
+
+/*!
+ \return Geometry for the legend
+ \sa activate(), invalidate()
+*/
+const QRectF &QwtPlotLayout::legendRect() const
+{
+ return d_data->legendRect;
+}
+
+/*!
+ \param axis Axis index
+ \return Geometry for the scale
+ \sa activate(), invalidate()
+*/
+const QRectF &QwtPlotLayout::scaleRect( int axis ) const
+{
+ if ( axis < 0 || axis >= QwtPlot::axisCnt )
+ {
+ static QRectF dummyRect;
+ return dummyRect;
+ }
+ return d_data->scaleRect[axis];
+}
+
+/*!
+ \return Geometry for the canvas
+ \sa activate(), invalidate()
+*/
+const QRectF &QwtPlotLayout::canvasRect() const
+{
+ return d_data->canvasRect;
+}
+
+/*!
+ Invalidate the geometry of all components.
+ \sa activate()
+*/
+void QwtPlotLayout::invalidate()
+{
+ d_data->titleRect = d_data->legendRect = d_data->canvasRect = QRect();
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ d_data->scaleRect[axis] = QRect();
+}
+
+/*!
+ \brief Return a minimum size hint
+ \sa QwtPlot::minimumSizeHint()
+*/
+
+QSize QwtPlotLayout::minimumSizeHint( const QwtPlot *plot ) const
+{
+ class ScaleData
+ {
+ public:
+ ScaleData()
+ {
+ w = h = minLeft = minRight = tickOffset = 0;
+ }
+
+ int w;
+ int h;
+ int minLeft;
+ int minRight;
+ int tickOffset;
+ } scaleData[QwtPlot::axisCnt];
+
+ int canvasBorder[QwtPlot::axisCnt];
+
+ int axis;
+ for ( axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( plot->axisEnabled( axis ) )
+ {
+ const QwtScaleWidget *scl = plot->axisWidget( axis );
+ ScaleData &sd = scaleData[axis];
+
+ const QSize hint = scl->minimumSizeHint();
+ sd.w = hint.width();
+ sd.h = hint.height();
+ scl->getBorderDistHint( sd.minLeft, sd.minRight );
+ sd.tickOffset = scl->margin();
+ if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ sd.tickOffset += qCeil( scl->scaleDraw()->maxTickLength() );
+ }
+
+ canvasBorder[axis] = plot->canvas()->frameWidth() +
+ d_data->canvasMargin[axis] + 1;
+
+ }
+
+
+ for ( axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ ScaleData &sd = scaleData[axis];
+ if ( sd.w && ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) )
+ {
+ if ( ( sd.minLeft > canvasBorder[QwtPlot::yLeft] )
+ && scaleData[QwtPlot::yLeft].w )
+ {
+ int shiftLeft = sd.minLeft - canvasBorder[QwtPlot::yLeft];
+ if ( shiftLeft > scaleData[QwtPlot::yLeft].w )
+ shiftLeft = scaleData[QwtPlot::yLeft].w;
+
+ sd.w -= shiftLeft;
+ }
+ if ( ( sd.minRight > canvasBorder[QwtPlot::yRight] )
+ && scaleData[QwtPlot::yRight].w )
+ {
+ int shiftRight = sd.minRight - canvasBorder[QwtPlot::yRight];
+ if ( shiftRight > scaleData[QwtPlot::yRight].w )
+ shiftRight = scaleData[QwtPlot::yRight].w;
+
+ sd.w -= shiftRight;
+ }
+ }
+
+ if ( sd.h && ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight ) )
+ {
+ if ( ( sd.minLeft > canvasBorder[QwtPlot::xBottom] ) &&
+ scaleData[QwtPlot::xBottom].h )
+ {
+ int shiftBottom = sd.minLeft - canvasBorder[QwtPlot::xBottom];
+ if ( shiftBottom > scaleData[QwtPlot::xBottom].tickOffset )
+ shiftBottom = scaleData[QwtPlot::xBottom].tickOffset;
+
+ sd.h -= shiftBottom;
+ }
+ if ( ( sd.minLeft > canvasBorder[QwtPlot::xTop] ) &&
+ scaleData[QwtPlot::xTop].h )
+ {
+ int shiftTop = sd.minRight - canvasBorder[QwtPlot::xTop];
+ if ( shiftTop > scaleData[QwtPlot::xTop].tickOffset )
+ shiftTop = scaleData[QwtPlot::xTop].tickOffset;
+
+ sd.h -= shiftTop;
+ }
+ }
+ }
+
+ const QwtPlotCanvas *canvas = plot->canvas();
+ const QSize minCanvasSize = canvas->minimumSize();
+
+ int w = scaleData[QwtPlot::yLeft].w + scaleData[QwtPlot::yRight].w;
+ int cw = qMax( scaleData[QwtPlot::xBottom].w, scaleData[QwtPlot::xTop].w )
+ + 2 * ( canvas->frameWidth() + 1 );
+ w += qMax( cw, minCanvasSize.width() );
+
+ int h = scaleData[QwtPlot::xBottom].h + scaleData[QwtPlot::xTop].h;
+ int ch = qMax( scaleData[QwtPlot::yLeft].h, scaleData[QwtPlot::yRight].h )
+ + 2 * ( canvas->frameWidth() + 1 );
+ h += qMax( ch, minCanvasSize.height() );
+
+ const QwtTextLabel *title = plot->titleLabel();
+ if ( title && !title->text().isEmpty() )
+ {
+ // If only QwtPlot::yLeft or QwtPlot::yRight is showing,
+ // we center on the plot canvas.
+ const bool centerOnCanvas = !( plot->axisEnabled( QwtPlot::yLeft )
+ && plot->axisEnabled( QwtPlot::yRight ) );
+
+ int titleW = w;
+ if ( centerOnCanvas )
+ {
+ titleW -= scaleData[QwtPlot::yLeft].w
+ + scaleData[QwtPlot::yRight].w;
+ }
+
+ int titleH = title->heightForWidth( titleW );
+ if ( titleH > titleW ) // Compensate for a long title
+ {
+ w = titleW = titleH;
+ if ( centerOnCanvas )
+ {
+ w += scaleData[QwtPlot::yLeft].w
+ + scaleData[QwtPlot::yRight].w;
+ }
+
+ titleH = title->heightForWidth( titleW );
+ }
+ h += titleH + d_data->spacing;
+ }
+
+ // Compute the legend contribution
+
+ const QwtLegend *legend = plot->legend();
+ if ( d_data->legendPos != QwtPlot::ExternalLegend
+ && legend && !legend->isEmpty() )
+ {
+ if ( d_data->legendPos == QwtPlot::LeftLegend
+ || d_data->legendPos == QwtPlot::RightLegend )
+ {
+ int legendW = legend->sizeHint().width();
+ int legendH = legend->heightForWidth( legendW );
+
+ if ( legend->frameWidth() > 0 )
+ w += d_data->spacing;
+
+ if ( legendH > h )
+ legendW += legend->verticalScrollBar()->sizeHint().width();
+
+ if ( d_data->legendRatio < 1.0 )
+ legendW = qMin( legendW, int( w / ( 1.0 - d_data->legendRatio ) ) );
+
+ w += legendW + d_data->spacing;
+ }
+ else // QwtPlot::Top, QwtPlot::Bottom
+ {
+ int legendW = qMin( legend->sizeHint().width(), w );
+ int legendH = legend->heightForWidth( legendW );
+
+ if ( legend->frameWidth() > 0 )
+ h += d_data->spacing;
+
+ if ( d_data->legendRatio < 1.0 )
+ legendH = qMin( legendH, int( h / ( 1.0 - d_data->legendRatio ) ) );
+
+ h += legendH + d_data->spacing;
+ }
+ }
+
+ return QSize( w, h );
+}
+
+/*!
+ Find the geometry for the legend
+ \param options Options how to layout the legend
+ \param rect Rectangle where to place the legend
+ \return Geometry for the legend
+ \sa Options
+*/
+
+QRectF QwtPlotLayout::layoutLegend( Options options,
+ const QRectF &rect ) const
+{
+ const QSize hint( d_data->layoutData.legend.hint );
+
+ int dim;
+ if ( d_data->legendPos == QwtPlot::LeftLegend
+ || d_data->legendPos == QwtPlot::RightLegend )
+ {
+ // We don't allow vertical legends to take more than
+ // half of the available space.
+
+ dim = qMin( hint.width(), int( rect.width() * d_data->legendRatio ) );
+
+ if ( !( options & IgnoreScrollbars ) )
+ {
+ if ( hint.height() > rect.height() )
+ {
+ // The legend will need additional
+ // space for the vertical scrollbar.
+
+ dim += d_data->layoutData.legend.vScrollBarWidth;
+ }
+ }
+ }
+ else
+ {
+ dim = qMin( hint.height(), int( rect.height() * d_data->legendRatio ) );
+ dim = qMax( dim, d_data->layoutData.legend.hScrollBarHeight );
+ }
+
+ QRectF legendRect = rect;
+ switch ( d_data->legendPos )
+ {
+ case QwtPlot::LeftLegend:
+ legendRect.setWidth( dim );
+ break;
+ case QwtPlot::RightLegend:
+ legendRect.setX( rect.right() - dim );
+ legendRect.setWidth( dim );
+ break;
+ case QwtPlot::TopLegend:
+ legendRect.setHeight( dim );
+ break;
+ case QwtPlot::BottomLegend:
+ legendRect.setY( rect.bottom() - dim );
+ legendRect.setHeight( dim );
+ break;
+ case QwtPlot::ExternalLegend:
+ break;
+ }
+
+ return legendRect;
+}
+
+/*!
+ Align the legend to the canvas
+ \param canvasRect Geometry of the canvas
+ \param legendRect Maximum geometry for the legend
+ \return Geometry for the aligned legend
+*/
+QRectF QwtPlotLayout::alignLegend( const QRectF &canvasRect,
+ const QRectF &legendRect ) const
+{
+ QRectF alignedRect = legendRect;
+
+ if ( d_data->legendPos == QwtPlot::BottomLegend
+ || d_data->legendPos == QwtPlot::TopLegend )
+ {
+ if ( d_data->layoutData.legend.hint.width() < canvasRect.width() )
+ {
+ alignedRect.setX( canvasRect.x() );
+ alignedRect.setWidth( canvasRect.width() );
+ }
+ }
+ else
+ {
+ if ( d_data->layoutData.legend.hint.height() < canvasRect.height() )
+ {
+ alignedRect.setY( canvasRect.y() );
+ alignedRect.setHeight( canvasRect.height() );
+ }
+ }
+
+ return alignedRect;
+}
+
+/*!
+ Expand all line breaks in text labels, and calculate the height
+ of their widgets in orientation of the text.
+
+ \param options Options how to layout the legend
+ \param rect Bounding rect for title, axes and canvas.
+ \param dimTitle Expanded height of the title widget
+ \param dimAxis Expanded heights of the axis in axis orientation.
+
+ \sa Options
+*/
+void QwtPlotLayout::expandLineBreaks( int options, const QRectF &rect,
+ int &dimTitle, int dimAxis[QwtPlot::axisCnt] ) const
+{
+ dimTitle = 0;
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ dimAxis[axis] = 0;
+
+ int backboneOffset[QwtPlot::axisCnt];
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ backboneOffset[axis] = 0;
+ if ( !d_data->alignCanvasToScales )
+ backboneOffset[axis] += d_data->canvasMargin[axis];
+ if ( !( options & IgnoreFrames ) )
+ backboneOffset[axis] += d_data->layoutData.canvas.frameWidth;
+ }
+
+ bool done = false;
+ while ( !done )
+ {
+ done = true;
+
+ // the size for the 4 axis depend on each other. Expanding
+ // the height of a horizontal axis will shrink the height
+ // for the vertical axis, shrinking the height of a vertical
+ // axis will result in a line break what will expand the
+ // width and results in shrinking the width of a horizontal
+ // axis what might result in a line break of a horizontal
+ // axis ... . So we loop as long until no size changes.
+
+ if ( !d_data->layoutData.title.text.isEmpty() )
+ {
+ double w = rect.width();
+
+ if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled
+ != d_data->layoutData.scale[QwtPlot::yRight].isEnabled )
+ {
+ // center to the canvas
+ w -= dimAxis[QwtPlot::yLeft] + dimAxis[QwtPlot::yRight];
+ }
+
+ int d = qCeil( d_data->layoutData.title.text.heightForWidth( w ) );
+ if ( !( options & IgnoreFrames ) )
+ d += 2 * d_data->layoutData.title.frameWidth;
+
+ if ( d > dimTitle )
+ {
+ dimTitle = d;
+ done = false;
+ }
+ }
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ const struct LayoutData::t_scaleData &scaleData =
+ d_data->layoutData.scale[axis];
+
+ if ( scaleData.isEnabled )
+ {
+ double length;
+ if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom )
+ {
+ length = rect.width() - dimAxis[QwtPlot::yLeft]
+ - dimAxis[QwtPlot::yRight];
+ length -= scaleData.start + scaleData.end;
+
+ if ( dimAxis[QwtPlot::yRight] > 0 )
+ length -= 1;
+
+ length += qMin( dimAxis[QwtPlot::yLeft],
+ scaleData.start - backboneOffset[QwtPlot::yLeft] );
+ length += qMin( dimAxis[QwtPlot::yRight],
+ scaleData.end - backboneOffset[QwtPlot::yRight] );
+ }
+ else // QwtPlot::yLeft, QwtPlot::yRight
+ {
+ length = rect.height() - dimAxis[QwtPlot::xTop]
+ - dimAxis[QwtPlot::xBottom];
+ length -= scaleData.start + scaleData.end;
+ length -= 1;
+
+ if ( dimAxis[QwtPlot::xBottom] <= 0 )
+ length -= 1;
+ if ( dimAxis[QwtPlot::xTop] <= 0 )
+ length -= 1;
+
+ if ( dimAxis[QwtPlot::xBottom] > 0 )
+ {
+ length += qMin(
+ d_data->layoutData.scale[QwtPlot::xBottom].tickOffset,
+ double( scaleData.start - backboneOffset[QwtPlot::xBottom] ) );
+ }
+ if ( dimAxis[QwtPlot::xTop] > 0 )
+ {
+ length += qMin(
+ d_data->layoutData.scale[QwtPlot::xTop].tickOffset,
+ double( scaleData.end - backboneOffset[QwtPlot::xTop] ) );
+ }
+
+ if ( dimTitle > 0 )
+ length -= dimTitle + d_data->spacing;
+ }
+
+ if (d_data->fixedOffset[axis])
+ {
+ dimAxis[axis] = d_data->fixedOffset[axis]
+ + backboneOffset[QwtPlot::yLeft];
+ continue;
+ }
+
+ int d = scaleData.dimWithoutTitle;
+ if ( !scaleData.scaleWidget->title().isEmpty() )
+ {
+ d += scaleData.scaleWidget->titleHeightForWidth( qFloor( length ) );
+ }
+
+
+ if ( d > dimAxis[axis] )
+ {
+ dimAxis[axis] = d;
+ done = false;
+ }
+ }
+ }
+ }
+}
+
+/*!
+ Align the ticks of the axis to the canvas borders using
+ the empty corners.
+
+ \sa Options
+*/
+
+void QwtPlotLayout::alignScales( int options,
+ QRectF &canvasRect, QRectF scaleRect[QwtPlot::axisCnt] ) const
+{
+ int backboneOffset[QwtPlot::axisCnt];
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ backboneOffset[axis] = 0;
+ if ( !d_data->alignCanvasToScales )
+ backboneOffset[axis] += d_data->canvasMargin[axis];
+ if ( !( options & IgnoreFrames ) )
+ backboneOffset[axis] += d_data->layoutData.canvas.frameWidth;
+ }
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( !scaleRect[axis].isValid() )
+ continue;
+
+ const int startDist = d_data->layoutData.scale[axis].start;
+ const int endDist = d_data->layoutData.scale[axis].end;
+
+ QRectF &axisRect = scaleRect[axis];
+
+ if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom )
+ {
+ const QRectF &leftScaleRect = scaleRect[QwtPlot::yLeft];
+ const int leftOffset =
+ backboneOffset[QwtPlot::yLeft] - startDist;
+
+ if ( leftScaleRect.isValid() )
+ {
+ const double dx = leftOffset + leftScaleRect.width();
+ if ( d_data->alignCanvasToScales && dx < 0.0 )
+ {
+ /*
+ The axis needs more space than the width
+ of the left scale.
+ */
+ const double cLeft = canvasRect.left(); // qreal -> double
+ canvasRect.setLeft( qMax( cLeft, axisRect.left() - dx ) );
+ }
+ else
+ {
+ const double minLeft = leftScaleRect.left();
+ const double left = axisRect.left() + leftOffset;
+ axisRect.setLeft( qMax( left, minLeft ) );
+ }
+ }
+ else
+ {
+ if ( d_data->alignCanvasToScales && leftOffset < 0 )
+ {
+ canvasRect.setLeft( qMax( canvasRect.left(),
+ axisRect.left() - leftOffset ) );
+ }
+ else
+ {
+ if ( leftOffset > 0 )
+ axisRect.setLeft( axisRect.left() + leftOffset );
+ }
+ }
+
+ const QRectF &rightScaleRect = scaleRect[QwtPlot::yRight];
+ const int rightOffset =
+ backboneOffset[QwtPlot::yRight] - endDist + 1;
+
+ if ( rightScaleRect.isValid() )
+ {
+ const double dx = rightOffset + rightScaleRect.width();
+ if ( d_data->alignCanvasToScales && dx < 0 )
+ {
+ /*
+ The axis needs more space than the width
+ of the right scale.
+ */
+ const double cRight = canvasRect.right(); // qreal -> double
+ canvasRect.setRight( qMin( cRight, axisRect.right() + dx ) );
+ }
+
+ const double maxRight = rightScaleRect.right();
+ const double right = axisRect.right() - rightOffset;
+ axisRect.setRight( qMin( right, maxRight ) );
+ }
+ else
+ {
+ if ( d_data->alignCanvasToScales && rightOffset < 0 )
+ {
+ canvasRect.setRight( qMin( canvasRect.right(),
+ axisRect.right() + rightOffset ) );
+ }
+ else
+ {
+ if ( rightOffset > 0 )
+ axisRect.setRight( axisRect.right() - rightOffset );
+ }
+ }
+ }
+ else // QwtPlot::yLeft, QwtPlot::yRight
+ {
+ const QRectF &bottomScaleRect = scaleRect[QwtPlot::xBottom];
+ const int bottomOffset =
+ backboneOffset[QwtPlot::xBottom] - endDist + 1;
+
+ if ( bottomScaleRect.isValid() )
+ {
+ const double dy = bottomOffset + bottomScaleRect.height();
+ if ( d_data->alignCanvasToScales && dy < 0 )
+ {
+ /*
+ The axis needs more space than the height
+ of the bottom scale.
+ */
+ const double cBottom = canvasRect.bottom(); // qreal -> double
+ canvasRect.setBottom( qMin( cBottom, axisRect.bottom() + dy ) );
+ }
+ else
+ {
+ const double maxBottom = bottomScaleRect.top() +
+ d_data->layoutData.scale[QwtPlot::xBottom].tickOffset;
+ const double bottom = axisRect.bottom() - bottomOffset;
+ axisRect.setBottom( qMin( bottom, maxBottom ) );
+ }
+ }
+ else
+ {
+ if ( d_data->alignCanvasToScales && bottomOffset < 0 )
+ {
+ canvasRect.setBottom( qMin( canvasRect.bottom(),
+ axisRect.bottom() + bottomOffset ) );
+ }
+ else
+ {
+ if ( bottomOffset > 0 )
+ axisRect.setBottom( axisRect.bottom() - bottomOffset );
+ }
+ }
+
+ const QRectF &topScaleRect = scaleRect[QwtPlot::xTop];
+ const int topOffset = backboneOffset[QwtPlot::xTop] - startDist;
+
+ if ( topScaleRect.isValid() )
+ {
+ const double dy = topOffset + topScaleRect.height();
+ if ( d_data->alignCanvasToScales && dy < 0 )
+ {
+ /*
+ The axis needs more space than the height
+ of the top scale.
+ */
+ const double cTop = canvasRect.top(); // qreal -> double
+ canvasRect.setTop( qMax( cTop, axisRect.top() - dy ) );
+ }
+ else
+ {
+ const double minTop = topScaleRect.bottom() -
+ d_data->layoutData.scale[QwtPlot::xTop].tickOffset;
+ const double top = axisRect.top() + topOffset;
+ axisRect.setTop( qMax( top, minTop ) );
+ }
+ }
+ else
+ {
+ if ( d_data->alignCanvasToScales && topOffset < 0 )
+ {
+ canvasRect.setTop( qMax( canvasRect.top(),
+ axisRect.top() - topOffset ) );
+ }
+ else
+ {
+ if ( topOffset > 0 )
+ axisRect.setTop( axisRect.top() + topOffset );
+ }
+ }
+ }
+ }
+
+ if ( d_data->alignCanvasToScales )
+ {
+ /*
+ The canvas has been aligned to the scale with largest
+ border distances. Now we have to realign the other scale.
+ */
+
+ int fw = 0;
+ if ( !( options & IgnoreFrames ) )
+ fw = d_data->layoutData.canvas.frameWidth;
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( !scaleRect[axis].isValid() )
+ continue;
+
+ if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop )
+ {
+ scaleRect[axis].setLeft( canvasRect.left() + fw
+ - d_data->layoutData.scale[axis].start );
+ scaleRect[axis].setRight( canvasRect.right() - fw - 1
+ + d_data->layoutData.scale[axis].end );
+ }
+ else
+ {
+ scaleRect[axis].setTop( canvasRect.top() + fw
+ - d_data->layoutData.scale[axis].start );
+ scaleRect[axis].setBottom( canvasRect.bottom() - fw - 1
+ + d_data->layoutData.scale[axis].end );
+ }
+ }
+
+ if ( scaleRect[QwtPlot::xTop].isValid() )
+ scaleRect[QwtPlot::xTop].setBottom( canvasRect.top() );
+ if ( scaleRect[QwtPlot::xBottom].isValid() )
+ scaleRect[QwtPlot::xBottom].setTop( canvasRect.bottom() );
+ if ( scaleRect[QwtPlot::yLeft].isValid() )
+ scaleRect[QwtPlot::yLeft].setRight( canvasRect.left() );
+ if ( scaleRect[QwtPlot::yRight].isValid() )
+ scaleRect[QwtPlot::yRight].setLeft( canvasRect.right() );
+ }
+}
+
+/*!
+ \brief Recalculate the geometry of all components.
+
+ \param plot Plot to be layout
+ \param plotRect Rect where to place the components
+ \param options Layout options
+
+ \sa invalidate(), titleRect(),
+ legendRect(), scaleRect(), canvasRect()
+*/
+void QwtPlotLayout::activate( const QwtPlot *plot,
+ const QRectF &plotRect, Options options )
+{
+ invalidate();
+
+ QRectF rect( plotRect ); // undistributed rest of the plot rect
+
+ // We extract all layout relevant data from the widgets,
+ // filter them through pfilter and save them to d_data->layoutData.
+
+ d_data->layoutData.init( plot, rect );
+
+ if ( !( options & IgnoreLegend )
+ && d_data->legendPos != QwtPlot::ExternalLegend
+ && plot->legend() && !plot->legend()->isEmpty() )
+ {
+ d_data->legendRect = layoutLegend( options, rect );
+
+ // subtract d_data->legendRect from rect
+
+ const QRegion region( rect.toRect() );
+ rect = region.subtract( d_data->legendRect.toRect() ).boundingRect();
+
+ switch ( d_data->legendPos )
+ {
+ case QwtPlot::LeftLegend:
+ rect.setLeft( rect.left() + d_data->spacing );
+ break;
+ case QwtPlot::RightLegend:
+ rect.setRight( rect.right() - d_data->spacing );
+ break;
+ case QwtPlot::TopLegend:
+ rect.setTop( rect.top() + d_data->spacing );
+ break;
+ case QwtPlot::BottomLegend:
+ rect.setBottom( rect.bottom() - d_data->spacing );
+ break;
+ case QwtPlot::ExternalLegend:
+ break; // suppress compiler warning
+ }
+ }
+
+ /*
+ +---+-----------+---+
+ | Title |
+ +---+-----------+---+
+ | | Axis | |
+ +---+-----------+---+
+ | A | | A |
+ | x | Canvas | x |
+ | i | | i |
+ | s | | s |
+ +---+-----------+---+
+ | | Axis | |
+ +---+-----------+---+
+ */
+
+ // axes and title include text labels. The height of each
+ // label depends on its line breaks, that depend on the width
+ // for the label. A line break in a horizontal text will reduce
+ // the available width for vertical texts and vice versa.
+ // expandLineBreaks finds the height/width for title and axes
+ // including all line breaks.
+
+ int dimTitle, dimAxes[QwtPlot::axisCnt];
+ expandLineBreaks( options, rect, dimTitle, dimAxes );
+
+ if ( dimTitle > 0 )
+ {
+ d_data->titleRect.setRect(
+ rect.left(), rect.top(), rect.width(), dimTitle );
+
+ if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled !=
+ d_data->layoutData.scale[QwtPlot::yRight].isEnabled )
+ {
+ // if only one of the y axes is missing we align
+ // the title centered to the canvas
+
+ d_data->titleRect.setX( rect.left() + dimAxes[QwtPlot::yLeft] );
+ d_data->titleRect.setWidth( rect.width()
+ - dimAxes[QwtPlot::yLeft] - dimAxes[QwtPlot::yRight] );
+ }
+
+ // subtract title
+ rect.setTop( rect.top() + dimTitle + d_data->spacing );
+ }
+
+ d_data->canvasRect.setRect(
+ rect.x() + dimAxes[QwtPlot::yLeft],
+ rect.y() + dimAxes[QwtPlot::xTop],
+ rect.width() - dimAxes[QwtPlot::yRight] - dimAxes[QwtPlot::yLeft],
+ rect.height() - dimAxes[QwtPlot::xBottom] - dimAxes[QwtPlot::xTop] );
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ // set the rects for the axes
+
+ if ( dimAxes[axis] )
+ {
+ int dim = dimAxes[axis];
+ QRectF &scaleRect = d_data->scaleRect[axis];
+
+ scaleRect = d_data->canvasRect;
+ switch ( axis )
+ {
+ case QwtPlot::yLeft:
+ scaleRect.setX( d_data->canvasRect.left() - dim );
+ scaleRect.setWidth( dim );
+ break;
+ case QwtPlot::yRight:
+ scaleRect.setX( d_data->canvasRect.right() );
+ scaleRect.setWidth( dim );
+ break;
+ case QwtPlot::xBottom:
+ scaleRect.setY( d_data->canvasRect.bottom() );
+ scaleRect.setHeight( dim );
+ break;
+ case QwtPlot::xTop:
+ scaleRect.setY( d_data->canvasRect.top() - dim );
+ scaleRect.setHeight( dim );
+ break;
+ }
+ scaleRect = scaleRect.normalized();
+ }
+ }
+
+ // +---+-----------+---+
+ // | <- Axis -> |
+ // +-^-+-----------+-^-+
+ // | | | | | |
+ // | | | |
+ // | A | | A |
+ // | x | Canvas | x |
+ // | i | | i |
+ // | s | | s |
+ // | | | |
+ // | | | | | |
+ // +-V-+-----------+-V-+
+ // | <- Axis -> |
+ // +---+-----------+---+
+
+ // The ticks of the axes - not the labels above - should
+ // be aligned to the canvas. So we try to use the empty
+ // corners to extend the axes, so that the label texts
+ // left/right of the min/max ticks are moved into them.
+
+ alignScales( options, d_data->canvasRect, d_data->scaleRect );
+
+ if ( !d_data->legendRect.isEmpty() )
+ {
+ // We prefer to align the legend to the canvas - not to
+ // the complete plot - if possible.
+
+ d_data->legendRect = alignLegend( d_data->canvasRect, d_data->legendRect );
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_layout.h b/src/libpcp_qwt/src/qwt_plot_layout.h
new file mode 100644
index 0000000..2d9348d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_layout.h
@@ -0,0 +1,108 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_LAYOUT_H
+#define QWT_PLOT_LAYOUT_H
+
+#include "qwt_global.h"
+#include "qwt_plot.h"
+
+/*!
+ \brief Layout engine for QwtPlot.
+
+ It is used by the QwtPlot widget to organize its internal widgets
+ or by QwtPlot::print() to render its content to a QPaintDevice like
+ a QPrinter, QPixmap/QImage or QSvgRenderer.
+*/
+
+class QWT_EXPORT QwtPlotLayout
+{
+public:
+ /*!
+ Options to configure the plot layout engine
+ \sa activate(), QwtPlotRenderer
+ */
+ enum Option
+ {
+ //! Unused
+ AlignScales = 0x01,
+
+ /*!
+ Ignore the dimension of the scrollbars. There are no
+ scrollbars, when the plot is not rendered to widgets.
+ */
+ IgnoreScrollbars = 0x02,
+
+ //! Ignore all frames.
+ IgnoreFrames = 0x04,
+
+ //! Ignore the legend.
+ IgnoreLegend = 0x08
+ };
+
+ //! Layout options
+ typedef QFlags<Option> Options;
+
+ explicit QwtPlotLayout();
+ virtual ~QwtPlotLayout();
+
+ void setCanvasMargin( int margin, int axis = -1 );
+ int canvasMargin( int axis ) const;
+
+ void setFixedAxisOffset(int offset, int axis = -1);
+ int fixedAxisOffset(int axis) const;
+
+ void setAlignCanvasToScales( bool );
+ bool alignCanvasToScales() const;
+
+ void setSpacing( int );
+ int spacing() const;
+
+ void setLegendPosition( QwtPlot::LegendPosition pos, double ratio );
+ void setLegendPosition( QwtPlot::LegendPosition pos );
+ QwtPlot::LegendPosition legendPosition() const;
+
+ void setLegendRatio( double ratio );
+ double legendRatio() const;
+
+ virtual QSize minimumSizeHint( const QwtPlot * ) const;
+
+ virtual void activate( const QwtPlot *,
+ const QRectF &rect, Options options = 0x00 );
+
+ virtual void invalidate();
+
+ const QRectF &titleRect() const;
+ const QRectF &legendRect() const;
+ const QRectF &scaleRect( int axis ) const;
+ const QRectF &canvasRect() const;
+
+ class LayoutData;
+
+protected:
+
+ QRectF layoutLegend( Options options, const QRectF & ) const;
+ QRectF alignLegend( const QRectF &canvasRect,
+ const QRectF &legendRect ) const;
+
+ void expandLineBreaks( int options, const QRectF &rect,
+ int &dimTitle, int dimAxes[QwtPlot::axisCnt] ) const;
+
+ void alignScales( int options, QRectF &canvasRect,
+ QRectF scaleRect[QwtPlot::axisCnt] ) const;
+
+private:
+ class PrivateData;
+
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotLayout::Options )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_magnifier.cpp b/src/libpcp_qwt/src/qwt_plot_magnifier.cpp
new file mode 100644
index 0000000..b490d80
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_magnifier.cpp
@@ -0,0 +1,143 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_div.h"
+#include "qwt_plot_magnifier.h"
+#include <qevent.h>
+
+class QwtPlotMagnifier::PrivateData
+{
+public:
+ PrivateData()
+ {
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ isAxisEnabled[axis] = true;
+ }
+
+ bool isAxisEnabled[QwtPlot::axisCnt];
+};
+
+/*!
+ Constructor
+ \param canvas Plot canvas to be magnified
+*/
+QwtPlotMagnifier::QwtPlotMagnifier( QwtPlotCanvas *canvas ):
+ QwtMagnifier( canvas )
+{
+ d_data = new PrivateData();
+}
+
+//! Destructor
+QwtPlotMagnifier::~QwtPlotMagnifier()
+{
+ delete d_data;
+}
+
+/*!
+ \brief En/Disable an axis
+
+ Only Axes that are enabled will be zoomed.
+ All other axes will remain unchanged.
+
+ \param axis Axis, see QwtPlot::Axis
+ \param on On/Off
+
+ \sa isAxisEnabled()
+*/
+void QwtPlotMagnifier::setAxisEnabled( int axis, bool on )
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->isAxisEnabled[axis] = on;
+}
+
+/*!
+ Test if an axis is enabled
+
+ \param axis Axis, see QwtPlot::Axis
+ \return True, if the axis is enabled
+
+ \sa setAxisEnabled()
+*/
+bool QwtPlotMagnifier::isAxisEnabled( int axis ) const
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ return d_data->isAxisEnabled[axis];
+
+ return true;
+}
+
+//! Return observed plot canvas
+QwtPlotCanvas *QwtPlotMagnifier::canvas()
+{
+ return qobject_cast<QwtPlotCanvas *>( parent() );
+}
+
+//! Return Observed plot canvas
+const QwtPlotCanvas *QwtPlotMagnifier::canvas() const
+{
+ return qobject_cast<const QwtPlotCanvas *>( parent() );
+}
+
+//! Return plot widget, containing the observed plot canvas
+QwtPlot *QwtPlotMagnifier::plot()
+{
+ QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+//! Return plot widget, containing the observed plot canvas
+const QwtPlot *QwtPlotMagnifier::plot() const
+{
+ const QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+/*!
+ Zoom in/out the axes scales
+ \param factor A value < 1.0 zooms in, a value > 1.0 zooms out.
+*/
+void QwtPlotMagnifier::rescale( double factor )
+{
+ factor = qAbs( factor );
+ if ( factor == 1.0 || factor == 0.0 )
+ return;
+
+ bool doReplot = false;
+ QwtPlot* plt = plot();
+
+ const bool autoReplot = plt->autoReplot();
+ plt->setAutoReplot( false );
+
+ for ( int axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
+ {
+ const QwtScaleDiv *scaleDiv = plt->axisScaleDiv( axisId );
+ if ( isAxisEnabled( axisId ) && scaleDiv->isValid() )
+ {
+ const double center =
+ scaleDiv->lowerBound() + scaleDiv->range() / 2;
+ const double width_2 = scaleDiv->range() / 2 * factor;
+
+ plt->setAxisScale( axisId, center - width_2, center + width_2 );
+ doReplot = true;
+ }
+ }
+
+ plt->setAutoReplot( autoReplot );
+
+ if ( doReplot )
+ plt->replot();
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_magnifier.h b/src/libpcp_qwt/src/qwt_plot_magnifier.h
new file mode 100644
index 0000000..e7369c7
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_magnifier.h
@@ -0,0 +1,55 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_MAGNIFIER_H
+#define QWT_PLOT_MAGNIFIER_H 1
+
+#include "qwt_global.h"
+#include "qwt_magnifier.h"
+
+class QwtPlotCanvas;
+class QwtPlot;
+
+/*!
+ \brief QwtPlotMagnifier provides zooming, by magnifying in steps.
+
+ Using QwtPlotMagnifier a plot can be zoomed in/out in steps using
+ keys, the mouse wheel or moving a mouse button in vertical direction.
+
+ Together with QwtPlotZoomer and QwtPlotPanner it is possible to implement
+ individual and powerful navigation of the plot canvas.
+
+ \sa QwtPlotZoomer, QwtPlotPanner, QwtPlot
+*/
+class QWT_EXPORT QwtPlotMagnifier: public QwtMagnifier
+{
+ Q_OBJECT
+
+public:
+ explicit QwtPlotMagnifier( QwtPlotCanvas * );
+ virtual ~QwtPlotMagnifier();
+
+ void setAxisEnabled( int axis, bool on );
+ bool isAxisEnabled( int axis ) const;
+
+ QwtPlotCanvas *canvas();
+ const QwtPlotCanvas *canvas() const;
+
+ QwtPlot *plot();
+ const QwtPlot *plot() const;
+
+protected:
+ virtual void rescale( double factor );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_marker.cpp b/src/libpcp_qwt/src/qwt_plot_marker.cpp
new file mode 100644
index 0000000..900f145
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_marker.cpp
@@ -0,0 +1,608 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_marker.h"
+#include "qwt_painter.h"
+#include "qwt_scale_map.h"
+#include "qwt_symbol.h"
+#include "qwt_text.h"
+#include "qwt_math.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include <qpainter.h>
+
+class QwtPlotMarker::PrivateData
+{
+public:
+ PrivateData():
+ labelAlignment( Qt::AlignCenter ),
+ labelOrientation( Qt::Horizontal ),
+ spacing( 2 ),
+ symbol( NULL ),
+ style( QwtPlotMarker::NoLine ),
+ xValue( 0.0 ),
+ yValue( 0.0 )
+ {
+ }
+
+ ~PrivateData()
+ {
+ delete symbol;
+ }
+
+ QwtText label;
+ Qt::Alignment labelAlignment;
+ Qt::Orientation labelOrientation;
+ int spacing;
+
+ QPen pen;
+ const QwtSymbol *symbol;
+ LineStyle style;
+
+ double xValue;
+ double yValue;
+};
+
+//! Sets alignment to Qt::AlignCenter, and style to QwtPlotMarker::NoLine
+QwtPlotMarker::QwtPlotMarker():
+ QwtPlotItem( QwtText( "Marker" ) )
+{
+ d_data = new PrivateData;
+ setZ( 30.0 );
+}
+
+//! Destructor
+QwtPlotMarker::~QwtPlotMarker()
+{
+ delete d_data;
+}
+
+//! \return QwtPlotItem::Rtti_PlotMarker
+int QwtPlotMarker::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotMarker;
+}
+
+//! Return Value
+QPointF QwtPlotMarker::value() const
+{
+ return QPointF( d_data->xValue, d_data->yValue );
+}
+
+//! Return x Value
+double QwtPlotMarker::xValue() const
+{
+ return d_data->xValue;
+}
+
+//! Return y Value
+double QwtPlotMarker::yValue() const
+{
+ return d_data->yValue;
+}
+
+//! Set Value
+void QwtPlotMarker::setValue( const QPointF& pos )
+{
+ setValue( pos.x(), pos.y() );
+}
+
+//! Set Value
+void QwtPlotMarker::setValue( double x, double y )
+{
+ if ( x != d_data->xValue || y != d_data->yValue )
+ {
+ d_data->xValue = x;
+ d_data->yValue = y;
+ itemChanged();
+ }
+}
+
+//! Set X Value
+void QwtPlotMarker::setXValue( double x )
+{
+ setValue( x, d_data->yValue );
+}
+
+//! Set Y Value
+void QwtPlotMarker::setYValue( double y )
+{
+ setValue( d_data->xValue, y );
+}
+
+/*!
+ Draw the marker
+
+ \param painter Painter
+ \param xMap x Scale Map
+ \param yMap y Scale Map
+ \param canvasRect Contents rect of the canvas in painter coordinates
+*/
+void QwtPlotMarker::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ const QPointF pos( xMap.transform( d_data->xValue ),
+ yMap.transform( d_data->yValue ) );
+
+ // draw lines
+
+ drawLines( painter, canvasRect, pos );
+
+ // draw symbol
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtSymbol::NoSymbol ) )
+ {
+ const QSizeF sz = d_data->symbol->size();
+
+ const QRectF clipRect = canvasRect.adjusted(
+ -sz.width(), -sz.height(), sz.width(), sz.height() );
+
+ if ( clipRect.contains( pos ) )
+ d_data->symbol->drawSymbol( painter, pos );
+ }
+
+ drawLabel( painter, canvasRect, pos );
+}
+
+/*!
+ Draw the lines marker
+
+ \param painter Painter
+ \param canvasRect Contents rect of the canvas in painter coordinates
+ \param pos Position of the marker, translated into widget coordinates
+
+ \sa drawLabel(), QwtSymbol::drawSymbol()
+*/
+void QwtPlotMarker::drawLines( QPainter *painter,
+ const QRectF &canvasRect, const QPointF &pos ) const
+{
+ if ( d_data->style == NoLine )
+ return;
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ painter->setPen( d_data->pen );
+ if ( d_data->style == QwtPlotMarker::HLine ||
+ d_data->style == QwtPlotMarker::Cross )
+ {
+ double y = pos.y();
+ if ( doAlign )
+ y = qRound( y );
+
+ QwtPainter::drawLine( painter, canvasRect.left(),
+ y, canvasRect.right() - 1.0, y );
+ }
+ if ( d_data->style == QwtPlotMarker::VLine ||
+ d_data->style == QwtPlotMarker::Cross )
+ {
+ double x = pos.x();
+ if ( doAlign )
+ x = qRound( x );
+
+ QwtPainter::drawLine( painter, x,
+ canvasRect.top(), x, canvasRect.bottom() - 1.0 );
+ }
+}
+
+/*!
+ Align and draw the text label of the marker
+
+ \param painter Painter
+ \param canvasRect Contents rect of the canvas in painter coordinates
+ \param pos Position of the marker, translated into widget coordinates
+
+ \sa drawLabel(), QwtSymbol::drawSymbol()
+*/
+void QwtPlotMarker::drawLabel( QPainter *painter,
+ const QRectF &canvasRect, const QPointF &pos ) const
+{
+ if ( d_data->label.isEmpty() )
+ return;
+
+ Qt::Alignment align = d_data->labelAlignment;
+ QPointF alignPos = pos;
+
+ QSizeF symbolOff( 0, 0 );
+
+ switch ( d_data->style )
+ {
+ case QwtPlotMarker::VLine:
+ {
+ // In VLine-style the y-position is pointless and
+ // the alignment flags are relative to the canvas
+
+ if ( d_data->labelAlignment & Qt::AlignTop )
+ {
+ alignPos.setY( canvasRect.top() );
+ align &= ~Qt::AlignTop;
+ align |= Qt::AlignBottom;
+ }
+ else if ( d_data->labelAlignment & Qt::AlignBottom )
+ {
+ // In HLine-style the x-position is pointless and
+ // the alignment flags are relative to the canvas
+
+ alignPos.setY( canvasRect.bottom() - 1 );
+ align &= ~Qt::AlignBottom;
+ align |= Qt::AlignTop;
+ }
+ else
+ {
+ alignPos.setY( canvasRect.center().y() );
+ }
+ break;
+ }
+ case QwtPlotMarker::HLine:
+ {
+ if ( d_data->labelAlignment & Qt::AlignLeft )
+ {
+ alignPos.setX( canvasRect.left() );
+ align &= ~Qt::AlignLeft;
+ align |= Qt::AlignRight;
+ }
+ else if ( d_data->labelAlignment & Qt::AlignRight )
+ {
+ alignPos.setX( canvasRect.right() - 1 );
+ align &= ~Qt::AlignRight;
+ align |= Qt::AlignLeft;
+ }
+ else
+ {
+ alignPos.setX( canvasRect.center().x() );
+ }
+ break;
+ }
+ default:
+ {
+ if ( d_data->symbol &&
+ ( d_data->symbol->style() != QwtSymbol::NoSymbol ) )
+ {
+ symbolOff = d_data->symbol->size() + QSizeF( 1, 1 );
+ symbolOff /= 2;
+ }
+ }
+ }
+
+ qreal pw2 = d_data->pen.widthF() / 2.0;
+ if ( pw2 == 0.0 )
+ pw2 = 0.5;
+
+ const int spacing = d_data->spacing;
+
+ const qreal xOff = qMax( pw2, symbolOff.width() );
+ const qreal yOff = qMax( pw2, symbolOff.height() );
+
+ const QSizeF textSize = d_data->label.textSize( painter->font() );
+
+ if ( align & Qt::AlignLeft )
+ {
+ alignPos.rx() -= xOff + spacing;
+ if ( d_data->labelOrientation == Qt::Vertical )
+ alignPos.rx() -= textSize.height();
+ else
+ alignPos.rx() -= textSize.width();
+ }
+ else if ( align & Qt::AlignRight )
+ {
+ alignPos.rx() += xOff + spacing;
+ }
+ else
+ {
+ if ( d_data->labelOrientation == Qt::Vertical )
+ alignPos.rx() -= textSize.height() / 2;
+ else
+ alignPos.rx() -= textSize.width() / 2;
+ }
+
+ if ( align & Qt::AlignTop )
+ {
+ alignPos.ry() -= yOff + spacing;
+ if ( d_data->labelOrientation != Qt::Vertical )
+ alignPos.ry() -= textSize.height();
+ }
+ else if ( align & Qt::AlignBottom )
+ {
+ alignPos.ry() += yOff + spacing;
+ if ( d_data->labelOrientation == Qt::Vertical )
+ alignPos.ry() += textSize.width();
+ }
+ else
+ {
+ if ( d_data->labelOrientation == Qt::Vertical )
+ alignPos.ry() += textSize.width() / 2;
+ else
+ alignPos.ry() -= textSize.height() / 2;
+ }
+
+ painter->translate( alignPos.x(), alignPos.y() );
+ if ( d_data->labelOrientation == Qt::Vertical )
+ painter->rotate( -90.0 );
+
+ const QRectF textRect( 0, 0, textSize.width(), textSize.height() );
+ d_data->label.draw( painter, textRect );
+}
+
+/*!
+ \brief Set the line style
+ \param style Line style.
+ \sa lineStyle()
+*/
+void QwtPlotMarker::setLineStyle( LineStyle style )
+{
+ if ( style != d_data->style )
+ {
+ d_data->style = style;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the line style
+ \sa setLineStyle()
+*/
+QwtPlotMarker::LineStyle QwtPlotMarker::lineStyle() const
+{
+ return d_data->style;
+}
+
+/*!
+ \brief Assign a symbol
+ \param symbol New symbol
+ \sa symbol()
+*/
+void QwtPlotMarker::setSymbol( const QwtSymbol *symbol )
+{
+ if ( symbol != d_data->symbol )
+ {
+ delete d_data->symbol;
+ d_data->symbol = symbol;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the symbol
+ \sa setSymbol(), QwtSymbol
+*/
+const QwtSymbol *QwtPlotMarker::symbol() const
+{
+ return d_data->symbol;
+}
+
+/*!
+ \brief Set the label
+ \param label label text
+ \sa label()
+*/
+void QwtPlotMarker::setLabel( const QwtText& label )
+{
+ if ( label != d_data->label )
+ {
+ d_data->label = label;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the label
+ \sa setLabel()
+*/
+QwtText QwtPlotMarker::label() const
+{
+ return d_data->label;
+}
+
+/*!
+ \brief Set the alignment of the label
+
+ In case of QwtPlotMarker::HLine the alignment is relative to the
+ y position of the marker, but the horizontal flags correspond to the
+ canvas rectangle. In case of QwtPlotMarker::VLine the alignment is
+ relative to the x position of the marker, but the vertical flags
+ correspond to the canvas rectangle.
+
+ In all other styles the alignment is relative to the marker's position.
+
+ \param align Alignment.
+ \sa labelAlignment(), labelOrientation()
+*/
+void QwtPlotMarker::setLabelAlignment( Qt::Alignment align )
+{
+ if ( align != d_data->labelAlignment )
+ {
+ d_data->labelAlignment = align;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the label alignment
+ \sa setLabelAlignment(), setLabelOrientation()
+*/
+Qt::Alignment QwtPlotMarker::labelAlignment() const
+{
+ return d_data->labelAlignment;
+}
+
+/*!
+ \brief Set the orientation of the label
+
+ When orientation is Qt::Vertical the label is rotated by 90.0 degrees
+ ( from bottom to top ).
+
+ \param orientation Orientation of the label
+
+ \sa labelOrientation(), setLabelAlignment()
+*/
+void QwtPlotMarker::setLabelOrientation( Qt::Orientation orientation )
+{
+ if ( orientation != d_data->labelOrientation )
+ {
+ d_data->labelOrientation = orientation;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the label orientation
+ \sa setLabelOrientation(), labelAlignment()
+*/
+Qt::Orientation QwtPlotMarker::labelOrientation() const
+{
+ return d_data->labelOrientation;
+}
+
+/*!
+ \brief Set the spacing
+
+ When the label is not centered on the marker position, the spacing
+ is the distance between the position and the label.
+
+ \param spacing Spacing
+ \sa spacing(), setLabelAlignment()
+*/
+void QwtPlotMarker::setSpacing( int spacing )
+{
+ if ( spacing < 0 )
+ spacing = 0;
+
+ if ( spacing == d_data->spacing )
+ return;
+
+ d_data->spacing = spacing;
+ itemChanged();
+}
+
+/*!
+ \return the spacing
+ \sa setSpacing()
+*/
+int QwtPlotMarker::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ Specify a pen for the line.
+
+ \param pen New pen
+ \sa linePen()
+*/
+void QwtPlotMarker::setLinePen( const QPen &pen )
+{
+ if ( pen != d_data->pen )
+ {
+ d_data->pen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \return the line pen
+ \sa setLinePen()
+*/
+const QPen &QwtPlotMarker::linePen() const
+{
+ return d_data->pen;
+}
+
+QRectF QwtPlotMarker::boundingRect() const
+{
+ return QRectF( d_data->xValue, d_data->yValue, 0.0, 0.0 );
+}
+
+/*!
+ \brief Update the widget that represents the item on the legend
+
+ \param legend Legend
+ \sa drawLegendIdentifier(), legendItem(), itemChanged(), QwtLegend()
+
+ \note In the default setting QwtPlotItem::Legend is disabled
+*/
+void QwtPlotMarker::updateLegend( QwtLegend *legend ) const
+{
+ if ( legend && testItemAttribute( QwtPlotItem::Legend )
+ && d_data->symbol && d_data->symbol->style() != QwtSymbol::NoSymbol )
+ {
+ QWidget *lgdItem = legend->find( this );
+ if ( lgdItem == NULL )
+ {
+ lgdItem = legendItem();
+ if ( lgdItem )
+ legend->insert( this, lgdItem );
+ }
+
+ QwtLegendItem *l = qobject_cast<QwtLegendItem *>( lgdItem );
+ if ( l )
+ l->setIdentifierSize( d_data->symbol->boundingSize() );
+ }
+
+ QwtPlotItem::updateLegend( legend );
+}
+
+/*!
+ \brief Draw the identifier representing the marker on the legend
+
+ \param painter Painter
+ \param rect Bounding rectangle for the identifier
+
+ \sa updateLegend(), QwtPlotItem::Legend
+*/
+void QwtPlotMarker::drawLegendIdentifier(
+ QPainter *painter, const QRectF &rect ) const
+{
+ if ( rect.isEmpty() )
+ return;
+
+ painter->save();
+ painter->setClipRect( rect, Qt::IntersectClip );
+
+ if ( d_data->style != QwtPlotMarker::NoLine )
+ {
+ painter->setPen( d_data->pen );
+
+ if ( d_data->style == QwtPlotMarker::HLine ||
+ d_data->style == QwtPlotMarker::Cross )
+ {
+ QwtPainter::drawLine( painter, rect.left(), rect.center().y(),
+ rect.right(), rect.center().y() );
+ }
+
+ if ( d_data->style == QwtPlotMarker::VLine ||
+ d_data->style == QwtPlotMarker::Cross )
+ {
+ QwtPainter::drawLine( painter, rect.center().x(), rect.top(),
+ rect.center().x(), rect.bottom() );
+ }
+ }
+
+ if ( d_data->symbol && d_data->symbol->style() != QwtSymbol::NoSymbol )
+ {
+ QSize symbolSize = d_data->symbol->boundingSize();
+ symbolSize -= QSize( 2, 2 );
+
+ // scale the symbol size down if it doesn't fit into rect.
+
+ double xRatio = 1.0;
+ if ( rect.width() < symbolSize.width() )
+ xRatio = rect.width() / symbolSize.width();
+ double yRatio = 1.0;
+ if ( rect.height() < symbolSize.height() )
+ yRatio = rect.height() / symbolSize.height();
+
+ const double ratio = qMin( xRatio, yRatio );
+
+ painter->scale( ratio, ratio );
+ d_data->symbol->drawSymbol( painter, rect.center() / ratio );
+ }
+
+ painter->restore();
+}
+
diff --git a/src/libpcp_qwt/src/qwt_plot_marker.h b/src/libpcp_qwt/src/qwt_plot_marker.h
new file mode 100644
index 0000000..63a0d1d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_marker.h
@@ -0,0 +1,124 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_MARKER_H
+#define QWT_PLOT_MARKER_H
+
+#include <qpen.h>
+#include <qfont.h>
+#include <qstring.h>
+#include <qbrush.h>
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+
+class QRectF;
+class QwtText;
+class QwtSymbol;
+
+/*!
+ \brief A class for drawing markers
+
+ A marker can be a horizontal line, a vertical line,
+ a symbol, a label or any combination of them, which can
+ be drawn around a center point inside a bounding rectangle.
+
+ The QwtPlotMarker::setSymbol() member assigns a symbol to the marker.
+ The symbol is drawn at the specified point.
+
+ With setLabel(), a label can be assigned to the marker.
+ The setLabelAlignment() member specifies where the label is
+ drawn. All the Align*-constants in Qt::AlignmentFlags (see Qt documentation)
+ are valid. The interpretation of the alignment depends on the marker's
+ line style. The alignment refers to the center point of
+ the marker, which means, for example, that the label would be printed
+ left above the center point if the alignment was set to
+ Qt::AlignLeft | Qt::AlignTop.
+*/
+
+class QWT_EXPORT QwtPlotMarker: public QwtPlotItem
+{
+public:
+
+ /*!
+ Line styles.
+ \sa setLineStyle(), lineStyle()
+ */
+ enum LineStyle
+ {
+ //! No line
+ NoLine,
+
+ //! A horizontal line
+ HLine,
+
+ //! A vertical line
+ VLine,
+
+ //! A crosshair
+ Cross
+ };
+
+ explicit QwtPlotMarker();
+ virtual ~QwtPlotMarker();
+
+ virtual int rtti() const;
+
+ double xValue() const;
+ double yValue() const;
+ QPointF value() const;
+
+ void setXValue( double );
+ void setYValue( double );
+ void setValue( double, double );
+ void setValue( const QPointF & );
+
+ void setLineStyle( LineStyle st );
+ LineStyle lineStyle() const;
+
+ void setLinePen( const QPen &p );
+ const QPen &linePen() const;
+
+ void setSymbol( const QwtSymbol * );
+ const QwtSymbol *symbol() const;
+
+ void setLabel( const QwtText& );
+ QwtText label() const;
+
+ void setLabelAlignment( Qt::Alignment );
+ Qt::Alignment labelAlignment() const;
+
+ void setLabelOrientation( Qt::Orientation );
+ Qt::Orientation labelOrientation() const;
+
+ void setSpacing( int );
+ int spacing() const;
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF & ) const;
+
+ virtual QRectF boundingRect() const;
+
+ virtual void updateLegend( QwtLegend * ) const;
+ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const;
+
+protected:
+ virtual void drawLines( QPainter *,
+ const QRectF &, const QPointF & ) const;
+
+ virtual void drawLabel( QPainter *,
+ const QRectF &, const QPointF & ) const;
+
+private:
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_panner.cpp b/src/libpcp_qwt/src/qwt_plot_panner.cpp
new file mode 100644
index 0000000..fa8cb54
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_panner.cpp
@@ -0,0 +1,175 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_panner.h"
+#include "qwt_scale_div.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+
+class QwtPlotPanner::PrivateData
+{
+public:
+ PrivateData()
+ {
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ isAxisEnabled[axis] = true;
+ }
+
+ bool isAxisEnabled[QwtPlot::axisCnt];
+};
+
+/*!
+ \brief Create a plot panner
+
+ The panner is enabled for all axes
+
+ \param canvas Plot canvas to pan, also the parent object
+
+ \sa setAxisEnabled()
+*/
+QwtPlotPanner::QwtPlotPanner( QwtPlotCanvas *canvas ):
+ QwtPanner( canvas )
+{
+ d_data = new PrivateData();
+
+ connect( this, SIGNAL( panned( int, int ) ),
+ SLOT( moveCanvas( int, int ) ) );
+}
+
+//! Destructor
+QwtPlotPanner::~QwtPlotPanner()
+{
+ delete d_data;
+}
+
+/*!
+ \brief En/Disable an axis
+
+ Axes that are enabled will be synchronized to the
+ result of panning. All other axes will remain unchanged.
+
+ \param axis Axis, see QwtPlot::Axis
+ \param on On/Off
+
+ \sa isAxisEnabled(), moveCanvas()
+*/
+void QwtPlotPanner::setAxisEnabled( int axis, bool on )
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->isAxisEnabled[axis] = on;
+}
+
+/*!
+ Test if an axis is enabled
+
+ \param axis Axis, see QwtPlot::Axis
+ \return True, if the axis is enabled
+
+ \sa setAxisEnabled(), moveCanvas()
+*/
+bool QwtPlotPanner::isAxisEnabled( int axis ) const
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ return d_data->isAxisEnabled[axis];
+
+ return true;
+}
+
+//! Return observed plot canvas
+QwtPlotCanvas *QwtPlotPanner::canvas()
+{
+ return qobject_cast<QwtPlotCanvas *>( parentWidget() );
+}
+
+//! Return Observed plot canvas
+const QwtPlotCanvas *QwtPlotPanner::canvas() const
+{
+ return qobject_cast<const QwtPlotCanvas *>( parentWidget() );
+}
+
+//! Return plot widget, containing the observed plot canvas
+QwtPlot *QwtPlotPanner::plot()
+{
+ QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+//! Return plot widget, containing the observed plot canvas
+const QwtPlot *QwtPlotPanner::plot() const
+{
+ const QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+/*!
+ Adjust the enabled axes according to dx/dy
+
+ \param dx Pixel offset in x direction
+ \param dy Pixel offset in y direction
+
+ \sa QwtPanner::panned()
+*/
+void QwtPlotPanner::moveCanvas( int dx, int dy )
+{
+ if ( dx == 0 && dy == 0 )
+ return;
+
+ QwtPlot *plot = this->plot();
+ if ( plot == NULL )
+ return;
+
+ const bool doAutoReplot = plot->autoReplot();
+ plot->setAutoReplot( false );
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( !d_data->isAxisEnabled[axis] )
+ continue;
+
+ const QwtScaleMap map = plot->canvasMap( axis );
+
+ const double p1 = map.transform( plot->axisScaleDiv( axis )->lowerBound() );
+ const double p2 = map.transform( plot->axisScaleDiv( axis )->upperBound() );
+
+ double d1, d2;
+ if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop )
+ {
+ d1 = map.invTransform( p1 - dx );
+ d2 = map.invTransform( p2 - dx );
+ }
+ else
+ {
+ d1 = map.invTransform( p1 - dy );
+ d2 = map.invTransform( p2 - dy );
+ }
+
+ plot->setAxisScale( axis, d1, d2 );
+ }
+
+ plot->setAutoReplot( doAutoReplot );
+ plot->replot();
+}
+
+/*!
+ Calculate a mask from the border mask of the canvas
+ \sa QwtPlotCanvas::borderMask()
+*/
+QBitmap QwtPlotPanner::contentsMask() const
+{
+ if ( canvas() )
+ return canvas()->borderMask( size() );
+
+ return QwtPanner::contentsMask();
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_panner.h b/src/libpcp_qwt/src/qwt_plot_panner.h
new file mode 100644
index 0000000..fc783e3
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_panner.h
@@ -0,0 +1,60 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_PANNER_H
+#define QWT_PLOT_PANNER_H 1
+
+#include "qwt_global.h"
+#include "qwt_panner.h"
+
+class QwtPlotCanvas;
+class QwtPlot;
+
+/*!
+ \brief QwtPlotPanner provides panning of a plot canvas
+
+ QwtPlotPanner is a panner for a QwtPlotCanvas, that
+ adjusts the scales of the axes after dropping
+ the canvas on its new position.
+
+ Together with QwtPlotZoomer and QwtPlotMagnifier powerful ways
+ of navigating on a QwtPlot widget can be implemented easily.
+
+ \note The axes are not updated, while dragging the canvas
+ \sa QwtPlotZoomer, QwtPlotMagnifier
+*/
+class QWT_EXPORT QwtPlotPanner: public QwtPanner
+{
+ Q_OBJECT
+
+public:
+ explicit QwtPlotPanner( QwtPlotCanvas * );
+ virtual ~QwtPlotPanner();
+
+ QwtPlotCanvas *canvas();
+ const QwtPlotCanvas *canvas() const;
+
+ QwtPlot *plot();
+ const QwtPlot *plot() const;
+
+ void setAxisEnabled( int axis, bool on );
+ bool isAxisEnabled( int axis ) const;
+
+protected Q_SLOTS:
+ virtual void moveCanvas( int dx, int dy );
+
+protected:
+ virtual QBitmap contentsMask() const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_picker.cpp b/src/libpcp_qwt/src/qwt_plot_picker.cpp
new file mode 100644
index 0000000..7a4073c
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_picker.cpp
@@ -0,0 +1,383 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_picker.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_div.h"
+#include "qwt_painter.h"
+#include "qwt_scale_map.h"
+#include "qwt_picker_machine.h"
+
+/*!
+ \brief Create a plot picker
+
+ The picker is set to those x- and y-axis of the plot
+ that are enabled. If both or no x-axis are enabled, the picker
+ is set to QwtPlot::xBottom. If both or no y-axis are
+ enabled, it is set to QwtPlot::yLeft.
+
+ \param canvas Plot canvas to observe, also the parent object
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect()
+*/
+
+QwtPlotPicker::QwtPlotPicker( QwtPlotCanvas *canvas ):
+ QwtPicker( canvas ),
+ d_xAxis( -1 ),
+ d_yAxis( -1 )
+{
+ if ( !canvas )
+ return;
+
+ // attach axes
+
+ int xAxis = QwtPlot::xBottom;
+
+ const QwtPlot *plot = QwtPlotPicker::plot();
+ if ( !plot->axisEnabled( QwtPlot::xBottom ) &&
+ plot->axisEnabled( QwtPlot::xTop ) )
+ {
+ xAxis = QwtPlot::xTop;
+ }
+
+ int yAxis = QwtPlot::yLeft;
+ if ( !plot->axisEnabled( QwtPlot::yLeft ) &&
+ plot->axisEnabled( QwtPlot::yRight ) )
+ {
+ yAxis = QwtPlot::yRight;
+ }
+
+ setAxis( xAxis, yAxis );
+}
+
+/*!
+ Create a plot picker
+
+ \param xAxis Set the x axis of the picker
+ \param yAxis Set the y axis of the picker
+ \param canvas Plot canvas to observe, also the parent object
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect()
+*/
+QwtPlotPicker::QwtPlotPicker( int xAxis, int yAxis, QwtPlotCanvas *canvas ):
+ QwtPicker( canvas ),
+ d_xAxis( xAxis ),
+ d_yAxis( yAxis )
+{
+}
+
+/*!
+ Create a plot picker
+
+ \param xAxis X axis of the picker
+ \param yAxis Y axis of the picker
+ \param rubberBand Rubberband style
+ \param trackerMode Tracker mode
+ \param canvas Plot canvas to observe, also the parent object
+
+ \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(),
+ QwtPicker::setTrackerMode
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot(), scaleRect()
+*/
+QwtPlotPicker::QwtPlotPicker( int xAxis, int yAxis,
+ RubberBand rubberBand, DisplayMode trackerMode,
+ QwtPlotCanvas *canvas ):
+ QwtPicker( rubberBand, trackerMode, canvas ),
+ d_xAxis( xAxis ),
+ d_yAxis( yAxis )
+{
+}
+
+//! Destructor
+QwtPlotPicker::~QwtPlotPicker()
+{
+}
+
+//! Return observed plot canvas
+QwtPlotCanvas *QwtPlotPicker::canvas()
+{
+ return qobject_cast<QwtPlotCanvas *>( parentWidget() );
+}
+
+//! Return Observed plot canvas
+const QwtPlotCanvas *QwtPlotPicker::canvas() const
+{
+ return qobject_cast<const QwtPlotCanvas *>( parentWidget() );
+}
+
+//! Return plot widget, containing the observed plot canvas
+QwtPlot *QwtPlotPicker::plot()
+{
+ QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+//! Return plot widget, containing the observed plot canvas
+const QwtPlot *QwtPlotPicker::plot() const
+{
+ const QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+/*!
+ Return normalized bounding rect of the axes
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot().
+*/
+QRectF QwtPlotPicker::scaleRect() const
+{
+ QRectF rect;
+
+ if ( plot() )
+ {
+ const QwtScaleDiv *xs = plot()->axisScaleDiv( xAxis() );
+ const QwtScaleDiv *ys = plot()->axisScaleDiv( yAxis() );
+
+ if ( xs && ys )
+ {
+ rect = QRectF( xs->lowerBound(), ys->lowerBound(),
+ xs->range(), ys->range() );
+ rect = rect.normalized();
+ }
+ }
+
+ return rect;
+}
+
+/*!
+ Set the x and y axes of the picker
+
+ \param xAxis X axis
+ \param yAxis Y axis
+*/
+void QwtPlotPicker::setAxis( int xAxis, int yAxis )
+{
+ const QwtPlot *plt = plot();
+ if ( !plt )
+ return;
+
+ if ( xAxis != d_xAxis || yAxis != d_yAxis )
+ {
+ d_xAxis = xAxis;
+ d_yAxis = yAxis;
+ }
+}
+
+//! Return x axis
+int QwtPlotPicker::xAxis() const
+{
+ return d_xAxis;
+}
+
+//! Return y axis
+int QwtPlotPicker::yAxis() const
+{
+ return d_yAxis;
+}
+
+/*!
+ Translate a pixel position into a position string
+
+ \param pos Position in pixel coordinates
+ \return Position string
+*/
+QwtText QwtPlotPicker::trackerText( const QPoint &pos ) const
+{
+ return trackerTextF( invTransform( pos ) );
+}
+
+/*!
+ \brief Translate a position into a position string
+
+ In case of HLineRubberBand the label is the value of the
+ y position, in case of VLineRubberBand the value of the x position.
+ Otherwise the label contains x and y position separated by a ',' .
+
+ The format for the double to string conversion is "%.4f".
+
+ \param pos Position
+ \return Position string
+*/
+QwtText QwtPlotPicker::trackerTextF( const QPointF &pos ) const
+{
+ QString text;
+
+ switch ( rubberBand() )
+ {
+ case HLineRubberBand:
+ text.sprintf( "%.4f", pos.y() );
+ break;
+ case VLineRubberBand:
+ text.sprintf( "%.4f", pos.x() );
+ break;
+ default:
+ text.sprintf( "%.4f, %.4f", pos.x(), pos.y() );
+ }
+ return QwtText( text );
+}
+
+/*!
+ Append a point to the selection and update rubberband and tracker.
+
+ \param pos Additional point
+ \sa isActive, begin(), end(), move(), appended()
+
+ \note The appended(const QPoint &), appended(const QDoublePoint &)
+ signals are emitted.
+*/
+void QwtPlotPicker::append( const QPoint &pos )
+{
+ QwtPicker::append( pos );
+ Q_EMIT appended( invTransform( pos ) );
+}
+
+/*!
+ Move the last point of the selection
+
+ \param pos New position
+ \sa isActive, begin(), end(), append()
+
+ \note The moved(const QPoint &), moved(const QDoublePoint &)
+ signals are emitted.
+*/
+void QwtPlotPicker::move( const QPoint &pos )
+{
+ QwtPicker::move( pos );
+ Q_EMIT moved( invTransform( pos ) );
+}
+
+/*!
+ Close a selection setting the state to inactive.
+
+ \param ok If true, complete the selection and emit selected signals
+ otherwise discard the selection.
+ \return true if the selection is accepted, false otherwise
+*/
+
+bool QwtPlotPicker::end( bool ok )
+{
+ ok = QwtPicker::end( ok );
+ if ( !ok )
+ return false;
+
+ QwtPlot *plot = QwtPlotPicker::plot();
+ if ( !plot )
+ return false;
+
+ const QPolygon points = selection();
+ if ( points.count() == 0 )
+ return false;
+
+ QwtPickerMachine::SelectionType selectionType =
+ QwtPickerMachine::NoSelection;
+
+ if ( stateMachine() )
+ selectionType = stateMachine()->selectionType();
+
+ switch ( selectionType )
+ {
+ case QwtPickerMachine::PointSelection:
+ {
+ const QPointF pos = invTransform( points.first() );
+ Q_EMIT selected( pos );
+ break;
+ }
+ case QwtPickerMachine::RectSelection:
+ {
+ if ( points.count() >= 2 )
+ {
+ const QPoint p1 = points.first();
+ const QPoint p2 = points.last();
+
+ const QRect rect = QRect( p1, p2 ).normalized();
+ Q_EMIT selected( invTransform( rect ) );
+ }
+ break;
+ }
+ case QwtPickerMachine::PolygonSelection:
+ {
+ QVector<QPointF> dpa( points.count() );
+ for ( int i = 0; i < points.count(); i++ )
+ dpa[i] = invTransform( points[i] );
+
+ Q_EMIT selected( dpa );
+ }
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/*!
+ Translate a rectangle from pixel into plot coordinates
+
+ \return Rectangle in plot coordinates
+ \sa transform()
+*/
+QRectF QwtPlotPicker::invTransform( const QRect &rect ) const
+{
+ const QwtScaleMap xMap = plot()->canvasMap( d_xAxis );
+ const QwtScaleMap yMap = plot()->canvasMap( d_yAxis );
+
+ return QwtScaleMap::invTransform( xMap, yMap, rect );
+}
+
+/*!
+ Translate a rectangle from plot into pixel coordinates
+ \return Rectangle in pixel coordinates
+ \sa invTransform()
+*/
+QRect QwtPlotPicker::transform( const QRectF &rect ) const
+{
+ const QwtScaleMap xMap = plot()->canvasMap( d_xAxis );
+ const QwtScaleMap yMap = plot()->canvasMap( d_yAxis );
+
+ return QwtScaleMap::transform( xMap, yMap, rect ).toRect();
+}
+
+/*!
+ Translate a point from pixel into plot coordinates
+ \return Point in plot coordinates
+ \sa transform()
+*/
+QPointF QwtPlotPicker::invTransform( const QPoint &pos ) const
+{
+ QwtScaleMap xMap = plot()->canvasMap( d_xAxis );
+ QwtScaleMap yMap = plot()->canvasMap( d_yAxis );
+
+ return QPointF(
+ xMap.invTransform( pos.x() ),
+ yMap.invTransform( pos.y() )
+ );
+}
+
+/*!
+ Translate a point from plot into pixel coordinates
+ \return Point in pixel coordinates
+ \sa invTransform()
+*/
+QPoint QwtPlotPicker::transform( const QPointF &pos ) const
+{
+ QwtScaleMap xMap = plot()->canvasMap( d_xAxis );
+ QwtScaleMap yMap = plot()->canvasMap( d_yAxis );
+
+ const QPointF p( xMap.transform( pos.x() ),
+ yMap.transform( pos.y() ) );
+
+ return p.toPoint();
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_picker.h b/src/libpcp_qwt/src/qwt_plot_picker.h
new file mode 100644
index 0000000..f7d1bee
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_picker.h
@@ -0,0 +1,115 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_PICKER_H
+#define QWT_PLOT_PICKER_H
+
+#include "qwt_global.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_picker.h"
+#include <qvector.h>
+
+class QwtPlot;
+
+/*!
+ \brief QwtPlotPicker provides selections on a plot canvas
+
+ QwtPlotPicker is a QwtPicker tailored for selections on
+ a plot canvas. It is set to a x-Axis and y-Axis and
+ translates all pixel coordinates into this coodinate system.
+*/
+
+class QWT_EXPORT QwtPlotPicker: public QwtPicker
+{
+ Q_OBJECT
+
+public:
+ explicit QwtPlotPicker( QwtPlotCanvas * );
+ virtual ~QwtPlotPicker();
+
+ explicit QwtPlotPicker( int xAxis, int yAxis, QwtPlotCanvas * );
+
+ explicit QwtPlotPicker( int xAxis, int yAxis,
+ RubberBand rubberBand, DisplayMode trackerMode,
+ QwtPlotCanvas * );
+
+ virtual void setAxis( int xAxis, int yAxis );
+
+ int xAxis() const;
+ int yAxis() const;
+
+ QwtPlot *plot();
+ const QwtPlot *plot() const;
+
+ QwtPlotCanvas *canvas();
+ const QwtPlotCanvas *canvas() const;
+
+Q_SIGNALS:
+
+ /*!
+ A signal emitted in case of selectionFlags() & PointSelection.
+ \param pos Selected point
+ */
+ void selected( const QPointF &pos );
+
+ /*!
+ A signal emitted in case of selectionFlags() & RectSelection.
+ \param rect Selected rectangle
+ */
+ void selected( const QRectF &rect );
+
+ /*!
+ A signal emitting the selected points,
+ at the end of a selection.
+
+ \param pa Selected points
+ */
+ void selected( const QVector<QPointF> &pa );
+
+ /*!
+ A signal emitted when a point has been appended to the selection
+
+ \param pos Position of the appended point.
+ \sa append(). moved()
+ */
+ void appended( const QPointF &pos );
+
+ /*!
+ A signal emitted whenever the last appended point of the
+ selection has been moved.
+
+ \param pos Position of the moved last point of the selection.
+ \sa move(), appended()
+ */
+ void moved( const QPointF &pos );
+
+protected:
+ QRectF scaleRect() const;
+
+public:
+ QRectF invTransform( const QRect & ) const;
+ QRect transform( const QRectF & ) const;
+
+ QPointF invTransform( const QPoint & ) const;
+ QPoint transform( const QPointF & ) const;
+
+protected:
+ virtual QwtText trackerText( const QPoint & ) const;
+ virtual QwtText trackerTextF( const QPointF & ) const;
+
+ virtual void move( const QPoint & );
+ virtual void append( const QPoint & );
+ virtual bool end( bool ok = true );
+
+private:
+ int d_xAxis;
+ int d_yAxis;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_rasteritem.cpp b/src/libpcp_qwt/src/qwt_plot_rasteritem.cpp
new file mode 100644
index 0000000..08e02e7
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_rasteritem.cpp
@@ -0,0 +1,904 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_rasteritem.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_scale_map.h"
+#include "qwt_painter.h"
+#include <qapplication.h>
+#include <qdesktopwidget.h>
+#include <qpainter.h>
+#include <qpaintengine.h>
+#include <float.h>
+
+class QwtPlotRasterItem::PrivateData
+{
+public:
+ PrivateData():
+ alpha( -1 ),
+ paintAttributes( QwtPlotRasterItem::PaintInDeviceResolution )
+ {
+ cache.policy = QwtPlotRasterItem::NoCache;
+ }
+
+ int alpha;
+ QwtPlotRasterItem::PaintAttributes paintAttributes;
+
+ struct ImageCache
+ {
+ QwtPlotRasterItem::CachePolicy policy;
+ QRectF area;
+ QSizeF size;
+ QImage image;
+ } cache;
+};
+
+
+static QRectF qwtAlignRect(const QRectF &rect)
+{
+ QRectF r;
+ r.setLeft( qRound( rect.left() ) );
+ r.setRight( qRound( rect.right() ) );
+ r.setTop( qRound( rect.top() ) );
+ r.setBottom( qRound( rect.bottom() ) );
+
+ return r;
+}
+
+static QRectF qwtStripRect(const QRectF &rect, const QRectF &area,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QwtInterval &xInterval, const QwtInterval &yInterval)
+{
+ QRectF r = rect;
+ if ( xInterval.borderFlags() & QwtInterval::ExcludeMinimum )
+ {
+ if ( area.left() <= xInterval.minValue() )
+ {
+ if ( xMap.isInverting() )
+ r.adjust(0, 0, -1, 0);
+ else
+ r.adjust(1, 0, 0, 0);
+ }
+ }
+
+ if ( xInterval.borderFlags() & QwtInterval::ExcludeMaximum )
+ {
+ if ( area.right() >= xInterval.maxValue() )
+ {
+ if ( xMap.isInverting() )
+ r.adjust(1, 0, 0, 0);
+ else
+ r.adjust(0, 0, -1, 0);
+ }
+ }
+
+ if ( yInterval.borderFlags() & QwtInterval::ExcludeMinimum )
+ {
+ if ( area.top() <= yInterval.minValue() )
+ {
+ if ( yMap.isInverting() )
+ r.adjust(0, 0, 0, -1);
+ else
+ r.adjust(0, 1, 0, 0);
+ }
+ }
+
+ if ( yInterval.borderFlags() & QwtInterval::ExcludeMaximum )
+ {
+ if ( area.bottom() >= yInterval.maxValue() )
+ {
+ if ( yMap.isInverting() )
+ r.adjust(0, 1, 0, 0);
+ else
+ r.adjust(0, 0, 0, -1);
+ }
+ }
+
+ return r;
+}
+
+static QImage qwtExpandImage(const QImage &image,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &area, const QRectF &area2, const QRectF &paintRect,
+ const QwtInterval &xInterval, const QwtInterval &yInterval )
+{
+ const QRectF strippedRect = qwtStripRect(paintRect, area2,
+ xMap, yMap, xInterval, yInterval);
+ const QSize sz = strippedRect.toRect().size();
+
+ const int w = image.width();
+ const int h = image.height();
+
+ const QRectF r = QwtScaleMap::transform(xMap, yMap, area).normalized();
+ const double pw = ( r.width() - 1) / w;
+ const double ph = ( r.height() - 1) / h;
+
+ double px0, py0;
+ if ( !xMap.isInverting() )
+ {
+ px0 = xMap.transform( area2.left() );
+ px0 = qRound( px0 );
+ px0 = px0 - xMap.transform( area.left() );
+ }
+ else
+ {
+ px0 = xMap.transform( area2.right() );
+ px0 = qRound( px0 );
+ px0 -= xMap.transform( area.right() );
+
+ px0 -= 1.0;
+ }
+ px0 += strippedRect.left() - paintRect.left();
+
+ if ( !yMap.isInverting() )
+ {
+ py0 = yMap.transform( area2.top() );
+ py0 = qRound( py0 );
+ py0 -= yMap.transform( area.top() );
+ }
+ else
+ {
+ py0 = yMap.transform( area2.bottom() );
+ py0 = qRound( py0 );
+ py0 -= yMap.transform( area.bottom() );
+
+ py0 -= 1.0;
+ }
+ py0 += strippedRect.top() - paintRect.top();
+
+ QImage expanded(sz, image.format());
+
+ switch( image.depth() )
+ {
+ case 32:
+ {
+ for ( int y1 = 0; y1 < h; y1++ )
+ {
+ int yy1;
+ if ( y1 == 0 )
+ {
+ yy1 = 0;
+ }
+ else
+ {
+ yy1 = qRound( y1 * ph - py0 );
+ if ( yy1 < 0 )
+ yy1 = 0;
+ }
+
+ int yy2;
+ if ( y1 == h - 1 )
+ {
+ yy2 = sz.height();
+ }
+ else
+ {
+ yy2 = qRound( ( y1 + 1 ) * ph - py0 );
+ if ( yy2 > sz.height() )
+ yy2 = sz.height();
+ }
+
+ const quint32 *line1 = (const quint32 *) image.scanLine( y1 );
+
+ for ( int x1 = 0; x1 < w; x1++ )
+ {
+ int xx1;
+ if ( x1 == 0 )
+ {
+ xx1 = 0;
+ }
+ else
+ {
+ xx1 = qRound( x1 * pw - px0 );
+ if ( xx1 < 0 )
+ xx1 = 0;
+ }
+
+ int xx2;
+ if ( x1 == w - 1 )
+ {
+ xx2 = sz.width();
+ }
+ else
+ {
+ xx2 = qRound( ( x1 + 1 ) * pw - px0 );
+ if ( xx2 > sz.width() )
+ xx2 = sz.width();
+ }
+
+ const quint32 rgb( line1[x1] );
+ for ( int y2 = yy1; y2 < yy2; y2++ )
+ {
+ quint32 *line2 = ( quint32 *) expanded.scanLine( y2 );
+ for ( int x2 = xx1; x2 < xx2; x2++ )
+ line2[x2] = rgb;
+ }
+ }
+ }
+ break;
+ }
+ case 8:
+ {
+ for ( int y1 = 0; y1 < h; y1++ )
+ {
+ int yy1;
+ if ( y1 == 0 )
+ {
+ yy1 = 0;
+ }
+ else
+ {
+ yy1 = qRound( y1 * ph - py0 );
+ if ( yy1 < 0 )
+ yy1 = 0;
+ }
+
+ int yy2;
+ if ( y1 == h - 1 )
+ {
+ yy2 = sz.height();
+ }
+ else
+ {
+ yy2 = qRound( ( y1 + 1 ) * ph - py0 );
+ if ( yy2 > sz.height() )
+ yy2 = sz.height();
+ }
+
+ const uchar *line1 = image.scanLine( y1 );
+
+ for ( int x1 = 0; x1 < w; x1++ )
+ {
+ int xx1;
+ if ( x1 == 0 )
+ {
+ xx1 = 0;
+ }
+ else
+ {
+ xx1 = qRound( x1 * pw - px0 );
+ if ( xx1 < 0 )
+ xx1 = 0;
+ }
+
+ int xx2;
+ if ( x1 == w - 1 )
+ {
+ xx2 = sz.width();
+ }
+ else
+ {
+ xx2 = qRound( ( x1 + 1 ) * pw - px0 );
+ if ( xx2 > sz.width() )
+ xx2 = sz.width();
+ }
+
+ for ( int y2 = yy1; y2 < yy2; y2++ )
+ {
+ uchar *line2 = expanded.scanLine( y2 );
+ memset( line2 + xx1, line1[x1], xx2 - xx1 );
+ }
+ }
+ }
+ break;
+ }
+ default:
+ expanded = image;
+ }
+
+ return expanded;
+}
+
+static QRectF expandToPixels(const QRectF &rect, const QRectF &pixelRect)
+{
+ const double pw = pixelRect.width();
+ const double ph = pixelRect.height();
+
+ const double dx1 = pixelRect.left() - rect.left();
+ const double dx2 = pixelRect.right() - rect.right();
+ const double dy1 = pixelRect.top() - rect.top();
+ const double dy2 = pixelRect.bottom() - rect.bottom();
+
+ QRectF r;
+ r.setLeft( pixelRect.left() - qCeil( dx1 / pw ) * pw );
+ r.setTop( pixelRect.top() - qCeil( dy1 / ph ) * ph );
+ r.setRight( pixelRect.right() - qFloor( dx2 / pw ) * pw );
+ r.setBottom( pixelRect.bottom() - qFloor( dy2 / ph ) * ph );
+
+ return r;
+}
+
+static void transformMaps( const QTransform &tr,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ QwtScaleMap &xxMap, QwtScaleMap &yyMap )
+{
+ const QPointF p1 = tr.map( QPointF( xMap.p1(), yMap.p1() ) );
+ const QPointF p2 = tr.map( QPointF( xMap.p2(), yMap.p2() ) );
+
+ xxMap = xMap;
+ xxMap.setPaintInterval( p1.x(), p2.x() );
+
+ yyMap = yMap;
+ yyMap.setPaintInterval( p1.y(), p2.y() );
+}
+
+static void adjustMaps( QwtScaleMap &xMap, QwtScaleMap &yMap,
+ const QRectF &area, const QRectF &paintRect)
+{
+ double sx1 = area.left();
+ double sx2 = area.right();
+ if ( xMap.isInverting() )
+ qSwap(sx1, sx2);
+
+ double sy1 = area.top();
+ double sy2 = area.bottom();
+
+ if ( yMap.isInverting() )
+ qSwap(sy1, sy2);
+
+ xMap.setPaintInterval(paintRect.left(), paintRect.right());
+ xMap.setScaleInterval(sx1, sx2);
+
+ yMap.setPaintInterval(paintRect.top(), paintRect.bottom());
+ yMap.setScaleInterval(sy1, sy2);
+}
+
+static bool useCache( QwtPlotRasterItem::CachePolicy policy,
+ const QPainter *painter )
+{
+ bool doCache = false;
+
+ if ( policy == QwtPlotRasterItem::PaintCache )
+ {
+ // Caching doesn't make sense, when the item is
+ // not painted to screen
+
+ switch ( painter->paintEngine()->type() )
+ {
+ case QPaintEngine::SVG:
+ case QPaintEngine::Pdf:
+ case QPaintEngine::PostScript:
+ case QPaintEngine::MacPrinter:
+ case QPaintEngine::Picture:
+ break;
+ default:;
+ doCache = true;
+ }
+ }
+
+ return doCache;
+}
+
+static QImage toRgba( const QImage& image, int alpha )
+{
+ if ( alpha < 0 || alpha >= 255 )
+ return image;
+
+ QImage alphaImage( image.size(), QImage::Format_ARGB32 );
+
+ const QRgb mask1 = qRgba( 0, 0, 0, alpha );
+ const QRgb mask2 = qRgba( 255, 255, 255, 0 );
+ const QRgb mask3 = qRgba( 0, 0, 0, 255 );
+
+ const int w = image.size().width();
+ const int h = image.size().height();
+
+ if ( image.depth() == 8 )
+ {
+ for ( int y = 0; y < h; y++ )
+ {
+ QRgb* alphaLine = ( QRgb* )alphaImage.scanLine( y );
+ const unsigned char *line = image.scanLine( y );
+
+ for ( int x = 0; x < w; x++ )
+ *alphaLine++ = ( image.color( *line++ ) & mask2 ) | mask1;
+ }
+ }
+ else if ( image.depth() == 32 )
+ {
+ for ( int y = 0; y < h; y++ )
+ {
+ QRgb* alphaLine = ( QRgb* )alphaImage.scanLine( y );
+ const QRgb* line = ( const QRgb* ) image.scanLine( y );
+
+ for ( int x = 0; x < w; x++ )
+ {
+ const QRgb rgb = *line++;
+ if ( rgb & mask3 ) // alpha != 0
+ *alphaLine++ = ( rgb & mask2 ) | mask1;
+ else
+ *alphaLine++ = rgb;
+ }
+ }
+ }
+
+ return alphaImage;
+}
+
+//! Constructor
+QwtPlotRasterItem::QwtPlotRasterItem( const QString& title ):
+ QwtPlotItem( QwtText( title ) )
+{
+ init();
+}
+
+//! Constructor
+QwtPlotRasterItem::QwtPlotRasterItem( const QwtText& title ):
+ QwtPlotItem( title )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotRasterItem::~QwtPlotRasterItem()
+{
+ delete d_data;
+}
+
+void QwtPlotRasterItem::init()
+{
+ d_data = new PrivateData();
+
+ setItemAttribute( QwtPlotItem::AutoScale, true );
+ setItemAttribute( QwtPlotItem::Legend, false );
+
+ setZ( 8.0 );
+}
+
+/*!
+ Specify an attribute how to draw the raster item
+
+ \param attribute Paint attribute
+ \param on On/Off
+ /sa PaintAttribute, testPaintAttribute()
+*/
+void QwtPlotRasterItem::setPaintAttribute( PaintAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+}
+
+/*!
+ \brief Return the current paint attributes
+ \sa PaintAttribute, setPaintAttribute()
+*/
+bool QwtPlotRasterItem::testPaintAttribute( PaintAttribute attribute ) const
+{
+ return ( d_data->paintAttributes & attribute );
+}
+
+/*!
+ \brief Set an alpha value for the raster data
+
+ Often a plot has several types of raster data organized in layers.
+ ( f.e a geographical map, with weather statistics ).
+ Using setAlpha() raster items can be stacked easily.
+
+ The alpha value is a value [0, 255] to
+ control the transparency of the image. 0 represents a fully
+ transparent color, while 255 represents a fully opaque color.
+
+ \param alpha Alpha value
+
+ - alpha >= 0\n
+ All alpha values of the pixels returned by renderImage() will be set to
+ alpha, beside those with an alpha value of 0 (invalid pixels).
+ - alpha < 0
+ The alpha values returned by renderImage() are not changed.
+
+ The default alpha value is -1.
+
+ \sa alpha()
+*/
+void QwtPlotRasterItem::setAlpha( int alpha )
+{
+ if ( alpha < 0 )
+ alpha = -1;
+
+ if ( alpha > 255 )
+ alpha = 255;
+
+ if ( alpha != d_data->alpha )
+ {
+ d_data->alpha = alpha;
+
+ itemChanged();
+ }
+}
+
+/*!
+ \return Alpha value of the raster item
+ \sa setAlpha()
+*/
+int QwtPlotRasterItem::alpha() const
+{
+ return d_data->alpha;
+}
+
+/*!
+ Change the cache policy
+
+ The default policy is NoCache
+
+ \param policy Cache policy
+ \sa CachePolicy, cachePolicy()
+*/
+void QwtPlotRasterItem::setCachePolicy(
+ QwtPlotRasterItem::CachePolicy policy )
+{
+ if ( d_data->cache.policy != policy )
+ {
+ d_data->cache.policy = policy;
+
+ invalidateCache();
+ itemChanged();
+ }
+}
+
+/*!
+ \return Cache policy
+ \sa CachePolicy, setCachePolicy()
+*/
+QwtPlotRasterItem::CachePolicy QwtPlotRasterItem::cachePolicy() const
+{
+ return d_data->cache.policy;
+}
+
+/*!
+ Invalidate the paint cache
+ \sa setCachePolicy()
+*/
+void QwtPlotRasterItem::invalidateCache()
+{
+ d_data->cache.image = QImage();
+ d_data->cache.area = QRect();
+ d_data->cache.size = QSize();
+}
+
+/*!
+ \brief Pixel hint
+
+ The geometry of a pixel is used to calculated the resolution and
+ alignment of the rendered image.
+
+ Width and height of the hint need to be the horizontal
+ and vertical distances between 2 neighboured points.
+ The center of the hint has to be the position of any point
+ ( it doesn't matter which one ).
+
+ Limiting the resolution of the image might significantly improve
+ the performance and heavily reduce the amount of memory when rendering
+ a QImage from the raster data.
+
+ The default implementation returns an empty rectangle (QRectF()),
+ meaning, that the image will be rendered in target device ( f.e screen )
+ resolution.
+
+ \param area In most implementations the resolution of the data doesn't
+ depend on the requested area.
+
+ \return Bounding rectangle of a pixel
+
+ \sa render(), renderImage()
+*/
+QRectF QwtPlotRasterItem::pixelHint( const QRectF &area ) const
+{
+ Q_UNUSED( area );
+ return QRectF();
+}
+
+/*!
+ \brief Draw the raster data
+ \param painter Painter
+ \param xMap X-Scale Map
+ \param yMap Y-Scale Map
+ \param canvasRect Contents rect of the plot canvas
+*/
+void QwtPlotRasterItem::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ if ( canvasRect.isEmpty() || d_data->alpha == 0 )
+ return;
+
+ const bool doCache = useCache( d_data->cache.policy, painter );
+
+ const QwtInterval xInterval = interval( Qt::XAxis );
+ const QwtInterval yInterval = interval( Qt::YAxis );
+
+ /*
+ Scaling a rastered image always results in a loss of
+ precision/quality. So we always render the image in
+ paint device resolution.
+ */
+
+ QwtScaleMap xxMap, yyMap;
+ transformMaps( painter->transform(), xMap, yMap, xxMap, yyMap );
+
+ QRectF paintRect = painter->transform().mapRect( canvasRect );
+ QRectF area = QwtScaleMap::invTransform( xxMap, yyMap, paintRect );
+
+ const QRectF br = boundingRect();
+ if ( br.isValid() && !br.contains( area ) )
+ {
+ area &= br;
+ if ( !area.isValid() )
+ return;
+
+ paintRect = QwtScaleMap::transform( xxMap, yyMap, area );
+ }
+
+ QRectF imageRect;
+ QImage image;
+
+ QRectF pixelRect = pixelHint(area);
+ if ( !pixelRect.isEmpty() )
+ {
+ // pixel in target device resolution
+ const double dx = qAbs( xxMap.invTransform( 1 ) - xxMap.invTransform( 0 ) );
+ const double dy = qAbs( yyMap.invTransform( 1 ) - yyMap.invTransform( 0 ) );
+
+ if ( dx > pixelRect.width() && dy > pixelRect.height() )
+ {
+ /*
+ When the resolution of the data pixels is higher than
+ the resolution of the target device we render in
+ target device resolution.
+ */
+ pixelRect = QRectF();
+ }
+ }
+
+ if ( pixelRect.isEmpty() )
+ {
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ // we want to have maps, where the boundaries of
+ // the aligned paint rectangle exactly match the area
+
+ paintRect = qwtAlignRect(paintRect);
+ adjustMaps(xxMap, yyMap, area, paintRect);
+ }
+
+ // When we have no information about position and size of
+ // data pixels we render in resolution of the paint device.
+
+ image = compose(xxMap, yyMap,
+ area, paintRect, paintRect.size().toSize(), doCache);
+ if ( image.isNull() )
+ return;
+
+ // Remove pixels at the boundaries, when explicitly
+ // excluded in the intervals
+
+ imageRect = qwtStripRect(paintRect, area,
+ xxMap, yyMap, xInterval, yInterval);
+
+ if ( imageRect != paintRect )
+ {
+ const QRect r(
+ qRound( imageRect.x() - paintRect.x()),
+ qRound( imageRect.y() - paintRect.y() ),
+ qRound( imageRect.width() ),
+ qRound( imageRect.height() ) );
+
+ image = image.copy(r);
+ }
+ }
+ else
+ {
+ if ( QwtPainter::roundingAlignment( painter ) )
+ paintRect = qwtAlignRect(paintRect);
+
+ // align the area to the data pixels
+ QRectF imageArea = expandToPixels(area, pixelRect);
+
+ if ( imageArea.right() == xInterval.maxValue() &&
+ !( xInterval.borderFlags() & QwtInterval::ExcludeMaximum ) )
+ {
+ imageArea.adjust(0, 0, pixelRect.width(), 0);
+ }
+ if ( imageArea.bottom() == yInterval.maxValue() &&
+ !( yInterval.borderFlags() & QwtInterval::ExcludeMaximum ) )
+ {
+ imageArea.adjust(0, 0, 0, pixelRect.height() );
+ }
+
+ QSize imageSize;
+ imageSize.setWidth( qRound( imageArea.width() / pixelRect.width() ) );
+ imageSize.setHeight( qRound( imageArea.height() / pixelRect.height() ) );
+ image = compose(xxMap, yyMap,
+ imageArea, paintRect, imageSize, doCache );
+ if ( image.isNull() )
+ return;
+
+ imageRect = qwtStripRect(paintRect, area,
+ xxMap, yyMap, xInterval, yInterval);
+
+ if ( ( image.width() > 1 || image.height() > 1 ) &&
+ testPaintAttribute( PaintInDeviceResolution ) )
+ {
+ // Because of rounding errors the pixels
+ // need to be expanded manually to rectangles of
+ // different sizes
+
+ image = qwtExpandImage(image, xxMap, yyMap,
+ imageArea, area, paintRect, xInterval, yInterval );
+ }
+ }
+
+ painter->save();
+ painter->setWorldTransform( QTransform() );
+
+ QwtPainter::drawImage( painter, imageRect, image );
+
+ painter->restore();
+}
+
+/*!
+ \return Bounding interval for an axis
+
+ This method is intended to be reimplemented by derived classes.
+ The default implementation returns an invalid interval.
+
+ \param axis X, Y, or Z axis
+*/
+QwtInterval QwtPlotRasterItem::interval(Qt::Axis axis) const
+{
+ Q_UNUSED( axis );
+ return QwtInterval();
+}
+
+/*!
+ \return Bounding rect of the data
+ \sa QwtPlotRasterItem::interval()
+*/
+QRectF QwtPlotRasterItem::boundingRect() const
+{
+ const QwtInterval intervalX = interval( Qt::XAxis );
+ const QwtInterval intervalY = interval( Qt::YAxis );
+
+ if ( !intervalX.isValid() && !intervalY.isValid() )
+ return QRectF(); // no bounding rect
+
+ QRectF r;
+
+ if ( intervalX.isValid() )
+ {
+ r.setLeft( intervalX.minValue() );
+ r.setRight( intervalX.maxValue() );
+ }
+ else
+ {
+ r.setLeft(-0.5 * FLT_MAX);
+ r.setWidth(FLT_MAX);
+ }
+
+ if ( intervalY.isValid() )
+ {
+ r.setTop( intervalY.minValue() );
+ r.setBottom( intervalY.maxValue() );
+ }
+ else
+ {
+ r.setTop(-0.5 * FLT_MAX);
+ r.setHeight(FLT_MAX);
+ }
+
+ return r.normalized();
+}
+
+QImage QwtPlotRasterItem::compose(
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &imageArea, const QRectF &paintRect,
+ const QSize &imageSize, bool doCache) const
+{
+ QImage image;
+ if ( imageArea.isEmpty() || paintRect.isEmpty() || imageSize.isEmpty() )
+ return image;
+
+ if ( doCache )
+ {
+ if ( !d_data->cache.image.isNull()
+ && d_data->cache.area == imageArea
+ && d_data->cache.size == paintRect.size() )
+ {
+ image = d_data->cache.image;
+ }
+ }
+
+ if ( image.isNull() )
+ {
+ double dx = 0.0;
+ if ( paintRect.toRect().width() > imageSize.width() )
+ dx = imageArea.width() / imageSize.width();
+
+ const QwtScaleMap xxMap =
+ imageMap(Qt::Horizontal, xMap, imageArea, imageSize, dx);
+
+ double dy = 0.0;
+ if ( paintRect.toRect().height() > imageSize.height() )
+ dy = imageArea.height() / imageSize.height();
+
+ const QwtScaleMap yyMap =
+ imageMap(Qt::Vertical, yMap, imageArea, imageSize, dy);
+
+ image = renderImage( xxMap, yyMap, imageArea, imageSize );
+
+ if ( doCache )
+ {
+ d_data->cache.area = imageArea;
+ d_data->cache.size = paintRect.size();
+ d_data->cache.image = image;
+ }
+ }
+
+ if ( d_data->alpha >= 0 && d_data->alpha < 255 )
+ image = toRgba( image, d_data->alpha );
+
+ return image;
+}
+
+/*!
+ \brief Calculate a scale map for painting to an image
+
+ \param orientation Orientation, Qt::Horizontal means a X axis
+ \param map Scale map for rendering the plot item
+ \param area Area to be painted on the image
+ \param imageSize Image size
+ \param pixelSize Width/Height of a data pixel
+*/
+QwtScaleMap QwtPlotRasterItem::imageMap(
+ Qt::Orientation orientation,
+ const QwtScaleMap &map, const QRectF &area,
+ const QSize &imageSize, double pixelSize) const
+{
+ double p1, p2, s1, s2;
+
+ if ( orientation == Qt::Horizontal )
+ {
+ p1 = 0.0;
+ p2 = imageSize.width();
+ s1 = area.left();
+ s2 = area.right();
+ }
+ else
+ {
+ p1 = 0.0;
+ p2 = imageSize.height();
+ s1 = area.top();
+ s2 = area.bottom();
+ }
+
+ if ( pixelSize > 0.0 )
+ {
+ double off = 0.5 * pixelSize;
+ if ( map.isInverting() )
+ off = -off;
+
+ s1 += off;
+ s2 += off;
+ }
+ else
+ {
+ p2--;
+ }
+
+ if ( map.isInverting() && ( s1 < s2 ) )
+ qSwap( s1, s2 );
+
+ QwtScaleMap newMap = map;
+ newMap.setPaintInterval( p1, p2 );
+ newMap.setScaleInterval( s1, s2 );
+
+ return newMap;
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_rasteritem.h b/src/libpcp_qwt/src/qwt_plot_rasteritem.h
new file mode 100644
index 0000000..b2292d8
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_rasteritem.h
@@ -0,0 +1,146 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_RASTERITEM_H
+#define QWT_PLOT_RASTERITEM_H
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include "qwt_interval.h"
+#include <qglobal.h>
+#include <qstring.h>
+#include <qimage.h>
+
+/*!
+ \brief A class, which displays raster data
+
+ Raster data is a grid of pixel values, that can be represented
+ as a QImage. It is used for many types of information like
+ spectrograms, cartograms, geographical maps ...
+
+ Often a plot has several types of raster data organized in layers.
+ ( f.e a geographical map, with weather statistics ).
+ Using setAlpha() raster items can be stacked easily.
+
+ QwtPlotRasterItem is only implemented for images of the following formats:
+ QImage::Format_Indexed8, QImage::Format_ARGB32.
+
+ \sa QwtPlotSpectrogram
+*/
+
+class QWT_EXPORT QwtPlotRasterItem: public QwtPlotItem
+{
+public:
+ /*!
+ - NoCache\n
+ renderImage() is called, whenever the item has to be repainted
+ - PaintCache\n
+ renderImage() is called, whenever the image cache is not valid,
+ or the scales, or the size of the canvas has changed. This type
+ of cache is only useful for improving the performance of hide/show
+ operations. All other situations are already handled by the
+ plot canvas cache.
+
+ The default policy is NoCache
+ */
+ enum CachePolicy
+ {
+ NoCache,
+ PaintCache
+ };
+
+ /*!
+ Attributes to modify the drawing algorithm.
+ \sa setPaintAttribute(), testPaintAttribute()
+ */
+ enum PaintAttribute
+ {
+ /*!
+ When the image is rendered according to the data pixels
+ ( QwtRasterData::pixelHint() ) it can be expanded to paint
+ device resolution before it is passed to QPainter.
+ The expansion algorithm rounds the pixel borders in the same
+ way as the axis ticks, what is usually better than the
+ scaling algorithm implemented in Qt.
+ Disabling this flag might make sense, to reduce the size of a
+ document/file. If this is possible for a document format
+ depends on the implementation of the specific QPaintEngine.
+ */
+
+ PaintInDeviceResolution = 1
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ explicit QwtPlotRasterItem( const QString& title = QString::null );
+ explicit QwtPlotRasterItem( const QwtText& title );
+ virtual ~QwtPlotRasterItem();
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ void setAlpha( int alpha );
+ int alpha() const;
+
+ void setCachePolicy( CachePolicy );
+ CachePolicy cachePolicy() const;
+
+ void invalidateCache();
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &rect ) const;
+
+ virtual QRectF pixelHint( const QRectF & ) const;
+
+ virtual QwtInterval interval(Qt::Axis) const;
+ virtual QRectF boundingRect() const;
+
+protected:
+ /*!
+ \brief Render an image
+
+ An implementation of render() might iterate over all
+ pixels of imageRect. Each pixel has to be translated into
+ the corresponding position in scale coordinates using the maps.
+ This position can be used to look up a value in a implementation
+ specific way and to map it into a color.
+
+ \param xMap X-Scale Map
+ \param yMap Y-Scale Map
+ \param area Requested area for the image in scale coordinates
+ \param imageSize Requested size of the image
+ */
+ virtual QImage renderImage( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap, const QRectF &area,
+ const QSize &imageSize ) const = 0;
+
+ virtual QwtScaleMap imageMap( Qt::Orientation,
+ const QwtScaleMap &map, const QRectF &area,
+ const QSize &imageSize, double pixelSize) const;
+
+private:
+ QwtPlotRasterItem( const QwtPlotRasterItem & );
+ QwtPlotRasterItem &operator=( const QwtPlotRasterItem & );
+
+ void init();
+
+ QImage compose( const QwtScaleMap &, const QwtScaleMap &,
+ const QRectF &imageArea, const QRectF &paintRect,
+ const QSize &imageSize, bool doCache) const;
+
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRasterItem::PaintAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_renderer.cpp b/src/libpcp_qwt/src/qwt_plot_renderer.cpp
new file mode 100644
index 0000000..4dfbfec
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_renderer.cpp
@@ -0,0 +1,897 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_renderer.h"
+#include "qwt_plot.h"
+#include "qwt_painter.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_plot_layout.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_dyngrid_layout.h"
+#include "qwt_scale_widget.h"
+#include "qwt_scale_engine.h"
+#include "qwt_text.h"
+#include "qwt_text_label.h"
+#include "qwt_math.h"
+#include <qpainter.h>
+#include <qpaintengine.h>
+#include <qtransform.h>
+#include <qprinter.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qimagewriter.h>
+#include <qfileinfo.h>
+#ifndef QWT_NO_SVG
+#ifdef QT_SVG_LIB
+#include <qsvggenerator.h>
+#endif
+#endif
+
+class QwtPlotRenderer::PrivateData
+{
+public:
+ PrivateData():
+ discardFlags( QwtPlotRenderer::DiscardBackground ),
+ layoutFlags( QwtPlotRenderer::DefaultLayout )
+ {
+ }
+
+ QwtPlotRenderer::DiscardFlags discardFlags;
+ QwtPlotRenderer::LayoutFlags layoutFlags;
+};
+
+static void qwtRenderBackground( QPainter *painter,
+ const QRectF &rect, const QWidget *widget )
+{
+ if ( widget->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ QStyleOption opt;
+ opt.initFrom( widget );
+ opt.rect = rect.toAlignedRect();
+
+ widget->style()->drawPrimitive(
+ QStyle::PE_Widget, &opt, painter, widget);
+ }
+ else
+ {
+ const QBrush brush =
+ widget->palette().brush( widget->backgroundRole() );
+
+ painter->fillRect( rect, brush );
+ }
+}
+
+/*!
+ Constructor
+ \param parent Parent object
+*/
+QwtPlotRenderer::QwtPlotRenderer( QObject *parent ):
+ QObject( parent )
+{
+ d_data = new PrivateData;
+}
+
+//! Destructor
+QwtPlotRenderer::~QwtPlotRenderer()
+{
+ delete d_data;
+}
+
+/*!
+ Change a flag, indicating what to discard from rendering
+
+ \param flag Flag to change
+ \param on On/Off
+
+ \sa DiscardFlag, testDiscardFlag(), setDiscardFlags(), discardFlags()
+*/
+void QwtPlotRenderer::setDiscardFlag( DiscardFlag flag, bool on )
+{
+ if ( on )
+ d_data->discardFlags |= flag;
+ else
+ d_data->discardFlags &= ~flag;
+}
+
+/*!
+ Check if a flag is set.
+
+ \param flag Flag to be tested
+ \sa DiscardFlag, setDiscardFlag(), setDiscardFlags(), discardFlags()
+*/
+bool QwtPlotRenderer::testDiscardFlag( DiscardFlag flag ) const
+{
+ return d_data->discardFlags & flag;
+}
+
+/*!
+ Set the flags, indicating what to discard from rendering
+
+ \param flags Flags
+ \sa DiscardFlag, setDiscardFlag(), testDiscardFlag(), discardFlags()
+*/
+void QwtPlotRenderer::setDiscardFlags( DiscardFlags flags )
+{
+ d_data->discardFlags = flags;
+}
+
+/*!
+ \return Flags, indicating what to discard from rendering
+ \sa DiscardFlag, setDiscardFlags(), setDiscardFlag(), testDiscardFlag()
+*/
+QwtPlotRenderer::DiscardFlags QwtPlotRenderer::discardFlags() const
+{
+ return d_data->discardFlags;
+}
+
+/*!
+ Change a layout flag
+
+ \param flag Flag to change
+ \param on On/Off
+
+ \sa LayoutFlag, testLayoutFlag(), setLayoutFlags(), layoutFlags()
+*/
+void QwtPlotRenderer::setLayoutFlag( LayoutFlag flag, bool on )
+{
+ if ( on )
+ d_data->layoutFlags |= flag;
+ else
+ d_data->layoutFlags &= ~flag;
+}
+
+/*!
+ Check if a flag is set.
+
+ \param flag Flag to be tested
+ \sa LayoutFlag, setLayoutFlag(), setLayoutFlags(), layoutFlags()
+*/
+bool QwtPlotRenderer::testLayoutFlag( LayoutFlag flag ) const
+{
+ return d_data->layoutFlags & flag;
+}
+
+/*!
+ Set the layout flags
+
+ \param flags Flags
+ \sa LayoutFlag, setLayoutFlag(), testLayoutFlag(), layoutFlags()
+*/
+void QwtPlotRenderer::setLayoutFlags( LayoutFlags flags )
+{
+ d_data->layoutFlags = flags;
+}
+
+/*!
+ \return Layout flags
+ \sa LayoutFlag, setLayoutFlags(), setLayoutFlag(), testLayoutFlag()
+*/
+QwtPlotRenderer::LayoutFlags QwtPlotRenderer::layoutFlags() const
+{
+ return d_data->layoutFlags;
+}
+
+/*!
+ Render a plot to a file
+
+ The format of the document will be autodetected from the
+ suffix of the filename.
+
+ \param plot Plot widget
+ \param fileName Path of the file, where the document will be stored
+ \param sizeMM Size for the document in millimeters.
+ \param resolution Resolution in dots per Inch (dpi)
+*/
+void QwtPlotRenderer::renderDocument( QwtPlot *plot,
+ const QString &fileName, const QSizeF &sizeMM, int resolution )
+{
+ renderDocument( plot, fileName,
+ QFileInfo( fileName ).suffix(), sizeMM, resolution );
+}
+
+/*!
+ Render a plot to a file
+
+ Supported formats are:
+
+ - pdf\n
+ Portable Document Format PDF
+ - ps\n
+ Postcript
+ - svg\n
+ Scalable Vector Graphics SVG
+ - all image formats supported by Qt\n
+ see QImageWriter::supportedImageFormats()
+
+ Scalable vector graphic formats like PDF or SVG are superior to
+ raster graphics formats.
+
+ \param plot Plot widget
+ \param fileName Path of the file, where the document will be stored
+ \param format Format for the document
+ \param sizeMM Size for the document in millimeters.
+ \param resolution Resolution in dots per Inch (dpi)
+
+ \sa renderTo(), render(), QwtPainter::setRoundingAlignment()
+*/
+void QwtPlotRenderer::renderDocument( QwtPlot *plot,
+ const QString &fileName, const QString &format,
+ const QSizeF &sizeMM, int resolution )
+{
+ if ( plot == NULL || sizeMM.isEmpty() || resolution <= 0 )
+ return;
+
+ QString title = plot->title().text();
+ if ( title.isEmpty() )
+ title = "Plot Document";
+
+ const double mmToInch = 1.0 / 25.4;
+ const QSizeF size = sizeMM * mmToInch * resolution;
+
+ const QRectF documentRect( 0.0, 0.0, size.width(), size.height() );
+
+ const QString fmt = format.toLower();
+ if ( fmt == "pdf" )
+ {
+#ifndef QT_NO_PRINTER
+ QPrinter printer;
+ printer.setFullPage( true );
+ printer.setPaperSize( sizeMM, QPrinter::Millimeter );
+ printer.setDocName( title );
+ printer.setOutputFileName( fileName );
+ printer.setOutputFormat( QPrinter::PdfFormat );
+ printer.setResolution( resolution );
+
+ QPainter painter( &printer );
+ render( plot, &painter, documentRect );
+#endif
+ }
+ else if ( fmt == "ps" )
+ {
+#if QT_VERSION < 0x050000
+#ifndef QT_NO_PRINTER
+ QPrinter printer;
+ printer.setFullPage( true );
+ printer.setPaperSize( sizeMM, QPrinter::Millimeter );
+ printer.setDocName( title );
+ printer.setOutputFileName( fileName );
+ printer.setOutputFormat( QPrinter::PostScriptFormat );
+ printer.setResolution( resolution );
+
+ QPainter painter( &printer );
+ render( plot, &painter, documentRect );
+#endif
+#endif
+ }
+ else if ( fmt == "svg" )
+ {
+#ifndef QWT_NO_SVG
+#ifdef QT_SVG_LIB
+#if QT_VERSION >= 0x040500
+ QSvgGenerator generator;
+ generator.setTitle( title );
+ generator.setFileName( fileName );
+ generator.setResolution( resolution );
+ generator.setViewBox( documentRect );
+
+ QPainter painter( &generator );
+ render( plot, &painter, documentRect );
+#endif
+#endif
+#endif
+ }
+ else
+ {
+ if ( QImageWriter::supportedImageFormats().indexOf(
+ format.toLatin1() ) >= 0 )
+ {
+ const QRect imageRect = documentRect.toRect();
+ const int dotsPerMeter = qRound( resolution * mmToInch * 1000.0 );
+
+ QImage image( imageRect.size(), QImage::Format_ARGB32 );
+ image.setDotsPerMeterX( dotsPerMeter );
+ image.setDotsPerMeterY( dotsPerMeter );
+ image.fill( QColor( Qt::white ).rgb() );
+
+ QPainter painter( &image );
+ render( plot, &painter, imageRect );
+ painter.end();
+
+ image.save( fileName, format.toLatin1() );
+ }
+ }
+}
+
+/*!
+ \brief Render the plot to a \c QPaintDevice
+
+ This function renders the contents of a QwtPlot instance to
+ \c QPaintDevice object. The target rectangle is derived from
+ its device metrics.
+
+ \param plot Plot to be rendered
+ \param paintDevice device to paint on, f.e a QImage
+
+ \sa renderDocument(), render(), QwtPainter::setRoundingAlignment()
+*/
+
+void QwtPlotRenderer::renderTo(
+ QwtPlot *plot, QPaintDevice &paintDevice ) const
+{
+ int w = paintDevice.width();
+ int h = paintDevice.height();
+
+ QPainter p( &paintDevice );
+ render( plot, &p, QRectF( 0, 0, w, h ) );
+}
+
+/*!
+ \brief Render the plot to a QPrinter
+
+ This function renders the contents of a QwtPlot instance to
+ \c QPaintDevice object. The size is derived from the printer
+ metrics.
+
+ \param plot Plot to be rendered
+ \param printer Printer to paint on
+
+ \sa renderDocument(), render(), QwtPainter::setRoundingAlignment()
+*/
+
+#ifndef QT_NO_PRINTER
+
+void QwtPlotRenderer::renderTo(
+ QwtPlot *plot, QPrinter &printer ) const
+{
+ int w = printer.width();
+ int h = printer.height();
+
+ QRectF rect( 0, 0, w, h );
+ double aspect = rect.width() / rect.height();
+ if ( ( aspect < 1.0 ) )
+ rect.setHeight( aspect * rect.width() );
+
+ QPainter p( &printer );
+ render( plot, &p, rect );
+}
+
+#endif
+
+#ifndef QWT_NO_SVG
+#ifdef QT_SVG_LIB
+#if QT_VERSION >= 0x040500
+
+/*!
+ \brief Render the plot to a QSvgGenerator
+
+ If the generator has a view box, the plot will be rendered into it.
+ If it has no viewBox but a valid size the target coordinates
+ will be (0, 0, generator.width(), generator.height()). Otherwise
+ the target rectangle will be QRectF(0, 0, 800, 600);
+
+ \param plot Plot to be rendered
+ \param generator SVG generator
+*/
+void QwtPlotRenderer::renderTo(
+ QwtPlot *plot, QSvgGenerator &generator ) const
+{
+ QRectF rect = generator.viewBoxF();
+ if ( rect.isEmpty() )
+ rect.setRect( 0, 0, generator.width(), generator.height() );
+
+ if ( rect.isEmpty() )
+ rect.setRect( 0, 0, 800, 600 ); // something
+
+ QPainter p( &generator );
+ render( plot, &p, rect );
+}
+#endif
+#endif
+#endif
+
+/*!
+ Paint the contents of a QwtPlot instance into a given rectangle.
+
+ \param plot Plot to be rendered
+ \param painter Painter
+ \param plotRect Bounding rectangle
+
+ \sa renderDocument(), renderTo(), QwtPainter::setRoundingAlignment()
+*/
+void QwtPlotRenderer::render( QwtPlot *plot,
+ QPainter *painter, const QRectF &plotRect ) const
+{
+ int axisId;
+
+ if ( painter == 0 || !painter->isActive() ||
+ !plotRect.isValid() || plot->size().isNull() )
+ return;
+
+ if ( !( d_data->discardFlags & DiscardBackground ) )
+ qwtRenderBackground( painter, plotRect, plot );
+
+ /*
+ The layout engine uses the same methods as they are used
+ by the Qt layout system. Therefore we need to calculate the
+ layout in screen coordinates and paint with a scaled painter.
+ */
+ QTransform transform;
+ transform.scale(
+ double( painter->device()->logicalDpiX() ) / plot->logicalDpiX(),
+ double( painter->device()->logicalDpiY() ) / plot->logicalDpiY() );
+
+
+ QRectF layoutRect = transform.inverted().mapRect( plotRect );
+
+ if ( !( d_data->discardFlags & DiscardBackground ) )
+ {
+ // subtract the contents margins
+
+ int left, top, right, bottom;
+ plot->getContentsMargins( &left, &top, &right, &bottom );
+ layoutRect.adjust( left, top, -right, -bottom );
+ }
+
+ int baseLineDists[QwtPlot::axisCnt];
+ if ( d_data->layoutFlags & FrameWithScales )
+ {
+ for ( axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
+ {
+ QwtScaleWidget *scaleWidget = plot->axisWidget( axisId );
+ if ( scaleWidget )
+ {
+ baseLineDists[axisId] = scaleWidget->margin();
+ scaleWidget->setMargin( 0 );
+ }
+
+ if ( !plot->axisEnabled( axisId ) )
+ {
+ int left = 0;
+ int right = 0;
+ int top = 0;
+ int bottom = 0;
+
+ // When we have a scale the frame is painted on
+ // the position of the backbone - otherwise we
+ // need to introduce a margin around the canvas
+
+ switch( axisId )
+ {
+ case QwtPlot::yLeft:
+ layoutRect.adjust( 1, 0, 0, 0 );
+ break;
+ case QwtPlot::yRight:
+ layoutRect.adjust( 0, 0, -1, 0 );
+ break;
+ case QwtPlot::xTop:
+ layoutRect.adjust( 0, 1, 0, 0 );
+ break;
+ case QwtPlot::xBottom:
+ layoutRect.adjust( 0, 0, 0, -1 );
+ break;
+ default:
+ break;
+ }
+ layoutRect.adjust( left, top, right, bottom );
+ }
+ }
+ }
+
+ // Calculate the layout for the document.
+
+ QwtPlotLayout::Options layoutOptions =
+ QwtPlotLayout::IgnoreScrollbars | QwtPlotLayout::IgnoreFrames;
+
+ if ( d_data->discardFlags & DiscardLegend )
+ layoutOptions |= QwtPlotLayout::IgnoreLegend;
+
+ plot->plotLayout()->activate( plot, layoutRect, layoutOptions );
+
+ // now start painting
+
+ painter->save();
+ painter->setWorldTransform( transform, true );
+
+ // canvas
+
+ QwtScaleMap maps[QwtPlot::axisCnt];
+ buildCanvasMaps( plot, plot->plotLayout()->canvasRect(), maps );
+ renderCanvas( plot, painter, plot->plotLayout()->canvasRect(), maps );
+
+ if ( !( d_data->discardFlags & DiscardTitle )
+ && ( !plot->titleLabel()->text().isEmpty() ) )
+ {
+ renderTitle( plot, painter, plot->plotLayout()->titleRect() );
+ }
+
+ if ( !( d_data->discardFlags & DiscardLegend )
+ && plot->legend() && !plot->legend()->isEmpty() )
+ {
+ renderLegend( plot, painter, plot->plotLayout()->legendRect() );
+ }
+
+ for ( axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
+ {
+ QwtScaleWidget *scaleWidget = plot->axisWidget( axisId );
+ if ( scaleWidget )
+ {
+ int baseDist = scaleWidget->margin();
+
+ int startDist, endDist;
+ scaleWidget->getBorderDistHint( startDist, endDist );
+
+ renderScale( plot, painter, axisId, startDist, endDist,
+ baseDist, plot->plotLayout()->scaleRect( axisId ) );
+ }
+ }
+
+
+ plot->plotLayout()->invalidate();
+
+ // reset all widgets with their original attributes.
+ if ( d_data->layoutFlags & FrameWithScales )
+ {
+ // restore the previous base line dists
+
+ for ( axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
+ {
+ QwtScaleWidget *scaleWidget = plot->axisWidget( axisId );
+ if ( scaleWidget )
+ scaleWidget->setMargin( baseLineDists[axisId] );
+ }
+ }
+
+ painter->restore();
+}
+
+/*!
+ Render the title into a given rectangle.
+
+ \param plot Plot widget
+ \param painter Painter
+ \param rect Bounding rectangle
+*/
+void QwtPlotRenderer::renderTitle( const QwtPlot *plot,
+ QPainter *painter, const QRectF &rect ) const
+{
+ painter->setFont( plot->titleLabel()->font() );
+
+ const QColor color = plot->titleLabel()->palette().color(
+ QPalette::Active, QPalette::Text );
+
+ painter->setPen( color );
+ plot->titleLabel()->text().draw( painter, rect );
+}
+
+/*!
+ Render the legend into a given rectangle.
+
+ \param plot Plot widget
+ \param painter Painter
+ \param rect Bounding rectangle
+*/
+void QwtPlotRenderer::renderLegend( const QwtPlot *plot,
+ QPainter *painter, const QRectF &rect ) const
+{
+ if ( !plot->legend() || plot->legend()->isEmpty() )
+ return;
+
+ if ( !( d_data->discardFlags & DiscardBackground ) )
+ {
+ if ( plot->legend()->autoFillBackground() ||
+ plot->legend()->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ qwtRenderBackground( painter, rect, plot->legend() );
+ }
+ }
+
+ const QwtDynGridLayout *legendLayout = qobject_cast<QwtDynGridLayout *>(
+ plot->legend()->contentsWidget()->layout() );
+ if ( legendLayout == NULL )
+ return;
+
+ uint numCols = legendLayout->columnsForWidth( qFloor( rect.width() ) );
+ QList<QRect> itemRects =
+ legendLayout->layoutItems( rect.toRect(), numCols );
+
+ int index = 0;
+
+ for ( int i = 0; i < legendLayout->count(); i++ )
+ {
+ QLayoutItem *item = legendLayout->itemAt( i );
+ QWidget *w = item->widget();
+ if ( w )
+ {
+ painter->save();
+
+ painter->setClipRect( itemRects[index] );
+ renderLegendItem( plot, painter, w, itemRects[index] );
+
+ index++;
+ painter->restore();
+ }
+ }
+}
+
+/*!
+ Render the legend item into a given rectangle.
+
+ \param plot Plot widget
+ \param painter Painter
+ \param widget Widget representing a legend item
+ \param rect Bounding rectangle
+
+ \note When widget is not derived from QwtLegendItem renderLegendItem
+ does nothing and needs to be overloaded
+*/
+void QwtPlotRenderer::renderLegendItem( const QwtPlot *plot,
+ QPainter *painter, const QWidget *widget, const QRectF &rect ) const
+{
+ if ( !( d_data->discardFlags & DiscardBackground ) )
+ {
+ if ( widget->autoFillBackground() ||
+ widget->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ qwtRenderBackground( painter, rect, widget );
+ }
+ }
+
+ const QwtLegendItem *item = qobject_cast<const QwtLegendItem *>( widget );
+ if ( item )
+ {
+ const QSize sz = item->identifierSize();
+
+ const QRectF identifierRect( rect.x() + item->margin(),
+ rect.center().y() - 0.5 * sz.height(), sz.width(), sz.height() );
+
+ QwtLegendItemManager *itemManger = plot->legend()->find( item );
+ if ( itemManger )
+ {
+ painter->save();
+ painter->setClipRect( identifierRect, Qt::IntersectClip );
+ itemManger->drawLegendIdentifier( painter, identifierRect );
+ painter->restore();
+ }
+
+ // Label
+
+ QRectF titleRect = rect;
+ titleRect.setX( identifierRect.right() + 2 * item->spacing() );
+
+ painter->setFont( item->font() );
+ item->text().draw( painter, titleRect );
+ }
+}
+
+/*!
+ \brief Paint a scale into a given rectangle.
+ Paint the scale into a given rectangle.
+
+ \param plot Plot widget
+ \param painter Painter
+ \param axisId Axis
+ \param startDist Start border distance
+ \param endDist End border distance
+ \param baseDist Base distance
+ \param rect Bounding rectangle
+*/
+void QwtPlotRenderer::renderScale( const QwtPlot *plot,
+ QPainter *painter,
+ int axisId, int startDist, int endDist, int baseDist,
+ const QRectF &rect ) const
+{
+ if ( !plot->axisEnabled( axisId ) )
+ return;
+
+ const QwtScaleWidget *scaleWidget = plot->axisWidget( axisId );
+ if ( scaleWidget->isColorBarEnabled()
+ && scaleWidget->colorBarWidth() > 0 )
+ {
+ scaleWidget->drawColorBar( painter, scaleWidget->colorBarRect( rect ) );
+
+ const int off = scaleWidget->colorBarWidth() + scaleWidget->spacing();
+ if ( scaleWidget->scaleDraw()->orientation() == Qt::Horizontal )
+ baseDist += off;
+ else
+ baseDist += off;
+ }
+
+ painter->save();
+
+ QwtScaleDraw::Alignment align;
+ double x, y, w;
+
+ switch ( axisId )
+ {
+ case QwtPlot::yLeft:
+ {
+ x = rect.right() - 1.0 - baseDist;
+ y = rect.y() + startDist;
+ w = rect.height() - startDist - endDist;
+ align = QwtScaleDraw::LeftScale;
+ break;
+ }
+ case QwtPlot::yRight:
+ {
+ x = rect.left() + baseDist;
+ y = rect.y() + startDist;
+ w = rect.height() - startDist - endDist;
+ align = QwtScaleDraw::RightScale;
+ break;
+ }
+ case QwtPlot::xTop:
+ {
+ x = rect.left() + startDist;
+ y = rect.bottom() - 1.0 - baseDist;
+ w = rect.width() - startDist - endDist;
+ align = QwtScaleDraw::TopScale;
+ break;
+ }
+ case QwtPlot::xBottom:
+ {
+ x = rect.left() + startDist;
+ y = rect.top() + baseDist;
+ w = rect.width() - startDist - endDist;
+ align = QwtScaleDraw::BottomScale;
+ break;
+ }
+ default:
+ return;
+ }
+
+ scaleWidget->drawTitle( painter, align, rect );
+
+ painter->setFont( scaleWidget->font() );
+
+ QwtScaleDraw *sd = const_cast<QwtScaleDraw *>( scaleWidget->scaleDraw() );
+ const QPointF sdPos = sd->pos();
+ const double sdLength = sd->length();
+
+ sd->move( x, y );
+ sd->setLength( w );
+
+ QPalette palette = scaleWidget->palette();
+ palette.setCurrentColorGroup( QPalette::Active );
+ sd->draw( painter, palette );
+
+ // reset previous values
+ sd->move( sdPos );
+ sd->setLength( sdLength );
+
+ painter->restore();
+}
+
+/*!
+ Render the canvas into a given rectangle.
+
+ \param plot Plot widget
+ \param painter Painter
+ \param map Maps mapping between plot and paint device coordinates
+ \param canvasRect Canvas rectangle
+*/
+void QwtPlotRenderer::renderCanvas( const QwtPlot *plot,
+ QPainter *painter, const QRectF &canvasRect,
+ const QwtScaleMap *map ) const
+{
+ painter->save();
+
+ QPainterPath clipPath;
+
+ QRectF r = canvasRect.adjusted( 0.0, 0.0, -1.0, -1.0 );
+
+ if ( d_data->layoutFlags & FrameWithScales )
+ {
+ r.adjust( -1.0, -1.0, 1.0, 1.0 );
+ painter->setPen( QPen( Qt::black ) );
+
+ if ( !( d_data->discardFlags & DiscardCanvasBackground ) )
+ {
+ const QBrush bgBrush =
+ plot->canvas()->palette().brush( plot->backgroundRole() );
+ painter->setBrush( bgBrush );
+ }
+
+ QwtPainter::drawRect( painter, r );
+ }
+ else
+ {
+ if ( !( d_data->discardFlags & DiscardCanvasBackground ) )
+ {
+ qwtRenderBackground( painter, r, plot->canvas() );
+
+ if ( plot->canvas()->testAttribute( Qt::WA_StyledBackground ) )
+ {
+ // The clip region is calculated in integers
+ // To avoid too much rounding errors better
+ // calculate it in target device resolution
+ // TODO ...
+
+ int x1 = qCeil( canvasRect.left() );
+ int x2 = qFloor( canvasRect.right() );
+ int y1 = qCeil( canvasRect.top() );
+ int y2 = qFloor( canvasRect.bottom() );
+
+ clipPath = plot->canvas()->borderPath(
+ QRect( x1, y1, x2 - x1 - 1, y2 - y1 - 1 ) );
+ }
+ }
+ }
+
+ painter->restore();
+
+ painter->save();
+
+ if ( clipPath.isEmpty() )
+ painter->setClipRect( canvasRect );
+ else
+ painter->setClipPath( clipPath );
+
+ plot->drawItems( painter, canvasRect, map );
+
+ painter->restore();
+}
+
+/*!
+ Calculated the scale maps for rendering the canvas
+
+ \param plot Plot widget
+ \param canvasRect Target rectangle
+ \param maps Scale maps to be calculated
+*/
+void QwtPlotRenderer::buildCanvasMaps( const QwtPlot *plot,
+ const QRectF &canvasRect, QwtScaleMap maps[] ) const
+{
+ for ( int axisId = 0; axisId < QwtPlot::axisCnt; axisId++ )
+ {
+ maps[axisId].setTransformation(
+ plot->axisScaleEngine( axisId )->transformation() );
+
+ const QwtScaleDiv &scaleDiv = *plot->axisScaleDiv( axisId );
+ maps[axisId].setScaleInterval(
+ scaleDiv.lowerBound(), scaleDiv.upperBound() );
+
+ double from, to;
+ if ( plot->axisEnabled( axisId ) )
+ {
+ const int sDist = plot->axisWidget( axisId )->startBorderDist();
+ const int eDist = plot->axisWidget( axisId )->endBorderDist();
+ const QRectF &scaleRect = plot->plotLayout()->scaleRect( axisId );
+
+ if ( axisId == QwtPlot::xTop || axisId == QwtPlot::xBottom )
+ {
+ from = scaleRect.left() + sDist;
+ to = scaleRect.right() - eDist;
+ }
+ else
+ {
+ from = scaleRect.bottom() - eDist;
+ to = scaleRect.top() + sDist;
+ }
+ }
+ else
+ {
+ int margin = 0;
+ if ( !plot->plotLayout()->alignCanvasToScales() )
+ margin = plot->plotLayout()->canvasMargin( axisId );
+
+ if ( axisId == QwtPlot::yLeft || axisId == QwtPlot::yRight )
+ {
+ from = canvasRect.bottom() - margin;
+ to = canvasRect.top() + margin;
+ }
+ else
+ {
+ from = canvasRect.left() + margin;
+ to = canvasRect.right() - margin;
+ }
+ }
+ maps[axisId].setPaintInterval( from, to );
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_renderer.h b/src/libpcp_qwt/src/qwt_plot_renderer.h
new file mode 100644
index 0000000..f79146a
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_renderer.h
@@ -0,0 +1,154 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_RENDERER_H
+#define QWT_PLOT_RENDERER_H
+
+#include "qwt_global.h"
+#include <qobject.h>
+
+class QwtPlot;
+class QwtScaleMap;
+class QSizeF;
+class QRectF;
+class QPainter;
+class QPaintDevice;
+
+#ifndef QT_NO_PRINTER
+class QPrinter;
+#endif
+
+#ifndef QWT_NO_SVG
+#ifdef QT_SVG_LIB
+class QSvgGenerator;
+#endif
+#endif
+
+/*!
+ \brief Renderer for exporting a plot to a document, a printer
+ or anything else, that is supported by QPainter/QPaintDevice
+*/
+class QWT_EXPORT QwtPlotRenderer: public QObject
+{
+ Q_OBJECT
+
+public:
+ //! Disard flags
+ enum DiscardFlag
+ {
+ //! Render all components of the plot
+ DiscardNone = 0x00,
+
+ //! Don't render the background of the plot
+ DiscardBackground = 0x01,
+
+ //! Don't render the title of the plot
+ DiscardTitle = 0x02,
+
+ //! Don't render the legend of the plot
+ DiscardLegend = 0x04,
+
+ //! Don't render the background of the canvas
+ DiscardCanvasBackground = 0x08
+ };
+
+ //! Disard flags
+ typedef QFlags<DiscardFlag> DiscardFlags;
+
+ /*!
+ \brief Layout flags
+ \sa setLayoutFlag(), testLayoutFlag()
+ */
+ enum LayoutFlag
+ {
+ //! Use the default layout without margins and frames
+ DefaultLayout = 0x00,
+
+ //! Render all frames of the plot
+ KeepFrames = 0x01,
+
+ /*!
+ Instead of the scales a box is painted around the plot canvas,
+ where the scale ticks are aligned to.
+ */
+ FrameWithScales = 0x02
+ };
+
+ //! Layout flags
+ typedef QFlags<LayoutFlag> LayoutFlags;
+
+ explicit QwtPlotRenderer( QObject * = NULL );
+ virtual ~QwtPlotRenderer();
+
+ void setDiscardFlag( DiscardFlag flag, bool on = true );
+ bool testDiscardFlag( DiscardFlag flag ) const;
+
+ void setDiscardFlags( DiscardFlags flags );
+ DiscardFlags discardFlags() const;
+
+ void setLayoutFlag( LayoutFlag flag, bool on = true );
+ bool testLayoutFlag( LayoutFlag flag ) const;
+
+ void setLayoutFlags( LayoutFlags flags );
+ LayoutFlags layoutFlags() const;
+
+ void renderDocument( QwtPlot *, const QString &fileName,
+ const QSizeF &sizeMM, int resolution = 85 );
+
+ void renderDocument( QwtPlot *,
+ const QString &fileName, const QString &format,
+ const QSizeF &sizeMM, int resolution = 85 );
+
+#ifndef QWT_NO_SVG
+#ifdef QT_SVG_LIB
+#if QT_VERSION >= 0x040500
+ void renderTo( QwtPlot *, QSvgGenerator & ) const;
+#endif
+#endif
+#endif
+
+#ifndef QT_NO_PRINTER
+ void renderTo( QwtPlot *, QPrinter & ) const;
+#endif
+
+ void renderTo( QwtPlot *, QPaintDevice &p ) const;
+
+ virtual void render( QwtPlot *,
+ QPainter *, const QRectF &rect ) const;
+
+ virtual void renderLegendItem( const QwtPlot *,
+ QPainter *, const QWidget *, const QRectF & ) const;
+
+ virtual void renderTitle( const QwtPlot *,
+ QPainter *, const QRectF & ) const;
+
+ virtual void renderScale( const QwtPlot *, QPainter *,
+ int axisId, int startDist, int endDist,
+ int baseDist, const QRectF & ) const;
+
+ virtual void renderCanvas( const QwtPlot *,
+ QPainter *, const QRectF &canvasRect,
+ const QwtScaleMap* maps ) const;
+
+ virtual void renderLegend(
+ const QwtPlot *, QPainter *, const QRectF & ) const;
+
+protected:
+ void buildCanvasMaps( const QwtPlot *,
+ const QRectF &, QwtScaleMap maps[] ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRenderer::DiscardFlags )
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotRenderer::LayoutFlags )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_rescaler.cpp b/src/libpcp_qwt/src/qwt_plot_rescaler.cpp
new file mode 100644
index 0000000..69c0f4f
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_rescaler.cpp
@@ -0,0 +1,628 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_rescaler.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_div.h"
+#include "qwt_interval.h"
+#include <qevent.h>
+#include <qalgorithms.h>
+
+class QwtPlotRescaler::AxisData
+{
+public:
+ AxisData():
+ aspectRatio( 1.0 ),
+ expandingDirection( QwtPlotRescaler::ExpandUp )
+ {
+ }
+
+ double aspectRatio;
+ QwtInterval intervalHint;
+ QwtPlotRescaler::ExpandingDirection expandingDirection;
+ mutable QwtScaleDiv scaleDiv;
+};
+
+class QwtPlotRescaler::PrivateData
+{
+public:
+ PrivateData():
+ referenceAxis( QwtPlot::xBottom ),
+ rescalePolicy( QwtPlotRescaler::Expanding ),
+ isEnabled( false ),
+ inReplot( 0 )
+ {
+ }
+
+ int referenceAxis;
+ RescalePolicy rescalePolicy;
+ QwtPlotRescaler::AxisData axisData[QwtPlot::axisCnt];
+ bool isEnabled;
+
+ mutable int inReplot;
+};
+
+/*!
+ Constructor
+
+ \param canvas Canvas
+ \param referenceAxis Reference axis, see RescalePolicy
+ \param policy Rescale policy
+
+ \sa setRescalePolicy(), setReferenceAxis()
+*/
+QwtPlotRescaler::QwtPlotRescaler( QwtPlotCanvas *canvas,
+ int referenceAxis, RescalePolicy policy ):
+ QObject( canvas )
+{
+ d_data = new PrivateData;
+ d_data->referenceAxis = referenceAxis;
+ d_data->rescalePolicy = policy;
+
+ setEnabled( true );
+}
+
+//! Destructor
+QwtPlotRescaler::~QwtPlotRescaler()
+{
+ delete d_data;
+}
+
+/*!
+ \brief En/disable the rescaler
+
+ When enabled is true an event filter is installed for
+ the canvas, otherwise the event filter is removed.
+
+ \param on true or false
+ \sa isEnabled(), eventFilter()
+*/
+void QwtPlotRescaler::setEnabled( bool on )
+{
+ if ( d_data->isEnabled != on )
+ {
+ d_data->isEnabled = on;
+
+ QWidget *w = canvas();
+ if ( w )
+ {
+ if ( d_data->isEnabled )
+ w->installEventFilter( this );
+ else
+ w->removeEventFilter( this );
+ }
+ }
+}
+
+/*!
+ \return true when enabled, false otherwise
+ \sa setEnabled, eventFilter()
+*/
+bool QwtPlotRescaler::isEnabled() const
+{
+ return d_data->isEnabled;
+}
+
+/*!
+ Change the rescale policy
+
+ \param policy Rescale policy
+ \sa rescalePolicy()
+*/
+void QwtPlotRescaler::setRescalePolicy( RescalePolicy policy )
+{
+ d_data->rescalePolicy = policy;
+}
+
+/*!
+ \return Rescale policy
+ \sa setRescalePolicy()
+*/
+QwtPlotRescaler::RescalePolicy QwtPlotRescaler::rescalePolicy() const
+{
+ return d_data->rescalePolicy;
+}
+
+/*!
+ Set the reference axis ( see RescalePolicy )
+
+ \param axis Axis index ( QwtPlot::Axis )
+ \sa referenceAxis()
+*/
+void QwtPlotRescaler::setReferenceAxis( int axis )
+{
+ d_data->referenceAxis = axis;
+}
+
+/*!
+ \return Reference axis ( see RescalePolicy )
+ \sa setReferenceAxis()
+*/
+int QwtPlotRescaler::referenceAxis() const
+{
+ return d_data->referenceAxis;
+}
+
+/*!
+ Set the direction in which all axis should be expanded
+
+ \param direction Direction
+ \sa expandingDirection()
+*/
+void QwtPlotRescaler::setExpandingDirection(
+ ExpandingDirection direction )
+{
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ setExpandingDirection( axis, direction );
+}
+
+/*!
+ Set the direction in which an axis should be expanded
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \param direction Direction
+ \sa expandingDirection()
+*/
+void QwtPlotRescaler::setExpandingDirection(
+ int axis, ExpandingDirection direction )
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->axisData[axis].expandingDirection = direction;
+}
+
+/*!
+ Return direction in which an axis should be expanded
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \sa setExpandingDirection()
+*/
+QwtPlotRescaler::ExpandingDirection
+QwtPlotRescaler::expandingDirection( int axis ) const
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ return d_data->axisData[axis].expandingDirection;
+
+ return ExpandBoth;
+}
+
+/*!
+ Set the aspect ratio between the scale of the reference axis
+ and the other scales. The default ratio is 1.0
+
+ \param ratio Aspect ratio
+ \sa aspectRatio()
+*/
+void QwtPlotRescaler::setAspectRatio( double ratio )
+{
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ setAspectRatio( axis, ratio );
+}
+
+/*!
+ Set the aspect ratio between the scale of the reference axis
+ and another scale. The default ratio is 1.0
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \param ratio Aspect ratio
+ \sa aspectRatio()
+*/
+void QwtPlotRescaler::setAspectRatio( int axis, double ratio )
+{
+ if ( ratio < 0.0 )
+ ratio = 0.0;
+
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->axisData[axis].aspectRatio = ratio;
+}
+
+/*!
+ Return aspect ratio between an axis and the reference axis.
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \sa setAspectRatio()
+*/
+double QwtPlotRescaler::aspectRatio( int axis ) const
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ return d_data->axisData[axis].aspectRatio;
+
+ return 0.0;
+}
+
+/*!
+ Set an interval hint for an axis
+
+ In Fitting mode, the hint is used as minimal interval
+ taht always needs to be displayed.
+
+ \param axis Axis, see QwtPlot::Axis
+ \param interval Axis
+ \sa intervalHint(), RescalePolicy
+*/
+void QwtPlotRescaler::setIntervalHint( int axis,
+ const QwtInterval &interval )
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ d_data->axisData[axis].intervalHint = interval;
+}
+
+/*!
+ \param axis Axis, see QwtPlot::Axis
+ \return Interval hint
+ \sa setIntervalHint(), RescalePolicy
+*/
+QwtInterval QwtPlotRescaler::intervalHint( int axis ) const
+{
+ if ( axis >= 0 && axis < QwtPlot::axisCnt )
+ return d_data->axisData[axis].intervalHint;
+
+ return QwtInterval();
+}
+
+//! \return plot canvas
+QwtPlotCanvas *QwtPlotRescaler::canvas()
+{
+ return qobject_cast<QwtPlotCanvas *>( parent() );
+}
+
+//! \return plot canvas
+const QwtPlotCanvas *QwtPlotRescaler::canvas() const
+{
+ return qobject_cast<const QwtPlotCanvas *>( parent() );
+}
+
+//! \return plot widget
+QwtPlot *QwtPlotRescaler::plot()
+{
+ QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+//! \return plot widget
+const QwtPlot *QwtPlotRescaler::plot() const
+{
+ const QwtPlotCanvas *w = canvas();
+ if ( w )
+ return w->plot();
+
+ return NULL;
+}
+
+//! Event filter for the plot canvas
+bool QwtPlotRescaler::eventFilter( QObject *object, QEvent *event )
+{
+ if ( object && object == canvas() )
+ {
+ switch ( event->type() )
+ {
+ case QEvent::Resize:
+ {
+ canvasResizeEvent( ( QResizeEvent * )event );
+ break;
+ }
+ case QEvent::PolishRequest:
+ {
+ rescale();
+ break;
+ }
+ default:;
+ }
+ }
+
+ return false;
+}
+
+/*!
+ Event handler for resize events of the plot canvas
+
+ \param event Resize event
+ \sa rescale()
+*/
+void QwtPlotRescaler::canvasResizeEvent( QResizeEvent* event )
+{
+ const int fw = 2 * canvas()->frameWidth();
+ const QSize newSize = event->size() - QSize( fw, fw );
+ const QSize oldSize = event->oldSize() - QSize( fw, fw );
+
+ rescale( oldSize, newSize );
+}
+
+//! Adjust the plot axes scales
+void QwtPlotRescaler::rescale() const
+{
+ const QSize size = canvas()->contentsRect().size();
+ rescale( size, size );
+}
+
+/*!
+ Adjust the plot axes scales
+
+ \param oldSize Previous size of the canvas
+ \param newSize New size of the canvas
+*/
+void QwtPlotRescaler::rescale(
+ const QSize &oldSize, const QSize &newSize ) const
+{
+ if ( newSize.isEmpty() )
+ return;
+
+ QwtInterval intervals[QwtPlot::axisCnt];
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ intervals[axis] = interval( axis );
+
+ const int refAxis = referenceAxis();
+ intervals[refAxis] = expandScale( refAxis, oldSize, newSize );
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( aspectRatio( axis ) > 0.0 && axis != refAxis )
+ intervals[axis] = syncScale( axis, intervals[refAxis], newSize );
+ }
+
+ updateScales( intervals );
+}
+
+/*!
+ Calculate the new scale interval of a plot axis
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \param oldSize Previous size of the canvas
+ \param newSize New size of the canvas
+
+ \return Calculated new interval for the axis
+*/
+QwtInterval QwtPlotRescaler::expandScale( int axis,
+ const QSize &oldSize, const QSize &newSize ) const
+{
+ const QwtInterval oldInterval = interval( axis );
+
+ QwtInterval expanded = oldInterval;
+ switch ( rescalePolicy() )
+ {
+ case Fixed:
+ {
+ break; // do nothing
+ }
+ case Expanding:
+ {
+ if ( !oldSize.isEmpty() )
+ {
+ double width = oldInterval.width();
+ if ( orientation( axis ) == Qt::Horizontal )
+ width *= double( newSize.width() ) / oldSize.width();
+ else
+ width *= double( newSize.height() ) / oldSize.height();
+
+ expanded = expandInterval( oldInterval,
+ width, expandingDirection( axis ) );
+ }
+ break;
+ }
+ case Fitting:
+ {
+ double dist = 0.0;
+ for ( int ax = 0; ax < QwtPlot::axisCnt; ax++ )
+ {
+ const double d = pixelDist( ax, newSize );
+ if ( d > dist )
+ dist = d;
+ }
+ if ( dist > 0.0 )
+ {
+ double width;
+ if ( orientation( axis ) == Qt::Horizontal )
+ width = newSize.width() * dist;
+ else
+ width = newSize.height() * dist;
+
+ expanded = expandInterval( intervalHint( axis ),
+ width, expandingDirection( axis ) );
+ }
+ break;
+ }
+ }
+
+ return expanded;
+}
+
+/*!
+ Synchronize an axis scale according to the scale of the reference axis
+
+ \param axis Axis index ( see QwtPlot::AxisId )
+ \param reference Interval of the reference axis
+ \param size Size of the canvas
+*/
+QwtInterval QwtPlotRescaler::syncScale( int axis,
+ const QwtInterval& reference, const QSize &size ) const
+{
+ double dist;
+ if ( orientation( referenceAxis() ) == Qt::Horizontal )
+ dist = reference.width() / size.width();
+ else
+ dist = reference.width() / size.height();
+
+ if ( orientation( axis ) == Qt::Horizontal )
+ dist *= size.width();
+ else
+ dist *= size.height();
+
+ dist /= aspectRatio( axis );
+
+ QwtInterval intv;
+ if ( rescalePolicy() == Fitting )
+ intv = intervalHint( axis );
+ else
+ intv = interval( axis );
+
+ intv = expandInterval( intv, dist, expandingDirection( axis ) );
+
+ return intv;
+}
+
+/*!
+ Return orientation of an axis
+ \param axis Axis index ( see QwtPlot::AxisId )
+*/
+Qt::Orientation QwtPlotRescaler::orientation( int axis ) const
+{
+ if ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight )
+ return Qt::Vertical;
+
+ return Qt::Horizontal;
+}
+
+/*!
+ Return interval of an axis
+ \param axis Axis index ( see QwtPlot::AxisId )
+*/
+QwtInterval QwtPlotRescaler::interval( int axis ) const
+{
+ if ( axis < 0 || axis >= QwtPlot::axisCnt )
+ return QwtInterval();
+
+ const QwtPlot *plt = plot();
+
+ const double v1 = plt->axisScaleDiv( axis )->lowerBound();
+ const double v2 = plt->axisScaleDiv( axis )->upperBound();
+
+ return QwtInterval( v1, v2 ).normalized();
+}
+
+/*!
+ Expand the interval
+
+ \param interval Interval to be expanded
+ \param width Distance to be added to the interval
+ \param direction Direction of the expand operation
+
+ \return Expanded interval
+*/
+QwtInterval QwtPlotRescaler::expandInterval(
+ const QwtInterval &interval, double width,
+ ExpandingDirection direction ) const
+{
+ QwtInterval expanded = interval;
+
+ switch ( direction )
+ {
+ case ExpandUp:
+ expanded.setMinValue( interval.minValue() );
+ expanded.setMaxValue( interval.minValue() + width );
+ break;
+
+ case ExpandDown:
+ expanded.setMaxValue( interval.maxValue() );
+ expanded.setMinValue( interval.maxValue() - width );
+ break;
+
+ case ExpandBoth:
+ default:
+ expanded.setMinValue( interval.minValue() +
+ interval.width() / 2.0 - width / 2.0 );
+ expanded.setMaxValue( expanded.minValue() + width );
+ }
+ return expanded;
+}
+
+double QwtPlotRescaler::pixelDist( int axis, const QSize &size ) const
+{
+ const QwtInterval intv = intervalHint( axis );
+
+ double dist = 0.0;
+ if ( !intv.isNull() )
+ {
+ if ( axis == referenceAxis() )
+ dist = intv.width();
+ else
+ {
+ const double r = aspectRatio( axis );
+ if ( r > 0.0 )
+ dist = intv.width() * r;
+ }
+ }
+
+ if ( dist > 0.0 )
+ {
+ if ( orientation( axis ) == Qt::Horizontal )
+ dist /= size.width();
+ else
+ dist /= size.height();
+ }
+
+ return dist;
+}
+
+/*!
+ Update the axes scales
+
+ \param intervals Scale intervals
+*/
+void QwtPlotRescaler::updateScales(
+ QwtInterval intervals[QwtPlot::axisCnt] ) const
+{
+ if ( d_data->inReplot >= 5 )
+ {
+ return;
+ }
+
+ QwtPlot *plt = const_cast<QwtPlot *>( plot() );
+
+ const bool doReplot = plt->autoReplot();
+ plt->setAutoReplot( false );
+
+ for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ )
+ {
+ if ( axis == referenceAxis() || aspectRatio( axis ) > 0.0 )
+ {
+ double v1 = intervals[axis].minValue();
+ double v2 = intervals[axis].maxValue();
+
+ if ( plt->axisScaleDiv( axis )->lowerBound() >
+ plt->axisScaleDiv( axis )->upperBound() )
+ {
+ qSwap( v1, v2 );
+ }
+
+ if ( d_data->inReplot >= 1 )
+ {
+ d_data->axisData[axis].scaleDiv = *plt->axisScaleDiv( axis );
+ }
+
+ if ( d_data->inReplot >= 2 )
+ {
+ QList<double> ticks[QwtScaleDiv::NTickTypes];
+ for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
+ ticks[i] = d_data->axisData[axis].scaleDiv.ticks( i );
+
+ plt->setAxisScaleDiv( axis, QwtScaleDiv( v1, v2, ticks ) );
+ }
+ else
+ {
+ plt->setAxisScale( axis, v1, v2 );
+ }
+ }
+ }
+
+ const bool immediatePaint =
+ plt->canvas()->testPaintAttribute( QwtPlotCanvas::ImmediatePaint );
+ plt->canvas()->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, false );
+
+ plt->setAutoReplot( doReplot );
+
+ d_data->inReplot++;
+ plt->replot();
+ d_data->inReplot--;
+
+ plt->canvas()->setPaintAttribute(
+ QwtPlotCanvas::ImmediatePaint, immediatePaint );
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_rescaler.h b/src/libpcp_qwt/src/qwt_plot_rescaler.h
new file mode 100644
index 0000000..40cf3f9
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_rescaler.h
@@ -0,0 +1,143 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_RESCALER_H
+#define QWT_PLOT_RESCALER_H 1
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include "qwt_plot.h"
+#include <qobject.h>
+
+class QwtPlotCanvas;
+class QwtPlot;
+class QResizeEvent;
+
+/*!
+ \brief QwtPlotRescaler takes care of fixed aspect ratios for plot scales
+
+ QwtPlotRescaler autoadjusts the axes of a QwtPlot according
+ to fixed aspect ratios.
+*/
+
+class QWT_EXPORT QwtPlotRescaler: public QObject
+{
+public:
+ /*!
+ The rescale policy defines how to rescale the reference axis and
+ their depending axes.
+
+ \sa ExpandingDirection, setIntervalHint()
+ */
+ enum RescalePolicy
+ {
+ /*!
+ The interval of the reference axis remains unchanged, when the
+ geometry of the canvas changes. All other axes
+ will be adjusted according to their aspect ratio.
+ */
+ Fixed,
+
+ /*!
+ The interval of the reference axis will be shrinked/expanded,
+ when the geometry of the canvas changes. All other axes
+ will be adjusted according to their aspect ratio.
+
+ The interval, that is represented by one pixel is fixed.
+
+ */
+ Expanding,
+
+ /*!
+ The intervals of the axes are calculated, so that all axes include
+ their interval hint.
+ */
+ Fitting
+ };
+
+ /*!
+ When rescalePolicy() is set to Expanding its direction depends
+ on ExpandingDirection
+ */
+ enum ExpandingDirection
+ {
+ //! The upper limit of the scale is adjusted
+ ExpandUp,
+
+ //! The lower limit of the scale is adjusted
+ ExpandDown,
+
+ //! Both limits of the scale are adjusted
+ ExpandBoth
+ };
+
+ explicit QwtPlotRescaler( QwtPlotCanvas *,
+ int referenceAxis = QwtPlot::xBottom,
+ RescalePolicy = Expanding );
+
+ virtual ~QwtPlotRescaler();
+
+ void setEnabled( bool );
+ bool isEnabled() const;
+
+ void setRescalePolicy( RescalePolicy );
+ RescalePolicy rescalePolicy() const;
+
+ void setExpandingDirection( ExpandingDirection );
+ void setExpandingDirection( int axis, ExpandingDirection );
+ ExpandingDirection expandingDirection( int axis ) const;
+
+ void setReferenceAxis( int axis );
+ int referenceAxis() const;
+
+ void setAspectRatio( double ratio );
+ void setAspectRatio( int axis, double ratio );
+ double aspectRatio( int axis ) const;
+
+ void setIntervalHint( int axis, const QwtInterval& );
+ QwtInterval intervalHint( int axis ) const;
+
+ QwtPlotCanvas *canvas();
+ const QwtPlotCanvas *canvas() const;
+
+ QwtPlot *plot();
+ const QwtPlot *plot() const;
+
+ virtual bool eventFilter( QObject *, QEvent * );
+
+ void rescale() const;
+
+protected:
+ virtual void canvasResizeEvent( QResizeEvent * );
+
+ virtual void rescale( const QSize &oldSize, const QSize &newSize ) const;
+ virtual QwtInterval expandScale(
+ int axis, const QSize &oldSize, const QSize &newSize ) const;
+
+ virtual QwtInterval syncScale(
+ int axis, const QwtInterval& reference,
+ const QSize &size ) const;
+
+ virtual void updateScales(
+ QwtInterval intervals[QwtPlot::axisCnt] ) const;
+
+ Qt::Orientation orientation( int axis ) const;
+ QwtInterval interval( int axis ) const;
+ QwtInterval expandInterval( const QwtInterval &,
+ double width, ExpandingDirection ) const;
+
+private:
+ double pixelDist( int axis, const QSize & ) const;
+
+ class AxisData;
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_scaleitem.cpp b/src/libpcp_qwt/src/qwt_plot_scaleitem.cpp
new file mode 100644
index 0000000..212f5be
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_scaleitem.cpp
@@ -0,0 +1,445 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_scaleitem.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_map.h"
+#include "qwt_interval.h"
+#include <qpalette.h>
+#include <qpainter.h>
+
+class QwtPlotScaleItem::PrivateData
+{
+public:
+ PrivateData():
+ position( 0.0 ),
+ borderDistance( -1 ),
+ scaleDivFromAxis( true ),
+ scaleDraw( new QwtScaleDraw() )
+ {
+ }
+
+ ~PrivateData()
+ {
+ delete scaleDraw;
+ }
+
+ void updateBorders( const QRectF &,
+ const QwtScaleMap &, const QwtScaleMap & );
+
+ QPalette palette;
+ QFont font;
+ double position;
+ int borderDistance;
+ bool scaleDivFromAxis;
+ QwtScaleDraw *scaleDraw;
+ QRectF canvasRectCache;
+};
+
+void QwtPlotScaleItem::PrivateData::updateBorders( const QRectF &canvasRect,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap )
+{
+ QwtInterval interval;
+ if ( scaleDraw->orientation() == Qt::Horizontal )
+ {
+ interval.setMinValue( xMap.invTransform( canvasRect.left() ) );
+ interval.setMaxValue( xMap.invTransform( canvasRect.right() - 1 ) );
+ }
+ else
+ {
+ interval.setMinValue( yMap.invTransform( canvasRect.bottom() - 1 ) );
+ interval.setMaxValue( yMap.invTransform( canvasRect.top() ) );
+ }
+
+ QwtScaleDiv scaleDiv = scaleDraw->scaleDiv();
+ scaleDiv.setInterval( interval );
+ scaleDraw->setScaleDiv( scaleDiv );
+}
+/*!
+ \brief Constructor for scale item at the position pos.
+
+ \param alignment In case of QwtScaleDraw::BottomScale or QwtScaleDraw::TopScale
+ the scale item is corresponding to the xAxis(),
+ otherwise it corresponds to the yAxis().
+
+ \param pos x or y position, depending on the corresponding axis.
+
+ \sa setPosition(), setAlignment()
+*/
+QwtPlotScaleItem::QwtPlotScaleItem(
+ QwtScaleDraw::Alignment alignment, const double pos ):
+ QwtPlotItem( QwtText( "Scale" ) )
+{
+ d_data = new PrivateData;
+ d_data->position = pos;
+ d_data->scaleDraw->setAlignment( alignment );
+
+ setZ( 11.0 );
+}
+
+//! Destructor
+QwtPlotScaleItem::~QwtPlotScaleItem()
+{
+ delete d_data;
+}
+
+//! \return QwtPlotItem::Rtti_PlotScale
+int QwtPlotScaleItem::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotScale;
+}
+
+/*!
+ \brief Assign a scale division
+
+ When assigning a scaleDiv the scale division won't be synchronized
+ with the corresponding axis anymore.
+
+ \param scaleDiv Scale division
+ \sa scaleDiv(), setScaleDivFromAxis(), isScaleDivFromAxis()
+*/
+void QwtPlotScaleItem::setScaleDiv( const QwtScaleDiv& scaleDiv )
+{
+ d_data->scaleDivFromAxis = false;
+ d_data->scaleDraw->setScaleDiv( scaleDiv );
+}
+
+//! \return Scale division
+const QwtScaleDiv& QwtPlotScaleItem::scaleDiv() const
+{
+ return d_data->scaleDraw->scaleDiv();
+}
+
+/*!
+ Enable/Disable the synchronization of the scale division with
+ the corresponding axis.
+
+ \param on true/false
+ \sa isScaleDivFromAxis()
+*/
+void QwtPlotScaleItem::setScaleDivFromAxis( bool on )
+{
+ if ( on != d_data->scaleDivFromAxis )
+ {
+ d_data->scaleDivFromAxis = on;
+ if ( on )
+ {
+ const QwtPlot *plt = plot();
+ if ( plt )
+ {
+ updateScaleDiv( *plt->axisScaleDiv( xAxis() ),
+ *plt->axisScaleDiv( yAxis() ) );
+ itemChanged();
+ }
+ }
+ }
+}
+
+/*!
+ \return True, if the synchronization of the scale division with
+ the corresponding axis is enabled.
+ \sa setScaleDiv(), setScaleDivFromAxis()
+*/
+bool QwtPlotScaleItem::isScaleDivFromAxis() const
+{
+ return d_data->scaleDivFromAxis;
+}
+
+/*!
+ Set the palette
+ \sa QwtAbstractScaleDraw::draw(), palette()
+*/
+void QwtPlotScaleItem::setPalette( const QPalette &palette )
+{
+ if ( palette != d_data->palette )
+ {
+ d_data->palette = palette;
+ itemChanged();
+ }
+}
+
+/*!
+ \return palette
+ \sa setPalette()
+*/
+QPalette QwtPlotScaleItem::palette() const
+{
+ return d_data->palette;
+}
+
+/*!
+ Change the tick label font
+ \sa font()
+*/
+void QwtPlotScaleItem::setFont( const QFont &font )
+{
+ if ( font != d_data->font )
+ {
+ d_data->font = font;
+ itemChanged();
+ }
+}
+
+/*!
+ \return tick label font
+ \sa setFont()
+*/
+QFont QwtPlotScaleItem::font() const
+{
+ return d_data->font;
+}
+
+/*!
+ \brief Set a scale draw
+
+ \param scaleDraw object responsible for drawing scales.
+
+ The main use case for replacing the default QwtScaleDraw is
+ to overload QwtAbstractScaleDraw::label, to replace or swallow
+ tick labels.
+
+ \sa scaleDraw()
+*/
+void QwtPlotScaleItem::setScaleDraw( QwtScaleDraw *scaleDraw )
+{
+ if ( scaleDraw == NULL )
+ return;
+
+ if ( scaleDraw != d_data->scaleDraw )
+ delete d_data->scaleDraw;
+
+ d_data->scaleDraw = scaleDraw;
+
+ const QwtPlot *plt = plot();
+ if ( plt )
+ {
+ updateScaleDiv( *plt->axisScaleDiv( xAxis() ),
+ *plt->axisScaleDiv( yAxis() ) );
+ }
+
+ itemChanged();
+}
+
+/*!
+ \return Scale draw
+ \sa setScaleDraw()
+*/
+const QwtScaleDraw *QwtPlotScaleItem::scaleDraw() const
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ \return Scale draw
+ \sa setScaleDraw()
+*/
+QwtScaleDraw *QwtPlotScaleItem::scaleDraw()
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ Change the position of the scale
+
+ The position is interpreted as y value for horizontal axes
+ and as x value for vertical axes.
+
+ The border distance is set to -1.
+
+ \param pos New position
+ \sa position(), setAlignment()
+*/
+void QwtPlotScaleItem::setPosition( double pos )
+{
+ if ( d_data->position != pos )
+ {
+ d_data->position = pos;
+ d_data->borderDistance = -1;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Position of the scale
+ \sa setPosition(), setAlignment()
+*/
+double QwtPlotScaleItem::position() const
+{
+ return d_data->position;
+}
+
+/*!
+ \brief Align the scale to the canvas
+
+ If distance is >= 0 the scale will be aligned to a
+ border of the contents rect of the canvas. If
+ alignment() is QwtScaleDraw::LeftScale, the scale will
+ be aligned to the right border, if it is QwtScaleDraw::TopScale
+ it will be aligned to the bottom (and vice versa),
+
+ If distance is < 0 the scale will be at the position().
+
+ \param distance Number of pixels between the canvas border and the
+ backbone of the scale.
+
+ \sa setPosition(), borderDistance()
+*/
+void QwtPlotScaleItem::setBorderDistance( int distance )
+{
+ if ( distance < 0 )
+ distance = -1;
+
+ if ( distance != d_data->borderDistance )
+ {
+ d_data->borderDistance = distance;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Distance from a canvas border
+ \sa setBorderDistance(), setPosition()
+*/
+int QwtPlotScaleItem::borderDistance() const
+{
+ return d_data->borderDistance;
+}
+
+/*!
+ Change the alignment of the scale
+
+ The alignment sets the orientation of the scale and the position of
+ the ticks:
+
+ - QwtScaleDraw::BottomScale: horizontal, ticks below
+ - QwtScaleDraw::TopScale: horizontal, ticks above
+ - QwtScaleDraw::LeftScale: vertical, ticks left
+ - QwtScaleDraw::RightScale: vertical, ticks right
+
+ For horizontal scales the position corresponds to QwtPlotItem::yAxis(),
+ otherwise to QwtPlotItem::xAxis().
+
+ \sa scaleDraw(), QwtScaleDraw::alignment(), setPosition()
+*/
+void QwtPlotScaleItem::setAlignment( QwtScaleDraw::Alignment alignment )
+{
+ QwtScaleDraw *sd = d_data->scaleDraw;
+ if ( sd->alignment() != alignment )
+ {
+ sd->setAlignment( alignment );
+ itemChanged();
+ }
+}
+
+/*!
+ \brief Draw the scale
+*/
+void QwtPlotScaleItem::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ if ( d_data->scaleDivFromAxis )
+ {
+ if ( canvasRect != d_data->canvasRectCache )
+ {
+ d_data->updateBorders( canvasRect, xMap, yMap );
+ d_data->canvasRectCache = canvasRect;
+ }
+ }
+
+ QPen pen = painter->pen();
+ pen.setStyle( Qt::SolidLine );
+ painter->setPen( pen );
+
+ QwtScaleDraw *sd = d_data->scaleDraw;
+ if ( sd->orientation() == Qt::Horizontal )
+ {
+ double y;
+ if ( d_data->borderDistance >= 0 )
+ {
+ if ( sd->alignment() == QwtScaleDraw::BottomScale )
+ y = canvasRect.top() + d_data->borderDistance;
+ else
+ {
+ y = canvasRect.bottom() - d_data->borderDistance;
+ }
+
+ }
+ else
+ {
+ y = yMap.transform( d_data->position );
+ }
+
+ if ( y < canvasRect.top() || y > canvasRect.bottom() )
+ return;
+
+ sd->move( canvasRect.left(), y );
+ sd->setLength( canvasRect.width() - 1 );
+ sd->setTransformation( xMap.transformation()->copy() );
+ }
+ else // == Qt::Vertical
+ {
+ double x;
+ if ( d_data->borderDistance >= 0 )
+ {
+ if ( sd->alignment() == QwtScaleDraw::RightScale )
+ x = canvasRect.left() + d_data->borderDistance;
+ else
+ {
+ x = canvasRect.right() - d_data->borderDistance;
+ }
+ }
+ else
+ {
+ x = xMap.transform( d_data->position );
+ }
+ if ( x < canvasRect.left() || x > canvasRect.right() )
+ return;
+
+ sd->move( x, canvasRect.top() );
+ sd->setLength( canvasRect.height() - 1 );
+ sd->setTransformation( yMap.transformation()->copy() );
+ }
+
+ painter->setFont( d_data->font );
+
+ sd->draw( painter, d_data->palette );
+}
+
+/*!
+ \brief Update the item to changes of the axes scale division
+
+ In case of isScaleDivFromAxis(), the scale draw is synchronized
+ to the correspond axis.
+
+ \param xScaleDiv Scale division of the x-axis
+ \param yScaleDiv Scale division of the y-axis
+
+ \sa QwtPlot::updateAxes()
+*/
+
+void QwtPlotScaleItem::updateScaleDiv( const QwtScaleDiv& xScaleDiv,
+ const QwtScaleDiv& yScaleDiv )
+{
+ QwtScaleDraw *sd = d_data->scaleDraw;
+ if ( d_data->scaleDivFromAxis && sd )
+ {
+ sd->setScaleDiv(
+ sd->orientation() == Qt::Horizontal ? xScaleDiv : yScaleDiv );
+
+ const QwtPlot *plt = plot();
+ if ( plt != NULL )
+ {
+ d_data->updateBorders( plt->canvas()->contentsRect(),
+ plt->canvasMap( xAxis() ), plt->canvasMap( yAxis() ) );
+ d_data->canvasRectCache = QRect();
+ }
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_scaleitem.h b/src/libpcp_qwt/src/qwt_plot_scaleitem.h
new file mode 100644
index 0000000..ead67a3
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_scaleitem.h
@@ -0,0 +1,94 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_SCALE_ITEM_H
+#define QWT_PLOT_SCALE_ITEM_H
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include "qwt_scale_draw.h"
+
+class QPalette;
+
+/*!
+ \brief A class which draws a scale inside the plot canvas
+
+ QwtPlotScaleItem can be used to draw an axis inside the plot canvas.
+ It might by synchronized to one of the axis of the plot, but can
+ also display its own ticks and labels.
+
+ It is allowed to synchronize the scale item with a disabled axis.
+ In plots with vertical and horizontal scale items, it might be
+ necessary to remove ticks at the intersections, by overloading
+ updateScaleDiv().
+
+ The scale might be at a specific position (f.e 0.0) or it might be
+ aligned to a canvas border.
+
+ \par Example
+ The following example shows how to replace the left axis, by a scale item
+ at the x position 0.0.
+ \verbatim
+QwtPlotScaleItem *scaleItem =
+ new QwtPlotScaleItem(QwtScaleDraw::RightScale, 0.0);
+scaleItem->setFont(plot->axisWidget(QwtPlot::yLeft)->font());
+scaleItem->attach(plot);
+
+plot->enableAxis(QwtPlot::yLeft, false);
+\endverbatim
+*/
+
+class QWT_EXPORT QwtPlotScaleItem: public QwtPlotItem
+{
+public:
+ explicit QwtPlotScaleItem(
+ QwtScaleDraw::Alignment = QwtScaleDraw::BottomScale,
+ const double pos = 0.0 );
+
+ virtual ~QwtPlotScaleItem();
+
+ virtual int rtti() const;
+
+ void setScaleDiv( const QwtScaleDiv& );
+ const QwtScaleDiv& scaleDiv() const;
+
+ void setScaleDivFromAxis( bool on );
+ bool isScaleDivFromAxis() const;
+
+ void setPalette( const QPalette & );
+ QPalette palette() const;
+
+ void setFont( const QFont& );
+ QFont font() const;
+
+ void setScaleDraw( QwtScaleDraw * );
+
+ const QwtScaleDraw *scaleDraw() const;
+ QwtScaleDraw *scaleDraw();
+
+ void setPosition( double pos );
+ double position() const;
+
+ void setBorderDistance( int numPixels );
+ int borderDistance() const;
+
+ void setAlignment( QwtScaleDraw::Alignment );
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &rect ) const;
+
+ virtual void updateScaleDiv( const QwtScaleDiv &, const QwtScaleDiv & );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_seriesitem.cpp b/src/libpcp_qwt/src/qwt_plot_seriesitem.cpp
new file mode 100644
index 0000000..c4cdd53
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_seriesitem.cpp
@@ -0,0 +1,90 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_seriesitem.h"
+
+class QwtPlotAbstractSeriesItem::PrivateData
+{
+public:
+ PrivateData():
+ orientation( Qt::Vertical )
+ {
+ }
+
+ Qt::Orientation orientation;
+};
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotAbstractSeriesItem::QwtPlotAbstractSeriesItem( const QwtText &title ):
+ QwtPlotItem( title )
+{
+ d_data = new PrivateData();
+}
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotAbstractSeriesItem::QwtPlotAbstractSeriesItem( const QString &title ):
+ QwtPlotItem( QwtText( title ) )
+{
+ d_data = new PrivateData();
+}
+
+//! Destructor
+QwtPlotAbstractSeriesItem::~QwtPlotAbstractSeriesItem()
+{
+ delete d_data;
+}
+
+/*!
+ Set the orientation of the item.
+
+ The orientation() might be used in specific way by a plot item.
+ F.e. a QwtPlotCurve uses it to identify how to display the curve
+ int QwtPlotCurve::Steps or QwtPlotCurve::Sticks style.
+
+ \sa orientation()
+*/
+void QwtPlotAbstractSeriesItem::setOrientation( Qt::Orientation orientation )
+{
+ if ( d_data->orientation != orientation )
+ {
+ d_data->orientation = orientation;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Orientation of the plot item
+ \sa setOrientation()
+*/
+Qt::Orientation QwtPlotAbstractSeriesItem::orientation() const
+{
+ return d_data->orientation;
+}
+
+/*!
+ \brief Draw the complete series
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+*/
+void QwtPlotAbstractSeriesItem::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ drawSeries( painter, xMap, yMap, canvasRect, 0, -1 );
+}
+
diff --git a/src/libpcp_qwt/src/qwt_plot_seriesitem.h b/src/libpcp_qwt/src/qwt_plot_seriesitem.h
new file mode 100644
index 0000000..d6c2d1f
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_seriesitem.h
@@ -0,0 +1,206 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_SERIES_ITEM_H
+#define QWT_PLOT_SERIES_ITEM_H
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include "qwt_scale_div.h"
+#include "qwt_series_data.h"
+
+/*!
+ \brief Base class for plot items representing a series of samples
+*/
+class QWT_EXPORT QwtPlotAbstractSeriesItem: public QwtPlotItem
+{
+public:
+ explicit QwtPlotAbstractSeriesItem( const QString &title = QString::null );
+ explicit QwtPlotAbstractSeriesItem( const QwtText &title );
+
+ virtual ~QwtPlotAbstractSeriesItem();
+
+ void setOrientation( Qt::Orientation );
+ Qt::Orientation orientation() const;
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF & ) const;
+
+ /*!
+ Draw a subset of the samples
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first point to be painted
+ \param to Index of the last point to be painted. If to < 0 the
+ curve will be painted to its last point.
+ */
+ virtual void drawSeries( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const = 0;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ \brief Class template for plot items representing a series of samples
+*/
+template <typename T>
+class QwtPlotSeriesItem: public QwtPlotAbstractSeriesItem
+{
+public:
+ explicit QwtPlotSeriesItem<T>( const QString &title = QString::null );
+ explicit QwtPlotSeriesItem<T>( const QwtText &title );
+
+ virtual ~QwtPlotSeriesItem<T>();
+
+ void setData( QwtSeriesData<T> * );
+
+ QwtSeriesData<T> *data();
+ const QwtSeriesData<T> *data() const;
+
+ size_t dataSize() const;
+ T sample( int index ) const;
+
+ virtual QRectF boundingRect() const;
+ virtual void updateScaleDiv( const QwtScaleDiv &,
+ const QwtScaleDiv & );
+
+protected:
+ //! Series
+ QwtSeriesData<T> *d_series;
+};
+
+/*!
+ Constructor
+ \param title Title of the series item
+*/
+template <typename T>
+QwtPlotSeriesItem<T>::QwtPlotSeriesItem( const QString &title ):
+ QwtPlotAbstractSeriesItem( QwtText( title ) ),
+ d_series( NULL )
+{
+}
+
+/*!
+ Constructor
+ \param title Title of the series item
+*/
+template <typename T>
+QwtPlotSeriesItem<T>::QwtPlotSeriesItem( const QwtText &title ):
+ QwtPlotAbstractSeriesItem( title ),
+ d_series( NULL )
+{
+}
+
+//! Destructor
+template <typename T>
+QwtPlotSeriesItem<T>::~QwtPlotSeriesItem()
+{
+ delete d_series;
+}
+
+//! \return the the curve data
+template <typename T>
+inline QwtSeriesData<T> *QwtPlotSeriesItem<T>::data()
+{
+ return d_series;
+}
+
+//! \return the the curve data
+template <typename T>
+inline const QwtSeriesData<T> *QwtPlotSeriesItem<T>::data() const
+{
+ return d_series;
+}
+
+/*!
+ \param index Index
+ \return Sample at position index
+*/
+template <typename T>
+inline T QwtPlotSeriesItem<T>::sample( int index ) const
+{
+ return d_series ? d_series->sample( index ) : T();
+}
+
+/*!
+ Assign a series of samples
+
+ \param data Data
+ \warning The item takes ownership of the data object, deleting
+ it when its not used anymore.
+*/
+template <typename T>
+void QwtPlotSeriesItem<T>::setData( QwtSeriesData<T> *data )
+{
+ if ( d_series != data )
+ {
+ delete d_series;
+ d_series = data;
+ itemChanged();
+ }
+}
+
+/*!
+ Return the size of the data arrays
+ \sa setData()
+*/
+template <typename T>
+size_t QwtPlotSeriesItem<T>::dataSize() const
+{
+ if ( d_series == NULL )
+ return 0;
+
+ return d_series->size();
+}
+
+/*!
+ \return Bounding rectangle of the data.
+ If there is no bounding rect, like for empty data the rectangle is invalid.
+
+ \sa QwtSeriesData<T>::boundingRect(), QRectF::isValid()
+*/
+template <typename T>
+QRectF QwtPlotSeriesItem<T>::boundingRect() const
+{
+ if ( d_series == NULL )
+ return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid
+
+ return d_series->boundingRect();
+}
+
+/*!
+ Update the rect of interest according to the current scale ranges
+
+ \param xScaleDiv Scale division of the x-axis
+ \param yScaleDiv Scale division of the y-axis
+
+ \sa QwtSeriesData<T>::setRectOfInterest()
+*/
+template <typename T>
+void QwtPlotSeriesItem<T>::updateScaleDiv(
+ const QwtScaleDiv &xScaleDiv, const QwtScaleDiv &yScaleDiv )
+{
+ if ( d_series )
+ {
+ const QRectF rect = QRectF(
+ xScaleDiv.lowerBound(), yScaleDiv.lowerBound(),
+ xScaleDiv.range(), yScaleDiv.range() );
+
+ d_series->setRectOfInterest( rect );
+ }
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_spectrocurve.cpp b/src/libpcp_qwt/src/qwt_plot_spectrocurve.cpp
new file mode 100644
index 0000000..5df254d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_spectrocurve.cpp
@@ -0,0 +1,300 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_spectrocurve.h"
+#include "qwt_color_map.h"
+#include "qwt_scale_map.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+
+class QwtPlotSpectroCurve::PrivateData
+{
+public:
+ PrivateData():
+ colorRange( 0.0, 1000.0 ),
+ penWidth(0.0),
+ paintAttributes( QwtPlotSpectroCurve::ClipPoints )
+ {
+ colorMap = new QwtLinearColorMap();
+ }
+
+ ~PrivateData()
+ {
+ delete colorMap;
+ }
+
+ QwtColorMap *colorMap;
+ QwtInterval colorRange;
+ QVector<QRgb> colorTable;
+ double penWidth;
+ QwtPlotSpectroCurve::PaintAttributes paintAttributes;
+};
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotSpectroCurve::QwtPlotSpectroCurve( const QwtText &title ):
+ QwtPlotSeriesItem<QwtPoint3D>( title )
+{
+ init();
+}
+
+/*!
+ Constructor
+ \param title Title of the curve
+*/
+QwtPlotSpectroCurve::QwtPlotSpectroCurve( const QString &title ):
+ QwtPlotSeriesItem<QwtPoint3D>( QwtText( title ) )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotSpectroCurve::~QwtPlotSpectroCurve()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Initialize data members
+*/
+void QwtPlotSpectroCurve::init()
+{
+ setItemAttribute( QwtPlotItem::Legend );
+ setItemAttribute( QwtPlotItem::AutoScale );
+
+ d_data = new PrivateData;
+ d_series = new QwtPoint3DSeriesData();
+
+ setZ( 20.0 );
+}
+
+//! \return QwtPlotItem::Rtti_PlotSpectroCurve
+int QwtPlotSpectroCurve::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotSpectroCurve;
+}
+
+/*!
+ Specify an attribute how to draw the curve
+
+ \param attribute Paint attribute
+ \param on On/Off
+ /sa PaintAttribute, testPaintAttribute()
+*/
+void QwtPlotSpectroCurve::setPaintAttribute( PaintAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+}
+
+/*!
+ \brief Return the current paint attributes
+ \sa PaintAttribute, setPaintAttribute()
+*/
+bool QwtPlotSpectroCurve::testPaintAttribute( PaintAttribute attribute ) const
+{
+ return ( d_data->paintAttributes & attribute );
+}
+
+/*!
+ Initialize data with an array of samples.
+ \param samples Vector of points
+*/
+void QwtPlotSpectroCurve::setSamples( const QVector<QwtPoint3D> &samples )
+{
+ delete d_series;
+ d_series = new QwtPoint3DSeriesData( samples );
+ itemChanged();
+}
+
+/*!
+ Change the color map
+
+ Often it is useful to display the mapping between intensities and
+ colors as an additional plot axis, showing a color bar.
+
+ \param colorMap Color Map
+
+ \sa colorMap(), setColorRange(), QwtColorMap::color(),
+ QwtScaleWidget::setColorBarEnabled(), QwtScaleWidget::setColorMap()
+*/
+void QwtPlotSpectroCurve::setColorMap( QwtColorMap *colorMap )
+{
+ if ( colorMap != d_data->colorMap )
+ {
+ delete d_data->colorMap;
+ d_data->colorMap = colorMap;
+ }
+
+ itemChanged();
+}
+
+/*!
+ \return Color Map used for mapping the intensity values to colors
+ \sa setColorMap(), setColorRange(), QwtColorMap::color()
+*/
+const QwtColorMap *QwtPlotSpectroCurve::colorMap() const
+{
+ return d_data->colorMap;
+}
+
+/*!
+ Set the value interval, that corresponds to the color map
+
+ \param interval interval.minValue() corresponds to 0.0,
+ interval.maxValue() to 1.0 on the color map.
+
+ \sa colorRange(), setColorMap(), QwtColorMap::color()
+*/
+void QwtPlotSpectroCurve::setColorRange( const QwtInterval &interval )
+{
+ if ( interval != d_data->colorRange )
+ {
+ d_data->colorRange = interval;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Value interval, that corresponds to the color map
+ \sa setColorRange(), setColorMap(), QwtColorMap::color()
+*/
+QwtInterval &QwtPlotSpectroCurve::colorRange() const
+{
+ return d_data->colorRange;
+}
+
+/*!
+ Assign a pen width
+
+ \param penWidth New pen width
+ \sa penWidth()
+*/
+void QwtPlotSpectroCurve::setPenWidth(double penWidth)
+{
+ if ( penWidth < 0.0 )
+ penWidth = 0.0;
+
+ if ( d_data->penWidth != penWidth )
+ {
+ d_data->penWidth = penWidth;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Pen width used to draw a dot
+ \sa setPenWidth()
+*/
+double QwtPlotSpectroCurve::penWidth() const
+{
+ return d_data->penWidth;
+}
+
+/*!
+ Draw a subset of the points
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ series will be painted to its last sample.
+
+ \sa drawDots()
+*/
+void QwtPlotSpectroCurve::drawSeries( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ if ( !painter || dataSize() <= 0 )
+ return;
+
+ if ( to < 0 )
+ to = dataSize() - 1;
+
+ if ( from < 0 )
+ from = 0;
+
+ if ( from > to )
+ return;
+
+ drawDots( painter, xMap, yMap, canvasRect, from, to );
+}
+
+/*!
+ Draw a subset of the points
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas
+ \param from Index of the first sample to be painted
+ \param to Index of the last sample to be painted. If to < 0 the
+ series will be painted to its last sample.
+
+ \sa drawSeries()
+*/
+void QwtPlotSpectroCurve::drawDots( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const
+{
+ if ( !d_data->colorRange.isValid() )
+ return;
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ const QwtColorMap::Format format = d_data->colorMap->format();
+ if ( format == QwtColorMap::Indexed )
+ d_data->colorTable = d_data->colorMap->colorTable( d_data->colorRange );
+
+ for ( int i = from; i <= to; i++ )
+ {
+ const QwtPoint3D sample = d_series->sample( i );
+
+ double xi = xMap.transform( sample.x() );
+ double yi = yMap.transform( sample.y() );
+ if ( doAlign )
+ {
+ xi = qRound( xi );
+ yi = qRound( yi );
+ }
+
+ if ( d_data->paintAttributes & QwtPlotSpectroCurve::ClipPoints )
+ {
+ if ( !canvasRect.contains( xi, yi ) )
+ continue;
+ }
+
+ if ( format == QwtColorMap::RGB )
+ {
+ const QRgb rgb = d_data->colorMap->rgb(
+ d_data->colorRange, sample.z() );
+
+ painter->setPen( QPen( QColor( rgb ), d_data->penWidth ) );
+ }
+ else
+ {
+ const unsigned char index = d_data->colorMap->colorIndex(
+ d_data->colorRange, sample.z() );
+
+ painter->setPen( QPen( QColor( d_data->colorTable[index] ),
+ d_data->penWidth ) );
+ }
+
+ QwtPainter::drawPoint( painter, QPointF( xi, yi ) );
+ }
+
+ d_data->colorTable.clear();
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_spectrocurve.h b/src/libpcp_qwt/src/qwt_plot_spectrocurve.h
new file mode 100644
index 0000000..93caa17
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_spectrocurve.h
@@ -0,0 +1,76 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_CURVE_3D_H
+#define QWT_PLOT_CURVE_3D_H
+
+#include "qwt_global.h"
+#include "qwt_plot_seriesitem.h"
+#include "qwt_series_data.h"
+
+class QwtSymbol;
+class QwtColorMap;
+
+/*!
+ \brief Curve that displays 3D points as dots, where the z coordinate is
+ mapped to a color.
+*/
+class QWT_EXPORT QwtPlotSpectroCurve: public QwtPlotSeriesItem<QwtPoint3D>
+{
+public:
+ //! Paint attributes
+ enum PaintAttribute
+ {
+ //! Clip points outside the canvas rectangle
+ ClipPoints = 1
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ explicit QwtPlotSpectroCurve( const QString &title = QString::null );
+ explicit QwtPlotSpectroCurve( const QwtText &title );
+
+ virtual ~QwtPlotSpectroCurve();
+
+ virtual int rtti() const;
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ void setSamples( const QVector<QwtPoint3D> & );
+
+ void setColorMap( QwtColorMap * );
+ const QwtColorMap *colorMap() const;
+
+ void setColorRange( const QwtInterval & );
+ QwtInterval & colorRange() const;
+
+ virtual void drawSeries( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+ void setPenWidth(double width);
+ double penWidth() const;
+
+protected:
+ virtual void drawDots( QPainter *,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to ) const;
+
+private:
+ void init();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotSpectroCurve::PaintAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_spectrogram.cpp b/src/libpcp_qwt/src/qwt_plot_spectrogram.cpp
new file mode 100644
index 0000000..0a0b694
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_spectrogram.cpp
@@ -0,0 +1,663 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_spectrogram.h"
+#include "qwt_painter.h"
+#include "qwt_interval.h"
+#include "qwt_scale_map.h"
+#include "qwt_color_map.h"
+#include <qimage.h>
+#include <qpen.h>
+#include <qpainter.h>
+#include <qmath.h>
+#include <qalgorithms.h>
+#if QT_VERSION >= 0x040400
+#include <qthread.h>
+#include <qfuture.h>
+#include <qtconcurrentrun.h>
+#endif
+
+class QwtPlotSpectrogram::PrivateData
+{
+public:
+ PrivateData():
+ data( NULL ),
+ renderThreadCount( 1 )
+ {
+ colorMap = new QwtLinearColorMap();
+ displayMode = ImageMode;
+
+ conrecFlags = QwtRasterData::IgnoreAllVerticesOnLevel;
+ conrecFlags |= QwtRasterData::IgnoreOutOfRange;
+ }
+ ~PrivateData()
+ {
+ delete data;
+ delete colorMap;
+ }
+
+ QwtRasterData *data;
+ QwtColorMap *colorMap;
+ DisplayModes displayMode;
+
+ uint renderThreadCount;
+
+ QList<double> contourLevels;
+ QPen defaultContourPen;
+ QwtRasterData::ConrecFlags conrecFlags;
+};
+
+/*!
+ Sets the following item attributes:
+ - QwtPlotItem::AutoScale: true
+ - QwtPlotItem::Legend: false
+
+ The z value is initialized by 8.0.
+
+ \param title Title
+
+ \sa QwtPlotItem::setItemAttribute(), QwtPlotItem::setZ()
+*/
+QwtPlotSpectrogram::QwtPlotSpectrogram( const QString &title ):
+ QwtPlotRasterItem( title )
+{
+ d_data = new PrivateData();
+
+ setItemAttribute( QwtPlotItem::AutoScale, true );
+ setItemAttribute( QwtPlotItem::Legend, false );
+
+ setZ( 8.0 );
+}
+
+//! Destructor
+QwtPlotSpectrogram::~QwtPlotSpectrogram()
+{
+ delete d_data;
+}
+
+//! \return QwtPlotItem::Rtti_PlotSpectrogram
+int QwtPlotSpectrogram::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotSpectrogram;
+}
+
+/*!
+ The display mode controls how the raster data will be represented.
+
+ \param mode Display mode
+ \param on On/Off
+
+ The default setting enables ImageMode.
+
+ \sa DisplayMode, displayMode()
+*/
+void QwtPlotSpectrogram::setDisplayMode( DisplayMode mode, bool on )
+{
+ if ( on != bool( mode & d_data->displayMode ) )
+ {
+ if ( on )
+ d_data->displayMode |= mode;
+ else
+ d_data->displayMode &= ~mode;
+ }
+
+ itemChanged();
+}
+
+/*!
+ The display mode controls how the raster data will be represented.
+
+ \param mode Display mode
+ \return true if mode is enabled
+*/
+bool QwtPlotSpectrogram::testDisplayMode( DisplayMode mode ) const
+{
+ return ( d_data->displayMode & mode );
+}
+
+/*!
+ Rendering an image from the raster data can often be done
+ parallel on a multicore system.
+
+ \param numThreads Number of threads to be used for rendering.
+ If numThreads is set to 0, the system specific
+ ideal thread count is used.
+
+ The default thread count is 1 ( = no additional threads )
+
+ \warning Rendering in multiple threads is only supported for Qt >= 4.4
+ \sa renderThreadCount(), renderImage(), renderTile()
+*/
+void QwtPlotSpectrogram::setRenderThreadCount( uint numThreads )
+{
+ d_data->renderThreadCount = numThreads;
+}
+
+/*!
+ \return Number of threads to be used for rendering.
+ If numThreads is set to 0, the system specific
+ ideal thread count is used.
+
+ \warning Rendering in multiple threads is only supported for Qt >= 4.4
+ \sa setRenderThreadCount(), renderImage(), renderTile()
+*/
+uint QwtPlotSpectrogram::renderThreadCount() const
+{
+ return d_data->renderThreadCount;
+}
+
+/*!
+ Change the color map
+
+ Often it is useful to display the mapping between intensities and
+ colors as an additional plot axis, showing a color bar.
+
+ \param colorMap Color Map
+
+ \sa colorMap(), QwtScaleWidget::setColorBarEnabled(),
+ QwtScaleWidget::setColorMap()
+*/
+void QwtPlotSpectrogram::setColorMap( QwtColorMap *colorMap )
+{
+ if ( d_data->colorMap != colorMap )
+ {
+ delete d_data->colorMap;
+ d_data->colorMap = colorMap;
+ }
+
+ invalidateCache();
+ itemChanged();
+}
+
+/*!
+ \return Color Map used for mapping the intensity values to colors
+ \sa setColorMap()
+*/
+const QwtColorMap *QwtPlotSpectrogram::colorMap() const
+{
+ return d_data->colorMap;
+}
+
+/*!
+ \brief Set the default pen for the contour lines
+
+ If the spectrogram has a valid default contour pen
+ a contour line is painted using the default contour pen.
+ Otherwise (pen.style() == Qt::NoPen) the pen is calculated
+ for each contour level using contourPen().
+
+ \sa defaultContourPen(), contourPen()
+*/
+void QwtPlotSpectrogram::setDefaultContourPen( const QPen &pen )
+{
+ if ( pen != d_data->defaultContourPen )
+ {
+ d_data->defaultContourPen = pen;
+ itemChanged();
+ }
+}
+
+/*!
+ \return Default contour pen
+ \sa setDefaultContourPen()
+*/
+QPen QwtPlotSpectrogram::defaultContourPen() const
+{
+ return d_data->defaultContourPen;
+}
+
+/*!
+ \brief Calculate the pen for a contour line
+
+ The color of the pen is the color for level calculated by the color map
+
+ \param level Contour level
+ \return Pen for the contour line
+ \note contourPen is only used if defaultContourPen().style() == Qt::NoPen
+
+ \sa setDefaultContourPen(), setColorMap(), setContourLevels()
+*/
+QPen QwtPlotSpectrogram::contourPen( double level ) const
+{
+ if ( d_data->data == NULL || d_data->colorMap == NULL )
+ return QPen();
+
+ const QwtInterval intensityRange = d_data->data->interval(Qt::ZAxis);
+ const QColor c( d_data->colorMap->rgb( intensityRange, level ) );
+
+ return QPen( c );
+}
+
+/*!
+ Modify an attribute of the CONREC algorithm, used to calculate
+ the contour lines.
+
+ \param flag CONREC flag
+ \param on On/Off
+
+ \sa testConrecFlag(), renderContourLines(),
+ QwtRasterData::contourLines()
+*/
+void QwtPlotSpectrogram::setConrecFlag(
+ QwtRasterData::ConrecFlag flag, bool on )
+{
+ if ( bool( d_data->conrecFlags & flag ) == on )
+ return;
+
+ if ( on )
+ d_data->conrecFlags |= flag;
+ else
+ d_data->conrecFlags &= ~flag;
+
+ itemChanged();
+}
+
+/*!
+ Test an attribute of the CONREC algorithm, used to calculate
+ the contour lines.
+
+ \param flag CONREC flag
+ \return true, is enabled
+
+ \sa setConrecClag(), renderContourLines(),
+ QwtRasterData::contourLines()
+*/
+bool QwtPlotSpectrogram::testConrecFlag(
+ QwtRasterData::ConrecFlag flag ) const
+{
+ return d_data->conrecFlags & flag;
+}
+
+/*!
+ Set the levels of the contour lines
+
+ \param levels Values of the contour levels
+ \sa contourLevels(), renderContourLines(),
+ QwtRasterData::contourLines()
+
+ \note contourLevels returns the same levels but sorted.
+*/
+void QwtPlotSpectrogram::setContourLevels( const QList<double> &levels )
+{
+ d_data->contourLevels = levels;
+ qSort( d_data->contourLevels );
+ itemChanged();
+}
+
+/*!
+ \brief Return the levels of the contour lines.
+
+ The levels are sorted in increasing order.
+
+ \sa contourLevels(), renderContourLines(),
+ QwtRasterData::contourLines()
+*/
+QList<double> QwtPlotSpectrogram::contourLevels() const
+{
+ return d_data->contourLevels;
+}
+
+/*!
+ Set the data to be displayed
+
+ \param data Spectrogram Data
+ \sa data()
+*/
+void QwtPlotSpectrogram::setData( QwtRasterData *data )
+{
+ if ( data != d_data->data )
+ {
+ delete d_data->data;
+ d_data->data = data;
+
+ invalidateCache();
+ itemChanged();
+ }
+}
+
+/*!
+ \return Spectrogram data
+ \sa setData()
+*/
+const QwtRasterData *QwtPlotSpectrogram::data() const
+{
+ return d_data->data;
+}
+
+/*!
+ \return Spectrogram data
+ \sa setData()
+*/
+QwtRasterData *QwtPlotSpectrogram::data()
+{
+ return d_data->data;
+}
+
+/*!
+ \return Bounding interval for an axis
+
+ The default implementation returns the interval of the
+ associated raster data object.
+
+ \param axis X, Y, or Z axis
+ \sa QwtRasterData::interval()
+*/
+QwtInterval QwtPlotSpectrogram::interval(Qt::Axis axis) const
+{
+ if ( d_data->data == NULL )
+ return QwtInterval();
+
+ return d_data->data->interval( axis );
+}
+
+/*!
+ \brief Pixel hint
+
+ The geometry of a pixel is used to calculated the resolution and
+ alignment of the rendered image.
+
+ The default implementation returns data()->pixelHint( rect );
+
+ \param area In most implementations the resolution of the data doesn't
+ depend on the requested area.
+
+ \return Bounding rectangle of a pixel
+
+ \sa QwtPlotRasterItem::pixelHint(), QwtRasterData::pixelHint(),
+ render(), renderImage()
+*/
+QRectF QwtPlotSpectrogram::pixelHint( const QRectF &area ) const
+{
+ if ( d_data->data == NULL )
+ return QRectF();
+
+ return d_data->data->pixelHint( area );
+}
+
+/*!
+ \brief Render an image from data and color map.
+
+ For each pixel of rect the value is mapped into a color.
+
+ \param xMap X-Scale Map
+ \param yMap Y-Scale Map
+ \param area Requested area for the image in scale coordinates
+ \param imageSize Size of the requested image
+
+ \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending
+ on the color map.
+
+ \sa QwtRasterData::value(), QwtColorMap::rgb(),
+ QwtColorMap::colorIndex()
+*/
+QImage QwtPlotSpectrogram::renderImage(
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &area, const QSize &imageSize ) const
+{
+ if ( imageSize.isEmpty() || d_data->data == NULL
+ || d_data->colorMap == NULL )
+ {
+ return QImage();
+ }
+
+ const QwtInterval intensityRange = d_data->data->interval( Qt::ZAxis );
+ if ( !intensityRange.isValid() )
+ return QImage();
+
+ QImage::Format format = ( d_data->colorMap->format() == QwtColorMap::RGB )
+ ? QImage::Format_ARGB32 : QImage::Format_Indexed8;
+
+ QImage image( imageSize, format );
+
+ if ( d_data->colorMap->format() == QwtColorMap::Indexed )
+ image.setColorTable( d_data->colorMap->colorTable( intensityRange ) );
+
+ d_data->data->initRaster( area, image.size() );
+
+#if QT_VERSION >= 0x040400 && !defined(QT_NO_QFUTURE)
+ uint numThreads = d_data->renderThreadCount;
+
+ if ( numThreads <= 0 )
+ numThreads = QThread::idealThreadCount();
+
+ if ( numThreads <= 0 )
+ numThreads = 1;
+
+ const int numRows = imageSize.height() / numThreads;
+
+ QList< QFuture<void> > futures;
+ for ( uint i = 0; i < numThreads; i++ )
+ {
+ QRect tile( 0, i * numRows, image.width(), numRows );
+ if ( i == numThreads - 1 )
+ {
+ tile.setHeight( image.height() - i * numRows );
+ renderTile( xMap, yMap, tile, &image );
+ }
+ else
+ {
+ futures += QtConcurrent::run(
+ this, &QwtPlotSpectrogram::renderTile,
+ xMap, yMap, tile, &image );
+ }
+ }
+ for ( int i = 0; i < futures.size(); i++ )
+ futures[i].waitForFinished();
+
+#else // QT_VERSION < 0x040400
+ const QRect tile( 0, 0, image.width(), image.height() );
+ renderTile( xMap, yMap, tile, &image );
+#endif
+
+ d_data->data->discardRaster();
+
+ return image;
+}
+
+/*!
+ \brief Render a tile of an image.
+
+ Rendering in tiles can be used to composite an image in parallel
+ threads.
+
+ \param xMap X-Scale Map
+ \param yMap Y-Scale Map
+ \param tile Geometry of the tile in image coordinates
+ \param image Image to be rendered
+*/
+void QwtPlotSpectrogram::renderTile(
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRect &tile, QImage *image ) const
+{
+ const QwtInterval range = d_data->data->interval( Qt::ZAxis );
+ if ( !range.isValid() )
+ return;
+
+ if ( d_data->colorMap->format() == QwtColorMap::RGB )
+ {
+ for ( int y = tile.top(); y <= tile.bottom(); y++ )
+ {
+ const double ty = yMap.invTransform( y );
+
+ QRgb *line = ( QRgb * )image->scanLine( y );
+ line += tile.left();
+
+ for ( int x = tile.left(); x <= tile.right(); x++ )
+ {
+ const double tx = xMap.invTransform( x );
+
+ *line++ = d_data->colorMap->rgb( range,
+ d_data->data->value( tx, ty ) );
+ }
+ }
+ }
+ else if ( d_data->colorMap->format() == QwtColorMap::Indexed )
+ {
+ for ( int y = tile.top(); y <= tile.bottom(); y++ )
+ {
+ const double ty = yMap.invTransform( y );
+
+ unsigned char *line = image->scanLine( y );
+ line += tile.left();
+
+ for ( int x = tile.left(); x <= tile.right(); x++ )
+ {
+ const double tx = xMap.invTransform( x );
+
+ *line++ = d_data->colorMap->colorIndex( range,
+ d_data->data->value( tx, ty ) );
+ }
+ }
+ }
+}
+
+/*!
+ \brief Return the raster to be used by the CONREC contour algorithm.
+
+ A larger size will improve the precisision of the CONREC algorithm,
+ but will slow down the time that is needed to calculate the lines.
+
+ The default implementation returns rect.size() / 2 bounded to
+ the resolution depending on pixelSize().
+
+ \param area Rect, where to calculate the contour lines
+ \param rect Rect in pixel coordinates, where to paint the contour lines
+ \return Raster to be used by the CONREC contour algorithm.
+
+ \note The size will be bounded to rect.size().
+
+ \sa drawContourLines(), QwtRasterData::contourLines()
+*/
+QSize QwtPlotSpectrogram::contourRasterSize(
+ const QRectF &area, const QRect &rect ) const
+{
+ QSize raster = rect.size() / 2;
+
+ const QRectF pixelRect = pixelHint( area );
+ if ( !pixelRect.isEmpty() )
+ {
+ const QSize res( qCeil( rect.width() / pixelRect.width() ),
+ qCeil( rect.height() / pixelRect.height() ) );
+ raster = raster.boundedTo( res );
+ }
+
+ return raster;
+}
+
+/*!
+ Calculate contour lines
+
+ \param rect Rectangle, where to calculate the contour lines
+ \param raster Raster, used by the CONREC algorithm
+
+ \sa contourLevels(), setConrecFlag(),
+ QwtRasterData::contourLines()
+*/
+QwtRasterData::ContourLines QwtPlotSpectrogram::renderContourLines(
+ const QRectF &rect, const QSize &raster ) const
+{
+ if ( d_data->data == NULL )
+ return QwtRasterData::ContourLines();
+
+ return d_data->data->contourLines( rect, raster,
+ d_data->contourLevels, d_data->conrecFlags );
+}
+
+/*!
+ Paint the contour lines
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param contourLines Contour lines
+
+ \sa renderContourLines(), defaultContourPen(), contourPen()
+*/
+void QwtPlotSpectrogram::drawContourLines( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QwtRasterData::ContourLines &contourLines ) const
+{
+ if ( d_data->data == NULL )
+ return;
+
+ const int numLevels = d_data->contourLevels.size();
+ for ( int l = 0; l < numLevels; l++ )
+ {
+ const double level = d_data->contourLevels[l];
+
+ QPen pen = defaultContourPen();
+ if ( pen.style() == Qt::NoPen )
+ pen = contourPen( level );
+
+ if ( pen.style() == Qt::NoPen )
+ continue;
+
+ painter->setPen( pen );
+
+ const QPolygonF &lines = contourLines[level];
+ for ( int i = 0; i < lines.size(); i += 2 )
+ {
+ const QPointF p1( xMap.transform( lines[i].x() ),
+ yMap.transform( lines[i].y() ) );
+ const QPointF p2( xMap.transform( lines[i+1].x() ),
+ yMap.transform( lines[i+1].y() ) );
+
+ QwtPainter::drawLine( painter, p1, p2 );
+ }
+ }
+}
+
+/*!
+ \brief Draw the spectrogram
+
+ \param painter Painter
+ \param xMap Maps x-values into pixel coordinates.
+ \param yMap Maps y-values into pixel coordinates.
+ \param canvasRect Contents rect of the canvas in painter coordinates
+
+ \sa setDisplayMode(), renderImage(),
+ QwtPlotRasterItem::draw(), drawContourLines()
+*/
+void QwtPlotSpectrogram::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ if ( d_data->displayMode & ImageMode )
+ QwtPlotRasterItem::draw( painter, xMap, yMap, canvasRect );
+
+ if ( d_data->displayMode & ContourMode )
+ {
+ // Add some pixels at the borders
+ const int margin = 2;
+ QRectF rasterRect( canvasRect.x() - margin, canvasRect.y() - margin,
+ canvasRect.width() + 2 * margin, canvasRect.height() + 2 * margin );
+
+ QRectF area = QwtScaleMap::invTransform( xMap, yMap, rasterRect );
+
+ const QRectF br = boundingRect();
+ if ( br.isValid() )
+ {
+ area &= br;
+ if ( area.isEmpty() )
+ return;
+
+ rasterRect = QwtScaleMap::transform( xMap, yMap, area );
+ }
+
+ QSize raster = contourRasterSize( area, rasterRect.toRect() );
+ raster = raster.boundedTo( rasterRect.toRect().size() );
+ if ( raster.isValid() )
+ {
+ const QwtRasterData::ContourLines lines =
+ renderContourLines( area, raster );
+
+ drawContourLines( painter, xMap, yMap, lines );
+ }
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_spectrogram.h b/src/libpcp_qwt/src/qwt_plot_spectrogram.h
new file mode 100644
index 0000000..423507c
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_spectrogram.h
@@ -0,0 +1,115 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_SPECTROGRAM_H
+#define QWT_PLOT_SPECTROGRAM_H
+
+#include "qwt_global.h"
+#include "qwt_raster_data.h"
+#include "qwt_plot_rasteritem.h"
+#include <qlist.h>
+
+class QwtColorMap;
+
+/*!
+ \brief A plot item, which displays a spectrogram
+
+ A spectrogram displays threedimenional data, where the 3rd dimension
+ ( the intensity ) is displayed using colors. The colors are calculated
+ from the values using a color map.
+
+ In ContourMode contour lines are painted for the contour levels.
+
+ \image html spectrogram3.png
+
+ \sa QwtRasterData, QwtColorMap
+*/
+
+class QWT_EXPORT QwtPlotSpectrogram: public QwtPlotRasterItem
+{
+public:
+ /*!
+ The display mode controls how the raster data will be represented.
+ \sa setDisplayMode(), testDisplayMode()
+ */
+
+ enum DisplayMode
+ {
+ //! The values are mapped to colors using a color map.
+ ImageMode = 0x01,
+
+ //! The data is displayed using contour lines
+ ContourMode = 0x02
+ };
+
+ //! Display modes
+ typedef QFlags<DisplayMode> DisplayModes;
+
+ explicit QwtPlotSpectrogram( const QString &title = QString::null );
+ virtual ~QwtPlotSpectrogram();
+
+ void setRenderThreadCount( uint numThreads );
+ uint renderThreadCount() const;
+
+ void setDisplayMode( DisplayMode, bool on = true );
+ bool testDisplayMode( DisplayMode ) const;
+
+ void setData( QwtRasterData *data );
+ const QwtRasterData *data() const;
+ QwtRasterData *data();
+
+ void setColorMap( QwtColorMap * );
+ const QwtColorMap *colorMap() const;
+
+ virtual QwtInterval interval(Qt::Axis) const;
+ virtual QRectF pixelHint( const QRectF & ) const;
+
+ void setDefaultContourPen( const QPen & );
+ QPen defaultContourPen() const;
+
+ virtual QPen contourPen( double level ) const;
+
+ void setConrecFlag( QwtRasterData::ConrecFlag, bool on );
+ bool testConrecFlag( QwtRasterData::ConrecFlag ) const;
+
+ void setContourLevels( const QList<double> & );
+ QList<double> contourLevels() const;
+
+ virtual int rtti() const;
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &rect ) const;
+
+protected:
+ virtual QImage renderImage(
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &area, const QSize &imageSize ) const;
+
+ virtual QSize contourRasterSize(
+ const QRectF &, const QRect & ) const;
+
+ virtual QwtRasterData::ContourLines renderContourLines(
+ const QRectF &rect, const QSize &raster ) const;
+
+ virtual void drawContourLines( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QwtRasterData::ContourLines& lines ) const;
+
+ void renderTile( const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRect &imageRect, QImage *image ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotSpectrogram::DisplayModes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_svgitem.cpp b/src/libpcp_qwt/src/qwt_plot_svgitem.cpp
new file mode 100644
index 0000000..c84395d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_svgitem.cpp
@@ -0,0 +1,214 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_svgitem.h"
+#include "qwt_scale_map.h"
+#include "qwt_legend.h"
+#include "qwt_legend_item.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qsvgrenderer.h>
+
+class QwtPlotSvgItem::PrivateData
+{
+public:
+ PrivateData()
+ {
+ }
+
+ QRectF boundingRect;
+ QSvgRenderer renderer;
+};
+
+/*!
+ \brief Constructor
+
+ Sets the following item attributes:
+ - QwtPlotItem::AutoScale: true
+ - QwtPlotItem::Legend: false
+
+ \param title Title
+*/
+QwtPlotSvgItem::QwtPlotSvgItem( const QString& title ):
+ QwtPlotItem( QwtText( title ) )
+{
+ init();
+}
+
+/*!
+ \brief Constructor
+
+ Sets the following item attributes:
+ - QwtPlotItem::AutoScale: true
+ - QwtPlotItem::Legend: false
+
+ \param title Title
+*/
+QwtPlotSvgItem::QwtPlotSvgItem( const QwtText& title ):
+ QwtPlotItem( title )
+{
+ init();
+}
+
+//! Destructor
+QwtPlotSvgItem::~QwtPlotSvgItem()
+{
+ delete d_data;
+}
+
+void QwtPlotSvgItem::init()
+{
+ d_data = new PrivateData();
+
+ setItemAttribute( QwtPlotItem::AutoScale, true );
+ setItemAttribute( QwtPlotItem::Legend, false );
+
+ setZ( 8.0 );
+}
+
+//! \return QwtPlotItem::Rtti_PlotSVG
+int QwtPlotSvgItem::rtti() const
+{
+ return QwtPlotItem::Rtti_PlotSVG;
+}
+
+/*!
+ Load a SVG file
+
+ \param rect Bounding rectangle
+ \param fileName SVG file name
+
+ \return true, if the SVG file could be loaded
+*/
+bool QwtPlotSvgItem::loadFile( const QRectF &rect,
+ const QString &fileName )
+{
+ d_data->boundingRect = rect;
+ const bool ok = d_data->renderer.load( fileName );
+ itemChanged();
+ return ok;
+}
+
+/*!
+ Load SVG data
+
+ \param rect Bounding rectangle
+ \param data in SVG format
+
+ \return true, if the SVG data could be loaded
+*/
+bool QwtPlotSvgItem::loadData( const QRectF &rect,
+ const QByteArray &data )
+{
+ d_data->boundingRect = rect;
+ const bool ok = d_data->renderer.load( data );
+ itemChanged();
+ return ok;
+}
+
+//! Bounding rect of the item
+QRectF QwtPlotSvgItem::boundingRect() const
+{
+ return d_data->boundingRect;
+}
+
+//! \return Renderer used to render the SVG data
+const QSvgRenderer &QwtPlotSvgItem::renderer() const
+{
+ return d_data->renderer;
+}
+
+//! \return Renderer used to render the SVG data
+QSvgRenderer &QwtPlotSvgItem::renderer()
+{
+ return d_data->renderer;
+}
+
+/*!
+ Draw the SVG item
+
+ \param painter Painter
+ \param xMap X-Scale Map
+ \param yMap Y-Scale Map
+ \param canvasRect Contents rect of the plot canvas
+*/
+void QwtPlotSvgItem::draw( QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect ) const
+{
+ const QRectF cRect = QwtScaleMap::invTransform(
+ xMap, yMap, canvasRect.toRect() );
+ const QRectF bRect = boundingRect();
+ if ( bRect.isValid() && cRect.isValid() )
+ {
+ QRectF rect = bRect;
+ if ( bRect.contains( cRect ) )
+ rect = cRect;
+
+ const QRectF r = QwtScaleMap::transform( xMap, yMap, rect );
+ render( painter, viewBox( rect ), r );
+ }
+}
+
+/*!
+ Render the SVG data
+
+ \param painter Painter
+ \param viewBox View Box, see QSvgRenderer::viewBox
+ \param rect Traget rectangle on the paint device
+*/
+void QwtPlotSvgItem::render( QPainter *painter,
+ const QRectF &viewBox, const QRectF &rect ) const
+{
+ if ( !viewBox.isValid() )
+ return;
+
+ QRectF r = rect;
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ r.setLeft ( qRound( r.left() ) );
+ r.setRight ( qRound( r.right() ) );
+ r.setTop ( qRound( r.top() ) );
+ r.setBottom ( qRound( r.bottom() ) );
+ }
+
+ d_data->renderer.setViewBox( viewBox );
+ d_data->renderer.render( painter, r );
+}
+
+/*!
+ Calculate the viewBox from an rect and boundingRect().
+
+ \param rect Rectangle in scale coordinates
+ \return viewBox View Box, see QSvgRenderer::viewBox
+*/
+QRectF QwtPlotSvgItem::viewBox( const QRectF &rect ) const
+{
+ const QSize sz = d_data->renderer.defaultSize();
+ const QRectF br = boundingRect();
+
+ if ( !rect.isValid() || !br.isValid() || sz.isNull() )
+ return QRectF();
+
+ QwtScaleMap xMap;
+ xMap.setScaleInterval( br.left(), br.right() );
+ xMap.setPaintInterval( 0, sz.width() );
+
+ QwtScaleMap yMap;
+ yMap.setScaleInterval( br.top(), br.bottom() );
+ yMap.setPaintInterval( sz.height(), 0 );
+
+ const double x1 = xMap.transform( rect.left() );
+ const double x2 = xMap.transform( rect.right() );
+ const double y1 = yMap.transform( rect.bottom() );
+ const double y2 = yMap.transform( rect.top() );
+
+ return QRectF( x1, y1, x2 - x1, y2 - y1 );
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_svgitem.h b/src/libpcp_qwt/src/qwt_plot_svgitem.h
new file mode 100644
index 0000000..1d98dee
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_svgitem.h
@@ -0,0 +1,61 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_SVGITEM_H
+#define QWT_PLOT_SVGITEM_H
+
+#include "qwt_global.h"
+#include "qwt_plot_item.h"
+#include <qstring.h>
+
+class QSvgRenderer;
+class QByteArray;
+
+/*!
+ \brief A plot item, which displays
+ data in Scalable Vector Graphics (SVG) format.
+
+ SVG images are often used to display maps
+*/
+
+class QWT_EXPORT QwtPlotSvgItem: public QwtPlotItem
+{
+public:
+ explicit QwtPlotSvgItem( const QString& title = QString::null );
+ explicit QwtPlotSvgItem( const QwtText& title );
+ virtual ~QwtPlotSvgItem();
+
+ bool loadFile( const QRectF&, const QString &fileName );
+ bool loadData( const QRectF&, const QByteArray & );
+
+ virtual QRectF boundingRect() const;
+
+ virtual void draw( QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &rect ) const;
+
+ virtual int rtti() const;
+
+protected:
+ const QSvgRenderer &renderer() const;
+ QSvgRenderer &renderer();
+
+ void render( QPainter *painter,
+ const QRectF &viewBox, const QRectF &rect ) const;
+
+ QRectF viewBox( const QRectF &area ) const;
+
+private:
+ void init();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_plot_xml.cpp b/src/libpcp_qwt/src/qwt_plot_xml.cpp
new file mode 100644
index 0000000..0e72a29
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_xml.cpp
@@ -0,0 +1,41 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot.h"
+
+/*!
+ This method is intended for manipulating the plot widget
+ from a specific editor in the Qwt designer plugin.
+
+ \warning The plot editor has never been implemented.
+*/
+void QwtPlot::applyProperties( const QString & /* xmlDocument */ )
+{
+#if 0
+ // Temporary dummy code, for designer tests
+ setTitle( xmlDocument );
+ replot();
+#endif
+}
+
+/*!
+ This method is intended for manipulating the plot widget
+ from a specific editor in the Qwt designer plugin.
+
+ \warning The plot editor has never been implemented.
+*/
+QString QwtPlot::grabProperties() const
+{
+#if 0
+ // Temporary dummy code, for designer tests
+ return title().text();
+#else
+ return QString::null;
+#endif
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_zoomer.cpp b/src/libpcp_qwt/src/qwt_plot_zoomer.cpp
new file mode 100644
index 0000000..2add7d4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_zoomer.cpp
@@ -0,0 +1,607 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_plot_zoomer.h"
+#include "qwt_plot.h"
+#include "qwt_plot_canvas.h"
+#include "qwt_scale_div.h"
+#include "qwt_picker_machine.h"
+#include <qalgorithms.h>
+
+class QwtPlotZoomer::PrivateData
+{
+public:
+ uint zoomRectIndex;
+ QStack<QRectF> zoomStack;
+
+ int maxStackDepth;
+};
+
+/*!
+ \brief Create a zoomer for a plot canvas.
+
+ The zoomer is set to those x- and y-axis of the parent plot of the
+ canvas that are enabled. If both or no x-axis are enabled, the picker
+ is set to QwtPlot::xBottom. If both or no y-axis are
+ enabled, it is set to QwtPlot::yLeft.
+
+ The zoomer is initialized with a QwtPickerDragRectMachine,
+ the tracker mode is set to QwtPicker::ActiveOnly and the rubberband
+ is set to QwtPicker::RectRubberBand
+
+ \param canvas Plot canvas to observe, also the parent object
+ \param doReplot Call replot for the attached plot before initializing
+ the zoomer with its scales. This might be necessary,
+ when the plot is in a state with pending scale changes.
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
+*/
+QwtPlotZoomer::QwtPlotZoomer( QwtPlotCanvas *canvas, bool doReplot ):
+ QwtPlotPicker( canvas )
+{
+ if ( canvas )
+ init( doReplot );
+}
+
+/*!
+ \brief Create a zoomer for a plot canvas.
+
+ The zoomer is initialized with a QwtPickerDragRectMachine,
+ the tracker mode is set to QwtPicker::ActiveOnly and the rubberband
+ is set to QwtPicker;;RectRubberBand
+
+ \param xAxis X axis of the zoomer
+ \param yAxis Y axis of the zoomer
+ \param canvas Plot canvas to observe, also the parent object
+ \param doReplot Call replot for the attached plot before initializing
+ the zoomer with its scales. This might be necessary,
+ when the plot is in a state with pending scale changes.
+
+ \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase()
+*/
+
+QwtPlotZoomer::QwtPlotZoomer( int xAxis, int yAxis,
+ QwtPlotCanvas *canvas, bool doReplot ):
+ QwtPlotPicker( xAxis, yAxis, canvas )
+{
+ if ( canvas )
+ init( doReplot );
+}
+
+//! Init the zoomer, used by the constructors
+void QwtPlotZoomer::init( bool doReplot )
+{
+ d_data = new PrivateData;
+
+ d_data->maxStackDepth = -1;
+
+ setTrackerMode( ActiveOnly );
+ setRubberBand( RectRubberBand );
+ setStateMachine( new QwtPickerDragRectMachine() );
+
+ if ( doReplot && plot() )
+ plot()->replot();
+
+ setZoomBase( scaleRect() );
+}
+
+QwtPlotZoomer::~QwtPlotZoomer()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Limit the number of recursive zoom operations to depth.
+
+ A value of -1 set the depth to unlimited, 0 disables zooming.
+ If the current zoom rectangle is below depth, the plot is unzoomed.
+
+ \param depth Maximum for the stack depth
+ \sa maxStackDepth()
+ \note depth doesn't include the zoom base, so zoomStack().count() might be
+ maxStackDepth() + 1.
+*/
+void QwtPlotZoomer::setMaxStackDepth( int depth )
+{
+ d_data->maxStackDepth = depth;
+
+ if ( depth >= 0 )
+ {
+ // unzoom if the current depth is below d_data->maxStackDepth
+
+ const int zoomOut =
+ int( d_data->zoomStack.count() ) - 1 - depth; // -1 for the zoom base
+
+ if ( zoomOut > 0 )
+ {
+ zoom( -zoomOut );
+ for ( int i = int( d_data->zoomStack.count() ) - 1;
+ i > int( d_data->zoomRectIndex ); i-- )
+ {
+ ( void )d_data->zoomStack.pop(); // remove trailing rects
+ }
+ }
+ }
+}
+
+/*!
+ \return Maximal depth of the zoom stack.
+ \sa setMaxStackDepth()
+*/
+int QwtPlotZoomer::maxStackDepth() const
+{
+ return d_data->maxStackDepth;
+}
+
+/*!
+ Return the zoom stack. zoomStack()[0] is the zoom base,
+ zoomStack()[1] the first zoomed rectangle.
+
+ \sa setZoomStack(), zoomRectIndex()
+*/
+const QStack<QRectF> &QwtPlotZoomer::zoomStack() const
+{
+ return d_data->zoomStack;
+}
+
+/*!
+ \return Initial rectangle of the zoomer
+ \sa setZoomBase(), zoomRect()
+*/
+QRectF QwtPlotZoomer::zoomBase() const
+{
+ return d_data->zoomStack[0];
+}
+
+/*!
+ Reinitialized the zoom stack with scaleRect() as base.
+
+ \param doReplot Call replot for the attached plot before initializing
+ the zoomer with its scales. This might be necessary,
+ when the plot is in a state with pending scale changes.
+
+ \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot().
+*/
+void QwtPlotZoomer::setZoomBase( bool doReplot )
+{
+ QwtPlot *plt = plot();
+ if ( plt == NULL )
+ return;
+
+ if ( doReplot )
+ plt->replot();
+
+ d_data->zoomStack.clear();
+ d_data->zoomStack.push( scaleRect() );
+ d_data->zoomRectIndex = 0;
+
+ rescale();
+}
+
+/*!
+ \brief Set the initial size of the zoomer.
+
+ base is united with the current scaleRect() and the zoom stack is
+ reinitalized with it as zoom base. plot is zoomed to scaleRect().
+
+ \param base Zoom base
+
+ \sa zoomBase(), scaleRect()
+*/
+void QwtPlotZoomer::setZoomBase( const QRectF &base )
+{
+ const QwtPlot *plt = plot();
+ if ( !plt )
+ return;
+
+ const QRectF sRect = scaleRect();
+ const QRectF bRect = base | sRect;
+
+ d_data->zoomStack.clear();
+ d_data->zoomStack.push( bRect );
+ d_data->zoomRectIndex = 0;
+
+ if ( base != sRect )
+ {
+ d_data->zoomStack.push( sRect );
+ d_data->zoomRectIndex++;
+ }
+
+ rescale();
+}
+
+/*!
+ Rectangle at the current position on the zoom stack.
+
+ \sa zoomRectIndex(), scaleRect().
+*/
+QRectF QwtPlotZoomer::zoomRect() const
+{
+ return d_data->zoomStack[d_data->zoomRectIndex];
+}
+
+/*!
+ \return Index of current position of zoom stack.
+*/
+uint QwtPlotZoomer::zoomRectIndex() const
+{
+ return d_data->zoomRectIndex;
+}
+
+/*!
+ \brief Zoom in
+
+ Clears all rectangles above the current position of the
+ zoom stack and pushs the normalized rect on it.
+
+ \note If the maximal stack depth is reached, zoom is ignored.
+ \note The zoomed signal is emitted.
+*/
+
+void QwtPlotZoomer::zoom( const QRectF &rect )
+{
+ if ( d_data->maxStackDepth >= 0 &&
+ int( d_data->zoomRectIndex ) >= d_data->maxStackDepth )
+ {
+ return;
+ }
+
+ const QRectF zoomRect = rect.normalized();
+ if ( zoomRect != d_data->zoomStack[d_data->zoomRectIndex] )
+ {
+ for ( uint i = int( d_data->zoomStack.count() ) - 1;
+ i > d_data->zoomRectIndex; i-- )
+ {
+ ( void )d_data->zoomStack.pop();
+ }
+
+ d_data->zoomStack.push( zoomRect );
+ d_data->zoomRectIndex++;
+
+ rescale();
+
+ Q_EMIT zoomed( zoomRect );
+ }
+}
+
+/*!
+ \brief Zoom in or out
+
+ Activate a rectangle on the zoom stack with an offset relative
+ to the current position. Negative values of offest will zoom out,
+ positive zoom in. A value of 0 zooms out to the zoom base.
+
+ \param offset Offset relative to the current position of the zoom stack.
+ \note The zoomed signal is emitted.
+ \sa zoomRectIndex()
+*/
+void QwtPlotZoomer::zoom( int offset )
+{
+ if ( offset == 0 )
+ d_data->zoomRectIndex = 0;
+ else
+ {
+ int newIndex = d_data->zoomRectIndex + offset;
+ newIndex = qMax( 0, newIndex );
+ newIndex = qMin( int( d_data->zoomStack.count() ) - 1, newIndex );
+
+ d_data->zoomRectIndex = uint( newIndex );
+ }
+
+ rescale();
+
+ Q_EMIT zoomed( zoomRect() );
+}
+
+/*!
+ \brief Assign a zoom stack
+
+ In combination with other types of navigation it might be useful to
+ modify to manipulate the complete zoom stack.
+
+ \param zoomStack New zoom stack
+ \param zoomRectIndex Index of the current position of zoom stack.
+ In case of -1 the current position is at the top
+ of the stack.
+
+ \note The zoomed signal might be emitted.
+ \sa zoomStack(), zoomRectIndex()
+*/
+void QwtPlotZoomer::setZoomStack(
+ const QStack<QRectF> &zoomStack, int zoomRectIndex )
+{
+ if ( zoomStack.isEmpty() )
+ return;
+
+ if ( d_data->maxStackDepth >= 0 &&
+ int( zoomStack.count() ) > d_data->maxStackDepth )
+ {
+ return;
+ }
+
+ if ( zoomRectIndex < 0 || zoomRectIndex > int( zoomStack.count() ) )
+ zoomRectIndex = zoomStack.count() - 1;
+
+ const bool doRescale = zoomStack[zoomRectIndex] != zoomRect();
+
+ d_data->zoomStack = zoomStack;
+ d_data->zoomRectIndex = uint( zoomRectIndex );
+
+ if ( doRescale )
+ {
+ rescale();
+ Q_EMIT zoomed( zoomRect() );
+ }
+}
+
+/*!
+ Adjust the observed plot to zoomRect()
+
+ \note Initiates QwtPlot::replot
+*/
+
+void QwtPlotZoomer::rescale()
+{
+ QwtPlot *plt = plot();
+ if ( !plt )
+ return;
+
+ const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
+ if ( rect != scaleRect() )
+ {
+ const bool doReplot = plt->autoReplot();
+ plt->setAutoReplot( false );
+
+ double x1 = rect.left();
+ double x2 = rect.right();
+ if ( plt->axisScaleDiv( xAxis() )->lowerBound() >
+ plt->axisScaleDiv( xAxis() )->upperBound() )
+ {
+ qSwap( x1, x2 );
+ }
+
+ plt->setAxisScale( xAxis(), x1, x2 );
+
+ double y1 = rect.top();
+ double y2 = rect.bottom();
+ if ( plt->axisScaleDiv( yAxis() )->lowerBound() >
+ plt->axisScaleDiv( yAxis() )->upperBound() )
+ {
+ qSwap( y1, y2 );
+ }
+ plt->setAxisScale( yAxis(), y1, y2 );
+
+ plt->setAutoReplot( doReplot );
+
+ plt->replot();
+ }
+}
+
+/*!
+ Reinitialize the axes, and set the zoom base to their scales.
+
+ \param xAxis X axis
+ \param yAxis Y axis
+*/
+
+void QwtPlotZoomer::setAxis( int xAxis, int yAxis )
+{
+ if ( xAxis != QwtPlotPicker::xAxis() || yAxis != QwtPlotPicker::yAxis() )
+ {
+ QwtPlotPicker::setAxis( xAxis, yAxis );
+ setZoomBase( scaleRect() );
+ }
+}
+
+/*!
+ Qt::MidButton zooms out one position on the zoom stack,
+ Qt::RightButton to the zoom base.
+
+ Changes the current position on the stack, but doesn't pop
+ any rectangle.
+
+ \note The mouse events can be changed, using
+ QwtEventPattern::setMousePattern: 2, 1
+*/
+void QwtPlotZoomer::widgetMouseReleaseEvent( QMouseEvent *me )
+{
+ if ( mouseMatch( MouseSelect2, me ) )
+ zoom( 0 );
+ else if ( mouseMatch( MouseSelect3, me ) )
+ zoom( -1 );
+ else if ( mouseMatch( MouseSelect6, me ) )
+ zoom( +1 );
+ else
+ QwtPlotPicker::widgetMouseReleaseEvent( me );
+}
+
+/*!
+ Qt::Key_Plus zooms in, Qt::Key_Minus zooms out one position on the
+ zoom stack, Qt::Key_Escape zooms out to the zoom base.
+
+ Changes the current position on the stack, but doesn't pop
+ any rectangle.
+
+ \note The keys codes can be changed, using
+ QwtEventPattern::setKeyPattern: 3, 4, 5
+*/
+
+void QwtPlotZoomer::widgetKeyPressEvent( QKeyEvent *ke )
+{
+ if ( !isActive() )
+ {
+ if ( keyMatch( KeyUndo, ke ) )
+ zoom( -1 );
+ else if ( keyMatch( KeyRedo, ke ) )
+ zoom( +1 );
+ else if ( keyMatch( KeyHome, ke ) )
+ zoom( 0 );
+ }
+
+ QwtPlotPicker::widgetKeyPressEvent( ke );
+}
+
+/*!
+ Move the current zoom rectangle.
+
+ \param dx X offset
+ \param dy Y offset
+
+ \note The changed rectangle is limited by the zoom base
+*/
+void QwtPlotZoomer::moveBy( double dx, double dy )
+{
+ const QRectF &rect = d_data->zoomStack[d_data->zoomRectIndex];
+ moveTo( QPointF( rect.left() + dx, rect.top() + dy ) );
+}
+
+/*!
+ Move the the current zoom rectangle.
+
+ \param pos New position
+
+ \sa QRectF::moveTo()
+ \note The changed rectangle is limited by the zoom base
+*/
+void QwtPlotZoomer::moveTo( const QPointF &pos )
+{
+ double x = pos.x();
+ double y = pos.y();
+
+ if ( x < zoomBase().left() )
+ x = zoomBase().left();
+ if ( x > zoomBase().right() - zoomRect().width() )
+ x = zoomBase().right() - zoomRect().width();
+
+ if ( y < zoomBase().top() )
+ y = zoomBase().top();
+ if ( y > zoomBase().bottom() - zoomRect().height() )
+ y = zoomBase().bottom() - zoomRect().height();
+
+ if ( x != zoomRect().left() || y != zoomRect().top() )
+ {
+ d_data->zoomStack[d_data->zoomRectIndex].moveTo( x, y );
+ rescale();
+ }
+}
+
+/*!
+ \brief Check and correct a selected rectangle
+
+ Reject rectangles with a hight or width < 2, otherwise
+ expand the selected rectangle to a minimum size of 11x11
+ and accept it.
+
+ \return true If rect is accepted, or has been changed
+ to a accepted rectangle.
+*/
+
+bool QwtPlotZoomer::accept( QPolygon &pa ) const
+{
+ if ( pa.count() < 2 )
+ return false;
+
+ QRect rect = QRect( pa[0], pa[int( pa.count() ) - 1] );
+ rect = rect.normalized();
+
+ const int minSize = 2;
+ if ( rect.width() < minSize && rect.height() < minSize )
+ return false;
+
+ const int minZoomSize = 11;
+
+ const QPoint center = rect.center();
+ rect.setSize( rect.size().expandedTo( QSize( minZoomSize, minZoomSize ) ) );
+ rect.moveCenter( center );
+
+ pa.resize( 2 );
+ pa[0] = rect.topLeft();
+ pa[1] = rect.bottomRight();
+
+ return true;
+}
+
+/*!
+ \brief Limit zooming by a minimum rectangle
+
+ \return zoomBase().width() / 10e4, zoomBase().height() / 10e4
+*/
+QSizeF QwtPlotZoomer::minZoomSize() const
+{
+ return QSizeF( d_data->zoomStack[0].width() / 10e4,
+ d_data->zoomStack[0].height() / 10e4 );
+}
+
+/*!
+ Rejects selections, when the stack depth is too deep, or
+ the zoomed rectangle is minZoomSize().
+
+ \sa minZoomSize(), maxStackDepth()
+*/
+void QwtPlotZoomer::begin()
+{
+ if ( d_data->maxStackDepth >= 0 )
+ {
+ if ( d_data->zoomRectIndex >= uint( d_data->maxStackDepth ) )
+ return;
+ }
+
+ const QSizeF minSize = minZoomSize();
+ if ( minSize.isValid() )
+ {
+ const QSizeF sz =
+ d_data->zoomStack[d_data->zoomRectIndex].size() * 0.9999;
+
+ if ( minSize.width() >= sz.width() &&
+ minSize.height() >= sz.height() )
+ {
+ return;
+ }
+ }
+
+ QwtPlotPicker::begin();
+}
+
+/*!
+ Expand the selected rectangle to minZoomSize() and zoom in
+ if accepted.
+
+ \sa accept(), minZoomSize()
+*/
+bool QwtPlotZoomer::end( bool ok )
+{
+ ok = QwtPlotPicker::end( ok );
+ if ( !ok )
+ return false;
+
+ QwtPlot *plot = QwtPlotZoomer::plot();
+ if ( !plot )
+ return false;
+
+ const QPolygon &pa = selection();
+ if ( pa.count() < 2 )
+ return false;
+
+ QRect rect = QRect( pa[0], pa[int( pa.count() - 1 )] );
+ rect = rect.normalized();
+
+ QRectF zoomRect = invTransform( rect ).normalized();
+
+ const QSizeF minSize = minZoomSize();
+ if ( minSize.isValid() )
+ {
+ const QPointF center = zoomRect.center();
+ zoomRect.setSize( zoomRect.size().expandedTo( minZoomSize() ) );
+ zoomRect.moveCenter( center );
+ }
+
+ zoom( zoomRect );
+
+ return true;
+}
diff --git a/src/libpcp_qwt/src/qwt_plot_zoomer.h b/src/libpcp_qwt/src/qwt_plot_zoomer.h
new file mode 100644
index 0000000..84e23c7
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_plot_zoomer.h
@@ -0,0 +1,104 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_PLOT_ZOOMER_H
+#define QWT_PLOT_ZOOMER_H
+
+#include "qwt_global.h"
+#include "qwt_plot_picker.h"
+#include <qstack.h>
+
+/*!
+ \brief QwtPlotZoomer provides stacked zooming for a plot widget
+
+ QwtPlotZoomer offers rubberband selections on the plot canvas,
+ translating the selected rectangles into plot coordinates and
+ adjusting the axes to them. Zooming can repeated as often as
+ possible, limited only by maxStackDepth() or minZoomSize().
+ Each rectangle is pushed on a stack.
+
+ Zoom rectangles can be selected depending on selectionFlags() using the
+ mouse or keyboard (QwtEventPattern, QwtPickerMachine).
+ QwtEventPattern::MouseSelect3,QwtEventPattern::KeyUndo,
+ or QwtEventPattern::MouseSelect6,QwtEventPattern::KeyRedo
+ walk up and down the zoom stack.
+ QwtEventPattern::MouseSelect2 or QwtEventPattern::KeyHome unzoom to
+ the initial size.
+
+ QwtPlotZoomer is tailored for plots with one x and y axis, but it is
+ allowed to attach a second QwtPlotZoomer for the other axes.
+
+ \note The realtime example includes an derived zoomer class that adds
+ scrollbars to the plot canvas.
+*/
+
+class QWT_EXPORT QwtPlotZoomer: public QwtPlotPicker
+{
+ Q_OBJECT
+public:
+ explicit QwtPlotZoomer( QwtPlotCanvas *, bool doReplot = true );
+ explicit QwtPlotZoomer( int xAxis, int yAxis,
+ QwtPlotCanvas *, bool doReplot = true );
+
+ virtual ~QwtPlotZoomer();
+
+ virtual void setZoomBase( bool doReplot = true );
+ virtual void setZoomBase( const QRectF & );
+
+ QRectF zoomBase() const;
+ QRectF zoomRect() const;
+
+ virtual void setAxis( int xAxis, int yAxis );
+
+ void setMaxStackDepth( int );
+ int maxStackDepth() const;
+
+ const QStack<QRectF> &zoomStack() const;
+ void setZoomStack( const QStack<QRectF> &,
+ int zoomRectIndex = -1 );
+
+ uint zoomRectIndex() const;
+
+public Q_SLOTS:
+ void moveBy( double x, double y );
+ virtual void moveTo( const QPointF & );
+
+ virtual void zoom( const QRectF & );
+ virtual void zoom( int up );
+
+Q_SIGNALS:
+ /*!
+ A signal emitting the zoomRect(), when the plot has been
+ zoomed in or out.
+
+ \param rect Current zoom rectangle.
+ */
+
+ void zoomed( const QRectF &rect );
+
+protected:
+ virtual void rescale();
+
+ virtual QSizeF minZoomSize() const;
+
+ virtual void widgetMouseReleaseEvent( QMouseEvent * );
+ virtual void widgetKeyPressEvent( QKeyEvent * );
+
+ virtual void begin();
+ virtual bool end( bool ok = true );
+ virtual bool accept( QPolygon & ) const;
+
+private:
+ void init( bool doReplot );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_point_3d.cpp b/src/libpcp_qwt/src/qwt_point_3d.cpp
new file mode 100644
index 0000000..27e4c1e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_point_3d.cpp
@@ -0,0 +1,22 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_point_3d.h"
+
+#ifndef QT_NO_DEBUG_STREAM
+
+QDebug operator<<( QDebug debug, const QwtPoint3D &point )
+{
+ debug.nospace() << "QwtPoint3D(" << point.x()
+ << "," << point.y() << "," << point.z() << ")";
+ return debug.space();
+}
+
+#endif
+
diff --git a/src/libpcp_qwt/src/qwt_point_3d.h b/src/libpcp_qwt/src/qwt_point_3d.h
new file mode 100644
index 0000000..ac18938
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_point_3d.h
@@ -0,0 +1,189 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+/*! \file */
+#ifndef QWT_POINT_3D_H
+#define QWT_POINT_3D_H 1
+
+#include "qwt_global.h"
+#include <qpoint.h>
+#ifndef QT_NO_DEBUG_STREAM
+#include <qdebug.h>
+#endif
+
+/*!
+ \brief QwtPoint3D class defines a 3D point in double coordinates
+*/
+
+class QWT_EXPORT QwtPoint3D
+{
+public:
+ QwtPoint3D();
+ QwtPoint3D( double x, double y, double z );
+ QwtPoint3D( const QwtPoint3D & );
+ QwtPoint3D( const QPointF & );
+
+ bool isNull() const;
+
+ double x() const;
+ double y() const;
+ double z() const;
+
+ double &rx();
+ double &ry();
+ double &rz();
+
+ void setX( double x );
+ void setY( double y );
+ void setZ( double y );
+
+ QPointF toPoint() const;
+
+ bool operator==( const QwtPoint3D & ) const;
+ bool operator!=( const QwtPoint3D & ) const;
+
+private:
+ double d_x;
+ double d_y;
+ double d_z;
+};
+
+Q_DECLARE_TYPEINFO(QwtPoint3D, Q_MOVABLE_TYPE);
+
+#ifndef QT_NO_DEBUG_STREAM
+QWT_EXPORT QDebug operator<<( QDebug, const QwtPoint3D & );
+#endif
+
+/*!
+ Constructs a null point.
+ \sa isNull()
+*/
+inline QwtPoint3D::QwtPoint3D():
+ d_x( 0.0 ),
+ d_y( 0.0 ),
+ d_z( 0.0 )
+{
+}
+
+//! Constructs a point with coordinates specified by x, y and z.
+inline QwtPoint3D::QwtPoint3D( double x, double y, double z = 0.0 ):
+ d_x( x ),
+ d_y( y ),
+ d_z( z )
+{
+}
+
+/*!
+ Copy constructor.
+ Constructs a point using the values of the point specified.
+*/
+inline QwtPoint3D::QwtPoint3D( const QwtPoint3D &other ):
+ d_x( other.d_x ),
+ d_y( other.d_y ),
+ d_z( other.d_z )
+{
+}
+
+/*!
+ Constructs a point with x and y coordinates from a 2D point,
+ and a z coordinate of 0.
+*/
+inline QwtPoint3D::QwtPoint3D( const QPointF &other ):
+ d_x( other.x() ),
+ d_y( other.y() ),
+ d_z( 0.0 )
+{
+}
+
+/*!
+ Returns true if the point is null; otherwise returns false.
+
+ A point is considered to be null if x, y and z-coordinates
+ are equal to zero.
+*/
+inline bool QwtPoint3D::isNull() const
+{
+ return d_x == 0.0 && d_y == 0.0 && d_z == 0.0;
+}
+
+//! Returns the x-coordinate of the point.
+inline double QwtPoint3D::x() const
+{
+ return d_x;
+}
+
+//! Returns the y-coordinate of the point.
+inline double QwtPoint3D::y() const
+{
+ return d_y;
+}
+
+//! Returns the z-coordinate of the point.
+inline double QwtPoint3D::z() const
+{
+ return d_z;
+}
+
+//! Returns a reference to the x-coordinate of the point.
+inline double &QwtPoint3D::rx()
+{
+ return d_x;
+}
+
+//! Returns a reference to the y-coordinate of the point.
+inline double &QwtPoint3D::ry()
+{
+ return d_y;
+}
+
+//! Returns a reference to the z-coordinate of the point.
+inline double &QwtPoint3D::rz()
+{
+ return d_z;
+}
+
+//! Sets the x-coordinate of the point to the value specified by x.
+inline void QwtPoint3D::setX( double x )
+{
+ d_x = x;
+}
+
+//! Sets the y-coordinate of the point to the value specified by y.
+inline void QwtPoint3D::setY( double y )
+{
+ d_y = y;
+}
+
+//! Sets the z-coordinate of the point to the value specified by z.
+inline void QwtPoint3D::setZ( double z )
+{
+ d_z = z;
+}
+
+/*!
+ Rounds 2D point, where the z coordinate is dropped.
+*/
+inline QPointF QwtPoint3D::toPoint() const
+{
+ return QPointF( d_x, d_y );
+}
+
+//! Returns true if this point and other are equal; otherwise returns false.
+inline bool QwtPoint3D::operator==( const QwtPoint3D &other ) const
+{
+ return ( d_x == other.d_x ) && ( d_y == other.d_y ) && ( d_z == other.d_z );
+}
+
+//! Returns true if this rect and other are different; otherwise returns false.
+inline bool QwtPoint3D::operator!=( const QwtPoint3D &other ) const
+{
+ return !operator==( other );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_point_polar.cpp b/src/libpcp_qwt/src/qwt_point_polar.cpp
new file mode 100644
index 0000000..83224ee
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_point_polar.cpp
@@ -0,0 +1,114 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * QwtPolar Widget Library
+ * Copyright (C) 2008 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_point_polar.h"
+#include "qwt_math.h"
+
+#if QT_VERSION < 0x040601
+#define qAtan2(y, x) ::atan2(y, x)
+#endif
+
+/*!
+ Convert and assign values from a point in Cartesian coordinates
+
+ \param p Point in Cartesian coordinates
+ \sa setPoint(), toPoint()
+*/
+QwtPointPolar::QwtPointPolar( const QPointF &p )
+{
+ d_radius = qSqrt( qwtSqr( p.x() ) + qwtSqr( p.y() ) );
+ d_azimuth = qAtan2( p.y(), p.x() );
+}
+
+/*!
+ Convert and assign values from a point in Cartesian coordinates
+ \param p Point in Cartesian coordinates
+*/
+void QwtPointPolar::setPoint( const QPointF &p )
+{
+ d_radius = qSqrt( qwtSqr( p.x() ) + qwtSqr( p.y() ) );
+ d_azimuth = qAtan2( p.y(), p.x() );
+}
+
+/*!
+ Convert and return values in Cartesian coordinates
+
+ \note Invalid or null points will be returned as QPointF(0.0, 0.0)
+ \sa isValid(), isNull()
+*/
+QPointF QwtPointPolar::toPoint() const
+{
+ if ( d_radius <= 0.0 )
+ return QPointF( 0.0, 0.0 );
+
+ const double x = d_radius * qCos( d_azimuth );
+ const double y = d_radius * qSin( d_azimuth );
+
+ return QPointF( x, y );
+}
+
+/*!
+ Returns true if point1 is equal to point2; otherwise returns false.
+
+ Two points are equal to each other if radius and
+ azimuth-coordinates are the same. Points are not equal, when
+ the azimuth differs, but other.azimuth() == azimuth() % (2 * PI).
+
+ \sa normalized()
+*/
+bool QwtPointPolar::operator==( const QwtPointPolar &other ) const
+{
+ return d_radius == other.d_radius && d_azimuth == other.d_azimuth;
+}
+
+/*!
+ Returns true if point1 is not equal to point2; otherwise returns false.
+
+ Two points are equal to each other if radius and
+ azimuth-coordinates are the same. Points are not equal, when
+ the azimuth differs, but other.azimuth() == azimuth() % (2 * PI).
+
+ \sa normalized()
+*/
+bool QwtPointPolar::operator!=( const QwtPointPolar &other ) const
+{
+ return d_radius != other.d_radius || d_azimuth != other.d_azimuth;
+}
+
+/*!
+ Normalize radius and azimuth
+
+ When the radius is < 0.0 it is set to 0.0. The azimuth is
+ a value >= 0.0 and < 2 * M_PI.
+*/
+QwtPointPolar QwtPointPolar::normalized() const
+{
+ const double radius = qMax( d_radius, 0.0 );
+
+ double azimuth = d_azimuth;
+ if ( azimuth < -2.0 * M_PI || azimuth >= 2 * M_PI )
+ azimuth = ::fmod( d_azimuth, 2 * M_PI );
+
+ if ( azimuth < 0.0 )
+ azimuth += 2 * M_PI;
+
+ return QwtPointPolar( azimuth, radius );
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+
+QDebug operator<<( QDebug debug, const QwtPointPolar &point )
+{
+ debug.nospace() << "QwtPointPolar("
+ << point.azimuth() << "," << point.radius() << ")";
+
+ return debug.space();
+}
+
+#endif
+
diff --git a/src/libpcp_qwt/src/qwt_point_polar.h b/src/libpcp_qwt/src/qwt_point_polar.h
new file mode 100644
index 0000000..17c8121
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_point_polar.h
@@ -0,0 +1,195 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+/*! \file */
+#ifndef _QWT_POINT_POLAR_H_
+#define _QWT_POINT_POLAR_H_ 1
+
+#include "qwt_global.h"
+#include "qwt_math.h"
+#include <qpoint.h>
+#ifndef QT_NO_DEBUG_STREAM
+#include <qdebug.h>
+#endif
+
+/*!
+ \brief A point in polar coordinates
+
+ In polar coordinates a point is determined by an angle and a distance.
+ See http://en.wikipedia.org/wiki/Polar_coordinate_system
+*/
+
+class QWT_EXPORT QwtPointPolar
+{
+public:
+ QwtPointPolar();
+ QwtPointPolar( double azimuth, double radius );
+ QwtPointPolar( const QwtPointPolar & );
+ QwtPointPolar( const QPointF & );
+
+ void setPoint( const QPointF & );
+ QPointF toPoint() const;
+
+ bool isValid() const;
+ bool isNull() const;
+
+ double radius() const;
+ double azimuth() const;
+
+ double &rRadius();
+ double &rAzimuth();
+
+ void setRadius( double );
+ void setAzimuth( double );
+
+ bool operator==( const QwtPointPolar & ) const;
+ bool operator!=( const QwtPointPolar & ) const;
+
+ QwtPointPolar normalized() const;
+
+private:
+ double d_azimuth;
+ double d_radius;
+};
+
+/*!
+ Constructs a null point, with a radius and azimuth set to 0.0.
+ \sa QPointF::isNull
+*/
+inline QwtPointPolar::QwtPointPolar():
+ d_azimuth( 0.0 ),
+ d_radius( 0.0 )
+{
+}
+
+/*!
+ Constructs a point with coordinates specified by radius and azimuth.
+
+ \param azimuth Azimuth
+ \param radius Radius
+*/
+inline QwtPointPolar::QwtPointPolar( double azimuth, double radius ):
+ d_azimuth( azimuth ),
+ d_radius( radius )
+{
+}
+
+/*!
+ Constructs a point using the values of the point specified.
+ \param other Other point
+*/
+inline QwtPointPolar::QwtPointPolar( const QwtPointPolar &other ):
+ d_azimuth( other.d_azimuth ),
+ d_radius( other.d_radius )
+{
+}
+
+//! Returns true if radius() >= 0.0
+inline bool QwtPointPolar::isValid() const
+{
+ return d_radius >= 0.0;
+}
+
+//! Returns true if radius() >= 0.0
+inline bool QwtPointPolar::isNull() const
+{
+ return d_radius == 0.0;
+}
+
+//! Returns the radius.
+inline double QwtPointPolar::radius() const
+{
+ return d_radius;
+}
+
+//! Returns the azimuth.
+inline double QwtPointPolar::azimuth() const
+{
+ return d_azimuth;
+}
+
+//! Returns the radius.
+inline double &QwtPointPolar::rRadius()
+{
+ return d_radius;
+}
+
+//! Returns the azimuth.
+inline double &QwtPointPolar::rAzimuth()
+{
+ return d_azimuth;
+}
+
+//! Sets the radius to radius.
+inline void QwtPointPolar::setRadius( double radius )
+{
+ d_radius = radius;
+}
+
+//! Sets the atimuth to atimuth.
+inline void QwtPointPolar::setAzimuth( double azimuth )
+{
+ d_azimuth = azimuth;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QWT_EXPORT QDebug operator<<( QDebug, const QwtPointPolar & );
+#endif
+
+inline QPoint qwtPolar2Pos( const QPoint &pole,
+ double radius, double angle )
+{
+ const double x = pole.x() + radius * qCos( angle );
+ const double y = pole.y() - radius * qSin( angle );
+
+ return QPoint( qRound( x ), qRound( y ) );
+}
+
+inline QPoint qwtDegree2Pos( const QPoint &pole,
+ double radius, double angle )
+{
+ return qwtPolar2Pos( pole, radius, angle / 180.0 * M_PI );
+}
+
+inline QPointF qwtPolar2Pos( const QPointF &pole,
+ double radius, double angle )
+{
+ const double x = pole.x() + radius * qCos( angle );
+ const double y = pole.y() - radius * qSin( angle );
+
+ return QPointF( x, y);
+}
+
+inline QPointF qwtDegree2Pos( const QPointF &pole,
+ double radius, double angle )
+{
+ return qwtPolar2Pos( pole, radius, angle / 180.0 * M_PI );
+}
+
+inline QPointF qwtFastPolar2Pos( const QPointF &pole,
+ double radius, double angle )
+{
+#if QT_VERSION < 0x040601
+ const double x = pole.x() + radius * ::cos( angle );
+ const double y = pole.y() - radius * ::sin( angle );
+#else
+ const double x = pole.x() + radius * qFastCos( angle );
+ const double y = pole.y() - radius * qFastSin( angle );
+#endif
+
+ return QPointF( x, y);
+}
+
+inline QPointF qwtFastDegree2Pos( const QPointF &pole,
+ double radius, double angle )
+{
+ return qwtFastPolar2Pos( pole, radius, angle / 180.0 * M_PI );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_raster_data.cpp b/src/libpcp_qwt/src/qwt_raster_data.cpp
new file mode 100644
index 0000000..f148d81
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_raster_data.cpp
@@ -0,0 +1,390 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_raster_data.h"
+#include "qwt_point_3d.h"
+
+class QwtRasterData::ContourPlane
+{
+public:
+ inline ContourPlane( double z ):
+ d_z( z )
+ {
+ }
+
+ inline bool intersect( const QwtPoint3D vertex[3],
+ QPointF line[2], bool ignoreOnPlane ) const;
+
+ inline double z() const { return d_z; }
+
+private:
+ inline int compare( double z ) const;
+ inline QPointF intersection(
+ const QwtPoint3D& p1, const QwtPoint3D &p2 ) const;
+
+ double d_z;
+};
+
+inline bool QwtRasterData::ContourPlane::intersect(
+ const QwtPoint3D vertex[3], QPointF line[2],
+ bool ignoreOnPlane ) const
+{
+ bool found = true;
+
+ // Are the vertices below (-1), on (0) or above (1) the plan ?
+ const int eq1 = compare( vertex[0].z() );
+ const int eq2 = compare( vertex[1].z() );
+ const int eq3 = compare( vertex[2].z() );
+
+ /*
+ (a) All the vertices lie below the contour level.
+ (b) Two vertices lie below and one on the contour level.
+ (c) Two vertices lie below and one above the contour level.
+ (d) One vertex lies below and two on the contour level.
+ (e) One vertex lies below, one on and one above the contour level.
+ (f) One vertex lies below and two above the contour level.
+ (g) Three vertices lie on the contour level.
+ (h) Two vertices lie on and one above the contour level.
+ (i) One vertex lies on and two above the contour level.
+ (j) All the vertices lie above the contour level.
+ */
+
+ static const int tab[3][3][3] =
+ {
+ // jump table to avoid nested case statements
+ { { 0, 0, 8 }, { 0, 2, 5 }, { 7, 6, 9 } },
+ { { 0, 3, 4 }, { 1, 10, 1 }, { 4, 3, 0 } },
+ { { 9, 6, 7 }, { 5, 2, 0 }, { 8, 0, 0 } }
+ };
+
+ const int edgeType = tab[eq1+1][eq2+1][eq3+1];
+ switch ( edgeType )
+ {
+ case 1:
+ // d(0,0,-1), h(0,0,1)
+ line[0] = vertex[0].toPoint();
+ line[1] = vertex[1].toPoint();
+ break;
+ case 2:
+ // d(-1,0,0), h(1,0,0)
+ line[0] = vertex[1].toPoint();
+ line[1] = vertex[2].toPoint();
+ break;
+ case 3:
+ // d(0,-1,0), h(0,1,0)
+ line[0] = vertex[2].toPoint();
+ line[1] = vertex[0].toPoint();
+ break;
+ case 4:
+ // e(0,-1,1), e(0,1,-1)
+ line[0] = vertex[0].toPoint();
+ line[1] = intersection( vertex[1], vertex[2] );
+ break;
+ case 5:
+ // e(-1,0,1), e(1,0,-1)
+ line[0] = vertex[1].toPoint();
+ line[1] = intersection( vertex[2], vertex[0] );
+ break;
+ case 6:
+ // e(-1,1,0), e(1,0,-1)
+ line[0] = vertex[2].toPoint();
+ line[1] = intersection( vertex[0], vertex[1] );
+ break;
+ case 7:
+ // c(-1,1,-1), f(1,1,-1)
+ line[0] = intersection( vertex[0], vertex[1] );
+ line[1] = intersection( vertex[1], vertex[2] );
+ break;
+ case 8:
+ // c(-1,-1,1), f(1,1,-1)
+ line[0] = intersection( vertex[1], vertex[2] );
+ line[1] = intersection( vertex[2], vertex[0] );
+ break;
+ case 9:
+ // f(-1,1,1), c(1,-1,-1)
+ line[0] = intersection( vertex[2], vertex[0] );
+ line[1] = intersection( vertex[0], vertex[1] );
+ break;
+ case 10:
+ // g(0,0,0)
+ // The CONREC algorithm has no satisfying solution for
+ // what to do, when all vertices are on the plane.
+
+ if ( ignoreOnPlane )
+ found = false;
+ else
+ {
+ line[0] = vertex[2].toPoint();
+ line[1] = vertex[0].toPoint();
+ }
+ break;
+ default:
+ found = false;
+ }
+
+ return found;
+}
+
+inline int QwtRasterData::ContourPlane::compare( double z ) const
+{
+ if ( z > d_z )
+ return 1;
+
+ if ( z < d_z )
+ return -1;
+
+ return 0;
+}
+
+inline QPointF QwtRasterData::ContourPlane::intersection(
+ const QwtPoint3D& p1, const QwtPoint3D &p2 ) const
+{
+ const double h1 = p1.z() - d_z;
+ const double h2 = p2.z() - d_z;
+
+ const double x = ( h2 * p1.x() - h1 * p2.x() ) / ( h2 - h1 );
+ const double y = ( h2 * p1.y() - h1 * p2.y() ) / ( h2 - h1 );
+
+ return QPointF( x, y );
+}
+
+//! Constructor
+QwtRasterData::QwtRasterData()
+{
+}
+
+//! Destructor
+QwtRasterData::~QwtRasterData()
+{
+}
+
+/*!
+ Set the bounding interval for the x, y or z coordinates.
+
+ \param axis Axis
+ \param interval Bounding interval
+
+ \sa interval()
+*/
+void QwtRasterData::setInterval( Qt::Axis axis, const QwtInterval &interval )
+{
+ d_intervals[axis] = interval;
+}
+
+/*!
+ \brief Initialize a raster
+
+ Before the composition of an image QwtPlotSpectrogram calls initRaster,
+ announcing the area and its resolution that will be requested.
+
+ The default implementation does nothing, but for data sets that
+ are stored in files, it might be good idea to reimplement initRaster,
+ where the data is resampled and loaded into memory.
+
+ \param area Area of the raster
+ \param raster Number of horizontal and vertical pixels
+
+ \sa initRaster(), value()
+*/
+void QwtRasterData::initRaster( const QRectF &area, const QSize &raster )
+{
+ Q_UNUSED( area );
+ Q_UNUSED( raster );
+}
+
+/*!
+ \brief Discard a raster
+
+ After the composition of an image QwtPlotSpectrogram calls discardRaster().
+
+ The default implementation does nothing, but if data has been loaded
+ in initRaster(), it could deleted now.
+
+ \sa initRaster(), value()
+*/
+void QwtRasterData::discardRaster()
+{
+}
+
+/*!
+ \brief Pixel hint
+
+ pixelHint() returns the geometry of a pixel, that can be used
+ to calculate the resolution and alignment of the plot item, that is
+ representing the data.
+
+ Width and height of the hint need to be the horizontal
+ and vertical distances between 2 neighboured points.
+ The center of the hint has to be the position of any point
+ ( it doesn't matter which one ).
+
+ An empty hint indicates, that there are values for any detail level.
+
+ Limiting the resolution of the image might significantly improve
+ the performance and heavily reduce the amount of memory when rendering
+ a QImage from the raster data.
+
+ The default implementation returns an empty rectangle recommending
+ to render in target device ( f.e. screen ) resolution.
+
+ \param area In most implementations the resolution of the data doesn't
+ depend on the requested area.
+
+ \return Bounding rectangle of a pixel
+*/
+QRectF QwtRasterData::pixelHint( const QRectF &area ) const
+{
+ Q_UNUSED( area );
+ return QRectF();
+}
+
+/*!
+ Calculate contour lines
+
+ An adaption of CONREC, a simple contouring algorithm.
+ http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/
+*/
+QwtRasterData::ContourLines QwtRasterData::contourLines(
+ const QRectF &rect, const QSize &raster,
+ const QList<double> &levels, ConrecFlags flags ) const
+{
+ ContourLines contourLines;
+
+ if ( levels.size() == 0 || !rect.isValid() || !raster.isValid() )
+ return contourLines;
+
+ const double dx = rect.width() / raster.width();
+ const double dy = rect.height() / raster.height();
+
+ const bool ignoreOnPlane =
+ flags & QwtRasterData::IgnoreAllVerticesOnLevel;
+
+ const QwtInterval range = interval( Qt::ZAxis );
+ bool ignoreOutOfRange = false;
+ if ( range.isValid() )
+ ignoreOutOfRange = flags & IgnoreOutOfRange;
+
+ QwtRasterData *that = const_cast<QwtRasterData *>( this );
+ that->initRaster( rect, raster );
+
+ for ( int y = 0; y < raster.height() - 1; y++ )
+ {
+ enum Position
+ {
+ Center,
+
+ TopLeft,
+ TopRight,
+ BottomRight,
+ BottomLeft,
+
+ NumPositions
+ };
+
+ QwtPoint3D xy[NumPositions];
+
+ for ( int x = 0; x < raster.width() - 1; x++ )
+ {
+ const QPointF pos( rect.x() + x * dx, rect.y() + y * dy );
+
+ if ( x == 0 )
+ {
+ xy[TopRight].setX( pos.x() );
+ xy[TopRight].setY( pos.y() );
+ xy[TopRight].setZ(
+ value( xy[TopRight].x(), xy[TopRight].y() )
+ );
+
+ xy[BottomRight].setX( pos.x() );
+ xy[BottomRight].setY( pos.y() + dy );
+ xy[BottomRight].setZ(
+ value( xy[BottomRight].x(), xy[BottomRight].y() )
+ );
+ }
+
+ xy[TopLeft] = xy[TopRight];
+ xy[BottomLeft] = xy[BottomRight];
+
+ xy[TopRight].setX( pos.x() + dx );
+ xy[TopRight].setY( pos.y() );
+ xy[BottomRight].setX( pos.x() + dx );
+ xy[BottomRight].setY( pos.y() + dy );
+
+ xy[TopRight].setZ(
+ value( xy[TopRight].x(), xy[TopRight].y() )
+ );
+ xy[BottomRight].setZ(
+ value( xy[BottomRight].x(), xy[BottomRight].y() )
+ );
+
+ double zMin = xy[TopLeft].z();
+ double zMax = zMin;
+ double zSum = zMin;
+
+ for ( int i = TopRight; i <= BottomLeft; i++ )
+ {
+ const double z = xy[i].z();
+
+ zSum += z;
+ if ( z < zMin )
+ zMin = z;
+ if ( z > zMax )
+ zMax = z;
+ }
+
+ if ( ignoreOutOfRange )
+ {
+ if ( !range.contains( zMin ) || !range.contains( zMax ) )
+ continue;
+ }
+
+ if ( zMax < levels[0] ||
+ zMin > levels[levels.size() - 1] )
+ {
+ continue;
+ }
+
+ xy[Center].setX( pos.x() + 0.5 * dx );
+ xy[Center].setY( pos.y() + 0.5 * dy );
+ xy[Center].setZ( 0.25 * zSum );
+
+ const int numLevels = levels.size();
+ for ( int l = 0; l < numLevels; l++ )
+ {
+ const double level = levels[l];
+ if ( level < zMin || level > zMax )
+ continue;
+ QPolygonF &lines = contourLines[level];
+ const ContourPlane plane( level );
+
+ QPointF line[2];
+ QwtPoint3D vertex[3];
+
+ for ( int m = TopLeft; m < NumPositions; m++ )
+ {
+ vertex[0] = xy[m];
+ vertex[1] = xy[0];
+ vertex[2] = xy[m != BottomLeft ? m + 1 : TopLeft];
+
+ const bool intersects =
+ plane.intersect( vertex, line, ignoreOnPlane );
+ if ( intersects )
+ {
+ lines += line[0];
+ lines += line[1];
+ }
+ }
+ }
+ }
+ }
+
+ that->discardRaster();
+
+ return contourLines;
+}
diff --git a/src/libpcp_qwt/src/qwt_raster_data.h b/src/libpcp_qwt/src/qwt_raster_data.h
new file mode 100644
index 0000000..d8016c9
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_raster_data.h
@@ -0,0 +1,95 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_RASTER_DATA_H
+#define QWT_RASTER_DATA_H 1
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include <qmap.h>
+#include <qlist.h>
+#include <qpolygon.h>
+
+class QwtScaleMap;
+
+/*!
+ \brief QwtRasterData defines an interface to any type of raster data.
+
+ QwtRasterData is an abstract interface, that is used by
+ QwtPlotRasterItem to find the values at the pixels of its raster.
+
+ Often a raster item is used to display values from a matrix. Then the
+ derived raster data class needs to implement some sort of resampling,
+ that maps the raster of the matrix into the requested raster of
+ the raster item ( depending on resolution and scales of the canvas ).
+*/
+class QWT_EXPORT QwtRasterData
+{
+public:
+ //! Contour lines
+ typedef QMap<double, QPolygonF> ContourLines;
+
+ //! Flags to modify the contour algorithm
+ enum ConrecFlag
+ {
+ //! Ignore all verices on the same level
+ IgnoreAllVerticesOnLevel = 0x01,
+
+ //! Ignore all values, that are out of range
+ IgnoreOutOfRange = 0x02
+ };
+
+ //! Flags to modify the contour algorithm
+ typedef QFlags<ConrecFlag> ConrecFlags;
+
+ QwtRasterData();
+ virtual ~QwtRasterData();
+
+ virtual void setInterval( Qt::Axis, const QwtInterval & );
+ const QwtInterval &interval(Qt::Axis) const;
+
+ virtual QRectF pixelHint( const QRectF & ) const;
+
+ virtual void initRaster( const QRectF &, const QSize& raster );
+ virtual void discardRaster();
+
+ /*!
+ \return the value at a raster position
+ \param x X value in plot coordinates
+ \param y Y value in plot coordinates
+ */
+ virtual double value( double x, double y ) const = 0;
+
+ virtual ContourLines contourLines( const QRectF &rect,
+ const QSize &raster, const QList<double> &levels,
+ ConrecFlags ) const;
+
+ class Contour3DPoint;
+ class ContourPlane;
+
+private:
+ // Disabled copy constructor and operator=
+ QwtRasterData( const QwtRasterData & );
+ QwtRasterData &operator=( const QwtRasterData & );
+
+ QwtInterval d_intervals[3];
+};
+
+/*!
+ \return Bounding interval for a axis
+ \sa setInterval
+*/
+inline const QwtInterval &QwtRasterData::interval( Qt::Axis axis) const
+{
+ return d_intervals[axis];
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtRasterData::ConrecFlags )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_round_scale_draw.cpp b/src/libpcp_qwt/src/qwt_round_scale_draw.cpp
new file mode 100644
index 0000000..e59192b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_round_scale_draw.cpp
@@ -0,0 +1,309 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_round_scale_draw.h"
+#include "qwt_painter.h"
+#include "qwt_scale_div.h"
+#include "qwt_scale_map.h"
+#include <qpen.h>
+#include <qpainter.h>
+#include <qfontmetrics.h>
+#include <qmath.h>
+
+class QwtRoundScaleDraw::PrivateData
+{
+public:
+ PrivateData():
+ center( 50.0, 50.0 ),
+ radius( 50.0 ),
+ startAngle( -135 * 16 ),
+ endAngle( 135 * 16 )
+ {
+ }
+
+ QPointF center;
+ double radius;
+
+ double startAngle;
+ double endAngle;
+};
+
+/*!
+ \brief Constructor
+
+ The range of the scale is initialized to [0, 100],
+ The center is set to (50, 50) with a radius of 50.
+ The angle range is set to [-135, 135].
+*/
+QwtRoundScaleDraw::QwtRoundScaleDraw()
+{
+ d_data = new QwtRoundScaleDraw::PrivateData;
+
+ setRadius( 50 );
+ scaleMap().setPaintInterval( d_data->startAngle, d_data->endAngle );
+}
+
+//! Destructor
+QwtRoundScaleDraw::~QwtRoundScaleDraw()
+{
+ delete d_data;
+}
+
+/*!
+ Change of radius the scale
+
+ Radius is the radius of the backbone without ticks and labels.
+
+ \param radius New Radius
+ \sa moveCenter()
+*/
+void QwtRoundScaleDraw::setRadius( int radius )
+{
+ d_data->radius = radius;
+}
+
+/*!
+ Get the radius
+
+ Radius is the radius of the backbone without ticks and labels.
+
+ \sa setRadius(), extent()
+*/
+int QwtRoundScaleDraw::radius() const
+{
+ return qCeil( d_data->radius );
+}
+
+/*!
+ Move the center of the scale draw, leaving the radius unchanged
+
+ \param center New center
+ \sa setRadius()
+*/
+void QwtRoundScaleDraw::moveCenter( const QPointF &center )
+{
+ d_data->center = center;
+}
+
+//! Get the center of the scale
+QPointF QwtRoundScaleDraw::center() const
+{
+ return d_data->center;
+}
+
+/*!
+ \brief Adjust the baseline circle segment for round scales.
+
+ The baseline will be drawn from min(angle1,angle2) to max(angle1, angle2).
+ The default setting is [ -135, 135 ].
+ An angle of 0 degrees corresponds to the 12 o'clock position,
+ and positive angles count in a clockwise direction.
+ \param angle1
+ \param angle2 boundaries of the angle interval in degrees.
+ \warning <ul>
+ <li>The angle range is limited to [-360, 360] degrees. Angles exceeding
+ this range will be clipped.
+ <li>For angles more than 359 degrees above or below min(angle1, angle2),
+ scale marks will not be drawn.
+ <li>If you need a counterclockwise scale, use QwtScaleDiv::setRange
+ </ul>
+*/
+void QwtRoundScaleDraw::setAngleRange( double angle1, double angle2 )
+{
+ angle1 = qBound( -360.0, angle1, 360.0 );
+ angle2 = qBound( -360.0, angle2, 360.0 );
+
+ d_data->startAngle = angle1 * 16.0;
+ d_data->endAngle = angle2 * 16.0;
+
+ if ( d_data->startAngle == d_data->endAngle )
+ {
+ d_data->startAngle -= 1;
+ d_data->endAngle += 1;
+ }
+
+ scaleMap().setPaintInterval( d_data->startAngle, d_data->endAngle );
+}
+
+/*!
+ Draws the label for a major scale tick
+
+ \param painter Painter
+ \param value Value
+
+ \sa drawTick(), drawBackbone()
+*/
+void QwtRoundScaleDraw::drawLabel( QPainter *painter, double value ) const
+{
+ const QwtText label = tickLabel( painter->font(), value );
+ if ( label.isEmpty() )
+ return;
+
+ const double tval = scaleMap().transform( value );
+ if ( ( tval > d_data->startAngle + 359 * 16 )
+ || ( tval < d_data->startAngle - 359 * 16 ) )
+ {
+ return;
+ }
+
+ double radius = d_data->radius;
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ||
+ hasComponent( QwtAbstractScaleDraw::Backbone ) )
+ {
+ radius += spacing();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ radius += tickLength( QwtScaleDiv::MajorTick );
+
+ const QSizeF sz = label.textSize( painter->font() );
+ const double arc = tval / 16.0 / 360.0 * 2 * M_PI;
+
+ const double x = d_data->center.x() +
+ ( radius + sz.width() / 2.0 ) * qSin( arc );
+ const double y = d_data->center.y() -
+ ( radius + sz.height() / 2.0 ) * cos( arc );
+
+ const QRectF r( x - sz.width() / 2, y - sz.height() / 2,
+ sz.width(), sz.height() );
+ label.draw( painter, r );
+}
+
+/*!
+ Draw a tick
+
+ \param painter Painter
+ \param value Value of the tick
+ \param len Lenght of the tick
+
+ \sa drawBackbone(), drawLabel()
+*/
+void QwtRoundScaleDraw::drawTick( QPainter *painter, double value, double len ) const
+{
+ if ( len <= 0 )
+ return;
+
+ const double tval = scaleMap().transform( value );
+
+ const double cx = d_data->center.x();
+ const double cy = d_data->center.y();
+ const double radius = d_data->radius;
+
+ if ( ( tval <= d_data->startAngle + 359 * 16 )
+ || ( tval >= d_data->startAngle - 359 * 16 ) )
+ {
+ const double arc = double( tval ) / 16.0 * M_PI / 180.0;
+
+ const double sinArc = qSin( arc );
+ const double cosArc = qCos( arc );
+
+ const double x1 = cx + radius * sinArc;
+ const double x2 = cx + ( radius + len ) * sinArc;
+ const double y1 = cy - radius * cosArc;
+ const double y2 = cy - ( radius + len ) * cosArc;
+
+ QwtPainter::drawLine( painter, x1, y1, x2, y2 );
+ }
+}
+
+/*!
+ Draws the baseline of the scale
+ \param painter Painter
+
+ \sa drawTick(), drawLabel()
+*/
+void QwtRoundScaleDraw::drawBackbone( QPainter *painter ) const
+{
+ const double deg1 = scaleMap().p1();
+ const double deg2 = scaleMap().p2();
+
+ const int a1 = qRound( qMin( deg1, deg2 ) - 90 * 16 );
+ const int a2 = qRound( qMax( deg1, deg2 ) - 90 * 16 );
+
+ const double radius = d_data->radius;
+ const double x = d_data->center.x() - radius;
+ const double y = d_data->center.y() - radius;
+
+ painter->drawArc( QRectF( x, y, 2 * radius, 2 * radius ),
+ -a2, a2 - a1 + 1 ); // counterclockwise
+}
+
+/*!
+ Calculate the extent of the scale
+
+ The extent is the distance between the baseline to the outermost
+ pixel of the scale draw. radius() + extent() is an upper limit
+ for the radius of the bounding circle.
+
+ \param font Font used for painting the labels
+
+ \sa setMinimumExtent(), minimumExtent()
+ \warning The implemented algo is not too smart and
+ calculates only an upper limit, that might be a
+ few pixels too large
+*/
+double QwtRoundScaleDraw::extent( const QFont &font ) const
+{
+ double d = 0.0;
+
+ if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
+ {
+ const QwtScaleDiv &sd = scaleDiv();
+ const QList<double> &ticks = sd.ticks( QwtScaleDiv::MajorTick );
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ const double value = ticks[i];
+ if ( !sd.contains( value ) )
+ continue;
+
+ const QwtText label = tickLabel( font, value );
+ if ( label.isEmpty() )
+ continue;
+
+ const double tval = scaleMap().transform( value );
+ if ( ( tval < d_data->startAngle + 360 * 16 )
+ && ( tval > d_data->startAngle - 360 * 16 ) )
+ {
+ const double arc = tval / 16.0 / 360.0 * 2 * M_PI;
+
+ const QSizeF sz = label.textSize( font );
+ const double off = qMax( sz.width(), sz.height() );
+
+ double x = off * qSin( arc );
+ double y = off * qCos( arc );
+
+ const double dist = qSqrt( x * x + y * y );
+ if ( dist > d )
+ d = dist;
+ }
+ }
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ {
+ d += maxTickLength();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
+ {
+ const double pw = qMax( 1, penWidth() ); // penwidth can be zero
+ d += pw;
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Labels ) &&
+ ( hasComponent( QwtAbstractScaleDraw::Ticks ) ||
+ hasComponent( QwtAbstractScaleDraw::Backbone ) ) )
+ {
+ d += spacing();
+ }
+
+ d = qMax( d, minimumExtent() );
+
+ return d;
+}
diff --git a/src/libpcp_qwt/src/qwt_round_scale_draw.h b/src/libpcp_qwt/src/qwt_round_scale_draw.h
new file mode 100644
index 0000000..9e02060
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_round_scale_draw.h
@@ -0,0 +1,68 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_ROUND_SCALE_DRAW_H
+#define QWT_ROUND_SCALE_DRAW_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_scale_draw.h"
+#include <qpoint.h>
+
+class QPen;
+
+/*!
+ \brief A class for drawing round scales
+
+ QwtRoundScaleDraw can be used to draw round scales.
+ The circle segment can be adjusted by QwtRoundScaleDraw::setAngleRange().
+ The geometry of the scale can be specified with
+ QwtRoundScaleDraw::moveCenter() and QwtRoundScaleDraw::setRadius().
+
+ After a scale division has been specified as a QwtScaleDiv object
+ using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s),
+ the scale can be drawn with the QwtAbstractScaleDraw::draw() member.
+*/
+
+class QWT_EXPORT QwtRoundScaleDraw: public QwtAbstractScaleDraw
+{
+public:
+ QwtRoundScaleDraw();
+ virtual ~QwtRoundScaleDraw();
+
+ void setRadius( int radius );
+ int radius() const;
+
+ void moveCenter( double x, double y );
+ void moveCenter( const QPointF & );
+ QPointF center() const;
+
+ void setAngleRange( double angle1, double angle2 );
+
+ virtual double extent( const QFont & ) const;
+
+protected:
+ virtual void drawTick( QPainter *p, double val, double len ) const;
+ virtual void drawBackbone( QPainter *p ) const;
+ virtual void drawLabel( QPainter *p, double val ) const;
+
+private:
+ QwtRoundScaleDraw( const QwtRoundScaleDraw & );
+ QwtRoundScaleDraw &operator=( const QwtRoundScaleDraw &other );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+//! Move the center of the scale draw, leaving the radius unchanged
+inline void QwtRoundScaleDraw::moveCenter( double x, double y )
+{
+ moveCenter( QPointF( x, y ) );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_sampling_thread.cpp b/src/libpcp_qwt/src/qwt_sampling_thread.cpp
new file mode 100644
index 0000000..4cffb3d
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_sampling_thread.cpp
@@ -0,0 +1,106 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_sampling_thread.h"
+#include "qwt_system_clock.h"
+
+class QwtSamplingThread::PrivateData
+{
+public:
+ QwtSystemClock clock;
+
+ double interval;
+ bool isStopped;
+};
+
+
+//! Constructor
+QwtSamplingThread::QwtSamplingThread( QObject *parent ):
+ QThread( parent )
+{
+ d_data = new PrivateData;
+ d_data->interval = 1000; // 1 second
+ d_data->isStopped = true;
+}
+
+//! Destructor
+QwtSamplingThread::~QwtSamplingThread()
+{
+ delete d_data;
+}
+
+/*!
+ Change the interval (in ms), when sample() is called.
+ The default interval is 1000.0 ( = 1s )
+
+ \param interval Interval
+ \sa interval()
+*/
+void QwtSamplingThread::setInterval( double interval )
+{
+ if ( interval < 0.0 )
+ interval = 0.0;
+
+ d_data->interval = interval;
+}
+
+/*!
+ \return Interval (in ms), between 2 calls of sample()
+ \sa setInterval()
+*/
+double QwtSamplingThread::interval() const
+{
+ return d_data->interval;
+}
+
+/*!
+ \return Time (in ms) since the thread was started
+ \sa QThread::start(), run()
+*/
+double QwtSamplingThread::elapsed() const
+{
+ if ( d_data->isStopped )
+ return 0.0;
+
+ return d_data->clock.elapsed();
+}
+
+/*!
+ Terminate the collecting thread
+ \sa QThread::start(), run()
+*/
+void QwtSamplingThread::stop()
+{
+ d_data->isStopped = true;
+}
+
+/*!
+ Loop collecting samples started from QThread::start()
+ \sa stop()
+*/
+void QwtSamplingThread::run()
+{
+ d_data->clock.start();
+ d_data->isStopped = false;
+
+ while ( !d_data->isStopped )
+ {
+ const double elapsed = d_data->clock.elapsed();
+ sample( elapsed / 1000.0 );
+
+ if ( d_data->interval > 0.0 )
+ {
+ const double msecs =
+ d_data->interval - ( d_data->clock.elapsed() - elapsed );
+
+ if ( msecs > 0.0 )
+ usleep( qRound( 1000.0 * msecs ) );
+ }
+ }
+}
diff --git a/src/libpcp_qwt/src/qwt_sampling_thread.h b/src/libpcp_qwt/src/qwt_sampling_thread.h
new file mode 100644
index 0000000..85b876e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_sampling_thread.h
@@ -0,0 +1,50 @@
+#ifndef _QWT_SAMPLING_THREAD_H_
+#define _QWT_SAMPLING_THREAD_H_
+
+#include "qwt_global.h"
+#include <qthread.h>
+
+/*!
+ \brief A thread collecting samples at regular intervals.
+
+ Contiounous signals are converted into a discrete signal by
+ collecting samples at regular intervals. A discrete signal
+ can be displayed by a QwtPlotSeriesItem on a QwtPlot widget.
+
+ QwtSamplingThread starts a thread calling perodically sample(),
+ to collect and store ( or emit ) a single sample.
+
+ \sa QwtPlotCurve, QwtPlotSeriesItem
+*/
+class QWT_EXPORT QwtSamplingThread: public QThread
+{
+ Q_OBJECT
+
+public:
+ virtual ~QwtSamplingThread();
+
+ double interval() const;
+ double elapsed() const;
+
+public Q_SLOTS:
+ void setInterval( double interval );
+ void stop();
+
+protected:
+ explicit QwtSamplingThread( QObject *parent = NULL );
+
+ virtual void run();
+
+ /*!
+ Collect a sample
+
+ \param elapsed Time since the thread was started in miliseconds
+ */
+ virtual void sample( double elapsed ) = 0;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_div.cpp b/src/libpcp_qwt/src/qwt_scale_div.cpp
new file mode 100644
index 0000000..fab2bcc
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_div.cpp
@@ -0,0 +1,173 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_scale_div.h"
+#include "qwt_math.h"
+#include "qwt_interval.h"
+#include <qalgorithms.h>
+
+//! Construct an invalid QwtScaleDiv instance.
+QwtScaleDiv::QwtScaleDiv():
+ d_lowerBound( 0.0 ),
+ d_upperBound( 0.0 ),
+ d_isValid( false )
+{
+}
+
+/*!
+ Construct QwtScaleDiv instance.
+
+ \param interval Interval
+ \param ticks List of major, medium and minor ticks
+*/
+QwtScaleDiv::QwtScaleDiv( const QwtInterval &interval,
+ QList<double> ticks[NTickTypes] ):
+ d_lowerBound( interval.minValue() ),
+ d_upperBound( interval.maxValue() ),
+ d_isValid( true )
+{
+ for ( int i = 0; i < NTickTypes; i++ )
+ d_ticks[i] = ticks[i];
+}
+
+/*!
+ Construct QwtScaleDiv instance.
+
+ \param lowerBound First interval limit
+ \param upperBound Second interval limit
+ \param ticks List of major, medium and minor ticks
+*/
+QwtScaleDiv::QwtScaleDiv(
+ double lowerBound, double upperBound,
+ QList<double> ticks[NTickTypes] ):
+ d_lowerBound( lowerBound ),
+ d_upperBound( upperBound ),
+ d_isValid( true )
+{
+ for ( int i = 0; i < NTickTypes; i++ )
+ d_ticks[i] = ticks[i];
+}
+
+/*!
+ Change the interval
+ \param interval Interval
+*/
+void QwtScaleDiv::setInterval( const QwtInterval &interval )
+{
+ setInterval( interval.minValue(), interval.maxValue() );
+}
+
+/*!
+ \brief Equality operator
+ \return true if this instance is equal to other
+*/
+bool QwtScaleDiv::operator==( const QwtScaleDiv &other ) const
+{
+ if ( d_lowerBound != other.d_lowerBound ||
+ d_upperBound != other.d_upperBound ||
+ d_isValid != other.d_isValid )
+ {
+ return false;
+ }
+
+ for ( int i = 0; i < NTickTypes; i++ )
+ {
+ if ( d_ticks[i] != other.d_ticks[i] )
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ \brief Inequality
+ \return true if this instance is not equal to s
+*/
+bool QwtScaleDiv::operator!=( const QwtScaleDiv &s ) const
+{
+ return ( !( *this == s ) );
+}
+
+//! Invalidate the scale division
+void QwtScaleDiv::invalidate()
+{
+ d_isValid = false;
+
+ // detach arrays
+ for ( int i = 0; i < NTickTypes; i++ )
+ d_ticks[i].clear();
+
+ d_lowerBound = d_upperBound = 0;
+}
+
+//! Check if the scale division is valid
+bool QwtScaleDiv::isValid() const
+{
+ return d_isValid;
+}
+
+/*!
+ Return if a value is between lowerBound() and upperBound()
+
+ \param value Value
+ \return true/false
+*/
+bool QwtScaleDiv::contains( double value ) const
+{
+ if ( !d_isValid )
+ return false;
+
+ const double min = qMin( d_lowerBound, d_upperBound );
+ const double max = qMax( d_lowerBound, d_upperBound );
+
+ return value >= min && value <= max;
+}
+
+//! Invert the scale divison
+void QwtScaleDiv::invert()
+{
+ qSwap( d_lowerBound, d_upperBound );
+
+ for ( int i = 0; i < NTickTypes; i++ )
+ {
+ QList<double>& ticks = d_ticks[i];
+
+ const int size = ticks.count();
+ const int size2 = size / 2;
+
+ for ( int i = 0; i < size2; i++ )
+ qSwap( ticks[i], ticks[size - 1 - i] );
+ }
+}
+
+/*!
+ Assign ticks
+
+ \param type MinorTick, MediumTick or MajorTick
+ \param ticks Values of the tick positions
+*/
+void QwtScaleDiv::setTicks( int type, const QList<double> &ticks )
+{
+ if ( type >= 0 && type < NTickTypes )
+ d_ticks[type] = ticks;
+}
+
+/*!
+ Return a list of ticks
+
+ \param type MinorTick, MediumTick or MajorTick
+*/
+const QList<double> &QwtScaleDiv::ticks( int type ) const
+{
+ if ( type >= 0 && type < NTickTypes )
+ return d_ticks[type];
+
+ static QList<double> noTicks;
+ return noTicks;
+}
diff --git a/src/libpcp_qwt/src/qwt_scale_div.h b/src/libpcp_qwt/src/qwt_scale_div.h
new file mode 100644
index 0000000..fee0824
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_div.h
@@ -0,0 +1,132 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SCALE_DIV_H
+#define QWT_SCALE_DIV_H
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include <qlist.h>
+
+class QwtInterval;
+
+/*!
+ \brief A class representing a scale division
+
+ A scale division consists of its limits and 3 list
+ of tick values qualified as major, medium and minor ticks.
+
+ In most cases scale divisions are calculated by a QwtScaleEngine.
+
+ \sa subDivideInto(), subDivide()
+*/
+
+class QWT_EXPORT QwtScaleDiv
+{
+public:
+ //! Scale tick types
+ enum TickType
+ {
+ //! No ticks
+ NoTick = -1,
+
+ //! Minor ticks
+ MinorTick,
+
+ //! Medium ticks
+ MediumTick,
+
+ //! Major ticks
+ MajorTick,
+
+ //! Number of valid tick types
+ NTickTypes
+ };
+
+ explicit QwtScaleDiv();
+ explicit QwtScaleDiv( const QwtInterval &, QList<double>[NTickTypes] );
+ explicit QwtScaleDiv(
+ double lowerBound, double upperBound, QList<double>[NTickTypes] );
+
+ bool operator==( const QwtScaleDiv &s ) const;
+ bool operator!=( const QwtScaleDiv &s ) const;
+
+ void setInterval( double lowerBound, double upperBound );
+ void setInterval( const QwtInterval & );
+ QwtInterval interval() const;
+
+ double lowerBound() const;
+ double upperBound() const;
+ double range() const;
+
+ bool contains( double v ) const;
+
+ void setTicks( int type, const QList<double> & );
+ const QList<double> &ticks( int type ) const;
+
+ void invalidate();
+ bool isValid() const;
+
+ void invert();
+
+private:
+ double d_lowerBound;
+ double d_upperBound;
+ QList<double> d_ticks[NTickTypes];
+
+ bool d_isValid;
+};
+
+Q_DECLARE_TYPEINFO(QwtScaleDiv, Q_MOVABLE_TYPE);
+
+/*!
+ Change the interval
+ \param lowerBound lower bound
+ \param upperBound upper bound
+*/
+inline void QwtScaleDiv::setInterval( double lowerBound, double upperBound )
+{
+ d_lowerBound = lowerBound;
+ d_upperBound = upperBound;
+}
+
+/*!
+ \return lowerBound -> upperBound
+*/
+inline QwtInterval QwtScaleDiv::interval() const
+{
+ return QwtInterval( d_lowerBound, d_upperBound );
+}
+
+/*!
+ \return lower bound
+ \sa upperBound()
+*/
+inline double QwtScaleDiv::lowerBound() const
+{
+ return d_lowerBound;
+}
+
+/*!
+ \return upper bound
+ \sa lowerBound()
+*/
+inline double QwtScaleDiv::upperBound() const
+{
+ return d_upperBound;
+}
+
+/*!
+ \return upperBound() - lowerBound()
+*/
+inline double QwtScaleDiv::range() const
+{
+ return d_upperBound - d_lowerBound;
+}
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_draw.cpp b/src/libpcp_qwt/src/qwt_scale_draw.cpp
new file mode 100644
index 0000000..9a9b05b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_draw.cpp
@@ -0,0 +1,903 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_scale_draw.h"
+#include "qwt_scale_div.h"
+#include "qwt_scale_map.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qpen.h>
+#include <qpainter.h>
+#include <qmath.h>
+
+#if QT_VERSION < 0x040601
+#define qFastSin(x) qSin(x)
+#define qFastCos(x) qCos(x)
+#endif
+
+class QwtScaleDraw::PrivateData
+{
+public:
+ PrivateData():
+ len( 0 ),
+ alignment( QwtScaleDraw::BottomScale ),
+ labelAlignment( 0 ),
+ labelRotation( 0.0 )
+ {
+ }
+
+ QPointF pos;
+ double len;
+
+ Alignment alignment;
+
+ Qt::Alignment labelAlignment;
+ double labelRotation;
+};
+
+/*!
+ \brief Constructor
+
+ The range of the scale is initialized to [0, 100],
+ The position is at (0, 0) with a length of 100.
+ The orientation is QwtAbstractScaleDraw::Bottom.
+*/
+QwtScaleDraw::QwtScaleDraw()
+{
+ d_data = new QwtScaleDraw::PrivateData;
+ setLength( 100 );
+}
+
+//! Destructor
+QwtScaleDraw::~QwtScaleDraw()
+{
+ delete d_data;
+}
+
+/*!
+ Return alignment of the scale
+ \sa setAlignment()
+*/
+QwtScaleDraw::Alignment QwtScaleDraw::alignment() const
+{
+ return d_data->alignment;
+}
+
+/*!
+ Set the alignment of the scale
+
+ The default alignment is QwtScaleDraw::BottomScale
+ \sa alignment()
+*/
+void QwtScaleDraw::setAlignment( Alignment align )
+{
+ d_data->alignment = align;
+}
+
+/*!
+ Return the orientation
+
+ TopScale, BottomScale are horizontal (Qt::Horizontal) scales,
+ LeftScale, RightScale are vertical (Qt::Vertical) scales.
+
+ \sa alignment()
+*/
+Qt::Orientation QwtScaleDraw::orientation() const
+{
+ switch ( d_data->alignment )
+ {
+ case TopScale:
+ case BottomScale:
+ return Qt::Horizontal;
+ case LeftScale:
+ case RightScale:
+ default:
+ return Qt::Vertical;
+ }
+}
+
+/*!
+ \brief Determine the minimum border distance
+
+ This member function returns the minimum space
+ needed to draw the mark labels at the scale's endpoints.
+
+ \param font Font
+ \param start Start border distance
+ \param end End border distance
+*/
+void QwtScaleDraw::getBorderDistHint( const QFont &font,
+ int &start, int &end ) const
+{
+ start = 0;
+ end = 0;
+
+ if ( !hasComponent( QwtAbstractScaleDraw::Labels ) )
+ return;
+
+ const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
+ if ( ticks.count() == 0 )
+ return;
+
+ // Find the ticks, that are mapped to the borders.
+ // minTick is the tick, that is mapped to the top/left-most position
+ // in widget coordinates.
+
+ double minTick = ticks[0];
+ double minPos = scaleMap().transform( minTick );
+ double maxTick = minTick;
+ double maxPos = minPos;
+
+ for ( int i = 1; i < ticks.count(); i++ )
+ {
+ const double tickPos = scaleMap().transform( ticks[i] );
+ if ( tickPos < minPos )
+ {
+ minTick = ticks[i];
+ minPos = tickPos;
+ }
+ if ( tickPos > scaleMap().transform( maxTick ) )
+ {
+ maxTick = ticks[i];
+ maxPos = tickPos;
+ }
+ }
+
+ double e = 0.0;
+ double s = 0.0;
+ if ( orientation() == Qt::Vertical )
+ {
+ s = -labelRect( font, minTick ).top();
+ s -= qAbs( minPos - qRound( scaleMap().p2() ) );
+
+ e = labelRect( font, maxTick ).bottom();
+ e -= qAbs( maxPos - scaleMap().p1() );
+ }
+ else
+ {
+ s = -labelRect( font, minTick ).left();
+ s -= qAbs( minPos - scaleMap().p1() );
+
+ e = labelRect( font, maxTick ).right();
+ e -= qAbs( maxPos - scaleMap().p2() );
+ }
+
+ if ( s < 0.0 )
+ s = 0.0;
+ if ( e < 0.0 )
+ e = 0.0;
+
+ start = qCeil( s );
+ end = qCeil( e );
+}
+
+/*!
+ Determine the minimum distance between two labels, that is necessary
+ that the texts don't overlap.
+
+ \param font Font
+ \return The maximum width of a label
+
+ \sa getBorderDistHint()
+*/
+
+int QwtScaleDraw::minLabelDist( const QFont &font ) const
+{
+ if ( !hasComponent( QwtAbstractScaleDraw::Labels ) )
+ return 0;
+
+ const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
+ if ( ticks.isEmpty() )
+ return 0;
+
+ const QFontMetrics fm( font );
+
+ const bool vertical = ( orientation() == Qt::Vertical );
+
+ QRectF bRect1;
+ QRectF bRect2 = labelRect( font, ticks[0] );
+ if ( vertical )
+ {
+ bRect2.setRect( -bRect2.bottom(), 0.0, bRect2.height(), bRect2.width() );
+ }
+
+ double maxDist = 0.0;
+
+ for ( int i = 1; i < ticks.count(); i++ )
+ {
+ bRect1 = bRect2;
+ bRect2 = labelRect( font, ticks[i] );
+ if ( vertical )
+ {
+ bRect2.setRect( -bRect2.bottom(), 0.0,
+ bRect2.height(), bRect2.width() );
+ }
+
+ double dist = fm.leading(); // space between the labels
+ if ( bRect1.right() > 0 )
+ dist += bRect1.right();
+ if ( bRect2.left() < 0 )
+ dist += -bRect2.left();
+
+ if ( dist > maxDist )
+ maxDist = dist;
+ }
+
+ double angle = labelRotation() / 180.0 * M_PI;
+ if ( vertical )
+ angle += M_PI / 2;
+
+ const double sinA = qFastSin( angle ); // qreal -> double
+ if ( qFuzzyCompare( sinA + 1.0, 1.0 ) )
+ return qCeil( maxDist );
+
+ const int fmHeight = fm.ascent() - 2;
+
+ // The distance we need until there is
+ // the height of the label font. This height is needed
+ // for the neighbour labal.
+
+ double labelDist = fmHeight / qFastSin( angle ) * qFastCos( angle );
+ if ( labelDist < 0 )
+ labelDist = -labelDist;
+
+ // For text orientations close to the scale orientation
+
+ if ( labelDist > maxDist )
+ labelDist = maxDist;
+
+ // For text orientations close to the opposite of the
+ // scale orientation
+
+ if ( labelDist < fmHeight )
+ labelDist = fmHeight;
+
+ return qCeil( labelDist );
+}
+
+/*!
+ Calculate the width/height that is needed for a
+ vertical/horizontal scale.
+
+ The extent is calculated from the pen width of the backbone,
+ the major tick length, the spacing and the maximum width/height
+ of the labels.
+
+ \param font Font used for painting the labels
+
+ \sa minLength()
+*/
+double QwtScaleDraw::extent( const QFont &font ) const
+{
+ double d = 0;
+
+ if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
+ {
+ if ( orientation() == Qt::Vertical )
+ d = maxLabelWidth( font );
+ else
+ d = maxLabelHeight( font );
+
+ if ( d > 0 )
+ d += spacing();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ {
+ d += maxTickLength();
+ }
+
+ if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
+ {
+ const double pw = qMax( 1, penWidth() ); // penwidth can be zero
+ d += pw;
+ }
+
+ d = qMax( d, minimumExtent() );
+ return d;
+}
+
+/*!
+ Calculate the minimum length that is needed to draw the scale
+
+ \param font Font used for painting the labels
+
+ \sa extent()
+*/
+int QwtScaleDraw::minLength( const QFont &font ) const
+{
+ int startDist, endDist;
+ getBorderDistHint( font, startDist, endDist );
+
+ const QwtScaleDiv &sd = scaleDiv();
+
+ const uint minorCount =
+ sd.ticks( QwtScaleDiv::MinorTick ).count() +
+ sd.ticks( QwtScaleDiv::MediumTick ).count();
+ const uint majorCount =
+ sd.ticks( QwtScaleDiv::MajorTick ).count();
+
+ int lengthForLabels = 0;
+ if ( hasComponent( QwtAbstractScaleDraw::Labels ) )
+ lengthForLabels = minLabelDist( font ) * majorCount;
+
+ int lengthForTicks = 0;
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ {
+ const double pw = qMax( 1, penWidth() ); // penwidth can be zero
+ lengthForTicks = qCeil( ( majorCount + minorCount ) * ( pw + 1.0 ) );
+ }
+
+ return startDist + endDist + qMax( lengthForLabels, lengthForTicks );
+}
+
+/*!
+ Find the position, where to paint a label
+
+ The position has a distance of majTickLength() + spacing() + 1
+ from the backbone. The direction depends on the alignment()
+
+ \param value Value
+*/
+QPointF QwtScaleDraw::labelPosition( double value ) const
+{
+ const double tval = scaleMap().transform( value );
+ double dist = spacing();
+ if ( hasComponent( QwtAbstractScaleDraw::Backbone ) )
+ dist += qMax( 1, penWidth() );
+
+ if ( hasComponent( QwtAbstractScaleDraw::Ticks ) )
+ dist += tickLength( QwtScaleDiv::MajorTick );
+
+ double px = 0;
+ double py = 0;
+
+ switch ( alignment() )
+ {
+ case RightScale:
+ {
+ px = d_data->pos.x() + dist;
+ py = tval;
+ break;
+ }
+ case LeftScale:
+ {
+ px = d_data->pos.x() - dist;
+ py = tval;
+ break;
+ }
+ case BottomScale:
+ {
+ px = tval;
+ py = d_data->pos.y() + dist;
+ break;
+ }
+ case TopScale:
+ {
+ px = tval;
+ py = d_data->pos.y() - dist;
+ break;
+ }
+ }
+
+ return QPointF( px, py );
+}
+
+/*!
+ Draw a tick
+
+ \param painter Painter
+ \param value Value of the tick
+ \param len Lenght of the tick
+
+ \sa drawBackbone(), drawLabel()
+*/
+void QwtScaleDraw::drawTick( QPainter *painter, double value, double len ) const
+{
+ if ( len <= 0 )
+ return;
+
+ const bool roundingAlignment = QwtPainter::roundingAlignment( painter );
+
+ QPointF pos = d_data->pos;
+
+ double tval = scaleMap().transform( value );
+ if ( roundingAlignment )
+ tval = qRound( tval );
+
+ const int pw = penWidth();
+ int a = 0;
+ if ( pw > 1 && roundingAlignment )
+ a = 1;
+
+ switch ( alignment() )
+ {
+ case LeftScale:
+ {
+ double x1 = pos.x() + a;
+ double x2 = pos.x() + a - pw - len;
+ if ( roundingAlignment )
+ {
+ x1 = qRound( x1 );
+ x2 = qRound( x2 );
+ }
+
+ QwtPainter::drawLine( painter, x1, tval, x2, tval );
+ break;
+ }
+
+ case RightScale:
+ {
+ double x1 = pos.x();
+ double x2 = pos.x() + pw + len;
+ if ( roundingAlignment )
+ {
+ x1 = qRound( x1 );
+ x2 = qRound( x2 );
+ }
+
+ QwtPainter::drawLine( painter, x1, tval, x2, tval );
+ break;
+ }
+
+ case BottomScale:
+ {
+ double y1 = pos.y();
+ double y2 = pos.y() + pw + len;
+ if ( roundingAlignment )
+ {
+ y1 = qRound( y1 );
+ y2 = qRound( y2 );
+ }
+
+ QwtPainter::drawLine( painter, tval, y1, tval, y2 );
+ break;
+ }
+
+ case TopScale:
+ {
+ double y1 = pos.y() + a;
+ double y2 = pos.y() - pw - len + a;
+ if ( roundingAlignment )
+ {
+ y1 = qRound( y1 );
+ y2 = qRound( y2 );
+ }
+
+ QwtPainter::drawLine( painter, tval, y1, tval, y2 );
+ break;
+ }
+ }
+}
+
+/*!
+ Draws the baseline of the scale
+ \param painter Painter
+
+ \sa drawTick(), drawLabel()
+*/
+void QwtScaleDraw::drawBackbone( QPainter *painter ) const
+{
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ const QPointF &pos = d_data->pos;
+ const double len = d_data->len;
+ const int pw = qMax( penWidth(), 1 );
+
+ // pos indicates a border not the center of the backbone line
+ // so we need to shift its position depending on the pen width
+ // and the alignment of the scale
+
+ double off;
+ if ( doAlign )
+ {
+ if ( alignment() == LeftScale || alignment() == TopScale )
+ off = ( pw - 1 ) / 2;
+ else
+ off = pw / 2;
+ }
+ else
+ {
+ off = 0.5 * penWidth();
+ }
+
+ switch ( alignment() )
+ {
+ case LeftScale:
+ {
+ double x = pos.x() - off;
+ if ( doAlign )
+ x = qRound( x );
+
+ QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + len );
+ break;
+ }
+ case RightScale:
+ {
+ double x = pos.x() + off;
+ if ( doAlign )
+ x = qRound( x );
+
+ QwtPainter::drawLine( painter, x, pos.y(), x, pos.y() + len );
+ break;
+ }
+ case TopScale:
+ {
+ double y = pos.y() - off;
+ if ( doAlign )
+ y = qRound( y );
+
+ QwtPainter::drawLine( painter, pos.x(), y, pos.x() + len, y );
+ break;
+ }
+ case BottomScale:
+ {
+ double y = pos.y() + off;
+ if ( doAlign )
+ y = qRound( y );
+
+ QwtPainter::drawLine( painter, pos.x(), y, pos.x() + len, y );
+ break;
+ }
+ }
+}
+
+/*!
+ \brief Move the position of the scale
+
+ The meaning of the parameter pos depends on the alignment:
+ <dl>
+ <dt>QwtScaleDraw::LeftScale
+ <dd>The origin is the topmost point of the
+ backbone. The backbone is a vertical line.
+ Scale marks and labels are drawn
+ at the left of the backbone.
+ <dt>QwtScaleDraw::RightScale
+ <dd>The origin is the topmost point of the
+ backbone. The backbone is a vertical line.
+ Scale marks and labels are drawn
+ at the right of the backbone.
+ <dt>QwtScaleDraw::TopScale
+ <dd>The origin is the leftmost point of the
+ backbone. The backbone is a horizontal line.
+ Scale marks and labels are drawn
+ above the backbone.
+ <dt>QwtScaleDraw::BottomScale
+ <dd>The origin is the leftmost point of the
+ backbone. The backbone is a horizontal line
+ Scale marks and labels are drawn
+ below the backbone.
+ </dl>
+
+ \param pos Origin of the scale
+
+ \sa pos(), setLength()
+*/
+void QwtScaleDraw::move( const QPointF &pos )
+{
+ d_data->pos = pos;
+ updateMap();
+}
+
+/*!
+ \return Origin of the scale
+ \sa move(), length()
+*/
+QPointF QwtScaleDraw::pos() const
+{
+ return d_data->pos;
+}
+
+/*!
+ Set the length of the backbone.
+
+ The length doesn't include the space needed for
+ overlapping labels.
+
+ \sa move(), minLabelDist()
+*/
+void QwtScaleDraw::setLength( double length )
+{
+ if ( length >= 0 && length < 10 )
+ length = 10;
+ if ( length < 0 && length > -10 )
+ length = -10;
+
+ d_data->len = length;
+ updateMap();
+}
+
+/*!
+ \return the length of the backbone
+ \sa setLength(), pos()
+*/
+double QwtScaleDraw::length() const
+{
+ return d_data->len;
+}
+
+/*!
+ Draws the label for a major scale tick
+
+ \param painter Painter
+ \param value Value
+
+ \sa drawTick(), drawBackbone(), boundingLabelRect()
+*/
+void QwtScaleDraw::drawLabel( QPainter *painter, double value ) const
+{
+ QwtText lbl = tickLabel( painter->font(), value );
+ if ( lbl.isEmpty() )
+ return;
+
+ QPointF pos = labelPosition( value );
+
+ QSizeF labelSize = lbl.textSize( painter->font() );
+
+ const QTransform transform = labelTransformation( pos, labelSize );
+
+ painter->save();
+ painter->setWorldTransform( transform, true );
+
+ lbl.draw ( painter, QRect( QPoint( 0, 0 ), labelSize.toSize() ) );
+
+ painter->restore();
+}
+
+/*!
+ Find the bounding rect for the label. The coordinates of
+ the rect are absolute coordinates ( calculated from pos() ).
+ in direction of the tick.
+
+ \param font Font used for painting
+ \param value Value
+
+ \sa labelRect()
+*/
+QRect QwtScaleDraw::boundingLabelRect( const QFont &font, double value ) const
+{
+ QwtText lbl = tickLabel( font, value );
+ if ( lbl.isEmpty() )
+ return QRect();
+
+ const QPointF pos = labelPosition( value );
+ QSizeF labelSize = lbl.textSize( font );
+
+ const QTransform transform = labelTransformation( pos, labelSize );
+ return transform.mapRect( QRect( QPoint( 0, 0 ), labelSize.toSize() ) );
+}
+
+/*!
+ Calculate the transformation that is needed to paint a label
+ depending on its alignment and rotation.
+
+ \param pos Position where to paint the label
+ \param size Size of the label
+
+ \sa setLabelAlignment(), setLabelRotation()
+*/
+QTransform QwtScaleDraw::labelTransformation(
+ const QPointF &pos, const QSizeF &size ) const
+{
+ QTransform transform;
+ transform.translate( pos.x(), pos.y() );
+ transform.rotate( labelRotation() );
+
+ int flags = labelAlignment();
+ if ( flags == 0 )
+ {
+ switch ( alignment() )
+ {
+ case RightScale:
+ {
+ if ( flags == 0 )
+ flags = Qt::AlignRight | Qt::AlignVCenter;
+ break;
+ }
+ case LeftScale:
+ {
+ if ( flags == 0 )
+ flags = Qt::AlignLeft | Qt::AlignVCenter;
+ break;
+ }
+ case BottomScale:
+ {
+ if ( flags == 0 )
+ flags = Qt::AlignHCenter | Qt::AlignBottom;
+ break;
+ }
+ case TopScale:
+ {
+ if ( flags == 0 )
+ flags = Qt::AlignHCenter | Qt::AlignTop;
+ break;
+ }
+ }
+ }
+
+ double x, y;
+
+ if ( flags & Qt::AlignLeft )
+ x = -size.width();
+ else if ( flags & Qt::AlignRight )
+ x = 0.0;
+ else // Qt::AlignHCenter
+ x = -( 0.5 * size.width() );
+
+ if ( flags & Qt::AlignTop )
+ y = -size.height();
+ else if ( flags & Qt::AlignBottom )
+ y = 0;
+ else // Qt::AlignVCenter
+ y = -( 0.5 * size.height() );
+
+ transform.translate( x, y );
+
+ return transform;
+}
+
+/*!
+ Find the bounding rect for the label. The coordinates of
+ the rect are relative to spacing + ticklength from the backbone
+ in direction of the tick.
+
+ \param font Font used for painting
+ \param value Value
+*/
+QRectF QwtScaleDraw::labelRect( const QFont &font, double value ) const
+{
+ QwtText lbl = tickLabel( font, value );
+ if ( lbl.isEmpty() )
+ return QRectF( 0.0, 0.0, 0.0, 0.0 );
+
+ const QPointF pos = labelPosition( value );
+
+ const QSizeF labelSize = lbl.textSize( font );
+ const QTransform transform = labelTransformation( pos, labelSize );
+
+ QRectF br = transform.mapRect( QRectF( QPointF( 0, 0 ), labelSize ) );
+ br.translate( -pos.x(), -pos.y() );
+
+ return br;
+}
+
+/*!
+ Calculate the size that is needed to draw a label
+
+ \param font Label font
+ \param value Value
+*/
+QSizeF QwtScaleDraw::labelSize( const QFont &font, double value ) const
+{
+ return labelRect( font, value ).size();
+}
+
+/*!
+ Rotate all labels.
+
+ When changing the rotation, it might be necessary to
+ adjust the label flags too. Finding a useful combination is
+ often the result of try and error.
+
+ \param rotation Angle in degrees. When changing the label rotation,
+ the label flags often needs to be adjusted too.
+
+ \sa setLabelAlignment(), labelRotation(), labelAlignment().
+
+*/
+void QwtScaleDraw::setLabelRotation( double rotation )
+{
+ d_data->labelRotation = rotation;
+}
+
+/*!
+ \return the label rotation
+ \sa setLabelRotation(), labelAlignment()
+*/
+double QwtScaleDraw::labelRotation() const
+{
+ return d_data->labelRotation;
+}
+
+/*!
+ \brief Change the label flags
+
+ Labels are aligned to the point ticklength + spacing away from the backbone.
+
+ The alignment is relative to the orientation of the label text.
+ In case of an flags of 0 the label will be aligned
+ depending on the orientation of the scale:
+
+ QwtScaleDraw::TopScale: Qt::AlignHCenter | Qt::AlignTop\n
+ QwtScaleDraw::BottomScale: Qt::AlignHCenter | Qt::AlignBottom\n
+ QwtScaleDraw::LeftScale: Qt::AlignLeft | Qt::AlignVCenter\n
+ QwtScaleDraw::RightScale: Qt::AlignRight | Qt::AlignVCenter\n
+
+ Changing the alignment is often necessary for rotated labels.
+
+ \param alignment Or'd Qt::AlignmentFlags see <qnamespace.h>
+
+ \sa setLabelRotation(), labelRotation(), labelAlignment()
+ \warning The various alignments might be confusing.
+ The alignment of the label is not the alignment
+ of the scale and is not the alignment of the flags
+ (QwtText::flags()) returned from QwtAbstractScaleDraw::label().
+*/
+
+void QwtScaleDraw::setLabelAlignment( Qt::Alignment alignment )
+{
+ d_data->labelAlignment = alignment;
+}
+
+/*!
+ \return the label flags
+ \sa setLabelAlignment(), labelRotation()
+*/
+Qt::Alignment QwtScaleDraw::labelAlignment() const
+{
+ return d_data->labelAlignment;
+}
+
+/*!
+ \param font Font
+ \return the maximum width of a label
+*/
+int QwtScaleDraw::maxLabelWidth( const QFont &font ) const
+{
+ double maxWidth = 0.0;
+
+ const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ const double v = ticks[i];
+ if ( scaleDiv().contains( v ) )
+ {
+ const double w = labelSize( font, ticks[i] ).width();
+ if ( w > maxWidth )
+ maxWidth = w;
+ }
+ }
+
+ return qCeil( maxWidth );
+}
+
+/*!
+ \param font Font
+ \return the maximum height of a label
+*/
+int QwtScaleDraw::maxLabelHeight( const QFont &font ) const
+{
+ double maxHeight = 0.0;
+
+ const QList<double> &ticks = scaleDiv().ticks( QwtScaleDiv::MajorTick );
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ const double v = ticks[i];
+ if ( scaleDiv().contains( v ) )
+ {
+ const double h = labelSize( font, ticks[i] ).height();
+ if ( h > maxHeight )
+ maxHeight = h;
+ }
+ }
+
+ return qCeil( maxHeight );
+}
+
+void QwtScaleDraw::updateMap()
+{
+ const QPointF pos = d_data->pos;
+ double len = d_data->len;
+
+ QwtScaleMap &sm = scaleMap();
+ if ( orientation() == Qt::Vertical )
+ sm.setPaintInterval( pos.y() + len, pos.y() );
+ else
+ sm.setPaintInterval( pos.x(), pos.x() + len );
+}
diff --git a/src/libpcp_qwt/src/qwt_scale_draw.h b/src/libpcp_qwt/src/qwt_scale_draw.h
new file mode 100644
index 0000000..8eeb71b
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_draw.h
@@ -0,0 +1,117 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SCALE_DRAW_H
+#define QWT_SCALE_DRAW_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_scale_draw.h"
+#include <qpoint.h>
+#include <qrect.h>
+#include <qtransform.h>
+
+/*!
+ \brief A class for drawing scales
+
+ QwtScaleDraw can be used to draw linear or logarithmic scales.
+ A scale has a position, an alignment and a length, which can be specified .
+ The labels can be rotated and aligned
+ to the ticks using setLabelRotation() and setLabelAlignment().
+
+ After a scale division has been specified as a QwtScaleDiv object
+ using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s),
+ the scale can be drawn with the QwtAbstractScaleDraw::draw() member.
+*/
+
+class QWT_EXPORT QwtScaleDraw: public QwtAbstractScaleDraw
+{
+public:
+ /*!
+ Alignment of the scale draw
+ \sa setAlignment(), alignment()
+ */
+ enum Alignment
+ {
+ //! The scale is below
+ BottomScale,
+
+ //! The scale is above
+ TopScale,
+
+ //! The scale is left
+ LeftScale,
+
+ //! The scale is right
+ RightScale
+ };
+
+ QwtScaleDraw();
+ virtual ~QwtScaleDraw();
+
+ void getBorderDistHint( const QFont &, int &start, int &end ) const;
+ int minLabelDist( const QFont & ) const;
+
+ int minLength( const QFont & ) const;
+ virtual double extent( const QFont & ) const;
+
+ void move( double x, double y );
+ void move( const QPointF & );
+ void setLength( double length );
+
+ Alignment alignment() const;
+ void setAlignment( Alignment );
+
+ Qt::Orientation orientation() const;
+
+ QPointF pos() const;
+ double length() const;
+
+ void setLabelAlignment( Qt::Alignment );
+ Qt::Alignment labelAlignment() const;
+
+ void setLabelRotation( double rotation );
+ double labelRotation() const;
+
+ int maxLabelHeight( const QFont & ) const;
+ int maxLabelWidth( const QFont & ) const;
+
+ QPointF labelPosition( double val ) const;
+
+ QRectF labelRect( const QFont &, double val ) const;
+ QSizeF labelSize( const QFont &, double val ) const;
+
+ QRect boundingLabelRect( const QFont &, double val ) const;
+
+protected:
+ QTransform labelTransformation( const QPointF &, const QSizeF & ) const;
+
+ virtual void drawTick( QPainter *, double val, double len ) const;
+ virtual void drawBackbone( QPainter * ) const;
+ virtual void drawLabel( QPainter *, double val ) const;
+
+private:
+ QwtScaleDraw( const QwtScaleDraw & );
+ QwtScaleDraw &operator=( const QwtScaleDraw &other );
+
+ void updateMap();
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ Move the position of the scale
+ \sa move(const QPointF &)
+*/
+inline void QwtScaleDraw::move( double x, double y )
+{
+ move( QPointF( x, y ) );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_engine.cpp b/src/libpcp_qwt/src/qwt_scale_engine.cpp
new file mode 100644
index 0000000..9e5e97e
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_engine.cpp
@@ -0,0 +1,967 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_scale_engine.h"
+#include "qwt_math.h"
+#include "qwt_scale_map.h"
+#include <qalgorithms.h>
+#include <qmath.h>
+#include <float.h>
+
+#if QT_VERSION < 0x040601
+#define qFabs(x) ::fabs(x)
+#define qExp(x) ::exp(x)
+#endif
+
+static const double _eps = 1.0e-6;
+
+/*!
+ Ceil a value, relative to an interval
+
+ \param value Value to ceil
+ \param intervalSize Interval size
+
+ \sa floorEps()
+*/
+double QwtScaleArithmetic::ceilEps( double value,
+ double intervalSize )
+{
+ const double eps = _eps * intervalSize;
+
+ value = ( value - eps ) / intervalSize;
+ return ::ceil( value ) * intervalSize;
+}
+
+/*!
+ Floor a value, relative to an interval
+
+ \param value Value to floor
+ \param intervalSize Interval size
+
+ \sa floorEps()
+*/
+double QwtScaleArithmetic::floorEps( double value, double intervalSize )
+{
+ const double eps = _eps * intervalSize;
+
+ value = ( value + eps ) / intervalSize;
+ return ::floor( value ) * intervalSize;
+}
+
+/*!
+ \brief Divide an interval into steps
+
+ \f$stepSize = (intervalSize - intervalSize * 10e^{-6}) / numSteps\f$
+
+ \param intervalSize Interval size
+ \param numSteps Number of steps
+ \return Step size
+*/
+double QwtScaleArithmetic::divideEps( double intervalSize, double numSteps )
+{
+ if ( numSteps == 0.0 || intervalSize == 0.0 )
+ return 0.0;
+
+ return ( intervalSize - ( _eps * intervalSize ) ) / numSteps;
+}
+
+/*!
+ Find the smallest value out of {1,2,5}*10^n with an integer number n
+ which is greater than or equal to x
+
+ \param x Input value
+*/
+double QwtScaleArithmetic::ceil125( double x )
+{
+ if ( x == 0.0 )
+ return 0.0;
+
+ const double sign = ( x > 0 ) ? 1.0 : -1.0;
+ const double lx = ::log10( qFabs( x ) );
+ const double p10 = ::floor( lx );
+
+ double fr = qPow( 10.0, lx - p10 );
+ if ( fr <= 1.0 )
+ fr = 1.0;
+ else if ( fr <= 2.0 )
+ fr = 2.0;
+ else if ( fr <= 5.0 )
+ fr = 5.0;
+ else
+ fr = 10.0;
+
+ return sign * fr * qPow( 10.0, p10 );
+}
+
+/*!
+ \brief Find the largest value out of {1,2,5}*10^n with an integer number n
+ which is smaller than or equal to x
+
+ \param x Input value
+*/
+double QwtScaleArithmetic::floor125( double x )
+{
+ if ( x == 0.0 )
+ return 0.0;
+
+ double sign = ( x > 0 ) ? 1.0 : -1.0;
+ const double lx = ::log10( qFabs( x ) );
+ const double p10 = ::floor( lx );
+
+ double fr = qPow( 10.0, lx - p10 );
+ if ( fr >= 10.0 )
+ fr = 10.0;
+ else if ( fr >= 5.0 )
+ fr = 5.0;
+ else if ( fr >= 2.0 )
+ fr = 2.0;
+ else
+ fr = 1.0;
+
+ return sign * fr * qPow( 10.0, p10 );
+}
+
+class QwtScaleEngine::PrivateData
+{
+public:
+ PrivateData():
+ attributes( QwtScaleEngine::NoAttribute ),
+ lowerMargin( 0.0 ),
+ upperMargin( 0.0 ),
+ referenceValue( 0.0 )
+ {
+ }
+
+ QwtScaleEngine::Attributes attributes; // scale attributes
+
+ double lowerMargin; // margins
+ double upperMargin;
+
+ double referenceValue; // reference value
+
+};
+
+//! Constructor
+QwtScaleEngine::QwtScaleEngine()
+{
+ d_data = new PrivateData;
+}
+
+
+//! Destructor
+QwtScaleEngine::~QwtScaleEngine ()
+{
+ delete d_data;
+}
+
+/*!
+ \return the margin at the lower end of the scale
+ The default margin is 0.
+
+ \sa setMargins()
+*/
+double QwtScaleEngine::lowerMargin() const
+{
+ return d_data->lowerMargin;
+}
+
+/*!
+ \return the margin at the upper end of the scale
+ The default margin is 0.
+
+ \sa setMargins()
+*/
+double QwtScaleEngine::upperMargin() const
+{
+ return d_data->upperMargin;
+}
+
+/*!
+ \brief Specify margins at the scale's endpoints
+ \param lower minimum distance between the scale's lower boundary and the
+ smallest enclosed value
+ \param upper minimum distance between the scale's upper boundary and the
+ greatest enclosed value
+
+ Margins can be used to leave a minimum amount of space between
+ the enclosed intervals and the boundaries of the scale.
+
+ \warning
+ \li QwtLog10ScaleEngine measures the margins in decades.
+
+ \sa upperMargin(), lowerMargin()
+*/
+
+void QwtScaleEngine::setMargins( double lower, double upper )
+{
+ d_data->lowerMargin = qMax( lower, 0.0 );
+ d_data->upperMargin = qMax( upper, 0.0 );
+}
+
+/*!
+ Calculate a step size for an interval size
+
+ \param intervalSize Interval size
+ \param numSteps Number of steps
+
+ \return Step size
+*/
+double QwtScaleEngine::divideInterval(
+ double intervalSize, int numSteps ) const
+{
+ if ( numSteps <= 0 )
+ return 0.0;
+
+ double v = QwtScaleArithmetic::divideEps( intervalSize, numSteps );
+ return QwtScaleArithmetic::ceil125( v );
+}
+
+/*!
+ Check if an interval "contains" a value
+
+ \param interval Interval
+ \param value Value
+
+ \sa QwtScaleArithmetic::compareEps()
+*/
+bool QwtScaleEngine::contains(
+ const QwtInterval &interval, double value ) const
+{
+ if ( !interval.isValid() )
+ return false;
+
+ if ( qwtFuzzyCompare( value, interval.minValue(), interval.width() ) < 0 )
+ return false;
+
+ if ( qwtFuzzyCompare( value, interval.maxValue(), interval.width() ) > 0 )
+ return false;
+
+ return true;
+}
+
+/*!
+ Remove ticks from a list, that are not inside an interval
+
+ \param ticks Tick list
+ \param interval Interval
+
+ \return Stripped tick list
+*/
+QList<double> QwtScaleEngine::strip( const QList<double>& ticks,
+ const QwtInterval &interval ) const
+{
+ if ( !interval.isValid() || ticks.count() == 0 )
+ return QList<double>();
+
+ if ( contains( interval, ticks.first() )
+ && contains( interval, ticks.last() ) )
+ {
+ return ticks;
+ }
+
+ QList<double> strippedTicks;
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ if ( contains( interval, ticks[i] ) )
+ strippedTicks += ticks[i];
+ }
+ return strippedTicks;
+}
+
+/*!
+ \brief Build an interval for a value
+
+ In case of v == 0.0 the interval is [-0.5, 0.5],
+ otherwide it is [0.5 * v, 1.5 * v]
+*/
+
+QwtInterval QwtScaleEngine::buildInterval( double v ) const
+{
+ const double delta = ( v == 0.0 ) ? 0.5 : qAbs( 0.5 * v );
+
+ if ( DBL_MAX - delta < v )
+ return QwtInterval( DBL_MAX - delta, DBL_MAX );
+
+ if ( -DBL_MAX + delta > v )
+ return QwtInterval( -DBL_MAX, -DBL_MAX + delta );
+
+ return QwtInterval( v - delta, v + delta );
+}
+
+/*!
+ Change a scale attribute
+
+ \param attribute Attribute to change
+ \param on On/Off
+
+ \sa Attribute, testAttribute()
+*/
+void QwtScaleEngine::setAttribute( Attribute attribute, bool on )
+{
+ if ( on )
+ d_data->attributes |= attribute;
+ else
+ d_data->attributes &= ~attribute;
+}
+
+/*!
+ Check if a attribute is set.
+
+ \param attribute Attribute to be tested
+ \sa Attribute, setAttribute()
+*/
+bool QwtScaleEngine::testAttribute( Attribute attribute ) const
+{
+ return ( d_data->attributes & attribute );
+}
+
+/*!
+ Change the scale attribute
+
+ \param attributes Set scale attributes
+ \sa Attribute, attributes()
+*/
+void QwtScaleEngine::setAttributes( Attributes attributes )
+{
+ d_data->attributes = attributes;
+}
+
+/*!
+ Return the scale attributes
+ \sa Attribute, setAttributes(), testAttribute()
+*/
+QwtScaleEngine::Attributes QwtScaleEngine::attributes() const
+{
+ return d_data->attributes;
+}
+
+/*!
+ \brief Specify a reference point
+ \param r new reference value
+
+ The reference point is needed if options IncludeReference or
+ Symmetric are active. Its default value is 0.0.
+
+ \sa Attribute
+*/
+void QwtScaleEngine::setReference( double r )
+{
+ d_data->referenceValue = r;
+}
+
+/*!
+ \return the reference value
+ \sa setReference(), setAttribute()
+*/
+double QwtScaleEngine::reference() const
+{
+ return d_data->referenceValue;
+}
+
+/*!
+ Return a transformation, for linear scales
+*/
+QwtScaleTransformation *QwtLinearScaleEngine::transformation() const
+{
+ return new QwtScaleTransformation( QwtScaleTransformation::Linear );
+}
+
+/*!
+ Align and divide an interval
+
+ \param maxNumSteps Max. number of steps
+ \param x1 First limit of the interval (In/Out)
+ \param x2 Second limit of the interval (In/Out)
+ \param stepSize Step size (Out)
+
+ \sa setAttribute()
+*/
+void QwtLinearScaleEngine::autoScale( int maxNumSteps,
+ double &x1, double &x2, double &stepSize ) const
+{
+ QwtInterval interval( x1, x2 );
+ interval = interval.normalized();
+
+ interval.setMinValue( interval.minValue() - lowerMargin() );
+ interval.setMaxValue( interval.maxValue() + upperMargin() );
+
+ if ( testAttribute( QwtScaleEngine::Symmetric ) )
+ interval = interval.symmetrize( reference() );
+
+ if ( testAttribute( QwtScaleEngine::IncludeReference ) )
+ interval = interval.extend( reference() );
+
+ if ( interval.width() == 0.0 )
+ interval = buildInterval( interval.minValue() );
+
+ stepSize = divideInterval( interval.width(), qMax( maxNumSteps, 1 ) );
+
+ if ( !testAttribute( QwtScaleEngine::Floating ) )
+ interval = align( interval, stepSize );
+
+ x1 = interval.minValue();
+ x2 = interval.maxValue();
+
+ if ( testAttribute( QwtScaleEngine::Inverted ) )
+ {
+ qSwap( x1, x2 );
+ stepSize = -stepSize;
+ }
+}
+
+/*!
+ \brief Calculate a scale division
+
+ \param x1 First interval limit
+ \param x2 Second interval limit
+ \param maxMajSteps Maximum for the number of major steps
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size. If stepSize == 0, the scaleEngine
+ calculates one.
+
+ \sa QwtScaleEngine::stepSize(), QwtScaleEngine::subDivide()
+*/
+QwtScaleDiv QwtLinearScaleEngine::divideScale( double x1, double x2,
+ int maxMajSteps, int maxMinSteps, double stepSize ) const
+{
+ QwtInterval interval = QwtInterval( x1, x2 ).normalized();
+ if ( interval.width() <= 0 )
+ return QwtScaleDiv();
+
+ stepSize = qAbs( stepSize );
+ if ( stepSize == 0.0 )
+ {
+ if ( maxMajSteps < 1 )
+ maxMajSteps = 1;
+
+ stepSize = divideInterval( interval.width(), maxMajSteps );
+ }
+
+ QwtScaleDiv scaleDiv;
+
+ if ( stepSize != 0.0 )
+ {
+ QList<double> ticks[QwtScaleDiv::NTickTypes];
+ buildTicks( interval, stepSize, maxMinSteps, ticks );
+
+ scaleDiv = QwtScaleDiv( interval, ticks );
+ }
+
+ if ( x1 > x2 )
+ scaleDiv.invert();
+
+ return scaleDiv;
+}
+
+/*!
+ \brief Calculate ticks for an interval
+
+ \param interval Interval
+ \param stepSize Step size
+ \param maxMinSteps Maximum number of minor steps
+ \param ticks Arrays to be filled with the calculated ticks
+
+ \sa buildMajorTicks(), buildMinorTicks
+*/
+void QwtLinearScaleEngine::buildTicks(
+ const QwtInterval& interval, double stepSize, int maxMinSteps,
+ QList<double> ticks[QwtScaleDiv::NTickTypes] ) const
+{
+ const QwtInterval boundingInterval = align( interval, stepSize );
+
+ ticks[QwtScaleDiv::MajorTick] =
+ buildMajorTicks( boundingInterval, stepSize );
+
+ if ( maxMinSteps > 0 )
+ {
+ buildMinorTicks( ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize,
+ ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick] );
+ }
+
+ for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
+ {
+ ticks[i] = strip( ticks[i], interval );
+
+ // ticks very close to 0.0 are
+ // explicitely set to 0.0
+
+ for ( int j = 0; j < ticks[i].count(); j++ )
+ {
+ if ( qwtFuzzyCompare( ticks[i][j], 0.0, stepSize ) == 0 )
+ ticks[i][j] = 0.0;
+ }
+ }
+}
+
+/*!
+ \brief Calculate major ticks for an interval
+
+ \param interval Interval
+ \param stepSize Step size
+
+ \return Calculated ticks
+*/
+QList<double> QwtLinearScaleEngine::buildMajorTicks(
+ const QwtInterval &interval, double stepSize ) const
+{
+ int numTicks = qRound( interval.width() / stepSize ) + 1;
+ if ( numTicks > 10000 )
+ numTicks = 10000;
+
+ QList<double> ticks;
+
+ ticks += interval.minValue();
+ for ( int i = 1; i < numTicks - 1; i++ )
+ ticks += interval.minValue() + i * stepSize;
+ ticks += interval.maxValue();
+
+ return ticks;
+}
+
+/*!
+ \brief Calculate minor/medium ticks for major ticks
+
+ \param majorTicks Major ticks
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size
+ \param minorTicks Array to be filled with the calculated minor ticks
+ \param mediumTicks Array to be filled with the calculated medium ticks
+
+*/
+void QwtLinearScaleEngine::buildMinorTicks(
+ const QList<double>& majorTicks,
+ int maxMinSteps, double stepSize,
+ QList<double> &minorTicks,
+ QList<double> &mediumTicks ) const
+{
+ double minStep = divideInterval( stepSize, maxMinSteps );
+ if ( minStep == 0.0 )
+ return;
+
+ // # ticks per interval
+ int numTicks = qCeil( qAbs( stepSize / minStep ) ) - 1;
+
+ // Do the minor steps fit into the interval?
+ if ( qwtFuzzyCompare( ( numTicks + 1 ) * qAbs( minStep ),
+ qAbs( stepSize ), stepSize ) > 0 )
+ {
+ numTicks = 1;
+ minStep = stepSize * 0.5;
+ }
+
+ int medIndex = -1;
+ if ( numTicks % 2 )
+ medIndex = numTicks / 2;
+
+ // calculate minor ticks
+
+ for ( int i = 0; i < majorTicks.count(); i++ )
+ {
+ double val = majorTicks[i];
+ for ( int k = 0; k < numTicks; k++ )
+ {
+ val += minStep;
+
+ double alignedValue = val;
+ if ( qwtFuzzyCompare( val, 0.0, stepSize ) == 0 )
+ alignedValue = 0.0;
+
+ if ( k == medIndex )
+ mediumTicks += alignedValue;
+ else
+ minorTicks += alignedValue;
+ }
+ }
+}
+
+/*!
+ \brief Align an interval to a step size
+
+ The limits of an interval are aligned that both are integer
+ multiples of the step size.
+
+ \param interval Interval
+ \param stepSize Step size
+
+ \return Aligned interval
+*/
+QwtInterval QwtLinearScaleEngine::align(
+ const QwtInterval &interval, double stepSize ) const
+{
+ double x1 = interval.minValue();
+ double x2 = interval.maxValue();
+
+ if ( -DBL_MAX + stepSize <= x1 )
+ {
+ const double x = QwtScaleArithmetic::floorEps( x1, stepSize );
+ if ( qwtFuzzyCompare( x1, x, stepSize ) != 0 )
+ x1 = x;
+ }
+
+ if ( DBL_MAX - stepSize >= x2 )
+ {
+ const double x = QwtScaleArithmetic::ceilEps( x2, stepSize );
+ if ( qwtFuzzyCompare( x2, x, stepSize ) != 0 )
+ x2 = x;
+ }
+
+ return QwtInterval( x1, x2 );
+}
+
+/*!
+ Return a transformation, for logarithmic (base 10) scales
+*/
+QwtScaleTransformation *QwtLog10ScaleEngine::transformation() const
+{
+ return new QwtScaleTransformation( QwtScaleTransformation::Log10 );
+}
+
+/*!
+ Align and divide an interval
+
+ \param maxNumSteps Max. number of steps
+ \param x1 First limit of the interval (In/Out)
+ \param x2 Second limit of the interval (In/Out)
+ \param stepSize Step size (Out)
+
+ \sa QwtScaleEngine::setAttribute()
+*/
+void QwtLog10ScaleEngine::autoScale( int maxNumSteps,
+ double &x1, double &x2, double &stepSize ) const
+{
+ if ( x1 > x2 )
+ qSwap( x1, x2 );
+
+ QwtInterval interval( x1 / qPow( 10.0, lowerMargin() ),
+ x2 * qPow( 10.0, upperMargin() ) );
+
+ if ( interval.maxValue() / interval.minValue() < 10.0 )
+ {
+ // scale width is less than one decade -> build linear scale
+
+ QwtLinearScaleEngine linearScaler;
+ linearScaler.setAttributes( attributes() );
+ linearScaler.setReference( reference() );
+ linearScaler.setMargins( lowerMargin(), upperMargin() );
+
+ linearScaler.autoScale( maxNumSteps, x1, x2, stepSize );
+
+ if ( stepSize < 0.0 )
+ stepSize = -::log10( qAbs( stepSize ) );
+ else
+ stepSize = ::log10( stepSize );
+
+ return;
+ }
+
+ double logRef = 1.0;
+ if ( reference() > LOG_MIN / 2 )
+ logRef = qMin( reference(), LOG_MAX / 2 );
+
+ if ( testAttribute( QwtScaleEngine::Symmetric ) )
+ {
+ const double delta = qMax( interval.maxValue() / logRef,
+ logRef / interval.minValue() );
+ interval.setInterval( logRef / delta, logRef * delta );
+ }
+
+ if ( testAttribute( QwtScaleEngine::IncludeReference ) )
+ interval = interval.extend( logRef );
+
+ interval = interval.limited( LOG_MIN, LOG_MAX );
+
+ if ( interval.width() == 0.0 )
+ interval = buildInterval( interval.minValue() );
+
+ stepSize = divideInterval( log10( interval ).width(), qMax( maxNumSteps, 1 ) );
+ if ( stepSize < 1.0 )
+ stepSize = 1.0;
+
+ if ( !testAttribute( QwtScaleEngine::Floating ) )
+ interval = align( interval, stepSize );
+
+ x1 = interval.minValue();
+ x2 = interval.maxValue();
+
+ if ( testAttribute( QwtScaleEngine::Inverted ) )
+ {
+ qSwap( x1, x2 );
+ stepSize = -stepSize;
+ }
+}
+
+/*!
+ \brief Calculate a scale division
+
+ \param x1 First interval limit
+ \param x2 Second interval limit
+ \param maxMajSteps Maximum for the number of major steps
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size. If stepSize == 0, the scaleEngine
+ calculates one.
+
+ \sa QwtScaleEngine::stepSize(), QwtLog10ScaleEngine::subDivide()
+*/
+QwtScaleDiv QwtLog10ScaleEngine::divideScale( double x1, double x2,
+ int maxMajSteps, int maxMinSteps, double stepSize ) const
+{
+ QwtInterval interval = QwtInterval( x1, x2 ).normalized();
+ interval = interval.limited( LOG_MIN, LOG_MAX );
+
+ if ( interval.width() <= 0 )
+ return QwtScaleDiv();
+
+ if ( interval.maxValue() / interval.minValue() < 10.0 )
+ {
+ // scale width is less than one decade -> build linear scale
+
+ QwtLinearScaleEngine linearScaler;
+ linearScaler.setAttributes( attributes() );
+ linearScaler.setReference( reference() );
+ linearScaler.setMargins( lowerMargin(), upperMargin() );
+
+ if ( stepSize != 0.0 )
+ {
+ if ( stepSize < 0.0 )
+ stepSize = -qPow( 10.0, -stepSize );
+ else
+ stepSize = qPow( 10.0, stepSize );
+ }
+
+ return linearScaler.divideScale( x1, x2,
+ maxMajSteps, maxMinSteps, stepSize );
+ }
+
+ stepSize = qAbs( stepSize );
+ if ( stepSize == 0.0 )
+ {
+ if ( maxMajSteps < 1 )
+ maxMajSteps = 1;
+
+ stepSize = divideInterval( log10( interval ).width(), maxMajSteps );
+ if ( stepSize < 1.0 )
+ stepSize = 1.0; // major step must be >= 1 decade
+ }
+
+ QwtScaleDiv scaleDiv;
+ if ( stepSize != 0.0 )
+ {
+ QList<double> ticks[QwtScaleDiv::NTickTypes];
+ buildTicks( interval, stepSize, maxMinSteps, ticks );
+
+ scaleDiv = QwtScaleDiv( interval, ticks );
+ }
+
+ if ( x1 > x2 )
+ scaleDiv.invert();
+
+ return scaleDiv;
+}
+
+/*!
+ \brief Calculate ticks for an interval
+
+ \param interval Interval
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size
+ \param ticks Arrays to be filled with the calculated ticks
+
+ \sa buildMajorTicks(), buildMinorTicks
+*/
+void QwtLog10ScaleEngine::buildTicks(
+ const QwtInterval& interval, double stepSize, int maxMinSteps,
+ QList<double> ticks[QwtScaleDiv::NTickTypes] ) const
+{
+ const QwtInterval boundingInterval = align( interval, stepSize );
+
+ ticks[QwtScaleDiv::MajorTick] =
+ buildMajorTicks( boundingInterval, stepSize );
+
+ if ( maxMinSteps > 0 )
+ {
+ ticks[QwtScaleDiv::MinorTick] = buildMinorTicks(
+ ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize );
+ }
+
+ for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ )
+ ticks[i] = strip( ticks[i], interval );
+}
+
+/*!
+ \brief Calculate major ticks for an interval
+
+ \param interval Interval
+ \param stepSize Step size
+
+ \return Calculated ticks
+*/
+QList<double> QwtLog10ScaleEngine::buildMajorTicks(
+ const QwtInterval &interval, double stepSize ) const
+{
+ double width = log10( interval ).width();
+
+ int numTicks = qRound( width / stepSize ) + 1;
+ if ( numTicks > 10000 )
+ numTicks = 10000;
+
+ const double lxmin = ::log( interval.minValue() );
+ const double lxmax = ::log( interval.maxValue() );
+ const double lstep = ( lxmax - lxmin ) / double( numTicks - 1 );
+
+ QList<double> ticks;
+
+ ticks += interval.minValue();
+
+ for ( int i = 1; i < numTicks - 1; i++ )
+ ticks += qExp( lxmin + double( i ) * lstep );
+
+ ticks += interval.maxValue();
+
+ return ticks;
+}
+
+/*!
+ \brief Calculate minor/medium ticks for major ticks
+
+ \param majorTicks Major ticks
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size
+*/
+QList<double> QwtLog10ScaleEngine::buildMinorTicks(
+ const QList<double> &majorTicks,
+ int maxMinSteps, double stepSize ) const
+{
+ if ( stepSize < 1.1 ) // major step width is one decade
+ {
+ if ( maxMinSteps < 1 )
+ return QList<double>();
+
+ int k0, kstep, kmax;
+
+ if ( maxMinSteps >= 8 )
+ {
+ k0 = 2;
+ kmax = 9;
+ kstep = 1;
+ }
+ else if ( maxMinSteps >= 4 )
+ {
+ k0 = 2;
+ kmax = 8;
+ kstep = 2;
+ }
+ else if ( maxMinSteps >= 2 )
+ {
+ k0 = 2;
+ kmax = 5;
+ kstep = 3;
+ }
+ else
+ {
+ k0 = 5;
+ kmax = 5;
+ kstep = 1;
+ }
+
+ QList<double> minorTicks;
+
+ for ( int i = 0; i < majorTicks.count(); i++ )
+ {
+ const double v = majorTicks[i];
+ for ( int k = k0; k <= kmax; k += kstep )
+ minorTicks += v * double( k );
+ }
+
+ return minorTicks;
+ }
+ else // major step > one decade
+ {
+ double minStep = divideInterval( stepSize, maxMinSteps );
+ if ( minStep == 0.0 )
+ return QList<double>();
+
+ if ( minStep < 1.0 )
+ minStep = 1.0;
+
+ // # subticks per interval
+ int nMin = qRound( stepSize / minStep ) - 1;
+
+ // Do the minor steps fit into the interval?
+
+ if ( qwtFuzzyCompare( ( nMin + 1 ) * minStep,
+ qAbs( stepSize ), stepSize ) > 0 )
+ {
+ nMin = 0;
+ }
+
+ if ( nMin < 1 )
+ return QList<double>(); // no subticks
+
+ // substep factor = 10^substeps
+ const qreal minFactor = qMax( qPow( 10.0, minStep ), qreal( 10.0 ) );
+
+ QList<double> minorTicks;
+ for ( int i = 0; i < majorTicks.count(); i++ )
+ {
+ double val = majorTicks[i];
+ for ( int k = 0; k < nMin; k++ )
+ {
+ val *= minFactor;
+ minorTicks += val;
+ }
+ }
+ return minorTicks;
+ }
+}
+
+/*!
+ \brief Align an interval to a step size
+
+ The limits of an interval are aligned that both are integer
+ multiples of the step size.
+
+ \param interval Interval
+ \param stepSize Step size
+
+ \return Aligned interval
+*/
+QwtInterval QwtLog10ScaleEngine::align(
+ const QwtInterval &interval, double stepSize ) const
+{
+ const QwtInterval intv = log10( interval );
+
+ double x1 = QwtScaleArithmetic::floorEps( intv.minValue(), stepSize );
+ if ( qwtFuzzyCompare( interval.minValue(), x1, stepSize ) == 0 )
+ x1 = interval.minValue();
+
+ double x2 = QwtScaleArithmetic::ceilEps( intv.maxValue(), stepSize );
+ if ( qwtFuzzyCompare( interval.maxValue(), x2, stepSize ) == 0 )
+ x2 = interval.maxValue();
+
+ return pow10( QwtInterval( x1, x2 ) );
+}
+
+/*!
+ Return the interval [log10(interval.minValue(), log10(interval.maxValue]
+*/
+
+QwtInterval QwtLog10ScaleEngine::log10( const QwtInterval &interval ) const
+{
+ return QwtInterval( ::log10( interval.minValue() ),
+ ::log10( interval.maxValue() ) );
+}
+
+/*!
+ Return the interval [pow10(interval.minValue(), pow10(interval.maxValue]
+*/
+QwtInterval QwtLog10ScaleEngine::pow10( const QwtInterval &interval ) const
+{
+ return QwtInterval( qPow( 10.0, interval.minValue() ),
+ qPow( 10.0, interval.maxValue() ) );
+}
diff --git a/src/libpcp_qwt/src/qwt_scale_engine.h b/src/libpcp_qwt/src/qwt_scale_engine.h
new file mode 100644
index 0000000..a7b2c74
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_engine.h
@@ -0,0 +1,217 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SCALE_ENGINE_H
+#define QWT_SCALE_ENGINE_H
+
+#include "qwt_global.h"
+#include "qwt_scale_div.h"
+#include "qwt_interval.h"
+
+class QwtScaleTransformation;
+
+/*!
+ \brief Arithmetic including a tolerance
+*/
+class QWT_EXPORT QwtScaleArithmetic
+{
+public:
+ static double ceilEps( double value, double intervalSize );
+ static double floorEps( double value, double intervalSize );
+
+ static double divideEps( double interval, double steps );
+
+ static double ceil125( double x );
+ static double floor125( double x );
+};
+
+/*!
+ \brief Base class for scale engines.
+
+ A scale engine tries to find "reasonable" ranges and step sizes
+ for scales.
+
+ The layout of the scale can be varied with setAttribute().
+
+ Qwt offers implementations for logarithmic (log10)
+ and linear scales. Contributions for other types of scale engines
+ (date/time, log2 ... ) are welcome.
+*/
+
+class QWT_EXPORT QwtScaleEngine
+{
+public:
+ /*!
+ Layout attributes
+ \sa setAttribute(), testAttribute(), reference(),
+ lowerMargin(), upperMargin()
+ */
+
+ enum Attribute
+ {
+ //! No attributes
+ NoAttribute = 0x00,
+
+ //! Build a scale which includes the reference() value.
+ IncludeReference = 0x01,
+
+ //! Build a scale which is symmetric to the reference() value.
+ Symmetric = 0x02,
+
+ /*!
+ The endpoints of the scale are supposed to be equal the
+ outmost included values plus the specified margins
+ (see setMargins()).
+ If this attribute is *not* set, the endpoints of the scale will
+ be integer multiples of the step size.
+ */
+ Floating = 0x04,
+
+ //! Turn the scale upside down.
+ Inverted = 0x08
+ };
+
+ //! Layout attributes
+ typedef QFlags<Attribute> Attributes;
+
+ explicit QwtScaleEngine();
+ virtual ~QwtScaleEngine();
+
+ void setAttribute( Attribute, bool on = true );
+ bool testAttribute( Attribute ) const;
+
+ void setAttributes( Attributes );
+ Attributes attributes() const;
+
+ void setReference( double reference );
+ double reference() const;
+
+ void setMargins( double lower, double upper );
+ double lowerMargin() const;
+ double upperMargin() const;
+
+ /*!
+ Align and divide an interval
+
+ \param maxNumSteps Max. number of steps
+ \param x1 First limit of the interval (In/Out)
+ \param x2 Second limit of the interval (In/Out)
+ \param stepSize Step size (Return value)
+ */
+ virtual void autoScale( int maxNumSteps,
+ double &x1, double &x2, double &stepSize ) const = 0;
+
+ /*!
+ \brief Calculate a scale division
+
+ \param x1 First interval limit
+ \param x2 Second interval limit
+ \param maxMajSteps Maximum for the number of major steps
+ \param maxMinSteps Maximum number of minor steps
+ \param stepSize Step size. If stepSize == 0.0, the scaleEngine
+ calculates one.
+ */
+ virtual QwtScaleDiv divideScale( double x1, double x2,
+ int maxMajSteps, int maxMinSteps,
+ double stepSize = 0.0 ) const = 0;
+
+ //! \return a transformation
+ virtual QwtScaleTransformation *transformation() const = 0;
+
+protected:
+ bool contains( const QwtInterval &, double val ) const;
+ QList<double> strip( const QList<double>&, const QwtInterval & ) const;
+ double divideInterval( double interval, int numSteps ) const;
+
+ QwtInterval buildInterval( double v ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ \brief A scale engine for linear scales
+
+ The step size will fit into the pattern
+ \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer.
+*/
+
+class QWT_EXPORT QwtLinearScaleEngine: public QwtScaleEngine
+{
+public:
+ virtual void autoScale( int maxSteps,
+ double &x1, double &x2, double &stepSize ) const;
+
+ virtual QwtScaleDiv divideScale( double x1, double x2,
+ int numMajorSteps, int numMinorSteps,
+ double stepSize = 0.0 ) const;
+
+ virtual QwtScaleTransformation *transformation() const;
+
+protected:
+ QwtInterval align( const QwtInterval&, double stepSize ) const;
+
+ void buildTicks(
+ const QwtInterval &, double stepSize, int maxMinSteps,
+ QList<double> ticks[QwtScaleDiv::NTickTypes] ) const;
+
+ QList<double> buildMajorTicks(
+ const QwtInterval &interval, double stepSize ) const;
+
+ void buildMinorTicks(
+ const QList<double>& majorTicks,
+ int maxMinMark, double step,
+ QList<double> &, QList<double> & ) const;
+};
+
+/*!
+ \brief A scale engine for logarithmic (base 10) scales
+
+ The step size is measured in *decades*
+ and the major step size will be adjusted to fit the pattern
+ \f$\left\{ 1,2,3,5\right\} \cdot 10^{n}\f$, where n is a natural number
+ including zero.
+
+ \warning the step size as well as the margins are measured in *decades*.
+*/
+
+class QWT_EXPORT QwtLog10ScaleEngine: public QwtScaleEngine
+{
+public:
+ virtual void autoScale( int maxSteps,
+ double &x1, double &x2, double &stepSize ) const;
+
+ virtual QwtScaleDiv divideScale( double x1, double x2,
+ int numMajorSteps, int numMinorSteps,
+ double stepSize = 0.0 ) const;
+
+ virtual QwtScaleTransformation *transformation() const;
+
+protected:
+ QwtInterval log10( const QwtInterval& ) const;
+ QwtInterval pow10( const QwtInterval& ) const;
+
+ QwtInterval align( const QwtInterval&, double stepSize ) const;
+
+ void buildTicks(
+ const QwtInterval &, double stepSize, int maxMinSteps,
+ QList<double> ticks[QwtScaleDiv::NTickTypes] ) const;
+
+ QList<double> buildMajorTicks(
+ const QwtInterval &interval, double stepSize ) const;
+
+ QList<double> buildMinorTicks(
+ const QList<double>& majorTicks,
+ int maxMinMark, double step ) const;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtScaleEngine::Attributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_map.cpp b/src/libpcp_qwt/src/qwt_scale_map.cpp
new file mode 100644
index 0000000..1e16be1
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_map.cpp
@@ -0,0 +1,344 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_scale_map.h"
+#include <qrect.h>
+#include <qalgorithms.h>
+#include <qmath.h>
+#include <qdebug.h>
+
+#if QT_VERSION < 0x040601
+#define qExp(x) ::exp(x)
+#endif
+
+//! Smallest allowed value for logarithmic scales: 1.0e-150
+QT_STATIC_CONST_IMPL double QwtScaleMap::LogMin = 1.0e-150;
+
+//! Largest allowed value for logarithmic scales: 1.0e150
+QT_STATIC_CONST_IMPL double QwtScaleMap::LogMax = 1.0e150;
+
+//! Constructor for a linear transformation
+QwtScaleTransformation::QwtScaleTransformation( Type type ):
+ d_type( type )
+{
+}
+
+//! Destructor
+QwtScaleTransformation::~QwtScaleTransformation()
+{
+}
+
+//! Create a clone of the transformation
+QwtScaleTransformation *QwtScaleTransformation::copy() const
+{
+ return new QwtScaleTransformation( d_type );
+}
+
+/*!
+ \brief Transform a value from the coordinate system of a scale
+ into the coordinate system of the paint device
+
+ \param s Value related to the coordinate system of the scale
+ \param s1 First border of the coordinate system of the scale
+ \param s2 Second border of the coordinate system of the scale
+ \param p1 First border of the coordinate system of the paint device
+ \param p2 Second border of the coordinate system of the paint device
+ \return
+ <dl>
+ <dt>linear mapping:<dd>p1 + (p2 - p1) / (s2 - s1) * (s - s1);</dd>
+ </dl>
+ <dl>
+ <dt>log10 mapping: <dd>p1 + (p2 - p1) / log(s2 / s1) * log(s / s1);</dd>
+ </dl>
+*/
+
+double QwtScaleTransformation::xForm(
+ double s, double s1, double s2, double p1, double p2 ) const
+{
+ if ( d_type == Log10 )
+ return p1 + ( p2 - p1 ) / log( s2 / s1 ) * log( s / s1 );
+ else
+ return p1 + ( p2 - p1 ) / ( s2 - s1 ) * ( s - s1 );
+}
+
+/*!
+ \brief Transform a value from the coordinate system of the paint device
+ into the coordinate system of a scale.
+
+ \param p Value related to the coordinate system of the paint device
+ \param p1 First border of the coordinate system of the paint device
+ \param p2 Second border of the coordinate system of the paint device
+ \param s1 First border of the coordinate system of the scale
+ \param s2 Second border of the coordinate system of the scale
+ \return
+ <dl>
+ <dt>linear mapping:<dd>s1 + ( s2 - s1 ) / ( p2 - p1 ) * ( p - p1 );</dd>
+ </dl>
+ <dl>
+ <dt>log10 mapping:<dd>exp((p - p1) / (p2 - p1) * log(s2 / s1)) * s1;</dd>
+ </dl>
+*/
+
+double QwtScaleTransformation::invXForm( double p, double p1, double p2,
+ double s1, double s2 ) const
+{
+ if ( d_type == Log10 )
+ return qExp( ( p - p1 ) / ( p2 - p1 ) * log( s2 / s1 ) ) * s1;
+ else
+ return s1 + ( s2 - s1 ) / ( p2 - p1 ) * ( p - p1 );
+}
+
+/*!
+ \brief Constructor
+
+ The scale and paint device intervals are both set to [0,1].
+*/
+QwtScaleMap::QwtScaleMap():
+ d_s1( 0.0 ),
+ d_s2( 1.0 ),
+ d_p1( 0.0 ),
+ d_p2( 1.0 ),
+ d_cnv( 1.0 )
+{
+ d_transformation = new QwtScaleTransformation(
+ QwtScaleTransformation::Linear );
+}
+
+//! Copy constructor
+QwtScaleMap::QwtScaleMap( const QwtScaleMap& other ):
+ d_s1( other.d_s1 ),
+ d_s2( other.d_s2 ),
+ d_p1( other.d_p1 ),
+ d_p2( other.d_p2 ),
+ d_cnv( other.d_cnv )
+{
+ d_transformation = other.d_transformation->copy();
+}
+
+/*!
+ Destructor
+*/
+QwtScaleMap::~QwtScaleMap()
+{
+ delete d_transformation;
+}
+
+//! Assignment operator
+QwtScaleMap &QwtScaleMap::operator=( const QwtScaleMap & other )
+{
+ d_s1 = other.d_s1;
+ d_s2 = other.d_s2;
+ d_p1 = other.d_p1;
+ d_p2 = other.d_p2;
+ d_cnv = other.d_cnv;
+
+ delete d_transformation;
+ d_transformation = other.d_transformation->copy();
+
+ return *this;
+}
+
+/*!
+ Initialize the map with a transformation
+*/
+void QwtScaleMap::setTransformation(
+ QwtScaleTransformation *transformation )
+{
+ if ( transformation == NULL )
+ return;
+
+ if ( transformation != d_transformation )
+ {
+ delete d_transformation;
+ d_transformation = transformation;
+ }
+
+ setScaleInterval( d_s1, d_s2 );
+}
+
+//! Get the transformation
+const QwtScaleTransformation *QwtScaleMap::transformation() const
+{
+ return d_transformation;
+}
+
+/*!
+ \brief Specify the borders of the scale interval
+ \param s1 first border
+ \param s2 second border
+ \warning logarithmic scales might be aligned to [LogMin, LogMax]
+*/
+void QwtScaleMap::setScaleInterval( double s1, double s2 )
+{
+ if ( d_transformation->type() == QwtScaleTransformation::Log10 )
+ {
+ if ( s1 < LogMin )
+ s1 = LogMin;
+ else if ( s1 > LogMax )
+ s1 = LogMax;
+
+ if ( s2 < LogMin )
+ s2 = LogMin;
+ else if ( s2 > LogMax )
+ s2 = LogMax;
+ }
+
+ d_s1 = s1;
+ d_s2 = s2;
+
+ if ( d_transformation->type() != QwtScaleTransformation::Other )
+ newFactor();
+}
+
+/*!
+ \brief Specify the borders of the paint device interval
+ \param p1 first border
+ \param p2 second border
+*/
+void QwtScaleMap::setPaintInterval( double p1, double p2 )
+{
+ d_p1 = p1;
+ d_p2 = p2;
+
+ if ( d_transformation->type() != QwtScaleTransformation::Other )
+ newFactor();
+}
+
+/*!
+ \brief Re-calculate the conversion factor.
+*/
+void QwtScaleMap::newFactor()
+{
+ d_cnv = 0.0;
+
+ switch ( d_transformation->type() )
+ {
+ case QwtScaleTransformation::Linear:
+ {
+ if ( d_s2 != d_s1 )
+ d_cnv = ( d_p2 - d_p1 ) / ( d_s2 - d_s1 );
+ break;
+ }
+ case QwtScaleTransformation::Log10:
+ {
+ if ( d_s1 != 0 )
+ d_cnv = ( d_p2 - d_p1 ) / log( d_s2 / d_s1 );
+ break;
+ }
+ default:;
+ }
+}
+
+/*!
+ Transform a rectangle from scale to paint coordinates
+
+ \param xMap X map
+ \param yMap Y map
+ \param rect Rectangle in scale coordinates
+ \return Rectangle in paint coordinates
+
+ \sa invTransform()
+*/
+QRectF QwtScaleMap::transform( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap, const QRectF &rect )
+{
+ double x1 = xMap.transform( rect.left() );
+ double x2 = xMap.transform( rect.right() );
+ double y1 = yMap.transform( rect.top() );
+ double y2 = yMap.transform( rect.bottom() );
+
+ if ( x2 < x1 )
+ qSwap( x1, x2 );
+ if ( y2 < y1 )
+ qSwap( y1, y2 );
+
+ if ( qwtFuzzyCompare( x1, 0.0, x2 - x1 ) == 0 )
+ x1 = 0.0;
+ if ( qwtFuzzyCompare( x2, 0.0, x2 - x1 ) == 0 )
+ x2 = 0.0;
+ if ( qwtFuzzyCompare( y1, 0.0, y2 - y1 ) == 0 )
+ y1 = 0.0;
+ if ( qwtFuzzyCompare( y2, 0.0, y2 - y1 ) == 0 )
+ y2 = 0.0;
+
+ return QRectF( x1, y1, x2 - x1 + 1, y2 - y1 + 1 );
+}
+
+/*!
+ Transform a rectangle from paint to scale coordinates
+
+ \param xMap X map
+ \param yMap Y map
+ \param pos Position in paint coordinates
+ \return Position in scale coordinates
+ \sa transform()
+*/
+QPointF QwtScaleMap::invTransform( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap, const QPointF &pos )
+{
+ return QPointF(
+ xMap.invTransform( pos.x() ),
+ yMap.invTransform( pos.y() )
+ );
+}
+
+/*!
+ Transform a point from scale to paint coordinates
+
+ \param xMap X map
+ \param yMap Y map
+ \param pos Position in scale coordinates
+ \return Position in paint coordinates
+
+ \sa invTransform()
+*/
+QPointF QwtScaleMap::transform( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap, const QPointF &pos )
+{
+ return QPointF(
+ xMap.transform( pos.x() ),
+ yMap.transform( pos.y() )
+ );
+}
+
+/*!
+ Transform a rectangle from paint to scale coordinates
+
+ \param xMap X map
+ \param yMap Y map
+ \param rect Rectangle in paint coordinates
+ \return Rectangle in scale coordinates
+ \sa transform()
+*/
+QRectF QwtScaleMap::invTransform( const QwtScaleMap &xMap,
+ const QwtScaleMap &yMap, const QRectF &rect )
+{
+ const double x1 = xMap.invTransform( rect.left() );
+ const double x2 = xMap.invTransform( rect.right() - 1 );
+ const double y1 = yMap.invTransform( rect.top() );
+ const double y2 = yMap.invTransform( rect.bottom() - 1 );
+
+ const QRectF r( x1, y1, x2 - x1, y2 - y1 );
+ return r.normalized();
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+
+QDebug operator<<( QDebug debug, const QwtScaleMap &map )
+{
+ debug.nospace() << "QwtScaleMap("
+ << static_cast<int>( map.transformation()->type() )
+ << ", s:" << map.s1() << "->" << map.s2()
+ << ", p:" << map.p1() << "->" << map.p2()
+ << ")";
+
+ return debug.space();
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_map.h b/src/libpcp_qwt/src/qwt_scale_map.h
new file mode 100644
index 0000000..31dbb67
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_map.h
@@ -0,0 +1,219 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SCALE_MAP_H
+#define QWT_SCALE_MAP_H
+
+#include "qwt_global.h"
+#include "qwt_math.h"
+#ifndef QT_NO_DEBUG_STREAM
+#include <qdebug.h>
+#endif
+
+class QRectF;
+
+/*!
+ \brief A transformation between coordinate systems
+
+ QwtScaleTransformation offers transformations from the coordinate system
+ of a scale into the linear coordinate system of a paint device
+ and vice versa.
+*/
+class QWT_EXPORT QwtScaleTransformation
+{
+public:
+ //! Transformation type
+ enum Type
+ {
+ //! Transformation between 2 linear scales
+ Linear,
+
+ //! Transformation between a linear and a logarithmic ( base 10 ) scale
+ Log10,
+
+ //! Any other type of transformation
+ Other
+ };
+
+ QwtScaleTransformation( Type type );
+ virtual ~QwtScaleTransformation();
+
+ virtual double xForm( double s, double s1, double s2,
+ double p1, double p2 ) const;
+ virtual double invXForm( double p, double p1, double p2,
+ double s1, double s2 ) const;
+
+ Type type() const;
+
+ virtual QwtScaleTransformation *copy() const;
+
+private:
+ QwtScaleTransformation();
+ QwtScaleTransformation &operator=( const QwtScaleTransformation );
+
+ const Type d_type;
+};
+
+//! \return Transformation type
+inline QwtScaleTransformation::Type QwtScaleTransformation::type() const
+{
+ return d_type;
+}
+
+/*!
+ \brief A scale map
+
+ QwtScaleMap offers transformations from the coordinate system
+ of a scale into the linear coordinate system of a paint device
+ and vice versa.
+*/
+class QWT_EXPORT QwtScaleMap
+{
+public:
+ QwtScaleMap();
+ QwtScaleMap( const QwtScaleMap& );
+
+ ~QwtScaleMap();
+
+ QwtScaleMap &operator=( const QwtScaleMap & );
+
+ void setTransformation( QwtScaleTransformation * );
+ const QwtScaleTransformation *transformation() const;
+
+ void setPaintInterval( double p1, double p2 );
+ void setScaleInterval( double s1, double s2 );
+
+ double transform( double s ) const;
+ double invTransform( double p ) const;
+
+ double p1() const;
+ double p2() const;
+
+ double s1() const;
+ double s2() const;
+
+ double pDist() const;
+ double sDist() const;
+
+ QT_STATIC_CONST double LogMin;
+ QT_STATIC_CONST double LogMax;
+
+ static QRectF transform( const QwtScaleMap &,
+ const QwtScaleMap &, const QRectF & );
+ static QRectF invTransform( const QwtScaleMap &,
+ const QwtScaleMap &, const QRectF & );
+
+ static QPointF transform( const QwtScaleMap &,
+ const QwtScaleMap &, const QPointF & );
+ static QPointF invTransform( const QwtScaleMap &,
+ const QwtScaleMap &, const QPointF & );
+
+ bool isInverting() const;
+
+private:
+ void newFactor();
+
+ double d_s1, d_s2; // scale interval boundaries
+ double d_p1, d_p2; // paint device interval boundaries
+
+ double d_cnv; // conversion factor
+
+ QwtScaleTransformation *d_transformation;
+};
+
+/*!
+ \return First border of the scale interval
+*/
+inline double QwtScaleMap::s1() const
+{
+ return d_s1;
+}
+
+/*!
+ \return Second border of the scale interval
+*/
+inline double QwtScaleMap::s2() const
+{
+ return d_s2;
+}
+
+/*!
+ \return First border of the paint interval
+*/
+inline double QwtScaleMap::p1() const
+{
+ return d_p1;
+}
+
+/*!
+ \return Second border of the paint interval
+*/
+inline double QwtScaleMap::p2() const
+{
+ return d_p2;
+}
+
+/*!
+ \return qwtAbs(p2() - p1())
+*/
+inline double QwtScaleMap::pDist() const
+{
+ return qAbs( d_p2 - d_p1 );
+}
+
+/*!
+ \return qwtAbs(s2() - s1())
+*/
+inline double QwtScaleMap::sDist() const
+{
+ return qAbs( d_s2 - d_s1 );
+}
+
+/*!
+ Transform a point related to the scale interval into an point
+ related to the interval of the paint device
+
+ \param s Value relative to the coordinates of the scale
+*/
+inline double QwtScaleMap::transform( double s ) const
+{
+ // try to inline code from QwtScaleTransformation
+
+ if ( d_transformation->type() == QwtScaleTransformation::Linear )
+ return d_p1 + ( s - d_s1 ) * d_cnv;
+
+ if ( d_transformation->type() == QwtScaleTransformation::Log10 )
+ return d_p1 + log( s / d_s1 ) * d_cnv;
+
+ return d_transformation->xForm( s, d_s1, d_s2, d_p1, d_p2 );
+}
+
+/*!
+ Transform an paint device value into a value in the
+ interval of the scale.
+
+ \param p Value relative to the coordinates of the paint device
+ \sa transform()
+*/
+inline double QwtScaleMap::invTransform( double p ) const
+{
+ return d_transformation->invXForm( p, d_p1, d_p2, d_s1, d_s2 );
+}
+
+//! \return True, when ( p1() < p2() ) != ( s1() < s2() )
+inline bool QwtScaleMap::isInverting() const
+{
+ return ( ( d_p1 < d_p2 ) != ( d_s1 < d_s2 ) );
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QWT_EXPORT QDebug operator<<( QDebug, const QwtScaleMap & );
+#endif
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_scale_widget.cpp b/src/libpcp_qwt/src/qwt_scale_widget.cpp
new file mode 100644
index 0000000..d9ffacc
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_widget.cpp
@@ -0,0 +1,918 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_scale_widget.h"
+#include "qwt_painter.h"
+#include "qwt_color_map.h"
+#include "qwt_scale_map.h"
+#include "qwt_math.h"
+#include "qwt_scale_div.h"
+#include "qwt_text.h"
+#include <qpainter.h>
+#include <qevent.h>
+#include <qmath.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+
+class QwtScaleWidget::PrivateData
+{
+public:
+ PrivateData():
+ scaleDraw( NULL )
+ {
+ colorBar.colorMap = NULL;
+ }
+
+ ~PrivateData()
+ {
+ delete scaleDraw;
+ delete colorBar.colorMap;
+ }
+
+ QwtScaleDraw *scaleDraw;
+
+ int borderDist[2];
+ int minBorderDist[2];
+ int scaleLength;
+ int margin;
+
+ int titleOffset;
+ int spacing;
+ QwtText title;
+
+ QwtScaleWidget::LayoutFlags layoutFlags;
+
+ struct t_colorBar
+ {
+ bool isEnabled;
+ int width;
+ QwtInterval interval;
+ QwtColorMap *colorMap;
+ } colorBar;
+};
+
+/*!
+ \brief Create a scale with the position QwtScaleWidget::Left
+ \param parent Parent widget
+*/
+QwtScaleWidget::QwtScaleWidget( QWidget *parent ):
+ QWidget( parent )
+{
+ initScale( QwtScaleDraw::LeftScale );
+}
+
+/*!
+ \brief Constructor
+ \param align Alignment.
+ \param parent Parent widget
+*/
+QwtScaleWidget::QwtScaleWidget(
+ QwtScaleDraw::Alignment align, QWidget *parent ):
+ QWidget( parent )
+{
+ initScale( align );
+}
+
+//! Destructor
+QwtScaleWidget::~QwtScaleWidget()
+{
+ delete d_data;
+}
+
+//! Initialize the scale
+void QwtScaleWidget::initScale( QwtScaleDraw::Alignment align )
+{
+ d_data = new PrivateData;
+
+ d_data->layoutFlags = 0;
+ if ( align == QwtScaleDraw::RightScale )
+ d_data->layoutFlags |= TitleInverted;
+
+ d_data->borderDist[0] = 0;
+ d_data->borderDist[1] = 0;
+ d_data->minBorderDist[0] = 0;
+ d_data->minBorderDist[1] = 0;
+ d_data->margin = 4;
+ d_data->titleOffset = 0;
+ d_data->spacing = 2;
+
+ d_data->scaleDraw = new QwtScaleDraw;
+ d_data->scaleDraw->setAlignment( align );
+ d_data->scaleDraw->setLength( 10 );
+
+ d_data->colorBar.colorMap = new QwtLinearColorMap();
+ d_data->colorBar.isEnabled = false;
+ d_data->colorBar.width = 10;
+
+ const int flags = Qt::AlignHCenter
+ | Qt::TextExpandTabs | Qt::TextWordWrap;
+ d_data->title.setRenderFlags( flags );
+ d_data->title.setFont( font() );
+
+ QSizePolicy policy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed );
+ if ( d_data->scaleDraw->orientation() == Qt::Vertical )
+ policy.transpose();
+
+ setSizePolicy( policy );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+}
+
+/*!
+ Toggle an layout flag
+
+ \param flag Layout flag
+ \param on true/false
+
+ \sa testLayoutFlag(), LayoutFlag
+*/
+void QwtScaleWidget::setLayoutFlag( LayoutFlag flag, bool on )
+{
+ if ( ( ( d_data->layoutFlags & flag ) != 0 ) != on )
+ {
+ if ( on )
+ d_data->layoutFlags |= flag;
+ else
+ d_data->layoutFlags &= ~flag;
+ }
+}
+
+/*!
+ Test a layout flag
+
+ \param flag Layout flag
+ \return true/false
+ \sa setLayoutFlag(), LayoutFlag
+*/
+bool QwtScaleWidget::testLayoutFlag( LayoutFlag flag ) const
+{
+ return ( d_data->layoutFlags & flag );
+}
+
+/*!
+ Give title new text contents
+
+ \param title New title
+ \sa title(), setTitle(const QwtText &);
+*/
+void QwtScaleWidget::setTitle( const QString &title )
+{
+ if ( d_data->title.text() != title )
+ {
+ d_data->title.setText( title );
+ layoutScale();
+ }
+}
+
+/*!
+ Give title new text contents
+
+ \param title New title
+ \sa title()
+ \warning The title flags are interpreted in
+ direction of the label, AlignTop, AlignBottom can't be set
+ as the title will always be aligned to the scale.
+*/
+void QwtScaleWidget::setTitle( const QwtText &title )
+{
+ QwtText t = title;
+ const int flags = title.renderFlags() & ~( Qt::AlignTop | Qt::AlignBottom );
+ t.setRenderFlags( flags );
+
+ if ( t != d_data->title )
+ {
+ d_data->title = t;
+ layoutScale();
+ }
+}
+
+/*!
+ Change the alignment
+
+ \param alignment New alignment
+ \sa alignment()
+*/
+void QwtScaleWidget::setAlignment( QwtScaleDraw::Alignment alignment )
+{
+ if ( d_data->scaleDraw )
+ d_data->scaleDraw->setAlignment( alignment );
+
+ if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
+ {
+ QSizePolicy policy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed );
+ if ( d_data->scaleDraw->orientation() == Qt::Vertical )
+ policy.transpose();
+
+ setSizePolicy( policy );
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+ }
+
+ layoutScale();
+}
+
+
+/*!
+ \return position
+ \sa setPosition()
+*/
+QwtScaleDraw::Alignment QwtScaleWidget::alignment() const
+{
+ if ( !scaleDraw() )
+ return QwtScaleDraw::LeftScale;
+
+ return scaleDraw()->alignment();
+}
+
+/*!
+ Specify distances of the scale's endpoints from the
+ widget's borders. The actual borders will never be less
+ than minimum border distance.
+ \param dist1 Left or top Distance
+ \param dist2 Right or bottom distance
+ \sa borderDist()
+*/
+void QwtScaleWidget::setBorderDist( int dist1, int dist2 )
+{
+ if ( dist1 != d_data->borderDist[0] || dist2 != d_data->borderDist[1] )
+ {
+ d_data->borderDist[0] = dist1;
+ d_data->borderDist[1] = dist2;
+ layoutScale();
+ }
+}
+
+/*!
+ \brief Specify the margin to the colorBar/base line.
+ \param margin Margin
+ \sa margin()
+*/
+void QwtScaleWidget::setMargin( int margin )
+{
+ margin = qMax( 0, margin );
+ if ( margin != d_data->margin )
+ {
+ d_data->margin = margin;
+ layoutScale();
+ }
+}
+
+/*!
+ \brief Specify the distance between color bar, scale and title
+ \param spacing Spacing
+ \sa spacing()
+*/
+void QwtScaleWidget::setSpacing( int spacing )
+{
+ spacing = qMax( 0, spacing );
+ if ( spacing != d_data->spacing )
+ {
+ d_data->spacing = spacing;
+ layoutScale();
+ }
+}
+
+/*!
+ \brief Change the alignment for the labels.
+
+ \sa QwtScaleDraw::setLabelAlignment(), setLabelRotation()
+*/
+void QwtScaleWidget::setLabelAlignment( Qt::Alignment alignment )
+{
+ d_data->scaleDraw->setLabelAlignment( alignment );
+ layoutScale();
+}
+
+/*!
+ \brief Change the rotation for the labels.
+ See QwtScaleDraw::setLabelRotation().
+
+ \param rotation Rotation
+ \sa QwtScaleDraw::setLabelRotation(), setLabelFlags()
+*/
+void QwtScaleWidget::setLabelRotation( double rotation )
+{
+ d_data->scaleDraw->setLabelRotation( rotation );
+ layoutScale();
+}
+
+/*!
+ Set a scale draw
+ sd has to be created with new and will be deleted in
+ ~QwtScaleWidget() or the next call of setScaleDraw().
+
+ \param sd ScaleDraw object
+ \sa scaleDraw()
+*/
+void QwtScaleWidget::setScaleDraw( QwtScaleDraw *sd )
+{
+ if ( sd == NULL || sd == d_data->scaleDraw )
+ return;
+
+ if ( d_data->scaleDraw )
+ sd->setAlignment( d_data->scaleDraw->alignment() );
+
+ delete d_data->scaleDraw;
+ d_data->scaleDraw = sd;
+
+ layoutScale();
+}
+
+/*!
+ scaleDraw of this scale
+ \sa setScaleDraw(), QwtScaleDraw::setScaleDraw()
+*/
+const QwtScaleDraw *QwtScaleWidget::scaleDraw() const
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ scaleDraw of this scale
+ \sa QwtScaleDraw::setScaleDraw()
+*/
+QwtScaleDraw *QwtScaleWidget::scaleDraw()
+{
+ return d_data->scaleDraw;
+}
+
+/*!
+ \return title
+ \sa setTitle()
+*/
+QwtText QwtScaleWidget::title() const
+{
+ return d_data->title;
+}
+
+/*!
+ \return start border distance
+ \sa setBorderDist()
+*/
+int QwtScaleWidget::startBorderDist() const
+{
+ return d_data->borderDist[0];
+}
+
+/*!
+ \return end border distance
+ \sa setBorderDist()
+*/
+int QwtScaleWidget::endBorderDist() const
+{
+ return d_data->borderDist[1];
+}
+
+/*!
+ \return margin
+ \sa setMargin()
+*/
+int QwtScaleWidget::margin() const
+{
+ return d_data->margin;
+}
+
+/*!
+ \return distance between scale and title
+ \sa setMargin()
+*/
+int QwtScaleWidget::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ \brief paintEvent
+*/
+void QwtScaleWidget::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ draw( &painter );
+}
+
+/*!
+ \brief draw the scale
+*/
+void QwtScaleWidget::draw( QPainter *painter ) const
+{
+ d_data->scaleDraw->draw( painter, palette() );
+
+ if ( d_data->colorBar.isEnabled && d_data->colorBar.width > 0 &&
+ d_data->colorBar.interval.isValid() )
+ {
+ drawColorBar( painter, colorBarRect( contentsRect() ) );
+ }
+
+ QRect r = contentsRect();
+ if ( d_data->scaleDraw->orientation() == Qt::Horizontal )
+ {
+ r.setLeft( r.left() + d_data->borderDist[0] );
+ r.setWidth( r.width() - d_data->borderDist[1] );
+ }
+ else
+ {
+ r.setTop( r.top() + d_data->borderDist[0] );
+ r.setHeight( r.height() - d_data->borderDist[1] );
+ }
+
+ if ( !d_data->title.isEmpty() )
+ drawTitle( painter, d_data->scaleDraw->alignment(), r );
+}
+
+/*!
+ Calculate the the rectangle for the color bar
+
+ \param rect Bounding rectangle for all components of the scale
+ \return Rectabgle for the color bar
+*/
+QRectF QwtScaleWidget::colorBarRect( const QRectF& rect ) const
+{
+ QRectF cr = rect;
+
+ if ( d_data->scaleDraw->orientation() == Qt::Horizontal )
+ {
+ cr.setLeft( cr.left() + d_data->borderDist[0] );
+ cr.setWidth( cr.width() - d_data->borderDist[1] + 1 );
+ }
+ else
+ {
+ cr.setTop( cr.top() + d_data->borderDist[0] );
+ cr.setHeight( cr.height() - d_data->borderDist[1] + 1 );
+ }
+
+ switch ( d_data->scaleDraw->alignment() )
+ {
+ case QwtScaleDraw::LeftScale:
+ {
+ cr.setLeft( cr.right() - d_data->margin
+ - d_data->colorBar.width );
+ cr.setWidth( d_data->colorBar.width );
+ break;
+ }
+
+ case QwtScaleDraw::RightScale:
+ {
+ cr.setLeft( cr.left() + d_data->margin );
+ cr.setWidth( d_data->colorBar.width );
+ break;
+ }
+
+ case QwtScaleDraw::BottomScale:
+ {
+ cr.setTop( cr.top() + d_data->margin );
+ cr.setHeight( d_data->colorBar.width );
+ break;
+ }
+
+ case QwtScaleDraw::TopScale:
+ {
+ cr.setTop( cr.bottom() - d_data->margin
+ - d_data->colorBar.width );
+ cr.setHeight( d_data->colorBar.width );
+ break;
+ }
+ }
+
+ return cr;
+}
+
+/*!
+ Event handler for resize event
+ \param event Resize event
+*/
+void QwtScaleWidget::resizeEvent( QResizeEvent *event )
+{
+ Q_UNUSED( event );
+ layoutScale( false );
+}
+
+/*!
+ Recalculate the scale's geometry and layout based on
+ the current rect and fonts.
+
+ \param update_geometry Notify the layout system and call update
+ to redraw the scale
+*/
+
+void QwtScaleWidget::layoutScale( bool update_geometry )
+{
+ int bd0, bd1;
+ getBorderDistHint( bd0, bd1 );
+ if ( d_data->borderDist[0] > bd0 )
+ bd0 = d_data->borderDist[0];
+ if ( d_data->borderDist[1] > bd1 )
+ bd1 = d_data->borderDist[1];
+
+ int colorBarWidth = 0;
+ if ( d_data->colorBar.isEnabled && d_data->colorBar.interval.isValid() )
+ colorBarWidth = d_data->colorBar.width + d_data->spacing;
+
+ const QRectF r = contentsRect();
+ double x, y, length;
+
+ if ( d_data->scaleDraw->orientation() == Qt::Vertical )
+ {
+ y = r.top() + bd0;
+ length = r.height() - ( bd0 + bd1 );
+
+ if ( d_data->scaleDraw->alignment() == QwtScaleDraw::LeftScale )
+ x = r.right() - 1.0 - d_data->margin - colorBarWidth;
+ else
+ x = r.left() + d_data->margin + colorBarWidth;
+ }
+ else
+ {
+ x = r.left() + bd0;
+ length = r.width() - ( bd0 + bd1 );
+
+ if ( d_data->scaleDraw->alignment() == QwtScaleDraw::BottomScale )
+ y = r.top() + d_data->margin + colorBarWidth;
+ else
+ y = r.bottom() - 1.0 - d_data->margin - colorBarWidth;
+ }
+
+ d_data->scaleDraw->move( x, y );
+ d_data->scaleDraw->setLength( length );
+
+ const int extent = qCeil( d_data->scaleDraw->extent( font() ) );
+
+ d_data->titleOffset =
+ d_data->margin + d_data->spacing + colorBarWidth + extent;
+
+ if ( update_geometry )
+ {
+ updateGeometry();
+ update();
+ }
+}
+
+/*!
+ Draw the color bar of the scale widget
+
+ \param painter Painter
+ \param rect Bounding rectangle for the color bar
+
+ \sa setColorBarEnabled()
+*/
+void QwtScaleWidget::drawColorBar( QPainter *painter, const QRectF& rect ) const
+{
+ if ( !d_data->colorBar.interval.isValid() )
+ return;
+
+ const QwtScaleDraw* sd = d_data->scaleDraw;
+
+ QwtPainter::drawColorBar( painter, *d_data->colorBar.colorMap,
+ d_data->colorBar.interval.normalized(), sd->scaleMap(),
+ sd->orientation(), rect );
+}
+
+/*!
+ Rotate and paint a title according to its position into a given rectangle.
+
+ \param painter Painter
+ \param align Alignment
+ \param rect Bounding rectangle
+*/
+
+void QwtScaleWidget::drawTitle( QPainter *painter,
+ QwtScaleDraw::Alignment align, const QRectF &rect ) const
+{
+ QRectF r = rect;
+ double angle;
+ int flags = d_data->title.renderFlags() &
+ ~( Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter );
+
+ switch ( align )
+ {
+ case QwtScaleDraw::LeftScale:
+ angle = -90.0;
+ flags |= Qt::AlignTop;
+ r.setRect( r.left(), r.bottom(),
+ r.height(), r.width() - d_data->titleOffset );
+ break;
+
+ case QwtScaleDraw::RightScale:
+ angle = -90.0;
+ flags |= Qt::AlignTop;
+ r.setRect( r.left() + d_data->titleOffset, r.bottom(),
+ r.height(), r.width() - d_data->titleOffset );
+ break;
+
+ case QwtScaleDraw::BottomScale:
+ angle = 0.0;
+ flags |= Qt::AlignBottom;
+ r.setTop( r.top() + d_data->titleOffset );
+ break;
+
+ case QwtScaleDraw::TopScale:
+ default:
+ angle = 0.0;
+ flags |= Qt::AlignTop;
+ r.setBottom( r.bottom() - d_data->titleOffset );
+ break;
+ }
+
+ if ( d_data->layoutFlags & TitleInverted )
+ {
+ if ( align == QwtScaleDraw::LeftScale
+ || align == QwtScaleDraw::RightScale )
+ {
+ angle = -angle;
+ r.setRect( r.x() + r.height(), r.y() - r.width(),
+ r.width(), r.height() );
+ }
+ }
+
+ painter->save();
+ painter->setFont( font() );
+ painter->setPen( palette().color( QPalette::Text ) );
+
+ painter->translate( r.x(), r.y() );
+ if ( angle != 0.0 )
+ painter->rotate( angle );
+
+ QwtText title = d_data->title;
+ title.setRenderFlags( flags );
+ title.draw( painter, QRectF( 0.0, 0.0, r.width(), r.height() ) );
+
+ painter->restore();
+}
+
+/*!
+ \brief Notify a change of the scale
+
+ This virtual function can be overloaded by derived
+ classes. The default implementation updates the geometry
+ and repaints the widget.
+*/
+
+void QwtScaleWidget::scaleChange()
+{
+ layoutScale();
+}
+
+/*!
+ \return a size hint
+*/
+QSize QwtScaleWidget::sizeHint() const
+{
+ return minimumSizeHint();
+}
+
+/*!
+ \return a minimum size hint
+*/
+QSize QwtScaleWidget::minimumSizeHint() const
+{
+ const Qt::Orientation o = d_data->scaleDraw->orientation();
+
+ // Border Distance cannot be less than the scale borderDistHint
+ // Note, the borderDistHint is already included in minHeight/minWidth
+ int length = 0;
+ int mbd1, mbd2;
+ getBorderDistHint( mbd1, mbd2 );
+ length += qMax( 0, d_data->borderDist[0] - mbd1 );
+ length += qMax( 0, d_data->borderDist[1] - mbd2 );
+ length += d_data->scaleDraw->minLength( font() );
+
+ int dim = dimForLength( length, font() );
+ if ( length < dim )
+ {
+ // compensate for long titles
+ length = dim;
+ dim = dimForLength( length, font() );
+ }
+
+ QSize size( length + 2, dim );
+ if ( o == Qt::Vertical )
+ size.transpose();
+
+ int left, right, top, bottom;
+ getContentsMargins( &left, &top, &right, &bottom );
+ return size + QSize( left + right, top + bottom );
+}
+
+/*!
+ \brief Find the height of the title for a given width.
+ \param width Width
+ \return height Height
+ */
+
+int QwtScaleWidget::titleHeightForWidth( int width ) const
+{
+ return qCeil( d_data->title.heightForWidth( width, font() ) );
+}
+
+/*!
+ \brief Find the minimum dimension for a given length.
+ dim is the height, length the width seen in
+ direction of the title.
+ \param length width for horizontal, height for vertical scales
+ \param scaleFont Font of the scale
+ \return height for horizontal, width for vertical scales
+*/
+
+int QwtScaleWidget::dimForLength( int length, const QFont &scaleFont ) const
+{
+ const int extent = qCeil( d_data->scaleDraw->extent( scaleFont ) );
+
+ int dim = d_data->margin + extent + 1;
+
+ if ( !d_data->title.isEmpty() )
+ dim += titleHeightForWidth( length ) + d_data->spacing;
+
+ if ( d_data->colorBar.isEnabled && d_data->colorBar.interval.isValid() )
+ dim += d_data->colorBar.width + d_data->spacing;
+
+ return dim;
+}
+
+/*!
+ \brief Calculate a hint for the border distances.
+
+ This member function calculates the distance
+ of the scale's endpoints from the widget borders which
+ is required for the mark labels to fit into the widget.
+ The maximum of this distance an the minimum border distance
+ is returned.
+
+ \warning
+ <ul> <li>The minimum border distance depends on the font.</ul>
+ \sa setMinBorderDist(), getMinBorderDist(), setBorderDist()
+*/
+void QwtScaleWidget::getBorderDistHint( int &start, int &end ) const
+{
+ d_data->scaleDraw->getBorderDistHint( font(), start, end );
+
+ if ( start < d_data->minBorderDist[0] )
+ start = d_data->minBorderDist[0];
+
+ if ( end < d_data->minBorderDist[1] )
+ end = d_data->minBorderDist[1];
+}
+
+/*!
+ Set a minimum value for the distances of the scale's endpoints from
+ the widget borders. This is useful to avoid that the scales
+ are "jumping", when the tick labels or their positions change
+ often.
+
+ \param start Minimum for the start border
+ \param end Minimum for the end border
+ \sa getMinBorderDist(), getBorderDistHint()
+*/
+void QwtScaleWidget::setMinBorderDist( int start, int end )
+{
+ d_data->minBorderDist[0] = start;
+ d_data->minBorderDist[1] = end;
+}
+
+/*!
+ Get the minimum value for the distances of the scale's endpoints from
+ the widget borders.
+
+ \sa setMinBorderDist(), getBorderDistHint()
+*/
+void QwtScaleWidget::getMinBorderDist( int &start, int &end ) const
+{
+ start = d_data->minBorderDist[0];
+ end = d_data->minBorderDist[1];
+}
+
+/*!
+ \brief Assign a scale division
+
+ The scale division determines where to set the tick marks.
+
+ \param transformation Transformation, needed to translate between
+ scale and pixal values
+ \param scaleDiv Scale Division
+ \sa For more information about scale divisions, see QwtScaleDiv.
+*/
+void QwtScaleWidget::setScaleDiv(
+ QwtScaleTransformation *transformation,
+ const QwtScaleDiv &scaleDiv )
+{
+ QwtScaleDraw *sd = d_data->scaleDraw;
+ if ( sd->scaleDiv() != scaleDiv ||
+ sd->scaleMap().transformation()->type() != transformation->type() )
+ {
+ sd->setTransformation( transformation );
+ sd->setScaleDiv( scaleDiv );
+ layoutScale();
+
+ Q_EMIT scaleDivChanged();
+ }
+ else
+ {
+ /*
+ The transformation doesn't anything different as the
+ previous one. So we better throw it silently away instead of
+ initiating heavy updates
+ */
+
+ delete transformation;
+ }
+}
+
+/*!
+ En/disable a color bar associated to the scale
+ \sa isColorBarEnabled(), setColorBarWidth()
+*/
+void QwtScaleWidget::setColorBarEnabled( bool on )
+{
+ if ( on != d_data->colorBar.isEnabled )
+ {
+ d_data->colorBar.isEnabled = on;
+ layoutScale();
+ }
+}
+
+/*!
+ \return true, when the color bar is enabled
+ \sa setColorBarEnabled(), setColorBarWidth()
+*/
+bool QwtScaleWidget::isColorBarEnabled() const
+{
+ return d_data->colorBar.isEnabled;
+}
+
+/*!
+ Set the width of the color bar
+
+ \param width Width
+ \sa colorBarWidth(), setColorBarEnabled()
+*/
+void QwtScaleWidget::setColorBarWidth( int width )
+{
+ if ( width != d_data->colorBar.width )
+ {
+ d_data->colorBar.width = width;
+ if ( isColorBarEnabled() )
+ layoutScale();
+ }
+}
+
+/*!
+ \return Width of the color bar
+ \sa setColorBarEnabled(), setColorBarEnabled()
+*/
+int QwtScaleWidget::colorBarWidth() const
+{
+ return d_data->colorBar.width;
+}
+
+/*!
+ \return Value interval for the color bar
+ \sa setColorMap(), colorMap()
+*/
+QwtInterval QwtScaleWidget::colorBarInterval() const
+{
+ return d_data->colorBar.interval;
+}
+
+/*!
+ Set the color map and value interval, that are used for displaying
+ the color bar.
+
+ \param interval Value interval
+ \param colorMap Color map
+
+ \sa colorMap(), colorBarInterval()
+*/
+void QwtScaleWidget::setColorMap(
+ const QwtInterval &interval, QwtColorMap *colorMap )
+{
+ d_data->colorBar.interval = interval;
+
+ if ( colorMap != d_data->colorBar.colorMap )
+ {
+ delete d_data->colorBar.colorMap;
+ d_data->colorBar.colorMap = colorMap;
+ }
+
+ if ( isColorBarEnabled() )
+ layoutScale();
+}
+
+/*!
+ \return Color map
+ \sa setColorMap(), colorBarInterval()
+*/
+const QwtColorMap *QwtScaleWidget::colorMap() const
+{
+ return d_data->colorBar.colorMap;
+}
diff --git a/src/libpcp_qwt/src/qwt_scale_widget.h b/src/libpcp_qwt/src/qwt_scale_widget.h
new file mode 100644
index 0000000..7007902
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_scale_widget.h
@@ -0,0 +1,135 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SCALE_WIDGET_H
+#define QWT_SCALE_WIDGET_H
+
+#include "qwt_global.h"
+#include "qwt_text.h"
+#include "qwt_scale_draw.h"
+#include <qwidget.h>
+#include <qfont.h>
+#include <qcolor.h>
+#include <qstring.h>
+
+class QPainter;
+class QwtScaleTransformation;
+class QwtScaleDiv;
+class QwtColorMap;
+
+/*!
+ \brief A Widget which contains a scale
+
+ This Widget can be used to decorate composite widgets with
+ a scale.
+*/
+
+class QWT_EXPORT QwtScaleWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ //! Layout flags of the title
+ enum LayoutFlag
+ {
+ /*!
+ The title of vertical scales is painted from top to bottom.
+ Otherwise it is painted from bottom to top.
+ */
+ TitleInverted = 1
+ };
+
+ //! Layout flags of the title
+ typedef QFlags<LayoutFlag> LayoutFlags;
+
+ explicit QwtScaleWidget( QWidget *parent = NULL );
+ explicit QwtScaleWidget( QwtScaleDraw::Alignment, QWidget *parent = NULL );
+ virtual ~QwtScaleWidget();
+
+Q_SIGNALS:
+ //! Signal emitted, whenever the scale divison changes
+ void scaleDivChanged();
+
+public:
+ void setTitle( const QString &title );
+ void setTitle( const QwtText &title );
+ QwtText title() const;
+
+ void setLayoutFlag( LayoutFlag, bool on );
+ bool testLayoutFlag( LayoutFlag ) const;
+
+ void setBorderDist( int start, int end );
+ int startBorderDist() const;
+ int endBorderDist() const;
+
+ void getBorderDistHint( int &start, int &end ) const;
+
+ void getMinBorderDist( int &start, int &end ) const;
+ void setMinBorderDist( int start, int end );
+
+ void setMargin( int );
+ int margin() const;
+
+ void setSpacing( int td );
+ int spacing() const;
+
+ void setScaleDiv( QwtScaleTransformation *, const QwtScaleDiv &sd );
+
+ void setScaleDraw( QwtScaleDraw * );
+ const QwtScaleDraw *scaleDraw() const;
+ QwtScaleDraw *scaleDraw();
+
+ void setLabelAlignment( Qt::Alignment );
+ void setLabelRotation( double rotation );
+
+ void setColorBarEnabled( bool );
+ bool isColorBarEnabled() const;
+
+ void setColorBarWidth( int );
+ int colorBarWidth() const;
+
+ void setColorMap( const QwtInterval &, QwtColorMap * );
+
+ QwtInterval colorBarInterval() const;
+ const QwtColorMap *colorMap() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ int titleHeightForWidth( int width ) const;
+ int dimForLength( int length, const QFont &scaleFont ) const;
+
+ void drawColorBar( QPainter *painter, const QRectF & ) const;
+ void drawTitle( QPainter *painter, QwtScaleDraw::Alignment,
+ const QRectF &rect ) const;
+
+ void setAlignment( QwtScaleDraw::Alignment );
+ QwtScaleDraw::Alignment alignment() const;
+
+ QRectF colorBarRect( const QRectF& ) const;
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+
+ void draw( QPainter *p ) const;
+
+ void scaleChange();
+ void layoutScale( bool update = true );
+
+private:
+ void initScale( QwtScaleDraw::Alignment );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtScaleWidget::LayoutFlags )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_series_data.cpp b/src/libpcp_qwt/src/qwt_series_data.cpp
new file mode 100644
index 0000000..faf7fc8
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_series_data.cpp
@@ -0,0 +1,591 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_series_data.h"
+#include "qwt_math.h"
+#include <qnumeric.h>
+
+static inline QRectF qwtBoundingRect( const QPointF &sample )
+{
+ return QRectF( sample.x(), sample.y(), 0.0, 0.0 );
+}
+
+static inline QRectF qwtBoundingRect( const QwtPoint3D &sample )
+{
+ return QRectF( sample.x(), sample.y(), 0.0, 0.0 );
+}
+
+static inline QRectF qwtBoundingRect( const QwtPointPolar &sample )
+{
+ return QRectF( sample.azimuth(), sample.radius(), 0.0, 0.0 );
+}
+
+static inline QRectF qwtBoundingRect( const QwtIntervalSample &sample )
+{
+ return QRectF( sample.interval.minValue(), sample.value,
+ sample.interval.maxValue() - sample.interval.minValue(), 0.0 );
+}
+
+static inline QRectF qwtBoundingRect( const QwtSetSample &sample )
+{
+ double minX = sample.set[0];
+ double maxX = sample.set[0];
+
+ for ( int i = 1; i < sample.set.size(); i++ )
+ {
+ if ( sample.set[i] < minX )
+ minX = sample.set[i];
+ if ( sample.set[i] > maxX )
+ maxX = sample.set[i];
+ }
+
+ double minY = sample.value;
+ double maxY = sample.value;
+
+ return QRectF( minX, minY, maxX - minX, maxY - minY );
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+
+template <class T>
+QRectF qwtBoundingRectT(
+ const QwtSeriesData<T>& series, int from, int to )
+{
+ QRectF boundingRect( 1.0, 1.0, -2.0, -2.0 ); // invalid;
+
+ if ( from < 0 )
+ from = 0;
+
+ if ( to < 0 )
+ to = series.size() - 1;
+
+ if ( to < from )
+ return boundingRect;
+
+ int i;
+ for ( i = from; i <= to; i++ )
+ {
+ const QRectF rect = qwtBoundingRect( series.sample( i ) );
+ if ( rect.width() >= 0.0 && rect.height() >= 0.0 )
+ {
+ boundingRect = rect;
+ i++;
+ break;
+ }
+ }
+
+ for ( ; i <= to; i++ )
+ {
+ const QRectF rect = qwtBoundingRect( series.sample( i ) );
+ if ( rect.width() >= 0.0 && rect.height() >= 0.0 )
+ {
+ if (!qIsNaN(rect.left()))
+ boundingRect.setLeft( qMin( boundingRect.left(), rect.left() ) );
+ if (!qIsNaN(rect.right()))
+ boundingRect.setRight( qMax( boundingRect.right(), rect.right() ) );
+ if (!qIsNaN(rect.top()))
+ boundingRect.setTop( qMin( boundingRect.top(), rect.top() ) );
+ if (!qIsNaN(rect.bottom()))
+ boundingRect.setBottom( qMax( boundingRect.bottom(), rect.bottom() ) );
+ }
+ }
+
+ return boundingRect;
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+QRectF qwtBoundingRect(
+ const QwtSeriesData<QPointF> &series, int from, int to )
+{
+ return qwtBoundingRectT<QPointF>( series, from, to );
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtPoint3D> &series, int from, int to )
+{
+ return qwtBoundingRectT<QwtPoint3D>( series, from, to );
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ The horizontal coordinates represent the azimuth, the
+ vertical coordinates the radius.
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtPointPolar> &series, int from, int to )
+{
+ return qwtBoundingRectT<QwtPointPolar>( series, from, to );
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtIntervalSample>& series, int from, int to )
+{
+ return qwtBoundingRectT<QwtIntervalSample>( series, from, to );
+}
+
+/*!
+ \brief Calculate the bounding rect of a series subset
+
+ Slow implementation, that iterates over the series.
+
+ \param series Series
+ \param from Index of the first sample, <= 0 means from the beginning
+ \param to Index of the last sample, < 0 means to the end
+
+ \return Bounding rectangle
+*/
+QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtSetSample>& series, int from, int to )
+{
+ return qwtBoundingRectT<QwtSetSample>( series, from, to );
+}
+
+/*!
+ Constructor
+ \param samples Samples
+*/
+QwtPointSeriesData::QwtPointSeriesData(
+ const QVector<QPointF> &samples ):
+ QwtArraySeriesData<QPointF>( samples )
+{
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtPointSeriesData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0.0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+/*!
+ Constructor
+ \param samples Samples
+*/
+QwtPoint3DSeriesData::QwtPoint3DSeriesData(
+ const QVector<QwtPoint3D> &samples ):
+ QwtArraySeriesData<QwtPoint3D>( samples )
+{
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtPoint3DSeriesData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0.0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+/*!
+ Constructor
+ \param samples Samples
+*/
+QwtIntervalSeriesData::QwtIntervalSeriesData(
+ const QVector<QwtIntervalSample> &samples ):
+ QwtArraySeriesData<QwtIntervalSample>( samples )
+{
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtIntervalSeriesData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0.0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+/*!
+ Constructor
+ \param samples Samples
+*/
+QwtSetSeriesData::QwtSetSeriesData(
+ const QVector<QwtSetSample> &samples ):
+ QwtArraySeriesData<QwtSetSample>( samples )
+{
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtSetSeriesData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0.0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+/*!
+ Constructor
+
+ \param x Array of x values
+ \param y Array of y values
+
+ \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples()
+*/
+QwtPointArrayData::QwtPointArrayData(
+ const QVector<double> &x, const QVector<double> &y ):
+ d_x( x ),
+ d_y( y )
+{
+}
+
+/*!
+ Constructor
+
+ \param x Array of x values
+ \param y Array of y values
+ \param size Size of the x and y arrays
+ \sa QwtPlotCurve::setData(), QwtPlotCurve::setSamples()
+*/
+QwtPointArrayData::QwtPointArrayData( const double *x,
+ const double *y, size_t size )
+{
+ d_x.resize( size );
+ qMemCopy( d_x.data(), x, size * sizeof( double ) );
+
+ d_y.resize( size );
+ qMemCopy( d_y.data(), y, size * sizeof( double ) );
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtPointArrayData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+//! \return Size of the data set
+size_t QwtPointArrayData::size() const
+{
+ return qMin( d_x.size(), d_y.size() );
+}
+
+/*!
+ Return the sample at position i
+
+ \param i Index
+ \return Sample at position i
+*/
+QPointF QwtPointArrayData::sample( size_t i ) const
+{
+ return QPointF( d_x[int( i )], d_y[int( i )] );
+}
+
+//! \return Array of the x-values
+const QVector<double> &QwtPointArrayData::xData() const
+{
+ return d_x;
+}
+
+//! \return Array of the y-values
+const QVector<double> &QwtPointArrayData::yData() const
+{
+ return d_y;
+}
+
+/*!
+ Constructor
+
+ \param x Array of x values
+ \param y Array of y values
+ \param size Size of the x and y arrays
+
+ \warning The programmer must assure that the memory blocks referenced
+ by the pointers remain valid during the lifetime of the
+ QwtPlotCPointer object.
+
+ \sa QwtPlotCurve::setData(), QwtPlotCurve::setRawSamples()
+*/
+QwtCPointerData::QwtCPointerData(
+ const double *x, const double *y, size_t size ):
+ d_x( x ),
+ d_y( y ),
+ d_size( size )
+{
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ The bounding rectangle is calculated once by iterating over all
+ points and is stored for all following requests.
+
+ \return Bounding rectangle
+*/
+QRectF QwtCPointerData::boundingRect() const
+{
+ if ( d_boundingRect.width() < 0 )
+ d_boundingRect = qwtBoundingRect( *this );
+
+ return d_boundingRect;
+}
+
+//! \return Size of the data set
+size_t QwtCPointerData::size() const
+{
+ return d_size;
+}
+
+/*!
+ Return the sample at position i
+
+ \param i Index
+ \return Sample at position i
+*/
+QPointF QwtCPointerData::sample( size_t i ) const
+{
+ return QPointF( d_x[int( i )], d_y[int( i )] );
+}
+
+//! \return Array of the x-values
+const double *QwtCPointerData::xData() const
+{
+ return d_x;
+}
+
+//! \return Array of the y-values
+const double *QwtCPointerData::yData() const
+{
+ return d_y;
+}
+
+/*!
+ Constructor
+
+ \param size Number of points
+ \param interval Bounding interval for the points
+
+ \sa setInterval(), setSize()
+*/
+QwtSyntheticPointData::QwtSyntheticPointData(
+ size_t size, const QwtInterval &interval ):
+ d_size( size ),
+ d_interval( interval )
+{
+}
+
+/*!
+ Change the number of points
+
+ \param size Number of points
+ \sa size(), setInterval()
+*/
+void QwtSyntheticPointData::setSize( size_t size )
+{
+ d_size = size;
+}
+
+/*!
+ \return Number of points
+ \sa setSize(), interval()
+*/
+size_t QwtSyntheticPointData::size() const
+{
+ return d_size;
+}
+
+/*!
+ Set the bounding interval
+
+ \param interval Interval
+ \sa interval(), setSize()
+*/
+void QwtSyntheticPointData::setInterval( const QwtInterval &interval )
+{
+ d_interval = interval.normalized();
+}
+
+/*!
+ \return Bounding interval
+ \sa setInterval(), size()
+*/
+QwtInterval QwtSyntheticPointData::interval() const
+{
+ return d_interval;
+}
+
+/*!
+ Set a the "rect of interest"
+
+ QwtPlotSeriesItem defines the current area of the plot canvas
+ as "rect of interest" ( QwtPlotSeriesItem::updateScaleDiv() ).
+
+ If interval().isValid() == false the x values are calculated
+ in the interval rect.left() -> rect.right().
+
+ \sa rectOfInterest()
+*/
+void QwtSyntheticPointData::setRectOfInterest( const QRectF &rect )
+{
+ d_rectOfInterest = rect;
+ d_intervalOfInterest = QwtInterval(
+ rect.left(), rect.right() ).normalized();
+}
+
+/*!
+ \return "rect of interest"
+ \sa setRectOfInterest()
+*/
+QRectF QwtSyntheticPointData::rectOfInterest() const
+{
+ return d_rectOfInterest;
+}
+
+/*!
+ \brief Calculate the bounding rect
+
+ This implementation iterates over all points, what could often
+ be implemented much faster using the characteristics of the series.
+ When there are many points it is recommended to overload and
+ reimplement this method using the characteristics of the series
+ ( if possible ).
+
+ \return Bounding rectangle
+*/
+QRectF QwtSyntheticPointData::boundingRect() const
+{
+ if ( d_size == 0 ||
+ !( d_interval.isValid() || d_intervalOfInterest.isValid() ) )
+ {
+ return QRectF( 1.0, 1.0, -2.0, -2.0 ); // something invalid
+ }
+
+ return qwtBoundingRect( *this );
+}
+
+/*!
+ Calculate the point from an index
+
+ \param index Index
+ \return QPointF(x(index), y(x(index)));
+
+ \warning For invalid indices ( index < 0 || index >= size() )
+ (0, 0) is returned.
+*/
+QPointF QwtSyntheticPointData::sample( size_t index ) const
+{
+ if ( index >= d_size )
+ return QPointF( 0, 0 );
+
+ const double xValue = x( index );
+ const double yValue = y( xValue );
+
+ return QPointF( xValue, yValue );
+}
+
+/*!
+ Calculate a x-value from an index
+
+ x values are calculated by deviding an interval into
+ equidistant steps. If !interval().isValid() the
+ interval is calculated from the "rect of interest".
+
+ \sa interval(), rectOfInterest(), y()
+*/
+double QwtSyntheticPointData::x( uint index ) const
+{
+ const QwtInterval &interval = d_interval.isValid() ?
+ d_interval : d_intervalOfInterest;
+
+ if ( !interval.isValid() || d_size == 0 || index >= d_size )
+ return 0.0;
+
+ const double dx = interval.width() / d_size;
+ return interval.minValue() + index * dx;
+}
diff --git a/src/libpcp_qwt/src/qwt_series_data.h b/src/libpcp_qwt/src/qwt_series_data.h
new file mode 100644
index 0000000..cf4a8d2
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_series_data.h
@@ -0,0 +1,460 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SERIES_DATA_H
+#define QWT_SERIES_DATA_H 1
+
+#include "qwt_global.h"
+#include "qwt_interval.h"
+#include "qwt_point_3d.h"
+#include "qwt_point_polar.h"
+#include <qvector.h>
+#include <qrect.h>
+
+//! \brief A sample of the types (x1-x2, y) or (x, y1-y2)
+class QWT_EXPORT QwtIntervalSample
+{
+public:
+ QwtIntervalSample();
+ QwtIntervalSample( double, const QwtInterval & );
+ QwtIntervalSample( double value, double min, double max );
+
+ bool operator==( const QwtIntervalSample & ) const;
+ bool operator!=( const QwtIntervalSample & ) const;
+
+ //! Value
+ double value;
+
+ //! Interval
+ QwtInterval interval;
+};
+
+/*!
+ Constructor
+ The value is set to 0.0, the interval is invalid
+*/
+inline QwtIntervalSample::QwtIntervalSample():
+ value( 0.0 )
+{
+}
+
+//! Constructor
+inline QwtIntervalSample::QwtIntervalSample(
+ double v, const QwtInterval &intv ):
+ value( v ),
+ interval( intv )
+{
+}
+
+//! Constructor
+inline QwtIntervalSample::QwtIntervalSample(
+ double v, double min, double max ):
+ value( v ),
+ interval( min, max )
+{
+}
+
+//! Compare operator
+inline bool QwtIntervalSample::operator==(
+ const QwtIntervalSample &other ) const
+{
+ return value == other.value && interval == other.interval;
+}
+
+//! Compare operator
+inline bool QwtIntervalSample::operator!=(
+ const QwtIntervalSample &other ) const
+{
+ return !( *this == other );
+}
+
+//! \brief A sample of the types (x1...xn, y) or (x, y1..yn)
+class QWT_EXPORT QwtSetSample
+{
+public:
+ QwtSetSample();
+ bool operator==( const QwtSetSample &other ) const;
+ bool operator!=( const QwtSetSample &other ) const;
+
+ //! value
+ double value;
+
+ //! Vector of values associated to value
+ QVector<double> set;
+};
+
+/*!
+ Constructor
+ The value is set to 0.0
+*/
+inline QwtSetSample::QwtSetSample():
+ value( 0.0 )
+{
+}
+
+//! Compare operator
+inline bool QwtSetSample::operator==( const QwtSetSample &other ) const
+{
+ return value == other.value && set == other.set;
+}
+
+//! Compare operator
+inline bool QwtSetSample::operator!=( const QwtSetSample &other ) const
+{
+ return !( *this == other );
+}
+
+/*!
+ \brief Abstract interface for iterating over samples
+
+ Qwt offers several implementations of the QwtSeriesData API,
+ but in situations, where data of an application specific format
+ needs to be displayed, without having to copy it, it is recommended
+ to implement an individual data access.
+
+ A subclass of QwtSeriesData<QPointF> must implement:
+
+ - size()\n
+ Should return number of data points.
+
+ - sample()\n
+ Should return values x and y values of the sample at specific position
+ as QPointF object.
+
+ - boundingRect()\n
+ Should return the bounding rectangle of the data series.
+ It is used for autoscaling and might help certain algorithms for displaying
+ the data. You can use qwtBoundingRect() for an implementation
+ but often it is possible to implement a more efficient alogrithm
+ depending on the characteristics of the series.
+ The member d_boundingRect is intended for caching the calculated rectangle.
+
+*/
+template <typename T>
+class QwtSeriesData
+{
+public:
+ QwtSeriesData();
+ virtual ~QwtSeriesData();
+
+ //! \return Number of samples
+ virtual size_t size() const = 0;
+
+ /*!
+ Return a sample
+ \param i Index
+ \return Sample at position i
+ */
+ virtual T sample( size_t i ) const = 0;
+
+ /*!
+ Calculate the bounding rect of all samples
+
+ The bounding rect is necessary for autoscaling and can be used
+ for a couple of painting optimizations.
+
+ qwtBoundingRect(...) offers slow implementations iterating
+ over the samples. For large sets it is recommended to implement
+ something faster f.e. by caching the bounding rect.
+ */
+ virtual QRectF boundingRect() const = 0;
+
+ virtual void setRectOfInterest( const QRectF & );
+
+protected:
+ //! Can be used to cache a calculated bounding rectangle
+ mutable QRectF d_boundingRect;
+
+private:
+ QwtSeriesData<T> &operator=( const QwtSeriesData<T> & );
+};
+
+//! Constructor
+template <typename T>
+QwtSeriesData<T>::QwtSeriesData():
+ d_boundingRect( 0.0, 0.0, -1.0, -1.0 )
+{
+}
+
+//! Destructor
+template <typename T>
+QwtSeriesData<T>::~QwtSeriesData()
+{
+}
+
+/*!
+ Set a the "rect of interest"
+
+ QwtPlotSeriesItem defines the current area of the plot canvas
+ as "rect of interest" ( QwtPlotSeriesItem::updateScaleDiv() ).
+ It can be used to implement different levels of details.
+
+ The default implementation does nothing.
+*/
+template <typename T>
+void QwtSeriesData<T>::setRectOfInterest( const QRectF & )
+{
+}
+
+/*!
+ \brief Template class for data, that is organized as QVector
+
+ QVector uses implicit data sharing and can be
+ passed around as argument efficiently.
+*/
+template <typename T>
+class QwtArraySeriesData: public QwtSeriesData<T>
+{
+public:
+ QwtArraySeriesData();
+ QwtArraySeriesData( const QVector<T> & );
+
+ void setSamples( const QVector<T> & );
+ const QVector<T> samples() const;
+
+ virtual size_t size() const;
+ virtual T sample( size_t ) const;
+
+protected:
+ //! Vector of samples
+ QVector<T> d_samples;
+};
+
+//! Constructor
+template <typename T>
+QwtArraySeriesData<T>::QwtArraySeriesData()
+{
+}
+
+/*!
+ Constructor
+ \param samples Array of samples
+*/
+template <typename T>
+QwtArraySeriesData<T>::QwtArraySeriesData( const QVector<T> &samples ):
+ d_samples( samples )
+{
+}
+
+/*!
+ Assign an array of samples
+ \param samples Array of samples
+*/
+template <typename T>
+void QwtArraySeriesData<T>::setSamples( const QVector<T> &samples )
+{
+ QwtSeriesData<T>::d_boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 );
+ d_samples = samples;
+}
+
+//! \return Array of samples
+template <typename T>
+const QVector<T> QwtArraySeriesData<T>::samples() const
+{
+ return d_samples;
+}
+
+//! \return Number of samples
+template <typename T>
+size_t QwtArraySeriesData<T>::size() const
+{
+ return d_samples.size();
+}
+
+/*!
+ Return a sample
+ \param i Index
+ \return Sample at position i
+*/
+template <typename T>
+T QwtArraySeriesData<T>::sample( size_t i ) const
+{
+ return d_samples[ static_cast<int>( i ) ];
+}
+
+//! Interface for iterating over an array of points
+class QWT_EXPORT QwtPointSeriesData: public QwtArraySeriesData<QPointF>
+{
+public:
+ QwtPointSeriesData(
+ const QVector<QPointF> & = QVector<QPointF>() );
+
+ virtual QRectF boundingRect() const;
+};
+
+//! Interface for iterating over an array of 3D points
+class QWT_EXPORT QwtPoint3DSeriesData: public QwtArraySeriesData<QwtPoint3D>
+{
+public:
+ QwtPoint3DSeriesData(
+ const QVector<QwtPoint3D> & = QVector<QwtPoint3D>() );
+ virtual QRectF boundingRect() const;
+};
+
+//! Interface for iterating over an array of intervals
+class QWT_EXPORT QwtIntervalSeriesData: public QwtArraySeriesData<QwtIntervalSample>
+{
+public:
+ QwtIntervalSeriesData(
+ const QVector<QwtIntervalSample> & = QVector<QwtIntervalSample>() );
+
+ virtual QRectF boundingRect() const;
+};
+
+//! Interface for iterating over an array of samples
+class QWT_EXPORT QwtSetSeriesData: public QwtArraySeriesData<QwtSetSample>
+{
+public:
+ QwtSetSeriesData(
+ const QVector<QwtSetSample> & = QVector<QwtSetSample>() );
+
+ virtual QRectF boundingRect() const;
+};
+
+/*!
+ \brief Interface for iterating over two QVector<double> objects.
+*/
+class QWT_EXPORT QwtPointArrayData: public QwtSeriesData<QPointF>
+{
+public:
+ QwtPointArrayData( const QVector<double> &x, const QVector<double> &y );
+ QwtPointArrayData( const double *x, const double *y, size_t size );
+
+ virtual QRectF boundingRect() const;
+
+ virtual size_t size() const;
+ virtual QPointF sample( size_t i ) const;
+
+ const QVector<double> &xData() const;
+ const QVector<double> &yData() const;
+
+private:
+ QVector<double> d_x;
+ QVector<double> d_y;
+};
+
+/*!
+ \brief Data class containing two pointers to memory blocks of doubles.
+ */
+class QWT_EXPORT QwtCPointerData: public QwtSeriesData<QPointF>
+{
+public:
+ QwtCPointerData( const double *x, const double *y, size_t size );
+
+ virtual QRectF boundingRect() const;
+ virtual size_t size() const;
+ virtual QPointF sample( size_t i ) const;
+
+ const double *xData() const;
+ const double *yData() const;
+
+private:
+ const double *d_x;
+ const double *d_y;
+ size_t d_size;
+};
+
+/*!
+ \brief Synthetic point data
+
+ QwtSyntheticPointData provides a fixed number of points for an interval.
+ The points are calculated in equidistant steps in x-direction.
+
+ If the interval is invalid, the points are calculated for
+ the "rect of interest", what normally is the displayed area on the
+ plot canvas. In this mode you get different levels of detail, when
+ zooming in/out.
+
+ \par Example
+
+ The following example shows how to implement a sinus curve.
+
+ \verbatim
+#include <cmath>
+#include <qwt_series_data.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot.h>
+#include <qapplication.h>
+
+class SinusData: public QwtSyntheticPointData
+{
+public:
+ SinusData():
+ QwtSyntheticPointData(100)
+ {
+ }
+ virtual double y(double x) const
+ {
+ return qSin(x);
+ }
+};
+
+int main(int argc, char **argv)
+{
+ QApplication a(argc, argv);
+
+ QwtPlot plot;
+ plot.setAxisScale(QwtPlot::xBottom, 0.0, 10.0);
+ plot.setAxisScale(QwtPlot::yLeft, -1.0, 1.0);
+
+ QwtPlotCurve *curve = new QwtPlotCurve("y = sin(x)");
+ curve->setData(SinusData());
+ curve->attach(&plot);
+
+ plot.show();
+ return a.exec();
+}
+ \endverbatim
+*/
+class QWT_EXPORT QwtSyntheticPointData: public QwtSeriesData<QPointF>
+{
+public:
+ QwtSyntheticPointData( size_t size,
+ const QwtInterval & = QwtInterval() );
+
+ void setSize( size_t size );
+ size_t size() const;
+
+ void setInterval( const QwtInterval& );
+ QwtInterval interval() const;
+
+ virtual QRectF boundingRect() const;
+ virtual QPointF sample( size_t i ) const;
+
+ /*!
+ Calculate a y value for a x value
+
+ \param x x value
+ \return Corresponding y value
+ */
+ virtual double y( double x ) const = 0;
+ virtual double x( uint index ) const;
+
+ virtual void setRectOfInterest( const QRectF & );
+ QRectF rectOfInterest() const;
+
+private:
+ size_t d_size;
+ QwtInterval d_interval;
+ QRectF d_rectOfInterest;
+ QwtInterval d_intervalOfInterest;
+};
+
+QWT_EXPORT QRectF qwtBoundingRect(
+ const QwtSeriesData<QPointF> &, int from = 0, int to = -1 );
+QWT_EXPORT QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtPoint3D> &, int from = 0, int to = -1 );
+QWT_EXPORT QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtPointPolar> &, int from = 0, int to = -1 );
+QWT_EXPORT QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtIntervalSample> &, int from = 0, int to = -1 );
+QWT_EXPORT QRectF qwtBoundingRect(
+ const QwtSeriesData<QwtSetSample> &, int from = 0, int to = -1 );
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_slider.cpp b/src/libpcp_qwt/src/qwt_slider.cpp
new file mode 100644
index 0000000..357f920
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_slider.cpp
@@ -0,0 +1,805 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_slider.h"
+#include "qwt_painter.h"
+#include "qwt_scale_draw.h"
+#include "qwt_scale_map.h"
+#include <qevent.h>
+#include <qdrawutil.h>
+#include <qpainter.h>
+#include <qalgorithms.h>
+#include <qmath.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qapplication.h>
+
+class QwtSlider::PrivateData
+{
+public:
+ QRect sliderRect;
+
+ QSize handleSize;
+ int borderWidth;
+ int spacing;
+
+ QwtSlider::ScalePos scalePos;
+ QwtSlider::BackgroundStyles bgStyle;
+
+ /*
+ Scale and values might have different maps. This is
+ confusing and I can't see strong arguments for such
+ a feature. TODO ...
+ */
+ QwtScaleMap map; // linear map
+ mutable QSize sizeHintCache;
+};
+
+/*!
+ \brief Constructor
+ \param parent parent widget
+ \param orientation Orientation of the slider. Can be Qt::Horizontal
+ or Qt::Vertical. Defaults to Qt::Horizontal.
+ \param scalePos Position of the scale.
+ Defaults to QwtSlider::NoScale.
+ \param bgStyle Background style. QwtSlider::Trough draws the
+ slider button in a trough, QwtSlider::Slot draws
+ a slot underneath the button. An or-combination of both
+ may also be used. The default is QwtSlider::Trough.
+
+ QwtSlider enforces valid combinations of its orientation and scale position.
+ If the combination is invalid, the scale position will be set to NoScale.
+ Valid combinations are:
+ - Qt::Horizonal with NoScale, TopScale, or BottomScale;
+ - Qt::Vertical with NoScale, LeftScale, or RightScale.
+*/
+QwtSlider::QwtSlider( QWidget *parent,
+ Qt::Orientation orientation, ScalePos scalePos,
+ BackgroundStyles bgStyle ):
+ QwtAbstractSlider( orientation, parent )
+{
+ initSlider( orientation, scalePos, bgStyle );
+}
+
+void QwtSlider::initSlider( Qt::Orientation orientation,
+ ScalePos scalePos, BackgroundStyles bgStyle )
+{
+ if ( orientation == Qt::Vertical )
+ setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding );
+ else
+ setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+
+ d_data = new QwtSlider::PrivateData;
+
+ d_data->borderWidth = 2;
+ d_data->spacing = 4;
+ d_data->scalePos = scalePos;
+ d_data->bgStyle = bgStyle;
+
+ const int handleThickness = 16;
+ d_data->handleSize.setWidth( 2 * handleThickness );
+ d_data->handleSize.setHeight( handleThickness );
+
+ if ( !( bgStyle & QwtSlider::Trough ) )
+ d_data->handleSize.transpose();
+
+ if ( orientation == Qt::Vertical )
+ d_data->handleSize.transpose();
+
+ d_data->sliderRect.setRect( 0, 0, 8, 8 );
+
+ QwtScaleDraw::Alignment align;
+ if ( orientation == Qt::Vertical )
+ {
+ // enforce a valid combination of scale position and orientation
+ if ( ( d_data->scalePos == QwtSlider::BottomScale )
+ || ( d_data->scalePos == QwtSlider::TopScale ) )
+ {
+ d_data->scalePos = NoScale;
+ }
+
+ // adopt the policy of layoutSlider (NoScale lays out like Left)
+ if ( d_data->scalePos == QwtSlider::RightScale )
+ align = QwtScaleDraw::RightScale;
+ else
+ align = QwtScaleDraw::LeftScale;
+ }
+ else
+ {
+ // enforce a valid combination of scale position and orientation
+ if ( ( d_data->scalePos == QwtSlider::LeftScale )
+ || ( d_data->scalePos == QwtSlider::RightScale ) )
+ {
+ d_data->scalePos = QwtSlider::NoScale;
+ }
+
+ // adopt the policy of layoutSlider (NoScale lays out like Bottom)
+ if ( d_data->scalePos == QwtSlider::TopScale )
+ align = QwtScaleDraw::TopScale;
+ else
+ align = QwtScaleDraw::BottomScale;
+ }
+
+ scaleDraw()->setAlignment( align );
+ scaleDraw()->setLength( 100 );
+
+ setRange( 0.0, 100.0, 1.0 );
+ setValue( 0.0 );
+}
+
+QwtSlider::~QwtSlider()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Set the orientation.
+ \param o Orientation. Allowed values are Qt::Horizontal and Qt::Vertical.
+
+ If the new orientation and the old scale position are an invalid combination,
+ the scale position will be set to QwtSlider::NoScale.
+ \sa QwtAbstractSlider::orientation()
+*/
+void QwtSlider::setOrientation( Qt::Orientation o )
+{
+ if ( o == orientation() )
+ return;
+
+ if ( o == Qt::Horizontal )
+ {
+ if ( d_data->scalePos == LeftScale
+ || d_data->scalePos == RightScale )
+ {
+ d_data->scalePos = NoScale;
+ }
+ }
+ else // if (o == Qt::Vertical)
+ {
+ if ( d_data->scalePos == BottomScale ||
+ d_data->scalePos == TopScale )
+ {
+ d_data->scalePos = NoScale;
+ }
+ }
+
+ if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
+ {
+ QSizePolicy sp = sizePolicy();
+ sp.transpose();
+ setSizePolicy( sp );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+ }
+
+ QwtAbstractSlider::setOrientation( o );
+ layoutSlider( true );
+}
+
+/*!
+ \brief Change the scale position (and slider orientation).
+
+ \param scalePos Position of the scale.
+
+ A valid combination of scale position and orientation is enforced:
+ - if the new scale position is Left or Right, the scale orientation will
+ become Qt::Vertical;
+ - if the new scale position is Bottom or Top the scale orientation will
+ become Qt::Horizontal;
+ - if the new scale position is QwtSlider::NoScale, the scale
+ orientation will not change.
+*/
+void QwtSlider::setScalePosition( ScalePos scalePos )
+{
+ if ( d_data->scalePos == scalePos )
+ return;
+
+ d_data->scalePos = scalePos;
+
+ switch ( d_data->scalePos )
+ {
+ case QwtSlider::BottomScale:
+ {
+ setOrientation( Qt::Horizontal );
+ scaleDraw()->setAlignment( QwtScaleDraw::BottomScale );
+ break;
+ }
+ case QwtSlider::TopScale:
+ {
+ setOrientation( Qt::Horizontal );
+ scaleDraw()->setAlignment( QwtScaleDraw::TopScale );
+ break;
+ }
+ case QwtSlider::LeftScale:
+ {
+ setOrientation( Qt::Vertical );
+ scaleDraw()->setAlignment( QwtScaleDraw::LeftScale );
+ break;
+ }
+ case RightScale:
+ {
+ QwtSlider::setOrientation( Qt::Vertical );
+ scaleDraw()->setAlignment( QwtScaleDraw::RightScale );
+ break;
+ }
+ default:
+ {
+ // nothing
+ }
+ }
+
+ layoutSlider( true );
+}
+
+//! Return the scale position.
+QwtSlider::ScalePos QwtSlider::scalePosition() const
+{
+ return d_data->scalePos;
+}
+
+/*!
+ \brief Change the slider's border width
+ \param width Border width
+*/
+void QwtSlider::setBorderWidth( int width )
+{
+ if ( width < 0 )
+ width = 0;
+
+ if ( width != d_data->borderWidth )
+ {
+ d_data->borderWidth = width;
+ layoutSlider( true );
+ }
+}
+
+/*!
+ \return the border width.
+ \sa setBorderWidth()
+*/
+int QwtSlider::borderWidth() const
+{
+ return d_data->borderWidth;
+}
+
+/*!
+ \brief Change the spacing between pipe and scale
+
+ A spacing of 0 means, that the backbone of the scale is below
+ the trough.
+
+ The default setting is 4 pixels.
+
+ \param spacing Number of pixels
+ \sa spacing();
+*/
+void QwtSlider::setSpacing( int spacing )
+{
+ if ( spacing <= 0 )
+ spacing = 0;
+
+ if ( spacing != d_data->spacing )
+ {
+ d_data->spacing = spacing;
+ layoutSlider( true );
+ }
+}
+
+/*!
+ \return Number of pixels between slider and scale
+ \sa setSpacing()
+*/
+int QwtSlider::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ \brief Set the slider's handle size
+ \param width Width
+ \param height Height
+
+ \sa handleSize()
+*/
+void QwtSlider::setHandleSize( int width, int height )
+{
+ setHandleSize( QSize( width, height ) );
+}
+
+/*!
+ \brief Set the slider's handle size
+ \param size New size
+
+ \sa handleSize()
+*/
+void QwtSlider::setHandleSize( const QSize &size )
+{
+ const QSize handleSize = size.expandedTo( QSize( 8, 4 ) );
+ if ( handleSize != d_data->handleSize )
+ {
+ d_data->handleSize = handleSize;
+ layoutSlider( true );
+ }
+}
+
+/*!
+ \return Size of the handle.
+ \sa setHandleSize()
+*/
+QSize QwtSlider::handleSize() const
+{
+ return d_data->handleSize;
+}
+
+/*!
+ \brief Set a scale draw
+
+ For changing the labels of the scales, it
+ is necessary to derive from QwtScaleDraw and
+ overload QwtScaleDraw::label().
+
+ \param scaleDraw ScaleDraw object, that has to be created with
+ new and will be deleted in ~QwtSlider or the next
+ call of setScaleDraw().
+
+ \sa scaleDraw()
+*/
+void QwtSlider::setScaleDraw( QwtScaleDraw *scaleDraw )
+{
+ const QwtScaleDraw *previousScaleDraw = this->scaleDraw();
+ if ( scaleDraw == NULL || scaleDraw == previousScaleDraw )
+ return;
+
+ if ( previousScaleDraw )
+ scaleDraw->setAlignment( previousScaleDraw->alignment() );
+
+ setAbstractScaleDraw( scaleDraw );
+ layoutSlider( true );
+}
+
+/*!
+ \return the scale draw of the slider
+ \sa setScaleDraw()
+*/
+const QwtScaleDraw *QwtSlider::scaleDraw() const
+{
+ return static_cast<const QwtScaleDraw *>( abstractScaleDraw() );
+}
+
+/*!
+ \return the scale draw of the slider
+ \sa setScaleDraw()
+*/
+QwtScaleDraw *QwtSlider::scaleDraw()
+{
+ return static_cast<QwtScaleDraw *>( abstractScaleDraw() );
+}
+
+//! Notify changed scale
+void QwtSlider::scaleChange()
+{
+ layoutSlider( true );
+}
+
+/*!
+ Draw the slider into the specified rectangle.
+
+ \param painter Painter
+ \param sliderRect Bounding rectangle of the slider
+*/
+void QwtSlider::drawSlider(
+ QPainter *painter, const QRect &sliderRect ) const
+{
+ QRect innerRect( sliderRect );
+
+ if ( d_data->bgStyle & QwtSlider::Trough )
+ {
+ const int bw = d_data->borderWidth;
+
+ qDrawShadePanel( painter, sliderRect, palette(), true, bw, NULL );
+
+ innerRect = sliderRect.adjusted( bw, bw, -bw, -bw );
+ painter->fillRect( innerRect, palette().brush( QPalette::Mid ) );
+ }
+
+ if ( d_data->bgStyle & QwtSlider::Groove )
+ {
+ int ws = 4;
+ int ds = d_data->handleSize.width() / 2 - 4;
+ if ( ds < 1 )
+ ds = 1;
+
+ QRect rSlot;
+ if ( orientation() == Qt::Horizontal )
+ {
+ if ( innerRect.height() & 1 )
+ ws++;
+
+ rSlot = QRect( innerRect.x() + ds,
+ innerRect.y() + ( innerRect.height() - ws ) / 2,
+ innerRect.width() - 2 * ds, ws );
+ }
+ else
+ {
+ if ( innerRect.width() & 1 )
+ ws++;
+
+ rSlot = QRect( innerRect.x() + ( innerRect.width() - ws ) / 2,
+ innerRect.y() + ds,
+ ws, innerRect.height() - 2 * ds );
+ }
+
+ QBrush brush = palette().brush( QPalette::Dark );
+ qDrawShadePanel( painter, rSlot, palette(), true, 1 , &brush );
+ }
+
+ if ( isValid() )
+ drawHandle( painter, innerRect, transform( value() ) );
+}
+
+/*!
+ Draw the thumb at a position
+
+ \param painter Painter
+ \param sliderRect Bounding rectangle of the slider
+ \param pos Position of the slider thumb
+*/
+void QwtSlider::drawHandle( QPainter *painter,
+ const QRect &sliderRect, int pos ) const
+{
+ const int bw = d_data->borderWidth;
+
+ pos++; // shade line points one pixel below
+ if ( orientation() == Qt::Horizontal )
+ {
+ QRect handleRect(
+ pos - d_data->handleSize.width() / 2,
+ sliderRect.y(),
+ d_data->handleSize.width(),
+ sliderRect.height()
+ );
+
+ qDrawShadePanel( painter,
+ handleRect, palette(), false, bw,
+ &palette().brush( QPalette::Button ) );
+
+ qDrawShadeLine( painter, pos, sliderRect.top() + bw,
+ pos, sliderRect.bottom() - bw,
+ palette(), true, 1 );
+ }
+ else // Vertical
+ {
+ QRect handleRect(
+ sliderRect.left(),
+ pos - d_data->handleSize.height() / 2,
+ sliderRect.width(),
+ d_data->handleSize.height()
+ );
+
+ qDrawShadePanel( painter,
+ handleRect, palette(), false, bw,
+ &palette().brush( QPalette::Button ) );
+
+ qDrawShadeLine( painter, sliderRect.left() + bw, pos,
+ sliderRect.right() - bw, pos,
+ palette(), true, 1 );
+ }
+}
+
+/*!
+ Map and round a value into widget coordinates
+ \param value Value
+*/
+int QwtSlider::transform( double value ) const
+{
+ return qRound( d_data->map.transform( value ) );
+}
+
+/*!
+ Determine the value corresponding to a specified mouse location.
+ \param pos Mouse position
+*/
+double QwtSlider::getValue( const QPoint &pos )
+{
+ return d_data->map.invTransform(
+ orientation() == Qt::Horizontal ? pos.x() : pos.y() );
+}
+
+/*!
+ \brief Determine scrolling mode and direction
+ \param p point
+ \param scrollMode Scrolling mode
+ \param direction Direction
+*/
+void QwtSlider::getScrollMode( const QPoint &p,
+ QwtAbstractSlider::ScrollMode &scrollMode, int &direction ) const
+{
+ if ( !d_data->sliderRect.contains( p ) )
+ {
+ scrollMode = QwtAbstractSlider::ScrNone;
+ direction = 0;
+ return;
+ }
+
+ const int pos = ( orientation() == Qt::Horizontal ) ? p.x() : p.y();
+ const int markerPos = transform( value() );
+
+ if ( ( pos > markerPos - d_data->handleSize.width() / 2 )
+ && ( pos < markerPos + d_data->handleSize.width() / 2 ) )
+ {
+ scrollMode = QwtAbstractSlider::ScrMouse;
+ direction = 0;
+ return;
+ }
+
+ scrollMode = QwtAbstractSlider::ScrPage;
+ direction = ( pos > markerPos ) ? 1 : -1;
+
+ if ( scaleDraw()->scaleMap().p1() > scaleDraw()->scaleMap().p2() )
+ direction = -direction;
+}
+
+/*!
+ Qt paint event
+ \param event Paint event
+*/
+void QwtSlider::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ if ( d_data->scalePos != QwtSlider::NoScale )
+ {
+ if ( !d_data->sliderRect.contains( event->rect() ) )
+ scaleDraw()->draw( &painter, palette() );
+ }
+
+ drawSlider( &painter, d_data->sliderRect );
+
+ if ( hasFocus() )
+ QwtPainter::drawFocusRect( &painter, this, d_data->sliderRect );
+}
+
+//! Qt resize event
+void QwtSlider::resizeEvent( QResizeEvent * )
+{
+ layoutSlider( false );
+}
+
+//! Qt change event handler
+void QwtSlider::changeEvent( QEvent *event )
+{
+ switch( event->type() )
+ {
+ case QEvent::StyleChange:
+ case QEvent::FontChange:
+ layoutSlider( true );
+ break;
+ default:
+ break;
+ }
+}
+
+/*!
+ Recalculate the slider's geometry and layout based on
+ the current rect and fonts.
+ \param update_geometry notify the layout system and call update
+ to redraw the scale
+*/
+void QwtSlider::layoutSlider( bool update_geometry )
+{
+ int handleThickness;
+ if ( orientation() == Qt::Horizontal )
+ handleThickness = d_data->handleSize.width();
+ else
+ handleThickness = d_data->handleSize.height();
+
+ int sld1 = handleThickness / 2 - 1;
+ int sld2 = handleThickness / 2 + handleThickness % 2;
+
+ if ( d_data->bgStyle & QwtSlider::Trough )
+ {
+ sld1 += d_data->borderWidth;
+ sld2 += d_data->borderWidth;
+ }
+
+ int scd = 0;
+ if ( d_data->scalePos != QwtSlider::NoScale )
+ {
+ int d1, d2;
+ scaleDraw()->getBorderDistHint( font(), d1, d2 );
+ scd = qMax( d1, d2 );
+ }
+
+ int slo = scd - sld1;
+ if ( slo < 0 )
+ slo = 0;
+
+ int x, y, length;
+ QRect sliderRect;
+
+ length = x = y = 0;
+
+ const QRect cr = contentsRect();
+ if ( orientation() == Qt::Horizontal )
+ {
+ int sh = d_data->handleSize.height();
+ if ( d_data->bgStyle & QwtSlider::Trough )
+ sh += 2 * d_data->borderWidth;
+
+ sliderRect.setLeft( cr.left() + slo );
+ sliderRect.setRight( cr.right() - slo );
+ sliderRect.setTop( cr.top() );
+ sliderRect.setBottom( cr.top() + sh - 1);
+
+ if ( d_data->scalePos == QwtSlider::BottomScale )
+ {
+ y = sliderRect.bottom() + d_data->spacing;
+ }
+ else if ( d_data->scalePos == QwtSlider::TopScale )
+ {
+ sliderRect.setTop( cr.bottom() - sh + 1 );
+ sliderRect.setBottom( cr.bottom() );
+
+ y = sliderRect.top() - d_data->spacing;
+ }
+
+ x = sliderRect.left() + sld1;
+ length = sliderRect.width() - ( sld1 + sld2 );
+ }
+ else // Qt::Vertical
+ {
+ int sw = d_data->handleSize.width();
+ if ( d_data->bgStyle & QwtSlider::Trough )
+ sw += 2 * d_data->borderWidth;
+
+ sliderRect.setLeft( cr.right() - sw + 1 );
+ sliderRect.setRight( cr.right() );
+ sliderRect.setTop( cr.top() + slo );
+ sliderRect.setBottom( cr.bottom() - slo );
+
+ if ( d_data->scalePos == QwtSlider::LeftScale )
+ {
+ x = sliderRect.left() - d_data->spacing;
+ }
+ else if ( d_data->scalePos == QwtSlider::RightScale )
+ {
+ sliderRect.setLeft( cr.left() );
+ sliderRect.setRight( cr.left() + sw - 1);
+
+ x = sliderRect.right() + d_data->spacing;
+ }
+
+ y = sliderRect.top() + sld1;
+ length = sliderRect.height() - ( sld1 + sld2 );
+ }
+
+ d_data->sliderRect = sliderRect;
+
+ scaleDraw()->move( x, y );
+ scaleDraw()->setLength( length );
+
+ d_data->map.setPaintInterval( scaleDraw()->scaleMap().p1(),
+ scaleDraw()->scaleMap().p2() );
+
+ if ( update_geometry )
+ {
+ d_data->sizeHintCache = QSize(); // invalidate
+ updateGeometry();
+ update();
+ }
+}
+
+//! Notify change of value
+void QwtSlider::valueChange()
+{
+ QwtAbstractSlider::valueChange();
+ update();
+}
+
+
+//! Notify change of range
+void QwtSlider::rangeChange()
+{
+ d_data->map.setScaleInterval( minValue(), maxValue() );
+
+ if ( autoScale() )
+ rescale( minValue(), maxValue() );
+
+ QwtAbstractSlider::rangeChange();
+ layoutSlider( true );
+}
+
+/*!
+ Set the background style.
+*/
+void QwtSlider::setBackgroundStyle( BackgroundStyles style )
+{
+ d_data->bgStyle = style;
+ layoutSlider( true );
+}
+
+/*!
+ \return the background style.
+*/
+QwtSlider::BackgroundStyles QwtSlider::backgroundStyle() const
+{
+ return d_data->bgStyle;
+}
+
+/*!
+ \return QwtSlider::minimumSizeHint()
+*/
+QSize QwtSlider::sizeHint() const
+{
+ const QSize hint = minimumSizeHint();
+ return hint.expandedTo( QApplication::globalStrut() );
+}
+
+/*!
+ \brief Return a minimum size hint
+ \warning The return value of QwtSlider::minimumSizeHint() depends on
+ the font and the scale.
+*/
+QSize QwtSlider::minimumSizeHint() const
+{
+ if ( !d_data->sizeHintCache.isEmpty() )
+ return d_data->sizeHintCache;
+
+ const int minLength = 84; // from QSlider
+
+ int handleLength = d_data->handleSize.width();
+ int handleThickness = d_data->handleSize.height();
+
+ if ( orientation() == Qt::Vertical )
+ qSwap( handleLength, handleThickness );
+
+ int w = minLength;
+ int h = handleThickness;
+
+ if ( d_data->scalePos != QwtSlider::NoScale )
+ {
+ int d1, d2;
+ scaleDraw()->getBorderDistHint( font(), d1, d2 );
+
+ const int sdBorderDist = 2 * qMax( d1, d2 );
+ const int sdExtent = qCeil( scaleDraw()->extent( font() ) );
+ const int sdLength = scaleDraw()->minLength( font() );
+
+ int l = sdLength;
+ if ( handleLength > sdBorderDist )
+ {
+ // We need additional space for the overlapping handle
+ l += handleLength - sdBorderDist;
+ }
+
+ w = qMax( l, w );
+ h += sdExtent + d_data->spacing;
+ }
+
+ if ( d_data->bgStyle & QwtSlider::Trough )
+ h += 2 * d_data->borderWidth;
+
+ if ( orientation() == Qt::Vertical )
+ qSwap( w, h );
+
+ int left, right, top, bottom;
+ getContentsMargins( &left, &top, &right, &bottom );
+
+ w += left + right;
+ h += top + bottom;
+
+ d_data->sizeHintCache = QSize( w, h );
+ return d_data->sizeHintCache;
+}
diff --git a/src/libpcp_qwt/src/qwt_slider.h b/src/libpcp_qwt/src/qwt_slider.h
new file mode 100644
index 0000000..bc39456
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_slider.h
@@ -0,0 +1,150 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SLIDER_H
+#define QWT_SLIDER_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_scale.h"
+#include "qwt_abstract_slider.h"
+
+class QwtScaleDraw;
+
+/*!
+ \brief The Slider Widget
+
+ QwtSlider is a slider widget which operates on an interval
+ of type double. QwtSlider supports different layouts as
+ well as a scale.
+
+ \image html sliders.png
+
+ \sa QwtAbstractSlider and QwtAbstractScale for the descriptions
+ of the inherited members.
+*/
+
+class QWT_EXPORT QwtSlider : public QwtAbstractSlider, public QwtAbstractScale
+{
+ Q_OBJECT
+ Q_ENUMS( ScalePos )
+ Q_ENUMS( BackgroundStyle )
+ Q_PROPERTY( ScalePos scalePosition READ scalePosition
+ WRITE setScalePosition )
+ Q_PROPERTY( BackgroundStyles backgroundStyle
+ READ backgroundStyle WRITE setBackgroundStyle )
+ Q_PROPERTY( QSize handleSize READ handleSize WRITE setHandleSize )
+ Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth )
+ Q_PROPERTY( int spacing READ spacing WRITE setSpacing )
+
+public:
+
+ /*!
+ Scale position. QwtSlider tries to enforce valid combinations of its
+ orientation and scale position:
+
+ - Qt::Horizonal combines with NoScale, TopScale and BottomScale
+ - Qt::Vertical combines with NoScale, LeftScale and RightScale
+
+ \sa QwtSlider()
+ */
+ enum ScalePos
+ {
+ //! The slider has no scale
+ NoScale,
+
+ //! The scale is left of the slider
+ LeftScale,
+
+ //! The scale is right of the slider
+ RightScale,
+
+ //! The scale is above of the slider
+ TopScale,
+
+ //! The scale is below of the slider
+ BottomScale
+ };
+
+ /*!
+ Background style.
+ \sa QwtSlider()
+ */
+ enum BackgroundStyle
+ {
+ //! Trough background
+ Trough = 0x01,
+
+ //! Groove
+ Groove = 0x02,
+ };
+
+ //! Background styles
+ typedef QFlags<BackgroundStyle> BackgroundStyles;
+
+ explicit QwtSlider( QWidget *parent,
+ Qt::Orientation = Qt::Horizontal,
+ ScalePos = NoScale, BackgroundStyles = Trough );
+
+ virtual ~QwtSlider();
+
+ virtual void setOrientation( Qt::Orientation );
+
+ void setBackgroundStyle( BackgroundStyles );
+ BackgroundStyles backgroundStyle() const;
+
+ void setScalePosition( ScalePos s );
+ ScalePos scalePosition() const;
+
+ void setHandleSize( int width, int height );
+ void setHandleSize( const QSize & );
+ QSize handleSize() const;
+
+ void setBorderWidth( int bw );
+ int borderWidth() const;
+
+ void setSpacing( int );
+ int spacing() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setScaleDraw( QwtScaleDraw * );
+ const QwtScaleDraw *scaleDraw() const;
+
+protected:
+ virtual double getValue( const QPoint &p );
+ virtual void getScrollMode( const QPoint &p,
+ QwtAbstractSlider::ScrollMode &, int &direction ) const;
+
+ virtual void drawSlider ( QPainter *, const QRect & ) const;
+ virtual void drawHandle( QPainter *, const QRect &, int pos ) const;
+
+ virtual void resizeEvent( QResizeEvent * );
+ virtual void paintEvent ( QPaintEvent * );
+ virtual void changeEvent( QEvent * );
+
+ virtual void valueChange();
+ virtual void rangeChange();
+ virtual void scaleChange();
+
+ int transform( double v ) const;
+
+ QwtScaleDraw *scaleDraw();
+
+private:
+ void layoutSlider( bool );
+ void initSlider( Qt::Orientation, ScalePos, BackgroundStyles );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtSlider::BackgroundStyles )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_spline.cpp b/src/libpcp_qwt/src/qwt_spline.cpp
new file mode 100644
index 0000000..5c1e13f
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_spline.cpp
@@ -0,0 +1,380 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_spline.h"
+#include "qwt_math.h"
+
+class QwtSpline::PrivateData
+{
+public:
+ PrivateData():
+ splineType( QwtSpline::Natural )
+ {
+ }
+
+ QwtSpline::SplineType splineType;
+
+ // coefficient vectors
+ QVector<double> a;
+ QVector<double> b;
+ QVector<double> c;
+
+ // control points
+ QPolygonF points;
+};
+
+static int lookup( double x, const QPolygonF &values )
+{
+#if 0
+//qLowerBound/qHigherBound ???
+#endif
+ int i1;
+ const int size = values.size();
+
+ if ( x <= values[0].x() )
+ i1 = 0;
+ else if ( x >= values[size - 2].x() )
+ i1 = size - 2;
+ else
+ {
+ i1 = 0;
+ int i2 = size - 2;
+ int i3 = 0;
+
+ while ( i2 - i1 > 1 )
+ {
+ i3 = i1 + ( ( i2 - i1 ) >> 1 );
+
+ if ( values[i3].x() > x )
+ i2 = i3;
+ else
+ i1 = i3;
+ }
+ }
+ return i1;
+}
+
+//! Constructor
+QwtSpline::QwtSpline()
+{
+ d_data = new PrivateData;
+}
+
+/*!
+ Copy constructor
+ \param other Spline used for initilization
+*/
+QwtSpline::QwtSpline( const QwtSpline& other )
+{
+ d_data = new PrivateData( *other.d_data );
+}
+
+/*!
+ Assignment operator
+ \param other Spline used for initilization
+*/
+QwtSpline &QwtSpline::operator=( const QwtSpline & other )
+{
+ *d_data = *other.d_data;
+ return *this;
+}
+
+//! Destructor
+QwtSpline::~QwtSpline()
+{
+ delete d_data;
+}
+
+/*!
+ Select the algorithm used for calculating the spline
+
+ \param splineType Spline type
+ \sa splineType()
+*/
+void QwtSpline::setSplineType( SplineType splineType )
+{
+ d_data->splineType = splineType;
+}
+
+/*!
+ \return the spline type
+ \sa setSplineType()
+*/
+QwtSpline::SplineType QwtSpline::splineType() const
+{
+ return d_data->splineType;
+}
+
+/*!
+ \brief Calculate the spline coefficients
+
+ Depending on the value of \a periodic, this function
+ will determine the coefficients for a natural or a periodic
+ spline and store them internally.
+
+ \param points Points
+ \return true if successful
+ \warning The sequence of x (but not y) values has to be strictly monotone
+ increasing, which means <code>points[i].x() < points[i+1].x()</code>.
+ If this is not the case, the function will return false
+*/
+bool QwtSpline::setPoints( const QPolygonF& points )
+{
+ const int size = points.size();
+ if ( size <= 2 )
+ {
+ reset();
+ return false;
+ }
+
+ d_data->points = points;
+
+ d_data->a.resize( size - 1 );
+ d_data->b.resize( size - 1 );
+ d_data->c.resize( size - 1 );
+
+ bool ok;
+ if ( d_data->splineType == Periodic )
+ ok = buildPeriodicSpline( points );
+ else
+ ok = buildNaturalSpline( points );
+
+ if ( !ok )
+ reset();
+
+ return ok;
+}
+
+/*!
+ Return points passed by setPoints
+*/
+QPolygonF QwtSpline::points() const
+{
+ return d_data->points;
+}
+
+//! \return A coefficients
+const QVector<double> &QwtSpline::coefficientsA() const
+{
+ return d_data->a;
+}
+
+//! \return B coefficients
+const QVector<double> &QwtSpline::coefficientsB() const
+{
+ return d_data->b;
+}
+
+//! \return C coefficients
+const QVector<double> &QwtSpline::coefficientsC() const
+{
+ return d_data->c;
+}
+
+
+//! Free allocated memory and set size to 0
+void QwtSpline::reset()
+{
+ d_data->a.resize( 0 );
+ d_data->b.resize( 0 );
+ d_data->c.resize( 0 );
+ d_data->points.resize( 0 );
+}
+
+//! True if valid
+bool QwtSpline::isValid() const
+{
+ return d_data->a.size() > 0;
+}
+
+/*!
+ Calculate the interpolated function value corresponding
+ to a given argument x.
+*/
+double QwtSpline::value( double x ) const
+{
+ if ( d_data->a.size() == 0 )
+ return 0.0;
+
+ const int i = lookup( x, d_data->points );
+
+ const double delta = x - d_data->points[i].x();
+ return( ( ( ( d_data->a[i] * delta ) + d_data->b[i] )
+ * delta + d_data->c[i] ) * delta + d_data->points[i].y() );
+}
+
+/*!
+ \brief Determines the coefficients for a natural spline
+ \return true if successful
+*/
+bool QwtSpline::buildNaturalSpline( const QPolygonF &points )
+{
+ int i;
+
+ const QPointF *p = points.data();
+ const int size = points.size();
+
+ double *a = d_data->a.data();
+ double *b = d_data->b.data();
+ double *c = d_data->c.data();
+
+ // set up tridiagonal equation system; use coefficient
+ // vectors as temporary buffers
+ QVector<double> h( size - 1 );
+ for ( i = 0; i < size - 1; i++ )
+ {
+ h[i] = p[i+1].x() - p[i].x();
+ if ( h[i] <= 0 )
+ return false;
+ }
+
+ QVector<double> d( size - 1 );
+ double dy1 = ( p[1].y() - p[0].y() ) / h[0];
+ for ( i = 1; i < size - 1; i++ )
+ {
+ b[i] = c[i] = h[i];
+ a[i] = 2.0 * ( h[i-1] + h[i] );
+
+ const double dy2 = ( p[i+1].y() - p[i].y() ) / h[i];
+ d[i] = 6.0 * ( dy1 - dy2 );
+ dy1 = dy2;
+ }
+
+ //
+ // solve it
+ //
+
+ // L-U Factorization
+ for ( i = 1; i < size - 2; i++ )
+ {
+ c[i] /= a[i];
+ a[i+1] -= b[i] * c[i];
+ }
+
+ // forward elimination
+ QVector<double> s( size );
+ s[1] = d[1];
+ for ( i = 2; i < size - 1; i++ )
+ s[i] = d[i] - c[i-1] * s[i-1];
+
+ // backward elimination
+ s[size - 2] = - s[size - 2] / a[size - 2];
+ for ( i = size - 3; i > 0; i-- )
+ s[i] = - ( s[i] + b[i] * s[i+1] ) / a[i];
+ s[size - 1] = s[0] = 0.0;
+
+ //
+ // Finally, determine the spline coefficients
+ //
+ for ( i = 0; i < size - 1; i++ )
+ {
+ a[i] = ( s[i+1] - s[i] ) / ( 6.0 * h[i] );
+ b[i] = 0.5 * s[i];
+ c[i] = ( p[i+1].y() - p[i].y() ) / h[i]
+ - ( s[i+1] + 2.0 * s[i] ) * h[i] / 6.0;
+ }
+
+ return true;
+}
+
+/*!
+ \brief Determines the coefficients for a periodic spline
+ \return true if successful
+*/
+bool QwtSpline::buildPeriodicSpline( const QPolygonF &points )
+{
+ int i;
+
+ const QPointF *p = points.data();
+ const int size = points.size();
+
+ double *a = d_data->a.data();
+ double *b = d_data->b.data();
+ double *c = d_data->c.data();
+
+ QVector<double> d( size - 1 );
+ QVector<double> h( size - 1 );
+ QVector<double> s( size );
+
+ //
+ // setup equation system; use coefficient
+ // vectors as temporary buffers
+ //
+ for ( i = 0; i < size - 1; i++ )
+ {
+ h[i] = p[i+1].x() - p[i].x();
+ if ( h[i] <= 0.0 )
+ return false;
+ }
+
+ const int imax = size - 2;
+ double htmp = h[imax];
+ double dy1 = ( p[0].y() - p[imax].y() ) / htmp;
+ for ( i = 0; i <= imax; i++ )
+ {
+ b[i] = c[i] = h[i];
+ a[i] = 2.0 * ( htmp + h[i] );
+ const double dy2 = ( p[i+1].y() - p[i].y() ) / h[i];
+ d[i] = 6.0 * ( dy1 - dy2 );
+ dy1 = dy2;
+ htmp = h[i];
+ }
+
+ //
+ // solve it
+ //
+
+ // L-U Factorization
+ a[0] = qSqrt( a[0] );
+ c[0] = h[imax] / a[0];
+ double sum = 0;
+
+ for ( i = 0; i < imax - 1; i++ )
+ {
+ b[i] /= a[i];
+ if ( i > 0 )
+ c[i] = - c[i-1] * b[i-1] / a[i];
+ a[i+1] = qSqrt( a[i+1] - qwtSqr( b[i] ) );
+ sum += qwtSqr( c[i] );
+ }
+ b[imax-1] = ( b[imax-1] - c[imax-2] * b[imax-2] ) / a[imax-1];
+ a[imax] = qSqrt( a[imax] - qwtSqr( b[imax-1] ) - sum );
+
+
+ // forward elimination
+ s[0] = d[0] / a[0];
+ sum = 0;
+ for ( i = 1; i < imax; i++ )
+ {
+ s[i] = ( d[i] - b[i-1] * s[i-1] ) / a[i];
+ sum += c[i-1] * s[i-1];
+ }
+ s[imax] = ( d[imax] - b[imax-1] * s[imax-1] - sum ) / a[imax];
+
+
+ // backward elimination
+ s[imax] = - s[imax] / a[imax];
+ s[imax-1] = -( s[imax-1] + b[imax-1] * s[imax] ) / a[imax-1];
+ for ( i = imax - 2; i >= 0; i-- )
+ s[i] = - ( s[i] + b[i] * s[i+1] + c[i] * s[imax] ) / a[i];
+
+ //
+ // Finally, determine the spline coefficients
+ //
+ s[size-1] = s[0];
+ for ( i = 0; i < size - 1; i++ )
+ {
+ a[i] = ( s[i+1] - s[i] ) / ( 6.0 * h[i] );
+ b[i] = 0.5 * s[i];
+ c[i] = ( p[i+1].y() - p[i].y() )
+ / h[i] - ( s[i+1] + 2.0 * s[i] ) * h[i] / 6.0;
+ }
+
+ return true;
+}
diff --git a/src/libpcp_qwt/src/qwt_spline.h b/src/libpcp_qwt/src/qwt_spline.h
new file mode 100644
index 0000000..802e1da
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_spline.h
@@ -0,0 +1,101 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SPLINE_H
+#define QWT_SPLINE_H
+
+#include "qwt_global.h"
+#include <qpolygon.h>
+#include <qvector.h>
+
+/*!
+ \brief A class for spline interpolation
+
+ The QwtSpline class is used for cubical spline interpolation.
+ Two types of splines, natural and periodic, are supported.
+
+ \par Usage:
+ <ol>
+ <li>First call setPoints() to determine the spline coefficients
+ for a tabulated function y(x).
+ <li>After the coefficients have been set up, the interpolated
+ function value for an argument x can be determined by calling
+ QwtSpline::value().
+ </ol>
+
+ \par Example:
+ \code
+#include <qwt_spline.h>
+
+QPolygonF interpolate(const QPolygonF& points, int numValues)
+{
+ QwtSpline spline;
+ if ( !spline.setPoints(points) )
+ return points;
+
+ QPolygonF interpolatedPoints(numValues);
+
+ const double delta =
+ (points[numPoints - 1].x() - points[0].x()) / (points.size() - 1);
+ for(i = 0; i < points.size(); i++) / interpolate
+ {
+ const double x = points[0].x() + i * delta;
+ interpolatedPoints[i].setX(x);
+ interpolatedPoints[i].setY(spline.value(x));
+ }
+ return interpolatedPoints;
+}
+ \endcode
+*/
+
+class QWT_EXPORT QwtSpline
+{
+public:
+ //! Spline type
+ enum SplineType
+ {
+ //! A natural spline
+ Natural,
+
+ //! A periodic spline
+ Periodic
+ };
+
+ QwtSpline();
+ QwtSpline( const QwtSpline & );
+
+ ~QwtSpline();
+
+ QwtSpline &operator=( const QwtSpline & );
+
+ void setSplineType( SplineType );
+ SplineType splineType() const;
+
+ bool setPoints( const QPolygonF& points );
+ QPolygonF points() const;
+
+ void reset();
+
+ bool isValid() const;
+ double value( double x ) const;
+
+ const QVector<double> &coefficientsA() const;
+ const QVector<double> &coefficientsB() const;
+ const QVector<double> &coefficientsC() const;
+
+protected:
+ bool buildNaturalSpline( const QPolygonF & );
+ bool buildPeriodicSpline( const QPolygonF & );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_symbol.cpp b/src/libpcp_qwt/src/qwt_symbol.cpp
new file mode 100644
index 0000000..0966814
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_symbol.cpp
@@ -0,0 +1,1006 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_symbol.h"
+#include "qwt_painter.h"
+#include <qapplication.h>
+#include <qpainter.h>
+#include <qmath.h>
+
+namespace QwtTriangle
+{
+ enum Type
+ {
+ Left,
+ Right,
+ Up,
+ Down
+ };
+}
+
+static inline void qwtDrawEllipseSymbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ painter->setBrush( symbol.brush() );
+ painter->setPen( symbol.pen() );
+
+ const QSize size = symbol.size();
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ const int sw = size.width();
+ const int sh = size.height();
+ const int sw2 = size.width() / 2;
+ const int sh2 = size.height() / 2;
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const int x = qRound( points[i].x() );
+ const int y = qRound( points[i].y() );
+
+ const QRectF r( x - sw2, y - sh2, sw, sh );
+ QwtPainter::drawEllipse( painter, r );
+ }
+ }
+ else
+ {
+ const double sw = size.width();
+ const double sh = size.height();
+ const double sw2 = 0.5 * size.width();
+ const double sh2 = 0.5 * size.height();
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const double x = points[i].x();
+ const double y = points[i].y();
+
+ const QRectF r( x - sw2, y - sh2, sw, sh );
+ QwtPainter::drawEllipse( painter, r );
+ }
+ }
+}
+
+static inline void qwtDrawRectSymbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+
+ QPen pen = symbol.pen();
+ pen.setJoinStyle( Qt::MiterJoin );
+ painter->setPen( pen );
+ painter->setBrush( symbol.brush() );
+ painter->setRenderHint( QPainter::Antialiasing, false );
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ const int sw = size.width();
+ const int sh = size.height();
+ const int sw2 = size.width() / 2;
+ const int sh2 = size.height() / 2;
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const int x = qRound( points[i].x() );
+ const int y = qRound( points[i].y() );
+
+ const QRect r( x - sw2, y - sh2, sw, sh );
+ QwtPainter::drawRect( painter, r );
+ }
+ }
+ else
+ {
+ const double sw = size.width();
+ const double sh = size.height();
+ const double sw2 = 0.5 * size.width();
+ const double sh2 = 0.5 * size.height();
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const double x = points[i].x();
+ const double y = points[i].y();
+
+ const QRectF r( x - sw2, y - sh2, sw, sh );
+ QwtPainter::drawRect( painter, r );
+ }
+ }
+}
+
+static inline void qwtDrawDiamondSymbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+
+ QPen pen = symbol.pen();
+ pen.setJoinStyle( Qt::MiterJoin );
+ painter->setPen( pen );
+ painter->setBrush( symbol.brush() );
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const int x = qRound( points[i].x() );
+ const int y = qRound( points[i].y() );
+
+ const int x1 = x - size.width() / 2;
+ const int y1 = y - size.height() / 2;
+ const int x2 = x1 + size.width();
+ const int y2 = y1 + size.height();
+
+ QPolygonF polygon;
+ polygon += QPointF( x, y1 );
+ polygon += QPointF( x1, y );
+ polygon += QPointF( x, y2 );
+ polygon += QPointF( x2, y );
+
+ QwtPainter::drawPolygon( painter, polygon );
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const QPointF &pos = points[i];
+
+ const double x1 = pos.x() - 0.5 * size.width();
+ const double y1 = pos.y() - 0.5 * size.height();
+ const double x2 = x1 + size.width();
+ const double y2 = y1 + size.height();
+
+ QPolygonF polygon;
+ polygon += QPointF( pos.x(), y1 );
+ polygon += QPointF( x2, pos.y() );
+ polygon += QPointF( pos.x(), y2 );
+ polygon += QPointF( x1, pos.y() );
+
+ QwtPainter::drawPolygon( painter, polygon );
+ }
+ }
+}
+
+static inline void qwtDrawTriangleSymbols(
+ QPainter *painter, QwtTriangle::Type type,
+ const QPointF *points, int numPoints,
+ const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+
+ QPen pen = symbol.pen();
+ pen.setJoinStyle( Qt::MiterJoin );
+ painter->setPen( pen );
+
+ painter->setBrush( symbol.brush() );
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ double sw2 = 0.5 * size.width();
+ double sh2 = 0.5 * size.height();
+
+ if ( doAlign )
+ {
+ sw2 = qFloor( sw2 );
+ sh2 = qFloor( sh2 );
+ }
+
+ QPolygonF triangle( 3 );
+ QPointF *trianglePoints = triangle.data();
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const QPointF &pos = points[i];
+
+ double x = pos.x();
+ double y = pos.y();
+
+ if ( doAlign )
+ {
+ x = qRound( x );
+ y = qRound( y );
+ }
+
+ const double x1 = x - sw2;
+ const double x2 = x1 + size.width();
+ const double y1 = y - sh2;
+ const double y2 = y1 + size.height();
+
+ switch ( type )
+ {
+ case QwtTriangle::Left:
+ {
+ trianglePoints[0].rx() = x2;
+ trianglePoints[0].ry() = y1;
+
+ trianglePoints[1].rx() = x1;
+ trianglePoints[1].ry() = y;
+
+ trianglePoints[2].rx() = x2;
+ trianglePoints[2].ry() = y2;
+
+ break;
+ }
+ case QwtTriangle::Right:
+ {
+ trianglePoints[0].rx() = x1;
+ trianglePoints[0].ry() = y1;
+
+ trianglePoints[1].rx() = x2;
+ trianglePoints[1].ry() = y;
+
+ trianglePoints[2].rx() = x1;
+ trianglePoints[2].ry() = y2;
+
+ break;
+ }
+ case QwtTriangle::Up:
+ {
+ trianglePoints[0].rx() = x1;
+ trianglePoints[0].ry() = y2;
+
+ trianglePoints[1].rx() = x;
+ trianglePoints[1].ry() = y1;
+
+ trianglePoints[2].rx() = x2;
+ trianglePoints[2].ry() = y2;
+
+ break;
+ }
+ case QwtTriangle::Down:
+ {
+ trianglePoints[0].rx() = x1;
+ trianglePoints[0].ry() = y1;
+
+ trianglePoints[1].rx() = x;
+ trianglePoints[1].ry() = y2;
+
+ trianglePoints[2].rx() = x2;
+ trianglePoints[2].ry() = y1;
+
+ break;
+ }
+ }
+ QwtPainter::drawPolygon( painter, triangle );
+ }
+}
+
+static inline void qwtDrawLineSymbols(
+ QPainter *painter, int orientations,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+
+ int off = 0;
+
+ QPen pen = symbol.pen();
+ if ( pen.width() > 1 )
+ {
+ pen.setCapStyle( Qt::FlatCap );
+ off = 1;
+ }
+
+ painter->setPen( pen );
+ painter->setRenderHint( QPainter::Antialiasing, false );
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ const int sw = qFloor( size.width() );
+ const int sh = qFloor( size.height() );
+ const int sw2 = size.width() / 2;
+ const int sh2 = size.height() / 2;
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ if ( orientations & Qt::Horizontal )
+ {
+ const int x = qRound( points[i].x() ) - sw2;
+ const int y = qRound( points[i].y() );
+
+ QwtPainter::drawLine( painter, x, y, x + sw + off, y );
+ }
+ if ( orientations & Qt::Vertical )
+ {
+ const int x = qRound( points[i].x() );
+ const int y = qRound( points[i].y() ) - sh2;
+
+ QwtPainter::drawLine( painter, x, y, x, y + sh + off );
+ }
+ }
+ }
+ else
+ {
+ const double sw = size.width();
+ const double sh = size.height();
+ const double sw2 = 0.5 * size.width();
+ const double sh2 = 0.5 * size.height();
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ if ( orientations & Qt::Horizontal )
+ {
+ const double x = points[i].x() - sw2;
+ const double y = points[i].y();
+
+ QwtPainter::drawLine( painter, x, y, x + sw, y );
+ }
+ if ( orientations & Qt::Vertical )
+ {
+ const double y = points[i].y() - sh2;
+ const double x = points[i].x();
+
+ QwtPainter::drawLine( painter, x, y, x, y + sh );
+ }
+ }
+ }
+}
+
+static inline void qwtDrawXCrossSymbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+ int off = 0;
+
+ QPen pen = symbol.pen();
+ if ( pen.width() > 1 )
+ {
+ pen.setCapStyle( Qt::FlatCap );
+ off = 1;
+ }
+ painter->setPen( pen );
+
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ const int sw = size.width();
+ const int sh = size.height();
+ const int sw2 = size.width() / 2;
+ const int sh2 = size.height() / 2;
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const QPointF &pos = points[i];
+
+ const int x = qRound( pos.x() );
+ const int y = qRound( pos.y() );
+
+ const int x1 = x - sw2;
+ const int x2 = x1 + sw + off;
+ const int y1 = y - sh2;
+ const int y2 = y1 + sh + off;
+
+ QwtPainter::drawLine( painter, x1, y1, x2, y2 );
+ QwtPainter::drawLine( painter, x2, y1, x1, y2 );
+ }
+ }
+ else
+ {
+ const double sw = size.width();
+ const double sh = size.height();
+ const double sw2 = 0.5 * size.width();
+ const double sh2 = 0.5 * size.height();
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ const QPointF &pos = points[i];
+
+ const double x1 = pos.x() - sw2;
+ const double x2 = x1 + sw;
+ const double y1 = pos.y() - sh2;
+ const double y2 = y1 + sh;
+
+ QwtPainter::drawLine( painter, x1, y1, x2, y2 );
+ QwtPainter::drawLine( painter, x1, y2, x2, y1 );
+ }
+ }
+}
+
+static inline void qwtDrawStar1Symbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ const QSize size = symbol.size();
+ painter->setPen( symbol.pen() );
+
+ if ( QwtPainter::roundingAlignment( painter ) )
+ {
+ QRect r( 0, 0, size.width(), size.height() );
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ r.moveCenter( points[i].toPoint() );
+
+ const double sqrt1_2 = 0.70710678118654752440; /* 1/sqrt(2) */
+
+ const double d1 = r.width() / 2.0 * ( 1.0 - sqrt1_2 );
+
+ QwtPainter::drawLine( painter,
+ qRound( r.left() + d1 ), qRound( r.top() + d1 ),
+ qRound( r.right() - d1 ), qRound( r.bottom() - d1 ) );
+ QwtPainter::drawLine( painter,
+ qRound( r.left() + d1 ), qRound( r.bottom() - d1 ),
+ qRound( r .right() - d1), qRound( r.top() + d1 ) );
+
+ const QPoint c = r.center();
+
+ QwtPainter::drawLine( painter,
+ c.x(), r.top(), c.x(), r.bottom() );
+ QwtPainter::drawLine( painter,
+ r.left(), c.y(), r.right(), c.y() );
+ }
+ }
+ else
+ {
+ QRectF r( 0, 0, size.width(), size.height() );
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ r.moveCenter( points[i] );
+
+ const double sqrt1_2 = 0.70710678118654752440; /* 1/sqrt(2) */
+
+ const QPointF c = r.center();
+ const double d1 = r.width() / 2.0 * ( 1.0 - sqrt1_2 );
+
+ QwtPainter::drawLine( painter,
+ r.left() + d1, r.top() + d1,
+ r.right() - d1, r.bottom() - d1 );
+ QwtPainter::drawLine( painter,
+ r.left() + d1, r.bottom() - d1,
+ r.right() - d1, r.top() + d1 );
+ QwtPainter::drawLine( painter,
+ c.x(), r.top(),
+ c.x(), r.bottom() );
+ QwtPainter::drawLine( painter,
+ r.left(), c.y(),
+ r.right(), c.y() );
+ }
+ }
+}
+
+static inline void qwtDrawStar2Symbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ QPen pen = symbol.pen();
+ if ( pen.width() > 1 )
+ pen.setCapStyle( Qt::FlatCap );
+ pen.setJoinStyle( Qt::MiterJoin );
+ painter->setPen( pen );
+
+ painter->setBrush( symbol.brush() );
+
+ const double cos30 = 0.866025; // cos(30°)
+
+ const double dy = 0.25 * symbol.size().height();
+ const double dx = 0.5 * symbol.size().width() * cos30 / 3.0;
+
+ QPolygonF star( 12 );
+ QPointF *starPoints = star.data();
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ double x = points[i].x();
+ double y = points[i].y();
+ if ( doAlign )
+ {
+ x = qRound( x );
+ y = qRound( y );
+ }
+
+ double x1 = x - 3 * dx;
+ double y1 = y - 2 * dy;
+ if ( doAlign )
+ {
+ x1 = qRound( x - 3 * dx );
+ y1 = qRound( y - 2 * dy );
+ }
+
+ const double x2 = x1 + 1 * dx;
+ const double x3 = x1 + 2 * dx;
+ const double x4 = x1 + 3 * dx;
+ const double x5 = x1 + 4 * dx;
+ const double x6 = x1 + 5 * dx;
+ const double x7 = x1 + 6 * dx;
+
+ const double y2 = y1 + 1 * dy;
+ const double y3 = y1 + 2 * dy;
+ const double y4 = y1 + 3 * dy;
+ const double y5 = y1 + 4 * dy;
+
+ starPoints[0].rx() = x4;
+ starPoints[0].ry() = y1;
+
+ starPoints[1].rx() = x5;
+ starPoints[1].ry() = y2;
+
+ starPoints[2].rx() = x7;
+ starPoints[2].ry() = y2;
+
+ starPoints[3].rx() = x6;
+ starPoints[3].ry() = y3;
+
+ starPoints[4].rx() = x7;
+ starPoints[4].ry() = y4;
+
+ starPoints[5].rx() = x5;
+ starPoints[5].ry() = y4;
+
+ starPoints[6].rx() = x4;
+ starPoints[6].ry() = y5;
+
+ starPoints[7].rx() = x3;
+ starPoints[7].ry() = y4;
+
+ starPoints[8].rx() = x1;
+ starPoints[8].ry() = y4;
+
+ starPoints[9].rx() = x2;
+ starPoints[9].ry() = y3;
+
+ starPoints[10].rx() = x1;
+ starPoints[10].ry() = y2;
+
+ starPoints[11].rx() = x3;
+ starPoints[11].ry() = y2;
+
+ QwtPainter::drawPolygon( painter, star );
+ }
+}
+
+static inline void qwtDrawHexagonSymbols( QPainter *painter,
+ const QPointF *points, int numPoints, const QwtSymbol &symbol )
+{
+ painter->setBrush( symbol.brush() );
+ painter->setPen( symbol.pen() );
+
+ const double cos30 = 0.866025; // cos(30°)
+ const double dx = 0.5 * ( symbol.size().width() - cos30 );
+
+ const double dy = 0.25 * symbol.size().height();
+
+ QPolygonF hexaPolygon( 6 );
+ QPointF *hexaPoints = hexaPolygon.data();
+
+ const bool doAlign = QwtPainter::roundingAlignment( painter );
+
+ for ( int i = 0; i < numPoints; i++ )
+ {
+ double x = points[i].x();
+ double y = points[i].y();
+ if ( doAlign )
+ {
+ x = qRound( x );
+ y = qRound( y );
+ }
+
+ double x1 = x - dx;
+ double y1 = y - 2 * dy;
+ if ( doAlign )
+ {
+ x1 = qCeil( x1 );
+ y1 = qCeil( y1 );
+ }
+
+ const double x2 = x1 + 1 * dx;
+ const double x3 = x1 + 2 * dx;
+
+ const double y2 = y1 + 1 * dy;
+ const double y3 = y1 + 3 * dy;
+ const double y4 = y1 + 4 * dy;
+
+ hexaPoints[0].rx() = x2;
+ hexaPoints[0].ry() = y1;
+
+ hexaPoints[1].rx() = x3;
+ hexaPoints[1].ry() = y2;
+
+ hexaPoints[2].rx() = x3;
+ hexaPoints[2].ry() = y3;
+
+ hexaPoints[3].rx() = x2;
+ hexaPoints[3].ry() = y4;
+
+ hexaPoints[4].rx() = x1;
+ hexaPoints[4].ry() = y3;
+
+ hexaPoints[5].rx() = x1;
+ hexaPoints[5].ry() = y2;
+
+ QwtPainter::drawPolygon( painter, hexaPolygon );
+ }
+}
+
+class QwtSymbol::PrivateData
+{
+public:
+ PrivateData( QwtSymbol::Style st, const QBrush &br,
+ const QPen &pn, const QSize &sz ):
+ style( st ),
+ size( sz ),
+ brush( br ),
+ pen( pn )
+ {
+ }
+
+ bool operator==( const PrivateData &other ) const
+ {
+ return ( style == other.style )
+ && ( size == other.size )
+ && ( brush == other.brush )
+ && ( pen == other.pen );
+ }
+
+
+ Style style;
+ QSize size;
+ QBrush brush;
+ QPen pen;
+};
+
+/*!
+ Default Constructor
+ \param style Symbol Style
+
+ The symbol is constructed with gray interior,
+ black outline with zero width, no size and style 'NoSymbol'.
+*/
+QwtSymbol::QwtSymbol( Style style )
+{
+ d_data = new PrivateData( style, QBrush( Qt::gray ),
+ QPen( Qt::black ), QSize( 0.0, 0.0 ) );
+}
+
+/*!
+ \brief Constructor
+ \param style Symbol Style
+ \param brush brush to fill the interior
+ \param pen outline pen
+ \param size size
+
+ \sa setStyle(), setBrush(), setPen(), setSize()
+*/
+QwtSymbol::QwtSymbol( QwtSymbol::Style style, const QBrush &brush,
+ const QPen &pen, const QSize &size )
+{
+ d_data = new PrivateData( style, brush, pen, size );
+}
+
+/*!
+ \brief Copy constructor
+
+ \param other Symbol
+*/
+QwtSymbol::QwtSymbol( const QwtSymbol &other )
+{
+ d_data = new PrivateData( other.style(), other.brush(),
+ other.pen(), other.size() );
+};
+
+//! Destructor
+QwtSymbol::~QwtSymbol()
+{
+ delete d_data;
+}
+
+//! \brief Assignment operator
+QwtSymbol &QwtSymbol::operator=( const QwtSymbol &other )
+{
+ *d_data = *other.d_data;
+ return *this;
+}
+
+//! \brief Compare two symbols
+bool QwtSymbol::operator==( const QwtSymbol &other ) const
+{
+ return *d_data == *other.d_data;
+}
+
+//! \brief Compare two symbols
+bool QwtSymbol::operator!=( const QwtSymbol &other ) const
+{
+ return !( *d_data == *other.d_data );
+}
+
+/*!
+ \brief Specify the symbol's size
+
+ If the 'h' parameter is left out or less than 0,
+ and the 'w' parameter is greater than or equal to 0,
+ the symbol size will be set to (w,w).
+ \param width Width
+ \param height Height (defaults to -1)
+
+ \sa size()
+*/
+void QwtSymbol::setSize( int width, int height )
+{
+ if ( ( width >= 0 ) && ( height < 0 ) )
+ height = width;
+
+ d_data->size = QSize( width, height );
+}
+
+/*!
+ Set the symbol's size
+ \param size Size
+
+ \sa size()
+*/
+void QwtSymbol::setSize( const QSize &size )
+{
+ if ( size.isValid() )
+ d_data->size = size;
+}
+
+/*!
+ \return Size
+ \sa setSize()
+*/
+const QSize& QwtSymbol::size() const
+{
+ return d_data->size;
+}
+
+/*!
+ \brief Assign a brush
+
+ The brush is used to draw the interior of the symbol.
+ \param brush Brush
+
+ \sa brush()
+*/
+void QwtSymbol::setBrush( const QBrush &brush )
+{
+ d_data->brush = brush;
+}
+
+/*!
+ \return Brush
+ \sa setBrush()
+*/
+const QBrush& QwtSymbol::brush() const
+{
+ return d_data->brush;
+}
+
+/*!
+ Assign a pen
+
+ The pen is used to draw the symbol's outline.
+
+ \param pen Pen
+ \sa pen(), setBrush()
+*/
+void QwtSymbol::setPen( const QPen &pen )
+{
+ d_data->pen = pen;
+}
+
+/*!
+ \return Pen
+ \sa setPen(), brush()
+*/
+const QPen& QwtSymbol::pen() const
+{
+ return d_data->pen;
+}
+
+/*!
+ \brief Set the color of the symbol
+
+ Change the color of the brush for symbol types with a filled area.
+ For all other symbol types the color will be assigned to the pen.
+
+ \param color Color
+
+ \sa setBrush(), setPen(), brush(), pen()
+*/
+void QwtSymbol::setColor( const QColor &color )
+{
+ switch ( d_data->style )
+ {
+ case QwtSymbol::Ellipse:
+ case QwtSymbol::Rect:
+ case QwtSymbol::Diamond:
+ case QwtSymbol::Triangle:
+ case QwtSymbol::UTriangle:
+ case QwtSymbol::DTriangle:
+ case QwtSymbol::RTriangle:
+ case QwtSymbol::LTriangle:
+ case QwtSymbol::Star2:
+ case QwtSymbol::Hexagon:
+ {
+ d_data->brush.setColor( color );
+ break;
+ }
+ case QwtSymbol::Cross:
+ case QwtSymbol::XCross:
+ case QwtSymbol::HLine:
+ case QwtSymbol::VLine:
+ case QwtSymbol::Star1:
+ {
+ d_data->pen.setColor( color );
+ break;
+ }
+ default:
+ {
+ d_data->brush.setColor( color );
+ d_data->pen.setColor( color );
+ }
+ }
+}
+
+/*!
+ Draw an array of symbols
+
+ Painting several symbols is more effective than drawing symbols
+ one by one, as a couple of layout calculations and setting of pen/brush
+ can be done once for the complete array.
+
+ \param painter Painter
+ \param points Array of points
+ \param numPoints Number of points
+*/
+void QwtSymbol::drawSymbols( QPainter *painter,
+ const QPointF *points, int numPoints ) const
+{
+ if ( numPoints <= 0 )
+ return;
+
+ painter->save();
+
+ switch ( d_data->style )
+ {
+ case QwtSymbol::Ellipse:
+ {
+ qwtDrawEllipseSymbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Rect:
+ {
+ qwtDrawRectSymbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Diamond:
+ {
+ qwtDrawDiamondSymbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Cross:
+ {
+ qwtDrawLineSymbols( painter, Qt::Horizontal | Qt::Vertical,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::XCross:
+ {
+ qwtDrawXCrossSymbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Triangle:
+ case QwtSymbol::UTriangle:
+ {
+ qwtDrawTriangleSymbols( painter, QwtTriangle::Up,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::DTriangle:
+ {
+ qwtDrawTriangleSymbols( painter, QwtTriangle::Down,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::RTriangle:
+ {
+ qwtDrawTriangleSymbols( painter, QwtTriangle::Right,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::LTriangle:
+ {
+ qwtDrawTriangleSymbols( painter, QwtTriangle::Left,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::HLine:
+ {
+ qwtDrawLineSymbols( painter, Qt::Horizontal,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::VLine:
+ {
+ qwtDrawLineSymbols( painter, Qt::Vertical,
+ points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Star1:
+ {
+ qwtDrawStar1Symbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Star2:
+ {
+ qwtDrawStar2Symbols( painter, points, numPoints, *this );
+ break;
+ }
+ case QwtSymbol::Hexagon:
+ {
+ qwtDrawHexagonSymbols( painter, points, numPoints, *this );
+ break;
+ }
+ default:;
+ }
+ painter->restore();
+}
+
+//! \return Size of the bounding rectangle of a symbol
+QSize QwtSymbol::boundingSize() const
+{
+ QSizeF size;
+
+ switch ( d_data->style )
+ {
+ case QwtSymbol::Ellipse:
+ case QwtSymbol::Rect:
+ case QwtSymbol::Hexagon:
+ {
+ qreal pw = 0.0;
+ if ( d_data->pen.style() != Qt::NoPen )
+ pw = qMax( d_data->pen.widthF(), qreal( 1.0 ) );
+
+ size = d_data->size + QSizeF( pw, pw );
+
+ break;
+ }
+ case QwtSymbol::XCross:
+ case QwtSymbol::Diamond:
+ case QwtSymbol::Triangle:
+ case QwtSymbol::UTriangle:
+ case QwtSymbol::DTriangle:
+ case QwtSymbol::RTriangle:
+ case QwtSymbol::LTriangle:
+ case QwtSymbol::Star1:
+ case QwtSymbol::Star2:
+ {
+ qreal pw = 0.0;
+ if ( d_data->pen.style() != Qt::NoPen )
+ pw = qMax( d_data->pen.widthF(), qreal( 1.0 ) );
+
+ size = d_data->size + QSizeF( 2 * pw, 2 * pw );
+ break;
+ }
+ default:
+ {
+ size = d_data->size;
+ }
+ }
+
+ size += QSizeF( 1.0, 1.0 ); // for antialiasing
+
+ return QSize( qCeil( size.width() ), qCeil( size.height() ) );
+}
+
+/*!
+ Specify the symbol style
+
+ \param style Style
+ \sa style()
+*/
+void QwtSymbol::setStyle( QwtSymbol::Style style )
+{
+ d_data->style = style;
+}
+
+/*!
+ \return Current symbol style
+ \sa setStyle()
+*/
+QwtSymbol::Style QwtSymbol::style() const
+{
+ return d_data->style;
+}
diff --git a/src/libpcp_qwt/src/qwt_symbol.h b/src/libpcp_qwt/src/qwt_symbol.h
new file mode 100644
index 0000000..4e38431
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_symbol.h
@@ -0,0 +1,154 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SYMBOL_H
+#define QWT_SYMBOL_H
+
+#include "qwt_global.h"
+#include <QPolygonF>
+
+class QPainter;
+class QRect;
+class QSize;
+class QBrush;
+class QPen;
+class QColor;
+class QPointF;
+
+//! A class for drawing symbols
+class QWT_EXPORT QwtSymbol
+{
+public:
+ /*!
+ Symbol Style
+ \sa setStyle(), style()
+ */
+ enum Style
+ {
+ //! No Style. The symbol cannot be drawn.
+ NoSymbol = -1,
+
+ //! Ellipse or circle
+ Ellipse,
+
+ //! Rectangle
+ Rect,
+
+ //! Diamond
+ Diamond,
+
+ //! Triangle pointing upwards
+ Triangle,
+
+ //! Triangle pointing downwards
+ DTriangle,
+
+ //! Triangle pointing upwards
+ UTriangle,
+
+ //! Triangle pointing left
+ LTriangle,
+
+ //! Triangle pointing right
+ RTriangle,
+
+ //! Cross (+)
+ Cross,
+
+ //! Diagonal cross (X)
+ XCross,
+
+ //! Horizontal line
+ HLine,
+
+ //! Vertical line
+ VLine,
+
+ //! X combined with +
+ Star1,
+
+ //! Six-pointed star
+ Star2,
+
+ //! Hexagon
+ Hexagon,
+
+ /*!
+ Styles >= QwtSymbol::UserSymbol are reserved for derived
+ classes of QwtSymbol that overload drawSymbols() with
+ additional application specific symbol types.
+ */
+ UserStyle = 1000
+ };
+
+public:
+ QwtSymbol( Style = NoSymbol );
+ QwtSymbol( Style, const QBrush &, const QPen &, const QSize & );
+ QwtSymbol( const QwtSymbol & );
+ virtual ~QwtSymbol();
+
+ QwtSymbol &operator=( const QwtSymbol & );
+ bool operator==( const QwtSymbol & ) const;
+ bool operator!=( const QwtSymbol & ) const;
+
+ void setSize( const QSize & );
+ void setSize( int width, int height = -1 );
+ const QSize& size() const;
+
+ virtual void setColor( const QColor & );
+
+ void setBrush( const QBrush& b );
+ const QBrush& brush() const;
+
+ void setPen( const QPen & );
+ const QPen& pen() const;
+
+ void setStyle( Style );
+ Style style() const;
+
+ void drawSymbol( QPainter *, const QPointF & ) const;
+ void drawSymbols( QPainter *, const QPolygonF & ) const;
+
+ virtual QSize boundingSize() const;
+
+protected:
+ virtual void drawSymbols( QPainter *,
+ const QPointF *, int numPoints ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+/*!
+ \brief Draw the symbol at a specified position
+
+ \param painter Painter
+ \param pos Position of the symbol in screen coordinates
+*/
+inline void QwtSymbol::drawSymbol(
+ QPainter *painter, const QPointF &pos ) const
+{
+ drawSymbols( painter, &pos, 1 );
+}
+
+/*!
+ \brief Draw symbols at the specified points
+
+ \param painter Painter
+ \param points Positions of the symbols in screen coordinates
+*/
+
+inline void QwtSymbol::drawSymbols(
+ QPainter *painter, const QPolygonF &points ) const
+{
+ drawSymbols( painter, points.data(), points.size() );
+}
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_system_clock.cpp b/src/libpcp_qwt/src/qwt_system_clock.cpp
new file mode 100644
index 0000000..4816b12
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_system_clock.cpp
@@ -0,0 +1,364 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_system_clock.h"
+#include <qdatetime.h>
+
+#if !defined(Q_OS_WIN)
+#include <unistd.h>
+#endif
+
+#if defined(Q_OS_MAC)
+#include <stdint.h>
+#include <mach/mach_time.h>
+#define QWT_HIGH_RESOLUTION_CLOCK
+#elif defined(_POSIX_TIMERS)
+#include <time.h>
+#define QWT_HIGH_RESOLUTION_CLOCK
+#elif defined(Q_OS_WIN)
+#define QWT_HIGH_RESOLUTION_CLOCK
+#include <qt_windows.h>
+#endif
+
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+
+class QwtHighResolutionClock
+{
+public:
+ QwtHighResolutionClock();
+
+ void start();
+ double restart();
+ double elapsed() const;
+
+ bool isNull() const;
+
+ static double precision();
+
+private:
+
+#if defined(Q_OS_MAC)
+ static double msecsTo( uint64_t, uint64_t );
+
+ uint64_t d_timeStamp;
+#elif defined(_POSIX_TIMERS)
+
+ static double msecsTo( const struct timespec &,
+ const struct timespec & );
+
+ static bool isMonotonic();
+
+ struct timespec d_timeStamp;
+ clockid_t d_clockId;
+
+#elif defined(Q_OS_WIN)
+
+ LARGE_INTEGER d_startTicks;
+ LARGE_INTEGER d_ticksPerSecond;
+#endif
+};
+
+#if defined(Q_OS_MAC)
+QwtHighResolutionClock::QwtHighResolutionClock():
+ d_timeStamp( 0 )
+{
+}
+
+double QwtHighResolutionClock::precision()
+{
+ return 1e-6;
+}
+
+void QwtHighResolutionClock::start()
+{
+ d_timeStamp = mach_absolute_time();
+}
+
+double QwtHighResolutionClock::restart()
+{
+ const uint64_t timeStamp = mach_absolute_time();
+ const double elapsed = msecsTo( d_timeStamp, timeStamp );
+ d_timeStamp = timeStamp;
+
+ return elapsed;
+}
+
+double QwtHighResolutionClock::elapsed() const
+{
+ return msecsTo( d_timeStamp, mach_absolute_time() );
+}
+
+bool QwtHighResolutionClock::isNull() const
+{
+ return d_timeStamp == 0;
+}
+
+double QwtHighResolutionClock::msecsTo(
+ uint64_t from, uint64_t to )
+{
+ const uint64_t difference = to - from;
+
+ static double conversion = 0.0;
+ if ( conversion == 0.0 )
+ {
+ mach_timebase_info_data_t info;
+ kern_return_t err = mach_timebase_info( &info );
+
+ //Convert the timebase into ms
+ if ( err == 0 )
+ conversion = 1e-6 * ( double ) info.numer / ( double ) info.denom;
+ }
+
+ return conversion * ( double ) difference;
+}
+
+#elif defined(_POSIX_TIMERS)
+
+QwtHighResolutionClock::QwtHighResolutionClock()
+{
+ d_clockId = isMonotonic() ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+ d_timeStamp.tv_sec = d_timeStamp.tv_nsec = 0;
+}
+
+double QwtHighResolutionClock::precision()
+{
+ struct timespec resolution;
+
+ int clockId = isMonotonic() ? CLOCK_MONOTONIC : CLOCK_REALTIME;
+ ::clock_getres( clockId, &resolution );
+
+ return resolution.tv_nsec / 1e3;
+}
+
+inline bool QwtHighResolutionClock::isNull() const
+{
+ return d_timeStamp.tv_sec <= 0 && d_timeStamp.tv_nsec <= 0;
+}
+
+inline void QwtHighResolutionClock::start()
+{
+ ::clock_gettime( d_clockId, &d_timeStamp );
+}
+
+double QwtHighResolutionClock::restart()
+{
+ struct timespec timeStamp;
+ ::clock_gettime( d_clockId, &timeStamp );
+
+ const double elapsed = msecsTo( d_timeStamp, timeStamp );
+
+ d_timeStamp = timeStamp;
+ return elapsed;
+}
+
+inline double QwtHighResolutionClock::elapsed() const
+{
+ struct timespec timeStamp;
+ ::clock_gettime( d_clockId, &timeStamp );
+
+ return msecsTo( d_timeStamp, timeStamp );
+}
+
+inline double QwtHighResolutionClock::msecsTo(
+ const struct timespec &t1, const struct timespec &t2 )
+{
+ return ( t2.tv_sec - t1.tv_sec ) * 1e3
+ + ( t2.tv_nsec - t1.tv_nsec ) * 1e-6;
+}
+
+bool QwtHighResolutionClock::isMonotonic()
+{
+ // code copied from qcore_unix.cpp
+
+#if (_POSIX_MONOTONIC_CLOCK-0 > 0)
+ return true;
+#else
+ static int returnValue = 0;
+
+ if ( returnValue == 0 )
+ {
+#if (_POSIX_MONOTONIC_CLOCK-0 < 0) || !defined(_SC_MONOTONIC_CLOCK)
+ returnValue = -1;
+#elif (_POSIX_MONOTONIC_CLOCK == 0)
+ // detect if the system support monotonic timers
+ const long x = sysconf( _SC_MONOTONIC_CLOCK );
+ returnValue = ( x >= 200112L ) ? 1 : -1;
+#endif
+ }
+
+ return returnValue != -1;
+#endif
+}
+
+#elif defined(Q_OS_WIN)
+
+QwtHighResolutionClock::QwtHighResolutionClock()
+{
+ d_startTicks.QuadPart = 0;
+ QueryPerformanceFrequency( &d_ticksPerSecond );
+}
+
+double QwtHighResolutionClock::precision()
+{
+ LARGE_INTEGER ticks;
+ if ( QueryPerformanceFrequency( &ticks ) && ticks.QuadPart > 0 )
+ return 1e3 / ticks.QuadPart;
+
+ return 0.0;
+}
+
+inline bool QwtHighResolutionClock::isNull() const
+{
+ return d_startTicks.QuadPart <= 0;
+}
+
+inline void QwtHighResolutionClock::start()
+{
+ QueryPerformanceCounter( &d_startTicks );
+}
+
+inline double QwtHighResolutionClock::restart()
+{
+ LARGE_INTEGER ticks;
+ QueryPerformanceCounter( &ticks );
+
+ const double dt = ticks.QuadPart - d_startTicks.QuadPart;
+ d_startTicks = ticks;
+
+ return dt / d_ticksPerSecond.QuadPart * 1e3;
+}
+
+inline double QwtHighResolutionClock::elapsed() const
+{
+ LARGE_INTEGER ticks;
+ QueryPerformanceCounter( &ticks );
+
+ const double dt = ticks.QuadPart - d_startTicks.QuadPart;
+ return dt / d_ticksPerSecond.QuadPart * 1e3;
+}
+
+#endif
+
+#endif // QWT_HIGH_RESOLUTION_CLOCK
+
+class QwtSystemClock::PrivateData
+{
+public:
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ QwtHighResolutionClock *clock;
+#endif
+ QTime time;
+};
+
+//! Constructs a null clock object.
+QwtSystemClock::QwtSystemClock()
+{
+ d_data = new PrivateData;
+
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ d_data->clock = NULL;
+ if ( QwtHighResolutionClock::precision() > 0.0 )
+ d_data->clock = new QwtHighResolutionClock;
+#endif
+}
+
+//! Destructor
+QwtSystemClock::~QwtSystemClock()
+{
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ delete d_data->clock;
+#endif
+ delete d_data;
+}
+
+/*!
+ \return true if the clock has never been started.
+*/
+bool QwtSystemClock::isNull() const
+{
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ if ( d_data->clock )
+ return d_data->clock->isNull();
+#endif
+
+ return d_data->time.isNull();
+}
+
+/*!
+ Sets the start time to the current time.
+*/
+void QwtSystemClock::start()
+{
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ if ( d_data->clock )
+ {
+ d_data->clock->start();
+ return;
+ }
+#endif
+
+ d_data->time.start();
+}
+
+/*!
+ The start time to the current time and
+ return the time, that is elapsed since the
+ previous start time.
+*/
+double QwtSystemClock::restart()
+{
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ if ( d_data->clock )
+ return d_data->clock->restart();
+#endif
+
+ return d_data->time.restart();
+}
+
+/*!
+ \return Number of milliseconds that have elapsed since the last time
+ start() or restart() was called or 0.0 for null clocks.
+*/
+double QwtSystemClock::elapsed() const
+{
+ double elapsed = 0.0;
+
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ if ( d_data->clock )
+ {
+ if ( !d_data->clock->isNull() )
+ elapsed = d_data->clock->elapsed();
+
+ return elapsed;
+ }
+#endif
+
+ if ( !d_data->time.isNull() )
+ elapsed = d_data->time.elapsed();
+
+ return elapsed;
+}
+
+/*!
+ \return Accuracy of the system clock in milliseconds.
+*/
+double QwtSystemClock::precision()
+{
+ static double prec = 0.0;
+ if ( prec <= 0.0 )
+ {
+#if defined(QWT_HIGH_RESOLUTION_CLOCK)
+ prec = QwtHighResolutionClock::precision();
+#endif
+ if ( prec <= 0.0 )
+ prec = 1.0; // QTime offers 1 ms
+ }
+
+ return prec;
+}
diff --git a/src/libpcp_qwt/src/qwt_system_clock.h b/src/libpcp_qwt/src/qwt_system_clock.h
new file mode 100644
index 0000000..a9da150
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_system_clock.h
@@ -0,0 +1,49 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_SYSTEM_CLOCK_H
+#define QWT_SYSTEM_CLOCK_H
+
+#include "qwt_global.h"
+
+/*!
+ \brief QwtSystemClock provides high resolution clock time functions.
+
+ Sometimes the resolution offered by QTime ( millisecond ) is not accurate
+ enough for implementing time measurements ( f.e. sampling ).
+ QwtSystemClock offers a subset of the QTime functionality using higher
+ resolution timers ( if possible ).
+
+ Precision and time intervals are multiples of milliseconds (ms).
+
+ \note The implementation uses high-resolution performance counter on Windows,
+ mach_absolute_time() on the Mac or POSIX timers on other systems.
+ If none is available it falls back on QTimer.
+*/
+
+class QWT_EXPORT QwtSystemClock
+{
+public:
+ QwtSystemClock();
+ virtual ~QwtSystemClock();
+
+ bool isNull() const;
+
+ void start();
+ double restart();
+ double elapsed() const;
+
+ static double precision();
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_text.cpp b/src/libpcp_qwt/src/qwt_text.cpp
new file mode 100644
index 0000000..3b0ded5
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text.cpp
@@ -0,0 +1,643 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2003 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_text.h"
+#include "qwt_painter.h"
+#include "qwt_text_engine.h"
+#include <qmap.h>
+#include <qfont.h>
+#include <qcolor.h>
+#include <qpen.h>
+#include <qbrush.h>
+#include <qpainter.h>
+#include <qapplication.h>
+#include <qdesktopwidget.h>
+#include <qmath.h>
+
+class QwtTextEngineDict
+{
+public:
+ static QwtTextEngineDict &dict();
+
+ void setTextEngine( QwtText::TextFormat, QwtTextEngine * );
+
+ const QwtTextEngine *textEngine( QwtText::TextFormat ) const;
+ const QwtTextEngine *textEngine( const QString &,
+ QwtText::TextFormat ) const;
+
+private:
+ QwtTextEngineDict();
+ ~QwtTextEngineDict();
+
+ typedef QMap<int, QwtTextEngine *> EngineMap;
+
+ inline const QwtTextEngine *engine( EngineMap::const_iterator &it ) const
+ {
+ return it.value();
+ }
+
+ EngineMap d_map;
+};
+
+QwtTextEngineDict &QwtTextEngineDict::dict()
+{
+ static QwtTextEngineDict engineDict;
+ return engineDict;
+}
+
+QwtTextEngineDict::QwtTextEngineDict()
+{
+ d_map.insert( QwtText::PlainText, new QwtPlainTextEngine() );
+#ifndef QT_NO_RICHTEXT
+ d_map.insert( QwtText::RichText, new QwtRichTextEngine() );
+#endif
+}
+
+QwtTextEngineDict::~QwtTextEngineDict()
+{
+ for ( EngineMap::const_iterator it = d_map.begin();
+ it != d_map.end(); ++it )
+ {
+ const QwtTextEngine *textEngine = engine( it );
+ delete textEngine;
+ }
+}
+
+const QwtTextEngine *QwtTextEngineDict::textEngine( const QString& text,
+ QwtText::TextFormat format ) const
+{
+ if ( format == QwtText::AutoText )
+ {
+ for ( EngineMap::const_iterator it = d_map.begin();
+ it != d_map.end(); ++it )
+ {
+ if ( it.key() != QwtText::PlainText )
+ {
+ const QwtTextEngine *e = engine( it );
+ if ( e && e->mightRender( text ) )
+ return e;
+ }
+ }
+ }
+
+ EngineMap::const_iterator it = d_map.find( format );
+ if ( it != d_map.end() )
+ {
+ const QwtTextEngine *e = engine( it );
+ if ( e )
+ return e;
+ }
+
+ it = d_map.find( QwtText::PlainText );
+ return engine( it );
+}
+
+void QwtTextEngineDict::setTextEngine( QwtText::TextFormat format,
+ QwtTextEngine *engine )
+{
+ if ( format == QwtText::AutoText )
+ return;
+
+ if ( format == QwtText::PlainText && engine == NULL )
+ return;
+
+ EngineMap::const_iterator it = d_map.find( format );
+ if ( it != d_map.end() )
+ {
+ const QwtTextEngine *e = this->engine( it );
+ if ( e )
+ delete e;
+
+ d_map.remove( format );
+ }
+
+ if ( engine != NULL )
+ d_map.insert( format, engine );
+}
+
+const QwtTextEngine *QwtTextEngineDict::textEngine(
+ QwtText::TextFormat format ) const
+{
+ const QwtTextEngine *e = NULL;
+
+ EngineMap::const_iterator it = d_map.find( format );
+ if ( it != d_map.end() )
+ e = engine( it );
+
+ return e;
+}
+
+class QwtText::PrivateData
+{
+public:
+ PrivateData():
+ renderFlags( Qt::AlignCenter ),
+ backgroundPen( Qt::NoPen ),
+ backgroundBrush( Qt::NoBrush ),
+ paintAttributes( 0 ),
+ layoutAttributes( 0 ),
+ textEngine( NULL )
+ {
+ }
+
+ int renderFlags;
+ QString text;
+ QFont font;
+ QColor color;
+ QPen backgroundPen;
+ QBrush backgroundBrush;
+
+ QwtText::PaintAttributes paintAttributes;
+ QwtText::LayoutAttributes layoutAttributes;
+
+ const QwtTextEngine *textEngine;
+};
+
+class QwtText::LayoutCache
+{
+public:
+ void invalidate()
+ {
+ textSize = QSizeF();
+ }
+
+ QFont font;
+ QSizeF textSize;
+};
+
+/*!
+ Constructor
+
+ \param text Text content
+ \param textFormat Text format
+*/
+QwtText::QwtText( const QString &text, QwtText::TextFormat textFormat )
+{
+ d_data = new PrivateData;
+ d_data->text = text;
+ d_data->textEngine = textEngine( text, textFormat );
+
+ d_layoutCache = new LayoutCache;
+}
+
+//! Copy constructor
+QwtText::QwtText( const QwtText &other )
+{
+ d_data = new PrivateData;
+ *d_data = *other.d_data;
+
+ d_layoutCache = new LayoutCache;
+ *d_layoutCache = *other.d_layoutCache;
+}
+
+//! Destructor
+QwtText::~QwtText()
+{
+ delete d_data;
+ delete d_layoutCache;
+}
+
+//! Assignment operator
+QwtText &QwtText::operator=( const QwtText & other )
+{
+ *d_data = *other.d_data;
+ *d_layoutCache = *other.d_layoutCache;
+ return *this;
+}
+
+//! Relational operator
+bool QwtText::operator==( const QwtText &other ) const
+{
+ return d_data->renderFlags == other.d_data->renderFlags &&
+ d_data->text == other.d_data->text &&
+ d_data->font == other.d_data->font &&
+ d_data->color == other.d_data->color &&
+ d_data->backgroundPen == other.d_data->backgroundPen &&
+ d_data->backgroundBrush == other.d_data->backgroundBrush &&
+ d_data->paintAttributes == other.d_data->paintAttributes &&
+ d_data->textEngine == other.d_data->textEngine;
+}
+
+//! Relational operator
+bool QwtText::operator!=( const QwtText &other ) const // invalidate
+{
+ return !( other == *this );
+}
+
+/*!
+ Assign a new text content
+
+ \param text Text content
+ \param textFormat Text format
+
+ \sa text()
+*/
+void QwtText::setText( const QString &text,
+ QwtText::TextFormat textFormat )
+{
+ d_data->text = text;
+ d_data->textEngine = textEngine( text, textFormat );
+ d_layoutCache->invalidate();
+}
+
+/*!
+ Return the text.
+ \sa setText()
+*/
+QString QwtText::text() const
+{
+ return d_data->text;
+}
+
+/*!
+ \brief Change the render flags
+
+ The default setting is Qt::AlignCenter
+
+ \param renderFlags Bitwise OR of the flags used like in QPainter::drawText
+
+ \sa renderFlags(), QwtTextEngine::draw()
+ \note Some renderFlags might have no effect, depending on the text format.
+*/
+void QwtText::setRenderFlags( int renderFlags )
+{
+ if ( renderFlags != d_data->renderFlags )
+ {
+ d_data->renderFlags = renderFlags;
+ d_layoutCache->invalidate();
+ }
+}
+
+/*!
+ \return Render flags
+ \sa setRenderFlags()
+*/
+int QwtText::renderFlags() const
+{
+ return d_data->renderFlags;
+}
+
+/*!
+ Set the font.
+
+ \param font Font
+ \note Setting the font might have no effect, when
+ the text contains control sequences for setting fonts.
+*/
+void QwtText::setFont( const QFont &font )
+{
+ d_data->font = font;
+ setPaintAttribute( PaintUsingTextFont );
+}
+
+//! Return the font.
+QFont QwtText::font() const
+{
+ return d_data->font;
+}
+
+/*!
+ Return the font of the text, if it has one.
+ Otherwise return defaultFont.
+
+ \param defaultFont Default font
+ \sa setFont(), font(), PaintAttributes
+*/
+QFont QwtText::usedFont( const QFont &defaultFont ) const
+{
+ if ( d_data->paintAttributes & PaintUsingTextFont )
+ return d_data->font;
+
+ return defaultFont;
+}
+
+/*!
+ Set the pen color used for painting the text.
+
+ \param color Color
+ \note Setting the color might have no effect, when
+ the text contains control sequences for setting colors.
+*/
+void QwtText::setColor( const QColor &color )
+{
+ d_data->color = color;
+ setPaintAttribute( PaintUsingTextColor );
+}
+
+//! Return the pen color, used for painting the text
+QColor QwtText::color() const
+{
+ return d_data->color;
+}
+
+/*!
+ Return the color of the text, if it has one.
+ Otherwise return defaultColor.
+
+ \param defaultColor Default color
+ \sa setColor(), color(), PaintAttributes
+*/
+QColor QwtText::usedColor( const QColor &defaultColor ) const
+{
+ if ( d_data->paintAttributes & PaintUsingTextColor )
+ return d_data->color;
+
+ return defaultColor;
+}
+
+/*!
+ Set the background pen
+
+ \param pen Background pen
+ \sa backgroundPen(), setBackgroundBrush()
+*/
+void QwtText::setBackgroundPen( const QPen &pen )
+{
+ d_data->backgroundPen = pen;
+ setPaintAttribute( PaintBackground );
+}
+
+/*!
+ \return Background pen
+ \sa setBackgroundPen(), backgroundBrush()
+*/
+QPen QwtText::backgroundPen() const
+{
+ return d_data->backgroundPen;
+}
+
+/*!
+ Set the background brush
+
+ \param brush Background brush
+ \sa backgroundBrush(), setBackgroundPen()
+*/
+void QwtText::setBackgroundBrush( const QBrush &brush )
+{
+ d_data->backgroundBrush = brush;
+ setPaintAttribute( PaintBackground );
+}
+
+/*!
+ \return Background brush
+ \sa setBackgroundBrush(), backgroundPen()
+*/
+QBrush QwtText::backgroundBrush() const
+{
+ return d_data->backgroundBrush;
+}
+
+/*!
+ Change a paint attribute
+
+ \param attribute Paint attribute
+ \param on On/Off
+
+ \note Used by setFont(), setColor(),
+ setBackgroundPen() and setBackgroundBrush()
+ \sa testPaintAttribute()
+*/
+void QwtText::setPaintAttribute( PaintAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->paintAttributes |= attribute;
+ else
+ d_data->paintAttributes &= ~attribute;
+}
+
+/*!
+ Test a paint attribute
+
+ \param attribute Paint attribute
+ \return true, if attribute is enabled
+
+ \sa setPaintAttribute()
+*/
+bool QwtText::testPaintAttribute( PaintAttribute attribute ) const
+{
+ return d_data->paintAttributes & attribute;
+}
+
+/*!
+ Change a layout attribute
+
+ \param attribute Layout attribute
+ \param on On/Off
+ \sa testLayoutAttribute()
+*/
+void QwtText::setLayoutAttribute( LayoutAttribute attribute, bool on )
+{
+ if ( on )
+ d_data->layoutAttributes |= attribute;
+ else
+ d_data->layoutAttributes &= ~attribute;
+}
+
+/*!
+ Test a layout attribute
+
+ \param attribute Layout attribute
+ \return true, if attribute is enabled
+
+ \sa setLayoutAttribute()
+*/
+bool QwtText::testLayoutAttribute( LayoutAttribute attribute ) const
+{
+ return d_data->layoutAttributes | attribute;
+}
+
+/*!
+ Find the height for a given width
+
+ \param defaultFont Font, used for the calculation if the text has no font
+ \param width Width
+
+ \return Calculated height
+*/
+double QwtText::heightForWidth( double width, const QFont &defaultFont ) const
+{
+ // We want to calculate in screen metrics. So
+ // we need a font that uses screen metrics
+
+ const QFont font( usedFont( defaultFont ), QApplication::desktop() );
+
+ double h = 0;
+
+ if ( d_data->layoutAttributes & MinimumLayout )
+ {
+ double left, right, top, bottom;
+ d_data->textEngine->textMargins( font, d_data->text,
+ left, right, top, bottom );
+
+ h = d_data->textEngine->heightForWidth(
+ font, d_data->renderFlags, d_data->text,
+ width + left + right );
+
+ h -= top + bottom;
+ }
+ else
+ {
+ h = d_data->textEngine->heightForWidth(
+ font, d_data->renderFlags, d_data->text, width );
+ }
+
+ return h;
+}
+
+/*!
+ Find the height for a given width
+
+ \param defaultFont Font, used for the calculation if the text has no font
+
+ \return Calculated height
+*/
+
+/*!
+ Returns the size, that is needed to render text
+
+ \param defaultFont Font of the text
+ \return Caluclated size
+*/
+QSizeF QwtText::textSize( const QFont &defaultFont ) const
+{
+ // We want to calculate in screen metrics. So
+ // we need a font that uses screen metrics
+
+ const QFont font( usedFont( defaultFont ), QApplication::desktop() );
+
+ if ( !d_layoutCache->textSize.isValid()
+ || d_layoutCache->font != font )
+ {
+ d_layoutCache->textSize = d_data->textEngine->textSize(
+ font, d_data->renderFlags, d_data->text );
+ d_layoutCache->font = font;
+ }
+
+ QSizeF sz = d_layoutCache->textSize;
+
+ if ( d_data->layoutAttributes & MinimumLayout )
+ {
+ double left, right, top, bottom;
+ d_data->textEngine->textMargins( font, d_data->text,
+ left, right, top, bottom );
+ sz -= QSizeF( left + right, top + bottom );
+ }
+
+ return sz;
+}
+
+/*!
+ Draw a text into a rectangle
+
+ \param painter Painter
+ \param rect Rectangle
+*/
+void QwtText::draw( QPainter *painter, const QRectF &rect ) const
+{
+ if ( d_data->paintAttributes & PaintBackground )
+ {
+ if ( d_data->backgroundPen != Qt::NoPen ||
+ d_data->backgroundBrush != Qt::NoBrush )
+ {
+ painter->save();
+ painter->setPen( d_data->backgroundPen );
+ painter->setBrush( d_data->backgroundBrush );
+ QwtPainter::drawRect( painter, rect );
+ painter->restore();
+ }
+ }
+
+ painter->save();
+
+ if ( d_data->paintAttributes & PaintUsingTextFont )
+ {
+ painter->setFont( d_data->font );
+ }
+
+ if ( d_data->paintAttributes & PaintUsingTextColor )
+ {
+ if ( d_data->color.isValid() )
+ painter->setPen( d_data->color );
+ }
+
+ QRectF expandedRect = rect;
+ if ( d_data->layoutAttributes & MinimumLayout )
+ {
+ // We want to calculate in screen metrics. So
+ // we need a font that uses screen metrics
+
+ const QFont font( painter->font(), QApplication::desktop() );
+
+ double left, right, top, bottom;
+ d_data->textEngine->textMargins(
+ font, d_data->text, left, right, top, bottom );
+
+ expandedRect.setTop( rect.top() - top );
+ expandedRect.setBottom( rect.bottom() + bottom );
+ expandedRect.setLeft( rect.left() - left );
+ expandedRect.setRight( rect.right() + right );
+ }
+
+ d_data->textEngine->draw( painter, expandedRect,
+ d_data->renderFlags, d_data->text );
+
+ painter->restore();
+}
+
+/*!
+ Find the text engine for a text format
+
+ In case of QwtText::AutoText the first text engine
+ (beside QwtPlainTextEngine) is returned, where QwtTextEngine::mightRender
+ returns true. If there is none QwtPlainTextEngine is returnd.
+
+ If no text engine is registered for the format QwtPlainTextEngine
+ is returnd.
+
+ \param text Text, needed in case of AutoText
+ \param format Text format
+*/
+const QwtTextEngine *QwtText::textEngine( const QString &text,
+ QwtText::TextFormat format )
+{
+ return QwtTextEngineDict::dict().textEngine( text, format );
+}
+
+/*!
+ Assign/Replace a text engine for a text format
+
+ With setTextEngine it is possible to extend Qwt with
+ other types of text formats.
+
+ For QwtText::PlainText it is not allowed to assign a engine == NULL.
+
+ \param format Text format
+ \param engine Text engine
+
+ \sa QwtMathMLTextEngine
+ \warning Using QwtText::AutoText does nothing.
+*/
+void QwtText::setTextEngine( QwtText::TextFormat format,
+ QwtTextEngine *engine )
+{
+ QwtTextEngineDict::dict().setTextEngine( format, engine );
+}
+
+/*!
+ \brief Find the text engine for a text format
+
+ textEngine can be used to find out if a text format is supported.
+
+ \param format Text format
+ \return The text engine, or NULL if no engine is available.
+*/
+const QwtTextEngine *QwtText::textEngine( QwtText::TextFormat format )
+{
+ return QwtTextEngineDict::dict().textEngine( format );
+}
diff --git a/src/libpcp_qwt/src/qwt_text.h b/src/libpcp_qwt/src/qwt_text.h
new file mode 100644
index 0000000..3c6a438
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text.h
@@ -0,0 +1,216 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2003 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_TEXT_H
+#define QWT_TEXT_H
+
+#include "qwt_global.h"
+#include <qstring.h>
+#include <qsize.h>
+#include <qfont.h>
+
+class QColor;
+class QPen;
+class QBrush;
+class QRectF;
+class QPainter;
+class QwtTextEngine;
+
+/*!
+ \brief A class representing a text
+
+ A QwtText is a text including a set of attributes how to render it.
+
+ - Format\n
+ A text might include control sequences (f.e tags) describing
+ how to render it. Each format (f.e MathML, TeX, Qt Rich Text)
+ has its own set of control sequences, that can be handles by
+ a QwtTextEngine for this format.
+ - Background\n
+ A text might have a background, defined by a QPen and QBrush
+ to improve its visibility.
+ - Font\n
+ A text might have an individual font.
+ - Color\n
+ A text might have an individual color.
+ - Render Flags\n
+ Flags from Qt::AlignmentFlag and Qt::TextFlag used like in
+ QPainter::drawText.
+
+ \sa QwtTextEngine, QwtTextLabel
+*/
+
+class QWT_EXPORT QwtText
+{
+public:
+
+ /*!
+ \brief Text format
+
+ The text format defines the QwtTextEngine, that is used to render
+ the text.
+
+ \sa QwtTextEngine, setTextEngine()
+ */
+
+ enum TextFormat
+ {
+ /*!
+ The text format is determined using QwtTextEngine::mightRender for
+ all available text engines in increasing order > PlainText.
+ If none of the text engines can render the text is rendered
+ like QwtText::PlainText.
+ */
+ AutoText = 0,
+
+ //! Draw the text as it is, using a QwtPlainTextEngine.
+ PlainText,
+
+ //! Use the Scribe framework (Qt Rich Text) to render the text.
+ RichText,
+
+ /*!
+ Use a MathML (http://en.wikipedia.org/wiki/MathML) render engine
+ to display the text. The Qwt MathML extension offers such an engine
+ based on the MathML renderer of the Qt solutions package.
+ To enable MathML support the following code needs to be added to the
+ application:
+\verbatim QwtText::setTextEngine(QwtText::MathMLText, new QwtMathMLTextEngine()); \endverbatim
+ */
+ MathMLText,
+
+ /*!
+ Use a TeX (http://en.wikipedia.org/wiki/TeX) render engine
+ to display the text ( not implemented yet ).
+ */
+ TeXText,
+
+ /*!
+ The number of text formats can be extended using setTextEngine.
+ Formats >= QwtText::OtherFormat are not used by Qwt.
+ */
+ OtherFormat = 100
+ };
+
+ /*!
+ \brief Paint Attributes
+
+ Font and color and background are optional attributes of a QwtText.
+ The paint attributes hold the information, if they are set.
+ */
+ enum PaintAttribute
+ {
+ //! The text has an individual font.
+ PaintUsingTextFont = 0x01,
+
+ //! The text has an individual color.
+ PaintUsingTextColor = 0x02,
+
+ //! The text has an individual background.
+ PaintBackground = 0x04
+ };
+
+ //! Paint attributes
+ typedef QFlags<PaintAttribute> PaintAttributes;
+
+ /*!
+ \brief Layout Attributes
+ The layout attributes affects some aspects of the layout of the text.
+ */
+ enum LayoutAttribute
+ {
+ /*!
+ Layout the text without its margins. This mode is useful if a
+ text needs to be aligned accurately, like the tick labels of a scale.
+ If QwtTextEngine::textMargins is not implemented for the format
+ of the text, MinimumLayout has no effect.
+ */
+ MinimumLayout = 0x01
+ };
+
+ //! Layout attributes
+ typedef QFlags<LayoutAttribute> LayoutAttributes;
+
+ QwtText( const QString & = QString::null,
+ TextFormat textFormat = AutoText );
+ QwtText( const QwtText & );
+ ~QwtText();
+
+ QwtText &operator=( const QwtText & );
+
+ bool operator==( const QwtText & ) const;
+ bool operator!=( const QwtText & ) const;
+
+ void setText( const QString &,
+ QwtText::TextFormat textFormat = AutoText );
+ QString text() const;
+
+ bool isNull() const;
+ bool isEmpty() const;
+
+ void setFont( const QFont & );
+ QFont font() const;
+
+ QFont usedFont( const QFont & ) const;
+
+ void setRenderFlags( int flags );
+ int renderFlags() const;
+
+ void setColor( const QColor & );
+ QColor color() const;
+
+ QColor usedColor( const QColor & ) const;
+
+ void setBackgroundPen( const QPen & );
+ QPen backgroundPen() const;
+
+ void setBackgroundBrush( const QBrush & );
+ QBrush backgroundBrush() const;
+
+ void setPaintAttribute( PaintAttribute, bool on = true );
+ bool testPaintAttribute( PaintAttribute ) const;
+
+ void setLayoutAttribute( LayoutAttribute, bool on = true );
+ bool testLayoutAttribute( LayoutAttribute ) const;
+
+ double heightForWidth( double width, const QFont & = QFont() ) const;
+ QSizeF textSize( const QFont & = QFont() ) const;
+
+ void draw( QPainter *painter, const QRectF &rect ) const;
+
+ static const QwtTextEngine *textEngine(
+ const QString &text, QwtText::TextFormat = AutoText );
+
+ static const QwtTextEngine *textEngine( QwtText::TextFormat );
+ static void setTextEngine( QwtText::TextFormat, QwtTextEngine * );
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+
+ class LayoutCache;
+ LayoutCache *d_layoutCache;
+};
+
+//! \return text().isNull()
+inline bool QwtText::isNull() const
+{
+ return text().isNull();
+}
+
+//! \return text().isEmpty()
+inline bool QwtText::isEmpty() const
+{
+ return text().isEmpty();
+}
+
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtText::PaintAttributes )
+Q_DECLARE_OPERATORS_FOR_FLAGS( QwtText::LayoutAttributes )
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_text_engine.cpp b/src/libpcp_qwt/src/qwt_text_engine.cpp
new file mode 100644
index 0000000..ec74512
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text_engine.cpp
@@ -0,0 +1,344 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2003 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_text_engine.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qimage.h>
+#include <qmap.h>
+#include <qwidget.h>
+#include <qtextobject.h>
+#include <qtextdocument.h>
+#include <qabstracttextdocumentlayout.h>
+
+static QString taggedRichText( const QString &text, int flags )
+{
+ QString richText = text;
+
+ // By default QSimpleRichText is Qt::AlignLeft
+ if ( flags & Qt::AlignJustify )
+ {
+ richText.prepend( QString::fromLatin1( "<div align=\"justify\">" ) );
+ richText.append( QString::fromLatin1( "</div>" ) );
+ }
+ else if ( flags & Qt::AlignRight )
+ {
+ richText.prepend( QString::fromLatin1( "<div align=\"right\">" ) );
+ richText.append( QString::fromLatin1( "</div>" ) );
+ }
+ else if ( flags & Qt::AlignHCenter )
+ {
+ richText.prepend( QString::fromLatin1( "<div align=\"center\">" ) );
+ richText.append( QString::fromLatin1( "</div>" ) );
+ }
+
+ return richText;
+}
+
+class QwtRichTextDocument: public QTextDocument
+{
+public:
+ QwtRichTextDocument( const QString &text, int flags, const QFont &font )
+ {
+ setUndoRedoEnabled( false );
+ setDefaultFont( font );
+ setHtml( text );
+
+ // make sure we have a document layout
+ ( void )documentLayout();
+
+ QTextOption option = defaultTextOption();
+ if ( flags & Qt::TextWordWrap )
+ option.setWrapMode( QTextOption::WordWrap );
+ else
+ option.setWrapMode( QTextOption::NoWrap );
+
+ option.setAlignment( ( Qt::Alignment ) flags );
+ setDefaultTextOption( option );
+
+ QTextFrame *root = rootFrame();
+ QTextFrameFormat fm = root->frameFormat();
+ fm.setBorder( 0 );
+ fm.setMargin( 0 );
+ fm.setPadding( 0 );
+ fm.setBottomMargin( 0 );
+ fm.setLeftMargin( 0 );
+ root->setFrameFormat( fm );
+
+ adjustSize();
+ }
+};
+
+class QwtPlainTextEngine::PrivateData
+{
+public:
+ int effectiveAscent( const QFont &font ) const
+ {
+ const QString fontKey = font.key();
+
+ QMap<QString, int>::const_iterator it =
+ d_ascentCache.find( fontKey );
+ if ( it == d_ascentCache.end() )
+ {
+ int ascent = findAscent( font );
+ it = d_ascentCache.insert( fontKey, ascent );
+ }
+
+ return ( *it );
+ }
+
+private:
+ int findAscent( const QFont &font ) const
+ {
+ static const QString dummy( "E" );
+ static const QColor white( Qt::white );
+
+ const QFontMetrics fm( font );
+ QPixmap pm( fm.width( dummy ), fm.height() );
+ pm.fill( white );
+
+ QPainter p( &pm );
+ p.setFont( font );
+ p.drawText( 0, 0, pm.width(), pm.height(), 0, dummy );
+ p.end();
+
+ const QImage img = pm.toImage();
+
+ int row = 0;
+ for ( row = 0; row < img.height(); row++ )
+ {
+ const QRgb *line = ( const QRgb * )img.scanLine( row );
+
+ const int w = pm.width();
+ for ( int col = 0; col < w; col++ )
+ {
+ if ( line[col] != white.rgb() )
+ return fm.ascent() - row + 1;
+ }
+ }
+
+ return fm.ascent();
+ }
+
+ mutable QMap<QString, int> d_ascentCache;
+};
+
+//! Constructor
+QwtTextEngine::QwtTextEngine()
+{
+}
+
+//! Destructor
+QwtTextEngine::~QwtTextEngine()
+{
+}
+
+//! Constructor
+QwtPlainTextEngine::QwtPlainTextEngine()
+{
+ d_data = new PrivateData;
+}
+
+//! Destructor
+QwtPlainTextEngine::~QwtPlainTextEngine()
+{
+ delete d_data;
+}
+
+/*!
+ Find the height for a given width
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+ \param width Width
+
+ \return Calculated height
+*/
+double QwtPlainTextEngine::heightForWidth( const QFont& font, int flags,
+ const QString& text, double width ) const
+{
+ const QFontMetricsF fm( font );
+ const QRectF rect = fm.boundingRect(
+ QRectF( 0, 0, width, QWIDGETSIZE_MAX ), flags, text );
+
+ return rect.height();
+}
+
+/*!
+ Returns the size, that is needed to render text
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+
+ \return Caluclated size
+*/
+QSizeF QwtPlainTextEngine::textSize( const QFont &font,
+ int flags, const QString& text ) const
+{
+ const QFontMetricsF fm( font );
+ const QRectF rect = fm.boundingRect(
+ QRectF( 0, 0, QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ), flags, text );
+
+ return rect.size();
+}
+
+/*!
+ Return margins around the texts
+
+ \param font Font of the text
+ \param left Return 0
+ \param right Return 0
+ \param top Return value for the top margin
+ \param bottom Return value for the bottom margin
+*/
+void QwtPlainTextEngine::textMargins( const QFont &font, const QString &,
+ double &left, double &right, double &top, double &bottom ) const
+{
+ left = right = top = 0;
+
+ const QFontMetricsF fm( font );
+ top = fm.ascent() - d_data->effectiveAscent( font );
+ bottom = fm.descent();
+}
+
+/*!
+ \brief Draw the text in a clipping rectangle
+
+ A wrapper for QPainter::drawText.
+
+ \param painter Painter
+ \param rect Clipping rectangle
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+*/
+void QwtPlainTextEngine::draw( QPainter *painter, const QRectF &rect,
+ int flags, const QString& text ) const
+{
+ QwtPainter::drawText( painter, rect, flags, text );
+}
+
+/*!
+ Test if a string can be rendered by this text engine.
+ \return Always true. All texts can be rendered by QwtPlainTextEngine
+*/
+bool QwtPlainTextEngine::mightRender( const QString & ) const
+{
+ return true;
+}
+
+#ifndef QT_NO_RICHTEXT
+
+//! Constructor
+QwtRichTextEngine::QwtRichTextEngine()
+{
+}
+
+/*!
+ Find the height for a given width
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+ \param width Width
+
+ \return Calculated height
+*/
+double QwtRichTextEngine::heightForWidth( const QFont& font, int flags,
+ const QString& text, double width ) const
+{
+ QwtRichTextDocument doc( text, flags, font );
+
+ doc.setPageSize( QSizeF( width, QWIDGETSIZE_MAX ) );
+ return doc.documentLayout()->documentSize().height();
+}
+
+/*!
+ Returns the size, that is needed to render text
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+
+ \return Caluclated size
+*/
+
+QSizeF QwtRichTextEngine::textSize( const QFont &font,
+ int flags, const QString& text ) const
+{
+ QwtRichTextDocument doc( text, flags, font );
+
+ QTextOption option = doc.defaultTextOption();
+ if ( option.wrapMode() != QTextOption::NoWrap )
+ {
+ option.setWrapMode( QTextOption::NoWrap );
+ doc.setDefaultTextOption( option );
+ doc.adjustSize();
+ }
+
+ return doc.size();
+}
+
+/*!
+ Draw the text in a clipping rectangle
+
+ \param painter Painter
+ \param rect Clipping rectangle
+ \param flags Bitwise OR of the flags like in for QPainter::drawText
+ \param text Text to be rendered
+*/
+void QwtRichTextEngine::draw( QPainter *painter, const QRectF &rect,
+ int flags, const QString& text ) const
+{
+ QwtRichTextDocument doc( text, flags, painter->font() );
+ QwtPainter::drawSimpleRichText( painter, rect, flags, doc );
+}
+
+/*!
+ Wrap text into <div align=...> </div> tags according flags
+
+ \param text Text
+ \param flags Bitwise OR of the flags like in for QPainter::drawText
+
+ \return Tagged text
+*/
+QString QwtRichTextEngine::taggedText( const QString &text, int flags ) const
+{
+ return taggedRichText( text, flags );
+}
+
+/*!
+ Test if a string can be rendered by this text engine
+
+ \param text Text to be tested
+ \return QStyleSheet::mightBeRichText(text);
+*/
+bool QwtRichTextEngine::mightRender( const QString &text ) const
+{
+ return Qt::mightBeRichText( text );
+}
+
+/*!
+ Return margins around the texts
+
+ \param left Return 0
+ \param right Return 0
+ \param top Return 0
+ \param bottom Return 0
+*/
+void QwtRichTextEngine::textMargins( const QFont &, const QString &,
+ double &left, double &right, double &top, double &bottom ) const
+{
+ left = right = top = bottom = 0;
+}
+
+#endif // !QT_NO_RICHTEXT
diff --git a/src/libpcp_qwt/src/qwt_text_engine.h b/src/libpcp_qwt/src/qwt_text_engine.h
new file mode 100644
index 0000000..e378acf
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text_engine.h
@@ -0,0 +1,172 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2003 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_TEXT_ENGINE_H
+#define QWT_TEXT_ENGINE_H 1
+
+#include "qwt_global.h"
+#include <qsize.h>
+
+class QFont;
+class QRectF;
+class QString;
+class QPainter;
+
+/*!
+ \brief Abstract base class for rendering text strings
+
+ A text engine is responsible for rendering texts for a
+ specific text format. They are used by QwtText to render a text.
+
+ QwtPlainTextEngine and QwtRichTextEngine are part of the Qwt library.
+ The implementation of QwtMathMLTextEngine uses code from the
+ Qt solution package. Because of license implications it is built into
+ a separate library.
+
+ \sa QwtText::setTextEngine()
+*/
+
+class QWT_EXPORT QwtTextEngine
+{
+public:
+ virtual ~QwtTextEngine();
+
+ /*!
+ Find the height for a given width
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags used like in QPainter::drawText
+ \param text Text to be rendered
+ \param width Width
+
+ \return Calculated height
+ */
+ virtual double heightForWidth( const QFont &font, int flags,
+ const QString &text, double width ) const = 0;
+
+ /*!
+ Returns the size, that is needed to render text
+
+ \param font Font of the text
+ \param flags Bitwise OR of the flags like in for QPainter::drawText
+ \param text Text to be rendered
+
+ \return Caluclated size
+ */
+ virtual QSizeF textSize( const QFont &font, int flags,
+ const QString &text ) const = 0;
+
+ /*!
+ Test if a string can be rendered by this text engine
+
+ \param text Text to be tested
+ \return true, if it can be rendered
+ */
+ virtual bool mightRender( const QString &text ) const = 0;
+
+ /*!
+ Return margins around the texts
+
+ The textSize might include margins around the
+ text, like QFontMetrics::descent. In situations
+ where texts need to be aligend in detail, knowing
+ these margins might improve the layout calculations.
+
+ \param font Font of the text
+ \param text Text to be rendered
+ \param left Return value for the left margin
+ \param right Return value for the right margin
+ \param top Return value for the top margin
+ \param bottom Return value for the bottom margin
+ */
+ virtual void textMargins( const QFont &font, const QString &text,
+ double &left, double &right, double &top, double &bottom ) const = 0;
+
+ /*!
+ Draw the text in a clipping rectangle
+
+ \param painter Painter
+ \param rect Clipping rectangle
+ \param flags Bitwise OR of the flags like in for QPainter::drawText
+ \param text Text to be rendered
+ */
+ virtual void draw( QPainter *painter, const QRectF &rect,
+ int flags, const QString &text ) const = 0;
+
+protected:
+ QwtTextEngine();
+};
+
+
+/*!
+ \brief A text engine for plain texts
+
+ QwtPlainTextEngine renders texts using the basic Qt classes
+ QPainter and QFontMetrics.
+*/
+class QWT_EXPORT QwtPlainTextEngine: public QwtTextEngine
+{
+public:
+ QwtPlainTextEngine();
+ virtual ~QwtPlainTextEngine();
+
+ virtual double heightForWidth( const QFont &font, int flags,
+ const QString &text, double width ) const;
+
+ virtual QSizeF textSize( const QFont &font, int flags,
+ const QString &text ) const;
+
+ virtual void draw( QPainter *painter, const QRectF &rect,
+ int flags, const QString &text ) const;
+
+ virtual bool mightRender( const QString & ) const;
+
+ virtual void textMargins( const QFont &, const QString &,
+ double &left, double &right, double &top, double &bottom ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+
+#ifndef QT_NO_RICHTEXT
+
+/*!
+ \brief A text engine for Qt rich texts
+
+ QwtRichTextEngine renders Qt rich texts using the classes
+ of the Scribe framework of Qt.
+*/
+class QWT_EXPORT QwtRichTextEngine: public QwtTextEngine
+{
+public:
+ QwtRichTextEngine();
+
+ virtual double heightForWidth( const QFont &font, int flags,
+ const QString &text, double width ) const;
+
+ virtual QSizeF textSize( const QFont &font, int flags,
+ const QString &text ) const;
+
+ virtual void draw( QPainter *painter, const QRectF &rect,
+ int flags, const QString &text ) const;
+
+ virtual bool mightRender( const QString & ) const;
+
+ virtual void textMargins( const QFont &, const QString &,
+ double &left, double &right, double &top, double &bottom ) const;
+
+private:
+ QString taggedText( const QString &, int flags ) const;
+};
+
+#endif // !QT_NO_RICHTEXT
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_text_label.cpp b/src/libpcp_qwt/src/qwt_text_label.cpp
new file mode 100644
index 0000000..e895928
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text_label.cpp
@@ -0,0 +1,306 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_text_label.h"
+#include "qwt_text.h"
+#include "qwt_painter.h"
+#include <qpainter.h>
+#include <qevent.h>
+#include <qmath.h>
+
+class QwtTextLabel::PrivateData
+{
+public:
+ PrivateData():
+ indent( 4 ),
+ margin( 0 )
+ {
+ }
+
+ int indent;
+ int margin;
+ QwtText text;
+};
+
+/*!
+ Constructs an empty label.
+ \param parent Parent widget
+*/
+QwtTextLabel::QwtTextLabel( QWidget *parent ):
+ QFrame( parent )
+{
+ init();
+}
+
+/*!
+ Constructs a label that displays the text, text
+ \param parent Parent widget
+ \param text Text
+*/
+QwtTextLabel::QwtTextLabel( const QwtText &text, QWidget *parent ):
+ QFrame( parent )
+{
+ init();
+ d_data->text = text;
+}
+
+//! Destructor
+QwtTextLabel::~QwtTextLabel()
+{
+ delete d_data;
+}
+
+void QwtTextLabel::init()
+{
+ d_data = new PrivateData();
+ setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
+}
+
+/*!
+ Change the label's text, keeping all other QwtText attributes
+ \param text New text
+ \param textFormat Format of text
+
+ \sa QwtText
+*/
+void QwtTextLabel::setText( const QString &text, QwtText::TextFormat textFormat )
+{
+ d_data->text.setText( text, textFormat );
+
+ update();
+ updateGeometry();
+}
+
+/*!
+ Change the label's text
+ \param text New text
+*/
+void QwtTextLabel::setText( const QwtText &text )
+{
+ d_data->text = text;
+
+ update();
+ updateGeometry();
+}
+
+//! Return the text
+const QwtText &QwtTextLabel::text() const
+{
+ return d_data->text;
+}
+
+//! Clear the text and all QwtText attributes
+void QwtTextLabel::clear()
+{
+ d_data->text = QwtText();
+
+ update();
+ updateGeometry();
+}
+
+//! Return label's text indent in pixels
+int QwtTextLabel::indent() const
+{
+ return d_data->indent;
+}
+
+/*!
+ Set label's text indent in pixels
+ \param indent Indentation in pixels
+*/
+void QwtTextLabel::setIndent( int indent )
+{
+ if ( indent < 0 )
+ indent = 0;
+
+ d_data->indent = indent;
+
+ update();
+ updateGeometry();
+}
+
+//! Return label's text indent in pixels
+int QwtTextLabel::margin() const
+{
+ return d_data->margin;
+}
+
+/*!
+ Set label's margin in pixels
+ \param margin Margin in pixels
+*/
+void QwtTextLabel::setMargin( int margin )
+{
+ d_data->margin = margin;
+
+ update();
+ updateGeometry();
+}
+
+//! Return label's margin in pixels
+QSize QwtTextLabel::sizeHint() const
+{
+ return minimumSizeHint();
+}
+
+//! Return a minimum size hint
+QSize QwtTextLabel::minimumSizeHint() const
+{
+ QSizeF sz = d_data->text.textSize( font() );
+
+ int mw = 2 * ( frameWidth() + d_data->margin );
+ int mh = mw;
+
+ int indent = d_data->indent;
+ if ( indent <= 0 )
+ indent = defaultIndent();
+
+ if ( indent > 0 )
+ {
+ const int align = d_data->text.renderFlags();
+ if ( align & Qt::AlignLeft || align & Qt::AlignRight )
+ mw += d_data->indent;
+ else if ( align & Qt::AlignTop || align & Qt::AlignBottom )
+ mh += d_data->indent;
+ }
+
+ sz += QSizeF( mw, mh );
+
+ return QSize( qCeil( sz.width() ), qCeil( sz.height() ) );
+}
+
+/*!
+ \param width Width
+ \return Preferred height for this widget, given the width.
+*/
+int QwtTextLabel::heightForWidth( int width ) const
+{
+ const int renderFlags = d_data->text.renderFlags();
+
+ int indent = d_data->indent;
+ if ( indent <= 0 )
+ indent = defaultIndent();
+
+ width -= 2 * frameWidth();
+ if ( renderFlags & Qt::AlignLeft || renderFlags & Qt::AlignRight )
+ width -= indent;
+
+ int height = qCeil( d_data->text.heightForWidth( width, font() ) );
+ if ( ( renderFlags & Qt::AlignTop ) || ( renderFlags & Qt::AlignBottom ) )
+ height += indent;
+
+ height += 2 * frameWidth();
+
+ return height;
+}
+
+/*!
+ Qt paint event
+ \param event Paint event
+*/
+void QwtTextLabel::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+
+ if ( !contentsRect().contains( event->rect() ) )
+ {
+ painter.save();
+ painter.setClipRegion( event->region() & frameRect() );
+ drawFrame( &painter );
+ painter.restore();
+ }
+
+ painter.setClipRegion( event->region() & contentsRect() );
+
+ drawContents( &painter );
+}
+
+//! Redraw the text and focus indicator
+void QwtTextLabel::drawContents( QPainter *painter )
+{
+ const QRect r = textRect();
+ if ( r.isEmpty() )
+ return;
+
+ painter->setFont( font() );
+ painter->setPen( palette().color( QPalette::Active, QPalette::Text ) );
+
+ drawText( painter, r );
+
+ if ( hasFocus() )
+ {
+ const int margin = 2;
+
+ QRect focusRect = contentsRect();
+ focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin,
+ focusRect.width() - 2 * margin - 2,
+ focusRect.height() - 2 * margin - 2 );
+
+ QwtPainter::drawFocusRect( painter, this, focusRect );
+ }
+}
+
+//! Redraw the text
+void QwtTextLabel::drawText( QPainter *painter, const QRect &textRect )
+{
+ d_data->text.draw( painter, textRect );
+}
+
+/*!
+ Calculate the rect for the text in widget coordinates
+ \return Text rect
+*/
+QRect QwtTextLabel::textRect() const
+{
+ QRect r = contentsRect();
+
+ if ( !r.isEmpty() && d_data->margin > 0 )
+ {
+ r.setRect( r.x() + d_data->margin, r.y() + d_data->margin,
+ r.width() - 2 * d_data->margin, r.height() - 2 * d_data->margin );
+ }
+
+ if ( !r.isEmpty() )
+ {
+ int indent = d_data->indent;
+ if ( indent <= 0 )
+ indent = defaultIndent();
+
+ if ( indent > 0 )
+ {
+ const int renderFlags = d_data->text.renderFlags();
+
+ if ( renderFlags & Qt::AlignLeft )
+ r.setX( r.x() + indent );
+ else if ( renderFlags & Qt::AlignRight )
+ r.setWidth( r.width() - indent );
+ else if ( renderFlags & Qt::AlignTop )
+ r.setY( r.y() + indent );
+ else if ( renderFlags & Qt::AlignBottom )
+ r.setHeight( r.height() - indent );
+ }
+ }
+
+ return r;
+}
+
+int QwtTextLabel::defaultIndent() const
+{
+ if ( frameWidth() <= 0 )
+ return 0;
+
+ QFont fnt;
+ if ( d_data->text.testPaintAttribute( QwtText::PaintUsingTextFont ) )
+ fnt = d_data->text.font();
+ else
+ fnt = font();
+
+ return QFontMetrics( fnt ).width( 'x' ) / 2;
+}
+
diff --git a/src/libpcp_qwt/src/qwt_text_label.h b/src/libpcp_qwt/src/qwt_text_label.h
new file mode 100644
index 0000000..44d3a56
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_text_label.h
@@ -0,0 +1,72 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_TEXT_LABEL_H
+#define QWT_TEXT_LABEL_H
+
+#include "qwt_global.h"
+#include "qwt_text.h"
+#include <qframe.h>
+
+class QString;
+class QPaintEvent;
+class QPainter;
+
+/*!
+ \brief A Widget which displays a QwtText
+*/
+
+class QWT_EXPORT QwtTextLabel : public QFrame
+{
+ Q_OBJECT
+
+ Q_PROPERTY( int indent READ indent WRITE setIndent )
+ Q_PROPERTY( int margin READ margin WRITE setMargin )
+
+public:
+ explicit QwtTextLabel( QWidget *parent = NULL );
+ explicit QwtTextLabel( const QwtText &, QWidget *parent = NULL );
+ virtual ~QwtTextLabel();
+
+public Q_SLOTS:
+ void setText( const QString &,
+ QwtText::TextFormat textFormat = QwtText::AutoText );
+ virtual void setText( const QwtText & );
+
+ void clear();
+
+public:
+ const QwtText &text() const;
+
+ int indent() const;
+ void setIndent( int );
+
+ int margin() const;
+ void setMargin( int );
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+ virtual int heightForWidth( int ) const;
+
+ QRect textRect() const;
+
+protected:
+ virtual void paintEvent( QPaintEvent *e );
+ virtual void drawContents( QPainter * );
+ virtual void drawText( QPainter *, const QRect & );
+
+private:
+ void init();
+ int defaultIndent() const;
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_thermo.cpp b/src/libpcp_qwt/src/qwt_thermo.cpp
new file mode 100644
index 0000000..6bfa8c4
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_thermo.cpp
@@ -0,0 +1,1036 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_thermo.h"
+#include "qwt_scale_engine.h"
+#include "qwt_scale_draw.h"
+#include "qwt_scale_map.h"
+#include "qwt_color_map.h"
+#include <qpainter.h>
+#include <qevent.h>
+#include <qdrawutil.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+
+static inline bool qwtIsLogarithmic( const QwtThermo *thermo )
+{
+ const QwtScaleTransformation::Type scaleType =
+ thermo->scaleEngine()->transformation()->type();
+
+ return ( scaleType == QwtScaleTransformation::Log10 );
+}
+
+static inline void qwtDrawLine(
+ QPainter *painter, int pos,
+ const QColor &color, const QRect pipeRect,
+ Qt::Orientation orientation )
+{
+ painter->setPen( color );
+ if ( orientation == Qt::Horizontal )
+ painter->drawLine( pos, pipeRect.top(), pos, pipeRect.bottom() );
+ else
+ painter->drawLine( pipeRect.left(), pos, pipeRect.right(), pos );
+}
+
+QVector<double> qwtTickList( const QwtScaleDiv &scaleDiv, double value )
+{
+ QVector<double> values;
+
+ double lowerLimit = scaleDiv.interval().minValue();
+ double upperLimit = scaleDiv.interval().maxValue();
+
+ if ( upperLimit < lowerLimit )
+ qSwap( lowerLimit, upperLimit );
+
+ if ( value < lowerLimit )
+ return values;
+
+ if ( value < upperLimit )
+ upperLimit = value;
+
+ values += lowerLimit;
+
+ for ( int tickType = QwtScaleDiv::MinorTick;
+ tickType < QwtScaleDiv::NTickTypes; tickType++ )
+ {
+ const QList<double> ticks = scaleDiv.ticks( tickType );
+
+ for ( int i = 0; i < ticks.count(); i++ )
+ {
+ const double v = ticks[i];
+ if ( v > lowerLimit && v < upperLimit )
+ values += v;
+ }
+ }
+
+ values += upperLimit;
+
+ return values;
+}
+
+class QwtThermo::PrivateData
+{
+public:
+ PrivateData():
+ orientation( Qt::Vertical ),
+ scalePos( QwtThermo::LeftScale ),
+ spacing( 3 ),
+ borderWidth( 2 ),
+ pipeWidth( 10 ),
+ minValue( 0.0 ),
+ maxValue( 0.0 ),
+ value( 0.0 ),
+ alarmLevel( 0.0 ),
+ alarmEnabled( false ),
+ autoFillPipe( true ),
+ colorMap( NULL )
+ {
+ rangeFlags = QwtInterval::IncludeBorders;
+ }
+
+ ~PrivateData()
+ {
+ delete colorMap;
+ }
+
+ QwtScaleMap map;
+
+ Qt::Orientation orientation;
+ ScalePos scalePos;
+ int spacing;
+ int borderWidth;
+ int pipeWidth;
+
+ double minValue;
+ double maxValue;
+ QwtInterval::BorderFlags rangeFlags;
+ double value;
+ double alarmLevel;
+ bool alarmEnabled;
+ bool autoFillPipe;
+
+ QwtColorMap *colorMap;
+};
+
+/*!
+ Constructor
+ \param parent Parent widget
+*/
+QwtThermo::QwtThermo( QWidget *parent ):
+ QWidget( parent )
+{
+ d_data = new PrivateData;
+ setRange( 0.0, 1.0, false );
+
+ QSizePolicy policy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
+ if ( d_data->orientation == Qt::Vertical )
+ policy.transpose();
+
+ setSizePolicy( policy );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+}
+
+//! Destructor
+QwtThermo::~QwtThermo()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Exclude/Include min/max values
+
+ According to the flags minValue() and maxValue()
+ are included/excluded from the pipe. In case of an
+ excluded value the corresponding tick is painted
+ 1 pixel off of the pipeRect().
+
+ F.e. when a minimum
+ of 0.0 has to be displayed as an empty pipe the minValue()
+ needs to be excluded.
+
+ \param flags Range flags
+ \sa rangeFlags()
+*/
+void QwtThermo::setRangeFlags( QwtInterval::BorderFlags flags )
+{
+ if ( d_data->rangeFlags != flags )
+ {
+ d_data->rangeFlags = flags;
+ update();
+ }
+}
+
+/*!
+ \return Range flags
+ \sa setRangeFlags()
+*/
+QwtInterval::BorderFlags QwtThermo::rangeFlags() const
+{
+ return d_data->rangeFlags;
+}
+
+/*!
+ Set the maximum value.
+
+ \param maxValue Maximum value
+ \sa maxValue(), setMinValue(), setRange()
+*/
+void QwtThermo::setMaxValue( double maxValue )
+{
+ setRange( d_data->minValue, maxValue, qwtIsLogarithmic( this ) );
+}
+
+//! Return the maximum value.
+double QwtThermo::maxValue() const
+{
+ return d_data->maxValue;
+}
+
+/*!
+ Set the minimum value.
+
+ \param minValue Minimum value
+ \sa minValue(), setMaxValue(), setRange()
+*/
+void QwtThermo::setMinValue( double minValue )
+{
+ setRange( minValue, d_data->maxValue, qwtIsLogarithmic( this ) );
+}
+
+//! Return the minimum value.
+double QwtThermo::minValue() const
+{
+ return d_data->minValue;
+}
+
+/*!
+ Set the current value.
+
+ \param value New Value
+ \sa value()
+*/
+void QwtThermo::setValue( double value )
+{
+ if ( d_data->value != value )
+ {
+ d_data->value = value;
+ update();
+ }
+}
+
+//! Return the value.
+double QwtThermo::value() const
+{
+ return d_data->value;
+}
+
+/*!
+ \brief Set a scale draw
+
+ For changing the labels of the scales, it
+ is necessary to derive from QwtScaleDraw and
+ overload QwtScaleDraw::label().
+
+ \param scaleDraw ScaleDraw object, that has to be created with
+ new and will be deleted in ~QwtThermo or the next
+ call of setScaleDraw().
+*/
+void QwtThermo::setScaleDraw( QwtScaleDraw *scaleDraw )
+{
+ setAbstractScaleDraw( scaleDraw );
+}
+
+/*!
+ \return the scale draw of the thermo
+ \sa setScaleDraw()
+*/
+const QwtScaleDraw *QwtThermo::scaleDraw() const
+{
+ return static_cast<const QwtScaleDraw *>( abstractScaleDraw() );
+}
+
+/*!
+ \return the scale draw of the thermo
+ \sa setScaleDraw()
+*/
+QwtScaleDraw *QwtThermo::scaleDraw()
+{
+ return static_cast<QwtScaleDraw *>( abstractScaleDraw() );
+}
+
+/*!
+ Qt paint event.
+ \param event Paint event
+*/
+void QwtThermo::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ const QRect tRect = pipeRect();
+
+ if ( !tRect.contains( event->rect() ) )
+ {
+ if ( d_data->scalePos != NoScale )
+ scaleDraw()->draw( &painter, palette() );
+ }
+
+ const int bw = d_data->borderWidth;
+
+ const QBrush brush = palette().brush( QPalette::Base );
+ qDrawShadePanel( &painter,
+ tRect.adjusted( -bw, -bw, bw, bw ),
+ palette(), true, bw,
+ d_data->autoFillPipe ? &brush : NULL );
+
+ drawLiquid( &painter, tRect );
+}
+
+/*!
+ Qt resize event handler
+ \param event Resize event
+*/
+void QwtThermo::resizeEvent( QResizeEvent *event )
+{
+ Q_UNUSED( event );
+ layoutThermo( false );
+}
+
+/*!
+ Qt change event handler
+ \param event Event
+*/
+void QwtThermo::changeEvent( QEvent *event )
+{
+ switch( event->type() )
+ {
+ case QEvent::StyleChange:
+ case QEvent::FontChange:
+ {
+ layoutThermo( true );
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/*!
+ Recalculate the QwtThermo geometry and layout based on
+ the QwtThermo::contentsRect() and the fonts.
+
+ \param update_geometry notify the layout system and call update
+ to redraw the scale
+*/
+void QwtThermo::layoutThermo( bool update_geometry )
+{
+ const QRect tRect = pipeRect();
+ const int bw = d_data->borderWidth + d_data->spacing;
+ const bool inverted = ( maxValue() < minValue() );
+
+ int from, to;
+
+ if ( d_data->orientation == Qt::Horizontal )
+ {
+ from = tRect.left();
+ to = tRect.right();
+
+ if ( d_data->rangeFlags & QwtInterval::ExcludeMinimum )
+ {
+ if ( inverted )
+ to++;
+ else
+ from--;
+ }
+ if ( d_data->rangeFlags & QwtInterval::ExcludeMaximum )
+ {
+ if ( inverted )
+ from--;
+ else
+ to++;
+ }
+
+ switch ( d_data->scalePos )
+ {
+ case TopScale:
+ {
+ scaleDraw()->setAlignment( QwtScaleDraw::TopScale );
+ scaleDraw()->move( from, tRect.top() - bw );
+ scaleDraw()->setLength( to - from );
+ break;
+ }
+
+ case BottomScale:
+ case NoScale:
+ default:
+ {
+ scaleDraw()->setAlignment( QwtScaleDraw::BottomScale );
+ scaleDraw()->move( from, tRect.bottom() + bw );
+ scaleDraw()->setLength( to - from );
+ break;
+ }
+ }
+
+ d_data->map.setPaintInterval( from, to );
+ }
+ else // Qt::Vertical
+ {
+ from = tRect.top();
+ to = tRect.bottom();
+
+ if ( d_data->rangeFlags & QwtInterval::ExcludeMinimum )
+ {
+ if ( inverted )
+ from--;
+ else
+ to++;
+ }
+ if ( d_data->rangeFlags & QwtInterval::ExcludeMaximum )
+ {
+ if ( inverted )
+ to++;
+ else
+ from--;
+ }
+
+ switch ( d_data->scalePos )
+ {
+ case RightScale:
+ {
+ scaleDraw()->setAlignment( QwtScaleDraw::RightScale );
+ scaleDraw()->move( tRect.right() + bw, from );
+ scaleDraw()->setLength( to - from );
+ break;
+ }
+
+ case LeftScale:
+ case NoScale:
+ default:
+ {
+ scaleDraw()->setAlignment( QwtScaleDraw::LeftScale );
+ scaleDraw()->move( tRect.left() - bw, from );
+ scaleDraw()->setLength( to - from );
+ break;
+ }
+ }
+ d_data->map.setPaintInterval( to, from );
+ }
+
+ if ( update_geometry )
+ {
+ updateGeometry();
+ update();
+ }
+}
+
+/*!
+ \return Bounding rectangle of the pipe ( without borders )
+ in widget coordinates
+*/
+QRect QwtThermo::pipeRect() const
+{
+ const QRect cr = contentsRect();
+
+ int mbd = 0;
+ if ( d_data->scalePos != NoScale )
+ {
+ int d1, d2;
+ scaleDraw()->getBorderDistHint( font(), d1, d2 );
+ mbd = qMax( d1, d2 );
+ }
+ const int bw = d_data->borderWidth;
+
+ QRect tRect;
+ if ( d_data->orientation == Qt::Horizontal )
+ {
+ switch ( d_data->scalePos )
+ {
+ case TopScale:
+ {
+ tRect.setRect(
+ cr.x() + mbd + bw,
+ cr.y() + cr.height() - d_data->pipeWidth - bw,
+ cr.width() - 2 * ( bw + mbd ),
+ d_data->pipeWidth
+ );
+ break;
+ }
+
+ case BottomScale:
+ case NoScale:
+ default:
+ {
+ tRect.setRect(
+ cr.x() + mbd + bw,
+ cr.y() + bw,
+ cr.width() - 2 * ( bw + mbd ),
+ d_data->pipeWidth
+ );
+ break;
+ }
+ }
+ }
+ else // Qt::Vertical
+ {
+ switch ( d_data->scalePos )
+ {
+ case RightScale:
+ {
+ tRect.setRect(
+ cr.x() + bw,
+ cr.y() + mbd + bw,
+ d_data->pipeWidth,
+ cr.height() - 2 * ( bw + mbd )
+ );
+ break;
+ }
+ case LeftScale:
+ case NoScale:
+ default:
+ {
+ tRect.setRect(
+ cr.x() + cr.width() - bw - d_data->pipeWidth,
+ cr.y() + mbd + bw,
+ d_data->pipeWidth,
+ cr.height() - 2 * ( bw + mbd ) );
+ break;
+ }
+ }
+ }
+
+ return tRect;
+}
+
+/*!
+ \brief Set the thermometer orientation and the scale position.
+
+ The scale position NoScale disables the scale.
+ \param o orientation. Possible values are Qt::Horizontal and Qt::Vertical.
+ The default value is Qt::Vertical.
+ \param s Position of the scale.
+ The default value is NoScale.
+
+ A valid combination of scale position and orientation is enforced:
+ - a horizontal thermometer can have the scale positions TopScale,
+ BottomScale or NoScale;
+ - a vertical thermometer can have the scale positions LeftScale,
+ RightScale or NoScale;
+ - an invalid scale position will default to NoScale.
+
+ \sa setScalePosition()
+*/
+void QwtThermo::setOrientation( Qt::Orientation o, ScalePos s )
+{
+ if ( o == d_data->orientation && s == d_data->scalePos )
+ return;
+
+ switch ( o )
+ {
+ case Qt::Horizontal:
+ {
+ if ( ( s == NoScale ) || ( s == BottomScale ) || ( s == TopScale ) )
+ d_data->scalePos = s;
+ else
+ d_data->scalePos = NoScale;
+ break;
+ }
+ case Qt::Vertical:
+ {
+ if ( ( s == NoScale ) || ( s == LeftScale ) || ( s == RightScale ) )
+ d_data->scalePos = s;
+ else
+ d_data->scalePos = NoScale;
+ break;
+ }
+ }
+
+ if ( o != d_data->orientation )
+ {
+ if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
+ {
+ QSizePolicy sp = sizePolicy();
+ sp.transpose();
+ setSizePolicy( sp );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+ }
+ }
+
+ d_data->orientation = o;
+ layoutThermo( true );
+}
+
+/*!
+ \brief Change the scale position (and thermometer orientation).
+
+ \param scalePos Position of the scale.
+
+ A valid combination of scale position and orientation is enforced:
+ - if the new scale position is LeftScale or RightScale, the
+ scale orientation will become Qt::Vertical;
+ - if the new scale position is BottomScale or TopScale, the scale
+ orientation will become Qt::Horizontal;
+ - if the new scale position is NoScale, the scale orientation
+ will not change.
+
+ \sa setOrientation(), scalePosition()
+*/
+void QwtThermo::setScalePosition( ScalePos scalePos )
+{
+ if ( ( scalePos == BottomScale ) || ( scalePos == TopScale ) )
+ setOrientation( Qt::Horizontal, scalePos );
+ else if ( ( scalePos == LeftScale ) || ( scalePos == RightScale ) )
+ setOrientation( Qt::Vertical, scalePos );
+ else
+ setOrientation( d_data->orientation, NoScale );
+}
+
+/*!
+ Return the scale position.
+ \sa setScalePosition()
+*/
+QwtThermo::ScalePos QwtThermo::scalePosition() const
+{
+ return d_data->scalePos;
+}
+
+//! Notify a scale change.
+void QwtThermo::scaleChange()
+{
+ layoutThermo( true );
+}
+
+/*!
+ Redraw the liquid in thermometer pipe.
+ \param painter Painter
+ \param pipeRect Bounding rectangle of the pipe without borders
+*/
+void QwtThermo::drawLiquid(
+ QPainter *painter, const QRect &pipeRect ) const
+{
+ painter->save();
+ painter->setClipRect( pipeRect, Qt::IntersectClip );
+
+ const bool inverted = ( maxValue() < minValue() );
+ if ( d_data->colorMap != NULL )
+ {
+ QwtInterval interval( d_data->minValue, d_data->maxValue );
+ interval = interval.normalized();
+
+ // Because the positions of the ticks are rounded
+ // we calculate the colors for the rounded tick values
+
+ QVector<double> values = qwtTickList(
+ scaleDraw()->scaleDiv(), d_data->value );
+
+ if ( d_data->map.isInverting() )
+ qSort( values.begin(), values.end(), qGreater<double>() );
+ else
+ qSort( values.begin(), values.end(), qLess<double>() );
+
+ int from;
+ if ( !values.isEmpty() )
+ {
+ from = qRound( d_data->map.transform( values[0] ) );
+ qwtDrawLine( painter, from,
+ d_data->colorMap->color( interval, values[0] ),
+ pipeRect, d_data->orientation );
+ }
+
+ for ( int i = 1; i < values.size(); i++ )
+ {
+ const int to = qRound( d_data->map.transform( values[i] ) );
+
+ for ( int pos = from + 1; pos < to; pos++ )
+ {
+ const double v = d_data->map.invTransform( pos );
+
+ qwtDrawLine( painter, pos,
+ d_data->colorMap->color( interval, v ),
+ pipeRect, d_data->orientation );
+ }
+ qwtDrawLine( painter, to,
+ d_data->colorMap->color( interval, values[i] ),
+ pipeRect, d_data->orientation );
+
+ from = to;
+ }
+ }
+ else
+ {
+ const int tval = qRound( d_data->map.transform( d_data->value ) );
+
+ QRect fillRect = pipeRect;
+ if ( d_data->orientation == Qt::Horizontal )
+ {
+ if ( inverted )
+ fillRect.setLeft( tval );
+ else
+ fillRect.setRight( tval );
+ }
+ else // Qt::Vertical
+ {
+ if ( inverted )
+ fillRect.setBottom( tval );
+ else
+ fillRect.setTop( tval );
+ }
+
+ if ( d_data->alarmEnabled &&
+ d_data->value >= d_data->alarmLevel )
+ {
+ QRect alarmRect = fillRect;
+
+ const int taval = qRound( d_data->map.transform( d_data->alarmLevel ) );
+ if ( d_data->orientation == Qt::Horizontal )
+ {
+ if ( inverted )
+ alarmRect.setRight( taval );
+ else
+ alarmRect.setLeft( taval );
+ }
+ else
+ {
+ if ( inverted )
+ alarmRect.setTop( taval );
+ else
+ alarmRect.setBottom( taval );
+ }
+
+ fillRect = QRegion( fillRect ).subtracted( alarmRect ).boundingRect();
+
+ painter->fillRect( alarmRect, palette().brush( QPalette::Highlight ) );
+ }
+
+ painter->fillRect( fillRect, palette().brush( QPalette::ButtonText ) );
+ }
+
+ painter->restore();
+}
+
+/*!
+ \brief Change the spacing between pipe and scale
+
+ A spacing of 0 means, that the backbone of the scale is below
+ the pipe.
+
+ The default setting is 3 pixels.
+
+ \param spacing Number of pixels
+ \sa spacing();
+*/
+void QwtThermo::setSpacing( int spacing )
+{
+ if ( spacing <= 0 )
+ spacing = 0;
+
+ if ( spacing != d_data->spacing )
+ {
+ d_data->spacing = spacing;
+ layoutThermo( true );
+ }
+}
+
+/*!
+ \return Number of pixels between pipe and scale
+ \sa setSpacing()
+*/
+int QwtThermo::spacing() const
+{
+ return d_data->spacing;
+}
+
+/*!
+ Set the border width of the pipe.
+ \param width Border width
+ \sa borderWidth()
+*/
+void QwtThermo::setBorderWidth( int width )
+{
+ if ( width <= 0 )
+ width = 0;
+
+ if ( width != d_data->borderWidth )
+ {
+ d_data->borderWidth = width;
+ layoutThermo( true );
+ }
+}
+
+/*!
+ Return the border width of the thermometer pipe.
+ \sa setBorderWidth()
+*/
+int QwtThermo::borderWidth() const
+{
+ return d_data->borderWidth;
+}
+
+/*!
+ \brief Set the range
+
+ \param minValue value corresponding lower or left end
+ of the thermometer
+ \param maxValue value corresponding to the upper or
+ right end of the thermometer
+ \param logarithmic logarithmic mapping, true or false
+*/
+void QwtThermo::setRange(
+ double minValue, double maxValue, bool logarithmic )
+{
+ if ( minValue == d_data->minValue && maxValue == d_data->maxValue
+ && logarithmic == qwtIsLogarithmic( this ) )
+ {
+ return;
+ }
+
+ if ( logarithmic != qwtIsLogarithmic( this ) )
+ {
+ if ( logarithmic )
+ setScaleEngine( new QwtLog10ScaleEngine );
+ else
+ setScaleEngine( new QwtLinearScaleEngine );
+ }
+
+ d_data->minValue = minValue;
+ d_data->maxValue = maxValue;
+
+ /*
+ There are two different maps, one for the scale, the other
+ for the values. This is confusing and will be changed
+ in the future. TODO ...
+ */
+
+ d_data->map.setTransformation( scaleEngine()->transformation() );
+ d_data->map.setScaleInterval( minValue, maxValue );
+
+ if ( autoScale() )
+ rescale( minValue, maxValue );
+
+ layoutThermo( true );
+}
+
+/*!
+ \brief Assign a color map for the fill color
+
+ \param colorMap Color map
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+void QwtThermo::setColorMap( QwtColorMap *colorMap )
+{
+ if ( colorMap != d_data->colorMap )
+ {
+ delete d_data->colorMap;
+ d_data->colorMap = colorMap;
+ }
+}
+
+/*!
+ \return Color map for the fill color
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+QwtColorMap *QwtThermo::colorMap()
+{
+ return d_data->colorMap;
+}
+
+/*!
+ \return Color map for the fill color
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+const QwtColorMap *QwtThermo::colorMap() const
+{
+ return d_data->colorMap;
+}
+
+/*!
+ \brief Change the brush of the liquid.
+
+ Changes the QPalette::ButtonText brush of the palette.
+
+ \param brush New brush.
+ \sa fillBrush(), QWidget::setPalette()
+*/
+void QwtThermo::setFillBrush( const QBrush& brush )
+{
+ QPalette pal = palette();
+ pal.setBrush( QPalette::ButtonText, brush );
+ setPalette( pal );
+}
+
+/*!
+ Return the liquid ( QPalette::ButtonText ) brush.
+ \sa setFillBrush(), QWidget::palette()
+*/
+const QBrush& QwtThermo::fillBrush() const
+{
+ return palette().brush( QPalette::ButtonText );
+}
+
+/*!
+ \brief Specify the liquid brush above the alarm threshold
+
+ Changes the QPalette::Highlight brush of the palette.
+
+ \param brush New brush.
+ \sa alarmBrush(), QWidget::setPalette()
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+void QwtThermo::setAlarmBrush( const QBrush& brush )
+{
+ QPalette pal = palette();
+ pal.setBrush( QPalette::Highlight, brush );
+ setPalette( pal );
+}
+
+/*!
+ Return the liquid brush ( QPalette::Highlight ) above the alarm threshold.
+ \sa setAlarmBrush(), QWidget::palette()
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+const QBrush& QwtThermo::alarmBrush() const
+{
+ return palette().brush( QPalette::Highlight );
+}
+
+/*!
+ Specify the alarm threshold.
+
+ \param level Alarm threshold
+ \sa alarmLevel()
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+void QwtThermo::setAlarmLevel( double level )
+{
+ d_data->alarmLevel = level;
+ d_data->alarmEnabled = 1;
+ update();
+}
+
+/*!
+ Return the alarm threshold.
+ \sa setAlarmLevel()
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+double QwtThermo::alarmLevel() const
+{
+ return d_data->alarmLevel;
+}
+
+/*!
+ Change the width of the pipe.
+
+ \param width Width of the pipe
+ \sa pipeWidth()
+*/
+void QwtThermo::setPipeWidth( int width )
+{
+ if ( width > 0 )
+ {
+ d_data->pipeWidth = width;
+ layoutThermo( true );
+ }
+}
+
+/*!
+ Return the width of the pipe.
+ \sa setPipeWidth()
+*/
+int QwtThermo::pipeWidth() const
+{
+ return d_data->pipeWidth;
+}
+
+/*!
+ \brief Enable or disable the alarm threshold
+ \param tf true (disabled) or false (enabled)
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+void QwtThermo::setAlarmEnabled( bool tf )
+{
+ d_data->alarmEnabled = tf;
+ update();
+}
+
+/*!
+ \return True, when the alarm threshold is enabled.
+
+ \warning The alarm threshold has no effect, when
+ a color map has been assigned
+*/
+bool QwtThermo::alarmEnabled() const
+{
+ return d_data->alarmEnabled;
+}
+
+/*!
+ \return the minimum size hint
+ \sa minimumSizeHint()
+*/
+QSize QwtThermo::sizeHint() const
+{
+ return minimumSizeHint();
+}
+
+/*!
+ \brief Return a minimum size hint
+ \warning The return value depends on the font and the scale.
+ \sa sizeHint()
+*/
+QSize QwtThermo::minimumSizeHint() const
+{
+ int w = 0, h = 0;
+
+ if ( d_data->scalePos != NoScale )
+ {
+ const int sdExtent = qCeil( scaleDraw()->extent( font() ) );
+ const int sdLength = scaleDraw()->minLength( font() );
+
+ w = sdLength;
+ h = d_data->pipeWidth + sdExtent + d_data->spacing;
+
+ }
+ else // no scale
+ {
+ w = 200;
+ h = d_data->pipeWidth;
+ }
+
+ if ( d_data->orientation == Qt::Vertical )
+ qSwap( w, h );
+
+ w += 2 * d_data->borderWidth;
+ h += 2 * d_data->borderWidth;
+
+ // finally add the margins
+ int left, right, top, bottom;
+ getContentsMargins( &left, &top, &right, &bottom );
+ w += left + right;
+ h += top + bottom;
+
+ return QSize( w, h );
+}
diff --git a/src/libpcp_qwt/src/qwt_thermo.h b/src/libpcp_qwt/src/qwt_thermo.h
new file mode 100644
index 0000000..e43ab12
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_thermo.h
@@ -0,0 +1,202 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_THERMO_H
+#define QWT_THERMO_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_scale.h"
+#include "qwt_interval.h"
+#include <qwidget.h>
+
+class QwtScaleDraw;
+class QwtColorMap;
+
+/*!
+ \brief The Thermometer Widget
+
+ QwtThermo is a widget which displays a value in an interval. It supports:
+ - a horizontal or vertical layout;
+ - a range;
+ - a scale;
+ - an alarm level.
+
+ \image html sysinfo.png
+
+ The fill colors might be calculated from an optional color map
+ If no color map has been assigned QwtThermo uses the
+ following colors/brushes from the widget palette:
+
+ - QPalette::Base
+ Background of the pipe
+ - QPalette::ButtonText
+ Fill brush below the alarm level
+ - QPalette::Highlight
+ Fill brush for the values above the alarm level
+ - QPalette::WindowText
+ For the axis of the scale
+ - QPalette::Text
+ For the labels of the scale
+
+ By default, the scale and range run over the same interval of values.
+ QwtAbstractScale::setScale() changes the interval of the scale and allows
+ easy conversion between physical units.
+
+ The example shows how to make the scale indicate in degrees Fahrenheit and
+ to set the value in degrees Kelvin:
+\code
+#include <qapplication.h>
+#include <qwt_thermo.h>
+
+double Kelvin2Fahrenheit(double kelvin)
+{
+ // see http://en.wikipedia.org/wiki/Kelvin
+ return 1.8*kelvin - 459.67;
+}
+
+int main(int argc, char **argv)
+{
+ const double minKelvin = 0.0;
+ const double maxKelvin = 500.0;
+
+ QApplication a(argc, argv);
+ QwtThermo t;
+ t.setRange(minKelvin, maxKelvin);
+ t.setScale(Kelvin2Fahrenheit(minKelvin), Kelvin2Fahrenheit(maxKelvin));
+ // set the value in Kelvin but the scale displays in Fahrenheit
+ // 273.15 Kelvin = 0 Celsius = 32 Fahrenheit
+ t.setValue(273.15);
+ a.setMainWidget(&t);
+ t.show();
+ return a.exec();
+}
+\endcode
+
+ \todo Improve the support for a logarithmic range and/or scale.
+*/
+class QWT_EXPORT QwtThermo: public QWidget, public QwtAbstractScale
+{
+ Q_OBJECT
+
+ Q_ENUMS( ScalePos )
+
+ Q_PROPERTY( bool alarmEnabled READ alarmEnabled WRITE setAlarmEnabled )
+ Q_PROPERTY( double alarmLevel READ alarmLevel WRITE setAlarmLevel )
+ Q_PROPERTY( ScalePos scalePosition READ scalePosition
+ WRITE setScalePosition )
+ Q_PROPERTY( int spacing READ spacing WRITE setSpacing )
+ Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth )
+ Q_PROPERTY( double maxValue READ maxValue WRITE setMaxValue )
+ Q_PROPERTY( double minValue READ minValue WRITE setMinValue )
+ Q_PROPERTY( int pipeWidth READ pipeWidth WRITE setPipeWidth )
+ Q_PROPERTY( double value READ value WRITE setValue )
+
+public:
+ /*!
+ Scale position. QwtThermo tries to enforce valid combinations of its
+ orientation and scale position:
+
+ - Qt::Horizonal combines with NoScale, TopScale and BottomScale
+ - Qt::Vertical combines with NoScale, LeftScale and RightScale
+
+ \sa setOrientation(), setScalePosition()
+ */
+ enum ScalePos
+ {
+ //! No scale
+ NoScale,
+
+ //! The scale is left of the pipe
+ LeftScale,
+
+ //! The scale is right of the pipe
+ RightScale,
+
+ //! The scale is above the pipe
+ TopScale,
+
+ //! The scale is below the pipe
+ BottomScale
+ };
+
+ explicit QwtThermo( QWidget *parent = NULL );
+ virtual ~QwtThermo();
+
+ void setOrientation( Qt::Orientation, ScalePos );
+
+ void setScalePosition( ScalePos s );
+ ScalePos scalePosition() const;
+
+ void setSpacing( int );
+ int spacing() const;
+
+ void setBorderWidth( int w );
+ int borderWidth() const;
+
+ void setFillBrush( const QBrush &b );
+ const QBrush &fillBrush() const;
+
+ void setAlarmBrush( const QBrush &b );
+ const QBrush &alarmBrush() const;
+
+ void setAlarmLevel( double v );
+ double alarmLevel() const;
+
+ void setAlarmEnabled( bool tf );
+ bool alarmEnabled() const;
+
+ void setColorMap( QwtColorMap * );
+ QwtColorMap *colorMap();
+ const QwtColorMap *colorMap() const;
+
+ void setPipeWidth( int w );
+ int pipeWidth() const;
+
+ void setRangeFlags( QwtInterval::BorderFlags );
+ QwtInterval::BorderFlags rangeFlags() const;
+
+ void setMaxValue( double v );
+ double maxValue() const;
+
+ void setMinValue( double v );
+ double minValue() const;
+
+ double value() const;
+
+ void setRange( double vmin, double vmax, bool lg = false );
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setScaleDraw( QwtScaleDraw * );
+ const QwtScaleDraw *scaleDraw() const;
+
+public Q_SLOTS:
+ virtual void setValue( double val );
+
+protected:
+ virtual void drawLiquid( QPainter *, const QRect & ) const;
+ virtual void scaleChange();
+
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+ virtual void changeEvent( QEvent * );
+
+ QwtScaleDraw *scaleDraw();
+
+ QRect pipeRect() const;
+
+private:
+ void layoutThermo( bool );
+
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_qwt/src/qwt_wheel.cpp b/src/libpcp_qwt/src/qwt_wheel.cpp
new file mode 100644
index 0000000..1379e18
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_wheel.cpp
@@ -0,0 +1,544 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#include "qwt_wheel.h"
+#include "qwt_math.h"
+#include "qwt_painter.h"
+#include <qevent.h>
+#include <qdrawutil.h>
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qstyleoption.h>
+#include <qapplication.h>
+
+#if QT_VERSION < 0x040601
+#define qFastSin(x) ::sin(x)
+#endif
+
+class QwtWheel::PrivateData
+{
+public:
+ PrivateData()
+ {
+ viewAngle = 175.0;
+ totalAngle = 360.0;
+ tickCnt = 10;
+ wheelBorderWidth = 2;
+ borderWidth = 2;
+ wheelWidth = 20;
+ };
+
+ double viewAngle;
+ double totalAngle;
+ int tickCnt;
+ int wheelBorderWidth;
+ int borderWidth;
+ int wheelWidth;
+};
+
+//! Constructor
+QwtWheel::QwtWheel( QWidget *parent ):
+ QwtAbstractSlider( Qt::Horizontal, parent )
+{
+ d_data = new PrivateData;
+
+ setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+ setUpdateTime( 50 );
+}
+
+//! Destructor
+QwtWheel::~QwtWheel()
+{
+ delete d_data;
+}
+
+/*!
+ \brief Adjust the number of grooves in the wheel's surface.
+
+ The number of grooves is limited to 6 <= cnt <= 50.
+ Values outside this range will be clipped.
+ The default value is 10.
+
+ \param cnt Number of grooves per 360 degrees
+ \sa tickCnt()
+*/
+void QwtWheel::setTickCnt( int cnt )
+{
+ d_data->tickCnt = qBound( 6, cnt, 50 );
+ update();
+}
+
+/*!
+ \return Number of grooves in the wheel's surface.
+ \sa setTickCnt()
+*/
+int QwtWheel::tickCnt() const
+{
+ return d_data->tickCnt;
+}
+
+/*!
+ \return mass
+*/
+double QwtWheel::mass() const
+{
+ return QwtAbstractSlider::mass();
+}
+
+/*!
+ \brief Set the wheel border width of the wheel.
+
+ The wheel border must not be smaller than 1
+ and is limited in dependence on the wheel's size.
+ Values outside the allowed range will be clipped.
+
+ The wheel border defaults to 2.
+
+ \param borderWidth Border width
+ \sa internalBorder()
+*/
+void QwtWheel::setWheelBorderWidth( int borderWidth )
+{
+ const int d = qMin( width(), height() ) / 3;
+ borderWidth = qMin( borderWidth, d );
+ d_data->wheelBorderWidth = qMax( borderWidth, 1 );
+ update();
+}
+
+/*!
+ \return Wheel border width
+ \sa setWheelBorderWidth()
+*/
+int QwtWheel::wheelBorderWidth() const
+{
+ return d_data->wheelBorderWidth;
+}
+
+/*!
+ \brief Set the border width
+
+ The border defaults to 2.
+
+ \param width Border width
+ \sa borderWidth()
+*/
+void QwtWheel::setBorderWidth( int width )
+{
+ d_data->borderWidth = qMax( width, 0 );
+ update();
+}
+
+/*!
+ \return Border width
+ \sa setBorderWidth()
+*/
+int QwtWheel::borderWidth() const
+{
+ return d_data->borderWidth;
+}
+
+/*!
+ \return Rectangle of the wheel without the outer border
+*/
+QRect QwtWheel::wheelRect() const
+{
+ const int bw = d_data->borderWidth;
+ return contentsRect().adjusted( bw, bw, -bw, -bw );
+}
+
+/*!
+ \brief Set the total angle which the wheel can be turned.
+
+ One full turn of the wheel corresponds to an angle of
+ 360 degrees. A total angle of n*360 degrees means
+ that the wheel has to be turned n times around its axis
+ to get from the minimum value to the maximum value.
+
+ The default setting of the total angle is 360 degrees.
+
+ \param angle total angle in degrees
+ \sa totalAngle()
+*/
+void QwtWheel::setTotalAngle( double angle )
+{
+ if ( angle < 0.0 )
+ angle = 0.0;
+
+ d_data->totalAngle = angle;
+ update();
+}
+
+/*!
+ \return Total angle which the wheel can be turned.
+ \sa setTotalAngle()
+*/
+double QwtWheel::totalAngle() const
+{
+ return d_data->totalAngle;
+}
+
+/*!
+ \brief Set the wheel's orientation.
+ \param o Orientation. Allowed values are
+ Qt::Horizontal and Qt::Vertical.
+ Defaults to Qt::Horizontal.
+ \sa QwtAbstractSlider::orientation()
+*/
+void QwtWheel::setOrientation( Qt::Orientation o )
+{
+ if ( orientation() == o )
+ return;
+
+ if ( !testAttribute( Qt::WA_WState_OwnSizePolicy ) )
+ {
+ QSizePolicy sp = sizePolicy();
+ sp.transpose();
+ setSizePolicy( sp );
+
+ setAttribute( Qt::WA_WState_OwnSizePolicy, false );
+ }
+
+ QwtAbstractSlider::setOrientation( o );
+ update();
+}
+
+/*!
+ \brief Specify the visible portion of the wheel.
+
+ You may use this function for fine-tuning the appearance of
+ the wheel. The default value is 175 degrees. The value is
+ limited from 10 to 175 degrees.
+
+ \param angle Visible angle in degrees
+ \sa viewAngle(), setTotalAngle()
+*/
+void QwtWheel::setViewAngle( double angle )
+{
+ d_data->viewAngle = qBound( 10.0, angle, 175.0 );
+ update();
+}
+
+/*!
+ \return Visible portion of the wheel
+ \sa setViewAngle(), totalAngle()
+*/
+double QwtWheel::viewAngle() const
+{
+ return d_data->viewAngle;
+}
+
+//! Determine the value corresponding to a specified point
+double QwtWheel::getValue( const QPoint &p )
+{
+ const QRectF rect = wheelRect();
+
+ // The reference position is arbitrary, but the
+ // sign of the offset is important
+ double w, dx;
+ if ( orientation() == Qt::Vertical )
+ {
+ w = rect.height();
+ dx = rect.y() - p.y();
+ }
+ else
+ {
+ w = rect.width();
+ dx = p.x() - rect.x();
+ }
+
+ if ( w == 0.0 )
+ return 0.0;
+
+ // w pixels is an arc of viewAngle degrees,
+ // so we convert change in pixels to change in angle
+ const double ang = dx * d_data->viewAngle / w;
+
+ // value range maps to totalAngle degrees,
+ // so convert the change in angle to a change in value
+ const double val = ang * ( maxValue() - minValue() ) / d_data->totalAngle;
+
+ // Note, range clamping and rasterizing to step is automatically
+ // handled by QwtAbstractSlider, so we simply return the change in value
+ return val;
+}
+
+/*!
+ \brief Qt Resize Event
+ \param event Resize event
+*/
+void QwtWheel::resizeEvent( QResizeEvent *event )
+{
+ QwtAbstractSlider::resizeEvent( event );
+}
+
+/*!
+ \brief Qt Paint Event
+ \param event Paint event
+*/
+void QwtWheel::paintEvent( QPaintEvent *event )
+{
+ QPainter painter( this );
+ painter.setClipRegion( event->region() );
+
+ QStyleOption opt;
+ opt.init(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
+
+ qDrawShadePanel( &painter,
+ contentsRect(), palette(), true, d_data->borderWidth );
+
+ drawWheelBackground( &painter, wheelRect() );
+ drawTicks( &painter, wheelRect() );
+
+ if ( hasFocus() )
+ QwtPainter::drawFocusRect( &painter, this );
+}
+
+/*!
+ Draw the Wheel's background gradient
+
+ \param painter Painter
+ \param rect Rectangle for the wheel
+*/
+void QwtWheel::drawWheelBackground(
+ QPainter *painter, const QRectF &rect )
+{
+ painter->save();
+
+ QPalette pal = palette();
+
+ // draw shaded background
+ QLinearGradient gradient( rect.topLeft(),
+ ( orientation() == Qt::Horizontal ) ? rect.topRight() : rect.bottomLeft() );
+ gradient.setColorAt( 0.0, pal.color( QPalette::Button ) );
+ gradient.setColorAt( 0.2, pal.color( QPalette::Light ) );
+ gradient.setColorAt( 0.7, pal.color( QPalette::Mid ) );
+ gradient.setColorAt( 1.0, pal.color( QPalette::Dark ) );
+
+ painter->fillRect( rect, gradient );
+
+ // draw internal border
+
+ const QPen lightPen( palette().color( QPalette::Light ),
+ d_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap );
+ const QPen darkPen( pal.color( QPalette::Dark ),
+ d_data->wheelBorderWidth, Qt::SolidLine, Qt::FlatCap );
+
+ const double bw2 = 0.5 * d_data->wheelBorderWidth;
+
+ if ( orientation() == Qt::Horizontal )
+ {
+ painter->setPen( lightPen );
+ painter->drawLine( QPointF( rect.left(), rect.top() + bw2 ),
+ QPointF( rect.right(), rect.top() + bw2 ) );
+
+ painter->setPen( darkPen );
+ painter->drawLine( QPointF( rect.left(), rect.bottom() - bw2 ),
+ QPointF( rect.right(), rect.bottom() - bw2 ) );
+ }
+ else // Qt::Vertical
+ {
+ painter->setPen( lightPen );
+ painter->drawLine( QPointF( rect.left() + bw2, rect.top() ),
+ QPointF( rect.left() + bw2, rect.bottom() ) );
+
+ painter->setPen( darkPen );
+ painter->drawLine( QPointF( rect.right() - bw2, rect.top() ),
+ QPointF( rect.right() - bw2, rect.bottom() ) );
+ }
+
+ painter->restore();
+}
+
+/*!
+ Draw the Wheel's ticks
+
+ \param painter Painter
+ \param rect Rectangle for the wheel
+*/
+void QwtWheel::drawTicks( QPainter *painter, const QRectF &rect )
+{
+ if ( maxValue() == minValue() || d_data->totalAngle == 0.0 )
+ {
+ return;
+ }
+
+ const QPen lightPen( palette().color( QPalette::Light ),
+ 0, Qt::SolidLine, Qt::FlatCap );
+ const QPen darkPen( palette().color( QPalette::Dark ),
+ 0, Qt::SolidLine, Qt::FlatCap );
+
+ const double sign = ( minValue() < maxValue() ) ? 1.0 : -1.0;
+ const double cnvFactor = qAbs( d_data->totalAngle / ( maxValue() - minValue() ) );
+ const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor;
+ const double loValue = value() - halfIntv;
+ const double hiValue = value() + halfIntv;
+ const double tickWidth = 360.0 / double( d_data->tickCnt ) / cnvFactor;
+ const double sinArc = qFastSin( d_data->viewAngle * M_PI / 360.0 );
+
+ if ( orientation() == Qt::Horizontal )
+ {
+ const double halfSize = rect.width() * 0.5;
+
+ double l1 = rect.top() + d_data->wheelBorderWidth;
+ double l2 = rect.bottom() - d_data->wheelBorderWidth - 1;
+
+ // draw one point over the border if border > 1
+ if ( d_data->wheelBorderWidth > 1 )
+ {
+ l1--;
+ l2++;
+ }
+
+ const double maxpos = rect.right() - 2;
+ const double minpos = rect.left() + 2;
+
+ // draw tick marks
+ for ( double tickValue = ::ceil( loValue / tickWidth ) * tickWidth;
+ tickValue < hiValue; tickValue += tickWidth )
+ {
+ const double angle = ( tickValue - value() ) * M_PI / 180.0;
+ const double s = qFastSin( angle * cnvFactor );
+
+ const double tickPos =
+ rect.right() - halfSize * ( sinArc + sign * s ) / sinArc;
+
+ if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) )
+ {
+ painter->setPen( darkPen );
+ painter->drawLine( QPointF( tickPos - 1 , l1 ),
+ QPointF( tickPos - 1, l2 ) );
+ painter->setPen( lightPen );
+ painter->drawLine( QPointF( tickPos, l1 ),
+ QPointF( tickPos, l2 ) );
+ }
+ }
+ }
+ else // Qt::Vertical
+ {
+ const double halfSize = rect.height() * 0.5;
+
+ double l1 = rect.left() + d_data->wheelBorderWidth;
+ double l2 = rect.right() - d_data->wheelBorderWidth - 1;
+
+ if ( d_data->wheelBorderWidth > 1 )
+ {
+ l1--;
+ l2++;
+ }
+
+ const double maxpos = rect.bottom() - 2;
+ const double minpos = rect.top() + 2;
+
+ for ( double tickValue = ::ceil( loValue / tickWidth ) * tickWidth;
+ tickValue < hiValue; tickValue += tickWidth )
+ {
+ const double angle = ( tickValue - value() ) * M_PI / 180.0;
+ const double s = qFastSin( angle * cnvFactor );
+
+ const double tickPos =
+ rect.y() + halfSize * ( sinArc + sign * s ) / sinArc;
+
+ if ( ( tickPos <= maxpos ) && ( tickPos > minpos ) )
+ {
+ painter->setPen( darkPen );
+ painter->drawLine( QPointF( l1, tickPos - 1 ),
+ QPointF( l2, tickPos - 1 ) );
+ painter->setPen( lightPen );
+ painter->drawLine( QPointF( l1, tickPos ),
+ QPointF( l2, tickPos ) );
+ }
+ }
+ }
+}
+
+//! Notify value change
+void QwtWheel::valueChange()
+{
+ QwtAbstractSlider::valueChange();
+ update();
+}
+
+/*!
+ \brief Determine the scrolling mode and direction corresponding
+ to a specified point
+ \param p point
+ \param scrollMode scrolling mode
+ \param direction direction
+*/
+void QwtWheel::getScrollMode( const QPoint &p,
+ QwtAbstractSlider::ScrollMode &scrollMode, int &direction ) const
+{
+ if ( wheelRect().contains( p ) )
+ scrollMode = QwtAbstractSlider::ScrMouse;
+ else
+ scrollMode = QwtAbstractSlider::ScrNone;
+
+ direction = 0;
+}
+
+/*!
+ \brief Set the mass of the wheel
+
+ Assigning a mass turns the wheel into a flywheel.
+ \param mass The wheel's mass
+*/
+void QwtWheel::setMass( double mass )
+{
+ QwtAbstractSlider::setMass( mass );
+}
+
+/*!
+ \brief Set the width of the wheel
+
+ Corresponds to the wheel height for horizontal orientation,
+ and the wheel width for vertical orientation.
+
+ \param width the wheel's width
+ \sa wheelWidth()
+*/
+void QwtWheel::setWheelWidth( int width )
+{
+ d_data->wheelWidth = width;
+ update();
+}
+
+/*!
+ \return Width of the wheel
+ \sa setWheelWidth()
+*/
+int QwtWheel::wheelWidth() const
+{
+ return d_data->wheelWidth;
+}
+
+/*!
+ \return a size hint
+*/
+QSize QwtWheel::sizeHint() const
+{
+ const QSize hint = minimumSizeHint();
+ return hint.expandedTo( QApplication::globalStrut() );
+}
+
+/*!
+ \brief Return a minimum size hint
+ \warning The return value is based on the wheel width.
+*/
+QSize QwtWheel::minimumSizeHint() const
+{
+ QSize sz( 3 * d_data->wheelWidth + 2 * d_data->borderWidth,
+ d_data->wheelWidth + 2 * d_data->borderWidth );
+ if ( orientation() != Qt::Horizontal )
+ sz.transpose();
+
+ return sz;
+}
diff --git a/src/libpcp_qwt/src/qwt_wheel.h b/src/libpcp_qwt/src/qwt_wheel.h
new file mode 100644
index 0000000..7e18e46
--- /dev/null
+++ b/src/libpcp_qwt/src/qwt_wheel.h
@@ -0,0 +1,89 @@
+/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
+ * Qwt Widget Library
+ * Copyright (C) 1997 Josef Wilgen
+ * Copyright (C) 2002 Uwe Rathmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the Qwt License, Version 1.0
+ *****************************************************************************/
+
+#ifndef QWT_WHEEL_H
+#define QWT_WHEEL_H
+
+#include "qwt_global.h"
+#include "qwt_abstract_slider.h"
+
+/*!
+ \brief The Wheel Widget
+
+ The wheel widget can be used to change values over a very large range
+ in very small steps. Using the setMass member, it can be configured
+ as a flywheel.
+
+ \sa The radio example.
+*/
+class QWT_EXPORT QwtWheel : public QwtAbstractSlider
+{
+ Q_OBJECT
+
+ Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle )
+ Q_PROPERTY( double viewAngle READ viewAngle WRITE setViewAngle )
+ Q_PROPERTY( int tickCnt READ tickCnt WRITE setTickCnt )
+ Q_PROPERTY( int wheelWidth READ wheelWidth WRITE setWheelWidth )
+ Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth )
+ Q_PROPERTY( int wheelBorderWidth READ wheelBorderWidth WRITE setWheelBorderWidth )
+ Q_PROPERTY( double mass READ mass WRITE setMass )
+
+public:
+ explicit QwtWheel( QWidget *parent = NULL );
+ virtual ~QwtWheel();
+
+public Q_SLOTS:
+ void setTotalAngle ( double );
+ void setViewAngle( double );
+
+public:
+ virtual void setOrientation( Qt::Orientation );
+
+ double totalAngle() const;
+ double viewAngle() const;
+
+ void setTickCnt( int );
+ int tickCnt() const;
+
+ void setMass( double );
+ double mass() const;
+
+ void setWheelWidth( int );
+ int wheelWidth() const;
+
+ void setWheelBorderWidth( int );
+ int wheelBorderWidth() const;
+
+ void setBorderWidth( int );
+ int borderWidth() const;
+
+ QRect wheelRect() const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+protected:
+ virtual void paintEvent( QPaintEvent * );
+ virtual void resizeEvent( QResizeEvent * );
+
+ virtual void drawTicks( QPainter *, const QRectF & );
+ virtual void drawWheelBackground( QPainter *, const QRectF & );
+
+ virtual void valueChange();
+
+ virtual double getValue( const QPoint & );
+ virtual void getScrollMode( const QPoint &,
+ QwtAbstractSlider::ScrollMode &, int &direction ) const;
+
+private:
+ class PrivateData;
+ PrivateData *d_data;
+};
+
+#endif
diff --git a/src/libpcp_trace/GNUmakefile b/src/libpcp_trace/GNUmakefile
new file mode 100644
index 0000000..477919d
--- /dev/null
+++ b/src/libpcp_trace/GNUmakefile
@@ -0,0 +1,28 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+
+default install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/libpcp_trace/src/GNUmakefile b/src/libpcp_trace/src/GNUmakefile
new file mode 100644
index 0000000..a99c493
--- /dev/null
+++ b/src/libpcp_trace/src/GNUmakefile
@@ -0,0 +1,74 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+HFILES = hash.h
+CFILES = trace.c hash.c pdu.c pdubuf.c p_ack.c p_data.c ftrace.c
+VERSION_SCRIPT = exports
+
+LCFLAGS = -DPMTRACE_DEBUG
+LLDLIBS = $(LIB_FOR_PTHREADS) -lpcp
+LSRCFILES = $(VERSION_SCRIPT)
+
+STATICLIBTARGET = libpcp_trace.a
+
+DSOVERSION = 2
+LIBTARGET = libpcp_trace.$(DSOSUFFIX).$(DSOVERSION)
+SYMTARGET = libpcp_trace.$(DSOSUFFIX)
+
+ifeq "$(TARGET_OS)" "darwin"
+LIBTARGET = libpcp_trace.$(DSOVERSION).$(DSOSUFFIX)
+endif
+ifeq "$(TARGET_OS)" "mingw"
+STATICLIBTARGET =
+LIBTARGET = libpcp_trace.$(DSOSUFFIX)
+SYMTARGET =
+endif
+ifeq "$(ENABLE_SHARED)" "no"
+LIBTARGET =
+SYMTARGET =
+endif
+
+LDIRT = $(SYMTARGET)
+
+base default: $(LIBTARGET) $(SYMTARGET) $(STATICLIBTARGET)
+
+ifneq ($(SYMTARGET),)
+$(SYMTARGET):
+ $(LN_S) -f $(LIBTARGET) $(SYMTARGET)
+endif
+
+include $(BUILDRULES)
+
+install : default
+ifneq ($(LIBTARGET),)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET)
+endif
+ifneq ($(SYMTARGET),)
+ $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$(SYMTARGET)
+endif
+ifneq ($(STATICLIBTARGET),)
+ $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET)
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+ifneq ($(LIBTARGET),)
+$(LIBTARGET): $(VERSION_SCRIPT)
+endif
diff --git a/src/libpcp_trace/src/exports b/src/libpcp_trace/src/exports
new file mode 100644
index 0000000..415e3c8
--- /dev/null
+++ b/src/libpcp_trace/src/exports
@@ -0,0 +1,42 @@
+PCP_TRACE_2.0 {
+ global:
+ /* C and Fortran calling interfaces */
+ pmtraceabort;
+ pmtraceabort_;
+ pmtracebegin;
+ pmtracebegin_;
+ pmtracecounter;
+ pmtracecounter_;
+ pmtraceend;
+ pmtraceend_;
+ pmtraceobs;
+ pmtraceobs_;
+ pmtracepoint;
+ pmtracepoint_;
+ pmtracestate;
+ pmtracestate_;
+ pmtraceerrstr;
+ pmtraceerrstr_;
+
+ /* Internals shared between pmdatrace and libpcp_trace */
+ __pmhashinit;
+ __pmhashinsert;
+ __pmhashlookup;
+ __pmhashtraverse;
+ __pmhashtrunc;
+ __pmstate;
+ __pmtracedecodeack;
+ __pmtracedecodedata;
+ __pmtracefindPDUbuf;
+ __pmtracegetPDU;
+ __pmtracemoreinput;
+ __pmtracenomoreinput;
+ __pmtracepinPDUbuf;
+ __pmtraceprotocol;
+ __pmtracesendack;
+ __pmtracesenddata;
+ __pmtraceunpinPDUbuf;
+ __pmtracexmitPDU;
+
+ local: *;
+};
diff --git a/src/libpcp_trace/src/ftrace.c b/src/libpcp_trace/src/ftrace.c
new file mode 100644
index 0000000..1dc8560
--- /dev/null
+++ b/src/libpcp_trace/src/ftrace.c
@@ -0,0 +1,134 @@
+/*
+ * ftrace.c - Fortran front-end to the libpcp_trace entry points
+ *
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+int
+pmtracebegin_(const char *tag, int tag_len)
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtracebegin(tmp);
+ free(tmp);
+ return sts;
+}
+
+int
+pmtraceend_(const char *tag, int tag_len)
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtraceend(tmp);
+ free(tmp);
+ return sts;
+}
+
+int
+pmtraceabort_(const char *tag, int tag_len)
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtraceabort(tmp);
+ free(tmp);
+ return sts;
+}
+
+int
+pmtracepoint_(const char *tag, int tag_len)
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtracepoint(tmp);
+ free(tmp);
+ return sts;
+}
+
+int
+pmtracecounter_(const char *tag, double *value, int tag_len)
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtracecounter(tmp, *value);
+ free(tmp);
+ return sts;
+}
+
+int
+#ifdef __GNUC__
+pmtraceobs_(const char *tag, int tag_len, double *value)
+#else
+pmtraceobs_(const char *tag, double *value, int tag_len)
+#endif
+{
+ char *tmp = NULL;
+ int sts;
+
+ if ((tmp = malloc(tag_len + 1)) == NULL)
+ return -oserror();
+ strncpy(tmp, tag, tag_len);
+ tmp[tag_len] = '\0';
+ sts = pmtraceobs(tmp, *value);
+ free(tmp);
+ return sts;
+}
+
+void
+pmtraceerrstr_(int *code, char *msg, int msg_len)
+{
+ char *tmp;
+ int len;
+
+ tmp = pmtraceerrstr(*code);
+ len = (int)strlen(tmp);
+ len = (len < msg_len ? len : msg_len);
+
+ strncpy(msg, tmp, len);
+ for (; len < msg_len; len++) /* blank fill */
+ msg[len-1] = ' ';
+}
+
+int
+pmtracestate_(int *code)
+{
+ return pmtracestate(*code);
+}
diff --git a/src/libpcp_trace/src/hash.c b/src/libpcp_trace/src/hash.c
new file mode 100644
index 0000000..d323285
--- /dev/null
+++ b/src/libpcp_trace/src/hash.c
@@ -0,0 +1,181 @@
+/*
+ * hash.c - hash table used by trace pmda and libpcp_trace
+ *
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "hash.h"
+
+
+static unsigned int
+hashindex(const char *key, size_t tablesize)
+{
+ unsigned int hash, i;
+ char *p = (char *)key;
+
+ /* use first few chars for hash table distribution */
+ for (i=0, hash=0; i < 5 && *p; i++)
+ hash = (hash << PM_HASH_SHFT) - hash + *p++;
+
+#ifdef DESPERATE
+ fprintf(stderr, "Generated hash number: %d (%s)\n", hash % tablesize, key);
+#endif
+ return hash % (unsigned int)tablesize;
+}
+
+
+int
+__pmhashinit(__pmHashTable *t, size_t tsize, size_t esize,
+ __pmHashCmpFunc cmp, __pmHashDelFunc del)
+{
+ size_t blocksize;
+
+ t->tsize = tsize;
+ t->esize = esize;
+ t->entries = 0;
+ t->cmp = cmp;
+ t->del = del;
+ if (t->tsize <= 0) /* use default */
+ t->tsize = PM_HASH_SIZE;
+ blocksize = sizeof(__pmHashEnt *) * t->tsize;
+ if ((t->rows = (__pmHashEnt **)malloc(blocksize)) == NULL)
+ return -oserror();
+ memset((void *)t->rows, 0, blocksize);
+ return 0;
+}
+
+
+static int
+hashalloc(__pmHashTable *t, __pmHashEnt **entry)
+{
+ int e;
+
+ if ((*entry = (__pmHashEnt *)malloc(sizeof(__pmHashEnt))) == NULL)
+ return -oserror();
+ if (((*entry)->ent = malloc(t->esize)) == NULL) {
+ e = -oserror();
+ free(*entry);
+ *entry = NULL;
+ return e;
+ }
+ (*entry)->next = NULL;
+ return 0;
+}
+
+
+void
+__pmhashtrunc(__pmHashTable *t)
+{
+ if (t == NULL || t->rows == NULL || t->entries <= 0)
+ return;
+ else {
+ __pmHashEnt *tmp, *e;
+ int i;
+
+ for (i = 0; i < PM_HASH_SIZE; i++) {
+ e = t->rows[i];
+ while (e != NULL) {
+ tmp = e;
+ e = e->next;
+ if (tmp->ent != NULL) {
+ t->del(tmp->ent);
+ tmp->ent = NULL;
+ }
+ if (tmp) {
+ free(tmp);
+ tmp = NULL;
+ }
+ }
+ t->rows[i] = NULL;
+ }
+ memset((void *)t->rows, 0, sizeof(__pmHashEnt *)*t->tsize);
+ t->entries = 0;
+ }
+}
+
+
+void *
+__pmhashlookup(__pmHashTable *t, const char *key, void *result)
+{
+ __pmHashEnt *e;
+ int index;
+
+ if (t->entries == 0)
+ return NULL;
+
+ index = hashindex(key, t->tsize);
+ e = t->rows[index];
+ while (e != NULL) {
+ if (t->cmp(e->ent, result))
+ break;
+ e = e->next;
+ }
+ if (e == NULL)
+ return NULL;
+ return e->ent;
+}
+
+
+int
+__pmhashinsert(__pmHashTable *t, const char *key, void *entry)
+{
+ __pmHashEnt *hash = NULL;
+ int index, sts;
+
+ if ((sts = hashalloc(t, &hash)) < 0)
+ return sts;
+
+ index = hashindex(key, t->tsize);
+
+ /* stick at head of list (locality of reference) */
+ memcpy(hash->ent, entry, t->esize);
+ if (t->rows[index]) {
+ hash->next = t->rows[index]->next;
+ t->rows[index]->next = hash;
+ }
+ else {
+ t->rows[index] = hash;
+ t->rows[index]->next = NULL;
+ }
+ t->entries++;
+#ifdef DESPERATE
+ fprintf(stderr, "Insert: %s at 0x%x (key=%d entry=%d)\n",
+ key, &t->rows[index], index, t->entries);
+#endif
+ return 0;
+}
+
+
+void
+__pmhashtraverse(__pmHashTable *t, __pmHashIterFunc func)
+{
+ __pmHashEnt *e = NULL;
+ unsigned int i, hits;
+
+ if (t == NULL || func == NULL)
+ return;
+
+ for (i = 0, hits = 0; i < PM_HASH_SIZE && hits < t->entries; i++) {
+ e = t->rows[i];
+ while (e != NULL && hits < t->entries) {
+ hits++;
+ if (e->ent != NULL)
+ func(t, e->ent);
+ e = e->next;
+ }
+ }
+#ifdef DESPERATE
+ fprintf(stderr, "Traverse: looped %d times\n", i);
+#endif
+}
diff --git a/src/libpcp_trace/src/hash.h b/src/libpcp_trace/src/hash.h
new file mode 100644
index 0000000..dfe9be0
--- /dev/null
+++ b/src/libpcp_trace/src/hash.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _TRACE_HASH_H
+#define _TRACE_HASH_H
+
+typedef int (*__pmHashCmpFunc)(void *, void *);
+typedef int (*__pmHashKeyCmpFunc)(void *, const char *);
+typedef void (*__pmHashDelFunc)(void *);
+
+struct __pmHashEl {
+ void *ent;
+ struct __pmHashEl *next;
+};
+typedef struct __pmHashEl __pmHashEnt;
+
+struct __pmHashTab {
+ size_t tsize;
+ size_t esize;
+ unsigned int entries;
+ __pmHashCmpFunc cmp;
+ __pmHashDelFunc del;
+ __pmHashEnt **rows;
+};
+typedef struct __pmHashTab __pmHashTable;
+
+extern int __pmhashinit(__pmHashTable *, size_t, size_t, __pmHashCmpFunc, __pmHashDelFunc);
+
+extern void __pmhashtrunc(__pmHashTable *);
+
+extern int __pmhashinsert(__pmHashTable *, const char *, void *);
+
+extern void *__pmhashlookup(__pmHashTable *, const char *, void *);
+
+typedef void (*__pmHashIterFunc)(__pmHashTable *, void *);
+extern void __pmhashtraverse(__pmHashTable *, __pmHashIterFunc);
+
+#ifndef PM_HASH_TUNE
+#define PM_HASH_SHFT 5
+#define PM_HASH_SIZE 31
+#endif
+
+#endif /* _TRACE_HASH_H */
diff --git a/src/libpcp_trace/src/p_ack.c b/src/libpcp_trace/src/p_ack.c
new file mode 100644
index 0000000..9bf67f9
--- /dev/null
+++ b/src/libpcp_trace/src/p_ack.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+
+/*
+ * PDU for general receive acknowledgement (TRACE_PDU_ACK)
+ */
+typedef struct {
+ __pmTracePDUHdr hdr;
+ __int32_t data; /* ack for specific PDU type / error code */
+} ack_t;
+
+int
+__pmtracesendack(int fd, __int32_t data)
+{
+ ack_t *pp;
+
+ if (__pmstate & PMTRACE_STATE_NOAGENT) {
+ fprintf(stderr, "__pmtracesendack: sending acka (skipped)\n");
+ return 0;
+ }
+
+ if ((pp = (ack_t *)__pmtracefindPDUbuf(sizeof(ack_t))) == NULL)
+ return -oserror();
+ pp->hdr.len = sizeof(ack_t);
+ pp->hdr.type = TRACE_PDU_ACK;
+ pp->data = htonl(data);
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU)
+ fprintf(stderr, "__pmtracesendack(data=%d)\n",
+ (int)pp->data);
+#endif
+ return __pmtracexmitPDU(fd, (__pmTracePDU *)pp);
+}
+
+int
+__pmtracedecodeack(__pmTracePDU *pdubuf, __int32_t *data)
+{
+ ack_t *pp;
+ char *pduend;
+
+ pp = (ack_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp != sizeof(ack_t))
+ return PMTRACE_ERR_IPC;
+
+ *data = ntohl(pp->data);
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU)
+ fprintf(stderr, "__pmtracedecodeack -> data=%d\n",
+ (int)*data);
+#endif
+ return 0;
+}
diff --git a/src/libpcp_trace/src/p_data.c b/src/libpcp_trace/src/p_data.c
new file mode 100644
index 0000000..7780a48
--- /dev/null
+++ b/src/libpcp_trace/src/p_data.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * Assumptions:
+ * 1. "double" is 64-bits
+ * 2. "double" is IEEE format and needs endian conversion (like a
+ * 64-bit integer, but no other format conversion
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+/*
+ * PDU for all trace data updates (TRACE_PDU_DATA)
+ *
+ * note "taglen" includes the null-byte terminator
+ */
+typedef struct {
+ __pmTracePDUHdr hdr;
+ struct {
+#ifdef HAVE_BITFIELDS_LTOR
+ unsigned int version : 8;
+ unsigned int taglen : 8;
+ unsigned int tagtype : 8;
+ unsigned int protocol : 1;
+ unsigned int pad : 7;
+#else
+ unsigned int pad : 7;
+ unsigned int protocol : 1;
+ unsigned int tagtype : 8;
+ unsigned int taglen : 8;
+ unsigned int version : 8;
+#endif
+ } bits;
+ /*
+ * avoid struct padding traps!
+ *
+ * what really follows is a double (data) and then the tag
+ */
+} tracedata_t;
+
+#ifdef HAVE_NETWORK_BYTEORDER
+#define trace_htonll(a) do { } while (0) /* noop */
+#define trace_ntohll(a) do { } while (0) /* noop */
+#else
+static void
+trace_htonll(char *p)
+{
+ char c;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ c = p[i];
+ p[i] = p[7-i];
+ p[7-i] = c;
+ }
+}
+#define trace_ntohll(v) trace_htonll(v)
+#endif
+
+int
+__pmtracesenddata(int fd, char *tag, int taglen, int tagtype, double data)
+{
+ tracedata_t *pp = NULL;
+ size_t need = 0;
+ char *cp;
+ int *ip;
+
+ if (taglen <= 0)
+ return PMTRACE_ERR_IPC;
+ else if (__pmstate & PMTRACE_STATE_NOAGENT) {
+ fprintf(stderr, "__pmtracesenddata: sending data (skipped)\n");
+ return 0;
+ }
+
+ /*
+ * pad to the next __pmTracePDU boundary
+ */
+ need = sizeof(tracedata_t) + sizeof(double) + sizeof(__pmTracePDU)*((taglen - 1 + sizeof(__pmTracePDU))/sizeof(__pmTracePDU));
+
+ if ((pp = (tracedata_t *)__pmtracefindPDUbuf((int)need)) == NULL)
+ return -oserror();
+ pp->hdr.len = (int)need;
+ pp->hdr.type = TRACE_PDU_DATA;
+ pp->bits.taglen = taglen;
+ pp->bits.tagtype = tagtype;
+ pp->bits.version = TRACE_PDU_VERSION;
+ if (__pmtraceprotocol(TRACE_PROTOCOL_QUERY) == TRACE_PROTOCOL_SYNC)
+ pp->bits.protocol = 1;
+ else
+ pp->bits.protocol = 0;
+ pp->bits.pad = 0;
+ ip = (int *)&pp->bits;
+ *ip = htonl(*ip);
+
+ cp = (char *)pp;
+ cp += sizeof(tracedata_t);
+ memcpy((void *)cp, (void *)&data, sizeof(double));
+ trace_htonll(cp); /* send in network byte order */
+ cp += sizeof(double);
+ strcpy(cp, tag);
+#ifdef PCP_DEBUG
+ if ((taglen % sizeof(__pmTracePDU)) != 0) {
+ /* for Purify */
+ int pad;
+ char *padp = cp + taglen;
+ for (pad = sizeof(__pmTracePDU) - 1; pad >= (taglen % sizeof(__pmTracePDU)); pad--)
+ *padp++ = '~'; /* buffer end */
+ }
+#endif
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU)
+ fprintf(stderr, "__pmtracesenddata(tag=\"%s\", data=%f)\n", tag, data);
+#endif
+
+ return __pmtracexmitPDU(fd, (__pmTracePDU *)pp);
+}
+
+int
+__pmtracedecodedata(__pmTracePDU *pdubuf, char **tag, int *taglenp,
+ int *tagtype, int *protocol, double *data)
+{
+ tracedata_t *pp;
+ int *ip;
+ char *cp;
+ char *pduend;
+ int taglen;
+
+ if (pdubuf == NULL)
+ return PMTRACE_ERR_IPC;
+
+ pp = (tracedata_t *)pdubuf;
+ pduend = (char *)pdubuf + pp->hdr.len;
+
+ if (pduend - (char*)pp < sizeof(tracedata_t) + sizeof(double))
+ return PMTRACE_ERR_IPC;
+
+ ip = (int *)&pp->bits;
+ *ip = ntohl(*ip);
+ taglen = pp->bits.taglen;
+ if (pp->bits.version != TRACE_PDU_VERSION)
+ return PMTRACE_ERR_VERSION;
+ if (taglen <= 0 || taglen >= CHAR_MAX - 1 || taglen > pp->hdr.len)
+ return PMTRACE_ERR_IPC;
+ if (pduend - (char *)pp < sizeof(tracedata_t) + sizeof(double) + taglen)
+ return PMTRACE_ERR_IPC;
+ *taglenp = taglen;
+ *tagtype = pp->bits.tagtype;
+ *protocol = pp->bits.protocol;
+
+ cp = (char *)pp;
+ cp += sizeof(tracedata_t);
+ memcpy((void *)data, (void *)cp, sizeof(double));
+ trace_ntohll((char *)data); /* receive in network byte order */
+ cp += sizeof(double);
+ if ((*tag = (char *)malloc(taglen+1)) == NULL)
+ return -oserror();
+ strncpy(*tag, cp, taglen);
+ (*tag)[taglen] = '\0';
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU)
+ fprintf(stderr, "__pmtracedecodedata -> tag=\"%s\" data=%f\n", *tag, *data);
+#endif
+ return 0;
+}
diff --git a/src/libpcp_trace/src/pdu.c b/src/libpcp_trace/src/pdu.c
new file mode 100644
index 0000000..ad3c039
--- /dev/null
+++ b/src/libpcp_trace/src/pdu.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 1997-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2012 Red Hat. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <signal.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+typedef struct {
+ __pmTracePDU *pdubuf;
+ int len;
+} more_ctl;
+static more_ctl *more;
+static int maxfd = -1;
+
+static char *
+pdutypestr(int type)
+{
+ if (type == TRACE_PDU_ACK) return "ACK";
+ else if (type == TRACE_PDU_DATA) return "DATA";
+ else {
+ static char buf[20];
+ snprintf(buf, sizeof(buf), "TYPE-%d?", type);
+ return buf;
+ }
+}
+
+static void
+moreinput(int fd, __pmTracePDU *pdubuf, int len)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU) {
+ __pmTracePDUHdr *php = (__pmTracePDUHdr *)pdubuf;
+ __pmTracePDU *p;
+ int j, jend;
+ char *q;
+
+ jend = (php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU);
+ fprintf(stderr, "moreinput: fd=%d pdubuf=0x%p len=%d\n",
+ fd, pdubuf, len);
+ fprintf(stderr, "Piggy-back PDU: %s addr=0x%p len=%d from=%d",
+ pdutypestr(php->type), php, php->len, php->from);
+ fprintf(stderr, "%03d: ", 0);
+ p = (__pmTracePDU *)php;
+
+ /* for Purify ... */
+ q = (char *)p + php->len;
+ while (q < (char *)p + jend*sizeof(__pmTracePDU))
+ *q++ = '~'; /* buffer end */
+
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", p[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+ if (fd > maxfd) {
+ int next = maxfd + 1;
+ if ((more = (more_ctl *)realloc(more, (fd+1)*sizeof(more[0]))) == NULL)
+{
+ fprintf(stderr, "realloc failed (%d bytes): %s\n",
+ (fd+1)*(int)sizeof(more[0]), osstrerror());
+ return;
+ }
+ maxfd = fd;
+ while (next <= maxfd) {
+ more[next].pdubuf = NULL;
+ next++;
+ }
+ }
+
+ __pmtracepinPDUbuf(pdubuf);
+ more[fd].pdubuf = pdubuf;
+ more[fd].len = len;
+}
+
+int
+__pmtracemoreinput(int fd)
+{
+ if (fd < 0 || fd > maxfd)
+ return 0;
+
+ return more[fd].pdubuf == NULL ? 0 : 1;
+}
+
+void
+__pmtracenomoreinput(int fd)
+{
+ if (fd < 0 || fd > maxfd)
+ return;
+
+ if (more[fd].pdubuf != NULL) {
+ __pmtraceunpinPDUbuf(more[fd].pdubuf);
+ more[fd].pdubuf = NULL;
+ }
+}
+
+static int
+pduread(int fd, char *buf, int len, int mode, int timeout)
+{
+ /*
+ * handle short reads that may split a PDU ...
+ */
+ int status = 0;
+ int have = 0;
+ __pmFdSet onefd;
+ static int done_default = 0;
+ static struct timeval def_wait = { 10, 0 };
+
+ if (timeout == TRACE_TIMEOUT_DEFAULT) {
+ if (!done_default) {
+ double def_timeout;
+ char *timeout_str;
+ char *end_ptr;
+
+ if ((timeout_str = getenv(TRACE_ENV_REQTIMEOUT)) != NULL) {
+ def_timeout = strtod(timeout_str, &end_ptr);
+ if (*end_ptr != '\0' || def_timeout < 0.0) {
+ status = PMTRACE_ERR_ENVFORMAT;
+ return status;
+ }
+ else {
+ def_wait.tv_sec = (int)def_timeout; /* truncate -> secs */
+ if (def_timeout > (double)def_wait.tv_sec)
+ def_wait.tv_usec = (long)((def_timeout -
+ (double)def_wait.tv_sec) * 1000000);
+ else
+ def_wait.tv_usec = 0;
+ }
+ }
+ done_default = 1;
+ }
+ }
+
+ while (len) {
+ struct timeval wait;
+ /*
+ * either never timeout (i.e. block forever), or timeout
+ */
+ if (timeout != TRACE_TIMEOUT_NEVER) {
+ if (timeout > 0) {
+ wait.tv_sec = timeout;
+ wait.tv_usec = 0;
+ }
+ else
+ wait = def_wait;
+ __pmFD_ZERO(&onefd);
+ __pmFD_SET(fd, &onefd);
+ status = __pmSelectRead(fd+1, &onefd, &wait);
+ if (status == 0)
+ return PMTRACE_ERR_TIMEOUT;
+ else if (status < 0) {
+ setoserror(neterror());
+ return status;
+ }
+ }
+ status = (int)__pmRead(fd, buf, len);
+ if (status <= 0) { /* EOF or error */
+ setoserror(neterror());
+ return status;
+ }
+ if (mode == -1)
+ /* special case, see __pmtracegetPDU */
+ return status;
+ have += status;
+ buf += status;
+ len -= status;
+ }
+
+ return have;
+}
+
+
+/*
+ * Because the default handler for SIGPIPE is to exit, we always want a handler
+ * installed to override that so that the write() just returns an error. The
+ * problem is that the user might have installed one prior to the first write()
+ * or may install one at some later stage. This doesn't matter. As long as a
+ * handler other than SIG_DFL is there, all will be well. The first time that
+ * __pmtracexmitPDU is called, install SIG_IGN as the handler for SIGPIPE.
+ * If the user had already changed the handler from SIG_DFL, put back what was
+ * there before.
+ */
+
+int
+__pmtracexmitPDU(int fd, __pmTracePDU *pdubuf)
+{
+ int n, len;
+ __pmTracePDUHdr *php = (__pmTracePDUHdr *)pdubuf;
+
+#if defined(HAVE_SIGPIPE)
+ SIG_PF user_onpipe;
+ user_onpipe = signal(SIGPIPE, SIG_IGN);
+ if (user_onpipe != SIG_DFL) /* put user handler back */
+ signal(SIGPIPE, user_onpipe);
+#endif
+
+ php->from = (__int32_t)getpid();
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU) {
+ int j;
+ int jend = (php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU);
+ char *p;
+
+ /* for Purify ... */
+ p = (char *)pdubuf + php->len;
+ while (p < (char *)pdubuf + jend*sizeof(__pmTracePDU))
+ *p++ = '~'; /* buffer end */
+
+ fprintf(stderr, "[%d]__pmtracexmitPDU: %s fd=%d len=%d",
+ php->from, pdutypestr(php->type), fd, php->len);
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", pdubuf[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+ len = php->len;
+
+ php->len = htonl(php->len);
+ php->from = htonl(php->from);
+ php->type = htonl(php->type);
+ n = (int)__pmWrite(fd, pdubuf, len);
+ php->len = ntohl(php->len);
+ php->from = ntohl(php->from);
+ php->type = ntohl(php->type);
+
+ if (n != len)
+ return -oserror();
+
+ return n;
+}
+
+
+int
+__pmtracegetPDU(int fd, int timeout, __pmTracePDU **result)
+{
+ int need, len;
+ char *handle;
+ static int maxsize = TRACE_PDU_CHUNK;
+ __pmTracePDU *pdubuf;
+ __pmTracePDU *pdubuf_prev;
+ __pmTracePDUHdr *php;
+
+ /*
+ * This stuff is a little tricky. What we try to do is read()
+ * an amount of data equal to the largest PDU we have (or are
+ * likely to have) seen thus far. In the majority of cases
+ * this returns exactly one PDU's worth, i.e. read() returns
+ * a length equal to php->len.
+ *
+ * For this to work, we have a special "mode" of -1
+ * to pduread() which means read, but return after the
+ * first read(), rather than trying to read up to the request
+ * length with multiple read()s, which would of course "hang"
+ * after the first PDU arrived.
+ *
+ * We need to handle the following tricky cases:
+ * 1. We get _more_ than we need for a single PDU -- happens
+ * when PDU's arrive together. This requires "moreinput"
+ * to handle leftovers here (it gets even uglier if we
+ * have part, but not all of the second PDU).
+ * 2. We get _less_ than we need for a single PDU -- this
+ * requires at least another read(), and possibly acquiring
+ * another pdubuf and doing a memcpy() for the partial PDU
+ * from the earlier call.
+ */
+ if (__pmtracemoreinput(fd)) {
+ /* some leftover from last time ... handle -> start of PDU */
+ pdubuf = more[fd].pdubuf;
+ len = more[fd].len;
+ __pmtracenomoreinput(fd);
+ }
+ else {
+ if ((pdubuf = __pmtracefindPDUbuf(maxsize)) == NULL)
+ return -oserror();
+ len = pduread(fd, (void *)pdubuf, maxsize, -1, timeout);
+ }
+ php = (__pmTracePDUHdr *)pdubuf;
+
+ if (len < (int)sizeof(__pmTracePDUHdr)) {
+ if (len == -1) {
+ if (oserror() == ECONNRESET||
+ oserror() == ETIMEDOUT || oserror() == ENETDOWN ||
+ oserror() == ENETUNREACH || oserror() == EHOSTDOWN ||
+ oserror() == EHOSTUNREACH || oserror() == ECONNREFUSED)
+ /*
+ * failed as a result of pmdatrace exiting and the
+ * connection being reset, or as a result of the kernel
+ * ripping down the connection (most likely because the
+ * host at the other end just took a dive)
+ *
+ * treat this like end of file on input
+ *
+ * from irix/kern/fs/nfs/bds.c seems like all of the
+ * following are peers here:
+ * ECONNRESET (pmdatrace terminated?)
+ * ETIMEDOUT ENETDOWN ENETUNREACH EHOSTDOWN EHOSTUNREACH
+ * ECONNREFUSED
+ * peers for bds but not here:
+ * ENETRESET ENONET ESHUTDOWN (cache_fs only?)
+ * ECONNABORTED (accept, user req only?)
+ * ENOTCONN (udp?)
+ * EPIPE EAGAIN (nfs, bds & ..., but not ip or tcp?)
+ */
+ len = 0;
+ else
+ fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s",
+ fd, osstrerror());
+ }
+ else if (len > 0)
+ fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: len=%d, not %d?",
+ fd, len, (int)sizeof(__pmTracePDUHdr));
+ else if (len == PMTRACE_ERR_TIMEOUT)
+ return PMTRACE_ERR_TIMEOUT;
+ else if (len < 0)
+ fprintf(stderr, "__pmtracegetPDU: fd=%d hdr: %s", fd, pmtraceerrstr(len));
+ return len ? PMTRACE_ERR_IPC : 0;
+ }
+
+ php->len = ntohl(php->len);
+ if (php->len < 0) {
+ fprintf(stderr, "__pmtracegetPDU: fd=%d illegal len=%d in hdr\n", fd, php->len);
+ return PMTRACE_ERR_IPC;
+ }
+
+ if (len == php->len)
+ /* return below */
+ ;
+ else if (len > php->len) {
+ /*
+ * read more than we need for this one, save it up for next time
+ */
+ handle = (char *)pdubuf;
+ moreinput(fd, (__pmTracePDU *)&handle[php->len], len - php->len);
+ }
+ else {
+ int tmpsize;
+
+ /*
+ * need to read more ...
+ */
+ __pmtracepinPDUbuf(pdubuf);
+ pdubuf_prev = pdubuf;
+ if (php->len > maxsize)
+ tmpsize = TRACE_PDU_CHUNK * ( 1 + php->len / TRACE_PDU_CHUNK);
+ else
+ tmpsize = maxsize;
+ if ((pdubuf = __pmtracefindPDUbuf(tmpsize)) == NULL) {
+ __pmtraceunpinPDUbuf(pdubuf_prev);
+ return -oserror();
+ }
+ if (php->len > maxsize)
+ maxsize = tmpsize;
+ memmove((void *)pdubuf, (void *)php, len);
+ __pmtraceunpinPDUbuf(pdubuf_prev);
+ php = (__pmTracePDUHdr *)pdubuf;
+ need = php->len - len;
+ handle = (char *)pdubuf;
+ /* block until all of the PDU is received this time */
+ len = pduread(fd, (void *)&handle[len], need, 0, timeout);
+ if (len != need) {
+ if (len == PMTRACE_ERR_TIMEOUT)
+ return PMTRACE_ERR_TIMEOUT;
+ if (len < 0)
+ fprintf(stderr, "__pmtracegetPDU: error (%d) fd=%d: %s\n", (int)oserror(), fd, osstrerror());
+ else
+ fprintf(stderr, "__pmtracegetPDU: len=%d, not %d? (fd=%d)\n", len, need, fd);
+ fprintf(stderr, "hdr: len=0x%08x type=0x%08x from=0x%08x\n",
+ php->len, (int)ntohl(php->type), (int)ntohl(php->from));
+ return PMTRACE_ERR_IPC;
+ }
+ }
+
+ *result = (__pmTracePDU *)php;
+ php->type = ntohl(php->type);
+ php->from = ntohl(php->from);
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDU) {
+ int j;
+ int jend = (int)(php->len+(int)sizeof(__pmTracePDU)-1)/(int)sizeof(__pmTracePDU);
+ char *p;
+
+ /* for Purify ... */
+ p = (char *)*result + php->len;
+ while (p < (char *)*result + jend*sizeof(__pmTracePDU))
+ *p++ = '~'; /* buffer end */
+
+ fprintf(stderr, "[%" FMT_PID "]__pmtracegetPDU: %s fd=%d len=%d from=%d",
+ getpid(), pdutypestr(php->type), fd, php->len, php->from);
+
+ for (j = 0; j < jend; j++) {
+ if ((j % 8) == 0)
+ fprintf(stderr, "\n%03d: ", j);
+ fprintf(stderr, "%8x ", (*result)[j]);
+ }
+ putc('\n', stderr);
+ }
+#endif
+
+ return php->type;
+}
diff --git a/src/libpcp_trace/src/pdubuf.c b/src/libpcp_trace/src/pdubuf.c
new file mode 100644
index 0000000..0440275
--- /dev/null
+++ b/src/libpcp_trace/src/pdubuf.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "trace.h"
+#include "trace_dev.h"
+
+
+typedef struct bufctl {
+ struct bufctl *bc_next;
+ int bc_size;
+ int bc_pincnt;
+ char *bc_buf;
+ char *bc_bufend;
+} bufctl_t;
+
+static bufctl_t *buf_free;
+static bufctl_t *buf_pin;
+static bufctl_t *buf_pin_tail;
+
+#ifdef PMTRACE_DEBUG
+static void
+pdubufdump(void)
+{
+ bufctl_t *pcp;
+
+ if (buf_free != NULL) {
+ fprintf(stderr, " free pdubuf[size]:");
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next)
+ fprintf(stderr, " 0x%p[%d]", pcp->bc_buf, pcp->bc_size);
+ fputc('\n', stderr);
+ }
+
+ if (buf_pin != NULL) {
+ fprintf(stderr, " pinned pdubuf[pincnt]:");
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next)
+ fprintf(stderr, " 0x%p[%d]", pcp->bc_buf, pcp->bc_pincnt);
+ fputc('\n', stderr);
+ }
+}
+#endif
+
+__pmTracePDU *
+__pmtracefindPDUbuf(int need)
+{
+ bufctl_t *pcp;
+
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_size >= need)
+ break;
+ }
+ if (pcp == NULL) {
+ if ((pcp = (bufctl_t *)malloc(sizeof(*pcp))) == NULL)
+ return NULL;
+ pcp->bc_pincnt = 0;
+ pcp->bc_size = TRACE_PDU_CHUNK * (1 + need/TRACE_PDU_CHUNK);
+ if ((pcp->bc_buf = (char *)valloc(pcp->bc_size)) == NULL) {
+ free(pcp);
+ return NULL;
+ }
+ pcp->bc_next = buf_free;
+ pcp->bc_bufend = &pcp->bc_buf[pcp->bc_size];
+ buf_free = pcp;
+ }
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDUBUF) {
+ fprintf(stderr, "__pmtracefindPDUbuf(%d) -> 0x%p\n", need, pcp->bc_buf);
+ pdubufdump();
+ }
+#endif
+
+ return (__pmTracePDU *)pcp->bc_buf;
+}
+
+void
+__pmtracepinPDUbuf(void *handle)
+{
+ bufctl_t *pcp;
+ bufctl_t *prior = NULL;
+
+ for (pcp = buf_free; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend)
+ break;
+ prior = pcp;
+ }
+
+ if (pcp != NULL) {
+ /* first pin for this buffer, move between lists */
+ if (prior == NULL)
+ buf_free = pcp->bc_next;
+ else
+ prior->bc_next = pcp->bc_next;
+ pcp->bc_next = NULL;
+ if (buf_pin_tail != NULL)
+ buf_pin_tail->bc_next = pcp;
+ buf_pin_tail = pcp;
+ if (buf_pin == NULL)
+ buf_pin = pcp;
+ pcp->bc_pincnt = 1;
+ }
+ else {
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < pcp->bc_bufend)
+ break;
+ }
+ if (pcp != NULL)
+ pcp->bc_pincnt++;
+ else {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDUBUF) {
+ fprintf(stderr, "__pmtracepinPDUbuf: 0x%p not in pool!", handle);
+ pdubufdump();
+ }
+#endif
+ return;
+ }
+ }
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDUBUF)
+ fprintf(stderr, "__pmtracepinPDUbuf(0x%p) -> pdubuf=0x%p, cnt=%d\n",
+ handle, pcp->bc_buf, pcp->bc_pincnt);
+#endif
+ return;
+}
+
+int
+__pmtraceunpinPDUbuf(void *handle)
+{
+ bufctl_t *pcp;
+ bufctl_t *prior = NULL;
+
+ for (pcp = buf_pin; pcp != NULL; pcp = pcp->bc_next) {
+ if (pcp->bc_buf <= (char *)handle && (char *)handle < &pcp->bc_buf[pcp->bc_size])
+ break;
+ prior = pcp;
+ }
+ if (pcp == NULL) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDUBUF) {
+ fprintf(stderr, "__pmtraceunpinPDUbuf(0x%p) -> fails\n", handle);
+ pdubufdump();
+ }
+#endif
+ return 0;
+ }
+
+ if (--pcp->bc_pincnt == 0) {
+ if (prior == NULL)
+ buf_pin = pcp->bc_next;
+ else
+ prior->bc_next = pcp->bc_next;
+ if (buf_pin_tail == pcp)
+ buf_pin_tail = prior;
+
+ pcp->bc_next = buf_free;
+ buf_free = pcp;
+ }
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_PDUBUF)
+ fprintf(stderr, "__pmtraceunpinPDUbuf(0x%p) -> pdubuf=0x%p, pincnt=%d\n",
+ handle, pcp->bc_buf, pcp->bc_pincnt);
+#endif
+
+ return 1;
+}
diff --git a/src/libpcp_trace/src/trace.c b/src/libpcp_trace/src/trace.c
new file mode 100644
index 0000000..34a3135
--- /dev/null
+++ b/src/libpcp_trace/src/trace.c
@@ -0,0 +1,950 @@
+/*
+ * trace.c - client-side interface for trace PMDA
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1997-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include <inttypes.h>
+#include "pmapi.h"
+#include "impl.h"
+
+#if defined(HAVE_PTHREAD_H)
+#include <pthread.h>
+#elif defined(HAVE_ABI_MUTEX_H)
+#include <abi_mutex.h>
+#elif !defined(IS_MINGW)
+#error !bozo!
+#endif
+
+#include "hash.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+static int _pmtimedout = 1;
+static time_t _pmttimeout = 0;
+
+static int _pmtraceconnect(int);
+static int _pmtracereconnect(void);
+static int _pmauxtraceconnect(void);
+static void _pmtraceupdatewait(void);
+static int _pmtracegetack(int, int);
+static int _pmtraceremaperr(int);
+static __uint64_t _pmtraceid(void);
+
+int __pmstate = PMTRACE_STATE_NONE;
+
+
+static double
+__pmtracetvsub(const struct timeval *a, const struct timeval *b)
+{
+ return (double)(a->tv_sec - b->tv_sec + (double)(a->tv_usec - b->tv_usec)/1000000.0);
+}
+
+/* transaction data unit */
+typedef struct {
+ __uint64_t id;
+ char *tag;
+ unsigned int tracetype : 7;
+ unsigned int inprogress : 1;
+ unsigned int taglength : 8;
+ unsigned int pad : 16;
+ struct timeval start; /* transaction started timestamp */
+ double data; /* time interval / -1 / user-defined */
+} _pmTraceLibdata;
+
+static int
+_pmlibcmp(void *a, void *b)
+{
+ _pmTraceLibdata *aa = (_pmTraceLibdata *)a;
+ _pmTraceLibdata *bb = (_pmTraceLibdata *)b;
+
+ if (!aa || !bb || (aa->id != bb->id))
+ return 0;
+ if (aa->tracetype != bb->tracetype)
+ return 0;
+ if (aa->id != bb->id)
+ return 0;
+ return !strcmp(aa->tag, bb->tag);
+}
+
+static void
+_pmlibdel(void *entry)
+{
+ _pmTraceLibdata *data = (_pmTraceLibdata *)entry;
+
+ if (data->tag)
+ free(data->tag);
+ if (data)
+ free(data);
+}
+
+static int __pmfd;
+static __pmHashTable _pmtable;
+
+#if defined(HAVE_PTHREAD_MUTEX_T)
+
+/* use portable pthreads for mutex */
+static pthread_mutex_t _pmtracelock;
+#define TRACE_LOCK_INIT pthread_mutex_init(&_pmtracelock, NULL)
+#define TRACE_LOCK pthread_mutex_lock(&_pmtracelock)
+#define TRACE_UNLOCK pthread_mutex_unlock(&_pmtracelock)
+
+#elif defined(HAVE_ABI_MUTEX_H)
+/* use an SGI spinlock for mutex */
+static abilock_t _pmtracelock;
+#define TRACE_LOCK_INIT init_lock(&_pmtracelock)
+#define TRACE_LOCK spin_lock(&_pmtracelock)
+#define TRACE_UNLOCK release_lock(&_pmtracelock)
+
+#elif defined(IS_MINGW)
+/* use native Win32 primitives */
+static HANDLE _pmtracelock;
+#define TRACE_LOCK_INIT (_pmtracelock = CreateMutex(NULL, FALSE, NULL), 0)
+#define TRACE_LOCK WaitForSingleObject(_pmtracelock, INFINITE)
+#define TRACE_UNLOCK ReleaseMutex(_pmtracelock)
+
+#else
+#error !bozo!
+#endif
+
+int
+pmtracebegin(const char *tag)
+{
+ static int first = 1;
+ _pmTraceLibdata *hptr;
+ _pmTraceLibdata hash;
+ int len, a_sts = 0, b_sts = 0, protocol;
+
+ if (tag == NULL || *tag == '\0')
+ return PMTRACE_ERR_TAGNAME;
+ if ((len = strlen(tag)+1) >= MAXTAGNAMELEN)
+ return PMTRACE_ERR_TAGLENGTH;
+
+ hash.tag = (char *)tag;
+ hash.taglength = len;
+ hash.id = _pmtraceid();
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+
+ /*
+ * We need to do both the connect and hash table manipulation,
+ * otherwise the reconnect isn't reliable and the hash table
+ * (potentially) becomes completely wrong, and we reject some
+ * transact calls which actually were in a valid call sequence.
+ */
+
+ protocol = __pmtraceprotocol(TRACE_PROTOCOL_QUERY);
+
+ if (_pmtimedout && (a_sts = _pmtraceconnect(1)) < 0) {
+ if (first || protocol == TRACE_PROTOCOL_ASYNC)
+ return a_sts; /* exception to the rule */
+ a_sts = _pmtraceremaperr(a_sts);
+ if (a_sts == PMTRACE_ERR_IPC && protocol == TRACE_PROTOCOL_SYNC) {
+ _pmtimedout = 1; /* try reconnect */
+ a_sts = 0;
+ }
+ }
+ if (a_sts >= 0)
+ first = 0;
+
+ /* lock hash table for search and subsequent insert/update */
+ TRACE_LOCK;
+
+ if ((hptr = __pmhashlookup(&_pmtable, tag, &hash)) == NULL) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtracebegin: new transaction '%s' "
+ "(id=0x%" PRIx64 ")\n", tag, hash.id);
+#endif
+ hash.pad = 0;
+ if ((hash.tag = strdup(tag)) == NULL)
+ b_sts = -oserror();
+ __pmtimevalNow(&hash.start);
+ if (b_sts >= 0) {
+ hash.inprogress = 1;
+ b_sts = __pmhashinsert(&_pmtable, tag, &hash);
+ }
+ if (b_sts < 0 && hash.tag != NULL)
+ free(hash.tag);
+ }
+ else if (hptr->inprogress == 1)
+ b_sts = PMTRACE_ERR_INPROGRESS;
+ else if (hptr->tracetype != TRACE_TYPE_TRANSACT)
+ b_sts = PMTRACE_ERR_TAGTYPE;
+ else {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtracebegin: updating transaction '%s' "
+ "(id=0x%" PRIx64 ")\n", tag, hash.id);
+#endif
+ __pmtimevalNow(&hptr->start);
+ hptr->inprogress = 1;
+ }
+
+ /* unlock hash table */
+ if (TRACE_UNLOCK != 0)
+ b_sts = -oserror();
+
+ if (a_sts < 0)
+ return a_sts;
+ return b_sts;
+}
+
+int
+pmtraceend(const char *tag)
+{
+ _pmTraceLibdata hash;
+ _pmTraceLibdata *hptr;
+ struct timeval now;
+ int len, protocol, sts = 0;
+
+ if (tag == NULL || *tag == '\0')
+ return PMTRACE_ERR_TAGNAME;
+ if ((len = strlen(tag)+1) >= MAXTAGNAMELEN)
+ return PMTRACE_ERR_TAGLENGTH;
+
+ __pmtimevalNow(&now);
+
+ /* give just enough info for comparison routine */
+ hash.tag = (char *)tag;
+ hash.taglength = len;
+ hash.id = _pmtraceid();
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+
+ /* lock hash table for search and update then send data */
+ TRACE_LOCK;
+
+ if ((hptr = __pmhashlookup(&_pmtable, tag, &hash)) == NULL)
+ sts = PMTRACE_ERR_NOSUCHTAG;
+ else if (hptr->inprogress != 1)
+ sts = PMTRACE_ERR_NOPROGRESS;
+ else if (hptr->tracetype != TRACE_TYPE_TRANSACT)
+ sts = PMTRACE_ERR_TAGTYPE;
+ else {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtraceend: sending transaction data '%s' "
+ "(id=0x%" PRIx64 ")\n", tag, hash.id);
+#endif
+ hptr->inprogress = 0;
+ hptr->data = __pmtracetvsub(&now, &hptr->start);
+
+ if (sts >= 0 && _pmtimedout) {
+ sts = _pmtracereconnect();
+ sts = _pmtraceremaperr(sts);
+ }
+
+ if (sts >= 0) {
+ sts = __pmtracesenddata(__pmfd, hptr->tag, hptr->taglength,
+ TRACE_TYPE_TRANSACT, hptr->data);
+ sts = _pmtraceremaperr(sts);
+ }
+
+ protocol = __pmtraceprotocol(TRACE_PROTOCOL_QUERY);
+
+ if (sts >= 0 && protocol == TRACE_PROTOCOL_SYNC)
+ sts = _pmtracegetack(sts, TRACE_TYPE_TRANSACT);
+
+ if (sts == PMTRACE_ERR_IPC && protocol == TRACE_PROTOCOL_SYNC) {
+ _pmtimedout = 1; /* try reconnect */
+ sts = 0;
+ }
+ }
+
+ if (TRACE_UNLOCK != 0)
+ return -oserror();
+
+ return sts;
+}
+
+int
+pmtraceabort(const char *tag)
+{
+ _pmTraceLibdata hash;
+ _pmTraceLibdata *hptr;
+ int len, sts = 0;
+
+ if (tag == NULL || *tag == '\0')
+ return PMTRACE_ERR_TAGNAME;
+ if ((len = strlen(tag)+1) >= MAXTAGNAMELEN)
+ return PMTRACE_ERR_TAGLENGTH;
+
+ hash.tag = (char *)tag;
+ hash.taglength = len;
+ hash.id = _pmtraceid();
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+
+ /* lock hash table for search and update */
+ TRACE_LOCK;
+ if ((hptr = __pmhashlookup(&_pmtable, tag, &hash)) == NULL)
+ sts = PMTRACE_ERR_NOSUCHTAG;
+ else if (hptr->inprogress != 1)
+ sts = PMTRACE_ERR_NOPROGRESS;
+ else if (hptr->tracetype != TRACE_TYPE_TRANSACT)
+ sts = PMTRACE_ERR_TAGTYPE;
+ else {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtraceabort: aborting transaction '%s' "
+ "(id=0x%" PRIx64 ")\n", tag, hash.id);
+#endif
+ hptr->inprogress = 0;
+ }
+
+ if (TRACE_UNLOCK != 0)
+ return -oserror();
+
+ return sts;
+}
+
+
+static int
+_pmtracecommon(const char *label, double value, int type)
+{
+ static int first = 1;
+ int taglength;
+ int protocol;
+ int sts = 0;
+
+ if (label == NULL || *label == '\0')
+ return PMTRACE_ERR_TAGNAME;
+
+ taglength = (unsigned int)strlen(label)+1;
+ if (taglength >= MAXTAGNAMELEN)
+ return PMTRACE_ERR_TAGLENGTH;
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "_pmtracecommon: trace tag '%s' (type=%d,value=%f)\n",
+ label, type, value);
+#endif
+
+ protocol = __pmtraceprotocol(TRACE_PROTOCOL_QUERY);
+
+ if (_pmtimedout && (sts = _pmtraceconnect(1)) < 0) {
+ if (first || protocol == TRACE_PROTOCOL_ASYNC)
+ return sts;
+ sts = _pmtraceremaperr(sts);
+ if (sts == PMTRACE_ERR_IPC && protocol == TRACE_PROTOCOL_SYNC) {
+ _pmtimedout = 1; /* try reconnect */
+ sts = 0;
+ }
+ return sts;
+ }
+ first = 0;
+
+ TRACE_LOCK;
+
+ if (sts >= 0 && _pmtimedout) {
+ sts = _pmtracereconnect();
+ sts = _pmtraceremaperr(sts);
+ }
+
+ if (sts >= 0) {
+ sts = __pmtracesenddata(__pmfd, (char *)label, (int)strlen(label)+1,
+ type, value);
+ sts = _pmtraceremaperr(sts);
+ }
+
+ protocol = __pmtraceprotocol(TRACE_PROTOCOL_QUERY);
+
+ if (sts >= 0 && protocol == TRACE_PROTOCOL_SYNC)
+ sts = _pmtracegetack(sts, type);
+
+ if (sts == PMTRACE_ERR_IPC && protocol == TRACE_PROTOCOL_SYNC) {
+ _pmtimedout = 1;
+ sts = 0;
+ }
+
+ if (TRACE_UNLOCK != 0)
+ return -oserror();
+
+ return sts;
+}
+
+
+int
+pmtracepoint(const char *label)
+{
+ return _pmtracecommon(label, -1, TRACE_TYPE_POINT);
+}
+
+
+int
+pmtracecounter(const char *label, double value)
+{
+ return _pmtracecommon(label, value, TRACE_TYPE_COUNTER);
+}
+
+int
+pmtraceobs(const char *label, double value)
+{
+ return _pmtracecommon(label, value, TRACE_TYPE_OBSERVE);
+}
+
+
+char *
+pmtraceerrstr(int code)
+{
+ static const struct {
+ int code;
+ char *msg;
+ } errtab[] = {
+ { PMTRACE_ERR_TAGNAME,
+ "Invalid tag name - tag names cannot be NULL" },
+ { PMTRACE_ERR_INPROGRESS,
+ "Transaction is already in progress - cannot begin" },
+ { PMTRACE_ERR_NOPROGRESS,
+ "Transaction is not currently in progress - cannot end" },
+ { PMTRACE_ERR_NOSUCHTAG,
+ "Transaction tag was not successfully initialised" },
+ { PMTRACE_ERR_TAGTYPE,
+ "Tag is already in use for a different type of tracing" },
+ { PMTRACE_ERR_TAGLENGTH,
+ "Tag name is too long (maximum 256 characters)" },
+ { PMTRACE_ERR_IPC,
+ "IPC protocol failure" },
+ { PMTRACE_ERR_ENVFORMAT,
+ "Unrecognised environment variable format" },
+ { PMTRACE_ERR_TIMEOUT,
+ "Application timed out connecting to the PMDA" },
+ { PMTRACE_ERR_VERSION,
+ "Incompatible versions between application and PMDA" },
+ { PMTRACE_ERR_PERMISSION,
+ "Cannot connect to PMDA - permission denied" },
+ { PMTRACE_ERR_CONNLIMIT,
+ "Cannot connect to PMDA - connection limit reached" },
+ { 0, "" }
+ };
+
+ if ((code < 0) && (code > -PMTRACE_ERR_BASE))
+ /* intro(2) errors */
+ return strerror(-code);
+ else if (code == 0)
+ return "No error.";
+ else {
+ int i;
+ for (i=0; errtab[i].code; i++) {
+ if (errtab[i].code == code)
+ return errtab[i].msg;
+ }
+ }
+ return "Unknown error code.";
+}
+
+
+static int
+_pmtraceremaperr(int sts)
+{
+ int save_oserror;
+ int socket_closed;
+
+ /*
+ * sts is negative.
+ * Use __pmSocketClosed() to decode it, since it may have come from
+ * __pmSecureSocketsError(). __pmSocketClosed uses oserror() and expects it to
+ * be non-negative.
+ */
+ save_oserror = oserror();
+ setoserror(-sts);
+ socket_closed = __pmSocketClosed();
+ setoserror(save_oserror);
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceremaperr: status %d remapped to %d\n", sts,
+ socket_closed ? PMTRACE_ERR_IPC : sts);
+#endif
+
+ if (socket_closed) {
+ _pmtimedout = 1;
+ return PMTRACE_ERR_IPC;
+ }
+ return sts;
+}
+
+
+static __uint64_t
+_pmtraceid(void)
+{
+ __uint64_t myid = 0;
+ myid |= getpid();
+ return myid;
+}
+
+
+/*
+ * gets an ack from the PMDA, based on expected ACK type.
+ * if type is zero, expects and returns the version, otherwise
+ * ACK is for a sent data PDU.
+ */
+static int
+_pmtracegetack(int sts, int type)
+{
+ if (sts >= 0) {
+ __pmTracePDU *ack;
+ int status, acktype;
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_NOAGENT) {
+ fprintf(stderr, "_pmtracegetack: awaiting ack (skipped)\n");
+ return 0;
+ }
+ else if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtracegetack: awaiting ack ...\n");
+#endif
+
+ status = __pmtracegetPDU(__pmfd, TRACE_TIMEOUT_DEFAULT, &ack);
+
+ if (status < 0) {
+ if ((status = _pmtraceremaperr(status)) == PMTRACE_ERR_IPC)
+ return 0; /* hide this - try reconnect later */
+ return status;
+ }
+ else if (status == 0) {
+ _pmtimedout = 1;
+ return PMTRACE_ERR_IPC;
+ }
+ else if (status == TRACE_PDU_ACK) {
+ if ((status = __pmtracedecodeack(ack, &acktype)) < 0)
+ return _pmtraceremaperr(status);
+ else if (type != 0 && acktype == type)
+ return 0; /* alls well */
+ else if (acktype < 0)
+ return _pmtraceremaperr(acktype);
+ else if (type == 0)
+ /* acktype contains PDU version if needed (not currently) */
+ return acktype;
+ return PMTRACE_ERR_IPC;
+ }
+ else {
+ fprintf(stderr, "_pmtracegetack: unknown PDU type (0x%x)\n", status);
+ return PMTRACE_ERR_IPC;
+ }
+ }
+ return _pmtraceremaperr(sts);
+}
+
+
+static void
+_pmtraceupdatewait(void)
+{
+ static int defbackoff[] = {5, 10, 20, 40, 80};
+ static int *backoff = NULL;
+ static int n_backoff = 0;
+ char *q;
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceupdatewait: updating reconnect back-off\n");
+#endif
+ if (n_backoff == 0) { /* compute backoff before trying again */
+ if ((q = getenv(TRACE_ENV_RECTIMEOUT)) != NULL) {
+ char *pend, *p;
+ int val;
+
+ for (p=q; *p != '\0'; ) {
+ val = (int)strtol(p, &pend, 10);
+ if (val <= 0 || (*pend != ',' && *pend != '\0')) {
+ n_backoff = 0;
+ if (backoff != NULL)
+ free(backoff);
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceupdatewait: bad reconnect "
+ "format in %s.\n", TRACE_ENV_RECTIMEOUT);
+#endif
+ break;
+ }
+ if ((backoff = (int *)realloc(backoff, n_backoff *
+ sizeof(backoff[0]))) == NULL)
+ break;
+ backoff[n_backoff++] = val;
+ if (*pend == '\0')
+ break;
+ p = &pend[1];
+ }
+ }
+ if (n_backoff == 0) { /* use defaults */
+ n_backoff = 5;
+ backoff = defbackoff;
+ }
+ }
+ if (_pmtimedout == 0)
+ _pmtimedout = 1;
+ else if (_pmtimedout < n_backoff)
+ _pmtimedout++;
+ _pmttimeout = time(NULL) + backoff[_pmtimedout-1];
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceupdatewait: next attempt after %d seconds\n",
+ backoff[_pmtimedout-1]);
+#endif
+}
+
+
+static int
+_pmtracereconnect(void)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_NOAGENT) {
+ fprintf(stderr, "_pmtracereconnect: reconnect attempt (skipped)\n");
+ return 0;
+ }
+ else if (__pmstate & PMTRACE_STATE_COMMS) {
+ fprintf(stderr, "_pmtracereconnect: attempting PMDA reconnection\n");
+ }
+#endif
+
+ if (_pmtimedout && time(NULL) < _pmttimeout) { /* too soon to retry */
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtracereconnect: too soon to retry "
+ "(%d seconds remain)\n", (int)(_pmttimeout - time(NULL)));
+#endif
+ return -ETIMEDOUT;
+ }
+ if (__pmfd >= 0) {
+ __pmtracenomoreinput(__pmfd);
+ __pmCloseSocket(__pmfd);
+ __pmfd = -1;
+ }
+ if (_pmtraceconnect(1) < 0) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtracereconnect: failed to reconnect\n");
+#endif
+ _pmtraceupdatewait();
+ return -ETIMEDOUT;
+ }
+ else {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtracereconnect: reconnect succeeded!\n");
+#endif
+ _pmtimedout = 0;
+ }
+ return 0;
+}
+
+#ifndef IS_MINGW
+static struct itimerval _pmmyitimer, off_itimer;
+static void _pmtracealarm(int dummy) { }
+static void _pmtraceinit(void) { }
+#else
+static void _pmtraceinit(void)
+{
+ WORD wVersionRequested = MAKEWORD(2, 2);
+ WSADATA wsaData;
+ WSAStartup(wVersionRequested, &wsaData);
+ _fmode = O_BINARY;
+}
+#endif
+
+static int
+_pmtraceconnect(int doit)
+{
+ static int first = 1;
+ int sts = 0;
+
+ if (!_pmtimedout)
+ return 0;
+ else if (first) { /* once-off, not to be done on reconnect */
+ _pmtraceinit();
+ if (TRACE_LOCK_INIT < 0)
+ return -oserror();
+ first = 0;
+ TRACE_LOCK;
+ sts = __pmhashinit(&_pmtable, 0, sizeof(_pmTraceLibdata),
+ _pmlibcmp, _pmlibdel);
+ if (TRACE_UNLOCK != 0)
+ return -oserror();
+ }
+ else if (__pmtraceprotocol(TRACE_PROTOCOL_QUERY) == TRACE_PROTOCOL_ASYNC)
+ return PMTRACE_ERR_IPC;
+
+ if (sts >= 0 && doit)
+ sts = _pmauxtraceconnect();
+ if (sts >= 0)
+ __pmtraceprotocol(TRACE_PROTOCOL_FINAL);
+
+ return sts;
+}
+
+static int
+_pmauxtraceconnect(void)
+{
+ int port = TRACE_PORT;
+ char hostname[MAXHOSTNAMELEN];
+ struct timeval timeout = { 3, 0 }; /* default 3 secs */
+ __pmSockAddr *myaddr;
+ __pmHostEnt *servinfo;
+ void *enumIx;
+#ifndef IS_MINGW
+ struct itimerval _pmolditimer;
+ void (*old_handler)(int foo);
+#endif
+ int rc, sts;
+ int flags = 0;
+ char *sptr, *endptr, *endnum;
+ struct timeval canwait = { 5, 000000 };
+ struct timeval stv;
+ struct timeval *pstv;
+ __pmFdSet wfds;
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_NOAGENT) {
+ fprintf(stderr, "_pmtraceconnect: connecting to PMDA (skipped)\n");
+ return 0;
+ }
+ else if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceconnect: connecting to PMDA ...\n");
+#endif
+
+ /*
+ * get optional stuff from environment ...
+ * PCP_TRACE_HOST, PCP_TRACE_PORT, PCP_TRACE_TIMEOUT, and
+ * PCP_TRACE_NOAGENT
+ */
+ if ((sptr = getenv(TRACE_ENV_HOST)) != NULL)
+ strcpy(hostname, sptr);
+ else {
+ (void)gethostname(hostname, MAXHOSTNAMELEN);
+ hostname[MAXHOSTNAMELEN-1] = '\0';
+ }
+ if ((sptr = getenv(TRACE_ENV_PORT)) != NULL) {
+ port = (int)strtol(sptr, &endnum, 0);
+ if (*endnum != '\0' || port < 0) {
+ fprintf(stderr, "trace warning: bad PCP_TRACE_PORT ignored.");
+ port = TRACE_PORT;
+ }
+ }
+ if ((sptr = getenv(TRACE_ENV_TIMEOUT)) != NULL) {
+ double timesec = strtod(sptr, &endptr);
+ if (*endptr != '\0' || timesec < 0.0)
+ fprintf(stderr, "trace warning: bogus PCP_TRACE_TIMEOUT.");
+ else {
+ timeout.tv_sec = (time_t)timesec;
+ timeout.tv_usec = (int)((timesec - (double)timeout.tv_sec)*1000000);
+ }
+ }
+ if (getenv(TRACE_ENV_NOAGENT) != NULL)
+ __pmstate |= PMTRACE_STATE_NOAGENT;
+
+ if ((servinfo = __pmGetAddrInfo(hostname)) == NULL) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceconnect(__pmGetAddrInfo(hostname=%s): "
+ "hosterror=%d, ``%s''\n", hostname, hosterror(),
+ hoststrerror());
+#endif
+ return -EHOSTUNREACH;
+ }
+
+ /* Try each address in turn until one connects. */
+ sts = EHOSTUNREACH;
+ __pmfd = -1;
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myaddr))
+ __pmfd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ __pmfd = __pmCreateIPv6Socket();
+ else {
+ fprintf(stderr, "_pmtraceconnect(invalid address family): %d\n",
+ __pmSockAddrGetFamily(myaddr));
+ }
+ if (__pmfd < 0) {
+ sts = neterror();
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ /* Set the port. */
+ __pmSockAddrSetPort(myaddr, port);
+
+#ifndef IS_MINGW
+ /* arm interval timer */
+ _pmmyitimer.it_value.tv_sec = timeout.tv_sec;
+ _pmmyitimer.it_value.tv_usec = timeout.tv_usec;
+ _pmmyitimer.it_interval.tv_sec = 0;
+ _pmmyitimer.it_interval.tv_usec = 0;
+ old_handler = signal(SIGALRM, _pmtracealarm);
+ setitimer(ITIMER_REAL, &_pmmyitimer, &_pmolditimer);
+#endif
+
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS) {
+ char *name = __pmHostEntGetName (servinfo);
+ fprintf(stderr, "_pmtraceconnect: PMDA host=%s port=%d timeout=%d"
+ "secs\n", name == NULL ? "unknown" : name, port, (int)timeout.tv_sec);
+ if (name != NULL)
+ free(name);
+ }
+#endif
+
+ /* Attempt to connect */
+ flags = __pmConnectTo(__pmfd, myaddr, port);
+ __pmSockAddrFree(myaddr);
+
+ if (flags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address. __pmfd has been closed in __pmConnectTo().
+ */
+ sts = -flags;
+ __pmfd = -1;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ stv = canwait;
+ pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(__pmfd, &wfds);
+ if ((rc = __pmSelectWrite(__pmfd+1, &wfds, pstv)) == 1) {
+ sts = __pmConnectCheckError(__pmfd);
+ }
+ else if (rc == 0) {
+ sts = ETIMEDOUT;
+ }
+ else {
+ sts = (rc < 0) ? neterror() : EINVAL;
+ }
+
+#ifndef IS_MINGW
+ /* re-arm interval timer */
+ setitimer(ITIMER_REAL, &off_itimer, &_pmmyitimer);
+ signal(SIGALRM, old_handler);
+ if (_pmolditimer.it_value.tv_sec != 0 && _pmolditimer.it_value.tv_usec != 0) {
+ _pmolditimer.it_value.tv_usec -= timeout.tv_usec - _pmmyitimer.it_value.tv_usec;
+ while (_pmolditimer.it_value.tv_usec < 0) {
+ _pmolditimer.it_value.tv_usec += 1000000;
+ _pmolditimer.it_value.tv_sec--;
+ }
+ while (_pmolditimer.it_value.tv_usec > 1000000) {
+ _pmolditimer.it_value.tv_usec -= 1000000;
+ _pmolditimer.it_value.tv_sec++;
+ }
+ _pmolditimer.it_value.tv_sec -= timeout.tv_sec - _pmmyitimer.it_value.tv_sec;
+ if (_pmolditimer.it_value.tv_sec < 0) {
+ /* missed the user's itimer, pretend there is 1 msec to go! */
+ _pmolditimer.it_value.tv_sec = 0;
+ _pmolditimer.it_value.tv_usec = 1000;
+ }
+ setitimer(ITIMER_REAL, &_pmolditimer, &_pmmyitimer);
+ }
+#endif
+
+ /* Was the connection successful? */
+ if (sts == 0)
+ break;
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(__pmfd);
+ __pmfd = -1;
+ } /* loop over addresses */
+
+ __pmHostEntFree(servinfo);
+
+ /* Was the connection successful? */
+ if (__pmfd < 0) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceconnect(socket failed): %s\n",
+ netstrerror());
+#endif
+ return -sts;
+ }
+
+ _pmtimedout = 0;
+
+ /* Restore the original file status flags. */
+ if (__pmSetFileStatusFlags(__pmfd, flags) < 0) {
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, ":_pmtraceconnect: cannot restore file status flags\n");
+#endif
+ return -oserror();
+ }
+
+ /* make sure this file descriptor is closed if exec() is called */
+ if ((flags = __pmGetFileDescriptorFlags(__pmfd)) != -1)
+ sts = __pmSetFileDescriptorFlags(__pmfd, flags | FD_CLOEXEC);
+ else
+ sts = -1;
+ if (sts == -1)
+ return -oserror();
+
+ if (__pmtraceprotocol(TRACE_PROTOCOL_QUERY) == TRACE_PROTOCOL_ASYNC) {
+ /* in the asynchronoous protocol - ensure no delay after close */
+ if ((flags = __pmGetFileStatusFlags(__pmfd)) != -1)
+ sts = __pmSetFileStatusFlags(__pmfd, flags | FNDELAY);
+ else
+ sts = -1;
+ if (sts == -1)
+ return -oserror();
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceconnect: async protocol setup complete\n");
+#endif
+ }
+ else
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_COMMS)
+ fprintf(stderr, "_pmtraceconnect: sync protocol setup complete\n");
+#endif
+
+ /* trace PMDA sends an ACK on successful connect */
+ sts = _pmtracegetack(sts, 0);
+
+ return sts;
+}
+
+
+/* this is used internally to maintain connection state */
+int
+__pmtraceprotocol(int update)
+{
+ static int fixedinstone = 0;
+ static int protocol = TRACE_PROTOCOL_SYNC;
+
+ if (update == TRACE_PROTOCOL_QUERY) /* just a state request */
+ return protocol;
+ else if (update == TRACE_PROTOCOL_FINAL) /* no more changes allowed */
+ fixedinstone = 1;
+ else if (!fixedinstone && update == TRACE_PROTOCOL_ASYNC) {
+ __pmstate |= PMTRACE_STATE_ASYNC;
+ protocol = update;
+ }
+
+ return protocol;
+}
+
+int
+pmtracestate(int code)
+{
+ int old = __pmstate;
+
+ if (code & PMTRACE_STATE_ASYNC) {
+ if (__pmtraceprotocol(TRACE_PROTOCOL_ASYNC) != TRACE_PROTOCOL_ASYNC)
+ /* only can do this before connection established */
+ return -EINVAL;
+ }
+
+ __pmstate = code;
+ return old;
+}
diff --git a/src/mrtg2pcp/GNUmakefile b/src/mrtg2pcp/GNUmakefile
new file mode 100644
index 0000000..caf3cd9
--- /dev/null
+++ b/src/mrtg2pcp/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPT = mrtg2pcp
+LDIRT = $(MAN_PAGES) $(MAN_PAGES).tmp
+LSRCFILES = $(SCRIPT) README
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = $(SCRIPT).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: $(MAN_PAGES)
+
+$(SCRIPT).$(MAN_SECTION): $(SCRIPT)
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BIN_DIR)/$(SCRIPT)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/mrtg2pcp/README b/src/mrtg2pcp/README
new file mode 100644
index 0000000..8198fdd
--- /dev/null
+++ b/src/mrtg2pcp/README
@@ -0,0 +1,13 @@
+Converting a mrtg log file to a PCP archive
+
+This example uses the PCP::LogImport Perl wrapper around the libpcp_import
+library to convert a sadc datafile into a PCP archive.
+
+Usage: mrtg2pcp hostname devname timezone mrtglogfile archive
+
+The translation currently supports the following PCP metrics:
+ network.interface.in.bytes
+ network.interface.out.bytes
+
+This is sufficient to support the following standard pmchart views:
+ Netbytes
diff --git a/src/mrtg2pcp/mrtg2pcp b/src/mrtg2pcp/mrtg2pcp
new file mode 100755
index 0000000..5fac924
--- /dev/null
+++ b/src/mrtg2pcp/mrtg2pcp
@@ -0,0 +1,144 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+
+use PCP::LogImport;
+
+if ($#ARGV != 4) {
+ print "Usage: mrtg2pcp hostname devname timezone infile outfile\n";
+ exit(1);
+}
+
+# Note: these match the linux pmda, change to your heart's content
+my $domain = 60;
+my $cluster = 3;
+my $in_item = 0;
+my $out_item = 8;
+my $indomid = 3;
+
+# the internal instance id
+my $intinstid= 1;
+
+my $hostname=$ARGV[0];
+my $devname=$ARGV[1];
+my $zone=$ARGV[2];
+my $infile=$ARGV[3];
+my $outfile=$ARGV[4];
+
+# first, read the input file
+open(INFILE, "<$infile") or die "open($infile): $!";
+
+<INFILE>; # skip the first line
+
+my @lines = <INFILE>; # read the remainder into memory
+@lines = reverse(@lines);
+
+close(INFILE) or die "close($infile): $!";
+
+# now the PCP part
+pmiStart($outfile, 0) >= 0
+ or die "pmiStart($outfile, 0): " . pmiErrStr(-1) . "\n";
+
+pmiSetHostname($hostname) == 0
+ or die "pmiSetHostname($hostname): ". pmiErrStr(-1) . "\n";
+
+pmiSetTimezone($zone) == 0
+ or die "pmiSetTimezone($zone): ". pmiErrStr(-1) . "\n";
+
+my $indom = pmInDom_build($domain, $indomid);
+pmiAddMetric("network.interface.in.bytes",
+ pmid_build($domain,$cluster,$in_item), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmiUnits(1,0,0,PM_SPACE_BYTE,0,0)) == 0
+ or die "pmiAddMetric(network.interface.in.bytes, ...): " . pmiErrStr(-1) . "\n";
+pmiAddMetric("network.interface.out.bytes",
+ pmid_build($domain,$cluster,$out_item), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmiUnits(1,0,0,PM_SPACE_BYTE,0,0)) == 0
+ or die "pmiAddMetric(network.interface.out.bytes, ...): " . pmiErrStr(-1) . "\n";
+
+pmiAddInstance($indom, $devname, $intinstid) >= 0
+ or die "pmiAddInstance(..., ".$devname.", $intinstid): " . pmiErrStr(-1) . "\n";
+
+my $ihndl = pmiGetHandle("network.interface.in.bytes", $devname);
+my $ohndl = pmiGetHandle("network.interface.out.bytes", $devname);
+
+my @prev = ();
+my $ictr = 0;
+my $octr = 0;
+foreach (@lines) {
+ my @info = split(/ /);
+
+ if (scalar(@prev) > 0) {
+ $ictr += $info[1] * ($info[0] - $prev[0]);
+ $octr += $info[2] * ($info[0] - $prev[0]);
+ }
+
+ pmiPutValueHandle($ihndl, $ictr);
+ pmiPutValueHandle($ohndl, $octr);
+
+ pmiWrite($info[0], 0) >= 0
+ or die "pmiWrite: @ ".$info[0].": " . pmiErrStr(-1) . "\n";
+
+ @prev = @info;
+}
+
+
+pmiEnd();
+
+=pod
+
+=head1 NAME
+
+mrtg2pcp - Import mrtg data and create a PCP archive
+
+=head1 SYNOPSIS
+
+B<mrtg2pcp> I<hostname> I<devname> I<timezone> I<infile> I<outfile>
+
+=head1 DESCRIPTION
+
+B<mrtg2pcp> is intended to read an MRTG log file as created by B<mrtg>(1)
+and translate this into a Performance Co-Pilot (PCP) archive with the
+basename I<outfile>. The I<hostname>, I<devname>, and I<timezone> arguments
+specify information about the system for which the statistics were gathered.
+
+The resultant PCP achive may be used with all the PCP client tools
+to graph subsets of the data using B<pmchart>(1),
+perform data reduction and reporting, filter with
+the PCP inference engine B<pmie>(1), etc.
+
+A series of physical files will be created with the prefix I<outfile>.
+These are I<outfile>B<.0> (the performance data),
+I<outfile>B<.meta> (the metadata that describes the performance data) and
+I<outfile>B<.index> (a temporal index to improve efficiency of replay
+operations for the archive). If any of these files exists already,
+then B<mrtg2pcp> will B<not> overwrite them and will exit with an error
+message of the form
+
+__pmLogNewFile: "blah.0" already exists, not over-written
+
+B<mrtg2pcp> is a Perl script that uses the PCP::LogImport Perl wrapper
+around the PCP I<libpcp_import>
+library, and as such could be used as an example to develop new
+tools to import other types of performance data and create PCP archives.
+
+=head1 SEE ALSO
+
+B<logimport>(3),
+B<PCP::LogImport>(3pm),
+B<pmchart>(1),
+B<pmie>(1)
+B<pmlogger>(1).
diff --git a/src/newhelp/GNUmakefile b/src/newhelp/GNUmakefile
new file mode 100644
index 0000000..658f5cc
--- /dev/null
+++ b/src/newhelp/GNUmakefile
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGETS = newhelp$(EXECSUFFIX) chkhelp$(EXECSUFFIX)
+CFILES = newhelp.c chkhelp.c
+
+LLDFLAGS = $(WARN_OFF)
+LLDLIBS = $(PCP_PMDALIB)
+LDIRT = $(TARGETS)
+
+default: $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 $(TARGETS) $(PCP_BINADM_DIR)
+
+include $(BUILDRULES)
+
+newhelp$(EXECSUFFIX): newhelp.o
+ $(CCF) -o $@ $(LDFLAGS) newhelp.o $(LDLIBS)
+
+chkhelp$(EXECSUFFIX): chkhelp.o
+ $(CCF) -o $@ $(LDFLAGS) chkhelp.o $(LDLIBS)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/newhelp/chkhelp.c b/src/newhelp/chkhelp.c
new file mode 100644
index 0000000..3e4f4ad
--- /dev/null
+++ b/src/newhelp/chkhelp.c
@@ -0,0 +1,348 @@
+/*
+ *
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * check help files build by newhelp
+ *
+ * Usage:
+ * chkhelp helpfile metricname ...
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#define VERSION 2
+static int version = VERSION;
+
+static int handle;
+
+/*
+ * Note: these two are from libpcp_pmda/src/help.c
+ */
+typedef struct {
+ pmID pmid;
+ __uint32_t off_oneline;
+ __uint32_t off_text;
+} help_idx_t;
+
+typedef struct {
+ int dir_fd;
+ int pag_fd;
+ int numidx;
+ help_idx_t *index;
+ char *text;
+ int textlen;
+} help_t;
+
+static int
+next(int *ident, int *type)
+{
+ static help_t *hp = NULL;
+ static int nextidx;
+ pmID pmid;
+ __pmID_int *pi = (__pmID_int *)&pmid;
+ extern void *__pmdaHelpTab(void);
+
+ if (hp == NULL) {
+ hp = (help_t *)__pmdaHelpTab();
+ /*
+ * Note, skip header and version info at index[0]
+ */
+ nextidx = 1;
+ }
+
+ if (nextidx > hp->numidx)
+ return 0;
+
+ pmid = hp->index[nextidx].pmid;
+ nextidx++;
+
+ if (pi->flag == 0) {
+ /* real PMID */
+ *ident = (int)pmid;
+ *type = 1;
+ }
+ else {
+ /* special hack, this is encoding a domain id, not a PMID */
+ pi->flag = 0;
+ *ident = (int)pmid;
+ *type = 2;
+ }
+
+ return 1;
+}
+
+
+/*
+ * with -e come here for every metric in the PMNS ...
+ */
+void
+dometric(const char *name)
+{
+ int sts;
+ pmID pmid;
+ char *tp;
+
+ sts = pmLookupName(1, (char **)&name, &pmid);
+ if (sts < 0) {
+ fprintf(stderr, "pmLookupName: failed for \"%s\": %s\n", name, pmErrStr(sts));
+ return;
+ }
+ if (sts == 0) {
+ fprintf(stderr, "pmLookupName: failed for \"%s\"\n", name);
+ return;
+ }
+
+ tp = pmdaGetHelp(handle, pmid, PM_TEXT_ONELINE);
+ if (tp != NULL)
+ return;
+ tp = pmdaGetHelp(handle, pmid, PM_TEXT_HELP);
+ if (tp != NULL)
+ return;
+
+ /* no help text, report metric */
+ printf("%s\n", name);
+}
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ int c;
+ int help = 0;
+ int oneline = 0;
+ char *pmnsfile = PM_NS_DEFAULT;
+ int errflag = 0;
+ int aflag = 0;
+ int eflag = 0;
+ int allpmid = 0;
+ int allindom = 0;
+ char *filename;
+ char *tp;
+ char *name;
+ int id;
+ int next_type;
+ char *endnum;
+
+ __pmSetProgname(argv[0]);
+
+ while ((c = getopt(argc, argv, "D:eHin:Opv:?")) != EOF) {
+ switch (c) {
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(optarg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, optarg);
+ errflag++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'e': /* help text exists? */
+ eflag = 1;
+ break;
+
+ case 'H': /* help text */
+ help = 1;
+ break;
+
+ case 'i':
+ aflag++;
+ allindom = 1;
+ break;
+
+ case 'n': /* alternative namespace file */
+ pmnsfile = optarg;
+ break;
+
+ case 'O': /* oneline text */
+ oneline = 1;
+ break;
+
+ case 'p':
+ aflag++;
+ allpmid = 1;
+ break;
+
+ case 'v': /* version 2 only these days */
+ version = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr, "%s: -v requires numeric argument\n", pmProgname);
+ errflag++;
+ }
+ if (version != 2) {
+ fprintf(stderr
+ ,"%s: deprecated option - only version 2 is supported\n"
+ , pmProgname);
+ errflag++;
+ }
+ break;
+
+ case '?':
+ default:
+ errflag++;
+ break;
+ }
+ }
+
+ if (optind == argc) {
+ fprintf(stderr, "%s: missing helpfile argument\n\n", pmProgname);
+ errflag = 1;
+ }
+
+ if (aflag && optind < argc-1) {
+ fprintf(stderr, "%s: metricname arguments cannot be used with -i or -p\n\n",
+ pmProgname);
+ errflag = 1;
+ }
+
+ if (aflag == 0 && optind == argc-1 && oneline+help != 0) {
+ fprintf(stderr, "%s: -O or -H require metricname arguments or -i or -p\n\n",
+ pmProgname);
+ errflag = 1;
+ }
+
+ if (eflag && (allpmid || allindom)) {
+ fprintf(stderr, "%s: -e cannot be used with -i or -p\n\n",
+ pmProgname);
+ errflag = 1;
+ }
+
+ if (errflag || optind >= argc) {
+ fprintf(stderr,
+"Usage: %s helpfile\n"
+" %s [options] helpfile [metricname ...]\n"
+"\n"
+"Options:\n"
+" -e exists check, only report metrics with no help text\n"
+" -H display verbose help text\n"
+" -i process all the instance domains\n"
+" -n pmnsfile use an alternative PMNS\n"
+" -O display the one line help summary\n"
+" -p process all the metrics (PMIDs)\n"
+" -v version deprecated (only version 2 format supported)\n"
+"\n"
+"No options implies silently check internal integrity of the helpfile.\n",
+ pmProgname, pmProgname);
+ exit(1);
+ }
+
+ filename = argv[optind++];
+ if ((handle = pmdaOpenHelp(filename)) < 0) {
+ fprintf(stderr, "pmdaOpenHelp: failed to open \"%s\": ", filename);
+ if (handle == -EINVAL)
+ fprintf(stderr, "Bad format, not version %d PCP help text\n", version);
+ else
+ fprintf(stderr, "%s\n", pmErrStr(handle));
+ exit(1);
+ }
+
+ if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ fprintf(stderr, "pmLoadNameSpace: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+
+ if (help + oneline == 0 && (optind < argc || aflag))
+ /* if metric names, -p or -i => -O is default */
+ oneline = 1;
+
+ if (optind == argc && aflag == 0)
+ /* no metric names, process all entries */
+ aflag = 1;
+
+ if (eflag) {
+ if (optind == argc)
+ sts = pmTraversePMNS("", dometric);
+ if (sts < 0)
+ fprintf(stderr, "Error: pmTraversePMNS(\"\", ...): %s\n", pmErrStr(sts));
+ else {
+ for ( ; optind < argc; optind++) {
+ sts = pmTraversePMNS(argv[optind], dometric);
+ if (sts < 0)
+ fprintf(stderr, "Error: pmTraversePMNS(\"%s\", ...): %s\n", argv[optind], pmErrStr(sts));
+ }
+ }
+ exit(0);
+ }
+
+ while (optind < argc || aflag) {
+ if (aflag) {
+ if (next(&id, &next_type) == 0)
+ break;
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL0) && allindom+allpmid == 0)
+ fprintf(stderr, "next_type=%d id=0x%x\n", next_type, id);
+#endif
+ if (next_type == 2) {
+ if (!allindom)
+ continue;
+ printf("\nInDom %s:", pmInDomStr((pmInDom)id));
+ }
+ else {
+ char *p;
+ if (!allpmid)
+ continue;
+
+ printf("\nPMID %s", pmIDStr((pmID)id));
+ sts = pmNameID(id, &p);
+ if (sts == 0) {
+ printf(" %s", p);
+ free(p);
+ }
+ putchar(':');
+ }
+ }
+ else {
+ next_type = 1;
+ name = argv[optind++];
+ if ((sts = pmLookupName(1, &name, (pmID *)&id)) < 0) {
+ printf("\n%s: %s\n", name, pmErrStr(sts));
+ continue;
+ }
+ if (id == PM_ID_NULL) {
+ printf("\n%s: unknown metric\n", name);
+ continue;
+ }
+ printf("\nPMID %s %s:", pmIDStr((pmID)id), name);
+ }
+
+ if (oneline) {
+ if (next_type == 1)
+ tp = pmdaGetHelp(handle, (pmID)id, PM_TEXT_ONELINE);
+ else
+ tp = pmdaGetInDomHelp(handle, (pmInDom)id, PM_TEXT_ONELINE);
+ if (tp != NULL)
+ printf(" %s", tp);
+ putchar('\n');
+ }
+
+ if (help) {
+ if (next_type == 1)
+ tp = pmdaGetHelp(handle, (pmID)id, PM_TEXT_HELP);
+ else
+ tp = pmdaGetInDomHelp(handle, (pmInDom)id, PM_TEXT_HELP);
+ if (tp != NULL && *tp)
+ printf("%s\n", tp);
+ }
+
+ }
+
+ return 0;
+}
diff --git a/src/newhelp/newhelp.c b/src/newhelp/newhelp.c
new file mode 100644
index 0000000..97f0eaa
--- /dev/null
+++ b/src/newhelp/newhelp.c
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * newhelp file
+ *
+ * in the model of newaliases, build ndbm data files for a PMDA's help file
+ * -- given the bloat of the ndbm files, version 2 uses a much simpler
+ * file access method, but preserves the file name conventions from
+ * version 1 that is based on ndbm.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include "pmapi.h"
+#include "impl.h"
+
+#define DEFAULT_HELP_VERSION 2
+
+/* maximum bytes per line and bytes per entry */
+#define MAXLINE 128
+#define MAXENTRY 1024
+
+static int verbose;
+static int ln;
+static char *filename;
+static int status;
+static int version = DEFAULT_HELP_VERSION;
+static FILE *f;
+
+typedef struct {
+ pmID pmid;
+ __uint32_t off_oneline;
+ __uint32_t off_text;
+} help_idx_t;
+
+static help_idx_t *hindex;
+static int numindex;
+static int thisindex = -1;
+
+static void
+newentry(char *buf)
+{
+ int n;
+ char *p;
+ char *end_name;
+ char end_c;
+ char *name;
+ pmID pmid;
+ char *start;
+ int warn = 0;
+ int i;
+
+ /* skip leading white space ... */
+ for (p = buf; isspace((int)*p); p++)
+ ;
+ /* skip over metric name or indom spec ... */
+ name = p;
+ for (p = buf; *p != '\n' && !isspace((int)*p); p++)
+ ;
+ end_c = *p;
+ *p = '\0'; /* terminate metric name */
+ end_name = p;
+
+ if ((n = pmLookupName(1, &name, &pmid)) < 0) {
+ /* apparently not a metric name */
+ int domain;
+ int cluster;
+ int item;
+ int serial;
+ pmID *pmidp;
+ if (sscanf(buf, "%d.%d.%d", &domain, &cluster, &item) == 3) {
+ /* a numeric pmid */
+ __pmID_int ii;
+ ii.domain = domain;
+ ii.cluster = cluster;
+ ii.item = item;
+ ii.flag = 0;
+ pmidp = (pmID *)&ii;
+ pmid = *pmidp;
+ }
+ else if (sscanf(buf, "%d.%d", &domain, &serial) == 2) {
+ /* an entry for an instance domain */
+ __pmInDom_int ii;
+ ii.domain = domain;
+ ii.serial = serial;
+ /* set a bit here to disambiguate pmInDom from pmID */
+ ii.flag = 1;
+ pmidp = (pmID *)&ii;
+ pmid = *pmidp;
+ }
+ else {
+ fprintf(stderr, "%s: [%s:%d] %s: %s, entry abandoned\n",
+ pmProgname, filename, ln, buf, pmErrStr(n));
+ status = 2;
+ return;
+ }
+ }
+ else {
+ if (pmid == PM_ID_NULL) {
+ fprintf(stderr, "%s: [%s:%d] %s: unknown metric, entry abandoned\n",
+ pmProgname, filename, ln, name);
+ status = 2;
+ return;
+ }
+ }
+
+ for (i = 0; i < thisindex; i++) {
+ if (hindex[thisindex].pmid == pmid) {
+ __pmInDom_int *kp = (__pmInDom_int *)&pmid;
+ fprintf(stderr, "%s: [%s:%d] duplicate key (",
+ pmProgname, filename, ln);
+ if (kp->flag == 0)
+ fprintf(stderr, "%s", pmIDStr(pmid));
+ else {
+ kp->flag = 0;
+ fprintf(stderr, "%s", pmInDomStr((pmInDom)pmid));
+ }
+ fprintf(stderr, ") entry abandoned\n");
+ status = 2;
+ return;
+ }
+ }
+
+ if (++thisindex >= numindex) {
+ if (numindex == 0)
+ numindex = 128;
+ else
+ numindex *= 2;
+ if ((hindex = (help_idx_t *)realloc(hindex, numindex * sizeof(hindex[0]))) == NULL) {
+ __pmNoMem("newentry", numindex * sizeof(hindex[0]), PM_FATAL_ERR);
+ }
+ }
+
+ fprintf(f, "\n@ %s ", name);
+
+ hindex[thisindex].pmid = pmid;
+ hindex[thisindex].off_oneline = ftell(f);
+
+ /* skip white space ... to start of oneline */
+ *p = end_c;
+ if (*p != '\n')
+ p++;
+ for ( ; *p != '\n' && isspace((int)*p); p++)
+ ;
+ start = p;
+
+ /* skip to end of line ... */
+ for ( ; *p != '\n'; p++)
+ ;
+ *p = '\0';
+ p++;
+
+ if (p - start == 1 && verbose) {
+ fprintf(stderr, "%s: [%s:%d] %s: warning, null oneline\n",
+ pmProgname, filename, ln, name);
+ warn = 1;
+ if (!status) status = 1;
+ }
+
+ if (fwrite(start, sizeof(*start), p - start, f) != p - start || ferror(f)) {
+ fprintf(stderr, "%s: [%s:%d] %s: write oneline failed, entry abandoned\n",
+ pmProgname, filename, ln, name);
+ thisindex--;
+ status = 2;
+ return;
+ }
+
+ hindex[thisindex].off_text = ftell(f);
+
+ /* trim all but last newline ... */
+ i = (int)strlen(p) - 1;
+ while (i >= 0 && p[i] == '\n')
+ i--;
+ if (i < 0)
+ i = 0;
+ else {
+ /* really have text ... p[i] is last non-newline char */
+ i++;
+ if (version == 1)
+ p[i++] = '\n';
+ }
+ p[i] = '\0';
+
+ if (i == 0 && verbose) {
+ fprintf(stderr, "%s: [%s:%d] %s: warning, null help\n",
+ pmProgname, filename, ln, name);
+ warn = 1;
+ if (!status) status = 1;
+ }
+
+ if (fwrite(p, sizeof(*p), i+1, f) != i+1 || ferror(f)) {
+ fprintf(stderr,
+ "%s: [%s:%d] %s: write help failed, entry abandoned\n",
+ pmProgname, filename, ln, name);
+ thisindex--;
+ status = 2;
+ return;
+ }
+
+ if (verbose && warn == 0) {
+ *end_name = '\0';
+ fprintf(stderr, "%s\n", name);
+ *end_name = end_c;
+ }
+}
+
+static int
+idcomp(const void *a, const void *b)
+{
+ /*
+ * comparing 32-bit keys here ... want PMIDs to go first the
+ * InDoms, sort by low order bits ... serial from InDom is easier
+ * than cluster and item from PMID, so use InDom format
+ */
+ __pmInDom_int *iiap, *iibp;
+
+ iiap = (__pmInDom_int *)(&((help_idx_t *)a)->pmid);
+ iibp = (__pmInDom_int *)(&((help_idx_t *)b)->pmid);
+
+ if (iiap->flag == iibp->flag)
+ /* both of the same type, use serial to order */
+ return iiap->serial - iibp->serial;
+ else if (iiap->flag == 0)
+ /* a is the PMID, b is an InDom */
+ return -1;
+ else
+ /* b is the PMID, a is an InDom */
+ return 1;
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ PMOPT_NAMESPACE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Output options"),
+ { "output", 1, 'o', "FILE", "base name for output files" },
+ { "verbose", 0, 'V', 0, "verbose/diagnostic output" },
+ { "version", 0, 'v', 0, "deprecated (only version 2 format supported)" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:n:o:Vv:?",
+ .long_options = longopts,
+ .short_usage = "[options] [file ...]",
+};
+
+int
+main(int argc, char **argv)
+{
+ int n;
+ int c;
+ int i;
+ int sts;
+ char *pmnsfile = PM_NS_DEFAULT;
+ char *fname = NULL;
+ char pathname[MAXPATHLEN];
+ FILE *inf;
+ char buf[MAXENTRY+MAXLINE];
+ char *endnum;
+ char *bp;
+ char *p;
+ int skip;
+ help_idx_t hdr;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'D': /* debug flag */
+ if ((sts = __pmParseDebug(opts.optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'n': /* alternative namespace file */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'o': /* alternative output file name */
+ fname = opts.optarg;
+ break;
+
+ case 'V': /* more chit-chat */
+ verbose++;
+ break;
+
+ case 'v': /* version 2 only these days */
+ version = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: -v requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ if (version != 2) {
+ pmprintf("%s: deprecated option - only version 2 is supported\n",
+ pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(2);
+ }
+
+ if ((n = pmLoadNameSpace(pmnsfile)) < 0) {
+ fprintf(stderr, "%s: pmLoadNameSpace: %s\n", pmProgname, pmErrStr(n));
+ exit(2);
+ }
+
+ do {
+ if (opts.optind < argc) {
+ filename = argv[opts.optind];
+ if ((inf = fopen(filename, "r")) == NULL) {
+ perror(filename);
+ exit(2);
+ }
+ if (fname == NULL)
+ fname = filename;
+ }
+ else {
+ if (fname == NULL) {
+ fprintf(stderr,
+ "%s: need either a -o option or a filename "
+ "argument to name the output file\n", pmProgname);
+ exit(2);
+ }
+ filename = "<stdin>";
+ inf = stdin;
+ }
+
+ if (version == 2 && f == NULL) {
+ sprintf(pathname, "%s.pag", fname);
+ if ((f = fopen(pathname, "w")) == NULL) {
+ fprintf(stderr, "%s: fopen(\"%s\", ...) failed: %s\n",
+ pmProgname, pathname, osstrerror());
+ exit(2);
+ }
+ /* header: 2 => pag cf 1 => dir */
+ fprintf(f, "PcPh2%c\n", '0' + version);
+ }
+
+ bp = buf;
+ skip = 1;
+ for ( ; ; ) {
+ if (fgets(bp, MAXLINE, inf) == NULL) {
+ skip = -1;
+ *bp = '@';
+ }
+ ln++;
+ if (bp[0] == '#')
+ continue;
+ if (bp[0] == '@') {
+ /* start of a new entry */
+ if (bp > buf) {
+ /* really have a prior entry */
+ p = bp - 1;
+ while (p > buf && *p == '\n')
+ p--;
+ *++p = '\n';
+ *++p = '\0';
+ newentry(buf);
+ }
+ if (skip == -1)
+ break;
+ skip = 0;
+ bp++; /* skip '@' */
+ while (*bp && isspace((int)*bp))
+ bp++;
+ if (bp[0] == '\0') {
+ if (verbose)
+ fprintf(stderr, "%s: [%s:%d] null entry?\n",
+ pmProgname, filename, ln);
+ skip = 1;
+ bp = buf;
+ if (!status) status = 1;
+ }
+ else {
+ for (p = bp; *p; p++)
+ ;
+ memmove(buf, bp, p - bp + 1);
+ for (bp = buf; *bp; bp++)
+ ;
+ }
+ }
+ if (skip)
+ continue;
+ for (p = bp; *p; p++)
+ ;
+ if (bp > buf && p[-1] != '\n') {
+ *p++ = '\n';
+ *p = '\0';
+ fprintf(stderr, "%s: [%s:%d] long line split after ...\n%s",
+ pmProgname, filename, ln, buf);
+ ln--;
+ if (!status) status = 1;
+ }
+ bp = p;
+ if (bp > &buf[MAXENTRY]) {
+ bp = &buf[MAXENTRY];
+ bp[-1] = '\0';
+ bp[-2] = '\n';
+ fprintf(stderr, "%s: [%s:%d] entry truncated after ... %s",
+ pmProgname, filename, ln, &bp[-64]);
+ skip = 1;
+ if (!status) status = 1;
+ }
+ }
+
+ fclose(inf);
+ opts.optind++;
+ } while (opts.optind < argc);
+
+ if (f != NULL) {
+ fclose(f);
+
+ /* do the directory index ... */
+ sprintf(pathname, "%s.dir", fname);
+ if ((f = fopen(pathname, "w")) == NULL) {
+ fprintf(stderr, "%s: fopen(\"%s\", ...) failed: %s\n",
+ pmProgname, pathname, osstrerror());
+ exit(2);
+ }
+
+ /* index header */
+ hdr.pmid = 0x50635068; /* "PcPh" */
+ /* "1" => dir, next char is version */
+ hdr.off_oneline = 0x31000000 | (('0' + version) << 16);
+ hdr.off_text = thisindex + 1; /* # entries */
+ if (fwrite(&hdr, sizeof(hdr), 1, f) != 1 || ferror(f)) {
+ fprintf(stderr, "%s: fwrite index failed: %s\n",
+ pmProgname, osstrerror());
+ exit(2);
+ }
+
+ /* sort and write index */
+ qsort((void *)hindex, thisindex+1, sizeof(hindex[0]), idcomp);
+ for (i = 0; i <= thisindex; i++) {
+ if (fwrite(&hindex[i], sizeof(hindex[0]), 1, f) != 1
+ || ferror(f)) {
+ fprintf(stderr, "%s: fwrite index failed: %s\n",
+ pmProgname, osstrerror());
+ exit(2);
+ }
+ }
+ }
+
+ exit(status);
+}
diff --git a/src/pcp/GNUmakefile b/src/pcp/GNUmakefile
new file mode 100644
index 0000000..12cefd2
--- /dev/null
+++ b/src/pcp/GNUmakefile
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = pcp.sh
+SUBDIRS = free uptime numastat dmcache
+
+default :: default_pcp
+
+default_pcp : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install :: install_pcp
+
+install_pcp : $(SUBDIRS)
+ $(INSTALL) -m 755 pcp.sh $(PCP_BIN_DIR)/pcp$(SHELLSUFFIX)
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
diff --git a/src/pcp/dmcache/GNUmakefile b/src/pcp/dmcache/GNUmakefile
new file mode 100644
index 0000000..5a2ed67
--- /dev/null
+++ b/src/pcp/dmcache/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pcp-dmcache
+MAN_SECTION = 1
+MAN_PAGES = $(TARGET).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+
+default: $(TARGET).py $(MAN_PAGES)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(TARGET).py $(PCP_BINADM_DIR)/$(TARGET)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pcp/dmcache/pcp-dmcache.1 b/src/pcp/dmcache/pcp-dmcache.1
new file mode 100644
index 0000000..cac4fc3
--- /dev/null
+++ b/src/pcp/dmcache/pcp-dmcache.1
@@ -0,0 +1,52 @@
+.TH PCP-DMCACHE 1 "PCP" "Performance Co-Pilot"
+.SH NAME
+\f3pcp-dmcache\f1 \- report on Device Mapper Cache devices
+.SH SYNOPSIS
+\f3pcp-dmcache\f1
+[\f3\-i\f1/\f3--iops\f1]
+[\f3\-R\f1/\f3--repeat\f1 \f2N\f1]
+[\f2device\f1 ...]
+.SH DESCRIPTION
+.B pcp-dmcache
+reports on the activity of any configured Device Mapper Cache targets.
+The reported information includes device IOPs, cache and metadata device
+utilization, as well as hit and miss rates and ratios for both reads and
+writes for each cache device.
+.PP
+When invoked via the
+.BR pcp (1)
+command, the
+.BR \-h /\c
+.BR \-\-host ,
+.BR \-a /\c
+.BR \-\-archive ,
+.BR \-O /\c
+.BR \-\-origin ,
+.BR \-s /\c
+.BR \-\-samples ,
+.BR \-t /\c
+.BR \-\-interval ,
+.BR \-Z /\c
+.BR \-\-timezone
+and several other options become indirectly available.
+.PP
+By default,
+.B pcp-dmcache
+reports on all available cache target devices (one line each, per sample),
+but this can be restricted to specific devices on the command line.
+.SH OPTIONS
+Additional options are available for changing the output displayed by
+.BR pcp-dmcache .
+.PP
+.BR \-i /\c
+.BR \-\-iops
+displays total read and write operations instead of the default cache hit ratio.
+.PP
+.BR \-R /\c
+.BR \-\-repeat
+changes the frequency at which the column heading is displayed.
+By default, a header is displayed after every tenth sample.
+.SH "SEE ALSO"
+.BR pcp (1)
+and
+.BR PCPIntro (1).
diff --git a/src/pcp/dmcache/pcp-dmcache.py b/src/pcp/dmcache/pcp-dmcache.py
new file mode 100755
index 0000000..778330e
--- /dev/null
+++ b/src/pcp/dmcache/pcp-dmcache.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# DmCache Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=C0103,R0914,R0902,W0141
+""" Display device mapper cache statistics for the system """
+
+import sys
+from pcp import pmapi, pmcc
+
+CACHE_METRICS = ['dmcache.cache.used', 'dmcache.cache.total',
+ 'dmcache.metadata.used', 'dmcache.metadata.total',
+ 'dmcache.read_hits', 'dmcache.read_misses',
+ 'dmcache.write_hits', 'dmcache.write_misses',
+ 'disk.dm.read', 'disk.dm.write']
+
+HEADING = \
+ '---device--- ---%used--- ---------reads--------- --------writes---------'
+SUBHEAD_IOPS = \
+ ' meta cache hit miss ops hit miss ops'
+SUBHEAD_RATIO = \
+ ' meta cache hit miss ratio hit miss ratio'
+RATIO = True # default to displaying cache hit ratios
+REPEAT = 10 # repeat heading after every N samples
+
+def option(opt, optarg, index):
+ """ Perform setup for an individual command line option """
+ global RATIO
+ global REPEAT
+ if opt == 'R':
+ REPEAT = int(optarg)
+ elif opt == 'i':
+ RATIO = False
+
+def cache_value(group, device, width, values):
+ """ Lookup value for device instance, return it in a short string """
+ if device not in values:
+ return '?'.rjust(width)
+ result = group.contextCache.pmNumberStr(values[device])
+ return result.strip(' ').rjust(width)
+
+def cache_percent(device, width, used, total):
+ """ From used and total values (dict), calculate 'percentage used' """
+ if device not in used or device not in total:
+ return '?%'.rjust(width)
+ numerator = float(used[device])
+ denominator = float(total[device])
+ if denominator == 0.0:
+ return '0%'.rjust(width)
+ value = 100.0 * numerator / denominator
+ if value >= 100.0:
+ return '100%'.rjust(width)
+ return ('%3.1f%%' % value).rjust(width)
+
+def cache_dict(group, metric):
+ """ Create an instance:value dictionary for the given metric """
+ values = group[metric].netConvValues
+ if not values:
+ return {}
+ return dict(map(lambda x: (x[1], x[2]), values))
+
+
+class DmCachePrinter(pmcc.MetricGroupPrinter):
+ """ Report device mapper cache statistics """
+
+ def __init__(self, devices):
+ """ Construct object - prepare for command line handling """
+ pmcc.MetricGroupPrinter.__init__(self)
+ self.hostname = None
+ self.devices = devices
+
+ def report_values(self, group):
+ """ Report values for one of more device mapper cache devices """
+
+ # Build several dictionaries, keyed on cache names, with the values
+ cache_used = cache_dict(group, 'dmcache.cache.used')
+ cache_total = cache_dict(group, 'dmcache.cache.total')
+ meta_used = cache_dict(group, 'dmcache.metadata.used')
+ meta_total = cache_dict(group, 'dmcache.metadata.total')
+ read_hits = cache_dict(group, 'dmcache.read_hits')
+ read_misses = cache_dict(group, 'dmcache.read_misses')
+ read_ops = cache_dict(group, 'disk.dm.read')
+ write_hits = cache_dict(group, 'dmcache.write_hits')
+ write_misses = cache_dict(group, 'dmcache.write_misses')
+ write_ops = cache_dict(group, 'disk.dm.write')
+
+ devicelist = self.devices
+ if not devicelist:
+ devicelist = cache_used.keys()
+ if not devicelist:
+ print('No values available')
+ for name in sorted(devicelist):
+ if RATIO:
+ read_column = cache_percent(name, 7, read_hits, read_ops)
+ write_column = cache_percent(name, 7, write_hits, write_ops)
+ else:
+ read_column = cache_value(group, name, 7, read_ops)
+ write_column = cache_value(group, name, 7, write_ops)
+ print('%s %s %s %s %s %s %s %s %s' % (name.ljust(12),
+ cache_percent(name, 5, meta_used, meta_total),
+ cache_percent(name, 5, cache_used, cache_total),
+ cache_value(group, name, 7, read_hits),
+ cache_value(group, name, 7, read_misses),
+ read_column,
+ cache_value(group, name, 7, write_hits),
+ cache_value(group, name, 7, write_misses),
+ write_column))
+
+ def report(self, groups):
+ """ Report driver routine - headings, sub-headings and values """
+ self.convert(groups)
+ group = groups['dmcache']
+ if groups.counter % REPEAT == 1:
+ if not self.hostname:
+ self.hostname = group.contextCache.pmGetContextHostName()
+ stamp = group.contextCache.pmCtime(long(group.timestamp))
+ title = '@ %s (host %s)' % (stamp.rstrip(), self.hostname)
+ if RATIO:
+ style = SUBHEAD_RATIO
+ else:
+ style = SUBHEAD_IOPS
+ print('%s\n%s\n%s' % (title, HEADING, style))
+ self.report_values(group)
+
+if __name__ == '__main__':
+ try:
+ options = pmapi.pmOptions('iR:?')
+ options.pmSetShortUsage('[options] [device ...]')
+ options.pmSetOptionCallback(option)
+ options.pmSetLongOptionHeader('Options')
+ options.pmSetLongOption('repeat', 1, 'R', 'N', 'repeat the header after every N samples')
+ options.pmSetLongOption('iops', 0, 'i', '', 'display IOPs instead of cache hit ratio')
+ options.pmSetLongOptionVersion()
+ options.pmSetLongOptionHelp()
+ manager = pmcc.MetricGroupManager.builder(options, sys.argv)
+ manager.printer = DmCachePrinter(options.pmNonOptionsFromList(sys.argv))
+ manager['dmcache'] = CACHE_METRICS
+ manager.run()
+ except pmapi.pmErr as error:
+ print('%s: %s\n' % (error.progname(), error.message()))
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ except KeyboardInterrupt:
+ pass
diff --git a/src/pcp/free/GNUmakefile b/src/pcp/free/GNUmakefile
new file mode 100644
index 0000000..6a73f55
--- /dev/null
+++ b/src/pcp/free/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pcp-free
+MAN_SECTION = 1
+MAN_PAGES = $(TARGET).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+
+default: $(TARGET).py $(MAN_PAGES)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(TARGET).py $(PCP_BINADM_DIR)/$(TARGET)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pcp/free/pcp-free.1 b/src/pcp/free/pcp-free.1
new file mode 100644
index 0000000..2a8b4f8
--- /dev/null
+++ b/src/pcp/free/pcp-free.1
@@ -0,0 +1,71 @@
+.TH PCP-FREE 1 "PCP" "Performance Co-Pilot"
+.SH NAME
+\f3pcp-free\f1 \- report on free and used memory in the system
+.SH SYNOPSIS
+\f3pcp-free\f1
+[\f3\-bkmgoltV\f1]
+[\f3\-c\f1 \f2count\f1]
+[\f3\-s\f1 \f2interval\f1]
+.SH DESCRIPTION
+.B pcp-free
+gives a summary display of the total amount of free and used
+physical memory and swap in the system, as well as the caches
+used by the kernel.
+.PP
+When invoked via the
+.BR pcp (1)
+command, the
+.BR \-h /\c
+.BR \-\-host ,
+.BR \-a /\c
+.BR \-\-archive ,
+.BR \-O /\c
+.BR \-\-origin ,
+.BR \-s /\c
+.BR \-\-samples ,
+.BR \-t /\c
+.BR \-\-interval ,
+.BR \-Z /\c
+.BR \-\-timezone
+and several other options become indirectly available.
+.SS Options
+The \fB\-b\fP/\fB\-\-bytes\fP switch displays the amount of memory in bytes; the
+\fB\-k\fP/\fB\-\-kilobytes\fP switch (set by default) displays it in kilobytes;
+the \fB\-m\fP/\fB\-\-megabytes\fP switch displays it in megabytes.
+.PP
+The \fB\-t\fP/\fB\-\-total\fP switch displays a line containing the totals.
+.PP
+The \fB-o\fP switch disables the display of a "buffer adjusted" line.
+If the \fB-o\fP option is not specified, \fBfree\fP subtracts buffer memory
+from the used memory and adds it to the free memory reported.
+.PP
+The \fB\-s\fP/\fB\-\-interval\fP switch activates continuous polling \fIdelay\fP
+seconds apart.
+You may specify any floating point number for \fIdelay\fP, or indeed any valid
+.BR pmParseInterval (3)
+specification, which includes microsecond resolution delay times.
+This can be used in conjunction with the \fB\-c\fP/\fB\-\-samples\fP option
+to terminate the display after \fIcount\fP iterations.
+.PP
+The \fB\-l\fP switch shows detailed low and high memory statistics.
+.PP
+The current version of
+.B pcp-free
+can be queried using the
+.BR \-V /\c
+.B \-\-version
+option.
+.SH NOTES
+.B pcp-free
+is inspired by the
+.BR free (1)
+command and aims to be command line and output compatible with it.
+.PP
+The shared memory column should be ignored; it is obsolete.
+.SH "SEE ALSO"
+.BR pcp (1),
+.BR free(1),
+.BR PCPIntro (1),
+.BR pmParseInterval (1)
+and
+.BR environ (5).
diff --git a/src/pcp/free/pcp-free.py b/src/pcp/free/pcp-free.py
new file mode 100755
index 0000000..9631a97
--- /dev/null
+++ b/src/pcp/free/pcp-free.py
@@ -0,0 +1,216 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=C0103,R0914,R0902
+""" Display amount of free and used memory in the system """
+
+import sys
+from pcp import pmapi
+from cpmapi import PM_TYPE_U64, PM_CONTEXT_ARCHIVE, PM_SPACE_KBYTE
+
+class Free(object):
+ """ Gives a short summary of kernel virtual memory information,
+ in a variety of formats, possibly sampling in a loop.
+
+ Knows about some of the default PCP arguments - can function
+ using remote hosts or historical data, using the timezone of
+ the metric source, at an offset within an archive, and so on.
+ """
+
+ def __init__(self):
+ """ Construct object - prepare for command line handling """
+ self.count = 0 # number of samples to report
+ self.pause = None # time interval to pause between samples
+ self.shift = 10 # bitshift conversion between B/KB/MB/GB
+ self.show_high = 0
+ self.show_total = 0
+ self.show_compat = 0
+ self.opts = self.options()
+ self.interval = pmapi.timeval()
+ self.context = None
+
+ def options(self):
+ """ Setup default command line argument option handling """
+ opts = pmapi.pmOptions()
+ opts.pmSetOptionCallback(self.option)
+ opts.pmSetOverrideCallback(self.override)
+ opts.pmSetShortOptions("bc:gklmots:V?")
+ opts.pmSetLongOptionHeader("Options")
+ opts.pmSetLongOption("bytes", 0, 'b', '', "show output in bytes")
+ opts.pmSetLongOption("kilobytes", 0, 'k', '', "show output in KB")
+ opts.pmSetLongOption("megabytes", 0, 'm', '', "show output in MB")
+ opts.pmSetLongOption("gigabytes", 0, 'g', '', "show output in GB")
+ opts.pmSetLongOption("", 0, 'o', '',
+ "use old format (no -/+buffers/cache line)")
+ opts.pmSetLongOption("", 0, 'l', '',
+ "show detailed low and high memory statistics")
+ opts.pmSetLongOption("total", 0, 't', '',
+ "display total for RAM + swap")
+ opts.pmSetLongOption("samples", 1, 'c', "COUNT", "number of samples")
+ opts.pmSetLongOption("interval", 1, 's', "DELTA", "sampling interval")
+ opts.pmSetLongOptionVersion()
+ opts.pmSetLongOptionHelp()
+ return opts
+
+ def override(self, opt):
+ """ Override a few standard PCP options to match free(1) """
+ # pylint: disable=R0201
+ if (opt == 'g' or opt == 's' or opt == 't'):
+ return 1
+ return 0
+
+ def option(self, opt, optarg, index):
+ """ Perform setup for an individual command line option """
+ # pylint: disable=W0613
+ if opt == 'b':
+ self.shift = 0
+ elif opt == 'k':
+ self.shift = 10
+ elif opt == 'm':
+ self.shift = 20
+ elif opt == 'g':
+ self.shift = 30
+ elif opt == 'o':
+ self.show_compat = 1
+ elif opt == 'l':
+ self.show_high = 1
+ elif opt == 't':
+ self.show_total = 1
+ elif opt == 's':
+ self.pause = optarg
+ self.opts.pmSetOptionInterval(optarg)
+ self.interval = self.opts.pmGetOptionInterval()
+ elif opt == 'c':
+ self.opts.pmSetOptionSamples(optarg)
+ self.count = self.opts.pmGetOptionSamples()
+
+ def scale(self, value):
+ """ Convert a given value in kilobytes into display units """
+ return long(value << 10) >> self.shift
+
+ def extract(self, descs, result):
+ """ Extract the set of metric values from a given pmResult """
+ values = []
+ for index in range(len(descs)):
+ if result.contents.get_numval(index) > 0:
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(index),
+ result.contents.get_vlist(index, 0),
+ descs[index].contents.type, PM_TYPE_U64)
+ atom = self.context.pmConvScale(PM_TYPE_U64, atom, descs, index,
+ pmapi.pmUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0))
+ values.append(long(atom.ull))
+ else:
+ values.append(long(0))
+ return values
+
+ def execute(self):
+ """ Using a PMAPI context (could be either host or archive),
+ fetch and report a fixed set of values related to memory.
+ """
+ metrics = ('mem.physmem',
+ 'mem.util.free', 'mem.util.shared',
+ 'mem.util.bufmem', 'mem.util.cached',
+ 'mem.util.highFree', 'mem.util.highTotal',
+ 'mem.util.lowFree', 'mem.util.lowTotal',
+ 'mem.util.swapFree', 'mem.util.swapTotal')
+
+ pmids = self.context.pmLookupName(metrics)
+ descs = self.context.pmLookupDescs(pmids)
+
+ if self.pause == None and self.count == 0:
+ self.count = 1
+ if self.pause != None and self.count == 0:
+ self.count = -1
+
+ while self.count != 0:
+ result = self.context.pmFetch(pmids)
+ values = self.extract(descs, result)
+ self.context.pmFreeResult(result)
+ self.report(values)
+ if self.pause != None:
+ print('') # empty line
+ sys.stdout.flush()
+ if self.count > 0:
+ self.count -= 1
+ if self.count == 0:
+ break
+ if self.context.type != PM_CONTEXT_ARCHIVE:
+ self.context.pmtimevalSleep(self.interval)
+ elif self.count == 1 and self.pause == None:
+ break
+ elif self.count > 0:
+ self.count -= 1
+
+ def report(self, values):
+ """ Given the set of metric values report them in free(1) form """
+ physmem = values[0]
+ free = values[1]
+ shared = values[2]
+ buffers = values[3]
+ cached = values[4]
+ highfree = values[5]
+ hightotal = values[6]
+ lowfree = values[7]
+ lowtotal = values[8]
+ swapfree = values[9]
+ swaptotal = values[10]
+
+ used = physmem - free
+ swapused = swaptotal - swapfree
+
+ # low == main memory, except with large-memory support
+ if lowtotal == 0:
+ lowtotal = physmem
+ lowfree = free
+
+ columns = ('total', 'used', 'free', 'shared', 'buffers', 'cached')
+ print("%18s %10s %10s %10s %10s %10s" % columns)
+ print("%-7s %10Lu %10Lu %10Lu %10Lu %10Lu %10Lu" % ('Mem:',
+ self.scale(physmem), self.scale(used), self.scale(free),
+ self.scale(shared), self.scale(buffers), self.scale(cached)))
+
+ if self.show_high:
+ print("%-7s %10Lu %10Lu %10Lu" % ('Low:', self.scale(lowtotal),
+ self.scale(lowtotal - lowfree), self.scale(lowtotal)))
+ print("%-7s %10Lu %10Lu %10Lu" % ('High:', self.scale(hightotal),
+ self.scale(hightotal - highfree), self.scale(highfree)))
+ if self.show_compat != 1:
+ cache = buffers + cached
+ print("%s: %10Lu %10Lu" % ('-/+ buffers/cache',
+ self.scale(used - cache), self.scale(free + cache)))
+
+ print("%-7s %10Lu %10Lu %10Lu" % ('Swap', self.scale(swaptotal),
+ self.scale(swapused), self.scale(swapfree)))
+
+ if self.show_total == 1:
+ print("%-7s %10Lu %10Lu %10Lu" % ('Total',
+ self.scale(physmem + swaptotal),
+ self.scale(used + swapused), self.scale(free + swapfree)))
+
+ def connect(self):
+ """ Establish a PMAPI context to archive, host or local, via args """
+ self.context = pmapi.pmContext.fromOptions(self.opts, sys.argv)
+
+if __name__ == '__main__':
+ try:
+ FREE = Free()
+ FREE.connect()
+ FREE.execute()
+ except pmapi.pmErr as error:
+ print("free:", error.message())
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ except KeyboardInterrupt:
+ pass
diff --git a/src/pcp/numastat/GNUmakefile b/src/pcp/numastat/GNUmakefile
new file mode 100644
index 0000000..ce1ced3
--- /dev/null
+++ b/src/pcp/numastat/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pcp-numastat
+MAN_SECTION = 1
+MAN_PAGES = $(TARGET).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+
+default: $(TARGET).py $(MAN_PAGES)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(TARGET).py $(PCP_BINADM_DIR)/$(TARGET)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pcp/numastat/pcp-numastat.1 b/src/pcp/numastat/pcp-numastat.1
new file mode 100644
index 0000000..b2de006
--- /dev/null
+++ b/src/pcp/numastat/pcp-numastat.1
@@ -0,0 +1,57 @@
+.TH PCP-NUMASTAT 1 "PCP" "Performance Co-Pilot"
+.SH NAME
+\f3pcp-numastat\f1 \- report on NUMA memory allocation
+.SH SYNOPSIS
+\f3pcp-numastat\f1
+[\f3\-V\f1]
+.SH DESCRIPTION
+.B pcp-numastat
+displays NUMA allocation statistics from the kernel memory
+allocator.
+Each process has NUMA policies that specify on which node
+pages are allocated.
+The performance counters in the kernel track on which
+nodes memory is allocated and these values are sampled and
+reported by
+.BR pcp-numastat .
+.PP
+Counters are maintained individually for each NUMA node.
+Details of the semantics of each reported metric can be
+retrieved using the following command:
+.P
+.ft CW
+.nf
+.in +0.5i
+# pminfo \(hydt mem.numa.alloc
+.in
+.fi
+.PP
+When invoked via the
+.BR pcp (1)
+command, the
+.BR \-h /\c
+.BR \-\-host ,
+.BR \-a /\c
+.BR \-\-archive ,
+.BR \-O /\c
+.BR \-\-origin ,
+.BR \-Z /\c
+.BR \-\-timezone
+and several other options become indirectly available.
+.PP
+The current version of
+.B pcp-numastat
+can be queried using the
+.BR \-V /\c
+.B \-\-version
+option.
+.SH NOTES
+.B pcp-numastat
+is inspired by the
+.BR numastat (1)
+command and produces exactly the same output.
+.SH "SEE ALSO"
+.BR pcp (1),
+.BR numastat (1)
+and
+.BR PCPIntro (1).
diff --git a/src/pcp/numastat/pcp-numastat.py b/src/pcp/numastat/pcp-numastat.py
new file mode 100755
index 0000000..bdb3519
--- /dev/null
+++ b/src/pcp/numastat/pcp-numastat.py
@@ -0,0 +1,157 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=C0103,R0914,R0902
+""" Display NUMA memory allocation statistucs """
+
+import os
+import sys
+from pcp import pmapi
+from cpmapi import PM_TYPE_U64
+
+class NUMAStat(object):
+ """ Gives a short summary of per-node NUMA memory information.
+
+ Knows about some of the default PCP arguments - can function
+ using remote hosts or historical data, using the timezone of
+ the metric source, at an offset within an archive, and so on.
+ """
+
+ def __init__(self):
+ """ Construct object - prepare for command line handling """
+ self.opts = self.options()
+ self.context = None
+ self.width = 0
+
+ def resize(self):
+ """ Find a suitable display width limit """
+ if self.width == 0:
+ if not sys.stdout.isatty():
+ self.width = 1000000000 # mimic numastat(1) here
+ else:
+ (rows, width) = os.popen('stty size', 'r').read().split()
+ self.width = int(width)
+ self.width = int(os.getenv('NUMASTAT_WIDTH', self.width))
+ if self.width < 32:
+ self.width = 32
+
+ def option(self, opt, optarg, index):
+ """ Perform setup for an individual command line option """
+ if (opt == 'w'):
+ self.width = int(optarg)
+
+ def options(self):
+ """ Setup default command line argument option handling """
+ opts = pmapi.pmOptions()
+ opts.pmSetOptionCallback(self.option)
+ opts.pmSetShortOptions("w:V?")
+ opts.pmSetLongOptionHeader("Options")
+ opts.pmSetLongOption("width", 1, 'w', "N", "limit the display width")
+ opts.pmSetLongOptionVersion()
+ opts.pmSetLongOptionHelp()
+ return opts
+
+ def extract(self, descs, insts, result):
+ """ Extract the set of metric values from a given pmResult """
+ values = [[]]
+ for metrics in range(len(descs)):
+ values.append([])
+ for nodes in range(len(insts)):
+ if result.contents.get_numval(metrics) > 0:
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(metrics),
+ result.contents.get_vlist(metrics, nodes),
+ descs[metrics].contents.type, PM_TYPE_U64)
+ values[metrics].append(long(atom.ull))
+ else:
+ values[metrics].append(long(0))
+ return values
+
+ def execute(self):
+ """ Using a PMAPI context (could be either host or archive),
+ fetch and report per-node values related to NUMA memory.
+ """
+ metrics = ('mem.numa.alloc.hit', 'mem.numa.alloc.miss',
+ 'mem.numa.alloc.foreign', 'mem.numa.alloc.interleave_hit',
+ 'mem.numa.alloc.local_node', 'mem.numa.alloc.other_node')
+
+ pmids = self.context.pmLookupName(metrics)
+ descs = self.context.pmLookupDescs(pmids)
+ (insts, nodes) = self.context.pmGetInDom(descs[0])
+ result = self.context.pmFetch(pmids)
+ values = self.extract(descs, insts, result)
+ self.context.pmFreeResult(result)
+ self.report(metrics, nodes, values)
+
+ def report(self, metrics, nodes, values):
+ """ Given per-node metric names and values, dump 'em like numastat(1)
+ Nodes is a list of strings, values is a list of lists of values.
+ """
+ columns = len(nodes) * 16
+ if (columns == 0):
+ print("No NUMA nodes found, exiting")
+ sys.exit(1)
+ self.resize()
+ maxnodes = (self.width - 16) / 16
+ if maxnodes > len(nodes): # just an initial header suffices
+ header = '%-16s' % ''
+ for node in nodes:
+ header += '%16s' % node
+ print(header)
+ for index in range(len(metrics)):
+ title = self.prefix(metrics[index])
+ self.metric(title, nodes, values[index], maxnodes)
+
+ def metric(self, prefix, nodes, values, maxnodes):
+ """ Given one metric and its per-node values, produce one or more
+ lines of output with the values, each line node-name prefixed
+ and with a new node header for each.
+ """
+ done = 0
+ while done < len(nodes):
+ header = '%-16s' % ''
+ window = '%-16s' % prefix
+ for index in range(maxnodes):
+ current = done + index
+ if current < len(nodes):
+ header += '%16s' % (nodes[current])
+ window += '%16d' % (values[current])
+ if done > maxnodes or maxnodes <= len(nodes):
+ print('%s\n%s' % (header, window))
+ else:
+ print('%s' % window)
+ done += maxnodes
+
+ def prefix(self, metric):
+ """ Transform the PCP metric names into the reported sub-headings """
+ title = metric[15:]
+ if '_' not in title:
+ title = 'numa_' + title
+ return title
+
+ def connect(self):
+ """ Establish a PMAPI context to archive, host or local, via args """
+ self.context = pmapi.pmContext.fromOptions(self.opts, sys.argv)
+
+if __name__ == '__main__':
+ try:
+ NUMASTAT = NUMAStat()
+ NUMASTAT.connect()
+ NUMASTAT.execute()
+ except pmapi.pmErr as error:
+ print("numastat:", error.message())
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ except KeyboardInterrupt:
+ pass
diff --git a/src/pcp/pcp.sh b/src/pcp/pcp.sh
new file mode 100755
index 0000000..33ddb47
--- /dev/null
+++ b/src/pcp/pcp.sh
@@ -0,0 +1,521 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Displays the Performance Co-Pilot configuration for a host running the
+# pmcd(1) daemon or from an archive created by pmlogger(1).
+#
+
+. $PCP_DIR/etc/pcp.env
+
+sts=2
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$sts" 0 1 2 3 15
+
+errors=0
+progname=`basename $0`
+pcp_host=`hostname` # may match pmcd.hostname
+for var in unknown version build numagents numclients ncpu ndisk nnode nrouter nxbow ncell mem cputype uname timezone hostname services status
+do
+ eval $var="unknown?"
+done
+for var in Aflag aflag Dflag gflag hflag Lflag nflag Oflag Pflag pflag Sflag sflag Tflag tflag Zflag zflag
+do
+ eval $var=false
+done
+
+# metrics
+metrics="pmcd.numagents pmcd.numclients pmcd.version pmcd.build pmcd.timezone pmcd.hostname pmcd.services pmcd.agent.status pmcd.pmlogger.archive pmcd.pmlogger.pmcd_host hinv.ncpu hinv.ndisk hinv.nnode hinv.nrouter hinv.nxbow hinv.ncell hinv.physmem hinv.cputype pmda.uname pmcd.pmie.pmcd_host pmcd.pmie.configfile pmcd.pmie.numrules pmcd.pmie.logfile"
+pmiemetrics="pmcd.pmie.actions pmcd.pmie.eval.true pmcd.pmie.eval.false pmcd.pmie.eval.unknown pmcd.pmie.eval.expected"
+
+# usage spec for pmgetopt, note posix flag (commands mean no reordering)
+cat > $tmp/usage << EOF
+# getopts: A:a:D:gh:Ln:O:p:PS:s:T:t:Z:z?
+# Usage: [options] [[...] command [...]]
+
+Summary Options:
+ --archive
+ --host
+ --namespace
+ -P,--pmie display pmie evaluation statistics
+ --help
+
+Command Options:
+ --align
+ --archive
+ --debug
+ --guimode
+ --host
+ --namespace
+ --origin
+ --guiport
+ --start
+ --samples
+ --finish
+ --interval
+ --timezone
+ --hostzone
+# end
+EOF
+
+_usage()
+{
+ [ ! -z "$@" ] && echo $@ 1>&2
+
+ ls $PCP_BINADM_DIR/pcp-* $HOME/.pcp/bin/pcp-* 2>/dev/null | \
+ while read command
+ do
+ [ -x "$command" ] || continue
+ basename "$command" | sed -e 's/^pcp-//g' >> $tmp/cmds
+ done
+
+ echo >> $tmp/usage
+ ( $PCP_ECHO_PROG $PCP_ECHO_N "Available Commands: ""$PCP_ECHO_C" && \
+ sort -u < $tmp/cmds ) | _fmt >> $tmp/usage
+ pmgetopt --progname=$progname --usage --config=$tmp/usage
+ exit 1
+}
+
+_plural()
+{
+ if [ "$1" = $unknown -o "$1" = 0 ]
+ then
+ echo ""
+ elif [ "$1" = 1 ]
+ then
+ echo " $1 $2,"
+ else
+ echo " $1 ${2}s,"
+ fi
+}
+
+_fmt()
+{
+ if [ "$PCP_PLATFORM" = netbsd ]
+ then
+ fmt -g 64
+ else
+ fmt -w 64
+ fi \
+ | tr -d '\r' | tr -s '\n' | $PCP_AWK_PROG '
+NR > 1 { printf " %s\n", $0; next }
+ { print }'
+}
+
+opts=""
+ARGS=`pmgetopt --progname=$progname --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1" in
+ -A)
+ Aflag=true
+ pcp_align_time="$2"
+ shift
+ ;;
+ -a)
+ aflag=true
+ pcp_archive="$2"
+ opts="$opts -a $2"
+ shift
+ ;;
+ -D)
+ Dflag=true
+ pcp_debug="$2"
+ opts="$opts -D $2"
+ shift
+ ;;
+ -g)
+ gflag=true
+ ;;
+ -h)
+ hflag=true
+ pcp_host="$2"
+ opts="$opts -h $2"
+ shift
+ ;;
+ -L)
+ Lflag=true
+ ;;
+ -n)
+ nflag=true
+ pcp_namespace="$2"
+ opts="$opts -n $2"
+ shift
+ ;;
+ -O)
+ Oflag=true
+ pcp_origin_time="$2"
+ shift
+ ;;
+ -P)
+ Pflag=true
+ metrics="$metrics $pmiemetrics"
+ ;;
+ -p)
+ pflag=true
+ pcp_guiport="$2"
+ shift
+ ;;
+ -S)
+ Sflag=true
+ pcp_start_time="$2"
+ shift
+ ;;
+ -s)
+ sflag=true
+ pcp_samples="$2"
+ shift
+ ;;
+ -T)
+ Tflag=true
+ pcp_finish_time="$2"
+ shift
+ ;;
+ -t)
+ tflag=true
+ pcp_interval="$2"
+ shift
+ ;;
+ -Z)
+ Zflag=true
+ pcp_timezone="$2"
+ shift
+ ;;
+ -z)
+ zflag=true
+ ;;
+ -\?)
+ _usage ""
+ ;;
+ --) # end of options, start of arguments
+ shift
+ break
+ ;;
+ esac
+ shift # finished with this option now, next!
+done
+
+if [ $# -gt 0 ]
+then
+ # pcp-command mode - seek out a matching command and execute it
+ # with the remaining arguments - pmGetOptions(3) will discover
+ # all of the standard arguments we've set above, automagically.
+ #
+ command=$1
+ shift
+
+ if [ -x "$HOME/.pcp/bin/pcp-$command" ]
+ then
+ command="$HOME/.pcp/bin/pcp-$command"
+ elif [ -x "$PCP_BINADM_DIR/pcp-$command" ]
+ then
+ command="$PCP_BINADM_DIR/pcp-$command"
+ else
+ _usage "Cannot find a pcp-$command command to execute"
+ fi
+ $Aflag && export PCP_ALIGN_TIME="$pcp_align_time"
+ $aflag && export PCP_ARCHIVE="$pcp_archive"
+ $Dflag && export PCP_DEBUG="$pcp_debug"
+ $gflag && export PCP_GUIMODE=true
+ $hflag && export PCP_HOST="$pcp_host"
+ $Lflag && export PCP_LOCALMODE=true
+ $nflag && export PCP_NAMESPACE="$pcp_namespace"
+ $Oflag && export PCP_ORIGIN_TIME="$pcp_origin_time"
+ $pflag && export PCP_GUIPORT="$pcp_guiport"
+ $Sflag && export PCP_START_TIME="$pcp_start_time"
+ $sflag && export PCP_SAMPLES="$pcp_samples"
+ $Tflag && export PCP_FINISH_TIME="$pcp_finish_time"
+ $tflag && export PCP_INTERVAL="$pcp_interval"
+ $Zflag && export PCP_TIMEZONE="$pcp_timezone"
+ $zflag && export PCP_HOSTZONE=true
+ exec $command $@
+fi
+
+$hflag && $aflag && _usage "$progname: -a and -h mutually exclusive"
+
+if $aflag
+then
+ eval `pmdumplog -Lz "$pcp_archive" 2>/dev/null | $PCP_AWK_PROG '
+/^Performance metrics from host/ { printf "pcp_host=%s\n", $5 }
+/^Archive timezone: / { printf "timezone=%s\n", $3 }
+/^ commencing/ { tmp = substr($5, 7, 6)
+ sub(tmp, tmp+0.001, $5)
+ sub("commencing", "@")
+ printf "offset=\"%s\"\n", $0
+ }'`
+ [ "X$pcp_host" = X ] && pcp_host="unknown host"
+ [ "X$offset" != X ] && opts="$opts -O '$offset' -z"
+fi
+
+
+if eval pminfo $opts -f $metrics > $tmp/metrics 2>$tmp/err
+then
+ :
+else
+ if grep "^pminfo:" $tmp/err > /dev/null 2>&1
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "$progname: ""$PCP_ECHO_C"
+ sed < $tmp/err -e 's/^pminfo: //g'
+ sts=1
+ exit
+ fi
+fi
+
+[ -s $tmp/err ] && sed -e '/Unknown metric name/d' <$tmp/err >&2
+
+eval `$PCP_AWK_PROG < $tmp/metrics -v out=$tmp '
+BEGIN { mode = 0; count = 0; errors = 0; quote="" }
+
+function quoted()
+{
+ if ($1 == "value") {
+ printf "%s=", quote
+ for (i = 2; i < NF; i++)
+ printf "%s ", $i
+ printf "%s\n", $NF
+ }
+ else
+ errors++
+}
+
+function inst()
+{
+ if (count == 0)
+ file=sprintf("%s/%s", out, quote)
+ if (NF == 0) {
+ mode = 0
+ printf "%s=%d\n", quote, count
+ }
+ else if ($1 == "inst") {
+ count++
+ id=substr($2, 2, length($2) - 1)
+ value=$6
+ if (mode == 2) {
+ agent=substr($4, 2, length($4) - 3)
+ printf "%s %s %s\n", id, agent, value > file
+ }
+ else
+ printf "%s %s\n", id, value > file
+ }
+ else {
+ printf "%s=%d\n", quote, 0
+ mode = 0
+ errors++
+ }
+}
+
+mode == 1 { quoted(); mode = 0; next }
+mode == 2 { inst(); next }
+mode == 3 { inst(); next }
+/pmcd.version/ { mode = 1; quote="version"; next }
+/pmcd.build/ { mode = 1; quote="build"; next }
+/pmcd.numagents/ { mode = 1; quote="numagents"; next }
+/pmcd.numclients/ { mode = 1; quote="numclients"; next }
+/pmcd.timezone/ { mode = 1; quote="timezone"; next }
+/pmcd.hostname/ { mode = 1; quote="hostname"; next }
+/pmcd.services/ { mode = 1; quote="services"; next }
+/pmcd.agent.status/ { mode = 2; count = 0; quote="status"; next }
+/pmcd.pmlogger.archive/ { mode = 3; count = 0; quote="log_archive"; next }
+/pmcd.pmlogger.pmcd_host/ { mode = 3; count = 0; quote="log_host"; next }
+/pmcd.pmie.pmcd_host/ { mode = 3; count = 0; quote="ie_host"; next }
+/pmcd.pmie.logfile/ { mode = 3; count = 0; quote="ie_log"; next }
+/pmcd.pmie.configfile/ { mode = 3; count = 0; quote="ie_config"; next }
+/pmcd.pmie.numrules/ { mode = 3; count = 0; quote="ie_numrules"; next }
+/pmcd.pmie.actions/ { mode = 3; count = 0; quote="ie_actions"; next }
+/pmcd.pmie.eval.true/ { mode = 3; count = 0; quote="ie_true"; next }
+/pmcd.pmie.eval.false/ { mode = 3; count = 0; quote="ie_false"; next }
+/pmcd.pmie.eval.unknown/ { mode = 3; count = 0; quote="ie_unknown"; next }
+/pmcd.pmie.eval.expected/ { mode = 3; count = 0; quote="ie_expected"; next }
+/hinv.ncpu/ { mode = 1; quote="ncpu"; next }
+/hinv.ndisk/ { mode = 1; quote="ndisk"; next }
+/hinv.nnode/ { mode = 1; quote="nnode"; next }
+/hinv.nrouter/ { mode = 1; quote="nrouter"; next }
+/hinv.nxbow/ { mode = 1; quote="nxbow"; next }
+/hinv.ncell/ { mode = 1; quote="ncell"; next }
+/hinv.physmem/ { mode = 1; quote="mem"; next }
+/hinv.cputype/ { mode = 3; count = 0; quote="cputype"; next }
+/pmda.uname/ { mode = 1; quote="uname"; next }
+END { printf "errors=%d\n", errors }'`
+
+numagents=`_plural $numagents agent`
+ndisk=`_plural $ndisk disk`
+nnode=`_plural $nnode node`
+nrouter=`_plural $nrouter router`
+nxbow=`_plural $nxbow xbow`
+ncell=`_plural $ncell cell`
+
+if [ -f $tmp/status ]
+then
+ agents=`$PCP_AWK_PROG < $tmp/status '
+$3 == 0 { printf "%s ",$2 }
+$3 != 0 { printf "%s[%d] ",$2,$3 }' | _fmt`
+fi
+
+if [ "$numclients" = $unknown ]
+then
+ numclients=""
+else
+ numclients=`expr $numclients - 1`
+ numclients=`_plural $numclients client | tr -d ','`
+ [ "$numclients" = "" ] && numagents=`echo "$numagents" | tr -d ','`
+fi
+
+if [ "$version" = $unknown ]
+then
+ version="Version unknown"
+else
+ version="Version $version"
+ [ "$build" != $unknown ] && version="$version-$build"
+fi
+
+if [ "$mem" = $unknown -o "$mem" = 0 ]
+then
+ mem=""
+else
+ mem=" ${mem}MB RAM"
+fi
+
+if [ "$uname" = $unknown ]
+then
+ uname=""
+else
+ uname="$uname"
+fi
+
+[ "$services" = $unknown ] && services=""
+[ "$timezone" = $unknown ] && timezone="Unknown"
+[ "$hostname" = $unknown ] || pcp_host="$hostname"
+
+if [ "$cputype" = $unknown ]
+then
+ cputype=""
+elif [ -f $tmp/cputype ]
+then
+ cputype=`head -1 $tmp/cputype | sed -e 's/^.*"R/R/' -e 's/"$//g'`
+else
+ cputype=""
+fi
+
+ncpu=`_plural $ncpu "$cputype cpu"`
+
+hardware="${ncpu}${ndisk}${nnode}${nrouter}${nxbow}${ncell}$mem"
+
+if [ -f $tmp/log_archive -a -f $tmp/log_host ]
+then
+ sort $tmp/log_archive -o $tmp/log_archive
+ sort $tmp/log_host -o $tmp/log_host
+
+ # need \n\n here to force line breaks when piped into fmt later
+ #
+ numloggers=`join $tmp/log_host $tmp/log_archive | sort \
+ | sed -e 's/"//g' | tee $tmp/log | $PCP_AWK_PROG '
+BEGIN { count = 0 }
+$1 == "0" { next }
+$1 == "1" { next }
+ { count++ }
+END { print count }'`
+
+ $PCP_AWK_PROG < $tmp/log > $tmp/loggers '
+BEGIN { primary=0 }
+$1 == "0" { primary=$3; next }
+$3 == primary { printf "primary logger: %s\n\n",$3; exit }'
+
+ $PCP_AWK_PROG < $tmp/log >> $tmp/loggers '
+BEGIN { primary=0 }
+$1 == "0" { primary=$3; next }
+$1 == "1" { next }
+$3 == primary { next }
+ { printf "%s: %s\n\n",$2,$3 }'
+else
+ numloggers=0
+fi
+
+if [ -f $tmp/ie_host -a -f $tmp/ie_config -a -f $tmp/ie_log -a -f $tmp/ie_numrules ]
+then
+ sort $tmp/ie_log -o $tmp/ie_log
+ sort $tmp/ie_host -o $tmp/ie_host
+ sort $tmp/ie_config -o $tmp/ie_config
+ sort $tmp/ie_numrules -o $tmp/ie_numrules
+ if [ $Pflag = "true" ]; then
+ numpmies=`join $tmp/ie_host $tmp/ie_config | join - $tmp/ie_numrules \
+ | sort -n | sed -e 's/"//g' | tee $tmp/pmie | wc -l | tr -d ' '`
+ else
+ numpmies=`join $tmp/ie_host $tmp/ie_log \
+ | sort -n | sed -e 's/"//g' | tee $tmp/pmie | wc -l | tr -d ' '`
+ fi
+
+ if [ $Pflag = "true" -a -f $tmp/ie_actions -a -f $tmp/ie_true -a \
+ -f $tmp/ie_false -a -f $tmp/ie_unknown -a -f $tmp/ie_expected ]
+ then
+ sort $tmp/ie_actions -o $tmp/ie_actions
+ sort $tmp/ie_true -o $tmp/ie_true
+ sort $tmp/ie_false -o $tmp/ie_false
+ sort $tmp/ie_unknown -o $tmp/ie_unknown
+ sort $tmp/ie_expected -o $tmp/ie_expected
+ join $tmp/pmie $tmp/ie_true | join - $tmp/ie_false \
+ | join - $tmp/ie_unknown | join - $tmp/ie_actions \
+ | join - $tmp/ie_expected > $tmp/tmp
+ mv $tmp/tmp $tmp/pmie
+ fi
+
+ $PCP_AWK_PROG -v Pflag=$Pflag < $tmp/pmie '{
+ if (Pflag == "true") {
+ printf "%s: %s (%u rules)\n\n",$2,$3,$4
+ printf "evaluations true=%u false=%u unknown=%u (actions=%u)\n\n",$5,$6,$7,$8
+ printf "expected evaluation rate=%.2f rules/sec\n\n",$9
+ } else {
+ printf "%s: %s\n\n",$2,$3
+ }
+ }' > $tmp/pmies
+else
+ numpmies=0
+fi
+
+# finally, display everything we've found...
+#
+echo "Performance Co-Pilot configuration on ${pcp_host}:"
+echo
+[ -n "$pcp_archive" ] && echo " archive: $pcp_archive"
+echo " platform: ${uname}"
+echo " hardware: "`echo $hardware | _fmt`
+echo " timezone: $timezone"
+[ -n "$services" ] && echo " services: $services"
+
+echo " pmcd: ${version},${numagents}$numclients"
+
+[ -n "$agents" ] && echo " pmda: $agents"
+
+if [ "$numloggers" != 0 ]
+then
+ $PCP_ECHO_PROG $PCP_ECHO_N " pmlogger: ""$PCP_ECHO_C"
+ LC_COLLATE=POSIX sort < $tmp/loggers \
+ | sed -e '/^$/d' | sed -e '1!s/^/ /'
+fi
+
+if [ "$numpmies" != 0 ]
+then
+ $PCP_ECHO_PROG $PCP_ECHO_N " pmie: ""$PCP_ECHO_C"
+ if [ $Pflag = "true" ]; then
+ _fmt < $tmp/pmies
+ else
+ LC_COLLATE=POSIX sort < $tmp/pmies \
+ | sed -e '/^$/d' | sed -e '1!s/^/ /'
+ fi
+fi
+
+sts=0
+exit
diff --git a/src/pcp/uptime/GNUmakefile b/src/pcp/uptime/GNUmakefile
new file mode 100644
index 0000000..d33092f
--- /dev/null
+++ b/src/pcp/uptime/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pcp-uptime
+MAN_SECTION = 1
+MAN_PAGES = $(TARGET).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+
+default: $(TARGET).py $(MAN_PAGES)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(TARGET).py $(PCP_BINADM_DIR)/$(TARGET)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pcp/uptime/pcp-uptime.1 b/src/pcp/uptime/pcp-uptime.1
new file mode 100644
index 0000000..0797767
--- /dev/null
+++ b/src/pcp/uptime/pcp-uptime.1
@@ -0,0 +1,44 @@
+.TH PCP-UPTIME 1 "PCP" "Performance Co-Pilot"
+.SH NAME
+\f3pcp-uptime\f1 \- tell how long the system has been running
+.SH SYNOPSIS
+\f3pcp-uptime\f1
+[\f3\-V\f1/[\f3\--version\f1]
+.SH DESCRIPTION
+.B pcp-uptime
+gives a one line display of the following information.
+The current time,
+how long the system has been running,
+how many users are currently logged on,
+and the system load averages for the past 1, 5, and 15 minutes.
+.PP
+When invoked via the
+.BR pcp (1)
+command, the
+.BR \-h /\c
+.BR \-\-host ,
+.BR \-a /\c
+.BR \-\-archive ,
+.BR \-O /\c
+.BR \-\-origin ,
+.BR \-Z /\c
+.BR \-\-timezone
+and several other options become indirectly available.
+.PP
+The current version of
+.B pcp-uptime
+can be queried using the
+.BR \-V /\c
+.B \--version
+option.
+.SH NOTES
+.B pcp-uptime
+is inspired by the
+.BR uptime (1)
+command and aims to be command line and output compatible with it.
+.SH "SEE ALSO"
+.BR pcp (1),
+.BR uptime (1),
+.BR PCPIntro (1)
+and
+.BR environ (5).
diff --git a/src/pcp/uptime/pcp-uptime.py b/src/pcp/uptime/pcp-uptime.py
new file mode 100755
index 0000000..fb35be7
--- /dev/null
+++ b/src/pcp/uptime/pcp-uptime.py
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=C0103
+""" Tell how long the system has been running """
+
+import sys
+from pcp import pmapi
+from cpmapi import PM_TYPE_U32, PM_TYPE_FLOAT
+
+def print_timestamp(stamp):
+ """ Report the sample time (struct tm) in HH:MM:SS form """
+ return " %02d:%02d:%02d" % (stamp.tm_hour, stamp.tm_min, stamp.tm_sec)
+
+def print_uptime(seconds):
+ """ Report on system up-time in days, hours and minutes """
+ days = seconds / (60 * 60 * 24)
+ minutes = seconds / 60
+ hours = minutes / 60
+ hours = hours % 24
+ minutes = minutes % 60
+ result = " up"
+ if days > 1:
+ result += " %d days," % days
+ elif days != 0:
+ result += " 1 day,"
+ if hours != 0:
+ result += ' %2d:%02d,' % (hours, minutes)
+ else:
+ result += ' %d min,' % minutes
+ return result
+
+def print_users(nusers):
+ """ Report the number of logged in users at sample time """
+ if nusers == 1:
+ return ' 1 user, '
+ else:
+ return (' %2d users, ' % nusers)
+
+def print_load(one, five, fifteen):
+ """ Report 1, 5, 15 minute load averages at sample time """
+ return ' load average: %.2f, %.2f, %.2f' % (one, five, fifteen)
+
+
+class Uptime(object):
+ """ Gives a one line display of the following information:
+ The current time;
+ How long the system has been running;
+ How many users are currently logged on; and
+ The system load averages for the past 1, 5, and 15 minutes.
+
+ Knows about some of the default PCP arguments - can function
+ using remote hosts or historical data, using the timezone of
+ the metric source, at an offset within an archive, and so on.
+ """
+
+ def __init__(self):
+ """ Construct object - prepare for command line handling """
+ self.context = None
+ self.opts = pmapi.pmOptions()
+ self.opts.pmSetShortOptions("V?")
+ self.opts.pmSetLongOptionHeader("Options")
+ self.opts.pmSetLongOptionVersion()
+ self.opts.pmSetLongOptionHelp()
+
+ def execute(self):
+ """ Using a PMAPI context (could be either host or archive),
+ fetch and report a fixed set of values related to uptime.
+ """
+ metrics = ('kernel.all.uptime', 'kernel.all.nusers', 'kernel.all.load')
+ pmids = self.context.pmLookupName(metrics)
+ descs = self.context.pmLookupDescs(pmids)
+ result = self.context.pmFetch(pmids)
+ uptime = ''
+
+ sample_time = result.contents.timestamp.tv_sec
+ time_struct = self.context.pmLocaltime(sample_time)
+ uptime += print_timestamp(time_struct)
+
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(0),
+ result.contents.get_vlist(0, 0),
+ descs[0].contents.type, PM_TYPE_U32)
+ uptime += print_uptime(atom.ul)
+
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(1),
+ result.contents.get_vlist(1, 0),
+ descs[1].contents.type, PM_TYPE_U32)
+ uptime += print_users(atom.ul)
+
+ averages = [1, 5, 15]
+ for inst in range(3):
+ averages[inst] = self.context.pmExtractValue(
+ result.contents.get_valfmt(2),
+ result.contents.get_vlist(2, inst),
+ descs[2].contents.type, PM_TYPE_FLOAT)
+ uptime += print_load(averages[0].f, averages[1].f, averages[2].f)
+ print(uptime)
+ self.context.pmFreeResult(result)
+
+ def connect(self):
+ """ Establish a PMAPI context to archive, host or local, via args """
+ self.context = pmapi.pmContext.fromOptions(self.opts, sys.argv)
+
+
+if __name__ == '__main__':
+ try:
+ UPTIME = Uptime()
+ UPTIME.connect()
+ UPTIME.execute()
+ except pmapi.pmErr as error:
+ print("uptime:", error.message())
+ except pmapi.pmUsageErr as usage:
+ usage.message()
+ except KeyboardInterrupt:
+ pass
diff --git a/src/perl/GNUmakefile b/src/perl/GNUmakefile
new file mode 100644
index 0000000..f97ee88
--- /dev/null
+++ b/src/perl/GNUmakefile
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = PMDA LogSummary MMV LogImport
+
+default: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ifeq "$(shell [ '$(PACKAGE_DISTRIBUTION)' = cocoa \
+ -o '$(PACKAGE_DISTRIBUTION)' = macosx \
+ -o '$(PACKAGE_DISTRIBUTION)' = gentoo \
+ -o '$(PACKAGE_DISTRIBUTION)' = solaris \
+ ] && echo 1)" "1"
+ # Gather installed Perl files before packaging
+ if [ -n "$(DIST_MANIFEST)" ]; then \
+ if [ "`echo $(TOPDIR)/perl-pcp-*.list`" != "$(TOPDIR)/perl-pcp-*.list" ]; then \
+ cat $(TOPDIR)/perl-pcp-*.list | while read f; do \
+ bn=`basename $$f .gz`; \
+ dn=`dirname $$f`; \
+ $(INSTALL) -d $$dn || exit 1; \
+ src=`find */blib -name $$bn`; \
+ if [ -x $$src ] ; then mode=0755; else mode=0644; fi; \
+ $(INSTALL) -m $$mode $$src $$dn/$$bn || exit 1; \
+ done; \
+ fi; \
+ fi
+endif
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/perl/LogImport/Changes b/src/perl/LogImport/Changes
new file mode 100644
index 0000000..db6f621
--- /dev/null
+++ b/src/perl/LogImport/Changes
@@ -0,0 +1,13 @@
+Revision history for Perl extension PCP::LogImport
+
+1.02 Thu May 23 15:30:53 EST 2013
+ - add pmiBatch{PutValue,Write,End} extensions
+ - additional API error codes (badname,badtime)
+
+1.01 Sun Sep 23 12:34:53 EST 2012
+ - add in definitions of the API error codes
+ - add in helper routines (pmiID + pmiInDom)
+
+1.00 Tue Jul 13 16:41:17 EST 2010
+ - initial version
+
diff --git a/src/perl/LogImport/GNUmakefile b/src/perl/LogImport/GNUmakefile
new file mode 100644
index 0000000..d7b4dec
--- /dev/null
+++ b/src/perl/LogImport/GNUmakefile
@@ -0,0 +1,71 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+PERLMOD = LogImport.pm
+INTERFACE = LogImport.xs typemap
+TESTCODE =
+PERLDOCS = Changes MANIFEST
+LSRCFILES = Makefile.PL $(PERLDOCS) $(PERLMOD) $(INTERFACE) $(TESTCODE)
+
+LPKGDIRT = PCP-LogImport-* tmp MYMETA.yml MYMETA.json
+LDIRT = Makefile COPYING LogImport.bs LogImport.c LogImport.o cvalue pm_to_blib blib \
+ Makefile.old $(LPKGDIRT)
+
+default: dist
+
+MAKEMAKER_OPTIONS = INSTALLDIRS=$(PERL_INSTALLDIRS) INSTALLVENDORMAN3DIR=$(PCP_MAN_DIR)/man3
+INSTALLER_OPTIONS = DESTDIR=$$DIST_ROOT
+
+ifeq ($(TARGET_OS),mingw)
+PERLMAKE = dmake.exe
+else
+PERLMAKE = $(MAKE)
+endif
+
+LogImport.o: Makefile LogImport.xs typemap
+ $(PERLMAKE) -f Makefile
+
+Makefile: COPYING Makefile.PL
+ $(call PERL_MAKE_MAKEFILE)
+
+COPYING:
+ $(LN_S) $(TOPDIR)/COPYING COPYING
+
+test dist: LogImport.o
+ rm -rf $(LPKGDIRT)
+ $(PERLMAKE) -f Makefile $@
+
+include $(BUILDRULES)
+
+install: default
+ifneq "$(PACKAGE_DISTRIBUTION)" "debian"
+ $(call PERL_GET_FILELIST,$(TOPDIR)/perl-pcp-logimport.list,LogImport)
+endif
+
+install_perl:
+ $(PERLMAKE) -f Makefile pure_install $(INSTALLER_OPTIONS)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/perl/LogImport/LogImport.pm b/src/perl/LogImport/LogImport.pm
new file mode 100644
index 0000000..92eaba3
--- /dev/null
+++ b/src/perl/LogImport/LogImport.pm
@@ -0,0 +1,190 @@
+package PCP::LogImport;
+
+use strict;
+use warnings;
+
+require Exporter;
+require DynaLoader;
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+@ISA = qw( Exporter DynaLoader );
+@EXPORT = qw(
+ pmiStart pmiUseContext pmiEnd pmiSetHostname pmiSetTimezone
+ pmiAddMetric pmiAddInstance pmiPutValue pmiGetHandle pmiPutValueHandle
+ pmiWrite
+ pmiDump pmiErrStr pmiUnits pmiID pmiInDom
+ pmid_build pmInDom_build
+ pmiBatchPutValue pmiBatchPutValueHandle pmiBatchWrite pmiBatchEnd
+ PM_ID_NULL PM_INDOM_NULL PM_IN_NULL
+ PM_SPACE_BYTE PM_SPACE_KBYTE PM_SPACE_MBYTE PM_SPACE_GBYTE PM_SPACE_TBYTE
+ PM_TIME_NSEC PM_TIME_USEC PM_TIME_MSEC PM_TIME_SEC PM_TIME_MIN PM_TIME_HOUR
+ PM_COUNT_ONE
+ PM_TYPE_NOSUPPORT PM_TYPE_32 PM_TYPE_U32 PM_TYPE_64 PM_TYPE_U64
+ PM_TYPE_FLOAT PM_TYPE_DOUBLE PM_TYPE_STRING
+ PM_SEM_COUNTER PM_SEM_INSTANT PM_SEM_DISCRETE
+ PMI_DOMAIN
+);
+%EXPORT_TAGS = qw();
+@EXPORT_OK = qw();
+
+# set the version for version checking
+$VERSION = '1.02';
+
+# metric identification
+sub PM_ID_NULL { 0xffffffff; }
+sub PM_INDOM_NULL { 0xffffffff; }
+sub PM_IN_NULL { 0xffffffff; }
+
+# units - space scale
+sub PM_SPACE_BYTE { 0; } # bytes
+sub PM_SPACE_KBYTE { 1; } # kilobytes
+sub PM_SPACE_MBYTE { 2; } # megabytes
+sub PM_SPACE_GBYTE { 3; } # gigabytes
+sub PM_SPACE_TBYTE { 4; } # terabytes
+
+# units - time scale
+sub PM_TIME_NSEC { 0; } # nanoseconds
+sub PM_TIME_USEC { 1; } # microseconds
+sub PM_TIME_MSEC { 2; } # milliseconds
+sub PM_TIME_SEC { 3; } # seconds
+sub PM_TIME_MIN { 4; } # minutes
+sub PM_TIME_HOUR { 5; } # hours
+
+# units - count scale (for metrics such as count events, syscalls,
+# interrupts, etc - these are simply powers of ten and not enumerated here
+# (e.g. 6 for 10^6, or -3 for 10^-3).
+sub PM_COUNT_ONE { 0; } # 1
+
+# data type of metric values
+sub PM_TYPE_NOSUPPORT { 0xffffffff; } # not implemented in this version
+sub PM_TYPE_32 { 0; } # 32-bit signed integer
+sub PM_TYPE_U32 { 1; } # 32-bit unsigned integer
+sub PM_TYPE_64 { 2; } # 64-bit signed integer
+sub PM_TYPE_U64 { 3; } # 64-bit unsigned integer
+sub PM_TYPE_FLOAT { 4; } # 32-bit floating point
+sub PM_TYPE_DOUBLE { 5; } # 64-bit floating point
+sub PM_TYPE_STRING { 6; } # array of characters
+
+# semantics/interpretation of metric values
+sub PM_SEM_COUNTER { 1; } # cumulative counter (monotonic increasing)
+sub PM_SEM_INSTANT { 3; } # instantaneous value, continuous domain
+sub PM_SEM_DISCRETE { 4; } # instantaneous value, discrete domain
+
+# reserved domain (see $PCP_VAR_DIR/pmns/stdpmid)
+sub PMI_DOMAIN { 245; }
+
+# error codes
+sub PMI_ERR_DUPMETRICNAME { -20001; }
+sub PMI_ERR_DUPMETRICID { -20002; } # Metric pmID already defined
+sub PMI_ERR_DUPINSTNAME { -20003; } # External instance name already defined
+sub PMI_ERR_DUPINSTID { -20004; } # Internal instance identifer already defined
+sub PMI_ERR_INSTNOTNULL { -20005; } # Non-null instance expected for a singular metric
+sub PMI_ERR_INSTNULL { -20006; } # Null instance not allowed for a non-singular metric
+sub PMI_ERR_BADHANDLE { -20007; } # Illegal handle
+sub PMI_ERR_DUPVALUE { -20008; } # Value already assigned for singular metric
+sub PMI_ERR_BADTYPE { -20009; } # Illegal metric type
+sub PMI_ERR_BADSEM { -20010; } # Illegal metric semantics
+sub PMI_ERR_NODATA { -20011; } # No data to output
+sub PMI_ERR_BADMETRICNAME { -20012; } # Illegal metric name
+sub PMI_ERR_BADTIMESTAMP { -20013; } # Illegal result timestamp
+
+# Batch operations
+our %pmi_batch = ();
+
+sub pmiBatchPutValue($$$) {
+ my ($name, $instance, $value) = @_;
+ push @{$pmi_batch{'b'}}, [ $name, $instance, $value ];
+ return 0;
+}
+
+sub pmiBatchPutValueHandle($$) {
+ my ($handle, $value) = @_;
+ push @{$pmi_batch{'b'}}, [ $handle, $value ];
+ return 0;
+}
+
+sub pmiBatchWrite($$) {
+ my ($sec, $usec) = @_;
+ push @{$pmi_batch{"$sec.$usec"}}, @{delete $pmi_batch{'b'}};
+ return 0;
+}
+
+sub pmiBatchEnd() {
+ my ($arr, $r);
+ my $ts = -1;
+ # Iterate over the sorted hash and call pmiPutValue/pmiWrite accordingly
+ delete $pmi_batch{'b'};
+ for my $k (map { $_->[0] }
+ sort { $a->[1] <=> $b->[1] || $a->[2] <=> $b->[2] }
+ map { [$_, /(\d+)\.(\d+)/] }
+ keys %pmi_batch) {
+ $arr = $pmi_batch{$k};
+ $ts = $k if $ts eq -1;
+ if ($k > $ts) {
+ $r = pmiWrite(split(/\./, $ts));
+ return $r if ($r != 0);
+ $ts = $k;
+ }
+ for my $v (@$arr) {
+ if (defined($v->[2])) {
+ $r = pmiPutValue($v->[0], $v->[1], $v->[2]);
+ } else {
+ $r = pmiPutValueHandle($v->[0], $v->[1]);
+ }
+ return $r if ($r != 0);
+ }
+ }
+ $r = pmiWrite(split(/\./, $ts));
+ return $r if ($r != 0);
+ %pmi_batch = ();
+ return 0;
+}
+
+bootstrap PCP::LogImport $VERSION;
+
+1; # don't forget to return a true value from the file
+
+__END__
+
+=head1 NAME
+
+PCP::LogImport - Perl module for importing performance data to create a Performance Co-Pilot archive
+
+=head1 SYNOPSIS
+
+ use PCP::LogImport;
+
+=head1 DESCRIPTION
+
+The PCP::LogImport module contains the language bindings for building
+Perl applications that import performance data from a file or real-time
+source and create a Performance Co-Pilot (PCP) archive suitable for use
+with the PCP tools.
+
+The routines in this module provide wrappers around the libpcp_import
+library.
+
+=head1 SEE ALSO
+
+pmiAddInstance(3), pmiAddMetric(3), pmiEnd(3), pmiErrStr(3),
+pmiGetHandle(3), pmiPutResult(3), pmiPutValue(3), pmiPutValueHandle(3),
+pmiStart(3), pmiSetHostname(3), pmiSetTimezone(3), pmiUnits(3),
+pmiUseContext(3) and pmiWrite(3).
+
+The PCP mailing list pcp@mail.performancecopilot.org can be used for
+questions about this module.
+
+Further details can be found at http://www.performancecopilot.org
+
+=head1 AUTHOR
+
+Ken McDonell, E<lt>kenj@internode.on.netE<gt>
+
+Copyright (C) 2010 by Ken McDonell.
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
+=cut
diff --git a/src/perl/LogImport/LogImport.xs b/src/perl/LogImport/LogImport.xs
new file mode 100644
index 0000000..3ee9d2d
--- /dev/null
+++ b/src/perl/LogImport/LogImport.xs
@@ -0,0 +1,129 @@
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include <pmapi.h>
+#include <impl.h>
+#include <import.h>
+
+MODULE = PCP::LogImport PACKAGE = PCP::LogImport
+
+# helper methods
+#
+
+# name here is a little odd ... follows impl.h definition rather
+# than pmi* naming so calls from C and Perl are the same
+pmID
+pmid_build(domain, cluster, item)
+ unsigned int domain;
+ unsigned int cluster;
+ unsigned int item;
+ CODE:
+ pmID id;
+ __pmid_int(&id)->flag = 0;
+ __pmid_int(&id)->domain = domain;
+ __pmid_int(&id)->cluster = cluster;
+ __pmid_int(&id)->item = item;
+ RETVAL = id;
+ OUTPUT:
+ RETVAL
+
+# name here is a little odd ... follows impl.h definition rather
+# than pmi* naming so calls from C and Perl are the same
+pmInDom
+pmInDom_build(domain, serial)
+ unsigned int domain;
+ unsigned int serial;
+ CODE:
+ pmInDom indom;
+ __pmindom_int(&indom)->flag = 0;
+ __pmindom_int(&indom)->domain = domain;
+ __pmindom_int(&indom)->serial = serial;
+ RETVAL = indom;
+ OUTPUT:
+ RETVAL
+
+# libpcp_import wrappers
+#
+
+void
+pmiDump()
+
+pmUnits
+pmiUnits(dimSpace, dimTime, dimCount, scaleSpace, scaleTime, scaleCount)
+ int dimSpace;
+ int dimTime;
+ int dimCount;
+ int scaleSpace;
+ int scaleTime;
+ int scaleCount;
+
+pmID
+pmiID(domain, cluster, item)
+ int domain;
+ int cluster;
+ int item;
+
+pmInDom
+pmiInDom(domain, serial)
+ int domain;
+ int serial;
+
+const char *
+pmiErrStr(sts)
+ int sts;
+
+int
+pmiStart(archive, inherit)
+ char *archive;
+ int inherit;
+
+int
+pmiUseContext(context)
+ int context;
+
+int
+pmiEnd()
+
+int
+pmiSetHostname(value)
+ char *value;
+
+int
+pmiSetTimezone(value)
+ char *value;
+
+int
+pmiAddMetric(name, pmid, type, indom, sem, units)
+ const char *name;
+ pmID pmid;
+ int type;
+ pmInDom indom;
+ int sem;
+ pmUnits units;
+
+int
+pmiAddInstance(indom, instance, inst)
+ pmInDom indom;
+ const char *instance;
+ int inst;
+
+int
+pmiPutValue(name, instance, value)
+ const char *name;
+ const char *instance;
+ const char *value;
+
+int
+pmiGetHandle(name, instance)
+ const char *name;
+ const char *instance;
+
+int
+pmiPutValueHandle(handle, value)
+ int handle;
+ const char *value;
+
+int
+pmiWrite(sec, usec)
+ int sec;
+ int usec;
diff --git a/src/perl/LogImport/MANIFEST b/src/perl/LogImport/MANIFEST
new file mode 100644
index 0000000..61c6108
--- /dev/null
+++ b/src/perl/LogImport/MANIFEST
@@ -0,0 +1,7 @@
+Changes
+COPYING
+Makefile.PL
+MANIFEST
+LogImport.pm
+LogImport.xs
+typemap
diff --git a/src/perl/LogImport/Makefile.PL b/src/perl/LogImport/Makefile.PL
new file mode 100644
index 0000000..20b716d
--- /dev/null
+++ b/src/perl/LogImport/Makefile.PL
@@ -0,0 +1,49 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+my $ldfrom;
+my $inc;
+my $libs;
+my $lddlflags;
+my $cccdlflags;
+
+if ($ENV{TARGET_OS} eq "mingw") {
+ $ldfrom = "-L$ENV{PCP_TOPDIR}/src/libpcp/src -L$ENV{PCP_TOPDIR}/src/libpcp_import/src -L$ENV{PCP_DIR}\\local\\bin -lpcp_import -lpcp LogImport.o",
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp -I$ENV{PCP_DIR}\\include\\pcp -I$ENV{PCP_DIR}\\c\\include";
+ $libs = ["-L$ENV{PCP_DIR}\\local\\bin", '-lpcp_import', '-lpcp'];
+}
+else {
+ $ldfrom = "LogImport.o",
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp";
+ $libs = ["-L$ENV{PCP_TOPDIR}/src/libpcp/src -L$ENV{PCP_TOPDIR}/src/libpcp_import/src -lpcp_import -lpcp"];
+}
+if ($ENV{TARGET_OS} eq "darwin") {
+ # standard ones, minus -arch ppc
+ $lddlflags = "-arch x86_64 -arch i386 -bundle -undefined dynamic_lookup";
+}
+else {
+ $lddlflags = "-shared \$(OPTIMIZE) \$(LDFLAGS)";
+}
+if ($ENV{TARGET_OS} eq "solaris") {
+ # for OpenSolaris Makefile ends up with -KPIC instead of -fPIC otherwise
+ $cccdlflags = "-fPIC"
+}
+
+WriteMakefile(
+ NAME => 'PCP::LogImport',
+ AUTHOR => 'Ken McDonell <kenj@internode.on.net>',
+ VERSION_FROM => 'LogImport.pm', # finds $VERSION
+ ABSTRACT_FROM => 'LogImport.pm', # retrieve abstract from module
+ C => ['LogImport.c'],
+ OPTIMIZE => '-g',
+ XSPROTOARG => '-noprototypes',
+ OBJECT => 'LogImport.o',
+ DEFINE => '-DPERLIO_NOT_STDIO=0 -DPCP_VERSION -DPCP_DEBUG',
+ LDFROM => $ldfrom,
+ LDDLFLAGS => $lddlflags,
+ CCCDLFLAGS => $cccdlflags,
+ INC => $inc,
+ LIBS => $libs,
+ CC => $ENV{"CC"},
+ LD => $ENV{"CC"},
+);
diff --git a/src/perl/LogImport/typemap b/src/perl/LogImport/typemap
new file mode 100644
index 0000000..162308b
--- /dev/null
+++ b/src/perl/LogImport/typemap
@@ -0,0 +1,24 @@
+######################################################################
+# INPUT/OUTPUT maps
+# O_OBJECT -> links an opaque C object to a blessed Perl object.
+#
+TYPEMAP
+pmUnits T_INT_EQUIV
+pmID T_INT_EQUIV
+pmInDom T_INT_EQUIV
+
+######################################################################
+INPUT
+# struct or typedef that is really the same size as a 32-bit integer
+T_INT_EQUIV
+ {
+ __int32_t tmp = SvIV($arg);
+ memcpy((void *)&$var, (void *)&tmp, sizeof(__int32_t));
+ }
+
+######################################################################
+OUTPUT
+# struct or typedef that is really the same size as a 32-bit integer
+T_INT_EQUIV
+ sv_setiv($arg, *((int *)&$var));
+######################################################################
diff --git a/src/perl/LogSummary/Changes b/src/perl/LogSummary/Changes
new file mode 100644
index 0000000..e2c8ffe
--- /dev/null
+++ b/src/perl/LogSummary/Changes
@@ -0,0 +1,8 @@
+Revision history for Perl extension PCP::LogSummary.
+
+1.01 Web Mar 4 10:48:37 2009
+ - updated excel-export demo to remove unwanted dependency
+
+1.00 Fri Nov 28 09:20:55 2008
+ - original version created
+
diff --git a/src/perl/LogSummary/GNUmakefile b/src/perl/LogSummary/GNUmakefile
new file mode 100644
index 0000000..3411826
--- /dev/null
+++ b/src/perl/LogSummary/GNUmakefile
@@ -0,0 +1,62 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+PERLMOD = LogSummary.pm
+PERLDOCS = Changes MANIFEST README
+PERLCODE = extract.pl exceldemo.pl
+LSRCFILES = Makefile.PL $(PERLDOCS) $(PERLMOD) $(PERLCODE)
+SUBDIRS = t
+
+LPKGDIRT = PCP-LogSummary-* MYMETA.yml MYMETA.json
+LDIRT = Makefile COPYING pm_to_blib blib Makefile.old $(LPKGDIRT) *.xls
+
+default: dist
+
+MAKEMAKER_OPTIONS = INSTALLDIRS=$(PERL_INSTALLDIRS) INSTALLVENDORMAN3DIR=$(PCP_MAN_DIR)/man3
+INSTALLER_OPTIONS = DESTDIR=$$DIST_ROOT
+
+ifeq ($(TARGET_OS),mingw)
+PERLMAKE = dmake.exe
+else
+PERLMAKE = $(MAKE)
+endif
+
+Makefile: COPYING Makefile.PL
+ $(call PERL_MAKE_MAKEFILE)
+
+COPYING:
+ $(LN_S) $(TOPDIR)/COPYING COPYING
+
+test dist: Makefile
+ rm -f $(LPKGDIRT)
+ $(PERLMAKE) -f Makefile $@
+
+include $(BUILDRULES)
+
+install: default
+ifneq "$(PACKAGE_DISTRIBUTION)" "debian"
+ $(call PERL_GET_FILELIST,$(TOPDIR)/perl-pcp-logsummary.list,LogSummary)
+endif
+
+install_perl:
+ $(PERLMAKE) -f Makefile pure_install $(INSTALLER_OPTIONS)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/perl/LogSummary/LogSummary.pm b/src/perl/LogSummary/LogSummary.pm
new file mode 100644
index 0000000..a5f2c36
--- /dev/null
+++ b/src/perl/LogSummary/LogSummary.pm
@@ -0,0 +1,117 @@
+package PCP::LogSummary;
+
+use strict;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+require Exporter;
+
+@ISA = qw(Exporter);
+@EXPORT = qw(new metric_instance);
+@EXPORT_OK = qw( );
+$VERSION = '1.01';
+
+sub new
+{
+ my ( $self, $archive, $metricsref, $start, $finish ) = @_;
+ my $opts = '-F -N -z -biImMy -p6';
+ my @metrica = @{$metricsref};
+ my $metrics = ' ';
+ my %results;
+
+ foreach my $m (@metrica) { $metrics .= $m . ' '; }
+ if (defined($start)) { $opts .= " -S'$start'"; }
+ if (defined($finish)) { $opts .= " -T'$finish'"; }
+
+ open SUMMARY, "pmlogsummary $opts $archive $metrics |"
+ || die "pmlogsummary: $!\n";
+
+ # metric,[inst],stocavg,timeavg,minimum,mintime,maximum,maxtime,count,units
+ LINE: while (<SUMMARY>) {
+ # print "Input line: $_\n";
+ m/^(\S.+),(.*),(\S+),(\S+),(\S+),(.+),(\S+),(.+),(\S+),(.*)$/
+ || next LINE;
+
+ # If counter metric doesn't cover 90% of archive, metric name
+ # is preceded by an asterix. Chop this off and set a flag.
+ my $metric = $1;
+ $metric =~ s/^\*//;
+ my $asterix = ($metric ne $1);
+
+ my %result;
+ $result{'average'} = $3;
+ $result{'timeavg'} = $4;
+ $result{'minimum'} = $5;
+ $result{'mintime'} = $6;
+ $result{'maximum'} = $7;
+ $result{'maxtime'} = $8;
+ $result{'samples'} = $9;
+ $result{'units'} = $10;
+ $result{'ninety%'} = $asterix;
+
+ my $key = $1;
+ if ($2 ne "") { $key .= $2; }
+ $results{$key} = \%result;
+
+ # print "key=", $key, " average=$3\n";
+ }
+ close SUMMARY;
+ return \%results;
+}
+
+sub metric_instance
+{
+ my ( $metric, $instance ) = @_;
+ return "$metric\[\"$instance\"\]";
+}
+
+1;
+__END__
+
+=head1 NAME
+
+PCP::LogSummary - Perl interface for pmlogsummary(1)
+
+=head1 SYNOPSIS
+
+ use PCP::LogSummary;
+
+ my $summary = new PCP::LogSummary($log, \@metrics, $start, $end);
+
+=head1 DESCRIPTION
+
+The PCP::LogSummary module is a wrapper around the Performance Co-Pilot
+pmlogsummary(1) command.
+Its primary purpose is to automate the production of post-processed
+pmlogsummary data, in particular to automate the step where the
+summarised data is imported into a spreadsheet for further anaylsis.
+This has proven to often be an iterative process - done manually it
+involves much cutting+pasting, and can be a significant time waster.
+
+=head2 EXPORT
+
+new
+
+metric_instance
+
+=head1 SEE ALSO
+
+pmlogsummary(1).
+
+The PCP mailing list pcp@mail.performancecopilot.org can be used for
+questions about this module.
+
+Further details can be found at http://www.performancecopilot.org
+
+=head1 AUTHOR
+
+Nathan Scott, E<lt>nathans@debian.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2008 by Aconex
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
+=cut
diff --git a/src/perl/LogSummary/MANIFEST b/src/perl/LogSummary/MANIFEST
new file mode 100644
index 0000000..df94fca
--- /dev/null
+++ b/src/perl/LogSummary/MANIFEST
@@ -0,0 +1,20 @@
+Changes
+Makefile.PL
+MANIFEST
+README
+LogSummary.pm
+extract.pl
+exceldemo.pl
+t/test.t
+t/app/20081125.index
+t/app/20081125.meta
+t/app/20081125.0
+t/app/20081126.index
+t/app/20081126.meta
+t/app/20081126.0
+t/db/20081125.index
+t/db/20081125.meta
+t/db/20081125.0
+t/db/20081126.index
+t/db/20081126.meta
+t/db/20081126.0
diff --git a/src/perl/LogSummary/Makefile.PL b/src/perl/LogSummary/Makefile.PL
new file mode 100644
index 0000000..89f0436
--- /dev/null
+++ b/src/perl/LogSummary/Makefile.PL
@@ -0,0 +1,9 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ NAME => 'PCP::LogSummary',
+ AUTHOR => 'Nathan Scott <nathans@debian.org>',
+ VERSION_FROM => 'LogSummary.pm', # finds $VERSION
+ ABSTRACT_FROM => 'LogSummary.pm', # retrieve abstract from module
+);
diff --git a/src/perl/LogSummary/README b/src/perl/LogSummary/README
new file mode 100644
index 0000000..dd11f5f
--- /dev/null
+++ b/src/perl/LogSummary/README
@@ -0,0 +1,32 @@
+PCP-LogSummary version 1.00
+===========================
+
+The PCP::LogSummary module is a wrapper around the PCP pmlogsummary(1)
+command. Its primary purpose is to automate the production of post-
+processed pmlogsummary data, in particular to automate the step where
+the summarised data is imported into a spreadsheet for further anaylsis.
+This has proven to often be an iterative process - when done manually
+it involves much cutting+pasting and can be a significant time waster.
+
+INSTALLATION
+
+To install this module type the following:
+
+ perl Makefile.PL
+ make
+ make test
+ make install
+
+DEPENDENCIES
+
+This module requires no other Perl modules and libraries, but does
+obviously require a working Performance Co-Pilot installation.
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2008 by Aconex
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
diff --git a/src/perl/LogSummary/exceldemo.pl b/src/perl/LogSummary/exceldemo.pl
new file mode 100644
index 0000000..1aed252
--- /dev/null
+++ b/src/perl/LogSummary/exceldemo.pl
@@ -0,0 +1,89 @@
+# A demo script which interfaces the LogSummary Perl module to the
+# WriteExcel module, with some real data, to show what it can do.
+# Author: Nathan Scott <nathans@debian.org>
+# Note: No #! line, as this pulls in an external dependency that
+# we really don't want in packaging tools like rpm.
+
+use strict;
+use warnings;
+use PCP::LogSummary;
+use Spreadsheet::WriteExcel;
+
+my @app = ('aconex.response_time.samples', 'aconex.response_time.adjavg');
+my @db = ('kernel.all.cpu.user', 'kernel.all.cpu.sys', 'kernel.all.cpu.intr');
+my @dbdisk = ('disk.dev.idle', 'disk.dev.read_bytes');
+
+my ( $dblog1, $dblog2 ) = ('t/db/20081125', 't/db/20081126');
+my $dbdisk_before = PCP::LogSummary->new($dblog1, \@dbdisk);
+my $dbdisk_after = PCP::LogSummary->new($dblog2, \@dbdisk);
+my $dbcpu_before = PCP::LogSummary->new($dblog1, \@db);
+my $dbcpu_after = PCP::LogSummary->new($dblog2, \@db);
+
+my ( $applog1, $applog2 ) = ('t/app/20081125', 't/app/20081126');
+my $app_before = PCP::LogSummary->new($applog1, \@app);
+my $app_after = PCP::LogSummary->new($applog2, \@app);
+
+my $workbook = Spreadsheet::WriteExcel->new('new-dxb-dbserver.xls');
+
+my $heading = $workbook->add_format();
+$heading->set_bold();
+$heading->set_italic();
+my $subheading = $workbook->add_format();
+$subheading->set_italic();
+$subheading->set_bg_color('silver');
+my $unitscolumn = $workbook->add_format();
+$unitscolumn->set_align('center');
+
+my $sheet = $workbook->add_worksheet();
+my ( $precol, $postcol, $units ) = ( 1, 2, 3 );
+$sheet->set_column('A:A', 32); # metric names column
+$sheet->set_column('B:B', 14); # column for "Before" values
+$sheet->set_column('C:C', 14); # column for "After" values
+$sheet->set_column('D:D', 12); # metrics units column
+
+my $row = 0;
+$sheet->write($row, 0, 'Dubai Database Storage Upgrade', $heading);
+$row = 2;
+$sheet->write($row, 0, 'Metrics', $subheading);
+$sheet->write($row, $precol, 'Before', $subheading);
+$sheet->write($row, $postcol, 'After', $subheading);
+$sheet->write($row, $units, 'Units', $subheading);
+
+foreach my $m ( @app ) {
+ my $metric = metric_instance($m, 'dxb');
+ $row++;
+ $sheet->write($row, 0, $metric);
+ $sheet->write($row, $precol, $$app_before{$metric}{'average'});
+ $sheet->write($row, $postcol, $$app_after{$metric}{'average'});
+ $sheet->write($row, $units, $$app_after{$metric}{'units'}, $unitscolumn);
+}
+foreach my $m ( @dbdisk ) {
+ my $metric = metric_instance($m, 'G:'); # Windows drive letter
+ $row++;
+ $sheet->write($row, 0, $metric);
+ $sheet->write($row, $precol, $$dbdisk_before{$metric}{'average'});
+ $sheet->write($row, $postcol, $$dbdisk_after{$metric}{'average'});
+ $sheet->write($row, $units, $$dbdisk_after{$metric}{'units'}, $unitscolumn);
+}
+
+# Report CPU metrics as a single utilisation value
+{
+ my $syscpu1 = $$dbcpu_before{'kernel.all.cpu.sys'};
+ my $intcpu1 = $$dbcpu_before{'kernel.all.cpu.intr'};
+ my $usrcpu1 = $$dbcpu_before{'kernel.all.cpu.user'};
+ my $syscpu2 = $$dbcpu_after{'kernel.all.cpu.sys'};
+ my $intcpu2 = $$dbcpu_after{'kernel.all.cpu.intr'};
+ my $usrcpu2 = $$dbcpu_after{'kernel.all.cpu.user'};
+ my $ncpu = 4;
+
+ my $cpu_before = $ncpu * ( $$syscpu1{'average'} +
+ $$intcpu1{'average'} + $$usrcpu1{'average'} );
+ my $cpu_after = $ncpu * ( $$syscpu2{'average'} +
+ $$intcpu2{'average'} + $$usrcpu2{'average'} );
+
+ $row++;
+ $sheet->write($row, 0, 'kernel.all.cpu');
+ $sheet->write($row, $precol, $cpu_before * 100.0);
+ $sheet->write($row, $postcol, $cpu_after * 100.0);
+ $sheet->write($row, $units, 'percent', $unitscolumn);
+}
diff --git a/src/perl/LogSummary/extract.pl b/src/perl/LogSummary/extract.pl
new file mode 100755
index 0000000..1165a7b
--- /dev/null
+++ b/src/perl/LogSummary/extract.pl
@@ -0,0 +1,18 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use PCP::LogSummary;
+
+my $archive = 't/db/20081125';
+my @metrics = ( 'kernel.all.cpu.user', 'kernel.all.cpu.idle',
+ 'kernel.all.cpu.intr', 'kernel.all.cpu.sys' );
+my $results = PCP::LogSummary->new($archive, \@metrics, '@09:00', '@17:00');
+#my $results = PCP::LogSummary->new($archive, \@metrics);
+
+foreach my $metric ( keys %$results ) {
+ my $summary = $$results{$metric};
+ print "metric=", $metric, "\n";
+ print " average=", $$summary{'average'}, "\n";
+ print " samples=", $$summary{'samples'}, "\n";
+}
diff --git a/src/perl/LogSummary/t/GNUmakefile b/src/perl/LogSummary/t/GNUmakefile
new file mode 100644
index 0000000..6c00e8d
--- /dev/null
+++ b/src/perl/LogSummary/t/GNUmakefile
@@ -0,0 +1,12 @@
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+LSRCFILES = test.t
+SUBDIRS = app db
+
+default:
+
+include $(BUILDRULES)
+
+install:
+
+default_pcp install_pcp:
diff --git a/src/perl/LogSummary/t/app/20081125.0 b/src/perl/LogSummary/t/app/20081125.0
new file mode 100644
index 0000000..43e5f4a
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081125.0
Binary files differ
diff --git a/src/perl/LogSummary/t/app/20081125.index b/src/perl/LogSummary/t/app/20081125.index
new file mode 100644
index 0000000..76eb3b9
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081125.index
Binary files differ
diff --git a/src/perl/LogSummary/t/app/20081125.meta b/src/perl/LogSummary/t/app/20081125.meta
new file mode 100644
index 0000000..5007180
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081125.meta
Binary files differ
diff --git a/src/perl/LogSummary/t/app/20081126.0 b/src/perl/LogSummary/t/app/20081126.0
new file mode 100644
index 0000000..9563c3c
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081126.0
Binary files differ
diff --git a/src/perl/LogSummary/t/app/20081126.index b/src/perl/LogSummary/t/app/20081126.index
new file mode 100644
index 0000000..d5d7fa6
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081126.index
Binary files differ
diff --git a/src/perl/LogSummary/t/app/20081126.meta b/src/perl/LogSummary/t/app/20081126.meta
new file mode 100644
index 0000000..b60336c
--- /dev/null
+++ b/src/perl/LogSummary/t/app/20081126.meta
Binary files differ
diff --git a/src/perl/LogSummary/t/app/GNUmakefile b/src/perl/LogSummary/t/app/GNUmakefile
new file mode 100644
index 0000000..8309f9c
--- /dev/null
+++ b/src/perl/LogSummary/t/app/GNUmakefile
@@ -0,0 +1,13 @@
+TOPDIR = ../../../../..
+include $(TOPDIR)/src/include/builddefs
+LSRCFILES = \
+ 20081125.meta 20081125.index 20081125.0 \
+ 20081126.meta 20081126.index 20081126.0
+
+default:
+
+include $(BUILDRULES)
+
+install:
+
+default_pcp install_pcp:
diff --git a/src/perl/LogSummary/t/db/20081125.0 b/src/perl/LogSummary/t/db/20081125.0
new file mode 100644
index 0000000..5c8d606
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081125.0
Binary files differ
diff --git a/src/perl/LogSummary/t/db/20081125.index b/src/perl/LogSummary/t/db/20081125.index
new file mode 100644
index 0000000..450dd1a
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081125.index
Binary files differ
diff --git a/src/perl/LogSummary/t/db/20081125.meta b/src/perl/LogSummary/t/db/20081125.meta
new file mode 100644
index 0000000..52f8376
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081125.meta
Binary files differ
diff --git a/src/perl/LogSummary/t/db/20081126.0 b/src/perl/LogSummary/t/db/20081126.0
new file mode 100644
index 0000000..758c6ae
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081126.0
Binary files differ
diff --git a/src/perl/LogSummary/t/db/20081126.index b/src/perl/LogSummary/t/db/20081126.index
new file mode 100644
index 0000000..eac9dbb
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081126.index
Binary files differ
diff --git a/src/perl/LogSummary/t/db/20081126.meta b/src/perl/LogSummary/t/db/20081126.meta
new file mode 100644
index 0000000..835e3be
--- /dev/null
+++ b/src/perl/LogSummary/t/db/20081126.meta
Binary files differ
diff --git a/src/perl/LogSummary/t/db/GNUmakefile b/src/perl/LogSummary/t/db/GNUmakefile
new file mode 100644
index 0000000..8309f9c
--- /dev/null
+++ b/src/perl/LogSummary/t/db/GNUmakefile
@@ -0,0 +1,13 @@
+TOPDIR = ../../../../..
+include $(TOPDIR)/src/include/builddefs
+LSRCFILES = \
+ 20081125.meta 20081125.index 20081125.0 \
+ 20081126.meta 20081126.index 20081126.0
+
+default:
+
+include $(BUILDRULES)
+
+install:
+
+default_pcp install_pcp:
diff --git a/src/perl/LogSummary/t/test.t b/src/perl/LogSummary/t/test.t
new file mode 100644
index 0000000..816c114
--- /dev/null
+++ b/src/perl/LogSummary/t/test.t
@@ -0,0 +1,41 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.t'
+
+#########################
+
+# change 'tests => 1' to 'tests => last_test_to_print';
+
+use Test;
+BEGIN { plan tests => 15 };
+use PCP::LogSummary;
+ok(1); # If we made it this far, we're ok.
+
+#########################
+
+my $archive = 't/db/20081125';
+my @metrics = ( 'kernel.all.cpu.user', 'kernel.all.cpu.sys' );
+my $results = PCP::LogSummary->new($archive, \@metrics);
+ok(1, defined($results), "log summarised");
+
+foreach my $metric ( sort keys %$results ) {
+ my $summary = $$results{$metric};
+ #print("metric=", $metric, "\n");
+ #print(" average=", $$summary{'average'}, "\n");
+ #print(" samples=", $$summary{'samples'}, "\n");
+ ok(1, ($$summary{'samples'} == 5758), "samples verified");
+ ok(1, ($$summary{'average'} > 0), "average lower bounds check");
+ ok(1, ($$summary{'average'} < 1), "average upper bounds check") ;
+}
+
+$results = PCP::LogSummary->new($archive, \@metrics, '@09:00', '@17:00');
+ok(1, defined($results), "restricted log summarised");
+
+foreach my $metric ( sort keys %$results ) {
+ my $summary = $$results{$metric};
+ #print("metric=", $metric, "\n");
+ #print(" average=", $$summary{'average'}, "\n");
+ #print(" samples=", $$summary{'samples'}, "\n");
+ ok(1, ($$summary{'samples'} == 1919), "restricted samples verified");
+ ok(1, ($$summary{'average'} > 0), "average lower bounds check");
+ ok(1, ($$summary{'average'} < 1), "average upper bounds check") ;
+}
diff --git a/src/perl/MMV/Changes b/src/perl/MMV/Changes
new file mode 100644
index 0000000..a799519
--- /dev/null
+++ b/src/perl/MMV/Changes
@@ -0,0 +1,12 @@
+Revision history for Perl extension PCP::MMV.
+
+1.00 Mon Aug 24 08:52:36 EST 2009
+ - added stop interface
+ - added stats_set interface
+ - fixed incorrect memory free on init
+ - extend server.pl example to report idle time
+ - fixed use of av_len return codes (off by one)
+
+0.01 Fri Jun 12 16:55:20 EST 2009
+ - original version
+
diff --git a/src/perl/MMV/GNUmakefile b/src/perl/MMV/GNUmakefile
new file mode 100644
index 0000000..1fc485d
--- /dev/null
+++ b/src/perl/MMV/GNUmakefile
@@ -0,0 +1,67 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+PERLMOD = MMV.pm
+INTERFACE = MMV.xs typemap
+TESTCODE = test.pl server.pl
+PERLDOCS = Changes MANIFEST
+LSRCFILES = Makefile.PL $(PERLDOCS) $(PERLMOD) $(INTERFACE) $(TESTCODE)
+
+LPKGDIRT = PCP-MMV-* MYMETA.yml MYMETA.json
+LDIRT = Makefile COPYING MMV.bs MMV.c MMV.o pm_to_blib blib \
+ Makefile.old $(LPKGDIRT)
+
+default: dist
+
+MAKEMAKER_OPTIONS = INSTALLDIRS=$(PERL_INSTALLDIRS) INSTALLVENDORMAN3DIR=$(PCP_MAN_DIR)/man3
+INSTALLER_OPTIONS = DESTDIR=$$DIST_ROOT
+
+ifeq ($(TARGET_OS),mingw)
+PERLMAKE = dmake.exe
+else
+PERLMAKE = $(MAKE)
+endif
+
+MMV.o: Makefile MMV.xs
+ $(PERLMAKE) -f Makefile
+
+Makefile: COPYING Makefile.PL
+ $(call PERL_MAKE_MAKEFILE)
+
+COPYING:
+ $(LN_S) $(TOPDIR)/COPYING COPYING
+
+test dist: MMV.o
+ rm -f $(LPKGDIRT)
+ $(PERLMAKE) -f Makefile $@
+
+include $(BUILDRULES)
+
+install: default
+ifneq "$(PACKAGE_DISTRIBUTION)" "debian"
+ $(call PERL_GET_FILELIST,$(TOPDIR)/perl-pcp-mmv.list,MMV)
+ find $$DIST_ROOT -name server.pl -exec chmod 755 '{}' ';'
+endif
+
+install_perl:
+ $(PERLMAKE) -f Makefile pure_install $(INSTALLER_OPTIONS)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/perl/MMV/MANIFEST b/src/perl/MMV/MANIFEST
new file mode 100644
index 0000000..1551ac8
--- /dev/null
+++ b/src/perl/MMV/MANIFEST
@@ -0,0 +1,9 @@
+Changes
+COPYING
+Makefile.PL
+MANIFEST
+MMV.pm
+MMV.xs
+server.pl
+test.pl
+typemap
diff --git a/src/perl/MMV/MMV.pm b/src/perl/MMV/MMV.pm
new file mode 100644
index 0000000..9f3d7ea
--- /dev/null
+++ b/src/perl/MMV/MMV.pm
@@ -0,0 +1,120 @@
+package PCP::MMV;
+
+use strict;
+use warnings;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+require Exporter;
+require DynaLoader;
+
+@ISA = qw(Exporter DynaLoader);
+@EXPORT = qw(
+ mmv_stats_init mmv_stats_stop mmv_units
+ mmv_lookup_value_desc
+ mmv_inc_value mmv_set_value mmv_set_string
+ mmv_stats_add mmv_stats_inc mmv_stats_set
+ mmv_stats_add_fallback mmv_stats_inc_fallback
+ mmv_stats_interval_start mmv_stats_interval_end
+ mmv_stats_set_string
+ MMV_FLAG_NOPREFIX MMV_FLAG_PROCESS
+ MMV_INDOM_NULL
+ MMV_TYPE_NOSUPPORT
+ MMV_TYPE_I32 MMV_TYPE_U32
+ MMV_TYPE_I64 MMV_TYPE_U64
+ MMV_TYPE_FLOAT MMV_TYPE_DOUBLE
+ MMV_TYPE_STRING MMV_TYPE_ELAPSED
+ MMV_COUNT_ONE
+ MMV_SEM_COUNTER MMV_SEM_INSTANT MMV_SEM_DISCRETE
+ MMV_SPACE_BYTE MMV_SPACE_KBYTE MMV_SPACE_MBYTE
+ MMV_SPACE_GBYTE MMV_SPACE_TBYTE
+ MMV_TIME_NSEC MMV_TIME_USEC MMV_TIME_MSEC
+ MMV_TIME_SEC MMV_TIME_MIN MMV_TIME_HOUR
+);
+@EXPORT_OK = qw();
+$VERSION = '1.00';
+
+sub MMV_INDOM_NULL { 0xffffffff; }
+
+# flags for pmdammv
+sub MMV_FLAG_NOPREFIX { 0x1; } # metric names not prefixed by file name
+sub MMV_FLAG_PROCESS { 0x2; } # instrumented process must be running
+
+# data type of metric values
+sub MMV_TYPE_NOSUPPORT { 0xffffffff; } # not implemented in this version
+sub MMV_TYPE_I32 { 0; } # 32-bit signed integer
+sub MMV_TYPE_U32 { 1; } # 32-bit unsigned integer
+sub MMV_TYPE_I64 { 2; } # 64-bit signed integer
+sub MMV_TYPE_U64 { 3; } # 64-bit signed integer
+sub MMV_TYPE_FLOAT { 4; } # 32-bit floating point
+sub MMV_TYPE_DOUBLE { 5; } # 64-bit floating point
+sub MMV_TYPE_STRING { 6; } # null-terminated string
+sub MMV_TYPE_ELAPSED { 10; } # 64-bit elapsed time
+
+# units - space scale
+sub MMV_SPACE_BYTE { 0; } # bytes
+sub MMV_SPACE_KBYTE { 1; } # kilobytes
+sub MMV_SPACE_MBYTE { 2; } # megabytes
+sub MMV_SPACE_GBYTE { 3; } # gigabytes
+sub MMV_SPACE_TBYTE { 4; } # terabytes
+
+# units - time scale
+sub MMV_TIME_NSEC { 0; } # nanoseconds
+sub MMV_TIME_USEC { 1; } # microseconds
+sub MMV_TIME_MSEC { 2; } # milliseconds
+sub MMV_TIME_SEC { 3; } # seconds
+sub MMV_TIME_MIN { 4; } # minutes
+sub MMV_TIME_HOUR { 5; } # hours
+
+# units - count scale (for metrics such as count events, syscalls,
+# interrupts, etc - these are simply powers of ten and not enumerated here
+# (e.g. 6 for 10^6, or -3 for 10^-3).
+sub MMV_COUNT_ONE { 0; } # 1
+
+# semantics/interpretation of metric values
+sub MMV_SEM_COUNTER { 1; } # cumulative counter, monotonic increasing
+sub MMV_SEM_INSTANT { 3; } # instantaneous value, continuous domain
+sub MMV_SEM_DISCRETE { 4; } # instantaneous value, discrete domain
+
+
+bootstrap PCP::MMV $VERSION;
+
+1;
+__END__
+
+=head1 NAME
+
+PCP::MMV - Perl module for Memory Mapped Value instrumentation
+
+=head1 SYNOPSIS
+
+ use PCP::MMV;
+
+=head1 DESCRIPTION
+
+The PCP::MMV Perl module contains the language bindings for
+building Perl programs instrumented with the Performance Co-Pilot
+Memory Mapped Value infrastructure - an efficient data transport
+mechanism for making performance data from within a Perl program
+available as PCP metrics using the MMV PMDA.
+
+=head1 SEE ALSO
+
+mmv_stats_init(3), mmv_inc_value(3), mmv_lookup_value_desc(3),
+mmv(4) and pmda(3).
+
+The PCP mailing list pcp@mail.performancecopilot.org can be used for
+questions about this module.
+
+Further details can be found at http://www.performancecopilot.org
+
+=head1 AUTHOR
+
+Nathan Scott, E<lt>nathans@debian.orgE<gt>
+
+Copyright (C) 2009 by Aconex.
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
+=cut
diff --git a/src/perl/MMV/MMV.xs b/src/perl/MMV/MMV.xs
new file mode 100644
index 0000000..2ee6ff8
--- /dev/null
+++ b/src/perl/MMV/MMV.xs
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "mmv_stats.h"
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+static int
+list_to_metric(SV *list, mmv_metric_t *metric)
+{
+ int i, len;
+ SV **entry[8];
+ AV *mlist = (AV *) SvRV(list);
+
+ if (SvTYPE((SV *)mlist) != SVt_PVAV) {
+ warn("metric declaration is not an array reference");
+ return -1;
+ }
+ len = av_len(mlist) + 1;
+ if (len < 6) {
+ warn("too few entries in metric array reference");
+ return -1;
+ }
+ if (len > 8) {
+ warn("too many entries in metric array reference");
+ return -1;
+ }
+ for (i = 0; i < len; i++)
+ entry[i] = av_fetch(mlist, i, 0);
+
+ strncpy(metric->name, SvPV_nolen(*entry[0]), MMV_NAMEMAX);
+ metric->name[MMV_NAMEMAX-1] = '\0';
+ metric->item = SvIV(*entry[1]);
+ metric->type = SvIV(*entry[2]);
+ metric->indom = SvIV(*entry[3]);
+ i = SvIV(*entry[4]);
+ memcpy(&metric->dimension, &i, sizeof(pmUnits));
+ metric->semantics = SvIV(*entry[5]);
+ if (len > 6)
+ metric->shorttext = strdup(SvPV_nolen(*entry[6]));
+ else
+ metric->shorttext = NULL;
+ if (len > 7)
+ metric->helptext = strdup(SvPV_nolen(*entry[7]));
+ else
+ metric->helptext = NULL;
+ return 0;
+}
+
+static int
+list_to_instances(SV *list, mmv_instances_t **insts)
+{
+ mmv_instances_t *instances;
+ int i, len;
+ AV *inlist = (AV *) SvRV(list);
+
+ if (SvTYPE((SV *)inlist) != SVt_PVAV) {
+ warn("instances declaration is not an array reference");
+ return -1;
+ }
+ len = av_len(inlist) + 1;
+ if (len++ % 2) {
+ warn("odd number of entries in instance array reference");
+ return -1;
+ }
+
+ len /= 2;
+ instances = (mmv_instances_t *)calloc(len, sizeof(mmv_instances_t));
+ if (instances == NULL) {
+ warn("insufficient memory for instance array");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ SV **id = av_fetch(inlist, i*2, 0);
+ SV **name = av_fetch(inlist, i*2+1, 0);
+ instances[i].internal = SvIV(*id);
+ strncpy(instances[i].external, SvPV_nolen(*name), MMV_NAMEMAX);
+ instances[i].external[MMV_NAMEMAX-1] = '\0';
+ }
+ *insts = instances;
+ return len;
+}
+
+static int
+list_to_indom(SV *list, mmv_indom_t *indom)
+{
+ int i, len;
+ SV **entry[4];
+ AV *ilist = (AV *) SvRV(list);
+
+ if (SvTYPE((SV *)ilist) != SVt_PVAV) {
+ warn("indom declaration is not an array reference");
+ return -1;
+ }
+ len = av_len(ilist) + 1;
+ if (len < 2) {
+ warn("too few entries in indom array reference");
+ return -1;
+ }
+ if (len > 4) {
+ warn("too many entries in indom array reference");
+ return -1;
+ }
+ for (i = 0; i < len; i++)
+ entry[i] = av_fetch(ilist, i, 0);
+
+ indom->serial = SvIV(*entry[0]);
+ if ((i = list_to_instances(*entry[1], &indom->instances)) < 0)
+ return -1;
+ indom->count = i;
+ if (len > 2)
+ indom->shorttext = strdup(SvPV_nolen(*entry[2]));
+ else
+ indom->shorttext = NULL;
+ if (len > 3)
+ indom->helptext = strdup(SvPV_nolen(*entry[3]));
+ else
+ indom->helptext = NULL;
+ return 0;
+}
+
+static int
+list_to_metrics(SV *list, mmv_metric_t **metriclist, int *mcount)
+{
+ mmv_metric_t *metrics;
+ int i, len;
+ AV *mlist = (AV *) SvRV(list);
+
+ if (SvTYPE((SV *)mlist) != SVt_PVAV) {
+ warn("metrics list is not an array reference");
+ return -1;
+ }
+ len = av_len(mlist) + 1;
+ metrics = (mmv_metric_t *)calloc(len, sizeof(mmv_metric_t));
+ if (metrics == NULL) {
+ warn("insufficient memory for metrics array");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ SV **entry = av_fetch(mlist, i, 0);
+ if (list_to_metric(*entry, &metrics[i]) < 0)
+ break;
+ }
+ *metriclist = metrics;
+ *mcount = len;
+ return (i == len);
+}
+
+static int
+list_to_indoms(SV *list, mmv_indom_t **indomlist, int *icount)
+{
+ mmv_indom_t *indoms;
+ int i, len;
+ AV *ilist = (AV *) SvRV(list);
+
+ if (SvTYPE((SV *)ilist) != SVt_PVAV) {
+ warn("indoms list is not an array reference");
+ return -1;
+ }
+ len = av_len(ilist) + 1;
+ indoms = (mmv_indom_t *)calloc(len, sizeof(mmv_indom_t));
+ if (indoms == NULL) {
+ warn("insufficient memory for indoms array");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ SV **entry = av_fetch(ilist, i, 0);
+ if (list_to_indom(*entry, &indoms[i]) < 0)
+ break;
+ }
+ *indomlist = indoms;
+ *icount = len;
+ return (i == len);
+}
+
+
+MODULE = PCP::MMV PACKAGE = PCP::MMV
+
+void *
+mmv_stats_init(name,cl,fl,metrics,indoms)
+ char * name
+ int cl
+ int fl
+ SV * metrics
+ SV * indoms
+ PREINIT:
+ int i, j;
+ int mcount;
+ int icount;
+ mmv_metric_t * mlist;
+ mmv_indom_t * ilist;
+ CODE:
+ i = list_to_metrics(metrics, &mlist, &mcount);
+ j = list_to_indoms(indoms, &ilist, &icount);
+
+ if (i <= 0 || j <= 0) {
+ warn("mmv_stats_init: bad list conversion: metrics=%d indoms=%d\n", i, j);
+ RETVAL = NULL;
+ }
+ else {
+ RETVAL = mmv_stats_init(name, cl, fl, mlist, mcount, ilist, icount);
+ if (RETVAL == NULL)
+ warn("mmv_stats_init failed: %s\n", osstrerror());
+ }
+
+ for (i = 0; i < icount; i++) {
+ if (ilist[i].shorttext)
+ free(ilist[i].shorttext);
+ if (ilist[i].helptext)
+ free(ilist[i].helptext);
+ free(ilist[i].instances);
+ }
+ if (ilist)
+ free(ilist);
+ for (i = 0; i < mcount; i++) {
+ if (mlist[i].shorttext)
+ free(mlist[i].shorttext);
+ if (mlist[i].helptext)
+ free(mlist[i].helptext);
+ }
+ if (mlist)
+ free(mlist);
+
+ if (!RETVAL)
+ XSRETURN_UNDEF;
+ OUTPUT:
+ RETVAL
+
+void
+mmv_stats_stop(handle,name)
+ void * handle
+ char * name
+ CODE:
+ mmv_stats_stop(handle, name);
+
+int
+mmv_units(dim_space,dim_time,dim_count,scale_space,scale_time,scale_count)
+ unsigned int dim_space
+ unsigned int dim_time
+ unsigned int dim_count
+ unsigned int scale_space
+ unsigned int scale_time
+ unsigned int scale_count
+ PREINIT:
+ pmUnits units;
+ CODE:
+ units.pad = 0;
+ units.dimSpace = dim_space; units.scaleSpace = scale_space;
+ units.dimTime = dim_time; units.scaleTime = scale_time;
+ units.dimCount = dim_count; units.scaleCount = scale_count;
+ RETVAL = *(int *)(&units);
+ OUTPUT:
+ RETVAL
+
+pmAtomValue *
+mmv_lookup_value_desc(handle,metric,instance)
+ void * handle
+ char * metric
+ char * instance
+ CODE:
+ RETVAL = mmv_lookup_value_desc(handle, metric, instance);
+ OUTPUT:
+ RETVAL
+
+void
+mmv_inc_value(handle,atom,value)
+ void * handle
+ pmAtomValue * atom
+ double value
+ CODE:
+ mmv_inc_value(handle, atom, value);
+
+void
+mmv_set_value(handle,atom,value)
+ void * handle
+ pmAtomValue * atom
+ double value
+ CODE:
+ mmv_set_value(handle, atom, value);
+
+void
+mmv_set_string(handle,atom,string)
+ void * handle
+ pmAtomValue * atom
+ SV * string
+ PREINIT:
+ int length;
+ char * data;
+ CODE:
+ data = SvPV_nolen(string);
+ length = strlen(data);
+ mmv_set_string(handle, atom, data, length);
+
+void
+mmv_stats_add(handle,metric,instance,count)
+ void * handle
+ char * metric
+ char * instance
+ double count
+ CODE:
+ mmv_stats_add(handle, metric, instance, count);
+
+void
+mmv_stats_inc(handle,metric,instance)
+ void * handle
+ char * metric
+ char * instance
+ CODE:
+ mmv_stats_inc(handle, metric, instance);
+
+void
+mmv_stats_set(handle,metric,instance, value)
+ void * handle
+ char * metric
+ char * instance
+ double value
+ CODE:
+ mmv_stats_set(handle, metric, instance, value);
+
+void
+mmv_stats_add_fallback(handle,metric,instance,instance2,count)
+ void * handle
+ char * metric
+ char * instance
+ char * instance2
+ double count
+ CODE:
+ mmv_stats_add_fallback(handle, metric, instance, instance2, count);
+
+void
+mmv_stats_inc_fallback(handle,metric,instance,instance2)
+ void * handle
+ char * metric
+ char * instance
+ char * instance2
+ CODE:
+ mmv_stats_inc_fallback(handle, metric, instance, instance2);
+
+void
+mmv_stats_interval_start(handle,value,metric,instance)
+ void * handle
+ pmAtomValue * value
+ char * metric
+ char * instance
+ CODE:
+ mmv_stats_interval_start(handle, value, metric, instance);
+
+void
+mmv_stats_interval_end(handle, value)
+ void * handle
+ pmAtomValue * value
+ CODE:
+ mmv_stats_interval_end(handle, value);
+
+void
+mmv_stats_set_string(handle,metric,instance,string)
+ void * handle
+ char * metric
+ char * instance
+ SV * string
+ PREINIT:
+ int length;
+ char * data;
+ CODE:
+ data = SvPV_nolen(string);
+ length = strlen(data);
+ mmv_stats_set_strlen(handle, metric, instance, data, length);
+
diff --git a/src/perl/MMV/Makefile.PL b/src/perl/MMV/Makefile.PL
new file mode 100644
index 0000000..6a85129
--- /dev/null
+++ b/src/perl/MMV/Makefile.PL
@@ -0,0 +1,49 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+my $ldfrom;
+my $inc;
+my $libs;
+my $lddlflags;
+my $cccdlflags;
+
+if ($ENV{TARGET_OS} eq "mingw") {
+ $ldfrom = "-L$ENV{PCP_TOPDIR}/src/libpcp/src -L$ENV{PCP_TOPDIR}/src/libpcp_mmv/src -L$ENV{PCP_DIR}\\local\\bin -lpcp_mmv -lpcp MMV.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp -I$ENV{PCP_DIR}\\include\\pcp -I$ENV{PCP_DIR}\\c\\include";
+ $libs = ["-L$ENV{PCP_DIR}\\local\\bin", '-lpcp_mmv', '-lpcp'];
+}
+else {
+ $ldfrom = "MMV.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp";
+ $libs = ["-L$ENV{PCP_TOPDIR}/src/libpcp_mmv/src -L$ENV{PCP_TOPDIR}/src/libpcp/src -lpcp_mmv -lpcp"];
+}
+if ($ENV{TARGET_OS} eq "darwin") {
+ # standard ones, minus -arch ppc
+ $lddlflags = "-arch x86_64 -arch i386 -bundle -undefined dynamic_lookup";
+}
+else {
+ $lddlflags = "-shared \$(OPTIMIZE) \$(LDFLAGS)";
+}
+if ($ENV{TARGET_OS} eq "solaris") {
+ # for OpenSolaris Makefile ends up with -KPIC instead of -fPIC otherwise
+ $cccdlflags = "-fPIC"
+}
+
+WriteMakefile(
+ NAME => 'PCP::MMV',
+ AUTHOR => 'Nathan Scott <nathans@debian.org>',
+ VERSION_FROM => 'MMV.pm', # finds $VERSION
+ ABSTRACT_FROM => 'MMV.pm', # retrieve abstract from module
+ C => ['MMV.c'],
+ OPTIMIZE => '-g',
+ XSPROTOARG => '-noprototypes',
+ OBJECT => 'MMV.o',
+ DEFINE => '-DPERLIO_NOT_STDIO=0 -DPCP_VERSION -DPCP_DEBUG',
+ LDFROM => $ldfrom,
+ LDDLFLAGS => $lddlflags,
+ CCCDLFLAGS => $cccdlflags,
+ INC => $inc,
+ LIBS => $libs,
+ CC => $ENV{"CC"},
+ LD => $ENV{"CC"},
+);
diff --git a/src/perl/MMV/server.pl b/src/perl/MMV/server.pl
new file mode 100755
index 0000000..f62f440
--- /dev/null
+++ b/src/perl/MMV/server.pl
@@ -0,0 +1,101 @@
+#!/usr/bin/perl
+#
+# Example server application that demonstrates use of the
+# Perl PCP MMV module for runtime instrumentation.
+#
+
+use strict;
+use warnings;
+use PCP::MMV;
+use Time::HiRes qw ( usleep );
+
+my @db_instances = ( 0 => "tempdb", 1 => "datadb" );
+
+my $db_indom = 1;
+my @indoms = (
+ [ $db_indom, \@db_instances,
+ 'Database instances',
+ 'An instance domain for each database used by this server.',
+ ],
+);
+
+my @metrics = (
+ [ 'response_time.requests',
+ 1, MMV_TYPE_U64, MMV_INDOM_NULL,
+ mmv_units(0,0,1,0,0,MMV_COUNT_ONE), MMV_SEM_COUNTER,
+ 'Number of server requests processed', ''
+ ],
+ [ 'response_time.total',
+ 2, MMV_TYPE_U64, MMV_INDOM_NULL,
+ mmv_units(0,0,1,0,0,MMV_COUNT_ONE), MMV_SEM_COUNTER,
+ 'Maximum observed response time in milliseconds', ''
+ ],
+ [ 'response_time.maximum',
+ 3, MMV_TYPE_DOUBLE, MMV_INDOM_NULL,
+ mmv_units(0,1,0,0,MMV_TIME_MSEC,0), MMV_SEM_INSTANT,
+ 'Maximum observed response time in milliseconds', ''
+ ],
+ [ 'version',
+ 4, MMV_TYPE_STRING, MMV_INDOM_NULL,
+ mmv_units(0,0,0,0,0,0), MMV_SEM_DISCRETE,
+ 'Version number of the server process', ''
+ ],
+ [ 'database.transactions.count',
+ 5, MMV_TYPE_U64, $db_indom,
+ mmv_units(0,0,1,0,0,MMV_COUNT_ONE), MMV_SEM_COUNTER,
+ 'Number of requests issued to each database', ''
+ ],
+ [ 'database.transactions.time',
+ 6, MMV_TYPE_U64, $db_indom,
+ mmv_units(0,1,0,0,MMV_TIME_MSEC,0), MMV_SEM_COUNTER,
+ 'Total time spent waiting for results from each database', ''
+ ],
+ [ 'idletime',
+ 7, MMV_TYPE_U64, MMV_INDOM_NULL,
+ mmv_units(0,1,0,0,MMV_TIME_USEC,0), MMV_SEM_COUNTER,
+ 'Total time spent asleep, in-between requests', ''
+ ],
+);
+
+my $handle = mmv_stats_init('server', 0, MMV_FLAG_PROCESS, \@metrics, \@indoms);
+die("mmv_stats_init failed\n") unless (defined($handle));
+
+mmv_stats_set_string($handle, 'version', '', '7.4.2-5');
+
+my $maxtime = 0.0; # milliseconds
+
+for (;;) {
+
+ my $idletime = 0.0; # microseconds
+ my $dbtime = 0.0; # milliseconds
+ my $response = 0.0;
+
+
+ # start a request ...
+
+ $dbtime = rand 1000; # milliseconds
+ $response += $dbtime;
+ mmv_stats_inc($handle, 'database.transactions.count', 'tempdb');
+ mmv_stats_add($handle, 'database.transactions.time', 'tempdb', $dbtime);
+
+ # ... more work, involving a second DB request ...
+ $dbtime = rand 1000; # milliseconds
+ $response += $dbtime;
+ mmv_stats_inc($handle, 'database.transactions.count', 'datadb');
+ mmv_stats_add($handle, 'database.transactions.time', 'datadb', $dbtime);
+
+ # ... request completed
+
+
+ $response += rand 42; # milliseconds
+ mmv_stats_inc($handle, 'response_time.requests', '');
+ mmv_stats_add($handle, 'response_time.total', '', $response);
+ if ($response > $maxtime) {
+ $maxtime = $response;
+ mmv_stats_set($handle, 'response_time.maximum', '', $maxtime);
+ }
+
+ $idletime = rand 50000; # microseconds
+ usleep($idletime);
+ mmv_stats_add($handle, 'idletime', '', $idletime);
+}
diff --git a/src/perl/MMV/test.pl b/src/perl/MMV/test.pl
new file mode 100644
index 0000000..15bd1c6
--- /dev/null
+++ b/src/perl/MMV/test.pl
@@ -0,0 +1,25 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+######################### We start with some black magic to print on failure.
+
+BEGIN { $| = 1; print "1..4\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use PCP::MMV;
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
diff --git a/src/perl/MMV/typemap b/src/perl/MMV/typemap
new file mode 100644
index 0000000..85f8812
--- /dev/null
+++ b/src/perl/MMV/typemap
@@ -0,0 +1,10 @@
+######################################################################
+# INPUT/OUTPUT maps
+# O_OBJECT -> links an opaque C object to a blessed Perl object.
+#
+TYPEMAP
+pmAtomValue * T_PTROBJ
+mmv_indom_t * T_PTROBJ
+mmv_metric_t * T_PTROBJ
+
+######################################################################
diff --git a/src/perl/PMDA/Changes b/src/perl/PMDA/Changes
new file mode 100644
index 0000000..0f49bd5
--- /dev/null
+++ b/src/perl/PMDA/Changes
@@ -0,0 +1,101 @@
+Revision history for Perl extension PCP::PMDA.
+
+1.15 Thu Sep 5 11:22:34 EST 2013
+ - Fix a subtle reference counting problem in the hash
+ indom code.
+ - Consistently use unsigned type to index the indom
+ table.
+
+1.14 Tue Jul 10 11:40:14 EST 2012
+ - Add support for hash-based indom handling via PMDA
+ cache interfaces in libpcp_pmda. This introduces a
+ routine for looking up opaque PMDA data from instid
+ (pmda_inst_lookup) suitable for fetch callback use.
+ - Fix pmda_inst_name interface used by dtsrun PMDA -
+ using an incorrect value for indom lookup. This is
+ now consistent with the other instance interfaces,
+ and dtsrun PMDA (not widely used) has been fixed.
+ - Fix instance interface arguments - no current PMDA
+ makes use of this, but if one did (pmdaoracle will)
+ it would be given the internal indom identifier and
+ not the handle given to all other indom interfaces.
+ - Remove unused parameters to internal preinstance()
+ and prefetch() routines, no visible effect for any
+ PMDAs but the wrapper code becomes more readable.
+ - Fix help file handling which (for current gcc/perl
+ combinations at least) was using potentially-freed
+ memory.
+
+1.13 Thu Oct 20 11:50:18 EST 2011
+ - Add an interface to allow a PMDA to drop privileges.
+ - Fix pmda_long and pmda_ulong 32-bit detection.
+
+1.12 Fri Jul 22 11:15:41 EST 2011
+ - Ensure local fetch and instance wrapper routines
+ are always called. Otherwise refresh interface
+ doesn't work, and pmns_refresh may not be called
+ in all situations where it should be.
+
+1.11 Wed May 11 21:04:09 EST 2011
+ - Make file tail non-blocking so it can be used for
+ named pipes as well as regular files.
+
+1.10 Thu Nov 25 03:30:18 EST 2010
+ - Convert to using dynamic namespace interfaces.
+ - Added metric and indom table clearing interfaces.
+
+1.09 Sun Aug 1 09:04:38 EST 2010
+ - Fix memory leak in fetch routine string handling.
+
+1.08 Thu Nov 19 10:37:26 EST 2009
+ - Fix typo on name export for pmda_inst_name.
+ - Add a fast path lookup in pmda_inst_name for direct
+ instance identifier to offset case (common).
+ - When tailing logfiles, seek to end initially so we
+ don't spend potentially copious amounts of time on
+ scanning the entire log file - we only want events
+ that happen after PMDA startup to be counted too.
+
+1.07 Fri Aug 21 09:27:51 EST 2009
+ - Add helper routines to determine native long sizes.
+
+1.06 Wed Jul 22 12:49:43 EST 2009
+ - Ensure the process runs in its own process group,
+ and block SIGTERM in atexit handler. This resolves
+ a regression in the 1.05 fix for reaping children.
+
+1.05 Tue Jul 8 16:19:49 EST 2009
+ - Explicitly free all local temporaries immediately
+ for routines that call Perl from C (fixes memleak).
+ - Fix an off-by-one when handling long input lines.
+ - Terminate any children started by the PMDA at exit.
+
+1.04 Mon Jul 6 11:08:20 EST 2009
+ - Rewrote PMNS file generation to use libpcp routines.
+
+1.03 Wed Jun 10 14:53:05 EST 2009
+ - Incorporated Win32 build changes (paths, etc).
+ - Remove (unneeded) use of hsearch and <search.h>.
+
+1.02 Tue Jun 2 17:16:25 EST 2009
+ - Implemented log file rotation and host reconnect handling.
+ - Generally improved the file "tail" mode of operation.
+
+1.01 Fri Feb 13 17:33:31 EST 2009
+ - Added simple instance name lookup routine.
+
+1.00 Thu Aug 20 08:48:14 EST 2008
+ - Added several API components, 1st stable version.
+
+0.04 Mon Feb 25 15:01:33 EST 2008
+ - Smaller, but still incompatible, API refinements.
+
+0.03 Sun Feb 24 09:06:39 EST 2008
+ - API changes, moved existing Perl PMDAs out into PCP.
+
+0.02 Wed Feb 20 16:38:38 EST 2008
+ - port forward to Perl API changes (circa Perl 5.6).
+
+0.01 Mon Sep 20 09:01:16 EST 1999
+ - original version; created by h2xs 1.18
+
diff --git a/src/perl/PMDA/GNUmakefile b/src/perl/PMDA/GNUmakefile
new file mode 100644
index 0000000..98c6826
--- /dev/null
+++ b/src/perl/PMDA/GNUmakefile
@@ -0,0 +1,67 @@
+#!gmake
+#
+# Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+PERLMOD = PMDA.pm
+INTERFACE = PMDA.xs local.h local.c typemap
+TESTCODE = test.pl cvalue.c
+PERLDOCS = Changes MANIFEST
+LSRCFILES = Makefile.PL $(PERLDOCS) $(PERLMOD) $(INTERFACE) $(TESTCODE)
+
+LPKGDIRT = PCP-PMDA-* MYMETA.yml MYMETA.json
+LDIRT = Makefile COPYING PMDA.bs PMDA.c PMDA.o cvalue pm_to_blib blib \
+ Makefile.old local.o $(LPKGDIRT)
+
+default: dist
+
+MAKEMAKER_OPTIONS = INSTALLDIRS=$(PERL_INSTALLDIRS) INSTALLVENDORMAN3DIR=$(PCP_MAN_DIR)/man3
+INSTALLER_OPTIONS = DESTDIR=$$DIST_ROOT
+
+ifeq ($(TARGET_OS),mingw)
+PERLMAKE = dmake.exe
+else
+PERLMAKE = $(MAKE)
+endif
+
+PMDA.o: Makefile PMDA.xs
+ $(PERLMAKE) -f Makefile
+
+Makefile: COPYING Makefile.PL
+ $(call PERL_MAKE_MAKEFILE)
+
+COPYING:
+ $(LN_S) $(TOPDIR)/COPYING COPYING
+
+test dist: PMDA.o
+ rm -rf $(LPKGDIRT)
+ $(PERLMAKE) -f Makefile $@
+
+include $(BUILDRULES)
+
+install: default
+ifneq "$(PACKAGE_DISTRIBUTION)" "debian"
+ $(call PERL_GET_FILELIST,$(TOPDIR)/perl-pcp-pmda.list,PMDA)
+endif
+
+install_perl:
+ $(PERLMAKE) -f Makefile pure_install $(INSTALLER_OPTIONS)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/perl/PMDA/MANIFEST b/src/perl/PMDA/MANIFEST
new file mode 100644
index 0000000..b4749b6
--- /dev/null
+++ b/src/perl/PMDA/MANIFEST
@@ -0,0 +1,11 @@
+Changes
+COPYING
+cvalue.c
+Makefile.PL
+MANIFEST
+PMDA.pm
+PMDA.xs
+local.h
+local.c
+test.pl
+typemap
diff --git a/src/perl/PMDA/Makefile.PL b/src/perl/PMDA/Makefile.PL
new file mode 100644
index 0000000..1191a02
--- /dev/null
+++ b/src/perl/PMDA/Makefile.PL
@@ -0,0 +1,49 @@
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+my $ldfrom;
+my $inc;
+my $libs;
+my $lddlflags;
+my $cccdlflags;
+
+if ($ENV{TARGET_OS} eq "mingw") {
+ $ldfrom = "-L$ENV{PCP_TOPDIR}/src/libpcp/src -L$ENV{PCP_TOPDIR}/src/libpcp_pmda/src -L$ENV{PCP_DIR}\\local\\bin -lpcp_pmda -lpcp local.o PMDA.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp -I$ENV{PCP_DIR}\\include\\pcp -I$ENV{PCP_DIR}\\c\\include";
+ $libs = ["-L$ENV{PCP_DIR}\\local\\bin", '-lpcp_pmda', '-lpcp'];
+}
+else {
+ $ldfrom = "local.o PMDA.o";
+ $inc = "-I$ENV{PCP_TOPDIR}/src/include/pcp -I/usr/include/pcp";
+ $libs = ["-L$ENV{PCP_TOPDIR}/src/libpcp_pmda/src -L$ENV{PCP_TOPDIR}/src/libpcp/src -lpcp_pmda -lpcp"];
+}
+if ($ENV{TARGET_OS} eq "darwin") {
+ # standard ones, minus -arch ppc
+ $lddlflags = "-arch x86_64 -arch i386 -bundle -undefined dynamic_lookup";
+}
+else {
+ $lddlflags = "-shared \$(OPTIMIZE) \$(LDFLAGS)";
+}
+if ($ENV{TARGET_OS} eq "solaris") {
+ # for OpenSolaris Makefile ends up with -KPIC instead of -fPIC otherwise
+ $cccdlflags = "-fPIC"
+}
+
+WriteMakefile(
+ NAME => 'PCP::PMDA',
+ AUTHOR => 'Nathan Scott <nathans@debian.org>',
+ VERSION_FROM => 'PMDA.pm', # finds $VERSION
+ ABSTRACT_FROM => 'PMDA.pm', # retrieve abstract from module
+ C => ['local.c', 'PMDA.c'],
+ OPTIMIZE => '-g',
+ XSPROTOARG => '-noprototypes',
+ OBJECT => 'local.o PMDA.o',
+ DEFINE => '-DPERLIO_NOT_STDIO=0 -DPCP_VERSION -DPCP_DEBUG',
+ LDFROM => $ldfrom,
+ LDDLFLAGS => $lddlflags,
+ CCCDLFLAGS => $cccdlflags,
+ INC => $inc,
+ LIBS => $libs,
+ CC => $ENV{"CC"},
+ LD => $ENV{"CC"},
+);
diff --git a/src/perl/PMDA/PMDA.pm b/src/perl/PMDA/PMDA.pm
new file mode 100644
index 0000000..d6b1a52
--- /dev/null
+++ b/src/perl/PMDA/PMDA.pm
@@ -0,0 +1,495 @@
+package PCP::PMDA;
+
+use strict;
+use warnings;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
+
+require Exporter;
+require DynaLoader;
+
+@ISA = qw(Exporter DynaLoader);
+@EXPORT = qw(
+ pmda_pmid pmda_pmid_name pmda_pmid_text pmda_inst_name pmda_inst_lookup
+ pmda_units pmda_config pmda_uptime pmda_long pmda_ulong
+ PM_ID_NULL PM_INDOM_NULL PM_IN_NULL
+ PM_SPACE_BYTE PM_SPACE_KBYTE PM_SPACE_MBYTE PM_SPACE_GBYTE PM_SPACE_TBYTE
+ PM_TIME_NSEC PM_TIME_USEC PM_TIME_MSEC PM_TIME_SEC PM_TIME_MIN PM_TIME_HOUR
+ PM_COUNT_ONE
+ PM_TYPE_NOSUPPORT PM_TYPE_32 PM_TYPE_U32 PM_TYPE_64 PM_TYPE_U64
+ PM_TYPE_FLOAT PM_TYPE_DOUBLE PM_TYPE_STRING
+ PM_SEM_COUNTER PM_SEM_INSTANT PM_SEM_DISCRETE
+ PM_ERR_GENERIC PM_ERR_PMNS PM_ERR_NOPMNS PM_ERR_DUPPMNS PM_ERR_TEXT
+ PM_ERR_APPVERSION PM_ERR_VALUE PM_ERR_TIMEOUT
+ PM_ERR_NODATA PM_ERR_RESET PM_ERR_NAME PM_ERR_PMID
+ PM_ERR_INDOM PM_ERR_INST PM_ERR_UNIT PM_ERR_CONV PM_ERR_TRUNC
+ PM_ERR_SIGN PM_ERR_PROFILE PM_ERR_IPC PM_ERR_EOF
+ PM_ERR_NOTHOST PM_ERR_EOL PM_ERR_MODE PM_ERR_LABEL PM_ERR_LOGREC
+ PM_ERR_NOTARCHIVE PM_ERR_NOCONTEXT PM_ERR_PROFILESPEC PM_ERR_PMID_LOG
+ PM_ERR_INDOM_LOG PM_ERR_INST_LOG PM_ERR_NOPROFILE PM_ERR_NOAGENT
+ PM_ERR_PERMISSION PM_ERR_CONNLIMIT PM_ERR_AGAIN PM_ERR_ISCONN
+ PM_ERR_NOTCONN PM_ERR_NEEDPORT PM_ERR_NONLEAF
+ PM_ERR_PMDANOTREADY PM_ERR_PMDAREADY
+ PM_ERR_TOOSMALL PM_ERR_TOOBIG PM_ERR_NYI
+);
+@EXPORT_OK = qw();
+$VERSION = '1.15';
+
+# metric identification
+sub PM_ID_NULL { 0xffffffff; }
+sub PM_INDOM_NULL { 0xffffffff; }
+sub PM_IN_NULL { 0xffffffff; }
+
+# units - space scale
+sub PM_SPACE_BYTE { 0; } # bytes
+sub PM_SPACE_KBYTE { 1; } # kilobytes
+sub PM_SPACE_MBYTE { 2; } # megabytes
+sub PM_SPACE_GBYTE { 3; } # gigabytes
+sub PM_SPACE_TBYTE { 4; } # terabytes
+sub PM_SPACE_PBYTE { 5; } # petabytes
+sub PM_SPACE_EBYTE { 6; } # exabytes
+
+# units - time scale
+sub PM_TIME_NSEC { 0; } # nanoseconds
+sub PM_TIME_USEC { 1; } # microseconds
+sub PM_TIME_MSEC { 2; } # milliseconds
+sub PM_TIME_SEC { 3; } # seconds
+sub PM_TIME_MIN { 4; } # minutes
+sub PM_TIME_HOUR { 5; } # hours
+
+# units - count scale (for metrics such as count events, syscalls,
+# interrupts, etc - these are simply powers of ten and not enumerated here
+# (e.g. 6 for 10^6, or -3 for 10^-3).
+sub PM_COUNT_ONE { 0; } # 1
+
+# data type of metric values
+sub PM_TYPE_NOSUPPORT { 0xffffffff; } # not implemented in this version
+sub PM_TYPE_32 { 0; } # 32-bit signed integer
+sub PM_TYPE_U32 { 1; } # 32-bit unsigned integer
+sub PM_TYPE_64 { 2; } # 64-bit signed integer
+sub PM_TYPE_U64 { 3; } # 64-bit unsigned integer
+sub PM_TYPE_FLOAT { 4; } # 32-bit floating point
+sub PM_TYPE_DOUBLE { 5; } # 64-bit floating point
+sub PM_TYPE_STRING { 6; } # array of characters
+sub PM_TYPE_AGGREGATE { 7; } # arbitrary binary data (aggregate)
+sub PM_TYPE_AGGREGATE_STATIC { 8; } # static pointer to aggregate
+sub PM_TYPE_EVENT { 9; } # packed pmEventArray
+sub PM_TYPE_UNKNOWN { 255; }
+
+# semantics/interpretation of metric values
+sub PM_SEM_COUNTER { 1; } # cumulative counter (monotonic increasing)
+sub PM_SEM_INSTANT { 3; } # instantaneous value, continuous domain
+sub PM_SEM_DISCRETE { 4; } # instantaneous value, discrete domain
+
+# error codes
+sub PM_ERR_GENERIC { -12345; } # Generic error, already reported above
+sub PM_ERR_PMNS { -12346; } # Problems parsing PMNS definitions
+sub PM_ERR_NOPMNS { -12347; } # PMNS not accessible
+sub PM_ERR_DUPPMNS { -12348; } # Attempt to reload the PMNS
+sub PM_ERR_TEXT { -12349; } # Oneline or help text is not available
+sub PM_ERR_APPVERSION { -12350; } # Metric not supported by this version of monitored application
+sub PM_ERR_VALUE { -12351; } # Missing metric value(s)
+sub PM_ERR_TIMEOUT { -12353; } # Timeout waiting for a response from PMCD
+sub PM_ERR_NODATA { -12354; } # Empty archive log file
+sub PM_ERR_RESET { -12355; } # PMCD reset or configuration change
+sub PM_ERR_NAME { -12357; } # Unknown metric name
+sub PM_ERR_PMID { -12358; } # Unknown or illegal metric identifier
+sub PM_ERR_INDOM { -12359; } # Unknown or illegal instance domain identifier
+sub PM_ERR_INST { -12360; } # Unknown or illegal instance identifier
+sub PM_ERR_UNIT { -12361; } # Illegal pmUnits specification
+sub PM_ERR_CONV { -12362; } # Impossible value or scale conversion
+sub PM_ERR_TRUNC { -12363; } # Truncation in value conversion
+sub PM_ERR_SIGN { -12364; } # Negative value in conversion to unsigned
+sub PM_ERR_PROFILE { -12365; } # Explicit instance identifier(s) required
+sub PM_ERR_IPC { -12366; } # IPC protocol failure
+sub PM_ERR_EOF { -12368; } # IPC channel closed
+sub PM_ERR_NOTHOST { -12369; } # Operation requires context with host source of metrics
+sub PM_ERR_EOL { -12370; } # End of PCP archive log
+sub PM_ERR_MODE { -12371; } # Illegal mode specification
+sub PM_ERR_LABEL { -12372; } # Illegal label record at start of a PCP archive log file
+sub PM_ERR_LOGREC { -12373; } # Corrupted record in a PCP archive log
+sub PM_ERR_NOTARCHIVE { -12374; } # Operation requires context with archive source of metrics
+sub PM_ERR_NOCONTEXT { -12376; } # Attempt to use an illegal context
+sub PM_ERR_PROFILESPEC { -12377; } # NULL pmInDom with non-NULL instlist
+sub PM_ERR_PMID_LOG { -12378; } # Metric not defined in the PCP archive log
+sub PM_ERR_INDOM_LOG { -12379; } # Instance domain identifier not defined in the PCP archive log
+sub PM_ERR_INST_LOG { -12380; } # Instance identifier not defined in the PCP archive log
+sub PM_ERR_NOPROFILE { -12381; } # Missing profile - protocol botch
+sub PM_ERR_NOAGENT { -12386; } # No PMCD agent for domain of request
+sub PM_ERR_PERMISSION { -12387; } # No permission to perform requested operation
+
+sub PM_ERR_CONNLIMIT { -12388; } # PMCD connection limit for this host exceeded
+sub PM_ERR_AGAIN { -12389; } # Try again. Information not currently available
+sub PM_ERR_ISCONN { -12390; } # Already Connected
+sub PM_ERR_NOTCONN { -12391; } # Not Connected
+sub PM_ERR_NEEDPORT { -12392; } # A non-null port name is required
+sub PM_ERR_NONLEAF { -12394; } # Metric name is not a leaf in PMNS
+sub PM_ERR_PMDANOTREADY { -13394; } # PMDA is not yet ready to respond to requests
+sub PM_ERR_PMDAREADY { -13393; } # PMDA is now responsive to requests
+sub PM_ERR_TOOSMALL { -12443; } # Insufficient elements in list
+sub PM_ERR_TOOBIG { -12444; } # Result size exceeded
+sub PM_ERR_NYI { -21344; } # Functionality not yet implemented
+
+
+bootstrap PCP::PMDA $VERSION;
+
+1;
+__END__
+
+=head1 NAME
+
+PCP::PMDA - Perl extension for Performance Metrics Domain Agents
+
+=head1 SYNOPSIS
+
+ use PCP::PMDA;
+
+ $pmda = PCP::PMDA->new('myname', $MYDOMAIN);
+
+ $pmda->connect_pmcd;
+
+ $pmda->add_metric($pmid, $type, $indom, $sem, $units, 'name', '', '');
+ $pmda->add_indom($indom, [0 => 'white', 1 => 'black', ...], '', '');
+
+ $pmda->set_fetch(\&fetch_method);
+ $pmda->set_refresh(\&refresh_method);
+ $pmda->set_instance(\&instance_method);
+ $pmda->set_fetch_callback(\&fetch_callback_method);
+ $pmda->set_store_callback(\&store_callback_method);
+
+ $pmda->set_user('pcp');
+
+ $pmda->run;
+
+=head1 DESCRIPTION
+
+The PCP::PMDA Perl module contains the language bindings for
+building Performance Metric Domain Agents (PMDAs) using Perl.
+Each PMDA exports performance data for one specific domain, for
+example the operating system kernel, Cisco routers, a database,
+an application, etc.
+
+=head1 METHODS
+
+=over
+
+=item PCP::PMDA->new(name, domain)
+
+PCP::PMDA class constructor. I<name> is a string that becomes the
+name of the PMDA for messages and default prefix for the names of
+external files used by the PMDA. I<domain> is an integer domain
+number for the PMDA, usually from the register of domain numbers
+found in B<$PCP_VAR_DIR/pmns/stdpmid>.
+
+=item $pmda->run()
+
+Once all local setup is complete (i.e. instance domains and metrics
+are registered, callbacks registered - as discussed below) the PMDA
+must connect to B<pmcd>(1) to complete its initialisation and begin
+answering client requests for its metrics. This is the role performed
+by I<run>, and upon invoking it all interaction within the PMDA is
+done via callback routines (that is to say, under normal cicrumstances,
+the I<run> routine does not return).
+
+The behaviour of the I<run> method is different in the presence of
+either the B<PCP_PERL_PMNS> or B<PCP_PERL_DOMAIN> environment variables.
+These can be used to generate the namespace or domain number files,
+which are used as part of the PMDA installation process.
+
+=item $pmda->connect_pmcd()
+
+Allows the PMDA to set up the IPC channel to B<pmcd>(1) and complete
+the credentials handshake with B<pmcd>(1). If I<connect_pmcd> is not
+explicitly called the setup and handshake will be done when the
+I<run> method is called.
+
+The advantage of explicitly calling I<connect_pmcd> early in the life
+of the PMDA is that this reduces the risk of a fatal timeout during
+the credentials handshake, which may be an issue if the PMDA has
+considerable work to do, e.g. determining which metrics and
+instance domains are available, before calling I<run>.
+
+=item $pmda->add_indom(indom, insts, help, longhelp)
+
+Define a new instance domain. The instance domain identifier is
+I<indom>, which is an integer and unique across all instance domains
+for single PMDA.
+
+The instances of the instance domain are defined by I<insts> which
+can be specified as either a list or a hash.
+
+In list form, the contents of the list must provide consecutive pairs
+of identifier (a integer, unique across all instances in the instance
+domain) and external instance name (a string, must by unique up to the
+first space, if any, across all instances in the instance domain).
+For example:
+
+ @colours = [0 => 'red', 1 => 'green', 2 => 'blue'];
+
+In hash form, the external instance identifier (string) is used as the
+hash key. An arbitrary value can be stored along with the key (this
+value is often used as a convenient place to hold the latest value for
+each metric instance, for example).
+
+ %timeslices = ('sec' => 42, 'min' => \&min_func, 'hour' => '0');
+
+The I<help> and I<longhelp> strings are interpreted as the one-line and
+expanded help text to be used for this instance domain as further
+described in B<pmLookupInDomText>(3).
+
+Refer also to the B<replace_indom>() discussion below for further details
+about manipulating instance domains.
+
+=item $pmda->add_metric(pmid, type, indom, sem, units, name, help, longhelp)
+
+Define a new metric identified by the PMID I<pmid> and the full
+metric name I<name>.
+
+The metric's metadata is defined by I<type>, I<indom>, I<sem> and
+I<units> and these parameters are used to set up the I<pmDesc>
+structure as described in B<pmLookupDesc>(3).
+
+The I<help> and I<longhelp> strings are interpreted as the one-line
+and expanded help text to be used for the metric as further described
+in B<pmLookupText>(3).
+
+=item $pmda->replace_indom(index, insts)
+
+Whenever an instance domain identified by I<index>,
+previously registered using B<add_indom>(),
+changes in any way, this change must be reflected by replacing the
+existing mapping with a new one (I<insts>).
+
+The replacement mapping must be a hash if the instance domain
+was registered initially with B<add_indom>() as a hash, otherwise it must be
+a list.
+
+Refer to the earlier B<add_indom>() discussion concerning these two
+different types of instance domains definitions.
+
+=item $pmda->add_pipe(command, callback, data)
+
+Allow data to be injected into the PMDA using a B<pipe>(2).
+
+The given I<command> is run early in the life of the PMDA, and a pipe
+is formed between the PMDA and the I<command>. Line-oriented output
+is assumed (else truncation will occur), and on receipt of each line
+of text on the pipe, the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+=item $pmda->add_sock(hostname, port, callback, data)
+
+Create a B<socket>(2) connection to the I<hostname>, I<port> pair.
+Whenever data arrives (as above, a line-oriented protocol is best)
+the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+An opaque integer-sized identifier for the socket will be returned,
+which can later be used in calls to B<put_sock>() as discussed below.
+
+=item $pmda->put_sock(id, output)
+
+Write an I<output> string to the socket identified by I<id>, which
+must refer to a socket previously registered using B<add_sock>().
+
+=item $pmda->add_tail(filename, callback, data)
+
+Monitor the given I<filename> for the arrival of newly appended
+information. Line-oriented input is assumed (else truncation
+will occur), and on receipt of each line of text on the pipe,
+the I<callback> function will be called.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+This interface deals with the issue of the file being renamed (such
+as on daily log file rotation), and will attempt to automatically
+re-route information from the new log file if this occurs.
+
+=item $pmda->add_timer(timeout, callback, data)
+
+Registers a timer with the PMDA, such that on expiry of a I<timeout>
+a I<callback> routine will be called. This is a repeating timer.
+
+The optional I<data> parameter can be used to specify extra data to
+pass into the I<callback> routine.
+
+=item $pmda->err(message)
+
+Report a timestamped error message into the PMDA log file.
+
+=item $pmda->error(message)
+
+Report a timestamped error message into the PMDA log file.
+
+=item $pmda->log(message)
+
+Report a timestamped informational message into the PMDA log file.
+
+=item $pmda->set_fetch_callback(cb_function)
+
+Register a callback function akin to B<pmdaSetFetchCallBack>(3).
+
+=item $pmda->set_fetch(function)
+
+Register a fetch function, as used by B<pmdaInit>(3).
+
+=item $pmda->set_instance(function)
+
+Register an instance function, as used by B<pmdaInit>(3).
+
+=item $pmda->set_refresh(function)
+
+Register a refresh function, which will be called once per metric
+cluster, during the fetch operation. Only clusters being requested
+during this fetch will be refreshed, allowing selective metric value
+updates within the PMDA.
+
+=item $pmda->set_store_callback(cb_function)
+
+Register an store function, used indirectly by B<pmdaInit>(3).
+The I<cb_function> is called once for each metric/instance pair
+into which a B<pmStore>(3) is performed.
+
+=item $pmda->set_inet_socket(port)
+
+Specify the IPv4 socket I<port> to be used to communicate with B<pmcd>(1).
+
+=item $pmda->set_ipv6_socket(port)
+
+Specify the IPv6 socket I<port> to be used to communicate with B<pmcd>(1).
+
+=item $pmda->set_unix_socket(socket_name)
+
+Specify the filesystem I<socket_name> path to be used for communication
+with B<pmcd>(1).
+
+=item $pmda->set_user(username)
+
+Run the PMDA under the I<username> user account, instead of the
+default (root) user.
+
+=back
+
+=head1 HELPER METHODS
+
+=over
+
+=item pmda_pmid(cluster, item)
+
+Construct a Performance Metric Identifier (PMID) from the domain
+number (passed as an argument to the I<new> constructor), the
+I<cluster> (an integer in the range 0 to 2^12-1) and the
+I<item> (an integer in the range 0 to 2^10-1).
+
+Every performance metric exported from a PMDA must have a unique
+PMID.
+
+=item pmda_pmid_name(cluster, item)
+
+Perform a reverse metric identifier to name lookup - given the metric
+I<cluster> and I<item> numbers, returns the metric name string.
+
+=item pmda_pmid_text(cluster, item)
+
+Returns the one-line metric help text string - given the metric
+I<cluster> and I<item> numbers, returns the help text string.
+
+=item pmda_inst_name(index, instance)
+
+Perform a reverse instance identifier to instance name lookup
+for the instance domain identified by I<index>.
+Given the
+internal I<instance> identifier, returns the external instance name string.
+
+=item pmda_inst_lookup(index, instance)
+
+Given an internal I<instance> identifier (key) for the
+instance domain identified by I<index> with an associated indom hash,
+return the value associated with that key.
+The value can be any scalar value (this includes references, of course,
+so complex data structures can be referenced).
+
+=item pmda_units(dim_space, dim_time, dim_count, scale_space, scale_time, scale_count)
+
+Construct a B<pmUnits> structure suitable for registering a metrics metadata
+via B<add_metric>().
+
+=item pmda_config(name)
+
+Lookup the value for configuration variable I<name> from the
+I</etc/pcp.conf> file,
+using B<pmGetConfig>(3).
+
+=item pmda_uptime(now)
+
+Return a human-readable uptime string, based on I<now> seconds since the epoch.
+
+=item pmda_long()
+
+Return either PM_TYPE_32 or PM_TYPE_64 depending on the platform size for a
+signed long integer.
+
+=item pmda_ulong()
+
+Return either PM_TYPE_U32 or PM_TYPE_U64 depending on the platform size for an
+unsigned long integer.
+
+=back
+
+=head1 MACROS
+
+Most of the PM_* macros from the PCP C headers are available.
+
+For example the I<type> of a metric's value may be directly
+specified as one of
+B<PM_TYPE_32>, B<PM_TYPE_U32>, B<PM_TYPE_64>, B<PM_TYPE_U64>,
+B<PM_TYPE_FLOAT>, B<PM_TYPE_DOUBLE>, B<PM_TYPE_STRING> or
+B<PM_TYPE_NOSUPPORT>.
+
+=head1 DEBUGGING
+
+Perl PMDAs do not follow the B<-D> convention of other PCP applications
+for enabling run-time diagnostics and tracing. Rather the environment
+variable B<PCP_PERL_DEBUG> needs to be set to a string value matching
+the syntax accepted for the option value for B<-D> elsewhere, see
+B<__pmParseDebug>(3).
+
+This requires a little trickery. The B<pmcd>(1) configuration file
+(B<PCP_PMCDCONF_PATH> from I</etc/pcp.conf>) needs hand editing.
+This is best demonstrated by example.
+
+Replace this line
+
+ foo 242 pipe binary python /somepath/foo.py
+
+with
+
+ foo 242 pipe binary python \
+ sh -c "PCP_PERL_DEBUG=pdu,fetch /usr/bin/python /somepath/foo.py"
+
+=head1 SEE ALSO
+
+perl(1) and PCPIntro(1).
+
+The PCP mailing list pcp@mail.performancecopilot.org can be used for
+questions about this module.
+
+Further details can be found at http://www.performancecopilot.org/
+
+=head1 AUTHOR
+
+The Performance Co-Pilot development team.
+
+Copyright (C) 2014 Red Hat.
+Copyright (C) 2008-2010 Aconex.
+Copyright (C) 2004 Silicon Graphics, Inc.
+
+This library is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License, version 2 (see
+the "COPYING" file in the PCP source tree for further details).
+
+=cut
diff --git a/src/perl/PMDA/PMDA.xs b/src/perl/PMDA/PMDA.xs
new file mode 100644
index 0000000..a6d16b2
--- /dev/null
+++ b/src/perl/PMDA/PMDA.xs
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2008-2012 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "local.h"
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+static pmdaInterface dispatch;
+static pmdaMetric *metrictab;
+static int mtab_size;
+static __pmnsTree *pmns;
+static int need_refresh;
+static pmdaIndom *indomtab;
+static int itab_size;
+static int *clustertab;
+static int ctab_size;
+
+static HV *metric_names;
+static HV *metric_oneline;
+static HV *metric_helptext;
+static HV *indom_helptext;
+static HV *indom_oneline;
+
+static SV *fetch_func;
+static SV *refresh_func;
+static SV *instance_func;
+static SV *store_cb_func;
+static SV *fetch_cb_func;
+
+int
+clustertab_lookup(int cluster)
+{
+ int i, found = 0;
+
+ for (i = 0; i < ctab_size; i++) {
+ if (cluster == clustertab[i]) {
+ found = 1;
+ break;
+ }
+ }
+ return found;
+}
+
+void
+clustertab_replace(int index, int cluster)
+{
+ if (index >= 0 && index < ctab_size)
+ clustertab[index] = cluster;
+ else
+ warn("invalid cluster table replacement requested");
+}
+
+void
+clustertab_scratch()
+{
+ memset(clustertab, -1, sizeof(int) * ctab_size);
+}
+
+void
+clustertab_refresh(int index)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(clustertab[index])));
+ PUTBACK;
+
+ perl_call_sv(refresh_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+void
+refresh(int numpmid, pmID *pmidlist)
+{
+ int i, numclusters = 0;
+ __pmID_int *pmid;
+
+ /* Create list of affected clusters from pmidlist
+ * Note: we overwrite the initial cluster array here, to avoid
+ * allocating memory. The initial array contains all possible
+ * clusters whereas we (possibly) construct a subset here. We
+ * do not touch ctab_size at all, however, which lets us reuse
+ * the preallocated array space on every fetch.
+ */
+ clustertab_scratch();
+ for (i = 0; i < numpmid; i++) {
+ pmid = (__pmID_int *) &pmidlist[i];
+ if (clustertab_lookup(pmid->cluster) == 0)
+ clustertab_replace(numclusters++, pmid->cluster);
+ }
+
+ /* For each unique cluster, call the cluster refresh method */
+ for (i = 0; i < numclusters; i++)
+ clustertab_refresh(i);
+}
+
+void
+pmns_refresh(void)
+{
+ char *pmid, *next;
+ I32 idsize;
+ SV *metric;
+ int sts;
+
+ if (pmns)
+ __pmFreePMNS(pmns);
+
+ if ((sts = __pmNewPMNS(&pmns)) < 0)
+ croak("failed to create namespace root: %s", pmErrStr(sts));
+
+ hv_iterinit(metric_names);
+ while ((metric = hv_iternextsv(metric_names, &pmid, &idsize)) != NULL) {
+ unsigned int domain, cluster, item, id;
+
+ domain = strtoul(pmid, &next, 10);
+ cluster = strtoul(next+1, &next, 10);
+ item = strtoul(next+1, &next, 10);
+ id = pmid_build(domain, cluster, item);
+ if ((sts = __pmAddPMNSNode(pmns, id, SvPV_nolen(metric))) < 0)
+ croak("failed to add metric %s(%s) to namespace: %s",
+ SvPV_nolen(metric), pmIDStr(id), pmErrStr(sts));
+ }
+
+ pmdaTreeRebuildHash(pmns, mtab_size); /* for reverse (pmid->name) lookups */
+ need_refresh = 0;
+}
+
+int
+pmns_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaDesc(pmid, desc, ep);
+}
+
+int
+pmns_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+int
+pmns_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+int
+pmns_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+void
+pmns_write(void)
+{
+ __pmnsNode *node;
+ char *pppenv = getenv("PCP_PERL_PMNS");
+ int root = pppenv ? strcmp(pppenv, "root") == 0 : 0;
+ char *prefix = root ? "\t" : "";
+
+ pmns_refresh();
+
+ if (root)
+ printf("root {\n");
+ for (node = pmns->root->first; node != NULL; node = node->next)
+ printf("%s%s\t%u:*:*\n", prefix, node->name, dispatch.domain);
+ if (root)
+ printf("}\n");
+}
+
+void
+domain_write(void)
+{
+ char *p, name[512] = { 0 };
+ int i, len = strlen(pmProgname);
+
+ if (len >= sizeof(name) - 1)
+ len = sizeof(name) - 2;
+ p = pmProgname;
+ if (strncmp(pmProgname, "pmda", 4) == 0)
+ p += 4;
+ for (i = 0; i < len; i++)
+ name[i] = toupper(p[i]);
+ printf("#define %s %u\n", name, dispatch.domain);
+}
+
+void
+prefetch(void)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ PUTBACK;
+
+ perl_call_sv(fetch_func, G_VOID|G_NOARGS);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+fetch(int numpmid, pmID *pmidlist, pmResult **rp, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ if (fetch_func)
+ prefetch();
+ if (refresh_func)
+ refresh(numpmid, pmidlist);
+ return pmdaFetch(numpmid, pmidlist, rp, pmda);
+}
+
+int
+instance_index(pmInDom indom)
+{
+ int i;
+
+ for (i = 0; i < itab_size; i++)
+ if (indomtab[i].it_indom == indom)
+ return i;
+ return PM_INDOM_NULL;
+}
+
+void
+preinstance(pmInDom indom)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(indom)));
+ PUTBACK;
+
+ perl_call_sv(instance_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+instance(pmInDom indom, int a, char *b, __pmInResult **rp, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ if (instance_func)
+ preinstance(instance_index(indom));
+ return pmdaInstance(indom, a, b, rp, pmda);
+}
+
+void
+timer_callback(int afid, void *data)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSViv(local_timer_get_cookie(afid))));
+ PUTBACK;
+
+ perl_call_sv(local_timer_get_callback(afid), G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+void
+input_callback(SV *input_cb_func, int data, char *string)
+{
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSViv(data)));
+ XPUSHs(sv_2mortal(newSVpv(string,0)));
+ PUTBACK;
+
+ perl_call_sv(input_cb_func, G_VOID);
+
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+int
+fetch_callback(pmdaMetric *metric, unsigned int inst, pmAtomValue *atom)
+{
+ dSP;
+ __pmID_int *pmid;
+ int sts;
+ STRLEN n_a; /* required by older Perl versions, used in POPpx */
+
+ ENTER;
+ SAVETMPS; /* allows us to tidy our perl stack changes later */
+
+ (void)n_a;
+ pmid = (__pmID_int *) &metric->m_desc.pmid;
+
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(pmid->cluster)));
+ XPUSHs(sv_2mortal(newSVuv(pmid->item)));
+ XPUSHs(sv_2mortal(newSVuv(inst)));
+ PUTBACK;
+
+ sts = perl_call_sv(fetch_cb_func, G_ARRAY);
+ SPAGAIN; /* refresh local perl stack pointer after call */
+ if (sts != 2) {
+ croak("fetch CB error (returned %d values, expected 2)", sts);
+ sts = -EINVAL;
+ goto fetch_end;
+ }
+ sts = POPi; /* pop function return status */
+ if (sts < 0) {
+ goto fetch_end;
+ }
+ else if (sts == 0) {
+ sts = POPi;
+ goto fetch_end;
+ }
+
+ sts = PMDA_FETCH_STATIC;
+ switch (metric->m_desc.type) { /* pop result value */
+ case PM_TYPE_32: atom->l = POPi; break;
+ case PM_TYPE_U32: atom->ul = POPi; break;
+ case PM_TYPE_64: atom->ll = POPl; break;
+ case PM_TYPE_U64: atom->ull = POPl; break;
+ case PM_TYPE_FLOAT: atom->f = POPn; break;
+ case PM_TYPE_DOUBLE: atom->d = POPn; break;
+ case PM_TYPE_STRING: {
+ atom->cp = strdup(POPpx);
+ sts = PMDA_FETCH_DYNAMIC;
+ break;
+ }
+ }
+
+fetch_end:
+ PUTBACK;
+ FREETMPS;
+ LEAVE; /* fix up the perl stack, freeing anything we created */
+ return sts;
+}
+
+int
+store_callback(__pmID_int *pmid, unsigned int inst, pmAtomValue av, int type)
+{
+ dSP;
+ int sts;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(sp);
+ XPUSHs(sv_2mortal(newSVuv(pmid->cluster)));
+ XPUSHs(sv_2mortal(newSVuv(pmid->item)));
+ XPUSHs(sv_2mortal(newSVuv(inst)));
+ switch (type) {
+ case PM_TYPE_32: XPUSHs(sv_2mortal(newSViv(av.l))); break;
+ case PM_TYPE_U32: XPUSHs(sv_2mortal(newSVuv(av.ul))); break;
+ case PM_TYPE_64: XPUSHs(sv_2mortal(newSVuv(av.ll))); break;
+ case PM_TYPE_U64: XPUSHs(sv_2mortal(newSVuv(av.ull))); break;
+ case PM_TYPE_FLOAT: XPUSHs(sv_2mortal(newSVnv(av.f))); break;
+ case PM_TYPE_DOUBLE: XPUSHs(sv_2mortal(newSVnv(av.d))); break;
+ case PM_TYPE_STRING: XPUSHs(sv_2mortal(newSVpv(av.cp,0)));break;
+ }
+ PUTBACK;
+
+ sts = perl_call_sv(store_cb_func, G_SCALAR);
+ SPAGAIN; /* refresh local perl stack pointer after call */
+ if (sts != 1) {
+ croak("store CB error (returned %d values, expected 1)", sts);
+ sts = -EINVAL;
+ goto store_end;
+ }
+ sts = POPi; /* pop function return status */
+
+store_end:
+ PUTBACK;
+ FREETMPS;
+ LEAVE; /* fix up the perl stack, freeing anything we created */
+ return sts;
+}
+
+int
+store(pmResult *result, pmdaExt *pmda)
+{
+ int i, j;
+ int type;
+ int sts;
+ pmAtomValue av;
+ pmValueSet *vsp;
+ __pmID_int *pmid;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmid = (__pmID_int *)&vsp->pmid;
+
+ /* need to find the type associated with this PMID */
+ for (j = 0; j < mtab_size; j++)
+ if (metrictab[j].m_desc.pmid == *(pmID *)pmid)
+ break;
+ if (j == mtab_size)
+ return PM_ERR_PMID;
+ type = metrictab[j].m_desc.type;
+
+ for (j = 0; j < vsp->numval; j++) {
+ sts = pmExtractValue(vsp->valfmt, &vsp->vlist[j],type, &av, type);
+ if (sts < 0)
+ return sts;
+ sts = store_callback(pmid, vsp->vlist[j].inst, av, type);
+ if (sts < 0)
+ return sts;
+ }
+ }
+ return 0;
+}
+
+int
+text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ const char *hash;
+ int size;
+ SV **sv;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ hash = pmIDStr((pmID)ident);
+ size = strlen(hash);
+ if (type & PM_TEXT_ONELINE)
+ sv = hv_fetch(metric_oneline, hash, size, 0);
+ else
+ sv = hv_fetch(metric_helptext, hash, size, 0);
+ }
+ else {
+ hash = pmInDomStr((pmInDom)ident);
+ size = strlen(hash);
+ if (type & PM_TEXT_ONELINE)
+ sv = hv_fetch(indom_oneline, hash, size, 0);
+ else
+ sv = hv_fetch(indom_helptext, hash, size, 0);
+ }
+
+ if (sv && (*sv))
+ *buffer = SvPV_nolen(*sv);
+ return (*buffer == NULL) ? PM_ERR_TEXT : 0;
+}
+
+/*
+ * Converts Perl hash ref like {'foo' => \&data, 'boo' => \&data}
+ * into an instance structure (indom).
+ */
+static int
+update_hash_indom(SV *insts, pmInDom indom)
+{
+ int sts;
+ SV *data;
+ I32 instsize;
+ char *instance;
+ HV *ihash = (HV *) SvRV(insts);
+
+ sts = pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+ if (sts < 0)
+ warn("pmda cache inactivation failed: %s", pmErrStr(sts));
+
+ hv_iterinit(ihash);
+ while ((data = hv_iternextsv(ihash, &instance, &instsize)) != NULL)
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, instance, SvREFCNT_inc(data));
+
+ sts = pmdaCacheOp(indom, PMDA_CACHE_SAVE);
+ if (sts < 0)
+ warn("pmda cache persistance failed: %s", pmErrStr(sts));
+
+ return 0;
+}
+
+/*
+ * Free all memory associated with a Perl list based indom
+ */
+static void
+release_list_indom(pmdaInstid *instances, int numinst)
+{
+ int i;
+
+ if (instances && numinst > 0) {
+ for (i = 0; i < numinst; i++)
+ free(instances[i].i_name); /* update_list_indom strdup */
+ free(instances); /* update_list_indom calloc */
+ }
+}
+
+/*
+ * Converts Perl list ref like [a => 'foo', b => 'boo'] into an indom.
+ */
+static int
+update_list_indom(SV *insts, pmdaInstid **set)
+{
+ int i, len;
+ SV **id;
+ SV **name;
+ AV *ilist = (AV *) SvRV(insts);
+ pmdaInstid *instances;
+
+ if ((len = av_len(ilist)) == -1) { /* empty */
+ *set = NULL;
+ return 0;
+ }
+ if (len++ % 2 == 0) {
+ warn("invalid instance list (length must be a multiple of 2)");
+ return -1;
+ }
+
+ len /= 2;
+ instances = (pmdaInstid *) calloc(len, sizeof(pmdaInstid));
+ if (instances == NULL) {
+ warn("insufficient memory for instance array");
+ return -1;
+ }
+ for (i = 0; i < len; i++) {
+ id = av_fetch(ilist,i*2,0);
+ name = av_fetch(ilist,i*2+1,0);
+ instances[i].i_inst = SvIV(*id);
+ instances[i].i_name = strdup(SvPV_nolen(*name));
+ if (instances[i].i_name == NULL) {
+ release_list_indom(instances, i);
+ warn("insufficient memory for instance array names");
+ return -1;
+ }
+ }
+ *set = instances;
+ return len;
+}
+
+/*
+ * Converts a Perl instance reference into a populated indom.
+ * This interface handles either the hash or list formats.
+ */
+static int
+update_indom(SV *insts, pmInDom indom, pmdaInstid **set)
+{
+ SV *rv = (SV *) SvRV(insts);
+ pmdaInstid *instances;
+
+ if (! SvROK(insts)) {
+ warn("expected a reference for instances argument");
+ return -1;
+ }
+ if (SvTYPE(rv) == SVt_PVAV)
+ return update_list_indom(insts, set);
+ if (SvTYPE(rv) == SVt_PVHV)
+ return update_hash_indom(insts, indom);
+ warn("instance argument is neither an array nor hash reference");
+ return -1;
+}
+
+
+MODULE = PCP::PMDA PACKAGE = PCP::PMDA
+
+
+pmdaInterface *
+new(CLASS,name,domain)
+ char * CLASS
+ char * name
+ int domain
+ PREINIT:
+ int sep;
+ char * p;
+ char * logfile;
+ char * pmdaname;
+ char helpfile[256];
+ CODE:
+ pmProgname = name;
+ RETVAL = &dispatch;
+ logfile = local_strdup_suffix(name, ".log");
+ pmdaname = local_strdup_prefix("pmda", name);
+ __pmSetProgname(pmdaname);
+ sep = __pmPathSeparator();
+ if ((p = getenv("PCP_PERL_DEBUG")) != NULL)
+ if ((pmDebug = __pmParseDebug(p)) < 0)
+ pmDebug = 0;
+#ifndef IS_MINGW
+ setsid();
+#endif
+ atexit(&local_atexit);
+ snprintf(helpfile, sizeof(helpfile), "%s%c%s%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, name, sep);
+ if (access(helpfile, R_OK) != 0) {
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmdaname, domain,
+ logfile, NULL);
+ dispatch.version.four.text = text;
+ }
+ else {
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmdaname, domain,
+ logfile, strdup(helpfile));
+ }
+ dispatch.version.four.fetch = fetch;
+ dispatch.version.four.instance = instance;
+ dispatch.version.four.desc = pmns_desc;
+ dispatch.version.four.pmid = pmns_pmid;
+ dispatch.version.four.name = pmns_name;
+ dispatch.version.four.children = pmns_children;
+
+ if (!getenv("PCP_PERL_PMNS") && !getenv("PCP_PERL_DOMAIN")) {
+ pmdaOpenLog(&dispatch);
+ }
+ metric_names = newHV();
+ metric_oneline = newHV();
+ metric_helptext = newHV();
+ indom_helptext = newHV();
+ indom_oneline = newHV();
+ OUTPUT:
+ RETVAL
+
+int
+pmda_pmid(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ CODE:
+ RETVAL = pmid_build(dispatch.domain, cluster, item);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_pmid_name(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ PREINIT:
+ const char *name;
+ SV **rval;
+ CODE:
+ name = pmIDStr(pmid_build(dispatch.domain, cluster, item));
+ rval = hv_fetch(metric_names, name, strlen(name), 0);
+ if (!rval || !(*rval))
+ XSRETURN_UNDEF;
+ RETVAL = newSVsv(*rval);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_pmid_text(cluster,item)
+ unsigned int cluster
+ unsigned int item
+ PREINIT:
+ const char *name;
+ SV **rval;
+ CODE:
+ name = pmIDStr(pmid_build(dispatch.domain, cluster, item));
+ rval = hv_fetch(metric_oneline, name, strlen(name), 0);
+ if (!rval || !(*rval))
+ XSRETURN_UNDEF;
+ RETVAL = newSVsv(*rval);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_inst_name(index,instance)
+ unsigned int index
+ int instance
+ PREINIT:
+ int i;
+ pmdaIndom * p;
+ pmdaInstid *instp;
+ CODE:
+ if (index >= itab_size) /* is this a valid indom */
+ XSRETURN_UNDEF;
+ p = indomtab + index;
+ if (!p->it_set) /* was this indom previously setup via a hash? */
+ XSRETURN_UNDEF;
+
+ /* Optimistic (fast) direct lookup first, then iterate */
+ i = instance;
+ if (i > p->it_numinst || i < 0 || instance != p->it_set[i].i_inst) {
+ for (i = 0; i < p->it_numinst; i++)
+ if (instance == p->it_set[i].i_inst)
+ break;
+ if (i == p->it_numinst)
+ XSRETURN_UNDEF;
+ }
+ RETVAL = newSVpv(p->it_set[i].i_name,0);
+ OUTPUT:
+ RETVAL
+
+SV *
+pmda_inst_lookup(index,instance)
+ unsigned int index
+ int instance
+ PREINIT:
+ pmdaIndom * p;
+ SV * svp;
+ int i, sts;
+ CODE:
+ if (index >= itab_size) /* is this a valid indom */
+ XSRETURN_UNDEF;
+ p = indomtab + index;
+ if (p->it_set) /* was this indom previously setup via an array? */
+ XSRETURN_UNDEF;
+ sts = pmdaCacheLookup(p->it_indom, instance, NULL, (void *)&svp);
+ if (sts != PMDA_CACHE_ACTIVE)
+ XSRETURN_UNDEF;
+ RETVAL = SvREFCNT_inc(svp);
+ OUTPUT:
+ RETVAL
+
+int
+pmda_units(dim_space,dim_time,dim_count,scale_space,scale_time,scale_count)
+ unsigned int dim_space
+ unsigned int dim_time
+ unsigned int dim_count
+ unsigned int scale_space
+ unsigned int scale_time
+ unsigned int scale_count
+ PREINIT:
+ pmUnits units;
+ CODE:
+ units.pad = 0;
+ units.dimSpace = dim_space; units.scaleSpace = scale_space;
+ units.dimTime = dim_time; units.scaleTime = scale_time;
+ units.dimCount = dim_count; units.scaleCount = scale_count;
+ RETVAL = *(int *)(&units);
+ OUTPUT:
+ RETVAL
+
+char *
+pmda_config(name)
+ char * name
+ CODE:
+ RETVAL = pmGetConfig(name);
+ if (!RETVAL)
+ XSRETURN_UNDEF;
+ OUTPUT:
+ RETVAL
+
+char *
+pmda_uptime(now)
+ int now
+ PREINIT:
+ static char s[32];
+ size_t sz = sizeof(s);
+ int days, hours, mins, secs;
+ CODE:
+ days = now / (60 * 60 * 24);
+ now %= (60 * 60 * 24);
+ hours = now / (60 * 60);
+ now %= (60 * 60);
+ mins = now / 60;
+ now %= 60;
+ secs = now;
+
+ if (days > 1)
+ snprintf(s, sz, "%ddays %02d:%02d:%02d", days, hours, mins, secs);
+ else if (days == 1)
+ snprintf(s, sz, "%dday %02d:%02d:%02d", days, hours, mins, secs);
+ else
+ snprintf(s, sz, "%02d:%02d:%02d", hours, mins, secs);
+ RETVAL = s;
+ OUTPUT:
+ RETVAL
+
+int
+pmda_long()
+ CODE:
+ RETVAL = (sizeof(long) == 4) ? PM_TYPE_32 : PM_TYPE_64;
+ OUTPUT:
+ RETVAL
+
+int
+pmda_ulong()
+ CODE:
+ RETVAL = (sizeof(unsigned long) == 4) ? PM_TYPE_32 : PM_TYPE_64;
+ OUTPUT:
+ RETVAL
+
+
+void
+error(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_ERR, "%s", message);
+
+int
+set_user(self,username)
+ pmdaInterface *self
+ char * username
+ CODE:
+ RETVAL = __pmSetProcessIdentity(username);
+ OUTPUT:
+ RETVAL
+
+void
+set_fetch(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ fetch_func = newSVsv(function);
+ }
+
+void
+set_refresh(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ refresh_func = newSVsv(function);
+ }
+
+void
+set_instance(self,function)
+ pmdaInterface *self
+ SV * function
+ CODE:
+ if (function != (SV *)NULL) {
+ instance_func = newSVsv(function);
+ }
+
+void
+set_store_callback(self,cb_function)
+ pmdaInterface *self
+ SV * cb_function
+ CODE:
+ if (cb_function != (SV *)NULL) {
+ store_cb_func = newSVsv(cb_function);
+ self->version.four.store = store;
+ }
+
+void
+set_fetch_callback(self,cb_function)
+ pmdaInterface *self
+ SV * cb_function
+ CODE:
+ if (cb_function != (SV *)NULL) {
+ fetch_cb_func = newSVsv(cb_function);
+ pmdaSetFetchCallBack(self, fetch_callback);
+ }
+
+void
+set_inet_socket(self,port)
+ pmdaInterface *self
+ int port
+ CODE:
+ self->version.four.ext->e_io = pmdaInet;
+ self->version.four.ext->e_port = port;
+
+void
+set_ipv6_socket(self,port)
+ pmdaInterface *self
+ int port
+ CODE:
+ self->version.four.ext->e_io = pmdaIPv6;
+ self->version.four.ext->e_port = port;
+
+void
+set_unix_socket(self,socket_name)
+ pmdaInterface *self
+ char * socket_name
+ CODE:
+ self->version.four.ext->e_io = pmdaUnix;
+ self->version.four.ext->e_sockname = socket_name;
+
+void
+clear_metrics(self)
+ pmdaInterface *self
+ CODE:
+ need_refresh = 1;
+ if (clustertab)
+ free(clustertab);
+ ctab_size = 0;
+ if (metrictab)
+ free(metrictab);
+ mtab_size = 0;
+ clearHV(metric_names);
+ clearHV(metric_oneline);
+ clearHV(metric_helptext);
+
+void
+add_metric(self,pmid,type,indom,sem,units,name,help,longhelp)
+ pmdaInterface *self
+ int pmid
+ int type
+ int indom
+ int sem
+ int units
+ char * name
+ char * help
+ char * longhelp
+ PREINIT:
+ pmdaMetric * p;
+ __pmID_int * pmidp;
+ const char * hash;
+ int size;
+ CODE:
+ (void)self;
+ need_refresh = 1;
+ pmidp = (__pmID_int *)&pmid;
+ if (!clustertab_lookup(pmidp->cluster)) {
+ size = sizeof(int) * (ctab_size + 1);
+ clustertab = (int *)realloc(clustertab, size);
+ if (clustertab)
+ clustertab[ctab_size++] = pmidp->cluster;
+ else {
+ warn("unable to allocate memory for cluster table");
+ ctab_size = 0;
+ XSRETURN_UNDEF;
+ }
+ }
+
+ size = sizeof(pmdaMetric) * (mtab_size + 1);
+ metrictab = (pmdaMetric *)realloc(metrictab, size);
+ if (metrictab == NULL) {
+ warn("unable to allocate memory for metric table");
+ mtab_size = 0;
+ XSRETURN_UNDEF;
+ }
+
+ p = metrictab + mtab_size++;
+ p->m_user = NULL; p->m_desc.pmid = *(pmID *)&pmid;
+ p->m_desc.type = type; p->m_desc.indom = *(pmInDom *)&indom;
+ p->m_desc.sem = sem; p->m_desc.units = *(pmUnits *)&units;
+
+ hash = pmIDStr(pmid);
+ size = strlen(hash);
+ hv_store(metric_names, hash, size, newSVpv(name,0), 0);
+ if (help)
+ hv_store(metric_oneline, hash, size, newSVpv(help,0), 0);
+ if (longhelp)
+ hv_store(metric_helptext, hash, size, newSVpv(longhelp,0), 0);
+
+void
+clear_indoms(self)
+ pmdaInterface *self
+ CODE:
+ if (indomtab)
+ free(indomtab);
+ itab_size = 0;
+ if (metrictab)
+ free(metrictab);
+ mtab_size = 0;
+ clearHV(indom_oneline);
+ clearHV(indom_helptext);
+
+int
+add_indom(self,indom,insts,help,longhelp)
+ pmdaInterface * self
+ int indom
+ SV * insts
+ char * help
+ char * longhelp
+ PREINIT:
+ pmdaIndom * p;
+ const char * hash;
+ int sts, size;
+ CODE:
+ size = sizeof(pmdaIndom) * (itab_size + 1);
+ indomtab = (pmdaIndom *)realloc(indomtab, size);
+ if (indomtab == NULL) {
+ warn("unable to allocate memory for indom table");
+ itab_size = 0;
+ XSRETURN_UNDEF;
+ }
+
+ p = indomtab + itab_size;
+ memset(p, 0, sizeof(pmdaIndom));
+ p->it_indom = pmInDom_build(self->domain, indom);
+
+ sts = update_indom(insts, p->it_indom, &p->it_set);
+ if (sts < 0)
+ XSRETURN_UNDEF;
+ if (p->it_set)
+ p->it_numinst = sts;
+ RETVAL = itab_size++; /* used in calls to replace_indom() */
+
+ hash = pmInDomStr(indom);
+ size = strlen(hash);
+ if (help)
+ hv_store(indom_oneline, hash, size, newSVpv(help,0), 0);
+ if (longhelp)
+ hv_store(indom_helptext, hash, size, newSVpv(longhelp,0), 0);
+ OUTPUT:
+ RETVAL
+
+int
+replace_indom(self,index,insts)
+ pmdaInterface * self
+ unsigned int index
+ SV * insts
+ PREINIT:
+ pmdaIndom * p;
+ int sts;
+ CODE:
+ if (index >= itab_size) {
+ warn("attempt to replace non-existent instance domain");
+ XSRETURN_UNDEF;
+ }
+ else {
+ p = indomtab + index;
+ /* was this indom previously setup via an array? */
+ if (p->it_set)
+ release_list_indom(p->it_set, p->it_numinst);
+ sts = update_indom(insts, p->it_indom, &p->it_set);
+ if (sts < 0)
+ XSRETURN_UNDEF;
+ if (p->it_set)
+ p->it_numinst = sts;
+ RETVAL = sts;
+ }
+ OUTPUT:
+ RETVAL
+
+int
+add_timer(self,timeout,callback,data)
+ pmdaInterface * self
+ double timeout
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_timer(timeout, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_pipe(self,command,callback,data)
+ pmdaInterface *self
+ char * command
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_pipe(command, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_tail(self,filename,callback,data)
+ pmdaInterface *self
+ char * filename
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_tail(filename, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+add_sock(self,hostname,port,callback,data)
+ pmdaInterface *self
+ char * hostname
+ int port
+ SV * callback
+ int data
+ CODE:
+ if (getenv("PCP_PERL_PMNS") || getenv("PCP_PERL_DOMAIN") || !callback)
+ XSRETURN_UNDEF;
+ RETVAL = local_sock(hostname, port, newSVsv(callback), data);
+ OUTPUT:
+ RETVAL
+
+int
+put_sock(self,id,output)
+ pmdaInterface *self
+ int id
+ char * output
+ CODE:
+ size_t length = strlen(output);
+ RETVAL = __pmWrite(local_files_get_descriptor(id), output, length);
+ OUTPUT:
+ RETVAL
+
+void
+log(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_INFO, "%s", message);
+
+void
+err(self,message)
+ pmdaInterface *self
+ char * message
+ CODE:
+ __pmNotifyErr(LOG_ERR, "%s", message);
+
+void
+connect_pmcd(self)
+ pmdaInterface *self
+ CODE:
+ /*
+ * Need to mimic the same special cases handled in run()
+ * that explicitly do NOT connect to pmcd and treat these
+ * as no-ops here
+ *
+ * Otherwise call pmdaConnet() to complete the PMDA's IPC
+ * channel setup and complete the connection handshake with
+ * pmcd.
+ */
+ if (getenv("PCP_PERL_PMNS") != NULL)
+ ;
+ else if (getenv("PCP_PERL_DOMAIN") != NULL)
+ ;
+ else {
+ /*
+ * On success pmdaConnect sets PMDA_EXT_CONNECTED in e_flags ...
+ * this used in the guard below to stop run() calling
+ * pmdaConnect() again.
+ */
+ pmdaConnect(self);
+ }
+
+void
+run(self)
+ pmdaInterface *self
+ CODE:
+ if (getenv("PCP_PERL_PMNS") != NULL)
+ pmns_write(); /* generate ascii namespace */
+ else if (getenv("PCP_PERL_DOMAIN") != NULL)
+ domain_write(); /* generate the domain header */
+ else { /* or normal operating mode ... */
+ pmns_refresh();
+ pmdaInit(self, indomtab, itab_size, metrictab, mtab_size);
+ if ((self->version.any.ext->e_flags & PMDA_EXT_CONNECTED) != PMDA_EXT_CONNECTED) {
+ /*
+ * connect_pmcd() not called before, so need pmdaConnect()
+ * here before falling into the PDU-driven mainloop
+ */
+ pmdaConnect(self);
+ }
+ local_pmdaMain(self);
+ }
+
+void
+debug_metric(self)
+ pmdaInterface *self
+ PREINIT:
+ int i;
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ fprintf(stderr, "metric table size = %d\n", mtab_size);
+ for (i = 0; i < mtab_size; i++) {
+ fprintf(stderr, "metric idx = %d\n\tpmid = %s\n\ttype = %u\n"
+ "\tindom= %d\n\tsem = %u\n\tunits= %u\n",
+ i, pmIDStr(metrictab[i].m_desc.pmid), metrictab[i].m_desc.type,
+ (int)metrictab[i].m_desc.indom, metrictab[i].m_desc.sem,
+ *(unsigned int *)&metrictab[i].m_desc.units);
+ }
+ (void)self;
+
+void
+debug_indom(self)
+ pmdaInterface *self
+ PREINIT:
+ int i,j;
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ fprintf(stderr, "indom table size = %d\n", itab_size);
+ for (i = 0; i < itab_size; i++) {
+ fprintf(stderr, "indom idx = %d\n\tindom = %d\n"
+ "\tninst = %u\n\tiptr = 0x%p\n",
+ i, *(int *)&indomtab[i].it_indom, indomtab[i].it_numinst,
+ indomtab[i].it_set);
+ for (j = 0; j < indomtab[i].it_numinst; j++) {
+ fprintf(stderr, "\t\tid=%d name=%s\n",
+ indomtab[i].it_set[j].i_inst, indomtab[i].it_set[j].i_name);
+ }
+ }
+ (void)self;
+
+void
+debug_init(self)
+ pmdaInterface *self
+ CODE:
+ /* NB: debugging only (used in test.pl to verify state) */
+ pmdaInit(self, indomtab, itab_size, metrictab, mtab_size);
+
diff --git a/src/perl/PMDA/cvalue.c b/src/perl/PMDA/cvalue.c
new file mode 100644
index 0000000..fe9723c
--- /dev/null
+++ b/src/perl/PMDA/cvalue.c
@@ -0,0 +1,155 @@
+/*
+ * Verifies that the information we see from PMDA.pm (perl) matches
+ * the local PCP installation (PMAPI) - used by test.pl: `make test`
+ *
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <values.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+
+void
+ids(void)
+{
+ printf("PMDA_PMID: 0,0 = %d\n", PMDA_PMID(0,0));
+ printf("PMDA_PMID: 1,1 = %d\n", PMDA_PMID(1,1));
+ printf("PMDA_PMID: 27,13 = %d\n", PMDA_PMID(27,13));
+ printf("PMDA_PMID: %d,0 = %d\n", MAXINT, PMDA_PMID(MAXINT,0));
+ printf("PMDA_PMID: 0,%d = %d\n", MAXINT, PMDA_PMID(0,MAXINT));
+ printf("PMDA_PMID: %d,%d = %d\n", MAXINT,MAXINT, PMDA_PMID(MAXINT,MAXINT));
+}
+
+void
+units(void)
+{
+ pmUnits forw;
+ pmUnits back;
+
+ forw.pad = back.pad = 0;
+ forw.dimSpace = back.scaleCount = 1;
+ forw.dimTime = back.scaleTime = 2;
+ forw.dimCount = back.scaleSpace = 3;
+ forw.scaleSpace = back.dimCount = 4;
+ forw.scaleTime = back.dimTime = 5;
+ forw.scaleCount = back.dimSpace = 6;
+ printf("pmUnits: 1,2,3,4,5,6 = %d\n", *(unsigned int *)&forw);
+ printf("pmUnits: 6,5,4,3,2,1 = %d\n", *(unsigned int *)&back);
+}
+
+void
+defines(void)
+{
+ printf("PM_ID_NULL=%u\n", PM_ID_NULL);
+ printf("PM_INDOM_NULL=%u\n", PM_INDOM_NULL);
+ printf("PM_IN_NULL=%u\n", PM_IN_NULL);
+
+ printf("PM_SPACE_BYTE=%u\n", PM_SPACE_BYTE);
+ printf("PM_SPACE_KBYTE=%u\n", PM_SPACE_KBYTE);
+ printf("PM_SPACE_MBYTE=%u\n", PM_SPACE_MBYTE);
+ printf("PM_SPACE_GBYTE=%u\n", PM_SPACE_GBYTE);
+ printf("PM_SPACE_TBYTE=%u\n", PM_SPACE_TBYTE);
+
+ printf("PM_TIME_NSEC=%u\n", PM_TIME_NSEC);
+ printf("PM_TIME_USEC=%u\n", PM_TIME_USEC);
+ printf("PM_TIME_MSEC=%u\n", PM_TIME_MSEC);
+ printf("PM_TIME_SEC=%u\n", PM_TIME_SEC);
+ printf("PM_TIME_MIN=%u\n", PM_TIME_MIN);
+ printf("PM_TIME_HOUR=%u\n", PM_TIME_HOUR);
+
+ printf("PM_TYPE_NOSUPPORT=%u\n", PM_TYPE_NOSUPPORT);
+ printf("PM_TYPE_32=%u\n", PM_TYPE_32);
+ printf("PM_TYPE_U32=%u\n", PM_TYPE_U32);
+ printf("PM_TYPE_64=%u\n", PM_TYPE_64);
+ printf("PM_TYPE_U64=%u\n", PM_TYPE_U64);
+ printf("PM_TYPE_FLOAT=%u\n", PM_TYPE_FLOAT);
+ printf("PM_TYPE_DOUBLE=%u\n", PM_TYPE_DOUBLE);
+ printf("PM_TYPE_STRING=%u\n", PM_TYPE_STRING);
+
+ printf("PM_SEM_COUNTER=%u\n", PM_SEM_COUNTER);
+ printf("PM_SEM_INSTANT=%u\n", PM_SEM_INSTANT);
+ printf("PM_SEM_DISCRETE=%u\n", PM_SEM_DISCRETE);
+
+ printf("PM_ERR_GENERIC=%d\n", PM_ERR_GENERIC);
+ printf("PM_ERR_PMNS=%d\n", PM_ERR_PMNS);
+ printf("PM_ERR_NOPMNS=%d\n", PM_ERR_NOPMNS);
+ printf("PM_ERR_DUPPMNS=%d\n", PM_ERR_DUPPMNS);
+ printf("PM_ERR_TEXT=%d\n", PM_ERR_TEXT);
+ printf("PM_ERR_APPVERSION=%d\n", PM_ERR_APPVERSION);
+ printf("PM_ERR_VALUE=%d\n", PM_ERR_VALUE);
+ printf("PM_ERR_TIMEOUT=%d\n", PM_ERR_TIMEOUT);
+ printf("PM_ERR_NODATA=%d\n", PM_ERR_NODATA);
+ printf("PM_ERR_RESET=%d\n", PM_ERR_RESET);
+ printf("PM_ERR_NAME=%d\n", PM_ERR_NAME);
+ printf("PM_ERR_PMID=%d\n", PM_ERR_PMID);
+ printf("PM_ERR_INDOM=%d\n", PM_ERR_INDOM);
+ printf("PM_ERR_INST=%d\n", PM_ERR_INST);
+ printf("PM_ERR_UNIT=%d\n", PM_ERR_UNIT);
+ printf("PM_ERR_CONV=%d\n", PM_ERR_CONV);
+ printf("PM_ERR_TRUNC=%d\n", PM_ERR_TRUNC);
+ printf("PM_ERR_SIGN=%d\n", PM_ERR_SIGN);
+ printf("PM_ERR_PROFILE=%d\n", PM_ERR_PROFILE);
+ printf("PM_ERR_IPC=%d\n", PM_ERR_IPC);
+ printf("PM_ERR_EOF=%d\n", PM_ERR_EOF);
+ printf("PM_ERR_NOTHOST=%d\n", PM_ERR_NOTHOST);
+ printf("PM_ERR_EOL=%d\n", PM_ERR_EOL);
+ printf("PM_ERR_MODE=%d\n", PM_ERR_MODE);
+ printf("PM_ERR_LABEL=%d\n", PM_ERR_LABEL);
+ printf("PM_ERR_LOGREC=%d\n", PM_ERR_LOGREC);
+ printf("PM_ERR_NOTARCHIVE=%d\n", PM_ERR_NOTARCHIVE);
+ printf("PM_ERR_LOGFILE=%d\n", PM_ERR_LOGFILE);
+ printf("PM_ERR_NOCONTEXT=%d\n", PM_ERR_NOCONTEXT);
+ printf("PM_ERR_PROFILESPEC=%d\n", PM_ERR_PROFILESPEC);
+ printf("PM_ERR_PMID_LOG=%d\n", PM_ERR_PMID_LOG);
+ printf("PM_ERR_INDOM_LOG=%d\n", PM_ERR_INDOM_LOG);
+ printf("PM_ERR_INST_LOG=%d\n", PM_ERR_INST_LOG);
+ printf("PM_ERR_NOPROFILE=%d\n", PM_ERR_NOPROFILE);
+ printf("PM_ERR_NOAGENT=%d\n", PM_ERR_NOAGENT);
+ printf("PM_ERR_PERMISSION=%d\n", PM_ERR_PERMISSION);
+ printf("PM_ERR_CONNLIMIT=%d\n", PM_ERR_CONNLIMIT);
+ printf("PM_ERR_AGAIN=%d\n", PM_ERR_AGAIN);
+ printf("PM_ERR_ISCONN=%d\n", PM_ERR_ISCONN);
+ printf("PM_ERR_NOTCONN=%d\n", PM_ERR_NOTCONN);
+ printf("PM_ERR_NEEDPORT=%d\n", PM_ERR_NEEDPORT);
+ printf("PM_ERR_NONLEAF=%d\n", PM_ERR_NONLEAF);
+ printf("PM_ERR_TYPE=%d\n", PM_ERR_TYPE);
+ printf("PM_ERR_TOOSMALL=%d\n", PM_ERR_TOOSMALL);
+ printf("PM_ERR_TOOBIG=%d\n", PM_ERR_TOOBIG);
+ printf("PM_ERR_PMDAREADY=%d\n", PM_ERR_PMDAREADY);
+ printf("PM_ERR_PMDANOTREADY=%d\n", PM_ERR_PMDANOTREADY);
+ printf("PM_ERR_NYI=%d\n", PM_ERR_NYI);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *use = "Error: must provide one argument - either 'd', 'i' or 'u'\n";
+ if (argc != 2) {
+ fputs(use, stderr);
+ return 1;
+ }
+ else if (argv[1][0] == 'd')
+ defines();
+ else if (argv[1][0] == 'i')
+ ids();
+ else if (argv[1][0] == 'u')
+ units();
+ else {
+ fputs(use, stderr);
+ fprintf(stderr, "(ouch!!!! that really hurt! who throws a '%s' anyway? --Austin)\n", argv[1]);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/perl/PMDA/local.c b/src/perl/PMDA/local.c
new file mode 100644
index 0000000..03a67fa
--- /dev/null
+++ b/src/perl/PMDA/local.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2008-2011 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "local.h"
+#include <dirent.h>
+#include <search.h>
+#include <sys/stat.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+static timers_t *timers;
+static int ntimers;
+static files_t *files;
+static int nfiles;
+
+char *
+local_strdup_suffix(const char *string, const char *suffix)
+{
+ size_t length = strlen(string) + strlen(suffix) + 1;
+ char *result = malloc(length);
+
+ if (!result)
+ return result;
+ sprintf(result, "%s%s", string, suffix);
+ return result;
+}
+
+char *
+local_strdup_prefix(const char *prefix, const char *string)
+{
+ size_t length = strlen(prefix) + strlen(string) + 1;
+ char *result = malloc(length);
+
+ if (!result)
+ return result;
+ sprintf(result, "%s%s", prefix, string);
+ return result;
+}
+
+int
+local_timer(double timeout, scalar_t *callback, int cookie)
+{
+ int size = sizeof(*timers) * (ntimers + 1);
+ delta_t delta;
+
+ delta.tv_sec = (time_t)timeout;
+ delta.tv_usec = (long)((timeout - (double)delta.tv_sec) * 1000000.0);
+
+ if ((timers = realloc(timers, size)) == NULL)
+ __pmNoMem("timers resize", size, PM_FATAL_ERR);
+ timers[ntimers].id = -1; /* not yet registered */
+ timers[ntimers].delta = delta;
+ timers[ntimers].cookie = cookie;
+ timers[ntimers].callback = callback;
+ return ntimers++;
+}
+
+int
+local_timer_get_cookie(int id)
+{
+ int i;
+
+ for (i = 0; i < ntimers; i++)
+ if (timers[i].id == id)
+ return timers[i].cookie;
+ return -1;
+}
+
+scalar_t *
+local_timer_get_callback(int id)
+{
+ int i;
+
+ for (i = 0; i < ntimers; i++)
+ if (timers[i].id == id)
+ return timers[i].callback;
+ return NULL;
+}
+
+static int
+local_file(int type, int fd, scalar_t *callback, int cookie)
+{
+ int size = sizeof(*files) * (nfiles + 1);
+
+ if ((files = realloc(files, size)) == NULL)
+ __pmNoMem("files resize", size, PM_FATAL_ERR);
+ files[nfiles].type = type;
+ files[nfiles].fd = fd;
+ files[nfiles].cookie = cookie;
+ files[nfiles].callback = callback;
+ return nfiles++;
+}
+
+int
+local_pipe(char *pipe, scalar_t *callback, int cookie)
+{
+ FILE *fp = popen(pipe, "r");
+ int me;
+
+#if defined(HAVE_SIGPIPE)
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ if (!fp) {
+ __pmNotifyErr(LOG_ERR, "popen failed (%s): %s", pipe, osstrerror());
+ exit(1);
+ }
+ me = local_file(FILE_PIPE, fileno(fp), callback, cookie);
+ files[me].me.pipe.file = fp;
+ return fileno(fp);
+}
+
+int
+local_tail(char *file, scalar_t *callback, int cookie)
+{
+ int fd = open(file, O_RDONLY | O_NDELAY);
+ struct stat stats;
+ int me;
+
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "open failed (%s): %s", file, osstrerror());
+ exit(1);
+ }
+ if (fstat(fd, &stats) < 0) {
+ __pmNotifyErr(LOG_ERR, "fstat failed (%s): %s", file, osstrerror());
+ exit(1);
+ }
+ lseek(fd, 0L, SEEK_END);
+ me = local_file(FILE_TAIL, fd, callback, cookie);
+ files[me].me.tail.path = strdup(file);
+ files[me].me.tail.dev = stats.st_dev;
+ files[me].me.tail.ino = stats.st_ino;
+ return me;
+}
+
+int
+local_sock(char *host, int port, scalar_t *callback, int cookie)
+{
+ __pmSockAddr *myaddr;
+ __pmHostEnt *servinfo = NULL;
+ void *enumIx;
+ int sts = -1;
+ int me, fd = -1;
+
+ if ((servinfo = __pmGetAddrInfo(host)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "__pmGetAddrInfo (%s): %s", host, netstrerror());
+ goto error;
+ }
+ /* Loop over the addresses resolved for this host name until one of them
+ connects. */
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR, "invalid address family: %d\n",
+ __pmSockAddrGetFamily(myaddr));
+ fd = -1;
+ }
+
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ __pmSockAddrSetPort(myaddr, port);
+
+ sts = __pmConnect(fd, (void *)myaddr, __pmSockAddrSize());
+ __pmSockAddrFree(myaddr);
+ if (sts == 0)
+ break; /* Successful connection */
+
+ /* Try the next address */
+ __pmCloseSocket(fd);
+ fd = -1;
+ }
+ __pmHostEntFree(servinfo);
+
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "__pmConnect (%s): %s", host, netstrerror());
+ goto error;
+ }
+
+ me = local_file(FILE_SOCK, fd, callback, cookie);
+ files[me].me.sock.host = strdup(host);
+ files[me].me.sock.port = port;
+
+ return me;
+
+ error:
+ if (fd >= 0)
+ __pmCloseSocket(fd);
+ if (servinfo)
+ __pmHostEntFree(servinfo);
+ exit(1);
+}
+
+static char *
+local_filetype(int type)
+{
+ if (type == FILE_SOCK)
+ return "socket connection";
+ if (type == FILE_PIPE)
+ return "command pipe";
+ if (type == FILE_TAIL)
+ return "tailed file";
+ return NULL;
+}
+
+int
+local_files_get_descriptor(int id)
+{
+ if (id < 0 || id >= nfiles)
+ return -1;
+ return files[id].fd;
+}
+
+void
+local_atexit(void)
+{
+ while (ntimers > 0) {
+ --ntimers;
+ __pmAFunregister(timers[ntimers].id);
+ }
+ if (timers) {
+ free(timers);
+ timers = NULL;
+ }
+ while (nfiles > 0) {
+ --nfiles;
+ if (files[nfiles].type == FILE_PIPE)
+ pclose(files[nfiles].me.pipe.file);
+ if (files[nfiles].type == FILE_TAIL) {
+ close(files[nfiles].fd);
+ if (files[nfiles].me.tail.path)
+ free(files[nfiles].me.tail.path);
+ files[nfiles].me.tail.path = NULL;
+ }
+ if (files[nfiles].type == FILE_SOCK) {
+ __pmCloseSocket(files[nfiles].fd);
+ if (files[nfiles].me.sock.host)
+ free(files[nfiles].me.sock.host);
+ files[nfiles].me.sock.host = NULL;
+ }
+ }
+ if (files) {
+ free(files);
+ files = NULL;
+ }
+ /* take out any children we created */
+#ifdef HAVE_SIGNAL
+ signal(SIGTERM, SIG_IGN);
+#endif
+ __pmProcessTerminate((pid_t)0, 0);
+}
+
+static void
+local_log_rotated(files_t *file)
+{
+ struct stat stats;
+
+ if (stat(file->me.tail.path, &stats) < 0)
+ return;
+ if (stats.st_ino == file->me.tail.ino && stats.st_dev == file->me.tail.dev)
+ return;
+
+ close(file->fd);
+ file->fd = open(file->me.tail.path, O_RDONLY | O_NDELAY);
+ if (file->fd < 0) {
+ __pmNotifyErr(LOG_ERR, "open failed after log rotate (%s): %s",
+ file->me.tail.path, osstrerror());
+ return;
+ }
+ files->me.tail.dev = stats.st_dev;
+ files->me.tail.ino = stats.st_ino;
+}
+
+static void
+local_reconnector(files_t *file)
+{
+ __pmSockAddr *myaddr = NULL;
+ __pmHostEnt *servinfo = NULL;
+ int fd = -1;
+ int sts = -1;
+ void *enumIx;
+
+ if (file->fd >= 0) /* reconnect-needed flag */
+ goto done;
+ if ((servinfo = __pmGetAddrInfo(file->me.sock.host)) == NULL)
+ goto done;
+ /* Loop over the addresses resolved for this host name until one of them
+ connects. */
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else {
+ __pmNotifyErr(LOG_ERR, "invalid address family: %d\n",
+ __pmSockAddrGetFamily(myaddr));
+ fd = -1;
+ }
+
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ __pmSockAddrSetPort(myaddr, files->me.sock.port);
+ sts = __pmConnect(fd, (void *)myaddr, __pmSockAddrSize());
+ __pmSockAddrFree(myaddr);
+ if (sts == 0) /* good connection */
+ break;
+
+ /* Try the next address */
+ __pmCloseSocket(fd);
+ fd = -1;
+ }
+
+ if (fd >= 0)
+ files->fd = fd;
+
+done:
+ if (myaddr)
+ __pmSockAddrFree(myaddr);
+ if (servinfo)
+ __pmHostEntFree(servinfo);
+}
+
+static void
+local_connection(files_t *file)
+{
+ if (file->type == FILE_TAIL)
+ local_log_rotated(file);
+ else if (file->type == FILE_TAIL)
+ local_reconnector(file);
+}
+
+void
+local_pmdaMain(pmdaInterface *self)
+{
+ static char buffer[4096];
+ int pmcdfd, nready, nfds, i, j, count, fd, maxfd = -1;
+ __pmFdSet fds, readyfds;
+ ssize_t bytes;
+ size_t offset;
+ char *s, *p;
+
+ if ((pmcdfd = __pmdaInFd(self)) < 0)
+ exit(1);
+
+ for (i = 0; i < ntimers; i++)
+ timers[i].id = __pmAFregister(&timers[i].delta, &timers[i].cookie,
+ timer_callback);
+
+ /* custom PMDA main loop */
+ for (count = 0; ; count++) {
+ struct timeval timeout = { 1, 0 };
+
+ __pmFD_ZERO(&fds);
+ __pmFD_SET(pmcdfd, &fds);
+ for (i = 0; i < nfiles; i++) {
+ if (files[i].type == FILE_TAIL)
+ continue;
+ fd = files[i].fd;
+ __pmFD_SET(fd, &fds);
+ if (fd > maxfd)
+ maxfd = fd;
+ }
+ nfds = ((pmcdfd > maxfd) ? pmcdfd : maxfd) + 1;
+
+ __pmFD_COPY(&readyfds, &fds);
+ nready = __pmSelectRead(nfds, &readyfds, &timeout);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failed: %s\n",
+ netstrerror());
+ exit(1);
+ }
+ continue;
+ }
+
+ __pmAFblock();
+
+ if (__pmFD_ISSET(pmcdfd, &readyfds)) {
+ if (__pmdaMainPDU(self) < 0) {
+ __pmAFunblock();
+ exit(1);
+ }
+ }
+
+ for (i = 0; i < nfiles; i++) {
+ fd = files[i].fd;
+ /* check for log rotation or host reconnection needed */
+ if ((count % 10) == 0) /* but only once every 10 */
+ local_connection(&files[i]);
+ if (files[i].type != FILE_TAIL && !(__pmFD_ISSET(fd, &readyfds)))
+ continue;
+ offset = 0;
+multiread:
+ bytes = __pmRead(fd, buffer + offset, sizeof(buffer)-1 - offset);
+ if (bytes < 0) {
+ if ((files[i].type == FILE_TAIL) &&
+ (oserror() == EINTR) ||
+ (oserror() == EAGAIN) ||
+ (oserror() == EWOULDBLOCK))
+ continue;
+ if (files[i].type == FILE_SOCK) {
+ close(files[i].fd);
+ files[i].fd = -1;
+ continue;
+ }
+ __pmNotifyErr(LOG_ERR, "Data read error on %s: %s\n",
+ local_filetype(files[i].type), osstrerror());
+ exit(1);
+ }
+ if (bytes == 0) {
+ if (files[i].type == FILE_TAIL)
+ continue;
+ __pmNotifyErr(LOG_ERR, "No data to read - %s may be closed\n",
+ local_filetype(files[i].type));
+ exit(1);
+ }
+ buffer[sizeof(buffer)-1] = '\0';
+ for (s = p = buffer, j = 0;
+ *s != '\0' && j < sizeof(buffer)-1;
+ s++, j++) {
+ if (*s != '\n')
+ continue;
+ *s = '\0';
+ /*__pmNotifyErr(LOG_INFO, "Input callback: %s\n", p);*/
+ input_callback(files[i].callback, files[i].cookie, p);
+ p = s + 1;
+ }
+ if (files[i].type == FILE_TAIL) {
+ /* did we just do a full buffer read? */
+ if (p == buffer) {
+ __pmNotifyErr(LOG_ERR, "Ignoring long line: \"%s\"\n", p);
+ } else if (j == sizeof(buffer) - 1) {
+ offset = sizeof(buffer)-1 - (p - buffer);
+ memmove(buffer, p, offset);
+ goto multiread; /* read rest of line */
+ }
+ }
+ }
+
+ __pmAFunblock();
+ }
+}
diff --git a/src/perl/PMDA/local.h b/src/perl/PMDA/local.h
new file mode 100644
index 0000000..11812cd
--- /dev/null
+++ b/src/perl/PMDA/local.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef LOCAL_H
+#define LOCAL_H
+
+#include <pmapi.h>
+#include <impl.h>
+#include <pmda.h>
+
+typedef struct sv scalar_t;
+typedef struct timeval delta_t;
+
+typedef struct {
+ int id;
+ delta_t delta;
+ int cookie;
+ scalar_t *callback;
+} timers_t;
+
+typedef enum { FILE_PIPE, FILE_SOCK, FILE_TAIL } file_type_t;
+
+typedef struct {
+ FILE *file;
+} pipe_data_t;
+
+typedef struct {
+ char *path;
+ dev_t dev;
+ ino_t ino;
+} tail_data_t;
+
+typedef struct {
+ char *host;
+ int port;
+} sock_data_t;
+
+typedef struct {
+ int fd;
+ int type;
+ int cookie;
+ scalar_t *callback;
+ union {
+ pipe_data_t pipe;
+ tail_data_t tail;
+ sock_data_t sock;
+ } me;
+} files_t;
+
+extern void timer_callback(int, void *);
+extern void input_callback(scalar_t *, int, char *);
+
+extern char *local_strdup_suffix(const char *string, const char *suffix);
+extern char *local_strdup_prefix(const char *prefix, const char *string);
+
+extern int local_user(const char *username);
+
+extern int local_timer(double timeout, scalar_t *callback, int cookie);
+extern int local_timer_get_cookie(int id);
+extern scalar_t *local_timer_get_callback(int id);
+
+extern int local_pipe(char *pipe, scalar_t *callback, int cookie);
+extern int local_tail(char *file, scalar_t *callback, int cookie);
+extern int local_sock(char *host, int port, scalar_t *callback, int cookie);
+
+extern void local_atexit(void);
+extern int local_files_get_descriptor(int id);
+extern void local_pmdaMain(pmdaInterface *self);
+
+#endif /* LOCAL_H */
diff --git a/src/perl/PMDA/test.pl b/src/perl/PMDA/test.pl
new file mode 100644
index 0000000..a40443d
--- /dev/null
+++ b/src/perl/PMDA/test.pl
@@ -0,0 +1,93 @@
+# Before `make install' is performed this script should be runnable with
+# `make test'. After `make install' it should work as `perl test.pl'
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+######################### We start with some black magic to print on failure.
+
+BEGIN { $| = 1; print "1..4\n"; }
+END {print "not ok 1\n" unless $loaded;}
+use PCP::PMDA;
+$loaded = 1;
+print "ok 1\n";
+
+######################### End of black magic.
+
+use vars qw( $cvalue $perlvalue $failed $cases );
+
+`make -f Makefile cvalue`;
+
+# verify constants are defined and match their C counterparts
+# - assuming here that the header file matches our PMDA.pm
+# (d==define)
+#
+$failed = 0;
+$cases = 0;
+open(TEST, './cvalue d |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^(\w+)=(.*)$/;
+ $cvalue = $2;
+ $perlvalue = &$1;
+ unless ($perlvalue == $cvalue) {
+ print "$1: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 2 (failed $failed of $cases cases)\n"; }
+else { print "ok 2\n"; }
+
+#########################
+
+# test data initialisation via the pmda_pmid macro (i==id)
+#
+$failed = 0;
+$cases = 0;
+open(TEST, './cvalue i |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^PMDA_PMID: (\d+),(\d+) = (.*)$/;
+ $cvalue = $3;
+ $perlvalue = pmda_pmid($1, $2);
+ unless ($perlvalue == $cvalue) {
+ print "$1,$2: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 3 (failed $failed of $cases cases)\n"; }
+else { print "ok 3\n"; }
+
+# test data initialisation via the pmda_units macro (u==units)
+#
+$failed=0;
+$cases = 0;
+open(TEST, './cvalue u |') || die "cannot run test program 'cvalue'";
+while (<TEST>) {
+ /^pmUnits: {(\d+),}5(\d) = (.*)$/;
+ $cvalue = $7;
+ $perlvalue = pmda_units($1, $2, $3, $4, $5, $6);
+ unless ($perlvalue == $cvalue) {
+ print "$1,$2,$3,$4,$5,$6: $perlvalue != $cvalue\n";
+ $failed++;
+ }
+ $cases++;
+}
+close TEST;
+if ($failed != 0) { print "not ok 4 (failed $failed of $cases cases)\n"; }
+else { print "ok 4\n"; }
+
+
+#########################
diff --git a/src/perl/PMDA/typemap b/src/perl/PMDA/typemap
new file mode 100644
index 0000000..5846f97
--- /dev/null
+++ b/src/perl/PMDA/typemap
@@ -0,0 +1,27 @@
+######################################################################
+# INPUT/OUTPUT maps
+# O_OBJECT -> links an opaque C object to a blessed Perl object.
+#
+TYPEMAP
+pmdaInterface * O_OBJECT
+pmdaInstid * T_PTROBJ
+
+######################################################################
+OUTPUT
+
+O_OBJECT
+ sv_setref_pv( $arg, CLASS, (void*)$var );
+
+
+######################################################################
+INPUT
+
+O_OBJECT
+ if (sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG))
+ $var = ($type)SvIV((SV *)SvRV($arg));
+ else {
+ warn(\"${Package}::$func_name() -- $var is not a blessed SV reference\");
+ XSRETURN_UNDEF;
+ }
+
+######################################################################
diff --git a/src/pmafm/GNUmakefile b/src/pmafm/GNUmakefile
new file mode 100644
index 0000000..88e13fc
--- /dev/null
+++ b/src/pmafm/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+TARGETS = pmafm mkaf
+LSRCFILES = mkaf pmafm pmafm.pcp pmafm.pcp-gui template
+AFM_DIR = $(PCP_VAR_DIR)/config/pmafm
+
+default :: default_pcp
+
+default_pcp : $(TARGETS)
+
+default_pro : $(TARGETS)
+
+install :: install_pcp
+
+install_pcp : default_pcp
+ $(INSTALL) -m 755 pmafm $(PCP_BIN_DIR)/pmafm$(SHELLSUFFIX)
+ $(INSTALL) -m 755 mkaf $(PCP_BINADM_DIR)/mkaf$(SHELLSUFFIX)
+ $(INSTALL) -m 755 -d $(AFM_DIR)
+ $(INSTALL) -m 644 pmafm.pcp $(AFM_DIR)/pcp
+ $(INSTALL) -m 644 pmafm.pcp-gui $(AFM_DIR)/pcp-gui
+
+include $(BUILDRULES)
diff --git a/src/pmafm/mkaf b/src/pmafm/mkaf
new file mode 100755
index 0000000..e11c388
--- /dev/null
+++ b/src/pmafm/mkaf
@@ -0,0 +1,141 @@
+#!/bin/sh
+#
+# Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+_usage()
+{
+ echo "Usage: mkaf [findopts] filename ..."
+}
+
+# check for magic numbers in a file that indicate it is a PCP archive
+#
+# if file(1) was reliable, this would be much easier, ... sigh
+#
+# if you need to change this, make consistent changes in all these
+# places:
+# _check_file() in src/pmafm/mkaf
+# _is_archive() in src/pmafm/pmafm
+# _is_archive() in src/pmview/front-ends/pmview-args
+#
+_is_archive()
+{
+ dd ibs=1 count=7 if="$1" 2>/dev/null | od -X | $PCP_AWK_PROG '
+BEGIN { sts = 1 }
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "0084" && $4 == "5005" && $5 == "2600" { sts = 0 }
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "8400" && $4 == "0550" && $5 == "0026" { sts = 0 }
+NR == 1 && NF == 3 && $2 == "00000084" && $3 == "50052600" { sts = 0 }
+NR == 1 && NF == 3 && $2 == "84000000" && $3 == "00260550" { sts = 0 }
+END { exit sts }'
+ return $?
+}
+
+if [ $# -eq 0 ]
+then
+ _usage
+ exit 1
+fi
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+findopts=""
+
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+ -?)
+ _usage
+ exit 1
+ ;;
+ -*)
+ findopts="$findopts $1"
+ ;;
+
+ *)
+ if [ -d $1 ]
+ then
+ [ -z "$findopts" ] && findopts="-follow"
+ $PCP_ECHO_PROG >&2 $PCP_ECHO_N "Searching \"find $1 $findopts ...\" $PCP_ECHO_C"
+ find $1 $findopts -type f -print \
+ | while read file
+ do
+ if _is_archive $file
+ then
+ echo $file >>$tmp/base
+ fi
+ done
+ $PCP_ECHO_PROG >&2 " done"
+ elif [ ! -f $1 ]
+ then
+ echo >&2 "mkaf: $1: No such file"
+ elif _is_archive $1
+ then
+ echo $1 >>$tmp/base
+ else
+ echo >&2 "mkaf: $1: Not a PCP archive file"
+ fi
+ ;;
+
+ esac
+
+ shift
+done
+
+
+if [ ! -s $tmp/base ]
+then
+ echo >&2 "mkaf: Warning: no PCP archives found, so no folio created"
+ status=1
+ exit
+fi
+
+host=somehost
+which hostname >/dev/null 2>&1 && host=`hostname`
+
+cat <<End-of-File
+PCPFolio
+Version: 1
+# use pmafm(1) to process this PCP archive folio
+#
+Created: on $host at `date`
+Creator: pmchart
+# Host Basename
+#
+End-of-File
+
+sed <$tmp/base \
+ -e 's/\.[0-9][0-9]*$//' \
+ -e 's/\.meta$//' \
+ -e 's/\.index$//' \
+| sort -u \
+| while read base
+do
+ host=`pmdumplog -l $base 2>&1 | sed -n -e '/^Performance metrics/s/.* host //p'`
+ if [ -z "$host" ]
+ then
+ echo >&2 "mkaf: Warning: cannot extract hostname from archive \"$base\" ... skipped"
+ else
+ printf "%-15s %-23s %s\n" "Archive:" "$host" "$base"
+ fi
+done
+
+exit
diff --git a/src/pmafm/pmafm b/src/pmafm/pmafm
new file mode 100755
index 0000000..54db2fa
--- /dev/null
+++ b/src/pmafm/pmafm
@@ -0,0 +1,568 @@
+#!/bin/sh
+#
+# Copyright (c) 1995-2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+_usage()
+{
+ echo "Usage: pmafm folioname [command [arg ...]]"
+}
+
+_help()
+{
+ echo \
+'PCP Archive Folio Manager
+
+Commands:
+ archives - select all archives
+ archives N[,...] - select archives with these ordinal numbers
+ archives name[,...] - select archives with these names
+ check - integrity check for folio
+ help - this message
+ hosts - select archives for all hosts (the default)
+ hosts hostname[,...] - select archives for just these hosts
+ list [verbose] - display folio contents
+ quit - exit
+ remove - echo the sh(1) command to delete all files
+ associated with the folio
+ repeat tool [arg ...] - execute a known PCP tool on each archive in turn
+ replay - replay archives using the tool that created the folio
+ [run] tool [arg ...] - execute a known PCP tool on the selected archives
+ selections - list selected archives
+
+Selection:
+ If specified, both the "archives" and the "hosts" selection criteria
+ are applied as a conjunction.'
+}
+
+if [ $# -lt 1 ]
+then
+ _usage 1>&2
+ exit 1
+fi
+
+if [ ! -f $1 ]
+then
+ if [ "X$1" = "X-?" ]
+ then
+ _usage 1>&2
+ echo
+ _help 1>&2
+ else
+ echo "pmafm: cannot open folio \"$1\"" 1>&2
+ fi
+ exit 1
+fi
+
+_FOLIOPATH=`dirname $1`
+if [ "$_FOLIOPATH" = "." ]
+then
+ _FOLIOPATH=""
+elif [ -d $_FOLIOPATH ]
+then
+ _TMP=`pwd`
+ cd $_FOLIOPATH
+ _FOLIOPATH=`pwd`/
+ cd $_TMP
+fi
+_FOLIO="$1"
+_FOLIONAME=`basename $_FOLIO`
+
+# if file(1) worked everywhere we could use that ... sigh.
+#
+if grep '^PCPFolio' $1 >/dev/null && grep '^Version:' $1 >/dev/null
+then
+ :
+else
+ echo "pmafm: \"${_FOLIOPATH}$_FOLIONAME\" is not in PCP archive folio format" 1>&2
+ exit 1
+fi
+
+_HOSTS=""
+_ALL_HOSTS=`$PCP_AWK_PROG <$_FOLIO '$1 == "Archive:" { printf " %s",$2 }'; echo | sort -u`
+_ARCHIVES=""
+_ALL_ARCHIVES=`$PCP_AWK_PROG <$_FOLIO '$1 == "Archive:" { printf " %s",$3 }'; echo`
+_DEBUG=false
+export _DEBUG _FOLIO _HOSTS _ALL_HOSTS _ARCHIVES _ALL_ARCHIVES
+
+_SINGLE=""
+_MULTI=""
+_REPLAY=""
+_SPECIAL=""
+
+# collect names of known tools, and "run" scripts for each
+#
+for config in $PCP_VAR_DIR/config/pmafm/* $HOME/.pcp/pmafm/*
+do
+ if [ "$config" = $PCP_VAR_DIR/config/pmafm/'*' ]
+ then
+ echo "pmafm: Warning: no PCP tool configurations in $PCP_VAR_DIR/config/pmafm/"
+ elif [ "$config" = $HOME/.pcp/pmafm/'*' ]
+ then
+ # no user defined list of tools
+ :
+ else
+ SINGLE=""
+ MULTI=""
+ REPLAY=""
+ SPECIAL=""
+ . $config
+ [ ! -z "$SINGLE" ] && _SINGLE="$_SINGLE $SINGLE"
+ [ ! -z "$MULTI" ] && _MULTI="$_MULTI $MULTI"
+ [ ! -z "$REPLAY" ] && _REPLAY="$_REPLAY $REPLAY"
+ [ ! -z "$SPECIAL" ] && _SPECIAL="$_SPECIAL $SPECIAL"
+ fi
+done
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+# check for magic numbers in a file that indicate it is a PCP archive
+#
+# if file(1) was reliable, this would be much easier, ... sigh
+#
+# if you need to change this, make consistent changes in all these
+# places:
+# _check_file() in src/pmafm/mkaf
+# _is_archive() in src/pmafm/pmafm
+# _is_archive() in src/pmview/front-ends/pmview-args
+#
+_check_file()
+{
+ if [ ! -f "$1" ]
+ then
+ echo "No such file: $1" >>$tmp/base
+ else
+ dd ibs=1 count=7 if="$1" 2>/dev/null | od -X | $PCP_AWK_PROG '
+BEGIN { sts = 1 }
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "0084" && $4 == "5005" && $5 == "2600" { sts = 0 }
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "8400" && $4 == "0550" && $5 == "0026" { sts = 0 }
+NR == 1 && NF == 3 && $2 == "00000084" && $3 == "50052600" { sts = 0 }
+NR == 1 && NF == 3 && $2 == "84000000" && $3 == "00260550" { sts = 0 }
+END { exit sts }'
+ if [ $? -eq 1 ]
+ then
+ echo "Not a PCP archive file: $1" >>$tmp/base
+ fi
+ fi
+}
+
+_check()
+{
+ sed -n '/^Archive:/p' <$_FOLIO \
+ | while read xxx host arch
+ do
+ rm -f $tmp/base
+ path=`_fix_arch_path "" $arch`
+ $PCP_ECHO_PROG $PCP_ECHO_N "Archive: $path ... ""$PCP_ECHO_C"
+ _check_file $path.index
+ _check_file $path.meta
+ _check_file $path.0
+ if [ ! -s $tmp/base ]
+ then
+ realhost=`pmdumplog -l $path | sed -n -e '/^Performance metrics/s/.* host //p'`
+ [ "X$host" != "X$realhost" ] && \
+ echo "Hostname mismatch: folio=$host archive=$realhost" >>$tmp/base
+ fi
+ if [ -s $tmp/base ]
+ then
+ echo "Errors"
+ sed -e 's/^/ /' $tmp/base
+ else
+ echo "OK"
+ fi
+ done
+}
+
+_dir()
+{
+ echo
+ echo "PCP Archive Folio: $_FOLIONAME"
+ [ ! -z "$_FOLIOPATH" ] && echo "Folio Directory: $_FOLIOPATH"
+ $PCP_AWK_PROG <$_FOLIO '
+$1 == "Created:" { print; next }
+$1 == "Creator:" { print; next }'
+ echo
+ echo "Ordinal Hostname Archive Basename"
+ i=1
+ sed -n '/^Archive:/p' $_FOLIO \
+ | while read xxx host archive
+ do
+ printf " [%3s] %-20s %s\n" $i $host $archive
+ [ "X$1" = "Xverbose" ] && pmdumplog -l `_fix_arch_path "" $archive` | sed -e 's/^/ /'
+ i=`expr $i + 1`
+ done
+}
+
+# $1 is separater
+#
+_fix_arch_path()
+{
+ sep="$1"
+ shift
+ archlist=""
+ for arch
+ do
+ if [ ! -z "$_FOLIOPATH" ]
+ then
+ case $arch
+ in
+ /*)
+ ;;
+ *)
+ arch=${_FOLIOPATH}$arch
+ ;;
+ esac
+ fi
+ if [ -z "$archlist" ]
+ then
+ archlist="$arch"
+ else
+ archlist="${archlist}${sep}$arch"
+ fi
+ done
+
+ echo $archlist
+}
+
+_get_archlist()
+{
+ if [ -z "$_ARCHIVES" ]
+ then
+ archlist="$_ALL_ARCHIVES"
+ else
+ archlist="$_ARCHIVES"
+ fi
+
+ if [ ! -z "$_HOSTS" ]
+ then
+ newlist=""
+ for arch in $archlist
+ do
+ host=`$PCP_AWK_PROG <$_FOLIO '$3 == "'$arch'" { print $2; exit }'`
+ if echo "$_HOSTS " | grep " $host " >/dev/null
+ then
+ newlist="$newlist $arch"
+ fi
+ done
+ archlist=$newlist
+ fi
+
+ echo $archlist
+}
+
+_replay()
+{
+ _CREATOR=""
+ _REPLAY_CONFIG=""
+ eval `sed -n <$_FOLIO -e '/^Creator:/{
+s/Creator:[ ]*//
+s/^/_CREATOR=/
+s/[ ][ ]*/ _REPLAY_CONFIG=/
+p
+}'`
+ if [ -z "$_CREATOR" ]
+ then
+ echo "Error: cannot determine folio creator"
+ return
+ else
+ if [ ! -z "$_REPLAY_CONFIG" ]
+ then
+ _REPLAY_CONFIG=`_fix_arch_path "" "$_REPLAY_CONFIG"`
+ echo "Configuration File: $_REPLAY_CONFIG"
+ if [ ! -f "$_REPLAY_CONFIG" ]
+ then
+ echo "Error: cannot find Configuration File"
+ return
+ fi
+ fi
+ fi
+
+ if echo "$_REPLAY " | grep " $_CREATOR " >/dev/null
+ then
+ if [ -z "$_REPLAY_CONFIG" ]
+ then
+ _run $_CREATOR
+ else
+ _run $_CREATOR -c $_REPLAY_CONFIG
+ fi
+ else
+ echo "Error: I don't know how to replay a folio created by \"$_CREATOR\""
+ echo " ... choose a PCP tool and use the \"run\" command to replay"
+ fi
+}
+
+_known()
+{
+ echo "Known PCP tools:"
+ echo "$_SINGLE$_MULTI" \
+ | sed -e 's/^ *//' \
+ | tr ' ' '\012' \
+ | sort \
+ | fmt \
+ | sed -e 's/^/ /'
+}
+
+_run()
+{
+ tool=$1
+ tool_basename=`basename $tool`
+ shift
+ archlist=`_get_archlist`
+ if [ -z "$archlist" ]
+ then
+ echo "Error: no selected archives"
+ return
+ fi
+
+ if $_REPEAT || ( echo "$_SINGLE " | grep " $tool_basename " >/dev/null )
+ then
+ n_arch=`echo $archlist | wc -w | sed -e 's/ *//g'`
+ [ "$n_arch" -gt 1 ] && echo "Note: running $tool serially, once per archive"
+ for arch in $archlist
+ do
+ $PCP_AWK_PROG <$_FOLIO '$3 == "'$arch'" { printf "Host: %s",$2; exit }'
+ path=`_fix_arch_path "" "$arch"`
+ echo " Archive: $path"
+ # arrgh ... special case for pmdumplog where -a is something else
+ # and archive comes last ... sigh
+ #
+ _done=false
+ if [ ! -z "$_SPECIAL" ]
+ then
+ for special in $_SPECIAL
+ do
+ if [ "$tool_basename" = "$special" ]
+ then
+ $tool $* $path
+ _done=true
+ break
+ fi
+ done
+ fi
+
+ if $_done
+ then
+ :
+ else
+ $tool -a $path $*
+ fi
+ done
+ elif echo "$_MULTI " | grep " $tool_basename " >/dev/null
+ then
+ $tool -a `_fix_arch_path " -a " $archlist` $*
+ elif [ "X$tool" = "X_show_me_" ]
+ then
+ for arch in $archlist
+ do
+ path=`_fix_arch_path "" "$arch"`
+ echo "Archive: $path"
+ done
+ else
+ echo "Sorry, don't know how to run \"$tool\" ..."
+ fi
+}
+
+_MORE=true
+while $_MORE
+do
+ if [ $# -gt 1 ]
+ then
+ shift
+ cmd="$1"
+ shift
+ args=$*
+ _MORE=false
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "pmafm> ""$PCP_ECHO_C"
+ read cmd args
+ if [ $? -ne 0 ]
+ then
+ echo
+ break
+ elif [ -z "$cmd" ]
+ then
+ continue
+ fi
+ fi
+
+ _REPEAT=false
+ if echo "$_SINGLE $_MULTI " | grep " $cmd " >/dev/null
+ then
+ # recognized command
+ _run $cmd $args
+ else
+ case $cmd
+ in
+
+ a|ar|arch|archi|archiv|archive|archives)
+ case $args
+ in
+ '')
+ _ARCHIVES=""
+ ;;
+ *)
+ _ARCHIVES=""
+ for arch in `echo $args | sed -e 's/,/ /g'`
+ do
+ _TMP=`$PCP_AWK_PROG <$_FOLIO '
+ $1 == "Archive:" { if ("'$arch'" == $3) {
+ print $3
+ exit
+ }
+ i++
+ if ("'$arch'" == i) {
+ print $3
+ exit
+ }
+ }'`
+ if [ -z "$_TMP" ]
+ then
+ echo "Warning: archive \"$arch\" not in folio ... ignored"
+ else
+ _ARCHIVES="$_ARCHIVES $_TMP"
+ fi
+ done
+ ;;
+ esac
+ ;;
+
+ c|ch|che|chec|check)
+ _check
+ ;;
+
+ d|de|deb|debu|debug)
+ if $_DEBUG
+ then
+ _DEBUG=false
+ set -
+ else
+ _DEBUG=true
+ set -x
+ fi
+ ;;
+
+ \?|he|hel|help)
+ _help
+ ;;
+
+ ho|hos|host|hosts)
+ case $args
+ in
+ '')
+ _HOSTS=""
+ ;;
+ *)
+ _HOSTS=""
+ for host in `echo $args | sed -e 's/,/ /g'`
+ do
+ if echo "$_ALL_HOSTS " | grep " $host " >/dev/null
+ then
+ _HOSTS="$_HOSTS $host"
+ else
+ echo "Warning: host \"$host\" not in folio ... ignored"
+ fi
+ done
+ ;;
+ esac
+ ;;
+
+ l|li|lis|list)
+ case $args
+ in
+ v|ve|ver|verb|verbo|verbos|verbose)
+ _dir verbose
+ ;;
+ '')
+ _dir
+ ;;
+ *)
+ echo "Illegal option \"$args\" ... ignored"
+ _dir
+ ;;
+ esac
+ [ ! -z "$_HOSTS" ] && echo "Host Selections:$_HOSTS"
+ [ ! -z "$_ARCHIVES" ] && echo "Archive Selections:$_ARCHIVES"
+ ;;
+
+ q|qu|qui|quit)
+ break
+ ;;
+
+ ru|run|repe|repea|repeat)
+ case $cmd
+ in
+ repe|repea|repeat)
+ _REPEAT=true
+ ;;
+ esac
+ tool=`echo "$args" | sed -e 's/ .*//'`
+ tool_basename=`basename $tool`
+ if [ -z "$tool" ]
+ then
+ echo "Error: missing PCP tool name"
+ _known
+ elif [ "X$tool" = 'X?' ]
+ then
+ _known
+ elif echo "$_SINGLE $_MULTI " | grep " $tool_basename " >/dev/null
+ then
+ _run $args
+ else
+ echo "Error: Unknown PCP tool: $tool"
+ _known
+ fi
+ ;;
+
+ rem|remo|remov|remove)
+ [ ! -z "$_FOLIOPATH" ] && $PCP_ECHO_PROG $PCP_ECHO_N "( cd $_FOLIOPATH; ""$PCP_ECHO_C"
+ $PCP_ECHO_PROG $PCP_ECHO_N "rm -f $_FOLIONAME""$PCP_ECHO_C"
+ $PCP_AWK_PROG <$_FOLIO '$1 == "Creator:" && NF > 2 { printf " %s",$3; exit }'
+ for arch in $_ALL_ARCHIVES
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N " $arch.log $arch.config $arch.meta $arch.index""$PCP_ECHO_C"
+ path=`_fix_arch_path "" $arch`
+ pmdumplog -t $path 2>/dev/null \
+ | $PCP_AWK_PROG '/^[0-9][0-9]:/ { print $2 }' \
+ | sort -u \
+ | while read vol
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N " $arch.$vol""$PCP_ECHO_C"
+ done
+ done
+ [ ! -z "$_FOLIOPATH" ] && $PCP_ECHO_PROG $PCP_ECHO_N " )""$PCP_ECHO_C"
+ echo
+ ;;
+
+ repl|repla|replay)
+ _replay
+ ;;
+
+ s|se|sel|sele|selec|select|selecti|selectio|selection|selections)
+ _run _show_me_
+ ;;
+
+ *)
+ echo "Unknown command \"$cmd\" ... enter \"help\" for more information"
+ ;;
+ esac
+ fi
+done
+
diff --git a/src/pmafm/pmafm.pcp b/src/pmafm/pmafm.pcp
new file mode 100644
index 0000000..84b7a8d
--- /dev/null
+++ b/src/pmafm/pmafm.pcp
@@ -0,0 +1,23 @@
+#
+# pmafm support for the PCP open source release
+#
+
+# the tools that can display multiple concurrent archives ...
+#
+MULTI="pmie pmstat"
+
+# any other tools that can display one archive at a time ...
+# (should be disjoint from $MULTI)
+#
+SINGLE="pcp pmval pminfo pmprobe pmclient pmdumplog pmlogsummary pmlogcheck"
+
+# the tools that know how to replay a folio they have created
+# (should be a subset of those in $MULTI and $SINGLE)
+#
+REPLAY=""
+
+# the tools that have Usage: foo [options] archive
+# rather than Usage: foo -a archive [options]
+# (should be a subset of those in $SINGLE)
+#
+SPECIAL="pmdumplog pmlogsummary pmlogcheck"
diff --git a/src/pmafm/pmafm.pcp-gui b/src/pmafm/pmafm.pcp-gui
new file mode 100644
index 0000000..e45823a
--- /dev/null
+++ b/src/pmafm/pmafm.pcp-gui
@@ -0,0 +1,5 @@
+# pmafm support for PCP GUI
+
+MULTI="pmchart pmdumptext"
+REPLAY="pmchart"
+
diff --git a/src/pmafm/template b/src/pmafm/template
new file mode 100644
index 0000000..40cddd2
--- /dev/null
+++ b/src/pmafm/template
@@ -0,0 +1,49 @@
+PCPFolio
+Version: 1
+
+Created: on hostname at ctime #abitrary string
+Creator: pmchart [config]
+
+# use pmfolio(1) to process this PCP Archive Folio
+#
+
+# Notes
+#
+# 0. first two lines are "magic", and know to various scripts and
+# tools
+#
+# 1. blank lines are skipped, # introduces a comment line ... more
+# strictly, after the first two lines, only lines containing one
+# of the magic keyword strings ("Created: ", "Creator: " and
+# "Archive: " are ever looked at
+#
+# 2. in the Created: line, hostname should be the hostname where the
+# creator pmlogger instances were running, and ctime should be
+# the creator's local time in ctime(3) format ... both are for
+# annotation, so do not worry too much about them ... pmfolio's
+# "list" command echoes this line verbatim
+#
+# 3. [config] is optional, and if present is used to replay with the
+# Creator using all of the archives in the folio ... e.g. pmchart's
+# config file for all of the charts that were visible when "record"
+# mode started. Creator: is expected to be one of "pmchart",
+# "pmview", or "cron.pmdaily" ... yes, this will happen!
+#
+# 4. for a Basename of eek, expect to find
+# eek.meta
+# eek.index
+# eek.0
+# eek.1, ... [ maybe ]
+# eek.pmlogger [ maybe, pmlogger config used to create _this_ archive ]
+
+# Host Basename
+#
+Archive: gonzo 12345a
+Archive: moomba.melbourne 12345b
+Archive: bozo /no/such/files
+Archive: gonzo /mylogs/gonzo/960619
+Archive: gonzo /mylogs/gonzo/960620
+Archive: gonzo /mylogs/gonzo/960621
+Archive: gonzo /mylogs/gonzo/960622
+Archive: brutus /mylogs/brutus/960624
+Archive: brutus /mylogs/brutus/960625
diff --git a/src/pmatop/GNUmakefile b/src/pmatop/GNUmakefile
new file mode 100644
index 0000000..4676b93
--- /dev/null
+++ b/src/pmatop/GNUmakefile
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pmatop
+PYFILES = $(TARGET).py
+
+default default_pcp: $(PYFILES)
+
+include $(BUILDRULES)
+
+install install_pcp: $(PYFILES)
+ifneq "$(PYTHON)" ""
+ $(INSTALL) -m 755 $(PYFILES) $(PCP_BIN_DIR)/$(TARGET)
+endif
diff --git a/src/pmatop/pmatop.py b/src/pmatop/pmatop.py
new file mode 100644
index 0000000..b15ae83
--- /dev/null
+++ b/src/pmatop/pmatop.py
@@ -0,0 +1,917 @@
+#!/usr/bin/python
+
+#
+# pmatop.py
+#
+# Copyright (C) 2013, 2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+"""Advanced System & Process Monitor using the libpcp Wrapper module
+
+Additional Information:
+
+Performance Co-Pilot Web Site
+http://www.performancecopilot.org
+"""
+
+# ignore line too long, missing docstring, method could be a function,
+# too many public methods
+# pylint: disable=C0301
+# pylint: disable=C0111
+# pylint: disable=R0201
+# pylint: disable=R0904
+
+##############################################################################
+#
+# imports
+#
+
+import os
+import datetime
+import time
+import sys
+import select
+import signal
+import cpmapi as c_api
+import cpmgui as c_gui
+from pcp import pmapi, pmgui
+from pcp.pmsubsys import Subsystem
+try:
+ import curses
+except ImportError as e:
+ print(e)
+ print("pmatop requires curses.py")
+ sys.exit(0)
+
+ME = "pmatop"
+
+def debug(mssg):
+ import logging
+ logging.basicConfig(filename='pmatop.log',level=logging.DEBUG)
+ if type(mssg) == type(""):
+ logging.debug(mssg)
+ else:
+ logging.debug(str(mssg) + "\n")
+
+
+# scale -------------------------------------------------------------
+
+
+def scale(value, magnitude):
+ return value / magnitude
+
+
+# record ---------------------------------------------------------------
+
+def record(context, config, duration, path, host):
+
+ # -f saves the metrics in a directory
+ if os.path.exists(path):
+ return "playback directory %s already exists\n" % path
+ try:
+ # Non-graphical application using libpcp_gui services - never want
+ # to see popup dialogs from pmlogger(1) here, so force the issue.
+ os.environ['PCP_XCONFIRM_PROG'] = '/bin/true'
+ interval = pmapi.timeval.fromInterval(str(duration) + " seconds")
+ context.pmRecordSetup(path, ME, 0) # pylint: disable=W0621
+ context.pmRecordAddHost(host, 1, config)
+ deadhand = "-T" + str(interval) + "seconds"
+ context.pmRecordControl(0, c_gui.PM_REC_SETARG, deadhand)
+ context.pmRecordControl(0, c_gui.PM_REC_ON, "")
+ interval.sleep()
+ context.pmRecordControl(0, c_gui.PM_REC_OFF, "")
+ # Note: pmlogger has a deadhand timer that will make it stop of its
+ # own accord once -T limit is reached; but we send an OFF-recording
+ # message anyway for cleanliness, just prior to pmcollectl exiting.
+ except pmapi.pmErr as e:
+ return "Cannot create PCP archive: " + path + " " + str(e)
+ return ""
+
+# record_add_creator ------------------------------------------------------
+
+def record_add_creator(path):
+ fdesc = open(path, "a+")
+ args = ""
+ for i in sys.argv:
+ args = args + i + " "
+ fdesc.write("# Created by " + args)
+ fdesc.write("\n#\n")
+ fdesc.close()
+
+# minutes_seconds ----------------------------------------------------------
+
+
+def minutes_seconds(milli):
+ milli = abs(milli)
+ sec, milli = divmod(milli, 1000)
+ tenth, milli = divmod(milli, 100)
+ milli = milli / 10
+ minute, sec = divmod(sec, 60)
+ hour, minute = divmod(minute, 60)
+ day, hour = divmod(hour, 24)
+ if day > 0:
+ return "%dd" % (day)
+ elif hour > 0:
+ return "%dh%dm" % (hour, minute)
+ elif minute > 0:
+ return "%dm%ds" % (minute, sec)
+ else:
+ return "%d.%d%1ds" % (sec, tenth, milli)
+
+
+# _StandardOutput --------------------------------------------------
+
+
+class _StandardOutput(object):
+ def width_write(self, value):
+ self._width = value
+ width = property(None, width_write, None, None)
+
+ def __init__(self, out):
+ if out == sys.stdout:
+ self._width = 80
+ self.stdout = True
+ else:
+ self.stdout = False
+ self.so_stdscr = out
+ def addstr(self, str, clrtoeol=False):
+ if self.stdout:
+ sys.stdout.write(str)
+ else:
+ self.so_stdscr.addstr(str)
+ if clrtoeol:
+ self.so_stdscr.clrtoeol()
+ def clear(self):
+ if not self.stdout:
+ self.so_stdscr.clear()
+ def move(self, y, x):
+ if not self.stdout:
+ self.so_stdscr.move(y, x)
+ def getyx(self):
+ if self.stdout:
+ return (0, 0)
+ else:
+ return self.so_stdscr.getyx()
+ def getmaxyx(self):
+ if self.stdout:
+ return (1000, self._width)
+ else:
+ return self.so_stdscr.getmaxyx()
+ def nodelay(self, tf):
+ if not self.stdout:
+ self.so_stdscr.nodelay(tf)
+ def timeout(self, milliseconds):
+ if not self.stdout:
+ self.so_stdscr.timeout(milliseconds)
+ def refresh(self):
+ if not self.stdout:
+ self.so_stdscr.refresh()
+ def clrtobot(self):
+ if not self.stdout:
+ self.so_stdscr.clrtobot()
+ def clear(self):
+ if not self.stdout:
+ self.so_stdscr.clear()
+ def getch(self):
+ if not self.stdout:
+ return self.so_stdscr.getch()
+ else:
+ while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
+ char = sys.stdin.read(1)
+ if len(char) == 0:
+ return -1
+ else:
+ return ord(char)
+ return -1
+
+
+# _AtopPrint --------------------------------------------------
+
+class _AtopPrint(object):
+ def __init__(self, ss, a_stdscr):
+ self.ss = ss
+ self.p_stdscr = a_stdscr
+ self.command_line = self.p_stdscr.getyx()[0]
+ self.apyx = a_stdscr.getmaxyx()
+ self.ONEKBYTE = 1024
+ self.ONEMBYTE = 1048576
+ self.ONEGBYTE = 1073741824
+ self.ONETBYTE = 1099511627776
+ self.MAXBYTE = 1024
+ self.MAXKBYTE = self.ONEKBYTE*99999
+ self.MAXMBYTE = self.ONEMBYTE*999
+ self.MAXGBYTE = self.ONEGBYTE*999
+ self.ANYFORMAT = 0
+ self.KBFORMAT = 1
+ self.MBFORMAT = 2
+ self.GBFORMAT = 3
+ self.TBFORMAT = 4
+ self.OVFORMAT = 9
+
+ def end_of_screen(self):
+ return self.p_stdscr.getyx()[0] >= self.apyx[0]-1
+ def set_line(self):
+ self.command_line = self.p_stdscr.getyx()[0]
+ self.p_stdscr.addstr('\n')
+ def next_line(self):
+ if self.p_stdscr.stdout:
+ print('')
+ return
+ line = self.p_stdscr.getyx()
+ apy = line[0]
+ if line[1] > 0:
+ apy += 1
+ self.p_stdscr.addstr(' ' * (self.apyx[1] - line[1]))
+ self.p_stdscr.move(apy, 0)
+ def valstr(self, value, width, avg_secs=0):
+ '''
+ Function valstr() converts 'value' to a string of 'width' fixed
+ number of positions. If 'value' does not fit, it will be formatted to
+ exponent-notation.
+ '''
+ maxval = 0
+ remain = 0
+ exp = 0
+ suffix = ""
+ strvalue = ""
+
+ if avg_secs:
+ value = (value + (avg_secs/2)) / avg_secs
+ width = width - 2
+ suffix = "/s"
+
+ maxval = pow(10.0, width) - 1
+ if value < 0:
+ sign = -1
+ value = abs(value)
+ else:
+ sign = 1
+
+ if value == 0:
+ strvalue = "%*d" % (width, value)
+ elif abs(value) >= 1: # exponent, if needed, will be positive
+ maxval = pow(10.0, width) - 1
+ if value > maxval:
+ # convert to E format: canonical form, fit width
+ maxval = pow(10.0, width-2) - 1
+ while value > maxval:
+ exp += 1
+ remain = value % 10
+ value /= 10
+
+ width -= 2
+ if remain >= 5:
+ value += 1
+ strvalue = "%*de%d%s" % (width, value * sign, exp, suffix)
+ else:
+ # E format not needed: split int and fraction, fit width
+ intval = str(int(value * sign))
+ fractional = str(value%1)[1:width-len(intval)+1]
+ if fractional == ".":
+ fractional = ""
+
+ prval = "%s%s" % (intval, fractional)
+ strvalue = "%*s%s" % (width, prval, suffix)
+ else: # exponent, if needed, will be negative
+ if value < 0.01:
+ # convert to E format: canonical form, fit width
+ width -= 3
+ while value < 1:
+ exp += 1
+ value *= 10
+
+ fractional = str(value%1)[1:width]
+ if fractional == ".":
+ fractional = ""
+ prval = "%d%s" % (value * sign, fractional)
+ strvalue = "%*se-%d%s" % (width, prval, exp, suffix)
+ else:
+ # E format not needed: reduce precision, remove trailing 0s
+ svalue = str(value * sign).replace("0.",".")[0:width]
+ strvalue = "%*s" % (width, svalue.rstrip('0'))
+ return strvalue
+
+ def memstr(self, value, width=6, pformat=-1, avg_secs=0):
+ '''
+ Function memstr() converts 'value' to a string of 'width' fixed
+ number of positions and a memory size unit specifier which may
+ optionally be specified 'pformat'; otherwise it is deduced.
+ '''
+ if pformat == -1:
+ pformat = self.ANYFORMAT
+ aformat = ""
+ verifyval = 0
+ suffix = ""
+ strvalue = ""
+
+ if value < 0:
+ verifyval = -value * 10
+ else:
+ verifyval = value
+
+ if avg_secs:
+ value /= avg_secs
+ verifyval *= 100
+ width -= 2
+ suffix = "/s"
+
+ if verifyval <= self.MAXBYTE: # bytes ?
+ aformat = self.ANYFORMAT
+ elif verifyval <= self.MAXKBYTE: # kbytes ?
+ aformat = self.KBFORMAT
+ elif verifyval <= self.MAXMBYTE: # mbytes ?
+ aformat = self.MBFORMAT
+ elif verifyval <= self.MAXGBYTE: # mbytes ?
+ aformat = self.GBFORMAT
+ else:
+ aformat = self.TBFORMAT
+
+ if aformat <= pformat:
+ aformat = pformat
+
+ if aformat == self.ANYFORMAT:
+ strvalue = "%s%s" % (self.valstr((value), width), suffix)
+ elif aformat == self.KBFORMAT:
+ strvalue = "%sK%s" % (self.valstr((value/self.ONEKBYTE), width-1), suffix)
+ elif aformat == self.MBFORMAT:
+ strvalue = "%sM%s" % (self.valstr((value/self.ONEMBYTE), width-1), suffix)
+ elif aformat == self.GBFORMAT:
+ strvalue = "%sG%s" % (self.valstr((value/self.ONEGBYTE), width-1), suffix)
+ elif aformat == self.TBFORMAT:
+ strvalue = "%sT%s" % (self.valstr((value/self.ONETBYTE), width-1), suffix)
+ else:
+ strvalue = "*****"
+
+ return strvalue
+
+
+# _ProcessorPrint --------------------------------------------------
+
+
+class _ProcessorPrint(_AtopPrint):
+# Missing: #trun (total # running threads)
+# Missing: #exit (requires accounting)
+# Substitutions: proc.runq.sleeping for #tslpi (threads sleeping)
+# Substitutions: proc.runq.blocked for #tslpu (threads uninterrupt sleep)
+ def prc(self):
+ self.p_stdscr.addstr('PRC |')
+ self.p_stdscr.addstr(' sys %8s |' % (minutes_seconds(self.ss.get_metric_value('kernel.all.cpu.sys'))))
+ self.p_stdscr.addstr(' user %7s |' % (minutes_seconds(self.ss.get_metric_value('kernel.all.cpu.user'))))
+ self.p_stdscr.addstr(' #proc %6d |' % (self.ss.get_metric_value('kernel.all.nprocs')))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' #tslpi %s |' % self.valstr(self.ss.get_metric_value('proc.runq.sleeping'), 5))
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr(' #tslpu %s |' % self.valstr(self.ss.get_metric_value('proc.runq.blocked'), 5))
+ self.p_stdscr.addstr(' #zombie %s' % self.valstr(self.ss.get_metric_value('proc.runq.defunct'), 4))
+ self.next_line()
+# Missing: curscal (current current scaling percentage)
+ def cpu(self):
+ self.ss.get_total()
+ self.p_stdscr.addstr('CPU |')
+ self.p_stdscr.addstr(' sys %s%% |' % self.valstr(100 * self.ss.get_metric_value('kernel.all.cpu.sys') / self.ss.cpu_total, 7))
+ self.p_stdscr.addstr(' user %s%% |' % self.valstr(100 * self.ss.get_metric_value('kernel.all.cpu.user') / self.ss.cpu_total, 6))
+ self.p_stdscr.addstr(' irq %7d%% |' % (
+ 100 * self.ss.get_metric_value('kernel.all.cpu.irq.hard') / self.ss.cpu_total +
+ 100 * self.ss.get_metric_value('kernel.all.cpu.irq.soft') / self.ss.cpu_total))
+ self.p_stdscr.addstr(' idle %s%% |' % self.valstr(100 * self.ss.get_metric_value('kernel.all.cpu.idle') / self.ss.cpu_total, 6))
+ self.p_stdscr.addstr(' wait %s%% |' % self.valstr(100 * self.ss.get_metric_value('kernel.all.cpu.wait.total') / self.ss.cpu_total, 6))
+ self.next_line()
+ ncpu = self.ss.get_metric_value('hinv.ncpu')
+ max_display_cpus = self.apyx[0] / 4
+ for k in range(ncpu):
+ percpu_sys = (100 * self.ss.get_scalar_value('kernel.percpu.cpu.sys', k) / self.ss.cpu_total)
+ percpu_user = (100 * self.ss.get_scalar_value('kernel.percpu.cpu.user', k) / self.ss.cpu_total)
+ if percpu_sys == 0 and percpu_user == 0:
+ continue
+ self.p_stdscr.addstr('cpu |')
+ self.p_stdscr.addstr(' sys %7d%% |' % percpu_sys)
+ self.p_stdscr.addstr(' user %6d%% |' % percpu_user)
+ self.p_stdscr.addstr(' irq %7d%% |' % (
+ 100 * self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k) / self.ss.cpu_total +
+ 100 * self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k) / self.ss.cpu_total))
+ self.p_stdscr.addstr(' idle %6d%% |' % (100 * self.ss.get_scalar_value('kernel.percpu.cpu.idle', k) / self.ss.cpu_total))
+ self.p_stdscr.addstr(' cpu%02d %5d%% |' % (k, 100 * self.ss.get_scalar_value('kernel.percpu.cpu.wait.total', k) / self.ss.cpu_total))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' curf %sMHz |' % (self.valstr(scale(self.ss.get_scalar_value('hinv.cpu.clock', k), 1000), 4)))
+ self.next_line()
+ if ncpu > max_display_cpus and k >= max_display_cpus:
+ break
+
+ self.p_stdscr.addstr('CPL |')
+ self.p_stdscr.addstr(' avg1 %s |' % self.valstr(self.ss.get_scalar_value('kernel.all.load', 0), 7))
+ self.p_stdscr.addstr(' avg5 %s |' % self.valstr(self.ss.get_scalar_value('kernel.all.load', 1), 7))
+ self.p_stdscr.addstr(' avg15 %s |' % self.valstr(self.ss.get_scalar_value('kernel.all.load', 2), 6))
+ self.p_stdscr.addstr(' csw %s |' % self.valstr(self.ss.get_metric_value('kernel.all.pswitch'), 8))
+ self.p_stdscr.addstr(' intr %s |' % self.valstr(self.ss.get_metric_value('kernel.all.intr'), 7))
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr(' |')
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' numcpu %2d |' % (self.ss.get_metric_value('hinv.ncpu')))
+ self.next_line()
+
+# _InterruptPrint --------------------------------------------------
+
+
+class _InterruptPrint(_AtopPrint):
+ pass
+
+
+# _DiskPrint --------------------------------------------------
+
+
+class _DiskPrint(_AtopPrint):
+ def interval_write(self, value):
+ self._interval = value
+ interval = property(None, interval_write, None, None)
+ def replay_archive_write(self, value):
+ self._replay_archive = value
+ replay_archive = property(None, replay_archive_write, None, None)
+
+ def disk(self, context):
+ try:
+ (inst, iname) = context.pmGetInDom(self.ss.metric_descs[self.ss.metrics_dict['disk.partitions.read']])
+ except pmapi.pmErr as e:
+ iname = iname = "X"
+
+# Missing: LVM avq (average queue depth)
+
+ lvms = dict(map(lambda x: (os.path.realpath("/dev/mapper/" + x)[5:], x),
+ (os.listdir("/dev/mapper"))))
+
+ for j in xrange(self.ss.get_len(self.ss.get_metric_value('disk.partitions.read'))):
+ if self._replay_archive == True:
+ if iname[j][:2] != "dm":
+ continue
+ lvm = iname[j]
+ else:
+ if iname[j] not in lvms:
+ continue
+ lvm = lvms[iname[j]]
+ partitions_read = self.ss.get_scalar_value('disk.partitions.read', j)
+ partitions_write = self.ss.get_scalar_value('disk.partitions.write', j)
+ if partitions_read == 0 and partitions_write == 0:
+ continue
+ self.p_stdscr.addstr('LVM |')
+ self.p_stdscr.addstr(' %-12s |' % (lvm[len(lvm)-12:]))
+ self.p_stdscr.addstr(' |')
+ self.p_stdscr.addstr(' read %s |' % self.valstr(partitions_read, 7))
+ self.p_stdscr.addstr(' write %s |' % self.valstr(partitions_write, 6))
+ if self.apyx[1] >= 95:
+ val = (float(self.ss.get_scalar_value('disk.partitions.blkread', j)) / float(self._interval * 1000)) * 100
+ self.p_stdscr.addstr(' MBr/s %s |' % self.valstr(val, 6))
+ if self.apyx[1] >= 110:
+ val = (float(self.ss.get_scalar_value('disk.partitions.blkwrite', j)) / float(self._interval * 1000)) * 100
+ self.p_stdscr.addstr(' MBw/s %s |' % self.valstr(val, 6))
+ if self.end_of_screen():
+ break
+ self.next_line()
+
+ try:
+ (inst, iname) = context.pmGetInDom(self.ss.metric_descs[self.ss.metrics_dict['disk.dev.read']])
+ except pmapi.pmErr as e:
+ iname = iname = "X"
+
+ for j in xrange(self.ss.get_len(self.ss.get_metric_value('disk.dev.read_bytes'))):
+ self.p_stdscr.addstr('DSK |')
+ self.p_stdscr.addstr(' %-12s |' % (iname[j]))
+ busy = (float(self.ss.get_scalar_value('disk.dev.avactive', j)) / float(self._interval * 1000)) * 100
+ if busy > 100:
+ busy = 0
+ self.p_stdscr.addstr(' busy %6d%% |' % (busy))
+ val = self.ss.get_scalar_value('disk.dev.read', j)
+ self.p_stdscr.addstr(' read %s |' % self.valstr(val, 7))
+ self.p_stdscr.addstr(' write %s |' % self.valstr(self.ss.get_scalar_value('disk.dev.write', j), 6))
+ if self.apyx[1] >= 95:
+ val = (float(self.ss.get_scalar_value('disk.partitions.blkread', j)) / float(self._interval * 1000)) * 100
+ self.p_stdscr.addstr(' MBr/s %s |' % self.valstr(val, 6))
+ if self.apyx[1] >= 110:
+ val = (float(self.ss.get_scalar_value('disk.partitions.blkwrite', j)) / float(self._interval * 1000)) * 100
+ self.p_stdscr.addstr(' MBw/s %s |' % self.valstr(val, 6))
+ try:
+ avio = (float(self.ss.get_scalar_value('disk.dev.avactive', j)) / float(self.ss.get_scalar_value('disk.dev.total', j)))
+ except ZeroDivisionError:
+ avio = 0
+ self.p_stdscr.addstr(' avio %4.2g ms |' % (avio))
+ if self.end_of_screen():
+ break
+ self.next_line()
+
+
+# _MemoryPrint --------------------------------------------------
+
+
+class _MemoryPrint(_AtopPrint):
+# Missing: shrss (resident shared memory size)
+ def mem(self):
+ self.p_stdscr.addstr('MEM |')
+ self.p_stdscr.addstr(' tot %s |' % (self.memstr(self.ss.get_metric_value('mem.physmem') * self.ONEKBYTE, 8)))
+ self.p_stdscr.addstr(' free %s |' % (self.memstr(self.ss.get_metric_value('mem.freemem') * self.ONEKBYTE, 7)))
+ self.p_stdscr.addstr(' cache %s |' % (self.memstr(self.ss.get_metric_value('mem.util.cached') * self.ONEKBYTE, 6)))
+ self.p_stdscr.addstr(' buff %s |' % (self.memstr(self.ss.get_metric_value('mem.util.bufmem') * self.ONEKBYTE, 7)))
+ self.p_stdscr.addstr(' slab %s |' % (self.memstr(self.ss.get_metric_value('mem.util.slab') * self.ONEKBYTE, 7)))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' #shmem %s |' % (self.memstr(self.ss.get_metric_value('mem.util.shmem') * self.ONEKBYTE, 5)))
+ self.next_line()
+
+ self.p_stdscr.addstr('SWP |')
+ self.p_stdscr.addstr(' tot %s |' % (self.memstr(self.ss.get_metric_value('mem.util.swapTotal') * self.ONEKBYTE, 8)))
+ self.p_stdscr.addstr(' free %s |' % (self.memstr(self.ss.get_metric_value('mem.util.swapFree') * self.ONEKBYTE, 7)))
+ self.p_stdscr.addstr(' |')
+ self.p_stdscr.addstr(' vmcom %s |' % (self.memstr(self.ss.get_metric_value('mem.util.committed_AS') * self.ONEKBYTE, 6)))
+ self.p_stdscr.addstr(' vmlim %s |' % (self.memstr(self.ss.get_metric_value('mem.util.commitLimit') * self.ONEKBYTE, 6)))
+ self.next_line()
+
+ self.p_stdscr.addstr('PAG |')
+ self.p_stdscr.addstr(' scan %s |' % (self.valstr(self.ss.get_metric_value('mem.vmstat.slabs_scanned'), 7)))
+ self.p_stdscr.addstr(' steal %s |' % (self.valstr(self.ss.get_metric_value('mem.vmstat.pginodesteal'), 6)))
+ self.p_stdscr.addstr(' stall %s |' % (self.valstr(self.ss.get_metric_value('mem.vmstat.allocstall'), 6)))
+ self.p_stdscr.addstr(' swin %s |' % (self.valstr(self.ss.get_metric_value('mem.vmstat.pswpin'), 7)))
+ self.p_stdscr.addstr(' swout %s |' % (self.valstr(self.ss.get_metric_value('mem.vmstat.pswpout'), 6)))
+ self.next_line()
+
+
+# _NetPrint --------------------------------------------------
+
+
+class _NetPrint(_AtopPrint):
+ def net(self, context):
+ if self.end_of_screen():
+ return
+ self.p_stdscr.addstr('NET | transport |')
+ self.p_stdscr.addstr(' tcpi %sM |' % self.valstr(self.ss.get_metric_value('network.tcp.insegs'), 6))
+ self.p_stdscr.addstr(' tcpo %sM |' % self.valstr(self.ss.get_metric_value('network.tcp.outsegs'), 6))
+ self.p_stdscr.addstr(' udpi %sM |' % self.valstr(self.ss.get_metric_value('network.udp.indatagrams'), 6))
+ self.p_stdscr.addstr(' udpo %sM |' % self.valstr(self.ss.get_metric_value('network.udp.outdatagrams'), 6))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' tcpao %sM |' % self.valstr(self.ss.get_metric_value('network.tcp.activeopens'), 5))
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr(' tcppo %sM |' % self.valstr(self.ss.get_metric_value('network.tcp.passiveopens'), 5))
+ self.next_line()
+
+# Missing: icmpi (internet control message protocol received datagrams)
+# Missing: icmpo (internet control message protocol transmitted datagrams)
+ self.p_stdscr.addstr('NET | network |')
+ self.p_stdscr.addstr(' ipi %sM |' % self.valstr(self.ss.get_metric_value('network.ip.inreceives'), 7))
+ self.p_stdscr.addstr(' ipo %sM |' % self.valstr(self.ss.get_metric_value('network.ip.outrequests'), 7))
+ self.p_stdscr.addstr(' ipfrw %sM |' % self.valstr(self.ss.get_metric_value('network.ip.forwdatagrams'), 5))
+ self.p_stdscr.addstr(' deliv %sM |' % self.valstr(self.ss.get_metric_value('network.ip.indelivers'), 5))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' icmpi %s |' % self.valstr(self.ss.get_metric_value('network.icmp.inmsgs'), 6))
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr(' icmpo %s |' % self.valstr(self.ss.get_metric_value('network.icmp.outmsgs'), 6))
+ self.next_line()
+
+ try:
+ (inst, iname) = context.pmGetInDom(self.ss.metric_descs[self.ss.metrics_dict['network.interface.in.bytes']])
+ except pmapi.pmErr as e:
+ iname = iname = "X"
+ net_metric = self.ss.get_metric_value('network.interface.in.bytes')
+ if type(net_metric) == type([]):
+ for j in xrange(len(self.ss.get_metric_value('network.interface.in.bytes'))):
+ pcki = self.ss.get_scalar_value('network.interface.in.packets', j)
+ pcko = self.ss.get_scalar_value('network.interface.out.packets', j)
+ if pcki == 0 and pcko == 0:
+ continue
+ self.p_stdscr.addstr('NET |')
+ self.p_stdscr.addstr(' %-12s |' % (iname[j]))
+ self.p_stdscr.addstr(' pcki %sM |' % self.valstr(pcki, 6))
+ self.p_stdscr.addstr(' pcko %sM |' % self.valstr(pcko, 6))
+ self.p_stdscr.addstr(' si %s Kbps |' % self.valstr(scale(self.ss.get_scalar_value('network.interface.in.bytes', j), 100000000), 4))
+ self.p_stdscr.addstr(' so %s Kpbs |' % self.valstr(scale(self.ss.get_scalar_value('network.interface.out.bytes', j), 100000000), 4))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr(' erri %sM |' % self.valstr(self.ss.get_scalar_value('network.interface.in.errors', j), 6))
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr(' erro %sM |' % self.valstr(self.ss.get_scalar_value('network.interface.out.errors', j), 6))
+ if self.end_of_screen():
+ break
+ self.next_line()
+
+
+# _ProcPrint --------------------------------------------------
+
+
+class _ProcPrint(_AtopPrint):
+ def type_write(self, value):
+ self._output_type = value
+ output_type = property(None, type_write, None, None)
+
+ @staticmethod
+ def sort_l(l1, l2):
+ if l1[1] < l2[1]:
+ return -1
+ elif l1[1] > l2[1]:
+ return 1
+ else: return 0
+
+ def proc(self):
+ if self._output_type in ['g']:
+ self.p_stdscr.addstr(' PID SYSCPU USRCPU VGROW RGROW RUID THR ST EXC S CPU CMD')
+ elif self._output_type in ['m']:
+ self.p_stdscr.addstr('PID ')
+ if self.apyx[1] >= 110:
+ self.p_stdscr.addstr('MINFLT MAJFLT ')
+ else:
+ self.p_stdscr.addstr(' ')
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr('VSTEXT VSLIBS ')
+ self.p_stdscr.addstr('VDATA VSTACK VGROW RGROW VSIZE RSIZE MEM CMD')
+ self.next_line()
+
+ # TODO Remember this state for Next/Previous Page
+ cpu_time_sorted = list()
+ for j in xrange(self.ss.get_metric_value('proc.nprocs')):
+ cpu_time_sorted.append((j, self.ss.get_scalar_value('proc.psinfo.utime', j)
+ + self.ss.get_scalar_value('proc.psinfo.stime', j)))
+ cpu_time_sorted.sort(self.sort_l, reverse=True)
+
+ for i in xrange(len(cpu_time_sorted)):
+ j = cpu_time_sorted[i][0]
+ if self._output_type in ['g', 'm']:
+ self.p_stdscr.addstr('%5d ' % (self.ss.get_scalar_value('proc.psinfo.pid', j)))
+ if self._output_type in ['g']:
+ self.p_stdscr.addstr('%6s ' % minutes_seconds(self.ss.get_scalar_value('proc.psinfo.stime', j)))
+ self.p_stdscr.addstr(' %6s ' % minutes_seconds(self.ss.get_scalar_value('proc.psinfo.utime', j)))
+ self.p_stdscr.addstr('%s ' % self.memstr(self.ss.get_scalar_value('proc.psinfo.vsize', j), 5))
+ self.p_stdscr.addstr('%s ' % self.memstr(self.ss.get_scalar_value('proc.psinfo.rss', j), 5))
+ self.p_stdscr.addstr('%6s ' % (self.ss.get_scalar_value('proc.id.uid_nm', j)[0:6]))
+ self.p_stdscr.addstr('%4d ' % self.ss.get_scalar_value('proc.psinfo.threads', j))
+ self.p_stdscr.addstr('%3s ' % '--')
+ state = self.ss.get_scalar_value('proc.psinfo.sname', j)
+ self.p_stdscr.addstr(' %2s ' % '-')
+ if state not in ('D', 'R', 'S', 'T', 'W', 'X', 'Z'):
+ state = 'S'
+ self.p_stdscr.addstr('%2s ' % (state))
+ cpu_total = float(self.ss.cpu_total - self.ss.get_metric_value('kernel.all.cpu.idle'))
+ proc_cpu_total = (self.ss.get_scalar_value('proc.psinfo.utime', j) + self.ss.get_scalar_value('proc.psinfo.stime', j))
+ if proc_cpu_total > cpu_total:
+ proc_percent = 0
+ else:
+ proc_percent = (100 * proc_cpu_total / cpu_total)
+ self.p_stdscr.addstr('%2d%% ' % proc_percent)
+ self.p_stdscr.addstr('%-15s ' % (self.ss.get_scalar_value('proc.psinfo.cmd', j)))
+ if self._output_type in ['m']:
+ # Missing: SWAPSZ, proc.psinfo.nswap frequently returns -1
+ if self.apyx[1] >= 110:
+ minf = self.ss.get_scalar_value('proc.psinfo.minflt', j)
+ majf = self.ss.get_scalar_value('proc.psinfo.maj_flt', j)
+ if minf < 0:
+ minf = 0
+ if majf < 0:
+ majf = 0
+ self.p_stdscr.addstr("%s " % self.valstr(minf, 3))
+ self.p_stdscr.addstr("%s " % self.valstr(majf, 3))
+ if self.apyx[1] >= 95:
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.memory.textrss', j) * self.ONEKBYTE, 7))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.memory.librss', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.memory.datrss', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.memory.vmstack', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.psinfo.vsize', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.psinfo.rss', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.psinfo.vsize', j) * self.ONEKBYTE, 6))
+ self.p_stdscr.addstr("%s " % self.memstr(self.ss.get_scalar_value('proc.psinfo.rss', j) * self.ONEKBYTE, 6))
+ val = float(self.ss.get_old_scalar_value('proc.psinfo.rss', j)) / float(self.ss.get_metric_value('mem.physmem')) * 100
+ if val > 100:
+ val = 0
+ self.p_stdscr.addstr('%2d%% ' % val)
+ self.p_stdscr.addstr('%-15s' % (self.ss.get_scalar_value('proc.psinfo.cmd', j)))
+ if self.end_of_screen():
+ break
+ self.next_line()
+
+
+class _Options(object):
+ def __init__(self):
+ self.input_file = ""
+ self.output_file = ""
+ self.output_type = "g"
+ self.host = "local:"
+ self.create_archive = False
+ self.replay_archive = False
+ self.have_interval_arg = False
+ self.interval_arg = 5
+ self.width = 0
+ self.n_samples = 0
+ self.opts = self.setup()
+
+ def setup(self):
+ """ Setup default command line argument option handling """
+ opts = pmapi.pmOptions()
+ opts.pmSetOptionCallback(self.option_callback)
+ opts.pmSetOverrideCallback(self.override)
+ # leading - returns args that are not options with leading ^A
+ opts.pmSetShortOptions("-gmw:r:L:h:V?")
+ opts.pmSetLongOptionHeader("Options")
+ opts.pmSetLongOption("generic", 0, 'g', '', "Display generic metrics")
+ opts.pmSetLongOption("memory", 0, 'm', '', "Display memory metrics")
+ opts.pmSetLongOption("write", 1, 'w', 'FILENAME', "Write metric data to file")
+ opts.pmSetLongOption("read", 1, 'r', 'FILENAME', "Read metric data from file")
+ opts.pmSetLongOption("width", 1, 'L', 'WIDTH', "Width of the output")
+ opts.pmSetShortUsage("[options]\nInteractive: [-g|-m] [-L linelen] [-h host] [ interval [ samples ]]\nWrite raw logfile: pmatop -w rawfile [ interval [ samples ]]\nRead raw logfile: pmatop -r [ rawfile ] [-g|-m] [-L linelen] [-h host]")
+ opts.pmSetLongOptionHost()
+ opts.pmSetLongOptionVersion()
+ opts.pmSetLongOptionHelp()
+ return opts
+
+
+ def override(self, opt):
+ """ Override a few standard PCP options to match free(1) """
+ # pylint: disable=R0201
+ if opt == 'g':
+ return 1
+ return 0
+
+ def option_callback(self, opt, optarg, index):
+ """ Perform setup for an individual command line option """
+ # pylint: disable=W0613
+
+ if opt == "g":
+ self.output_type = "g"
+ elif opt == "m":
+ self.output_type = "m"
+ elif opt == "w":
+ self.output_file = optarg
+ self.create_archive = True
+ elif opt == "r":
+ self.opts.pmSetOptionArchiveFolio(optarg)
+ self.input_file = optarg
+ self.replay_archive = True
+ elif opt == "L":
+ self.width = int(optarg)
+ elif opt == 'h':
+ self.host = optarg
+ elif opt == "":
+ if self.have_interval_arg == False:
+ self.interval_arg = optarg
+ self.have_interval_arg = True
+ else:
+ self.n_samples = int(optarg)
+
+
+# main ----------------------------------------------------------------------
+
+
+def main(stdscr_p):
+ global stdscr
+ stdscr = _StandardOutput(stdscr_p)
+ sort = ""
+ duration = 0.0
+ i = 1
+
+ ss = Subsystem()
+ ss.init_processor_metrics()
+ ss.init_memory_metrics()
+ ss.init_disk_metrics()
+ ss.init_network_metrics()
+ ss.init_process_metrics()
+
+ cpu = _ProcessorPrint(ss, stdscr)
+ mem = _MemoryPrint(ss, stdscr)
+ disk = _DiskPrint(ss, stdscr)
+ net = _NetPrint(ss, stdscr)
+ proc = _ProcPrint(ss, stdscr)
+
+ proc.output_type = opts.output_type
+ stdscr.width = opts.width
+
+ pmc = pmapi.pmContext.fromOptions(opts.opts, sys.argv)
+ if pmc.type == c_api.PM_CONTEXT_ARCHIVE:
+ pmc.pmSetMode(c_api.PM_MODE_FORW, pmapi.timeval(0, 0), 0)
+
+
+ host = pmc.pmGetContextHostName()
+
+ (delta, errmsg) = pmc.pmParseInterval(str(opts.interval_arg) + " seconds")
+
+ ss.setup_metrics(pmc)
+
+ if opts.create_archive:
+ delta_seconds = c_api.pmtimevalToReal(delta.tv_sec, delta.tv_usec)
+ msec = str(int(1000.0 * delta_seconds))
+ configuration = "log mandatory on every " + msec + " milliseconds { "
+ configuration += ss.dump_metrics()
+ configuration += "}"
+ if opts.n_samples != 0:
+ duration = float(opts.n_samples) * delta_seconds
+ else:
+ duration = float(10) * delta_seconds
+ status = record(pmgui.GuiClient(), configuration, duration, opts.output_file, host)
+ if status != "":
+ return status
+ record_add_creator(opts.output_file)
+ sys.exit(0)
+
+ i_samples = 0
+
+ disk.interval = delta.tv_sec
+ disk.replay_archive = opts.replay_archive
+
+ try:
+ elapsed = ss.get_metric_value('kernel.all.uptime')
+ while (i_samples < opts.n_samples) or (opts.n_samples == 0):
+ ss.get_stats(pmc)
+ stdscr.move(0, 0)
+ stdscr.addstr('ATOP - %s\t\t%s elapsed\n\n' % (
+ time.strftime("%c"),
+ datetime.timedelta(0, elapsed)))
+ elapsed = delta.tv_sec
+ stdscr.move(2, 0)
+
+ try:
+ cpu.prc()
+ cpu.cpu()
+ mem.mem()
+ disk.disk(pmc)
+ net.net(pmc)
+ proc.set_line()
+ proc.proc()
+ except pmapi.pmErr as e:
+ return str(e) + " while processing " + str(ssx[0])
+ except Exception as e: # catch all errors, pcp or python or other
+ pass
+ stdscr.move(proc.command_line, 0)
+ stdscr.refresh()
+
+ stdscr.timeout(delta.tv_sec * 1000)
+ char = stdscr.getch()
+
+ if char != -1: # user typed a command
+ try:
+ cmd = chr(char)
+ except ValueError:
+ cmd = None
+ if cmd == "q":
+ raise KeyboardInterrupt
+ elif cmd == " ":
+ stdscr.clear()
+ stdscr.refresh()
+ elif cmd == "z":
+ stdscr.timeout(-1)
+ # currently it just does "hit any key to continue"
+ char = stdscr.getch()
+ elif cmd == "h" or cmd == "?":
+ stdscr.clear ()
+ stdscr.move (0, 0)
+ stdscr.addstr ('\nOptions shown for active processes:\n')
+ stdscr.addstr ( "'g' - generic info (default)\n")
+ stdscr.addstr ( "'m' - memory details\n")
+ stdscr.addstr ( "Miscellaneous commands:\n")
+ stdscr.addstr ("'z' - pause-button to freeze current sample (toggle)\n")
+ stdscr.addstr ("^L - redraw the screen\n")
+ stdscr.addstr ("hit any key to continue\n")
+ stdscr.timeout(-1)
+ char = stdscr.getch()
+ stdscr.clear()
+ elif cmd in ['g', 'm']:
+ stdscr.clear()
+ proc.output_type = cmd
+ # TODO Next/Previous Page
+ else:
+ stdscr.move(proc.command_line, 0)
+ stdscr.addstr("Invalid command %s\n" % (cmd), True)
+ stdscr.addstr("Type 'h' to see a list of valid commands", True)
+ stdscr.refresh()
+ time.sleep(2)
+ i_samples += 1
+ except KeyboardInterrupt:
+ pass
+ stdscr.refresh()
+ time.sleep(1)
+ return ""
+
+def sigwinch_handler(n, frame):
+ global stdscr
+ curses.endwin()
+ curses.initscr()
+ # consume any subsequent characters awhile
+ while 1:
+ char = stdscr.getch()
+ if char == -1:
+ break
+
+if __name__ == '__main__':
+ global opts
+ opts = _Options()
+ if c_api.pmGetOptionsFromList(sys.argv) != 0:
+ c_api.pmUsageMessage()
+ sys.exit(1)
+
+ if sys.stdout.isatty():
+ signal.signal(signal.SIGWINCH, sigwinch_handler)
+ try:
+ status = curses.wrapper(main) # pylint: disable-msg=C0103
+ except curses.error as e:
+ status = "Error in the curses module. Try running " + ME + " in a larger window."
+ else: # Output is piped or redirected
+ status = main(sys.stdout)
+ if status != "":
+ print(status)
diff --git a/src/pmcd/GNUmakefile b/src/pmcd/GNUmakefile
new file mode 100644
index 0000000..6370205
--- /dev/null
+++ b/src/pmcd/GNUmakefile
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+LDIRT = *.log
+
+default : $(SUBDIRS) pmcd.service
+ $(SUBDIRS_MAKERULE)
+
+install : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMCDOPTIONS_PATH)`
+ $(INSTALL) -m 644 pmcd.options $(PCP_PMCDOPTIONS_PATH)
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMCDRCLOCAL_PATH)`
+ $(INSTALL) -m 755 rc_local $(PCP_PMCDRCLOCAL_PATH)
+ $(INSTALL) -m 755 rc_pmcd $(PCP_RC_DIR)/pmcd
+ $(INSTALL) -m 755 rc_pcp $(PCP_RC_DIR)/pcp
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmcd.service $(PCP_SYSTEMDUNIT_DIR)/pmcd.service
+endif
+ $(INSTALL) -m 644 pmdaproc.sh $(PCP_SHARE_DIR)/lib/pmdaproc.sh
+ $(INSTALL) -m 644 rc-proc.sh $(PCP_SHARE_DIR)/lib/rc-proc.sh
+ $(INSTALL) -m 644 rc-proc.sh.minimal $(PCP_SHARE_DIR)/lib/rc-proc.sh.minimal
+ $(INSTALL) -o $(PCP_USER) -g $(PCP_GROUP) -m 775 -d $(PCP_LOG_DIR)/pmcd
+ifneq ($(PACKAGE_DISTRIBUTION),debian)
+ $(INSTALL) -m 755 -d $(PCP_SYSCONF_DIR)/pmcd
+endif
+ $(INSTALL) -m 644 sasl2.conf $(PCP_SASLCONF_DIR)/pmcd.conf
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+pmcd.service: pmcd.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
diff --git a/src/pmcd/pmcd.options b/src/pmcd/pmcd.options
new file mode 100644
index 0000000..bcd501e
--- /dev/null
+++ b/src/pmcd/pmcd.options
@@ -0,0 +1,36 @@
+# command-line options to pmcd, uncomment/edit lines as required
+
+# listen for connections to pmcd on only the interface bound to this
+# IP address
+# -i 192.168.0.100
+
+# longer timeout delay for slow agents
+# -t 10
+
+# or suppress timeouts
+# -t 0
+
+# make log go someplace else
+# -l /some/place/else
+
+# debugging knobs, see pmdbg(1)
+# -D fetch,pmns
+
+# run in the foreground (not as a daemon)
+# -f
+
+# maximum incoming PDU size (default 64KB)
+# -L 16384
+
+# assume identity of some user other than "pcp"
+# -U root
+
+# enable event tracing bit fields
+# 1 trace client connections
+# 2 trace PDUs
+# 256 unbuffered tracing
+# -T 3
+
+# setting of environment variables for pmcd and
+# the PCP rc scripts. See pmcd(1) and PMAPI(3).
+# PMCD_WAIT_TIMEOUT=120
diff --git a/src/pmcd/pmcd.service b/src/pmcd/pmcd.service
new file mode 100644
index 0000000..aea489d
--- /dev/null
+++ b/src/pmcd/pmcd.service
@@ -0,0 +1,14 @@
+[Unit]
+Description=Performance Metrics Collector Daemon
+Documentation=man:pmcd(8)
+Wants=avahi-daemon.service
+After=local-fs.target network.target avahi-daemon.service
+
+[Service]
+Type=oneshot
+ExecStart=/etc/init.d/pmcd start
+ExecStop=/etc/init.d/pmcd stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmcd/pmcd.service.in b/src/pmcd/pmcd.service.in
new file mode 100644
index 0000000..8da7798
--- /dev/null
+++ b/src/pmcd/pmcd.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=Performance Metrics Collector Daemon
+Documentation=man:pmcd(8)
+Wants=avahi-daemon.service
+After=local-fs.target network.target avahi-daemon.service
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmcd start
+ExecStop=@path@/pmcd stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmcd/pmdaproc.sh b/src/pmcd/pmdaproc.sh
new file mode 100644
index 0000000..0ef6eae
--- /dev/null
+++ b/src/pmcd/pmdaproc.sh
@@ -0,0 +1,1457 @@
+# Common sh(1) procedures to be used in the Performance Co-Pilot
+# PMDA Install and Remove scripts
+#
+# Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+# Portions Copyright (c) 2008 Aconex. All Rights Reserved.
+# Portions Copyright (c) 2013-2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d $PCP_TMPFILE_DIR/pmdaproc.XXXXXXXXX` || exit 1
+pmdatmp=$tmp
+trap "rm -rf $pmdatmp $pcptmp; exit" 0 1 2 3 15
+
+_setup_platform()
+{
+ case "$PCP_PLATFORM"
+ in
+ mingw)
+ uid=0 # no permissions we can usefully test here
+ dso_suffix=dll
+ default_pipe_opt=false
+ default_socket_opt=true
+ CHOWN=": skip chown"
+ CHMOD=chmod
+ ;;
+ *)
+ eval `id | sed -e 's/(.*//'`
+ dso_suffix=so
+ [ "$PCP_PLATFORM" = darwin ] && dso_suffix=dylib
+ default_pipe_opt=true
+ default_socket_opt=false
+ CHOWN=chown
+ CHMOD=chmod
+ ;;
+ esac
+}
+
+_setup_localhost()
+{
+ # Try to catch some truly evil preconditions. If you cannot reach
+ # localhost, all bets are off!
+ #
+ if which ping >/dev/null 2>&1
+ then
+ __opt=''
+ # Larry Wall style hunt for the version of ping that is first
+ # on our path
+ #
+ ping --help >$tmp/hlp 2>&1
+ if grep '.-c count' $tmp/hlp >/dev/null 2>&1
+ then
+ __opt='-c 1 localhost'
+ elif grep '.-n count' $tmp/hlp >/dev/null 2>&1
+ then
+ __opt='-n 1 localhost'
+ elif grep 'host .*packetsize .*count' $tmp/hlp >/dev/null 2>&1
+ then
+ __opt='localhost 56 1'
+ elif grep 'host .*data_size.*npackets' $tmp/hlp >/dev/null 2>&1
+ then
+ __opt='localhost 56 1'
+ fi
+ if [ -z "$__opt" ]
+ then
+ echo "Warning: can't find a ping(1) that I understand ... pushing on"
+ else
+ if ping $__opt >$tmp/hlp 2>&1
+ then
+ :
+ else
+ # failing that, try 3 pings ... failure means all 3 were lost,
+ # and so there is no hope of continuing
+ #
+ __opt=`echo "$__opt" | sed -e 's/1/3/'`
+ if ping $__opt >/dev/null 2>&1
+ then
+ :
+ else
+ echo "Error: no route to localhost, pmcd reconfiguration abandoned"
+ exit 1
+ fi
+ fi
+ fi
+ fi
+}
+
+_setup_localhost
+_setup_platform
+rm -rf $tmp
+
+# some useful common variables for Install/Remove scripts
+#
+# put your PMNS files here
+PMNSDIR=$PCP_VAR_DIR/pmns
+
+# pmcd and pcp log files here
+if [ ! -z "$PCP_LOGDIR" ]
+then
+ # this is being discouraged and is no longer documented anywhere
+ LOGDIR=$PCP_LOGDIR
+else
+ if [ -d $PCP_LOG_DIR/pmcd ]
+ then
+ # the preferred naming scheme
+ #
+ LOGDIR=$PCP_LOG_DIR/pmcd
+ else
+ # backwards compatibility for IRIX
+ #
+ LOGDIR=$PCP_LOG_DIR
+ fi
+fi
+
+# writeable root of PMNS
+NAMESPACE=${PMNS_DEFAULT-$PMNSDIR/root}
+PMNSROOT=`basename $NAMESPACE`
+
+# echo without newline - deprecated - use the $PCP_ECHO_* ones from
+# /etc/pcp.conf instead, however some old Install and Remove scripts may
+# still use $ECHONL, so keep it here
+#
+ECHONL="echo -n"
+
+# Install control variables
+# Can install as DSO?
+dso_opt=false
+# Can install as perl script?
+perl_opt=false
+# Can install as python script?
+python_opt=false
+# Can install as daemon?
+daemon_opt=true
+# If daemon, pipe?
+pipe_opt=$default_pipe_opt
+# If daemon, socket? and default for Internet sockets?
+socket_opt=$default_socket_opt
+socket_inet_def=''
+# IPC Protocol for daemon (binary only now)
+ipc_prot=binary
+# Need to force a restart of pmcd?
+forced_restart=true
+# Delay after install before checking (sec)
+check_delay=3
+# Additional command line args to go in $PCP_PMCDCONF_PATH
+args=""
+# ditto for perl PMDAs
+perl_args=""
+# ditto for python PMDAS
+python_args=""
+# Source for the pmns
+pmns_source=pmns
+# Source for the helptext
+help_source=help
+# Assume libpcp_pmda.so.1
+pmda_interface=1
+# Full pathname to directory where PMDA is to be found ...
+# exectable and/or DSO, domain.h, pmns, control files, etc.
+pmda_dir="`pwd`"
+
+
+# Other variables and constants
+#
+prog=`basename $0`
+tmp=`mktemp -d $PCP_TMPFILE_DIR/pcp.XXXXXXXXX` || exit 1
+pcptmp=$tmp
+do_pmda=true
+do_check=true
+__here=`pwd`
+__pmcd_is_dead=false
+__echo=false
+__verbose=false
+__ns_opt=''
+
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+# Parse command line args
+#
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+ -e) # echo user input
+ __echo=true
+ ;;
+
+ -N) # name space only
+ do_pmda=false
+ ;;
+
+ -n) # alternate name space
+ if [ $# -lt 2 ]
+ then
+ echo "$prog: -n requires a name space file option"
+ exit 1
+ fi
+ NAMESPACE=$2
+ PMNSROOT=`basename $NAMESPACE`
+ PMNSDIR=`dirname $NAMESPACE`
+ __ns_opt="-n $2"
+ shift
+ ;;
+
+ -Q) # skip check for metrics going away
+ do_check=false
+ ;;
+
+ -R) # $ROOT
+ if [ "$prog" = "Remove" ]
+ then
+ echo "Usage: $prog [-eNQV] [-n namespace]"
+ exit 1
+ fi
+ if [ $# -lt 2 ]
+ then
+ echo "$prog: -R requires a directory option"
+ exit 1
+ fi
+ root=$2
+ shift
+ ;;
+
+ -V) # verbose
+ __verbose=true
+ ;;
+
+ *)
+ if [ "$prog" = "Install" ]
+ then
+ echo "Usage: $prog [-eNQV] [-n namespace] [-R rootdir]"
+ else
+ echo "Usage: $prog [-eNQV] [-n namespace]"
+ fi
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+# wait for pmcd to be alive again
+# Usage: __wait_for_pmcd [can_wait]
+#
+__wait_for_pmcd()
+{
+ # 60 seconds default seems like a reasonble max time to get going
+ [ -z "$__can_wait" ] && __can_wait=${1-60}
+ if pmcd_wait -t $__can_wait
+ then
+ :
+ else
+ echo "Arrgghhh ... PMCD failed to start after $__can_wait seconds"
+ if [ -f $LOGDIR/pmcd.log ]
+ then
+ echo "Here is the PMCD logfile ($LOGDIR/pmcd.log):"
+ ls -l $LOGDIR/pmcd.log
+ cat $LOGDIR/pmcd.log
+ else
+ echo "No trace of the PMCD logfile ($LOGDIR/pmcd.log)!"
+ fi
+ __pmcd_is_dead=true
+ fi
+}
+
+# try and put pmcd back the way it was
+#
+__restore_pmcd()
+{
+ if [ -f $tmp/pmcd.conf.save ]
+ then
+ __pmcd_is_dead=false
+ echo
+ echo "Save current PMCD control file in $PCP_PMCDCONF_PATH.prev ..."
+ rm -f $PCP_PMCDCONF_PATH.prev
+ mv $PCP_PMCDCONF_PATH $PCP_PMCDCONF_PATH.prev
+ echo "Restoring previous PMCD control file, and trying to restart PMCD ..."
+ cp $tmp/pmcd.conf.save $PCP_PMCDCONF_PATH
+ eval $CHOWN root $PCP_PMCDCONF_PATH
+ eval $CHMOD 644 $PCP_PMCDCONF_PATH
+ rm -f $tmp/pmcd.conf.save
+ $PCP_RC_DIR/pcp start
+ __wait_for_pmcd
+ fi
+ if $__pmcd_is_dead
+ then
+ echo
+ echo "Sorry, failed to restart PMCD."
+ fi
+}
+
+# __pmda_cull name domain
+#
+__pmda_cull()
+{
+ # context and integrity checks
+ #
+ if [ $# -ne 2 ]
+ then
+ echo "pmdaproc.sh: internal botch: __pmda_cull() called with $# (instead of 2) arguments"
+ exit 1
+ fi
+ [ ! -f $PCP_PMCDCONF_PATH ] && return
+ if eval $CHMOD u+w $PCP_PMCDCONF_PATH
+ then
+ :
+ else
+ echo "pmdaproc.sh: __pmda_cull: Unable to make $PCP_PMCDCONF_PATH writable"
+ exit 1
+ fi
+ if [ ! -w $PCP_PMCDCONF_PATH ]
+ then
+ echo "pmdaproc.sh: \"$PCP_PMCDCONF_PATH\" is not writeable"
+ exit 1
+ fi
+
+ # remove matching entry from $PCP_PMCDCONF_PATH if present
+ #
+ $PCP_AWK_PROG <$PCP_PMCDCONF_PATH >$tmp/pmcd.conf '
+BEGIN { status = 0 }
+$1 == "'"$1"'" && $2 == "'"$2"'" { status = 1; next }
+ { print }
+END { exit status }'
+ if [ $? -eq 0 ]
+ then
+ # no match
+ :
+ else
+
+ # log change to the PCP NOTICES file
+ #
+ $PCP_BINADM_DIR/pmpost "PMDA cull: from $PCP_PMCDCONF_PATH: $1 $2"
+
+ # save pmcd.conf in case we encounter a problem, and then
+ # install updated $PCP_PMCDCONF_PATH
+ #
+ cp $PCP_PMCDCONF_PATH $tmp/pmcd.conf.save
+ cp $tmp/pmcd.conf $PCP_PMCDCONF_PATH
+ eval $CHOWN root $PCP_PMCDCONF_PATH
+ eval $CHMOD 644 $PCP_PMCDCONF_PATH
+
+ # signal pmcd if it is running
+ #
+ if pminfo -v pmcd.version >/dev/null 2>&1
+ then
+ pmsignal -a -s HUP pmcd >/dev/null 2>&1
+ # allow signal processing to be done before checking status
+ sleep 2
+ __wait_for_pmcd
+ if $__pmcd_is_dead
+ then
+ __restore_pmcd
+ # give PMCD a chance to get back into original state
+ sleep 3
+ __wait_for_pmcd
+ fi
+ fi
+ fi
+ rm -f $tmp/pmcd.conf
+
+ # stop any matching PMDA that is still running
+ #
+ for __sig in TERM KILL
+ do
+ __pids=`_get_pids_by_name pmda$1`
+ if [ ! -z "$__pids" ]
+ then
+ pmsignal -s $__sig $__pids >/dev/null 2>&1
+ # allow signal processing to be done
+ sleep 2
+ else
+ break
+ fi
+ done
+}
+
+# __pmda_add "entry for $PCP_PMCDCONF_PATH"
+#
+__pmda_add()
+{
+ # context and integrity checks
+ #
+ if [ $# -ne 1 ]
+ then
+ echo "pmdaproc.sh: internal botch: __pmda_add() called with $# (instead of 1) arguments"
+ exit 1
+ fi
+ if eval $CHMOD u+w $PCP_PMCDCONF_PATH
+ then
+ :
+ else
+ echo "pmdaproc.sh: __pmda_add: Unable to make $PCP_PMCDCONF_PATH writable"
+ exit 1
+ fi
+ if [ ! -w $PCP_PMCDCONF_PATH ]
+ then
+ echo "pmdaproc.sh: \"$PCP_PMCDCONF_PATH\" is not writeable"
+ exit 1
+ fi
+
+ # save pmcd.conf in case we encounter a problem
+ #
+ cp $PCP_PMCDCONF_PATH $tmp/pmcd.conf.save
+
+ myname=`echo $1 | $PCP_AWK_PROG '{print $1}'`
+ mydomain=`echo $1 | $PCP_AWK_PROG '{print $2}'`
+ # add entry to $PCP_PMCDCONF_PATH
+ #
+ echo >$tmp/pmcd.body
+ echo >$tmp/pmcd.access
+ $PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+NF==0 { next }
+/^[ ]*\[[ ]*access[ ]*\]/ { state = 2 }
+state == 2 { print >"'$tmp/pmcd.access'"; next }
+$1=="'$myname'" && $2=="'$mydomain'" { next }
+ { print >"'$tmp/pmcd.body'"; next }'
+ ( cat $tmp/pmcd.body \
+ ; echo "$1" \
+ ; cat $tmp/pmcd.access \
+ ) >$PCP_PMCDCONF_PATH
+ rm -f $tmp/pmcd.access $tmp/pmcd.body
+ eval $CHOWN root $PCP_PMCDCONF_PATH
+ eval $CHMOD 644 $PCP_PMCDCONF_PATH
+
+ # log change to pcplog/NOTICES
+ #
+ $PCP_BINADM_DIR/pmpost "PMDA add: to $PCP_PMCDCONF_PATH: $1"
+
+ # signal pmcd if it is running (and ok to do so), else start it
+ #
+ if ! $forced_restart && pminfo -v pmcd.version >/dev/null 2>&1
+ then
+ pmsignal -a -s HUP pmcd >/dev/null 2>&1
+ # allow signal processing to be done before checking status
+ sleep 2
+ __wait_for_pmcd
+ $__pmcd_is_dead && __restore_pmcd
+ else
+ log=$LOGDIR/pmcd.log
+ rm -f $log
+ $PCP_RC_DIR/pcp start
+ __wait_for_pmcd
+ $__pmcd_is_dead && __restore_pmcd
+ fi
+}
+
+# expect -R root or $ROOT not set in environment
+#
+__check_root()
+{
+ if [ "X$root" != X ]
+ then
+ ROOT="$root"
+ export ROOT
+ else
+ if [ "X$ROOT" != X -a "X$ROOT" != X/ ]
+ then
+ echo "Install: \$ROOT was set to \"$ROOT\""
+ echo " Use -R rootdir to install somewhere other than /"
+ exit 1
+ fi
+ fi
+}
+
+# should be able to extract default domain from domain.h
+#
+__check_domain()
+{
+ if [ -f domain.h ]
+ then
+ __infile=domain.h
+ elif [ -f domain.h.perl ]
+ then
+ __infile=domain.h.perl
+ elif [ -f domain.h.python ]
+ then
+ __infile=domain.h.python
+ else
+ echo "Install: cannot find ./domain.h to determine the Performance Metrics Domain"
+ exit 1
+ fi
+ # $domain is for backwards compatibility, modern PMDAs
+ # have something like
+ # #define FOO 123
+ #
+ domain=''
+ eval `$PCP_AWK_PROG <$__infile '
+/^#define/ && $3 ~ /^[0-9][0-9]*$/ { print $2 "=" $3
+ if (seen == 0) {
+ print "domain=" $3
+ sub(/^PMDA/, "", $2)
+ print "SYMDOM=" $2
+ seen = 1
+ }
+ }'`
+ if [ "X$domain" = X ]
+ then
+ echo "Install: cannot determine the Performance Metrics Domain from ./domain.h"
+ exit 1
+ fi
+}
+
+# handle optional configuration files that maybe already given in an
+# $PCP_PMCDCONF_PATH line or user-supplied or some default or sample
+# file
+#
+# before calling _choose_configfile, optionally define the following
+# variables
+#
+# Name Default Use
+#
+# configdir $PCP_VAR_DIR/config/$iam directory for config ... assumed
+# name is $iam.conf in this directory
+#
+# configfile "" set if have a preferred choice,
+# e.g. from $PCP_PMCDCONF_PATH
+# this will be set on return if we've
+# found an acceptable config file
+#
+# default_configfile
+# "" if set, this is the default which
+# will be offered
+#
+# Note:
+# If the choice is aborted then $configfile will be set to empty.
+# Therefore, there should be a test for an empty $configfile after
+# the call to this function.
+#
+_choose_configfile()
+{
+ configdir=${configdir-$PCP_VAR_DIR/config/$iam}
+
+ if [ ! -d $configdir ]
+ then
+ mkdir -p $configdir
+ fi
+
+ while true
+ do
+ echo "Possible configuration files to choose from:"
+ # List viable alternatives
+ __i=0 # menu item number
+ __filelist="" # list of configuration files
+ __choice="" # the choice of configuration file
+ __choice1="" # the menu item for the 1st possible choice
+ __choice2="" # the menu item for the 2nd possible choice
+ __choice3="" # the menu item for the 3rd possible choice
+
+ if [ ! -z "$configfile" ]
+ then
+ if [ -f $configfile ]
+ then
+ __i=`expr $__i + 1`
+ __choice1=$__i
+ __filelist="$__filelist $configfile"
+ echo "[$__i] $configfile"
+ fi
+ fi
+
+ if [ -f $configdir/$iam.conf ]
+ then
+ if echo $__filelist | grep "$configdir/$iam.conf" >/dev/null
+ then
+ :
+ else
+ __i=`expr $__i + 1`
+ __choice2=$__i
+ __filelist="$__filelist $configdir/$iam.conf"
+ echo "[$__i] $configdir/$iam.conf"
+ fi
+ fi
+
+ if [ -f $default_configfile ]
+ then
+ if echo $__filelist | grep "$default_configfile" >/dev/null
+ then
+ :
+ else
+ __i=`expr $__i + 1`
+ __choice3=$__i
+ __filelist="$__filelist $default_configfile"
+ echo "[$__i] $default_configfile"
+ fi
+ fi
+
+ __i=`expr $__i + 1`
+ __own_choice=$__i
+ echo "[$__i] Specify your own configuration file."
+
+ __i=`expr $__i + 1`
+ __abort_choice=$__i
+ echo "[$__i] None of the above (abandon configuration file selection)."
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Which configuration file do you want to use ? [1] ""$PCP_ECHO_C"
+ read __reply
+ $__echo && echo "$__reply"
+
+ # default
+ if [ -z "$__reply" ]
+ then
+ __reply=1
+ fi
+
+ # Process the reply from the user
+ if [ $__reply = $__own_choice ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Enter the name of the existing configuration file: ""$PCP_ECHO_C"
+ read __choice
+ $__echo && echo "$__choice"
+ if [ ! -f "$__choice" ]
+ then
+ echo "Cannot open \"$__choice\"."
+ echo ""
+ echo "Please choose another configuration file."
+ __choice=""
+ fi
+ elif [ $__reply = $__abort_choice ]
+ then
+ echo "Abandoning configuration file selection."
+ configfile=""
+ return 0
+ elif [ "X$__reply" = "X$__choice1" -o "X$__reply" = "X$__choice2" -o "X$__reply" = "X$__choice3" ]
+ then
+ # extract nth field as the file
+ __choice=`echo $__filelist | $PCP_AWK_PROG -v n=$__reply '{ print $n }'`
+ else
+ echo "Illegal choice: $__reply"
+ echo ""
+ echo "Please choose number between: 1 and $__i"
+ fi
+
+ if [ ! -z "$__choice" ]
+ then
+ echo
+ echo "Contents of the selected configuration file:"
+ echo "--------------- start $__choice ---------------"
+ cat $__choice
+ echo "--------------- end $__choice ---------------"
+ echo
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Use this configuration file? [y] ""$PCP_ECHO_C"
+ read ans
+ $__echo && echo "$ans"
+ if [ ! -z "$ans" -a "X$ans" != Xy -a "X$ans" != XY ]
+ then
+ echo ""
+ echo "Please choose another configuration file."
+ else
+ break
+ fi
+ fi
+ done
+
+
+ __dest=$configdir/$iam.conf
+ if [ "$__choice" != "$__dest" ]
+ then
+ if [ -f $__dest ]
+ then
+ echo "Removing old configuration file \"$__dest\""
+ rm -f $__dest
+ if [ -f $__dest ]
+ then
+ echo "Error: cannot remove old configuration file \"$__dest\""
+ exit 1
+ fi
+ fi
+ if cp $__choice $__dest
+ then
+ :
+ else
+ echo "Error: cannot install new configuration file \"$__dest\""
+ exit 1
+ fi
+ __choice=$__dest
+ fi
+
+ configfile=$__choice
+}
+
+# choose correct PMDA installation mode
+#
+# make sure we are installing in the correct style of configuration
+#
+__choose_mode()
+{
+ __def=m
+ $do_pmda && __def=b
+ echo \
+'You will need to choose an appropriate configuration for installation of
+the "'$iam'" Performance Metrics Domain Agent (PMDA).
+
+ collector collect performance statistics on this system
+ monitor allow this system to monitor local and/or remote systems
+ both collector and monitor configuration for this system
+'
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Please enter c(ollector) or m(onitor) or b(oth) ['$__def'] '"$PCP_ECHO_C"
+ read ans
+ $__echo && echo "$ans"
+ case "$ans"
+ in
+ "") break
+ ;;
+ c|collector|b|both)
+ do_pmda=true
+ break
+ ;;
+ m|monitor)
+ do_pmda=false
+ break
+ ;;
+ *) echo "Sorry, that is not acceptable response ..."
+ ;;
+ esac
+ done
+}
+
+# choose an IPC method
+#
+__choose_ipc()
+{
+ _dir=$1
+ ipc_type=''
+ $pipe_opt && ipc_type=pipe
+ $socket_opt && ipc_type=socket
+ $pipe_opt && $socket_opt && ipc_type=''
+ if [ -z "$ipc_type" ]
+ then
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "PMCD should communicate with the $iam daemon via a pipe or a socket? [pipe] ""$PCP_ECHO_C"
+ read ipc_type
+ $__echo && echo "$ipc_type"
+ if [ "X$ipc_type" = Xpipe -o "X$ipc_type" = X ]
+ then
+ ipc_type=pipe
+ break
+ elif [ "X$ipc_type" = Xsocket ]
+ then
+ break
+ else
+ echo "Must choose one of \"pipe\" or \"socket\", please try again"
+ fi
+ done
+ fi
+
+ if [ $ipc_type = pipe ]
+ then
+ # This defaults to binary unless the Install file
+ # specifies ipc_prot="binary notready" -- See pmcd(1)
+ type="pipe $ipc_prot $_dir/$pmda_name"
+ else
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Use Internet, IPv6 or Unix domain sockets? [Internet] ""$PCP_ECHO_C"
+ read ans
+ $__echo && echo "$ans"
+ if [ "X$ans" = XInternet -o "X$ans" = XIPv6 -o "X$ans" = X ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Internet port number or service name? [$socket_inet_def] ""$PCP_ECHO_C"
+ read port
+ $__echo && echo "$port"
+ [ "X$port" = X ] && port=$socket_inet_def
+ case $port
+ in
+ [0-9]*)
+ ;;
+ *)
+ if grep "^$port[ ]*[0-9]*/tcp" /etc/services >/dev/null 2>&1
+ then
+ :
+ else
+ echo "Warning: there is no tcp service for \"$port\" in /etc/services!"
+ fi
+ ;;
+ esac
+ if [ "X$ans" = XInternet -o "X$ans" = X ]
+ then
+ type="socket inet $port $_dir/$pmda_name"
+ args="-i $port $args"
+ else
+ type="socket ipv6 $port $_dir/$pmda_name"
+ args="-6 $port $args"
+ fi
+ break
+ elif [ "X$ans" = XUnix ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Unix FIFO name? ""$PCP_ECHO_C"
+ read fifo
+ $__echo && echo "$fifo"
+ if [ "X$fifo" = X ]
+ then
+ echo "Must provide a name, please try again"
+ else
+ type="socket unix $fifo $_dir/$pmda_name"
+ args="-u $fifo $args"
+ break
+ fi
+ else
+ echo "Must choose one of \"Unix\" or \"Internet\", please try again"
+ fi
+ done
+ fi
+}
+
+# filter pmprobe -i output of the format:
+# postgresql.active.is_in_recovery -12351 Missing metric value(s)
+# postgresql.statio.sys_sequences.blks_hit 0
+# disk.partitions.read 13 ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?10 ?11 ?12
+# to produce a summary of metrics, values and warnings
+#
+__filter()
+{
+ $PCP_AWK_PROG '
+/^'$1'/ { metric++
+ if ($2 < 0)
+ warn++
+ else
+ value += $2
+ next
+ }
+ { warn++ }
+END { if (warn) printf "%d warnings, ",warn
+ printf "%d metrics and %d values\n",metric,value
+ }'
+}
+
+_setup()
+{
+ # some more configuration controls
+ pmns_name=${pmns_name-$iam}
+ pmda_name=pmda$iam
+ pmda_dso_name="${PCP_PMDAS_DIR}/${iam}/pmda_${iam}.${dso_suffix}"
+ dso_name="${dso_name-$pmda_dso_name}"
+ dso_entry=${iam}_init
+
+ _check_userroot
+ _check_directory
+
+ # automatically generate files for those lazy Perl programmers
+ #
+ if $perl_opt
+ then
+ perl_name="${pmda_dir}/pmda${iam}.perl"
+ [ -f "$perl_name" ] || perl_name="${pmda_dir}/pmda${iam}.pl"
+ if [ -f "$perl_name" ]
+ then
+ perl_pmns="${pmda_dir}/pmns.perl"
+ perl_dom="${pmda_dir}/domain.h.perl"
+ perl -e 'use PCP::PMDA' 2>/dev/null
+ if test $? -eq 0
+ then
+ eval PCP_PERL_DOMAIN=1 perl "$perl_name" > "$perl_dom"
+ eval PCP_PERL_PMNS=1 perl "$perl_name" > "$perl_pmns"
+ elif $dso_opt || $daemon_opt
+ then
+ : # we have an alternative, so continue on
+ else
+ echo 'Perl PCP::PMDA module is not installed, install it and try again'
+ exit 1
+ fi
+ else
+ if $dso_opt || $daemon_opt
+ then
+ : # we have an alternative, so continue on
+ else
+ echo "Neither pmda${iam}.perl nor pmda${iam}.pl found in ${pmda_dir}"
+ echo "Error: no Perl PMDA to install"
+ exit 1
+ fi
+ fi
+ fi
+
+ # automatically generate files for the Python programmers too
+ #
+ if $python_opt
+ then
+ python_name="${pmda_dir}/pmda${iam}.python"
+ [ -f "$python_name" ] || python_name="${pmda_dir}/pmda${iam}.py"
+ if [ -f "$python_name" ]
+ then
+ python_pmns="${pmda_dir}/pmns.python"
+ python_dom="${pmda_dir}/domain.h.python"
+ python -c 'from pcp import pmda' 2>/dev/null
+ if test $? -eq 0
+ then
+ eval PCP_PYTHON_DOMAIN=1 python "$python_name" > "$python_dom"
+ eval PCP_PYTHON_PMNS=1 python "$python_name" > "$python_pmns"
+ elif $dso_opt || $daemon_opt
+ then
+ : # we have an alternative, so continue on
+ else
+ echo 'Python pcp.pmda module is not installed, install it and try again'
+ exit 1
+ fi
+ else
+ if $dso_opt || $daemon_opt
+ then
+ : # we have an alternative, so continue on
+ else
+ echo "Neither pmda${iam}.python nor pmda${iam}.py found in ${pmda_dir}"
+ echo "Error: no Python PMDA to install"
+ exit 1
+ fi
+ fi
+ fi
+
+ # Juggle pmns and domain.h in case perl/python pmda install was done here
+ # last time
+ #
+ for file in pmns domain.h
+ do
+ [ -f $file.save ] && mv $file.save $file
+ done
+
+ # Set $domain and $SYMDOM from domain.h
+ #
+ __check_domain
+
+ case $prog
+ in
+ *Install*)
+ # Check that $ROOT is not set, we have a default domain value and
+ # choose the installation mode (collector, monitor or both)
+ #
+ __check_root
+ __choose_mode
+ ;;
+ esac
+}
+
+_install_views()
+{
+ viewer="$1"
+ have_views=false
+
+ [ `echo *.$viewer` != "*.$viewer" ] && have_views=true
+ if [ -d $PCP_VAR_DIR/config/$viewer ]
+ then
+ $have_views && echo "Installing $viewer view(s) ..."
+ for __i in *.$viewer
+ do
+ if [ "$__i" != "*.$viewer" ]
+ then
+ __dest=$PCP_VAR_DIR/config/$viewer/`basename $__i .$viewer`
+ rm -f $__dest
+ cp $__i $__dest
+ fi
+ done
+ else
+ $have_views && \
+ echo "Skip installing $viewer view(s) ... no \"$PCP_VAR_DIR/config/$viewer\" directory"
+ fi
+}
+
+# Configurable PMDA installation
+#
+# before calling _install,
+# 1. set $iam
+# 2. set one/some/all of $dso_opt, $perl_opt, $python_opt or $daemon_opt to true
+# (optional, $daemon_opt is true by default)
+# 3. if $daemon_opt set one or both of $pipe_opt or $socket_opt true
+# (optional, $pipe_opt is true by default)
+# 4. if $socket_opt and there is a default Internet socket, set
+# $socket_inet_def
+
+_install()
+{
+ if [ -z "$iam" ]
+ then
+ echo 'Botch: must define $iam before calling _install()'
+ exit 1
+ fi
+
+ if $do_pmda
+ then
+ if $dso_opt || $perl_opt || $python_opt || $daemon_opt
+ then
+ :
+ else
+ echo 'Botch: must set at least one of $dso_opt, $perl_opt, $python_opt or $daemon_opt to "true"'
+ exit 1
+ fi
+ if $daemon_opt
+ then
+ if $pipe_opt || $socket_opt
+ then
+ :
+ else
+ echo 'Botch: must set at least one of $pipe_opt or $socket_opt to "true"'
+ exit 1
+ fi
+ fi
+
+ # Select a PMDA style (dso/perl/python/deamon), and for daemons the
+ # IPC method for communication between PMCD and the PMDA.
+ #
+ pmda_options=''
+ pmda_default_option=''
+ pmda_multiple_options=false
+
+ if $dso_opt
+ then
+ pmda_options="dso"
+ pmda_default_option="dso"
+ fi
+ if $perl_opt
+ then
+ pmda_default_option="perl"
+ if test -n "$pmda_options"
+ then
+ pmda_options="perl or $pmda_options"
+ pmda_multiple_options=true
+ else
+ pmda_options="perl"
+ fi
+ fi
+ if $python_opt
+ then
+ pmda_default_option="python"
+ if test -n "$pmda_options"
+ then
+ pmda_options="python or $pmda_options"
+ pmda_multiple_options=true
+ else
+ pmda_options="python"
+ fi
+ fi
+ if $daemon_opt
+ then
+ pmda_default_option="daemon"
+ if test -n "$pmda_options"
+ then
+ pmda_options="daemon or $pmda_options"
+ pmda_multiple_options=true
+ else
+ pmda_options="daemon"
+ fi
+ fi
+
+ pmda_type="$pmda_default_option"
+ if $pmda_multiple_options
+ then
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Install $iam as a $pmda_options agent? [$pmda_default_option] ""$PCP_ECHO_C"
+ read pmda_type
+ $__echo && echo "$pmda_type"
+ if [ "X$pmda_type" = Xdaemon -o "X$pmda_type" = X ]
+ then
+ pmda_type=daemon
+ break
+ elif [ "X$pmda_type" = Xdso ]
+ then
+ break
+ elif [ "X$pmda_type" = Xperl ]
+ then
+ perl -e 'use PCP::PMDA' 2>/dev/null
+ if test $? -ne 0
+ then
+ echo 'Perl PCP::PMDA module is not installed, install it and try again'
+ else
+ break
+ fi
+ elif [ "X$pmda_type" = Xpython ]
+ then
+ python -c 'from pcp import pmda' 2>/dev/null
+ if test $? -ne 0
+ then
+ echo 'Python pcp pmda module is not installed, install it and try again'
+ else
+ break
+ fi
+ else
+ echo "Must choose one of $pmda_options, please try again"
+ fi
+ done
+ fi
+ if [ "$pmda_type" = daemon ]
+ then
+ __choose_ipc $pmda_dir
+ args="-d $domain $args"
+ elif [ "$pmda_type" = perl ]
+ then
+ type="pipe binary perl $perl_name $perl_args"
+ args=""
+ elif [ "$pmda_type" = python ]
+ then
+ type="pipe binary python $python_name $python_args"
+ args=""
+ else
+ type="dso $dso_entry $dso_name"
+ args=""
+ fi
+
+ # Install binaries
+ #
+ if [ "$pmda_type" = perl -o "$pmda_type" = python ]
+ then
+ : # we can safely skip building binaries
+ elif [ -f Makefile -o -f makefile -o -f GNUmakefile ]
+ then
+ # $PCP_MAKE_PROG may contain command line args ... executable
+ # is first word
+ #
+ if [ ! -f "`echo $PCP_MAKE_PROG | sed -e 's/ .*//'`" -o ! -f "$PCP_INC_DIR/pmda.h" ]
+ then
+ echo "$prog: Arrgh, PCP devel environment required to install this PMDA"
+ exit 1
+ fi
+
+ echo "Installing files ..."
+ if $PCP_MAKE_PROG install
+ then
+ :
+ else
+ echo "$prog: Arrgh, \"$PCP_MAKE_PROG install\" failed!"
+ exit 1
+ fi
+ fi
+
+ # Fix domain in help for instance domains (if any)
+ #
+ if [ -f $help_source ]
+ then
+ case $pmda_interface
+ in
+ 1)
+ help_version=1
+ ;;
+ *) # PMDA_INTERFACE_2 or later
+ help_version=2
+ ;;
+ esac
+ sed -e "/^@ $SYMDOM\./s/$SYMDOM\./$domain./" <$help_source \
+ | newhelp -n root -v $help_version -o $help_source
+ fi
+ fi
+
+ if $do_pmda
+ then
+ if [ "X$pmda_type" = Xperl -o "X$pmda_type" = Xpython ]
+ then
+ # Juggle pmns and domain.h ... save originals and
+ # use *.{perl,python} ones created earlier
+ for file in pmns domain.h
+ do
+ if [ ! -f "$file.$pmda_type" ]
+ then
+ echo "Botch: $file.$pmda_type missing ... giving up"
+ exit 1
+ fi
+ if [ -f $file ]
+ then
+ if diff $file.$pmda_type $file >/dev/null
+ then
+ :
+ else
+ [ ! -f $file.save ] && mv $file $file.save
+ mv $file.$pmda_type $file
+ fi
+ else
+ mv $file.$pmda_type $file
+ fi
+ done
+ fi
+ else
+ # Maybe PMNS only install, and only implementation may be
+ # Perl or Python ones ... simpler juggling needed here.
+ #
+ for file in pmns domain.h
+ do
+ [ ! -f $file -a -f $file.perl ] && mv $file.perl $file
+ [ ! -f $file -a -f $file.python ] && mv $file.python $file
+ done
+ fi
+
+ $PCP_SHARE_DIR/lib/lockpmns $NAMESPACE
+ trap "$PCP_SHARE_DIR/lib/unlockpmns \$NAMESPACE; rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15
+
+ echo "Updating the Performance Metrics Name Space (PMNS) ..."
+
+ # Install the namespace
+ #
+
+ if [ ! -f $NAMESPACE ]
+ then
+ # We may be installing an agent right after an install -
+ # before pmcd startup, which has a pre-execution step of
+ # rebuilding the namespace root. Do so now.
+ if [ -x $PMNSDIR/Rebuild ]
+ then
+ echo "$prog: cannot Rebuild the PMNS for \"$NAMESPACE\""
+ exit 1
+ fi
+ cd $PMNSDIR
+ ./Rebuild -dus
+ cd $__here
+ forced_restart=true
+ fi
+
+ for __n in $pmns_name
+ do
+ if pminfo $__ns_opt $__n >/dev/null 2>&1
+ then
+ cd $PMNSDIR
+ if pmnsdel -n $PMNSROOT $__n >$tmp/base 2>&1
+ then
+ pmsignal -a -s HUP pmcd >/dev/null 2>&1
+ # Make sure the PMNS timestamp will be different the next
+ # time the PMNS is updated (for Linux only 1 sec resolution)
+ sleep 2
+ else
+ if grep 'Non-terminal "'"$__n"'" not found' $tmp/base >/dev/null
+ then
+ :
+ elif grep 'Error: metricpath "'"$__n"'" not defined' $tmp/base >/dev/null
+ then
+ :
+ else
+ echo "$prog: failed to delete \"$__n\" from the PMNS"
+ cat $tmp/base
+ exit 1
+ fi
+ fi
+ cd $__here
+ fi
+
+ # Put the default domain number into the namespace file
+ #
+ # If there is only one namespace, then the pmns file will
+ # be named "pmns". If there are multiple metric trees,
+ # subsequent pmns files will be named "pmns.<metricname>"
+ #
+ # the string "pmns" can be overridden by the Install/Remove
+ # scripts by altering $pmns_source
+ #
+ if [ "$__n" = "$iam" -o "$__n" = "$pmns_name" ]
+ then
+ __s=$pmns_source
+ else
+ __s=$pmns_source.$__n
+ fi
+ sed -e "s/$SYMDOM:/$domain:/" <$__s >$PMNSDIR/$__n
+
+ cd $PMNSDIR
+ if pmnsadd -n $PMNSROOT $__n
+ then
+ pmsignal -a -s HUP pmcd >/dev/null 2>&1
+ # Make sure the PMNS timestamp will be different the next
+ # time the PMNS is updated (for Linux only 1 sec resolution)
+ sleep 2
+ else
+ echo "$prog: failed to add the PMNS entries for \"$__n\" ..."
+ echo
+ ls -l
+ exit 1
+ fi
+ cd $__here
+ done
+
+ trap "rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15
+ $PCP_SHARE_DIR/lib/unlockpmns $NAMESPACE
+
+ _install_views pmchart
+ _install_views kmchart
+ _install_views pmview
+
+ if $do_pmda
+ then
+ # Terminate old PMDA
+ #
+ echo "Terminate PMDA if already installed ..."
+ __pmda_cull $iam $domain
+
+ # Rotate log files
+ #
+ if [ -f $PCP_LOG_DIR/pmcd/$iam.log ]
+ then
+ rm -f $PCP_LOG_DIR/pmcd/$iam.log.prev
+ mv -f $PCP_LOG_DIR/pmcd/$iam.log $PCP_LOG_DIR/pmcd/$iam.log.prev
+ fi
+
+ # Add PMDA to pmcd's configuration file
+ #
+ echo "Updating the PMCD control file, and notifying PMCD ..."
+ __pmda_add "$iam $domain $type $args"
+
+ # Check that the agent is running OK
+ #
+ if $do_check
+ then
+ [ "$check_delay" -gt 5 ] && echo "Wait $check_delay seconds for the $iam agent to initialize ..."
+ sleep $check_delay
+ for __n in $pmns_name
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Check $__n metrics have appeared ... ""$PCP_ECHO_C"
+ pmprobe -i $__ns_opt $__n | tee $tmp/verbose | __filter $__n
+ if $__verbose
+ then
+ echo "pminfo output ..."
+ cat $tmp/verbose
+ fi
+ done
+ fi
+ else
+ echo "Skipping PMDA install and PMCD re-configuration"
+ fi
+}
+
+_remove()
+{
+ # Update the namespace
+ #
+
+ $PCP_SHARE_DIR/lib/lockpmns $NAMESPACE
+ trap "$PCP_SHARE_DIR/lib/unlockpmns \$NAMESPACE; rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15
+
+ echo "Culling the Performance Metrics Name Space ..."
+ cd $PMNSDIR
+
+ for __n in $pmns_name
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "$__n ... ""$PCP_ECHO_C"
+ if pmnsdel -n $PMNSROOT $__n >$tmp/base 2>&1
+ then
+ rm -f $PMNSDIR/$__n
+ pmsignal -a -s HUP pmcd >/dev/null 2>&1
+ sleep 2
+ echo "done"
+ else
+ if grep 'Non-terminal "'"$__n"'" not found' $tmp/base >/dev/null
+ then
+ echo "not found in Name Space, this is OK"
+ elif grep 'Error: metricpath "'"$__n"'" not defined' $tmp/base >/dev/null
+ then
+ echo "not found in Name Space, this is OK"
+ else
+ echo "error"
+ cat $tmp/base
+ exit
+ fi
+ fi
+ done
+
+ # Remove the PMDA and help files
+ #
+ cd $__here
+
+ if $do_pmda
+ then
+ echo "Updating the PMCD control file, and notifying PMCD ..."
+ __pmda_cull $iam $domain
+
+ if [ -f Makefile -o -f makefile -o -f GNUmakefile ]
+ then
+ echo "Removing files ..."
+ $PCP_MAKE_PROG clobber >/dev/null
+ fi
+ for __i in *.pmchart
+ do
+ if [ "$__i" != "*.pmchart" ]
+ then
+ __dest=$PCP_VAR_DIR/config/pmchart/`basename $__i .pmchart`
+ rm -f $__dest
+ fi
+ done
+ for __i in *.kmchart
+ do
+ if [ "$__i" != "*.kmchart" ]
+ then
+ __dest=$PCP_VAR_DIR/config/kmchart/`basename $__i .kmchart`
+ rm -f $__dest
+ fi
+ done
+
+ if $do_check
+ then
+ for __n in $pmns_name
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Check $__n metrics have gone away ... ""$PCP_ECHO_C"
+ if pminfo -n $NAMESPACE -f $__n >$tmp/base 2>&1
+ then
+ echo "Arrgh, something has gone wrong!"
+ cat $tmp/base
+ else
+ echo "OK"
+ fi
+ done
+ fi
+ else
+ echo "Skipping PMDA removal and PMCD re-configuration"
+ fi
+
+ trap "rm -rf $pcptmp $pmdatmp; exit" 0 1 2 3 15
+ $PCP_SHARE_DIR/lib/unlockpmns $NAMESPACE
+}
+
+_check_userroot()
+{
+ if [ "$uid" -ne 0 ]
+ then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "Error: You must be root (uid 0) to update the PCP collector configuration."
+ exit 1
+ fi
+ fi
+}
+
+_check_directory()
+{
+ case "$__here"
+ in
+ */pmdas/$iam)
+ ;;
+ *)
+ echo "Error: expect current directory to be .../pmdas/$iam, not $__here"
+ echo " (typical location is $PCP_PMDAS_DIR/$iam on this platform)"
+ exit 1
+ ;;
+ esac
+}
+
+# preferred public interfaces
+#
+pmdaSetup()
+{
+ _setup
+}
+
+pmdaChooseConfigFile()
+{
+ _choose_configfile
+}
+
+pmdaInstall()
+{
+ _install
+}
+
+pmdaRemove()
+{
+ _remove
+}
diff --git a/src/pmcd/rc-proc.sh b/src/pmcd/rc-proc.sh
new file mode 100644
index 0000000..3897c7f
--- /dev/null
+++ b/src/pmcd/rc-proc.sh
@@ -0,0 +1,394 @@
+#
+# Common sh(1) procedures to be used in PCP rc scripts
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# These functions use chkconfig if available, else tolerate missing chkconfig
+# command (as on SUSE) by manipulating symlinks in /etc/rc.d directly.
+#
+# Usage:
+#
+# is_chkconfig_on : return 0 if $1 is chkconfig "on" else 1
+# chkconfig_on : chkconfig $1 "on"
+# chkconfig_off : chkconfig $1 "off"
+# chkconfig_on_msg: echo a message about how to chkconfig $1 on
+#
+
+#
+# private functions
+#
+_which()
+{
+ # some versions of which(1) have historically not reflected the
+ # correct exit status ... but it appears that all modern platforms
+ # get this correct
+ #
+ # keeping the old logic structure, just in case
+ #
+
+ if $PCP_WHICH_PROG $1 >/dev/null 2>&1
+ then
+ if [ "$PCP_PLATFORM" = broken ]
+ then
+ if $PCP_WHICH_PROG $1 | grep "no $1" >/dev/null
+ then
+ :
+ else
+ return 0
+ fi
+ else
+ return 0
+ fi
+ fi
+ return 1
+}
+
+_cmds_exist()
+{
+ _have_flag=false
+ [ -f $PCP_RC_DIR/$1 ] && _have_flag=true
+
+ _have_systemctl=false
+ _which systemctl && _have_systemctl=true
+ _have_runlevel=false
+ _which runlevel && _have_runlevel=true
+ _have_chkconfig=false
+ _which chkconfig && _have_chkconfig=true
+ _have_sysvrcconf=false
+ _which sysvrcconf && _have_sysvrcconf=true
+ _have_rcupdate=false
+ _which rc-update && _have_rcupdate=true
+ _have_svcadm=false
+ _which svcadm && _have_svcadm=true
+}
+
+#
+# return the run levels for $1
+#
+_runlevels()
+{
+ $PCP_AWK_PROG '/^# chkconfig:/ {print $3}' $PCP_RC_DIR/$1 | sed -e 's/[0-9]/& /g'
+}
+
+#
+# return rc start number for $1
+#
+_runlevel_start()
+{
+ $PCP_AWK_PROG '/^# chkconfig:/ {print $4}' $PCP_RC_DIR/$1
+}
+
+#
+# return runlevel stop number for $1
+#
+_runlevel_stop()
+{
+ $PCP_AWK_PROG '/^# chkconfig:/ {print $5}' $PCP_RC_DIR/$1
+}
+
+#
+# Return 0 if $1 is chkconfig "on" (enabled) at the current run level
+# Handles missing chkconfig command and other assorted atrocities.
+#
+is_chkconfig_on()
+{
+ # if non-default install, everything is "on"
+ [ -n "$PCP_DIR" ] && return 0
+
+ LANG=C
+ _flag=$1
+
+ _ret=1 # return "off" by default
+ _rl=3 # default run level if !_have_runlevel
+
+ _cmds_exist $_flag
+ $_have_runlevel && _rl=`runlevel | $PCP_AWK_PROG '{print $2}'`
+
+ if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ]
+ then
+ # unknown mechanism, just do it
+ _ret=0
+ elif [ "$PCP_PLATFORM" = "darwin" ]
+ then
+ case "$1"
+ in
+ pmcd) [ "`. /etc/hostconfig; echo $PMCD`" = "-YES-" ] && _ret=0 ;;
+ pmlogger) [ "`. /etc/hostconfig; echo $PMLOGGER`" = "-YES-" ] && _ret=0 ;;
+ pmie) [ "`. /etc/hostconfig; echo $PMIE`" = "-YES-" ] && _ret=0 ;;
+ pmproxy) [ "`. /etc/hostconfig; echo $PMPROXY`" = "-YES-" ] && _ret=0 ;;
+ pmwebd) [ "`. /etc/hostconfig; echo $PMWEBD`" = "-YES-" ] && _ret=0 ;;
+ esac
+ elif $_have_systemctl
+ then
+ systemctl is-enabled "$_flag".service >/dev/null 2>&1 && _ret=0
+ elif $_have_chkconfig
+ then
+ chkconfig --list "$_flag" 2>&1 | grep $_rl":on" >/dev/null 2>&1 && _ret=0
+ elif $_have_sysvrcconf
+ then
+ sysv-rc-conf --list "$_flag" 2>&1 | grep $_rl":on" >/dev/null 2>&1 && _ret=0
+ elif $_have_rcupdate
+ then
+ rc-update show 2>&1 | grep "$_flag" >/dev/null 2>&1 && _ret=0
+ elif $_have_svcadm
+ then
+ svcs -l pcp/$_flag | grep "enabled *true" >/dev/null 2>&1 && _ret=0
+ else
+ #
+ # don't know, fallback to using the existence of rc symlinks
+ #
+ if [ -f /etc/debian_version ]; then
+ ls /etc/rc$_rl.d/S[0-9]*$_flag >/dev/null 2>&1 && _ret=0
+ else
+ ls /etc/rc.d/rc$_rl.d/S[0-9]*$_flag >/dev/null 2>&1 && _ret=0
+ fi
+ fi
+
+ return $_ret
+}
+
+#
+# chkconfig "on" $1
+# Handles missing chkconfig command.
+#
+chkconfig_on()
+{
+ # if non-default install, everything is "on"
+ [ -n "$PCP_DIR" ] && return 0
+
+ _flag=$1
+ [ -z "$_flag" ] && return 1 # fail
+
+ _cmds_exist $_flag
+ $_have_flag || return 1 # fail
+
+ if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ]
+ then
+ # unknown mechanism, just pretend
+ return 0
+ elif [ "$PCP_PLATFORM" = "darwin" ]
+ then
+ echo "To enable $_flag, add the following line to /etc/hostconfig:"
+ case "$_flag"
+ in
+ pmcd) echo "PMCD=-YES-" ;;
+ pmlogger) echo "PMLOGGER=-YES-" ;;
+ pmie) echo "PMIE=-YES-" ;;
+ pmproxy) echo "PMPROXY=-YES-" ;;
+ pmwebd) echo "PMWEBD=-YES-" ;;
+ esac
+ elif $_have_systemctl
+ then
+ systemctl --no-reload enable "$_flag".service >/dev/null 2>&1
+ elif $_have_chkconfig
+ then
+ chkconfig "$_flag" on >/dev/null 2>&1
+ elif $_have_sysvrcconf
+ then
+ sysv-rc-conf "$_flag" on >/dev/null 2>&1
+ elif $_have_rcupdate
+ then
+ rc-update add "$_flag" >/dev/null 2>&1
+ elif $_have_svcadm
+ then
+ svcadm enable pcp/$_flag >/dev/null 2>&1
+ else
+ _start=`_runlevel_start $_flag`
+ _stop=`_runlevel_stop $_flag`
+ if [ -f /etc/debian_version ]
+ then
+ update-rc.d -f $_flag defaults s$_start k$_stop
+ else
+ for _r in `_runlevels $_flag`
+ do
+ ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/S$_start""$_flag >/dev/null 2>&1
+ ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/K$_stop""$_flag >/dev/null 2>&1
+ done
+ fi
+ fi
+
+ return 0
+}
+
+#
+# chkconfig "off" $1
+# Handles missing chkconfig command.
+#
+chkconfig_off()
+{
+ # if non-default install, everything is "on"
+ [ -n "$PCP_DIR" ] && return 1
+
+ _flag=$1
+ [ -z "$_flag" ] && return 1 # fail
+
+ _cmds_exist $_flag
+ $_have_flag || return 1 # fail
+
+ if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ]
+ then
+ # unknown mechanism, just pretend
+ return 0
+ elif $_have_systemctl
+ then
+ systemctl --no-reload disable "$_flag".service >/dev/null 2>&1
+ elif $_have_chkconfig
+ then
+ chkconfig --level 2345 "$_flag" off >/dev/null 2>&1
+ elif $_have_sysvrcconf
+ then
+ sysv-rc-conf --level 2345 "$_flag" off >/dev/null 2>&1
+ elif $_have_rcupdate
+ then
+ rc-update delete "$_flag" >/dev/null 2>&1
+ elif $_have_svcadm
+ then
+ svcadm disable pcp/$_flag >/dev/null 2>&1
+ else
+ # remove the symlinks
+ if [ -f /etc/debian_version ]
+ then
+ update-rc.d -f $_flag remove
+ else
+ rm -f /etc/rc.d/rc[0-9].d/[SK][0-9]*$_flag >/dev/null 2>&1
+ fi
+ fi
+
+ return 0
+}
+
+#
+# Echo a message about how to chkconfig $1 "on"
+# Tolerates missing chkconfig command
+#
+chkconfig_on_msg()
+{
+ _flag=$1
+ _cmds_exist $_flag
+ $_have_flag || return 1 # fail
+
+ if [ "$PCP_PLATFORM" = mingw -o "$PCP_PLATFORM" = "freebsd" ]
+ then
+ # no mechanism, just pretend
+ #
+ return 0
+ else
+ echo " To enable $_flag, run the following as root:"
+ if $_have_systemctl
+ then
+ _cmd=`$PCP_WHICH_PROG systemctl`
+ echo " # $_cmd enable $_flag.service"
+ elif $_have_chkconfig
+ then
+ _cmd=`$PCP_WHICH_PROG chkconfig`
+ echo " # $_cmd $_flag on"
+ elif $_have_sysvrcconf
+ then
+ _cmd=`$PCP_WHICH_PROG sysvrcconf`
+ echo " # $_cmd $_flag on"
+ elif $_have_rcupdate
+ then
+ _cmd=`$PCP_WHICH_PROG rc-update`
+ echo " # $_cmd add $_flag"
+ elif $_have_svcadm
+ then
+ _cmd=`$PCP_WHICH_PROG svcadm`
+ echo " # $_cmd enable pcp/$_flag"
+ else
+ _start=`_runlevel_start $_flag`
+ _stop=`_runlevel_stop $_flag`
+ if [ -f /etc/debian_version ]
+ then
+ echo " update-rc.d -f $_flag remove"
+ echo " update-rc.d $_flag defaults $_start $_stop"
+ else
+ for _r in `_runlevels $_flag`
+ do
+ echo " # ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/S$_start""$_flag"
+ echo " # ln -sf ../init.d/$_flag /etc/rc.d/rc$_r.d/K$_stop""$_flag"
+ done
+ fi
+ fi
+ fi
+
+ return 0
+}
+
+#
+# load some rc functions if available
+#
+# In openSUSE 12.1, /etc/rc.status intercepts our rc script and passes
+# control to systemctl which uses systemd ... the result is that messages
+# from our rc scripts are sent to syslog by default, and there is no
+# apparent way to revert to the classical behaviour, so this "hack" allows
+# PCP QA to set $PCPQA_NO_RC_STATUS and continue to see stdout and stderr
+# from our rc scripts
+# - Ken 1 Dec 2011
+#
+if [ -r /etc/rc.status -a -z "${PCPQA_NO_RC_STATUS+set}" ]
+then
+ # SuSE style
+ . /etc/rc.status
+ RC_STATUS=rc_status
+ RC_RESET=rc_reset
+ RC_CHECKPROC=checkproc
+else
+ # Roll our own
+ RC_STATUS=_RC_STATUS
+ _RC_STATUS()
+ {
+ _rc_status=$?
+ if [ "$1" = "-v" ]
+ then
+ if [ $_rc_status -eq 0 ]
+ then $ECHO
+ else
+ $ECHO "failed (status=$_rc_status)"
+ fi
+ fi
+ return $_rc_status
+ }
+
+ RC_RESET=_RC_RESET
+ _RC_RESET()
+ {
+ _rc_status=0
+ return $_rc_status
+ }
+
+ RC_CHECKPROC=_RC_CHECKPROC
+ _RC_CHECKPROC()
+ {
+ # usage
+ [ $# -ne 1 ] && return 2
+
+ # running
+ _b=`basename "$1"`
+ _n=`_get_pids_by_name $_b | wc -l`
+ [ $_n -ge 1 ] && return 0
+
+ # not running, but pid exists
+ [ -e /var/run/$_b.pid ] && return 1
+
+ # program not installed
+ [ ! -e "$1" ] && return 5
+
+ # not running and no pid
+ return 3
+ }
+fi
diff --git a/src/pmcd/rc-proc.sh.minimal b/src/pmcd/rc-proc.sh.minimal
new file mode 100644
index 0000000..42e5b80
--- /dev/null
+++ b/src/pmcd/rc-proc.sh.minimal
@@ -0,0 +1,74 @@
+#
+# Common sh(1) procedures to be used in PCP rc scripts
+#
+# Minimalist version - use this if your system's init script regime
+# does not follow the "chkconfig + runlevel" model.
+#
+# Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# These functions use chkconfig if available, else tolerate missing chkconfig
+# command (as on SUSE) by manipulating symlinks in /etc/rc.d directly.
+#
+# Usage:
+#
+# is_chkconfig_on : return 0 if $1 is chkconfig "on" else 1
+# chkconfig_on : chkconfig $1 "on"
+# chkconfig_off : chkconfig $1 "off"
+# chkconfig_on_msg: echo a message about how to chkconfig $1 on
+#
+
+#
+# Return 0 if $1 is chkconfig "on" (enabled) at the current run level
+# Handles missing chkconfig command and other assorted atrocities.
+#
+is_chkconfig_on()
+{
+ return 0
+}
+
+#
+# chkconfig "on" $1
+# Handles missing chkconfig command.
+# (this is used by the pcp rpm %post script)
+#
+chkconfig_on()
+{
+ :
+}
+
+#
+# chkconfig "off" $1
+# Handles missing chkconfig command.
+# (this is used by the pcp rpm %preun script)
+#
+chkconfig_off()
+{
+ :
+}
+
+#
+# Echo a message about how to chkconfig $1 "on"
+# Tolerates missing chkconfig command
+#
+chkconfig_on_msg()
+{
+ :
+}
diff --git a/src/pmcd/rc_local b/src/pmcd/rc_local
new file mode 100644
index 0000000..4e51942
--- /dev/null
+++ b/src/pmcd/rc_local
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# rc.local - perform local Performance Co-Pilot boot/shutdown/restart actions
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+_usage()
+{
+ echo "Usage: $PCP_SYSCONF_DIR/pmcd/rc.local [-v] {start|stop}"
+}
+
+# defaults
+#
+VERBOSE_CTL=off
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# uncomment this if you wish
+#
+# [ $VERBOSE_CTL = on ] && echo "Local Performance Co-Pilot $1 script called."
+
+case $1 in
+
+ 'start')
+ # Add startup actions here
+ ;;
+
+ 'stop')
+ # Add shutdown actions here
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop}"
+ ;;
+esac
diff --git a/src/pmcd/rc_pcp b/src/pmcd/rc_pcp
new file mode 100644
index 0000000..974ca06
--- /dev/null
+++ b/src/pmcd/rc_pcp
@@ -0,0 +1,76 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Legacy wrapper for the Performance Co-Pilot Daemon(s) Start / Stop scripts
+#
+# This script is NOT part of the "rc" framework ... the "pmcd" and "pmlogger"
+# scripts ARE part of the "rc" framework. This script is provided as a
+# legacy bridge for any scripts or procedures that used $PCP_RC_DIR/pcp
+# from the days before PCP 3.6.
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig:
+# description: Legacy init script wrapper for the Performance Co-Pilot (PCP) daemons
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pcp
+# Required-Start:
+# Should-Start:
+# Required-Stop:
+# Should-Stop:
+# Default-Start:
+# Default-Stop:
+# Short-Description: Legacy control for PCP daemons
+# Description: Legacy init script wrapper for the Performance Co-Pilot (PCP) daemons
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=$PCP_RC_DIR/pcp
+
+_usage()
+{
+ echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ $PCP_RC_DIR/pmcd $*
+ $PCP_RC_DIR/pmlogger $*
+ ;;
+
+ 'stop')
+ $PCP_RC_DIR/pmlogger $*
+ $PCP_RC_DIR/pmcd $*
+ ;;
+
+ 'status')
+ $PCP_RC_DIR/pmcd $* || status=$?
+ $PCP_RC_DIR/pmlogger $* || status=$?
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
diff --git a/src/pmcd/rc_pmcd b/src/pmcd/rc_pmcd
new file mode 100644
index 0000000..49b7430
--- /dev/null
+++ b/src/pmcd/rc_pmcd
@@ -0,0 +1,540 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000-2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Start or Stop the Performance Co-Pilot Collection Daemon (PMCD)
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 95 05
+# description: pmcd is the collection daemon for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmcd
+# Required-Start: $local_fs
+# Should-Start: $network $remote_fs $syslog $time
+# Required-Stop: $local_fs
+# Should-Stop: $network $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmcd (the collection daemon for PCP)
+# Description: Configure and control pmcd (the collection daemon for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMCD=$PCP_BINADM_DIR/pmcd
+PMCDOPTS=$PCP_PMCDOPTIONS_PATH
+PCPLOCAL=$PCP_PMCDRCLOCAL_PATH
+RUNDIR=$PCP_LOG_DIR/pmcd
+prog=$PCP_RC_DIR/`basename $0`
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ IAM=0
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+_pmcd_logfile()
+{
+default=$RUNDIR/pmcd.log
+$PCP_AWK_PROG <$PMCDOPTS '
+BEGIN { logf = "'$default'" }
+$1 == "-l" { if (NF > 1) logf = $2 }
+END { print logf }'
+}
+
+_reboot_setup()
+{
+ # base directories and house-keeping for daemon processes
+ #
+ # For most packages, $PCP_RUN_DIR is included in the package,
+ # but for Debian and cases where /var/run is a mounted filesystem
+ # it may not exist, so create it here before it is used to create
+ # any pid/lock files
+ #
+ # $PCP_RUN_DIR creation is also done in pmlogger_daily, but if pmcd
+ # is running, we'd expect do this one first
+ #
+ if [ ! -d "$PCP_RUN_DIR" ]
+ then
+ mkdir -p -m 775 "$PCP_RUN_DIR"
+ chown $PCP_USER:$PCP_GROUP "$PCP_RUN_DIR"
+ fi
+
+ # setup and clean up base directories and house-keeping for tracking
+ # pmlogger instances ... needs to be done here because pmcd needs the
+ # information, even if no pmlogger instances are started (and even if
+ # $PCP_RC_DIR/pmlogger is not run)
+ #
+ if [ ! -d "$PCP_TMP_DIR/pmlogger" ]
+ then
+ mkdir -p -m 777 "$PCP_TMP_DIR/pmlogger"
+ chown $PCP_USER:$PCP_GROUP "$PCP_TMP_DIR/pmlogger"
+ else
+ rm -rf $tmp/ent $tmp/pid
+ here=`pwd`
+ cd "$PCP_TMP_DIR/pmlogger"
+ rm -f primary vcr
+ _get_pids_by_name pmlogger | sort >$tmp/pid
+ ls [0-9]* 2>&1 | sed -e '/\[0-9]\*/d' \
+ | sed -e 's/[ ][ ]*//g' | sort >$tmp/ent
+ # remove entries without a pmlogger process
+ #
+ rm -f `comm -23 $tmp/ent $tmp/pid`
+ rm -f $tmp/ent $tmp/pid
+ cd "$here"
+ fi
+
+ # Rebuild PMNS?
+ #
+ PMNSDIR=$PCP_VAR_DIR/pmns
+
+ rebuild=false
+ if [ -d $PMNSDIR -a \( -f $PMNSDIR/.NeedRebuild -o ! -f $PMNSDIR/root \) ]
+ then
+ rebuild=true
+ else
+ num=`find $PMNSDIR -newer $PMNSDIR/root -iname 'root_*' 2>/dev/null | wc -l`
+ [ "$num" -gt 0 ] && rebuild=true
+ fi
+
+ if $rebuild
+ then
+ if [ -x $PMNSDIR/Rebuild ]
+ then
+ $ECHO $PCP_ECHO_N "Rebuilding PMNS ..." "$PCP_ECHO_C"
+ here=`pwd`
+ cd $PMNSDIR
+ ./Rebuild -du $REBUILDOPT
+ $RC_STATUS -v
+ # The 'root' file does not get updated when data did not change,
+ # so we must touch it to update date.
+ [ $? -eq 0 ] && { rm -f .NeedRebuild; touch root; }
+ cd "$here"
+ fi
+ fi
+}
+
+_pmda_setup()
+{
+ # Auto-Install PMDAs?
+ #
+ if [ -d $PCP_PMDAS_DIR ]
+ then
+ here=`pwd`
+ cd $PCP_PMDAS_DIR
+ for file in */.NeedInstall
+ do
+ [ "$file" = '*/.NeedInstall' ] && break
+ pmda=`dirname $file`
+ if [ -d "$pmda" -a -f "$pmda/.NeedInstall" ]
+ then
+ cd $pmda
+ $PCP_ECHO_PROG "Installing $pmda PMDA ..."
+
+ # rename .NeedInstall _before_ calling Install because
+ # Install can call this start script (recursively) and
+ # we don't want to get stuck in an infinite loop.
+ #
+ rm -f .NeedInstall.sav
+ mv .NeedInstall .NeedInstall.sav
+ if ./Install </dev/null >/dev/null
+ then
+ # success
+ $PCP_BINADM_DIR/pmpost "PMDA setup: automated install: $pmda"
+ rm -f .NeedInstall.sav
+ else
+ # put the file back, maybe we'll be luckier next time
+ $PCP_BINADM_DIR/pmpost "PMDA setup: automated install FAILED (exit=$?): $pmda"
+ mv .NeedInstall.sav .NeedInstall
+ fi
+
+ cd $PCP_PMDAS_DIR
+ fi
+ done
+ cd "$here"
+ fi
+}
+
+_start_pmcheck()
+{
+ if [ ! -z "$PMCD_WAIT_TIMEOUT" ]
+ then
+ wait_option="-t $PMCD_WAIT_TIMEOUT"
+ else
+ wait_option=''
+ fi
+
+ if pmcd_wait $wait_option
+ then
+ :
+ else
+ status=$?
+ $PCP_BINADM_DIR/pmpost "pmcd_wait failed in $prog: exit status: $status"
+ if [ ! -z "$MAIL" ]
+ then
+ echo "pmcd_wait exit status: $status" | $MAIL -s "pmcd_wait failed in $prog" root
+ else
+ echo "$prog: pmcd_wait failed: exit status: $status"
+ fi
+ fi
+}
+
+# Use $PCP_PMCDCONF_PATH to find and terminate pipe/socket PMDAs.
+# (First join up continued lines in config file)
+#
+_killpmdas()
+{
+ if [ ! -f $PCP_PMCDCONF_PATH ]
+ then
+ echo "$prog:"'
+Warning: pmcd control file '"$PCP_PMCDCONF_PATH"' is missing, cannot identify PMDAs
+ to be terminated.'
+ return
+ fi
+ # Give each PMDA 2 seconds after a SIGTERM to die, then SIGKILL
+ for pmda in `$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+/\\\\$/ { printf "%s ", substr($0, 0, length($0) - 1); next }
+ { print }' \
+| $PCP_AWK_PROG '
+$1 ~ /^#/ { next }
+tolower($3) == "pipe" && NF > 4 { print $5; next }
+tolower($3) == "socket" && NF > 5 { print $6; next }' \
+| sort -u`
+ do
+ pmsignal -a -s TERM `basename $pmda` > /dev/null 2>&1 &
+ done
+ sleep 2
+ for pmda in `$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+/\\\\$/ { printf "%s ", substr($0, 0, length($0) - 1); next }
+ { print }' \
+| $PCP_AWK_PROG '
+$1 ~ /^#/ { next }
+tolower($3) == "pipe" && NF > 4 { print $5; next }
+tolower($3) == "socket" && NF > 5 { print $6; next }' \
+| sort -u`
+ do
+ pmsignal -a -s KILL `basename $pmda` > /dev/null 2>&1 &
+ done
+
+ wait
+}
+
+_shutdown()
+{
+ # Is pmcd running?
+ #
+ _get_pids_by_name pmcd >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$prog: pmcd not running"
+ rm -f $PCP_RUN_DIR/pmcd.pid $PCP_RUN_DIR/pmcd.socket
+ return 0
+ fi
+
+ # If pmcd is running but we can't find a pidfile, or a logfile at the
+ # configured or default location, assume this script is being run via
+ # a chroot build environment (and hence we do not want to signal pmcd).
+ #
+ logf=`_pmcd_logfile`
+ [ -f $logf ] || logf=$RUNDIR/pmcd.log
+ if [ ! -f $PCP_RUN_DIR/pmcd.pid -a ! -f $logf ]
+ then
+ pmcdpid=`cat $tmp/tmp`
+ echo "PMCD process ... $pmcdpid"
+ echo "$prog:
+Warning: found no $PCP_RUN_DIR/pmcd.pid
+ and no $logf.
+ Assuming an uninstall from a chroot: pmcd not killed.
+ If this is incorrect, \"pmsignal -s TERM $pmcdpid\" can be used."
+ exit
+ elif [ -f $PCP_RUN_DIR/pmcd.pid ]
+ then
+ TOKILL=`cat $PCP_RUN_DIR/pmcd.pid`
+ if grep "^$TOKILL$" $tmp/tmp >/dev/null
+ then
+ :
+ else
+ echo "PMCD process ... "`cat $tmp/tmp`
+ echo "$prog:
+Warning: process ID in $PCP_RUN_DIR/pmcd.pid is $TOKILL.
+ Check logfile $logf. When you are ready to proceed, remove
+ $PCP_RUN_DIR/pmcd.pid before retrying."
+ exit
+ fi
+ else
+ TOKILL=
+ fi
+
+ # Send pmcd a SIGTERM, which is noted as a pending shutdown.
+ # When finished the currently active request, pmcd will close any
+ # connections, wait for any agents, and then exit.
+ # On failure, resort to SIGKILL.
+ #
+ $ECHO $PCP_ECHO_N "Waiting for pmcd to terminate ...""$PCP_ECHO_C"
+ delay=200 # tenths of a second
+ for SIG in TERM KILL
+ do
+ if [ "x$TOKILL" = "x" ]
+ then
+ pmsignal -a -s $SIG pmcd > /dev/null 2>&1
+ else
+ pmsignal -s $SIG $TOKILL >/dev/null 2>&1
+ rm -f $PCP_RUN_DIR/pmcd.pid $PCP_RUN_DIR/pmcd.socket
+ fi
+ while [ $delay -gt 0 ]
+ do
+ _get_pids_by_name pmcd >$tmp/tmp
+ [ ! -s $tmp/tmp ] && break 2
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ [ "$SIG" = "TERM" ] && [ `expr $delay % 10` -eq 0 ] \
+ && $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C"
+ done
+ echo
+ echo "Process ..."
+ if [ "$SIG" = "TERM" ]
+ then
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps
+ sed 1q $tmp/ps
+ for pid in `cat $tmp/tmp`
+ do
+ $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }"
+ done
+ echo "$prog: Warning: Forcing pmcd to terminate!"
+ delay=20
+ else
+ cat $tmp/tmp
+ echo "$prog: Warning: pmcd won't die!"
+ exit
+ fi
+ done
+ _killpmdas
+ $RC_STATUS -v
+ $PCP_BINADM_DIR/pmpost "stop pmcd from $prog"
+}
+
+_usage()
+{
+ echo "Usage: $prog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+VERBOSE_CTL=on
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose ... for historical reasons only as $VERBOSE_CTL
+ # is always "on"
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+ REBUILDOPT=''
+ VFLAG='-v'
+else # For a quiet startup and shutdown
+ ECHO=:
+ REBUILDOPT=-s
+ VFLAG=
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the Performance Co-Pilot pmcd.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmcd
+ then
+ status=0
+ exit
+ fi
+ _shutdown quietly
+
+ # Clean the environment for PMCD:
+ # PMCD and PMDA messages should go to stderr, not the GUI notifiers
+ # Clients in these scripts should test PMCD status without TLS/SSL.
+ #
+ unset PCP_STDERR PCP_SECURE_SOCKETS
+
+ _reboot_setup
+
+ if [ -x $PMCD ]
+ then
+ if [ ! -f $PCP_PMCDCONF_PATH ]
+ then
+ echo "$prog:"'
+Error: pmcd control file '"$PCP_PMCDCONF_PATH"' is missing, cannot start pmcd.'
+ exit
+ fi
+ if [ ! -d "$RUNDIR" ]
+ then
+ mkdir -p -m 775 "$RUNDIR"
+ chown $PCP_USER:$PCP_GROUP "$RUNDIR"
+ fi
+ cd "$RUNDIR"
+
+ # salvage the previous versions of any PMCD and PMDA logfiles
+ #
+ for log in pmcd `sed -e '/^#/d' -e '/\[access\]/q' -e 's/[ ].*//' <$PCP_PMCDCONF_PATH`
+ do
+ if [ -f $log.log ]
+ then
+ rm -f $log.log.prev
+ mv $log.log $log.log.prev
+ fi
+ done
+
+ $ECHO $PCP_ECHO_N "Starting pmcd ..." "$PCP_ECHO_C"
+
+ # only consider lines which start with a hyphen
+ # get rid of the -f option
+ # ensure multiple lines concat onto 1 line
+ OPTS=`sed <$PMCDOPTS 2>/dev/null \
+ -e '/^[^-]/d' \
+ -e 's/^/ /' \
+ -e 's/$/ /' \
+ -e 's/ -f / /g' \
+ -e 's/^ //' \
+ -e 's/ $//' \
+ | tr '\012' ' ' `
+
+ $PMCD $OPTS
+ $RC_STATUS -v
+
+ $PCP_BINADM_DIR/pmpost "start pmcd from $prog"
+
+ _pmda_setup
+
+ # force removal of primary pmlogger link ... if primary
+ # pmlogger is started, this will re-create the link
+ #
+ rm -f "$PCP_TMP_DIR/pmlogger/primary"
+
+ # site-local customisations after PMCD startup
+ #
+ [ -x $PCPLOCAL ] && $PCPLOCAL $VFLAG start
+
+ fi
+ status=0
+ ;;
+
+ 'stop')
+ # site-local customisations before pmcd shutdown
+ #
+ [ -x $PCPLOCAL ] && $PCPLOCAL $VFLAG stop
+ _shutdown verbose
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmcd:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMCD
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMCD
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmcd/sasl2.conf b/src/pmcd/sasl2.conf
new file mode 100644
index 0000000..774149d
--- /dev/null
+++ b/src/pmcd/sasl2.conf
@@ -0,0 +1,19 @@
+# Enabled authentication mechanisms (space-separated list).
+# You can list many mechanisms at once, then the user can choose
+# by adding e.g. '?authmech=gssapi' to their host specification.
+# For other options, refer to SASL pluginviewer command output.
+mech_list: plain login digest-md5 gssapi
+
+# If deferring to the SASL auth daemon (runs as root, can do PAM
+# login using regular user accounts, unprivileged daemons cannot).
+#pwcheck_method: saslauthd
+
+# If using plain/digest-md5 for user database, this sets the file
+# containing the passwords. Use 'saslpasswd2 -a pmcd [username]'
+# to add entries and 'sasldblistusers2 -f $sasldb_path' to browse.
+# Note: must be readable as the PCP daemons user (chown root:pcp).
+sasldb_path: /etc/pcp/passwd.db
+
+# Before using Kerberos via GSSAPI, you need a service principal on
+# the KDC server for pmcd, and that to be exported to the keytab.
+#keytab: /etc/pcp/krb5.tab
diff --git a/src/pmcd/src/GNUmakefile b/src/pmcd/src/GNUmakefile
new file mode 100644
index 0000000..86462c3
--- /dev/null
+++ b/src/pmcd/src/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmcd$(EXECSUFFIX)
+HFILES = client.h pmcd.h
+CFILES = pmcd.c config.c dofetch.c dopdus.c dostore.c client.c agent.c util.c
+
+LLDLIBS = $(PCPLIB) $(LIB_FOR_DLOPEN) -lpcp_pmcd
+PCPLIB_LDFLAGS += -L$(TOPDIR)/src/libpcp_pmcd/$(LIBPCP_ABIDIR)
+
+LLDFLAGS += $(RDYNAMIC_FLAG) $(PIELDFLAGS)
+LCFLAGS += $(PIECFLAGS)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmcd/src/agent.c b/src/pmcd/src/agent.c
new file mode 100644
index 0000000..700a0f2
--- /dev/null
+++ b/src/pmcd/src/agent.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmcd.h"
+#if defined(HAVE_DLFCN_H)
+#include <dlfcn.h>
+#elif defined(HAVE_DL_H)
+#include <dl.h>
+#endif
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_SYS_RESOURCE_H)
+#include <sys/resource.h>
+#endif
+
+/* Return a pointer to the agent that is reposible for a given domain.
+ * Note that the agent may not be in a connected state!
+ */
+AgentInfo *
+FindDomainAgent(int domain)
+{
+ int i;
+ for (i = 0; i < nAgents; i++)
+ if (agent[i].pmDomainId == domain)
+ return &agent[i];
+ return NULL;
+}
+
+void
+CleanupAgent(AgentInfo* aPtr, int why, int status)
+{
+ extern int AgentDied;
+#ifndef IS_MINGW
+ int exit_status = status;
+#endif
+ int reason = 0;
+
+ if (aPtr->ipcType == AGENT_DSO) {
+ if (aPtr->ipc.dso.dlHandle != NULL) {
+#ifdef HAVE_DLOPEN
+ dlclose(aPtr->ipc.dso.dlHandle);
+#endif
+ }
+ pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, -1, -1);
+ }
+ else {
+ pmcd_trace(TR_DEL_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd);
+ if (aPtr->inFd != -1) {
+ if (aPtr->ipcType == AGENT_SOCKET)
+ __pmCloseSocket(aPtr->inFd);
+ else {
+ close(aPtr->inFd);
+ __pmResetIPC(aPtr->inFd);
+ }
+ aPtr->inFd = -1;
+ }
+ if (aPtr->outFd != -1) {
+ if (aPtr->ipcType == AGENT_SOCKET)
+ __pmCloseSocket(aPtr->outFd);
+ else {
+ close(aPtr->outFd);
+ __pmResetIPC(aPtr->outFd);
+ }
+ aPtr->outFd = -1;
+ }
+ if (aPtr->ipcType == AGENT_SOCKET &&
+ aPtr->ipc.socket.addrDomain == AF_UNIX) {
+ /* remove the Unix domain socket */
+ unlink(aPtr->ipc.socket.name);
+ }
+ }
+
+ __pmNotifyErr(LOG_INFO, "CleanupAgent ...\n");
+ fprintf(stderr, "Cleanup \"%s\" agent (dom %d):", aPtr->pmDomainLabel, aPtr->pmDomainId);
+
+ if (why == AT_EXIT) {
+ /* waitpid has already been done */
+ fprintf(stderr, " terminated");
+ reason = (status << 8) | REASON_EXIT;
+ }
+ else {
+ if (why == AT_CONFIG) {
+ fprintf(stderr, " unconfigured");
+ } else {
+ reason = REASON_PROTOCOL;
+ fprintf(stderr, " protocol failure for fd=%d", status);
+#ifndef IS_MINGW
+ exit_status = -1;
+#endif
+ }
+ if (aPtr->status.isChild == 1) {
+ pid_t pid = (pid_t)-1;
+ pid_t done;
+ int wait_status;
+ int slept = 0;
+
+ if (aPtr->ipcType == AGENT_PIPE)
+ pid = aPtr->ipc.pipe.agentPid;
+ else if (aPtr->ipcType == AGENT_SOCKET)
+ pid = aPtr->ipc.socket.agentPid;
+ for ( ; ; ) {
+
+#if defined(HAVE_WAIT3)
+ done = wait3(&wait_status, WNOHANG, NULL);
+#elif defined(HAVE_WAITPID)
+ done = waitpid((pid_t)-1, &wait_status, WNOHANG);
+#else
+ wait_status = 0;
+ done = 0;
+#endif
+ if (done == pid) {
+#ifndef IS_MINGW
+ exit_status = wait_status;
+#endif
+ break;
+ }
+ if (done > 0) {
+ continue;
+ }
+ if (slept) {
+ break;
+ }
+ /* give PMDA a chance to notice the close() and exit */
+ sleep(1);
+ slept = 1;
+ }
+ }
+ }
+#ifndef IS_MINGW
+ if (exit_status != -1) {
+ if (WIFEXITED(exit_status)) {
+ fprintf(stderr, ", exit(%d)", WEXITSTATUS(exit_status));
+ reason = (WEXITSTATUS(exit_status) << 8) | reason;
+ }
+ else if (WIFSIGNALED(exit_status)) {
+ fprintf(stderr, ", signal(%d)", WTERMSIG(exit_status));
+#ifdef WCOREDUMP
+ if (WCOREDUMP(exit_status))
+ fprintf(stderr, ", dumped core");
+#endif
+ reason = (WTERMSIG(exit_status) << 16) | reason;
+ }
+ }
+#endif
+ fputc('\n', stderr);
+ aPtr->reason = reason;
+ aPtr->status.connected = 0;
+ aPtr->status.busy = 0;
+ aPtr->status.notReady = 0;
+ aPtr->status.flags = 0;
+ AgentDied = 1;
+
+ if (_pmcd_trace_mask)
+ pmcd_dump_trace(stderr);
+
+ MarkStateChanges(PMCD_DROP_AGENT);
+}
+
+/* Wait up to total secs for agents to terminate.
+ * Return 0 if all terminate, else -1
+ */
+int
+HarvestAgents(unsigned int total)
+{
+ int i;
+ int sts;
+ int found;
+ pid_t pid;
+ AgentInfo *ap;
+
+ /*
+ * Check for child process termination. Be careful, and ignore any
+ * non-agent processes found.
+ */
+ do {
+#if defined(HAVE_WAIT3)
+ pid = wait3(&sts, WNOHANG, NULL);
+#elif defined(HAVE_WAITPID)
+ pid = waitpid((pid_t)-1, &sts, WNOHANG);
+#else
+ break;
+#endif
+ found = 0;
+ for ( i = 0; i < nAgents; i++) {
+ ap = &agent[i];
+ if (!ap->status.connected || ap->ipcType == AGENT_DSO)
+ continue;
+
+ found = 1;
+ if (pid <= (pid_t)0) {
+ if (total--) {
+ sleep(1);
+ break;
+ } else {
+ return -1;
+ }
+ }
+ if (pid == ((ap->ipcType == AGENT_SOCKET)
+ ? ap->ipc.socket.agentPid
+ : ap->ipc.pipe.agentPid)) {
+ CleanupAgent(ap, AT_EXIT, sts);
+ break;
+ }
+ }
+ } while (found);
+
+ return 0;
+}
diff --git a/src/pmcd/src/client.c b/src/pmcd/src/client.c
new file mode 100644
index 0000000..9d46338
--- /dev/null
+++ b/src/pmcd/src/client.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmcd.h"
+
+#define MIN_CLIENTS_ALLOC 8
+
+int maxClientFd = -1; /* largest fd for a client */
+__pmFdSet clientFds; /* for client select() */
+
+static int clientSize;
+
+/*
+ * For PMDA_INTERFACE_5 or later PMDAs, post a notification that
+ * a context has been closed.
+ */
+static void
+NotifyEndContext(int ctx)
+{
+ int i;
+
+ for (i = 0; i < nAgents; i++) {
+ if (!agent[i].status.connected ||
+ agent[i].status.busy || agent[i].status.notReady)
+ continue;
+ if (agent[i].ipcType == AGENT_DSO) {
+ pmdaInterface *dp = &agent[i].ipc.dso.dispatch;
+ if (dp->comm.pmda_interface >= PMDA_INTERFACE_5) {
+ if (dp->version.four.ext->e_endCallBack != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "NotifyEndContext: DSO PMDA %s (%d) notified of context %d close\n",
+ agent[i].pmDomainLabel, agent[i].pmDomainId,
+ ctx);
+ }
+#endif
+ (*(dp->version.four.ext->e_endCallBack))(ctx);
+ }
+ }
+ }
+ else {
+ /*
+ * Daemon PMDA case ... we don't know the PMDA_INTERFACE
+ * version, so send the notification PDU anyway, and rely on
+ * __pmdaMainPDU() doing the right thing.
+ * Do not expect a response.
+ * Agent may have decided to spontaneously die so don't
+ * bother about any return status from the __pmSendError
+ * either.
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ fprintf(stderr, "NotifyEndContext: daemon PMDA %s (%d) notified of context %d close\n",
+ agent[i].pmDomainLabel, agent[i].pmDomainId, ctx);
+ }
+#endif
+ pmcd_trace(TR_XMIT_PDU, agent[i].inFd, PDU_ERROR, PM_ERR_NOTCONN);
+ __pmSendError(agent[i].inFd, ctx, PM_ERR_NOTCONN);
+ }
+ }
+}
+
+/* Establish a new socket connection to a client */
+ClientInfo *
+AcceptNewClient(int reqfd)
+{
+ static unsigned int seq = 0;
+ int i, fd;
+ __pmSockLen addrlen;
+ struct timeval now;
+
+ i = NewClient();
+ addrlen = __pmSockAddrSize();
+ fd = __pmAccept(reqfd, client[i].addr, &addrlen);
+ if (fd == -1) {
+ if (neterror() == EPERM) {
+ __pmNotifyErr(LOG_NOTICE, "AcceptNewClient(%d): "
+ "Permission Denied\n", reqfd);
+ client[i].fd = -1;
+ DeleteClient(&client[i]);
+ return NULL;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "AcceptNewClient(%d) __pmAccept: %s\n",
+ reqfd, netstrerror());
+ Shutdown();
+ exit(1);
+ }
+ }
+ if (fd > maxClientFd)
+ maxClientFd = fd;
+
+ pmcd_openfds_sethi(fd);
+
+ __pmFD_SET(fd, &clientFds);
+ __pmSetVersionIPC(fd, UNKNOWN_VERSION); /* before negotiation */
+ __pmSetSocketIPC(fd);
+
+ client[i].fd = fd;
+ client[i].status.connected = 1;
+ client[i].status.changes = 0;
+ memset(&client[i].attrs, 0, sizeof(__pmHashCtl));
+
+ /*
+ * Note seq needs to be unique, but we're using a free running counter
+ * and not bothering to check here ... unless we churn through
+ * 4,294,967,296 (2^32) clients while one client remains connected
+ * we won't have a problem
+ */
+ client[i].seq = seq++;
+ __pmtimevalNow(&now);
+ client[i].start = now.tv_sec;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "AcceptNewClient(%d): client[%d] (fd %d)\n", reqfd, i, fd);
+#endif
+ pmcd_trace(TR_ADD_CLIENT, i, 0, 0);
+
+ return &client[i];
+}
+
+int
+NewClient(void)
+{
+ int i, sz;
+
+ for (i = 0; i < nClients; i++)
+ if (!client[i].status.connected)
+ break;
+
+ if (i == clientSize) {
+ clientSize = clientSize ? clientSize * 2 : MIN_CLIENTS_ALLOC;
+ sz = sizeof(ClientInfo) * clientSize;
+ client = (ClientInfo *) realloc(client, sz);
+ if (client == NULL) {
+ __pmNoMem("NewClient", sz, PM_RECOV_ERR);
+ Shutdown();
+ exit(1);
+ }
+ sz -= (sizeof(ClientInfo) * i);
+ memset(&client[i], 0, sz);
+ }
+ client[i].addr = __pmSockAddrAlloc();
+ if (client[i].addr == NULL) {
+ __pmNoMem("NewClient", __pmSockAddrSize(), PM_RECOV_ERR);
+ Shutdown();
+ exit(1);
+ }
+ if (i >= nClients)
+ nClients = i + 1;
+ return i;
+}
+
+/*
+ * Expose ClientInfo struct for client #n
+ */
+ClientInfo *
+GetClient(int n)
+{
+ if (0 <= n && n < nClients && client[n].status.connected)
+ return &client[n];
+ return NULL;
+}
+
+void
+DeleteClient(ClientInfo *cp)
+{
+ int i;
+
+ for (i = 0; i < nClients; i++)
+ if (cp == &client[i])
+ break;
+
+ if (i == nClients) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmNotifyErr(LOG_ERR, "DeleteClient: tried to delete non-existent client\n");
+ Shutdown();
+ exit(1);
+ }
+#endif
+ return;
+ }
+ if (cp->fd != -1) {
+ __pmFD_CLR(cp->fd, &clientFds);
+ __pmCloseSocket(cp->fd);
+ }
+ if (i == nClients-1) {
+ i--;
+ while (i >= 0 && !client[i].status.connected)
+ i--;
+ nClients = (i >= 0) ? i + 1 : 0;
+ }
+ if (cp->fd == maxClientFd) {
+ maxClientFd = -1;
+ for (i = 0; i < nClients; i++) {
+ if (client[i].fd > maxClientFd)
+ maxClientFd = client[i].fd;
+ }
+ }
+ for (i = 0; i < cp->szProfile; i++) {
+ if (cp->profile[i] != NULL) {
+ __pmFreeProfile(cp->profile[i]);
+ cp->profile[i] = NULL;
+ }
+ }
+ __pmFreeAttrsSpec(&cp->attrs);
+ __pmHashClear(&cp->attrs);
+ __pmSockAddrFree(cp->addr);
+ cp->addr = NULL;
+ cp->status.connected = 0;
+ cp->fd = -1;
+
+ NotifyEndContext(cp-client);
+}
+
+void
+MarkStateChanges(int changes)
+{
+ int i;
+
+ for (i = 0; i < nClients; i++) {
+ if (client[i].status.connected == 0)
+ continue;
+ client[i].status.changes |= changes;
+ }
+}
+
+int
+CheckAccountAccess(ClientInfo *cp)
+{
+ __pmHashNode *node;
+ const char *userid;
+ const char *groupid;
+
+ userid = ((node = __pmHashSearch(PCP_ATTR_USERID, &cp->attrs)) ?
+ (const char *)node->data : NULL);
+ groupid = ((node = __pmHashSearch(PCP_ATTR_GROUPID, &cp->attrs)) ?
+ (const char *)node->data : NULL);
+ if (!userid || !groupid)
+ if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD))
+ return PM_ERR_PERMISSION;
+ return __pmAccAddAccount(userid, groupid, &cp->denyOps);
+}
+
+int
+CheckClientAccess(ClientInfo *cp)
+{
+ return __pmAccAddClient(cp->addr, &cp->denyOps);
+}
diff --git a/src/pmcd/src/client.h b/src/pmcd/src/client.h
new file mode 100644
index 0000000..2758e9f
--- /dev/null
+++ b/src/pmcd/src/client.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+/* The table of clients, used by pmcd */
+typedef struct {
+ int fd; /* Socket descriptor */
+ struct { /* Status of connection to client */
+ unsigned int connected : 1; /* Client connected */
+ unsigned int changes : 3; /* PMCD_* bits for changes since last fetch */
+ } status;
+ /* There is an array of profiles, as there is a profile associated
+ * with each client context. The array is not guaranteed to be dense.
+ * The context number sent with each profile/fetch is used to index it.
+ */
+ __pmProfile **profile; /* Client context profile pointers */
+ int szProfile; /* Size of array */
+ unsigned int denyOps; /* Disallowed operations for client */
+ __pmPDUInfo pduInfo;
+ unsigned int seq; /* Client sequence number (pmdapmcd) */
+ time_t start; /* Time client connected (pmdapmcd) */
+ __pmSockAddr *addr; /* Network address of client */
+ __pmHashCtl attrs; /* Connection attributes (auth info) */
+} ClientInfo;
+
+PMCD_EXTERN ClientInfo *client; /* Array of clients */
+PMCD_EXTERN int nClients; /* Number of entries in array */
+extern int maxClientFd; /* largest fd for a client */
+extern __pmFdSet clientFds; /* for client select() */
+PMCD_EXTERN int this_client_id; /* client for current request */
+
+/* prototypes */
+extern ClientInfo *AcceptNewClient(int);
+extern int NewClient(void);
+extern void DeleteClient(ClientInfo *);
+extern ClientInfo *GetClient(int);
+PMCD_EXTERN void ShowClients(FILE *m);
+extern int CheckClientAccess(ClientInfo *);
+extern int CheckAccountAccess(ClientInfo *);
+
+#ifdef PCP_DEBUG
+extern char *nameclient(int);
+#endif
+
+#endif /* _CLIENT_H */
diff --git a/src/pmcd/src/config.c b/src/pmcd/src/config.c
new file mode 100644
index 0000000..740a0f5
--- /dev/null
+++ b/src/pmcd/src/config.c
@@ -0,0 +1,2526 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * PMCD routines for reading config file, creating children and
+ * attaching to DSOs.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmcd.h"
+#include <ctype.h>
+#include <sys/stat.h>
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_SYS_UN_H)
+#include <sys/un.h>
+#endif
+#if defined(HAVE_DLFCN_H)
+#include <dlfcn.h>
+#elif defined(HAVE_DL_H)
+#include <dl.h>
+#endif
+
+#define MIN_AGENTS_ALLOC 3 /* Number to allocate first time */
+#define LINEBUF_SIZE 200
+
+/* Config file modification time */
+#if defined(HAVE_STAT_TIMESTRUC)
+static struct timestruc configFileTime;
+#elif defined(HAVE_STAT_TIMESPEC)
+static struct timespec configFileTime;
+#elif defined(HAVE_STAT_TIMESPEC_T)
+static timespec_t configFileTime;
+#elif defined(HAVE_STAT_TIME_T)
+static time_t configFileTime;
+#else
+!bozo!
+#endif
+
+int szAgents; /* Number currently allocated */
+int mapdom[MAXDOMID+2]; /* The DomainId-to-AgentIndex map */
+ /* Don't use it during parsing! */
+
+static FILE *inputStream; /* Input stream for scanner */
+static int scanInit;
+static int scanError; /* Problem in scanner */
+static char *linebuf; /* Buffer for input stream */
+static int szLineBuf; /* Allocated size of linebuf */
+static char *token; /* Start of current token */
+static char *tokenend; /* End of current token */
+static int nLines; /* Current line of config file */
+static int linesize; /* Length of line in linebuf */
+
+/* Macro to compare a string with token. The linebuf is always null terminated
+ * so there are no nasty boundary conditions.
+ */
+#define TokenIs(str) ((tokenend - token) == strlen(str) && \
+ !strncasecmp(token, str, strlen(str)))
+
+/* Return the numeric value of token (or zero if token is not numeric). */
+static int
+TokenNumVal(void)
+{
+ int val = 0;
+ char *p = token;
+ while (isdigit((int)*p)) {
+ val = val * 10 + *p - '0';
+ p++;
+ }
+ return val;
+}
+
+/* Return true if token is a numeric value */
+static int
+TokenIsNumber(void)
+{
+ char *p;
+ if (token == tokenend) /* Nasty end of input case */
+ return 0;
+ for (p = token; isdigit((int)*p); p++)
+ ;
+ return p == tokenend;
+}
+
+/* Return a strdup-ed copy of the current token. */
+static char*
+CopyToken(void)
+{
+ int len = (int)(tokenend - token);
+ char *copy = (char *)malloc(len + 1);
+ if (copy != NULL) {
+ strncpy(copy, token, len);
+ copy[len] = '\0';
+ }
+ return copy;
+}
+
+/* Get the next line from the input stream into linebuf. */
+
+static void
+GetNextLine(void)
+{
+ char *end;
+ int more; /* There is more line to read */
+ int still_to_read;
+ int atEOF = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "%d: GetNextLine()\n", nLines);
+#endif
+
+ if (szLineBuf == 0) {
+ szLineBuf = LINEBUF_SIZE;
+ linebuf = (char *)malloc(szLineBuf);
+ if (linebuf == NULL)
+ __pmNoMem("pmcd config: GetNextLine init", szLineBuf, PM_FATAL_ERR);
+ }
+
+ linebuf[0] = '\0';
+ token = linebuf;
+ if (feof(inputStream))
+ return;
+
+ end = linebuf;
+ more = 0;
+ still_to_read = szLineBuf;
+ do {
+ /* Read into linebuf. If more is set, the read is into a realloc()ed
+ * version of linebuf. In this case, more is the number of characters
+ * at the end of the previous batch that should be overwritten
+ */
+ if (fgets(end, still_to_read, inputStream) == NULL) {
+ if (!feof(inputStream)) {
+ fprintf(stderr, "pmcd config[line %d]: Error: fgets failed: %s\n",
+ nLines, osstrerror());
+ scanError = 1;
+ return;
+ }
+ atEOF = 1;
+ }
+
+ linesize = (int)strlen(linebuf);
+ more = 0;
+ if (linesize == 0)
+ break;
+ if (linebuf[linesize - 1] != '\n') {
+ if (feof(inputStream)) {
+ /* Final input line has no '\n', so add one. If a terminating
+ * null fits after it, that's the line, so break out of loop.
+ */
+ linebuf[linesize] = '\n';
+ /* Add terminating null if it fits */
+ if (linesize + 1 < szLineBuf) {
+ linebuf[++linesize] = '\0';
+ break;
+ }
+ /* If no room for null, get more buffer space */
+ }
+ more = 1; /* More buffer space required */
+ }
+ /* Check for continued lines */
+ else if (linesize > 1 && linebuf[linesize - 2] == '\\') {
+ linebuf[linesize - 2] = ' ';
+ linesize--; /* Pretend the \n isn't there */
+ more = 2; /* Overwrite \n and \0 on next read */
+ }
+
+ /* Make buffer larger to accomodate more of the line. */
+ if (more) {
+ if (szLineBuf > 10 * LINEBUF_SIZE) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: ridiculously long line (%d characters)\n",
+ nLines+1, szLineBuf);
+ linebuf[0] = '\0';
+ scanError = 1;
+ return;
+ }
+ szLineBuf += LINEBUF_SIZE;
+ if ((linebuf = realloc(linebuf, szLineBuf)) == NULL) {
+ static char fallback[2];
+
+ __pmNoMem("pmcd config: GetNextLine", szLineBuf, PM_RECOV_ERR);
+ linebuf = fallback;
+ linebuf[0] = '\0';
+ scanError = 1;
+ return;
+ }
+ end = linebuf + linesize;
+ still_to_read = LINEBUF_SIZE + more;
+ /* *end is where the next fgets will start putting data.
+ * There is a special case if we are already at end of input:
+ * *end is the '\n' added to the line since it didn't have one.
+ * We are here because the terminating null wouldn't fit.
+ */
+ if (atEOF) {
+ end[1] = '\0';
+ linesize++;
+ break;
+ }
+ token = linebuf; /* We may have a new buffer */
+ }
+ } while (more);
+ nLines++;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "\n===================NEWLINE=================\n\n");
+ fprintf(stderr, "len = %d\nline = \"%s\"\n", (int)strlen(linebuf), linebuf);
+ }
+#endif
+}
+
+/* Advance through the input stream until either a non-whitespace character, a
+ * newline, or end of input is reached.
+ */
+static void
+SkipWhitespace(void)
+{
+ while (*token) {
+ char ch = *token;
+
+ if (isspace((int)ch))
+ if (ch == '\n') /* Stop at end of line */
+ return;
+ else
+ token++;
+ else if (ch == '#') {
+ token = &linebuf[linesize-1];
+ return;
+ }
+ else
+ return;
+ }
+}
+
+static int scanReadOnly; /* Set => don't modify input scanner */
+static int doingAccess; /* Set => parsing [access] section */
+static int tokenQuoted; /* True when token a quoted string */
+
+/* Print the current token on a given stream. */
+
+static void
+PrintToken(FILE *stream)
+{
+ char *p;
+ if (tokenQuoted)
+ fputc('"', stream);
+ for (p = token; p < tokenend; p++) {
+ if (*p == '\n')
+ fputs("<newline>", stream);
+ else if (*p == '\0')
+ fputs("<null>", stream);
+ else
+ fputc(*p, stream);
+ }
+ if (tokenQuoted)
+ fputc('"', stream);
+}
+
+/* Move to the next token in the input stream. This is done by skipping any
+ * non-whitespace characters to get to the end of the current token then
+ * skipping any whitespace and newline characters to get to the next token.
+ */
+
+static void
+FindNextToken(void)
+{
+ static char *rawToken; /* Used in pathological quoting case */
+ char ch;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "FindNextToken() ");
+ fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend);
+ }
+#endif
+
+ do {
+ if (scanInit) {
+ if (*token == '\0') /* EOF is EOF and that's final */
+ return;
+ if (scanError) /* Ditto for scanner errors */
+ return;
+
+ if (*token == '\n') /* If at end of line, get next line */
+ GetNextLine();
+ else { /* Otherwise move past "old" token */
+ if (tokenQuoted) { /* Move past last quote */
+ tokenend++;
+ tokenQuoted = 0;
+ }
+ token = tokenend;
+ }
+ SkipWhitespace(); /* Find null, newline or non-space */
+ }
+ else {
+ scanInit = 1;
+ scanError = 0;
+ GetNextLine();
+ SkipWhitespace(); /* Don't return yet, find tokenend */
+ }
+ } while (doingAccess && *token == '\n');
+
+ /* Now we have the start of a token. Find the end. */
+
+ ch = *token;
+ if (ch == '\0' || ch == '\n') {
+ tokenend = token;
+ return;
+ }
+
+ if (doingAccess)
+ if (ch == ',' || ch == ':' || ch == ';' || ch == '[' || ch == ']') {
+ tokenend = token + 1;
+ return;
+ }
+
+ rawToken = token; /* Save real token start in case it moves */
+ tokenend = token + 1;
+ if (ch == '#') /* For comments, token is newline */
+ token = tokenend = &linebuf[linesize-1];
+ else {
+ int inQuotes = *token == '"';
+ int fixToken = 0;
+
+ do {
+ int gotEnd = isspace((int)*tokenend);
+
+ while (!gotEnd) {
+ switch (*tokenend) {
+ case '#': /* \# or # in quotes does not start a comment */
+ if (*(tokenend - 1) == '\\' || inQuotes)
+ fixToken = 1;
+ else /* Comments don't need whitespace in front */
+ gotEnd = 1;
+ break;
+
+ case ',':
+ case ':':
+ case ';':
+ case '[':
+ case ']':
+ gotEnd = doingAccess && !inQuotes;
+ break;
+
+ case '"':
+ if (*(tokenend - 1) == '\\')
+ fixToken = 1;
+ else {
+ if (inQuotes) {
+ inQuotes = 0;
+ gotEnd = 1;
+ }
+ }
+ break;
+
+ default:
+ gotEnd = isspace((int)*tokenend);
+ }
+ if (gotEnd)
+ break;
+ tokenend++;
+ }
+ /* Skip any whitespace if still in quotes, but stop at end of line */
+ if (inQuotes)
+ while (isspace((int)*tokenend) && *tokenend != '\n')
+ tokenend++;
+ } while (inQuotes && *tokenend != '\n');
+
+ if (inQuotes) {
+ scanError = 1;
+ *token = 0;
+ tokenend = token;
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: unterminated quoted string\n",
+ nLines);
+ return;
+ }
+
+ /* Replace any \# or \" in the token with # or " */
+ if (fixToken && !scanReadOnly) {
+ char *p, *q;
+
+ for (p = q = tokenend; p >= token; p--) {
+ if (*p == '\\' && ( p[1] == '#' || p[1] == '"') )
+ continue;
+ *q-- = *p;
+ }
+ token = q + 1;
+ }
+ }
+
+ /* If token originally started with a quote, token is what's inside quotes.
+ * Note that *rawToken is checked since *token will also be " if the
+ * token originally started with a \" that has been changed to ".
+ */
+ if (*rawToken == '"') {
+ token++;
+ tokenQuoted = 1;
+ }
+ else
+ tokenQuoted = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fputs("TOKEN = '", stderr);
+ PrintToken(stderr);
+ fputs("' ", stderr);
+ fprintf(stderr, "scanInit=%d scanError=%d scanReadOnly=%d doingAccess=%d tokenQuoted=%d token=%p tokenend=%p\n", scanInit, scanError, scanReadOnly, doingAccess, tokenQuoted, token, tokenend);
+ }
+#endif
+}
+
+/* Move to the next line of the input stream. */
+
+static void
+SkipLine(void)
+{
+ while (*token && *token != '\n')
+ FindNextToken();
+ FindNextToken(); /* Move over the newline */
+}
+
+/* From an argv, build a command line suitable for display in logs etc. */
+
+static char *
+BuildCmdLine(char **argv)
+{
+ int i, cmdLen = 0;
+ char *cmdLine;
+ char *p;
+
+ if (argv == NULL)
+ return NULL;
+ for (i = 0; argv[i] != NULL; i++) {
+ cmdLen += strlen(argv[i]) + 1; /* +1 for space separator or null */
+ /* any arg with whitespace appears in quotes */
+ if (strpbrk(argv[i], " \t") != NULL)
+ cmdLen += 2;
+ /* any quote gets a \ prepended */
+ for (p = argv[i]; *p; p++)
+ if (*p == '"')
+ cmdLen++;
+ }
+
+ if ((cmdLine = (char *)malloc(cmdLen)) == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: failed to build command line\n",
+ nLines);
+ __pmNoMem("pmcd config: BuildCmdLine", cmdLen, PM_RECOV_ERR);
+ return NULL;
+ }
+ for (i = 0, p = cmdLine; argv[i] != NULL; i++) {
+ int quote = strpbrk(argv[i], " \t") != NULL;
+ char *q;
+
+ if (quote)
+ *p++ = '"';
+ for (q = argv[i]; *q; q++) {
+ if (*q == '"')
+ *p++ = '\\';
+ *p++ = *q;
+ }
+ if (quote)
+ *p++ = '"';
+ if (argv[i+1] != NULL)
+ *p++ = ' ';
+ }
+ *p = '\0';
+ return cmdLine;
+}
+
+
+/* Build an argument list suitable for an exec call from the rest of the tokens
+ * on the current line.
+ */
+char **
+BuildArgv(void)
+{
+ int nArgs;
+ char **result;
+
+ nArgs = 0;
+ result = NULL;
+ do {
+ /* Make result big enough for new arg and terminating NULL pointer */
+ result = (char **)realloc(result, (nArgs + 2) * sizeof(char *));
+ if (result != NULL) {
+ if (*token != '/')
+ result[nArgs] = CopyToken();
+ else if ((result[nArgs] = CopyToken()) != NULL)
+ __pmNativePath(result[nArgs]);
+ }
+ if (result == NULL || result[nArgs] == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: failed to build argument list\n",
+ nLines);
+ __pmNoMem("pmcd config: build argv", nArgs * sizeof(char *),
+ PM_RECOV_ERR);
+ if (result != NULL) {
+ while (nArgs >= 0) {
+ if (result[nArgs] != NULL)
+ free(result[nArgs]);
+ nArgs--;
+ }
+ free(result);
+ }
+ return NULL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "argv[%d] = '%s'\n", nArgs, result[nArgs]);
+#endif
+
+ nArgs++;
+ FindNextToken();
+ } while (*token && *token != '\n');
+ result[nArgs] = NULL;
+
+ return result;
+}
+
+/* Return the next unused index into the agent array, extending the array
+ as necessary. */
+static AgentInfo *
+GetNewAgent(void)
+{
+ AgentInfo *na;
+
+ if (agent == NULL) {
+ agent = (AgentInfo*)malloc(sizeof(AgentInfo) * MIN_AGENTS_ALLOC);
+ if (agent == NULL) {
+ perror("GetNewAgentIndex: malloc");
+ exit(1);
+ }
+ szAgents = MIN_AGENTS_ALLOC;
+ }
+ else if (nAgents >= szAgents) {
+ agent = (AgentInfo*)
+ realloc(agent, sizeof(AgentInfo) * 2 * szAgents);
+ if (agent == NULL) {
+ perror("GetNewAgentIndex: realloc");
+ exit(1);
+ }
+ szAgents *= 2;
+ }
+
+ na = agent+nAgents; nAgents++;
+ memset (na, 0, sizeof(AgentInfo));
+
+ return na;
+}
+
+/* Free any malloc()-ed memory associated with an agent */
+
+static void
+FreeAgent(AgentInfo *ap)
+{
+ int i;
+ char **argv = NULL;
+
+ free(ap->pmDomainLabel);
+ if (ap->ipcType == AGENT_DSO) {
+ free(ap->ipc.dso.pathName);
+ free(ap->ipc.dso.entryPoint);
+ }
+ else if (ap->ipcType == AGENT_SOCKET) {
+ if (ap->ipc.socket.commandLine != NULL) {
+ free(ap->ipc.socket.commandLine);
+ argv = ap->ipc.socket.argv;
+ }
+ }
+ else
+ if (ap->ipc.pipe.commandLine != NULL) {
+ free(ap->ipc.pipe.commandLine);
+ argv = ap->ipc.pipe.argv;
+ }
+
+ if (argv != NULL) {
+ for (i = 0; argv[i] != NULL; i++)
+ free(argv[i]);
+ free(argv);
+ }
+}
+
+/* Parse a DSO specification, creating and initialising a new entry in the
+ * agent table if the spec has no errors.
+ */
+static int
+ParseDso(char *pmDomainLabel, int pmDomainId)
+{
+ char *pathName;
+ char *entryPoint;
+ AgentInfo *newAgent;
+ int xlatePath = 0;
+
+ FindNextToken();
+ if (*token == '\n') {
+ fprintf(stderr, "pmcd config[line %d]: Error: expected DSO entry point\n", nLines);
+ return -1;
+ }
+ if ((entryPoint = CopyToken()) == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO entry point\n",
+ nLines);
+ __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR);
+ }
+
+ FindNextToken();
+ if (*token == '\n') {
+ fprintf(stderr, "pmcd config[line %d]: Error: expected DSO pathname\n", nLines);
+ free(entryPoint);
+ return -1;
+ }
+ if (*token != '/') {
+ if (token[strlen(token)-1] == '\n')
+ token[strlen(token)-1] = '\0';
+ fprintf(stderr, "pmcd config[line %d]: Error: path \"%s\" to PMDA is not absolute\n", nLines, token);
+ free(entryPoint);
+ return -1;
+ }
+
+ if ((pathName = CopyToken()) == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy DSO pathname\n",
+ nLines);
+ __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR);
+ }
+ __pmNativePath(pathName);
+
+ FindNextToken();
+ if (*token != '\n') {
+ fprintf(stderr, "pmcd config[line %d]: Error: too many parameters for DSO\n",
+ nLines);
+ free(entryPoint);
+ free(pathName);
+ return -1;
+ }
+
+ /* Now create and initialise a slot in the agents table for the new agent */
+
+ newAgent = GetNewAgent();
+
+ newAgent->ipcType = AGENT_DSO;
+ newAgent->pmDomainId = pmDomainId;
+ newAgent->inFd = -1;
+ newAgent->outFd = -1;
+ newAgent->pmDomainLabel = strdup(pmDomainLabel);
+ newAgent->ipc.dso.pathName = pathName;
+ newAgent->ipc.dso.xlatePath = xlatePath;
+ newAgent->ipc.dso.entryPoint = entryPoint;
+
+ return 0;
+}
+
+/* Parse a socket specification, creating and initialising a new entry in the
+ * agent table if the spec has no errors.
+ */
+static int
+ParseSocket(char *pmDomainLabel, int pmDomainId)
+{
+ int addrDomain, port = -1;
+ char *socketName = NULL;
+ AgentInfo *newAgent;
+
+ FindNextToken();
+ if (TokenIs("inet"))
+ addrDomain = AF_INET;
+ else if (TokenIs("ipv6"))
+ addrDomain = AF_INET6;
+ else if (TokenIs("unix"))
+ addrDomain = AF_UNIX;
+ else {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: expected socket address domain (`inet', `ipv6', or `unix')\n",
+ nLines);
+ return -1;
+ }
+
+ FindNextToken();
+ if (*token == '\n') {
+ fprintf(stderr, "pmcd config[line %d]: Error: expected socket port name or number\n",
+ nLines);
+ return -1;
+ }
+ else if (TokenIsNumber())
+ port = TokenNumVal();
+ else
+ if ((socketName = CopyToken()) == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: couldn't copy port name\n",
+ nLines);
+ __pmNoMem("pmcd config", tokenend - token + 1, PM_FATAL_ERR);
+ }
+ FindNextToken();
+
+ /* If an internet domain port name was specified, find the corresponding
+ port number. */
+
+ if ((addrDomain == AF_INET || addrDomain == AF_INET6) && socketName) {
+ struct servent *service;
+
+ service = getservbyname(socketName, NULL);
+ if (service)
+ port = service->s_port;
+ else {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: failed to get port number for port name %s\n",
+ nLines, socketName);
+ free(socketName);
+ return -1;
+ }
+ }
+
+ /* Now create and initialise a slot in the agents table for the new agent */
+
+ newAgent = GetNewAgent();
+
+ newAgent->ipcType = AGENT_SOCKET;
+ newAgent->pmDomainId = pmDomainId;
+ newAgent->inFd = -1;
+ newAgent->outFd = -1;
+ newAgent->pmDomainLabel = strdup(pmDomainLabel);
+ newAgent->ipc.socket.addrDomain = addrDomain;
+ newAgent->ipc.socket.name = socketName;
+ newAgent->ipc.socket.port = port;
+ if (*token != '\n') {
+ newAgent->ipc.socket.argv = BuildArgv();
+ if (newAgent->ipc.socket.argv == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n",
+ nLines, newAgent->pmDomainLabel);
+ FreeAgent(newAgent);
+ nAgents--;
+ return -1;
+ }
+ newAgent->ipc.socket.commandLine = BuildCmdLine(newAgent->ipc.socket.argv);
+ }
+ newAgent->ipc.socket.agentPid = (pid_t)-1;
+
+ return 0;
+}
+
+/* Parse a pipe specification, creating and initialising a new entry in the
+ * agent table if the spec has no errors.
+ */
+static int
+ParsePipe(char *pmDomainLabel, int pmDomainId)
+{
+ int i;
+ AgentInfo *newAgent;
+ int notReady = 0;
+
+ FindNextToken();
+ if (!TokenIs("binary")) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: pipe PDU type expected (`binary')\n",
+ nLines);
+ return -1;
+ }
+
+ do {
+ i = 0;
+ FindNextToken();
+ if (*token == '\n') {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: command to create pipe agent expected.\n",
+ nLines);
+ return -1;
+ } else if ((i = TokenIs ("notready"))) {
+ notReady = 1;
+ }
+ } while (i);
+
+ /* Now create and initialise a slot in the agents table for the new agent */
+
+ newAgent = GetNewAgent();
+ newAgent->ipcType = AGENT_PIPE;
+ newAgent->pmDomainId = pmDomainId;
+ newAgent->inFd = -1;
+ newAgent->outFd = -1;
+ newAgent->pmDomainLabel = strdup(pmDomainLabel);
+ newAgent->status.startNotReady = notReady;
+ newAgent->ipc.pipe.argv = BuildArgv();
+
+ if (newAgent->ipc.pipe.argv == NULL) {
+ fprintf(stderr, "pmcd config[line %d]: Error: building argv for \"%s\" agent.\n",
+ nLines, newAgent->pmDomainLabel);
+ FreeAgent(newAgent);
+ nAgents--;
+ return -1;
+ }
+ newAgent->ipc.pipe.commandLine = BuildCmdLine(newAgent->ipc.pipe.argv);
+
+ return 0;
+}
+
+static int
+ParseAccessSpec(int allow, int *specOps, int *denyOps, int *maxCons, int recursion)
+{
+ int op; /* >0 for specific ops, 0 otherwise */
+ int haveOps = 0, haveAll = 0;
+ int haveComma = 0;
+
+ if (*token == ';') {
+ fprintf(stderr, "pmcd config[line %d]: Error: empty or incomplete permissions list\n",
+ nLines);
+ return -1;
+ }
+
+ if (!recursion) /* Set maxCons to unspecified 1st time */
+ *maxCons = 0;
+ while (*token && *token != ';') {
+ op = 0;
+ if (TokenIs("fetch"))
+ op = PMCD_OP_FETCH;
+ else if (TokenIs("store"))
+ op = PMCD_OP_STORE;
+ else if (TokenIs("all")) {
+ if (haveOps) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n",
+ nLines);
+ return -1;
+ }
+ haveAll = 1;
+ if (recursion) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: can't have \"all\" within an \"all except\"\n",
+ nLines);
+ return -1;
+ }
+ FindNextToken();
+
+ /* Any "all" statement specifies permissions for all operations
+ * Start off with all operations in allow/disallowed state
+ */
+ *denyOps = allow ? PMCD_OP_NONE : PMCD_OP_ALL;
+
+ if (TokenIs("except")) {
+ /* Now deal with exceptions by reversing the "allow" sense */
+ int sts;
+
+ FindNextToken();
+ sts = ParseAccessSpec(!allow, specOps, denyOps, maxCons, 1);
+ if (sts < 0) return -1;
+ }
+ *specOps = PMCD_OP_ALL; /* Do this AFTER any recursive call */
+ }
+ else if (TokenIs("maximum") || TokenIs("unlimited")) {
+ int unlimited = (*token == 'u' || *token == 'U');
+
+ if (*maxCons) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: connection limit already specified\n",
+ nLines);
+ return -1;
+ }
+ if (recursion && !haveOps) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: connection limit may not immediately follow \"all except\"\n",
+ nLines);
+ return -1;
+ }
+
+ /* "maximum N connections" or "unlimited connections" is not
+ * allowed in a disallow statement. This is a bit tricky, because
+ * of the recursion in "all except", which flips "allow" into
+ * !"allow" and recursion from 0 to 1 for the recursive call to
+ * this function. The required test is !XOR: "!recursion && allow"
+ * is an "allow" with no "except". "recursion && !allow" is an
+ * "allow" with an "except" anything else is a "disallow" (i.e. an
+ * error)
+ */
+ if (!(recursion ^ allow)) { /* disallow statement */
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: can't specify connection limit in a disallow statement\n",
+ nLines);
+ return -1;
+ }
+ if (unlimited)
+ *maxCons = -1;
+ else {
+ FindNextToken();
+ if (!TokenIsNumber() || TokenNumVal() <= 0) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: maximum connection limit must be a positive number\n",
+ nLines);
+ return -1;
+ }
+ *maxCons = TokenNumVal();
+ FindNextToken();
+ }
+ if (!TokenIs("connections")) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: \"connections\" expected\n",
+ nLines);
+ return -1;
+ }
+ FindNextToken();
+ }
+ else {
+ fprintf(stderr, "pmcd config[line %d]: Error: bad access specifier\n",
+ nLines);
+ return -1;
+ }
+
+ /* If there was a specific operation mentioned, (dis)allow it */
+ if (op) {
+ if (haveAll) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: can't have \"all\" mixed with specific permissions\n",
+ nLines);
+ return -1;
+ }
+ haveOps = 1;
+ *specOps |= op;
+ if (allow)
+ *denyOps &= (~op);
+ else
+ *denyOps |= op;
+ FindNextToken();
+ }
+ if (*token != ',' && *token != ';') {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: ',' or ';' expected in permission list\n",
+ nLines);
+ return -1;
+ }
+ if (*token == ',') {
+ haveComma = 1;
+ FindNextToken();
+ }
+ else
+ haveComma = 0;
+ }
+ if (haveComma) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: misplaced (trailing) ',' in permission list\n",
+ nLines);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+ParseNames(char ***namesp, const char *nametype)
+{
+ static char **names;
+ static int szNames;
+ int nnames = 0;
+ int another = 1;
+
+ /* Beware of quoted tokens of length longer than 1. e.g. ":*" */
+ while (*token && another &&
+ ((tokenend - token > 1) || (*token != ':' && *token != ';'))) {
+ if (nnames == szNames) {
+ int need;
+
+ szNames += 8;
+ need = szNames * (int)sizeof(char**);
+ if ((names = (char **)realloc(names, need)) == NULL)
+ __pmNoMem("pmcd ParseNames name list", need, PM_FATAL_ERR);
+ }
+ if ((names[nnames++] = CopyToken()) == NULL)
+ __pmNoMem("pmcd ParseNames name", tokenend - token, PM_FATAL_ERR);
+ FindNextToken();
+ if (*token != ',' && *token != ':') {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: ',' or ':' expected after \"%s\"\n",
+ nLines, names[nnames-1]);
+ return -1;
+ }
+ if (*token == ',') {
+ FindNextToken();
+ another = 1;
+ }
+ else
+ another = 0;
+ }
+ if (nnames == 0) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: no %ss in allow/disallow statement\n",
+ nLines, nametype);
+ return -1;
+ }
+ if (another) {
+ fprintf(stderr, "pmcd config[line %d]: Error: %s expected after ','\n",
+ nLines, nametype);
+ return -1;
+ }
+ if (*token != ':') {
+ fprintf(stderr, "pmcd config[line %d]: Error: ':' expected after \"%s\"\n",
+ nLines, names[nnames-1]);
+ return -1;
+ }
+ *namesp = names;
+ return nnames;
+}
+
+static int
+ParseHosts(int allow)
+{
+ int sts;
+ int nhosts;
+ int i;
+ int ok = 0;
+ int specOps = 0;
+ int denyOps = 0;
+ int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */
+ char **hostnames;
+
+ if ((nhosts = ParseNames(&hostnames, "host")) < 0)
+ goto error;
+
+ FindNextToken();
+ if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0)
+ goto error;
+
+ if (pmDebug & DBG_TRACE_APPL1) {
+ for (i = 0; i < nhosts; i++)
+ fprintf(stderr, "HOST ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n",
+ hostnames[i], specOps, denyOps, maxCons);
+ }
+
+ /* Make new entries for hosts in host access list */
+ for (i = 0; i < nhosts; i++) {
+ if ((sts = __pmAccAddHost(hostnames[i], specOps, denyOps, maxCons)) < 0) {
+ if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN)
+ fprintf(stderr, "Warning: the following access control specification will be ignored\n");
+ fprintf(stderr,
+ "pmcd config[line %d]: Warning: access control error for host '%s': %s\n",
+ nLines, hostnames[i], pmErrStr(sts));
+ if (sts == -EHOSTUNREACH || sts == -EHOSTDOWN)
+ ;
+ else
+ goto error;
+ }
+ else
+ ok = 1;
+ }
+ return ok;
+
+error:
+ for (i = 0; i < nhosts; i++)
+ free(hostnames[i]);
+ return -1;
+}
+
+static int
+ParseUsers(int allow)
+{
+ int sts;
+ int nusers;
+ int i;
+ int ok = 0;
+ int specOps = 0;
+ int denyOps = 0;
+ int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */
+ char **usernames;
+
+ if ((nusers = ParseNames(&usernames, "user")) < 0)
+ goto error;
+
+ FindNextToken();
+ if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0)
+ goto error;
+
+ if (pmDebug & DBG_TRACE_APPL1) {
+ for (i = 0; i < nusers; i++)
+ fprintf(stderr, "USER ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n",
+ usernames[i], specOps, denyOps, maxCons);
+ }
+
+ /* Make new entries for users in user access list */
+ for (i = 0; i < nusers; i++) {
+ if ((sts = __pmAccAddUser(usernames[i], specOps, denyOps, maxCons)) < 0) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Warning: access control error for user '%s': %s\n",
+ nLines, usernames[i], pmErrStr(sts));
+ goto error;
+ }
+ ok = 1;
+ }
+ return ok;
+
+error:
+ for (i = 0; i < nusers; i++)
+ free(usernames[i]);
+ return -1;
+}
+
+static int
+ParseGroups(int allow)
+{
+ int sts;
+ int ngroups;
+ int i;
+ int ok = 0;
+ int specOps = 0;
+ int denyOps = 0;
+ int maxCons = 0; /* Zero=>unspecified, -1=>unlimited */
+ char **groupnames;
+
+ if ((ngroups = ParseNames(&groupnames, "group")) < 0)
+ goto error;
+
+ FindNextToken();
+ if (ParseAccessSpec(allow, &specOps, &denyOps, &maxCons, 0) < 0)
+ goto error;
+
+ if (pmDebug & DBG_TRACE_APPL1) {
+ for (i = 0; i < ngroups; i++)
+ fprintf(stderr, "GROUP ACCESS: %s specOps=%02x denyOps=%02x maxCons=%d\n",
+ groupnames[i], specOps, denyOps, maxCons);
+ }
+
+ /* Make new entries for groups in group access list */
+ for (i = 0; i < ngroups; i++) {
+ if ((sts = __pmAccAddGroup(groupnames[i], specOps, denyOps, maxCons)) < 0) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Warning: access control error for group '%s': %s\n",
+ nLines, groupnames[i], pmErrStr(sts));
+ goto error;
+ }
+ ok = 1;
+ }
+ return ok;
+
+error:
+ for (i = 0; i < ngroups; i++)
+ free(groupnames[i]);
+ return -1;
+}
+
+static int
+ParseAccessControls(void)
+{
+ int sts = 0;
+ int tmp;
+ int allow;
+ int naccess = 0;
+ int need_creds = 0;
+
+ doingAccess = 1;
+ /* This gets a little tricky, because the token may be "[access]", or
+ * "[access" or "[". "[" and "]" can't be made special characters until
+ * the scanner knows it is in the access control section because the arg
+ * lists for agents may contain them.
+ */
+ if (TokenIs("[access]"))
+ FindNextToken();
+ else {
+ if (TokenIs("[")) {
+ FindNextToken();
+ if (!TokenIs("access")) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: \"access\" keyword expected\n",
+ nLines);
+ return -1;
+ }
+ }
+ else if (!TokenIs("[access")) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: \"access\" keyword expected\n",
+ nLines);
+ return -1;
+ }
+ FindNextToken();
+ if (*token != ']') {
+ fprintf(stderr, "pmcd config[line %d]: Error: ']' expected\n", nLines);
+ return -1;
+ }
+ FindNextToken();
+ }
+ while (*token && !scanError) {
+ if (TokenIs("allow"))
+ allow = 1;
+ else if (TokenIs("disallow"))
+ allow = 0;
+ else {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: allow or disallow statement expected\n",
+ nLines);
+ sts = -1;
+ while (*token && !scanError && *token != ';')
+ FindNextToken();
+ if (*token && !scanError && *token == ';') {
+ FindNextToken();
+ continue;
+ }
+ return -1;
+ }
+ FindNextToken();
+ if (TokenIs("user") || TokenIs("users")) {
+ FindNextToken();
+ if ((tmp = ParseUsers(allow)) < 0)
+ sts = -1;
+ else
+ need_creds = 1;
+ } else if (TokenIs("group") || TokenIs("groups")) {
+ FindNextToken();
+ if ((tmp = ParseGroups(allow)) < 0)
+ sts = -1;
+ else
+ need_creds = 1;
+ } else if (TokenIs("host") || TokenIs("hosts")) {
+ FindNextToken();
+ if ((tmp = ParseHosts(allow)) < 0)
+ sts = -1;
+ } else {
+ if ((tmp = ParseHosts(allow)) < 0)
+ sts = -1;
+ }
+ if (tmp > 0)
+ naccess++;
+ while (*token && !scanError && *token != ';')
+ FindNextToken();
+ if (!*token || scanError)
+ return -1;
+ FindNextToken();
+ }
+ if (sts != 0)
+ return sts;
+
+ if (naccess == 0) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: no valid statements in [access] section\n",
+ nLines);
+ return -1;
+ }
+
+ if (need_creds)
+ __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmAccDumpLists(stderr);
+#endif
+
+ return 0;
+}
+
+/* Parse the configuration file, creating the agent list. */
+static int
+ReadConfigFile(FILE *configFile)
+{
+ char *pmDomainLabel = NULL;
+ int i, pmDomainId;
+ int sts = 0;
+
+ inputStream = configFile;
+ scanInit = 0;
+ scanError = 0;
+ doingAccess = 0;
+ nLines = 0;
+ FindNextToken();
+ while (*token && !scanError) {
+ if (*token == '\n') /* It's a comment or blank line */
+ goto doneLine;
+
+ if (*token == '[') /* Start of access control specs */
+ break;
+
+ if ((pmDomainLabel = CopyToken()) == NULL)
+ __pmNoMem("pmcd config: domain label", tokenend - token + 1, PM_FATAL_ERR);
+
+ FindNextToken();
+ if (TokenIsNumber()) {
+ pmDomainId = TokenNumVal();
+ FindNextToken();
+ }
+ else {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: expected domain number for \"%s\" agent\n",
+ nLines, pmDomainLabel);
+ sts = -1;
+ goto doneLine;
+ }
+ if (pmDomainId < 0 || pmDomainId > MAXDOMID) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: Illegal domain number (%d) for \"%s\" agent\n",
+ nLines, pmDomainId, pmDomainLabel);
+ sts = -1;
+ goto doneLine;
+ }
+ /* Can't use mapdom because it isn't built yet. Can't build it during
+ * parsing because this might be a restart parse that fails, requiring
+ * a revert to the old mapdom.
+ */
+ for (i = 0; i < nAgents; i++)
+ if (pmDomainId == agent[i].pmDomainId) {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: domain number for \"%s\" agent clashes with \"%s\" agent\n",
+ nLines, pmDomainLabel, agent[i].pmDomainLabel);
+ sts = -1;
+ goto doneLine;
+ }
+
+ /*
+ * ParseXXX routines must return
+ * -1 for failure and ensure a NewAgent structure has NOT been
+ * allocated
+ * 0 for success with a NewAgent structure allocated
+ */
+ if (TokenIs("dso"))
+ sts = ParseDso(pmDomainLabel, pmDomainId);
+ else if (TokenIs("socket"))
+ sts = ParseSocket(pmDomainLabel, pmDomainId);
+ else if (TokenIs("pipe"))
+ sts = ParsePipe(pmDomainLabel, pmDomainId);
+ else {
+ fprintf(stderr,
+ "pmcd config[line %d]: Error: expected `dso', `socket' or `pipe'\n",
+ nLines);
+ sts = -1;
+ }
+doneLine:
+ if (pmDomainLabel != NULL) {
+ free(pmDomainLabel);
+ pmDomainLabel = NULL;
+ }
+ SkipLine();
+ }
+ if (scanError) {
+ fprintf(stderr, "pmcd config: Can't continue, giving up\n");
+ sts = -1;
+ }
+ if (*token == '[' && sts != -1)
+ if (ParseAccessControls() < 0)
+ sts = -1;
+ return sts;
+}
+
+static int
+DoAuthentication(AgentInfo *ap, int clientID)
+{
+ int sts = 0;
+ __pmHashCtl *attrs = &client[clientID].attrs;
+ __pmHashNode *node;
+
+ if ((ap->status.flags & PDU_FLAG_AUTH) == 0)
+ return 0;
+
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface < PMDA_INTERFACE_6 ||
+ ap->ipc.dso.dispatch.version.six.attribute == NULL)
+ return 0;
+ for (node = __pmHashWalk(attrs, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char buffer[64];
+ __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer));
+ fprintf(stderr, "pmcd: send client[%d] attr %s to dso agent[%d]",
+ clientID, buffer, (int)(ap - agent));
+ }
+#endif
+ if ((sts = ap->ipc.dso.dispatch.version.six.attribute(
+ clientID, node->key, node->data,
+ node->data ? strlen(node->data)+1 : 0,
+ ap->ipc.dso.dispatch.version.six.ext)) < 0)
+ break;
+ }
+ } else {
+ /* daemon PMDA ... ship attributes */
+ if (ap->status.notReady)
+ return PM_ERR_AGAIN;
+ for (node = __pmHashWalk(attrs, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(attrs, PM_HASH_WALK_NEXT)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ char buffer[64];
+ __pmAttrStr_r(node->key, node->data, buffer, sizeof(buffer));
+ fprintf(stderr, "pmcd: send client[%d] attr %s to daemon agent[%d]",
+ clientID, buffer, (int)(ap - agent));
+ }
+#endif
+ if ((sts = __pmSendAuth(ap->inFd,
+ clientID, node->key, node->data,
+ node->data ? strlen(node->data)+1 : 0)) < 0)
+ break;
+ }
+ }
+ return sts;
+}
+
+/*
+ * Once a secure client arrives, we need to inform any interested PMDAs.
+ * Iterate over the authenticating agents and send connection attributes.
+ */
+int
+AgentsAuthentication(int clientID)
+{
+ int agentID, sts = 0;
+
+ for (agentID = 0; agentID < nAgents; agentID++) {
+ if (agent[agentID].status.connected &&
+ (sts = DoAuthentication(&agent[agentID], clientID)) < 0)
+ break;
+ }
+ return sts;
+}
+
+/*
+ * Once a PMDA has started, we need to inform it about secure clients.
+ * Iterate over the authenticated clients and send connection attributes
+ */
+int
+ClientsAuthentication(AgentInfo *ap)
+{
+ int clientID, sts = 0;
+
+ for (clientID = 0; clientID < nClients; clientID++) {
+ if (client[clientID].status.connected &&
+ (sts = DoAuthentication(ap, clientID)) < 0)
+ break;
+ }
+ return sts;
+}
+
+static int
+DoAgentCreds(AgentInfo* aPtr, __pmPDU *pb)
+{
+ int i;
+ int sts = 0;
+ int flags = 0;
+ int sender = 0;
+ int credcount = 0;
+ int version = UNKNOWN_VERSION;
+ __pmCred *credlist = NULL;
+ __pmVersionCred *vcp;
+
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0)
+ return sts;
+ pmcd_trace(TR_RECV_PDU, aPtr->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+
+ for (i = 0; i < credcount; i++) {
+ switch (credlist[i].c_type) {
+ case CVERSION:
+ vcp = (__pmVersionCred *)&credlist[i];
+ aPtr->pduVersion = version = vcp->c_version;
+ aPtr->status.flags = flags = vcp->c_flags;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmcd: version creds (version=%u,flags=%x)\n",
+ aPtr->pduVersion, aPtr->status.flags);
+#endif
+ break;
+ }
+ }
+
+ if (credlist != NULL)
+ free(credlist);
+
+ if (((sts = __pmSetVersionIPC(aPtr->inFd, version)) < 0) ||
+ ((sts = __pmSetVersionIPC(aPtr->outFd, version)) < 0))
+ return sts;
+
+ if (version != UNKNOWN_VERSION) { /* finish the version exchange */
+ __pmVersionCred handshake;
+ __pmCred *cp = (__pmCred *)&handshake;
+
+ /* return pmcd PDU version and all flags pmcd knows about */
+ handshake.c_type = CVERSION;
+ handshake.c_version = PDU_VERSION;
+ handshake.c_flags = (flags & PDU_FLAG_AUTH);
+ if ((sts = __pmSendCreds(aPtr->inFd, (int)getpid(), 1, cp)) < 0)
+ return sts;
+ pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_CREDS, credcount);
+
+ /* send auth attributes for existing connected clients */
+ if ((flags & PDU_FLAG_AUTH) != 0 &&
+ (sts = ClientsAuthentication(aPtr)) < 0)
+ return sts;
+ }
+
+ return 0;
+}
+
+/* version exchange - get a credentials PDU from 2.0 agents */
+static int
+AgentNegotiate(AgentInfo *aPtr)
+{
+ int sts;
+ __pmPDU *ack;
+
+ sts = __pmGetPDU(aPtr->outFd, ANY_SIZE, _creds_timeout, &ack);
+ if (sts == PDU_CREDS) {
+ if ((sts = DoAgentCreds(aPtr, ack)) < 0) {
+ fprintf(stderr, "pmcd: version exchange failed "
+ "for \"%s\" agent: %s\n", aPtr->pmDomainLabel, pmErrStr(sts));
+ }
+ __pmUnpinPDUBuf(ack);
+ return sts;
+ }
+
+ if (sts > 0) {
+ fprintf(stderr, "pmcd: unexpected PDU type (0x%x) at initial "
+ "exchange with %s PMDA\n", sts, aPtr->pmDomainLabel);
+ __pmUnpinPDUBuf(ack);
+ }
+ else if (sts == 0)
+ fprintf(stderr, "pmcd: unexpected end-of-file at initial "
+ "exchange with %s PMDA\n", aPtr->pmDomainLabel);
+ else
+ fprintf(stderr, "pmcd: error at initial PDU exchange with "
+ "%s PMDA: %s\n", aPtr->pmDomainLabel, pmErrStr(sts));
+ return PM_ERR_IPC;
+}
+
+/* Connect to an agent's socket. */
+static int
+ConnectSocketAgent(AgentInfo *aPtr)
+{
+ int sts = 0;
+ int fd = -1; /* pander to gcc */
+
+ if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6) {
+ __pmSockAddr *addr;
+ __pmHostEnt *host;
+ void *enumIx;
+
+ if ((host = __pmGetAddrInfo("localhost")) == NULL) {
+ fputs("pmcd: Error getting inet address for localhost\n", stderr);
+ goto error;
+ }
+ enumIx = NULL;
+ for (addr = __pmHostEntGetSockAddr(host, &enumIx);
+ addr != NULL;
+ addr = __pmHostEntGetSockAddr(host, &enumIx)) {
+ if (__pmSockAddrIsInet(addr)) {
+ /* Only consider addresses of the chosen family. */
+ if (aPtr->ipc.socket.addrDomain != AF_INET)
+ continue;
+ fd = __pmCreateSocket();
+ }
+ else if (__pmSockAddrIsIPv6(addr)) {
+ /* Only consider addresses of the chosen family. */
+ if (aPtr->ipc.socket.addrDomain != AF_INET6)
+ continue;
+ fd = __pmCreateIPv6Socket();
+ }
+ else {
+ fprintf(stderr,
+ "pmcd: Error creating socket for \"%s\" agent : invalid address family %d\n",
+ aPtr->pmDomainLabel, __pmSockAddrGetFamily(addr));
+ fd = -1;
+ }
+ if (fd < 0) {
+ __pmSockAddrFree(addr);
+ continue; /* Try the next address */
+ }
+
+ __pmSockAddrSetPort(addr, aPtr->ipc.socket.port);
+ sts = __pmConnect(fd, (void *)addr, __pmSockAddrSize());
+ __pmSockAddrFree(addr);
+
+ if (sts == 0)
+ break; /* good connection */
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(fd);
+ fd = -1;
+ }
+ __pmHostEntFree(host);
+ }
+ else {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ struct sockaddr_un addr;
+ int len;
+
+ fd = socket(aPtr->ipc.socket.addrDomain, SOCK_STREAM, 0);
+ if (fd < 0) {
+ fprintf(stderr,
+ "pmcd: Error creating socket for \"%s\" agent : %s\n",
+ aPtr->pmDomainLabel, netstrerror());
+ return -1;
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, aPtr->ipc.socket.name);
+ len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(addr.sun_path);
+ sts = connect(fd, (struct sockaddr *) &addr, len);
+#else
+ fprintf(stderr, "pmcd: UNIX sockets are not supported : \"%s\" agent\n",
+ aPtr->pmDomainLabel);
+ goto error;
+#endif
+ }
+ if (sts < 0) {
+ fprintf(stderr, "pmcd: Error connecting to \"%s\" agent : %s\n",
+ aPtr->pmDomainLabel, netstrerror());
+ goto error;
+ }
+ aPtr->outFd = aPtr->inFd = fd; /* Sockets are bi-directional */
+ pmcd_openfds_sethi(fd);
+
+ if ((sts = AgentNegotiate(aPtr)) < 0)
+ goto error;
+
+ return 0;
+
+error:
+ if (fd != -1) {
+ if (aPtr->ipc.socket.addrDomain == AF_INET || aPtr->ipc.socket.addrDomain == AF_INET6)
+ __pmCloseSocket(fd);
+ else
+ close(fd);
+ }
+ return -1;
+}
+
+#ifndef IS_MINGW
+static pid_t
+CreateAgentPOSIX(AgentInfo *aPtr)
+{
+ int i;
+ int inPipe[2]; /* Pipe for input to child */
+ int outPipe[2]; /* For output to child */
+ pid_t childPid = (pid_t)-1;
+ char **argv = NULL;
+
+ if (aPtr->ipcType == AGENT_PIPE) {
+ argv = aPtr->ipc.pipe.argv;
+ if (pipe1(inPipe) < 0) {
+ fprintf(stderr,
+ "pmcd: input pipe create failed for \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ return (pid_t)-1;
+ }
+
+ if (pipe1(outPipe) < 0) {
+ fprintf(stderr,
+ "pmcd: output pipe create failed for \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ close(inPipe[0]);
+ close(inPipe[1]);
+ return (pid_t)-1;
+ }
+ pmcd_openfds_sethi(outPipe[1]);
+ }
+ else if (aPtr->ipcType == AGENT_SOCKET)
+ argv = aPtr->ipc.socket.argv;
+
+ if (argv != NULL) { /* Start a new agent if required */
+ childPid = fork();
+ if (childPid == (pid_t)-1) {
+ fprintf(stderr, "pmcd: creating child for \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ if (aPtr->ipcType == AGENT_PIPE) {
+ close(inPipe[0]);
+ close(inPipe[1]);
+ close(outPipe[0]);
+ close(outPipe[1]);
+ }
+ return (pid_t)-1;
+ }
+
+ if (childPid) {
+ /* This is the parent (PMCD) */
+ if (aPtr->ipcType == AGENT_PIPE) {
+ close(inPipe[0]);
+ close(outPipe[1]);
+ aPtr->inFd = inPipe[1];
+ aPtr->outFd = outPipe[0];
+ }
+ }
+ else {
+ /*
+ * This is the child (new agent)
+ * make sure stderr is fd 2
+ */
+ dup2(fileno(stderr), STDERR_FILENO);
+ if (aPtr->ipcType == AGENT_PIPE) {
+ /* make pipe stdin for PMDA */
+ dup2(inPipe[0], STDIN_FILENO);
+ /* make pipe stdout for PMDA */
+ dup2(outPipe[1], STDOUT_FILENO);
+ }
+ else {
+ /*
+ * not a pipe, close stdin and attach stdout to stderr
+ */
+ close(STDIN_FILENO);
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ }
+
+ for (i = 0; i <= pmcd_hi_openfds; i++) {
+ /* Close all except std{in,out,err} */
+ if (i == STDIN_FILENO ||
+ i == STDOUT_FILENO ||
+ i == STDERR_FILENO)
+ continue;
+ close(i);
+ }
+
+ execvp(argv[0], argv);
+ /* botch if reach here */
+ fprintf(stderr, "pmcd: error starting %s: %s\n",
+ argv[0], osstrerror());
+ /* avoid atexit() processing, so _exit not exit */
+ _exit(1);
+ }
+ }
+ return childPid;
+}
+
+#else
+
+static pid_t
+CreateAgentWin32(AgentInfo *aPtr)
+{
+ SECURITY_ATTRIBUTES saAttr;
+ PROCESS_INFORMATION piProcInfo;
+ STARTUPINFO siStartInfo;
+ HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr;
+ BOOL bSuccess = FALSE;
+ LPTSTR command = NULL;
+
+ if (aPtr->ipcType == AGENT_PIPE)
+ command = (LPTSTR)aPtr->ipc.pipe.commandLine;
+ else if (aPtr->ipcType == AGENT_SOCKET)
+ command = (LPTSTR)aPtr->ipc.socket.commandLine;
+
+ // Set the bInheritHandle flag so pipe handles are inherited
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ // Create a pipe for the child process's STDOUT.
+ if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
+ fprintf(stderr, "pmcd: stdout CreatePipe failed, \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ return (pid_t)-1;
+ }
+ // Ensure the read handle to the pipe for STDOUT is not inherited.
+ if (!SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0)) {
+ fprintf(stderr, "pmcd: stdout SetHandleInformation, \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ return (pid_t)-1;
+ }
+
+ // Create a pipe for the child process's STDIN.
+ if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
+ fprintf(stderr, "pmcd: stdin CreatePipe failed, \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ return (pid_t)-1;
+ }
+ // Ensure the write handle to the pipe for STDIN is not inherited.
+ if (!SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0)) {
+ fprintf(stderr, "pmcd: stdin SetHandleInformation, \"%s\" agent: %s\n",
+ aPtr->pmDomainLabel, osstrerror());
+ return (pid_t)-1;
+ }
+
+ // Create the child process.
+
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+ siStartInfo.cb = sizeof(STARTUPINFO);
+ siStartInfo.hStdOutput = hChildStdoutWr;
+ siStartInfo.hStdError = hChildStdoutWr;
+ siStartInfo.hStdInput = hChildStdinRd;
+ siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ bSuccess = CreateProcess(NULL, command,
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ TRUE, // handles are inherited
+ 0, // creation flags
+ NULL, // use parent's environment
+ NULL, // use parent's current directory
+ &siStartInfo, // STARTUPINFO pointer
+ &piProcInfo); // receives PROCESS_INFORMATION
+ if (!bSuccess) {
+ fprintf(stderr, "pmcd: CreateProcess for \"%s\" agent: %s: %s\n",
+ aPtr->pmDomainLabel, command, osstrerror());
+ return (pid_t)-1;
+ }
+
+ aPtr->inFd = _open_osfhandle((intptr_t)hChildStdinRd, _O_WRONLY);
+ aPtr->outFd = _open_osfhandle((intptr_t)hChildStdoutWr, _O_RDONLY);
+ pmcd_openfds_sethi(aPtr->outFd);
+
+ CloseHandle(piProcInfo.hProcess);
+ CloseHandle(piProcInfo.hThread);
+ CloseHandle(hChildStdoutRd);
+ CloseHandle(hChildStdinWr);
+ return piProcInfo.dwProcessId;
+}
+#endif
+
+/* Create the specified agent running at the end of a pair of pipes. */
+static int
+CreateAgent(AgentInfo *aPtr)
+{
+ pid_t childPid;
+ int sts;
+
+ fflush(stderr);
+ fflush(stdout);
+
+#ifdef IS_MINGW
+ childPid = CreateAgentWin32(aPtr);
+#else
+ childPid = CreateAgentPOSIX(aPtr);
+#endif
+ if (childPid < 0)
+ return (int)childPid;
+
+ aPtr->status.isChild = 1;
+ if (aPtr->ipcType == AGENT_PIPE) {
+ aPtr->ipc.pipe.agentPid = childPid;
+ /* ready for version negotiation */
+ if ((sts = AgentNegotiate(aPtr)) < 0) {
+ close(aPtr->inFd);
+ close(aPtr->outFd);
+ return sts;
+ }
+ }
+ else if (aPtr->ipcType == AGENT_SOCKET)
+ aPtr->ipc.socket.agentPid = childPid;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "pmcd: started PMDA %s (%d), pid=%" FMT_PID "\n",
+ aPtr->pmDomainLabel, aPtr->pmDomainId, childPid);
+#endif
+ return 0;
+}
+
+/* Print a table of all of the agent configuration info on a given stream. */
+void
+PrintAgentInfo(FILE *stream)
+{
+ int i, version;
+ AgentInfo *aPtr;
+
+ fputs("\nactive agent dom pid in out ver protocol parameters\n", stream);
+ fputs( "============ === ===== === === === ======== ==========\n", stream);
+ for (i = 0; i < nAgents; i++) {
+ aPtr = &agent[i];
+ if (aPtr->status.connected == 0)
+ continue;
+ fprintf(stream, "%-12s", aPtr->pmDomainLabel);
+
+ switch (aPtr->ipcType) {
+ case AGENT_DSO:
+ fprintf(stream, " %3d %3d dso i:%d",
+ aPtr->pmDomainId,
+ aPtr->ipc.dso.dispatch.comm.pmapi_version,
+ aPtr->ipc.dso.dispatch.comm.pmda_interface);
+ fprintf(stream, " lib=%s entry=%s [" PRINTF_P_PFX "%p]\n",
+ aPtr->ipc.dso.pathName, aPtr->ipc.dso.entryPoint,
+ aPtr->ipc.dso.initFn);
+ break;
+
+ case AGENT_SOCKET:
+ version = __pmVersionIPC(aPtr->inFd);
+ fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ",
+ aPtr->pmDomainId, aPtr->ipc.socket.agentPid, aPtr->inFd, aPtr->outFd, version);
+ fputs("bin ", stream);
+ fputs("sock ", stream);
+ if (aPtr->ipc.socket.addrDomain == AF_UNIX)
+ fprintf(stream, "dom=unix port=%s", aPtr->ipc.socket.name);
+ else if (aPtr->ipc.socket.addrDomain == AF_INET) {
+ if (aPtr->ipc.socket.name)
+ fprintf(stream, "dom=inet port=%s (%d)",
+ aPtr->ipc.socket.name, aPtr->ipc.socket.port);
+ else
+ fprintf(stream, "dom=inet port=%d", aPtr->ipc.socket.port);
+ }
+ else if (aPtr->ipc.socket.addrDomain == AF_INET6) {
+ if (aPtr->ipc.socket.name)
+ fprintf(stream, "dom=ipv6 port=%s (%d)",
+ aPtr->ipc.socket.name, aPtr->ipc.socket.port);
+ else
+ fprintf(stream, "dom=ipv6 port=%d", aPtr->ipc.socket.port);
+ }
+ else {
+ fputs("dom=???", stream);
+ }
+ if (aPtr->ipc.socket.commandLine) {
+ fputs(" cmd=", stream);
+ fputs(aPtr->ipc.socket.commandLine, stream);
+ }
+ putc('\n', stream);
+ break;
+
+ case AGENT_PIPE:
+ version = __pmVersionIPC(aPtr->inFd);
+ fprintf(stream, " %3d %5" FMT_PID " %3d %3d %3d ",
+ aPtr->pmDomainId, aPtr->ipc.pipe.agentPid, aPtr->inFd, aPtr->outFd, version);
+ fputs("bin ", stream);
+ if (aPtr->ipc.pipe.commandLine) {
+ fputs("pipe cmd=", stream);
+ fputs(aPtr->ipc.pipe.commandLine, stream);
+ putc('\n', stream);
+ }
+ break;
+
+ default:
+ fputs("????\n", stream);
+ break;
+ }
+ }
+ fflush(stream); /* Ensure that it appears now */
+}
+
+/* Load the DSO for a specified agent and initialise it. */
+static int
+GetAgentDso(AgentInfo *aPtr)
+{
+ DsoInfo *dso = &aPtr->ipc.dso;
+ const char *name;
+ unsigned int challenge;
+
+ aPtr->status.connected = 0;
+ aPtr->reason = REASON_NOSTART;
+
+ name = __pmFindPMDA(dso->pathName);
+ if (name == NULL) {
+ fprintf(stderr, "Cannot find %s DSO at \"%s\"\n",
+ aPtr->pmDomainLabel, dso->pathName);
+ fputc('\n', stderr);
+ return -1;
+ }
+
+ if (name != dso->pathName) {
+ /* some searching was done */
+ free(dso->pathName);
+ dso->pathName = strdup(name);
+ if (dso->pathName == NULL) {
+ __pmNoMem("pmcd config: pathName", strlen(name), PM_FATAL_ERR);
+ }
+ dso->xlatePath = 1;
+ }
+
+#if defined(HAVE_DLOPEN)
+ /*
+ * RTLD_NOW would be better in terms of detecting unresolved symbols
+ * now, rather than taking a SEGV later ... but various combinations
+ * of dynamic and static libraries used to create the DSO PMDA,
+ * combined with hiding symbols in the DSO PMDA may result in benign
+ * unresolved symbols remaining and the dlopen() would fail under
+ * these circumstances.
+ */
+ dso->dlHandle = dlopen(dso->pathName, RTLD_LAZY);
+#else
+ fprintf(stderr, "Error attaching %s DSO at \"%s\"\n",
+ aPtr->pmDomainLabel, dso->pathName);
+ fprintf(stderr, "No dynamic shared library support on this platform\n");
+ return -1;
+#endif
+
+ if (dso->dlHandle == NULL) {
+ fprintf(stderr, "Error attaching %s DSO at \"%s\"\n",
+ aPtr->pmDomainLabel, dso->pathName);
+#if defined(HAVE_DLOPEN)
+ fprintf(stderr, "%s\n\n", dlerror());
+#else
+ fprintf(stderr, "%s\n\n", osstrerror());
+#endif
+ return -1;
+ }
+
+ /* Get a pointer to the DSO's init function and call it to get the agent's
+ dispatch table for the DSO. */
+
+#if defined(HAVE_DLOPEN)
+ dso->initFn = (void (*)(pmdaInterface*))dlsym(dso->dlHandle, dso->entryPoint);
+ if (dso->initFn == NULL) {
+ fprintf(stderr, "Couldn't find init function `%s' in %s DSO\n",
+ dso->entryPoint, aPtr->pmDomainLabel);
+ dlclose(dso->dlHandle);
+ return -1;
+ }
+#endif
+
+ /*
+ * Pass in the expected domain id.
+ * The PMDA initialization routine can (a) ignore it, (b) check it
+ * is the expected value, or (c) self-adapt.
+ */
+ dso->dispatch.domain = aPtr->pmDomainId;
+
+ /*
+ * the PMDA interface / PMAPI version discovery as a "challenge" ...
+ * for pmda_interface it is all the bits being set,
+ * for pmapi_version it is the complement of the one you are using now
+ */
+ challenge = 0xff;
+ dso->dispatch.comm.pmda_interface = challenge;
+ dso->dispatch.comm.pmapi_version = ~PMAPI_VERSION;
+
+ dso->dispatch.comm.flags = 0;
+ dso->dispatch.status = 0;
+
+ (*dso->initFn)(&dso->dispatch);
+
+ if (dso->dispatch.status != 0) {
+ /* initialization failed for some reason */
+ fprintf(stderr,
+ "Initialization routine %s in %s DSO failed: %s\n",
+ dso->entryPoint, aPtr->pmDomainLabel,
+ pmErrStr(dso->dispatch.status));
+#if defined(HAVE_DLOPEN)
+ dlclose(dso->dlHandle);
+#endif
+ return -1;
+ }
+
+ if (dso->dispatch.comm.pmda_interface < PMDA_INTERFACE_2 ||
+ dso->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) {
+ __pmNotifyErr(LOG_ERR,
+ "Unknown PMDA interface version (%d) used by DSO %s\n",
+ dso->dispatch.comm.pmda_interface, aPtr->pmDomainLabel);
+#if defined(HAVE_DLOPEN)
+ dlclose(dso->dlHandle);
+#endif
+ return -1;
+ }
+
+ if (dso->dispatch.comm.pmapi_version == PMAPI_VERSION_2)
+ aPtr->pduVersion = PDU_VERSION2;
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "Unsupported PMAPI version (%d) used by DSO %s\n",
+ dso->dispatch.comm.pmapi_version, aPtr->pmDomainLabel);
+#if defined(HAVE_DLOPEN)
+ dlclose(dso->dlHandle);
+#endif
+ return -1;
+ }
+
+ aPtr->reason = 0;
+ aPtr->status.connected = 1;
+ aPtr->status.flags = dso->dispatch.comm.flags;
+ if (dso->dispatch.comm.flags & PDU_FLAG_AUTH)
+ ClientsAuthentication(aPtr);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "pmcd: started DSO PMDA %s (%d) using pmPMDA version=%d, "
+ "PDU version=%d\n", aPtr->pmDomainLabel, aPtr->pmDomainId,
+ dso->dispatch.comm.pmda_interface, aPtr->pduVersion);
+#endif
+
+ return 0;
+}
+
+
+/* For creating and establishing contact with agents of the PMCD. */
+static void
+ContactAgents(void)
+{
+ int i;
+ int sts = 0;
+ int createdSocketAgents = 0;
+ AgentInfo *aPtr;
+
+ for (i = 0; i < nAgents; i++) {
+ aPtr = &agent[i];
+ if (aPtr->status.connected)
+ continue;
+ switch (aPtr->ipcType) {
+ case AGENT_DSO:
+ sts = GetAgentDso(aPtr);
+ break;
+
+ case AGENT_SOCKET:
+ if (aPtr->ipc.socket.argv) { /* Create agent if required */
+ sts = CreateAgent(aPtr);
+ if (sts >= 0)
+ createdSocketAgents = 1;
+
+ /* Don't attempt to connect yet, if the agent has just been
+ created, it will need time to initialise socket. */
+ }
+ else
+ sts = ConnectSocketAgent(aPtr);
+ break; /* Connect to existing agent */
+
+ case AGENT_PIPE:
+ sts = CreateAgent(aPtr);
+ break;
+ }
+ aPtr->status.connected = sts == 0;
+ if (aPtr->status.connected) {
+ if (aPtr->ipcType == AGENT_DSO)
+ pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, -1, -1);
+ else
+ pmcd_trace(TR_ADD_AGENT, aPtr->pmDomainId, aPtr->inFd, aPtr->outFd);
+ MarkStateChanges(PMCD_ADD_AGENT);
+ aPtr->status.notReady = aPtr->status.startNotReady;
+ }
+ else
+ aPtr->reason = REASON_NOSTART;
+ }
+
+ /* Allow newly created socket agents time to initialise before attempting
+ to connect to them. */
+
+ if (createdSocketAgents) {
+ sleep(2); /* Allow 2 second for startup */
+ for (i = 0; i < nAgents; i++) {
+ aPtr = &agent[i];
+ if (aPtr->ipcType == AGENT_SOCKET &&
+ aPtr->ipc.socket.agentPid != (pid_t)-1) {
+ sts = ConnectSocketAgent(aPtr);
+ aPtr->status.connected = sts == 0;
+ if (!aPtr->status.connected)
+ aPtr->reason = REASON_NOSTART;
+ }
+ }
+ }
+}
+
+int
+ParseInitAgents(char *fileName)
+{
+ int sts;
+ int i;
+ FILE *configFile;
+ struct stat statBuf;
+ static int firstTime = 1;
+
+ memset(&configFileTime, 0, sizeof(configFileTime));
+ configFile = fopen(fileName, "r");
+ if (configFile == NULL)
+ fprintf(stderr, "ParseInitAgents: %s: %s\n", fileName, osstrerror());
+ else if (stat(fileName, &statBuf) == -1)
+ fprintf(stderr, "ParseInitAgents: stat(%s): %s\n",
+ fileName, osstrerror());
+ else {
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ configFileTime = statBuf.st_mtime;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseInitAgents: configFileTime=%ld sec\n",
+ (long)configFileTime);
+#endif
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ configFileTime = statBuf.st_mtimespec; /* struct assignment */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n",
+ (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec);
+#endif
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ configFileTime = statBuf.st_mtim; /* struct assignment */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseInitAgents: configFileTime=%ld.%09ld sec\n",
+ (long)configFileTime.tv_sec, (long)configFileTime.tv_nsec);
+#endif
+#else
+!bozo!
+#endif
+ }
+ if (configFile == NULL)
+ return -1;
+
+ if (firstTime)
+ if (__pmAccAddOp(PMCD_OP_FETCH) < 0 || __pmAccAddOp(PMCD_OP_STORE) < 0) {
+ fprintf(stderr,
+ "ParseInitAgents: __pmAccAddOp: can't create access ops\n");
+ exit(1);
+ }
+
+ sts = ReadConfigFile(configFile);
+ fclose(configFile);
+
+ /* If pmcd is restarting, don't create/contact the agents until the results
+ * of the parse can be compared with the previous setup to determine
+ * whether anything has changed.
+ */
+ if (!firstTime)
+ return sts;
+
+ firstTime = 0;
+ if (sts == 0) {
+ ContactAgents();
+ for (i = 0; i < MAXDOMID + 2; i++)
+ mapdom[i] = nAgents;
+ for (i = 0; i < nAgents; i++)
+ if (agent[i].status.connected)
+ mapdom[agent[i].pmDomainId] = i;
+ }
+ return sts;
+}
+
+static int
+AgentsDiffer(AgentInfo *a1, AgentInfo *a2)
+{
+ int i;
+
+ if (a1->pmDomainId != a2->pmDomainId)
+ return 1;
+ if (a1->ipcType != a2->ipcType)
+ return 1;
+ if (a1->ipcType == AGENT_DSO) {
+ DsoInfo *dso1 = &a1->ipc.dso;
+ DsoInfo *dso2 = &a2->ipc.dso;
+ if (strcmp(dso1->pathName, dso2->pathName) != 0)
+ return 1;
+ if (dso1->entryPoint == NULL || dso2->entryPoint == NULL)
+ return 1; /* should never happen */
+ if (strcmp(dso1->entryPoint, dso2->entryPoint))
+ return 1;
+ }
+ else if (a1->ipcType == AGENT_SOCKET) {
+ SocketInfo *sock1 = &a1->ipc.socket;
+ SocketInfo *sock2 = &a2->ipc.socket;
+
+ if (sock1 == NULL || sock2 == NULL)
+ return 1; /* should never happen */
+ if (sock1->addrDomain != sock2->addrDomain)
+ return 1;
+ /* The names don't really matter, it's the port that counts */
+ if (sock1->port != sock2->port)
+ return 1;
+ if ((sock1->commandLine == NULL && sock2->commandLine != NULL) ||
+ (sock1->commandLine != NULL && sock2->commandLine == NULL))
+ return 1;
+ if (sock1->argv != NULL && sock2->argv != NULL) {
+ /* Don't just compare commandLines, changes may be cosmetic */
+ for (i = 0; sock1->argv[i] != NULL && sock2->argv[i] != NULL; i++)
+ if (strcmp(sock1->argv[i], sock2->argv[i]))
+ return 1;
+ if (sock1->argv[i] != NULL || sock2->argv[i] != NULL)
+ return 1;
+ }
+ else if ((sock1->argv == NULL && sock2->argv != NULL) ||
+ (sock1->argv != NULL && sock2->argv == NULL))
+ return 1;
+ }
+
+ else {
+ PipeInfo *pipe1 = &a1->ipc.pipe;
+ PipeInfo *pipe2 = &a2->ipc.pipe;
+
+ if (pipe1 == NULL || pipe2 == NULL)
+ return 1; /* should never happen */
+ if ((pipe1->commandLine == NULL && pipe2->commandLine != NULL) ||
+ (pipe1->commandLine != NULL && pipe2->commandLine == NULL))
+ return 1;
+ if (pipe1->argv != NULL && pipe2->argv != NULL) {
+ /* Don't just compare commandLines, changes may be cosmetic */
+ for (i = 0; pipe1->argv[i] != NULL && pipe2->argv[i] != NULL; i++)
+ if (strcmp(pipe1->argv[i], pipe2->argv[i]))
+ return 1;
+ if (pipe1->argv[i] != NULL || pipe2->argv[i] != NULL)
+ return 1;
+ }
+ else if ((pipe1->argv == NULL && pipe2->argv != NULL) ||
+ (pipe1->argv != NULL && pipe2->argv == NULL))
+ return 1;
+ }
+ return 0;
+}
+
+/* Make the "dest" agent the equivalent of an existing "src" agent.
+ * This assumes that the agents are identical according to AgentsDiffer(), and
+ * that they have distinct copies of the fields compared therein.
+ * Note that only the the low level PDU I/O information is copied here.
+ */
+static void
+DupAgent(AgentInfo *dest, AgentInfo *src)
+{
+ dest->inFd = src->inFd;
+ dest->outFd = src->outFd;
+ dest->profClient = src->profClient;
+ dest->profIndex = src->profIndex;
+ /* IMPORTANT: copy the status, connections stay connected */
+ memcpy(&dest->status, &src->status, sizeof(dest->status));
+ if (src->ipcType == AGENT_DSO) {
+ dest->ipc.dso.dlHandle = src->ipc.dso.dlHandle;
+ memcpy(&dest->ipc.dso.dispatch, &src->ipc.dso.dispatch,
+ sizeof(dest->ipc.dso.dispatch));
+ /* initFn should never be needed */
+ dest->ipc.dso.initFn = (DsoInitPtr)0;
+ }
+ else if (src->ipcType == AGENT_SOCKET)
+ dest->ipc.socket.agentPid = src->ipc.socket.agentPid;
+ else
+ dest->ipc.pipe.agentPid = src->ipc.pipe.agentPid;
+}
+
+void
+ParseRestartAgents(char *fileName)
+{
+ int sts;
+ int i, j;
+ struct stat statBuf;
+ AgentInfo *oldAgent;
+ int oldNAgents;
+ AgentInfo *ap;
+ __pmFdSet fds;
+
+ /* Clean up any deceased agents. We haven't seen an agent's death unless
+ * a PDU transfer involving the agent has occurred. This cleans up others
+ * as well.
+ */
+ __pmFD_ZERO(&fds);
+ j = -1;
+ for (i = 0; i < nAgents; i++) {
+ ap = &agent[i];
+ if (ap->status.connected &&
+ (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE)) {
+
+ __pmFD_SET(ap->outFd, &fds);
+ if (ap->outFd > j)
+ j = ap->outFd;
+ }
+ }
+ if (++j) {
+ /* any agent with output ready has either closed the file descriptor or
+ * sent an unsolicited PDU. Clean up the agent in either case.
+ */
+ struct timeval timeout = {0, 0};
+
+ sts = __pmSelectRead(j, &fds, &timeout);
+ if (sts > 0) {
+ for (i = 0; i < nAgents; i++) {
+ ap = &agent[i];
+ if (ap->status.connected &&
+ (ap->ipcType == AGENT_SOCKET || ap->ipcType == AGENT_PIPE) &&
+ __pmFD_ISSET(ap->outFd, &fds)) {
+
+ /* try to discover more ... */
+ __pmPDU *pb;
+ sts = __pmGetPDU(ap->outFd, ANY_SIZE, TIMEOUT_NEVER, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == 0)
+ pmcd_trace(TR_EOF, ap->outFd, -1, -1);
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, -1, sts);
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+
+ CleanupAgent(ap, AT_COMM, ap->outFd);
+ }
+ }
+ }
+ else if (sts < 0)
+ fprintf(stderr, "pmcd: deceased agents select: %s\n",
+ netstrerror());
+ }
+
+ /* gather any deceased children */
+ HarvestAgents(0);
+
+ if (stat(fileName, &statBuf) == -1) {
+ fprintf(stderr, "ParseRestartAgents: stat(%s): %s\n",
+ fileName, osstrerror());
+ fprintf(stderr, "Configuration left unchanged\n");
+ return;
+ }
+
+ /* If the config file's modification time hasn't changed, just try to
+ * restart any deceased agents
+ */
+#if defined(HAVE_ST_MTIME_WITH_SPEC)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n",
+ (long)statBuf.st_mtimespec.tv_sec, (long)statBuf.st_mtimespec.tv_nsec);
+#endif
+ if (statBuf.st_mtimespec.tv_sec == configFileTime.tv_sec &&
+ statBuf.st_mtimespec.tv_nsec == configFileTime.tv_nsec) {
+#elif defined(HAVE_STAT_TIMESPEC_T) || defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld.%09ld sec\n",
+ (long)statBuf.st_mtim.tv_sec, (long)statBuf.st_mtim.tv_nsec);
+#endif
+ if (statBuf.st_mtim.tv_sec == configFileTime.tv_sec &&
+ statBuf.st_mtim.tv_nsec == configFileTime.tv_nsec) {
+#elif defined(HAVE_STAT_TIME_T)
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "ParseRestartAgents: new configFileTime=%ld sec\n",
+ (long)configFileTime);
+#endif
+ if (statBuf.st_mtime == configFileTime) {
+#else
+!bozo!
+#endif
+ fprintf(stderr, "Configuration file '%s' unchanged\n", fileName);
+ fprintf(stderr, "Restarting any deceased agents:\n");
+ j = 0;
+ for (i = 0; i < nAgents; i++)
+ if (!agent[i].status.connected) {
+ fprintf(stderr, " \"%s\" agent\n",
+ agent[i].pmDomainLabel);
+ j++;
+ }
+ if (j == 0)
+ fprintf(stderr, " (no agents required restarting)\n");
+ else {
+ putc('\n', stderr);
+ ContactAgents();
+ for (i = 0; i < nAgents; i++) {
+ mapdom[agent[i].pmDomainId] =
+ agent[i].status.connected ? i : nAgents;
+ }
+
+ MarkStateChanges(PMCD_RESTART_AGENT);
+ }
+ PrintAgentInfo(stderr);
+ __pmAccDumpLists(stderr);
+ return;
+ }
+
+ /* Save the current agent[] and host access tables, Reset the internal
+ * state of the config file parser and re-parse the config file.
+ */
+ oldAgent = agent;
+ oldNAgents = nAgents;
+ agent = NULL;
+ nAgents = 0;
+ szAgents = 0;
+ scanInit = 0;
+ scanError = 0;
+ if (__pmAccSaveLists() < 0) {
+ fprintf(stderr, "Error saving access controls\n");
+ sts = -2;
+ }
+ else
+ sts = ParseInitAgents(fileName);
+
+ /* If the config file had errors or there were no valid agents in the new
+ * config file, ignore it and stick with the old setup.
+ */
+ if (sts < 0 || nAgents == 0) {
+ if (sts == -1)
+ fprintf(stderr,
+ "Configuration file '%s' has errors\n", fileName);
+ else
+ fprintf(stderr,
+ "Configuration file '%s' has no valid agents\n",
+ fileName);
+ fprintf(stderr, "Configuration left unchanged\n");
+ agent = oldAgent;
+ nAgents = oldNAgents;
+ if (sts != -2 && __pmAccRestoreLists() < 0) {
+ fprintf(stderr, "Error restoring access controls!\n");
+ exit(1);
+ }
+ PrintAgentInfo(stderr);
+ __pmAccDumpLists(stderr);
+ return;
+ }
+
+ /* Reconcile the old and new agent tables, creating or destroying agents
+ * as reqired.
+ */
+ for (j = 0; j < oldNAgents; j++)
+ oldAgent[j].status.restartKeep = 0;
+
+ for (i = 0; i < nAgents; i++)
+ for (j = 0; j < oldNAgents; j++)
+ if (!AgentsDiffer(&agent[i], &oldAgent[j]) &&
+ oldAgent[j].status.connected) {
+ DupAgent(&agent[i], &oldAgent[j]);
+ oldAgent[j].status.restartKeep = 1;
+ }
+
+ for (j = 0; j < oldNAgents; j++) {
+ if (oldAgent[j].status.connected && !oldAgent[j].status.restartKeep)
+ CleanupAgent(&oldAgent[j], AT_CONFIG, 0);
+ FreeAgent(&oldAgent[j]);
+ }
+ free(oldAgent);
+ __pmAccFreeSavedLists();
+
+ /* Start the new agents */
+ ContactAgents();
+ for (i = 0; i < MAXDOMID + 2; i++)
+ mapdom[i] = nAgents;
+ for (i = 0; i < nAgents; i++)
+ if (agent[i].status.connected)
+ mapdom[agent[i].pmDomainId] = i;
+
+ /* Now recalculate the access controls for each client and update the
+ * connection count in the ACL entries matching the client (and account).
+ * If the client is no longer permitted the connection because of a change
+ * in permissions or connection limit, the client's connection is closed.
+ */
+ for (i = 0; i < nClients; i++) {
+ ClientInfo *cp = &client[i];
+
+ if (!cp->status.connected)
+ continue;
+ if ((sts = CheckClientAccess(cp)) >= 0)
+ sts = CheckAccountAccess(cp);
+ if (sts < 0) {
+ /* ignore errors, the client is being terminated in any case */
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts);
+ __pmSendError(cp->fd, FROM_ANON, sts);
+ CleanupClient(cp, sts);
+ }
+ }
+
+ PrintAgentInfo(stderr);
+ __pmAccDumpLists(stderr);
+
+ /* Gather any deceased children, some may be PMDAs that were
+ * terminated by CleanupAgent or killed and had not exited
+ * when the previous harvest() was done
+ */
+ HarvestAgents(0);
+}
diff --git a/src/pmcd/src/dofetch.c b/src/pmcd/src/dofetch.c
new file mode 100644
index 0000000..0e73fe2
--- /dev/null
+++ b/src/pmcd/src/dofetch.c
@@ -0,0 +1,582 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmcd.h"
+
+/* Freq. histogram: pmids for each agent in current fetch request */
+
+static int *aFreq;
+
+/* Routine to break a list of pmIDs up into sublists of metrics within the
+ * same metric domain. The resulting lists are returned via a pointer to an
+ * array of per-domain lists as defined by the struct below. Any metrics for
+ * which no agent exists are collected into a list at the end of the list of
+ * valid lists. This list has domain = -1 and is used to indicate the end of
+ * the list of pmID lists.
+ */
+
+typedef struct {
+ int domain;
+ int listSize;
+ pmID *list;
+} DomPmidList;
+
+static DomPmidList *
+SplitPmidList(int nPmids, pmID *pmidList)
+{
+ int i, j;
+ static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */
+ static int nDoms = 0; /* No. of entries in two tables above */
+ int nGood;
+ static int currentSize = 0;
+ int resultSize;
+ static DomPmidList *result;
+ pmID *resultPmids;
+
+ /* Allocate the frequency histogram and array for mapping from agent to
+ * result list index. Because a SIGHUP reconfiguration may have caused a
+ * change in the number of agents, reallocation using a new size may be
+ * necessary.
+ * There are nAgents + 1 entries in the aFreq and resIndex arrays. The
+ * last entry in each is used for the pmIDs for which no agent could be
+ * found.
+ */
+ if (nAgents > nDoms) {
+ nDoms = nAgents;
+ if (resIndex != NULL)
+ free(resIndex);
+ if (aFreq != NULL)
+ free(aFreq);
+ resIndex = (int *)malloc((nAgents + 1) * sizeof(int));
+ aFreq = (int *)malloc((nAgents + 1) * sizeof(int));
+ if (resIndex == NULL || aFreq == NULL) {
+ __pmNoMem("SplitPmidList.resIndex", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR);
+ }
+ }
+
+ memset(aFreq, 0, (nAgents + 1) * sizeof(aFreq[0]));
+
+ if (nPmids == 1) {
+ /* FastTrack this case */
+ for (i = 0; i < nAgents; i++)
+ resIndex[i] = 1;
+ i = mapdom[((__pmID_int *)&pmidList[0])->domain];
+ aFreq[i] = 1;
+ resIndex[i] = 0;
+ nGood = i == nAgents ? 0 : 1;
+ goto doit;
+ }
+
+ /*
+ * Build a frequency histogram of metric domains (use aFreq[nAgents],
+ * via mapdom[] for pmids for which there is no agent).
+ */
+ for (i = 0; i < nPmids; i++) {
+ j = mapdom[((__pmID_int *)&pmidList[i])->domain];
+ aFreq[j]++;
+ }
+
+ /* Build the mapping between agent index and the position of the agent's
+ * subset of the pmidList in the returned result's DomPmidList.
+ */
+ nGood = 0;
+ for (i = 0; i < nAgents; i++)
+ if (aFreq[i])
+ nGood++;
+
+ /* nGood is the number of "valid" result pmid lists. It is also the INDEX
+ * of the BAD list in the resulting list of DomPmidLists).
+ */
+ j = 0;
+ for (i = 0; i < nAgents; i++)
+ resIndex[i] = (aFreq[i]) ? j++ : nGood;
+ resIndex[nAgents] = nGood; /* For the "bad" list */
+
+ /* Now malloc up a single heap block for the resulting list of pmID lists.
+ * First is a list of (nDoms + 1) DomPmidLists (the last is a sentinel with
+ * a domain of -1), then come the pmID lists pointed to by the
+ * DomPmidLists.
+ */
+doit:
+ resultSize = (nGood + 1) * (int)sizeof(DomPmidList);
+ resultSize += nPmids * sizeof(pmID);
+ if (resultSize > currentSize) {
+ if (currentSize > 0)
+ free(result);
+ result = (DomPmidList *)malloc(resultSize);
+ if (result == NULL) {
+ __pmNoMem("SplitPmidList.result", resultSize, PM_FATAL_ERR);
+ }
+ currentSize = resultSize;
+ }
+
+ resultPmids = (pmID *)&result[nGood + 1];
+ if (nPmids == 1) {
+ /* more FastTrack */
+ if (nGood) {
+ /* domain known, otherwise things fixed up below */
+ i = mapdom[((__pmID_int *)&pmidList[0])->domain];
+ j = resIndex[i];
+ result[j].domain = agent[i].pmDomainId;
+ result[j].listSize = 0;
+ result[j].list = resultPmids;
+ resultPmids++;
+ }
+ }
+ else {
+ for (i = 0; i < nAgents; i++) {
+ if (aFreq[i]) {
+ j = resIndex[i];
+ result[j].domain = agent[i].pmDomainId;
+ result[j].listSize = 0;
+ result[j].list = resultPmids;
+ resultPmids += aFreq[i];
+ }
+ }
+ }
+ result[nGood].domain = -1; /* Set up the "bad" list */
+ result[nGood].listSize = 0;
+ result[nGood].list = resultPmids;
+
+ for (i = 0; i < nPmids; i++) {
+ j = resIndex[mapdom[((__pmID_int *)&pmidList[i])->domain]];
+ result[j].list[result[j].listSize++] = pmidList[i];
+ }
+ return result;
+}
+
+/* Build a pmResult indicating that no values are available for the pmID list
+ * supplied.
+ */
+
+static pmResult *
+MakeBadResult(int npmids, pmID *list, int sts)
+{
+ int need;
+ int i;
+ pmValueSet *vSet;
+ pmResult *result;
+
+ need = (int)sizeof(pmResult) +
+ (npmids - 1) * (int)sizeof(pmValueSet *);
+ /* npmids - 1 because there is already 1 pmValueSet* in a pmResult */
+ result = (pmResult *)malloc(need);
+ if (result == NULL) {
+ __pmNoMem("MakeBadResult.result", need, PM_FATAL_ERR);
+ }
+ result->numpmid = npmids;
+ for (i = 0; i < npmids; i++) {
+ vSet = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (vSet == NULL) {
+ __pmNoMem("MakeBadResult.vSet", sizeof(pmValueSet), PM_FATAL_ERR);
+ }
+ result->vset[i] = vSet;
+ vSet->pmid = list[i];
+ vSet->numval = sts;
+ }
+ return result;
+}
+
+static pmResult *
+SendFetch(DomPmidList *dpList, AgentInfo *aPtr, ClientInfo *cPtr, int ctxnum)
+{
+ pmResult *result = NULL;
+ int sts = 0;
+ static __pmTimeval when = {0, 0}; /* Agents never see archive requests */
+ int bad = 0;
+ int i;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "SendFetch %d metrics to PMDA domain %d ",
+ dpList->listSize, dpList->domain);
+ switch (aPtr->ipcType) {
+ case AGENT_DSO:
+ fprintf(stderr, "(dso)\n");
+ break;
+
+ case AGENT_SOCKET:
+ fprintf(stderr, "(socket)\n");
+ break;
+
+ case AGENT_PIPE:
+ fprintf(stderr, "(pipe)\n");
+ break;
+
+ default:
+ fprintf(stderr, "(type %d unknown!)\n", aPtr->ipcType);
+ break;
+ }
+ for (i = 0; i < dpList->listSize; i++)
+ fprintf(stderr, " pmid[%d] %s\n", i, pmIDStr(dpList->list[i]));
+ }
+#endif
+
+ /* status.madeDsoResult is only used for DSO agents so don't waste time by
+ * checking that the agent is a DSO first.
+ */
+ aPtr->status.madeDsoResult = 0;
+
+ if (aPtr->profClient != cPtr || ctxnum != aPtr->profIndex) {
+ if (aPtr->ipcType == AGENT_DSO) {
+ if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client;
+ sts = aPtr->ipc.dso.dispatch.version.any.profile(cPtr->profile[ctxnum],
+ aPtr->ipc.dso.dispatch.version.any.ext);
+ }
+ else {
+ if (aPtr->status.notReady == 0) {
+ pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_PROFILE, ctxnum);
+ if ((sts = __pmSendProfile(aPtr->inFd, cPtr - client,
+ ctxnum, cPtr->profile[ctxnum])) < 0) {
+ pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_PROFILE, sts);
+ }
+ } else {
+ sts = PM_ERR_AGAIN;
+ }
+
+ }
+ if (sts >= 0) {
+ aPtr->profClient = cPtr;
+ aPtr->profIndex = ctxnum;
+ }
+ }
+
+ if (sts >= 0) {
+ if (aPtr->ipcType == AGENT_DSO) {
+ if (aPtr->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ aPtr->ipc.dso.dispatch.version.four.ext->e_context = cPtr - client;
+ sts = aPtr->ipc.dso.dispatch.version.any.fetch(dpList->listSize,
+ dpList->list, &result,
+ aPtr->ipc.dso.dispatch.version.any.ext);
+ if (sts >= 0) {
+ if (result == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "\"%s\" agent (DSO) returned a null result\n",
+ aPtr->pmDomainLabel);
+ sts = PM_ERR_PMID;
+ bad = 1;
+ }
+ else {
+ if (result->numpmid != dpList->listSize) {
+ __pmNotifyErr(LOG_WARNING,
+ "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n",
+ aPtr->pmDomainLabel,
+ result->numpmid,dpList->listSize);
+ sts = PM_ERR_PMID;
+ bad = 2;
+ }
+ }
+ }
+ }
+ else {
+ if (aPtr->status.notReady == 0) {
+ /* agent is ready for PDUs */
+ pmcd_trace(TR_XMIT_PDU, aPtr->inFd, PDU_FETCH, dpList->listSize);
+ if ((sts = __pmSendFetch(aPtr->inFd, cPtr - client, ctxnum, &when,
+ dpList->listSize, dpList->list)) < 0)
+ pmcd_trace(TR_XMIT_ERR, aPtr->inFd, PDU_FETCH, sts);
+ }
+ else {
+ /* agent is not ready for PDUs */
+ sts = PM_ERR_AGAIN;
+ }
+ }
+ }
+
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ switch (bad) {
+ case 0:
+ fprintf(stderr, "FETCH error: \"%s\" agent : %s\n",
+ aPtr->pmDomainLabel, pmErrStr(sts));
+ break;
+ case 1:
+ fprintf(stderr, "\"%s\" agent (DSO) returned a null result\n",
+ aPtr->pmDomainLabel);
+ break;
+ case 2:
+ fprintf(stderr, "\"%s\" agent (DSO) returned %d pmIDs (%d expected)\n",
+ aPtr->pmDomainLabel,
+ result->numpmid, dpList->listSize);
+ break;
+ }
+#endif
+ if (aPtr->ipcType == AGENT_DSO) {
+ aPtr->status.madeDsoResult = 1;
+ sts = 0;
+ }
+ else if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE)
+ CleanupAgent(aPtr, AT_COMM, aPtr->inFd);
+
+ result = MakeBadResult(dpList->listSize, dpList->list, sts);
+ }
+
+ return result;
+}
+
+int
+DoFetch(ClientInfo *cip, __pmPDU* pb)
+{
+ int i, j;
+ int sts;
+ int ctxnum;
+ __pmTimeval when;
+ int nPmids;
+ pmID *pmidList;
+ static pmResult *endResult = NULL;
+ static int maxnpmids = 0; /* sizes endResult */
+ DomPmidList *dList; /* NOTE: NOT indexed by agent index */
+ static int nDoms = 0;
+ static pmResult **results = NULL;
+ static int *resIndex = NULL;
+ __pmFdSet waitFds;
+ __pmFdSet readyFds;
+ int nWait;
+ int maxFd;
+ struct timeval timeout;
+
+ if (nAgents > nDoms) {
+ if (results != NULL)
+ free(results);
+ if (resIndex != NULL)
+ free(resIndex);
+ results = (pmResult **)malloc((nAgents + 1) * sizeof (pmResult *));
+ resIndex = (int *)malloc((nAgents + 1) * sizeof(int));
+ if (results == NULL || resIndex == NULL) {
+ __pmNoMem("DoFetch.results", (nAgents + 1) * sizeof (pmResult *) + (nAgents + 1) * sizeof(int), PM_FATAL_ERR);
+ }
+ nDoms = nAgents;
+ }
+ memset(results, 0, (nAgents + 1) * sizeof(results[0]));
+
+ sts = __pmDecodeFetch(pb, &ctxnum, &when, &nPmids, &pmidList);
+ if (sts < 0)
+ return sts;
+
+ /* Check that a profile has been received from the specified context */
+ if (ctxnum < 0 || ctxnum >= cip->szProfile ||
+ cip->profile[ctxnum] == NULL) {
+ __pmUnpinPDUBuf(pb);
+ if (ctxnum < 0 || ctxnum >= cip->szProfile)
+ __pmNotifyErr(LOG_ERR, "DoFetch: bad ctxnum=%d\n", ctxnum);
+ else
+ __pmNotifyErr(LOG_ERR, "DoFetch: no profile for ctxnum=%d\n", ctxnum);
+ return PM_ERR_NOPROFILE;
+ }
+
+ if (nPmids > maxnpmids) {
+ int need;
+ if (endResult != NULL)
+ free(endResult);
+ need = (int)sizeof(pmResult) + (nPmids - 1) * (int)sizeof(pmValueSet *);
+ if ((endResult = (pmResult *)malloc(need)) == NULL) {
+ __pmNoMem("DoFetch.endResult", need, PM_FATAL_ERR);
+ }
+ maxnpmids = nPmids;
+ }
+
+ dList = SplitPmidList(nPmids, pmidList);
+
+ /* For each domain in the split pmidList, dispatch the per-domain subset
+ * of pmIDs to the appropriate agent. For DSO agents, the pmResult will
+ * come back immediately. If a request cannot be sent to an agent, a
+ * suitable pmResult (containing metric not available values) will be
+ * returned.
+ */
+ __pmFD_ZERO(&waitFds);
+ nWait = 0;
+ maxFd = -1;
+ for (i = 0; dList[i].domain != -1; i++) {
+ j = mapdom[dList[i].domain];
+ results[j] = SendFetch(&dList[i], &agent[j], cip, ctxnum);
+ if (results[j] == NULL) { /* Wait for agent's response */
+ int fd = agent[j].outFd;
+ agent[j].status.busy = 1;
+ __pmFD_SET(fd, &waitFds);
+ if (fd > maxFd)
+ maxFd = fd;
+ nWait++;
+ }
+ }
+ /* Construct pmResult for bad-pmID list */
+ if (dList[i].listSize != 0)
+ results[nAgents] = MakeBadResult(dList[i].listSize, dList[i].list, PM_ERR_NOAGENT);
+
+ /* Wait for results to roll in from agents */
+ while (nWait > 0) {
+ __pmFD_COPY(&readyFds, &waitFds);
+ if (nWait > 1) {
+ timeout.tv_sec = _pmcd_timeout;
+ timeout.tv_usec = 0;
+
+ sts = __pmSelectRead(maxFd+1, &readyFds, &timeout);
+
+ if (sts == 0) {
+ __pmNotifyErr(LOG_INFO, "DoFetch: select timeout");
+
+ /* Timeout, terminate agents with undelivered results */
+ for (i = 0; i < nAgents; i++) {
+ if (agent[i].status.busy) {
+ /* Find entry in dList for this agent */
+ for (j = 0; dList[j].domain != -1; j++)
+ if (dList[j].domain == agent[i].pmDomainId)
+ break;
+ results[i] = MakeBadResult(dList[j].listSize,
+ dList[j].list,
+ PM_ERR_NOAGENT);
+ pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_RESULT, 0);
+ CleanupAgent(&agent[i], AT_COMM, agent[i].inFd);
+ }
+ }
+ break;
+ }
+ else if (sts < 0) {
+ /* this is not expected to happen! */
+ __pmNotifyErr(LOG_ERR, "DoFetch: fatal select failure: %s\n",
+ netstrerror());
+ Shutdown();
+ exit(1);
+ }
+ }
+
+ /* Read results from agents that have them ready */
+ for (i = 0; i < nAgents; i++) {
+ AgentInfo *ap = &agent[i];
+ int pinpdu;
+ if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds))
+ continue;
+ ap->status.busy = 0;
+ __pmFD_CLR(ap->outFd, &waitFds);
+ nWait--;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_RESULT) {
+ if ((sts = __pmDecodeResult(pb, &results[i])) >= 0)
+ if (results[i]->numpmid != aFreq[i]) {
+ pmFreeResult(results[i]);
+ sts = PM_ERR_IPC;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_ERR, "DoFetch: \"%s\" agent given %d pmIDs, returned %d\n",
+ ap->pmDomainLabel, aFreq[i], results[i]->numpmid);
+#endif
+ }
+ }
+ else {
+ if (sts == PDU_ERROR) {
+ int s;
+ if ((s = __pmDecodeError(pb, &sts)) < 0)
+ sts = s;
+ else if (sts >= 0)
+ sts = PM_ERR_GENERIC;
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts);
+ }
+ else if (sts >= 0) {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_RESULT, sts);
+ sts = PM_ERR_IPC;
+ }
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (sts < 0) {
+ /* Find entry in dList for this agent */
+ for (j = 0; dList[j].domain != -1; j++)
+ if (dList[j].domain == agent[i].pmDomainId)
+ break;
+ results[i] = MakeBadResult(dList[j].listSize,
+ dList[j].list, sts);
+
+ if (sts == PM_ERR_PMDANOTREADY) {
+ /* the agent is indicating it can't handle PDUs for now */
+ int k;
+ extern int CheckError(AgentInfo *ap, int sts);
+
+ for (k = 0; k < dList[j].listSize; k++)
+ results[i]->vset[k]->numval = PM_ERR_AGAIN;
+ sts = CheckError(&agent[i], sts);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "RESULT error from \"%s\" agent : %s\n",
+ ap->pmDomainLabel, pmErrStr(sts));
+ }
+#endif
+ if (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT)
+ CleanupAgent(ap, AT_COMM, ap->outFd);
+ }
+ }
+ }
+
+ endResult->numpmid = nPmids;
+ __pmtimevalNow(&endResult->timestamp);
+ /* The order of the pmIDs in the per-domain results is the same as in the
+ * original request, but on a per-domain basis. resIndex is an array of
+ * indeces (one per agent) of the next metric to be retrieved from each
+ * per-domain result's vset.
+ */
+ memset(resIndex, 0, (nAgents + 1) * sizeof(resIndex[0]));
+
+ for (i = 0; i < nPmids; i++) {
+ j = mapdom[((__pmID_int *)&pmidList[i])->domain];
+ endResult->vset[i] = results[j]->vset[resIndex[j]++];
+ }
+ pmcd_trace(TR_XMIT_PDU, cip->fd, PDU_RESULT, endResult->numpmid);
+
+ sts = 0;
+ if (cip->status.changes) {
+ /* notify client of PMCD state change */
+ sts = __pmSendError(cip->fd, FROM_ANON, (int)cip->status.changes);
+ if (sts > 0)
+ sts = 0;
+ cip->status.changes = 0;
+ }
+ if (sts == 0)
+ sts = __pmSendResult(cip->fd, FROM_ANON, endResult);
+
+ if (sts < 0) {
+ pmcd_trace(TR_XMIT_ERR, cip->fd, PDU_RESULT, sts);
+ CleanupClient(cip, sts);
+ }
+
+ /*
+ * pmFreeResult() all the accumulated results.
+ */
+ for (i = 0; dList[i].domain != -1; i++) {
+ j = mapdom[dList[i].domain];
+ if (agent[j].ipcType == AGENT_DSO && agent[j].status.connected &&
+ !agent[j].status.madeDsoResult)
+ /* Living DSO's manage their own pmResult skeleton unless
+ * MakeBadResult was called to create the result. The value sets
+ * within the skeleton need to be freed though!
+ */
+ __pmFreeResultValues(results[j]);
+ else
+ /* For others it is dynamically allocated in __pmDecodeResult or
+ * MakeBadResult
+ */
+ pmFreeResult(results[j]);
+ }
+ if (results[nAgents] != NULL)
+ pmFreeResult(results[nAgents]);
+ __pmUnpinPDUBuf(pmidList);
+ return 0;
+}
diff --git a/src/pmcd/src/dopdus.c b/src/pmcd/src/dopdus.c
new file mode 100644
index 0000000..7eb71c4
--- /dev/null
+++ b/src/pmcd/src/dopdus.c
@@ -0,0 +1,1057 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmcd.h"
+
+/* Check returned error from a client.
+ * If client returns ready/not_ready status change, check then update agent
+ * status.
+ * If the client goes from not_ready to ready, it sends an unsolicited error
+ * PDU. If this happens, the retry flag indicates that the expected response
+ * is yet to arrive, and that the caller should try reading
+ * and the expected response will follow it.
+ */
+int
+CheckError(AgentInfo *ap, int sts)
+{
+ int retSts;
+
+ if (sts == PM_ERR_PMDANOTREADY) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "%s agent (%s) sent NOT READY\n",
+ ap->pmDomainLabel,
+ ap->status.notReady ? "not ready" : "ready");
+#endif
+ if (ap->status.notReady == 0) {
+ ap->status.notReady = 1;
+ retSts = PM_ERR_AGAIN;
+ }
+ else
+ retSts = PM_ERR_IPC;
+ }
+ else if (sts == PM_ERR_PMDAREADY) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "%s agent (%s) sent unexpected READY\n",
+ ap->pmDomainLabel,
+ ap->status.notReady ? "not ready" : "ready");
+#endif
+ retSts = PM_ERR_IPC;
+ }
+ else
+ retSts = sts;
+
+ return retSts;
+}
+
+int
+DoText(ClientInfo *cp, __pmPDU* pb)
+{
+ int sts, s;
+ int ident;
+ int type;
+ AgentInfo *ap;
+ char *buffer = NULL;
+
+ if ((sts = __pmDecodeTextReq(pb, &ident, &type)) < 0)
+ return sts;
+
+ if ((ap = FindDomainAgent(((__pmID_int *)&ident)->domain)) == NULL)
+ return PM_ERR_PMID;
+ else if (!ap->status.connected)
+ return PM_ERR_NOAGENT;
+
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ sts = ap->ipc.dso.dispatch.version.any.text(ident, type, &buffer,
+ ap->ipc.dso.dispatch.version.any.ext);
+ }
+ else {
+ if (ap->status.notReady)
+ return PM_ERR_AGAIN;
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_TEXT_REQ, ident);
+ sts = __pmSendTextReq(ap->inFd, cp - client, ident, type);
+ if (sts >= 0) {
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_TEXT)
+ sts = __pmDecodeText(pb, &ident, &buffer);
+ else if (sts == PDU_ERROR) {
+ s = __pmDecodeError(pb, &sts);
+ if (s < 0)
+ sts = s;
+ else
+ sts = CheckError(ap, sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_TEXT, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_TEXT, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else
+ pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_TEXT_REQ, sts);
+ }
+
+ if (ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE))
+ CleanupAgent(ap, AT_COMM, ap->inFd);
+
+ if (sts >= 0) {
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_TEXT, ident);
+ sts = __pmSendText(cp->fd, FROM_ANON, ident, buffer);
+ if (sts < 0 && ap->ipcType != AGENT_DSO) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_TEXT, sts);
+ CleanupClient(cp, sts);
+ }
+ if (ap->ipcType != AGENT_DSO) {
+ /* daemon PMDAs have a malloc'd buffer */
+ free(buffer);
+ }
+ }
+ return sts;
+}
+
+int
+DoProfile(ClientInfo *cp, __pmPDU *pb)
+{
+ __pmProfile *newProf;
+ int ctxnum, sts, i;
+
+ sts = __pmDecodeProfile(pb, &ctxnum, &newProf);
+ if (sts >= 0) {
+ /* Allocate more profile pointers if required */
+ if (ctxnum >= cp->szProfile) {
+ __pmProfile **newProfPtrs;
+ int need, oldSize = cp->szProfile;
+
+ if (ctxnum - cp->szProfile < 4)
+ cp->szProfile += 4;
+ else
+ cp->szProfile = ctxnum + 1;
+ need = cp->szProfile * (int)sizeof(__pmProfile *);
+ if ((newProfPtrs = (__pmProfile **)malloc(need)) == NULL) {
+ cp->szProfile = oldSize;
+ __pmNoMem("DoProfile.newProfPtrs", need, PM_RECOV_ERR);
+ __pmFreeProfile(newProf);
+ return -oserror();
+ }
+
+ /* Copy any old pointers and zero the newly allocated ones */
+ if ((need = oldSize * (int)sizeof(__pmProfile *))) {
+ memcpy(newProfPtrs, cp->profile, need);
+ free(cp->profile); /* But not the __pmProfile ptrs! */
+ }
+ need = (cp->szProfile - oldSize) * (int)sizeof(__pmProfile *);
+ memset(&newProfPtrs[oldSize], 0, need);
+ cp->profile = newProfPtrs;
+ }
+ else /* cp->profile is big enough */
+ if (cp->profile[ctxnum] != NULL)
+ __pmFreeProfile(cp->profile[ctxnum]);
+ cp->profile[ctxnum] = newProf;
+
+ /* "Invalidate" any references to the client context's profile in the
+ * agents to which the old profile was last sent
+ */
+ for (i = 0; i < nAgents; i++) {
+ AgentInfo *ap = &agent[i];
+
+ if (ap->profClient == cp && ap->profIndex == ctxnum)
+ ap->profClient = NULL;
+ }
+ }
+ return sts;
+}
+
+int
+DoDesc(ClientInfo *cp, __pmPDU *pb)
+{
+ int sts, s;
+ pmID pmid;
+ AgentInfo *ap;
+ pmDesc desc = {0};
+ int fdfail = -1;
+
+ if ((sts = __pmDecodeDescReq(pb, &pmid)) < 0)
+ return sts;
+
+ if ((ap = FindDomainAgent(((__pmID_int *)&pmid)->domain)) == NULL)
+ return PM_ERR_PMID;
+ else if (!ap->status.connected)
+ return PM_ERR_NOAGENT;
+
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ sts = ap->ipc.dso.dispatch.version.any.desc(pmid, &desc,
+ ap->ipc.dso.dispatch.version.any.ext);
+ }
+ else {
+ if (ap->status.notReady)
+ return PM_ERR_AGAIN;
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_DESC_REQ, (int)pmid);
+ sts = __pmSendDescReq(ap->inFd, cp - client, pmid);
+ if (sts >= 0) {
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_DESC)
+ sts = __pmDecodeDesc(pb, &desc);
+ else if (sts == PDU_ERROR) {
+ s = __pmDecodeError(pb, &sts);
+ if (s < 0)
+ sts = s;
+ else
+ sts = CheckError(ap, sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_DESC, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_DESC, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_DESC_REQ, sts);
+ fdfail = ap->inFd;
+ }
+ }
+
+ if (sts >= 0) {
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_DESC, (int)desc.pmid);
+ sts = __pmSendDesc(cp->fd, FROM_ANON, &desc);
+ if (sts < 0) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_DESC, sts);
+ CleanupClient(cp, sts);
+ }
+ }
+ else
+ if (ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+
+ return sts;
+}
+
+int
+DoInstance(ClientInfo *cp, __pmPDU* pb)
+{
+ int sts, s;
+ __pmTimeval when;
+ pmInDom indom;
+ int inst;
+ char *name;
+ __pmInResult *inresult = NULL;
+ AgentInfo *ap;
+ int fdfail = -1;
+
+ sts = __pmDecodeInstanceReq(pb, &when, &indom, &inst, &name);
+ if (sts < 0)
+ return sts;
+ if (when.tv_sec != 0 || when.tv_usec != 0) {
+ /*
+ * we have no idea how to do anything but current, yet!
+ *
+ * TODO EXCEPTION PCP 2.0 ...
+ * this may be left over from the pmvcr days, and can be tossed?
+ * ... leaving it here is benign
+ */
+ if (name != NULL) free(name);
+ return PM_ERR_NYI;
+ }
+ if ((ap = FindDomainAgent(((__pmInDom_int *)&indom)->domain)) == NULL) {
+ if (name != NULL) free(name);
+ return PM_ERR_INDOM;
+ }
+ else if (!ap->status.connected) {
+ if (name != NULL) free(name);
+ return PM_ERR_NOAGENT;
+ }
+
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ sts = ap->ipc.dso.dispatch.version.any.instance(indom, inst, name,
+ &inresult,
+ ap->ipc.dso.dispatch.version.any.ext);
+ }
+ else {
+ if (ap->status.notReady) {
+ if (name != NULL) free(name);
+ return PM_ERR_AGAIN;
+ }
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_INSTANCE_REQ, (int)indom);
+ sts = __pmSendInstanceReq(ap->inFd, cp - client, &when, indom, inst, name);
+ if (sts >= 0) {
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_INSTANCE)
+ sts = __pmDecodeInstance(pb, &inresult);
+ else if (sts == PDU_ERROR) {
+ inresult = NULL;
+ s = __pmDecodeError(pb, &sts);
+ if (s < 0)
+ sts = s;
+ else
+ sts = CheckError(ap, sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_INSTANCE, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_INSTANCE, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_INSTANCE_REQ, sts);
+ fdfail = ap->inFd;
+ }
+ }
+ if (name != NULL) free(name);
+
+ if (sts >= 0) {
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_INSTANCE, (int)(inresult->indom));
+ sts = __pmSendInstance(cp->fd, FROM_ANON, inresult);
+ if (sts < 0) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_INSTANCE, sts);
+ CleanupClient(cp, sts);
+ }
+ __pmFreeInResult(inresult);
+ }
+ else
+ if (ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+
+ return sts;
+}
+
+/*
+ * This handler is for remote versions of pmNameAll or pmNameID.
+ * Note: only one pmid for the list should be sent.
+ */
+int
+DoPMNSIDs(ClientInfo *cp, __pmPDU *pb)
+{
+ int sts = 0;
+ int op_sts = 0;
+ int numnames = 0;
+ pmID idlist[1];
+ char **namelist = NULL;
+ AgentInfo *ap = NULL;
+ int fdfail = -1;
+
+ if ((sts = __pmDecodeIDList(pb, 1, idlist, &op_sts)) < 0)
+ goto fail;
+
+ if ((sts = pmNameAll(idlist[0], &namelist)) < 0) {
+ /*
+ * failure may be a real failure, or could be a metric within a
+ * dynamic sutree of the PMNS
+ */
+ if ((ap = FindDomainAgent(((__pmID_int *)&idlist[0])->domain)) == NULL) {
+ sts = PM_ERR_NOAGENT;
+ goto fail;
+ }
+ if (!ap->status.connected) {
+ sts = PM_ERR_NOAGENT;
+ goto fail;
+ }
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ sts = ap->ipc.dso.dispatch.version.four.name(idlist[0], &namelist,
+ ap->ipc.dso.dispatch.version.four.ext);
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ sts = PM_ERR_PMID;
+ }
+ }
+ else {
+ /* daemon PMDA ... ship request on */
+ if (ap->status.notReady)
+ return PM_ERR_AGAIN;
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_IDS, 1);
+ sts = __pmSendIDList(ap->inFd, cp - client, 1, &idlist[0], 0);
+ if (sts >= 0) {
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_PMNS_NAMES) {
+ sts = __pmDecodeNameList(pb, &numnames, &namelist, NULL);
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ /* __pmSendIDList failed */
+ sts = __pmMapErrno(sts);
+ fdfail = ap->inFd;
+ }
+ }
+ if (sts < 0) goto fail;
+ }
+
+ numnames = sts;
+
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames);
+ if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, namelist, NULL)) < 0){
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts);
+ CleanupClient(cp, sts);
+ goto fail;
+ }
+ /* fall through OK */
+
+fail:
+ if (ap != NULL && ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+ if (namelist) free(namelist);
+ return sts;
+}
+
+/*
+ * This handler is for the remote version of pmLookupName.
+ */
+int
+DoPMNSNames(ClientInfo *cp, __pmPDU *pb)
+{
+ int sts = 0;
+ int numids = 0;
+ pmID *idlist = NULL;
+ char **namelist = NULL;
+ int i;
+ AgentInfo *ap = NULL;
+
+ if ((sts = __pmDecodeNameList(pb, &numids, &namelist, NULL)) < 0)
+ goto done;
+
+ if ((idlist = (pmID *)calloc(numids, sizeof(int))) == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+
+ sts = pmLookupName(numids, namelist, idlist);
+ for (i = 0; i < numids; i++) {
+ if (idlist[i] == PM_ID_NULL) continue;
+ if (pmid_domain(idlist[i]) == DYNAMIC_PMID && pmid_item(idlist[i]) == 0) {
+ int lsts;
+ int domain = pmid_cluster(idlist[i]);
+ /*
+ * don't return <domain>.*.* ... all return paths from here
+ * must either set a valid PMID in idlist[i] or indicate
+ * the first error in the return from pmLookupName
+ */
+ idlist[i] = PM_ID_NULL; /* default case if cannot translate */
+ if ((ap = FindDomainAgent(domain)) == NULL) {
+ if (sts > 0) sts = PM_ERR_NOAGENT;
+ continue;
+ }
+ if (!ap->status.connected) {
+ if (sts > 0) sts = PM_ERR_NOAGENT;
+ continue;
+ }
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ lsts = ap->ipc.dso.dispatch.version.four.pmid(namelist[i], &idlist[i],
+ ap->ipc.dso.dispatch.version.four.ext);
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ lsts = PM_ERR_NAME;
+ }
+ }
+ else {
+ /* daemon PMDA ... ship request on */
+ int fdfail = -1;
+ if (ap->status.notReady)
+ lsts = PM_ERR_AGAIN;
+ else {
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_NAMES, 1);
+ lsts = __pmSendNameList(ap->inFd, cp - client, 1, &namelist[i], NULL);
+ if (lsts >= 0) {
+ int pinpdu;
+ pinpdu = lsts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (lsts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (lsts == PDU_PMNS_IDS) {
+ int xsts;
+ lsts = __pmDecodeIDList(pb, 1, &idlist[i], &xsts);
+ if (lsts >= 0)
+ lsts = xsts;
+ }
+ else if (lsts == PDU_ERROR) {
+ __pmDecodeError(pb, &lsts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_IDS, lsts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts);
+ lsts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ /* __pmSendNameList failed */
+ lsts = __pmMapErrno(lsts);
+ pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_PMNS_NAMES, lsts);
+ fdfail = ap->inFd;
+ }
+ }
+ if (ap != NULL && ap->ipcType != AGENT_DSO &&
+ (lsts == PM_ERR_IPC || lsts == PM_ERR_TIMEOUT || lsts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+ }
+ /*
+ * only set error status to the current error status
+ * if this is the first error for this set of metrics
+ */
+ if (lsts < 0 && sts > 0) sts = lsts;
+ }
+ }
+
+ if (sts < 0) {
+ /* If get an error which should be passed back along
+ * with valid data to the client
+ * then do NOT fail -> return status with the id-list.
+ */
+ if (sts != PM_ERR_NAME && sts != PM_ERR_NONLEAF && sts != PM_ERR_NOAGENT)
+ goto done;
+ }
+
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_IDS, numids);
+ if ((sts = __pmSendIDList(cp->fd, FROM_ANON, numids, idlist, sts)) < 0) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_IDS, sts);
+ CleanupClient(cp, sts);
+ goto done;
+ }
+
+done:
+ if (idlist) free(idlist);
+ if (namelist) free(namelist);
+
+ return sts;
+}
+
+/*
+ * This handler is for the remote version of pmGetChildren.
+ */
+int
+DoPMNSChild(ClientInfo *cp, __pmPDU *pb)
+{
+ int sts = 0;
+ int numnames = 0;
+ char *name = NULL;
+ char **offspring = NULL;
+ int *statuslist = NULL;
+ int subtype;
+ char *namelist[1];
+ pmID idlist[1];
+
+ if ((sts = __pmDecodeChildReq(pb, &name, &subtype)) < 0)
+ goto done;
+
+ namelist[0] = name;
+ sts = pmLookupName(1, namelist, idlist);
+ if (sts == 1 && pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) {
+ int domain = pmid_cluster(idlist[0]);
+ AgentInfo *ap = NULL;
+ if ((ap = FindDomainAgent(domain)) == NULL) {
+ sts = PM_ERR_NOAGENT;
+ goto done;
+ }
+ if (!ap->status.connected) {
+ sts = PM_ERR_NOAGENT;
+ goto done;
+ }
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ sts = ap->ipc.dso.dispatch.version.four.children(name, 0, &offspring, &statuslist,
+ ap->ipc.dso.dispatch.version.four.ext);
+ if (sts < 0)
+ goto done;
+ if (subtype == 0) {
+ if (statuslist) free(statuslist);
+ statuslist = NULL;
+ }
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ sts = PM_ERR_NAME;
+ goto done;
+ }
+ }
+ else {
+ /* daemon PMDA ... ship request on */
+ int fdfail = -1;
+ if (ap->status.notReady)
+ sts = PM_ERR_AGAIN;
+ else {
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_CHILD, 1);
+ sts = __pmSendChildReq(ap->inFd, cp - client, name, subtype);
+ if (sts >= 0) {
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_PMNS_NAMES) {
+ sts = __pmDecodeNameList(pb, &numnames,
+ &offspring, &statuslist);
+ if (sts >= 0) {
+ sts = numnames;
+ if (subtype == 0) {
+ free(statuslist);
+ statuslist = NULL;
+ }
+ }
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_NAMES, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ /* __pmSendChildReq failed */
+ sts = __pmMapErrno(sts);
+ fdfail = ap->inFd;
+ }
+ }
+ if (ap != NULL && ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+ }
+ }
+ else {
+ if (subtype == 0) {
+ if ((sts = pmGetChildren(name, &offspring)) < 0)
+ goto done;
+ }
+ else {
+ if ((sts = pmGetChildrenStatus(name, &offspring, &statuslist)) < 0)
+ goto done;
+ }
+ }
+
+ numnames = sts;
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, numnames);
+ if ((sts = __pmSendNameList(cp->fd, FROM_ANON, numnames, offspring, statuslist)) < 0) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts);
+ CleanupClient(cp, sts);
+ }
+
+done:
+ if (name) free(name);
+ if (offspring) free(offspring);
+ if (statuslist) free(statuslist);
+ return sts;
+}
+
+/*************************************************************************/
+
+static char **travNL; /* list of names for traversal */
+static char *travNL_ptr; /* pointer into travNL */
+static int travNL_num; /* number of names in list */
+static int travNL_strlen; /* number of bytes of names */
+static int travNL_i; /* array index */
+
+static void
+AddLengths(const char *name)
+{
+ travNL_strlen += strlen(name) + 1;
+ travNL_num++;
+}
+
+static void
+BuildNameList(const char *name)
+{
+ travNL[travNL_i++] = travNL_ptr;
+ strcpy(travNL_ptr, name);
+ travNL_ptr += strlen(name) + 1;
+}
+
+/*
+ * handle dynamic PMNS entries in remote version of pmTraversePMNS.
+ *
+ * num_names and names[] is the result of pmTraversePMNS for the
+ * loaded PMNS ... need to preserve the semantics of this in the
+ * end result, so names[] and all of the name[i] strings are in a
+ * single malloc block
+ */
+static void
+traverse_dynamic(ClientInfo *cp, char *start, int *num_names, char ***names)
+{
+ int sts;
+ int i;
+ char **offspring;
+ int *statuslist;
+ char *namelist[1];
+ pmID idlist[1];
+ int fake = 0;
+
+ /*
+ * if we get any errors in the setup (unexpected), simply skip
+ * that name[i] entry and move on ... any client using the associated
+ * name[i] will get an error later, e.g. when trying to fetch the
+ * pmDesc
+ *
+ * process in reverse order so stitching does not disturb the ones
+ * we've not processed yet
+ */
+ if (*num_names == 0) {
+ /*
+ * special case, where starting point is _below_ the dynamic
+ * node in the PMNS known to pmcd (or name is simply invalid) ...
+ * fake a single name in the list so far ... names[] does not hold
+ * the string value as well, but this is OK because names[0] will
+ * be rebuilt * replacing "name" (or cleaned up at the end) ...
+ * note travNL_strlen initialization so resize below is correct
+ */
+ fake = 1;
+ *names = (char **)malloc(sizeof((*names)[0]));
+ if (*names == NULL)
+ return;
+ (*names)[0] = start;
+ *num_names = 1;
+ travNL_strlen = strlen(start) + 1;
+ }
+ for (i = *num_names-1; i >= 0; i--) {
+ offspring = NULL;
+ namelist[0] = (*names)[i];
+ sts = pmLookupName(1, namelist, idlist);
+ if (sts < 1)
+ continue;
+ if (pmid_domain(idlist[0]) == DYNAMIC_PMID && pmid_item(idlist[0]) == 0) {
+ int domain = pmid_cluster(idlist[0]);
+ AgentInfo *ap;
+ if ((ap = FindDomainAgent(domain)) == NULL)
+ continue;
+ if (!ap->status.connected)
+ continue;
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_4) {
+ sts = ap->ipc.dso.dispatch.version.four.children(namelist[0], 1, &offspring, &statuslist,
+ ap->ipc.dso.dispatch.version.four.ext);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "traverse_dynamic: DSO PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0]));
+ if (sts < 0)
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ else {
+ int j;
+ fprintf(stderr, "%d names\n", sts);
+ for (j = 0; j < sts; j++) {
+ fprintf(stderr, " %s\n", offspring[j]);
+ }
+ }
+ }
+#endif
+ if (sts < 0)
+ continue;
+ if (statuslist) free(statuslist);
+ }
+ else {
+ /* Not PMDA_INTERFACE_4 or later */
+ continue;
+ }
+ }
+ else {
+ /* daemon PMDA ... ship request on */
+ int fdfail = -1;
+ if (ap->status.notReady)
+ continue;
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_PMNS_TRAVERSE, 1);
+ sts = __pmSendTraversePMNSReq(ap->inFd, cp - client, namelist[0]);
+ if (sts >= 0) {
+ int numnames;
+ __pmPDU *pb;
+ int pinpdu;
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_PMNS_NAMES) {
+ sts = __pmDecodeNameList(pb, &numnames,
+ &offspring, &statuslist);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "traverse_dynamic: daemon PMDA: expand dynamic PMNS entry %s (%s) -> ", namelist[0], pmIDStr(idlist[0]));
+ if (sts < 0)
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ else {
+ int j;
+ fprintf(stderr, "%d names\n", sts);
+ for (j = 0; j < sts; j++) {
+ fprintf(stderr, " %s\n", offspring[j]);
+ }
+ }
+ }
+#endif
+ if (statuslist) {
+ free(statuslist);
+ statuslist = NULL;
+ }
+ if (sts >= 0) {
+ sts = numnames;
+ }
+ }
+ else if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_PMNS_NAMES, sts);
+ }
+ else {
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_PMNS_IDS, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ fdfail = ap->outFd;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+ else {
+ /* __pmSendChildReq failed */
+ sts = __pmMapErrno(sts);
+ fdfail = ap->inFd;
+ }
+ if (ap != NULL && ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || sts == -EPIPE) &&
+ fdfail != -1)
+ CleanupAgent(ap, AT_COMM, fdfail);
+ }
+ }
+ /* Stitching ... remove names[i] and add sts names from offspring[] */
+ if (offspring) {
+ int j;
+ int k; /* index for copying to new[] */
+ int ii; /* index for copying from names[] */
+ char **new;
+ char *p; /* string copy dest ptr */
+ int new_len;
+
+ fake = 0; /* don't need to undo faking */
+ new_len = travNL_strlen - strlen(namelist[0]) - 1;
+ for (j = 0; j < sts; j++)
+ new_len += strlen(offspring[j]) + 1;
+ new = (char **)malloc(new_len + (*num_names - 1 + sts)*sizeof(new[0]));
+ if (new == NULL) {
+ /* tough luck! */
+ free(offspring);
+ continue;
+ }
+ *num_names = *num_names - 1 + sts;
+ p = (char *)&new[*num_names];
+ ii = 0;
+ for (k = 0; k < *num_names; k++) {
+ if (k < i || k >= i+sts) {
+ /* copy across old name */
+ if (k == i+sts)
+ ii++; /* skip name than new ones replaced */
+ strcpy(p, (*names)[ii]);
+ ii++;
+ }
+ else {
+ /* stitch in new name */
+ strcpy(p, offspring[k-i]);
+ }
+ new[k] = p;
+ p += strlen(p) + 1;
+ }
+
+ free(offspring);
+ free(*names);
+ *names = new;
+ travNL_strlen = new_len;
+ }
+ }
+
+ if (fake == 1) {
+ /*
+ * need to undo initial faking as this name is simply not valid!
+ */
+ *num_names = 0;
+ free(*names);
+ *names = NULL;
+ travNL_strlen = 0;
+ }
+
+}
+
+/*
+ * This handler is for the remote version of pmTraversePMNS.
+ *
+ * Notes:
+ * We are building up a name-list and giving it to
+ * __pmSendNameList.
+ * This is a bit inefficient but convenient.
+ * It would really be better to build up a PDU buffer
+ * directly and not do the extra copying !
+ */
+int
+DoPMNSTraverse(ClientInfo *cp, __pmPDU *pb)
+{
+ int sts = 0;
+ char *name = NULL;
+ int travNL_need = 0;
+
+ travNL = NULL;
+
+ if ((sts = __pmDecodeTraversePMNSReq(pb, &name)) < 0)
+ goto done;
+
+ travNL_strlen = 0;
+ travNL_num = 0;
+ if ((sts = pmTraversePMNS(name, AddLengths)) < 0)
+ goto check;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PMNS) {
+ fprintf(stderr, "DoPMNSTraverse: %d names below %s after pmTraversePMNS\n", travNL_num, name);
+ }
+#endif
+
+ /* for each ptr, string bytes, and string terminators */
+ travNL_need = travNL_num * (int)sizeof(char*) + travNL_strlen;
+
+ if ((travNL = (char**)malloc(travNL_need)) == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+
+ travNL_i = 0;
+ travNL_ptr = (char*)&travNL[travNL_num];
+ sts = pmTraversePMNS(name, BuildNameList);
+
+check:
+ /*
+ * sts here is last result of calling pmTraversePMNS() ... may need
+ * this later
+ * for dynamic PMNS entries, travNL_num will be 0 (PM_ERR_PMID from
+ * pmTraversePMNS()).
+ */
+ traverse_dynamic(cp, name, &travNL_num, &travNL);
+ if (travNL_num < 1)
+ goto done;
+
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_PMNS_NAMES, travNL_num);
+ if ((sts = __pmSendNameList(cp->fd, FROM_ANON, travNL_num, travNL, NULL)) < 0) {
+ pmcd_trace(TR_XMIT_ERR, cp->fd, PDU_PMNS_NAMES, sts);
+ CleanupClient(cp, sts);
+ goto done;
+ }
+
+done:
+ if (name) free(name);
+ if (travNL) free(travNL);
+ return sts;
+}
+
+/*************************************************************************/
+
+int
+DoCreds(ClientInfo *cp, __pmPDU *pb)
+{
+ int i, sts, flags = 0, version = 0, sender = 0, credcount = 0;
+ __pmCred *credlist = NULL;
+ __pmVersionCred *vcp;
+
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0)
+ return sts;
+ pmcd_trace(TR_RECV_PDU, cp->fd, PDU_CREDS, credcount);
+
+ for (i = 0; i < credcount; i++) {
+ switch(credlist[i].c_type) {
+ case CVERSION:
+ vcp = (__pmVersionCred *)&credlist[i];
+ flags = vcp->c_flags;
+ version = vcp->c_version;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmcd: version cred (%u) flags=%x\n", vcp->c_version, vcp->c_flags);
+#endif
+ break;
+
+ default:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "pmcd: Error: bogus cred type %d\n", credlist[i].c_type);
+#endif
+ sts = PM_ERR_IPC;
+ break;
+ }
+ }
+ if (credlist != NULL)
+ free(credlist);
+
+ if (sts >= 0 && version)
+ sts = __pmSetVersionIPC(cp->fd, version);
+ if (sts >= 0 && flags) {
+ /*
+ * new client has arrived; may want encryption, authentication, etc
+ * complete the handshake (depends on features requested), continue
+ * on to check access is allowed for the authenticated persona, and
+ * finally notify any interested PMDAs
+ */
+ if ((sts = __pmSecureServerHandshake(cp->fd, flags, &cp->attrs)) < 0)
+ return sts;
+ }
+ if ((sts = CheckAccountAccess(cp)) < 0) /* host access done already */
+ return sts;
+ else if (sts > 0) /* account authentication successful - inform PMDAs */
+ sts = AgentsAuthentication(cp - client);
+ /* else: no account-based authentication needed, so finish successfully */
+
+ return sts;
+}
diff --git a/src/pmcd/src/dostore.c b/src/pmcd/src/dostore.c
new file mode 100644
index 0000000..8e6071a
--- /dev/null
+++ b/src/pmcd/src/dostore.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmcd.h"
+#include <assert.h>
+
+/* Routine to split a result into a list of results, each containing metrics
+ * from a single domain. The end of the list is marked by a pmResult with a
+ * numpmid of zero. Any pmids for which there is no agent will be in the
+ * second to last pmResult which will have a negated numpmid value.
+ */
+
+pmResult **
+SplitResult(pmResult *res)
+{
+ int i, j;
+ static int *aFreq = NULL; /* Freq. histogram: pmids for each agent */
+ static int *resIndex = NULL; /* resIndex[k] = index of agent[k]'s list in result */
+ static int nDoms = 0; /* No. of entries in two tables above */
+ int nGood;
+ int need;
+ pmResult **results;
+
+ /* Allocate the frequency histogram and array for mapping from agent to
+ * result list index. Because a SIGHUP reconfiguration may have caused a
+ * change in the number of agents, reallocation using a new size may be
+ * necessary.
+ * There are nAgents + 1 entries in the aFreq and resIndex arrays. The
+ * last entry in each is used for the pmIDs for which no agent could be
+ * found.
+ */
+ if (nAgents > nDoms) {
+ nDoms = nAgents;
+ if (aFreq != NULL)
+ free(aFreq);
+ if (resIndex != NULL)
+ free(resIndex);
+ aFreq = (int *)malloc((nAgents + 1) * sizeof(int));
+ resIndex = (int *)malloc((nAgents + 1) * sizeof(int));
+ if (aFreq == NULL || resIndex == NULL) {
+ __pmNoMem("SplitResult.freq", 2 * (nAgents + 1) * sizeof(int), PM_FATAL_ERR);
+ }
+ }
+
+ /* Build a frequency histogram of metric domains (use aFreq[nAgents] for
+ * pmids for which there is no agent).
+ */
+ for (i = 0; i <= nAgents; i++)
+ aFreq[i] = 0;
+ for (i = 0; i < res->numpmid; i++) {
+ int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain;
+ for (j = 0; j < nAgents; j++)
+ if (agent[j].pmDomainId == dom && agent[j].status.connected)
+ break;
+ aFreq[j]++;
+ }
+
+ /* Initialise resIndex and allocate the results structures */
+ nGood = 0;
+ for (i = 0; i < nAgents; i++)
+ if (aFreq[i]) {
+ resIndex[i] = nGood;
+ nGood++;
+ }
+ resIndex[nAgents] = nGood;
+
+ need = nGood + 1 + ((aFreq[nAgents]) ? 1 : 0);
+ need *= sizeof(pmResult *);
+ if ((results = (pmResult **) malloc(need)) == NULL) {
+ __pmNoMem("SplitResult.results", need, PM_FATAL_ERR);
+ }
+ j = 0;
+ for (i = 0; i <= nAgents; i++)
+ if (aFreq[i]) {
+ need = (int)sizeof(pmResult) + (aFreq[i] - 1) * (int)sizeof(pmValueSet *);
+ results[j] = (pmResult *) malloc(need);
+ if (results[j] == NULL) {
+ __pmNoMem("SplitResult.domain", need, PM_FATAL_ERR);
+ }
+ results[j]->numpmid = aFreq[i];
+ j++;
+ }
+
+ /* Make the "end of list" pmResult */
+ if ((results[j] = (pmResult *) malloc(sizeof(pmResult))) == NULL) {
+ __pmNoMem("SplitResult.domain", sizeof(pmResult), PM_FATAL_ERR);
+ }
+ results[j]->numpmid = 0;
+
+ /* Foreach vset in res, find it's pmResult in the per domain results array
+ * and copy a pointer to the vset to the next available position in the per
+ * domain result.
+ */
+ for (i = 0; i <= nAgents; i++)
+ aFreq[i] = 0;
+ for (i = 0; i < res->numpmid; i++) {
+ int dom = ((__pmID_int *)&res->vset[i]->pmid)->domain;
+ for (j = 0; j < nAgents; j++)
+ if (dom == agent[j].pmDomainId && agent[j].status.connected)
+ break;
+ results[resIndex[j]]->vset[aFreq[j]] = res->vset[i];
+ aFreq[j]++;
+ }
+
+ /* Flip the sign of numpmids in the "bad list" */
+ if (aFreq[nAgents]) {
+ int bad = resIndex[nAgents];
+ results[bad]->numpmid = -results[bad]->numpmid;
+ }
+
+ return results;
+}
+
+int
+DoStore(ClientInfo *cp, __pmPDU* pb)
+{
+ int sts;
+ int s = 0;
+ AgentInfo *ap;
+ pmResult *result;
+ pmResult **dResult;
+ int i;
+ __pmFdSet readyFds;
+ __pmFdSet waitFds;
+ int nWait = 0;
+ int maxFd = -1;
+ int badStore; /* != 0 => store to nonexistent agent */
+ int notReady = 0; /* != 0 => store to agent that's not ready */
+ struct timeval timeout;
+
+
+ if ((sts = __pmDecodeResult(pb, &result)) < 0)
+ return sts;
+
+ dResult = SplitResult(result);
+
+ /* Send the per-domain results to their respective agents */
+
+ __pmFD_ZERO(&waitFds);
+ for (i = 0; dResult[i]->numpmid > 0; i++) {
+ int fd;
+ ap = FindDomainAgent(((__pmID_int *)&dResult[i]->vset[0]->pmid)->domain);
+ /* If it's in a "good" list, pmID has agent that is connected */
+ assert(ap != NULL);
+
+ if (ap->ipcType == AGENT_DSO) {
+ if (ap->ipc.dso.dispatch.comm.pmda_interface >= PMDA_INTERFACE_5)
+ ap->ipc.dso.dispatch.version.four.ext->e_context = cp - client;
+ s = ap->ipc.dso.dispatch.version.any.store(dResult[i],
+ ap->ipc.dso.dispatch.version.any.ext);
+ }
+ else {
+ if (ap->status.notReady == 0) {
+ /* agent is ready for PDUs */
+ pmcd_trace(TR_XMIT_PDU, ap->inFd, PDU_RESULT, dResult[i]->numpmid);
+ s = __pmSendResult(ap->inFd, cp - client, dResult[i]);
+ if (s >= 0) {
+ ap->status.busy = 1;
+ fd = ap->outFd;
+ __pmFD_SET(fd, &waitFds);
+ if (fd > maxFd)
+ maxFd = fd;
+ nWait++;
+ }
+ else if (s == PM_ERR_IPC || sts == PM_ERR_TIMEOUT || s == -EPIPE) {
+ pmcd_trace(TR_XMIT_ERR, ap->inFd, PDU_RESULT, sts);
+ CleanupAgent(ap, AT_COMM, ap->inFd);
+ }
+ }
+ else
+ /* agent is not ready for PDUs */
+ notReady = 1;
+ }
+ if (s < 0) {
+ sts = s;
+ continue;
+ }
+ }
+
+ /* If there was no agent for one or more pmIDs, there will be a "bad list"
+ * with a negated numpmid value. Store as many "good" pmIDs as possible
+ * but remember that there were homeless ones.
+ */
+ badStore = dResult[i]->numpmid < 0;
+
+ /* Collect error PDUs containing store status from each active agent */
+
+ while (nWait > 0) {
+ __pmFD_COPY(&readyFds, &waitFds);
+ if (nWait > 1) {
+ timeout.tv_sec = _pmcd_timeout;
+ timeout.tv_usec = 0;
+
+ s = __pmSelectRead(maxFd+1, &readyFds, &timeout);
+
+ if (s == 0) {
+ __pmNotifyErr(LOG_INFO, "DoStore: select timeout");
+
+ /* Timeout, terminate agents that haven't responded */
+ for (i = 0; i < nAgents; i++) {
+ if (agent[i].status.busy) {
+ pmcd_trace(TR_RECV_TIMEOUT, agent[i].outFd, PDU_ERROR, 0);
+ CleanupAgent(&agent[i], AT_COMM, agent[i].inFd);
+ }
+ }
+ sts = PM_ERR_IPC;
+ break;
+ }
+ else if (sts < 0) {
+ /* this is not expected to happen! */
+ __pmNotifyErr(LOG_ERR, "DoStore: fatal select failure: %s\n",
+ netstrerror());
+ Shutdown();
+ exit(1);
+ }
+ }
+
+ for (i = 0; i < nAgents; i++) {
+ int pinpdu;
+ ap = &agent[i];
+ if (!ap->status.busy || !__pmFD_ISSET(ap->outFd, &readyFds))
+ continue;
+ ap->status.busy = 0;
+ __pmFD_CLR(ap->outFd, &waitFds);
+ nWait--;
+ pinpdu = s = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (s > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, s, (int)((__psint_t)pb & 0xffffffff));
+ if (s == PDU_ERROR) {
+ int ss;
+ if ((ss = __pmDecodeError(pb, &s)) < 0)
+ sts = ss;
+ else {
+ if (s < 0) {
+ extern int CheckError(AgentInfo *, int);
+
+ sts = CheckError(ap, s);
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts);
+ }
+ }
+ }
+ else {
+ /* Agent protocol error */
+ if (s < 0)
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, s);
+ else
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, s);
+ sts = PM_ERR_IPC;
+ }
+
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (ap->ipcType != AGENT_DSO &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT))
+ CleanupAgent(ap, AT_COMM, ap->outFd);
+ }
+ }
+
+ /* Only one error code can be returned, so "no agent" or "not
+ * ready" errors have precedence over all except IPC and TIMEOUT
+ * protocol failures.
+ * Note that we make only a weak effort to return the most
+ * appropriate error status because clients interested in the
+ * outcome should be using pmStore on individual metric/instances
+ * if the outcome is important. In particular, in multi-agent
+ * stores, an earlier PM_ERR_IPC error can be "overwritten" by a
+ * subsequent less serious error.
+ */
+ if (sts != PM_ERR_IPC && sts != PM_ERR_TIMEOUT) {
+ if (badStore) {
+ sts = PM_ERR_NOAGENT;
+ }
+ else if (notReady) {
+ sts = PM_ERR_AGAIN;
+ }
+ }
+
+ if (sts >= 0) {
+ /* send PDU_ERROR, even if result was 0 */
+ int s;
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, 0);
+ s = __pmSendError(cp->fd, FROM_ANON, 0);
+ if (s < 0)
+ CleanupClient(cp, s);
+ }
+
+ pmFreeResult(result);
+ i = 0;
+ do {
+ s = dResult[i]->numpmid;
+ free(dResult[i]);
+ i++;
+ } while (s); /* numpmid == 0 terminates list */
+ free(dResult);
+
+ return sts;
+}
diff --git a/src/pmcd/src/pmcd.c b/src/pmcd/src/pmcd.c
new file mode 100644
index 0000000..1bafee5
--- /dev/null
+++ b/src/pmcd/src/pmcd.c
@@ -0,0 +1,1024 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmcd.h"
+#include "impl.h"
+#include <sys/stat.h>
+#include <assert.h>
+
+#define SHUTDOWNWAIT 12 /* < PMDAs wait previously used in rc_pcp */
+#define MAXPENDING 5 /* maximum number of pending connections */
+#define FDNAMELEN 40 /* maximum length of a fd description */
+#define STRINGIFY(s) #s
+#define TO_STRING(s) STRINGIFY(s)
+
+static char *FdToString(int);
+static void ResetBadHosts(void);
+
+int AgentDied; /* for updating mapdom[] */
+static int timeToDie; /* For SIGINT handling */
+static int restart; /* For SIGHUP restart */
+static int maxReqPortFd; /* Largest request port fd */
+static char configFileName[MAXPATHLEN]; /* path to pmcd.conf */
+static char *logfile = "pmcd.log"; /* log file name */
+static int run_daemon = 1; /* run as a daemon, see -f */
+int _creds_timeout = 3; /* Timeout for agents credential PDU */
+static char *fatalfile = "/dev/tty";/* fatal messages at startup go here */
+static char *pmnsfile = PM_NS_DEFAULT;
+static char *username;
+static char *certdb; /* certificate database path (NSS) */
+static char *dbpassfile; /* certificate database password file */
+static int dupok; /* set to 1 for -N pmnsfile */
+static char sockpath[MAXPATHLEN]; /* local unix domain socket path */
+
+#ifdef HAVE_SA_SIGINFO
+static pid_t killer_pid;
+static uid_t killer_uid;
+#endif
+static int killer_sig;
+
+static void
+DontStart(void)
+{
+ FILE *tty;
+ FILE *log;
+
+ __pmNotifyErr(LOG_ERR, "pmcd not started due to errors!\n");
+
+ if ((tty = fopen(fatalfile, "w")) != NULL) {
+ fflush(stderr);
+ fprintf(tty, "NOTE: pmcd not started due to errors! ");
+ if ((log = fopen(logfile, "r")) != NULL) {
+ int c;
+ fprintf(tty, "Log file \"%s\" contains ...\n", logfile);
+ while ((c = fgetc(log)) != EOF)
+ fputc(c, tty);
+ fclose(log);
+ }
+ else
+ fprintf(tty, "Log file \"%s\" has vanished!\n", logfile);
+ fclose(tty);
+ }
+ /*
+ * We are often called after the request ports have been opened. If we don't
+ * explicitely close them, then the unix domain socket file (if any) will be
+ * left in the file system, causing "address already in use" the next time
+ * pmcd starts.
+ */
+ __pmServerCloseRequestPorts();
+
+ exit(1);
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ PMOPT_NAMESPACE,
+ PMOPT_DUPNAMES,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Service options"),
+ { "", 0, 'A', 0, "disable service advertisement" },
+ { "foreground", 0, 'f', 0, "run in the foreground" },
+ { "hostname", 1, 'H', "HOST", "set the hostname to be used for pmcd.hostname metric" },
+ { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" },
+ PMAPI_OPTIONS_HEADER("Configuration options"),
+ { "config", 1, 'c', "PATH", "path to configuration file" },
+ { "certdb", 1, 'C', "PATH", "path to NSS certificate database" },
+ { "passfile", 1, 'P', "PATH", "password file for certificate database access" },
+ { "", 1, 'L', "BYTES", "maximum size for PDUs from clients [default 65536]" },
+ { "", 1, 'q', "TIME", "PMDA initial negotiation timeout (seconds) [default 3]" },
+ { "", 1, 't', "TIME", "PMDA response timeout (seconds) [default 5]" },
+ PMAPI_OPTIONS_HEADER("Connection options"),
+ { "interface", 1, 'i', "ADDR", "accept connections on this IP address" },
+ { "port", 1, 'p', "N", "accept connections on this port" },
+ { "socket", 1, 's', "PATH", "Unix domain socket file [default $PCP_RUN_DIR/pmcd.socket]" },
+ PMAPI_OPTIONS_HEADER("Diagnostic options"),
+ { "trace", 1, 'T', "FLAG", "Event trace control" },
+ { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" },
+ { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_POSIX,
+ .short_options = "Ac:C:D:fH:i:l:L:N:n:p:P:q:s:St:T:U:x:?",
+ .long_options = longopts,
+};
+
+static void
+ParseOptions(int argc, char *argv[], int *nports)
+{
+ int c;
+ int sts;
+ char *endptr;
+ int usage = 0;
+ int val;
+
+ endptr = pmGetConfig("PCP_PMCDCONF_PATH");
+ strncpy(configFileName, endptr, sizeof(configFileName)-1);
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'A': /* disable pmcd service advertising */
+ __pmServerClearFeature(PM_SERVER_FEATURE_DISCOVERY);
+ break;
+
+ case 'c': /* configuration file */
+ strncpy(configFileName, opts.optarg, sizeof(configFileName)-1);
+ break;
+
+ case 'C': /* path to NSS certificate database */
+ certdb = opts.optarg;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ pmDebug |= sts;
+ break;
+
+ case 'f':
+ /* foreground, i.e. do _not_ run as a daemon */
+ run_daemon = 0;
+ break;
+
+ case 'i':
+ /* one (of possibly several) interfaces for client requests */
+ __pmServerAddInterface(opts.optarg);
+ break;
+
+ case 'H':
+ /* use the given name as the pmcd.hostname for this host */
+ _pmcd_hostname = opts.optarg;
+ break;
+
+ case 'l':
+ /* log file name */
+ logfile = opts.optarg;
+ break;
+
+ case 'L': /* Maximum size for PDUs from clients */
+ val = (int)strtol(opts.optarg, NULL, 0);
+ if (val <= 0) {
+ pmprintf("%s: -L requires a positive value\n", pmProgname);
+ opts.errors++;
+ } else {
+ __pmSetPDUCeiling(val);
+ }
+ break;
+
+ case 'N':
+ dupok = 1;
+ /*FALLTHROUGH*/
+ case 'n':
+ /* name space file name */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'p':
+ if (__pmServerAddPorts(opts.optarg) < 0) {
+ pmprintf("%s: -p requires a positive numeric argument (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ *nports += 1;
+ }
+ break;
+
+ case 'P': /* password file for certificate database access */
+ dbpassfile = opts.optarg;
+ break;
+
+ case 'q':
+ val = (int)strtol(opts.optarg, &endptr, 10);
+ if (*endptr != '\0' || val <= 0.0) {
+ pmprintf("%s: -q requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ _creds_timeout = val;
+ }
+ break;
+
+ case 's': /* path to local unix domain socket */
+ snprintf(sockpath, sizeof(sockpath), "%s", opts.optarg);
+ break;
+
+ case 'S': /* only allow authenticated clients */
+ __pmServerSetFeature(PM_SERVER_FEATURE_CREDS_REQD);
+ break;
+
+ case 't':
+ val = (int)strtol(opts.optarg, &endptr, 10);
+ if (*endptr != '\0' || val < 0.0) {
+ pmprintf("%s: -t requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ _pmcd_timeout = val;
+ }
+ break;
+
+ case 'T':
+ val = (int)strtol(opts.optarg, &endptr, 10);
+ if (*endptr != '\0' || val < 0) {
+ pmprintf("%s: -T requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ _pmcd_trace_mask = val;
+ }
+ break;
+
+ case 'U':
+ username = opts.optarg;
+ break;
+
+ case 'x':
+ fatalfile = opts.optarg;
+ break;
+
+ case '?':
+ usage = 1;
+ break;
+
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (usage || opts.errors || opts.optind < argc) {
+ pmUsageMessage(&opts);
+ if (usage)
+ exit(0);
+ DontStart();
+ }
+}
+
+/*
+ * Determine which clients (if any) have sent data to the server and handle it
+ * as required.
+ */
+void
+HandleClientInput(__pmFdSet *fdsPtr)
+{
+ int sts;
+ int i;
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ ClientInfo *cp;
+
+ for (i = 0; i < nClients; i++) {
+ int pinpdu;
+ if (!client[i].status.connected || !__pmFD_ISSET(client[i].fd, fdsPtr))
+ continue;
+
+ cp = &client[i];
+ this_client_id = i;
+
+ pinpdu = sts = __pmGetPDU(cp->fd, LIMIT_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0) {
+ pmcd_trace(TR_RECV_PDU, cp->fd, sts, (int)((__psint_t)pb & 0xffffffff));
+ } else {
+ CleanupClient(cp, sts);
+ continue;
+ }
+
+ php = (__pmPDUHdr *)pb;
+ if (__pmVersionIPC(cp->fd) == UNKNOWN_VERSION && php->type != PDU_CREDS) {
+ /* old V1 client protocol, no longer supported */
+ sts = PM_ERR_IPC;
+ CleanupClient(cp, sts);
+ __pmUnpinPDUBuf(pb);
+ continue;
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ ShowClients(stderr);
+
+ switch (php->type) {
+ case PDU_PROFILE:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoProfile(cp, pb);
+ break;
+
+ case PDU_FETCH:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoFetch(cp, pb);
+ break;
+
+ case PDU_INSTANCE_REQ:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoInstance(cp, pb);
+ break;
+
+ case PDU_DESC_REQ:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoDesc(cp, pb);
+ break;
+
+ case PDU_TEXT_REQ:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoText(cp, pb);
+ break;
+
+ case PDU_RESULT:
+ sts = (cp->denyOps & PMCD_OP_STORE) ?
+ PM_ERR_PERMISSION : DoStore(cp, pb);
+ break;
+
+ case PDU_PMNS_IDS:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoPMNSIDs(cp, pb);
+ break;
+
+ case PDU_PMNS_NAMES:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoPMNSNames(cp, pb);
+ break;
+
+ case PDU_PMNS_CHILD:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoPMNSChild(cp, pb);
+ break;
+
+ case PDU_PMNS_TRAVERSE:
+ sts = (cp->denyOps & PMCD_OP_FETCH) ?
+ PM_ERR_PERMISSION : DoPMNSTraverse(cp, pb);
+ break;
+
+ case PDU_CREDS:
+ sts = DoCreds(cp, pb);
+ break;
+
+ default:
+ sts = PM_ERR_IPC;
+ }
+ if (sts < 0) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "PDU: %s client[%d]: %s\n",
+ __pmPDUTypeStr(php->type), i, pmErrStr(sts));
+ /* Make sure client still alive before sending. */
+ if (cp->status.connected) {
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts);
+ sts = __pmSendError(cp->fd, FROM_ANON, sts);
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "HandleClientInput: "
+ "error sending Error PDU to client[%d] %s\n", i, pmErrStr(sts));
+ }
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+}
+
+/* Called to shutdown pmcd in an orderly manner */
+
+void
+Shutdown(void)
+{
+ int i;
+
+ for (i = 0; i < nAgents; i++) {
+ AgentInfo *ap = &agent[i];
+ if (!ap->status.connected)
+ continue;
+ if (ap->inFd != -1) {
+ if (__pmSocketIPC(ap->inFd))
+ __pmCloseSocket(ap->inFd);
+ else
+ close(ap->inFd);
+ }
+ if (ap->outFd != -1) {
+ if (__pmSocketIPC(ap->outFd))
+ __pmCloseSocket(ap->outFd);
+ else
+ close(ap->outFd);
+ }
+ if (ap->ipcType == AGENT_SOCKET &&
+ ap->ipc.socket.addrDomain == AF_UNIX) {
+ /* remove the Unix domain socket */
+ unlink(ap->ipc.socket.name);
+ }
+ }
+ if (HarvestAgents(SHUTDOWNWAIT) < 0) {
+ /* terminate with prejudice any still remaining */
+ for (i = 0; i < nAgents; i++) {
+ AgentInfo *ap = &agent[i];
+ if (ap->status.connected) {
+ pid_t pid = ap->ipcType == AGENT_SOCKET ?
+ ap->ipc.socket.agentPid : ap->ipc.pipe.agentPid;
+ __pmProcessTerminate(pid, 1);
+ }
+ }
+ }
+ for (i = 0; i < nClients; i++)
+ if (client[i].status.connected)
+ __pmCloseSocket(client[i].fd);
+ __pmServerCloseRequestPorts();
+ __pmSecureServerShutdown();
+ __pmNotifyErr(LOG_INFO, "pmcd Shutdown\n");
+ fflush(stderr);
+}
+
+static void
+SignalShutdown(void)
+{
+#ifdef HAVE_SA_SIGINFO
+#if DESPERATE
+ char buf[256];
+#endif
+ if (killer_pid != 0) {
+ __pmNotifyErr(LOG_INFO, "pmcd caught %s from pid=%" FMT_PID " uid=%d\n",
+ killer_sig == SIGINT ? "SIGINT" : "SIGTERM", killer_pid, killer_uid);
+#if DESPERATE
+ __pmNotifyErr(LOG_INFO, "Try to find process in ps output ...\n");
+ sprintf(buf, "sh -c \". \\$PCP_DIR/etc/pcp.env; ( \\$PCP_PS_PROG \\$PCP_PS_ALL_FLAGS | \\$PCP_AWK_PROG 'NR==1 {print} \\$2==%" FMT_PID " {print}' )\"", killer_pid);
+ system(buf);
+#endif
+ }
+ else {
+ __pmNotifyErr(LOG_INFO, "pmcd caught %s from unknown process\n",
+ killer_sig == SIGINT ? "SIGINT" : "SIGTERM");
+ }
+#else
+ __pmNotifyErr(LOG_INFO, "pmcd caught %s\n",
+ killer_sig == SIGINT ? "SIGINT" : "SIGTERM");
+#endif
+ Shutdown();
+ exit(0);
+}
+
+static void
+SignalRestart(void)
+{
+ time_t now;
+
+ time(&now);
+ __pmNotifyErr(LOG_INFO, "\n\npmcd RESTARTED at %s", ctime(&now));
+ fprintf(stderr, "\nCurrent PMCD clients ...\n");
+ ShowClients(stderr);
+ ResetBadHosts();
+ ParseRestartAgents(configFileName);
+}
+
+static void
+SignalReloadPMNS(void)
+{
+ int sts;
+
+ /* Reload PMNS if necessary.
+ * Note: this will only stat() the base name i.e. ASCII pmns,
+ * typically $PCP_VAR_DIR/pmns/root and not $PCP_VAR_DIR/pmns/root.bin .
+ * This is considered a very low risk problem, as the binary
+ * PMNS is always compiled from the ASCII version;
+ * when one changes so should the other.
+ * This caveat was allowed to make the code a lot simpler.
+ */
+ if (__pmHasPMNSFileChanged(pmnsfile)) {
+ __pmNotifyErr(LOG_INFO, "Reloading PMNS \"%s\"",
+ (pmnsfile==PM_NS_DEFAULT)?"DEFAULT":pmnsfile);
+ pmUnloadNameSpace();
+ if (dupok)
+ sts = pmLoadASCIINameSpace(pmnsfile, 1);
+ else
+ sts = pmLoadNameSpace(pmnsfile);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "PMNS \"%s\" load failed: %s",
+ (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile,
+ pmErrStr(sts));
+ }
+ }
+ else {
+ __pmNotifyErr(LOG_INFO, "PMNS file \"%s\" is unchanged",
+ (pmnsfile == PM_NS_DEFAULT) ? "DEFAULT" : pmnsfile);
+ }
+}
+
+/* Process I/O on file descriptors from agents that were marked as not ready
+ * to handle PDUs.
+ */
+static int
+HandleReadyAgents(__pmFdSet *readyFds)
+{
+ int i, s, sts;
+ int fd;
+ int reason;
+ int ready = 0;
+ AgentInfo *ap;
+ __pmPDU *pb;
+
+ for (i = 0; i < nAgents; i++) {
+ ap = &agent[i];
+ if (ap->status.notReady) {
+ fd = ap->outFd;
+ if (__pmFD_ISSET(fd, readyFds)) {
+ int pinpdu;
+
+ /* Expect an error PDU containing PM_ERR_PMDAREADY */
+ reason = AT_COMM; /* most errors are protocol failures */
+ pinpdu = sts = __pmGetPDU(ap->outFd, ANY_SIZE, _pmcd_timeout, &pb);
+ if (sts > 0)
+ pmcd_trace(TR_RECV_PDU, ap->outFd, sts, (int)((__psint_t)pb & 0xffffffff));
+ if (sts == PDU_ERROR) {
+ s = __pmDecodeError(pb, &sts);
+ if (s < 0) {
+ sts = s;
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts);
+ }
+ else {
+ /* sts is the status code from the error PDU */
+ if (pmDebug && DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO,
+ "%s agent (not ready) sent %s status(%d)\n",
+ ap->pmDomainLabel,
+ sts == PM_ERR_PMDAREADY ?
+ "ready" : "unknown", sts);
+ if (sts == PM_ERR_PMDAREADY) {
+ ap->status.notReady = 0;
+ sts = 1;
+ ready++;
+ }
+ else {
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_ERROR, sts);
+ sts = PM_ERR_IPC;
+ }
+ }
+ }
+ else {
+ if (sts < 0)
+ pmcd_trace(TR_RECV_ERR, ap->outFd, PDU_RESULT, sts);
+ else
+ pmcd_trace(TR_WRONG_PDU, ap->outFd, PDU_ERROR, sts);
+ sts = PM_ERR_IPC; /* Wrong PDU type */
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (ap->ipcType != AGENT_DSO && sts <= 0)
+ CleanupAgent(ap, reason, fd);
+ }
+ }
+ }
+ return ready;
+}
+
+static void
+CheckNewClient(__pmFdSet * fdset, int rfd, int family)
+{
+ int s, sts, accepted = 1;
+ __uint32_t challenge;
+ ClientInfo *cp;
+
+ if (__pmFD_ISSET(rfd, fdset)) {
+ if ((cp = AcceptNewClient(rfd)) == NULL)
+ return; /* Accept failed and no client added */
+
+ sts = __pmAccAddClient(cp->addr, &cp->denyOps);
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ if (sts >= 0 && family == AF_UNIX) {
+ if ((sts = __pmServerSetLocalCreds(cp->fd, &cp->attrs)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "ClientLoop: error extracting local credentials: %s",
+ pmErrStr(sts));
+ }
+ }
+#endif
+ if (sts >= 0) {
+ memset(&cp->pduInfo, 0, sizeof(cp->pduInfo));
+ cp->pduInfo.version = PDU_VERSION;
+ cp->pduInfo.licensed = 1;
+ if (__pmServerHasFeature(PM_SERVER_FEATURE_SECURE))
+ cp->pduInfo.features |= (PDU_FLAG_SECURE | PDU_FLAG_SECURE_ACK);
+ if (__pmServerHasFeature(PM_SERVER_FEATURE_COMPRESS))
+ cp->pduInfo.features |= PDU_FLAG_COMPRESS;
+ if (__pmServerHasFeature(PM_SERVER_FEATURE_AUTH)) /* optionally */
+ cp->pduInfo.features |= PDU_FLAG_AUTH;
+ if (__pmServerHasFeature(PM_SERVER_FEATURE_CREDS_REQD)) /* required */
+ cp->pduInfo.features |= PDU_FLAG_CREDS_REQD;
+ challenge = *(__uint32_t *)(&cp->pduInfo);
+ sts = 0;
+ }
+ else {
+ challenge = 0;
+ accepted = 0;
+ }
+
+ pmcd_trace(TR_XMIT_PDU, cp->fd, PDU_ERROR, sts);
+
+ /* reset (no meaning, use fd table to version) */
+ cp->pduInfo.version = UNKNOWN_VERSION;
+
+ s = __pmSendXtendError(cp->fd, FROM_ANON, sts, htonl(challenge));
+ if (s < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "ClientLoop: error sending Conn ACK PDU to new client %s\n",
+ pmErrStr(s));
+ if (sts >= 0)
+ /*
+ * prefer earlier failure status if any, else
+ * use the one from __pmSendXtendError()
+ */
+ sts = s;
+ accepted = 0;
+ }
+ if (!accepted)
+ CleanupClient(cp, sts);
+ }
+}
+
+/* Loop, synchronously processing requests from clients. */
+
+static void
+ClientLoop(void)
+{
+ int i, fd, sts;
+ int maxFd;
+ int checkAgents;
+ int reload_ns = 0;
+ __pmFdSet readableFds;
+
+ for (;;) {
+
+ /* Figure out which file descriptors to wait for input on. Keep
+ * track of the highest numbered descriptor for the select call.
+ */
+ readableFds = clientFds;
+ maxFd = maxClientFd + 1;
+
+ /* If an agent was not ready, it may send an ERROR PDU to indicate it
+ * is now ready. Add such agents to the list of file descriptors.
+ */
+ checkAgents = 0;
+ for (i = 0; i < nAgents; i++) {
+ AgentInfo *ap = &agent[i];
+
+ if (ap->status.notReady) {
+ fd = ap->outFd;
+ __pmFD_SET(fd, &readableFds);
+ if (fd > maxFd)
+ maxFd = fd + 1;
+ checkAgents = 1;
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO,
+ "not ready: check %s agent on fd %d (max = %d)\n",
+ ap->pmDomainLabel, fd, maxFd);
+ }
+ }
+
+ sts = __pmSelectRead(maxFd, &readableFds, NULL);
+ if (sts > 0) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ for (i = 0; i <= maxClientFd; i++)
+ if (__pmFD_ISSET(i, &readableFds))
+ fprintf(stderr, "DATA: from %s (fd %d)\n",
+ FdToString(i), i);
+ __pmServerAddNewClients(&readableFds, CheckNewClient);
+ if (checkAgents)
+ reload_ns = HandleReadyAgents(&readableFds);
+ HandleClientInput(&readableFds);
+ }
+ else if (sts == -1 && neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "ClientLoop select: %s\n", netstrerror());
+ break;
+ }
+ if (restart) {
+ restart = 0;
+ reload_ns = 1;
+ SignalRestart();
+ }
+ if (reload_ns) {
+ reload_ns = 0;
+ SignalReloadPMNS();
+ }
+ if (timeToDie) {
+ SignalShutdown();
+ break;
+ }
+ if (AgentDied) {
+ AgentDied = 0;
+ for (i = 0; i < nAgents; i++) {
+ if (!agent[i].status.connected)
+ mapdom[agent[i].pmDomainId] = nAgents;
+ }
+ }
+ }
+}
+
+#ifdef HAVE_SA_SIGINFO
+static void
+SigIntProc(int sig, siginfo_t *sip, void *x)
+{
+ killer_sig = sig;
+ if (sip != NULL) {
+ killer_pid = sip->si_pid;
+ killer_uid = sip->si_uid;
+ }
+ timeToDie = 1;
+}
+#elif IS_MINGW
+static void
+SigIntProc(int sig)
+{
+ SignalShutdown();
+}
+#else
+static void
+SigIntProc(int sig)
+{
+ killer_sig = sig;
+ signal(SIGINT, SigIntProc);
+ signal(SIGTERM, SigIntProc);
+ timeToDie = 1;
+}
+#endif
+
+#ifdef IS_MINGW
+static void
+SigHupProc(int sig)
+{
+ SignalRestart();
+ SignalReloadPMNS();
+}
+#else
+static void
+SigHupProc(int sig)
+{
+ signal(SIGHUP, SigHupProc);
+ restart = 1;
+}
+#endif
+
+static void
+SigBad(int sig)
+{
+ if (pmDebug & DBG_TRACE_DESPERATE) {
+ __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig);
+
+ /* -D desperate on the command line to enable traceback,
+ * if we have platform support for it
+ */
+ fprintf(stderr, "\nProcedure call traceback ...\n");
+ __pmDumpStack(stderr);
+ fflush(stderr);
+ }
+ _exit(sig);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int sts;
+ int nport = 0;
+ char *envstr;
+#ifdef HAVE_SA_SIGINFO
+ static struct sigaction act;
+#endif
+
+ umask(022);
+ __pmProcessDataSize(NULL);
+ __pmGetUsername(&username);
+ __pmSetInternalState(PM_STATE_PMCS);
+ __pmServerSetFeature(PM_SERVER_FEATURE_DISCOVERY);
+
+ if ((envstr = getenv("PMCD_PORT")) != NULL)
+ nport = __pmServerAddPorts(envstr);
+ ParseOptions(argc, argv, &nport);
+ if (nport == 0)
+ __pmServerAddPorts(TO_STRING(SERVER_PORT));
+
+ /* Set the local socket path. A message will be generated into the log
+ * if this fails, but it is not fatal, since other connection options
+ * may exist.
+ */
+ __pmServerSetLocalSocket(sockpath);
+
+ /* Set the service spec. This will cause our service to be advertised on
+ * the network if that is supported.
+ */
+ __pmServerSetServiceSpec(PM_SERVER_SERVICE_SPEC);
+
+ if (run_daemon) {
+ fflush(stderr);
+ StartDaemon(argc, argv);
+ }
+
+#ifdef HAVE_SA_SIGINFO
+ act.sa_sigaction = SigIntProc;
+ act.sa_flags = SA_SIGINFO;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+#else
+ __pmSetSignalHandler(SIGINT, SigIntProc);
+ __pmSetSignalHandler(SIGTERM, SigIntProc);
+#endif
+ __pmSetSignalHandler(SIGHUP, SigHupProc);
+ __pmSetSignalHandler(SIGBUS, SigBad);
+ __pmSetSignalHandler(SIGSEGV, SigBad);
+
+ if ((sts = __pmServerOpenRequestPorts(&clientFds, MAXPENDING)) < 0)
+ DontStart();
+ maxReqPortFd = maxClientFd = sts;
+
+ __pmOpenLog(pmProgname, logfile, stderr, &sts);
+ /* close old stdout, and force stdout into same stream as stderr */
+ fflush(stdout);
+ close(fileno(stdout));
+ sts = dup(fileno(stderr));
+ /* if this fails beware of the sky falling in */
+ assert(sts >= 0);
+
+ if (dupok)
+ sts = pmLoadASCIINameSpace(pmnsfile, 1);
+ else
+ sts = pmLoadNameSpace(pmnsfile);
+ if (sts < 0) {
+ fprintf(stderr, "Error: pmLoadNameSpace: %s\n", pmErrStr(sts));
+ DontStart();
+ }
+
+ if (ParseInitAgents(configFileName) < 0) {
+ /* error already reported in ParseInitAgents() */
+ DontStart();
+ }
+
+ if (nAgents <= 0) {
+ fprintf(stderr, "Error: No PMDAs found in the configuration file \"%s\"\n",
+ configFileName);
+ DontStart();
+ }
+
+ if (run_daemon) {
+ if (__pmServerCreatePIDFile(PM_SERVER_SERVICE_SPEC, PM_FATAL_ERR) < 0)
+ DontStart();
+ if (__pmSetProcessIdentity(username) < 0)
+ DontStart();
+ }
+
+ if (__pmSecureServerSetup(certdb, dbpassfile) < 0)
+ DontStart();
+
+ PrintAgentInfo(stderr);
+ __pmAccDumpLists(stderr);
+ fprintf(stderr, "\npmcd: PID = %" FMT_PID, getpid());
+ fprintf(stderr, ", PDU version = %u\n", PDU_VERSION);
+ __pmServerDumpRequestPorts(stderr);
+ fflush(stderr);
+
+ /* all the work is done here */
+ ClientLoop();
+
+ Shutdown();
+ exit(0);
+}
+
+/* The bad host list is a list of IP addresses for hosts that have had clients
+ * cleaned up because of an access violation (permission or connection limit).
+ * This is used to ensure that the message printed in PMCD's log file when a
+ * client is terminated like this only appears once per host. That stops the
+ * log from growing too large if repeated access violations occur.
+ * The list is cleared when PMCD is reconfigured.
+ */
+
+static int nBadHosts;
+static int szBadHosts;
+static __pmSockAddr **badHost;
+
+static int
+AddBadHost(struct __pmSockAddr *hostId)
+{
+ int i, need;
+
+ for (i = 0; i < nBadHosts; i++)
+ if (__pmSockAddrCompare(hostId, badHost[i]) == 0)
+ /* already there */
+ return 0;
+
+ /* allocate more entries if required */
+ if (nBadHosts == szBadHosts) {
+ szBadHosts += 8;
+ need = szBadHosts * (int)sizeof(badHost[0]);
+ if ((badHost = (__pmSockAddr **)realloc(badHost, need)) == NULL) {
+ __pmNoMem("pmcd.AddBadHost", need, PM_FATAL_ERR);
+ }
+ }
+ badHost[nBadHosts++] = __pmSockAddrDup(hostId);
+ return 1;
+}
+
+static void
+ResetBadHosts(void)
+{
+ if (szBadHosts) {
+ while (nBadHosts > 0) {
+ --nBadHosts;
+ free (badHost[nBadHosts]);
+ }
+ free(badHost);
+ }
+ nBadHosts = 0;
+ szBadHosts = 0;
+ badHost = NULL;
+}
+
+void
+CleanupClient(ClientInfo *cp, int sts)
+{
+ char *caddr;
+ int i, msg;
+ int force;
+
+ force = pmDebug & DBG_TRACE_APPL0;
+
+ if (sts != 0 || force) {
+ /* for access violations, only print the message if this host hasn't
+ * been dinged for an access violation since startup or reconfiguration
+ */
+ if (sts == PM_ERR_PERMISSION || sts == PM_ERR_CONNLIMIT) {
+ if ( (msg = AddBadHost(cp->addr)) ) {
+ caddr = __pmSockAddrToString(cp->addr);
+ fprintf(stderr, "access violation from host %s\n", caddr);
+ free(caddr);
+ }
+ }
+ else
+ msg = 0;
+
+ if (msg || force) {
+ for (i = 0; i < nClients; i++) {
+ if (cp == &client[i])
+ break;
+ }
+ fprintf(stderr, "endclient client[%d]: (fd %d) %s (%d)\n",
+ i, cp->fd, pmErrStr(sts), sts);
+ }
+ }
+
+ /* If the client is being cleaned up because its connection was refused
+ * don't do this because it hasn't actually contributed to the connection
+ * count
+ */
+ if (sts != PM_ERR_PERMISSION && sts != PM_ERR_CONNLIMIT)
+ __pmAccDelClient(cp->addr);
+
+ pmcd_trace(TR_DEL_CLIENT, cp->fd, sts, 0);
+ DeleteClient(cp);
+
+ if (maxClientFd < maxReqPortFd)
+ maxClientFd = maxReqPortFd;
+
+ for (i = 0; i < nAgents; i++)
+ if (agent[i].profClient == cp)
+ agent[i].profClient = NULL;
+}
+
+/* Convert a file descriptor to a string describing what it is for. */
+static char *
+FdToString(int fd)
+{
+ static char fdStr[FDNAMELEN];
+ static char *stdFds[4] = {"*UNKNOWN FD*", "stdin", "stdout", "stderr"};
+ int i;
+
+ if (fd >= -1 && fd < 3)
+ return stdFds[fd + 1];
+ if (__pmServerRequestPortString(fd, fdStr, FDNAMELEN) != NULL)
+ return fdStr;
+ for (i = 0; i < nClients; i++)
+ if (client[i].status.connected) {
+ if (fd == client[i].fd) {
+ sprintf(fdStr, "client[%d] input socket", i);
+ return fdStr;
+ }
+ }
+ for (i = 0; i < nAgents; i++)
+ if (agent[i].status.connected) {
+ if (fd == agent[i].inFd) {
+ sprintf(fdStr, "agent[%d] input", i);
+ return fdStr;
+ }
+ else if (fd == agent[i].outFd) {
+ sprintf(fdStr, "agent[%d] output", i);
+ return fdStr;
+ }
+ }
+ return stdFds[0];
+}
diff --git a/src/pmcd/src/pmcd.h b/src/pmcd/src/pmcd.h
new file mode 100644
index 0000000..ba8be5e
--- /dev/null
+++ b/src/pmcd/src/pmcd.h
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _PMCD_H
+#define _PMCD_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#ifdef IS_MINGW
+#ifdef PMCD_INTERNAL
+#define PMCD_INTERN __declspec(dllexport)
+#define PMCD_EXTERN
+#else
+#define PMCD_INTERN
+#define PMCD_EXTERN __declspec(dllimport)
+#endif
+#else /*!MINGW*/
+#define PMCD_INTERN
+#define PMCD_EXTERN extern
+#endif
+
+#include "client.h"
+
+/* Structures of type-specific info for each kind of domain agent-PMCD
+ * connection (DSO, socket, pipe).
+ */
+
+typedef void (*DsoInitPtr)(pmdaInterface*);
+
+typedef struct {
+ char *pathName; /* Where the DSO lives */
+ int xlatePath; /* translated pathname? */
+ char *entryPoint; /* Name of the entry point */
+ void *dlHandle; /* Handle for DSO */
+ DsoInitPtr initFn; /* Function to initialise DSO */
+ /* and return dispatch table */
+ pmdaInterface dispatch; /* Dispatch table for dso agent */
+} DsoInfo;
+
+typedef struct {
+ int addrDomain; /* AF_UNIX, AF_INET or AF_INET6 */
+ int port; /* Port number if an INET socket */
+ char *name; /* Port name if supplied for INET */
+ /* or socket name for UNIX */
+ char *commandLine; /* Optional command to start agent */
+ char* *argv; /* Arg list built from commandLine */
+ pid_t agentPid; /* Process ID of agent if PMCD started */
+} SocketInfo;
+
+typedef struct {
+ char* commandLine; /* Command line to use for child */
+ char* *argv; /* Arg list built from command line */
+ pid_t agentPid; /* Process ID of the agent */
+} PipeInfo;
+
+/* The agent table and its size. */
+
+typedef struct {
+ int pmDomainId; /* PMD identifier */
+ int ipcType; /* DSO, socket or pipe */
+ int pduVersion; /* PDU_VERSION for this agent */
+ int inFd, outFd; /* For input to/output from agent */
+ int done; /* Set when processed for this Fetch */
+ ClientInfo *profClient; /* Last client to send profile to agent */
+ int profIndex; /* Index of profile that client sent */
+ char *pmDomainLabel; /* Textual label for agent's PMD */
+ struct { /* Status of agent */
+ unsigned int
+ connected : 1, /* Agent connected to pmcd */
+ busy : 1, /* Processing a request */
+ isChild : 1, /* Is a child process of pmcd */
+ madeDsoResult : 1, /* Pmcd made a "bad" pmResult (DSO only) */
+ restartKeep : 1, /* Keep agent if set during restart */
+ notReady : 1, /* Agent not ready to process PDUs */
+ startNotReady : 1, /* Agent starts in non-ready state */
+ unused : 9, /* Zero-padded, unused space */
+ flags : 16; /* Agent-supplied connection flags */
+ } status;
+ int reason; /* if ! connected */
+ union { /* per-ipcType info */
+ DsoInfo dso;
+ SocketInfo socket;
+ PipeInfo pipe;
+ } ipc;
+} AgentInfo;
+
+PMCD_EXTERN AgentInfo *agent; /* Array of domain agent structs */
+PMCD_EXTERN int nAgents; /* Number of agents in array */
+
+/*
+ * DomainId-to-AgentIndex map
+ * 9 bits of DomainId, max value is 510 because 511 is special (see
+ * DYNAMIC_PMID in impl.h)
+ */
+#define MAXDOMID 510
+extern int mapdom[]; /* the map */
+
+/* Domain agent-PMCD connection types (AgentInfo.ipcType) */
+
+#define AGENT_DSO 0
+#define AGENT_SOCKET 1
+#define AGENT_PIPE 2
+
+/* Masks for operations used in access controls for clients. */
+#define PMCD_OP_FETCH 0x1
+#define PMCD_OP_STORE 0x2
+
+#define PMCD_OP_NONE 0x0
+#define PMCD_OP_ALL 0x3
+
+/* Agent termination reasons */
+#define AT_CONFIG 1
+#define AT_COMM 2
+#define AT_EXIT 3
+
+/*
+ * Agent termination reasons for "reason" in AgentInfo, and pmcd.agent.state
+ * as exported by PMCD PMDA ... these encode the low byte, next byte contains
+ * exit status and next byte encodes signal
+ */
+ /* 0 connected */
+ /* 1 connected, not ready */
+#define REASON_EXIT 2
+#define REASON_NOSTART 4
+#define REASON_PROTOCOL 8
+
+extern AgentInfo *FindDomainAgent(int);
+extern void CleanupAgent(AgentInfo *, int, int);
+extern int HarvestAgents(unsigned int);
+
+/* timeout to PMDAs (secs) */
+PMCD_EXTERN int _pmcd_timeout;
+
+/* timeout for credentials */
+extern int _creds_timeout;
+
+/* global PMCD PMDA variables */
+
+/*
+ * trace types
+ */
+
+#define TR_ADD_CLIENT 1
+#define TR_DEL_CLIENT 2
+#define TR_ADD_AGENT 3
+#define TR_DEL_AGENT 4
+#define TR_EOF 5
+#define TR_XMIT_PDU 7
+#define TR_RECV_PDU 8
+#define TR_WRONG_PDU 9
+#define TR_XMIT_ERR 10
+#define TR_RECV_TIMEOUT 11
+#define TR_RECV_ERR 12
+
+/*
+ * trace control
+ */
+PMCD_EXTERN int _pmcd_trace_mask;
+PMCD_EXTERN int _pmcd_trace_nbufs;
+
+/*
+ * trace mask bits
+ */
+#define TR_MASK_CONN 1
+#define TR_MASK_PDU 2
+#define TR_MASK_NOBUF 256
+
+/*
+ * routines
+ */
+extern void pmcd_init_trace(int);
+extern void pmcd_trace(int, int, int, int);
+extern void pmcd_dump_trace(FILE *);
+extern int pmcd_load_libpcp_pmda(void);
+
+/*
+ * PDU handling routines
+ */
+extern int DoFetch(ClientInfo *, __pmPDU *);
+extern int DoProfile(ClientInfo *, __pmPDU *);
+extern int DoDesc(ClientInfo *, __pmPDU *);
+extern int DoInstance(ClientInfo *, __pmPDU *);
+extern int DoText(ClientInfo *, __pmPDU *);
+extern int DoStore(ClientInfo *, __pmPDU *);
+extern int DoCreds(ClientInfo *, __pmPDU *);
+extern int DoPMNSIDs(ClientInfo *, __pmPDU *);
+extern int DoPMNSNames(ClientInfo *, __pmPDU *);
+extern int DoPMNSChild(ClientInfo *, __pmPDU *);
+extern int DoPMNSTraverse(ClientInfo *, __pmPDU *);
+
+/*
+ * General purpose routines
+ */
+extern void StartDaemon(int, char **);
+extern void Shutdown(void);
+extern int ParseInitAgents(char *);
+extern void ParseRestartAgents(char *);
+extern void PrintAgentInfo(FILE *);
+extern void MarkStateChanges(int);
+extern void CleanupClient(ClientInfo *, int);
+extern int ClientsAuthentication(AgentInfo *);
+extern int AgentsAuthentication(int);
+extern pmResult **SplitResult(pmResult *);
+
+/*
+ * Highest known file descriptor used for a Client or an Agent connection.
+ * This is reported in the pmcd.openfds metric.
+ */
+PMCD_EXTERN int pmcd_hi_openfds;
+extern void pmcd_openfds_sethi(int fd);
+
+/* Explicitly requested hostname (pmcd.hostname metric) */
+PMCD_EXTERN char *_pmcd_hostname;
+
+#endif /* _PMCD_H */
diff --git a/src/pmcd/src/util.c b/src/pmcd/src/util.c
new file mode 100644
index 0000000..094d400
--- /dev/null
+++ b/src/pmcd/src/util.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmcd.h"
+
+#ifdef IS_MINGW
+
+void
+StartDaemon(int argc, char **argv)
+{
+ PROCESS_INFORMATION piProcInfo;
+ STARTUPINFO siStartInfo;
+ LPTSTR cmdline = NULL;
+ int i, sz = 3; /* -f\0 */
+
+ for (i = 0; i < argc; i++)
+ sz += strlen(argv[i]) + 1;
+ if ((cmdline = malloc(sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "StartDaemon: no memory");
+ exit(1);
+ }
+ for (sz = i = 0; i < argc; i++)
+ sz += sprintf(cmdline, "%s ", argv[i]);
+ sprintf(cmdline + sz, "-f");
+
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+ siStartInfo.cb = sizeof(STARTUPINFO);
+
+ if (0 == CreateProcess(
+ NULL, cmdline,
+ NULL, NULL, /* process and thread attributes */
+ FALSE, /* inherit handles */
+ CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS,
+ NULL, /* environment (from parent) */
+ NULL, /* current directory */
+ &siStartInfo, /* STARTUPINFO pointer */
+ &piProcInfo)) { /* receives PROCESS_INFORMATION */
+ __pmNotifyErr(LOG_ERR, "StartDaemon: CreateProcess");
+ /* but keep going */
+ }
+ else {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+}
+
+#else
+
+/* Based on Stevens (Unix Network Programming, p.83) */
+void
+StartDaemon(int argc, char **argv)
+{
+ pid_t childpid;
+
+ (void)argc; (void)argv;
+
+#if defined(HAVE_TERMIO_SIGNALS)
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+#endif
+
+ if ((childpid = fork()) < 0)
+ __pmNotifyErr(LOG_ERR, "StartDaemon: fork");
+ /* but keep going */
+ else if (childpid > 0) {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+
+ /* not a process group leader, lose controlling tty */
+ if (setsid() == -1)
+ __pmNotifyErr(LOG_WARNING, "StartDaemon: setsid");
+ /* but keep going */
+
+ close(0);
+ /* don't close other fd's -- we know that only good ones are open! */
+
+ /* don't chdir("/") -- we still need to open pmcd.log */
+}
+#endif
diff --git a/src/pmcd_wait/GNUmakefile b/src/pmcd_wait/GNUmakefile
new file mode 100644
index 0000000..49b2636
--- /dev/null
+++ b/src/pmcd_wait/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmcd_wait.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmcd_wait$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmcd_wait/pmcd_wait.c b/src/pmcd_wait/pmcd_wait.c
new file mode 100644
index 0000000..0dd2234
--- /dev/null
+++ b/src/pmcd_wait/pmcd_wait.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1998 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <limits.h>
+#include "pmapi.h"
+#include "impl.h"
+
+/* possible exit states */
+#define EXIT_STS_SUCCESS 0
+#define EXIT_STS_USAGE 1
+#define EXIT_STS_TIMEOUT 2
+#define EXIT_STS_UNIXERR 3
+#define EXIT_STS_PCPERR 4
+
+static char *hostname;
+static long delta = 60;
+static int verbose;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ { "host", 1, 'h', "HOST", "wait for PMCD on host" },
+ { "interval", 1, 't', "TIME", "maximum interval to wait for PMCD [default 60 seconds]" },
+ { "verbose", 0, 'v', 0, "turn on output messages" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:h:t:v?",
+ .long_options = longopts,
+};
+
+void
+PrintTimeout(void)
+{
+ if (verbose) {
+ fprintf(stderr, "%s: Failed to connect to PMCD on host \"%s\""
+ " in %ld seconds\n",
+ pmProgname, hostname, delta);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts;
+ char env[256];
+ long delta_count;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'v':
+ verbose = 1;
+ break;
+ }
+ }
+
+ if (opts.optind < argc) {
+ pmprintf("%s: Too many arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.interval.tv_sec != 0) {
+ delta = opts.interval.tv_sec;
+ if (delta <= 0) {
+ pmprintf("%s: -t argument must be at least 1 second\n",
+ pmProgname);
+ opts.errors++;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(EXIT_STS_USAGE);
+ }
+
+ if (opts.nhosts == 0)
+ hostname = "local:";
+ else
+ hostname = opts.hosts[0];
+
+ sts = sprintf(env, "PMCD_CONNECT_TIMEOUT=%ld", delta);
+ if (sts < 0) {
+ if (verbose) {
+ fprintf(stderr, "%s: Failed to create env string: %s\n",
+ pmProgname, osstrerror());
+ }
+ exit(EXIT_STS_UNIXERR);
+ }
+ sts = putenv(env);
+ if (sts != 0) {
+ if (verbose) {
+ fprintf(stderr, "%s: Failed to set PMCD_CONNECT_TIMEOUT: %s\n",
+ pmProgname, osstrerror());
+ }
+ exit(EXIT_STS_UNIXERR);
+ }
+
+ delta_count = delta;
+ for(;;) {
+ sts = pmNewContext(PM_CONTEXT_HOST, hostname);
+
+ if (sts >= 0) {
+ (void)pmDestroyContext(sts);
+ exit(EXIT_STS_SUCCESS);
+ }
+ if (sts == -ECONNREFUSED || sts == -ENOENT || sts == PM_ERR_IPC) {
+ static const struct timeval onesec = { 1, 0 };
+
+ delta_count--;
+ if (delta_count < 0) {
+ PrintTimeout();
+ exit(EXIT_STS_TIMEOUT);
+ }
+ __pmtimevalSleep(onesec);
+ }
+ else if (sts == PM_ERR_TIMEOUT) {
+ PrintTimeout();
+ exit(EXIT_STS_TIMEOUT);
+ }
+ else {
+ if (verbose) {
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, hostname, pmErrStr(sts));
+ }
+ if (sts > PM_ERR_BASE)
+ exit(EXIT_STS_UNIXERR);
+ else
+ exit(EXIT_STS_PCPERR);
+
+ }
+ }
+}
diff --git a/src/pmchart/GNUmakefile b/src/pmchart/GNUmakefile
new file mode 100644
index 0000000..e3fdef9
--- /dev/null
+++ b/src/pmchart/GNUmakefile
@@ -0,0 +1,90 @@
+TOPDIR = ../..
+COMMAND = pmchart
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+WRAPPER = $(COMMAND).sh
+QRCFILE = $(COMMAND).qrc
+RCFILE = $(COMMAND).rc
+ICOFILE = $(COMMAND).ico
+ICNFILE = $(COMMAND).icns
+XMLFILE = $(COMMAND).info
+DESKTOP = $(COMMAND).desktop
+UIFILES = $(shell echo *.ui)
+HEADERS = aboutdialog.h chartdialog.h exportdialog.h hostdialog.h \
+ infodialog.h pmchart.h openviewdialog.h saveviewdialog.h \
+ recorddialog.h seealsodialog.h searchdialog.h settingsdialog.h \
+ samplesdialog.h tabdialog.h tab.h tabwidget.h \
+ chart.h console.h main.h namespace.h \
+ colorbutton.h colorscheme.h qcolorpicker.h \
+ statusbar.h timeaxis.h timecontrol.h \
+ groupcontrol.h gadget.h sampling.h tracing.h
+SOURCES = $(HEADERS:.h=.cpp) view.cpp
+LSRCFILES = $(QRCFILE) $(RCFILE) $(UIFILES) $(HEADERS) $(SOURCES) \
+ $(PROJECT) $(DESKTOP) $(WRAPPER).in $(XMLFILE).in
+LDIRT = $(COMMAND) $(ICONLINKS) $(WRAPPER) $(XMLFILE) images
+
+SUBDIRS = views
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me:: images wrappers
+ $(QTMAKE)
+ $(LNMAKE)
+
+build-me:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+ifeq ($(WINDOW),mac)
+PKG_MAC_DIR = /Applications/$(COMMAND).app/Contents
+wrappers: $(WRAPPER) $(XMLFILE)
+else
+wrappers:
+endif
+
+$(WRAPPER): $(WRAPPER).in
+ $(SED) -e '/\# .*/b' -e 's;PKG_MAC_DIR;$(PKG_MAC_DIR);g' < $< > $@
+$(XMLFILE): $(XMLFILE).in
+ $(SED) -e 's;PACKAGE_VERSION;$(PACKAGE_VERSION);g' < $< > $@
+
+install: default $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ifeq ($(WINDOW),win)
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+endif
+ifeq ($(WINDOW),x11)
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+ $(INSTALL) -m 755 -d $(PCP_DESKTOP_DIR)
+ $(INSTALL) -m 644 $(DESKTOP) $(PCP_DESKTOP_DIR)/$(DESKTOP)
+endif
+ifeq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(WRAPPER) $(PCP_BIN_DIR)/$(COMMAND)
+ $(call INSTALL_DIRECTORY_HIERARCHY,$(PKG_MAC_DIR),/Applications)
+ $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo
+ $(INSTALL) -m 644 $(XMLFILE) $(PKG_MAC_DIR)/Info.plist
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS
+ $(call INSTALL_QT_FRAMEWORKS,$(BINARY))
+ $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND)
+ rm $(BINARY)
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources
+ $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE)
+ $(call INSTALL_QT_RESOURCES,$(PKG_MAC_DIR)/Resources)
+endif
+
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+images: $(ICNFILE)
+ $(LN_S) $(TOPDIR)/images images
+
+$(ICNFILE):
+ $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE)
diff --git a/src/pmchart/aboutdialog.cpp b/src/pmchart/aboutdialog.cpp
new file mode 100644
index 0000000..37de391
--- /dev/null
+++ b/src/pmchart/aboutdialog.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "aboutdialog.h"
+#include <pcp/pmapi.h>
+
+AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ QRegExp rx("\\b(VERSION)\\b");
+ QString version = versionTextLabel->text();
+ version.replace(rx, pmGetConfig("PCP_VERSION"));
+ versionTextLabel->setText(version);
+}
+
+void AboutDialog::aboutOKButton_clicked()
+{
+ done(0);
+}
diff --git a/src/pmchart/aboutdialog.h b/src/pmchart/aboutdialog.h
new file mode 100644
index 0000000..0f8bd81
--- /dev/null
+++ b/src/pmchart/aboutdialog.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef ABOUTDIALOG_H
+#define ABOUTDIALOG_H
+
+#include "ui_aboutdialog.h"
+
+class AboutDialog : public QDialog, public Ui::AboutDialog
+{
+ Q_OBJECT
+
+public:
+ AboutDialog(QWidget* parent);
+
+public slots:
+ virtual void aboutOKButton_clicked();
+};
+
+#endif // ABOUTDIALOG_H
diff --git a/src/pmchart/aboutdialog.ui b/src/pmchart/aboutdialog.ui
new file mode 100644
index 0000000..04dc413
--- /dev/null
+++ b/src/pmchart/aboutdialog.ui
@@ -0,0 +1,229 @@
+<ui version="4.0" >
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>330</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>330</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>330</width>
+ <height>260</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>About</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/pmchart.png</iconset>
+ </property>
+ <property name="modal" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="aboutPixmapLabel" >
+ <property name="pixmap" >
+ <pixmap resource="pmchart.qrc" >:/images/pmchart.png</pixmap>
+ </property>
+ <property name="scaledContents" >
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="versionTextLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>&lt;b>&lt;i>pmchart&lt;/i>&lt;/b>&lt;br>
+&lt;b>&lt;i>Version VERSION&lt;/i>&lt;/b></string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="authorsTextLabel" >
+ <property name="midLineWidth" >
+ <number>0</number>
+ </property>
+ <property name="text" >
+ <string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
+p, li { white-space: pre-wrap; }
+&lt;/style>&lt;/head>&lt;body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
+&lt;p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Copyright 2012, Red Hat.&lt;br />Copyright 2012-2014, Nathan Scott.&lt;br />Copyright 2006, Ken McDonell.&lt;br />Copyright 2006-2010, Aconex.&lt;br />All rights reserved.&lt;br />&lt;span style=" font-style:italic;">&lt;br />This program is licensed under the&lt;br />GNU General Public License.&lt;br />&lt;/span>&lt;/p>&lt;/body>&lt;/html></string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="aboutOKButton" >
+ <property name="focusPolicy" >
+ <enum>Qt::TabFocus</enum>
+ </property>
+ <property name="text" >
+ <string>OK</string>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>aboutOKButton</sender>
+ <signal>clicked()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>aboutOKButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/chart.cpp b/src/pmchart/chart.cpp
new file mode 100644
index 0000000..106ae48
--- /dev/null
+++ b/src/pmchart/chart.cpp
@@ -0,0 +1,1058 @@
+/*
+ * Copyright (c) 2012-2014, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ * Copyright (c) 2006-2010, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "tracing.h"
+#include "sampling.h"
+#include "saveviewdialog.h"
+
+#include <QtCore/QPoint>
+#include <QtCore/QRegExp>
+#include <QtGui/QApplication>
+#include <QtGui/QWhatsThis>
+#include <QtGui/QCursor>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_picker.h>
+#include <qwt_plot_renderer.h>
+#include <qwt_legend_item.h>
+#include <qwt_scale_widget.h>
+
+#define DESPERATE 0
+
+Chart::Chart(Tab *chartTab, QWidget *parent) : QwtPlot(parent), Gadget(this)
+{
+ my.tab = chartTab;
+ my.title = QString::null;
+ my.style = NoStyle;
+ my.scheme = QString::null;
+ my.sequence = 0;
+
+ setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+ plotLayout()->setCanvasMargin(0);
+ plotLayout()->setAlignCanvasToScales(true);
+ plotLayout()->setFixedAxisOffset(54, QwtPlot::yLeft);
+ enableAxis(xBottom, false);
+
+ // prepare initial engine dealing with (no) chart type
+ my.engine = new ChartEngine(this);
+
+ // setup the legend (all charts must have one)
+ setLegendVisible(true);
+ QwtPlot::legend()->contentsWidget()->setFont(*globalFont);
+ connect(this, SIGNAL(legendChecked(QwtPlotItem *, bool)),
+ SLOT(legendChecked(QwtPlotItem *, bool)));
+
+ // setup a picker (all charts must have one)
+ my.picker = new ChartPicker(canvas());
+ connect(my.picker, SIGNAL(activated(bool)),
+ SLOT(activated(bool)));
+ connect(my.picker, SIGNAL(selected(const QPolygon &)),
+ SLOT(selected(const QPolygon &)));
+ connect(my.picker, SIGNAL(selected(const QPointF &)),
+ SLOT(selected(const QPointF &)));
+ connect(my.picker, SIGNAL(moved(const QPointF &)),
+ SLOT(moved(const QPointF &)));
+
+ // feedback into the group about any selection
+ connect(this, SIGNAL(timeSelectionActive(Gadget *, int)),
+ my.tab->group(), SLOT(timeSelectionActive(Gadget *, int)));
+ connect(this, SIGNAL(timeSelectionReactive(Gadget *, int)),
+ my.tab->group(), SLOT(timeSelectionReactive(Gadget *, int)));
+ connect(this, SIGNAL(timeSelectionInactive(Gadget *)),
+ my.tab->group(), SLOT(timeSelectionInactive(Gadget *)));
+
+ console->post("Chart::ctor complete(%p)", this);
+}
+
+Chart::~Chart()
+{
+ console->post("Chart::~Chart() for chart %p", this);
+
+ for (int i = 0; i < my.items.size(); i++)
+ delete my.items[i];
+ delete my.engine;
+ delete my.picker;
+}
+
+ChartEngine::ChartEngine(Chart *chart)
+{
+ my.rateConvert = true;
+ my.antiAliasing = true;
+ chart->setAutoReplot(false);
+ chart->setCanvasBackground(globalSettings.chartBackground);
+ chart->setAxisFont(QwtPlot::yLeft, *globalFont);
+ chart->setAxisAutoScale(QwtPlot::yLeft);
+}
+
+ChartItem::ChartItem(QmcMetric *mp,
+ pmMetricSpec *msp, pmDesc *dp, const QString &legend)
+{
+ my.metric = mp;
+ my.units = dp->units;
+ my.name = shortHostName().append(':').append(QString(msp->metric));
+ if (msp->ninst == 1) {
+ my.name.append("[").append(msp->inst[0]).append("]");
+ my.inst = QString(msp->inst[0]);
+ } else {
+ my.inst = QString::null;
+ }
+ setLegend(legend);
+ my.removed = false;
+ my.hidden = false;
+}
+
+//
+// Build the legend label string, even if the chart is declared
+// "legend off" so that subsequent Edit->Chart Title and Legend
+// changes can turn the legend on and off dynamically.
+//
+// NB: "legend" is not expanded (wrt %h, %i, etc), "label" is.
+//
+void
+ChartItem::setLegend(const QString &legend)
+{
+ my.legend = legend;
+ if (legend != QString::null)
+ expandLegendLabel(legend);
+ else
+ clearLegendLabel();
+}
+
+void
+ChartItem::updateLegend()
+{
+ // drive any change to label into QwtLegendItem
+ item()->setTitle(my.label);
+}
+
+void
+ChartItem::expandLegendLabel(const QString &legend)
+{
+ bool expandMetricShort = legend.contains("%m");
+ bool expandMetricLong = legend.contains("%M");
+ bool expandInstShort = legend.contains("%i");
+ bool expandInstLong = legend.contains("%I");
+ bool expandHostShort = legend.contains("%h");
+ bool expandHostLong = legend.contains("%H");
+
+ my.label = legend;
+ if (expandMetricShort)
+ my.label.replace(QRegExp("%m"), shortMetricName());
+ if (expandMetricLong)
+ my.label.replace(QRegExp("%M"), my.metric->name());
+ if (expandInstShort)
+ my.label.replace(QRegExp("%i"), shortInstName());
+ if (expandInstLong)
+ my.label.replace(QRegExp("%I"), my.inst);
+ if (expandHostShort)
+ my.label.replace(QRegExp("%h"), shortHostName());
+ if (expandHostLong)
+ my.label.replace(QRegExp("%H"), hostname());
+}
+
+QString
+ChartItem::hostname(void) const
+{
+ return my.metric->context()->source().context_hostname();
+
+}
+
+QString
+ChartItem::shortHostName(void) const
+{
+ QString hostName = hostname();
+ int index;
+
+ if ((index = hostName.indexOf(QChar('.'))) != -1) {
+ // no change if it looks even vaguely like an IP address
+ if (!hostName.contains(QRegExp("^\\d+\\.")) && // IPv4
+ !hostName.contains(QChar(':'))) // IPv6
+ hostName.truncate(index);
+ }
+ return hostName;
+}
+
+QString
+ChartItem::shortMetricName(void) const
+{
+ QString shortName = my.metric->name();
+ int count, index;
+
+ // heuristic: use final two PMNS components (e.g. dev.bytes)
+ // reason: short, often this is enough to differentiate well
+ while ((count = shortName.count(QChar('.'))) > 1) {
+ index = shortName.indexOf(QChar('.'));
+ shortName.remove(0, index + 1);
+ }
+ return shortName;
+}
+
+QString
+ChartItem::shortInstName(void) const
+{
+ QString shortName = my.inst;
+ int index;
+
+ // heuristic: cull from the first space onward
+ // reason: this is required to be unique from PMDAs
+ if ((index = shortName.indexOf(QChar(' '))) != -1) {
+ shortName.truncate(index);
+ }
+ return shortName;
+}
+
+void
+ChartItem::clearLegendLabel(void)
+{
+ //
+ // No legend has been explicitly requested - but something
+ // must be displayed. Use metric name with optional [inst].
+ // If that goes beyond a limit, truncate from the front and
+ // use ellipses to indicate this has happened.
+ //
+ if (my.name.size() > PmChart::maximumLegendLength()) {
+ int size = PmChart::maximumLegendLength() - 3;
+ my.label = QString("...");
+ my.label.append(my.name.right(size));
+ } else {
+ my.label = my.name;
+ }
+}
+
+
+void
+Chart::setCurrent(bool enable)
+{
+ QwtScaleWidget *sp;
+ QPalette palette;
+ QwtText t;
+
+ console->post("Chart::setCurrent(%p) %s", this, enable ? "true" : "false");
+
+ // (Re)set title and y-axis highlight for new/old current chart.
+ // For title, have to set both QwtText and QwtTextLabel because of
+ // the way attributes are cached and restored when printing charts
+
+ t = titleLabel()->text();
+ t.setColor(enable ? globalSettings.chartHighlight : "black");
+ setTitle(t);
+ palette = titleLabel()->palette();
+ palette.setColor(QPalette::Active, QPalette::Text,
+ enable ? globalSettings.chartHighlight : QColor("black"));
+ titleLabel()->setPalette(palette);
+
+ sp = axisWidget(QwtPlot::yLeft);
+ t = sp->title();
+ t.setColor(enable ? globalSettings.chartHighlight : "black");
+ sp->setTitle(t);
+}
+
+void
+Chart::preserveSample(int index, int oldindex)
+{
+#if DESPERATE
+ console->post("Chart::preserveSample %d <- %d (%d items)",
+ index, oldindex, my.items.size());
+#endif
+ for (int i = 0; i < my.items.size(); i++)
+ my.items[i]->preserveSample(index, oldindex);
+}
+
+void
+Chart::punchoutSample(int index)
+{
+#if DESPERATE
+ console->post("Chart::punchoutSample %d (%d items)",
+ index, my.items.size());
+#endif
+ for (int i = 0; i < my.items.size(); i++)
+ my.items[i]->punchoutSample(index);
+}
+
+void
+Chart::adjustValues(void)
+{
+ my.engine->replot();
+ replot();
+}
+
+void
+Chart::updateValues(bool forward, bool visible, int size, int points,
+ double left, double right, double delta)
+{
+#if DESPERATE
+ console->post(PmChart::DebugForce,
+ "Chart::updateValues(forward=%d,visible=%d) sz=%d pts=%d (%d items)",
+ forward, visible, size, points, my.items.size());
+#endif
+
+ if (visible) {
+ double scale = pmchart->timeAxis()->scaleValue(delta, points);
+ setAxisScale(QwtPlot::xBottom, left, right, scale);
+ }
+
+ if (my.items.size() > 0)
+ my.engine->updateValues(forward, size, points, left, right, delta);
+
+ if (visible) {
+ replot(); // done first so Value Axis range is updated
+ my.engine->redoScale();
+ }
+}
+
+void
+Chart::replot()
+{
+ my.engine->replot();
+ QwtPlot::replot();
+}
+
+void
+Chart::legendChecked(QwtPlotItem *item, bool down)
+{
+#if DESPERATE
+ console->post(PmChart::DebugForce, "Chart::legendChecked %s for item %p",
+ down? "down":"up", item);
+#endif
+
+ // find matching item and update hidden status if required
+ bool changed = false;
+ for (int i = 0; i < my.items.size(); i++) {
+ if (my.items[i]->item() != item)
+ continue;
+ // if the state is changing, note it and update
+ if (my.items[i]->hidden() != down) {
+ my.items[i]->setHidden(down);
+ changed = true;
+ }
+ break;
+ }
+
+ if (changed) {
+ item->setVisible(down == false);
+ replot();
+ }
+}
+
+//
+// Add a new chart item (metric, usually with a specific instance)
+//
+int
+Chart::addItem(pmMetricSpec *msp, const QString &legend)
+{
+ console->post("Chart::addItem src=%s", msp->source);
+ if (msp->ninst == 0)
+ console->post("addItem metric=%s", msp->metric);
+ else
+ console->post("addItem instance %s[%s]", msp->metric, msp->inst[0]);
+
+ QmcMetric *mp = my.tab->group()->addMetric(msp, 0.0, true);
+ if (mp->status() < 0)
+ return mp->status();
+
+ pmDesc desc = mp->desc().desc();
+ if (my.items.size() == 0) {
+ // first plot item, setup a new ChartEngine
+ ChartEngine *engine =
+ (desc.type == PM_TYPE_EVENT ||
+ desc.type == PM_TYPE_HIGHRES_EVENT) ?
+ (ChartEngine *) new TracingEngine(this) :
+ (ChartEngine *) new SamplingEngine(this, desc);
+ delete my.engine;
+ my.engine = engine;
+ }
+ else if (!my.engine->isCompatible(desc)) {
+ // not compatible with existing metrics, fail
+ return PM_ERR_CONV;
+ }
+
+ // Finally, request the engine allocate a new chart item,
+ // set prevailing chart style and default color, show it.
+ //
+ ChartItem *item = my.engine->addItem(mp, msp, &desc, legend);
+ setStroke(item, my.style, nextColor(my.scheme, &my.sequence));
+ item->setVisible(true);
+ replot();
+
+ my.items.append(item);
+ console->post("addItem %p nitems=%d", item, my.items.size());
+
+ changeTitle(title(), true); // regenerate %h and/or %H expansion
+ return my.items.size() - 1;
+}
+
+bool
+Chart::activeMetric(int index) const
+{
+ return activeItem(index);
+}
+
+bool
+Chart::activeItem(int index) const
+{
+ return (my.items[index]->removed() == false);
+}
+
+void
+Chart::removeItem(int index)
+{
+ my.items[index]->remove();
+ changeTitle(title(), true); // regenerate %h and/or %H expansion
+}
+
+void
+Chart::reviveItem(int index)
+{
+ my.items[index]->revive();
+ changeTitle(title(), true); // regenerate %h and/or %H expansion
+}
+
+void
+Chart::resetValues(int samples, double left, double right)
+{
+ for (int i = 0; i < my.items.size(); i++)
+ my.items[i]->resetValues(samples, left, right);
+ replot();
+}
+
+int
+Chart::metricCount() const
+{
+ return my.items.size();
+}
+
+QString
+Chart::title()
+{
+ return my.title;
+}
+
+void
+Chart::resetTitleFont(void)
+{
+ QwtText t = titleLabel()->text();
+ t.setFont(*globalFont);
+ setTitle(t);
+ // have to set font for both QwtText and QwtTextLabel because of
+ // the way attributes are cached and restored when printing charts
+ QFont titleFont = *globalFont;
+ titleFont.setBold(true);
+ titleLabel()->setFont(titleFont);
+}
+
+void
+Chart::resetFont(void)
+{
+ QwtPlot::legend()->contentsWidget()->setFont(*globalFont);
+ setAxisFont(QwtPlot::yLeft, *globalFont);
+ resetTitleFont();
+}
+
+QString
+Chart::hostNameString(bool shortened)
+{
+ int dot = -1;
+ QSet<QString> hostNameSet;
+ QList<ChartItem*>::Iterator item;
+
+ // iterate across this chart's items, not activeGroup
+ for (item = my.items.begin(); item != my.items.end(); ++item) {
+ // NB: ... but we don't get notified of direct calls to
+ // ChartItem::setRemoved().
+ if ((*item)->removed())
+ continue;
+
+ // QString host = (*item)->metricContext()->source().host();
+ // ... but .host() is a possibly-munged of the pmchart -h STRING
+ // argument, not the actual host name. So get the data source's
+ // self-declared host name. This string will not have pmproxy @
+ // stuff, or pcp://....&attr=... miscellanea.
+ QString hostName = (*item)->metricContext()->source().context_hostname();
+
+ // decide whether or not to truncate this hostname
+ if (shortened)
+ dot = hostName.indexOf(QChar('.'));
+ if (dot != -1) {
+ // no change if it looks even vaguely like an IP address
+ if (!hostName.contains(QRegExp("^\\d+\\.")) && // IPv4
+ !hostName.contains(QChar(':'))) // IPv6
+ hostName.truncate(dot);
+ }
+ hostNameSet.insert(hostName);
+ }
+
+ // extract the duplicate-eliminated host names
+ QSet<QString>::Iterator qsi;
+ QString names;
+ for (qsi = hostNameSet.begin(); qsi != hostNameSet.end(); qsi++) {
+ if (names != "")
+ names += ",";
+ names += (*qsi);
+ }
+ return names;
+}
+
+//
+// If expand is true then expand %h or %H to host name in rendered title label
+//
+void
+Chart::changeTitle(QString title, bool expand)
+{
+ bool hadTitle = (my.title != QString::null);
+ bool expandHostShort = title.contains("%h");
+ bool expandHostLong = title.contains("%H");
+
+ my.title = title; // copy into QString
+
+ if (my.title != QString::null) {
+ if (!hadTitle)
+ pmchart->updateHeight(titleLabel()->height());
+ resetTitleFont();
+ if (expand && (expandHostShort || expandHostLong)) {
+ if (expandHostShort)
+ title.replace(QRegExp("%h"), hostNameString(true));
+ if (expandHostLong)
+ title.replace(QRegExp("%H"), hostNameString(false));
+ setTitle(title);
+ // NB: my.title retains the %h and/or %H
+ }
+ else
+ setTitle(my.title);
+ }
+ else {
+ if (hadTitle)
+ pmchart->updateHeight(-(titleLabel()->height()));
+ setTitle(NULL);
+ }
+}
+
+QString
+Chart::scheme() const
+{
+ return my.scheme;
+}
+
+void
+Chart::setScheme(QString scheme)
+{
+ my.sequence = 0;
+ my.scheme = scheme;
+}
+
+void
+Chart::setScheme(QString scheme, int sequence)
+{
+ my.sequence = sequence;
+ my.scheme = scheme;
+}
+
+Chart::Style
+Chart::style()
+{
+ return my.style;
+}
+
+void
+Chart::setStyle(Style style)
+{
+ my.style = style;
+}
+
+void
+Chart::setStroke(int index, Style style, QColor color)
+{
+ setStroke(my.items[index], style, color);
+}
+
+void
+Chart::setStroke(ChartItem *item, Style style, QColor color)
+{
+ item->setColor(color);
+ item->setStroke(style, color, my.engine->antiAliasing());
+
+ if (style != my.style) {
+ my.engine->setStyle(my.style);
+ my.style = style;
+ adjustValues();
+ }
+}
+
+QColor
+Chart::color(int index)
+{
+ if (index >= 0 && index < my.items.size())
+ return my.items[index]->color();
+ return QColor("white");
+}
+
+void
+Chart::setLabel(int index, const QString &s)
+{
+ if (index >= 0 && index < my.items.size()) {
+ ChartItem *item = my.items[index];
+ item->setLegend(s);
+ item->updateLegend();
+ }
+}
+
+void
+Chart::scale(bool *autoScale, double *yMin, double *yMax)
+{
+ my.engine->scale(autoScale, yMin, yMax);
+}
+
+bool
+Chart::autoScale(void)
+{
+ return my.engine->autoScale();
+}
+
+void
+Chart::setScale(bool autoScale, double yMin, double yMax)
+{
+ my.engine->setScale(autoScale, yMin, yMax);
+ replot();
+ my.engine->redoScale();
+}
+
+bool
+Chart::rateConvert()
+{
+ return my.engine->rateConvert();
+}
+
+void
+Chart::setRateConvert(bool enabled)
+{
+ my.engine->setRateConvert(enabled);
+}
+
+bool
+Chart::antiAliasing()
+{
+ return my.engine->antiAliasing();
+}
+
+void
+Chart::setAntiAliasing(bool enabled)
+{
+ my.engine->setAntiAliasing(enabled);
+}
+
+QString
+Chart::YAxisTitle(void) const
+{
+ return axisTitle(QwtPlot::yLeft).text();
+}
+
+void
+Chart::setYAxisTitle(const char *p)
+{
+ QwtText *t;
+ bool enable = (my.tab->currentGadget() == this);
+
+ if (!p || *p == '\0')
+ t = new QwtText(" "); // for y-axis alignment (space is invisible)
+ else
+ t = new QwtText(p);
+ t->setFont(*globalFont);
+ t->setColor(enable ? globalSettings.chartHighlight : "black");
+ setAxisTitle(QwtPlot::yLeft, *t);
+}
+
+void
+Chart::activated(bool on)
+{
+ if (on)
+ Q_EMIT timeSelectionActive(this,
+ canvas()->mapFromGlobal(QCursor::pos()).x());
+ else
+ Q_EMIT timeSelectionInactive(this);
+}
+
+void
+Chart::selected(const QPolygon &poly)
+{
+ my.engine->selected(poly);
+ my.tab->setCurrent(this);
+}
+
+void
+Chart::selected(const QPointF &p)
+{
+ showPoint(p);
+ my.tab->setCurrent(this);
+}
+
+void
+Chart::moved(const QPointF &p)
+{
+ Q_EMIT timeSelectionReactive(this,
+ canvas()->mapFromGlobal(QCursor::pos()).x());
+ my.engine->moved(p);
+}
+
+
+void
+Chart::showPoint(const QPointF &p)
+{
+ ChartItem *selected = NULL;
+ double dist, distance = 10e10;
+ int index = -1;
+
+ // pixel point
+ QPoint pp = my.picker->transform(p);
+
+ console->post("Chart::showPoint p=%.2f,%.2f pixel=%d,%d",
+ p.x(), p.y(), pp.x(), pp.y());
+
+ // seek the closest curve to the point selected
+ for (int i = 0; i < my.items.size(); i++) {
+ QwtPlotCurve *curve = my.items[i]->curve();
+ int point = curve->closestPoint(pp, &dist);
+
+ if (dist < distance) {
+ index = point;
+ distance = dist;
+ selected = my.items[i];
+ }
+ }
+
+ // clear existing selections then show this one
+ bool update = (index >= 0 && pp.y() >= 0);
+ for (int i = 0; i < my.items.size(); i++) {
+ ChartItem *item = my.items[i];
+
+ item->clearCursor();
+ if (update && item == selected)
+ item->updateCursor(p, index);
+ }
+}
+
+void
+Chart::activateTime(QMouseEvent *event)
+{
+ bool block = signalsBlocked();
+ blockSignals(true);
+ my.picker->widgetMousePressEvent(event);
+ blockSignals(block);
+}
+
+void
+Chart::reactivateTime(QMouseEvent *event)
+{
+ bool block = signalsBlocked();
+ blockSignals(true);
+ my.picker->widgetMouseMoveEvent(event);
+ blockSignals(block);
+}
+
+void
+Chart::deactivateTime(QMouseEvent *event)
+{
+ bool block = signalsBlocked();
+ blockSignals(true);
+ my.picker->widgetMouseReleaseEvent(event);
+ blockSignals(block);
+}
+
+void
+Chart::showPoints(const QPolygon &poly)
+{
+ Q_ASSERT(poly.size() == 2);
+
+ console->post("Chart::showPoints: %d,%d -> %d,%d",
+ poly.at(0).x(), poly.at(0).y(), poly.at(1).x(), poly.at(1).y());
+
+ // Transform selected (pixel) points to our coordinate system
+ QRectF cp = my.picker->invTransform(poly.boundingRect());
+ const QPointF &p = cp.topLeft();
+
+ //
+ // If a single-point selected, use showPoint instead
+ // (this uses proximity checking, not bounding box).
+ //
+ if (cp.width() == 0 && cp.height() == 0) {
+ showPoint(p);
+ }
+ else {
+ // clear existing selections, find and show new ones
+ for (int i = 0; i < my.items.size(); i++) {
+ ChartItem *item = my.items[i];
+ int itemDataSize = item->curve()->dataSize();
+
+ item->clearCursor();
+ for (int index = 0; index < itemDataSize; index++)
+ if (item->containsPoint(cp, index))
+ item->updateCursor(p, index);
+ }
+ }
+
+ showInfo();
+}
+
+//
+// give feedback (popup) about the selection
+//
+void
+Chart::showInfo(void)
+{
+ QString info = QString::null;
+
+ pmchart->timeout(); // clear status bar
+ for (int i = 0; i < my.items.size(); i++) {
+ ChartItem *item = my.items[i];
+ if (info != QString::null)
+ info.append("\n");
+ info.append(item->cursorInfo());
+ }
+
+ while (!info.isEmpty() && (info.at(info.length()-1) == '\n'))
+ info.chop(1);
+
+ if (!info.isEmpty())
+ QWhatsThis::showText(QCursor::pos(), info, this);
+ else
+ QWhatsThis::hideText();
+}
+
+bool
+Chart::legendVisible()
+{
+ // Legend is on or off for all items, only need to test the first item
+ if (my.items.size() > 0)
+ return QwtPlot::legend() != NULL;
+ return false;
+}
+
+// Use Edit->Chart Title and Legend to enable/disable the legend.
+// Clicking on individual legend buttons will hide/show the
+// corresponding item.
+//
+void
+Chart::setLegendVisible(bool on)
+{
+ QwtLegend *legend = QwtPlot::legend();
+
+ if (on) {
+ if (legend == NULL) { // currently disabled, enable it
+ legend = new QwtLegend;
+
+ legend->setItemMode(QwtLegend::CheckableItem);
+ insertLegend(legend, QwtPlot::BottomLegend);
+ // force each Legend item to "checked" state matching
+ // the initial plotting state
+ for (int i = 0; i < my.items.size(); i++)
+ my.items[i]->item()->setVisible(my.items[i]->removed());
+ replot();
+ }
+ }
+ else {
+ if (legend != NULL) {
+ // currently enabled, disable it
+ insertLegend(NULL, QwtPlot::BottomLegend);
+
+ // delete legend;
+ // WISHLIST: this can cause a core dump - needs investigating
+ // [memleak]. Really, all of the legend code needs reworking.
+ }
+ }
+}
+
+void
+Chart::save(FILE *f, bool hostDynamic)
+{
+ SaveViewDialog::saveChart(f, this, hostDynamic);
+}
+
+void
+Chart::print(QPainter *qp, QRect &rect, bool transparent)
+{
+ QwtPlotRenderer renderer;
+
+ if (transparent)
+ renderer.setDiscardFlag(QwtPlotRenderer::DiscardBackground);
+ renderer.render(this, qp, rect);
+}
+
+QString Chart::name(int index) const
+{
+ return my.items[index]->name();
+}
+
+QString Chart::legend(int index) const
+{
+ return my.items[index]->legend();
+}
+
+QmcDesc *Chart::metricDesc(int index) const
+{
+ return my.items[index]->metricDesc();
+}
+
+QString Chart::metricName(int index) const
+{
+ return my.items[index]->metricName();
+}
+
+QString Chart::metricInstance(int index) const
+{
+ return my.items[index]->metricInstance();
+}
+
+QmcContext *Chart::metricContext(int index) const
+{
+ return my.items[index]->metricContext();
+}
+
+QmcMetric *Chart::metric(int index) const
+{
+ return my.items[index]->metric();
+}
+
+QSize Chart::minimumSizeHint() const
+{
+ return QSize(10,10);
+}
+
+QSize Chart::sizeHint() const
+{
+ return QSize(150,100);
+}
+
+void
+Chart::setupTree(QTreeWidget *tree)
+{
+ for (int i = 0; i < my.items.size(); i++) {
+ ChartItem *item = my.items[i];
+
+ if (item->removed())
+ continue;
+ addToTree(tree, item->name(),
+ item->metricContext(), item->metricHasInstances(),
+ item->color(), item->legend());
+ }
+}
+
+void
+Chart::addToTree(QTreeWidget *treeview, const QString &metric,
+ const QmcContext *context, bool isInst, QColor color,
+ const QString &label)
+{
+ QRegExp regexInstance("\\[(.*)\\]$");
+ QRegExp regexNameNode("\\.");
+ QString source = context->source().source();
+ QString inst, name = metric;
+ QStringList namelist;
+ int depth, index;
+
+ console->post("Chart::addToTree src=%s metric=%s, isInst=%d",
+ (const char *)source.toAscii(), (const char *)metric.toAscii(),
+ isInst);
+
+ depth = name.indexOf(regexInstance);
+ if (depth > 0) {
+ inst = name.mid(depth+1); // after '['
+ inst.chop(1); // final ']'
+ name = name.mid(0, depth); // prior '['
+ }
+ // note: hostname removal must be done *after* instance removal
+ // and must take into consideration IPv4/IPv6 address types too
+ index = name.lastIndexOf(QChar(':'));
+ if (index > 0)
+ name = name.remove(0, index+1);
+
+ namelist = name.split(regexNameNode);
+ namelist.prepend(source); // add the host/archive root as well.
+ if (depth > 0)
+ namelist.append(inst);
+ depth = namelist.size();
+
+ // Walk through each component of this name, creating them in the
+ // target tree (if not there already), right down to the leaf.
+
+ NameSpace *tree = (NameSpace *)treeview->invisibleRootItem();
+ NameSpace *item = NULL;
+
+ for (int b = 0; b < depth; b++) {
+ QString text = namelist.at(b);
+ bool foundMatchingName = false;
+ for (int i = 0; i < tree->childCount(); i++) {
+ item = (NameSpace *)tree->child(i);
+ if (text == item->text(0)) {
+ // No insert at this level necessary, move down a level
+ tree = item;
+ foundMatchingName = true;
+ break;
+ }
+ }
+
+ // When no more children and no match so far, we create & insert
+ if (foundMatchingName == false) {
+ NameSpace *n;
+ if (b == 0) {
+ n = new NameSpace(treeview, context);
+ n->expand();
+ n->setExpanded(true, true);
+ n->setSelectable(false);
+ }
+ else {
+ bool isLeaf = (b == depth-1);
+ n = new NameSpace(tree, text, isLeaf && isInst);
+ if (isLeaf) {
+ n->setLabel(label);
+ n->setOriginalColor(color);
+ n->setCurrentColor(color, NULL);
+ }
+ n->expand();
+ n->setExpanded(!isLeaf, true);
+ n->setSelectable(isLeaf);
+ if (!isLeaf)
+ n->setType(NameSpace::NonLeafName);
+ else if (isInst) // Constructor sets Instance type
+ tree->setType(NameSpace::LeafWithIndom);
+ else
+ n->setType(NameSpace::LeafNullIndom);
+ }
+ tree = n;
+ }
+ }
+}
+
+
+//
+// Override behaviour from QwtPlotCurve legend rendering
+// Gives us fine-grained control over the colour that we
+// display in the legend boxes for each ChartItem.
+//
+void
+ChartCurve::drawLegendIdentifier(QPainter *painter, const QRectF &rect) const
+{
+ if (rect.isEmpty())
+ return;
+
+ QRectF r(0, 0, rect.width()-1, rect.height()-1);
+ r.moveCenter(rect.center());
+
+ QPen pen(QColor(Qt::black));
+ pen.setCapStyle(Qt::FlatCap);
+ QBrush brush(legendColor, Qt::SolidPattern);
+
+ painter->setPen(pen);
+ painter->setBrush(brush);
+ painter->setRenderHint(QPainter::Antialiasing, false);
+ painter->drawRect(r.x(), r.y(), r.width(), r.height());
+}
diff --git a/src/pmchart/chart.h b/src/pmchart/chart.h
new file mode 100644
index 0000000..f0ddc8e
--- /dev/null
+++ b/src/pmchart/chart.h
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2012-2014, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ * Copyright (c) 2006-2010, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef CHART_H
+#define CHART_H
+
+#include <QtCore/QString>
+#include <QtCore/QDateTime>
+#include <QtGui/QColor>
+#include <QtGui/QTreeWidget>
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_picker.h>
+#include <qmc_metric.h>
+#include "gadget.h"
+
+class Tab;
+class ChartItem;
+class ChartPicker;
+class ChartEngine;
+class TracingEngine;
+class SamplingEngine;
+
+//
+// Centre of the pmchart universe
+//
+class Chart : public QwtPlot, public Gadget
+{
+ Q_OBJECT
+
+public:
+ Chart(Tab *, QWidget *);
+ ~Chart(void);
+
+ typedef enum {
+ NoStyle,
+ LineStyle,
+ BarStyle,
+ StackStyle,
+ AreaStyle,
+ UtilisationStyle,
+ EventStyle
+ } Style;
+
+ virtual void resetFont();
+ virtual void setCurrent(bool);
+ virtual QString scheme() const; // return chart color scheme
+ virtual void setScheme(QString); // set the chart color scheme
+
+ int addItem(pmMetricSpec *, const QString &);
+ bool activeItem(int) const;
+ void removeItem(int);
+ void reviveItem(int);
+
+ QString title(void); // return copy of chart title
+ void changeTitle(QString, bool); // QString::null to clear; expand?
+ QString hostNameString(bool); // short/long host names as qstring
+
+ Style style(void); // return chart style
+ void setStyle(Style); // set default chart plot style
+
+ QColor color(int); // return color for ith plot
+ static QColor schemeColor(QString, int *);
+ void setStroke(int, Style, QColor); // set chart style and color
+ void setScheme(QString, int); // set the chart scheme and position
+
+ int sequence() // return chart color scheme position
+ { return my.sequence; }
+ void setSequence(int sequence) // set the chart color scheme position
+ { my.sequence = sequence; }
+
+ QString label(int); // return legend label for ith plot
+ void setLabel(int, const QString &); // set plot legend label
+
+ bool autoScale(void);
+ void scale(bool *, double *, double *);
+ // return autoscale state and fixed scale parameters
+ void setScale(bool, double, double);
+ // set autoscale state and fixed scale parameters
+ QString YAxisTitle() const;
+ void setYAxisTitle(const char *);
+ bool legendVisible();
+ void setLegendVisible(bool);
+ bool rateConvert();
+ void setRateConvert(bool);
+ bool antiAliasing();
+ void setAntiAliasing(bool);
+
+ virtual void save(FILE *, bool);
+ virtual void print(QPainter *, QRect &, bool);
+
+ virtual void updateValues(bool, bool, int, int, double, double, double);
+ virtual void resetValues(int, double, double);
+ virtual void adjustValues();
+
+ virtual void preserveSample(int, int);
+ virtual void punchoutSample(int);
+
+ virtual int metricCount() const;
+ virtual bool activeMetric(int) const;
+ virtual QString name(int) const;
+ virtual QString legend(int) const;
+ virtual QmcMetric *metric(int) const;
+ virtual QString metricName(int) const;
+ virtual QmcDesc *metricDesc(int) const;
+ virtual QString metricInstance(int) const;
+ virtual QmcContext *metricContext(int) const;
+
+ virtual QSize sizeHint() const;
+ virtual QSize minimumSizeHint() const;
+
+ void setupTree(QTreeWidget *);
+ void addToTree(QTreeWidget *, const QString &, const QmcContext *,
+ bool, QColor, const QString &);
+
+ void activateTime(QMouseEvent *);
+ void reactivateTime(QMouseEvent *);
+ void deactivateTime(QMouseEvent *);
+
+Q_SIGNALS:
+ void timeSelectionActive(Gadget *, int);
+ void timeSelectionReactive(Gadget *, int);
+ void timeSelectionInactive(Gadget *);
+
+public Q_SLOTS:
+ void replot(void);
+
+private Q_SLOTS:
+ void activated(bool);
+ void selected(const QPolygon &);
+ void selected(const QPointF &);
+ void moved(const QPointF &);
+ void legendChecked(QwtPlotItem *, bool);
+
+private:
+ // changing properties
+ void setStroke(ChartItem *, Style, QColor);
+ void resetTitleFont(void);
+
+ // handling selection
+ void showInfo(void);
+ void showPoint(const QPointF &);
+ void showPoints(const QPolygon &);
+
+ struct {
+ Tab *tab;
+ QList<ChartItem *> items;
+
+ QString title;
+ QString scheme;
+ int sequence;
+ Style style;
+
+ ChartEngine *engine;
+ ChartPicker *picker;
+ } my;
+
+ friend class TracingEngine;
+ friend class SamplingEngine;
+};
+
+//
+// Wrapper class that simply allows us to call the mouse event handlers
+// for the picker class. Helps implement the time axis picker extender.
+//
+class ChartPicker : public QwtPlotPicker
+{
+public:
+ ChartPicker(QwtPlotCanvas *canvas) :
+ QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft,
+ QwtPicker::CrossRubberBand, QwtPicker::AlwaysOff, canvas) { }
+
+ void widgetMousePressEvent(QMouseEvent *event)
+ { QwtPlotPicker::widgetMousePressEvent(event); }
+ void widgetMouseReleaseEvent(QMouseEvent *event)
+ { QwtPlotPicker::widgetMouseReleaseEvent(event); }
+ void widgetMouseMoveEvent(QMouseEvent *event)
+ { QwtPlotPicker::widgetMouseMoveEvent(event); }
+};
+
+//
+// Abstraction for differences between event tracing and sampling models
+// Note that this base class is used for an initially empty chart
+//
+class ChartEngine
+{
+public:
+ ChartEngine() { }
+ ChartEngine(Chart *chart);
+ virtual ~ChartEngine() {}
+
+ // test whether a new metric (type and units) would be compatible
+ // with this engine and any metrics already plotted in the chart.
+ virtual bool isCompatible(pmDesc &) { return true; }
+
+ // insert a new item (plot curve) into a chart
+ virtual ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &)
+ { return NULL; } // cannot add to an empty engine
+
+ // indicates movement forward/backward occurred
+ virtual void updateValues(bool, int, int, double, double, double) { }
+
+ // indicates the Y-axis scale needs updating
+ virtual bool autoScale(void) { return false; }
+ virtual void redoScale(void) { }
+ virtual void setScale(bool, double, double) { }
+ virtual void scale(bool *autoScale, double *yMin, double *yMax)
+ { *autoScale = false; *yMin = 0.0; *yMax = 1.0; }
+
+ // get/set other attributes of the chart
+ virtual bool rateConvert(void) const { return my.rateConvert; }
+ virtual void setRateConvert(bool enabled) { my.rateConvert = enabled; }
+ virtual bool antiAliasing(void) const { return my.antiAliasing; }
+ virtual void setAntiAliasing(bool enabled) { my.antiAliasing = enabled; }
+ virtual void setStyle(Chart::Style) { }
+
+ // prepare for chart replot() being called
+ virtual void replot(void) { }
+
+ // a selection has been made/changed, handle it
+ virtual void selected(const QPolygon &) { }
+ virtual void moved(const QPointF &) { }
+
+private:
+ struct {
+ bool rateConvert;
+ bool antiAliasing;
+ } my;
+};
+
+//
+// Helper dealing with overriding of legend behaviour
+//
+class ChartCurve : public QwtPlotCurve
+{
+public:
+ ChartCurve(const QString &title)
+ : QwtPlotCurve(title), legendColor(Qt::white) { }
+
+ virtual void drawLegendIdentifier(QPainter *painter,
+ const QRectF &rect ) const;
+ void setLegendColor(QColor color) { legendColor = color; }
+ QColor legendColor;
+};
+
+//
+// Container for an individual plot item within a chart,
+// which is always backed by a single metric.
+//
+class ChartItem
+{
+public:
+ ChartItem() { }
+ ChartItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &);
+ virtual ~ChartItem(void) { }
+
+ virtual QwtPlotItem *item(void) = 0;
+ virtual QwtPlotCurve *curve(void) = 0;
+
+ virtual void preserveSample(int, int) = 0;
+ virtual void punchoutSample(int) = 0;
+ virtual void resetValues(int, double, double) = 0;
+
+ virtual void clearCursor() = 0;
+ virtual bool containsPoint(const QRectF &, int) = 0;
+ virtual void updateCursor(const QPointF &, int) = 0;
+ virtual const QString &cursorInfo() = 0;
+
+ virtual void setVisible(bool on) { item()->setVisible(on); }
+ virtual void setStroke(Chart::Style, QColor, bool) = 0;
+ virtual void revive(void) = 0;
+ virtual void remove(void) = 0;
+
+ QString name(void) const { return my.name; }
+ QString label(void) const { return my.label; } // as displayed, expanded
+ QString legend(void) const { return my.legend; } // no %i/%h/.. expansion
+ QString metricName(void) const { return my.metric->name(); }
+ QString metricInstance(void) const
+ { return my.metric->numInst() > 0 ? my.metric->instName(0) : QString::null; }
+ bool metricHasInstances(void) const { return my.metric->hasInstances(); }
+ QmcDesc *metricDesc(void) const { return (QmcDesc *)&my.metric->desc(); }
+ QmcContext *metricContext(void) const { return my.metric->context(); }
+ QmcMetric *metric(void) const { return my.metric; }
+ QColor color(void) const { return my.color; }
+
+ void setColor(QColor color) { my.color = color; }
+ void setLegend(const QString &legend); // may include wildcards
+ void updateLegend();
+
+ bool hidden(void) { return my.hidden; }
+ void setHidden(bool hidden) { my.hidden = hidden; }
+
+ bool removed(void) { return my.removed; }
+ void setRemoved(bool removed) { my.removed = removed; }
+
+protected:
+ struct {
+ QmcMetric *metric;
+ pmUnits units; // base units, *not* scaled
+
+ QString name;
+ QString inst;
+ QString legend; // may contain wildcards (not expanded)
+ QString label; // as appears visibly, in plot legend
+ QColor color;
+
+ bool removed;
+ bool hidden; // true if hidden through legend push button
+ } my;
+
+private:
+ void expandLegendLabel(const QString &legend);
+ void clearLegendLabel(void);
+
+ QString hostname(void) const;
+ QString shortHostName(void) const;
+ QString shortMetricName(void) const;
+ QString shortInstName(void) const;
+};
+
+#endif // CHART_H
diff --git a/src/pmchart/chartdialog.cpp b/src/pmchart/chartdialog.cpp
new file mode 100644
index 0000000..d42b1d4
--- /dev/null
+++ b/src/pmchart/chartdialog.cpp
@@ -0,0 +1,916 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui/QHeaderView>
+#include <QtGui/QFileDialog>
+#include <QtGui/QMessageBox>
+#include "chartdialog.h"
+#include "qed_colorpicker.h"
+#include "hostdialog.h"
+#include "chart.h"
+#include "tab.h"
+#include "main.h"
+
+ChartDialog::ChartDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ init();
+}
+
+void ChartDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void ChartDialog::init()
+{
+ my.rateConvert = true;
+ my.chartTreeSelected = false;
+ my.availableTreeSelected = false;
+ my.chartTreeSingleSelected = NULL;
+ my.availableTreeSingleSelected = NULL;
+ connect(chartMetricsTreeWidget, SIGNAL(itemSelectionChanged()),
+ this, SLOT(chartMetricsItemSelectionChanged()));
+ connect(availableMetricsTreeWidget, SIGNAL(itemSelectionChanged()),
+ this, SLOT(availableMetricsItemSelectionChanged()));
+ connect(availableMetricsTreeWidget,
+ SIGNAL(itemExpanded(QTreeWidgetItem *)), this,
+ SLOT(availableMetricsItemExpanded(QTreeWidgetItem *)));
+
+ my.currentColor = qRgb( -1, -1, -1 );
+ hEd->setRange(0, 359);
+
+ connect(hEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd()));
+ connect(sEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd()));
+ connect(vEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd()));
+ connect(rEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd()));
+ connect(gEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd()));
+ connect(bEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd()));
+
+ connect(applyColorLabel,
+ SIGNAL(colorDropped(QRgb)), this, SIGNAL(newCol(QRgb)));
+ connect(applyColorLabel,
+ SIGNAL(colorDropped(QRgb)), this, SLOT(setRgb(QRgb)));
+ connect(colorPicker,
+ SIGNAL(newCol(int,int)), luminancePicker, SLOT(setCol(int,int)));
+ connect(luminancePicker,
+ SIGNAL(newHsv(int,int,int)), this, SLOT(newHsv(int,int,int)));
+ connect(colorLineEdit,
+ SIGNAL(newColor(QColor)), this, SLOT(newColor(QColor)));
+ connect(this,
+ SIGNAL(newCol(QRgb)), this, SLOT(newColorTypedIn(QRgb)));
+}
+
+void ChartDialog::reset()
+{
+ my.chart = NULL;
+
+ setWindowTitle(tr("New Chart"));
+ tabWidget->setCurrentIndex(1);
+ chartMetricsTreeWidget->clear();
+ titleLineEdit->setText(tr(""));
+ typeComboBox->setCurrentIndex((int)Chart::LineStyle - 1);
+ legendOn->setChecked(true);
+ legendOff->setChecked(false);
+ antiAliasingOn->setChecked(false);
+ antiAliasingOff->setChecked(false);
+ antiAliasingAuto->setChecked(true);
+ rateConvertCheckBox->setChecked(true);
+ rateConvertCheckBox->setEnabled(true);
+ setCurrentScheme(QString::null);
+ my.sequence = 0;
+
+ resetCompletely();
+ enableUi();
+}
+
+void ChartDialog::reset(Chart *cp)
+{
+ bool yAutoScale;
+ double yMin, yMax;
+
+ my.chart = cp;
+
+ setWindowTitle(tr("Edit Chart"));
+ tabWidget->setCurrentIndex(0);
+ setupChartMetricsTree();
+ titleLineEdit->setText(cp->title());
+ typeComboBox->setCurrentIndex(cp->style() - 1);
+ legendOn->setChecked(cp->legendVisible());
+ legendOff->setChecked(!cp->legendVisible());
+ antiAliasingOn->setChecked(cp->antiAliasing());
+ antiAliasingOff->setChecked(!cp->antiAliasing());
+ antiAliasingAuto->setChecked(false);
+ my.rateConvert = cp->rateConvert();
+ rateConvertCheckBox->setChecked(my.rateConvert);
+ rateConvertCheckBox->setEnabled(false);
+ cp->scale(&yAutoScale, &yMin, &yMax);
+ setScale(yAutoScale, yMin, yMax);
+ setScheme(cp->scheme(), cp->sequence());
+ setupSchemeComboBox();
+
+ resetCompletely();
+ enableUi();
+}
+
+void ChartDialog::resetPartially(Chart *cp)
+{
+ my.chart = cp;
+
+ setWindowTitle(tr("Edit Chart"));
+ setupChartMetricsTree();
+ enableUi();
+}
+
+//
+// Code paths common to both Create/Edit dialog uses
+//
+void ChartDialog::resetCompletely()
+{
+ if ((my.archiveSource = pmchart->isArchiveTab()) == true) {
+ sourceButton->setToolTip(tr("Add archives"));
+ sourceButton->setIcon(QIcon(":/images/archive.png"));
+ }
+ else {
+ sourceButton->setToolTip(tr("Add a host"));
+ sourceButton->setIcon(QIcon(":/images/computer.png"));
+ }
+ setupAvailableMetricsTree(my.archiveSource == true);
+ my.yMin = yAxisMinimum->value();
+ my.yMax = yAxisMaximum->value();
+ my.chartTreeSelected = false;
+ my.availableTreeSelected = false;
+ my.chartTreeSingleSelected = NULL;
+ my.availableTreeSingleSelected = NULL;
+}
+
+void ChartDialog::enableUi()
+{
+ bool selfScaling = autoScaleOff->isChecked();
+ minTextLabel->setEnabled(selfScaling);
+ maxTextLabel->setEnabled(selfScaling);
+ yAxisMinimum->setEnabled(selfScaling);
+ yAxisMaximum->setEnabled(selfScaling);
+
+ chartMetricLineEdit->setText(my.chartTreeSingleSelected ?
+ ((NameSpace *)my.chartTreeSingleSelected)->metricName() : tr(""));
+ availableMetricLineEdit->setText(my.availableTreeSingleSelected ?
+ ((NameSpace *)my.availableTreeSingleSelected)->metricName() : tr(""));
+ metricInfoButton->setEnabled( // there can be only one source
+ (my.availableTreeSingleSelected && !my.chartTreeSingleSelected) ||
+ (!my.availableTreeSingleSelected && my.chartTreeSingleSelected));
+ metricDeleteButton->setEnabled(my.chartTreeSelected);
+ metricAddButton->setEnabled(my.availableTreeSelected);
+ metricSearchButton->setEnabled(true);
+
+ revertColorButton->setEnabled(my.chartTreeSingleSelected != NULL);
+ applyColorButton->setEnabled(my.chartTreeSingleSelected != NULL);
+ plotLabelLineEdit->setEnabled(my.chartTreeSingleSelected != NULL);
+ if (my.chartTreeSingleSelected != NULL) {
+ NameSpace *n = (NameSpace *)my.chartTreeSingleSelected;
+ revertColorLabel->setColor(n->originalColor());
+ setCurrentColor(n->currentColor().rgb());
+ plotLabelLineEdit->setText(n->label());
+ }
+ else {
+ revertColorLabel->setColor(QColor(0xff, 0xff, 0xff));
+ setCurrentColor(QColor(0x00, 0x00, 0x00).rgb());
+ plotLabelLineEdit->setText("");
+ }
+}
+
+//
+// Verify user input and don't dismiss dialog (OK) if problems found.
+// Needs to handle many cases: New Chart (!my.chart) and Edit Chart,
+// as well as Apply and OK.
+//
+bool ChartDialog::validate(QString &message, int &index)
+{
+ bool validInput;
+
+ // Check some plots have been selected.
+ if (!my.chart && chartMetricsTreeWidget->topLevelItemCount() == 0 &&
+ my.availableTreeSelected == false) {
+ message = tr("No metrics have been selected for plotting.\n");
+ validInput = false;
+ index = 1;
+ }
+ // Validate Values Axis scale range if not auto-scaling
+ else if (autoScaleOn->isChecked() == false && my.yMin >= my.yMax) {
+ message = tr("Values Axis scale minimum/maximum range is invalid.");
+ validInput = false;
+ index = 0;
+ }
+ // Check the archive/live type still matches the current Tab
+ else if (!my.chart && my.archiveSource && !pmchart->isArchiveTab()) {
+ message = tr("Cannot add an archive Chart to a live Tab");
+ validInput = false;
+ index = 1;
+ }
+ else if (!my.chart && !my.archiveSource && pmchart->isArchiveTab()) {
+ message = tr("Cannot add a live host Chart to an archive Tab");
+ validInput = false;
+ index = 1;
+ }
+ else {
+ validInput = true;
+ index = 0;
+ }
+ return validInput;
+}
+
+void ChartDialog::buttonOk_clicked()
+{
+ QString message;
+ int index;
+
+ if (validate(message, index)) {
+ if (my.chart)
+ pmchart->acceptEditChart();
+ else
+ pmchart->acceptNewChart();
+ QDialog::accept();
+ } else {
+ tabWidget->setCurrentIndex(index);
+ QMessageBox::warning(this, pmProgname, message,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ }
+}
+
+void ChartDialog::buttonApply_clicked()
+{
+ QString message;
+ int index;
+
+ if (validate(message, index)) {
+ if (my.chart)
+ pmchart->acceptEditChart();
+ else // New Chart to Edit Chart transition:
+ resetPartially(pmchart->acceptNewChart());
+ }
+ else {
+ tabWidget->setCurrentIndex(index);
+ QMessageBox::warning(this, pmProgname, message,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ }
+}
+
+Chart *ChartDialog::chart()
+{
+ return my.chart;
+}
+
+void ChartDialog::chartMetricsItemSelectionChanged()
+{
+ QTreeWidgetItemIterator iterator(chartMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selected);
+ my.chartTreeSingleSelected = *iterator;
+ if ((my.chartTreeSelected = (my.chartTreeSingleSelected != NULL)))
+ if (*(++iterator) != NULL)
+ my.chartTreeSingleSelected = NULL; // multiple selections
+ enableUi();
+}
+
+void ChartDialog::availableMetricsItemSelectionChanged()
+{
+ QTreeWidgetItemIterator iterator(availableMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selected);
+ my.availableTreeSingleSelected = *iterator;
+ if ((my.availableTreeSelected = (my.availableTreeSingleSelected != NULL)))
+ if (*(++iterator) != NULL)
+ my.availableTreeSingleSelected = NULL; // multiple selections
+ enableUi();
+}
+
+void ChartDialog::availableMetricsItemExpanded(QTreeWidgetItem *item)
+{
+ console->post(PmChart::DebugUi,
+ "ChartDialog::availableMetricsItemExpanded %p", item);
+ NameSpace *metricName = (NameSpace *)item;
+ metricName->setExpanded(true, true);
+}
+
+void ChartDialog::metricInfoButtonClicked()
+{
+ NameSpace *name = (NameSpace *)(my.chartTreeSingleSelected ?
+ my.chartTreeSingleSelected : my.availableTreeSingleSelected);
+ pmchart->metricInfo(name->sourceName(), name->metricName(),
+ name->metricInstance(), name->sourceType());
+}
+
+void ChartDialog::metricDeleteButtonClicked()
+{
+ QList<QTreeWidgetItem *> items = chartMetricsTreeWidget->selectedItems();
+ for (int i = 0; i < items.size(); i++) {
+ NameSpace *name = (NameSpace *)items.at(i);
+ name->removeFromTree(chartMetricsTreeWidget);
+ }
+}
+
+void ChartDialog::metricSearchButtonClicked()
+{
+ pmchart->metricSearch(availableMetricsTreeWidget);
+}
+
+void ChartDialog::availableMetricsTreeWidget_doubleClicked(QModelIndex)
+{
+ metricAddButtonClicked();
+}
+
+void ChartDialog::metricAddButtonClicked()
+{
+ QList<NameSpace *> list;
+ QTreeWidgetItemIterator iterator(availableMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selected);
+ for (; (*iterator); ++iterator) {
+ NameSpace *item = (NameSpace *)(*iterator);
+
+ if (QmcMetric::real(item->desc().type) == true)
+ list.append(item);
+ else {
+ QString message = item->metricName();
+ message.prepend(tr("Cannot plot metric: "));
+ message.append(tr("\nThis metric does not have a numeric type."));
+ QMessageBox::warning(this, pmProgname, message,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ }
+ }
+
+ QString scheme = my.chart ? my.chart->scheme() : my.scheme;
+ int sequence = my.chart ? my.chart->sequence() : my.sequence;
+
+ availableMetricsTreeWidget->clearSelection();
+ chartMetricsTreeWidget->clearSelection(); // selection(s) made below
+ for (int i = 0; i < list.size(); i++)
+ list.at(i)->addToTree(chartMetricsTreeWidget, scheme, &sequence);
+
+ if (my.chart)
+ my.chart->setSequence(sequence);
+ else
+ my.sequence = sequence;
+}
+
+void ChartDialog::archiveButtonClicked()
+{
+ QFileDialog *af = new QFileDialog(this);
+ QStringList al;
+ int sts;
+
+ af->setFileMode(QFileDialog::ExistingFiles);
+ af->setAcceptMode(QFileDialog::AcceptOpen);
+ af->setIconProvider(fileIconProvider);
+ af->setWindowTitle(tr("Add Archive"));
+ af->setDirectory(QDir::toNativeSeparators(QDir::homePath()));
+
+ if (af->exec() == QDialog::Accepted)
+ al = af->selectedFiles();
+ for (QStringList::Iterator it = al.begin(); it != al.end(); ++it) {
+ QString archive = *it;
+ if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ archive.prepend(tr("Cannot open PCP archive: "));
+ archive.append(tr("\n"));
+ archive.append(tr(pmErrStr(sts)));
+ QMessageBox::warning(this, pmProgname, archive,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else {
+ setupAvailableMetricsTree(true);
+ archiveGroup->updateBounds();
+ const QmcSource source = archiveGroup->context()->source();
+ pmtime->addArchive(source.start(), source.end(),
+ source.timezone(), source.host(), false);
+ }
+ }
+ delete af;
+}
+
+void ChartDialog::hostButtonClicked()
+{
+ HostDialog *host = new HostDialog(this);
+
+ if (host->exec() == QDialog::Accepted) {
+ QString hostspec = host->getHostSpecification();
+ int sts, flags = host->getContextFlags();
+
+ if (hostspec == QString::null || hostspec.length() == 0) {
+ hostspec.append(tr("Hostname not specified\n"));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) {
+ hostspec.prepend(tr("Cannot connect to host: "));
+ hostspec.append(tr("\n"));
+ hostspec.append(tr(pmErrStr(sts)));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else {
+ console->post(PmChart::DebugUi,
+ "ChartDialog::newHost: %s (flags=0x%x)",
+ (const char *)hostspec.toAscii(), flags);
+ setupAvailableMetricsTree(false);
+ }
+ }
+ delete host;
+}
+
+void ChartDialog::sourceButtonClicked()
+{
+ if (my.archiveSource)
+ archiveButtonClicked();
+ else
+ hostButtonClicked();
+}
+
+QString ChartDialog::title(void)
+{
+ return titleLineEdit->text();
+}
+
+bool ChartDialog::legend(void)
+{
+ return legendOn->isChecked();
+}
+
+void ChartDialog::legendOnClicked()
+{
+ legendOn->setChecked(true);
+ legendOff->setChecked(false);
+}
+
+void ChartDialog::legendOffClicked()
+{
+ legendOn->setChecked(false);
+ legendOff->setChecked(true);
+}
+
+bool ChartDialog::antiAliasing(void)
+{
+ if (antiAliasingAuto->isChecked()) {
+ Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1);
+ return (style != Chart::LineStyle);
+ }
+ return antiAliasingOn->isChecked();
+}
+
+void ChartDialog::antiAliasingOnClicked()
+{
+ antiAliasingOn->setChecked(true);
+ antiAliasingOff->setChecked(false);
+ antiAliasingAuto->setChecked(false);
+}
+
+void ChartDialog::antiAliasingOffClicked()
+{
+ antiAliasingOn->setChecked(false);
+ antiAliasingOff->setChecked(true);
+ antiAliasingAuto->setChecked(false);
+}
+
+void ChartDialog::antiAliasingAutoClicked()
+{
+ antiAliasingOn->setChecked(false);
+ antiAliasingOff->setChecked(false);
+ antiAliasingAuto->setChecked(true);
+}
+
+void ChartDialog::scheme(QString *scheme, int *sequence)
+{
+ *scheme = my.scheme;
+ *sequence = my.sequence;
+}
+
+void ChartDialog::setScheme(QString scheme, int sequence)
+{
+ my.scheme = scheme;
+ my.sequence = sequence;
+}
+
+void ChartDialog::scale(bool *autoScale, double *yMin, double *yMax)
+{
+ *autoScale = autoScaleOn->isChecked();
+ *yMin = my.yMin;
+ *yMax = my.yMax;
+}
+
+void ChartDialog::setScale(bool autoScale, double yMin, double yMax)
+{
+ autoScaleOn->setChecked(autoScale);
+ autoScaleOff->setChecked(!autoScale);
+ yAxisMaximum->setValue(yMax);
+ yAxisMinimum->setValue(yMin);
+}
+
+void ChartDialog::autoScaleOnClicked()
+{
+ autoScaleOn->setChecked(true);
+ autoScaleOff->setChecked(false);
+ minTextLabel->setEnabled(false);
+ maxTextLabel->setEnabled(false);
+ yAxisMinimum->setEnabled(false);
+ yAxisMaximum->setEnabled(false);
+}
+
+void ChartDialog::autoScaleOffClicked()
+{
+ autoScaleOn->setChecked(false);
+ autoScaleOff->setChecked(true);
+ minTextLabel->setEnabled(true);
+ maxTextLabel->setEnabled(true);
+ yAxisMinimum->setEnabled(true);
+ yAxisMaximum->setEnabled(true);
+}
+
+void ChartDialog::yAxisMinimumValueChanged(double value)
+{
+ my.yMin = value;
+}
+
+void ChartDialog::yAxisMaximumValueChanged(double value)
+{
+ my.yMax = value;
+}
+
+void ChartDialog::rateConvertClicked()
+{
+ my.rateConvert = rateConvertCheckBox->isChecked();
+}
+
+bool ChartDialog::rateConvert()
+{
+ return my.rateConvert;
+}
+
+void ChartDialog::setRateConvert(bool rateConvert)
+{
+ my.rateConvert = rateConvert;
+}
+
+// Sets all widgets to display h,s,v
+void ChartDialog::newHsv(int h, int s, int v)
+{
+ setHsv(h, s, v);
+ colorPicker->setCol(h, s);
+ luminancePicker->setCol(h, s, v);
+ colorLineEdit->setCol(h, s, v);
+}
+
+// Sets all widgets to display rgb
+void ChartDialog::setCurrentColor(QRgb rgb)
+{
+ setRgb(rgb);
+ newColorTypedIn(rgb);
+}
+
+// Sets all widgets except cle to display color
+void ChartDialog::newColor(QColor col)
+{
+ console->post(PmChart::DebugUi, "ChartDialog::newColor");
+ int h, s, v;
+ col.getHsv(&h, &s, &v);
+ colorPicker->setCol(h, s);
+ luminancePicker->setCol(h, s, v);
+ setRgb(col.rgb());
+}
+
+// Sets all widgets except cs to display rgb
+void ChartDialog::newColorTypedIn(QRgb rgb)
+{
+ console->post(PmChart::DebugUi, "ChartDialog::newColorTypedIn");
+ int h, s, v;
+ rgb2hsv(rgb, h, s, v);
+ colorPicker->setCol(h, s);
+ luminancePicker->setCol(h, s, v);
+ colorLineEdit->setCol(h, s, v);
+}
+
+void ChartDialog::setRgb(QRgb rgb)
+{
+ console->post(PmChart::DebugUi, "ChartDialog::setRgb");
+ my.currentColor = rgb;
+ rgb2hsv(my.currentColor, my.hue, my.sat, my.val);
+ hEd->setValue(my.hue);
+ sEd->setValue(my.sat);
+ vEd->setValue(my.val);
+ rEd->setValue(qRed(my.currentColor));
+ gEd->setValue(qGreen(my.currentColor));
+ bEd->setValue(qBlue(my.currentColor));
+ showCurrentColor();
+}
+
+void ChartDialog::setHsv(int h, int s, int v)
+{
+ console->post(PmChart::DebugUi, "ChartDialog::setHsv h=%d s=%d v=%d",h,s,v);
+ QColor c;
+ c.setHsv(h, s, v);
+ my.currentColor = c.rgb();
+ my.hue = h; my.sat = s; my.val = v;
+ hEd->setValue(my.hue);
+ sEd->setValue(my.sat);
+ vEd->setValue(my.val);
+ rEd->setValue(qRed(my.currentColor));
+ gEd->setValue(qGreen(my.currentColor));
+ bEd->setValue(qBlue(my.currentColor));
+ showCurrentColor();
+}
+
+QRgb ChartDialog::currentColor()
+{
+ return my.currentColor;
+}
+
+void ChartDialog::rgbEd()
+{
+ my.currentColor = qRgb(rEd->value(), gEd->value(), bEd->value());
+ rgb2hsv(my.currentColor, my.hue, my.sat, my.val);
+ hEd->setValue(my.hue);
+ sEd->setValue(my.sat);
+ vEd->setValue(my.val);
+ showCurrentColor();
+ Q_EMIT newCol(my.currentColor);
+}
+
+void ChartDialog::hsvEd()
+{
+ my.hue = hEd->value();
+ my.sat = sEd->value();
+ my.val = vEd->value();
+ QColor c;
+ c.setHsv(my.hue, my.sat, my.val);
+ my.currentColor = c.rgb();
+ rEd->setValue(qRed(my.currentColor));
+ gEd->setValue(qGreen(my.currentColor));
+ bEd->setValue(qBlue(my.currentColor));
+ showCurrentColor();
+ Q_EMIT newCol(my.currentColor);
+}
+
+void ChartDialog::showCurrentColor()
+{
+ console->post(PmChart::DebugUi, "ChartDialog::showCurrentColor");
+ applyColorLabel->setColor(my.currentColor);
+ colorLineEdit->setColor(my.currentColor);
+}
+
+void ChartDialog::applyColorButtonClicked()
+{
+ NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected;
+ ns->setCurrentColor(my.currentColor, chartMetricsTreeWidget);
+}
+
+void ChartDialog::revertColorButtonClicked()
+{
+ NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected;
+ ns->setCurrentColor(ns->originalColor(), NULL);
+}
+
+void ChartDialog::plotLabelLineEdit_editingFinished()
+{
+ NameSpace *ns = (NameSpace *)my.chartTreeSingleSelected;
+ ns->setLabel(plotLabelLineEdit->text().trimmed());
+}
+
+void ChartDialog::setupChartMetricsTree()
+{
+ chartMetricsTreeWidget->clear();
+ my.chart->setupTree(chartMetricsTreeWidget);
+}
+
+void ChartDialog::setupAvailableMetricsTree(bool arch)
+{
+ NameSpace *current = NULL;
+ QList<QTreeWidgetItem*> items;
+ QmcGroup *group = arch ? archiveGroup : liveGroup;
+
+ availableMetricsTreeWidget->clear();
+ for (unsigned int i = 0; i < group->numContexts(); i++) {
+ QmcContext *cp = group->context(i);
+ if (cp->status() < 0)
+ continue;
+ NameSpace *name = new NameSpace(availableMetricsTreeWidget, cp);
+ name->setExpanded(true, group->numContexts() == 1);
+ name->setSelectable(false);
+ availableMetricsTreeWidget->addTopLevelItem(name);
+ if (i == group->contextIndex())
+ current = name;
+ items.append(name);
+ }
+ if (items.size() > 0)
+ availableMetricsTreeWidget->insertTopLevelItems(0, items);
+ if (current)
+ availableMetricsTreeWidget->setCurrentItem(current);
+}
+
+
+void ChartDialog::updateChartPlots(Chart *cp)
+{
+ deleteChartPlots(cp);
+ if (setupChartPlotsShortcut(cp) == false)
+ setupChartPlots(cp);
+}
+
+void ChartDialog::deleteChartPlots(Chart *cp)
+{
+ int m, nplots = cp->metricCount(); // Copy, as we change it in the loop body
+
+ // Iterate over the current Charts metrics, removing any
+ // that are no longer in the chartMetricsTreeWidget.
+ // This is a no-op in the createChart case, of course.
+
+ for (m = 0; m < nplots; m++) {
+ QTreeWidgetItemIterator iterator1(chartMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selectable);
+ for (; (*iterator1); ++iterator1) {
+ if (matchChartPlot(cp, (NameSpace *)(*iterator1), m))
+ break;
+ }
+ if ((*iterator1) == NULL)
+ deleteChartPlot(cp, m);
+ }
+}
+
+void ChartDialog::setupChartPlots(Chart *cp)
+{
+ // Second step is to iterate over all the chartMetricsTreeWidget
+ // entries, and either create new plots or edit existing ones.
+
+ QTreeWidgetItemIterator iterator2(chartMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selectable);
+ for (; *iterator2; ++iterator2) {
+ NameSpace *n = (NameSpace *)(*iterator2);
+ int m;
+
+ if (existsChartPlot(cp, n, &m))
+ changeChartPlot(cp, n, m);
+ else
+ createChartPlot(cp, n);
+ }
+}
+
+bool ChartDialog::setupChartPlotsShortcut(Chart *cp)
+{
+ // This "shortcut" is used in both the New Chart and Edit Chart
+ // case. It allows the user to bypass the step of moving plots
+ // from the Available Metrics list to the Chart Plots list.
+ //
+ // Return value indicates whether the Chart change is complete
+ // at the end (i.e. used the shortcut), or whether we need to
+ // continue on populating the new chart with Chart Plots.
+
+ if (chartMetricsTreeWidget->topLevelItemCount() > 0)
+ return false; // go do regular creation paths
+
+ int i, m, seq = 0;
+ QTreeWidgetItemIterator iterator(availableMetricsTreeWidget,
+ QTreeWidgetItemIterator::Selected);
+ for (i = 0; (*iterator); ++iterator, i++) {
+ NameSpace *n = (NameSpace *)(*iterator);
+ if (existsChartPlot(cp, n, &m)) {
+ changeChartPlot(cp, n, m);
+ } else {
+ QColor c = nextColor(cp->scheme(), &seq);
+ n->setCurrentColor(c, NULL);
+ createChartPlot(cp, n);
+ my.sequence = seq;
+ }
+ }
+ return true; // either way, we're finished now
+}
+
+bool ChartDialog::matchChartPlot(Chart *cp, NameSpace *name, int m)
+{
+ if (cp->metricContext(m) != name->metricContext())
+ return false;
+ if (cp->metricName(m) != name->metricName())
+ return false;
+ if (cp->metricInstance(m) != name->metricInstance())
+ return false;
+ return true;
+}
+
+bool ChartDialog::existsChartPlot(Chart *cp, NameSpace *name, int *m)
+{
+ for (int i = 0; i < cp->metricCount(); i++) {
+ if (matchChartPlot(cp, name, i)) {
+ *m = i;
+ return true;
+ }
+ }
+ *m = -1;
+ return false;
+}
+
+void ChartDialog::changeChartPlot(Chart *cp, NameSpace *name, int m)
+{
+ Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1);
+ cp->setStroke(m, style, name->currentColor());
+ cp->setLabel(m, name->label());
+ cp->reviveItem(m);
+}
+
+void ChartDialog::createChartPlot(Chart *cp, NameSpace *name)
+{
+ Chart::Style style = (Chart::Style)(typeComboBox->currentIndex() + 1);
+ pmMetricSpec pms;
+ QString label;
+
+ label = name->label().isEmpty() ? QString::null : name->label();
+ pms.isarch = (name->sourceType() == PM_CONTEXT_LOCAL) ? 2 :
+ ((name->sourceType() == PM_CONTEXT_ARCHIVE) ? 1 : 0);
+ pms.source = strdup((const char *)name->sourceName().toAscii());
+ pms.metric = strdup((const char *)name->metricName().toAscii());
+ if (!pms.source || !pms.metric)
+ nomem();
+ if (name->isInst()) {
+ pms.ninst = 1;
+ pms.inst[0] = strdup((const char *)name->metricInstance().toAscii());
+ if (!pms.inst[0])
+ nomem();
+ }
+ else {
+ pms.ninst = 0;
+ pms.inst[0] = NULL;
+ }
+ cp->setStyle(style);
+ int m = cp->addItem(&pms, label);
+ if (m < 0) {
+ QString msg;
+ if (pms.inst[0] != NULL)
+ msg.sprintf("Error:\nFailed to plot metric \"%s[%s]\" for\n%s %s:\n",
+ pms.metric, pms.inst[0],
+ pms.isarch ? "archive" : "host",
+ pms.source);
+ else
+ msg.sprintf("Error:\nFailed to plot metric \"%s\" for\n%s %s:\n",
+ pms.metric, pms.isarch ? "archive" : "host",
+ pms.source);
+ if (m == PM_ERR_CONV) {
+ msg.append("Units for this metric are not compatible with other plots in this chart");
+ }
+ else
+ msg.append(pmErrStr(m));
+ QMessageBox::critical(pmchart, pmProgname, msg);
+ }
+ else {
+ cp->setStroke(m, style, name->currentColor());
+ cp->setLabel(m, name->label());
+ }
+
+ if (pms.ninst == 1)
+ free(pms.inst[0]);
+ free(pms.metric);
+ free(pms.source);
+}
+
+void ChartDialog::deleteChartPlot(Chart *cp, int m)
+{
+ cp->removeItem(m);
+}
+
+void ChartDialog::setCurrentScheme(QString scheme)
+{
+ my.scheme = scheme;
+ setupSchemeComboBox();
+}
+
+void ChartDialog::setupSchemeComboBox()
+{
+ int index = 0;
+
+ colorSchemeComboBox->blockSignals(true);
+ colorSchemeComboBox->clear();
+ colorSchemeComboBox->addItem("Default Scheme");
+ colorSchemeComboBox->addItem("New Scheme");
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++) {
+ QString name = globalSettings.colorSchemes[i].name();
+ if (name == my.scheme)
+ index = i + 2;
+ colorSchemeComboBox->addItem(name);
+ }
+ colorSchemeComboBox->setCurrentIndex(index);
+ colorSchemeComboBox->blockSignals(false);
+}
+
+void ChartDialog::colorSchemeComboBox_currentIndexChanged(int index)
+{
+ if (index == 0)
+ my.scheme = QString::null;
+ else if (index == 1)
+ pmchart->newScheme();
+ else
+ my.scheme = colorSchemeComboBox->itemText(index);
+}
diff --git a/src/pmchart/chartdialog.h b/src/pmchart/chartdialog.h
new file mode 100644
index 0000000..19f8673
--- /dev/null
+++ b/src/pmchart/chartdialog.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef CHARTDIALOG_H
+#define CHARTDIALOG_H
+
+#include "ui_chartdialog.h"
+
+class ChartDialog : public QDialog, public Ui::ChartDialog
+{
+ Q_OBJECT
+
+public:
+ ChartDialog(QWidget* parent);
+
+ virtual void init();
+ virtual void reset();
+ virtual void reset(Chart *);
+ virtual void enableUi();
+
+ virtual Chart *chart(void);
+ virtual QString title(void);
+ virtual bool legend(void);
+ virtual bool antiAliasing(void);
+ virtual void scale(bool *, double *, double *);
+ virtual void setScale(bool, double, double);
+ virtual bool rateConvert();
+ virtual void setRateConvert(bool);
+ virtual void scheme(QString *, int *);
+ virtual void setScheme(QString, int);
+ virtual void setHsv(int h, int s, int v);
+ virtual QRgb currentColor();
+ virtual void setCurrentColor(QRgb);
+ virtual void showCurrentColor();
+ virtual void setCurrentScheme(QString);
+ virtual void setupSchemeComboBox();
+ virtual void setupChartMetricsTree();
+ virtual void setupAvailableMetricsTree(bool);
+
+ virtual void updateChartPlots(Chart *);
+
+public slots:
+ virtual void buttonOk_clicked();
+ virtual void buttonApply_clicked();
+ virtual void chartMetricsItemSelectionChanged();
+ virtual void availableMetricsItemSelectionChanged();
+ virtual void availableMetricsItemExpanded(QTreeWidgetItem *);
+ virtual void availableMetricsTreeWidget_doubleClicked(QModelIndex);
+ virtual void metricInfoButtonClicked();
+ virtual void metricSearchButtonClicked();
+ virtual void metricDeleteButtonClicked();
+ virtual void metricAddButtonClicked();
+ virtual void archiveButtonClicked();
+ virtual void hostButtonClicked();
+ virtual void sourceButtonClicked();
+ virtual void legendOnClicked();
+ virtual void legendOffClicked();
+ virtual void antiAliasingOnClicked();
+ virtual void antiAliasingOffClicked();
+ virtual void autoScaleOnClicked();
+ virtual void autoScaleOffClicked();
+ virtual void rateConvertClicked();
+ virtual void antiAliasingAutoClicked();
+ virtual void yAxisMinimumValueChanged(double);
+ virtual void yAxisMaximumValueChanged(double);
+ virtual void newColor(QColor);
+ virtual void newColorTypedIn(QRgb);
+ virtual void applyColorButtonClicked();
+ virtual void revertColorButtonClicked();
+ virtual void newHsv(int, int, int);
+ virtual void setRgb(QRgb);
+ virtual void rgbEd();
+ virtual void hsvEd();
+ virtual void plotLabelLineEdit_editingFinished();
+ virtual void colorSchemeComboBox_currentIndexChanged(int);
+
+Q_SIGNALS:
+ void newCol(QRgb);
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ struct {
+ bool archiveSource;
+
+ bool chartTreeSelected;
+ QTreeWidgetItem *chartTreeSingleSelected;
+
+ bool availableTreeSelected;
+ QTreeWidgetItem *availableTreeSingleSelected;
+
+ double yMin;
+ double yMax;
+ Chart *chart;
+ bool rateConvert;
+ int sequence;
+ QString scheme;
+
+ int hue;
+ int sat;
+ int val;
+ QRgb currentColor;
+ } my;
+
+ void resetCompletely();
+ void resetPartially(Chart *);
+ bool validate(QString &, int &);
+ void setupChartPlots(Chart *);
+ bool setupChartPlotsShortcut(Chart *);
+ bool matchChartPlot(Chart *, NameSpace *, int);
+ bool existsChartPlot(Chart *, NameSpace *, int *);
+ void changeChartPlot(Chart *, NameSpace *, int);
+ void createChartPlot(Chart *, NameSpace *);
+ void deleteChartPlot(Chart *, int);
+ void deleteChartPlots(Chart *);
+};
+
+#endif // CHARTDIALOG_H
diff --git a/src/pmchart/chartdialog.ui b/src/pmchart/chartdialog.ui
new file mode 100644
index 0000000..192c451
--- /dev/null
+++ b/src/pmchart/chartdialog.ui
@@ -0,0 +1,2249 @@
+<ui version="4.0" >
+ <class>ChartDialog</class>
+ <widget class="QDialog" name="ChartDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>612</width>
+ <height>457</height>
+ </rect>
+ </property>
+ <property name="focusPolicy" >
+ <enum>Qt::WheelFocus</enum>
+ </property>
+ <property name="windowTitle" >
+ <string>New Chart</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/pmchart.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="1" >
+ <widget class="QTabWidget" name="tabWidget" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex" >
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="Widget2" >
+ <attribute name="title" >
+ <string>Chart</string>
+ </attribute>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="1" >
+ <widget class="QComboBox" name="colorSchemeComboBox" >
+ <property name="whatsThis" >
+ <string>Drop down llist of available color schemes. The color scheme determines the initial color for each new plot added to a chart.</string>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Default Scheme</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>New Scheme...</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QComboBox" name="typeComboBox" >
+ <property name="whatsThis" >
+ <string>Drop down list of chart styles, specifying how the values in a chart should be displayed</string>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Line Plot</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Bar Plot</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Stacked Bar</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Area Plot</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Utilization</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="colorSchemeLabel" >
+ <property name="whatsThis" >
+ <string>Drop down llist of available color schemes. The color scheme determines the initial color for each new plot added to a chart.</string>
+ </property>
+ <property name="text" >
+ <string>Colors:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="legendOn" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string>
+ </property>
+ <property name="text" >
+ <string>On</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="legendOff" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string>
+ </property>
+ <property name="text" >
+ <string>Off</string>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QLabel" name="legendTextLabel" >
+ <property name="whatsThis" >
+ <string>Enables or disables the chart legend, which shows the mapping between plot names and colors</string>
+ </property>
+ <property name="text" >
+ <string>Legend:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" >
+ <widget class="QLabel" name="antiAliasingTextLabel" >
+ <property name="whatsThis" >
+ <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string>
+ </property>
+ <property name="text" >
+ <string>Antialiasing:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" >
+ <layout class="QHBoxLayout" >
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="antiAliasingAuto" >
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>Enable or disable chart rendering with antialiasing automatically, based on the chart style chosen. Line plots will be drawn anti-aliased as this gives a color exactly matching that requested, and bar plots are drawn anti-aliased so their outlines are correctly rendered.</string>
+ </property>
+ <property name="text" >
+ <string>Auto</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>14</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="antiAliasingOn" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string>
+ </property>
+ <property name="text" >
+ <string>On</string>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="antiAliasingOff" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>Enable or disable chart rendering with antialiasing. This affects the way in which charts are drawn, in particular line plots are less "blocky" when rendered this way. Also, the black like around bar plots are drawn more accurately with antialiasing enabled.</string>
+ </property>
+ <property name="text" >
+ <string>Off</string>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>5</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QLineEdit" name="titleLineEdit" >
+ <property name="whatsThis" >
+ <string>Add or modifiy the chart title (description)</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="titleTextLabel" >
+ <property name="whatsThis" >
+ <string>Add or modifiy the chart title (description)</string>
+ </property>
+ <property name="text" >
+ <string>Title:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="styleLabel" >
+ <property name="whatsThis" >
+ <string>Drop down list of chart styles, specifying how the values in a chart should be displayed</string>
+ </property>
+ <property name="text" >
+ <string>Style:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="yAxisGroupBox" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="whatsThis" >
+ <string>When enabled, autoscaling causes the Values axis to be scaled based on the minimum and maximum observed values. When disabled, the minimum and maximum points on the Values Axis specified here are always used (even if the values observed lie outside that range).</string>
+ </property>
+ <property name="title" >
+ <string>Values Axis</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="maxTextLabel" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Maximum:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QDoubleSpinBox" name="yAxisMinimum" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="maximum" >
+ <double>1000000000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="minTextLabel" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>Minimum:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>21</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="autoScaleOn" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>On</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="autoScaleOff" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>Off</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QDoubleSpinBox" name="yAxisMaximum" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="maximum" >
+ <double>1000000000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="autoScale" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>Auto-Scale:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QCheckBox" name="rateConvertCheckBox" >
+ <property name="text" >
+ <string>Rate convert counter metrics</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="Widget3" >
+ <attribute name="title" >
+ <string>Metrics</string>
+ </attribute>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="1" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="availableMetricsTreeWidget" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="cursor" >
+ <cursor>0</cursor>
+ </property>
+ <property name="mouseTracking" >
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis" >
+ <string>Complete list of metrics and instances which can be added to a new or current chart</string>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="verticalScrollBarPolicy" >
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="alternatingRowColors" >
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="rootIsDecorated" >
+ <bool>false</bool>
+ </property>
+ <property name="uniformRowHeights" >
+ <bool>true</bool>
+ </property>
+ <property name="columnCount" >
+ <number>1</number>
+ </property>
+ <column>
+ <property name="text" >
+ <string>Available Metrics</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="availableMetricLineEdit" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="whatsThis" >
+ <string>Complete metric name of the currently selected metric in the Available Metrics list</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metricInfoButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Metric Info</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Display information about a metric selected from either of the Chart Plots or Available Metrics lists. This includes metric descriptor, help text (if available), and value at the current time position.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/help-browser.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metricSearchButton" >
+ <property name="enabled" >
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Metric Search</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Search for metrics using a regular expression on metric/instance names.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/system-search.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metricDeleteButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Remove plot from chart</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Remove the current selected plot(s) from the Chart Plots list. When OK is pressed, these plots will be removed from the current chart.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/process-stop.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="metricAddButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Insert plot into chart</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Add a new plot into the Chart Plots list. When OK is pressed, this plot will be added to the new or current chart.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>41</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="sourceButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Add new host</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Add new metric source (host or archive) from which additional Available Metrics can be found for plotting.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>51</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="TabPage" >
+ <attribute name="title" >
+ <string>Plots</string>
+ </attribute>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="revertColorButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Revert to previous color</string>
+ </property>
+ <property name="statusTip" >
+ <string>Change back to the previous plot color, as displayed in the frame below.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/go-jump.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QedColorShowLabel" name="revertColorLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth" >
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>8</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QedColorShowLabel" name="applyColorLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth" >
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="applyColorButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Apply new plot color</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Change the color of the currently selected plot in the Chart Plots list.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QedColorPicker" name="colorPicker" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>176</width>
+ <height>176</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>176</width>
+ <height>176</height>
+ </size>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::Panel</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth" >
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QedColorLuminancePicker" native="1" name="luminancePicker" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>24</width>
+ <height>176</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>24</width>
+ <height>176</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="2" >
+ <widget class="QLabel" name="saturationTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Saturation</string>
+ </property>
+ <property name="text" >
+ <string>S:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="redTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Red</string>
+ </property>
+ <property name="text" >
+ <string>R:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" >
+ <widget class="QedColSpinBox" name="rEd" >
+ <property name="toolTip" >
+ <string>Red</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5" >
+ <widget class="QedColSpinBox" name="bEd" >
+ <property name="toolTip" >
+ <string>Blue</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="hueTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Hue</string>
+ </property>
+ <property name="text" >
+ <string>H:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" >
+ <widget class="QedColSpinBox" name="gEd" >
+ <property name="toolTip" >
+ <string>Green</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4" >
+ <widget class="QLabel" name="blueTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Blue</string>
+ </property>
+ <property name="text" >
+ <string>B:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QLabel" name="greenTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Green</string>
+ </property>
+ <property name="text" >
+ <string>G:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" >
+ <widget class="QedColSpinBox" name="sEd" >
+ <property name="toolTip" >
+ <string>Saturation</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QedColSpinBox" name="hEd" >
+ <property name="toolTip" >
+ <string>Hue</string>
+ </property>
+ <property name="whatsThis" >
+ <string/>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5" >
+ <widget class="QedColSpinBox" name="vEd" >
+ <property name="toolTip" >
+ <string>Value</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4" >
+ <widget class="QLabel" name="valueTextLabel" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="toolTip" >
+ <string>Value</string>
+ </property>
+ <property name="text" >
+ <string>V:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="plotLabelLabel" >
+ <property name="toolTip" >
+ <string>Label to use in chart legend for the currently selected Chart Plot.</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Label to use in chart legend for the currently selected Chart Plot.</string>
+ </property>
+ <property name="text" >
+ <string>Plot Label:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QedColLineEdit" name="colorLineEdit" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip" >
+ <string>Color specification string (e.g. RGB hex value)</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Directly edit the color specification string (e.g. RGB hex value) using this text box. This color can then be used to set the color of the currently selected plot in the Chart Plots list using the left arrow button above.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="plotColorLabel" >
+ <property name="toolTip" >
+ <string>Color specification string (e.g. RGB hex value)</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Directly edit the color specification string (e.g. RGB hex value) using this text box. This color can then be used to set the color of the currently selected plot in the Chart Plots list using the left arrow button above.</string>
+ </property>
+ <property name="text" >
+ <string>Plot Color:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1" colspan="2" >
+ <widget class="QLineEdit" name="plotLabelLineEdit" >
+ <property name="toolTip" >
+ <string>Label to use in chart legend for the currently selected Chart Plot.</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Label to use in chart legend for the currently selected Chart Plot.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="chartMetricsTreeWidget" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="cursor" >
+ <cursor>0</cursor>
+ </property>
+ <property name="mouseTracking" >
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis" >
+ <string>List of plots for either a new chart or the currently selected chart</string>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="verticalScrollBarPolicy" >
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="alternatingRowColors" >
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="rootIsDecorated" >
+ <bool>false</bool>
+ </property>
+ <column>
+ <property name="text" >
+ <string>Chart Plots</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="chartMetricLineEdit" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="whatsThis" >
+ <string>Displays the full metric name of the currently selected chart plot</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonApply" >
+ <property name="text" >
+ <string>&amp;Apply</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QedColorShowLabel</class>
+ <extends>QFrame</extends>
+ <header>qed_colorpicker.h</header>
+ </customwidget>
+ <customwidget>
+ <class>QedColLineEdit</class>
+ <extends>QLineEdit</extends>
+ <header>qed_colorpicker.h</header>
+ </customwidget>
+ <customwidget>
+ <class>QedColSpinBox</class>
+ <extends>QSpinBox</extends>
+ <header>qed_colorpicker.h</header>
+ </customwidget>
+ <customwidget>
+ <class>QedColorLuminancePicker</class>
+ <extends>QWidget</extends>
+ <header>qed_colorpicker.h</header>
+ </customwidget>
+ <customwidget>
+ <class>QedColorPicker</class>
+ <extends>QFrame</extends>
+ <header>qed_colorpicker.h</header>
+ </customwidget>
+ </customwidgets>
+ <includes>
+ <include location="local" >chart.h</include>
+ <include location="local" >namespace.h</include>
+ <include location="local" >qed_colorpicker.h</include>
+ </includes>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonApply</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>buttonApply_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>buttonOk_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>metricInfoButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>metricInfoButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>metricDeleteButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>metricDeleteButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>metricAddButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>metricAddButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sourceButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>sourceButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>applyColorButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>applyColorButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>revertColorButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>revertColorButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>legendOn</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>legendOnClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>legendOff</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>legendOffClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>autoScaleOn</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>autoScaleOnClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>autoScaleOff</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>autoScaleOffClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>yAxisMinimum</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>yAxisMinimumValueChanged(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>yAxisMaximum</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>yAxisMaximumValueChanged(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>rateConvertCheckBox</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>rateConvertClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>plotLabelLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>plotLabelLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>471</x>
+ <y>295</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>302</x>
+ <y>213</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>metricSearchButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>metricSearchButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>303</x>
+ <y>118</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>302</x>
+ <y>213</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorSchemeComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>colorSchemeComboBox_currentIndexChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>464</x>
+ <y>131</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>302</x>
+ <y>213</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>availableMetricsTreeWidget</sender>
+ <signal>doubleClicked(QModelIndex)</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>availableMetricsTreeWidget_doubleClicked(QModelIndex)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>334</x>
+ <y>48</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>305</x>
+ <y>213</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>antiAliasingOn</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>antiAliasingOnClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>425</x>
+ <y>194</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>305</x>
+ <y>228</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>antiAliasingOff</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>antiAliasingOffClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>492</x>
+ <y>194</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>305</x>
+ <y>228</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>antiAliasingAuto</sender>
+ <signal>clicked()</signal>
+ <receiver>ChartDialog</receiver>
+ <slot>antiAliasingAutoClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>560</x>
+ <y>194</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>305</x>
+ <y>228</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/colorbutton.cpp b/src/pmchart/colorbutton.cpp
new file mode 100644
index 0000000..66826cf
--- /dev/null
+++ b/src/pmchart/colorbutton.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "colorbutton.h"
+#include <QtGui/QPainter>
+#include <QtGui/QColorDialog>
+#include <QtGui/QPaintEvent>
+
+ColorButton::ColorButton(QWidget* parent) : QToolButton(parent)
+{
+ my.modified = false;
+ my.color = Qt::white;
+}
+
+bool ColorButton::isSet()
+{
+ return my.color != Qt::white;
+}
+
+void ColorButton::paintEvent(QPaintEvent *e)
+{
+ QToolButton::paintEvent(e);
+ QPainter p(this);
+ QRect rect = contentsRect()&e->rect();
+ p.fillRect(rect.adjusted(+4,+5,-4,-5), my.color);
+}
+
+void ColorButton::clicked()
+{
+ QColor color = QColorDialog::getColor(my.color);
+ if (color.isValid()) {
+ my.color = color;
+ my.modified = true;
+ update();
+ }
+}
diff --git a/src/pmchart/colorbutton.h b/src/pmchart/colorbutton.h
new file mode 100644
index 0000000..e992531
--- /dev/null
+++ b/src/pmchart/colorbutton.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef COLORBUTTON_H
+#define COLORBUTTON_H
+
+#include <QtGui/QToolButton>
+
+class ColorButton : public QToolButton
+{
+ Q_OBJECT
+
+public:
+ ColorButton(QWidget* parent);
+
+ bool isSet();
+ bool isModified() { return my.modified; }
+
+ QColor color() { return my.color; }
+ void setColor(QColor color) { my.color = color; update(); }
+
+public slots:
+ virtual void clicked();
+ virtual void paintEvent(QPaintEvent *);
+
+private:
+ struct {
+ QColor color;
+ bool modified;
+ } my;
+};
+
+#endif // COLORBUTTON_H
diff --git a/src/pmchart/colorscheme.cpp b/src/pmchart/colorscheme.cpp
new file mode 100644
index 0000000..b16b399
--- /dev/null
+++ b/src/pmchart/colorscheme.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ * Copyright (c) 2013, Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "colorscheme.h"
+#include "main.h"
+
+ColorScheme::ColorScheme()
+{
+ my.isModified = false;
+ my.name = QString::null;
+}
+
+bool ColorScheme::lookupScheme(QString name)
+{
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++)
+ if (name == globalSettings.colorSchemes[i].name())
+ return true;
+ return false;
+}
+
+ColorScheme *ColorScheme::findScheme(QString name)
+{
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++)
+ if (name == globalSettings.colorSchemes[i].name())
+ return &globalSettings.colorSchemes[i];
+ return NULL;
+}
+
+bool ColorScheme::removeScheme(QString name)
+{
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++)
+ if (name == globalSettings.colorSchemes[i].name()) {
+ globalSettings.colorSchemes.removeAt(i);
+ return true;
+ }
+ return false;
+}
+
+static inline int hexval(float f)
+{
+ return ((int)(0.5 + f*256) < 256 ? (int)(0.5 + f*256) : 256);
+}
+
+QColor ColorScheme::colorSpec(QString name)
+{
+ QColor color;
+ QString rgbi = name;
+
+ if (rgbi.left(5) != "rgbi:")
+ color.setNamedColor(name);
+ else {
+ float fr, fg, fb;
+ if (sscanf((const char *)rgbi.toAscii(), "rgbi:%f/%f/%f", &fr, &fg, &fb) == 3)
+ color.setRgb(hexval(fr), hexval(fg), hexval(fb));
+ // else return color as-is, i.e. invalid.
+ }
+ return color;
+}
+
+void ColorScheme::clear()
+{
+ my.colors.clear();
+ my.colorNames.clear();
+}
+
+void ColorScheme::setColorNames(QStringList colorNames)
+{
+ my.colorNames = colorNames;
+ for (int i = 0; i < colorNames.size(); i++)
+ my.colors << QColor(colorNames.at(i));
+}
+
+void ColorScheme::addColor(QColor color)
+{
+ my.colors.append(color);
+ my.colorNames.append(color.name());
+}
+
+void ColorScheme::addColor(QString name)
+{
+ my.colors.append(colorSpec(name));
+ my.colorNames.append(name);
+}
diff --git a/src/pmchart/colorscheme.h b/src/pmchart/colorscheme.h
new file mode 100644
index 0000000..972a305
--- /dev/null
+++ b/src/pmchart/colorscheme.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef COLORSCHEME_H
+#define COLORSCHEME_H
+
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtGui/QColor>
+
+class ColorScheme
+{
+public:
+ ColorScheme();
+
+ QString name() { return my.name; }
+ int size() { return my.colors.size(); }
+ QColor color(int i) { return my.colors.at(i); }
+ QString colorName(int i) { return my.colorNames.at(i); }
+
+ QList<QColor> colors() { return my.colors; }
+ QStringList colorNames() { return my.colorNames; }
+
+ void setName(QString name) { my.name = name; }
+ void setModified(bool modified) { my.isModified = modified; }
+
+ void clear();
+ void addColor(QString name);
+ void addColor(QColor color);
+ void setColorNames(QStringList);
+
+ static ColorScheme *findScheme(QString);
+ static bool lookupScheme(QString); // search in global list
+ static bool removeScheme(QString); // remove from global list
+
+ static QColor colorSpec(QString); // QT color / convert rgbi:
+
+private:
+ struct {
+ QString name;
+ bool isModified;
+ QList<QColor> colors;
+ QStringList colorNames;
+ } my;
+};
+
+#endif
diff --git a/src/pmchart/exportdialog.cpp b/src/pmchart/exportdialog.cpp
new file mode 100644
index 0000000..bc99672
--- /dev/null
+++ b/src/pmchart/exportdialog.cpp
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ * Copyright (c) 2013, Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QDir>
+#include <QtGui/QPainter>
+#include <QtGui/QMessageBox>
+#include <QtGui/QImageWriter>
+#include "main.h"
+#include "exportdialog.h"
+
+// ExportFileDialog is the one which is displayed when you click on
+// the image file selection push button
+ExportFileDialog::ExportFileDialog(QWidget *parent) : QFileDialog(parent)
+{
+ setAcceptMode(QFileDialog::AcceptSave);
+ setFileMode(QFileDialog::AnyFile);
+ setIconProvider(fileIconProvider);
+ setConfirmOverwrite(true);
+}
+
+ExportDialog::ExportDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ init();
+}
+
+ExportDialog::~ExportDialog()
+{
+ free(my.format);
+}
+
+void ExportDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void ExportDialog::init()
+{
+ QChar sep(__pmPathSeparator());
+ QString imgfile = QDir::toNativeSeparators(QDir::homePath());
+
+ my.quality = 0;
+ my.format = strdup("png");
+ imgfile.append(sep);
+ imgfile.append("export.png");
+ fileLineEdit->setText(imgfile);
+
+ int png = 0;
+ QStringList formats;
+ QList<QByteArray> array = QImageWriter::supportedImageFormats();
+ for (int i = 0; i < array.size(); i++) {
+ if (array.at(i) == "png")
+ png = i;
+ formats << QString(array.at(i));
+ }
+ formatComboBox->blockSignals(true);
+ formatComboBox->addItems(formats);
+ formatComboBox->setCurrentIndex(png);
+ formatComboBox->blockSignals(false);
+
+ qualitySlider->setValue(100);
+ qualitySlider->setRange(0, 100);
+ qualitySpinBox->setValue(100);
+ qualitySpinBox->setRange(0, 100);
+}
+
+void ExportDialog::reset()
+{
+ QSize size = imageSize();
+ widthSpinBox->setValue(size.width());
+ heightSpinBox->setValue(size.height());
+}
+
+void ExportDialog::selectedRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(true);
+ allChartsRadioButton->setChecked(false);
+ reset();
+}
+
+void ExportDialog::allChartsRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(false);
+ allChartsRadioButton->setChecked(true);
+ reset();
+}
+
+void ExportDialog::quality_valueChanged(int value)
+{
+ if (value != my.quality) {
+ my.quality = value;
+ displayQualitySpinBox();
+ displayQualitySlider();
+ }
+}
+
+void ExportDialog::displayQualitySpinBox()
+{
+ qualitySpinBox->blockSignals(true);
+ qualitySpinBox->setValue(my.quality);
+ qualitySpinBox->blockSignals(false);
+}
+
+void ExportDialog::displayQualitySlider()
+{
+ qualitySlider->blockSignals(true);
+ qualitySlider->setValue(my.quality);
+ qualitySlider->blockSignals(false);
+}
+
+void ExportDialog::filePushButton_clicked()
+{
+ ExportFileDialog file(this);
+
+ file.setDirectory(QDir::toNativeSeparators(QDir::homePath()));
+ if (file.exec() == QDialog::Accepted)
+ fileLineEdit->setText(file.selectedFiles().at(0));
+}
+
+void ExportDialog::formatComboBox_currentIndexChanged(QString suffix)
+{
+ char *format = strdup((const char *)suffix.toAscii());
+ QString file = fileLineEdit->text().trimmed();
+ QString regex = my.format;
+
+ regex.append("$");
+ file.replace(QRegExp(regex), suffix);
+ fileLineEdit->setText(file);
+ free(my.format);
+ my.format = format;
+}
+
+QSize ExportDialog::imageSize()
+{
+ Tab *tab = pmchart->activeTab();
+ int height = 0, width = 0;
+
+ for (int i = 0; i < tab->gadgetCount(); i++) {
+ Gadget *gadget = tab->gadget(i);
+ if (gadget != tab->currentGadget() && selectedRadioButton->isChecked())
+ continue;
+ width = qMax(width, gadget->width());
+ height += gadget->height();
+ }
+ height += pmchart->timeAxis()->height() + pmchart->dateLabel()->height();
+ height -= TIMEAXISFUDGE;
+
+ return QSize(width, height);
+}
+
+void ExportDialog::flush()
+{
+ QString file = fileLineEdit->text().trimmed();
+ int width = widthSpinBox->value();
+ int height = heightSpinBox->value();
+ bool everything = allChartsRadioButton->isChecked();
+ bool transparent = transparentCheckBox->isChecked();
+
+ if (ExportDialog::exportFile(file, my.format, my.quality, width, height,
+ transparent, everything) == false) {
+ QString message = tr("Failed to save image file\n");
+ message.append(file);
+ QMessageBox::warning(this, pmProgname, message,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+}
+
+bool ExportDialog::exportFile(QString &file, const char *format, int quality,
+ int width, int height, bool transparent, bool everything)
+{
+ enum QImage::Format rgbFormat = transparent ?
+ QImage::Format_ARGB32 : QImage::Format_RGB32;
+ QImage image(width, height, rgbFormat);
+ QPainter qp(&image);
+
+ console->post("ExportDialog::exportFile file=%s fmt=%s qual=%d w=%d h=%d trans=%d every=%d\n",
+ (const char *)file.toAscii(), format, quality, width, height, transparent, everything);
+
+ if (transparent) {
+ image.fill(qRgba(255, 255, 255, 0));
+ }
+ else {
+ image.fill(qRgba(255, 255, 255, 255));
+ }
+
+ pmchart->painter(&qp, width, height, transparent, everything == false);
+ QImageWriter writer(file, format);
+ writer.setQuality(quality);
+ bool sts = writer.write(image);
+ if (!sts)
+ fprintf(stderr, "%s: error writing %s (%s): %s\n",
+ pmProgname, (const char *) file.toAscii(), format,
+ (const char *) writer.errorString().toAscii());
+ return sts;
+}
+
+int ExportDialog::exportFile(char *outfile, char *geometry, bool transparent)
+{
+ QRegExp regex;
+ QString file(outfile), format;
+ bool noFormat = false;
+ char suffix[32];
+ int i;
+
+ // Ensure the requested image format is supported, else use GIF
+ regex.setPattern("\\.([a-z]+)$");
+ regex.setCaseSensitivity(Qt::CaseInsensitive);
+ if (regex.indexIn(file) == 0) {
+ noFormat = true;
+ }
+ else {
+ format = regex.cap(1);
+ QList<QByteArray> array = QImageWriter::supportedImageFormats();
+ for (i = 0; i < array.size(); i++) {
+ if (strcmp(array.at(i), (const char *)format.toAscii()) == 0)
+ break;
+ }
+ if (i == array.size())
+ noFormat = true;
+ }
+ if (noFormat) {
+ file.append(".png");
+ format = QString("png");
+ }
+ strncpy(suffix, (const char *)format.toAscii(), sizeof(suffix));
+ suffix[sizeof(suffix)-1] = '\0';
+
+ regex.setPattern("(\\d+)x(\\d+)");
+ if (regex.indexIn(QString(geometry)) != -1) {
+ QSize fixed = QSize(regex.cap(1).toInt(), regex.cap(2).toInt());
+ pmchart->setFixedSize(fixed);
+ }
+
+ return ExportDialog::exportFile(file, suffix, 100, pmchart->width(),
+ pmchart->exportHeight(), transparent, true) == false;
+}
diff --git a/src/pmchart/exportdialog.h b/src/pmchart/exportdialog.h
new file mode 100644
index 0000000..705455d
--- /dev/null
+++ b/src/pmchart/exportdialog.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef EXPORTDIALOG_H
+#define EXPORTDIALOG_H
+
+#include "ui_exportdialog.h"
+#include <QtGui/QFileDialog>
+
+class ExportDialog : public QDialog, public Ui::ExportDialog
+{
+ Q_OBJECT
+
+public:
+ ExportDialog(QWidget* parent);
+ ~ExportDialog();
+
+ virtual void init();
+ virtual void reset();
+ virtual void flush();
+ virtual void displayQualitySpinBox();
+ virtual void displayQualitySlider();
+
+ static bool exportFile(QString &file, const char *format,
+ int quality, int width, int height,
+ bool transparent, bool everything);
+ static int exportFile(char *outfile, char *geometry, bool transparent);
+
+public slots:
+ virtual void selectedRadioButton_clicked();
+ virtual void allChartsRadioButton_clicked();
+ virtual void quality_valueChanged(int);
+ virtual void filePushButton_clicked();
+ virtual void formatComboBox_currentIndexChanged(QString);
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ QSize imageSize();
+
+ struct {
+ int quality;
+ char *format;
+ } my;
+};
+
+class ExportFileDialog : public QFileDialog
+{
+ Q_OBJECT
+
+public:
+ ExportFileDialog(QWidget *);
+};
+
+#endif // EXPORTDIALOG_H
diff --git a/src/pmchart/exportdialog.ui b/src/pmchart/exportdialog.ui
new file mode 100644
index 0000000..85cbabe
--- /dev/null
+++ b/src/pmchart/exportdialog.ui
@@ -0,0 +1,463 @@
+<ui version="4.0" >
+ <class>ExportDialog</class>
+ <widget class="QDialog" name="ExportDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>418</width>
+ <height>218</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>418</width>
+ <height>218</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>418</width>
+ <height>218</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>Export</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/document-export.png</iconset>
+ </property>
+ <property name="toolTip" >
+ <string/>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>20</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="formatTextLabel" >
+ <property name="text" >
+ <string>Image Format</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="formatComboBox" >
+ <property name="maxCount" >
+ <number>32</number>
+ </property>
+ <property name="duplicatesEnabled" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="transparentCheckBox" >
+ <property name="text" >
+ <string>Transparent Background</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="selectedRadioButton" >
+ <property name="text" >
+ <string>Selected Chart Only</string>
+ </property>
+ <property name="checked" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="allChartsRadioButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>All Charts in Tab</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item row="1" column="1" >
+ <widget class="QSpinBox" name="heightSpinBox" >
+ <property name="suffix" >
+ <string> px</string>
+ </property>
+ <property name="maximum" >
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="qualityLabel" >
+ <property name="text" >
+ <string>Quality</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QSpinBox" name="qualitySpinBox" >
+ <property name="suffix" >
+ <string>%</string>
+ </property>
+ <property name="maximum" >
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2" >
+ <widget class="QSlider" name="qualitySlider" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="widthLabel" >
+ <property name="text" >
+ <string>Width</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="heightLabel" >
+ <property name="text" >
+ <string>Height</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QSpinBox" name="widthSpinBox" >
+ <property name="suffix" >
+ <string> px</string>
+ </property>
+ <property name="maximum" >
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="fileLabel" >
+ <property name="text" >
+ <string>File</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="fileLineEdit" >
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignLeading</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="filePushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/document-export.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>214</width>
+ <height>27</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>selectedRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>selectedRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>allChartsRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>allChartsRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>filePushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>filePushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>qualitySlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>quality_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>298</x>
+ <y>116</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>187</x>
+ <y>109</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>qualitySpinBox</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>quality_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>332</x>
+ <y>91</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>187</x>
+ <y>109</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>formatComboBox</sender>
+ <signal>currentIndexChanged(QString)</signal>
+ <receiver>ExportDialog</receiver>
+ <slot>formatComboBox_currentIndexChanged(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>145</x>
+ <y>25</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>187</x>
+ <y>109</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/gadget.cpp b/src/pmchart/gadget.cpp
new file mode 100644
index 0000000..0646ffa
--- /dev/null
+++ b/src/pmchart/gadget.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "gadget.h"
+
+Gadget::Gadget(QWidget *widget)
+{
+ my.widget = widget;
+}
+
+QStringList Gadget::hosts()
+{
+ QStringList hosts;
+
+ for (int m = 0; m < metricCount(); m++) {
+ if (activeMetric(m) == false)
+ continue;
+ QString host = metricContext(m)->source().host();
+ if (!hosts.contains(host))
+ hosts.append(host);
+ }
+ return hosts;
+}
+
+QString Gadget::pmloggerMetricSyntax(int m)
+{
+ QmcMetric *mp = metric(m);
+ QString config = mp->name();
+
+ if (mp->numInst() == 1) {
+ config.append(" [ \"");
+ config.append(metricInstance(m));
+ config.append("\" ]");
+ }
+ return config;
+}
+
+QString Gadget::pmloggerSyntax()
+{
+ QString config;
+ bool beDiscrete = false;
+ bool nonDiscrete = false;
+
+ // discover whether we need separate log-once/log-every sections
+ for (int m = 0; m < metricCount(); m++) {
+ if (activeMetric(m) == false)
+ continue;
+ if (metricDesc(m)->desc().sem == PM_SEM_DISCRETE)
+ beDiscrete = true;
+ else
+ nonDiscrete = true;
+ }
+
+ if (beDiscrete) {
+ config.append("log mandatory on once {\n");
+ for (int m = 0; m < metricCount(); m++) {
+ if (activeMetric(m) == false)
+ continue;
+ if (metricDesc(m)->desc().sem != PM_SEM_DISCRETE)
+ continue;
+ config.append('\t');
+ config.append(pmloggerMetricSyntax(m));
+ config.append('\n');
+ }
+ config.append("}\n");
+ }
+ if (nonDiscrete) {
+ config.append("log mandatory on default {\n");
+ for (int m = 0; m < metricCount(); m++) {
+ if (activeMetric(m) == false)
+ continue;
+ if (metricDesc(m)->desc().sem == PM_SEM_DISCRETE)
+ continue;
+ config.append('\t');
+ config.append(pmloggerMetricSyntax(m));
+ config.append('\n');
+ }
+ config.append("}\n");
+ }
+ return config;
+}
diff --git a/src/pmchart/gadget.h b/src/pmchart/gadget.h
new file mode 100644
index 0000000..1c62e14
--- /dev/null
+++ b/src/pmchart/gadget.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2012-2014, Red Hat.
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef GADGET_H
+#define GADGET_H
+
+#include <QtGui/QColor>
+#include <QtGui/QWidget>
+#include <QtGui/QPainter>
+#include "qmc_metric.h"
+
+class Gadget
+{
+public:
+ Gadget(QWidget *);
+ virtual ~Gadget() { }
+ virtual void resetFont() { }
+ virtual void setCurrent(bool) { }
+ virtual void setScheme(QString &s) { my.scheme = s; }
+ virtual QString scheme() const { return my.scheme; }
+
+ virtual void updateBackground(QColor) { }
+ virtual void updateValues(bool, bool, int, int, double, double, double) { }
+
+ virtual void resetValues(int, double, double) { }
+ virtual void adjustValues() { }
+ virtual void preserveSample(int, int) { }
+ virtual void punchoutSample(int) { }
+
+ virtual void showWidget() { return my.widget->show(); }
+ virtual int width() const { return my.widget->width(); }
+ virtual int height() const { return my.widget->height(); }
+ virtual QSize size() const { return my.widget->size(); }
+
+ virtual void activateTime(QMouseEvent *) { }
+ virtual void reactivateTime(QMouseEvent *) { }
+ virtual void deactivateTime(QMouseEvent *) { }
+
+ virtual void save(FILE *, bool) { }
+ virtual void print(QPainter *, QRect &, bool) { }
+
+ virtual int metricCount() const { return 0; }
+ virtual bool activeMetric(int) const { return true; }
+ virtual QmcMetric *metric(int) const { return NULL; }
+ virtual QmcDesc *metricDesc(int) const { return NULL; }
+ virtual QString metricInstance(int) const { return QString::null; }
+ virtual QmcContext *metricContext(int) const { return NULL; }
+
+ virtual QStringList hosts(); // unique hostnames across all metrics
+ virtual QString pmloggerSyntax(); // pmlogger config text for all metrics
+ virtual QString pmloggerMetricSyntax(int); // config text for 1 metric
+
+private:
+ struct {
+ QWidget *widget;
+ QString scheme;
+ } my;
+};
+
+#endif // GADGET_H
diff --git a/src/pmchart/groupcontrol.cpp b/src/pmchart/groupcontrol.cpp
new file mode 100644
index 0000000..5bac8d3
--- /dev/null
+++ b/src/pmchart/groupcontrol.cpp
@@ -0,0 +1,675 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "groupcontrol.h"
+
+#define DESPERATE 0
+
+GroupControl::GroupControl()
+{
+ my.samples = 0;
+ my.visible = 0;
+ my.realDelta = 0;
+ my.realPosition = 0;
+ my.timeData = NULL;
+ my.timeState = StartState;
+ my.buttonState = QedTimeButton::Timeless;
+ my.pmtimeState = QmcTime::StoppedState;
+ memset(&my.delta, 0, sizeof(struct timeval));
+ memset(&my.position, 0, sizeof(struct timeval));
+}
+
+void
+GroupControl::init(int samples, int visible,
+ struct timeval *interval, struct timeval *position)
+{
+ my.samples = samples;
+ my.visible = visible;
+
+ if (isArchiveSource()) {
+ my.pmtimeState = QmcTime::StoppedState;
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else {
+ my.pmtimeState = QmcTime::ForwardState;
+ my.buttonState = QedTimeButton::ForwardLive;
+ }
+ my.delta = *interval;
+ my.position = *position;
+ my.realDelta = tosec(*interval);
+ my.realPosition = tosec(*position);
+
+ my.timeData = (double *)malloc(samples * sizeof(double));
+ for (int i = 0; i < samples; i++)
+ my.timeData[i] = my.realPosition - (i * my.realDelta);
+}
+
+bool
+GroupControl::isArchiveSource(void)
+{
+ // Note: We purposefully are not using QmcGroup::mode() here, as we
+ // may not have initialised any contexts yet. In such a case, live
+ // mode is always returned (default, from the QmcGroup constructor).
+
+ return this == archiveGroup;
+}
+
+bool
+GroupControl::isActive(QmcTime::Packet *packet)
+{
+ return (((activeGroup == archiveGroup) &&
+ (packet->source == QmcTime::ArchiveSource)) ||
+ ((activeGroup == liveGroup) &&
+ (packet->source == QmcTime::HostSource)));
+}
+
+void
+GroupControl::addGadget(Gadget *gadget)
+{
+ my.gadgetsList.append(gadget);
+}
+
+void
+GroupControl::deleteGadget(Gadget *gadget)
+{
+ for (int i = 0; i < gadgetCount(); i++)
+ if (my.gadgetsList.at(i) == gadget)
+ my.gadgetsList.removeAt(i);
+}
+
+int
+GroupControl::gadgetCount() const
+{
+ return my.gadgetsList.size();
+}
+
+void
+GroupControl::updateBackground(void)
+{
+ for (int i = 0; i < gadgetCount(); i++)
+ my.gadgetsList.at(i)->updateBackground(globalSettings.chartBackground);
+}
+
+void
+GroupControl::updateTimeAxis(void)
+{
+ QString tz, otz, unused;
+
+ if (numContexts() > 0 || isArchiveSource() == false) {
+ if (numContexts() > 0)
+ defaultTZ(unused, otz);
+ else
+ otz = QmcSource::localHost;
+ tz = otz;
+ pmchart->timeAxis()->setAxisScale(QwtPlot::xBottom,
+ my.timeData[my.visible - 1], my.timeData[0],
+ pmchart->timeAxis()->scaleValue(my.realDelta, my.visible));
+ pmchart->setDateLabel(my.position.tv_sec, tz);
+ pmchart->timeAxis()->replot();
+ } else {
+ pmchart->timeAxis()->noArchiveSources();
+ pmchart->setDateLabel(tr("[No open archives]"));
+ }
+
+ if (console->logLevel(PmChart::DebugProtocol)) {
+ int i = my.visible - 1;
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: tz=%s; visible points=%d",
+ (const char *)tz.toAscii(), i);
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: first time is %.3f (%s)",
+ my.timeData[i], timeString(my.timeData[i]));
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::updateTimeAxis: final time is %.3f (%s)",
+ my.timeData[0], timeString(my.timeData[0]));
+ }
+}
+
+void
+GroupControl::updateTimeButton(void)
+{
+ pmchart->setButtonState(my.buttonState);
+}
+
+QmcTime::State
+GroupControl::pmtimeState(void)
+{
+ return my.pmtimeState;
+}
+
+char *
+GroupControl::timeState()
+{
+ static char buf[16];
+
+ switch (my.timeState) {
+ case StartState: strcpy(buf, "Start"); break;
+ case ForwardState: strcpy(buf, "Forward"); break;
+ case BackwardState: strcpy(buf, "Backward"); break;
+ case EndLogState: strcpy(buf, "EndLog"); break;
+ case StandbyState: strcpy(buf, "Standby"); break;
+ default: strcpy(buf, "Dodgey"); break;
+ }
+ return buf;
+}
+
+void
+GroupControl::timeSelectionActive(Gadget *source, int timestamp)
+{
+ QPoint point(timestamp, -1);
+ QMouseEvent pressed(QEvent::MouseButtonPress, point,
+ Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->activateTime(&pressed);
+ }
+}
+
+void
+GroupControl::timeSelectionReactive(Gadget *source, int timestamp)
+{
+ QPoint point(timestamp, -1);
+ QMouseEvent moved(QEvent::MouseMove, point,
+ Qt::NoButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->reactivateTime(&moved);
+ }
+}
+
+void
+GroupControl::timeSelectionInactive(Gadget *source)
+{
+ QPoint point(-1, -1);
+ QMouseEvent release(QEvent::MouseButtonRelease, point,
+ Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ for (int i = 0; i < gadgetCount(); i++) {
+ Gadget *gadget = my.gadgetsList.at(i);
+ if (source != gadget)
+ gadget->deactivateTime(&release);
+ }
+}
+
+//
+// Drive all updates into each gadget (refresh the display)
+//
+void
+GroupControl::refreshGadgets(bool active)
+{
+#if DESPERATE
+ for (int s = 0; s < my.samples; s++)
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::refreshGadgets: timeData[%2d] is %.2f (%s)",
+ s, my.timeData[s], timeString(my.timeData[s]));
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::refreshGadgets: state=%s", timeState());
+#endif
+
+ for (int i = 0; i < gadgetCount(); i++) {
+ my.gadgetsList.at(i)->updateValues(my.timeState != BackwardState,
+ active, my.samples, my.visible,
+ my.timeData[my.visible - 1],
+ my.timeData[0],
+ my.realDelta);
+ }
+ if (active) {
+ updateTimeButton();
+ updateTimeAxis();
+ }
+}
+
+//
+// Setup the initial data needed after opening a view.
+// All of the work is in archive mode; in live mode we have
+// not yet got any historical data that we can display...
+//
+void
+GroupControl::setupWorldView(void)
+{
+ if (isArchiveSource() == false)
+ return;
+
+ QmcTime::Packet packet;
+ packet.source = QmcTime::ArchiveSource;
+ packet.state = QmcTime::ForwardState;
+ packet.mode = QmcTime::NormalMode;
+ memcpy(&packet.delta, pmtime->archiveInterval(), sizeof(packet.delta));
+ memcpy(&packet.position, pmtime->archivePosition(), sizeof(packet.position));
+ memcpy(&packet.start, pmtime->archiveStart(), sizeof(packet.start));
+ memcpy(&packet.end, pmtime->archiveEnd(), sizeof(packet.end));
+ adjustWorldView(&packet, true);
+}
+
+//
+// Received a Set or a VCRMode requiring us to adjust our state
+// and possibly rethink everything. This can result from a time
+// control position change, delta change, direction change, etc.
+//
+void
+GroupControl::adjustWorldView(QmcTime::Packet *packet, bool vcrMode)
+{
+ my.delta = packet->delta;
+ my.position = packet->position;
+ my.realDelta = tosec(packet->delta);
+ my.realPosition = tosec(packet->position);
+
+ console->post("GroupControl::adjustWorldView: "
+ "sh=%d vh=%d delta=%.2f position=%.2f (%s) state=%s",
+ my.samples, my.visible, my.realDelta, my.realPosition,
+ timeString(my.realPosition), timeState());
+
+ QmcTime::State state = packet->state;
+ if (isArchiveSource()) {
+ if (packet->state == QmcTime::ForwardState)
+ adjustArchiveWorldViewForward(packet, vcrMode);
+ else if (packet->state == QmcTime::BackwardState)
+ adjustArchiveWorldViewBackward(packet, vcrMode);
+ else
+ adjustArchiveWorldViewStopped(packet, vcrMode);
+ }
+ else if (state != QmcTime::StoppedState)
+ adjustLiveWorldViewForward(packet);
+ else
+ adjustLiveWorldViewStopped(packet);
+}
+
+void
+GroupControl::adjustLiveWorldViewStopped(QmcTime::Packet *packet)
+{
+ if (isActive(packet)) {
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ updateTimeButton();
+ }
+}
+
+static bool
+fuzzyTimeMatch(double a, double b, double tolerance)
+{
+ // a matches b if the difference is within 1% of the delta (tolerance)
+ return (a == b ||
+ (b > a && a + tolerance > b) ||
+ (b < a && a - tolerance < b));
+}
+
+void
+GroupControl::adjustLiveWorldViewForward(QmcTime::Packet *packet)
+{
+ //
+ // X-Axis _max_ becomes packet->position.
+ // Rest of (preceeding) time window filled in using packet->delta.
+ // In live mode, we can only fetch current data. However, we make
+ // an effort to keep old data that happens to align with the delta
+ // time points (or near enough) that we are now interested in. So,
+ // "oi" is the old index, whereas "i" is the new timeData[] index.
+ // First we try to find a fuzzy match on current old index, if it
+ // doesn't exit, we continue moving "oi" until it points at a time
+ // larger than the one we're after, and then see how that fares on
+ // the next iteration.
+ //
+ int i, oi, last = my.samples - 1;
+ bool preserve = false;
+ double tolerance = my.realDelta;
+ double position = my.realPosition - (my.realDelta * my.samples);
+
+ for (i = oi = last; i >= 0; i--, position += my.realDelta) {
+ while (i > 0 && my.timeData[oi] < position + my.realDelta && oi > 0) {
+ if (fuzzyTimeMatch(my.timeData[oi], position, tolerance) == false) {
+#if DESPERATE
+ console->post("NO fuzzyTimeMatch %.3f to %.3f (%s)",
+ my.timeData[oi], position, timeString(position));
+#endif
+ if (my.timeData[oi] > position)
+ break;
+ oi--;
+ continue;
+ }
+ console->post("Saved live data (oi=%d/i=%d) for %s", oi, i,
+ timeString(position));
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->preserveSample(i, oi);
+ my.timeData[i] = my.timeData[oi];
+ preserve = true;
+ oi--;
+ break;
+ }
+
+ if (i == 0) { // refreshGadgets() finishes up last one
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ my.timeData[i] = position;
+ fetch();
+ }
+ else if (preserve == false) {
+#if DESPERATE
+ console->post("No live data for %s", timeString(position));
+#endif
+ my.timeData[i] = position;
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->punchoutSample(i);
+ }
+ else
+ preserve = false;
+ }
+ // One (per-gadget) recalculation & refresh at the end, after all data moved
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->adjustValues();
+ my.timeState = (packet->state == QmcTime::StoppedState) ?
+ StandbyState : ForwardState;
+
+ bool active = isActive(packet);
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewForward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("GroupControl::adjustArchiveWorldViewForward");
+ my.timeState = ForwardState;
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ //
+ // X-Axis _max_ becomes packet->position.
+ // Rest of (preceeding) time window filled in using packet->delta.
+ //
+ int last = my.samples - 1;
+ double tolerance = my.realDelta / 20.0; // 5% of the sample interval
+ double position = my.realPosition - (my.realDelta * last);
+
+ double left = position;
+ double right = my.realPosition;
+ double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible);
+
+ for (int i = last; i >= 0; i--, position += my.realDelta) {
+ if (setup == false &&
+ fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) {
+ continue;
+ }
+
+ my.timeData[i] = position;
+
+ struct timeval timeval;
+ fromsec(position, &timeval);
+ setArchiveMode(setmode, &timeval, delta);
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ fetch();
+ if (i == 0) // refreshGadgets() finishes up last one
+ break;
+ console->post("GroupControl::adjustArchiveWorldViewForward: "
+ "setting time position[%d]=%.2f[%s] state=%s count=%d",
+ i, position, timeString(position),
+ timeState(), gadgetCount());
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->updateValues(true, false, my.samples, my.visible,
+ left, right, interval);
+ }
+
+ bool active = isActive(packet);
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ pmtime->setArchivePosition(&packet->position);
+ pmtime->setArchiveInterval(&packet->delta);
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewBackward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("GroupControl::adjustArchiveWorldViewBackward");
+ my.timeState = BackwardState;
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ //
+ // X-Axis _min_ becomes packet->position.
+ // Rest of (following) time window filled in using packet->delta.
+ //
+ int last = my.samples - 1;
+ double tolerance = my.realDelta / 20.0; // 5% of the sample interval
+ double position = my.realPosition;
+
+ double left = position - (my.realDelta * last);
+ double right = position;
+ double interval = pmchart->timeAxis()->scaleValue((double)delta, my.visible);
+
+ for (int i = 0; i <= last; i++, position -= my.realDelta) {
+ if (setup == false &&
+ fuzzyTimeMatch(my.timeData[i], position, tolerance) == true) {
+ continue;
+ }
+
+ my.timeData[i] = position;
+
+ struct timeval timeval;
+ fromsec(position, &timeval);
+ setArchiveMode(setmode, &timeval, -delta);
+ console->post("Fetching data[%d] at %s", i, timeString(position));
+ fetch();
+ if (i == last) // refreshGadgets() finishes up last one
+ break;
+ console->post("GroupControl::adjustArchiveWorldViewBackward: "
+ "setting time position[%d]=%.2f[%s] state=%s count=%d",
+ i, position, timeString(position),
+ timeState(), gadgetCount());
+ for (int j = 0; j < gadgetCount(); j++)
+ my.gadgetsList.at(j)->updateValues(false, false, my.samples, my.visible,
+ left, right, interval);
+ }
+
+ bool active = isActive(packet);
+ if (setup)
+ packet->state = QmcTime::StoppedState;
+ if (active)
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ pmtime->setArchivePosition(&packet->position);
+ pmtime->setArchiveInterval(&packet->delta);
+ refreshGadgets(active);
+}
+
+void
+GroupControl::adjustArchiveWorldViewStopped(QmcTime::Packet *packet, bool needFetch)
+{
+ if (needFetch) { // stopped, but VCR reposition event occurred
+ adjustArchiveWorldViewForward(packet, needFetch);
+ } else {
+ my.timeState = StandbyState;
+ packet->state = QmcTime::StoppedState;
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ updateTimeButton();
+ }
+}
+
+//
+// Catch the situation where we get a larger than expected increase
+// in position. This happens when we restart after a stop in live
+// mode (both with and without a change in the delta).
+//
+static bool
+sideStep(double n, double o, double interval)
+{
+ // tolerance set to 5% of the sample interval:
+ return fuzzyTimeMatch(o + interval, n, interval/20.0) == false;
+}
+
+//
+// Fetch all metric values across all gadgets, and also update the
+// unified time axis.
+//
+void
+GroupControl::step(QmcTime::Packet *packet)
+{
+ double stepPosition = tosec(packet->position);
+
+ console->post(PmChart::DebugProtocol,
+ "GroupControl::step: stepping to time %.2f, delta=%.2f, state=%s",
+ stepPosition, my.realDelta, timeState());
+
+ if ((packet->source == QmcTime::ArchiveSource &&
+ ((packet->state == QmcTime::ForwardState &&
+ my.timeState != ForwardState) ||
+ (packet->state == QmcTime::BackwardState &&
+ my.timeState != BackwardState))) ||
+ sideStep(stepPosition, my.realPosition, my.realDelta))
+ return adjustWorldView(packet, false);
+
+ my.pmtimeState = packet->state;
+ my.position = packet->position;
+ my.realPosition = stepPosition;
+
+ int last = my.samples - 1;
+ if (packet->state == QmcTime::ForwardState) { // left-to-right (all but 1st)
+ if (my.samples > 1)
+ memmove(&my.timeData[1], &my.timeData[0], sizeof(double) * last);
+ my.timeData[0] = my.realPosition;
+ }
+ else if (packet->state == QmcTime::BackwardState) { // right-to-left
+ if (my.samples > 1)
+ memmove(&my.timeData[0], &my.timeData[1], sizeof(double) * last);
+ my.timeData[last] = my.realPosition - torange(my.delta, last);
+ }
+
+ fetch();
+
+ bool active = isActive(packet);
+ if (isActive(packet))
+ newButtonState(packet->state, packet->mode, pmchart->isTabRecording());
+ refreshGadgets(active);
+}
+
+void
+GroupControl::VCRMode(QmcTime::Packet *packet, bool dragMode)
+{
+ if (!dragMode)
+ adjustWorldView(packet, true);
+}
+
+void
+GroupControl::setTimezone(QmcTime::Packet *packet, char *tz)
+{
+ console->post(PmChart::DebugProtocol, "GroupControl::setTimezone %s", tz);
+
+ useTZ(QString(tz));
+
+ if (isActive(packet))
+ updateTimeAxis();
+}
+
+void
+GroupControl::setSampleHistory(int v)
+{
+ console->post("GroupControl::setSampleHistory (%d -> %d)", my.samples, v);
+ if (my.samples != v) {
+ my.samples = v;
+
+ my.timeData = (double *)malloc(my.samples * sizeof(my.timeData[0]));
+ if (my.timeData == NULL)
+ nomem();
+
+ double right = my.realPosition;
+ for (v = 0; v < my.samples; v++)
+ my.timeData[v] = my.realPosition - (v * my.realDelta);
+ double left = my.timeData[v-1];
+ for (v = 0; v < gadgetCount(); v++)
+ my.gadgetsList.at(v)->resetValues(my.samples, left, right);
+ }
+}
+
+int
+GroupControl::sampleHistory(void)
+{
+ return my.samples;
+}
+
+void
+GroupControl::setVisibleHistory(int v)
+{
+ console->post("GroupControl::setVisibleHistory (%d -> %d)", my.visible, v);
+
+ if (my.visible != v)
+ my.visible = v;
+}
+
+int
+GroupControl::visibleHistory(void)
+{
+ return my.visible;
+}
+
+double *
+GroupControl::timeAxisData(void)
+{
+ return my.timeData;
+}
+
+QedTimeButton::State
+GroupControl::buttonState(void)
+{
+ return my.buttonState;
+}
+
+void
+GroupControl::newButtonState(QmcTime::State s, QmcTime::Mode m, bool record)
+{
+ if (isArchiveSource() == false) {
+ if (s == QmcTime::StoppedState)
+ my.buttonState = record ?
+ QedTimeButton::StoppedRecord : QedTimeButton::StoppedLive;
+ else
+ my.buttonState = record ?
+ QedTimeButton::ForwardRecord : QedTimeButton::ForwardLive;
+ }
+ else if (m == QmcTime::StepMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::StepForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::StepBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (m == QmcTime::FastMode) {
+ if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::FastForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::FastBackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+ }
+ else if (s == QmcTime::ForwardState)
+ my.buttonState = QedTimeButton::ForwardArchive;
+ else if (s == QmcTime::BackwardState)
+ my.buttonState = QedTimeButton::BackwardArchive;
+ else
+ my.buttonState = QedTimeButton::StoppedArchive;
+}
diff --git a/src/pmchart/groupcontrol.h b/src/pmchart/groupcontrol.h
new file mode 100644
index 0000000..c20f29c
--- /dev/null
+++ b/src/pmchart/groupcontrol.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2006-2008, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef GROUPCONTROL_H
+#define GROUPCONTROL_H
+
+#include <QtCore/QList>
+#include <QtGui/QLabel>
+#include <QtGui/QLayout>
+#include <QtGui/QPixmap>
+#include <qwt_plot.h>
+#include <qwt_scale_draw.h>
+#include <qmc_group.h>
+#include <qmc_time.h>
+#include "gadget.h"
+#include "qed_timebutton.h"
+
+class GroupControl : public QObject, public QmcGroup
+{
+ Q_OBJECT
+
+public:
+ GroupControl();
+ void init(int, int, struct timeval *, struct timeval *);
+
+ void addGadget(Gadget *);
+ void deleteGadget(Gadget *);
+ int gadgetCount() const;
+
+ bool isArchiveSource();
+ void updateBackground();
+
+ void setVisibleHistory(int);
+ int visibleHistory();
+ void setSampleHistory(int);
+ int sampleHistory();
+
+ double *timeAxisData(void);
+
+ void step(QmcTime::Packet *);
+ void VCRMode(QmcTime::Packet *, bool);
+ void setTimezone(QmcTime::Packet *, char *);
+
+ void setupWorldView();
+ void updateTimeButton();
+ void updateTimeAxis(void);
+ void updateTimeAxis(time_t secs);
+
+ QedTimeButton::State buttonState();
+ QmcTime::State pmtimeState();
+ void newButtonState(QmcTime::State, QmcTime::Mode, bool);
+
+public Q_SLOTS:
+ void timeSelectionActive(Gadget *, int);
+ void timeSelectionReactive(Gadget *, int);
+ void timeSelectionInactive(Gadget *);
+
+private:
+ typedef enum {
+ StartState,
+ ForwardState,
+ BackwardState,
+ EndLogState,
+ StandbyState,
+ } State;
+
+ char *timeState();
+ void refreshGadgets(bool);
+ bool isActive(QmcTime::Packet *);
+ void adjustWorldView(QmcTime::Packet *, bool);
+ void adjustLiveWorldViewForward(QmcTime::Packet *);
+ void adjustLiveWorldViewStopped(QmcTime::Packet *);
+ void adjustArchiveWorldViewForward(QmcTime::Packet *, bool);
+ void adjustArchiveWorldViewStopped(QmcTime::Packet *, bool);
+ void adjustArchiveWorldViewBackward(QmcTime::Packet *, bool);
+
+ struct {
+ QList<Gadget*> gadgetsList; // gadgets with metrics in this group
+
+ double realDelta; // current update interval
+ double realPosition; // current time position
+ struct timeval delta;
+ struct timeval position;
+
+ int visible; // -v visible points
+ int samples; // -s total number of samples
+ double *timeData; // time array (intervals)
+
+ QedTimeButton::State buttonState;
+ QmcTime::Source pmtimeSource; // reliable archive/host test
+ QmcTime::State pmtimeState;
+ State timeState;
+ } my;
+};
+
+#endif // GROUPCONTROL_H
diff --git a/src/pmchart/hostdialog.cpp b/src/pmchart/hostdialog.cpp
new file mode 100644
index 0000000..e46635e
--- /dev/null
+++ b/src/pmchart/hostdialog.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QDir>
+#include <QtGui/QMessageBox>
+#include "hostdialog.h"
+#include "main.h"
+
+HostDialog::HostDialog(QWidget *parent) : QDialog(parent)
+{
+ setupUi(this);
+ my.nssGuiStarted = false;
+}
+
+void
+HostDialog::quit()
+{
+ if (my.nssGuiStarted) {
+ my.nssGuiProc->terminate();
+ my.nssGuiStarted = false;
+ }
+}
+
+void
+HostDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void
+HostDialog::secureCheckBox_toggled(bool enableSecure)
+{
+ certificatesPushButton->setEnabled(enableSecure);
+}
+
+void
+HostDialog::proxyCheckBox_toggled(bool enableProxy)
+{
+ proxyLineEdit->setEnabled(enableProxy);
+}
+
+void
+HostDialog::authenticateCheckBox_toggled(bool enableAuthenticate)
+{
+ usernameLabel->setEnabled(enableAuthenticate);
+ usernameLineEdit->setEnabled(enableAuthenticate);
+ passwordLabel->setEnabled(enableAuthenticate);
+ passwordLineEdit->setEnabled(enableAuthenticate);
+ realmLabel->setEnabled(enableAuthenticate);
+ realmLineEdit->setEnabled(enableAuthenticate);
+}
+
+QString
+HostDialog::getHostName(void) const
+{
+ QString host;
+
+ if (hostLineEdit->isModified())
+ host = hostLineEdit->text().trimmed();
+ if (host.length() == 0)
+ return QString::null;
+ return host;
+}
+
+QString
+HostDialog::getHostSpecification(void) const
+{
+ QString host = getHostName();
+
+ if (hostLineEdit->isModified())
+ host = hostLineEdit->text().trimmed();
+ if (host.length() == 0)
+ return QString::null;
+
+ if (proxyLineEdit->isModified()) {
+ QString proxy = proxyLineEdit->text().trimmed();
+ if (proxy.length() > 0)
+ host.prepend("@").prepend(proxy);
+ }
+
+ if (authenticateCheckBox->isChecked()) {
+ QString username = usernameLineEdit->text().trimmed();
+ QString password = passwordLineEdit->text().trimmed();
+ QString realm = realmLineEdit->text().trimmed();
+
+ host.append("?username=").append(username);
+ host.append("&password=").append(password);
+ host.append("&realm=").append(realm);
+ }
+
+ return host;
+}
+
+int
+HostDialog::getContextFlags(void) const
+{
+ int flags = 0;
+
+ if (secureCheckBox->isChecked())
+ flags |= PM_CTXFLAG_SECURE;
+ if (compressCheckBox->isChecked())
+ flags |= PM_CTXFLAG_COMPRESS;
+ if (authenticateCheckBox->isChecked())
+ flags |= PM_CTXFLAG_AUTH;
+ return flags;
+}
+
+void
+HostDialog::certificatesPushButton_clicked()
+{
+ if (!my.nssGuiStarted) {
+ my.nssGuiStarted = true;
+ nssGuiStart();
+ }
+}
+
+void
+HostDialog::nssGuiStart()
+{
+ QString dbpath = QDir::toNativeSeparators(QDir::homePath());
+ int sep = __pmPathSeparator();
+
+ dbpath.append(sep).append(".pki").append(sep).append("nssdb");
+ dbpath.prepend("sql:"); // only use sqlite NSS databases
+
+ QStringList arguments;
+ arguments << "--dbdir";
+ arguments << dbpath;
+
+ my.nssGuiProc = new QProcess(this);
+ connect(my.nssGuiProc, SIGNAL(finished(int, QProcess::ExitStatus)),
+ this, SLOT(nssGuiFinished(int, QProcess::ExitStatus)));
+ my.nssGuiProc->start("nss-gui", arguments);
+}
+
+void
+HostDialog::nssGuiFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ (void)exitStatus;
+
+ if (exitCode) {
+ QString message(tr("nss-gui helper process failed\nExit status was:"));
+ message.append(exitCode).append("\n");
+ QMessageBox::warning(this, pmProgname, message,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ }
+ my.nssGuiStarted = false;
+}
diff --git a/src/pmchart/hostdialog.h b/src/pmchart/hostdialog.h
new file mode 100644
index 0000000..8303674
--- /dev/null
+++ b/src/pmchart/hostdialog.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2013-2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef HOSTDIALOG_H
+#define HOSTDIALOG_H
+
+#include "ui_hostdialog.h"
+#include <QtCore/QProcess>
+
+class HostDialog : public QDialog, public Ui::HostDialog
+{
+ Q_OBJECT
+
+public:
+ HostDialog(QWidget* parent);
+ int getContextFlags() const;
+ QString getHostName(void) const;
+ QString getHostSpecification() const;
+
+protected slots:
+ virtual void languageChange();
+
+private slots:
+ virtual void quit();
+ virtual void proxyCheckBox_toggled(bool);
+ virtual void secureCheckBox_toggled(bool);
+ virtual void certificatesPushButton_clicked();
+ virtual void authenticateCheckBox_toggled(bool);
+ virtual void nssGuiFinished(int, QProcess::ExitStatus);
+
+private:
+ void nssGuiStart();
+
+ struct {
+ bool nssGuiStarted;
+ QProcess *nssGuiProc;
+ } my;
+};
+
+#endif // HOSTDIALOG_H
diff --git a/src/pmchart/hostdialog.ui b/src/pmchart/hostdialog.ui
new file mode 100644
index 0000000..59b88cd
--- /dev/null
+++ b/src/pmchart/hostdialog.ui
@@ -0,0 +1,320 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HostDialog</class>
+ <widget class="QDialog" name="HostDialog">
+ <property name="windowModality">
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>383</width>
+ <height>302</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>300</width>
+ <height>100</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Add Host</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="pmchart.qrc">
+ <normaloff>:/images/computer.png</normaloff>:/images/computer.png</iconset>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>9</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="hostLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="text">
+ <string>Hostname:</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::AutoText</enum>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ <property name="buddy">
+ <cstring>hostLineEdit</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="proxyLineEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>27</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="hostLineEdit">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>27</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="proxyCheckBox">
+ <property name="text">
+ <string>Proxy:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="secureGridLayout">
+ <item row="0" column="0">
+ <widget class="QCheckBox" name="secureCheckBox">
+ <property name="text">
+ <string>Secure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="certificatesPushButton">
+ <property name="text">
+ <string>Certificates</string>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="compressCheckBox">
+ <property name="text">
+ <string>Compress</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="authenticateCheckBox">
+ <property name="text">
+ <string>Authenticate:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="authenticateGridLayout">
+ <item row="0" column="1">
+ <widget class="QLabel" name="usernameLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Username:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="usernameLineEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="passwordLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Password:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLineEdit" name="passwordLineEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="echoMode">
+ <enum>QLineEdit::Password</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="realmLabel">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Realm:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLineEdit" name="realmLineEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="7" column="0">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>hostLineEdit</tabstop>
+ <tabstop>proxyCheckBox</tabstop>
+ <tabstop>proxyLineEdit</tabstop>
+ <tabstop>secureCheckBox</tabstop>
+ <tabstop>certificatesPushButton</tabstop>
+ <tabstop>compressCheckBox</tabstop>
+ <tabstop>authenticateCheckBox</tabstop>
+ <tabstop>usernameLineEdit</tabstop>
+ <tabstop>passwordLineEdit</tabstop>
+ <tabstop>realmLineEdit</tabstop>
+ <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources>
+ <include location="pmchart.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>HostDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>HostDialog</receiver>
+ <slot>reject()</slot>
+ </connection>
+ <connection>
+ <sender>proxyCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>HostDialog</receiver>
+ <slot>proxyCheckBox_toggled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>certificatesPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>HostDialog</receiver>
+ <slot>certificatesPushButton_clicked()</slot>
+ </connection>
+ <connection>
+ <sender>secureCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>HostDialog</receiver>
+ <slot>secureCheckBox_toggled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>authenticateCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>HostDialog</receiver>
+ <slot>authenticateCheckBox_toggled(bool)</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/infodialog.cpp b/src/pmchart/infodialog.cpp
new file mode 100644
index 0000000..aaa779a
--- /dev/null
+++ b/src/pmchart/infodialog.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "infodialog.h"
+#include <QMessageBox>
+#include "main.h"
+
+InfoDialog::InfoDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ my.pminfoStarted = false;
+ my.pmvalStarted = false;
+}
+
+void InfoDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void InfoDialog::reset(QString source, QString metric, QString instance,
+ int sourceType)
+{
+ pminfoTextEdit->setText(tr(""));
+ pmvalTextEdit->setText(tr(""));
+ my.pminfoStarted = false;
+ my.pmvalStarted = false;
+ my.metric = metric;
+ my.source = source;
+ my.sourceType = sourceType;
+ my.instance = instance;
+
+ infoTab->setCurrentWidget(pminfoTab);
+ infoTabCurrentChanged(0);
+}
+
+void InfoDialog::pminfo(void)
+{
+ my.pminfoProc = new QProcess(this);
+ QStringList arguments;
+
+ arguments << "-df";
+ switch (my.sourceType) {
+ case PM_CONTEXT_ARCHIVE:
+ arguments << "-a";
+ arguments << my.source;
+ // no help text in archive mode
+ break;
+ case PM_CONTEXT_LOCAL:
+ arguments << "-L";
+ // no host name in local mode
+ arguments << "-tT";
+ break;
+ default:
+ arguments << "-h";
+ arguments << my.source;
+ arguments << "-tT";
+ break;
+ }
+ arguments << my.metric;
+
+ connect(my.pminfoProc, SIGNAL(readyReadStandardOutput()),
+ this, SLOT(pminfoStdout()));
+ connect(my.pminfoProc, SIGNAL(readyReadStandardError()),
+ this, SLOT(pminfoStderr()));
+
+ my.pminfoProc->start("pminfo", arguments);
+}
+
+void InfoDialog::pminfoStdout()
+{
+ QString s(my.pminfoProc->readAllStandardOutput());
+ pminfoTextEdit->append(s);
+}
+
+void InfoDialog::pminfoStderr()
+{
+ QString s(my.pminfoProc->readAllStandardError());
+ pminfoTextEdit->append(s);
+}
+
+void InfoDialog::pmval(void)
+{
+ QStringList arguments;
+ QString port;
+ port.setNum(pmtime->port());
+
+ my.pmvalProc = new QProcess(this);
+ arguments << "-f4" << "-p" << port;
+ if (my.sourceType == PM_CONTEXT_ARCHIVE)
+ arguments << "-a" << my.source;
+ else if (my.sourceType == PM_CONTEXT_LOCAL)
+ arguments << "-L";
+ else
+ arguments << "-h" << my.source;
+ arguments << my.metric;
+
+ connect(my.pmvalProc, SIGNAL(readyReadStandardOutput()),
+ this, SLOT(pmvalStdout()));
+ connect(my.pmvalProc, SIGNAL(readyReadStandardError()),
+ this, SLOT(pmvalStderr()));
+ connect(this, SIGNAL(finished(int)), this, SLOT(quit()));
+ my.pmvalProc->start("pmval", arguments);
+}
+
+void InfoDialog::quit()
+{
+ if (my.pmvalStarted) {
+ my.pmvalProc->terminate();
+ my.pmvalStarted = false;
+ }
+ if (my.pminfoStarted) {
+ my.pminfoProc->terminate();
+ my.pminfoStarted = false;
+ }
+}
+
+void InfoDialog::pmvalStdout()
+{
+ QString s(my.pmvalProc->readAllStandardOutput());
+ s.trimmed();
+ pmvalTextEdit->append(s);
+}
+
+void InfoDialog::pmvalStderr()
+{
+ QString s(my.pmvalProc->readAllStandardError());
+ s.trimmed();
+ s.prepend("<b>");
+ s.append("</b>");
+ pmvalTextEdit->append(s);
+}
+
+void InfoDialog::infoTabCurrentChanged(int)
+{
+ if (infoTab->currentWidget() == pminfoTab) {
+ if (!my.pminfoStarted) {
+ pminfo();
+ my.pminfoStarted = true;
+ }
+ }
+ else if (infoTab->currentWidget() == pmvalTab) {
+ if (!my.pmvalStarted) {
+ pmval();
+ my.pmvalStarted = true;
+ }
+ }
+}
diff --git a/src/pmchart/infodialog.h b/src/pmchart/infodialog.h
new file mode 100644
index 0000000..9acbff6
--- /dev/null
+++ b/src/pmchart/infodialog.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef INFODIALOG_H
+#define INFODIALOG_H
+
+#include "ui_infodialog.h"
+#include <QtCore/QProcess>
+
+class InfoDialog : public QDialog, public Ui::InfoDialog
+{
+ Q_OBJECT
+
+public:
+ InfoDialog(QWidget* parent);
+ void reset(QString, QString, QString, int);
+ void pminfo();
+ void pmval();
+
+public slots:
+ virtual void pminfoStdout();
+ virtual void pminfoStderr();
+ virtual void pmvalStdout();
+ virtual void pmvalStderr();
+ virtual void infoTabCurrentChanged(int);
+ virtual void quit();
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ struct {
+ bool pminfoStarted;
+ bool pmvalStarted;
+ int sourceType;
+ QString source;
+ QString metric;
+ QString instance;
+ QProcess *pminfoProc;
+ QProcess *pmvalProc;
+ } my;
+};
+
+#endif // INFODIALOG_H
diff --git a/src/pmchart/infodialog.ui b/src/pmchart/infodialog.ui
new file mode 100644
index 0000000..f466101
--- /dev/null
+++ b/src/pmchart/infodialog.ui
@@ -0,0 +1,181 @@
+<ui version="4.0" stdsetdef="1" >
+ <class>InfoDialog</class>
+ <widget class="QDialog" name="InfoDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>413</width>
+ <height>330</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Metric Information</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/help-browser.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <item row="0" column="0" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer name="Horizontal Spacing2" >
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="sizeType" >
+ <enum>Expanding</enum>
+ </property>
+ <property name="orientation" >
+ <enum>Horizontal</enum>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QTabWidget" name="infoTab" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>250</width>
+ <height>150</height>
+ </size>
+ </property>
+ <widget class="QWidget" name="pminfoTab" >
+ <attribute name="title" >
+ <string>pminfo</string>
+ </attribute>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item row="0" column="0" >
+ <widget class="QTextEdit" name="pminfoTextEdit" >
+ <property name="font" >
+ <font>
+ <family>Monospace</family>
+ <pointsize>9</pointsize>
+ </font>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth" >
+ <number>1</number>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="undoRedoEnabled" >
+ <bool>false</bool>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pmvalTab" >
+ <attribute name="title" >
+ <string>pmval</string>
+ </attribute>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item row="0" column="0" >
+ <widget class="QTextEdit" name="pmvalTextEdit" >
+ <property name="font" >
+ <font>
+ <family>Monospace</family>
+ <pointsize>9</pointsize>
+ </font>
+ </property>
+ <property name="frameShape" >
+ <enum>QFrame::Box</enum>
+ </property>
+ <property name="frameShadow" >
+ <enum>QFrame::Sunken</enum>
+ </property>
+ <property name="lineWidth" >
+ <number>1</number>
+ </property>
+ <property name="undoRedoEnabled" >
+ <bool>false</bool>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>InfoDialog</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>infoTab</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>InfoDialog</receiver>
+ <slot>infoTabCurrentChanged(int)</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/main.cpp b/src/pmchart/main.cpp
new file mode 100644
index 0000000..93f2acf
--- /dev/null
+++ b/src/pmchart/main.cpp
@@ -0,0 +1,742 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QSettings>
+#include <QtGui/QStatusBar>
+#include <QtGui/QApplication>
+#include <QtGui/QDesktopWidget>
+#include "main.h"
+#include "openviewdialog.h"
+
+#define DESPERATE 0
+
+int Cflag;
+int Hflag;
+int Lflag;
+int Wflag;
+char *outfile;
+char *outgeometry;
+QFont *globalFont;
+Settings globalSettings;
+
+// Globals used to provide single instances of classes used across pmchart
+GroupControl *liveGroup; // one metrics class group for all hosts
+GroupControl *archiveGroup; // one metrics class group for all archives
+GroupControl *activeGroup; // currently active metric fetchgroup
+TimeControl *pmtime; // one timecontrol class for pmtime
+PmChart *pmchart;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_ALIGN,
+ PMOPT_ARCHIVE,
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ PMOPT_HOSTSFILE,
+ PMOPT_NAMESPACE,
+ PMOPT_SPECLOCAL,
+ PMOPT_LOCALPMDA,
+ PMOPT_ORIGIN,
+ PMOPT_GUIPORT,
+ PMOPT_START,
+ PMOPT_SAMPLES,
+ PMOPT_FINISH,
+ PMOPT_INTERVAL,
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_VERSION,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Display options"),
+ { "view", 1, 'c', "VIEW", "chart view(s) to load on startup" },
+ { "check", 0, 'C', 0, "parse views, report any errors and exit" },
+ { "font-size", 1, 'F', "SIZE", "use font of given size" },
+ { "font-family", 1, 'f', "FONT", "use font family" },
+ { "geometry", 1, 'g', "WxH", "image geometry Width x Height" },
+ { "output", 1, 'o', "FILE", "export image to FILE (type from suffix)" },
+ { "samples", 1, 's', "N", "buffer up N points of sample history" },
+ { "visible", 1, 'v', "N", "display N points of visible history" },
+ { "white", 0, 'W', 0, "export images using an opaque (white) background" },
+ PMAPI_OPTIONS_END
+};
+
+// a := a + b for struct timevals
+void tadd(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec += b->tv_usec;
+ if (a->tv_usec > 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec++;
+ }
+ a->tv_sec += b->tv_sec;
+}
+
+//
+// a : b for struct timevals ... <0 for a<b, ==0 for a==b, >0 for a>b
+//
+int tcmp(struct timeval *a, struct timeval *b)
+{
+ int res = (int)(a->tv_sec - b->tv_sec);
+ if (res == 0)
+ res = (int)(a->tv_usec - b->tv_usec);
+ return res;
+}
+
+// convert timeval to seconds
+double tosec(struct timeval t)
+{
+ return t.tv_sec + (t.tv_usec / 1000000.0);
+}
+
+// create a time range in seconds from (delta x points)
+double torange(struct timeval t, int points)
+{
+ return tosec(t) * points;
+}
+
+// conversion from seconds (double precision) to struct timeval
+void fromsec(double value, struct timeval *tv)
+{
+ tv->tv_sec = (time_t)value;
+ tv->tv_usec = (long)(((value - (double)tv->tv_sec) * 1000000.0));
+}
+
+// debugging, display seconds-since-epoch in human readable format
+char *timeString(double seconds)
+{
+ static char string[32];
+ time_t secs = (time_t)seconds;
+ char *s;
+
+ s = pmCtime(&secs, string);
+ s[strlen(s)-1] = '\0';
+ return s;
+}
+
+// return a string containing hour and milliseconds
+char *timeHiResString(double time)
+{
+ static char s[16];
+ char m[8];
+ time_t secs = (time_t)time;
+ struct tm t;
+
+ sprintf(m, "%.3f", time - floor(time));
+ pmLocaltime(&secs, &t);
+ sprintf(s, "%02d:%02d:%02d.%s", t.tm_hour, t.tm_min, t.tm_sec, m+2);
+ s[strlen(s)-1] = '\0';
+ return s;
+}
+
+void nomem(void)
+{
+ // no point trying to report anything ... dump core is the best bet
+ abort();
+}
+
+void setupEnvironment(void)
+{
+ char *value;
+ QString confirm = pmGetConfig("PCP_BIN_DIR");
+ confirm.prepend("PCP_XCONFIRM_PROG=");
+ confirm.append(QChar(__pmPathSeparator()));
+ confirm.append("pmquery");
+ if ((value = strdup((const char *)confirm.toAscii())) != NULL)
+ putenv(value);
+ if (getenv("PCP_STDERR") == NULL && // do not overwrite, for QA
+ ((value = strdup("PCP_STDERR=DISPLAY")) != NULL))
+ putenv(value);
+
+ QCoreApplication::setOrganizationName("PCP");
+ QCoreApplication::setApplicationName(pmProgname);
+}
+
+void writeSettings(void)
+{
+ QSettings userSettings;
+
+ userSettings.beginGroup(pmProgname);
+ if (globalSettings.chartDeltaModified) {
+ globalSettings.chartDeltaModified = false;
+ userSettings.setValue("chartDelta", globalSettings.chartDelta);
+ }
+ if (globalSettings.loggerDeltaModified) {
+ globalSettings.loggerDeltaModified = false;
+ userSettings.setValue("loggerDelta", globalSettings.loggerDelta);
+ }
+ if (globalSettings.sampleHistoryModified) {
+ globalSettings.sampleHistoryModified = false;
+ userSettings.setValue("sampleHistory", globalSettings.sampleHistory);
+ }
+ if (globalSettings.visibleHistoryModified) {
+ globalSettings.visibleHistoryModified = false;
+ userSettings.setValue("visibleHistory", globalSettings.visibleHistory);
+ }
+ if (globalSettings.defaultSchemeModified) {
+ globalSettings.defaultSchemeModified = false;
+ userSettings.setValue("defaultColorScheme",
+ globalSettings.defaultScheme.colorNames());
+ }
+ if (globalSettings.colorSchemesModified) {
+ globalSettings.colorSchemesModified = false;
+ userSettings.beginWriteArray("schemes");
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++) {
+ userSettings.setArrayIndex(i);
+ userSettings.setValue("name",
+ globalSettings.colorSchemes[i].name());
+ userSettings.setValue("colors",
+ globalSettings.colorSchemes[i].colorNames());
+ }
+ userSettings.endArray();
+ }
+ if (globalSettings.chartBackgroundModified) {
+ globalSettings.chartBackgroundModified = false;
+ userSettings.setValue("chartBackgroundColor",
+ globalSettings.chartBackgroundName);
+ }
+ if (globalSettings.chartHighlightModified) {
+ globalSettings.chartHighlightModified = false;
+ userSettings.setValue("chartHighlightColor",
+ globalSettings.chartHighlightName);
+ }
+ if (globalSettings.initialToolbarModified) {
+ globalSettings.initialToolbarModified = false;
+ userSettings.setValue("initialToolbar", globalSettings.initialToolbar);
+ }
+ if (globalSettings.nativeToolbarModified) {
+ globalSettings.nativeToolbarModified = false;
+ userSettings.setValue("nativeToolbar", globalSettings.nativeToolbar);
+ }
+ if (globalSettings.toolbarLocationModified) {
+ globalSettings.toolbarLocationModified = false;
+ userSettings.setValue("toolbarLocation",
+ globalSettings.toolbarLocation);
+ }
+ if (globalSettings.toolbarActionsModified) {
+ globalSettings.toolbarActionsModified = false;
+ userSettings.setValue("toolbarActions", globalSettings.toolbarActions);
+ }
+ if (globalSettings.fontFamilyModified) {
+ globalSettings.fontFamilyModified = false;
+ if (globalSettings.fontFamily != QString(PmChart::defaultFontFamily()))
+ userSettings.setValue("fontFamily", globalSettings.fontFamily);
+ else
+ userSettings.remove("fontFamily");
+ }
+ if (globalSettings.fontStyleModified) {
+ globalSettings.fontStyleModified = false;
+ if (globalSettings.fontStyle != QString("Normal"))
+ userSettings.setValue("fontStyle", globalSettings.fontStyle);
+ else
+ userSettings.remove("fontStyle");
+ }
+ if (globalSettings.fontSizeModified) {
+ globalSettings.fontSizeModified = false;
+ if (globalSettings.fontSize != PmChart::defaultFontSize())
+ userSettings.setValue("fontSize", globalSettings.fontSize);
+ else
+ userSettings.remove("fontSize");
+ }
+ if (globalSettings.savedHostsModified) {
+ globalSettings.savedHostsModified = false;
+ if (globalSettings.savedHosts.isEmpty() == false)
+ userSettings.setValue("savedHosts", globalSettings.savedHosts);
+ else
+ userSettings.remove("savedHosts");
+ }
+
+ userSettings.endGroup();
+}
+
+void checkHistory(int samples, int visible)
+{
+ // sanity checking on sample sizes
+ if (samples < PmChart::minimumPoints()) {
+ globalSettings.sampleHistory = PmChart::minimumPoints();
+ globalSettings.sampleHistoryModified = true;
+ }
+ if (samples > PmChart::maximumPoints()) {
+ globalSettings.sampleHistory = PmChart::maximumPoints();
+ globalSettings.sampleHistoryModified = true;
+ }
+ if (visible < PmChart::minimumPoints()) {
+ globalSettings.visibleHistory = PmChart::minimumPoints();
+ globalSettings.visibleHistoryModified = true;
+ }
+ if (visible > PmChart::maximumPoints()) {
+ globalSettings.visibleHistory = PmChart::maximumPoints();
+ globalSettings.visibleHistoryModified = true;
+ }
+ if (samples < visible) {
+ globalSettings.sampleHistory = globalSettings.visibleHistory;
+ globalSettings.sampleHistoryModified = true;
+ }
+}
+
+static void readSettings(void)
+{
+ QSettings userSettings;
+ userSettings.beginGroup(pmProgname);
+
+ //
+ // Parameters related to sampling
+ //
+ globalSettings.chartDeltaModified = false;
+ globalSettings.chartDelta = userSettings.value("chartDelta",
+ PmChart::defaultChartDelta()).toDouble();
+ globalSettings.loggerDeltaModified = false;
+ globalSettings.loggerDelta = userSettings.value("loggerDelta",
+ PmChart::defaultLoggerDelta()).toDouble();
+ globalSettings.sampleHistoryModified = false;
+ globalSettings.sampleHistory = userSettings.value("sampleHistory",
+ PmChart::defaultSampleHistory()).toInt();
+ globalSettings.visibleHistoryModified = false;
+ globalSettings.visibleHistory = userSettings.value("visibleHistory",
+ PmChart::defaultVisibleHistory()).toInt();
+ checkHistory(globalSettings.sampleHistory, globalSettings.visibleHistory);
+ if (globalSettings.sampleHistoryModified) {
+ userSettings.setValue("samplePoints", globalSettings.sampleHistory);
+ globalSettings.sampleHistoryModified = false;
+ }
+ if (globalSettings.visibleHistoryModified) {
+ userSettings.setValue("visiblePoints", globalSettings.visibleHistory);
+ globalSettings.visibleHistoryModified = false;
+ }
+
+ //
+ // Everything colour (scheme) related
+ //
+ QStringList colorList;
+ globalSettings.defaultSchemeModified = false;
+ if (userSettings.contains("defaultColorScheme") == true)
+ colorList = userSettings.value("defaultColorScheme").toStringList();
+ else
+ colorList
+ << "#ffff00" << "#0000ff" << "#ff0000" << "#008000" << "#ee82ee"
+ << "#aa5500" << "#666666" << "#aaff00" << "#aa00ff" << "#aaaa7f";
+ globalSettings.defaultScheme.setName("#-cycle");
+ globalSettings.defaultScheme.setModified(false);
+ globalSettings.defaultScheme.setColorNames(colorList);
+
+ int size = userSettings.beginReadArray("schemes");
+ for (int i = 0; i < size; i++) {
+ userSettings.setArrayIndex(i);
+ ColorScheme scheme;
+ scheme.setName(userSettings.value("name").toString());
+ scheme.setModified(false);
+ scheme.setColorNames(userSettings.value("colors").toStringList());
+ globalSettings.colorSchemes.append(scheme);
+ }
+ userSettings.endArray();
+
+ //
+ // Everything (else) colour related
+ //
+ globalSettings.chartBackgroundModified = false;
+ globalSettings.chartBackgroundName = userSettings.value(
+ "chartBackgroundColor", "#6ca2c9").toString();
+ globalSettings.chartBackground = QColor(globalSettings.chartBackgroundName);
+
+ globalSettings.chartHighlightModified = false;
+ globalSettings.chartHighlightName = userSettings.value(
+ "chartHighlightColor", "blue").toString();
+ globalSettings.chartHighlight = QColor(globalSettings.chartHighlightName);
+
+ //
+ // Toolbar user preferences
+ //
+ globalSettings.initialToolbarModified = false;
+ globalSettings.initialToolbar = userSettings.value(
+ "initialToolbar", 1).toInt();
+ globalSettings.nativeToolbarModified = false;
+ globalSettings.nativeToolbar = userSettings.value(
+ "nativeToolbar", 1).toInt();
+ globalSettings.toolbarLocationModified = false;
+ globalSettings.toolbarLocation = userSettings.value(
+ "toolbarLocation", 0).toInt();
+ QStringList actionList;
+ globalSettings.toolbarActionsModified = false;
+ if (userSettings.contains("toolbarActions") == true)
+ globalSettings.toolbarActions =
+ userSettings.value("toolbarActions").toStringList();
+ // else: (defaults come from the pmchart.ui interface specification)
+
+ //
+ // Font preferences
+ //
+ globalSettings.fontFamilyModified = false;
+ globalSettings.fontFamily = userSettings.value(
+ "fontFamily", PmChart::defaultFontFamily()).toString();
+ globalSettings.fontStyleModified = false;
+ QString fontStyle;
+ globalSettings.fontStyle = userSettings.value(
+ "fontStyle", "Normal").toString();
+ globalSettings.fontSizeModified = false;
+ globalSettings.fontSize = userSettings.value(
+ "fontSize", PmChart::defaultFontSize()).toInt();
+
+ //
+ // Saved Hosts list preferences
+ //
+ globalSettings.savedHostsModified = false;
+ if (userSettings.contains("savedHosts") == true)
+ globalSettings.savedHosts =
+ userSettings.value("savedHosts").toStringList();
+
+ userSettings.endGroup();
+}
+
+static void readSchemes(void)
+{
+ QChar sep(__pmPathSeparator());
+ QString schemes = pmGetConfig("PCP_VAR_DIR");
+ schemes.append(sep).append("config");
+ schemes.append(sep).append("pmchart");
+ schemes.append(sep).append("Schemes");
+
+ QFileInfo fi(schemes);
+ if (fi.exists())
+ OpenViewDialog::openView(schemes.toAscii());
+}
+
+// Get next color from given scheme or from default colors for #-cycle
+QColor nextColor(QString scheme, int *sequence)
+{
+ QList<QColor> colorList;
+ int seq = (*sequence)++;
+
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++) {
+ if (globalSettings.colorSchemes[i].name() == scheme) {
+ colorList = globalSettings.colorSchemes[i].colors();
+ break;
+ }
+ }
+ if (colorList.size() < 2) // common case
+ colorList = globalSettings.defaultScheme.colors();
+ if (colorList.size() < 2) // idiot user!?
+ colorList << QColor("yellow") << QColor("blue") << QColor("red")
+ << QColor("green") << QColor("violet");
+ seq %= colorList.size();
+ return colorList.at(seq);
+}
+
+static void setupViewGlobals()
+{
+ int w, h, points, x, y;
+
+ OpenViewDialog::globals(&w, &h, &points, &x, &y);
+ if (w || h) {
+ QSize size = pmchart->size().expandedTo(QSize(w, h));
+ QSize desk = QApplication::desktop()->availableGeometry().size();
+ pmchart->resize(size.boundedTo(desk));
+ }
+ if (x || y) {
+ QPoint pos = pmchart->pos();
+ if (x) pos.setX(x);
+ if (y) pos.setY(y);
+ pmchart->move(pos);
+ }
+ if (points) {
+ if (activeGroup->sampleHistory() < points)
+ activeGroup->setSampleHistory(points);
+ activeGroup->setVisibleHistory(points);
+ }
+}
+
+static int
+override(int opt, pmOptions *opts)
+{
+ (void)opts;
+ if (opt == 'g')
+ return 1;
+ if (opt == 'H')
+ Hflag = 1;
+ if (opt == 'L')
+ Lflag = 1;
+ return 0;
+}
+
+int
+main(int argc, char ** argv)
+{
+ int c, sts;
+ int sh = -1; /* sample history length */
+ int vh = -1; /* visible history length */
+ char *endnum;
+ Tab *tab;
+ struct timeval logStartTime;
+ struct timeval logEndTime;
+ QStringList configs;
+ QString tzLabel;
+ QString tzString;
+ pmOptions opts;
+
+ memset(&opts, 0, sizeof(opts));
+ __pmtimevalNow(&opts.origin);
+ __pmSetProgname(argv[0]);
+ QApplication a(argc, argv);
+ setupEnvironment();
+ readSettings();
+
+ opts.flags = PM_OPTFLAG_MULTI | PM_OPTFLAG_MIXED;
+ opts.short_options = "A:a:Cc:D:f:F:g:h:H:Ln:o:O:p:s:S:T:t:Vv:WzZ:?";
+ opts.long_options = longopts;
+ opts.short_usage = "[options] [sources]";
+ opts.override = override;
+
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'C':
+ Cflag++;
+ break;
+
+ case 'c':
+ configs.append(opts.optarg);
+ break;
+
+ case 'f':
+ globalSettings.fontFamily = opts.optarg;
+ break;
+
+ case 'F':
+ sts = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || c < 0) {
+ pmprintf("%s: -F requires a numeric argument\n", pmProgname);
+ opts.errors++;
+ } else {
+ globalSettings.fontSize = sts;
+ }
+ break;
+
+ case 'g':
+ outgeometry = opts.optarg;
+ break;
+
+ case 'o': /* output image file */
+ outfile = opts.optarg;
+ break;
+
+ case 'W': /* white image background */
+ Wflag = 1;
+ break;
+
+ case 'v': /* visible history */
+ vh = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || vh < 1) {
+ pmprintf("%s: -v requires a numeric argument, larger than 1\n",
+ pmProgname);
+ opts.errors++;
+ }
+ break;
+ }
+ }
+
+ /* hosts from a Hosts file are added to the SavedHosts list */
+ if (Hflag) {
+ for (int i = 0; i < opts.nhosts; i++)
+ globalSettings.savedHosts.append(opts.hosts[i]);
+ globalSettings.savedHostsModified = true;
+ }
+
+ if (opts.narchives > 0) {
+ while (opts.optind < argc)
+ __pmAddOptArchive(&opts, argv[opts.optind++]);
+ } else {
+ if (!Hflag) {
+ for (c = 0; c < globalSettings.savedHosts.size(); c++) {
+ const QString &host = globalSettings.savedHosts.at(c);
+ __pmAddOptHost(&opts, (char *)(const char *)host.toAscii());
+ }
+ }
+ while (opts.optind < argc)
+ __pmAddOptHost(&opts, argv[opts.optind++]);
+ }
+
+ if (opts.optind != argc)
+ opts.errors++;
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ /* set initial sampling interval from command line, else global setting */
+ if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0)
+ fromsec(globalSettings.chartDelta, &opts.interval);
+
+ console = new QedConsole(opts.origin);
+
+ //
+ // Deal with user requested sample/visible points globalSettings. These
+ // (command line) override the QSettings values, for this instance
+ // of pmchart. They should not be written though, unless requested
+ // later via the Settings dialog.
+ //
+ sh = opts.samples ? opts.samples : -1;
+ if (vh != -1 || sh != -1) {
+ if (sh == -1)
+ sh = globalSettings.sampleHistory;
+ if (vh == -1)
+ vh = globalSettings.visibleHistory;
+ checkHistory(sh, vh);
+ if (globalSettings.sampleHistoryModified ||
+ globalSettings.visibleHistoryModified) {
+ pmprintf("%s: invalid sample/visible history\n", pmProgname);
+ pmflush();
+ exit(1);
+ }
+ globalSettings.sampleHistory = sh;
+ globalSettings.visibleHistory = vh;
+ }
+ console->post("Global settings setup complete");
+
+ // Create all of the sources
+ liveGroup = new GroupControl();
+ archiveGroup = new GroupControl();
+ if (Lflag)
+ liveGroup->use(PM_CONTEXT_LOCAL, QmcSource::localHost);
+ sts = opts.nhosts + opts.narchives;
+ for (c = 0; c < opts.nhosts; c++)
+ if (liveGroup->use(PM_CONTEXT_HOST, opts.hosts[c]) < 0)
+ sts--;
+ for (c = 0; c < opts.narchives; c++)
+ if (archiveGroup->use(PM_CONTEXT_ARCHIVE, opts.archives[c]) < 0)
+ sts--;
+ if (Lflag == 0 && sts == 0)
+ liveGroup->createLocalContext();
+ pmflush();
+ console->post("Metric group setup complete (%d hosts, %d archives)",
+ opts.nhosts, opts.narchives);
+
+ if (opts.tzflag) {
+ if (opts.narchives > 0)
+ archiveGroup->useTZ();
+ if (opts.nhosts > 0)
+ liveGroup->useTZ();
+ }
+ else if (opts.timezone != NULL) {
+ if (opts.narchives > 0)
+ archiveGroup->useTZ(QString(opts.timezone));
+ if (opts.nhosts > 0)
+ liveGroup->useTZ(QString(opts.timezone));
+ if ((sts = pmNewZone(opts.timezone)) < 0) {
+ pmprintf("%s: cannot set timezone to \"%s\": %s\n",
+ pmProgname, (char *)opts.timezone, pmErrStr(sts));
+ pmflush();
+ exit(1);
+ }
+ }
+
+ //
+ // Choose which Tab will be displayed initially - archive/live.
+ // If any archives given on command line, we go Archive mode;
+ // otherwise Live mode wins. Our initial pmtime connection is
+ // set in that mode too. Later we'll make a second connection
+ // in the other mode (and only "on-demand").
+ //
+ if (opts.narchives > 0) {
+ archiveGroup->defaultTZ(tzLabel, tzString);
+ archiveGroup->updateBounds();
+ logStartTime = archiveGroup->logStart();
+ logEndTime = archiveGroup->logEnd();
+ if ((sts = pmParseTimeWindow(opts.start_optarg, opts.finish_optarg,
+ opts.align_optarg, opts.origin_optarg,
+ &logStartTime, &logEndTime, &opts.start,
+ &opts.finish, &opts.origin, &endnum)) < 0) {
+ pmprintf("Cannot parse archive time window\n%s\n", endnum);
+ pmUsageMessage(&opts);
+ free(endnum);
+ exit(1);
+ }
+ // move position to account for initial visible points
+ if (tcmp(&opts.origin, &opts.start) <= 0)
+ for (c = 0; c < globalSettings.visibleHistory - 2; c++)
+ tadd(&opts.origin, &opts.interval);
+ if (tcmp(&opts.origin, &opts.finish) > 0)
+ opts.origin = opts.finish;
+ }
+ else {
+ liveGroup->defaultTZ(tzLabel, tzString);
+ __pmtimevalNow(&logStartTime);
+ logEndTime.tv_sec = logEndTime.tv_usec = INT_MAX;
+ if ((sts = pmParseTimeWindow(opts.start_optarg, opts.finish_optarg,
+ opts.align_optarg, opts.origin_optarg,
+ &logStartTime, &logEndTime, &opts.start,
+ &opts.finish, &opts.origin, &endnum)) < 0) {
+ pmprintf("Cannot parse live time window\n%s\n", endnum);
+ pmUsageMessage(&opts);
+ free(endnum);
+ exit(1);
+ }
+ }
+ console->post("Timezones and time window setup complete");
+
+ globalFont = new QFont(globalSettings.fontFamily, globalSettings.fontSize);
+ if (globalSettings.fontStyle.contains("Italic"))
+ globalFont->setItalic(true);
+ if (globalSettings.fontStyle.contains("Bold"))
+ globalFont->setBold(true);
+
+ tab = new Tab;
+ fileIconProvider = new QedFileIconProvider();
+
+ pmchart = new PmChart;
+ pmtime = new TimeControl;
+
+ console->post("Phase1 user interface constructors complete");
+
+ // Start pmtime process for time management
+ pmtime->init(opts.guiport, opts.narchives == 0, &opts.interval, &opts.origin,
+ &opts.start, &opts.finish, tzString, tzLabel);
+
+ pmchart->init();
+ liveGroup->init(globalSettings.sampleHistory,
+ globalSettings.visibleHistory,
+ pmtime->liveInterval(), pmtime->livePosition());
+ archiveGroup->init(globalSettings.sampleHistory,
+ globalSettings.visibleHistory,
+ pmtime->archiveInterval(), pmtime->archivePosition());
+
+ //
+ // We setup the pmchart tab list late, so we don't have to deal
+ // with pmtime messages reaching the Tabs until we're all setup.
+ //
+ if (opts.narchives == 0)
+ tab->init(pmchart->tabWidget(), liveGroup, "Live");
+ else
+ tab->init(pmchart->tabWidget(), archiveGroup, "Archive");
+ pmchart->tabWidget()->insertTab(tab);
+ pmchart->setActiveTab(0, true);
+ console->post("Phase2 user interface setup complete");
+
+ readSchemes();
+ for (c = 0; c < configs.size(); c++)
+ if (!OpenViewDialog::openView((const char *)configs[c].toAscii()))
+ opts.errors++;
+ if (opts.errors)
+ exit(1);
+ setupViewGlobals();
+ pmflush();
+
+ if (Cflag) // done with -c config, quit
+ return 0;
+
+ pmchart->enableUi();
+ pmchart->show();
+ console->post("Top level window shown");
+
+ a.connect(&a, SIGNAL(lastWindowClosed()), pmchart, SLOT(quit()));
+ return a.exec();
+}
diff --git a/src/pmchart/main.h b/src/pmchart/main.h
new file mode 100644
index 0000000..f09c77a
--- /dev/null
+++ b/src/pmchart/main.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <stdio.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+#include "tab.h"
+#include "colorscheme.h"
+#include "qed_console.h"
+#include "timecontrol.h"
+#include "qed_fileiconprovider.h"
+#include "pmchart.h"
+
+typedef struct {
+ // Sampling
+ double chartDelta;
+ bool chartDeltaModified;
+ double loggerDelta;
+ bool loggerDeltaModified;
+ int sampleHistory;
+ bool sampleHistoryModified;
+ int visibleHistory;
+ bool visibleHistoryModified;
+
+ // Default Colors
+ QColor chartBackground;
+ QString chartBackgroundName;
+ bool chartBackgroundModified;
+ QColor chartHighlight;
+ QString chartHighlightName;
+ bool chartHighlightModified;
+ // Color Schemes
+ ColorScheme defaultScheme;
+ bool defaultSchemeModified;
+ QList<ColorScheme> colorSchemes;
+ bool colorSchemesModified;
+
+ // Toolbar
+ int initialToolbar;
+ bool initialToolbarModified;
+ int nativeToolbar;
+ bool nativeToolbarModified;
+ int toolbarLocation;
+ int toolbarLocationModified;
+ QStringList toolbarActions;
+ bool toolbarActionsModified;
+
+ // Font
+ QString fontFamily;
+ bool fontFamilyModified;
+ QString fontStyle;
+ bool fontStyleModified;
+ int fontSize;
+ bool fontSizeModified;
+
+ // Saved Hosts
+ QStringList savedHosts;
+ bool savedHostsModified;
+} Settings;
+
+extern Settings globalSettings;
+extern void writeSettings();
+extern QColor nextColor(QString, int *);
+
+extern int Cflag;
+extern int Lflag;
+extern int Wflag;
+extern char *outfile;
+extern char *outgeometry;
+
+extern QFont *globalFont;
+
+extern GroupControl *activeGroup;
+extern GroupControl *liveGroup;
+extern GroupControl *archiveGroup;
+
+class PmChart;
+extern PmChart *pmchart;
+
+class TimeControl;
+extern TimeControl *pmtime;
+
+extern double tosec(struct timeval);
+extern double torange(struct timeval, int);
+extern void fromsec(double, struct timeval *);
+extern char *timeString(double);
+extern char *timeHiResString(double);
+extern void nomem(void);
+
+/*
+ * number of Y pixels to move the time axis up when exporting to
+ * an image or printing
+ */
+#define TIMEAXISFUDGE 0
+
+#endif // MAIN_H
diff --git a/src/pmchart/namespace.cpp b/src/pmchart/namespace.cpp
new file mode 100644
index 0000000..f2f91c9
--- /dev/null
+++ b/src/pmchart/namespace.cpp
@@ -0,0 +1,585 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QtCore/QList>
+#include <QtCore/QString>
+#include <QtGui/QApplication>
+#include <QtGui/QMessageBox>
+#include <QtGui/QListView>
+#include "namespace.h"
+#include "chart.h"
+#include "main.h"
+
+#define DESPERATE 0
+
+NameSpace::NameSpace(NameSpace *parent, const QString &name, bool inst)
+ : QTreeWidgetItem(parent, QTreeWidgetItem::UserType)
+{
+ my.expanded = false;
+ my.back = parent;
+ my.desc = parent->my.desc;
+ my.context = parent->my.context;
+ my.basename = name;
+ if (name == QString::null)
+ my.type = ChildMinder;
+ else if (!inst)
+ my.type = NoType;
+ else {
+ my.type = InstanceName;
+ QFont font = QTreeWidgetItem::font(0);
+ font.setItalic(true);
+ setFont(0, font);
+ }
+ setText(0, my.basename);
+#if DESPERATE
+ if (my.type == ChildMinder) {
+ console->post(PmChart::DebugUi, "Added namespace childminder");
+ }
+ else {
+ console->post(PmChart::DebugUi, "Added non-root namespace node %s (inst=%d)",
+ (const char *)my.basename.toAscii(), inst);
+ }
+#endif
+}
+
+NameSpace::NameSpace(QTreeWidget *list, const QmcContext *context)
+ : QTreeWidgetItem(list, QTreeWidgetItem::UserType)
+{
+ my.expanded = false;
+ my.back = this;
+ memset(&my.desc, 0, sizeof(my.desc));
+ my.context = (QmcContext *)context;
+ switch (my.context->source().type()) {
+ case PM_CONTEXT_ARCHIVE:
+ my.basename = context->source().source();
+ my.icon = QIcon(":/images/archive.png");
+ my.type = ArchiveRoot;
+ break;
+ case PM_CONTEXT_LOCAL:
+ my.basename = QString("Local context");
+ my.icon = QIcon(":/images/emblem-system.png");
+ my.type = LocalRoot;
+ break;
+ default:
+ my.basename = context->source().source();
+ my.icon = QIcon(":/images/computer.png");
+ my.type = HostRoot;
+ break;
+ }
+ setToolTip(0, sourceTip());
+ setText(0, my.basename);
+ setIcon(0, my.icon);
+
+ console->post(PmChart::DebugUi, "Added root namespace node %s",
+ (const char *)my.basename.toAscii());
+}
+
+QString NameSpace::sourceTip()
+{
+ QString tooltip;
+ QmcSource source = my.context->source();
+
+ tooltip = "Performance metrics from host ";
+ tooltip.append(source.host());
+
+ if (my.context->source().type() == PM_CONTEXT_ARCHIVE) {
+ tooltip.append("\n commencing ");
+ tooltip.append(source.startTime());
+ tooltip.append("\n ending ");
+ tooltip.append(source.endTime());
+ }
+ tooltip.append("\nTimezone: ");
+ tooltip.append(source.timezone());
+ return tooltip;
+}
+
+int NameSpace::sourceType()
+{
+ return my.context->source().type();
+}
+
+QString NameSpace::sourceName()
+{
+ return my.context->source().source();
+}
+
+QString NameSpace::metricName()
+{
+ QString s;
+
+ if (my.back->isRoot())
+ s = text(0);
+ else if (my.type == InstanceName)
+ s = my.back->metricName();
+ else {
+ s = my.back->metricName();
+ s.append(".");
+ s.append(text(0));
+ }
+ return s;
+}
+
+QString NameSpace::metricInstance()
+{
+ if (my.type == InstanceName)
+ return text(0);
+ return QString::null;
+}
+
+void NameSpace::setExpanded(bool expand, bool show)
+{
+#if DESPERATE
+ console->post(PmChart::DebugUi, "NameSpace::setExpanded "
+ "on %p %s (type=%d expanded=%s, expand=%s, show=%s)",
+ this, (const char *)metricName().toAscii(),
+ my.type,
+ my.expanded? "y" : "n", expand? "y" : "n", show? "y" : "n");
+#endif
+
+ if (expand && !my.expanded) {
+ NameSpace *kid = (NameSpace *)child(0);
+
+ if (kid && kid->isChildMinder()) {
+ takeChild(0);
+ delete kid;
+ }
+ my.expanded = true;
+ pmUseContext(my.context->handle());
+
+ if (my.type == LeafWithIndom)
+ expandInstanceNames(show);
+ else if (my.type != InstanceName) {
+ expandMetricNames(isRoot() ? "" : metricName(), show);
+ }
+ }
+
+ if (show) {
+ QTreeWidgetItem::setExpanded(expand);
+ }
+
+}
+
+void NameSpace::setSelectable(bool selectable)
+{
+ if (selectable)
+ setFlags(flags() | Qt::ItemIsSelectable);
+ else
+ setFlags(flags() & ~Qt::ItemIsSelectable);
+}
+
+void NameSpace::setExpandable(bool expandable)
+{
+ console->post(PmChart::DebugUi, "NameSpace::setExpandable "
+ "on %p %s (expanded=%s, expandable=%s)",
+ this, (const char *)metricName().toAscii(),
+ my.expanded ? "y" : "n", expandable ? "y" : "n");
+
+ // NOTE: QT4.3 has setChildIndicatorPolicy for this workaround, but we want
+ // to work on QT4.2 as well (this is used on Debian 4.0 - i.e. my laptop!).
+ // This is the ChildMinder workaround - we insert a "dummy" child into items
+ // that we know have children (since we have no way to explicitly set it and
+ // we want to delay finding the actual children as late as possible).
+ // When we later do fill in the real kids, we first delete the ChildMinder.
+
+ if (expandable)
+ addChild(new NameSpace(this, QString::null, false));
+}
+
+static char *namedup(const char *name, const char *suffix)
+{
+ char *n;
+
+ if (strlen(name) > 0) {
+ n = (char *)malloc(strlen(name) + 1 + strlen(suffix) + 1);
+ sprintf(n, "%s.%s", name, suffix);
+ }
+ else {
+ n = strdup(suffix);
+ }
+ return n;
+}
+
+void NameSpace::setFailed(bool failed)
+{
+ bool selectable = (failed == false &&
+ (my.type == LeafNullIndom || my.type == InstanceName));
+ setSelectable(selectable);
+ bool expandable = (failed == false &&
+ (my.type != LeafNullIndom && my.type != InstanceName));
+ setExpandable(expandable);
+
+ QFont font = QTreeWidgetItem::font(0);
+ font.setStrikeOut(failed);
+ setFont(0, font);
+}
+
+void NameSpace::expandMetricNames(QString parent, bool show)
+{
+ char **offspring = NULL;
+ int *status = NULL;
+ pmID *pmidlist = NULL;
+ int i, nleaf = 0;
+ int sts, noffspring;
+ NameSpace *m, **leaflist = NULL;
+ char *name = strdup(parent.toAscii());
+ int sort_done, fail_count = 0;
+ QString failmsg;
+
+ sts = pmGetChildrenStatus(name, &offspring, &status);
+ if (sts < 0) {
+ if (!show)
+ goto done;
+ QString msg = QString();
+ if (isRoot())
+ msg.sprintf("Cannot get metric names from source\n%s: %s.\n\n",
+ (const char *)my.basename.toAscii(), pmErrStr(sts));
+ else
+ msg.sprintf("Cannot get children of node\n\"%s\".\n%s.\n\n",
+ name, pmErrStr(sts));
+ QMessageBox::warning(NULL, pmProgname, msg,
+ QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ goto done;
+ }
+ else {
+ noffspring = sts;
+ }
+
+ // Ugliness ahead.
+ // The Qt routine sortChildren() does not work as we maintain
+ // our own pointers into the tree items via my.back ... if
+ // sortChildren() is used, our expansion picking does not work later
+ // on.
+ // Sort the PMNS children lexicographically by name before adding them
+ // into the QTreeWidget ... only tricky part is that we need to sort
+ // offspring[] AND status[]
+ // Bubble Sort is probably OK as the number of descendents in the
+ // PMNS is never huge.
+ //
+ sort_done = 0;
+ while (!sort_done) {
+ char *ctmp;
+ int itmp;
+ sort_done = 1;
+ for (i = 0; i < noffspring-1; i++) {
+ if (strcmp(offspring[i], offspring[i+1]) <= 0)
+ continue;
+ // swap
+ ctmp = offspring[i];
+ offspring[i] = offspring[i+1];
+ offspring[i+1] = ctmp;
+ itmp = status[i];
+ status[i] = status[i+1];
+ status[i+1] = itmp;
+ sort_done = 0;
+ }
+ }
+
+ for (i = 0; i < noffspring; i++) {
+ m = new NameSpace(this, offspring[i], false);
+
+ if (status[i] == PMNS_NONLEAF_STATUS) {
+ m->setSelectable(false);
+ m->setExpandable(true);
+ m->my.type = NonLeafName;
+ }
+ else {
+ // type still not set, could be LEAF_NULL_INDOM or LEAF_WITH_INDOM
+ // here we add this NameSpace pointer to the leaf list, and also
+ // modify the offspring list to only contain names (when done).
+ leaflist = (NameSpace **)realloc(leaflist,
+ (nleaf + 1) * sizeof(*leaflist));
+ leaflist[nleaf] = m;
+ offspring[nleaf] = namedup(name, offspring[i]);
+ nleaf++;
+ }
+ }
+
+ if (nleaf == 0) {
+ my.expanded = true;
+ goto done;
+ }
+
+ pmidlist = (pmID *)malloc(nleaf * sizeof(*pmidlist));
+ if ((sts = pmLookupName(nleaf, offspring, pmidlist)) < 0) {
+ if (!show)
+ goto done;
+ failmsg.sprintf("Cannot find PMIDs: %s.\n", pmErrStr(sts));
+ for (i = 0; i < nleaf; i++) {
+ leaflist[i]->setFailed(true);
+ if (pmidlist[i] == PM_ID_NULL) {
+ if (fail_count == 0)
+ failmsg.append("Metrics:\n");
+ if (fail_count < 5)
+ failmsg.append(" ").append(offspring[i]).append("\n");
+ else if (fail_count == 5)
+ failmsg.append("... (further metrics omitted).\n");
+ fail_count++;
+ }
+ }
+ fail_count = fail_count ? fail_count : 1;
+ }
+ else {
+ for (i = 0; i < nleaf; i++) {
+ m = leaflist[i];
+ sts = pmLookupDesc(pmidlist[i], &m->my.desc);
+ if (sts < 0) {
+ if (!show)
+ goto done;
+ if (fail_count < 3) {
+ failmsg.append("Cannot find metric descriptor at \"");
+ failmsg.append(offspring[i]).append("\":\n ");
+ failmsg.append(pmErrStr(sts)).append(".\n\n");
+ }
+ else if (fail_count == 3) {
+ failmsg.append("... (further errors omitted).\n");
+ }
+ m->setFailed(true);
+ fail_count++;
+ }
+ else if (m->my.desc.indom == PM_INDOM_NULL) {
+ m->my.type = LeafNullIndom;
+ m->setExpandable(false);
+ m->setSelectable(true);
+ }
+ else {
+ m->my.type = LeafWithIndom;
+ m->setExpandable(true);
+ m->setSelectable(false);
+ }
+ }
+ my.expanded = true;
+ }
+
+done:
+ if (fail_count)
+ QMessageBox::warning(NULL, pmProgname, failmsg,
+ QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ if (pmidlist)
+ free(pmidlist);
+ if (leaflist)
+ free(leaflist);
+ if (offspring) {
+ for (i = 0; i < nleaf; i++)
+ free(offspring[i]);
+ free(offspring);
+ }
+ if (status)
+ free(status);
+ free(name);
+}
+
+void NameSpace::expandInstanceNames(bool show)
+{
+ int sts, i;
+ int ninst = 0;
+ int *instlist = NULL;
+ char **namelist = NULL;
+ bool live = (my.context->source().type() != PM_CONTEXT_ARCHIVE);
+
+ sts = live ? pmGetInDom(my.desc.indom, &instlist, &namelist) :
+ pmGetInDomArchive(my.desc.indom, &instlist, &namelist);
+ if (sts < 0) {
+ if (!show)
+ goto done;
+ QString msg = QString();
+ msg.sprintf("Error fetching instance domain at node \"%s\".\n%s.\n\n",
+ (const char *)metricName().toAscii(), pmErrStr(sts));
+ QMessageBox::warning(NULL, pmProgname, msg,
+ QMessageBox::Ok | QMessageBox::Default |
+ QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ else {
+ ninst = sts;
+ my.expanded = true;
+ }
+
+ for (i = 0; i < ninst; i++) {
+ NameSpace *m = new NameSpace(this, namelist[i], true);
+
+ m->setExpandable(false);
+ m->setSelectable(true);
+ }
+
+done:
+ if (instlist)
+ free(instlist);
+ if (namelist)
+ free(namelist);
+}
+
+QString NameSpace::text(int column) const
+{
+ if (column > 0)
+ return QString::null;
+ return my.basename;
+}
+
+QIcon NameSpace::icon(int) const
+{
+ return my.icon;
+}
+
+void NameSpace::setIcon(int i, const QIcon &icon)
+{
+ my.icon = icon;
+ QTreeWidgetItem::setIcon(i, icon);
+}
+
+NameSpace *NameSpace::dup(QTreeWidget *list)
+{
+ NameSpace *n;
+
+ n = new NameSpace(list, my.context);
+ n->expand();
+ n->setSelectable(false);
+ return n;
+}
+
+NameSpace *NameSpace::dup(QTreeWidget *, NameSpace *tree,
+ QString scheme, int *sequence)
+{
+ NameSpace *n;
+
+ n = new NameSpace(tree, my.basename, my.type == InstanceName);
+ n->my.context = my.context;
+ n->my.desc = my.desc;
+ n->my.type = my.type;
+
+ if (my.type == NoType || my.type == ChildMinder) {
+ console->post("NameSpace::dup bad type=%d on %p %s)",
+ my.type, this, (const char *)metricName().toAscii());
+ abort();
+ }
+ else if (!isLeaf()) {
+ n->expand();
+ n->setSelectable(false);
+ }
+ else {
+ n->expand();
+ n->setSelectable(true);
+
+ QColor c = nextColor(scheme, sequence);
+ n->setOriginalColor(c);
+ n->setCurrentColor(c, NULL);
+
+ // this is a leaf so walk back up to the root, opening each node up
+ NameSpace *up;
+ for (up = tree; up->my.back != up; up = up->my.back) {
+ up->expand();
+ up->setExpanded(true, true);
+ }
+ up->expand();
+ up->setExpanded(true, true); // add the host/archive root as well.
+
+ n->setSelected(true);
+ }
+ return n;
+}
+
+bool NameSpace::cmp(NameSpace *item)
+{
+ if (!item) // empty list
+ return false;
+ return (item->text(0) == my.basename);
+}
+
+void NameSpace::addToTree(QTreeWidget *target, QString scheme, int *sequence)
+{
+ QList<NameSpace *> nodelist;
+ NameSpace *node;
+
+ // Walk through each component of this name, creating them in the
+ // target list (if not there already), right down to the leaf.
+ // We hold everything in a temporary list since we need to add to
+ // the target in root-to-leaf order but we're currently at a leaf.
+
+ for (node = this; node->my.back != node; node = node->my.back)
+ nodelist.prepend(node);
+ nodelist.prepend(node); // add the host/archive root as well.
+
+ NameSpace *tree = (NameSpace *)target->invisibleRootItem();
+ NameSpace *item = NULL;
+
+ for (int n = 0; n < nodelist.size(); n++) {
+ node = nodelist.at(n);
+ bool foundMatchingName = false;
+ for (int i = 0; i < tree->childCount(); i++) {
+ item = (NameSpace *)tree->child(i);
+ if (node->cmp(item)) {
+ // no insert at this level necessary, move down a level
+ if (!node->isLeaf()) {
+ tree = item;
+ item = (NameSpace *)item->child(0);
+ }
+ // already there, just select the existing name
+ else {
+ item->setSelected(true);
+ }
+ foundMatchingName = true;
+ break;
+ }
+ }
+
+ // When no more children and no match so far, we dup & insert
+ if (foundMatchingName == false) {
+ if (node->isRoot()) {
+ tree = node->dup(target);
+ }
+ else if (tree) {
+ tree = node->dup(target, tree, scheme, sequence);
+ }
+ }
+ }
+}
+
+void NameSpace::removeFromTree(QTreeWidget *)
+{
+ NameSpace *self, *tree, *node = this;
+
+ this->setSelected(false);
+ do {
+ self = node;
+ tree = node->my.back;
+ if (node->childCount() == 0)
+ delete node;
+ node = tree;
+ } while (self != tree);
+}
+
+void NameSpace::setCurrentColor(QColor color, QTreeWidget *treeview)
+{
+ QPixmap pix(8, 8);
+
+ // Set the current metric color, and (optionally) move on to the next
+ // leaf node in the view (if given treeview parameter is non-NULL).
+ // We're taking a punt here that the user will move down through the
+ // metrics just added, and set their prefered color on each one; so,
+ // by doing the next selection automatically, we save some clicks.
+
+ my.current = color;
+ pix.fill(color);
+ setIcon(0, QIcon(pix));
+
+ if (treeview) {
+ QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selectable);
+ if (*it) {
+ (*it)->setSelected(true);
+ this->setSelected(false);
+ }
+ }
+}
diff --git a/src/pmchart/namespace.h b/src/pmchart/namespace.h
new file mode 100644
index 0000000..ba73fb2
--- /dev/null
+++ b/src/pmchart/namespace.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef NAMESPACE_H
+#define NAMESPACE_H
+
+#include <QtCore/QString>
+#include <QtGui/QTreeWidget>
+#include <QtGui/QTreeWidgetItem>
+#include <pcp/pmapi.h>
+
+class Chart;
+class QmcContext;
+
+class NameSpace : public QTreeWidgetItem
+{
+public:
+ typedef enum {
+ NoType,
+ ChildMinder,
+ ArchiveRoot,
+ LocalRoot,
+ HostRoot,
+ NonLeafName,
+ LeafNullIndom,
+ LeafWithIndom,
+ InstanceName,
+ } Type;
+
+ NameSpace(QTreeWidget *, const QmcContext *); // for root nodes only
+ NameSpace(NameSpace *, const QString &, bool); // for all other nodes
+
+ QString text(int) const;
+ QIcon icon(int) const;
+ void setIcon(int, const QIcon &);
+ void expand() { my.expanded = true; }
+ void setExpanded(bool expanded, bool show);
+
+ pmDesc desc() const { return my.desc; }
+ int sourceType();
+ QString sourceName();
+ QString metricName();
+ QString metricInstance();
+ QmcContext *metricContext() { return my.context; }
+
+ void setType(Type type) { my.type = type; }
+ bool isRoot() { return my.type == HostRoot ||
+ my.type == LocalRoot ||
+ my.type == ArchiveRoot; }
+ bool isLeaf() { return my.type == InstanceName ||
+ my.type == LeafNullIndom; }
+ bool isMetric() { return my.type == LeafWithIndom ||
+ my.type == LeafNullIndom; }
+ bool isNonLeaf() { return my.type == HostRoot ||
+ my.type == LocalRoot ||
+ my.type == ArchiveRoot ||
+ my.type == NonLeafName; }
+ bool isInst() { return my.type == InstanceName; }
+ bool isChildMinder() { return my.type == ChildMinder; }
+
+ void addToTree(QTreeWidget *, QString, int *); // add leaf node to Selected
+ void removeFromTree(QTreeWidget *); // remove leaf nodes from Selected set
+
+ QColor currentColor() { return my.current; }
+ void setCurrentColor(QColor, QTreeWidget *);
+ QColor originalColor() { return my.original; }
+ void setOriginalColor(QColor original) { my.original = original; }
+
+ QString label() const { return my.label; }
+ void setLabel(const QString &label) { my.label = label; }
+
+ void setSelectable(bool selectable);
+ void setExpandable(bool expandable);
+ void setFailed(bool failed);
+
+private:
+ void expandMetricNames(QString, bool);
+ void expandInstanceNames(bool);
+ QString sourceTip();
+
+ bool cmp(NameSpace *);
+ NameSpace *dup(QTreeWidget *); // copies the root node in addToTree
+ NameSpace *dup(QTreeWidget *, NameSpace *, QString, int *); // all other nodes
+
+ struct {
+ bool expanded; // pmGet{ChildrenStatus,Indom} done
+ pmDesc desc; // metric descriptor for metric leaves
+ QmcContext *context; // metrics class metric context
+ QIcon icon;
+ QColor current; // color we'll use if OK'd
+ QColor original; // color we started with
+ QString basename;
+ QString label; // proxy (live), host (archive), legend label
+ NameSpace *back;
+ NameSpace::Type type;
+ } my;
+};
+
+#endif // NAMESPACE_H
diff --git a/src/pmchart/openviewdialog.cpp b/src/pmchart/openviewdialog.cpp
new file mode 100644
index 0000000..d2b63e6
--- /dev/null
+++ b/src/pmchart/openviewdialog.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "openviewdialog.h"
+#include <QtGui/QCompleter>
+#include <QtGui/QFileDialog>
+#include <QtGui/QMessageBox>
+#include "main.h"
+#include "hostdialog.h"
+
+OpenViewDialog::OpenViewDialog(QWidget *parent) : QDialog(parent)
+{
+ setupUi(this);
+ my.dirModel = new QDirModel;
+ my.dirModel->setIconProvider(fileIconProvider);
+ dirListView->setModel(my.dirModel);
+
+ my.completer = new QCompleter;
+ my.completer->setModel(my.dirModel);
+ fileNameLineEdit->setCompleter(my.completer);
+
+ connect(dirListView->selectionModel(),
+ SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
+ this, SLOT(dirListView_selectionChanged()));
+
+ QDir dir;
+ QChar sep(__pmPathSeparator());
+ QString sys = my.systemDir = pmGetConfig("PCP_VAR_DIR");
+ my.systemDir.append(sep);
+ my.systemDir.append("config");
+ my.systemDir.append(sep);
+ my.systemDir.append("kmchart");
+ if (dir.exists(my.systemDir))
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ my.systemDir);
+ my.systemDir = sys;
+ my.systemDir.append(sep);
+ my.systemDir.append("config");
+ my.systemDir.append(sep);
+ my.systemDir.append("pmchart");
+ if (dir.exists(my.systemDir))
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ my.systemDir);
+
+ QString home = my.userDir = QDir::toNativeSeparators(QDir::homePath());
+ my.userDir.append(sep);
+ my.userDir.append(".pcp");
+ my.userDir.append(sep);
+ my.userDir.append("kmchart");
+ if (dir.exists(my.userDir))
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ my.userDir);
+ my.userDir = home;
+ my.userDir.append(sep);
+ my.userDir.append(".pcp");
+ my.userDir.append(sep);
+ my.userDir.append("pmchart");
+ if (dir.exists(my.userDir))
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ my.userDir);
+
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ home);
+}
+
+OpenViewDialog::~OpenViewDialog()
+{
+ delete my.completer;
+ delete my.dirModel;
+}
+
+void OpenViewDialog::reset()
+{
+ if ((my.archiveSource = pmchart->isArchiveTab())) {
+ sourceLabel->setText(tr("Archive:"));
+ sourcePushButton->setIcon(QIcon(":/images/archive.png"));
+ } else {
+ sourceLabel->setText(tr("Host:"));
+ sourcePushButton->setIcon(QIcon(":/images/computer.png"));
+ }
+ setupComboBoxes(my.archiveSource);
+ setPath(my.systemDir);
+}
+
+void OpenViewDialog::setPathUi(const QString &path)
+{
+ if (path.isEmpty())
+ return;
+
+ int index = pathComboBox->findText(path);
+ if (index == -1) {
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ path);
+ index = pathComboBox->count() - 1;
+ }
+ pathComboBox->setCurrentIndex(index);
+ dirListView->selectionModel()->clear();
+
+ userToolButton->setChecked(path == my.userDir);
+ systemToolButton->setChecked(path == my.systemDir);
+
+ fileNameLineEdit->setModified(false);
+ fileNameLineEdit->clear();
+}
+
+void OpenViewDialog::setPath(const QModelIndex &index)
+{
+ console->post("OpenViewDialog::setPath QModelIndex path=%s",
+ (const char *)my.dirModel->filePath(index).toAscii());
+ my.dirIndex = index;
+ my.dirModel->refresh(index);
+ dirListView->setRootIndex(index);
+ setPathUi(my.dirModel->filePath(index));
+}
+
+void OpenViewDialog::setPath(const QString &path)
+{
+ console->post("OpenViewDialog::setPath QString path=%s",
+ (const char *)path.toAscii());
+ my.dirIndex = my.dirModel->index(path);
+ my.dirModel->refresh(my.dirIndex);
+ dirListView->setRootIndex(my.dirIndex);
+ setPathUi(path);
+}
+
+void OpenViewDialog::pathComboBox_currentIndexChanged(QString path)
+{
+ console->post("OpenViewDialog::pathComboBox_currentIndexChanged");
+ setPath(path);
+}
+
+void OpenViewDialog::parentToolButton_clicked()
+{
+ console->post("OpenViewDialog::parentToolButton_clicked");
+ setPath(my.dirModel->parent(my.dirIndex));
+}
+
+void OpenViewDialog::userToolButton_clicked(bool enabled)
+{
+ if (enabled) {
+ QDir dir;
+ if (!dir.exists(my.userDir))
+ dir.mkpath(my.userDir);
+ setPath(my.userDir);
+ }
+}
+
+void OpenViewDialog::systemToolButton_clicked(bool enabled)
+{
+ if (enabled)
+ setPath(my.systemDir);
+}
+
+void OpenViewDialog::dirListView_selectionChanged()
+{
+ QItemSelectionModel *selections = dirListView->selectionModel();
+ QModelIndexList selectedIndexes = selections->selectedIndexes();
+
+ console->post("OpenViewDialog::dirListView_clicked");
+
+ my.completer->setCompletionPrefix(my.dirModel->filePath(my.dirIndex));
+ if (selectedIndexes.count() != 1)
+ fileNameLineEdit->setText("");
+ else
+ fileNameLineEdit->setText(my.dirModel->fileName(selectedIndexes.at(0)));
+}
+
+void OpenViewDialog::dirListView_activated(const QModelIndex &index)
+{
+ QFileInfo fi = my.dirModel->fileInfo(index);
+
+ console->post("OpenViewDialog::dirListView_activated");
+
+ if (fi.isDir())
+ setPath(index);
+ else if (fi.isFile()) {
+ QStringList files;
+ files << fi.absoluteFilePath();
+ if (openViewFiles(files) == true)
+ done(0);
+ }
+}
+
+int OpenViewDialog::setupArchiveComboBoxes()
+{
+ QIcon archiveIcon = fileIconProvider->icon(QedFileIconProvider::Archive);
+ QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer);
+ int index = 0;
+
+ for (unsigned int i = 0; i < archiveGroup->numContexts(); i++) {
+ QmcSource source = archiveGroup->context(i)->source();
+ sourceComboBox->insertItem(i, archiveIcon, source.source());
+ if (i == archiveGroup->contextIndex())
+ index = i;
+ }
+ return index;
+}
+
+int OpenViewDialog::setupLiveComboBoxes()
+{
+ QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer);
+ int index = 0;
+
+ for (unsigned int i = 0; i < liveGroup->numContexts(); i++) {
+ QmcSource source = liveGroup->context(i)->source();
+ sourceComboBox->insertItem(i, hostIcon, source.host());
+ if (i == liveGroup->contextIndex())
+ index = i;
+ }
+ return index;
+}
+
+void OpenViewDialog::setupComboBoxes(bool arch)
+{
+ // We block signals on the target combo boxes so that we don't
+ // send spurious signals out about their lists being changed.
+ // If we did that, we would keep changing the current context.
+ sourceComboBox->blockSignals(true);
+ sourceComboBox->clear();
+ int index = arch ? setupArchiveComboBoxes() : setupLiveComboBoxes();
+ sourceComboBox->setCurrentIndex(index);
+ sourceComboBox->blockSignals(false);
+}
+
+void OpenViewDialog::archiveAdd()
+{
+ QFileDialog *af = new QFileDialog(this);
+ QStringList al;
+ int sts;
+
+ af->setFileMode(QFileDialog::ExistingFiles);
+ af->setAcceptMode(QFileDialog::AcceptOpen);
+ af->setWindowTitle(tr("Add Archive"));
+ af->setIconProvider(fileIconProvider);
+ af->setDirectory(QDir::toNativeSeparators(QDir::homePath()));
+
+ if (af->exec() == QDialog::Accepted)
+ al = af->selectedFiles();
+ for (QStringList::Iterator it = al.begin(); it != al.end(); ++it) {
+ QString archive = *it;
+ if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ archive.prepend(tr("Cannot open PCP archive: "));
+ archive.append(tr("\n"));
+ archive.append(tr(pmErrStr(sts)));
+ QMessageBox::warning(this, pmProgname, archive,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ } else {
+ setupComboBoxes(true);
+ archiveGroup->updateBounds();
+ QmcSource source = archiveGroup->context()->source();
+ pmtime->addArchive(source.start(), source.end(),
+ source.timezone(), source.host(), false);
+ }
+ }
+ delete af;
+}
+
+void OpenViewDialog::hostAdd()
+{
+ HostDialog *host = new HostDialog(this);
+
+ if (host->exec() == QDialog::Accepted) {
+ QString hostspec = host->getHostSpecification();
+ int sts, flags = host->getContextFlags();
+
+ if (hostspec == QString::null || hostspec.length() == 0) {
+ hostspec.append(tr("Hostname not specified\n"));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) {
+ hostspec.prepend(tr("Cannot connect to host: "));
+ hostspec.append(tr("\n"));
+ hostspec.append(tr(pmErrStr(sts)));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else {
+ console->post(PmChart::DebugUi,
+ "OpenViewDialog::newHost: %s (flags=0x%x)",
+ (const char *)hostspec.toAscii(), flags);
+ setupComboBoxes(false);
+ }
+ }
+ delete host;
+}
+
+void OpenViewDialog::sourcePushButton_clicked()
+{
+ if (my.archiveSource)
+ archiveAdd();
+ else
+ hostAdd();
+}
+
+void OpenViewDialog::sourceComboBox_currentIndexChanged(int index)
+{
+ console->post("OpenViewDialog::sourceComboBox_currentIndexChanged %d", index);
+ if (my.archiveSource == false)
+ liveGroup->use((unsigned int)index);
+ else
+ archiveGroup->use((unsigned int)index);
+}
+
+bool OpenViewDialog::useLiveContext(QString source)
+{
+ int sts;
+
+ if ((sts = liveGroup->use(PM_CONTEXT_HOST, source)) < 0) {
+ QString msg;
+ msg.sprintf("Failed to connect to pmcd on \"%s\".\n%s.\n\n",
+ (const char *)source.toAscii(), pmErrStr(sts));
+ QMessageBox::warning(NULL, pmProgname, msg,
+ QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ return false;
+ }
+ return true;
+}
+
+bool OpenViewDialog::useArchiveContext(QString source)
+{
+ int sts;
+
+ if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, source)) < 0) {
+ QString msg;
+ msg.sprintf("Failed to open archive \"%s\".\n%s.\n\n",
+ (const char *)source.toAscii(), pmErrStr(sts));
+ QMessageBox::warning(NULL, pmProgname, msg,
+ QMessageBox::Ok | QMessageBox::Default | QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ return false;
+ }
+ return true;
+}
+
+bool OpenViewDialog::useComboBoxContext(bool arch)
+{
+ if (arch == false)
+ return useLiveContext(sourceComboBox->currentText());
+ else
+ return useArchiveContext(sourceComboBox->currentText());
+}
+
+bool OpenViewDialog::openViewFiles(const QStringList &fl)
+{
+ QString msg;
+ bool result = true;
+
+ if (pmchart->isArchiveTab() != my.archiveSource) {
+ if (pmchart->isArchiveTab())
+ msg = tr("Cannot open Host View(s) in an Archive Tab\n");
+ else
+ msg = tr("Cannot open Archive View(s) in a Host Tab\n");
+ QMessageBox::warning(this, pmProgname, msg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ return false;
+ }
+ if (useComboBoxContext(my.archiveSource) == false)
+ return false;
+ QStringList files = fl;
+ for (QStringList::Iterator it = files.begin(); it != files.end(); ++it)
+ if (openView((const char *)(*it).toAscii()) == false)
+ result = false;
+ pmchart->enableUi();
+ return result;
+}
+
+void OpenViewDialog::openPushButton_clicked()
+{
+ QStringList files;
+ QString msg;
+
+ if (fileNameLineEdit->isModified()) {
+ QString filename = fileNameLineEdit->text().trimmed();
+ if (filename.isEmpty())
+ msg = tr("No View file(s) specified");
+ else {
+ QFileInfo f(filename);
+ if (f.isDir()) {
+ setPath(filename);
+ }
+ else if (f.exists()) {
+ files << filename;
+ if (openViewFiles(files) == true)
+ done(0);
+ }
+ else {
+ msg = tr("No such View file exists:\n");
+ msg.append(filename);
+ }
+ }
+ }
+ else {
+ QItemSelectionModel *selections = dirListView->selectionModel();
+ QModelIndexList selectedIndexes = selections->selectedIndexes();
+
+ if (selectedIndexes.count() == 0)
+ msg = tr("No View file(s) selected");
+ else {
+ for (int i = 0; i < selectedIndexes.count(); i++) {
+ QString filename = my.dirModel->filePath(selectedIndexes.at(i));
+ QFileInfo f(filename);
+ if (f.isDir())
+ continue;
+ files << filename;
+ }
+ if (files.size() > 0)
+ if (openViewFiles(files) == true)
+ done(0);
+ }
+ }
+
+ if (msg.isEmpty() == false) {
+ QMessageBox::warning(this, pmProgname, msg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+}
diff --git a/src/pmchart/openviewdialog.h b/src/pmchart/openviewdialog.h
new file mode 100644
index 0000000..861fd34
--- /dev/null
+++ b/src/pmchart/openviewdialog.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef OPENVIEWDIALOG_H
+#define OPENVIEWDIALOG_H
+
+#include "ui_openviewdialog.h"
+#include <QtGui/QDirModel>
+
+class OpenViewDialog : public QDialog, public Ui::OpenViewDialog
+{
+ Q_OBJECT
+
+public:
+ OpenViewDialog(QWidget* parent);
+ ~OpenViewDialog();
+
+ void reset();
+ void sourceAdd();
+
+ static bool openView(const char *);
+ static void globals(int *w, int *h, int *pts, int *x, int *y);
+
+public slots:
+ virtual void parentToolButton_clicked();
+ virtual void userToolButton_clicked(bool);
+ virtual void systemToolButton_clicked(bool);
+ virtual void pathComboBox_currentIndexChanged(QString);
+ virtual void dirListView_selectionChanged();
+ virtual void dirListView_activated(const QModelIndex &);
+
+ virtual void sourceComboBox_currentIndexChanged(int);
+ virtual void sourcePushButton_clicked();
+ virtual void openPushButton_clicked();
+
+private:
+ struct {
+ QString userDir;
+ QString systemDir;
+ bool archiveSource;
+ QDirModel *dirModel;
+ QModelIndex dirIndex;
+ QCompleter *completer;
+ } my;
+
+ void setPath(const QString &);
+ void setPath(const QModelIndex &);
+ void setPathUi(const QString &);
+
+ void setupComboBoxes(bool);
+ int setupLiveComboBoxes();
+ int setupArchiveComboBoxes();
+
+ void hostAdd();
+ void archiveAdd();
+
+ bool useLiveContext(QString);
+ bool useArchiveContext(QString);
+ bool useComboBoxContext(bool);
+ bool openViewFiles(const QStringList &);
+};
+
+#endif // OPENVIEWDIALOG_H
diff --git a/src/pmchart/openviewdialog.ui b/src/pmchart/openviewdialog.ui
new file mode 100644
index 0000000..19c82d6
--- /dev/null
+++ b/src/pmchart/openviewdialog.ui
@@ -0,0 +1,553 @@
+<ui version="4.0" >
+ <class>OpenViewDialog</class>
+ <widget class="QDialog" name="OpenViewDialog" >
+ <property name="windowModality" >
+ <enum>Qt::NonModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>469</width>
+ <height>283</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Open View</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/view.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="0" >
+ <layout class="QGridLayout" >
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item row="1" column="1" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="4" >
+ <widget class="QPushButton" name="openPushButton" >
+ <property name="text" >
+ <string>Open</string>
+ </property>
+ <property name="autoDefault" >
+ <bool>false</bool>
+ </property>
+ <property name="default" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" >
+ <widget class="QComboBox" name="sourceComboBox" />
+ </item>
+ <item row="2" column="4" >
+ <widget class="QPushButton" name="cancelPushButton" >
+ <property name="text" >
+ <string>Cancel</string>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>16</width>
+ <height>29</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="fileNameLabel" >
+ <property name="text" >
+ <string>File:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="sourceLabel" >
+ <property name="text" >
+ <string>Source:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4" >
+ <widget class="QPushButton" name="sourcePushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/computer.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>10</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="2" >
+ <widget class="QLineEdit" name="fileNameLineEdit" />
+ </item>
+ <item row="1" column="3" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>16</width>
+ <height>32</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="pathLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>Path:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="pathComboBox" />
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>8</width>
+ <height>26</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="parentToolButton" >
+ <property name="minimumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Parent</string>
+ </property>
+ <property name="statusTip" >
+ <string/>
+ </property>
+ <property name="whatsThis" >
+ <string>Open the parent directory</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="checkable" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>8</width>
+ <height>26</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="userToolButton" >
+ <property name="minimumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>User Views</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Display your personal saved Views. A view is one or more charts.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/toolusers.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="systemToolButton" >
+ <property name="minimumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>26</width>
+ <height>26</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>System Views</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Display preconfigured system Views. A view is one or more charts.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/view.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QListView" name="dirListView" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="flow" >
+ <enum>QListView::TopToBottom</enum>
+ </property>
+ <property name="isWrapping" stdset="0" >
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode" >
+ <enum>QListView::Adjust</enum>
+ </property>
+ <property name="gridSize" >
+ <size>
+ <width>150</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="uniformItemSizes" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>fileNameLineEdit</sender>
+ <signal>returnPressed()</signal>
+ <receiver>openPushButton</receiver>
+ <slot>click()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>255</x>
+ <y>194</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>398</x>
+ <y>196</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dirListView</sender>
+ <signal>activated(QModelIndex)</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>dirListView_activated(QModelIndex)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>243</x>
+ <y>109</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>409</x>
+ <y>196</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>userToolButton</sender>
+ <signal>clicked(bool)</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>userToolButton_clicked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>392</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>systemToolButton</sender>
+ <signal>clicked(bool)</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>systemToolButton_clicked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>418</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>openPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>openPushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>388</x>
+ <y>197</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pathComboBox</sender>
+ <signal>currentIndexChanged(QString)</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>pathComboBox_currentIndexChanged(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>213</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sourcePushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>sourcePushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>388</x>
+ <y>229</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>cancelPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>388</x>
+ <y>295</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>219</x>
+ <y>159</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>parentToolButton</sender>
+ <signal>clicked()</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>parentToolButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>307</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>211</x>
+ <y>145</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sourceComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>OpenViewDialog</receiver>
+ <slot>sourceComboBox_currentIndexChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>185</x>
+ <y>206</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>211</x>
+ <y>145</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/pmchart.cpp b/src/pmchart/pmchart.cpp
new file mode 100644
index 0000000..c80c6f2
--- /dev/null
+++ b/src/pmchart/pmchart.cpp
@@ -0,0 +1,909 @@
+/*
+ * Copyright (c) 2012-2014, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QUrl>
+#include <QtCore/QTimer>
+#include <QtCore/QLibraryInfo>
+#include <QtGui/QDesktopServices>
+#include <QtGui/QDesktopWidget>
+#include <QtGui/QApplication>
+#include <QtGui/QPrintDialog>
+#include <QtGui/QMessageBox>
+#include <QtGui/QWhatsThis>
+#include <QtGui/QPainter>
+
+#include "main.h"
+#include "pmchart.h"
+#include "aboutdialog.h"
+#include "chartdialog.h"
+#include "exportdialog.h"
+#include "infodialog.h"
+#include "openviewdialog.h"
+#include "recorddialog.h"
+#include "searchdialog.h"
+#include "samplesdialog.h"
+#include "saveviewdialog.h"
+#include "seealsodialog.h"
+#include "settingsdialog.h"
+#include "tabdialog.h"
+#include "statusbar.h"
+
+PmChart::PmChart() : QMainWindow(NULL)
+{
+ my.dialogsSetup = false;
+ setIconSize(QSize(22, 22));
+
+ setupUi(this);
+
+ my.statusBar = new StatusBar;
+ setStatusBar(my.statusBar);
+ connect(my.statusBar->timeFrame(), SIGNAL(clicked()),
+ this, SLOT(editSamples()));
+ connect(my.statusBar->timeButton(), SIGNAL(clicked()),
+ this, SLOT(optionsShowTimeControl()));
+
+ my.timeAxisRightAlign = toolBar->height();
+ toolBar->setAllowedAreas(Qt::RightToolBarArea | Qt::TopToolBarArea);
+ connect(toolBar, SIGNAL(orientationChanged(Qt::Orientation)),
+ this, SLOT(updateToolbarOrientation(Qt::Orientation)));
+ updateToolbarLocation();
+ setupEnabledActionsList();
+ if (!globalSettings.initialToolbar && !outfile)
+ toolBar->hide();
+
+ toolbarAction->setChecked(true);
+ my.toolbarHidden = !globalSettings.initialToolbar;
+ my.consoleHidden = true;
+ if (!pmDebug)
+ consoleAction->setVisible(false);
+ consoleAction->setChecked(false);
+
+ if (outfile)
+ QTimer::singleShot(0, this, SLOT(exportFile()));
+ else
+ QTimer::singleShot(PmChart::defaultTimeout(), this, SLOT(timeout()));
+}
+
+void PmChart::languageChange()
+{
+ retranslateUi(this);
+}
+
+void PmChart::init(void)
+{
+ my.statusBar->init();
+}
+
+void PmChart::setupDialogs(void)
+{
+ // In order to speed startup times, we delay creation of these
+ // global dialogs until after the main window is displayed. We
+ // do NOT delay until the very last minute, otherwise we start
+ // to hit the same problem with the dialogs (if we create them
+ // on-demand there's a noticable delay after action selected).
+
+ if (my.dialogsSetup)
+ return;
+
+ my.info = new InfoDialog(this);
+ my.search = new SearchDialog(this);
+ my.newtab = new TabDialog(this);
+ my.edittab = new TabDialog(this);
+ my.samples = new SamplesDialog(this);
+ my.exporter = new ExportDialog(this);
+ my.newchart = new ChartDialog(this);
+ my.openview = new OpenViewDialog(this);
+ my.saveview = new SaveViewDialog(this);
+ my.settings = new SettingsDialog(this);
+
+ connect(my.newtab->buttonOk, SIGNAL(clicked()),
+ this, SLOT(acceptNewTab()));
+ connect(my.edittab->buttonOk, SIGNAL(clicked()),
+ this, SLOT(acceptEditTab()));
+ connect(my.samples->buttonOk, SIGNAL(clicked()),
+ this, SLOT(acceptEditSamples()));
+ connect(my.exporter->buttonOk, SIGNAL(clicked()),
+ this, SLOT(acceptExport()));
+ my.dialogsSetup = true;
+}
+
+void PmChart::quit()
+{
+ // End any processes we may have started and close any open dialogs
+ if (my.dialogsSetup) {
+ my.info->reject();
+ }
+ if (pmtime)
+ pmtime->quit();
+#ifdef HAVE_UNSETENV
+ unsetenv("PCP_STDERR");
+#else
+ putenv("PCP_STDERR=");
+#endif
+ pmflush();
+}
+
+void PmChart::setValueText(QString &string)
+{
+ my.statusBar->setValueText(string);
+ QTimer::singleShot(PmChart::defaultTimeout(), this, SLOT(timeout()));
+}
+
+void PmChart::timeout()
+{
+ my.statusBar->clearValueText();
+}
+
+void PmChart::closeEvent(QCloseEvent *)
+{
+ quit();
+}
+
+void PmChart::enableUi(void)
+{
+ bool haveTabs = (chartTabWidget->size() > 1);
+ bool haveCharts = (activeTab()->gadgetCount() > 0);
+ bool haveLoggers = (activeTab()->isRecording());
+ bool haveLiveHosts = (!activeTab()->isArchiveSource() && !Lflag);
+
+ closeTabAction->setEnabled(haveTabs);
+ fileSaveViewAction->setEnabled(haveCharts);
+ fileExportAction->setEnabled(haveCharts);
+ filePrintAction->setEnabled(haveCharts);
+ editChartAction->setEnabled(haveCharts);
+ closeChartAction->setEnabled(haveCharts);
+ recordStartAction->setEnabled(haveCharts && haveLiveHosts && !haveLoggers);
+ recordQueryAction->setEnabled(haveLoggers);
+ recordStopAction->setEnabled(haveLoggers);
+ recordDetachAction->setEnabled(haveLoggers);
+
+ zoomInAction->setEnabled(activeGroup->visibleHistory() > minimumPoints());
+ zoomOutAction->setEnabled(activeGroup->visibleHistory() < maximumPoints());
+}
+
+void PmChart::updateBackground(void)
+{
+ liveGroup->updateBackground();
+ archiveGroup->updateBackground();
+}
+
+int PmChart::defaultFontSize()
+{
+#if defined(IS_DARWIN)
+ return 9;
+#elif defined(IS_MINGW)
+ return 8;
+#else
+ return 7;
+#endif
+}
+
+void PmChart::updateHeight(int adjustment)
+{
+ QSize newSize = size();
+ int ideal = newSize.height() + adjustment; // may be negative
+ int sized = QApplication::desktop()->availableGeometry().height();
+
+#ifdef DESPERATE
+ console->post(PmChart::DebugUi,
+ "updateHeight() oldsize h=%d,w=%d maximum=%d proposed=%d",
+ newSize.height(), newSize.width(), sized, ideal);
+#endif
+
+ if (ideal > sized)
+ ideal = sized;
+ newSize.setHeight(ideal);
+ resize(newSize);
+}
+
+void PmChart::updateToolbarLocation()
+{
+ if (globalSettings.toolbarLocation)
+ addToolBar(Qt::RightToolBarArea, toolBar);
+ else
+ addToolBar(Qt::TopToolBarArea, toolBar);
+ setUnifiedTitleAndToolBarOnMac(globalSettings.nativeToolbar);
+}
+
+void PmChart::updateToolbarOrientation(Qt::Orientation orientation)
+{
+ my.statusBar->setTimeAxisRightAlignment(
+ orientation == Qt::Vertical ? my.timeAxisRightAlign : 0);
+}
+
+void PmChart::setButtonState(QedTimeButton::State state)
+{
+ my.statusBar->timeButton()->setButtonState(state);
+}
+
+void PmChart::step(bool live, QmcTime::Packet *packet)
+{
+ if (live)
+ liveGroup->step(packet);
+ else
+ archiveGroup->step(packet);
+}
+
+void PmChart::VCRMode(bool live, QmcTime::Packet *packet, bool drag)
+{
+ if (live)
+ liveGroup->VCRMode(packet, drag);
+ else
+ archiveGroup->VCRMode(packet, drag);
+}
+
+void PmChart::timeZone(bool live, QmcTime::Packet *packet, char *tzdata)
+{
+ if (live)
+ liveGroup->setTimezone(packet, tzdata);
+ else
+ archiveGroup->setTimezone(packet, tzdata);
+}
+
+void PmChart::setStyle(char *newlook)
+{
+ QApplication::setStyle(newlook);
+}
+
+void PmChart::fileOpenView()
+{
+ setupDialogs();
+ my.openview->reset();
+ my.openview->show();
+}
+
+void PmChart::fileSaveView()
+{
+ // If we have one host only, we default to "host dynamic" views.
+ // Otherwise (multiple hosts), default to explicit host names.
+ int i, ngadgets = activeTab()->gadgetCount();
+ bool hostDynamic = true;
+ for (i = 0; i < ngadgets; i++) {
+ Gadget *gadget = activeTab()->gadget(i);
+ if (gadget->hosts().size() > 1)
+ hostDynamic = false;
+ }
+
+ setupDialogs();
+ my.saveview->reset(hostDynamic);
+ my.saveview->show();
+}
+
+void PmChart::fileExport()
+{
+ setupDialogs();
+ my.exporter->reset();
+ my.exporter->show();
+}
+
+void PmChart::acceptExport()
+{
+ my.exporter->flush();
+}
+
+void PmChart::filePrint()
+{
+ QPrinter printer;
+ QString creator = QString("pmchart Version ");
+
+ creator.append(pmGetConfig("PCP_VERSION"));
+ printer.setCreator(creator);
+ printer.setOrientation(QPrinter::Portrait);
+ printer.setDocName("pmchart.pdf");
+
+ QPrintDialog print(&printer, (QWidget *)this);
+ if (print.exec()) {
+ QPainter qp(&printer);
+ painter(&qp, printer.width(), printer.height(), false, false);
+ }
+}
+
+void PmChart::updateFont(const QString &family, const QString &style, int size)
+{
+ int i, ngadgets = activeTab()->gadgetCount();
+
+ globalFont->setFamily(family);
+ globalFont->setPointSize(size);
+ globalFont->setStyle(QFont::StyleNormal);
+ if (style.contains("Italic"))
+ globalFont->setItalic(true);
+ if (globalSettings.fontStyle.contains("Bold"))
+ globalFont->setBold(true);
+
+ for (i = 0; i < ngadgets; i++)
+ activeTab()->gadget(i)->resetFont();
+ my.statusBar->resetFont();
+}
+
+void PmChart::painter(QPainter *qp, int pw, int ph, bool transparent, bool currentOnly)
+{
+ double scale_h = 0;
+ double scale_w = 0;
+ int i, ngadgets = activeTab()->gadgetCount();
+ QSize size;
+ QRect rect; // used for print layout calculations
+
+ qp->setFont(*globalFont);
+
+ console->post("painter() pw=%d ph=%d ngadgets=%d", pw, ph, ngadgets);
+ for (i = 0; i < ngadgets; i++) {
+ Gadget *gadget = activeTab()->gadget(i);
+ if (currentOnly && gadget != activeTab()->currentGadget())
+ continue;
+ size = gadget->size();
+ console->post(" [%d] w=%d h=%d", i, size.width(), size.height());
+ if (size.width() > scale_w) scale_w = size.width();
+ scale_h += size.height();
+ }
+ console->post(" final_w=%d final_h=%d", (int)scale_w, (int)scale_h);
+ // timeaxis is _always_ less narrow than the plot(s)
+ // datelabel is _never_ wider than the timeaxis
+ // so width calculation is done, just need to consider the heights
+ size = my.statusBar->timeAxis()->size();
+ console->post(" timeaxis w=%d h=%d", size.width(), size.height());
+ scale_h += size.height() - TIMEAXISFUDGE;
+ size = my.statusBar->dateLabel()->size();
+ console->post(" datelabel w=%d h=%d", size.width(), size.height());
+ scale_h += size.height();
+ console->post(" final_w=%d final_h=%d", (int)scale_w, (int)scale_h);
+ if (scale_w/pw > scale_h/ph) {
+ // window width drives scaling
+ scale_w = pw / scale_w;
+ scale_h = scale_w;
+ }
+ else {
+ // window height drives scaling
+ scale_h = ph / scale_h;
+ scale_w = scale_h;
+ }
+ console->post(" final chart scale_w=%.2f scale_h=%.2f", scale_w, scale_h);
+ rect.setX(0);
+ rect.setY(0);
+ for (i = 0; i < ngadgets; i++) {
+ Gadget *gadget = activeTab()->gadget(i);
+ if (currentOnly && gadget != activeTab()->currentGadget())
+ continue;
+ size = gadget->size();
+ rect.setWidth((int)(size.width()*scale_w+0.5));
+ rect.setHeight((int)(size.height()*scale_h+0.5));
+ console->post(" [%d] @ (%d,%d) w=%d h=%d", i, rect.x(), rect.y(), rect.width(), rect.height());
+ gadget->print(qp, rect, transparent);
+ rect.setY(rect.y()+rect.height());
+ }
+
+ // time axis
+ rect.setY(rect.y() - TIMEAXISFUDGE);
+ size = my.statusBar->timeAxis()->size();
+ rect.setX(pw-(int)(size.width()*scale_w+0.5));
+ rect.setWidth((int)(size.width()*scale_w+0.5));
+ rect.setHeight((int)(size.height()*scale_h+0.5));
+ console->post(" timeaxis @ (%d,%d) w=%d h=%d", rect.x(), rect.y(), rect.width(), rect.height());
+ my.statusBar->timeAxis()->print(qp, rect, transparent);
+ rect.setY(rect.y()+rect.height());
+
+ // date label below time axis
+ size = my.statusBar->dateLabel()->size();
+ rect.setX(pw-(int)(size.width()*scale_w+0.5));
+ rect.setWidth((int)(size.width()*scale_w+0.5));
+ rect.setHeight((int)(size.height()*scale_h+0.5));
+ console->post(" datelabel @ (%d,%d) w=%d h=%d", rect.x(), rect.y(), rect.width(), rect.height());
+ qp->drawText(rect, Qt::AlignRight, my.statusBar->dateText());
+}
+
+void PmChart::fileQuit()
+{
+ QApplication::exit(0);
+}
+
+void PmChart::helpManual()
+{
+ bool ok;
+ QString documents("file://");
+ QString separator = QString(__pmPathSeparator());
+ documents.append(pmGetConfig("PCP_HTML_DIR"));
+ documents.append(separator).append("index.html");
+ ok = QDesktopServices::openUrl(QUrl(documents, QUrl::TolerantMode));
+ if (!ok) {
+ documents.prepend("Failed to open:\n");
+ QMessageBox::warning(this, pmProgname, documents);
+ }
+}
+
+void PmChart::helpAbout()
+{
+ AboutDialog about(this);
+ about.exec();
+}
+
+void PmChart::helpAboutQt()
+{
+ QApplication::aboutQt();
+}
+
+void PmChart::helpSeeAlso()
+{
+ SeeAlsoDialog seealso(this);
+ seealso.exec();
+}
+
+void PmChart::whatsThis()
+{
+ QWhatsThis::enterWhatsThisMode();
+}
+
+void PmChart::optionsShowTimeControl()
+{
+ if (activeTab()->isArchiveSource())
+ pmtime->showArchiveTimeControl();
+ else
+ pmtime->showLiveTimeControl();
+}
+
+void PmChart::optionsHideTimeControl()
+{
+ if (activeTab()->isArchiveSource())
+ pmtime->hideArchiveTimeControl();
+ else
+ pmtime->hideLiveTimeControl();
+}
+
+void PmChart::optionsToolbar()
+{
+ if (my.toolbarHidden)
+ toolBar->show();
+ else
+ toolBar->hide();
+ my.toolbarHidden = !my.toolbarHidden;
+}
+
+void PmChart::optionsConsole()
+{
+ if (pmDebug) {
+ if (my.consoleHidden)
+ console->show();
+ else
+ console->hide();
+ my.consoleHidden = !my.consoleHidden;
+ }
+}
+
+void PmChart::optionsNewPmchart()
+{
+ QProcess *buddy = new QProcess(this);
+ QStringList arguments;
+ QString port;
+
+ port.setNum(pmtime->port());
+ arguments << "-p" << port;
+ for (unsigned int i = 0; i < archiveGroup->numContexts(); i++) {
+ QmcSource source = archiveGroup->context(i)->source();
+ arguments << "-a" << source.source();
+ }
+ for (unsigned int i = 0; i < liveGroup->numContexts(); i++) {
+ QmcSource source = liveGroup->context(i)->source();
+ arguments << "-h" << source.source();
+ }
+ if (Lflag)
+ arguments << "-L";
+ buddy->start("pmchart", arguments);
+}
+
+//
+// Note: Called from both Apply and OK on New Chart dialog
+// Must only called when the changes are definately going to
+// be applied, so all input validation must be done by now.
+//
+Chart *PmChart::acceptNewChart()
+{
+ bool yAutoScale;
+ double yMin, yMax;
+ QString scheme;
+ int sequence;
+
+ Chart *cp = new Chart(activeTab(), activeTab()->splitter());
+ activeGroup->addGadget(cp);
+ activeTab()->addGadget(cp);
+
+ QString newTitle = my.newchart->title().trimmed();
+ if (newTitle.isEmpty() == false)
+ cp->changeTitle(newTitle, true);
+ cp->setLegendVisible(my.newchart->legend());
+ cp->setAntiAliasing(my.newchart->antiAliasing());
+ cp->setRateConvert(my.newchart->rateConvert());
+ my.newchart->updateChartPlots(cp);
+ my.newchart->scale(&yAutoScale, &yMin, &yMax);
+ cp->setScale(yAutoScale, yMin, yMax);
+ my.newchart->scheme(&scheme, &sequence);
+ cp->setScheme(scheme, sequence);
+
+ activeGroup->setupWorldView();
+ activeTab()->showGadgets();
+
+ enableUi();
+ return cp;
+}
+
+void PmChart::fileNewChart()
+{
+ setupDialogs();
+ my.newchart->reset();
+ my.newchart->show();
+}
+
+void PmChart::editChart()
+{
+ Chart *cp = (Chart *)activeTab()->currentGadget();
+
+ setupDialogs();
+ my.newchart->reset(cp);
+ my.newchart->show();
+}
+
+void PmChart::acceptEditChart()
+{
+ bool yAutoScale;
+ double yMin, yMax;
+ QString scheme;
+ int sequence;
+
+ Chart *cp = my.newchart->chart();
+ QString editTitle = my.newchart->title().trimmed();
+ if (editTitle.isEmpty() == false && editTitle != cp->title())
+ cp->changeTitle(editTitle, true);
+ cp->setLegendVisible(my.newchart->legend());
+ cp->setAntiAliasing(my.newchart->antiAliasing());
+ cp->setRateConvert(my.newchart->rateConvert());
+ my.newchart->scale(&yAutoScale, &yMin, &yMax);
+ cp->setScale(yAutoScale, yMin, yMax);
+ my.newchart->updateChartPlots(cp);
+ my.newchart->scheme(&scheme, &sequence);
+ cp->setScheme(scheme, sequence);
+ cp->replot();
+ cp->show();
+
+ enableUi();
+}
+
+void PmChart::closeChart()
+{
+ activeTab()->deleteCurrent();
+ enableUi();
+}
+
+void PmChart::metricInfo(QString src, QString m, QString inst, int srcType)
+{
+ setupDialogs();
+ my.info->reset(src, m, inst, srcType);
+ my.info->show();
+}
+
+void PmChart::metricSearch(QTreeWidget *pmns)
+{
+ setupDialogs();
+ my.search->reset(pmns);
+ my.search->show();
+}
+
+void PmChart::editSamples()
+{
+ int samples = activeGroup->sampleHistory();
+ int visible = activeGroup->visibleHistory();
+
+ setupDialogs();
+ my.samples->reset(samples, visible);
+ my.samples->show();
+}
+
+void PmChart::acceptEditSamples()
+{
+ activeGroup->setSampleHistory(my.samples->samples());
+ activeGroup->setVisibleHistory(my.samples->visible());
+ activeGroup->setupWorldView();
+ activeTab()->showGadgets();
+}
+
+void PmChart::editTab()
+{
+ setupDialogs();
+ my.edittab->reset(chartTabWidget->tabText(chartTabWidget->currentIndex()),
+ activeGroup->isArchiveSource() == false);
+ my.edittab->show();
+}
+
+void PmChart::acceptEditTab()
+{
+ QString label = my.edittab->label();
+ chartTabWidget->setTabText(chartTabWidget->currentIndex(), label);
+}
+
+void PmChart::createNewTab(bool live)
+{
+ setupDialogs();
+ my.newtab->reset(QString::null, live);
+ my.newtab->show();
+}
+
+void PmChart::addTab()
+{
+ createNewTab(isArchiveTab() == false);
+}
+
+void PmChart::acceptNewTab()
+{
+ Tab *tab = new Tab;
+ QString label = my.newtab->labelLineEdit->text().trimmed();
+
+ if (my.newtab->isArchiveSource())
+ tab->init(chartTabWidget, archiveGroup, label);
+ else
+ tab->init(chartTabWidget, liveGroup, label);
+ chartTabWidget->insertTab(tab);
+ enableUi();
+}
+
+void PmChart::addActiveTab(Tab *tab)
+{
+ chartTabWidget->insertTab(tab);
+ setActiveTab(chartTabWidget->size() - 1, true);
+ enableUi();
+}
+
+void PmChart::zoomIn()
+{
+ int visible = activeGroup->visibleHistory();
+ int samples = activeGroup->sampleHistory();
+ int decrease = qMax(qMin((int)((double)samples / 10), visible/2), 1);
+
+ console->post("zoomIn: vis=%d s=%d dec=%d\n", visible, samples, decrease);
+
+ visible = qMax(visible - decrease, minimumPoints());
+ activeGroup->setVisibleHistory(visible);
+ activeGroup->setupWorldView();
+ activeTab()->showGadgets();
+
+ zoomInAction->setEnabled(visible > minimumPoints());
+ zoomOutAction->setEnabled(visible < samples);
+}
+
+void PmChart::zoomOut()
+{
+ int visible = activeGroup->visibleHistory();
+ int samples = activeGroup->sampleHistory();
+ int increase = qMax(qMin((int)((double)samples / 10), visible/2), 1);
+
+ console->post("zoomOut: vis=%d s=%d dec=%d\n", visible, samples, increase);
+
+ visible = qMin(visible + increase, samples);
+ activeGroup->setVisibleHistory(visible);
+ activeGroup->setupWorldView();
+ activeTab()->showGadgets();
+
+ zoomInAction->setEnabled(visible > minimumPoints());
+ zoomOutAction->setEnabled(visible < samples);
+}
+
+bool PmChart::isTabRecording()
+{
+ return activeTab()->isRecording();
+}
+
+bool PmChart::isArchiveTab()
+{
+ return activeTab()->isArchiveSource();
+}
+
+void PmChart::closeTab()
+{
+ int index = chartTabWidget->currentIndex();
+
+ chartTabWidget->removeTab(index);
+ if (index > 0)
+ index--;
+ setActiveTab(index, false);
+ enableUi();
+}
+
+void PmChart::setActiveTab(int index, bool redisplay)
+{
+ console->post("PmChart::setActiveTab index=%d r=%d", index, redisplay);
+
+ if (chartTabWidget->setActiveTab(index) == true)
+ activeGroup = archiveGroup;
+ else
+ activeGroup = liveGroup;
+ activeGroup->updateTimeButton();
+ activeGroup->updateTimeAxis();
+
+ if (redisplay)
+ chartTabWidget->setCurrentIndex(index);
+}
+
+void PmChart::activeTabChanged(int index)
+{
+ if (index < chartTabWidget->size())
+ setActiveTab(index, false);
+ enableUi();
+}
+
+void PmChart::editSettings()
+{
+ setupDialogs();
+ my.settings->reset();
+ my.settings->show();
+}
+
+void PmChart::setDateLabel(time_t seconds, QString tz)
+{
+ char datestring[32];
+ QString label;
+
+ if (seconds) {
+ pmCtime(&seconds, datestring);
+ label = tr(datestring);
+ label.remove(10, 9);
+ label.replace(15, 1, " ");
+ label.append(tz);
+ }
+ else {
+ label = tr("");
+ }
+ my.statusBar->setDateText(label);
+}
+
+void PmChart::setDateLabel(QString label)
+{
+ my.statusBar->setDateText(label);
+}
+
+void PmChart::setRecordState(bool record)
+{
+ liveGroup->newButtonState(liveGroup->pmtimeState(),
+ QmcTime::NormalMode, record);
+ setButtonState(liveGroup->buttonState());
+ enableUi();
+}
+
+void PmChart::recordStart()
+{
+ if (activeTab()->startRecording())
+ setRecordState(true);
+}
+
+void PmChart::recordStop()
+{
+ activeTab()->stopRecording();
+}
+
+void PmChart::recordQuery()
+{
+ activeTab()->queryRecording();
+}
+
+void PmChart::recordDetach()
+{
+ activeTab()->detachLoggers();
+}
+
+QList<QAction*> PmChart::toolbarActionsList()
+{
+ return my.toolbarActionsList;
+}
+
+QList<QAction*> PmChart::enabledActionsList()
+{
+ return my.enabledActionsList;
+}
+
+void PmChart::setupEnabledActionsList()
+{
+ // ToolbarActionsList is a list of all Actions available.
+ // The SeparatorsList contains Actions that are group "breaks", and
+ // which must be followed by a separator (if they are not the final
+ // action in the toolbar, of course).
+ // Finally the enabledActionsList lists the default enabled Actions.
+
+ my.toolbarActionsList << fileNewChartAction
+ << editChartAction << closeChartAction;
+ my.toolbarActionsList << fileOpenViewAction << fileSaveViewAction;
+ addSeparatorAction(); // end of chart/view group
+ my.toolbarActionsList << fileExportAction << filePrintAction;
+ addSeparatorAction(); // end exported formats
+ my.toolbarActionsList << addTabAction << editTabAction << closeTabAction;
+ my.toolbarActionsList << zoomInAction << zoomOutAction;
+ addSeparatorAction(); // end tab group
+ my.toolbarActionsList << recordStartAction << recordStopAction;
+ addSeparatorAction(); // end recording group
+ my.toolbarActionsList << editSettingsAction;
+ addSeparatorAction(); // end settings group
+ my.toolbarActionsList << showTimeControlAction << hideTimeControlAction
+ << newPmchartAction;
+ addSeparatorAction(); // end other processes
+ my.toolbarActionsList << helpManualAction << helpWhatsThisAction;
+
+ // needs to match pmchart.ui
+ my.enabledActionsList << fileNewChartAction << fileOpenViewAction
+ // separator
+ << zoomInAction << zoomOutAction
+ // separator
+ << fileExportAction
+ // separator
+ << showTimeControlAction << newPmchartAction;
+
+ if (globalSettings.toolbarActions.size() > 0) {
+ setEnabledActionsList(globalSettings.toolbarActions, false);
+ updateToolbarContents();
+ }
+}
+
+void PmChart::addSeparatorAction()
+{
+ int index = my.toolbarActionsList.size() - 1;
+ my.separatorsList << my.toolbarActionsList.at(index);
+}
+
+void PmChart::updateToolbarContents()
+{
+ bool needSeparator = false;
+
+ toolBar->clear();
+ for (int i = 0; i < my.toolbarActionsList.size(); i++) {
+ QAction *action = my.toolbarActionsList.at(i);
+ if (my.enabledActionsList.contains(action)) {
+ toolBar->addAction(action);
+ if (needSeparator) {
+ toolBar->insertSeparator(action);
+ needSeparator = false;
+ }
+ }
+ if (my.separatorsList.contains(action))
+ needSeparator = true;
+ }
+}
+
+void PmChart::setEnabledActionsList(QStringList tools, bool redisplay)
+{
+ my.enabledActionsList.clear();
+ for (int i = 0; i < my.toolbarActionsList.size(); i++) {
+ QAction *action = my.toolbarActionsList.at(i);
+ if (tools.contains(action->iconText()))
+ my.enabledActionsList.append(action);
+ }
+
+ if (redisplay) {
+ my.toolbarHidden = (my.enabledActionsList.size() == 0);
+ toolbarAction->setChecked(my.toolbarHidden);
+ if (my.toolbarHidden)
+ toolBar->hide();
+ else
+ toolBar->show();
+ }
+}
+
+void PmChart::newScheme()
+{
+ my.settings->newScheme();
+}
+
+void PmChart::newScheme(QString cs)
+{
+ my.newchart->setCurrentScheme(cs);
+ my.newchart->setupSchemeComboBox();
+}
+
+void PmChart::exportFile()
+{
+ int sts = ExportDialog::exportFile(outfile, outgeometry, Wflag == 0);
+ QApplication::exit(sts);
+}
diff --git a/src/pmchart/pmchart.desktop b/src/pmchart/pmchart.desktop
new file mode 100644
index 0000000..813a72e
--- /dev/null
+++ b/src/pmchart/pmchart.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Name=PCP Charts
+Comment=Strip Chart tool for plotting Performance Co-Pilot metrics
+Exec=pmchart
+Icon=pmchart
+Terminal=false
+Type=Application
+Categories=System;Utility;Network;Qt;
diff --git a/src/pmchart/pmchart.h b/src/pmchart/pmchart.h
new file mode 100644
index 0000000..381ccdc
--- /dev/null
+++ b/src/pmchart/pmchart.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2012-2014, Red Hat.
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMCHART_H
+#define PMCHART_H
+
+#include "ui_pmchart.h"
+#include "statusbar.h"
+#include <qmc_time.h>
+
+class TimeAxis;
+class NameSpace;
+class TabDialog;
+class InfoDialog;
+class ChartDialog;
+class ExportDialog;
+class SearchDialog;
+class SamplesDialog;
+class OpenViewDialog;
+class SaveViewDialog;
+class SettingsDialog;
+
+class PmChart : public QMainWindow, public Ui::PmChart
+{
+ Q_OBJECT
+
+public:
+ PmChart();
+
+ typedef enum {
+ DebugApp = 0x1,
+ DebugUi = 0x1,
+ DebugProtocol = 0x2,
+ DebugView = 0x4,
+ DebugTimeless = 0x8,
+ DebugForce = 0x10,
+ } DebugOptions;
+
+ static int defaultFontSize();
+ static const char *defaultFontFamily() { return "Sans Serif"; }
+ static double defaultChartDelta() { return 1.0; } // seconds
+ static double defaultLoggerDelta() { return 1.0; }
+ static int defaultVisibleHistory() { return 60; } // points
+ static int defaultSampleHistory() { return 180; }
+ static int defaultTimeout() { return 8000; } // milliseconds
+ static int minimumPoints() { return 2; }
+ static int maximumPoints() { return 720; }
+ static int maximumLegendLength() { return 120; } // chars
+ static int minimumChartHeight() { return 80; } // pixels
+
+ Tab *activeTab() { return chartTabWidget->activeTab(); }
+ void setActiveTab(int index, bool redisplay);
+ void addActiveTab(Tab *tab);
+ bool isArchiveTab();
+ bool isTabRecording();
+ TabWidget *tabWidget() { return chartTabWidget; }
+ TimeAxis *timeAxis() { return my.statusBar->timeAxis(); }
+ QLabel *dateLabel() { return my.statusBar->dateLabel(); }
+ Chart *acceptNewChart();
+
+ virtual void step(bool livemode, QmcTime::Packet *pmtime);
+ virtual void VCRMode(bool livemode, QmcTime::Packet *pmtime, bool drag);
+ virtual void timeZone(bool livemode, QmcTime::Packet *pmtime, char *tzdata);
+ virtual void setStyle(char *style);
+ virtual void updateHeight(int);
+ virtual void metricInfo(QString src, QString m, QString inst, int srcType);
+ virtual void metricSearch(QTreeWidget *pmns);
+ virtual void createNewTab(bool liveMode);
+ virtual void setValueText(QString &text);
+ virtual void setDateLabel(QString label);
+ virtual void setDateLabel(time_t seconds, QString tz);
+ virtual void setButtonState(QedTimeButton::State state);
+ virtual void setRecordState(bool recording);
+
+ virtual void updateToolbarContents();
+ virtual void updateToolbarLocation();
+ virtual QList<QAction*> toolbarActionsList();
+ virtual QList<QAction*> enabledActionsList();
+ virtual void setupEnabledActionsList();
+ virtual void addSeparatorAction();
+ virtual void setEnabledActionsList(QStringList tools, bool redisplay);
+
+ virtual void newScheme(); // request new scheme of settings dialog
+ virtual void newScheme(QString); // reply back to requesting dialog(s)
+ virtual void updateBackground();
+ virtual void updateFont(const QString &family, const QString &style, int size);
+
+ void painter(QPainter *qp, int pw, int ph, bool transparent, bool currentOnly);
+
+ // Adjusted height for exporting images (without UI elements)
+ int exportHeight()
+ { return height() - menuBar()->height() - toolBar->height(); }
+
+public slots:
+ virtual void init();
+ virtual void quit();
+ virtual void enableUi();
+ virtual void exportFile();
+ virtual void setupDialogs();
+ virtual void fileOpenView();
+ virtual void fileSaveView();
+ virtual void fileExport();
+ virtual void acceptExport();
+ virtual void filePrint();
+ virtual void fileQuit();
+ virtual void helpManual();
+ virtual void helpAbout();
+ virtual void helpAboutQt();
+ virtual void helpSeeAlso();
+ virtual void whatsThis();
+ virtual void optionsShowTimeControl();
+ virtual void optionsHideTimeControl();
+ virtual void optionsToolbar();
+ virtual void optionsConsole();
+ virtual void optionsNewPmchart();
+ virtual void fileNewChart();
+ virtual void editChart();
+ virtual void acceptEditChart();
+ virtual void closeChart();
+ virtual void editTab();
+ virtual void acceptEditTab();
+ virtual void editSamples();
+ virtual void acceptEditSamples();
+ virtual void addTab();
+ virtual void acceptNewTab();
+ virtual void closeTab();
+ virtual void activeTabChanged(int);
+ virtual void editSettings();
+ virtual void recordStart();
+ virtual void recordQuery();
+ virtual void recordStop();
+ virtual void recordDetach();
+ virtual void timeout();
+ virtual void zoomIn();
+ virtual void zoomOut();
+ virtual void updateToolbarOrientation(Qt::Orientation);
+
+protected slots:
+ virtual void languageChange();
+ virtual void closeEvent(QCloseEvent *);
+
+private:
+ struct {
+ bool dialogsSetup;
+ bool toolbarHidden;
+ bool consoleHidden;
+
+ TabDialog *newtab;
+ TabDialog *edittab;
+ InfoDialog *info;
+ ChartDialog *newchart; // shared by New and Edit Chart actions
+ ExportDialog *exporter;
+ SearchDialog *search;
+ SamplesDialog *samples;
+ OpenViewDialog *openview;
+ SaveViewDialog *saveview;
+ SettingsDialog *settings;
+
+ QList<QAction*> separatorsList; // separator follow these
+ QList<QAction*> toolbarActionsList; // all toolbar actions
+ QList<QAction*> enabledActionsList; // currently visible actions
+
+ int timeAxisRightAlign;
+ StatusBar *statusBar;
+ } my;
+
+ void editTab(int index);
+};
+
+#endif // PMCHART_H
diff --git a/src/pmchart/pmchart.info.in b/src/pmchart/pmchart.info.in
new file mode 100644
index 0000000..c9334c6
--- /dev/null
+++ b/src/pmchart/pmchart.info.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>pmchart.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>PACKAGE_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>pmchart</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.aconex.pmchart</string>
+</dict>
+</plist>
diff --git a/src/pmchart/pmchart.pro b/src/pmchart/pmchart.pro
new file mode 100644
index 0000000..0068be9
--- /dev/null
+++ b/src/pmchart/pmchart.pro
@@ -0,0 +1,45 @@
+TEMPLATE = app
+LANGUAGE = C++
+HEADERS = pmchart.h main.h \
+ aboutdialog.h chartdialog.h exportdialog.h \
+ hostdialog.h infodialog.h openviewdialog.h \
+ recorddialog.h samplesdialog.h saveviewdialog.h \
+ searchdialog.h seealsodialog.h settingsdialog.h \
+ tab.h tabdialog.h \
+ chart.h colorbutton.h colorscheme.h statusbar.h \
+ namespace.h \
+ tabwidget.h timeaxis.h timecontrol.h \
+ groupcontrol.h gadget.h sampling.h tracing.h
+SOURCES = pmchart.cpp main.cpp \
+ aboutdialog.cpp chartdialog.cpp exportdialog.cpp \
+ hostdialog.cpp infodialog.cpp openviewdialog.cpp \
+ recorddialog.cpp samplesdialog.cpp saveviewdialog.cpp \
+ searchdialog.cpp seealsodialog.cpp settingsdialog.cpp \
+ tab.cpp tabdialog.cpp \
+ chart.cpp colorbutton.cpp colorscheme.cpp statusbar.cpp \
+ namespace.cpp \
+ tabwidget.cpp timeaxis.cpp timecontrol.cpp \
+ groupcontrol.cpp gadget.cpp sampling.cpp tracing.cpp \
+ view.cpp
+FORMS = aboutdialog.ui chartdialog.ui exportdialog.ui \
+ hostdialog.ui infodialog.ui pmchart.ui openviewdialog.ui \
+ recorddialog.ui samplesdialog.ui saveviewdialog.ui \
+ searchdialog.ui seealsodialog.ui settingsdialog.ui \
+ tabdialog.ui
+ICON = pmchart.icns
+RC_FILE = pmchart.rc
+RESOURCES = pmchart.qrc
+INCLUDEPATH += ../include
+INCLUDEPATH += ../libpcp_qed/src ../libpcp_qmc/src ../libpcp_qwt/src
+CONFIG += qt warn_on
+release:DESTDIR = build/debug
+debug:DESTDIR = build/release
+LIBS += -L../libpcp/src
+LIBS += -L../libpcp_qed/src -L../libpcp_qed/src/$$DESTDIR
+LIBS += -L../libpcp_qmc/src -L../libpcp_qmc/src/$$DESTDIR
+LIBS += -L../libpcp_qwt/src -L../libpcp_qwt/src/$$DESTDIR
+LIBS += -lpcp_qed -lpcp_qmc -lpcp_qwt -lpcp
+win32:LIBS += -lwsock32
+QT += network svg
+QMAKE_INFO_PLIST = pmchart.info
+QMAKE_CXXFLAGS += $$(PCP_CFLAGS)
diff --git a/src/pmchart/pmchart.qrc b/src/pmchart/pmchart.qrc
new file mode 100644
index 0000000..0d057f5
--- /dev/null
+++ b/src/pmchart/pmchart.qrc
@@ -0,0 +1,60 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/aboutpcp.png</file>
+ <file>images/aboutpmchart.png</file>
+ <file>images/camera-video.png</file>
+ <file>images/camera-video-close.png</file>
+ <file>images/document-new.png</file>
+ <file>images/document-open.png</file>
+ <file>images/document-close.png</file>
+ <file>images/document-print.png</file>
+ <file>images/document-export.png</file>
+ <file>images/document-properties.png</file>
+ <file>images/document-save.png</file>
+ <file>images/pmtime.png</file>
+ <file>images/pmtime-close.png</file>
+ <file>images/edit-clear.png</file>
+ <file>images/emblem-system.png</file>
+ <file>images/whatsthis.png</file>
+ <file>images/computer.png</file>
+ <file>images/process-stop.png</file>
+ <file>images/go-previous.png</file>
+ <file>images/system-search.png</file>
+ <file>images/help-browser.png</file>
+ <file>images/help-contents.png</file>
+ <file>images/go-jump.png</file>
+ <file>images/pmchart.png</file>
+ <file>images/archive.png</file>
+ <file>images/filearchive.png</file>
+ <file>images/filegeneric.png</file>
+ <file>images/filehtml.png</file>
+ <file>images/fileimage.png</file>
+ <file>images/filefolder.png</file>
+ <file>images/filepackage.png</file>
+ <file>images/filespreadsheet.png</file>
+ <file>images/fileview.png</file>
+ <file>images/filewordprocessor.png</file>
+ <file>images/settings.png</file>
+ <file>images/folio.png</file>
+ <file>images/filefolio.png</file>
+ <file>images/view.png</file>
+ <file>images/logfile.png</file>
+ <file>images/tab-new.png</file>
+ <file>images/tab-edit.png</file>
+ <file>images/tab-close.png</file>
+ <file>images/toolusers.png</file>
+ <file>images/zoom-in.png</file>
+ <file>images/zoom-out.png</file>
+ <file>images/play_live.png</file>
+ <file>images/stop_live.png</file>
+ <file>images/play_record.png</file>
+ <file>images/stop_record.png</file>
+ <file>images/play_archive.png</file>
+ <file>images/stop_archive.png</file>
+ <file>images/back_archive.png</file>
+ <file>images/stepfwd_archive.png</file>
+ <file>images/stepback_archive.png</file>
+ <file>images/fastfwd_archive.png</file>
+ <file>images/fastback_archive.png</file>
+</qresource>
+</RCC>
diff --git a/src/pmchart/pmchart.rc b/src/pmchart/pmchart.rc
new file mode 100644
index 0000000..438b694
--- /dev/null
+++ b/src/pmchart/pmchart.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "images/pmchart.ico"
diff --git a/src/pmchart/pmchart.sh.in b/src/pmchart/pmchart.sh.in
new file mode 100755
index 0000000..9c9054d
--- /dev/null
+++ b/src/pmchart/pmchart.sh.in
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PKG_MAC_DIR/MacOS/pmchart "$@"
diff --git a/src/pmchart/pmchart.ui b/src/pmchart/pmchart.ui
new file mode 100644
index 0000000..094be26
--- /dev/null
+++ b/src/pmchart/pmchart.ui
@@ -0,0 +1,989 @@
+<ui version="4.0" >
+ <class>PmChart</class>
+ <widget class="QMainWindow" name="PmChart" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>370</width>
+ <height>270</height>
+ </rect>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>182</width>
+ <height>196</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>PCP Charts</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <widget class="QWidget" name="widget" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="TabWidget" name="chartTabWidget" >
+ <property name="focusPolicy" >
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="whatsThis" >
+ <string>The main chart canvas, displaying all charts in the current Tab.</string>
+ </property>
+ <property name="tabShape" >
+ <enum>QTabWidget::Rounded</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>2</number>
+ </property>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="MenuBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>370</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="Help" >
+ <property name="title" >
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="helpManualAction" />
+ <addaction name="separator" />
+ <addaction name="helpAboutAction" />
+ <addaction name="helpAboutQtAction" />
+ <addaction name="helpSeeAlsoAction" />
+ <addaction name="separator" />
+ <addaction name="helpWhatsThisAction" />
+ </widget>
+ <widget class="QMenu" name="Time" >
+ <property name="title" >
+ <string>&amp;Options</string>
+ </property>
+ <addaction name="showTimeControlAction" />
+ <addaction name="hideTimeControlAction" />
+ <addaction name="toolbarAction" />
+ <addaction name="consoleAction" />
+ <addaction name="separator" />
+ <addaction name="newPmchartAction" />
+ </widget>
+ <widget class="QMenu" name="Record" >
+ <property name="title" >
+ <string>&amp;Record</string>
+ </property>
+ <addaction name="recordStartAction" />
+ <addaction name="recordQueryAction" />
+ <addaction name="recordStopAction" />
+ <addaction name="separator" />
+ <addaction name="recordDetachAction" />
+ </widget>
+ <widget class="QMenu" name="File" >
+ <property name="title" >
+ <string>&amp;File</string>
+ </property>
+ <addaction name="fileNewChartAction" />
+ <addaction name="closeChartAction" />
+ <addaction name="fileOpenViewAction" />
+ <addaction name="addTabAction" />
+ <addaction name="closeTabAction" />
+ <addaction name="separator" />
+ <addaction name="fileSaveViewAction" />
+ <addaction name="separator" />
+ <addaction name="fileExportAction" />
+ <addaction name="filePrintAction" />
+ <addaction name="separator" />
+ <addaction name="fileQuitAction" />
+ </widget>
+ <widget class="QMenu" name="Edit" >
+ <property name="title" >
+ <string>&amp;Edit</string>
+ </property>
+ <addaction name="editChartAction" />
+ <addaction name="editTabAction" />
+ <addaction name="separator" />
+ <addaction name="editSamplesAction" />
+ <addaction name="zoomInAction" />
+ <addaction name="zoomOutAction" />
+ <addaction name="separator" />
+ <addaction name="editSettingsAction" />
+ </widget>
+ <addaction name="File" />
+ <addaction name="Edit" />
+ <addaction name="Record" />
+ <addaction name="Time" />
+ <addaction name="separator" />
+ <addaction name="Help" />
+ </widget>
+ <widget class="QToolBar" name="toolBar" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>48</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>Configurable toolbar, use the Preferences dialog to change its contents</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <attribute name="toolBarArea" >
+ <number>4</number>
+ </attribute>
+ <addaction name="fileNewChartAction" />
+ <addaction name="fileOpenViewAction" />
+ <addaction name="separator" />
+ <addaction name="zoomInAction" />
+ <addaction name="zoomOutAction" />
+ <addaction name="separator" />
+ <addaction name="fileExportAction" />
+ <addaction name="separator" />
+ <addaction name="showTimeControlAction" />
+ <addaction name="newPmchartAction" />
+ </widget>
+ <action name="fileOpenViewAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/view.png</normaloff>:/images/view.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Open View...</string>
+ </property>
+ <property name="iconText" >
+ <string>Open View</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="fileSaveViewAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-save.png</normaloff>:/images/document-save.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Save View ...</string>
+ </property>
+ <property name="iconText" >
+ <string>Save View</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="fileExportAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-export.png</normaloff>:/images/document-export.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Export...</string>
+ </property>
+ <property name="iconText" >
+ <string>Export</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+E</string>
+ </property>
+ </action>
+ <action name="filePrintAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-print.png</normaloff>:/images/document-print.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Print...</string>
+ </property>
+ <property name="iconText" >
+ <string>Print</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+P</string>
+ </property>
+ </action>
+ <action name="fileQuitAction" >
+ <property name="text" >
+ <string>&amp;Quit</string>
+ </property>
+ <property name="iconText" >
+ <string>Quit</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="newPmchartAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset>
+ </property>
+ <property name="text" >
+ <string>New &amp;pmchart</string>
+ </property>
+ <property name="iconText" >
+ <string>New pmchart</string>
+ </property>
+ </action>
+ <action name="showTimeControlAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/pmtime.png</normaloff>:/images/pmtime.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Show Time</string>
+ </property>
+ </action>
+ <action name="hideTimeControlAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/pmtime-close.png</normaloff>:/images/pmtime-close.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Hide Time</string>
+ </property>
+ </action>
+ <action name="toolbarAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Toolbar</string>
+ </property>
+ </action>
+ <action name="consoleAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Console</string>
+ </property>
+ </action>
+ <action name="editChartAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-properties.png</normaloff>:/images/document-properties.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Chart...</string>
+ </property>
+ <property name="iconText" >
+ <string>Edit Chart</string>
+ </property>
+ </action>
+ <action name="editTabAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/tab-edit.png</normaloff>:/images/tab-edit.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Tab...</string>
+ </property>
+ <property name="iconText" >
+ <string>Edit Tab</string>
+ </property>
+ </action>
+ <action name="editSamplesAction" >
+ <property name="text" >
+ <string>Samples...</string>
+ </property>
+ <property name="iconText" >
+ <string>Edit Samples</string>
+ </property>
+ </action>
+ <action name="editSettingsAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Preferences...</string>
+ </property>
+ <property name="iconText" >
+ <string>Preferences</string>
+ </property>
+ </action>
+ <action name="closeTabAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/tab-close.png</normaloff>:/images/tab-close.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Close Tab</string>
+ </property>
+ <property name="iconText" >
+ <string>Close Tab</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+W</string>
+ </property>
+ </action>
+ <action name="fileNewChartAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-new.png</normaloff>:/images/document-new.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;New Chart...</string>
+ </property>
+ <property name="iconText" >
+ <string>New Chart</string>
+ </property>
+ </action>
+ <action name="addTabAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/tab-new.png</normaloff>:/images/tab-new.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Add Tab...</string>
+ </property>
+ <property name="iconText" >
+ <string>Add Tab</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+T</string>
+ </property>
+ </action>
+ <action name="closeChartAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/document-close.png</normaloff>:/images/document-close.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Close Chart</string>
+ </property>
+ <property name="iconText" >
+ <string>Close Chart</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+D</string>
+ </property>
+ </action>
+ <action name="zoomInAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/zoom-in.png</normaloff>:/images/zoom-in.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Zoom &amp;In</string>
+ </property>
+ <property name="iconText" >
+ <string>Zoom In</string>
+ </property>
+ </action>
+ <action name="zoomOutAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/zoom-out.png</normaloff>:/images/zoom-out.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Zoom Out</string>
+ </property>
+ <property name="iconText" >
+ <string>Zoom Out</string>
+ </property>
+ </action>
+ <action name="helpManualAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/help-contents.png</normaloff>:/images/help-contents.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Manual</string>
+ </property>
+ <property name="iconText" >
+ <string>Manual</string>
+ </property>
+ <property name="shortcut" >
+ <string>F1</string>
+ </property>
+ </action>
+ <action name="helpAboutAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset>
+ </property>
+ <property name="text" >
+ <string>About</string>
+ </property>
+ <property name="iconText" >
+ <string>About</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </action>
+ <action name="helpAboutQtAction" >
+ <property name="text" >
+ <string>About Qt</string>
+ </property>
+ <property name="iconText" >
+ <string>About Qt</string>
+ </property>
+ </action>
+ <action name="helpSeeAlsoAction" >
+ <property name="text" >
+ <string>See Also</string>
+ </property>
+ <property name="iconText" >
+ <string>See Also</string>
+ </property>
+ </action>
+ <action name="helpWhatsThisAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/whatsthis.png</normaloff>:/images/whatsthis.png</iconset>
+ </property>
+ <property name="text" >
+ <string>What's This</string>
+ </property>
+ <property name="iconText" >
+ <string>What's This</string>
+ </property>
+ <property name="shortcut" >
+ <string>Shift+F1</string>
+ </property>
+ </action>
+ <action name="recordStartAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/camera-video.png</normaloff>:/images/camera-video.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Start...</string>
+ </property>
+ </action>
+ <action name="recordQueryAction" >
+ <property name="text" >
+ <string>Query</string>
+ </property>
+ </action>
+ <action name="recordStopAction" >
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/camera-video-close.png</normaloff>:/images/camera-video-close.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Stop</string>
+ </property>
+ </action>
+ <action name="recordDetachAction" >
+ <property name="text" >
+ <string>Detach</string>
+ </property>
+ </action>
+ </widget>
+ <includes>
+ <include location="local" >qmc_time.h</include>
+ <include location="local" >tabdialog.h</include>
+ <include location="local" >infodialog.h</include>
+ <include location="local" >chartdialog.h</include>
+ <include location="local" >exportdialog.h</include>
+ <include location="local" >recorddialog.h</include>
+ <include location="local" >settingsdialog.h</include>
+ <include location="local" >qprinter.h</include>
+ <include location="local" >qfiledialog.h</include>
+ <include location="local" >timeaxis.h</include>
+ <include location="local" >qed_timebutton.h</include>
+ <include location="local" >timeaxis.h</include>
+ </includes>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>fileNewChartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>fileNewChart()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>fileOpenViewAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>fileOpenView()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>fileSaveViewAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>fileSaveView()</slot>
+ </connection>
+ <connection>
+ <sender>fileExportAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>fileExport()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>filePrintAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>filePrint()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>fileQuitAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>fileQuit()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpAboutAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>helpAbout()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpAboutQtAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>helpAboutQt()</slot>
+ </connection>
+ <connection>
+ <sender>helpSeeAlsoAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>helpSeeAlso()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpWhatsThisAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>whatsThis()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>showTimeControlAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>optionsShowTimeControl()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>hideTimeControlAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>optionsHideTimeControl()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>toolbarAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>optionsToolbar()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>consoleAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>optionsConsole()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>newPmchartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>optionsNewPmchart()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>editSettingsAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>editSettings()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>chartTabWidget</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>PmChart</receiver>
+ <slot>activeTabChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>editChartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>editChart()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>closeChartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>closeChart()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>editTabAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>editTab()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>editSamplesAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>editSamples()</slot>
+ </connection>
+ <connection>
+ <sender>closeTabAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>closeTab()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>addTabAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>addTab()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpManualAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>helpManual()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>recordStartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>recordStart()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>recordQueryAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>recordQuery()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>recordStopAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>recordStop()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>recordDetachAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>recordDetach()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>zoomInAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>zoomIn()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>zoomOutAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmChart</receiver>
+ <slot>zoomOut()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>171</x>
+ <y>133</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/recorddialog.cpp b/src/pmchart/recorddialog.cpp
new file mode 100644
index 0000000..08415e3
--- /dev/null
+++ b/src/pmchart/recorddialog.cpp
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "recorddialog.h"
+#include <QtCore/QTextStream>
+#include <QtGui/QMessageBox>
+#include <QtGui/QDoubleValidator>
+#include "main.h"
+#include "tab.h"
+#include "saveviewdialog.h"
+
+RecordDialog::RecordDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ deltaLineEdit->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 3, deltaLineEdit));
+}
+
+void RecordDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void RecordDialog::init(Tab *tab)
+{
+ QChar sep(__pmPathSeparator());
+ QString pmlogger = QDir::toNativeSeparators(QDir::homePath());
+ QString view, folio, archive;
+
+ pmlogger.append(sep);
+ pmlogger.append(".pcp");
+ pmlogger.append(sep);
+ pmlogger.append("pmlogger");
+ pmlogger.append(sep);
+ view = folio = archive = pmlogger;
+
+ view.append("[date].view");
+ viewLineEdit->setText(view);
+ folio.append("[date].folio");
+ folioLineEdit->setText(folio);
+ archive.append("[host]");
+ archive.append(sep);
+ archive.append("[date]");
+ archiveLineEdit->setText(archive);
+
+ my.tab = tab;
+ my.units = QmcTime::Seconds;
+ deltaLineEdit->setText(
+ QmcTime::deltaString(globalSettings.loggerDelta, my.units));
+
+ selectedRadioButton->setChecked(false);
+ allGadgetsRadioButton->setChecked(true);
+}
+
+void RecordDialog::selectedRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(true);
+ allGadgetsRadioButton->setChecked(false);
+}
+
+void RecordDialog::allGadgetsRadioButton_clicked()
+{
+ selectedRadioButton->setChecked(false);
+ allGadgetsRadioButton->setChecked(true);
+}
+
+void RecordDialog::deltaUnitsComboBox_activated(int value)
+{
+ double delta = tosec(*(pmtime->liveInterval()));
+ my.units = (QmcTime::DeltaUnits)value;
+ deltaLineEdit->setText(QmcTime::deltaString(delta, my.units));
+}
+
+void RecordDialog::viewPushButton_clicked()
+{
+ RecordFileDialog view(this);
+
+ QChar sep(__pmPathSeparator());
+ QString pmlogger = QDir::toNativeSeparators(QDir::homePath());
+ pmlogger.append(sep);
+ pmlogger.append(".pcp");
+ pmlogger.append(sep);
+ pmlogger.append("pmlogger");
+ pmlogger.append(sep);
+
+ view.setDirectory(pmlogger);
+ if (view.exec() == QDialog::Accepted)
+ viewLineEdit->setText(view.selectedFiles().at(0));
+}
+
+void RecordDialog::folioPushButton_clicked()
+{
+ RecordFileDialog folio(this);
+
+ QChar sep(__pmPathSeparator());
+ QString pmlogger = QDir::toNativeSeparators(QDir::homePath());
+ pmlogger.append(sep);
+ pmlogger.append(".pcp");
+ pmlogger.append(sep);
+ pmlogger.append("pmlogger");
+ pmlogger.append(sep);
+
+ folio.setDirectory(pmlogger);
+ if (folio.exec() == QDialog::Accepted)
+ folioLineEdit->setText(folio.selectedFiles().at(0));
+}
+
+void RecordDialog::archivePushButton_clicked()
+{
+ RecordFileDialog archive(this);
+
+ QChar sep(__pmPathSeparator());
+ QString pmlogger = QDir::toNativeSeparators(QDir::homePath());
+ pmlogger.append(sep);
+ pmlogger.append(".pcp");
+ pmlogger.append(sep);
+ pmlogger.append("pmlogger");
+ pmlogger.append(sep);
+
+ archive.setDirectory(pmlogger);
+ if (archive.exec() == QDialog::Accepted)
+ archiveLineEdit->setText(archive.selectedFiles().at(0));
+}
+
+bool RecordDialog::saveFolio(QString folioname, QString viewname)
+{
+ QFile folio(folioname);
+
+ if (!folio.open(QIODevice::WriteOnly)) {
+ QString msg = tr("Cannot open file: ");
+ msg.append(folioname);
+ msg.append("\n");
+ msg.append(folio.errorString());
+ QMessageBox::warning(this, pmProgname, msg);
+ return false;
+ }
+
+ QTextStream stream(&folio);
+ QString datetime;
+
+ datetime = QDateTime::currentDateTime().toString("ddd MMM d hh:mm:ss yyyy");
+ stream << "PCPFolio\n";
+ stream << "Version: 1\n";
+ stream << "# use pmafm(1) to process this PCP archive folio\n" << "#\n";
+ stream << "Created: on " << QmcSource::localHost;
+ stream << " at " << datetime << "\n";
+ stream << "Creator: pmchart " << viewname << "\n";
+ stream << "#\t\tHost\t\tBasename\n";
+
+ for (int i = 0; i < my.hosts.size(); i++) {
+ QString host = my.hosts.at(i);
+ QString archive = my.archives.at(i);
+ QFileInfo logFile(archive);
+ QDir logDir = logFile.dir();
+ logDir.mkpath(logDir.absolutePath());
+ stream << "Archive:\t" << my.hosts.at(i) << "\t\t" << archive << "\n";
+ }
+ return true;
+}
+
+bool RecordDialog::saveConfig(QString configfile, QString configdata)
+{
+ QFile config(configfile);
+
+ if (!config.open(QIODevice::WriteOnly)) {
+ QString msg = tr("Cannot open file: ");
+ msg.append(configfile);
+ msg.append("\n");
+ msg.append(config.errorString());
+ QMessageBox::warning(this, pmProgname, msg);
+ return false;
+ }
+
+ QTextStream stream(&config);
+ stream << configdata;
+ return true;
+}
+
+PmLogger::PmLogger(QObject *parent) : QProcess(parent)
+{
+ connect(this, SIGNAL(finished(int, QProcess::ExitStatus)),
+ this, SLOT(finished(int, QProcess::ExitStatus)));
+}
+
+void PmLogger::init(Tab *tab, QString host, QString logfile)
+{
+ my.tab = tab;
+ my.host = host;
+ my.logfile = logfile;
+ my.terminating = false;
+}
+
+void PmLogger::terminate()
+{
+ my.terminating = true;
+}
+
+void PmLogger::finished(int, QProcess::ExitStatus)
+{
+ if (my.terminating == false) {
+ my.terminating = true;
+ my.tab->stopRecording();
+
+ QString msg = "Recording process (pmlogger) exited unexpectedly\n";
+ msg.append("for host ");
+ msg.append(my.host);
+ msg.append(".\n\n");
+ msg.append("Additional diagnostics may be available in the log:\n");
+ msg.append(my.logfile);
+ QMessageBox::warning(pmchart, pmProgname, msg);
+ }
+}
+
+void RecordDialog::buttonOk_clicked()
+{
+ if (deltaLineEdit->isModified()) {
+ // convert to seconds, make sure its still in range 0.001-INT_MAX
+ double input = QmcTime::deltaValue(deltaLineEdit->text(), my.units);
+ if (input < 0.001 || input > INT_MAX) {
+ QString msg = tr("Record Sampling Interval is invalid.\n");
+ msg.append(deltaLineEdit->text());
+ msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+ }
+
+ QString today = QDateTime::currentDateTime().toString("yyyyMMdd.hh.mm.ss");
+
+ QString view = viewLineEdit->text().trimmed();
+ view.replace(QRegExp("^~"), QDir::toNativeSeparators(QDir::homePath()));
+ view.replace(QRegExp("\\[date\\]"), today);
+ view.replace(QRegExp("\\[host\\]"), QmcSource::localHost);
+ QFileInfo viewFile(view);
+ QDir viewDir = viewFile.dir();
+ if (viewDir.mkpath(viewDir.absolutePath()) == false) {
+ QString msg = tr("Failed to create path for view:\n");
+ msg.append(view);
+ msg.append("\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+
+ QString folio = folioLineEdit->text().trimmed();
+ folio.replace(QRegExp("^~"), QDir::toNativeSeparators(QDir::homePath()));
+ folio.replace(QRegExp("\\[date\\]"), today);
+ folio.replace(QRegExp("\\[host\\]"), QmcSource::localHost);
+ QFileInfo folioFile(folio);
+ QDir folioDir = folioFile.dir();
+ if (folioDir.mkpath(folioDir.absolutePath()) == false) {
+ QString msg = tr("Failed to create path for folio:\n");
+ msg.append(folio);
+ msg.append("\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ return;
+ }
+
+ console->post("RecordDialog verifying paths view=%s folio=%s",
+ (const char *)folio.toAscii(), (const char *)view.toAscii());
+
+ my.view = view;
+ my.folio = folio;
+ my.delta.setNum(QmcTime::deltaValue(deltaLineEdit->text(), my.units), 'f');
+
+ Tab *tab = pmchart->activeTab();
+ for (int c = 0; c < tab->gadgetCount(); c++) {
+ Gadget *gadget = tab->gadget(c);
+ if (selectedRadioButton->isChecked() && gadget != tab->currentGadget())
+ continue;
+ QStringList ghosts = gadget->hosts();
+ for (int i = 0; i < ghosts.count(); i++) {
+ if (!my.hosts.contains(ghosts.at(i)))
+ my.hosts.append(ghosts.at(i));
+ }
+ }
+
+ for (int h = 0; h < my.hosts.count(); h++) {
+ QString archive = archiveLineEdit->text().trimmed();
+ QString rehomer = QDir::toNativeSeparators(QDir::homePath());
+ archive.replace(QRegExp("^~"), rehomer);
+ archive.replace(QRegExp("\\[host\\]"), my.hosts.at(h));
+ archive.replace(QRegExp("\\[date\\]"), today);
+ my.archives.append(archive);
+ }
+
+ if (SaveViewDialog::saveView(view, false, false, false, true) == false)
+ return;
+ if (saveFolio(folio, view) == false)
+ return;
+ QDialog::accept();
+}
+
+//
+// write pmlogger, pmchart and pmafm configs, then start pmloggers.
+//
+void RecordDialog::startLoggers()
+{
+ QString pmlogger = pmGetConfig("PCP_BINADM_DIR");
+ QChar sep(__pmPathSeparator());
+ pmlogger.append(sep);
+ pmlogger.append("pmlogger");
+
+ QString regex = "^";
+ regex.append(QDir::toNativeSeparators(QDir::homePath()));
+ my.folio.replace(QRegExp(regex), "~");
+
+ Tab *tab = pmchart->activeTab();
+ tab->addFolio(my.folio, my.view);
+
+ for (int i = 0; i < my.hosts.size(); i++) {
+ PmLogger *process = new PmLogger(pmchart);
+ QString archive = my.archives.at(i);
+ QString host = my.hosts.at(i);
+ QString logfile, configfile;
+
+ configfile = archive;
+ configfile.append(".config");
+ logfile = archive;
+ logfile.append(".log");
+
+ process->init(my.tab, host, logfile);
+
+ QStringList arguments;
+ arguments << "-r" << "-c" << configfile << "-h" << host << "-x0";
+ arguments << "-l" << logfile << "-t" << my.delta << archive;
+
+ QString configdata("#pmlogger Version 1\n\n"); // header for file(1)
+ if (selectedRadioButton->isChecked())
+ configdata.append(tab->currentGadget()->pmloggerSyntax());
+ else
+ for (int c = 0; c < tab->gadgetCount(); c++)
+ configdata.append(tab->gadget(c)->pmloggerSyntax());
+ saveConfig(configfile, configdata);
+
+ process->start(pmlogger, arguments);
+ tab->addLogger(process, archive);
+
+ // Send initial control messages to pmlogger
+ QStringList control;
+ control << "V0\n";
+ control << "F" << my.folio << "\n";
+ control << "Ppmchart\n" << "R\n";
+ for (int i = 0; i < control.size(); i++)
+ process->write(control.at(i).toAscii());
+ }
+}
+
+// RecordFileDialog is the one which is displayed when you click
+// on one of the file selection push buttons (view/logfile/folio).
+
+RecordFileDialog::RecordFileDialog(QWidget *parent) : QFileDialog(parent)
+{
+ setAcceptMode(QFileDialog::AcceptSave);
+ setFileMode(QFileDialog::AnyFile);
+ setIconProvider(fileIconProvider);
+ setConfirmOverwrite(true);
+}
+
+void RecordFileDialog::setFileName(QString path)
+{
+ selectFile(path);
+}
diff --git a/src/pmchart/recorddialog.h b/src/pmchart/recorddialog.h
new file mode 100644
index 0000000..41c9ca9
--- /dev/null
+++ b/src/pmchart/recorddialog.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef RECORDDIALOG_H
+#define RECORDDIALOG_H
+
+#include "ui_recorddialog.h"
+#include <QtCore/QProcess>
+#include <QtGui/QFileDialog>
+#include <qmc_time.h>
+
+class Tab;
+
+class RecordDialog : public QDialog, public Ui::RecordDialog
+{
+ Q_OBJECT
+
+public:
+ RecordDialog(QWidget* parent);
+
+ virtual void init(Tab *tab);
+ virtual bool saveFolio(QString, QString);
+ virtual bool saveConfig(QString, QString);
+ virtual void startLoggers();
+
+public slots:
+ virtual void deltaUnitsComboBox_activated(int);
+ virtual void selectedRadioButton_clicked();
+ virtual void allGadgetsRadioButton_clicked();
+ virtual void viewPushButton_clicked();
+ virtual void folioPushButton_clicked();
+ virtual void archivePushButton_clicked();
+ virtual void buttonOk_clicked();
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ struct {
+ Tab *tab;
+ QString delta;
+ QmcTime::DeltaUnits units;
+
+ QString view;
+ QString folio;
+ QStringList hosts;
+ QStringList archives;
+ } my;
+};
+
+class PmLogger : public QProcess
+{
+ Q_OBJECT
+
+public:
+ PmLogger(QObject *parent);
+ void init(Tab *tab, QString host, QString log);
+ QString host() { return my.host; }
+
+public slots:
+ void terminate();
+ void finished(int, QProcess::ExitStatus);
+
+private:
+ struct {
+ Tab *tab;
+ QString host;
+ QString logfile;
+ bool terminating;
+ } my;
+};
+
+class RecordFileDialog : public QFileDialog, public Ui::RecordDialog
+{
+ Q_OBJECT
+
+public:
+ RecordFileDialog(QWidget* parent);
+ void setFileName(QString);
+};
+
+#endif // RECORDDIALOG_H
diff --git a/src/pmchart/recorddialog.ui b/src/pmchart/recorddialog.ui
new file mode 100644
index 0000000..0f8744d
--- /dev/null
+++ b/src/pmchart/recorddialog.ui
@@ -0,0 +1,540 @@
+<ui version="4.0" >
+ <class>RecordDialog</class>
+ <widget class="QDialog" name="RecordDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>410</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>410</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>16777215</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>Record</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/archive.png</iconset>
+ </property>
+ <property name="toolTip" >
+ <string/>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QRadioButton" name="allGadgetsRadioButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>All Charts in Tab</string>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="selectedRadioButton" >
+ <property name="text" >
+ <string>Selected Chart Only</string>
+ </property>
+ <property name="checked" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="deltaLabel" >
+ <property name="text" >
+ <string>Logging Interval:</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="deltaLineEdit" >
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="deltaUnitsComboBox" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex" >
+ <number>1</number>
+ </property>
+ <property name="maxCount" >
+ <number>6</number>
+ </property>
+ <property name="insertPolicy" >
+ <enum>QComboBox::NoInsert</enum>
+ </property>
+ <property name="duplicatesEnabled" >
+ <bool>false</bool>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="folioTextLabel" >
+ <property name="text" >
+ <string>Folio</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="folioLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="folioPushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/folio.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="viewTextLabel" >
+ <property name="text" >
+ <string>View</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="viewLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="viewPushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/view.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="archiveTextLabel" >
+ <property name="text" >
+ <string>Archive</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="archiveLineEdit" />
+ </item>
+ <item>
+ <widget class="QPushButton" name="archivePushButton" >
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/archive.png</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>buttonOk_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>selectedRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>selectedRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>allGadgetsRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>allGadgetsRadioButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>deltaUnitsComboBox</sender>
+ <signal>activated(int)</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>deltaUnitsComboBox_activated(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>viewPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>viewPushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>folioPushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>folioPushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>archivePushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>RecordDialog</receiver>
+ <slot>archivePushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/samplesdialog.cpp b/src/pmchart/samplesdialog.cpp
new file mode 100644
index 0000000..9244dc8
--- /dev/null
+++ b/src/pmchart/samplesdialog.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "samplesdialog.h"
+#include "main.h"
+
+SamplesDialog::SamplesDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+}
+
+void SamplesDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void SamplesDialog::reset(int samples, int visible)
+{
+ my.samples = my.visible = 0;
+
+ visibleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ visibleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ sampleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ sampleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ visibleCounter->setValue(visible);
+ visibleSlider->setValue(visible);
+ sampleCounter->setValue(samples);
+ sampleSlider->setValue(samples);
+
+ console->post(PmChart::DebugUi, "SamplesDialog::reset tot=%d/%d vis=%d/%d",
+ samples, my.samples, visible, my.visible);
+}
+
+int SamplesDialog::samples()
+{
+ return sampleCounter->value();
+}
+
+int SamplesDialog::visible()
+{
+ return visibleCounter->value();
+}
+
+void SamplesDialog::sampleValueChanged(int value)
+{
+ if (my.samples != value) {
+ my.samples = value;
+ displaySampleCounter();
+ displaySampleSlider();
+ if (my.visible > my.samples)
+ visibleSlider->setValue(value);
+ }
+}
+
+void SamplesDialog::visibleValueChanged(int value)
+{
+ if (my.visible != value) {
+ my.visible = value;
+ displayVisibleCounter();
+ displayVisibleSlider();
+ if (my.visible > my.samples)
+ sampleSlider->setValue(value);
+ }
+}
+
+void SamplesDialog::displaySampleSlider()
+{
+ sampleSlider->blockSignals(true);
+ sampleSlider->setValue(my.samples);
+ sampleSlider->blockSignals(false);
+}
+
+void SamplesDialog::displayVisibleSlider()
+{
+ visibleSlider->blockSignals(true);
+ visibleSlider->setValue(my.visible);
+ visibleSlider->blockSignals(false);
+}
+
+void SamplesDialog::displaySampleCounter()
+{
+ sampleCounter->blockSignals(true);
+ sampleCounter->setValue(my.samples);
+ sampleCounter->blockSignals(false);
+}
+
+void SamplesDialog::displayVisibleCounter()
+{
+ visibleCounter->blockSignals(true);
+ visibleCounter->setValue(my.visible);
+ visibleCounter->blockSignals(false);
+}
diff --git a/src/pmchart/samplesdialog.h b/src/pmchart/samplesdialog.h
new file mode 100644
index 0000000..f2c0b1f
--- /dev/null
+++ b/src/pmchart/samplesdialog.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2006-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SAMPLESDIALOG_H
+#define SAMPLESDIALOG_H
+
+#include "ui_samplesdialog.h"
+
+class SamplesDialog : public QDialog, public Ui::SamplesDialog
+{
+ Q_OBJECT
+
+public:
+ SamplesDialog(QWidget* parent);
+ void reset(int, int);
+ int samples();
+ int visible();
+
+public slots:
+ void sampleValueChanged(int);
+ void visibleValueChanged(int);
+
+protected slots:
+ void languageChange();
+
+private:
+ void displaySampleSlider();
+ void displayVisibleSlider();
+ void displaySampleCounter();
+ void displayVisibleCounter();
+
+ struct {
+ int samples;
+ int visible;
+ } my;
+};
+
+#endif // SAMPLESDIALOG_H
diff --git a/src/pmchart/samplesdialog.ui b/src/pmchart/samplesdialog.ui
new file mode 100644
index 0000000..06fc356
--- /dev/null
+++ b/src/pmchart/samplesdialog.ui
@@ -0,0 +1,353 @@
+<ui version="4.0" >
+ <class>SamplesDialog</class>
+ <widget class="QDialog" name="SamplesDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>330</width>
+ <height>220</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>330</width>
+ <height>220</height>
+ </size>
+ </property>
+ <property name="focusPolicy" >
+ <enum>Qt::WheelFocus</enum>
+ </property>
+ <property name="windowTitle" >
+ <string>Add Samples</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/zoom-out.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="sampleTextLabel" >
+ <property name="text" >
+ <string>Total Samples:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>26</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="sampleCounter" >
+ <property name="maximum" >
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QSlider" name="visibleSlider" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QSlider" name="sampleSlider" >
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="visibleTextLabel" >
+ <property name="text" >
+ <string>Visible Points:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>26</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="visibleCounter" >
+ <property name="maximum" >
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <spacer name="verticalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sampleCounter</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>sampleValueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sampleSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>sampleValueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>visibleCounter</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>visibleValueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>visibleSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SamplesDialog</receiver>
+ <slot>visibleValueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/sampling.cpp b/src/pmchart/sampling.cpp
new file mode 100644
index 0000000..f3e1507
--- /dev/null
+++ b/src/pmchart/sampling.cpp
@@ -0,0 +1,880 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <limits>
+#include "sampling.h"
+#include "main.h"
+#include <qnumeric.h>
+#include <qwt_picker_machine.h>
+
+SamplingItem::SamplingItem(Chart *parent,
+ QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp,
+ const QString &legend, Chart::Style style, int samples, int index)
+ : ChartItem(mp, msp, dp, legend)
+{
+ pmDesc desc = mp->desc().desc();
+
+ my.chart = parent;
+ my.info = QString::null;
+
+ // initialize the pcp data and item data arrays
+ my.dataCount = 0;
+ my.data = NULL;
+ my.itemData = NULL;
+ resetValues(samples, 0.0, 0.0);
+
+ // set base scale, then tweak if value to plot is time / time
+ my.scale = 1;
+ if (style != Chart::UtilisationStyle &&
+ desc.sem == PM_SEM_COUNTER && desc.units.dimTime == 0) {
+ if (desc.units.scaleTime == PM_TIME_USEC)
+ my.scale = 0.000001;
+ else if (desc.units.scaleTime == PM_TIME_MSEC)
+ my.scale = 0.001;
+ }
+
+ // create and attach the plot right here
+ my.curve = new SamplingCurve(label());
+ my.curve->attach(parent);
+
+ // the 1000 is arbitrary ... just want numbers to be monotonic
+ // decreasing as plots are added
+ my.curve->setZ(1000 - index);
+}
+
+SamplingItem::~SamplingItem(void)
+{
+ if (my.data != NULL)
+ free(my.data);
+ if (my.itemData != NULL)
+ free(my.itemData);
+}
+
+QwtPlotItem *
+SamplingItem::item(void)
+{
+ return my.curve;
+}
+
+QwtPlotCurve *
+SamplingItem::curve(void)
+{
+ return my.curve;
+}
+
+void
+SamplingItem::resetValues(int values, double, double)
+{
+ size_t size;
+
+ // Reset sizes of pcp data array and the plot data array
+ size = values * sizeof(my.data[0]);
+ if ((my.data = (double *)realloc(my.data, size)) == NULL)
+ nomem();
+ size = values * sizeof(my.itemData[0]);
+ if ((my.itemData = (double *)realloc(my.itemData, size)) == NULL)
+ nomem();
+ if (my.dataCount > values)
+ my.dataCount = values;
+}
+
+void
+SamplingItem::preserveSample(int index, int oldindex)
+{
+ if (my.dataCount > oldindex)
+ my.itemData[index] = my.data[index] = my.data[oldindex];
+ else
+ my.itemData[index] = my.data[index] = qQNaN();
+}
+
+void
+SamplingItem::punchoutSample(int index)
+{
+ my.data[index] = my.itemData[index] = qQNaN();
+}
+
+void
+SamplingItem::updateValues(bool forward,
+ bool rateConvert, pmUnits *units, int sampleHistory, int,
+ double, double, double)
+{
+ pmAtomValue scaled, raw;
+ QmcMetric *metric = ChartItem::my.metric;
+ double value;
+ int sz;
+
+ if (metric->numValues() < 1 || metric->error(0)) {
+ value = qQNaN();
+ } else {
+ // convert raw value to current chart scale
+ raw.d = rateConvert ? metric->value(0) : metric->currentValue(0);
+ pmConvScale(PM_TYPE_DOUBLE, &raw, &ChartItem::my.units, &scaled, units);
+ value = scaled.d * my.scale;
+ }
+
+ if (my.dataCount < sampleHistory)
+ sz = qMax(0, (int)(my.dataCount * sizeof(double)));
+ else
+ sz = qMax(0, (int)((my.dataCount - 1) * sizeof(double)));
+
+ if (forward) {
+ memmove(&my.data[1], &my.data[0], sz);
+ memmove(&my.itemData[1], &my.itemData[0], sz);
+ my.data[0] = value;
+ } else {
+ memmove(&my.data[0], &my.data[1], sz);
+ memmove(&my.itemData[0], &my.itemData[1], sz);
+ my.data[my.dataCount - 1] = value;
+ }
+
+ if (my.dataCount < sampleHistory)
+ my.dataCount++;
+}
+
+void
+SamplingItem::rescaleValues(pmUnits *new_units)
+{
+ pmUnits *old_units = &ChartItem::my.units;
+ pmAtomValue old_av, new_av;
+
+ console->post("Chart::update change units from %s to %s",
+ pmUnitsStr(old_units), pmUnitsStr(new_units));
+
+ for (int i = my.dataCount - 1; i >= 0; i--) {
+ if (my.data[i] != qQNaN()) {
+ old_av.d = my.data[i];
+ pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units);
+ my.data[i] = new_av.d;
+ }
+ if (my.itemData[i] != qQNaN()) {
+ old_av.d = my.itemData[i];
+ pmConvScale(PM_TYPE_DOUBLE, &old_av, old_units, &new_av, new_units);
+ my.itemData[i] = new_av.d;
+ }
+ }
+}
+
+void
+SamplingItem::replot(int history, double *timeData)
+{
+ int count = qMin(history, my.dataCount);
+ my.curve->setRawSamples(timeData, my.itemData, count);
+}
+
+void
+SamplingItem::revive(void)
+{
+ if (removed()) {
+ setRemoved(false);
+ my.curve->attach(my.chart);
+ }
+}
+
+void
+SamplingItem::remove(void)
+{
+ setRemoved(true);
+ my.curve->detach();
+
+ // We can't really do this properly (free memory, etc) - working around
+ // metrics class limit (its using an ordinal index for metrics, remove any
+ // and we'll get problems. Which means the plots array must also remain
+ // unchanged, as we drive things via the metriclist at times. D'oh.
+ // This blows - it means we have to continue to fetch metrics for those
+ // metrics that have been removed from the chart, which may be remote
+ // hosts, hosts which are down (introducing retry issues...). Bother.
+
+ //delete my.curve;
+ //free(my.legend);
+}
+
+void
+SamplingItem::setStroke(Chart::Style style, QColor color, bool antiAlias)
+{
+ int sem = metric()->desc().desc().sem;
+ bool step = (sem == PM_SEM_INSTANT || sem == PM_SEM_DISCRETE);
+
+ my.curve->setLegendColor(color);
+ my.curve->setRenderHint(QwtPlotItem::RenderAntialiased, antiAlias);
+
+ switch (style) {
+ case Chart::BarStyle:
+ my.curve->setPen(color);
+ my.curve->setBrush(QBrush(color, Qt::SolidPattern));
+ my.curve->setStyle(QwtPlotCurve::Sticks);
+ break;
+
+ case Chart::AreaStyle:
+ my.curve->setPen(color);
+ my.curve->setBrush(QBrush(color, Qt::SolidPattern));
+ my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines);
+ break;
+
+ case Chart::UtilisationStyle:
+ my.curve->setPen(QColor(Qt::black));
+ my.curve->setStyle(QwtPlotCurve::Steps);
+ my.curve->setBrush(QBrush(color, Qt::SolidPattern));
+ break;
+
+ case Chart::LineStyle:
+ my.curve->setPen(color);
+ my.curve->setBrush(QBrush(Qt::NoBrush));
+ my.curve->setStyle(step? QwtPlotCurve::Steps : QwtPlotCurve::Lines);
+ break;
+
+ case Chart::StackStyle:
+ my.curve->setPen(QColor(Qt::black));
+ my.curve->setBrush(QBrush(color, Qt::SolidPattern));
+ my.curve->setStyle(QwtPlotCurve::Steps);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+SamplingItem::clearCursor()
+{
+ // nothing to do here.
+}
+
+bool
+SamplingItem::containsPoint(const QRectF &, int)
+{
+ return false;
+}
+
+void
+SamplingItem::updateCursor(const QPointF &p, int)
+{
+ QString title = my.chart->YAxisTitle();
+
+ my.info.sprintf("[%.2f", (float)p.y());
+ if (title != QString::null) {
+ my.info.append(" ");
+ my.info.append(title);
+ }
+ my.info.append(" at ");
+ my.info.append(timeHiResString(p.x()));
+ my.info.append("]");
+
+ pmchart->setValueText(my.info);
+}
+
+const QString &
+SamplingItem::cursorInfo()
+{
+ return my.info;
+}
+
+void
+SamplingItem::copyRawDataPoint(int index)
+{
+ if (index < 0)
+ index = my.dataCount - 1;
+ my.itemData[index] = my.data[index];
+}
+
+int
+SamplingItem::maximumDataCount(int maximum)
+{
+ return qMax(maximum, my.dataCount);
+}
+
+void
+SamplingItem::truncateData(int offset)
+{
+ for (int index = my.dataCount + 1; index < offset; index++) {
+ my.data[index] = 0;
+ // don't re-set dataCount ... so we don't plot these values,
+ // we just want them to count 0 towards any Stack aggregation
+ }
+}
+
+double
+SamplingItem::sumData(int index, double sum)
+{
+ if (index < 0)
+ index = my.dataCount - 1;
+ if (index < my.dataCount && !qIsNaN(my.data[index]))
+ sum += my.data[index];
+ return sum;
+}
+
+void
+SamplingItem::copyRawDataArray(void)
+{
+ for (int index = 0; index < my.dataCount; index++)
+ my.itemData[index] = my.data[index];
+}
+
+void
+SamplingItem::copyDataPoint(int index)
+{
+ if (hidden() || index >= my.dataCount)
+ my.itemData[index] = qQNaN();
+ else
+ my.itemData[index] = my.data[index];
+}
+
+void
+SamplingItem::setPlotUtil(int index, double sum)
+{
+ if (index < 0)
+ index = my.dataCount - 1;
+ if (hidden() || sum == 0.0 ||
+ index >= my.dataCount || qIsNaN(my.data[index]))
+ my.itemData[index] = qQNaN();
+ else
+ my.itemData[index] = 100.0 * my.data[index] / sum;
+}
+
+double
+SamplingItem::setPlotStack(int index, double sum)
+{
+ if (index < 0)
+ index = my.dataCount - 1;
+ if (!hidden() && !qIsNaN(my.itemData[index])) {
+ sum += my.itemData[index];
+ my.itemData[index] = sum;
+ }
+ return sum;
+}
+
+double
+SamplingItem::setDataStack(int index, double sum)
+{
+ if (index < 0)
+ index = my.dataCount - 1;
+ if (hidden() || qIsNaN(my.data[index])) {
+ my.itemData[index] = qQNaN();
+ } else {
+ sum += my.data[index];
+ my.itemData[index] = sum;
+ }
+ return sum;
+}
+
+
+//
+// SamplingCurve deals with overriding some QwtPlotCurve defaults;
+// particularly around dealing with empty sections of chart (NaN),
+// and the way the legend is rendered.
+//
+
+void
+SamplingCurve::drawSeries(QPainter *p,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to) const
+{
+ int okFrom, okTo = from;
+ int size = (to > 0) ? to : dataSize();
+
+ while (okTo < size) {
+ okFrom = okTo;
+ while (qIsNaN(sample(okFrom).y()) && okFrom < size)
+ ++okFrom;
+ okTo = okFrom;
+ while (!qIsNaN(sample(okTo).y()) && okTo < size)
+ ++okTo;
+ if (okFrom < size)
+ QwtPlotCurve::drawSeries(p, xMap, yMap, canvasRect, okFrom, okTo-1);
+ }
+}
+
+
+//
+// SamplingScaleEngine deals with rendering the vertical Y-Axis
+//
+
+SamplingScaleEngine::SamplingScaleEngine() : QwtLinearScaleEngine()
+{
+ my.autoScale = true;
+ my.minimum = 0.0;
+ my.maximum = 1.0;
+}
+
+void
+SamplingScaleEngine::setScale(bool autoScale,
+ double minValue, double maxValue)
+{
+ my.autoScale = autoScale;
+ my.minimum = minValue;
+ my.maximum = maxValue;
+}
+
+void
+SamplingScaleEngine::autoScale(int maxSteps, double &minValue,
+ double &maxValue, double &stepSize) const
+{
+ if (my.autoScale) {
+ if (minValue > 0)
+ minValue = 0.0;
+ } else {
+ minValue = my.minimum;
+ maxValue = my.maximum;
+ }
+ QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize);
+}
+
+
+//
+// The SamplingEngine implements all sampling-specific Chart behaviour
+//
+SamplingEngine::SamplingEngine(Chart *chart, pmDesc &desc)
+{
+ QwtPlotPicker *picker = chart->my.picker;
+ ChartEngine *engine = chart->my.engine;
+
+ my.chart = chart;
+ my.rateConvert = engine->rateConvert();
+ my.antiAliasing = engine->antiAliasing();
+
+ normaliseUnits(desc);
+ my.units = desc.units;
+
+ my.scaleEngine = new SamplingScaleEngine();
+ chart->setAxisScaleEngine(QwtPlot::yLeft, my.scaleEngine);
+ chart->setAxisScaleDraw(QwtPlot::yLeft, new QwtScaleDraw());
+
+ // use an individual point picker for sampled data
+ picker->setStateMachine(new QwtPickerDragPointMachine());
+ picker->setRubberBand(QwtPicker::CrossRubberBand);
+ picker->setRubberBandPen(QColor(Qt::green));
+}
+
+SamplingItem *
+SamplingEngine::samplingItem(int index)
+{
+ return (SamplingItem *)my.chart->my.items[index];
+}
+
+ChartItem *
+SamplingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend)
+{
+ int sampleHistory = my.chart->my.tab->group()->sampleHistory();
+ int existingItemCount = my.chart->metricCount();
+ SamplingItem *item = new SamplingItem(my.chart, mp, msp, desc, legend,
+ my.chart->my.style,
+ sampleHistory, existingItemCount);
+
+ // Find current max count for all plot items
+ int i, size = 0;
+ for (i = 0; i < existingItemCount; i++)
+ size = samplingItem(i)->maximumDataCount(size);
+ // Zero any plot from there to end, so Stack<->Line transitions work
+ for (i = 0; i < existingItemCount; i++)
+ samplingItem(i)->truncateData(size);
+
+ return item;
+}
+
+void
+SamplingEngine::normaliseUnits(pmDesc &desc)
+{
+ if (my.rateConvert && desc.sem == PM_SEM_COUNTER) {
+ if (desc.units.dimTime == 0) {
+ desc.units.dimTime = -1;
+ desc.units.scaleTime = PM_TIME_SEC;
+ }
+ else if (desc.units.dimTime == 1) {
+ desc.units.dimTime = 0;
+ // don't play with scaleTime, need native per item scaleTime
+ // so we can apply correct scaling via item->scale, e.g. in
+ // the msec -> msec/sec after rate conversion ... see the
+ // calculation for item->scale below
+ }
+ }
+}
+
+bool
+SamplingEngine::isCompatible(pmDesc &desc)
+{
+ console->post("SamplingEngine::isCompatible"
+ " type=%d, units=%s", desc.type, pmUnitsStr(&desc.units));
+
+ if (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT)
+ return false;
+ normaliseUnits(desc);
+ if (my.units.dimSpace != desc.units.dimSpace ||
+ my.units.dimTime != desc.units.dimTime ||
+ my.units.dimCount != desc.units.dimCount)
+ return false;
+ return true;
+}
+
+void
+SamplingEngine::updateValues(bool forward,
+ int size, int points, double left, double right, double delta)
+{
+ int i, index = forward ? 0 : -1; /* first or last data point */
+ int itemCount = my.chart->metricCount();
+ Chart::Style style = my.chart->my.style;
+
+ // Drive new values into each chart item
+ for (int i = 0; i < itemCount; i++) {
+ samplingItem(i)->updateValues(forward, my.rateConvert, &my.units,
+ size, points, left, right, delta);
+ }
+
+ if (style == Chart::BarStyle || style == Chart::AreaStyle || style == Chart::LineStyle) {
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->copyRawDataPoint(index);
+ }
+ // Utilisation: like Stack, but normalize value to a percentage (0,100)
+ else if (style == Chart::UtilisationStyle) {
+ double sum = 0.0;
+ // compute sum
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->sumData(index, sum);
+ // scale all components
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->setPlotUtil(index, sum);
+ // stack components
+ sum = 0.0;
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->setPlotStack(index, sum);
+ }
+ else if (style == Chart::StackStyle) {
+ double sum = 0.0;
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->setDataStack(index, sum);
+ }
+
+#if DESPERATE
+ for (i = 0; i < my.chart->metricCount(); i++) {
+ console->post(PmChart::DebugForce, "metric[%d] value %f", i,
+ samplingItem(i)->metric()->currentValue(0));
+ }
+#endif
+}
+
+void
+SamplingEngine::redoScale(void)
+{
+ bool rescale = false;
+
+ // The 1,000 and 0.1 thresholds are just a heuristic guess.
+ //
+ // We're assuming lBound() plays no part in this, which is OK as
+ // the upper bound of the y-axis range (hBound()) drives the choice
+ // of appropriate units scaling.
+ //
+ if (my.scaleEngine->autoScale() &&
+ my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound() > 1000) {
+ double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound();
+ if (my.units.dimSpace == 1) {
+ switch (my.units.scaleSpace) {
+ case PM_SPACE_BYTE:
+ my.units.scaleSpace = PM_SPACE_KBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_KBYTE:
+ my.units.scaleSpace = PM_SPACE_MBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_MBYTE:
+ my.units.scaleSpace = PM_SPACE_GBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_GBYTE:
+ my.units.scaleSpace = PM_SPACE_TBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_TBYTE:
+ my.units.scaleSpace = PM_SPACE_PBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_PBYTE:
+ my.units.scaleSpace = PM_SPACE_EBYTE;
+ rescale = true;
+ break;
+ }
+ if (rescale) {
+ // logic here depends on PM_SPACE_* values being consecutive
+ // integer values as the scale increases
+ scaled_max /= 1024;
+ while (scaled_max > 1000) {
+ my.units.scaleSpace++;
+ scaled_max /= 1024;
+ if (my.units.scaleSpace == PM_SPACE_EBYTE) break;
+ }
+ }
+ }
+ else if (my.units.dimTime == 1) {
+ switch (my.units.scaleTime) {
+ case PM_TIME_NSEC:
+ my.units.scaleTime = PM_TIME_USEC;
+ rescale = true;
+ scaled_max /= 1000;
+ break;
+ case PM_TIME_USEC:
+ my.units.scaleTime = PM_TIME_MSEC;
+ rescale = true;
+ scaled_max /= 1000;
+ break;
+ case PM_TIME_MSEC:
+ my.units.scaleTime = PM_TIME_SEC;
+ rescale = true;
+ scaled_max /= 1000;
+ break;
+ case PM_TIME_SEC:
+ my.units.scaleTime = PM_TIME_MIN;
+ rescale = true;
+ scaled_max /= 60;
+ break;
+ case PM_TIME_MIN:
+ my.units.scaleTime = PM_TIME_HOUR;
+ rescale = true;
+ scaled_max /= 60;
+ break;
+ }
+ if (rescale) {
+ // logic here depends on PM_TIME* values being consecutive
+ // integer values as the scale increases
+ while (scaled_max > 1000) {
+ my.units.scaleTime++;
+ if (my.units.scaleTime <= PM_TIME_SEC)
+ scaled_max /= 1000;
+ else
+ scaled_max /= 60;
+ if (my.units.scaleTime == PM_TIME_HOUR) break;
+ }
+ }
+ }
+ }
+
+ if (rescale == false &&
+ my.scaleEngine->autoScale() &&
+ my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound() < 0.1) {
+ double scaled_max = my.chart->axisScaleDiv(QwtPlot::yLeft)->upperBound();
+ if (my.units.dimSpace == 1) {
+ switch (my.units.scaleSpace) {
+ case PM_SPACE_KBYTE:
+ my.units.scaleSpace = PM_SPACE_BYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_MBYTE:
+ my.units.scaleSpace = PM_SPACE_KBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_GBYTE:
+ my.units.scaleSpace = PM_SPACE_MBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_TBYTE:
+ my.units.scaleSpace = PM_SPACE_GBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_PBYTE:
+ my.units.scaleSpace = PM_SPACE_TBYTE;
+ rescale = true;
+ break;
+ case PM_SPACE_EBYTE:
+ my.units.scaleSpace = PM_SPACE_PBYTE;
+ rescale = true;
+ break;
+ }
+ if (rescale) {
+ // logic here depends on PM_SPACE_* values being consecutive
+ // integer values (in reverse) as the scale decreases
+ scaled_max *= 1024;
+ while (scaled_max < 0.1) {
+ my.units.scaleSpace--;
+ scaled_max *= 1024;
+ if (my.units.scaleSpace == PM_SPACE_BYTE) break;
+ }
+ }
+ }
+ else if (my.units.dimTime == 1) {
+ switch (my.units.scaleTime) {
+ case PM_TIME_USEC:
+ my.units.scaleTime = PM_TIME_NSEC;
+ rescale = true;
+ scaled_max *= 1000;
+ break;
+ case PM_TIME_MSEC:
+ my.units.scaleTime = PM_TIME_USEC;
+ rescale = true;
+ scaled_max *= 1000;
+ break;
+ case PM_TIME_SEC:
+ my.units.scaleTime = PM_TIME_MSEC;
+ rescale = true;
+ scaled_max *= 1000;
+ break;
+ case PM_TIME_MIN:
+ my.units.scaleTime = PM_TIME_SEC;
+ rescale = true;
+ scaled_max *= 60;
+ break;
+ case PM_TIME_HOUR:
+ my.units.scaleTime = PM_TIME_MIN;
+ rescale = true;
+ scaled_max *= 60;
+ break;
+ }
+ if (rescale) {
+ // logic here depends on PM_TIME* values being consecutive
+ // integer values (in reverse) as the scale decreases
+ while (scaled_max < 0.1) {
+ my.units.scaleTime--;
+ if (my.units.scaleTime < PM_TIME_SEC)
+ scaled_max *= 1000;
+ else
+ scaled_max *= 60;
+ if (my.units.scaleTime == PM_TIME_NSEC) break;
+ }
+ }
+ }
+ }
+
+ if (rescale) {
+ //
+ // need to rescale ... we transform all of the historical (raw)
+ // data, new data will be taken care of by changing my.units.
+ //
+ for (int i = 0; i < my.chart->metricCount(); i++)
+ samplingItem(i)->rescaleValues(&my.units);
+
+ if (my.chart->my.style == Chart::UtilisationStyle)
+ my.chart->setYAxisTitle("% utilization");
+ else
+ my.chart->setYAxisTitle(pmUnitsStr(&my.units));
+ my.chart->replot();
+ }
+}
+
+void
+SamplingEngine::replot(void)
+{
+ GroupControl *group = my.chart->my.tab->group();
+ int vh = group->visibleHistory();
+ double *vp = group->timeAxisData();
+ int itemCount = my.chart->metricCount();
+ int maxCount = 0;
+ int i, m;
+ double sum;
+
+#if DESPERATE
+ console->post(PmChart::DebugForce, "SamplingEngine::replot %d items)", itemCount);
+#endif
+
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->replot(vh, vp);
+
+ switch (my.chart->style()) {
+ case Chart::BarStyle:
+ case Chart::AreaStyle:
+ case Chart::LineStyle:
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->copyRawDataArray();
+ break;
+
+ case Chart::UtilisationStyle:
+ for (i = 0; i < itemCount; i++)
+ maxCount = samplingItem(i)->maximumDataCount(maxCount);
+ for (m = 0; m < maxCount; m++) {
+ sum = 0.0;
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->sumData(m, sum);
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->setPlotUtil(m, sum);
+ sum = 0.0;
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->setPlotStack(m, sum);
+ }
+ break;
+
+ case Chart::StackStyle:
+ for (i = 0; i < itemCount; i++)
+ maxCount = samplingItem(i)->maximumDataCount(maxCount);
+ for (m = 0; m < maxCount; m++) {
+ for (i = 0; i < itemCount; i++)
+ samplingItem(i)->copyDataPoint(m);
+ sum = 0.0;
+ for (i = 0; i < itemCount; i++)
+ sum = samplingItem(i)->setPlotStack(m, sum);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+SamplingEngine::scale(bool *autoScale, double *yMin, double *yMax)
+{
+ *autoScale = my.scaleEngine->autoScale();
+ *yMin = my.scaleEngine->minimum();
+ *yMax = my.scaleEngine->maximum();
+}
+
+void
+SamplingEngine::setScale(bool autoScale, double yMin, double yMax)
+{
+ my.scaleEngine->setScale(autoScale, yMin, yMax);
+
+ if (autoScale)
+ my.chart->setAxisAutoScale(QwtPlot::yLeft);
+ else
+ my.chart->setAxisScale(QwtPlot::yLeft, yMin, yMax);
+}
+
+void
+SamplingEngine::selected(const QPolygon &)
+{
+ // Nothing to do here.
+}
+
+void
+SamplingEngine::moved(const QPointF &p)
+{
+ my.chart->showPoint(p);
+}
+
+void
+SamplingEngine::setStyle(Chart::Style style)
+{
+ // Y-Axis title choice is difficult. A Utilisation plot by definition
+ // is dimensionless and scaled to a percentage, so a label of just
+ // "% utilization" makes sense ... there has been some argument in
+ // support of "% time utilization" as a special case when the metrics
+ // involve some aspect of time, but the base metrics in the common case
+ // are counters in units of time (e.g. the CPU view), which after rate
+ // conversion is indistinguishable from instantaneous or discrete
+ // metrics of dimension time^0 which are units compatible ... so we're
+ // opting for the simplest possible interpretation of utilization or
+ // everything else.
+ //
+ switch (style) {
+ case Chart::BarStyle:
+ case Chart::AreaStyle:
+ case Chart::LineStyle:
+ case Chart::StackStyle:
+ if (my.chart->style() == Chart::UtilisationStyle)
+ my.scaleEngine->setAutoScale(true);
+ my.chart->setYAxisTitle(pmUnitsStr(&my.units));
+ break;
+ case Chart::UtilisationStyle:
+ my.scaleEngine->setScale(false, 0.0, 100.0);
+ my.chart->setYAxisTitle("% utilization");
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/pmchart/sampling.h b/src/pmchart/sampling.h
new file mode 100644
index 0000000..fa8c552
--- /dev/null
+++ b/src/pmchart/sampling.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SAMPLING_H
+#define SAMPLING_H
+
+#include <QtCore/QVariant>
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_scale_engine.h>
+#include "chart.h"
+
+class SamplingCurve : public ChartCurve
+{
+public:
+ SamplingCurve(const QString &title) : ChartCurve(title) { }
+
+ virtual void drawSeries(QPainter *painter,
+ const QwtScaleMap &xMap, const QwtScaleMap &yMap,
+ const QRectF &canvasRect, int from, int to) const;
+};
+
+class SamplingItem : public ChartItem
+{
+public:
+ SamplingItem(Chart *,
+ QmcMetric *, pmMetricSpec *, pmDesc *,
+ const QString &, Chart::Style, int, int);
+ ~SamplingItem(void);
+
+ QwtPlotItem *item();
+ QwtPlotCurve *curve();
+
+ void preserveSample(int, int);
+ void punchoutSample(int);
+ void updateValues(bool, bool, pmUnits *, int, int, double, double, double);
+ void rescaleValues(pmUnits *);
+ void resetValues(int, double, double);
+ void revive();
+ void remove();
+ void setStroke(Chart::Style, QColor, bool);
+
+ void clearCursor();
+ bool containsPoint(const QRectF &, int);
+ void updateCursor(const QPointF &, int);
+ const QString &cursorInfo();
+
+ void replot(int, double *);
+ void copyRawDataArray(void);
+ void copyRawDataPoint(int index);
+ void copyDataPoint(int index);
+ int maximumDataCount(int maximum);
+ void truncateData(int offset);
+ double sumData(int index, double sum);
+ void setPlotUtil(int index, double sum);
+ double setPlotStack(int index, double sum);
+ double setDataStack(int index, double sum);
+
+private:
+ struct {
+ Chart *chart;
+ SamplingCurve *curve;
+ QString info;
+ double scale;
+ double *data;
+ double *itemData;
+ int dataCount;
+ } my;
+};
+
+//
+// *Always* clamp minimum metric value at zero when positive -
+// preventing confusion when values silently change up towards
+// the maximum over time (for pmchart, our users are expecting
+// a constant zero baseline at all times, or so we're told).
+//
+class SamplingScaleEngine : public QwtLinearScaleEngine
+{
+ friend class Chart;
+
+public:
+ SamplingScaleEngine();
+
+ double minimum() const { return my.minimum; }
+ double maximum() const { return my.maximum; }
+ bool autoScale() const { return my.autoScale; }
+ void setAutoScale(bool autoScale) { my.autoScale = autoScale; }
+ void setScale(bool autoScale, double minValue, double maxValue);
+ virtual void autoScale(int maxSteps, double &minValue,
+ double &maxValue, double &stepSize) const;
+
+private:
+ struct {
+ bool autoScale;
+ double minimum;
+ double maximum;
+ } my;
+};
+//
+//
+// Implement sampling-specific behaviour within a Chart
+//
+class SamplingEngine : public ChartEngine
+{
+public:
+ SamplingEngine(Chart *chart, pmDesc &);
+
+ bool isCompatible(pmDesc &);
+ ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &);
+
+ void updateValues(bool, int, int, double, double, double);
+ void replot(void);
+
+ bool autoScale() { return my.scaleEngine->autoScale(); }
+ void redoScale(void);
+ void setScale(bool, double, double);
+ void scale(bool *, double *, double *);
+ void setStyle(Chart::Style);
+
+ bool rateConvert() const { return my.rateConvert; }
+ void setRateConvert(bool enabled) { my.rateConvert = enabled; }
+ bool antiAliasing() const { return my.antiAliasing; }
+ void setAntiAliasing(bool enabled) { my.antiAliasing = enabled; }
+
+ void selected(const QPolygon &);
+ void moved(const QPointF &);
+
+private:
+ SamplingItem *samplingItem(int index);
+ void normaliseUnits(pmDesc &desc);
+
+ struct {
+ pmUnits units;
+ bool rateConvert;
+ bool antiAliasing;
+ SamplingScaleEngine *scaleEngine;
+ Chart *chart;
+ } my;
+};
+
+#endif // SAMPLING_H
diff --git a/src/pmchart/saveviewdialog.cpp b/src/pmchart/saveviewdialog.cpp
new file mode 100644
index 0000000..0a03d27
--- /dev/null
+++ b/src/pmchart/saveviewdialog.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "saveviewdialog.h"
+#include <QtCore/QDir>
+#include <QtGui/QCompleter>
+#include <QtGui/QMessageBox>
+#include "main.h"
+
+SaveViewDialog::SaveViewDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ my.dirModel = new QDirModel;
+ my.dirModel->setIconProvider(fileIconProvider);
+ dirListView->setModel(my.dirModel);
+
+ my.completer = new QCompleter;
+ my.completer->setModel(my.dirModel);
+ fileNameLineEdit->setCompleter(my.completer);
+
+ connect(dirListView->selectionModel(),
+ SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
+ this, SLOT(dirListView_selectionChanged()));
+
+ QDir dir;
+ QChar sep(__pmPathSeparator());
+ QString home = my.userDir = QDir::toNativeSeparators(QDir::homePath());
+ my.userDir.append(sep);
+ my.userDir.append(".pcp");
+ my.userDir.append(sep);
+ my.userDir.append("kmchart");
+ if (!dir.exists(my.userDir)) {
+ my.userDir = home;
+ my.userDir.append(sep);
+ my.userDir.append(".pcp");
+ my.userDir.append(sep);
+ my.userDir.append("pmchart");
+ }
+ my.hostDynamic = true;
+ my.sizeDynamic = true;
+
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ my.userDir);
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ home);
+}
+
+SaveViewDialog::~SaveViewDialog()
+{
+ delete my.completer;
+ delete my.dirModel;
+}
+
+void SaveViewDialog::reset(bool hostDynamic)
+{
+ QDir d;
+ if (!d.exists(my.userDir))
+ d.mkpath(my.userDir);
+ setPath(my.userDir);
+ preserveHostCheckBox->setChecked(hostDynamic == false);
+}
+
+void SaveViewDialog::setPathUi(const QString &path)
+{
+ if (path.isEmpty())
+ return;
+
+ int index = pathComboBox->findText(path);
+ if (index == -1) {
+ pathComboBox->addItem(fileIconProvider->icon(QFileIconProvider::Folder),
+ path);
+ index = pathComboBox->count() - 1;
+ }
+ pathComboBox->setCurrentIndex(index);
+ dirListView->selectionModel()->clear();
+
+ userToolButton->setChecked(path == my.userDir);
+
+ fileNameLineEdit->setModified(false);
+ fileNameLineEdit->clear();
+}
+
+void SaveViewDialog::setPath(const QModelIndex &index)
+{
+ console->post("SaveViewDialog::setPath QModelIndex path=%s",
+ (const char *)my.dirModel->filePath(index).toAscii());
+ my.dirIndex = index;
+ my.dirModel->refresh(index);
+ dirListView->setRootIndex(index);
+ setPathUi(my.dirModel->filePath(index));
+}
+
+void SaveViewDialog::setPath(const QString &path)
+{
+ console->post("SaveViewDialog::setPath QString path=%s",
+ (const char *)path.toAscii());
+ my.dirIndex = my.dirModel->index(path);
+ my.dirModel->refresh(my.dirIndex);
+ dirListView->setRootIndex(my.dirIndex);
+ setPathUi(path);
+}
+
+void SaveViewDialog::pathComboBox_currentIndexChanged(QString path)
+{
+ console->post("SaveViewDialog::pathComboBox_currentIndexChanged");
+ setPath(path);
+}
+
+void SaveViewDialog::parentToolButton_clicked()
+{
+ console->post("SaveViewDialog::parentToolButton_clicked");
+ setPath(my.dirModel->parent(my.dirIndex));
+}
+
+void SaveViewDialog::userToolButton_clicked(bool enabled)
+{
+ if (enabled) {
+ QDir dir;
+ if (!dir.exists(my.userDir))
+ dir.mkpath(my.userDir);
+ setPath(my.userDir);
+ }
+}
+
+void SaveViewDialog::dirListView_selectionChanged()
+{
+ QItemSelectionModel *selections = dirListView->selectionModel();
+ QModelIndexList selectedIndexes = selections->selectedIndexes();
+
+ console->post("SaveViewDialog::dirListView_clicked");
+
+ my.completer->setCompletionPrefix(my.dirModel->filePath(my.dirIndex));
+ if (selectedIndexes.count() != 1)
+ fileNameLineEdit->setText("");
+ else
+ fileNameLineEdit->setText(my.dirModel->fileName(selectedIndexes.at(0)));
+}
+
+void SaveViewDialog::dirListView_activated(const QModelIndex &index)
+{
+ QFileInfo fi = my.dirModel->fileInfo(index);
+
+ console->post("SaveViewDialog::dirListView_activated");
+
+ if (fi.isDir()) {
+ setPath(index);
+ }
+ else {
+ QString msg = fi.filePath();
+ msg.prepend(tr("View file "));
+ msg.append(tr(" exists. Overwrite?\n"));
+ if (QMessageBox::question(this, pmProgname, msg,
+ QMessageBox::Cancel|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::Ok, QMessageBox::NoButton) == QMessageBox::Ok)
+ if (saveViewFile(fi.absoluteFilePath()) == true)
+ done(0);
+ }
+}
+
+void SaveViewDialog::preserveHostCheckBox_toggled(bool hostPreserve)
+{
+ my.hostDynamic = (hostPreserve == false);
+}
+
+void SaveViewDialog::preserveSizeCheckBox_toggled(bool sizePreserve)
+{
+ my.sizeDynamic = (sizePreserve == false);
+}
+
+bool SaveViewDialog::saveViewFile(const QString &filename)
+{
+ if (my.sizeDynamic == false)
+ setGlobals(pmchart->size().width(), pmchart->size().height(),
+ activeGroup->visibleHistory(),
+ pmchart->pos().x(), pmchart->pos().y());
+ return saveView(filename, my.hostDynamic, my.sizeDynamic, false, true);
+}
+
+void SaveViewDialog::savePushButton_clicked()
+{
+ QString msg, filename;
+ QChar sep(__pmPathSeparator());
+
+ if (fileNameLineEdit->isModified()) {
+ filename = fileNameLineEdit->text().trimmed();
+ filename.prepend(my.dirModel->filePath(my.dirIndex).append(sep));
+ } else {
+ QItemSelectionModel *selections = dirListView->selectionModel();
+ QModelIndexList selectedIndexes = selections->selectedIndexes();
+
+ if (selectedIndexes.count() == 1)
+ filename = my.dirModel->filePath(selectedIndexes.at(0));
+ }
+
+ if (filename.isEmpty())
+ msg = tr("No View file specified");
+ else {
+ QFileInfo fi(filename);
+ if (fi.isDir())
+ setPath(filename);
+ else if (fi.exists()) {
+ msg = filename;
+ msg.prepend(tr("View file "));
+ msg.append(tr(" exists. Overwrite?\n"));
+ if (QMessageBox::question(this, pmProgname, msg,
+ QMessageBox::Cancel|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::Ok, QMessageBox::NoButton) == QMessageBox::Ok)
+ if (saveViewFile(fi.absoluteFilePath()) == true)
+ done(0);
+ msg = "";
+ }
+ else if (saveViewFile(fi.absoluteFilePath()) == true)
+ done(0);
+ }
+
+ if (msg.isEmpty() == false) {
+ QMessageBox::warning(this, pmProgname, msg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+}
diff --git a/src/pmchart/saveviewdialog.h b/src/pmchart/saveviewdialog.h
new file mode 100644
index 0000000..8ad81a2
--- /dev/null
+++ b/src/pmchart/saveviewdialog.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SAVEVIEWDIALOG_H
+#define SAVEVIEWDIALOG_H
+
+#include "ui_saveviewdialog.h"
+#include <QtGui/QDirModel>
+
+class Chart;
+
+class SaveViewDialog : public QDialog, public Ui::SaveViewDialog
+{
+ Q_OBJECT
+
+public:
+ SaveViewDialog(QWidget* parent);
+ ~SaveViewDialog();
+ void reset(bool);
+
+ static void saveChart(FILE *, Chart *, bool);
+ static bool saveView(QString, bool, bool, bool, bool);
+ static void setGlobals(int w, int h, int pts, int x, int y);
+
+public slots:
+ virtual void parentToolButton_clicked();
+ virtual void userToolButton_clicked(bool);
+ virtual void pathComboBox_currentIndexChanged(QString);
+
+ virtual void dirListView_selectionChanged();
+ virtual void dirListView_activated(const QModelIndex &);
+
+ virtual void preserveHostCheckBox_toggled(bool);
+ virtual void preserveSizeCheckBox_toggled(bool);
+ virtual void savePushButton_clicked();
+
+private:
+ struct {
+ QString userDir;
+ bool hostDynamic; // on-the-fly or explicit-hostnames-in-view
+ bool sizeDynamic; // on-the-fly or explicit-geometry-in-view
+ QDirModel *dirModel;
+ QModelIndex dirIndex;
+ QCompleter *completer;
+ } my;
+
+ void setPath(const QString &);
+ void setPath(const QModelIndex &);
+ void setPathUi(const QString &);
+
+ bool saveViewFile(const QString &);
+};
+
+#endif // SAVEVIEWDIALOG_H
diff --git a/src/pmchart/saveviewdialog.ui b/src/pmchart/saveviewdialog.ui
new file mode 100644
index 0000000..877567e
--- /dev/null
+++ b/src/pmchart/saveviewdialog.ui
@@ -0,0 +1,453 @@
+<ui version="4.0" >
+ <class>SaveViewDialog</class>
+ <widget class="QDialog" name="SaveViewDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>414</width>
+ <height>260</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Save View</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/view.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="2" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="fileNameLabel" >
+ <property name="minimumSize" >
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>Filename:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="fileNameLineEdit" />
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QListView" name="dirListView" >
+ <property name="font" >
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="flow" >
+ <enum>QListView::TopToBottom</enum>
+ </property>
+ <property name="isWrapping" stdset="0" >
+ <bool>true</bool>
+ </property>
+ <property name="resizeMode" >
+ <enum>QListView::Adjust</enum>
+ </property>
+ <property name="gridSize" >
+ <size>
+ <width>150</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="uniformItemSizes" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="pathLabel" >
+ <property name="minimumSize" >
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>Path:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="pathComboBox" />
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>8</width>
+ <height>26</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="parentToolButton" >
+ <property name="minimumSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string>Parent</string>
+ </property>
+ <property name="statusTip" >
+ <string/>
+ </property>
+ <property name="whatsThis" >
+ <string>Open the parent directory</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/go-previous.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="checkable" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>8</width>
+ <height>26</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="userToolButton" >
+ <property name="minimumSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/toolusers.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="preserveHostCheckBox" >
+ <property name="layoutDirection" >
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text" >
+ <string>Preserve hostnames in View</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="preserveSizeCheckBox" >
+ <property name="text" >
+ <string>Preserve window geometry in View</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>101</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="savePushButton" >
+ <property name="text" >
+ <string>Save</string>
+ </property>
+ <property name="autoDefault" >
+ <bool>false</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cancelButton" >
+ <property name="text" >
+ <string>Cancel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>savePushButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>savePushButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>fileNameLineEdit</sender>
+ <signal>returnPressed()</signal>
+ <receiver>savePushButton</receiver>
+ <slot>click()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>181</x>
+ <y>182</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>355</x>
+ <y>183</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>cancelButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>355</x>
+ <y>217</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>preserveHostCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>preserveHostCheckBox_toggled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>159</x>
+ <y>217</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>parentToolButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>parentToolButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>309</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>userToolButton</sender>
+ <signal>clicked(bool)</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>userToolButton_clicked(bool)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>385</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>pathComboBox</sender>
+ <signal>currentIndexChanged(QString)</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>pathComboBox_currentIndexChanged(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>180</x>
+ <y>22</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>dirListView</sender>
+ <signal>activated(QModelIndex)</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>dirListView_activated(QModelIndex)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>202</x>
+ <y>102</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>202</x>
+ <y>120</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>preserveSizeCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>SaveViewDialog</receiver>
+ <slot>preserveSizeCheckBox_toggled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>110</x>
+ <y>233</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>206</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/searchdialog.cpp b/src/pmchart/searchdialog.cpp
new file mode 100644
index 0000000..668d61a
--- /dev/null
+++ b/src/pmchart/searchdialog.cpp
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "searchdialog.h"
+#include <QMessageBox>
+#include "main.h"
+
+SearchDialog::SearchDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ my.count = 0;
+}
+
+void SearchDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void SearchDialog::reset(QTreeWidget *pmns)
+{
+ my.pmns = pmns;
+ buttonOk->setEnabled(false);
+ if (matchList->count() > 0)
+ buttonAll->setEnabled(true);
+ else
+ buttonAll->setEnabled(false);
+ changed();
+ listchanged();
+}
+
+void SearchDialog::clear()
+{
+ this->hostPattern->clear();
+ this->metricPattern->clear();
+ this->instancePattern->clear();
+ this->resultStatus->setText("");
+ matchList->clear();
+ my.pmnsList.clear();
+ buttonSearch->setEnabled(false);
+ buttonOk->setEnabled(false);
+ buttonAll->setEnabled(false);
+}
+
+void SearchDialog::changed()
+{
+ bool hostEnabled = (hostPattern->text() != QString::null);
+ bool metricEnabled = (metricPattern->text() != QString::null);
+ bool instanceEnabled = (instancePattern->text() != QString::null);
+ buttonSearch->setEnabled(hostEnabled || metricEnabled || instanceEnabled);
+}
+
+void SearchDialog::selectall()
+{
+ matchList->selectAll();
+}
+
+void SearchDialog::listchanged()
+{
+ QList<QListWidgetItem *> check = matchList->selectedItems();
+ if (check.size() == 0) {
+ buttonOk->setEnabled(false);
+ }
+ else {
+ buttonOk->setEnabled(true);
+ }
+}
+
+void SearchDialog::search()
+{
+ QString res;
+ QTreeWidgetItemIterator iterator(my.pmns, QTreeWidgetItemIterator::All);
+ int h_match = 0;
+ int m_match = 0;
+ int i_match;
+ int count;
+ QRegExp h_rx;
+ QRegExp m_rx;
+ QRegExp i_rx;
+
+ console->post(PmChart::DebugUi,
+ "SearchDialog::search host=\"%s\" metric=\"%s\" instance=\"%s\"",
+ (const char *)hostPattern->text().toAscii(),
+ (const char *)metricPattern->text().toAscii(),
+ (const char *)instancePattern->text().toAscii());
+
+ if (hostPattern->text() == QString::null &&
+ metricPattern->text() == QString::null &&
+ instancePattern->text() == QString::null) {
+ // got here via pressing Enter from one of the pattern input fields,
+ // and all the fields are empty ... do nothing
+ return;
+ }
+
+ if (hostPattern->text() != QString::null)
+ h_rx.setPattern(hostPattern->text());
+
+ if (metricPattern->text() != QString::null)
+ m_rx.setPattern(metricPattern->text());
+
+ if (instancePattern->text() != QString::null)
+ i_rx.setPattern(instancePattern->text());
+
+ matchList->clear();
+ my.pmnsList.clear();
+ count = 0;
+ for (; (*iterator); ++iterator) {
+ NameSpace *item = (NameSpace *)(*iterator);
+ if (item->isRoot()) {
+ // host name
+ if (hostPattern->text() != QString::null)
+ h_match = h_rx.indexIn(item->sourceName());
+ else
+ h_match = 0;
+ if (h_match >= 0) {
+ console->post(PmChart::DebugUi, "SearchDialog::search "
+ "host=\"%s\" h_match=%d",
+ (const char *)item->sourceName().toAscii(), h_match);
+ }
+ item->setExpanded(true, false);
+ m_match = -2;
+ }
+ else if (h_match >= 0 && item->isMetric()) {
+ // metric name
+ count++;
+ if (metricPattern->text() != QString::null)
+ m_match = m_rx.indexIn(item->metricName());
+ else
+ m_match = 0;
+ if (m_match >= 0) {
+ if (item->isLeaf() &&
+ instancePattern->text() == QString::null) {
+ QString fqn = item->sourceName().append(":");
+ fqn.append(item->metricName());
+ matchList->addItem(fqn);
+ my.pmnsList.append(item);
+ m_match = -2;
+
+ console->post(PmChart::DebugUi, "SearchDialog::search "
+ "host=%s h_match=%d metric=%s m_match=%d",
+ (const char *)item->sourceName().toAscii(), h_match,
+ (const char *)item->metricName().toAscii(), m_match);
+ }
+ if (item->isLeaf() == false) {
+ // has instance domain
+ item->setExpanded(true, false);
+ count--;
+ }
+ }
+ }
+ else if (h_match >= 0 && m_match >= 0 && item->isInst()) {
+ // matched last metric, now related instance name ...
+ count++;
+ if (instancePattern->text() != QString::null)
+ i_match = i_rx.indexIn(item->metricInstance());
+ else
+ i_match = 0;
+ if (i_match >= 0) {
+ QString fqn = item->sourceName().append(":");
+ fqn.append(item->metricName());
+ fqn.append("[").append(item->metricInstance()).append("]");
+ matchList->addItem(fqn);
+ my.pmnsList.append(item);
+
+ console->post(PmChart::DebugUi, "SearchDialog::search "
+ "host=%s h_match=%d metric=%s m_match=%d inst=%s i_match=%d",
+ (const char *)item->sourceName().toAscii(), h_match,
+ (const char *)item->metricName().toAscii(), m_match,
+ (const char *)item->metricInstance().toAscii(), i_match);
+ }
+ }
+ else if (item->isNonLeaf()) {
+ item->setExpanded(true, false);
+ m_match = -2;
+ }
+ else
+ m_match = -2;
+ }
+
+ if (matchList->count() > 0) {
+ buttonAll->setEnabled(true);
+ }
+
+ QTextStream(&res) << "Matched " << matchList->count() << " of " << count << " possibilities";
+ this->resultStatus->setText(res);
+}
+
+void SearchDialog::ok()
+{
+ QList<QListWidgetItem *> selected = matchList->selectedItems();
+ int i;
+ int row;
+ NameSpace *parent;
+
+ for (i = 0; i < selected.size(); i++) {
+ row = matchList->row(selected[i]);
+#if DESPERATE
+ fprintf(stderr, "[%d] %s:%s[%s]\n",
+ row, (const char *)my.pmnsList[row]->sourceName().toAscii(),
+ (const char *)my.pmnsList[row]->metricName().toAscii(),
+ (const char *)my.pmnsList[row]->metricInstance().toAscii());
+#endif
+ my.pmnsList[row]->setSelected(true);
+ parent = (NameSpace *)my.pmnsList[row]->parent();
+ while (parent->isRoot() == false) {
+#if DESPERATE
+ fprintf(stderr, "SearchDialog::ok expand: %s\n",
+ (const char *)parent->metricName().toAscii());
+#endif
+ parent->QTreeWidgetItem::setExpanded(true);
+ parent = (NameSpace *)parent->parent();
+ }
+ }
+
+ accept();
+}
diff --git a/src/pmchart/searchdialog.h b/src/pmchart/searchdialog.h
new file mode 100644
index 0000000..f156e9a
--- /dev/null
+++ b/src/pmchart/searchdialog.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SEARCHDIALOG_H
+#define SEARCHDIALOG_H
+
+#include "ui_searchdialog.h"
+#include <QtCore/QProcess>
+
+class NameSpace;
+class QTreeWidget;
+
+class SearchDialog : public QDialog, public Ui::SearchDialog
+{
+ Q_OBJECT
+
+public:
+ SearchDialog(QWidget* parent);
+ void reset(QTreeWidget *pmns);
+
+public slots:
+ virtual void clear();
+ virtual void search();
+ virtual void ok();
+ virtual void changed();
+ virtual void selectall();
+ virtual void listchanged();
+
+protected slots:
+ virtual void languageChange();
+
+private:
+ struct {
+ QTreeWidget *pmns;
+ bool isArchive;
+ QString source;
+ QString metric;
+ int count;
+ QList<NameSpace *> pmnsList;
+ } my;
+};
+
+#endif // SEARCHDIALOG_H
diff --git a/src/pmchart/searchdialog.ui b/src/pmchart/searchdialog.ui
new file mode 100644
index 0000000..c2cd03c
--- /dev/null
+++ b/src/pmchart/searchdialog.ui
@@ -0,0 +1,353 @@
+<ui version="4.0" >
+ <class>SearchDialog</class>
+ <widget class="QDialog" name="SearchDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>452</width>
+ <height>345</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle" >
+ <string>Metric Search</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="hostLabel" >
+ <property name="text" >
+ <string>Host Name Pattern:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="metricLabel" >
+ <property name="text" >
+ <string>Metric Name Pattern:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="instanceLabel" >
+ <property name="text" >
+ <string>Instance Name Pattern:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QLineEdit" name="hostPattern" />
+ </item>
+ <item row="1" column="1" >
+ <widget class="QLineEdit" name="metricPattern" />
+ </item>
+ <item row="2" column="1" >
+ <widget class="QLineEdit" name="instancePattern" />
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="buttonAll" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>Select &amp;All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="resultStatus" >
+ <property name="text" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>271</width>
+ <height>27</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonSearch" >
+ <property name="text" >
+ <string>&amp;Search</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonClear" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>C&amp;lear</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QListWidget" name="matchList" >
+ <property name="selectionMode" >
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>71</width>
+ <height>27</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>hostPattern</tabstop>
+ <tabstop>metricPattern</tabstop>
+ <tabstop>instancePattern</tabstop>
+ <tabstop>buttonSearch</tabstop>
+ <tabstop>matchList</tabstop>
+ <tabstop>buttonOk</tabstop>
+ <tabstop>buttonCancel</tabstop>
+ <tabstop>buttonClear</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonSearch</sender>
+ <signal>clicked()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>search()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>280</x>
+ <y>250</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>225</x>
+ <y>83</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonClear</sender>
+ <signal>clicked()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>clear()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>450</x>
+ <y>96</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>225</x>
+ <y>83</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>ok()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>365</x>
+ <y>250</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>225</x>
+ <y>83</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>450</x>
+ <y>250</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>225</x>
+ <y>83</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>hostPattern</sender>
+ <signal>textEdited(QString)</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>changed()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>303</x>
+ <y>21</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>229</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>metricPattern</sender>
+ <signal>textEdited(QString)</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>changed()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>303</x>
+ <y>21</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>229</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>instancePattern</sender>
+ <signal>textEdited(QString)</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>changed()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>303</x>
+ <y>79</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>229</x>
+ <y>130</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonAll</sender>
+ <signal>clicked()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>selectall()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>47</x>
+ <y>118</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>230</x>
+ <y>195</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>matchList</sender>
+ <signal>itemSelectionChanged()</signal>
+ <receiver>SearchDialog</receiver>
+ <slot>listchanged()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>230</x>
+ <y>242</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>230</x>
+ <y>195</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/seealsodialog.cpp b/src/pmchart/seealsodialog.cpp
new file mode 100644
index 0000000..ae663e8
--- /dev/null
+++ b/src/pmchart/seealsodialog.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "seealsodialog.h"
+
+SeeAlsoDialog::SeeAlsoDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+}
+
+void SeeAlsoDialog::seeAlsoOKButton_clicked()
+{
+ done(0);
+}
diff --git a/src/pmchart/seealsodialog.h b/src/pmchart/seealsodialog.h
new file mode 100644
index 0000000..b5465b6
--- /dev/null
+++ b/src/pmchart/seealsodialog.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SEEALSODIALOG_H
+#define SEEALSODIALOG_H
+
+#include "ui_seealsodialog.h"
+
+class SeeAlsoDialog : public QDialog, public Ui::SeeAlsoDialog
+{
+ Q_OBJECT
+
+public:
+ SeeAlsoDialog(QWidget* parent);
+
+public slots:
+ virtual void seeAlsoOKButton_clicked();
+};
+
+#endif // SEEALSODIALOG_H
diff --git a/src/pmchart/seealsodialog.ui b/src/pmchart/seealsodialog.ui
new file mode 100644
index 0000000..ed89b55
--- /dev/null
+++ b/src/pmchart/seealsodialog.ui
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SeeAlsoDialog</class>
+ <widget class="QDialog" name="SeeAlsoDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>410</width>
+ <height>250</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>410</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Acknowledgements</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="pmchart.qrc">
+ <normaloff>:/images/pmchart.png</normaloff>:/images/pmchart.png</iconset>
+ </property>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>9</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="seeAlsoPCPPixmapLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>76</width>
+ <height>82</height>
+ </size>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="pmchart.qrc">:/images/aboutpcp.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>51</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="versionPCPTextLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;&lt;i&gt;Performance Co-Pilot (PCP)&lt;/i&gt;&lt;/b&gt;&lt;br&gt;
+Copyright (c) 2010-2014 Red Hat&lt;br&gt;
+Copyright (c) 2006-2012 Aconex&lt;br&gt;
+Copyright (c) 1995-2007 SGI&lt;br&gt;
+... and many, many others!&lt;br&gt;
+&lt;i&gt;http://oss.sgi.com/projects/pcp&lt;/i&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="versionQwtTextLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;&lt;i&gt;Qt Widgets for Technical Apps (Qwt)&lt;/i&gt;&lt;/b&gt;&lt;br&gt;
+Copyright (c) 1997 Josef Wilgen&lt;br&gt;
+Copyright (c) 2002 Uwe Rathmann&lt;br&gt;
+&lt;i&gt;http://qwt.sourceforge.net/&lt;/i&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="seeAlsoOKButton">
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ <property name="text">
+ <string>OK</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>seeAlsoOKButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SeeAlsoDialog</receiver>
+ <slot>seeAlsoOKButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/settingsdialog.cpp b/src/pmchart/settingsdialog.cpp
new file mode 100644
index 0000000..2daa8b8
--- /dev/null
+++ b/src/pmchart/settingsdialog.cpp
@@ -0,0 +1,738 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007, 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "settingsdialog.h"
+#include <QtGui/QCompleter>
+#include <QtGui/QMessageBox>
+#include <QtGui/QFontDatabase>
+#include <QtGui/QListWidgetItem>
+#include "main.h"
+#include "hostdialog.h"
+
+SettingsDialog::SettingsDialog(QWidget* parent)
+ : QDialog(parent), disabled(Qt::Dense4Pattern)
+{
+ setupUi(this);
+
+#ifndef IS_DARWIN // only relevent as an option on Mac OS X
+ nativeToolbarCheckBox->setEnabled(false);
+#endif
+
+ setupActionsList();
+ enabled = actionListWidget->item(0)->background();
+ setupHostComboBox(activeGroup->context()->source().host());
+
+ chartDeltaLineEdit->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 3, chartDeltaLineEdit));
+ loggerDeltaLineEdit->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 3, loggerDeltaLineEdit));
+}
+
+void SettingsDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+int SettingsDialog::colorArray(ColorButton ***array)
+{
+ static ColorButton *buttons[] = {
+ colorButton1, colorButton2, colorButton3, colorButton4,
+ colorButton5, colorButton6, colorButton7, colorButton8,
+ colorButton9, colorButton10, colorButton11, colorButton12,
+ colorButton13, colorButton14, colorButton15, colorButton16,
+ colorButton17, colorButton18, colorButton19, colorButton20,
+ colorButton21, colorButton22,
+ };
+ *array = &buttons[0];
+ return sizeof(buttons) / sizeof(buttons[0]);
+}
+
+void SettingsDialog::enableUi()
+{
+ bool colors = (settingsTab->currentIndex() == 1);
+ bool userScheme = (schemeComboBox->currentIndex() > 1);
+
+ if (colors) {
+ removeSchemeButton->show();
+ updateSchemeButton->show();
+ }
+ else {
+ removeSchemeButton->hide();
+ updateSchemeButton->hide();
+ }
+ removeSchemeButton->setEnabled(userScheme);
+}
+
+void SettingsDialog::reset()
+{
+ my.chartUnits = QmcTime::Seconds;
+ chartDeltaLineEdit->setText(
+ QmcTime::deltaString(globalSettings.chartDelta, my.chartUnits));
+ my.loggerUnits = QmcTime::Seconds;
+ loggerDeltaLineEdit->setText(
+ QmcTime::deltaString(globalSettings.loggerDelta, my.loggerUnits));
+
+ my.visibleHistory = my.sampleHistory = 0;
+ visibleCounter->setValue(globalSettings.visibleHistory);
+ visibleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ visibleSlider->setValue(globalSettings.visibleHistory);
+ visibleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ sampleCounter->setValue(globalSettings.sampleHistory);
+ sampleCounter->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+ sampleSlider->setValue(globalSettings.sampleHistory);
+ sampleSlider->setRange(PmChart::minimumPoints(), PmChart::maximumPoints());
+
+ defaultBackgroundButton->setColor(QColor(globalSettings.chartBackground));
+ selectedHighlightButton->setColor(QColor(globalSettings.chartHighlight));
+
+ setupSchemeComboBox();
+ setupSchemePalette();
+ setupActionsList();
+ setupFontLists();
+
+ setupSavedHostsList();
+ removeHostButton->setEnabled(globalSettings.savedHosts.size() > 0);
+
+ startupToolbarCheckBox->setCheckState(
+ globalSettings.initialToolbar ? Qt::Checked : Qt::Unchecked);
+ nativeToolbarCheckBox->setCheckState(
+ globalSettings.nativeToolbar ? Qt::Checked : Qt::Unchecked);
+ toolbarAreasComboBox->setCurrentIndex(
+ globalSettings.toolbarLocation ? 1: 0);
+
+ enableUi();
+}
+
+void SettingsDialog::settingsTab_currentChanged(int)
+{
+ enableUi();
+}
+
+//
+// Sampling preferences
+//
+
+void SettingsDialog::chartDeltaLineEdit_editingFinished()
+{
+ double input = globalSettings.chartDelta;
+
+ // convert to seconds, make sure its still in range 0.001-INT_MAX
+ if (chartDeltaLineEdit->isModified())
+ input = QmcTime::deltaValue(chartDeltaLineEdit->text(), my.chartUnits);
+ if (input < 0.001 || input > INT_MAX) {
+ QString msg = tr("Default Chart Sampling Interval is invalid.\n");
+ msg.append(chartDeltaLineEdit->text());
+ msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ }
+ else if (input != globalSettings.chartDelta) {
+ globalSettings.chartDeltaModified = true;
+ globalSettings.chartDelta = input;
+ writeSettings();
+ }
+}
+
+void SettingsDialog::loggerDeltaLineEdit_editingFinished()
+{
+ double input = globalSettings.loggerDelta;
+
+ // convert to seconds, make sure its still in range 0.001-INT_MAX
+ if (loggerDeltaLineEdit->isModified())
+ input = QmcTime::deltaValue(loggerDeltaLineEdit->text(), my.loggerUnits);
+ if (input < 0.001 || input > INT_MAX) {
+ QString msg = tr("Default Record Sampling Interval is invalid.\n");
+ msg.append(loggerDeltaLineEdit->text());
+ msg.append(" is out of range (0.001 to 0x7fffffff seconds)\n");
+ QMessageBox::warning(this, pmProgname, msg);
+ }
+ else if (input != globalSettings.loggerDelta) {
+ globalSettings.loggerDeltaModified = true;
+ globalSettings.loggerDelta = input;
+ writeSettings();
+ }
+}
+
+void SettingsDialog::chartDeltaUnitsComboBox_activated(int value)
+{
+ double v = QmcTime::deltaValue(chartDeltaLineEdit->text(), my.chartUnits);
+ my.chartUnits = (QmcTime::DeltaUnits)value;
+ chartDeltaLineEdit->setText(QmcTime::deltaString(v, my.chartUnits));
+}
+
+void SettingsDialog::loggerDeltaUnitsComboBox_activated(int value)
+{
+ double v = QmcTime::deltaValue(loggerDeltaLineEdit->text(), my.loggerUnits);
+ my.loggerUnits = (QmcTime::DeltaUnits)value;
+ loggerDeltaLineEdit->setText(QmcTime::deltaString(v, my.loggerUnits));
+}
+
+void SettingsDialog::visible_valueChanged(int value)
+{
+ if (value != my.visibleHistory) {
+ my.visibleHistory = value;
+ displayVisibleCounter();
+ displayVisibleSlider();
+ if (my.visibleHistory > my.sampleHistory)
+ sampleSlider->setValue(value);
+ globalSettings.visibleHistoryModified = true;
+ globalSettings.visibleHistory = my.visibleHistory;
+ writeSettings();
+ }
+}
+
+void SettingsDialog::sample_valueChanged(int value)
+{
+ if (value != my.sampleHistory) {
+ my.sampleHistory = value;
+ displayTotalCounter();
+ displayTotalSlider();
+ if (my.visibleHistory > my.sampleHistory)
+ visibleSlider->setValue(value);
+ globalSettings.sampleHistoryModified = true;
+ globalSettings.sampleHistory = my.sampleHistory;
+ writeSettings();
+ }
+}
+
+void SettingsDialog::displayTotalSlider()
+{
+ sampleSlider->blockSignals(true);
+ sampleSlider->setValue(my.sampleHistory);
+ sampleSlider->blockSignals(false);
+}
+
+void SettingsDialog::displayVisibleSlider()
+{
+ visibleSlider->blockSignals(true);
+ visibleSlider->setValue(my.visibleHistory);
+ visibleSlider->blockSignals(false);
+}
+
+void SettingsDialog::displayTotalCounter()
+{
+ sampleCounter->blockSignals(true);
+ sampleCounter->setValue(my.sampleHistory);
+ sampleCounter->blockSignals(false);
+}
+
+void SettingsDialog::displayVisibleCounter()
+{
+ visibleCounter->blockSignals(true);
+ visibleCounter->setValue(my.visibleHistory);
+ visibleCounter->blockSignals(false);
+}
+
+//
+// Font preferences
+//
+
+void SettingsDialog::setupFontLists()
+{
+ QFontDatabase database;
+ const QStringList families = database.families();
+
+ console->post(PmChart::DebugUi,
+ "SettingsDialog::setupFontLists: default %s [%d]",
+ PmChart::defaultFontFamily(),
+ PmChart::defaultFontSize());
+
+ QCompleter *completeFamily = new QCompleter(families, familyLineEdit);
+ familyLineEdit->setCompleter(completeFamily);
+
+ familyListWidget->insertItems(0, families);
+ QString family = globalSettings.fontFamily;
+ familyLineEdit->setText(family);
+ updateFontList(familyListWidget, family);
+
+ styleListWidget->insertItems(0, database.styles(family));
+ QString style = globalSettings.fontStyle;
+ styleLineEdit->setText(style);
+ updateFontList(styleListWidget, style);
+
+ QStringList sizes;
+ foreach (int points, database.smoothSizes(family, style))
+ sizes << QString::number(points);
+ sizeListWidget->insertItems(0, sizes);
+ QString size = QString::number(globalSettings.fontSize);
+ sizeLineEdit->setText(size);
+ updateFontList(sizeListWidget, size);
+}
+
+void SettingsDialog::updateFontList(QListWidget *list, const QString &text)
+{
+ QList<QListWidgetItem *>items;
+
+ items = list->findItems(text, Qt::MatchExactly);
+ if (items.size() > 0)
+ list->setCurrentItem(items.at(0));
+}
+
+void SettingsDialog::familyLineEdit_editingFinished()
+{
+ updateFontList(familyListWidget, familyLineEdit->text());
+}
+
+void SettingsDialog::familyListWidget_itemClicked(QListWidgetItem *item)
+{
+ familyLineEdit->setText(item->text());
+}
+
+void SettingsDialog::styleLineEdit_editingFinished()
+{
+ updateFontList(styleListWidget, styleLineEdit->text());
+}
+
+void SettingsDialog::styleListWidget_itemClicked(QListWidgetItem *item)
+{
+ styleLineEdit->setText(item->text());
+}
+
+void SettingsDialog::sizeLineEdit_editingFinished()
+{
+ updateFontList(sizeListWidget, sizeLineEdit->text());
+}
+
+void SettingsDialog::sizeListWidget_itemClicked(QListWidgetItem *item)
+{
+ sizeLineEdit->setText(item->text());
+}
+
+void SettingsDialog::resetFontButton_clicked()
+{
+ QString family = PmChart::defaultFontFamily();
+ familyLineEdit->setText(family);
+ updateFontList(familyListWidget, family);
+ globalSettings.fontFamilyModified = true;
+ globalSettings.fontFamily = family;
+
+ QString style = QString("Normal");
+ styleLineEdit->setText(style);
+ updateFontList(styleListWidget, style);
+ globalSettings.fontStyleModified = true;
+ globalSettings.fontStyle = style;
+
+ QString size = QString::number(PmChart::defaultFontSize());
+ int fontSize = size.toInt();
+ sizeLineEdit->setText(size);
+ updateFontList(sizeListWidget, size);
+ globalSettings.fontSizeModified = true;
+ globalSettings.fontSize = fontSize;
+
+ writeSettings();
+ pmchart->updateFont(family, style, fontSize);
+}
+
+void SettingsDialog::applyFontButton_clicked()
+{
+ QString family = familyLineEdit->text();
+ globalSettings.fontFamilyModified = true;
+ globalSettings.fontFamily = family;
+
+ QString style = styleLineEdit->text();
+ globalSettings.fontStyleModified = true;
+ globalSettings.fontStyle = style;
+
+ int size = sizeLineEdit->text().toInt();
+ globalSettings.fontSizeModified = true;
+ globalSettings.fontSize = size;
+
+ writeSettings();
+ pmchart->updateFont(family, style, size);
+}
+
+//
+// Toolbar preferences
+//
+
+void SettingsDialog::setupActionsList()
+{
+ QList<QAction*> actionsList = pmchart->toolbarActionsList();
+ QList<QAction*> enabledList = pmchart->enabledActionsList();
+
+ actionListWidget->blockSignals(true);
+ actionListWidget->clear();
+ for (int i = 0; i < actionsList.size(); i++) {
+ QAction *action = actionsList.at(i);
+ QListWidgetItem *item = new QListWidgetItem(action->icon(), action->iconText());
+ actionListWidget->insertItem(i, item);
+ if (enabledList.contains(action) == false)
+ item->setBackground(disabled);
+ }
+ actionListWidget->blockSignals(false);
+}
+
+void SettingsDialog::startupToolbarCheckBox_clicked()
+{
+ globalSettings.initialToolbar =
+ startupToolbarCheckBox->checkState() == Qt::Checked;
+ globalSettings.initialToolbarModified = true;
+ writeSettings();
+}
+
+void SettingsDialog::nativeToolbarCheckBox_clicked()
+{
+ globalSettings.nativeToolbar =
+ nativeToolbarCheckBox->checkState() == Qt::Checked;
+ globalSettings.nativeToolbarModified = true;
+ writeSettings();
+ pmchart->updateToolbarLocation();
+}
+
+void SettingsDialog::toolbarAreasComboBox_currentIndexChanged(int)
+{
+ globalSettings.toolbarLocation = toolbarAreasComboBox->currentIndex();
+ globalSettings.toolbarLocationModified = true;
+ writeSettings();
+ pmchart->updateToolbarLocation();
+}
+
+void SettingsDialog::actionListWidget_itemClicked(QListWidgetItem *item)
+{
+ QStringList actions;
+
+ item->setBackground(item->background() == disabled ? enabled : disabled);
+ for (int i = 0; i < actionListWidget->count(); i++) {
+ QListWidgetItem *listitem = actionListWidget->item(i);
+ if (listitem->background() != disabled)
+ actions.append(listitem->text());
+ }
+ globalSettings.toolbarActions = actions;
+ globalSettings.toolbarActionsModified = true;
+ writeSettings();
+ pmchart->setEnabledActionsList(actions, true);
+ pmchart->updateToolbarContents();
+}
+
+//
+// Colors preferences
+//
+
+void SettingsDialog::selectedHighlightButton_clicked()
+{
+ selectedHighlightButton->clicked();
+ if (selectedHighlightButton->isSet()) {
+ globalSettings.chartHighlightModified = true;
+ globalSettings.chartHighlight = selectedHighlightButton->color();
+ globalSettings.chartHighlightName =
+ globalSettings.chartHighlight.name();
+ writeSettings();
+ }
+}
+
+void SettingsDialog::defaultBackgroundButton_clicked()
+{
+ defaultBackgroundButton->clicked();
+ if (defaultBackgroundButton->isSet()) {
+ globalSettings.chartBackgroundModified = true;
+ globalSettings.chartBackground = defaultBackgroundButton->color();
+ globalSettings.chartBackgroundName =
+ globalSettings.chartBackground.name();
+ pmchart->updateBackground();
+ writeSettings();
+ }
+}
+
+void SettingsDialog::colorButtonClicked(int n)
+{
+ ColorButton **buttons;
+
+ colorArray(&buttons);
+ buttons[n-1]->clicked();
+ if (buttons[n-1]->isSet()) {
+ ColorButton **buttons;
+
+ int colorCount = colorArray(&buttons);
+ for (int i = 0; i < colorCount; i++) {
+ QColor c = buttons[i]->color();
+ if (c == Qt::white)
+ continue;
+ globalSettings.defaultScheme.addColor(c);
+ }
+
+ globalSettings.defaultSchemeModified = true;
+ writeSettings();
+ }
+}
+
+void SettingsDialog::newScheme()
+{
+ reset();
+ my.newScheme = QString::null;
+ settingsTab->setCurrentIndex(1); // Colors Tab
+
+ // Disable signals here and explicitly call the index changed
+ // routine so that we don't race with setFocus and selectAll.
+ schemeComboBox->blockSignals(true);
+ schemeComboBox->setCurrentIndex(1); // New Scheme
+ schemeComboBox->blockSignals(false);
+ schemeComboBox_currentIndexChanged(1);
+ schemeLineEdit->selectAll();
+ schemeLineEdit->setFocus();
+ show();
+}
+
+void SettingsDialog::removeSchemeButton_clicked()
+{
+ ColorScheme::removeScheme(schemeComboBox->currentText());
+ setupSchemeComboBox();
+ schemeLineEdit->clear();
+ globalSettings.colorSchemesModified = true;
+ writeSettings();
+}
+
+void SettingsDialog::updateSchemeColors(ColorScheme *scheme)
+{
+ ColorButton **buttons;
+ int colorCount = colorArray(&buttons);
+
+ scheme->clear();
+ for (int i = 0; i < colorCount; i++) {
+ QColor c = buttons[i]->color();
+ if (c == Qt::white)
+ continue;
+ scheme->addColor(c);
+ }
+ scheme->setModified(true);
+}
+
+void SettingsDialog::updateSchemeButton_clicked()
+{
+ int index;
+ QString oldName = schemeComboBox->currentText();
+ QString newName = schemeLineEdit->text();
+
+ if (schemeComboBox->currentIndex() > 1) { // Edit scheme
+ if (newName != oldName) {
+ if (ColorScheme::lookupScheme(newName) == true)
+ goto conflict;
+ index = schemeComboBox->currentIndex();
+ schemeComboBox->setItemText(index, newName);
+ }
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++) {
+ if (oldName == globalSettings.colorSchemes[i].name()) {
+ globalSettings.colorSchemes[i].setName(newName);
+ updateSchemeColors(&globalSettings.colorSchemes[i]);
+ break;
+ }
+ }
+ }
+ else if (schemeComboBox->currentIndex() == 1) { // New Scheme
+ if (ColorScheme::lookupScheme(newName) == true)
+ goto conflict;
+ ColorScheme scheme;
+ my.newScheme = newName;
+ scheme.setName(newName);
+ updateSchemeColors(&scheme);
+ index = globalSettings.colorSchemes.size();
+ globalSettings.colorSchemes.append(scheme);
+ pmchart->newScheme(my.newScheme);
+ schemeComboBox->blockSignals(true);
+ schemeComboBox->addItem(newName);
+ schemeComboBox->setCurrentIndex(index + 2);
+ schemeComboBox->blockSignals(false);
+ }
+ else if (schemeComboBox->currentIndex() == 0) { // Default
+ updateSchemeColors(&globalSettings.defaultScheme);
+ }
+ globalSettings.colorSchemesModified = true;
+ writeSettings();
+ return;
+
+conflict:
+ QString msg = newName;
+ msg.prepend("New scheme name \"");
+ msg.append("\" conflicts with an existing name");
+ QMessageBox::warning(this, pmProgname, msg);
+}
+
+void SettingsDialog::setupSchemePalette()
+{
+ ColorButton **buttons;
+ int colorCount = colorArray(&buttons);
+ int i = 0, index = schemeComboBox->currentIndex();
+
+ if (index == 1) // keep whatever is there as the starting point
+ i = colorCount;
+ else if (index == 0) {
+ for (i = 0; i < globalSettings.defaultScheme.size() && i < colorCount; i++)
+ buttons[i]->setColor(globalSettings.defaultScheme.color(i));
+ }
+ else if (index > 1) {
+ int j = index - 2;
+ for (i = 0; i < globalSettings.colorSchemes[j].size() && i < colorCount; i++)
+ buttons[i]->setColor(globalSettings.colorSchemes[j].color(i));
+ }
+
+ while (i < colorCount)
+ buttons[i++]->setColor(QColor(Qt::white));
+}
+
+void SettingsDialog::setupSchemeComboBox()
+{
+ schemeComboBox->blockSignals(true);
+ schemeComboBox->clear();
+ schemeComboBox->addItem(tr("Default Scheme"));
+ schemeComboBox->addItem(tr("New Scheme"));
+ for (int i = 0; i < globalSettings.colorSchemes.size(); i++)
+ schemeComboBox->addItem(globalSettings.colorSchemes[i].name());
+ schemeComboBox->setCurrentIndex(0);
+ schemeComboBox->blockSignals(false);
+}
+
+void SettingsDialog::schemeComboBox_currentIndexChanged(int index)
+{
+ if (index == 0) { // Default Scheme
+ schemeLineEdit->setEnabled(false);
+ schemeLineEdit->setText("#-cycle");
+ removeSchemeButton->setEnabled(false);
+ setupSchemePalette();
+ }
+ else {
+ schemeLineEdit->setText(schemeComboBox->currentText());
+ schemeLineEdit->setEnabled(true);
+ if (index == 1)
+ removeSchemeButton->setEnabled(false);
+ else {
+ removeSchemeButton->setEnabled(true);
+ setupSchemePalette();
+ }
+ }
+}
+
+//
+// Host Preferences
+//
+
+void SettingsDialog::setupSavedHostsList()
+{
+ QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer);
+ QStringList savedHostsList = globalSettings.savedHosts;
+ const QString hostcombo = hostComboBox->currentText();
+
+ savedHostsListWidget->blockSignals(true);
+ savedHostsListWidget->clear();
+ for (int i = 0; i < savedHostsList.size(); i++) {
+ const QString &hostname = savedHostsList.at(i);
+ QListWidgetItem *item = new QListWidgetItem(hostIcon, hostname);
+ savedHostsListWidget->insertItem(i, item);
+ if (hostname == hostcombo)
+ savedHostsListWidget->setCurrentItem(item);
+ }
+ savedHostsListWidget->blockSignals(false);
+}
+
+void SettingsDialog::savedHostsListWidget_itemSelectionChanged()
+{
+ QList<QListWidgetItem *>selections = savedHostsListWidget->selectedItems();
+ removeHostButton->setEnabled(selections.size() > 0);
+}
+
+void SettingsDialog::removeHostButton_clicked()
+{
+ QList<QListWidgetItem *>selections = savedHostsListWidget->selectedItems();
+
+ for (int i = 0; i < selections.size(); i++) {
+ QListWidgetItem *item = selections.at(i);
+ savedHostsListWidget->removeItemWidget(item);
+ delete item;
+ }
+ globalSettings.savedHosts.clear();
+ for (int i = 0; i < savedHostsListWidget->count(); i++)
+ globalSettings.savedHosts << savedHostsListWidget->item(i)->text();
+ globalSettings.savedHostsModified = true;
+ removeHostButton->setEnabled(false);
+ writeSettings();
+}
+
+void SettingsDialog::addHostButton_clicked()
+{
+ QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer);
+ const QString hostname = hostComboBox->currentText();
+ bool found = false;
+
+ for (int i = 0; i < savedHostsListWidget->count(); i++) {
+ QListWidgetItem *item = savedHostsListWidget->item(i);
+ if (item->text() != hostname) {
+ item->setSelected(false);
+ } else {
+ savedHostsListWidget->setCurrentItem(item);
+ found = true;
+ }
+ }
+ if (!found) {
+ QListWidgetItem *item = new QListWidgetItem(hostIcon, hostname);
+ savedHostsListWidget->addItem(item);
+ savedHostsListWidget->setCurrentItem(item);
+ globalSettings.savedHostsModified = true;
+ globalSettings.savedHosts << hostname;
+ }
+ removeHostButton->setEnabled(true);
+ writeSettings();
+}
+
+void SettingsDialog::setupHostComboBox(const QString &hostname)
+{
+ QIcon hostIcon = fileIconProvider->icon(QFileIconProvider::Computer);
+ int index = 0;
+
+ hostComboBox->blockSignals(true);
+ hostComboBox->clear();
+ for (unsigned int i = 0; i < liveGroup->numContexts(); i++) {
+ QmcSource source = liveGroup->context(i)->source();
+ const QString &srchost = source.host();
+
+ if (hostname == srchost)
+ index = i;
+ hostComboBox->insertItem(i, hostIcon, source.host());
+ }
+ hostComboBox->setCurrentIndex(index);
+ hostComboBox->blockSignals(false);
+}
+
+void SettingsDialog::hostButton_clicked()
+{
+ HostDialog *host = new HostDialog(this);
+
+ if (host->exec() == QDialog::Accepted) {
+ QString hostname = host->getHostName();
+ QString hostspec = host->getHostSpecification();
+ int sts, flags = host->getContextFlags();
+
+ if (hostspec == QString::null || hostspec.length() == 0) {
+ hostspec.append(tr("Hostname not specified\n"));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else if ((sts = liveGroup->use(PM_CONTEXT_HOST, hostspec, flags)) < 0) {
+ hostspec.prepend(tr("Cannot connect to host: "));
+ hostspec.append(tr("\n"));
+ hostspec.append(tr(pmErrStr(sts)));
+ QMessageBox::warning(this, pmProgname, hostspec,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ Qt::NoButton, Qt::NoButton);
+ } else {
+ console->post(PmChart::DebugUi,
+ "OpenViewDialog::newHost: %s (flags=0x%x)",
+ (const char *)hostspec.toAscii(), flags);
+ setupHostComboBox(hostname);
+ if (globalSettings.savedHosts.contains(hostname) == false) {
+ globalSettings.savedHostsModified = true;
+ globalSettings.savedHosts << hostname;
+ setupSavedHostsList();
+ writeSettings();
+ }
+ }
+ }
+ delete host;
+}
diff --git a/src/pmchart/settingsdialog.h b/src/pmchart/settingsdialog.h
new file mode 100644
index 0000000..91a6df8
--- /dev/null
+++ b/src/pmchart/settingsdialog.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007, 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SETTINGSDIALOG_H
+#define SETTINGSDIALOG_H
+
+#include "ui_settingsdialog.h"
+#include "colorbutton.h"
+#include "colorscheme.h"
+#include <qmc_time.h>
+
+class SettingsDialog : public QDialog, public Ui::SettingsDialog
+{
+ Q_OBJECT
+
+public:
+ SettingsDialog(QWidget* parent);
+ void enableUi();
+ void reset();
+
+ void newScheme();
+ int colorArray(ColorButton *** array);
+
+public slots:
+ virtual void settingsTab_currentChanged(int index);
+
+ virtual void chartDeltaLineEdit_editingFinished();
+ virtual void loggerDeltaLineEdit_editingFinished();
+ virtual void chartDeltaUnitsComboBox_activated(int value);
+ virtual void loggerDeltaUnitsComboBox_activated(int value);
+ virtual void visible_valueChanged(int value);
+ virtual void sample_valueChanged(int value);
+
+ virtual void selectedHighlightButton_clicked();
+ virtual void defaultBackgroundButton_clicked();
+ virtual void colorButtonClicked(int);
+ virtual void removeSchemeButton_clicked();
+ virtual void updateSchemeButton_clicked();
+ virtual void schemeComboBox_currentIndexChanged(int);
+
+ virtual void familyLineEdit_editingFinished();
+ virtual void familyListWidget_itemClicked(QListWidgetItem *);
+ virtual void styleLineEdit_editingFinished();
+ virtual void styleListWidget_itemClicked(QListWidgetItem *);
+ virtual void sizeLineEdit_editingFinished();
+ virtual void sizeListWidget_itemClicked(QListWidgetItem *);
+ virtual void resetFontButton_clicked();
+ virtual void applyFontButton_clicked();
+
+ virtual void hostButton_clicked();
+ virtual void savedHostsListWidget_itemSelectionChanged();
+ virtual void removeHostButton_clicked();
+ virtual void addHostButton_clicked();
+
+ virtual void startupToolbarCheckBox_clicked();
+ virtual void nativeToolbarCheckBox_clicked();
+ virtual void toolbarAreasComboBox_currentIndexChanged(int);
+ virtual void actionListWidget_itemClicked(QListWidgetItem *);
+
+protected slots:
+ virtual void languageChange();
+
+ virtual void colorButton1_clicked() { colorButtonClicked(1); }
+ virtual void colorButton2_clicked() { colorButtonClicked(2); }
+ virtual void colorButton3_clicked() { colorButtonClicked(3); }
+ virtual void colorButton4_clicked() { colorButtonClicked(4); }
+ virtual void colorButton5_clicked() { colorButtonClicked(5); }
+ virtual void colorButton6_clicked() { colorButtonClicked(6); }
+ virtual void colorButton7_clicked() { colorButtonClicked(7); }
+ virtual void colorButton8_clicked() { colorButtonClicked(8); }
+ virtual void colorButton9_clicked() { colorButtonClicked(9); }
+ virtual void colorButton10_clicked() { colorButtonClicked(10); }
+ virtual void colorButton11_clicked() { colorButtonClicked(11); }
+ virtual void colorButton12_clicked() { colorButtonClicked(12); }
+ virtual void colorButton13_clicked() { colorButtonClicked(13); }
+ virtual void colorButton14_clicked() { colorButtonClicked(14); }
+ virtual void colorButton15_clicked() { colorButtonClicked(15); }
+ virtual void colorButton16_clicked() { colorButtonClicked(16); }
+ virtual void colorButton17_clicked() { colorButtonClicked(17); }
+ virtual void colorButton18_clicked() { colorButtonClicked(18); }
+ virtual void colorButton19_clicked() { colorButtonClicked(19); }
+ virtual void colorButton20_clicked() { colorButtonClicked(20); }
+ virtual void colorButton21_clicked() { colorButtonClicked(21); }
+ virtual void colorButton22_clicked() { colorButtonClicked(22); }
+
+private:
+ // font preferences
+ void setupFontLists();
+ void updateFontList(QListWidget *, const QString &);
+
+ // hosts preferences
+ void setupSavedHostsList();
+ void setupHostComboBox(const QString &);
+
+ // toolbar preferences
+ void setupActionsList();
+
+ // colors preferences
+ void setupSchemePalette();
+ void setupSchemeComboBox();
+ ColorScheme *lookupScheme(QString);
+ void updateSchemeColors(ColorScheme *);
+
+ // sampling preferences
+ void displayTotalSlider();
+ void displayVisibleSlider();
+ void displayTotalCounter();
+ void displayVisibleCounter();
+
+ struct {
+ QmcTime::DeltaUnits chartUnits;
+ QmcTime::DeltaUnits loggerUnits;
+ int visibleHistory;
+ int sampleHistory;
+ QString newScheme;
+ } my;
+
+ QBrush enabled, disabled; // brushes for painting action list backgrounds
+};
+
+#endif // SETTINGSDIALOG_H
diff --git a/src/pmchart/settingsdialog.ui b/src/pmchart/settingsdialog.ui
new file mode 100644
index 0000000..8f49b91
--- /dev/null
+++ b/src/pmchart/settingsdialog.ui
@@ -0,0 +1,2330 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SettingsDialog</class>
+ <widget class="QDialog" name="SettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>390</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>390</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>390</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Settings</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="pmchart.qrc">
+ <normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <widget class="QTabWidget" name="settingsTab">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>371</width>
+ <height>241</height>
+ </rect>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="samplesTab">
+ <attribute name="title">
+ <string>Sampling</string>
+ </attribute>
+ <widget class="QWidget" name="samplesLayoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>5</y>
+ <width>351</width>
+ <height>171</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="1" column="0" colspan="2">
+ <widget class="QLabel" name="loggerDeltaLabel">
+ <property name="text">
+ <string>Default Record Interval:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="chartDeltaLabel">
+ <property name="text">
+ <string>Default Chart Interval:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="3">
+ <widget class="QSlider" name="visibleSlider">
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="visibleTextLabel">
+ <property name="text">
+ <string>Default Visible Points:</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="sampleTextLabel">
+ <property name="text">
+ <string>Default Sample Count:</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QSpinBox" name="sampleCounter">
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLineEdit" name="chartDeltaLineEdit">
+ <property name="maxLength">
+ <number>6</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="4">
+ <widget class="QSpinBox" name="visibleCounter">
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="3">
+ <widget class="QSlider" name="sampleSlider">
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" colspan="2">
+ <widget class="QComboBox" name="chartDeltaUnitsComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <property name="maxCount">
+ <number>6</number>
+ </property>
+ <property name="insertPolicy">
+ <enum>QComboBox::NoInsert</enum>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ <item>
+ <property name="text">
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="2">
+ <widget class="QComboBox" name="loggerDeltaUnitsComboBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <property name="maxCount">
+ <number>6</number>
+ </property>
+ <property name="insertPolicy">
+ <enum>QComboBox::NoInsert</enum>
+ </property>
+ <property name="duplicatesEnabled">
+ <bool>false</bool>
+ </property>
+ <item>
+ <property name="text">
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLineEdit" name="loggerDeltaLineEdit">
+ <property name="maxLength">
+ <number>6</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QWidget" name="colorTab">
+ <attribute name="title">
+ <string>Colors</string>
+ </attribute>
+ <widget class="QGroupBox" name="colorSchemesGroupBox">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>80</y>
+ <width>351</width>
+ <height>121</height>
+ </rect>
+ </property>
+ <property name="title">
+ <string>Color Schemes</string>
+ </property>
+ <widget class="ColorButton" name="colorButton1">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton2">
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton3">
+ <property name="geometry">
+ <rect>
+ <x>70</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton4">
+ <property name="geometry">
+ <rect>
+ <x>100</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton5">
+ <property name="geometry">
+ <rect>
+ <x>130</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton6">
+ <property name="geometry">
+ <rect>
+ <x>160</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton7">
+ <property name="geometry">
+ <rect>
+ <x>190</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton8">
+ <property name="geometry">
+ <rect>
+ <x>220</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton9">
+ <property name="geometry">
+ <rect>
+ <x>250</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton10">
+ <property name="geometry">
+ <rect>
+ <x>280</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton11">
+ <property name="geometry">
+ <rect>
+ <x>310</x>
+ <y>50</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton12">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton13">
+ <property name="geometry">
+ <rect>
+ <x>40</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton14">
+ <property name="geometry">
+ <rect>
+ <x>70</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton15">
+ <property name="geometry">
+ <rect>
+ <x>100</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton16">
+ <property name="geometry">
+ <rect>
+ <x>130</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton17">
+ <property name="geometry">
+ <rect>
+ <x>160</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton18">
+ <property name="geometry">
+ <rect>
+ <x>190</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton19">
+ <property name="geometry">
+ <rect>
+ <x>220</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton20">
+ <property name="geometry">
+ <rect>
+ <x>250</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton21">
+ <property name="geometry">
+ <rect>
+ <x>280</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="colorButton22">
+ <property name="geometry">
+ <rect>
+ <x>310</x>
+ <y>70</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="QComboBox" name="schemeComboBox">
+ <property name="geometry">
+ <rect>
+ <x>11</x>
+ <y>22</y>
+ <width>171</width>
+ <height>26</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="schemeLineEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>190</x>
+ <y>20</y>
+ <width>151</width>
+ <height>29</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>#-cycle</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="updateSchemeButton">
+ <property name="geometry">
+ <rect>
+ <x>200</x>
+ <y>100</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Update</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="removeSchemeButton">
+ <property name="geometry">
+ <rect>
+ <x>70</x>
+ <y>100</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="ColorButton" name="selectedHighlightButton">
+ <property name="geometry">
+ <rect>
+ <x>200</x>
+ <y>40</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="ColorButton" name="defaultBackgroundButton">
+ <property name="geometry">
+ <rect>
+ <x>200</x>
+ <y>10</y>
+ <width>30</width>
+ <height>20</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ <widget class="QLabel" name="selectedHighlightLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>30</y>
+ <width>171</width>
+ <height>41</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Selected Chart Highlight:</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QLabel" name="defaultBackgroundLabel">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>0</y>
+ <width>171</width>
+ <height>41</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Default Chart Background:</string>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QWidget" name="fontTab">
+ <attribute name="title">
+ <string>Font</string>
+ </attribute>
+ <widget class="QWidget" name="sizeLayoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>310</x>
+ <y>10</y>
+ <width>48</width>
+ <height>161</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="sizeVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="sizeLabel">
+ <property name="text">
+ <string>Size</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="sizeLineEdit"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="sizeListWidget"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="styleLayoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>220</x>
+ <y>10</y>
+ <width>82</width>
+ <height>161</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="styleVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="styleLabel">
+ <property name="text">
+ <string>Style</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="styleLineEdit"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="styleListWidget"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="familyLayoutWidget">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>205</width>
+ <height>161</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="familyVerticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="familyLabel">
+ <property name="text">
+ <string>Family</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="familyLineEdit"/>
+ </item>
+ <item>
+ <widget class="QListWidget" name="familyListWidget"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QPushButton" name="resetFontButton">
+ <property name="geometry">
+ <rect>
+ <x>80</x>
+ <y>180</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="applyFontButton">
+ <property name="geometry">
+ <rect>
+ <x>210</x>
+ <y>180</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Apply</string>
+ </property>
+ </widget>
+ </widget>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Hosts</string>
+ </attribute>
+ <widget class="QPushButton" name="addHostButton">
+ <property name="geometry">
+ <rect>
+ <x>210</x>
+ <y>180</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="removeHostButton">
+ <property name="geometry">
+ <rect>
+ <x>80</x>
+ <y>180</y>
+ <width>75</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ <widget class="QPushButton" name="hostButton">
+ <property name="text" >
+ <string>...</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >:/images/computer.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>260</x>
+ <y>10</y>
+ <width>92</width>
+ <height>27</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QComboBox" name="hostComboBox">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>241</width>
+ <height>27</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QGroupBox" name="savedHostsGroupBox">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>40</y>
+ <width>341</width>
+ <height>131</height>
+ </rect>
+ </property>
+ <property name="title">
+ <string>Saved Hosts</string>
+ </property>
+ <widget class="QListWidget" name="savedHostsListWidget">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>20</y>
+ <width>321</width>
+ <height>111</height>
+ </rect>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="viewMode">
+ <enum>QListView::ListMode</enum>
+ </property>
+ <property name="selectionRectVisible">
+ <bool>false</bool>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ <widget class="QWidget" name="toolbarTab">
+ <attribute name="title">
+ <string>Toolbar</string>
+ </attribute>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>9</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="startupToolbarCheckBox">
+ <property name="text">
+ <string>Show Toolbar on Startup</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="nativeToolbarCheckBox">
+ <property name="text">
+ <string>Native Toolbar Style</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="toolbarLocationLabel">
+ <property name="text">
+ <string>Location and Orientation:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="toolbarAreasComboBox">
+ <item>
+ <property name="text">
+ <string>Top and Horizonal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right and Vertical</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="toolbarActionsLabel">
+ <property name="text">
+ <string>Actions:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="actionListWidget">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="showDropIndicator" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="textElideMode">
+ <enum>Qt::ElideNone</enum>
+ </property>
+ <property name="horizontalScrollMode">
+ <enum>QAbstractItemView::ScrollPerPixel</enum>
+ </property>
+ <property name="flow">
+ <enum>QListView::LeftToRight</enum>
+ </property>
+ <property name="isWrapping" stdset="0">
+ <bool>false</bool>
+ </property>
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="viewMode">
+ <enum>QListView::IconMode</enum>
+ </property>
+ <property name="uniformItemSizes">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <widget class="QPushButton" name="closeSettingsButton">
+ <property name="geometry">
+ <rect>
+ <x>290</x>
+ <y>260</y>
+ <width>92</width>
+ <height>31</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>&amp;Close</string>
+ </property>
+ <property name="shortcut">
+ <string/>
+ </property>
+ <property name="autoDefault">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ColorButton</class>
+ <extends>QToolButton</extends>
+ <header>colorbutton.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources>
+ <include location="pmchart.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>closeSettingsButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>defaultBackgroundButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>defaultBackgroundButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton1</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton1_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton2</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton2_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton3</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton3_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton4</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton4_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton5</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton5_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton6</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton6_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton7</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton7_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton8</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton8_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton9</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton9_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton10</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton10_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton11</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton11_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>345</x>
+ <y>163</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>193</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton12</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton12_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>45</x>
+ <y>183</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>193</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton12</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton12_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton13</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton13_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton14</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton14_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton15</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton15_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton16</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton16_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton17</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton17_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton18</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton18_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton19</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton19_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton20</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton20_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton21</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton21_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>colorButton22</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>colorButton22_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>345</x>
+ <y>183</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>193</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>chartDeltaLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>chartDeltaLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>loggerDeltaLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>loggerDeltaLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>chartDeltaUnitsComboBox</sender>
+ <signal>activated(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>chartDeltaUnitsComboBox_activated(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>loggerDeltaUnitsComboBox</sender>
+ <signal>activated(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>loggerDeltaUnitsComboBox_activated(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sampleCounter</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>sample_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sampleSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>sample_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>visibleCounter</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>visible_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>visibleSlider</sender>
+ <signal>valueChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>visible_valueChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>startupToolbarCheckBox</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>startupToolbarCheckBox_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>111</x>
+ <y>51</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>nativeToolbarCheckBox</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>nativeToolbarCheckBox_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>toolbarAreasComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>toolbarAreasComboBox_currentIndexChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>276</x>
+ <y>86</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>actionListWidget</sender>
+ <signal>itemClicked(QListWidgetItem*)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>actionListWidget_itemClicked(QListWidgetItem*)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>201</x>
+ <y>163</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>199</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>schemeComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>schemeComboBox_currentIndexChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>110</x>
+ <y>138</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>193</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>selectedHighlightButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>selectedHighlightButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>346</x>
+ <y>94</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>194</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>removeSchemeButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>removeSchemeButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>129</x>
+ <y>244</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>194</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>updateSchemeButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>updateSchemeButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>43</x>
+ <y>244</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>194</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>settingsTab</sender>
+ <signal>currentChanged(int)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>settingsTab_currentChanged(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>195</x>
+ <y>114</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>194</x>
+ <y>129</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>familyLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>familyLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>styleLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>styleLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sizeLineEdit</sender>
+ <signal>editingFinished()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>sizeLineEdit_editingFinished()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>familyListWidget</sender>
+ <signal>itemClicked(QListWidgetItem*)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>familyListWidget_itemClicked(QListWidgetItem*)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>styleListWidget</sender>
+ <signal>itemClicked(QListWidgetItem*)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>styleListWidget_itemClicked(QListWidgetItem*)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sizeListWidget</sender>
+ <signal>itemClicked(QListWidgetItem*)</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>sizeListWidget_itemClicked(QListWidgetItem*)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>resetFontButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>resetFontButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>applyFontButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>applyFontButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>hostButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>hostButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>savedHostsListWidget</sender>
+ <signal>itemSelectionChanged()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>savedHostsListWidget_itemSelectionChanged()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>removeHostButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>removeHostButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>addHostButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SettingsDialog</receiver>
+ <slot>addHostButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/statusbar.cpp b/src/pmchart/statusbar.cpp
new file mode 100644
index 0000000..2947a87
--- /dev/null
+++ b/src/pmchart/statusbar.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui>
+#include <QHBoxLayout>
+#include "statusbar.h"
+#include "main.h"
+
+StatusBar::StatusBar()
+{
+ setFixedHeight(buttonSize());
+ setSizeGripEnabled(false);
+
+ my.timeButton = new QedTimeButton(this);
+ my.timeButton->setFixedSize(QSize(buttonSize(), buttonSize()));
+ my.timeButton->setWhatsThis(QApplication::translate("PmChart",
+ "VCR state button, also used to display the time control window.",
+ 0, QApplication::UnicodeUTF8));
+ my.timeFrame = new QToolButton(this);
+ my.timeFrame->setMinimumSize(QSize(buttonSize(), buttonSize()));
+ my.timeFrame->setSizePolicy(
+ QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
+ my.timeFrame->setWhatsThis(QApplication::translate("PmChart",
+ "Unified time axis, displaying the current time position at the "
+ "rightmost point, and either status information or the timeframe "
+ "covering all Visible Points to the left",
+ 0, QApplication::UnicodeUTF8));
+
+ delete layout();
+ QHBoxLayout *box = new QHBoxLayout;
+ box->setMargin(0);
+ box->setSpacing(1);
+ box->addWidget(my.timeButton);
+ box->addWidget(my.timeFrame);
+ setLayout(box);
+
+ my.timeAxis = new TimeAxis(my.timeFrame);
+ my.timeAxis->setFixedHeight(timeAxisHeight());
+ my.gadgetLabel = new QLabel(my.timeFrame);
+ my.gadgetLabel->hide(); // shown with gadget Tabs
+
+ my.dateLabel = new QLabel(my.timeFrame);
+ my.dateLabel->setIndent(8);
+ my.dateLabel->setAlignment(Qt::AlignRight | Qt::AlignBottom);
+
+ my.labelSpacer = new QSpacerItem(10, 0,
+ QSizePolicy::Fixed, QSizePolicy::Minimum);
+ my.rightSpacer = new QSpacerItem(0, 0,
+ QSizePolicy::Fixed, QSizePolicy::Minimum);
+
+ my.valueLabel = new QLabel(my.timeFrame);
+ my.valueLabel->setIndent(8);
+ my.valueLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
+
+ my.grid = new QGridLayout; // Grid of [5 x 3] cells
+ my.grid->setMargin(0);
+ my.grid->setSpacing(0);
+ my.grid->addWidget(my.gadgetLabel, 0, 0, 1, 3);
+ my.grid->addWidget(my.timeAxis, 0, 0, 1, 3); // top two rows, all columns
+ my.grid->addWidget(my.dateLabel, 2, 2, 1, 1); // bottom row, last two cols
+ my.grid->addItem(my.labelSpacer, 2, 1, 1, 1); // bottom row, second column
+ my.grid->addWidget(my.valueLabel, 2, 0, 1, 1); // bottom row, first column.
+ my.grid->addItem(my.rightSpacer, 0, 4, 2, 1); // all rows, in final column
+ my.timeFrame->setLayout(my.grid);
+
+ resetFont();
+}
+
+void StatusBar::resetFont()
+{
+ setFont(*globalFont);
+ my.dateLabel->setFont(*globalFont);
+ my.valueLabel->setFont(*globalFont);
+ my.gadgetLabel->setFont(*globalFont);
+ my.timeAxis->resetFont();
+}
+
+void StatusBar::setTimeAxisRightAlignment(int width)
+{
+ my.rightSpacer->changeSize(width, buttonSize(),
+ QSizePolicy::Fixed, QSizePolicy::Fixed);
+ my.grid->invalidate();
+}
+
+void StatusBar::init()
+{
+ my.timeAxis->init();
+}
+
+bool StatusBar::event(QEvent *e)
+{
+ if (e->type() == QEvent::Show)
+ my.grid->update();
+ return QStatusBar::event(e);
+}
+
+void StatusBar::resizeEvent(QResizeEvent *e)
+{
+ my.timeFrame->resize(e->size().width()-1 - buttonSize(), buttonSize());
+}
+
+void StatusBar::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ QStyleOption opt(0);
+
+ opt.rect.setRect(buttonSize()+2, 0, width()-buttonSize()-2, buttonSize());
+ opt.palette = palette();
+ opt.state = QStyle::State_None;
+ style()->drawPrimitive(QStyle::PE_PanelButtonTool, &opt, &p, my.timeFrame);
+}
diff --git a/src/pmchart/statusbar.h b/src/pmchart/statusbar.h
new file mode 100644
index 0000000..85f3b3d
--- /dev/null
+++ b/src/pmchart/statusbar.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef STATUSBAR_H
+#define STATUSBAR_H
+
+#include <QtGui/QLabel>
+#include <QtGui/QStatusBar>
+#include <QtGui/QGridLayout>
+#include "qed_timebutton.h"
+#include "timeaxis.h"
+
+class StatusBar : public QStatusBar
+{
+ Q_OBJECT
+
+public:
+ StatusBar();
+ void init();
+ void resetFont();
+
+ static int buttonSize() { return 56; } // pixels
+ static int timeAxisHeight() { return 30; } // pixels
+
+ QLabel *dateLabel() { return my.dateLabel; }
+ TimeAxis *timeAxis() { return my.timeAxis; }
+ QToolButton *timeFrame() { return my.timeFrame; }
+ QedTimeButton *timeButton() { return my.timeButton; }
+
+ QString dateText() { return my.dateLabel->text(); }
+ void setDateText(QString &s) { my.dateLabel->setText(s); }
+ void setValueText(QString &s) { my.valueLabel->setText(s); }
+ void clearValueText() { my.valueLabel->clear(); }
+
+ void setTimeAxisRightAlignment(int w);
+
+protected:
+ bool event(QEvent *);
+ void paintEvent(QPaintEvent *);
+ void resizeEvent(QResizeEvent *);
+
+private:
+ struct {
+ QGridLayout *grid;
+ QSpacerItem *labelSpacer; // spacer between date/value labels
+ QSpacerItem *rightSpacer; // spacer at right edge for toolbar
+ QToolButton *timeFrame;
+ QedTimeButton *timeButton;
+ TimeAxis *timeAxis;
+ QLabel *gadgetLabel;
+ QLabel *valueLabel;
+ QLabel *dateLabel;
+ } my;
+};
+
+#endif
diff --git a/src/pmchart/tab.cpp b/src/pmchart/tab.cpp
new file mode 100644
index 0000000..3d5ef2c
--- /dev/null
+++ b/src/pmchart/tab.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include <QtGui/QMessageBox>
+#include "openviewdialog.h"
+#include "recorddialog.h"
+
+Tab::Tab(): QWidget(NULL)
+{
+ my.currentGadget = -1;
+ my.recording = false;
+ my.splitter = NULL;
+ my.group = NULL;
+}
+
+void Tab::init(QTabWidget *tab, GroupControl *group, QString label)
+{
+ my.splitter = new QSplitter(tab);
+ my.splitter->setOrientation(Qt::Vertical);
+ my.splitter->setSizePolicy(QSizePolicy::MinimumExpanding,
+ QSizePolicy::MinimumExpanding);
+ tab->blockSignals(true);
+ tab->addTab(my.splitter, label);
+ tab->blockSignals(false);
+ my.group = group;
+}
+
+void Tab::addGadget(Gadget *gadget)
+{
+ if (gadgetCount())
+ pmchart->updateHeight(PmChart::minimumChartHeight());
+ my.gadgetsList.append(gadget);
+ if (my.currentGadget == -1)
+ setCurrentGadget(gadgetCount() - 1);
+ gadget->showWidget();
+ console->post("Tab::addChart: [%d]->Chart %p", my.currentGadget, gadget);
+}
+
+int Tab::deleteCurrent(void)
+{
+ return deleteGadget(my.currentGadget);
+}
+
+int Tab::deleteGadget(Gadget *gadget)
+{
+ for (int i = 0; i < gadgetCount(); i++)
+ if (my.gadgetsList.at(i) == gadget)
+ return deleteGadget(i);
+ return 0;
+}
+
+int Tab::deleteGadget(int index)
+{
+ Gadget *gadget = my.gadgetsList.at(index);
+ int newCurrent = my.currentGadget;
+ int oldCurrent = my.currentGadget;
+ int oldCount = gadgetCount();
+ int height = gadget->height();
+
+ if (index < oldCurrent || index == oldCount - 1)
+ newCurrent--;
+ if (newCurrent < 0)
+ my.currentGadget = -1;
+ else
+ setCurrent(my.gadgetsList.at(newCurrent));
+
+ my.group->deleteGadget(gadget);
+ my.gadgetsList.removeAt(index);
+ delete gadget;
+
+ if (gadgetCount() > 0)
+ pmchart->updateHeight(-height);
+
+ return my.currentGadget;
+}
+
+int Tab::gadgetCount(void)
+{
+ return my.gadgetsList.size();
+}
+
+int Tab::currentGadgetIndex(void)
+{
+ return my.currentGadget;
+}
+
+Gadget *Tab::gadget(int index)
+{
+ if (index >= gadgetCount())
+ return NULL;
+ return my.gadgetsList.at(index);
+}
+
+Gadget *Tab::currentGadget(void)
+{
+ if (my.currentGadget == -1)
+ return NULL;
+ return my.gadgetsList.at(my.currentGadget);
+}
+
+void Tab::setCurrent(Gadget *gadget)
+{
+ int index;
+
+ for (index = 0; index < gadgetCount(); index++)
+ if (my.gadgetsList.at(index) == gadget)
+ break;
+ if (index == gadgetCount())
+ abort(); // bad, bad, bad - attempted setCurrent on invalid gadget
+ setCurrentGadget(index);
+}
+
+void Tab::setCurrentGadget(int index)
+{
+ if (index >= gadgetCount() || index == my.currentGadget)
+ return;
+ if (my.currentGadget >= 0)
+ my.gadgetsList.at(my.currentGadget)->setCurrent(false);
+ my.currentGadget = index;
+ if (my.currentGadget >= 0)
+ my.gadgetsList.at(my.currentGadget)->setCurrent(true);
+}
+
+void Tab::showGadgets()
+{
+ for (int index = 0; index < gadgetCount() - 1; index++)
+ my.gadgetsList.at(index)->showWidget();
+}
+
+bool Tab::isArchiveSource(void)
+{
+ return my.group->isArchiveSource();
+}
+
+bool Tab::isRecording(void)
+{
+ return my.recording;
+}
+
+void Tab::addFolio(QString folio, QString view)
+{
+ my.view = view;
+ my.folio = folio;
+}
+
+void Tab::addLogger(PmLogger *pmlogger, QString archive)
+{
+ my.loggerList.append(pmlogger);
+ my.archiveList.append(archive);
+}
+
+bool Tab::startRecording(void)
+{
+ RecordDialog record(this);
+
+ console->post("Tab::startRecording");
+ record.init(this);
+ if (record.exec() != QDialog::Accepted)
+ my.recording = false;
+ else { // write pmlogger/pmchart/pmafm configs and start up loggers.
+ console->post("Tab::startRecording starting loggers");
+ record.startLoggers();
+ my.recording = true;
+ }
+ return my.recording;
+}
+
+void Tab::stopRecording(void)
+{
+ QString msg = "Q\n", errmsg;
+ int count = my.loggerList.size();
+ int i, sts, error = 0;
+
+ console->post("Tab::stopRecording stopping %d logger(s)", count);
+ for (int i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(tr("Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error++;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ my.loggerList.at(i)->terminate();
+ }
+ }
+
+ for (i = 0; i < my.archiveList.size(); i++) {
+ QString archive = my.archiveList.at(i);
+
+ console->post("Tab::stopRecording opening archive %s",
+ (const char *)archive.toAscii());
+ if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ errmsg.append(tr("Cannot open PCP archive: "));
+ errmsg.append(archive);
+ errmsg.append("\n");
+ errmsg.append(tr(pmErrStr(sts)));
+ errmsg.append("\n");
+ error++;
+ }
+ else {
+ archiveGroup->updateBounds();
+ QmcSource source = archiveGroup->context()->source();
+ pmtime->addArchive(source.start(), source.end(),
+ source.timezone(), source.host(), true);
+ }
+ }
+
+ // If all is well, we can now create the new "Record" Tab.
+ // Order of cleanup and changing Record mode state is different
+ // in the error case to non-error case, this is important for
+ // getting the window state correct (i.e. pmchart->enableUi()).
+
+ if (error) {
+ cleanupRecording();
+ pmchart->setRecordState(false);
+ QMessageBox::warning(this, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ else {
+ // Make the current Tab stop recording before changing Tabs
+ pmchart->setRecordState(false);
+
+ Tab *tab = new Tab;
+ console->post("Tab::stopRecording creating tab: delta=%.2f pos=%.2f",
+ tosec(*pmtime->archiveInterval()),
+ tosec(*pmtime->archivePosition()));
+ // TODO: may need to update archive samples/visible?
+ tab->init(pmchart->tabWidget(), archiveGroup, "Record");
+ pmchart->addActiveTab(tab);
+ OpenViewDialog::openView((const char *)my.view.toAscii());
+ cleanupRecording();
+ }
+}
+
+void Tab::cleanupRecording(void)
+{
+ my.recording = false;
+ my.loggerList.clear();
+ my.archiveList.clear();
+ my.view = QString::null;
+ my.folio = QString::null;
+}
+
+void Tab::queryRecording(void)
+{
+ QString msg = "?\n", errmsg;
+ int i, error = 0, count = my.loggerList.size();
+
+ console->post("Tab::stopRecording querying %d logger(s)", count);
+ for (i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(tr("Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error++;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ }
+ }
+
+ if (error) {
+ msg = "Q\n"; // if one fails, we shut down all loggers
+ for (i = 0; i < count; i++)
+ my.loggerList.at(i)->write(msg.toAscii());
+ cleanupRecording();
+ pmchart->setRecordState(false);
+ QMessageBox::warning(this, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+}
+
+void Tab::detachLoggers(void)
+{
+ QString msg = "D\n", errmsg;
+ int error = 0, count = my.loggerList.size();
+
+ console->post("Tab::detachLoggers detaching %d logger(s)", count);
+ for (int i = 0; i < count; i++) {
+ if (my.loggerList.at(i)->state() == QProcess::NotRunning) {
+ errmsg.append(tr("Record process (pmlogger) failed for host: "));
+ errmsg.append(my.loggerList.at(i)->host());
+ errmsg.append("\n");
+ error++;
+ }
+ else {
+ my.loggerList.at(i)->write(msg.toAscii());
+ }
+ }
+
+ if (error) {
+ cleanupRecording();
+ pmchart->setRecordState(false);
+ QMessageBox::warning(this, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ else {
+ pmchart->setRecordState(false);
+ cleanupRecording();
+ }
+}
diff --git a/src/pmchart/tab.h b/src/pmchart/tab.h
new file mode 100644
index 0000000..a9bc0f5
--- /dev/null
+++ b/src/pmchart/tab.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2006-2008, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TAB_H
+#define TAB_H
+
+#include <QtCore/QList>
+#include <QtGui/QSplitter>
+#include <QtGui/QTabWidget>
+#include "groupcontrol.h"
+#include "gadget.h"
+
+class PmLogger;
+
+class Tab : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Tab();
+ void init(QTabWidget *, GroupControl *, QString);
+ QWidget *splitter() { return my.splitter; }
+ GroupControl *group() { return my.group; }
+ bool isArchiveSource(); // query if tab is for archives
+
+ void addGadget(Gadget *); // append gadget to the Tab, make it current
+ int deleteCurrent(); // remove current gadget, return new current
+ int deleteGadget(Gadget *); // remove given gadget, return current index
+ int deleteGadget(int); // remove 'N'th gadget, return current index
+
+ int gadgetCount(); // count of entries in the list of gadgets
+ Gadget *gadget(int); // gadget at specified list position
+ Gadget *currentGadget(); // current gadget (can be NULL)
+ void setCurrent(Gadget *);
+ int currentGadgetIndex(); // current gadget index (can be -1)
+ void setCurrentGadget(int);
+
+ void showGadgets();
+
+ void addFolio(QString, QString);
+ void addLogger(PmLogger *, QString);
+
+ bool isRecording();
+ bool startRecording();
+ void queryRecording();
+ void stopRecording();
+ void detachLoggers();
+
+private:
+ void cleanupRecording();
+
+ struct {
+ QSplitter *splitter; // dynamically divides charts
+
+ GroupControl *group;
+ QList<Gadget*> gadgetsList;
+ int currentGadget;
+
+ bool recording; // running any pmlogger's?
+ QString view;
+ QString folio;
+ QList<QString> archiveList; // list of archive names
+ QList<PmLogger*> loggerList; // list of pmloggers for our Tab
+ } my;
+};
+
+#endif // TAB_H
diff --git a/src/pmchart/tabdialog.cpp b/src/pmchart/tabdialog.cpp
new file mode 100644
index 0000000..9da0c0d
--- /dev/null
+++ b/src/pmchart/tabdialog.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "tabdialog.h"
+#include "main.h"
+
+TabDialog::TabDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+}
+
+void TabDialog::languageChange()
+{
+ retranslateUi(this);
+}
+
+void TabDialog::reset(QString label, bool live)
+{
+ if (label == QString::null) {
+ setWindowTitle(tr("Add Tab"));
+ labelLineEdit->setText(live ? tr("Live") : tr("Archive"));
+ }
+ else {
+ setWindowTitle(tr("Edit Tab"));
+ liveHostRadioButton->setEnabled(false);
+ archivesRadioButton->setEnabled(false);
+ labelLineEdit->setText(label);
+ }
+
+ liveHostRadioButton->setChecked(live);
+ archivesRadioButton->setChecked(!live);
+
+ my.archiveSource = !live;
+
+ console->post(PmChart::DebugUi, "TabDialog::reset arch=%s",
+ my.archiveSource ? "true" : "false");
+}
+
+bool TabDialog::isArchiveSource()
+{
+ return my.archiveSource;
+}
+
+QString TabDialog::label() const
+{
+ return labelLineEdit->text();
+}
+
+void TabDialog::liveHostRadioButtonClicked()
+{
+ if (labelLineEdit->text() == tr("Archive"))
+ labelLineEdit->setText(tr("Live"));
+ liveHostRadioButton->setChecked(true);
+ archivesRadioButton->setChecked(false);
+ my.archiveSource = false;
+ console->post(PmChart::DebugUi,
+ "TabDialog::liveHostRadioButtonClicked archive=%s",
+ my.archiveSource ? "true" : "false");
+}
+
+void TabDialog::archivesRadioButtonClicked()
+{
+ if (labelLineEdit->text() == tr("Live"))
+ labelLineEdit->setText(tr("Archive"));
+ liveHostRadioButton->setChecked(false);
+ archivesRadioButton->setChecked(true);
+ my.archiveSource = true;
+ console->post(PmChart::DebugUi,
+ "TabDialog::archivesRadioButtonClicked archive=%s",
+ my.archiveSource ? "true" : "false");
+}
diff --git a/src/pmchart/tabdialog.h b/src/pmchart/tabdialog.h
new file mode 100644
index 0000000..7dfc6b3
--- /dev/null
+++ b/src/pmchart/tabdialog.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2006-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TABDIALOG_H
+#define TABDIALOG_H
+
+#include "ui_tabdialog.h"
+
+class TabDialog : public QDialog, public Ui::TabDialog
+{
+ Q_OBJECT
+
+public:
+ TabDialog(QWidget* parent);
+
+ void reset(QString, bool);
+ bool isArchiveSource();
+ QString label() const;
+
+public slots:
+ void liveHostRadioButtonClicked();
+ void archivesRadioButtonClicked();
+
+protected slots:
+ void languageChange();
+
+private:
+ struct {
+ bool archiveSource;
+ } my;
+};
+
+#endif // TABDIALOG_H
diff --git a/src/pmchart/tabdialog.ui b/src/pmchart/tabdialog.ui
new file mode 100644
index 0000000..f1c64b9
--- /dev/null
+++ b/src/pmchart/tabdialog.ui
@@ -0,0 +1,321 @@
+<ui version="4.0" >
+ <class>TabDialog</class>
+ <widget class="QDialog" name="TabDialog" >
+ <property name="windowModality" >
+ <enum>Qt::WindowModal</enum>
+ </property>
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>340</width>
+ <height>150</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>340</width>
+ <height>150</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>350</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="focusPolicy" >
+ <enum>Qt::WheelFocus</enum>
+ </property>
+ <property name="windowTitle" >
+ <string>Add Tab</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmchart.qrc" >:/images/tab-edit.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout" >
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="tabSourceLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>Source:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="liveHostRadioButton" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>Live</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/computer.png</normaloff>:/images/computer.png</iconset>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="archivesRadioButton" >
+ <property name="text" >
+ <string>Archives</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmchart.qrc" >
+ <normaloff>:/images/archive.png</normaloff>:/images/archive.png</iconset>
+ </property>
+ <property name="autoExclusive" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>34</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="tabTextLabel" >
+ <property name="text" >
+ <string>Label:</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="labelLineEdit" />
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" >
+ <spacer name="verticalSpacer" >
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>5</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOk" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmchart.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOk</sender>
+ <signal>clicked()</signal>
+ <receiver>TabDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>TabDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>liveHostRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>TabDialog</receiver>
+ <slot>liveHostRadioButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>archivesRadioButton</sender>
+ <signal>clicked()</signal>
+ <receiver>TabDialog</receiver>
+ <slot>archivesRadioButtonClicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmchart/tabwidget.cpp b/src/pmchart/tabwidget.cpp
new file mode 100644
index 0000000..bc1ede6
--- /dev/null
+++ b/src/pmchart/tabwidget.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "tabwidget.h"
+#include "tab.h"
+#include "main.h"
+
+TabWidget::TabWidget(QWidget *parent) : QTabWidget(parent)
+{
+ setFont(*globalFont);
+ my.activeTab = NULL;
+}
+
+bool TabWidget::setActiveTab(Tab *tab)
+{
+ my.activeTab = tab;
+ return tab->isArchiveSource();
+}
+
+bool TabWidget::setActiveTab(int index)
+{
+ my.activeTab = my.tabs.at(index);
+ return my.activeTab->isArchiveSource();
+}
+
+void TabWidget::insertTab(Tab *tab)
+{
+ my.tabs.append(tab);
+ tabBar()->setVisible(my.tabs.size() > 1);
+}
+
+void TabWidget::removeTab(int index)
+{
+ my.tabs.removeAt(index);
+ tabBar()->setVisible(my.tabs.size() > 1);
+ QTabWidget::removeTab(index);
+}
diff --git a/src/pmchart/tabwidget.h b/src/pmchart/tabwidget.h
new file mode 100644
index 0000000..42aa6c3
--- /dev/null
+++ b/src/pmchart/tabwidget.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TABWIDGET_H
+#define TABWIDGET_H
+
+#include <QtCore/QList>
+#include <QtGui/QTabBar>
+#include <QtGui/QTabWidget>
+
+class Tab;
+
+class TabWidget : public QTabWidget
+{
+ Q_OBJECT
+
+public:
+ TabWidget(QWidget *parent);
+
+ int size() { return my.tabs.size(); }
+ Tab *at(int i) { return my.tabs.at(i); }
+ Tab *activeTab() { return my.activeTab; }
+
+ bool setActiveTab(Tab *);
+ bool setActiveTab(int);
+ void insertTab(Tab *);
+ void removeTab(int);
+
+private:
+ struct {
+ Tab *activeTab;
+ QList<Tab*> tabs;
+ } my;
+};
+
+#endif // TABWIDGET_H
diff --git a/src/pmchart/timeaxis.cpp b/src/pmchart/timeaxis.cpp
new file mode 100644
index 0000000..d0b8a89
--- /dev/null
+++ b/src/pmchart/timeaxis.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2007-2008, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QEvent>
+#include <QtCore/QDateTime>
+#include <QtGui/QResizeEvent>
+#include <qwt_plot_renderer.h>
+#include <qwt_scale_draw.h>
+#include <qwt_scale_widget.h>
+#include <qwt_text.h>
+#include <qwt_text_label.h>
+#include "timeaxis.h"
+#include "main.h"
+
+#define DESPERATE 0
+
+class TimeScaleDraw: public QwtScaleDraw
+{
+public:
+ TimeScaleDraw(void) :QwtScaleDraw() { }
+ virtual QwtText label(double v) const
+ {
+ struct tm tm;
+ QString string;
+ time_t seconds = (time_t)v;
+
+ pmLocaltime(&seconds, &tm);
+ string.sprintf("%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
+ return string;
+ }
+ virtual void getBorderDistHint(const QFont &f, int &start, int &end) const
+ {
+ if (orientation() == Qt::Horizontal) {
+ start = 0;
+ end = 0;
+ }
+ else {
+ QwtScaleDraw::getBorderDistHint(f, start, end);
+ }
+ }
+};
+
+TimeAxis::TimeAxis(QWidget *parent) : QwtPlot(parent)
+{
+ clearScaleCache();
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void TimeAxis::init()
+{
+ enableAxis(xTop, false);
+ enableAxis(yLeft, false);
+ enableAxis(yRight, false);
+ enableAxis(xBottom, true);
+
+ setAutoReplot(false);
+ setAxisFont(QwtPlot::xBottom, *globalFont);
+ setAxisScaleDraw(QwtPlot::xBottom, new TimeScaleDraw());
+ setAxisLabelAlignment(QwtPlot::xBottom, Qt::AlignHCenter | Qt::AlignBottom);
+}
+
+void TimeAxis::resetFont()
+{
+ setAxisFont(QwtPlot::xBottom, *globalFont);
+}
+
+void TimeAxis::print(QPainter *qp, QRect &rect, bool transparent)
+{
+ QwtPlotRenderer renderer;
+
+ if (transparent)
+ renderer.setDiscardFlag(QwtPlotRenderer::DiscardBackground);
+ renderer.render(this, qp, rect);
+}
+
+void TimeAxis::noArchiveSources()
+{
+ setAxisScale(QwtPlot::xBottom, 0, 1, 0);
+ replot();
+}
+
+void TimeAxis::clearScaleCache()
+{
+ my.points = 0;
+ my.delta = my.scale = 0.0;
+}
+
+double TimeAxis::scaleValue(double delta, int points)
+{
+ if (my.delta == delta && my.points == points)
+ return my.scale;
+
+ // divisor is the amount of space (pixels) set aside for one major label.
+ int maxMajor = qMax(1, width() / 54);
+ int maxMinor;
+
+ my.scale = (1.0 / ((double)width() / (points * 8.0))) * 8.0; // 8.0 is magic
+ my.scale *= delta;
+
+ // This is a sliding scale which converts arbitrary steps into more
+ // human-digestable increments - seconds, ten seconds, minutes, etc.
+ if (my.scale <= 10.0) {
+ maxMinor = 10;
+ } else if (my.scale <= 20.0) { // two-seconds up to 20 seconds
+ my.scale = floor((my.scale + 1) / 2) * 2.0;
+ maxMinor = 10;
+ } else if (my.scale <= 60.0) { // ten-secondly up to a minute
+ my.scale = floor((my.scale + 5) / 10) * 10.0;
+ maxMinor = 10;
+ } else if (my.scale <= 600.0) { // minutely up to ten minutes
+ my.scale = floor((my.scale + 30) / 60) * 60.0;
+ maxMinor = 10;
+ } else if (my.scale < 3600.0) { // 10 minutely up to an hour
+ my.scale = floor((my.scale + 300) / 600) * 600.0;
+ maxMinor = 6;
+ } else if (my.scale < 86400.0) { // hourly up to a day
+ my.scale = floor((my.scale + 1800) / 3600) * 3600.0;
+ maxMinor = 24;
+ } else { // daily then on (60 * 60 * 24)
+ my.scale = 86400.0;
+ maxMinor = 10;
+ }
+
+#if DESPERATE
+ console->post(PmChart::DebugForce, "TimeAxis::scaleValue"
+ " width=%d points=%d scale=%.2f delta=%.2f maj=%d min=%d\n",
+ width(), points, my.scale, delta, maxMajor, maxMinor);
+#endif
+
+ my.delta = delta;
+ my.points = points;
+ setAxisMaxMajor(xBottom, maxMajor);
+ setAxisMaxMinor(xBottom, maxMinor);
+ return my.scale;
+}
+
+//
+// Update the time axis if width changes, idea is to display increased
+// precision as more screen real estate becomes available. scaleValue
+// is the critical piece of code in implementing it, as it uses width().
+//
+void TimeAxis::resizeEvent(QResizeEvent *e)
+{
+ QwtPlot::resizeEvent(e);
+ if (e->size().width() != e->oldSize().width()) {
+ clearScaleCache();
+ activeGroup->updateTimeAxis();
+ }
+}
diff --git a/src/pmchart/timeaxis.h b/src/pmchart/timeaxis.h
new file mode 100644
index 0000000..323d626
--- /dev/null
+++ b/src/pmchart/timeaxis.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TIMEAXIS_H
+#define TIMEAXIS_H
+
+#include <qwt_plot_canvas.h>
+#include <qwt_plot_layout.h>
+#include <qwt_plot.h>
+#include <QResizeEvent>
+
+class Tab;
+
+class TimeAxis : public QwtPlot
+{
+ Q_OBJECT
+public:
+ TimeAxis(QWidget *);
+
+ void init();
+ void resetFont();
+ void clearScaleCache();
+ double scaleValue(double delta, int count);
+ void noArchiveSources();
+ void print(QPainter *, QRect &, bool);
+
+protected:
+ void resizeEvent(QResizeEvent *);
+
+private:
+ struct {
+ int points;
+ double delta;
+ double scale;
+ } my;
+};
+
+#endif /* TIMEAXIS_H */
diff --git a/src/pmchart/timecontrol.cpp b/src/pmchart/timecontrol.cpp
new file mode 100644
index 0000000..e72bcd0
--- /dev/null
+++ b/src/pmchart/timecontrol.cpp
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include <qmc_time.h>
+#include <QtGui/QMessageBox>
+#include <QtGui/QApplication>
+#include <QtNetwork/QHostAddress>
+
+TimeControl::TimeControl() : QProcess(NULL)
+{
+ my.tcpPort = -1;
+ my.tzLength = 0;
+ my.tzData = NULL;
+ my.bufferLength = sizeof(QmcTime::Packet);
+
+ my.buffer = (char *)malloc(my.bufferLength);
+ my.livePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ my.archivePacket = (QmcTime::Packet *)malloc(sizeof(QmcTime::Packet));
+ if (!my.buffer || !my.livePacket || !my.archivePacket)
+ nomem();
+ my.livePacket->magic = QmcTime::Magic;
+ my.livePacket->source = QmcTime::HostSource;
+ my.liveState = TimeControl::Disconnected;
+ my.liveSocket = new QTcpSocket(this);
+ connect(my.liveSocket, SIGNAL(connected()),
+ SLOT(liveSocketConnected()));
+ connect(my.liveSocket, SIGNAL(disconnected()),
+ SLOT(liveCloseConnection()));
+ connect(my.liveSocket, SIGNAL(readyRead()),
+ SLOT(liveProtocolMessage()));
+
+ my.archivePacket->magic = QmcTime::Magic;
+ my.archivePacket->source = QmcTime::ArchiveSource;
+ my.archiveState = TimeControl::Disconnected;
+ my.archiveSocket = new QTcpSocket(this);
+ connect(my.archiveSocket, SIGNAL(connected()),
+ SLOT(archiveSocketConnected()));
+ connect(my.archiveSocket, SIGNAL(disconnected()),
+ SLOT(archiveCloseConnection()));
+ connect(my.archiveSocket, SIGNAL(readyRead()),
+ SLOT(archiveProtocolMessage()));
+}
+
+void TimeControl::quit()
+{
+ disconnect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ if (my.liveSocket)
+ disconnect(my.liveSocket, SIGNAL(disconnected()), this,
+ SLOT(liveCloseConnection()));
+ if (my.archiveSocket)
+ disconnect(my.archiveSocket, SIGNAL(disconnected()), this,
+ SLOT(archiveCloseConnection()));
+ if (my.liveSocket) {
+ my.liveSocket->close();
+ my.liveSocket = NULL;
+ }
+ if (my.archiveSocket) {
+ my.archiveSocket->close();
+ my.archiveSocket = NULL;
+ }
+ terminate();
+}
+
+void TimeControl::init(int port, bool live,
+ struct timeval *interval, struct timeval *position,
+ struct timeval *starttime, struct timeval *endtime,
+ QString tzstring, QString tzlabel)
+{
+ struct timeval now;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+
+ my.tzLength = tzlen+1 + lablen+1;
+ my.tzData = (char *)realloc(my.tzData, my.tzLength);
+ if (!my.tzData)
+ nomem();
+
+ my.livePacket->length = my.archivePacket->length =
+ sizeof(QmcTime::Packet) + my.tzLength;
+ my.livePacket->command = my.archivePacket->command = QmcTime::Set;
+ my.livePacket->delta = my.archivePacket->delta = *interval;
+ if (live) {
+ my.livePacket->position = *position;
+ my.livePacket->start = *starttime;
+ my.livePacket->end = *endtime;
+ memset(&my.archivePacket->position, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->start, 0, sizeof(struct timeval));
+ memset(&my.archivePacket->end, 0, sizeof(struct timeval));
+ } else {
+ __pmtimevalNow(&now);
+ my.archivePacket->position = *position;
+ my.archivePacket->start = *starttime;
+ my.archivePacket->end = *endtime;
+ my.livePacket->position = now;
+ my.livePacket->start = now;
+ my.livePacket->end = now;
+ }
+ strncpy(my.tzData, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy(my.tzData + tzlen+1, (const char *)tzlabel.toAscii(), lablen+1);
+
+ if (port <= 0) {
+ startTimeServer();
+ } else {
+ my.tcpPort = port;
+ liveConnect();
+ archiveConnect();
+ }
+}
+
+void TimeControl::addArchive(
+ struct timeval starttime, struct timeval endtime,
+ QString tzstring, QString tzlabel, bool atEnd)
+{
+ QmcTime::Packet *message;
+ int tzlen = tzstring.length(), lablen = tzlabel.length();
+ int sz = sizeof(QmcTime::Packet) + tzlen + 1 + lablen + 1;
+
+ if (my.archivePacket->position.tv_sec == 0) { // first archive
+ my.archivePacket->position = atEnd ? endtime : starttime;
+ my.archivePacket->start = starttime;
+ my.archivePacket->end = endtime;
+ }
+
+ if ((message = (QmcTime::Packet *)malloc(sz)) == NULL)
+ nomem();
+ *message = *my.archivePacket;
+ message->command = QmcTime::Bounds;
+ message->length = sz;
+ message->start = starttime;
+ message->end = endtime;
+ strncpy((char *)message->data, (const char *)tzstring.toAscii(), tzlen+1);
+ strncpy((char *)message->data + tzlen+1,
+ (const char *)tzlabel.toAscii(), lablen+1);
+ if (my.archiveSocket->write((const char *)message, sz) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot update pmtime boundaries."),
+ QApplication::tr("Quit") );
+ free(message);
+}
+
+void TimeControl::liveConnect()
+{
+ console->post("Connecting to pmtime, live source");
+ my.liveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void TimeControl::archiveConnect()
+{
+ console->post("Connecting to pmtime, archive source");
+ my.archiveSocket->connectToHost(QHostAddress::LocalHost, my.tcpPort);
+}
+
+void TimeControl::showLiveTimeControl(void)
+{
+ my.livePacket->command = QmcTime::GUIShow;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::showArchiveTimeControl(void)
+{
+ my.archivePacket->command = QmcTime::GUIShow;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to show itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::hideLiveTimeControl()
+{
+ my.livePacket->command = QmcTime::GUIHide;
+ my.livePacket->length = sizeof(QmcTime::Packet);
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::hideArchiveTimeControl()
+{
+ my.archivePacket->command = QmcTime::GUIHide;
+ my.archivePacket->length = sizeof(QmcTime::Packet);
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0)
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Cannot get pmtime to hide itself."),
+ QApplication::tr("Quit") );
+}
+
+void TimeControl::endTimeControl(void)
+{
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Time Control process pmtime has exited."),
+ QApplication::tr("Quit") );
+ exit(-1);
+}
+
+void TimeControl::liveCloseConnection()
+{
+ console->post("Disconnected from pmtime, live source");
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Connection to pmtime lost (live mode)."),
+ QApplication::tr("Quit") );
+ quit();
+}
+
+void TimeControl::archiveCloseConnection()
+{
+ console->post("Disconnected from pmtime, archive source");
+ QMessageBox::warning(0,
+ QApplication::tr("Error"),
+ QApplication::tr("Connection to pmtime lost (archive mode)."),
+ QApplication::tr("Quit") );
+ quit();
+}
+
+void TimeControl::liveSocketConnected()
+{
+ if (my.liveSocket->write((const char *)my.livePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in live pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.liveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed to send timezone in live pmtime negotiation."),
+ QApplication::tr("Quit"));
+ exit(1);
+ }
+ my.liveState = TimeControl::AwaitingACK;
+}
+
+void TimeControl::archiveSocketConnected()
+{
+ if (my.archiveSocket->write((const char *)my.archivePacket,
+ sizeof(QmcTime::Packet)) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed socket write in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (my.archiveSocket->write((const char *)my.tzData, my.tzLength) < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr(
+ "Failed timezone send in archive pmtime negotiation."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ my.archiveState = TimeControl::AwaitingACK;
+}
+
+//
+// Start a shiny new pmtime process.
+// The one process serves time for all (live and archive) tabs.
+// We do have to specify which form will be used first, however.
+//
+void TimeControl::startTimeServer()
+{
+ QStringList arguments;
+
+ if (pmDebug & DBG_TRACE_TIMECONTROL)
+ arguments << "-D" << "all";
+ connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this,
+ SLOT(endTimeControl()));
+ connect(this, SIGNAL(readyReadStandardOutput()), this,
+ SLOT(readPortFromStdout()));
+ start("pmtime", arguments);
+}
+
+//
+// When pmtime starts in "port probe" mode, port# is written to
+// stdout. We can only complete negotiation once we have that...
+//
+void TimeControl::readPortFromStdout(void)
+{
+ bool ok;
+ QString data = readAllStandardOutput();
+
+ my.tcpPort = data.remove("port=").toInt(&ok, 10);
+ if (!ok) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad port number from pmtime program."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+
+ liveConnect();
+ archiveConnect();
+}
+
+void TimeControl::protocolMessage(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state)
+{
+ int sts, need = sizeof(QmcTime::Packet), offset = 0;
+ QmcTime::Packet *msg;
+
+ // Read one pmtime packet, handling both small reads and large packets
+ for (;;) {
+ sts = socket->read(my.buffer + offset, need);
+ if (sts < 0) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed socket read in pmtime transfer."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ else if (sts != need) {
+ need -= sts;
+ offset += sts;
+ continue;
+ }
+
+ msg = (QmcTime::Packet *)my.buffer;
+ if (msg->magic != QmcTime::Magic) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Bad client message magic number."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->length > my.bufferLength) {
+ my.bufferLength = msg->length;
+ my.buffer = (char *)realloc(my.buffer, my.bufferLength);
+ if (!my.buffer)
+ nomem();
+ msg = (QmcTime::Packet *)my.buffer;
+ }
+ if (msg->length > (uint)offset + sts) {
+ offset += sts;
+ need = msg->length - offset;
+ continue;
+ }
+ break;
+ }
+
+#if DESPERATE
+ console->post(PmChart::DebugProtocol,
+ "TimeControl::protocolMessage: recv pos=%s state=%d",
+ timeString(tosec(packet->position)), *state);
+#endif
+
+ switch (*state) {
+ case TimeControl::AwaitingACK:
+ if (msg->command != QmcTime::ACK) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Initial ACK not received from pmtime."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ if (msg->source != packet->source) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("pmtime not serving same metric source."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ *state = TimeControl::ClientReady;
+ if (msg->length > packet->length) {
+ packet = (QmcTime::Packet *)realloc(packet, msg->length);
+ if (!packet)
+ nomem();
+ }
+ //
+ // Note: we drive the local state from the time control values,
+ // and _not_ from the values that we initially sent to it.
+ //
+ memcpy(packet, msg, msg->length);
+ pmchart->VCRMode(live, msg, true);
+ break;
+
+ case TimeControl::ClientReady:
+ if (msg->command == QmcTime::Step) {
+ pmchart->step(live, msg);
+ msg->command = QmcTime::ACK;
+ msg->length = sizeof(QmcTime::Packet);
+ sts = socket->write((const char *)msg, msg->length);
+ if (sts < 0 || sts != (int)msg->length) {
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Failed pmtime write for STEP ACK."),
+ QApplication::tr("Quit") );
+ exit(1);
+ }
+ } else if (msg->command == QmcTime::VCRMode ||
+ msg->command == QmcTime::VCRModeDrag ||
+ msg->command == QmcTime::Bounds) {
+ pmchart->VCRMode(live, msg, msg->command == QmcTime::VCRModeDrag);
+ } else if (msg->command == QmcTime::TZ) {
+ pmchart->timeZone(live, msg, (char *)msg->data);
+ }
+ break;
+
+ default:
+ QMessageBox::critical(0,
+ QApplication::tr("Fatal error"),
+ QApplication::tr("Protocol error with pmtime."),
+ QApplication::tr("Quit") );
+ // fall through
+ case TimeControl::Disconnected:
+ exit(1);
+ }
+}
+
+void TimeControl::protocolMessageLoop(bool live,
+ QmcTime::Packet *packet, QTcpSocket *socket, ProtocolState *state)
+{
+ do {
+ protocolMessage(live, packet, socket, state);
+ } while (socket->bytesAvailable() >= (int)sizeof(QmcTime::Packet));
+}
diff --git a/src/pmchart/timecontrol.h b/src/pmchart/timecontrol.h
new file mode 100644
index 0000000..2884bc7
--- /dev/null
+++ b/src/pmchart/timecontrol.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TIMECONTROL_H
+#define TIMECONTROL_H
+
+#include <QtCore/QString>
+#include <QtCore/QProcess>
+#include <QtNetwork/QTcpSocket>
+#include <qmc_time.h>
+
+class TimeControl : public QProcess
+{
+ Q_OBJECT
+
+public:
+ TimeControl();
+
+ void init(int port, bool livemode,
+ struct timeval *interval, struct timeval *position,
+ struct timeval *starttime, struct timeval *endtime,
+ QString tzstring, QString tzlabel);
+ void quit();
+
+ void addArchive(struct timeval starttime, struct timeval endtime,
+ QString tzstring, QString tzlabel, bool atEnd);
+
+ void liveConnect();
+ void archiveConnect();
+
+ int port() { return my.tcpPort; }
+ struct timeval *liveInterval() { return &my.livePacket->delta; }
+ struct timeval *livePosition() { return &my.livePacket->position; }
+ struct timeval *archiveInterval() { return &my.archivePacket->delta; }
+ struct timeval *archivePosition() { return &my.archivePacket->position; }
+ void setArchivePosition(struct timeval *pos) { my.archivePacket->position = *pos; }
+ void setArchiveInterval(struct timeval *delta) { my.archivePacket->delta = *delta; }
+ struct timeval *archiveStart() { return &my.archivePacket->start; }
+ struct timeval *archiveEnd() { return &my.archivePacket->end; }
+
+public slots:
+ void showLiveTimeControl();
+ void hideLiveTimeControl();
+ void showArchiveTimeControl();
+ void hideArchiveTimeControl();
+ void endTimeControl();
+
+ void readPortFromStdout();
+
+ void liveCloseConnection();
+ void liveSocketConnected();
+ void liveProtocolMessage()
+ {
+ protocolMessageLoop(true, my.livePacket, my.liveSocket, &my.liveState);
+ }
+
+ void archiveCloseConnection();
+ void archiveSocketConnected();
+ void archiveProtocolMessage()
+ {
+ protocolMessageLoop(false, my.archivePacket, my.archiveSocket,
+ &my.archiveState);
+ }
+
+private:
+ typedef enum {
+ Disconnected = 1,
+ AwaitingACK = 2,
+ ClientReady = 3,
+ } ProtocolState;
+
+ void startTimeServer();
+ void protocolMessage(bool live, QmcTime::Packet *pmtime,
+ QTcpSocket *socket, ProtocolState *state);
+ void protocolMessageLoop(bool live, QmcTime::Packet *pmtime,
+ QTcpSocket *socket, ProtocolState *state);
+
+ struct {
+ int tcpPort;
+ int tzLength;
+ char *tzData;
+
+ unsigned int bufferLength;
+ char *buffer;
+
+ QTcpSocket *liveSocket;
+ QmcTime::Packet *livePacket;
+ ProtocolState liveState;
+
+ QTcpSocket *archiveSocket;
+ QmcTime::Packet *archivePacket;
+ ProtocolState archiveState;
+ } my;
+};
+
+#endif // TIMECONTROL_H
diff --git a/src/pmchart/tracing.cpp b/src/pmchart/tracing.cpp
new file mode 100644
index 0000000..feb0dac
--- /dev/null
+++ b/src/pmchart/tracing.cpp
@@ -0,0 +1,720 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <qwt_plot_directpainter.h>
+#include <qwt_picker_machine.h>
+#include <qwt_plot_marker.h>
+#include <qwt_symbol.h>
+#include "tracing.h"
+#include "main.h"
+
+#define DESPERATE 0
+
+TracingItem::TracingItem(Chart *chart,
+ QmcMetric *mp, pmMetricSpec *msp, pmDesc *dp, const QString &legend)
+ : ChartItem(mp, msp, dp, legend)
+{
+ my.chart = chart;
+ my.minSpanID = 0;
+ my.maxSpanID = 1;
+ my.minSpanTime = tosec(mp->context()->source().start());
+ if (mp->context()->source().isArchive())
+ my.maxSpanTime = tosec(mp->context()->source().end());
+ else
+ my.maxSpanTime = my.minSpanTime * 1.1;
+
+ my.spanSymbol = new QwtIntervalSymbol(QwtIntervalSymbol::Box);
+ my.spanCurve = new QwtPlotIntervalCurve(label());
+ my.spanCurve->setItemAttribute(QwtPlotItem::Legend, false);
+ my.spanCurve->setStyle(QwtPlotIntervalCurve::NoCurve);
+ my.spanCurve->setOrientation(Qt::Horizontal);
+ my.spanCurve->setSymbol(my.spanSymbol);
+ my.spanCurve->setZ(1); // lowest/furthest
+
+ my.dropSymbol = new QwtIntervalSymbol(QwtIntervalSymbol::Box);
+ my.dropCurve = new QwtPlotIntervalCurve(label());
+ my.dropCurve->setItemAttribute(QwtPlotItem::Legend, false);
+ my.dropCurve->setStyle(QwtPlotIntervalCurve::NoCurve);
+ my.dropCurve->setOrientation(Qt::Vertical);
+ my.dropCurve->setSymbol(my.dropSymbol);
+ my.dropCurve->setZ(2); // middle/central
+
+ my.pointSymbol = new QwtSymbol(QwtSymbol::Ellipse);
+ my.pointCurve = new ChartCurve(label());
+ my.pointCurve->setStyle(QwtPlotCurve::NoCurve);
+ my.pointCurve->setSymbol(my.pointSymbol);
+ my.pointCurve->setZ(3); // higher/closer
+
+ my.selectionSymbol = new QwtSymbol(QwtSymbol::Ellipse);
+ my.selectionCurve = new QwtPlotCurve(label());
+ my.selectionCurve->setItemAttribute(QwtPlotItem::Legend, false);
+ my.selectionCurve->setStyle(QwtPlotCurve::NoCurve);
+ my.selectionCurve->setSymbol(my.selectionSymbol);
+ my.selectionCurve->setZ(4); // highest/closest
+
+ my.spanCurve->attach(chart);
+ my.dropCurve->attach(chart);
+ my.pointCurve->attach(chart);
+ my.selectionCurve->attach(chart);
+}
+
+TracingItem::~TracingItem(void)
+{
+ delete my.spanCurve;
+ delete my.spanSymbol;
+ delete my.dropCurve;
+ delete my.dropSymbol;
+ delete my.pointCurve;
+ delete my.pointSymbol;
+ delete my.selectionCurve;
+ delete my.selectionSymbol;
+}
+
+QwtPlotItem *
+TracingItem::item(void)
+{
+ return my.pointCurve;
+}
+
+QwtPlotCurve *
+TracingItem::curve(void)
+{
+ return my.pointCurve;
+}
+
+TracingEvent::TracingEvent(QmcEventRecord const &record, pmID pmid, int inst)
+{
+ my.timestamp = tosec(*record.timestamp());
+ my.missed = record.missed();
+ my.flags = record.flags();
+ my.pmid = pmid;
+ my.inst = inst;
+ my.spanID = record.identifier();
+ my.rootID = record.parent();
+
+ // details displayed about this record (on selection)
+ my.description.append(timeHiResString(my.timestamp));
+ my.description.append(": flags=");
+ my.description.append(pmEventFlagsStr(record.flags()));
+ if (record.missed()> 0) {
+ my.description.append(" (");
+ my.description.append(QString::number(record.missed()));
+ my.description.append(" missed)");
+ }
+ my.description.append("\n");
+ record.parameterSummary(my.description, inst);
+}
+
+TracingEvent::~TracingEvent()
+{
+ my.timestamp = 0.0;
+}
+
+void
+TracingItem::rescaleValues(double *minValue, double *maxValue)
+{
+ if (my.minSpanID < *minValue)
+ *minValue = my.minSpanID;
+ if (my.maxSpanID > *maxValue)
+ *maxValue = my.maxSpanID;
+}
+
+//
+// Walk the vectors/lists and drop no-longer-needed events.
+// For points/events/drops (single time value):
+// Events arrive time-ordered, so we can short-circuit these
+// walks once we are within the time window. Two phases -
+// walk once from the left, then a subsequent walk from the
+// right (note: done *after* we modify the original vector)
+// For horizonal spans (i.e. two time values):
+// This is still true - events arrive in order - but we have
+// to walk the entire list as these ranges can overlap. But
+// thats OK - we expect far fewer spans than total events.
+//
+
+void
+TracingItem::cullOutlyingSpans(double left, double right)
+{
+ // Start from the end so that we can remove as we go
+ // without interfering with the index we are using.
+ for (int i = my.spans.size() - 1; i >= 0; i--) {
+ const QwtIntervalSample &span = my.spans.at(i);
+ if (span.interval.maxValue() >= left ||
+ span.interval.minValue() <= right)
+ continue;
+ my.spans.remove(i);
+ }
+}
+
+void
+TracingItem::cullOutlyingDrops(double left, double right)
+{
+ int i, cull;
+
+ for (i = cull = 0; i < my.drops.size(); i++, cull++)
+ if (my.drops.at(i).value >= left)
+ break;
+ if (cull)
+ my.drops.remove(0, cull); // cull from the start (0-index)
+ for (i = my.drops.size() - 1, cull = 0; i >= 0; i--, cull++)
+ if (my.drops.at(i).value <= right)
+ break;
+ if (cull)
+ my.drops.remove(my.drops.size() - cull, cull); // cull from end
+}
+
+void
+TracingItem::cullOutlyingPoints(double left, double right)
+{
+ int i, cull;
+
+ for (i = cull = 0; i < my.points.size(); i++, cull++)
+ if (my.points.at(i).x() >= left)
+ break;
+ if (cull)
+ my.points.remove(0, cull); // cull from the start (0-index)
+ for (i = my.points.size() - 1, cull = 0; i >= 0; i--, cull++)
+ if (my.points.at(i).x() <= right)
+ break;
+ if (cull)
+ my.points.remove(my.points.size() - cull, cull); // cull from end
+}
+
+void
+TracingItem::cullOutlyingEvents(double left, double right)
+{
+ int i, cull;
+
+ for (i = cull = 0; i < my.events.size(); i++, cull++)
+ if (my.events.at(i).timestamp() >= left)
+ break;
+ if (cull)
+ my.events.remove(0, cull); // cull from the start (0-index)
+ for (i = my.events.size() - 1, cull = 0; i >= 0; i--, cull++)
+ if (my.events.at(i).timestamp() <= right)
+ break;
+ if (cull)
+ my.events.remove(my.events.size() - cull, cull); // cull from end
+}
+
+void
+TracingItem::resetValues(int, double left, double right)
+{
+ cullOutlyingSpans(left, right);
+ cullOutlyingDrops(left, right);
+ cullOutlyingPoints(left, right);
+ cullOutlyingEvents(left, right);
+
+ // update the display
+ my.dropCurve->setSamples(my.drops);
+ my.spanCurve->setSamples(my.spans);
+ my.pointCurve->setSamples(my.points);
+ my.selectionCurve->setSamples(my.selections);
+}
+
+//
+// Requirement here is to merge in any event records that have
+// just arrived in the last timestep (from my.metric) with the
+// set already being displayed.
+// Additionally, we need to cull those that are no longer within
+// the time window of interest.
+//
+void
+TracingItem::updateValues(TracingEngine *engine, double left, double right)
+{
+ QmcMetric *metric = ChartItem::my.metric;
+
+#if DESPERATE
+ console->post(PmChart::DebugForce, "TracingItem::updateValues: "
+ "%d total events, left=%.2f right=%.2f\n"
+ "Metadata counts: drops=%d spans=%d points=%d "
+ "Metric values: %d count\n",
+ my.events.size(), left, right,
+ my.drops.size(), my.spans.size(), my.points.size(), metric->numValues());
+#endif
+
+ cullOutlyingSpans(left, right);
+ cullOutlyingDrops(left, right);
+ cullOutlyingPoints(left, right);
+ cullOutlyingEvents(left, right);
+
+ // crack open newly arrived event records
+ if (metric->numValues() > 0)
+ updateEvents(engine, metric);
+
+ // update the display
+ my.dropCurve->setSamples(my.drops);
+ my.spanCurve->setSamples(my.spans);
+ my.pointCurve->setSamples(my.points);
+ my.selectionCurve->setSamples(my.selections);
+}
+
+void
+TracingItem::updateEvents(TracingEngine *engine, QmcMetric *metric)
+{
+ if (metric->hasIndom() && !metric->explicitInsts()) {
+ for (int i = 0; i < metric->numInst(); i++)
+ updateEventRecords(engine, metric, i);
+ } else {
+ updateEventRecords(engine, metric, 0);
+ }
+}
+
+//
+// Fetch the new set of events, merging them into the existing sets
+// - "Points" curve has an entry for *every* event to be displayed.
+// - "Span" curve has an entry for all *begin/end* flagged events.
+// These are initially unpaired, unless PMDA is playing games, and
+// so usually min/max is used as a placeholder until corresponding
+// begin/end event arrives.
+// - "Drop" curve has an entry to match up events with the parents.
+// The parent is the root "span" (terminology on loan from Dapper)
+//
+void
+TracingItem::updateEventRecords(TracingEngine *engine, QmcMetric *metric, int index)
+{
+ if (metric->error(index) == 0) {
+ QVector<QmcEventRecord> const &records = metric->eventRecords(index);
+ int slot = 0, parentSlot = -1;
+ QString name;
+
+ // First lookup the event "slot" (aka "span" / y-axis-entry)
+ // Strategy is to use the ID from the event (if one exists),
+ // which must be mapped to a y-axis integer-based index. If
+ // no ID, we fall back to metric instance ID, else just zero.
+ //
+ if (metric->hasInstances()) {
+ if (metric->explicitInsts())
+ index = 0;
+ slot = metric->instIndex(index);
+ name = metric->instName(index);
+ } else {
+ name = metric->name();
+ }
+ addTraceSpan(engine, name, slot);
+
+ for (int i = 0; i < records.size(); i++) {
+ QmcEventRecord const &record = records.at(i);
+
+ my.events.append(TracingEvent(record, metric->metricID(), index));
+ TracingEvent &event = my.events.last();
+
+ if (event.hasIdentifier() && name == QString::null) {
+ addTraceSpan(engine, event.spanID(), slot);
+ }
+
+ // this adds the basic point (ellipse), all events get one
+ my.points.append(QPointF(event.timestamp(), slot));
+
+ parentSlot = -1;
+ if (event.hasParent()) { // lookup parent in yMap
+ parentSlot = engine->getTraceSpan(event.rootID(), parentSlot);
+ if (parentSlot == -1)
+ addTraceSpan(engine, event.rootID(), parentSlot);
+ // do this on start/end only? (or if first?)
+ my.drops.append(QwtIntervalSample(event.timestamp(),
+ QwtInterval(slot, parentSlot)));
+ }
+
+#if DESPERATE
+ QString timestamp = QmcSource::timeStringBrief(record.timestamp());
+ console->post(PmChart::DebugForce, "TracingItem::updateEventRecords: "
+ "[%s] span: %s (slot=%d) id=%s, root: %s (slot=%d,id=%s), start=%s end=%s",
+ (const char *)timestamp.toAscii(),
+ (const char *)event.spanID().toAscii(), slot,
+ event.hasIdentifier() ? "y" : "n",
+ (const char *)event.rootID().toAscii(), parentSlot,
+ event.hasParent() ? "y" : "n",
+ event.hasStartFlag() ? "y" : "n", event.hasEndFlag() ? "y" : "n");
+#endif
+
+ if (event.hasStartFlag()) {
+ if (!my.spans.isEmpty()) {
+ QwtIntervalSample &active = my.spans.last();
+ // did we get a start, then another start?
+ // (if so, just end the previous span now)
+ if (active.interval.maxValue() == my.maxSpanTime)
+ active.interval.setMaxValue(event.timestamp());
+ }
+ // no matter what, we'll start a new span here
+ my.spans.append(QwtIntervalSample(slot,
+ QwtInterval(event.timestamp(), my.maxSpanTime)));
+ }
+ if (event.hasEndFlag()) {
+ if (!my.spans.isEmpty()) {
+ QwtIntervalSample &active = my.spans.last();
+ // did we get an end, then another end?
+ // (if so, move previous span end to now)
+ if (active.interval.maxValue() == my.maxSpanTime)
+ active.interval.setMaxValue(event.timestamp());
+ } else {
+ // got an end, but we haven't seen a start
+ my.spans.append(QwtIntervalSample(index,
+ QwtInterval(my.minSpanTime, event.timestamp())));
+ }
+ }
+ // Have not yet handled missed events (i.e. event.missed())
+ // Could have a separate list of events? (render differently?)
+ }
+ } else {
+#if DESPERATE
+ //
+ // TODO: need to track this failure point, and ensure that the
+ // begin/end spans do not cross this boundary.
+ //
+ console->post(PmChart::DebugForce,
+ "TracingItem::updateEventRecords: NYI error path: %d (%s)",
+ metric->error(index), pmErrStr(metric->error(index)));
+#endif
+ }
+}
+
+void
+TracingItem::addTraceSpan(TracingEngine *engine, const QString &span, int slot)
+{
+ double spanID = (double)slot;
+
+ my.minSpanID = qMin(my.minSpanID, spanID);
+ my.maxSpanID = qMax(my.maxSpanID, spanID);
+ engine->addTraceSpan(span, slot);
+}
+
+void
+TracingItem::setStroke(Chart::Style, QColor color, bool)
+{
+ QPen outline(QColor(Qt::black));
+ QColor darkColor(color);
+ QColor alphaColor(color);
+
+ darkColor.dark(180);
+ alphaColor.setAlpha(196);
+ QBrush alphaBrush(alphaColor);
+
+ my.pointCurve->setLegendColor(color);
+
+ my.spanSymbol->setWidth(6);
+ my.spanSymbol->setBrush(alphaBrush);
+ my.spanSymbol->setPen(outline);
+
+ my.dropSymbol->setWidth(1);
+ my.dropSymbol->setBrush(Qt::NoBrush);
+ my.dropSymbol->setPen(outline);
+
+ my.pointSymbol->setSize(8);
+ my.pointSymbol->setColor(color);
+ my.pointSymbol->setPen(outline);
+
+ my.selectionSymbol->setSize(8);
+ my.selectionSymbol->setColor(color.dark(180));
+ my.selectionSymbol->setPen(outline);
+}
+
+bool
+TracingItem::containsPoint(const QRectF &rect, int index)
+{
+ if (my.points.isEmpty())
+ return false;
+ return rect.contains(my.points.at(index));
+}
+
+void
+TracingItem::updateCursor(const QPointF &, int index)
+{
+ Q_ASSERT(index <= my.points.size());
+ Q_ASSERT(index <= (int)my.pointCurve->dataSize());
+
+ my.selections.append(my.points.at(index));
+ my.selectionInfo.append(my.events.at(index).description());
+
+ // required for immediate chart update after selection
+ QBrush pointBrush = my.pointSymbol->brush();
+ my.pointSymbol->setBrush(my.selectionSymbol->brush());
+ QwtPlotDirectPainter directPainter;
+ directPainter.drawSeries(my.pointCurve, index, index);
+ my.pointSymbol->setBrush(pointBrush);
+}
+
+void
+TracingItem::clearCursor(void)
+{
+ // immediately clear any current visible selections
+ for (int index = 0; index < my.selections.size(); index++) {
+ QwtPlotDirectPainter directPainter;
+ directPainter.drawSeries(my.pointCurve, index, index);
+ }
+ my.selections.clear();
+ my.selectionInfo.clear();
+}
+
+//
+// Display information text associated with selected events
+//
+const QString &
+TracingItem::cursorInfo(void)
+{
+ if (my.selections.size() > 0) {
+ QString preamble = metricName();
+ if (metricHasInstances())
+ preamble.append("[").append(metricInstance()).append("]");
+ preamble.append("\n");
+ my.selectionInfo.prepend(preamble);
+ }
+ return my.selectionInfo;
+}
+
+void
+TracingItem::revive(void)
+{
+ if (removed()) {
+ setRemoved(false);
+ my.dropCurve->attach(my.chart);
+ my.spanCurve->attach(my.chart);
+ my.pointCurve->attach(my.chart);
+ my.selectionCurve->attach(my.chart);
+ }
+}
+
+void
+TracingItem::remove(void)
+{
+ setRemoved(true);
+ my.dropCurve->detach();
+ my.spanCurve->detach();
+ my.pointCurve->detach();
+ my.selectionCurve->detach();
+}
+
+void
+TracingItem::redraw(void)
+{
+ if (removed() == false) {
+ // point curve update by legend check, but not the rest:
+ my.dropCurve->setVisible(hidden() == false);
+ my.spanCurve->setVisible(hidden() == false);
+ my.selectionCurve->setVisible(hidden() == false);
+ }
+}
+
+
+TracingScaleEngine::TracingScaleEngine(TracingEngine *engine) : QwtLinearScaleEngine()
+{
+ my.engine = engine;
+ my.minSpanID = 0.0;
+ my.maxSpanID = 1.0;
+ setMargins(0.5, 0.5);
+}
+
+void
+TracingScaleEngine::getScale(double *minValue, double *maxValue)
+{
+ *minValue = my.minSpanID;
+ *maxValue = my.maxSpanID;
+}
+
+void
+TracingScaleEngine::setScale(double minValue, double maxValue)
+{
+ my.minSpanID = minValue;
+ my.maxSpanID = maxValue;
+}
+
+bool
+TracingScaleEngine::updateScale(double minValue, double maxValue)
+{
+ bool changed = false;
+
+ if (minValue < my.minSpanID) {
+ my.minSpanID = minValue;
+ changed = true;
+ }
+ if (maxValue > my.maxSpanID) {
+ my.maxSpanID = maxValue;
+ changed = true;
+ }
+ return changed;
+}
+
+void
+TracingScaleEngine::autoScale(int maxSteps, double &minValue,
+ double &maxValue, double &stepSize) const
+{
+ minValue = my.minSpanID;
+ maxValue = my.maxSpanID;
+ stepSize = 1.0;
+ QwtLinearScaleEngine::autoScale(maxSteps, minValue, maxValue, stepSize);
+}
+
+QwtScaleDiv
+TracingScaleEngine::divideScale(double x1, double x2, int numMajorSteps,
+ int /*numMinorSteps*/, double /*stepSize*/) const
+{
+ // discard minor steps - y-axis is displaying trace identifiers;
+ // sub-divisions of an identifier makes no sense
+ return QwtLinearScaleEngine::divideScale(x1, x2, numMajorSteps, 0, 1.0);
+}
+
+
+//
+// Use the hash map to provide event identifiers that map to given numeric IDs
+// These values were mapped into the hash when we decoded the event records.
+//
+QwtText
+TracingScaleDraw::label(double value) const
+{
+ int slot = (int)value;
+ const int LABEL_CUTOFF = 8; // maximum width for label (units: characters)
+ QString label = my.engine->getSpanLabel(slot);
+
+#if DESPERATE
+ console->post(PmChart::DebugForce,
+ "TracingScaleDraw::label: lookup ID %d (=>\"%s\")",
+ slot, (const char *)label.toAscii());
+#endif
+
+ // ensure label is not too long to fit
+ label.truncate(LABEL_CUTOFF);
+ // and only use up to the first space
+ if ((slot = label.indexOf(' ')) >= 0)
+ label.truncate(slot);
+ return label;
+}
+
+void
+TracingScaleDraw::getBorderDistHint(const QFont &f, int &start, int &end) const
+{
+ if (orientation() == Qt::Vertical)
+ start = end = 0;
+ else
+ QwtScaleDraw::getBorderDistHint(f, start, end);
+}
+
+
+//
+// The (chart-level) implementation of tracing charts
+//
+TracingEngine::TracingEngine(Chart *chart)
+{
+ QwtPlotPicker *picker = chart->my.picker;
+
+ my.chart = chart;
+ my.chart->my.style = Chart::EventStyle;
+
+ my.scaleDraw = new TracingScaleDraw(this);
+ chart->setAxisScaleDraw(QwtPlot::yLeft, my.scaleDraw);
+
+ my.scaleEngine = new TracingScaleEngine(this);
+ chart->setAxisScaleEngine(QwtPlot::yLeft, my.scaleEngine);
+
+ // use a rectangular point picker for event tracing
+ picker->setStateMachine(new QwtPickerDragRectMachine());
+ picker->setRubberBand(QwtPicker::RectRubberBand);
+ picker->setRubberBandPen(QColor(Qt::green));
+}
+
+ChartItem *
+TracingEngine::addItem(QmcMetric *mp, pmMetricSpec *msp, pmDesc *desc, const QString &legend)
+{
+ return new TracingItem(my.chart, mp, msp, desc, legend);
+}
+
+TracingItem *
+TracingEngine::tracingItem(int index)
+{
+ return (TracingItem *)my.chart->my.items[index];
+}
+
+void
+TracingEngine::selected(const QPolygon &poly)
+{
+ my.chart->showPoints(poly);
+}
+
+void
+TracingEngine::replot(void)
+{
+ for (int i = 0; i < my.chart->metricCount(); i++)
+ tracingItem(i)->redraw();
+}
+
+void
+TracingEngine::updateValues(bool, int, int, double left, double right, double)
+{
+ // Drive new values into each chart item
+ for (int i = 0; i < my.chart->metricCount(); i++)
+ tracingItem(i)->updateValues(this, left, right);
+}
+
+int
+TracingEngine::getTraceSpan(const QString &spanID, int slot) const
+{
+ return my.traceSpanMapping.value(spanID, slot);
+}
+
+void
+TracingEngine::addTraceSpan(const QString &spanID, int slot)
+{
+ Q_ASSERT(spanID != QString::null && spanID != "");
+ console->post("TracingEngine::addTraceSpan: \"%s\" <=> slot %d (%d/%d span/label)",
+ (const char *)spanID.toAscii(), slot,
+ my.traceSpanMapping.size(), my.labelSpanMapping.size());
+ my.traceSpanMapping.insert(spanID, slot);
+ my.labelSpanMapping.insert(slot, spanID);
+}
+
+QString
+TracingEngine::getSpanLabel(int slot) const
+{
+ return my.labelSpanMapping.value(slot);
+}
+
+void
+TracingEngine::redoScale(void)
+{
+ double minValue, maxValue;
+
+ my.scaleEngine->getScale(&minValue, &maxValue);
+
+ for (int i = 0; i < my.chart->metricCount(); i++)
+ tracingItem(i)->rescaleValues(&minValue, &maxValue);
+
+ if (my.scaleEngine->updateScale(minValue, maxValue)) {
+ my.scaleDraw->invalidate();
+ replot();
+ }
+}
+
+bool
+TracingEngine::isCompatible(pmDesc &desc)
+{
+ return (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT);
+}
+
+void
+TracingEngine::scale(bool *autoScale, double *yMin, double *yMax)
+{
+ *autoScale = true;
+ my.scaleEngine->getScale(yMin, yMax);
+}
+
+void
+TracingEngine::setScale(bool, double, double)
+{
+ my.chart->setAxisAutoScale(QwtPlot::yLeft);
+}
+
+void
+TracingEngine::setStyle(Chart::Style)
+{
+ my.chart->setYAxisTitle("");
+}
diff --git a/src/pmchart/tracing.h b/src/pmchart/tracing.h
new file mode 100644
index 0000000..3072808
--- /dev/null
+++ b/src/pmchart/tracing.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2012, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TRACING_H
+#define TRACING_H
+
+#include "chart.h"
+#include <qvector.h>
+#include <qwt_plot.h>
+#include <qwt_scale_draw.h>
+#include <qwt_scale_engine.h>
+#include <qwt_interval_symbol.h>
+#include <qwt_plot_intervalcurve.h>
+
+class TracingEvent
+{
+public:
+ TracingEvent() { }
+ TracingEvent(QmcEventRecord const &, pmID, int);
+ bool operator<(TracingEvent const& rhs) // for sorting
+ { return my.timestamp < rhs.timestamp(); }
+ virtual ~TracingEvent();
+
+ double timestamp(void) const { return my.timestamp; }
+ int missed(void) const { return my.missed; }
+ bool hasIdentifier(void) const { return my.flags & PM_EVENT_FLAG_ID; }
+ bool hasParent(void) const { return my.flags & PM_EVENT_FLAG_PARENT; }
+ bool hasStartFlag(void) const { return my.flags & PM_EVENT_FLAG_START; }
+ bool hasEndFlag(void) const { return my.flags & PM_EVENT_FLAG_END; }
+
+ const QString &spanID(void) const { return my.spanID; }
+ const QString &rootID(void) const { return my.rootID; }
+ const QString &description(void) const { return my.description; }
+
+private:
+ struct {
+ double timestamp; // from PMDA
+ int missed; // from PMDA
+ int flags; // from PMDA
+ pmID pmid; // metric
+ int inst; // instance
+ QString spanID; // identifier
+ QString rootID; // parent ID
+ QString description; // parameters, etc
+ } my;
+};
+
+class TracingItem : public ChartItem
+{
+public:
+ TracingItem() : ChartItem() { }
+ TracingItem(Chart *, QmcMetric *, pmMetricSpec *, pmDesc *, const QString &);
+ virtual ~TracingItem();
+
+ QwtPlotItem *item();
+ QwtPlotCurve *curve();
+
+ void preserveSample(int, int) { }
+ void punchoutSample(int) { }
+ void resetValues(int, double, double);
+ void updateValues(TracingEngine *, double, double);
+ void rescaleValues(double *, double *);
+
+ void clearCursor(void);
+ bool containsPoint(const QRectF &, int);
+ void updateCursor(const QPointF &, int);
+ const QString &cursorInfo();
+
+ void setStroke(Chart::Style, QColor, bool);
+ void revive(void);
+ void remove(void);
+ void redraw(void);
+
+private:
+ void cullOutlyingDrops(double, double);
+ void cullOutlyingSpans(double, double);
+ void cullOutlyingPoints(double, double);
+ void cullOutlyingEvents(double, double);
+
+ void updateEvents(TracingEngine *, QmcMetric *);
+ void updateEventRecords(TracingEngine *, QmcMetric *, int);
+ void addTraceSpan(TracingEngine *, const QString &, int);
+ void showEventInfo(bool, int);
+
+ struct {
+ QVector<TracingEvent> events; // all events, raw data
+ QVector<QPointF> selections; // time series of selected points
+ QString selectionInfo;
+
+ QVector<QPointF> points; // displayed trace data (point form)
+ ChartCurve *pointCurve;
+ QwtSymbol *pointSymbol;
+
+ QVector<QPointF> selectionPoints; // displayed user-selected trace points
+ QwtPlotCurve *selectionCurve;
+ QwtSymbol *selectionSymbol;
+
+ QVector<QwtIntervalSample> spans; // displayed trace data (horizontal span)
+ QwtPlotIntervalCurve *spanCurve;
+ QwtIntervalSymbol *spanSymbol;
+
+ QVector<QwtIntervalSample> drops; // displayed trace data (vertical drop)
+ QwtPlotIntervalCurve *dropCurve;
+ QwtIntervalSymbol *dropSymbol;
+
+ double minSpanID;
+ double maxSpanID;
+ double minSpanTime;
+ double maxSpanTime;
+
+ Chart *chart;
+ } my;
+};
+
+//
+// Handles updates to the Y-Axis
+//
+class TracingScaleEngine : public QwtLinearScaleEngine
+{
+public:
+ TracingScaleEngine(TracingEngine *engine);
+
+ virtual void autoScale(int maxSteps, double &minValue,
+ double &maxValue, double &stepSize) const;
+ virtual QwtScaleDiv divideScale(double x1, double x2,
+ int numMajorSteps, int numMinorSteps, double stepSize = 0.0) const;
+
+ void getScale(double *minSpanID, double *maxSpanID);
+ void setScale(double minSpanID, double maxSpanID);
+ bool updateScale(double minSpanID, double maxSpanID);
+
+private:
+ struct {
+ double minSpanID;
+ double maxSpanID;
+ TracingEngine *engine;
+ } my;
+};
+
+//
+// Implements a mapping from span identifier to span name
+// for updating the Y-Axis display.
+//
+class TracingScaleDraw : public QwtScaleDraw
+{
+public:
+ TracingScaleDraw(TracingEngine *engine) : QwtScaleDraw() { my.engine = engine; }
+ virtual QwtText label(double v) const;
+ virtual void getBorderDistHint(const QFont &f, int &start, int &end) const;
+ void invalidate() { invalidateCache(); }
+
+private:
+ struct {
+ TracingEngine *engine;
+ } my;
+};
+
+//
+// Implement tracing-specific behaviour within a Chart
+//
+class TracingEngine : public ChartEngine
+{
+ friend class Chart;
+
+public:
+ TracingEngine(Chart *);
+
+ bool isCompatible(pmDesc &);
+ ChartItem *addItem(QmcMetric *, pmMetricSpec *, pmDesc *, const QString &);
+
+ void updateValues(bool, int, int, double, double, double);
+
+ void redoScale(void);
+ void setScale(bool, double, double);
+ void scale(bool *, double *, double *);
+ void setStyle(Chart::Style);
+
+ void addTraceSpan(const QString &, int);
+ int getTraceSpan(const QString &, int) const;
+ QString getSpanLabel(int) const;
+
+ void selected(const QPolygon &);
+ void replot(void);
+
+private:
+ TracingItem *tracingItem(int index);
+
+ struct {
+ QHash<QString, int> traceSpanMapping; // map, event ID to y-axis point
+ QHash<int, QString> labelSpanMapping; // reverse -> y-axis point to ID
+ TracingScaleEngine *scaleEngine;
+ TracingScaleDraw *scaleDraw; // convert ints to spanID
+ Chart *chart;
+ } my;
+};
+
+#endif // TRACING_H
diff --git a/src/pmchart/view.cpp b/src/pmchart/view.cpp
new file mode 100644
index 0000000..fcb6cf0
--- /dev/null
+++ b/src/pmchart/view.cpp
@@ -0,0 +1,1313 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2013, Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QString>
+#include <QtGui/QMessageBox>
+#include <QtGui/QColor>
+
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include "main.h"
+#include "openviewdialog.h"
+#include "saveviewdialog.h"
+
+/*
+ * View file parsing routines and global variables follow. These are
+ * currently not part of any class, and may need to be reworked a bit
+ * to use more portable (Qt) file IO interfaces (for a Windows port).
+ */
+static char _fname[MAXPATHLEN];
+static uint _line;
+static uint _errors;
+static uint _width;
+static uint _height;
+static uint _points;
+static uint _xpos;
+static uint _ypos;
+
+#define MAXWDSZ 256
+
+// parser states
+#define S_BEGIN 0
+#define S_VERSION 1
+#define S_TOP 2
+#define S_CHART 3
+#define S_PLOT 4
+
+// config file styles (mode)
+#define M_UNKNOWN 0
+#define M_PMCHART 1
+#define M_KMCHART 2
+
+// global attributes
+#define G_WIDTH 1
+#define G_HEIGHT 2
+#define G_POINTS 3
+#define G_XPOS 4
+#define G_YPOS 5
+
+// host mode
+#define H_DYNAMIC 1
+#define H_LITERAL 2
+
+// error severity
+#define E_INFO 0
+#define E_CRIT 1
+#define E_WARN 2
+
+// version numbers we're willing and able to support
+#define P1_1 101
+#define P1_2 102
+#define P2_0 200
+#define P2_1 201
+#define K1 1
+
+// instance / matching / not-matching
+#define IM_NONE 0
+#define IM_INST 1
+#define IM_MATCH 2
+#define IM_NOT_MATCH 3
+
+const char *_style[] = { "None", "Line", "Bar", "Stack", "Area", "Util" };
+#define stylestr(x) _style[(int)x]
+
+static void err(int severity, int do_where, QString msg)
+{
+ if (do_where) {
+ QString where = QString();
+ where.sprintf("%s[%d] ", _fname, _line);
+ msg.prepend(where);
+ }
+ if (Cflag) {
+ if (severity == E_CRIT)
+ msg.prepend("Error: ");
+ else if (severity == E_WARN)
+ msg.prepend("Warning: ");
+ // else do nothing for E_INFO
+ msg.append("\n");
+ fflush(stderr);
+ pmprintf("%s", (const char *)msg.toAscii());
+ pmflush();
+ }
+ else {
+ if (severity == E_CRIT)
+ QMessageBox::critical(pmchart, pmProgname, msg);
+ else if (severity == E_WARN)
+ QMessageBox::warning(pmchart, pmProgname, msg);
+ else
+ QMessageBox::information(pmchart, pmProgname, msg);
+ }
+ _errors++;
+}
+
+static char *getwd(FILE *f)
+{
+ static char buf[MAXWDSZ];
+ static int lastc = 0;
+ char *p;
+ int c;
+ int quote = 0;
+
+ if ((char)lastc == '\n') {
+eol:
+ buf[0] = '\n';
+ buf[1] = '\0';
+ _line++;
+ lastc = 0;
+ goto done;
+ }
+
+ // skip to first non-white space
+ p = buf;
+ while ((c = fgetc(f)) != EOF) {
+ if ((char)c == '\n')
+ goto eol;
+ if (!isspace((char)c)) {
+ // got one
+ if ((char)c == '"') {
+ quote = 1;
+ }
+ else {
+ *p++ = c;
+ }
+ break;
+ }
+ }
+ if (feof(f)) return NULL;
+
+ for ( ; p < &buf[MAXWDSZ]; ) {
+ if ((c = fgetc(f)) == EOF) break;
+ if ((char)c == '\n') {
+ lastc = c;
+ break;
+ }
+ if (quote == 0 && isspace((char)c)) break;
+ if (quote && (char)c == '"') break;
+ *p++ = c;
+ }
+
+ if (p == &buf[MAXWDSZ]) {
+ QString msg = QString();
+ p[-1] = '\0';
+ msg.sprintf("Word truncated after %d characters!\n\"%20.20s ... %20.20s\"", (int)sizeof(buf)-1, buf, &p[-21]);
+ err(E_CRIT, true, msg);
+ }
+ else
+ *p = '\0';
+
+done:
+ if ((pmDebug & DBG_TRACE_APPL0) && (pmDebug & DBG_TRACE_APPL2)) {
+ if (buf[0] == '\n')
+ fprintf(stderr, "openView getwd=EOL\n");
+ else
+ fprintf(stderr, "openView getwd=\"%s\"\n", buf);
+ }
+
+ return buf;
+}
+
+static void eol(FILE *f)
+{
+ char *w;
+
+ while ((w = getwd(f)) != NULL && w[0] != '\n') {
+ QString msg = QString("Syntax error: unexpected word \"");
+ msg.append(w);
+ msg.append("\"");
+ err(E_CRIT, true, msg);
+ }
+}
+
+static void skip2eol(FILE *f)
+{
+ char *w;
+
+ while ((w = getwd(f)) != NULL && w[0] != '\n') {
+ ;
+ }
+}
+
+static void xpect(const char *want, const char *got)
+{
+ QString msg = QString("Syntax error: expecting \"");
+ msg.append(want);
+ msg.append("\", found ");
+ if (got == NULL)
+ msg.append("End-of-File");
+ else if (got[0] == '\n')
+ msg.append("End-of-Line");
+ else {
+ msg.append("\"");
+ msg.append(got);
+ msg.append("\"");
+ }
+ err(E_CRIT, true, msg);
+}
+
+static QColor colorSpec(QString colorName, int *sequence)
+{
+ if (colorName == "#-cycle")
+ return nextColor("#-cycle", sequence);
+ if (ColorScheme::lookupScheme(colorName) == true)
+ return nextColor(colorName, sequence);
+ QColor color = ColorScheme::colorSpec(colorName);
+ if (!color.isValid()) {
+ QString errmsg;
+ errmsg.append("Invalid color name: ");
+ errmsg.append(colorName);
+ err(E_CRIT, true, errmsg);
+ color = Qt::white;
+ }
+ return color;
+}
+
+void OpenViewDialog::globals(int *w, int *h, int *pts, int *x, int *y)
+{
+ // Note: we use global variables here so that all views specified
+ // on the command line get input into these values (the maximum
+ // observed value is always used), without clobbering each other.
+ //
+ *w = _width;
+ *h = _height;
+ *pts = _points;
+ *x = _xpos;
+ *y = _ypos;
+}
+
+bool OpenViewDialog::openView(const char *path)
+{
+ Tab *tab;
+ Chart *cp = NULL;
+ int ct = pmchart->tabWidget()->currentIndex();
+ int m;
+ ColorScheme scheme;
+ FILE *f;
+ int is_popen = 0;
+ char *w;
+ int state = S_BEGIN;
+ int mode = M_UNKNOWN;
+ int h_mode;
+ int version;
+ QString errmsg;
+ QRegExp regex;
+ int sep = __pmPathSeparator();
+ int sts = 0;
+
+ if (strcmp(path, "-") == 0) {
+ f = stdin;
+ strcpy(_fname, "stdin");
+ }
+ else if (path[0] == '/') {
+ strcpy(_fname, path);
+ if ((f = fopen(_fname, "r")) == NULL)
+ goto noview;
+ }
+ else {
+ QString homepath = QDir::toNativeSeparators(QDir::homePath());
+
+ strcpy(_fname, path);
+ if ((f = fopen(_fname, "r")) == NULL) {
+ // not found, start the great hunt
+ // try user's pmchart dir ...
+ snprintf(_fname, sizeof(_fname),
+ "%s%c" ".pcp%c" "pmchart%c" "%s",
+ (const char *)homepath.toAscii(), sep, sep, sep, path);
+ if ((f = fopen(_fname, "r")) == NULL) {
+ // try system pmchart dir
+ snprintf(_fname, sizeof(_fname),
+ "%s%c" "config%c" "pmchart%c" "%s",
+ pmGetConfig("PCP_VAR_DIR"), sep, sep, sep, path);
+ if ((f = fopen(_fname, "r")) == NULL) {
+ // try user's kmchart dir
+ snprintf(_fname, sizeof(_fname),
+ "%s%c" ".pcp%c" "kmchart%c" "%s",
+ (const char *)homepath.toAscii(),
+ sep, sep, sep, path);
+ if ((f = fopen(_fname, "r")) == NULL) {
+ // try system kmchart dir
+ snprintf(_fname, sizeof(_fname),
+ "%s%c" "config%c" "kmchart%c" "%s",
+ pmGetConfig("PCP_VAR_DIR"),
+ sep, sep, sep, path);
+ if ((f = fopen(_fname, "r")) == NULL) {
+ snprintf(_fname, sizeof(_fname),
+ "%s%c" "config%c" "pmchart%c" "%s",
+ pmGetConfig("PCP_VAR_DIR"),
+ sep, sep, sep, path);
+ goto noview;
+ }
+ }
+ }
+ }
+ }
+ // check for executable and popen() as needed
+ //
+ if (fgetc(f) == '#' && fgetc(f) == '!') {
+ char cmd[MAXPATHLEN];
+ sprintf(cmd, "%s", _fname);
+ fclose(f);
+ if ((f = popen(cmd, "r")) == NULL)
+ goto nopipe;
+ is_popen = 1;
+ }
+ else {
+ rewind(f);
+ }
+ }
+
+ _line = 1;
+ _errors = 0;
+ console->post(PmChart::DebugView, "Load View: %s", _fname);
+
+ while ((w = getwd(f)) != NULL) {
+ if (state == S_BEGIN) {
+ // expect #pmchart
+ if (strcasecmp(w, "#pmchart") == 0)
+ mode = M_PMCHART;
+ else if (strcasecmp(w, "#kmchart") == 0)
+ mode = M_KMCHART;
+ else {
+ xpect("#pmchart\" or \"#kmchart", w);
+ goto abandon;
+ }
+ eol(f);
+ state = S_VERSION;
+ continue;
+ }
+
+ if (w[0] == '\n')
+ // skip empty lines and comments
+ continue;
+
+ if (w[0] == '#') {
+ // and comments
+ skip2eol(f);
+ continue;
+ }
+
+ if (state == S_VERSION) {
+ // expect version X.X host [dynamic|static]
+ if (strcasecmp(w, "version") != 0) {
+ xpect("version", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ xpect("<version number>", w);
+ goto abandon;
+ }
+ version = 0;
+ if (mode == M_PMCHART) {
+ if (strcmp(w, "2.1") == 0)
+ version = P2_1;
+ else if (strcmp(w, "2.0") == 0)
+ version = P2_0;
+ else if (strcmp(w, "1.1") == 0)
+ version = P1_1;
+ else if (strcmp(w, "1.2") == 0)
+ version = P1_2;
+ }
+ else if (mode == M_KMCHART) {
+ if (strcmp(w, "1") == 0)
+ version = K1;
+ }
+ if (version == 0) {
+ xpect("<version number>", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ if (mode == M_KMCHART) {
+ // host [literal|dynamic] is optional for new pmchart
+ h_mode = H_DYNAMIC;
+ state = S_TOP;
+ continue;
+ }
+ else {
+ xpect("host", w);
+ goto abandon;
+ }
+ }
+ if (strcasecmp(w, "host") != 0) {
+ xpect("host", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w != NULL && strcasecmp(w, "literal") == 0) {
+ h_mode = H_LITERAL;
+ }
+ else if (w != NULL && strcasecmp(w, "dynamic") == 0) {
+ h_mode = H_DYNAMIC;
+ }
+ else {
+ xpect("literal\" or \"dynamic", w);
+ goto abandon;
+ }
+ eol(f);
+ state = S_TOP;
+ }
+
+ else if (state == S_TOP) {
+ if (strcasecmp(w, "chart") == 0) {
+new_chart:
+ char *title = NULL;
+ Chart::Style style = Chart::NoStyle;
+ int autoscale = 1;
+ char *endnum;
+ double ymin = 0;
+ double ymax = 0;
+ int legend = 1;
+ int antialias = 1;
+
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("title\" or \"style", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "title") == 0) {
+ // optional title "<title>"
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<title>", w);
+ goto abort_chart;
+ }
+ if ((title = strdup(w)) == NULL) nomem();
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("style", w);
+ goto abort_chart;
+ }
+ }
+ if (strcasecmp(w, "style") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<chart style>", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "plot") == 0)
+ style = Chart::LineStyle;
+ else if (strcasecmp(w, "line") == 0)
+ style = Chart::LineStyle;
+ else if (strcasecmp(w, "bar") == 0)
+ style = Chart::BarStyle;
+ else if (strcasecmp(w, "stacking") == 0)
+ style = Chart::StackStyle;
+ else if (strcasecmp(w, "area") == 0)
+ style = Chart::AreaStyle;
+ else if (strcasecmp(w, "utilization") == 0)
+ style = Chart::UtilisationStyle;
+ else if (strcasecmp(w, "event") == 0)
+ style = Chart::EventStyle;
+ else {
+ xpect("<chart style>", w);
+ goto abort_chart;
+ }
+ }
+
+ // down to the optional bits
+ // - scale
+ // - legend
+ if ((w = getwd(f)) == NULL || w[0] == '\n')
+ goto done_chart;
+ if (strcasecmp(w, "scale") == 0) {
+ // scale [from] ymin [to] ymax
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("from or <ymin>", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "from") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<ymin>", w);
+ goto abort_chart;
+ }
+ }
+ ymin = strtod(w, &endnum);
+ if (*endnum != '\0') {
+ xpect("<ymin>", w);
+ goto abort_chart;
+ }
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("to or <ymax>", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "to") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<ymax>", w);
+ goto abort_chart;
+ }
+ }
+ ymax = strtod(w, &endnum);
+ if (*endnum != '\0') {
+ xpect("<ymax>", w);
+ goto abort_chart;
+ }
+ autoscale = 0;
+ if ((w = getwd(f)) == NULL || w[0] == '\n')
+ goto done_chart;
+ }
+ if (strcasecmp(w, "legend") == 0) {
+ // optional legend on|off
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("on\" or \"off", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "on") == 0)
+ legend = 1;
+ else if (strcasecmp(w, "off") == 0)
+ legend = 0;
+ else {
+ xpect("on\" or \"off", w);
+ goto abort_chart;
+ }
+ if ((w = getwd(f)) == NULL || w[0] == '\n')
+ goto done_chart;
+ }
+ if (strcasecmp(w, "antialiasing") == 0) {
+ // optional antialiasing on|off
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("on\" or \"off", w);
+ goto abort_chart;
+ }
+ if (strcasecmp(w, "on") == 0)
+ antialias = 1;
+ else if (strcasecmp(w, "off") == 0)
+ antialias = 0;
+ else {
+ xpect("on\" or \"off", w);
+ goto abort_chart;
+ }
+ if ((w = getwd(f)) == NULL || w[0] == '\n')
+ goto done_chart;
+ }
+done_chart:
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "openView: new chart: style=%s",
+ stylestr(style));
+ if (title != NULL)
+ fprintf(stderr, " title=\"%s\"", title);
+ if (autoscale)
+ fprintf(stderr, " autoscale=yes");
+ else
+ fprintf(stderr, " ymin=%.1f ymax=%.1f", ymin, ymax);
+ if (legend)
+ fprintf(stderr, " legend=yes");
+ if (!antialias)
+ fprintf(stderr, " antialias=no");
+ fputc('\n', stderr);
+ }
+ if (Cflag == 0 || Cflag == 2) {
+ tab = pmchart->activeTab();
+ cp = new Chart(tab, tab->splitter());
+ cp->setStyle(style);
+ cp->setScheme(scheme.name());
+ if (title != NULL)
+ cp->changeTitle(title, mode == M_KMCHART);
+ if (legend == 0)
+ cp->setLegendVisible(false);
+ if (antialias == 0)
+ cp->setAntiAliasing(false);
+ cp->setScale(autoscale, ymin, ymax);
+ activeGroup->addGadget(cp);
+ tab->addGadget(cp);
+ }
+ state = S_CHART;
+ if (title != NULL) free(title);
+ continue;
+
+abort_chart:
+ // unrecoverable error in the chart clause of the view
+ // specification, abandon this one
+ if (title != NULL) free(title);
+ goto abandon;
+ }
+ else if (strcasecmp(w, "global") == 0) {
+ char *endnum;
+ uint value, attr;
+
+ // Global window attributes (geometry and visible points)
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w);
+ goto abandon;
+ }
+ else if (strcasecmp(w, "width") == 0)
+ attr = G_WIDTH;
+ else if (strcasecmp(w, "height") == 0)
+ attr = G_HEIGHT;
+ else if (strcasecmp(w, "points") == 0)
+ attr = G_POINTS;
+ else if (strcasecmp(w, "xpos") == 0)
+ attr = G_XPOS;
+ else if (strcasecmp(w, "ypos") == 0)
+ attr = G_YPOS;
+ else {
+ xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ xpect("<global attribute value>", w);
+ goto abandon;
+ }
+ value = (uint)strtoul(w, &endnum, 0);
+ if (*endnum != '\0') {
+ xpect("<global attribute value>", w);
+ goto abandon;
+ }
+ switch (attr) {
+ case G_WIDTH:
+ _width = qMax(_width, value);
+ break;
+ case G_HEIGHT:
+ _height = qMax(_height, value);
+ break;
+ case G_POINTS:
+ _points = qMax(_points, value);
+ break;
+ case G_XPOS:
+ _xpos = qMax(_xpos, value);
+ break;
+ case G_YPOS:
+ _ypos = qMax(_ypos, value);
+ break;
+ }
+ eol(f);
+ }
+ else if (strcasecmp(w, "scheme") == 0) {
+ //
+ // scheme <name> <color> <color>...
+ // provides finer-grained control over the color selections
+ // for an individual chart. The default color scheme is
+ // named #-cycle. A scheme can be used in place of a direct
+ // color name specification, and the color for a plot is
+ // then defined as the next unused color from that scheme.
+ //
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ xpect("<color scheme value>", w);
+ goto abandon;
+ }
+ else if (strcmp(w, "#-cycle") == 0) {
+ xpect("<non-default color scheme name>", w);
+ goto abandon;
+ }
+ if (ColorScheme::lookupScheme(w) == true) {
+ // duplicate - ignore (probably using a seen view again)
+ skip2eol(f);
+ continue;
+ }
+ scheme.setName(QString(w));
+ scheme.clear();
+ w = getwd(f);
+ while (w && w[0] != '\n') {
+ scheme.addColor(QString(w));
+ w = getwd(f);
+ }
+ if (scheme.size() < 2) {
+ xpect("<list of color names>", w);
+ goto abandon;
+ }
+ globalSettings.colorSchemes.append(scheme);
+ }
+ else if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) {
+new_tab:
+ QString label, host;
+ int samples = globalSettings.sampleHistory;
+ int points = globalSettings.visibleHistory;
+ char *endnum;
+
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ xpect("<tab label>", w);
+ goto abandon;
+ }
+ label = w;
+
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n')
+ goto done_tab;
+
+ // default "host" specification for the tab is optional
+ if (strcasecmp(w, "host") == 0) {
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n') {
+ xpect("<host>", w);
+ goto abandon;
+ }
+ host = w;
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n')
+ goto done_tab;
+ }
+ if (strcasecmp(w, "points") != 0) {
+ xpect("<tab points>", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w)
+ points = (uint)strtoul(w, &endnum, 0);
+ if (w == NULL || w[0] == '\n' || *endnum != '\0') {
+ xpect("<tab points value>", w);
+ goto abandon;
+ }
+
+ w = getwd(f);
+ if (w == NULL || w[0] == '\n')
+ goto done_tab;
+ if (strcasecmp(w, "samples") != 0) {
+ xpect("<tab samples>", w);
+ goto abandon;
+ }
+ w = getwd(f);
+ if (w)
+ samples = (uint)strtoul(w, &endnum, 0);
+ if (w == NULL || w[0] == '\n' || *endnum != '\0') {
+ xpect("<tab samples value>", w);
+ goto abandon;
+ }
+
+done_tab:
+ tab = pmchart->activeTab();
+ bool isArchive = tab->isArchiveSource();
+
+ if (host != QString::null) {
+ if (isArchive)
+ archiveGroup->use(PM_CONTEXT_ARCHIVE, host);
+ else
+ liveGroup->use(PM_CONTEXT_HOST, host);
+ }
+
+ if (tab->gadgetCount() == 0) { // edit the initial tab
+ TabWidget *tabWidget = pmchart->tabWidget();
+ tabWidget->setTabText(tabWidget->currentIndex(), label);
+ }
+ else { // create a completely new tab from scratch
+ tab = new Tab;
+ if (isArchive)
+ tab->init(pmchart->tabWidget(), archiveGroup, label);
+ else
+ tab->init(pmchart->tabWidget(), liveGroup, label);
+ pmchart->addActiveTab(tab);
+ }
+ activeGroup->setSampleHistory(samples);
+ activeGroup->setVisibleHistory(points);
+ }
+ else {
+ xpect("chart\", \"global\", \"scheme\" or \"tab", w);
+ goto abandon;
+ }
+ }
+
+ else if (state == S_CHART) {
+ int optional;
+ char *legend = NULL;
+ char *color = NULL;
+ char *host = NULL;
+ int inst_match_type = IM_NONE;
+ int numinst = -1;
+ int nextinst = -1;
+ int *instlist = NULL;
+ char **namelist = NULL;
+ pmMetricSpec pms;
+ int abort = 1; // default @ skip
+
+ memset(&pms, 0, sizeof(pms));
+ if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) {
+ // new tab
+ state = S_TOP;
+ goto new_tab;
+ }
+ if (strcasecmp(w, "chart") == 0) {
+ // new chart
+ state = S_TOP;
+ goto new_chart;
+ }
+ if (strcasecmp(w, "plot") == 0) {
+ optional = 0;
+ }
+ else if (strcasecmp(w, "optional-plot") == 0) {
+ optional = 1;
+ }
+ else {
+ xpect("plot\" or \"optional-plot", w);
+ goto skip;
+ }
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("title\" or \"color", w);
+ goto skip;
+ }
+ if (strcasecmp(w, "title") == 0 ||
+ (mode == M_KMCHART && strcasecmp(w, "legend") == 0)) {
+ // optional title "<title>" or
+ // (for new pmchart) legend "<title>"
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<legend title>", w);
+ goto skip;
+ }
+ if ((legend = strdup(w)) == NULL) nomem();
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("color", w);
+ goto skip;
+ }
+ }
+ // color <color> is mandatory for pmchart, optional for
+ // new pmchart (where the default is color #-cycle)
+ if (strcasecmp(w, "color") == 0 || strcasecmp(w, "colour") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<color>", w);
+ goto skip;
+ }
+ if ((color = strdup(w)) == NULL) nomem();
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("host", w);
+ goto skip;
+ }
+ }
+ else if (mode == M_PMCHART) {
+ xpect("color", w);
+ goto skip;
+ }
+ // host <host> is mandatory for pmchart, optional for
+ // new pmchart (where the default is host *)
+ if (strcasecmp(w, "host") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<host>", w);
+ goto skip;
+ }
+ if (strcmp(w, "*") == 0)
+ host = NULL; // just like the new pmchart default
+ else {
+ if ((host = strdup(w)) == NULL) nomem();
+ }
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("metric", w);
+ goto skip;
+ }
+ }
+ else if (mode == M_PMCHART) {
+ xpect("host", w);
+ goto skip;
+ }
+ // metric is mandatory
+ if (strcasecmp(w, "metric") == 0) {
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<metric>", w);
+ goto skip;
+ }
+ if ((pms.metric = strdup(w)) == NULL) nomem();
+ }
+ else {
+ xpect("metric", w);
+ goto skip;
+ }
+ pms.ninst = 0;
+ pms.inst[0] = NULL;
+ if ((w = getwd(f)) != NULL && w[0] != '\n') {
+ // optional parts
+ // instance ...
+ // matching ...
+ // not-matching ...
+ if (strcasecmp(w, "instance") == 0) {
+ inst_match_type = IM_INST;
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<instance>", w);
+ goto skip;
+ }
+ pms.ninst = 1;
+ if ((pms.inst[0] = strdup(w)) == NULL) nomem();
+ }
+ else if (strcasecmp(w, "matching") == 0) {
+ inst_match_type = IM_MATCH;
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<pattern>", w);
+ goto skip;
+ }
+ pms.ninst = 1;
+ pms.inst[0] = strdup(w);
+ }
+ else if (strcasecmp(w, "not-matching") == 0) {
+ inst_match_type = IM_NOT_MATCH;
+ if ((w = getwd(f)) == NULL || w[0] == '\n') {
+ xpect("<pattern>", w);
+ goto skip;
+ }
+ pms.ninst = 1;
+ pms.inst[0] = strdup(w);
+ }
+ else {
+ xpect("instance\" or \"matching\" or \"not-matching", w);
+ goto skip;
+ }
+ if (mode == M_PMCHART) {
+ // pmchart has this lame "instance extends to end
+ // of line" syntax ... sigh
+ while ((w = getwd(f)) != NULL && w[0] != '\n') {
+ pms.inst[0] = (char *)realloc(pms.inst[0], strlen(pms.inst[0]) + strlen(w) + 2);
+ if (pms.inst[0] == NULL) nomem();
+ // if more than one space in the input, touch luck!
+ strcat(pms.inst[0], " ");
+ strcat(pms.inst[0], w);
+ }
+ if (pms.inst[0] != NULL) {
+ pms.ninst = 1;
+ }
+ }
+ else {
+ // expect end of line after instance/pattern
+ // (pmchart uses quotes to make instance a single
+ // lexical element in the line)
+ eol(f);
+ }
+ }
+
+ abort = 0;
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "openView: new %s", optional ? "optional-plot" : "plot");
+ if (legend != NULL) fprintf(stderr, " legend=\"%s\"", legend);
+ if (color != NULL) fprintf(stderr, " color=%s", color);
+ if (host != NULL) fprintf(stderr, " host=%s", host);
+ fprintf(stderr, " metric=%s", pms.metric);
+ if (pms.ninst == 1) {
+ fprintf(stderr, " inst=%s", pms.inst[0]);
+ if (inst_match_type == IM_NONE)
+ fprintf(stderr, " match=none (botch?)");
+ else if (inst_match_type == IM_INST)
+ fprintf(stderr, " match=instance");
+ else if (inst_match_type == IM_MATCH)
+ fprintf(stderr, " match=matching");
+ else if (inst_match_type == IM_NOT_MATCH)
+ fprintf(stderr, " match=not-matching");
+ }
+ fputc('\n', stderr);
+ }
+ if (Cflag == 0 || Cflag == 2) {
+ pms.isarch = (activeGroup == archiveGroup);
+ if (host != NULL) {
+ // host literal, add to the list of sources
+ if (activeGroup == archiveGroup) {
+ QString hostname = host;
+ if (archiveGroup->use(PM_CONTEXT_HOST, hostname) < 0) {
+ QString msg;
+ msg.sprintf("\nHost \"%s\" cannot be matched to an open archive for metric %s",
+ host, pms.metric);
+ errmsg.append(msg);
+ goto skip;
+ }
+ QmcSource source = archiveGroup->context()->source();
+ pms.source = strdup((const char *)
+ source.source().toAscii());
+ }
+ else {
+ pms.source = strdup(host);
+ }
+ }
+ else {
+ // no explicit host, use current default source
+ QmcSource source = activeGroup->context()->source();
+ pms.source = strdup((const char *)
+ source.source().toAscii());
+ }
+ // expand instances when not specified for metrics
+ // with instance domains and all instances required,
+ // or matching or not-matching instances required
+ //
+ if (inst_match_type != IM_INST) {
+ pmID pmid;
+ pmDesc desc;
+
+ // if pmLookupName() or pmLookupDesc() fail, we'll
+ // notice in addPlot() and report the error below,
+ // so no need to do anything special here
+ //
+ if (pmLookupName(1, &pms.metric, &pmid) < 0)
+ goto try_plot;
+ if (pmLookupDesc(pmid, &desc) < 0)
+ goto try_plot;
+ if (desc.indom == PM_INDOM_NULL) {
+ if (inst_match_type == IM_MATCH ||
+ inst_match_type == IM_NOT_MATCH) {
+ // a bit embarrassing
+ QString msg = QString();
+ msg.sprintf("\nMetric \"%s\" for\n%s %s: no instance domain, cannot handle matching specification",
+ pms.metric, pms.isarch ? "archive" : "host",
+ pms.source);
+ errmsg.append(msg);
+ goto skip;
+ }
+ goto try_plot;
+ }
+
+ if (pms.isarch)
+ numinst = pmGetInDomArchive(desc.indom, &instlist, &namelist);
+ else
+ numinst = pmGetInDom(desc.indom, &instlist, &namelist);
+ if (numinst < 0) {
+ QString msg = QString();
+ msg.sprintf("\nMetric \"%s\" for\n%s %s: empty instance domain",
+ pms.metric, pms.isarch ? "archive" : "host",
+ pms.source);
+ errmsg.append(msg);
+ goto skip;
+ }
+ if (inst_match_type != IM_NONE) {
+ regex.setPattern(pms.inst[0]);
+ if (!regex.isValid()) {
+ errmsg = "Invalid regular expression:\n ";
+ errmsg.append(pms.inst[0]);
+ errmsg.append("\n\n");
+ errmsg.append(regex.errorString());
+ goto skip;
+ }
+ }
+ pms.ninst = numinst ? 1 : 0;
+ if (pms.inst[0] != NULL) {
+ free(pms.inst[0]);
+ pms.inst[0] = NULL;
+ }
+ }
+
+try_plot:
+ if (numinst > 0) {
+ pms.inst[0] = NULL;
+ for (nextinst++ ; nextinst < numinst; nextinst++) {
+ sts = 0;
+ if (inst_match_type == IM_MATCH ||
+ inst_match_type == IM_NOT_MATCH) {
+ sts = regex.indexIn(QString(namelist[nextinst]));
+ }
+ switch (inst_match_type) {
+ case IM_MATCH:
+ if (sts != -1)
+ pms.inst[0] = namelist[nextinst];
+ break;
+ case IM_NOT_MATCH:
+ if (sts == -1)
+ pms.inst[0] = namelist[nextinst];
+ break;
+ case IM_NONE:
+ pms.inst[0] = namelist[nextinst];
+ break;
+ }
+ if (pms.inst[0] != NULL)
+ break;
+ }
+ if (nextinst == numinst)
+ goto skip;
+ }
+ m = cp->addItem(&pms, QString(legend));
+ if (m < 0) {
+ if (!optional) {
+ QString msg;
+ if (pms.inst[0] != NULL)
+ msg.sprintf("\nFailed to plot metric \"%s[%s]\" for\n%s %s:\n",
+ pms.metric, pms.inst[0],
+ pms.isarch ? "archive" : "host",
+ pms.source);
+ else
+ msg.sprintf("\nFailed to plot metric \"%s\" for\n%s %s:\n",
+ pms.metric, pms.isarch ? "archive" : "host",
+ pms.source);
+ if (m == PM_ERR_CONV) {
+ msg.append("Units for this metric are not compatible with other plots in this chart");
+ }
+ else
+ msg.append(pmErrStr(m));
+ errmsg.append(msg);
+ }
+ }
+ else if (color != NULL && strcmp(color, "#-cycle") != 0) {
+ int seq = cp->sequence();
+ cp->setStroke(m, cp->style(), colorSpec(color, &seq));
+ cp->setSequence(seq);
+ }
+ if (numinst > 0)
+ // more instances still to be processed for this metric
+ goto try_plot;
+
+ }
+
+skip:
+ if (legend != NULL) free(legend);
+ if (color != NULL) free(color);
+ if (host != NULL) free(host);
+ if (instlist != NULL) free(instlist);
+ if (namelist != NULL) free(namelist);
+ if (pms.source != NULL) free(pms.source);
+ if (pms.metric != NULL) free(pms.metric);
+ if (pms.inst[0] != NULL) free(pms.inst[0]);
+
+ if (abort)
+ goto abandon;
+
+ continue;
+ }
+
+ else {
+ QString msg = QString();
+ msg.sprintf("Botch, state=%d", state);
+ err(E_CRIT, true, msg);
+ goto abandon;
+ }
+
+ continue;
+
+abandon:
+ // giving up on the whole view specification
+ break;
+
+ }
+
+ if (!errmsg.isEmpty()) {
+ err(E_CRIT, true, errmsg);
+ }
+
+ if (f != stdin) {
+ if (is_popen)
+ pclose(f);
+ else
+ fclose(f);
+ }
+
+ if (_errors)
+ return false;
+
+ if (ct != pmchart->tabWidget()->currentIndex()) // new Tabs added
+ pmchart->setActiveTab(ct, true);
+
+ if ((Cflag == 0 || Cflag == 2) && cp != NULL) {
+ activeGroup->setupWorldView();
+ pmchart->activeTab()->showGadgets();
+ }
+ return true;
+
+noview:
+ errmsg = QString("Cannot open view file \"");
+ errmsg.append(_fname);
+ errmsg.append("\"\n");
+ errmsg.append(strerror(errno));
+ err(E_CRIT, false, errmsg);
+ return false;
+
+nopipe:
+ errmsg.sprintf("Cannot execute \"%s\"\n%s", _fname, strerror(errno));
+ err(E_CRIT, false, errmsg);
+ return false;
+}
+
+void SaveViewDialog::setGlobals(int width, int height, int points, int x, int y)
+{
+ _width = width;
+ _height = height;
+ _points = points;
+ _xpos = x;
+ _ypos = y;
+}
+
+static void saveScheme(FILE *f, QString scheme)
+{
+ ColorScheme *cs = ColorScheme::findScheme(scheme);
+ int m;
+
+ if (cs) {
+ fprintf(f, "scheme %s", (const char *)cs->name().toAscii());
+ for (m = 0; m < cs->size(); m++)
+ fprintf(f, " %s", (const char *)cs->colorName(m).toAscii());
+ fprintf(f, "\n\n");
+ }
+}
+
+void SaveViewDialog::saveChart(FILE *f, Chart *cp, bool hostDynamic)
+{
+ const char *s;
+ double ymin, ymax;
+ bool autoscale;
+
+ fprintf(f, "chart");
+ if (cp->title() != QString::null)
+ fprintf(f, " title \"%s\"", (const char*)cp->title().toAscii());
+ switch (cp->style()) {
+ case Chart::LineStyle:
+ s = "plot";
+ break;
+ case Chart::BarStyle:
+ s = "bar";
+ break;
+ case Chart::StackStyle:
+ s ="stacking";
+ break;
+ case Chart::AreaStyle:
+ s = "area";
+ break;
+ case Chart::UtilisationStyle:
+ s = "utilization";
+ break;
+ case Chart::EventStyle:
+ s = "event";
+ break;
+ case Chart::NoStyle:
+ default:
+ s = "none";
+ break;
+ }
+ fprintf(f, " style %s", s);
+ if (cp->style() != Chart::UtilisationStyle) {
+ cp->scale(&autoscale, &ymin, &ymax);
+ if (!autoscale)
+ fprintf(f, " scale %f %f", ymin, ymax);
+ }
+ if (!cp->legendVisible())
+ fprintf(f, " legend off");
+ if (!cp->antiAliasing())
+ fprintf(f, " antialiasing off");
+ fputc('\n', f);
+ for (int m = 0; m < cp->metricCount(); m++) {
+ QString legend;
+ if (cp->activeItem(m) == false)
+ continue;
+ fprintf(f, "\tplot");
+ legend = cp->legend(m);
+ if (legend != QString::null)
+ fprintf(f, " legend \"%s\"", (const char *)legend.toAscii());
+ fprintf(f, " color %s", (const char *)cp->color(m).name().toAscii());
+ if (hostDynamic == false)
+ fprintf(f, " host %s", (const char *)
+ cp->metricContext(m)->source().host().toAscii());
+ fprintf(f, " metric %s", (const char *)
+ cp->metricName(m).toAscii());
+ if (cp->metric(m)->explicitInsts())
+ fprintf(f, " instance \"%s\"", (const char*)cp->metricInstance(m).toAscii());
+ fputc('\n', f);
+ }
+}
+
+bool SaveViewDialog::saveView(QString file, bool hostDynamic,
+ bool sizeDynamic, bool allTabs, bool allCharts)
+{
+ FILE *f;
+ int c, t;
+ Tab *tab;
+ Gadget *gadget;
+ char *path = strdup((const char *)file.toAscii());
+ QStringList schemes;
+
+ if ((f = fopen(path, "w")) == NULL)
+ goto noview;
+
+ fprintf(f, "#kmchart\nversion %d\n\n", K1);
+ if (sizeDynamic == false) {
+ fprintf(f, "global width %u\n", _width);
+ fprintf(f, "global height %u\n", _height);
+ fprintf(f, "global points %u\n", _points);
+ fprintf(f, "global xpos %u\n", _xpos);
+ fprintf(f, "global ypos %u\n", _ypos);
+ fprintf(f, "\n");
+ }
+
+ for (c = 0; c < pmchart->activeTab()->gadgetCount(); c++) {
+ gadget = pmchart->activeTab()->gadget(c);
+ if (gadget->scheme() == QString::null ||
+ schemes.contains(gadget->scheme()) == true)
+ continue;
+ schemes.append(gadget->scheme());
+ }
+ for (c = 0; c < schemes.size(); c++)
+ saveScheme(f, schemes.at(c));
+
+ if (allTabs) {
+ TabWidget *tabWidget = pmchart->tabWidget();
+ for (t = 0; t < tabWidget->size(); t++) {
+ tab = tabWidget->at(t);
+ fprintf(f, "\ntab \"%s\"\n\n",
+ (const char *) tabWidget->tabText(t).toAscii());
+ for (c = 0; c < tab->gadgetCount(); c++)
+ tab->gadget(c)->save(f, hostDynamic);
+ }
+ }
+ else {
+ tab = pmchart->activeTab();
+ if (!allCharts)
+ tab->currentGadget()->save(f, hostDynamic);
+ else
+ for (c = 0; c < tab->gadgetCount(); c++)
+ tab->gadget(c)->save(f, hostDynamic);
+ }
+
+ fflush(f);
+ fclose(f);
+ free (path);
+ return true;
+
+noview:
+ QString errmsg;
+ errmsg.sprintf("Cannot open \"%s\" for writing\n%s", path, strerror(errno));
+ err(E_CRIT, false, errmsg);
+ free (path);
+ return false;
+}
diff --git a/src/pmchart/views/Apache b/src/pmchart/views/Apache
new file mode 100644
index 0000000..5df9062
--- /dev/null
+++ b/src/pmchart/views/Apache
@@ -0,0 +1,8 @@
+#kmchart
+version 1
+
+chart title "Apache Hit Rate [%h]" style plot legend off
+ plot metric apache.total_accesses
+
+chart title "Apache Data Rate [%h]" style plot legend off
+ plot metric apache.total_kbytes
diff --git a/src/pmchart/views/ApacheServer b/src/pmchart/views/ApacheServer
new file mode 100644
index 0000000..10b01b9
--- /dev/null
+++ b/src/pmchart/views/ApacheServer
@@ -0,0 +1,38 @@
+#kmchart
+version 1
+
+# CPU view
+chart title "CPU Utilization [%h]" style utilization
+ plot legend "User" color #2d2de2 metric kernel.all.cpu.user
+ plot legend "Sys" color #e71717 metric kernel.all.cpu.sys
+ optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice
+ optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr
+ optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total
+ optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal
+ plot legend "Idle" color #16d816 metric kernel.all.cpu.idle
+
+chart title "Average Load [%h]" style plot antialiasing off
+ plot legend "1 min" metric kernel.all.load instance "1 minute"
+ plot legend "# cpus" metric hinv.ncpu
+
+# Netbytes view
+chart title "Network Interface Bytes [%h]" style stacking
+ plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface"
+ plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface"
+
+chart title "Busy/Idle servers" style plot antialiasing off
+ plot legend "Busy" metric apache.busy_servers
+ plot legend "Idle" metric apache.idle_servers
+
+chart style stacking
+ plot legend "Closing" color #ffff00 metric apache.sb_closing
+ plot legend "DNS" color #0000ff metric apache.sb_dns_lookup
+ plot legend "Finishing" color #ff0000 metric apache.sb_finishing
+ plot legend "Cleanup" color #008000 metric apache.sb_idle_cleanup
+ plot legend "Keepalive" color #ee82ee metric apache.sb_keepalive
+ plot legend "Logging" color #aa5500 metric apache.sb_logging
+ plot legend "Open" color #666666 metric apache.sb_open_slot
+ plot legend "Reading" color #aaff00 metric apache.sb_reading
+ plot legend "Starting" color #aa00ff metric apache.sb_starting
+ plot legend "Waiting" color #aaaa7f metric apache.sb_waiting
+ plot legend "Writing" color #ffff00 metric apache.sb_writing_reply
diff --git a/src/pmchart/views/BusyCPU b/src/pmchart/views/BusyCPU
new file mode 100755
index 0000000..90903da
--- /dev/null
+++ b/src/pmchart/views/BusyCPU
@@ -0,0 +1,164 @@
+#!/bin/sh
+#
+# Dynamic kmchart view for the most busy current processes ... note
+# this is a snapshot at the time the view is instantiated and does
+# not track the busiest processes over time
+#
+# Busy here means CPU consumption
+#
+
+# a token attempt to make this general
+. /etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+# bad stuff ...
+#
+_err()
+{
+ echo "Failed to fetch user mode CPU cycles metrics" >$tmp/msg
+ echo >>$tmp/msg
+ for f in $tmp/err.*
+ do
+ cat $f >>$tmp/msg
+ done
+ echo >>$tmp/msg
+ echo "Sorry, there is nothing to display." >>$tmp/msg
+ pmconfirm >/dev/null \
+ -header "pmchart view construction failure" \
+ -file $tmp/msg \
+ -icon error \
+ -B "OK"
+ exit
+}
+
+# find top 10 consumers of user mode CPU cycles and generate a plot for each
+#
+# top(1) is ill-suited for use in a script, so we have to emulate this
+# using PCP tools as follows:
+# 1. get cummulative user mode CPU cycles for all processes
+# 2. sleep 5 seconds
+# 3. get cummulative user mode CPU cycles for all processes
+# 4. compute delta user mode CPU cycles from 1. and 2.
+# 5. sort and select top 10 processes
+#
+
+# use $tmp/sed to remove this shell and its children from
+# the list of busy processses
+#
+echo "/\[0*$$ /d" >$tmp/sed
+
+# progress notifier while we do our job
+#
+pmquery >/dev/null 2>&1 \
+ -timeout 5 \
+ -header "kmchart view construction" \
+ -t "Finding top CPU burners, please wait 5 seconds ..." &
+query=$!
+echo "/\[0*$query /d" >>$tmp/sed
+
+# deal with alternative metric names ...
+#
+i=0
+fail=true
+for metric in proc.psinfo.utime proc.psusage.utime
+do
+ nval=`pmprobe -if $* $metric 2>&1 \
+ | tee $tmp/err.$i \
+ | $PCP_AWK_PROG '{print $2}'`
+ if [ "$nval" -gt 0 ]
+ then
+ fail=false
+ break
+ fi
+ i=`expr $i + 1`
+done
+if $fail
+then
+ _err
+ #NOTREACHED
+fi
+
+# note arguments from pmchart are nothing or -h host or -a archive
+#
+if pminfo -F $* $metric >$tmp/1 2>$tmp/err.a
+then
+ sleep 5
+ if pminfo -F $* $metric >$tmp/2 2>$tmp/err.b
+ then
+ :
+ else
+ _err
+ #NOTREACHED
+ fi
+else
+ _err
+ #NOTREACHED
+fi
+
+# get pid and user mode CPU cycles usage from lines like
+# inst [8072 or "08072 rlogin gonzo.melbourne "] value 28
+# and turn them into this
+# $tmp/1.list
+# 8072 28
+# $tmp/2.list
+# 8072 28 08072 rlogin gonzo.melbourne
+#
+
+sed -f $tmp/sed $tmp/1 \
+| sed -n -e '/inst \[/{
+s/.*inst \[//
+s/ or .* \([0-9][0-9]*\)$/ \1/p
+}' \
+| sort >$tmp/1.list
+
+sed -n -e '/inst \[/{
+s/.*inst \[//
+s/or "\(.*\)".* \([0-9][0-9]*\)$/\2 \1/p
+}' $tmp/2 \
+| sort >$tmp/2.list
+
+#DEBUG# echo "First list ..." >/tmp/busy.debug
+#DEBUG# cat $tmp/1.list >>/tmp/busy.debug
+#DEBUG# echo "Second list ..." >>/tmp/busy.debug
+#DEBUG# cat $tmp/2.list >>/tmp/busy.debug
+
+join $tmp/1.list $tmp/2.list \
+| $PCP_AWK_PROG '
+$3 > $2 { # this process has consumed some user mode CPU cycles
+ printf "%d",$3-$2
+ for (i = 4; i <= NF; i++) printf " %s",$i
+ print""
+ }' >$tmp/found
+
+if [ ! -s $tmp/found ]
+then
+ # no processes qualify
+ #
+ pmconfirm >/dev/null \
+ -header "pmchart view construction failure" \
+ -t "No qualifying processes were found!" \
+ -icon warning \
+ -B "OK"
+ exit
+fi
+
+# chart preamble
+#
+cat <<End-of-File
+#pmchart
+Version 2.0 host dynamic
+
+Chart Title "Top user mode CPU burners at `date +'%a %b %e %R'`" Style stacking
+End-of-File
+
+# plot specifications, one per process
+#
+
+sort -nr +0 -1 $tmp/found \
+| sed 10q \
+| while read cpuburn inst
+do
+ echo " Plot Color #-cycle Host * Metric $metric Instance $inst"
+done
diff --git a/src/pmchart/views/CPU b/src/pmchart/views/CPU
new file mode 100644
index 0000000..8724505
--- /dev/null
+++ b/src/pmchart/views/CPU
@@ -0,0 +1,11 @@
+#kmchart
+version 1
+
+chart title "CPU Utilization [%h]" style utilization
+ plot legend "User" color #2d2de2 metric kernel.all.cpu.user
+ plot legend "Kernel" color #e71717 metric kernel.all.cpu.sys
+ optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice
+ optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr
+ optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total
+ optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal
+ plot legend "Idle" color #16d816 metric kernel.all.cpu.idle
diff --git a/src/pmchart/views/Cisco b/src/pmchart/views/Cisco
new file mode 100644
index 0000000..fae3416
--- /dev/null
+++ b/src/pmchart/views/Cisco
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Cisco Data Rate [%h]" style plot
+ plot legend "In %i" metric cisco.rate_in
+ plot legend "Out %i" metric cisco.rate_out
diff --git a/src/pmchart/views/Disk b/src/pmchart/views/Disk
new file mode 100644
index 0000000..a6da4d1
--- /dev/null
+++ b/src/pmchart/views/Disk
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "IOPS over all Disks [%h]" style stacking
+ plot legend "Reads" color yellow metric disk.all.read
+ plot legend "Writes" color violet metric disk.all.write
diff --git a/src/pmchart/views/Diskbytes b/src/pmchart/views/Diskbytes
new file mode 100644
index 0000000..4dcadbe
--- /dev/null
+++ b/src/pmchart/views/Diskbytes
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Disk Throughput [%h]" style stacking
+ plot legend "Read rate" color yellow metric disk.all.read_bytes
+ plot legend "Write rate" color violet metric disk.all.write_bytes
diff --git a/src/pmchart/views/ElasticsearchServer b/src/pmchart/views/ElasticsearchServer
new file mode 100644
index 0000000..4372c8a
--- /dev/null
+++ b/src/pmchart/views/ElasticsearchServer
@@ -0,0 +1,29 @@
+#kmchart
+version 1
+
+# CPU view
+chart title "CPU Utilization [%h]" style utilization
+ plot legend "User" color #2d2de2 metric kernel.all.cpu.user
+ plot legend "Sys" color #e71717 metric kernel.all.cpu.sys
+ optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice
+ optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr
+ optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total
+ optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal
+ plot legend "Idle" color #16d816 metric kernel.all.cpu.idle
+
+chart title "Average Load [%h]" style plot antialiasing off
+ plot legend "1 min" metric kernel.all.load instance "1 minute"
+ plot legend "# cpus" metric hinv.ncpu
+
+# Query/Fetch per second
+chart title "Searches" style plot antialiasing off
+ plot legend "Query" metric elasticsearch.search.all.total.search.query_total
+ plot legend "Fetch" metric elasticsearch.search.all.total.search.fetch_total
+
+# CPU usage per node
+chart title "CPU %" style plot antialiasing off
+ plot legend "cpu %i" metric elasticsearch.nodes.process.cpu.percent
+
+# Doc count per node
+chart title "Document" style plot antialiasing off
+ plot legend "docs %i" metric elasticsearch.nodes.indices.docs.count \ No newline at end of file
diff --git a/src/pmchart/views/Filesystem b/src/pmchart/views/Filesystem
new file mode 100644
index 0000000..34c587c
--- /dev/null
+++ b/src/pmchart/views/Filesystem
@@ -0,0 +1,5 @@
+#kmchart
+version 1
+
+chart title "File System Fullness % [%h]" style plot
+ plot legend "%i" metric filesys.full not-matching "/dev/shm|/dev$"
diff --git a/src/pmchart/views/GNUmakefile b/src/pmchart/views/GNUmakefile
new file mode 100644
index 0000000..1d6f3f3
--- /dev/null
+++ b/src/pmchart/views/GNUmakefile
@@ -0,0 +1,21 @@
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+VIEWDIR = $(PCP_VAR_DIR)/config/pmchart
+VIEWS = CPU Disk Diskbytes Loadavg NFS2 NFS3 Filesystem Memory Netbytes \
+ Netpackets PMCD Syscalls Paging Overview Schemes Sockets Swap \
+ ApacheServer ElasticsearchServer vCPU MemAvailable
+
+LSRCFILES = $(VIEWS)
+
+default build-me:
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(VIEWDIR)
+ $(INSTALL) -m 0444 $(VIEWS) $(VIEWDIR)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmchart/views/Loadavg b/src/pmchart/views/Loadavg
new file mode 100644
index 0000000..8655205
--- /dev/null
+++ b/src/pmchart/views/Loadavg
@@ -0,0 +1,7 @@
+#kmchart
+version 1
+
+chart title "Load Average [%h]" style plot antialiasing off
+ plot title "1 min" metric kernel.all.load instance "1 minute"
+ plot title "5 min" metric kernel.all.load instance "5 minute"
+ plot title "15 min"metric kernel.all.load instance "15 minute"
diff --git a/src/pmchart/views/MemAvailable b/src/pmchart/views/MemAvailable
new file mode 100644
index 0000000..718a95b
--- /dev/null
+++ b/src/pmchart/views/MemAvailable
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Memory Available [%h]" style area
+ optional-plot color #00ff00 metric mem.util.available
+ plot color #3549ff metric mem.physmem
diff --git a/src/pmchart/views/Memory b/src/pmchart/views/Memory
new file mode 100644
index 0000000..3c54ad6
--- /dev/null
+++ b/src/pmchart/views/Memory
@@ -0,0 +1,10 @@
+#kmchart
+version 1
+
+chart title "Real Memory Usage [%h]" style stacking
+ # Linux
+ optional-plot color #9cffab metric mem.util.cached
+ optional-plot color #fe68ad metric mem.util.bufmem
+ optional-plot color #ffae2c metric mem.util.other
+ # all
+ plot color #00ff00 metric mem.util.free
diff --git a/src/pmchart/views/NFS2 b/src/pmchart/views/NFS2
new file mode 100644
index 0000000..1d442a7
--- /dev/null
+++ b/src/pmchart/views/NFS2
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "NFS2 Calls [%h]" style plot
+ plot legend "client" metric nfs.client.calls
+ plot legend "server" metric nfs.server.calls
diff --git a/src/pmchart/views/NFS3 b/src/pmchart/views/NFS3
new file mode 100644
index 0000000..807935e
--- /dev/null
+++ b/src/pmchart/views/NFS3
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "NFS3 Calls [%h]" style plot
+ plot legend "client" metric nfs3.client.calls
+ plot legend "server" metric nfs3.server.calls
diff --git a/src/pmchart/views/Netbytes b/src/pmchart/views/Netbytes
new file mode 100644
index 0000000..451faea
--- /dev/null
+++ b/src/pmchart/views/Netbytes
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Network Interface Bytes [%h]" style stacking
+ plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface"
+ plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface"
diff --git a/src/pmchart/views/Netpackets b/src/pmchart/views/Netpackets
new file mode 100644
index 0000000..a492b55
--- /dev/null
+++ b/src/pmchart/views/Netpackets
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Network Interface Packets [%h]" style stacking
+ plot legend "in %i" metric network.interface.in.packets not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface"
+ plot legend "out %i" metric network.interface.out.packets not-matching "^lo|^gif|^sl|^sit|^stf|^tun|^wlt|^virbr|^vnet|^vmnet|^MS TCP Loopback interface"
diff --git a/src/pmchart/views/Overview b/src/pmchart/views/Overview
new file mode 100644
index 0000000..a70ed40
--- /dev/null
+++ b/src/pmchart/views/Overview
@@ -0,0 +1,32 @@
+#kmchart
+version 1
+
+# CPU view
+chart title "CPU Utilization [%h]" style utilization
+ plot legend "User" color #2d2de2 metric kernel.all.cpu.user
+ plot legend "Sys" color #e71717 metric kernel.all.cpu.sys
+ optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice
+ optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr
+ optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total
+ optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal
+ plot legend "Idle" color #16d816 metric kernel.all.cpu.idle
+
+chart title "Average Load [%h]" style plot antialiasing off
+ plot legend "1 min" metric kernel.all.load instance "1 minute"
+ plot legend "# cpus" metric hinv.ncpu
+
+# Disk view
+chart title "IOPS over all Disks [%h]" style stacking
+ plot legend "Reads" color yellow metric disk.all.read
+ plot legend "Writes" color violet metric disk.all.write
+
+# Netbytes view
+chart title "Network Interface Bytes [%h]" style stacking
+ plot legend "in %i" metric network.interface.in.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface"
+ plot legend "out %i" metric network.interface.out.bytes not-matching "^lo|^sl|^ppp|^sit|^gif|^stf|^wlt|^vmnet|^MS TCP Loopback interface"
+
+chart title "Real Memory Usage [%h]" style stacking
+ optional-plot color #9cffab metric mem.util.cached
+ optional-plot color #fe68ad metric mem.util.bufmem
+ optional-plot color #ffae2c metric mem.util.other
+ plot color #00ff00 metric mem.util.free
diff --git a/src/pmchart/views/PMCD b/src/pmchart/views/PMCD
new file mode 100644
index 0000000..bd82c56
--- /dev/null
+++ b/src/pmchart/views/PMCD
@@ -0,0 +1,14 @@
+#kmchart
+version 1
+
+chart title "Packets for PMCD [%h]" style stacking
+ plot legend "In" metric pmcd.pdu_in.total
+ plot legend "Out" metric pmcd.pdu_out.total
+
+chart title "CPU Time for PMCD and DSO PMDAs [%h]" style stacking
+ optional-plot legend "User" color #2d2de2 metric proc.psinfo.utime matching "[\\/](pmcd |pmcd$)"
+ optional-plot legend "Sys" color #e71717 metric proc.psinfo.stime matching "[\\/](pmcd |pmcd$)"
+
+chart title "CPU Time for Other PMDAs [%h]" style stacking legend off
+ optional-plot color #2d2de2 metric proc.psinfo.utime matching "[\\/]pmda[a-z]"
+ optional-plot color #e71717 metric proc.psinfo.stime matching "[\\/]pmda[a-z]"
diff --git a/src/pmchart/views/Paging b/src/pmchart/views/Paging
new file mode 100644
index 0000000..39a397c
--- /dev/null
+++ b/src/pmchart/views/Paging
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "VM Activity - Page Migrations [%h]" style plot
+ plot legend "Out" metric swap.pagesout
+ plot legend "In" metric swap.pagesin
diff --git a/src/pmchart/views/Schemes b/src/pmchart/views/Schemes
new file mode 100644
index 0000000..b4c98d3
--- /dev/null
+++ b/src/pmchart/views/Schemes
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+scheme Fire #f2b600 #ede200 #d21005 #e4d904 #f35241
+scheme Nature #b4ff3a #d2954e #069806 #8b5f07 #08ed00
+scheme Neon #ff34f9 #fffb00 #1df0ff #12ff01 #ffc800
+scheme Ocean #f5dd85 #aaf5cb #4790f5 #bdcfcc #12cfa0
diff --git a/src/pmchart/views/Sendmail b/src/pmchart/views/Sendmail
new file mode 100644
index 0000000..faf1f59
--- /dev/null
+++ b/src/pmchart/views/Sendmail
@@ -0,0 +1,10 @@
+#kmchart
+version 1
+
+chart title "Sendmail Bytes [%h]" style plot
+ plot legend "Recv" color #137bfe metric sendmail.total.bytes_from
+ plot legend "Sent" color #fefa1a metric sendmail.total.bytes_to
+
+chart title "Sendmail Mail Items [%h]" style plot
+ plot legend "Recv" color #1e1cfe metric sendmail.total.msgs_from
+ plot legend "Sent" color #fe9913 metric sendmail.total.msgs_to
diff --git a/src/pmchart/views/ShpingCPU b/src/pmchart/views/ShpingCPU
new file mode 100644
index 0000000..a39e808
--- /dev/null
+++ b/src/pmchart/views/ShpingCPU
@@ -0,0 +1,8 @@
+#kmchart
+version 1
+
+chart title "User CPU Time for shping Commands [%h]" style plot
+ plot legend "%i" metric shping.time.cpu_usr
+
+chart title "System CPU Time for shping Commands [%h]" style plot
+ plot legend "%i" metric shping.time.cpu_sys
diff --git a/src/pmchart/views/ShpingElapsed b/src/pmchart/views/ShpingElapsed
new file mode 100644
index 0000000..b83d3c0
--- /dev/null
+++ b/src/pmchart/views/ShpingElapsed
@@ -0,0 +1,5 @@
+#kmchart
+version 1
+
+chart title "shping Response Time [%h]" style plot
+ plot legend "%i" metric shping.time.real
diff --git a/src/pmchart/views/Sockets b/src/pmchart/views/Sockets
new file mode 100644
index 0000000..177b3aa
--- /dev/null
+++ b/src/pmchart/views/Sockets
@@ -0,0 +1,7 @@
+#kmchart
+version 1
+
+chart title "Sockets in Use [%h]" style plot
+ optional-plot legend "TCP" metric network.sockstat.tcp.inuse
+ optional-plot legend "UDP" metric network.sockstat.udp.inuse
+ optional-plot legend "Raw" metric network.sockstat.raw.inuse
diff --git a/src/pmchart/views/Swap b/src/pmchart/views/Swap
new file mode 100644
index 0000000..542636c
--- /dev/null
+++ b/src/pmchart/views/Swap
@@ -0,0 +1,6 @@
+#kmchart
+version 1
+
+chart title "Logical Swap Allocation [%h]" style stacking
+ optional-plot legend "Free" color #16e116 metric swap.free
+ optional-plot legend "Used" color #e71717 metric swap.used
diff --git a/src/pmchart/views/Syscalls b/src/pmchart/views/Syscalls
new file mode 100644
index 0000000..bd260b3
--- /dev/null
+++ b/src/pmchart/views/Syscalls
@@ -0,0 +1,9 @@
+#kmchart
+version 1
+
+chart title "System Calls [%h]" style plot
+ optional-plot legend "All" metric kernel.all.syscall
+ optional-plot legend "exec" metric kernel.all.sysexec
+ optional-plot legend "fork" metric kernel.all.sysfork
+ optional-plot legend "read" metric kernel.all.sysread
+ optional-plot legend "write" metric kernel.all.syswrite
diff --git a/src/pmchart/views/vCPU b/src/pmchart/views/vCPU
new file mode 100644
index 0000000..a45680f
--- /dev/null
+++ b/src/pmchart/views/vCPU
@@ -0,0 +1,12 @@
+#kmchart
+version 1
+
+chart title "CPU and Guest CPU Utilization [%h]" style utilization
+ plot legend "User" color #2d2de2 metric kernel.all.cpu.vuser
+ plot legend "Kernel" color #e71717 metric kernel.all.cpu.sys
+ optional-plot legend "Guest" color #666666 metric kernel.all.cpu.guest
+ optional-plot legend "Nice" color #c2f3c2 metric kernel.all.cpu.nice
+ optional-plot legend "Intr" color #cdcd00 metric kernel.all.cpu.intr
+ optional-plot legend "Wait" color #00cdcd metric kernel.all.cpu.wait.total
+ optional-plot legend "Steal" color #fba2f5 metric kernel.all.cpu.steal
+ plot legend "Idle" color #16d816 metric kernel.all.cpu.idle
diff --git a/src/pmclient/GNUmakefile b/src/pmclient/GNUmakefile
new file mode 100644
index 0000000..095be2a
--- /dev/null
+++ b/src/pmclient/GNUmakefile
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2013-2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmclient.c
+CMDTARGET = pmclient$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+LDIRT = pmnsmap.h mylog.* runme.sh
+CONFIGS = pmnsmap.spec pmlogger.config
+LSRCFILES = README GNUmakefile.install $(CONFIGS)
+
+DEMODIR = $(PCP_DEMOS_DIR)/pmclient
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -m 755 -d $(DEMODIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(DEMODIR)/Makefile
+ $(INSTALL) -m 644 $(CFILES) $(CONFIGS) README $(DEMODIR)
+
+pmclient.o: pmnsmap.h
+
+pmnsmap.h: pmnsmap.spec
+ sed -e "s;^\. .PCP_DIR.etc.pcp.env;. $(TOPDIR)/src/include/pcp.env;" \
+ ../pmgenmap/pmgenmap.sh >runme.sh; \
+ $(RUN_IN_BUILD_ENV) sh ./runme.sh pmnsmap.spec >pmnsmap.h
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmclient/GNUmakefile.install b/src/pmclient/GNUmakefile.install
new file mode 100644
index 0000000..db72b27
--- /dev/null
+++ b/src/pmclient/GNUmakefile.install
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+CFILES = pmclient.c
+CFLAGS = -I/usr/include/pcp -DPCP_DEBUG=1
+TARGETS = pmclient
+LDOPTS =
+LDLIBS = -lpcp
+
+default: $(TARGETS)
+
+install:
+
+clobber:
+ rm -f $(TARGETS) *.o core a.out pmnsmap.h mylog.* pmlogger.log
+
+pmclient: pmclient.c pmnsmap.h
+ rm -f $@
+ $(CC) $(CFLAGS) pmclient.c -o $@ $(LDOPTS) $(LDLIBS)
+
+pmnsmap.h: pmnsmap.spec
+ pmgenmap pmnsmap.spec >pmnsmap.h
diff --git a/src/pmclient/README b/src/pmclient/README
new file mode 100644
index 0000000..a2d4aab
--- /dev/null
+++ b/src/pmclient/README
@@ -0,0 +1,36 @@
+pmclient - a sample client using the PMAPI
+==========================================
+
+pmclient is a sample client that uses the Performance Metrics
+Application Programming Interface (PMAPI) to report some performance
+data, collected from either a local host, a remote host, or a
+Performance Co-Pilot (PCP) performance metrics archive log.
+
+The binary is shipped as part of pcp and is typically installed in
+/usr/bin/pmclient. A pmclient(1) "man" page is shipped in the pcp
+package also.
+
+The source is shipped as part of the pcp development packages and
+is installed in $PCP_DEMOS_DIR/pmclient. If you have a C compiler
+installed, the source and Makefile in this directory may be used to
+create a functionally equivalent binary, by entering the command
+
+ % make
+
+The source in pmclient.c demonstrates many of the PMAPI services, and
+may be used as a template and style guide when creating your own PMAPI
+clients. Note in particular, the use of ./pmnsmap.spec and the shipped
+tool pmgenmap(1) to assist in the creation of arguments to the PMAPI
+routines, and the manipulation of PMAPI data structures.
+
+To experiment with the archives,
+
+ % rm -f mylog.*
+ % config=$PCP_DEMOS_DIR/pmclient/pmlogger.config
+ % cat $config
+ % pmlogger -c $config -s 6 mylog
+
+this will collect 30 seconds of performance data into the archive
+stored as the files mylog.*. To play this back,
+
+ % pmclient -a mylog
diff --git a/src/pmclient/pmclient.c b/src/pmclient/pmclient.c
new file mode 100644
index 0000000..5abbc95
--- /dev/null
+++ b/src/pmclient/pmclient.c
@@ -0,0 +1,357 @@
+/*
+ * pmclient - sample, simple PMAPI client
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmnsmap.h"
+
+pmLongOptions longopts[] = {
+ PMAPI_GENERAL_OPTIONS,
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "pause", 0, 'P', 0, "pause between updates for archive replay" },
+ PMAPI_OPTIONS_END
+};
+
+pmOptions opts = {
+ .flags = PM_OPTFLAG_STDOUT_TZ,
+ .short_options = PMAPI_OPTIONS "P",
+ .long_options = longopts,
+};
+
+typedef struct {
+ struct timeval timestamp; /* last fetched time */
+ float cpu_util; /* aggregate CPU utilization, usr+sys */
+ int peak_cpu; /* most utilized CPU, if > 1 CPU */
+ float peak_cpu_util; /* utilization for most utilized CPU */
+ float freemem; /* free memory (Mbytes) */
+ unsigned int dkiops; /* aggregate disk I/O's per second */
+ float load1; /* 1 minute load average */
+ float load15; /* 15 minute load average */
+} info_t;
+
+static unsigned int ncpu;
+
+static unsigned int
+get_ncpu(void)
+{
+ /* there is only one metric in the pmclient_init group */
+ pmID pmidlist[1];
+ pmDesc desclist[1];
+ pmResult *rp;
+ pmAtomValue atom;
+ int sts;
+
+ if ((sts = pmLookupName(1, pmclient_init, pmidlist)) < 0) {
+ fprintf(stderr, "%s: pmLookupName: %s\n", pmProgname, pmErrStr(sts));
+ fprintf(stderr, "%s: metric \"%s\" not in name space\n",
+ pmProgname, pmclient_init[0]);
+ exit(1);
+ }
+ if ((sts = pmLookupDesc(pmidlist[0], desclist)) < 0) {
+ fprintf(stderr, "%s: cannot retrieve description for metric \"%s\" (PMID: %s)\nReason: %s\n",
+ pmProgname, pmclient_init[0], pmIDStr(pmidlist[0]), pmErrStr(sts));
+ exit(1);
+ }
+ if ((sts = pmFetch(1, pmidlist, &rp)) < 0) {
+ fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* the thing we want is known to be the first value */
+ pmExtractValue(rp->vset[0]->valfmt, rp->vset[0]->vlist, desclist[0].type,
+ &atom, PM_TYPE_U32);
+ pmFreeResult(rp);
+
+ return atom.ul;
+}
+
+static void
+get_sample(info_t *ip)
+{
+ static pmResult *crp = NULL; /* current */
+ static pmResult *prp = NULL; /* prior */
+ static int first = 1;
+ static int numpmid;
+ static pmID *pmidlist;
+ static pmDesc *desclist;
+ static int inst1;
+ static int inst15;
+ static pmUnits mbyte_scale;
+
+ int sts;
+ int i;
+ float u;
+ pmAtomValue tmp;
+ pmAtomValue atom;
+ double dt;
+
+ if (first) {
+ /* first time initialization */
+ mbyte_scale.dimSpace = 1;
+ mbyte_scale.scaleSpace = PM_SPACE_MBYTE;
+
+ numpmid = sizeof(pmclient_sample) / sizeof(char *);
+ if ((pmidlist = (pmID *)malloc(numpmid * sizeof(pmidlist[0]))) == NULL) {
+ fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+ if ((desclist = (pmDesc *)malloc(numpmid * sizeof(desclist[0]))) == NULL) {
+ fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+ if ((sts = pmLookupName(numpmid, pmclient_sample, pmidlist)) < 0) {
+ printf("%s: pmLookupName: %s\n", pmProgname, pmErrStr(sts));
+ for (i = 0; i < numpmid; i++) {
+ if (pmidlist[i] == PM_ID_NULL)
+ fprintf(stderr, "%s: metric \"%s\" not in name space\n", pmProgname, pmclient_sample[i]);
+ }
+ exit(1);
+ }
+ for (i = 0; i < numpmid; i++) {
+ if ((sts = pmLookupDesc(pmidlist[i], &desclist[i])) < 0) {
+ fprintf(stderr, "%s: cannot retrieve description for metric \"%s\" (PMID: %s)\nReason: %s\n",
+ pmProgname, pmclient_sample[i], pmIDStr(pmidlist[i]), pmErrStr(sts));
+ exit(1);
+ }
+ }
+ }
+
+ /* fetch the current metrics */
+ if ((sts = pmFetch(numpmid, pmidlist, &crp)) < 0) {
+ fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /*
+ * minor gotcha ... for archives, it helps to do the first fetch of
+ * real data before interrogating the instance domains ... this
+ * forces us to be "after" the first batch of instance domain info
+ * in the meta data files
+ */
+ if (first) {
+ /*
+ * from now on, just want the 1 minute and 15 minute load averages,
+ * so limit the instance profile for this metric
+ */
+ pmDelProfile(desclist[LOADAV].indom, 0, NULL); /* all off */
+ if ((inst1 = pmLookupInDom(desclist[LOADAV].indom, "1 minute")) < 0) {
+ fprintf(stderr, "%s: cannot translate instance for 1 minute load average\n", pmProgname);
+ exit(1);
+ }
+ pmAddProfile(desclist[LOADAV].indom, 1, &inst1);
+ if ((inst15 = pmLookupInDom(desclist[LOADAV].indom, "15 minute")) < 0) {
+ fprintf(stderr, "%s: cannot translate instance for 15 minute load average\n", pmProgname);
+ exit(1);
+ }
+ pmAddProfile(desclist[LOADAV].indom, 1, &inst15);
+
+ first = 0;
+ }
+
+ /* if the second or later sample, pick the results apart */
+ if (prp != NULL) {
+
+ dt = __pmtimevalSub(&crp->timestamp, &prp->timestamp);
+ ip->cpu_util = 0;
+ ip->peak_cpu_util = -1; /* force re-assignment at first CPU */
+ for (i = 0; i < ncpu; i++) {
+ pmExtractValue(crp->vset[CPU_USR]->valfmt,
+ &crp->vset[CPU_USR]->vlist[i],
+ desclist[CPU_USR].type, &atom, PM_TYPE_FLOAT);
+ u = atom.f;
+ pmExtractValue(prp->vset[CPU_USR]->valfmt,
+ &prp->vset[CPU_USR]->vlist[i],
+ desclist[CPU_USR].type, &atom, PM_TYPE_FLOAT);
+ u -= atom.f;
+ pmExtractValue(crp->vset[CPU_SYS]->valfmt,
+ &crp->vset[CPU_SYS]->vlist[i],
+ desclist[CPU_SYS].type, &atom, PM_TYPE_FLOAT);
+ u += atom.f;
+ pmExtractValue(prp->vset[CPU_SYS]->valfmt,
+ &prp->vset[CPU_SYS]->vlist[i],
+ desclist[CPU_SYS].type, &atom, PM_TYPE_FLOAT);
+ u -= atom.f;
+ /*
+ * really should use pmConvertValue, but I _know_ the times
+ * are in msec!
+ */
+ u = u / (1000 * dt);
+
+ if (u > 1.0)
+ /* small errors are possible, so clip the utilization at 1.0 */
+ u = 1.0;
+ ip->cpu_util += u;
+ if (u > ip->peak_cpu_util) {
+ ip->peak_cpu_util = u;
+ ip->peak_cpu = i;
+ }
+ }
+ ip->cpu_util /= ncpu;
+
+ /* freemem - expect just one value */
+ pmExtractValue(crp->vset[FREEMEM]->valfmt, crp->vset[FREEMEM]->vlist,
+ desclist[FREEMEM].type, &tmp, PM_TYPE_FLOAT);
+ /* convert from today's units at the collection site to Mbytes */
+ pmConvScale(PM_TYPE_FLOAT, &tmp, &desclist[FREEMEM].units,
+ &atom, &mbyte_scale);
+ ip->freemem = atom.f;
+
+ /* disk IOPS - expect just one value, but need delta */
+ pmExtractValue(crp->vset[DKIOPS]->valfmt, crp->vset[DKIOPS]->vlist,
+ desclist[DKIOPS].type, &atom, PM_TYPE_U32);
+ ip->dkiops = atom.ul;
+ pmExtractValue(prp->vset[DKIOPS]->valfmt, prp->vset[DKIOPS]->vlist,
+ desclist[DKIOPS].type, &atom, PM_TYPE_U32);
+ ip->dkiops -= atom.ul;
+ ip->dkiops = ((float)(ip->dkiops) + 0.5) / dt;
+
+ /* load average ... process all values, matching up the instances */
+ for (i = 0; i < crp->vset[LOADAV]->numval; i++) {
+ pmExtractValue(crp->vset[LOADAV]->valfmt,
+ &crp->vset[LOADAV]->vlist[i],
+ desclist[LOADAV].type, &atom, PM_TYPE_FLOAT);
+ if (crp->vset[LOADAV]->vlist[i].inst == inst1)
+ ip->load1 = atom.f;
+ else if (crp->vset[LOADAV]->vlist[i].inst == inst15)
+ ip->load15 = atom.f;
+ }
+
+ /* free very old result */
+ pmFreeResult(prp);
+ }
+ ip->timestamp = crp->timestamp;
+
+ /* swizzle result pointers */
+ prp = crp;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts;
+ int samples;
+ int pauseFlag = 0;
+ int lines = 0;
+ char *source;
+ const char *host;
+ info_t info; /* values to report each sample */
+ char timebuf[26]; /* for pmCtime result */
+
+ setlinebuf(stdout);
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'p':
+ pauseFlag++;
+ break;
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (pauseFlag && opts.context != PM_CONTEXT_ARCHIVE) {
+ pmprintf("%s: pause can only be used with archives\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors || opts.optind < argc - 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ source = opts.archives[0];
+ } else if (opts.context == PM_CONTEXT_HOST) {
+ source = opts.hosts[0];
+ } else {
+ opts.context = PM_CONTEXT_HOST;
+ source = "local:";
+ }
+
+ if ((sts = c = pmNewContext(opts.context, source)) < 0) {
+ if (opts.context == PM_CONTEXT_HOST)
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* complete TZ and time window option (origin) setup */
+ if (pmGetContextOptions(c, &opts)) {
+ pmflush();
+ exit(1);
+ }
+
+ host = pmGetContextHostName(c);
+ ncpu = get_ncpu();
+
+ if ((opts.context == PM_CONTEXT_ARCHIVE) &&
+ (opts.start.tv_sec != 0 || opts.start.tv_usec != 0)) {
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ get_sample(&info);
+
+ /* set a default sampling interval if none has been requested */
+ if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0)
+ opts.interval.tv_sec = 5;
+
+ /* set sampling loop termination via the command line options */
+ samples = opts.samples ? opts.samples : -1;
+
+ while (samples == -1 || samples-- > 0) {
+ if (lines % 15 == 0) {
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ printf("Archive: %s, ", opts.archives[0]);
+ printf("Host: %s, %d cpu(s), %s",
+ host, ncpu,
+ pmCtime(&info.timestamp.tv_sec, timebuf));
+/* - report format
+ CPU Busy Busy Free Mem Disk Load Average
+ Util CPU Util (Mbytes) IOPS 1 Min 15 Min
+X.XXX XXX X.XXX XXXXX.XXX XXXXXX XXXX.XX XXXX.XX
+*/
+ printf(" CPU");
+ if (ncpu > 1)
+ printf(" Busy Busy");
+ printf(" Free Mem Disk Load Average\n");
+ printf(" Util");
+ if (ncpu > 1)
+ printf(" CPU Util");
+ printf(" (Mbytes) IOPS 1 Min 15 Min\n");
+ }
+ if (opts.context != PM_CONTEXT_ARCHIVE || pauseFlag)
+ __pmtimevalSleep(opts.interval);
+ get_sample(&info);
+ printf("%5.2f", info.cpu_util);
+ if (ncpu > 1)
+ printf(" %3d %5.2f", info.peak_cpu, info.peak_cpu_util);
+ printf(" %9.3f", info.freemem);
+ printf(" %6d", info.dkiops);
+ printf(" %7.2f %7.2f\n", info.load1, info.load15);
+ lines++;
+ }
+ exit(0);
+}
diff --git a/src/pmclient/pmlogger.config b/src/pmclient/pmlogger.config
new file mode 100644
index 0000000..5673538
--- /dev/null
+++ b/src/pmclient/pmlogger.config
@@ -0,0 +1,16 @@
+#
+# pmlogger(1) configuration file suitable for creating an archive to be
+# used with pmclient(1)
+#
+
+log mandatory on once {
+ hinv.ncpu
+}
+
+log mandatory on 5 secs {
+ kernel.all.load [ "1 minute", "15 minute" ]
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.sys
+ mem.freemem
+ disk.all.total
+}
diff --git a/src/pmclient/pmnsmap.spec b/src/pmclient/pmnsmap.spec
new file mode 100644
index 0000000..fc8fb97
--- /dev/null
+++ b/src/pmclient/pmnsmap.spec
@@ -0,0 +1,11 @@
+pmclient_init {
+ hinv.ncpu NUMCPU
+}
+
+pmclient_sample {
+ kernel.all.load LOADAV
+ kernel.percpu.cpu.user CPU_USR
+ kernel.percpu.cpu.sys CPU_SYS
+ mem.freemem FREEMEM
+ disk.all.total DKIOPS
+}
diff --git a/src/pmcollectl/GNUmakefile b/src/pmcollectl/GNUmakefile
new file mode 100644
index 0000000..c094578
--- /dev/null
+++ b/src/pmcollectl/GNUmakefile
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2012 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pmcollectl
+PYFILES = $(TARGET).py
+
+default default_pcp: $(PYFILES)
+
+include $(BUILDRULES)
+
+install install_pcp: $(PYFILES)
+ifneq "$(PYTHON)" ""
+ $(INSTALL) -m 755 $(PYFILES) $(PCP_BIN_DIR)/$(TARGET)
+endif
diff --git a/src/pmcollectl/pmcollectl.py b/src/pmcollectl/pmcollectl.py
new file mode 100755
index 0000000..13769f9
--- /dev/null
+++ b/src/pmcollectl/pmcollectl.py
@@ -0,0 +1,708 @@
+#!/usr/bin/python
+
+#
+# pmcollectl.py
+#
+# Copyright (C) 2012-2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+"""System status collector using the libpcp Wrapper module
+
+Additional Information:
+
+Performance Co-Pilot Web Site
+http://www.performancecopilot.org
+"""
+
+# ignore line too long, missing docstring, method could be a function,
+# too many public methods
+# pylint: disable=C0301
+# pylint: disable=C0111
+# pylint: disable=R0201
+# pylint: disable=R0904
+
+##############################################################################
+#
+# imports
+#
+
+import os
+import sys
+import time
+import cpmapi as c_api
+import cpmgui as c_gui
+from pcp import pmapi, pmgui
+from pcp.pmsubsys import Subsystem
+
+ME = "pmcollectl"
+
+
+# scale -----------------------------------------------------------------
+
+
+def scale(value, magnitude):
+ return (value + (magnitude / 2)) / magnitude
+
+
+# record ---------------------------------------------------------------
+
+def record(context, config, duration, path, host):
+ if os.path.exists(path):
+ print ME + "archive %s already exists\n" % path
+ sys.exit(1)
+ # Non-graphical application using libpcp_gui services - never want
+ # to see popup dialogs from pmlogger(1) here, so force the issue.
+ os.environ['PCP_XCONFIRM_PROG'] = '/bin/true'
+ interval = pmapi.timeval.fromInterval(str(duration) + " seconds")
+ context.pmRecordSetup(path, ME, 0) # pylint: disable=W0621
+ context.pmRecordAddHost(host, 1, config) # just a filename
+ deadhand = "-T" + str(interval) + "seconds"
+ context.pmRecordControl(0, c_gui.PM_REC_SETARG, deadhand)
+ context.pmRecordControl(0, c_gui.PM_REC_ON, "")
+ interval.sleep()
+ context.pmRecordControl(0, c_gui.PM_REC_OFF, "")
+ # Note: pmlogger has a deadhand timer that will make it stop of its
+ # own accord once -T limit is reached; but we send an OFF-recording
+ # message anyway for cleanliness, just prior to pmcollectl exiting.
+
+# record_add_creator ------------------------------------------------------
+
+def record_add_creator(path):
+ fdesc = open(path, "a+")
+ args = ""
+ for i in sys.argv:
+ args = args + i + " "
+ fdesc.write("# Created by " + args)
+ fdesc.write("\n#\n")
+ fdesc.close()
+
+# _CollectPrint -------------------------------------------------------
+
+
+class _CollectPrint(object):
+ def __init__(self, ss):
+ self.ss = ss
+ def print_header1(self):
+ if self.verbosity == "brief":
+ self.print_header1_brief()
+ elif self.verbosity == "detail":
+ self.print_header1_detail()
+ elif self.verbosity == "verbose":
+ self.print_header1_verbose()
+ sys.stdout.flush()
+ def print_header2(self):
+ if self.verbosity == "brief":
+ self.print_header2_brief()
+ elif self.verbosity == "detail":
+ self.print_header2_detail()
+ elif self.verbosity == "verbose":
+ self.print_header2_verbose()
+ sys.stdout.flush()
+ def print_header1_brief(self):
+ True # pylint: disable-msg=W0104
+ def print_header2_brief(self):
+ True # pylint: disable-msg=W0104
+ def print_header1_detail(self):
+ True # pylint: disable-msg=W0104
+ def print_header2_detail(self):
+ True # pylint: disable-msg=W0104
+ def print_header1_verbose(self):
+ True # pylint: disable-msg=W0104
+ def print_header2_verbose(self):
+ True # pylint: disable-msg=W0104
+ def print_line(self):
+ if self.verbosity == "brief":
+ self.print_brief()
+ elif self.verbosity == "detail":
+ self.print_detail()
+ elif self.verbosity == "verbose":
+ self.print_verbose()
+ def print_brief(self):
+ True # pylint: disable-msg=W0104
+ def print_verbose(self):
+ True # pylint: disable-msg=W0104
+ def print_detail(self):
+ True # pylint: disable-msg=W0104
+ def divide_check(self, dividend, divisor):
+ if divisor == 0:
+ return 0
+ else:
+ return dividend / divisor
+ def set_verbosity(self, cpverbosity):
+ self.verbosity = cpverbosity # pylint: disable-msg=W0201
+
+
+# _cpuCollectPrint --------------------------------------------------
+
+
+class _cpuCollectPrint(_CollectPrint):
+ def print_header1_brief(self):
+ sys.stdout.write('#<--------CPU-------->')
+ def print_header1_detail(self):
+ print '# SINGLE CPU STATISTICS'
+ def print_header1_verbose(self):
+ print '# CPU SUMMARY (INTR, CTXSW & PROC /sec)'
+
+ def print_header2_brief(self):
+ sys.stdout.write('#cpu sys inter ctxsw')
+ def print_header2_detail(self):
+ print '# Cpu User Nice Sys Wait IRQ Soft Steal Idle'
+ def print_header2_verbose(self):
+ print '#User Nice Sys Wait IRQ Soft Steal Idle CPUs Intr Ctxsw Proc RunQ Run Avg1 Avg5 Avg15 RunT BlkT'
+
+ def print_brief(self):
+ print "%4d" % (100 * (self.ss.get_metric_value('kernel.all.cpu.nice') +
+ self.ss.get_metric_value('kernel.all.cpu.user') +
+ self.ss.get_metric_value('kernel.all.cpu.intr') +
+ self.ss.get_metric_value('kernel.all.cpu.sys') +
+ self.ss.get_metric_value('kernel.all.cpu.steal') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
+ ss.cpu_total),
+ print "%3d" % (100 * (self.ss.get_metric_value('kernel.all.cpu.intr') +
+ self.ss.get_metric_value('kernel.all.cpu.sys') +
+ self.ss.get_metric_value('kernel.all.cpu.steal') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
+ ss.cpu_total),
+ print "%5d %6d" % (self.ss.get_metric_value('kernel.all.intr'),
+ self.ss.get_metric_value('kernel.all.pswitch')),
+ def print_detail(self):
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.cpu.user'))):
+ print " %3d %4d %4d %3d %4d %3d %4d %5d %4d" % (
+ k,
+ (100 * (self.ss.get_scalar_value('kernel.percpu.cpu.nice', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.user', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.intr', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.sys', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.steal', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k)) /
+ ss.cpu_total),
+ self.ss.get_scalar_value('kernel.percpu.cpu.nice', k),
+ (100 * (self.ss.get_scalar_value('kernel.percpu.cpu.intr', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.sys', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.steal', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k) +
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k)) /
+ ss.cpu_total),
+ self.ss.get_scalar_value('kernel.percpu.cpu.wait.total', k),
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.hard', k),
+ self.ss.get_scalar_value('kernel.percpu.cpu.irq.soft', k),
+ self.ss.get_scalar_value('kernel.percpu.cpu.steal', k),
+ self.ss.get_scalar_value('kernel.percpu.cpu.idle', k) / 10)
+ def print_verbose(self):
+ ncpu = self.ss.get_metric_value('hinv.ncpu')
+ print "%4d %6d %5d %4d %4d %5d " % (
+ (100 * (self.ss.get_metric_value('kernel.all.cpu.nice') +
+ self.ss.get_metric_value('kernel.all.cpu.user') +
+ self.ss.get_metric_value('kernel.all.cpu.intr') +
+ self.ss.get_metric_value('kernel.all.cpu.sys') +
+ self.ss.get_metric_value('kernel.all.cpu.steal') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
+ ss.cpu_total),
+ self.ss.get_metric_value('kernel.all.cpu.nice'),
+ (100 * (self.ss.get_metric_value('kernel.all.cpu.intr') +
+ self.ss.get_metric_value('kernel.all.cpu.sys') +
+ self.ss.get_metric_value('kernel.all.cpu.steal') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.hard') +
+ self.ss.get_metric_value('kernel.all.cpu.irq.soft')) /
+ ss.cpu_total),
+ self.ss.get_metric_value('kernel.all.cpu.wait.total'),
+ self.ss.get_metric_value('kernel.all.cpu.irq.hard'),
+ self.ss.get_metric_value('kernel.all.cpu.irq.soft')
+ ),
+ print "%6d %6d %5d %5d %6d" % (
+ self.ss.get_metric_value('kernel.all.cpu.steal'),
+ self.ss.get_metric_value('kernel.all.cpu.idle') / (10 * ncpu),
+ ncpu,
+ self.ss.get_metric_value('kernel.all.intr'),
+ self.ss.get_metric_value('kernel.all.pswitch')
+ ),
+ print "%5d %5d %5d %5.2f %5.2f %5.2f %4d %4d" % (
+ self.ss.get_metric_value('kernel.all.nprocs'),
+ self.ss.get_metric_value('kernel.all.runnable'),
+ self.ss.get_metric_value('proc.runq.runnable'),
+ self.ss.get_metric_value('kernel.all.load')[0],
+ self.ss.get_metric_value('kernel.all.load')[1],
+ self.ss.get_metric_value('kernel.all.load')[2],
+ self.ss.get_metric_value('kernel.all.runnable'),
+ self.ss.get_metric_value('proc.runq.blocked'))
+
+
+# _interruptCollectPrint ---------------------------------------------
+
+
+class _interruptCollectPrint(_CollectPrint):
+ def print_header1_brief(self):
+ ndashes = (((self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))) * 6) - 6) / 2
+ hdr = "#<"
+ for k in range(ndashes):
+ hdr += "-"
+ hdr += "Int"
+ for k in range(ndashes):
+ hdr += "-"
+ hdr += ">"
+ print hdr,
+ def print_header1_detail(self):
+ print '# INTERRUPT DETAILS'
+ print '# Int ',
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ print 'Cpu%d ' % k,
+ print 'Type Device(s)'
+ def print_header1_verbose(self):
+ print '# INTERRUPT SUMMARY'
+ def print_header2_brief(self):
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ if k == 0:
+ print '#Cpu%d ' % k,
+ else:
+ print 'Cpu%d ' % k,
+ def print_header2_verbose(self):
+ print '# ',
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ print 'Cpu%d ' % k,
+ print
+ def print_brief(self):
+ int_count = []
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ int_count.append(0)
+ for j in ss.metrics:
+ if j[0:24] == 'kernel.percpu.interrupts':
+ int_count[k] += self.ss.get_scalar_value(self.ss.metrics_dict[j], k)
+
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ print "%4d " % (int_count[k]),
+ def print_detail(self):
+ for j in ss.metrics:
+ if j[0:24] != 'kernel.percpu.interrupts':
+ continue
+ j_i = self.ss.metrics_dict[j]
+ have_nonzero_value = False
+ for k in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ if self.ss.get_scalar_value(j_i, k) != 0:
+ have_nonzero_value = True
+ if not have_nonzero_value:
+ continue
+ if have_nonzero_value:
+ # pcp does not give the interrupt # so print spaces
+ print "%-8s" % self.ss.metrics[j_i].split(".")[3],
+ for i in range(self.ss.get_len(self.ss.get_metric_value('kernel.percpu.interrupts.THR'))):
+ print "%4d " % (self.ss.get_scalar_value(j_i, i)),
+ text = (pm.pmLookupText(self.ss.metric_pmids[j_i], c_api.PM_TEXT_ONELINE))
+ print "%-18s %s" % (text[:(str.index(text, " "))],
+ text[(str.index(text, " ")):])
+ def print_verbose(self):
+ print " ",
+ self.print_brief()
+ print
+
+
+# _diskCollectPrint --------------------------------------------------
+
+
+class _diskCollectPrint(_CollectPrint):
+ def print_header1_brief(self):
+ sys.stdout.write('<----------Disks----------->')
+ def print_header1_detail(self):
+ print '# DISK STATISTICS (/sec)'
+ def print_header1_verbose(self):
+ print '\n\n# DISK SUMMARY (/sec)'
+ def print_header2_brief(self):
+ sys.stdout.write(' KBRead Reads KBWrit Writes')
+ def print_header2_detail(self):
+ print '# <---------reads---------><---------writes---------><--------averages--------> Pct'
+ print '#Name KBytes Merged IOs Size KBytes Merged IOs Size RWSize QLen Wait SvcTim Util'
+ def print_header2_verbose(self):
+ sys.stdout.write('#KBRead RMerged Reads SizeKB KBWrite WMerged Writes SizeKB\n')
+ def print_brief(self):
+ sys.stdout.write("%6d %6d %6d %6d" % (
+ self.ss.get_metric_value('disk.all.read_bytes'),
+ self.ss.get_metric_value('disk.all.read'),
+ self.ss.get_metric_value('disk.all.write_bytes'),
+ self.ss.get_metric_value('disk.all.write')))
+ def print_detail(self):
+ for j in xrange(len(self.ss.metric_pmids)):
+ try:
+ if self.ss.metrics[j] == 'disk.dev.read':
+ (inst, iname) = pm.pmGetInDom(self.ss.metric_descs[j])
+ break
+ except pmapi.pmErr, e:
+ iname = "X"
+
+ # metric values may be scalars or arrays depending on # of disks
+ for j in xrange(len(iname)):
+ print "%-10s %6d %6d %4d %4d %6d %6d %4d %4d %6d %6d %4d %6d %4d" % (
+ iname[j],
+ self.ss.get_scalar_value('disk.dev.read_bytes', j),
+ self.ss.get_scalar_value('disk.dev.read_merge', j),
+ self.ss.get_scalar_value('disk.dev.read', j),
+ self.ss.get_scalar_value('disk.dev.blkread', j),
+ self.ss.get_scalar_value('disk.dev.write_bytes', j),
+ self.ss.get_scalar_value('disk.dev.write_merge', j),
+ self.ss.get_scalar_value('disk.dev.write', j),
+ self.ss.get_scalar_value('disk.dev.blkwrite', j),
+ 0, 0, 0, 0, 0)
+# ??? replace 0 with required fields
+
+ def print_verbose(self):
+ avgrdsz = avgwrsz = 0
+ if self.ss.get_metric_value('disk.all.read') > 0:
+ avgrdsz = self.ss.get_metric_value('disk.all.read_bytes')
+ avgrdsz /= self.ss.get_metric_value('disk.all.read')
+ if self.ss.get_metric_value('disk.all.write') > 0:
+ avgwrsz = self.ss.get_metric_value('disk.all.write_bytes')
+ avgwrsz /= self.ss.get_metric_value('disk.all.write')
+
+ print '%6d %6d %6d %6d %7d %8d %6d %6d' % (
+ avgrdsz,
+ self.ss.get_metric_value('disk.all.read_merge'),
+ self.ss.get_metric_value('disk.all.read'),
+ 0,
+ avgwrsz,
+ self.ss.get_metric_value('disk.all.write_merge'),
+ self.ss.get_metric_value('disk.all.write'),
+ 0)
+
+
+# _memoryCollectPrint ------------------------------------------------
+
+
+class _memoryCollectPrint(_CollectPrint):
+ def print_header1_brief(self):
+ sys.stdout.write('#<-----------Memory----------->')
+ def print_header1_verbose(self):
+ print '# MEMORY SUMMARY'
+ def print_header2_brief(self):
+ print '#Free Buff Cach Inac Slab Map'
+ def print_header2_verbose(self):
+ print '#<-------------------------------Physical Memory--------------------------------------><-----------Swap------------><-------Paging------>'
+ print '# Total Used Free Buff Cached Slab Mapped Anon Commit Locked Inact Total Used Free In Out Fault MajFt In Out'
+ def print_brief(self):
+ print "%4dM %3dM %3dM %3dM %3dM %3dM " % (
+ scale(self.ss.get_metric_value('mem.freemem'), 1000),
+ scale(self.ss.get_metric_value('mem.util.bufmem'), 1000),
+ scale(self.ss.get_metric_value('mem.util.cached'), 1000),
+ scale(self.ss.get_metric_value('mem.util.inactive'), 1000),
+ scale(self.ss.get_metric_value('mem.util.slab'), 1000),
+ scale(self.ss.get_metric_value('mem.util.mapped'), 1000)),
+ def print_verbose(self):
+ print "%8dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %6dM %5dM %5dM %5dM %5dM %6d %6d %6d %6d %6d %6d " % (
+ scale(self.ss.get_metric_value('mem.physmem'), 1000),
+ scale(self.ss.get_metric_value('mem.util.used'), 1000),
+ scale(self.ss.get_metric_value('mem.freemem'), 1000),
+ scale(self.ss.get_metric_value('mem.util.bufmem'), 1000),
+ scale(self.ss.get_metric_value('mem.util.cached'), 1000),
+ scale(self.ss.get_metric_value('mem.util.slab'), 1000),
+ scale(self.ss.get_metric_value('mem.util.mapped'), 1000),
+ scale(self.ss.get_metric_value('mem.util.anonpages'), 1000),
+ scale(self.ss.get_metric_value('mem.util.committed_AS'), 1000),
+ scale(self.ss.get_metric_value('mem.util.mlocked'), 1000),
+ scale(self.ss.get_metric_value('mem.util.inactive'), 1000),
+ scale(self.ss.get_metric_value('mem.util.swapTotal'), 1000),
+ scale(self.ss.get_metric_value('swap.used'), 1000),
+ scale(self.ss.get_metric_value('swap.free'), 1000),
+ scale(self.ss.get_metric_value('swap.pagesin'), 1000),
+ scale(self.ss.get_metric_value('swap.pagesout'), 1000),
+ scale(self.ss.get_metric_value('mem.vmstat.pgfault') -
+ self.ss.get_metric_value('mem.vmstat.pgmajfault'), 1000),
+ scale(self.ss.get_metric_value('mem.vmstat.pgmajfault'), 1000),
+ scale(self.ss.get_metric_value('mem.vmstat.pgpgin'), 1000),
+ scale(self.ss.get_metric_value('mem.vmstat.pgpgout'), 1000))
+
+
+# _netCollectPrint --------------------------------------------------
+
+
+class _netCollectPrint(_CollectPrint):
+ def print_header1_brief(self):
+ sys.stdout.write('<----------Network---------->')
+ def print_header1_detail(self):
+ print '# NETWORK STATISTICS (/sec)'
+ def print_header1_verbose(self):
+ print '\n\n# NETWORK SUMMARY (/sec)'
+ def print_header2_brief(self):
+ sys.stdout.write(' KBIn PktIn KBOut PktOut')
+ def print_header2_detail(self):
+ print '#Num Name KBIn PktIn SizeIn MultI CmpI ErrsI KBOut PktOut SizeO CmpO ErrsO'
+ def print_header2_verbose(self):
+ print '# KBIn PktIn SizeIn MultI CmpI ErrsI KBOut PktOut SizeO CmpO ErrsO'
+ def print_brief(self):
+ print "%5d %6d %6d %6d" % (
+ sum(self.ss.get_metric_value('network.interface.in.bytes')) / 1024,
+ sum(self.ss.get_metric_value('network.interface.in.packets')),
+ sum(self.ss.get_metric_value('network.interface.out.bytes')) / 1024,
+ sum(self.ss.get_metric_value('network.interface.out.packets'))),
+ def average_packet_size(self, bytes, packets):
+ # calculate mean packet size safely (note that divisor may be zero)
+ result = 0
+ bin = sum(self.ss.get_metric_value('network.interface.' + bytes))
+ pin = sum(self.ss.get_metric_value('network.interface.' + packets))
+ if pin > 0:
+ result = bin / pin
+ return result
+ def print_verbose(self):
+ # don't include loopback; TODO: pmDelProfile would be more appropriate
+ self.ss.get_metric_value('network.interface.in.bytes')[0] = 0
+ self.ss.get_metric_value('network.interface.out.bytes')[0] = 0
+ print '%6d %5d %6d %6d %6d %6d %6d %6d %6d %6d %7d' % (
+ sum(self.ss.get_metric_value('network.interface.in.bytes')) / 1024,
+ sum(self.ss.get_metric_value('network.interface.in.packets')),
+ self.average_packet_size('in.bytes', 'in.packets'),
+ sum(self.ss.get_metric_value('network.interface.in.mcasts')),
+ sum(self.ss.get_metric_value('network.interface.in.compressed')),
+ sum(self.ss.get_metric_value('network.interface.in.errors')),
+ sum(self.ss.get_metric_value('network.interface.out.bytes')) / 1024,
+ sum(self.ss.get_metric_value('network.interface.out.packets')),
+ self.average_packet_size('out.bytes', 'out.packets'),
+ sum(self.ss.get_metric_value('network.interface.total.mcasts')),
+ sum(self.ss.get_metric_value('network.interface.out.errors')))
+ def print_detail(self):
+ for j in xrange(len(self.ss.metric_pmids)):
+ try:
+ if self.ss.metrics[j] == 'network.interface.in.bytes':
+ (inst, iname) = pm.pmGetInDom(self.ss.metric_descs[j])
+ break
+ except pmapi.pmErr, e: # pylint: disable-msg=C0103
+ iname = "X"
+
+ for j in xrange(len(iname)):
+ print '%4d %-7s %6d %5d %6d %6d %6d %6d %6d %6d %6d %6d %7d' % (
+ j, iname[j],
+ self.ss.get_metric_value('network.interface.in.bytes')[j] / 1024,
+ self.ss.get_metric_value('network.interface.in.packets')[j],
+ self.divide_check(self.ss.get_metric_value('network.interface.in.bytes')[j],
+ self.ss.get_metric_value('network.interface.in.packets')[j]),
+ self.ss.get_metric_value('network.interface.in.mcasts')[j],
+ self.ss.get_metric_value('network.interface.in.compressed')[j],
+ self.ss.get_metric_value('network.interface.in.errors')[j],
+ self.ss.get_metric_value('network.interface.in.packets')[j],
+ self.ss.get_metric_value('network.interface.out.packets')[j],
+ self.divide_check(self.ss.get_metric_value('network.interface.in.packets')[j],
+ self.ss.get_metric_value('network.interface.out.packets')[j]) / 1024,
+ self.ss.get_metric_value('network.interface.total.mcasts')[j],
+ self.ss.get_metric_value(
+ 'network.interface.out.compressed')[j])
+
+class _Options(object):
+ def __init__(self):
+ self.subsys_arg = ""
+ self.verbosity = "brief"
+ self.input_file = ""
+ self.output_file = ""
+ self.archive = []
+ self.host = "local:"
+ self.create_archive = False
+ self.replay_archive = False
+ self.interval_arg = 1
+ self.n_samples = 0
+ self.duration_arg = 0
+ self.opts = self.setup()
+
+ def setup(self):
+ """ Setup default command line argument option handling """
+ opts = pmapi.pmOptions()
+ opts.pmSetOptionCallback(self.option_callback)
+ opts.pmSetOverrideCallback(self.override)
+ opts.pmSetShortOptions("vp:c:f:R:i:s:h:?")
+ opts.pmSetLongOptionHeader("Options")
+ opts.pmSetLongOption("verbose", 0, 'v', '', "Produce verbose output")
+ opts.pmSetLongOption("playback", 0, 'p', '', "Read sample data from file")
+ opts.pmSetLongOption("count", 1, 'c', 'COUNT', "Number of samples")
+ opts.pmSetLongOption("filename", 1, 'f', 'FILENAME', "Name of output file")
+ opts.pmSetLongOption("runtime", 1, 'R', 'N', "How long to take samples")
+ opts.pmSetLongOption("interval", 1, 'i', 'N', "The sample time interval")
+ opts.pmSetLongOption("subsys", 1, 's', 'SUBSYS', "The subsystem to sample")
+ opts.pmSetShortUsage("[options]\nInteractive: [-v] [-h host] [-s subsys] [-c N] [-i N] [-R N]\nWrite raw logfile: pmcollectl -f rawfile [-c N] [-i N] [-R N]\nRead raw logfile: pmcollectl -p rawfile")
+ opts.pmSetLongOptionHost()
+ opts.pmSetLongOptionVersion()
+ opts.pmSetLongOptionHelp()
+ return opts
+
+
+ def override(self, opt):
+ """ Override a few standard PCP options to match free(1) """
+ # pylint: disable=R0201
+ if opt == 's' or opt == 'i' or opt == 'h' or opt == "p":
+ return 1
+ return 0
+
+ def option_callback(self, opt, optarg, index):
+ """ Perform setup for an individual command line option """
+
+ s_options = {"d":[disk, "brief"], "D":[disk, "detail"],
+ "c":[cpu, "brief"], "C":[cpu, "detail"],
+ "n":[net, "brief"], "N":[net, "detail"],
+ "j":[interrupt, "brief"], "J":[interrupt, "detail"],
+ "m":[memory, "brief"], # "M":[ss, "detail"],
+ }
+
+ # pylint: disable=W0613
+ if opt == 's':
+ for ssx in xrange(len(optarg)):
+ self.subsys_arg = optarg[ssx:ssx+1]
+ try:
+ subsys.append(s_options[self.subsys_arg][0])
+ except KeyError:
+ print sys.argv[0] + \
+ ": Unimplemented subsystem -s" + self.subsys_arg
+ sys.exit(1)
+ if self.subsys_arg.isupper():
+ self.verbosity = s_options[self.subsys_arg][1]
+ elif opt == 'R':
+ self.duration_arg = optarg
+ elif opt == 'p':
+ self.opts.pmSetOptionArchiveFolio(optarg)
+ self.input_file = optarg
+ self.replay_archive = True
+ elif opt == 'f':
+ self.output_file = optarg
+ self.create_archive = True
+ elif opt == 'v':
+ if self.verbosity != "detail":
+ self.verbosity = "verbose"
+ elif opt == 'i':
+ self.opts.pmSetOptionInterval(optarg)
+ self.interval_arg = self.opts.pmGetOptionInterval()
+ elif opt == 'c':
+ self.opts.pmSetOptionSamples(optarg)
+ self.n_samples = int(self.opts.pmGetOptionSamples())
+ elif opt == 'h':
+ self.host = optarg
+
+# main -----------------------------------------------------------------
+
+
+# ignore These are actually global names; ignore invalid name warning for now
+# TODO move main into a def and enable
+# pylint: disable-msg=C0103
+
+
+if __name__ == '__main__':
+ subsys = list()
+ output_file = ""
+ input_file = ""
+ duration = 0.0
+
+ ss = Subsystem()
+ ss.init_processor_metrics()
+ ss.init_interrupt_metrics()
+ ss.init_disk_metrics()
+ ss.init_memory_metrics()
+ ss.init_network_metrics()
+
+ cpu = _cpuCollectPrint(ss)
+ interrupt = _interruptCollectPrint(ss)
+ disk = _diskCollectPrint(ss)
+ memory = _memoryCollectPrint(ss)
+ net = _netCollectPrint(ss)
+
+ # Establish a PMAPI context to archive, host or local, via args
+ opts = _Options()
+ if c_api.pmGetOptionsFromList(sys.argv) != 0:
+ c_api.pmUsageMessage()
+ sys.exit(1)
+
+ if len(subsys) == 0:
+ if opts.create_archive:
+ map(subsys.append, (cpu, disk, net, interrupt, memory))
+ else:
+ map(subsys.append, (cpu, disk, net))
+
+ if opts.duration_arg != 0:
+ (timeval, errmsg) = pm.pmParseInterval(str(opts.duration_arg))
+ duration = c_api.pmtimevalToReal(timeval)
+
+ pm = pmapi.pmContext.fromOptions(opts.opts, sys.argv)
+ if pm.type == c_api.PM_CONTEXT_ARCHIVE:
+ pm.pmSetMode(c_api.PM_MODE_FORW, pmapi.timeval(0, 0), 0)
+
+ # Find server-side pmcd host-name
+ host = pm.pmGetContextHostName()
+
+ (delta, errmsg) = pmapi.pmContext.pmParseInterval(str(opts.interval_arg) + " seconds")
+
+ if opts.create_archive:
+ delta_seconds = c_api.pmtimevalToReal(delta.tv_sec, delta.tv_usec)
+ msec = str(int(1000.0 * delta_seconds))
+ configuration = "log mandatory on every " + msec + " milliseconds { "
+ configuration += ss.dump_metrics()
+ configuration += "}"
+ if duration == 0.0:
+ if opts.n_samples != 0:
+ duration = float(opts.n_samples) * delta_seconds
+ else:
+ duration = float(10) * delta_seconds
+ record(pmgui.GuiClient(), configuration, duration, opts.output_file, host)
+ record_add_creator(opts.output_file)
+ sys.exit(0)
+
+ try:
+ ss.setup_metrics(pm)
+ ss.get_stats(pm)
+ except pmapi.pmErr, e:
+ if opts.replay_archive:
+ import textwrap
+ print "One of the following metrics is required " + \
+ "but absent in " + input_file + "\n" + \
+ textwrap.fill(str(ss.metrics))
+ else:
+ print "unable to setup metrics"
+ sys.exit(1)
+
+ for ssx in subsys:
+ ssx.set_verbosity(opts.verbosity)
+
+ # brief headings for different subsystems are concatenated together
+ if opts.verbosity == "brief":
+ for ssx in subsys:
+ if ssx == 0:
+ continue
+ ssx.print_header1()
+ print
+ for ssx in subsys:
+ if ssx == 0:
+ continue
+ ssx.print_header2()
+ print
+
+ try:
+ i_samples = 0
+ while (i_samples < opts.n_samples) or (opts.n_samples == 0):
+ pm.pmtimevalSleep(delta)
+ if opts.verbosity != "brief" and len(subsys) > 1:
+ print "\n### RECORD %d >>> %s <<< %s ###" % \
+ (i_samples+1, host, time.strftime("%a %b %d %H:%M:%S %Y"))
+
+ try:
+ ss.get_stats(pm)
+ ss.get_total()
+ for ssx in subsys:
+ if ssx == 0:
+ continue
+ if opts.verbosity != "brief" and (len(subsys) > 1 or i_samples == 0):
+ print
+ ssx.print_header1()
+ ssx.print_header2()
+ ssx.print_line()
+ if opts.verbosity == "brief":
+ print
+ except pmapi.pmErr, e:
+ if str(e).find("PM_ERR_EOL") != -1:
+ print str(e)
+ break
+
+ i_samples += 1
+ except KeyboardInterrupt:
+ True # pylint: disable-msg=W0104
diff --git a/src/pmconfig/GNUmakefile b/src/pmconfig/GNUmakefile
new file mode 100644
index 0000000..84056ae
--- /dev/null
+++ b/src/pmconfig/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmconfig.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmconfig$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmconfig/pmconfig.c b/src/pmconfig/pmconfig.c
new file mode 100644
index 0000000..882e4ce
--- /dev/null
+++ b/src/pmconfig/pmconfig.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+static int apiflag;
+static const char *empty = "";
+static const char *none = "false";
+
+static void
+direct_report(const char *var, const char *val)
+{
+ if (!val || val[0] == '\0')
+ val = empty;
+ printf("%s=%s\n", var, val);
+}
+
+static void
+export_report(const char *var, const char *val)
+{
+ char buffer[4096];
+ const char *p;
+ int i = 0;
+
+ if (!val || val[0] == '\0')
+ val = empty;
+ else {
+ /* ensure we do not leak any problematic characters into export */
+ for (p = val; *p != '\0' && i < sizeof(buffer)-6; p++) {
+ if ((int)*p == '\'') {
+ buffer[i++] = '\'';
+ buffer[i++] = '\\';
+ buffer[i++] = '\'';
+ buffer[i++] = '\'';
+ } else {
+ buffer[i++] = *p;
+ }
+ }
+ buffer[i] = '\0';
+ val = buffer;
+ }
+ if (apiflag) /* API mode: no override allowed */
+ printf("export %s='%s'\n", var, val);
+ else
+ printf("export %s=${%s:-'%s'}\n", var, var, val);
+}
+
+static void
+pcp_conf_extract(char *var, char *prefix, char *val)
+{
+ __pmNativeConfig(var, prefix, val);
+ val = getenv(var);
+ direct_report(var, val);
+}
+
+static void
+pcp_conf_shell_extract(char *var, char *prefix, char *val)
+{
+ __pmNativeConfig(var, prefix, val);
+ val = getenv(var);
+ export_report(var, val);
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "all", 0, 'a', 0, "show all, unmodified format (default)" },
+ { "list", 0, 'l', 0, "synonym for showing \"all\" (above)" },
+ { "library", 0, 'L', 0, "show library features instead of environment" },
+ { "shell", 0, 's', 0, "show all, quoted format for shell expansion" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "alLs?",
+ .long_options = longopts,
+ .short_usage = "[variable ...]",
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sflag = 0;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'a': /* show all, default (unmodified) list format */
+ case 'l':
+ sflag = 0;
+ break;
+ case 's': /* show all, guarded format for shell expansion */
+ sflag = 1;
+ break;
+ case 'L':
+ apiflag = 1;
+ break;
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (sflag) {
+ putenv("SHELL=/bin/sh");
+ empty = none;
+ }
+
+ /* the complete list of variables is to be reported */
+ if (opts.optind >= argc) {
+ if (apiflag)
+ __pmAPIConfig(sflag ? export_report : direct_report);
+ else
+ __pmConfig(sflag ? pcp_conf_shell_extract : pcp_conf_extract);
+ exit(0);
+ }
+
+ /* an explicit list of variables has been requested */
+ if (sflag) {
+ if (apiflag)
+ for (c = opts.optind; c < argc; c++)
+ export_report(argv[c], __pmGetAPIConfig(argv[c]));
+ else
+ for (c = opts.optind; c < argc; c++)
+ export_report(argv[c], pmGetConfig(argv[c]));
+ } else {
+ if (apiflag)
+ for (c = opts.optind; c < argc; c++)
+ direct_report(argv[c], __pmGetAPIConfig(argv[c]));
+ else
+ for (c = opts.optind; c < argc; c++)
+ direct_report(argv[c], pmGetConfig(argv[c]));
+ }
+ exit(0);
+}
diff --git a/src/pmcpp/GNUmakefile b/src/pmcpp/GNUmakefile
new file mode 100644
index 0000000..25c3371
--- /dev/null
+++ b/src/pmcpp/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmcpp.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmcpp$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmcpp/pmcpp.c b/src/pmcpp/pmcpp.c
new file mode 100644
index 0000000..a9bf3ec
--- /dev/null
+++ b/src/pmcpp/pmcpp.c
@@ -0,0 +1,566 @@
+/*
+ *
+ * Simple cpp replacement to be used to pre-process a PMNS from
+ * pmLoadNameSpace() in libpcp.
+ *
+ * Supports ...
+ * - #define name value
+ * no spaces in value, no quotes or escapes
+ * name begins with an alpha or _, then zero or more alphanumeric or _
+ * value is optional and defaults to the empty string
+ * - macro substitution
+ * - standard C-style comment stripping
+ * - #include "file" or #include <file>
+ * up to a depth of 5 levels, for either syntax the directory search
+ * is hard-wired to <file>, the directory of command line file (if any)
+ * and then $PCP_VAR_DIR/pmns
+ * - #ifdef ... #endif and #ifndef ... #endif
+ *
+ * Does NOT support ...
+ * - #if
+ * - nested #ifdef
+ * - error recovery - first error is fatal
+ * - C++ style // comments
+ *
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <ctype.h>
+#include <sys/stat.h>
+
+static char ibuf[256]; /* input line buffer */
+
+/*
+ optind #include file control
+ * allow MAXLEVEL-1 levels of #include
+ */
+#define MAXLEVEL 5
+static struct {
+ char *fname;
+ FILE *fin;
+ int lineno;
+} file_ctl[MAXLEVEL], *currfile = NULL;
+
+/*
+ * macro definitions via #define
+ */
+typedef struct {
+ int len;
+ char *name;
+ char *value;
+} macro_t;
+static macro_t *macro = NULL;
+static int nmacro = 0;
+
+static void err(char *) __attribute__((noreturn));
+
+/*
+ * use pmprintf for fatal messages as we're usually run from
+ * pmLoadNameSpace() in libpcp
+ */
+static void
+err(char *msg)
+{
+ fflush(stdout);
+ if (currfile != NULL) {
+ if (currfile->lineno > 0)
+ pmprintf("pmcpp: %s[%d]: %s", currfile->fname, currfile->lineno, ibuf);
+ else
+ pmprintf("pmcpp: %s:\n", currfile->fname);
+ }
+ pmprintf("pmcpp: Error: %s\n", msg);
+ pmflush();
+ exit(1);
+}
+
+#define OP_DEFINE 1
+#define OP_UNDEF 2
+#define OP_IFDEF 3
+#define OP_IFNDEF 4
+#define OP_ENDIF 5
+#define IF_FALSE 0
+#define IF_TRUE 1
+#define IF_NONE 2
+/*
+ * handle # cpp directives
+ * - return 0 for do nothing and include following lines
+ * - return 1 to skip following lines
+ */
+static int
+directive(void)
+{
+ char *ip;
+ char *name = NULL;
+ char *value = NULL;
+ int namelen = 0; /* pander to gcc */
+ int valuelen = 0; /* pander to gcc */
+ int op;
+ int i;
+ static int in_if = IF_NONE;
+
+ if (strncmp(ibuf, "#define", strlen("#define")) == 0) {
+ ip = &ibuf[strlen("#define")];
+ op = OP_DEFINE;
+ }
+ else if (strncmp(ibuf, "#undef", strlen("#undef")) == 0) {
+ ip = &ibuf[strlen("#undef")];
+ op = OP_UNDEF;
+ }
+ else if (strncmp(ibuf, "#ifdef", strlen("#ifdef")) == 0) {
+ ip = &ibuf[strlen("#ifdef")];
+ op = OP_IFDEF;
+ }
+ else if (strncmp(ibuf, "#ifndef", strlen("#ifndef")) == 0) {
+ ip = &ibuf[strlen("#ifndef")];
+ op = OP_IFNDEF;
+ }
+ else if (strncmp(ibuf, "#endif", strlen("#endif")) == 0) {
+ ip = &ibuf[strlen("#endif")];
+ op = OP_ENDIF;
+ }
+ else {
+ err("Unrecognized control line");
+ /*NOTREACHED*/
+ }
+
+ while (*ip && isblank((int)*ip)) ip++;
+ if (op != OP_ENDIF) {
+ if (*ip == '\n' || *ip == '\0') {
+ err("Missing macro name");
+ /*NOTREACHED*/
+ }
+ name = ip;
+ for ( ;*ip && !isblank((int)*ip); ip++) {
+ if (isalpha((int)*ip) || *ip == '_') continue;
+ if (ip > name &&
+ (isdigit((int)*ip) || *ip == '_'))
+ continue;
+ break;
+ }
+ if (!isspace((int)*ip)) {
+ err("Illegal character in macro name");
+ /*NOTREACHED*/
+ }
+ namelen = ip - name;
+ if (op == OP_DEFINE) {
+ if (*ip == '\n' || *ip == '\0') {
+ value = "";
+ valuelen = 0;
+ }
+ else {
+ while (*ip && isblank((int)*ip)) ip++;
+ value = ip;
+ while (!isspace((int)*ip)) ip++;
+ valuelen = ip - value;
+ }
+ }
+ }
+
+ while (*ip && isblank((int)*ip)) ip++;
+ if (*ip != '\n' && *ip != '\0') {
+ err("Unexpected extra text in a control line");
+ /*NOTREACHED*/
+ }
+
+ if (op == OP_ENDIF) {
+ if (in_if != IF_NONE)
+ in_if = IF_NONE;
+ else {
+ err("No matching #ifdef or #ifndef for #endif");
+ /*NOTREACHED*/
+ }
+ return 0;
+ }
+
+ if (op == OP_IFDEF || op == OP_IFNDEF) {
+ if (in_if != IF_NONE) {
+ err("Nested #ifdef or #ifndef");
+ /*NOTREACHED*/
+ }
+ }
+
+ if (in_if == IF_FALSE)
+ /* skipping, waiting for #endif to match #if[n]def */
+ return 1;
+
+ for (i = 0; i < nmacro; i++) {
+ if (macro[i].len != namelen ||
+ strncmp(name, macro[i].name, macro[i].len) != 0)
+ continue;
+ /* found a match */
+ if (op == OP_IFDEF) {
+ in_if = IF_TRUE;
+ return 0;
+ }
+ else if (op == OP_IFNDEF) {
+ in_if = IF_FALSE;
+ return 1;
+ }
+ else if (op == OP_UNDEF) {
+ macro[i].len = 0;
+ return 0;
+ }
+ else {
+ err("Macro redefinition");
+ /*NOTREACHED*/
+ }
+ }
+
+ /* no matching macro name */
+ if (op == OP_IFDEF) {
+ in_if = IF_FALSE;
+ return 1;
+ }
+ else if (op == OP_IFNDEF) {
+ in_if = IF_TRUE;
+ return 0;
+ }
+ else if (op == OP_UNDEF)
+ /* silently accept #undef for something that was not defined */
+ return 0;
+ else {
+ /* OP_DEFINE case */
+ macro = (macro_t *)realloc(macro, (nmacro+1)*sizeof(macro_t));
+ if (macro == NULL) {
+ __pmNoMem("pmcpp: macro[]", (nmacro+1)*sizeof(macro_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ macro[nmacro].len = namelen;
+ macro[nmacro].name = (char *)malloc(namelen+1);
+ if (macro[nmacro].name == NULL) {
+ __pmNoMem("pmcpp: name", namelen+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ strncpy(macro[nmacro].name, name, namelen);
+ macro[nmacro].name[namelen] = '\0';
+ macro[nmacro].value = (char *)malloc(valuelen+1);
+ if (macro[nmacro].value == NULL) {
+ __pmNoMem("pmcpp: value", valuelen+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if (value && valuelen)
+ strncpy(macro[nmacro].value, value, valuelen);
+ macro[nmacro].value[valuelen] = '\0';
+ nmacro++;
+ return 0;
+ }
+}
+
+static void
+do_macro(void)
+{
+ /*
+ * break line into words at white space or '.' or ':' boundaries
+ * and apply macro substitution to each word
+ */
+ char *ip = ibuf; /* next from ibuf[] to be copied */
+ char *w; /* start of word */
+ int len;
+ char tmp[256]; /* copy output line here */
+ char *op = tmp;
+ int sub = 0; /* true if any substitution made */
+
+
+ while (*ip && isblank((int)*ip))
+ *op++ = *ip++;
+ w = ip;
+ for ( ; ; ) {
+ if (isspace((int)*ip) || *ip == '.' || *ip == ':' || *ip == '\0') {
+ len = ip - w;
+// printf("word=|%*.*s|\n", len, len, w);
+ if (len > 0) {
+ int i;
+ int match = 0;
+ for (i = 0; i < nmacro; i++) {
+ if (len == macro[i].len &&
+ strncmp(w, macro[i].name, len) == 0) {
+ match = 1;
+ sub++;
+ strcpy(op, macro[i].value);
+ op += strlen(macro[i].value);
+ break;
+ }
+ }
+ if (match == 0) {
+ strncpy(op, w, len);
+ op += len;
+ }
+ }
+ *op++ = *ip;
+ if (*ip == '\n' || *ip == '\0')
+ break;
+ ip++;
+ while (*ip && isblank((int)*ip))
+ *op++ = *ip++;
+ w = ip;
+ }
+ ip++;
+ }
+
+ if (sub) {
+ *op = '\0';
+ strcpy(ibuf, tmp);
+ }
+}
+
+/*
+ * Open a regular file for reading, checking that its regular and accessible
+ */
+FILE *
+openfile(const char *fname)
+{
+ struct stat sbuf;
+ FILE *fp = fopen(fname, "r");
+
+ if (!fp)
+ return NULL;
+ if (fstat(fileno(fp), &sbuf) < 0) {
+ fclose(fp);
+ return NULL;
+ }
+ if (!S_ISREG(sbuf.st_mode)) {
+ fclose(fp);
+ setoserror(ENOENT);
+ return NULL;
+ }
+ return fp;
+}
+
+static pmLongOptions longopts[] = {
+ PMOPT_HELP,
+ { "define", 1, 'D', "MACRO", "associate a value with a macro" },
+ PMAPI_OPTIONS_END
+};
+static pmOptions opts = {
+ .short_options = "D:?",
+ .long_options = longopts,
+ .short_usage = "[-Dname ...] [file]",
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int skip_if_false = 0;
+ int incomment = 0;
+ char *ip;
+
+ currfile = &file_ctl[0];
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'D': /* define */
+ for (ip = opts.optarg; *ip; ip++) {
+ if (*ip == '=')
+ *ip = ' ';
+ }
+ snprintf(ibuf, sizeof(ibuf), "#define %s\n", opts.optarg);
+ currfile->fname = "<arg>";
+ currfile->lineno = opts.optind;
+ directive();
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind < argc - 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ currfile->lineno = 0;
+ if (opts.optind == argc) {
+ currfile->fname = "<stdin>";
+ currfile->fin = stdin;
+ }
+ else {
+ currfile->fname = argv[opts.optind];
+ currfile->fin = openfile(currfile->fname);
+ if (currfile->fin == NULL) {
+ err((char *)pmErrStr(-oserror()));
+ /*NOTREACHED*/
+ }
+ }
+ printf("# %d \"%s\"\n", currfile->lineno+1, currfile->fname);
+
+ for ( ; ; ) {
+ if (fgets(ibuf, sizeof(ibuf), currfile->fin) == NULL) {
+ fclose(currfile->fin);
+ if (currfile == &file_ctl[0])
+ break;
+ free(currfile->fname);
+ currfile--;
+ printf("# %d \"%s\"\n", currfile->lineno+1, currfile->fname);
+ continue;
+ }
+ currfile->lineno++;
+
+ /* strip comments ... */
+ for (ip = ibuf; *ip ; ip++) {
+ if (incomment) {
+ if (*ip == '*' && ip[1] == '/') {
+ /* end of comment */
+ incomment = 0;
+ *ip++ = ' ';
+ *ip = ' ';
+ }
+ else
+ *ip = ' ';
+ }
+ else {
+ if (*ip == '/' && ip[1] == '*') {
+ /* start of comment */
+ incomment = currfile->lineno;
+ *ip++ = ' ';
+ *ip = ' ';
+ }
+ }
+ }
+ ip--;
+ while (ip >= ibuf && isspace((int)*ip)) ip--;
+ *++ip = '\n';
+ *++ip = '\0';
+ if (incomment && ibuf[0] == '\n') {
+ printf("\n");
+ continue;
+ }
+
+ if (ibuf[0] == '#') {
+ /* cpp control line */
+ if (strncmp(ibuf, "#include", strlen("#include")) == 0) {
+ char *p;
+ char *pend;
+ char c;
+ FILE *f;
+ static char tmpbuf[MAXPATHLEN];
+
+ if (skip_if_false) {
+ printf("\n");
+ continue;
+ }
+ p = &ibuf[strlen("#include")];
+ while (*p && isblank((int)*p)) p++;
+ if (*p != '"' && *p != '<') {
+ err("Expected \" or < after #include");
+ /*NOTREACHED*/
+ }
+ pend = ++p;
+ while (*pend && *pend != '\n' &&
+ ((p[-1] != '"' || *pend != '"') &&
+ (p[-1] != '<' || *pend != '>'))) pend++;
+ if (p[-1] == '"' && *pend != '"') {
+ err("Expected \" after file name");
+ /*NOTREACHED*/
+ }
+ if (p[-1] == '<' && *pend != '>') {
+ err("Expected > after file name");
+ /*NOTREACHED*/
+ }
+ if (currfile == &file_ctl[MAXLEVEL-1]) {
+ err("#include nesting too deep");
+ /*NOTREACHED*/
+ }
+ if (pend[1] != '\n' && pend[1] != '\0') {
+ err("Unexpected extra text in #include line");
+ /*NOTREACHED*/
+ }
+ c = *pend;
+ *pend = '\0';
+ f = openfile(p);
+ if (f == NULL && file_ctl[0].fin != stdin) {
+ /* check in directory of file from command line */
+ static int sep;
+ static char *dir = NULL;
+ if (dir == NULL) {
+ /*
+ * some versions of dirname() clobber the input
+ * argument, some do not ... hence the obscurity
+ * here
+ */
+ static char *dirbuf;
+ dirbuf = strdup(file_ctl[0].fname);
+ if (dirbuf == NULL) {
+ __pmNoMem("pmcpp: dir name alloc", strlen(file_ctl[0].fname)+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ dir = dirname(dirbuf);
+ sep = __pmPathSeparator();
+ }
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s%c%s", dir, sep, p);
+ f = openfile(tmpbuf);
+ if (f != NULL)
+ p = tmpbuf;
+ }
+ if (f == NULL) {
+ /* check in $PCP_VAR_DIR/pmns */
+ static int sep;
+ static char *var_dir = NULL;
+ if (var_dir == NULL) {
+ var_dir = pmGetConfig("PCP_VAR_DIR");
+ sep = __pmPathSeparator();
+ }
+ snprintf(tmpbuf, sizeof(tmpbuf), "%s%cpmns%c%s", var_dir, sep, sep, p);
+ f = openfile(tmpbuf);
+ if (f != NULL)
+ p = tmpbuf;
+ }
+ if (f == NULL) {
+ *pend = c;
+ err("Cannot open file for #include");
+ /*NOTREACHED*/
+ }
+ currfile++;
+ currfile->lineno = 0;
+ currfile->fin = f;
+ currfile->fname = strdup(p);
+ *pend = c;
+ if (currfile->fname == NULL) {
+ __pmNoMem("pmcpp: file name alloc", strlen(p)+1, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ printf("# %d \"%s\"\n", currfile->lineno+1, currfile->fname);
+ }
+ else {
+ /* expect other cpp control ... */
+ skip_if_false = directive();
+ printf("\n");
+ }
+ continue;
+ }
+ if (skip_if_false)
+ printf("\n");
+ else {
+ if (nmacro > 0)
+ do_macro();
+ printf("%s", ibuf);
+ }
+ }
+
+ /* EOF for the top level file */
+ if (incomment) {
+ char msgbuf[80];
+ snprintf(msgbuf, sizeof(msgbuf), "Comment at line %d not terminated before end of file", incomment);
+ currfile->lineno = 0;
+ err(msgbuf);
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/GNUmakefile b/src/pmdas/GNUmakefile
new file mode 100644
index 0000000..f809ef0
--- /dev/null
+++ b/src/pmdas/GNUmakefile
@@ -0,0 +1,61 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CPMDAS = pmcd linux darwin freebsd solaris aix windows \
+ sample simple trivial sendmail mailq txmon \
+ cisco trace apache shping mounts weblog \
+ lmsensors process roomtemp summary hotproc \
+ lustrecomm linux_proc mmv etw logger bash \
+ systemd gfs2 netbsd linux_xfs infiniband jbd2 \
+ rpm nvidia papi
+
+PLPMDAS = bonding netfilter zimbra postgresql \
+ dbping memcache systemtap mysql vmware kvm \
+ named news pdns samba dtsrun postfix gpsd \
+ rsyslog elasticsearch snmp nginx nfsclient
+
+PYPMDAS = gluster zswap dmcache
+
+SUBDIRS = $(CPMDAS) $(PLPMDAS) $(PYPMDAS)
+LDIRT = pmcd.conf
+
+default :: default_pcp
+
+default_pcp : $(SUBDIRS)
+ @echo '# Performance Metrics Domain Specifications' > pmcd.conf
+ @echo '# ' >> pmcd.conf
+ @echo '# This file is automatically generated during the build' >> pmcd.conf
+ @echo '# Name Id IPC IPC Params File/Cmd' >> pmcd.conf
+ $(SUBDIRS_MAKERULE)
+ @echo >> pmcd.conf
+ @echo '[access]' >> pmcd.conf
+ @echo 'disallow ".*" : store;' >> pmcd.conf
+ @echo 'disallow ":*" : store;' >> pmcd.conf
+ @echo 'allow "local:*" : all;' >> pmcd.conf
+
+install :: install_pcp
+
+install_pcp :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp :: default_pcp
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMCDCONF_PATH)`
+ $(INSTALL) -m 644 pmcd.conf $(PCP_PMCDCONF_PATH)
+
+include $(BUILDRULES)
diff --git a/src/pmdas/aix/GNUmakefile b/src/pmdas/aix/GNUmakefile
new file mode 100644
index 0000000..9282d76
--- /dev/null
+++ b/src/pmdas/aix/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = aix
+DOMAIN = AIX
+CMDTARGET = pmdaaix
+LIBTARGET = pmda_aix.so
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "aix 80 dso aix_init $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = aix.c data.c disk_total.c disk.c cpu_total.c cpu.c netif.c
+
+LLDLIBS = $(PCP_PMDALIB) -lperfstat
+PMNS = pmns.disk pmns.kernel pmns.mem pmns.network pmns.hinv
+LSRCFILES = $(PMNS) help root common.h
+
+LDIRT = *.log *.dir *.pag root_aix domain.h
+
+default: build-me
+
+ifeq "$(TARGET_OS)" "aix"
+build-me: common.h root_aix domain.h $(CMDTARGET) $(LIBTARGET) help.dir help.pag
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help.dir help.pag $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_aix $(PCP_VAR_DIR)/pmns/root_aix
+else
+build-me:
+install:
+endif
+
+$(OBJECTS): common.h
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+help.dir help.pag: help root_aix
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_aix -v 2 -o help < help
+
+root_aix: ../../pmns/stdpmid
+ rm -f root_aix
+ sed -e 's;<stdpmid>;"../../pmns/stdpmid";' <root \
+ | $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp \
+ | sed -e '/^#/d' -e '/^$$/d' >root_aix
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/aix/aix.c b/src/pmdas/aix/aix.c
new file mode 100644
index 0000000..335aacd
--- /dev/null
+++ b/src/pmdas/aix/aix.c
@@ -0,0 +1,127 @@
+/*
+ * AIX PMDA
+ *
+ * Collect performance data from the AIX kernel using libperfstat for
+ * the most part.
+ *
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <time.h>
+#include "common.h"
+
+static int _isDSO = 1;
+static char mypath[MAXPATHLEN];
+static char *username;
+
+/*
+ * wrapper for pmdaFetch which primes the methods ready for
+ * the next fetch
+ * ... real callback is fetch_callback()
+ */
+static int
+aix_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i;
+
+ // TODO: this should only fetch metrics from "pmidlist"
+ for (i = 0; i < methodtab_sz; i++) {
+ methodtab[i].m_prefetch();
+ }
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+aix_fetch_callback(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ metricdesc_t *mdp;
+
+ mdp = (metricdesc_t *)mdesc->m_user;
+ return methodtab[mdp->md_method].m_fetch(mdesc, inst, atom);
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+aix_init(pmdaInterface *dp)
+{
+ if (_isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "aix" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_3, "AIX DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.fetch = aix_fetch;
+ pmdaSetFetchCallBack(dp, aix_fetch_callback);
+ init_data(dp->domain);
+ pmdaInit(dp, indomtab, indomtab_sz, metrictab, metrictab_sz);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "aix" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_3, pmProgname, AIX, "aix.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ aix_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/aix/common.h b/src/pmdas/aix/common.h
new file mode 100644
index 0000000..604ff73
--- /dev/null
+++ b/src/pmdas/aix/common.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "./domain.h"
+
+typedef long long longlong_t; // TODO nuke for AIX
+typedef unsigned long long u_longlong_t; // TODO nuke for AIX build
+typedef unsigned char uchar; // TODO nuke for AIX build
+
+#include <libperfstat.h>
+
+/*
+ * libperfstat method controls
+ *
+ * md_method choices (see below) ... must be contiguous integers so
+ * we can index directly into method[]
+ */
+#define M_CPU_TOTAL 0
+#define M_CPU 1
+#define M_DISK_TOTAL 2
+#define M_DISK 3
+#define M_NETIF 4
+#define M_NETBUF 5 // TODO
+#define M_PROTO 6 // TODO
+#define M_MEM_TOTAL 7 // TODO
+
+/*
+ * special values for offset
+ */
+#define OFF_NOVALUES -2
+#define OFF_DERIVED -1
+
+typedef struct {
+ void (*m_init)(int);
+ void (*m_prefetch)(void);
+ int (*m_fetch)(pmdaMetric *, int, pmAtomValue *);
+} method_t;
+
+extern method_t methodtab[];
+extern int methodtab_sz;
+
+extern void init_data(int);
+
+extern void cpu_total_init(int);
+extern void cpu_total_prefetch(void);
+extern int cpu_total_fetch(pmdaMetric *, int, pmAtomValue *);
+
+extern void cpu_init(int);
+extern void cpu_prefetch(void);
+extern int cpu_fetch(pmdaMetric *, int, pmAtomValue *);
+
+extern void disk_total_init(int);
+extern void disk_total_prefetch(void);
+extern int disk_total_fetch(pmdaMetric *, int, pmAtomValue *);
+
+extern void disk_init(int);
+extern void disk_prefetch(void);
+extern int disk_fetch(pmdaMetric *, int, pmAtomValue *);
+
+extern void netif_init(int);
+extern void netif_prefetch(void);
+extern int netif_fetch(pmdaMetric *, int, pmAtomValue *);
+
+/*
+ * metric descriptions
+ */
+typedef struct {
+ pmDesc md_desc; // PMDA's idea of the semantics
+ int md_method; // specific stats method
+ int md_offset; // offset into stats structure
+} metricdesc_t;
+
+extern metricdesc_t metricdesc[];
+extern pmdaMetric *metrictab;
+extern int metrictab_sz;
+
+#define DISK_INDOM 0
+#define CPU_INDOM 1
+#define NETIF_INDOM 2
+
+extern pmdaIndom indomtab[];
+extern int indomtab_sz;
diff --git a/src/pmdas/aix/cpu.c b/src/pmdas/aix/cpu.c
new file mode 100644
index 0000000..98d3348
--- /dev/null
+++ b/src/pmdas/aix/cpu.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+
+static int ncpu;
+static int ncpu_alloc;
+static int *fetched;
+static perfstat_cpu_t *cpustat;
+
+void
+cpu_init(int first)
+{
+ perfstat_id_t id;
+ int i;
+
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+
+ ncpu = perfstat_cpu(NULL, NULL, sizeof(perfstat_cpu_t), 0);
+ if ((fetched = (int *)malloc(ncpu * sizeof(int))) == NULL) {
+ fprintf(stderr, "cpu_init: fetched malloc[%d] failed: %s\n",
+ ncpu * sizeof(int), osstrerror());
+ exit(1);
+ }
+ if ((cpustat = (perfstat_cpu_t *)malloc(ncpu * sizeof(perfstat_cpu_t))) == NULL) {
+ fprintf(stderr, "cpu_init: cpustat malloc[%d] failed: %s\n",
+ ncpu * sizeof(perfstat_cpu_t), osstrerror());
+ exit(1);
+ }
+ ncpu_alloc = ncpu;
+
+ /*
+ * set up instance domain
+ */
+ strcpy(id.name, "");
+ ncpu = perfstat_cpu(&id, cpustat, sizeof(perfstat_cpu_t), ncpu_alloc);
+
+ indomtab[CPU_INDOM].it_numinst = ncpu;
+ indomtab[CPU_INDOM].it_set = (pmdaInstid *)malloc(ncpu * sizeof(pmdaInstid));
+ if (indomtab[CPU_INDOM].it_set == NULL) {
+ fprintf(stderr, "cpu_init: indomtab malloc[%d] failed: %s\n",
+ ncpu * sizeof(pmdaInstid), osstrerror());
+ exit(1);
+ }
+ for (i = 0; i < ncpu; i++) {
+ indomtab[CPU_INDOM].it_set[i].i_inst = i;
+ indomtab[CPU_INDOM].it_set[i].i_name = strdup(cpustat[i].name);
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_init: ncpu=%d\n", ncpu);
+ }
+#endif
+}
+
+void
+cpu_prefetch(void)
+{
+ int i;
+
+ for (i = 0; i < ncpu_alloc; i++)
+ fetched[i] = 0;
+}
+
+static __uint64_t
+cpu_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+ switch (pmid) {
+
+ default:
+ fprintf(stderr, "cpu_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+int
+cpu_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int offset;
+
+ if (fetched[inst] == 0) {
+ int sts;
+ int i;
+ perfstat_id_t id;
+
+ strcpy(id.name, "");
+ sts = perfstat_cpu(&id, cpustat, sizeof(perfstat_cpu_t), ncpu_alloc);
+
+ /* TODO ...
+ * - if sts != ncpu, the number of CPUs has changed, need to set
+ * fetched[i] to -1 for the missing ones
+ * - is sts > ncpu possible? worse, if the number of cpus is >
+ * ncpu_alloc what should we do?
+ * - possibly reshape the instance domain?
+ * - error handling?
+ */
+
+ for (i = 0; i < ncpu; i++) {
+ fetched[i] = 1;
+ }
+ }
+
+ if (fetched[inst] != 1)
+ return 0;
+
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ if (offset == OFF_NOVALUES)
+ return 0;
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ if (offset == OFF_DERIVED)
+ atom->ull = cpu_derived(mdesc, inst);
+ else {
+ __uint64_t *ullp;
+ ullp = (__uint64_t *)&((char *)&cpustat[inst])[offset];
+ atom->ull = *ullp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ull *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ull);
+ }
+#endif
+ }
+ else {
+ if (offset == OFF_DERIVED)
+ atom->ul = (__uint32_t)cpu_derived(mdesc, inst);
+ else {
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&cpustat[inst])[offset];
+ atom->ul = *ulp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ul *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_fetch: pmid %s inst %d val %lu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ul);
+ }
+#endif
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/aix/cpu_total.c b/src/pmdas/aix/cpu_total.c
new file mode 100644
index 0000000..bf3d118
--- /dev/null
+++ b/src/pmdas/aix/cpu_total.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+#include <sys/m_param.h>
+
+static perfstat_cpu_total_t cpustat;
+static int fetched;
+
+void
+cpu_total_init(int first)
+{
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+}
+
+void
+cpu_total_prefetch(void)
+{
+ int i;
+
+ fetched = 0;
+}
+
+static __uint64_t
+cpu_total_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+ switch (pmid) {
+
+ default:
+ fprintf(stderr, "cpu_total_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_total_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+int
+cpu_total_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int offset;
+
+ if (fetched == 0) {
+ int sts;
+ sts = perfstat_cpu_total(NULL, &cpustat, sizeof(perfstat_cpu_total_t), 1);
+ if (sts != 1) {
+ /* TODO - how to find/decode errors? */
+ fprintf(stderr, "perfstat_cpu_total: failed %s\n", osstrerror());
+ fetched = -1;
+ }
+ else
+ fetched = 1;
+ }
+
+ if (fetched != 1)
+ return 0;
+
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ if (offset == OFF_NOVALUES)
+ return 0;
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ if (offset == OFF_DERIVED)
+ atom->ull = cpu_total_derived(mdesc, inst);
+ else {
+ __uint64_t *ullp;
+ ullp = (__uint64_t *)&((char *)&cpustat)[offset];
+ atom->ull = *ullp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ull *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_total_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ull);
+ }
+#endif
+ }
+ else {
+ if (offset == OFF_DERIVED)
+ atom->ul = (__uint32_t)cpu_total_derived(mdesc, inst);
+ else {
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&cpustat)[offset];
+ atom->ul = *ulp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ul *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_total_fetch: pmid %s inst %d val %lu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ul);
+ }
+#endif
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/aix/data.c b/src/pmdas/aix/data.c
new file mode 100644
index 0000000..6198e78
--- /dev/null
+++ b/src/pmdas/aix/data.c
@@ -0,0 +1,435 @@
+/*
+ * Data structures that define metrics and control the AIX PMDA
+ *
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+#include <ctype.h>
+
+/*
+ * List of instance domains ... we expect the *_INDOM macros
+ * to index into this table.
+ */
+pmdaIndom indomtab[] = {
+ { DISK_INDOM, 0, NULL },
+ { CPU_INDOM, 0, NULL },
+ { NETIF_INDOM, 0, NULL }
+};
+int indomtab_sz = sizeof(indomtab) / sizeof(indomtab[0]);
+
+pmdaMetric *metrictab;
+
+method_t methodtab[] = {
+ { cpu_total_init, cpu_total_prefetch, cpu_total_fetch }, // M_CPU_TOTAL
+ { cpu_init, cpu_prefetch, cpu_fetch }, // M_CPU
+ { disk_total_init, disk_total_prefetch, disk_total_fetch }, // M_DISK_TOTAL
+ { disk_init, disk_prefetch, disk_fetch }, // M_DISK
+ { netif_init, netif_prefetch, netif_fetch } // M_NETIF
+ // M_NETBUF - TODO
+ // M_PROTO - TODO
+ // M_MEM_TOTAL - TODO
+};
+int methodtab_sz = sizeof(methodtab) / sizeof(methodtab[0]);
+
+#define CPU_OFF(field) ((int)&((perfstat_cpu_t *)0)->field)
+#define CPU_TOTAL_OFF(field) ((int)&((perfstat_cpu_total_t *)0)->field)
+#define DISK_OFF(field) ((int)&((perfstat_disk_t *)0)->field)
+#define DISK_TOTAL_OFF(field) ((int)&((perfstat_disk_total_t *)0)->field)
+#define NETIF_OFF(field) ((int)&((perfstat_netinterface_t *)0)->field)
+
+/*
+ * all metrics supported in this PMDA - one table entry for each metric
+ */
+metricdesc_t metricdesc[] = {
+
+/* kernel.all.cpu.idle */
+ { { PMDA_PMID(0,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(idle) },
+
+/* kernel.all.cpu.user */
+ { { PMDA_PMID(0,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(user) },
+
+/* kernel.all.cpu.sys */
+ { { PMDA_PMID(0,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(sys) },
+
+/* kernel.all.cpu.wait.total */
+ { { PMDA_PMID(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(wait) },
+
+/* kernel.percpu.cpu.idle */
+ { { PMDA_PMID(0,4), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(idle) },
+
+/* kernel.percpu.cpu.user */
+ { { PMDA_PMID(0,5), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(user) },
+
+/* kernel.percpu.cpu.sys */
+ { { PMDA_PMID(0,6), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(sys) },
+
+/* kernel.percpu.cpu.wait.total */
+ { { PMDA_PMID(0,7), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(wait) },
+
+/* kernel.all.readch */
+ { { PMDA_PMID(0,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(readch) },
+
+/* kernel.all.writech */
+ { { PMDA_PMID(0,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(readch) },
+
+/* kernel.all.io.softintrs */
+ { { PMDA_PMID(0,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(softintrs) },
+
+/* kernel.percpu.readch */
+ { { PMDA_PMID(0,11), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(readch) },
+
+/* kernel.percpu.writech */
+ { { PMDA_PMID(0,12), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, CPU_OFF(writech) },
+
+/* kernel.percpu.cpu.intr */
+ { { PMDA_PMID(0,13), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU, OFF_NOVALUES },
+
+/* kernel.all.io.bread */
+ { { PMDA_PMID(0,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(bread) },
+
+/* kernel.all.io.bwrite */
+ { { PMDA_PMID(0,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(bwrite) },
+
+/* kernel.all.io.lread */
+ { { PMDA_PMID(0,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(lread) },
+
+/* kernel.all.io.lwrite */
+ { { PMDA_PMID(0,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(lwrite) },
+
+/* kernel.percpu.io.bread */
+ { { PMDA_PMID(0,18), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(bread) },
+
+/* kernel.percpu.io.bwrite */
+ { { PMDA_PMID(0,19), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(bwrite) },
+
+/* kernel.percpu.io.lread */
+ { { PMDA_PMID(0,20), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(lread) },
+
+/* kernel.percpu.io.lwrite */
+ { { PMDA_PMID(0,21), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(lwrite) },
+
+/* kernel.all.syscall */
+ { { PMDA_PMID(0,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(syscall) },
+
+/* kernel.all.pswitch */
+ { { PMDA_PMID(0,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(pswitch) },
+
+/* kernel.percpu.syscall */
+ { { PMDA_PMID(0,24), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(syscall) },
+
+/* kernel.percpu.pswitch */
+ { { PMDA_PMID(0,25), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(pswitch) },
+
+/* kernel.all.io.phread */
+ { { PMDA_PMID(0,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(phread) },
+
+/* kernel.all.io.phwrite */
+ { { PMDA_PMID(0,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(phwrite) },
+
+/* kernel.all.io.devintrs */
+ { { PMDA_PMID(0,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(devintrs) },
+
+/* kernel.percpu.io.phread */
+ { { PMDA_PMID(0,29), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(phread) },
+
+/* kernel.percpu.io.phwrite */
+ { { PMDA_PMID(0,30), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(phwrite) },
+
+/* kernel.all.cpu.intr */
+ { { PMDA_PMID(0,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_CPU_TOTAL, OFF_NOVALUES },
+
+/* kernel.all.trap */
+ { { PMDA_PMID(0,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(traps) },
+
+/* kernel.all.sysexec */
+ { { PMDA_PMID(0,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(sysexec) },
+
+/* kernel.all.sysfork */
+ { { PMDA_PMID(0,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(sysfork) },
+
+/* kernel.all.io.namei */
+ { { PMDA_PMID(0,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(namei) },
+
+/* kernel.all.sysread */
+ { { PMDA_PMID(0,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(sysread) },
+
+/* kernel.all.syswrite */
+ { { PMDA_PMID(0,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(syswrite) },
+
+/* hinv.ncpu_cfg */
+ { { PMDA_PMID(0,38), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(ncpus_cfg) },
+
+/* kernel.percpu.sysexec */
+ { { PMDA_PMID(0,39), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(sysexec) },
+
+/* kernel.percpu.sysfork */
+ { { PMDA_PMID(0,40), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(sysfork) },
+
+/* kernel.percpu.io.namei */
+ { { PMDA_PMID(0,41), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(namei) },
+
+/* kernel.percpu.sysread */
+ { { PMDA_PMID(0,42), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(sysread) },
+
+/* kernel.percpu.syswrite */
+ { { PMDA_PMID(0,43), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU, CPU_OFF(syswrite) },
+
+/* disk.all.read -- not available */
+ { { PMDA_PMID(0,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK_TOTAL, OFF_NOVALUES },
+
+/* disk.all.write -- not available */
+ { { PMDA_PMID(0,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK_TOTAL, OFF_NOVALUES },
+
+/* disk.all.total */
+ { { PMDA_PMID(0,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK_TOTAL, DISK_TOTAL_OFF(xfers) },
+
+/* disk.all.read_bytes */
+ { { PMDA_PMID(0,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK_TOTAL, DISK_TOTAL_OFF(rblks) },
+
+/* disk.all.write_bytes */
+ { { PMDA_PMID(0,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK_TOTAL, DISK_TOTAL_OFF(wblks) },
+
+/* disk.all.total_bytes */
+ { { PMDA_PMID(0,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK_TOTAL, OFF_DERIVED },
+
+/* disk.dev.read -- not available */
+ { { PMDA_PMID(0,50), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK, OFF_NOVALUES },
+
+/* disk.dev.write -- not available */
+ { { PMDA_PMID(0,51), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK, OFF_NOVALUES },
+
+/* disk.dev.total */
+ { { PMDA_PMID(0,52), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK, DISK_OFF(xfers) },
+
+/* disk.dev.read_bytes */
+ { { PMDA_PMID(0,53), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK, DISK_OFF(rblks) },
+
+/* disk.dev.write_bytes */
+ { { PMDA_PMID(0,54), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK, DISK_OFF(wblks) },
+
+/* disk.dev.total_bytes */
+ { { PMDA_PMID(0,55), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, M_DISK, OFF_DERIVED },
+
+/* hinv.ncpu */
+ { { PMDA_PMID(0,56), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_CPU_TOTAL, CPU_TOTAL_OFF(ncpus) },
+
+/* hinv.ndisk */
+ { { PMDA_PMID(0,57), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_DISK_TOTAL, DISK_TOTAL_OFF(number) },
+
+/* hinv.nnetif */
+ { { PMDA_PMID(0,58), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, OFF_DERIVED },
+
+/* network.interface.in.packets */
+ { { PMDA_PMID(0,59), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, NETIF_OFF(ipackets) },
+
+/* network.interface.in.bytes */
+ { { PMDA_PMID(0,60), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, M_NETIF, NETIF_OFF(ibytes) },
+
+/* network.interface.in.errors */
+ { { PMDA_PMID(0,61), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, NETIF_OFF(ierrors) },
+
+/* network.interface.out.packets */
+ { { PMDA_PMID(0,62), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, NETIF_OFF(opackets) },
+
+/* network.interface.out.bytes */
+ { { PMDA_PMID(0,63), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, M_NETIF, NETIF_OFF(obytes) },
+
+/* network.interface.out.errors */
+ { { PMDA_PMID(0,64), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, NETIF_OFF(oerrors) },
+
+/* network.interface.total.packets */
+ { { PMDA_PMID(0,65), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NETIF, OFF_DERIVED },
+
+/* network.interface.total.bytes */
+ { { PMDA_PMID(0,66), PM_TYPE_U64, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, M_NETIF, OFF_DERIVED }
+
+/* remember to add trailing comma before adding more entries ... */
+};
+int metrictab_sz = sizeof(metricdesc) / sizeof(metricdesc[0]);
+
+void
+init_data(int domain)
+{
+ int i;
+ int serial;
+ __pmID_int *ip;
+ __pmInDom_int *iip;
+
+ /*
+ * Create the PMDA's metrictab[] version of the per-metric table.
+ *
+ * Also do domain initialization for each pmid and indom element of
+ * the metricdesc[] table ... the PMDA table is fixed up in
+ * libpcp_pmda
+ */
+ if ((metrictab = (pmdaMetric *)malloc(metrictab_sz * sizeof(pmdaMetric))) == NULL) {
+ fprintf(stderr, "init_data: Error: malloc metrictab [%d] failed: %s\n",
+ metrictab_sz * sizeof(pmdaMetric), osstrerror());
+ exit(1);
+ }
+ for (i = 0; i < metrictab_sz; i++) {
+ metrictab[i].m_user = &metricdesc[i];
+ metrictab[i].m_desc = metricdesc[i].md_desc;
+ ip = (__pmID_int *)&metricdesc[i].md_desc.pmid;
+ ip->domain = domain;
+ if (metricdesc[i].md_desc.indom != PM_INDOM_NULL) {
+ serial = metricdesc[i].md_desc.indom;
+ iip = (__pmInDom_int *)&metricdesc[i].md_desc.indom;
+ iip->serial = serial;
+ iip->pad = 0;
+ iip->domain = domain;
+ }
+ }
+
+ /*
+ * initialize each of the methods
+ */
+ for (i = 0; i < methodtab_sz; i++) {
+ methodtab[i].m_init(1);
+ }
+}
diff --git a/src/pmdas/aix/disk.c b/src/pmdas/aix/disk.c
new file mode 100644
index 0000000..5ab52d6
--- /dev/null
+++ b/src/pmdas/aix/disk.c
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+
+static int ndisk;
+static int ndisk_alloc;
+static int *fetched;
+static perfstat_disk_t *diskstat;
+
+void
+disk_init(int first)
+{
+ perfstat_id_t id;
+ int i;
+
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+
+ ndisk = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0);
+ if ((fetched = (int *)malloc(ndisk * sizeof(int))) == NULL) {
+ fprintf(stderr, "disk_init: fetched malloc[%d] failed: %s\n",
+ ndisk * sizeof(int), osstrerror());
+ exit(1);
+ }
+ if ((diskstat = (perfstat_disk_t *)malloc(ndisk * sizeof(perfstat_disk_t))) == NULL) {
+ fprintf(stderr, "disk_init: diskstat malloc[%d] failed: %s\n",
+ ndisk * sizeof(perfstat_disk_t), osstrerror());
+ exit(1);
+ }
+ ndisk_alloc = ndisk;
+
+ /*
+ * set up instance domain
+ */
+ strcpy(id.name, "");
+ ndisk = perfstat_disk(&id, diskstat, sizeof(perfstat_disk_t), ndisk_alloc);
+
+ indomtab[DISK_INDOM].it_numinst = ndisk;
+ indomtab[DISK_INDOM].it_set = (pmdaInstid *)malloc(ndisk * sizeof(pmdaInstid));
+ if (indomtab[DISK_INDOM].it_set == NULL) {
+ fprintf(stderr, "disk_init: indomtab malloc[%d] failed: %s\n",
+ ndisk * sizeof(pmdaInstid), osstrerror());
+ exit(1);
+ }
+ for (i = 0; i < ndisk; i++) {
+ indomtab[DISK_INDOM].it_set[i].i_inst = i;
+ indomtab[DISK_INDOM].it_set[i].i_name = strdup(diskstat[i].name);
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_init: ndisk=%d\n", ndisk);
+ }
+#endif
+}
+
+void
+disk_prefetch(void)
+{
+ int i;
+
+ for (i = 0; i < ndisk_alloc; i++)
+ fetched[i] = 0;
+}
+
+static __uint64_t
+disk_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+ switch (pmid) {
+ case PMDA_PMID(0,55): /* disk.dev.total_bytes */
+ val = diskstat[inst].rblks + diskstat[inst].wblks;
+ break;
+
+ default:
+ fprintf(stderr, "disk_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+int
+disk_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int offset;
+
+ if (fetched[inst] == 0) {
+ int sts;
+ int i;
+ perfstat_id_t id;
+
+ strcpy(id.name, "");
+ sts = perfstat_disk(&id, diskstat, sizeof(perfstat_disk_t), ndisk_alloc);
+
+ /* TODO ...
+ * - if sts != ndisk, the number of disks has changed, need to set
+ * fetched[i] to -1 for the missing ones
+ * - is sts > ndisk possible? worse, if the number of disks is >
+ * ndisk_alloc what should we do?
+ * - possibly reshape the instance domain?
+ * - error handling?
+ */
+
+ for (i = 0; i < ndisk; i++) {
+ fetched[i] = 1;
+ }
+ }
+
+ if (fetched[inst] != 1)
+ return 0;
+
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ if (offset == OFF_NOVALUES)
+ return 0;
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ if (offset == OFF_DERIVED)
+ atom->ull = disk_derived(mdesc, inst);
+ else {
+ __uint64_t *ullp;
+ ullp = (__uint64_t *)&((char *)&diskstat[inst])[offset];
+ atom->ull = *ullp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ull *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ull);
+ }
+#endif
+ }
+ else {
+ if (offset == OFF_DERIVED)
+ atom->ul = (__uint32_t)disk_derived(mdesc, inst);
+ else {
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&diskstat[inst])[offset];
+ atom->ul = *ulp;
+ }
+ if (mdesc->m_desc.units.scaleTime == PM_TIME_MSEC) {
+ /* assumed to be CPU time */
+ atom->ul *= 1000 / HZ;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_fetch: pmid %s inst %d val %lu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ul);
+ }
+#endif
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/aix/disk_total.c b/src/pmdas/aix/disk_total.c
new file mode 100644
index 0000000..1087d74
--- /dev/null
+++ b/src/pmdas/aix/disk_total.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+
+static perfstat_disk_total_t diskstat;
+static int fetched;
+
+void
+disk_total_init(int first)
+{
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+}
+
+void
+disk_total_prefetch(void)
+{
+ int i;
+
+ fetched = 0;
+}
+
+static __uint64_t
+disk_total_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+ switch (pmid) {
+ case PMDA_PMID(0,49): /* disk.all.total_bytes */
+ val = diskstat.rblks + diskstat.wblks;
+ break;
+
+ default:
+ fprintf(stderr, "disk_total_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_total_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+int
+disk_total_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int offset;
+
+ if (fetched == 0) {
+ int sts;
+ sts = perfstat_disk_total(NULL, &diskstat, sizeof(perfstat_disk_total_t), 1);
+ if (sts != 1) {
+ /* TODO - how to find/decode errors? */
+ fprintf(stderr, "perfstat_disk_total: failed %s\n", osstrerror());
+ fetched = -1;
+ }
+ else
+ fetched = 1;
+ }
+
+ if (fetched != 1)
+ return 0;
+
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ if (offset == OFF_NOVALUES)
+ return 0;
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ if (offset == OFF_DERIVED)
+ atom->ull = disk_total_derived(mdesc, inst);
+ else {
+ __uint64_t *ullp;
+ ullp = (__uint64_t *)&((char *)&diskstat)[offset];
+ atom->ull = *ullp;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_total_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ull);
+ }
+#endif
+ }
+ else {
+ if (offset == OFF_DERIVED)
+ atom->ul = (__uint32_t)disk_total_derived(mdesc, inst);
+ else {
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&diskstat)[offset];
+ atom->ul = *ulp;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "disk_total_fetch: pmid %s inst %d val %lu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ul);
+ }
+#endif
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/aix/help b/src/pmdas/aix/help
new file mode 100644
index 0000000..afce5d8
--- /dev/null
+++ b/src/pmdas/aix/help
@@ -0,0 +1,61 @@
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# AIX PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ kernel.all.cpu.idle TODO
+@ kernel.all.cpu.user TODO
+@ kernel.all.cpu.sys TODO
+@ kernel.all.cpu.wait.total TODO
+@ kernel.percpu.cpu.user TODO
+@ kernel.percpu.cpu.idle TODO
+@ kernel.percpu.cpu.sys TODO
+@ kernel.percpu.cpu.wait.total TODO
+@ disk.all.read TODO
+@ disk.all.write TODO
+@ disk.all.total TODO
+@ disk.all.read_bytes TODO
+@ disk.all.write_bytes TODO
+@ disk.all.total_bytes TODO
+@ disk.dev.read TODO
+@ disk.dev.write TODO
+@ disk.dev.total TODO
+@ disk.dev.read_bytes TODO
+@ disk.dev.write_bytes TODO
+@ disk.dev.total_bytes TODO
+@ network.interface.in.packets TODO
+@ network.interface.in.bytes TODO
+@ network.interface.in.errors TODO
+@ network.interface.out.packets TODO
+@ network.interface.out.bytes TODO
+@ network.interface.out.errors TODO
+@ network.interface.total.packets TODO
+@ network.interface.total.bytes TODO
diff --git a/src/pmdas/aix/netif.c b/src/pmdas/aix/netif.c
new file mode 100644
index 0000000..01e0ac6
--- /dev/null
+++ b/src/pmdas/aix/netif.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+
+static int nnetif;
+static int nnetif_alloc;
+static int *fetched;
+static perfstat_netinterface_t *netifstat;
+
+void
+netif_init(int first)
+{
+ perfstat_id_t id;
+ int i;
+
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+
+ nnetif = perfstat_netinterface(NULL, NULL, sizeof(perfstat_netinterface_t), 0);
+ if ((fetched = (int *)malloc(nnetif * sizeof(int))) == NULL) {
+ fprintf(stderr, "netif_init: fetched malloc[%d] failed: %s\n",
+ nnetif * sizeof(int), osstrerror());
+ exit(1);
+ }
+ if ((netifstat = (perfstat_netinterface_t *)malloc(nnetif * sizeof(perfstat_netinterface_t))) == NULL) {
+ fprintf(stderr, "netif_init: netifstat malloc[%d] failed: %s\n",
+ nnetif * sizeof(perfstat_netinterface_t), osstrerror());
+ exit(1);
+ }
+ nnetif_alloc = nnetif;
+
+ /*
+ * set up instance domain
+ */
+ strcpy(id.name, "");
+ nnetif = perfstat_netinterface(&id, netifstat, sizeof(perfstat_netinterface_t), nnetif_alloc);
+
+ indomtab[NETIF_INDOM].it_numinst = nnetif;
+ indomtab[NETIF_INDOM].it_set = (pmdaInstid *)malloc(nnetif * sizeof(pmdaInstid));
+ if (indomtab[NETIF_INDOM].it_set == NULL) {
+ fprintf(stderr, "netif_init: indomtab malloc[%d] failed: %s\n",
+ nnetif * sizeof(pmdaInstid), osstrerror());
+ exit(1);
+ }
+ for (i = 0; i < nnetif; i++) {
+ indomtab[NETIF_INDOM].it_set[i].i_inst = i;
+ indomtab[NETIF_INDOM].it_set[i].i_name = strdup(netifstat[i].name);
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "netif_init: nnetif=%d\n", nnetif);
+ }
+#endif
+}
+
+void
+netif_prefetch(void)
+{
+ int i;
+
+ for (i = 0; i < nnetif_alloc; i++)
+ fetched[i] = 0;
+}
+
+static __uint64_t
+netif_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+ switch (pmid) {
+ case PMDA_PMID(0,58): /* hinv.nnetif */
+ val = nnetif;
+ break;
+
+ case PMDA_PMID(0,65): /* network.interface.total.packets */
+ val = netifstat[inst].ipackets + netifstat[inst].opackets;
+ break;
+
+ case PMDA_PMID(0,66): /* network.interface.total.bytes */
+ val = netifstat[inst].ibytes + netifstat[inst].obytes;
+ break;
+
+ default:
+ fprintf(stderr, "netif_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "netif_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+int
+netif_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int offset;
+
+ if (fetched[inst] == 0) {
+ int sts;
+ int i;
+ perfstat_id_t id;
+
+ strcpy(id.name, "");
+ sts = perfstat_netinterface(&id, netifstat, sizeof(perfstat_netinterface_t), nnetif_alloc);
+
+ /* TODO ...
+ * - if sts != nnetif, the number of network interfaces has changed,
+ * need to set fetched[i] to -1 for the missing ones
+ * - is sts > nnetif possible? worse, if the number of network
+ * interfaces is > nnetif_alloc what should we do?
+ * - possibly reshape the instance domain?
+ * - error handling?
+ */
+
+ for (i = 0; i < nnetif; i++) {
+ fetched[i] = 1;
+ }
+ }
+
+ /* hinv.nnetif is a singular metric ... so no "instance" for this one */
+ if (inst != PM_IN_NULL && fetched[inst] != 1)
+ return 0;
+
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ if (offset == OFF_NOVALUES)
+ return 0;
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ if (offset == OFF_DERIVED)
+ atom->ull = netif_derived(mdesc, inst);
+ else {
+ __uint64_t *ullp;
+ ullp = (__uint64_t *)&((char *)&netifstat[inst])[offset];
+ atom->ull = *ullp;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "netif_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ull);
+ }
+#endif
+ }
+ else {
+ if (offset == OFF_DERIVED)
+ atom->ul = (__uint32_t)netif_derived(mdesc, inst);
+ else {
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&netifstat[inst])[offset];
+ atom->ul = *ulp;
+ }
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "netif_fetch: pmid %s inst %d val %lu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, atom->ul);
+ }
+#endif
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/aix/pmns.disk b/src/pmdas/aix/pmns.disk
new file mode 100644
index 0000000..d9ed093
--- /dev/null
+++ b/src/pmdas/aix/pmns.disk
@@ -0,0 +1,22 @@
+disk {
+ all
+ dev
+}
+
+disk.all {
+ read AIX:0:44
+ write AIX:0:45
+ total AIX:0:46
+ read_bytes AIX:0:47
+ write_bytes AIX:0:48
+ total_bytes AIX:0:49
+}
+
+disk.dev {
+ read AIX:0:50
+ write AIX:0:51
+ total AIX:0:52
+ read_bytes AIX:0:53
+ write_bytes AIX:0:54
+ total_bytes AIX:0:55
+}
diff --git a/src/pmdas/aix/pmns.hinv b/src/pmdas/aix/pmns.hinv
new file mode 100644
index 0000000..ba148e2
--- /dev/null
+++ b/src/pmdas/aix/pmns.hinv
@@ -0,0 +1,6 @@
+hinv {
+ ncpu AIX:0:56
+ ncpu_cfg AIX:0:38
+ ndisk AIX:0:57
+ nnetif AIX:0:58
+}
diff --git a/src/pmdas/aix/pmns.kernel b/src/pmdas/aix/pmns.kernel
new file mode 100644
index 0000000..fcafd54
--- /dev/null
+++ b/src/pmdas/aix/pmns.kernel
@@ -0,0 +1,77 @@
+kernel {
+ all
+ percpu
+}
+
+kernel.all {
+ cpu
+ io
+ trap AIX:0:32
+ pswitch AIX:0:23
+ syscall AIX:0:22
+ sysexec AIX:0:33
+ sysfork AIX:0:34
+ sysread AIX:0:36
+ syswrite AIX:0:37
+ readch AIX:0:8
+ writech AIX:0:9
+}
+
+kernel.all.cpu {
+ idle AIX:0:0
+ user AIX:0:1
+ sys AIX:0:2
+ intr AIX:0:31
+ wait
+}
+
+kernel.all.cpu.wait {
+ total AIX:0:3
+}
+
+kernel.all.io {
+ bread AIX:0:14
+ bwrite AIX:0:15
+ lread AIX:0:16
+ lwrite AIX:0:17
+ phread AIX:0:26
+ phwrite AIX:0:27
+ devintrs AIX:0:28
+ softintrs AIX:0:10
+ namei AIX:0:35
+}
+
+kernel.percpu {
+ cpu
+ io
+ pswitch AIX:0:25
+ syscall AIX:0:24
+ sysexec AIX:0:39
+ sysfork AIX:0:40
+ sysread AIX:0:42
+ syswrite AIX:0:43
+ readch AIX:0:11
+ writech AIX:0:12
+}
+
+kernel.percpu.cpu {
+ idle AIX:0:4
+ user AIX:0:5
+ sys AIX:0:6
+ intr AIX:0:13
+ wait
+}
+
+kernel.percpu.cpu.wait {
+ total AIX:0:7
+}
+
+kernel.percpu.io {
+ bread AIX:0:18
+ bwrite AIX:0:19
+ lread AIX:0:20
+ lwrite AIX:0:21
+ phread AIX:0:29
+ phwrite AIX:0:30
+ namei AIX:0:41
+}
diff --git a/src/pmdas/aix/pmns.mem b/src/pmdas/aix/pmns.mem
new file mode 100644
index 0000000..bfe42d2
--- /dev/null
+++ b/src/pmdas/aix/pmns.mem
@@ -0,0 +1,2 @@
+mem {
+}
diff --git a/src/pmdas/aix/pmns.network b/src/pmdas/aix/pmns.network
new file mode 100644
index 0000000..a469296
--- /dev/null
+++ b/src/pmdas/aix/pmns.network
@@ -0,0 +1,26 @@
+network {
+ interface
+}
+
+network.interface {
+ in
+ out
+ total
+}
+
+network.interface.in {
+ packets AIX:0:59
+ bytes AIX:0:60
+ errors AIX:0:61
+}
+
+network.interface.out {
+ packets AIX:0:62
+ bytes AIX:0:63
+ errors AIX:0:64
+}
+
+network.interface.total {
+ packets AIX:0:65
+ bytes AIX:0:66
+}
diff --git a/src/pmdas/aix/root b/src/pmdas/aix/root
new file mode 100644
index 0000000..ec99587
--- /dev/null
+++ b/src/pmdas/aix/root
@@ -0,0 +1,20 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root {
+ kernel
+ disk
+ mem
+ network
+ hinv
+}
+
+#include "pmns.kernel"
+#include "pmns.disk"
+#include "pmns.mem"
+#include "pmns.network"
+#include "pmns.hinv"
+
diff --git a/src/pmdas/apache/Apache.pmchart b/src/pmdas/apache/Apache.pmchart
new file mode 100644
index 0000000..b830e4e
--- /dev/null
+++ b/src/pmdas/apache/Apache.pmchart
@@ -0,0 +1,9 @@
+#pmchart
+Version 2.0 host dynamic
+
+Chart Title "Apache Hit Rate" Style plot
+ Plot Color #-cycle Host * Metric apache.requests_per_sec
+Chart Title "Apache Data Rate" Style plot
+ Plot Color #-cycle Host * Metric apache.bytes_per_sec
+#
+# Created Fri Jun 10 05:16:55 2005
diff --git a/src/pmdas/apache/GNUmakefile b/src/pmdas/apache/GNUmakefile
new file mode 100644
index 0000000..8465706
--- /dev/null
+++ b/src/pmdas/apache/GNUmakefile
@@ -0,0 +1,61 @@
+#
+# Copyright (C) 2000 Michal Kara. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = apache
+DOMAIN = APACHE
+TARGETS = $(IAM)$(EXECSUFFIX)
+CFILES = apache.c
+SCRIPTS = Install Remove
+DFILES = README
+LSRCFILES= $(SCRIPTS) pmns help root Apache.pmchart $(DFILES) \
+ pmlogconf.summary pmlogconf.processes pmlogconf.uptime
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so $(TARGETS)
+LCFLAGS = -I$(TOPDIR)/src/libpcp_http/src
+LLDFLAGS= -L$(TOPDIR)/src/libpcp_http/src
+LLDLIBS = -lpcp_http $(PCP_PMDALIB)
+
+default: build-me
+
+include $(BUILDRULES)
+
+build-me: $(TARGETS)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) pmns help root domain.h $(PMDADIR)
+ $(INSTALL) -m 644 Apache.pmchart $(PMCHART)/Apache
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+ $(INSTALL) -m 644 pmlogconf.processes $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/processes
+ $(INSTALL) -m 644 pmlogconf.uptime $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/uptime
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+apache.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmdas/apache/Install b/src/pmdas/apache/Install
new file mode 100755
index 0000000..8bd8e72
--- /dev/null
+++ b/src/pmdas/apache/Install
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2010 Aconex. All Rights Reserved.
+# Copyright (c) 1999,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the apache PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=apache
+pmda_interface=3
+forced_restart=false
+
+# Override interactive dialog from pmdaSetup in pmdaproc.sh
+__choose_mode()
+{
+ echo "Installing the \"$iam\" Performance Metrics Domain Agent (PMDA) ..."
+ echo
+}
+
+pmdaSetup
+
+if $do_pmda
+then
+ args=""
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Apache port number [80]? ""$PCP_ECHO_C"
+ read value
+
+ if [ "X$value" = "X" ]
+ then
+ :
+ elif [ "X`expr 0 + $value 2>/dev/null`" != "X$value" ]
+ then
+ echo "-- Sorry, port number must be numeric (not $value), ignored --" >&2
+ else
+ args="$args -P $value"
+ fi
+fi
+
+pmdaInstall
+exit 0
diff --git a/src/pmdas/apache/README b/src/pmdas/apache/README
new file mode 100644
index 0000000..75eb7e3
--- /dev/null
+++ b/src/pmdas/apache/README
@@ -0,0 +1,84 @@
+Apache PMDA
+===========
+
+Export information from apache server info.
+
+Author: Michal Kara
+
+To use this PMDA, you must be running Apache 1.3.2 or later (for the
+extended statistics).
+
+First configure your apache http server to enable status support.
+The full procedure is documented at
+http://www.apache.org/docs/mod/mod_status.html
+
+Edit /etc/apache2/mods-enabled/status.conf and enable the following:
+
+<Location /server-status>
+ SetHandler server-status
+ Allow from all
+</Location>
+ExtendedStatus On
+
+Note: you may wish to restrict access to the server status reports to
+the local host only. See the Apache web site for instructions.
+
+You will need to stop and restart your Apache server after making
+this change:
+# apachectl restart
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT apache
+
+The metrics give basic information about Apache performance and about usage of
+server threads.
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/apache
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and optionally specify the port number where Apache is running.
+ Everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/apache
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/apache.log) should be checked for any warnings
+ or errors.
+
+Credits
+=======
+
+Original PMDA was written by Michal Kara.
+
+This PMDA embeds a (slightly modified) lightweight "Http Fetcher" library,
+written by Lyle Hanson (lhanson@users.sourceforge.net) (C) 2001, 2003, 2004
+http://http-fetcher.sourceforge.net
diff --git a/src/pmdas/apache/Remove b/src/pmdas/apache/Remove
new file mode 100644
index 0000000..8d9fae1
--- /dev/null
+++ b/src/pmdas/apache/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 1999,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the apache PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=apache
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/apache/apache.c b/src/pmdas/apache/apache.c
new file mode 100644
index 0000000..3597308
--- /dev/null
+++ b/src/pmdas/apache/apache.c
@@ -0,0 +1,538 @@
+/*
+ * Apache PMDA
+ *
+ * Copyright (C) 2012-2014 Red Hat.
+ * Copyright (C) 2008-2010 Aconex. All Rights Reserved.
+ * Copyright (C) 2000 Michal Kara. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "http_fetcher.h"
+#include <inttypes.h>
+
+static char url[256];
+static char uptime_s[64];
+static char *username;
+
+static int http_port = 80;
+static char *http_server = "localhost";
+static char *http_path = "server-status";
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "server", 1, 'S', "HOST", "use remote host, instead of localhost" },
+ { "port", 1, 'P', "PORT", "use given port on server, instead of port 80" },
+ { "location", 1, 'L', "LOC", "use location on server, instead of 'server-status'" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_TEXT("\nExactly one of the following options may appear:"),
+ PMDAOPT_INET,
+ PMDAOPT_PIPE,
+ PMDAOPT_UNIX,
+ PMDAOPT_IPV6,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:i:l:pu:L:P:S:U:6:?",
+ .long_options = longopts,
+};
+
+static pmdaMetric metrictab[] = {
+/* apache.total_accesses */
+ { NULL, { PMDA_PMID(0,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.total_kbytes */
+ { NULL, { PMDA_PMID(0,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+/* apache.uptime */
+ { NULL, { PMDA_PMID(0,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) } },
+/* apache.requests_per_sec */
+ { NULL, { PMDA_PMID(0,3), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) } },
+/* apache.bytes_per_sec */
+ { NULL, { PMDA_PMID(0,4), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) } },
+/* apache.bytes_per_request */
+ { NULL, { PMDA_PMID(0,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,-1,PM_SPACE_BYTE,0,PM_COUNT_ONE) } },
+/* apache.busy_servers */
+ { NULL, { PMDA_PMID(0,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.idle_servers */
+ { NULL, { PMDA_PMID(0,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_waiting */
+ { NULL, { PMDA_PMID(0,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_starting */
+ { NULL, { PMDA_PMID(0,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_reading */
+ { NULL, { PMDA_PMID(0,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_writing_reply */
+ { NULL, { PMDA_PMID(0,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_keepalive */
+ { NULL, { PMDA_PMID(0,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_dns_lookup */
+ { NULL, { PMDA_PMID(0,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_logging */
+ { NULL, { PMDA_PMID(0,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_finishing */
+ { NULL, { PMDA_PMID(0,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_open_slot */
+ { NULL, { PMDA_PMID(0,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_closing */
+ { NULL, { PMDA_PMID(0,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.sb_idle_cleanup */
+ { NULL, { PMDA_PMID(0,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* apache.uptime_s */
+ { NULL, { PMDA_PMID(0,19), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+};
+
+/*
+ * To speed everything up, the PMDA is caching the data.
+ * Values are refreshed only if older than one second.
+ */
+struct {
+ unsigned int flags; /* Tells which values are valid */
+ unsigned int timeout; /* There was a timeout (a bool) */
+ time_t timestamp; /* Time of last attempted fetch */
+
+ __uint64_t uptime;
+ __uint64_t total_accesses;
+ __uint64_t total_kbytes;
+ double requests_per_sec;
+ double bytes_per_sec;
+ unsigned int bytes_per_request;
+ unsigned int busy_servers;
+ unsigned int idle_servers;
+ unsigned int sb_waiting;
+ unsigned int sb_starting;
+ unsigned int sb_reading;
+ unsigned int sb_writing_reply;
+ unsigned int sb_keepalive;
+ unsigned int sb_dns_lookup;
+ unsigned int sb_logging;
+ unsigned int sb_finishing;
+ unsigned int sb_open_slot;
+ unsigned int sb_closing;
+ unsigned int sb_idle_cleanup;
+} data;
+
+/*
+ * Valid values of flags - tell us which values are currently setup.
+ * Depending on server version and configuration, some may be missing.
+ */
+enum {
+ ACCESSES = (1<<0),
+ KILOBYTES = (1<<1),
+ UPTIME = (1<<2),
+ REQPERSEC = (1<<3),
+ BYTESPERSEC = (1<<4),
+ BYTESPERREQ = (1<<5),
+ BUSYSERVERS = (1<<6),
+ IDLESERVERS = (1<<7),
+ SCOREBOARD = (1<<8),
+};
+
+static void uptime_string(time_t now, char *s, size_t sz)
+{
+ int days, hours, minutes, seconds;
+
+ days = now / (60 * 60 * 24);
+ now %= (60 * 60 * 24);
+ hours = now / (60 * 60);
+ now %= (60 * 60);
+ minutes = now / 60;
+ now %= 60;
+ seconds = now;
+
+ if (days > 1)
+ snprintf(s, sz, "%ddays %02d:%02d:%02d", days, hours, minutes, seconds);
+ else if (days == 1)
+ snprintf(s, sz, "%dday %02d:%02d:%02d", days, hours, minutes, seconds);
+ else
+ snprintf(s, sz, "%02d:%02d:%02d", hours, minutes, seconds);
+}
+
+static void dumpData(void)
+{
+ uptime_string(data.uptime, uptime_s, sizeof(uptime_s));
+ fprintf(stderr, "Apache data from %s port %d, path %s:\n",
+ http_server, http_port, http_path);
+ fprintf(stderr, " flags=0x%x timeout=%d timestamp=%lu\n",
+ data.flags, data.timeout, (unsigned long)data.timestamp);
+ fprintf(stderr, " uptime=%" PRIu64 " (%s)\n", data.uptime, uptime_s);
+ fprintf(stderr, " accesses=%" PRIu64 " kbytes=%" PRIu64
+ " req/sec=%.2f b/sec=%.2f\n",
+ data.total_accesses, data.total_kbytes,
+ data.requests_per_sec, data.bytes_per_sec);
+ fprintf(stderr, " b/req=%u busyserv=%u idleserv=%u\n",
+ data.bytes_per_request, data.busy_servers, data.idle_servers);
+ fprintf(stderr, " scoreboard: waiting=%u starting=%u reading=%u\n",
+ data.sb_waiting, data.sb_starting, data.sb_reading);
+ fprintf(stderr, " writing_reply=%u keepalive=%u dn_lookup=%u\n",
+ data.sb_writing_reply, data.sb_keepalive, data.sb_dns_lookup);
+ fprintf(stderr, " logging=%u finishing=%u open_slot=%u\n",
+ data.sb_logging, data.sb_finishing, data.sb_open_slot);
+ fprintf(stderr, " closing=%u idle_cleanup=%u\n",
+ data.sb_closing, data.sb_idle_cleanup);
+}
+
+/*
+ * Refresh data. Returns 1 of OK, 0 on error.
+ */
+static int refreshData(time_t now)
+{
+ char *res = NULL;
+ int len;
+ char *s,*s2,*s3;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Doing http_fetch(%s)\n", url);
+
+ len = http_fetch(url, &res);
+ if (len < 0) {
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_ERR, "HTTP fetch (stats) failed: %s\n", http_strerror());
+ data.timeout = http_getTimeoutError();
+ if (data.timeout)
+ data.timestamp = now; /* Don't retry too soon */
+ if (res)
+ free(res);
+ return 0; /* failed */
+ }
+
+ memset(&data, 0, sizeof(data));
+
+ for (s = res; *s; ) {
+ s2 = s;
+ s3 = NULL;
+ for (; *s && *s != 10; s++) {
+ if (*s == ':') {
+ s3 = s + 1;
+ if (*s3) {
+ *s3++ = 0;
+ s++;
+ }
+ }
+ }
+
+ if (*s == 10)
+ *s++ = 0;
+
+ if (strcmp(s2, "CPULoad:") == 0)
+ /* ignored */ ;
+ else if (strcmp(s2, "Total Accesses:") == 0) {
+ data.total_accesses = strtoull(s3, (char **)NULL, 10);
+ data.flags |= ACCESSES;
+ }
+ else if (strcmp(s2, "Total kBytes:") == 0) {
+ data.total_kbytes = strtoull(s3, (char **)NULL, 10);
+ data.flags |= KILOBYTES;
+ }
+ else if (strcmp(s2, "Uptime:") == 0) {
+ data.uptime = strtoull(s3, (char **)NULL, 10);
+ data.flags |= UPTIME;
+ }
+ else if (strcmp(s2, "ReqPerSec:") == 0) {
+ data.requests_per_sec = strtod(s3, (char **)NULL);
+ data.flags |= REQPERSEC;
+ }
+ else if (strcmp(s2, "BytesPerSec:") == 0) {
+ data.bytes_per_sec = strtod(s3, (char **)NULL);
+ data.flags |= BYTESPERSEC;
+ }
+ else if (strcmp(s2, "BytesPerReq:") == 0) {
+ data.bytes_per_request = (unsigned int)strtoul(s3, (char **)NULL, 10);
+ data.flags |= BYTESPERREQ;
+ }
+ else if ((strcmp(s2, "BusyServers:") == 0) ||
+ (strcmp(s2, "BusyWorkers:") == 0)) {
+ data.busy_servers = (unsigned int)strtoul(s3, (char **)NULL, 10);
+ data.flags |= BUSYSERVERS;
+ }
+ else if ((strcmp(s2, "IdleServers:") == 0) ||
+ (strcmp(s2, "IdleWorkers:") == 0)) {
+ data.idle_servers = (unsigned int)strtoul(s3, (char **)NULL, 10);
+ data.flags |= IDLESERVERS;
+ }
+ else if (strcmp(s2, "Scoreboard:") == 0) {
+ data.flags |= SCOREBOARD;
+ while(*s3) {
+ switch(*s3) {
+ case '_':
+ data.sb_waiting++;
+ break;
+ case 'S':
+ data.sb_starting++;
+ break;
+ case 'R':
+ data.sb_reading++;
+ break;
+ case 'W':
+ data.sb_writing_reply++;
+ break;
+ case 'K':
+ data.sb_keepalive++;
+ break;
+ case 'D':
+ data.sb_dns_lookup++;
+ break;
+ case 'C':
+ data.sb_closing++;
+ break;
+ case 'L':
+ data.sb_logging++;
+ break;
+ case 'G':
+ data.sb_finishing++;
+ break;
+ case 'I':
+ data.sb_idle_cleanup++;
+ break;
+ case '.':
+ data.sb_open_slot++;
+ break;
+ default:
+ if (pmDebug & DBG_TRACE_APPL1) {
+ __pmNotifyErr(LOG_WARNING,
+ "Unknown scoreboard character '%c'\n", *s3);
+ }
+ }
+ s3++;
+ }
+ }
+ else if (pmDebug & DBG_TRACE_APPL1) {
+ __pmNotifyErr(LOG_WARNING, "Unknown value name '%s'!\n", s2);
+ }
+ }
+
+ data.timestamp = now;
+
+ if (pmDebug & DBG_TRACE_APPL2)
+ dumpData();
+ free(res);
+ return 1;
+}
+
+static int
+apache_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ time_t now = time(NULL);
+
+ if (now > data.timestamp && !refreshData(now + 1))
+ return PM_ERR_AGAIN;
+ if (data.timeout)
+ return PM_ERR_AGAIN;
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+apache_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+ else if (inst != PM_IN_NULL)
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+ case 0:
+ if (!(data.flags & ACCESSES))
+ return 0;
+ atom->ull = data.total_accesses;
+ break;
+ case 1:
+ if (!(data.flags & KILOBYTES))
+ return 0;
+ atom->ull = data.total_kbytes;
+ break;
+ case 2:
+ if (!(data.flags & UPTIME))
+ return 0;
+ atom->ull = data.uptime;
+ break;
+ case 3:
+ if (!(data.flags & REQPERSEC))
+ return 0;
+ atom->d = data.requests_per_sec;
+ break;
+ case 4:
+ if (!(data.flags & BYTESPERSEC))
+ return 0;
+ atom->d = data.bytes_per_sec;
+ break;
+ case 5:
+ if (!(data.flags & BYTESPERREQ))
+ return 0;
+ atom->ul = data.bytes_per_request;
+ break;
+ case 6:
+ if (!(data.flags & BUSYSERVERS))
+ return 0;
+ atom->ul = data.busy_servers;
+ break;
+ case 7:
+ if (!(data.flags & IDLESERVERS))
+ return 0;
+ atom->ul = data.idle_servers;
+ break;
+ case 8:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_waiting;
+ break;
+ case 9:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_starting;
+ break;
+ case 10:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_reading;
+ break;
+ case 11:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_writing_reply;
+ break;
+ case 12:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_keepalive;
+ break;
+ case 13:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_dns_lookup;
+ break;
+ case 14:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_logging;
+ break;
+ case 15:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_finishing;
+ break;
+ case 16:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_open_slot;
+ break;
+ case 17:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_closing;
+ break;
+ case 18:
+ if (!(data.flags & SCOREBOARD))
+ return 0;
+ atom->ul = data.sb_idle_cleanup;
+ break;
+ case 19:
+ if (!(data.flags & UPTIME))
+ return 0;
+ uptime_string(data.uptime, uptime_s, sizeof(uptime_s));
+ atom->cp = uptime_s;
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
+
+void
+apache_init(pmdaInterface *dp)
+{
+ __pmSetProcessIdentity(username);
+
+ http_setTimeout(1);
+ http_setUserAgent(pmProgname);
+ snprintf(url, sizeof(url), "http://%s:%u/%s?auto", http_server, http_port, http_path);
+
+ dp->version.two.fetch = apache_fetch;
+ pmdaSetFetchCallBack(dp, apache_fetchCallBack);
+ pmdaInit(dp, NULL, 0, metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, sep = __pmPathSeparator();
+ pmdaInterface pmda;
+ char helppath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "apache" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&pmda, PMDA_INTERFACE_3, pmProgname, APACHE, "apache.log",
+ helppath);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &pmda)) != EOF) {
+ switch(c) {
+ case 'S':
+ http_server = opts.optarg;
+ break;
+ case 'P':
+ http_port = (int)strtol(opts.optarg, (char **)NULL, 10);
+ break;
+ case 'L':
+ if (opts.optarg[0] == '/')
+ opts.optarg++;
+ http_path = opts.optarg;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&pmda);
+ apache_init(&pmda);
+ pmdaConnect(&pmda);
+ pmdaMain(&pmda);
+ exit(0);
+}
diff --git a/src/pmdas/apache/help b/src/pmdas/apache/help
new file mode 100644
index 0000000..08ee3b3
--- /dev/null
+++ b/src/pmdas/apache/help
@@ -0,0 +1,91 @@
+#
+# Copyright (C) 2000 Michal Kara. All Rights Reserved.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# apache PMDA help file in the ASCII format
+#
+
+@apache.total_accesses Number of accesses since Apache started
+Number of accesses (requests) Apache handled since start of the server.
+
+@apache.total_kbytes KBytes transferred since Apache started
+Number of kilobytes Apache transferred since start of the server.
+
+@apache.uptime Seconds since Apache started
+Number of seconds elapsed since the server was started.
+
+@apache.requests_per_sec Requests per second
+Average requests-per-second rate. Apache computes this as
+(total_accesses / uptime), so its better to use total_accesses
+to get current request rate.
+
+@apache.bytes_per_sec Bytes per second
+Average bytes-per-second rate. Apache computes this as
+(total_kbytes / uptime), so its better to use total_kbytes
+to get current throughput.
+
+@apache.bytes_per_requests Bytes per request
+Average number of bytes per request. Apache computes this as
+total_kbytes/total_accesses.
+
+@apache.busy_servers Number of working processes
+Number of Apache processes which are now working - reading requests,
+receiving data, etc. Together with idle_servers this number gives the
+total number of running Apache processes.
+
+@apache.idle_servers Number of idle processes
+Number of Apache processes which are now idle - waiting for connection.
+Together with busy_servers this number gives the total number of running
+Apache processes.
+
+@apache.sb_waiting Number of waiting processes
+Number of Apache processes waiting for connection.
+
+@apache.sb_starting Number of starting processes
+Number of Apache processes just starting.
+
+@apache.sb_reading Number of processes reading
+Number of Apache processes reading a request.
+
+@apache.sb_writing_reply Number of processes writing
+Number of Apache processes writing a reply.
+
+@apache.sb_keepalive Number of processes keeping-alive connection
+Number of Apache processes waiting for next request on a keep-alive
+connection.
+
+@apache.sb_dns_lookup Number of processes doing DNS lookup
+Number of Apache processes currently doing DNS lookup.
+
+@apache.sb_logging Number of processes writing to log
+Number of Apache processes writing to a log.
+
+@apache.sb_finishing Number of proceses finishing
+Number of Apache processes gracefuly finishing (becase the
+RequestsPerChild limit was reached).
+
+@apache.sb_open_slot Number of open slots
+Number of open "slots" which could be occupied by a process. Together
+with busy_servers and idle_servers, this gives the MaxClients value.
+
+@apache.sb_closing Number of processes closing client connections
+Number of Apache processes closing client connections
+
+@apache.sb_idle_cleanup Number of processes performing idle cleanup
+Number of Apache processes performing idle cleanup
+
+@apache.uptime_s Time since Apache started, as a string
diff --git a/src/pmdas/apache/pmlogconf.processes b/src/pmdas/apache/pmlogconf.processes
new file mode 100644
index 0000000..9513e2d
--- /dev/null
+++ b/src/pmdas/apache/pmlogconf.processes
@@ -0,0 +1,14 @@
+#pmlogconf-setup 2.0
+ident Apache process state information
+probe apache.sb_waiting
+ apache.sb_waiting
+ apache.sb_starting
+ apache.sb_reading
+ apache.sb_writing_reply
+ apache.sb_keepalive
+ apache.sb_dns_lookup
+ apache.sb_logging
+ apache.sb_finishing
+ apache.sb_open_slot
+ apache.sb_closing
+ apache.sb_idle_cleanup
diff --git a/src/pmdas/apache/pmlogconf.summary b/src/pmdas/apache/pmlogconf.summary
new file mode 100644
index 0000000..7a88d85
--- /dev/null
+++ b/src/pmdas/apache/pmlogconf.summary
@@ -0,0 +1,10 @@
+#pmlogconf-setup 2.0
+ident Apache summary information
+probe apache.total_accesses exists ? include : exclude
+ apache.total_accesses
+ apache.total_kbytes
+ apache.requests_per_sec
+ apache.bytes_per_sec
+ apache.bytes_per_requests
+ apache.busy_servers
+ apache.idle_servers
diff --git a/src/pmdas/apache/pmlogconf.uptime b/src/pmdas/apache/pmlogconf.uptime
new file mode 100644
index 0000000..2b2b7b7
--- /dev/null
+++ b/src/pmdas/apache/pmlogconf.uptime
@@ -0,0 +1,6 @@
+#pmlogconf-setup 2.0
+ident Apache uptime
+probe apache.uptime exists ? include : exclude
+delta 5 minutes
+ apache.uptime
+ apache.uptime_s
diff --git a/src/pmdas/apache/pmns b/src/pmdas/apache/pmns
new file mode 100644
index 0000000..c4d9f88
--- /dev/null
+++ b/src/pmdas/apache/pmns
@@ -0,0 +1,43 @@
+/*
+ * Metrics for apache PMDA
+ *
+ * Copyright (C) 2000 Michal Kara. All Rights Reserved.
+ * Copyright (c) 2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+apache {
+ total_accesses APACHE:0:0
+ total_kbytes APACHE:0:1
+ uptime APACHE:0:2
+ requests_per_sec APACHE:0:3
+ bytes_per_sec APACHE:0:4
+ bytes_per_requests APACHE:0:5
+ busy_servers APACHE:0:6
+ idle_servers APACHE:0:7
+ sb_waiting APACHE:0:8
+ sb_starting APACHE:0:9
+ sb_reading APACHE:0:10
+ sb_writing_reply APACHE:0:11
+ sb_keepalive APACHE:0:12
+ sb_dns_lookup APACHE:0:13
+ sb_logging APACHE:0:14
+ sb_finishing APACHE:0:15
+ sb_open_slot APACHE:0:16
+ sb_closing APACHE:0:17
+ sb_idle_cleanup APACHE:0:18
+ uptime_s APACHE:0:19
+}
diff --git a/src/pmdas/apache/root b/src/pmdas/apache/root
new file mode 100644
index 0000000..7d2b7ed
--- /dev/null
+++ b/src/pmdas/apache/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { apache }
+
+#include "pmns"
+
diff --git a/src/pmdas/bash/GNUmakefile b/src/pmdas/bash/GNUmakefile
new file mode 100644
index 0000000..0143cac
--- /dev/null
+++ b/src/pmdas/bash/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reversed.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = bash
+DOMAIN = BASH
+
+TARGETS = $(IAM)$(EXECSUFFIX)
+LLDLIBS = $(PCP_PMDALIB)
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LDIRT = domain.h *.log *.dir *.pag so_locations $(TARGETS)
+
+CFILES = event.c bash.c util.c
+HFILES = event.h
+SCRIPTS = bashproc.sh pcp.sh
+SAMPLES = test-child.sh test-trace.sh
+LSRCFILES = Install Remove pmns help root $(SCRIPTS) $(SAMPLES)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me:
+install:
+else
+build-me: $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 root help pmns $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 644 bashproc.sh $(PCP_SHARE_DIR)/lib/bashproc.sh
+ $(INSTALL) -m 644 pcp.sh $(PCP_ETC_DIR)/pcp.sh
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+bash.o: domain.h
+event.o bash.o util.o: event.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmdas/bash/Install b/src/pmdas/bash/Install
new file mode 100644
index 0000000..5cb784d
--- /dev/null
+++ b/src/pmdas/bash/Install
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# Copyright (c) 2012 Nathan Scott. All rights reversed.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the bash PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=bash
+dso_opt=true
+socket_opt=true
+socket_inet_def=2082
+forced_restart=false
+pmda_interface=5
+
+if [ ! -e "$PCP_TMP_DIR/pmdabash" ]
+then
+ echo "creating $PCP_TMP_DIR/pmdabash"
+ mkdir -p -m 1777 "$PCP_TMP_DIR/pmdabash"
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/bash/README b/src/pmdas/bash/README
new file mode 100644
index 0000000..58ca47f
--- /dev/null
+++ b/src/pmdas/bash/README
@@ -0,0 +1,50 @@
+Bash PMDA
+=========
+
+This PMDA exports information from bash (GNU Bourne-Again SHell)
+processes which are setup to send trace information to PCP. This
+requires functionality present in bash version four or later.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT bash
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/bash
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/bash
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/bash.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/bash/Remove b/src/pmdas/bash/Remove
new file mode 100644
index 0000000..9f87da3
--- /dev/null
+++ b/src/pmdas/bash/Remove
@@ -0,0 +1,23 @@
+#! /bin/sh
+#
+# Copyright (c) 2012 Nathan Scott. All rights reversed.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the bash PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=bash
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/bash/bash.c b/src/pmdas/bash/bash.c
new file mode 100644
index 0000000..e25ef2e
--- /dev/null
+++ b/src/pmdas/bash/bash.c
@@ -0,0 +1,472 @@
+/*
+ * Bash -x trace PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "domain.h"
+#include "event.h"
+#include "pmda.h"
+
+#define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */
+long bash_maxmem;
+
+static int bash_interval_expired;
+static struct timeval bash_interval = { 1, 0 };
+
+static char *username;
+
+#define BASH_INDOM 0
+static pmdaIndom indoms[] = { { BASH_INDOM, 0, NULL } };
+#define INDOM_COUNT (sizeof(indoms)/sizeof(indoms[0]))
+
+static pmdaMetric metrics[] = {
+
+#define bash_xtrace_numclients 0
+ { NULL, { PMDA_PMID(0,bash_xtrace_numclients), PM_TYPE_U32,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+#define bash_xtrace_maxmem 1
+ { NULL, { PMDA_PMID(0,bash_xtrace_maxmem), PM_TYPE_U64,
+ BASH_INDOM, PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+#define bash_xtrace_queuemem 2
+ { NULL, { PMDA_PMID(0,bash_xtrace_queuemem), PM_TYPE_U64,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+#define bash_xtrace_count 3
+ { NULL, { PMDA_PMID(0,bash_xtrace_count), PM_TYPE_U32,
+ BASH_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+#define bash_xtrace_records 4
+ { NULL, { PMDA_PMID(0,bash_xtrace_records), PM_TYPE_EVENT,
+ BASH_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+#define bash_xtrace_parameters_pid 5
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_pid), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_parent 6
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_parent), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_lineno 7
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_lineno), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_function 8
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_function), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+#define bash_xtrace_parameters_command 9
+ { NULL, { PMDA_PMID(0,bash_xtrace_parameters_command), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+#define METRIC_COUNT (sizeof(metrics)/sizeof(metrics[0]))
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "memory", 1, 'm', "SIZE", "maximum memory used per logfile (default 2MB)" },
+ { "interval", 1, 's', "DELTA", "default delay between iterations (default 1 sec)" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:l:m:s:U:?",
+ .long_options = longopts,
+};
+
+
+static void
+check_processes(int context)
+{
+ pmdaEventNewClient(context);
+ event_refresh(indoms[BASH_INDOM].it_indom);
+}
+
+static int
+bash_instance(pmInDom indom, int inst, char *name,
+ __pmInResult **result, pmdaExt *pmda)
+{
+ check_processes(pmda->e_context);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+bash_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ check_processes(pmda->e_context);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+bash_trace_parser(bash_process_t *bash, bash_trace_t *trace,
+ struct timeval *timestamp, const char *buffer, size_t size)
+{
+ trace->flags = (PM_EVENT_FLAG_ID | PM_EVENT_FLAG_PARENT);
+
+ /* empty event inserted into queue to signal process has exited */
+ if (size <= 0) {
+ trace->flags |= PM_EVENT_FLAG_END;
+ if (fstat(bash->fd, &bash->stat) < 0 || !S_ISFIFO(bash->stat.st_mode))
+ memcpy(&trace->timestamp, timestamp, sizeof(*timestamp));
+ else
+ process_stat_timestamp(bash, &trace->timestamp);
+ close(bash->fd);
+ } else {
+ char *p = (char *)buffer, *end = (char *)buffer + size - 1;
+ int sz, time = -1;
+
+ /* version 1 format: time, line#, function, and command line */
+ p += extract_int(p, "time:", sizeof("time:")-1, &time);
+ p += extract_int(p, "line:", sizeof("line:")-1, &trace->line);
+ p += extract_str(p, end - p, "func:", sizeof("func:")-1,
+ trace->function, sizeof(trace->function));
+ sz = extract_cmd(p, end - p, "+", sizeof("+")-1,
+ trace->command, sizeof(trace->command));
+ if (sz <= 0) /* wierd trace - no command */
+ trace->command[0] = '\0';
+
+ if (strncmp(trace->command, "pcp_trace ", 10) == 0 ||
+ strncmp(trace->function, "pcp_trace", 10) == 0)
+ return 1; /* suppress tracing function, its white noise */
+
+ if (time != -1) { /* normal case */
+ trace->timestamp.tv_sec = bash->starttime.tv_sec + time;
+ trace->timestamp.tv_usec = bash->starttime.tv_usec;
+ } else { /* wierd trace */
+ memcpy(&trace->timestamp, timestamp, sizeof(*timestamp));
+ }
+
+ if (event_start(bash, &trace->timestamp))
+ trace->flags |= PM_EVENT_FLAG_START;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "event parsed: flags: %x time: %d line: %d func: '%s' cmd: '%s'",
+ trace->flags, time, trace->line, trace->function, trace->command);
+ }
+ return 0;
+}
+
+static int
+bash_trace_decoder(int eventarray,
+ void *buffer, size_t size, struct timeval *timestamp, void *data)
+{
+ pmAtomValue atom;
+ bash_process_t *process = (bash_process_t *)data;
+ bash_trace_t trace = { 0 };
+ pmID pmid;
+ int sts, count = 0;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_trace_decoder[%ld bytes]", (long)size);
+
+ if (bash_trace_parser(process, &trace, timestamp, (const char *)buffer, size))
+ return 0;
+
+ sts = pmdaEventAddRecord(eventarray, &trace.timestamp, trace.flags);
+ if (sts < 0)
+ return sts;
+
+ atom.ul = process->pid;
+ pmid = metrics[bash_xtrace_parameters_pid].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+
+ atom.ul = process->parent;
+ pmid = metrics[bash_xtrace_parameters_parent].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+
+ if (trace.line) {
+ atom.ul = trace.line;
+ pmid = metrics[bash_xtrace_parameters_lineno].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_U32, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ if (trace.function[0] != '\0') {
+ atom.cp = trace.function;
+ pmid = metrics[bash_xtrace_parameters_function].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ if (trace.command[0] != '\0') {
+ atom.cp = trace.command;
+ pmid = metrics[bash_xtrace_parameters_command].m_desc.pmid;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ count++;
+ }
+
+ return count;
+}
+
+static int
+bash_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ bash_process_t *bp;
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+
+ switch (idp->item) {
+ case bash_xtrace_maxmem:
+ atom->ull = (unsigned long long)bash_maxmem;
+ return PMDA_FETCH_STATIC;
+ case bash_xtrace_parameters_pid:
+ case bash_xtrace_parameters_parent:
+ case bash_xtrace_parameters_lineno:
+ case bash_xtrace_parameters_function:
+ case bash_xtrace_parameters_command:
+ return PMDA_FETCH_NOVALUES;
+ }
+
+ if (PMDA_CACHE_ACTIVE !=
+ pmdaCacheLookup(indoms[BASH_INDOM].it_indom, inst, NULL, (void **)&bp))
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+ case bash_xtrace_numclients:
+ return pmdaEventQueueClients(bp->queueid, atom);
+ case bash_xtrace_queuemem:
+ return pmdaEventQueueMemory(bp->queueid, atom);
+ case bash_xtrace_count:
+ return pmdaEventQueueCounter(bp->queueid, atom);
+ case bash_xtrace_records:
+ return pmdaEventQueueRecords(bp->queueid, atom, pmdaGetContext(),
+ bash_trace_decoder, bp);
+ }
+
+ return PM_ERR_PMID;
+}
+
+static int
+bash_store_metric(pmValueSet *vsp, int context)
+{
+ __pmID_int *idp = (__pmID_int *)&vsp->pmid;
+ pmInDom processes = indoms[BASH_INDOM].it_indom;
+ int sts;
+
+ if (idp->cluster != 0 || idp->item != bash_xtrace_records)
+ return PM_ERR_PERMISSION;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store_metric walking bash set");
+
+ pmdaCacheOp(processes, PMDA_CACHE_WALK_REWIND);
+ while ((sts = pmdaCacheOp(processes, PMDA_CACHE_WALK_NEXT)) != -1) {
+ bash_process_t *bp;
+
+ if (!pmdaCacheLookup(processes, sts, NULL, (void **)&bp))
+ continue;
+ if ((sts = pmdaEventSetAccess(context, bp->queueid, 1)) < 0)
+ return sts;
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "Access granted client=%d bash=%d queueid=%d",
+ context, bp->pid, bp->queueid);
+ }
+ return 0;
+}
+
+static int
+bash_store(pmResult *result, pmdaExt *pmda)
+{
+ int i, sts;
+ int context = pmda->e_context;
+
+ check_processes(context);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store called (%d)", result->numpmid);
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "bash_store_metric called");
+ if ((sts = bash_store_metric(vsp, context)) < 0)
+ return sts;
+ }
+ return 0;
+}
+
+static void
+bash_end_contextCallBack(int context)
+{
+ pmdaEventEndClient(context);
+}
+
+static void
+timer_expired(int sig, void *ptr)
+{
+ bash_interval_expired = 1;
+}
+
+void
+bash_main(pmdaInterface *dispatch)
+{
+ fd_set fds, readyfds;
+ int maxfd, nready, pmcdfd;
+
+ pmcdfd = __pmdaInFd(dispatch);
+ maxfd = pmcdfd;
+
+ FD_ZERO(&fds);
+ FD_SET(pmcdfd, &fds);
+
+ /* arm interval timer */
+ if (__pmAFregister(&bash_interval, NULL, timer_expired) < 0) {
+ __pmNotifyErr(LOG_ERR, "registering event interval handler");
+ exit(1);
+ }
+
+ for (;;) {
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(maxfd+1, &readyfds, NULL, NULL, NULL);
+ if (pmDebug & DBG_TRACE_APPL2)
+ __pmNotifyErr(LOG_DEBUG, "select: nready=%d interval=%d",
+ nready, bash_interval_expired);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror());
+ exit(1);
+ } else if (!bash_interval_expired) {
+ continue;
+ }
+ }
+
+ __pmAFblock();
+ if (nready > 0 && FD_ISSET(pmcdfd, &readyfds)) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "processing pmcd PDU [fd=%d]", pmcdfd);
+ if (__pmdaMainPDU(dispatch) < 0) {
+ __pmAFunblock();
+ exit(1); /* fatal if we lose pmcd */
+ }
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "completed pmcd PDU [fd=%d]", pmcdfd);
+ }
+ if (bash_interval_expired) {
+ bash_interval_expired = 0;
+ event_refresh(indoms[BASH_INDOM].it_indom);
+ }
+ __pmAFunblock();
+ }
+}
+
+void
+bash_init(pmdaInterface *dp)
+{
+ __pmSetProcessIdentity(username);
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.fetch = bash_fetch;
+ dp->version.four.store = bash_store;
+ dp->version.four.instance = bash_instance;
+ pmdaSetFetchCallBack(dp, bash_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, bash_end_contextCallBack);
+ pmdaInit(dp, indoms, INDOM_COUNT, metrics, METRIC_COUNT);
+
+ event_init();
+}
+
+static void
+convertUnits(char **endnum, long *maxmem)
+{
+ switch ((int) **endnum) {
+ case 'b':
+ case 'B':
+ break;
+ case 'k':
+ case 'K':
+ *maxmem *= 1024;
+ break;
+ case 'm':
+ case 'M':
+ *maxmem *= 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ *maxmem *= 1024 * 1024 * 1024;
+ break;
+ }
+ (*endnum)++;
+}
+
+int
+main(int argc, char **argv)
+{
+ static char helppath[MAXPATHLEN];
+ char *endnum;
+ pmdaInterface desc;
+ long minmem;
+ int c, sep = __pmPathSeparator();
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ minmem = getpagesize();
+ bash_maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM;
+ snprintf(helppath, sizeof(helppath), "%s%c" "bash" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_5, pmProgname, BASH, "bash.log", helppath);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &desc)) != EOF) {
+ switch (c) {
+ case 'm':
+ bash_maxmem = strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0')
+ convertUnits(&endnum, &bash_maxmem);
+ if (*endnum != '\0' || bash_maxmem < minmem) {
+ pmprintf("%s: invalid max memory '%s' (min=%ld)\n",
+ pmProgname, opts.optarg, minmem);
+ opts.errors++;
+ }
+ break;
+
+ case 's':
+ if (pmParseInterval(opts.optarg, &bash_interval, &endnum) < 0) {
+ pmprintf("%s: -s requires a time interval: %s\n",
+ pmProgname, endnum);
+ free(endnum);
+ opts.errors++;
+ }
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ bash_init(&desc);
+ pmdaConnect(&desc);
+ bash_main(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/bash/bashproc.sh b/src/pmdas/bash/bashproc.sh
new file mode 100755
index 0000000..4d867a7
--- /dev/null
+++ b/src/pmdas/bash/bashproc.sh
@@ -0,0 +1,64 @@
+# Common bash(1) procedures to be used in the Performance Co-Pilot
+# Bash PMDA trace instrumentation mechanism
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+pcp_trace()
+{
+ command="$1"
+ shift
+
+ case "$command"
+ in
+ setup|init)
+ [ -z "${PCP_TMP_DIR}" ] && return 0 # incorrect call sequence
+ trap "pcp_trace off" 0
+ export PCP_TRACE_DIR="${PCP_TMP_DIR}/pmdabash"
+ export PCP_TRACE_HEADER="${PCP_TRACE_DIR}/.$$"
+ export PCP_TRACE_DATA="${PCP_TRACE_DIR}/$$"
+ ;;
+
+ stop|off)
+ exec 99>/dev/null
+ # unlink is performed by pmdabash to resolve race conditions
+ unset BASH_XTRACEFD PCP_TRACE_DIR PCP_TRACE_HEADER PCP_TRACE_DATA
+ ;;
+
+ start|on)
+ # series of sanity checks first
+ [ -n "${BASH_VERSION}" ] || return 0 # wrong shell
+ [ "${BASH_VERSINFO[0]}" -ge 4 ] || return 0 # no support
+ [ "${BASH_VERSINFO[0]}" -ne 4 -o "${BASH_VERSINFO[1]}" -ge 2 ] || \
+ return 0 # no support
+ [ -z "${PCP_TMP_DIR}" ] && return 0 # incorrect setup
+ [ -z "${PCP_TRACE_DIR}" ] && pcp_trace init $@ # not yet tracing
+ [ -d "${PCP_TRACE_DIR}" ] || return 0 # no pcp pmda yet
+
+ # is this the child of a traced shell?
+ [ -e /proc/self/fd/99 ] && pcp_trace init $@
+
+ trap "pcp_trace on" 13 # reset on sigpipe (consumer died)
+ printf -v PCP_START '%(%s)T' -2
+ mkfifo -m 600 "${PCP_TRACE_DATA}" 2>/dev/null || return 0
+ # header: version, command, parent, and start time
+ echo "version:1 ppid:${PPID} date:${PCP_START} + $@" \
+ > "${PCP_TRACE_HEADER}" || return 0
+ # setup link between xtrace & fifo
+ exec 99>"${PCP_TRACE_DATA}"
+ BASH_XTRACEFD=99 # magic bash environment variable
+ set -o xtrace # start tracing from here onward
+ # traces: time, line#, and (optionally) function
+ PS4='time:${SECONDS} line:${LINENO} func:${FUNCNAME[0]-} + '
+ ;;
+ esac
+}
diff --git a/src/pmdas/bash/event.c b/src/pmdas/bash/event.c
new file mode 100644
index 0000000..6129e5a
--- /dev/null
+++ b/src/pmdas/bash/event.c
@@ -0,0 +1,434 @@
+/*
+ * Event support for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott. All rights reversed.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <ctype.h>
+#include "event.h"
+#include "pmda.h"
+
+static char *prefix = "pmdabash";
+static char *pcptmpdir; /* probably /var/tmp */
+static char pidpath[MAXPATHLEN];
+
+/*
+ * Extract time of creation of the trace files. Initially uses header file
+ * as a reference since its created initially and then never modified again.
+ * Subsequent calls will use last modification to the trace data file.
+ */
+void
+process_stat_timestamp(bash_process_t *process, struct timeval *timestamp)
+{
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ timestamp->tv_sec = process->stat.st_mtime.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtime.tv_nsec / 1000;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ timestamp->tv_sec = process->stat.st_mtimespec.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtimespec.tv_nsec / 1000;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ timestamp->tv_sec = process->stat.st_mtim.tv_sec;
+ timestamp->tv_usec = process->stat.st_mtim.tv_nsec / 1000;
+#else
+!bozo!
+#endif
+}
+
+/*
+ * Parse the header file (/path/.pid) containing xtrace metadata.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static void
+process_head_parser(bash_process_t *verify, const char *buffer, size_t size)
+{
+ char *p = (char *)buffer, *end = (char *)buffer + size - 1;
+ char script[1024];
+ int version = 0;
+ int date = 0;
+
+ p += extract_int(p, "version:", sizeof("version:")-1, &version);
+ p += extract_int(p, "ppid:", sizeof("ppid:")-1, &verify->parent);
+ p += extract_int(p, "date:", sizeof("date:")-1, &date);
+ extract_cmd(p, end - p, "+", sizeof("+")-1, script, sizeof(script));
+
+ if (date) {
+ /* Use the given starttime of the script from the header */
+ verify->starttime.tv_sec = date;
+ verify->starttime.tv_usec = 0;
+ } else {
+ /* Use a timestamp from the header as a best-effort guess */
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ verify->starttime.tv_sec = verify->stat.st_mtime.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtime.tv_nsec / 1000;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ verify->starttime.tv_sec = verify->stat.st_mtimespec.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtimespec.tv_nsec / 1000;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ verify->starttime.tv_sec = verify->stat.st_mtim.tv_sec;
+ verify->starttime.tv_usec = verify->stat.st_mtim.tv_nsec / 1000;
+#else
+!bozo!
+#endif
+ }
+ verify->version = version;
+
+ size = 16 + strlen(script); /* pid and script name */
+ verify->instance = malloc(size);
+ snprintf(verify->instance, size, "%u %s", verify->pid, script);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process header v%d: inst='%s' ppid=%d",
+ verify->version, verify->instance, verify->parent);
+}
+
+/*
+ * Verify the header file (/path/.pid) containing xtrace metadata.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static int
+process_head_verify(const char *filename, bash_process_t *verify)
+{
+ size_t size;
+ char buffer[1024];
+ int fd = open(filename, O_RDONLY);
+
+ if (fd < 0)
+ return fd;
+ if (fstat(fd, &verify->stat) < 0 || !S_ISREG(verify->stat.st_mode)) {
+ close(fd);
+ return -1;
+ }
+
+ size = read(fd, buffer, sizeof(buffer));
+ if (size > 0)
+ process_head_parser(verify, buffer, size);
+ close(fd);
+
+ /* make sure we only parse header/trace file formats we understand */
+ if (verify->version < MINIMUM_VERSION || verify->version > MAXIMUM_VERSION)
+ return -1;
+ return 0;
+}
+
+/*
+ * Verify the data files associated with a traced bash process.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static int
+process_verify(const char *bashname, bash_process_t *verify)
+{
+ int fd;
+ char *endnum;
+ char path[MAXPATHLEN];
+ struct stat stat;
+
+ verify->pid = (pid_t) strtoul(bashname, &endnum, 10);
+ if (*endnum != '\0' || verify->pid < 1)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s%c.%s", pidpath, __pmPathSeparator(), bashname);
+ if (process_head_verify(path, verify) < 0)
+ return -1;
+
+ snprintf(path, sizeof(path), "%s%c%s", pidpath, __pmPathSeparator(), bashname);
+ if ((fd = open(path, O_RDONLY | O_NONBLOCK)) < 0)
+ return -1;
+ if (fstat(fd, &stat) < 0 || !S_ISFIFO(stat.st_mode)) {
+ close(fd);
+ return -1;
+ }
+ verify->fd = fd;
+ return 0;
+}
+
+/*
+ * Finally allocate memory for a verified, traced bash process.
+ * Helper routine, used during initialising of a tracked shell.
+ */
+static bash_process_t *
+process_alloc(const char *bashname, bash_process_t *init, int numclients)
+{
+ int queueid = pmdaEventNewActiveQueue(bashname, bash_maxmem, numclients);
+ bash_process_t *bashful = malloc(sizeof(bash_process_t));
+
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "process_alloc: %s, queueid=%d", bashname, queueid);
+
+ if (!bashful) {
+ __pmNotifyErr(LOG_ERR, "process allocation out of memory");
+ return NULL;
+ }
+ if (queueid < 0) {
+ __pmNotifyErr(LOG_ERR, "attempt to dup queue for %s", bashname);
+ free(bashful);
+ return NULL;
+ }
+
+ /* Tough access situation - how to log without this? */
+ pmdaEventSetAccess(pmdaGetContext(), queueid, 1);
+
+ bashful->fd = init->fd;
+ bashful->pid = init->pid;
+ bashful->parent = init->parent;
+ bashful->queueid = queueid;
+ bashful->exited = 0;
+ bashful->finished = 0;
+ bashful->noaccess = 0;
+ bashful->version = init->version;
+ bashful->padding = 0;
+
+ memcpy(&bashful->starttime, &init->starttime, sizeof(struct timeval));
+ memcpy(&bashful->stat, &init->stat, sizeof(struct stat));
+ /* copy of first stat time, identifies first event */
+ process_stat_timestamp(bashful, &bashful->startstat);
+ /* copy of pointer to dynamically allocated memory */
+ bashful->instance = init->instance;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_alloc: %s", bashful->instance);
+
+ return bashful;
+}
+
+int
+event_start(bash_process_t *bp, struct timeval *timestamp)
+{
+ int start = memcmp(timestamp, &bp->startstat, sizeof(*timestamp));
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "check start event for %s (%d), %ld vs %ld",
+ bp->instance, start, bp->startstat.tv_sec, timestamp->tv_sec);
+
+ return start == 0;
+}
+
+/*
+ * Initialise a bash process data structure using the header and
+ * trace file. Ready to accept event traces from this shell on
+ * completion of this routine - file descriptor setup, structure
+ * filled with all metadata (exported) about this process.
+ * Note: this is using an on-stack process structure, only if it
+ * all checks out will we allocate memory for it, and keep it.
+ */
+static int
+process_init(const char *bashname, bash_process_t **bp)
+{
+ bash_process_t init = { 0 };
+
+ pmAtomValue atom;
+ pmdaEventClients(&atom);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_init: %s (%d clients)",
+ bashname, atom.ul);
+
+ if (process_verify(bashname, &init) < 0)
+ return -1;
+ *bp = process_alloc(bashname, &init, atom.ul);
+ if (*bp == NULL)
+ return -1;
+ return 0;
+}
+
+static int
+process_read(bash_process_t *process)
+{
+ int j;
+ char *s, *p;
+ size_t offset;
+ ssize_t bytes;
+ struct timeval timestamp;
+
+ static char *buffer;
+ static int bufsize;
+
+ /*
+ * Using a static (global) event buffer to hold initial read.
+ * The aim is to reduce memory allocation until we know we'll
+ * need to keep something.
+ */
+ if (!buffer) {
+ int sts = 0;
+ bufsize = 16 * getpagesize();
+#ifdef HAVE_POSIX_MEMALIGN
+ sts = posix_memalign((void **)&buffer, getpagesize(), bufsize);
+#else
+#ifdef HAVE_MEMALIGN
+ buffer = (char *)memalign(getpagesize(), bufsize);
+ if (buffer == NULL) sts = -1;
+#else
+ buffer = (char *)malloc(bufsize);
+ if (buffer == NULL) sts = -1;
+#endif
+#endif
+ if (sts != 0) {
+ __pmNotifyErr(LOG_ERR, "event buffer allocation failure");
+ return -1;
+ }
+ }
+
+ offset = 0;
+multiread:
+ if (process->fd < 0)
+ return 0;
+
+ bytes = read(process->fd, buffer + offset, bufsize - 1 - offset);
+
+ /*
+ * Ignore the error if:
+ * - we've got EOF (0 bytes read)
+ * - EAGAIN/EWOULDBLOCK (fd is marked nonblocking and read would block)
+ */
+ if (bytes == 0)
+ return 0;
+ if (bytes < 0 && (errno == EBADF))
+ return 0;
+ if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+ return 0;
+ if (bytes > bash_maxmem)
+ return 0;
+ if (bytes < 0) {
+ __pmNotifyErr(LOG_ERR, "read failure on process %s: %s",
+ process->instance, strerror(errno));
+ return -1;
+ }
+
+ process_stat_timestamp(process, &timestamp);
+
+ buffer[bufsize-1] = '\0';
+ for (s = p = buffer, j = 0; *s != '\0' && j < bufsize-1; s++, j++) {
+ if (*s != '\n')
+ continue;
+ *s = '\0';
+ bytes = (s+1) - p;
+ pmdaEventQueueAppend(process->queueid, p, bytes, &timestamp);
+ p = s + 1;
+ }
+ /* did we just do a full buffer read? */
+ if (p == buffer) {
+ char msg[64];
+ __pmNotifyErr(LOG_ERR, "Ignoring long (%d bytes) line: \"%s\"", (int)
+ bytes, __pmdaEventPrint(p, bytes, msg, sizeof(msg)));
+ } else if (j == bufsize - 1) {
+ offset = bufsize-1 - (p - buffer);
+ memmove(buffer, p, offset);
+ goto multiread; /* read rest of line */
+ }
+ return 1;
+}
+
+static void
+process_unlink(bash_process_t *process, const char *bashname)
+{
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "%s%c%s", pidpath, __pmPathSeparator(), bashname);
+ unlink(path);
+ snprintf(path, sizeof(path), "%s%c.%s", pidpath, __pmPathSeparator(), bashname);
+ unlink(path);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_unlink: removed %s", bashname);
+}
+
+static int
+process_drained(bash_process_t *process)
+{
+ pmAtomValue value = { 0 };
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_queue_drained check on queue %d (pid %d)",
+ process->queueid, process->pid);
+ if (pmdaEventQueueMemory(process->queueid, &value) < 0)
+ return 1; /* error, consider it drained and cleanup */
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_queue_drained: %s (%llu)", value.ll?"n":"y", (long long)value.ull);
+ return value.ull == 0;
+}
+
+static void
+process_done(bash_process_t *process, const char *bashname)
+{
+ struct timeval timestamp;
+
+ if (process->exited == 0) {
+ process->exited = (__pmProcessExists(process->pid) == 0);
+
+ if (!process->exited)
+ return;
+ /* empty event inserted into queue to denote bash has exited */
+ if (!process->finished) {
+ process->finished = 1; /* generate no further events */
+ process_stat_timestamp(process, &timestamp);
+ pmdaEventQueueAppend(process->queueid, NULL, 0, &timestamp);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "process_done: marked queueid %d (pid %d) done",
+ process->queueid, process->pid);
+ }
+ }
+
+ if (process->finished) {
+ /* once all clients have seen final events, clean named queue */
+ if (process_drained(process))
+ process_unlink(process, bashname);
+ }
+}
+
+void
+event_refresh(pmInDom bash_indom)
+{
+ struct dirent **files;
+ bash_process_t *bp;
+ int i, id, sts, num = scandir(pidpath, &files, NULL, NULL);
+
+ if (pmDebug & DBG_TRACE_APPL0 && num > 2)
+ __pmNotifyErr(LOG_DEBUG, "event_refresh: phase1: %d files", num - 2);
+
+ pmdaCacheOp(bash_indom, PMDA_CACHE_INACTIVE);
+
+ /* (re)activate processes that are actively generating events */
+ for (i = 0; i < num; i++) {
+ char *processid = files[i]->d_name;
+
+ if (processid[0] == '.')
+ continue;
+
+ /* either create or re-activate a bash process structure */
+ sts = pmdaCacheLookupName(bash_indom, processid, &id, (void **)&bp);
+ if (sts != PMDA_CACHE_INACTIVE) {
+ if (process_init(processid, &bp) < 0)
+ continue;
+ }
+ pmdaCacheStore(bash_indom, PMDA_CACHE_ADD, bp->instance, (void *)bp);
+
+ /* read any/all new events for this bash process, enqueue 'em */
+ process_read(bp);
+
+ /* check if process is running and generate end marker if not */
+ process_done(bp, files[i]->d_name);
+ }
+
+ for (i = 0; i < num; i++)
+ free(files[i]);
+ if (num > 0)
+ free(files);
+}
+
+void
+event_init(void)
+{
+ pcptmpdir = pmGetConfig("PCP_TMP_DIR");
+ sprintf(pidpath, "%s%c%s", pcptmpdir, __pmPathSeparator(), prefix);
+}
diff --git a/src/pmdas/bash/event.h b/src/pmdas/bash/event.h
new file mode 100644
index 0000000..f36b5b4
--- /dev/null
+++ b/src/pmdas/bash/event.h
@@ -0,0 +1,65 @@
+/*
+ * Event support for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott. All rights reversed.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _EVENT_H
+#define _EVENT_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+
+typedef struct bash_process {
+ int fd;
+ pid_t pid;
+ pid_t parent;
+ int queueid;
+
+ int exited : 1; /* flag: process running? */
+ int finished: 1; /* flag: exit event sent? */
+ int noaccess: 1; /* flag: store-to-access? */
+ int version : 8; /* pmda <-> bash xtrace version */
+ int padding : 21; /* filler */
+
+ struct timeval starttime; /* timestamp trace started */
+ struct timeval startstat; /* timestamp of first stat */
+ struct stat stat; /* stat of the header file */
+
+ char *instance; /* process id, space, script */
+} bash_process_t;
+
+typedef struct bash_trace {
+ int flags;
+ struct timeval timestamp;
+ int line;
+ char function[64];
+ char command[512];
+} bash_trace_t;
+
+extern long bash_maxmem;
+
+extern void event_init(void);
+extern void event_refresh(pmInDom);
+extern int event_start(bash_process_t *, struct timeval *);
+extern void process_stat_timestamp(bash_process_t *, struct timeval *);
+
+#define MINIMUM_VERSION 1
+#define MAXIMUM_VERSION 1
+
+extern int extract_int(char *, const char *, size_t, int *);
+extern int extract_str(char *, size_t, const char *, size_t, char *, size_t);
+extern int extract_cmd(char *, size_t, const char *, size_t, char *, size_t);
+
+#endif /* _EVENT_H */
diff --git a/src/pmdas/bash/help b/src/pmdas/bash/help
new file mode 100644
index 0000000..953d214
--- /dev/null
+++ b/src/pmdas/bash/help
@@ -0,0 +1,48 @@
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# bash PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ BASH.0 Instance domain of traced bash processes
+An instance domain representing all of the currently running bash
+processes which are exporting trace metrics.
+
+@ bash.xtrace.numclients
+The number of attached clients.
+
+@ bash.xtrace.maxmem
+Maximum number of queued event bytes for each log file.
+
+@ bash.xtrace.queuemem
+@ bash.xtrace.count
+@ bash.xtrace.records
+@ bash.xtrace.parameters.pid
+@ bash.xtrace.parameters.parent
+@ bash.xtrace.parameters.lineno
+@ bash.xtrace.parameters.function
+@ bash.xtrace.parameters.command
+
diff --git a/src/pmdas/bash/pcp.sh b/src/pmdas/bash/pcp.sh
new file mode 100755
index 0000000..cef1e11
--- /dev/null
+++ b/src/pmdas/bash/pcp.sh
@@ -0,0 +1,30 @@
+# Shell interface to PCP shell event tracing PMDA
+
+if [ -z "$PCP_SH_DONE" ]
+then
+ if [ -n "$PCP_CONF" ]
+ then
+ __CONF="$PCP_CONF"
+ elif [ -n "$PCP_DIR" ]
+ then
+ __CONF="$PCP_DIR/etc/pcp.conf"
+ else
+ __CONF=/etc/pcp.conf
+ fi
+ if [ ! -f "$__CONF" ]
+ then
+ echo "pcp.env: Fatal Error: \"$__CONF\" not found" >&2
+ exit 1
+ fi
+ eval `sed -e 's/"//g' $__CONF \
+ | awk -F= '
+/^PCP_/ && NF == 2 {
+ exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+} END {
+ print "export", exports
+}'`
+ export PCP_ENV_DONE=y
+fi
+
+. $PCP_SHARE_DIR/lib/bashproc.sh
diff --git a/src/pmdas/bash/pmns b/src/pmdas/bash/pmns
new file mode 100644
index 0000000..a26790c
--- /dev/null
+++ b/src/pmdas/bash/pmns
@@ -0,0 +1,36 @@
+/*
+ * Metrics for bash xtrace PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+bash {
+ xtrace
+}
+
+bash.xtrace {
+ numclients BASH:0:0
+ maxmem BASH:0:1
+ queuemem BASH:0:2
+ count BASH:0:3
+ records BASH:0:4
+ parameters
+}
+
+bash.xtrace.parameters {
+ pid BASH:0:5
+ parent BASH:0:6
+ lineno BASH:0:7
+ function BASH:0:8
+ command BASH:0:9
+}
diff --git a/src/pmdas/bash/root b/src/pmdas/bash/root
new file mode 100644
index 0000000..2e66cb4
--- /dev/null
+++ b/src/pmdas/bash/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { bash }
+
+#include "pmns"
+
diff --git a/src/pmdas/bash/test-child.sh b/src/pmdas/bash/test-child.sh
new file mode 100755
index 0000000..2b5d482
--- /dev/null
+++ b/src/pmdas/bash/test-child.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.sh
+pcp_trace on $0 $@
+
+wired()
+{
+ # burn a little CPU, then sleep
+ for i in 0 1 2 3 4 5 6 7 8 9 0
+ do
+ /bin/true && /bin/true
+ done
+ sleep $1
+}
+
+count=0
+while true
+do
+ (( count++ ))
+ echo "get busy, $count" # top level
+ wired 2 # call a shell function
+ [ $count -ge 10 ] && break
+done
+
+pcp_trace off
+exit 0
diff --git a/src/pmdas/bash/test-trace.sh b/src/pmdas/bash/test-trace.sh
new file mode 100755
index 0000000..6ff89b7
--- /dev/null
+++ b/src/pmdas/bash/test-trace.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+#
+# Copyright (c) 2012 Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.sh
+pcp_trace on $0 $@
+
+tired()
+{
+ sleep $1
+}
+
+count=0
+while true
+do
+ (( count++ ))
+ echo "awoke, $count" # top level
+ tired 2 # call a shell function
+ branch=$(( count % 3 ))
+ case $branch
+ in
+ 0) ./test-child.sh $count &
+ ;;
+ 2) wait
+ ;;
+ esac
+done
+
+pcp_trace off
+exit 0
diff --git a/src/pmdas/bash/util.c b/src/pmdas/bash/util.c
new file mode 100644
index 0000000..3284579
--- /dev/null
+++ b/src/pmdas/bash/util.c
@@ -0,0 +1,84 @@
+/*
+ * Utility routines for the bash tracing PMDA
+ *
+ * Copyright (c) 2012 Nathan Scott.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "event.h"
+#include "pmda.h"
+#include <ctype.h>
+
+int
+extract_int(char *s, const char *field, size_t length, int *value)
+{
+ char *endnum;
+ int num;
+
+ if (strncmp(s, field, length) != 0)
+ return 0;
+ num = strtol(s + length, &endnum, 10);
+ if (*endnum != ',' && *endnum != '\0' && !isspace((int)*endnum))
+ return 0;
+ *value = num;
+ return endnum - s + 1;
+}
+
+int
+extract_str(char *s, size_t end, const char *field, size_t length, char *value, size_t vsz)
+{
+ char *p;
+
+ if (strncmp(s, field, length) != 0)
+ return 0;
+ p = s + length;
+ while (*p != ',' && *p != '\0' && !isspace((int)*p))
+ p++;
+ *p = '\0';
+ strncpy(value, s + length, vsz);
+ return p - s + 1;
+}
+
+int
+extract_cmd(char *s, size_t end, const char *field, size_t length, char *value, size_t vsz)
+{
+ char *start = NULL, *stop = NULL, *p;
+ int len;
+
+ /* find the start of the command */
+ for (p = s; p < s + end; p++) {
+ if (strncmp(p, field, length) != 0)
+ continue;
+ p++;
+ if (*p == ' ')
+ p++;
+ break;
+ }
+ if (p == s + end)
+ return 0;
+ start = p;
+
+ /* find the command terminator */
+ while (*p != '\n' && *p != '\0' && p < s + end)
+ p++;
+ stop = p;
+
+ /* truncate it if necessary */
+ len = stop - start;
+ if (len > vsz - 1)
+ len = vsz - 1;
+
+ /* copy it over to "value" */
+ start[len] = '\0';
+ strncpy(value, start, len + 1);
+ return len;
+}
diff --git a/src/pmdas/bonding/GNUmakefile b/src/pmdas/bonding/GNUmakefile
new file mode 100644
index 0000000..be7f4b6
--- /dev/null
+++ b/src/pmdas/bonding/GNUmakefile
@@ -0,0 +1,56 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = bonding
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+else
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/bonding/Install b/src/pmdas/bonding/Install
new file mode 100755
index 0000000..1abb843
--- /dev/null
+++ b/src/pmdas/bonding/Install
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Install the Bonding PMDA (bonded network interfaces)
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=bonding
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+if ! test -d /sys/class/net; then
+ echo "SYSFS not enabled in your kernel"
+ exit 1
+fi
+if ! test -f /sys/class/net/bonding_masters; then
+ echo "Bonding statistics unavailable (load bonding module)"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/bonding/Remove b/src/pmdas/bonding/Remove
new file mode 100755
index 0000000..04385a5
--- /dev/null
+++ b/src/pmdas/bonding/Remove
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the bonding PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=bonding
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/bonding/pmdabonding.pl b/src/pmdas/bonding/pmdabonding.pl
new file mode 100644
index 0000000..3a0a0e5
--- /dev/null
+++ b/src/pmdas/bonding/pmdabonding.pl
@@ -0,0 +1,154 @@
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my $pmda = PCP::PMDA->new('bonding', 96);
+my $sysfs = '/sys/class/net/';
+
+sub bonding_interface_check
+{
+ my @interfaces = ();
+ my $instanceid = 0;
+
+ if (open(BONDS, $sysfs . 'bonding_masters')) {
+ my @bonds = split / /, <BONDS>;
+ foreach my $bond (@bonds) {
+ chomp $bond;
+ push @interfaces, $instanceid++, $bond;
+ }
+ close BONDS;
+ }
+ $pmda->replace_indom(0, \@interfaces);
+}
+
+sub bonding_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ($path, $name, $value, $fh, @vals);
+
+ #$pmda->log("bonding_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+
+ # special case: failures count from /proc (no sysfs equivalent)
+ if ($item == 7) {
+ $value = 0;
+ $name = '/proc/net/bonding/' . 'bond' . $inst;
+ open($fh, $name) || return (PM_ERR_APPVERSION, 0);
+ while (<$fh>) {
+ if (m/^Link Failure Count: (\d+)$/) { $value += $1; }
+ }
+ close $fh;
+ } else {
+ $name = $metric_name;
+ $path = $sysfs . 'bond' . $inst . '/bonding/';
+ $name =~ s/^bonding\./$path/;
+
+ # special case: mode contains two values (name and type)
+ if ($item == 5) { $name =~ s/\.type$//; }
+ if ($item == 6) { $name =~ s/\.name$//; }
+
+ open($fh, $name) || return (PM_ERR_APPVERSION, 0);
+ $value = <$fh>;
+ close $fh;
+
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ if ($item == 5) { @vals = split / /, $value; $value = $vals[1]; }
+ if ($item == 6) { @vals = split / /, $value; $value = $vals[0]; }
+ chomp $value;
+ }
+
+ return ($value, 1);
+}
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING, 0, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), "bonding.slaves", '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, 0, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), "bonding.active_slave", '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, 0, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), "bonding.use_carrier", '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, 0, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), "bonding.updelay", '', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, 0, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), "bonding.downdelay", '', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, 0, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), "bonding.mode.type", '', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_STRING, 0, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), "bonding.mode.name", '', '');
+$pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U32, 0, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), "bonding.failures", '', '');
+
+$pmda->add_indom(0, [], '', '');
+
+$pmda->set_fetch(\&bonding_interface_check);
+$pmda->set_instance(\&bonding_interface_check);
+$pmda->set_fetch_callback(\&bonding_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdabonding - Linux bonded interface performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdabonding> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from bonded network interfaces in the Linux
+kernel.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the bonding performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/bonding
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/bonding
+ # ./Remove
+
+B<pmdabonding> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/bonding/Install
+
+installation script for the B<pmdabonding> agent
+
+=item $PCP_PMDAS_DIR/bonding/Remove
+
+undo installation script for the B<pmdabonding> agent
+
+=item $PCP_LOG_DIR/pmcd/bonding.log
+
+default log file for error messages from B<pmdabonding>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and ifenslave(8).
diff --git a/src/pmdas/cisco/Cisco.pmchart b/src/pmdas/cisco/Cisco.pmchart
new file mode 100644
index 0000000..6baf4e7
--- /dev/null
+++ b/src/pmdas/cisco/Cisco.pmchart
@@ -0,0 +1,11 @@
+#pmchart
+Version 1.2 host dynamic
+
+Chart Style plot
+ Plot Color #-cycle Host * Metric cisco.rate_in
+
+Chart Style plot
+ Plot Color #-cycle Host * Metric cisco.rate_out
+
+#
+# Created Mon Sep 1 11:34:14 1997
diff --git a/src/pmdas/cisco/GNUmakefile b/src/pmdas/cisco/GNUmakefile
new file mode 100644
index 0000000..e6c2733
--- /dev/null
+++ b/src/pmdas/cisco/GNUmakefile
@@ -0,0 +1,70 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = cisco
+DOMAIN = CISCO
+TARGETS = $(IAM)$(EXECSUFFIX) probe$(EXECSUFFIX)
+CFILES = cisco.c pmda.c interface.c telnet.c
+HFILES = cisco.h
+DFILES = README
+LSRCFILES=Install Remove root pmns Cisco.pmchart help Samples Tested $(DFILES) \
+ cisco.in_util.pmie cisco.out_util.pmie probe.c parse.sh
+
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_PTHREADS)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+PMIEDIR = $(PCP_VAR_DIR)/config/pmieconf/$(IAM)
+
+LDIRT = domain.h cisco.log *.dir *.pag so_locations a.out probe.o $(TARGETS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(TARGETS)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 parse.sh $(PMDADIR)/parse
+ $(INSTALL) -m 755 probe Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns domain.h $(PMDADIR)
+ $(INSTALL) -m 644 Cisco.pmchart $(PMCHART)/Cisco
+ $(INSTALL) -m 755 -d $(PMIEDIR)
+ $(INSTALL) -m 644 cisco.in_util.pmie $(PMIEDIR)/in_util
+ $(INSTALL) -m 644 cisco.out_util.pmie $(PMIEDIR)/out_util
+else
+build-me:
+install:
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+probe$(EXECSUFFIX): probe.o interface.o telnet.o
+ $(CCF) -o $@ $(LDFLAGS) probe.o interface.o telnet.o $(LDLIBS)
+
+cisco.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/cisco/Install b/src/pmdas/cisco/Install
new file mode 100644
index 0000000..5ee4742
--- /dev/null
+++ b/src/pmdas/cisco/Install
@@ -0,0 +1,156 @@
+#! /bin/sh
+#
+# Copyright (c) 1997-2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the cisco PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=cisco
+pmda_interface=2
+forced_restart=false
+
+# Do it
+#
+pmdaSetup
+
+check_delay=20
+pollrate=120 # poll each Cisco interface once every 120 seconds
+ # -- change this if desired
+
+# special cisco PMDA args
+
+if $do_pmda
+then
+ args="-r$pollrate"
+ default="wanptg"
+ while true
+ do
+ no_host=true
+ while $no_host
+ do
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Cisco hostname or IP address? [return to quit Cisco selection] ""$PCP_ECHO_C"
+ read host
+ [ "X$host" = X ] && break
+ echo '
+A username and/or user-level password may be required for the Cisco
+"show interface" command.
+ If you are unsure, try the command
+ $ telnet '$host'
+ and if the prompt "Username:" appears, then a username is required,
+ and if the prompt "Password:" appears, a user-level password is required,
+ otherwise respond with an empty line for the next two questions.
+ Once logged in, we need to know the termination string for the command
+ line prompt (one or more unique characters at the end of the prompt) -
+ the default is ">", but if this is not correct, enter the prompt
+ termination string also.
+'
+ $PCP_ECHO_PROG $PCP_ECHO_N "Cisco username? ""$PCP_ECHO_C"
+ read username
+ userarg=""
+ [ "X$username" != X ] && userarg="-U$username"
+ $PCP_ECHO_PROG $PCP_ECHO_N "User-level Cisco password? ""$PCP_ECHO_C"
+ read passwd
+ passarg=""
+ [ "X$passwd" != X ] && passarg="-P$passwd"
+ $PCP_ECHO_PROG $PCP_ECHO_N "Cisco command line prompt termination? [>] ""$PCP_ECHO_C"
+ read prompt
+ promptarg=""
+ [ "X$prompt" != X ] && promptarg="-s'$prompt'"
+ echo "Probing Cisco for list of interfaces ..."
+ for try in 1 2 3
+ do
+ intf=`eval ./probe $userarg $passarg $promptarg $host 2>$tmp/err`
+ [ ! -z "$intf" ] && break
+ sleep 2
+ done
+ if [ -z "$intf" ]
+ then
+ echo '
+There appears to be a problem ... after three attempts could not get
+interfaces. Output at the last attempt was:'
+ sed -e 's/^/ /' <$tmp/err
+ echo '
+You may wish to try the following commands to identify the configured
+interfaces for this Cisco.
+ $ telnet '$host'
+ ....> terminal length 0
+ ....> show interface
+ ....> quit
+'
+ else
+ no_host=false
+ fi
+ done
+ [ "X$host" = X ] && break
+
+ if [ -z "$username" ]
+ then
+ login=""
+ else
+ login="@$username"
+ fi
+ [ ! -z "$passwd" ] && login="$login?$passwd"
+ [ ! -z "$prompt" ] && login="$login!$prompt"
+
+ echo '
+Enter interfaces to monitor, one per line in the format tX where "t" is
+a type and one of "e" (Ethernet), "E" (FastEthernet), "f" (Fddi), "s"
+(Serial), "a" (ATM), "B" (ISDN BRI) or "h" (HSSC) and "X" is an
+interface identifier which is either an integer (e.g. 4000 Series
+routers) or two integers separated by a slash (e.g. 7000 Series
+routers).'
+
+ while true
+ do
+ echo
+ echo 'The currently unselected interfaces for the Cisco "'$host'" are:'
+ echo "$intf" | fmt | sed -e 's/^/ /'
+ echo 'Enter "*" to select all, "quit" to terminate selections for this Cisco.'
+
+ first=`echo "$intf" | sed -e 's/ .*//'`
+ [ -z "$first" ] && first=quit
+ $PCP_ECHO_PROG $PCP_ECHO_N "Interface? [$first] ""$PCP_ECHO_C"
+ read ans
+ [ "X$ans" = Xquit ] && break
+ if [ "X$ans" = "X*" ]
+ then
+ # do them all
+ for ans in `echo "$intf"`
+ do
+ args="$args $host:$ans$login"
+ login=''
+ done
+ break
+ fi
+ [ -z "$ans" ] && ans="$first"
+ if echo " $intf" | grep " $ans" >/dev/null
+ then
+ sed_ans=`echo $ans | sed -e 's;/;\\\\/;g'`
+ intf=`echo " $intf" | sed -e "s/ $sed_ans//" -e 's/^ //'`
+ else
+ echo "Warning: $ans is not in the list, I hope you know what you're doing"
+ fi
+ args="$args $host:$ans$login"
+ login=''
+ [ -z "$intf" ] && break
+ done
+ done
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/cisco/README b/src/pmdas/cisco/README
new file mode 100644
index 0000000..908c837
--- /dev/null
+++ b/src/pmdas/cisco/README
@@ -0,0 +1,76 @@
+Performance Co-Pilot PMDA for Monitoring Cisco Routers
+======================================================
+
+This PMDA is capable of collecting throughput measures from Cisco
+routers throughout a network.
+
+The PMDA needs to be configured to collect performance data from a
+designated set of interfaces on nominated Cisco routers. There is one
+instance of the metrics for nominated each router-interface pair.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT cisco
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/cisco
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + The Cisco PMDA is one that polls the Cisco routers, and caches the
+ most recent value for the performance metrics. The cached values
+ are the ones returned via the PMCD to clients requesting Cisco
+ performance metrics. The default polling rate is once every two
+ minutes to each Cisco interface being monitored, if you wish to
+ change this, edit Install and change the value of pollrate near the
+ start of the script.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted to identify the Cisco routers and
+ interfaces you wish to monitor.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/cisco
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/cisco.log) should be checked for any warnings
+ or errors.
+
+ + The configured interfaces on a particular Cisco may be discovered
+ using the $PCP_PMDAS_DIR/cisco/probe application. See pmdacisco(1)
+ for more details.
+
+ + The $PCP_PMDAS_DIR/cisco/parse application supports the same
+ command line options as pmdacisco for identifying interfaces on
+ Cisco devices and shares the parser for the output from the Cisco
+ "show interface" command; this application may be used to gain
+ diagnostic traces of the interaction between the parser code and
+ the Cisco device in the event that metrics values are not being
+ returned by the Cisco PMDA. See pmdacisco(1) for more details.
diff --git a/src/pmdas/cisco/Remove b/src/pmdas/cisco/Remove
new file mode 100644
index 0000000..0f4f109
--- /dev/null
+++ b/src/pmdas/cisco/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the cisco PMDA and/or PMNS
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=cisco
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/cisco/Samples b/src/pmdas/cisco/Samples
new file mode 100644
index 0000000..dd25be6
--- /dev/null
+++ b/src/pmdas/cisco/Samples
@@ -0,0 +1,291 @@
+
+Serial 0 is up, line protocol is up
+ Hardware is MK5025
+ Description: frame-relay to MtVA, Tokyo, Hong Kong
+ Internet address is 155.11.201.172, subnet mask is 255.255.255.128
+ MTU 1500 bytes, BW 64 Kbit, DLY 20000 usec, rely 255/255, load 15/255
+ Encapsulation FRAME-RELAY, loopback not set, keepalive set (10 sec)
+ LMI DLCI 0, LMI sent 346516, LMI stat recvd 345070, LMI upd recvd 2
+ LMI type is ANSI Annex D
+ Last input 0:00:01, output 0:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/8, 298580 drops; input queue 0/75, 0 drops
+ Five minute input rate 60000 bits/sec, 16 packets/sec
+ Five minute output rate 4000 bits/sec, 12 packets/sec
+ 18139408 packets input, 2763796661 bytes, 0 no buffer
+ Received 0 broadcasts, 0 runts, 0 giants
+ 189 input errors, 189 CRC, 0 frame, 0 overrun, 0 ignored, 0 abort
+ 15860554 packets output, 3098844973 bytes, 0 underruns
+ 0 output errors, 0 collisions, 668 interface resets, 0 restarts
+ 686 carrier transitions
+
+Ethernet 0 is up, line protocol is up
+ Hardware is Lance, address is 0000.0c03.ed85 (bia 0000.0c03.ed85)
+ Description: Sydney, Australia
+ Internet address is 155.11.226.3, subnet mask is 255.255.255.128
+ MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec, rely 255/255, load 1/255
+ Encapsulation ARPA, loopback not set, keepalive set (10 sec)
+ ARP type: ARPA, ARP Timeout 4:00:00
+ Last input 0:00:00, output 0:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/40, 19188 drops; input queue 1/75, 0 drops
+ Five minute input rate 5000 bits/sec, 10 packets/sec
+ Five minute output rate 60000 bits/sec, 13 packets/sec
+ 12884820 packets input, 1321046112 bytes, 0 no buffer
+ Received 1570813 broadcasts, 0 runts, 0 giants
+ 891 input errors, 891 CRC, 634 frame, 0 overrun, 0 ignored, 0 abort
+ 14717105 packets output, 870622060 bytes, 0 underruns
+ 98605 output errors, 1883820 collisions, 1 interface resets, 0 restarts
+
+Fddi2/0 is up, line protocol is up
+ Hardware is cxBus FDDI, address is 0000.0c38.4360 (bia 0000.0c38.4360)
+ Internet address is 192.26.80.22, subnet mask is 255.255.255.0
+ MTU 4470 bytes, BW 100000 Kbit, DLY 100 usec, rely 255/255, load 1/255
+ Encapsulation SNAP, loopback not set, keepalive not set
+ ARP type: SNAP, ARP Timeout 4:00:00
+ Phy-A state is connect, neighbor is unk, cmt signal bits 008/000, status QLS
+ Phy-B state is active, neighbor is M, cmt signal bits 20C/00E, status ILS
+ CFM is wrap B, token rotation 5000 usec, ring operational 0:00:58
+ Upstream neighbor 0800.6904.155d, downstream neighbor 0040.0b80.a052
+ Last input 0:00:00, output 0:00:00, output hang never
+ Last clearing of "show interface" counters 1w0d
+ Output queue 0/40, 85 drops; input queue 0/75, 33437 drops
+ Five minute input rate 160000 bits/sec, 39 packets/sec
+ Five minute output rate 93000 bits/sec, 108 packets/sec
+ 33780622 packets input, 4009491185 bytes, 87 no buffer
+ Received 3768432 broadcasts, 0 runts, 0 giants
+ 2 input errors, 1 CRC, 1 frame, 0 overrun, 19 ignored, 0 abort
+ 83194423 packets output, 612803885 bytes, 492 underruns
+ 0 output errors, 0 collisions, 0 interface resets, 0 restarts
+ 21602 transitions, 0 traces, 11625 claims, 0 beacon
+
+Serial1 is up, line protocol is up
+ Hardware is HD64570
+ Description: Frame-relay, MtV, Hong Kong, Sydney
+ MTU 1500 bytes, BW 1544 Kbit, DLY 20000 usec, rely 255/255, load 5/255
+ Encapsulation FRAME-RELAY, loopback not set, keepalive set (10 sec)
+ LMI enq sent 14998, LMI stat recvd 14885, LMI upd recvd 0, DTE LMI up
+ LMI enq recvd 0, LMI stat sent 0, LMI upd sent 0
+ LMI DLCI 0 LMI type is ANSI Annex D frame relay DTE
+ Broadcast queue 0/200, broadcasts sent/dropped 508604/0
+ Last input 0:00:00, output 0:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/40, 947 drops; input queue 0/75, 0 drops
+ Five minute input rate 117000 bits/sec, 29 packets/sec
+ Five minute output rate 32000 bits/sec, 20 packets/sec
+ 2578327 packets input, 1105345262 bytes, 0 no buffer
+ Received 0 broadcasts, 0 runts, 0 giants
+ 54 input errors, 4 CRC, 0 frame, 0 overrun, 1 ignored, 4 abort
+ 1975049 packets output, 675844616 bytes, 0 underruns
+ 0 output errors, 0 collisions, 142 interface resets, 0 restarts
+ 3 carrier transitions
+ DCD=up DSR=up DTR=up RTS=up CTS=up
+
+Ethernet3/5 is up, line protocol is up
+ Hardware is cxBus Ethernet, address is 0000.0c38.436d (bia 0000.0c38.436d)
+ Internet address is 198.29.108.4, subnet mask is 255.255.255.0
+ MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec, rely 255/255, load 2/255
+ Encapsulation ARPA, loopback not set, keepalive set (10 sec)
+ ARP type: ARPA, ARP Timeout 4:00:00
+ Last input 0:00:03, output 0:00:01, output hang never
+ Last clearing of "show interface" counters 6w2d
+ Output queue 0/40, 54880 drops; input queue 0/75, 1492 drops
+ 5 minute input rate 1000 bits/sec, 0 packets/sec
+ 5 minute output rate 91000 bits/sec, 98 packets/sec
+ 24627812 packets input, 1448819515 bytes, 862 no buffer
+ Received 3959194 broadcasts, 79 runts, 4 giants
+ 292 input errors, 195 CRC, 14 frame, 0 overrun, 0 ignored, 0 abort
+ 0 input packets with dribble condition detected
+ 150470373 packets output, 3227527737 bytes, 0 underruns
+ 1274 output errors, 11605581 collisions, 0 interface resets, 0 restarts
+
+ATM12/0 is up, line protocol is up
+ Hardware is cxBus ATM
+ Internet address is 150.166.230.24 255.255.255.0
+ MTU 4470 bytes, BW 156250 Kbit, DLY 80 usec, rely 255/255, load 1/255
+ Encapsulation ATM, loopback not set, keepalive set (10 sec)
+ Encapsulation(s): AAL5, PVC mode
+ 256 TX buffers, 256 RX buffers, 2048 maximum active VCs, 1024 VCs per VP, 15 current VCCs
+ Last input 0:00:00, output 0:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/40, 511 drops; input queue 0/75, 10612 drops
+ 5 minute input rate 181000 bits/sec, 116 packets/sec
+ 5 minute output rate 160000 bits/sec, 68 packets/sec
+ 658262505 packets input, 1239748718 bytes, 199 no buffer
+ Received 0 broadcasts, 0 runts, 0 giants
+ 2570 input errors, 2569 CRC, 1 frame, 0 overrun, 0 ignored, 0 abort
+ 788426419 packets output, 4188341273 bytes, 0 underruns
+ 0 output errors, 0 collisions, 21 interface resets, 0 restarts
+ 0 output buffer failures, 63 output buffers swapped out
+
+Hssi3/0 is administratively down, line protocol is down
+ Hardware is cxBus HSSI
+ Description: 45 Meg Microwave Link to Building 27
+ Internet address is 150.166.124.1 255.255.255.0
+ MTU 4470 bytes, BW 45045 Kbit, DLY 200 usec, rely 255/255, load 1/255
+ Encapsulation HDLC, loopback not set, keepalive set (10 sec)
+ Last input 1:07:12, output 1:07:08, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/40, 2 drops; input queue 0/75, 37 drops
+ 5 minute input rate 0 bits/sec, 0 packets/sec
+ 5 minute output rate 0 bits/sec, 0 packets/sec
+ 306700515 packets input, 1443052046 bytes, 5 no buffer
+ Received 1118787 broadcasts, 0 runts, 0 giants
+ 0 parity
+ 241 input errors, 0 CRC, 241 frame, 0 overrun, 0 ignored, 0 abort
+ 246381377 packets output, 3684064154 bytes, 0 underruns
+ 0 output errors, 0 applique, 210 interface resets, 0 restarts
+ 0 output buffer failures, 83 output buffers swapped out
+ 8214 carrier transitions
+
+FastEthernet1/0 is up, line protocol is up
+ Hardware is cyBus FastEthernet Interface, address is 0060.3eb0.8c20 (bia 0060.3eb0.8c20)
+ Internet address is 192.111.17.1 255.255.255.0
+ MTU 1500 bytes, BW 100000 Kbit, DLY 100 usec, rely 255/255, load 1/255
+ Encapsulation ARPA, loopback not set, keepalive set (10 sec), fdx, MII
+ ARP type: ARPA, ARP Timeout 4:00:00
+ Last input 0:00:05, output 0:00:05, output hang never
+ Last clearing of "show interface" counters never
+ Output queue 0/40, 0 drops; input queue 0/75, 362 drops
+ 5 minute input rate 8000 bits/sec, 1 packets/sec
+ 5 minute output rate 11000 bits/sec, 0 packets/sec
+ 4615550 packets input, 1942263093 bytes, 33 no buffer
+ Received 1947224 broadcasts, 0 runts, 8 giants
+ 30351 input errors, 30351 CRC, 15187 frame, 0 overrun, 0 ignored, 0 abort
+ 0 watchdog, 27381 multicast
+ 0 input packets with dribble condition detected
+ 11521698 packets output, 1811832796 bytes, 0 underruns
+ 0 output errors, 0 collisions, 2 interface resets, 0 restarts
+ 0 babbles, 0 late collision, 0 deferred
+ 0 lost carrier, 0 no carrier
+ 0 output buffer failures, 0 output buffers swapped out
+
+Serial1/0 is up, line protocol is up
+ Hardware is M4T
+ Description: frame-relay using subinterfaces
+ MTU 1500 bytes, BW 64 Kbit, DLY 20000 usec, rely 255/255, load 11/255
+ Encapsulation FRAME-RELAY, loopback not set, keepalive set (10 sec)
+ LMI enq sent 10084, LMI stat recvd 9997, LMI upd recvd 0, DTE LMI up
+ LMI enq recvd 0, LMI stat sent 0, LMI upd sent 0
+ LMI DLCI 0 LMI type is ANSI Annex D frame relay DTE
+ FR SVC disabled, LAPF state down
+ Broadcast queue 0/200, broadcasts sent/dropped 53152/0, interface broadcasts 49826
+ Last input 00:00:01, output 00:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Queueing strategy: fifo
+ Output queue 0/8, 0 drops; input queue 0/75, 0 drops
+ 5 minute input rate 3000 bits/sec, 4 packets/sec
+ 5 minute output rate 3000 bits/sec, 4 packets/sec
+ 429076 packets input, 132623595 bytes, 0 no buffer
+ Received 0 broadcasts, 0 runts, 1 giants, 0 throttles
+ 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored, 0 abort
+ 474808 packets output, 73186481 bytes, 0 underruns
+ 0 output errors, 0 collisions, 31 interface resets
+ 0 output buffer failures, 0 output buffers swapped out
+ 35 carrier transitions DCD=up DSR=up DTR=up RTS=up CTS=up
+
+Serial1/0 is up, line protocol is up
+ Hardware is M4T
+ Description: frame-relay using subinterfaces
+ MTU 1500 bytes, BW 64 Kbit, DLY 20000 usec, rely 255/255, load 31/255
+ Encapsulation FRAME-RELAY, loopback not set, keepalive set (10 sec)
+ LMI enq sent 39756, LMI stat recvd 39753, LMI upd recvd 0, DTE LMI up
+ LMI enq recvd 0, LMI stat sent 0, LMI upd sent 0
+ LMI DLCI 0 LMI type is ANSI Annex D frame relay DTE
+ FR SVC disabled, LAPF state down
+ Broadcast queue 0/200, broadcasts sent/dropped 91696/0, interface broadcasts 85069
+ Last input 00:00:00, output 00:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Queueing strategy: fifo
+ Output queue 0/8, 0 drops; input queue 0/75, 0 drops
+ 30 second input rate 62000 bits/sec, 9 packets/sec
+ 30 second output rate 8000 bits/sec, 5 packets/sec
+ 2238005 packets input, 630502832 bytes, 0 no buffer
+ Received 0 broadcasts, 0 runts, 0 giants, 0 throttles
+ 111 input errors, 108 CRC, 0 frame, 0 overrun, 0 ignored, 3 abort
+ 2274950 packets output, 230325061 bytes, 0 underruns
+ 0 output errors, 0 collisions, 2 interface resets
+ 0 output buffer failures, 0 output buffers swapped out
+ 3 carrier transitions DCD=up DSR=up DTR=up RTS=up CTS=up
+
+>show int s2/2.1
+Serial2/2.1 is up, line protocol is up
+ Hardware is M4T
+ Description: Frame Relay to Sydney (134.14.63.129)
+ Internet address is 134.14.63.130/30
+ MTU 1500 bytes, BW 512 Kbit, DLY 20000 usec,
+ reliability 255/255, txload 2/255, rxload 17/255
+ Encapsulation FRAME-RELAY
+
+CIR is BW 512 Kbit == 512*1024/10 bytes/sec (?)
+
+This number is hand typed, but there is no alternative for the CIR.
+
+For FrameRelay, utilization > 50% of CIR for alarm.
+
+>show frame pvc int s2/3.7
+
+PVC Statistics for interface Serial2/3.7 (Frame Relay DTE)
+
+ Active Inactive Deleted Static
+ Local 2 0 0 0
+ Switched 0 0 0 0
+ Unused 0 0 0 0
+
+DLCI = 17, DLCI USAGE = LOCAL, PVC STATUS = ACTIVE, INTERFACE = Serial2/3.7
+
+ input pkts 1287959 output pkts 1446754 in bytes 811690634
+ out bytes 187572881 dropped pkts 0 in FECN pkts 2333
+ in BECN pkts 0 out FECN pkts 0 out BECN pkts 0
+ in DE pkts 0 out DE pkts 0
+ out bcast pkts 43801 out bcast bytes 13172828
+ pvc create time 8w3d, last time pvc status changed 1w2d
+
+>show int Vlan256
+Vlan256 is up, line protocol is up
+ Hardware is Cat6k RP Virtual Ethernet, address is 0030.b630.b318 (bia 0030.b630.b318)
+ Description: Uplink to CTC tipctc2 (Area 2)
+ Internet address is 192.132.156.72/26
+ MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec,
+ reliablility 255/255, txload 1/255, rxload 44/255
+ Encapsulation ARPA, loopback not set
+ ARP type: ARPA, ARP Timeout 04:00:00
+ Last input 00:00:00, output never, output hang never
+ Last clearing of "show interface" counters never
+ Queueing strategy: fifo
+ Output queue 0/40, 2 drops; input queue 0/75, 1027 drops
+ 5 minute input rate 1754000 bits/sec, 158 packets/sec
+ 5 minute output rate 9000 bits/sec, 6 packets/sec
+ 3087365859 packets input, 816292345 bytes, 224 no buffer
+ Received 3085491728 broadcasts, 0 runts, 0 giants, 0 throttles
+ 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
+ 110141390 packets output, 3736073981 bytes, 0 underruns
+ 0 output errors, 3 interface resets
+ 0 output buffer failures, 0 output buffers swapped out
+
+>show int e1/0
+Ethernet1/0 is up, line protocol is up
+ Hardware is AmdP2, address is 0050.7343.f011 (bia 0050.7343.f011)
+ Description: >>> Connecion to melbourne pix-e1 <<<
+ Internet address is 134.14.71.241/30
+ MTU 1500 bytes, BW 10000 Kbit, DLY 1000 usec,
+ reliability 255/255, txload 3/255, rxload 1/255
+ Encapsulation ARPA, loopback not set
+ Keepalive set (10 sec)
+ ARP type: ARPA, ARP Timeout 04:00:00
+ Last input 00:09:05, output 00:00:00, output hang never
+ Last clearing of "show interface" counters never
+ Queueing strategy: fifo
+ Output queue 0/40, 0 drops; input queue 0/75, 0 drops
+ 30 second input rate 26000 bits/sec, 4 packets/sec
+ 30 second output rate 118000 bits/sec, 97 packets/sec
+ 3657465 packets input, 2161281566 bytes, 0 no buffer
+ Received 79047 broadcasts, 0 runts, 0 giants, 0 throttles
+ 0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
+ 0 input packets with dribble condition detected
+ 13202080 packets output, 4162655553 bytes, 0 underruns
+ 6 output errors, 249819 collisions, 11 interface resets
+ 0 babbles, 0 late collision, 207020 deferred
+ 6 lost carrier, 0 no carrier
+ 0 output buffer failures, 0 output buffers swapped out
+
diff --git a/src/pmdas/cisco/Tested b/src/pmdas/cisco/Tested
new file mode 100644
index 0000000..fc3d6aa
--- /dev/null
+++ b/src/pmdas/cisco/Tested
@@ -0,0 +1,76 @@
+
+Cisco Model : MGS
+Interface(s) e2
+
+Cisco Model: Cisco 7000
+Interface(s) e1/2 f2/0
+
+Cisco Model: Cisco 7000
+Interface(s) e0/0 e0/1 e0/2 e0/3 e0/4 e0/5
+ f2/0 f4/0
+
+Cisco Model : 4500
+Interface(s) e0 f1
+
+Cisco Model : 2500 (68030) processor (revision A) 3000 Software
+Interface(s) e0 s0
+
+Cisco Model : 4000M
+Interface(s) e0#starchild
+ e1 -P starchild
+ f0 -P starchild
+ s0 -P starchild
+ s1#starchild
+ s2#starchild
+
+Cisco Model : 4500
+Interface(s) e0 e1
+ f0 f1
+
+
+Cisco Model : 7513
+Interface(s) e0/0 e0/1 e0/2 e0/3 e0/4 e0/5
+ E1/0 E1/1
+ s2/0 s2/1 s2/2 s2/3
+ h3/0
+ f5/0 f8/0 f9/0 f10/0 f11/0
+ a12/0
+
+Cisco Model : 7000
+Interface(s) e0/0 e0/1 e0/2 e0/3 e0/4 e0/5
+ e1/0 e1/1 e1/2 e1/3 e1/4 e1/5
+ e2/0 e2/1 e2/2 e2/3 e2/4 e2/5
+ f3/0
+ a4/0
+
+Cisco Model : 2500
+Interfaces e0?cisco e1 s0 s1
+
+Cisco Model : 3640 IOS 3600 Version 11.3(6), RELEASE SOFTWARE (fc1)
+Interface(s) e0/0 s1/0 s1/1 s1/2 s1/3
+
+
+Cisco Model: Cat6k-MSFC
+Interfaces Vlan32 Vlan33 Vlan34 Vlan35 Vlan36 Vlan37
+ Vlan38 Vlan39 Vlan41 Vlan43 Vlan44 Vlan47
+ Vlan48 Vlan50 Vlan51
+ Vlan140 Vlan149 Vlan156 Vlan240 Vlan249
+ Vlan256
+
+Cisco Model: Catalyst 6509
+Interfaces Vl2 Vl3 Vl4 Vl5 Vl6 Vl7 Vl8 Vl14 Vl15
+ Vl16 Vl17 Vl18 Vl19 Vl20 Vl21 Vl22 Vl23
+ Vl101 Vl109 Vl110 Vl111 Vl112 Vl113
+ Vl156 Vl209 Vl210 Vl211 Vl212 Vl213
+ Vl256
+
+Cisco Model: C2960 IOS Version 12.2(25)SEE2, RELEASE SOFTWARE (fc1)
+Interfaces
+ Vl1 Vl50
+ E0/1 E0/2 E0/3 E0/4 E0/5 E0/6 E0/7 E0/8 E0/9 E0/10
+ E0/11 E0/12 E0/13 E0/14 E0/15 E0/16 E0/17 E0/18 E0/19 E0/20
+ E0/21 E0/22 E0/23 E0/24 E0/25 E0/26 E0/27 E0/28 E0/29 E0/30
+ E0/31 E0/32 E0/33 E0/34 E0/35 E0/36 E0/37 E0/38 E0/39 E0/40
+ E0/41 E0/42 E0/43 E0/44 E0/45 E0/46 E0/47 E0/48
+ G0/1 G0/2
+
diff --git a/src/pmdas/cisco/cisco.c b/src/pmdas/cisco/cisco.c
new file mode 100644
index 0000000..f303480
--- /dev/null
+++ b/src/pmdas/cisco/cisco.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <ctype.h>
+#include <signal.h>
+#include "cisco.h"
+#if defined(HAVE_SYS_RESOURCE_H)
+#include <sys/resource.h>
+#endif
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_PTHREAD_H)
+#include <pthread.h>
+#endif
+#if defined(HAVE_PRCTL_H)
+#include <sys/prctl.h>
+#endif
+
+extern int refreshdelay;
+
+#ifdef HAVE_SPROC
+static pid_t sproc_pid = 0;
+#elif defined (HAVE_PTHREAD_H)
+#include <pthread.h>
+static pthread_t sproc_pid;
+#else
+#error "Need sproc or pthreads here!"
+#endif
+
+/*
+ * all metrics supported in this PMD - one table entry for each
+ */
+static pmdaMetric metrictab[] = {
+ /* 0,0 ... for direct map, sigh */
+ { NULL, { PMDA_PMID(0,0), 0, 0, 0, PMDA_PMUNITS(0,0,0,0,0,0) } },
+ /* bytes-in */
+ { NULL, { PMDA_PMID(0,1), PM_TYPE_U64, CISCO_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ /* bytes-out */
+ { NULL, { PMDA_PMID(0,2), PM_TYPE_U64, CISCO_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ /* rate-in */
+ { NULL, { PMDA_PMID(0,3), PM_TYPE_U32, CISCO_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) } },
+ /* rate-out */
+ { NULL, { PMDA_PMID(0,4), PM_TYPE_U32, CISCO_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) } },
+ /* bandwidth */
+ { NULL, { PMDA_PMID(0,5), PM_TYPE_U32, CISCO_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) } },
+ /* bytes_out_bcast */
+ { NULL, { PMDA_PMID(0,6), PM_TYPE_U64, CISCO_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ };
+
+/* filled in from command line args in main() ... */
+pmdaIndom indomtab[] = {
+ { 0, 0, 0 },
+};
+
+#ifdef HAVE_SPROC
+static RETSIGTYPE
+onhup(int s)
+{
+ signal(SIGHUP, onhup);
+ exit(0);
+}
+#endif
+
+/*
+ * the sproc starts here to refresh the metric values periodically
+ */
+void
+refresh(void *dummy)
+{
+ int i;
+
+#ifdef HAVE_SPROC
+#if HAVE_PRCTL
+ signal(SIGHUP, onhup);
+#if HAVE_PR_TERMCHILD
+ prctl(PR_TERMCHILD); /* SIGHUP when the parent dies */
+#elif HAVE_PR_SET_PDEATHSIG
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+#endif
+#endif
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Starting sproc ...\n");
+ for (i = 0; i < n_cisco; i++) {
+ int j;
+
+ fprintf(stderr, "cisco[%d] host: %s username: %s passwd: %s prompt: %s intf:",
+ i, cisco[i].host, cisco[i].username, cisco[i].passwd, cisco[i].prompt);
+
+ for (j = 0; j < n_intf; j++) {
+ if (intf[j].cp == (cisco+i))
+ fprintf(stderr, " %d-%s", j, intf[j].interface);
+ }
+ fputc('\n', stderr);
+ }
+ }
+#endif
+
+ for ( ; ; ) {
+ for (i = 0; i < n_intf; i++) {
+ if (grab_cisco(intf+i) != -1) {
+ intf[i].fetched = 1;
+ }
+ else
+ intf[i].fetched = 0;
+ }
+
+ if (parse_only)
+ exit(0);
+
+ for (i = 0; i < n_cisco; i++) {
+ if (cisco[i].fout != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "... %s voluntary disconnect fout=%d\n", cisco[i].host, fileno(cisco[i].fout));
+#endif
+ /* close CISCO telnet session */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: exit\n");
+ }
+#endif
+ fprintf(cisco[i].fout, "exit\n");
+ fclose(cisco[i].fout);
+ cisco[i].fout = NULL;
+ }
+ if (cisco[i].fin != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "... %s close fin=%d\n", cisco[i].host, fileno(cisco[i].fin));
+#endif
+ fclose(cisco[i].fin);
+ cisco[i].fin = NULL;
+ }
+ }
+
+ sleep(refreshdelay);
+ }
+}
+
+static int
+cisco_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *avp)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+#ifndef HAVE_SPROC
+ /* Check is refresh thread is still with us */
+ int err;
+
+ if ( (err = pthread_kill (sproc_pid, 0)) != 0 ) {
+ exit (1);
+ }
+#endif
+
+ if (!intf[inst].fetched)
+ return PM_ERR_AGAIN;
+
+ switch (idp->item) {
+
+ case 1: /* bytes_in */
+ if (intf[inst].bytes_in == -1) return 0;
+ avp->ull = intf[inst].bytes_in;
+ break;
+
+ case 2: /* bytes_out */
+ if (intf[inst].bytes_out == -1) return 0;
+ avp->ull = intf[inst].bytes_out;
+ break;
+
+ case 3: /* rate_in */
+ if (intf[inst].rate_in == -1) return 0;
+ avp->ul = intf[inst].rate_in;
+ break;
+
+ case 4: /* rate_out */
+ if (intf[inst].rate_out == -1) return 0;
+ avp->ul = intf[inst].rate_out;
+ break;
+
+ case 5: /* bandwidth */
+ if (intf[inst].bandwidth == -1) return 0;
+ avp->ul = intf[inst].bandwidth;
+ break;
+
+ case 6: /* bytes_out_bcast */
+ if (intf[inst].bytes_out_bcast == -1) return 0;
+ avp->ull = intf[inst].bytes_out_bcast;
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
+
+void
+cisco_init(pmdaInterface *dp)
+{
+ int i;
+
+ pmdaSetFetchCallBack(dp, cisco_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+
+ for (i = 0; i < n_intf; i++)
+ intf[i].fetched = 0;
+
+ /* start the sproc for async fetches */
+#ifdef HAVE_SPROC
+ i = sproc_pid = sproc(refresh, PR_SADDR);
+#elif defined (HAVE_PTHREAD_H)
+ i = pthread_create(&sproc_pid, NULL, (void (*))refresh, NULL);
+#else
+#error "Need sproc or pthread here!"
+#endif
+
+ if (i < 0)
+ dp->status = i;
+ else
+ dp->status = 0;
+}
+
+void
+cisco_done(void)
+{
+ int i;
+
+ if (sproc_pid > 0) {
+#ifndef HAVE_SPROC
+ pthread_kill(sproc_pid, SIGHUP);
+#else
+ kill(sproc_pid, SIGHUP);
+#endif
+ while (wait(&i) >= 0)
+ ;
+ }
+}
+
diff --git a/src/pmdas/cisco/cisco.h b/src/pmdas/cisco/cisco.h
new file mode 100644
index 0000000..acf14bc
--- /dev/null
+++ b/src/pmdas/cisco/cisco.h
@@ -0,0 +1,86 @@
+/*
+ * Instance Domain Data Structures, suitable for a PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _CISCO_H
+#define _CISCO_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+
+typedef struct {
+ char *host; /* CISCO hostname */
+ __pmHostEnt *hostinfo; /* Address info for 'host' */
+ int port; /* port */
+ char *username; /* username */
+ char *passwd; /* password */
+ char *prompt; /* command prompt */
+ FILE *fout; /* write cmds here */
+ FILE *fin; /* read output here */
+} cisco_t;
+
+typedef struct {
+
+ cisco_t *cp; /* which CISCO? */
+ char *interface; /* interface name, e.g. s0 or e10/10 */
+ int fetched; /* valid stats? */
+ __uint32_t bandwidth; /* peak bandwidth */
+ __uint32_t rate_in;
+ __uint32_t rate_out;
+ __uint64_t bytes_in; /* stats */
+ __uint64_t bytes_out;
+ __uint64_t bytes_out_bcast;
+} intf_t;
+
+extern cisco_t *cisco;
+extern int n_cisco;
+extern intf_t *intf;
+extern int n_intf;
+
+/*
+ * Supported Cisco Interfaces
+ */
+typedef struct {
+ char *type; /* NULL to skip, else unique per interface
+ * type */
+ char *name; /* full name as per "show" */
+} intf_tab_t;
+
+extern intf_tab_t intf_tab[];
+extern int num_intf_tab;
+
+#define CISCO_INDOM 0
+extern pmdaIndom indomtab[];
+extern pmdaInstid *_router;
+
+#define PWPROMPT "Password:"
+#define USERPROMPT "Username:"
+
+extern int conn_cisco(cisco_t *);
+extern int grab_cisco(intf_t *);
+extern int dousername(cisco_t *, char **);
+extern int dopasswd(cisco_t *, char *);
+extern char *mygetwd(FILE *, char *);
+
+extern int parse_only;
+
+#endif /* _CISCO_H */
diff --git a/src/pmdas/cisco/cisco.in_util.pmie b/src/pmdas/cisco/cisco.in_util.pmie
new file mode 100644
index 0000000..75ecd08
--- /dev/null
+++ b/src/pmdas/cisco/cisco.in_util.pmie
@@ -0,0 +1,64 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+
+rule cisco.in_util
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 100 * cisco.rate_in $hosts$ / cisco.bandwidth $hosts$
+ > $threshold$
+)"
+ enabled = no
+ version = 1
+ help =
+"Some Cisco router interface exceeded threshold percent of its
+peak bandwidth receiving data during the last sample interval.
+Use the command:
+ $ pminfo -f cisco.bandwidth
+to discover the list of Cisco router interfaces currently being
+monitored by the Cisco PMDA - pmdacisco(1).";
+
+string rule
+ default = "Cisco router inbound bandwidth saturation"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 90
+ help =
+"Threshold percentage for Cisco router saturation, in the range 0
+(idle) to 100 (operating at peak bandwidth)";
+
+string action_expand
+ default = %v%util[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h Cisco router: %i inbound utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200093"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cisco.in_util
+ display = no
+ modify = no;
+string enln_units
+ default = %util[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmdas/cisco/cisco.out_util.pmie b/src/pmdas/cisco/cisco.out_util.pmie
new file mode 100644
index 0000000..e003f59
--- /dev/null
+++ b/src/pmdas/cisco/cisco.out_util.pmie
@@ -0,0 +1,64 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+
+rule cisco.out_util
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 100 * cisco.rate_out $hosts$ / cisco.bandwidth $hosts$
+ > $threshold$
+)"
+ enabled = no
+ version = 1
+ help =
+"Some Cisco router interface exceeded threshold percent of its
+peak bandwidth sending data during the last sample interval.
+Use the command:
+ $ pminfo -f cisco.bandwidth
+to discover the list of Cisco router interfaces currently being
+monitored by the Cisco PMDA - pmdacisco(1).";
+
+string rule
+ default = "Cisco router outbound bandwidth saturation"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 90
+ help =
+"Threshold percentage for Cisco router saturation, in the range 0
+(idle) to 100 (operating at peak bandwidth)";
+
+string action_expand
+ default = %v%util[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h Cisco router: %i outbound utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200094"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cisco.out_util
+ display = no
+ modify = no;
+string enln_units
+ default = %util[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmdas/cisco/help b/src/pmdas/cisco/help
new file mode 100644
index 0000000..eeb8c5f
--- /dev/null
+++ b/src/pmdas/cisco/help
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# cisco PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric-name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric-name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ CISCO.1 Interfaces on Cisco router
+There is one instance in this domain for each interface on a Cisco router
+that the Cisco PMDA (Performance Metrics Domain Agent) has been told about
+when the PMDA is started.
+
+The names of the instances are of the form hostname:tX where "t" is one of
+"a" for ATM, "B" for ISDN BRI, "e" for Ethernet, "E" (FastEthernet), "f" for
+Fddi, "h" for HSSC, "s" for Serial or "Vl" for Vlan. The "X" is the
+interface identifier which is either an integer (e.g. 4000 Series routers) or
+two integers separated by a slash (e.g. 7000 Series routers) or three
+integers separated by a slash and a period (Frame-Relay PVCs on serial line
+subinterfaces).
+
+@ cisco.bytes_in Total Kbytes input to the Cisco
+Total number of Kbytes input to the Cisco on this interface.
+
+Note that due to network delays in extracting the metrics from the
+Cisco routers, any rate computed from this metric over small deltas in time
+are likely to be subject to wide variance.
+
+@ cisco.bytes_out Total Kbytes output from the Cisco
+Total number of Kbytes output from the Cisco on this interface.
+
+Note that due to network delays in extracting the metrics from the
+Cisco routers, any rate computed from this metric over small deltas in time
+are likely to be subject to wide variance.
+
+@ cisco.bytes_out_bcast Total broadcast Kbytes output from the Cisco
+Total number of broadcast Kbytes output from the Cisco on this interface.
+
+Note that due to network delays in extracting the metrics from the
+Cisco routers, any rate computed from this metric over small deltas in
+time are likely to be subject to wide variance.
+
+@ cisco.rate_in 5 minutes average input rate in bytes (not bits!) per second
+Cisco's computed average input rate in bytes per second, over the recent
+past, for this interface.
+
+@ cisco.rate_out 5 minutes average output rate in bytes (not bits!) per second
+Cisco's computed average output rate in bytes per second, over the recent
+past, for this interface.
+
+@ cisco.bandwidth peak interface bandwidth in bytes per second
diff --git a/src/pmdas/cisco/interface.c b/src/pmdas/cisco/interface.c
new file mode 100644
index 0000000..022c8ac
--- /dev/null
+++ b/src/pmdas/cisco/interface.c
@@ -0,0 +1,46 @@
+/*
+ * Known Cisco Interfaces
+ *
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "./cisco.h"
+
+/*
+ * first letter defines command line argument for interface type
+ * (0 means not allowed in command line)
+ * full name of interface is as found in "show interface" command
+ * output.
+ */
+
+intf_tab_t intf_tab[] = {
+ { "s", "Serial" },
+ { "e", "Ethernet" },
+ { "E", "FastEthernet" },
+ { "f", "Fddi" },
+ { "h", "Hssi" },
+ { "a", "ATM" },
+ { "B", "BRI" },
+ { "Vl", "Vlan" },
+ { "G", "GigabitEthernet" },
+ { NULL, "Controller" },
+ { NULL, "Port-channel" },
+ { NULL, "Dialer" },
+ { NULL, "Loopback" },
+};
+
+int num_intf_tab = sizeof(intf_tab) / sizeof(intf_tab[0]);
diff --git a/src/pmdas/cisco/parse.sh b/src/pmdas/cisco/parse.sh
new file mode 100755
index 0000000..c35a27c
--- /dev/null
+++ b/src/pmdas/cisco/parse.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+. $PCP_DIR/etc/pcp.env
+exec $PCP_PMDAS_DIR/cisco/pmdacisco -n parse -C $@
diff --git a/src/pmdas/cisco/pmda.c b/src/pmdas/cisco/pmda.c
new file mode 100644
index 0000000..d78190c
--- /dev/null
+++ b/src/pmdas/cisco/pmda.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Cisco PMDA, based on generic driver for a daemon-based PMDA
+ * - cisco interfaces to monitor are named in the command line as
+ * hostname:tX[@username]
+ * hostname:tX[?passwd]
+ * hostname:tX[@username?passwd]
+ * hostname:tX[!prompt]
+ * hostname:tX[@username!prompt]
+ * hostname:tX[?passwd!prompt]
+ * hostname:tX[@username?passwd!prompt]
+ *
+ * where t identifies an interface type as defined by intf_tab[] in
+ * interface.c
+ * and X is either the ordinal i/f number (base 0, for Series 4000)
+ * or the card/port number (Series 7000 routers)
+ * e.g sydcisco.sydney:s0 (Frame-Relay to Mtn View)
+ * sydcisco.sydney:s1 (ISDN to Melbourne)
+ * cisco.melbourne:s0 (ISDN to Sydney)
+ * b8u-cisco1-e15.corp:e0 (Ethernet in Bldg 8 upper)
+ * b9u-cisco1-81.engr.sgi.com:f2/0
+ * wanbris.brisbane:B0 (BRI ISDN)
+ * If specified the username (after the @ delimiter) and/or the
+ * user-level password (after the ? delimiter) and/or the prompt (after
+ * the ! delimiter) over-rides the global username and/or user-level
+ * password and/or prompt, as specified via -U and/or -P and/or -s
+ * options and applies for all occurrences of the hostname.
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <string.h>
+#include "./cisco.h"
+
+pmdaInstid *_router;
+cisco_t *cisco;
+int n_cisco;
+intf_t *intf;
+int n_intf;
+int refreshdelay = 120; /* default poll every two minutes */
+char *pmdausername; /* username for the pmda */
+char *username; /* username */
+char *passwd; /* user-level password */
+char *prompt = ">"; /* command prompt */
+int port = 23;
+int parse_only;
+int no_lookups;
+
+extern void cisco_init(pmdaInterface *);
+extern void cisco_done(void);
+
+int
+main(int argc, char **argv)
+{
+ int err = 0;
+ int sep = __pmPathSeparator();
+ char *endnum;
+ pmdaInterface dispatch;
+ int n;
+ int i;
+ int c;
+ char helptext[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&pmdausername);
+
+ snprintf(helptext, sizeof(helptext), "%s%c" "cisco" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, CISCO,
+ "cisco.log", helptext);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:h:i:l:pu:6:" "CM:Nn:P:r:s:U:x:?",
+ &dispatch, &err)) != EOF) {
+ switch (c) {
+
+ case 'C': /* parser checking mode (debugging) */
+ pmDebug = DBG_TRACE_APPL0;
+ parse_only++;
+ break;
+
+ case 'N': /* do not perform name lookups (debugging) */
+ no_lookups = 1;
+ break;
+
+ case 'n': /* set program name, for parse (debugging) */
+ pmProgname = optarg;
+ break;
+
+ case 'P': /* passwd */
+ passwd = optarg;
+ break;
+
+ case 'r':
+ refreshdelay = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr, "%s: -r requires numeric (number of seconds) argument\n",
+ pmProgname);
+ err++;
+ }
+ break;
+
+ case 's': /* command prompt */
+ prompt = optarg;
+ break;
+
+ case 'M': /* username (for the PMDA) */
+ pmdausername = optarg;
+ break;
+
+ case 'U': /* username (for the Cisco) */
+ username = optarg;
+ break;
+
+ case 'x':
+ port = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr, "%s: -x requires numeric argument\n",
+ pmProgname);
+ err++;
+ }
+ break;
+
+ case '?':
+ err++;
+ }
+ }
+
+ n_intf = argc - optind;
+ if (n_intf == 0 || err) {
+ fprintf(stderr,
+ "Usage: %s [options] host:{a|B|E|e|f|h|s}N[/M[.I]] [...]\n\n",
+ pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -l logfile redirect diagnostics and trace output to logfile\n"
+ " -M username user account to run PMDA under (default \"pcp\")\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -P password default user-level Cisco password\n"
+ " -r refresh update metrics every refresh seconds\n"
+ " -s prompt Cisco command prompt [default >]\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -U username Cisco username\n"
+ " -x port telnet port [default 23]\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ stderr);
+ exit(1);
+ }
+
+ /* force errors from here on into the log */
+ if (!parse_only) {
+ pmdaOpenLog(&dispatch);
+ __pmSetProcessIdentity(pmdausername);
+ } else {
+ dispatch.version.two.text = NULL;
+ dispatch.version.two.ext->e_helptext = NULL;
+ }
+
+ /*
+ * build the instance domain and cisco data structures from the
+ * command line arguments.
+ */
+ if ((_router = (pmdaInstid *)malloc(n_intf * sizeof(pmdaInstid))) == NULL) {
+ __pmNoMem("main.router", n_intf * sizeof(pmdaInstid), PM_FATAL_ERR);
+ }
+ if ((intf = (intf_t *)malloc(n_intf * sizeof(intf_t))) == NULL) {
+ __pmNoMem("main.intf", n_intf * sizeof(intf_t), PM_FATAL_ERR);
+ }
+ /* pre-allocated cisco[] to avoid realloc and ptr movement */
+ if ((cisco = (cisco_t *)malloc(n_intf * sizeof(cisco_t))) == NULL) {
+ __pmNoMem("main.cisco", n_intf * sizeof(cisco_t), PM_FATAL_ERR);
+ }
+
+ indomtab[CISCO_INDOM].it_numinst = n_intf;
+ indomtab[CISCO_INDOM].it_set = _router;
+
+ for (n = 0 ; optind < argc; optind++, n++) {
+ char *p = strdup(argv[optind]);
+ char *q;
+ char *myusername;
+ char *mypasswd;
+ char *myprompt;
+
+ myprompt = strchr(p, '!');
+ if (myprompt) {
+ /* save prompt for later */
+ *myprompt++ = '\0';
+ }
+ else
+ myprompt = NULL;
+ mypasswd = strchr(p, '?');
+ if (mypasswd) {
+ /* save user-level password for later */
+ *mypasswd++ = '\0';
+ }
+ else
+ mypasswd = passwd;
+ myusername = strchr(p, '@');
+ if (myusername) {
+ /* save username for later */
+ *myusername++ = '\0';
+ }
+ else
+ myusername = username;
+
+ _router[n].i_inst = n;
+ _router[n].i_name = strdup(p);
+
+ if ((q = strchr(p, ':')) == NULL)
+ goto badintfspec;
+ *q++ = '\0';
+ for (i = 0; i < num_intf_tab; i++) {
+ if (strncmp(q, intf_tab[i].type, strlen(intf_tab[i].type)) == 0)
+ break;
+ }
+ if (i == num_intf_tab)
+ goto badintfspec;
+ if (strcmp(intf_tab[i].type, "E") == 0) {
+ /*
+ * Cisco parser is case insensitive, so 'E' means "Ethernet"
+ * and 'F' means "Fddi", need to use "FastEthernet" here
+ */
+ q++;
+ intf[n].interface = (char *)malloc(strlen("FastEthernet")+strlen(q)+1);
+ if ((intf[n].interface = (char *)malloc(strlen("FastEthernet")+strlen(q)+1)) == NULL) {
+ __pmNoMem("main.cisco", strlen("FastEthernet")+strlen(q)+1, PM_FATAL_ERR);
+ }
+ strcpy(intf[n].interface, "FastEthernet");
+ strcat(intf[n].interface, q);
+ }
+ else
+ intf[n].interface = q;
+
+ for (i = 0; i < n_cisco; i++) {
+ if (strcmp(p, cisco[i].host) == 0)
+ break;
+ }
+ if (i == n_cisco) {
+ __pmHostEnt *hostInfo = NULL;
+
+ if (!no_lookups)
+ hostInfo = __pmGetAddrInfo(p);
+
+ if (!hostInfo && parse_only) {
+ FILE *f;
+
+ /*
+ * for debugging, "host" may be a file ...
+ */
+ if ((f = fopen(p, "r")) == NULL) {
+ fprintf(stderr, "%s: unknown hostname or filename %s: %s\n",
+ pmProgname, argv[optind], hoststrerror());
+ /* abandon this host (cisco) */
+ continue;
+ }
+ else {
+ fprintf(stderr, "%s: assuming file %s contains output from \"show int\" command\n",
+ pmProgname, p);
+
+ cisco[i].host = p;
+ cisco[i].username = myusername != NULL ? myusername : username;
+ cisco[i].passwd = mypasswd != NULL ? mypasswd : passwd;
+ cisco[i].prompt = myprompt != NULL ? myprompt : prompt;
+ cisco[i].fin = f;
+ cisco[i].fout = stdout;
+ n_cisco++;
+ }
+ } else if (!hostInfo) {
+ fprintf(stderr, "%s: unknown hostname %s: %s\n",
+ pmProgname, p, hoststrerror());
+ /* abandon this host (cisco) */
+ continue;
+ } else {
+ cisco[i].host = p;
+ cisco[i].username = myusername != NULL ? myusername : username;
+ cisco[i].passwd = mypasswd != NULL ? mypasswd : passwd;
+ cisco[i].prompt = myprompt != NULL ? myprompt : prompt;
+ cisco[i].fin = NULL;
+ cisco[i].fout = NULL;
+ cisco[i].hostinfo = hostInfo;
+ cisco[i].port = port;
+
+ n_cisco++;
+ fprintf(stderr, "Adding new host %s\n", p);
+ fflush(stderr);
+ }
+ }
+ else {
+ if (cisco[i].username == NULL) {
+ if (myusername != NULL)
+ /* username on 2nd or later interface ... applies to all */
+ cisco[i].username = myusername;
+ }
+ else {
+ if (myusername != NULL) {
+ if (strcmp(cisco[i].username, myusername) != 0) {
+ fprintf(stderr,
+ "%s: conflicting usernames (\"%s\" "
+ "and \"%s\") for cisco \"%s\"\n",
+ pmProgname, cisco[i].username, myusername,
+ cisco[i].host);
+ exit(1);
+ }
+ }
+ }
+ if (cisco[i].passwd == NULL) {
+ if (mypasswd != NULL)
+ /* passwd on 2nd or later interface ... applies to all */
+ cisco[i].passwd = mypasswd;
+ }
+ else {
+ if (mypasswd != NULL) {
+ if (strcmp(cisco[i].passwd, mypasswd) != 0) {
+ fprintf(stderr,
+ "%s: conflicting user-level passwords (\"%s\" "
+ "and \"%s\") for cisco \"%s\"\n",
+ pmProgname, cisco[i].passwd, mypasswd,
+ cisco[i].host);
+ exit(1);
+ }
+ }
+ }
+ if (cisco[i].prompt == NULL) {
+ if (myprompt != NULL)
+ /* prompt on 2nd or later interface ... applies to all */
+ cisco[i].prompt = myprompt;
+ }
+ else {
+ if (myprompt != NULL) {
+ if (strcmp(cisco[i].prompt, myprompt) != 0) {
+ fprintf(stderr,
+ "%s: conflicting user-level prompts (\"%s\" "
+ "and \"%s\") for cisco \"%s\"\n",
+ pmProgname, cisco[i].prompt, myprompt,
+ cisco[i].host);
+ exit(1);
+ }
+ }
+ }
+ }
+
+ intf[n].cp = cisco+i;
+ /*
+ * special one-trip initialization for Frame-Relay over serial
+ * lines ... see grab_cisco()
+ */
+ intf[n].bandwidth = -2;
+
+ fprintf(stderr, "Interface %s(%d) is on host %s\n",
+ intf[n].interface, n, cisco[i].host);
+ fflush(stderr);
+
+ continue;
+
+badintfspec:
+ fprintf(stderr, "%s: bad interface specification \"%s\"\n", pmProgname, argv[optind]);
+ fprintf(stderr, " should be like sydcisco.sydney:s1 or b9u-cisco1-81.engr.sgi.com:f2/0\n");
+ fprintf(stderr, " or cisco.melbourne:e0?secret\n");
+ exit(1);
+ }
+
+ if (n_cisco == 0) {
+ fprintf(stderr, "%s: Nothing to monitor\n", pmProgname);
+ exit(1);
+ }
+
+ if (parse_only) {
+ fprintf(stderr, "Sleeping while sproc does the work ... SIGINT to terminate\n");
+ cisco_init(&dispatch);
+ for (i = 0; i < n_intf; i++)
+ intf[i].fetched = 0;
+ pause();
+ } else {
+ /* set up connection to PMCD */
+ cisco_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ }
+
+ cisco_done();
+ exit(0);
+}
diff --git a/src/pmdas/cisco/pmns b/src/pmdas/cisco/pmns
new file mode 100644
index 0000000..fc1ce50
--- /dev/null
+++ b/src/pmdas/cisco/pmns
@@ -0,0 +1,28 @@
+/*
+ * Metrics for cisco PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+cisco {
+ bandwidth CISCO:0:5
+ bytes_in CISCO:0:1
+ bytes_out CISCO:0:2
+ bytes_out_bcast CISCO:0:6
+ rate_in CISCO:0:3
+ rate_out CISCO:0:4
+}
diff --git a/src/pmdas/cisco/probe.c b/src/pmdas/cisco/probe.c
new file mode 100644
index 0000000..5cd5b4d
--- /dev/null
+++ b/src/pmdas/cisco/probe.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include "./cisco.h"
+
+int port = 23;
+int seen_fr = 0;
+char *prompt = ">"; /* unique suffix to IOS prompt */
+
+char *
+mygetfirstwd(FILE *f)
+{
+ char *p;
+ int c;
+ char line[1024];
+ char *lp;
+
+ for ( ; ; ) {
+ c = fgetc(f);
+ if (c == EOF)
+ break;
+ if (c == '\r' || c == '\n')
+ continue;
+ if (c != ' ') {
+ ungetc(c, f);
+ break;
+ }
+ lp = line;
+ while ((c = fgetc(f)) != EOF) {
+ *lp++ = c;
+ if (c == '\r' || c == '\n') {
+ *lp = '\0';
+ break;
+ }
+ }
+ /*
+ * some interesting things to look for here ...
+ */
+ if (strncmp(&line[1], "Encapsulation FRAME-RELAY", strlen("Encapsulation FRAME-RELAY")) == 0) {
+ seen_fr = 1;
+ }
+ }
+ /* either EOF, or line starts with a non-space */
+
+ p = mygetwd(f, prompt);
+
+ if (p != NULL && (strlen(p) < strlen(prompt) ||
+ strcmp(&p[strlen(p)-strlen(prompt)], prompt)) != 0) {
+ /* skip to end of line, ready for next one */
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\r' || c == '\n')
+ break;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "mygetfirstwd: %s\n", p == NULL ? "<NULL>" : p);
+#endif
+
+ return p;
+}
+
+#define PREAMBLE 1
+#define IN_BODY 2
+
+static void
+probe_cisco(cisco_t * cp)
+{
+ char *w;
+ int fd;
+ int fd2;
+ int first = 1;
+ char *pass = NULL;
+ int defer = 0;
+ int state = PREAMBLE;
+ int i;
+ int namelen;
+ char *ctype = NULL;
+ char *name = NULL;
+
+ if (cp->fin == NULL) {
+ fd = conn_cisco(cp);
+ if (fd == -1) {
+ fprintf(stderr, "grab_cisco(%s): connect failed: %s\n",
+ cp->host, osstrerror());
+ return;
+ }
+ else {
+ cp->fin = fdopen (fd, "r");
+ if ((fd2 = dup(fd)) < 0) {
+ perror("dup");
+ exit(1);
+ }
+ cp->fout = fdopen (fd2, "w");
+ if (cp->username != NULL) {
+ /*
+ * Username stuff ...
+ */
+ if (dousername(cp, &pass) == 0) {
+ exit(1);
+ }
+ }
+ if (cp->passwd != NULL) {
+ /*
+ * User-level password stuff ...
+ */
+ if (dopasswd(cp, pass) == 0) {
+ exit(1);
+ }
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: \n");
+ fprintf(stderr, "Send: terminal length 0\n");
+ }
+#endif
+ fprintf(cp->fout, "\n");
+ fflush(cp->fout);
+ fprintf(cp->fout, "terminal length 0\n");
+ fflush(cp->fout);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: show int\n");
+ }
+#endif
+ fprintf(cp->fout, "show int\n");
+ fflush(cp->fout);
+
+ }
+ else {
+ /*
+ * parsing text from a file, not a TCP/IP connection to a
+ * Cisco device
+ */
+ ;
+ }
+
+ for ( ; ; ) {
+ w = mygetfirstwd(cp->fin);
+ if (defer && ctype != NULL && name != NULL) {
+ if (seen_fr) {
+ if (first)
+ first = 0;
+ else
+ putchar(' ');
+ printf("%s%s", ctype, name);
+ free(name);
+ name = NULL;
+ }
+ }
+ defer = 0;
+ if (w == NULL) {
+ /*
+ * End of File (telenet timeout?)
+ */
+ fprintf(stderr, "grab_cisco(%s): forced disconnect fin=%d\n",
+ cp->host, fileno(cp->fin));
+ return;
+ }
+ if (*w == '\0')
+ continue;
+ if (state == PREAMBLE) {
+ if (strcmp(w, "show") == 0)
+ state = IN_BODY;
+ else if (strcmp(w, PWPROMPT) == 0) {
+ fprintf(stderr,
+ "Error: user-level password required for \"%s\"\n",
+ cp->host);
+ exit(1);
+ }
+ continue;
+ }
+ else {
+ if (strlen(w) >= strlen(prompt) &&
+ strcmp(&w[strlen(w)-strlen(prompt)], prompt) == 0)
+ break;
+ ctype = NULL;
+ for (i = 0; i < num_intf_tab; i++) {
+ namelen = strlen(intf_tab[i].name);
+ if (strncmp(w, intf_tab[i].name, namelen) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "Match: if=%s word=%s\n", intf_tab[i].name, w);
+ }
+#endif
+ name = strdup(&w[namelen]);
+ ctype = intf_tab[i].type;
+ if (intf_tab[i].type != NULL && strcmp(intf_tab[i].type, "a") == 0) {
+ /*
+ * skip ATMN.M ... need ATMN
+ */
+ if (strchr(&w[namelen], '.') != NULL)
+ ctype = NULL;
+ }
+ else if (intf_tab[i].type != NULL && strcmp(intf_tab[i].type, "s") == 0) {
+ /*
+ * skip SerialN.M ... need SerialN, unless frame-relay
+ */
+ if (strchr(&w[namelen], '.') != NULL)
+ defer = 1;
+ }
+ break;
+ }
+ }
+ if (i == num_intf_tab)
+ fprintf(stderr, "%s: Warning, unknown interface: %s\n", pmProgname, w);
+ if (ctype != NULL && name != NULL && !defer) {
+ if (first)
+ first = 0;
+ else
+ putchar(' ');
+ printf("%s%s", ctype, name);
+ free(name);
+ name = NULL;
+ }
+ }
+ }
+ putchar('\n');
+
+ /* close CISCO telnet session */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: exit\n");
+ }
+#endif
+ fprintf(cp->fout, "exit\n");
+ fflush(cp->fout);
+
+ return;
+}
+
+int
+main(int argc, char **argv)
+{
+ int errflag = 0;
+ int Nflag = 0;
+ int c;
+ int sts;
+ char *endnum;
+ char *passwd = NULL;
+ char *username = NULL;
+ __pmHostEnt *hostInfo = NULL;
+
+ __pmSetProgname(argv[0]);
+
+ while ((c = getopt(argc, argv, "ND:P:s:U:x:?")) != EOF) {
+ switch (c) {
+
+ case 'N': /* check flag */
+ Nflag = 1;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(optarg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, optarg);
+ errflag++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'P': /* passwd */
+ passwd = optarg;
+ break;
+
+ case 's': /* prompt */
+ prompt = optarg;
+ break;
+
+ case 'U': /* username */
+ username = optarg;
+ break;
+
+ case 'x':
+ port = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr, "%s: -x requires numeric argument\n",
+ pmProgname);
+ errflag++;
+ }
+ break;
+
+ case '?':
+ errflag++;
+ }
+ }
+
+ if (errflag || optind != argc-1) {
+ fprintf(stderr, "Usage: %s [-U username] [-P passwd] [-s prompt] [-x port] host\n\n", pmProgname);
+ exit(1);
+ }
+
+ if (!Nflag)
+ hostInfo = __pmGetAddrInfo(argv[optind]);
+
+ if (hostInfo == NULL) {
+ FILE *f;
+
+ if ((f = fopen(argv[optind], "r")) == NULL) {
+ fprintf(stderr, "%s: unknown hostname or filename %s: %s\n",
+ pmProgname, argv[optind], hoststrerror());
+ exit(1);
+ }
+ else {
+ cisco_t c;
+
+ fprintf(stderr, "%s: assuming file %s contains output from \"show int\" command\n",
+ pmProgname, argv[optind]);
+
+ c.host = argv[optind];
+ c.username = NULL;
+ c.passwd = NULL;
+ c.fin = f;
+ c.fout = fopen("/dev/null", "w");
+ c.prompt = prompt;
+
+ probe_cisco(&c);
+ }
+ } else {
+ cisco_t c;
+
+ c.host = argv[optind];
+ c.username = username;
+ c.passwd = passwd;
+ c.fin = NULL;
+ c.fout = NULL;
+ c.prompt = prompt;
+ c.hostinfo = hostInfo;
+ c.port = 23; /* telnet */
+
+ probe_cisco(&c);
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/cisco/root b/src/pmdas/cisco/root
new file mode 100644
index 0000000..e0c1b81
--- /dev/null
+++ b/src/pmdas/cisco/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { cisco }
+
+#include "pmns"
+
diff --git a/src/pmdas/cisco/telnet.c b/src/pmdas/cisco/telnet.c
new file mode 100644
index 0000000..bcee1db
--- /dev/null
+++ b/src/pmdas/cisco/telnet.c
@@ -0,0 +1,755 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <syslog.h>
+#include "./cisco.h"
+
+extern int port;
+
+int
+conn_cisco(cisco_t * cp)
+{
+ __pmFdSet wfds;
+ __pmSockAddr *myaddr;
+ void *enumIx;
+ int flags = 0;
+ int fd;
+ int ret;
+
+ fd = -1;
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(cp->hostinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(cp->hostinfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else
+ continue;
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ flags = __pmConnectTo(fd, myaddr, cp->port);
+ __pmSockAddrFree(myaddr);
+
+ if (flags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address. fd has been closed in __pmConnectTo().
+ */
+ setoserror(ECONNREFUSED);
+ fd = -1;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(fd, &wfds);
+ ret = __pmSelectWrite(fd+1, &wfds, NULL);
+
+ /* Was the connection successful? */
+ if (ret == 0)
+ setoserror(ETIMEDOUT);
+ else if (ret > 0) {
+ ret = __pmConnectCheckError(fd);
+ if (ret == 0)
+ break;
+ setoserror(ret);
+ }
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(fd);
+ fd = -1;
+ } /* loop over addresses */
+
+ if (fd == -1) {
+ fprintf(stderr, "conn_cisco(%s): connect: %s\n",
+ cp->host, netstrerror());
+ return -1;
+ }
+
+ fd = __pmConnectRestoreFlags(fd, flags);
+ if (fd < 0) {
+ fprintf(stderr, "conn_cisco(%s): setsockopt: %s\n",
+ cp->host, netstrerror());
+ return -1;
+ }
+
+ return fd;
+}
+
+static void
+skip2eol(FILE *f)
+{
+ int c;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "skip2eol:");
+#endif
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\n')
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "%c", c);
+#endif
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fputc('\n', stderr);
+#endif
+}
+
+char *
+mygetwd(FILE *f, char *prompt)
+{
+ char *p;
+ int c;
+ static char buf[1024];
+ int len_prompt = strlen(prompt);
+ int found_prompt = 0;
+
+ p = buf;
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
+ if (p == buf)
+ continue;
+ break;
+ }
+ *p++ = c;
+ if (p-buf >= len_prompt && strncmp(&p[-len_prompt], prompt, len_prompt) == 0) {
+ found_prompt = 1;
+ break;
+ }
+ }
+ *p = '\0';
+
+ if (feof(f)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "mygetwd: EOF fd=%d\n", fileno(f));
+#endif
+ return NULL;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "mygetwd: fd=%d wd=\"%s\"%s\n", fileno(f), buf, found_prompt ? " [prompt]" : "");
+#endif
+
+ return buf;
+}
+
+/*
+ * The CISCO "show interface" command output is parsed.
+ *
+ * See the file Samples for examples.
+ *
+ * The parser is a Finite State Automaton (FSA) that follows these
+ * rules:
+ *
+ * SHOW_INT style ... uses "show int <interface>" command
+ * state token next state
+ * NOISE <interface name> IN_REPORT
+ * IN_REPORT Description: skip rest of line, IN_REPORT
+ * IN_REPORT <prompt> DONE
+ * IN_REPORT minute RATE
+ * IN_REPORT second RATE
+ * IN_REPORT input, BYTES_IN
+ * IN_REPORT output, BYTES_OUT
+ * IN_REPORT BW BW
+ * RATE input skip next token, RATE_IN
+ * RATE output skip next token, RATE_OUT
+ * RATE_IN <number> (rate_in) IN_REPORT
+ * RATE_OUT <number> (rate_out) IN_REPORT
+ * BYTES_IN <number> (bytes_in) IN_REPORT
+ * BYTES_OUT <number> (bytes_out) IN_REPORT
+ * BW <number> (bandwidth) IN_REPORT
+ *
+ * SHOW_FRAME style ... uses "show frame pvc int <interface>" command
+ * state token next state
+ * NOISE <interface name> IN_REPORT
+ * IN_REPORT Description: skip rest of line, IN_REPORT
+ * IN_REPORT <prompt> DONE
+ * IN_REPORT 1st bytes BYTES_IN
+ * IN_REPORT 2nd bytes BYTES_OUT
+ * IN_REPORT 3rd bytes BYTES_OUT_BCAST
+ * BYTES_IN <number> (bytes_in) IN_REPORT
+ * BYTES_OUT <number> (bytes_out) IN_REPORT
+ * BYTES_OUT_BCAST <number> (bytes_out_bcast) IN_REPORT
+ *
+ * Note lines are terminated with \r
+ */
+
+#define DONE -1
+#define NOISE 0
+#define IN_REPORT 1
+#define RATE 2
+#define RATE_IN 3
+#define RATE_OUT 4
+#define BYTES_IN 5
+#define BYTES_OUT 6
+#define BW 7
+#define BYTES_OUT_BCAST 8
+
+#ifdef PCP_DEBUG
+static char *statestr[] = {
+ "done", "noise", "in_report",
+ "rate", "rate_in", "rate_out", "bytes_in", "bytes_out", "bw",
+ "bytes_out_bcast"
+};
+#endif
+
+int
+dousername(cisco_t *cp, char **pw_prompt)
+{
+ char *w;
+ int len, done = 0;
+ int len_prompt = strlen(cp->prompt);
+
+ for ( ; ; ) {
+ w = mygetwd(cp->fin, cp->prompt);
+ if (w == NULL)
+ break;
+ if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0)
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Username:? got - %s\n", w);
+#endif
+ if (strcmp(w, USERPROMPT) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send username: %s\n", cp->username);
+ }
+#endif
+ fprintf(cp->fout, "%s\n", cp->username);
+ fflush(cp->fout);
+ for ( ; ; ) {
+ w = mygetwd(cp->fin, cp->prompt);
+ if (w == NULL || strcmp(w, USERPROMPT) == 0)
+ /* closed connection or Username re-prompt */
+ break;
+ len = strlen(w);
+ if ((len >= len_prompt && strncmp(&w[len-len_prompt], cp->prompt, len_prompt) == 0) ||
+ w[len-1] == ':') {
+ /* command prompt or passwd */
+ if (w[len-1] == ':')
+ *pw_prompt = w;
+ done = 1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (done == 0) {
+ fprintf(stderr, "Error: Cisco username negotiation failed for \"%s\"\n",
+ cp->host);
+ fprintf(stderr,
+"To check that a username is required, enter the following command:\n"
+" $ telnet %s\n"
+"If the prompt \"%s\" does not appear, no username is required.\n"
+"Otherwise, enter the username \"%s\" to check that this\n"
+"is correct.\n",
+cp->host, USERPROMPT, cp->username);
+ }
+
+ return done;
+}
+
+int
+dopasswd(cisco_t *cp, char *pw_prompt)
+{
+ char *w;
+ int done = 0;
+ int len_prompt = strlen(cp->prompt);
+
+ for ( ; ; ) {
+ if (pw_prompt) /* dousername may have read passwd prompt */
+ w = pw_prompt;
+ else
+ w = mygetwd(cp->fin, cp->prompt);
+ pw_prompt = NULL;
+ if (w == NULL)
+ break;
+ if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0)
+ break;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Password:? got - %s\n", w);
+#endif
+ if (strcmp(w, PWPROMPT) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send passwd: %s\n", cp->passwd);
+ }
+#endif
+ fprintf(cp->fout, "%s\n", cp->passwd);
+ fflush(cp->fout);
+ for ( ; ; ) {
+ w = mygetwd(cp->fin, cp->prompt);
+ if (w == NULL || strcmp(w, PWPROMPT) == 0)
+ /* closed connection or user-level password re-prompt */
+ break;
+ if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0) {
+ /* command prompt */
+ done = 1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (done == 0) {
+ fprintf(stderr, "Error: Cisco user-level password negotiation failed for \"%s\"\n",
+ cp->host);
+ fprintf(stderr,
+"To check that a user-level password is required, enter the following command:\n"
+" $ telnet %s\n"
+"If the prompt \"%s\" does not appear, no user-level password is required.\n"
+"Otherwise, enter the user-level password \"%s\" to check that this\n"
+"is correct.\n",
+cp->host, PWPROMPT, cp->passwd);
+ }
+
+ return done;
+}
+
+static int timeout;
+
+void
+onalarm(int dummy)
+{
+ timeout = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Alarm timeout!\n");
+ }
+#endif
+
+}
+
+static int
+get_fr_bw(cisco_t *cp, char *interface)
+{
+ int state = NOISE;
+ int bandwidth = -1;
+ char *w;
+ int len_prompt = strlen(cp->prompt);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: s%s\n", interface);
+ }
+#endif
+ fprintf(cp->fout, "show int s%s\n", interface);
+ fflush(cp->fout);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "BW Parse:");
+#endif
+ while (state != DONE) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "[%s] ", statestr[state+1]);
+#endif
+ w = mygetwd(cp->fin, cp->prompt);
+ if (w == NULL || timeout) {
+ /*
+ * End of File (telenet timeout?)
+ */
+ alarm(0);
+ return -1;
+ }
+ switch (state) {
+
+ case NOISE:
+ if (strncmp(w, "Serial", 6) == 0 && strcmp(&w[6], interface) == 0)
+ state = IN_REPORT;
+ break;
+
+ case IN_REPORT:
+ if (strcmp(w, "Description:") == 0)
+ skip2eol(cp->fin);
+ else if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0)
+ state = DONE;
+ else if (strcmp(w, "BW") == 0)
+ state = BW;
+ break;
+
+ case BW:
+ sscanf(w, "%d", &bandwidth);
+ bandwidth *= 1000; /* Kbit -> bytes/sec */
+ bandwidth /= 8;
+ state = IN_REPORT;
+ break;
+
+ }
+ }
+ alarm(0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Extracted bandwidth: %d bytes/sec\n", bandwidth);
+ }
+#endif
+ return bandwidth;
+}
+
+#define SHOW_INT 1
+#define SHOW_FRAME 2
+
+int
+grab_cisco(intf_t *ip)
+{
+ int style;
+ int next_state;
+ int state = NOISE;
+ int skip = 0;
+ int i;
+ int namelen;
+ char *pw_prompt = NULL;
+ char *w;
+ int fd;
+ int fd2;
+ int nval = 0;
+ cisco_t *cp = ip->cp;
+ intf_t tmp;
+ int len_prompt = strlen(cp->prompt);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "grab_cisco(%s:%s):\n", cp->host, ip->interface);
+ }
+#endif
+
+ tmp.bandwidth = tmp.rate_in = tmp.rate_out = -1;
+ tmp.bytes_in = tmp.bytes_out = tmp.bytes_out_bcast = -1;
+
+ if (cp->fin == NULL) {
+ fd = conn_cisco(cp);
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "grab_cisco(%s:%s): connect failed: %s\n",
+ cp->host, ip->interface, netstrerror());
+#endif
+ return -1;
+ }
+ else {
+ cp->fin = fdopen (fd, "r");
+ if ((fd2 = dup(fd)) < 0) {
+ perror("dup");
+ exit(1);
+ }
+ cp->fout = fdopen (fd2, "w");
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "grab_cisco(%s:%s): connected fin=%d fout=%d",
+ cp->host, ip->interface, fileno(cp->fin), fileno(cp->fout));
+ if (cp->username != NULL)
+ fprintf(stderr, " username=%s", cp->username);
+ else
+ fprintf(stderr, " NO username");
+ if (cp->passwd != NULL)
+ fprintf(stderr, " passwd=%s", cp->passwd);
+ else
+ fprintf(stderr, " NO passwd");
+ fputc('\n', stderr);
+ }
+#endif
+
+ if (cp->username != NULL) {
+ /*
+ * Username stuff ...
+ */
+ if (dousername(cp, &pw_prompt) == 0) {
+ fclose(cp->fin);
+ fclose(cp->fout);
+ cp->fin = cp->fout = NULL;
+ return -1;
+ }
+ }
+ if (cp->passwd != NULL) {
+ /*
+ * User-level password stuff ...
+ */
+ if (dopasswd(cp, pw_prompt) == 0) {
+ fclose(cp->fin);
+ fclose(cp->fout);
+ cp->fin = cp->fout = NULL;
+ return -1;
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: \n");
+ }
+#endif
+ fprintf(cp->fout, "\n");
+ fflush(cp->fout);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: terminal length 0\n");
+ }
+#endif
+ fprintf(cp->fout, "terminal length 0\n");
+ fflush(cp->fout);
+ }
+
+ timeout = 0;
+ signal(SIGALRM, onalarm);
+ /*
+ * Choice of timeout here is somewhat arbitrary ... for a long
+ * time this was 5 (seconds), but then testing with an entry
+ * level Model 800 ADSL router revealed that up to 20 seconds
+ * was required to generate the expected output.
+ */
+ alarm(20);
+
+ style = SHOW_INT; /* default Cisco command */
+ if (ip->interface[0] == 's' && strchr(ip->interface, '.') != NULL) {
+ /*
+ * Frame-relay PVC on subinterface for s2/3.7 style interface name
+ */
+ style = SHOW_FRAME;
+ if (ip->bandwidth == -2) {
+ /*
+ * one-trip initialzation ... need show int s2/3.7 to
+ * get bandwidth
+ */
+ ip->bandwidth = get_fr_bw(cp, &ip->interface[1]);
+ }
+ tmp.bandwidth = ip->bandwidth;
+ if (tmp.bandwidth != -1)
+ nval++;
+ }
+ if (style == SHOW_FRAME) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: show frame pvc int s%s\n", &ip->interface[1]);
+ }
+#endif
+ fprintf(cp->fout, "show frame pvc int s%s\n", &ip->interface[1]);
+ next_state = BYTES_IN;
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Send: show int %s\n", ip->interface);
+ }
+#endif
+ fprintf(cp->fout, "show int %s\n", ip->interface);
+ }
+ fflush(cp->fout);
+ state = NOISE;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "Parse:");
+ fflush(stderr);
+ }
+#endif
+ while (state != DONE) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "[%s] ", statestr[state+1]);
+ fflush(stderr);
+ }
+#endif
+ w = mygetwd(cp->fin, cp->prompt);
+ if (w == NULL || timeout) {
+ /*
+ * End of File (telenet timeout?)
+ * ... mark as closed, and try again at next request
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "grab_cisco(%s:%s): forced disconnect fin=%d\n",
+ cp->host, ip->interface, fileno(cp->fin));
+#endif
+ fclose(cp->fin);
+ fclose(cp->fout);
+ cp->fin = cp->fout = NULL;
+ alarm(0);
+ return -1;
+ }
+ switch (state) {
+
+ case NOISE:
+ for (i = 0; i < num_intf_tab; i++) {
+ namelen = strlen(intf_tab[i].name);
+ if (strncmp(w, intf_tab[i].name, namelen) == 0) {
+ state = IN_REPORT;
+ break;
+ }
+ }
+ break;
+
+ case IN_REPORT:
+ if (strcmp(w, "Description:") == 0)
+ skip2eol(cp->fin);
+ if (strlen(w) >= len_prompt && strncmp(&w[strlen(w)-len_prompt], cp->prompt, len_prompt) == 0)
+ state = DONE;
+ else if (style == SHOW_INT) {
+ if (strcmp(w, "minute") == 0 || strcmp(w, "second") == 0)
+ state = RATE;
+ else if (strcmp(w, "input,") == 0)
+ state = BYTES_IN;
+ else if (strcmp(w, "output,") == 0)
+ state = BYTES_OUT;
+ else if (strcmp(w, "BW") == 0)
+ state = BW;
+ }
+ else if (style == SHOW_FRAME) {
+ if (strcmp(w, "bytes") == 0) {
+ if (next_state == BYTES_IN) {
+ state = BYTES_IN;
+ next_state = BYTES_OUT;
+ }
+ else if (next_state == BYTES_OUT) {
+ state = BYTES_OUT;
+ next_state = BYTES_OUT_BCAST;
+ }
+ else if (next_state == BYTES_OUT_BCAST) {
+ state = BYTES_OUT_BCAST;
+ next_state = IN_REPORT;
+ }
+ else
+ state = next_state;
+ }
+ }
+ break;
+
+ case RATE:
+ if (strcmp(w, "input") == 0) {
+ skip = 1;
+ state = RATE_IN;
+ }
+ else if (strcmp(w, "output") == 0) {
+ skip = 1;
+ state = RATE_OUT;
+ }
+ break;
+
+ case RATE_IN:
+ if (skip-- == 0) {
+ tmp.rate_in = atol(w) / 8;
+ nval++;
+ state = IN_REPORT;
+ }
+ break;
+
+ case RATE_OUT:
+ if (skip-- == 0) {
+ tmp.rate_out = atol(w) / 8;
+ nval++;
+ state = IN_REPORT;
+ }
+ break;
+
+ case BYTES_IN:
+ tmp.bytes_in = strtoull(w, NULL, 10);
+ nval++;
+ state = IN_REPORT;
+ break;
+
+ case BYTES_OUT:
+ tmp.bytes_out = strtoull(w, NULL, 10);
+ nval++;
+ state = IN_REPORT;
+ break;
+
+ case BYTES_OUT_BCAST:
+ tmp.bytes_out_bcast = strtoull(w, NULL, 10);
+ nval++;
+ state = IN_REPORT;
+ break;
+
+ case BW:
+ sscanf(w, "%d", &tmp.bandwidth);
+ tmp.bandwidth *= 1000; /* Kbit -> bytes/sec */
+ tmp.bandwidth /= 8;
+ nval++;
+ state = IN_REPORT;
+ break;
+
+ }
+ }
+ alarm(0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Extracted %d values ...\n", nval);
+ if (tmp.bandwidth != 0xffffffff)
+ fprintf(stderr, "bandwidth: %d bytes/sec\n", tmp.bandwidth);
+ else
+ fprintf(stderr, "bandwidth: ? bytes/sec\n");
+ fprintf(stderr, "recent rate (bytes/sec):");
+ if (tmp.rate_in != 0xffffffff)
+ fprintf(stderr, " %d in", tmp.rate_in);
+ else
+ fprintf(stderr, " ? in");
+ if (tmp.rate_out != 0xffffffff)
+ fprintf(stderr, " %d out", tmp.rate_out);
+ else
+ fprintf(stderr, " ? out");
+ fprintf(stderr, "\ntotal bytes:");
+ if (tmp.bytes_in != 0xffffffffffffffffLL)
+ fprintf(stderr, " %llu in", (unsigned long long)tmp.bytes_in);
+ else
+ fprintf(stderr, " ? in");
+ if (tmp.bytes_out != 0xffffffffffffffffLL)
+ fprintf(stderr, " %llu out", (unsigned long long)tmp.bytes_out);
+ else
+ fprintf(stderr, " ? out");
+ if (tmp.bytes_out_bcast != 0xffffffffffffffffLL)
+ fprintf(stderr, " %llu out_bcast", (unsigned long long)tmp.bytes_out_bcast);
+ else
+ fprintf(stderr, " ? out_bcast");
+ fprintf(stderr, "\n\n");
+ }
+#endif
+
+ /* pretend this is atomic */
+ ip->bandwidth = tmp.bandwidth;
+ ip->rate_in = tmp.rate_in;
+ ip->rate_out = tmp.rate_out;
+ ip->bytes_in = tmp.bytes_in;
+ ip->bytes_out = tmp.bytes_out;
+ ip->bytes_out_bcast = tmp.bytes_out_bcast;
+
+ return nval;
+}
diff --git a/src/pmdas/darwin/GNUmakefile b/src/pmdas/darwin/GNUmakefile
new file mode 100644
index 0000000..a2fd3a7
--- /dev/null
+++ b/src/pmdas/darwin/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = darwin
+DOMAIN = DARWIN
+CMDTARGET = pmdadarwin
+LIBTARGET = pmda_darwin.dylib
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "darwin 78 dso darwin_init $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = pmda.c kernel.c disk.c network.c
+HFILES = disk.h network.h
+
+LSRCFILES = help root pmns
+
+FRAMEWORKS = -framework CoreFoundation -framework IOKit
+LLDLIBS = $(PCP_PMDALIB) $(FRAMEWORKS)
+
+LDIRT = *.log *.dir *.pag root_darwin domain.h
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "darwin"
+build-me: root_darwin domain.h $(LIBTARGET) $(CMDTARGET) help.dir help.pag
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help.dir help.pag $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_darwin $(PCP_VAR_DIR)/pmns/root_darwin
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+help.dir help.pag: help root_darwin
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_darwin -v 2 -o help < help
+
+root_darwin: ../../pmns/stdpmid root pmns
+ rm -f root_darwin
+ sed -e 's;<stdpmid>;"../../pmns/stdpmid";' <root \
+ | $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp \
+ | sed -e '/^#/d' -e '/^$$/d' >root_darwin
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/darwin/disk.c b/src/pmdas/darwin/disk.c
new file mode 100644
index 0000000..5d175b3
--- /dev/null
+++ b/src/pmdas/darwin/disk.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <mach/mach.h>
+#define IOKIT 1
+#include <device/device_types.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/storage/IOBlockStorageDriver.h>
+#include <IOKit/storage/IOMedia.h>
+#include <IOKit/IOBSD.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include "disk.h"
+
+/*
+ * Ensure we have space for the next device in our pre-allocated
+ * device pool. If not, make some or pass on the error.
+ */
+static int
+check_stats_size(struct diskstats *stats, int count)
+{
+ if (count > stats->highwater) {
+ stats->highwater++;
+ stats->disks = realloc(stats->disks,
+ stats->highwater * sizeof(struct diskstat));
+ if (!stats->disks) {
+ stats->highwater = 0;
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Insert all disks into the global disk instance domain.
+ */
+static int
+update_disk_indom(struct diskstats *all, int count, pmdaIndom *indom)
+{
+ int i;
+
+ if (count > 0 && count != indom->it_numinst) {
+ i = sizeof(pmdaInstid) * count;
+ if ((indom->it_set = realloc(indom->it_set, i)) == NULL) {
+ indom->it_numinst = 0;
+ return -ENOMEM;
+ }
+ }
+ for (i = 0; i < count; i++) {
+ indom->it_set[i].i_name = all->disks[i].name;
+ indom->it_set[i].i_inst = i;
+ }
+ indom->it_numinst = count;
+ return 0;
+}
+
+/*
+ * Update the global counters with values from one disk.
+ */
+static void
+update_disk_totals(struct diskstats *all, struct diskstat *disk)
+{
+ all->read += disk->read;
+ all->write += disk->write;
+ all->read_bytes += disk->read_bytes;
+ all->write_bytes += disk->write_bytes;
+ all->blkread += disk->read_bytes / disk->blocksize;
+ all->blkwrite += disk->write_bytes / disk->blocksize;
+ all->read_time += disk->read_time;
+ all->write_time += disk->write_time;
+}
+
+static void
+clear_disk_totals(struct diskstats *all)
+{
+ all->read = 0;
+ all->write = 0;
+ all->read_bytes = 0;
+ all->write_bytes = 0;
+ all->blkread = 0;
+ all->blkwrite = 0;
+ all->read_time = 0;
+ all->write_time = 0;
+}
+
+/*
+ * Update the counters associated with a single disk.
+ */
+static void
+update_disk_stats(struct diskstat *disk,
+ CFDictionaryRef pproperties, CFDictionaryRef properties)
+{
+ CFDictionaryRef statistics;
+ CFStringRef name;
+ CFNumberRef number;
+
+ memset(disk, 0, sizeof(struct diskstat));
+
+ /* Get name from the drive properties */
+ name = (CFStringRef) CFDictionaryGetValue(pproperties,
+ CFSTR(kIOBSDNameKey));
+ if(name == NULL)
+ return; /* Not much we can do with no name */
+
+ CFStringGetCString(name, disk->name, DEVNAMEMAX,
+ CFStringGetSystemEncoding());
+
+ /* Get the blocksize from the drive properties */
+ number = (CFNumberRef) CFDictionaryGetValue(pproperties,
+ CFSTR(kIOMediaPreferredBlockSizeKey));
+ if(number == NULL)
+ return; /* Not much we can do with no number */
+ CFNumberGetValue(number, kCFNumberSInt64Type, &disk->blocksize);
+
+ /* Get the statistics from the device properties. */
+ statistics = (CFDictionaryRef) CFDictionaryGetValue(properties,
+ CFSTR(kIOBlockStorageDriverStatisticsKey));
+ if (statistics) {
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsReadsKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->read);
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsWritesKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->write);
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->read_bytes);
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->write_bytes);
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->read_time);
+ number = (CFNumberRef) CFDictionaryGetValue(statistics,
+ CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey));
+ if (number)
+ CFNumberGetValue(number, kCFNumberSInt64Type,
+ &disk->write_time);
+ }
+}
+
+static int
+update_disk(diskstats_t *stats, io_registry_entry_t drive, int index)
+{
+ io_registry_entry_t device;
+ CFDictionaryRef pproperties, properties;
+ int status;
+
+ /* Get the drives parent, from which we get statistics. */
+ status = IORegistryEntryGetParentEntry(drive, kIOServicePlane, &device);
+ if (status != KERN_SUCCESS)
+ return -oserror();
+
+ if (!IOObjectConformsTo(device, "IOBlockStorageDriver")) {
+ IOObjectRelease(device);
+ return 0;
+ }
+
+ /* Obtain the drive properties. */
+ pproperties = 0;
+ status = IORegistryEntryCreateCFProperties(drive,
+ (CFMutableDictionaryRef *)&pproperties,
+ kCFAllocatorDefault, kNilOptions);
+ if (status != KERN_SUCCESS) {
+ IOObjectRelease(device);
+ return -oserror();
+ }
+
+ /* Obtain the device properties. */
+ properties = 0;
+ status = IORegistryEntryCreateCFProperties(device,
+ (CFMutableDictionaryRef *)&properties,
+ kCFAllocatorDefault, kNilOptions);
+ if (status != KERN_SUCCESS) {
+ IOObjectRelease(device);
+ return -oserror();
+ }
+
+ /* Make space to store the actual values, then go get them. */
+ status = check_stats_size(stats, index + 1);
+ if (status < 0) {
+ IOObjectRelease(device);
+ } else {
+ update_disk_stats(&stats->disks[index], pproperties, properties);
+ update_disk_totals(stats, &stats->disks[index]);
+ }
+ CFRelease(pproperties);
+ CFRelease(properties);
+ return status;
+}
+
+int
+refresh_disks(struct diskstats *stats, pmdaIndom *indom)
+{
+ io_registry_entry_t drive;
+ CFMutableDictionaryRef match;
+ int i, status;
+ static int inited = 0;
+ static mach_port_t mach_master_port;
+ static io_iterator_t mach_device_list;
+
+ if (!inited) {
+ /* Get ports and services for device statistics. */
+ if (IOMasterPort(bootstrap_port, &mach_master_port)) {
+ fprintf(stderr, "%s: IOMasterPort error\n", __FUNCTION__);
+ return -oserror();
+ }
+ memset(stats, 0, sizeof(struct diskstats));
+ inited = 1;
+ }
+
+ /* Get an interator for IOMedia objects (drives). */
+ match = IOServiceMatching("IOMedia");
+ CFDictionaryAddValue(match, CFSTR(kIOMediaWholeKey), kCFBooleanTrue);
+ status = IOServiceGetMatchingServices(mach_master_port,
+ match, &mach_device_list);
+ if (status != KERN_SUCCESS) {
+ fprintf(stderr, "%s: IOServiceGetMatchingServices error\n",
+ __FUNCTION__);
+ return -oserror();
+ }
+
+ indom->it_numinst = 0;
+ clear_disk_totals(stats);
+ for (i = 0; (drive = IOIteratorNext(mach_device_list)) != 0; i++) {
+ status = update_disk(stats, drive, i);
+ if (status)
+ break;
+ IOObjectRelease(drive);
+ }
+ IOIteratorReset(mach_device_list);
+
+ if (!status)
+ status = update_disk_indom(stats, i, indom);
+ return status;
+}
diff --git a/src/pmdas/darwin/disk.h b/src/pmdas/darwin/disk.h
new file mode 100644
index 0000000..c3579ff
--- /dev/null
+++ b/src/pmdas/darwin/disk.h
@@ -0,0 +1,55 @@
+/*
+ * Disk statistics types
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define DEVNAMEMAX 255 /* largest device name we allow */
+
+/*
+ * Per-device statistics
+ */
+typedef struct diskstat {
+ __uint64_t read;
+ __uint64_t write;
+ __uint64_t read_bytes;
+ __uint64_t write_bytes;
+ __uint64_t read_time;
+ __uint64_t write_time;
+ __uint64_t blocksize;
+ char name[DEVNAMEMAX + 1];
+} diskstat_t;
+
+/*
+ * Global statistics.
+ *
+ * We avoid continually realloc'ing memory by keeping track
+ * of the maximum number of devices we've allocated space for
+ * so far, and only realloc new space if we go beyond that.
+ */
+typedef struct diskstats {
+ __uint64_t read;
+ __uint64_t write;
+ __uint64_t read_bytes;
+ __uint64_t write_bytes;
+ __uint64_t blkread;
+ __uint64_t blkwrite;
+ __uint64_t read_time;
+ __uint64_t write_time;
+ int highwater; /* largest number of devices seen so far */
+ diskstat_t *disks; /* space for highwater number of devices */
+} diskstats_t;
+
diff --git a/src/pmdas/darwin/help b/src/pmdas/darwin/help
new file mode 100644
index 0000000..ef8deee
--- /dev/null
+++ b/src/pmdas/darwin/help
@@ -0,0 +1,199 @@
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# MacOS X PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+@ kernel.uname.release release level of the running kernel
+@ kernel.uname.version version level (build number) and build date of the running kernel
+@ kernel.uname.sysname name of the implementation of the operating system
+@ kernel.uname.machine name of the hardware type the system is running on
+@ kernel.uname.nodename host name of this node on the network
+
+@ kernel.all.cpu.user total user time for all processors
+@ kernel.all.cpu.nice total nice time for all processors
+@ kernel.all.cpu.sys total system time for all processors
+@ kernel.all.cpu.idle total idle time for all processors
+@ kernel.all.load 1, 5 and 15 minute load average
+@ kernel.all.uptime time the current kernel has been running
+@ kernel.all.hz value of HZ (jiffies/second) for the currently running kernel
+@ hinv.ncpu number of processors
+@ kernel.percpu.cpu.user percpu user processor time metric
+@ kernel.percpu.cpu.nice percpu nice user processor time metric
+@ kernel.percpu.cpu.sys percpu system processor time metric
+@ kernel.percpu.cpu.idle percpu idle processor time metric
+
+@ hinv.physmem total system memory
+@ hinv.pagesize system memory page size
+@ mem.physmem total system memory metric
+@ mem.freemem total pages free in the system
+@ mem.active the total pages currently in use and pageable
+@ mem.inactive the total pages on the inactive list
+@ mem.pages.freemem total number of free pages in the system
+@ mem.pages.active the number of pages currently in use and pageable
+@ mem.pages.inactive the number of pages on the inactive list
+@ mem.pages.reactivated the number of pages that have been moved from inactive to active list
+@ mem.pages.wired the total number of pages wired down (cannot be paged out)
+@ mem.pages.faults the number of times the "vm_fault" routine has been called
+@ mem.pages.cow_faults the number of faults that caused a page to be copied
+@ mem.pages.zero_filled the number of pages that have been zero-filled on demand
+@ mem.pageins the number of requests for pages from a pager
+@ mem.pageouts the number of pages that have been paged out
+@ mem.cache_hits the number of object cache hits
+@ mem.cache_lookups the number of object cache lookups
+@ mem.util.wired wired memory
+@ mem.util.active active memory
+@ mem.util.inactive inactive memory
+@ mem.util.free free memory
+
+@ hinv.nfilesys number of file systems currently mounted
+@ filesys.capacity total capacity of mounted filesystem (Kbytes)
+@ filesys.used total space used on mounted filesystem (Kbytes)
+@ filesys.free total space free on mounted filesystem (Kbytes)
+@ filesys.usedfiles number of inodes allocated on mounted filesystem
+@ filesys.freefiles number of unallocated inodes on mounted filesystem
+@ filesys.mountdir file system mount point
+@ filesys.full percentage of filesystem in use
+@ filesys.blocksize size of each block on mounted filesystem (Bytes)
+@ filesys.avail total space free to non-superusers on mounted filesystem (Kbytes)
+@ filesys.type filesystem type name for each mounted filesystem
+
+@ hinv.ndisk number of disks in the system
+@ disk.dev.read per-disk read operations
+Cumulative number of disk read operations since system boot time (subject
+to counter wrap).
+
+@ disk.dev.write per-disk write operations
+Cumulative number of disk write operations since system boot time (subject
+to counter wrap).
+
+@ disk.dev.total per-disk total (read+write) operations
+Cumulative number of disk read and write operations since system boot
+time (subject to counter wrap).
+
+@ disk.dev.read_bytes per-disk count of bytes read
+@ disk.dev.write_bytes per-disk count of bytes written
+@ disk.dev.total_bytes per-disk count bytes read and written
+@ disk.dev.blkread per-disk block read operations
+Cumulative number of disk block read operations since system boot time
+(subject to counter wrap).
+
+@ disk.dev.blkwrite per-disk block write operations
+Cumulative number of disk block write operations since system boot time
+(subject to counter wrap).
+
+@ disk.dev.blktotal per-disk total (read+write) block operations
+Cumulative number of disk block read and write operations since system
+boot time (subject to counter wrap).
+
+@ disk.dev.read_time i dunno either
+@ disk.dev.write_time i dunno either
+@ disk.dev.total_time i dunno either
+@ disk.all.read_time i dunno either
+@ disk.all.write_time i dunno either
+@ disk.all.total_time i dunno either
+
+@ disk.all.read total read operations, summed for all disks
+Cumulative number of disk read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.write total write operations, summed for all disks
+Cumulative number of disk read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.total total read and write operations, summed for all disks
+Cumulative number of disk read and write operations since system boot
+time (subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blkread block read operations, summed for all disks
+Cumulative number of disk block read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blkwrite block write operations, summed for all disks
+Cumulative number of disk block write operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blktotal total (read+write) block operations, summed for all disks
+Cumulative number of disk block read and write operations since system
+boot time (subject to counter wrap), summed over all disk devices.
+
+@ disk.all.read_bytes count of bytes read for all disk devices
+@ disk.all.write_bytes count of bytes written for all disk devices
+@ disk.all.total_bytes count of bytes read and written for all disk devices
+
+@ network.interface.in.bytes network receive read bytes
+@ network.interface.in.packets network receive read packets
+@ network.interface.in.errors network receive read errors
+@ network.interface.in.drops connections dropped on input
+@ network.interface.in.mcasts network receive multicasts
+@ network.interface.out.bytes network send write bytes
+@ network.interface.out.packets network send write packets
+@ network.interface.out.errors network send write errors
+@ network.interface.out.mcasts network send multicasts
+@ network.interface.collisions network send collisions for CDMA interfaces
+@ network.interface.mtu maximum transmission size for network interfaces
+@ network.interface.baudrate line speed for network interfaces
+@ network.interface.total.bytes total network bytes received and sent
+@ network.interface.total.packets total network packets received and sent
+@ network.interface.total.errors total network errors on receive and send
+@ network.interface.total.drops total network connections dropped
+@ network.interface.total.mcasts total network multicasts
+
+@ nfs3.client.calls
+@ nfs3.client.reqs
+@ nfs3.server.calls
+@ nfs3.server.reqs
+@ rpc.client.rpccnt
+@ rpc.client.rpcretrans
+@ rpc.client.rpctimeouts
+@ rpc.client.rpcinvalid
+@ rpc.client.rpcunexpected
+@ rpc.client.attrcache.hits
+@ rpc.client.attrcache.misses
+@ rpc.client.lookupcache.hits
+@ rpc.client.lookupcache.misses
+@ rpc.client.biocache.read.hits
+@ rpc.client.biocache.read.misses
+@ rpc.client.biocache.write.hits
+@ rpc.client.biocache.write.misses
+@ rpc.client.biocache.readlink.hits
+@ rpc.client.biocache.readlink.misses
+@ rpc.client.biocache.readdir.hits
+@ rpc.client.biocache.readdir.misses
+@ rpc.client.direofcache.hits
+@ rpc.client.direofcache.misses
+@ rpc.server.retfailed
+@ rpc.server.faults
+@ rpc.server.cache.inprog
+@ rpc.server.cache.nonidem
+@ rpc.server.cache.idem
+@ rpc.server.cache.misses
+@ rpc.server.vopwrites
+@ rpc.server.pageins
+@ rpc.server.pageouts
diff --git a/src/pmdas/darwin/kernel.c b/src/pmdas/darwin/kernel.c
new file mode 100644
index 0000000..57921c9
--- /dev/null
+++ b/src/pmdas/darwin/kernel.c
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/utsname.h>
+#include <sys/sysctl.h>
+#include <sys/mount.h>
+#include <mach/mach.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+extern mach_port_t mach_host;
+extern int mach_hertz;
+
+int
+refresh_vmstat(struct vm_statistics *vmstat)
+{
+ int error, info = HOST_VM_INFO;
+ natural_t count = HOST_VM_INFO_COUNT;
+
+ error = host_statistics(mach_host, info, (host_info_t)vmstat, &count);
+ return (error != KERN_SUCCESS) ? -oserror() : 0;
+}
+
+int
+refresh_cpuload(struct host_cpu_load_info *cpuload)
+{
+ int error, info = HOST_CPU_LOAD_INFO;
+ natural_t count = HOST_CPU_LOAD_INFO_COUNT;
+
+ error = host_statistics(mach_host, info, (host_info_t)cpuload, &count);
+ return (error != KERN_SUCCESS) ? -oserror() : 0;
+}
+
+int
+refresh_uname(struct utsname *utsname)
+{
+ return (uname(utsname) == -1) ? -oserror() : 0;
+}
+
+int
+refresh_hertz(unsigned int *hertz)
+{
+ int mib[2] = { CTL_KERN, KERN_CLOCKRATE };
+ size_t size = sizeof(struct clockinfo);
+ struct clockinfo clockrate;
+
+ if (sysctl(mib, 2, &clockrate, &size, NULL, 0) == -1)
+ return -oserror();
+ *hertz = clockrate.hz;
+ return 0;
+}
+
+int
+refresh_loadavg(float *loadavg)
+{
+ int mib[2] = { CTL_VM, VM_LOADAVG };
+ size_t size = sizeof(struct loadavg);
+ struct loadavg loadavgs;
+
+ if (sysctl(mib, 2, &loadavgs, &size, NULL, 0) == -1)
+ return -oserror();
+ loadavg[0] = (float)loadavgs.ldavg[0] / (float)loadavgs.fscale;
+ loadavg[1] = (float)loadavgs.ldavg[1] / (float)loadavgs.fscale;
+ loadavg[2] = (float)loadavgs.ldavg[2] / (float)loadavgs.fscale;
+ return 0;
+}
+
+int
+refresh_uptime(unsigned int *uptime)
+{
+ static struct timeval boottime;
+ struct timeval timediff;
+
+ if (!boottime.tv_sec) {
+ int mib[2] = { CTL_KERN, KERN_BOOTTIME };
+ size_t size = sizeof(struct timeval);
+
+ if (sysctl(mib, 2, &boottime, &size, NULL, 0) == -1)
+ return -oserror();
+ }
+
+ __pmtimevalNow(&timediff);
+ timediff.tv_usec -= boottime.tv_usec;
+ if (timediff.tv_usec < 0) {
+ timediff.tv_usec += 1000000;
+ timediff.tv_sec--;
+ }
+ timediff.tv_sec -= boottime.tv_sec;
+
+ *uptime = timediff.tv_sec;
+ return 0;
+}
+
+int
+refresh_cpus(struct processor_cpu_load_info **cpuload, pmdaIndom *indom)
+{
+ natural_t ncpu, icount;
+ processor_info_array_t iarray;
+ struct processor_cpu_load_info *cpuinfo;
+ int error, i, info = PROCESSOR_CPU_LOAD_INFO;
+
+ error = host_processor_info(mach_host, info, &ncpu, &iarray, &icount);
+ if (error != KERN_SUCCESS)
+ return -oserror();
+
+ cpuinfo = (struct processor_cpu_load_info *)iarray;
+ if (ncpu != indom->it_numinst) {
+ char name[16]; /* 8 is real max atm, but be conservative */
+
+ error = -ENOMEM;
+ i = sizeof(unsigned long) * CPU_STATE_MAX * ncpu;
+ if ((*cpuload = realloc(*cpuload, i)) == NULL)
+ goto vmdealloc;
+
+ i = sizeof(pmdaInstid) * ncpu;
+ if ((indom->it_set = realloc(indom->it_set, i)) == NULL) {
+ free(*cpuload);
+ *cpuload = NULL;
+ indom->it_numinst = 0;
+ goto vmdealloc;
+ }
+
+ for (i = 0; i < ncpu; i++) {
+ snprintf(name, sizeof(name), "cpu%d", i);
+ indom->it_set[i].i_name = strdup(name);
+ indom->it_set[i].i_inst = i;
+ }
+ indom->it_numinst = ncpu;
+ }
+
+ error = 0;
+ for (i = 0; i < ncpu; i++)
+ memcpy(&(*cpuload)[i], &cpuinfo[i],
+ sizeof(unsigned long) * CPU_STATE_MAX);
+
+vmdealloc:
+ vm_deallocate(mach_host, (vm_address_t)iarray, icount);
+ return error;
+}
+
+int
+refresh_filesys(struct statfs **filesys, pmdaIndom *indom)
+{
+ int i, count = getmntinfo(filesys, MNT_NOWAIT);
+
+ if (count < 0) {
+ indom->it_numinst = 0;
+ indom->it_set = NULL;
+ return -oserror();
+ }
+ if (count > 0 && count != indom->it_numinst) {
+ i = sizeof(pmdaInstid) * count;
+ if ((indom->it_set = realloc(indom->it_set, i)) == NULL) {
+ indom->it_numinst = 0;
+ return -ENOMEM;
+ }
+ }
+ for (i = 0; i < count; i++) {
+ indom->it_set[i].i_name = (*filesys)[i].f_mntfromname;
+ indom->it_set[i].i_inst = i;
+ }
+ indom->it_numinst = count;
+ return 0;
+}
+
+#if 0
+int
+refresh_hinv()
+{
+sysctl...
+hw.machine = Power Macintosh
+hw.model = PowerMac4,2
+hw.busfrequency = 99837332
+hw.cpufrequency = 700000000
+hw.cachelinesize = 32
+hw.l1icachesize = 32768
+hw.l1dcachesize = 32768
+hw.l2settings = 2147483648
+hw.l2cachesize = 262144
+}
+#endif
diff --git a/src/pmdas/darwin/network.c b/src/pmdas/darwin/network.c
new file mode 100644
index 0000000..bc134c6
--- /dev/null
+++ b/src/pmdas/darwin/network.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2004,2006 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+#include <mach/mach.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "network.h"
+
+extern char *pmProgname;
+extern mach_port_t mach_master_port;
+
+void
+init_network(void)
+{
+
+}
+
+/*
+ * Ensure we have space for the next interface in our pre-allocated
+ * interface stats pool. If not, make some or pass on the error.
+ */
+static int
+check_stats_size(struct netstats *stats, int count)
+{
+ if (count > stats->highwater) {
+ stats->highwater++;
+ stats->interfaces = realloc(stats->interfaces,
+ stats->highwater * sizeof(struct ifacestat));
+ if (!stats->interfaces) {
+ stats->highwater = 0;
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Insert all interfaces into the global network instance domain.
+ */
+static int
+update_network_indom(struct netstats *all, int count, pmdaIndom *indom)
+{
+ int i;
+
+ if (count > 0 && count != indom->it_numinst) {
+ i = sizeof(pmdaInstid) * count;
+ if ((indom->it_set = realloc(indom->it_set, i)) == NULL) {
+ indom->it_numinst = 0;
+ return -ENOMEM;
+ }
+ }
+ for (i = 0; i < count; i++) {
+ indom->it_set[i].i_name = all->interfaces[i].name;
+ indom->it_set[i].i_inst = i;
+ }
+ indom->it_numinst = count;
+ return 0;
+}
+
+int
+refresh_network(struct netstats *stats, pmdaIndom *indom)
+{
+ int i = 0, status = 0;
+
+ size_t n;
+ char *new_buf, *next, *end;
+ struct sockaddr_dl *sdl;
+
+ static char *buf=NULL;
+ static size_t buf_len=0;
+
+#ifdef RTM_IFINFO2
+ struct if_msghdr2 *ifm;
+ int mib[6] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0};
+#else
+ struct if_msghdr *ifm;
+ int mib[6] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0};
+#endif
+
+ if( sysctl( mib, 6, NULL, &n, NULL, 0 ) < 0 ) {
+ /* unable to query buffer size */
+ fprintf( stderr, "%s: get net mib buf len failed\n", pmProgname );
+ return -ENXIO;
+ }
+ if( n > buf_len ) {
+ if( (new_buf = malloc(n)) == NULL ) {
+ /* unable to malloc buf */
+ fprintf( stderr, "%s: net mib buf malloc failed\n", pmProgname );
+ return -ENXIO;
+ } else {
+ if( buf != NULL ) free( buf );
+ buf = new_buf;
+ buf_len = n;
+ }
+ }
+ if( sysctl( mib, 6, buf, &n, NULL, 0 ) < 0 ) {
+ /* unable to copy-in buffer */
+ fprintf( stderr, "%s: net mib buf read failed\n", pmProgname );
+ return -ENXIO;
+ }
+
+ for( next = buf, i=0, end = buf + n; next < end; ) {
+
+#ifdef RTM_IFINFO2
+ ifm = (struct if_msghdr2 *)next;
+ next += ifm->ifm_msglen;
+ if( ifm->ifm_type == RTM_IFINFO2 ) {
+#else
+ ifm = (struct if_msghdr *)next;
+ next += ifm->ifm_msglen;
+ if( ifm->ifm_type == RTM_IFINFO ) {
+#endif
+
+ status = check_stats_size(stats, i + 1);
+ if (status < 0) break;
+
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ n = sdl->sdl_nlen < IFNAMEMAX ? sdl->sdl_nlen : IFNAMEMAX;
+ strncpy( stats->interfaces[i].name, sdl->sdl_data, n );
+ stats->interfaces[i].name[n] = 0;
+
+ stats->interfaces[i].mtu = ifm->ifm_data.ifi_mtu;
+ stats->interfaces[i].baudrate = ifm->ifm_data.ifi_baudrate;
+ stats->interfaces[i].ipackets = ifm->ifm_data.ifi_ipackets;
+ stats->interfaces[i].ierrors = ifm->ifm_data.ifi_ierrors;
+ stats->interfaces[i].opackets = ifm->ifm_data.ifi_opackets;
+ stats->interfaces[i].oerrors = ifm->ifm_data.ifi_oerrors;
+ stats->interfaces[i].collisions = ifm->ifm_data.ifi_collisions;
+ stats->interfaces[i].ibytes = ifm->ifm_data.ifi_ibytes;
+ stats->interfaces[i].obytes = ifm->ifm_data.ifi_obytes;
+ stats->interfaces[i].imcasts = ifm->ifm_data.ifi_imcasts;
+ stats->interfaces[i].omcasts = ifm->ifm_data.ifi_omcasts;
+ stats->interfaces[i].iqdrops = ifm->ifm_data.ifi_iqdrops;
+ i++;
+ }
+ }
+ if (!status) update_network_indom(stats, i, indom);
+ return status;
+}
+
+int
+refresh_nfs(struct nfsstats *stats)
+{
+ int name[3];
+ size_t length = sizeof(struct nfsstats);
+ static int nfstype = -1;
+
+ if (nfstype == -1) {
+ struct vfsconf vfsconf;
+
+ if (getvfsbyname("nfs", &vfsconf) == -1)
+ return -oserror();
+ nfstype = vfsconf.vfc_typenum;
+ }
+
+ name[0] = CTL_VFS;
+ name[1] = nfstype;
+ name[0] = NFS_NFSSTATS;
+ if (sysctl(name, 3, stats, &length, NULL, 0) == -1)
+ return -oserror();
+ stats->biocache_reads -= stats->read_bios;
+ stats->biocache_writes -= stats->write_bios;
+ stats->biocache_readlinks -= stats->readlink_bios;
+ stats->biocache_readdirs -= stats->readdir_bios;
+ return 0;
+}
diff --git a/src/pmdas/darwin/network.h b/src/pmdas/darwin/network.h
new file mode 100644
index 0000000..985ec9a
--- /dev/null
+++ b/src/pmdas/darwin/network.h
@@ -0,0 +1,57 @@
+/*
+ * Network interface statistics types
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <sys/mount.h>
+#include <nfs/rpcv2.h>
+#include <nfs/nfsproto.h>
+#include <nfs/nfs.h>
+
+#define IFNAMEMAX 16 /* largest interface name we allow */
+
+/*
+ * Per-interface statistics
+ */
+typedef struct ifacestat {
+ __uint64_t mtu; /* maximum transmission unit */
+ __uint64_t baudrate; /* linespeed */
+ __uint64_t ipackets; /* packets received on interface */
+ __uint64_t ierrors; /* input errors on interface */
+ __uint64_t opackets; /* packets sent on interface */
+ __uint64_t oerrors; /* output errors on interface */
+ __uint64_t collisions; /* collisions on csma interfaces */
+ __uint64_t ibytes; /* total number of octets received */
+ __uint64_t obytes; /* total number of octets sent */
+ __uint64_t imcasts; /* packets received via multicast */
+ __uint64_t omcasts; /* packets sent via multicast */
+ __uint64_t iqdrops; /* dropped on input, this interface */
+ char name[IFNAMEMAX + 1];
+} ifacestat_t;
+
+/*
+ * Global statistics.
+ *
+ * We avoid continually realloc'ing memory by keeping track
+ * of the maximum number of interfaces we've allocated space
+ * for so far, and only realloc new space if we go beyond that.
+ */
+typedef struct netstats {
+ int highwater; /* largest number of interfaces seen so far */
+ ifacestat_t *interfaces; /* space for highwater number of interfaces */
+} netstats_t;
+
diff --git a/src/pmdas/darwin/pmda.c b/src/pmdas/darwin/pmda.c
new file mode 100644
index 0000000..e000b11
--- /dev/null
+++ b/src/pmdas/darwin/pmda.c
@@ -0,0 +1,1268 @@
+/*
+ * MacOS X kernel PMDA
+ * "darwin" is easier to type than "macosx", especially for Aussies. ;-)
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/utsname.h>
+#include <mach/mach.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+
+#include "disk.h"
+#include "network.h"
+
+
+#define page_count_to_kb(x) (((__uint64_t)(x) << mach_page_shift) >> 10)
+#define page_count_to_mb(x) (((__uint64_t)(x) << mach_page_shift) >> 20)
+
+static pmdaInterface dispatch;
+static int _isDSO = 1; /* =0 I am a daemon */
+static char *username;
+
+mach_port_t mach_host = 0;
+vm_size_t mach_page_size = 0;
+unsigned int mach_page_shift = 0;
+
+unsigned int mach_hertz = 0;
+extern int refresh_hertz(unsigned int *);
+
+int mach_uname_error = 0;
+struct utsname mach_uname = { { 0 } };
+extern int refresh_uname(struct utsname *);
+
+int mach_loadavg_error = 0;
+float mach_loadavg[3] = { 0,0,0 };
+extern int refresh_loadavg(float *);
+
+int mach_cpuload_error = 0;
+struct host_cpu_load_info mach_cpuload = { { 0 } };
+extern int refresh_cpuload(struct host_cpu_load_info *);
+
+int mach_vmstat_error = 0;
+struct vm_statistics mach_vmstat = { 0 };
+extern int refresh_vmstat(struct vm_statistics *);
+
+int mach_fs_error = 0;
+struct statfs *mach_fs = NULL;
+extern int refresh_filesys(struct statfs **, pmdaIndom *);
+
+int mach_disk_error = 0;
+struct diskstats mach_disk = { 0 };
+extern int refresh_disks(struct diskstats *, pmdaIndom *);
+
+int mach_cpu_error = 0;
+struct processor_cpu_load_info *mach_cpu = NULL;
+extern int refresh_cpus(struct processor_cpu_load_info **, pmdaIndom *);
+
+int mach_uptime_error = 0;
+unsigned int mach_uptime = 0;
+extern int refresh_uptime(unsigned int *);
+
+int mach_net_error = 0;
+struct netstats mach_net = { 0 };
+extern int refresh_network(struct netstats *, pmdaIndom *);
+extern void init_network(void);
+
+int mach_nfs_error = 0;
+struct nfsstats mach_nfs = { 0 };
+extern int refresh_nfs(struct nfsstats *);
+
+/*
+ * Metric Instance Domains (statically initialized ones only)
+ */
+static pmdaInstid loadavg_indom_id[] = {
+ { 1, "1 minute" }, { 5, "5 minute" }, { 15, "15 minute" }
+};
+#define LOADAVG_COUNT (sizeof(loadavg_indom_id)/sizeof(pmdaInstid))
+
+static pmdaInstid nfs3_indom_id[] = {
+ { 0, "null" }, { 1, "getattr" }, { 2, "setattr" },
+ { 3, "lookup" }, { 4, "access" }, { 5, "readlink" },
+ { 6, "read" }, { 7, "write" }, { 8, "create" },
+ { 9, "mkdir" }, { 10, "symlink" }, { 11, "mknod" },
+ { 12, "remove" }, { 13, "rmdir" }, { 14, "rename" },
+ { 15, "link" }, { 16, "readdir" }, { 17, "readdir+" },
+ { 18, "statfs" }, { 19, "fsinfo" }, { 20, "pathconf" },
+ { 21, "commit" }, { 22, "getlease" }, { 23, "vacate" },
+ { 24, "evict" }
+};
+#define NFS3_RPC_COUNT (sizeof(nfs3_indom_id)/sizeof(pmdaInstid))
+
+/*
+ * Metric Instance Domain table
+ */
+enum {
+ LOADAVG_INDOM, /* 0 - 1, 5, 15 minute run queue averages */
+ FILESYS_INDOM, /* 1 - set of all mounted filesystems */
+ DISK_INDOM, /* 2 - set of all disk devices */
+ CPU_INDOM, /* 3 - set of all processors */
+ NETWORK_INDOM, /* 4 - set of all network interfaces */
+ NFS3_INDOM, /* 6 - nfs v3 operations */
+ NUM_INDOMS /* total number of instance domains */
+};
+
+static pmdaIndom indomtab[] = {
+ { LOADAVG_INDOM, 3, loadavg_indom_id },
+ { FILESYS_INDOM, 0, NULL },
+ { DISK_INDOM, 0, NULL },
+ { CPU_INDOM, 0, NULL },
+ { NETWORK_INDOM, 0, NULL },
+ { NFS3_INDOM, NFS3_RPC_COUNT, nfs3_indom_id },
+};
+
+/*
+ * Fetch clusters and metric table
+ */
+enum {
+ CLUSTER_INIT = 0, /* 0 = values we know at startup */
+ CLUSTER_VMSTAT, /* 1 = mach memory statistics */
+ CLUSTER_KERNEL_UNAME, /* 2 = utsname information */
+ CLUSTER_LOADAVG, /* 3 = run queue averages */
+ CLUSTER_HINV, /* 4 = hardware inventory */
+ CLUSTER_FILESYS, /* 5 = mounted filesystems */
+ CLUSTER_CPULOAD, /* 6 = number of ticks in state */
+ CLUSTER_DISK, /* 7 = disk device statistics */
+ CLUSTER_CPU, /* 8 = per-cpu statistics */
+ CLUSTER_UPTIME, /* 9 = system uptime in seconds */
+ CLUSTER_NETWORK, /* 10 = networking statistics */
+ CLUSTER_NFS, /* 11 = nfs filesystem statistics */
+ NUM_CLUSTERS /* total number of clusters */
+};
+
+static pmdaMetric metrictab[] = {
+
+/* hinv.pagesize */
+ { &mach_page_size,
+ { PMDA_PMID(CLUSTER_INIT,0), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* kernel.all.hz */
+ { &mach_hertz,
+ { PMDA_PMID(CLUSTER_INIT,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) }, },
+
+/* hinv.physmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_MBYTE,0,0) }, },
+/* mem.physmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,3), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.freemem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,4), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,5), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.inactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,6), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.pages.free */
+ { &mach_vmstat.free_count,
+ { PMDA_PMID(CLUSTER_VMSTAT,7), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.active */
+ { &mach_vmstat.active_count,
+ { PMDA_PMID(CLUSTER_VMSTAT,8), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.inactive */
+ { &mach_vmstat.inactive_count,
+ { PMDA_PMID(CLUSTER_VMSTAT,9), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.reactivated */
+ { &mach_vmstat.reactivations,
+ { PMDA_PMID(CLUSTER_VMSTAT,10), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.wired */
+ { &mach_vmstat.wire_count,
+ { PMDA_PMID(CLUSTER_VMSTAT,11), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.faults */
+ { &mach_vmstat.faults,
+ { PMDA_PMID(CLUSTER_VMSTAT,12), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.cow_faults */
+ { &mach_vmstat.cow_faults,
+ { PMDA_PMID(CLUSTER_VMSTAT,13), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pages.zero_filled */
+ { &mach_vmstat.zero_fill_count,
+ { PMDA_PMID(CLUSTER_VMSTAT,14), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pageins */
+ { &mach_vmstat.pageins,
+ { PMDA_PMID(CLUSTER_VMSTAT,15), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.pageouts */
+ { &mach_vmstat.pageouts,
+ { PMDA_PMID(CLUSTER_VMSTAT,16), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.cache_hits */
+ { &mach_vmstat.hits,
+ { PMDA_PMID(CLUSTER_VMSTAT,17), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.cache_lookups */
+ { &mach_vmstat.lookups,
+ { PMDA_PMID(CLUSTER_VMSTAT,18), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* mem.util.wired */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,19), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.util.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,20), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.util.inactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,21), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* mem.util.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_VMSTAT,22), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* kernel.uname.release */
+ { mach_uname.release,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 23), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* kernel.uname.version */
+ { mach_uname.version,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 24), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* kernel.uname.sysname */
+ { mach_uname.sysname,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 25), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* kernel.uname.machine */
+ { mach_uname.machine,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 26), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* kernel.uname.nodename */
+ { mach_uname.nodename,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 27), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* pmda.uname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 28), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* pmda.version */
+ { NULL,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 29), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* kernel.all.load */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LOADAVG,30), PM_TYPE_FLOAT, LOADAVG_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.nfilesys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,31), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* filesys.capacity */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,32), PM_TYPE_U64, FILESYS_INDOM,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* filesys.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,33), PM_TYPE_U64, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* filesys.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,34), PM_TYPE_U64, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* filesys.usedfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,35), PM_TYPE_U32, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* filesys.freefiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,36), PM_TYPE_U32, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* filesys.mountdir */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,37), PM_TYPE_STRING, FILESYS_INDOM,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* filesys.full */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,38), PM_TYPE_DOUBLE, FILESYS_INDOM,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* filesys.blocksize */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,39), PM_TYPE_U32, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* filesys.avail */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,40), PM_TYPE_U64, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* filesys.type */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,41), PM_TYPE_STRING, FILESYS_INDOM,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* kernel.all.cpu.user */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPULOAD,42), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.all.cpu.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPULOAD,43), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.all.cpu.sys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPULOAD,44), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.all.cpu.idle */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPULOAD,45), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* hinv.ndisk */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,46), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* disk.dev.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,47), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,48), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,49), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,50), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.dev.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,51), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.dev.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,52), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.dev.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,53), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,54), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,55), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.dev.read_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,56), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+/* disk.dev.write_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,57), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+/* disk.dev.total_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,58), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+/* disk.all.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,61), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,62), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.all.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.all.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* disk.all.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,65), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,66), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,67), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* disk.all.read_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,68), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+/* disk.all.write_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,69), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+/* disk.all.total_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DISK,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+/* hinv.ncpu */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPU,71), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* kernel.percpu.cpu.user */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPU,72), PM_TYPE_U64, CPU_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.percpu.cpu.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPU,73), PM_TYPE_U64, CPU_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.percpu.cpu.sys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPU,74), PM_TYPE_U64, CPU_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* kernel.percpu.cpu.idle */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPU,75), PM_TYPE_U64, CPU_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.uptime */
+ { &mach_uptime,
+ { PMDA_PMID(CLUSTER_UPTIME,76), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,1,0,PM_TIME_SEC,PM_COUNT_ONE) }, },
+
+/* network.interface.in.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,77), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* network.interface.in.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,78), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.in.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,79), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.in.drops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,80), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.in.mcasts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,81), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.out.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,82), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+/* network.interface.out.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,83), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.out.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,84), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.out.mcasts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,85), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.collisions */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,86), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.mtu */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,87), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+/* network.interface.baudrate */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,88), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+/* network.interface.total.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,89), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+/* network.interface.total.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,90), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.total.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,91), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.total.drops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,92), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* network.interface.total.mcasts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NETWORK,93), PM_TYPE_U64, NETWORK_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* nfs3.client.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,94), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* nfs3.client.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,95), PM_TYPE_32, NFS3_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* nfs3.server.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,96), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* nfs3.server.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,97), PM_TYPE_32, NFS3_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.rpccnt */
+ { &mach_nfs.rpcrequests,
+ { PMDA_PMID(CLUSTER_NFS,98), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.rpcretrans */
+ { &mach_nfs.rpcretries,
+ { PMDA_PMID(CLUSTER_NFS,99), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.rpctimeouts */
+ { &mach_nfs.rpctimeouts,
+ { PMDA_PMID(CLUSTER_NFS,100), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.rpcinvalid */
+ { &mach_nfs.rpcinvalid,
+ { PMDA_PMID(CLUSTER_NFS,101), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.rpcunexpected */
+ { &mach_nfs.rpcunexpected,
+ { PMDA_PMID(CLUSTER_NFS,102), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.attrcache.hits */
+ { &mach_nfs.attrcache_hits,
+ { PMDA_PMID(CLUSTER_NFS,103), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.attrcache.misses */
+ { &mach_nfs.attrcache_misses,
+ { PMDA_PMID(CLUSTER_NFS,104), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.lookupcache.hits */
+ { &mach_nfs.lookupcache_hits,
+ { PMDA_PMID(CLUSTER_NFS,105), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.lookupcache.misses */
+ { &mach_nfs.lookupcache_misses,
+ { PMDA_PMID(CLUSTER_NFS,106), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.read.hits */
+ { &mach_nfs.read_bios,
+ { PMDA_PMID(CLUSTER_NFS,107), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.read.misses */
+ { &mach_nfs.biocache_reads,
+ { PMDA_PMID(CLUSTER_NFS,108), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.write.hits */
+ { &mach_nfs.write_bios,
+ { PMDA_PMID(CLUSTER_NFS,109), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.write.misses */
+ { &mach_nfs.biocache_writes,
+ { PMDA_PMID(CLUSTER_NFS,110), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.readlink.hits */
+ { &mach_nfs.readlink_bios,
+ { PMDA_PMID(CLUSTER_NFS,111), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.readlink.misses */
+ { &mach_nfs.biocache_readlinks,
+ { PMDA_PMID(CLUSTER_NFS,112), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.readdir.hits */
+ { &mach_nfs.readdir_bios,
+ { PMDA_PMID(CLUSTER_NFS,113), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.biocache.readdir.misses */
+ { &mach_nfs.biocache_readdirs,
+ { PMDA_PMID(CLUSTER_NFS,114), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.direofcache.hits */
+ { &mach_nfs.direofcache_hits,
+ { PMDA_PMID(CLUSTER_NFS,115), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.client.direofcache.misses */
+ { &mach_nfs.direofcache_misses,
+ { PMDA_PMID(CLUSTER_NFS,116), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.retfailed */
+ { &mach_nfs.srvrpc_errs,
+ { PMDA_PMID(CLUSTER_NFS,117), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.faults */
+ { &mach_nfs.srvrpc_errs,
+ { PMDA_PMID(CLUSTER_NFS,118), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.cache.inprog */
+ { &mach_nfs.srvcache_inproghits,
+ { PMDA_PMID(CLUSTER_NFS,119), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.cache.nonidem */
+ { &mach_nfs.srvcache_nonidemdonehits,
+ { PMDA_PMID(CLUSTER_NFS,120), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.cache.idem */
+ { &mach_nfs.srvcache_idemdonehits,
+ { PMDA_PMID(CLUSTER_NFS,121), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.cache.misses */
+ { &mach_nfs.srvcache_misses,
+ { PMDA_PMID(CLUSTER_NFS,122), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.nqnfs.leases -- deprecated */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,123), PM_TYPE_NOSUPPORT, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.nqnfs.maxleases -- deprecated */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,124), PM_TYPE_NOSUPPORT, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.nqnfs.getleases -- deprecated */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NFS,125), PM_TYPE_NOSUPPORT, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.vopwrites */
+ { &mach_nfs.srvvop_writes,
+ { PMDA_PMID(CLUSTER_NFS,126), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.pageins */
+ { &mach_nfs.pageins,
+ { PMDA_PMID(CLUSTER_NFS,127), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* rpc.server.pageouts */
+ { &mach_nfs.pageouts,
+ { PMDA_PMID(CLUSTER_NFS,128), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* filesys.maxfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,129), PM_TYPE_U32, FILESYS_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+};
+
+static void
+darwin_refresh(int *need_refresh)
+{
+ if (need_refresh[CLUSTER_LOADAVG])
+ mach_loadavg_error = refresh_loadavg(mach_loadavg);
+ if (need_refresh[CLUSTER_CPULOAD])
+ mach_cpuload_error = refresh_cpuload(&mach_cpuload);
+ if (need_refresh[CLUSTER_VMSTAT])
+ mach_vmstat_error = refresh_vmstat(&mach_vmstat);
+ if (need_refresh[CLUSTER_KERNEL_UNAME])
+ mach_uname_error = refresh_uname(&mach_uname);
+ if (need_refresh[CLUSTER_FILESYS])
+ mach_fs_error = refresh_filesys(&mach_fs, &indomtab[FILESYS_INDOM]);
+ if (need_refresh[CLUSTER_DISK])
+ mach_disk_error = refresh_disks(&mach_disk, &indomtab[DISK_INDOM]);
+ if (need_refresh[CLUSTER_CPU])
+ mach_cpu_error = refresh_cpus(&mach_cpu, &indomtab[CPU_INDOM]);
+ if (need_refresh[CLUSTER_UPTIME])
+ mach_uptime_error = refresh_uptime(&mach_uptime);
+ if (need_refresh[CLUSTER_NETWORK])
+ mach_net_error = refresh_network(&mach_net, &indomtab[NETWORK_INDOM]);
+ if (need_refresh[CLUSTER_NFS])
+ mach_nfs_error = refresh_nfs(&mach_nfs);
+}
+
+static inline int
+fetch_loadavg(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_loadavg_error)
+ return mach_loadavg_error;
+ switch (item) {
+ case 30: /* kernel.all.load */
+ if (inst == 1)
+ atom->f = mach_loadavg[0];
+ else if (inst == 5)
+ atom->f = mach_loadavg[1];
+ else if (inst == 15)
+ atom->f = mach_loadavg[2];
+ else
+ return PM_ERR_INST;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_cpuload(unsigned int item, pmAtomValue *atom)
+{
+ if (mach_cpuload_error)
+ return mach_cpuload_error;
+ switch (item) {
+ case 42: /* kernel.all.cpu.user */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpuload.cpu_ticks[CPU_STATE_USER] / mach_hertz;
+ return 1;
+ case 43: /* kernel.all.cpu.nice */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpuload.cpu_ticks[CPU_STATE_NICE] / mach_hertz;
+ return 1;
+ case 44: /* kernel.all.cpu.sys */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpuload.cpu_ticks[CPU_STATE_SYSTEM] / mach_hertz;
+ return 1;
+ case 45: /* kernel.all.cpu.idle */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpuload.cpu_ticks[CPU_STATE_IDLE] / mach_hertz;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_vmstat(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_vmstat_error)
+ return mach_vmstat_error;
+ switch (item) {
+ case 2: /* hinv.physmem */
+ atom->ul = (__uint32_t)page_count_to_mb(
+ mach_vmstat.free_count + mach_vmstat.wire_count +
+ mach_vmstat.active_count + mach_vmstat.inactive_count);
+ return 1;
+ case 3: /* mem.physmem */
+ atom->ull = page_count_to_kb(
+ mach_vmstat.free_count + mach_vmstat.wire_count +
+ mach_vmstat.active_count + mach_vmstat.inactive_count);
+ return 1;
+ case 4: /* mem.freemem */
+ atom->ull = page_count_to_kb(mach_vmstat.free_count);
+ return 1;
+ case 5: /* mem.active */
+ atom->ull = page_count_to_kb(mach_vmstat.active_count);
+ return 1;
+ case 6: /* mem.inactive */
+ atom->ull = page_count_to_kb(mach_vmstat.inactive_count);
+ return 1;
+ case 19: /* mem.util.wired */
+ atom->ull = page_count_to_kb(mach_vmstat.wire_count);
+ return 1;
+ case 20: /* mem.util.active */
+ atom->ull = page_count_to_kb(mach_vmstat.active_count);
+ return 1;
+ case 21: /* mem.util.inactive */
+ atom->ull = page_count_to_kb(mach_vmstat.inactive_count);
+ return 1;
+ case 22: /* mem.util.free */
+ atom->ull = page_count_to_kb(mach_vmstat.free_count);
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_uname(unsigned int item, pmAtomValue *atom)
+{
+ static char mach_uname_all[(_SYS_NAMELEN*5)+8];
+
+ if (mach_uname_error)
+ return mach_uname_error;
+ switch (item) {
+ case 28: /* pmda.uname */
+ snprintf(mach_uname_all, sizeof(mach_uname_all), "%s %s %s %s %s",
+ mach_uname.sysname, mach_uname.nodename,
+ mach_uname.release, mach_uname.version,
+ mach_uname.machine);
+ atom->cp = mach_uname_all;
+ return 1;
+ case 29: /* pmda.version */
+ atom->cp = pmGetConfig("PCP_VERSION");
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_filesys(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ __uint64_t ull, used;
+
+ if (mach_fs_error)
+ return mach_fs_error;
+ if (item == 31) { /* hinv.nfilesys */
+ atom->ul = indomtab[FILESYS_INDOM].it_numinst;
+ return 1;
+ }
+ if (indomtab[FILESYS_INDOM].it_numinst == 0)
+ return 0; /* no values available */
+ if (inst < 0 || inst >= indomtab[FILESYS_INDOM].it_numinst)
+ return PM_ERR_INST;
+ switch (item) {
+ case 32: /* filesys.capacity */
+ ull = (__uint64_t)mach_fs[inst].f_blocks;
+ atom->ull = ull * mach_fs[inst].f_bsize >> 10;
+ return 1;
+ case 33: /* filesys.used */
+ used = (__uint64_t)(mach_fs[inst].f_blocks - mach_fs[inst].f_bfree);
+ atom->ull = used * mach_fs[inst].f_bsize >> 10;
+ return 1;
+ case 34: /* filesys.free */
+ ull = (__uint64_t)mach_fs[inst].f_bfree;
+ atom->ull = ull * mach_fs[inst].f_bsize >> 10;
+ return 1;
+ case 129: /* filesys.maxfiles */
+ atom->ul = mach_fs[inst].f_files;
+ return 1;
+ case 35: /* filesys.usedfiles */
+ atom->ul = mach_fs[inst].f_files - mach_fs[inst].f_ffree;
+ return 1;
+ case 36: /* filesys.freefiles */
+ atom->ul = mach_fs[inst].f_ffree;
+ return 1;
+ case 37: /* filesys.mountdir */
+ atom->cp = mach_fs[inst].f_mntonname;
+ return 1;
+ case 38: /* filesys.full */
+ used = (__uint64_t)(mach_fs[inst].f_blocks - mach_fs[inst].f_bfree);
+ ull = used + (__uint64_t)mach_fs[inst].f_bavail;
+ atom->d = (100.0 * (double)used) / (double)ull;
+ return 1;
+ case 39: /* filesys.blocksize */
+ atom->ul = mach_fs[inst].f_bsize;
+ return 1;
+ case 40: /* filesys.avail */
+ ull = (__uint64_t)mach_fs[inst].f_bavail;
+ atom->ull = ull * mach_fs[inst].f_bsize >> 10;
+ return 1;
+ case 41: /* filesys.type */
+ atom->cp = mach_fs[inst].f_fstypename;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_disk(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_disk_error)
+ return mach_disk_error;
+ if (item == 46) { /* hinv.ndisk */
+ atom->ul = indomtab[DISK_INDOM].it_numinst;
+ return 1;
+ }
+ if (indomtab[DISK_INDOM].it_numinst == 0)
+ return 0; /* no values available */
+ if (item < 59 && (inst < 0 || inst >= indomtab[DISK_INDOM].it_numinst))
+ return PM_ERR_INST;
+ switch (item) {
+ case 47: /* disk.dev.read */
+ atom->ull = mach_disk.disks[inst].read;
+ return 1;
+ case 48: /* disk.dev.write */
+ atom->ull = mach_disk.disks[inst].write;
+ return 1;
+ case 49: /* disk.dev.total */
+ atom->ull = mach_disk.disks[inst].read + mach_disk.disks[inst].write;
+ return 1;
+ case 50: /* disk.dev.read_bytes */
+ atom->ull = mach_disk.disks[inst].read_bytes >> 10;
+ return 1;
+ case 51: /* disk.dev.write_bytes */
+ atom->ull = mach_disk.disks[inst].write_bytes >> 10;
+ return 1;
+ case 52: /* disk.dev.total_bytes */
+ atom->ull = (mach_disk.disks[inst].read_bytes +
+ mach_disk.disks[inst].write_bytes) >> 10;
+ return 1;
+ case 53: /* disk.dev.blkread */
+ atom->ull = mach_disk.disks[inst].read_bytes /
+ mach_disk.disks[inst].blocksize;
+ return 1;
+ case 54: /* disk.dev.blkwrite */
+ atom->ull = mach_disk.disks[inst].write_bytes /
+ mach_disk.disks[inst].blocksize;
+ return 1;
+ case 55: /* disk.dev.blktotal */
+ atom->ull = (mach_disk.disks[inst].read_bytes +
+ mach_disk.disks[inst].write_bytes) /
+ mach_disk.disks[inst].blocksize;
+ return 1;
+ case 56: /* disk.dev.read_time */
+ atom->ull = mach_disk.disks[inst].read_time;
+ return 1;
+ case 57: /* disk.dev.write_time */
+ atom->ull = mach_disk.disks[inst].write_time;
+ return 1;
+ case 58: /* disk.dev.total_time */
+ atom->ull = mach_disk.disks[inst].read_time +
+ mach_disk.disks[inst].write_time;
+ return 1;
+ case 59: /* disk.all.read */
+ atom->ull = mach_disk.read;
+ return 1;
+ case 60: /* disk.all.write */
+ atom->ull = mach_disk.write;
+ return 1;
+ case 61: /* disk.all.total */
+ atom->ull = mach_disk.read + mach_disk.write;
+ return 1;
+ case 62: /* disk.all.read_bytes */
+ atom->ull = mach_disk.read_bytes >> 10;
+ return 1;
+ case 63: /* disk.all.write_bytes */
+ atom->ull = mach_disk.write_bytes >> 10;
+ return 1;
+ case 64: /* disk.all.total_bytes */
+ atom->ull = (mach_disk.read_bytes + mach_disk.write_bytes) >> 10;
+ return 1;
+ case 65: /* disk.all.blkread */
+ atom->ull = mach_disk.blkread;
+ return 1;
+ case 66: /* disk.all.blkwrite */
+ atom->ull = mach_disk.blkwrite;
+ return 1;
+ case 67: /* disk.all.blktotal */
+ atom->ull = mach_disk.blkread + mach_disk.blkwrite;
+ return 1;
+ case 68: /* disk.all.read_time */
+ atom->ull = mach_disk.read_time;
+ return 1;
+ case 69: /* disk.all.write_time */
+ atom->ull = mach_disk.write_time;
+ return 1;
+ case 70: /* disk.all.total_time */
+ atom->ull = mach_disk.read_time + mach_disk.write_time;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_cpu(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_cpu_error)
+ return mach_cpu_error;
+ if (item == 71) { /* hinv.ncpu */
+ atom->ul = indomtab[CPU_INDOM].it_numinst;
+ return 1;
+ }
+ if (indomtab[CPU_INDOM].it_numinst == 0) /* uh-huh. */
+ return 0; /* no values available */
+ if (inst < 0 || inst >= indomtab[CPU_INDOM].it_numinst)
+ return PM_ERR_INST;
+ switch (item) {
+ case 72: /* kernel.percpu.cpu.user */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpu[inst].cpu_ticks[CPU_STATE_USER] / mach_hertz;
+ return 1;
+ case 73: /* kernel.percpu.cpu.nice */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpu[inst].cpu_ticks[CPU_STATE_NICE] / mach_hertz;
+ return 1;
+ case 74: /* kernel.percpu.cpu.sys */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpu[inst].cpu_ticks[CPU_STATE_SYSTEM] / mach_hertz;
+ return 1;
+ case 75: /* kernel.percpu.cpu.idle */
+ atom->ull = LOAD_SCALE * (double)
+ mach_cpu[inst].cpu_ticks[CPU_STATE_IDLE] / mach_hertz;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_network(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_net_error)
+ return mach_net_error;
+ if (indomtab[NETWORK_INDOM].it_numinst == 0)
+ return 0; /* no values available */
+ if (inst < 0 || inst >= indomtab[NETWORK_INDOM].it_numinst)
+ return PM_ERR_INST;
+ switch (item) {
+ case 77: /* network.interface.in.bytes */
+ atom->ull = mach_net.interfaces[inst].ibytes;
+ return 1;
+ case 78: /* network.interface.in.packets */
+ atom->ull = mach_net.interfaces[inst].ipackets;
+ return 1;
+ case 79: /* network.interface.in.errors */
+ atom->ull = mach_net.interfaces[inst].ierrors;
+ return 1;
+ case 80: /* network.interface.in.drops */
+ atom->ull = mach_net.interfaces[inst].iqdrops;
+ return 1;
+ case 81: /* network.interface.in.mcasts */
+ atom->ull = mach_net.interfaces[inst].imcasts;
+ return 1;
+ case 82: /* network.interface.out.bytes */
+ atom->ull = mach_net.interfaces[inst].obytes;
+ return 1;
+ case 83: /* network.interface.out.packets */
+ atom->ull = mach_net.interfaces[inst].opackets;
+ return 1;
+ case 84: /* network.interface.out.errors */
+ atom->ull = mach_net.interfaces[inst].oerrors;
+ return 1;
+ case 85: /* network.interface.out.mcasts */
+ atom->ull = mach_net.interfaces[inst].omcasts;
+ return 1;
+ case 86: /* network.interface.collisions */
+ atom->ull = mach_net.interfaces[inst].collisions;
+ return 1;
+ case 87: /* network.interface.mtu */
+ atom->ull = mach_net.interfaces[inst].mtu;
+ return 1;
+ case 88: /* network.interface.baudrate */
+ atom->ull = mach_net.interfaces[inst].baudrate;
+ return 1;
+ case 89: /* network.interface.total.bytes */
+ atom->ull = mach_net.interfaces[inst].ibytes +
+ mach_net.interfaces[inst].obytes;
+ return 1;
+ case 90: /* network.interface.total.packets */
+ atom->ull = mach_net.interfaces[inst].ipackets +
+ mach_net.interfaces[inst].opackets;
+ return 1;
+ case 91: /* network.interface.total.errors */
+ atom->ull = mach_net.interfaces[inst].ierrors +
+ mach_net.interfaces[inst].oerrors;
+ return 1;
+ case 92: /* network.interface.total.drops */
+ atom->ull = mach_net.interfaces[inst].iqdrops;
+ return 1;
+ case 93: /* network.interface.total.mcasts */
+ atom->ull = mach_net.interfaces[inst].imcasts +
+ mach_net.interfaces[inst].omcasts;
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+static inline int
+fetch_nfs(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (mach_net_error)
+ return mach_net_error;
+ switch (item) {
+ case 94: /* nfs3.client.calls */
+ for (atom->l = 0, inst = 0; inst < NFS3_RPC_COUNT; inst++)
+ atom->l += mach_nfs.rpccnt[inst];
+ return 1;
+ case 95: /* nfs3.client.reqs */
+ if (inst < 0 || inst >= NFS3_RPC_COUNT)
+ return PM_ERR_INST;
+ atom->l = mach_nfs.rpccnt[inst];
+ return 1;
+ case 96: /* nfs3.server.calls */
+ for (atom->l = 0, inst = 0; inst < NFS3_RPC_COUNT; inst++)
+ atom->l += mach_nfs.srvrpccnt[inst];
+ return 1;
+ case 97: /* nfs3.server.reqs */
+ if (inst < 0 || inst >= NFS3_RPC_COUNT)
+ return PM_ERR_INST;
+ atom->l = mach_nfs.srvrpccnt[inst];
+ return 1;
+ case 123: /* rpc.server.nqnfs.leases -- deprecated */
+ case 124: /* rpc.server.nqnfs.maxleases -- deprecated */
+ case 125: /* rpc.server.nqnfs.getleases -- deprecated */
+ return PM_ERR_APPVERSION;
+ }
+ return PM_ERR_PMID;
+}
+
+
+static int
+darwin_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (mdesc->m_user) {
+ /*
+ * The metric value is extracted directly via the address specified
+ * in metrictab. Note: not all metrics support this - those that
+ * don't have NULL for the m_user field in their respective
+ * metrictab slot.
+ */
+ switch (mdesc->m_desc.type) {
+ case PM_TYPE_32: atom->l = *(__int32_t *)mdesc->m_user; break;
+ case PM_TYPE_U32: atom->ul = *(__uint32_t *)mdesc->m_user; break;
+ case PM_TYPE_64: atom->ll = *(__int64_t *)mdesc->m_user; break;
+ case PM_TYPE_U64: atom->ull = *(__uint64_t *)mdesc->m_user; break;
+ case PM_TYPE_FLOAT: atom->f = *(float *)mdesc->m_user; break;
+ case PM_TYPE_DOUBLE: atom->d = *(double *)mdesc->m_user; break;
+ case PM_TYPE_STRING: atom->cp = (char *)mdesc->m_user; break;
+ case PM_TYPE_NOSUPPORT: return 0;
+ default: fprintf(stderr,
+ "Error in fetchCallBack: unsupported metric type %s\n",
+ pmTypeStr(mdesc->m_desc.type));
+ return 0;
+ }
+ return 1;
+ }
+
+ switch (idp->cluster) {
+ case CLUSTER_LOADAVG: return fetch_loadavg(idp->item, inst, atom);
+ case CLUSTER_CPULOAD: return fetch_cpuload(idp->item, atom);
+ case CLUSTER_VMSTAT: return fetch_vmstat(idp->item, inst, atom);
+ case CLUSTER_KERNEL_UNAME: return fetch_uname(idp->item, atom);
+ case CLUSTER_FILESYS: return fetch_filesys(idp->item, inst, atom);
+ case CLUSTER_DISK: return fetch_disk(idp->item, inst, atom);
+ case CLUSTER_CPU: return fetch_cpu(idp->item, inst, atom);
+ case CLUSTER_NETWORK: return fetch_network(idp->item, inst, atom);
+ case CLUSTER_NFS: return fetch_nfs(idp->item, inst, atom);
+ }
+ return 0;
+}
+
+static int
+darwin_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ __pmInDom_int *indomp = (__pmInDom_int *)&indom;
+ int need_refresh[NUM_CLUSTERS] = { 0 };
+
+ switch (indomp->serial) {
+ case FILESYS_INDOM: need_refresh[CLUSTER_FILESYS]++; break;
+ case DISK_INDOM: need_refresh[CLUSTER_DISK]++; break;
+ case CPU_INDOM: need_refresh[CLUSTER_CPU]++; break;
+ case NETWORK_INDOM: need_refresh[CLUSTER_NETWORK]++; break;
+ }
+ darwin_refresh(need_refresh);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+darwin_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i, need_refresh[NUM_CLUSTERS] = { 0 };
+
+ for (i = 0; i < numpmid; i++) {
+ __pmID_int *idp = (__pmID_int *)&(pmidlist[i]);
+ if (idp->cluster >= 0 && idp->cluster < NUM_CLUSTERS)
+ need_refresh[idp->cluster]++;
+ }
+ darwin_refresh(need_refresh);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+void
+darwin_init(pmdaInterface *dp)
+{
+ if (_isDSO) {
+ int sep = __pmPathSeparator();
+ char helppath[MAXPATHLEN];
+ sprintf(helppath, "%s%c" "darwin" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_3, "darwin DSO", helppath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.instance = darwin_instance;
+ dp->version.two.fetch = darwin_fetch;
+ pmdaSetFetchCallBack(dp, darwin_fetchCallBack);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_DIRECT);
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+
+ mach_host = mach_host_self();
+ host_page_size(mach_host, &mach_page_size);
+ mach_page_shift = ffs(mach_page_size) - 1;
+ if (refresh_hertz(&mach_hertz) != 0)
+ mach_hertz = 100;
+ init_network();
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fputs("Options:\n"
+" -d domain use domain (numeric) for metrics domain of PMDA\n"
+" -l logfile write log into logfile rather than using default log name\n"
+" -U username user account to run under (default \"pcp\")\n"
+"\nExactly one of the following options may appear:\n"
+" -i port expect PMCD to connect on given inet port (number or name)\n"
+" -p expect PMCD to supply stdin/stdout (pipe)\n"
+" -u socket expect PMCD to connect on given unix domain socket\n"
+" -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ stderr);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, sep = __pmPathSeparator();
+ int errflag = 0;
+ char helppath[MAXPATHLEN];
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ sprintf(helppath, "%s%c" "darwin" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, DARWIN, "darwin.log",
+ helppath);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:i:l:pu:U:6:?", &dispatch, &errflag)) != EOF) {
+ switch(c) {
+ case 'U':
+ username = optarg;
+ break;
+ default:
+ errflag++;
+ }
+ }
+ if (errflag)
+ usage();
+
+ pmdaOpenLog(&dispatch);
+ darwin_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/darwin/pmns b/src/pmdas/darwin/pmns
new file mode 100644
index 0000000..da82370
--- /dev/null
+++ b/src/pmdas/darwin/pmns
@@ -0,0 +1,258 @@
+
+hinv {
+ physmem DARWIN:1:2
+ pagesize DARWIN:0:0
+ ncpu DARWIN:8:71
+ nfilesys DARWIN:5:31
+ ndisk DARWIN:7:46
+}
+
+pmda {
+ uname DARWIN:2:28
+ version DARWIN:2:29
+}
+
+kernel {
+ uname
+ all
+ percpu
+}
+
+kernel.uname {
+ release DARWIN:2:23
+ version DARWIN:2:24
+ sysname DARWIN:2:25
+ machine DARWIN:2:26
+ nodename DARWIN:2:27
+}
+
+kernel.all {
+ cpu
+ load DARWIN:3:30
+ uptime DARWIN:9:76
+ hz DARWIN:0:1
+}
+
+kernel.all.cpu {
+ user DARWIN:6:42
+ nice DARWIN:6:43
+ sys DARWIN:6:44
+ idle DARWIN:6:45
+}
+
+kernel.percpu {
+ cpu
+}
+
+kernel.percpu.cpu {
+ user DARWIN:8:72
+ nice DARWIN:8:73
+ sys DARWIN:8:74
+ idle DARWIN:8:75
+}
+
+mem {
+ physmem DARWIN:1:3
+ freemem DARWIN:1:4
+ active DARWIN:1:5
+ inactive DARWIN:1:6
+ pages
+ pageins DARWIN:1:15
+ pageouts DARWIN:1:16
+ cache_hits DARWIN:1:17
+ cache_lookups DARWIN:1:18
+ util
+}
+
+mem.pages {
+ freemem DARWIN:1:7
+ active DARWIN:1:8
+ inactive DARWIN:1:9
+ reactivated DARWIN:1:10
+ wired DARWIN:1:11
+ faults DARWIN:1:12
+ cow_faults DARWIN:1:13
+ zero_filled DARWIN:1:14
+}
+
+mem.util {
+ wired DARWIN:1:19
+ active DARWIN:1:20
+ inactive DARWIN:1:21
+ free DARWIN:1:22
+}
+
+filesys {
+ capacity DARWIN:5:32
+ used DARWIN:5:33
+ free DARWIN:5:34
+ maxfiles DARWIN:5:129
+ usedfiles DARWIN:5:35
+ freefiles DARWIN:5:36
+ mountdir DARWIN:5:37
+ full DARWIN:5:38
+ blocksize DARWIN:5:39
+ avail DARWIN:5:40
+ type DARWIN:5:41
+}
+
+disk {
+ dev
+ all
+}
+
+disk.dev {
+ read DARWIN:7:47
+ write DARWIN:7:48
+ total DARWIN:7:49
+ read_bytes DARWIN:7:50
+ write_bytes DARWIN:7:51
+ total_bytes DARWIN:7:52
+ blkread DARWIN:7:53
+ blkwrite DARWIN:7:54
+ blktotal DARWIN:7:55
+ read_time DARWIN:7:56
+ write_time DARWIN:7:57
+ total_time DARWIN:7:58
+}
+
+disk.all {
+ read DARWIN:7:59
+ write DARWIN:7:60
+ total DARWIN:7:61
+ read_bytes DARWIN:7:62
+ write_bytes DARWIN:7:63
+ total_bytes DARWIN:7:64
+ blkread DARWIN:7:65
+ blkwrite DARWIN:7:66
+ blktotal DARWIN:7:67
+ read_time DARWIN:7:68
+ write_time DARWIN:7:69
+ total_time DARWIN:7:70
+}
+
+network {
+ interface
+}
+
+network.interface {
+ in
+ out
+ collisions DARWIN:10:86
+ mtu DARWIN:10:87
+ baudrate DARWIN:10:88
+ total
+}
+
+network.interface.in {
+ bytes DARWIN:10:77
+ packets DARWIN:10:78
+ errors DARWIN:10:79
+ drops DARWIN:10:80
+ mcasts DARWIN:10:81
+}
+
+network.interface.out {
+ bytes DARWIN:10:82
+ packets DARWIN:10:83
+ errors DARWIN:10:84
+ mcasts DARWIN:10:85
+}
+
+network.interface.total {
+ bytes DARWIN:10:89
+ packets DARWIN:10:90
+ errors DARWIN:10:91
+ drops DARWIN:10:92
+ mcasts DARWIN:10:93
+}
+
+nfs3 {
+ client
+ server
+}
+
+nfs3.client {
+ calls DARWIN:11:94
+ reqs DARWIN:11:95
+}
+
+nfs3.server {
+ calls DARWIN:11:96
+ reqs DARWIN:11:97
+}
+
+rpc {
+ client
+ server
+}
+
+rpc.client {
+ rpccnt DARWIN:11:98
+ rpcretrans DARWIN:11:99
+ rpctimeouts DARWIN:11:100
+ rpcinvalid DARWIN:11:101
+ rpcunexpected DARWIN:11:102
+ attrcache
+ lookupcache
+ biocache
+ direofcache
+}
+
+rpc.client.attrcache {
+ hits DARWIN:11:103
+ misses DARWIN:11:104
+}
+
+rpc.client.lookupcache {
+ hits DARWIN:11:105
+ misses DARWIN:11:106
+}
+
+rpc.client.biocache {
+ read
+ write
+ readlink
+ readdir
+}
+
+rpc.client.biocache.read {
+ hits DARWIN:11:107
+ misses DARWIN:11:108
+}
+
+rpc.client.biocache.write {
+ hits DARWIN:11:109
+ misses DARWIN:11:110
+}
+
+rpc.client.biocache.readlink {
+ hits DARWIN:11:111
+ misses DARWIN:11:112
+}
+
+rpc.client.biocache.readdir {
+ hits DARWIN:11:113
+ misses DARWIN:11:114
+}
+
+rpc.client.direofcache {
+ hits DARWIN:11:115
+ misses DARWIN:11:116
+}
+
+rpc.server {
+ retfailed DARWIN:11:117
+ faults DARWIN:11:118
+ cache
+ vopwrites DARWIN:11:126
+ pageins DARWIN:11:127
+ pageouts DARWIN:11:128
+}
+
+rpc.server.cache {
+ inprog DARWIN:11:119
+ nonidem DARWIN:11:120
+ idem DARWIN:11:121
+ misses DARWIN:11:122
+}
diff --git a/src/pmdas/darwin/root b/src/pmdas/darwin/root
new file mode 100644
index 0000000..4a4ad71
--- /dev/null
+++ b/src/pmdas/darwin/root
@@ -0,0 +1,15 @@
+#include <stdpmid>
+
+root {
+ kernel
+ pmda
+ mem
+ hinv
+ filesys
+ disk
+ network
+ nfs3
+ rpc
+}
+
+#include "pmns"
diff --git a/src/pmdas/dbping/GNUmakefile b/src/pmdas/dbping/GNUmakefile
new file mode 100644
index 0000000..43044e3
--- /dev/null
+++ b/src/pmdas/dbping/GNUmakefile
@@ -0,0 +1,56 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = dbping
+DOMAIN = DBPING
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove dbprobe.pl pmda$(IAM).pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION) dbprobe.$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+dbprobe.1: dbprobe.pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl dbprobe.pl $(PMDADIR)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/dbping/Install b/src/pmdas/dbping/Install
new file mode 100755
index 0000000..09f5432
--- /dev/null
+++ b/src/pmdas/dbping/Install
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Database Ping PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dbping
+dso_opt=false
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+perl -e "use DBI" 2>/dev/null
+if test $? -ne 0; then
+ echo "Perl database interface (DBI) is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/dbping/Remove b/src/pmdas/dbping/Remove
new file mode 100755
index 0000000..7924339
--- /dev/null
+++ b/src/pmdas/dbping/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the Database Ping PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dbping
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/dbping/dbprobe.pl b/src/pmdas/dbping/dbprobe.pl
new file mode 100644
index 0000000..5f822dc
--- /dev/null
+++ b/src/pmdas/dbping/dbprobe.pl
@@ -0,0 +1,93 @@
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+use strict;
+use warnings;
+use DBI;
+use PCP::PMDA qw(pmda_config);
+use Time::HiRes qw(gettimeofday);
+
+my $database = 'DBI:mysql:mysql';
+my $username = 'dbmonitor';
+my $password = 'dbmonitor';
+my $response = 'SELECT 1';
+my $delay = $ARGV[0]; # delay in seconds between database ping's
+$delay = 60 unless defined($delay);
+
+# Configuration files for overriding the above settings
+for my $file ( '/etc/pcpdbi.conf', # system defaults (lowest priority)
+ pmda_config('PCP_PMDAS_DIR') . '/dbping/dbprobe.conf',
+ './dbprobe.conf' ) { # current directory (high priority)
+ eval `cat $file` unless ! -f $file;
+}
+
+use vars qw( $pmda $dbh $sth );
+
+sub dbping { # must return array of (response_time, status, time_stamp)
+ my $before;
+
+ if (!defined($dbh)) { # reconnect if necessary
+ $dbh = DBI->connect($database, $username, $password, undef) || return;
+ $pmda->log("Connected to database.\n");
+ undef $sth;
+ }
+ if (!defined($sth)) { # prepare SQL statement once only
+ $sth = $dbh->prepare($response) || return;
+ }
+ $before = gettimeofday;
+ $sth->execute || return;
+ while (my @row = $sth->fetchrow_array) {
+ ($sth->err) && return;
+ }
+
+ my $timenow = localtime;
+ print "$timenow\t", gettimeofday - $before, "\n";
+}
+
+$dbh = DBI->connect($database, $username, $password, undef) ||
+ $pmda->log("Failed initial connect: $dbh->errstr\n");
+
+$| = 1; # IMPORTANT! Enables auto-flush, for piping hot pipes.
+for (;;) {
+ dbping;
+ sleep($delay);
+}
+
+=pod
+
+=head1 NAME
+
+dbprobe.pl - database response time and availability information
+
+=head1 SYNOPSIS
+
+dbprobe.pl [ delay ]
+
+=head1 DESCRIPTION
+
+The B<dbprobe.pl> utility is used by pmdadbping(1) to measure
+response time from a database. A given query is executed on
+the database at the requested interval (I<delay>, which defaults
+to 60 seconds). This response time measure can be exported
+via the Performance Co-Pilot framework for live and historical
+monitoring using pmdadbping(1).
+
+=head1 SEE ALSO
+
+pmcd(1), pmdadbping(1) and DBI(3).
diff --git a/src/pmdas/dbping/pmdadbping.pl b/src/pmdas/dbping/pmdadbping.pl
new file mode 100644
index 0000000..ec8f2a0
--- /dev/null
+++ b/src/pmdas/dbping/pmdadbping.pl
@@ -0,0 +1,192 @@
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use vars qw( $pmda $stamp $response $status $timestamp );
+
+my $delay = $ARGV[0]; # delay in seconds between database ping's
+$delay = 60 unless defined($delay);
+my $dbprobe = pmda_config('PCP_PMDAS_DIR') . '/dbping/dbprobe.pl';
+$dbprobe = "perl " . $dbprobe . " $delay";
+my ( $stamp, $response, $status, $timestamp ) = ( 0, 0, 1, 0 );
+
+sub dbping_probe_callback
+{
+ ( $_ ) = @_;
+ ($stamp, $response) = split(/\t/);
+
+ # $pmda->log("dbping_probe_callback: time=$stamp resp=$response\n");
+
+ if (defined($stamp) && defined($response)) {
+ $timestamp = $stamp;
+ $status = 0;
+ } else {
+ $response = -1;
+ $status = 1; # bad result, keep old $timestamp
+ }
+}
+
+sub dbping_fetch_callback # must return array of value,status
+{
+ my ($cluster, $item, $inst) = @_;
+
+ # $pmda->log("dbping_fetch_callback $cluster:$item ($inst)\n");
+
+ return (PM_ERR_INST, 0) unless ($inst == PM_IN_NULL);
+
+ if ($cluster == 0) {
+ if ($item == 0) { return ($response, 1); }
+ elsif ($item == 1) { return ($status, 1); }
+ }
+ elsif ($cluster == 1) {
+ if ($item == 0) { return ($timestamp, 1); }
+ elsif ($item == 1) { return ($delay, 1); }
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+sub dbping_store_callback # must return a single value (scalar context)
+{
+ my ($cluster, $item, $inst, $val) = @_;
+ my $sts = 0;
+
+ # $pmda->log("dbping_store_callback $cluster:$item ($inst) $val\n");
+
+ if ($cluster == 1 && $item == 1) {
+ $delay = $val;
+ return 0;
+ }
+ elsif ( ($cluster == 0 && ($item == 0 || $item == 1))
+ || ($cluster == 1 && $item == 0) ) {
+ return PM_ERR_PERMISSION;
+ }
+ return PM_ERR_PMID;
+}
+
+$pmda = PCP::PMDA->new('dbping', 244);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dbping.response_time',
+ 'Length of time taken to access the database', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dbping.status',
+ 'Success state of last attempt to ping the database', '');
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dbping.control.timestamp',
+ 'Time of last successful database ping', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dbping.control.delay',
+ 'Time to sleep between database ping attempts', '');
+$pmda->set_fetch_callback( \&dbping_fetch_callback );
+$pmda->set_store_callback( \&dbping_store_callback );
+$pmda->add_pipe( $dbprobe, \&dbping_probe_callback, 0 );
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdadbping - database response time and availability PMDA
+
+=head1 DESCRIPTION
+
+Simple database response time measurement PMDA. dbprobe.pl(1)
+should be configured to use the type of DBI appropriate
+for your database, which includes: RDBMS flavour, user/password,
+delay between "ping" requests, and the SQL statement to use.
+B<pmdadbping> runs dbprobe.pl(1), and exports the performance
+measurements it makes available as PCP metrics.
+
+=head1 INSTALLATION
+
+Configure dbprobe.pl(1) - it uses a configuration file from
+(in this order):
+
+=over
+
+=item * /etc/pcpdbi.conf
+
+=item * $PCP_PMDAS_DIR/dbping/dbprobe.conf
+
+=back
+
+This file can contain overridden values (Perl code) for the settings
+listed at the start of dbprobe.pl, namely:
+
+=over
+
+=item * database name (see DBI(3) for details)
+
+=item * database user name
+
+=item * database pass word
+
+=item * SQL statement to measure (probe)
+
+=item * delay between probes
+
+=back
+
+Once this is setup, you can access the names and values for the
+dbping performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/dbping
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/dbping
+ # ./Remove
+
+B<pmdadbping> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/dbping/probes.stp
+
+probe configuration file for stap(1), run by B<pmdadbping>
+
+=item $PCP_PMDAS_DIR/dbping/Install
+
+installation script for the B<pmdadbping> agent
+
+=item $PCP_PMDAS_DIR/dbping/Remove
+
+undo installation script for the B<pmdadbping> agent
+
+=item $PCP_LOG_DIR/pmcd/dbping.log
+
+default log file for error messages from B<pmdadbping>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), dbprobe.pl(1) and DBI(3).
diff --git a/src/pmdas/dmcache/GNUmakefile b/src/pmdas/dmcache/GNUmakefile
new file mode 100644
index 0000000..243f08f
--- /dev/null
+++ b/src/pmdas/dmcache/GNUmakefile
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = dmcache
+PYSCRIPT = pmda$(IAM).python
+LSRCFILES = Install Remove $(PYSCRIPT)
+
+DOMAIN = DMCACHE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log
+
+default_pcp default: check_domain
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(PYSCRIPT) $(PMDADIR)/$(PYSCRIPT)
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PYTHONRULE)
diff --git a/src/pmdas/dmcache/Install b/src/pmdas/dmcache/Install
new file mode 100644
index 0000000..64ef3fa
--- /dev/null
+++ b/src/pmdas/dmcache/Install
@@ -0,0 +1,35 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Device Mapper Cache PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dmcache
+python_opt=true
+daemon_opt=false
+forced_restart=true
+
+which dmsetup >/dev/null 2>&1
+if [ $? -ne 0 ]
+then
+ echo "Device Mapper 'dmsetup' binary not found, cannot proceed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/dmcache/Remove b/src/pmdas/dmcache/Remove
new file mode 100644
index 0000000..6586d55
--- /dev/null
+++ b/src/pmdas/dmcache/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Device Mapper Cache PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dmcache
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/dmcache/pmdadmcache.python b/src/pmdas/dmcache/pmdadmcache.python
new file mode 100644
index 0000000..35db1a0
--- /dev/null
+++ b/src/pmdas/dmcache/pmdadmcache.python
@@ -0,0 +1,264 @@
+'''
+Performance Metrics Domain Agent exporting Device Mapper Cache metrics.
+'''
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaUnits
+from ctypes import POINTER, Structure, cast, c_ulonglong, c_uint
+from cpmapi import (PM_SEM_DISCRETE, PM_SEM_INSTANT, PM_SEM_COUNTER,
+ PM_ERR_INST, PM_ERR_PMID, PM_SPACE_BYTE, PM_SPACE_KBYTE,
+ PM_TYPE_U32, PM_TYPE_U64, PM_TYPE_STRING, PM_COUNT_ONE)
+from subprocess import PIPE, Popen
+from os import getenv
+
+IOMODES = { 'writeback': 1, 'writethrough': 2, 'passthrough': 3 }
+
+class DmCacheStats(Structure):
+ ''' Metric values associated with each dm-cache target '''
+ _fields_ = [("size", c_ulonglong),
+ ("metadata_block_size", c_uint),
+ ("metadata_used", c_ulonglong),
+ ("metadata_total", c_ulonglong),
+ ("cache_block_size", c_uint),
+ ("cache_used", c_ulonglong),
+ ("cache_total", c_ulonglong),
+ ("read_hits", c_uint),
+ ("read_misses", c_uint),
+ ("write_hits", c_uint),
+ ("write_misses", c_uint),
+ ("demotions", c_uint),
+ ("promotions", c_uint),
+ ("dirty", c_ulonglong),
+ ("iomode", c_uint)]
+
+ def __init__(self, text):
+ Structure.__init__(self)
+ self.parse(text)
+
+ def parse(self, line):
+ '''
+ Parse an individual line of dmcache statistics text. The format is:
+ <name>: <start> <end> <target> [Values...]
+ Values are as in Documentation/device-mapper/cache.txt Status section:
+ <metadata block size> <#used metadata blocks>/<#total metadata blocks>
+ <cache block size> <#used cache blocks>/<#total cache blocks>
+ <#read hits> <#read misses> <#write hits> <#write misses>
+ <#demotions> <#promotions> <#dirty> <#features> <features>*
+ '''
+ data = line.replace('/', ' ').split(' ')
+ if len(data) < 19:
+ raise ValueError(line)
+ mbsize = long(data[4]) * 512 # metadata block size (from sectors)
+ cbsize = long(data[7]) * 512 # cachedev block size (from sectors)
+
+ self.size = long(data[2]) - long(data[1])
+ self.metadata_block_size = mbsize
+ self.metadata_used = long(data[5]) * mbsize / 1024
+ self.metadata_total = long(data[6]) * mbsize / 1024
+ self.cache_block_size = cbsize
+ self.cache_used = long(data[8]) * cbsize / 1024
+ self.cache_total = long(data[9]) * cbsize / 1024
+ self.read_hits = long(data[10])
+ self.read_misses = long(data[11])
+ self.write_hits = long(data[12])
+ self.write_misses = long(data[13])
+ self.demotions = long(data[14])
+ self.promotions = long(data[15])
+ self.dirty = long(data[16]) * cbsize / 1024
+ self.iomode = IOMODES[data[18]]
+
+ def io_mode(self):
+ ''' Human-readable representation of numeric io_mode encoding '''
+ for mode in IOMODES:
+ if self.iomode == IOMODES[mode]:
+ return mode
+ return 'unknown'
+
+class DmCachePMDA(PMDA):
+ '''
+ Performance Metrics Domain Agent exporting dm-cache module metrics.
+ Install and make basic use of it, if you use dm-cache, as follows:
+ # $PCP_PMDAS_DIR/dmcache/Install
+ $ pminfo -fmdtT dmcache
+ '''
+
+ def __init__(self, name, domain):
+ '''
+ Setup metric table (see drivers/md/dm-cache-target.c::cache_status),
+ the cache devices instance domain, and our callback routines.
+ '''
+ PMDA.__init__(self, name, domain)
+
+ # dm-cache devices instance domain
+ self.caches = {}
+ self.dmstatus = getenv('DM_STATUS', 'dmsetup status --target=cache')
+
+ self.cache_indom = self.indom(0)
+ self.add_indom(pmdaIndom(self.cache_indom, self.caches))
+
+ self.set_fetch(self.dmcache_refresh)
+ self.set_fetch_callback(self.dmcache_fetch_callback)
+
+ self.add_metric('dmcache.size',
+ pmdaMetric(self.pmid(0, 0),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total size of each cache device (Kbytes)')
+
+ self.add_metric('dmcache.metadata.block_size',
+ pmdaMetric(self.pmid(0, 1),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_BYTE, 0, 0)),
+ 'Fixed block size for each metadata block in bytes')
+ self.add_metric('dmcache.metadata.used',
+ pmdaMetric(self.pmid(0, 2),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of metadata blocks used')
+ self.add_metric('dmcache.metadata.total',
+ pmdaMetric(self.pmid(0, 3),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total number of metadata blocks available')
+
+ self.add_metric('dmcache.cache.block_size',
+ pmdaMetric(self.pmid(0, 4),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_BYTE, 0, 0)),
+ 'Fixed block size for each metadata block in bytes')
+ self.add_metric('dmcache.cache.used',
+ pmdaMetric(self.pmid(0, 5),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of cache blocks used')
+ self.add_metric('dmcache.cache.total',
+ pmdaMetric(self.pmid(0, 6),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Total number of cache blocks available')
+
+ self.add_metric('dmcache.read_hits',
+ pmdaMetric(self.pmid(0, 7),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a READ bio has been mapped to the cache')
+ self.add_metric('dmcache.read_misses',
+ pmdaMetric(self.pmid(0, 8),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a READ bio has been mapped to the origin')
+ self.add_metric('dmcache.write_hits',
+ pmdaMetric(self.pmid(0, 9),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a WRITE bio has been mapped to the cache')
+ self.add_metric('dmcache.write_misses',
+ pmdaMetric(self.pmid(0, 10),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a WRITE bio has been mapped to the origin')
+
+ self.add_metric('dmcache.demotions',
+ pmdaMetric(self.pmid(0, 11),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a block has been removed from the cache')
+ self.add_metric('dmcache.promotions',
+ pmdaMetric(self.pmid(0, 12),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_COUNTER,
+ pmdaUnits(0, 0, 1, 0, 0, PM_COUNT_ONE)),
+ 'Number of times a block has been moved to the cache')
+
+ self.add_metric('dmcache.dirty',
+ pmdaMetric(self.pmid(0, 13),
+ PM_TYPE_U64, self.cache_indom, PM_SEM_INSTANT,
+ pmdaUnits(1, 0, 0, PM_SPACE_KBYTE, 0, 0)),
+ 'Number of blocks in the cache that differ from the origin')
+ self.add_metric('dmcache.io_mode_code',
+ pmdaMetric(self.pmid(0, 14),
+ PM_TYPE_U32, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(0, 0, 0, 0, 0, 0)),
+ 'Cache mode code - writeback, writethrough or passthrough')
+ self.add_metric('dmcache.io_mode',
+ pmdaMetric(self.pmid(1, 0),
+ PM_TYPE_STRING, self.cache_indom, PM_SEM_DISCRETE,
+ pmdaUnits(0, 0, 0, 0, 0, 0)),
+ 'Cache mode string - writeback, writethrough or passthrough')
+
+ def dmcache_refresh(self):
+ ''' Refresh the values and instances for all device mapper caches '''
+ pipe = Popen(self.dmstatus, shell = True, stdout = PIPE, stderr = PIPE)
+ output, errors = pipe.communicate()
+ if errors:
+ self.err("dmcache_refresh: %s error: %s" % (self.dmstatus, errors))
+
+ self.caches.clear()
+ for line in output.splitlines():
+ if 'No devices found' in line:
+ continue
+ name = line[:line.find(':')] # extract cache name
+ self.caches[name] = DmCacheStats(line) # extract stats values
+ self.replace_indom(self.cache_indom, self.caches)
+
+ def dmcache_fetch_callback(self, cluster, item, inst):
+ '''
+ Look up value associated with requested instance (inst) of a
+ given metric (cluster:item) PMID, returning a [value,status]
+ '''
+ value = self.inst_lookup(self.cache_indom, inst)
+ if (value == None):
+ return [PM_ERR_INST, 0]
+ value = cast(value, POINTER(DmCacheStats))
+ cache = value.contents
+
+ if cluster == 1 and item == 0:
+ return [cache.io_mode(), 1] # human-readable string encoding
+ elif cluster != 0:
+ return [PM_ERR_PMID, 0]
+
+ if item == 0:
+ return [cache.size, 1]
+ elif item == 1:
+ return [cache.metadata_block_size, 1]
+ elif item == 2:
+ return [cache.metadata_used, 1]
+ elif item == 3:
+ return [cache.metadata_total, 1]
+ elif item == 4:
+ return [cache.cache_block_size, 1]
+ elif item == 5:
+ return [cache.cache_used, 1]
+ elif item == 6:
+ return [cache.cache_total, 1]
+ elif item == 7:
+ return [cache.read_hits, 1]
+ elif item == 8:
+ return [cache.read_misses, 1]
+ elif item == 9:
+ return [cache.write_hits, 1]
+ elif item == 10:
+ return [cache.write_misses, 1]
+ elif item == 11:
+ return [cache.demotions, 1]
+ elif item == 12:
+ return [cache.promotions, 1]
+ elif item == 13:
+ return [cache.dirty, 1]
+ elif item == 14:
+ return [cache.iomode, 1]
+ return [PM_ERR_PMID, 0]
+
+if __name__ == '__main__':
+ DmCachePMDA('dmcache', 129).run()
diff --git a/src/pmdas/dtsrun/GNUmakefile b/src/pmdas/dtsrun/GNUmakefile
new file mode 100644
index 0000000..496567b
--- /dev/null
+++ b/src/pmdas/dtsrun/GNUmakefile
@@ -0,0 +1,52 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = dtsrun
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+else
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/dtsrun/Install b/src/pmdas/dtsrun/Install
new file mode 100755
index 0000000..c7cc772
--- /dev/null
+++ b/src/pmdas/dtsrun/Install
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the DTS (Data Transformation Services) dtsrun log file PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dtsrun
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+pmdaSetup
+
+if $do_pmda
+then
+ while true
+ do
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "DTS log filename? ""$PCP_ECHO_C"
+ read logfile
+ [ ! -z "$logfile" ] && break
+ echo
+ done
+ args="$args logfile"
+fi
+
+pmdaInstall
+exit 0
diff --git a/src/pmdas/dtsrun/Remove b/src/pmdas/dtsrun/Remove
new file mode 100755
index 0000000..aa878af
--- /dev/null
+++ b/src/pmdas/dtsrun/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the DTS (Data Transformation Services) dtsrun log PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=dtsrun
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/dtsrun/pmdadtsrun.pl b/src/pmdas/dtsrun/pmdadtsrun.pl
new file mode 100644
index 0000000..9d09d6e
--- /dev/null
+++ b/src/pmdas/dtsrun/pmdadtsrun.pl
@@ -0,0 +1,341 @@
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use Time::Local;
+
+my $pmda = PCP::PMDA->new('dtsrun', 102);
+
+my ( $package, $package_starttime, $instance_refresh );
+my $package_indom = 0; # instance domain for DTS package
+my @package_instances = ();
+my ( $step, $step_starttime );
+my $steps_indom = 1; # instance domain for DTS package steps
+my @steps_instances = ();
+
+use vars qw ( %lastpkg_times %lastpkg_stamp %allpkgs_times %allpkgs_count
+ %laststep_times %laststep_stamp %laststep_offset
+ %laststep_status %laststep_message %allsteps_times
+ %allsteps_count %allsteps_errors %allpkgs_delta
+ %packages %steps
+ );
+
+sub parsetime
+{
+ shift;
+ #$pmda->log("parsetime got dtsrun timestamp: $_");
+ m|(\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+) (\w+)|;
+ my ($mm, $dd, $year, $hr, $min, $sec, $ampm) = ($1,$2,$3,$4,$5,$6,$7);
+ $hr += 12 unless ($ampm eq 'AM');
+ $hr -= 1; # range 0..23 (hours)
+ $mm -= 1; # range 0..11 (months)
+ my $tm = timelocal($sec,$min,$hr,$dd,$mm,$year);
+ return $tm;
+}
+
+sub dtsrun_parser
+{
+ ( undef, $_ ) = @_;
+
+ s/\r//g; # cull Windows line endings
+ #$pmda->log("dtsrun_parser got line: $_");
+
+ if (/^Step '(\S+)' (.*)/) {
+ $step = "$package/$1";
+ if (defined($steps{$step})) {
+ $allsteps_count{$step}++;
+ } else { # new step instance
+ $steps{$step} = 1;
+ $allsteps_times{$step} = 0;
+ $allsteps_count{$step} = 1;
+ $allsteps_errors{$step} = 0;
+ $laststep_times{$step} = 0;
+ $laststep_stamp{$step} = '';
+ $laststep_offset{$step} = 0;
+ $laststep_status{$step} = -1;
+ $laststep_message{$step} = '';
+ $instance_refresh = 1;
+ }
+ if ($2 eq 'succeeded') {
+ $laststep_status{$step} = 0;
+ } else {
+ $laststep_status{$step} = -1;
+ $allsteps_errors{$step}++;
+ }
+ $laststep_message{$step} = $2;
+ }
+ elsif (/^Step Execution Started: (.*)/) {
+ $step_starttime = parsetime($1);
+ $laststep_stamp{$step} = $1;
+ $laststep_offset{$step} = $step_starttime - $package_starttime;
+ }
+ elsif (/^Total Step Execution Time: (\S+) seconds/) {
+ $laststep_times{$step} = $1;
+ $allsteps_times{$step} += $1;
+ }
+ elsif (/^Package Name: (\w+)/) {
+ $package = $1;
+ if (defined($packages{$package})) {
+ $allpkgs_count{$package}++;
+ } else { # new package instance
+ $packages{$package} = 1;
+ $allpkgs_delta{$package} = 0;
+ $allpkgs_times{$package} = 0;
+ $allpkgs_count{$package} = 1;
+ $lastpkg_times{$package} = 0;
+ $lastpkg_stamp{$package} = '';
+ $instance_refresh = 1;
+ }
+ }
+ elsif (/^Execution Started: (.*)/) {
+ my $latest = parsetime($1);
+ if (defined($package_starttime) && $package_starttime >= 0) {
+ $allpkgs_delta{$package} = $latest - $package_starttime;
+ } else {
+ $allpkgs_delta{$package} = -1;
+ }
+ $package_starttime = $latest;
+ $lastpkg_stamp{$package} = $1;
+ }
+ elsif (/^Total Execution Time: (\S+) seconds/) {
+ $lastpkg_times{$package} = $1;
+ $allpkgs_times{$package} += $1;
+ }
+ elsif (/^\*\*\*/) {
+ # end section, cleanup global state
+ undef $step;
+ undef $package;
+ }
+}
+
+sub dtsrun_instance
+{
+ my ( $pkgcount, $stepcount );
+
+ #$pmda->log("dtsrun_instance");
+ return unless (defined($instance_refresh) && $instance_refresh);
+
+ @package_instances = ();
+ @steps_instances = ();
+
+ $pkgcount = $stepcount = 0;
+ foreach my $pkgname (keys(%packages)) {
+ push @package_instances, $pkgcount++, $pkgname;
+ foreach my $stepname (sort keys(%steps)) {
+ push @steps_instances, $stepcount++, $stepname;
+ }
+ }
+
+ #$pmda->log("dtsrun_instance: $pkgcount packages, $stepcount steps");
+ $pmda->replace_indom($package_indom, \@package_instances);
+ $pmda->replace_indom($steps_indom, \@steps_instances);
+ $instance_refresh = 0;
+}
+
+sub dtsrun_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $instance;
+
+ #$pmda->log("dtsrun_fetch_callback for PMID: $cluster.$item ($inst)");
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+
+ if ($cluster == 0 || $cluster == 1 || $cluster == 4) {
+ $instance = pmda_inst_name($package_indom, $inst);
+ } else {
+ $instance = pmda_inst_name($steps_indom, $inst);
+ }
+ if (!defined($instance)) { return (PM_ERR_INST, 0); }
+ #$pmda->log("dtsrun_fetch_callback using instance $instance");
+
+ if ($cluster == 0) {
+ # dtsrun.package.last.time
+ if ($item == 0) { return ($lastpkg_times{$instance}, 1); }
+ # dtsrun.package.last.timestamp
+ elsif ($item == 1) { return ($lastpkg_stamp{$instance}, 1); }
+ }
+ elsif ($cluster == 1) {
+ # dtsrun.package.total.time
+ if ($item == 0) { return ($allpkgs_times{$instance}, 1); }
+ # dtsrun.package.total.count
+ elsif ($item == 1) { return ($allpkgs_count{$instance}, 1); }
+ }
+ elsif ($cluster == 2) {
+ # dtsrun.package.steps.last.time
+ if ($item == 0) { return ($laststep_times{$instance}, 1); }
+ # dtsrun.package.steps.last.timestamp
+ if ($item == 1) { return ($laststep_stamp{$instance}, 1); }
+ # dtsrun.package.steps.last.offset
+ if ($item == 2) { return ($laststep_offset{$instance}, 1); }
+ # dtsrun.package.steps.last.status
+ if ($item == 3) { return ($laststep_status{$instance}, 1); }
+ # dtsrun.package.steps.last.message
+ if ($item == 4) { return ($laststep_message{$instance}, 1); }
+ }
+ elsif ($cluster == 3) {
+ # dtsrun.package.steps.total.time
+ if ($item == 0) { return ($allsteps_times{$instance}, 1); }
+ # dtsrun.package.steps.total.count
+ if ($item == 1) { return ($allsteps_count{$instance}, 1); }
+ # dtsrun.package.steps.total.errors
+ if ($item == 2) { return ($allsteps_errors{$instance}, 1); }
+ }
+ elsif ($cluster == 4) {
+ # dtsrun.package.interval
+ if ($item == 0) { return ($allpkgs_delta{$instance}, 1); }
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+sub dtsrun_setup_metrics
+{
+ $pmda->add_metric(pmda_pmid(0,0), PM_TYPE_FLOAT, $package_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.lastrun.time', '',
+ 'Time taken for DTS package to complete the last time it was run');
+ $pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, $package_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dtsrun.package.lastrun.timestamp', '',
+ 'Time stamp (string) of the last package run for each DTS package');
+
+ $pmda->add_metric(pmda_pmid(1,0), PM_TYPE_FLOAT, $package_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.total.time', '',
+ 'Cumulative time taken to date for all DTS package runs.');
+ $pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, $package_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'dtsrun.package.total.count', '',
+ 'Cumulative count of all DTS package runs to date.');
+
+ $pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $steps_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.steps.last.time', '',
+ 'Time taken to complete the last iteration of each step.');
+ $pmda->add_metric(pmda_pmid(2,1), PM_TYPE_STRING, $steps_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dtsrun.package.steps.last.timestamp', '',
+ 'Time of completion of the last iteration of each step.');
+ $pmda->add_metric(pmda_pmid(2,2), PM_TYPE_U32, $steps_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.steps.last.offset', '',
+ 'Offset from package start time for this step, in seconds.');
+ $pmda->add_metric(pmda_pmid(2,3), PM_TYPE_32, $steps_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dtsrun.package.steps.last.status', '',
+ 'Indicator of success (0) or failure of last run for each step.');
+ $pmda->add_metric(pmda_pmid(2,4), PM_TYPE_STRING, $steps_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'dtsrun.package.steps.last.message', '',
+ 'Message string used to determine status of last run for each step.');
+
+ $pmda->add_metric(pmda_pmid(3,0), PM_TYPE_FLOAT, $steps_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.steps.total.time', '',
+ 'Cumulative count of time taken for runs of each DTS package step.');
+ $pmda->add_metric(pmda_pmid(3,1), PM_TYPE_U32, $steps_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'dtsrun.package.steps.total.count', '',
+ 'Cumulative count of runs of each DTS package step.');
+ $pmda->add_metric(pmda_pmid(3,2), PM_TYPE_U32, $steps_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'dtsrun.package.steps.total.errors', '',
+ 'Cumulative count of errors observed for each DTS package step.');
+
+ $pmda->add_metric(pmda_pmid(4,0), PM_TYPE_32, $package_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'dtsrun.package.interval', '',
+ 'Time between consecutive observations of each DTS package run. Unknown is -1.');
+}
+
+sub dtsrun_setup_instances
+{
+ $package_indom = $pmda->add_indom($package_indom, [], '', '');
+ $steps_indom = $pmda->add_indom($steps_indom, [], '', '');
+}
+
+my $logfile = '';
+if (!defined($ENV{PCP_PERL_PMNS} && !defined($ENV{PCP_PERL_DOMAIN}))) {
+ die "No dtsrun statistics file specified\n" unless (defined($ARGV[0]));
+ $logfile = $ARGV[0];
+ die "Cannot find a valid dtsrun statistics file\n" unless -f $logfile;
+}
+$pmda->log("Using logfile: $logfile");
+
+dtsrun_setup_metrics;
+dtsrun_setup_instances;
+
+$pmda->add_tail($logfile, \&dtsrun_parser, 0);
+$pmda->set_fetch(\&dtsrun_instance);
+$pmda->set_instance(\&dtsrun_instance);
+$pmda->set_fetch_callback(\&dtsrun_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdadtsrun - Data Transformation Services process (dtsrun) log file PMDA
+
+=head1 DESCRIPTION
+
+B<pmdadtsrun> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from the DTS run executable, which is a data
+transformation server, used with SQL Server.
+Further details on DTS can be found at http://www.dtssql.com/.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the dtsrun performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/dtsrun
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/dtsrun
+ # ./Remove
+
+B<pmdadtsrun> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /var/log/dtsrun.log
+
+log file showing status and elapsed times exported from dtsrun
+
+=item $PCP_PMDAS_DIR/dtsrun/Install
+
+installation script for the B<pmdadtsrun> agent
+
+=item $PCP_PMDAS_DIR/dtsrun/Remove
+
+undo installation script for the B<pmdadtsrun> agent
+
+=item $PCP_LOG_DIR/pmcd/dtsrun.log
+
+default log file for error messages from B<pmdadtsrun>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/elasticsearch/GNUmakefile b/src/pmdas/elasticsearch/GNUmakefile
new file mode 100644
index 0000000..0a6ae50
--- /dev/null
+++ b/src/pmdas/elasticsearch/GNUmakefile
@@ -0,0 +1,54 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = elasticsearch
+DOMAIN = ELASTICSEARCH
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/elasticsearch/Install b/src/pmdas/elasticsearch/Install
new file mode 100755
index 0000000..ece2b20
--- /dev/null
+++ b/src/pmdas/elasticsearch/Install
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 2011-2012 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the ElasticSearch PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=elasticsearch
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+for module in JSON LWP::UserAgent
+do
+ perl -e "use $module" 2>/dev/null
+ if test $? -ne 0
+ then
+ echo "$module perl module is not installed"
+ exit 1
+ fi
+done
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/elasticsearch/Remove b/src/pmdas/elasticsearch/Remove
new file mode 100755
index 0000000..ee6fc31
--- /dev/null
+++ b/src/pmdas/elasticsearch/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2011-2012 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the ElasticSearch PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=elasticsearch
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/elasticsearch/pmdaelasticsearch.pl b/src/pmdas/elasticsearch/pmdaelasticsearch.pl
new file mode 100755
index 0000000..21f2eda
--- /dev/null
+++ b/src/pmdas/elasticsearch/pmdaelasticsearch.pl
@@ -0,0 +1,882 @@
+#
+# Copyright (c) 2011-2013 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use JSON;
+use PCP::PMDA;
+use LWP::UserAgent;
+
+my $es_port = 9200;
+my $es_instance = 'localhost';
+use vars qw($pmda $http $es_cluster $es_nodes $es_nodestats $es_root $es_searchstats $es_cluster_state);
+
+my $nodes_indom = 0;
+my @nodes_instances;
+my @nodes_instance_ids;
+my $search_indom = 1;
+my @search_instances;
+my @search_instance_ids;
+my $index_indom = 2;
+my @index_instances;
+my @index_instance_ids;
+
+my @cluster_cache; # time of last refresh for each cluster
+my $cache_interval = 2; # min secs between refreshes for clusters
+my $http_timeout = 1; # max secs for a request (*must* be small).
+
+# Configuration files for overriding the above settings
+for my $file (pmda_config('PCP_PMDAS_DIR') . '/elasticsearch/es.conf', 'es.conf') {
+ eval `cat $file` unless ! -f $file;
+}
+my $baseurl = "http://$es_instance:$es_port/";
+
+my $http = LWP::UserAgent->new;
+$http->agent('pmdaelasticsearch');
+$http->timeout($http_timeout); # if elasticsearch not timely, no soup for you
+
+# http GET of elasticsearch json from a given url
+sub es_agent_get
+{
+ my $response = $http->get(shift);
+ return undef unless $response->is_success;
+ return $response->decoded_content;
+}
+
+# crack json data structure, extract only data-node names
+sub es_data_node_instances
+{
+ my $nodeIDs = shift;
+ my $i = 0;
+
+ @nodes_instances = ();
+ @nodes_instance_ids = ();
+ foreach my $node (keys %$nodeIDs) {
+ my $attributes = $nodeIDs->{$node}->{'attributes'};
+ unless (defined($attributes) && $attributes->{'data'} == 'false') {
+ my $name = $nodeIDs->{$node}->{'name'};
+ $nodes_instances[$i*2] = $i;
+ $nodes_instances[($i*2)+1] = $name;
+ $nodes_instance_ids[$i*2] = $i;
+ $nodes_instance_ids[($i*2)+1] = $node;
+ $i++;
+ # $pmda->log("es_instances added node: $name ($node)");
+ }
+ }
+ $pmda->replace_indom($nodes_indom, \@nodes_instances);
+}
+
+sub es_data_index_instances
+{
+ my $indexIDs = shift;
+ my $i = 0;
+
+ @index_instances = ();
+ @index_instance_ids = ();
+ foreach my $index (keys %$indexIDs){
+ $index_instances[$i*2] = $i;
+ $index_instances[($i*2)+1] = $index;
+ $index_instance_ids[$i*2] = $i;
+ $index_instance_ids[($i*2)+1] = $index;
+ $i++;
+ }
+ $pmda->replace_indom($index_indom, \@index_instances);
+
+}
+
+# crack json data structure, extract index names
+sub es_search_instances
+{
+ my $searchIDs = shift;
+ my $i = 0;
+
+ @search_instances = ();
+ @search_instance_ids = ();
+ foreach my $search (keys %$searchIDs) {
+ $search_instances[$i*2] = $i;
+ $search_instances[($i*2)+1] = $search;
+ $search_instance_ids[$i*2] = $i;
+ $search_instance_ids[($i*2)+1] = $search;
+ $i++;
+ # $pmda->log("es_search_instances added index: $search");
+ }
+ $pmda->replace_indom($search_indom, \@search_instances);
+}
+
+sub es_refresh_cluster_health
+{
+ my $content = es_agent_get($baseurl . "_cluster/health");
+ $es_cluster = defined($content) ? decode_json($content) : undef;
+}
+
+# Update the JSON hash of ES indices so we can later map the metric names
+# much more easily back to the PMID (during the fetch callback routine).
+#
+sub es_rewrite_cluster_state
+{
+ my $indices = $es_cluster_state->{'metadata'}->{'indices'};
+ foreach my $index_key (keys %$indices) {
+ # Go over each setting key and transpose what the key name is called
+ my $settings = $indices->{$index_key}->{'settings'};
+ foreach my $settings_key (keys %$settings) {
+ # Convert keys like "index.version.created" to "version_created"
+ my $transformed_key = $settings_key;
+ $transformed_key =~ s/index\.//;
+ $transformed_key =~ s/\./_/g;
+ $settings->{$transformed_key} = $settings->{$settings_key};
+ }
+ }
+}
+
+sub es_refresh_cluster_state
+{
+ my $content = es_agent_get($baseurl . "_cluster/state");
+ if (defined($content)) {
+ $es_cluster_state = decode_json($content);
+ es_rewrite_cluster_state();
+ es_data_index_instances($es_cluster_state->{'metadata'}->{'indices'});
+ } else {
+ $es_cluster_state = undef;
+ }
+}
+
+sub es_refresh_cluster_nodes_stats_all
+{
+ my $content = es_agent_get($baseurl . "_cluster/nodes/stats?all");
+ if (defined($content)) {
+ $es_nodestats = decode_json($content);
+ es_data_node_instances($es_nodestats->{'nodes'});
+ } else {
+ $es_nodestats = undef;
+ }
+}
+
+sub es_refresh_cluster_nodes_all
+{
+ my $content = es_agent_get($baseurl . "_cluster/nodes?all");
+ if (defined($content)) {
+ $es_nodes = decode_json($content);
+ es_data_node_instances($es_nodes->{'nodes'});
+ } else {
+ $es_nodes = undef;
+ }
+}
+
+sub es_refresh_root
+{
+ my $content = es_agent_get($baseurl);
+ $es_root = defined($content) ? decode_json($content) : undef;
+}
+
+sub es_refresh_stats_search
+{
+ my $content = es_agent_get($baseurl . "_stats/search");
+ if (defined($content)) {
+ $es_searchstats = decode_json($content);
+ es_search_instances($es_searchstats->{'_all'}->{'indices'});
+ } else {
+ $es_searchstats = undef;
+ }
+}
+
+sub es_refresh
+{
+ my ($cluster) = @_;
+ my $now = time;
+
+ if (defined($cluster_cache[$cluster]) &&
+ $now - $cluster_cache[$cluster] <= $cache_interval) {
+ return;
+ }
+
+ if ($cluster == 0) { # Update the cluster metrics
+ es_refresh_cluster_health();
+ } elsif ($cluster == 1) { # Update the node metrics
+ es_refresh_cluster_nodes_stats_all();
+ } elsif ($cluster == 2) { # Update the other node metrics
+ es_refresh_cluster_nodes_all();
+ } elsif ($cluster == 3) { # Update the root metrics
+ es_refresh_root();
+ } elsif ($cluster == 4 || # Update the search metrics
+ $cluster == 5) {
+ es_refresh_stats_search();
+ # avoid 2nd refresh call on metrics in other cluster
+ $cluster_cache[4] = $cluster_cache[5] = $now;
+ } elsif ($cluster == 6 || $cluster == 7) { # Update the cluster state
+ es_refresh_cluster_state();
+ }
+ $cluster_cache[$cluster] = $now;
+}
+
+sub es_lookup_node
+{
+ my ($json, $inst) = @_;
+ my $nodeID = $nodes_instance_ids[($inst*2)+1];
+ return $json->{'nodes'}->{$nodeID};
+}
+
+sub es_lookup_search
+{
+ my ($json, $inst) = @_;
+ my $searchID = $search_instance_ids[($inst*2)+1];
+ return $json->{'_all'}->{'indices'}->{$searchID};
+}
+
+sub es_lookup_index
+{
+ my ($json, $inst) = @_;
+ my $indexID = $index_instance_ids[($inst*2)+1];
+ return $json->{'metadata'}->{'indices'}->{$indexID};
+}
+
+# iterate over metric-name components, performing hash lookups as we go.
+sub es_value
+{
+ my ( $values, $names ) = @_;
+ my ( $value, $name );
+
+ foreach $name (@$names) {
+ $value = $values->{$name};
+ return (PM_ERR_APPVERSION, 0) unless (defined($value));
+ $values = $value;
+ }
+ return (PM_ERR_APPVERSION, 0) unless (defined($value));
+ return ($value, 1);
+}
+
+# translate status string into a numeric code for pmie and friends
+sub es_status
+{
+ my ($value) = @_;
+
+ if (!defined($value)) {
+ return (PM_ERR_AGAIN, 0);
+ } elsif ($value eq "green" || $value eq "false") {
+ return (0, 1);
+ } elsif ($value eq "yellow" || $value eq "true") {
+ return (1, 1);
+ } elsif ($value eq "red") {
+ return (2, 1);
+ }
+ return (-1, 1); # unknown
+}
+
+sub es_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my @metric_subnames = split(/\./, $metric_name);
+ my ($node, $json, $search);
+
+ # $pmda->log("es_fetch_callback: $metric_name $cluster.$item ($inst)");
+
+ if ($cluster == 0) {
+ if (!defined($es_cluster)) { return (PM_ERR_AGAIN, 0); }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+
+ # remove first couple (i.e. elasticsearch.cluster.)
+ splice(@metric_subnames, 0, 2);
+
+ # cluster.timed_out and cluster.status (numeric codes)
+ if ($item == 1) {
+ my $value = $es_cluster->{'status'};
+ return (PM_ERR_APPVERSION, 0) unless (defined($value));
+ return ($value, 1);
+ }
+ elsif ($item == 2 || $item == 10) {
+ return es_status($es_cluster->{$metric_subnames[0]});
+ }
+ return es_value($es_cluster, \@metric_subnames);
+ }
+ elsif ($cluster == 1) {
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($inst > @nodes_instances) { return (PM_ERR_INST, 0); }
+
+ $node = es_lookup_node($es_nodestats, $inst);
+ if (!defined($node)) { return (PM_ERR_AGAIN, 0); }
+
+ # remove first couple (i.e. elasticsearch.node.)
+ splice(@metric_subnames, 0, 2);
+ return es_value($node, \@metric_subnames);
+ }
+ elsif ($cluster == 2) {
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($inst > @nodes_instances) { return (PM_ERR_INST, 0); }
+
+ $node = es_lookup_node($es_nodes, $inst);
+ if (!defined($node)) { return (PM_ERR_AGAIN, 0); }
+ # remove first couple (i.e. elasticsearch.node.)
+ splice(@metric_subnames, 0, 2);
+ return es_value($node, \@metric_subnames);
+ }
+ elsif ($cluster == 3) {
+ if (!defined($es_root)) { return (PM_ERR_AGAIN, 0); }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+
+ # remove first one (i.e. elasticsearch.)
+ splice(@metric_subnames, 0, 1);
+ return es_value($es_root, \@metric_subnames);
+ }
+ elsif ($cluster == 4) {
+ if (!defined($es_searchstats)) { return (PM_ERR_AGAIN, 0); }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+
+ # remove first couple (i.e. elasticsearch.search.)
+ splice(@metric_subnames, 0, 2);
+ # regex fixes up _all and _shard for us (invalid names)
+ $metric_subnames[0] =~ s/^(all|shards)$/_$1/;
+ return es_value($es_searchstats, \@metric_subnames);
+ }
+ elsif ($cluster == 5) {
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($inst > @search_instances) { return (PM_ERR_INST, 0); }
+
+ # remove first three (i.e. elasticsearch.search.perindex.)
+ splice(@metric_subnames, 0, 3);
+ $search = es_lookup_search($es_searchstats, $inst);
+ if (!defined($search)) { return (PM_ERR_AGAIN, 0); }
+ return es_value($search, \@metric_subnames);
+ }
+ elsif ($cluster == 6) {
+ if (!defined($es_cluster_state)){ return (PM_ERR_AGAIN, 0); }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ # remove first couple (i.e. elasticsearch.cluster.)
+ splice(@metric_subnames, 0, 2);
+ return es_value($es_cluster_state, \@metric_subnames);
+ }
+ elsif ($cluster == 7) {
+ # Remove elasticsearch.index
+ splice(@metric_subnames, 0, 2);
+ if (!defined($es_cluster_state)){ return (PM_ERR_AGAIN, 0); }
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ $search = es_lookup_index($es_cluster_state, $inst);
+ if (!defined($search)) { return (PM_ERR_AGAIN, 0); }
+ return es_value($search, \@metric_subnames);
+
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('elasticsearch', 108);
+
+# cluster stats
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.cluster_name',
+ 'Name of the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.status.colour',
+ 'Status (green,yellow,red) of the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.timed_out',
+ 'Timed out status (0:false,1:true) of the elasticsearch cluster',
+ 'Maps the cluster timed-out status to a numeric value for alarming');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.number_of_nodes',
+ 'Number of nodes in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.number_of_data_nodes',
+ 'Number of data nodes in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.active_primary_shards',
+ 'Number of active primary shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.active_shards',
+ 'Number of primary shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.relocating_shards',
+ 'Number of relocating shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.initializing_shards',
+ 'Number of initializing shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.unassigned_shards',
+ 'Number of unassigned shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(0,10), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.status.code',
+ 'Status code (0:green,1:yellow,2:red) of the elasticsearch cluster',
+ 'Maps the cluster status colour to a numeric value for alarming');
+
+# node stats
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U64, $nodes_indom, # deprecated
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.size_in_bytes', '', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U64, $nodes_indom, # deprecated
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.docs.count', '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.docs.num_docs',
+ 'Raw number of documents indexed by elasticsearch', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.cache.field_evictions', '', '');
+$pmda->add_metric(pmda_pmid(1,4), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.cache.field_size_in_bytes', '', '');
+$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.cache.filter_count', '', '');
+$pmda->add_metric(pmda_pmid(1,6), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.cache.filter_evictions', '', '');
+$pmda->add_metric(pmda_pmid(1,7), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.cache.filter_size_in_bytes', '', '');
+$pmda->add_metric(pmda_pmid(1,8), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.merges.current', '', '');
+$pmda->add_metric(pmda_pmid(1,9), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.merges.total', '', '');
+$pmda->add_metric(pmda_pmid(1,10), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.merges.total_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,11), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.jvm.uptime_in_millis',
+ 'Number of milliseconds each elasticsearch node has been running', '');
+$pmda->add_metric(pmda_pmid(1,12), PM_TYPE_STRING, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.uptime',
+ 'Time (as a string) that each elasticsearch node has been up', '');
+$pmda->add_metric(pmda_pmid(1,13), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.heap_used_in_bytes',
+ 'Actual amount of memory in use for the Java heap', '');
+$pmda->add_metric(pmda_pmid(1,14), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.heap_committed_in_bytes',
+ 'Virtual memory size', '');
+$pmda->add_metric(pmda_pmid(1,15), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.non_heap_used_in_bytes',
+ 'Actual memory in use by Java excluding heap space', '');
+$pmda->add_metric(pmda_pmid(1,16), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.non_heap_committed_in_bytes',
+ 'Virtual memory size excluding heap', '');
+$pmda->add_metric(pmda_pmid(1,17), PM_TYPE_U32, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.threads.count',
+ 'Number of Java threads currently in use on each node', '');
+$pmda->add_metric(pmda_pmid(1,18), PM_TYPE_U32, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.threads.peak_count',
+ 'Maximum observed Java threads in use on each node', '');
+$pmda->add_metric(pmda_pmid(1,19), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.jvm.gc.collection_count',
+ 'Count of Java garbage collections', '');
+$pmda->add_metric(pmda_pmid(1,20), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.jvm.gc.collection_time_in_millis',
+ 'Time spent performing garbage collections in Java', '');
+$pmda->add_metric(pmda_pmid(1,21), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.jvm.gc.collectors.Copy.collection_count',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,22), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.jvm.gc.collectors.Copy.collection_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,23), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.jvm.gc.collectors.ParNew.collection_count',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,24), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.jvm.gc.collectors.ParNew.collection_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,25), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.jvm.gc.collectors.ConcurrentMarkSweep.collection_count',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,26), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.jvm.gc.collectors.ConcurrentMarkSweep.collection_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,27), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.docs.deleted',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,28), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.indexing.index_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,29), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.indexing.index_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,30), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.indexing.delete_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,31), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.indexing.delete_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,32), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.merges.current_docs',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,33), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.merges.current_size_in_bytes',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,34), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.merges.total_docs',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.merges.total_size_in_bytes',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,36), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.refresh.total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,37), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.refresh.total_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,38), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.flush.total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,39), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.flush.total_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,40), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.process.timestamp', '', '');
+$pmda->add_metric(pmda_pmid(1,41), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.process.open_file_descriptors', '', '');
+$pmda->add_metric(pmda_pmid(1,42), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.process.cpu.percent', '', '');
+$pmda->add_metric(pmda_pmid(1,43), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.process.cpu.sys_in_millis', '', '');
+$pmda->add_metric(pmda_pmid(1,44), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.process.cpu.user_in_millis', '', '');
+$pmda->add_metric(pmda_pmid(1,45), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.process.mem.resident_in_bytes', '', '');
+$pmda->add_metric(pmda_pmid(1,46), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.process.mem.total_virtual_in_bytes',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,47), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.indices.store.size_in_bytes',
+ 'Size of indices store on each elasticsearch node', '');
+$pmda->add_metric(pmda_pmid(1,48), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.get.total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,49), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.get.time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,50), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.get.exists_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,51), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.get.exists_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,52), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.get.missing_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,53), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.get.missing_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,54), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.search.query_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,55), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.search.query_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,56), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.indices.search.fetch_total',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,57), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.nodes.indices.search.fetch_time_in_millis',
+ '', '');
+$pmda->add_metric(pmda_pmid(1,58), PM_TYPE_U32, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.transport.server_open',
+ 'Count of open server connections', '');
+$pmda->add_metric(pmda_pmid(1,59), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.transport.rx_count',
+ 'Receive transaction count', '');
+$pmda->add_metric(pmda_pmid(1,60), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.transport.rx_size_in_bytes',
+ 'Receive transaction size', '');
+$pmda->add_metric(pmda_pmid(1,61), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.transport.tx_count',
+ 'Transmit transaction count', '');
+$pmda->add_metric(pmda_pmid(1,62), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.transport.tx_size_in_bytes',
+ 'Transmit transaction size', '');
+$pmda->add_metric(pmda_pmid(1,63), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.http.current_open',
+ 'Number of currently open http connections', '');
+$pmda->add_metric(pmda_pmid(1,64), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.nodes.http.total_opened',
+ 'Count of http connections opened since starting', '');
+
+# node info stats
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $nodes_indom,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.pid',
+ 'Process identifier for elasticsearch on each node', '');
+$pmda->add_metric(pmda_pmid(2,1), PM_TYPE_STRING, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.version',
+ 'Java Runtime environment version', '');
+$pmda->add_metric(pmda_pmid(2,2), PM_TYPE_STRING, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.vm_name',
+ 'Name of the Java Virtual Machine running on each node', '');
+$pmda->add_metric(pmda_pmid(2,3), PM_TYPE_STRING, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.jvm.vm_version',
+ 'Java Virtual Machine version on each node', '');
+$pmda->add_metric(pmda_pmid(2,4), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.heap_init_in_bytes',
+ 'Initial Java heap memory configuration size', '');
+$pmda->add_metric(pmda_pmid(2,5), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.heap_max_in_bytes',
+ 'Maximum Java memory size', '');
+$pmda->add_metric(pmda_pmid(2,6), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.non_heap_init_in_bytes',
+ 'Initial Java memory configuration size excluding heap space', '');
+$pmda->add_metric(pmda_pmid(2,7), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'elasticsearch.nodes.jvm.mem.non_heap_max_in_bytes',
+ 'Maximum Java memory size excluding heap space', '');
+$pmda->add_metric(pmda_pmid(2,8), PM_TYPE_U64, $nodes_indom,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.nodes.process.max_file_descriptors', '', '');
+
+$pmda->add_metric(pmda_pmid(3,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.version.number',
+ 'Version number of elasticsearch', '');
+
+$pmda->add_metric(pmda_pmid(4,0), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.search.shards.total',
+ 'Number of shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(4,1), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.search.shards.successful',
+ 'Number of successful shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(4,2), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.search.shards.failed',
+ 'Number of failed shards in the elasticsearch cluster', '');
+$pmda->add_metric(pmda_pmid(4,3), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.all.primaries.search.query_total',
+ 'Number of search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,4), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.all.primaries.search.query_time_in_millis',
+ 'Time spent in search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,5), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.all.primaries.search.fetch_total',
+ 'Number of search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,6), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.all.primaries.search.fetch_time_in_millis',
+ 'Time spent in search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,7), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.all.total.search.query_total',
+ 'Number of search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,8), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.all.total.search.query_time_in_millis',
+ 'Time spent in search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,9), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.all.total.search.fetch_total',
+ 'Number of search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(4,10), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.all.total.search.fetch_time_in_millis',
+ 'Time spent in search fetches to all elasticsearch primaries', '');
+
+$pmda->add_metric(pmda_pmid(5,0), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.perindex.primaries.search.query_total',
+ 'Number of search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,1), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.perindex.primaries.search.query_time_in_millis',
+ 'Time spent in search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,2), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.perindex.primaries.search.fetch_total',
+ 'Number of search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,3), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.perindex.primaries.search.fetch_time_in_millis',
+ 'Time spent in search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,4), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.perindex.total.search.query_total',
+ 'Number of search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,5), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.perindex.total.search.query_time_in_millis',
+ 'Time spent in search queries to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,6), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'elasticsearch.search.perindex.total.search.fetch_total',
+ 'Number of search fetches to all elasticsearch primaries', '');
+$pmda->add_metric(pmda_pmid(5,7), PM_TYPE_U64, $search_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.search.perindex.total.search.fetch_time_in_millis',
+ 'Time spent in search fetches to all elasticsearch primaries', '');
+
+# cluster state
+$pmda->add_metric(pmda_pmid(6,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.cluster.master_node',
+ 'Internal identifier of the master node of the cluster', '');
+
+# index state
+$pmda->add_metric(pmda_pmid(7,0), PM_TYPE_64, $index_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'elasticsearch.index.settings.gateway_snapshot_interval',
+ 'Interval between gateway snapshots', '');
+$pmda->add_metric(pmda_pmid(7,1), PM_TYPE_U64, $index_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.index.settings.number_of_replicas',
+ 'Number of replicas of shards index setting', '');
+$pmda->add_metric(pmda_pmid(7,2), PM_TYPE_U64, $index_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.index.settings.number_of_shards',
+ 'Number of shards index setting', '');
+$pmda->add_metric(pmda_pmid(7,3), PM_TYPE_U64, $index_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'elasticsearch.index.settings.version_created',
+ 'The version of elasticsearch the index was created with', '');
+
+$pmda->add_indom($nodes_indom, \@nodes_instances,
+ 'Instance domain exporting each elasticsearch node', '');
+$pmda->add_indom($search_indom, \@search_instances,
+ 'Instance domain exporting each elasticsearch index', '');
+$pmda->add_indom($index_indom, \@index_instances,
+ 'Instance domain exporting each elasticsearch index metadata', '');
+
+$pmda->set_fetch_callback(\&es_fetch_callback);
+$pmda->set_refresh(\&es_refresh);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdaelasticsearch - elasticsearch performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdaelasticsearch> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from elasticsearch.
+
+
+=head1 INSTALLATION
+
+This PMDA requires that elasticsearch is running on the local host and
+is accepting queries on TCP port 9200
+
+
+Install the elasticsearch PMDA by using the B<Install> script as root:
+
+ # cd $PCP_PMDAS_DIR/elasticsearch
+ # ./Install
+
+To uninstall, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/elasticsearch
+ # ./Remove
+
+B<pmdaelasticsearch> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/elasticsearch/es.conf
+
+optional configuration file for B<pmdaelasticsearch>
+
+elasticsearch PMDA for PCP
+
+=item $PCP_PMDAS_DIR/elasticsearch/Install
+
+installation script for the B<pmdaelasticsearch> agent
+
+=item $PCP_PMDAS_DIR/elasticsearch/Remove
+
+undo installation script for the B<pmdaelasticsearch> agent
+
+=item $PCP_LOG_DIR/pmcd/elasticsearch.log
+
+default log file for error messages from B<pmdaelasticsearch>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/etw/GNUmakefile b/src/pmdas/etw/GNUmakefile
new file mode 100644
index 0000000..2951076
--- /dev/null
+++ b/src/pmdas/etw/GNUmakefile
@@ -0,0 +1,78 @@
+#
+# Copyright (c) 2011, Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+WINDIR = $(TOPDIR)/src/win32ctl
+
+IAM = etw
+DOMAIN = ETW
+CFILES = util.c event.c pmda.c
+HFILES = util.h event.h
+LSRCFILES = tdhlist.c tdhconsume.c pcp.xml pmns root help
+LCFLAGS = -I$(WINDIR)/include -D_WIN32_WINNT=0x0600 -DFORCEINLINE="static inline"
+LLDFLAGS = -L$(WINDIR)/lib
+LLDLIBS = $(PCP_PMDALIB) -lpcp_tdh
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LIBTARGET = pmda_etw.dll
+EXTRATARGETS = tdhlist.exe tdhconsume.exe help.dir help.pag
+LDIRT = $(EXTRATARGETS) root_etw domain.h $(IAM).log
+
+CONF_LINE = "etw 87 dso etw_init $(PCP_PMDAS_DIR)/etw/pmda_etw.dll"
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me: root_etw $(LSRCFILES) $(LIBTARGET) $(EXTRATARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 tdhlist.exe $(PMDADIR)/tdhlist.exe
+ $(INSTALL) -m 644 pmns $(PMDADIR)/root
+ $(INSTALL) -m 644 domain.h help.dir help.pag help $(PMDADIR)
+ $(INSTALL) -m 644 root_etw $(PCP_VAR_DIR)/pmns/root_etw
+else
+build-me:
+install:
+endif
+
+help.dir help.pag : help root_etw
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_etw -v 2 -o help < help
+
+default_pcp: default
+
+install_pcp: install
+
+root_etw: ../../pmns/stdpmid $(PMNS)
+ rm -f root_etw
+ sed -e 's;<stdpmid>;"../../pmns/stdpmid";' <root \
+ | $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp \
+ | sed -e '/^#/d' -e '/^$$/d' >root_etw
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(OBJECTS): domain.h util.h
+
+tdhlist.exe: tdhlist.o util.o
+ $(CCF) -o tdhlist.exe tdhlist.o util.o $(LLDFLAGS) -lpcp_tdh
+
+tdhconsume.exe: tdhconsume.o util.o
+ $(CCF) -o tdhconsume.exe tdhconsume.o util.o $(LLDFLAGS) -lpcp_tdh -lwsock32
diff --git a/src/pmdas/etw/event.c b/src/pmdas/etw/event.c
new file mode 100644
index 0000000..f07aa5e
--- /dev/null
+++ b/src/pmdas/etw/event.c
@@ -0,0 +1,251 @@
+/*
+ * Event queue support for the ETW PMDA
+ *
+ * Copyright (c) 2011 Nathan Scott. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "event.h"
+#include "util.h"
+
+static struct {
+ TRACEHANDLE session;
+ EVENT_TRACE_PROPERTIES properties;
+ EVENT_TRACE_LOGFILE tracemode;
+ BOOL sequence;
+ ULONG enabled;
+
+ /* session stats */
+ ULONG buffer_count;
+ ULONG buffer_size;
+ ULONG buffer_bytes;
+ ULONG buffer_reads;
+ ULONG events_lost;
+} sys;
+
+ULONG WINAPI
+event_buffer_callback(PEVENT_TRACE_LOGFILE mode)
+{
+ sys.buffer_count++;
+ sys.buffer_size = mode->BufferSize;
+ sys.buffer_reads += mode->BuffersRead;
+ sys.buffer_bytes += mode->Filled;
+ sys.events_lost += mode->EventsLost;
+ return TRUE;
+}
+
+/* Difference in seconds between 1/1/1970 and 1/1/1601 */
+#define EPOCH_DELTA_IN_MICROSEC 11644473600000000ULL
+
+static void
+event_decode_timestamp(PEVENT_RECORD event, struct timeval *tv)
+{
+ FILETIME ft; /* 100-nanosecond intervals since 1/1/1601 */
+ ft.dwHighDateTime = event->EventHeader.TimeStamp.HighPart;
+ ft.dwLowDateTime = event->EventHeader.TimeStamp.LowPart;
+
+ __uint64_t tmp = 0;
+ tmp |= ft.dwHighDateTime;
+ tmp <<= 32;
+ tmp |= ft.dwLowDateTime;
+ tmp /= 10; /* convert to microseconds */
+ tmp -= EPOCH_DELTA_IN_MICROSEC; /* convert Win32 -> Unix epoch */
+
+ tv->tv_sec = tmp / 1000000UL;
+ tv->tv_usec = tmp % 1000000UL;
+}
+
+void
+event_duplicate_record(PEVENT_RECORD source, PEVENT_RECORD target)
+{
+ memcpy(source, target, sizeof(EVENT_RECORD));
+}
+
+void
+event_duplicate_buffer(PEVENT_RECORD source, char *buffer, size_t *bytes)
+{
+ PTRACE_EVENT_INFO pinfo = (PTRACE_EVENT_INFO)buffer;
+ DWORD size = DEFAULT_MAXMEM, sts;
+
+ sts = TdhGetEventInformation(source, 0, NULL, pinfo, &size);
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ *bytes = 0; /* too large for us, too bad, bail out */
+ } else {
+ *bytes = size;
+ }
+}
+
+/*
+ * This is the main event callback routine. It uses a fixed
+ * maximally sized buffer to capture and do minimal decoding
+ * of the event, before passing it into a queue. Avoid any
+ * memory allocation and/or copying here where possible, as
+ * this is the event arrival fast path.
+ * Note: if there are no clients, the queuing code will drop
+ * this event (within pmdaEventQueueAppend), so don't waste
+ * time here - basically just lookup the queue and dispatch.
+ */
+VOID WINAPI
+event_record_callback(PEVENT_RECORD event)
+{
+ size_t bytes;
+ struct timeval timestamp;
+ struct {
+ EVENT_RECORD record;
+ char buffer[DEFAULT_MAXMEM];
+ } localevent;
+ etw_event_t *entry;
+
+ LPGUID guid = &event->EventHeader.ProviderId;
+ int eventid = event->EventHeader.EventDescriptor.Id;
+ int version = event->EventHeader.EventDescriptor.Version;
+
+ if ((entry = event_table_lookup(guid, eventid, version)) != NULL) {
+
+ event_decode_timestamp(event, &timestamp);
+ event_duplicate_record(event, &localevent.record);
+ event_duplicate_buffer(event, &localevent.buffer[0], &bytes);
+ bytes += sizeof(EVENT_RECORD);
+
+ event_queue_lock(entry);
+ pmdaEventQueueAppend(entry->queueid, &localevent, bytes, &timestamp);
+ event_queue_unlock(entry);
+
+ } else {
+ __pmNotifyErr(LOG_ERR, "failed to enqueue event %d:%d from provider %s",
+ eventid, version, strguid(guid));
+ }
+}
+
+static void
+event_sys_setup(void)
+{
+ ULONG size = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME);
+
+ sys.properties.Wnode.BufferSize = size;
+ sys.properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
+ sys.properties.Wnode.ClientContext = 1;
+ sys.properties.Wnode.Guid = SystemTraceControlGuid;
+ sys.properties.EnableFlags = sys.enabled;
+ sys.properties.LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
+ if (sys.sequence)
+ sys.properties.LogFileMode |= EVENT_TRACE_USE_GLOBAL_SEQUENCE;
+ sys.properties.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
+
+ sys.tracemode.LoggerName = KERNEL_LOGGER_NAME;
+ sys.tracemode.BufferCallback = event_buffer_callback;
+ sys.tracemode.EventRecordCallback = event_record_callback;
+ sys.tracemode.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME;
+}
+
+/* Kernel tracing thread main() */
+static DWORD WINAPI
+event_trace_sys(LPVOID ptr)
+{
+ ULONG sts, retried = 0;
+ TRACEHANDLE trace;
+
+retry_session:
+ event_sys_setup();
+
+ sts = StartTrace(&sys.session, KERNEL_LOGGER_NAME, &sys.properties);
+ if (sts != ERROR_SUCCESS) {
+ if (retried) {
+ __pmNotifyErr(LOG_ERR, "Cannot start session (in use)");
+ } else if (sts == ERROR_ALREADY_EXISTS) {
+ __pmNotifyErr(LOG_WARNING, "%s session in use, retry.. (flags=%lx)",
+ KERNEL_LOGGER_NAME, sys.enabled);
+ sys.properties.EnableFlags = 0;
+ ControlTrace(sys.session, KERNEL_LOGGER_NAME,
+ &sys.properties, EVENT_TRACE_CONTROL_STOP);
+ retried = 1;
+ goto retry_session;
+ }
+ return sts;
+ }
+
+ trace = OpenTrace(&sys.tracemode);
+ if (trace == INVALID_PROCESSTRACE_HANDLE) {
+ sts = GetLastError();
+ __pmNotifyErr(LOG_ERR, "failed to open kernel trace: %s (%lu)",
+ tdherror(sts), sts);
+ return sts;
+ }
+
+ sts = ProcessTrace(&trace, 1, NULL, NULL); /* blocks, awaiting events */
+ if (sts == ERROR_CANCELLED)
+ sts = ERROR_SUCCESS;
+ if (sts != ERROR_SUCCESS)
+ __pmNotifyErr(LOG_ERR, "failed to process kernel traces: %s (%lu)",
+ tdherror(sts), sts);
+ return sts;
+}
+
+void
+event_queue_lock(etw_event_t *entry)
+{
+ WaitForSingleObject(entry->mutex, INFINITE);
+}
+
+void
+event_queue_unlock(etw_event_t *entry)
+{
+ ReleaseMutex(entry->mutex);
+}
+
+int
+event_init(void)
+{
+ HANDLE thread;
+
+ __pmNotifyErr(LOG_INFO, "%s: Starting up tracing ...", __FUNCTION__);
+ thread = CreateThread(NULL, 0, event_trace_sys, &sys, 0, NULL);
+ if (thread == NULL)
+ return -ECHILD;
+ CloseHandle(thread); /* no longer need the handle */
+ return 0;
+}
+
+void __attribute__((constructor))
+event_startup(void)
+{
+ sys.session = INVALID_PROCESSTRACE_HANDLE;
+}
+
+void __attribute__((destructor))
+event_shutdown(void)
+{
+ __pmNotifyErr(LOG_INFO, "%s: Shutting down tracing ...", __FUNCTION__);
+ if (sys.session != INVALID_PROCESSTRACE_HANDLE) {
+ sys.properties.EnableFlags = 0;
+ ControlTrace(sys.session, 0, &sys.properties, EVENT_TRACE_CONTROL_STOP);
+ sys.session = INVALID_PROCESSTRACE_HANDLE;
+ }
+}
+
+int
+event_decoder(int eventarray, void *buffer, size_t size,
+ struct timeval *timestamp, void *data)
+{
+ int sts; /* , handle = *(int *)data; */
+ pmAtomValue atom;
+ pmID pmid = 0; /* TODO */
+
+ sts = pmdaEventAddRecord(eventarray, timestamp, PM_EVENT_FLAG_POINT);
+ if (sts < 0)
+ return sts;
+ atom.cp = buffer;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ return 1; /* simple decoder, added just one event array */
+}
diff --git a/src/pmdas/etw/event.h b/src/pmdas/etw/event.h
new file mode 100644
index 0000000..1f39ef0
--- /dev/null
+++ b/src/pmdas/etw/event.h
@@ -0,0 +1,94 @@
+/*
+ * Event queue support for the ETW PMDA
+ *
+ * Copyright (c) 2011 Nathan Scott. All rights reversed.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _EVENT_H
+#define _EVENT_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <tdh.h>
+#include <evntrace.h>
+
+enum {
+ CLUSTER_KERNEL_PROCESS = 0,
+ CLUSTER_KERNEL_THREAD = 1,
+ CLUSTER_KERNEL_IMAGE_LOAD = 2,
+ CLUSTER_KERNEL_DISK_IO = 3,
+ CLUSTER_KERNEL_DISK_FILE_IO = 4,
+ CLUSTER_KERNEL_MEMORY_PAGE_FAULTS = 5,
+ CLUSTER_KERNEL_MEMORY_HARD_FAULTS = 6,
+ CLUSTER_KERNEL_NETWORK_TCPIP = 7,
+ CLUSTER_KERNEL_REGISTRY = 8,
+ CLUSTER_KERNEL_DBGPRINT = 9,
+ CLUSTER_KERNEL_PROCESS_COUNTERS = 10,
+ CLUSTER_KERNEL_CSWITCH = 11,
+ CLUSTER_KERNEL_DPC = 12,
+ CLUSTER_KERNEL_INTERRUPT = 13,
+ CLUSTER_KERNEL_SYSTEMCALL = 14,
+ CLUSTER_KERNEL_DISK_IO_INIT = 15,
+ CLUSTER_KERNEL_ALPC = 16,
+ CLUSTER_KERNEL_SPLIT_IO = 17,
+ CLUSTER_KERNEL_DRIVER = 18,
+ CLUSTER_KERNEL_PROFILE = 19,
+ CLUSTER_KERNEL_FILE_IO = 20,
+ CLUSTER_KERNEL_FILE_IO_INIT = 21,
+ CLUSTER_KERNEL_DISPATCHER = 22,
+ CLUSTER_KERNEL_VIRTUAL_ALLOC = 23,
+ CLUSTER_KERNEL_EXTENSION = 24,
+ CLUSTER_KERNEL_FORWARD_WMI = 25,
+ CLUSTER_KERNEL_ENABLE_RESERVE = 26,
+
+ CLUSTER_SQLSERVER_RPC_STARTING = 30,
+ CLUSTER_SQLSERVER_BATCH_STARTING = 31,
+
+ CLUSTER_CONFIGURATION = 250
+};
+
+enum {
+ EVENT_PROCESS_START,
+ EVENT_PROCESS_EXIT,
+ EVENT_THREAD_START,
+ EVENT_THREAD_STOP,
+ EVENT_IMAGE_LOAD,
+ EVENT_IMAGE_UNLOAD,
+};
+
+#define DEFAULT_MAXMEM (128 * 1024) /* 128K per event queue */
+
+typedef struct {
+ int queueid;
+ int eventid;
+ int version;
+ ULONG flags;
+ HANDLE mutex;
+ char pmnsname[64];
+ const GUID *provider;
+ LPTSTR pname;
+ ULONG plen;
+} etw_event_t;
+
+extern etw_event_t eventtab[];
+extern int event_init(void);
+extern void event_shutdown(void);
+extern int event_decoder(int arrayid, void *buffer, size_t size,
+ struct timeval *timestamp, void *data);
+
+extern etw_event_t *event_table_lookup(LPGUID guid, int eventid, int version);
+
+void event_queue_lock(etw_event_t *entry);
+void event_queue_unlock(etw_event_t *entry);
+
+#endif /* _EVENT_H */
diff --git a/src/pmdas/etw/help b/src/pmdas/etw/help
new file mode 100644
index 0000000..a48a3fc
--- /dev/null
+++ b/src/pmdas/etw/help
@@ -0,0 +1,68 @@
+/*
+ * Event Tracing for Windows PMDA
+ *
+ * Copyright (c) 2011 Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+@ etw.kernel.process.start.count
+@ etw.kernel.process.start.records
+@ etw.kernel.process.start.numclients
+@ etw.kernel.process.start.queuemem
+@ etw.kernel.process.start.params.activityid
+@ etw.kernel.process.start.params.pid
+@ etw.kernel.process.start.params.parentid
+@ etw.kernel.process.start.params.starttime
+@ etw.kernel.process.start.params.session
+@ etw.kernel.process.start.params.image_name
+@ etw.kernel.process.exit.count
+@ etw.kernel.process.exit.numclients
+@ etw.kernel.process.exit.queuemem
+@ etw.kernel.process.exit.params.activityid
+@ etw.kernel.process.exit.params.pid
+@ etw.kernel.process.exit.params.parentid
+@ etw.kernel.process.exit.params.starttime
+@ etw.kernel.process.exit.params.exittime
+@ etw.kernel.process.exit.params.exitcode
+@ etw.kernel.process.exit.params.handle_count
+@ etw.kernel.process.exit.params.commit_charge
+@ etw.kernel.process.exit.params.commit_peak
+@ etw.kernel.process.exit.params.image_name
+@ etw.kernel.process.thread.start.count
+@ etw.kernel.process.thread.start.records
+@ etw.kernel.process.thread.start.numclients
+@ etw.kernel.process.thread.start.queuemem
+@ etw.kernel.process.thread.start.params.tid
+@ etw.kernel.process.thread.start.params.pid
+@ etw.kernel.process.thread.stop.count
+@ etw.kernel.process.thread.stop.records
+@ etw.kernel.process.thread.stop.numclients
+@ etw.kernel.process.thread.stop.queuemem
+@ etw.kernel.process.thread.stop.params.activityid
+@ etw.kernel.process.thread.stop.params.tid
+@ etw.kernel.process.thread.stop.params.pid
+@ etw.kernel.process.image_load.count
+@ etw.kernel.process.image_load.records
+@ etw.kernel.process.image_load.numclients
+@ etw.kernel.process.image_load.queuemem
+@ etw.kernel.process.image_load.params.activityid
+@ etw.kernel.process.image_load.params.pid
+@ etw.kernel.process.image_load.params.name
+@ etw.kernel.process.image_load.params.size
+@ etw.kernel.process.image_unload.count
+@ etw.kernel.process.image_unload.records
+@ etw.kernel.process.image_unload.numclients
+@ etw.kernel.process.image_unload.queuemem
+@ etw.kernel.process.image_unload.params.activityid
+@ etw.kernel.process.image_unload.params.pid
+@ etw.kernel.process.image_unload.params.name
+@ etw.kernel.process.image_unload.params.size
diff --git a/src/pmdas/etw/pcp.xml b/src/pmdas/etw/pcp.xml
new file mode 100644
index 0000000..d87f6a0
--- /dev/null
+++ b/src/pmdas/etw/pcp.xml
Binary files differ
diff --git a/src/pmdas/etw/pmda.c b/src/pmdas/etw/pmda.c
new file mode 100644
index 0000000..5a802bf
--- /dev/null
+++ b/src/pmdas/etw/pmda.c
@@ -0,0 +1,513 @@
+/*
+ * Event Tracing for Windows PMDA
+ *
+ * Copyright (c) 2011 Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "event.h"
+#include "domain.h"
+#include "util.h"
+
+etw_event_t eventtab[] = {
+ { EVENT_PROCESS_START, 0, 1, EVENT_TRACE_FLAG_PROCESS, NULL,
+ "process.start", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+ { EVENT_PROCESS_EXIT, 2, 1, EVENT_TRACE_FLAG_PROCESS, NULL,
+ "process.exit", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+ { EVENT_THREAD_START, 3, 1, EVENT_TRACE_FLAG_THREAD, NULL,
+ "thread.start", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+ { EVENT_THREAD_STOP, 4, 1, EVENT_TRACE_FLAG_THREAD, NULL,
+ "thread.stop", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+ { EVENT_IMAGE_LOAD, 5, 1, EVENT_TRACE_FLAG_IMAGE_LOAD, NULL,
+ "image.load", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+ { EVENT_IMAGE_UNLOAD, 6, 1, EVENT_TRACE_FLAG_IMAGE_LOAD, NULL,
+ "image.unload", &SystemTraceControlGuid,
+ KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME) },
+};
+
+#if 0
+static etw_event_t etwevents[] = {
+ { CLUSTER_KERNEL_PROCESS, 0, EVENT_TRACE_FLAG_PROCESS,
+ { CLUSTER_KERNEL_THREAD, 0,
+ EVENT_TRACE_FLAG_THREAD, "kernel.thread" },
+ { CLUSTER_KERNEL_IMAGE_LOAD, 0,
+ EVENT_TRACE_FLAG_IMAGE_LOAD, "kernel.image_load" },
+ { CLUSTER_KERNEL_DISK_IO, 0,
+ EVENT_TRACE_FLAG_DISK_IO, "kernel.disk_io" },
+ { CLUSTER_KERNEL_DISK_FILE_IO, 0,
+ EVENT_TRACE_FLAG_DISK_FILE_IO, "kernel.disk_file_io" },
+ { CLUSTER_KERNEL_MEMORY_PAGE_FAULTS, 0,
+ EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS, "kernel.memory_page_faults" },
+ { CLUSTER_KERNEL_MEMORY_HARD_FAULTS, 0,
+ EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS, "kernel.memory_hard_faults" },
+ { CLUSTER_KERNEL_NETWORK_TCPIP, 0,
+ EVENT_TRACE_FLAG_NETWORK_TCPIP, "kernel.network_tcpip" },
+ { CLUSTER_KERNEL_REGISTRY, 0,
+ EVENT_TRACE_FLAG_REGISTRY, "kernel.registry" },
+ { CLUSTER_KERNEL_DBGPRINT, 0,
+ EVENT_TRACE_FLAG_DBGPRINT, "kernel.dbgprint" },
+ { CLUSTER_KERNEL_PROCESS_COUNTERS, 0,
+ EVENT_TRACE_FLAG_PROCESS_COUNTERS, "kernel.process_counters" },
+ { CLUSTER_KERNEL_CSWITCH, 0,
+ EVENT_TRACE_FLAG_CSWITCH, "kernel.cswitch" },
+ { CLUSTER_KERNEL_DPC, 0,
+ EVENT_TRACE_FLAG_DPC, "kernel.dpc" },
+ { CLUSTER_KERNEL_INTERRUPT, 0,
+ EVENT_TRACE_FLAG_INTERRUPT, "kernel.interrupt" },
+ { CLUSTER_KERNEL_SYSTEMCALL, 0,
+ EVENT_TRACE_FLAG_SYSTEMCALL, "kernel.syscall" },
+ { CLUSTER_KERNEL_DISK_IO_INIT, 0,
+ EVENT_TRACE_FLAG_DISK_IO_INIT, "kernel.disk_io_init" },
+ { CLUSTER_KERNEL_ALPC, 0,
+ EVENT_TRACE_FLAG_ALPC, "kernel.alpc" },
+ { CLUSTER_KERNEL_SPLIT_IO, 0,
+ EVENT_TRACE_FLAG_SPLIT_IO, "kernel.split_io" },
+ { CLUSTER_KERNEL_DRIVER, 0,
+ EVENT_TRACE_FLAG_DRIVER, "kernel.driver" },
+ { CLUSTER_KERNEL_PROFILE, 0,
+ EVENT_TRACE_FLAG_PROFILE, "kernel.profile" },
+ { CLUSTER_KERNEL_FILE_IO, 0,
+ EVENT_TRACE_FLAG_FILE_IO, "kernel.file_io" },
+ { CLUSTER_KERNEL_FILE_IO_INIT, 0,
+ EVENT_TRACE_FLAG_FILE_IO_INIT, "kernel.file_io_init" },
+ { CLUSTER_KERNEL_DISPATCHER, 0,
+ EVENT_TRACE_FLAG_DISPATCHER, "kernel.dispatcher" },
+ { CLUSTER_KERNEL_VIRTUAL_ALLOC, 0,
+ EVENT_TRACE_FLAG_VIRTUAL_ALLOC, "kernel.virtual_alloc" },
+ { CLUSTER_KERNEL_EXTENSION, 0,
+ EVENT_TRACE_FLAG_EXTENSION, "kernel.extension" },
+ { CLUSTER_KERNEL_FORWARD_WMI, 0,
+ EVENT_TRACE_FLAG_FORWARD_WMI, "kernel.forward_wmi" },
+ { CLUSTER_KERNEL_ENABLE_RESERVE, 0,
+ EVENT_TRACE_FLAG_ENABLE_RESERVE, "kernel.enable_reserve" },
+
+ { CLUSTER_SQLSERVER_RPC_STARTING, 0, 0, "sqlserver.rpc.starting" },
+ { CLUSTER_SQLSERVER_BATCH_STARTING, 0, 0, "sqlserver.sql.batch_starting" },
+};
+static int numqueues = sizeof(etwevents)/sizeof(etw_event_t);
+#endif
+
+
+static pmdaMetric metrictab[] = {
+/* etw.kernel.process.start.count */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.start.records */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,1), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.start.numclients */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.start.queuemem */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,3), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.start.params.activityid */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,10), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.start.params.pid */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,11), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.start.params.parentid */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,12), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.start.params.starttime */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,13), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) }, },
+/* etw.kernel.process.start.params.session */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,14), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.start.params.image_name */
+ { &eventtab[EVENT_PROCESS_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,15), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* etw.kernel.process.exit.count */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,20), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.exit.records */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,21), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.numclients */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,22), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.exit.queuemem */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,23), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.exit.params.activityid */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,30), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.pid */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,31), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.parentid */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,32), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.starttime */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,33), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) }, },
+/* etw.kernel.process.exit.params.exittime */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,34), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) }, },
+/* etw.kernel.process.exit.params.exitcode */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,35), PM_TYPE_32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.handle_count */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,36), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.commit_charge */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,37), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.commit_peak */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,38), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.exit.params.image_name */
+ { &eventtab[EVENT_PROCESS_EXIT],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,39), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* etw.kernel.process.thread.start.count */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,50), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.thread.start.records */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,51), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.start.numclients */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,52), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.thread.start.queuemem */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,53), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.thread.start.params.activityid */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,60), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.start.params.tid */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,61), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.start.params.pid */
+ { &eventtab[EVENT_THREAD_START],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,62), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* etw.kernel.process.thread.stop.count */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,70), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.thread.stop.records */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,71), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.stop.numclients */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,72), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.thread.stop.queuemem */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,73), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.thread.stop.params.activityid */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,80), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.stop.params.tid */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,81), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.thread.stop.params.pid */
+ { &eventtab[EVENT_THREAD_STOP],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,82), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* etw.kernel.process.image_load.count */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,90), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.image_load.records */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,91), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_load.numclients */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,92), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.image_load.queuemem */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,93), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.image_load.params.activityid */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,100), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_load.params.pid */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,101), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_load.params.name */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,102), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_load.params.size */
+ { &eventtab[EVENT_IMAGE_LOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,103), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* etw.kernel.process.image_unload.count */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,110), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.image_unload.records */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,111), PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_unload.numclients */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,112), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* etw.kernel.process.image_unload.queuemem */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,113), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* etw.kernel.process.image_unload.params.activityid */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,120), PM_TYPE_AGGREGATE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_unload.params.pid */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,121), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_unload.params.name */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,122), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* etw.kernel.process.image_unload.params.size */
+ { &eventtab[EVENT_IMAGE_UNLOAD],
+ { PMDA_PMID(CLUSTER_KERNEL_PROCESS,123), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+static int
+etw_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ etw_event_t *etw;
+ int sts = PMDA_FETCH_STATIC;
+
+ __pmNotifyErr(LOG_WARNING, "called %s, mdesc=%p", __FUNCTION__, mdesc);
+
+ switch (idp->cluster) {
+ case CLUSTER_KERNEL_PROCESS:
+ if ((etw = ((mdesc != NULL) ? mdesc->m_user : NULL)) == NULL)
+ return PM_ERR_PMID;
+
+ switch (idp->item) {
+ case 0: /* etw.kernel.process.start.count */
+ case 20: /* etw.kernel.process.exit.count */
+ case 50: /* etw.kernel.thread.start.count */
+ case 70: /* etw.kernel.thread.stop.count */
+ case 90: /* etw.kernel.process.image_load.count */
+ case 110: /* etw.kernel.process.image_unload.count */
+ sts = pmdaEventQueueCounter(etw->queueid, atom);
+ break;
+ case 1: /* etw.kernel.process.start.records */
+ case 21: /* etw.kernel.process.exit.records */
+ case 51: /* etw.kernel.thread.start.records */
+ case 71: /* etw.kernel.thread.stop.records */
+ case 91: /* etw.kernel.process.image_load.records */
+ case 111: /* etw.kernel.process.image_unload.records */
+ event_queue_lock(etw);
+ sts = pmdaEventQueueRecords(etw->queueid, atom,
+ pmdaGetContext(),
+ event_decoder, &etw);
+ event_queue_unlock(etw);
+ break;
+ case 2: /* etw.kernel.process.start.numclients */
+ case 22: /* etw.kernel.process.exit.numclients */
+ case 52: /* etw.kernel.thread.start.numclients */
+ case 72: /* etw.kernel.thread.stop.numclients */
+ case 92: /* etw.kernel.process.image_load.numclients */
+ case 112: /* etw.kernel.process.image_unload.numclients */
+ sts = pmdaEventQueueClients(etw->queueid, atom);
+ break;
+ case 3: /* etw.kernel.process.start.queuemem */
+ case 23: /* etw.kernel.process.exit.queuemem */
+ case 53: /* etw.kernel.thread.start.queuemem */
+ case 73: /* etw.kernel.thread.stop.queuemem */
+ case 93: /* etw.kernel.process.image_load.queuemem */
+ case 113: /* etw.kernel.process.image_unload.queuemem */
+ sts = pmdaEventQueueMemory(etw->queueid, atom);
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_CONFIGURATION:
+ switch (idp->item) {
+ case 0: /* etw.numclients */
+ sts = pmdaEventClients(atom);
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return sts;
+}
+
+static int
+etw_profile(__pmProfile *prof, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return 0;
+}
+
+static int
+etw_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ __pmNotifyErr(LOG_WARNING, "called %s", __FUNCTION__);
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+etw_store(pmResult *result, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return PM_ERR_PERMISSION;
+}
+
+static void
+etw_end_contextCallBack(int context)
+{
+ __pmNotifyErr(LOG_WARNING, "called %s", __FUNCTION__);
+ pmdaEventEndClient(context);
+}
+
+static int
+etw_text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaText(ident, type, buffer, pmda);
+}
+
+static int
+event_equal(etw_event_t *event, LPGUID provider, int eventid, int version)
+{
+ if (memcmp(event->provider, provider, sizeof(GUID)) != 0)
+ return 0;
+ return (event->eventid == eventid && event->version == version);
+}
+
+etw_event_t *
+event_table_lookup(LPGUID guid, int eventid, int version)
+{
+ static int last; /* punt on previous type of event reoccurring */
+ int i = last;
+
+ if (event_equal(&eventtab[i], guid, eventid, version))
+ return &eventtab[i];
+
+ for (i = 0; i < sizeof(eventtab)/sizeof(eventtab[0]); i++) {
+ etw_event_t *e = &eventtab[i];
+ if (event_equal(e, guid, eventid, version)) {
+ last = i;
+ return e;
+ }
+ }
+ return NULL;
+}
+
+int
+event_table_init(void)
+{
+ int i, id;
+
+ for (i = 0; i < sizeof(eventtab)/sizeof(eventtab[0]); i++) {
+ HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
+ etw_event_t *e = &eventtab[i];
+
+ if (!mutex) {
+ __pmNotifyErr(LOG_WARNING, "failed to create mutex for event %s",
+ e->pmnsname);
+ } else if ((id = pmdaEventNewQueue(e->pmnsname, DEFAULT_MAXMEM)) < 0) {
+ __pmNotifyErr(LOG_WARNING, "failed to create queue for event %s",
+ e->pmnsname);
+ } else {
+ e->queueid = id;
+ e->mutex = mutex;
+ }
+ }
+ return 0;
+}
+
+void
+etw_init(pmdaInterface *dp, const char *configfile)
+{
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "etw" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "etw DSO", helppath);
+ if (dp->status != 0)
+ return;
+ if (event_table_init() < 0)
+ return;
+ if (event_init() < 0)
+ return;
+
+ dp->version.four.fetch = etw_fetch;
+ dp->version.four.store = etw_store;
+ dp->version.four.profile = etw_profile;
+ dp->version.four.text = etw_text;
+
+ pmdaSetFetchCallBack(dp, etw_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, etw_end_contextCallBack);
+
+ pmdaInit(dp, NULL, 0, metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
diff --git a/src/pmdas/etw/pmns b/src/pmdas/etw/pmns
new file mode 100644
index 0000000..7fc1b06
--- /dev/null
+++ b/src/pmdas/etw/pmns
@@ -0,0 +1,132 @@
+/*
+ * Event Tracing for Windows performance metrics namespace
+ *
+ * Copyright (c) 2011 Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+etw {
+ kernel
+}
+
+etw.kernel {
+ process
+}
+
+etw.kernel.process {
+ start
+ exit
+ thread
+ image_load
+ image_unload
+}
+
+etw.kernel.process.start {
+ count ETW:0:0
+ records ETW:0:1
+ numclients ETW:0:2
+ queuemem ETW:0:3
+ params
+}
+
+etw.kernel.process.start.params {
+ activityid ETW:0:10
+ pid ETW:0:11
+ parentid ETW:0:12
+ starttime ETW:0:13
+ session ETW:0:14
+ image_name ETW:0:15
+}
+
+etw.kernel.process.exit {
+ count ETW:0:20
+ records ETW:0:21
+ numclients ETW:0:22
+ queuemem ETW:0:23
+ params
+}
+
+etw.kernel.process.exit.params {
+ activityid ETW:0:30
+ pid ETW:0:31
+ parentid ETW:0:32
+ starttime ETW:0:33
+ exittime ETW:0:34
+ exitcode ETW:0:35
+ handle_count ETW:0:36
+ commit_charge ETW:0:37
+ commit_peak ETW:0:38
+ image_name ETW:0:39
+}
+
+etw.kernel.process.thread {
+ start
+ stop
+}
+
+etw.kernel.process.thread.start {
+ count ETW:0:50
+ records ETW:0:51
+ numclients ETW:0:52
+ queuemem ETW:0:53
+ params
+}
+
+etw.kernel.process.thread.start.params {
+ activityid ETW:0:60
+ tid ETW:0:61
+ pid ETW:0:62
+}
+
+etw.kernel.process.thread.stop {
+ count ETW:0:70
+ records ETW:0:71
+ numclients ETW:0:72
+ queuemem ETW:0:73
+ params
+}
+
+etw.kernel.process.thread.stop.params {
+ activityid ETW:0:80
+ tid ETW:0:81
+ pid ETW:0:82
+}
+
+etw.kernel.process.image_load {
+ count ETW:0:90
+ records ETW:0:91
+ numclients ETW:0:92
+ queuemem ETW:0:93
+ params
+}
+
+etw.kernel.process.image_load.params {
+ activityid ETW:0:100
+ pid ETW:0:101
+ name ETW:0:102
+ size ETW:0:103
+}
+
+etw.kernel.process.image_unload {
+ count ETW:0:110
+ records ETW:0:111
+ numclients ETW:0:112
+ queuemem ETW:0:113
+ params
+}
+
+etw.kernel.process.image_unload.params {
+ activityid ETW:0:120
+ pid ETW:0:121
+ name ETW:0:122
+ size ETW:0:123
+}
diff --git a/src/pmdas/etw/root b/src/pmdas/etw/root
new file mode 100644
index 0000000..9cfae6d
--- /dev/null
+++ b/src/pmdas/etw/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { etw }
+
+#include "pmns"
+
diff --git a/src/pmdas/etw/tdhconsume.c b/src/pmdas/etw/tdhconsume.c
new file mode 100644
index 0000000..6ee3224
--- /dev/null
+++ b/src/pmdas/etw/tdhconsume.c
@@ -0,0 +1,923 @@
+/*
+ * Event Trace Consumer for Windows events on the current platform.
+ *
+ * Copyright (c) 2011, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <pmapi.h>
+#include <impl.h>
+#include <tdh.h>
+#include <tdhmsg.h>
+#include <evntrace.h>
+#include <inttypes.h>
+#include "util.h"
+
+#define PROPERTY_BUFFER 1024
+#define MAX_SESSIONS 64
+#define PCP_SESSION "PCP Collector Set"
+
+static int verbose;
+
+/* Get the event metadata */
+static DWORD
+GetEventInformation(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO *pInfoPointer)
+{
+ PTRACE_EVENT_INFO pInfo = NULL;
+ DWORD sts = ERROR_SUCCESS;
+ DWORD size = 0;
+
+ sts = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &size);
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ pInfo = (TRACE_EVENT_INFO *)malloc(size);
+ if (pInfo == NULL) {
+ fprintf(stderr,
+ "Failed to allocate memory for event info (size=%lu).\n", size);
+ sts = ERROR_OUTOFMEMORY;
+ } else {
+ /* Retrieve event metadata */
+ sts = TdhGetEventInformation(pEvent, 0, NULL, pInfo, &size);
+ }
+ } else if (sts != ERROR_SUCCESS && verbose) {
+ fprintf(stderr, "TdhGetEventInformation failed: %s (%lu)\n",
+ tdherror(sts), sts);
+ }
+ *pInfoPointer = pInfo;
+ return sts;
+}
+
+static void
+PrintMapString(PEVENT_MAP_INFO pMapInfo, PBYTE pData)
+{
+ BOOL MatchFound = FALSE;
+ DWORD i;
+
+ if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP) ==
+ EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP ||
+ ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_VALUEMAP) ==
+ EVENTMAP_INFO_FLAG_WBEM_VALUEMAP &&
+ (pMapInfo->Flag & (~EVENTMAP_INFO_FLAG_WBEM_VALUEMAP)) !=
+ EVENTMAP_INFO_FLAG_WBEM_FLAG)) {
+ if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_NO_MAP) ==
+ EVENTMAP_INFO_FLAG_WBEM_NO_MAP) {
+ printf("%s\n", ((PBYTE)pMapInfo +
+ pMapInfo->MapEntryArray[*(PULONG)pData].OutputOffset));
+ } else {
+ for (i = 0; i < pMapInfo->EntryCount; i++) {
+ if (pMapInfo->MapEntryArray[i].Value == *(PULONG)pData) {
+ printf("%s\n", ((PBYTE)pMapInfo +
+ pMapInfo->MapEntryArray[i].OutputOffset));
+ MatchFound = TRUE;
+ break;
+ }
+ }
+
+ if (MatchFound == FALSE)
+ printf("%lu\n", *(PULONG)pData);
+ }
+ }
+ else if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_MANIFEST_BITMAP) ==
+ EVENTMAP_INFO_FLAG_MANIFEST_BITMAP ||
+ (pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_BITMAP) ==
+ EVENTMAP_INFO_FLAG_WBEM_BITMAP ||
+ ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_VALUEMAP) ==
+ EVENTMAP_INFO_FLAG_WBEM_VALUEMAP &&
+ (pMapInfo->Flag & (~EVENTMAP_INFO_FLAG_WBEM_VALUEMAP)) ==
+ EVENTMAP_INFO_FLAG_WBEM_FLAG)) {
+ if ((pMapInfo->Flag & EVENTMAP_INFO_FLAG_WBEM_NO_MAP) ==
+ EVENTMAP_INFO_FLAG_WBEM_NO_MAP) {
+ DWORD BitPosition = 0;
+
+ for (i = 0; i < pMapInfo->EntryCount; i++) {
+ BitPosition = (1 << i);
+ if ((*(PULONG)pData & BitPosition) == BitPosition) {
+ printf("%s%s", (MatchFound) ? " | " : "",
+ ((PBYTE)pMapInfo +
+ pMapInfo->MapEntryArray[i].OutputOffset));
+ MatchFound = TRUE;
+ }
+ }
+ } else {
+ for (i = 0; i < pMapInfo->EntryCount; i++) {
+ if ((pMapInfo->MapEntryArray[i].Value & *(PULONG)pData) ==
+ pMapInfo->MapEntryArray[i].Value) {
+ printf("%s%s", (MatchFound) ? " | " : "",
+ ((PBYTE)pMapInfo +
+ pMapInfo->MapEntryArray[i].OutputOffset));
+ MatchFound = TRUE;
+ }
+ }
+ }
+
+ if (MatchFound) {
+ printf("\n");
+ } else {
+ printf("%lu\n", *(PULONG)pData);
+ }
+ }
+}
+
+static DWORD
+FormatAndPrintData(PEVENT_RECORD pEvent, USHORT InType, USHORT OutType,
+ PBYTE pData, DWORD DataSize, PEVENT_MAP_INFO pMapInfo)
+{
+ DWORD i, sts = ERROR_SUCCESS;
+ size_t StringLength = 0;
+
+ switch (InType) {
+ case TDH_INTYPE_UNICODESTRING:
+ case TDH_INTYPE_COUNTEDSTRING:
+ case TDH_INTYPE_REVERSEDCOUNTEDSTRING:
+ case TDH_INTYPE_NONNULLTERMINATEDSTRING:
+ if (TDH_INTYPE_COUNTEDSTRING == InType)
+ StringLength = *(PUSHORT)pData;
+ else if (TDH_INTYPE_REVERSEDCOUNTEDSTRING == InType)
+ StringLength = MAKEWORD(
+ HIBYTE((PUSHORT)pData), LOBYTE((PUSHORT)pData));
+ else if (TDH_INTYPE_NONNULLTERMINATEDSTRING == InType)
+ StringLength = DataSize;
+ else
+ StringLength = wcslen((LPWSTR)pData);
+ printf("%.*s\n", StringLength, pData);
+ break;
+
+ case TDH_INTYPE_ANSISTRING:
+ case TDH_INTYPE_COUNTEDANSISTRING:
+ case TDH_INTYPE_REVERSEDCOUNTEDANSISTRING:
+ case TDH_INTYPE_NONNULLTERMINATEDANSISTRING:
+ if (TDH_INTYPE_COUNTEDANSISTRING == InType)
+ StringLength = *(PUSHORT)pData;
+ else if (TDH_INTYPE_REVERSEDCOUNTEDANSISTRING == InType)
+ StringLength = MAKEWORD(
+ HIBYTE((PUSHORT)pData), LOBYTE((PUSHORT)pData));
+ else if (TDH_INTYPE_NONNULLTERMINATEDANSISTRING == InType)
+ StringLength = DataSize;
+ else
+ StringLength = strlen((LPSTR)pData);
+ printf("%.*s\n", StringLength, pData);
+ break;
+
+ case TDH_INTYPE_INT8:
+ printf("%hd\n", *(PCHAR)pData);
+ break;
+
+ case TDH_INTYPE_UINT8:
+ if (TDH_OUTTYPE_HEXINT8 == OutType)
+ printf("0x%x\n", *(PBYTE)pData);
+ else
+ printf("%hu\n", *(PBYTE)pData);
+ break;
+
+ case TDH_INTYPE_INT16:
+ printf("%hd\n", *(PSHORT)pData);
+ break;
+
+ case TDH_INTYPE_UINT16:
+ if (TDH_OUTTYPE_HEXINT16 == OutType)
+ printf("0x%x\n", *(PUSHORT)pData);
+ else if (TDH_OUTTYPE_PORT == OutType)
+ printf("%hu\n", ntohs(*(PUSHORT)pData));
+ else
+ printf("%hu\n", *(PUSHORT)pData);
+ break;
+
+ case TDH_INTYPE_INT32:
+ if (TDH_OUTTYPE_HRESULT == OutType)
+ printf("0x%lx\n", *(PLONG)pData);
+ else
+ printf("%ld\n", *(PLONG)pData);
+ break;
+
+ case TDH_INTYPE_UINT32:
+ if (TDH_OUTTYPE_HRESULT == OutType ||
+ TDH_OUTTYPE_WIN32ERROR == OutType ||
+ TDH_OUTTYPE_NTSTATUS == OutType ||
+ TDH_OUTTYPE_HEXINT32 == OutType)
+ printf("0x%lx\n", *(PULONG)pData);
+ else if (TDH_OUTTYPE_IPV4 == OutType)
+ printf("%ld.%ld.%ld.%ld\n", (*(PLONG)pData >> 0) & 0xff,
+ (*(PLONG)pData >> 8) & 0xff,
+ (*(PLONG)pData >> 16) & 0xff,
+ (*(PLONG)pData >> 24) & 0xff);
+ else if (pMapInfo)
+ PrintMapString(pMapInfo, pData);
+ else
+ printf("%lu\n", *(PULONG)pData);
+ break;
+
+ case TDH_INTYPE_INT64:
+ printf("%I64d\n", *(PLONGLONG)pData);
+ break;
+
+ case TDH_INTYPE_UINT64:
+ if (TDH_OUTTYPE_HEXINT64 == OutType)
+ printf("0x%I64x\n", *(PULONGLONG)pData);
+ else
+ printf("%I64u\n", *(PULONGLONG)pData);
+ break;
+
+ case TDH_INTYPE_FLOAT:
+ printf("%f\n", *(PFLOAT)pData);
+ break;
+
+ case TDH_INTYPE_DOUBLE:
+ printf("%f\n", *(double*)pData);
+ break;
+
+ case TDH_INTYPE_BOOLEAN:
+ printf("%s\n", ((PBOOL)pData == 0) ? "false" : "true");
+ break;
+
+ case TDH_INTYPE_BINARY:
+ if (TDH_OUTTYPE_IPV6 == OutType)
+ break;
+ else {
+ for (i = 0; i < DataSize; i++)
+ printf("%.2x", pData[i]);
+ printf("\n");
+ }
+ break;
+
+ case TDH_INTYPE_GUID:
+ printf("%s\n", strguid((GUID *)pData));
+ break;
+
+ case TDH_INTYPE_POINTER:
+ case TDH_INTYPE_SIZET:
+ if (EVENT_HEADER_FLAG_32_BIT_HEADER ==
+ (pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER))
+ printf("0x%I32x\n", *(PULONG)pData);
+ else
+ printf("0x%I64x\n", *(PULONGLONG)pData);
+
+ case TDH_INTYPE_FILETIME:
+ break;
+
+ case TDH_INTYPE_SYSTEMTIME:
+ break;
+
+ case TDH_INTYPE_SID:
+ break;
+
+ case TDH_INTYPE_HEXINT32:
+ printf("0x%I32x\n", *(PULONG)pData);
+ break;
+
+ case TDH_INTYPE_HEXINT64:
+ printf("0x%I64x\n", *(PULONGLONG)pData);
+ break;
+
+ case TDH_INTYPE_UNICODECHAR:
+ printf("%c\n", *(PWCHAR)pData);
+ break;
+
+ case TDH_INTYPE_ANSICHAR:
+ printf("%C\n", *(PCHAR)pData);
+ break;
+
+ case TDH_INTYPE_WBEMSID:
+ break;
+
+ default:
+ sts = ERROR_NOT_FOUND;
+ }
+
+ return sts;
+}
+
+/*
+ * Get the size of the array.
+ * For MOF-based events, the size is specified in the declaration or using
+ * the MAX qualifier. For manifest-based events, the property can specify
+ * the size of the array using the count attribute.
+ * The count attribue can specify the size directly or specify the name
+ * of another property in the event data that contains the size.
+ */
+static void
+GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo,
+ USHORT i, PUSHORT ArraySize)
+{
+ PROPERTY_DATA_DESCRIPTOR DataDescriptor;
+ DWORD size = 0;
+
+ if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) ==
+ PropertyParamCount) {
+ DWORD cnt = 0; /* expecting count to be defined as uint16 or uint32 */
+ DWORD j = pInfo->EventPropertyInfoArray[i].countPropertyIndex;
+
+ ZeroMemory(&DataDescriptor, sizeof(PROPERTY_DATA_DESCRIPTOR));
+ DataDescriptor.PropertyName =
+ (ULONGLONG)((PBYTE)(pInfo) +
+ pInfo->EventPropertyInfoArray[j].NameOffset);
+ DataDescriptor.ArrayIndex = ULONG_MAX;
+ TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &size);
+ TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, size, (PBYTE)&cnt);
+ *ArraySize = (USHORT)cnt;
+ } else {
+ *ArraySize = pInfo->EventPropertyInfoArray[i].count;
+ }
+}
+
+/*
+ * Mapped string values defined in a manifest will contain a trailing space
+ * in the EVENT_MAP_ENTRY structure. Replace the trailing space with a null-
+ * terminating character, so that bit mapped strings are correctly formatted.
+ */
+static void
+RemoveTrailingSpace(PEVENT_MAP_INFO pMapInfo)
+{
+ SIZE_T ByteLength = 0;
+ DWORD i;
+
+ for (i = 0; i < pMapInfo->EntryCount; i++) {
+ ByteLength = (wcslen((LPWSTR)((PBYTE)pMapInfo +
+ pMapInfo->MapEntryArray[i].OutputOffset)) - 1) * 2;
+ *((LPWSTR)((PBYTE)pMapInfo +
+ (pMapInfo->MapEntryArray[i].OutputOffset +
+ ByteLength))) = L'\0';
+ }
+}
+
+/*
+ * Both MOF-based events and manifest-based events can specify name/value maps.
+ * The map values can be integer values or bit values.
+ * If the property specifies a value map, get the map.
+ */
+static DWORD
+GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, DWORD DecodingSource,
+ PEVENT_MAP_INFO pMapInfo)
+{
+ DWORD sts = ERROR_SUCCESS;
+ DWORD size = 0;
+
+ /* Retrieve required buffer size for map info */
+ sts = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &size);
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ pMapInfo = (PEVENT_MAP_INFO)malloc(size);
+ if (pMapInfo == NULL) {
+ fprintf(stderr, "Failed to allocate map info memory (size=%lu).\n",
+ size);
+ return ERROR_OUTOFMEMORY;
+ }
+
+ /* Retrieve the map info */
+ sts = TdhGetEventMapInformation(pEvent, pMapName, pMapInfo, &size);
+ }
+
+ if (sts == ERROR_SUCCESS) {
+ if (DecodingSourceXMLFile == DecodingSource)
+ RemoveTrailingSpace(pMapInfo);
+ } else if (sts == ERROR_NOT_FOUND) {
+ sts = ERROR_SUCCESS;
+ } else {
+ fprintf(stderr, "TdhGetEventMapInformation failed: %s (%ld)\n",
+ tdherror(sts), sts);
+ }
+ return sts;
+}
+
+static DWORD
+PrintProperties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo,
+ USHORT i, LPWSTR pStructureName, USHORT StructIndex)
+{
+ DWORD sts = ERROR_SUCCESS;
+ DWORD LastMember = 0;
+ USHORT ArraySize = 0;
+ PEVENT_MAP_INFO pMapInfo = NULL;
+ PROPERTY_DATA_DESCRIPTOR DataDescriptors[2];
+ ULONG DescriptorsCount = 0;
+ USHORT k, j;
+
+ static DWORD size;
+ static PBYTE pData;
+
+ if (pData == NULL) {
+ if ((pData = malloc(PROPERTY_BUFFER)) != NULL)
+ size = PROPERTY_BUFFER;
+ }
+
+ /* Get the size of the array (if the property is an array) */
+ GetArraySize(pEvent, pInfo, i, &ArraySize);
+
+ for (k = 0; k < ArraySize; k++) {
+ wprintf(L"%*s%s: ", (pStructureName) ? 4 : 0, L"", (LPWSTR)
+ ((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset));
+
+ /* If the property is a structure, print the members of the structure */
+ if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) {
+ printf("\n");
+
+ LastMember =
+ pInfo->EventPropertyInfoArray[i].structType.StructStartIndex +
+ pInfo->EventPropertyInfoArray[i].structType.NumOfStructMembers;
+
+ for (j = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < LastMember; j++) {
+ sts = PrintProperties(pEvent, pInfo, j,
+(LPWSTR)((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset), k);
+ if (sts != ERROR_SUCCESS) {
+ fprintf(stderr,
+ "Printing the members of the structure failed\n");
+ goto cleanup;
+ }
+ }
+ } else {
+ ZeroMemory(&DataDescriptors, sizeof(DataDescriptors));
+
+ /*
+ * To retrieve a member of a structure, you need to specify
+ * an array of descriptors. The first descriptor in the array
+ * identifies the name of the structure and the second
+ * descriptor defines the member of the structure whose data
+ * you want to retrieve.
+ */
+ if (pStructureName) {
+ DataDescriptors[0].PropertyName = (ULONGLONG)pStructureName;
+ DataDescriptors[0].ArrayIndex = StructIndex;
+ DataDescriptors[1].PropertyName = (ULONGLONG)
+ ((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset);
+ DataDescriptors[1].ArrayIndex = k;
+ DescriptorsCount = 2;
+ } else {
+ DataDescriptors[0].PropertyName = (ULONGLONG)
+ ((PBYTE)(pInfo) + pInfo->EventPropertyInfoArray[i].NameOffset);
+ DataDescriptors[0].ArrayIndex = k;
+ DescriptorsCount = 1;
+ }
+
+ /*
+ * TDH API does not support IPv6 addresses.
+ * If the output type is TDH_OUTTYPE_IPV6, you will be unable
+ * to consume the rest of the event.
+ * If you try to consume the remainder of the event, you will
+ * get ERROR_EVT_INVALID_EVENT_DATA.
+ */
+ if (TDH_INTYPE_BINARY ==
+ pInfo->EventPropertyInfoArray[i].nonStructType.InType &&
+ TDH_OUTTYPE_IPV6 ==
+ pInfo->EventPropertyInfoArray[i].nonStructType.OutType) {
+ fprintf(stderr, "Event contains an IPv6 address. Skipping.\n");
+ sts = ERROR_EVT_INVALID_EVENT_DATA;
+ break;
+ } else {
+retry:
+ sts = TdhGetProperty(pEvent, 0, NULL,
+ DescriptorsCount, &DataDescriptors[0], size, pData);
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ /* TdhGetPropertySize failing on Win2008, so do this: */
+ pData = realloc(pData, size *= 2);
+ goto retry;
+ } else if (sts != ERROR_SUCCESS) {
+ fprintf(stderr, "TdhGetProperty failed: %s (%ld)\n",
+ tdherror(sts), sts);
+ goto cleanup;
+ }
+
+ /*
+ * Get the name/value map if the property specifies a value map.
+ */
+ sts = GetMapInfo(pEvent, (PWCHAR) ((PBYTE)(pInfo) +
+ pInfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset),
+ pInfo->DecodingSource,
+ pMapInfo);
+ if (sts != ERROR_SUCCESS) {
+ fprintf(stderr, "GetMapInfo failed\n");
+ goto cleanup;
+ }
+
+ sts = FormatAndPrintData(pEvent,
+ pInfo->EventPropertyInfoArray[i].nonStructType.InType,
+ pInfo->EventPropertyInfoArray[i].nonStructType.OutType,
+ pData, size, pMapInfo);
+ if (sts != ERROR_SUCCESS) {
+ fprintf(stderr, "FormatAndPrintData failed\n");
+ goto cleanup;
+ }
+
+ if (pMapInfo) {
+ free(pMapInfo);
+ pMapInfo = NULL;
+ }
+ }
+ }
+ }
+
+cleanup:
+
+ if (pMapInfo) {
+ free(pMapInfo);
+ pMapInfo = NULL;
+ }
+
+ return sts;
+}
+
+void
+PrintHeader(PEVENT_RECORD pEvent)
+{
+ /* Note: EventHeader.ProcessId is defined as ULONG */
+ printf("Event HEADER (size=%u) flags=%s type=%s\npid=%lu tid=%ld eid=%u\n",
+ pEvent->EventHeader.Size,
+ eventHeaderFlags(pEvent->EventHeader.Flags),
+ eventPropertyFlags(pEvent->EventHeader.EventProperty),
+ pEvent->EventHeader.ThreadId, pEvent->EventHeader.ProcessId,
+ pEvent->EventHeader.EventDescriptor.Id);
+ if (pEvent->EventHeader.Flags &
+ (EVENT_HEADER_FLAG_PRIVATE_SESSION|EVENT_HEADER_FLAG_NO_CPUTIME)) {
+ printf("Time processor=%"PRIu64"\n", pEvent->EventHeader.ProcessorTime);
+ } else {
+ printf("Time: sys=%lu usr=%lu\n",
+ pEvent->EventHeader.KernelTime, pEvent->EventHeader.UserTime);
+ }
+ printf("Event PROVIDER %s\n", strguid(&pEvent->EventHeader.ProviderId));
+ printf("Event ACTIVITY %s\n", strguid(&pEvent->EventHeader.ActivityId));
+}
+
+void
+PrintTimestamp(PEVENT_RECORD pEvent)
+{
+ ULONGLONG TimeStamp = 0;
+ ULONGLONG Nanoseconds = 0;
+ SYSTEMTIME st;
+ SYSTEMTIME stLocal;
+ FILETIME ft;
+
+ /* Print the time stamp for when the event occurred */
+ ft.dwHighDateTime = pEvent->EventHeader.TimeStamp.HighPart;
+ ft.dwLowDateTime = pEvent->EventHeader.TimeStamp.LowPart;
+
+ FileTimeToSystemTime(&ft, &st);
+ SystemTimeToTzSpecificLocalTime(NULL, &st, &stLocal);
+ TimeStamp = pEvent->EventHeader.TimeStamp.QuadPart;
+ Nanoseconds = (TimeStamp % 10000000) * 100;
+
+ printf("Event TIMESTAMP: %02d/%02d/%02d %02d:%02d:%02d.%I64u\n",
+ stLocal.wMonth, stLocal.wDay, stLocal.wYear, stLocal.wHour,
+ stLocal.wMinute, stLocal.wSecond, Nanoseconds);
+}
+
+void
+PrintEventInfo(PTRACE_EVENT_INFO pInfo)
+{
+ if (DecodingSourceWbem == pInfo->DecodingSource)
+ printf("EventInfo: MOF class event\n");
+ else if (DecodingSourceXMLFile == pInfo->DecodingSource)
+ printf("EventInfo: XML manifest event\n");
+ else if (DecodingSourceWPP == pInfo->DecodingSource)
+ printf("EventInfo: WPP event\n");
+
+ printf("Event GUID: %s\n", strguid(&pInfo->EventGuid));
+
+ if (pInfo->ProviderNameOffset > 0)
+ wprintf(L"Provider name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->ProviderNameOffset));
+
+ if (DecodingSourceXMLFile == pInfo->DecodingSource)
+ wprintf(L"Event ID: %hu\n", pInfo->EventDescriptor.Id);
+
+ wprintf(L"Version: %d\n", pInfo->EventDescriptor.Version);
+
+ if (pInfo->ChannelNameOffset > 0)
+ wprintf(L"Channel name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->ChannelNameOffset));
+
+ if (pInfo->LevelNameOffset > 0)
+ wprintf(L"Level name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->LevelNameOffset));
+ else
+ wprintf(L"Level: %hu\n", pInfo->EventDescriptor.Level);
+
+ if (DecodingSourceXMLFile == pInfo->DecodingSource) {
+ if (pInfo->OpcodeNameOffset > 0)
+ wprintf(L"Opcode name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->OpcodeNameOffset));
+ } else
+ wprintf(L"Type: %hu\n", pInfo->EventDescriptor.Opcode);
+
+ if (DecodingSourceXMLFile == pInfo->DecodingSource) {
+ if (pInfo->TaskNameOffset > 0)
+ wprintf(L"Task name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->TaskNameOffset));
+ } else
+ wprintf(L"Task: %hu\n", pInfo->EventDescriptor.Task);
+
+ wprintf(L"Keyword mask: 0x%x\n", pInfo->EventDescriptor.Keyword);
+ if (pInfo->KeywordsNameOffset) {
+ LPWSTR pKeyword = (LPWSTR)((PBYTE)(pInfo) + pInfo->KeywordsNameOffset);
+
+ for (; *pKeyword != 0; pKeyword += (wcslen(pKeyword) + 1))
+ wprintf(L" Keyword name: %s\n", pKeyword);
+ }
+
+ if (pInfo->EventMessageOffset > 0)
+ wprintf(L"Event message: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->EventMessageOffset));
+
+ if (pInfo->ActivityIDNameOffset > 0)
+ wprintf(L"Activity ID name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->ActivityIDNameOffset));
+
+ if (pInfo->RelatedActivityIDNameOffset > 0)
+ wprintf(L"Related activity ID name: %s\n",
+ (LPWSTR)((PBYTE)(pInfo) + pInfo->RelatedActivityIDNameOffset));
+}
+
+/* Callback that receives the events */
+VOID WINAPI
+ProcessEvent(PEVENT_RECORD pEvent)
+{
+ DWORD sts = ERROR_SUCCESS;
+ PTRACE_EVENT_INFO pInfo = NULL;
+ USHORT i;
+
+ PrintHeader(pEvent);
+ PrintTimestamp(pEvent);
+
+ /*
+ * Process the event.
+ * The pEvent->UserData member is a pointer to the event specific data,
+ * if any exists.
+ */
+ sts = GetEventInformation(pEvent, &pInfo);
+ if (sts != ERROR_SUCCESS)
+ goto cleanup;
+
+ PrintEventInfo(pInfo);
+
+ if (DecodingSourceWPP == pInfo->DecodingSource)
+ /* Not handling the WPP case, unless just an inline string */
+ if (!(pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_STRING_ONLY))
+ goto cleanup;
+
+ /*
+ * Print the event data for all the top-level properties.
+ * Metadata for all the top-level properties comes before structure
+ * member properties in the property information array.
+ * If the EVENT_HEADER_FLAG_STRING_ONLY flag is set, the event data
+ * is a null-terminated string, so just print it.
+ */
+ if (EVENT_HEADER_FLAG_STRING_ONLY ==
+ (pEvent->EventHeader.Flags & EVENT_HEADER_FLAG_STRING_ONLY)) {
+ printf("Embedded: %s\n", (char *)pEvent->UserData);
+ } else {
+ for (i = 0; i < pInfo->TopLevelPropertyCount; i++) {
+ sts = PrintProperties(pEvent, pInfo, i, NULL, 0);
+ if (sts != ERROR_SUCCESS) {
+ fprintf(stderr,
+ "Printing top level properties failed, property %u.\n",
+ i);
+ }
+ }
+ }
+
+cleanup:
+ fflush(stdout);
+ if (pInfo)
+ free(pInfo);
+}
+
+static struct {
+ EVENT_TRACE_PROPERTIES *properties;
+ TRACEHANDLE session;
+ LPTSTR name;
+} sessions[MAX_SESSIONS];
+static int sCount;
+
+static void
+stopSession(TRACEHANDLE session, LPTSTR name,
+ PEVENT_TRACE_PROPERTIES properties)
+{
+ if (session != INVALID_PROCESSTRACE_HANDLE) {
+ ULONG sts = ControlTrace(session, name, properties,
+ EVENT_TRACE_CONTROL_STOP);
+ if (sts != ERROR_SUCCESS)
+ fprintf(stderr, "ControlTrace failed (%s): %s\n", name,
+ tdherror(sts));
+ else
+ fprintf(stderr, "Stopped %s event tracing session\n", name);
+ }
+}
+
+static void __attribute__((constructor)) initTracing()
+{
+ int i;
+
+ for (i = 0; i < sizeof(sessions) / sizeof(sessions[0]); i++)
+ sessions[i].session = INVALID_PROCESSTRACE_HANDLE;
+}
+
+static void __attribute__((destructor)) stopTracing()
+{
+ int i;
+
+ for (i = 0; i < sCount; i++) {
+ sessions[i].properties->EnableFlags = 0;
+ stopSession(sessions[i].session, sessions[i].name, sessions[i].properties);
+ }
+}
+
+void
+enableEventTrace(LPTSTR name, ULONG namelen, const GUID *guid,
+ ULONG enableFlags, BOOL useGlobalSequence)
+{
+ TRACEHANDLE session;
+ EVENT_TRACE_PROPERTIES *properties;
+ ULONG size, sts = ERROR_SUCCESS;
+ int retryStartTrace = 0;
+
+ size = sizeof(EVENT_TRACE_PROPERTIES) + namelen;
+ properties = malloc(size);
+ if (properties == NULL) {
+ fprintf(stderr, "Insufficient memory: %lu bytes\n", size);
+ exit(1);
+ }
+
+ sessions[sCount].properties = properties;
+ sessions[sCount].session = session;
+ sessions[sCount].name = name;
+ sCount++;
+
+retrySession:
+ ZeroMemory(properties, size);
+ properties->Wnode.BufferSize = size;
+ properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
+ properties->Wnode.ClientContext = 1;
+ properties->Wnode.Guid = *guid;
+ properties->EnableFlags = enableFlags;
+ properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
+ if (useGlobalSequence == TRUE)
+ properties->LogFileMode |= EVENT_TRACE_USE_GLOBAL_SEQUENCE;
+ properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
+
+ sts = StartTrace(&session, name, properties);
+ if (sts != ERROR_SUCCESS) {
+ if (retryStartTrace == 1) {
+ fprintf(stderr, "Cannot start %s session (in use)\n", name);
+ } else if (sts == ERROR_ALREADY_EXISTS) {
+ fprintf(stderr, "%s session is in use - retry ... (flags=%lx)\n",
+ name, enableFlags);
+ stopTracing();
+ retryStartTrace = 1;
+ goto retrySession;
+ }
+ else {
+ fprintf(stderr, "StartTrace: %s\n", tdherror(sts));
+ }
+ exit(1);
+ }
+}
+
+ULONG bufferCount, buffersRead, bufferTotal, bufferBytes, eventsLost, sysCount;
+
+ULONG WINAPI
+BufferCallback(PEVENT_TRACE_LOGFILE mode)
+{
+ buffersRead += mode->BuffersRead;
+ bufferTotal += mode->BufferSize;
+ bufferBytes += mode->Filled;
+ eventsLost += mode->EventsLost;
+ sysCount += mode->IsKernelTrace;
+ bufferCount++;
+ printf("Event BUFFER (size=%lu) all reads=%lu bytes=%lu lost=%lu sys=%lu\n",
+ mode->Filled, buffersRead, bufferBytes, eventsLost, sysCount);
+ return TRUE;
+}
+
+LPGUID
+LookupGuidInBuffer(LPTSTR name, PPROVIDER_ENUMERATION_INFO buffer)
+{
+ PTRACE_PROVIDER_INFO traceProviderInfo;
+ PWCHAR stringPointer;
+ char s[1024];
+ LPGUID guidPointer;
+ PBYTE bufferPointer = (PBYTE)buffer;
+ ULONG i;
+
+ for (i = 0; i < buffer->NumberOfProviders; i++) {
+ traceProviderInfo = &buffer->TraceProviderInfoArray[i];
+ if (traceProviderInfo->ProviderNameOffset == 0)
+ continue;
+ guidPointer = &traceProviderInfo->ProviderGuid;
+ stringPointer = (PWCHAR)
+ (bufferPointer + traceProviderInfo->ProviderNameOffset);
+ WideCharToMultiByte(CP_ACP, 0, stringPointer, -1, s, 1024, NULL, NULL);
+ if (strcmp(s, name) == 0)
+ return guidPointer;
+ }
+ return NULL;
+}
+
+ULONG
+ProviderGuid(LPTSTR name, LPGUID guidPointer)
+{
+ PROVIDER_ENUMERATION_INFO providerEnumerationInfo;
+ PPROVIDER_ENUMERATION_INFO buffer;
+ ULONG size, sts;
+
+ buffer = &providerEnumerationInfo;
+ size = sizeof(providerEnumerationInfo);
+ sts = TdhEnumerateProviders(buffer, &size);
+ do {
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ if (buffer != &providerEnumerationInfo)
+ BufferFree(buffer);
+ buffer = (PPROVIDER_ENUMERATION_INFO)BufferAllocate(size);
+ if (!buffer)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ sts = TdhEnumerateProviders(buffer, &size);
+ }
+ else if (sts == ERROR_SUCCESS) {
+ guidPointer = LookupGuidInBuffer(name, buffer);
+ break;
+ }
+ else {
+ fprintf(stderr, "TdhEnumerateProviders failed: %s (=%lu)\n",
+ tdherror(sts), sts);
+ break;
+ }
+ } while (1);
+
+ if (buffer != &providerEnumerationInfo)
+ BufferFree(buffer);
+
+ return sts;
+}
+
+TRACEHANDLE
+openTraceHandle(LPTSTR name)
+{
+ TRACEHANDLE handle;
+ EVENT_TRACE_LOGFILE traceMode;
+
+ ZeroMemory(&traceMode, sizeof(EVENT_TRACE_LOGFILE));
+ traceMode.LoggerName = name;
+ traceMode.BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)BufferCallback;
+ traceMode.EventRecordCallback = (PEVENT_RECORD_CALLBACK)ProcessEvent;
+ traceMode.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME;
+
+ handle = OpenTrace(&traceMode);
+ if (handle == INVALID_PROCESSTRACE_HANDLE) {
+ ULONG sts = GetLastError();
+ fprintf(stderr, "OpenTrace: %s (%lu)\n", tdherror(sts), sts);
+ CloseTrace(handle);
+ exit(1);
+ }
+ return handle;
+}
+
+static char *options = "k:v?";
+static char usage[] =
+ "Usage: %s [options] tracename\n\n"
+ "Options:\n"
+ " -k subsys kernel subsystem to trace\n"
+ " -g use global sequence numbers\n"
+ " -v verbose diagnostics (errors)\n";
+
+int
+main(int argc, LPTSTR *argv)
+{
+ BOOL useGlobalSequence = FALSE;
+ TRACEHANDLE session;
+ ULONG sts, sysFlags = 0;
+ int c;
+
+ while ((c = getopt(argc, argv, options)) != EOF) {
+ switch (c) {
+ case 'g':
+ useGlobalSequence = TRUE;
+ break;
+ case 'k':
+ sysFlags |= kernelTraceFlag(optarg);
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case '?':
+ default:
+ fprintf(stderr, usage, argv[0]);
+ exit(1);
+ }
+ }
+
+ if (sysFlags) {
+ enableEventTrace(KERNEL_LOGGER_NAME, sizeof(KERNEL_LOGGER_NAME),
+ &SystemTraceControlGuid, sysFlags, useGlobalSequence);
+ session = openTraceHandle(KERNEL_LOGGER_NAME);
+ } else {
+ session = openTraceHandle(PCP_SESSION);
+ }
+
+ sts = ProcessTrace(&session, 1, NULL, NULL);
+ if (sts == ERROR_CANCELLED)
+ sts = ERROR_SUCCESS;
+ if (sts != ERROR_SUCCESS)
+ fprintf(stderr, "ProcessTrace: %s (%lu)\n", tdherror(sts), sts);
+ return sts;
+}
diff --git a/src/pmdas/etw/tdhlist.c b/src/pmdas/etw/tdhlist.c
new file mode 100644
index 0000000..e21c024
--- /dev/null
+++ b/src/pmdas/etw/tdhlist.c
@@ -0,0 +1,267 @@
+/*
+ * List Event Traces Providers or sessions for Windows events.
+ *
+ * Copyright (c) 2011, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <inttypes.h>
+#include <windows.h>
+#include <wmistr.h>
+#include <stdio.h>
+#include <errno.h>
+#include <tdh.h>
+#include "util.h"
+
+#define MAX_SESSIONS (ULONG)64
+#define MAX_SESSION_NAME_LEN 1024
+#define MAX_LOGFILE_PATH_LEN 1024
+
+void
+PrintFieldInfo(PBYTE buffer,
+ PPROVIDER_FIELD_INFO fieldInfo, int id, EVENT_FIELD_TYPE eventFieldType)
+{
+ PWCHAR stringBuffer;
+
+ printf("\tField %u\n", id);
+ switch (eventFieldType) {
+ case EventKeywordInformation:
+ printf("\t\tType: KeywordInformation\n");
+ break;
+ case EventLevelInformation:
+ printf("\t\tType: LevelInformation\n");
+ break;
+ case EventChannelInformation:
+ printf("\t\tType: ChannelInformation\n");
+ break;
+ case EventTaskInformation:
+ printf("\t\tType: TaskInformation\n");
+ break;
+ case EventOpcodeInformation:
+ printf("\t\tType: OpcodeInformation\n");
+ break;
+ default:
+ break;
+ }
+ printf("\t\tValue: %" PRIu64 "\n", fieldInfo->Value);
+ if (fieldInfo->NameOffset) {
+ stringBuffer = (PWCHAR)(buffer + fieldInfo->NameOffset);
+ printf("\t\tField: %ls\n", stringBuffer);
+ }
+ if (fieldInfo->DescriptionOffset) {
+ stringBuffer = (PWCHAR)(buffer + fieldInfo->DescriptionOffset);
+ printf("\t\tDescription: %ls\n", stringBuffer);
+ }
+}
+
+void
+PrintFieldElements(PPROVIDER_FIELD_INFOARRAY buffer, EVENT_FIELD_TYPE eventFieldType)
+{
+ PPROVIDER_FIELD_INFO traceFieldInfo;
+ ULONG i;
+
+ for (i = 0; i < buffer->NumberOfElements; i++) {
+ traceFieldInfo = &buffer->FieldInfoArray[i];
+ PrintFieldInfo((PBYTE)buffer, traceFieldInfo, i, eventFieldType);
+ }
+}
+
+void
+EnumerateProviderFieldInformation(LPGUID guidPointer, EVENT_FIELD_TYPE eventFieldType)
+{
+ PPROVIDER_FIELD_INFOARRAY buffer = NULL;
+ ULONG sts, size = 0;
+
+ sts = TdhEnumerateProviderFieldInformation(guidPointer,
+ eventFieldType, buffer, &size);
+ do {
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ BufferFree(buffer);
+ buffer = (PPROVIDER_FIELD_INFOARRAY)BufferAllocate(size);
+ if (!buffer)
+ return;
+ sts = TdhEnumerateProviderFieldInformation(guidPointer,
+ eventFieldType, buffer, &size);
+ }
+ else if (sts == ERROR_SUCCESS) {
+ PrintFieldElements(buffer, eventFieldType);
+ break;
+ }
+ else if (sts == ERROR_NOT_FOUND) {
+ break;
+ }
+ else {
+ fprintf(stderr, "TdhEnumerateProviderFieldInformation: %s (%lu)\n",
+ tdherror(sts), sts);
+ break;
+ }
+ } while (1);
+
+ BufferFree(buffer);
+}
+
+void
+PrintTraceProviderInfo(PBYTE buffer, PTRACE_PROVIDER_INFO traceProviderInfo)
+{
+ LPGUID guidPointer = &traceProviderInfo->ProviderGuid;
+ PWCHAR stringBuffer;
+ ULONG i;
+
+ if (traceProviderInfo->ProviderNameOffset) {
+ stringBuffer = (PWCHAR)(buffer + traceProviderInfo->ProviderNameOffset);
+ printf("Name: %ls\n", stringBuffer);
+ }
+
+ printf("Guid: %s\n", strguid(guidPointer));
+ printf("SchemaSource: %ld (%s)\n", traceProviderInfo->SchemaSource,
+ traceProviderInfo->SchemaSource==0 ? "XML manifest" : "WMI MOF class");
+
+ for (i = EventKeywordInformation; i <= EventChannelInformation; i++) {
+ EnumerateProviderFieldInformation(guidPointer, (EVENT_FIELD_TYPE)i);
+ }
+}
+
+void
+PrintProviderEnumeration(PPROVIDER_ENUMERATION_INFO buffer)
+{
+ PTRACE_PROVIDER_INFO traceProviderInfo;
+ ULONG i;
+
+ for (i = 0; i < buffer->NumberOfProviders; i++) {
+ traceProviderInfo = &buffer->TraceProviderInfoArray[i];
+ PrintTraceProviderInfo((PBYTE)buffer, traceProviderInfo);
+ }
+}
+
+ULONG
+EnumerateProviders(void)
+{
+ PROVIDER_ENUMERATION_INFO providerEnumerationInfo;
+ PPROVIDER_ENUMERATION_INFO buffer;
+ ULONG size, sts;
+
+ buffer = &providerEnumerationInfo;
+ size = sizeof(providerEnumerationInfo);
+ sts = TdhEnumerateProviders(buffer, &size);
+ do {
+ if (sts == ERROR_INSUFFICIENT_BUFFER) {
+ if (buffer != &providerEnumerationInfo)
+ BufferFree(buffer);
+ buffer = (PPROVIDER_ENUMERATION_INFO)BufferAllocate(size);
+ if (!buffer)
+ return ERROR_NOT_ENOUGH_MEMORY;
+ sts = TdhEnumerateProviders(buffer, &size);
+ }
+ else if (sts == ERROR_SUCCESS) {
+ PrintProviderEnumeration(buffer);
+ break;
+ }
+ else {
+ fprintf(stderr, "TdhEnumerateProviders failed: %s (=%lu)\n",
+ tdherror(sts), sts);
+ break;
+ }
+ } while (1);
+
+ if (buffer != &providerEnumerationInfo)
+ BufferFree(buffer);
+
+ return sts;
+}
+
+ULONG
+EnumerateSessions(void)
+{
+ PEVENT_TRACE_PROPERTIES pSessions[MAX_SESSIONS];
+ PEVENT_TRACE_PROPERTIES pBuffer = NULL;
+ ULONG PropertiesSize = 0;
+ ULONG SessionCount = 0;
+ ULONG BufferSize = 0;
+ ULONG i, sts = ERROR_SUCCESS;
+
+ PropertiesSize = sizeof(EVENT_TRACE_PROPERTIES) +
+ (MAX_SESSION_NAME_LEN * sizeof(WCHAR)) +
+ (MAX_LOGFILE_PATH_LEN * sizeof(WCHAR));
+ BufferSize = PropertiesSize * MAX_SESSIONS;
+
+ pBuffer = (PEVENT_TRACE_PROPERTIES) malloc(BufferSize);
+ if (pBuffer) {
+ ZeroMemory(pBuffer, BufferSize);
+ for (i = 0; i < MAX_SESSIONS; i++) {
+ pSessions[i] = (EVENT_TRACE_PROPERTIES *)((BYTE *)pBuffer +
+ (i * PropertiesSize));
+ pSessions[i]->Wnode.BufferSize = PropertiesSize;
+ pSessions[i]->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
+ pSessions[i]->LogFileNameOffset = sizeof(EVENT_TRACE_PROPERTIES) +
+ (MAX_SESSION_NAME_LEN * sizeof(WCHAR));
+ }
+ } else {
+ fprintf(stderr, "Error allocating memory for properties.\n");
+ return ENOMEM;
+ }
+
+ sts = QueryAllTraces(pSessions, MAX_SESSIONS, &SessionCount);
+ if (sts == ERROR_SUCCESS || sts == ERROR_MORE_DATA) {
+ printf("Requested session count, %ld. Actual session count, %ld.\n\n",
+ MAX_SESSIONS, SessionCount);
+ for (i = 0; i < SessionCount; i++) {
+ LPCSTR l, f;
+ l = (LPCSTR)((char *)pSessions[i] + pSessions[i]->LoggerNameOffset);
+ f = (LPCSTR)((char*)pSessions[i] + pSessions[i]->LogFileNameOffset);
+ printf("Session GUID: %s\nSession ID: %"PRIu64"\n",
+ strguid(&pSessions[i]->Wnode.Guid),
+ pSessions[i]->Wnode.HistoricalContext);
+ if (pSessions[i]->LogFileNameOffset == 0)
+ printf("Realtime session name: %s\n", l);
+ else
+ printf("Log session name: %s\nLog file: %s\n", l, f);
+ if (memcmp(&SystemTraceControlGuid,
+ &pSessions[i]->Wnode.Guid, sizeof(GUID)) == 0) {
+ printf("Enable Flags: ");
+ dumpKernelTraceFlags(stdout, "", ",");
+ printf("\n");
+ }
+ printf("flush timer: %ld\n"
+ "min buffers: %ld\n"
+ "max buffers: %ld\n"
+ "buffers: %ld\n"
+ "buffers written: %ld\n"
+ "buffers lost: %ld\n"
+ "events lost: %ld\n\n",
+ pSessions[i]->FlushTimer,
+ pSessions[i]->MinimumBuffers,
+ pSessions[i]->MaximumBuffers,
+ pSessions[i]->NumberOfBuffers,
+ pSessions[i]->BuffersWritten,
+ pSessions[i]->LogBuffersLost,
+ pSessions[i]->EventsLost);
+ }
+ } else {
+ fprintf(stderr, "Error calling QueryAllTraces: %s (%ld)\n",
+ tdherror(sts), sts);
+ }
+
+ free(pBuffer);
+ return sts;
+}
+
+int
+main(int argc, char **argv)
+{
+ ULONG sts;
+
+ if (argc > 1 && strcmp(argv[1], "-s") == 0)
+ sts = EnumerateSessions();
+ else
+ sts = EnumerateProviders();
+ return sts;
+}
diff --git a/src/pmdas/etw/util.c b/src/pmdas/etw/util.c
new file mode 100644
index 0000000..19869e6
--- /dev/null
+++ b/src/pmdas/etw/util.c
@@ -0,0 +1,190 @@
+/*
+ * Event Trace for Windows utility routines.
+ *
+ * Copyright (c) 2011, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define INITGUID
+#include <pmapi.h>
+#include <impl.h>
+#include <tdh.h>
+#include <evntrace.h>
+#include "util.h"
+
+struct {
+ ULONG flag;
+ char *name;
+} kernelFlags[] = {
+ { EVENT_TRACE_FLAG_PROCESS, "process" },
+ { EVENT_TRACE_FLAG_THREAD, "thread" },
+ { EVENT_TRACE_FLAG_IMAGE_LOAD, "image_load" },
+ { EVENT_TRACE_FLAG_DISK_IO, "disk_io" },
+ { EVENT_TRACE_FLAG_DISK_FILE_IO, "disk_file_io" },
+ { EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS, "memory_page_faults" },
+ { EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS, "memory_hard_faults" },
+ { EVENT_TRACE_FLAG_NETWORK_TCPIP, "network_tcpip" },
+ { EVENT_TRACE_FLAG_REGISTRY, "registry" },
+ { EVENT_TRACE_FLAG_DBGPRINT, "dbgprint" },
+ { EVENT_TRACE_FLAG_PROCESS_COUNTERS, "process_counters" },
+ { EVENT_TRACE_FLAG_CSWITCH, "cswitch" },
+ { EVENT_TRACE_FLAG_DPC, "dpc" },
+ { EVENT_TRACE_FLAG_INTERRUPT, "interrupt" },
+ { EVENT_TRACE_FLAG_SYSTEMCALL, "syscall" },
+ { EVENT_TRACE_FLAG_DISK_IO_INIT, "disk_io_init" },
+ { EVENT_TRACE_FLAG_ALPC, "alpc" },
+ { EVENT_TRACE_FLAG_SPLIT_IO, "split_io" },
+ { EVENT_TRACE_FLAG_DRIVER, "driver" },
+ { EVENT_TRACE_FLAG_PROFILE, "profile" },
+ { EVENT_TRACE_FLAG_FILE_IO, "file_io" },
+ { EVENT_TRACE_FLAG_FILE_IO_INIT, "file_io_init" },
+ { EVENT_TRACE_FLAG_DISPATCHER, "dispatcher" },
+ { EVENT_TRACE_FLAG_VIRTUAL_ALLOC, "virtual_alloc" },
+ { EVENT_TRACE_FLAG_EXTENSION, "extension" },
+ { EVENT_TRACE_FLAG_FORWARD_WMI, "forward_wmi" },
+ { EVENT_TRACE_FLAG_ENABLE_RESERVE, "enable_reserve" },
+};
+
+void
+dumpKernelTraceFlags(FILE *output, const char *prefix, const char *suffix)
+{
+ int i;
+
+ for (i = 0; i < sizeof(kernelFlags)/sizeof(kernelFlags[0]); i++)
+ fprintf(output, "%s%s%s", prefix, kernelFlags[i].name,
+ (i+1 == sizeof(kernelFlags)/sizeof(kernelFlags[0])) ?
+ "\0" : suffix);
+}
+
+ULONG
+kernelTraceFlag(const char *name)
+{
+ int i;
+
+ for (i = 0; i < sizeof(kernelFlags)/sizeof(kernelFlags[0]); i++)
+ if (strcmp(kernelFlags[i].name, name) == 0)
+ return kernelFlags[i].flag;
+ fprintf(stderr, "Unrecognised kernel trace flag: %s\n", name);
+ fprintf(stderr, "List of all known options:\n");
+ dumpKernelTraceFlags(stderr, "\t", "\n");
+ fprintf(stderr, "\n\n");
+ exit(1);
+}
+
+const char *
+eventPropertyFlags(USHORT flags)
+{
+ if (flags & EVENT_HEADER_PROPERTY_XML)
+ return "XML";
+ if (flags & EVENT_HEADER_PROPERTY_FORWARDED_XML)
+ return "forwarded XML";
+ if (flags & EVENT_HEADER_PROPERTY_LEGACY_EVENTLOG)
+ return "legacy WMI MOF";
+ return "none";
+}
+
+const char *
+eventHeaderFlags(USHORT flags)
+{
+ static char buffer[128];
+ char *p = &buffer[0];
+
+ *p = '\0';
+ if (flags & EVENT_HEADER_FLAG_EXTENDED_INFO)
+ strcat(p, "extended info,");
+ if (flags & EVENT_HEADER_FLAG_PRIVATE_SESSION)
+ strcat(p, "private session,");
+ if (flags & EVENT_HEADER_FLAG_STRING_ONLY)
+ strcat(p, "string,");
+ if (flags & EVENT_HEADER_FLAG_TRACE_MESSAGE)
+ strcat(p, "TraceMessage,");
+ if (flags & EVENT_HEADER_FLAG_NO_CPUTIME)
+ strcat(p, "no cputime,");
+ if (flags & EVENT_HEADER_FLAG_32_BIT_HEADER)
+ strcat(p, "32bit,");
+ if (flags & EVENT_HEADER_FLAG_64_BIT_HEADER)
+ strcat(p, "64bit,");
+ if (flags & EVENT_HEADER_FLAG_CLASSIC_HEADER)
+ strcat(p, "classic,");
+ buffer[strlen(buffer)-1] = '\0';
+ return buffer;
+}
+
+const char *
+tdherror(ULONG code)
+{
+ switch (code){
+ case ERROR_ACCESS_DENIED:
+ return "Insufficient privileges for requested operation";
+ case ERROR_ALREADY_EXISTS:
+ return "A sessions with the same name or GUID already exists";
+ case ERROR_BAD_LENGTH:
+ return "Insufficient space or size for a parameter";
+ case ERROR_BAD_PATHNAME:
+ return "Given path parameter is not valid";
+ case ERROR_CANCELLED:
+ return "Consumer cancelled processing via buffer callback";
+ case ERROR_FILE_NOT_FOUND:
+ return "Unable to find the requested file";
+ case ERROR_INSUFFICIENT_BUFFER:
+ return "Size of buffer is too small";
+ case ERROR_INVALID_HANDLE:
+ return "Element of array is not a valid event tracing session handle";
+ case ERROR_INVALID_PARAMETER:
+ return "One or more of the parameters is not valid";
+ case ERROR_INVALID_TIME:
+ return "EndTime is less than StartTime";
+ case ERROR_NOACCESS:
+ return "An exception occurred in one of the event callback routines";
+ case ERROR_NOT_FOUND:
+ return "Requested class or field type not found";
+ case ERROR_NOT_SUPPORTED:
+ return "The requested field type is not supported";
+ case ERROR_OUTOFMEMORY:
+ return "Insufficient memory";
+ case ERROR_WMI_INSTANCE_NOT_FOUND:
+ return "Session from which realtime events to be consumed not running";
+ case ERROR_WMI_ALREADY_ENABLED:
+ return "Handle array contains more than one realtime session handle";
+ case ERROR_SUCCESS:
+ return "Success";
+ }
+ return strerror(code);
+}
+
+const char *
+strguid(LPGUID guidPointer)
+{
+ static char stringBuffer[64];
+
+ snprintf(stringBuffer, sizeof(stringBuffer),
+ "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}",
+ guidPointer->Data1, guidPointer->Data2, guidPointer->Data3,
+ guidPointer->Data4[0], guidPointer->Data4[1],
+ guidPointer->Data4[2], guidPointer->Data4[3],
+ guidPointer->Data4[4], guidPointer->Data4[5],
+ guidPointer->Data4[6], guidPointer->Data4[7]);
+ return stringBuffer;
+}
+
+void *
+BufferAllocate(ULONG size)
+{
+ return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size);
+}
+
+void
+BufferFree(void *buffer)
+{
+ if (buffer)
+ HeapFree(GetProcessHeap(), 0, buffer);
+}
diff --git a/src/pmdas/etw/util.h b/src/pmdas/etw/util.h
new file mode 100644
index 0000000..4c3b32a
--- /dev/null
+++ b/src/pmdas/etw/util.h
@@ -0,0 +1,31 @@
+/*
+ * Trace Data Helper utility routines.
+ *
+ * Copyright (c) 2011, Nathan Scott. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef UTIL_H
+#define UTIL_H
+
+extern void dumpKernelTraceFlags(FILE *, const char *, const char *);
+extern ULONG kernelTraceFlag(const char *);
+
+extern const char *eventHeaderFlags(USHORT);
+extern const char *eventPropertyFlags(USHORT);
+
+extern const char *strguid(LPGUID);
+extern const char *tdherror(ULONG);
+
+extern void *BufferAllocate(ULONG);
+extern void BufferFree(void *);
+
+#endif
diff --git a/src/pmdas/freebsd/GNUmakefile b/src/pmdas/freebsd/GNUmakefile
new file mode 100644
index 0000000..5aa647d
--- /dev/null
+++ b/src/pmdas/freebsd/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2000,2003,2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2007-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2012 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = freebsd
+DOMAIN = FREEBSD
+CMDTARGET = pmdafreebsd
+LIBTARGET = pmda_freebsd.so
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "freebsd 85 dso freebsd_init $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = freebsd.c disk.c netif.c
+
+HFILES = freebsd.h
+
+LSRCFILES = help root_freebsd
+LDIRT = help.dir help.pag help.sed help.tmp domain.h $(IAM).log
+
+LLDLIBS = $(PCP_PMDALIB) -ldevstat
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "freebsd"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) help.dir help.pag
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help help.dir help.pag $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_freebsd $(PCP_VAR_DIR)/pmns/root_freebsd
+else
+build-me:
+install:
+endif
+
+help.dir help.pag : domain.h help
+ $(SED) <domain.h >help.sed -n -e '/#define/s/#define[ ]*\([A-Z][A-Z]*\)[ ]*\([0-9][0-9]*\)/s@\1@\2@/p'
+ $(SED) -f help.sed <help >help.tmp
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_freebsd -v 2 -o help <help.tmp
+ rm -f help.tmp help.tmp
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(OBJECTS): domain.h freebsd.h
diff --git a/src/pmdas/freebsd/disk.c b/src/pmdas/freebsd/disk.c
new file mode 100644
index 0000000..c54de4d
--- /dev/null
+++ b/src/pmdas/freebsd/disk.c
@@ -0,0 +1,208 @@
+/*
+ * FreeBSD Kernel PMDA - disk metrics
+ *
+ * Copyright (c) 2012 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <devstat.h>
+#include "freebsd.h"
+
+struct devinfo devinfo = { 0 };
+struct statinfo statinfo;
+
+void
+refresh_disk_metrics(void)
+{
+ static int init_done = 0;
+ int i;
+ int sts;
+
+ if (!init_done) {
+ sts = devstat_checkversion(NULL);
+ if (sts != 0) {
+ fprintf(stderr, "refresh_disk_metrics: devstat_checkversion: failed! %s\n", devstat_errbuf);
+ exit(1);
+ }
+ statinfo.dinfo = &devinfo;
+ init_done = 1;
+ }
+
+ sts = devstat_getdevs(NULL, &statinfo);
+ if (sts < 0) {
+ fprintf(stderr, "refresh_disk_metrics: devstat_getdevs: %s\n", strerror(errno));
+ exit(1);
+ }
+ else if (sts == 1) {
+ /*
+ * First call, else devstat[] list has changed
+ */
+ struct devstat *dsp;
+ char iname[DEVSTAT_NAME_LEN+6];
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ for (i = 0; i < devinfo.numdevs; i++) {
+ dsp = &devinfo.devices[i];
+ /*
+ * Skip entries that are not interesting ... only include
+ * "da" (direct access) disks at this stage
+ */
+ if (strcmp(dsp->device_name, "da") != 0)
+ continue;
+ snprintf(iname, sizeof(iname), "%s%d", dsp->device_name, dsp->unit_number);
+ sts = pmdaCacheLookupName(indomtab[DISK_INDOM].it_indom, iname, NULL, NULL);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ int j;
+ fprintf(stderr, "refresh_disk_metrics: Warning: duplicate name (%s) in disk indom\n", iname);
+ for (j = 0; j < devinfo.numdevs; j++) {
+ dsp = &devinfo.devices[j];
+ fprintf(stderr, " devinfo[%d]: %s%d\n", j, dsp->device_name, dsp->unit_number);
+ }
+ continue;
+ }
+ else {
+ /* new entry or reactivate an existing one */
+ pmdaCacheStore(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_ADD, iname, (void *)dsp);
+ }
+ }
+ }
+
+}
+
+int
+do_disk_metrics(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ struct devstat *dsp;
+ int sts;
+
+ if (inst != PM_IN_NULL) {
+ /*
+ * per-disk metrics
+ */
+ sts = pmdaCacheLookup(indomtab[DISK_INDOM].it_indom, inst, NULL, (void **)&dsp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ sts = 1;
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 0: /* disk.dev.read */
+ atom->ull = dsp->operations[DEVSTAT_READ];
+ break;
+
+ case 1: /* disk.dev.write */
+ atom->ull = dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 2: /* disk.dev.total */
+ atom->ull = dsp->operations[DEVSTAT_READ] + dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 3: /* disk.dev.read_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_READ];
+ break;
+
+ case 4: /* disk.dev.write_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 5: /* disk.dev.total_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 12: /* disk.dev.blkread */
+ atom->ull = dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_READ] / dsp->block_size;
+ break;
+
+ case 13: /* disk.dev.blkwrite */
+ atom->ull = dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_WRITE] / dsp->block_size;
+ break;
+
+ case 14: /* disk.dev.blktotal */
+ atom->ull = dsp->block_size == 0 ? 0 : (dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE]) / dsp->block_size;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else
+ sts = 0;
+ }
+ else {
+ /*
+ * all-disk summary metrics
+ */
+ int i;
+ atom->ull = 0;
+ sts = 1;
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_WALK_REWIND);
+ while (sts == 1 && (i = pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_WALK_NEXT)) >= 0) {
+ int lsts;
+ lsts = pmdaCacheLookup(indomtab[DISK_INDOM].it_indom, i, NULL, (void **)&dsp);
+ if (lsts == PMDA_CACHE_ACTIVE) {
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 6: /* disk.all.read */
+ atom->ull += dsp->operations[DEVSTAT_READ];
+ break;
+
+ case 7: /* disk.all.write */
+ atom->ull += dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 8: /* disk.all.total */
+ atom->ull += dsp->operations[DEVSTAT_READ] + dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 9: /* disk.all.read_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_READ];
+ break;
+
+ case 10: /* disk.all.write_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 11: /* disk.all.total_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 15: /* disk.all.blkread */
+ atom->ull += dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_READ] / dsp->block_size;
+ break;
+
+ case 16: /* disk.all.blkwrite */
+ atom->ull += dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_WRITE] / dsp->block_size;
+ break;
+
+ case 17: /* disk.all.blktotal */
+ atom->ull += dsp->block_size == 0 ? 0 : (dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE]) / dsp->block_size;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ }
+ if (i < 0 && i != -1)
+ /* not end of indom from cache walk, some other error */
+ sts = i;
+ }
+
+ return sts;
+}
diff --git a/src/pmdas/freebsd/freebsd.c b/src/pmdas/freebsd/freebsd.c
new file mode 100644
index 0000000..3170b20
--- /dev/null
+++ b/src/pmdas/freebsd/freebsd.c
@@ -0,0 +1,991 @@
+/*
+ * FreeBSD Kernel PMDA
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2012 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <vm/vm_param.h>
+
+#include "domain.h"
+#include "freebsd.h"
+
+/* static instances */
+static pmdaInstid loadav_indom[] = {
+ { 1, "1 minute" }, { 5, "5 minute" }, { 15, "15 minute" }
+};
+
+/* instance domains */
+pmdaIndom indomtab[] = {
+ { LOADAV_INDOM, sizeof(loadav_indom)/sizeof(loadav_indom[0]), loadav_indom },
+ { CPU_INDOM, 0, NULL },
+ { DISK_INDOM, 0, NULL },
+ { NETIF_INDOM, 0, NULL },
+};
+static int indomtablen = sizeof(indomtab) / sizeof(indomtab[0]);
+
+#define CL_SYSCTL 0
+#define CL_SPECIAL 1
+#define CL_DISK 2
+#define CL_NETIF 3
+
+/*
+ * All the PCP metrics.
+ *
+ * For sysctl metrics, m_user (the first field) is set the the PCP
+ * name of the metric, and during initialization this is replaced by
+ * a pointer to the corresponding entry in mib[] (based on matching,
+ * or prefix matching (see matchname()) the PCP name here with
+ * m_pcpname[] in the mib[] entries.
+ *
+ * cluster map
+ * CL_SYSCTL simple sysctl() metrics, either one metric per mib, or
+ * one struct per mib
+ * CL_SPECIAL trickier sysctl() metrics involving synthesis or arithmetic
+ * or other methods
+ * CL_DISK disk metrics
+ * CL_NETIF network interface metrics
+ */
+
+static pmdaMetric metrictab[] = {
+ { (void *)"hinv.ncpu",
+ { PMDA_PMID(CL_SYSCTL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.physmem",
+ { PMDA_PMID(CL_SYSCTL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"kernel.all.load",
+ { PMDA_PMID(CL_SYSCTL,2), PM_TYPE_FLOAT, LOADAV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"kernel.all.cpu.user",
+ { PMDA_PMID(CL_SYSCTL,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.nice",
+ { PMDA_PMID(CL_SYSCTL,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.sys",
+ { PMDA_PMID(CL_SYSCTL,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.intr",
+ { PMDA_PMID(CL_SYSCTL,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.idle",
+ { PMDA_PMID(CL_SYSCTL,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.user",
+ { PMDA_PMID(CL_SYSCTL,8), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.nice",
+ { PMDA_PMID(CL_SYSCTL,9), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.sys",
+ { PMDA_PMID(CL_SYSCTL,10), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.intr",
+ { PMDA_PMID(CL_SYSCTL,11), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.idle",
+ { PMDA_PMID(CL_SYSCTL,12), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.hz",
+ { PMDA_PMID(CL_SYSCTL,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) } },
+ { (void *)"hinv.cpu.vendor",
+ { PMDA_PMID(CL_SYSCTL,15), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.cpu.model",
+ { PMDA_PMID(CL_SYSCTL,16), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.cpu.arch",
+ { PMDA_PMID(CL_SYSCTL,17), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"swap.pagesin",
+ { PMDA_PMID(CL_SYSCTL,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.pagesout",
+ { PMDA_PMID(CL_SYSCTL,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.in",
+ { PMDA_PMID(CL_SYSCTL,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.in",
+ { PMDA_PMID(CL_SYSCTL,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.pswitch",
+ { PMDA_PMID(CL_SYSCTL,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.syscall",
+ { PMDA_PMID(CL_SYSCTL,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.intr",
+ { PMDA_PMID(CL_SYSCTL,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.length",
+ { PMDA_PMID(CL_SYSCTL,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"swap.used",
+ { PMDA_PMID(CL_SYSCTL,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ { NULL, /* hinv.ndisk */
+ { PMDA_PMID(CL_SPECIAL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ /*
+ * swap.free is the difference between sysctl variables vm.swap_total
+ * and vm.swap_reserved, so it is important they are kept together
+ * in mib[] below
+ */
+ { (void *)"swap.length", /* swap.free */
+ { PMDA_PMID(CL_SPECIAL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* hinv.pagesize */
+ { PMDA_PMID(CL_SPECIAL,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"mem.util.all",
+ { PMDA_PMID(CL_SPECIAL,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ /*
+ * mem.util.used is computed from several of the vm.stats.vm.v_*_count
+ * sysctl metrics, so it is important they are kept together in mib[]
+ * below
+ */
+ { (void *)"mem.util.all", /* mem.util.used */
+ { PMDA_PMID(CL_SPECIAL,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.free",
+ { PMDA_PMID(CL_SPECIAL,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.bufmem",
+ { PMDA_PMID(CL_SPECIAL,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.cached",
+ { PMDA_PMID(CL_SPECIAL,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.wired",
+ { PMDA_PMID(CL_SPECIAL,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.active",
+ { PMDA_PMID(CL_SPECIAL,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.inactive",
+ { PMDA_PMID(CL_SPECIAL,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ /*
+ * mem.util.avail is computed from several of the vm.stats.vm.v_*_count
+ * sysctl metrics, so it is important they are kept together in mib[]
+ * below
+ */
+ { (void *)"mem.util.all", /* mem.util.avail */
+ { PMDA_PMID(CL_SPECIAL,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+ { NULL, /* disk.dev.read */
+ { PMDA_PMID(CL_DISK,0), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.write */
+ { PMDA_PMID(CL_DISK,1), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.total */
+ { PMDA_PMID(CL_DISK,2), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.read_bytes */
+ { PMDA_PMID(CL_DISK,3), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.write_bytes */
+ { PMDA_PMID(CL_DISK,4), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.total_bytes */
+ { PMDA_PMID(CL_DISK,5), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.read */
+ { PMDA_PMID(CL_DISK,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.write */
+ { PMDA_PMID(CL_DISK,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.total */
+ { PMDA_PMID(CL_DISK,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.read_bytes */
+ { PMDA_PMID(CL_DISK,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.write_bytes */
+ { PMDA_PMID(CL_DISK,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.total_bytes */
+ { PMDA_PMID(CL_DISK,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.blkread */
+ { PMDA_PMID(CL_DISK,12), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.blkwrite */
+ { PMDA_PMID(CL_DISK,13), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.blktotal */
+ { PMDA_PMID(CL_DISK,14), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blkread */
+ { PMDA_PMID(CL_DISK,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blkwrite */
+ { PMDA_PMID(CL_DISK,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blktotal */
+ { PMDA_PMID(CL_DISK,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+ { NULL, /* network.interface.mtu */
+ { PMDA_PMID(CL_NETIF,0), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.up */
+ { PMDA_PMID(CL_NETIF,1), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.baudrate */
+ { PMDA_PMID(CL_NETIF,2), PM_TYPE_U64, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.in.bytes */
+ { PMDA_PMID(CL_NETIF,3), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.in.packets */
+ { PMDA_PMID(CL_NETIF,4), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.mcasts */
+ { PMDA_PMID(CL_NETIF,5), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.errors */
+ { PMDA_PMID(CL_NETIF,6), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.drops */
+ { PMDA_PMID(CL_NETIF,7), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.bytes */
+ { PMDA_PMID(CL_NETIF,8), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.out.packets */
+ { PMDA_PMID(CL_NETIF,9), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.mcasts */
+ { PMDA_PMID(CL_NETIF,10), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.errors */
+ { PMDA_PMID(CL_NETIF,11), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.collisions */
+ { PMDA_PMID(CL_NETIF,12), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.bytes */
+ { PMDA_PMID(CL_NETIF,13), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.total.packets */
+ { PMDA_PMID(CL_NETIF,14), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.mcasts */
+ { PMDA_PMID(CL_NETIF,15), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.errors */
+ { PMDA_PMID(CL_NETIF,16), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+};
+static int metrictablen = sizeof(metrictab) / sizeof(metrictab[0]);
+
+/*
+ * mapping between PCP metrics and sysctl metrics
+ *
+ * initialization note ... all elments after m_pcpname and m_name are OK
+ * to be 0 or NULL
+ */
+typedef struct {
+ const char *m_pcpname; /* PCP metric name or prefix (see matchname() */
+ const char *m_name; /* sysctl metric name */
+ size_t m_miblen; /* number of elements in m_mib[] */
+ int *m_mib;
+ int m_fetched; /* 1 if m_data is current and valid */
+ size_t m_datalen; /* number of bytes in m_data[] */
+ void *m_data; /* value from sysctl */
+} mib_t;
+static mib_t map[] = {
+ { "hinv.ncpu", "hw.ncpu" },
+ { "hinv.physmem", "hw.physmem" },
+ { "hinv.cpu.vendor", "hw.machine" },
+ { "hinv.cpu.model", "hw.model" },
+ { "hinv.cpu.arch", "hw.machine_arch" },
+ { "kernel.all.load", "vm.loadavg" },
+ { "kernel.all.hz", "kern.clockrate" },
+ { "kernel.all.cpu.*", "kern.cp_time" },
+ { "kernel.percpu.cpu.*", "kern.cp_times" },
+ { "swap.pagesin", "vm.stats.vm.v_swappgsin" },
+ { "swap.pagesout", "vm.stats.vm.v_swappgsout" },
+ { "swap.in", "vm.stats.vm.v_swapin" },
+ { "swap.out", "vm.stats.vm.v_swapout" },
+ { "kernel.all.pswitch", "vm.stats.sys.v_swtch" },
+ { "kernel.all.syscall", "vm.stats.sys.v_syscall" },
+ { "kernel.all.intr", "vm.stats.sys.v_intr" },
+ { "mem.util.bufmem", "vfs.bufspace" },
+/*
+ * DO NOT MOVE next 2 entries ... see note above for swap.free
+ */
+ { "swap.length", "vm.swap_total" },
+ { "swap.used", "vm.swap_reserved" },
+/*
+ * DO NOT MOVE next 6 entries ... see note above for mem.util.avail
+ * and mem.util.used
+ */
+ { "mem.util.all", "vm.stats.vm.v_page_count" },
+ { "mem.util.free", "vm.stats.vm.v_free_count" },
+ { "mem.util.cached", "vm.stats.vm.v_cache_count" },
+ { "mem.util.wired", "vm.stats.vm.v_wire_count" },
+ { "mem.util.active", "vm.stats.vm.v_active_count" },
+ { "mem.util.inactive", "vm.stats.vm.v_inactive_count" },
+
+};
+static int maplen = sizeof(map) / sizeof(map[0]);
+static mib_t bad_mib = { "bad.mib", "bad.mib", 0, NULL, 0, 0, NULL };
+
+static char *username;
+static int isDSO = 1; /* =0 I am a daemon */
+static int cpuhz; /* frequency for CPU time metrics */
+static int ncpu; /* number of cpus in kern.cp_times data */
+static int pagesize; /* vm page size */
+
+/*
+ * Fetch values from sysctl()
+ *
+ * Expect the result to be xpect bytes to match the PCP data size or
+ * anticipated structure size, unless xpect is ==0 in which case the
+ * size test is skipped.
+ */
+static int
+do_sysctl(mib_t *mp, size_t xpect)
+{
+ /*
+ * Note zero trip if mp->m_data and mp->datalen are already valid
+ * and current
+ */
+ for ( ; mp->m_fetched == 0; ) {
+ int sts;
+ sts = sysctl(mp->m_mib, (u_int)mp->m_miblen, mp->m_data, &mp->m_datalen, NULL, 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "sysctl(%s%s) -> %d (datalen=%d)\n", mp->m_name, mp->m_data == NULL ? " firstcall" : "", sts, (int)mp->m_datalen);
+ }
+#endif
+ if (sts == 0 && mp->m_data != NULL) {
+ mp->m_fetched = 1;
+ break;
+ }
+ if ((sts == -1 && errno == ENOMEM) || (sts == 0 && mp->m_data == NULL)) {
+ /* first call for this one, or data changed size */
+ mp->m_data = realloc(mp->m_data, mp->m_datalen);
+ if (mp->m_data == NULL) {
+ fprintf(stderr, "Error: %s: buffer alloc failed for sysctl metric \"%s\"\n", mp->m_pcpname, mp->m_name);
+ __pmNoMem("do_sysctl", mp->m_datalen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+ else
+ return -errno;
+ }
+ if (xpect > 0 && mp->m_datalen != xpect) {
+ fprintf(stderr, "Error: %s: sysctl(%s) datalen=%d not %d!\n", mp->m_pcpname, mp->m_name, (int)mp->m_datalen, (int)xpect);
+ return 0;
+ }
+ return mp->m_datalen;
+}
+
+/*
+ * kernel memory reader setup
+ */
+struct nlist symbols[] = {
+ { .n_name = "_ifnet" },
+ { .n_name = NULL }
+};
+kvm_t *kvmp;
+
+static void
+kmemread_init(void)
+{
+ int sts;
+ int i;
+ char errmsg[_POSIX2_LINE_MAX];
+
+ /*
+ * If we're running as a daemon PMDA, assume we're setgid kmem,
+ * so we can open /dev/kmem, and downgrade privileges after the
+ * kvm_open().
+ * For a DSO PMDA, we have to assume pmcd has the required
+ * privileges and don't dink with them.
+ */
+ kvmp = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errmsg);
+ if (!isDSO)
+ setgid(getgid());
+ if (kvmp == NULL) {
+ fprintf(stderr, "kmemread_init: kvm_openfiles failed: %s\n", errmsg);
+ return;
+ }
+
+ sts = kvm_nlist(kvmp, symbols);
+ if (sts < 0) {
+ fprintf(stderr, "kmemread_init: kvm_nlist failed: %s\n", pmErrStr(-errno));
+ for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++)
+ symbols[i].n_value = 0;
+ return;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++) {
+ fprintf(stderr, "Info: kernel symbol %s found at 0x%08lx\n", symbols[i].n_name, symbols[i].n_value);
+ }
+ }
+#endif
+
+}
+
+/*
+ * Callback provided to pmdaFetch ... come here once per metric-instance
+ * pair in each pmFetch().
+ */
+static int
+freebsd_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ int sts = PM_ERR_PMID;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ mib_t *mp;
+
+ mp = (mib_t *)mdesc->m_user;
+ if (idp->cluster == CL_SYSCTL) {
+ /* sysctl() simple cases */
+ switch (idp->item) {
+ /* 32-bit integer values */
+ case 0: /* hinv.ncpu */
+ case 18: /* swap.pagesin */
+ case 19: /* swap.pagesout */
+ case 20: /* swap.in */
+ case 21: /* swap.out */
+ case 22: /* kernel.all.pswitch */
+ case 23: /* kernel.all.syscall */
+ case 24: /* kernel.all.intr */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data);
+ sts = 1;
+ }
+ break;
+
+ /* 64-bit integer values */
+ case 1: /* hinv.physmem */
+ case 25: /* swap.length */
+ case 26: /* swap.used */
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull = *((__uint64_t *)mp->m_data);
+ sts = 1;
+ }
+ break;
+
+ /* string values */
+ case 15: /* hinv.cpu.vendor */
+ case 16: /* hinv.cpu.model */
+ case 17: /* hinv.cpu.arch */
+ sts = do_sysctl(mp, (size_t)0);
+ if (sts > 0) {
+ atom->cp = (char *)mp->m_data;
+ sts = 1;
+ }
+ break;
+
+ /* structs and aggregates */
+ case 2: /* kernel.all.load */
+ sts = do_sysctl(mp, 3*sizeof(atom->d));
+ if (sts > 0) {
+ int i;
+ struct loadavg *lp = (struct loadavg *)mp->m_data;
+ if (inst == 1)
+ i = 0;
+ else if (inst == 5)
+ i = 1;
+ else if (inst == 15)
+ i = 2;
+ else
+ return PM_ERR_INST;
+ atom->f = (float)((double)lp->ldavg[i] / lp->fscale);
+ sts = 1;
+ }
+ break;
+
+ case 3: /* kernel.all.cpu.user */
+ case 4: /* kernel.all.cpu.nice */
+ case 5: /* kernel.all.cpu.sys */
+ case 6: /* kernel.all.cpu.intr */
+ case 7: /* kernel.all.cpu.idle */
+ sts = do_sysctl(mp, CPUSTATES*sizeof(atom->ull));
+ if (sts > 0) {
+ /*
+ * PMID assignment is important in the "-3" below so
+ * that metrics map to consecutive elements of the
+ * returned value in the order defined for CPUSTATES,
+ * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
+ * CP_IDLE
+ */
+ atom->ull = 1000*((__uint64_t *)mp->m_data)[idp->item-3]/cpuhz;
+ sts = 1;
+ }
+ break;
+
+ case 8: /* kernel.percpu.cpu.user */
+ case 9: /* kernel.percpu.cpu.nice */
+ case 10: /* kernel.percpu.cpu.sys */
+ case 11: /* kernel.percpu.cpu.intr */
+ case 12: /* kernel.percpu.cpu.idle */
+ sts = do_sysctl(mp, ncpu*CPUSTATES*sizeof(atom->ull));
+ if (sts > 0) {
+ /*
+ * PMID assignment is important in the "-8" below so
+ * that metrics map to consecutive elements of the
+ * returned value in the order defined for CPUSTATES,
+ * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
+ * CP_IDLE, and then there is one such set for each
+ * CPU up to the maximum number of CPUs installed in
+ * the system.
+ */
+ atom->ull = 1000*((__uint64_t *)mp->m_data)[inst * CPUSTATES + idp->item-8]/cpuhz;
+ sts = 1;
+ }
+ break;
+
+ case 13: /* kernel.all.hz */
+ sts = do_sysctl(mp, sizeof(struct clockinfo));
+ if (sts > 0) {
+ struct clockinfo *cp = (struct clockinfo *)mp->m_data;
+ atom->ul = cp->hz;
+ sts = 1;
+ }
+ break;
+
+ }
+ }
+ else if (idp->cluster == CL_SPECIAL) {
+ /* special cases */
+ switch (idp->item) {
+ case 0: /* hinv.ndisk */
+ refresh_disk_metrics();
+ atom->ul = pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_SIZE_ACTIVE);
+ sts = 1;
+ break;
+
+ case 1: /* swap.free */
+ /* first vm.swap_total */
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull = *((__uint64_t *)mp->m_data);
+ /*
+ * now subtract vm.swap_reserved ... assumes consecutive
+ * mib[] entries
+ */
+ mp++;
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull -= *((__uint64_t *)mp->m_data);
+ sts = 1;
+ }
+ }
+ break;
+
+ case 3: /* hinv.pagesize */
+ atom->ul = pagesize;
+ sts = 1;
+ break;
+
+ case 4: /* mem.util.all */
+ case 6: /* mem.util.free */
+ case 8: /* mem.util.cached */
+ case 9: /* mem.util.wired */
+ case 10: /* mem.util.active */
+ case 11: /* mem.util.inactive */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data) * (pagesize / 1024);
+ sts = 1;
+ }
+ break;
+
+ case 7: /* mem.util.bufmem */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data) / 1024;
+ sts = 1;
+ }
+ break;
+
+ case 5: /* mem.util.used */
+ /*
+ * mp-> v_page_count entry in mib[]
+ * assuming consecutive mib[] entries, we want
+ * v_page_count mp[0] - v_free_count mp[1] -
+ * v_cache_count mp[2] - v_inactive_count mp[5]
+ */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data);
+ sts = do_sysctl(&mp[1], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[1].m_data);
+ sts = do_sysctl(&mp[2], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[2].m_data);
+ sts = do_sysctl(&mp[5], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[5].m_data);
+ atom->ul *= (pagesize / 1024);
+ sts = 1;
+ }
+ }
+ }
+ }
+ break;
+
+ case 12: /* mem.util.avail */
+ /*
+ * mp-> v_page_count entry in mib[]
+ * assuming consecutive mib[] entries, we want
+ * v_free_count mp[1] + v_cache_count mp[2] +
+ * v_inactive_count mp[5]
+ */
+ sts = do_sysctl(&mp[1], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp[1].m_data);
+ sts = do_sysctl(&mp[2], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul += *((__uint32_t *)mp[2].m_data);
+ sts = do_sysctl(&mp[5], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul += *((__uint32_t *)mp[5].m_data);
+ atom->ul *= (pagesize / 1024);
+ sts = 1;
+ }
+ }
+ }
+ break;
+
+ }
+ }
+ else if (idp->cluster == CL_DISK) {
+ /* disk metrics */
+ sts = do_disk_metrics(mdesc, inst, atom);
+ }
+ else if (idp->cluster == CL_NETIF) {
+ /* network interface metrics */
+ sts = do_netif_metrics(mdesc, inst, atom);
+ }
+
+ return sts;
+}
+
+/*
+ * wrapper for pmdaFetch ... force value caches to be reloaded if needed,
+ * then do the fetch
+ */
+static int
+freebsd_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i;
+ int done_disk = 0;
+ int done_netif = 0;
+
+ for (i = 0; i < maplen; i++) {
+ map[i].m_fetched = 0;
+ }
+
+ /*
+ * pre-fetch all metrics if needed, and update instance domains if
+ * they have changed
+ */
+ for (i = 0; !done_disk && !done_netif && i < numpmid; i++) {
+ if (pmid_cluster(pmidlist[i]) == CL_DISK) {
+ refresh_disk_metrics();
+ done_disk = 1;
+ }
+ else if (pmid_cluster(pmidlist[i]) == CL_NETIF) {
+ refresh_netif_metrics();
+ done_netif = 1;
+ }
+ }
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * wrapper for pmdaInstance ... refresh required instance domain first
+ */
+static int
+freebsd_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ /*
+ * indomtab[] instance names and ids are not used for some indoms,
+ * ensure pmdaCache is current
+ */
+ if (indom == indomtab[DISK_INDOM].it_indom)
+ refresh_disk_metrics();
+ if (indom == indomtab[NETIF_INDOM].it_indom)
+ refresh_netif_metrics();
+
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+/*
+ * PCP metric name matching for linking metrictab[] entries to mib[]
+ * entries.
+ *
+ * Return 1 if prefix[] is equal to, or a prefix of name[]
+ *
+ * prefix[] of the form "a.bc" or "a.bc*" matches a name[] like "a.bc"
+ * or "a.bcanything", to improve readability of the initializers in
+ * mib[], and asterisk is a "match all" special case, so "a.b.*" matches
+ * "a.b.anything"
+ */
+static int
+matchname(const char *prefix, const char *name)
+{
+ while (*prefix != '\0' && *name != '\0' && *prefix == *name) {
+ prefix++;
+ name++;
+ }
+ if (*prefix == '\0' || *prefix == '*')
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ *
+ * Do mapping from sysclt(3) names to mibs.
+ * Collect some global constants.
+ * Build the system-specific, but not dynamic, instance domains,
+ * e.g. CPU_INDOM.
+ * Initialize the kernel memory reader.
+ */
+void
+freebsd_init(pmdaInterface *dp)
+{
+ int i;
+ int m;
+ int sts;
+ struct clockinfo clockrates;
+ size_t sz;
+ int mib[CTL_MAXNAME]; /* enough for longest mib key */
+ char iname[16]; /* enough for cpuNN.. */
+
+ if (isDSO) {
+ char mypath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "freebsd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "freebsd DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.fetch = freebsd_fetch;
+ dp->version.four.instance = freebsd_instance;
+
+ pmdaSetFetchCallBack(dp, freebsd_fetchCallBack);
+
+ pmdaInit(dp, indomtab, indomtablen, metrictab, metrictablen);
+
+ /*
+ * Link metrictab[] entries via m_user to map[] entries based on
+ * matching sysctl(3) name
+ *
+ * also translate the sysctl(3) name to a mib
+ */
+ for (m = 0; m < metrictablen; m++) {
+ if (metrictab[m].m_user == NULL) {
+ /* not using sysctl(3) */
+ continue;
+ }
+ for (i = 0; i < maplen; i++) {
+ if (matchname(map[i].m_pcpname, (char *)metrictab[m].m_user)) {
+ if (map[i].m_mib == NULL) {
+ /*
+ * multiple metrictab[] entries may point to the same
+ * mib[] entry, but this is the first time for this
+ * mib[] entry ...
+ */
+ map[i].m_miblen = sizeof(mib);
+ sts = sysctlnametomib(map[i].m_name, mib, &map[i].m_miblen);
+ if (sts == 0) {
+ map[i].m_mib = (int *)malloc(map[i].m_miblen*sizeof(map[i].m_mib[0]));
+ if (map[i].m_mib == NULL) {
+ fprintf(stderr, "Error: %s (%s): failed mib alloc for sysctl metric \"%s\"\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
+ __pmNoMem("freebsd_init: mib", map[i].m_miblen*sizeof(map[i].m_mib[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy(map[i].m_mib, mib, map[i].m_miblen*sizeof(map[i].m_mib[0]));
+ }
+ else {
+ fprintf(stderr, "Error: %s (%s): failed sysctlnametomib(\"%s\", ...): %s\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name, pmErrStr(-errno));
+ metrictab[m].m_user = (void *)&bad_mib;
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ int p;
+ fprintf(stderr, "Info: %s (%s): sysctl metric \"%s\" -> ", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
+ for (p = 0; p < map[i].m_miblen; p++) {
+ if (p > 0) fputc('.', stderr);
+ fprintf(stderr, "%d", map[i].m_mib[p]);
+ }
+ fputc('\n', stderr);
+ }
+#endif
+ metrictab[m].m_user = (void *)&map[i];
+ break;
+ }
+ }
+ if (i == maplen) {
+ fprintf(stderr, "Error: %s (%s): cannot match name in sysctl map[]\n", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid));
+ metrictab[m].m_user = (void *)&bad_mib;
+ }
+ }
+
+ /*
+ * Collect some global constants needed later ...
+ */
+ sz = sizeof(clockrates);
+ sts = sysctlbyname("kern.clockrate", &clockrates, &sz, NULL, 0);
+ if (sts < 0) {
+ fprintf(stderr, "Fatal Error: sysctlbyname(\"kern.clockrate\", ...) failed: %s\n", pmErrStr(-errno));
+ exit(1);
+ }
+ cpuhz = clockrates.stathz;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: CPU time \"hz\" = %d\n", cpuhz);
+#endif
+
+ sts = sysctlbyname("kern.cp_times", NULL, &sz, NULL, 0);
+ if (sts < 0) {
+ fprintf(stderr, "Fatal Error: sysctlbyname(\"kern.cp_times\", ...) failed: %s\n", pmErrStr(-errno));
+ exit(1);
+ }
+ /*
+ * see note below when fetching kernel.percpu.cpu.* metrics to
+ * explain this
+ */
+ ncpu = sz / (CPUSTATES * sizeof(__uint64_t));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: ncpu = %d\n", ncpu);
+#endif
+
+ sz = sizeof(pagesize);
+ sts = sysctlbyname("hw.pagesize", &pagesize, &sz, NULL, 0);
+ if (sts < 0) {
+ fprintf(stderr, "Fatal Error: sysctlbyname(\"hw.pagesize\", ...) failed: %s\n", pmErrStr(-errno));
+ exit(1);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: VM pagesize = %d\n", pagesize);
+#endif
+
+ /*
+ * Build some instance domains ...
+ */
+ indomtab[CPU_INDOM].it_numinst = ncpu;
+ indomtab[CPU_INDOM].it_set = (pmdaInstid *)malloc(ncpu * sizeof(pmdaInstid));
+ if (indomtab[CPU_INDOM].it_set == NULL) {
+ __pmNoMem("freebsd_init: CPU_INDOM it_set", ncpu * sizeof(pmdaInstid), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (i = 0; i < ncpu; i++) {
+ indomtab[CPU_INDOM].it_set[i].i_inst = i;
+ snprintf(iname, sizeof(iname), "cpu%d", i);
+ indomtab[CPU_INDOM].it_set[i].i_name = strdup(iname);
+ if (indomtab[CPU_INDOM].it_set[i].i_name == NULL) {
+ __pmNoMem("freebsd_init: CPU_INDOM strdup iname", strlen(iname), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+
+ kmemread_init();
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -U username user account to run under (default \"pcp\")\n"
+ "\nExactly one of the following options may appear:\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ stderr);
+ exit(1);
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int c, err = 0;
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char mypath[MAXPATHLEN];
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "freebsd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmProgname, FREEBSD,
+ "freebsd.log", mypath);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:i:l:pu:U:6:?", &dispatch, &err)) != EOF) {
+ switch(c) {
+ case 'U':
+ username = optarg;
+ break;
+ default:
+ err++;
+ }
+ }
+ if (err)
+ usage();
+
+ pmdaOpenLog(&dispatch);
+ freebsd_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/freebsd/freebsd.h b/src/pmdas/freebsd/freebsd.h
new file mode 100644
index 0000000..c6b9b72
--- /dev/null
+++ b/src/pmdas/freebsd/freebsd.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright (c) 2012 Ken McDonell All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * instance domains control
+ */
+#define LOADAV_INDOM 0
+#define CPU_INDOM 1
+#define DISK_INDOM 2
+#define NETIF_INDOM 3
+extern pmdaIndom indomtab[];
+
+extern void refresh_disk_metrics(void);
+extern int do_disk_metrics(pmdaMetric *, unsigned int, pmAtomValue *);
+
+extern void refresh_netif_metrics(void);
+extern int do_netif_metrics(pmdaMetric *, unsigned int, pmAtomValue *);
+
+/*
+ * kernel memory reader pieces
+ */
+#include <kvm.h>
+#include <nlist.h>
+
+extern kvm_t *kvmp;
+
+#define KERN_IFNET 0
+extern struct nlist symbols[];
diff --git a/src/pmdas/freebsd/help b/src/pmdas/freebsd/help
new file mode 100644
index 0000000..5bb566a
--- /dev/null
+++ b/src/pmdas/freebsd/help
@@ -0,0 +1,95 @@
+@ FREEBSD.0 Instance domain for load average
+Universally 3 instances, "1 minute" (1), "5 minute" (5) and
+"15 minute" (15).
+
+@ FREEBSD.1 CPU Instance domain for kernel.percpu metrics
+One instance for each physical CPU.
+
+@ FREEBSD.2 DISK Instance domain for disk.dev metrics
+One instance for each physical "direct access" device.
+
+@ kernel.all.hz system hz rate
+Microseconds per "hz" tick.
+@ kernel.all.load 1, 5 and 15 minute load average
+@ kernel.all.pswitch count of context switches
+@ kernel.all.syscall count of system calls
+@ kernel.all.intr count of interrupts serviced
+@ kernel.all.cpu.user total user CPU time for all CPUs
+@ kernel.all.cpu.nice total nice user CPU time for all CPUs
+@ kernel.all.cpu.sys total sys CPU time for all CPUs
+@ kernel.all.cpu.intr total interrupt CPU time for all CPUs
+@ kernel.all.cpu.idle total idle CPU time for all CPUs
+@ kernel.percpu.cpu.user user CPU time for each CPU
+@ kernel.percpu.cpu.nice nice user CPU time for each CPU
+@ kernel.percpu.cpu.sys sys CPU time for each CPU
+@ kernel.percpu.cpu.intr interrupt CPU time for each CPU
+@ kernel.percpu.cpu.idle idle CPU time for each CPU
+
+@ hinv.ncpu number of CPUs in the system
+@ hinv.ndisk number of disks in the system
+@ hinv.physmem total system memory
+@ hinv.pagesize kernel page size
+@ hinv.cpu.vendor system's CPU vendor
+@ hinv.cpu.model system's CPU model
+@ hinv.cpu.arch system's machine dependent CPU architecture type
+
+@ swap.length total swap space size
+@ swap.used reserved (or allocated) swap space
+@ swap.free available swap space
+
+@ swap.pagesin pages read from external storage to service page faults
+@ swap.pagesout dirty pages written to swap devices
+When the rate of page writes is non-zero, this is the most useful
+indication severe demand for physical memory.
+@ swap.in number of swap in operations
+@ swap.out number of swap out operations
+
+@ disk.dev.read Count of read operations per disk
+@ disk.dev.write Count of write operations per disk
+@ disk.dev.total Count of read or write operations (IOPs) per disk
+@ disk.dev.read_bytes Count of bytes read from each disk
+@ disk.dev.write_bytes Count of bytes written to each disk
+@ disk.dev.total_bytes Count of bytes transferred to or from each disk
+@ disk.dev.blkread Count of blocks read from each disk
+@ disk.dev.blkwrite Count of blocks written to each disk
+@ disk.dev.blktotal Count of blocks transferred to or from each disk
+@ disk.all.read Count of read operations across all disks
+@ disk.all.write Count of write operations across all disks
+@ disk.all.total Count of read or write operations (IOPs) across all disks
+@ disk.all.read_bytes Count of bytes read from all disks
+@ disk.all.write_bytes Count of bytes written to all disks
+@ disk.all.total_bytes Count of bytes transferred to or from all disks
+@ disk.all.blkread Count of blocks read from all disks
+@ disk.all.blkwrite Count of blocks written to all disks
+@ disk.all.blktotal Count of blocks transferred to or from all disks
+
+@ mem.util.all Total memory managed by the system
+@ mem.util.used Memory that is actively in use
+Equals "all" minus "free" minus "inactive" minus "cached".
+@ mem.util.free Unallocated and free memory
+@ mem.util.bufmem Memory associated with active buffer I/O
+@ mem.util.cached Cached memory
+Unmodified (clean and cached) pages from files in filesystems.
+@ mem.util.wired Wired or pinned memory that cannot be paged out
+@ mem.util.active Recently accessed memory
+@ mem.util.inactive Memory that is in use, but has not be accessed recently
+@ mem.util.avail Available memory
+Free plus inactive plus cached.
+
+@ network.interface.mtu Maximum Transfer Unit for each network interface
+@ network.interface.up "UP" state for each network interface
+@ network.interface.baudrate Data baudrate for each network interface
+@ network.interface.in.bytes Bytes received on each network interface
+@ network.interface.in.packets Packets received on each network interface
+@ network.interface.in.mcasts Multicast packets received on each network interface
+@ network.interface.in.errors Input errors on each network interface
+@ network.interface.in.drops Dropped packets on each network interface
+@ network.interface.out.bytes Bytes transmitted on each network interface
+@ network.interface.out.packets Packets transmitted on each network interface
+@ network.interface.out.mcasts Multicast packets transmitted on each network interface
+@ network.interface.out.errors Output errors on each network interface
+@ network.interface.out.collisions Output collisions on each network interface
+@ network.interface.total.bytes Bytes received or transmitted on each network interface
+@ network.interface.total.packets Packets received or transmitted on each network interface
+@ network.interface.total.mcasts Multicast packets received or transmitted on each network interface
+@ network.interface.total.errors Input or output errors on each network interface
diff --git a/src/pmdas/freebsd/netif.c b/src/pmdas/freebsd/netif.c
new file mode 100644
index 0000000..c19d7f3
--- /dev/null
+++ b/src/pmdas/freebsd/netif.c
@@ -0,0 +1,225 @@
+/*
+ * FreeBSD Kernel PMDA - network interface metrics
+ *
+ * Copyright (c) 2012 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+
+#include "freebsd.h"
+
+#define WARN_INIT 1
+#define WARN_READ_HEAD 2
+
+void
+refresh_netif_metrics(void)
+{
+ int i;
+ int sts;
+ unsigned long kaddr;
+ struct ifnethead ifnethead;
+ struct ifnet ifnet;
+ struct ifnet *ifp;
+ static int warn = 0; /* warn once control */
+
+ /*
+ * Not sure that the order of chained netif structs is invariant,
+ * especially if interfaces are added to the configuration after
+ * initial system boot ... so mark all the instances as inactive
+ * and re-match based on the interface name
+ */
+ pmdaCacheOp(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+
+ kaddr = symbols[KERN_IFNET].n_value;
+ if (kvmp == NULL || kaddr == 0) {
+ /* no network interface metrics for us today ... */
+ if ((warn & WARN_INIT) == 0) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: cannot get any network interface metrics\n");
+ warn |= WARN_INIT;
+ }
+ return;
+ }
+
+ /*
+ * Kernel data structures for the linked list of network interface
+ * information.
+ *
+ * _ifnet -> struct ifnethead {
+ * struct ifnet *tqh_first;
+ * struct ifnet **tqh_last;
+ * ...
+ * }
+ *
+ * and within an ifnet struct (declared in <net/if_var.h>) we find
+ * the linked list maintained in if_link, the external interface
+ * name in if_xname[] and if_data which is a nested if_data stuct
+ * (declared in <net/if.h>) that contains many of the goodies we're
+ * after, e.g. u_char ifi_type, u_long ifi_mtu, u_long ifi_baudrate,
+ * u_long ifi_ipackets, u_long ifi_opackets, u_long ifi_ibytes,
+ * u_long ifi_obytes, etc.
+ */
+ if (kvm_read(kvmp, kaddr, (char *)&ifnethead, sizeof(ifnethead)) != sizeof(ifnethead)) {
+ if ((warn & WARN_READ_HEAD) == 0) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: kvm_read: ifnethead: %s\n", kvm_geterr(kvmp));
+ warn |= WARN_READ_HEAD;
+ }
+ return;
+ }
+
+ for (i = 0; ; i++) {
+ if (i == 0)
+ kaddr = (unsigned long)TAILQ_FIRST(&ifnethead);
+ else
+ kaddr = (unsigned long)TAILQ_NEXT(&ifnet, if_link);
+
+ if (kaddr == 0)
+ break;
+
+ if (kvm_read(kvmp, kaddr, (char *)&ifnet, sizeof(ifnet)) != sizeof(ifnet)) {
+ fprintf(stderr, "refresh_netif_metrics: Error: kvm_read: ifnet[%d]: %s\n", i, kvm_geterr(kvmp));
+ return;
+ }
+
+ /* skip network interfaces that are not interesting ... */
+ if (strcmp(ifnet.if_xname, "lo0") == 0)
+ continue;
+
+ sts = pmdaCacheLookupName(indomtab[NETIF_INDOM].it_indom, ifnet.if_xname, NULL, (void **)&ifp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: duplicate name (%s) in network interface indom\n", ifnet.if_xname);
+ continue;
+ }
+ else if (sts == PMDA_CACHE_INACTIVE) {
+ /* reactivate an existing entry */
+ pmdaCacheStore(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_ADD, ifnet.if_xname, (void *)ifp);
+ }
+ else {
+ /* new entry */
+ ifp = (struct ifnet *)malloc(sizeof(*ifp));
+ if (ifp == NULL) {
+ fprintf(stderr, "Error: struct ifnet alloc failed for network interface \"%s\"\n", ifnet.if_xname);
+ __pmNoMem("refresh_netif_metrics", sizeof(*ifp), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ pmdaCacheStore(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_ADD, ifnet.if_xname, (void *)ifp);
+ }
+ memcpy((void *)ifp, (void *)&ifnet, sizeof(*ifp));
+ }
+}
+
+int
+do_netif_metrics(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ struct ifnet *ifp;
+ int sts = 0;
+
+ if (inst != PM_IN_NULL) {
+ /*
+ * per-network interface metrics
+ */
+ sts = pmdaCacheLookup(indomtab[NETIF_INDOM].it_indom, inst, NULL, (void **)&ifp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ sts = 1;
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 0: /* network.interface.mtu */
+ atom->ul = (__uint32_t)ifp->if_data.ifi_mtu;
+ break;
+
+ case 1: /* network.interface.up */
+ atom->ul = (ifp->if_flags & IFF_UP) == IFF_UP;
+ break;
+
+ case 2: /* network.interface.baudrate */
+ atom->ull = ifp->if_data.ifi_baudrate;
+ break;
+
+ case 3: /* network.interface.in.bytes */
+ atom->ull = ifp->if_data.ifi_ibytes;
+ break;
+
+ case 4: /* network.interface.in.packets */
+ atom->ull = ifp->if_data.ifi_ipackets;
+ break;
+
+ case 5: /* network.interface.in.mcasts */
+ atom->ull = ifp->if_data.ifi_imcasts;
+ break;
+
+ case 6: /* network.interface.in.errors */
+ atom->ull = ifp->if_data.ifi_ierrors;
+ break;
+
+ case 7: /* network.interface.in.drops */
+ atom->ull = ifp->if_data.ifi_iqdrops;
+ break;
+
+ case 8: /* network.interface.out.bytes */
+ atom->ull = ifp->if_data.ifi_obytes;
+ break;
+
+ case 9: /* network.interface.out.packets */
+ atom->ull = ifp->if_data.ifi_opackets;
+ break;
+
+ case 10: /* network.interface.out.mcasts */
+ atom->ull = ifp->if_data.ifi_omcasts;
+ break;
+
+ case 11: /* network.interface.out.errors */
+ atom->ull = ifp->if_data.ifi_oerrors;
+ break;
+
+ case 12: /* network.interface.out.collisions */
+ atom->ull = ifp->if_data.ifi_collisions;
+ break;
+
+ case 13: /* network.interface.total.bytes */
+ atom->ull = ifp->if_data.ifi_ibytes + ifp->if_data.ifi_obytes;
+ break;
+
+ case 14: /* network.interface.total.packets */
+ atom->ull = ifp->if_data.ifi_ipackets + ifp->if_data.ifi_opackets;
+ break;
+
+ case 15: /* network.interface.total.mcasts */
+ atom->ull = ifp->if_data.ifi_imcasts + ifp->if_data.ifi_omcasts;
+ break;
+
+ case 16: /* network.interface.total.errors */
+ atom->ull = ifp->if_data.ifi_ierrors + ifp->if_data.ifi_oerrors;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else
+ sts = 0;
+ }
+
+ return sts;
+}
diff --git a/src/pmdas/freebsd/root_freebsd b/src/pmdas/freebsd/root_freebsd
new file mode 100644
index 0000000..6f56d7e
--- /dev/null
+++ b/src/pmdas/freebsd/root_freebsd
@@ -0,0 +1,172 @@
+/*
+ * Metrics for FreeBSD kernel PMDA
+ *
+ * Copyright (c) 2012 Ken McDonell All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * the domain for the FreeBSD PMDA ...
+ */
+#ifndef FREEBSD
+#define FREEBSD 85
+#endif
+
+root {
+ hinv
+ kernel
+ disk
+ mem
+ network
+ swap
+}
+
+hinv {
+ ncpu FREEBSD:0:0
+ ndisk FREEBSD:1:0
+ physmem FREEBSD:0:1
+ pagesize FREEBSD:1:3
+ cpu
+}
+
+hinv.cpu {
+ vendor FREEBSD:0:15
+ model FREEBSD:0:16
+ arch FREEBSD:0:17
+}
+
+kernel {
+ all
+ percpu
+}
+
+kernel.all {
+ pswitch FREEBSD:0:22
+ syscall FREEBSD:0:23
+ intr FREEBSD:0:24
+ hz FREEBSD:0:13
+ load FREEBSD:0:2
+ cpu
+}
+
+kernel.all.cpu {
+ user FREEBSD:0:3
+ nice FREEBSD:0:4
+ sys FREEBSD:0:5
+ intr FREEBSD:0:6
+ idle FREEBSD:0:7
+}
+
+kernel.percpu {
+ cpu
+}
+
+kernel.percpu.cpu {
+ user FREEBSD:0:8
+ nice FREEBSD:0:9
+ sys FREEBSD:0:10
+ intr FREEBSD:0:11
+ idle FREEBSD:0:12
+}
+
+disk {
+ dev
+ all
+}
+
+disk.dev {
+ read FREEBSD:2:0
+ write FREEBSD:2:1
+ total FREEBSD:2:2
+ read_bytes FREEBSD:2:3
+ write_bytes FREEBSD:2:4
+ total_bytes FREEBSD:2:5
+ blkread FREEBSD:2:12
+ blkwrite FREEBSD:2:13
+ blktotal FREEBSD:2:14
+}
+
+disk.all {
+ read FREEBSD:2:6
+ write FREEBSD:2:7
+ total FREEBSD:2:8
+ read_bytes FREEBSD:2:9
+ write_bytes FREEBSD:2:10
+ total_bytes FREEBSD:2:11
+ blkread FREEBSD:2:15
+ blkwrite FREEBSD:2:16
+ blktotal FREEBSD:2:17
+}
+
+mem {
+ util
+}
+
+mem.util {
+ all FREEBSD:1:4
+ used FREEBSD:1:5
+ free FREEBSD:1:6
+ bufmem FREEBSD:1:7
+ cached FREEBSD:1:8
+ wired FREEBSD:1:9
+ active FREEBSD:1:10
+ inactive FREEBSD:1:11
+ avail FREEBSD:1:12
+}
+
+network {
+ interface
+}
+
+network.interface {
+ mtu FREEBSD:3:0
+ up FREEBSD:3:1
+ baudrate FREEBSD:3:2
+ in
+ out
+ total
+}
+
+network.interface.in {
+ bytes FREEBSD:3:3
+ packets FREEBSD:3:4
+ mcasts FREEBSD:3:5
+ errors FREEBSD:3:6
+ drops FREEBSD:3:7
+}
+
+network.interface.out {
+ bytes FREEBSD:3:8
+ packets FREEBSD:3:9
+ mcasts FREEBSD:3:10
+ errors FREEBSD:3:11
+ collisions FREEBSD:3:12
+}
+
+network.interface.total {
+ bytes FREEBSD:3:13
+ packets FREEBSD:3:14
+ mcasts FREEBSD:3:15
+ errors FREEBSD:3:16
+}
+
+swap {
+ pagesin FREEBSD:0:18
+ pagesout FREEBSD:0:19
+ in FREEBSD:0:20
+ out FREEBSD:0:21
+ length FREEBSD:0:25
+ used FREEBSD:0:26
+ free FREEBSD:1:1
+}
+
+#undef FREEBSD
diff --git a/src/pmdas/gfs2/GNUmakefile b/src/pmdas/gfs2/GNUmakefile
new file mode 100644
index 0000000..03fce96
--- /dev/null
+++ b/src/pmdas/gfs2/GNUmakefile
@@ -0,0 +1,60 @@
+#
+# Copyright (c) 2013 - 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = latency.c control.c glstats.c worst_glock.c glocks.c sbstats.c ftrace.c pmda.c
+HFILES = latency.c control.h glstats.h worst_glock.h glocks.h sbstats.h ftrace.h pmdagfs2.h
+CMDTARGET = pmdagfs2
+LLDLIBS = $(PCP_PMDALIB)
+LSRCFILES = Install Remove pmns root help README
+
+IAM = gfs2
+DOMAIN = GFS2
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(CMDTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root pmns domain.h help $(PMDADIR)
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+pmda.o glocks.o: glocks.h
+pmda.o sbstats.o: sbstats.h
+pmda.o: pmdagfs2.h
+pmda.o glstats.o: glstats.h
+pmda.o ftrace.o: ftrace.h
+pmda.o ftrace.o worst_glock.o: worst_glock.h
+pmda.o ftrace.o latency.o: latency.h
+pmda.o control.o: control.h
diff --git a/src/pmdas/gfs2/Install b/src/pmdas/gfs2/Install
new file mode 100644
index 0000000..5f60ce2
--- /dev/null
+++ b/src/pmdas/gfs2/Install
@@ -0,0 +1,27 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the GFS2 PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gfs2
+forced_restart=true
+pmda_interface=4
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/gfs2/README b/src/pmdas/gfs2/README
new file mode 100644
index 0000000..13e5228
--- /dev/null
+++ b/src/pmdas/gfs2/README
@@ -0,0 +1,108 @@
+Performance Co-Pilot PMDA for Monitoring GFS2 Filesystems
+=========================================================
+
+This PMDA is capable of collecting glock statistics from GFS2 filesystems
+mounted on the system in both local and clustered configurations.
+
+The PMDA collects its data from the trace-point output given by GFS2 as
+the filesystem is working, this information is provided in userland
+through debugfs. In order for pmdagfs2 to be able to provide any metric
+data the user must have debugfs mounted and at least on GFS2 filesystem
+mounted on the system to be monitored.
+
+As mentioned above the PMDA can be used both situations where GFS2
+filesystems are mounted as local filesystems and in cluster configurations
+where GFS2 is used as a shared disk filesystem. When the PMDA is being
+used in conjunction with a locally mounted filesystem (no clustering) only
+a base number of metrics will be available to provide metric information
+back to PMCD, these metrics can be recognised by their lack of
+corresponding “control” metrics.
+
+For configurations where GFS2 is used in a clustered environment the
+additional “cluster only” metrics are able to collect data through the
+cache control mechanism of the cluster. This data being passed between
+cluster nodes regarding the state of glocks is unavailable in local
+filesystem configurations leading the requirement for a cluster
+configuration for these metrics.
+
+For more information on GFS2 or cluster setups please visit www.redhat.com
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics which are
+exposed by this PMDA.
+
+Once the PMDA has been installed, the following command will list all of
+the available metrics and their explanatory “help” text:
+
+ + # $ pminfo -fT gfs2
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/gfs2
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDA's currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number (This should only be an issue on installations with
+ third party PMDA's installed as the domain number given has been
+ reserved for the GFS2 PMDA with base PCP installations).
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the “collector” and “monitor” installation
+ configuration options.
+
+Configuration
+=============
+
+Some of the metrics provided by the PMDA can be configured to whether they
+are turned on or off with regards to collecting metric data. These metrics
+are distinguished by having a corresponding “control” metric.
+
+Identification of these metrics which have this control can be found by
+issuing the following command.
+
+ + $ pminfo -fT gfs2.control
+
+The metrics given as output through pminfo in this way can be controlled
+by setting their metric value to either 0 (Off: no collection of metric
+data) or 1 (On: collection of metric data) using the provided command
+pmstore whilst specifying the metric to set the value for and a valid
+value.
+
+ + $ pmstore gfs2.control.tracepoints.all 1
+
+Any metrics without a corresponding control metric cannot have their
+functionality toggled in this way.
+
+De-Installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/gfs2
+ #./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/PMCD/gfs2.log) should be checked for any warnings or
+ errors.
+
+ + In an event where no values are being returned for most of the
+ metrics check ensure that both debugfs is mounted, metrics with
+ control options are enabled and your distribution supports the
+ full range of GFS2 trace-points.
+
+ $ mount -t debugfs none /sys/kernel/debug
+
+ $ pminfo -f gfs2.control
+
+ $ pmstore gfs2.control.tracepoints.all 1
diff --git a/src/pmdas/gfs2/Remove b/src/pmdas/gfs2/Remove
new file mode 100644
index 0000000..ba5409c
--- /dev/null
+++ b/src/pmdas/gfs2/Remove
@@ -0,0 +1,24 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the GFS2 PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gfs2
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/gfs2/control.c b/src/pmdas/gfs2/control.c
new file mode 100644
index 0000000..20d5c6f
--- /dev/null
+++ b/src/pmdas/gfs2/control.c
@@ -0,0 +1,123 @@
+/*
+ * GFS2 trace-point metrics control.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "control.h"
+#include "ftrace.h"
+#include "worst_glock.h"
+#include "latency.h"
+
+/* Locations of the enable files for the gfs2 tracepoints */
+const char *control_locations[] = {
+ [CONTROL_ALL] = "/sys/kernel/debug/tracing/events/gfs2/enable",
+ [CONTROL_GLOCK_STATE_CHANGE] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_glock_state_change/enable",
+ [CONTROL_GLOCK_PUT] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_glock_put/enable",
+ [CONTROL_DEMOTE_RQ] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_demote_rq/enable",
+ [CONTROL_PROMOTE] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_promote/enable",
+ [CONTROL_GLOCK_QUEUE] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_glock_queue/enable",
+ [CONTROL_GLOCK_LOCK_TIME] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_glock_lock_time/enable",
+ [CONTROL_PIN] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_pin/enable",
+ [CONTROL_LOG_FLUSH] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_log_flush/enable",
+ [CONTROL_LOG_BLOCKS] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_log_blocks/enable",
+ [CONTROL_AIL_FLUSH] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_ail_flush/enable",
+ [CONTROL_BLOCK_ALLOC] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_block_alloc/enable",
+ [CONTROL_BMAP] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_bmap/enable",
+ [CONTROL_RS] = "/sys/kernel/debug/tracing/events/gfs2/gfs2_rs/enable",
+ [CONTROL_BUFFER_SIZE_KB] = "/sys/kernel/debug/tracing/buffer_size_kb",
+ [CONTROL_GLOBAL_TRACING] = "/sys/kernel/debug/tracing/tracing_on"
+};
+
+/*
+ * Refresh callback for the control metrics; For traceppoints that have file
+ * based enabling we use gfs2_control_check_value(), for other metrics we
+ * call their corresponding "get" value.
+ */
+int
+gfs2_control_fetch(int item, pmAtomValue *atom)
+{
+ if (item >= CONTROL_ALL && item <= CONTROL_GLOBAL_TRACING) {
+ atom->ul = gfs2_control_check_value(control_locations[item]);
+
+ } else if (item == CONTROL_WORSTGLOCK) {
+ atom->ul = worst_glock_get_state();
+
+ } else if (item == CONTROL_LATENCY) {
+ atom->ul = latency_get_state();
+
+ } else if (item == CONTROL_FTRACE_GLOCK_THRESHOLD) {
+ atom->ul = ftrace_get_threshold();
+
+ } else {
+ return PM_ERR_PMID;
+
+ }
+ return 1;
+}
+
+/*
+ * Attempt open the enable file for the given filename and set the value
+ * contained in pmValueSet. The enable file for the tracepoint only accept
+ * 0 for disabled or 1 for enabled.
+ */
+int
+gfs2_control_set_value(const char *filename, pmValueSet *vsp)
+{
+ FILE *fp;
+ int sts = 0;
+ int value = vsp->vlist[0].value.lval;
+
+ if (strncmp(filename, control_locations[CONTROL_BUFFER_SIZE_KB],
+ sizeof(control_locations[CONTROL_BUFFER_SIZE_KB])-1) == 0) {
+ /* Special case for buffer_size_kb */
+ if (value < 0 || value > 131072) /* Allow upto 128mb buffer per CPU */
+ return - oserror();
+
+ } else if (value < 0 || value > 1) {
+ return -oserror();
+ }
+
+ fp = fopen(filename, "w");
+ if (!fp) {
+ sts = -oserror(); /* EACCESS, File not found (stats not supported) */;
+ } else {
+ fprintf(fp, "%d\n", value);
+ fclose(fp);
+ }
+ return sts;
+}
+
+/*
+ * We check the tracepoint enable file given by filename and return the value
+ * contained. This should either be 0 for disabled or 1 for enabled. In the
+ * event of permissions or file not found we will return zero.
+ */
+int
+gfs2_control_check_value(const char *filename)
+{
+ FILE *fp;
+ char buffer[16];
+ int value = 0;
+
+ fp = fopen(filename, "r");
+ if (fp) {
+ while (fgets(buffer, sizeof(buffer), fp) != NULL)
+ sscanf(buffer, "%d", &value);
+ fclose(fp);
+ }
+ return value;
+}
diff --git a/src/pmdas/gfs2/control.h b/src/pmdas/gfs2/control.h
new file mode 100644
index 0000000..e687dad
--- /dev/null
+++ b/src/pmdas/gfs2/control.h
@@ -0,0 +1,49 @@
+/*
+ * GFS2 trace-point enable controls.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef CONTROL_H
+#define CONTROL_H
+
+enum {
+ CONTROL_ALL = 0,
+ CONTROL_GLOCK_STATE_CHANGE,
+ CONTROL_GLOCK_PUT,
+ CONTROL_DEMOTE_RQ,
+ CONTROL_PROMOTE,
+ CONTROL_GLOCK_QUEUE,
+ CONTROL_GLOCK_LOCK_TIME,
+ CONTROL_PIN,
+ CONTROL_LOG_FLUSH,
+ CONTROL_LOG_BLOCKS,
+ CONTROL_AIL_FLUSH,
+ CONTROL_BLOCK_ALLOC,
+ CONTROL_BMAP,
+ CONTROL_RS,
+ CONTROL_BUFFER_SIZE_KB,
+ CONTROL_GLOBAL_TRACING,
+ CONTROL_WORSTGLOCK,
+ CONTROL_LATENCY,
+ CONTROL_FTRACE_GLOCK_THRESHOLD,
+ NUM_CONTROL_STATS
+};
+
+extern const char *control_locations[];
+
+extern int gfs2_control_fetch(int, pmAtomValue *);
+extern int gfs2_control_set_value(const char *, pmValueSet *);
+extern int gfs2_control_check_value(const char *);
+
+#endif /* CONTROL_H */
diff --git a/src/pmdas/gfs2/ftrace.c b/src/pmdas/gfs2/ftrace.c
new file mode 100644
index 0000000..6485c00
--- /dev/null
+++ b/src/pmdas/gfs2/ftrace.c
@@ -0,0 +1,586 @@
+/*
+ * GFS2 ftrace based trace-point metrics.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include "pmdagfs2.h"
+#include "ftrace.h"
+#include "worst_glock.h"
+#include "latency.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+
+static char *TRACE_PIPE = "/sys/kernel/debug/tracing/trace_pipe";
+static int max_glock_throughput = INITIAL_GLOBAL_MAX_GLOCK_THROUGHPUT;
+
+static struct ftrace_data ftrace_data;
+static int num_accepted_entries;
+
+/*
+ * Fetches the value for the given metric item and then assigns to pmAtomValue.
+ * We check to see if item is in valid range for the metric.
+ */
+int
+gfs2_ftrace_fetch(int item, struct ftrace *ftrace, pmAtomValue *atom)
+{
+ /* Ensure that metric value wanted is valid */
+ if ((item < 0 || item >= NUM_TRACEPOINT_STATS))
+ return PM_ERR_PMID;
+
+ atom->ull = ftrace->values[item];
+ return 1;
+}
+
+/*
+ * External function to allow the increment of the num_accepted_locks
+ * variable from pmStore.
+ */
+void
+ftrace_increase_num_accepted_entries(){
+ num_accepted_entries++;
+}
+
+/*
+ * Sets the value of max_glock_throughput using pmstore, value should
+ * must be positive.
+ */
+int
+ftrace_set_threshold(pmValueSet *vsp)
+{
+ int value = vsp->vlist[0].value.lval;
+
+ if (value < 0) /* Ensure positive value */
+ return PM_ERR_SIGN;
+
+ max_glock_throughput = value;
+
+ return 0;
+}
+
+/*
+ * Returns the max number of glocks we allow per run through the trace_pipe,
+ * Used by the fetch for the control metrics.
+ */
+int
+ftrace_get_threshold()
+{
+ return max_glock_throughput;
+}
+
+/*
+ * We open the ftrace trace file in write mode and straight after
+ * close it. This acts as a way to completely clear the trace ring-
+ * buffer when needed.
+ */
+static int
+ftrace_clear_buffer()
+{
+ char *TRACE = "/sys/kernel/debug/tracing/trace";
+ FILE *fp;
+
+ /* Open in write mode and then straight close will purge buffer contents */
+ if (( fp = fopen(TRACE, "w")) == NULL )
+ return -oserror();
+
+ fclose(fp);
+
+ return 0;
+}
+
+/*
+ * We take tracedata from the trace pipe and store only the data which is
+ * from GFS2 metrics. We collect all the data in one array to be worked
+ * through later, this is because all trace data comes through the
+ * trace pipe mixed.
+ */
+static int
+gfs2_extract_trace_values(char *buffer, pmInDom gfs_fs_indom)
+{
+ struct ftrace_data temp;
+
+ unsigned int major, minor;
+ char *data;
+
+ /* Interpret data, we work out what tracepoint it belongs to first */
+ if ((data = strstr(buffer, "gfs2_glock_state_change: "))) {
+ temp.tracepoint = GLOCK_STATE_CHANGE;
+ sscanf(data, "gfs2_glock_state_change: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ /*
+ * Pass tracepoint data over for latency metrics for processing,
+ * only if the metrics are enabled.
+ */
+ if (latency_get_state() == 1)
+ gfs2_extract_latency(major, minor, temp.tracepoint, buffer, gfs_fs_indom);
+
+ } else if ((data = strstr(buffer, "gfs2_glock_put: "))) {
+ temp.tracepoint = GLOCK_PUT;
+ sscanf(data, "gfs2_glock_put: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_demote_rq: "))) {
+ temp.tracepoint = DEMOTE_RQ;
+ sscanf(data, "gfs2_demote_rq: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ /*
+ * Pass tracepoint data over for latency metrics for processing,
+ * only if the metrics are enabled.
+ */
+ if (latency_get_state() == 1)
+ gfs2_extract_latency(major, minor, temp.tracepoint, buffer, gfs_fs_indom);
+
+ } else if ((data = strstr(buffer, "gfs2_promote: "))) {
+ temp.tracepoint = PROMOTE;
+ sscanf(data, "gfs2_promote: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_glock_queue: "))) {
+ temp.tracepoint = GLOCK_QUEUE;
+ sscanf(data, "gfs2_glock_queue: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ /*
+ * Pass tracepoint data over for latency metrics for processing,
+ * only if the metrics are enabled.
+ */
+ if (latency_get_state() == 1)
+ gfs2_extract_latency(major, minor, temp.tracepoint, buffer, gfs_fs_indom);
+
+ } else if ((data = strstr(buffer, "gfs2_glock_lock_time: "))) {
+ temp.tracepoint = GLOCK_LOCK_TIME;
+ sscanf(data, "gfs2_glock_lock_time: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ /*
+ * Pass tracepoint data over for worst_glock metrics for processing,
+ * only if the metrics are enabled.
+ */
+ if (worst_glock_get_state() == 1)
+ gfs2_extract_worst_glock(&data, gfs_fs_indom);
+
+ } else if ((data = strstr(buffer, "gfs2_pin: "))) {
+ temp.tracepoint = PIN;
+ sscanf(data, "gfs2_pin: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_log_flush: "))) {
+ temp.tracepoint = LOG_FLUSH;
+ sscanf(data, "gfs2_log_flush: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_log_blocks: "))) {
+ temp.tracepoint = LOG_BLOCKS;
+ sscanf(data, "gfs2_log_blocks: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_ail_flush: "))) {
+ temp.tracepoint = AIL_FLUSH;
+ sscanf(data, "gfs2_ail_flush: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_block_alloc: "))) {
+ temp.tracepoint = BLOCK_ALLOC;
+ sscanf(data, "gfs2_block_alloc: %"SCNu32",%"SCNu32" %s", &major, &minor, data);
+
+ } else if ((data = strstr(buffer, "gfs2_bmap: "))) {
+ temp.tracepoint = BMAP;
+ sscanf(data, "gfs2_bmap: %"SCNu32",%"SCNu32"", &major, &minor);
+
+ } else if ((data = strstr(buffer, "gfs2_rs: "))) {
+ temp.tracepoint = RS;
+ sscanf(data, "gfs2_rs: %"SCNu32",%"SCNu32" %s", &major, &minor, data);
+ } else {
+ return 0; /* If we do not have matching data, return and continue */
+ }
+
+ temp.dev_id = makedev(major, minor);
+ strncpy(temp.data, data, sizeof(temp.data)-1);
+
+ /* Assign data in the array and update counters */
+ ftrace_data = temp;
+ num_accepted_entries++;
+
+ return 0;
+}
+
+/*
+ * We work though each mounted filesystem and update the metric data based
+ * off what tracepoint information we have collected from the trace pipe.
+ */
+static void
+gfs2_assign_ftrace(pmInDom gfs2_fs_indom, int reset_flag)
+{
+ int i, j, sts;
+ struct gfs2_fs *fs;
+
+ /* We walk through for each filesystem */
+ for (pmdaCacheOp(gfs2_fs_indom, PMDA_CACHE_WALK_REWIND);;) {
+ if ((i = pmdaCacheOp(gfs2_fs_indom, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ sts = pmdaCacheLookup(gfs2_fs_indom, i, NULL, (void **)&fs);
+ if (sts != PMDA_CACHE_ACTIVE)
+ continue;
+
+ if(reset_flag == 1){
+ for (j = 0; j < NUM_TRACEPOINT_STATS; j++) {
+ /* Reset old metric data for all tracepoints */
+ fs->ftrace.values[j] = 0;
+ reset_flag = 0;
+ }
+ }
+
+ if (fs->dev_id == ftrace_data.dev_id) {
+
+ /* Work through the data, increasing metric counters */
+ if (ftrace_data.tracepoint == GLOCK_STATE_CHANGE) {
+ char state[3], target[3];
+
+ sscanf(ftrace_data.data,
+ "gfs2_glock_state_change: %*d,%*d glock %*d:%*d state %*s to %s tgt:%s dmt:%*s flags:%*s",
+ state, target
+ );
+
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_EXCLUSIVE]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKSTATE_TOTAL]++;
+
+ if (strncmp(state, target, 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_GLOCK_CHANGEDTARGET]++;
+ } else {
+ fs->ftrace.values[FTRACE_GLOCKSTATE_GLOCK_MISSEDTARGET]++;
+ }
+
+ } else if (ftrace_data.tracepoint == GLOCK_PUT) {
+ char state[3];
+
+ sscanf(ftrace_data.data,
+ "gfs2_glock_put: %*d,%*d glock %*d:%*d state %*s => %s flags:%*s",
+ state
+ );
+
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKPUT_EXCLUSIVE]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKPUT_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == DEMOTE_RQ) {
+ char state[3], remote[7];
+
+ sscanf(ftrace_data.data,
+ "gfs2_demote_rq: %*d,%*d glock %*d:%*d demote %*s to %s flags:%*s %s",
+ state, remote
+ );
+
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_EXCLUSIVE]++;
+ }
+ fs->ftrace.values[FTRACE_DEMOTERQ_TOTAL]++;
+
+ if (strncmp(remote, "remote", 6) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_REQUESTED_REMOTE]++;
+ } else if (strncmp(remote, "local", 6) == 0) {
+ fs->ftrace.values[FTRACE_DEMOTERQ_REQUESTED_LOCAL]++;
+ }
+
+ } else if (ftrace_data.tracepoint == PROMOTE) {
+ char state[3], first[6];
+
+ sscanf(ftrace_data.data,
+ "gfs2_promote: %*d,%*d glock %*d:%*d promote %s %s",
+ first, state
+ );
+
+ if (strncmp(first, "first", 5) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_FIRST_EXCLUSIVE]++;
+ }
+ } else if (strncmp(first, "other", 5) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_PROMOTE_OTHER_EXCLUSIVE]++;
+ }
+ }
+ fs->ftrace.values[FTRACE_PROMOTE_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == GLOCK_QUEUE) {
+ char state[3], queue[8];
+
+ sscanf(ftrace_data.data,
+ "gfs2_glock_queue: %*d,%*d glock %*d:%*d %s %s",
+ queue, state
+ );
+
+ if (strncmp(queue, "queue", 6) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_EXCLUSIVE]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_QUEUE_TOTAL]++;
+
+ } else if (strncmp(queue, "dequeue", 8) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_NULLLOCK]++;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTREAD]++;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTWRITE]++;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDREAD]++;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDWRITE]++;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_EXCLUSIVE]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_DEQUEUE_TOTAL]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKQUEUE_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == GLOCK_LOCK_TIME) {
+ uint32_t lock_type;
+
+ sscanf(ftrace_data.data,
+ "gfs2_glock_lock_time: %*d,%*d glock %"SCNu32":%*d status:%*d flags:%*x tdiff:%*d srtt:%*d/%*d srttb:%*d/%*d sirt:%*d/%*d dcnt:%*d qcnt:%*d",
+ &lock_type
+ );
+
+ if (lock_type == 1) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_TRANS]++;
+ } else if (lock_type == 2) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_INDOE]++;
+ } else if (lock_type == 3) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_RGRP]++;
+ } else if (lock_type == 4) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_META]++;
+ } else if (lock_type == 5) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_IOPEN]++;
+ } else if (lock_type == 6) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_FLOCK]++;
+ } else if (lock_type == 8) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_QUOTA]++;
+ } else if (lock_type == 9) {
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_JOURNAL]++;
+ }
+ fs->ftrace.values[FTRACE_GLOCKLOCKTIME_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == PIN) {
+ char pinned[6];
+ uint32_t length;
+
+ sscanf(ftrace_data.data,
+ "gfs2_pin: %*d,%*d log %s %*d/%"SCNu32" inode: %*d",
+ pinned, &length
+ );
+
+ if (strncmp(pinned, "pin", 5) == 0) {
+ fs->ftrace.values[FTRACE_PIN_PINTOTAL]++;
+ } else if (strncmp(pinned, "unpin", 5) == 0) {
+ fs->ftrace.values[FTRACE_PIN_UNPINTOTAL]++;
+ }
+ fs->ftrace.values[FTRACE_PIN_TOTAL]++;
+
+ if(fs->ftrace.values[FTRACE_PIN_LONGESTPINNED] < length)
+ fs->ftrace.values[FTRACE_PIN_LONGESTPINNED] = length;
+
+ } else if (ftrace_data.tracepoint == LOG_FLUSH) {
+ char end[6];
+
+ sscanf(ftrace_data.data,
+ "gfs2_log_flush: %*d,%*d log flush %s %*u",
+ end
+ );
+
+ if (strncmp(end, "end", 6) == 0)
+ fs->ftrace.values[FTRACE_LOGFLUSH_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == LOG_BLOCKS) {
+
+ fs->ftrace.values[FTRACE_LOGBLOCKS_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == AIL_FLUSH) {
+ char end[6];
+
+ sscanf(ftrace_data.data,
+ "gfs2_ail_flush: %*d,%*d ail flush %s %*s %*u",
+ end
+ );
+
+ if (strncmp(end, "end", 6) == 0)
+ fs->ftrace.values[FTRACE_AILFLUSH_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == BLOCK_ALLOC) {
+ char type[8];
+
+ sscanf(ftrace_data.data,
+ "gfs2_block_alloc: %*d,%*d bmap %*u alloc %*u/%*u %s rg:%*u rf:%*u rr:%*u",
+ type
+ );
+
+ if (strncmp(type, "free", 8) == 0) {
+ fs->ftrace.values[FTRACE_BLOCKALLOC_FREE]++;
+ } else if (strncmp(type, "used", 8) == 0) {
+ fs->ftrace.values[FTRACE_BLOCKALLOC_USED]++;
+ } else if (strncmp(type, "dinode", 8) == 0) {
+ fs->ftrace.values[FTRACE_BLOCKALLOC_DINODE]++;
+ } else if (strncmp(type, "unlinked", 8) == 0) {
+ fs->ftrace.values[FTRACE_BLOCKALLOC_UNLINKED]++;
+ }
+ fs->ftrace.values[FTRACE_BLOCKALLOC_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == BMAP) {
+ char type[8];
+
+ sscanf(ftrace_data.data,
+ "gfs2_bmap: %*d,%*d bmap %*u map %*u/%*u to %*u flags:%*x %s %*d",
+ type
+ );
+
+ if (strncmp(type, "create", 8) == 0) {
+ fs->ftrace.values[FTRACE_BMAP_CREATE]++;
+ } else if (strncmp(type, "nocreate", 8) == 0) {
+ fs->ftrace.values[FTRACE_BMAP_NOCREATE]++;
+ }
+ fs->ftrace.values[FTRACE_BMAP_TOTAL]++;
+
+ } else if (ftrace_data.tracepoint == RS) {
+ char type[8];
+
+ sscanf(ftrace_data.data,
+ "gfs2_rs: %*d,%*d bmap %*u resrv %*u rg:%*u rf:%*u rr:%*u %s f:%*u",
+ type
+ );
+
+ if (strncmp(type, "del", 4) == 0) {
+ fs->ftrace.values[FTRACE_RS_DEL]++;
+ } else if (strncmp(type, "tdel", 4) == 0) {
+ fs->ftrace.values[FTRACE_RS_TDEL]++;
+ } else if (strncmp(type, "ins", 4) == 0) {
+ fs->ftrace.values[FTRACE_RS_INS]++;
+ } else if (strncmp(type, "clm", 4) == 0) {
+ fs->ftrace.values[FTRACE_RS_CLM]++;
+ }
+ fs->ftrace.values[FTRACE_RS_TOTAL]++;
+
+ }
+ }
+ }
+}
+
+/*
+ * We take all required data from the trace_pipe. Whilst keeping track of
+ * the number of locks we have seen so far. After locks have been collected
+ * we assign values and return.
+ */
+int
+gfs2_refresh_ftrace_stats(pmInDom gfs_fs_indom)
+{
+ FILE *fp;
+ int fd, flags, reset_flag;
+ char buffer[8196];
+
+ /* Reset the metric types we have found */
+ num_accepted_entries = 0;
+ reset_flag = 1;
+
+ /* We open the pipe in both read-only and non-blocking mode */
+ if ((fp = fopen(TRACE_PIPE, "r")) == NULL)
+ return -oserror();
+
+ /* Set flags of fp as non-blocking */
+ fd = fileno(fp);
+ flags = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, flags | O_RDONLY | O_NONBLOCK) < 0) {
+ fclose(fp);
+ return -oserror();
+ }
+
+ /* Extract data from the trace_pipe */
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ if (num_accepted_entries >= max_glock_throughput)
+ break;
+
+ /* In the event of an allocation error */
+ if (gfs2_extract_trace_values(buffer, gfs_fs_indom) != 0)
+ break;
+
+ /* Processing here */
+ gfs2_assign_ftrace(gfs_fs_indom, reset_flag);
+ }
+
+ fclose(fp);
+
+ /* Clear the rest of the ring buffer after passing max_glock_throughput */
+ ftrace_clear_buffer();
+
+ return 0;
+}
diff --git a/src/pmdas/gfs2/ftrace.h b/src/pmdas/gfs2/ftrace.h
new file mode 100644
index 0000000..7974783
--- /dev/null
+++ b/src/pmdas/gfs2/ftrace.h
@@ -0,0 +1,142 @@
+/*
+ * GFS2 ftrace based trace-point metrics.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef FTRACE_H
+#define FTRACE_H
+
+#define INITIAL_GLOBAL_MAX_GLOCK_THROUGHPUT 750000
+
+enum {
+ GLOCK_STATE_CHANGE = 0,
+ GLOCK_PUT,
+ DEMOTE_RQ,
+ PROMOTE,
+ GLOCK_QUEUE,
+ GLOCK_LOCK_TIME,
+ PIN,
+ LOG_FLUSH,
+ LOG_BLOCKS,
+ AIL_FLUSH,
+ BLOCK_ALLOC,
+ BMAP,
+ RS,
+ NUM_FTRACE_TRACEPOINTS
+};
+
+enum {
+ FTRACE_GLOCKSTATE_TOTAL = 0,
+ FTRACE_GLOCKSTATE_NULLLOCK,
+ FTRACE_GLOCKSTATE_CONCURRENTREAD,
+ FTRACE_GLOCKSTATE_CONCURRENTWRITE,
+ FTRACE_GLOCKSTATE_PROTECTEDREAD,
+ FTRACE_GLOCKSTATE_PROTECTEDWRITE,
+ FTRACE_GLOCKSTATE_EXCLUSIVE,
+ FTRACE_GLOCKSTATE_GLOCK_CHANGEDTARGET,
+ FTRACE_GLOCKSTATE_GLOCK_MISSEDTARGET,
+ FTRACE_GLOCKPUT_TOTAL,
+ FTRACE_GLOCKPUT_NULLLOCK,
+ FTRACE_GLOCKPUT_CONCURRENTREAD,
+ FTRACE_GLOCKPUT_CONCURRENTWRITE,
+ FTRACE_GLOCKPUT_PROTECTEDREAD,
+ FTRACE_GLOCKPUT_PROTECTEDWRITE,
+ FTRACE_GLOCKPUT_EXCLUSIVE,
+ FTRACE_DEMOTERQ_TOTAL,
+ FTRACE_DEMOTERQ_NULLLOCK,
+ FTRACE_DEMOTERQ_CONCURRENTREAD,
+ FTRACE_DEMOTERQ_CONCURRENTWRITE,
+ FTRACE_DEMOTERQ_PROTECTEDREAD,
+ FTRACE_DEMOTERQ_PROTECTEDWRITE,
+ FTRACE_DEMOTERQ_EXCLUSIVE,
+ FTRACE_DEMOTERQ_REQUESTED_REMOTE,
+ FTRACE_DEMOTERQ_REQUESTED_LOCAL,
+ FTRACE_PROMOTE_TOTAL,
+ FTRACE_PROMOTE_FIRST_NULLLOCK,
+ FTRACE_PROMOTE_FIRST_CONCURRENTREAD,
+ FTRACE_PROMOTE_FIRST_CONCURRENTWRITE,
+ FTRACE_PROMOTE_FIRST_PROTECTEDREAD,
+ FTRACE_PROMOTE_FIRST_PROTECTEDWRITE,
+ FTRACE_PROMOTE_FIRST_EXCLUSIVE,
+ FTRACE_PROMOTE_OTHER_NULLLOCK,
+ FTRACE_PROMOTE_OTHER_CONCURRENTREAD,
+ FTRACE_PROMOTE_OTHER_CONCURRENTWRITE,
+ FTRACE_PROMOTE_OTHER_PROTECTEDREAD,
+ FTRACE_PROMOTE_OTHER_PROTECTEDWRITE,
+ FTRACE_PROMOTE_OTHER_EXCLUSIVE,
+ FTRACE_GLOCKQUEUE_TOTAL,
+ FTRACE_GLOCKQUEUE_QUEUE_TOTAL,
+ FTRACE_GLOCKQUEUE_QUEUE_NULLLOCK,
+ FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTREAD,
+ FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTWRITE,
+ FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDREAD,
+ FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDWRITE,
+ FTRACE_GLOCKQUEUE_QUEUE_EXCLUSIVE,
+ FTRACE_GLOCKQUEUE_DEQUEUE_TOTAL,
+ FTRACE_GLOCKQUEUE_DEQUEUE_NULLLOCK,
+ FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTREAD,
+ FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTWRITE,
+ FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDREAD,
+ FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDWRITE,
+ FTRACE_GLOCKQUEUE_DEQUEUE_EXCLUSIVE,
+ FTRACE_GLOCKLOCKTIME_TOTAL,
+ FTRACE_GLOCKLOCKTIME_TRANS,
+ FTRACE_GLOCKLOCKTIME_INDOE,
+ FTRACE_GLOCKLOCKTIME_RGRP,
+ FTRACE_GLOCKLOCKTIME_META,
+ FTRACE_GLOCKLOCKTIME_IOPEN,
+ FTRACE_GLOCKLOCKTIME_FLOCK,
+ FTRACE_GLOCKLOCKTIME_QUOTA,
+ FTRACE_GLOCKLOCKTIME_JOURNAL,
+ FTRACE_PIN_TOTAL,
+ FTRACE_PIN_PINTOTAL,
+ FTRACE_PIN_UNPINTOTAL,
+ FTRACE_PIN_LONGESTPINNED,
+ FTRACE_LOGFLUSH_TOTAL,
+ FTRACE_LOGBLOCKS_TOTAL,
+ FTRACE_AILFLUSH_TOTAL,
+ FTRACE_BLOCKALLOC_TOTAL,
+ FTRACE_BLOCKALLOC_FREE,
+ FTRACE_BLOCKALLOC_USED,
+ FTRACE_BLOCKALLOC_DINODE,
+ FTRACE_BLOCKALLOC_UNLINKED,
+ FTRACE_BMAP_TOTAL,
+ FTRACE_BMAP_CREATE,
+ FTRACE_BMAP_NOCREATE,
+ FTRACE_RS_TOTAL,
+ FTRACE_RS_DEL,
+ FTRACE_RS_TDEL,
+ FTRACE_RS_INS,
+ FTRACE_RS_CLM,
+ NUM_TRACEPOINT_STATS
+};
+
+struct ftrace {
+ uint64_t values[NUM_TRACEPOINT_STATS];
+};
+
+struct ftrace_data {
+ dev_t dev_id;
+ int tracepoint;
+ char data[512];
+};
+
+extern void ftrace_increase_num_accepted_entries();
+extern int gfs2_ftrace_fetch(int, struct ftrace *, pmAtomValue *);
+extern int gfs2_refresh_ftrace_stats(pmInDom);
+
+extern int ftrace_get_threshold();
+extern int ftrace_set_threshold(pmValueSet *vsp);
+
+#endif /*FTRACE_H*/
diff --git a/src/pmdas/gfs2/glocks.c b/src/pmdas/gfs2/glocks.c
new file mode 100644
index 0000000..e8dc81e
--- /dev/null
+++ b/src/pmdas/gfs2/glocks.c
@@ -0,0 +1,93 @@
+/*
+ * GFS2 glocks sysfs file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include "glocks.h"
+
+#include <ctype.h>
+
+int
+gfs2_glocks_fetch(int item, struct glocks *glocks, pmAtomValue *atom)
+{
+ /* Check for valid metric count */
+ if (item < 0 || item >= NUM_GLOCKS_STATS)
+ return PM_ERR_PMID;
+
+ /* Check for no values recorded */
+ if(glocks->values[item] == UINT64_MAX)
+ return 0;
+
+ atom->ull = glocks->values[item];
+ return 1;
+}
+
+int
+gfs2_refresh_glocks(const char *sysfs, const char *name, struct glocks *glocks)
+{
+ char buffer[4096];
+ FILE *fp;
+
+ /* Reset all counter for this fs */
+ memset(glocks, 0, sizeof(*glocks));
+
+ snprintf(buffer, sizeof(buffer), "%s/%s/glocks", sysfs, name);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if ((fp = fopen(buffer, "r")) == NULL) {
+ /*
+ * We set the values to UINT64_MAX to signify we have no
+ * current values (no metric support or debugfs not mounted)
+ *
+ */
+ memset(glocks, -1, sizeof(*glocks));
+ return -oserror();
+ }
+
+ /*
+ * Read through glocks file accumulating statistics as we go;
+ * as an early starting point, we're simply binning aggregate
+ * glock state counts.
+ *
+ */
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ char *p = buffer;
+
+ /* interested in glock lines only for now */
+ if (strncmp(p, "G:", 2) != 0)
+ continue;
+ for (p += 2; isspace((int)*p); p++) {;}
+
+ /* pick out the various state fields next */
+ if (strncmp(p, "s:SH", 4) == 0)
+ glocks->values[GLOCKS_SHARED]++;
+ else if (strncmp(p, "s:UN ", 4) == 0)
+ glocks->values[GLOCKS_UNLOCKED]++;
+ else if (strncmp(p, "s:DF ", 4) == 0)
+ glocks->values[GLOCKS_DEFERRED]++;
+ else if (strncmp(p, "s:EX", 4) == 0)
+ glocks->values[GLOCKS_EXCLUSIVE]++;
+ glocks->values[GLOCKS_TOTAL]++;
+ for (p += 4; isspace((int)*p); p++) {;}
+
+ /* [ extract any other field stats here ] */
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/gfs2/glocks.h b/src/pmdas/gfs2/glocks.h
new file mode 100644
index 0000000..b56a574
--- /dev/null
+++ b/src/pmdas/gfs2/glocks.h
@@ -0,0 +1,36 @@
+/*
+ * GFS2 glock file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef GLOCKS_H
+#define GLOCKS_H
+
+enum {
+ GLOCKS_TOTAL = 0,
+ GLOCKS_SHARED = 1,
+ GLOCKS_UNLOCKED = 2,
+ GLOCKS_DEFERRED = 3,
+ GLOCKS_EXCLUSIVE = 4,
+ NUM_GLOCKS_STATS
+};
+
+struct glocks {
+ __uint64_t values[NUM_GLOCKS_STATS];
+};
+
+extern int gfs2_glocks_fetch(int, struct glocks *, pmAtomValue *);
+extern int gfs2_refresh_glocks(const char *, const char *, struct glocks *);
+
+#endif /*GLOCKS_H*/
diff --git a/src/pmdas/gfs2/glstats.c b/src/pmdas/gfs2/glstats.c
new file mode 100644
index 0000000..bcbd7b6
--- /dev/null
+++ b/src/pmdas/gfs2/glstats.c
@@ -0,0 +1,117 @@
+/*
+ * GFS2 glstats file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include "glstats.h"
+
+/*
+ * GFS2 glock type identification; note that the glock type 7 is currently not
+ * used and is reserved to be on the safe side.
+ *
+ * Type Lock type Use
+ * 1 Trans Transaction lock
+ * 2 Inode Inode metadata and data
+ * 3 Rgrp Resource group metadata
+ * 4 Meta The superblock
+ * 5 Iopen Inode last closer detection
+ * 6 Flock flock(2) syscall
+ * 8 Quota Quota operations
+ * 9 Journal Journal mutex
+ *
+ */
+
+int
+gfs2_glstats_fetch(int item, struct glstats *glstats, pmAtomValue *atom)
+{
+ /* Handle the case for our reserved but not used glock type 7 */
+ if ((item < 0 || item >= NUM_GLSTATS_STATS) && item != 7)
+ return PM_ERR_PMID;
+
+ /* Check for no values recorded */
+ if(glstats->values[item] == UINT64_MAX)
+ return 0;
+
+ atom->ull = glstats->values[item];
+ return 1;
+}
+
+int
+gfs2_refresh_glstats(const char *sysfs, const char *name, struct glstats *glstats){
+ char buffer[4096];
+ FILE *fp;
+
+ /* Reset all counter for this fs */
+ memset(glstats, 0, sizeof(*glstats));
+
+ snprintf(buffer, sizeof(buffer), "%s/%s/glstats", sysfs, name);
+ buffer[sizeof(buffer) - 1] = '\0';
+
+ if ((fp = fopen(buffer, "r")) == NULL){
+ /*
+ * We set the values to UINT64_MAX to signify we have no
+ * current values (no metric support or debugfs not mounted)
+ *
+ */
+ memset(glstats, -1, sizeof(*glstats));
+ return -oserror();
+ }
+
+ /*
+ * We read through the glstats file, finding out what glock types we are
+ * coming across and tally up the number of each type of glock we find.
+ * This file however contains the total number of locks at this time,
+ * on a large, heavy utilized filesystem there could be millions of entries
+ * so needs to be quick and efficient.
+ *
+ */
+ while(fgets(buffer, sizeof(buffer), fp) != NULL){
+ char *p = buffer;
+
+ /* We pick out the various glock types by the identifying number */
+ if (strncmp(p, "G: n:1", 6) == 0){
+ glstats->values[GLSTATS_TRANS]++;
+ } else if (strncmp(p, "G: n:2 ", 6) == 0){
+ glstats->values[GLSTATS_INODE]++;
+ } else if (strncmp(p, "G: n:3 ", 6) == 0){
+ glstats->values[GLSTATS_RGRP]++;
+ } else if (strncmp(p, "G: n:4 ", 6) == 0){
+ glstats->values[GLSTATS_META]++;
+ } else if (strncmp(p, "G: n:5 ", 6) == 0){
+ glstats->values[GLSTATS_IOPEN]++;
+ } else if (strncmp(p, "G: n:6 ", 6) == 0){
+ glstats->values[GLSTATS_FLOCK]++;
+ } else if (strncmp(p, "G: n:8 ", 6) == 0){
+ glstats->values[GLSTATS_QUOTA]++;
+ } else if (strncmp(p, "G: n:9 ", 6) == 0){
+ glstats->values[GLSTATS_JOURNAL]++;
+ }
+ glstats->values[GLSTATS_TOTAL]++;
+
+ /*
+ * We advance the cursor for after we read what type of lock we have
+ * for (p += 6; isspace((int)*p); p++) {;}
+ *
+ * [ We can extract any other future fields from here on]
+ *
+ */
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/gfs2/glstats.h b/src/pmdas/gfs2/glstats.h
new file mode 100644
index 0000000..09e5264
--- /dev/null
+++ b/src/pmdas/gfs2/glstats.h
@@ -0,0 +1,57 @@
+/*
+ * GFS2 glstats file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef GLSTATS_H
+#define GLSTATS_H
+
+/*
+ * GFS2 glock type identification; note that the glock type 7 is currently not
+ * used and is reserved to be on the safe side.
+ *
+ * Type Lock type Use
+ * 1 Trans Transaction lock
+ * 2 Inode Inode metadata and data
+ * 3 Rgrp Resource group metadata
+ * 4 Meta The superblock
+ * 5 Iopen Inode last closer detection
+ * 6 Flock flock(2) syscall
+ * 8 Quota Quota operations
+ * 9 Journal Journal mutex
+ *
+ */
+
+enum {
+ GLSTATS_TOTAL = 0,
+ GLSTATS_TRANS = 1,
+ GLSTATS_INODE = 2,
+ GLSTATS_RGRP = 3,
+ GLSTATS_META = 4,
+ GLSTATS_IOPEN = 5,
+ GLSTATS_FLOCK = 6,
+ GLSTATS_RESERVED_NOT_USED = 7,
+ GLSTATS_QUOTA = 8,
+ GLSTATS_JOURNAL = 9,
+ NUM_GLSTATS_STATS
+};
+
+struct glstats {
+ __uint64_t values[NUM_GLSTATS_STATS];
+};
+
+extern int gfs2_glstats_fetch(int, struct glstats *, pmAtomValue *);
+extern int gfs2_refresh_glstats(const char *, const char *, struct glstats *);
+
+#endif /* GLSTATS_H */
diff --git a/src/pmdas/gfs2/help b/src/pmdas/gfs2/help
new file mode 100644
index 0000000..350bd2b
--- /dev/null
+++ b/src/pmdas/gfs2/help
@@ -0,0 +1,566 @@
+#
+# Copyright (c) 2013 - 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# GFS2 PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+@ GFS2.0 Instance domain for mounted GFS2 filesystems
+
+@ gfs2.glocks.total Count of total observed incore GFS2 global locks
+Count of total incore GFS2 glock data structures based on parsing the contents
+of the /sys/kernel/debug/gfs2/<bdev>/glocks files.
+
+@ gfs2.glocks.shared GFS2 global locks in shared state
+Count of incore GFS2 glock data structures in shared state, based on parsing
+/sys/kernel/debug/gfs2/<bdev>/glocks entries with state field (s:) value "SH".
+
+@ gfs2.glocks.unlocked GFS2 global locks in unlocked state
+Count of incore GFS2 glock data structures in unlocked state, based on parsing
+/sys/kernel/debug/gfs2/<bdev>/glocks entries with state field (s:) value "UN".
+
+@ gfs2.glocks.deferred GFS2 global locks in deferred state
+Count of incore GFS2 glock data structures in deferred state, based on parsing
+/sys/kernel/debug/gfs2/<bdev>/glocks entries with state field (s:) value "DF".
+
+@ gfs2.glocks.exclusive GFS2 global locks in exclusive state
+Count of incore GFS2 glock data structures in exclusive state, based on parsing
+/sys/kernel/debug/gfs2/<bdev>/glocks entries with state field (s:) value "EX".
+
+# help text for gfs2.sbstats.*.* is generated dynamically
+
+@ gfs2.glstats.total The total number of current glocks
+Total count of the number of glocks which currently reside for filesystem on
+the given node. Data is based from /sys/kernel/debug/gfs2/<bdev>/glstats
+counting the total number of glock entries.
+
+@ gfs2.glstats.trans The number of transaction glocks
+The count of the current number of transaction type glocks that currently exist
+for the given filesystem. The data is recorded and counted from /sys/kernel/
+debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.inode The number of inode (metadata and data) glocks
+The count of the current number of inode metadata and data type glocks that
+currently exist for the given filesystem. The data is recorded and counted from
+/sys/kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.rgrp The number of resource group metadata glocks
+The count of the current number of resource group metadata type glocks that
+currently exist for the given filesystem. The data is recorded and counted from
+/sys/kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.meta The number of superblock glocks
+The count of the current number of superblock type glocks that currently exist
+for the given filesystem. The data is recorded and counted from /sys/kernel/
+debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.iopen The number of inode last closer detection glocks
+The count of the current number of inode last closer detection type glocks that
+currently exist for the given filesystem. The data is recorded and counted from
+/sys/kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.flock The number of flock(2) syscall glocks
+The count of the current number of flock(2) syscall type glocks that currently
+exist for the given filesystem. The data is recorded and counted from /sys/
+kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.quota The number of quota operations glocks
+The count of the current number of quota operations type glocks that currently
+exist for the given filesystem. The data is recorded and counted from /sys/
+kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.glstats.journal The number of journal mutex glocks
+The count of the current number of journal mutex type glocks that currently
+exist for the given filesystem. The data is recorded and counted from /sys/
+kernel/debug/gfs2/<bdev>glstats file entries for this type of glock.
+
+@ gfs2.tracepoints.glock_state_change.total Total number of glock state
+changes. The total number of counted glock state changes.
+
+@ gfs2.tracepoints.glock_state_change.null_lock Number of null_lock state
+changes. The total number of glock state changes to the null_lock state.
+
+@ gfs2.tracepoints.glock_state_change.concurrent_read Number of
+concurrent_read state changes. The total number of glock state changes
+to current_read state.
+
+@ gfs2.tracepoints.glock_state_change.concurrent_write Number of
+concurrent_write state changes. The total number of glock state changes
+to current_write state.
+
+@ gfs2.tracepoints.glock_state_change.protected_read Number of
+protected_read state changes. The total number of glock state changes to
+protected_read state.
+
+@ gfs2.tracepoints.glock_state_change.protected_write Number of
+protected_write state changes. The total number of glock state changes to
+protected_write state.
+
+@ gfs2.tracepoints.glock_state_change.exclusive Number of exclusive state
+changes. The total number of glock state changes to exclusive state.
+
+@ gfs2.tracepoints.glock_state_change.glocks.changed_target Number of
+changed locks. The number of state changes that achieved their expected
+state change.
+
+@ gfs2.tracepoints.glock_state_change.glocks.missed_target Number of
+missed locks. The number of state changes that did not achieve their
+expected state change.
+
+@ gfs2.tracepoints.glock_put.total Total number of glocks changed.
+The total number of glocks that have been changed.
+
+@ gfs2.tracepoints.glock_put.null_lock Number of released locks.
+The number of glocks put into the null_lock state.
+
+@ gfs2.tracepoints.glock_put.concurrent_read Number of glocks put
+in concurrent_read. The number of glocks put into the concurrent_read
+state.
+
+@ gfs2.tracepoints.glock_put.concurrent_write Number of glocks put
+in concurrent_write. The number of glocks put into the concurrent_write
+state.
+
+@ gfs2.tracepoints.glock_put.protected_read Number of glocks put
+in protected_read. The number of glocks put into the protected_read
+state.
+
+@ gfs2.tracepoints.glock_put.protected_write Number of glocks put
+in protected_wirte. The number of glocks put into the protected_write
+state.
+
+@ gfs2.tracepoints.glock_put.exclusive Number of glocks put
+in exclusive. The number of glocks put into the exclusive
+state.
+
+@ gfs2.tracepoints.demote_rq.total Total number of lock demote requests.
+The total number of lock demotion requests.
+
+@ gfs2.tracepoints.demote_rq.null_lock Number of lock demote requests to
+null_lock. The total number of lock demotion requests to the null_lock state.
+
+@ gfs2.tracepoints.demote_rq.concurrent_read Number of lock demote requests to
+concurrent_read. The total number of lock demotion requests to the
+concurrent_read state.
+
+@ gfs2.tracepoints.demote_rq.concurrent_write Number of lock demote requests to
+concurrent_write. The total number of lock demotion requests to the
+concurrent_write state.
+
+@ gfs2.tracepoints.demote_rq.protected_read Number of lock demote requests to
+protected_read. The total number of lock demotion requests to the
+protected_read state.
+
+@ gfs2.tracepoints.demote_rq.protected_write Number of lock demote requests to
+protected_write. The total number of lock demotion requests to the
+protected_write state.
+
+@ gfs2.tracepoints.demote_rq.exclusive Number of lock demote requests to
+exclusive. The total number of lock demotion requests to the
+exclusive state.
+
+@gfs2.tracepoints.demote_rq.requested.remote Number of demote requests (remote).
+The total number of demote requests which were requested by a remote node of
+the cluster.
+
+@gfs2.tracepoints.demote_rq.requested.local Number of demote requests (local).
+The total number of demote requests which were requested by a local node of
+the cluster
+
+@ gfs2.tracepoints.promote.total Total number of lock state. The total number
+of lock state.
+
+@ gfs2.tracepoints.promote.first.null_lock Number of lock state to null_lock.
+The total number of successful first time lock state to the null_lock state.
+
+@ gfs2.tracepoints.promote.first.concurrent_read Number of lock state to
+concurrent_read. The total number of successful first time lock state to the
+concurrent_read state.
+
+@ gfs2.tracepoints.promote.first.concurrent_write Number of lock state to
+concurrent_write. The total number of successful first time lock state to the
+concurrent_write state.
+
+@ gfs2.tracepoints.promote.first.protected_read Number of lock state to
+protected_read. The total number of successful first time lock state to the
+protected_read state.
+
+@ gfs2.tracepoints.promote.first.protected_write Number of lock state to
+protected_write. The total number of successful first time lock state to the
+protected_write state.
+
+@ gfs2.tracepoints.promote.first.exclusive Number of lock state to
+exclusive. The total number of successful first time lock state to the
+exclusive state.
+
+@ gfs2.tracepoints.promote.other.null_lock Number of lock state to null_lock.
+The total number of successful other time lock state to the null_lock state.
+
+@ gfs2.tracepoints.promote.other.concurrent_read Number of lock state to
+concurrent_read. The total number of successful other time lock state to the
+concurrent_read state.
+
+@ gfs2.tracepoints.promote.other.concurrent_write Number of lock state to
+concurrent_write. The total number of successful other time lock state to the
+concurrent_write state.
+
+@ gfs2.tracepoints.promote.other.protected_read Number of lock state to
+protected_read. The total number of successful other time lock state to the
+protected_read state.
+
+@ gfs2.tracepoints.promote.other.protected_write Number of lock state to
+protected_write. The total number of successful other time lock state to the
+protected_write state.
+
+@ gfs2.tracepoints.promote.other.exclusive Number of lock state to
+exclusive. The total number of successful other time lock state to the
+exclusive state.
+
+@ gfs2.tracepoints.glock_queue.total Total numbe rof queued and dequeued
+requests. The total number of both queued and dequeued requests.
+
+@ gfs2.tracepoints.glock_queue.queue.total Total number of queued lock requests.
+The total number of queued lock requests.
+
+@ gfs2.tracepoints.glock_queue.queue.null_lock Number of null_lock requests. The
+number of lock requests to the null_lock state.
+
+@ gfs2.tracepoints.glock_queue.queue.concurrent_read Number of concurrent_read
+requests. The number of lock requests to the concurrent_read state.
+
+@ gfs2.tracepoints.glock_queue.queue.concurrent_write Number of concurrent_write
+requests. The number of lock requests to the concurrent_write state.
+
+@ gfs2.tracepoints.glock_queue.queue.protected_read Number of protected_read
+requests. The number of lock requests to the protected_read state.
+
+@ gfs2.tracepoints.glock_queue.queue.protected_write Number of protected_write
+requests. The number of lock requests to the protected_write state.
+
+@ gfs2.tracepoints.glock_queue.queue.exclusive Number of exclusive
+requests. The number of lock requests to the exclusive state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.total Total number of dequeued lock requests.
+The total number of dequeued lock requests.
+
+@ gfs2.tracepoints.glock_queue.dequeue.null_lock Number of null_lock requests. The
+number of lock requests to the null_lock state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.concurrent_read Number of concurrent_read
+requests. The number of lock requests to the concurrent_read state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.concurrent_write Number of concurrent_write
+requests. The number of lock requests to the concurrent_write state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.protected_read Number of protected_read
+requests. The number of lock requests to the protected_read state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.protected_write Number of protected_write
+requests. The number of lock requests to the protected_write state.
+
+@ gfs2.tracepoints.glock_queue.dequeue.exclusive Number of exclusive
+requests. The number of lock requests to the exclusive state.
+
+@ gfs2.tracepoints.glock_lock_time.total Total number of lock updates.
+The total number of lock updates.
+
+@ gfs2.tracepoints.glock_lock_time.trans Number of transaction lock updates.
+The number of updates for transaction based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.inode Number of inode lock updates.
+The number of updates for inode based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.rgrp Number of resource group lock updates.
+The number of updates for resource group based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.meta Number of metadata lock updates.
+The number of updates for metadata based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.iopen Number of iopen lock updates.
+The number of updates for iopen based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.flock Number of flock lock updates.
+The number of updates for flock based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.quota Number of quota lock updates.
+The number of updates for quota based glocks.
+
+@ gfs2.tracepoints.glock_lock_time.journal Number of journal lock updates.
+The number of updates for journal based glocks.
+
+@ gfs2.tracepoints.pin.total Total number of Pin/Unpin requests. The total
+number of requests to pin/unpin blocks on the log.
+
+@ gfs2.tracepoints.pin.pin_total Number of pin requests. The total number of
+requests to pin blocks on the log.
+
+@ gfs2.tracepoints.pin.unpin_total Number of unpin requests. The total number
+requests to unpin blocks on the log.
+
+@ gfs2.tracepoints.pin.longest_pinned Longest pinned. The longest pinned
+inode or resource group log block
+
+@ gfs2.tracepoints.log_flush.total Total log flushes. The total number of
+log flushes observed
+
+@ gfs2.tracepoints.log_block.total Total log blocks. The total number of
+blocks placed upon the log.
+
+@ gfs2.tracepoints.ail_flush.total Total AIL flushes. The total number of
+flushes back to the AIL.
+
+@ gfs2.tracepoints.block_alloc.total Total blocks allocated/deallocated.
+The total number of allocated/freed blocks this call.
+
+@ gfs2.tracepoints.block_alloc.free Freed blocks. The number of blocks
+freed.
+
+@ gfs2.tracepoints.block_alloc.used Used blocks. The number of blocks
+used.
+
+@ gfs2.tracepoints.block_alloc.dinode Dinode blocks. The number of blocks
+used for dinode.
+
+@ gfs2.tracepoints.block_alloc.unlinked Unlinked blocks. The number of
+unlinked blocks.
+
+@ gfs2.tracepoints.bmap.total Total number of bmap allocations. The total
+number of bmap allocations.
+
+@ gfs2.tracepoints.bmap.create Number of create bmap allocations. The number
+of create bmap allocations.
+
+@ gfs2.tracepoints.bmap.nocreate Number of nocreate bmap allocations. The
+number of nocreate bmap allocations.
+
+@ gfs2.tracepoints.rs.total Total multi-block allocations. The total number
+of multi-block allocations.
+
+@ gfs2.tracepoints.rs.del Number of resource group delete. The total number of
+resource group delete calls.
+
+@ gfs2.tracepoints.rs.tdel Number of resource group tree delete. The total number
+of resource group tree delete calls.
+
+@ gfs2.tracepoints.rs.ins Number of resource group insert. The total number of
+resource group insert calls.
+
+@ gfs2.tracepoints.rs.clm Number of resource group claims. The total number of
+resource group claim calls.
+
+# help text for gfs2.worst_glock.*.* is generated dynamically
+
+@ gfs2.latency.grant.all Average time in ms for all states. The total average
+latency time in ms for all lock states for grants.
+
+@ gfs2.latency.grant.null_lock Average time in ms to null lock state. The
+total average latency time in ms to change to null lock state for grants.
+
+@ gfs2.latency.grant.concurrent_read Average time in ms to concurrent read
+lock state. The total average latency time in ms to change to concurrent read
+lock state for grants.
+
+@ gfs2.latency.grant.concurrent_write Average time in ms to concurrent write
+lock state. The total average latency time in ms to change to concurrent write
+lock state for grants.
+
+@ gfs2.latency.grant.protected_read Average time in ms to protected read
+lock state. The total average latency time in ms to change to protected read
+lock state for grants.
+
+@ gfs2.latency.grant.protected_write Average time in ms to protected write
+lock state. The total average latency time in ms to change to protected write
+lock state for grants.
+
+@ gfs2.latency.grant.exclusive Average time in ms to exclusive lock state. The
+total average latency time in ms to change to exclusive lock state for grants.
+
+@ gfs2.latency.demote.all Average time in ms for all states. The total average
+latency time in ms for all lock states for demotes.
+
+@ gfs2.latency.demote.null_lock Average time in ms to null lock state. The
+total average latency time in ms to change to null lock state for demotes.
+
+@ gfs2.latency.demote.concurrent_read Average time in ms to concurrent read
+lock state. The total average latency time in ms to change to concurrent read
+lock state for demotes.
+
+@ gfs2.latency.demote.concurrent_write Average time in ms to concurrent write
+lock state. The total average latency time in ms to change to concurrent write
+lock state for demotes.
+
+@ gfs2.latency.demote.protected_read Average time in ms to protected read
+lock state. The total average latency time in ms to change to protected read
+lock state for demotes.
+
+@ gfs2.latency.demote.protected_write Average time in ms to protected write
+lock state. The total average latency time in ms to change to protected write
+lock state for demotes.
+
+@ gfs2.latency.demote.exclusive Average time in ms to exclusive lock state.
+The total average latency time in ms to change to exclusive lock state for
+demotes.
+
+@ gfs2.latency.queue.all Average time in ms for all states. The total average
+latency time in ms for all lock states for queues.
+
+@ gfs2.latency.queue.null_lock Average time in ms to null lock state. The
+total average latency time in ms to change to null lock state for queues.
+
+@ gfs2.latency.queue.concurrent_read Average time in ms to concurrent read
+lock state. The total average latency time in ms to change to concurrent read
+lock state for queues.
+
+@ gfs2.latency.queue.concurrent_write Average time in ms to concurrent write
+lock state. The total average latency time in ms to change to concurrent write
+lock state for queues.
+
+@ gfs2.latency.queue.protected_read Average time in ms to protected read
+lock state. The total average latency time in ms to change to protected read
+lock state for queues.
+
+@ gfs2.latency.queue.protected_write Average time in ms to protected write
+lock state. The total average latency time in ms to change to protected write
+lock state for queues.
+
+@ gfs2.latency.queue.exclusive Average time in ms to exclusive lock state.
+The total average latency time in ms to change to exclusive lock state for
+queues.
+
+@ gfs2.control.tracepoints.all Indication whether glock statistics are enabled
+The gfs2 tracepoint statistics can be manually controlled using pmstore
+gfs2.control.tracepoints.all 0 [off] or 1 [on]. Setting the value of the metric
+controls the behavior of the PMDA to whether it tries to collect from tracepoint
+metrics or not.
+
+@ gfs2.control.tracepoints.glock_state_change Indication whether
+glock_state_change glock stats are enabled. The gfs2 tracepoint statistics
+can be manually controlled using pmstore
+gfs2.control.tracepoints.glock_state_change 0 [off] or 1 [on]. Setting the
+value of the metric controls the behavior of the PMDA to whether it tries to
+collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.glock_put Indication whether glock_put glock stats
+are enabled. The gfs2 tracepoint statistics can be manually controlled using
+pmstore gfs2.control.tracepoints.glock_put 0 [off] or 1 [on]. Setting the value
+of the metric controls the behavior of the PMDA to whether it tries to collect
+from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.demote_rq Indication whether glock_demote_rq glock
+stats are enabled. The gfs2 tracepoint statistics can be manually controlled
+using pmstore gfs2.control.tracepoints.glock_demote_rq 0 [off] or 1 [on].
+Setting the value of the metric controls the behavior of the PMDA to whether
+it tries to collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.promote Indication whether glock_promote glock stats
+are enabled. The gfs2 tracepoint statistics can be manually controlled using
+pmstore gfs2.control.tracepoints.glock_promte 0 [off] or 1 [on]. Setting the
+value of the metric controls the behavior of the PMDA to whether it tries to
+collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.glock_queue Indication whether glock_queue glock
+stats are enabled. The gfs2 tracepoint statistics can be manually controlled
+using pmstore gfs2.control.tracepoints.glock_queue 0 [off] or 1 [on]. Setting
+the value of the metric controls the behavior of the PMDA to whether it tries
+to collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.glock_lock_time Indication whether glock_lock_time
+glock stats are enabled. The gfs2 tracepoint statistics can be manually
+controlled using pmstore gfs2.control.tracepoints.glock_lock_time 0 [off] or 1
+[on]. Setting the value of the metric controls the behavior of the PMDA to
+whether it tries to collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.pin Indication whether pin glock stats are enabled.
+The gfs2 tracepoint statistics can be manually controlled using pmstore
+gfs2.control.tracepoints.pin 0 [off] or 1 [on]. Setting the value of the
+metric controls the behavior of the PMDA to whether it tries to collect from
+tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.log_flush Indication whether log_flush glock stats
+are enabled. The gfs2 tracepoint statistics can be manually controlled using
+pmstore gfs2.control.tracepoints.log_flush 0 [off] or 1 [on]. Setting the
+value of the metric controls the behavior of the PMDA to whether it tries to
+collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.log_blocks Indication whether log_blocks glock stats
+are enabled. The gfs2 tracepoint statistics can be manually controlled using
+pmstore gfs2.control.tracepoints.log_blocks 0 [off] or 1 [on]. Setting the
+value of the metric controls the behavior of the PMDA to whether it tries to
+collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.ail_flush Indication whether ail_flush glock stats
+are enabled. The gfs2 tracepoint statistics can be manually controlled using
+pmstore gfs2.control.tracepoints.ail_flush 0 [off] or 1 [on]. Setting the value
+of the metric controls the behavior of the PMDA to whether it tries to collect
+from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.block_alloc Indication whether block_alloc glock
+stats are enabled. The gfs2 tracepoint statistics can be manually controlled
+using pmstore gfs2.control.tracepoints.block_alloc 0 [off] or 1 [on]. Setting
+the value of the metric controls the behavior of the PMDA to whether it tries
+to collect from tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.bmap Indication whether bmap glock stats are enabled.
+The gfs2 tracepoint statistics can be manually controlled using pmstore
+gfs2.control.tracepoints.bmap 0 [off] or 1 [on]. Setting the value of the
+metric controls the behavior of the PMDA to whether it tries to collect from
+tracepoint metrics or not.
+
+@ gfs2.control.tracepoints.rs Indication whether rs glock stats are enabled.
+The gfs2 tracepoint statistics can be manually controlled using pmstore
+gfs2.control.tracepoints.rs 0 [off] or 1 [on]. Setting the value of the metric
+controls the behavior of the PMDA to whether it tries to collect from
+tracepoint metrics or not.
+
+@ gfs2.control.buffer_size_kb Sets the buffer size for trace_pipe (per cpu).
+The size of the trace_pipe buffer can be controlled with this metrics, it
+allows the increase of the trace_pipe buffer to 128MB (131072KB) per cpu
+on the system. It is useful to increase the size of the buffer when there
+is expected to be heavy load on the file system in order to reduce the
+risk of overwritten entries in the trace_pipe before they are read (default
+value is 32MB (32768KB).
+
+@ gfs2.control.global_tracing Indication whether global tracing is enabled.
+The global tracing can be controlled using pmstore gfs2.control.global_tracing
+0 [off] or 1 [on]. This is required to be on for most of the gfs2 metrics to
+function.
+
+@ gfs2.control.worst_glock Indication whether gfs2.glock_lock_time statistics
+are enabled. The gfs2.glock_lock_time statistics can be manually controlled
+using pmstore gfs2.control.glock_lock_time 0 [off] or 1 [on]. Setting the value
+of the metric controls the behavior of the PMDA to whether it tries to collect
+the lock_time metrics or not. The machine must have the gfs2 trace-points
+available for the glock_lock_time based metrics to function.
+
+@ gfs2.control.latency Indication whether gfs2.latency statistics are enabled.
+The gfs2.latency statistics can be manually controlled using pmstore
+gfs2.control.latency 0 [off] or 1 [on]. Setting the value of the metric
+controls the behaviour of the PMDA to whether it tries to collect the latency
+metrics or not. The machice must have the gfs2 trace-points available for the
+latency metrics to function.
+
+@ gfs2.control.glock_threshold Threshold for maximum number of glocks accepted
+per fetch. The number of glocks that will be processed and accepted over all
+ftrace read trace statistics. This number can be manually altered using pmstore
+in order to tailor the number of glocks processed. This value must be positive.
diff --git a/src/pmdas/gfs2/latency.c b/src/pmdas/gfs2/latency.c
new file mode 100644
index 0000000..0b93ca9
--- /dev/null
+++ b/src/pmdas/gfs2/latency.c
@@ -0,0 +1,373 @@
+/*
+ * GFS2 latency metrics.
+ *
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "pmdagfs2.h"
+
+#include "ftrace.h"
+#include "worst_glock.h"
+#include "latency.h"
+
+#include <string.h>
+#include <inttypes.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+static struct ftrace_data latency_data;
+static int reset_flag;
+
+static int latency_state = DEFAULT_LATENCY_STATE;
+
+/*
+ * Calculates the offset position for the flat array as if it was
+ * a three dimentional array containing the latency values.
+ */
+int
+offset(int x, int y, int z)
+{
+ return (z * NUM_LATENCY_STATS * NUM_LATENCY_VALUES) + (y * NUM_LATENCY_STATS) + x ;
+}
+
+/*
+ * Fetches the value for the given metric item and then assigns to pmAtomValue.
+ * We check to see if item is in valid range for the metric.
+ */
+int
+gfs2_latency_fetch(int item, struct latency *latency, pmAtomValue *atom)
+{
+ int i, counter, position, results_used = 0;
+ int64_t result = 0;
+
+ /* We are assigning values so we want to reset on next extract */
+ reset_flag = 1;
+
+ /* Ensure that metric value wanted is valid */
+ if ((item < 0 || item >= NUM_LATENCY_STATS))
+ return PM_ERR_PMID;
+
+ counter = latency->counter[item];
+
+ /* Calculate latency for the metric (deduct start time and add the matching finish time) */
+ for (i = 0; i < counter; i++) {
+ position = offset(item, i, END);
+ result += latency->values[position].usecs;
+
+ position = offset(item, i, START);
+ result -= latency->values[position].usecs;
+
+ results_used++;
+ }
+ /* If we have no values return no values */
+ if (results_used == 0)
+ return 0;
+
+ /* Return no values if negative result */
+ if (result < 0 )
+ return 0;
+
+ /* Divide final value by number of counts */
+ result /= results_used;
+
+ /* Assign value to the metric */
+ atom->ll = result;
+
+ return 1;
+}
+
+/*
+ * Sets the value of latency using pmstore, value
+ * must be 0 or 1.
+ */
+int
+latency_set_state(pmValueSet *vsp)
+{
+ int value = vsp->vlist[0].value.lval;
+
+ if (value == 0 || value == 1) {
+ latency_state = value;
+
+ return 0;
+ } else {
+ return PM_ERR_SIGN;
+ }
+}
+
+/*
+ * Used to see whether the latency metrics are enabled or disabled. Should
+ * only return either 0 or 1.
+ */
+int
+latency_get_state()
+{
+ return latency_state;
+}
+
+/*
+ * Concatenates the ftrace time stamp (major and minor) components into one
+ * uint64_t timestamp in usecs.
+ *
+ */
+static int64_t
+concatenate(int64_t a, int64_t b)
+{
+ unsigned int power = 10;
+
+ while(a >= power)
+ power *= 10;
+
+ return a * power + b;
+}
+
+/*
+ * Converts lock state to an integer
+ */
+static int
+lock_to_decimal(char *state)
+{
+ if (strncmp(state, "NL", 2) == 0) {
+ return 0;
+ } else if (strncmp(state, "CR", 2) == 0) {
+ return 1;
+ } else if (strncmp(state, "CW", 2) == 0) {
+ return 2;
+ } else if (strncmp(state, "PR", 2) == 0) {
+ return 3;
+ } else if (strncmp(state, "PW", 2) == 0) {
+ return 4;
+ } else if (strncmp(state, "EX", 2) == 0) {
+ return 5;
+ }
+
+ return 0;
+}
+
+/*
+ * Updates the records held in the fs->latency structure with new latency
+ * stats.
+ */
+static int
+update_records(struct gfs2_fs *fs, int metric, struct latency_data data, int placement)
+{
+ int i, position, counter;
+ struct latency_data blank = { 0 };
+
+ counter = fs->latency.counter[metric];
+
+ /* If we have an intial value */
+ if (placement == START) {
+ position = offset(metric, counter, START);
+ fs->latency.values[position] = data;
+
+ position = offset(metric, counter, END);
+ fs->latency.values[position] = blank;
+
+ fs->latency.counter[metric] = (counter + 1) % NUM_LATENCY_VALUES;
+
+ /* If we have a final value */
+ } else if (placement == END) {
+ for (i = 0; i < counter; i++){
+ position = offset(metric, i, START);
+
+ if ((fs->latency.values[position].lock_type == data.lock_type) &&
+ (fs->latency.values[position].number == data.number) &&
+ (fs->latency.values[position].usecs < data.usecs )) {
+
+ position = offset(metric, i, END);
+ fs->latency.values[position] = data;
+
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * We work out the individual metric values from our buffer input and store
+ * them for processing after all of the values have been extracted from the
+ * trace pipe.
+ */
+int
+gfs2_extract_latency(unsigned int major, unsigned int minor, int tracepoint, char *data, pmInDom gfs_fs_indom)
+{
+ latency_data.dev_id = makedev(major, minor);
+ latency_data.tracepoint = tracepoint;
+ strncpy(latency_data.data, data, sizeof(latency_data.data)-1);
+
+ int i, sts;
+ struct gfs2_fs *fs;
+
+ /* We walk through for each filesystem */
+ for (pmdaCacheOp(gfs_fs_indom, PMDA_CACHE_WALK_REWIND);;) {
+ if ((i = pmdaCacheOp(gfs_fs_indom, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ sts = pmdaCacheLookup(gfs_fs_indom, i, NULL, (void **)&fs);
+ if (sts != PMDA_CACHE_ACTIVE)
+ continue;
+
+ /* Clear old entries if reset is set */
+ if (reset_flag == 1) {
+ memset(fs->latency.values, 0, sizeof fs->latency.values);
+ memset(fs->latency.counter, 0, sizeof fs->latency.counter);
+ reset_flag = 0;
+ }
+
+ /* Is the entry matching the filesystem we are on? */
+ if (fs->dev_id != latency_data.dev_id)
+ continue;
+
+ if (latency_data.tracepoint == GLOCK_QUEUE) {
+
+ struct latency_data data;
+ int64_t time_major, time_minor;
+ char queue[8], state[3];
+
+ sscanf(latency_data.data,
+ "%*s [%*d] %"SCNd64".%"SCNd64": gfs2_glock_queue: %*d,%*d glock %"SCNu32":%"SCNu64" %s %s",
+ &time_major, &time_minor, &data.lock_type, &data.number, queue, state
+ );
+ data.usecs = concatenate(time_major, time_minor);
+
+ if (data.lock_type == WORSTGLOCK_INODE || data.lock_type == WORSTGLOCK_RGRP) {
+
+ /* queue trace data is used both for latency.grant and latency.queue */
+ if (strncmp(queue, "queue", 6) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_NL, data, START);
+ update_records(fs, LATENCY_QUEUE_NL, data, START);
+ } else if (strncmp(state, "CR", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_CR, data, START);
+ update_records(fs, LATENCY_QUEUE_CR, data, START);
+ } else if (strncmp(state, "CW", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_CW, data, START);
+ update_records(fs, LATENCY_QUEUE_CW, data, START);
+ } else if (strncmp(state, "PR", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_PR, data, START);
+ update_records(fs, LATENCY_QUEUE_PR, data, START);
+ } else if (strncmp(state, "PW", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_PW, data, START);
+ update_records(fs, LATENCY_QUEUE_PW, data, START);
+ } else if (strncmp(state, "EX", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_EX, data, START);
+ update_records(fs, LATENCY_QUEUE_EX, data, START);
+ }
+ update_records(fs, LATENCY_GRANT_ALL, data, START);
+ update_records(fs, LATENCY_QUEUE_ALL, data, START);
+
+ } else if (strncmp(queue, "dequeue", 8) == 0) {
+ if (strncmp(state, "NL", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_NL, data, END);
+ } else if (strncmp(state, "CR", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_CR, data, END);
+ } else if (strncmp(state, "CW", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_CW, data, END);
+ } else if (strncmp(state, "PR", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_PR, data, END);
+ } else if (strncmp(state, "PW", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_PW, data, END);
+ } else if (strncmp(state, "EX", 2) == 0) {
+ update_records(fs, LATENCY_QUEUE_EX, data, END);
+ }
+ update_records(fs, LATENCY_QUEUE_ALL, data, END);
+ }
+ }
+ } else if (latency_data.tracepoint == GLOCK_STATE_CHANGE) {
+
+ struct latency_data data;
+ int64_t time_major, time_minor;
+ char state[3], to[3], target[3];
+ int state_decimal, to_decimal;
+
+ sscanf(latency_data.data,
+ "%*s [%*d] %"SCNd64".%"SCNd64": gfs2_glock_state_change: %*d,%*d glock %"SCNu32":%"SCNu64" state %s to %s tgt:%s dmt:%*s flags:%*s",
+ &time_major, &time_minor, &data.lock_type, &data.number, state, to, target
+ );
+ data.usecs = concatenate(time_major, time_minor);
+ state_decimal = lock_to_decimal(state);
+ to_decimal = lock_to_decimal(to);
+
+ if (data.lock_type == WORSTGLOCK_INODE || data.lock_type == WORSTGLOCK_RGRP) {
+
+ /* state change trace data is used both for latency.grant and latency.demote */
+ if ((state_decimal < to_decimal) && (strncmp(to, target, 2) == 0)) {
+ if (strncmp(to, "NL", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_NL, data, END);
+ } else if (strncmp(to, "CR", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_CR, data, END);
+ } else if (strncmp(to, "CW", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_CW, data, END);
+ } else if (strncmp(to, "PR", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_PR, data, END);
+ } else if (strncmp(to, "PW", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_PW, data, END);
+ } else if (strncmp(to, "EX", 2) == 0) {
+ update_records(fs, LATENCY_GRANT_EX, data, END);
+ }
+ update_records(fs, LATENCY_GRANT_ALL, data, END);
+
+ } else if ((state_decimal > to_decimal) && (strncmp(to, target, 2) == 0)) {
+ if (strncmp(state, "NL", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_NL, data, END);
+ } else if (strncmp(state, "CR", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_CR, data, END);
+ } else if (strncmp(state, "CW", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_CW, data, END);
+ } else if (strncmp(state, "PR", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_PR, data, END);
+ } else if (strncmp(state, "PW", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_PW, data, END);
+ } else if (strncmp(state, "EX", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_EX, data, END);
+ }
+ update_records(fs, LATENCY_DEMOTE_ALL, data, END);
+ }
+ }
+ } else if (latency_data.tracepoint == DEMOTE_RQ) {
+
+ struct latency_data data;
+ int64_t time_major, time_minor;
+ char state[3];
+
+ sscanf(latency_data.data,
+ "%*s [%*d] %"SCNd64".%"SCNd64": gfs2_demote_rq: %*d,%*d glock %"SCNu32":%"SCNu64" demote %s to %*s flags:%*s %*s",
+ &time_major, &time_minor, &data.lock_type, &data.number, state
+ );
+ data.usecs = concatenate(time_major, time_minor);
+
+ if ((data.lock_type == WORSTGLOCK_INODE) || (data.lock_type == WORSTGLOCK_RGRP)) {
+
+ /* demote rq trace data is used for latency.demote */
+ if (strncmp(state, "NL", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_NL, data, START);
+ } else if (strncmp(state, "CR", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_CR, data, START);
+ } else if (strncmp(state, "CW", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_CW, data, START);
+ } else if (strncmp(state, "PR", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_PR, data, START);
+ } else if (strncmp(state, "PW", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_PW, data, START);
+ } else if (strncmp(state, "EX", 2) == 0) {
+ update_records(fs, LATENCY_DEMOTE_EX, data, START);
+ }
+ update_records(fs, LATENCY_DEMOTE_ALL, data, START);
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/pmdas/gfs2/latency.h b/src/pmdas/gfs2/latency.h
new file mode 100644
index 0000000..aa2a1ef
--- /dev/null
+++ b/src/pmdas/gfs2/latency.h
@@ -0,0 +1,70 @@
+/*
+ * GFS2 latency metrics.
+ *
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef LATENCY_H
+#define LATENCY_H
+
+#define DEFAULT_LATENCY_STATE 1
+#define NUM_LATENCY_VALUES 512
+
+enum{
+ START = 0,
+ END
+};
+
+enum {
+ LATENCY_GRANT_ALL = 0,
+ LATENCY_GRANT_NL,
+ LATENCY_GRANT_CR,
+ LATENCY_GRANT_CW,
+ LATENCY_GRANT_PR,
+ LATENCY_GRANT_PW,
+ LATENCY_GRANT_EX,
+ LATENCY_DEMOTE_ALL,
+ LATENCY_DEMOTE_NL,
+ LATENCY_DEMOTE_CR,
+ LATENCY_DEMOTE_CW,
+ LATENCY_DEMOTE_PR,
+ LATENCY_DEMOTE_PW,
+ LATENCY_DEMOTE_EX,
+ LATENCY_QUEUE_ALL,
+ LATENCY_QUEUE_NL,
+ LATENCY_QUEUE_CR,
+ LATENCY_QUEUE_CW,
+ LATENCY_QUEUE_PR,
+ LATENCY_QUEUE_PW,
+ LATENCY_QUEUE_EX,
+ NUM_LATENCY_STATS
+};
+
+struct latency_data {
+ uint32_t lock_type;
+ uint64_t number;
+ int64_t usecs;
+};
+
+struct latency {
+ struct latency_data values [NUM_LATENCY_STATS * NUM_LATENCY_VALUES * 2]; /* START and STOP values */
+ int counter [NUM_LATENCY_STATS];
+};
+
+extern int gfs2_latency_fetch(int, struct latency *, pmAtomValue *);
+extern int gfs2_extract_latency(unsigned int, unsigned int, int, char *, pmInDom);
+
+extern int latency_get_state();
+extern int latency_set_state(pmValueSet *vsp);
+
+#endif /* LATENCY_H */
diff --git a/src/pmdas/gfs2/pmda.c b/src/pmdas/gfs2/pmda.c
new file mode 100644
index 0000000..2090296
--- /dev/null
+++ b/src/pmdas/gfs2/pmda.c
@@ -0,0 +1,1063 @@
+/*
+ * Global Filesystem v2 (GFS2) PMDA
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+
+#include "pmdagfs2.h"
+
+#include <sys/types.h>
+#include <ctype.h>
+
+static char *gfs2_sysfsdir = "/sys/kernel/debug/gfs2";
+static char *gfs2_sysdir = "/sys/fs/gfs2";
+
+pmdaIndom indomtable[] = {
+ { .it_indom = GFS_FS_INDOM },
+};
+
+#define INDOM(x) (indomtable[x].it_indom)
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ *
+ */
+pmdaMetric metrictable[] = {
+ /* GLOCK */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLOCKS, GLOCKS_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLOCKS, GLOCKS_SHARED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLOCKS, GLOCKS_UNLOCKED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLOCKS, GLOCKS_DEFERRED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLOCKS, GLOCKS_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ /* SBSTATS */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SRTT),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SRTTVAR),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) } },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SRTTB),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SRTTVARB),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SIRT),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_SIRTVAR),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,PM_TIME_NSEC,0,0,1,0) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_DCOUNT),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_SBSTATS, LOCKSTAT_QCOUNT),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ /* GLSTATS */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_TRANS),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_INODE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_RGRP),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_META),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_IOPEN),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_FLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_QUOTA),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_GLSTATS, GLSTATS_JOURNAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ /* TRACEPOINTS */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_GLOCK_CHANGEDTARGET),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKSTATE_GLOCK_MISSEDTARGET),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKPUT_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_REQUESTED_REMOTE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_DEMOTERQ_REQUESTED_LOCAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_FIRST_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PROMOTE_OTHER_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_QUEUE_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_NULLLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_CONCURRENTWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDREAD),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_PROTECTEDWRITE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKQUEUE_DEQUEUE_EXCLUSIVE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_TRANS),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_INDOE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_RGRP),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_META),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_IOPEN),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_FLOCK),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_QUOTA),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_GLOCKLOCKTIME_JOURNAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PIN_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PIN_PINTOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PIN_UNPINTOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_PIN_LONGESTPINNED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_LOGFLUSH_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_LOGBLOCKS_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_AILFLUSH_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BLOCKALLOC_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BLOCKALLOC_FREE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BLOCKALLOC_USED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BLOCKALLOC_DINODE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BLOCKALLOC_UNLINKED),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BMAP_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BMAP_CREATE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_BMAP_NOCREATE),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_RS_TOTAL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_RS_DEL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_RS_TDEL),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_RS_INS),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_TRACEPOINTS, FTRACE_RS_CLM),
+ PM_TYPE_U64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ /* WORST_GLOCKS */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_LOCK_TYPE),
+ PM_TYPE_U32, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_NUMBER),
+ PM_TYPE_U32, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SRTT),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SRTTVAR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SRTTB),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SRTTVARB),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SIRT),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_SIRTVAR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_DLM),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_WORSTGLOCK, WORSTGLOCK_QUEUE),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+ /* LATENCY */
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_ALL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_NL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_CR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_CW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_PR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_PW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_GRANT_EX),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_ALL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_NL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_CR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_CW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_PR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_PW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_DEMOTE_EX),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_ALL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_NL),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_CR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_CW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_PR),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_PW),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ { .m_desc = {
+ PMDA_PMID(CLUSTER_LATENCY, LATENCY_QUEUE_EX),
+ PM_TYPE_64, GFS_FS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,PM_TIME_MSEC,PM_COUNT_ONE) }, },
+ /* CONTROL */
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_ALL),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_GLOCK_STATE_CHANGE),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_GLOCK_PUT),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_DEMOTE_RQ),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_PROMOTE),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_GLOCK_QUEUE),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_GLOCK_LOCK_TIME),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_PIN),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_LOG_FLUSH),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_LOG_BLOCKS),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_AIL_FLUSH),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_BLOCK_ALLOC),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_BMAP),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_RS),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_BUFFER_SIZE_KB),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_GLOBAL_TRACING),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_WORSTGLOCK),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_LATENCY),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { NULL, {
+ PMDA_PMID(CLUSTER_CONTROL, CONTROL_FTRACE_GLOCK_THRESHOLD),
+ PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+int
+metrictable_size(void)
+{
+ return sizeof(metrictable)/sizeof(metrictable[0]);
+}
+
+static dev_t
+gfs2_device_identifier(const char *name)
+{
+ char buffer[4096];
+ int major, minor;
+ dev_t dev_id;
+ FILE *fp;
+
+ dev_id = makedev(0, 0); /* Error case */
+
+ /* gfs2_glock_lock_time requires block device id for each filesystem
+ * in order to match to the lock data, this info can be found in
+ * /sys/fs/gfs2/NAME/id, we extract the data and store it in gfs2_fs->dev
+ *
+ */
+ snprintf(buffer, sizeof(buffer), "%s/%s/id", gfs2_sysdir, name);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if ((fp = fopen(buffer, "r")) == NULL)
+ return oserror();
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ sscanf(buffer, "%d:%d", &major, &minor);
+ dev_id = makedev(major, minor);
+ }
+ fclose(fp);
+
+ return dev_id;
+}
+
+/*
+ * Update the GFS2 filesystems instance domain. This will change
+ * as filesystems are mounted and unmounted (and re-mounted, etc).
+ * Using the pmdaCache interfaces simplifies things and provides us
+ * with guarantees around consistent instance numbering in all of
+ * those interesting corner cases.
+ */
+static int
+gfs2_instance_refresh(void)
+{
+ int i, sts, count, gfs2_status;
+ struct dirent **files;
+ pmInDom indom = INDOM(GFS_FS_INDOM);
+
+ pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+
+ /* update indom cache based on scan of /sys/fs/gfs2 */
+ count = scandir(gfs2_sysdir, &files, NULL, NULL);
+ if (count < 0) { /* give some feedback as to GFS2 kernel state */
+ if (oserror() == EPERM)
+ gfs2_status = PM_ERR_PERMISSION;
+ else if (oserror() == ENOENT)
+ gfs2_status = PM_ERR_AGAIN; /* we might see a mount later */
+ else
+ gfs2_status = PM_ERR_APPVERSION;
+ } else {
+ gfs2_status = 0; /* we possibly have stats available */
+ }
+
+ for (i = 0; i < count; i++) {
+ struct gfs2_fs *fs;
+ const char *name = files[i]->d_name;
+
+ if (name[0] == '.')
+ continue;
+
+ sts = pmdaCacheLookupName(indom, name, NULL, (void **)&fs);
+ if (sts == PM_ERR_INST || (sts >= 0 && fs == NULL)){
+ fs = calloc(1, sizeof(struct gfs2_fs));
+ if (fs == NULL)
+ return PM_ERR_AGAIN;
+
+ fs->dev_id = gfs2_device_identifier(name);
+
+ if ((major(fs->dev_id) == 0) && (minor(fs->dev_id) == 0)) {
+ free(fs);
+ return PM_ERR_AGAIN;
+ }
+ }
+ else if (sts < 0)
+ continue;
+
+ /* (re)activate this entry for the current query */
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, name, (void *)fs);
+ }
+
+ for (i = 0; i < count; i++)
+ free(files[i]);
+ if (count > 0)
+ free(files);
+ return gfs2_status;
+}
+
+static int
+gfs2_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ gfs2_instance_refresh();
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+gfs2_fetch_refresh(pmdaExt *pmda, int *need_refresh)
+{
+ pmInDom indom = INDOM(GFS_FS_INDOM);
+ struct gfs2_fs *fs;
+ char *name;
+ int i, sts;
+
+ if ((sts = gfs2_instance_refresh()) < 0)
+ return sts;
+
+ for (pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND);;) {
+ if ((i = pmdaCacheOp(indom, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ if (!pmdaCacheLookup(indom, i, &name, (void **)&fs) || !fs)
+ continue;
+ if (need_refresh[CLUSTER_GLOCKS])
+ gfs2_refresh_glocks(gfs2_sysfsdir, name, &fs->glocks);
+ if (need_refresh[CLUSTER_SBSTATS])
+ gfs2_refresh_sbstats(gfs2_sysfsdir, name, &fs->sbstats);
+ if (need_refresh[CLUSTER_GLSTATS])
+ gfs2_refresh_glstats(gfs2_sysfsdir, name, &fs->glstats);
+ }
+
+ if (need_refresh[CLUSTER_TRACEPOINTS] || need_refresh[CLUSTER_WORSTGLOCK] || need_refresh[CLUSTER_LATENCY])
+ gfs2_refresh_ftrace_stats(indom);
+
+ return sts;
+}
+
+static int
+gfs2_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i, sts, need_refresh[NUM_CLUSTERS] = { 0 };
+
+ for (i = 0; i < numpmid; i++) {
+ __pmID_int *idp = (__pmID_int *)&(pmidlist[i]);
+ if (idp->cluster < NUM_CLUSTERS)
+ need_refresh[idp->cluster]++;
+ }
+
+ if ((sts = gfs2_fetch_refresh(pmda, need_refresh)) < 0)
+ return sts;
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+gfs2_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ struct gfs2_fs *fs;
+ int sts;
+
+ switch (idp->cluster) {
+ case CLUSTER_GLOCKS:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_glocks_fetch(idp->item, &fs->glocks, atom);
+
+ case CLUSTER_SBSTATS:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_sbstats_fetch(idp->item, &fs->sbstats, atom);
+
+ case CLUSTER_GLSTATS:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_glstats_fetch(idp->item, &fs->glstats, atom);
+
+ case CLUSTER_TRACEPOINTS:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_ftrace_fetch(idp->item, &fs->ftrace, atom);
+
+ case CLUSTER_WORSTGLOCK:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void**)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_worst_glock_fetch(idp->item, &fs->worst_glock, atom);
+
+ case CLUSTER_LATENCY:
+ sts = pmdaCacheLookup(INDOM(GFS_FS_INDOM), inst, NULL, (void**)&fs);
+ if (sts < 0)
+ return sts;
+ return gfs2_latency_fetch(idp->item, &fs->latency, atom);
+
+ case CLUSTER_CONTROL:
+ return gfs2_control_fetch(idp->item, atom);
+
+ default: /* unknown cluster */
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
+
+/*
+ * Enable all tracepoints by default on init
+ */
+static void
+gfs2_tracepoints_init()
+{
+ FILE *fp;
+
+ fp = fopen("/sys/kernel/debug/tracing/events/gfs2/enable", "w");
+ if (!fp) {
+ fprintf(stderr, "Unable to automatically enable GFS2 tracepoints");
+ } else {
+ fprintf(fp, "%d\n", 1);
+ fclose(fp);
+ }
+}
+
+/*
+ * Set default trace_pipe buffer size per cpu on init (32MB)
+ */
+static void
+gfs2_buffer_default_size_set()
+{
+ FILE *fp;
+
+ fp = fopen("/sys/kernel/debug/tracing/buffer_size_kb", "w");
+ if (!fp) {
+ fprintf(stderr, "Unable to set default buffer size");
+ } else {
+ fprintf(fp, "%d\n", 32768); /* Default 32MB per cpu */
+ fclose(fp);
+ }
+}
+
+/*
+ * Some version of ftrace are missing the irq-info option which alters the
+ * trace-pipe output, because of this we check for the option and if exists
+ * we switch off irq info output in trace_pipe
+ */
+static void
+gfs2_ftrace_irq_info_set()
+{
+ FILE *fp;
+
+ fp = fopen("/sys/kernel/debug/tracing/options/irq-info", "w");
+ if (fp) {
+ /* We only need to set value if irq-info exists */
+ fprintf(fp, "0"); /* Switch off irq-info in trace_pipe */
+ fclose(fp);
+ }
+}
+
+static int
+gfs2_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ int sts = 0;
+ pmValueSet *vsp;
+ __pmID_int *pmidp;
+
+ for (i = 0; i < result->numpmid && !sts; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster == CLUSTER_CONTROL && pmidp->item <= CONTROL_BUFFER_SIZE_KB) {
+ sts = gfs2_control_set_value(control_locations[pmidp->item], vsp);
+ }
+
+ if (pmidp->cluster == CLUSTER_CONTROL && pmidp->item == CONTROL_WORSTGLOCK) {
+ sts = worst_glock_set_state(vsp);
+ }
+
+ if (pmidp->cluster == CLUSTER_CONTROL && pmidp->item == CONTROL_LATENCY) {
+ sts = latency_set_state(vsp);
+ }
+
+ if (pmidp->cluster == CLUSTER_CONTROL && pmidp->item == CONTROL_FTRACE_GLOCK_THRESHOLD) {
+ sts = ftrace_set_threshold(vsp);
+ }
+ }
+ return sts;
+}
+
+static int
+gfs2_text(int ident, int type, char **buf, pmdaExt *pmda)
+{
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ int sts = pmdaDynamicLookupText(ident, type, buf, pmda);
+ if (sts != -ENOENT)
+ return sts;
+ }
+ return pmdaText(ident, type, buf, pmda);
+}
+
+static int
+gfs2_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ __pmnsTree *tree = pmdaDynamicLookupName(pmda, name);
+ return pmdaTreePMID(tree, name, pmid);
+}
+
+static int
+gfs2_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ __pmnsTree *tree = pmdaDynamicLookupPMID(pmda, pmid);
+ return pmdaTreeName(tree, pmid, nameset);
+}
+
+static int
+gfs2_children(const char *name, int flag, char ***kids, int **sts, pmdaExt *pmda)
+{
+ __pmnsTree *tree = pmdaDynamicLookupName(pmda, name);
+ return pmdaTreeChildren(tree, name, flag, kids, sts);
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+gfs2_init(pmdaInterface *dp)
+{
+ int nindoms = sizeof(indomtable)/sizeof(indomtable[0]);
+ int nmetrics = sizeof(metrictable)/sizeof(metrictable[0]);
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.instance = gfs2_instance;
+ dp->version.four.store = gfs2_store;
+ dp->version.four.fetch = gfs2_fetch;
+ dp->version.four.text = gfs2_text;
+ dp->version.four.pmid = gfs2_pmid;
+ dp->version.four.name = gfs2_name;
+ dp->version.four.children = gfs2_children;
+ pmdaSetFetchCallBack(dp, gfs2_fetchCallBack);
+
+ gfs2_sbstats_init(metrictable, nmetrics);
+ gfs2_worst_glock_init(metrictable, nmetrics);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, indomtable, nindoms, metrictable, nmetrics);
+
+ /* Set defaults */
+ gfs2_tracepoints_init(); /* Enables gfs2 tracepoints */
+ gfs2_buffer_default_size_set(); /* Sets default buffer size */
+ gfs2_ftrace_irq_info_set(); /* Disables irq-info output with trace_pipe */
+}
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:l:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ snprintf(helppath, sizeof(helppath), "%s%c" "gfs2" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, GFS2, "gfs2.log", helppath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ pmdaOpenLog(&dispatch);
+ gfs2_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/gfs2/pmdagfs2.h b/src/pmdas/gfs2/pmdagfs2.h
new file mode 100644
index 0000000..f897d13
--- /dev/null
+++ b/src/pmdas/gfs2/pmdagfs2.h
@@ -0,0 +1,57 @@
+/*
+ * Global Filesystem v2 (GFS2) PMDA
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef PMDAGFS2_H
+#define PMDAGFS2_H
+
+#include "glocks.h"
+#include "sbstats.h"
+#include "glstats.h"
+#include "worst_glock.h"
+#include "ftrace.h"
+#include "latency.h"
+#include "control.h"
+
+enum {
+ CLUSTER_GLOCKS = 0, /* 0 - /sys/kernel/debug/gfs2/<fs>/glocks */
+ CLUSTER_SBSTATS, /* 1 - /sys/kernel/debug/gfs2/<fs>/sbstats */
+ CLUSTER_GLSTATS, /* 2 - /sys/kernel/debug/gfs2/<fs>/glstats */
+ CLUSTER_TRACEPOINTS, /* 3 - /sys/kernel/debug/tracing/events/gfs2/ */
+ CLUSTER_WORSTGLOCK, /* 4 - Custom metric for worst glock */
+ CLUSTER_LATENCY, /* 5 - Custom metric for working out latency of filesystem operations */
+ CLUSTER_CONTROL, /* 6 - Control for specific tracepoint enabling (for installs without all of the GFS2 tracepoints) */
+ NUM_CLUSTERS
+};
+
+enum {
+ GFS_FS_INDOM = 0, /* 0 -- mounted gfs filesystem names */
+ NUM_INDOMS
+};
+
+struct gfs2_fs {
+ dev_t dev_id;
+ struct glocks glocks;
+ struct sbstats sbstats;
+ struct glstats glstats;
+ struct ftrace ftrace;
+ struct worst_glock worst_glock;
+ struct latency latency;
+};
+
+extern pmdaMetric metrictable[];
+extern int metrictable_size();
+
+#endif /*PMDAGFS2_H*/
diff --git a/src/pmdas/gfs2/pmns b/src/pmdas/gfs2/pmns
new file mode 100644
index 0000000..7d8a13a
--- /dev/null
+++ b/src/pmdas/gfs2/pmns
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Changed GFS2:*:* to root number 115 to remove ASCII parse errors during
+ * install of PMDA whilst testing.
+ */
+gfs2 {
+ glocks
+ sbstats GFS2:*:*
+ glstats
+ tracepoints
+ worst_glock GFS2:*:*
+ latency
+ control
+}
+
+gfs2.glocks {
+ total GFS2:0:0
+ shared GFS2:0:1
+ unlocked GFS2:0:2
+ deferred GFS2:0:3
+ exclusive GFS2:0:4
+}
+
+gfs2.glstats {
+ total GFS2:2:0
+ trans GFS2:2:1
+ inode GFS2:2:2
+ rgrp GFS2:2:3
+ meta GFS2:2:4
+ iopen GFS2:2:5
+ flock GFS2:2:6
+ quota GFS2:2:8
+ journal GFS2:2:9
+}
+
+gfs2.tracepoints {
+ glock_state_change
+ glock_put
+ demote_rq
+ promote
+ glock_queue
+ glock_lock_time
+ pin
+ log_flush
+ log_block
+ ail_flush
+ block_alloc
+ bmap
+ rs
+}
+
+gfs2.tracepoints.glock_state_change {
+ total GFS2:3:0
+ null_lock GFS2:3:1
+ concurrent_read GFS2:3:2
+ concurrent_write GFS2:3:3
+ protected_read GFS2:3:4
+ protected_write GFS2:3:5
+ exclusive GFS2:3:6
+ glocks
+}
+
+gfs2.tracepoints.glock_state_change.glocks {
+ changed_target GFS2:3:7
+ missed_target GFS2:3:8
+}
+
+gfs2.tracepoints.glock_put {
+ total GFS2:3:9
+ null_lock GFS2:3:10
+ concurrent_read GFS2:3:11
+ concurrent_write GFS2:3:12
+ protected_read GFS2:3:13
+ protected_write GFS2:3:14
+ exclusive GFS2:3:15
+}
+
+gfs2.tracepoints.demote_rq {
+ total GFS2:3:16
+ null_lock GFS2:3:17
+ concurrent_read GFS2:3:18
+ concurrent_write GFS2:3:19
+ protected_read GFS2:3:20
+ protected_write GFS2:3:21
+ exclusive GFS2:3:22
+ requested
+}
+
+gfs2.tracepoints.demote_rq.requested {
+ remote GFS2:3:23
+ local GFS2:3:24
+}
+
+gfs2.tracepoints.promote {
+ total GFS2:3:25
+ first
+ other
+}
+
+gfs2.tracepoints.promote.first {
+ null_lock GFS2:3:26
+ concurrent_read GFS2:3:27
+ concurrent_write GFS2:3:28
+ protected_read GFS2:3:29
+ protected_write GFS2:3:30
+ exclusive GFS2:3:31
+}
+
+gfs2.tracepoints.promote.other {
+ null_lock GFS2:3:32
+ concurrent_read GFS2:3:33
+ concurrent_write GFS2:3:34
+ protected_read GFS2:3:35
+ protected_write GFS2:3:36
+ exclusive GFS2:3:37
+}
+
+gfs2.tracepoints.glock_queue {
+ total GFS2:3:38
+ queue
+ dequeue
+}
+
+gfs2.tracepoints.glock_queue.queue {
+ total GFS2:3:39
+ null_lock GFS2:3:40
+ concurrent_read GFS2:3:41
+ concurrent_write GFS2:3:42
+ protected_read GFS2:3:43
+ protected_write GFS2:3:44
+ exclusive GFS2:3:45
+}
+
+gfs2.tracepoints.glock_queue.dequeue {
+ total GFS2:3:46
+ null_lock GFS2:3:47
+ concurrent_read GFS2:3:48
+ concurrent_write GFS2:3:49
+ protected_read GFS2:3:50
+ protected_write GFS2:3:51
+ exclusive GFS2:3:52
+}
+
+gfs2.tracepoints.glock_lock_time {
+ total GFS2:3:53
+ trans GFS2:3:54
+ inode GFS2:3:55
+ rgrp GFS2:3:56
+ meta GFS2:3:57
+ iopen GFS2:3:58
+ flock GFS2:3:59
+ quota GFS2:3:60
+ journal GFS2:3:61
+}
+
+gfs2.tracepoints.pin {
+ total GFS2:3:62
+ pin_total GFS2:3:63
+ unpin_total GFS2:3:64
+ longest_pinned GFS2:3:65
+}
+
+gfs2.tracepoints.log_flush {
+ total GFS2:3:66
+}
+
+gfs2.tracepoints.log_block {
+ total GFS2:3:67
+}
+
+gfs2.tracepoints.ail_flush {
+ total GFS2:3:68
+}
+
+gfs2.tracepoints.block_alloc {
+ total GFS2:3:69
+ free GFS2:3:70
+ used GFS2:3:71
+ dinode GFS2:3:72
+ unlinked GFS2:3:73
+}
+
+gfs2.tracepoints.bmap {
+ total GFS2:3:74
+ create GFS2:3:75
+ nocreate GFS2:3:76
+}
+
+gfs2.tracepoints.rs {
+ total GFS2:3:77
+ del GFS2:3:78
+ tdel GFS2:3:79
+ ins GFS2:3:80
+ clm GFS2:3:81
+}
+
+gfs2.latency {
+ grant
+ demote
+ queue
+}
+
+gfs2.latency.grant {
+ all GFS2:5:0
+ null_lock GFS2:5:1
+ concurrent_read GFS2:5:2
+ concurrent_write GFS2:5:3
+ protected_read GFS2:5:4
+ protected_write GFS2:5:5
+ exclusive GFS2:5:6
+}
+
+gfs2.latency.demote {
+ all GFS2:5:7
+ null_lock GFS2:5:8
+ concurrent_read GFS2:5:9
+ concurrent_write GFS2:5:10
+ protected_read GFS2:5:11
+ protected_write GFS2:5:12
+ exclusive GFS2:5:13
+}
+
+gfs2.latency.queue {
+ all GFS2:5:14
+ null_lock GFS2:5:15
+ concurrent_read GFS2:5:16
+ concurrent_write GFS2:5:17
+ protected_read GFS2:5:18
+ protected_write GFS2:5:19
+ exclusive GFS2:5:20
+}
+
+gfs2.control {
+ tracepoints
+ buffer_size_kb GFS2:6:14
+ global_tracing GFS2:6:15
+ worst_glock GFS2:6:16
+ latency GFS2:6:17
+ glock_threshold GFS2:6:18
+}
+
+gfs2.control.tracepoints {
+ all GFS2:6:0
+ glock_state_change GFS2:6:1
+ glock_put GFS2:6:2
+ demote_rq GFS2:6:3
+ promote GFS2:6:4
+ glock_queue GFS2:6:5
+ glock_lock_time GFS2:6:6
+ pin GFS2:6:7
+ log_flush GFS2:6:8
+ log_blocks GFS2:6:9
+ ail_flush GFS2:6:10
+ block_alloc GFS2:6:11
+ bmap GFS2:6:12
+ rs GFS2:6:13
+}
diff --git a/src/pmdas/gfs2/root b/src/pmdas/gfs2/root
new file mode 100644
index 0000000..5f8b73b
--- /dev/null
+++ b/src/pmdas/gfs2/root
@@ -0,0 +1,16 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root {
+ gfs2
+
+}
+
+#ifndef GFS2
+#define GFS2 115
+#endif
+
+#include "pmns"
diff --git a/src/pmdas/gfs2/sbstats.c b/src/pmdas/gfs2/sbstats.c
new file mode 100644
index 0000000..d8d4668
--- /dev/null
+++ b/src/pmdas/gfs2/sbstats.c
@@ -0,0 +1,264 @@
+/*
+ * GFS2 sbstats sysfs file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include "pmdagfs2.h"
+
+#include <ctype.h>
+
+static const char *stattype[] = {
+ [LOCKSTAT_SRTT] = "srtt",
+ [LOCKSTAT_SRTTVAR] = "srttvar",
+ [LOCKSTAT_SRTTB] = "srttb",
+ [LOCKSTAT_SRTTVARB] = "srttvarb",
+ [LOCKSTAT_SIRT] = "sirt",
+ [LOCKSTAT_SIRTVAR] = "sirtvar",
+ [LOCKSTAT_DCOUNT] = "dlm",
+ [LOCKSTAT_QCOUNT] = "queue",
+};
+
+static const char *stattext[] = {
+ [LOCKSTAT_SRTT] = "Non-blocking smoothed round trip time",
+ [LOCKSTAT_SRTTVAR] = "Non-blocking smoothed variance",
+ [LOCKSTAT_SRTTB] = "Blocking smoothed round trip time",
+ [LOCKSTAT_SRTTVARB] = "Blocking smoothed variance",
+ [LOCKSTAT_SIRT] = "Smoothed inter-request time",
+ [LOCKSTAT_SIRTVAR] = "Smoothed inter-request variance",
+ [LOCKSTAT_DCOUNT] = "Count of Distributed Lock Manager requests",
+ [LOCKSTAT_QCOUNT] = "Count of gfs2_holder queues",
+};
+
+static const char *locktype[] = {
+ [LOCKTYPE_RESERVED] = "reserved",
+ [LOCKTYPE_NONDISK] = "nondisk",
+ [LOCKTYPE_INODE] = "inode",
+ [LOCKTYPE_RGRB] = "rgrp",
+ [LOCKTYPE_META] = "meta",
+ [LOCKTYPE_IOPEN] = "iopen",
+ [LOCKTYPE_FLOCK] = "flock",
+ [LOCKTYPE_PLOCK] = "plock",
+ [LOCKTYPE_QUOTA] = "quota",
+ [LOCKTYPE_JOURNAL] = "journal",
+};
+
+int
+gfs2_sbstats_fetch(int item, struct sbstats *fs, pmAtomValue *atom)
+{
+ /* Check for valid metric count */
+ if (item < 0 || item >= SBSTATS_COUNT)
+ return PM_ERR_PMID;
+
+ /* Check for no values recorded */
+ if(fs->values[item] == UINT64_MAX)
+ return 0;
+
+ atom->ull = fs->values[item];
+ return 1;
+}
+
+int
+gfs2_refresh_sbstats(const char *sysfs, const char *name, struct sbstats *sb)
+{
+ unsigned int id = 0;
+ char buffer[4096];
+ FILE *fp;
+
+ /* Reset all counter for this fs */
+ memset(sb, 0, sizeof(*sb));
+
+ snprintf(buffer, sizeof(buffer), "%s/%s/sbstats", sysfs, name);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if ((fp = fopen(buffer, "r")) == NULL){
+ /*
+ * We set the values to UINT64_MAX to signify we have no
+ * current values (no metric support or debugfs not mounted)
+ *
+ */
+ memset(sb, -1, sizeof(*sb));
+ return -oserror();
+ }
+
+ /*
+ * Read through sbstats file one line at a time. This is called on each
+ * and every fetch request, so should be as quick as we can make it.
+ *
+ * Once we've skipped over the heading, the format is fixed so to make this
+ * faster (we expect the kernel has a fixed set of types/stats) we go right
+ * ahead and index directly into the stats structure for each line. We do
+ * check validity too - if there's a mismatch, we throw our toys.
+ *
+ * Also, we aggregate the per-CPU data for each statistic - we could go for
+ * separate per-CPU metrics as well, but for now just keep it simple until
+ * there's a real need for that extra complexity.
+ *
+ */
+ while (fgets(buffer, sizeof(buffer), fp) != NULL) {
+ char *typestr, *statstr, *end;
+ char *p = buffer;
+
+ if (strncmp(p, "type", 4) == 0)
+ continue;
+ if (id > SBSTATS_COUNT)
+ break;
+
+ typestr = p;
+ for (typestr = p; !isspace((int)*p); p++) { } /* skip lock type */
+ for (; isspace((int)*p); p++) { *p = '\0'; } /* eat whitespace */
+ for (statstr = p; *p != ':'; p++) { } /* skip stat type */
+ *p = '\0';
+
+ /* verify that the id we are up to matches what we see in the file */
+ unsigned int type = id / NUM_LOCKSTATS;
+ unsigned int stat = id % NUM_LOCKSTATS;
+ if (strcmp(typestr, locktype[type]) != 0) {
+ __pmNotifyErr(LOG_ERR,
+ "unexpected sbstat type \"%s\" (want %s at line %u)",
+ typestr, locktype[type], id);
+ break; /* eh? */
+ }
+ if (strcmp(statstr, stattype[stat]) != 0) {
+ __pmNotifyErr(LOG_ERR,
+ "unexpected sbstat stat \"%s\" (want %s at line %u)",
+ statstr, stattype[stat], id);
+ break; /* wha? */
+ }
+
+ /* decode all of the (per-CPU) values until the end of line */
+ for (p++; *p != '\0'; p++) {
+ __uint64_t value;
+
+ value = strtoull(p, &end, 10);
+ if (end == p)
+ break;
+ sb->values[id] += value;
+ p = end;
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO,
+ "got expected sbstat type \"%s\", stat \"%s\" at line %u",
+ typestr, statstr, id);
+
+ /* now we can move on to the next metric (on the next line) */
+ id++;
+ }
+ fclose(fp);
+ return (id == SBSTATS_COUNT) ? 0 : -EINVAL;
+}
+
+static void
+add_pmns_node(__pmnsTree *tree, int domain, int cluster, int lock, int stat)
+{
+ char entry[64];
+ pmID pmid = pmid_build(domain, cluster, (lock * NUM_LOCKSTATS) + stat);
+
+ snprintf(entry, sizeof(entry),
+ "gfs2.sbstats.%s.%s", locktype[lock], stattype[stat]);
+ __pmAddPMNSNode(tree, pmid, entry);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "GFS2 sbstats added %s (%s)", entry, pmIDStr(pmid));
+}
+
+static int
+refresh_sbstats(pmdaExt *pmda, __pmnsTree **tree)
+{
+ int t, s, sts;
+ static __pmnsTree *sbstats_tree;
+
+ if (sbstats_tree) {
+ *tree = sbstats_tree;
+ } else if ((sts = __pmNewPMNS(&sbstats_tree)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create sbstats names: %s\n",
+ pmProgname, pmErrStr(sts));
+ *tree = NULL;
+ } else {
+ for (t = 0; t < NUM_LOCKTYPES; t++)
+ for (s = 0; s < NUM_LOCKSTATS; s++)
+ add_pmns_node(sbstats_tree, pmda->e_domain, CLUSTER_SBSTATS, t, s);
+ *tree = sbstats_tree;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Create a new metric table entry based on an existing one.
+ *
+ */
+static void
+refresh_metrictable(pmdaMetric *source, pmdaMetric *dest, int lock)
+{
+ int item = pmid_item(source->m_desc.pmid);
+ int domain = pmid_domain(source->m_desc.pmid);
+ int cluster = pmid_cluster(source->m_desc.pmid);
+
+ memcpy(dest, source, sizeof(pmdaMetric));
+ item += lock * NUM_LOCKSTATS;
+ dest->m_desc.pmid = pmid_build(domain, cluster, item);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "GFS2 sbstats refresh_metrictable: (%p -> %p) "
+ "metric ID dup: %d.%d.%d -> %d.%d.%d\n",
+ source, dest, domain, cluster,
+ pmid_item(source->m_desc.pmid), domain, cluster, item);
+}
+
+/*
+ * Used to answer the question: how much extra space needs to be
+ * allocated in the metric table for (dynamic) sbstats metrics?
+ *
+ */
+static void
+size_metrictable(int *total, int *trees)
+{
+ *total = NUM_LOCKSTATS;
+ *trees = NUM_LOCKTYPES;
+}
+
+static int
+sbstats_text(pmdaExt *pmda, pmID pmid, int type, char **buf)
+{
+ int item = pmid_item(pmid);
+ static char text[128];
+
+ if (pmid_cluster(pmid) != CLUSTER_SBSTATS)
+ return PM_ERR_PMID;
+ if (item < 0 || item >= SBSTATS_COUNT)
+ return PM_ERR_PMID;
+ snprintf(text, sizeof(text), "%s for %s glocks",
+ stattext[item % NUM_LOCKSTATS], locktype[item / NUM_LOCKSTATS]);
+
+ *buf = text;
+
+ return 0;
+}
+
+void
+gfs2_sbstats_init(pmdaMetric *metrics, int nmetrics)
+{
+ int set[] = { CLUSTER_SBSTATS };
+
+ pmdaDynamicPMNS("gfs2.sbstats",
+ set, sizeof(set)/sizeof(int),
+ refresh_sbstats, sbstats_text,
+ refresh_metrictable, size_metrictable,
+ metrics, nmetrics);
+}
diff --git a/src/pmdas/gfs2/sbstats.h b/src/pmdas/gfs2/sbstats.h
new file mode 100644
index 0000000..5e71791
--- /dev/null
+++ b/src/pmdas/gfs2/sbstats.h
@@ -0,0 +1,56 @@
+/*
+ * GFS2 sbstats sysfs file statistics.
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef SBSTATS_H
+#define SBSTATS_H
+
+enum {
+ LOCKSTAT_SRTT = 0, /* Non-blocking smoothed round trip time */
+ LOCKSTAT_SRTTVAR, /* Non-blocking smoothed variance */
+ LOCKSTAT_SRTTB, /* Blocking smoothed round trip time */
+ LOCKSTAT_SRTTVARB, /* Blocking smoothed variance */
+ LOCKSTAT_SIRT, /* Smoothed inter-request time */
+ LOCKSTAT_SIRTVAR, /* Smoothed inter-request variance */
+ LOCKSTAT_DCOUNT, /* Count of DLM requests */
+ LOCKSTAT_QCOUNT, /* Count of gfs2_holder queues */
+ NUM_LOCKSTATS
+};
+
+enum {
+ LOCKTYPE_RESERVED = 0,
+ LOCKTYPE_NONDISK,
+ LOCKTYPE_INODE,
+ LOCKTYPE_RGRB,
+ LOCKTYPE_META,
+ LOCKTYPE_IOPEN,
+ LOCKTYPE_FLOCK,
+ LOCKTYPE_PLOCK,
+ LOCKTYPE_QUOTA,
+ LOCKTYPE_JOURNAL,
+ NUM_LOCKTYPES
+};
+
+#define SBSTATS_COUNT (NUM_LOCKSTATS*NUM_LOCKTYPES)
+
+struct sbstats {
+ __uint64_t values[SBSTATS_COUNT];
+};
+
+extern void gfs2_sbstats_init(pmdaMetric *, int);
+extern int gfs2_sbstats_fetch(int, struct sbstats *, pmAtomValue *);
+extern int gfs2_refresh_sbstats(const char *, const char *, struct sbstats *);
+
+#endif /* SBSTATS_H */
diff --git a/src/pmdas/gfs2/worst_glock.c b/src/pmdas/gfs2/worst_glock.c
new file mode 100644
index 0000000..ed851c3
--- /dev/null
+++ b/src/pmdas/gfs2/worst_glock.c
@@ -0,0 +1,391 @@
+/*
+ * GFS2 gfs2_glock_lock_time trace-point metrics.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "pmdagfs2.h"
+
+#include "ftrace.h"
+#include "worst_glock.h"
+
+#include <string.h>
+#include <inttypes.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+static struct glock glock_data;
+static int reset_flag;
+
+static int worst_glock_state = DEFAULT_WORST_GLOCK_STATE;
+
+static const char *stattype[] = {
+ [WORSTGLOCK_LOCK_TYPE] = "lock_type",
+ [WORSTGLOCK_NUMBER] = "number",
+ [WORSTGLOCK_SRTT] = "srtt",
+ [WORSTGLOCK_SRTTVAR] = "srttvar",
+ [WORSTGLOCK_SRTTB] = "srttb",
+ [WORSTGLOCK_SRTTVARB] = "srttvarb",
+ [WORSTGLOCK_SIRT] = "sirt",
+ [WORSTGLOCK_SIRTVAR] = "sirtvar",
+ [WORSTGLOCK_DLM] = "dlm",
+ [WORSTGLOCK_QUEUE] = "queue",
+};
+
+static const char *stattext[] = {
+ [WORSTGLOCK_LOCK_TYPE] = "Glock type number",
+ [WORSTGLOCK_NUMBER] = "Inode or resource group number",
+ [WORSTGLOCK_SRTT] = "Non-blocking smoothed round trip time",
+ [WORSTGLOCK_SRTTVAR] = "Non-blocking smoothed variance",
+ [WORSTGLOCK_SRTTB] = "Blocking smoothed round trip time",
+ [WORSTGLOCK_SRTTVARB] = "Blocking smoothed variance",
+ [WORSTGLOCK_SIRT] = "Smoothed Inter-request time",
+ [WORSTGLOCK_SIRTVAR] = "Smoothed Inter-request variance",
+ [WORSTGLOCK_DLM] = "Count of Distributed Lock Manager requests",
+ [WORSTGLOCK_QUEUE] = "Count of gfs2_holder queues",
+};
+
+static const char *topnum[] = {
+ [TOPNUM_FIRST] = "first",
+ [TOPNUM_SECOND] = "second",
+ [TOPNUM_THIRD] = "third",
+ [TOPNUM_FOURTH] = "fourth",
+ [TOPNUM_FIFTH] = "fifth",
+ [TOPNUM_SIXTH] = "sixth",
+ [TOPNUM_SEVENTH] = "seventh",
+ [TOPNUM_EIGHTH] = "eighth",
+ [TOPNUM_NINTH] = "ninth",
+ [TOPNUM_TENTH] = "tenth",
+};
+
+/*
+ * Sets the value of worst_glock_state using pmstore, value
+ * must be 0 or 1.
+ */
+int
+worst_glock_set_state(pmValueSet *vsp)
+{
+ int value = vsp->vlist[0].value.lval;
+
+ if (value == 0 || value == 1) {
+ worst_glock_state = value;
+
+ return 0;
+ } else {
+ return PM_ERR_SIGN;
+ }
+}
+
+/*
+ * Used to see whether the worst_glock metrics are enabled or disabled. Should
+ * only return either 0 or 1.
+ */
+int
+worst_glock_get_state()
+{
+ return worst_glock_state;
+}
+
+/*
+ * Refreshing of the metrics for gfs2.lock_time, some of metrics are of
+ * a different typing.
+ */
+int
+gfs2_worst_glock_fetch(int item, struct worst_glock *worst_glock, pmAtomValue *atom)
+{
+ /* If we are assigning, we should set the reset flag for next assign */
+ reset_flag = 1;
+
+ int pmid = (item % 10);
+ int position = (item / 10);
+
+ /* Check if tracepoint is enabled */
+ if (worst_glock_get_state() == 0)
+ return 0;
+
+ /* Check to see if we have values to assign */
+ if (worst_glock->glocks[position].lock_type == WORSTGLOCK_INODE ||
+ worst_glock->glocks[position].lock_type == WORSTGLOCK_RGRP){
+ switch(pmid){
+ case WORSTGLOCK_LOCK_TYPE:
+ atom->ul = worst_glock->glocks[position].lock_type; /* Glock type number */
+ break;
+ case WORSTGLOCK_NUMBER:
+ atom->ull = worst_glock->glocks[position].number; /* Inode or resource group number */
+ break;
+ case WORSTGLOCK_SRTT:
+ atom->ll = worst_glock->glocks[position].srtt; /* Non blocking smoothed round trip time */
+ break;
+ case WORSTGLOCK_SRTTVAR:
+ atom->ll = worst_glock->glocks[position].srttvar; /* Non blocking smoothed variance */
+ break;
+ case WORSTGLOCK_SRTTB:
+ atom->ll = worst_glock->glocks[position].srttb; /* Blocking smoothed round trip time */
+ break;
+ case WORSTGLOCK_SRTTVARB:
+ atom->ll = worst_glock->glocks[position].srttvarb; /* Blocking smoothed variance */
+ break;
+ case WORSTGLOCK_SIRT:
+ atom->ll = worst_glock->glocks[position].sirt; /* Smoothed Inter-request time */
+ break;
+ case WORSTGLOCK_SIRTVAR:
+ atom->ll = worst_glock->glocks[position].sirtvar; /* Smoothed Inter-request variance */
+ break;
+ case WORSTGLOCK_DLM:
+ atom->ll = worst_glock->glocks[position].dlm; /* Count of dlm requests */
+ break;
+ case WORSTGLOCK_QUEUE:
+ atom->ll = worst_glock->glocks[position].queue; /* Count of gfs2_holder queues */
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ return 1; /* Return we have had values */
+ } else {
+ return 0; /* If we have no valid values */
+ }
+}
+
+/*
+ * Comparison function we compare the values; we return the lock which
+ * is deemed to be the worst.
+ */
+static int
+lock_comparison(const void *a, const void *b)
+{
+ struct glock *aa = (struct glock *)a;
+ struct glock *bb = (struct glock *)b;
+ int true_count = 0;
+
+ /* Case to deal with the empty entries */
+ if (aa->lock_type == 0)
+ return 1; /* Move empty a up the list, b moves down list */
+
+ if (bb->lock_type == 0)
+ return -1; /* Move a down the list, empty b moves up list*/
+
+ /* A sirt (LESS THAN) B sirt = A worse */
+ if (aa->sirtvar < bb->sirtvar)
+ true_count++;
+
+ /* A srtt (MORE THAN) B srtt = A worse */
+ if (aa->srttvarb > bb->srttvarb)
+ true_count++;
+
+ /* A srttb (MORE THAN) B srttb = A worse */
+ if (aa->srttvar > bb->srttvar)
+ true_count++;
+
+ /* If there are more counts where A is worse than B? */
+ if ( true_count > 1 ) {
+ return -1; /* a is worse than b (move a down the list) */
+ } else if ( true_count == 1 ){
+ /* Tie break condition */
+ if ( aa->dlm > bb->queue ) return -1; /* a is worse than b (move a down the list) */
+ }
+ return 1; /* b is worse than a (move b up the list) */
+}
+
+/*
+ * We loop through each of our available file-sytems, find the locks that corr-
+ * esspond to the filesystem. With these locks we find the worst and assign it
+ * to the filesystem before returning the metric values.
+ */
+static void
+worst_glock_assign_glocks(pmInDom gfs_fs_indom)
+{
+ int i, j, sts;
+ struct gfs2_fs *fs;
+
+ /* We walk through for each filesystem */
+ for (pmdaCacheOp(gfs_fs_indom, PMDA_CACHE_WALK_REWIND);;) {
+ if ((i = pmdaCacheOp(gfs_fs_indom, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ sts = pmdaCacheLookup(gfs_fs_indom, i, NULL, (void **)&fs);
+ if (sts != PMDA_CACHE_ACTIVE)
+ continue;
+
+ /* Clear old entries if reset is set */
+ if(reset_flag == 1){
+ memset(&fs->worst_glock, 0, sizeof(struct worst_glock));
+ reset_flag = 0;
+ }
+
+ /* Is the entry matching the filesystem we are on? */
+ if (fs->dev_id != glock_data.dev_id)
+ continue;
+
+ /* Check if we are updating an existing entry */
+ for (j = 0; j < WORST_GLOCK_TOP; j++) {
+ if ((fs->worst_glock.glocks[j].lock_type == glock_data.lock_type) &&
+ (fs->worst_glock.glocks[j].number == glock_data.number)) {
+ fs->worst_glock.glocks[j] = glock_data;
+ return;
+ }
+ }
+
+ /* If not place in next available slot */
+ if (fs->worst_glock.assigned_entries < WORST_GLOCK_TOP) {
+ fs->worst_glock.glocks[fs->worst_glock.assigned_entries] = glock_data;
+ fs->worst_glock.assigned_entries++;
+ } else {
+ fs->worst_glock.glocks[WORST_GLOCK_TOP] = glock_data; /* Place in slot 11 */
+ }
+ qsort(fs->worst_glock.glocks, (WORST_GLOCK_TOP + 1), sizeof(struct glock), lock_comparison);
+ }
+}
+
+/*
+ * We work out the individual metric values from our buffer input and store
+ * them for processing after all of the values have been extracted from the
+ * trace pipe.
+ */
+int
+gfs2_extract_worst_glock(char **buffer, pmInDom gfs_fs_indom)
+{
+ struct glock temp;
+ unsigned int major, minor;
+
+ /* Assign data */
+ sscanf(*buffer,
+ "gfs2_glock_lock_time: %"SCNu32",%"SCNu32" glock %"SCNu32":%"SCNu64" status:%*d flags:%*x tdiff:%*d srtt:%"SCNd64"/%"SCNd64" srttb:%"SCNd64"/%"SCNd64" sirt:%"SCNd64"/%"SCNd64" dcnt:%"SCNd64" qcnt:%"SCNd64,
+ &major,
+ &minor,
+ &temp.lock_type,
+ &temp.number,
+ &temp.srtt,
+ &temp.srttvar,
+ &temp.srttb,
+ &temp.srttvarb,
+ &temp.sirt,
+ &temp.sirtvar,
+ &temp.dlm,
+ &temp.queue
+ );
+ temp.dev_id = makedev(major, minor);
+
+ /* Filter on required lock types */
+ if ((temp.lock_type == WORSTGLOCK_INODE || temp.lock_type == WORSTGLOCK_RGRP) &&
+ (temp.dlm > COUNT_THRESHOLD || temp.queue > COUNT_THRESHOLD)) {
+
+ /* Increase counters */
+ glock_data = temp;
+ ftrace_increase_num_accepted_entries();
+ }
+
+ worst_glock_assign_glocks(gfs_fs_indom);
+ return 0;
+}
+
+static void
+add_pmns_node(__pmnsTree *tree, int domain, int cluster, int lock, int stat)
+{
+ char entry[64];
+ pmID pmid = pmid_build(domain, cluster, (lock * NUM_GLOCKSTATS) + stat);
+
+ snprintf(entry, sizeof(entry),
+ "gfs2.worst_glock.%s.%s", topnum[lock], stattype[stat]);
+ __pmAddPMNSNode(tree, pmid, entry);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "GFS2 worst_glock added %s (%s)", entry, pmIDStr(pmid));
+}
+
+static int
+refresh_worst_glock(pmdaExt *pmda, __pmnsTree **tree)
+{
+ int t, s, sts;
+ static __pmnsTree *worst_glock_tree;
+
+ if (worst_glock_tree) {
+ *tree = worst_glock_tree;
+ } else if ((sts = __pmNewPMNS(&worst_glock_tree)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create worst_glock names: %s\n",
+ pmProgname, pmErrStr(sts));
+ *tree = NULL;
+ } else {
+ for (t = 0; t < NUM_TOPNUM; t++)
+ for (s = 0; s < NUM_GLOCKSTATS; s++)
+ add_pmns_node(worst_glock_tree, pmda->e_domain, CLUSTER_WORSTGLOCK, t, s);
+ *tree = worst_glock_tree;
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Create a new metric table entry based on an existing one.
+ *
+ */
+static void
+refresh_metrictable(pmdaMetric *source, pmdaMetric *dest, int lock)
+{
+ int item = pmid_item(source->m_desc.pmid);
+ int domain = pmid_domain(source->m_desc.pmid);
+ int cluster = pmid_cluster(source->m_desc.pmid);
+
+ memcpy(dest, source, sizeof(pmdaMetric));
+ item += lock * NUM_GLOCKSTATS;
+ dest->m_desc.pmid = pmid_build(domain, cluster, item);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "GFS2 worst_glock refresh_metrictable: (%p -> %p) "
+ "metric ID dup: %d.%d.%d -> %d.%d.%d\n",
+ source, dest, domain, cluster,
+ pmid_item(source->m_desc.pmid), domain, cluster, item);
+}
+
+/*
+ * Used to answer the question: how much extra space needs to be
+ * allocated in the metric table for (dynamic) worst_glock metrics?
+ *
+ */
+static void
+size_metrictable(int *total, int *trees)
+{
+ *total = NUM_GLOCKSTATS;
+ *trees = NUM_TOPNUM;
+}
+
+static int
+worst_glock_text(pmdaExt *pmda, pmID pmid, int type, char **buf)
+{
+ int item = pmid_item(pmid);
+ static char text[128];
+
+ if (pmid_cluster(pmid) != CLUSTER_WORSTGLOCK)
+ return PM_ERR_PMID;
+ if (item < 0 || item >= WORST_GLOCK_COUNT)
+ return PM_ERR_PMID;
+ snprintf(text, sizeof(text), "%s for %s worst glock",
+ stattext[item % NUM_GLOCKSTATS], topnum[item / NUM_TOPNUM]);
+
+ *buf = text;
+
+ return 0;
+}
+
+void
+gfs2_worst_glock_init(pmdaMetric *metrics, int nmetrics)
+{
+ int set[] = { CLUSTER_WORSTGLOCK };
+
+ pmdaDynamicPMNS("gfs2.worst_glock",
+ set, sizeof(set)/sizeof(int),
+ refresh_worst_glock, worst_glock_text,
+ refresh_metrictable, size_metrictable,
+ metrics, nmetrics);
+}
diff --git a/src/pmdas/gfs2/worst_glock.h b/src/pmdas/gfs2/worst_glock.h
new file mode 100644
index 0000000..6d1108c
--- /dev/null
+++ b/src/pmdas/gfs2/worst_glock.h
@@ -0,0 +1,91 @@
+/*
+ * GFS2 gfs2_glock_lock_time trace-point metrics.
+ *
+ * Copyright (c) 2013 - 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef LOCK_TIME_H
+#define LOCK_TIME_H
+
+#define COUNT_THRESHOLD 350
+#define DEFAULT_WORST_GLOCK_STATE 1
+#define WORST_GLOCK_TOP 10
+#define WORST_GLOCK_COUNT (NUM_GLOCKSTATS*NUM_TOPNUM)
+
+enum {
+ WORSTGLOCK_LOCK_TYPE = 0,
+ WORSTGLOCK_NUMBER,
+ WORSTGLOCK_SRTT,
+ WORSTGLOCK_SRTTVAR,
+ WORSTGLOCK_SRTTB,
+ WORSTGLOCK_SRTTVARB,
+ WORSTGLOCK_SIRT,
+ WORSTGLOCK_SIRTVAR,
+ WORSTGLOCK_DLM,
+ WORSTGLOCK_QUEUE,
+ NUM_GLOCKSTATS
+};
+
+enum {
+ WORSTGLOCK_TRANS = 1,
+ WORSTGLOCK_INODE = 2,
+ WORSTGLOCK_RGRP = 3,
+ WORSTGLOCK_META = 4,
+ WORSTGLOCK_IOPEN = 5,
+ WORSTGLOCK_FLOCK = 6,
+ WORSTGLOCK_RESERVED = 7,
+ WORSTGLOCK_QUOTA = 8,
+ WORSTGLOCK_JOURNAL = 9
+};
+
+enum {
+ TOPNUM_FIRST = 0,
+ TOPNUM_SECOND,
+ TOPNUM_THIRD,
+ TOPNUM_FOURTH,
+ TOPNUM_FIFTH,
+ TOPNUM_SIXTH,
+ TOPNUM_SEVENTH,
+ TOPNUM_EIGHTH,
+ TOPNUM_NINTH,
+ TOPNUM_TENTH,
+ NUM_TOPNUM
+};
+
+struct glock {
+ dev_t dev_id;
+ uint32_t lock_type; /* Glock type number */
+ uint64_t number; /* Inode or resource group number */
+ int64_t srtt; /* Non blocking smoothed round trip time */
+ int64_t srttvar; /* Non blocking smoothed variance */
+ int64_t srttb; /* Blocking smoothed round trip time */
+ int64_t srttvarb; /* Blocking smoothed variance */
+ int64_t sirt; /* Smoothed Inter-request time */
+ int64_t sirtvar; /* Smoothed Inter-request variance */
+ int64_t dlm; /* Count of dlm requests */
+ int64_t queue; /* Count of gfs2_holder queues */
+};
+
+struct worst_glock {
+ struct glock glocks[WORST_GLOCK_TOP + 1];
+ int assigned_entries;
+};
+
+extern void gfs2_worst_glock_init(pmdaMetric *, int);
+extern int gfs2_worst_glock_fetch(int, struct worst_glock *, pmAtomValue *);
+extern int gfs2_extract_worst_glock(char **, pmInDom);
+
+extern int worst_glock_get_state();
+extern int worst_glock_set_state(pmValueSet *vsp);
+
+#endif /* LOCK_TIME_H */
diff --git a/src/pmdas/gluster/GNUmakefile b/src/pmdas/gluster/GNUmakefile
new file mode 100644
index 0000000..bb68e16
--- /dev/null
+++ b/src/pmdas/gluster/GNUmakefile
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = gluster
+PYSCRIPT = pmda$(IAM).python
+LSRCFILES = Install Remove $(PYSCRIPT)
+
+DOMAIN = GLUSTER
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log
+
+default_pcp default: check_domain
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(PYSCRIPT) $(PMDADIR)/$(PYSCRIPT)
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PYTHONRULE)
diff --git a/src/pmdas/gluster/Install b/src/pmdas/gluster/Install
new file mode 100644
index 0000000..c37cf15
--- /dev/null
+++ b/src/pmdas/gluster/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the gluster PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gluster
+python_opt=true
+daemon_opt=false
+forced_restart=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/gluster/Remove b/src/pmdas/gluster/Remove
new file mode 100644
index 0000000..dfc08bb
--- /dev/null
+++ b/src/pmdas/gluster/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the gluster PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gluster
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/gluster/pmdagluster.python b/src/pmdas/gluster/pmdagluster.python
new file mode 100644
index 0000000..de42e0c
--- /dev/null
+++ b/src/pmdas/gluster/pmdagluster.python
@@ -0,0 +1,337 @@
+'''
+Performance Metrics Domain Agent exporting Gluster filesystem metrics.
+'''
+#
+# Copyright (c) 2013-2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+import cpmapi as c_api
+from pcp.pmapi import pmUnits
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom
+from ctypes import c_int, c_long, c_float, POINTER, cast, Structure
+import xml.etree.cElementTree as xmltree
+from os import getenv
+import subprocess
+
+VOL_INFO_COMMAND = 'gluster --xml volume info'
+VOL_STOP_COMMAND = 'gluster --xml volume profile %s stop'
+VOL_START_COMMAND = 'gluster --xml volume profile %s start'
+VOL_STATS_COMMAND = 'gluster --xml volume profile %s info'
+
+FILEOPS = [ # append only, do not change the order (changes PMID)
+ 'ACCESS', 'CREATE', 'DISCARD', 'ENTRYLK', 'FALLOCATE', 'FENTRYLK',
+ 'FGETXATTR', 'FINODELK', 'FLUSH', 'FREMOVEXATTR', 'FSETATTR',
+ 'FSETXATTR', 'FSTAT', 'FSYNC', 'FSYNCDIR', 'FTRUNCATE', 'FXATTROP',
+ 'GETSPEC', 'GETXATTR', 'INODELK', 'LINK', 'LK', 'LOOKUP', 'MKDIR',
+ 'MKNOD', 'OPEN', 'OPENDIR', 'RCHECKSUM', 'READDIR', 'READDIRP',
+ 'READLINK', 'READV', 'REMOVEXATTR', 'RENAME', 'RMDIR', 'SETATTR',
+ 'SETXATTR', 'STAT', 'STATFS', 'SYMLINK', 'TRUNCATE', 'UNLINK',
+ 'WRITEV', 'XATTROP', 'READ', 'WRITE', 'RELEASE', 'RELEASEDIR',
+ 'FORGET'
+]
+FILEOPS_INDICES = {}
+
+class GlusterVolume(Structure):
+ ''' Statistic values per-gluster-volume (volume indom cache lookup) '''
+ _fields_ = [("distCount", c_int),
+ ("stripeCount", c_int),
+ ("replicaCount", c_int),
+ ("fopHitsEnabled", c_int),
+ ("latencyEnabled", c_int)]
+
+class GlusterBrick(Structure):
+ ''' Statistic values per-gluster-brick, within a volume (brick indom) '''
+ _fields_ = [("mintime", c_long * len(FILEOPS)),
+ ("maxtime", c_long * len(FILEOPS)),
+ ("avgtime", c_float * len(FILEOPS)),
+ ("count", c_long * len(FILEOPS)),
+ ("read_bytes", c_long),
+ ("write_bytes", c_long)]
+
+class GlusterPMDA(PMDA):
+ '''
+ Performance Metrics Domain Agent exporting gluster brick metrics.
+ Install it and make basic use of it if you use glusterfs, as follows:
+
+ # $PCP_PMDAS_DIR/gluster/Install
+ $ pminfo -fmdtT gluster
+ '''
+
+ # volumes and bricks instance domains
+ volumes = {}
+ bricks = {}
+
+ def runVolumeInfo(self):
+ ''' Execute the gluster volume info command to extract volume names '''
+ process = subprocess.Popen(self.vol_info, shell = True,
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ out, err = process.communicate()
+ # self.log('Info command %s output: %s' % (self.vol_info, out))
+ return xmltree.fromstring(out)
+
+ def runVolumeProfileInfo(self, volume):
+ ''' Execute gluster volume profile info command for a given volume '''
+ command = self.vol_stats % volume
+ process = subprocess.Popen(command, shell = True,
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ out, err = process.communicate()
+ # self.log('Profile command %s output: %s' % (command, out))
+ return xmltree.fromstring(out)
+
+ def parseVolumeInfo(self, xml):
+ ''' Extract the set of volume names from given gluster XML string '''
+ volumenames = []
+ volsxml = xml.find('volInfo/volumes')
+ if volsxml == None:
+ return volumenames
+ for volxml in volsxml.findall('volume'):
+ volname = volxml.find('name').text
+ volumenames.append(volname)
+ volume = GlusterVolume()
+ volume.distCount = int(volxml.find('distCount').text)
+ volume.stripeCount = int(volxml.find('stripeCount').text)
+ volume.replicaCount = int(volxml.find('replicaCount').text)
+ volume.fopHitsEnabled = 0
+ volume.latencyEnabled = 0
+ for option in volxml.findall('options/option'):
+ name = option.find('name').text
+ value = option.find('value').text
+ if (name == 'diagnostics.count-fop-hits' and value == 'on'):
+ volume.fopHitsEnabled = 1
+ if (name == 'diagnostics.latency-measurement' and value == 'on'):
+ volume.latencyEnabled = 1
+ self.volumes[volname] = volume # prepare the volume indom cache
+ return volumenames
+
+ def parseVolumeProfileInfo(self, volume, xml):
+ ''' Extract the metric values from a given gluster profile string '''
+ for brickxml in xml.findall('volProfile/brick'):
+ brickname = brickxml.find('brickName').text
+ brick = GlusterBrick()
+ for fileop in brickxml.findall('cumulativeStats/fopStats/fop'):
+ name = fileop.find('name').text
+ try:
+ fop = FILEOPS_INDICES[name]
+ except KeyError:
+ # self.log('Unrecognised fileops key %s' % name)
+ pass
+ else:
+ brick.count[fop] = long(fileop.find('hits').text)
+ brick.avgtime[fop] = float(fileop.find('avgLatency').text)
+ brick.mintime[fop] = long(float(fileop.find('minLatency').text))
+ brick.maxtime[fop] = long(float(fileop.find('maxLatency').text))
+ brick.read_bytes = long(brickxml.find('cumulativeStats/totalRead').text)
+ brick.write_bytes = long(brickxml.find('cumulativeStats/totalWrite').text)
+ self.bricks[brickname] = brick # prepare the bricks indom cache
+
+
+ def gluster_refresh(self):
+ ''' Refresh the values and instances for gluster volumes and bricks '''
+ xml = self.runVolumeInfo()
+ if (xml != None):
+ for vol in self.parseVolumeInfo(xml):
+ xml = self.runVolumeProfileInfo(vol)
+ if (xml != None):
+ self.parseVolumeProfileInfo(vol, xml)
+
+ def gluster_instance(self, serial):
+ ''' Called once per "instance request" PDU '''
+ if (serial == 0 or serial == 1):
+ self.gluster_fetch()
+
+ def gluster_fetch(self):
+ ''' Called once per "fetch" PDU '''
+ self.bricks.clear()
+ self.volumes.clear()
+ self.gluster_refresh()
+ self.replace_indom(self.brick_indom, self.bricks)
+ self.replace_indom(self.volume_indom, self.volumes)
+
+
+ def gluster_fetch_thruput_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for thruput cluster
+ Helper for the fetch callback
+ '''
+ if (item < 0 or item > 2):
+ return [c_api.PM_ERR_PMID, 0]
+ voidp = self.inst_lookup(self.brick_indom, inst)
+ if (voidp == None):
+ return [c_api.PM_ERR_INST, 0]
+ cache = cast(voidp, POINTER(GlusterBrick))
+ brick = cache.contents
+ if (item == 0):
+ return [brick.read_bytes, 1]
+ return [brick.write_bytes, 1]
+
+ def gluster_fetch_latency_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for latency cluster
+ Helper for the fetch callback
+ '''
+ if (item < 0 or item > len(FILEOPS) * 4):
+ return [c_api.PM_ERR_PMID, 0]
+ voidp = self.inst_lookup(self.brick_indom, inst)
+ if (voidp == None):
+ return [c_api.PM_ERR_INST, 0]
+ cache = cast(voidp, POINTER(GlusterBrick))
+ brick = cache.contents
+ fileop = item / 4
+ index = item % 4
+ if (index == 0):
+ return [brick.mintime[fileop], 1]
+ elif (index == 1):
+ return [brick.maxtime[fileop], 1]
+ elif (index == 2):
+ return [brick.avgtime[fileop], 1]
+ return [brick.count[fileop], 1]
+
+ def gluster_fetch_volumes_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for volumes cluster
+ Helper for the fetch callback
+ '''
+ if (item < 0 or item > 3):
+ return [c_api.PM_ERR_PMID, 0]
+ voidp = self.inst_lookup(self.volume_indom, inst)
+ if (voidp == None):
+ return [c_api.PM_ERR_INST, 0]
+ cached = cast(voidp, POINTER(GlusterVolume))
+ volume = cached.contents
+ if (item == 0):
+ enabled = volume.fopHitsEnabled
+ if (enabled):
+ enabled = volume.latencyEnabled
+ return [enabled, 1]
+ elif (item == 1):
+ return [volume.distCount, 1]
+ elif (item == 2):
+ return [volume.stripeCount, 1]
+ elif (item == 3):
+ return [volume.replicaCount, 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+ def gluster_fetch_callback(self, cluster, item, inst):
+ '''
+ Main fetch callback, defers to helpers for each cluster.
+ Returns a list of value,status (single pair) for requested pmid/inst
+ '''
+ # self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
+ if (cluster == 0):
+ return self.gluster_fetch_thruput_callback(item, inst)
+ elif (cluster == 1):
+ return self.gluster_fetch_latency_callback(item, inst)
+ elif (cluster == 2):
+ return self.gluster_fetch_volumes_callback(item, inst)
+ return [c_api.PM_ERR_PMID, 0]
+
+
+ def gluster_store_volume_callback(self, inst, val):
+ ''' Helper for the store callback, volume profile enabling/disabling '''
+ sts = 0
+ name = self.inst_name_lookup(self.volume_indom, inst)
+ if (name == None):
+ sts = c_api.PM_ERR_INST
+ elif (val < 0):
+ sts = c_api.PM_ERR_SIGN
+ elif (val == 0):
+ command = self.vol_stop % name
+ # self.log("Running disable command: %s" % command)
+ subprocess.call(command, shell =True,
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ else:
+ command = self.vol_start % name
+ # self.log("Running enable command: %s" % command)
+ subprocess.call(command, shell =True,
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+ return sts
+
+ def gluster_store_callback(self, cluster, item, inst, val):
+ '''
+ Store callback, executed when a request to write to a metric happens
+ Defers to helpers for each storable metric. Returns a single value.
+ '''
+ # self.log("store callback for %d.%d[%d]" % (cluster, item, inst))
+ if (cluster == 2 and item == 0):
+ return self.gluster_store_volume_callback(inst, val)
+ elif (cluster == 0 or cluster == 1):
+ return c_api.PM_ERR_PERMISSION
+ return c_api.PM_ERR_PMID
+
+
+ def __init__(self, name, domain):
+ PMDA.__init__(self, name, domain)
+
+ self.vol_info = getenv('GLUSTER_VOL_INFO', VOL_INFO_COMMAND)
+ self.vol_stop = getenv('GLUSTER_VOL_STOP', VOL_STOP_COMMAND)
+ self.vol_start = getenv('GLUSTER_VOL_START', VOL_START_COMMAND)
+ self.vol_stats = getenv('GLUSTER_VOL_STATS', VOL_STATS_COMMAND)
+
+ self.volume_indom = self.indom(0)
+ self.add_indom(pmdaIndom(self.volume_indom, self.volumes))
+
+ self.brick_indom = self.indom(1)
+ self.add_indom(pmdaIndom(self.brick_indom, self.bricks))
+
+ self.add_metric(name + '.brick.read_bytes', pmdaMetric(self.pmid(0, 0),
+ c_api.PM_TYPE_U64, self.brick_indom, c_api.PM_SEM_COUNTER,
+ pmUnits(1, 0, 0, c_api.PM_SPACE_BYTE, 0, 0)))
+ self.add_metric(name + '.brick.write_bytes', pmdaMetric(self.pmid(0, 1),
+ c_api.PM_TYPE_U64, self.brick_indom, c_api.PM_SEM_COUNTER,
+ pmUnits(1, 0, 0, c_api.PM_SPACE_BYTE, 0, 0)))
+
+ item = 0
+ for fileop in FILEOPS:
+ metricname = name + '.brick.latency.' + fileop.lower() + '.'
+ self.add_metric(metricname + 'min', pmdaMetric(self.pmid(1, item),
+ c_api.PM_TYPE_U64, self.brick_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_USEC, 0)))
+ item += 1
+ self.add_metric(metricname + 'max', pmdaMetric(self.pmid(1, item),
+ c_api.PM_TYPE_U64, self.brick_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_USEC, 0)))
+ item += 1
+ self.add_metric(metricname + 'avg', pmdaMetric(self.pmid(1, item),
+ c_api.PM_TYPE_FLOAT, self.brick_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_USEC, 0)))
+ item += 1
+ self.add_metric(metricname + 'count', pmdaMetric(self.pmid(1, item),
+ c_api.PM_TYPE_U64, self.brick_indom, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)))
+ item += 1
+
+ metricname = name + '.volume.'
+ self.add_metric(metricname + 'profile', pmdaMetric(self.pmid(2, 0),
+ c_api.PM_TYPE_32, self.volume_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(metricname + 'dist.count', pmdaMetric(self.pmid(2, 1),
+ c_api.PM_TYPE_32, self.volume_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(metricname + 'stripe.count', pmdaMetric(self.pmid(2, 2),
+ c_api.PM_TYPE_32, self.volume_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(metricname + 'replica.count', pmdaMetric(self.pmid(2, 3),
+ c_api.PM_TYPE_32, self.volume_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+
+ self.set_fetch(self.gluster_fetch)
+ self.set_instance(self.gluster_instance)
+ self.set_fetch_callback(self.gluster_fetch_callback)
+ self.set_store_callback(self.gluster_store_callback)
+
+
+if __name__ == '__main__':
+ for index in range(0, len(FILEOPS)):
+ fileop = FILEOPS[index]
+ FILEOPS_INDICES[fileop] = index
+ GlusterPMDA('gluster', 118).run()
diff --git a/src/pmdas/gpsd/GNUmakefile b/src/pmdas/gpsd/GNUmakefile
new file mode 100644
index 0000000..acd1e76
--- /dev/null
+++ b/src/pmdas/gpsd/GNUmakefile
@@ -0,0 +1,48 @@
+#!gmake
+#
+# Copyright (c) 2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = gpsd
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/gpsd/Install b/src/pmdas/gpsd/Install
new file mode 100644
index 0000000..6b39be4
--- /dev/null
+++ b/src/pmdas/gpsd/Install
@@ -0,0 +1,34 @@
+#! /bin/sh
+#
+# Copyright (c) 2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the gpsd PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gpsd
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+perl -e "use JSON" 2>/dev/null
+if test $? -ne 0; then
+ echo "JSON perl module is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/gpsd/Remove b/src/pmdas/gpsd/Remove
new file mode 100644
index 0000000..2b52da3
--- /dev/null
+++ b/src/pmdas/gpsd/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the gpsd PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=gpsd
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/gpsd/pmdagpsd.pl b/src/pmdas/gpsd/pmdagpsd.pl
new file mode 100644
index 0000000..c924d04
--- /dev/null
+++ b/src/pmdas/gpsd/pmdagpsd.pl
@@ -0,0 +1,310 @@
+#
+# Copyright (c) 2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use JSON;
+use Time::HiRes qw ( time );
+
+use vars qw( $pmda );
+use vars qw( %devdata %satdata );
+
+my $gpspipe = "gpspipe -w";
+
+my $json;
+
+my $gpsd_release = "?";
+my $gpsd_rev = "?";
+my $gpsd_proto_maj = -1;
+my $gpsd_proto_min = -1;
+
+# FIXME: the following will need changing to support multiple devices
+my $gpsd_device = undef;
+my %devdata = (
+ "time" => -1,
+ "ept" => -1,
+ "lat" => -1,
+ "lon" => -1,
+ "alt" => -1,
+ "track" => -1,
+ "speed" => -1,
+ "climb" => -1,
+ "mode" => 0, # 0 = no fix
+ "xdop" => -1,
+ "ydop" => -1,
+ "vdop" => -1,
+ "tdop" => -1,
+ "hdop" => -1,
+ "gdop" => -1,
+ "pdop" => -1,
+ "num_satellites" => 0,
+);
+my $gpsd_sat_indom = 0;
+my @gpsd_sat_dom = ();
+
+sub gpsd_pipe_callback
+{
+ (undef, $_) = @_;
+
+ # $pmda->log("pipe got: $_");
+
+ my $jtxt = $json->decode($_);
+
+ if ($jtxt->{"class"} eq "VERSION") {
+ $gpsd_release = $jtxt->{"release"};
+ $gpsd_rev = $jtxt->{"rev"};
+ $gpsd_proto_maj = $jtxt->{"proto_major"};
+ $gpsd_proto_min = $jtxt->{"proto_minor"};
+ } elsif ($jtxt->{"class"} eq "DEVICES") {
+ foreach my $dev (@{$jtxt->{"devices"}}) {
+ if ($dev->{"class"} eq "DEVICE") {
+ $pmda->log("gpsd_pipe_callback: oops! multiple " .
+ "devices detected, only using the first " .
+ "($gpsd_device)") if (defined($gpsd_device) and
+ $dev->{"path"} ne $gpsd_device);
+
+ $gpsd_device = $dev->{"path"};
+ } else {
+ $pmda->log("gpsd_pipe_callback: unknown class '" .
+ $dev->{"class"} . "'");
+ }
+ }
+ } elsif ($jtxt->{"class"} eq "DEVICE") {
+ # nothing to do
+ } elsif ($jtxt->{"class"} eq "WATCH") {
+ # nothing to do
+ } elsif ($jtxt->{"class"} eq "TPV") {
+ return if ($jtxt->{"device"} ne $gpsd_device);
+
+ $devdata{"time"} = $jtxt->{"time"};
+ $devdata{"ept"} = $jtxt->{"ept"};
+ $devdata{"lat"} = $jtxt->{"lat"};
+ $devdata{"lon"} = $jtxt->{"lon"};
+ $devdata{"alt"} = $jtxt->{"alt"};
+ $devdata{"track"} = $jtxt->{"track"};
+ $devdata{"speed"} = $jtxt->{"speed"};
+ $devdata{"climb"} = $jtxt->{"climb"};
+ $devdata{"mode"} = $jtxt->{"mode"};
+ } elsif ($jtxt->{"class"} eq "SKY") {
+ return if ($jtxt->{"device"} ne $gpsd_device);
+
+ $devdata{"xdop"} = $jtxt->{"xdop"};
+ $devdata{"ydop"} = $jtxt->{"ydop"};
+ $devdata{"vdop"} = $jtxt->{"vdop"};
+ $devdata{"tdop"} = $jtxt->{"tdop"};
+ $devdata{"hdop"} = $jtxt->{"hdop"};
+ $devdata{"gdop"} = $jtxt->{"gdop"};
+ $devdata{"pdop"} = $jtxt->{"pdop"};
+
+ my %sats = {};
+ my @dom = ();
+ foreach my $sat (@{$jtxt->{"satellites"}}) {
+ push(@dom, $sat->{"PRN"} => "$sat->{'PRN'}");
+
+ $sats{"el"}{$sat->{"PRN"}} = $sat->{"el"};
+ $sats{"az"}{$sat->{"PRN"}} = $sat->{"az"};
+ $sats{"ss"}{$sat->{"PRN"}} = $sat->{"ss"};
+ $sats{"used"}{$sat->{"PRN"}} = $sat->{"used"};
+ }
+ %satdata = %sats;
+ $pmda->replace_indom($gpsd_sat_indom, \@dom);
+
+ $devdata{"num_satellites"} = scalar(@dom) / 2;
+ } else {
+ $pmda->log("gpsd_pipe_callback: unknown class '" . $jtxt->{"class"} . "'");
+ }
+}
+
+sub gpsd_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+
+ # $pmda->log("gpsd_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ return (PM_ERR_PMID, 0) if (!defined($metric_name));
+
+ if ($cluster == 0) {
+ return (PM_ERR_INST, 0) if ($inst != PM_IN_NULL);
+
+ if ($metric_name eq "gpsd.release") {
+ return ($gpsd_release, 1);
+ } elsif ($metric_name eq "gpsd.rev") {
+ return ($gpsd_rev, 1);
+ } elsif ($metric_name eq "gpsd.proto.major") {
+ return ($gpsd_proto_maj, 1);
+ } elsif ($metric_name eq "gpsd.proto.minor") {
+ return ($gpsd_proto_min, 1);
+ }
+ }
+
+ $metric_name =~ s/^gpsd\.devices\.dev0\.//;
+
+ if ($metric_name =~ m/^satellites\./) {
+ # satellite info
+ $metric_name =~ s/^satellites\.//;
+
+ # $pmda->log("gpsd_fetch_callbac2 $metric_name $cluster:$item ($inst): ${satdata{$metric_name}}\n");
+ # $pmda->log("gpsd_fetch_callbac2 $metric_name $cluster:$item ($inst): ${satdata{$metric_name}{$inst}}\n");
+ return (PM_ERR_INST, 0) if ($inst == PM_IN_NULL);
+ return (PM_ERR_PMID, 0) if (!defined($satdata{$metric_name}{$inst}));
+ return ($satdata{$metric_name}{$inst}, 1);
+ }
+
+ return (PM_ERR_INST, 0) if ($inst != PM_IN_NULL);
+ return (PM_ERR_PMID, 0) if (!defined($devdata{$metric_name}));
+ return ($devdata{$metric_name}, 1);
+}
+
+$json = new JSON;
+
+$pmda = PCP::PMDA->new('gpsd', 105);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.release", '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.rev", '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.proto.major", '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.proto.minor", '', '');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.time", '', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.ept", '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.lat", '', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.lon", '', '');
+$pmda->add_metric(pmda_pmid(1,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.alt", '', '');
+$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.track", '', '');
+$pmda->add_metric(pmda_pmid(1,6), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.speed", '', '');
+$pmda->add_metric(pmda_pmid(1,7), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.climb", '', '');
+$pmda->add_metric(pmda_pmid(1,8), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.mode", '', '');
+
+$pmda->add_metric(pmda_pmid(1,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.xdop", '', '');
+$pmda->add_metric(pmda_pmid(1,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.ydop", '', '');
+$pmda->add_metric(pmda_pmid(1,11), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.vdop", '', '');
+$pmda->add_metric(pmda_pmid(1,12), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.tdop", '', '');
+$pmda->add_metric(pmda_pmid(1,13), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.hdop", '', '');
+$pmda->add_metric(pmda_pmid(1,14), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.gdop", '', '');
+$pmda->add_metric(pmda_pmid(1,15), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.pdop", '', '');
+
+$pmda->add_metric(pmda_pmid(1,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.num_satellites", '', '');
+
+$pmda->add_metric(pmda_pmid(1,100), PM_TYPE_U32, $gpsd_sat_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.satellites.el", '', '');
+$pmda->add_metric(pmda_pmid(1,101), PM_TYPE_U32, $gpsd_sat_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.satellites.az", '', '');
+$pmda->add_metric(pmda_pmid(1,102), PM_TYPE_U32, $gpsd_sat_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.satellites.ss", '', '');
+$pmda->add_metric(pmda_pmid(1,103), PM_TYPE_U32, $gpsd_sat_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "gpsd.devices.dev0.satellites.used", '', '');
+
+$pmda->add_indom($gpsd_sat_indom, \@gpsd_sat_dom, '', '');
+$pmda->add_pipe($gpspipe, \&gpsd_pipe_callback, 0);
+
+$pmda->set_fetch_callback(\&gpsd_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdagpsd - gpsd performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdagpsd> is a Performance Metrics Domain Agent (PMDA) which exports
+values from the gpsd daemon.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for gpsd, do the following as
+root:
+
+ # cd $PCP_PMDAS_DIR/gpsd
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/gpsd
+ # ./Remove
+
+B<pmdagpsd> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/gpsd/Install
+
+installation script for the B<pmdagpsd> agent
+
+=item $PCP_PMDAS_DIR/gpsd/Remove
+
+undo installation script for the B<pmdagpsd> agent
+
+=item $PCP_LOG_DIR/pmcd/gpsd.log
+
+default log file for error messages from B<pmdagpsd>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/hotproc/GNUakefile b/src/pmdas/hotproc/GNUakefile
new file mode 100644
index 0000000..7ebb155
--- /dev/null
+++ b/src/pmdas/hotproc/GNUakefile
@@ -0,0 +1,52 @@
+#!make
+
+LTARGETS = help.dir
+LLDIRT = domain.h *.log *.dir *.pag pmns help
+
+PCP_SRC_DEPTH = ../..
+include $(PCP_SRC_DEPTH)/include/commondefs
+include $(PCP_SRC_DEPTH)/include/isacommondefs
+
+PROC_DIR = ../proc
+IAM = hotproc
+DOMAIN = HOTPROC
+IDBTAG = PMDA_$(DOMAIN)
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+install: default
+ $(INSTALL) -F /usr/pcp/lib -idb "$(IDBTAG)" -lns ../../..$(PMDADIR)/pmda$(IAM) pmda$(IAM)
+ $(INSTALL) -idb '$(IDBTAG) removeop("rm -f $$rbase$(PMDADIR)/help.*")' -m 755 -dir $(PMDADIR)
+ $(INSTALL) -F /usr/pcp/pmdas -idb "$(IDBTAG)" -lns ../../..$(PMDADIR) $(IAM)
+#if $(BEFORE_IRIX6_5)
+ $(I_32) $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG) $(MODE32) $(STRIPBIN)" -m 555 -src 32/pmda$(IAM) pmda$(IAM)
+ $(I_64) $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG) $(MODE64) $(STRIPBIN)" -m 555 -src 64/pmda$(IAM) pmda$(IAM)
+#else
+ $(I_N32) $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG) $(MODE32) $(STRIPBIN)" -m 555 -src N32/pmda$(IAM) pmda$(IAM)
+ $(I_64) $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG) $(MODE64) $(STRIPBIN)" -m 555 -src 64/pmda$(IAM) pmda$(IAM)
+#endif
+ $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG)" -m 555 Install Remove
+ $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG)" -m 444 README root help pmns domain.h sample.conf general.conf general.pmie
+ $(INSTALL) -f $(PMDADIR) -idb "$(IDBTAG)" -m 444 -src Makefile.install Makefile
+
+help: $(PROC_DIR)/help help.hotproc pmns
+ sed < $(PROC_DIR)/help -e 's/proc\./hotproc./g' \
+ -e 's/number of processes/number of "interesting" processes/g' \
+ | cat - help.hotproc | ./help.fmt > $@
+
+help.dir: domain.h help root pmns ../../buildtools/newhelp
+ PCP_SRC_DEPTH=$(PCP_SRC_DEPTH) $(PCP_SRC_DEPTH)/buildtools/check_help_src help root
+
+pmns: $(PROC_DIR)/root_proc pmns.hotproc fixpmns.awk
+ nawk < $(PROC_DIR)/root_proc -f fixpmns.awk \
+ | sed -e '/[ ]PROC:/s/PROC:/HOTPROC:/g' -e 's/^proc/hotproc/g' \
+ | cat - pmns.hotproc >$@
+
+.NOTPARALLEL:
+.ORDER: domain.h $(OBJECTS)
+
+domain.h: ../../pmns/stdpmid
+ rm -f domain.h
+ echo "/*" >domain.h
+ echo " * built from $(PCP_VAR_DIR)/pmns/stdpmid" >>domain.h
+ echo " */" >>domain.h
+ nawk <../../pmns/stdpmid >>domain.h '/#define[ ][ ]*$(DOMAIN)[ ]/ { print "#define $(DOMAIN) " $$3 }'
diff --git a/src/pmdas/hotproc/GNUmakefile b/src/pmdas/hotproc/GNUmakefile
new file mode 100644
index 0000000..2c69ac6
--- /dev/null
+++ b/src/pmdas/hotproc/GNUmakefile
@@ -0,0 +1,157 @@
+#
+# Copyright (c) 1995-2002,2007 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+# This is just to get the source exported to the community.
+# 'Makefile' in particular is included only for clues it might offer,
+# and should almost certainly be removed from SRCFILES once
+# the pmda is working.
+LSRCFILES = fixpmns.awk general.pmie help.fmt Install \
+ pmns.hotproc Remove sample.conf \
+ general.conf help.hotproc Makefile README root
+SUBDIRS = src
+
+default default_pcp:
+install install_pcp:
+include $(BUILDRULES)
+
+# Remove # from start of all following lines to get pre-existing
+# (but currently non-functional) content
+
+#IAM = hotproc
+#DOMAIN = HOTPROC
+#
+#TARGETS = $(IAM) pmns help
+#
+#PROC_DIR = ../linux
+#PROC_SRCDIR = $(PROC_DIR)/src
+#
+#FROM_PROC_H = cluster.h pracinfo.h proc_aux.h pscred.h \
+# pstatus.h pmemory.h proc.h procmem.h psinfo.h \
+# psusage.h ctltab.h nameinfo.h
+#FROM_PROC_C = pmemory.c pracinfo.c proc_aux.c \
+# pscred.c psinfo.c pstatus.c psusage.c \
+# ttyname.c procmem.c nameinfo.c
+#
+#FROM_PROC = $(FROM_PROC_C) $(FROM_PROC_H)
+#
+#CFILES = $(FROM_PROC_C) \
+# pglobal.c ctltab.c hotproc.c pcpu.c \
+# config.c gram_node.c error.c ppred_values.c
+#
+#OBJECTS = gram.o lex.o $(CFILES:S/.c/.o/g)
+#
+#LCOPTS = -fullwarn
+#LCINCS = $(PCP_INC_PATH)
+#LCDEFS = $(DEBUG)
+#LLDOPTS = $(PCP_LIBS_PATH)
+#LLDLIBS = $(PCP_PMDALIB)
+#
+#PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+#LDIRT = domain.h *.log *.dir *.pag
+#
+#default: $(TARGETS) check_help_src
+#
+#include $(TOPDIR)/src/include/buildrules.pro
+#
+#install: default
+# $(INSTALL) -m 755 -d $(PMDADIR)
+# $(INSTALL) -lns ../../..$(PMDADIR)/pmda$(IAM) pmda$(IAM) $(PCP_SHARE_DIR)/lib
+# $(INSTALL) -lns ../../..$(PMDADIR) $(IAM) $(PCP_PMDAS_DIR)
+# $(INSTALL) -m 755 pmda$(IAM) $(PMDADIR)
+# $(INSTALL) -m 755 Install Remove $(PMDADIR)
+# $(INSTALL) -m 644 README root help pmns domain.h sample.conf general.conf general.pmie $(PMDADIR)
+#
+#$(IAM): $(OBJECTS)
+#
+#domain.h: ../../pmns/stdpmid
+# $(DOMAIN_MAKERULE)
+#
+#help: $(PROC_DIR)/help help.hotproc pmns
+# $(AWK) <$(PROC_DIR)/help '\
+#$1 == "@" { want=0 }\
+#$1 == "@" && $2 ~ /^proc/ { want=1 }\
+#want == 1 { print }' \
+# | sed -e 's/proc\./hotproc./g' \
+# -e 's/number of processes/number of "interesting" processes/g' \
+# | cat - help.hotproc \
+# | ./help.fmt >$@
+#
+#pmns: $(PROC_DIR)/root_linux pmns.hotproc fixpmns.awk
+# $(AWK) < $(PROC_DIR)/root_linux -f fixpmns.awk \
+# | sed -e '/nprocs/d' -e 's/60:/HOTPROC:/g' -e 's/^proc/hotproc/g' \
+# | cat - pmns.hotproc >$@
+#
+#hotproc.o: domain.h
+#
+#config.o: gram.tab.h
+#
+#check_help_src: domain.h help root pmns
+# PCP_SRC_DEPTH=$(PCP_SRC_DEPTH) $(PCP_SRC_DEPTH)/buildtools/check_help_src help root
+#
+##
+## PROC_SRCDIR dependencies
+##
+#cluster.h: $(PROC_SRCDIR)/cluster.h
+# ln -s $? $@
+#pracinfo.h: $(PROC_SRCDIR)/pracinfo.h
+# ln -s $? $@
+#pracinfo.c: $(PROC_SRCDIR)/pracinfo.c
+# ln -s $? $@
+#proc_aux.h: $(PROC_SRCDIR)/proc_aux.h
+# ln -s $? $@
+#proc_aux.c: $(PROC_SRCDIR)/proc_aux.c
+# ln -s $? $@
+#pscred.h: $(PROC_SRCDIR)/pscred.h
+# ln -s $? $@
+#pscred.c: $(PROC_SRCDIR)/pscred.c
+# ln -s $? $@
+#pstatus.h: $(PROC_SRCDIR)/pstatus.h
+# ln -s $? $@
+#pstatus.c: $(PROC_SRCDIR)/pstatus.c
+# ln -s $? $@
+#pmemory.h: $(PROC_SRCDIR)/pmemory.h
+# ln -s $? $@
+#pmemory.c: $(PROC_SRCDIR)/pmemory.c
+# ln -s $? $@
+#proc.h: $(PROC_SRCDIR)/proc.h
+# ln -s $? $@
+#procmem.h: $(PROC_SRCDIR)/procmem.h
+# ln -s $? $@
+#procmem.c: $(PROC_SRCDIR)/procmem.c
+# ln -s $? $@
+#psinfo.h: $(PROC_SRCDIR)/psinfo.h
+# ln -s $? $@
+#psinfo.c: $(PROC_SRCDIR)/psinfo.c
+# ln -s $? $@
+#psusage.h: $(PROC_SRCDIR)/psusage.h
+# ln -s $? $@
+#psusage.c: $(PROC_SRCDIR)/psusage.c
+# ln -s $? $@
+#ttyname.c: $(PROC_SRCDIR)/ttyname.c
+# ln -s $? $@
+#ctltab.h: $(PROC_SRCDIR)/ctltab.h
+# ln -s $? $@
+#nameinfo.h: $(PROC_SRCDIR)/nameinfo.h
+# ln -s $? $@
+#nameinfo.c: $(PROC_SRCDIR)/nameinfo.c
+# ln -s $? $@
+#
+#
+#
+#
+#default_pro : default
+#
diff --git a/src/pmdas/hotproc/Install b/src/pmdas/hotproc/Install
new file mode 100644
index 0000000..e1cc8b1
--- /dev/null
+++ b/src/pmdas/hotproc/Install
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+# Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the hotproc PMDA and/or PMNS
+#
+# XXX these values are overwritten by pmdaproc.sh!
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=1 # failure is the default!
+trap "cd /; rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=hotproc
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+
+daemon_opt=true # can install as daemon
+dso_opt=false
+pipe_opt=true # supports pipe IPC
+socket_opt=false # no socket IPC
+
+# be careful that mortals cannot write any configuration files, as
+# these would present a security problem
+#
+umask 022
+
+# PMDA variables
+#
+configfile=""
+refresh=60
+debug=0
+storable="n"
+
+do_debug=false
+
+_parsedefaults()
+{
+ echo "Extracting options from current installation ..."
+ while getopts D:d:h:i:l:p:st:u: c
+ do
+ case $c in
+ \?) echo "Warning: Unrecognized option in $PCP_PMCDCONF_PATH"
+ echo " Remove line for the $iam PMDA in $PCP_PMCDCONF_PATH and re-run ./Install"
+ exit 2;;
+ D ) debug=$OPTARG;;
+ t ) refresh=$OPTARG;;
+ s ) storable="n";;
+ * ) ;;
+ esac
+ done
+ eval configfile='$'$OPTIND
+}
+
+if $do_pmda
+then
+
+ # set options from $PCP_PMCDCONF_PATH, if possible
+ #
+ ans=`$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+$1 == "'$iam'" { printf "%s",$6
+ for (i=7;i<=NF;i++) printf " %s",$i
+ print ""
+ }'`
+ if [ ! -z "$ans" ]
+ then
+ _parsedefaults $ans
+ fi
+
+ # go figure out which configuration file to use ...
+ #
+ default_configfile=./sample.conf
+ pmdaChooseConfigFile
+ if [ -z "$configfile" ]
+ then
+ echo ""
+ echo "Error: Abandoning installation as no configuration file was specified."
+ exit 1
+ fi
+
+ # make sure that the chosen configuration file parses ok
+ #
+ $pmda_dir/$pmda_name -C $configfile >$tmp/err 2>&1
+ if [ $? -eq 1 ]
+ then
+ echo ""
+ echo "Error: Abandoning installation due to errors in configuration file:"
+ cat $tmp/err
+ exit 1
+ fi
+
+ echo
+ echo "Enter the refresh interval (in seconds) [$refresh] \c"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ refresh=$ans
+ fi
+
+ echo
+ echo "Do you want to modify the configuration predicate or refresh interval"
+ echo "at run time [$storable]? \c"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ case $ans in
+ N|n|NO|No|no) storable=n;;
+ Y|y|YES|Yes|yes) storable=y;;
+ esac
+ fi
+ if [ $storable = y ]
+ then
+ store_opt=""
+ else
+ store_opt="-s"
+ fi
+
+ if [ "$do_debug" = true ]
+ then
+ echo
+ echo "Enter the debugging flag (see pmdbg(1)) [$debug] \c"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ debug=$ans
+ fi
+ fi
+
+ args="$store_opt -t $refresh -D $debug $configfile"
+fi
+
+pmdaInstall
+echo "Please note that instance related metrics will not be available"
+echo "until after the initial refresh of $refresh seconds."
+
+status=0
+exit 0
diff --git a/src/pmdas/hotproc/README b/src/pmdas/hotproc/README
new file mode 100644
index 0000000..be8c5a4
--- /dev/null
+++ b/src/pmdas/hotproc/README
@@ -0,0 +1,141 @@
+Performance Co-Pilot hotproc PMDA for Active Process Monitoring
+===============================================================
+
+This PMDA is designed to be configurable to monitor processes which
+the administrator deems "hot" or "interesting." The PMDA is similar
+to the proc PMDA except in two main aspects:
+
+(i) it extends the proc metric set by:
+ hotproc.cpuburn,
+ hotproc.control.*,
+ hotproc.predicate.*,
+ hotproc.total.* .
+
+(ii) it allows one to retrieve all the instances.
+
+It is allowed to retrieve all the instances because the set of
+instances is restricted by a predicate specified in a configuration
+file. The predicate specifies what processes are "interesting", for
+example,
+
+ (cpuburn > 0.1 && uname == "root")
+
+and it applies this predicate every <refresh> seconds.
+
+Therefore, hotproc.nprocs now refers to the number of "interesting"
+processes instead of the list of all the processes.
+
+To monitor how successful (according to activity) that the
+configuration predicate and refresh interval are, the hotproc.total.*
+metrics can be used. For example, hotproc.total.cpuother.transient
+shows how much of the cpu that transient processes (ones which do not
+live for the refresh interval) get. If one is interested in some of
+these processes then reducing the refresh interval may catch them.
+Hotproc.total.cpuother.not_cpuburn indicates how much of the cpu that
+the "uninteresting" processes are getting. On the basis of this value,
+one may decide to change what is "interesting" by modifying the
+configuration predicate. If one wants to get a simple indication of how
+much of the cpu that all of the transient and "uninteresting" processes
+are getting, then hotproc.total.cpuother.percent is the answer.
+
+In order to see why the instances (processes) of the hotproc agent
+were chosen, one can check the hotproc.predicate.* metrics. These
+metrics show the values used by the predicate evaluation at the
+last refresh of the instance domain. For example, if one used a
+predicate of (syscalls > 100), then doing:
+ $ pminfo -f hotproc.predicate.syscalls
+will show the values of the system call rates of the processes
+which satisfy the predicate (i.e. are greater than 100 per second
+over the last refresh interval).
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT hotproc
+
+Installation of the hotproc PMDA
+================================
+
+ + # cd $PCP_PMDAS_DIR/hotproc
+
+ + Check that there is no clash with the Performance Metrics Domain
+ number defined in domain.h and the other PMDAs currently in use
+ (see $PCP_PMCDCONF_PATH). If there is, edit domain.h and choose
+ another domain number.
+
+ + Inspect the ./sample.conf file and either modify it or create a new
+ configuration file that suits your need for "interesting". See
+ pmdahotproc(1) for configuration specification.
+
+ + Then run the Install script (as root)
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ Answer the questions, which include the option to specify the
+ configuration file that you created. You will also need to specify
+ a refresh interval which determines how often the "hot" predicate
+ is used over the current set of processes. A smaller number will
+ mean that the predicate will be able to choose processes which have
+ short lives or sporadic activity but will consume more CPU because
+ it is run more often.
+
+ + At the end of installation a check is made to verify that the
+ metrics of the agent can be retrieved. The reported number from this
+ check will be low because most of the hotproc metrics will not be
+ available until after the first refresh interval.
+
+Special TRIX Installation Considerations
+========================================
+
+ For SGI Trix systems, the hotproc PMDA needs the CAP_MAC_READ
+ capability in addition to the default capability (CAP_SCHED_MGT),
+ before it can interrogate the resource utilization of all processes.
+
+ To achieve this, run the ./Install script as described above, then
+
+ 1. edit /etc/pmcd.conf and for the pmdahotproc line, replace the
+ pmda invocation arguments
+ $PCP_PMDAS_DIR/hotproc/pmdahotproc ...
+ by
+ /sbin/suattr -C CAP_SCHED_MGT,CAP_MAC_READ+ipe -c "$PCP_PMDAS_DIR/hotproc/pmdahotproc ..."
+
+ 2. restart pmcd
+ # /etc/init.d/pcp start
+
+ Thanks to Roald Lygre for this recipe.
+
+De-installation
+===============
+
+Simply use
+
+ # cd $PCP_PMDAS_DIR/hotproc
+ # ./Remove
+
+Changing the settings
+=====================
+
+The refresh period can be dynamically modified using
+pmstore(1) for the metric hotproc.control.refresh.
+
+To make permanent changes, re-run the Install script.
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/hotproc.log) should be checked for any warnings
+ or errors.
+
+ + If the Install script reports some warnings when checking the
+ metrics, the problem should be listed in one of the log files.
diff --git a/src/pmdas/hotproc/Remove b/src/pmdas/hotproc/Remove
new file mode 100644
index 0000000..37b3a89
--- /dev/null
+++ b/src/pmdas/hotproc/Remove
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the sample PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=hotproc
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/hotproc/fixpmns.awk b/src/pmdas/hotproc/fixpmns.awk
new file mode 100644
index 0000000..bf2d568
--- /dev/null
+++ b/src/pmdas/hotproc/fixpmns.awk
@@ -0,0 +1,35 @@
+# states
+# 0 nothing interesting is happening, looking for proc { ... }
+# 1 inside proc { ... }; want to copy all lines except
+# "nprocs" metric that does not come from here for the
+# hotproc PMDA
+# 2 looking for proc.foo { ... }
+# 3 inside proc.foo { ... }; want to copy all lines
+#
+BEGIN { print "/*"
+ print " * Hotproc Performance Metric Domain (PMD) Identifiers"
+ print " * (generated from pmns for hotproc and proc metrics)"
+ print " */"
+ print ""
+ print "hotproc {"
+ print " nprocs HOTPROC:100:0"
+ print " cpuburn HOTPROC:102:0"
+ print " total"
+ print " predicate"
+ print " control"
+ state = 0
+ }
+
+state == 0 && /^proc / { state = 1; next }
+
+state == 1 && /nprocs/ { next }
+
+state == 1 && /^}/ { print; print ""; state = 2; next }
+
+state == 1 { print }
+
+state == 2 && /^proc\./ { state = 3 }
+
+state == 3 && /^}/ { print; print ""; state = 2; next }
+
+state == 3 { print }
diff --git a/src/pmdas/hotproc/general.conf b/src/pmdas/hotproc/general.conf
new file mode 100644
index 0000000..b51ca11
--- /dev/null
+++ b/src/pmdas/hotproc/general.conf
@@ -0,0 +1,27 @@
+#pmdahotproc
+Version 1.0
+
+# this configuration is of a more general nature, and identifies
+# a number of "interesting" classes of processes ... it is assumed
+# that some other monitoring tool, like pmie, would be able to
+# decide which processes are members of each "interesting" class
+#
+# a sample pmie ruleset matching this hotproc configuration may
+# be found in $PCP_PMDAS_DIR/hotproc/general.pmie
+#
+
+ cpuburn > 0.95 # one process using more than the equivalent of
+ # 95% of one processor
+
+|| iodemand > 500 # or more than 500 Kbytes/sec passing across the
+ # read() and write() system call interfaces
+
+|| syscalls > 100 # or more than 100 system calls/sec
+
+|| iowait > 0.33 # or more than 33% of the time is waiting for
+ # some sort of I/O
+
+|| schedwait > 0.25 # or more than 25% of the time is on the run
+ # queue waiting for the scheduler to assign
+ # a processor
+
diff --git a/src/pmdas/hotproc/general.pmie b/src/pmdas/hotproc/general.pmie
new file mode 100755
index 0000000..6f4929c
--- /dev/null
+++ b/src/pmdas/hotproc/general.pmie
@@ -0,0 +1,29 @@
+// this pmie configuration files assumes the hotproc PMDA has been
+// configured with the "general.conf" configuration file, and thus
+// is using as predicate something like:
+//
+// cpuburn > 0.95 || iodemand > 500 || syscalls > 100
+// || iowait > 0.33 || schedwait > 0.25
+//
+
+delta = 60sec; // change this if the hotproc PMDA refresh interval
+ // is different to the default of 60 seconds
+
+some_inst 100 * hotproc.psinfo.time > 95
+ -> print "\nCPU: %v% busy %i";
+
+io_bytes = "hotproc.accounting.counts";
+some_inst
+ (($io_bytes.chr + $io_bytes.chw) / 1024) > 500
+ -> print "\nI/O: %v Kbyte/sec %i";
+
+some_inst hotproc.psusage.sysc > 100
+ -> print "\nsyscall: %v call/sec %i";
+
+io_timers = "hotproc.accounting.timers";
+some_inst
+ 100 * ( $io_timers.bwtime + $io_timers.rwtime ) > 33
+ -> print "\nI/O: %v% waiting %i";
+
+some_inst 100 * $io_timers.qwtime > 25
+ -> print "\nCPU: %v% runq waiting %i";
diff --git a/src/pmdas/hotproc/help.fmt b/src/pmdas/hotproc/help.fmt
new file mode 100755
index 0000000..1e44ccd
--- /dev/null
+++ b/src/pmdas/hotproc/help.fmt
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Run paragraphs of help text thru fmt(1)
+# Lines with tabs or double spaces are run thru fmt(1) only for that line
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+$PCP_AWK_PROG -v tmpfile=$tmp/fmt '
+ function fmt_line(l) {
+ print l > tmpfile
+ system("fmt -78 < " tmpfile) # format line(s)
+ close(tmpfile)
+ system(">" tmpfile) # truncate file
+ }
+ function format_line() {
+ if (line != "") {
+ fmt_line(line)
+ line = ""
+ }
+ }
+ BEGIN { line = "" }
+ /^#/ || /^@/ { format_line(); print; next }
+ / / || / / { format_line(); fmt_line($0); next }
+ /^$/ { format_line(); print; next }
+ {
+ if (line == "")
+ line = $0
+ else
+ line = sprintf("%s\n%s",line,$0)
+ }
+ END { format_line() }
+'
diff --git a/src/pmdas/hotproc/help.hotproc b/src/pmdas/hotproc/help.hotproc
new file mode 100644
index 0000000..30a74da
--- /dev/null
+++ b/src/pmdas/hotproc/help.hotproc
@@ -0,0 +1,141 @@
+@ hotproc.cpuburn CPU utilization per "interesting" process
+CPU utilization, or the fraction of time that each "interesting" process
+was executing (user and system time) over the last refresh interval.
+Also known as the "cpuburn" time.
+
+@ hotproc.control.refresh time in secs between refreshes
+Controls how long it takes before the "interesting" process list is refreshed
+and new cpuburn times (see hotproc.cpuburn) calculated. This value can be
+changed at any time by using pmstore(1) if the permission is given during
+installation of the hotproc PMDA. Once the value is changed, the instances
+will not be available until after the new refresh period has elapsed.
+
+@ hotproc.control.config configuration predicate
+The configuration predicate that is used to characterize "interesting"
+processes. This will initially be the predicate as specified in the
+configuration file. This value can be changed at any time by using
+pmstore(1) if the permission is given during installation of the hotproc
+PMDA. Once the value is changed, the instances will not be available
+until after the refresh period has elapsed.
+
+@ hotproc.control.config_gen configuration generation number
+Each time the configuration predicate is updated (see hotproc.control.config)
+the configuration generation number is incremented.
+
+@ hotproc.total.cpuburn total amount of cpuburn over all "interesting" processes
+The sum of the CPU utilization ("cpuburn" or the fraction of time that each
+process was executing in user or system mode over the last refresh interval)
+for all the "interesting" processes.
+
+Values are in the range 0 to the number of CPUs.
+
+@ hotproc.total.cpuidle fraction of CPU idle time
+The fraction of all CPU time classified as idle over the last refresh
+interval.
+
+@ hotproc.total.cpuother.not_cpuburn total amount of cpuburn over all uninteresting processes
+The sum of the CPU utilization ("cpuburn" or the fraction of time that
+each process was executing in user or system mode over the last refresh
+interval) for all the "uninteresting" processes. If this value is high in
+comparison to hotproc.total.cpuburn, then configuration predicate of the
+hotproc PMDA is classifying a significant fraction of the CPU utilization
+to processes that are not "interesting".
+
+Values are in the range 0 to the number of CPUs.
+
+@ hotproc.total.cpuother.transient fraction of time utilized by "transient" processes
+The total CPU utilization (fraction of time that each process was executing
+in user or system mode) for processes which are not present throughout
+the most recent refreshes interval. The hotproc PMDA is limited to
+selecting processes which are present throughout each refresh intervals.
+If processes come and/or go during a refresh interval then they will never
+be considered. This metric gives an indication of the level of activity of
+these "transient" processes. If the value is large in comparison to the
+sum of hotproc.total.cpuburn and hotproc.total.cpuother.not_cpuburn then
+the "transient" processes are consuming lots of CPU time. Under these
+circumstances, the hotproc PMDA may be less useful, or consideration
+should be given to decreasing the value of the refresh interval
+(hotproc.control.refresh) so fewer "transient" processes escape
+consideration.
+
+Values are in the range 0 to the number of CPUs.
+
+@ hotproc.total.cpuother.total total amount of cpuburn other than the "interesting" processes
+Non-idle CPU utilization not accounted for by processes other than those
+deemed "interesting". It is equivalent to hotproc.total.cpuother.not_cpuburn
++ hotproc.total.cpuother.transient.
+
+Values are in the range 0 to the number of CPUs.
+
+@ hotproc.total.cpuother.percent how much of the cpu for "transients" and uninterestings
+Gives an indication of how much of the CPU time the "transient" processes
+and the "uninteresting" processes are accounting for. Computed as:
+ 100 * hotproc.total.cpuother.total / number of CPUs
+
+@ hotproc.predicate.syscalls number of system calls per second over refresh interval
+The number of system calls per second over the last refresh interval for
+each "interesting" process. If the refresh interval spans times from t1
+to t2, then this is calculated by:
+
+ m = hotproc.psusage.pu_sysc
+
+ (m@t2 - m@t1) / (t2 - t1)
+
+@ hotproc.predicate.ctxswitch number of context switches per second over refresh interval
+The number of context switches per second over the last refresh interval
+for each "interesting" process. If the refresh interval spans times from
+t1 to t2, then this is calculated by:
+
+ m = hotproc.psusage.pu_vctx + hotproc.psusage.pu_ictx
+
+ (m@t2 - m@t1) / (t2 - t1)
+
+@ hotproc.predicate.virtualsize virtual size of process in kilobytes at last refresh
+The virtual size of each "interesting" process in kilobytes at the last
+refresh time, calculated by:
+
+ hotproc.psinfo.size
+
+@ hotproc.predicate.residentsize resident size of process in kilobytes at last refresh
+The resident size of each "interesting" process in kilobytes at the last
+refresh, calculated by:
+
+ hotproc.psinfo.rssize
+
+@ hotproc.predicate.iodemand total kilobytes read and written per second over refresh interval
+The total kilobytes read and written per second over the last refresh
+interval for each "interesting" process. If the refresh interval spans
+times from t1 to t2, then this is calculated by:
+
+ // bytes read()
+ br = hotproc.psusage.bread@t2 - hotproc.psusage.bread@t1
+ // Gigabytes read()
+ gbr = hotproc.psusage.gbread@t2 - hotproc.psusage.gbread@t1
+ // bytes write()
+ bw = hotproc.psusage.bwrit@t2 - hotproc.psusage.bwrit@t1
+ // Gigabytes write()
+ gbw = hotproc.psusage.gbwrit@t2 - hotproc.psusage.gbwrit@t1
+
+ ((gbr + gbw) * 1024 * 1024 + (br + bw) / 1024 ) / (t2 - t1)
+
+@ hotproc.predicate.iowait time in secs waiting for I/O per second over refresh interval
+The fraction of time waiting for I/O for each "interesting" process over
+refresh interval. If the refresh interval spans times from t1 to t2,
+then this is calculated by:
+
+ bw = (hotproc.accounting.timers.bwtime@t2 -
+ hotproc.accounting.timers.bwtime@t1) / 1000000000
+ rw = (hotproc.accounting.timers.rwtime@t2 -
+ hotproc.accounting.timers.rwtime@t1) / 1000000000
+
+ (bw + rw) / (t2 - t1)
+
+@ hotproc.predicate.schedwait time in secs waiting on run queue per second over refresh interval
+The fraction of time waiting on the run queue for each "interesting"
+process over the last refresh interval. If the refresh interval spans
+times from t1 to t2, then this is calculated by:
+
+ qw = (hotproc.accounting.timers.qwtime@t2 -
+ hotproc.accounting.timers.qwtime@t1) / 1000000000
+
+ qw / (t2 - t1)
diff --git a/src/pmdas/hotproc/pmns.hotproc b/src/pmdas/hotproc/pmns.hotproc
new file mode 100644
index 0000000..0ea5a0e
--- /dev/null
+++ b/src/pmdas/hotproc/pmns.hotproc
@@ -0,0 +1,34 @@
+
+/*
+ * Metrics borrowed from "proc" pmda above ... these ones are specific
+ * to the hotproc PMDA.
+ */
+
+hotproc.control {
+ refresh HOTPROC:100:1
+ config HOTPROC:100:8
+ config_gen HOTPROC:100:9
+}
+
+hotproc.total {
+ cpuidle HOTPROC:100:2
+ cpuburn HOTPROC:100:3
+ cpuother
+}
+
+hotproc.total.cpuother {
+ transient HOTPROC:100:4
+ not_cpuburn HOTPROC:100:5
+ total HOTPROC:100:6
+ percent HOTPROC:100:7
+}
+
+hotproc.predicate {
+ syscalls HOTPROC:101:0
+ ctxswitch HOTPROC:101:1
+ virtualsize HOTPROC:101:2
+ residentsize HOTPROC:101:3
+ iodemand HOTPROC:101:4
+ iowait HOTPROC:101:5
+ schedwait HOTPROC:101:6
+}
diff --git a/src/pmdas/hotproc/root b/src/pmdas/hotproc/root
new file mode 100644
index 0000000..0e14a7d
--- /dev/null
+++ b/src/pmdas/hotproc/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { hotproc }
+
+#include "pmns"
+
diff --git a/src/pmdas/hotproc/sample.conf b/src/pmdas/hotproc/sample.conf
new file mode 100644
index 0000000..6421e51
--- /dev/null
+++ b/src/pmdas/hotproc/sample.conf
@@ -0,0 +1,15 @@
+#pmdahotproc
+Version 1.0
+
+# Sample configuration file for hotproc PMDA
+#
+# It selects processes who are using more than 85% of the cpu
+# over the refresh interval.
+
+cpuburn > 0.85
+
+# Another example:
+# Reporting processes which are either
+# using greater than 85% cpu or
+# are root processes using greater than 1% cpu
+# ((cpuburn > 0.01) && (uname == "root")) || (cpuburn > 0.85)
diff --git a/src/pmdas/hotproc/src/GNUmakefile b/src/pmdas/hotproc/src/GNUmakefile
new file mode 100644
index 0000000..6000032
--- /dev/null
+++ b/src/pmdas/hotproc/src/GNUmakefile
@@ -0,0 +1,34 @@
+#!gmake
+#
+# Copyright (c) 2007 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+# This is just to get the source exported to the community.
+# 'Makefile' in particular is included only for clues it might offer,
+# and should almost certainly be removed from SRCFILES once
+# the pmda is working.
+LSRCFILES = Makefile config.c config.h ctltab.c error.c gram.y \
+gram_node.c gram_node.h hotproc.c hotproc.h lex.l pcpu.c pcpu.h \
+pglobal.c pglobal.h ppred_values.c ppred_values.h
+
+default default_pcp:
+install install_pcp:
+include $(BUILDRULES)
+
diff --git a/src/pmdas/hotproc/src/config.c b/src/pmdas/hotproc/src/config.c
new file mode 100644
index 0000000..8710810
--- /dev/null
+++ b/src/pmdas/hotproc/src/config.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/procfs.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "gram_node.h"
+#include "gram.h"
+#include "nameinfo.h"
+#include "config.h"
+
+char *conf_buffer = NULL; /* contains config text */
+char *conf_buffer_ptr = NULL;
+char *pred_buffer = NULL; /* contains parsed predicate */
+
+static bool_node *the_tree = NULL;
+static config_vars *the_vars = NULL;
+
+/* internal functions */
+static int eval_predicate(bool_node *);
+static int eval_comparison(bool_node *);
+static int eval_num_comp(N_tag, bool_node *, bool_node *);
+static int eval_str_comp(N_tag, bool_node *, bool_node *);
+static int eval_match_comp(N_tag, bool_node *, bool_node *);
+static char* get_strvalue(bool_node *);
+static double get_numvalue(bool_node *);
+static void eval_error(char *);
+
+extern int parse_predicate(bool_node **);
+extern char *pmProgname;
+extern char *configfile;
+extern FILE *yyin;
+
+
+
+FILE *
+open_config(void)
+{
+ FILE *conf;
+ if ((conf = fopen(configfile, "r")) == NULL) {
+ (void)fprintf(stderr, "%s: Unable to open configuration file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ return conf;
+}
+
+int
+parse_config(bool_node **tree)
+{
+ int sts;
+ FILE *file = NULL;
+ char *fname;
+ struct stat stat_buf;
+ long size;
+ char *ptr;
+
+ if ((sts = parse_predicate(tree)) != 0) {
+ (void)fprintf(stderr, "%s: Failed to parse configuration file\n", pmProgname);
+ return sts;
+ }
+
+ /* --- dump to tmp file & read to buffer --- */
+ if ((fname = tmpnam(NULL)) == NULL ||
+ (file = fopen(fname, "w+")) == NULL) {
+ sts = -oserror();
+ fprintf(stderr, "%s: parse_config: failed to create \"%s\": %s\n",
+ pmProgname, fname, strerror(-sts));
+ goto error;
+ }
+ if (unlink(fname) == -1) {
+ sts = -oserror();
+ fprintf(stderr, "%s: parse_config: failed to unlink \"%s\": %s\n",
+ pmProgname, fname, strerror(-sts));
+ goto error;
+ }
+ dump_predicate(file, *tree);
+ fflush(file);
+ if (fstat(fileno(file), &stat_buf) < 0) {
+ sts = -oserror();
+ fprintf(stderr, "%s: parse_config: failed to stat \"%s\": %s\n",
+ pmProgname, fname, strerror(-sts));
+ goto error;
+ }
+ size = (long)stat_buf.st_size;
+ ptr = malloc(size+1);
+ if (ptr == NULL) {
+ sts = -oserror();
+ fprintf(stderr, "%s: parse_config: failed to malloc: %s\n",
+ pmProgname, strerror(-sts));
+ goto error;
+ }
+ rewind(file);
+ if (fread(ptr, size, 1, file) != 1) {
+ clearerr(file);
+ fprintf(stderr, "%s: parse_config: failed to fread \"%s\"\n",
+ pmProgname, fname);
+ sts = -1;
+ goto error;
+ }
+ (void)fclose(file);
+
+ if (pred_buffer != NULL)
+ free(pred_buffer);
+ pred_buffer = ptr;
+ pred_buffer[size] = '\0';
+ return 0;
+
+error:
+ if (file != NULL)
+ (void)fclose(file);
+ return sts;
+}
+
+void
+new_tree(bool_node *tree)
+{
+ free_tree(the_tree);
+ the_tree = tree;
+}
+
+void
+read_config(FILE *conf)
+{
+ struct stat stat_buf;
+ long size;
+ int sts;
+ size_t nread;
+
+ /* get length of file */
+ sts = fstat(fileno(conf), &stat_buf);
+ if (sts < 0) {
+ (void)fprintf(stderr, "%s: Failure to stat configuration file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ size = (long)stat_buf.st_size;
+
+ /* create buffer */
+ conf_buffer = (char*)malloc(size+1*sizeof(char));
+ if (conf_buffer == NULL) {
+ (void)fprintf(stderr, "%s: Failure to create buffer for configuration file \"%s\"\n",
+ pmProgname, configfile);
+ exit(1);
+ }
+
+ conf_buffer_ptr = conf_buffer;
+
+ /* read whole file into buffer */
+ nread = fread(conf_buffer, sizeof(char), size, conf);
+ if (nread != size) {
+ (void)fprintf(stderr, "%s: Failure to read configuration file \"%s\" into buffer\n",
+ pmProgname, configfile);
+ exit(1);
+ }
+ conf_buffer[size] = '\0'; /* terminate the buffer */
+
+ if (parse_config(&the_tree) != 0)
+ exit(1);
+}
+
+void
+dump_tree(FILE *f)
+{
+ dump_bool_tree(f, the_tree);
+}
+
+
+int
+eval_tree(config_vars *vars)
+{
+ the_vars = vars;
+ return eval_predicate(the_tree);
+}
+
+/*
+ * do predicate testing for qa
+ */
+
+#define QA_LINE 512
+
+/*
+ * Return convention
+ * EOF = finished line or file
+ * 0 = error in input
+ * 1 = successful and continue
+ */
+
+/*
+ * Read test vars of form: "var=value|var=value|var=value"
+ */
+
+static int
+read_test_var(char *line, config_vars *vars)
+{
+ const char EQUALS = '=';
+ const char DIVIDER = '|';
+ char var[QA_LINE];
+ char value[QA_LINE];
+ static char *c;
+ int i = 0;
+
+ /* if line is NULL then continue where left off */
+ if (line != NULL)
+ c = line;
+
+ if (*c == '\n')
+ return EOF;
+
+ /* --- get variable name --- */
+ i = 0;
+ while(*c != EQUALS && *c != '\n') {
+ var[i++] = *c++;
+ }
+ var[i] = '\0';
+
+ if (*c == '\n') {
+ fprintf(stderr, "%s: Error reading test variable, "
+ "looking for \"%c\"\n", pmProgname, EQUALS);
+ return 0;
+ }
+
+ c++; /* skip over EQUALS */
+
+ /* --- get value --- */
+ i = 0;
+ while(*c != DIVIDER && *c != '\n') {
+ value[i++] = *c++;
+ }
+ value[i] = '\0';
+
+ if (*c == DIVIDER) /* skip over DIVIDER */
+ c++;
+
+ /* --- var = value --- */
+ if (strcmp(var, "uid") == 0) {
+ vars->uid = atoi(value);
+ }
+ else if (strcmp(var, "uname") == 0) {
+ if ((vars->uname = strdup(value)) == NULL)
+ goto failure;
+ }
+ else if (strcmp(var, "gid") == 0) {
+ vars->gid = atoi(value);
+ }
+ else if (strcmp(var, "gname") == 0) {
+ if ((vars->gname = strdup(value)) == NULL)
+ goto failure;
+ }
+ else if (strcmp(var, "fname") == 0) {
+ (void)strcpy(vars->fname, value);
+ }
+ else if (strcmp(var, "psargs") == 0) {
+ (void)strcpy(vars->psargs, value);
+ }
+ else if (strcmp(var, "cpuburn") == 0) {
+ vars->cpuburn = atof(value);
+ }
+ else if (strcmp(var, "syscalls") == 0) {
+ vars->preds.syscalls = atof(value);
+ }
+ else if (strcmp(var, "pu_sysc") == 0) {
+ vars->pu_sysc = atol(value);
+ }
+ else if (strcmp(var, "ctxswitch") == 0) {
+ vars->preds.ctxswitch = atof(value);
+ }
+ else if (strcmp(var, "pu_vctx") == 0) {
+ vars->pu_vctx = atol(value);
+ }
+ else if (strcmp(var, "pu_ictx") == 0) {
+ vars->pu_ictx = atol(value);
+ }
+ else if (strcmp(var, "virtualsize") == 0) {
+ vars->preds.virtualsize = atof(value);
+ }
+ else if (strcmp(var, "pr_size") == 0) {
+ vars->pr_size = atol(value);
+ }
+ else if (strcmp(var, "residentsize") == 0) {
+ vars->preds.residentsize = atof(value);
+ }
+ else if (strcmp(var, "pr_rssize") == 0) {
+ vars->pr_rssize = atol(value);
+ }
+ else if (strcmp(var, "iodemand") == 0) {
+ vars->preds.iodemand = atof(value);
+ }
+ else if (strcmp(var, "pu_gbread") == 0) {
+ vars->pu_gbread = atol(value);
+ }
+ else if (strcmp(var, "pu_bread") == 0) {
+ vars->pu_bread = atol(value);
+ }
+ else if (strcmp(var, "pu_gbwrit") == 0) {
+ vars->pu_gbwrit = atol(value);
+ }
+ else if (strcmp(var, "pu_bwrit") == 0) {
+ vars->pu_bwrit = atol(value);
+ }
+ else if (strcmp(var, "iowait") == 0) {
+ vars->preds.iowait = atof(value);
+ }
+ else if (strcmp(var, "ac_bwtime") == 0) {
+ vars->ac_bwtime = atoll(value);
+ }
+ else if (strcmp(var, "ac_rwtime") == 0) {
+ vars->ac_rwtime = atoll(value);
+ }
+ else if (strcmp(var, "schedwait") == 0) {
+ vars->preds.schedwait = atof(value);
+ }
+ else if (strcmp(var, "ac_qwtime") == 0) {
+ vars->ac_qwtime = atoll(value);
+ }
+ else {
+ fprintf(stderr, "%s: Error unrecognised test variable: \"%s\"\n",
+ pmProgname, var);
+ return 0;
+ }
+
+ return 1;
+
+failure:
+ (void)fprintf(stderr, "%s: malloc failed for read_test_var()\n", pmProgname);
+ exit(1);
+}
+
+
+int
+read_test_values(FILE *file, config_vars *vars)
+{
+ static char line[QA_LINE];
+ int sts;
+ int i;
+
+ if (fgets(line, QA_LINE-1, file) == NULL)
+ return EOF;
+
+ if (strlen(line) == QA_LINE-1) {
+ fprintf(stderr, "%s: line limit exceeded\n", pmProgname);
+ return 0;
+ }
+
+ /* note that line must end in '\n' */
+
+ /* reset all values */
+ (void)memset(vars, 0, sizeof(*vars));
+
+ /* read each var=value pair for a line */
+ for(i=0;/*forever*/;i++) {
+ sts = read_test_var((i==0?line:NULL), vars);
+ if (sts == EOF)
+ return 1;
+ if (sts == 0)
+ return 0;
+ }
+}
+
+
+
+void
+do_pred_testing(void)
+{
+ int sts;
+ config_vars vars;
+ FILE *conf = NULL;
+
+ conf = open_config();
+ read_config(conf);
+ (void)fclose(conf);
+
+ dump_tree(stdout);
+
+ for (;;) {
+ sts = read_test_values(stdin, &vars);
+ if (sts == EOF)
+ break;
+ if (sts == 0) {
+ (void)fprintf(stderr, "Bad input line\n");
+ continue;
+ }
+
+ if (eval_tree(&vars)) {
+ (void)fprintf(stdout, "true\n");
+ }
+ else {
+ (void)fprintf(stdout, "false\n");
+ }
+ }
+}
+
+
+
+static void
+eval_error(char *msg)
+{
+ (void)fprintf(stderr, "%s: Internal error : %s\n", pmProgname, msg?msg:"");
+ exit(1);
+}
+
+static int
+eval_predicate(bool_node *pred)
+{
+ bool_node *lhs, *rhs;
+
+ switch(pred->tag) {
+ case N_and:
+ lhs = pred->data.children.left;
+ rhs = pred->data.children.right;
+ return eval_predicate(lhs) && eval_predicate(rhs);
+ case N_or:
+ lhs = pred->data.children.left;
+ rhs = pred->data.children.right;
+ return eval_predicate(lhs) || eval_predicate(rhs);
+ case N_not:
+ lhs = pred->data.children.left;
+ return !eval_predicate(lhs);
+ case N_true:
+ return 1;
+ case N_false:
+ return 0;
+ default:
+ return eval_comparison(pred);
+ }/*switch*/
+}
+
+static int
+eval_comparison(bool_node *comp)
+{
+ bool_node *lhs = comp->data.children.left;
+ bool_node *rhs = comp->data.children.right;
+
+ switch(comp->tag) {
+ case N_lt: case N_gt: case N_ge: case N_le:
+ case N_eq: case N_neq:
+ return eval_num_comp(comp->tag, lhs, rhs);
+ case N_seq: case N_sneq:
+ return eval_str_comp(comp->tag, lhs, rhs);
+ case N_match: case N_nmatch:
+ return eval_match_comp(comp->tag, lhs, rhs);
+ default:
+ eval_error("comparison");
+ }/*switch*/
+}
+
+static int
+eval_num_comp(N_tag tag, bool_node *lhs, bool_node *rhs)
+{
+ double x = get_numvalue(lhs);
+ double y = get_numvalue(rhs);
+
+ switch(tag) {
+ case N_lt: return (x < y);
+ case N_gt: return (x > y);
+ case N_le: return (x <= y);
+ case N_ge: return (x >= y);
+ case N_eq: return (x == y);
+ case N_neq: return (x != y);
+ default:
+ eval_error("number comparison");
+ }/*switch*/
+}
+
+static double
+get_numvalue(bool_node *n)
+{
+ switch(n->tag) {
+ case N_number: return n->data.num_val;
+ case N_cpuburn: return the_vars->cpuburn;
+ case N_syscalls: return the_vars->preds.syscalls;
+ case N_ctxswitch: return the_vars->preds.ctxswitch;
+ case N_virtualsize: return the_vars->preds.virtualsize;
+ case N_residentsize: return the_vars->preds.residentsize;
+ case N_iodemand: return the_vars->preds.iodemand;
+ case N_iowait: return the_vars->preds.iowait;
+ case N_schedwait: return the_vars->preds.schedwait;
+ case N_gid: return the_vars->gid;
+ case N_uid: return the_vars->uid;
+ default:
+ eval_error("number value");
+ }
+}
+
+static int
+eval_str_comp(N_tag tag, bool_node *lhs, bool_node *rhs)
+{
+ char *x = get_strvalue(lhs);
+ char *y = get_strvalue(rhs);
+
+ switch(tag) {
+ case N_seq: return (strcmp(x,y)==0?1:0);
+ case N_sneq: return (strcmp(x,y)==0?0:1);
+ default:
+ eval_error("string comparison");
+ }/*switch*/
+}
+
+static int
+eval_match_comp(N_tag tag, bool_node *lhs, bool_node *rhs)
+{
+ int sts;
+ char *res;
+ char *str= get_strvalue(lhs);
+ char *pat = get_strvalue(rhs);
+
+ if (rhs->tag != N_pat) {
+ eval_error("match");
+ }
+
+ res = re_comp(pat);
+ if (res != NULL) {
+ /* should have been checked at lex stage */
+ /* => internal error */
+ eval_error(res);
+ }
+ sts = re_exec(str);
+ if (sts < 0) {
+ eval_error("re_exec");
+ }
+
+ switch(tag) {
+ case N_match: return sts;
+ case N_nmatch: return !sts;
+ default:
+ eval_error("match comparison");
+ }/*switch*/
+}
+
+static char *
+get_strvalue(bool_node *n)
+{
+
+ switch(n->tag) {
+ case N_str:
+ case N_pat:
+ return n->data.str_val;
+ case N_gname:
+ if (the_vars->gname != NULL)
+ return the_vars->gname;
+ else
+ return get_gname_info(the_vars->gid);
+ case N_uname:
+ if (the_vars->uname != NULL)
+ return the_vars->uname;
+ else
+ return get_uname_info(the_vars->uid);
+ case N_fname: return the_vars->fname;
+ case N_psargs: return the_vars->psargs;
+ default:
+ eval_error("string value");
+ }/*switch*/
+}
diff --git a/src/pmdas/hotproc/src/config.h b/src/pmdas/hotproc/src/config.h
new file mode 100644
index 0000000..7de4186
--- /dev/null
+++ b/src/pmdas/hotproc/src/config.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+#include <sys/procfs.h>
+
+typedef struct {
+ double syscalls;
+ double ctxswitch;
+ double virtualsize;
+ double residentsize;
+ double iodemand;
+ double iowait;
+ double schedwait;
+} derived_pred_t;
+
+
+typedef struct {
+ uid_t uid; /* real user id */
+ gid_t gid; /* real group id */
+ char *uname;
+ char *gname;
+ char fname[PRCOMSIZ]; /* basename of exec()'d pathname */
+ char psargs[PRARGSZ]; /* initial chars of arg list */
+ double cpuburn;
+
+ derived_pred_t preds;
+
+ /* --- ioctl buffer fields for testing purposes only --- */
+
+ /* prpsinfo_t fields */
+ ulong_t pr_size;
+ ulong_t pr_rssize;
+
+ /* prusage_t fields */
+ ulong_t pu_sysc;
+ ulong_t pu_ictx;
+ ulong_t pu_vctx;
+ ulong_t pu_gbread;
+ ulong_t pu_bread;
+ ulong_t pu_gbwrit;
+ ulong_t pu_bwrit;
+
+ /* accounting fields */
+ accum_t ac_bwtime;
+ accum_t ac_rwtime;
+ accum_t ac_qwtime;
+
+} config_vars;
+
+#include "gram_node.h"
+
+FILE *open_config(void);
+void read_config(FILE *);
+int parse_config(bool_node **tree);
+void new_tree(bool_node *tree);
+int eval_tree(config_vars *);
+void dump_tree(FILE *);
+void do_pred_testing(void);
+int read_test_values(FILE *, config_vars *);
+
+#endif
diff --git a/src/pmdas/hotproc/src/ctltab.c b/src/pmdas/hotproc/src/ctltab.c
new file mode 100644
index 0000000..534cbe0
--- /dev/null
+++ b/src/pmdas/hotproc/src/ctltab.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <sys/procfs.h>
+#include "pmapi.h"
+#include "ctltab.h"
+#include "pglobal.h"
+#include "pmemory.h"
+#include "pracinfo.h"
+#include "pscred.h"
+#include "psinfo.h"
+#include "pstatus.h"
+#include "psusage.h"
+#include "pcpu.h"
+#include "ppred_values.h"
+
+ctltab_entry ctltab[] = {
+ { CLUSTER_GLOBAL, 1,
+ pglobal_init, pglobal_getdesc, pglobal_setatom, pglobal_getinfo, pglobal_allocbuf },
+ { 1, 1,
+ psinfo_init, psinfo_getdesc, psinfo_setatom, psinfo_getinfo, psinfo_allocbuf },
+ { 2, 1,
+ pstatus_init, pstatus_getdesc, pstatus_setatom, pstatus_getinfo, pstatus_allocbuf },
+ { 3, 1,
+ pscred_init, pscred_getdesc, pscred_setatom, pscred_getinfo, pscred_allocbuf },
+ { 4, 1,
+ psusage_init, psusage_getdesc, psusage_setatom, psusage_getinfo, psusage_allocbuf },
+ { 5, 1,
+ pmem_init, pmem_getdesc, pmem_setatom, pmem_getinfo, pmem_allocbuf },
+ { 6, 1,
+ pracinfo_init, pracinfo_getdesc, pracinfo_setatom, pracinfo_getinfo, pracinfo_allocbuf },
+ { CLUSTER_CPU, 1,
+ pcpu_init, pcpu_getdesc, pcpu_setatom, pcpu_getinfo, pcpu_allocbuf },
+ { CLUSTER_PRED, 1,
+ ppred_init, ppred_getdesc, ppred_setatom, ppred_getinfo, ppred_allocbuf },
+};
+
+
+int nctltab = sizeof(ctltab) / sizeof (ctltab[0]);
+
+int
+lookup_ctltab(int cluster)
+{
+ int i;
+
+ for (i = 0; i < nctltab; i++) {
+ if (ctltab[i].cluster == cluster)
+ return i;
+ }
+ return PM_ERR_PMID;
+}
diff --git a/src/pmdas/hotproc/src/error.c b/src/pmdas/hotproc/src/error.c
new file mode 100644
index 0000000..f71c5db
--- /dev/null
+++ b/src/pmdas/hotproc/src/error.c
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void
+yywarn(s)
+char *s;
+{
+ extern int yylineno;
+
+ (void)fprintf(stderr, "Warning [line %d]\n%s\n", yylineno, s);
+}
+
+void
+yyerror(s)
+char *s;
+{
+ extern int yylineno;
+ extern char yytext[];
+
+ (void)fprintf(stderr, "Specification error in configuration\n");
+ (void)fprintf(stderr, "[line %d] %s: %s\n", yylineno, s, yytext);
+}
diff --git a/src/pmdas/hotproc/src/gram.y b/src/pmdas/hotproc/src/gram.y
new file mode 100644
index 0000000..b98564f
--- /dev/null
+++ b/src/pmdas/hotproc/src/gram.y
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include <stdio.h>
+#include "./gram_node.h"
+
+void yyerror(char *s);
+int yylex(void);
+int yyparse(void);
+
+int need_psusage = 0;
+int need_accounting = 0;
+static bool_node *pred_tree = NULL;
+
+%}
+
+%union {
+ char *y_str;
+ double y_number;
+ bool_node *y_node;
+ }
+
+%token <y_number>
+ NUMBER
+
+%token <y_str>
+ STRING
+ PATTERN
+
+%term AND OR NOT
+ LPAREN RPAREN TRUE FALSE
+ EQUAL NEQUAL
+ LTHAN LEQUAL GTHAN GEQUAL
+ MATCH NMATCH
+ GID UID CPUBURN GNAME UNAME FNAME PSARGS
+ SYSCALLS CTXSWITCH VIRTUALSIZE RESIDENTSIZE
+ IODEMAND IOWAIT SCHEDWAIT
+ VERSION
+
+%type <y_node>
+ predicate comparison
+ num_compar numvar
+ str_compar strvar
+ pattern_compar
+
+%type <y_number>
+ version
+
+%left OR
+%left AND
+%left NOT
+
+%%
+
+pred_tree: predicate { pred_tree = $1;}
+ | version predicate { pred_tree = $2;}
+ ;
+
+version: VERSION NUMBER {
+ float version_num = $2;
+
+ if (version_num != 1.0) {
+ (void)fprintf(stderr, "Wrong version number in configuration predicate\n");
+ (void)fprintf(stderr, "Expected version %.2f, but was given version %.2f .\n",
+ 1.0, version_num);
+ YYABORT;
+ }
+ }
+
+predicate:
+ predicate AND predicate { $$ = create_tnode(N_and, $1, $3); }
+ | predicate OR predicate { $$ = create_tnode(N_or, $1, $3); }
+ | NOT predicate { $$ = create_tnode(N_not, $2, NULL); }
+ | LPAREN predicate RPAREN { $$ = $2; }
+ | comparison
+ | TRUE { $$ = create_tag_node(N_true); }
+ | FALSE { $$ = create_tag_node(N_false); }
+ ;
+
+comparison:
+ num_compar
+ | str_compar
+ | pattern_compar
+ ;
+
+num_compar:
+ numvar LTHAN numvar { $$ = create_tnode(N_lt, $1, $3); }
+ | numvar LEQUAL numvar { $$ = create_tnode(N_le, $1, $3); }
+ | numvar GTHAN numvar { $$ = create_tnode(N_gt, $1, $3); }
+ | numvar GEQUAL numvar { $$ = create_tnode(N_ge, $1, $3); }
+ | numvar EQUAL numvar { $$ = create_tnode(N_eq, $1, $3); }
+ | numvar NEQUAL numvar { $$ = create_tnode(N_neq, $1, $3); }
+ ;
+
+numvar: NUMBER { $$ = create_number_node($1); }
+ | GID { $$ = create_tag_node(N_gid); }
+ | UID { $$ = create_tag_node(N_uid); }
+ | CPUBURN { $$ = create_tag_node(N_cpuburn); }
+ | SYSCALLS { need_psusage = 1; $$ = create_tag_node(N_syscalls); }
+ | CTXSWITCH { need_psusage = 1; $$ = create_tag_node(N_ctxswitch); }
+ | VIRTUALSIZE { $$ = create_tag_node(N_virtualsize); }
+ | RESIDENTSIZE { $$ = create_tag_node(N_residentsize); }
+ | IODEMAND { need_psusage = 1; $$ = create_tag_node(N_iodemand); }
+ | IOWAIT { need_accounting = 1; $$ = create_tag_node(N_iowait); }
+ | SCHEDWAIT { need_accounting = 1; $$ = create_tag_node(N_schedwait); }
+ ;
+
+str_compar:
+ strvar EQUAL strvar { $$ = create_tnode(N_seq, $1, $3); }
+ | strvar NEQUAL strvar { $$ = create_tnode(N_sneq, $1, $3); }
+ ;
+
+strvar: STRING { $$ = create_str_node($1); }
+ | GNAME { $$ = create_tag_node(N_gname); }
+ | UNAME { $$ = create_tag_node(N_uname); }
+ | FNAME { $$ = create_tag_node(N_fname); }
+ | PSARGS { $$ = create_tag_node(N_psargs); }
+ ;
+
+pattern_compar:
+ strvar MATCH PATTERN { $$ = create_tnode(N_match, $1, create_pat_node($3)); }
+ | strvar NMATCH PATTERN { $$ = create_tnode(N_nmatch, $1, create_pat_node($3)); }
+ ;
+
+
+%%
+
+int
+parse_predicate(bool_node **tree)
+{
+ int sts;
+ extern int yylineno; /* defined by lex */
+
+ yylineno=1;
+
+ start_tree();
+ sts = yyparse();
+
+ /* free any partial trees */
+ if (sts != 0) {
+ free_tree(NULL);
+ return sts;
+ }
+
+ *tree = pred_tree;
+ return 0;
+}
diff --git a/src/pmdas/hotproc/src/gram_node.c b/src/pmdas/hotproc/src/gram_node.c
new file mode 100644
index 0000000..d2c36b5
--- /dev/null
+++ b/src/pmdas/hotproc/src/gram_node.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "./gram_node.h"
+
+/* functions */
+static void dump_comparison(FILE *, bool_node *);
+static void dump_var(FILE *, bool_node *);
+
+static bool_node *node_list = NULL;
+
+void start_tree(void)
+{
+ node_list = NULL;
+}
+
+void free_tree(bool_node *root)
+{
+ bool_node *n, *next;
+
+ if (root == NULL)
+ root = node_list; /* use last tree */
+
+ /* free all nodes in list */
+ for (n = root; n != NULL; ) {
+ next = n->next;
+ if (n->tag == N_pat || n->tag == N_str)
+ free(n->data.str_val);
+ free(n);
+ n = next;
+ }
+
+ if (root == node_list)
+ node_list = NULL;
+}
+
+bool_node *
+create_tag_node(N_tag tag)
+{
+ bool_node *new_node;
+
+ new_node = (bool_node*)malloc(sizeof(bool_node));
+ if (new_node == NULL) {
+ fprintf(stderr, "hotproc: malloc failed in config: %s", osstrerror());
+ exit(1);
+ }
+ new_node->tag = tag;
+
+ /* add to front of node-list */
+ new_node->next = node_list;
+ node_list = new_node;
+
+ return new_node;
+}
+
+bool_node *
+create_tnode(N_tag tag, bool_node *lnode, bool_node *rnode)
+{
+ bool_node *n = create_tag_node(tag);
+ n->data.children.left = lnode;
+ n->data.children.right = rnode;
+ return n;
+}
+
+bool_node *
+create_number_node(double x)
+{
+ bool_node *n = create_tag_node(N_number);
+ n->data.num_val = x;
+ return n;
+}
+
+
+bool_node *create_str_node(char *str)
+{
+ bool_node *n = create_tag_node(N_str);
+ n->data.str_val = str;
+ return n;
+}
+
+bool_node *create_pat_node(char *str)
+{
+ bool_node *n = create_tag_node(N_pat);
+ n->data.str_val = str;
+ return n;
+}
+
+void
+dump_bool_tree(FILE *f, bool_node *tree)
+{
+ (void)fprintf(f, "--- bool tree ---\n");
+ dump_predicate(f, tree);
+ (void)fprintf(f, "\n--- end bool tree ---\n");
+}
+
+void
+dump_predicate(FILE *f, bool_node *pred)
+{
+ bool_node *lhs, *rhs;
+
+ switch(pred->tag) {
+ case N_and:
+ lhs = pred->data.children.left;
+ rhs = pred->data.children.right;
+ (void)fprintf(f, "(");
+ dump_predicate(f, lhs);
+ (void)fprintf(f, " && ");
+ dump_predicate(f, rhs);
+ (void)fprintf(f, ")");
+ break;
+ case N_or:
+ lhs = pred->data.children.left;
+ rhs = pred->data.children.right;
+ (void)fprintf(f, "(");
+ dump_predicate(f, lhs);
+ (void)fprintf(f, " || ");
+ dump_predicate(f, rhs);
+ (void)fprintf(f, ")");
+ break;
+ case N_not:
+ lhs = pred->data.children.left;
+ (void)fprintf(f, "(! ");
+ dump_predicate(f, lhs);
+ (void)fprintf(f, ")");
+ break;
+ case N_true:
+ (void)fprintf(f, "(true)");
+ break;
+ case N_false:
+ (void)fprintf(f, "(false)");
+ break;
+ default:
+ dump_comparison(f, pred);
+ }/*switch*/
+}
+
+static void
+dump_comparison(FILE *f, bool_node *comp)
+{
+ bool_node *lhs = comp->data.children.left;
+ bool_node *rhs = comp->data.children.right;
+
+ (void)fprintf(f, "(");
+ dump_var(f, lhs);
+ switch(comp->tag) {
+ case N_lt: (void)fprintf(f, " < "); break;
+ case N_gt: (void)fprintf(f, " > "); break;
+ case N_le: (void)fprintf(f, " <= "); break;
+ case N_ge: (void)fprintf(f, " >= "); break;
+ case N_eq: (void)fprintf(f, " == "); break;
+ case N_seq: (void)fprintf(f, " == "); break;
+ case N_sneq: (void)fprintf(f, " != "); break;
+ case N_neq: (void)fprintf(f, " != "); break;
+ case N_match: (void)fprintf(f, " ~ "); break;
+ case N_nmatch: (void)fprintf(f, " !~ "); break;
+ default: (void)fprintf(f, "<ERROR>"); break;
+ }/*switch*/
+ dump_var(f, rhs);
+ (void)fprintf(f, ")");
+}
+
+static void
+dump_var(FILE *f, bool_node *var)
+{
+ switch(var->tag) {
+ case N_str: (void)fprintf(f, "\"%s\"", var->data.str_val); break;
+ case N_pat: (void)fprintf(f, "\"%s\"", var->data.str_val); break;
+ case N_number: (void)fprintf(f, "%f", var->data.num_val); break;
+ case N_uid: (void)fprintf(f, "uid"); break;
+ case N_gid: (void)fprintf(f, "gid"); break;
+ case N_uname: (void)fprintf(f, "uname"); break;
+ case N_gname: (void)fprintf(f, "gname"); break;
+ case N_fname: (void)fprintf(f, "fname"); break;
+ case N_psargs: (void)fprintf(f, "psargs"); break;
+ case N_cpuburn: (void)fprintf(f, "cpuburn"); break;
+ case N_syscalls: (void)fprintf(f, "syscalls"); break;
+ case N_ctxswitch: (void)fprintf(f, "ctxswitch"); break;
+ case N_virtualsize: (void)fprintf(f, "virtualsize"); break;
+ case N_residentsize: (void)fprintf(f, "residentsize"); break;
+ case N_iodemand: (void)fprintf(f, "iodemand"); break;
+ case N_iowait: (void)fprintf(f, "iowait"); break;
+ case N_schedwait: (void)fprintf(f, "schedwait"); break;
+ default: (void)fprintf(f, "<ERROR>"); break;
+ }/*switch*/
+}
diff --git a/src/pmdas/hotproc/src/gram_node.h b/src/pmdas/hotproc/src/gram_node.h
new file mode 100644
index 0000000..996208b
--- /dev/null
+++ b/src/pmdas/hotproc/src/gram_node.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef GRAM_NODE_H
+#define GRAM_NODE_H
+
+/* --- types --- */
+typedef enum
+{
+ N_and, N_or, N_not,
+ N_lt, N_le, N_gt, N_ge,
+ N_eq, N_neq, N_seq, N_sneq,
+ N_match, N_nmatch,
+ N_str, N_pat, N_number,
+ N_uid, N_gid, N_uname, N_gname,
+ N_fname, N_psargs, N_cpuburn,
+ N_true, N_false,
+ N_syscalls, N_ctxswitch,
+ N_virtualsize, N_residentsize,
+ N_iodemand, N_iowait, N_schedwait
+} N_tag;
+
+typedef struct
+{
+ struct bool_node *left;
+ struct bool_node *right;
+} bool_children;
+
+typedef struct bool_node
+{
+ N_tag tag;
+ struct bool_node *next;
+ union {
+ bool_children children;
+ char *str_val;
+ double num_val;
+ }
+ data;
+} bool_node;
+
+/* --- functions --- */
+
+void free_tree(bool_node *);
+void start_tree(void);
+
+bool_node *create_tnode(N_tag, bool_node *, bool_node *);
+bool_node *create_tag_node(N_tag);
+bool_node *create_number_node(double);
+bool_node *create_str_node(char *);
+bool_node *create_pat_node(char *);
+void dump_bool_tree(FILE *, bool_node *);
+void dump_predicate(FILE *, bool_node *);
+
+#endif
diff --git a/src/pmdas/hotproc/src/hotproc.c b/src/pmdas/hotproc/src/hotproc.c
new file mode 100644
index 0000000..50abf0d
--- /dev/null
+++ b/src/pmdas/hotproc/src/hotproc.c
@@ -0,0 +1,1555 @@
+/*
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/sysmp.h>
+#include <sys/sysinfo.h>
+#include <sys/procfs.h>
+#include <sys/immu.h>
+#include <sys/sysmacros.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+/* local stuff */
+#include "./cluster.h"
+#include "../domain.h"
+#include "./proc.h"
+#include "./proc_aux.h"
+#include "./psinfo.h"
+#include "./psusage.h"
+#include "./pglobal.h"
+#include "./pcpu.h"
+#include "./ctltab.h"
+#include "./config.h"
+#include "./hotproc.h"
+#include "./pracinfo.h"
+#include "./ppred_values.h"
+
+#define MIN_REFRESH 1
+#define INIT_PROC_MAX 200
+
+#if (_MIPS_SZLONG == 32)
+#define IO_PMTYPE PM_TYPE_U32 /* ulong_t from procfs.h/prusage */
+#define CTX_PMTYPE PM_TYPE_U32 /* ulong_t from procfs.h/prusage */
+#define SYSCALLS_PMTYPE PM_TYPE_U32 /* ulong_t from procfs.h/prusage */
+#define SYSIDLE_PMTYPE PM_TYPE_32 /* long=time_t from sysinfo.h/sysinfo */
+#define TIME_PMTYPE PM_TYPE_32 /* long from time.h or types.h */
+#define ACCUM_PMTYPE PM_TYPE_U64 /* accum_t from types.h */
+#endif /* _MIPS_SZLONG == 32 */
+
+#if (_MIPS_SZLONG == 64)
+#define IO_PMTYPE PM_TYPE_U64 /* ulong_t from procfs.h/prusage */
+#define CTX_PMTYPE PM_TYPE_U64 /* ulong_t from procfs.h/prusage */
+#define SYSCALLS_PMTYPE PM_TYPE_U64 /* ulong_t from procfs.h/prusage */
+#define SYSIDLE_PMTYPE PM_TYPE_64 /* long=time_t from sysinfo.h/sysinfo */
+#define TIME_PMTYPE PM_TYPE_64 /* long from time.h or types.h */
+#define ACCUM_PMTYPE PM_TYPE_U64 /* accum_t from types.h */
+#endif /* _MIPS_SZLONG == 64 */
+
+extern char *conf_buffer;
+extern char *conf_buffer_ptr;
+extern char *pred_buffer;
+char *configfile = NULL;
+static int conf_gen = 1; /* configuration file generation number */
+
+static int allow_stores = 1; /* allow stores or not */
+
+static int hotproc_domain = 7; /* set in hotproc_init */
+static int pred_testing; /* just do predicate testing or not */
+static int parse_only; /* just parse config file and exit */
+static char* testing_fname; /* filename root for testing */
+static char *username; /* user account for pmda */
+
+/* handle on /proc */
+static DIR *procdir;
+
+/* active list */
+static pid_t *active_list = NULL; /* generated per refresh */
+static int numactive = 0;
+static int maxactive = INIT_PROC_MAX;
+
+/* process lists for current and previous */
+static process_t *proc_list[2] = {NULL, NULL};
+
+/* array size allocated */
+static int maxprocs[2] = {INIT_PROC_MAX, INIT_PROC_MAX};
+
+/* number of procs used in list (<= maxprocs) */
+static int numprocs[2] = {0, 0};
+
+/* index into proc_list etc.. */
+static int current = 0;
+static int previous = 1;
+
+/* refresh time interval in seconds - cmd arg */
+static struct timeval refresh_delta;
+
+/* event id for refreshing */
+static int refresh_afid;
+
+/* various cpu time totals */
+static int num_cpus = 0;
+static int have_totals = 0;
+static double transient;
+static double cpuidle;
+static double total_active;
+static double total_inactive;
+
+/* number of refreshes */
+/* will wrap back to 2 */
+static unsigned long refresh_count = 0;
+
+/* format of an entry in /proc */
+char proc_fmt[8]; /* export for procfs fname conversions */
+int proc_entry_len;
+
+char *log = NULL;
+
+#ifndef USEC_PER_SEC
+#define USEC_PER_SEC 1000000 /* number of usecs for 1 second */
+#endif
+#ifndef NSEC_PER_SEC
+#define NSEC_PER_SEC 1000000000 /* number of nsecs for 1 second */
+#endif
+
+#define ntime2double(x) \
+ (((double)(x).tv_sec) + ((double)((ulong_t)((x).tv_nsec)))/NSEC_PER_SEC)
+
+#define utime2double(x) \
+ (((double)(x).tv_sec) + ((double)((ulong_t)((x).tv_usec)))/USEC_PER_SEC)
+
+#define TWOe32 (double) 4.295e9
+
+
+/*
+ * Make initial allocation for proc list.
+ */
+
+static int
+init_proc_list(void)
+{
+ active_list = (pid_t*)malloc(INIT_PROC_MAX * sizeof(pid_t));
+ proc_list[0] = (process_t*)malloc(INIT_PROC_MAX * sizeof(process_t));
+ proc_list[1] = (process_t*)malloc(INIT_PROC_MAX * sizeof(process_t));
+ if (proc_list[0] == NULL || proc_list[1] == NULL || active_list == NULL)
+ return -oserror();
+ return 0;
+}
+
+/*
+ * Work out the active list from the constraints.
+ */
+
+
+static void
+init_active_list(void)
+{
+ numactive = 0;
+}
+
+/*
+ * add_active_list:
+ * If unsuccessful in add - due to memory then return neg status.
+ * If member of active list return 1
+ * If non-member of active list return 0
+ */
+
+static int
+add_active_list(process_t *node, config_vars *vars)
+{
+ if (eval_tree(vars) == 0) {
+ return 0;
+ }
+
+ if (numactive == maxactive) {
+ pid_t *res;
+ maxactive = numactive*2;
+ res = (pid_t *)realloc(active_list, maxactive * sizeof(pid_t));
+ if (res == NULL)
+ return -osoerror();
+ active_list = res;
+ }
+ active_list[numactive++] = node->pid;
+ return 1;
+}
+
+#ifdef PCP_DEBUG
+
+static void
+dump_active_list(void)
+{
+ int i = 0;
+
+ (void)fprintf(stderr, "--- active list ---\n");
+ for(i = 0; i < numactive; i++) {
+ (void)fprintf(stderr, "[%d] = %" FMT_PID "\n", i, active_list[i]);
+ }/*for*/
+ (void)fprintf(stderr, "--- end active list ---\n");
+}
+
+
+static void
+dump_proc_list(void)
+{
+ int i;
+ process_t *node;
+
+ (void)fprintf(stderr, "--- proc list ---\n");
+ for (i = 0; i < numprocs[current]; i++) {
+ node = &proc_list[current][i];
+ (void)fprintf(stderr, "[%d] = %" FMT_PID " ", i, node->pid);
+ (void)fprintf(stderr, "(syscalls = %ld) ", node->r_syscalls);
+ (void)fputc('\n', stderr);
+ }/*for*/
+ (void)fprintf(stderr, "--- end proc list ---\n");
+}
+
+static void
+dump_cputime(double pre_usr, double pre_sys, double post_usr, double post_sys)
+{
+ fprintf(stderr, "CPU Time: user = %f, sys = %f\n",
+ post_usr - pre_usr, post_sys - pre_sys);
+}
+
+static void
+dump_pred(derived_pred_t *pred)
+{
+ (void)fprintf(stderr, "--- pred vars ---\n");
+ (void)fprintf(stderr, "syscalls = %f\n", pred->syscalls);
+ (void)fprintf(stderr, "ctxswitch = %f\n", pred->ctxswitch);
+ (void)fprintf(stderr, "virtualsize = %f\n", pred->virtualsize);
+ (void)fprintf(stderr, "residentsize = %f\n", pred->residentsize);
+ (void)fprintf(stderr, "iodemand = %f\n", pred->iodemand);
+ (void)fprintf(stderr, "iowait = %f\n", pred->iowait);
+ (void)fprintf(stderr, "schedwait = %f\n", pred->schedwait);
+ (void)fprintf(stderr, "--- end pred vars ---\n");
+}
+
+#endif
+
+/*
+ * Return 1 if pid is in active list.
+ * Return 0 if pid is NOT in active list.
+ */
+
+static int
+in_active_list(pid_t pid)
+{
+ int i;
+
+ for(i = 0; i < numactive; i++) {
+ if (pid == active_list[i])
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+compar_pids(const void *n1, const void *n2)
+{
+ return ((process_t*)n2)->pid - ((process_t*)n1)->pid;
+}
+
+
+static void
+set_proc_fmt(void)
+{
+ struct dirent *directp; /* go thru /proc directory */
+
+ for (rewinddir(procdir); directp=readdir(procdir);) {
+ if (!isdigit((int)directp->d_name[0]))
+ continue;
+ proc_entry_len = (int)strlen(directp->d_name);
+ (void)sprintf(proc_fmt, "%%0%dd", proc_entry_len);
+ break;
+ }
+}
+
+/*
+ * look up node in proc list
+ */
+static process_t *
+lookup_node(int curr_prev, pid_t pid)
+{
+ process_t key;
+ process_t *node;
+
+ key.pid = pid;
+
+ if ( (numprocs[curr_prev] > 0) &&
+ ((node = bsearch(&key, proc_list[curr_prev], numprocs[curr_prev],
+ sizeof(process_t), compar_pids)) != NULL) ) {
+ return node;
+ }
+ return NULL;
+}
+
+/*
+ * look up current node
+ */
+process_t *
+lookup_curr_node(pid_t pid)
+{
+ return lookup_node(current, pid);
+}
+
+/*
+ * Calculate difference allowing for single wrapping of counters
+ */
+
+static double
+DiffCounter(double current, double previous, int pmtype)
+{
+ double outval = current-previous;
+
+ if (outval < 0.0) {
+ switch (pmtype) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ outval += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ outval += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+
+ return outval;
+}
+
+static void
+set_psinfo(prpsinfo_t *psinfo, config_vars *vars)
+{
+ psinfo->pr_size = (long)vars->pr_size;
+ psinfo->pr_rssize = (long)vars->pr_rssize;
+}
+
+static void
+set_psusage(prusage_t *psusage, config_vars *vars)
+{
+ psusage->pu_sysc = vars->pu_sysc;
+ psusage->pu_vctx = vars->pu_vctx;
+ psusage->pu_ictx = vars->pu_ictx;
+ psusage->pu_gbread = vars->pu_gbread;
+ psusage->pu_bread = vars->pu_bread;
+ psusage->pu_gbwrit = vars->pu_gbwrit;
+ psusage->pu_bwrit = vars->pu_bwrit;
+}
+
+/* If have a test file then read in values
+ * and overwrite some variables in the proc buffer.
+ * Only read from file for each refresh =>
+ * make each process have same line from file as values
+ * If read fails, then return -1
+ * else if read something then return 0
+ * else if not time to read then return 1
+ */
+
+static int
+read_buf_file(int *rcount, char *suffix, FILE **testing_file, config_vars *vars)
+{
+ if (*testing_file == NULL) {
+ char fname[80];
+ strcpy(fname, testing_fname);
+ strcat(fname, suffix);
+ *testing_file = fopen(fname, "r");
+ if (*testing_file == NULL) {
+ fprintf(stderr, "%s: Unable to open test file \"%s\": %s\n",
+ pmProgname, fname, osstrerror());
+ return -1;
+ }
+ }
+ /* only read new values for each refresh */
+ if (refresh_count != *rcount) {
+ *rcount = (int)refresh_count;
+ if (read_test_values(*testing_file, vars) != 1)
+ return -1;
+ return 0;
+ }
+
+ return 1; /* no values actually read */
+}
+
+static int
+psusage_getbuf_file(pid_t pid, prusage_t *psusage)
+{
+ static config_vars vars; /* configuration variable values */
+ static FILE *testing_file = NULL;
+ static int rcount = -1; /* last refresh_count */
+ static int dont_read = 0;
+ int sts;
+
+ if ((sts = psusage_getbuf(pid, psusage)) != 0)
+ return sts;
+
+ if (testing_fname != NULL && !dont_read) {
+ sts = read_buf_file(&rcount, ".psusage", &testing_file, &vars);
+ if (sts >= 0)
+ set_psusage(psusage, &vars);
+ else
+ dont_read = 1;
+ }
+ return 0;
+}
+
+static int
+psinfo_getbuf_file(pid_t pid, prpsinfo_t *psinfo)
+{
+ static config_vars vars; /* configuration variable values */
+ static FILE *testing_file = NULL;
+ static int rcount = -1; /* last refresh_count */
+ static int dont_read = 0;
+ int sts;
+
+ if ((sts = psinfo_getbuf(pid, psinfo)) != 0)
+ return sts;
+
+ if (testing_fname != NULL && !dont_read) {
+ sts = read_buf_file(&rcount, ".psinfo", &testing_file, &vars);
+ if (sts >= 0)
+ set_psinfo(psinfo, &vars);
+ else
+ dont_read = 1;
+ }
+ return 0;
+}
+
+static void
+set_pracinfo(pracinfo_t *acct, config_vars *vars)
+{
+ acct->pr_timers.ac_bwtime = vars->ac_bwtime;
+ acct->pr_timers.ac_rwtime = vars->ac_rwtime;
+ acct->pr_timers.ac_qwtime = vars->ac_qwtime;
+}
+
+static int
+pracinfo_getbuf_file(pid_t pid, pracinfo_t *acct)
+{
+ static config_vars vars; /* configuration variable values */
+ static FILE *testing_file = NULL;
+ static int rcount = -1; /* last refresh_count */
+ static int dont_read = 0;
+ int sts;
+
+ if ((sts = pracinfo_getbuf(pid, acct)) != 0)
+ return sts;
+
+ if (testing_fname != NULL && !dont_read) {
+ sts = read_buf_file(&rcount, ".pracinfo", &testing_file, &vars);
+ if (sts >= 0)
+ set_pracinfo(acct, &vars);
+ else
+ dont_read = 1;
+ }
+ return 0;
+}
+
+/*
+ * Refresh the process list for /proc entries.
+ */
+
+static int
+refresh_proc_list(void)
+{
+ extern int need_psusage; /* is psusage buffer needed or not */
+ extern int need_accounting; /* is pracinfo buffer needed or not */
+
+ int sts;
+ int sysmp_sts;
+ pid_t pid;
+ struct dirent *directp; /* go thru /proc directory */
+ prpsinfo_t psinfo; /* read in proc info */
+ prusage_t psusage; /* read in proc usage */
+ pracinfo_t acct; /* read in acct info */
+ struct sysinfo sinfo; /* sysinfo from sysmp */
+ process_t *oldnode = NULL; /* node from previous proc list */
+ process_t *newnode = NULL; /* new node in current proc list */
+ int np = 0; /* number of procs index */
+ struct timeval p_timestamp; /* timestamp pre getting proc info */
+ config_vars vars; /* configuration variable values */
+
+ struct timeval ts;
+ static double refresh_time[2]; /* timestamp after refresh */
+ static time_t sysidle[2]; /* sys idle from sysmp */
+ double sysidle_delta; /* system idle delta time since last refresh */
+ double actual_delta; /* actual delta time since last refresh */
+ double transient_delta; /* calculated delta time of transient procs */
+ double cputime_delta; /* delta cpu time for a process */
+ double syscalls_delta; /* delta num of syscalls for a process */
+ double vctx_delta; /* delta num of valid ctx switches for a process */
+ double ictx_delta; /* delta num of invalid ctx switches for a process */
+ double bread_delta; /* delta num of bytes read */
+ double gbread_delta; /* delta num of gigabytes read */
+ double bwrit_delta; /* delta num of bytes written */
+ double gbwrit_delta; /* delta num of gigabytes written */
+ double bwtime_delta; /* delta num of nanosesc for waiting for blocked io */
+ double rwtime_delta; /* delta num of nanosesc for waiting for raw io */
+ double qwtime_delta; /* delta num of nanosesc waiting on run queue */
+ double timestamp_delta; /* real time delta b/w refreshes for process */
+ double total_cputime = 0; /* total of cputime_deltas for each process */
+ double total_activetime = 0; /* total of cputime_deltas for active processes */
+ double total_inactivetime = 0; /* total of cputime_deltas for inactive processes */
+
+#ifdef PCP_DEBUG
+ double curr_time; /* for current time */
+ double pre_usr, pre_sys;
+ double post_usr, post_sys;
+#endif
+
+ /* switch current and previous */
+ if (current == 0) {
+ current = 1; previous = 0;
+ }
+ else {
+ current = 0; previous = 1;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmtimevalNow(&ts);
+ curr_time = utime2double(ts);
+ fprintf(stderr, "refresh_proc_list():\n");
+ __pmProcessRunTimes(&pre_usr, &pre_sys);
+ }
+#endif
+
+#ifdef PCP_DEBUG
+ /* used to verify externally (approx.) the values */
+ /* note: doing this is _slow_ */
+ if (pmDebug & DBG_TRACE_APPL2) {
+ static char cmd[80];
+ sprintf(cmd, "(date ; ps -le )>> %s.refresh", log);
+ fprintf(stderr, "refresh: cmd = %s\n", cmd);
+ system(cmd);
+ }
+#endif
+
+ init_active_list();
+
+ (void)memset(&vars, 0, sizeof(config_vars));
+
+ for (np = 0, rewinddir(procdir); directp=readdir(procdir);) {
+ if (!isdigit((int)directp->d_name[0]))
+ continue;
+ (void)sscanf(directp->d_name, proc_fmt, &pid);
+
+ __pmtimevalNow(&p_timestamp);
+
+ if (psinfo_getbuf_file(pid, &psinfo) != 0)
+ continue;
+
+ /* reallocate if run out of room */
+ if (np == maxprocs[current]) {
+ process_t *res;
+ maxprocs[current] = np*2;
+ res = (process_t *)realloc(proc_list[current],
+ maxprocs[current] * sizeof(process_t));
+ if (res == NULL)
+ return -oserror();
+ proc_list[current] = res;
+ }
+
+ newnode = &proc_list[current][np++];
+ newnode->pid = pid;
+ newnode->r_cputime = ntime2double(psinfo.pr_time);
+ newnode->r_cputimestamp = utime2double(p_timestamp);
+
+ if (need_psusage) {
+ if (psusage_getbuf_file(pid, &psusage) != 0)
+ continue;
+ newnode->r_syscalls = psusage.pu_sysc;
+ newnode->r_vctx = psusage.pu_vctx;
+ newnode->r_ictx = psusage.pu_ictx;
+ newnode->r_bread = psusage.pu_bread;
+ newnode->r_gbread = psusage.pu_gbread;
+ newnode->r_bwrit = psusage.pu_bwrit;
+ newnode->r_gbwrit = psusage.pu_gbwrit;
+ }
+
+ if (need_accounting) {
+ if (pracinfo_getbuf_file(pid, &acct) != 0)
+ continue;
+ newnode->r_bwtime = acct.pr_timers.ac_bwtime;
+ newnode->r_rwtime = acct.pr_timers.ac_rwtime;
+ newnode->r_qwtime = acct.pr_timers.ac_qwtime;
+ }
+
+ /* if we have a previous i.e. not the first time */
+ if ((oldnode = lookup_node(previous, pid)) != NULL) {
+
+
+ cputime_delta = DiffCounter(newnode->r_cputime, oldnode->r_cputime, TIME_PMTYPE);
+ timestamp_delta = DiffCounter(newnode->r_cputimestamp, oldnode->r_cputimestamp, TIME_PMTYPE);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "fname = \"%s\"; cputime = %f; elapsed = %f\n",
+ psinfo.pr_fname, cputime_delta, timestamp_delta);
+ }
+#endif
+#ifdef PCP_DEBUG
+ /* used to verify externally (approx.) the values */
+ /* note: doing this is _slow_ */
+ if (pmDebug & DBG_TRACE_APPL2) {
+ static char cmd[80];
+ if (cputime_delta > timestamp_delta) {
+ sprintf(cmd, "(date ; ps -lp %" FMT_PID " )>> %s.refresh", pid, log);
+ fprintf(stderr, "refresh: cmd = %s\n", cmd);
+ system(cmd);
+ }
+ }
+#endif
+ newnode->r_cpuburn = cputime_delta / timestamp_delta;
+ vars.cpuburn = newnode->r_cpuburn;
+
+ if (need_psusage) {
+
+ /* rate convert syscalls */
+ syscalls_delta = DiffCounter((double)newnode->r_syscalls,
+ (double)oldnode->r_syscalls, SYSCALLS_PMTYPE);
+ vars.preds.syscalls = syscalls_delta / timestamp_delta;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "syscalls = %f\n", vars.preds.syscalls);
+ }
+#endif
+
+ /* rate convert ctxswitch */
+ vctx_delta = DiffCounter((double)newnode->r_vctx,
+ (double)oldnode->r_vctx, CTX_PMTYPE);
+ ictx_delta = DiffCounter((double)newnode->r_ictx,
+ (double)oldnode->r_ictx, CTX_PMTYPE);
+ vars.preds.ctxswitch = (vctx_delta + ictx_delta) / timestamp_delta;
+
+ /* rate convert reading/writing (iodemand) */
+ bread_delta = DiffCounter((double)newnode->r_bread,
+ (double)oldnode->r_bread, IO_PMTYPE);
+ gbread_delta = DiffCounter((double)newnode->r_gbread,
+ (double)oldnode->r_gbread, IO_PMTYPE);
+ bwrit_delta = DiffCounter((double)newnode->r_bwrit,
+ (double)oldnode->r_bwrit, IO_PMTYPE);
+ gbwrit_delta = DiffCounter((double)newnode->r_gbwrit,
+ (double)oldnode->r_gbwrit, IO_PMTYPE);
+ vars.preds.iodemand = (gbread_delta * 1024 * 1024 +
+ (double)bread_delta / 1024 +
+ gbwrit_delta * 1024 * 1024 +
+ (double)bwrit_delta / 1024) /
+ timestamp_delta;
+
+ }
+
+ if (need_accounting) {
+
+ /* rate convert iowait */
+ bwtime_delta = DiffCounter((double)newnode->r_bwtime,
+ (double)oldnode->r_bwtime, ACCUM_PMTYPE);
+ bwtime_delta /= 1000000000.0; /* nanosecs -> secs */
+ rwtime_delta = DiffCounter((double)newnode->r_rwtime,
+ (double)oldnode->r_rwtime, ACCUM_PMTYPE);
+ rwtime_delta /= 1000000000.0; /* nanosecs -> secs */
+ vars.preds.iowait = (bwtime_delta + rwtime_delta) / timestamp_delta;
+
+
+ /* rate convert schedwait */
+ qwtime_delta = DiffCounter((double)newnode->r_qwtime,
+ (double)oldnode->r_qwtime, ACCUM_PMTYPE);
+ qwtime_delta /= 1000000000.0; /* nanosecs -> secs */
+ vars.preds.schedwait = qwtime_delta / timestamp_delta;
+
+ }
+
+ newnode->preds = vars.preds; /* struct copy */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ dump_pred(&newnode->preds);
+ }
+#endif
+
+ }
+ else {
+ newnode->r_cpuburn = 0;
+ bzero(&newnode->preds, sizeof(newnode->preds));
+ vars.cpuburn = 0;
+ vars.preds.syscalls = 0;
+ vars.preds.ctxswitch = 0;
+ vars.preds.iowait = 0;
+ vars.preds.schedwait = 0;
+ vars.preds.iodemand = 0;
+ cputime_delta = 0;
+ }
+
+ total_cputime += cputime_delta;
+
+ /* fix up vars record from psinfo */
+ (void)strcpy(vars.fname, psinfo.pr_fname);
+ (void)strcpy(vars.psargs, psinfo.pr_psargs);
+ vars.uid = psinfo.pr_uid;
+ vars.gid = psinfo.pr_gid;
+ vars.uname = NULL;
+ vars.gname = NULL;
+ vars.preds.virtualsize = (double)psinfo.pr_size * (_pagesize / 1024);
+ vars.preds.residentsize = (double)psinfo.pr_rssize * (_pagesize / 1024);
+
+ if ((sts = add_active_list(newnode, &vars)) < 0) {
+ return sts;
+ }
+
+ if (sts == 0)
+ total_inactivetime += cputime_delta;
+ else
+ total_activetime += cputime_delta;
+
+ }/*for each pid*/
+
+ numprocs[current] = np;
+
+ __pmtimevalNow(&ts);
+ refresh_time[current] = utime2double(ts);
+
+ refresh_count++;
+ /* If we wrap then say that we atleast have 2,
+ * inorder to indicate we have seen two successive refreshes.
+ */
+ if (refresh_count == 0)
+ refresh_count = 2;
+
+ bzero(&sinfo, sizeof(sinfo)); /* for purify */
+ if ((sysmp_sts = (int)sysmp(MP_SAGET, MPSA_SINFO, &sinfo, sizeof(struct sysinfo))) < 0) {
+ __pmNotifyErr(LOG_ERR, "sysmp failed in refresh: %s\n", osstrerror());
+ sysidle[current] = -1;
+ }
+ else {
+ sysidle[current] = sinfo.cpu[CPU_IDLE];
+ }
+
+
+ have_totals = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ (void)fprintf(stderr, "refresh_proc_list: refresh_count = %lu\n",
+ refresh_count);
+ }
+#endif
+
+ if (refresh_count > 1 && sysmp_sts != -1 && sysidle[previous] != -1) {
+ sysidle_delta = DiffCounter(sysidle[current], sysidle[previous], SYSIDLE_PMTYPE) / (double)HZ;
+ actual_delta = DiffCounter(refresh_time[current], refresh_time[previous], TIME_PMTYPE);
+ transient_delta = num_cpus * actual_delta - (total_cputime + sysidle_delta);
+ if (transient_delta < 0) /* sysidle is only accurate to 0.01 second */
+ transient_delta = 0;
+
+ have_totals = 1;
+ transient = transient_delta / actual_delta;
+ cpuidle = sysidle_delta / actual_delta;
+ total_active = total_activetime / actual_delta;
+ total_inactive = total_inactivetime / actual_delta;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "total_cputime = %f\n", total_cputime);
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "total_activetime = %f, total_inactivetime = %f\n",
+ total_activetime, total_inactivetime);
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "sysidle_delta = %f, actual_delta = %f\n",
+ sysidle_delta, actual_delta);
+ (void)fprintf(stderr, "refresh_proc_list: "
+ "transient_delta = %f, transient = %f\n",
+ transient_delta, transient);
+ }
+#endif
+ }
+
+
+ /* sort it for bsearching later */
+ qsort(proc_list[current], numprocs[current],
+ sizeof(process_t), compar_pids);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmProcessRunTimes(&post_usr, &post_sys);
+ dump_proc_list();
+ fprintf(stderr, "refresh_proc_list: duration = %f secs; ",
+ refresh_time[current] - curr_time);
+ dump_cputime(pre_usr, pre_sys, post_usr, post_sys);
+ dump_active_list();
+ }
+#endif
+
+ return 0;
+}/*refresh_proc_list*/
+
+
+static int
+restart_proc_list(void)
+{
+ int sts;
+
+ refresh_count = 0;
+ numprocs[current] = 0; /* clear the current list */
+ sts = refresh_proc_list();
+
+ return sts;
+}
+
+static void
+timer_callback(int afid, void *data)
+{
+ int sts = refresh_proc_list();
+
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "timer_callback: refresh list failed: %s\n", pmErrStr(sts));
+ }
+}
+
+
+static int
+hotproc_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ __pmInResult *res;
+ int j;
+ int sts;
+
+
+ if (indom != proc_indom)
+ return PM_ERR_INDOM;
+
+ if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL)
+ return -oserror();
+ res->indom = indom;
+ res->instlist = NULL;
+ res->namelist = NULL;
+ sts = 0;
+
+ if (name == NULL && inst == PM_IN_NULL)
+ res->numinst = numactive;
+ else
+ res->numinst = 1;
+
+ if (inst == PM_IN_NULL) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+
+ if (name == NULL) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (j = 0; j < res->numinst; j++)
+ res->namelist[j] = NULL;
+ }
+
+ /* --> names and ids (the whole instance map) */
+ if (name == NULL && inst == PM_IN_NULL) {
+ /* find all instance ids and names for the PROC indom */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ (void)fprintf(stderr, "hotproc_instance: Getting whole instance map...\n");
+ }
+#endif
+
+ /* go thru active list */
+ for(j = 0; j < numactive; j++) {
+ res->instlist[j] = active_list[j];
+ if ((sts = proc_id_to_mapname(active_list[j], &res->namelist[j])) < 0)
+ break;
+ }
+ }
+ /* id --> name */
+ else if (name == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ (void)fprintf(stderr, "hotproc_instance: id --> name\n");
+ }
+#endif
+ if (!in_active_list(inst)) {
+ __pmNotifyErr(LOG_ERR, "proc_instance: pid not in active list %d\n", inst);
+ sts = PM_ERR_INST;
+ }
+ else {
+ sts = proc_id_to_name(inst, &res->namelist[0]);
+ }
+ }
+ /* name --> id */
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ (void)fprintf(stderr, "hotproc_instance: name --> id\n");
+ }
+#endif
+ /* find the instance for the given indom/name */
+ sts = proc_name_to_id(name, &res->instlist[0]);
+ if (!in_active_list(res->instlist[0])) {
+ __pmNotifyErr(LOG_ERR, "proc_instance: pid not in active list %d\n",
+ res->instlist[0]);
+ sts = PM_ERR_INST;
+ }
+ }
+
+ if (sts == 0)
+ *result = res;
+ else
+ __pmFreeInResult(res);
+
+ return sts;
+}
+
+static int
+hotproc_desc(pmID pmid, pmDesc *desc, pmdaExt *pmda)
+{
+ int pmidErr;
+ int ctl_i;
+ __pmID_int *pmidp = (__pmID_int *)&pmid;
+
+ pmidErr = PM_ERR_PMID;
+ if (pmidp->domain == hotproc_domain) {
+ ctl_i = lookup_ctltab(pmidp->cluster);
+ if (ctl_i < 0)
+ pmidErr = ctl_i;
+ else
+ pmidErr = ctltab[ctl_i].getdesc(pmid, desc);
+ }
+
+ return pmidErr;
+
+}
+
+
+static int
+hotproc_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i; /* over pmidlist[] */
+ int ctl_i; /* over ctltab[] and fetched[] */
+ int j;
+ int n;
+ int numvals;
+ int sts;
+ size_t need;
+ static pmResult *res = NULL;
+ static int maxnpmids = 0;
+ pmValueSet *vset;
+ pmDesc dp;
+ __pmID_int *pmidp;
+ pmAtomValue atom;
+ int aggregate_len;
+ static int max_numactive = 0;
+ static int **fetched = NULL;
+
+ if (fetched == NULL) {
+ fetched = (int **)malloc(nctltab * sizeof(fetched[0]));
+ if (fetched == NULL)
+ return -oserror();
+ memset(fetched, 0, nctltab * sizeof(fetched[0]));
+ }
+
+ /* allocate for result structure */
+ if (numpmid > maxnpmids) {
+ if (res != NULL)
+ free(res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *);
+ if ((res = (pmResult *) malloc(need)) == NULL)
+ return -oserror();
+ maxnpmids = numpmid;
+ }
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+
+ /* fix up allocations for fetched array */
+ for (ctl_i=1; ctl_i < nctltab; ctl_i++) {
+ if (numactive > max_numactive) {
+ int *f = (int*)realloc(fetched[ctl_i], numactive * sizeof(int));
+ int ctl_j; /* over ctltab[] and fetched[] */
+ if (f == NULL) {
+ max_numactive = 0;
+ for (ctl_j=1; ctl_j < nctltab; ctl_j++) {
+ if (fetched[ctl_j])
+ free(fetched[ctl_j]);
+ fetched[ctl_j] = NULL;
+ }
+ return -oserror();
+ }
+ fetched[ctl_i] = f;
+ }
+ (void)memset(fetched[ctl_i], 0, numactive * sizeof(int));
+ if ((sts = ctltab[ctl_i].allocbuf(numactive)) < 0)
+ return sts;
+ }/*for*/
+ if (numactive > max_numactive) {
+ max_numactive = numactive;
+ }
+
+ sts = 0;
+ for (i = 0; i < numpmid; i++) {
+ int pmidErr = 0;
+
+ pmidp = (__pmID_int *)&pmidlist[i];
+ pmidErr = hotproc_desc(pmidlist[i], &dp, pmda);
+
+ /* create a vset with the error code in it */
+ if (pmidErr < 0) {
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (vset == NULL) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = pmidErr;
+ continue;
+ }
+
+ if (pmidp->cluster == CLUSTER_GLOBAL) {
+
+ /* global metrics, singular instance domain */
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (vset == NULL) {
+ if (i > 0) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = 1;
+
+ switch (pmidp->item) {
+ case ITEM_NPROCS:
+ {
+ char *path;
+
+ /* pv#589180
+ * Reduce the active list if processes have exitted.
+ */
+ for (j=0; j < numactive; j++) {
+ pid_t pid = active_list[j];
+ proc_pid_to_path(pid, NULL, &path, PINFO_PATH);
+ /* if process not found then remove from active list */
+ if (access(path, R_OK) < 0) {
+ int ctl_k;
+ /* remove from active list */
+ /* replace with end one */
+ active_list[j] = active_list[numactive-1];
+ /* also do same thing to fetched array */
+ for(ctl_k = 1; ctl_k < nctltab; ctl_k++) {
+ fetched[ctl_k][j] = fetched[ctl_k][numactive-1];
+ }
+ numactive--;
+ j--; /* test this slot again */
+ }
+ }
+
+ atom.l = numactive;
+ break;
+ }
+ case ITEM_REFRESH:
+ atom.l = (__int32_t)refresh_delta.tv_sec;
+ break;
+ case ITEM_CONFIG:
+ atom.cp = pred_buffer;
+ break;
+ case ITEM_CONFIG_GEN:
+ atom.l = conf_gen;
+ break;
+ case ITEM_TRANSIENT:
+ if (!have_totals)
+ vset->numval = 0;
+ else
+ atom.f = transient;
+ break;
+ case ITEM_CPUIDLE:
+ if (!have_totals)
+ vset->numval = 0;
+ else
+ atom.f = cpuidle;
+ break;
+ case ITEM_TOTAL_CPUBURN:
+ if (!have_totals)
+ vset->numval = 0;
+ else
+ atom.f = total_active;
+ break;
+ case ITEM_TOTAL_NOTCPUBURN:
+ if (!have_totals)
+ vset->numval = 0;
+ else
+ atom.f = total_inactive;
+ break;
+ case ITEM_OTHER_TOTAL:
+ if (!have_totals)
+ vset->numval = 0;
+ else
+ atom.f = transient + total_inactive;
+ break;
+ case ITEM_OTHER_PERCENT:
+ {
+ double other = transient + total_inactive;
+ double non_idle = other + total_active;
+
+ /* if non_idle = 0, very unlikely,
+ * then the value here is meaningless
+ */
+ if (!have_totals || non_idle == 0)
+ vset->numval = 0;
+ else
+ atom.f = other / non_idle * 100;
+ }
+ break;
+ }
+
+ if (vset->numval == 1 ) {
+ sts = __pmStuffValue(&atom, 0, &vset->vlist[0], dp.type);
+ vset->valfmt = sts;
+ vset->vlist[0].inst = PM_IN_NULL;
+ }
+ }
+ else {
+ /*
+ * Multiple instance domain metrics.
+ */
+ if ((ctl_i = lookup_ctltab(pmidp->cluster)) < 0)
+ return ctl_i;
+ if (!ctltab[ctl_i].supported) {
+ numvals = 0;
+ }
+ else if (pmidp->cluster == CLUSTER_PRED && !ppred_available(pmidp->item)) {
+ numvals = 0;
+ }
+ else {
+ pid_t pid;
+
+ numvals = 0;
+ for (j = 0; j < numactive; j++) {
+ pid = active_list[j];
+ if (!__pmInProfile(proc_indom, pmda->e_prof, pid))
+ continue;
+ if (fetched[ctl_i][j] == 0) {
+ int sts;
+ if ((sts = ctltab[ctl_i].getinfo(pid, j)) == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "hotproc_fetch: getinfo succeeded: "
+ "pid=%" FMT_PID ", j=%d\n", pid, j);
+ }
+#endif
+ fetched[ctl_i][j] = 1;
+ numvals++;
+ }
+ else if (sts == -ENOENT) {
+ int ctl_k;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "hotproc_fetch: "
+ "getinfo failed: pid=%" FMT_PID ", j=%d\n", pid, j);
+ }
+#endif
+ /* remove from active list */
+ /* replace with end one */
+ active_list[j] = active_list[numactive-1];
+ /* also do same thing to fetched array */
+ for(ctl_k = 1; ctl_k < nctltab; ctl_k++) {
+ fetched[ctl_k][j] = fetched[ctl_k][numactive-1];
+ }
+ numactive--;
+ j--; /* test this slot again */
+ }
+ }
+ else {
+ numvals++;
+ }
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "hotproc_fetch: numvals = %d\n", numvals);
+ }
+#endif
+
+ res->vset[i] = vset = (pmValueSet *)malloc(
+ sizeof(pmValueSet) + (numvals-1) * sizeof(pmValue));
+ if (vset == NULL) {
+ if (i > 0) { /* not the first malloc failing */
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = numvals;
+ vset->valfmt = PM_VAL_INSITU;
+
+ if (!ctltab[ctl_i].supported) {
+ vset->numval = PM_ERR_APPVERSION;
+ }
+ else {
+ if (numvals != 0) {
+ pid_t pid;
+ for (n=j=0; j < numactive; j++) {
+ pid = active_list[j];
+ if (fetched[ctl_i][j] == 0)
+ continue;
+
+ aggregate_len = ctltab[ctl_i].setatom(pmidp->item, &atom, j);
+ sts = __pmStuffValue(&atom, aggregate_len, &vset->vlist[n], dp.type);
+ vset->valfmt = sts;
+ vset->vlist[n].inst = pid;
+ n++;
+ }/*for*/
+ }/*if*/
+ }/*if*/
+
+ }/*if*/
+ }/*for*/
+
+ *resp = res;
+ return 0;
+}/*hotproc_fetch*/
+
+static int
+restart_refresh(void)
+{
+ int sts;
+
+ /*
+ * Reschedule as if starting from init.
+ */
+ sts = restart_proc_list();
+ if (sts >= 0) {
+ /* Get rid of current event from queue, register new one */
+ __pmAFunregister(refresh_afid);
+ refresh_afid = __pmAFregister(&refresh_delta, NULL, timer_callback);
+ }
+
+ return sts;
+}
+
+static int
+hotproc_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ pmValueSet *vsp;
+ pmDesc desc;
+ __pmID_int *pmidp;
+ int sts = 0;
+ pmAtomValue av;
+
+ if (!allow_stores) {
+ return PM_ERR_PERMISSION;
+ }
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+ sts = hotproc_desc(vsp->pmid, &desc, pmda);
+ if (sts < 0)
+ break;
+ if (pmidp->cluster == CLUSTER_GLOBAL) {
+ switch (pmidp->item) {
+ case ITEM_REFRESH:
+ if (vsp->numval != 1 || vsp->valfmt != PM_VAL_INSITU) {
+ sts = PM_ERR_CONV;
+ }
+ break;
+ case ITEM_CONFIG:
+ if (vsp->numval != 1 || vsp->valfmt == PM_VAL_INSITU) {
+ sts = PM_ERR_CONV;
+ }
+ break;
+ default:
+ sts = PM_ERR_PERMISSION;
+ break;
+ }
+ }
+ else {
+ sts = PM_ERR_PERMISSION;
+ }
+
+ if (sts < 0)
+ break;
+
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ desc.type, &av, desc.type)) < 0)
+ break;
+
+ switch (pmidp->item) {
+ case ITEM_REFRESH:
+ if (av.l < MIN_REFRESH) {
+ sts = PM_ERR_CONV;
+ }
+ else {
+ refresh_delta.tv_sec = av.l;
+
+ if ((sts = restart_refresh()) < 0)
+ __pmNotifyErr(LOG_ERR, "hotproc_store: refresh list failed: %s\n",
+ pmErrStr(sts));
+ }
+ break;
+ case ITEM_CONFIG:
+ {
+ bool_node *tree;
+
+ conf_buffer_ptr = av.cp;
+
+ /* Only accept the new config if its predicate is parsed ok */
+ if (parse_config(&tree) != 0) {
+ conf_buffer_ptr = conf_buffer;
+ free(av.cp);
+ sts = PM_ERR_CONV;
+ }
+ else {
+ conf_gen++;
+ new_tree(tree);
+ free(conf_buffer); /* free old one */
+ conf_buffer = av.cp; /* use the new one */
+ if ((sts = restart_refresh()) < 0)
+ __pmNotifyErr(LOG_ERR, "hotproc_store: refresh list failed: %s\n",
+ pmErrStr(sts));
+ }
+ }
+ break;
+ }/*switch*/
+
+ }/*for*/
+
+ return sts;
+}
+
+
+
+/*
+ * Initialise the agent (only daemon and NOT DSO).
+ */
+
+static void
+hotproc_init(pmdaInterface *dp)
+{
+ int sts;
+
+ __pmSetProcessIdentity(username);
+
+ dp->version.two.fetch = hotproc_fetch;
+ dp->version.two.store = hotproc_store;
+ dp->version.two.desc = hotproc_desc;
+ dp->version.two.instance = hotproc_instance;
+
+ /*
+ * Maintaining my own desc and indom table/info.
+ * This is why the passed in tables are NULL.
+ */
+ pmdaInit(dp, NULL, 0, NULL, 0);
+ init_tables(dp->domain);
+ hotproc_domain = dp->domain;
+
+ if ((procdir = opendir(PROCFS)) == NULL) {
+ dp->status = -oserror();
+ __pmNotifyErr(LOG_ERR, "opendir(%s) failed: %s\n", PROCFS, osstrerror());
+ return;
+ }
+
+ set_proc_fmt();
+
+
+ if ((num_cpus = (int)sysmp(MP_NPROCS)) < 0) {
+ dp->status = -oserror();
+ __pmNotifyErr(LOG_ERR, "sysmp failed to get NPROCS: %s\n", osstrerror());
+ return;
+ }
+
+ if ((sts = init_proc_list()) < 0) {
+ dp->status = sts;
+ return;
+ }
+
+ if ((sts = restart_proc_list()) < 0) {
+ dp->status = sts;
+ return;
+ }
+}
+
+
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s [options] configfile\n\n", pmProgname);
+ (void)fputs("Options:\n"
+ " -C parse configfile and exit\n"
+ " -U username user account to run under (default \"pcp\")\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -s do NOT allow dynamic changes to the selection predicate\n"
+ " -t refresh set the refresh time interval in seconds\n",
+ stderr);
+ exit(1);
+}
+
+
+static void
+get_options(int argc, char *argv[], pmdaInterface *dispatch)
+{
+ int n;
+ int err = 0;
+ char *err_msg;
+
+
+ while ((n = pmdaGetOpt(argc, argv, "CD:d:l:t:U:xX:?s",
+ dispatch, &err)) != EOF) {
+ switch (n) {
+
+ case 'C':
+ parse_only = 1;
+ break;
+
+ case 's':
+ allow_stores = 0;
+ break;
+
+ case 't':
+ if (pmParseInterval(optarg, &refresh_delta, &err_msg) < 0) {
+ (void)fprintf(stderr,
+ "%s: -t requires a time interval: %s\n",
+ err_msg, pmProgname);
+ free(err_msg);
+ err++;
+ }
+ break;
+
+ case 'U':
+ username = optarg;
+ break;
+
+ case 'x':
+ /* hidden option for predicate testing */
+ pred_testing = 1;
+ break;
+
+ case 'X':
+ /* hidden option for predicate testing with iocntl buffers */
+ testing_fname = optarg;
+ break;
+
+ case '?':
+ err++;
+ }
+ }
+
+ if (err || optind != argc -1) {
+ usage();
+ }
+
+ configfile = argv[optind];
+}
+
+
+
+/*
+ * Set up the agent for running as a daemon.
+ */
+
+int
+main(int argc, char **argv)
+{
+ pmdaInterface dispatch;
+ char *p;
+ int sep = __pmPathSeparator();
+ int infd;
+ fd_set fds;
+ fd_set readyfds;
+ int nready;
+ int numfds;
+ FILE *conf;
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ refresh_delta.tv_sec = 10;
+ refresh_delta.tv_usec = 0;
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "hotproc" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, HOTPROC,
+ "hotproc.log", mypath);
+
+ get_options(argc, argv, &dispatch);
+
+ if (pred_testing) {
+ do_pred_testing();
+ exit(0);
+ }
+
+ if (!parse_only) {
+ pmdaOpenLog(&dispatch);
+ log = dispatch.version.two.ext->e_logfile;
+ }
+
+ conf = open_config();
+ read_config(conf);
+ (void)fclose(conf);
+
+ if (parse_only)
+ exit(0);
+
+ hotproc_init(&dispatch);
+ pmdaConnect(&dispatch);
+
+ if ((infd = __pmdaInFd(&dispatch)) < 0)
+ exit(1);
+ FD_ZERO(&fds);
+ FD_SET(infd, &fds);
+ numfds = infd+1;
+
+ refresh_afid = __pmAFregister(&refresh_delta, NULL, timer_callback);
+
+ /* custom pmda main loop */
+ for (;;) {
+ (void)memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(numfds, &readyfds, NULL, NULL, NULL);
+
+ if (nready > 0) {
+ __pmAFblock();
+ if (__pmdaMainPDU(&dispatch) < 0)
+ break;
+ __pmAFunblock();
+ }
+ else if (nready < 0 && neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failed: %s\n", netstrerror());
+ }
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/hotproc/src/hotproc.h b/src/pmdas/hotproc/src/hotproc.h
new file mode 100644
index 0000000..1c68fea
--- /dev/null
+++ b/src/pmdas/hotproc/src/hotproc.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PHOTPROC_H
+#define PHOTPROC_H
+
+#include "config.h"
+
+/* main process node type */
+typedef struct process_t {
+ pid_t pid;
+
+ /* refreshed data */
+ ulong_t r_vctx;
+ ulong_t r_ictx;
+ ulong_t r_syscalls;
+ ulong_t r_bread;
+ ulong_t r_gbread;
+ ulong_t r_bwrit;
+ ulong_t r_gbwrit;
+
+ float r_cpuburn;
+ double r_cputimestamp;
+ double r_cputime;
+
+ accum_t r_bwtime;
+ accum_t r_rwtime;
+ accum_t r_qwtime;
+
+ /* predicate values */
+ derived_pred_t preds;
+
+} process_t;
+
+process_t * lookup_curr_node(pid_t);
+
+#endif
diff --git a/src/pmdas/hotproc/src/lex.l b/src/pmdas/hotproc/src/lex.l
new file mode 100644
index 0000000..d19f999
--- /dev/null
+++ b/src/pmdas/hotproc/src/lex.l
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+#include "./gram_node.h"
+#include "./gram.h"
+
+void yyerror(char *s);
+
+static char emsg[256]; /* error message */
+static char yy_ichar; /* input char from conf_buffer */
+extern char *conf_buffer_ptr;
+
+#undef input
+#undef unput
+#define input() ( (yy_ichar = *conf_buffer_ptr++) == '\n' ? (yylineno++, yy_ichar) : yy_ichar)
+#define unput(c) { if ((*--conf_buffer_ptr = c) == '\n') yylineno--; }
+
+%}
+
+%option noinput
+%option nounput
+
+%%
+
+Version { return VERSION; }
+schedwait { return SCHEDWAIT; }
+iowait { return IOWAIT; }
+iodemand { return IODEMAND; }
+residentsize { return RESIDENTSIZE; }
+virtualsize { return VIRTUALSIZE; }
+ctxswitch { return CTXSWITCH; }
+syscalls { return SYSCALLS; }
+gid { return GID; }
+uid { return UID; }
+uname { return UNAME; }
+gname { return GNAME; }
+fname { return FNAME; }
+psargs { return PSARGS; }
+cpuburn { return CPUBURN; }
+"&&" { return AND; }
+"||" { return OR; }
+"!" { return NOT; }
+"(" { return LPAREN; }
+")" { return RPAREN; }
+true { return TRUE; }
+false { return FALSE; }
+"==" { return EQUAL; }
+"!=" { return NEQUAL; }
+"<" { return LTHAN; }
+"<=" { return LEQUAL; }
+">" { return GTHAN; }
+">=" { return GEQUAL; }
+"~" { return MATCH; }
+"!~" { return NMATCH; }
+
+\/[^/\n]*[/\n] {
+ char *str;
+ yylval.y_str = (char *)malloc(yyleng-1);
+ if (yylval.y_str == 0) {
+ (void)sprintf(emsg, "malloc failed: %s", osstrerror());
+ yyerror(emsg);
+ }
+ strncpy(yylval.y_str, &yytext[1], yyleng-2);
+ yylval.y_str[yyleng-2] = '\0';
+ if ((str = re_comp(yylval.y_str)) != 0) {
+ yyerror(str);
+ }
+ return PATTERN;
+ }
+
+\"[^"\n]*["\n] {
+ yylval.y_str = (char *)malloc(yyleng-1);
+ if (yylval.y_str == 0) {
+ (void)sprintf(emsg, "malloc failed: %s", osstrerror());
+ yyerror(emsg);
+ }
+ strncpy(yylval.y_str, &yytext[1], yyleng-2);
+ yylval.y_str[yyleng-2] = '\0';
+ return STRING;
+ }
+
+
+[0-9]+ |
+[0-9]*"."[0-9]+ |
+[0-9]+"."[0-9]* {
+ yylval.y_number = atof(yytext);
+ return NUMBER;
+ }
+
+\#.*\n { }
+
+[\t \r\n]+ { }
+
+
+[a-zA-Z]+ {
+ yyerror("Illegal word");
+ }
+
+. {
+ yyerror("Illegal character");
+ }
+%%
+
diff --git a/src/pmdas/hotproc/src/pcpu.c b/src/pmdas/hotproc/src/pcpu.c
new file mode 100644
index 0000000..da6abbd
--- /dev/null
+++ b/src/pmdas/hotproc/src/pcpu.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "proc.h"
+#include "proc_aux.h"
+#include "cluster.h"
+#include "pcpu.h"
+#include "hotproc.h"
+
+static pmDesc desctab[] = {
+ /* hotproc.cpuburn */
+ { PMID(CLUSTER_CPU,ITEM_CPUBURN),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+
+};
+
+static int ndesc = (sizeof(desctab)/sizeof(desctab[0]));
+
+static float *cpuburn_buf = NULL;
+
+void
+pcpu_init(int dom)
+{
+ init_table(ndesc, desctab, dom);
+}
+
+int
+pcpu_getdesc(pmID pmid, pmDesc *desc)
+{
+ return getdesc(ndesc, desctab, pmid, desc);
+}
+
+int
+pcpu_setatom(int item, pmAtomValue *atom, int j)
+{
+ switch (item) {
+ case ITEM_CPUBURN:
+ atom->f = cpuburn_buf[j];
+ break;
+ }
+ return 0;
+}
+
+int
+pcpu_getinfo(pid_t pid, int j)
+{
+ process_t *node;
+ char *path;
+
+ node = lookup_curr_node(pid);
+ if (node == NULL) {
+ /* node should be there if it's in active list ! */
+ (void)fprintf(stderr, "%s: Internal error for lookup_node()", pmProgname);
+ exit(1);
+ }
+ proc_pid_to_path(pid, NULL, &path, PINFO_PATH);
+ if (access(path, R_OK) < 0)
+ return -oserror();
+
+ cpuburn_buf[j] = node->r_cpuburn;
+ return 0;
+}
+
+
+int
+pcpu_allocbuf(int size)
+{
+ static int max_size = 0;
+ float *cpub;
+
+ if (size > max_size) {
+ cpub = realloc(cpuburn_buf, size * sizeof(float));
+ if (cpub == NULL)
+ return -oserror();
+ cpuburn_buf = cpub;
+ max_size = size;
+ }
+
+ return 0;
+}
diff --git a/src/pmdas/hotproc/src/pcpu.h b/src/pmdas/hotproc/src/pcpu.h
new file mode 100644
index 0000000..9bcf65c
--- /dev/null
+++ b/src/pmdas/hotproc/src/pcpu.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PCPU_H
+#define PCPU_H
+
+#define CLUSTER_CPU 102
+#define ITEM_CPUBURN 0
+
+void pcpu_init(int dom);
+int pcpu_getdesc(pmID pmid, pmDesc *desc);
+int pcpu_setatom(int item, pmAtomValue *atom, int j);
+int pcpu_getinfo(pid_t pid, int j);
+int pcpu_allocbuf(int size);
+
+#endif
diff --git a/src/pmdas/hotproc/src/pglobal.c b/src/pmdas/hotproc/src/pglobal.c
new file mode 100644
index 0000000..3c4a1ac
--- /dev/null
+++ b/src/pmdas/hotproc/src/pglobal.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/procfs.h>
+#include <sys/immu.h>
+#include <sys/sysmacros.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "pmapi.h"
+
+#include "./proc.h"
+#include "./proc_aux.h"
+#include "./pglobal.h"
+
+static pmDesc desctab[] = {
+ { PMID(CLUSTER_GLOBAL,ITEM_NPROCS),
+ PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_REFRESH),
+ PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_CONFIG),
+ PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_CONFIG_GEN),
+ PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_CPUIDLE),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_TOTAL_CPUBURN),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_TRANSIENT),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_TOTAL_NOTCPUBURN),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_OTHER_TOTAL),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} },
+ { PMID(CLUSTER_GLOBAL,ITEM_OTHER_PERCENT),
+ PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, {0,0,0,0,0,0} }
+};
+
+static int ndesc = (sizeof(desctab)/sizeof(desctab[0]));
+
+
+void
+pglobal_init(int dom)
+{
+ init_table(ndesc, desctab, dom);
+}
+
+int
+pglobal_getdesc(pmID pmid, pmDesc *desc)
+{
+ return getdesc(ndesc, desctab, pmid, desc);
+}
+
+int
+pglobal_setatom(int item, pmAtomValue *atom, int j)
+{
+ /* noop */
+ return 0;
+}
+
+
+int
+pglobal_getinfo(pid_t pid, int j)
+{
+ /* noop */
+ return 0;
+}
+
+
+int
+pglobal_allocbuf(int size)
+{
+ /* noop */
+ return 0;
+}
diff --git a/src/pmdas/hotproc/src/pglobal.h b/src/pmdas/hotproc/src/pglobal.h
new file mode 100644
index 0000000..659d7e0
--- /dev/null
+++ b/src/pmdas/hotproc/src/pglobal.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PGLOBAL_H
+#define PGLOBAL_H
+
+#define CLUSTER_GLOBAL 100
+
+#define ITEM_NPROCS 0
+#define ITEM_REFRESH 1
+#define ITEM_CPUIDLE 2
+#define ITEM_TOTAL_CPUBURN 3
+#define ITEM_TRANSIENT 4
+#define ITEM_TOTAL_NOTCPUBURN 5
+#define ITEM_OTHER_TOTAL 6
+#define ITEM_OTHER_PERCENT 7
+#define ITEM_CONFIG 8
+#define ITEM_CONFIG_GEN 9
+
+void pglobal_init(int dom);
+int pglobal_getdesc(pmID pmid, pmDesc *desc);
+int pglobal_setatom(int item, pmAtomValue *atom, int j);
+int pglobal_getinfo(pid_t pid, int j);
+int pglobal_allocbuf(int size);
+
+#endif
diff --git a/src/pmdas/hotproc/src/ppred_values.c b/src/pmdas/hotproc/src/ppred_values.c
new file mode 100644
index 0000000..98e17f1
--- /dev/null
+++ b/src/pmdas/hotproc/src/ppred_values.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "proc.h"
+#include "proc_aux.h"
+#include "cluster.h"
+#include "ppred_values.h"
+#include "hotproc.h"
+
+
+static pmDesc desctab[] = {
+ /* hotproc.predicate.syscalls */
+ { PMID(CLUSTER_PRED,ITEM_SYSCALLS),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {0,-1,1, 0,PM_TIME_SEC,0} },
+ /* hotproc.predicate.ctxswitch */
+ { PMID(CLUSTER_PRED,ITEM_CTXSWITCH),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {0,-1,1, 0,PM_TIME_SEC,0} },
+ /* hotproc.predicate.virtualsize */
+ { PMID(CLUSTER_PRED,ITEM_VIRTUALSIZE),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {1,0,0, PM_SPACE_KBYTE,0,0} },
+ /* hotproc.predicate.residentsize */
+ { PMID(CLUSTER_PRED,ITEM_RESIDENTSIZE),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {1,0,0, PM_SPACE_KBYTE,0,0} },
+ /* hotproc.predicate.iodemand */
+ { PMID(CLUSTER_PRED,ITEM_IODEMAND),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {1,-1,0, PM_SPACE_KBYTE,PM_TIME_SEC,0} },
+
+ /*
+ * NOTE
+ * iowait and schedwait really have units of sec/sec, i.e. utilization
+ */
+ /* hotproc.predicate.iowait */
+ { PMID(CLUSTER_PRED,ITEM_IOWAIT),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {0,0,0, 0,0,0} },
+ /* hotproc.predicate.schedwait */
+ { PMID(CLUSTER_PRED,ITEM_SCHEDWAIT),
+ PM_TYPE_FLOAT, PM_INDOM_PROC, PM_SEM_INSTANT, {0,0,0, 0,0,0} },
+};
+
+static int ndesc = (sizeof(desctab)/sizeof(desctab[0]));
+
+static derived_pred_t *pred_buf = NULL;
+
+void
+ppred_init(int dom)
+{
+ init_table(ndesc, desctab, dom);
+}
+
+int
+ppred_getdesc(pmID pmid, pmDesc *desc)
+{
+ return getdesc(ndesc, desctab, pmid, desc);
+}
+
+int
+ppred_available(int item)
+{
+ extern int need_psusage; /* is psusage buffer needed or not */
+ extern int need_accounting; /* is pracinfo buffer needed or not */
+
+ switch (item) {
+ case ITEM_SYSCALLS:
+ case ITEM_CTXSWITCH:
+ case ITEM_IODEMAND:
+ return need_psusage;
+ case ITEM_IOWAIT:
+ case ITEM_SCHEDWAIT:
+ return need_accounting;
+ case ITEM_VIRTUALSIZE:
+ case ITEM_RESIDENTSIZE:
+ /* need_psinfo - always have it */
+ return 1;
+ }
+ return 1;
+}
+
+int
+ppred_setatom(int item, pmAtomValue *atom, int j)
+{
+ switch (item) {
+ case ITEM_SYSCALLS:
+ atom->f = pred_buf[j].syscalls;
+ break;
+ case ITEM_CTXSWITCH:
+ atom->f = pred_buf[j].ctxswitch;
+ break;
+ case ITEM_VIRTUALSIZE:
+ atom->f = pred_buf[j].virtualsize;
+ break;
+ case ITEM_RESIDENTSIZE:
+ atom->f = pred_buf[j].residentsize;
+ break;
+ case ITEM_IODEMAND:
+ atom->f = pred_buf[j].iodemand;
+ break;
+ case ITEM_IOWAIT:
+ atom->f = pred_buf[j].iowait;
+ break;
+ case ITEM_SCHEDWAIT:
+ atom->f = pred_buf[j].schedwait;
+ break;
+ }
+ return 0;
+}
+
+int
+ppred_getinfo(pid_t pid, int j)
+{
+ process_t *node;
+ char *path;
+
+ node = lookup_curr_node(pid);
+ if (node == NULL) {
+ /* node should be there if it's in active list ! */
+ (void)fprintf(stderr, "%s: Internal error for lookup_node()", pmProgname);
+ exit(1);
+ }
+ proc_pid_to_path(pid, NULL, &path, PINFO_PATH);
+ if (access(path, R_OK) < 0)
+ return -oserror();
+
+ pred_buf[j] = node->preds;
+ return 0;
+}
+
+
+int
+ppred_allocbuf(int size)
+{
+ static int max_size = 0;
+ derived_pred_t *predb;
+
+ if (size > max_size) {
+ predb = realloc(pred_buf, size * sizeof(derived_pred_t));
+ if (predb == NULL)
+ return -oserror();
+ pred_buf = predb;
+ max_size = size;
+ }
+
+ return 0;
+}
diff --git a/src/pmdas/hotproc/src/ppred_values.h b/src/pmdas/hotproc/src/ppred_values.h
new file mode 100644
index 0000000..22a2332
--- /dev/null
+++ b/src/pmdas/hotproc/src/ppred_values.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef PPRED_H
+#define PPRED_H
+
+#define CLUSTER_PRED 101
+
+#define ITEM_SYSCALLS 0
+#define ITEM_CTXSWITCH 1
+#define ITEM_VIRTUALSIZE 2
+#define ITEM_RESIDENTSIZE 3
+#define ITEM_IODEMAND 4
+#define ITEM_IOWAIT 5
+#define ITEM_SCHEDWAIT 6
+
+void ppred_init(int dom);
+int ppred_getdesc(pmID pmid, pmDesc *desc);
+int ppred_setatom(int item, pmAtomValue *atom, int j);
+int ppred_getinfo(pid_t pid, int j);
+int ppred_allocbuf(int size);
+int ppred_available(int item);
+
+#endif
diff --git a/src/pmdas/infiniband/GNUmakefile b/src/pmdas/infiniband/GNUmakefile
new file mode 100644
index 0000000..79991dc
--- /dev/null
+++ b/src/pmdas/infiniband/GNUmakefile
@@ -0,0 +1,58 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2007-2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmdaib$(EXECSUFFIX)
+CFILES = ib.c pmda.c
+HFILES = ibpmda.h
+
+LSRCFILES = help root pmns Install Remove
+LLDLIBS = $(IB_LIBS) $(PCP_LIBS) -lpcp_pmda -lpcp
+
+IAM = ib
+DOMAIN = IB
+PMDADIR = $(PCP_PMDAS_DIR)/infiniband
+LDIRT = domain.h *.o $(IAM).log $(CMDTARGET)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(PMDA_INFINIBAND)" ""
+build-me: domain.h $(CMDTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -S $(PMDADIR) $(PCP_PMDAS_DIR)/$(IAM)
+ $(INSTALL) -m 755 Install Remove $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 pmns root help domain.h $(PMDADIR)
+else
+build-me:
+install:
+endif
+
+ib.o: domain.h
+
+.NOTPARALLEL:
+.ORDER: domain.h $(OBJECTS)
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/infiniband/Install b/src/pmdas/infiniband/Install
new file mode 100755
index 0000000..b674b88
--- /dev/null
+++ b/src/pmdas/infiniband/Install
@@ -0,0 +1,46 @@
+#! /bin/sh
+#
+# Copyright (C) 2013 Red Hat.
+# Copyright (C) 2007,2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. /etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=infiniband
+pmda_interface=3
+daemon_opt=true
+dso_opt=false
+pipe_opt=true
+socket_opt=false
+
+path=/sys/class/infiniband_mad
+if ! test -d $path; then
+ echo "Kernel lacks Infiniband support - $path directory not found"
+ exit 1
+fi
+
+__choose_mode()
+{
+ do_pmda=true
+}
+
+__choose_ipc()
+{
+ ipc_type=pipe
+ type="pipe binary $PCP_PMDAS_DIR/infiniband/pmdaib"
+}
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/infiniband/Remove b/src/pmdas/infiniband/Remove
new file mode 100755
index 0000000..c35c41f
--- /dev/null
+++ b/src/pmdas/infiniband/Remove
@@ -0,0 +1,24 @@
+#! /bin/sh
+#
+# Copyright (C) 2007,2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. /etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=infiniband
+
+pmdaSetup
+pmdaRemove
+exit 0
+
diff --git a/src/pmdas/infiniband/help b/src/pmdas/infiniband/help
new file mode 100644
index 0000000..93eaa7d
--- /dev/null
+++ b/src/pmdas/infiniband/help
@@ -0,0 +1,190 @@
+#
+# Copyright (c) 2007,2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+@ infiniband.hca.type Node type
+Node type: channel adapter (CA), switch, router etc
+@ infiniband.hca.ca_type HCA type
+HCA type, e.g. MT23108,
+@ infiniband.hca.numports Number of ports on HCA
+Number of ports on HCA
+@ infiniband.hca.fw_ver Version of HCA firmware
+Version of HCA firmware
+@ infiniband.hca.hw_ver Version of HCA hardware
+Version of HCA hardware
+@ infiniband.hca.node_guid Node's Global Unique Identifier
+Node's Global Unique Identifier - 64 bit integer to refer to the node
+@ infiniband.hca.system_guid System's Global Unique Identifier
+System's Global Unique Identifier - 64 bit integer to refer to the system
+@ infiniband.port.guid Port's Global Unique Identifier
+Port's Global Unique Identifier - 64 bit integer to refer to the port
+@ infiniband.port.gid_prefix GID prefix
+GID prefix, assigned by subnet manager
+@ infiniband.port.lid Port's Local Identifier
+Port's Local Identifier, assigned by subnet manager
+@ infiniband.port.state Port's state
+Port's state - can be Active, Down, NoChange, Armed or Initialize
+@ infiniband.port.phystate Port's physical state
+Port's physical state
+@ infiniband.port.rate Port's Data Rate
+Port's Data Rate: 2, 5, 10 or 20 Gbps
+@ infiniband.port.capabilities Port's capabilities
+Port's capabilities.
+@ infiniband.port.linkspeed Base link speed of the port.
+This is a string which represents the base link speed of the port.
+Multiplying link speed by link width gives port's data rate.
+@ infiniband.port.linkwidth Port's link width.
+Number of bi-directional Infiniband links active on the port.
+Also known as X-factor, as in 1X, 4X, 12X.
+@ infiniband.port.in.bytes Bytes received
+Counter of data octets received on all VLs at the port. This
+includes all octets between (and not including) the start of
+packet delimiter and the VCRC, and may include packets containing errors.
+It excludes all link packets.
+
+This counter is implemented by sampling underlying saturating PortRcvData
+counter. When a value of saturated counter reaches predefined threshold,
+the counter is reset after its value is copied into internal state.
+
+@ infiniband.port.in.packets Packets received
+Counter of data packets received on all VLs at the port. This
+may include packets containing errors but excludes all link packets.
+
+@ infiniband.port.in.errors.drop Packets dropped due to errors
+Number of packets received on the port that were discarded because they
+could not be forwarded by the switch relay due to DLID mapping, VL mapping
+or looping. Implemented by sampling 16 bit PortRcvSwitchRelayErrors
+counter.
+
+@ infiniband.port.in.errors.filter Packets filtered out
+Number of packets received by the port that were discarded because
+it was a raw packet and FilterRawInbound is enabled or because
+PartitionEnforcementInbound is enabled and packet failed partition
+key check or IP version check. Implemented by sampling 8 bit
+PortRcvConstraintErrors counter.
+
+@ infiniband.port.in.errors.local Packets with errors
+Counter of packets containing local physical errors, malformed data or
+link packets or packets discarded due to buffer overrun. Implemented by
+sampling 16 bit PortRcvErrors counter.
+
+@ infiniband.port.in.errors.remote Packets with EBP delimiter.
+Number of packets marked with End Bad Packet delimited received by
+the port. Implemented by sampling 16 bit PortRcvRemotePhysicalerrors
+counter.
+
+@ infiniband.port.out.bytes Bytes transmitted
+Counter of data octets, transmitted on all VLs from the port. This
+includes all octets between (and not including) the start of
+packet delimiter and the VCRC, and may include packets containing errors.
+It excludes all link packets.
+
+This counter is implemented by sampling underlying saturating PortXmtData
+counter. When a value of saturated counter reaches predefined threshold,
+the counter is reset after its value is copied into internal state.
+
+@ infiniband.port.out.packets Packets transmitted
+Counter of data packets transmitted on all VLs from the port. This
+may include packets containing errors but excludes all link packets.
+
+@ infiniband.port.out.errors.drop Packets dropped without transmitting
+Number of outbound packets which were droped because port is down
+or congested. Implemented by sampling 16 bit PortXmtDiscard counter.
+
+@ infiniband.port.out.errors.filter Packets filtered out before transmitting
+Number of packets not transmitted by the port because
+it was a raw packet and FilterRawInbound is enabled or because
+PartitionEnforcementInbound is enabled and packet failed partition
+key check or IP version check. Implemented by sampling 8 bit
+PortXmitConstraintErrors counter.
+
+@ infiniband.port.total.bytes Bytes transmitted and received
+Cumulative value of infiniband.port.in.bytes and
+infiniband.port.out.bytes, provided for convenience.
+
+@ infiniband.port.total.packets Packets transmitted and received
+Cumulative value of infiniband.port.in.packets and
+infiniband.port.out.packets, provided for convenience.
+
+@ infiniband.port.total.errors.drop Packet dropped
+Cumulative counter of infiniband.port.in.errors.drop and
+infiniband.out.errors.drops.
+
+@ infiniband.port.total.errors.filter Packet filtered out
+Cumulative counter of infiniband.port.in.errors.filter and
+infiniband.out.errors.filter.
+
+@ infiniband.port.total.errors.link Link downed
+Number of times Port Training state machine has failed to
+complete link recovery process and downed the link. Implemented by
+sampling 8 bit LinkDownedCounter.
+
+@ infiniband.port.total.errors.recover Successful recoveries
+Number of times Port Training state machine has managed successfully
+complete link recovery process. Implemented by sampling 8 bit
+LinkErrorRecoveryCounter.
+
+@ infiniband.port.total.errors.integrity Excessive local physical errors
+Number of times the count of local physical errors exceeded the threshold.
+Implemented by sampling 4 bit LocalLinkIntegrityErrors counter.
+
+@ infiniband.port.total.errors.vl15 Dropped packets to VL15
+Number of times packets to VL15 (management virtual line) was dropped
+due to resource limitations. Implemented by sampling 16 bit VL15Dropped
+counter.
+
+@ infiniband.port.total.errors.overrun Excessive Buffer Overruns
+The number of times buffer overrun errors had persisted over multiple
+flow control update times. Implemented by sampling 4 bit
+ExcessiveBufferOverrun counter.
+
+@ infiniband.port.total.errors.symbol Total number of minor link errors
+Total number of minor link errors detected on one or more physical lines.
+Implemented by sampling 16 bit SymbolErrorCounter.
+
+@ infiniband.control.query_timeout Timeout for MAD perquery
+Timeout in milliseconds for MAD rpcs. Default value is 1000 milliseconds.
+Timeout can be set per port.
+
+@ infiniband.control.hiwat Counter threshold values
+Threshold values for each MAD performance counter. Due to saturating
+nature of the counters they're reset when value of a particular counter
+gets above a threshold. Setting threshold to the maximum value disables
+the reset mechanism.
+
+@ infiniband.port.switch.in.bytes Bytes received (using switch counter)
+Counter for the bytes received by a port. This is calculated using the counter
+of the switch the port is attached to.
+
+@ infiniband.port.switch.in.packets Packets received (using switch counter)
+Counter for the packets received by a port. This is calculated using the
+counter of the switch the port is attached to.
+
+@ infiniband.port.switch.out.bytes Bytes transmitted (using switch counter)
+Counter for the bytes transmitted by a port. This is calculated using the
+counter of the switch the port is attached to.
+
+@ infiniband.port.switch.out.packets Packets transmitted (using switch counter)
+Counter for the packets transmitted by a port. This is calculated using the
+counter of the switch the port is attached to.
+
+@ infiniband.port.switch.total.bytes Bytes transmitted and received (using switch counters)
+Cumulative value of infiniband.port.switch.in.bytes and
+infiniband.port.switch.out.bytes, provided for convenience.
+
+@ infiniband.port.switch.total.packets Packets transmitted and received (using switch counters)
+Cumulative value of infiniband.port.switch.in.packets and
+infiniband.port.switch.out.packets, provided for convenience.
diff --git a/src/pmdas/infiniband/ib.c b/src/pmdas/infiniband/ib.c
new file mode 100644
index 0000000..b3dc182
--- /dev/null
+++ b/src/pmdas/infiniband/ib.c
@@ -0,0 +1,1050 @@
+/*
+ * Copyright (C) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * IB part of the PMDA - initialization, fetching etc.
+ */
+#include "ibpmda.h"
+#include <infiniband/umad.h>
+#include <infiniband/mad.h>
+#include <ctype.h>
+
+#define IBPMDA_MAX_HCAS (16)
+
+typedef struct local_port_s {
+ /*
+ * Cache the ca_name and portnum to avoid a bug in libibumad that
+ * leaks memory when umad_port_get() is called over and over.
+ * With ca_name and portnum we can safely do umad_port_release()
+ * first and then umad_port_get() without fear that some future
+ * version of release() will deallocate port->ca_name and
+ * port->portnum.
+ */
+ char ca_name[UMAD_CA_NAME_LEN];
+ int portnum;
+ umad_port_t *ump;
+ void * hndl;
+ int needsupdate;
+} local_port_t;
+
+/* umad_ca_t starts with a name which is good enough for us to use */
+typedef struct hca_state_s {
+ umad_ca_t ca;
+ local_port_t lports[UMAD_CA_MAX_PORTS];
+} hca_state_t;
+
+/* IB Architecture rel 1.2 demands that performance counters
+ * must plateau once they reach 2^32. This structure is used
+ * to track the counters and reset them when they get close to
+ * the magic boundary */
+typedef struct mad_counter_s {
+ uint64_t accum; /* Accumulated value */
+ uint32_t prev; /* Previous value of the counter */
+ uint32_t cur; /* Current value, only valid during iteration */
+ uint32_t isvalid; /* Is current value valid? */
+} mad_counter_t;
+
+typedef struct mad_cnt_desc_s {
+ enum MAD_FIELDS madid; /* ID for the counter */
+ char *name;
+ int resetmask; /* Reset mask for port_performance_reset */
+ uint32_t hiwat; /* If current value is over hiwat mark, reset it */
+ int multiplier;
+} mad_cnt_desc_t;
+
+#define MADDESC_INIT(id, mask, shft, mul) \
+ [ IBPMDA_##id ] {IB_PC_##id##_F, #id, (1<<mask), (1U<<shft), mul}
+
+static mad_cnt_desc_t mad_cnt_descriptors[] = {
+ MADDESC_INIT(ERR_SYM, 0, 15, 1),
+ MADDESC_INIT(LINK_RECOVERS, 1, 7, 1),
+ MADDESC_INIT(LINK_DOWNED, 2, 7, 1),
+ MADDESC_INIT(ERR_RCV, 3, 15, 1),
+ MADDESC_INIT(ERR_PHYSRCV, 4, 15, 1),
+ MADDESC_INIT(ERR_SWITCH_REL, 5, 15, 1),
+ MADDESC_INIT(XMT_DISCARDS, 6, 15, 1),
+ MADDESC_INIT(ERR_XMTCONSTR, 7, 7, 1),
+ MADDESC_INIT(ERR_RCVCONSTR, 8, 7, 1),
+ MADDESC_INIT(ERR_LOCALINTEG, 9, 3, 1),
+ MADDESC_INIT(ERR_EXCESS_OVR, 10, 3, 1),
+ MADDESC_INIT(VL15_DROPPED, 11, 15, 1),
+ MADDESC_INIT(XMT_BYTES, 12, 31, 4),
+ MADDESC_INIT(RCV_BYTES, 13, 31, 4),
+ MADDESC_INIT(XMT_PKTS, 14, 31, 1),
+ MADDESC_INIT(RCV_PKTS, 15, 31, 1)
+};
+
+#undef MADDESC_INIT
+
+static char *node_types[] = {"Unknown", "CA", "Switch", "Router", "iWARP RNIC"};
+
+static char *port_states[] = {
+ "Unknown",
+ "Down",
+ "Initializing",
+ "Armed",
+ "Active"
+};
+
+static char *port_phystates[] = {
+ "No change",
+ "Sleep",
+ "Polling",
+ "Disabled",
+ "Port Configuration Training",
+ "Link Up",
+ "Error Recovery",
+ "PHY Test"
+};
+
+/* Size is arbitrary, currently need 285 bytes for all caps */
+#define IB_ALLPORTCAPSTRLEN 320
+
+typedef struct port_state_s {
+ ib_portid_t portid;
+ local_port_t *lport;
+ int needupdate;
+ int validstate;
+ int resetmask;
+ int timeout;
+ uint64_t guid;
+ int remport;
+ unsigned char perfdata[IB_MAD_SIZE];
+ unsigned char portinfo[IB_MAD_SIZE];
+ uint8_t switchperfdata[1024];
+ mad_counter_t madcnts[ARRAYSZ(mad_cnt_descriptors)];
+ char pcap[IB_ALLPORTCAPSTRLEN];
+} port_state_t;
+
+static char confpath[MAXPATHLEN];
+static int portcount;
+/* Line number while parsing the config file */
+static FILE *fconf;
+static int lcnt;
+
+#define print_parse_err(loglevel, fmt, args...) \
+ if (fconf) { \
+ __pmNotifyErr(loglevel, "%s(%d): " fmt, confpath, lcnt, args); \
+ } else { \
+ __pmNotifyErr(loglevel, fmt, args); \
+ }
+
+static void
+monitor_guid(pmdaIndom *itab, char *name, long long guid, int rport,
+ char *local, int lport)
+{
+ int inst;
+ hca_state_t *hca = NULL;
+ port_state_t *ps;
+
+ if (pmdaCacheLookupName(itab[IB_HCA_INDOM].it_indom, local, NULL,
+ (void**)&hca) != PMDA_CACHE_ACTIVE) {
+ print_parse_err(LOG_ERR, "unknown HCA '%s' in 'via' clause\n", local);
+ return;
+ }
+
+ if ((lport >= UMAD_CA_MAX_PORTS) || (lport < 0)) {
+ print_parse_err(LOG_ERR,
+ "port number %d is out of bounds for HCA %s\n",
+ lport, local);
+ return;
+ }
+
+ if (hca->lports[lport].hndl == NULL) {
+ print_parse_err(LOG_ERR,
+ "port %s:%d has failed initialization\n",
+ local, lport);
+ return;
+ }
+
+ if ((ps = (port_state_t *)calloc(1, sizeof(port_state_t))) == NULL) {
+ __pmNotifyErr (LOG_ERR, "Out of memory to save state for %s\n", name);
+ return;
+ }
+
+ ps->guid = guid;
+ ps->remport = rport;
+ ps->lport = hca->lports + lport;
+ ps->portid.lid = -1;
+ ps->timeout = 1000;
+
+ if ((inst = pmdaCacheStore(itab[IB_PORT_INDOM].it_indom,
+ PMDA_CACHE_ADD, name, ps)) < 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot add %s to the cache - %s\n",
+ name, pmErrStr(inst));
+ free (ps);
+ return;
+ }
+
+ portcount++;
+}
+
+
+static int
+foreachport(hca_state_t *hst, void (*cb)(hca_state_t *, umad_port_t *, void *),
+ void *closure)
+{
+ int pcnt = hst->ca.numports;
+ int p;
+ int nports = 0;
+
+ for (p=0; (pcnt >0) && (p < UMAD_CA_MAX_PORTS); p++) {
+ umad_port_t *port = hst->ca.ports[p];
+
+ if (port ) {
+ pcnt--;
+ nports++;
+ if (cb) {
+ cb (hst, port, closure);
+ }
+ }
+ }
+ return (nports);
+}
+
+#ifdef HAVE_NETWORK_BYTEORDER
+#define guid_htonll(a) do { } while (0) /* noop */
+#define guid_ntohll(a) do { } while (0) /* noop */
+#else
+static void
+guid_htonll(char *p)
+{
+ char c;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ c = p[i];
+ p[i] = p[7-i];
+ p[7-i] = c;
+ }
+}
+#define guid_ntohll(v) guid_htonll(v)
+#endif
+
+static void
+printportconfig (hca_state_t *hst, umad_port_t *port, void *arg)
+{
+ uint64_t hguid = port->port_guid;
+
+ guid_ntohll((char *)&hguid);
+
+ fprintf (fconf, "%s:%d 0x%llx %d via %s:%d\n",
+ port->ca_name, port->portnum, (unsigned long long)hguid,
+ port->portnum, hst->ca.ca_name, port->portnum);
+}
+
+static void
+monitorport(hca_state_t *hst, umad_port_t *port, void *arg)
+{
+ pmdaIndom *itab = arg;
+ uint64_t hguid = port->port_guid;
+ char name[128];
+
+ guid_ntohll((char *)&hguid);
+ sprintf(name, "%s:%d", port->ca_name, port->portnum);
+
+ monitor_guid(itab, name, hguid, port->portnum, port->ca_name, port->portnum);
+}
+
+
+static int mgmt_classes[] = {IB_SMI_CLASS, IB_SMI_DIRECT_CLASS,
+ IB_SA_CLASS, IB_PERFORMANCE_CLASS};
+static void
+openumadport (hca_state_t *hst, umad_port_t *port, void *arg)
+{
+ void *hndl = arg;
+ local_port_t *lp;
+
+ if ((hndl = mad_rpc_open_port(port->ca_name, port->portnum, mgmt_classes,
+ ARRAYSZ(mgmt_classes))) == NULL) {
+ __pmNotifyErr(LOG_ERR, "Cannot open port handle for %s:%d\n",
+ port->ca_name, port->portnum);
+ }
+ lp = &hst->lports[port->portnum];
+ strcpy(lp->ca_name, port->ca_name);
+ lp->portnum = port->portnum;
+ lp->ump = port;
+ lp->hndl = hndl;
+}
+
+static void
+parse_config(pmdaIndom *itab)
+{
+ char buffer[2048];
+
+ while ((fgets(buffer, sizeof(buffer)-1, fconf)) != NULL) {
+ char *p;
+
+ lcnt++;
+
+ /* strip comments */
+ if ((p = strchr(buffer,'#')))
+ *p='\0';
+
+ for (p = buffer; *p; p++) {
+ if (!isspace (*p))
+ break;
+ }
+
+ if (*p != '\0') {
+ char name[128];
+ long long guid;
+ int rport;
+ char local[128];
+ int lport;
+
+ if (sscanf(p, "%[^ \t]%llx%d via %[^:]:%d",
+ name, &guid, &rport, local, &lport) != 5) {
+ __pmNotifyErr (LOG_ERR, "%s(%d): cannot parse the line\n",
+ confpath, lcnt);
+ continue;
+ }
+
+ monitor_guid(itab, name, guid, rport, local, lport);
+ }
+ }
+}
+
+int
+ib_load_config(const char *cp, int writeconf, pmdaIndom *itab, unsigned int nindoms)
+{
+ char hcas[IBPMDA_MAX_HCAS][UMAD_CA_NAME_LEN];
+ hca_state_t *st = NULL;
+ int i, n;
+ int (*closef)(FILE *) = fclose;
+
+ if (nindoms <= IB_CNT_INDOM)
+ return -EINVAL;
+
+ if (umad_init()) {
+ __pmNotifyErr(LOG_ERR,
+ "umad_init() failed. No IB kernel support or incorrect ABI version\n");
+ return -EIO;
+ }
+
+ if ((n = umad_get_cas_names(hcas, ARRAYSZ(hcas)))) {
+ if ((st = calloc (n, sizeof(hca_state_t))) == NULL)
+ return -ENOMEM;
+ } else
+ /* No HCAs */
+ return 0;
+
+ /* Open config file - if the executable bit is set then assume that
+ * user wants it to be a script and run it, otherwise try loading it.
+ */
+ strcpy(confpath, cp);
+ if (access(confpath, F_OK) == 0) {
+ if (writeconf) {
+ __pmNotifyErr(LOG_ERR,
+ "Config file exists and writeconf arg was given to pmdaib. Aborting.");
+ exit(1);
+ }
+
+ if (access(confpath, X_OK)) {
+ /* Not an executable, just read it */
+ fconf = fopen (confpath, "r");
+ } else {
+ fconf = popen(confpath, "r");
+ closef = pclose;
+ }
+ } else if (writeconf) {
+ fconf = fopen(confpath, "w");
+ }
+ /* else no config file: Just monitor local ports */
+
+ for (i=0; i < n; i++) {
+ if (umad_get_ca(hcas[i], &st[i].ca) == 0) {
+ int e = pmdaCacheStore(itab[IB_HCA_INDOM].it_indom, PMDA_CACHE_ADD,
+ st[i].ca.ca_name, &st[i].ca);
+
+ if (e < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot add instance for %s to the cache - %s\n",
+ st[i].ca.ca_name, pmErrStr(e));
+ continue;
+ }
+
+ foreachport(st+i, openumadport, NULL);
+ if (fconf == NULL)
+ /* No config file - monitor local ports */
+ foreachport(st+i, monitorport, itab);
+ if (writeconf)
+ foreachport(st+i, printportconfig, fconf);
+ }
+ }
+
+ if (fconf) {
+ parse_config(itab);
+ (*closef)(fconf);
+ }
+
+ if (writeconf)
+ /* Config file is now written. Exit. */
+ exit(0);
+
+ if (!portcount) {
+ __pmNotifyErr(LOG_INFO, "No IB ports found to monitor");
+ }
+
+ itab[IB_CNT_INDOM].it_set = (pmdaInstid *)calloc(ARRAYSZ(mad_cnt_descriptors),
+ sizeof(pmdaInstid));
+
+ if (itab[IB_CNT_INDOM].it_set == NULL) {
+ return -ENOMEM;
+ }
+
+ itab[IB_CNT_INDOM].it_numinst = ARRAYSZ(mad_cnt_descriptors);
+ for (i=0; i < ARRAYSZ(mad_cnt_descriptors); i++) {
+ itab[IB_CNT_INDOM].it_set[i].i_inst = i;
+ itab[IB_CNT_INDOM].it_set[i].i_name = mad_cnt_descriptors[i].name;
+
+ }
+
+ return 0;
+}
+
+static char *
+ib_portcap_to_string(port_state_t *pst)
+{
+ static struct {
+ int bit;
+ const char *cap;
+ } capdest [] = {
+ {1, "SM"},
+ {2, "Notice"},
+ {3, "Trap"},
+ {5, "AutomaticMigration"},
+ {6, "SLMapping"},
+ {7, "MKeyNVRAM"},
+ {8, "PKeyNVRAM"},
+ {9, "LedInfo"},
+ {10, "SMdisabled"},
+ {11, "SystemImageGUID"},
+ {12, "PkeySwitchExternalPortTrap"},
+ {16, "CommunicatonManagement"},
+ {17, "SNMPTunneling"},
+ {18, "Reinit"},
+ {19, "DeviceManagement"},
+ {20, "VendorClass"},
+ {21, "DRNotice"},
+ {22, "CapabilityMaskNotice"},
+ {23, "BootManagement"},
+ {24, "IsLinkRoundTripLatency"},
+ {25, "ClientRegistration"}
+ };
+ char *comma = "";
+ int commalen = 0;
+ int i;
+ char *ptr = pst->pcap;
+ uint32_t bsiz = sizeof(pst->pcap);
+ int pcap = mad_get_field(pst->portinfo, 0, IB_PORT_CAPMASK_F);
+
+ *ptr ='\0';
+
+ for (i=0; i < ARRAYSZ(capdest); i++) {
+ if (pcap & (1<<capdest[i].bit)) {
+ int sl = strlen(capdest[i].cap) + commalen;
+ if (sl < bsiz) {
+ sprintf (ptr, "%s%s", comma, capdest[i].cap);
+ comma = ","; commalen=1;
+ bsiz -= sl;
+ ptr += sl;
+ }
+ }
+ }
+
+ return (pst->pcap);
+}
+
+
+/* This function can be called multiple times during single
+ * fetch operation so take care to avoid side effects, for example,
+ * if the "previous" value of the counter is above the high
+ * watermark and must be reset, don't change the previous value here -
+ * it could lead to double counting on the second call */
+static uint64_t
+ib_update_perfcnt (port_state_t *pst, int udata, int *rv )
+{
+ mad_cnt_desc_t * md = mad_cnt_descriptors + udata;
+ mad_counter_t *mcnt = pst->madcnts + udata;
+
+ if (!mcnt->isvalid) {
+ uint32_t delta;
+
+ mcnt->cur = mad_get_field(pst->perfdata, 0, md->madid);
+ mcnt->isvalid = 1;
+
+ /* If someone resets the counters, then don't update the the
+ * accumulated value because we don't know what was the value before it
+ * was reset. And if the difference between current and previous value
+ * is larger then the high watermark then don't update the accumulated
+ * value either - current value could've pegged because we didn't
+ * fetch often enough */
+ delta = mcnt->cur - mcnt->prev;
+ if ((mcnt->cur < mcnt->prev) || (delta > md->hiwat)) {
+ mcnt->isvalid = PM_ERR_VALUE;
+ } else {
+ mcnt->accum += delta;
+ }
+
+ if (mcnt->cur > md->hiwat) {
+ pst->resetmask |= md->resetmask;
+ }
+ }
+
+ *rv = mcnt->isvalid;
+ return (mcnt->accum * md->multiplier);
+}
+
+static int
+ib_linkwidth (port_state_t *pst)
+{
+ int w = mad_get_field(pst->portinfo, 0, IB_PORT_LINK_WIDTH_ACTIVE_F);
+
+ switch (w) {
+ case 1:
+ return (1);
+ case 2:
+ return (4);
+ case 4:
+ return (8);
+ case 8:
+ return (12);
+ }
+ return (0);
+}
+
+int
+ib_fetch_val(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmInDom_int *ind = (__pmInDom_int *)&(mdesc->m_desc.indom);
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int rv = 1;
+ port_state_t *pst = NULL;
+ hca_state_t *hca = NULL;
+ int umask = 1<<idp->cluster;
+ int udata = (int)((__psint_t)mdesc->m_user);
+ void *closure = NULL;
+ int st;
+ char *name = NULL;
+
+ if (inst == PM_INDOM_NULL) {
+ return PM_ERR_INST;
+ }
+
+ if (ind->serial != IB_CNT_INDOM) {
+ if ((st = pmdaCacheLookup (mdesc->m_desc.indom, inst, &name,
+ &closure)) != PMDA_CACHE_ACTIVE) {
+ if (st == PMDA_CACHE_INACTIVE)
+ st = PM_ERR_INST;
+ __pmNotifyErr (LOG_ERR, "Cannot find instance %d in indom %s: %s\n",
+ inst, pmInDomStr(mdesc->m_desc.indom), pmErrStr(st));
+ return st;
+ }
+ }
+
+ /* If fetching from HCA indom, then no refreshing is necessary for the
+ * lifetime of a pmda. Ports could change state, so some update could be
+ * necessary */
+ switch (ind->serial) {
+ case IB_PORT_INDOM:
+ if (idp->cluster > 3) {
+ return PM_ERR_INST;
+ }
+
+ pst = closure;
+
+ if (pst->needupdate & umask) {
+ local_port_t *lp = pst->lport;
+
+ /* A port state is considered up-to date regardless of any
+ * errors which could happen later - this is used to implement
+ * one shot updates */
+ pst->needupdate ^= umask;
+
+ /* The state of the local port used for queries is checked
+ * once per fetch request */
+ if (lp->needsupdate) {
+ umad_release_port(lp->ump);
+ if (umad_get_port(lp->ca_name, lp->portnum, lp->ump) != 0) {
+ __pmNotifyErr (LOG_ERR,
+ "Cannot get state of the port %s:%d\n",
+ lp->ump->ca_name, lp->ump->portnum);
+ return 0;
+ }
+ lp->needsupdate = 0;
+ }
+
+ /* If the port which we're supposed to use to query the data
+ * does not have a LID then we don't even try to query anything,
+ * is it going to fail anyway */
+ if (lp->ump->base_lid == 0) {
+ return 0; /* No values available */
+ }
+
+ if (pst->portid.lid < 0) {
+ ib_portid_t sm = {0};
+ sm.lid = lp->ump->sm_lid;
+
+ memset (&pst->portid, 0, sizeof (pst->portid));
+ if (ib_resolve_guid_via (&pst->portid, &pst->guid, &sm,
+ pst->timeout, lp->hndl) < 0) {
+ __pmNotifyErr (LOG_ERR,
+ "Cannot resolve GUID 0x%llx for %s "
+ "via %s:%d\n",
+ (unsigned long long)pst->guid,
+ name, lp->ump->ca_name,
+ lp->ump->portnum);
+ pst->portid.lid = -1;
+ return 0;
+ }
+ }
+
+ switch (idp->cluster) {
+ case 0: /* port attributes */
+ memset (pst->portinfo, 0, sizeof(pst->portinfo));
+ if (!smp_query_via (pst->portinfo, &pst->portid,
+ IB_ATTR_PORT_INFO, 0, pst->timeout,
+ lp->hndl)) {
+ __pmNotifyErr (LOG_ERR,
+ "Cannot get port info for %s via %s:%d\n",
+ name, lp->ump->ca_name, lp->ump->portnum);
+ return 0;
+ }
+ break;
+
+ case 1: /* performance counters */
+ /* I thought about updating all accumulating counters
+ * in case port_performance_query() succeeds but
+ * decided not to do it right now - updating all counters
+ * could mean more resets even in case when nobody is
+ * actually looking at the particular counter and I'm
+ * trying to minimize resets. */
+ memset (pst->perfdata, 0, sizeof (pst->perfdata));
+ if (!port_perf_query(pst->perfdata, &pst->portid,
+ pst->remport, pst->timeout, lp->hndl)) {
+ __pmNotifyErr (LOG_ERR,
+ "Cannot get performance counters for %s "
+ "via %s:%d\n",
+ name, lp->ump->ca_name, lp->ump->portnum);
+ return 0;
+ }
+ break;
+
+ case 3: { /* switch performance counters */
+
+#ifdef HAVE_PMA_QUERY_VIA
+
+ // To find the LID of the switch the HCA is connected to,
+ // send an SMP on the directed route 0,1 and ask the port
+ // to identify itself.
+ ib_portid_t sw_port_id = {
+ .drpath = {
+ .cnt = 1,
+ .p = { 0, 1, },
+ },
+ };
+
+ uint8_t sw_info[64];
+ memset(sw_info, 0, sizeof(sw_info));
+ if (!smp_query_via(sw_info, &sw_port_id, IB_ATTR_PORT_INFO, 0,
+ pst->timeout, lp->hndl)) {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot get switch port info for %s via %s:%d.\n",
+ name, lp->ump->ca_name, lp->ump->portnum);
+ return 0;
+ }
+
+ int sw_lid, sw_port;
+ mad_decode_field(sw_info, IB_PORT_LID_F, &sw_lid);
+ mad_decode_field(sw_info, IB_PORT_LOCAL_PORT_F, &sw_port);
+
+ sw_port_id.lid = sw_lid;
+
+ // Query for the switch's performance counters' values.
+ memset(pst->switchperfdata, 0, sizeof(pst->switchperfdata));
+ if (!pma_query_via(pst->switchperfdata, &sw_port_id, sw_port,
+ pst->timeout, IB_GSI_PORT_COUNTERS_EXT, lp->hndl)) {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot query performance counters of switch LID %d, port %d.\n",
+ sw_lid, sw_port);
+ return 0;
+ }
+#endif
+ break;
+ }
+
+ }
+ pst->validstate ^= umask;
+ } else if (!(pst->validstate & umask)) {
+ /* We've hit an error on the previous update - continue
+ * reporting no data for this instance */
+ return (0);
+ }
+ break;
+
+ case IB_HCA_INDOM:
+ hca = closure;
+ break;
+
+ case IB_CNT_INDOM:
+ break;
+
+ default:
+ return (PM_ERR_INST);
+ }
+
+ switch (idp->cluster) {
+ case 0: /* UMAD data - hca name, fw_version, number of ports etc */
+ switch(idp->item) {
+ case METRIC_ib_hca_hw_ver:
+ atom->cp = hca->ca.hw_ver;
+ break;
+
+ case METRIC_ib_hca_system_guid:
+ atom->ull = hca->ca.system_guid;
+ break;
+
+ case METRIC_ib_hca_node_guid:
+ atom->ull = hca->ca.node_guid;
+ break;
+
+ case METRIC_ib_hca_numports:
+ atom->l = hca->ca.numports;
+ break;
+
+ case METRIC_ib_hca_type:
+ if (hca->ca.node_type < ARRAYSZ(node_types)) {
+ atom->cp = node_types[hca->ca.node_type];
+ } else {
+ __pmNotifyErr (LOG_INFO, "Unknown node type %d for %s\n",
+ hca->ca.node_type, hca->ca.ca_name);
+ atom->cp = "Unknown";
+ }
+ break;
+
+ case METRIC_ib_hca_fw_ver:
+ atom->cp = hca->ca.fw_ver;
+ break;
+
+ case METRIC_ib_port_gid_prefix:
+ atom->ull = mad_get_field64(pst->portinfo, 0, IB_PORT_GID_PREFIX_F);
+ break;
+
+ case METRIC_ib_port_rate:
+ atom->l = ib_linkwidth(pst) *
+ (5 * mad_get_field (pst->portinfo, 0,
+ IB_PORT_LINK_SPEED_ACTIVE_F))/2;
+ break;
+
+ case METRIC_ib_port_lid:
+ atom->l = pst->portid.lid;
+ break;
+
+ case METRIC_ib_port_capabilities:
+ atom->cp = ib_portcap_to_string(pst);
+ break;
+
+ case METRIC_ib_port_phystate:
+ st = mad_get_field (pst->portinfo, 0, IB_PORT_PHYS_STATE_F);
+ if (st < ARRAYSZ(port_phystates)) {
+ atom->cp = port_phystates[st];
+ } else {
+ __pmNotifyErr (LOG_INFO, "Unknown port PHY state %d on %s\n",
+ st, name);
+ atom->cp = "Unknown";
+ }
+ break;
+
+ case METRIC_ib_port_guid:
+ atom->ull = pst->guid;
+ break;
+
+ case METRIC_ib_hca_ca_type:
+ atom->cp = hca->ca.ca_type;
+ break;
+
+ case METRIC_ib_port_state:
+ st = mad_get_field (pst->portinfo, 0, IB_PORT_STATE_F);
+ if (st < ARRAYSZ(port_states)) {
+ atom->cp = port_states[st];
+ } else {
+ __pmNotifyErr (LOG_INFO, "Unknown port state %d on %s\n",
+ st, name);
+ atom->cp = "Unknown";
+ }
+ break;
+
+ case METRIC_ib_port_linkspeed:
+ switch ((st = mad_get_field(pst->portinfo, 0,
+ IB_PORT_LINK_SPEED_ACTIVE_F))) {
+ case 1:
+ atom->cp = "2.5 Gpbs";
+ break;
+ case 2:
+ atom->cp = "5.0 Gbps";
+ break;
+ case 4:
+ atom->cp = "10.0 Gbps";
+ break;
+ default:
+ __pmNotifyErr (LOG_INFO, "Unknown link speed %d on %s\n",
+ st, name);
+ atom->cp = "Unknown";
+ break;
+ }
+ break;
+
+ case METRIC_ib_port_linkwidth:
+ atom->l = ib_linkwidth(pst);
+ break;
+
+ default:
+ rv = PM_ERR_PMID;
+ break;
+ }
+ break;
+
+ case 1: /* Fetch values from mad rpc response */
+ if ((udata >= 0) && (udata < ARRAYSZ(mad_cnt_descriptors))) {
+ /* If a metric has udata set then it's one of the "direct"
+ * metrics - just update the accumulated counter
+ * and stuff its value into pmAtomValue */
+ switch (mdesc->m_desc.type) {
+ case PM_TYPE_32:
+ atom->l = (int32_t)ib_update_perfcnt (pst, udata, &rv);
+ break;
+ case PM_TYPE_64:
+ atom->ll = ib_update_perfcnt (pst, udata, &rv);
+ break;
+ default:
+ rv = PM_ERR_INST;
+ break;
+ }
+ } else {
+ int rv1=0, rv2=0;
+ /* Synthetic metrics */
+ switch (idp->item) {
+ case METRIC_ib_port_total_bytes:
+ atom->ll = ib_update_perfcnt (pst, IBPMDA_XMT_BYTES, &rv1)
+ + ib_update_perfcnt (pst, IBPMDA_RCV_BYTES, &rv2);
+ break;
+
+ case METRIC_ib_port_total_packets:
+ atom->ll = ib_update_perfcnt (pst, IBPMDA_XMT_PKTS, &rv1)
+ + ib_update_perfcnt (pst, IBPMDA_RCV_PKTS, &rv2);
+ break;
+
+ case METRIC_ib_port_total_errors_drop:
+ atom->l = (int)(ib_update_perfcnt (pst, IBPMDA_ERR_SWITCH_REL, &rv1)
+ + ib_update_perfcnt (pst, IBPMDA_XMT_DISCARDS, &rv2));
+ break;
+
+ case METRIC_ib_port_total_errors_filter:
+ atom->l = (int)(ib_update_perfcnt (pst, IBPMDA_ERR_XMTCONSTR, &rv1)
+ + ib_update_perfcnt (pst, IBPMDA_ERR_RCVCONSTR, &rv2));
+ break;
+
+ default:
+ rv = PM_ERR_PMID;
+ break;
+ }
+
+ if ((rv1 < 0) || (rv2 < 0)) {
+ rv = (rv1 < 0) ? rv1 : rv2;
+ }
+ }
+ break;
+
+ case 2: /* Control structures */
+ switch (idp->item) {
+ case METRIC_ib_control_query_timeout:
+ atom->l = pst->timeout;
+ break;
+
+ case METRIC_ib_control_hiwat:
+ if (inst < ARRAYSZ(mad_cnt_descriptors)) {
+ atom->ul = mad_cnt_descriptors[inst].hiwat;
+ } else {
+ rv = PM_ERR_INST;
+ }
+ break;
+
+ default:
+ rv = PM_ERR_PMID;
+ break;
+ }
+ break;
+
+ case 3: /* Fetch values from switch response */
+
+#ifdef HAVE_PMA_QUERY_VIA
+
+ // (The values are "swapped" because what the port receives is what the
+ // switch sends, and vice versa.)
+ switch (idp->item) {
+ case METRIC_ib_port_switch_in_bytes: {
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_XMT_BYTES_F, &atom->ull);
+ atom->ull *= 4; // TODO: programmatically determine link width
+ break;
+ }
+ case METRIC_ib_port_switch_in_packets: {
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_XMT_PKTS_F, &atom->ull);
+ break;
+ }
+ case METRIC_ib_port_switch_out_bytes: {
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_RCV_BYTES_F, &atom->ull);
+ atom->ull *= 4; // TODO: programmatically determine link width
+ break;
+ }
+ case METRIC_ib_port_switch_out_packets: {
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_RCV_PKTS_F, &atom->ull);
+ break;
+ }
+ case METRIC_ib_port_switch_total_bytes: {
+ uint64_t sw_rx_bytes, sw_tx_bytes;
+ int ib_lw;
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_RCV_BYTES_F, &sw_rx_bytes);
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_XMT_BYTES_F, &sw_tx_bytes);
+ ib_lw = 4; // TODO: programmatically determine link width
+ atom->ull = (sw_rx_bytes * ib_lw) + (sw_tx_bytes * ib_lw);
+ break;
+ }
+ case METRIC_ib_port_switch_total_packets: {
+ uint64_t sw_rx_packets, sw_tx_packets;
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_RCV_PKTS_F, &sw_rx_packets);
+ mad_decode_field(pst->switchperfdata,
+ IB_PC_EXT_XMT_PKTS_F, &sw_tx_packets);
+ atom->ull = sw_rx_packets + sw_tx_packets;
+ break;
+ }
+ default: {
+ rv = PM_ERR_PMID;
+ break;
+ }
+ }
+#else
+
+ return PM_ERR_VALUE;
+
+#endif
+ break;
+
+ default:
+ rv = PM_ERR_PMID;
+ break;
+ }
+
+ return rv;
+}
+
+/* Walk the instances and arm needupdate flag in each instance's
+ * state. The actuall updating is done in the fetch function */
+void
+ib_rearm_for_update(void *state)
+{
+ port_state_t *pst = state;
+
+ pst->lport->needsupdate = 1;
+
+ pst->needupdate = IB_PORTINFO_UPDATE | IB_HCA_PERF_UPDATE | IB_SWITCH_PERF_UPDATE;
+ pst->validstate = 4; /* 0x4 for timeout which is always valid */
+}
+
+void
+ib_reset_perfcounters (void *state)
+{
+ int m;
+ port_state_t *pst = state;
+
+ if (pst->resetmask && (pst->portid.lid != 0)) {
+ memset (pst->perfdata, 0, sizeof (pst->perfdata));
+
+ if (port_perf_reset(pst->perfdata, &pst->portid, pst->remport,
+ pst->resetmask, pst->timeout, pst->lport->hndl)) {
+ int j;
+
+ for (j=0; j < ARRAYSZ(mad_cnt_descriptors); j++) {
+ if (pst->resetmask & (1<<j)) {
+ pst->madcnts[j].prev = 0;
+ pst->madcnts[j].isvalid = 0;
+ }
+ }
+ }
+ }
+ pst->resetmask = 0;
+
+ for (m=0; m < ARRAYSZ(mad_cnt_descriptors); m++) {
+ if (pst->madcnts[m].isvalid) {
+ pst->madcnts[m].prev = pst->madcnts[m].cur;
+ pst->madcnts[m].isvalid = 0;
+ }
+ }
+}
+
+int
+ib_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+
+ for (i = 0; i < result->numpmid ; i++) {
+ pmValueSet *vs = result->vset[i];
+ __pmID_int *pmidp = (__pmID_int *)&vs->pmid;
+ int inst;
+
+ if (pmidp->cluster != 2) {
+ return (-EACCES);
+ }
+
+ if (vs->valfmt != PM_VAL_INSITU) {
+ return (-EINVAL);
+ }
+
+ for (inst=0; inst < vs->numval; inst++) {
+ int id = vs->vlist[inst].inst;
+ void *closure = NULL;
+
+ switch (pmidp->item) {
+ case METRIC_ib_control_query_timeout:
+ if (pmdaCacheLookup (pmda->e_indoms[IB_PORT_INDOM].it_indom,
+ id, NULL, &closure) == PMDA_CACHE_ACTIVE) {
+ port_state_t *pst = closure;
+ pst->timeout = vs->vlist[inst].value.lval;
+ } else {
+ return (PM_ERR_INST);
+ }
+ break;
+
+ case METRIC_ib_control_hiwat:
+ if ((id < 0) ||
+ (id > pmda->e_indoms[IB_CNT_INDOM].it_numinst)) {
+ return (PM_ERR_INST);
+ }
+
+ mad_cnt_descriptors[id].hiwat = (uint32_t)vs->vlist[inst].value.lval;
+ break;
+
+ default:
+ return (-EACCES);
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/src/pmdas/infiniband/ibpmda.h b/src/pmdas/infiniband/ibpmda.h
new file mode 100644
index 0000000..a9bbafa
--- /dev/null
+++ b/src/pmdas/infiniband/ibpmda.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2013 Red Hat.
+ * Copyright (C) 2007,2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _IBPMDA_H
+#define _IBPMDA_H
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+
+#ifdef HAVE_PORT_PERFORMANCE_QUERY_VIA
+#define port_perf_query(data, dst, port, timeout, srcport) \
+ port_performance_query_via(data, dst, port, timeout, srcport)
+#define port_perf_reset(data, dst, port, mask, timeout, srcport) \
+ port_performance_reset_via(data, dst, port, mask, timeout, srcport)
+#else
+#define port_perf_query(data, dst, port, timeout, srcport) \
+ pma_query_via(data, dst, port, timeout, IB_GSI_PORT_COUNTERS, srcport)
+#define port_perf_reset(data, dst, port, mask, timeout, srcport) \
+ performance_reset_via(data, dst, port, mask, timeout, IB_GSI_PORT_COUNTERS, srcport)
+#endif
+
+
+void ibpmda_init (const char *configpath, int, pmdaInterface *);
+
+int ib_fetch_val(pmdaMetric *, unsigned int, pmAtomValue *);
+int ib_load_config(const char *, int, pmdaIndom *, unsigned int);
+void ib_rearm_for_update(void *);
+void ib_reset_perfcounters (void *);
+int ib_store(pmResult *, pmdaExt *);
+
+#define IB_HCA_INDOM 0
+#define IB_PORT_INDOM 1
+#define IB_CNT_INDOM 2
+
+#define IB_PORTINFO_UPDATE 0x1
+#define IB_HCA_PERF_UPDATE 0x2
+#define IB_SWITCH_PERF_UPDATE 0x8
+
+#define ARRAYSZ(a) (sizeof(a)/sizeof(a[0]))
+
+#define METRIC_ib_hca_type 0
+#define METRIC_ib_hca_ca_type 1
+#define METRIC_ib_hca_numports 2
+#define METRIC_ib_hca_fw_ver 3
+#define METRIC_ib_hca_hw_ver 4
+#define METRIC_ib_hca_node_guid 5
+#define METRIC_ib_hca_system_guid 6
+#define METRIC_ib_port_guid 7
+#define METRIC_ib_port_gid_prefix 8
+#define METRIC_ib_port_lid 9
+#define METRIC_ib_port_state 10
+#define METRIC_ib_port_phystate 11
+#define METRIC_ib_port_rate 12
+#define METRIC_ib_port_capabilities 13
+#define METRIC_ib_port_linkspeed 14
+#define METRIC_ib_port_linkwidth 15
+
+/* Per-port performance counters, cluster #1 */
+#define METRIC_ib_port_in_bytes 0
+#define METRIC_ib_port_in_packets 1
+#define METRIC_ib_port_out_bytes 2
+#define METRIC_ib_port_out_packets 3
+#define METRIC_ib_port_in_errors_drop 4
+#define METRIC_ib_port_out_errors_drop 5
+#define METRIC_ib_port_total_bytes 6
+#define METRIC_ib_port_total_packets 7
+#define METRIC_ib_port_total_errors_drop 8
+#define METRIC_ib_port_in_errors_filter 9
+#define METRIC_ib_port_in_errors_local 10
+#define METRIC_ib_port_in_errors_remote 11
+#define METRIC_ib_port_out_errors_filter 12
+#define METRIC_ib_port_total_errors_filter 13
+#define METRIC_ib_port_total_errors_link 14
+#define METRIC_ib_port_total_errors_recover 15
+#define METRIC_ib_port_total_errors_integrity 16
+#define METRIC_ib_port_total_errors_vl15 17
+#define METRIC_ib_port_total_errors_overrun 18
+#define METRIC_ib_port_total_errors_symbol 19
+
+/* Control metrics */
+#define METRIC_ib_control_query_timeout 0
+#define METRIC_ib_control_hiwat 1
+
+/* Per-port switch performance counters, cluster #3 */
+#define METRIC_ib_port_switch_in_bytes 0
+#define METRIC_ib_port_switch_in_packets 1
+#define METRIC_ib_port_switch_out_bytes 2
+#define METRIC_ib_port_switch_out_packets 3
+#define METRIC_ib_port_switch_total_bytes 4
+#define METRIC_ib_port_switch_total_packets 5
+
+enum ibpmd_cndid {
+ IBPMDA_ERR_SYM = 0,
+ IBPMDA_LINK_RECOVERS,
+ IBPMDA_LINK_DOWNED,
+ IBPMDA_ERR_RCV,
+ IBPMDA_ERR_PHYSRCV,
+ IBPMDA_ERR_SWITCH_REL,
+ IBPMDA_XMT_DISCARDS,
+ IBPMDA_ERR_XMTCONSTR,
+ IBPMDA_ERR_RCVCONSTR,
+ IBPMDA_ERR_LOCALINTEG,
+ IBPMDA_ERR_EXCESS_OVR,
+ IBPMDA_VL15_DROPPED,
+ IBPMDA_XMT_BYTES,
+ IBPMDA_RCV_BYTES,
+ IBPMDA_XMT_PKTS,
+ IBPMDA_RCV_PKTS
+};
+
+#endif /* _IBPMDA_H */
diff --git a/src/pmdas/infiniband/pmda.c b/src/pmdas/infiniband/pmda.c
new file mode 100644
index 0000000..20c5ba1
--- /dev/null
+++ b/src/pmdas/infiniband/pmda.c
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2013 Red Hat.
+ * Copyright (C) 2007,2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "ibpmda.h"
+
+/*
+ * Metric Table
+ */
+pmdaMetric metrictab[] = {
+ /* infiniband.hca.type */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_type),
+ PM_TYPE_STRING, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.ca_type */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_ca_type),
+ PM_TYPE_STRING, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.numports */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_numports),
+ PM_TYPE_32, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.fw_ver */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_fw_ver),
+ PM_TYPE_STRING, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.hw_ver */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_hw_ver),
+ PM_TYPE_STRING, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.node_guid */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_node_guid),
+ PM_TYPE_U64, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.hca.system_guid */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_hca_system_guid),
+ PM_TYPE_U64, IB_HCA_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.guid */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_guid),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.gid_prefix */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_gid_prefix),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.lid */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_lid),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.state */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_state),
+ PM_TYPE_STRING, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.phystate */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_phystate),
+ PM_TYPE_STRING, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.rate */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_rate),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.capabilities */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_capabilities),
+ PM_TYPE_STRING, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.linkspeed */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_linkspeed),
+ PM_TYPE_STRING, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.linkwidth */
+ { NULL,
+ {PMDA_PMID(0,METRIC_ib_port_linkwidth),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* infiniband.port.in.bytes */
+ { (void *)IBPMDA_RCV_BYTES,
+ {PMDA_PMID(1,METRIC_ib_port_in_bytes),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+ /* infiniband.port.in.packets */
+ { (void *)IBPMDA_RCV_PKTS,
+ {PMDA_PMID(1,METRIC_ib_port_in_packets),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) }, },
+
+ /* infiniband.port.in.errors.drop */
+ { (void *)IBPMDA_ERR_SWITCH_REL,
+ {PMDA_PMID(1,METRIC_ib_port_in_errors_drop),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.in.errors.filter */
+ { (void *)IBPMDA_ERR_RCVCONSTR,
+ {PMDA_PMID(1,METRIC_ib_port_in_errors_filter),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.in.errors.local */
+ { (void *)IBPMDA_ERR_RCV,
+ {PMDA_PMID(1,METRIC_ib_port_in_errors_local),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.in.errors.filter */
+ { (void *)IBPMDA_ERR_PHYSRCV,
+ {PMDA_PMID(1,METRIC_ib_port_in_errors_remote),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.out.bytes */
+ { (void *)IBPMDA_XMT_BYTES,
+ {PMDA_PMID(1,METRIC_ib_port_out_bytes),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+ /* infiniband.port.out.packets */
+ {(void *)IBPMDA_XMT_PKTS,
+ {PMDA_PMID(1,METRIC_ib_port_out_packets),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) }, },
+
+ /* infiniband.port.out.errors.drop */
+ { (void *)IBPMDA_XMT_DISCARDS,
+ {PMDA_PMID(1,METRIC_ib_port_out_errors_drop),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.out.errors.filter */
+ { (void *)IBPMDA_ERR_XMTCONSTR,
+ {PMDA_PMID(1,METRIC_ib_port_out_errors_filter),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.bytes */
+ { (void *)-1,
+ {PMDA_PMID(1,METRIC_ib_port_total_bytes),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ /* infiniband.port.total.packets */
+ { (void *)-1,
+ {PMDA_PMID(1,METRIC_ib_port_total_packets),
+ PM_TYPE_64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.drop */
+ { (void *)-1,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_drop),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.filter */
+ { (void *)-1,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_filter),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.link */
+ { (void *)IBPMDA_LINK_DOWNED,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_link),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.recover */
+ { (void *)IBPMDA_LINK_RECOVERS,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_recover),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.integrity */
+ { (void *)IBPMDA_ERR_LOCALINTEG,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_integrity),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.vl15 */
+ { (void *)IBPMDA_VL15_DROPPED,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_vl15),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.overrun */
+ { (void *)IBPMDA_ERR_EXCESS_OVR,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_overrun),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.total.errors.symbol */
+ { (void *)IBPMDA_ERR_SYM,
+ {PMDA_PMID(1,METRIC_ib_port_total_errors_symbol),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.control.query_timeout */
+ { NULL,
+ {PMDA_PMID(2,METRIC_ib_control_query_timeout),
+ PM_TYPE_32, IB_PORT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* infiniband.control.hiwat */
+ { NULL,
+ {PMDA_PMID(2,METRIC_ib_control_hiwat),
+ PM_TYPE_U32, IB_CNT_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* infiniband.port.switch.in.bytes */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_in_bytes),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ /* infiniband.port.switch.in.packets */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_in_packets),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.switch.out.bytes */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_out_bytes),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ /* infiniband.port.switch.out.packets */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_out_packets),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+
+ /* infiniband.port.switch.total.bytes */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_total_bytes),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ /* infiniband.port.switch.total.packets */
+ { NULL,
+ {PMDA_PMID(3,METRIC_ib_port_switch_total_packets),
+ PM_TYPE_U64, IB_PORT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,0) } },
+};
+
+pmdaIndom indomtab[] = {
+ { IB_HCA_INDOM, 0, NULL },
+ { IB_PORT_INDOM, 0, NULL },
+ { IB_CNT_INDOM, 0, NULL },
+};
+
+static void
+foreach_inst(pmInDom indom, void (*cb)(void *state))
+{
+ int i;
+ pmdaCacheOp (indom, PMDA_CACHE_WALK_REWIND);
+
+ while ((i = pmdaCacheOp (indom, PMDA_CACHE_WALK_NEXT)) >= 0) {
+ void *state = NULL;
+
+ if (pmdaCacheLookup (indom, i, NULL, &state) != PMDA_CACHE_ACTIVE) {
+ abort();
+ }
+ cb (state);
+ }
+}
+
+int
+ib_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int rv;
+
+ foreach_inst (indomtab[IB_PORT_INDOM].it_indom, ib_rearm_for_update);
+ rv = pmdaFetch (numpmid, pmidlist, resp, pmda);
+ foreach_inst (indomtab[IB_PORT_INDOM].it_indom, ib_reset_perfcounters);
+
+ return (rv);
+}
+
+void
+ibpmda_init(const char *confpath, int writeconf, pmdaInterface *dp)
+{
+ char defconf[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ int i;
+
+ if (dp->status != 0)
+ return;
+
+ if (confpath == NULL) {
+ snprintf(defconf, sizeof(defconf), "%s%c" "infiniband" "%c" "config",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ confpath = defconf;
+ }
+
+ for (i=0; i < ARRAYSZ(indomtab); i++) {
+ __pmindom_int(&indomtab[i].it_indom)->domain = dp->domain;
+ if (IB_CNT_INDOM != __pmindom_int(&indomtab[i].it_indom)->serial) {
+ pmdaCacheOp (indomtab[i].it_indom, PMDA_CACHE_LOAD);
+ }
+ }
+
+
+ if ((dp->status = ib_load_config(confpath, writeconf, indomtab, ARRAYSZ(indomtab))))
+ return;
+
+ for (i=0; i < ARRAYSZ(indomtab); i++) {
+ if (IB_CNT_INDOM != __pmindom_int(&indomtab[i].it_indom)->serial) {
+ pmdaCacheOp (indomtab[i].it_indom, PMDA_CACHE_SAVE);
+ }
+ }
+
+ dp->version.two.fetch = ib_fetch;
+ dp->version.two.store = ib_store;
+ pmdaSetFetchCallBack(dp, ib_fetch_val);
+
+ pmdaInit(dp, indomtab, ARRAYSZ(indomtab), metrictab, ARRAYSZ(metrictab));
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -c path to configuration file\n"
+ " -w write the basic configuration file\n",
+ stderr);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int err = 0;
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+ char *confpath = NULL;
+ int opt;
+ int writeconf = 0;
+
+ __pmSetProgname(argv[0]);
+ snprintf(helppath, sizeof(helppath), "%s%c" "infiniband" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, IB, "infiniband.log", helppath);
+
+ while ((opt = pmdaGetOpt(argc, argv, "D:c:d:l:w?", &dispatch, &err)) != EOF) {
+ switch (opt) {
+ case 'c':
+ confpath = optarg;
+ break;
+ case 'w':
+ writeconf = 1;
+ break;
+ default:
+ err++;
+ }
+ }
+
+ if (err) {
+ usage();
+ }
+
+ if (!writeconf) {
+ /* If writeconf is specified, then errors should go to stdout
+ * since the PMDA daemon will exit immediately after writing
+ * out the default config file
+ */
+ pmdaOpenLog(&dispatch);
+ }
+ ibpmda_init(confpath, writeconf, &dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/infiniband/pmns b/src/pmdas/infiniband/pmns
new file mode 100644
index 0000000..b42919d
--- /dev/null
+++ b/src/pmdas/infiniband/pmns
@@ -0,0 +1,100 @@
+
+infiniband {
+ hca
+ port
+ control
+}
+
+infiniband.hca {
+ type IB:0:0
+ ca_type IB:0:1
+ numports IB:0:2
+ fw_ver IB:0:3
+ hw_ver IB:0:4
+ node_guid IB:0:5
+ system_guid IB:0:6
+}
+
+infiniband.port {
+ guid IB:0:7
+ gid_prefix IB:0:8
+ lid IB:0:9
+ state IB:0:10
+ phystate IB:0:11
+ rate IB:0:12
+ capabilities IB:0:13
+ linkspeed IB:0:14
+ linkwidth IB:0:15
+
+ in
+ out
+ total
+ switch
+}
+
+infiniband.port.in {
+ bytes IB:1:0
+ packets IB:1:1
+ errors
+}
+
+infiniband.port.in.errors {
+ drop IB:1:4
+ filter IB:1:9
+ local IB:1:10
+ remote IB:1:11
+}
+
+infiniband.port.out {
+ bytes IB:1:2
+ packets IB:1:3
+ errors
+}
+
+infiniband.port.out.errors {
+ drop IB:1:5
+ filter IB:1:12
+}
+
+infiniband.port.total {
+ bytes IB:1:6
+ packets IB:1:7
+ errors
+}
+
+infiniband.port.total.errors {
+ drop IB:1:8
+ filter IB:1:13
+ link IB:1:14
+ recover IB:1:15
+ integrity IB:1:16
+ vl15 IB:1:17
+ overrun IB:1:18
+ symbol IB:1:19
+}
+
+infiniband.port.switch {
+ in
+ out
+ total
+}
+
+infiniband.port.switch.in {
+ bytes IB:3:0
+ packets IB:3:1
+}
+
+infiniband.port.switch.out {
+ bytes IB:3:2
+ packets IB:3:3
+}
+
+infiniband.port.switch.total {
+ bytes IB:3:4
+ packets IB:3:5
+}
+
+infiniband.control {
+ query_timeout IB:2:0
+ hiwat IB:2:1
+}
diff --git a/src/pmdas/infiniband/root b/src/pmdas/infiniband/root
new file mode 100644
index 0000000..460798c
--- /dev/null
+++ b/src/pmdas/infiniband/root
@@ -0,0 +1,9 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { infiniband }
+
+#include "pmns"
diff --git a/src/pmdas/jbd2/GNUmakefile b/src/pmdas/jbd2/GNUmakefile
new file mode 100644
index 0000000..ac19900
--- /dev/null
+++ b/src/pmdas/jbd2/GNUmakefile
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = jbd2
+DOMAIN = JBD2
+CMDTARGET = pmdajbd2
+LIBTARGET = pmda_jbd2.so
+PMDAINIT = jbd2_init
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "jbd2 122 dso $(PMDAINIT) $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = proc_jbd2.c pmda.c
+HFILES = proc_jbd2.h convert.h
+SCRIPTS = Install Remove
+VERSION_SCRIPT = exports
+HELPTARGETS = help.dir help.pag
+LSRCFILES = help root root_jbd2 $(SCRIPTS)
+LDIRT = $(HELPTARGETS) domain.h $(VERSION_SCRIPT)
+
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) $(HELPTARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help $(HELPTARGETS) root root_jbd2 $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 root_jbd2 $(PCP_VAR_DIR)/pmns/root_jbd2
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(HELPTARGETS) : help
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_jbd2 -v 2 -o help < help
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+pmda.o: domain.h
+pmda.o proc_jbd2.o: convert.h
+pmda.o proc_jbd2.o: proc_jbd2.h
+pmda.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/jbd2/Install b/src/pmdas/jbd2/Install
new file mode 100755
index 0000000..4b1daea
--- /dev/null
+++ b/src/pmdas/jbd2/Install
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Linux JBD2 PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=jbd2
+pmda_interface=4
+forced_restart=false
+daemon_opt=true
+pipe_opt=true
+dso_opt=true
+pmns_source=root_jbd2
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/jbd2/Remove b/src/pmdas/jbd2/Remove
new file mode 100755
index 0000000..9e1158f
--- /dev/null
+++ b/src/pmdas/jbd2/Remove
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Linux JBD2 PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=jbd2
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/jbd2/convert.h b/src/pmdas/jbd2/convert.h
new file mode 100644
index 0000000..69fc51a
--- /dev/null
+++ b/src/pmdas/jbd2/convert.h
@@ -0,0 +1,30 @@
+/*
+ * Size conversion for different sized types extracted from the
+ * kernel on different platforms, particularly where the sizeof
+ * "long" differs.
+ *
+ * Copyright (c) 2007-2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Some metrics are exported by the kernel as "unsigned long".
+ * On most 64bit platforms this is not the same size as an
+ * "unsigned int".
+ */
+#if defined(HAVE_64BIT_LONG)
+#define KERNEL_ULONG PM_TYPE_U64
+#define _pm_assign_ulong(atomp, val) do { (atomp)->ull = (val); } while (0)
+#else
+#define KERNEL_ULONG PM_TYPE_U32
+#define _pm_assign_ulong(atomp, val) do { (atomp)->ul = (val); } while (0)
+#endif
diff --git a/src/pmdas/jbd2/help b/src/pmdas/jbd2/help
new file mode 100644
index 0000000..9cfada7
--- /dev/null
+++ b/src/pmdas/jbd2/help
@@ -0,0 +1,115 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# JBD2 PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ jbd2.njournals Count of active JBD2 (Journal Block Device v2) devices
+
+@ jbd2.transaction.count Total transactions committed per journal
+This metric is sourced from the per-device /proc/fs/jbd2 info file.
+
+@ jbd2.transaction.requested Total journal transactions requested per journal
+This metric is sourced from the per-device /proc/fs/jbd2 info file.
+
+@ jbd2.transaction.max_blocks Maximum transaction blocks (buffers) per journal
+This metric is sourced from the per-device /proc/fs/jbd2 info file.
+
+@ jbd2.transaction.average.time.waiting Average time waiting per journal
+Average time spent waiting for transactions to complete since mount.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.request_delay Average request delay per journal
+Average request delay for all transactions to complete since mount.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.running Average running time per journal
+Average transaction running time over all transactions since mount.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.being_locked Average locked time per journal
+Average transaction locked time over all transactions since mount.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.flushing_ordered_mode_data Average data flush time per journal
+Average time flushing data (ordered mode) for all transactions since
+mount. Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.logging Average logging time per journal
+Average time spent logging transactions for all transactions since
+mount. Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.time.committing Average commit time per journal
+Average time spent committing transactions for all transactions since
+mount. Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.blocks Average transaction blocks per journal
+Average number of blocks per transaction for all transactions.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.blocks_logged Average logged blocks per journal
+Average number of blocks logged per transaction for all transactions.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.average.handles Average handle count per journal
+Average number of handles used per transaction for all transactions.
+Exported directly from per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.waiting Total time waiting per journal
+Total time spent waiting for transactions to complete since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.request_delay Total request delay per journal
+Total request delay for all transactions to complete since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.running Total running time per journal
+Total transaction running time over all transactions since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.being_locked Total locked time per journal
+Total transaction locked time over all transactions since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.flushing_ordered_mode_data Total data flush time per journal
+Total time flushing data (ordered mode) for all transactions since
+mount. Derived from values in per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.time.logging Total logging time per journal
+Total time spent logging transactions for all transactions since
+mount. Derived from values in per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.blocks Total transaction blocks per journal
+Total number of blocks in all transactions since device mounted.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.blocks_logged Total logged blocks per journal
+Total number of blocks logged in all transactions since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
+
+@ jbd2.transaction.total.handles Total handle count per journal
+Total count of handles used in all transactions since mount.
+Derived from values in the per-device /proc/fs/jbd2 info files.
diff --git a/src/pmdas/jbd2/pmda.c b/src/pmdas/jbd2/pmda.c
new file mode 100644
index 0000000..3c2dcba
--- /dev/null
+++ b/src/pmdas/jbd2/pmda.c
@@ -0,0 +1,322 @@
+/*
+ * Journal Block Device v2 PMDA
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "convert.h"
+#include "proc_jbd2.h"
+
+static int _isDSO = 1; /* =0 I am a daemon */
+static char *prefix = "/proc/fs/jbd2";
+static char *username;
+
+#define JBD2_INDOM 0
+#define INDOM(i) (indomtab[i].it_indom)
+static pmdaIndom indomtab[] = {
+ { JBD2_INDOM, 0, NULL }, /* cached */
+};
+
+static pmdaMetric metrictab[] = {
+
+/* jbd2.njournals */
+ { NULL, { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* jbd2.transaction.count */
+ { NULL, { PMDA_PMID(0,1), KERNEL_ULONG, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.requested */
+ { NULL, { PMDA_PMID(0,2), KERNEL_ULONG, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.max_blocks */
+ { NULL, { PMDA_PMID(0,3), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* jbd2.transaction.total.time.waiting */
+ { NULL, { PMDA_PMID(0,4), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.time.request_delay */
+ { NULL, { PMDA_PMID(0,5), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.time.running */
+ { NULL, { PMDA_PMID(0,6), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.time.being_locked */
+ { NULL, { PMDA_PMID(0,7), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.time.flushing_ordered_mode_data */
+ { NULL, { PMDA_PMID(0,8), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.time.logging */
+ { NULL, { PMDA_PMID(0,9), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.total.blocks */
+ { NULL, { PMDA_PMID(0,10), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.total.blocks_logged */
+ { NULL, { PMDA_PMID(0,11), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.total.handles */
+ { NULL, { PMDA_PMID(0,12), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* jbd2.transaction.average.time.waiting */
+ { NULL, { PMDA_PMID(0,13), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.request_delay */
+ { NULL, { PMDA_PMID(0,14), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.running */
+ { NULL, { PMDA_PMID(0,15), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.being_locked */
+ { NULL, { PMDA_PMID(0,16), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.flushing_ordered_mode_data */
+ { NULL, { PMDA_PMID(0,17), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.logging */
+ { NULL, { PMDA_PMID(0,18), PM_TYPE_U32, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+/* jbd2.transaction.average.time.committing */
+ { NULL, { PMDA_PMID(0,19), PM_TYPE_U64, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) }, },
+/* jbd2.transaction.average.blocks */
+ { NULL, { PMDA_PMID(0,20), KERNEL_ULONG, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.average.blocks_logged */
+ { NULL, { PMDA_PMID(0,21), KERNEL_ULONG, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* jbd2.transaction.average.handles */
+ { NULL, { PMDA_PMID(0,22), KERNEL_ULONG, JBD2_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+};
+
+static int
+jbd2_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ refresh_jbd2(prefix, INDOM(JBD2_INDOM));
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+jbd2_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ refresh_jbd2(prefix, INDOM(JBD2_INDOM));
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+jbd2_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int sts;
+ proc_jbd2_t *jbd2;
+
+ switch (idp->cluster) {
+ case 0:
+ if (!idp->item) { /* jbd2.njournals */
+ atom->ul = pmdaCacheOp(INDOM(JBD2_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ break;
+ }
+
+ /* lookup the instance (journal device) */
+ sts = pmdaCacheLookup(INDOM(JBD2_INDOM), inst, NULL, (void **)&jbd2);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ if (jbd2->version < 2)
+ return 0;
+
+ switch (idp->item) {
+
+ case 1: /* transaction.count */
+ _pm_assign_ulong(atom, jbd2->tid);
+ break;
+ case 2: /* transaction.requested */
+ if (jbd2->version < 3)
+ return 0;
+ _pm_assign_ulong(atom, jbd2->requested);
+ break;
+ case 3: /* transaction.max_blocks */
+ atom->ul = jbd2->max_buffers;
+ break;
+
+ case 4: /* transaction.total.time.waiting */
+ atom->ull = jbd2->waiting * jbd2->tid;
+ break;
+ case 5: /* transaction.total.time.request_delay */
+ if (jbd2->version < 3)
+ return 0;
+ atom->ull = jbd2->request_delay * jbd2->requested;
+ break;
+ case 6: /* transaction.total.time.running */
+ atom->ull = jbd2->running * jbd2->tid;
+ break;
+ case 7: /* transaction.total.time.being_locked */
+ atom->ull = jbd2->locked * jbd2->tid;
+ break;
+ case 8: /* transaction.total.time.flushing_ordered_mode_data */
+ atom->ull = jbd2->flushing * jbd2->tid;
+ break;
+ case 9: /* transaction.total.time.logging */
+ atom->ull = jbd2->logging * jbd2->tid;
+ break;
+ case 10: /* transaction.total.blocks */
+ atom->ull = jbd2->blocks * jbd2->tid;
+ break;
+ case 11: /* transaction.total.blocks_logged */
+ atom->ull = jbd2->blocks_logged * jbd2->tid;
+ break;
+ case 12: /* transaction.total.handles */
+ atom->ull = jbd2->handles * jbd2->tid;
+ break;
+
+ case 13: /* transaction.average.time.waiting */
+ atom->ul = jbd2->waiting;
+ break;
+ case 14: /* transaction.total.time.request_delay */
+ if (jbd2->version < 3)
+ return 0;
+ atom->ul = jbd2->request_delay;
+ break;
+ case 15: /* transaction.total.time.running */
+ atom->ul = jbd2->running;
+ break;
+ case 16: /* transaction.total.time.being_locked */
+ atom->ul = jbd2->locked;
+ break;
+ case 17: /* transaction.total.time.flushing_ordered_mode_data */
+ atom->ul = jbd2->flushing;
+ break;
+ case 18: /* transaction.total.time.logging */
+ atom->ul = jbd2->logging;
+ break;
+ case 19: /* transaction.average.time.committing */
+ atom->ull = jbd2->average_commit_time;
+ break;
+ case 20: /* transaction.total.blocks */
+ _pm_assign_ulong(atom, jbd2->blocks);
+ break;
+ case 21: /* transaction.total.blocks_logged */
+ _pm_assign_ulong(atom, jbd2->blocks_logged);
+ break;
+ case 22: /* transaction.total.handles */
+ _pm_assign_ulong(atom, jbd2->handles);
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ default: /* unknown cluster */
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+__PMDA_INIT_CALL
+jbd2_init(pmdaInterface *dp)
+{
+ size_t nmetrics, nindoms;
+
+ if (_isDSO) {
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(helppath, sizeof(helppath), "%s%c" "jbd2" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_4, "jbd2 DSO", helppath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.instance = jbd2_instance;
+ dp->version.four.fetch = jbd2_fetch;
+ pmdaSetFetchCallBack(dp, jbd2_fetchCallBack);
+
+ nindoms = sizeof(indomtab)/sizeof(indomtab[0]);
+ nmetrics = sizeof(metrictab)/sizeof(metrictab[0]);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_DIRECT);
+ pmdaInit(dp, indomtab, nindoms, metrictab, nmetrics);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "", 1, 'j', "PATH", "path to stats files (default \"/proc/fs/jbd2\")" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:j:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int c, sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char help[MAXPATHLEN];
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(help, sizeof(help), "%s%c" "jbd2" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, JBD2, "jbd2.log", help);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &dispatch)) != EOF) {
+ switch(c) {
+ case 'j':
+ prefix = opts.optarg;
+ break;
+ }
+ }
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ jbd2_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/jbd2/proc_jbd2.c b/src/pmdas/jbd2/proc_jbd2.c
new file mode 100644
index 0000000..2a2692c
--- /dev/null
+++ b/src/pmdas/jbd2/proc_jbd2.c
@@ -0,0 +1,148 @@
+/*
+ * Linux JBD2 (ext3/ext4) driver metrics.
+ *
+ * Copyright (C) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "proc_jbd2.h"
+
+enum {
+ HEADER_STATS,
+ SEEKING_STATS,
+ AVERAGE_STATS,
+};
+
+static int
+refresh_journal(const char *path, const char *dev, pmInDom indom)
+{
+ int n, state, indom_changed = 0;
+ char buf[MAXPATHLEN], *id;
+ unsigned long long value;
+ proc_jbd2_t *jp;
+ FILE *fp;
+
+ if (dev[0] == '.')
+ return 0; /* only interest is in device files */
+ if (snprintf(buf, sizeof(buf), "%s/%s/info", path, dev) == sizeof(buf))
+ return 0; /* ignore, dodgey command line args */
+ if ((fp = fopen(buf, "r")) == NULL)
+ return 0; /* no permission, ignore this entry */
+
+ if (pmdaCacheLookupName(indom, dev, &n, (void **)&jp) < 0 || !jp) {
+ if ((jp = (proc_jbd2_t *)calloc(1, sizeof(proc_jbd2_t))) != NULL)
+ indom_changed++;
+ }
+ if (!jp) {
+ fclose(fp);
+ return 0;
+ }
+
+ state = HEADER_STATS; /* seeking the header, initially */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ switch (state) {
+ case HEADER_STATS:
+ if (sscanf(buf,
+ "%llu transactions (%llu requested), each up to %u blocks\n",
+ (unsigned long long *) &jp->tid,
+ (unsigned long long *) &jp->requested,
+ (unsigned int *) &jp->max_buffers) == 3) {
+ state = SEEKING_STATS;
+ jp->version = 3; /* 3.x kernel header format */
+ }
+ else if (sscanf(buf,
+ "%llu transaction, each up to %u blocks\n",
+ (unsigned long long *) &jp->tid,
+ (unsigned int *) &jp->max_buffers) == 2) {
+ state = SEEKING_STATS;
+ jp->version = 2; /* 2.x kernel header format */
+ }
+ break;
+
+ case SEEKING_STATS:
+ if (strncmp(buf, "average: \n", 8) == 0)
+ state = AVERAGE_STATS;
+ break;
+
+ case AVERAGE_STATS:
+ value = strtoull(buf, &id, 10);
+ if (id == buf)
+ continue;
+ else if (strcmp(id, "ms waiting for transaction\n") == 0)
+ jp->waiting = value;
+ else if (strcmp(id, "ms request delay\n") == 0)
+ jp->request_delay = value;
+ else if (strcmp(id, "ms running transaction\n") == 0)
+ jp->running = value;
+ else if (strcmp(id, "ms transaction was being locked\n") == 0)
+ jp->locked = value;
+ else if (strcmp(id, "ms flushing data (in ordered mode)\n") == 0)
+ jp->flushing = value;
+ else if (strcmp(id, "ms logging transaction\n") == 0)
+ jp->logging = value;
+ else if (strcmp(id, "us average transaction commit time\n") == 0)
+ jp->average_commit_time = value;
+ else if (strcmp(id, " handles per transaction\n") == 0)
+ jp->handles = value;
+ else if (strcmp(id, " blocks per transaction\n") == 0)
+ jp->blocks = value;
+ else if (strcmp(id, " logged blocks per transaction\n") == 0)
+ jp->blocks_logged = value;
+ break;
+
+ default:
+ break;
+ }
+ }
+ fclose(fp);
+
+ if (state != AVERAGE_STATS) {
+ if (indom_changed)
+ free(jp);
+ return 0;
+ }
+
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, dev, jp);
+ return indom_changed;
+}
+
+int
+refresh_jbd2(const char *path, pmInDom jbd2_indom)
+{
+ DIR *dirp;
+ struct dirent *dent;
+ int indom_changes = 0;
+ static int first = 1;
+
+ if (first) {
+ /* initialize the instance domain caches */
+ pmdaCacheOp(jbd2_indom, PMDA_CACHE_LOAD);
+ indom_changes = 1;
+ first = 0;
+ }
+
+ pmdaCacheOp(jbd2_indom, PMDA_CACHE_INACTIVE);
+ if ((dirp = opendir(path)) == NULL)
+ return -ENOENT;
+ while ((dent = readdir(dirp)) != NULL)
+ indom_changes |= refresh_journal(path, dent->d_name, jbd2_indom);
+ closedir(dirp);
+
+ if (indom_changes)
+ pmdaCacheOp(jbd2_indom, PMDA_CACHE_SAVE);
+ return 0;
+}
diff --git a/src/pmdas/jbd2/proc_jbd2.h b/src/pmdas/jbd2/proc_jbd2.h
new file mode 100644
index 0000000..fb9c825
--- /dev/null
+++ b/src/pmdas/jbd2/proc_jbd2.h
@@ -0,0 +1,38 @@
+/*
+ * Linux JBD2 (ext3/ext4) driver metrics.
+ *
+ * Copyright (C) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+typedef struct {
+ __uint32_t version;
+ __uint32_t max_buffers;
+
+ __uint64_t tid;
+ __uint64_t requested;
+
+ __uint64_t waiting;
+ __uint64_t request_delay;
+ __uint64_t running;
+ __uint64_t locked;
+ __uint64_t flushing;
+ __uint64_t logging;
+ __uint64_t average_commit_time;
+
+ __uint64_t handles;
+
+ __uint64_t blocks;
+ __uint64_t blocks_logged;
+} proc_jbd2_t;
+
+extern int refresh_jbd2(const char *path, pmInDom jbd2_indom);
diff --git a/src/pmdas/jbd2/root b/src/pmdas/jbd2/root
new file mode 100644
index 0000000..12878cb
--- /dev/null
+++ b/src/pmdas/jbd2/root
@@ -0,0 +1,6 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+#include "root_jbd2"
diff --git a/src/pmdas/jbd2/root_jbd2 b/src/pmdas/jbd2/root_jbd2
new file mode 100644
index 0000000..7cb10cd
--- /dev/null
+++ b/src/pmdas/jbd2/root_jbd2
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+root {
+ jbd2
+}
+
+jbd2 {
+ njournals 122:0:0
+ transaction
+}
+
+jbd2.transaction {
+ count 122:0:1
+ requested 122:0:2
+ max_blocks 122:0:3
+ total
+ average
+}
+
+jbd2.transaction.total {
+ time
+ blocks 122:0:10
+ blocks_logged 122:0:11
+ handles 122:0:12
+}
+
+jbd2.transaction.total.time {
+ waiting 122:0:4
+ request_delay 122:0:5
+ running 122:0:6
+ being_locked 122:0:7
+ flushing_ordered_mode_data 122:0:8
+ logging 122:0:9
+}
+
+jbd2.transaction.average {
+ time
+ blocks 122:0:20
+ blocks_logged 122:0:21
+ handles 122:0:22
+}
+
+jbd2.transaction.average.time {
+ waiting 122:0:13
+ request_delay 122:0:14
+ running 122:0:15
+ being_locked 122:0:16
+ flushing_ordered_mode_data 122:0:17
+ logging 122:0:18
+ committing 122:0:19
+}
diff --git a/src/pmdas/kvm/GNUmakefile b/src/pmdas/kvm/GNUmakefile
new file mode 100644
index 0000000..6fd0b6a
--- /dev/null
+++ b/src/pmdas/kvm/GNUmakefile
@@ -0,0 +1,56 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = kvm
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+else
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/kvm/Install b/src/pmdas/kvm/Install
new file mode 100755
index 0000000..3317662
--- /dev/null
+++ b/src/pmdas/kvm/Install
@@ -0,0 +1,40 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the KVM PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=kvm
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+if ! test -d /sys/kernel/debug; then
+ echo "DEBUGFS not enabled in your kernel"
+ exit 1
+fi
+if ! test -d /sys/kernel/debug/kvm; then
+ echo "KVM statistics unavailable (load kvm module and mount debugfs)"
+ exit 1
+fi
+if ! test -r /sys/kernel/debug/kvm; then
+ forced_restart=true
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/kvm/Remove b/src/pmdas/kvm/Remove
new file mode 100755
index 0000000..1032be0
--- /dev/null
+++ b/src/pmdas/kvm/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the KVM PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=kvm
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/kvm/pmdakvm.pl b/src/pmdas/kvm/pmdakvm.pl
new file mode 100644
index 0000000..6eb7419
--- /dev/null
+++ b/src/pmdas/kvm/pmdakvm.pl
@@ -0,0 +1,122 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda );
+my $kvm_path = '/sys/kernel/debug/kvm';
+
+sub kvm_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ($path, $value, $fh);
+
+ # $pmda->log("kvm_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+ $path = $metric_name;
+ $path =~ s/^kvm\./$kvm_path\//;
+ open($fh, $path) || return (PM_ERR_APPVERSION, 0);
+ $value = <$fh>;
+ close $fh;
+
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ return ($value, 1);
+}
+
+$pmda = PCP::PMDA->new('kvm', 95);
+$pmda->connect_pmcd;
+
+# May need to be root to read the directory $kvm_path (/sys/kernel/debug/kvm)
+# and so
+# (a) do not use $pmda->set_user('pcp') below, and
+# (b) need forced_restart=true in the Install script so pmcd is restarted
+# and we're running as root at this point (SIGHUP pmcd once it has
+# changed to user "pcp" is not going to work for PMDA installation)
+#
+my $pmid = 0;
+opendir(DIR, $kvm_path) || $pmda->err("pmdakvm failed to open $kvm_path: $!");
+my @metrics = grep {
+ unless (/^\./) {
+ $pmda->add_metric(pmda_pmid(0,$pmid++), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "kvm.$_", '', '');
+ # $pmda->log("pmdakvm added metric kvm.$_\n");
+ }
+} readdir(DIR);
+closedir DIR;
+
+$pmda->set_fetch_callback(\&kvm_fetch_callback);
+# Careful with permissions - may need to be root to read /sys/kernel/debug/kvm
+# see note above.
+#$pmda->set_user('pcp') if -r $kvm_path;
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdakvm - Linux virtualisation performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdakvm> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from the Linux KVM virtualisation subsystem.
+
+Unlike many PMDAs it dynamically enumerates its metric hierarchy,
+based entirely on the contents of /sys/kernel/debug/kvm.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the kvm performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/kvm
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/kvm
+ # ./Remove
+
+B<pmdakvm> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/kvm/Install
+
+installation script for the B<pmdakvm> agent
+
+=item $PCP_PMDAS_DIR/kvm/Remove
+
+undo installation script for the B<pmdakvm> agent
+
+=item $PCP_LOG_DIR/pmcd/kvm.log
+
+default log file for error messages from B<pmdakvm>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and kvm(1).
diff --git a/src/pmdas/linux/GNUmakefile b/src/pmdas/linux/GNUmakefile
new file mode 100644
index 0000000..4a0cc4f
--- /dev/null
+++ b/src/pmdas/linux/GNUmakefile
@@ -0,0 +1,128 @@
+#
+# Copyright (c) 2000,2003,2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2007-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = linux
+DOMAIN = LINUX
+CMDTARGET = pmdalinux
+LIBTARGET = pmda_linux.so
+PMDAINIT = linux_init
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LOGREWRITEDIR = $(PCP_VAR_DIR)/config/pmlogrewrite
+CONF_LINE = "linux 60 dso $(PMDAINIT) $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = pmda.c \
+ proc_stat.c proc_meminfo.c proc_loadavg.c \
+ proc_net_dev.c interrupts.c filesys.c \
+ swapdev.c devmapper.c proc_net_rpc.c proc_partitions.c \
+ getinfo.c proc_net_sockstat.c proc_net_snmp.c \
+ proc_scsi.c proc_cpuinfo.c proc_net_tcp.c \
+ proc_slabinfo.c sem_limits.c msg_limits.c shm_limits.c \
+ proc_uptime.c proc_sys_fs.c proc_vmstat.c \
+ sysfs_kernel.c linux_table.c numa_meminfo.c \
+ proc_net_netstat.c
+
+HFILES = clusters.h indom.h convert.h \
+ proc_stat.h proc_meminfo.h proc_loadavg.h \
+ proc_net_dev.h interrupts.h filesys.h \
+ swapdev.h devmapper.h proc_net_rpc.h proc_partitions.h \
+ getinfo.h proc_net_sockstat.h proc_net_snmp.h \
+ proc_scsi.h proc_cpuinfo.h proc_net_tcp.h \
+ proc_slabinfo.h sem_limits.h msg_limits.h shm_limits.h \
+ proc_uptime.h proc_sys_fs.h proc_vmstat.h \
+ sysfs_kernel.h linux_table.h numa_meminfo.h \
+ proc_net_netstat.h
+
+VERSION_SCRIPT = exports
+HELPTARGETS = help.dir help.pag
+LSRCFILES = help root_linux proc_net_snmp_migrate.conf
+LDIRT = $(HELPTARGETS) domain.h $(VERSION_SCRIPT)
+
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+
+# Uncomment these flags for profiling
+# LCFLAGS += -pg
+# LLDFLAGS += -pg
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) $(HELPTARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help $(HELPTARGETS) $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_linux $(PCP_VAR_DIR)/pmns/root_linux
+ $(INSTALL) -m 644 proc_net_snmp_migrate.conf $(LOGREWRITEDIR)/linux_proc_net_snmp_migrate.conf
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(HELPTARGETS) : help
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_linux -v 2 -o help < help
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+interrupts.o pmda.o proc_partitions.o: clusters.h
+pmda.o proc_partitions.o: convert.h
+pmda.o: domain.h
+filesys.o interrupts.o pmda.o: filesys.h
+pmda.o: getinfo.h
+pmda.o devmapper.o: devmapper.h
+numa_meminfo.o pmda.o proc_cpuinfo.o proc_partitions.o proc_stat.o: indom.h
+interrupts.o pmda.o: interrupts.h
+linux_table.o numa_meminfo.o pmda.o: linux_table.h
+msg_limits.o pmda.o: msg_limits.h
+numa_meminfo.o pmda.o: numa_meminfo.h
+pmda.o proc_cpuinfo.o proc_stat.o: proc_cpuinfo.h
+pmda.o proc_loadavg.o: proc_loadavg.h
+pmda.o proc_meminfo.o: proc_meminfo.h
+pmda.o proc_net_dev.o: proc_net_dev.h
+pmda.o proc_net_netstat.o: proc_net_netstat.h
+pmda.o proc_net_rpc.o: proc_net_rpc.h
+pmda.o proc_net_snmp.o: proc_net_snmp.h
+pmda.o proc_net_sockstat.o: proc_net_sockstat.h
+pmda.o proc_net_tcp.o: proc_net_tcp.h
+pmda.o proc_partitions.o: proc_partitions.h
+pmda.o proc_scsi.o: proc_scsi.h
+pmda.o proc_slabinfo.o: proc_slabinfo.h
+pmda.o proc_stat.o: proc_stat.h
+pmda.o proc_sys_fs.o: proc_sys_fs.h
+pmda.o proc_uptime.o: proc_uptime.h
+pmda.o proc_vmstat.o: proc_vmstat.h
+pmda.o sem_limits.o: sem_limits.h
+pmda.o shm_limits.o: shm_limits.h
+pmda.o swapdev.o: swapdev.h
+pmda.o sysfs_kernel.o: sysfs_kernel.h
+pmda.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/linux/clusters.h b/src/pmdas/linux/clusters.h
new file mode 100644
index 0000000..0a1fdfe
--- /dev/null
+++ b/src/pmdas/linux/clusters.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _CLUSTERS_H
+#define _CLUSTERS_H
+
+/*
+ * fetch cluster numbers
+ */
+enum {
+ CLUSTER_STAT = 0, /* 0 /proc/stat */
+ CLUSTER_MEMINFO, /* 1 /proc/meminfo */
+ CLUSTER_LOADAVG, /* 2 /proc/loadavg */
+ CLUSTER_NET_DEV, /* 3 /proc/net/dev */
+ CLUSTER_INTERRUPTS, /* 4 /proc/interrupts */
+ CLUSTER_FILESYS, /* 5 /proc/mounts + statfs */
+ CLUSTER_SWAPDEV, /* 6 /proc/swaps */
+ CLUSTER_NET_NFS, /* 7 /proc/net/rpc/nfs + /proc/net/rpc/nfsd */
+ PROC_PID_STAT, /* 8 /proc/<pid>/stat -> proc PMDA */
+ PROC_PID_STATM, /* 9 /proc/<pid>/statm + /proc/<pid>/maps -> proc PMDA */
+ CLUSTER_PARTITIONS, /* 10 /proc/partitions */
+ CLUSTER_NET_SOCKSTAT, /* 11 /proc/net/sockstat */
+ CLUSTER_KERNEL_UNAME, /* 12 uname() system call */
+ PROC_PROC_RUNQ, /* 13 number of processes in various states -> proc PMDA */
+ CLUSTER_NET_SNMP, /* 14 /proc/net/snmp */
+ CLUSTER_SCSI, /* 15 /proc/scsi/scsi */
+ CLUSTER_XFS, /* 16 /proc/fs/xfs/stat -> xfs PMDA */
+ CLUSTER_XFSBUF, /* 17 /proc/fs/pagebuf/stat -> xfs PMDA */
+ CLUSTER_CPUINFO, /* 18 /proc/cpuinfo */
+ CLUSTER_NET_TCP, /* 19 /proc/net/tcp */
+ CLUSTER_SLAB, /* 20 /proc/slabinfo */
+ CLUSTER_SEM_LIMITS, /* 21 semctl(IPC_INFO) system call */
+ CLUSTER_MSG_LIMITS, /* 22 msgctl(IPC_INFO) system call */
+ CLUSTER_SHM_LIMITS, /* 23 shmctl(IPC_INFO) system call */
+ PROC_PID_STATUS, /* 24 /proc/<pid>/status -> proc PMDA */
+ CLUSTER_NUSERS, /* 25 number of users */
+ CLUSTER_UPTIME, /* 26 /proc/uptime */
+ CLUSTER_VFS, /* 27 /proc/sys/fs */
+ CLUSTER_VMSTAT, /* 28 /proc/vmstat */
+ CLUSTER_IB, /* deprecated: do not re-use 29 infiniband */
+ CLUSTER_QUOTA, /* 30 quotactl() -> xfs PMDA */
+ PROC_PID_SCHEDSTAT, /* 31 /proc/<pid>/schedstat -> proc PMDA */
+ PROC_PID_IO, /* 32 /proc/<pid>/io -> proc PMDA */
+ CLUSTER_NET_ADDR, /* 33 /proc/net/dev and ioctl(SIOCGIFCONF) */
+ CLUSTER_TMPFS, /* 34 /proc/mounts + statfs (tmpfs only) */
+ CLUSTER_SYSFS_KERNEL, /* 35 /sys/kernel metrics */
+ CLUSTER_NUMA_MEMINFO, /* 36 /sys/devices/system/node* NUMA memory */
+ PROC_CGROUP_SUBSYS, /* 37 /proc/cgroups control group subsystems -> proc PMDA */
+ PROC_CGROUP_MOUNTS, /* 38 /proc/mounts active control groups -> proc PMDA */
+ PROC_CPUSET_GROUPS, /* 39 cpuset control groups -> proc PMDA */
+ PROC_CPUSET_PROCS, /* 40 cpuset control group processes -> proc PMDA */
+ PROC_CPUACCT_GROUPS, /* 41 cpu accounting control groups -> proc PMDA */
+ PROC_CPUACCT_PROCS, /* 42 cpu accounting group processes -> proc PMDA */
+ PROC_CPUSCHED_GROUPS, /* 43 scheduler control groups -> proc PMDA */
+ PROC_CPUSCHED_PROCS, /* 44 scheduler group processes -> proc PMDA */
+ PROC_MEMORY_GROUPS, /* 45 memory control groups -> proc PMDA */
+ PROC_MEMORY_PROCS, /* 46 memory group processes -> proc PMDA */
+ PROC_NET_CLS_GROUPS, /* 47 network classification control groups -> proc PMDA */
+ PROC_NET_CLS_PROCS, /* 48 network classification group processes -> proc PMDA */
+ CLUSTER_INTERRUPT_LINES,/* 49 /proc/interrupts percpu interrupts */
+ CLUSTER_INTERRUPT_OTHER,/* 50 /proc/interrupts percpu interrupts */
+ PROC_PID_FD, /* 51 /proc/<pid>/fd -> proc PMDA */
+ CLUSTER_LV, /* 52 /dev/mapper */
+ CLUSTER_NET_NETSTAT, /* 53 /proc/net/netstat */
+ CLUSTER_DM, /* 54 disk.dm.* */
+
+ NUM_CLUSTERS /* one more than highest numbered cluster */
+};
+
+#endif /* _CLUSTERS_H */
diff --git a/src/pmdas/linux/convert.h b/src/pmdas/linux/convert.h
new file mode 100644
index 0000000..8a31eda
--- /dev/null
+++ b/src/pmdas/linux/convert.h
@@ -0,0 +1,49 @@
+/*
+ * Size conversion for different sized types extracted from the
+ * kernel on different platforms, particularly where the sizeof
+ * "long" differs.
+ *
+ * Copyright (c) 2007-2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Some metrics are exported by the kernel as "unsigned long".
+ * On most 64bit platforms this is not the same size as an
+ * "unsigned int".
+ */
+#if defined(HAVE_64BIT_LONG)
+#define KERNEL_ULONG PM_TYPE_U64
+#define _pm_assign_ulong(atomp, val) do { (atomp)->ull = (val); } while (0)
+#else
+#define KERNEL_ULONG PM_TYPE_U32
+#define _pm_assign_ulong(atomp, val) do { (atomp)->ul = (val); } while (0)
+#endif
+
+/*
+ * Some metrics need to have their type set at runtime, based on the
+ * running kernel version (not simply a 64 vs 32 bit machine issue).
+ */
+#define KERNEL_UTYPE PM_TYPE_NOSUPPORT /* set to real type at runtime */
+#define _pm_metric_type(type, size) \
+ do { \
+ (type) = ((size)==8 ? PM_TYPE_U64 : PM_TYPE_U32); \
+ } while (0)
+#define _pm_assign_utype(size, atomp, val) \
+ do { \
+ if ((size)==8) { (atomp)->ull = (val); } else { (atomp)->ul = (val); } \
+ } while (0)
+
diff --git a/src/pmdas/linux/devmapper.c b/src/pmdas/linux/devmapper.c
new file mode 100644
index 0000000..38f87d3
--- /dev/null
+++ b/src/pmdas/linux/devmapper.c
@@ -0,0 +1,86 @@
+/*
+ * Linux LVM Devices Cluster
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "devmapper.h"
+
+int
+refresh_dev_mapper(dev_mapper_t *lvs)
+{
+ int i;
+ DIR *dirp;
+ struct dirent *dentry;
+ struct stat statbuf;
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "%s/dev/mapper", linux_statspath);
+ if ((dirp = opendir(path)) == NULL)
+ return 1;
+
+ for (i = 0; i < lvs->nlv; i++) {
+ free(lvs->lv[i].dev_name);
+ free(lvs->lv[i].lv_name);
+ }
+ lvs->nlv = 0;
+ lvs->lv = NULL;
+ while ((dentry = readdir(dirp)) != NULL) {
+ char linkname[MAXPATHLEN];
+ int linkname_len;
+
+ snprintf(path, sizeof(path),
+ "%s/dev/mapper/%s", linux_statspath, dentry->d_name);
+
+ if (stat(path, &statbuf) == -1)
+ continue;
+ if (!S_ISBLK(statbuf.st_mode))
+ continue;
+
+ if ((linkname_len = readlink(path, linkname, sizeof(linkname)-1)) < 0)
+ continue;
+ linkname[linkname_len] = '\0';
+
+ i = lvs->nlv;
+ lvs->nlv++;
+
+ lvs->lv = (lv_entry_t *)realloc(lvs->lv, lvs->nlv * sizeof(lv_entry_t));
+ lvs->lv[i].id = lvs->nlv;
+
+ lvs->lv[i].dev_name = malloc(strlen(dentry->d_name)+1);
+ strcpy(lvs->lv[i].dev_name, dentry->d_name);
+
+ lvs->lv[i].lv_name = malloc(linkname_len+1);
+ strcpy(lvs->lv[i].lv_name, linkname);
+ }
+ closedir(dirp);
+
+ if (lvs->lv_indom->it_numinst != lvs->nlv) {
+ lvs->lv_indom->it_numinst = lvs->nlv;
+ lvs->lv_indom->it_set = (pmdaInstid *)
+ realloc(lvs->lv_indom->it_set, lvs->nlv * sizeof(pmdaInstid));
+ }
+ for (i = 0; i < lvs->nlv; i++) {
+ int skip_prefix = 0;
+ lvs->lv_indom->it_set[i].i_inst = lvs->lv[i].id;
+ if (strncmp(lvs->lv[i].lv_name, "../", 3) == 0)
+ skip_prefix = 3;
+ lvs->lv_indom->it_set[i].i_name = lvs->lv[i].lv_name + skip_prefix;
+ }
+ return 0;
+}
diff --git a/src/pmdas/linux/devmapper.h b/src/pmdas/linux/devmapper.h
new file mode 100644
index 0000000..7b75281
--- /dev/null
+++ b/src/pmdas/linux/devmapper.h
@@ -0,0 +1,29 @@
+/*
+ * Linux /dev/mapper metrics cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+typedef struct {
+ int id; /* internal instance id */
+ char *dev_name;
+ char *lv_name;
+} lv_entry_t;
+
+typedef struct {
+ int nlv;
+ lv_entry_t *lv;
+ pmdaIndom *lv_indom;
+} dev_mapper_t;
+
+extern int refresh_dev_mapper(dev_mapper_t *);
diff --git a/src/pmdas/linux/filesys.c b/src/pmdas/linux/filesys.c
new file mode 100644
index 0000000..3c06c13
--- /dev/null
+++ b/src/pmdas/linux/filesys.c
@@ -0,0 +1,122 @@
+/*
+ * Linux Filesystem Cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "filesys.h"
+
+char *
+scan_filesys_options(const char *options, const char *option)
+{
+ static char buffer[128];
+ char *s;
+
+ strncpy(buffer, options, sizeof(buffer));
+ buffer[sizeof(buffer)-1] = '\0';
+
+ s = strtok(buffer, ",");
+ while (s) {
+ if (strcmp(s, option) == 0)
+ return s;
+ s = strtok(NULL, ",");
+ }
+ return NULL;
+}
+
+int
+refresh_filesys(pmInDom filesys_indom, pmInDom tmpfs_indom)
+{
+ char buf[MAXPATHLEN];
+ char realdevice[MAXPATHLEN];
+ filesys_t *fs;
+ pmInDom indom;
+ FILE *fp;
+ char *path, *device, *type, *options;
+ int sts;
+
+ pmdaCacheOp(tmpfs_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(filesys_indom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = linux_statsfile("/proc/mounts", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((device = strtok(buf, " ")) == 0)
+ continue;
+
+ path = strtok(NULL, " ");
+ type = strtok(NULL, " ");
+ options = strtok(NULL, " ");
+ if (strcmp(type, "proc") == 0 ||
+ strcmp(type, "nfs") == 0 ||
+ strcmp(type, "devfs") == 0 ||
+ strcmp(type, "devpts") == 0 ||
+ strcmp(type, "cgroup") == 0 ||
+ strncmp(type, "auto", 4) == 0)
+ continue;
+
+ indom = filesys_indom;
+ if (strcmp(type, "tmpfs") == 0) {
+ indom = tmpfs_indom;
+ device = path;
+ }
+ else if (strncmp(device, "/dev", 4) != 0)
+ continue;
+ if (realpath(device, realdevice) != NULL)
+ device = realdevice;
+
+ sts = pmdaCacheLookupName(indom, device, NULL, (void **)&fs);
+ if (sts == PMDA_CACHE_ACTIVE) /* repeated line in /proc/mounts? */
+ continue;
+ if (sts == PMDA_CACHE_INACTIVE) { /* re-activate an old mount */
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, device, fs);
+ if (strcmp(path, fs->path) != 0) { /* old device, new path */
+ free(fs->path);
+ fs->path = strdup(path);
+ }
+ if (strcmp(options, fs->options) != 0) { /* old device, new opts */
+ free(fs->options);
+ fs->options = strdup(options);
+ }
+ }
+ else { /* new mount */
+ if ((fs = malloc(sizeof(filesys_t))) == NULL)
+ continue;
+ fs->device = strdup(device);
+ fs->path = strdup(path);
+ fs->options = strdup(options);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_filesys: add \"%s\" \"%s\"\n",
+ fs->path, device);
+ }
+#endif
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, device, fs);
+ }
+ fs->flags = 0;
+ }
+
+ /*
+ * success
+ * Note: we do not call statfs() here since only some instances
+ * may be requested (rather, we do it in linux_fetch, see pmda.c).
+ */
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/filesys.h b/src/pmdas/linux/filesys.h
new file mode 100644
index 0000000..d90bdf7
--- /dev/null
+++ b/src/pmdas/linux/filesys.h
@@ -0,0 +1,32 @@
+/*
+ * Linux Filesystem Cluster
+ *
+ * Copyright (c) 2000,2004,2007 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/vfs.h>
+
+/* Values for flags in filesys_t */
+#define FSF_FETCHED (1U << 0)
+
+typedef struct filesys {
+ int id;
+ unsigned int flags;
+ char *device;
+ char *path;
+ char *options;
+ struct statfs stats;
+} filesys_t;
+
+extern int refresh_filesys(pmInDom, pmInDom);
+extern char *scan_filesys_options(const char *, const char *);
diff --git a/src/pmdas/linux/getinfo.c b/src/pmdas/linux/getinfo.c
new file mode 100644
index 0000000..d5f7f29
--- /dev/null
+++ b/src/pmdas/linux/getinfo.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include <sys/dir.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+
+char *
+get_distro_info(void)
+{
+ /*
+ * Heuristic guesswork ... add code here as we learn
+ * more about how to identify each Linux distribution.
+ */
+ static char *distro_name;
+ struct stat sbuf;
+ int r, sts, fd = -1, len = 0;
+ char path[MAXPATHLEN];
+ char prefix[16];
+ enum { /* rfiles array offsets */
+ DEB_VERSION = 0,
+ LSB_RELEASE = 6,
+ };
+ char *rfiles[] = { "debian_version", "oracle-release", "fedora-release",
+ "redhat-release", "slackware-version", "SuSE-release", "lsb-release",
+ /* insert any new distribution release variants here */
+ NULL
+ };
+
+ if (distro_name)
+ return distro_name;
+
+ for (r = 0; rfiles[r] != NULL; r++) {
+ snprintf(path, sizeof(path), "%s/etc/%s", linux_statspath, rfiles[r]);
+ if (stat(path, &sbuf) == 0 && S_ISREG(sbuf.st_mode)) {
+ fd = open(path, O_RDONLY);
+ break;
+ }
+ }
+ if (fd != -1) {
+ if (r == DEB_VERSION) { /* Debian, needs a prefix */
+ strncpy(prefix, "Debian ", sizeof(prefix));
+ len = 7;
+ }
+ /*
+ * at this point, assume sbuf is good and file contains
+ * the string we want, probably with a \n terminator
+ */
+ distro_name = (char *)malloc(len + (int)sbuf.st_size + 1);
+ if (distro_name != NULL) {
+ if (len)
+ strncpy(distro_name, prefix, len);
+ sts = read(fd, distro_name + len, (int)sbuf.st_size);
+ if (sts <= 0) {
+ free(distro_name);
+ distro_name = NULL;
+ } else {
+ char *nl;
+
+ if (r == LSB_RELEASE) { /* may be Ubuntu */
+ if (!strncmp(distro_name, "DISTRIB_ID = ", 13))
+ distro_name += 13; /* ick */
+ if (!strncmp(distro_name, "DISTRIB_ID=", 11))
+ distro_name += 11; /* more ick */
+ }
+ distro_name[sts + len] = '\0';
+ if ((nl = strchr(distro_name, '\n')) != NULL)
+ *nl = '\0';
+ }
+ }
+ close(fd);
+ }
+ if (distro_name == NULL)
+ distro_name = "?";
+ return distro_name;
+}
diff --git a/src/pmdas/linux/getinfo.h b/src/pmdas/linux/getinfo.h
new file mode 100644
index 0000000..0bf170d
--- /dev/null
+++ b/src/pmdas/linux/getinfo.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+extern char *get_distro_info(void);
+
diff --git a/src/pmdas/linux/help b/src/pmdas/linux/help
new file mode 100644
index 0000000..63166cf
--- /dev/null
+++ b/src/pmdas/linux/help
@@ -0,0 +1,1122 @@
+#
+# Copyright (c) 2000,2004-2008 Silicon Graphics, Inc. All Rights Reserved.
+# Portions Copyright (c) International Business Machines Corp., 2002
+# Portions Copyright (c) 2007-2009 Aconex. All Rights Reserved.
+# Portions Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Linux PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+@ kernel.uname.release release level of the running kernel
+Release level of the running kernel as reported via the release[]
+value returned from uname(2) or uname -r.
+
+See also pmda.uname.
+
+@ kernel.uname.version version level (build number) and build date of the running kernel
+Version level of the running kernel as reported by the version[]
+value returned from uname(2) or uname -v. Usually a build number
+followed by a build date.
+
+See also pmda.uname.
+
+@ kernel.uname.sysname name of the implementation of the operating system
+Name of the implementation of the running operating system as reported
+by the sysname[] value returned from uname(2) or uname -s. Usually
+"Linux".
+
+See also pmda.uname.
+
+@ kernel.uname.machine name of the hardware type the system is running on
+Name of the hardware type the system is running on as reported by the machine[]
+value returned from uname(2) or uname -m, e.g. "i686".
+
+See also pmda.uname.
+
+@ kernel.uname.nodename host name of this node on the network
+Name of this node on the network as reported by the nodename[]
+value returned from uname(2) or uname -n. Usually a synonym for
+the host name.
+
+See also pmda.uname.
+
+@ kernel.uname.distro Linux distribution name
+The Linux distribution name, as determined by a number of heuristics.
+For example:
++ on Fedora, the contents of /etc/fedora-release
++ on RedHat, the contents of /etc/redhat-release
+
+@ kernel.percpu.cpu.user percpu user CPU time metric from /proc/stat, including guest CPU time
+@ kernel.percpu.cpu.vuser percpu user CPU time metric from /proc/stat, excluding guest CPU time
+@ kernel.percpu.cpu.nice percpu nice user CPU time metric from /proc/stat
+@ kernel.percpu.cpu.sys percpu sys CPU time metric from /proc/stat
+@ kernel.percpu.cpu.idle percpu idle CPU time metric from /proc/stat
+@ kernel.percpu.cpu.wait.total percpu wait CPU time
+Per-CPU I/O wait CPU time - time spent with outstanding I/O requests.
+
+@ kernel.percpu.cpu.intr percpu interrupt CPU time
+Total time spent processing interrupts on each CPU (this includes
+both soft and hard interrupt processing time).
+
+@ kernel.percpu.cpu.irq.soft percpu soft interrupt CPU time
+Per-CPU soft interrupt CPU time (deferred interrupt handling code,
+not run in the initial interrupt handler).
+
+@ kernel.percpu.cpu.irq.hard percpu hard interrupt CPU time
+Per-CPU hard interrupt CPU time ("hard" interrupt handling code
+is the code run directly on receipt of the initial hardware
+interrupt, and does not include "soft" interrupt handling code
+which is deferred until later).
+
+@ kernel.percpu.cpu.steal percpu CPU steal time
+Per-CPU time when the CPU had a runnable process, but the hypervisor
+(virtualisation layer) chose to run something else instead.
+
+@ kernel.percpu.cpu.guest percpu guest CPU time
+Per-CPU time spent running (virtual) guest operating systems.
+
+@ kernel.all.interrupts.errors interrupt error count from /proc/interrupts
+This is a global counter (normally converted to a count/second)
+for any and all errors that occur while handling interrupts.
+
+@ disk.dev.read per-disk read operations
+Cumulative number of disk read operations since system boot time (subject
+to counter wrap).
+
+@ disk.dev.write per-disk write operations
+Cumulative number of disk write operations since system boot time (subject
+to counter wrap).
+
+@ disk.dev.total per-disk total (read+write) operations
+Cumulative number of disk read and write operations since system boot
+time (subject to counter wrap).
+
+@ disk.dev.blkread per-disk block read operations
+Cumulative number of disk block read operations since system boot time
+(subject to counter wrap).
+
+@ disk.dev.blkwrite per-disk block write operations
+Cumulative number of disk block write operations since system boot time
+(subject to counter wrap).
+
+@ disk.dev.blktotal per-disk total (read+write) block operations
+Cumulative number of disk block read and write operations since system
+boot time (subject to counter wrap).
+
+@ disk.dev.read_bytes per-disk count of bytes read
+@ disk.dev.write_bytes per-disk count of bytes written
+@ disk.dev.total_bytes per-disk count of total bytes read and written
+
+@ disk.dev.scheduler per-disk I/O scheduler
+The name of the I/O scheduler in use for each device. The scheduler
+is part of the block layer in the kernel, and attempts to optimise the
+I/O submission patterns using various techniques (typically, sorting
+and merging adjacent requests into larger ones to reduce seek activity,
+but certainly not limited to that).
+
+@ disk.dev.avactive per-disk count of active time
+When converted to a rate, this metric represents the average utilization of
+the disk during the sampling interval. A value of 0.5 (or 50%) means the
+disk was active (i.e. busy) half the time.
+
+@ disk.dev.read_rawactive per-disk raw count of active read time
+When converted to a rate, this metric represents the raw utilization of
+the disk during the sampling interval as a result of reads. Accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the results from existing performance tools:
+
+ iostat.dev.r_await = delta(disk.dev.read_rawactive) / delta(disk.dev.read)
+
+@ disk.dev.write_rawactive per-disk raw count of active write time
+When converted to a rate, this metric represents the raw utilization of
+the disk during the sampling interval as a result of writes. Accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the results from existing performance tools:
+
+ iostat.dev.w_await = delta(disk.dev.write_rawactive) / delta(disk.dev.write)
+
+@ disk.dev.aveq per-disk time averaged count of request queue length
+When converted to a rate, this metric represents the time averaged disk
+request queue length during the sampling interval. A value of 2.5 (or 250%)
+represents a time averaged queue length of 2.5 requests during the sampling
+interval.
+
+@ disk.dev.read_merge per-disk count of merged read requests
+Count of read requests that were merged with an already queued read request.
+
+@ disk.dev.write_merge per-disk count of merged write requests
+Count of write requests that were merged with an already queued write request.
+
+@ disk.dm.read per-device-mapper device read operations
+
+@ disk.dm.write per-device-mapper device write operations
+
+@ disk.dm.total per-device-mapper device total (read+write) operations
+
+@ disk.dm.blkread per-device-mapper device block read operations
+
+@ disk.dm.blkwrite per-device-mapper device block write operations
+
+@ disk.dm.blktotal per-device-mapper device total (read+write) block operations
+
+@ disk.dm.read_bytes per-device-mapper device count of bytes read
+
+@ disk.dm.write_bytes per-device-mapper device count of bytes written
+
+@ disk.dm.total_bytes per-device-mapper device count of total bytes read and written
+
+@ disk.dm.read_merge per-device-mapper device count of merged read requests
+
+@ disk.dm.write_merge per-device-mapper device count of merged write requests
+
+@ disk.dm.avactive per-device-mapper device count of active time
+
+@ disk.dm.aveq per-device-mapper device time averaged count of request queue length
+
+@ disk.dm.read_rawactive per-device-mapper raw count of active read time
+When converted to a rate, this metric represents the raw utilization of
+the device during the sampling interval as a result of reads. Accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the results from existing performance tools:
+
+ iostat.dm.r_await = delta(disk.dm.read_rawactive) / delta(disk.dm.read)
+
+@ disk.dm.write_rawactive per-device-mapper raw count of active write time
+When converted to a rate, this metric represents the raw utilization of
+the device during the sampling interval as a result of writes. Accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the results from existing performance tools:
+
+ iostat.dm.w_await = delta(disk.dm.write_rawactive) / delta(disk.dm.write)
+
+@ hinv.map.dmname per-device-mapper device persistent name mapping to dm-[0-9]*
+
+@ disk.all.read_merge total count of merged read requests, summed for all disks
+Total count of read requests that were merged with an already queued read request.
+
+@ disk.all.write_merge total count of merged write requests, summed for all disks
+Total count of write requests that were merged with an already queued write request.
+
+@ disk.all.avactive total count of active time, summed for all disks
+When converted to a rate, this metric represents the average utilization of
+all disks during the sampling interval. A value of 0.25 (or 25%) means that
+on average every disk was active (i.e. busy) one quarter of the time.
+
+@ disk.all.read_rawactive raw count of active read time, summed for all disks
+When converted to a rate, this metric represents the raw utilization of all
+disks during the sampling interval due to read requests. The accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the results from existing performance tools:
+
+ iostat.all.r_await = delta(disk.all.read_rawactive) / delta(disk.all.read)
+
+@ disk.all.write_rawactive raw count of active write time, summed for all disks
+When converted to a rate, this metric represents the raw utilization of all
+disks during the sampling interval due to write requests. The accounting for
+this metric is only done on I/O completion and can thus result in more than a
+second's worth of IO being accounted for within any one second, leading to
+>100% utilisation. It is suitable mainly for use in calculations with other
+metrics, e.g. mirroring the result from existing performance tools:
+
+ iostat.all.w_await = delta(disk.all.write_rawactive) / delta(disk.all.write)
+
+@ disk.all.aveq total time averaged count of request queue length, summed for all disks
+When converted to a rate, this metric represents the average across all disks
+of the time averaged request queue length during the sampling interval. A
+value of 1.5 (or 150%) suggests that (on average) each all disk experienced a
+time averaged queue length of 1.5 requests during the sampling interval.
+
+@ disk.all.read total read operations, summed for all disks
+Cumulative number of disk read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.write total write operations, summed for all disks
+Cumulative number of disk read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.total total (read+write) operations, summed for all disks
+Cumulative number of disk read and write operations since system boot
+time (subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blkread block read operations, summed for all disks
+Cumulative number of disk block read operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blkwrite block write operations, summed for all disks
+Cumulative number of disk block write operations since system boot time
+(subject to counter wrap), summed over all disk devices.
+
+@ disk.all.blktotal total (read+write) block operations, summed for all disks
+Cumulative number of disk block read and write operations since system
+boot time (subject to counter wrap), summed over all disk devices.
+
+@ disk.all.read_bytes count of bytes read for all disk devices
+@ disk.all.write_bytes count of bytes written for all disk devices
+@ disk.all.total_bytes total count of bytes read and written for all disk devices
+
+@ disk.partitions.read read operations metric for storage partitions
+Cumulative number of disk read operations since system boot time
+(subject to counter wrap) for individual disk partitions or logical
+volumes.
+
+@ disk.partitions.write write operations metric for storage partitions
+Cumulative number of disk write operations since system boot time
+(subject to counter wrap) for individual disk partitions or logical
+volumes.
+
+@ disk.partitions.total total (read+write) I/O operations metric for storage partitions
+Cumulative number of disk read and write operations since system boot
+time (subject to counter wrap) for individual disk partitions or
+logical volumes.
+
+@ disk.partitions.blkread block read operations metric for storage partitions
+Cumulative number of disk block read operations since system boot time
+(subject to counter wrap) for individual disk partitions or logical
+volumes.
+
+@ disk.partitions.blkwrite block write operations metric for storage partitions
+Cumulative number of disk block write operations since system boot time
+(subject to counter wrap) for individual disk partitions or logical
+volumes.
+
+@ disk.partitions.blktotal total (read+write) block operations metric for storage partitions
+Cumulative number of disk block read and write operations since system
+boot time (subject to counter wrap) for individual disk partitions or
+logical volumes.
+
+@ disk.partitions.read_bytes number of bytes read for storage partitions
+Cumulative number of bytes read since system boot time (subject to
+counter wrap) for individual disk partitions or logical volumes.
+
+@ disk.partitions.write_bytes number of bytes written for storage partitions
+Cumulative number of bytes written since system boot time (subject to
+counter wrap) for individual disk partitions or logical volumes.
+
+@ disk.partitions.total_bytes total number of bytes read and written for storage partitions
+Cumulative number of bytes read and written since system boot time
+(subject to counter wrap) for individual disk partitions or logical
+volumes.
+
+@ swap.pagesin pages read from swap devices due to demand for physical memory
+@ swap.pagesout pages written to swap devices due to demand for physical memory
+@ swap.in number of swap in operations
+@ swap.out number of swap out operations
+@ kernel.all.pswitch context switches metric from /proc/stat
+@ kernel.all.sysfork fork rate metric from /proc/stat
+@ kernel.all.intr intrrupt rate metric from /proc/stat
+The value is the first value from the intr field in /proc/stat,
+which is a counter of the total number of interrupts processed.
+The value is normally converted to a rate (count/second).
+This counter usually increases by at least HZ/second,
+i.e. the clock interrupt rate, wehich is usually 100/second.
+
+See also kernel.percpu.interrupts to get a breakdown
+of interrupt rates by interrupt type and which CPU
+processed each one.
+
+@ mem.physmem total system memory metric reported by /proc/meminfo
+The value of this metric corresponds to the "MemTotal" field
+reported by /proc/meminfo. Note that this does not necessarily
+correspond to actual installed physical memory - there may
+be areas of the physical address space mapped as ROM in
+various peripheral devices and the bios may be mirroring
+certain ROMs in RAM.
+@ mem.freemem free system memory metric from /proc/meminfo
+@ mem.util.used used memory metric from /proc/meminfo
+Used memory is the difference between mem.physmem and mem.freemem.
+@ mem.util.free free memory metric from /proc/meminfo
+Alias for mem.freemem.
+@ mem.util.available available memory from /proc/meminfo
+The amount of memory that is available for a new workload,
+without pushing the system into swap. Estimated from MemFree,
+Active(file), Inactive(file), and SReclaimable, as well as the "low"
+watermarks from /proc/zoneinfo.
+
+@ mem.util.shared shared memory metric from /proc/meminfo
+Shared memory metric. Currently always zero on Linux 2.4 kernels
+and has been removed from 2.6 kernels.
+@ mem.util.bufmem I/O buffers metric from /proc/meminfo
+Memory allocated for buffer_heads.
+@ mem.util.cached page cache metric from /proc/meminfo
+Memory used by the page cache, including buffered file data.
+This is in-memory cache for files read from the disk (the pagecache)
+but doesn't include SwapCached.
+@ mem.util.other unaccounted memory
+Memory that is not free (i.e. has been referenced) and is not cached.
+mem.physmem - mem.util.free - mem.util.cached - mem.util.buffers
+@ mem.util.active Kbytes on the active page list (recently referenced pages)
+Memory that has been used more recently and usually not reclaimed unless
+absolutely necessary.
+@ mem.util.inactive Kbytes on the inactive page list (candidates for discarding)
+Memory which has been less recently used. It is more eligible to be
+reclaimed for other purposes
+@ mem.util.swapCached Kbytes in swap cache, from /proc/meminfo
+Memory that once was swapped out, is swapped back in but still also
+is in the swapfile (if memory is needed it doesn't need to be swapped
+out AGAIN because it is already in the swapfile. This saves I/O)
+@ mem.util.highTotal Kbytes in high memory, from /proc/meminfo
+This is apparently an i386 specific metric, and seems to be always zero
+on ia64 architecture (and possibly others). On i386 arch (at least),
+highmem is all memory above ~860MB of physical memory. Highmem areas
+are for use by userspace programs, or for the pagecache. The kernel
+must use tricks to access this memory, making it slower to access
+than lowmem.
+@ mem.util.highFree Kbytes free high memory, from /proc/meminfo
+See mem.util.highTotal. Not used on ia64 arch (and possibly others).
+@ mem.util.lowTotal Kbytes in low memory total, from /proc/meminfo
+Lowmem is memory which can be used for everything that highmem can be
+used for, but it is also availble for the kernel's use for its own
+data structures. Among many other things, it is where everything
+from the Slab is allocated. Bad things happen when you're out of lowmem.
+(this may only be true on i386 architectures).
+@ mem.util.lowFree Kbytes free low memory, from /proc/meminfo
+See mem.util.lowTotal
+@ mem.util.swapTotal Kbytes swap, from /proc/meminfo
+total amount of swap space available
+@ mem.util.swapFree Kbytes free swap, from /proc/meminfo
+Memory which has been evicted from RAM, and is temporarily on the disk
+@ mem.util.dirty Kbytes in dirty pages, from /proc/meminfo
+Memory which is waiting to get written back to the disk
+@ mem.util.writeback Kbytes in writeback pages, from /proc/meminfo
+Memory which is actively being written back to the disk
+@ mem.util.mapped Kbytes in mapped pages, from /proc/meminfo
+files which have been mmaped, such as libraries
+@ mem.util.slab Kbytes in slab memory, from /proc/meminfo
+in-kernel data structures cache
+@ mem.util.commitLimit Kbytes limit for address space commit, from /proc/meminfo
+The static total, in Kbytes, available for commitment to address
+spaces. Thus, mem.util.committed_AS may range up to this total. Normally
+the kernel overcommits memory, so this value may exceed mem.physmem
+@ mem.util.committed_AS Kbytes committed to address spaces, from /proc/meminfo
+An estimate of how much RAM you would need to make a 99.99% guarantee
+that there never is OOM (out of memory) for this workload. Normally
+the kernel will overcommit memory. That means, say you do a 1GB malloc,
+nothing happens, really. Only when you start USING that malloc memory
+you will get real memory on demand, and just as much as you use.
+@ mem.util.pageTables Kbytes in kernel page tables, from /proc/meminfo
+@ mem.util.reverseMaps Kbytes in reverse mapped pages, from /proc/meminfo
+@ mem.util.cache_clean Kbytes cached and not dirty or writeback, derived from /proc/meminfo
+@ mem.util.anonpages Kbytes in user pages not backed by files, from /proc/meminfo
+@ mem.util.bounce Kbytes in bounce buffers, from /proc/meminfo
+@ mem.util.NFS_Unstable Kbytes in NFS unstable memory, from /proc/meminfo
+@ mem.util.slabReclaimable Kbytes in reclaimable slab pages, from /proc/meminfo
+@ mem.util.slabUnreclaimable Kbytes in unreclaimable slab pages, from /proc/meminfo
+@ mem.util.active_anon anonymous Active list LRU memory
+@ mem.util.inactive_anon anonymous Inactive list LRU memory
+@ mem.util.active_file file-backed Active list LRU memory
+@ mem.util.inactive_file file-backed Inactive list LRU memory
+@ mem.util.unevictable kbytes of memory that is unevictable
+@ mem.util.mlocked kbytes of memory that is pinned via mlock()
+@ mem.util.shmem kbytes of shmem
+@ mem.util.kernelStack kbytes of memory used for kernel stacks
+@ mem.util.hugepagesTotal a count of total hugepages
+@ mem.util.hugepagesFree a count of free hugepages
+@ mem.util.hugepagesSurp a count of surplus hugepages
+@ mem.util.directMap4k amount of memory that is directly mapped in 4kB pages
+@ mem.util.directMap2M amount of memory that is directly mapped in 2MB pages
+@ mem.util.directMap1G amount of memory that is directly mapped in 1GB pages
+@ mem.util.vmallocTotal amount of kernel memory allocated via vmalloc
+@ mem.util.vmallocUsed amount of used vmalloc memory
+@ mem.util.vmallocChunk amount of vmalloc chunk memory
+@ mem.util.mmap_copy amount of mmap_copy space (non-MMU kernels only)
+@ mem.util.quicklists amount of memory in the per-CPU quicklists
+@ mem.util.corrupthardware amount of memory in hardware corrupted pages
+@ mem.util.anonhugepages amount of memory in anonymous huge pages
+
+User memory (Kbytes) in pages not backed by files, e.g. from malloc()
+@ mem.numa.util.total per-node total memory
+@ mem.numa.util.free per-node free memory
+@ mem.numa.util.used per-node used memory
+@ mem.numa.util.active per-node Active list LRU memory
+@ mem.numa.util.inactive per-node Inactive list LRU memory
+@ mem.numa.util.active_anon per-node anonymous Active list LRU memory
+@ mem.numa.util.inactive_anon per-node anonymous Inactive list LRU memory
+@ mem.numa.util.active_file per-node file-backed Active list LRU memory
+@ mem.numa.util.inactive_file per-node file-backed Inactive list LRU memory
+@ mem.numa.util.highTotal per-node highmem total
+@ mem.numa.util.highFree per-node highmem free
+@ mem.numa.util.lowTotal per-node lowmem total
+@ mem.numa.util.lowFree per-node lowmem free
+@ mem.numa.util.unevictable per-node Unevictable memory
+@ mem.numa.util.mlocked per-node count of Mlocked memory
+@ mem.numa.util.dirty per-node dirty memory
+@ mem.numa.util.writeback per-node count of memory locked for writeback to stable storage
+@ mem.numa.util.filePages per-node count of memory backed by files
+@ mem.numa.util.mapped per-node mapped memory
+@ mem.numa.util.anonpages per-node anonymous memory
+@ mem.numa.util.shmem per-node amount of shared memory
+@ mem.numa.util.kernelStack per-node memory used as kernel stacks
+@ mem.numa.util.pageTables per-node memory used for pagetables
+@ mem.numa.util.NFS_Unstable per-node memory holding NFS data that needs writeback
+@ mem.numa.util.bounce per-node memory used for bounce buffers
+@ mem.numa.util.writebackTmp per-node temporary memory used for writeback
+@ mem.numa.util.slab per-node memory used for slab objects
+@ mem.numa.util.slabReclaimable per-node memory used for slab objects that can be reclaimed
+@ mem.numa.util.slabUnreclaimable per-node memory used for slab objects that is unreclaimable
+@ mem.numa.util.hugepagesTotal per-node total count of hugepages
+@ mem.numa.util.hugepagesFree per-node count of free hugepages
+@ mem.numa.util.hugepagesSurp per-node count of surplus hugepages
+@ mem.numa.alloc.hit per-node count of times a task wanted alloc on local node and succeeded
+@ mem.numa.alloc.miss per-node count of times a task wanted alloc on local node but got another node
+@ mem.numa.alloc.foreign count of times a task on another node alloced on that node, but got this node
+@ mem.numa.alloc.interleave_hit count of times interleaving wanted to allocate on this node and succeeded
+@ mem.numa.alloc.local_node count of times a process ran on this node and got memory on this node
+@ mem.numa.alloc.other_node count of times a process ran on this node and got memory from another node
+@ mem.vmstat.nr_dirty number of pages in dirty state
+Instantaneous number of pages in dirty state, from /proc/vmstat
+@ mem.vmstat.nr_dirtied count of pages dirtied
+Count of pages entering dirty state, from /proc/vmstat
+@ mem.vmstat.nr_writeback number of pages in writeback state
+Instantaneous number of pages in writeback state, from /proc/vmstat
+@ mem.vmstat.nr_unstable number of pages in unstable state
+Instantaneous number of pages in unstable state, from /proc/vmstat
+@ mem.vmstat.nr_page_table_pages number of page table pages
+Instantaneous number of page table pages, from /proc/vmstat
+@ mem.vmstat.nr_mapped number of mapped pagecache pages
+Instantaneous number of mapped pagecache pages, from /proc/vmstat
+See also mem.vmstat.nr_anon for anonymous mapped pages.
+@ mem.vmstat.nr_slab number of slab pages
+Instantaneous number of slab pages, from /proc/vmstat
+This counter was retired in 2.6.18 kernels, and is now the sum of
+mem.vmstat.nr_slab_reclaimable and mem.vmstat.nr_slab_unreclaimable.
+@ mem.vmstat.nr_written count of pages written out
+Count of pages written out, from /proc/vmstat
+@ mem.vmstat.numa_foreign count of foreign NUMA zone allocations
+@ mem.vmstat.numa_hit count of successful allocations from preferred NUMA zone
+@ mem.vmstat.numa_interleave count of interleaved NUMA allocations
+@ mem.vmstat.numa_local count of successful allocations from local NUMA zone
+@ mem.vmstat.numa_miss count of unsuccessful allocations from preferred NUMA zona
+@ mem.vmstat.numa_other count of unsuccessful allocations from local NUMA zone
+@ mem.vmstat.pgpgin page in operations
+Count of page in operations since boot, from /proc/vmstat
+@ mem.vmstat.pgpgout page out operations
+Count of page out operations since boot, from /proc/vmstat
+@ mem.vmstat.pswpin pages swapped in
+Count of pages swapped in since boot, from /proc/vmstat
+@ mem.vmstat.pswpout pages swapped out
+Count of pages swapped out since boot, from /proc/vmstat
+@ mem.vmstat.pgalloc_high high mem page allocations
+Count of high mem page allocations since boot, from /proc/vmstat
+@ mem.vmstat.pgalloc_normal normal mem page allocations
+Count of normal mem page allocations since boot, from /proc/vmstat
+@ mem.vmstat.pgalloc_dma dma mem page allocations
+Count of dma mem page allocations since boot, from /proc/vmstat
+@ mem.vmstat.pgalloc_dma32 dma32 mem page allocations
+Count of dma32 mem page allocations since boot, from /proc/vmstat
+@ mem.vmstat.pgalloc_movable movable mem page allocations
+Count of movable mem page allocations since boot, from /proc/vmstat
+@ mem.vmstat.pgfree page free operations
+Count of page free operations since boot, from /proc/vmstat
+@ mem.vmstat.pgactivate pages moved from inactive to active
+Count of pages moved from inactive to active since boot, from /proc/vmstat
+@ mem.vmstat.pgdeactivate pages moved from active to inactive
+Count of pages moved from active to inactive since boot, from /proc/vmstat
+@ mem.vmstat.pgfault page major and minor fault operations
+Count of page major and minor fault operations since boot, from /proc/vmstat
+@ mem.vmstat.pgmajfault major page fault operations
+Count of major page fault operations since boot, from /proc/vmstat
+@ mem.vmstat.pgrefill_high high mem pages inspected in refill_inactive_zone
+Count of high mem pages inspected in refill_inactive_zone since boot,
+from /proc/vmstat
+@ mem.vmstat.pgrefill_normal normal mem pages inspected in refill_inactive_zone
+Count of normal mem pages inspected in refill_inactive_zone since boot,
+from /proc/vmstat
+@ mem.vmstat.pgrefill_dma dma mem pages inspected in refill_inactive_zone
+Count of dma mem pages inspected in refill_inactive_zone since boot,
+from /proc/vmstat
+@ mem.vmstat.pgrefill_dma32 dma32 mem pages inspected in refill_inactive_zone
+Count of dma32 mem pages inspected in refill_inactive_zone since boot,
+from /proc/vmstat
+@ mem.vmstat.pgrefill_movable movable mem pages inspected in refill_inactive_zone
+Count of movable mem pages inspected in refill_inactive_zone since boot,
+from /proc/vmstat
+@ mem.vmstat.pgsteal_high high mem pages reclaimed
+Count of high mem pages reclaimed since boot, from /proc/vmstat
+@ mem.vmstat.pgsteal_normal normal mem pages reclaimed
+Count of normal mem pages reclaimed since boot, from /proc/vmstat
+@ mem.vmstat.pgsteal_dma dma mem pages reclaimed
+Count of dma mem pages reclaimed since boot, from /proc/vmstat
+@ mem.vmstat.pgsteal_dma32 dma32 mem pages reclaimed
+Count of dma32 mem pages reclaimed since boot, from /proc/vmstat
+@ mem.vmstat.pgsteal_movable movable mem pages reclaimed
+Count of movable mem pages reclaimed since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_kswapd_high high mem pages scanned by kswapd
+Count of high mem pages scanned by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_kswapd_normal normal mem pages scanned by kswapd
+Count of normal mem pages scanned by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_kswapd_dma dma mem pages scanned by kswapd
+Count of dma mem pages scanned by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_kswapd_dma32 dma32 mem pages scanned by kswapd
+Count of dma32 mem pages scanned by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_kswapd_movable movable mem pages scanned by kswapd
+Count of movable mem pages scanned by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_direct_high high mem pages scanned
+Count of high mem pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_direct_normal normal mem pages scanned
+Count of normal mem pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_direct_dma dma mem pages scanned
+Count of dma mem pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_direct_dma32 dma32 mem pages scanned
+Count of dma32 mem pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.pgscan_direct_movable
+Count of movable mem pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.pginodesteal pages reclaimed via inode freeing
+Count of pages reclaimed via inode freeing since boot, from /proc/vmstat
+@ mem.vmstat.slabs_scanned slab pages scanned
+Count of slab pages scanned since boot, from /proc/vmstat
+@ mem.vmstat.kswapd_steal pages reclaimed by kswapd
+Count of pages reclaimed by kswapd since boot, from /proc/vmstat
+@ mem.vmstat.kswapd_low_wmark_hit_quickly count of times low watermark reached quickly
+Count of times kswapd reached low watermark quickly, from /proc/vmstat
+@ mem.vmstat.kswapd_high_wmark_hit_quickly count of times high watermark reached quickly
+Count of times kswapd reached high watermark quickly, from /proc/vmstat
+@ mem.vmstat.kswapd_skip_congestion_wait count of times kswapd skipped waiting on device congestion
+Count of times kswapd skipped waiting due to device congestion as a
+result of being under the low watermark, from /proc/vmstat
+@ mem.vmstat.kswapd_inodesteal pages reclaimed via kswapd inode freeing
+Count of pages reclaimed via kswapd inode freeing since boot, from
+/proc/vmstat
+@ mem.vmstat.pageoutrun kswapd calls to page reclaim
+Count of kswapd calls to page reclaim since boot, from /proc/vmstat
+@ mem.vmstat.allocstall direct reclaim calls
+Count of direct reclaim calls since boot, from /proc/vmstat
+@ mem.vmstat.pgrotated pages rotated to tail of the LRU
+Count of pages rotated to tail of the LRU since boot, from /proc/vmstat
+@mem.vmstat.nr_anon_pages number of anonymous mapped pagecache pages
+Instantaneous number of anonymous mapped pagecache pages, from /proc/vmstat
+See also mem.vmstat.mapped for other mapped pages.
+@mem.vmstat.nr_anon_transparent_hugepages number of anonymous transparent huge pages
+Instantaneous number of anonymous transparent huge pages, from /proc/vmstat
+@mem.vmstat.nr_bounce number of bounce buffer pages
+Instantaneous number of bounce buffer pages, from /proc/vmstat
+@mem.vmstat.nr_slab_reclaimable reclaimable slab pages
+Instantaneous number of reclaimable slab pages, from /proc/vmstat.
+@mem.vmstat.nr_slab_unreclaimable unreclaimable slab pages
+Instantaneous number of unreclaimable slab pages, from /proc/vmstat.
+@mem.vmstat.nr_vmscan_write pages written by VM scanner from LRU
+Count of pages written from the LRU by the VM scanner, from /proc/vmstat.
+The VM is supposed to minimise the number of pages which get written
+from the LRU (for IO scheduling efficiency, and for high reclaim-success
+rates).
+@ mem.vmstat.htlb_buddy_alloc_fail huge TLB page buddy allocation failures
+Count of huge TLB page buddy allocation failures, from /proc/vmstat
+@ mem.vmstat.htlb_buddy_alloc_success huge TLB page buddy allocation successes
+Count of huge TLB page buddy allocation successes, from /proc/vmstat
+@ mem.vmstat.nr_active_anon number of active anonymous memory pages
+@ mem.vmstat.nr_active_file number of active file memory memory pages
+@ mem.vmstat.nr_free_pages number of free pages
+@ mem.vmstat.nr_inactive_anon number of inactive anonymous memory pages
+@ mem.vmstat.nr_inactive_file number of inactive file memory pages
+@ mem.vmstat.nr_isolated_anon number of isolated anonymous memory pages
+@ mem.vmstat.nr_isolated_file number of isolated file memory pages
+@ mem.vmstat.nr_kernel_stack number of pages of kernel stack
+@ mem.vmstat.nr_mlock number of pages under mlock
+@ mem.vmstat.nr_shmem number of shared memory pages
+@ mem.vmstat.nr_unevictable number of unevictable pages
+@ mem.vmstat.nr_writeback_temp number of temporary writeback pages
+@ mem.vmstat.compact_blocks_moved count of compact blocks moved
+@ mem.vmstat.compact_fail count of unsuccessful compactions for high order allocations
+@ mem.vmstat.compact_pagemigrate_failed count of pages unsuccessfully compacted
+@ mem.vmstat.compact_pages_moved count of pages successfully moved for compaction
+@ mem.vmstat.compact_stall count of failures to even start compacting
+@ mem.vmstat.compact_success count of successful compactions for high order allocations
+@ mem.vmstat.thp_fault_alloc transparent huge page fault allocations
+@ mem.vmstat.thp_fault_fallback transparent huge page fault fallbacks
+@ mem.vmstat.thp_collapse_alloc transparent huge page collapse allocations
+@ mem.vmstat.thp_collapse_alloc_failed transparent huge page collapse failures
+@ mem.vmstat.thp_split count of transparent huge page splits
+@ mem.vmstat.unevictable_pgs_cleared count of unevictable pages cleared
+@ mem.vmstat.unevictable_pgs_culled count of unevictable pages culled
+@ mem.vmstat.unevictable_pgs_mlocked count of mlocked unevictable pages
+@ mem.vmstat.unevictable_pgs_mlockfreed count of unevictable pages mlock freed
+@ mem.vmstat.unevictable_pgs_munlocked count of unevictable pages munlocked
+@ mem.vmstat.unevictable_pgs_rescued count of unevictable pages rescued
+@ mem.vmstat.unevictable_pgs_scanned count of unevictable pages scanned
+@ mem.vmstat.unevictable_pgs_stranded count of unevictable pages stranded
+@ mem.vmstat.zone_reclaim_failed number of zone reclaim failures
+
+
+@ swap.length total swap available metric from /proc/meminfo
+@ swap.used swap used metric from /proc/meminfo
+@ swap.free swap free metric from /proc/meminfo
+@ kernel.all.load 1, 5 and 15 minute load average
+@ kernel.all.cpu.user total user CPU time from /proc/stat for all CPUs, including guest CPU time
+@ kernel.all.cpu.vuser total user CPU time from /proc/stat for all CPUs, excluding guest CPU time
+@ kernel.all.cpu.intr total interrupt CPU time from /proc/stat for all CPUs
+Total time spent processing interrupts on all CPUs.
+This value includes both soft and hard interrupt processing time.
+@ kernel.all.cpu.wait.total total wait CPU time from /proc/stat for all CPUs
+@ kernel.all.cpu.nice total nice user CPU time from /proc/stat for all CPUs
+@ kernel.all.cpu.sys total sys CPU time from /proc/stat for all CPUs
+@ kernel.all.cpu.idle total idle CPU time from /proc/stat for all CPUs
+@ kernel.all.cpu.irq.soft soft interrupt CPU time from /proc/stat for all CPUs
+Total soft interrupt CPU time (deferred interrupt handling code,
+not run in the initial interrupt handler).
+@ kernel.all.cpu.irq.hard hard interrupt CPU time from /proc/stat for all CPUs
+Total hard interrupt CPU time ("hard" interrupt handling code
+is the code run directly on receipt of the initial hardware
+interrupt, and does not include "soft" interrupt handling code
+which is deferred until later).
+@ kernel.all.cpu.steal total virtualisation CPU steal time for all CPUs
+Total CPU time when a CPU had a runnable process, but the hypervisor
+(virtualisation layer) chose to run something else instead.
+@ kernel.all.cpu.guest total virtual guest CPU time for all CPUs
+Total CPU time spent running virtual guest operating systems.
+@ kernel.all.nusers number of user sessions on system
+
+@ hinv.ninterface number of active (up) network interfaces
+@ network.interface.in.bytes network recv read bytes from /proc/net/dev per network interface
+@ network.interface.in.packets network recv read packets from /proc/net/dev per network interface
+@ network.interface.in.errors network recv read errors from /proc/net/dev per network interface
+@ network.interface.in.drops network recv read drops from /proc/net/dev per network interface
+@ network.interface.in.mcasts network recv compressed from /proc/net/dev per network interface
+@ network.interface.in.fifo network recv read fifos from /proc/net/dev per network interface
+@ network.interface.in.frame network recv read frames from /proc/net/dev per network interface
+@ network.interface.in.compressed network recv compressed from /proc/net/dev per network interface
+@ network.interface.out.bytes network send bytes from /proc/net/dev per network interface
+@ network.interface.out.packets network send packets from /proc/net/dev per network interface
+@ network.interface.out.errors network send errors from /proc/net/dev per network interface
+@ network.interface.out.drops network send drops from /proc/net/dev per network interface
+@ network.interface.out.fifo network send fifos from /proc/net/dev per network interface
+@ network.interface.collisions network send collisions from /proc/net/dev per network interface
+@ network.interface.out.carrier network send carrier from /proc/net/dev per network interface
+@ network.interface.out.compressed network send compressed from /proc/net/dev per network interface
+@ network.interface.total.bytes network total (in+out) bytes from /proc/net/dev per network interface
+@ network.interface.total.packets network total (in+out) packets from /proc/net/dev per network interface
+@ network.interface.total.errors network total (in+out) errors from /proc/net/dev per network interface
+@ network.interface.total.drops network total (in+out) drops from /proc/net/dev per network interface
+@ network.interface.total.mcasts network total (in+out) mcasts from /proc/net/dev per network interface
+@ network.interface.mtu maximum transmission unit on network interface
+@ network.interface.speed interface speed in megabytes per second
+The linespeed on the network interface, as reported by the kernel,
+scaled from Megabits/second to Megabytes/second.
+See also network.interface.baudrate for the bytes/second value.
+@ network.interface.baudrate interface speed in bytes per second
+The linespeed on the network interface, as reported by the kernel,
+scaled up from Megabits/second to bits/second and divided by 8 to convert
+to bytes/second.
+See also network.interface.speed for the Megabytes/second value.
+@ network.interface.duplex value one for half or two for full duplex interface
+@ network.interface.up boolean for whether interface is currently up or down
+@ network.interface.running boolean for whether interface has resources allocated
+@ network.interface.inet_addr string INET interface address (ifconfig style)
+@ network.interface.ipv6_addr string IPv6 interface address (ifconfig style)
+@ network.interface.ipv6_scope string IPv6 interface scope (ifconfig style)
+@ network.interface.hw_addr hardware address (from sysfs)
+@ network.sockstat.tcp.inuse instantaneous number of tcp sockets currently in use
+@ network.sockstat.tcp.highest highest number of tcp sockets in use at any one time since boot
+@ network.sockstat.tcp.util instantaneous tcp socket utilization (100 * inuse/highest)
+@ network.sockstat.udp.inuse instantaneous number of udp sockets currently in use
+@ network.sockstat.udp.highest highest number of udp sockets in use at any one time since boot
+@ network.sockstat.udp.util instantaneous udp socket utilization (100 * inuse/highest)
+@ network.sockstat.raw.inuse instantaneous number of raw sockets currently in use
+@ network.sockstat.raw.highest highest number of raw sockets in use at any one time since boot
+@ network.sockstat.raw.util instantaneous raw socket utilization (100 * inuse/highest)
+@ hinv.physmem total system memory metric from /proc/meminfo
+@ hinv.pagesize Memory page size
+The memory page size of the running kernel in bytes.
+@ hinv.ncpu number of CPUs in the system
+@ hinv.ndisk number of disks in the system
+@ hinv.nfilesys number of (local) file systems currently mounted
+@ hinv.nnode number of NUMA nodes in the system
+@ hinv.map.scsi list of active SCSI devices
+There is one string value for each SCSI device active in the system,
+as extracted from /proc/scsi/scsi. The external instance name
+for each device is in the format scsiD:C:I:L where
+D is controller number, C is channel number, I is device ID
+and L is the SCSI LUN number for the device. The values for this
+metric are the actual device names (sd[a-z] are SCSI disks, st[0-9]
+are SCSI tapes and scd[0-9] are SCSI CD-ROMS.
+@ hinv.nlv number of logical volumes
+@ hinv.map.lvname mapping of logical volume names for devices
+Provides a logical-volume-name to device-name mapping for the device
+mapper subsystem.
+@ filesys.capacity Total capacity of mounted filesystem (Kbytes)
+@ filesys.used Total space used on mounted filesystem (Kbytes)
+@ filesys.free Total space free on mounted filesystem (Kbytes)
+@ filesys.maxfiles Inodes capacity of mounted filesystem
+@ filesys.usedfiles Number of inodes allocated on mounted filesystem
+@ filesys.freefiles Number of unallocated inodes on mounted filesystem
+@ filesys.mountdir File system mount point
+@ filesys.full Percentage of filesystem in use
+@ filesys.blocksize Size of each block on mounted filesystem (Bytes)
+@ filesys.avail Total space free to non-superusers on mounted filesystem (Kbytes)
+@ filesys.readonly Indicates whether a filesystem is mounted readonly
+@ tmpfs.capacity Total capacity of mounted tmpfs filesystem (Kbytes)
+@ tmpfs.used Total space used on mounted tmpfs filesystem (Kbytes)
+@ tmpfs.free Total space free on mounted tmpfs filesystem (Kbytes)
+@ tmpfs.maxfiles Inodes capacity of mounted tmpfs filesystem
+@ tmpfs.usedfiles Number of inodes allocated on mounted tmpfs filesystem
+@ tmpfs.freefiles Number of unallocated inodes on mounted tmpfs filesystem
+@ tmpfs.full Percentage of tmpfs filesystem in use
+@ swapdev.free physical swap free space
+@ swapdev.length physical swap size
+@ swapdev.maxswap maximum swap length (same as swapdev.length on Linux)
+@ swapdev.vlength virtual swap size (always zero on Linux)
+Virtual swap size (always zero on Linux since Linux does not support
+virtual swap).
+
+This metric is retained on Linux for interoperability with PCP monitor
+tools running on IRIX.
+
+@ swapdev.priority swap resource priority
+@ nfs.client.calls cumulative total of client NFSv2 requests
+@ nfs.client.reqs cumulative total of client NFSv2 requests by request type
+@ nfs.server.calls cumulative total of server NFSv2 requests
+@ nfs.server.reqs cumulative total of client NFSv2 requests by request type
+@ nfs3.client.calls cumulative total of client NFSv3 requests
+@ nfs3.client.reqs cumulative total of client NFSv3 requests by request type
+@ nfs3.server.calls cumulative total of server NFSv3 requests
+@ nfs3.server.reqs cumulative total of client NFSv3 requests by request type
+@ nfs4.client.calls cumulative total of client NFSv4 requests
+@ nfs4.client.reqs cumulative total for each client NFSv4 request type
+@ nfs4.server.calls cumulative total of server NFSv4 operations, plus NULL requests
+@ nfs4.server.reqs cumulative total for each server NFSv4 operation, and for NULL requests
+@ rpc.client.rpccnt cumulative total of client RPC requests
+@ rpc.client.rpcretrans cumulative total of client RPC retransmissions
+@ rpc.client.rpcauthrefresh cumulative total of client RPC auth refreshes
+@ rpc.client.netcnt cumulative total of client RPC network layer requests
+@ rpc.client.netudpcnt cumulative total of client RPC UDP network layer requests
+@ rpc.client.nettcpcnt cumulative total of client RPC TCP network layer requests
+@ rpc.client.nettcpconn cumulative total of client RPC TCP network layer connection requests
+@ rpc.server.rpccnt cumulative total of server RPC requests
+@ rpc.server.rpcerr cumulative total of server RPC errors
+@ rpc.server.rpcbadfmt cumulative total of server RPC bad format errors
+@ rpc.server.rpcbadauth cumulative total of server RPC bad auth errors
+@ rpc.server.rpcbadclnt cumulative total of server RPC bad client errors
+@ rpc.server.rchits cumulative total of request-reply-cache hits
+@ rpc.server.rcmisses cumulative total of request-reply-cache misses
+@ rpc.server.rcnocache cumulative total of uncached request-reply-cache requests
+@ rpc.server.fh_cached cumulative total of file handle cache requests
+@ rpc.server.fh_valid cumulative total of file handle cache validations
+@ rpc.server.fh_fixup cumulative total of file handle cache fixup validations
+@ rpc.server.fh_lookup cumulative total of file handle cache new lookups
+@ rpc.server.fh_stale cumulative total of stale file handle cache errors
+@ rpc.server.fh_concurrent cumulative total of concurrent file handle cache requests
+@ rpc.server.netcnt cumulative total of server RPC network layer requests
+@ rpc.server.netudpcnt cumulative total of server RPC UDP network layer requests
+@ rpc.server.nettcpcnt cumulative total of server RPC TCP network layer requests
+@ rpc.server.nettcpconn cumulative total of server RPC TCP network layer connection requests
+@ rpc.server.fh_anon cumulative total anonymous file dentries returned
+@ rpc.server.fh_nocache_dir count of directory file handles not found cached
+@ rpc.server.fh_nocache_nondir count of non-directory file handles not found cached
+@ rpc.server.io_read cumulative count of bytes returned from read requests
+@ rpc.server.io_write cumulative count of bytes passed into write requests
+@ rpc.server.th_cnt available nfsd threads
+@ rpc.server.th_fullcnt number of times the last free nfsd thread was used
+
+@ network.ip.forwarding count of ip forwarding
+@ network.ip.defaultttl count of ip defaultttl
+@ network.ip.inreceives count of ip inreceives
+@ network.ip.inhdrerrors count of ip inhdrerrors
+@ network.ip.inaddrerrors count of ip inaddrerrors
+@ network.ip.forwdatagrams count of ip forwdatagrams
+@ network.ip.inunknownprotos count of ip inunknownprotos
+@ network.ip.indiscards count of ip indiscards
+@ network.ip.indelivers count of ip indelivers
+@ network.ip.outrequests count of ip outrequests
+@ network.ip.outdiscards count of ip outdiscards
+@ network.ip.outnoroutes count of ip outnoroutes
+@ network.ip.reasmtimeout count of ip reasmtimeout
+@ network.ip.reasmreqds count of ip reasmreqds
+@ network.ip.reasmoks count of ip reasmoks
+@ network.ip.reasmfails count of ip reasmfails
+@ network.ip.fragoks count of ip fragoks
+@ network.ip.fragfails count of ip fragfails
+@ network.ip.fragcreates count of ip fragcreates
+@ network.icmp.inmsgs count of icmp inmsgs
+@ network.icmp.inerrors count of icmp inerrors
+@ network.icmp.indestunreachs count of icmp indestunreachs
+@ network.icmp.intimeexcds count of icmp intimeexcds
+@ network.icmp.inparmprobs count of icmp inparmprobs
+@ network.icmp.insrcquenchs count of icmp insrcquenchs
+@ network.icmp.inredirects count of icmp inredirects
+@ network.icmp.inechos count of icmp inechos
+@ network.icmp.inechoreps count of icmp inechoreps
+@ network.icmp.intimestamps count of icmp intimestamps
+@ network.icmp.intimestampreps count of icmp intimestampreps
+@ network.icmp.inaddrmasks count of icmp inaddrmasks
+@ network.icmp.inaddrmaskreps count of icmp inaddrmaskreps
+@ network.icmp.outmsgs count of icmp outmsgs
+@ network.icmp.outerrors count of icmp outerrors
+@ network.icmp.outdestunreachs count of icmp outdestunreachs
+@ network.icmp.outtimeexcds count of icmp outtimeexcds
+@ network.icmp.outparmprobs count of icmp outparmprobs
+@ network.icmp.outsrcquenchs count of icmp outsrcquenchs
+@ network.icmp.outredirects count of icmp outredirects
+@ network.icmp.outechos count of icmp outechos
+@ network.icmp.outechoreps count of icmp outechoreps
+@ network.icmp.outtimestamps count of icmp outtimestamps
+@ network.icmp.outtimestampreps count of icmp outtimestampreps
+@ network.icmp.outaddrmasks count of icmp outaddrmasks
+@ network.icmp.outaddrmaskreps count of icmp outaddrmaskreps
+@ network.icmp.incsumerrors count of icmp in checksum errors
+@ network.icmpmsg.intype count of icmp message types recvd
+@ network.icmpmsg.outtype count of icmp message types sent
+@ network.tcp.rtoalgorithm count of tcp rtoalgorithm
+@ network.tcp.rtomin count of tcp rtomin
+@ network.tcp.rtomax count of tcp rtomax
+@ network.tcp.maxconn count of tcp maxconn
+@ network.tcp.activeopens count of tcp activeopens
+@ network.tcp.passiveopens count of tcp passiveopens
+@ network.tcp.attemptfails count of tcp attemptfails
+@ network.tcp.estabresets count of tcp estabresets
+@ network.tcp.currestab count of tcp currestab
+@ network.tcp.insegs count of tcp insegs
+@ network.tcp.outsegs count of tcp outsegs
+@ network.tcp.retranssegs count of tcp retranssegs
+@ network.tcp.inerrs count of tcp inerrs
+@ network.tcp.outrsts count of tcp outrsts
+@ network.tcp.incsumerrors count of tcp in checksum errors
+@ network.tcpconn.established Number of established connections
+@ network.tcpconn.syn_sent Number of SYN_SENT connections
+@ network.tcpconn.syn_recv Number of SYN_RECV connections
+@ network.tcpconn.fin_wait1 Number of FIN_WAIT1 connections
+@ network.tcpconn.fin_wait2 Number of FIN_WAIT2 connections
+@ network.tcpconn.time_wait Number of TIME_WAIT connections
+@ network.tcpconn.close Number of CLOSE connections
+@ network.tcpconn.close_wait Number of CLOSE_WAIT connections
+@ network.tcpconn.last_ack Number of LAST_ACK connections
+@ network.tcpconn.listen Number of LISTEN connections
+@ network.tcpconn.closing Number of CLOSING connections
+@ network.udp.indatagrams count of udp indatagrams
+@ network.udp.noports count of udp noports
+@ network.udp.inerrors count of udp inerrors
+@ network.udp.outdatagrams count of udp outdatagrams
+@ network.udp.recvbuferrors count of udp receive buffer errors
+@ network.udp.sndbuferrors count of udp send buffer errors
+@ network.udp.incsumerrors count of udp in checksum errors
+@ network.udplite.indatagrams count of udplite indatagrams
+@ network.udplite.noports count of udplite noports
+@ network.udplite.inerrors count of udplite inerrors
+@ network.udplite.outdatagrams count of udplite outdatagrams
+@ network.udplite.recvbuferrors count of udplite receive buffer errors
+@ network.udplite.sndbuferrors count of udplite send buffer errors
+@ network.udplite.incsumerrors count of udplite in checksum errors
+
+@ network.ip.innoroutes Number of IP datagrams discarded due to no routes in forwarding path
+@ network.ip.intruncatedpkts Number of IP datagrams discarded due to frame not carrying enough data
+@ network.ip.inmcastpkts Number of received IP multicast datagrams
+@ network.ip.outmcastpkts Number of sent IP multicast datagrams
+@ network.ip.inbcastpkts Number of received IP broadcast datagrams
+@ network.ip.outbcastpkts Number of sent IP bradcast datagrams
+@ network.ip.inoctets Number of received octets
+@ network.ip.outoctets Number of sent octets
+@ network.ip.inmcastoctets Number of received IP multicast octets
+@ network.ip.outmcastoctets Number of sent IP multicast octets
+@ network.ip.inbcastoctets Number of received IP broadcast octets
+@ network.ip.outbcastoctets Number of sent IP broadcast octets
+@ network.ip.csumerrors Number of IP datagrams with checksum errors
+@ network.ip.noectpkts Number of packets received with NOECT
+@ network.ip.ect1pkts Number of packets received with ECT(1)
+@ network.ip.ect0pkts Number of packets received with ECT(0)
+@ network.ip.cepkts Number of packets received with Congestion Experimented
+
+@ network.tcp.syncookiessent Number of sent SYN cookies
+@ network.tcp.syncookiesrecv Number of received SYN cookies
+@ network.tcp.syncookiesfailed Number of failed SYN cookies
+@ network.tcp.embryonicrsts Number of resets received for embryonic SYN_RECV sockets
+@ network.tcp.prunecalled Number of packets pruned from receive queue because of socket buffer overrun
+@ network.tcp.rcvpruned Number of packets pruned from receive queue
+@ network.tcp.ofopruned Number of packets dropped from out-of-order queue because of socket buffer overrun
+@ network.tcp.outofwindowicmps Number of dropped out of window ICMPs
+@ network.tcp.lockdroppedicmps Number of dropped ICMP because socket was locked
+@ network.tcp.arpfilter Number of arp packets filtered
+@ network.tcp.timewaited Number of TCP sockets finished time wait in fast timer
+@ network.tcp.timewaitrecycled Number of time wait sockets recycled by time stamp
+@ network.tcp.timewaitkilled Number of TCP sockets finished time wait in slow timer
+@ network.tcp.pawspassiverejected Number of passive connections rejected because of timestamp
+@ network.tcp.pawsactiverejected Number of active connections rejected because of timestamp
+@ network.tcp.pawsestabrejected Number of packets rejects in established connections because of timestamp
+@ network.tcp.delayedacks Number of delayed acks sent
+@ network.tcp.delayedacklocked Number of delayed acks further delayed because of locked socket
+@ network.tcp.delayedacklost Number of times quick ack mode was activated times
+@ network.tcp.listenoverflows Number of times the listen queue of a socket overflowed
+@ network.tcp.listendrops Number of SYNs to LISTEN sockets dropped
+@ network.tcp.prequeued Number of packets directly queued to recvmsg prequeue
+@ network.tcp.directcopyfrombacklog Number of bytes directly in process context from backlog
+@ network.tcp.directcopyfromprequeue Number of bytes directly received in process context from prequeue
+@ network.tcp.prequeueddropped Number of packets dropped from prequeue
+@ network.tcp.hphits Number of packet headers predicted
+@ network.tcp.hphitstouser Number of packets header predicted and directly queued to user
+@ network.tcp.pureacks Number of acknowledgments not containing data payload received
+@ network.tcp.hpacks Number of predicted acknowledgments
+@ network.tcp.renorecovery Number of times recovered from packet loss due to fast retransmit
+@ network.tcp.sackrecovery Number of times recovered from packet loss by selective acknowledgements
+@ network.tcp.sackreneging Number of bad SACK blocks received
+@ network.tcp.fackreorder Number of times detected reordering using FACK
+@ network.tcp.sackreorder Number of times detected reordering using SACK
+@ network.tcp.renoreorder Number of times detected reordering using reno fast retransmit
+@ network.tcp.tsreorder Number of times detected reordering times using time stamp
+@ network.tcp.fullundo Number of congestion windows fully recovered without slow start
+@ network.tcp.partialundo Number of congestion windows partially recovered using Hoe heuristic
+@ network.tcp.dsackundo Number of congestion windows recovered without slow start using DSACK
+@ network.tcp.lossundo Number of congestion windows recovered without slow start after partial ack
+@ network.tcp.lostretransmit Number of retransmits lost
+@ network.tcp.renofailures Number of timeouts after reno fast retransmit
+@ network.tcp.sackfailures Number of timeouts after SACK recovery
+@ network.tcp.lossfailures Number of timeouts in loss state
+@ network.tcp.fastretrans Number of fast retransmits
+@ network.tcp.forwardretrans Number of forward retransmits
+@ network.tcp.slowstartretrans Number of retransmits in slow start
+@ network.tcp.timeouts Number of other TCP timeouts
+@ network.tcp.lossprobes Number of sent TCP loss probes
+@ network.tcp.lossproberecovery Number of TCP loss probe recoveries
+@ network.tcp.renorecoveryfail Number of reno fast retransmits failed
+@ network.tcp.sackrecoveryfail Number of SACK retransmits failed
+@ network.tcp.schedulerfail Number of times receiver scheduled too late for direct processing
+@ network.tcp.rcvcollapsed Number of packets collapsed in receive queue due to low socket buffer
+@ network.tcp.dsackoldsent Number of DSACKs sent for old packets
+@ network.tcp.dsackofosent Number of DSACKs sent for out of order packets
+@ network.tcp.dsackrecv Number of DSACKs received
+@ network.tcp.dsackoforecv Number of DSACKs for out of order packets received
+@ network.tcp.abortondata Number of connections reset due to unexpected data
+@ network.tcp.abortonclose Number of connections reset due to early user close
+@ network.tcp.abortonmemory Number of connections aborted due to memory pressure
+@ network.tcp.abortontimeout Number of connections aborted due to timeout
+@ network.tcp.abortonlinger Number of connections aborted after user close in linger timeout
+@ network.tcp.abortfailed Number of times unable to send RST due to no memory
+@ network.tcp.memorypressures Numer of times TCP ran low on memory
+@ network.tcp.sackdiscard Number of SACKs discarded
+@ network.tcp.dsackignoredold Number of ignored old duplicate SACKs
+@ network.tcp.dsackignorednoundo Number of ignored duplicate SACKs with undo_marker not set
+@ network.tcp.spuriousrtos Number of FRTO's successfully detected spurious RTOs
+@ network.tcp.md5notfound Number of times MD5 hash expected but not found
+@ network.tcp.md5unexpected Number of times MD5 hash unexpected but found
+@ network.tcp.sackshifted Number of SACKs shifted
+@ network.tcp.sackmerged Number of SACKs merged
+@ network.tcp.sackshiftfallback Number of SACKs fallbacks
+@ network.tcp.backlogdrop Number of frames dropped because of full backlog queue
+@ network.tcp.minttldrop Number of frames dropped when TTL is under the minimum
+@ network.tcp.deferacceptdrop Number of dropped ACK frames when socket is in SYN-RECV state
+Due to SYNACK retrans count lower than defer_accept value
+
+@ network.tcp.iprpfilter Number of packets dropped in input path because of rp_filter settings
+@ network.tcp.timewaitoverflow Number of occurences of time wait bucket overflow
+@ network.tcp.reqqfulldocookies Number of times a SYNCOOKIE was replied to client
+@ network.tcp.reqqfulldrop Number of times a SYN request was dropped due to disabled syncookies
+@ network.tcp.retransfail Number of failed tcp_retransmit_skb() calls
+@ network.tcp.rcvcoalesce Number of times tried to coalesce the receive queue
+@ network.tcp.ofoqueue Number of packets queued in OFO queue
+@ network.tcp.ofodrop Number of packets meant to be queued in OFO but dropped due to limits hit
+Number of packets meant to be queued in OFO but dropped because socket rcvbuf
+limit reached.
+@ network.tcp.ofomerge Number of packets in OFO that were merged with other packets
+@ network.tcp.challengeack Number of challenge ACKs sent (RFC 5961 3.2)
+@ network.tcp.synchallenge Number of challenge ACKs sent in response to SYN packets
+@ network.tcp.fastopenactive Number of successful active fast opens
+@ network.tcp.fastopenactivefail Number of fast open attempts failed due to remote not accepting it or time outs
+@ network.tcp.fastopenpassive Number of successful passive fast opens
+@ network.tcp.fastopenpassivefail Number of passive fast open attempts failed
+@ network.tcp.fastopenlistenoverflow Number of times the fastopen listen queue overflowed
+@ network.tcp.fastopencookiereqd Number of fast open cookies requested
+@ network.tcp.spuriousrtxhostqueues Number of times that the fast clone is not yet freed in tcp_transmit_skb()
+@ network.tcp.busypollrxpackets Number of low latency application-fetched packets
+@ network.tcp.autocorking Number of times stack detected skb was underused and its flush was deferred
+@ network.tcp.fromzerowindowadv Number of times window went from zero to non-zero
+@ network.tcp.tozerowindowadv Number of times window went from non-zero to zero
+@ network.tcp.wantzerowindowadv Number of times zero window announced
+@ network.tcp.synretrans Number of SYN-SYN/ACK retransmits
+Number of SYN-SYN/ACK retransmits to break down retransmissions in SYN, fast/timeout
+retransmits.
+@ network.tcp.origdatasent Number of outgoing packets with original data
+Excluding retransmission but including data-in-SYN). This counter is different from
+TcpOutSegs because TcpOutSegs also tracks pure ACKs. TCPOrigDataSent is
+more useful to track the TCP retransmission rate.
+
+@ pmda.uname identity and type of current system
+Identity and type of current system. The concatenation of the values
+returned from utsname(2), also similar to uname -a.
+
+See also the kernel.uname.* metrics
+
+@ pmda.version build version of Linux PMDA
+@ hinv.map.cpu_num logical to physical CPU mapping for each CPU
+@ hinv.map.cpu_node logical CPU to NUMA node mapping for each CPU
+@ hinv.machine machine name, IP35 if SGI SNIA, else simply linux
+@ hinv.cpu.clock clock rate in Mhz for each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.vendor manafacturer of each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.model model number of each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.model_name model name of each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.stepping stepping of each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.cache primary cache size of each CPU as reported by /proc/cpuinfo
+@ hinv.cpu.bogomips bogo mips rating for each CPU as reported by /proc/cpuinfo
+@ kernel.all.hz value of HZ (jiffies/second) for the currently running kernel
+@ kernel.all.uptime time the current kernel has been running
+@ kernel.all.idletime time the current kernel has been idle since boot
+@ kernel.all.lastpid most recently allocated process id
+@ kernel.all.runnable total number of processes in the (per-CPU) run queues
+@ kernel.all.nprocs total number of processes (lightweight)
+@ mem.slabinfo.objects.active number of active objects in each cache
+@ mem.slabinfo.objects.total total number of objects in each cache
+@ mem.slabinfo.objects.size size of individual objects of each cache
+@ mem.slabinfo.slabs.active number of active slabs comprising each cache
+@ mem.slabinfo.slabs.total total number of slabs comprising each cache
+@ mem.slabinfo.slabs.pages_per_slab number of pages in each slab
+@ mem.slabinfo.slabs.objects_per_slab number of objects in each slab
+@ mem.slabinfo.slabs.total_size total number of bytes allocated for active objects in each slab
+@ ipc.sem.max_semmap maximum number of entries in a semaphore map (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_semid maximum number of semaphore identifiers (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_sem maximum number of semaphores in system (from semctl(..,IPC_INFO,..))
+@ ipc.sem.num_undo number of undo structures in system (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_perid maximum number of semaphores per identifier (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_ops maximum number of operations per semop call (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_undoent maximum number of undo entries per process (from semctl(..,IPC_INFO,..))
+@ ipc.sem.sz_semundo size of struct sem_undo (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_semval semaphore maximum value (from semctl(..,IPC_INFO,..))
+@ ipc.sem.max_exit adjust on exit maximum value (from semctl(..,IPC_INFO,..))
+@ ipc.msg.sz_pool size of message pool in kilobytes (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.mapent number of entries in a message map (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.max_msgsz maximum size of a message in bytes (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.max_defmsgq default maximum size of a message queue (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.max_msgqid maximum number of message queue identifiers (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.max_msgseg message segment size (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.num_smsghdr number of system message headers (from msgctl(..,IPC_INFO,..))
+@ ipc.msg.max_seg maximum number of message segments (from msgctl(..,IPC_INFO,..))
+@ ipc.shm.max_segsz maximum shared segment size in bytes (from shmctl(..,IPC_INFO,..))
+@ ipc.shm.min_segsz minimum shared segment size in bytes (from shmctl(..,IPC_INFO,..))
+@ ipc.shm.max_seg maximum number of shared segments in system (from shmctl(..,IPC_INFO,..))
+@ ipc.shm.max_segproc maximum number of shared segments per process (from shmctl(..,IPC_INFO,..))
+@ ipc.shm.max_shmsys maximum amount of shared memory in system in pages (from shmctl(..,IPC_INFO,..))
+
+@ vfs.files.count number of in-use file structures
+@ vfs.files.free number of available file structures
+@ vfs.files.max hard maximum on number of file structures
+@ vfs.inodes.count number of in-use inode structures
+@ vfs.inodes.free number of available inode structures
+@ vfs.dentry.count number of in-use dentry structures
+@ vfs.dentry.free number of available dentry structures
+
+@ sysfs.kernel.uevent_seqnum counter of the number of uevents processed by the udev subsystem
diff --git a/src/pmdas/linux/indom.h b/src/pmdas/linux/indom.h
new file mode 100644
index 0000000..89f5236
--- /dev/null
+++ b/src/pmdas/linux/indom.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _INDOM_H
+#define _INDOM_H
+
+enum {
+ CPU_INDOM = 0, /* 0 - percpu */
+ DISK_INDOM, /* 1 - disks */
+ LOADAVG_INDOM, /* 2 - 1, 5, 15 minute load averages */
+ NET_DEV_INDOM, /* 3 - network interfaces */
+ PROC_INTERRUPTS_INDOM, /* 4 - interrupt lines -> proc PMDA */
+ FILESYS_INDOM, /* 5 - mounted bdev filesystems */
+ SWAPDEV_INDOM, /* 6 - swap devices */
+ NFS_INDOM, /* 7 - nfs operations */
+ NFS3_INDOM, /* 8 - nfs v3 operations */
+ PROC_PROC_INDOM, /* 9 - processes */
+ PARTITIONS_INDOM, /* 10 - disk partitions */
+ SCSI_INDOM, /* 11 - scsi devices */
+ SLAB_INDOM, /* 12 - kernel slabs */
+ STRINGS_INDOM, /* 13 - string dictionary */
+ NFS4_CLI_INDOM, /* 14 - nfs v4 client operations */
+ NFS4_SVR_INDOM, /* 15 - nfs n4 server operations */
+ QUOTA_PRJ_INDOM, /* 16 - project quota -> xfs PMDA */
+ NET_ADDR_INDOM, /* 17 - inet/ipv6 addresses */
+ TMPFS_INDOM, /* 18 - tmpfs mounts */
+ NODE_INDOM, /* 19 - NUMA nodes */
+ PROC_CGROUP_SUBSYS_INDOM, /* 20 - control group subsystems -> proc PMDA */
+ PROC_CGROUP_MOUNTS_INDOM, /* 21 - control group mounts -> proc PMDA */
+ LV_INDOM, /* 22 - lvm devices */
+ ICMPMSG_INDOM, /* 23 - icmp message types */
+ DM_INDOM, /* 24 device mapper devices */
+
+ NUM_INDOMS /* one more than highest numbered cluster */
+};
+
+extern pmInDom linux_indom(int);
+#define INDOM(i) linux_indom(i)
+
+extern pmdaIndom *linux_pmda_indom(int);
+#define PMDAINDOM(i) linux_pmda_indom(i)
+
+/*
+ * Optional path prefix for all stats files, used for testing.
+ */
+extern char *linux_statspath;
+extern FILE *linux_statsfile(const char *, char *, int);
+
+/*
+ * static string dictionary - one copy of oft-repeated strings;
+ * implemented using STRINGS_INDOM and pmdaCache(3) routines.
+ */
+char *linux_strings_lookup(int);
+int linux_strings_insert(const char *);
+
+#endif /* _INDOM_H */
diff --git a/src/pmdas/linux/interrupts.c b/src/pmdas/linux/interrupts.c
new file mode 100644
index 0000000..47377e8
--- /dev/null
+++ b/src/pmdas/linux/interrupts.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2011 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "filesys.h"
+#include "clusters.h"
+#include "interrupts.h"
+#include <sys/stat.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <ctype.h>
+
+typedef struct {
+ unsigned int id; /* becomes PMID item number */
+ char *name; /* becomes PMNS sub-component */
+ char *text; /* one-line metric help text */
+ unsigned long long *values; /* per-CPU values for this counter */
+} interrupt_t;
+
+static unsigned int cpu_count;
+static int *online_cpumap; /* maps input columns to CPU IDs */
+static unsigned int lines_count;
+static interrupt_t *interrupt_lines;
+static unsigned int other_count;
+static interrupt_t *interrupt_other;
+
+static __pmnsTree *interrupt_tree;
+unsigned int irq_err_count;
+
+static void
+update_lines_pmns(int domain, unsigned int item, unsigned int id)
+{
+ char entry[128];
+ pmID pmid = pmid_build(domain, CLUSTER_INTERRUPT_LINES, item);
+
+ snprintf(entry, sizeof(entry), "kernel.percpu.interrupts.line%d", id);
+ __pmAddPMNSNode(interrupt_tree, pmid, entry);
+}
+
+static void
+update_other_pmns(int domain, unsigned int item, const char *name)
+{
+ char entry[128];
+ pmID pmid = pmid_build(domain, CLUSTER_INTERRUPT_OTHER, item);
+
+ snprintf(entry, sizeof(entry), "kernel.percpu.interrupts.%s", name);
+ __pmAddPMNSNode(interrupt_tree, pmid, entry);
+}
+
+static int
+map_online_cpus(char *buffer)
+{
+ unsigned long i = 0, cpuid;
+ char *s, *end;
+
+ for (s = buffer; *s != '\0'; s++) {
+ if (!isdigit((int)*s))
+ continue;
+ cpuid = strtoul(s, &end, 10);
+ if (end == s)
+ break;
+ online_cpumap[i++] = cpuid;
+ s = end;
+ }
+ return i;
+}
+
+static int
+column_to_cpuid(int column)
+{
+ int i;
+
+ if (online_cpumap[column] == column)
+ return column;
+ for (i = 0; i < cpu_count; i++)
+ if (online_cpumap[i] == column)
+ return i;
+ return 0;
+}
+
+static char *
+extract_values(char *buffer, unsigned long long *values, int ncolumns)
+{
+ unsigned long i, value, cpuid;
+ char *s = buffer, *end = NULL;
+
+ for (i = 0; i < ncolumns; i++) {
+ value = strtoul(s, &end, 10);
+ if (*end != ' ')
+ return NULL;
+ s = end;
+ cpuid = column_to_cpuid(i);
+ values[cpuid] = value;
+ }
+ return end;
+}
+
+/* Create oneline help text - remove duplicates and end-of-line marker */
+static char *
+oneline_reformat(char *buf)
+{
+ char *result, *start, *end;
+
+ /* position end marker, and skip over whitespace at the start */
+ for (start = end = buf; *end != '\n' && *end != '\0'; end++)
+ if (isspace((int)*start) && isspace((int)*end))
+ start = end+1;
+ *end = '\0';
+
+ /* squash duplicate whitespace and remove trailing whitespace */
+ for (result = start; *result != '\0'; result++) {
+ if (isspace((int)result[0]) && (isspace((int)result[1]) || result[1] == '\0')) {
+ memmove(&result[0], &result[1], end - &result[0]);
+ result--;
+ }
+ }
+ return start;
+}
+
+static void
+initialise_interrupt(interrupt_t *ip, unsigned int id, char *s, char *end)
+{
+ ip->id = id;
+ ip->name = strdup(s);
+ if (end)
+ ip->text = strdup(oneline_reformat(end));
+}
+
+static int
+extend_interrupts(interrupt_t **interp, unsigned int *countp)
+{
+ int cnt = cpu_count * sizeof(unsigned long long);
+ unsigned long long *values = malloc(cnt);
+ interrupt_t *interrupt = *interp;
+ int count = *countp + 1;
+
+ if (!values)
+ return 0;
+
+ interrupt = realloc(interrupt, count * sizeof(interrupt_t));
+ if (!interrupt) {
+ free(values);
+ return 0;
+ }
+ interrupt[count-1].values = values;
+ *interp = interrupt;
+ *countp = count;
+ return 1;
+}
+
+static char *
+extract_interrupt_name(char *buffer, char **suffix)
+{
+ char *s = buffer, *end;
+
+ while (isspace((int)*s)) /* find start of name */
+ s++;
+ for (end = s; *end && isalnum((int)*end); end++) { }
+ *end = '\0'; /* mark end of name */
+ *suffix = end + 1; /* mark values start */
+ return s;
+}
+
+static int
+extract_interrupt_lines(char *buffer, int ncolumns, int nlines)
+{
+ unsigned long id;
+ char *name, *end, *values;
+ int resize = (nlines >= lines_count);
+
+ name = extract_interrupt_name(buffer, &values);
+ id = strtoul(name, &end, 10);
+ if (*end != '\0')
+ return 0;
+ if (resize && !extend_interrupts(&interrupt_lines, &lines_count))
+ return 0;
+ end = extract_values(values, interrupt_lines[nlines].values, ncolumns);
+ if (resize)
+ initialise_interrupt(&interrupt_lines[nlines], id, name, end);
+ return 1;
+}
+
+static int
+extract_interrupt_errors(char *buffer)
+{
+ return (sscanf(buffer, " ERR: %u", &irq_err_count) == 1 ||
+ sscanf(buffer, "Err: %u", &irq_err_count) == 1 ||
+ sscanf(buffer, "BAD: %u", &irq_err_count) == 1);
+}
+
+static int
+extract_interrupt_misses(char *buffer)
+{
+ unsigned int irq_mis_count; /* not exported */
+ return sscanf(buffer, " MIS: %u", &irq_mis_count) == 1;
+}
+
+static int
+extract_interrupt_other(char *buffer, int ncolumns, int nlines)
+{
+ char *name, *end, *values;
+ int resize = (nlines >= other_count);
+
+ name = extract_interrupt_name(buffer, &values);
+ if (resize && !extend_interrupts(&interrupt_other, &other_count))
+ return 0;
+ end = extract_values(values, interrupt_other[nlines].values, ncolumns);
+ if (resize)
+ initialise_interrupt(&interrupt_other[nlines], nlines, name, end);
+ return 1;
+}
+
+int
+refresh_interrupt_values(void)
+{
+ FILE *fp;
+ char buf[4096];
+ int i, ncolumns;
+
+ if (cpu_count == 0) {
+ long ncpus = sysconf(_SC_NPROCESSORS_CONF);
+ online_cpumap = malloc(ncpus * sizeof(int));
+ if (!online_cpumap)
+ return -oserror();
+ cpu_count = ncpus;
+ }
+ memset(online_cpumap, 0, cpu_count * sizeof(int));
+
+ if ((fp = linux_statsfile("/proc/interrupts", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ /* first parse header, which maps online CPU number to column number */
+ if (fgets(buf, sizeof(buf), fp)) {
+ ncolumns = map_online_cpus(buf);
+ } else {
+ fclose(fp);
+ return -EINVAL; /* unrecognised file format */
+ }
+
+ /* next we parse each interrupt line row (starting with a digit) */
+ i = 0;
+ while (fgets(buf, sizeof(buf), fp))
+ if (!extract_interrupt_lines(buf, ncolumns, i++))
+ break;
+
+ /* parse other per-CPU interrupt counter rows (starts non-digit) */
+ i = 0;
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (extract_interrupt_errors(buf))
+ continue;
+ if (extract_interrupt_misses(buf))
+ continue;
+ if (!extract_interrupt_other(buf, ncolumns, i++))
+ break;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+static int
+refresh_interrupts(pmdaExt *pmda, __pmnsTree **tree)
+{
+ int i, sts, dom = pmda->e_domain;
+
+ if (interrupt_tree) {
+ *tree = interrupt_tree;
+ } else if ((sts = __pmNewPMNS(&interrupt_tree)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create interrupt names: %s\n",
+ pmProgname, pmErrStr(sts));
+ *tree = NULL;
+ } else if ((sts = refresh_interrupt_values()) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to update interrupt values: %s\n",
+ pmProgname, pmErrStr(sts));
+ *tree = NULL;
+ } else {
+ for (i = 0; i < lines_count; i++)
+ update_lines_pmns(dom, i, interrupt_lines[i].id);
+ for (i = 0; i < other_count; i++)
+ update_other_pmns(dom, i, interrupt_other[i].name);
+ *tree = interrupt_tree;
+ return 1;
+ }
+ return 0;
+}
+
+int
+interrupts_fetch(int cluster, int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (inst >= cpu_count)
+ return PM_ERR_INST;
+
+ switch (cluster) {
+ case CLUSTER_INTERRUPT_LINES:
+ if (item > lines_count)
+ return PM_ERR_PMID;
+ atom->ull = interrupt_lines[item].values[inst];
+ return 1;
+ case CLUSTER_INTERRUPT_OTHER:
+ if (item > other_count)
+ return PM_ERR_PMID;
+ atom->ull = interrupt_other[item].values[inst];
+ return 1;
+ }
+ return PM_ERR_PMID;
+}
+
+/*
+ * Create a new metric table entry based on an existing one.
+ */
+static void
+refresh_metrictable(pmdaMetric *source, pmdaMetric *dest, int id)
+{
+ int domain = pmid_domain(source->m_desc.pmid);
+ int cluster = pmid_cluster(source->m_desc.pmid);
+
+ memcpy(dest, source, sizeof(pmdaMetric));
+ dest->m_desc.pmid = pmid_build(domain, cluster, id);
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "interrupts refresh_metrictable: (%p -> %p) "
+ "metric ID dup: %d.%d.%d -> %d.%d.%d\n",
+ source, dest, domain, cluster,
+ pmid_item(source->m_desc.pmid), domain, cluster, id);
+}
+
+/*
+ * Needs to answer the question: how much extra space needs to be
+ * allocated in the metric table for (dynamic) interrupt metrics"?
+ * Return value is the number of additional entries/trees needed.
+ */
+static void
+size_metrictable(int *total, int *trees)
+{
+ *total = 2; /* lines and other */
+ *trees = lines_count > other_count ? lines_count : other_count;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "interrupts size_metrictable: %d total x %d trees\n",
+ *total, *trees);
+}
+
+static int
+interrupts_text(pmdaExt *pmda, pmID pmid, int type, char **buf)
+{
+ int item = pmid_item(pmid);
+ int cluster = pmid_cluster(pmid);
+
+ switch (cluster) {
+ case CLUSTER_INTERRUPT_LINES:
+ if (item > lines_count)
+ return PM_ERR_PMID;
+ if (interrupt_lines[item].text == NULL)
+ return PM_ERR_TEXT;
+ *buf = interrupt_lines[item].text;
+ return 0;
+ case CLUSTER_INTERRUPT_OTHER:
+ if (item > other_count)
+ return PM_ERR_PMID;
+ if (interrupt_other[item].text == NULL)
+ return PM_ERR_TEXT;
+ *buf = interrupt_other[item].text;
+ return 0;
+ }
+ return PM_ERR_PMID;
+}
+
+void
+interrupts_init(pmdaMetric *metrictable, int nmetrics)
+{
+ int set[] = { CLUSTER_INTERRUPT_LINES, CLUSTER_INTERRUPT_OTHER };
+
+ pmdaDynamicPMNS("kernel.percpu.interrupts",
+ set, sizeof(set)/sizeof(int),
+ refresh_interrupts, interrupts_text,
+ refresh_metrictable, size_metrictable,
+ metrictable, nmetrics);
+}
diff --git a/src/pmdas/linux/interrupts.h b/src/pmdas/linux/interrupts.h
new file mode 100644
index 0000000..b8a0336
--- /dev/null
+++ b/src/pmdas/linux/interrupts.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+extern unsigned int irq_err_count;
+
+extern void interrupts_init(pmdaMetric *, int);
+extern int refresh_interrupt_values(void);
+extern int interrupts_fetch(int, int, unsigned int, pmAtomValue *);
diff --git a/src/pmdas/linux/linux_table.c b/src/pmdas/linux/linux_table.c
new file mode 100644
index 0000000..d04f454
--- /dev/null
+++ b/src/pmdas/linux/linux_table.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#include "linux_table.h"
+
+extern int linux_table_lookup(const char *field, struct linux_table *table, uint64_t *val);
+extern struct linux_table *linux_table_clone(struct linux_table *table);
+extern int linux_table_scan(FILE *fp, struct linux_table *table);
+
+inline int
+linux_table_lookup(const char *field, struct linux_table *table, uint64_t *val)
+{
+ struct linux_table *t;
+
+ for (t=table; t && t->field; t++) {
+ if (strncmp(field, t->field, t->field_len) == 0) {
+ if (t->valid) {
+ *val = t->val;
+ return 1;
+ }
+ /* Invalid */
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Warning: linux_table_lookup failed for \"%s\"\n", field);
+ return 0;
+}
+
+inline struct linux_table *
+linux_table_clone(struct linux_table *table)
+{
+ struct linux_table *ret;
+ struct linux_table *t;
+ int len;
+
+ if (!table)
+ return NULL;
+ for (len=1, t=table; t->field; t++)
+ len++;
+ ret = (struct linux_table *)malloc(len * sizeof(struct linux_table));
+ if (!ret)
+ return NULL;
+ memcpy(ret, table, len * sizeof(struct linux_table));
+
+ /* Initialize the table */
+ for (t=ret; t && t->field; t++) {
+ if (!t->field_len)
+ t->field_len = strlen(t->field);
+ t->valid = LINUX_TABLE_INVALID;
+ }
+
+ return ret;
+}
+
+inline int
+linux_table_scan(FILE *fp, struct linux_table *table)
+{
+ char *p;
+ struct linux_table *t;
+ char buf[1024];
+ int ret = 0;
+
+ while(fgets(buf, sizeof(buf), fp) != NULL) {
+ for (t=table; t && t->field; t++) {
+ if ((p = strstr(buf, t->field)) != NULL) {
+ /* first digit after the matched field */
+ for (p += t->field_len; *p; p++) {
+ if (isdigit((int)*p))
+ break;
+ }
+ if (isdigit((int)*p)) {
+ t->this = strtoul(p, NULL, 10);
+ t->valid = LINUX_TABLE_VALID;
+ ret++;
+ break;
+ }
+ }
+ }
+ }
+
+ /* calculate current value, accounting for counter wrap */
+ for (t=table; t && t->field; t++) {
+ if (t->maxval == 0)
+ /* instantaneous value */
+ t->val = t->this;
+ else {
+ /* counter value */
+ if (t->this >= t->prev)
+ t->val += t->this - t->prev;
+ else
+ t->val += t->this + (t->maxval - t->prev);
+ t->prev = t->this;
+ }
+ }
+
+ return ret;
+}
diff --git a/src/pmdas/linux/linux_table.h b/src/pmdas/linux/linux_table.h
new file mode 100644
index 0000000..3900690
--- /dev/null
+++ b/src/pmdas/linux/linux_table.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _LINUX_TABLE_H
+#define _LINUX_TABLE_H
+/*
+ * scans linux style /proc tables, e.g. :
+ *
+ * numa_hit 266809
+ * numa_miss 0
+ * numa_foreign 0
+ * interleave_hit 0
+ * local_node 265680
+ * other_node 1129
+ *
+ * Value is a counter that wraps at maxval,
+ * unless maxval is 0, in which case the
+ * value is treated as instantaneous and no
+ * wrap detection is attempted.
+ *
+ * Tables are typically declared as a static array, and
+ * then allocated dynamically with linux_table_clone().
+ * e.g. :
+ *
+ * static struct linux_table numa_meminfo_table[] = {
+ * { "numa_hit", 0xffffffffffffffff },
+ * { "numa_miss", 0xffffffffffffffff },
+ * { "numa_foreign", 0xffffffffffffffff },
+ * { "interleave_hit", 0xffffffffffffffff },
+ * { "local_node", 0xffffffffffffffff },
+ * { "other_node", 0xffffffffffffffff },
+ * { NULL };
+ * };
+ */
+
+enum {
+ LINUX_TABLE_INVALID,
+ LINUX_TABLE_VALID
+};
+
+struct linux_table {
+ char *field;
+ uint64_t maxval;
+ uint64_t val;
+ uint64_t this;
+ uint64_t prev;
+ int field_len;
+ int valid;
+};
+
+extern int linux_table_lookup(const char *field, struct linux_table *table, uint64_t *val);
+extern struct linux_table *linux_table_clone(struct linux_table *table);
+extern int linux_table_scan(FILE *fp, struct linux_table *table);
+
+#endif /* _LINUX_TABLE_H */
diff --git a/src/pmdas/linux/msg_limits.c b/src/pmdas/linux/msg_limits.c
new file mode 100644
index 0000000..afac20e
--- /dev/null
+++ b/src/pmdas/linux/msg_limits.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ * This code contributed by Mike Mason <mmlnx@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define __USE_GNU 1 /* required for IPC_INFO define */
+#include <sys/ipc.h>
+#include <sys/msg.h>
+
+#include "pmapi.h"
+#include "msg_limits.h"
+
+int
+refresh_msg_limits(msg_limits_t *msg_limits)
+{
+ static struct msginfo msginfo;
+ static int started;
+
+ if (!started) {
+ started = 1;
+ memset(msg_limits, 0, sizeof(msg_limits_t));
+ }
+
+ if (msgctl(0, IPC_INFO, (struct msqid_ds *) &msginfo) < 0) {
+ return -oserror();
+ }
+
+ msg_limits->msgpool = msginfo.msgpool;
+ msg_limits->msgmap = msginfo.msgmap;
+ msg_limits->msgmax = msginfo.msgmax;
+ msg_limits->msgmnb = msginfo.msgmnb;
+ msg_limits->msgmni = msginfo.msgmni;
+ msg_limits->msgssz = msginfo.msgssz;
+ msg_limits->msgtql = msginfo.msgtql;
+ msg_limits->msgseg = msginfo.msgseg;
+
+ /* success */
+ return 0;
+}
diff --git a/src/pmdas/linux/msg_limits.h b/src/pmdas/linux/msg_limits.h
new file mode 100644
index 0000000..06fb7c5
--- /dev/null
+++ b/src/pmdas/linux/msg_limits.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Mike Mason (mmlnx@us.ibm.com)
+ */
+
+typedef struct {
+ unsigned int msgpool; /* size of message pool (kbytes) */
+ unsigned int msgmap; /* # of entries in message map */
+ unsigned int msgmax; /* maximum size of a message */
+ unsigned int msgmnb; /* default maximum size of message queue */
+ unsigned int msgmni; /* maximum # of message queue identifiers */
+ unsigned int msgssz; /* message segment size */
+ unsigned int msgtql; /* # of system message headers */
+ unsigned int msgseg; /* maximum # of message segments */
+} msg_limits_t;
+
+extern int refresh_msg_limits(msg_limits_t*);
+
diff --git a/src/pmdas/linux/numa_meminfo.c b/src/pmdas/linux/numa_meminfo.c
new file mode 100644
index 0000000..22d8351
--- /dev/null
+++ b/src/pmdas/linux/numa_meminfo.c
@@ -0,0 +1,137 @@
+/*
+ * Linux NUMA meminfo metrics cluster from sysfs
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2009 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "linux_table.h"
+#include "proc_cpuinfo.h"
+#include "proc_stat.h"
+#include "numa_meminfo.h"
+
+/* sysfs file for numa meminfo */
+static struct linux_table numa_meminfo_table[] = {
+ { field: "MemTotal:", maxval: 0x0 },
+ { field: "MemFree:", maxval: 0x0 },
+ { field: "MemUsed:", maxval: 0x0 },
+ { field: "Active:", maxval: 0x0 },
+ { field: "Inactive:", maxval: 0x0 },
+ { field: "Active(anon):", maxval: 0x0 },
+ { field: "Inactive(anon):", maxval: 0x0 },
+ { field: "Active(file):", maxval: 0x0 },
+ { field: "Inactive(file):", maxval: 0x0 },
+ { field: "HighTotal:", maxval: 0x0 },
+ { field: "HighFree:", maxval: 0x0 },
+ { field: "LowTotal:", maxval: 0x0 },
+ { field: "LowFree:", maxval: 0x0 },
+ { field: "Unevictable:", maxval: 0x0 },
+ { field: "Mlocked:", maxval: 0x0 },
+ { field: "Dirty:", maxval: 0x0 },
+ { field: "Writeback:", maxval: 0x0 },
+ { field: "FilePages:", maxval: 0x0 },
+ { field: "Mapped:", maxval: 0x0 },
+ { field: "AnonPages:", maxval: 0x0 },
+ { field: "Shmem:", maxval: 0x0 },
+ { field: "KernelStack:", maxval: 0x0 },
+ { field: "PageTables:", maxval: 0x0 },
+ { field: "NFS_Unstable:", maxval: 0x0 },
+ { field: "Bounce:", maxval: 0x0 },
+ { field: "WritebackTmp:", maxval: 0x0 },
+ { field: "Slab:", maxval: 0x0 },
+ { field: "SReclaimable:", maxval: 0x0 },
+ { field: "SUnreclaim:", maxval: 0x0 },
+ { field: "HugePages_Total:", maxval: 0x0 },
+ { field: "HugePages_Free:", maxval: 0x0 },
+ { field: "HugePages_Surp:", maxval: 0x0 },
+ { field: NULL }
+};
+
+/* sysfs file for numastat */
+static struct linux_table numa_memstat_table[] = {
+ { field: "numa_hit", maxval: ULONGLONG_MAX },
+ { field: "numa_miss", maxval: ULONGLONG_MAX },
+ { field: "numa_foreign", maxval: ULONGLONG_MAX },
+ { field: "interleave_hit", maxval: ULONGLONG_MAX },
+ { field: "local_node", maxval: ULONGLONG_MAX },
+ { field: "other_node", maxval: ULONGLONG_MAX },
+ { field: NULL }
+};
+
+int refresh_numa_meminfo(numa_meminfo_t *numa_meminfo, proc_cpuinfo_t *proc_cpuinfo, proc_stat_t *proc_stat)
+{
+ int i;
+ FILE *fp;
+ pmdaIndom *idp = PMDAINDOM(NODE_INDOM);
+ static int started;
+
+ /* First time only */
+ if (!started) {
+ refresh_proc_stat(proc_cpuinfo, proc_stat);
+
+ if (!numa_meminfo->node_info) /* may have allocated this, but failed below */
+ numa_meminfo->node_info = (nodeinfo_t *)calloc(idp->it_numinst, sizeof(nodeinfo_t));
+ if (!numa_meminfo->node_info) {
+ fprintf(stderr, "%s: error allocating numa node_info: %s\n",
+ __FUNCTION__, osstrerror());
+ return -1;
+ }
+
+ for (i = 0; i < idp->it_numinst; i++) {
+ numa_meminfo->node_info[i].meminfo = linux_table_clone(numa_meminfo_table);
+ if (!numa_meminfo->node_info[i].meminfo) {
+ fprintf(stderr, "%s: error allocating meminfo: %s\n",
+ __FUNCTION__, osstrerror());
+ return -1;
+ }
+ numa_meminfo->node_info[i].memstat = linux_table_clone(numa_memstat_table);
+ if (!numa_meminfo->node_info[i].memstat) {
+ fprintf(stderr, "%s: error allocating memstat: %s\n",
+ __FUNCTION__, osstrerror());
+ return -1;
+ }
+ }
+
+ numa_meminfo->node_indom = idp;
+ started = 1;
+ }
+
+ /* Refresh */
+ for (i = 0; i < idp->it_numinst; i++) {
+ char buf[MAXPATHLEN];
+
+ snprintf(buf, sizeof(buf), "%s/sys/devices/system/node/node%d/meminfo",
+ linux_statspath, i);
+ if ((fp = fopen(buf, "r")) != NULL) {
+ linux_table_scan(fp, numa_meminfo->node_info[i].meminfo);
+ fclose(fp);
+ }
+
+ snprintf(buf, sizeof(buf), "%s/sys/devices/system/node/node%d/numastat",
+ linux_statspath, i);
+ if ((fp = fopen(buf, "r")) != NULL) {
+ linux_table_scan(fp, numa_meminfo->node_info[i].memstat);
+ fclose(fp);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/pmdas/linux/numa_meminfo.h b/src/pmdas/linux/numa_meminfo.h
new file mode 100644
index 0000000..22c1289
--- /dev/null
+++ b/src/pmdas/linux/numa_meminfo.h
@@ -0,0 +1,32 @@
+/*
+ * Linux NUMA meminfo metrics cluster from sysfs
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2009 Silicon Graphics Inc., All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Information from /sys/devices/node/node[0-9]+/meminfo and numastat
+ */
+typedef struct {
+ struct linux_table *meminfo;
+ struct linux_table *memstat;
+} nodeinfo_t;
+
+typedef struct {
+ nodeinfo_t *node_info;
+ pmdaIndom *node_indom;
+} numa_meminfo_t;
+
+extern int refresh_numa_meminfo(numa_meminfo_t *, proc_cpuinfo_t *, proc_stat_t *);
+
diff --git a/src/pmdas/linux/pmda.c b/src/pmdas/linux/pmda.c
new file mode 100644
index 0000000..73d961a
--- /dev/null
+++ b/src/pmdas/linux/pmda.c
@@ -0,0 +1,5807 @@
+/*
+ * Linux PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2007-2011 Aconex. All Rights Reserved.
+ * Copyright (c) 2002 International Business Machines Corp.
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#undef LINUX /* defined in NSS/NSPR headers as something different, which we do not need. */
+#include "domain.h"
+
+#include <ctype.h>
+#include <sys/vfs.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <sys/utsname.h>
+#include <utmp.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "convert.h"
+#include "clusters.h"
+#include "indom.h"
+
+#include "proc_cpuinfo.h"
+#include "proc_stat.h"
+#include "proc_meminfo.h"
+#include "proc_loadavg.h"
+#include "proc_net_dev.h"
+#include "filesys.h"
+#include "swapdev.h"
+#include "getinfo.h"
+#include "proc_net_rpc.h"
+#include "proc_net_sockstat.h"
+#include "proc_net_tcp.h"
+#include "proc_partitions.h"
+#include "proc_net_netstat.h"
+#include "proc_net_snmp.h"
+#include "proc_scsi.h"
+#include "proc_slabinfo.h"
+#include "proc_uptime.h"
+#include "sem_limits.h"
+#include "msg_limits.h"
+#include "shm_limits.h"
+#include "proc_sys_fs.h"
+#include "proc_vmstat.h"
+#include "sysfs_kernel.h"
+#include "linux_table.h"
+#include "numa_meminfo.h"
+#include "interrupts.h"
+#include "devmapper.h"
+
+static proc_stat_t proc_stat;
+static proc_meminfo_t proc_meminfo;
+static proc_loadavg_t proc_loadavg;
+static proc_net_rpc_t proc_net_rpc;
+static proc_net_tcp_t proc_net_tcp;
+static proc_net_sockstat_t proc_net_sockstat;
+static struct utsname kernel_uname;
+static char uname_string[sizeof(kernel_uname)];
+static proc_scsi_t proc_scsi;
+static dev_mapper_t dev_mapper;
+static proc_cpuinfo_t proc_cpuinfo;
+static proc_slabinfo_t proc_slabinfo;
+static sem_limits_t sem_limits;
+static msg_limits_t msg_limits;
+static shm_limits_t shm_limits;
+static proc_uptime_t proc_uptime;
+static proc_sys_fs_t proc_sys_fs;
+static sysfs_kernel_t sysfs_kernel;
+static numa_meminfo_t numa_meminfo;
+
+static int _isDSO = 1; /* =0 I am a daemon */
+static char *username;
+
+/* globals */
+size_t _pm_system_pagesize; /* for hinv.pagesize and used elsewhere */
+int _pm_have_proc_vmstat; /* if /proc/vmstat is available */
+int _pm_intr_size; /* size in bytes of interrupt sum count metric */
+int _pm_ctxt_size; /* size in bytes of context switch count metric */
+int _pm_cputime_size; /* size in bytes of most of the cputime metrics */
+int _pm_idletime_size; /* size in bytes of the idle cputime metric */
+proc_vmstat_t _pm_proc_vmstat;
+proc_net_snmp_t _pm_proc_net_snmp;
+pmdaInstid _pm_proc_net_snmp_indom_id[NR_ICMPMSG_COUNTERS];
+proc_net_netstat_t _pm_proc_net_netstat;
+
+/*
+ * Metric Instance Domains (statically initialized ones only)
+ */
+static pmdaInstid loadavg_indom_id[] = {
+ { 1, "1 minute" }, { 5, "5 minute" }, { 15, "15 minute" }
+};
+
+static pmdaInstid nfs_indom_id[] = {
+ { 0, "null" },
+ { 1, "getattr" },
+ { 2, "setattr" },
+ { 3, "root" },
+ { 4, "lookup" },
+ { 5, "readlink" },
+ { 6, "read" },
+ { 7, "wrcache" },
+ { 8, "write" },
+ { 9, "create" },
+ { 10, "remove" },
+ { 11, "rename" },
+ { 12, "link" },
+ { 13, "symlink" },
+ { 14, "mkdir" },
+ { 15, "rmdir" },
+ { 16, "readdir" },
+ { 17, "statfs" }
+};
+
+static pmdaInstid nfs3_indom_id[] = {
+ { 0, "null" },
+ { 1, "getattr" },
+ { 2, "setattr" },
+ { 3, "lookup" },
+ { 4, "access" },
+ { 5, "readlink" },
+ { 6, "read" },
+ { 7, "write" },
+ { 8, "create" },
+ { 9, "mkdir" },
+ { 10, "symlink" },
+ { 11, "mknod" },
+ { 12, "remove" },
+ { 13, "rmdir" },
+ { 14, "rename" },
+ { 15, "link" },
+ { 16, "readdir" },
+ { 17, "readdir+" },
+ { 18, "statfs" },
+ { 19, "fsinfo" },
+ { 20, "pathconf" },
+ { 21, "commit" }
+};
+
+static pmdaInstid nfs4_cli_indom_id[] = {
+ { 0, "null" },
+ { 1, "read" },
+ { 2, "write" },
+ { 3, "commit" },
+ { 4, "open" },
+ { 5, "open_conf" },
+ { 6, "open_noat" },
+ { 7, "open_dgrd" },
+ { 8, "close" },
+ { 9, "setattr" },
+ { 10, "fsinfo" },
+ { 11, "renew" },
+ { 12, "setclntid" },
+ { 13, "confirm" },
+ { 14, "lock" },
+ { 15, "lockt" },
+ { 16, "locku" },
+ { 17, "access" },
+ { 18, "getattr" },
+ { 19, "lookup" },
+ { 20, "lookup_root" },
+ { 21, "remove" },
+ { 22, "rename" },
+ { 23, "link" },
+ { 24, "symlink" },
+ { 25, "create" },
+ { 26, "pathconf" },
+ { 27, "statfs" },
+ { 28, "readlink" },
+ { 29, "readdir" },
+ { 30, "server_caps" },
+ { 31, "delegreturn" },
+ { 32, "getacl" },
+ { 33, "setacl" },
+ { 34, "fs_locatns" },
+};
+
+static pmdaInstid nfs4_svr_indom_id[] = {
+ { 0, "null" },
+ { 1, "op0-unused" },
+ { 2, "op1-unused"},
+ { 3, "minorversion"}, /* future use */
+ { 4, "access" },
+ { 5, "close" },
+ { 6, "commit" },
+ { 7, "create" },
+ { 8, "delegpurge" },
+ { 9, "delegreturn" },
+ { 10, "getattr" },
+ { 11, "getfh" },
+ { 12, "link" },
+ { 13, "lock" },
+ { 14, "lockt" },
+ { 15, "locku" },
+ { 16, "lookup" },
+ { 17, "lookup_root" },
+ { 18, "nverify" },
+ { 19, "open" },
+ { 20, "openattr" },
+ { 21, "open_conf" },
+ { 22, "open_dgrd" },
+ { 23, "putfh" },
+ { 24, "putpubfh" },
+ { 25, "putrootfh" },
+ { 26, "read" },
+ { 27, "readdir" },
+ { 28, "readlink" },
+ { 29, "remove" },
+ { 30, "rename" },
+ { 31, "renew" },
+ { 32, "restorefh" },
+ { 33, "savefh" },
+ { 34, "secinfo" },
+ { 35, "setattr" },
+ { 36, "setcltid" },
+ { 37, "setcltidconf" },
+ { 38, "verify" },
+ { 39, "write" },
+ { 40, "rellockowner" },
+};
+
+static pmdaIndom indomtab[] = {
+ { CPU_INDOM, 0, NULL },
+ { DISK_INDOM, 0, NULL }, /* cached */
+ { LOADAVG_INDOM, 3, loadavg_indom_id },
+ { NET_DEV_INDOM, 0, NULL },
+ { PROC_INTERRUPTS_INDOM, 0, NULL }, /* deprecated */
+ { FILESYS_INDOM, 0, NULL },
+ { SWAPDEV_INDOM, 0, NULL },
+ { NFS_INDOM, NR_RPC_COUNTERS, nfs_indom_id },
+ { NFS3_INDOM, NR_RPC3_COUNTERS, nfs3_indom_id },
+ { PROC_PROC_INDOM, 0, NULL }, /* migrated to the proc PMDA */
+ { PARTITIONS_INDOM, 0, NULL }, /* cached */
+ { SCSI_INDOM, 0, NULL },
+ { SLAB_INDOM, 0, NULL },
+ { STRINGS_INDOM, 0, NULL },
+ { NFS4_CLI_INDOM, NR_RPC4_CLI_COUNTERS, nfs4_cli_indom_id },
+ { NFS4_SVR_INDOM, NR_RPC4_SVR_COUNTERS, nfs4_svr_indom_id },
+ { QUOTA_PRJ_INDOM, 0, NULL }, /* migrated to the xfs PMDA */
+ { NET_ADDR_INDOM, 0, NULL },
+ { TMPFS_INDOM, 0, NULL },
+ { NODE_INDOM, 0, NULL },
+ { PROC_CGROUP_SUBSYS_INDOM, 0, NULL },
+ { PROC_CGROUP_MOUNTS_INDOM, 0, NULL },
+ { LV_INDOM, 0, NULL },
+ { ICMPMSG_INDOM, NR_ICMPMSG_COUNTERS, _pm_proc_net_snmp_indom_id },
+ { DM_INDOM, 0, NULL }, /* cached */
+};
+
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+static pmdaMetric metrictab[] = {
+
+/*
+ * /proc/stat cluster
+ */
+
+/* kernel.percpu.cpu.user */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,0), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,1), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.sys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,2), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.idle */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,3), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.wait.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,30), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.intr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,31), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.irq.soft */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,56), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.irq.hard */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,57), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.steal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,58), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.guest */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,61), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.percpu.cpu.vuser */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,76), KERNEL_UTYPE, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+
+/* kernel.pernode.cpu.user */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,62), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,63), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.sys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,64), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.idle */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,65), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.wait.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,69), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.intr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,66), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.irq.soft */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,70), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.irq.hard */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,71), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.steal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,67), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.guest */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,68), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.pernode.cpu.vuser */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,77), KERNEL_UTYPE, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.dev.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,4), KERNEL_ULONG, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,5), KERNEL_ULONG, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,6), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,7), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.avactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,46), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.dev.aveq */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,47), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.dev.read_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,49), KERNEL_ULONG, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.write_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,50), KERNEL_ULONG, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.scheduler */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,59), PM_TYPE_STRING, DISK_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* disk.dev.read_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,72), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.dev.write_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,73), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.all.avactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.all.aveq */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.all.read_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.write_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.read_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.all.read_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* swap.pagesin */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* swap.pagesout */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* swap.in */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* swap.out */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* kernel.all.intr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,12), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* kernel.all.pswitch */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,13), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* kernel.all.sysfork */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,14), KERNEL_ULONG, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* kernel.all.cpu.user */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,20), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,21), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.sys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,22), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.idle */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,23), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.intr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,34), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.wait.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,35), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.irq.soft */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,53), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.irq.hard */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,54), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.steal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,55), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.guest */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,60), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* kernel.all.cpu.vuser */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,78), KERNEL_UTYPE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+/* disk.all.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,28), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.dev.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,36), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.all.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* hinv.ncpu */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.ndisk */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.nnode */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* kernel.all.hz */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,48), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) }, },
+
+/*
+ * /proc/uptime cluster
+ * Uptime modified and idletime added by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+/* kernel.all.uptime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_UPTIME,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) }, },
+
+/* kernel.all.idletime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_UPTIME,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) }, },
+
+/*
+ * /proc/meminfo cluster
+ */
+
+/* mem.physmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.shared */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.bufmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.cached */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.inactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.swapCached */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.highTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.highFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.lowTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.lowFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.swapTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.swapFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.dirty */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.writeback */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.mapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.slab */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.committed_AS */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.pageTables */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.reverseMaps */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.cache_clean */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.anonpages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.commitLimit */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.bounce */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.NFS_Unstable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.slabReclaimable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.slabUnreclaimable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.active_anon */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.inactive_anon */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.active_file */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,38), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.inactive_file */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,39), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.unevictable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.mlocked */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,41), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.shmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,42), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.kernelStack */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,43), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.hugepagesTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.util.hugepagesFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.util.hugepagesRsvd */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.util.hugepagesSurp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.util.directMap4k */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.directMap2M */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.vmallocTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.vmallocUsed */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.vmallocChunk */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.mmap_copy */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.quicklists */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.corrupthardware */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.mmap_copy */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.directMap1G */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.util.available */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,0), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,1), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,2), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,3), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.inactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,4), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.active_anon */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,5), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.inactive_anon */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,6), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.active_file */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,7), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.inactive_file */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,8), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.highTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,9), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.highFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,10), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.lowTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,11), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.lowFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,12), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.unevictable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,13), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.mlocked */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,14), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.dirty */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,15), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.writeback */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,16), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.filePages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,17), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.mapped */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,18), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.anonpages */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,19), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.shmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,20), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.kernelStack */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,21), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.pageTables */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,22), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.NFS_Unstable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,23), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.bounce */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,24), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.writebackTmp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,25), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.slab */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,26), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.slabReclaimable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,27), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.slabUnreclaimable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,28), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* mem.numa.util.hugepagesTotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,29), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.util.hugepagesFree */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,30), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.util.hugepagesSurp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,31), PM_TYPE_U64, NODE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.hit */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,32), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.miss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,33), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.foreign */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,34), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.interleave_hit */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,35), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.local_node */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,36), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* mem.numa.alloc.other_node */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUMA_MEMINFO,37), PM_TYPE_U64, NODE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+
+/* swap.length */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* swap.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* swap.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* hinv.physmem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_MBYTE,0,0) }, },
+
+/* mem.freemem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* hinv.pagesize */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* mem.util.other */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MEMINFO,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/*
+ * /proc/slabinfo cluster
+ */
+
+ /* mem.slabinfo.objects.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,0), PM_TYPE_U64, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.objects.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,1), PM_TYPE_U64, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.objects.size */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,2), PM_TYPE_U32, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+ /* mem.slabinfo.slabs.active */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,3), PM_TYPE_U32, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.slabs.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,4), PM_TYPE_U32, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.slabs.pages_per_slab */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,5), PM_TYPE_U32, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.slabs.objects_per_slab */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,6), PM_TYPE_U32, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* mem.slabinfo.slabs.total_size */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SLAB,7), PM_TYPE_U64, SLAB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/*
+ * /proc/loadavg cluster
+ */
+
+ /* kernel.all.load */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LOADAVG,0), PM_TYPE_FLOAT, LOADAVG_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* kernel.all.lastpid -- added by Mike Mason <mmlnx@us.ibm.com> */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LOADAVG, 1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* kernel.all.runnable */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LOADAVG, 2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+ /* kernel.all.nprocs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LOADAVG, 3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/*
+ * /proc/net/dev cluster
+ */
+
+/* network.interface.in.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,0), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* network.interface.in.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,1), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,2), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.drops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,3), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.fifo */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,4), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.frame */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,5), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.compressed */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,6), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.in.mcasts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,7), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,8), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* network.interface.out.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,9), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,10), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.drops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,11), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.fifo */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,12), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.collisions */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,13), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.carrier */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,14), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.out.compressed */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,15), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.total.bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,16), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+
+/* network.interface.total.packets */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,17), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.total.errors */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,18), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.total.drops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,19), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.total.mcasts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,20), PM_TYPE_U64, NET_DEV_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* network.interface.mtu */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,21), PM_TYPE_U32, NET_DEV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,0,PM_SPACE_BYTE,0) }, },
+
+/* network.interface.speed */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,22), PM_TYPE_FLOAT, NET_DEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_MBYTE,PM_TIME_SEC,0) }, },
+
+/* network.interface.baudrate */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,23), PM_TYPE_U32, NET_DEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) }, },
+
+/* network.interface.duplex */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,24), PM_TYPE_U32, NET_DEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.up */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,25), PM_TYPE_U32, NET_DEV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.running */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,26), PM_TYPE_U32, NET_DEV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.ninterface */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_DEV,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.inet_addr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_ADDR,0), PM_TYPE_STRING, NET_ADDR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.ipv6_addr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_ADDR,1), PM_TYPE_STRING, NET_ADDR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.ipv6_scope */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_ADDR,2), PM_TYPE_STRING, NET_ADDR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* network.interface.hw_addr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_ADDR,3), PM_TYPE_STRING, NET_ADDR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/*
+ * filesys cluster
+ */
+
+/* hinv.nmounts */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* filesys.capacity */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,1), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* filesys.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,2), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* filesys.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,3), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* filesys.maxfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,4), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* filesys.usedfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,5), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* filesys.freefiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,6), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* filesys.mountdir */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,7), PM_TYPE_STRING, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* filesys.full */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,8), PM_TYPE_DOUBLE, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* filesys.blocksize */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,9), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+/* filesys.avail */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,10), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* filesys.readonly */
+ { NULL,
+ { PMDA_PMID(CLUSTER_FILESYS,11), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/*
+ * tmpfs filesystem cluster
+ */
+
+/* tmpfs.capacity */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,1), PM_TYPE_U64, TMPFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* tmpfs.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,2), PM_TYPE_U64, TMPFS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* tmpfs.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,3), PM_TYPE_U64, TMPFS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* tmpfs.maxfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,4), PM_TYPE_U32, TMPFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* tmpfs.usedfiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,5), PM_TYPE_U32, TMPFS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* tmpfs.freefiles */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,6), PM_TYPE_U32, TMPFS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* tmpfs.full */
+ { NULL,
+ { PMDA_PMID(CLUSTER_TMPFS,7), PM_TYPE_DOUBLE, TMPFS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/*
+ * swapdev cluster
+ */
+
+/* swapdev.free */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SWAPDEV,0), PM_TYPE_U32, SWAPDEV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* swapdev.length */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SWAPDEV,1), PM_TYPE_U32, SWAPDEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* swapdev.maxswap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SWAPDEV,2), PM_TYPE_U32, SWAPDEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* swapdev.vlength */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SWAPDEV,3), PM_TYPE_U32, SWAPDEV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* swapdev.priority */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SWAPDEV,4), PM_TYPE_32, SWAPDEV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/*
+ * socket stat cluster
+ */
+
+/* network.sockstat.tcp.inuse */
+ { &proc_net_sockstat.tcp[_PM_SOCKSTAT_INUSE],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.tcp.highest */
+ { &proc_net_sockstat.tcp[_PM_SOCKSTAT_HIGHEST],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.tcp.util */
+ { &proc_net_sockstat.tcp[_PM_SOCKSTAT_UTIL],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* network.sockstat.udp.inuse */
+ { &proc_net_sockstat.udp[_PM_SOCKSTAT_INUSE],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.udp.highest */
+ { &proc_net_sockstat.udp[_PM_SOCKSTAT_HIGHEST],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.udp.util */
+ { &proc_net_sockstat.udp[_PM_SOCKSTAT_UTIL],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* network.sockstat.raw.inuse */
+ { &proc_net_sockstat.raw[_PM_SOCKSTAT_INUSE],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,6), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.raw.highest */
+ { &proc_net_sockstat.raw[_PM_SOCKSTAT_HIGHEST],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,7), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.sockstat.raw.util */
+ { &proc_net_sockstat.raw[_PM_SOCKSTAT_UTIL],
+ { PMDA_PMID(CLUSTER_NET_SOCKSTAT,8), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/*
+ * nfs cluster
+ */
+
+/* nfs.client.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs.client.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,4), PM_TYPE_U32, NFS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs.server.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,50), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs.server.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,12), PM_TYPE_U32, NFS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs3.client.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,60), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs3.client.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,61), PM_TYPE_U32, NFS3_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs3.server.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,62), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs3.server.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,63), PM_TYPE_U32, NFS3_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs4.client.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,64), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs4.client.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,65), PM_TYPE_U32, NFS4_CLI_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs4.server.calls */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,66), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* nfs4.server.reqs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NET_NFS,67), PM_TYPE_U32, NFS4_SVR_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.rpccnt */
+ { &proc_net_rpc.client.rpccnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.rpcretrans */
+ { &proc_net_rpc.client.rpcretrans,
+ { PMDA_PMID(CLUSTER_NET_NFS,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.rpcauthrefresh */
+ { &proc_net_rpc.client.rpcauthrefresh,
+ { PMDA_PMID(CLUSTER_NET_NFS,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.netcnt */
+ { &proc_net_rpc.client.netcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.netudpcnt */
+ { &proc_net_rpc.client.netudpcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,25), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.nettcpcnt */
+ { &proc_net_rpc.client.nettcpcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,26), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.client.nettcpconn */
+ { &proc_net_rpc.client.nettcpconn,
+ { PMDA_PMID(CLUSTER_NET_NFS,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rpccnt */
+ { &proc_net_rpc.server.rpccnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,30), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rpcerr */
+ { &proc_net_rpc.server.rpcerr,
+ { PMDA_PMID(CLUSTER_NET_NFS,31), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rpcbadfmt */
+ { &proc_net_rpc.server.rpcbadfmt,
+ { PMDA_PMID(CLUSTER_NET_NFS,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rpcbadauth */
+ { &proc_net_rpc.server.rpcbadauth,
+ { PMDA_PMID(CLUSTER_NET_NFS,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rpcbadclnt */
+ { &proc_net_rpc.server.rpcbadclnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rchits */
+ { &proc_net_rpc.server.rchits,
+ { PMDA_PMID(CLUSTER_NET_NFS,35), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rcmisses */
+ { &proc_net_rpc.server.rcmisses,
+ { PMDA_PMID(CLUSTER_NET_NFS,36), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.rcnocache */
+ { &proc_net_rpc.server.rcnocache,
+ { PMDA_PMID(CLUSTER_NET_NFS,37), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_cached */
+ { &proc_net_rpc.server.fh_cached,
+ { PMDA_PMID(CLUSTER_NET_NFS,38), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_valid */
+ { &proc_net_rpc.server.fh_valid,
+ { PMDA_PMID(CLUSTER_NET_NFS,39), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_fixup */
+ { &proc_net_rpc.server.fh_fixup,
+ { PMDA_PMID(CLUSTER_NET_NFS,40), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_lookup */
+ { &proc_net_rpc.server.fh_lookup,
+ { PMDA_PMID(CLUSTER_NET_NFS,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_stale */
+ { &proc_net_rpc.server.fh_stale,
+ { PMDA_PMID(CLUSTER_NET_NFS,42), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_concurrent */
+ { &proc_net_rpc.server.fh_concurrent,
+ { PMDA_PMID(CLUSTER_NET_NFS,43), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.netcnt */
+ { &proc_net_rpc.server.netcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,44), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.netudpcnt */
+ { &proc_net_rpc.server.netudpcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,45), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.nettcpcnt */
+ { &proc_net_rpc.server.nettcpcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,46), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.nettcpconn */
+ { &proc_net_rpc.server.nettcpcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,47), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_anon */
+ { &proc_net_rpc.server.fh_anon,
+ { PMDA_PMID(CLUSTER_NET_NFS,51), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_nocache_dir */
+ { &proc_net_rpc.server.fh_nocache_dir,
+ { PMDA_PMID(CLUSTER_NET_NFS,52), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.fh_nocache_nondir */
+ { &proc_net_rpc.server.fh_nocache_nondir,
+ { PMDA_PMID(CLUSTER_NET_NFS,53), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.io_read */
+ { &proc_net_rpc.server.io_read,
+ { PMDA_PMID(CLUSTER_NET_NFS,54), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+/* rpc.server.io_write */
+ { &proc_net_rpc.server.io_write,
+ { PMDA_PMID(CLUSTER_NET_NFS,55), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+/* rpc.server.th_cnt */
+ { &proc_net_rpc.server.th_cnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,56), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* rpc.server.th_fullcnt */
+ { &proc_net_rpc.server.th_fullcnt,
+ { PMDA_PMID(CLUSTER_NET_NFS,57), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/*
+ * /proc/partitions cluster
+ */
+
+/* disk.partitions.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,0), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,1), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,2), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,3), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,4), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,5), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* disk.partitions.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,6), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.partitions.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,7), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.partitions.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PARTITIONS,8), PM_TYPE_U32, PARTITIONS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+
+/* disk.dev.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,38), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.dev.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,39), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.dev.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,40), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.all.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.all.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,42), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/* disk.all.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_STAT,43), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+/*
+ * kernel_uname cluster
+ */
+
+/* kernel.uname.release */
+ { kernel_uname.release,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* kernel.uname.version */
+ { kernel_uname.version,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* kernel.uname.sysname */
+ { kernel_uname.sysname,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* kernel.uname.machine */
+ { kernel_uname.machine,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 3), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* kernel.uname.nodename */
+ { kernel_uname.nodename,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 4), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* pmda.uname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 5), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* pmda.version */
+ { NULL,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 6), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* kernel.uname.distro */
+ { NULL,
+ { PMDA_PMID(CLUSTER_KERNEL_UNAME, 7), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/*
+ * network snmp cluster
+ */
+
+/* network.ip.forwarding */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FORWARDING],
+ { PMDA_PMID(CLUSTER_NET_SNMP,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.defaultttl */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_DEFAULTTTL],
+ { PMDA_PMID(CLUSTER_NET_SNMP,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inreceives */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INRECEIVES],
+ { PMDA_PMID(CLUSTER_NET_SNMP,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inhdrerrors */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INHDRERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inaddrerrors */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INADDRERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.forwdatagrams */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FORWDATAGRAMS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inunknownprotos */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INUNKNOWNPROTOS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.indiscards */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INDISCARDS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.indelivers */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INDELIVERS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outrequests */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTREQUESTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outdiscards */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTDISCARDS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outnoroutes */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTNOROUTES],
+ { PMDA_PMID(CLUSTER_NET_SNMP,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.reasmtimeout */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMTIMEOUT],
+ { PMDA_PMID(CLUSTER_NET_SNMP,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.reasmreqds */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMREQDS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.reasmoks */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMOKS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.reasmfails */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMFAILS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.fragoks */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGOKS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.fragfails */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGFAILS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.fragcreates */
+ { &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGCREATES],
+ { PMDA_PMID(CLUSTER_NET_SNMP,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+
+/* network.icmp.inmsgs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INMSGS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inerrors */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.indestunreachs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INDESTUNREACHS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.intimeexcds */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMEEXCDS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inparmprobs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INPARMPROBS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.insrcquenchs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INSRCQUENCHS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inredirects */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INREDIRECTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inechos */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INECHOS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inechoreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INECHOREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.intimestamps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMESTAMPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.intimestampreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMESTAMPREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inaddrmasks */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INADDRMASKS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.inaddrmaskreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INADDRMASKREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outmsgs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTMSGS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outerrors */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outdestunreachs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTDESTUNREACHS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outtimeexcds */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMEEXCDS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outparmprobs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTPARMPROBS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outsrcquenchs */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTSRCQUENCHS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,38), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outredirects */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTREDIRECTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,39), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outechos */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTECHOS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outechoreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTECHOREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,41), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outtimestamps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMESTAMPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,42), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outtimestampreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMESTAMPREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,43), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outaddrmasks */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTADDRMASKS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.outaddrmaskreps */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTADDRMASKREPS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmp.incsumerrors */
+ { &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INCSUMERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+
+/* network.tcp.rtoalgorithm */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOALGORITHM],
+ { PMDA_PMID(CLUSTER_NET_SNMP,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.rtomin */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOMIN],
+ { PMDA_PMID(CLUSTER_NET_SNMP,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.rtomax */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOMAX],
+ { PMDA_PMID(CLUSTER_NET_SNMP,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.maxconn */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_MAXCONN],
+ { PMDA_PMID(CLUSTER_NET_SNMP,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.established */
+ { &proc_net_tcp.stat[_PM_TCP_ESTABLISHED],
+ { PMDA_PMID(CLUSTER_NET_TCP, 1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.syn_sent */
+ { &proc_net_tcp.stat[_PM_TCP_SYN_SENT],
+ { PMDA_PMID(CLUSTER_NET_TCP, 2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.syn_recv */
+ { &proc_net_tcp.stat[_PM_TCP_SYN_RECV],
+ { PMDA_PMID(CLUSTER_NET_TCP, 3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.fin_wait1 */
+ { &proc_net_tcp.stat[_PM_TCP_FIN_WAIT1],
+ { PMDA_PMID(CLUSTER_NET_TCP, 4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.fin_wait2 */
+ { &proc_net_tcp.stat[_PM_TCP_FIN_WAIT2],
+ { PMDA_PMID(CLUSTER_NET_TCP, 5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.time_wait */
+ { &proc_net_tcp.stat[_PM_TCP_TIME_WAIT],
+ { PMDA_PMID(CLUSTER_NET_TCP, 6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.close */
+ { &proc_net_tcp.stat[_PM_TCP_CLOSE],
+ { PMDA_PMID(CLUSTER_NET_TCP, 7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.close_wait */
+ { &proc_net_tcp.stat[_PM_TCP_CLOSE_WAIT],
+ { PMDA_PMID(CLUSTER_NET_TCP, 8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.last_ack */
+ { &proc_net_tcp.stat[_PM_TCP_LAST_ACK],
+ { PMDA_PMID(CLUSTER_NET_TCP, 9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.listen */
+ { &proc_net_tcp.stat[_PM_TCP_LISTEN],
+ { PMDA_PMID(CLUSTER_NET_TCP, 10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcpconn.closing */
+ { &proc_net_tcp.stat[_PM_TCP_CLOSING],
+ { PMDA_PMID(CLUSTER_NET_TCP, 11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.activeopens */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ACTIVEOPENS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.passiveopens */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_PASSIVEOPENS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.attemptfails */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ATTEMPTFAILS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.estabresets */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ESTABRESETS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.currestab */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_CURRESTAB],
+ { PMDA_PMID(CLUSTER_NET_SNMP,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.insegs */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INSEGS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.outsegs */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_OUTSEGS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.retranssegs */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RETRANSSEGS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,61), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.inerrs */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INERRS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,62), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.outrsts */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_OUTRSTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.incsumerrors */
+ { &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INCSUMERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.indatagrams */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INDATAGRAMS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.noports */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_NOPORTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.inerrors */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.outdatagrams */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_OUTDATAGRAMS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.recvbuferrors */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_RECVBUFERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.sndbuferrors */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_SNDBUFERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udp.incsumerrors */
+ { &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INCSUMERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,83), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.indatagrams */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INDATAGRAMS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,77), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.noports */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_NOPORTS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,78), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.inerrors */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,79), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.outdatagrams */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_OUTDATAGRAMS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,80), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.recvbuferrors */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_RECVBUFERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,81), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.sndbuferrors */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_SNDBUFERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,82), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.udplite.incsumerrors */
+ { &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INCSUMERRORS],
+ { PMDA_PMID(CLUSTER_NET_SNMP,84), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmpmsg.intype */
+ { &_pm_proc_net_snmp.icmpmsg[_PM_SNMP_ICMPMSG_INTYPE],
+ { PMDA_PMID(CLUSTER_NET_SNMP,88), PM_TYPE_U64, ICMPMSG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.icmpmsg.outtype */
+ { &_pm_proc_net_snmp.icmpmsg[_PM_SNMP_ICMPMSG_OUTTYPE],
+ { PMDA_PMID(CLUSTER_NET_SNMP,89), PM_TYPE_U64, ICMPMSG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/*
+ * network netstat cluster
+ */
+
+/* network.ip.innoroutes */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INNOROUTES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.intruncatedpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INTRUNCATEDPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inmcastpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INMCASTPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outmcastpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTMCASTPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inbcastpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INBCASTPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outbcastpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTBCASTPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inmcastoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INMCASTOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outmcastoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTMCASTOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.inbcastoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INBCASTOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.outbcastoctets */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTBCASTOCTETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.csumerrors */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_CSUMERRORS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.noectpkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_NOECTPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.ect1pkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_ECT1PKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.ect0pkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_ECT0PKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.ip.cepkts */
+ { &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_CEPKTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.syncookiessent */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESSENT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.syncookiesrecv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESRECV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.syncookiesfailed */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESFAILED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.embryonicrsts */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_EMBRYONICRSTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.prunecalled */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PRUNECALLED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.rcvpruned */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_RCVPRUNED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.ofopruned */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_OFOPRUNED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.outofwindowicmps */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_OUTOFWINDOWICMPS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lockdroppedicmps */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LOCKDROPPEDICMPS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.arpfilter */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_ARPFILTER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.timewaited */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.timewaitrecycled */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITRECYCLED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.timewaitkilled */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITKILLED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.pawspassiverejected */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSPASSIVEREJECTED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.pawsactiverejected */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSACTIVEREJECTED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.pawsestabrejected */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSESTABREJECTED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.delayedacks */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.delayedacklocked */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKLOCKED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.delayedacklost */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKLOST],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.listenoverflows */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LISTENOVERFLOWS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.listendrops */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LISTENDROPS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.prequeued */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPREQUEUED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,38), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.directcopyfrombacklog */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMBACKLOG],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,39), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.directcopyfromprequeue */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMPREQUEUE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.prequeuedropped */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPREQUEUEDROPPED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,41), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.hphits*/
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPHITS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,42), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.hphitstouser */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPHITSTOUSER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,43), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.pureacks */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPUREACKS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.hpacks */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPACKS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.renorecovery */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENORECOVERY],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackrecovery */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRECOVERY],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackreneging */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRENEGING],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fackreorder */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFACKREORDER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackreorder */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKREORDER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.renoreorder */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENOREORDER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.tsreorder */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTSREORDER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fullundo */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFULLUNDO],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.partialundo */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPARTIALUNDO],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackundo */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKUNDO],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lossundo */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSUNDO],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lostretransmit */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSTRETRANSMIT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.renofailures */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENOFAILURES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackfailures */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKFAILURES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lossfailures */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSFAILURES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastretrans */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTRETRANS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,61), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.forwardretrans */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFORWARDRETRANS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,62), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.slowstartretrans */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSLOWSTARTRETRANS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.timeouts */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTIMEOUTS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lossprobes */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSPROBES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,65), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.lossproberecovery */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSPROBERECOVERY],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,66), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.renorecoveryfail */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENORECOVERYFAIL],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,67), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackrecoveryfail */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRECOVERYFAIL],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,68), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.schedulerfailed */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSCHEDULERFAILED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,69), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.rcvcollapsed */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRCVCOLLAPSED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackoldsent */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOLDSENT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackofosent */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOFOSENT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackrecv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKRECV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,73), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackoforecv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOFORECV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortondata */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONDATA],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortonclose */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONCLOSE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortonmemory */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONMEMORY],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,77), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortontimeout */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONTIMEOUT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,78), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortonlinger */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONLINGER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,79), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.abortfailed */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTFAILED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,80), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.memorypressures */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMEMORYPRESSURES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,81), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackdiscard */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKDISCARD],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,82), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackignoredold */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDOLD],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,83), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.dsackignorednoundo */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDNOUNDO],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,84), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.spuriousrtos */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSPURIOUSRTOS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,85), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.md5notfound */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMD5NOTFOUND],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,86), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.md5unexpected */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMD5UNEXPECTED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,87), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackshifted */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKSHIFTED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,88), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackmerged */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKMERGED],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,89), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.sackshiftfallback */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKSHIFTFALLBACK],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,90), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.backlogdrop */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPBACKLOGDROP],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,91), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.minttldrop */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMINTTLDROP],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,92), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.deferacceptdrop */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDEFERACCEPTDROP],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,93), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.iprpfilter */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_IPRPFILTER],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,94), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.timewaitoverflow */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTIMEWAITOVERFLOW],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,95), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.reqqfulldocookies */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPREQQFULLDOCOOKIES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,96), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.reqqfulldrop */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPREQQFULLDROP],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,97), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.retransfail */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRETRANSFAIL],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,98), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.rcvcoalesce */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRCVCOALESCE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,99), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.ofoqueue */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFOQUEUE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,100), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.ofodrop */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFODROP],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,101), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.ofomerge */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFOMERGE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,102), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.challengeack */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPCHALLENGEACK],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,103), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.synchallenge */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSYNCHALLENGE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,104), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopenactive */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,105), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopenactivefail */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVEFAIL],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,106), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopenpassive */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENPASSIVE],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,107), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopenpassivefail */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENPASSIVEFAIL],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,108), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopenlistenoverflow */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENLISTENOVERFLOW],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,109), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fastopencookiereqd */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENCOOKIEREQD],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,110), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.spuriousrtxhostqueues */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSPURIOUS_RTX_HOSTQUEUES],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,111), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.busypollrxpackets */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_BUSYPOLLRXPACKETS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,112), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.autocorking */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPAUTOCORKING],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,113), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.fromzerowindowadv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFROMZEROWINDOWADV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,114), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.tozerowindowadv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTOZEROWINDOWADV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,115), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.wantzerowindowadv */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPWANTZEROWINDOWADV],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,116), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.synretrans */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSYNRETRANS],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,117), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* network.tcp.origdatasent */
+ { &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPORIGDATASENT],
+ { PMDA_PMID(CLUSTER_NET_NETSTAT,118), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* hinv.map.scsi */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SCSI,0), PM_TYPE_STRING, SCSI_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.map.lvname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LV,0), PM_TYPE_STRING, LV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* hinv.nlv */
+ { NULL,
+ { PMDA_PMID(CLUSTER_LV,1), PM_TYPE_U32, LV_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/*
+ * /proc/cpuinfo cluster (cpu indom)
+ */
+
+/* hinv.cpu.clock */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 0), PM_TYPE_FLOAT, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,-6,0) } },
+
+/* hinv.cpu.vendor */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 1), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.model */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 2), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.stepping */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 3), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.cache */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 4), PM_TYPE_U32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* hinv.cpu.bogomips */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 5), PM_TYPE_FLOAT, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.map.cpu_num */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 6), PM_TYPE_U32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.machine */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 7), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.map.cpu_node */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 8), PM_TYPE_U32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.model_name */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 9), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.flags */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 10), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* hinv.cpu.cache_alignment */
+ { NULL,
+ { PMDA_PMID(CLUSTER_CPUINFO, 11), PM_TYPE_U32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,PM_SPACE_BYTE,0,0) } },
+
+/*
+ * semaphore limits cluster
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+/* ipc.sem.max_semmap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_semid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_sem */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.num_undo */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_perid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_ops */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_undoent */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.sz_semundo */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_semval */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.sem.max_exit */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SEM_LIMITS, 9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * message limits cluster
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+/* ipc.msg.sz_pool */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0, PM_SPACE_KBYTE,0,0)}},
+
+/* ipc.msg.mapent */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.msg.max_msgsz */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.msg.max_defmsgq */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.msg.max_msgqid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.msg.max_msgseg */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0, 0,0,0)}},
+
+/* ipc.msg.max_smsghdr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.msg.max_seg */
+ { NULL,
+ { PMDA_PMID(CLUSTER_MSG_LIMITS, 7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * shared memory limits cluster
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+/* ipc.shm.max_segsz */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SHM_LIMITS, 0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+
+/* ipc.shm.min_segsz */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SHM_LIMITS, 1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+
+/* ipc.shm.max_seg */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SHM_LIMITS, 2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.shm.max_segproc */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SHM_LIMITS, 3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* ipc.shm.max_shmsys */
+ { NULL,
+ { PMDA_PMID(CLUSTER_SHM_LIMITS, 4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * number of users cluster
+ */
+
+/* kernel.all.nusers */
+ { NULL,
+ { PMDA_PMID(CLUSTER_NUSERS, 0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * /proc/sys/fs vfs cluster
+ */
+
+/* vfs.files */
+ { &proc_sys_fs.fs_files_count,
+ { PMDA_PMID(CLUSTER_VFS,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_files_free,
+ { PMDA_PMID(CLUSTER_VFS,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_files_max,
+ { PMDA_PMID(CLUSTER_VFS,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_inodes_count,
+ { PMDA_PMID(CLUSTER_VFS,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_inodes_free,
+ { PMDA_PMID(CLUSTER_VFS,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_dentry_count,
+ { PMDA_PMID(CLUSTER_VFS,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+ { &proc_sys_fs.fs_dentry_free,
+ { PMDA_PMID(CLUSTER_VFS,6), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /*
+ * mem.vmstat cluster
+ */
+
+ /* mem.vmstat.nr_dirty */
+ { &_pm_proc_vmstat.nr_dirty,
+ {PMDA_PMID(28,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_writeback */
+ { &_pm_proc_vmstat.nr_writeback,
+ {PMDA_PMID(28,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_unstable */
+ { &_pm_proc_vmstat.nr_unstable,
+ {PMDA_PMID(28,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_page_table_pages */
+ { &_pm_proc_vmstat.nr_page_table_pages,
+ {PMDA_PMID(28,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_mapped */
+ { &_pm_proc_vmstat.nr_mapped,
+ {PMDA_PMID(28,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_slab */
+ { &_pm_proc_vmstat.nr_slab,
+ {PMDA_PMID(28,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.pgpgin */
+ { &_pm_proc_vmstat.pgpgin,
+ {PMDA_PMID(28,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgpgout */
+ { &_pm_proc_vmstat.pgpgout,
+ {PMDA_PMID(28,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pswpin */
+ { &_pm_proc_vmstat.pswpin,
+ {PMDA_PMID(28,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pswpout */
+ { &_pm_proc_vmstat.pswpout,
+ {PMDA_PMID(28,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgalloc_high */
+ { &_pm_proc_vmstat.pgalloc_high,
+ {PMDA_PMID(28,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgalloc_normal */
+ { &_pm_proc_vmstat.pgalloc_normal,
+ {PMDA_PMID(28,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgalloc_dma */
+ { &_pm_proc_vmstat.pgalloc_dma,
+ {PMDA_PMID(28,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgfree */
+ { &_pm_proc_vmstat.pgfree,
+ {PMDA_PMID(28,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgactivate */
+ { &_pm_proc_vmstat.pgactivate,
+ {PMDA_PMID(28,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgdeactivate */
+ { &_pm_proc_vmstat.pgdeactivate,
+ {PMDA_PMID(28,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgfault */
+ { &_pm_proc_vmstat.pgfault,
+ {PMDA_PMID(28,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgmajfault */
+ { &_pm_proc_vmstat.pgmajfault,
+ {PMDA_PMID(28,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrefill_high */
+ { &_pm_proc_vmstat.pgrefill_high,
+ {PMDA_PMID(28,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrefill_normal */
+ { &_pm_proc_vmstat.pgrefill_normal,
+ {PMDA_PMID(28,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrefill_dma */
+ { &_pm_proc_vmstat.pgrefill_dma,
+ {PMDA_PMID(28,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgsteal_high */
+ { &_pm_proc_vmstat.pgsteal_high,
+ {PMDA_PMID(28,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgsteal_normal */
+ { &_pm_proc_vmstat.pgsteal_normal,
+ {PMDA_PMID(28,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgsteal_dma */
+ { &_pm_proc_vmstat.pgsteal_dma,
+ {PMDA_PMID(28,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_kswapd_high */
+ { &_pm_proc_vmstat.pgscan_kswapd_high,
+ {PMDA_PMID(28,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_kswapd_normal */
+ { &_pm_proc_vmstat.pgscan_kswapd_normal,
+ {PMDA_PMID(28,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_kswapd_dma */
+ { &_pm_proc_vmstat.pgscan_kswapd_dma,
+ {PMDA_PMID(28,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_direct_high */
+ { &_pm_proc_vmstat.pgscan_direct_high,
+ {PMDA_PMID(28,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_direct_normal */
+ { &_pm_proc_vmstat.pgscan_direct_normal,
+ {PMDA_PMID(28,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_direct_dma */
+ { &_pm_proc_vmstat.pgscan_direct_dma,
+ {PMDA_PMID(28,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pginodesteal */
+ { &_pm_proc_vmstat.pginodesteal,
+ {PMDA_PMID(28,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.slabs_scanned */
+ { &_pm_proc_vmstat.slabs_scanned,
+ {PMDA_PMID(28,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.kswapd_steal */
+ { &_pm_proc_vmstat.kswapd_steal,
+ {PMDA_PMID(28,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.kswapd_inodesteal */
+ { &_pm_proc_vmstat.kswapd_inodesteal,
+ {PMDA_PMID(28,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pageoutrun */
+ { &_pm_proc_vmstat.pageoutrun,
+ {PMDA_PMID(28,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.allocstall */
+ { &_pm_proc_vmstat.allocstall,
+ {PMDA_PMID(28,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrotated */
+ { &_pm_proc_vmstat.pgrotated,
+ {PMDA_PMID(28,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_slab_reclaimable */
+ { &_pm_proc_vmstat.nr_slab_reclaimable,
+ {PMDA_PMID(28,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_slab_unreclaimable */
+ { &_pm_proc_vmstat.nr_slab_unreclaimable,
+ {PMDA_PMID(28,38), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_anon_pages */
+ { &_pm_proc_vmstat.nr_anon_pages,
+ {PMDA_PMID(28,39), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_bounce */
+ { &_pm_proc_vmstat.nr_bounce,
+ {PMDA_PMID(28,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_file_pages */
+ { &_pm_proc_vmstat.nr_file_pages,
+ {PMDA_PMID(28,41), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* mem.vmstat.nr_vmscan_write */
+ { &_pm_proc_vmstat.nr_vmscan_write,
+ {PMDA_PMID(28,42), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.htlb_buddy_alloc_fail */
+ { &_pm_proc_vmstat.htlb_buddy_alloc_fail,
+ {PMDA_PMID(28,43), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.htlb_buddy_alloc_success */
+ { &_pm_proc_vmstat.htlb_buddy_alloc_success,
+ {PMDA_PMID(28,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_active_anon */
+ { &_pm_proc_vmstat.nr_active_anon,
+ {PMDA_PMID(28,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_active_file */
+ { &_pm_proc_vmstat.nr_active_file,
+ {PMDA_PMID(28,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_free_pages */
+ { &_pm_proc_vmstat.nr_free_pages,
+ {PMDA_PMID(28,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_inactive_anon */
+ { &_pm_proc_vmstat.nr_inactive_anon,
+ {PMDA_PMID(28,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_inactive_file */
+ { &_pm_proc_vmstat.nr_inactive_file,
+ {PMDA_PMID(28,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_isolated_anon */
+ { &_pm_proc_vmstat.nr_isolated_anon,
+ {PMDA_PMID(28,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_isolated_file */
+ { &_pm_proc_vmstat.nr_isolated_file,
+ {PMDA_PMID(28,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_kernel_stack */
+ { &_pm_proc_vmstat.nr_kernel_stack,
+ {PMDA_PMID(28,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_mlock */
+ { &_pm_proc_vmstat.nr_mlock,
+ {PMDA_PMID(28,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_shmem */
+ { &_pm_proc_vmstat.nr_shmem,
+ {PMDA_PMID(28,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_unevictable */
+ { &_pm_proc_vmstat.nr_unevictable,
+ {PMDA_PMID(28,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_writeback_temp */
+ { &_pm_proc_vmstat.nr_writeback_temp,
+ {PMDA_PMID(28,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_blocks_moved */
+ { &_pm_proc_vmstat.compact_blocks_moved,
+ {PMDA_PMID(28,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_fail */
+ { &_pm_proc_vmstat.compact_fail,
+ {PMDA_PMID(28,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_pagemigrate_failed */
+ { &_pm_proc_vmstat.compact_pagemigrate_failed,
+ {PMDA_PMID(28,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_pages_moved */
+ { &_pm_proc_vmstat.compact_pages_moved,
+ {PMDA_PMID(28,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_stall */
+ { &_pm_proc_vmstat.compact_stall,
+ {PMDA_PMID(28,61), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.compact_success */
+ { &_pm_proc_vmstat.compact_success,
+ {PMDA_PMID(28,62), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgalloc_dma32 */
+ { &_pm_proc_vmstat.pgalloc_dma32,
+ {PMDA_PMID(28,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgalloc_movable */
+ { &_pm_proc_vmstat.pgalloc_movable,
+ {PMDA_PMID(28,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrefill_dma32 */
+ { &_pm_proc_vmstat.pgrefill_dma32,
+ {PMDA_PMID(28,65), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgrefill_movable */
+ { &_pm_proc_vmstat.pgrefill_movable,
+ {PMDA_PMID(28,66), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_direct_dma32 */
+ { &_pm_proc_vmstat.pgscan_direct_dma32,
+ {PMDA_PMID(28,67), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_direct_movable */
+ { &_pm_proc_vmstat.pgscan_direct_movable,
+ {PMDA_PMID(28,68), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_kswapd_dma32 */
+ { &_pm_proc_vmstat.pgscan_kswapd_dma32,
+ {PMDA_PMID(28,69), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgscan_kswapd_movable */
+ { &_pm_proc_vmstat.pgscan_kswapd_movable,
+ {PMDA_PMID(28,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgsteal_dma32 */
+ { &_pm_proc_vmstat.pgsteal_dma32,
+ {PMDA_PMID(28,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.pgsteal_movable */
+ { &_pm_proc_vmstat.pgsteal_movable,
+ {PMDA_PMID(28,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.thp_fault_alloc */
+ { &_pm_proc_vmstat.thp_fault_alloc,
+ {PMDA_PMID(28,73), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.thp_fault_fallback */
+ { &_pm_proc_vmstat.thp_fault_fallback,
+ {PMDA_PMID(28,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.thp_collapse_alloc */
+ { &_pm_proc_vmstat.thp_collapse_alloc,
+ {PMDA_PMID(28,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.thp_collapse_alloc_failed */
+ { &_pm_proc_vmstat.thp_collapse_alloc_failed,
+ {PMDA_PMID(28,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.thp_split */
+ { &_pm_proc_vmstat.thp_split,
+ {PMDA_PMID(28,77), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_cleared */
+ { &_pm_proc_vmstat.unevictable_pgs_cleared,
+ {PMDA_PMID(28,78), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_culled */
+ { &_pm_proc_vmstat.unevictable_pgs_culled,
+ {PMDA_PMID(28,79), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_mlocked */
+ { &_pm_proc_vmstat.unevictable_pgs_mlocked,
+ {PMDA_PMID(28,80), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_mlockfreed */
+ { &_pm_proc_vmstat.unevictable_pgs_mlockfreed,
+ {PMDA_PMID(28,81), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_munlocked */
+ { &_pm_proc_vmstat.unevictable_pgs_munlocked,
+ {PMDA_PMID(28,82), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_rescued */
+ { &_pm_proc_vmstat.unevictable_pgs_rescued,
+ {PMDA_PMID(28,83), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_scanned */
+ { &_pm_proc_vmstat.unevictable_pgs_scanned,
+ {PMDA_PMID(28,84), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.unevictable_pgs_stranded */
+ { &_pm_proc_vmstat.unevictable_pgs_stranded,
+ {PMDA_PMID(28,85), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.zone_reclaim_failed */
+ { &_pm_proc_vmstat.zone_reclaim_failed,
+ {PMDA_PMID(28,86), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.kswapd_low_wmark_hit_quickly */
+ { &_pm_proc_vmstat.kswapd_low_wmark_hit_quickly,
+ {PMDA_PMID(28,87), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.kswapd_high_wmark_hit_quickly */
+ { &_pm_proc_vmstat.kswapd_high_wmark_hit_quickly,
+ {PMDA_PMID(28,88), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.kswapd_skip_congestion_wait */
+ { &_pm_proc_vmstat.kswapd_skip_congestion_wait,
+ {PMDA_PMID(28,89), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_anon_transparent_hugepages */
+ { &_pm_proc_vmstat.nr_anon_transparent_hugepages,
+ {PMDA_PMID(28,90), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_dirtied */
+ { &_pm_proc_vmstat.nr_dirtied,
+ {PMDA_PMID(28,91), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_dirty_background_threshold */
+ { &_pm_proc_vmstat.nr_dirty_background_threshold,
+ {PMDA_PMID(28,92), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_dirty_threshold */
+ { &_pm_proc_vmstat.nr_dirty_threshold,
+ {PMDA_PMID(28,93), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.nr_written */
+ { &_pm_proc_vmstat.nr_written,
+ {PMDA_PMID(28,94), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_foreign */
+ { &_pm_proc_vmstat.numa_foreign,
+ {PMDA_PMID(28,95), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_hit */
+ { &_pm_proc_vmstat.numa_hit,
+ {PMDA_PMID(28,96), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_interleave */
+ { &_pm_proc_vmstat.numa_interleave,
+ {PMDA_PMID(28,97), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_local */
+ { &_pm_proc_vmstat.numa_local,
+ {PMDA_PMID(28,98), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_miss */
+ { &_pm_proc_vmstat.numa_miss,
+ {PMDA_PMID(28,99), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* mem.vmstat.numa_other */
+ { &_pm_proc_vmstat.numa_other,
+ {PMDA_PMID(28,100), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/*
+ * sysfs_kernel cluster
+ */
+ /* sysfs.kernel.uevent_seqnum */
+ { &sysfs_kernel.uevent_seqnum,
+ { PMDA_PMID(CLUSTER_SYSFS_KERNEL,0), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/*
+ * /proc/interrupts cluster
+ */
+ /* kernel.all.interrupts.errors */
+ { &irq_err_count,
+ { PMDA_PMID(CLUSTER_INTERRUPTS, 3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* kernel.percpu.interrupts.line[<N>] */
+ { NULL, { PMDA_PMID(CLUSTER_INTERRUPT_LINES, 0), PM_TYPE_U32,
+ CPU_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* kernel.percpu.interrupts.[<other>] */
+ { NULL, { PMDA_PMID(CLUSTER_INTERRUPT_OTHER, 0), PM_TYPE_U32,
+ CPU_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/*
+ * disk.dm cluster
+ */
+ /* disk.dm.read */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,0), KERNEL_ULONG, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.write */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,1), KERNEL_ULONG, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.total */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,2), KERNEL_ULONG, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.blkread */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,3), PM_TYPE_U64, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.blkwrite */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,4), PM_TYPE_U64, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.blktotal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,5), PM_TYPE_U64, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,6), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* disk.dm.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,7), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* disk.dm.total_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,8), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* disk.dm.read_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,9), KERNEL_ULONG, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.write_merge */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,10), KERNEL_ULONG, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* disk.dm.avactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,11), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+ /* disk.dm.aveq */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,12), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+ /* hinv.map.dmname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,13), PM_TYPE_STRING, DM_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* disk.dm.read_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,14), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+ /* disk.dm.write_rawactive */
+ { NULL,
+ { PMDA_PMID(CLUSTER_DM,15), PM_TYPE_U32, DM_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+};
+
+char *linux_statspath = ""; /* optional path prefix for all stats files */
+
+FILE *
+linux_statsfile(const char *path, char *buffer, int size)
+{
+ snprintf(buffer, size, "%s%s", linux_statspath, path);
+ buffer[size-1] = '\0';
+ return fopen(buffer, "r");
+}
+
+static void
+linux_refresh(pmdaExt *pmda, int *need_refresh)
+{
+ int need_refresh_mtab = 0;
+
+ if (need_refresh[CLUSTER_PARTITIONS])
+ refresh_proc_partitions(INDOM(DISK_INDOM), INDOM(PARTITIONS_INDOM), INDOM(DM_INDOM));
+
+ if (need_refresh[CLUSTER_STAT])
+ refresh_proc_stat(&proc_cpuinfo, &proc_stat);
+
+ if (need_refresh[CLUSTER_CPUINFO])
+ refresh_proc_cpuinfo(&proc_cpuinfo);
+
+ if (need_refresh[CLUSTER_MEMINFO])
+ refresh_proc_meminfo(&proc_meminfo);
+
+ if (need_refresh[CLUSTER_NUMA_MEMINFO])
+ refresh_numa_meminfo(&numa_meminfo, &proc_cpuinfo, &proc_stat);
+
+ if (need_refresh[CLUSTER_LOADAVG])
+ refresh_proc_loadavg(&proc_loadavg);
+
+ if (need_refresh[CLUSTER_NET_DEV])
+ refresh_proc_net_dev(INDOM(NET_DEV_INDOM));
+
+ if (need_refresh[CLUSTER_NET_ADDR])
+ refresh_net_dev_addr(INDOM(NET_ADDR_INDOM));
+
+ if (need_refresh[CLUSTER_FILESYS] || need_refresh[CLUSTER_TMPFS])
+ refresh_filesys(INDOM(FILESYS_INDOM), INDOM(TMPFS_INDOM));
+
+ if (need_refresh[CLUSTER_INTERRUPTS] ||
+ need_refresh[CLUSTER_INTERRUPT_LINES] ||
+ need_refresh[CLUSTER_INTERRUPT_OTHER])
+ need_refresh_mtab |= refresh_interrupt_values();
+
+ if (need_refresh[CLUSTER_SWAPDEV])
+ refresh_swapdev(INDOM(SWAPDEV_INDOM));
+
+ if (need_refresh[CLUSTER_NET_NFS])
+ refresh_proc_net_rpc(&proc_net_rpc);
+
+ if (need_refresh[CLUSTER_NET_SOCKSTAT])
+ refresh_proc_net_sockstat(&proc_net_sockstat);
+
+ if (need_refresh[CLUSTER_KERNEL_UNAME])
+ uname(&kernel_uname);
+
+ if (need_refresh[CLUSTER_NET_SNMP])
+ refresh_proc_net_snmp(&_pm_proc_net_snmp);
+
+ if (need_refresh[CLUSTER_SCSI])
+ refresh_proc_scsi(&proc_scsi);
+
+ if (need_refresh[CLUSTER_LV])
+ refresh_dev_mapper(&dev_mapper);
+
+ if (need_refresh[CLUSTER_NET_TCP])
+ refresh_proc_net_tcp(&proc_net_tcp);
+
+ if (need_refresh[CLUSTER_NET_NETSTAT])
+ refresh_proc_net_netstat(&_pm_proc_net_netstat);
+
+ if (need_refresh[CLUSTER_SLAB])
+ refresh_proc_slabinfo(&proc_slabinfo);
+
+ if (need_refresh[CLUSTER_SEM_LIMITS])
+ refresh_sem_limits(&sem_limits);
+
+ if (need_refresh[CLUSTER_MSG_LIMITS])
+ refresh_msg_limits(&msg_limits);
+
+ if (need_refresh[CLUSTER_SHM_LIMITS])
+ refresh_shm_limits(&shm_limits);
+
+ if (need_refresh[CLUSTER_UPTIME])
+ refresh_proc_uptime(&proc_uptime);
+
+ if (need_refresh[CLUSTER_VFS])
+ refresh_proc_sys_fs(&proc_sys_fs);
+
+ if (need_refresh[CLUSTER_VMSTAT])
+ refresh_proc_vmstat(&_pm_proc_vmstat);
+
+ if (need_refresh[CLUSTER_SYSFS_KERNEL])
+ refresh_sysfs_kernel(&sysfs_kernel);
+
+ if (need_refresh_mtab)
+ pmdaDynamicMetricTable(pmda);
+}
+
+static int
+linux_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ __pmInDom_int *indomp = (__pmInDom_int *)&indom;
+ int need_refresh[NUM_CLUSTERS];
+
+ memset(need_refresh, 0, sizeof(need_refresh));
+ switch (indomp->serial) {
+ case DISK_INDOM:
+ case PARTITIONS_INDOM:
+ case DM_INDOM:
+ need_refresh[CLUSTER_PARTITIONS]++;
+ break;
+ case CPU_INDOM:
+ need_refresh[CLUSTER_STAT]++;
+ break;
+ case NODE_INDOM:
+ need_refresh[CLUSTER_NUMA_MEMINFO]++;
+ break;
+ case LOADAVG_INDOM:
+ need_refresh[CLUSTER_LOADAVG]++;
+ break;
+ case NET_DEV_INDOM:
+ need_refresh[CLUSTER_NET_DEV]++;
+ break;
+ case FILESYS_INDOM:
+ need_refresh[CLUSTER_FILESYS]++;
+ break;
+ case TMPFS_INDOM:
+ need_refresh[CLUSTER_TMPFS]++;
+ break;
+ case SWAPDEV_INDOM:
+ need_refresh[CLUSTER_SWAPDEV]++;
+ break;
+ case NFS_INDOM:
+ case NFS3_INDOM:
+ case NFS4_CLI_INDOM:
+ case NFS4_SVR_INDOM:
+ need_refresh[CLUSTER_NET_NFS]++;
+ break;
+ case SCSI_INDOM:
+ need_refresh[CLUSTER_SCSI]++;
+ break;
+ case LV_INDOM:
+ need_refresh[CLUSTER_LV]++;
+ break;
+ case SLAB_INDOM:
+ need_refresh[CLUSTER_SLAB]++;
+ break;
+ case ICMPMSG_INDOM:
+ need_refresh[CLUSTER_NET_SNMP]++;
+ break;
+ /* no default label : pmdaInstance will pick up errors */
+ }
+
+ linux_refresh(pmda, need_refresh);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+
+static int
+linux_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int i;
+ int sts;
+ long sl;
+ struct filesys *fs;
+ net_addr_t *addrp;
+ net_interface_t *netip;
+
+ if (mdesc->m_user != NULL) {
+ /*
+ * The metric value is extracted directly via the address specified
+ * in metrictab. Note: not all metrics support this - those that
+ * don't have NULL for the m_user field in their respective
+ * metrictab slot.
+ */
+ if (idp->cluster == CLUSTER_VMSTAT) {
+ if (!(_pm_have_proc_vmstat) ||
+ *(__uint64_t *)mdesc->m_user == (__uint64_t)-1)
+ return 0; /* no value available on this kernel */
+ }
+ else
+ if (idp->cluster == CLUSTER_NET_SNMP) {
+ __uint64_t value;
+
+ /* network.icmpmsg has an indom - deal with it now */
+ if (idp->item == 88 || idp->item == 89) {
+ if (inst > NR_ICMPMSG_COUNTERS)
+ return PM_ERR_INST;
+ value = *((__uint64_t *)mdesc->m_user + inst);
+ if (value == (__uint64_t)-1)
+ return 0; /* no value for this instance */
+ atom->ull = value;
+ return 1;
+ }
+ if (*(__uint64_t *)mdesc->m_user == (__uint64_t)-1)
+ if (idp->item != 53) /* tcp.maxconn is special */
+ return 0; /* no value available on this kernel */
+ }
+ else
+ if (idp->cluster == CLUSTER_NET_NETSTAT) {
+ if (*(__uint64_t *)mdesc->m_user == (__uint64_t)-1)
+ return 0; /* no value available on this kernel */
+ }
+ else
+ if (idp->cluster == CLUSTER_NET_NFS) {
+ /*
+ * check if rpc stats are available
+ */
+ if (idp->item >= 20 && idp->item <= 27 && proc_net_rpc.client.errcode != 0)
+ /* no values available for client rpc/nfs - this is expected <= 2.0.36 */
+ return 0;
+ else
+ if (idp->item >= 30 && idp->item <= 47 && proc_net_rpc.server.errcode != 0)
+ /* no values available - expected without /proc/net/rpc/nfsd */
+ return 0; /* no values available */
+ if (idp->item >= 51 && idp->item <= 57 && proc_net_rpc.server.errcode != 0)
+ /* no values available - expected without /proc/net/rpc/nfsd */
+ return 0; /* no values available */
+ }
+ if (idp->cluster == CLUSTER_SYSFS_KERNEL) {
+ /* no values available for udev metrics */
+ if (idp->item == 0 && !sysfs_kernel.valid_uevent_seqnum) {
+ return 0;
+ }
+ }
+
+ switch (mdesc->m_desc.type) {
+ case PM_TYPE_32:
+ atom->l = *(__int32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U32:
+ atom->ul = *(__uint32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_64:
+ atom->ll = *(__int64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U64:
+ atom->ull = *(__uint64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_FLOAT:
+ atom->f = *(float *)mdesc->m_user;
+ break;
+ case PM_TYPE_DOUBLE:
+ atom->d = *(double *)mdesc->m_user;
+ break;
+ case PM_TYPE_STRING:
+ atom->cp = (char *)mdesc->m_user;
+ break;
+ default:
+ return 0;
+ }
+ }
+ else
+ switch (idp->cluster) {
+ case CLUSTER_STAT:
+ /*
+ * All metrics from /proc/stat
+ */
+ switch (idp->item) {
+ case 0: /* kernel.percpu.cpu.user */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_user[inst] / proc_stat.hz);
+ break;
+ case 1: /* kernel.percpu.cpu.nice */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_nice[inst] / proc_stat.hz);
+ break;
+ case 2: /* kernel.percpu.cpu.sys */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_sys[inst] / proc_stat.hz);
+ break;
+ case 3: /* kernel.percpu.cpu.idle */
+ _pm_assign_utype(_pm_idletime_size, atom,
+ 1000 * (double)proc_stat.p_idle[inst] / proc_stat.hz);
+ break;
+ case 30: /* kernel.percpu.cpu.wait.total */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_wait[inst] / proc_stat.hz);
+ break;
+ case 31: /* kernel.percpu.cpu.intr */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.p_irq[inst] +
+ (double)proc_stat.p_sirq[inst]) / proc_stat.hz);
+ break;
+ case 56: /* kernel.percpu.cpu.irq.soft */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_sirq[inst] / proc_stat.hz);
+ break;
+ case 57: /* kernel.percpu.cpu.irq.hard */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_irq[inst] / proc_stat.hz);
+ break;
+ case 58: /* kernel.percpu.cpu.steal */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_steal[inst] / proc_stat.hz);
+ break;
+ case 61: /* kernel.percpu.cpu.guest */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.p_guest[inst] / proc_stat.hz);
+ break;
+ case 76: /* kernel.percpu.cpu.vuser */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.p_user[inst] - (double)proc_stat.p_guest[inst])
+ / proc_stat.hz);
+ break;
+ case 62: /* kernel.pernode.cpu.user */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_user[inst] / proc_stat.hz);
+ break;
+ case 63: /* kernel.pernode.cpu.nice */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_nice[inst] / proc_stat.hz);
+ break;
+ case 64: /* kernel.pernode.cpu.sys */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_sys[inst] / proc_stat.hz);
+ break;
+ case 65: /* kernel.pernode.cpu.idle */
+ _pm_assign_utype(_pm_idletime_size, atom,
+ 1000 * (double)proc_stat.n_idle[inst] / proc_stat.hz);
+ break;
+ case 69: /* kernel.pernode.cpu.wait.total */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_wait[inst] / proc_stat.hz);
+ break;
+ case 66: /* kernel.pernode.cpu.intr */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.n_irq[inst] +
+ (double)proc_stat.n_sirq[inst]) / proc_stat.hz);
+ break;
+ case 70: /* kernel.pernode.cpu.irq.soft */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_sirq[inst] / proc_stat.hz);
+ break;
+ case 71: /* kernel.pernode.cpu.irq.hard */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_irq[inst] / proc_stat.hz);
+ break;
+ case 67: /* kernel.pernode.cpu.steal */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_steal[inst] / proc_stat.hz);
+ break;
+ case 68: /* kernel.pernode.cpu.guest */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.n_guest[inst] / proc_stat.hz);
+ break;
+ case 77: /* kernel.pernode.cpu.guest */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.n_user[inst] - (double)proc_stat.n_guest[inst])
+ / proc_stat.hz);
+ break;
+
+ case 8: /* pagesin */
+ if (_pm_have_proc_vmstat)
+ atom->ul = _pm_proc_vmstat.pswpin;
+ else
+ atom->ul = proc_stat.swap[0];
+ break;
+ case 9: /* pagesout */
+ if (_pm_have_proc_vmstat)
+ atom->ul = _pm_proc_vmstat.pswpout;
+ else
+ atom->ul = proc_stat.swap[1];
+ break;
+ case 10: /* in */
+ if (_pm_have_proc_vmstat)
+ return PM_ERR_APPVERSION; /* no swap operation counts in 2.6 */
+ else
+ atom->ul = proc_stat.page[0];
+ break;
+ case 11: /* out */
+ if (_pm_have_proc_vmstat)
+ return PM_ERR_APPVERSION; /* no swap operation counts in 2.6 */
+ else
+ atom->ul = proc_stat.page[1];
+ break;
+ case 12: /* intr */
+ _pm_assign_utype(_pm_intr_size, atom, proc_stat.intr);
+ break;
+ case 13: /* ctxt */
+ _pm_assign_utype(_pm_ctxt_size, atom, proc_stat.ctxt);
+ break;
+ case 14: /* processes */
+ _pm_assign_ulong(atom, proc_stat.processes);
+ break;
+
+ case 20: /* kernel.all.cpu.user */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.user / proc_stat.hz);
+ break;
+ case 21: /* kernel.all.cpu.nice */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.nice / proc_stat.hz);
+ break;
+ case 22: /* kernel.all.cpu.sys */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.sys / proc_stat.hz);
+ break;
+ case 23: /* kernel.all.cpu.idle */
+ _pm_assign_utype(_pm_idletime_size, atom,
+ 1000 * (double)proc_stat.idle / proc_stat.hz);
+ break;
+ case 34: /* kernel.all.cpu.intr */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.irq +
+ (double)proc_stat.sirq) / proc_stat.hz);
+ break;
+ case 35: /* kernel.all.cpu.wait.total */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.wait / proc_stat.hz);
+ break;
+ case 53: /* kernel.all.cpu.irq.soft */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.sirq / proc_stat.hz);
+ break;
+ case 54: /* kernel.all.cpu.irq.hard */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.irq / proc_stat.hz);
+ break;
+ case 55: /* kernel.all.cpu.steal */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.steal / proc_stat.hz);
+ break;
+ case 60: /* kernel.all.cpu.guest */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * (double)proc_stat.guest / proc_stat.hz);
+ break;
+ case 78: /* kernel.all.cpu.vuser */
+ _pm_assign_utype(_pm_cputime_size, atom,
+ 1000 * ((double)proc_stat.user - (double)proc_stat.guest)
+ / proc_stat.hz);
+ break;
+
+ case 19: /* hinv.nnode */
+ atom->ul = indomtab[NODE_INDOM].it_numinst;
+ break;
+ case 32: /* hinv.ncpu */
+ atom->ul = indomtab[CPU_INDOM].it_numinst;
+ break;
+ case 33: /* hinv.ndisk */
+ atom->ul = pmdaCacheOp(INDOM(DISK_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ break;
+
+ case 48: /* kernel.all.hz */
+ atom->ul = proc_stat.hz;
+ break;
+
+ default:
+ /*
+ * Disk metrics used to be fetched from /proc/stat (2.2 kernels)
+ * but have since moved to /proc/partitions (2.4 kernels) and
+ * /proc/diskstats (2.6 kernels). We preserve the cluster number
+ * (middle bits of a PMID) for backward compatibility.
+ *
+ * Note that proc_partitions_fetch() will return PM_ERR_PMID
+ * if we have tried to fetch an unknown metric.
+ */
+ return proc_partitions_fetch(mdesc, inst, atom);
+ }
+ break;
+
+ case CLUSTER_UPTIME: /* uptime */
+ switch (idp->item) {
+ case 0:
+ /*
+ * kernel.all.uptime (in seconds)
+ * contributed by "gilly" <gilly@exanet.com>
+ * modified by Mike Mason" <mmlnx@us.ibm.com>
+ */
+ atom->ul = proc_uptime.uptime;
+ break;
+ case 1:
+ /*
+ * kernel.all.idletime (in seconds)
+ * contributed by "Mike Mason" <mmlnx@us.ibm.com>
+ */
+ atom->ul = proc_uptime.idletime;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_MEMINFO: /* mem */
+ switch (idp->item) {
+ case 0: /* mem.physmem (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MemTotal >> 10;
+ break;
+ case 1: /* mem.util.used (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemTotal) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.MemFree))
+ return 0; /* no values available */
+ atom->ull = (proc_meminfo.MemTotal - proc_meminfo.MemFree) >> 10;
+ break;
+ case 2: /* mem.util.free (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MemFree >> 10;
+ break;
+ case 3: /* mem.util.shared (in kbytes) */
+ /*
+ * If this metric is exported by the running kernel, it is always
+ * zero (deprecated). PCP exports it for compatibility with older
+ * PCP monitoring tools, e.g. pmgsys running on IRIX(TM).
+ */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemShared))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MemShared >> 10;
+ break;
+ case 4: /* mem.util.bufmem (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Buffers))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Buffers >> 10;
+ break;
+ case 5: /* mem.util.cached (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Cached))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Cached >> 10;
+ break;
+ case 6: /* swap.length (in bytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapTotal;
+ break;
+ case 7: /* swap.used (in bytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapTotal) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.SwapFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapTotal - proc_meminfo.SwapFree;
+ break;
+ case 8: /* swap.free (in bytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapFree;
+ break;
+ case 9: /* hinv.physmem (in mbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemTotal))
+ return 0; /* no values available */
+ atom->ul = proc_meminfo.MemTotal >> 20;
+ break;
+ case 10: /* mem.freemem (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MemFree >> 10;
+ break;
+ case 11: /* hinv.pagesize (in bytes) */
+ atom->ul = _pm_system_pagesize;
+ break;
+ case 12: /* mem.util.other (in kbytes) */
+ /* other = used - (cached+buffers) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemTotal) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.MemFree) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.Cached) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.Buffers))
+ return 0; /* no values available */
+ sl = (proc_meminfo.MemTotal -
+ proc_meminfo.MemFree -
+ proc_meminfo.Cached -
+ proc_meminfo.Buffers) >> 10;
+ atom->ull = sl >= 0 ? sl : 0;
+ break;
+ case 13: /* mem.util.swapCached (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapCached))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapCached >> 10;
+ break;
+ case 14: /* mem.util.active (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Active))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Active >> 10;
+ break;
+ case 15: /* mem.util.inactive (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Inactive))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Inactive >> 10;
+ break;
+ case 16: /* mem.util.highTotal (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HighTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HighTotal >> 10;
+ break;
+ case 17: /* mem.util.highFree (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HighFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HighFree >> 10;
+ break;
+ case 18: /* mem.util.lowTotal (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.LowTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.LowTotal >> 10;
+ break;
+ case 19: /* mem.util.lowFree (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.LowFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.LowFree >> 10;
+ break;
+ case 20: /* mem.util.swapTotal (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapTotal >> 10;
+ break;
+ case 21: /* mem.util.swapFree (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SwapFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SwapFree >> 10;
+ break;
+ case 22: /* mem.util.dirty (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Dirty))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Dirty >> 10;
+ break;
+ case 23: /* mem.util.writeback (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Writeback))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Writeback >> 10;
+ break;
+ case 24: /* mem.util.mapped (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Mapped))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Mapped >> 10;
+ break;
+ case 25: /* mem.util.slab (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Slab))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Slab >> 10;
+ break;
+ case 26: /* mem.util.committed_AS (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Committed_AS))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Committed_AS >> 10;
+ break;
+ case 27: /* mem.util.pageTables (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.PageTables))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.PageTables >> 10;
+ break;
+ case 28: /* mem.util.reverseMaps (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.ReverseMaps))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.ReverseMaps >> 10;
+ break;
+ case 29: /* mem.util.clean_cache (in kbytes) */
+ /* clean=cached-(dirty+writeback) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Cached) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.Dirty) ||
+ !MEMINFO_VALID_VALUE(proc_meminfo.Writeback))
+ return 0; /* no values available */
+ sl = (proc_meminfo.Cached -
+ proc_meminfo.Dirty -
+ proc_meminfo.Writeback) >> 10;
+ atom->ull = sl >= 0 ? sl : 0;
+ break;
+ case 30: /* mem.util.anonpages */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.AnonPages))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.AnonPages >> 10;
+ break;
+ case 31: /* mem.util.commitLimit (in kbytes) */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.CommitLimit))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.CommitLimit >> 10;
+ break;
+ case 32: /* mem.util.bounce */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Bounce))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Bounce >> 10;
+ break;
+ case 33: /* mem.util.NFS_Unstable */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.NFS_Unstable))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.NFS_Unstable >> 10;
+ break;
+ case 34: /* mem.util.slabReclaimable */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SlabReclaimable))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SlabReclaimable >> 10;
+ break;
+ case 35: /* mem.util.slabUnreclaimable */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.SlabUnreclaimable))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.SlabUnreclaimable >> 10;
+ break;
+ case 36: /* mem.util.active_anon */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Active_anon))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Active_anon >> 10;
+ break;
+ case 37: /* mem.util.inactive_anon */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Inactive_anon))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Inactive_anon >> 10;
+ break;
+ case 38: /* mem.util.active_file */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Active_file))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Active_file >> 10;
+ break;
+ case 39: /* mem.util.inactive_file */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Inactive_file))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Inactive_file >> 10;
+ break;
+ case 40: /* mem.util.unevictable */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Unevictable))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Unevictable >> 10;
+ break;
+ case 41: /* mem.util.mlocked */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Mlocked))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Mlocked >> 10;
+ break;
+ case 42: /* mem.util.shmem */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Shmem))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Shmem >> 10;
+ break;
+ case 43: /* mem.util.kernelStack */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.KernelStack))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.KernelStack >> 10;
+ break;
+ case 44: /* mem.util.hugepagesTotal */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HugepagesTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HugepagesTotal;
+ break;
+ case 45: /* mem.util.hugepagesFree */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HugepagesFree))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HugepagesFree;
+ break;
+ case 46: /* mem.util.hugepagesRsvd */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HugepagesRsvd))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HugepagesRsvd;
+ break;
+ case 47: /* mem.util.hugepagesSurp */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HugepagesSurp))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HugepagesSurp;
+ break;
+ case 48: /* mem.util.directMap4k */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.directMap4k))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.directMap4k >> 10;
+ break;
+ case 49: /* mem.util.directMap2M */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.directMap2M))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.directMap2M >> 10;
+ break;
+ case 50: /* mem.util.vmallocTotal */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.VmallocTotal))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.VmallocTotal >> 10;
+ break;
+ case 51: /* mem.util.vmallocUsed */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.VmallocUsed))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.VmallocUsed >> 10;
+ break;
+ case 52: /* mem.util.vmallocChunk */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.VmallocChunk))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.VmallocChunk >> 10;
+ break;
+ case 53: /* mem.util.mmap_copy */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MmapCopy))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MmapCopy >> 10;
+ break;
+ case 54: /* mem.util.quicklists */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.Quicklists))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.Quicklists >> 10;
+ break;
+ case 55: /* mem.util.corrupthardware */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.HardwareCorrupted))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.HardwareCorrupted >> 10;
+ break;
+ case 56: /* mem.util.anonhugepages */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.AnonHugePages))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.AnonHugePages >> 10;
+ break;
+ case 57: /* mem.util.directMap1G */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.directMap1G))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.directMap1G >> 10;
+ break;
+ case 58: /* mem.util.available */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo.MemAvailable))
+ return 0; /* no values available */
+ atom->ull = proc_meminfo.MemAvailable >> 10;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_LOADAVG:
+ switch(idp->item) {
+ case 0: /* kernel.all.load */
+ if (inst == 1)
+ atom->f = proc_loadavg.loadavg[0];
+ else
+ if (inst == 5)
+ atom->f = proc_loadavg.loadavg[1];
+ else
+ if (inst == 15)
+ atom->f = proc_loadavg.loadavg[2];
+ else
+ return PM_ERR_INST;
+ break;
+ case 1: /* kernel.all.lastpid -- added by "Mike Mason" <mmlnx@us.ibm.com> */
+ atom->ul = proc_loadavg.lastpid;
+ break;
+ case 2: /* kernel.all.runnable */
+ atom->ul = proc_loadavg.runnable;
+ break;
+ case 3: /* kernel.all.nprocs */
+ atom->ul = proc_loadavg.nprocs;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_NET_DEV: /* network.interface */
+ if (idp->item == 27) { /* hinv.ninterface */
+ atom->ul = pmdaCacheOp(INDOM(NET_DEV_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ break;
+ }
+ sts = pmdaCacheLookup(INDOM(NET_DEV_INDOM), inst, NULL, (void **)&netip);
+ if (sts < 0)
+ return sts;
+ if (idp->item <= 15) {
+ /* network.interface.{in,out} */
+ atom->ull = netip->counters[idp->item];
+ }
+ else
+ switch (idp->item) {
+ case 16: /* network.interface.total.bytes */
+ atom->ull = netip->counters[0] + netip->counters[8];
+ break;
+ case 17: /* network.interface.total.packets */
+ atom->ull = netip->counters[1] + netip->counters[9];
+ break;
+ case 18: /* network.interface.total.errors */
+ atom->ull = netip->counters[2] + netip->counters[10];
+ break;
+ case 19: /* network.interface.total.drops */
+ atom->ull = netip->counters[3] + netip->counters[11];
+ break;
+ case 20: /* network.interface.total.mcasts */
+ /*
+ * NOTE: there is no network.interface.out.mcasts metric
+ * so this total only includes network.interface.in.mcasts
+ */
+ atom->ull = netip->counters[7];
+ break;
+ case 21: /* network.interface.mtu */
+ if (!netip->ioc.mtu)
+ return 0;
+ atom->ul = netip->ioc.mtu;
+ break;
+ case 22: /* network.interface.speed */
+ if (!netip->ioc.speed)
+ return 0;
+ atom->f = ((float)netip->ioc.speed * 1000000) / 8 / 1024 / 1024;
+ break;
+ case 23: /* network.interface.baudrate */
+ if (!netip->ioc.speed)
+ return 0;
+ atom->ul = ((long long)netip->ioc.speed * 1000000 / 8);
+ break;
+ case 24: /* network.interface.duplex */
+ if (!netip->ioc.duplex)
+ return 0;
+ atom->ul = netip->ioc.duplex;
+ break;
+ case 25: /* network.interface.up */
+ atom->ul = netip->ioc.linkup;
+ break;
+ case 26: /* network.interface.running */
+ atom->ul = netip->ioc.running;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_NET_ADDR:
+ sts = pmdaCacheLookup(INDOM(NET_ADDR_INDOM), inst, NULL, (void **)&addrp);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ switch (idp->item) {
+ case 0: /* network.interface.inet_addr */
+ if (addrp->has_inet == 0)
+ return 0;
+ atom->cp = addrp->inet;
+ break;
+ case 1: /* network.interface.ipv6_addr */
+ if (addrp->has_ipv6 == 0)
+ return 0;
+ atom->cp = addrp->ipv6;
+ break;
+ case 2: /* network.interface.ipv6_scope */
+ if (addrp->has_ipv6 == 0)
+ return 0;
+ atom->cp = lookup_ipv6_scope(addrp->ipv6scope);
+ break;
+ case 3: /* network.interface.hw_addr */
+ if (addrp->has_hw == 0)
+ return 0;
+ atom->cp = addrp->hw_addr;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_FILESYS:
+ if (idp->item == 0)
+ atom->ul = pmdaCacheOp(INDOM(FILESYS_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ else {
+ struct statfs *sbuf;
+ __uint64_t ull, used;
+
+ sts = pmdaCacheLookup(INDOM(FILESYS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ sbuf = &fs->stats;
+ if (!(fs->flags & FSF_FETCHED)) {
+ if (statfs(fs->path, sbuf) < 0)
+ return PM_ERR_INST;
+ fs->flags |= FSF_FETCHED;
+ }
+
+ switch (idp->item) {
+ case 1: /* filesys.capacity */
+ ull = (__uint64_t)sbuf->f_blocks;
+ atom->ull = ull * sbuf->f_bsize / 1024;
+ break;
+ case 2: /* filesys.used */
+ used = (__uint64_t)(sbuf->f_blocks - sbuf->f_bfree);
+ atom->ull = used * sbuf->f_bsize / 1024;
+ break;
+ case 3: /* filesys.free */
+ ull = (__uint64_t)sbuf->f_bfree;
+ atom->ull = ull * sbuf->f_bsize / 1024;
+ break;
+ case 4: /* filesys.maxfiles */
+ atom->ul = sbuf->f_files;
+ break;
+ case 5: /* filesys.usedfiles */
+ atom->ul = sbuf->f_files - sbuf->f_ffree;
+ break;
+ case 6: /* filesys.freefiles */
+ atom->ul = sbuf->f_ffree;
+ break;
+ case 7: /* filesys.mountdir */
+ atom->cp = fs->path;
+ break;
+ case 8: /* filesys.full */
+ used = (__uint64_t)(sbuf->f_blocks - sbuf->f_bfree);
+ ull = used + (__uint64_t)sbuf->f_bavail;
+ atom->d = (100.0 * (double)used) / (double)ull;
+ break;
+ case 9: /* filesys.blocksize -- added by Mike Mason <mmlnx@us.ibm.com> */
+ atom->ul = sbuf->f_bsize;
+ break;
+ case 10: /* filesys.avail -- added by Mike Mason <mmlnx@us.ibm.com> */
+ ull = (__uint64_t)sbuf->f_bavail;
+ atom->ull = ull * sbuf->f_bsize / 1024;
+ break;
+ case 11: /* filesys.readonly */
+ atom->ul = (scan_filesys_options(fs->options, "ro") != NULL);
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ break;
+
+ case CLUSTER_TMPFS: {
+ struct statfs *sbuf;
+ __uint64_t ull, used;
+
+ sts = pmdaCacheLookup(INDOM(TMPFS_INDOM), inst, NULL, (void **)&fs);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ sbuf = &fs->stats;
+ if (!(fs->flags & FSF_FETCHED)) {
+ if (statfs(fs->path, sbuf) < 0)
+ return PM_ERR_INST;
+ fs->flags |= FSF_FETCHED;
+ }
+
+ switch (idp->item) {
+ case 1: /* tmpfs.capacity */
+ ull = (__uint64_t)sbuf->f_blocks;
+ atom->ull = ull * sbuf->f_bsize / 1024;
+ break;
+ case 2: /* tmpfs.used */
+ used = (__uint64_t)(sbuf->f_blocks - sbuf->f_bfree);
+ atom->ull = used * sbuf->f_bsize / 1024;
+ break;
+ case 3: /* tmpfs.free */
+ ull = (__uint64_t)sbuf->f_bfree;
+ atom->ull = ull * sbuf->f_bsize / 1024;
+ break;
+ case 4: /* tmpfs.maxfiles */
+ atom->ul = sbuf->f_files;
+ break;
+ case 5: /* tmpfs.usedfiles */
+ atom->ul = sbuf->f_files - sbuf->f_ffree;
+ break;
+ case 6: /* tmpfs.freefiles */
+ atom->ul = sbuf->f_ffree;
+ break;
+ case 7: /* tmpfs.full */
+ used = (__uint64_t)(sbuf->f_blocks - sbuf->f_bfree);
+ ull = used + (__uint64_t)sbuf->f_bavail;
+ atom->d = (100.0 * (double)used) / (double)ull;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ break;
+
+ case CLUSTER_SWAPDEV: {
+ struct swapdev *swap;
+
+ sts = pmdaCacheLookup(INDOM(SWAPDEV_INDOM), inst, NULL, (void **)&swap);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+ case 0: /* swapdev.free (kbytes) */
+ atom->ul = swap->size - swap->used;
+ break;
+ case 1: /* swapdev.length (kbytes) */
+ case 2: /* swapdev.maxswap (kbytes) */
+ atom->ul = swap->size;
+ break;
+ case 3: /* swapdev.vlength (kbytes) */
+ atom->ul = 0;
+ break;
+ case 4: /* swapdev.priority */
+ atom->l = swap->priority;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+ }
+
+ case CLUSTER_NET_NFS:
+ switch (idp->item) {
+ case 1: /* nfs.client.calls */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.client.reqcounts[i];
+ }
+ break;
+ case 50: /* nfs.server.calls */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.server.reqcounts[i];
+ }
+ break;
+ case 4: /* nfs.client.reqs */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC_COUNTERS)
+ atom->ul = proc_net_rpc.client.reqcounts[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ case 12: /* nfs.server.reqs */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC_COUNTERS)
+ atom->ul = proc_net_rpc.server.reqcounts[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ case 60: /* nfs3.client.calls */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC3_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.client.reqcounts3[i];
+ }
+ break;
+
+ case 62: /* nfs3.server.calls */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC3_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.server.reqcounts3[i];
+ }
+ break;
+
+ case 61: /* nfs3.client.reqs */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC3_COUNTERS)
+ atom->ul = proc_net_rpc.client.reqcounts3[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ case 63: /* nfs3.server.reqs */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC3_COUNTERS)
+ atom->ul = proc_net_rpc.server.reqcounts3[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ case 64: /* nfs4.client.calls */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC4_CLI_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.client.reqcounts4[i];
+ }
+ break;
+
+ case 66: /* nfs4.server.calls */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ for (atom->ul=0, i=0; i < NR_RPC4_SVR_COUNTERS; i++) {
+ atom->ul += proc_net_rpc.server.reqcounts4[i];
+ }
+ break;
+
+ case 65: /* nfs4.client.reqs */
+ if (proc_net_rpc.client.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC4_CLI_COUNTERS)
+ atom->ul = proc_net_rpc.client.reqcounts4[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ case 67: /* nfs4.server.reqs */
+ if (proc_net_rpc.server.errcode != 0)
+ return 0; /* no values available */
+ if (inst < NR_RPC4_SVR_COUNTERS)
+ atom->ul = proc_net_rpc.server.reqcounts4[inst];
+ else
+ return PM_ERR_INST;
+ break;
+
+ /*
+ * Note: all other rpc metric values are extracted directly via the
+ * address specified in the metrictab (see above)
+ */
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_SLAB:
+ if (proc_slabinfo.ncaches == 0)
+ return 0; /* no values available */
+
+ if (inst >= proc_slabinfo.ncaches)
+ return PM_ERR_INST;
+
+ switch(idp->item) {
+ case 0: /* mem.slabinfo.objects.active */
+ atom->ull = proc_slabinfo.caches[inst].num_active_objs;
+ break;
+ case 1: /* mem.slabinfo.objects.total */
+ atom->ull = proc_slabinfo.caches[inst].total_objs;
+ break;
+ case 2: /* mem.slabinfo.objects.size */
+ if (proc_slabinfo.caches[inst].seen < 11) /* version 1.1 or later only */
+ return 0;
+ atom->ul = proc_slabinfo.caches[inst].object_size;
+ break;
+ case 3: /* mem.slabinfo.slabs.active */
+ if (proc_slabinfo.caches[inst].seen < 11) /* version 1.1 or later only */
+ return 0;
+ atom->ul = proc_slabinfo.caches[inst].num_active_slabs;
+ break;
+ case 4: /* mem.slabinfo.slabs.total */
+ if (proc_slabinfo.caches[inst].seen == 11) /* version 1.1 only */
+ return 0;
+ atom->ul = proc_slabinfo.caches[inst].total_slabs;
+ break;
+ case 5: /* mem.slabinfo.slabs.pages_per_slab */
+ if (proc_slabinfo.caches[inst].seen < 11) /* version 1.1 or later only */
+ return 0;
+ atom->ul = proc_slabinfo.caches[inst].pages_per_slab;
+ break;
+ case 6: /* mem.slabinfo.slabs.objects_per_slab */
+ if (proc_slabinfo.caches[inst].seen != 20) /* version 2.0 only */
+ return 0;
+ atom->ul = proc_slabinfo.caches[inst].objects_per_slab;
+ break;
+ case 7: /* mem.slabinfo.slabs.total_size */
+ if (proc_slabinfo.caches[inst].seen < 11) /* version 1.1 or later only */
+ return 0;
+ atom->ull = proc_slabinfo.caches[inst].total_size;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_PARTITIONS:
+ return proc_partitions_fetch(mdesc, inst, atom);
+
+ case CLUSTER_SCSI:
+ if (proc_scsi.nscsi == 0)
+ return 0; /* no values available */
+ switch(idp->item) {
+ case 0: /* hinv.map.scsi */
+ atom->cp = (char *)NULL;
+ for (i=0; i < proc_scsi.nscsi; i++) {
+ if (proc_scsi.scsi[i].id == inst) {
+ atom->cp = proc_scsi.scsi[i].dev_name;
+ break;
+ }
+ }
+ if (i == proc_scsi.nscsi)
+ return PM_ERR_INST;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_LV:
+ switch(idp->item) {
+ case 0: /* hinv.map.lvname */
+ if (dev_mapper.nlv == 0)
+ return 0; /* no values available */
+ atom->cp = (char *)NULL;
+ for (i = 0; i < dev_mapper.nlv; i++) {
+ if (dev_mapper.lv[i].id == inst) {
+ atom->cp = dev_mapper.lv[i].dev_name;
+ break;
+ }
+ }
+ if (i == dev_mapper.nlv)
+ return PM_ERR_INST;
+ break;
+ case 1: /* hinv.nlv */
+ atom->ul = dev_mapper.nlv;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_KERNEL_UNAME:
+ switch(idp->item) {
+ case 5: /* pmda.uname */
+ sprintf(uname_string, "%s %s %s %s %s",
+ kernel_uname.sysname,
+ kernel_uname.nodename,
+ kernel_uname.release,
+ kernel_uname.version,
+ kernel_uname.machine);
+ atom->cp = uname_string;
+ break;
+
+ case 6: /* pmda.version */
+ atom->cp = pmGetConfig("PCP_VERSION");
+ break;
+
+ case 7: /* kernel.uname.distro ... not from uname(2) */
+ atom->cp = get_distro_info();
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_CPUINFO:
+ if (idp->item != 7 && /* hinv.machine is singular */
+ (inst >= proc_cpuinfo.cpuindom->it_numinst))
+ return PM_ERR_INST;
+ switch(idp->item) {
+ case 0: /* hinv.cpu.clock */
+ if (proc_cpuinfo.cpuinfo[inst].clock == 0.0)
+ return 0;
+ atom->f = proc_cpuinfo.cpuinfo[inst].clock;
+ break;
+ case 1: /* hinv.cpu.vendor */
+ i = proc_cpuinfo.cpuinfo[inst].vendor;
+ atom->cp = linux_strings_lookup(i);
+ if (atom->cp == NULL)
+ atom->cp = "unknown";
+ break;
+ case 2: /* hinv.cpu.model */
+ if ((i = proc_cpuinfo.cpuinfo[inst].model) < 0)
+ i = proc_cpuinfo.cpuinfo[inst].model_name;
+ atom->cp = linux_strings_lookup(i);
+ if (atom->cp == NULL)
+ atom->cp = "unknown";
+ break;
+ case 3: /* hinv.cpu.stepping */
+ i = proc_cpuinfo.cpuinfo[inst].stepping;
+ atom->cp = linux_strings_lookup(i);
+ if (atom->cp == NULL)
+ atom->cp = "unknown";
+ break;
+ case 4: /* hinv.cpu.cache */
+ if (!proc_cpuinfo.cpuinfo[inst].cache)
+ return 0;
+ atom->ul = proc_cpuinfo.cpuinfo[inst].cache;
+ break;
+ case 5: /* hinv.cpu.bogomips */
+ if (proc_cpuinfo.cpuinfo[inst].bogomips == 0.0)
+ return 0;
+ atom->f = proc_cpuinfo.cpuinfo[inst].bogomips;
+ break;
+ case 6: /* hinv.map.cpu_num */
+ atom->ul = proc_cpuinfo.cpuinfo[inst].cpu_num;
+ break;
+ case 7: /* hinv.machine */
+ atom->cp = proc_cpuinfo.machine;
+ break;
+ case 8: /* hinv.map.cpu_node */
+ atom->ul = proc_cpuinfo.cpuinfo[inst].node;
+ break;
+ case 9: /* hinv.cpu.model_name */
+ if ((i = proc_cpuinfo.cpuinfo[inst].model_name) < 0)
+ i = proc_cpuinfo.cpuinfo[inst].model;
+ atom->cp = linux_strings_lookup(i);
+ if (atom->cp == NULL)
+ atom->cp = "unknown";
+ break;
+ case 10: /* hinv.cpu.flags */
+ i = proc_cpuinfo.cpuinfo[inst].flags;
+ atom->cp = linux_strings_lookup(i);
+ if (atom->cp == NULL)
+ atom->cp = "unknown";
+ break;
+ case 11: /* hinv.cpu.cache_alignment */
+ if (!proc_cpuinfo.cpuinfo[inst].cache_align)
+ return 0;
+ atom->ul = proc_cpuinfo.cpuinfo[inst].cache_align;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ /*
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ case CLUSTER_SEM_LIMITS:
+ switch (idp->item) {
+ case 0: /* ipc.sem.max_semmap */
+ atom->ul = sem_limits.semmap;
+ break;
+ case 1: /* ipc.sem.max_semid */
+ atom->ul = sem_limits.semmni;
+ break;
+ case 2: /* ipc.sem.max_sem */
+ atom->ul = sem_limits.semmns;
+ break;
+ case 3: /* ipc.sem.num_undo */
+ atom->ul = sem_limits.semmnu;
+ break;
+ case 4: /* ipc.sem.max_perid */
+ atom->ul = sem_limits.semmsl;
+ break;
+ case 5: /* ipc.sem.max_ops */
+ atom->ul = sem_limits.semopm;
+ break;
+ case 6: /* ipc.sem.max_undoent */
+ atom->ul = sem_limits.semume;
+ break;
+ case 7: /* ipc.sem.sz_semundo */
+ atom->ul = sem_limits.semusz;
+ break;
+ case 8: /* ipc.sem.max_semval */
+ atom->ul = sem_limits.semvmx;
+ break;
+ case 9: /* ipc.sem.max_exit */
+ atom->ul = sem_limits.semaem;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ /*
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ case CLUSTER_MSG_LIMITS:
+ switch (idp->item) {
+ case 0: /* ipc.msg.sz_pool */
+ atom->ul = msg_limits.msgpool;
+ break;
+ case 1: /* ipc.msg.mapent */
+ atom->ul = msg_limits.msgmap;
+ break;
+ case 2: /* ipc.msg.max_msgsz */
+ atom->ul = msg_limits.msgmax;
+ break;
+ case 3: /* ipc.msg.max_defmsgq */
+ atom->ul = msg_limits.msgmnb;
+ break;
+ case 4: /* ipc.msg.max_msgqid */
+ atom->ul = msg_limits.msgmni;
+ break;
+ case 5: /* ipc.msg.sz_msgseg */
+ atom->ul = msg_limits.msgssz;
+ break;
+ case 6: /* ipc.msg.num_smsghdr */
+ atom->ul = msg_limits.msgtql;
+ break;
+ case 7: /* ipc.msg.max_seg */
+ atom->ul = (unsigned long) msg_limits.msgseg;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ /*
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ case CLUSTER_SHM_LIMITS:
+ switch (idp->item) {
+ case 0: /* ipc.shm.max_segsz */
+ atom->ul = shm_limits.shmmax;
+ break;
+ case 1: /* ipc.shm.min_segsz */
+ atom->ul = shm_limits.shmmin;
+ break;
+ case 2: /* ipc.shm.max_seg */
+ atom->ul = shm_limits.shmmni;
+ break;
+ case 3: /* ipc.shm.max_segproc */
+ atom->ul = shm_limits.shmseg;
+ break;
+ case 4: /* ipc.shm.max_shmsys */
+ atom->ul = shm_limits.shmall;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ /*
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ case CLUSTER_NUSERS:
+ {
+ /* count the number of users */
+ struct utmp *ut;
+ atom->ul = 0;
+ setutent();
+ while ((ut = getutent())) {
+ if ((ut->ut_type == USER_PROCESS) && (ut->ut_name[0] != '\0'))
+ atom->ul++;
+ }
+ endutent();
+ }
+ break;
+
+
+ case CLUSTER_IB: /* deprecated: network.ib, use infiniband PMDA */
+ return PM_ERR_APPVERSION;
+
+ case CLUSTER_NUMA_MEMINFO:
+ /* NUMA memory metrics from /sys/devices/system/node/nodeX */
+ if (inst >= numa_meminfo.node_indom->it_numinst)
+ return PM_ERR_INST;
+
+ switch(idp->item) {
+ case 0: /* mem.numa.util.total */
+ sts = linux_table_lookup("MemTotal:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+ case 1: /* mem.numa.util.free */
+ sts = linux_table_lookup("MemFree:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 2: /* mem.numa.util.used */
+ sts = linux_table_lookup("MemUsed:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 3: /* mem.numa.util.active */
+ sts = linux_table_lookup("Active:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 4: /* mem.numa.util.inactive */
+ sts = linux_table_lookup("Inactive:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 5: /* mem.numa.util.active_anon */
+ sts = linux_table_lookup("Active(anon):", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 6: /* mem.numa.util.inactive_anon */
+ sts = linux_table_lookup("Inactive(anon):", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 7: /* mem.numa.util.active_file */
+ sts = linux_table_lookup("Active(file):", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 8: /* mem.numa.util.inactive_file */
+ sts = linux_table_lookup("Inactive(file):", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 9: /* mem.numa.util.highTotal */
+ sts = linux_table_lookup("HighTotal:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 10: /* mem.numa.util.highFree */
+ sts = linux_table_lookup("HighFree:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 11: /* mem.numa.util.lowTotal */
+ sts = linux_table_lookup("LowTotal:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 12: /* mem.numa.util.lowFree */
+ sts = linux_table_lookup("LowFree:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 13: /* mem.numa.util.unevictable */
+ sts = linux_table_lookup("Unevictable:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 14: /* mem.numa.util.mlocked */
+ sts = linux_table_lookup("Mlocked:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 15: /* mem.numa.util.dirty */
+ sts = linux_table_lookup("Dirty:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 16: /* mem.numa.util.writeback */
+ sts = linux_table_lookup("Writeback:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 17: /* mem.numa.util.filePages */
+ sts = linux_table_lookup("FilePages:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 18: /* mem.numa.util.mapped */
+ sts = linux_table_lookup("Mapped:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 19: /* mem.numa.util.anonPages */
+ sts = linux_table_lookup("AnonPages:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 20: /* mem.numa.util.shmem */
+ sts = linux_table_lookup("Shmem:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 21: /* mem.numa.util.kernelStack */
+ sts = linux_table_lookup("KernelStack:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 22: /* mem.numa.util.pageTables */
+ sts = linux_table_lookup("PageTables:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 23: /* mem.numa.util.NFS_Unstable */
+ sts = linux_table_lookup("NFS_Unstable:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 24: /* mem.numa.util.bounce */
+ sts = linux_table_lookup("Bounce:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 25: /* mem.numa.util.writebackTmp */
+ sts = linux_table_lookup("WritebackTmp:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 26: /* mem.numa.util.slab */
+ sts = linux_table_lookup("Slab:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 27: /* mem.numa.util.slabReclaimable */
+ sts = linux_table_lookup("SReclaimable:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 28: /* mem.numa.util.slabUnreclaimable */
+ sts = linux_table_lookup("SUnreclaim:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 29: /* mem.numa.util.hugepagesTotal */
+ sts = linux_table_lookup("HugePages_Total:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 30: /* mem.numa.util.hugepagesFree */
+ sts = linux_table_lookup("HugePages_Free:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 31: /* mem.numa.util.hugepagesSurp */
+ sts = linux_table_lookup("HugePages_Surp:", numa_meminfo.node_info[inst].meminfo,
+ &atom->ull);
+ break;
+
+ case 32: /* mem.numa.alloc.hit */
+ sts = linux_table_lookup("numa_hit", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ case 33: /* mem.numa.alloc.miss */
+ sts = linux_table_lookup("numa_miss", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ case 34: /* mem.numa.alloc.foreign */
+ sts = linux_table_lookup("numa_foreign", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ case 35: /* mem.numa.alloc.interleave_hit */
+ sts = linux_table_lookup("interleave_hit", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ case 36: /* mem.numa.alloc.local_node */
+ sts = linux_table_lookup("local_node", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ case 37: /* mem.numa.alloc.other_node */
+ sts = linux_table_lookup("other_node", numa_meminfo.node_info[inst].memstat,
+ &atom->ull);
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ return sts;
+
+ case CLUSTER_INTERRUPTS:
+ switch (idp->item) {
+ case 3: /* kernel.all.interrupts.error */
+ atom->ul = irq_err_count;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_INTERRUPT_LINES:
+ case CLUSTER_INTERRUPT_OTHER:
+ if (inst >= indomtab[CPU_INDOM].it_numinst)
+ return PM_ERR_INST;
+ return interrupts_fetch(idp->cluster, idp->item, inst, atom);
+
+ case CLUSTER_DM:
+ return proc_partitions_fetch(mdesc, inst, atom);
+ break;
+
+ default: /* unknown cluster */
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
+
+
+static int
+linux_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i;
+ int need_refresh[NUM_CLUSTERS];
+
+ memset(need_refresh, 0, sizeof(need_refresh));
+ for (i=0; i < numpmid; i++) {
+ __pmID_int *idp = (__pmID_int *)&(pmidlist[i]);
+ if (idp->cluster < NUM_CLUSTERS) {
+ need_refresh[idp->cluster]++;
+
+ if ((idp->cluster == CLUSTER_STAT || idp->cluster == CLUSTER_DM) &&
+ need_refresh[CLUSTER_PARTITIONS] == 0 &&
+ is_partitions_metric(pmidlist[i]))
+ need_refresh[CLUSTER_PARTITIONS]++;
+
+ if (idp->cluster == CLUSTER_CPUINFO ||
+ idp->cluster == CLUSTER_INTERRUPT_LINES ||
+ idp->cluster == CLUSTER_INTERRUPT_OTHER ||
+ idp->cluster == CLUSTER_INTERRUPTS)
+ need_refresh[CLUSTER_STAT]++;
+ }
+
+ /* In 2.6 kernels, swap.{pagesin,pagesout} are in /proc/vmstat */
+ if (_pm_have_proc_vmstat && idp->cluster == CLUSTER_STAT) {
+ if (idp->item >= 8 && idp->item <= 11)
+ need_refresh[CLUSTER_VMSTAT]++;
+ }
+ }
+
+ linux_refresh(pmda, need_refresh);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+linux_text(int ident, int type, char **buf, pmdaExt *pmda)
+{
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ int sts = pmdaDynamicLookupText(ident, type, buf, pmda);
+ if (sts != -ENOENT)
+ return sts;
+ }
+ return pmdaText(ident, type, buf, pmda);
+}
+
+static int
+linux_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupName(pmda, name);
+ return pmdaTreePMID(tree, name, pmid);
+}
+
+static int
+linux_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupPMID(pmda, pmid);
+ return pmdaTreeName(tree, pmid, nameset);
+}
+
+static int
+linux_children(const char *name, int flag, char ***kids, int **sts, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupName(pmda, name);
+ return pmdaTreeChildren(tree, name, flag, kids, sts);
+}
+
+pmInDom
+linux_indom(int serial)
+{
+ return indomtab[serial].it_indom;
+}
+
+pmdaIndom *
+linux_pmda_indom(int serial)
+{
+ return &indomtab[serial];
+}
+
+/*
+ * Helper routines for accessing a generic static string dictionary
+ */
+
+char *
+linux_strings_lookup(int index)
+{
+ char *value;
+ pmInDom dict = INDOM(STRINGS_INDOM);
+
+ if (pmdaCacheLookup(dict, index, &value, NULL) == PMDA_CACHE_ACTIVE)
+ return value;
+ return NULL;
+}
+
+int
+linux_strings_insert(const char *buf)
+{
+ pmInDom dict = INDOM(STRINGS_INDOM);
+ return pmdaCacheStore(dict, PMDA_CACHE_ADD, buf, NULL);
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+
+void
+__PMDA_INIT_CALL
+linux_init(pmdaInterface *dp)
+{
+ int i, major, minor, point;
+ size_t nmetrics, nindoms;
+ char *envpath;
+ __pmID_int *idp;
+
+ _pm_system_pagesize = getpagesize();
+ if ((envpath = getenv("LINUX_STATSPATH")) != NULL)
+ linux_statspath = envpath;
+
+ if (_isDSO) {
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(helppath, sizeof(helppath), "%s%c" "linux" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_4, "linux DSO", helppath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.instance = linux_instance;
+ dp->version.four.fetch = linux_fetch;
+ dp->version.four.text = linux_text;
+ dp->version.four.pmid = linux_pmid;
+ dp->version.four.name = linux_name;
+ dp->version.four.children = linux_children;
+ pmdaSetFetchCallBack(dp, linux_fetchCallBack);
+
+ proc_stat.cpu_indom = proc_cpuinfo.cpuindom = &indomtab[CPU_INDOM];
+ numa_meminfo.node_indom = proc_cpuinfo.node_indom = &indomtab[NODE_INDOM];
+ proc_scsi.scsi_indom = &indomtab[SCSI_INDOM];
+ dev_mapper.lv_indom = &indomtab[LV_INDOM];
+ proc_slabinfo.indom = &indomtab[SLAB_INDOM];
+
+ /*
+ * Figure out kernel version. The precision of certain metrics
+ * (e.g. percpu time counters) has changed over kernel versions.
+ * See include/linux/kernel_stat.h for all the various flavours.
+ */
+ uname(&kernel_uname);
+ _pm_ctxt_size = 8;
+ _pm_intr_size = 8;
+ _pm_cputime_size = 8;
+ _pm_idletime_size = 8;
+ if (sscanf(kernel_uname.release, "%d.%d.%d", &major, &minor, &point) == 3) {
+ if (major < 2 || (major == 2 && minor <= 4)) { /* 2.4 and earlier */
+ _pm_ctxt_size = 4;
+ _pm_intr_size = 4;
+ _pm_cputime_size = 4;
+ _pm_idletime_size = sizeof(unsigned long);
+ }
+ else if (major == 2 && minor == 6 &&
+ point >= 0 && point <= 4) { /* 2.6.0->.4 */
+ _pm_cputime_size = 4;
+ _pm_idletime_size = 4;
+ }
+ }
+ for (i = 0; i < sizeof(metrictab)/sizeof(pmdaMetric); i++) {
+ idp = (__pmID_int *)&(metrictab[i].m_desc.pmid);
+ if (idp->cluster == CLUSTER_STAT) {
+ switch (idp->item) {
+ case 0: /* kernel.percpu.cpu.user */
+ case 1: /* kernel.percpu.cpu.nice */
+ case 2: /* kernel.percpu.cpu.sys */
+ case 20: /* kernel.all.cpu.user */
+ case 21: /* kernel.all.cpu.nice */
+ case 22: /* kernel.all.cpu.sys */
+ case 30: /* kernel.percpu.cpu.wait.total */
+ case 31: /* kernel.percpu.cpu.intr */
+ case 34: /* kernel.all.cpu.intr */
+ case 35: /* kernel.all.cpu.wait.total */
+ case 53: /* kernel.all.cpu.irq.soft */
+ case 54: /* kernel.all.cpu.irq.hard */
+ case 55: /* kernel.all.cpu.steal */
+ case 56: /* kernel.percpu.cpu.irq.soft */
+ case 57: /* kernel.percpu.cpu.irq.hard */
+ case 58: /* kernel.percpu.cpu.steal */
+ case 60: /* kernel.all.cpu.guest */
+ case 78: /* kernel.all.cpu.vuser */
+ case 61: /* kernel.percpu.cpu.guest */
+ case 76: /* kernel.percpu.cpu.vuser */
+ case 62: /* kernel.pernode.cpu.user */
+ case 63: /* kernel.pernode.cpu.nice */
+ case 64: /* kernel.pernode.cpu.sys */
+ case 69: /* kernel.pernode.cpu.wait.total */
+ case 66: /* kernel.pernode.cpu.intr */
+ case 70: /* kernel.pernode.cpu.irq.soft */
+ case 71: /* kernel.pernode.cpu.irq.hard */
+ case 67: /* kernel.pernode.cpu.steal */
+ case 68: /* kernel.pernode.cpu.guest */
+ case 77: /* kernel.pernode.cpu.vuser */
+ _pm_metric_type(metrictab[i].m_desc.type, _pm_cputime_size);
+ break;
+ case 3: /* kernel.percpu.cpu.idle */
+ case 23: /* kernel.all.cpu.idle */
+ case 65: /* kernel.pernode.cpu.idle */
+ _pm_metric_type(metrictab[i].m_desc.type, _pm_idletime_size);
+ break;
+ case 12: /* kernel.all.intr */
+ _pm_metric_type(metrictab[i].m_desc.type, _pm_intr_size);
+ break;
+ case 13: /* kernel.all.pswitch */
+ _pm_metric_type(metrictab[i].m_desc.type, _pm_ctxt_size);
+ break;
+ }
+ }
+ if (metrictab[i].m_desc.type == PM_TYPE_NOSUPPORT)
+ fprintf(stderr, "Bad kernel metric descriptor type (%u.%u)\n",
+ idp->cluster, idp->item);
+ }
+
+ nindoms = sizeof(indomtab)/sizeof(indomtab[0]);
+ nmetrics = sizeof(metrictab)/sizeof(metrictab[0]);
+
+ proc_vmstat_init();
+ interrupts_init(metrictab, nmetrics);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, indomtab, nindoms, metrictab, nmetrics);
+
+ /* string metrics use the pmdaCache API for value indexing */
+ pmdaCacheOp(INDOM(STRINGS_INDOM), PMDA_CACHE_STRINGS);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "linux" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, LINUX, "linux.log", helppath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ linux_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/linux/proc_cpuinfo.c b/src/pmdas/linux/proc_cpuinfo.c
new file mode 100644
index 0000000..f76ac27
--- /dev/null
+++ b/src/pmdas/linux/proc_cpuinfo.c
@@ -0,0 +1,246 @@
+/*
+ * Linux /proc/cpuinfo metrics cluster
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000-2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Portions Copyright (c) 2001 Gilly Ran (gilly@exanet.com) - for the
+ * portions supporting the Alpha platform. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_cpuinfo.h"
+
+
+static void
+decode_map(proc_cpuinfo_t *proc_cpuinfo, char *cp, int node, int offset)
+{
+ uint32_t map = strtoul(cp, NULL, 16);
+
+ while (map) {
+ int i;
+
+ if ((i = ffsl(map))) {
+ /* the kernel returns 32bit words in the map file */
+ int cpu = i - 1 + 32*offset;
+
+ proc_cpuinfo->cpuinfo[cpu].node = node;
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cpu %d -> node %d\n",
+ cpu, node);
+ }
+ map &= ~(1 << (i-1));
+ }
+ }
+}
+
+static void
+map_cpu_nodes(proc_cpuinfo_t *proc_cpuinfo)
+{
+ int i, j;
+ const char *node_path = "sys/devices/system/node";
+ char path[MAXPATHLEN];
+ char cpumap[4096];
+ DIR *nodes;
+ FILE *f;
+ struct dirent *de;
+ int node, max_node = -1;
+ char *cp;
+ pmdaIndom *idp = PMDAINDOM(NODE_INDOM);
+
+ for (i = 0; i < proc_cpuinfo->cpuindom->it_numinst; i++)
+ proc_cpuinfo->cpuinfo[i].node = -1;
+
+ snprintf(path, sizeof(path), "%s/%s", linux_statspath, node_path);
+ if ((nodes = opendir(path)) == NULL)
+ return;
+
+ while ((de = readdir(nodes)) != NULL) {
+ if (sscanf(de->d_name, "node%d", &node) != 1)
+ continue;
+
+ if (node > max_node)
+ max_node = node;
+
+ snprintf(path, sizeof(path), "%s/%s/%s/cpumap",
+ linux_statspath, node_path, de->d_name);
+ if ((f = fopen(path, "r")) == NULL)
+ continue;
+ i = fscanf(f, "%s", cpumap);
+ fclose(f);
+ if (i != 1)
+ continue;
+
+ for (j = 0; (cp = strrchr(cpumap, ',')); j++) {
+ decode_map(proc_cpuinfo, cp+1, node, j);
+ *cp = '\0';
+ }
+ decode_map(proc_cpuinfo, cpumap, node, j);
+ }
+ closedir(nodes);
+
+ /* initialize node indom */
+ idp->it_numinst = max_node + 1;
+ idp->it_set = calloc(max_node + 1, sizeof(pmdaInstid));
+ for (i = 0; i <= max_node; i++) {
+ char node_name[256];
+
+ sprintf(node_name, "node%d", i);
+ idp->it_set[i].i_inst = i;
+ idp->it_set[i].i_name = strdup(node_name);
+ }
+ proc_cpuinfo->node_indom = idp;
+}
+
+char *
+cpu_name(proc_cpuinfo_t *proc_cpuinfo, int c)
+{
+ char name[1024];
+ char *p;
+ FILE *f;
+ static int started = 0;
+
+ if (!started) {
+ refresh_proc_cpuinfo(proc_cpuinfo);
+
+ proc_cpuinfo->machine = NULL;
+ f = linux_statsfile("/proc/sgi_prominfo/node0/version", name, sizeof(name));
+ if (f != NULL) {
+ while (fgets(name, sizeof(name), f)) {
+ if (strncmp(name, "SGI", 3) == 0) {
+ if ((p = strstr(name, " IP")) != NULL)
+ proc_cpuinfo->machine = strndup(p+1, 4);
+ break;
+ }
+ }
+ fclose(f);
+ }
+ if (proc_cpuinfo->machine == NULL)
+ proc_cpuinfo->machine = strdup("linux");
+
+ started = 1;
+ }
+
+ snprintf(name, sizeof(name), "cpu%d", c);
+ return strdup(name);
+}
+
+int
+refresh_proc_cpuinfo(proc_cpuinfo_t *proc_cpuinfo)
+{
+ char buf[4096];
+ FILE *fp;
+ int cpunum;
+ cpuinfo_t *info;
+ char *val;
+ char *p;
+ static int started = 0;
+
+ if (!started) {
+ int need = proc_cpuinfo->cpuindom->it_numinst * sizeof(cpuinfo_t);
+ proc_cpuinfo->cpuinfo = (cpuinfo_t *)calloc(1, need);
+ for (cpunum=0; cpunum < proc_cpuinfo->cpuindom->it_numinst; cpunum++) {
+ proc_cpuinfo->cpuinfo[cpunum].sapic = -1;
+ proc_cpuinfo->cpuinfo[cpunum].vendor = -1;
+ proc_cpuinfo->cpuinfo[cpunum].model = -1;
+ proc_cpuinfo->cpuinfo[cpunum].model_name = -1;
+ proc_cpuinfo->cpuinfo[cpunum].stepping = -1;
+ proc_cpuinfo->cpuinfo[cpunum].flags = -1;
+ }
+ started = 1;
+ }
+
+ if ((fp = linux_statsfile("/proc/cpuinfo", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+#if defined(HAVE_ALPHA_LINUX)
+ cpunum = 0;
+#else //intel
+ cpunum = -1;
+#endif
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((val = strrchr(buf, '\n')) != NULL)
+ *val = '\0';
+ if ((val = strchr(buf, ':')) == NULL)
+ continue;
+ val += 2;
+
+#if !defined(HAVE_ALPHA_LINUX)
+ if (strncmp(buf, "processor", 9) == 0) {
+ cpunum++;
+ proc_cpuinfo->cpuinfo[cpunum].cpu_num = atoi(val);
+ continue;
+ }
+#endif
+
+ if (cpunum < 0 || cpunum >= proc_cpuinfo->cpuindom->it_numinst)
+ continue;
+
+ info = &proc_cpuinfo->cpuinfo[cpunum];
+
+ /* note: order is important due to strNcmp comparisons */
+ if (info->sapic < 0 && strncasecmp(buf, "sapic", 5) == 0)
+ info->sapic = linux_strings_insert(val);
+ else if (info->model_name < 0 && strncasecmp(buf, "model name", 10) == 0)
+ info->model_name = linux_strings_insert(val);
+ else if (info->model < 0 && strncasecmp(buf, "model", 5) == 0)
+ info->model = linux_strings_insert(val);
+ else if (info->model < 0 && strncasecmp(buf, "cpu model", 9) == 0)
+ info->model = linux_strings_insert(val);
+ else if (info->vendor < 0 && strncasecmp(buf, "vendor", 6) == 0)
+ info->vendor = linux_strings_insert(val);
+ else if (info->stepping < 0 && strncasecmp(buf, "step", 4) == 0)
+ info->stepping = linux_strings_insert(val);
+ else if (info->stepping < 0 && strncasecmp(buf, "revision", 8) == 0)
+ info->stepping = linux_strings_insert(val);
+ else if (info->stepping < 0 && strncasecmp(buf, "cpu revision", 12) == 0)
+ info->stepping = linux_strings_insert(val);
+ else if (info->flags < 0 && strncasecmp(buf, "flags", 5) == 0)
+ info->flags = linux_strings_insert(val);
+ else if (info->flags < 0 && strncasecmp(buf, "features", 8) == 0)
+ info->flags = linux_strings_insert(val);
+ else if (info->cache == 0 && strncasecmp(buf, "cache size", 10) == 0)
+ info->cache = atoi(val);
+ else if (info->cache_align == 0 && strncasecmp(buf, "cache_align", 11) == 0)
+ info->cache_align = atoi(val);
+ else if (info->bogomips == 0.0 && strncasecmp(buf, "bogo", 4) == 0)
+ info->bogomips = atof(val);
+ else if (info->clock == 0.0 && strncasecmp(buf, "cpu MHz", 7) == 0)
+ info->clock = atof(val);
+ else if (info->clock == 0.0 && strncasecmp(buf, "cycle frequency", 15) == 0) {
+ if ((p = strchr(val, ' ')) != NULL)
+ *p = '\0';
+ info->clock = (atof(val))/1000000;
+ }
+ }
+ fclose(fp);
+
+#if defined(HAVE_ALPHA_LINUX)
+ /* all processors are identical, therefore duplicate it to all the instances */
+ for (cpunum = 1; cpunum < proc_cpuinfo->cpuindom->it_numinst; cpunum++)
+ memcpy(&proc_cpuinfo->cpuinfo[cpunum], info, sizeof(cpuinfo_t));
+#endif
+
+ if (started < 2) {
+ map_cpu_nodes(proc_cpuinfo);
+ started = 2;
+ }
+
+ /* success */
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_cpuinfo.h b/src/pmdas/linux/proc_cpuinfo.h
new file mode 100644
index 0000000..b0691b4
--- /dev/null
+++ b/src/pmdas/linux/proc_cpuinfo.h
@@ -0,0 +1,49 @@
+/*
+ * Linux /proc/cpuinfo metrics cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2001 Gilly Ran (gilly@exanet.com) for the
+ * portions of the code supporting the Alpha platform.
+ * All rights reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifdef __alpha__
+#define HAVE_ALPHA_LINUX
+#endif
+
+typedef struct {
+ int cpu_num;
+ int node;
+ char *name;
+ float clock;
+ float bogomips;
+ int sapic; /* strings dictionary hash key */
+ int vendor; /* strings dictionary hash key */
+ int model; /* strings dictionary hash key */
+ int model_name; /* strings dictionary hash key */
+ int stepping; /* strings dictionary hash key */
+ int flags; /* strings dictionary hash key */
+ unsigned int cache;
+ unsigned int cache_align;
+} cpuinfo_t;
+
+typedef struct {
+ char *machine;
+ cpuinfo_t *cpuinfo;
+ pmdaIndom *cpuindom;
+ pmdaIndom *node_indom;
+} proc_cpuinfo_t;
+
+extern int refresh_proc_cpuinfo(proc_cpuinfo_t *);
+extern char *cpu_name(proc_cpuinfo_t *, int);
diff --git a/src/pmdas/linux/proc_loadavg.c b/src/pmdas/linux/proc_loadavg.c
new file mode 100644
index 0000000..676cfc6
--- /dev/null
+++ b/src/pmdas/linux/proc_loadavg.c
@@ -0,0 +1,45 @@
+/*
+ * Linux /proc/loadavg metrics cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_loadavg.h"
+
+int
+refresh_proc_loadavg(proc_loadavg_t *proc_loadavg)
+{
+ char buf[1024];
+ FILE *fp;
+
+ if ((fp = linux_statsfile("/proc/loadavg", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ if (fgets(buf, sizeof(buf), fp) == NULL)
+ return -oserror();
+ fclose(fp);
+
+ /*
+ * 0.00 0.00 0.05 1/67 17563
+ * Lastpid added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ sscanf((const char *)buf, "%f %f %f %u/%u %u",
+ &proc_loadavg->loadavg[0], &proc_loadavg->loadavg[1],
+ &proc_loadavg->loadavg[2], &proc_loadavg->runnable,
+ &proc_loadavg->nprocs, &proc_loadavg->lastpid);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_loadavg.h b/src/pmdas/linux/proc_loadavg.h
new file mode 100644
index 0000000..15152c8
--- /dev/null
+++ b/src/pmdas/linux/proc_loadavg.h
@@ -0,0 +1,29 @@
+/*
+ * Linux /proc/stat metrics cluster
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ float loadavg[3]; /* 1, 5 and 15 min load average */
+ unsigned int runnable;
+ unsigned int nprocs;
+ unsigned int lastpid;
+} proc_loadavg_t;
+
+extern int refresh_proc_loadavg(proc_loadavg_t *);
+
diff --git a/src/pmdas/linux/proc_meminfo.c b/src/pmdas/linux/proc_meminfo.c
new file mode 100644
index 0000000..471e2ce
--- /dev/null
+++ b/src/pmdas/linux/proc_meminfo.c
@@ -0,0 +1,188 @@
+/*
+ * Linux /proc/meminfo metrics cluster
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include <sys/stat.h>
+#include "proc_meminfo.h"
+
+static proc_meminfo_t moff;
+extern size_t _pm_system_pagesize;
+
+static struct {
+ char *field;
+ int64_t *offset;
+} meminfo_fields[] = {
+ { "MemTotal", &moff.MemTotal },
+ { "MemFree", &moff.MemFree },
+ { "MemAvailable", &moff.MemAvailable },
+ { "MemShared", &moff.MemShared },
+ { "Buffers", &moff.Buffers },
+ { "Cached", &moff.Cached },
+ { "SwapCached", &moff.SwapCached },
+ { "Active", &moff.Active },
+ { "Inactive", &moff.Inactive },
+ { "Active(anon)", &moff.Active_anon },
+ { "Inactive(anon)", &moff.Inactive_anon },
+ { "Active(file)", &moff.Active_file },
+ { "Inactive(file)", &moff.Inactive_file },
+ { "Unevictable", &moff.Unevictable },
+ { "Mlocked", &moff.Mlocked },
+ { "HighTotal", &moff.HighTotal },
+ { "HighFree", &moff.HighFree },
+ { "LowTotal", &moff.LowTotal },
+ { "LowFree", &moff.LowFree },
+ { "MmapCopy", &moff.MmapCopy },
+ { "SwapTotal", &moff.SwapTotal },
+ { "SwapFree", &moff.SwapFree },
+ { "Dirty", &moff.Dirty },
+ { "Writeback", &moff.Writeback },
+ { "AnonPages", &moff.AnonPages },
+ { "Mapped", &moff.Mapped },
+ { "Shmem", &moff.Shmem },
+ { "Slab", &moff.Slab },
+ { "SReclaimable", &moff.SlabReclaimable },
+ { "SUnreclaim", &moff.SlabUnreclaimable },
+ { "KernelStack", &moff.KernelStack },
+ { "PageTables", &moff.PageTables },
+ { "Quicklists", &moff.Quicklists },
+ { "NFS_Unstable", &moff.NFS_Unstable },
+ { "Bounce", &moff.Bounce },
+ { "WritebackTmp", &moff.WritebackTmp },
+ { "CommitLimit", &moff.CommitLimit },
+ { "Committed_AS", &moff.Committed_AS },
+ { "VmallocTotal", &moff.VmallocTotal },
+ { "VmallocUsed", &moff.VmallocUsed },
+ { "VmallocChunk", &moff.VmallocChunk },
+ { "HardwareCorrupted", &moff.HardwareCorrupted },
+ { "AnonHugePages", &moff.AnonHugePages },
+ /* vendor kernel patches, some outdated now */
+ { "MemShared", &moff.MemShared },
+ { "ReverseMaps", &moff.ReverseMaps },
+ { "HugePages_Total", &moff.HugepagesTotal },
+ { "HugePages_Free", &moff.HugepagesFree },
+ { "HugePages_Rsvd", &moff.HugepagesRsvd },
+ { "HugePages_Surp", &moff.HugepagesSurp },
+ { "DirectMap4k", &moff.directMap4k },
+ { "DirectMap2M", &moff.directMap2M },
+ { "DirectMap1G", &moff.directMap1G },
+ { NULL, NULL }
+};
+
+#define MOFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)meminfo_fields[ii].offset - (__psint_t)&moff)
+
+int
+refresh_proc_meminfo(proc_meminfo_t *proc_meminfo)
+{
+ char buf[1024];
+ char *bufp;
+ int64_t *p;
+ int i;
+ FILE *fp;
+
+ for (i = 0; meminfo_fields[i].field != NULL; i++) {
+ p = MOFFSET(i, proc_meminfo);
+ *p = -1; /* marked as "no value available" */
+ }
+
+ if ((fp = linux_statsfile("/proc/meminfo", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((bufp = strchr(buf, ':')) == NULL)
+ continue;
+ *bufp = '\0';
+ for (i=0; meminfo_fields[i].field != NULL; i++) {
+ if (strcmp(buf, meminfo_fields[i].field) != 0)
+ continue;
+ p = MOFFSET(i, proc_meminfo);
+ for (bufp++; *bufp; bufp++) {
+ if (isdigit((int)*bufp)) {
+ sscanf(bufp, "%llu", (unsigned long long *)p);
+ *p *= 1024; /* kbytes -> bytes */
+ break;
+ }
+ }
+ }
+ }
+
+ fclose(fp);
+
+ /*
+ * MemAvailable is only in 3.x or later kernels but we can calculate it
+ * using other values, similar to upstream kernel commit 34e431b0ae.
+ * The environment variable is for QA purposes.
+ */
+ if (!MEMINFO_VALID_VALUE(proc_meminfo->MemAvailable) ||
+ getenv("PCP_QA_ESTIMATE_MEMAVAILABLE") != NULL) {
+ if (MEMINFO_VALID_VALUE(proc_meminfo->MemTotal) &&
+ MEMINFO_VALID_VALUE(proc_meminfo->MemFree) &&
+ MEMINFO_VALID_VALUE(proc_meminfo->Active_file) &&
+ MEMINFO_VALID_VALUE(proc_meminfo->Inactive_file) &&
+ MEMINFO_VALID_VALUE(proc_meminfo->SlabReclaimable)) {
+
+ int64_t pagecache;
+ int64_t wmark_low = 0;
+
+ /*
+ * sum for each zone->watermark[WMARK_LOW];
+ */
+ if ((fp = fopen("/proc/zoneinfo", "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((bufp = strstr(buf, "low ")) != NULL) {
+ int64_t low;
+ if (sscanf(bufp+4, "%lld", (long long int *)&low) == 1)
+ wmark_low += low;
+ }
+ }
+ fclose(fp);
+ wmark_low *= _pm_system_pagesize;
+ }
+
+ /*
+ * Free memory cannot be taken below the low watermark, before the
+ * system starts swapping.
+ */
+ proc_meminfo->MemAvailable = proc_meminfo->MemFree - wmark_low;
+
+ /*
+ * Not all the page cache can be freed, otherwise the system will
+ * start swapping. Assume at least half of the page cache, or the
+ * low watermark worth of cache, needs to stay.
+ */
+ pagecache = proc_meminfo->Active_file + proc_meminfo->Inactive_file;
+ pagecache -= MIN(pagecache / 2, wmark_low);
+ proc_meminfo->MemAvailable += pagecache;
+
+ /*
+ * Part of the reclaimable slab consists of items that are in use,
+ * and cannot be freed. Cap this estimate at the low watermark.
+ */
+ proc_meminfo->MemAvailable += proc_meminfo->SlabReclaimable;
+ proc_meminfo->MemAvailable -= MIN(proc_meminfo->SlabReclaimable / 2, wmark_low);
+
+ if (proc_meminfo->MemAvailable < 0)
+ proc_meminfo->MemAvailable = 0;
+ }
+ }
+
+ /* success */
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_meminfo.h b/src/pmdas/linux/proc_meminfo.h
new file mode 100644
index 0000000..3185dcc
--- /dev/null
+++ b/src/pmdas/linux/proc_meminfo.h
@@ -0,0 +1,79 @@
+/*
+ * Linux /proc/meminfo metrics cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define MEMINFO_VALID_VALUE(x) ((x) != (int64_t)-1)
+#define MEMINFO_VALUE_OR_ZERO(x) (((x) == (int64_t)-1) ? 0 : (x))
+
+/*
+ * All fields in /proc/meminfo
+ */
+typedef struct {
+ int64_t MemTotal;
+ int64_t MemFree;
+ int64_t MemAvailable;
+ int64_t MemShared;
+ int64_t Buffers;
+ int64_t Cached;
+ int64_t SwapCached;
+ int64_t Active;
+ int64_t Inactive;
+ int64_t Active_anon;
+ int64_t Inactive_anon;
+ int64_t Active_file;
+ int64_t Inactive_file;
+ int64_t Unevictable;
+ int64_t Mlocked;
+ int64_t HighTotal;
+ int64_t HighFree;
+ int64_t LowTotal;
+ int64_t LowFree;
+ int64_t MmapCopy;
+ int64_t SwapTotal;
+ int64_t SwapFree;
+ int64_t SwapUsed; /* computed */
+ int64_t Dirty;
+ int64_t Writeback;
+ int64_t Mapped;
+ int64_t Shmem;
+ int64_t Slab;
+ int64_t SlabReclaimable;
+ int64_t SlabUnreclaimable;
+ int64_t KernelStack;
+ int64_t CommitLimit;
+ int64_t Committed_AS;
+ int64_t PageTables;
+ int64_t Quicklists;
+ int64_t ReverseMaps;
+ int64_t AnonPages;
+ int64_t Bounce;
+ int64_t NFS_Unstable;
+ int64_t WritebackTmp;
+ int64_t VmallocTotal;
+ int64_t VmallocUsed;
+ int64_t VmallocChunk;
+ int64_t HardwareCorrupted;
+ int64_t AnonHugePages;
+ int64_t HugepagesTotal;
+ int64_t HugepagesFree;
+ int64_t HugepagesRsvd;
+ int64_t HugepagesSurp;
+ int64_t directMap4k;
+ int64_t directMap2M;
+ int64_t directMap1G;
+} proc_meminfo_t;
+
+extern int refresh_proc_meminfo(proc_meminfo_t *);
diff --git a/src/pmdas/linux/proc_net_dev.c b/src/pmdas/linux/proc_net_dev.c
new file mode 100644
index 0000000..93e3057
--- /dev/null
+++ b/src/pmdas/linux/proc_net_dev.c
@@ -0,0 +1,444 @@
+/*
+ * Linux /proc/net/dev metrics cluster
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <net/if.h>
+#include <ctype.h>
+#include "proc_net_dev.h"
+
+static int
+refresh_inet_socket()
+{
+ static int netfd = -1;
+ if (netfd < 0)
+ netfd = socket(AF_INET, SOCK_DGRAM, 0);
+ return netfd;
+}
+
+static int
+refresh_net_dev_ioctl(char *name, net_interface_t *netip)
+{
+ struct ethtool_cmd ecmd = { 0 };
+ /*
+ * Note:
+ * Initialization of ecmd is not really needed. If the ioctl()s
+ * work, ecmd is filled in ... but valgrind (at least up to
+ * version 3.9.0) does not know about the SIOCETHTOOL ioctl()
+ * and thinks the use of ecmd after this call propagates
+ * uninitialized data in to ioc.speed and ioc.duplex, causing
+ * failures for qa/957
+ * - Ken McDonell, 11 Apr 2014
+ */
+ struct ifreq ifr;
+ int fd;
+
+ if ((fd = refresh_inet_socket()) < 0)
+ return 0;
+
+ ecmd.cmd = ETHTOOL_GSET;
+ ifr.ifr_data = (caddr_t)&ecmd;
+ strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ ifr.ifr_name[IF_NAMESIZE-1] = '\0';
+ if (!(ioctl(fd, SIOCGIFMTU, &ifr) < 0))
+ netip->ioc.mtu = ifr.ifr_mtu;
+
+ ecmd.cmd = ETHTOOL_GSET;
+ ifr.ifr_data = (caddr_t)&ecmd;
+ strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ ifr.ifr_name[IF_NAMESIZE-1] = '\0';
+ if (!(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)) {
+ netip->ioc.linkup = !!(ifr.ifr_flags & IFF_UP);
+ netip->ioc.running = !!(ifr.ifr_flags & IFF_RUNNING);
+ }
+ /* ETHTOOL ioctl -> non-root permissions issues for old kernels */
+ ecmd.cmd = ETHTOOL_GSET;
+ ifr.ifr_data = (caddr_t)&ecmd;
+ strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ ifr.ifr_name[IF_NAMESIZE-1] = '\0';
+ if (!(ioctl(fd, SIOCETHTOOL, &ifr) < 0)) {
+ /*
+ * speed is defined in ethtool.h and returns the speed in
+ * Mbps, so 100 for 100Mbps, 1000 for 1Gbps, etc
+ */
+ netip->ioc.speed = ecmd.speed;
+ netip->ioc.duplex = ecmd.duplex + 1;
+ return 0;
+ }
+ return -ENOSYS; /* caller should try ioctl alternatives */
+}
+
+static void
+refresh_net_ipv4_addr(char *name, net_addr_t *addr)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if ((fd = refresh_inet_socket()) < 0)
+ return;
+ strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+ ifr.ifr_name[IF_NAMESIZE-1] = '\0';
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(fd, SIOCGIFADDR, &ifr) >= 0) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
+ if (inet_ntop(AF_INET, &sin->sin_addr, addr->inet, INET_ADDRSTRLEN))
+ addr->has_inet = 1;
+ }
+}
+
+/*
+ * No ioctl support or no permissions (more likely), so we
+ * fall back to grovelling about in /sys/class/net in a last
+ * ditch attempt to find the ethtool interface data (duplex
+ * and speed).
+ */
+static char *
+read_oneline(const char *path, char *buffer)
+{
+ FILE *fp = fopen(path, "r");
+
+ if (fp) {
+ int i = fscanf(fp, "%63s", buffer);
+ fclose(fp);
+ if (i == 1)
+ return buffer;
+ }
+ return "";
+}
+
+static void
+refresh_net_dev_sysfs(char *name, net_interface_t *netip)
+{
+ char path[MAXPATHLEN];
+ char line[64];
+ char *duplex;
+
+ snprintf(path, sizeof(path), "%s/sys/class/net/%s/speed", linux_statspath, name);
+ path[sizeof(path)-1] = '\0';
+ netip->ioc.speed = atoi(read_oneline(path, line));
+
+ snprintf(path, sizeof(path), "%s/sys/class/net/%s/duplex", linux_statspath, name);
+ path[sizeof(path)-1] = '\0';
+ duplex = read_oneline(path, line);
+
+ if (strcmp(duplex, "full") == 0)
+ netip->ioc.duplex = 2;
+ else if (strcmp(duplex, "half") == 0)
+ netip->ioc.duplex = 1;
+ else /* eh? */
+ netip->ioc.duplex = 0;
+}
+
+static void
+refresh_net_hw_addr(char *name, net_addr_t *netip)
+{
+ char path[MAXPATHLEN];
+ char line[64];
+ char *value;
+
+ snprintf(path, sizeof(path), "%s/sys/class/net/%s/address", linux_statspath, name);
+ path[sizeof(path)-1] = '\0';
+
+ value = read_oneline(path, line);
+
+ if (value[0] != '\0')
+ netip->has_hw = 1;
+ strncpy(netip->hw_addr, value, sizeof(netip->hw_addr));
+ netip->hw_addr[sizeof(netip->hw_addr)-1] = '\0';
+}
+
+int
+refresh_proc_net_dev(pmInDom indom)
+{
+ char buf[1024];
+ FILE *fp;
+ unsigned long long llval;
+ char *p, *v;
+ int j, sts;
+ net_interface_t *netip;
+
+ static uint64_t gen; /* refresh generation number */
+ static uint32_t cache_err;
+
+ if ((fp = linux_statsfile("/proc/net/dev", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ if (gen == 0) {
+ /*
+ * first time, reload cache from external file, and force any
+ * subsequent changes to be saved
+ */
+ pmdaCacheOp(indom, PMDA_CACHE_LOAD);
+ }
+ gen++;
+
+ /*
+Inter-| Receive | Transmit
+ face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
+ lo: 4060748 39057 0 0 0 0 0 0 4060748 39057 0 0 0 0 0 0
+ eth0: 0 337614 0 0 0 0 0 0 0 267537 0 0 0 27346 62 0
+ */
+
+ pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((p = v = strchr(buf, ':')) == NULL)
+ continue;
+ *p = '\0';
+ for (p=buf; *p && isspace((int)*p); p++) {;}
+
+ sts = pmdaCacheLookupName(indom, p, NULL, (void **)&netip);
+ if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) {
+ /* first time since re-loaded, else new one */
+ netip = (net_interface_t *)calloc(1, sizeof(net_interface_t));
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_proc_net_dev: initialize \"%s\"\n", p);
+ }
+#endif
+ }
+ else if (sts < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_proc_net_dev: pmdaCacheLookupName(%s, %s, ...) failed: %s\n",
+ pmInDomStr(indom), p, pmErrStr(sts));
+ }
+ continue;
+ }
+ if (netip->last_gen != gen-1) {
+ /*
+ * rediscovered one that went away and has returned
+ *
+ * kernel counters are reset, so clear last_counters to
+ * avoid false overflows
+ */
+ for (j=0; j < PROC_DEV_COUNTERS_PER_LINE; j++) {
+ netip->last_counters[j] = 0;
+ }
+ }
+ netip->last_gen = gen;
+ if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, p, (void *)netip)) < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_proc_net_dev: pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, " PRINTF_P_PFX "%p) failed: %s\n",
+ pmInDomStr(indom), p, netip, pmErrStr(sts));
+ }
+ continue;
+ }
+
+ /* Issue ioctls for remaining data, not exported through proc */
+ memset(&netip->ioc, 0, sizeof(netip->ioc));
+ if (refresh_net_dev_ioctl(p, netip) < 0)
+ refresh_net_dev_sysfs(p, netip);
+
+ for (p=v, j=0; j < PROC_DEV_COUNTERS_PER_LINE; j++) {
+ for (; !isdigit((int)*p); p++) {;}
+ sscanf(p, "%llu", &llval);
+ if (llval >= netip->last_counters[j]) {
+ netip->counters[j] +=
+ llval - netip->last_counters[j];
+ }
+ else {
+ /* 32bit counter has wrapped */
+ netip->counters[j] +=
+ llval + (UINT_MAX - netip->last_counters[j]);
+ }
+ netip->last_counters[j] = llval;
+ for (; !isspace((int)*p); p++) {;}
+ }
+ }
+
+ pmdaCacheOp(indom, PMDA_CACHE_SAVE);
+
+ /* success */
+ fclose(fp);
+ return 0;
+}
+
+static int
+refresh_net_dev_ipv4_addr(pmInDom indom)
+{
+ int n, fd, sts, numreqs = 30;
+ struct ifconf ifc;
+ struct ifreq *ifr;
+ net_addr_t *netip;
+ static uint32_t cache_err;
+
+ if ((fd = refresh_inet_socket()) < 0)
+ return fd;
+
+ ifc.ifc_buf = NULL;
+ for (;;) {
+ ifc.ifc_len = sizeof(struct ifreq) * numreqs;
+ ifc.ifc_buf = realloc(ifc.ifc_buf, ifc.ifc_len);
+
+ if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
+ free(ifc.ifc_buf);
+ return -oserror();
+ }
+ if (ifc.ifc_len == sizeof(struct ifreq) * numreqs) {
+ /* assume it overflowed and try again */
+ numreqs *= 2;
+ continue;
+ }
+ break;
+ }
+
+ for (n = 0, ifr = ifc.ifc_req;
+ n < ifc.ifc_len;
+ n += sizeof(struct ifreq), ifr++) {
+ sts = pmdaCacheLookupName(indom, ifr->ifr_name, NULL, (void **)&netip);
+ if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) {
+ /* first time since re-loaded, else new one */
+ netip = (net_addr_t *)calloc(1, sizeof(net_addr_t));
+ }
+ else if (sts < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_net_dev_ipv4_addr: "
+ "pmdaCacheLookupName(%s, %s, ...) failed: %s\n",
+ pmInDomStr(indom), ifr->ifr_name, pmErrStr(sts));
+ }
+ continue;
+ }
+ if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, ifr->ifr_name, (void *)netip)) < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_net_dev_ipv4_addr: "
+ "pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, "
+ PRINTF_P_PFX "%p) failed: %s\n",
+ pmInDomStr(indom), ifr->ifr_name, netip, pmErrStr(sts));
+ }
+ continue;
+ }
+
+ refresh_net_ipv4_addr(ifr->ifr_name, netip);
+ refresh_net_hw_addr(ifr->ifr_name, netip);
+ }
+ free(ifc.ifc_buf);
+ return 0;
+}
+
+static int
+refresh_net_dev_ipv6_addr(pmInDom indom)
+{
+ FILE *fp;
+ char addr6p[8][5];
+ char addr6[40], devname[20+1];
+ char addr[INET6_ADDRSTRLEN];
+ char buf[MAXPATHLEN];
+ struct sockaddr_in6 sin6;
+ int sts, plen, scope, dad_status, if_idx;
+ net_addr_t *netip;
+ static uint32_t cache_err;
+
+ if ((fp = linux_statsfile("/proc/net/if_inet6", buf, sizeof(buf))) == NULL)
+ return 0;
+
+ while (fscanf(fp, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n",
+ addr6p[0], addr6p[1], addr6p[2], addr6p[3],
+ addr6p[4], addr6p[5], addr6p[6], addr6p[7],
+ &if_idx, &plen, &scope, &dad_status, devname) != EOF) {
+ sts = pmdaCacheLookupName(indom, devname, NULL, (void **)&netip);
+ if (sts == PM_ERR_INST || (sts >= 0 && netip == NULL)) {
+ /* first time since re-loaded, else new one */
+ netip = (net_addr_t *)calloc(1, sizeof(net_addr_t));
+ }
+ else if (sts < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_net_dev_ipv6_addr: "
+ "pmdaCacheLookupName(%s, %s, ...) failed: %s\n",
+ pmInDomStr(indom), devname, pmErrStr(sts));
+ }
+ continue;
+ }
+ if ((sts = pmdaCacheStore(indom, PMDA_CACHE_ADD, devname, (void *)netip)) < 0) {
+ if (cache_err++ < 10) {
+ fprintf(stderr, "refresh_net_dev_ipv6_addr: "
+ "pmdaCacheStore(%s, PMDA_CACHE_ADD, %s, "
+ PRINTF_P_PFX "%p) failed: %s\n",
+ pmInDomStr(indom), devname, netip, pmErrStr(sts));
+ }
+ continue;
+ }
+
+ sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
+ addr6p[0], addr6p[1], addr6p[2], addr6p[3],
+ addr6p[4], addr6p[5], addr6p[6], addr6p[7]);
+ if (inet_pton(AF_INET6, addr6, sin6.sin6_addr.s6_addr) != 1)
+ continue;
+
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = 0;
+ if (!inet_ntop(AF_INET6, &sin6.sin6_addr, addr, INET6_ADDRSTRLEN))
+ continue;
+ snprintf(netip->ipv6, sizeof(netip->ipv6), "%s/%d", addr, plen);
+ netip->ipv6scope = (uint16_t)scope;
+ netip->has_ipv6 = 1;
+
+ refresh_net_hw_addr(devname, netip);
+ }
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * This separate indom provides the addresses for all interfaces including
+ * aliases (e.g. eth0, eth0:0, eth0:1, etc) - this is what ifconfig does.
+ */
+int
+refresh_net_dev_addr(pmInDom indom)
+{
+ int sts = 0;
+ net_addr_t*p;
+
+ for (pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND);;) {
+ if ((sts = pmdaCacheOp(indom, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ if (!pmdaCacheLookup(indom, sts, NULL, (void **)&p) || !p)
+ continue;
+ p->has_inet = 0;
+ p->has_ipv6 = 0;
+ p->has_hw = 0;
+ }
+
+ pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+
+ sts |= refresh_net_dev_ipv4_addr(indom);
+ sts |= refresh_net_dev_ipv6_addr(indom);
+
+ pmdaCacheOp(indom, PMDA_CACHE_SAVE);
+ return sts;
+}
+
+char *
+lookup_ipv6_scope(int scope)
+{
+ switch (scope) {
+ case IPV6_ADDR_ANY:
+ return "Global";
+ case IPV6_ADDR_LINKLOCAL:
+ return "Link";
+ case IPV6_ADDR_SITELOCAL:
+ return "Site";
+ case IPV6_ADDR_COMPATv4:
+ return "Compat";
+ case IPV6_ADDR_LOOPBACK:
+ return "Host";
+ }
+ return "Unknown";
+}
diff --git a/src/pmdas/linux/proc_net_dev.h b/src/pmdas/linux/proc_net_dev.h
new file mode 100644
index 0000000..9bb09f3
--- /dev/null
+++ b/src/pmdas/linux/proc_net_dev.h
@@ -0,0 +1,100 @@
+/*
+ * Linux /proc/net/dev metrics cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+typedef struct {
+ uint32_t mtu;
+ uint32_t speed;
+ uint8_t duplex;
+ uint8_t linkup;
+ uint8_t running;
+ uint8_t pad;
+} net_dev_t;
+
+#define HWADDRSTRLEN 64
+
+typedef struct {
+ int has_inet : 1;
+ int has_ipv6 : 1;
+ int has_hw : 1;
+ int padding : 13;
+ uint16_t ipv6scope;
+ char inet[INET_ADDRSTRLEN];
+ char ipv6[INET6_ADDRSTRLEN+16]; /* extra for /plen */
+ char hw_addr[HWADDRSTRLEN];
+} net_addr_t;
+
+#define PROC_DEV_COUNTERS_PER_LINE 16
+
+typedef struct {
+ uint64_t last_gen;
+ uint64_t last_counters[PROC_DEV_COUNTERS_PER_LINE];
+ uint64_t counters[PROC_DEV_COUNTERS_PER_LINE];
+ net_dev_t ioc;
+} net_interface_t;
+
+#ifndef ETHTOOL_GSET
+#define ETHTOOL_GSET 0x1
+#endif
+
+#ifndef SIOCGIFCONF
+#define SIOCGIFCONF 0x8912
+#endif
+
+#ifndef SIOCGIFFLAGS
+#define SIOCGIFFLAGS 0x8913
+#endif
+
+#ifndef SIOCGIFADDR
+#define SIOCGIFADDR 0x8915
+#endif
+
+#ifndef SIOCGIFMTU
+#define SIOCGIFMTU 0x8921
+#endif
+
+#ifndef SIOCETHTOOL
+#define SIOCETHTOOL 0x8946
+#endif
+
+/* ioctl(SIOCIFETHTOOL) GSET ("get settings") structure */
+struct ethtool_cmd {
+ uint32_t cmd;
+ uint32_t supported; /* Features this interface supports */
+ uint32_t advertising; /* Features this interface advertises */
+ uint16_t speed; /* The forced speed, 10Mb, 100Mb, gigabit */
+ uint8_t duplex; /* Duplex, half or full */
+ uint8_t port; /* Which connector port */
+ uint8_t phy_address;
+ uint8_t transceiver; /* Which tranceiver to use */
+ uint8_t autoneg; /* Enable or disable autonegotiation */
+ uint32_t maxtxpkt; /* Tx pkts before generating tx int */
+ uint32_t maxrxpkt; /* Rx pkts before generating rx int */
+ uint32_t reserved[4];
+};
+
+#define IPV6_ADDR_ANY 0x0000U
+#define IPV6_ADDR_UNICAST 0x0001U
+#define IPV6_ADDR_MULTICAST 0x0002U
+#define IPV6_ADDR_ANYCAST 0x0004U
+#define IPV6_ADDR_LOOPBACK 0x0010U
+#define IPV6_ADDR_LINKLOCAL 0x0020U
+#define IPV6_ADDR_SITELOCAL 0x0040U
+#define IPV6_ADDR_COMPATv4 0x0080U
+
+extern int refresh_proc_net_dev(pmInDom);
+extern int refresh_net_dev_addr(pmInDom);
+extern char *lookup_ipv6_scope(int);
diff --git a/src/pmdas/linux/proc_net_netstat.c b/src/pmdas/linux/proc_net_netstat.c
new file mode 100644
index 0000000..a7bd34a
--- /dev/null
+++ b/src/pmdas/linux/proc_net_netstat.c
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_net_netstat.h"
+
+extern proc_net_netstat_t _pm_proc_net_netstat;
+
+typedef struct {
+ const char *field;
+ __uint64_t *offset;
+} netstat_fields_t;
+
+netstat_fields_t netstat_ip_fields[] = {
+ { .field = "InNoRoutes",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INNOROUTES] },
+ { .field = "InTruncatedPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INTRUNCATEDPKTS] },
+ { .field = "InMcastPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INMCASTPKTS] },
+ { .field = "OutMcastPkts ",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTMCASTPKTS] },
+ { .field = "InBcastPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INBCASTPKTS] },
+ { .field = "OutBcastPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTBCASTPKTS] },
+ { .field = "InOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INOCTETS] },
+ { .field = "OutOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTOCTETS] },
+ { .field = "InMcastOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INMCASTOCTETS] },
+ { .field = "OutMcastOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTMCASTOCTETS] },
+ { .field = "InBcastOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_INBCASTOCTETS] },
+ { .field = "OutBcastOctets",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_OUTBCASTOCTETS] },
+ { .field = "InCsumErrors",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_CSUMERRORS] },
+ { .field = "InNoECTPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_NOECTPKTS] },
+ { .field = "InECT1Pkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_ECT1PKTS] },
+ { .field = "InECT0Pkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_ECT0PKTS] },
+ { .field = "InCEPkts",
+ .offset = &_pm_proc_net_netstat.ip[_PM_NETSTAT_IPEXT_CEPKTS] }
+};
+
+
+netstat_fields_t netstat_tcp_fields[] = {
+ { .field = "SyncookiesSent",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESSENT] },
+ { .field = "SyncookiesRecv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESRECV] },
+ { .field = "SyncookiesFailed",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SYNCOOKIESFAILED] },
+ { .field = "EmbryonicRsts",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_EMBRYONICRSTS] },
+ { .field = "PruneCalled",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PRUNECALLED] },
+ { .field = "RcvPruned",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_RCVPRUNED] },
+ { .field = "OfoPruned",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_OFOPRUNED] },
+ { .field = "OutOfWindowIcmps",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_OUTOFWINDOWICMPS] },
+ { .field = "LockDroppedIcmps",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LOCKDROPPEDICMPS] },
+ { .field = "ArpFilter",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_ARPFILTER] },
+ { .field = "TW",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITED] },
+ { .field = "TWRecycled",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITRECYCLED] },
+ { .field = "TWKilled",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TIMEWAITKILLED] },
+ { .field = "PAWSPassive",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSPASSIVEREJECTED] },
+ { .field = "PAWSActive",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSACTIVEREJECTED] },
+ { .field = "PAWSEstab",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_PAWSESTABREJECTED] },
+ { .field = "DelayedACKs",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKS] },
+ { .field = "DelayedACKLocked",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKLOCKED] },
+ { .field = "DelayedACKLost",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_DELAYEDACKLOST] },
+ { .field = "ListenOverflows",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LISTENOVERFLOWS] },
+ { .field = "ListenDrops",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_LISTENDROPS] },
+ { .field = "TCPPrequeued",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPREQUEUED] },
+ { .field = "TCPDirectCopyFromBacklog",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMBACKLOG] },
+ { .field = "TCPDirectCopyFromPrequeue",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMPREQUEUE] },
+ { .field = "TCPPrequeueDropped",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPREQUEUEDROPPED] },
+ { .field = "TCPHPHits",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPHITS] },
+ { .field = "TCPHPHitsToUser",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPHITSTOUSER] },
+ { .field = "TCPPureAcks",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPUREACKS] },
+ { .field = "TCPHPAcks",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPHPACKS] },
+ { .field = "TCPRenoRecovery",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENORECOVERY] },
+ { .field = "TCPSackRecovery",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRECOVERY] },
+ { .field = "TCPSACKReneging",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRENEGING] },
+ { .field = "TCPFACKReorder",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFACKREORDER] },
+ { .field = "TCPSACKReorder",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKREORDER] },
+ { .field = "TCPRenoReorder",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENOREORDER] },
+ { .field = "TCPTSReorder",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTSREORDER] },
+ { .field = "TCPFullUndo",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFULLUNDO] },
+ { .field = "TCPPartialUndo",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPPARTIALUNDO] },
+ { .field = "TCPDSACKUndo",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKUNDO] },
+ { .field = "TCPLossUndo",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSUNDO] },
+ { .field = "TCPLostRetransmit",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSTRETRANSMIT] },
+ { .field = "TCPRenoFailures",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENOFAILURES] },
+ { .field = "TCPSackFailures",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKFAILURES] },
+ { .field = "TCPLossFailures",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSFAILURES] },
+ { .field = "TCPFastRetrans",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTRETRANS] },
+ { .field = "TCPForwardRetrans",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFORWARDRETRANS] },
+ { .field = "TCPSlowStartRetrans",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSLOWSTARTRETRANS] },
+ { .field = "TCPTimeouts",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTIMEOUTS] },
+ { .field = "TCPLossProbes",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSPROBES] },
+ { .field = "TCPLossProbeRecovery",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPLOSSPROBERECOVERY] },
+ { .field = "TCPRenoRecoveryFail",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRENORECOVERYFAIL] },
+ { .field = "TCPSackRecoveryFail",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKRECOVERYFAIL] },
+ { .field = "TCPSchedulerFailed",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSCHEDULERFAILED] },
+ { .field = "TCPRcvCollapsed",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRCVCOLLAPSED] },
+ { .field = "TCPDSACKOldSent",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOLDSENT] },
+ { .field = "TCPDSACKOfoSent",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOFOSENT] },
+ { .field = "TCPDSACKRecv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKRECV] },
+ { .field = "TCPDSACKOfoRecv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKOFORECV] },
+ { .field = "TCPAbortOnData",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONDATA] },
+ { .field = "TCPAbortOnClose",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONCLOSE] },
+ { .field = "TCPAbortOnMemory",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONMEMORY] },
+ { .field = "TCPAbortOnTimeout",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONTIMEOUT] },
+ { .field = "TCPAbortOnLinger",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTONLINGER] },
+ { .field = "TCPAbortFailed",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPABORTFAILED] },
+ { .field = "TCPMemoryPressures",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMEMORYPRESSURES] },
+ { .field = "TCPSACKDiscard",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSACKDISCARD] },
+ { .field = "TCPDSACKIgnoredOld",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDOLD] },
+ { .field = "TCPDSACKIgnoredNoUndo",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDNOUNDO] },
+ { .field = "TCPSpuriousRTOs",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSPURIOUSRTOS] },
+ { .field = "TCPMD5NotFound",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMD5NOTFOUND] },
+ { .field = "TCPMD5Unexpected",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMD5UNEXPECTED] },
+ { .field = "TCPSackShifted",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKSHIFTED] },
+ { .field = "TCPSackMerged",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKMERGED] },
+ { .field = "TCPSackShiftFallback",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_SACKSHIFTFALLBACK] },
+ { .field = "TCPBacklogDrop",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPBACKLOGDROP] },
+ { .field = "TCPMinTTLDrop",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPMINTTLDROP] },
+ { .field = "TCPDeferAcceptDrop",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPDEFERACCEPTDROP] },
+ { .field = "IPReversePathFilter",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_IPRPFILTER] },
+ { .field = "TCPTimeWaitOverflow",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTIMEWAITOVERFLOW] },
+ { .field = "TCPReqQFullDoCookies",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPREQQFULLDOCOOKIES] },
+ { .field = "TCPReqQFullDrop",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPREQQFULLDROP] },
+ { .field = "TCPRetransFail",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRETRANSFAIL] },
+ { .field = "TCPRcvCoalesce",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPRCVCOALESCE] },
+ { .field = "TCPOFOQueue",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFOQUEUE] },
+ { .field = "TCPOFODrop",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFODROP] },
+ { .field = "TCPOFOMerge",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPOFOMERGE] },
+ { .field = "TCPChallengeACK",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPCHALLENGEACK] },
+ { .field = "TCPSYNChallenge",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSYNCHALLENGE] },
+ { .field = "TCPFastOpenActive",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVE] },
+ { .field = "TCPFastOpenPassive",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVEFAIL] },
+ { .field = "TCPFastOpenPassiveFail",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENPASSIVEFAIL] },
+ { .field = "TCPFastOpenListenOverflow",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENLISTENOVERFLOW] },
+ { .field = "TCPFastOpenCookieReqd",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFASTOPENCOOKIEREQD] },
+ { .field = "TCPSpuriousRtxHostQueues",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSPURIOUS_RTX_HOSTQUEUES] },
+ { .field = "BusyPollRxPackets",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_BUSYPOLLRXPACKETS] },
+ { .field = "TCPAutoCorking",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPAUTOCORKING] },
+ { .field = "TCPFromZeroWindowAdv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPFROMZEROWINDOWADV] },
+ { .field = "TCPToZeroWindowAdv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPTOZEROWINDOWADV] },
+ { .field = "TCPWantZeroWindowAdv",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPWANTZEROWINDOWADV] },
+ { .field = "TCPSynRetrans",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPSYNRETRANS] },
+ { .field = "TCPOrigDataSent",
+ .offset = &_pm_proc_net_netstat.tcp[_PM_NETSTAT_TCPEXT_TCPORIGDATASENT] }
+};
+
+static void
+get_fields(netstat_fields_t *fields, char *header, char *buffer)
+{
+ int i, j, count;
+ char *p, *indices[NETSTAT_MAX_COLUMNS];
+
+ /* first get pointers to each of the column headings */
+ strtok(header, " ");
+ for (i = 0; i < NETSTAT_MAX_COLUMNS; i++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ indices[i] = p;
+ }
+ count = i;
+
+ /*
+ * Extract values via back-referencing column headings.
+ * "i" is the last found index, which we use for a bit
+ * of optimisation for the (common) in-order maps case
+ * (where "in order" means in the order defined by the
+ * passed in "fields" table which typically matches the
+ * kernel - but may be out-of-order for older kernels).
+ */
+ strtok(buffer, " ");
+ for (i = j = 0; j < count && fields[i].field; j++, i++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ if (strcmp(fields[i].field, indices[j]) == 0)
+ *fields[i].offset = strtoull(p, NULL, 10);
+ else {
+ for (i = 0; fields[i].field; i++) {
+ if (strcmp(fields[i].field, indices[j]) != 0)
+ continue;
+ *fields[i].offset = strtoull(p, NULL, 10);
+ break;
+ }
+ if (fields[i].field == NULL) /* not found, ignore */
+ i = 0;
+ }
+ }
+}
+
+
+#define NETSTAT_IP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)netstat_ip_fields[ii].offset - (__psint_t)&_pm_proc_net_netstat.ip)
+#define NETSTAT_TCP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)netstat_tcp_fields[ii].offset - (__psint_t)&_pm_proc_net_netstat.tcp)
+
+static void
+init_refresh_proc_net_netstat(proc_net_netstat_t *netstat)
+{
+ int i;
+
+ /* initially, all marked as "no value available" */
+ for (i = 0; netstat_ip_fields[i].field != NULL; i++)
+ *(NETSTAT_IP_OFFSET(i, netstat->ip)) = -1;
+ for (i = 0; netstat_tcp_fields[i].field != NULL; i++)
+ *(NETSTAT_TCP_OFFSET(i, netstat->tcp)) = -1;
+}
+
+int
+refresh_proc_net_netstat(proc_net_netstat_t *netstat)
+{
+ /* Need a sufficiently large value to hold a full line */
+ char buf[MAXPATHLEN];
+ char header[2048];
+ FILE *fp;
+
+ init_refresh_proc_net_netstat(netstat);
+ if ((fp = linux_statsfile("/proc/net/netstat", buf, sizeof(buf))) == NULL)
+ return -oserror();
+ while (fgets(header, sizeof(header), fp) != NULL) {
+ if (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "IpExt:", 6) == 0)
+ get_fields(netstat_ip_fields, header, buf);
+ else if (strncmp(buf, "TcpExt:", 7) == 0)
+ get_fields(netstat_tcp_fields, header, buf);
+ else
+ __pmNotifyErr(LOG_ERR, "Unrecognised netstat row: %s\n", buf);
+ }
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_net_netstat.h b/src/pmdas/linux/proc_net_netstat.h
new file mode 100644
index 0000000..4a5a9c8
--- /dev/null
+++ b/src/pmdas/linux/proc_net_netstat.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define NETSTAT_MAX_COLUMNS 256 /* arbitrary upper bound (228 observed as of 22/04/2014)*/
+
+enum {
+ _PM_NETSTAT_IPEXT_INNOROUTES = 0,
+ _PM_NETSTAT_IPEXT_INTRUNCATEDPKTS,
+ _PM_NETSTAT_IPEXT_INMCASTPKTS,
+ _PM_NETSTAT_IPEXT_OUTMCASTPKTS,
+ _PM_NETSTAT_IPEXT_INBCASTPKTS,
+ _PM_NETSTAT_IPEXT_OUTBCASTPKTS,
+ _PM_NETSTAT_IPEXT_INOCTETS,
+ _PM_NETSTAT_IPEXT_OUTOCTETS,
+ _PM_NETSTAT_IPEXT_INMCASTOCTETS,
+ _PM_NETSTAT_IPEXT_OUTMCASTOCTETS,
+ _PM_NETSTAT_IPEXT_INBCASTOCTETS,
+ _PM_NETSTAT_IPEXT_OUTBCASTOCTETS,
+ _PM_NETSTAT_IPEXT_CSUMERRORS,
+ _PM_NETSTAT_IPEXT_NOECTPKTS,
+ _PM_NETSTAT_IPEXT_ECT1PKTS,
+ _PM_NETSTAT_IPEXT_ECT0PKTS,
+ _PM_NETSTAT_IPEXT_CEPKTS,
+ _PM_NETSTAT_IPEXT_NFIELDS /* must be last */
+};
+
+enum {
+ _PM_NETSTAT_TCPEXT_SYNCOOKIESSENT = 0,
+ _PM_NETSTAT_TCPEXT_SYNCOOKIESRECV,
+ _PM_NETSTAT_TCPEXT_SYNCOOKIESFAILED,
+ _PM_NETSTAT_TCPEXT_EMBRYONICRSTS,
+ _PM_NETSTAT_TCPEXT_PRUNECALLED,
+ _PM_NETSTAT_TCPEXT_RCVPRUNED,
+ _PM_NETSTAT_TCPEXT_OFOPRUNED,
+ _PM_NETSTAT_TCPEXT_OUTOFWINDOWICMPS,
+ _PM_NETSTAT_TCPEXT_LOCKDROPPEDICMPS,
+ _PM_NETSTAT_TCPEXT_ARPFILTER,
+ _PM_NETSTAT_TCPEXT_TIMEWAITED,
+ _PM_NETSTAT_TCPEXT_TIMEWAITRECYCLED,
+ _PM_NETSTAT_TCPEXT_TIMEWAITKILLED,
+ _PM_NETSTAT_TCPEXT_PAWSPASSIVEREJECTED,
+ _PM_NETSTAT_TCPEXT_PAWSACTIVEREJECTED,
+ _PM_NETSTAT_TCPEXT_PAWSESTABREJECTED,
+ _PM_NETSTAT_TCPEXT_DELAYEDACKS,
+ _PM_NETSTAT_TCPEXT_DELAYEDACKLOCKED,
+ _PM_NETSTAT_TCPEXT_DELAYEDACKLOST,
+ _PM_NETSTAT_TCPEXT_LISTENOVERFLOWS,
+ _PM_NETSTAT_TCPEXT_LISTENDROPS,
+ _PM_NETSTAT_TCPEXT_TCPPREQUEUED,
+ _PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMBACKLOG,
+ _PM_NETSTAT_TCPEXT_TCPDIRECTCOPYFROMPREQUEUE,
+ _PM_NETSTAT_TCPEXT_TCPPREQUEUEDROPPED,
+ _PM_NETSTAT_TCPEXT_TCPHPHITS,
+ _PM_NETSTAT_TCPEXT_TCPHPHITSTOUSER,
+ _PM_NETSTAT_TCPEXT_TCPPUREACKS,
+ _PM_NETSTAT_TCPEXT_TCPHPACKS,
+ _PM_NETSTAT_TCPEXT_TCPRENORECOVERY,
+ _PM_NETSTAT_TCPEXT_TCPSACKRECOVERY,
+ _PM_NETSTAT_TCPEXT_TCPSACKRENEGING,
+ _PM_NETSTAT_TCPEXT_TCPFACKREORDER,
+ _PM_NETSTAT_TCPEXT_TCPSACKREORDER,
+ _PM_NETSTAT_TCPEXT_TCPRENOREORDER,
+ _PM_NETSTAT_TCPEXT_TCPTSREORDER,
+ _PM_NETSTAT_TCPEXT_TCPFULLUNDO,
+ _PM_NETSTAT_TCPEXT_TCPPARTIALUNDO,
+ _PM_NETSTAT_TCPEXT_TCPDSACKUNDO,
+ _PM_NETSTAT_TCPEXT_TCPLOSSUNDO,
+ _PM_NETSTAT_TCPEXT_TCPLOSTRETRANSMIT,
+ _PM_NETSTAT_TCPEXT_TCPRENOFAILURES,
+ _PM_NETSTAT_TCPEXT_TCPSACKFAILURES,
+ _PM_NETSTAT_TCPEXT_TCPLOSSFAILURES,
+ _PM_NETSTAT_TCPEXT_TCPFASTRETRANS,
+ _PM_NETSTAT_TCPEXT_TCPFORWARDRETRANS,
+ _PM_NETSTAT_TCPEXT_TCPSLOWSTARTRETRANS,
+ _PM_NETSTAT_TCPEXT_TCPTIMEOUTS,
+ _PM_NETSTAT_TCPEXT_TCPLOSSPROBES,
+ _PM_NETSTAT_TCPEXT_TCPLOSSPROBERECOVERY,
+ _PM_NETSTAT_TCPEXT_TCPRENORECOVERYFAIL,
+ _PM_NETSTAT_TCPEXT_TCPSACKRECOVERYFAIL,
+ _PM_NETSTAT_TCPEXT_TCPSCHEDULERFAILED,
+ _PM_NETSTAT_TCPEXT_TCPRCVCOLLAPSED,
+ _PM_NETSTAT_TCPEXT_TCPDSACKOLDSENT,
+ _PM_NETSTAT_TCPEXT_TCPDSACKOFOSENT,
+ _PM_NETSTAT_TCPEXT_TCPDSACKRECV,
+ _PM_NETSTAT_TCPEXT_TCPDSACKOFORECV,
+ _PM_NETSTAT_TCPEXT_TCPABORTONDATA,
+ _PM_NETSTAT_TCPEXT_TCPABORTONCLOSE,
+ _PM_NETSTAT_TCPEXT_TCPABORTONMEMORY,
+ _PM_NETSTAT_TCPEXT_TCPABORTONTIMEOUT,
+ _PM_NETSTAT_TCPEXT_TCPABORTONLINGER,
+ _PM_NETSTAT_TCPEXT_TCPABORTFAILED,
+ _PM_NETSTAT_TCPEXT_TCPMEMORYPRESSURES,
+ _PM_NETSTAT_TCPEXT_TCPSACKDISCARD,
+ _PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDOLD,
+ _PM_NETSTAT_TCPEXT_TCPDSACKIGNOREDNOUNDO,
+ _PM_NETSTAT_TCPEXT_TCPSPURIOUSRTOS,
+ _PM_NETSTAT_TCPEXT_TCPMD5NOTFOUND,
+ _PM_NETSTAT_TCPEXT_TCPMD5UNEXPECTED,
+ _PM_NETSTAT_TCPEXT_SACKSHIFTED,
+ _PM_NETSTAT_TCPEXT_SACKMERGED,
+ _PM_NETSTAT_TCPEXT_SACKSHIFTFALLBACK,
+ _PM_NETSTAT_TCPEXT_TCPBACKLOGDROP,
+ _PM_NETSTAT_TCPEXT_TCPMINTTLDROP,
+ _PM_NETSTAT_TCPEXT_TCPDEFERACCEPTDROP,
+ _PM_NETSTAT_TCPEXT_IPRPFILTER,
+ _PM_NETSTAT_TCPEXT_TCPTIMEWAITOVERFLOW,
+ _PM_NETSTAT_TCPEXT_TCPREQQFULLDOCOOKIES,
+ _PM_NETSTAT_TCPEXT_TCPREQQFULLDROP,
+ _PM_NETSTAT_TCPEXT_TCPRETRANSFAIL,
+ _PM_NETSTAT_TCPEXT_TCPRCVCOALESCE,
+ _PM_NETSTAT_TCPEXT_TCPOFOQUEUE,
+ _PM_NETSTAT_TCPEXT_TCPOFODROP,
+ _PM_NETSTAT_TCPEXT_TCPOFOMERGE,
+ _PM_NETSTAT_TCPEXT_TCPCHALLENGEACK,
+ _PM_NETSTAT_TCPEXT_TCPSYNCHALLENGE,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVE,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENACTIVEFAIL,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENPASSIVE,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENPASSIVEFAIL,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENLISTENOVERFLOW,
+ _PM_NETSTAT_TCPEXT_TCPFASTOPENCOOKIEREQD,
+ _PM_NETSTAT_TCPEXT_TCPSPURIOUS_RTX_HOSTQUEUES,
+ _PM_NETSTAT_TCPEXT_BUSYPOLLRXPACKETS,
+ _PM_NETSTAT_TCPEXT_TCPAUTOCORKING,
+ _PM_NETSTAT_TCPEXT_TCPFROMZEROWINDOWADV,
+ _PM_NETSTAT_TCPEXT_TCPTOZEROWINDOWADV,
+ _PM_NETSTAT_TCPEXT_TCPWANTZEROWINDOWADV,
+ _PM_NETSTAT_TCPEXT_TCPSYNRETRANS,
+ _PM_NETSTAT_TCPEXT_TCPORIGDATASENT,
+ _PM_NETSTAT_TCPEXT_NFIELDS /* must be last */
+};
+
+
+typedef struct {
+ __uint64_t ip[_PM_NETSTAT_IPEXT_NFIELDS];
+ __uint64_t tcp[_PM_NETSTAT_TCPEXT_NFIELDS];
+} proc_net_netstat_t;
+
+extern int refresh_proc_net_netstat(proc_net_netstat_t *);
diff --git a/src/pmdas/linux/proc_net_rpc.c b/src/pmdas/linux/proc_net_rpc.c
new file mode 100644
index 0000000..1b1a940
--- /dev/null
+++ b/src/pmdas/linux/proc_net_rpc.c
@@ -0,0 +1,188 @@
+/*
+ * Linux /proc/net/rpc metrics cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_net_rpc.h"
+
+int
+refresh_proc_net_rpc(proc_net_rpc_t *proc_net_rpc)
+{
+ char buf[4096];
+ FILE *fp;
+ char *p;
+ int i;
+
+ memset(proc_net_rpc, 0, sizeof(proc_net_rpc_t));
+
+ /*
+ * client stats
+ */
+ if ((fp = linux_statsfile("/proc/net/rpc/nfs", buf, sizeof(buf))) == NULL) {
+ proc_net_rpc->client.errcode = -oserror();
+ }
+ else {
+ proc_net_rpc->client.errcode = 0;
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "net", 3) == 0)
+ sscanf(buf, "net %u %u %u %u",
+ &proc_net_rpc->client.netcnt,
+ &proc_net_rpc->client.netudpcnt,
+ &proc_net_rpc->client.nettcpcnt,
+ &proc_net_rpc->client.nettcpconn);
+ else
+ if (strncmp(buf, "rpc", 3) == 0)
+ sscanf(buf, "rpc %u %u %u",
+ &proc_net_rpc->client.rpccnt,
+ &proc_net_rpc->client.rpcretrans,
+ &proc_net_rpc->client.rpcauthrefresh);
+ else
+ if (strncmp(buf, "proc2", 5) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+ for (i=0; p && i < NR_RPC_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->client.reqcounts[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ else
+ if (strncmp(buf, "proc3", 5) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+ for (i=0; p && i < NR_RPC3_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->client.reqcounts3[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ else
+ if (strncmp(buf, "proc4", 5) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+ for (i=0; p && i < NR_RPC4_CLI_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->client.reqcounts4[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ }
+
+ fclose(fp);
+ }
+
+ /*
+ * server stats
+ */
+ if ((fp = linux_statsfile("/proc/net/rpc/nfsd", buf, sizeof(buf))) == NULL) {
+ proc_net_rpc->server.errcode = -oserror();
+ }
+ else {
+ proc_net_rpc->server.errcode = 0;
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "rc", 2) == 0)
+ sscanf(buf, "rc %u %u %u %u %u %u %u %u %u",
+ &proc_net_rpc->server.rchits,
+ &proc_net_rpc->server.rcmisses,
+ &proc_net_rpc->server.rcnocache,
+ &proc_net_rpc->server.fh_cached,
+ &proc_net_rpc->server.fh_valid,
+ &proc_net_rpc->server.fh_fixup,
+ &proc_net_rpc->server.fh_lookup,
+ &proc_net_rpc->server.fh_stale,
+ &proc_net_rpc->server.fh_concurrent);
+ else
+ if (strncmp(buf, "fh", 2) == 0)
+ sscanf(buf, "fh %u %u %u %u %u",
+ &proc_net_rpc->server.fh_stale,
+ &proc_net_rpc->server.fh_lookup,
+ &proc_net_rpc->server.fh_anon,
+ &proc_net_rpc->server.fh_nocache_dir,
+ &proc_net_rpc->server.fh_nocache_nondir);
+ else
+ if (strncmp(buf, "io", 2) == 0)
+ sscanf(buf, "io %u %u",
+ &proc_net_rpc->server.io_read,
+ &proc_net_rpc->server.io_write);
+ else
+ if (strncmp(buf, "th", 2) == 0)
+ sscanf(buf, "th %u %u",
+ &proc_net_rpc->server.th_cnt,
+ &proc_net_rpc->server.th_fullcnt);
+ else
+ if (strncmp(buf, "net", 3) == 0)
+ sscanf(buf, "net %u %u %u %u",
+ &proc_net_rpc->server.netcnt,
+ &proc_net_rpc->server.netudpcnt,
+ &proc_net_rpc->server.nettcpcnt,
+ &proc_net_rpc->server.nettcpconn);
+ else
+ if (strncmp(buf, "rpc", 3) == 0)
+ sscanf(buf, "rpc %u %u %u",
+ &proc_net_rpc->server.rpccnt,
+ &proc_net_rpc->server.rpcerr, /* always the sum of the following three fields */
+ &proc_net_rpc->server.rpcbadfmt);
+ else
+ if (strncmp(buf, "proc2", 5) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+ for (i=0; p && i < NR_RPC_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->server.reqcounts[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ else
+ if (strncmp(buf, "proc3", 5) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+ for (i=0; p && i < NR_RPC3_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->server.reqcounts3[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ else
+ if (strncmp(buf, "proc4ops", 8) == 0) {
+ if ((p = strtok(buf, " ")) != NULL)
+ p = strtok(NULL, " ");
+
+ /* Inst 0 is NULL count (below) */
+ for (i=1; p && i < NR_RPC4_SVR_COUNTERS; i++) {
+ if ((p = strtok(NULL, " ")) == NULL)
+ break;
+ proc_net_rpc->server.reqcounts4[i] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ else
+ if (strncmp(buf, "proc4", 5) == 0) {
+ if ((strtok(buf, " ")) != NULL &&
+ (strtok(NULL, " ")) != NULL &&
+ (p = strtok(NULL, " ")) != NULL) { /* 3rd token is NULL count */
+ proc_net_rpc->server.reqcounts4[0] = strtoul(p, (char **)NULL, 10);
+ }
+ }
+ }
+
+ fclose(fp);
+ }
+
+ if (proc_net_rpc->client.errcode == 0 && proc_net_rpc->server.errcode == 0)
+ return 0;
+ return -1;
+}
diff --git a/src/pmdas/linux/proc_net_rpc.h b/src/pmdas/linux/proc_net_rpc.h
new file mode 100644
index 0000000..880dbed
--- /dev/null
+++ b/src/pmdas/linux/proc_net_rpc.h
@@ -0,0 +1,99 @@
+/*
+ * Linux /proc/net/rpc metrics cluster
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define NR_RPC_COUNTERS 18
+#define NR_RPC3_COUNTERS 22
+#define NR_RPC4_CLI_COUNTERS 35
+#define NR_RPC4_SVR_COUNTERS 41
+
+typedef struct {
+ struct {
+ int errcode; /* error from last refresh */
+ /* /proc/net/rpc/nfs "net" */
+ unsigned int netcnt;
+ unsigned int netudpcnt;
+ unsigned int nettcpcnt;
+ unsigned int nettcpconn;
+
+ /* /proc/net/rpc/nfs "rpc" */
+ unsigned int rpccnt;
+ unsigned int rpcretrans;
+ unsigned int rpcauthrefresh;
+
+ /* /proc/net/rpc/nfs "proc2" */
+ unsigned int reqcounts[NR_RPC_COUNTERS];
+
+ /* /proc/net/rpc/nfs "proc3" */
+ unsigned int reqcounts3[NR_RPC3_COUNTERS];
+
+ /* /proc/net/rpc/nfs "proc4" */
+ unsigned int reqcounts4[NR_RPC4_CLI_COUNTERS];
+ } client;
+
+ struct {
+ int errcode; /* error from last refresh */
+ /* /proc/net/rpc/nfsd "rc" and "fh" */
+ unsigned int rchits; /* repcache hits */
+ unsigned int rcmisses; /* repcache hits */
+ unsigned int rcnocache; /* uncached reqs */
+ unsigned int fh_cached; /* dentry cached */
+ unsigned int fh_valid; /* dentry validated */
+ unsigned int fh_fixup; /* dentry fixup validated */
+ unsigned int fh_lookup; /* new lookup required */
+ unsigned int fh_stale; /* FH stale error */
+ unsigned int fh_concurrent; /* concurrent request */
+ unsigned int fh_anon; /* anon file dentry returned */
+ unsigned int fh_nocache_dir; /* dir filehandle not found in dcache */
+ unsigned int fh_nocache_nondir; /* nondir filehandle not in dcache */
+
+ /* /proc/net/rpc/nfsd "io" */
+ unsigned int io_read; /* bytes returned to read requests */
+ unsigned int io_write; /* bytes passed in write requests */
+
+ /* /proc/net/rpc/nfsd "th" */
+ unsigned int th_cnt; /* available nfsd threads */
+ unsigned int th_fullcnt; /* times last free thread used */
+
+ /* /proc/net/rpc/nfsd "net" */
+ unsigned int netcnt;
+ unsigned int netudpcnt;
+ unsigned int nettcpcnt;
+ unsigned int nettcpconn;
+
+ /* /proc/net/rpc/nfsd "rpc" */
+ unsigned int rpccnt;
+ unsigned int rpcerr;
+ unsigned int rpcbadfmt;
+ unsigned int rpcbadauth;
+ unsigned int rpcbadclnt;
+
+ /* /proc/net/rpc/nfsd "proc2" */
+ unsigned int reqcounts[NR_RPC_COUNTERS];
+
+ /* /proc/net/rpc/nfsd "proc3" */
+ unsigned int reqcounts3[NR_RPC3_COUNTERS];
+
+ /* /proc/net/rpc/nfsd "proc4" & "proc4ops" */
+ unsigned int reqcounts4[NR_RPC4_SVR_COUNTERS];
+ } server;
+
+} proc_net_rpc_t;
+
+extern int refresh_proc_net_rpc(proc_net_rpc_t *);
diff --git a/src/pmdas/linux/proc_net_snmp.c b/src/pmdas/linux/proc_net_snmp.c
new file mode 100644
index 0000000..431d984
--- /dev/null
+++ b/src/pmdas/linux/proc_net_snmp.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_net_snmp.h"
+
+extern proc_net_snmp_t _pm_proc_net_snmp;
+extern pmdaInstid _pm_proc_net_snmp_indom_id[];
+static char *proc_net_snmp_icmpmsg_names;
+
+typedef struct {
+ const char *field;
+ __uint64_t *offset;
+} snmp_fields_t;
+
+snmp_fields_t ip_fields[] = {
+ { .field = "Forwarding",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FORWARDING] },
+ { .field = "DefaultTTL",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_DEFAULTTTL] },
+ { .field = "InReceives",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INRECEIVES] },
+ { .field = "InHdrErrors",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INHDRERRORS] },
+ { .field = "InAddrErrors",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INADDRERRORS] },
+ { .field = "ForwDatagrams",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FORWDATAGRAMS] },
+ { .field = "InUnknownProtos",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INUNKNOWNPROTOS] },
+ { .field = "InDiscards",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INDISCARDS] },
+ { .field = "InDelivers",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_INDELIVERS] },
+ { .field = "OutRequests",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTREQUESTS] },
+ { .field = "OutDiscards",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTDISCARDS] },
+ { .field = "OutNoRoutes",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_OUTNOROUTES] },
+ { .field = "ReasmTimeout",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMTIMEOUT] },
+ { .field = "ReasmReqds",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMREQDS] },
+ { .field = "ReasmOKs",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMOKS] },
+ { .field = "ReasmFails",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_REASMFAILS] },
+ { .field = "FragOKs",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGOKS] },
+ { .field = "FragFails",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGFAILS] },
+ { .field = "FragCreates",
+ .offset = &_pm_proc_net_snmp.ip[_PM_SNMP_IP_FRAGCREATES] },
+ { .field = NULL, .offset = NULL }
+};
+
+snmp_fields_t icmp_fields[] = {
+ { .field = "InMsgs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INMSGS] },
+ { .field = "InErrors",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INERRORS] },
+ { .field = "InCsumErrors",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INCSUMERRORS] },
+ { .field = "InDestUnreachs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INDESTUNREACHS] },
+ { .field = "InTimeExcds",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMEEXCDS] },
+ { .field = "InParmProbs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INPARMPROBS] },
+ { .field = "InSrcQuenchs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INSRCQUENCHS] },
+ { .field = "InRedirects",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INREDIRECTS] },
+ { .field = "InEchos",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INECHOS] },
+ { .field = "InEchoReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INECHOREPS] },
+ { .field = "InTimestamps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMESTAMPS] },
+ { .field = "InTimestampReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INTIMESTAMPREPS] },
+ { .field = "InAddrMasks",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INADDRMASKS] },
+ { .field = "InAddrMaskReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_INADDRMASKREPS] },
+ { .field = "OutMsgs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTMSGS] },
+ { .field = "OutErrors",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTERRORS] },
+ { .field = "OutDestUnreachs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTDESTUNREACHS] },
+ { .field = "OutTimeExcds",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMEEXCDS] },
+ { .field = "OutParmProbs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTPARMPROBS] },
+ { .field = "OutSrcQuenchs",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTSRCQUENCHS] },
+ { .field = "OutRedirects",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTREDIRECTS] },
+ { .field = "OutEchos",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTECHOS] },
+ { .field = "OutEchoReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTECHOREPS] },
+ { .field = "OutTimestamps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMESTAMPS] },
+ { .field = "OutTimestampReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTTIMESTAMPREPS] },
+ { .field = "OutAddrMasks",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTADDRMASKS] },
+ { .field = "OutAddrMaskReps",
+ .offset = &_pm_proc_net_snmp.icmp[_PM_SNMP_ICMP_OUTADDRMASKREPS] },
+ { .field = NULL, .offset = NULL }
+};
+
+snmp_fields_t icmpmsg_fields[] = {
+ { .field = "InType%u",
+ .offset = &_pm_proc_net_snmp.icmpmsg[_PM_SNMP_ICMPMSG_INTYPE] },
+ { .field = "OutType%u",
+ .offset = &_pm_proc_net_snmp.icmpmsg[_PM_SNMP_ICMPMSG_OUTTYPE] },
+ { .field = NULL, .offset = NULL }
+};
+
+snmp_fields_t tcp_fields[] = {
+ { .field = "RtoAlgorithm",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOALGORITHM] },
+ { .field = "RtoMin",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOMIN] },
+ { .field = "RtoMax",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RTOMAX] },
+ { .field = "MaxConn",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_MAXCONN] },
+ { .field = "ActiveOpens",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ACTIVEOPENS] },
+ { .field = "PassiveOpens",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_PASSIVEOPENS] },
+ { .field = "AttemptFails",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ATTEMPTFAILS] },
+ { .field = "EstabResets",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_ESTABRESETS] },
+ { .field = "CurrEstab",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_CURRESTAB] },
+ { .field = "InSegs",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INSEGS] },
+ { .field = "OutSegs",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_OUTSEGS] },
+ { .field = "RetransSegs",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_RETRANSSEGS] },
+ { .field = "InErrs",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INERRS] },
+ { .field = "OutRsts",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_OUTRSTS] },
+ { .field = "InCsumErrors",
+ .offset = &_pm_proc_net_snmp.tcp[_PM_SNMP_TCP_INCSUMERRORS] },
+ { .field = NULL, .offset = NULL }
+};
+
+snmp_fields_t udp_fields[] = {
+ { .field = "InDatagrams",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INDATAGRAMS] },
+ { .field = "NoPorts",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_NOPORTS] },
+ { .field = "InErrors",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INERRORS] },
+ { .field = "OutDatagrams",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_OUTDATAGRAMS] },
+ { .field = "RcvbufErrors",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_RECVBUFERRORS] },
+ { .field = "SndbufErrors",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_SNDBUFERRORS] },
+ { .field = "InCsumErrors",
+ .offset = &_pm_proc_net_snmp.udp[_PM_SNMP_UDP_INCSUMERRORS] },
+ { .field = NULL, .offset = NULL }
+};
+
+snmp_fields_t udplite_fields[] = {
+ { .field = "InDatagrams",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INDATAGRAMS] },
+ { .field = "NoPorts",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_NOPORTS] },
+ { .field = "InErrors",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INERRORS] },
+ { .field = "OutDatagrams",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_OUTDATAGRAMS] },
+ { .field = "RcvbufErrors",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_RECVBUFERRORS] },
+ { .field = "SndbufErrors",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_SNDBUFERRORS] },
+ { .field = "InCsumErrors",
+ .offset = &_pm_proc_net_snmp.udplite[_PM_SNMP_UDPLITE_INCSUMERRORS] },
+ { .field = NULL, .offset = NULL }
+};
+
+static void
+get_fields(snmp_fields_t *fields, char *header, char *buffer)
+{
+ int i, j, count;
+ char *p, *indices[SNMP_MAX_COLUMNS];
+
+ /* first get pointers to each of the column headings */
+ strtok(header, " ");
+ for (i = 0; i < SNMP_MAX_COLUMNS; i++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ indices[i] = p;
+ }
+ count = i;
+
+ /*
+ * Extract values via back-referencing column headings.
+ * "i" is the last found index, which we use for a bit
+ * of optimisation for the (common) in-order maps case
+ * (where "in order" means in the order defined by the
+ * passed in "fields" table which typically matches the
+ * kernel - but may be out-of-order for older kernels).
+ */
+ strtok(buffer, " ");
+ for (i = j = 0; j < count && fields[i].field; j++, i++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ if (strcmp(fields[i].field, indices[j]) == 0) {
+ *fields[i].offset = strtoull(p, NULL, 10);
+ } else {
+ for (i = 0; fields[i].field; i++) {
+ if (strcmp(fields[i].field, indices[j]) != 0)
+ continue;
+ *fields[i].offset = strtoull(p, NULL, 10);
+ break;
+ }
+ if (fields[i].field == NULL) /* not found, ignore */
+ i = 0;
+ }
+ }
+}
+
+static void
+get_ordinal_fields(snmp_fields_t *fields, char *header, char *buffer,
+ unsigned limit)
+{
+ int i, j, count;
+ unsigned int inst;
+ char *p, *indices[SNMP_MAX_COLUMNS];
+
+ strtok(header, " ");
+ for (i = 0; i < SNMP_MAX_COLUMNS; i++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ indices[i] = p;
+ }
+ count = i;
+
+ strtok(buffer, " ");
+ for (j = 0; j < count; j++) {
+ if ((p = strtok(NULL, " \n")) == NULL)
+ break;
+ for (i = 0; fields[i].field; i++) {
+ if (sscanf(indices[j], fields[i].field, &inst) != 1)
+ continue;
+ if (inst >= limit)
+ continue;
+ *(fields[i].offset + inst) = strtoull(p, NULL, 10);
+ break;
+ }
+ }
+}
+
+#define SNMP_IP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)ip_fields[ii].offset - (__psint_t)&_pm_proc_net_snmp.ip)
+#define SNMP_ICMP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)icmp_fields[ii].offset - (__psint_t)&_pm_proc_net_snmp.icmp)
+#define SNMP_ICMPMSG_OFFSET(ii, nn, pp) (int64_t *)((char *)pp + \
+ (__psint_t)(icmpmsg_fields[ii].offset + nn) - (__psint_t)&_pm_proc_net_snmp.icmpmsg)
+#define SNMP_TCP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)tcp_fields[ii].offset - (__psint_t)&_pm_proc_net_snmp.tcp)
+#define SNMP_UDP_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)udp_fields[ii].offset - (__psint_t)&_pm_proc_net_snmp.udp)
+#define SNMP_UDPLITE_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)udplite_fields[ii].offset - (__psint_t)&_pm_proc_net_snmp.udplite)
+
+static void
+init_refresh_proc_net_snmp(proc_net_snmp_t *snmp)
+{
+ pmdaIndom *idp;
+ char *s;
+ int i, n;
+
+ /* initially, all marked as "no value available" */
+ for (i = 0; ip_fields[i].field != NULL; i++)
+ *(SNMP_IP_OFFSET(i, snmp->ip)) = -1;
+ for (i = 0; icmp_fields[i].field != NULL; i++)
+ *(SNMP_ICMP_OFFSET(i, snmp->icmp)) = -1;
+ for (i = 0; tcp_fields[i].field != NULL; i++)
+ *(SNMP_TCP_OFFSET(i, snmp->tcp)) = -1;
+ for (i = 0; udp_fields[i].field != NULL; i++)
+ *(SNMP_UDP_OFFSET(i, snmp->udp)) = -1;
+ for (i = 0; udplite_fields[i].field != NULL; i++)
+ *(SNMP_UDPLITE_OFFSET(i, snmp->udplite)) = -1;
+ for (i = 0; icmpmsg_fields[i].field != NULL; i++)
+ for (n = 0; n < NR_ICMPMSG_COUNTERS; n++)
+ *(SNMP_ICMPMSG_OFFSET(i, n, snmp->icmpmsg)) = -1;
+
+ /* only need to allocate and setup the names once */
+ if (proc_net_snmp_icmpmsg_names)
+ return;
+ i = NR_ICMPMSG_COUNTERS * SNMP_MAX_ICMPMSG_TYPESTR;
+ proc_net_snmp_icmpmsg_names = malloc(i);
+ if (!proc_net_snmp_icmpmsg_names)
+ return;
+ s = proc_net_snmp_icmpmsg_names;
+ for (n = 0; n < NR_ICMPMSG_COUNTERS; n++) {
+ sprintf(s, "Type%u", n);
+ _pm_proc_net_snmp_indom_id[n].i_name = s;
+ _pm_proc_net_snmp_indom_id[n].i_inst = n;
+ s += SNMP_MAX_ICMPMSG_TYPESTR;
+ }
+ idp = PMDAINDOM(ICMPMSG_INDOM);
+ idp->it_numinst = NR_ICMPMSG_COUNTERS;
+ idp->it_set = _pm_proc_net_snmp_indom_id;
+}
+
+int
+refresh_proc_net_snmp(proc_net_snmp_t *snmp)
+{
+ char buf[MAXPATHLEN];
+ char header[1024];
+ FILE *fp;
+
+ init_refresh_proc_net_snmp(snmp);
+ if ((fp = linux_statsfile("/proc/net/snmp", buf, sizeof(buf))) == NULL)
+ return -oserror();
+ while (fgets(header, sizeof(header), fp) != NULL) {
+ if (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "Ip:", 3) == 0)
+ get_fields(ip_fields, header, buf);
+ else if (strncmp(buf, "Icmp:", 5) == 0)
+ get_fields(icmp_fields, header, buf);
+ else if (strncmp(buf, "IcmpMsg:", 8) == 0)
+ get_ordinal_fields(icmpmsg_fields, header, buf,
+ NR_ICMPMSG_COUNTERS);
+ else if (strncmp(buf, "Tcp:", 4) == 0)
+ get_fields(tcp_fields, header, buf);
+ else if (strncmp(buf, "Udp:", 4) == 0)
+ get_fields(udp_fields, header, buf);
+ else if (strncmp(buf, "UdpLite:", 8) == 0)
+ get_fields(udplite_fields, header, buf);
+ else
+ fprintf(stderr, "Error: unrecognised snmp row: %s", buf);
+ }
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_net_snmp.h b/src/pmdas/linux/proc_net_snmp.h
new file mode 100644
index 0000000..eeeb2a6
--- /dev/null
+++ b/src/pmdas/linux/proc_net_snmp.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define SNMP_MAX_COLUMNS 64 /* arbitrary upper bound */
+#define SNMP_PERLINE 16 /* see net/ipv4/proc.c */
+#define SNMP_MAX_ICMPMSG_TYPESTR 8 /* longest name for type */
+#define NR_ICMPMSG_COUNTERS 256 /* half of __ICMPMSG_MIB_MAX from kernel */
+
+enum {
+ _PM_SNMP_IP_FORWARDING = 0,
+ _PM_SNMP_IP_DEFAULTTTL,
+ _PM_SNMP_IP_INRECEIVES,
+ _PM_SNMP_IP_INHDRERRORS,
+ _PM_SNMP_IP_INADDRERRORS,
+ _PM_SNMP_IP_FORWDATAGRAMS,
+ _PM_SNMP_IP_INUNKNOWNPROTOS,
+ _PM_SNMP_IP_INDISCARDS,
+ _PM_SNMP_IP_INDELIVERS,
+ _PM_SNMP_IP_OUTREQUESTS,
+ _PM_SNMP_IP_OUTDISCARDS,
+ _PM_SNMP_IP_OUTNOROUTES,
+ _PM_SNMP_IP_REASMTIMEOUT,
+ _PM_SNMP_IP_REASMREQDS,
+ _PM_SNMP_IP_REASMOKS,
+ _PM_SNMP_IP_REASMFAILS,
+ _PM_SNMP_IP_FRAGOKS,
+ _PM_SNMP_IP_FRAGFAILS,
+ _PM_SNMP_IP_FRAGCREATES,
+
+ _PM_SNMP_IP_NFIELDS /* must be last */
+};
+
+enum {
+ _PM_SNMP_ICMP_INMSGS = 0,
+ _PM_SNMP_ICMP_INERRORS,
+ _PM_SNMP_ICMP_INCSUMERRORS,
+ _PM_SNMP_ICMP_INDESTUNREACHS,
+ _PM_SNMP_ICMP_INTIMEEXCDS,
+ _PM_SNMP_ICMP_INPARMPROBS,
+ _PM_SNMP_ICMP_INSRCQUENCHS,
+ _PM_SNMP_ICMP_INREDIRECTS,
+ _PM_SNMP_ICMP_INECHOS,
+ _PM_SNMP_ICMP_INECHOREPS,
+ _PM_SNMP_ICMP_INTIMESTAMPS,
+ _PM_SNMP_ICMP_INTIMESTAMPREPS,
+ _PM_SNMP_ICMP_INADDRMASKS,
+ _PM_SNMP_ICMP_INADDRMASKREPS,
+ _PM_SNMP_ICMP_OUTMSGS,
+ _PM_SNMP_ICMP_OUTERRORS,
+ _PM_SNMP_ICMP_OUTDESTUNREACHS,
+ _PM_SNMP_ICMP_OUTTIMEEXCDS,
+ _PM_SNMP_ICMP_OUTPARMPROBS,
+ _PM_SNMP_ICMP_OUTSRCQUENCHS,
+ _PM_SNMP_ICMP_OUTREDIRECTS,
+ _PM_SNMP_ICMP_OUTECHOS,
+ _PM_SNMP_ICMP_OUTECHOREPS,
+ _PM_SNMP_ICMP_OUTTIMESTAMPS,
+ _PM_SNMP_ICMP_OUTTIMESTAMPREPS,
+ _PM_SNMP_ICMP_OUTADDRMASKS,
+ _PM_SNMP_ICMP_OUTADDRMASKREPS,
+
+ _PM_SNMP_ICMP_NFIELDS /* must be last */
+};
+
+enum {
+ _PM_SNMP_ICMPMSG_INTYPE = 0,
+ _PM_SNMP_ICMPMSG_OUTTYPE = NR_ICMPMSG_COUNTERS,
+ _PM_SNMP_ICMPMSG_NFIELDS = (NR_ICMPMSG_COUNTERS*2)
+};
+
+enum {
+ _PM_SNMP_TCP_RTOALGORITHM = 0,
+ _PM_SNMP_TCP_RTOMIN,
+ _PM_SNMP_TCP_RTOMAX,
+ _PM_SNMP_TCP_MAXCONN,
+ _PM_SNMP_TCP_ACTIVEOPENS,
+ _PM_SNMP_TCP_PASSIVEOPENS,
+ _PM_SNMP_TCP_ATTEMPTFAILS,
+ _PM_SNMP_TCP_ESTABRESETS,
+ _PM_SNMP_TCP_CURRESTAB,
+ _PM_SNMP_TCP_INSEGS,
+ _PM_SNMP_TCP_OUTSEGS,
+ _PM_SNMP_TCP_RETRANSSEGS,
+ _PM_SNMP_TCP_INERRS,
+ _PM_SNMP_TCP_OUTRSTS,
+ _PM_SNMP_TCP_INCSUMERRORS,
+
+ _PM_SNMP_TCP_NFIELDS /* must be last */
+};
+
+enum {
+ _PM_SNMP_UDP_INDATAGRAMS = 0,
+ _PM_SNMP_UDP_NOPORTS,
+ _PM_SNMP_UDP_INERRORS,
+ _PM_SNMP_UDP_OUTDATAGRAMS,
+ _PM_SNMP_UDP_RECVBUFERRORS,
+ _PM_SNMP_UDP_SNDBUFERRORS,
+ _PM_SNMP_UDP_INCSUMERRORS,
+
+ _PM_SNMP_UDP_NFIELDS /* must be last */
+};
+
+enum {
+ _PM_SNMP_UDPLITE_INDATAGRAMS = 0,
+ _PM_SNMP_UDPLITE_NOPORTS,
+ _PM_SNMP_UDPLITE_INERRORS,
+ _PM_SNMP_UDPLITE_OUTDATAGRAMS,
+ _PM_SNMP_UDPLITE_RECVBUFERRORS,
+ _PM_SNMP_UDPLITE_SNDBUFERRORS,
+ _PM_SNMP_UDPLITE_INCSUMERRORS,
+
+ _PM_SNMP_UDPLITE_NFIELDS /* must be last */
+};
+
+typedef struct {
+ __uint64_t ip[_PM_SNMP_IP_NFIELDS];
+ __uint64_t icmp[_PM_SNMP_ICMP_NFIELDS];
+ __uint64_t icmpmsg[_PM_SNMP_ICMPMSG_NFIELDS];
+ __uint64_t tcp[_PM_SNMP_TCP_NFIELDS];
+ __uint64_t udp[_PM_SNMP_UDP_NFIELDS];
+ __uint64_t udplite[_PM_SNMP_UDPLITE_NFIELDS];
+} proc_net_snmp_t;
+
+extern int refresh_proc_net_snmp(proc_net_snmp_t *);
diff --git a/src/pmdas/linux/proc_net_snmp_migrate.conf b/src/pmdas/linux/proc_net_snmp_migrate.conf
new file mode 100644
index 0000000..4e3c4f7
--- /dev/null
+++ b/src/pmdas/linux/proc_net_snmp_migrate.conf
@@ -0,0 +1,8 @@
+# Copyright 2013 Red Hat.
+#
+# pmlogrewrite configuration for migrating archives containing old
+# 32 bit /proc/net/snmp values to the 64 bit variants, matching up
+# with changes in the metadata supplied by the PMDA (and the kernel).
+#
+
+metric 60.14.* { type -> U64 }
diff --git a/src/pmdas/linux/proc_net_sockstat.c b/src/pmdas/linux/proc_net_sockstat.c
new file mode 100644
index 0000000..94f5a79
--- /dev/null
+++ b/src/pmdas/linux/proc_net_sockstat.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_net_sockstat.h"
+
+int
+refresh_proc_net_sockstat(proc_net_sockstat_t *proc_net_sockstat)
+{
+ char buf[1024];
+ char fmt[64];
+ FILE *fp;
+
+ if ((fp = linux_statsfile("/proc/net/sockstat", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "TCP:", 4) == 0) {
+ sscanf(buf, "%s %s %d %s %d", fmt, fmt,
+ &proc_net_sockstat->tcp[_PM_SOCKSTAT_INUSE], fmt,
+ &proc_net_sockstat->tcp[_PM_SOCKSTAT_HIGHEST]);
+ proc_net_sockstat->tcp[_PM_SOCKSTAT_UTIL] =
+ proc_net_sockstat->tcp[_PM_SOCKSTAT_HIGHEST] != 0 ?
+ 100 * proc_net_sockstat->tcp[_PM_SOCKSTAT_INUSE] /
+ proc_net_sockstat->tcp[_PM_SOCKSTAT_HIGHEST] : 0;
+ }
+ else
+ if (strncmp(buf, "UDP:", 4) == 0) {
+ sscanf(buf, "%s %s %d %s %d", fmt, fmt,
+ &proc_net_sockstat->udp[_PM_SOCKSTAT_INUSE], fmt,
+ &proc_net_sockstat->udp[_PM_SOCKSTAT_HIGHEST]);
+ proc_net_sockstat->udp[_PM_SOCKSTAT_UTIL] =
+ proc_net_sockstat->udp[_PM_SOCKSTAT_HIGHEST] != 0 ?
+ 100 * proc_net_sockstat->udp[_PM_SOCKSTAT_INUSE] /
+ proc_net_sockstat->udp[_PM_SOCKSTAT_HIGHEST] : 0;
+ }
+ else
+ if (strncmp(buf, "RAW:", 4) == 0) {
+ sscanf(buf, "%s %s %d %s %d", fmt, fmt,
+ &proc_net_sockstat->raw[_PM_SOCKSTAT_INUSE], fmt,
+ &proc_net_sockstat->raw[_PM_SOCKSTAT_HIGHEST]);
+ proc_net_sockstat->raw[_PM_SOCKSTAT_UTIL] =
+ proc_net_sockstat->raw[_PM_SOCKSTAT_HIGHEST] != 0 ?
+ 100 * proc_net_sockstat->raw[_PM_SOCKSTAT_INUSE] /
+ proc_net_sockstat->raw[_PM_SOCKSTAT_HIGHEST] : 0;
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_net_sockstat.h b/src/pmdas/linux/proc_net_sockstat.h
new file mode 100644
index 0000000..4ad402e
--- /dev/null
+++ b/src/pmdas/linux/proc_net_sockstat.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define _PM_SOCKSTAT_INUSE 0
+#define _PM_SOCKSTAT_HIGHEST 1
+#define _PM_SOCKSTAT_UTIL 2
+typedef struct {
+ int tcp[3];
+ int udp[3];
+ int raw[3];
+} proc_net_sockstat_t;
+
+extern int refresh_proc_net_sockstat(proc_net_sockstat_t *);
+
diff --git a/src/pmdas/linux/proc_net_tcp.c b/src/pmdas/linux/proc_net_tcp.c
new file mode 100644
index 0000000..52f59b0
--- /dev/null
+++ b/src/pmdas/linux/proc_net_tcp.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1999,2004 Silicon Graphics, Inc. All Rights Reserved.
+ * This code contributed by Michal Kara (lemming@arthur.plbohnice.cz)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_net_tcp.h"
+
+#define MYBUFSZ (1<<14) /*16k*/
+
+int
+refresh_proc_net_tcp(proc_net_tcp_t *proc_net_tcp)
+{
+ FILE *fp;
+ char buf[MYBUFSZ];
+ char *p = buf;
+ char *q;
+ unsigned int n;
+ ssize_t got = 0;
+ ptrdiff_t remnant = 0;
+
+ memset(proc_net_tcp, 0, sizeof(*proc_net_tcp));
+
+ if ((fp = linux_statsfile("/proc/net/tcp", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ /* skip header */
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ /* oops, no header! */
+ fclose(fp);
+ return -oserror();
+ }
+ for (buf[0]='\0';;) {
+ q = strchrnul(p, '\n');
+ if (*q == '\n') {
+ if (1 == sscanf(p, " %*s %*s %*s %x", &n)
+ && n < _PM_TCP_LAST) {
+ proc_net_tcp->stat[n]++;
+ }
+ p = q + 1;
+ continue;
+ }
+ remnant = (q - p);
+ if (remnant > 0 && p != buf)
+ memmove(buf, p, remnant);
+
+ got = read(fileno(fp), buf + remnant, MYBUFSZ - remnant - 1);
+ if (got <= 0)
+ break;
+
+ buf[remnant + got] = '\0';
+ p = buf;
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_net_tcp.h b/src/pmdas/linux/proc_net_tcp.h
new file mode 100644
index 0000000..87e662f
--- /dev/null
+++ b/src/pmdas/linux/proc_net_tcp.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 1999,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Michal Kara (lemming@arthur.plbohnice.cz)
+ */
+
+enum {
+ _PM_TCP_ESTABLISHED = 1,
+ _PM_TCP_SYN_SENT,
+ _PM_TCP_SYN_RECV,
+ _PM_TCP_FIN_WAIT1,
+ _PM_TCP_FIN_WAIT2,
+ _PM_TCP_TIME_WAIT,
+ _PM_TCP_CLOSE,
+ _PM_TCP_CLOSE_WAIT,
+ _PM_TCP_LAST_ACK,
+ _PM_TCP_LISTEN,
+ _PM_TCP_CLOSING,
+ _PM_TCP_LAST
+};
+
+
+typedef struct {
+ int stat[_PM_TCP_LAST];
+} proc_net_tcp_t;
+
+extern int refresh_proc_net_tcp(proc_net_tcp_t *);
+
diff --git a/src/pmdas/linux/proc_partitions.c b/src/pmdas/linux/proc_partitions.c
new file mode 100644
index 0000000..4901892
--- /dev/null
+++ b/src/pmdas/linux/proc_partitions.c
@@ -0,0 +1,808 @@
+/*
+ * Linux Partitions (disk and disk partition IO stats) Cluster
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2008,2012 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "convert.h"
+#include "clusters.h"
+#include "indom.h"
+#include "proc_partitions.h"
+
+extern int _pm_numdisks;
+
+/*
+ * _pm_ispartition : return true if arg is a partition name
+ * return false if arg is a disk name
+ * ide disks are named e.g. hda
+ * ide partitions are named e.g. hda1
+ *
+ * scsi disks are named e.g. sda
+ * scsi partitions are named e.g. sda1
+ *
+ * device-mapper devices are named dm-[0-9]* and are mapped to their persistent
+ * name using the symlinks in /dev/mapper.
+ *
+ * devfs scsi disks are named e.g. scsi/host0/bus0/target1/lun0/disc
+ * devfs scsi partitions are named e.g. scsi/host0/bus0/target1/lun0/part1
+ *
+ * Mylex raid disks are named e.g. rd/c0d0 or dac960/c0d0
+ * Mylex raid partitions are named e.g. rd/c0d0p1 or dac960/c0d0p1
+ *
+ * What this now tries to do is be a bit smarter, and guess that names
+ * with slashes that end in the form .../c0t0d0[p0], and ones without
+ * are good old 19th century device names like xx0 or xx0a.
+ */
+static int
+_pm_isloop(char *dname)
+{
+ return strncmp(dname, "loop", 4) == 0;
+}
+
+static int
+_pm_isramdisk(char *dname)
+{
+ return strncmp(dname, "ram", 3) == 0;
+}
+
+static int
+_pm_ismmcdisk(char *dname)
+{
+ if (strncmp(dname, "mmcblk", 6) != 0)
+ return 0;
+ /*
+ * Are we a disk or a partition of the disk? If there is a "p"
+ * assume it is a partition - e.g. mmcblk0p6.
+ */
+ return (strchr(dname + 6, 'p') == NULL);
+}
+
+/*
+ * return true if arg is a device-mapper device
+ */
+static int
+_pm_isdm(char *dname)
+{
+ return strncmp(dname, "dm-", 3) == 0;
+}
+
+/*
+ * slight improvement to heuristic suggested by
+ * Tim Bradshaw <tfb@cley.com> on 29 Dec 2003
+ */
+int
+_pm_ispartition(char *dname)
+{
+ int p, m = strlen(dname) - 1;
+
+ /*
+ * looking at something like foo/x, and we hope x ends p<n>, for
+ * a partition, or not for a disk.
+ */
+ if (strchr(dname, '/')) {
+ for (p = m; p > 0 && isdigit((int)dname[p]); p--)
+ ;
+ if (p == m)
+ /* name had no trailing digits. Wildly guess a disk. */
+ return 1;
+ else
+ /*
+ * ends with digits, if preceding character is a 'p' punt
+ * on a partition
+ */
+ return (dname[p] == 'p'? 1 : 0);
+ }
+ else {
+ /*
+ * default test : partition names end in a digit do not
+ * look like loopback devices. Handle other special-cases
+ * here - mostly seems to be RAM-type disk drivers that're
+ * choosing to end device names with numbers.
+ */
+ return isdigit((int)dname[m]) &&
+ !_pm_isloop(dname) &&
+ !_pm_isramdisk(dname) &&
+ !_pm_ismmcdisk(dname) &&
+ !_pm_isdm(dname);
+ }
+}
+
+/*
+ * return true is arg is an xvm volume name
+ */
+static int
+_pm_isxvmvol(char *dname)
+{
+ return strstr(dname, "xvm") != NULL;
+}
+
+/*
+ * return true is arg is a disk name
+ */
+static int
+_pm_isdisk(char *dname)
+{
+ return !_pm_isloop(dname) && !_pm_isramdisk(dname) && !_pm_ispartition(dname) && !_pm_isxvmvol(dname) && !_pm_isdm(dname);
+}
+
+static void
+refresh_udev(pmInDom disk_indom, pmInDom partitions_indom)
+{
+ char buf[MAXNAMELEN];
+ char realname[MAXNAMELEN];
+ char *shortname;
+ char *p;
+ char *udevname;
+ FILE *pfp;
+ partitions_entry_t *entry;
+ int indom;
+ int inst;
+
+ if (access("/dev/xscsi", R_OK) != 0)
+ return;
+ if (!(pfp = popen("find /dev/xscsi -name disc -o -name part[0-9]*", "r")))
+ return;
+ while (fgets(buf, sizeof(buf), pfp)) {
+ if ((p = strrchr(buf, '\n')) != NULL)
+ *p = '\0';
+ if (realpath(buf, realname)) {
+ udevname = buf + 5; /* /dev/xscsi/.... */
+ if ((shortname = strrchr(realname, '/')) != NULL) {
+ shortname++;
+ indom = _pm_ispartition(shortname) ?
+ partitions_indom : disk_indom;
+ if (pmdaCacheLookupName(indom, shortname, &inst, (void **)&entry) != PMDA_CACHE_ACTIVE)
+ continue;
+ entry->udevnamebuf = strdup(udevname);
+ pmdaCacheStore(indom, PMDA_CACHE_HIDE, shortname, entry); /* inactive */
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, udevname, entry); /* active */
+ }
+ }
+ }
+ pclose(pfp);
+}
+
+/*
+ * Replace dm-* in namebuf with it's persistent name. This is a symlink in
+ * /dev/mapper/something -> ../dm-X where dm-X is currently in namebuf. Some
+ * older platforms (e.g. RHEL5) don't have the symlinks, just block devices
+ * in /dev/mapper. On newer kernels, the persistent name mapping is also
+ * exported via sysfs, which we use in preference. If this fails we leave
+ * the argument namebuf unaltered and return 0.
+ */
+static int
+map_persistent_dm_name(char *namebuf, int namelen, int devmajor, int devminor)
+{
+ int fd;
+ char *p;
+ DIR *dp;
+ int found = 0;
+ struct dirent *dentry;
+ struct stat sb;
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", linux_statspath, namebuf);
+ if ((fd = open(path, O_RDONLY)) >= 0) {
+ memset(path, 0, sizeof(path));
+ if (read(fd, path, sizeof(path)) > 0) {
+ if ((p = strchr(path, '\n')) != NULL)
+ *p = '\0';
+ strncpy(namebuf, path, MIN(sizeof(path), namelen));
+ found = 1;
+ }
+ close(fd);
+ }
+
+ if (!found) {
+ /*
+ * The sysfs name isn't available, so we'll have to walk /dev/mapper
+ * and match up dev_t instead [happens on RHEL5 and maybe elsewhere].
+ */
+ snprintf(path, sizeof(path), "%s/dev/mapper", linux_statspath);
+ if ((dp = opendir(path)) != NULL) {
+ while ((dentry = readdir(dp)) != NULL) {
+ snprintf(path, sizeof(path),
+ "%s/dev/mapper/%s", linux_statspath, dentry->d_name);
+ if (stat(path, &sb) != 0 || !S_ISBLK(sb.st_mode))
+ continue; /* only interested in block devices */
+ if (devmajor == major(sb.st_rdev) && devminor == minor(sb.st_rdev)) {
+ strncpy(namebuf, dentry->d_name, namelen);
+ found = 1;
+ break;
+ }
+ }
+ closedir(dp);
+ }
+ }
+
+ return found;
+}
+
+int
+refresh_proc_partitions(pmInDom disk_indom, pmInDom partitions_indom, pmInDom dm_indom)
+{
+ FILE *fp;
+ int devmin;
+ int devmaj;
+ int n;
+ int indom;
+ int have_proc_diskstats;
+ int inst;
+ unsigned long long blocks;
+ partitions_entry_t *p;
+ int indom_changes = 0;
+ char *dmname;
+ char buf[MAXPATHLEN];
+ char namebuf[MAXPATHLEN];
+ static int first = 1;
+
+ if (first) {
+ /* initialize the instance domain caches */
+ pmdaCacheOp(disk_indom, PMDA_CACHE_LOAD);
+ pmdaCacheOp(partitions_indom, PMDA_CACHE_LOAD);
+ pmdaCacheOp(dm_indom, PMDA_CACHE_LOAD);
+
+ first = 0;
+ indom_changes = 1;
+ }
+
+ pmdaCacheOp(disk_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(partitions_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(dm_indom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = linux_statsfile("/proc/diskstats", buf, sizeof(buf))) != NULL)
+ /* 2.6 style disk stats */
+ have_proc_diskstats = 1;
+ else {
+ if ((fp = linux_statsfile("/proc/partitions", buf, sizeof(buf))) != NULL)
+ have_proc_diskstats = 0;
+ else
+ return -oserror();
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ dmname = NULL;
+ if (buf[0] != ' ' || buf[0] == '\n') {
+ /* skip heading */
+ continue;
+ }
+
+ if (have_proc_diskstats) {
+ if ((n = sscanf(buf, "%d %d %s", &devmaj, &devmin, namebuf)) != 3)
+ continue;
+ }
+ else {
+ /* /proc/partitions */
+ if ((n = sscanf(buf, "%d %d %llu %s", &devmaj, &devmin, &blocks, namebuf)) != 4)
+ continue;
+ }
+
+ if (_pm_isdm(namebuf)) {
+ indom = dm_indom;
+ dmname = strdup(namebuf);
+ }
+ else if (_pm_ispartition(namebuf))
+ indom = partitions_indom;
+ else if (_pm_isdisk(namebuf))
+ indom = disk_indom;
+ else
+ continue;
+
+ if (indom == dm_indom) {
+ /* replace dm-[0-9]* with the persistent name from /dev/mapper */
+ if (!map_persistent_dm_name(namebuf, sizeof(namebuf), devmaj, devmin)) {
+ /* skip dm devices that have no persistent name mapping */
+ free(dmname);
+ continue;
+ }
+ }
+
+ p = NULL;
+ if (pmdaCacheLookupName(indom, namebuf, &inst, (void **)&p) < 0 || !p) {
+ /* not found: allocate and add a new entry */
+ p = (partitions_entry_t *)malloc(sizeof(partitions_entry_t));
+ memset(p, 0, sizeof(partitions_entry_t));
+ indom_changes++;
+ }
+
+ if (p->dmname)
+ free(p->dmname);
+ p->dmname = dmname; /* NULL if not a dm device */
+
+ if (!p->namebuf)
+ p->namebuf = strdup(namebuf);
+ else
+ if (strcmp(namebuf, p->namebuf) != 0) {
+ free(p->namebuf);
+ p->namebuf = strdup(namebuf);
+ }
+
+ /* activate this entry */
+ if (p->udevnamebuf)
+ /* long xscsi name */
+ inst = pmdaCacheStore(indom, PMDA_CACHE_ADD, p->udevnamebuf, p);
+ else
+ /* short /proc/diskstats or /proc/partitions name */
+ inst = pmdaCacheStore(indom, PMDA_CACHE_ADD, namebuf, p);
+
+ if (have_proc_diskstats) {
+ /* 2.6 style /proc/diskstats */
+ p->nr_blocks = 0;
+ namebuf[0] = '\0';
+ /* Linux source: block/genhd.c::diskstats_show(1) */
+ n = sscanf(buf, "%u %u %s %lu %lu %llu %u %lu %lu %llu %u %u %u %u",
+ &p->major, &p->minor, namebuf,
+ &p->rd_ios, &p->rd_merges, &p->rd_sectors, &p->rd_ticks,
+ &p->wr_ios, &p->wr_merges, &p->wr_sectors, &p->wr_ticks,
+ &p->ios_in_flight, &p->io_ticks, &p->aveq);
+ if (n != 14) {
+ p->rd_merges = p->wr_merges = p->wr_ticks =
+ p->ios_in_flight = p->io_ticks = p->aveq = 0;
+ /* Linux source: block/genhd.c::diskstats_show(2) */
+ n = sscanf(buf, "%u %u %s %u %u %u %u\n",
+ &p->major, &p->minor, namebuf,
+ (unsigned int *)&p->rd_ios, (unsigned int *)&p->rd_sectors,
+ (unsigned int *)&p->wr_ios, (unsigned int *)&p->wr_sectors);
+ }
+ }
+ else {
+ /* 2.4 style /proc/partitions */
+ namebuf[0] = '\0';
+ n = sscanf(buf, "%u %u %lu %s %lu %lu %llu %u %lu %lu %llu %u %u %u %u",
+ &p->major, &p->minor, &p->nr_blocks, namebuf,
+ &p->rd_ios, &p->rd_merges, &p->rd_sectors,
+ &p->rd_ticks, &p->wr_ios, &p->wr_merges,
+ &p->wr_sectors, &p->wr_ticks, &p->ios_in_flight,
+ &p->io_ticks, &p->aveq);
+ }
+
+ }
+
+ /*
+ * If any new disks or partitions have appeared then we
+ * we need to remap the long device names (if /dev/xscsi
+ * exists) and then flush the pmda cache.
+ *
+ * We just let inactive instances rot in the inactive state
+ * (this doesn't happen very often, so is only a minor leak).
+ */
+ if (indom_changes) {
+ refresh_udev(disk_indom, partitions_indom);
+ pmdaCacheOp(disk_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(partitions_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(dm_indom, PMDA_CACHE_SAVE);
+ }
+
+ /*
+ * success
+ */
+ if (fp)
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * This table must always match the definitions in root_linux
+ * and metrictab[] in pmda.c
+ */
+static pmID disk_metric_table[] = {
+ /* disk.dev.read */ PMDA_PMID(CLUSTER_STAT,4),
+ /* disk.dev.write */ PMDA_PMID(CLUSTER_STAT,5),
+ /* disk.dev.total */ PMDA_PMID(CLUSTER_STAT,28),
+ /* disk.dev.blkread */ PMDA_PMID(CLUSTER_STAT,6),
+ /* disk.dev.blkwrite */ PMDA_PMID(CLUSTER_STAT,7),
+ /* disk.dev.blktotal */ PMDA_PMID(CLUSTER_STAT,36),
+ /* disk.dev.read_bytes */ PMDA_PMID(CLUSTER_STAT,38),
+ /* disk.dev.write_bytes */ PMDA_PMID(CLUSTER_STAT,39),
+ /* disk.dev.total_bytes */ PMDA_PMID(CLUSTER_STAT,40),
+ /* disk.dev.read_merge */ PMDA_PMID(CLUSTER_STAT,49),
+ /* disk.dev.write_merge */ PMDA_PMID(CLUSTER_STAT,50),
+ /* disk.dev.avactive */ PMDA_PMID(CLUSTER_STAT,46),
+ /* disk.dev.aveq */ PMDA_PMID(CLUSTER_STAT,47),
+ /* disk.dev.scheduler */ PMDA_PMID(CLUSTER_STAT,59),
+ /* disk.dev.read_rawactive */ PMDA_PMID(CLUSTER_STAT,72),
+ /* disk.dev.write_rawactive */ PMDA_PMID(CLUSTER_STAT,73),
+
+ /* disk.all.read */ PMDA_PMID(CLUSTER_STAT,24),
+ /* disk.all.write */ PMDA_PMID(CLUSTER_STAT,25),
+ /* disk.all.total */ PMDA_PMID(CLUSTER_STAT,29),
+ /* disk.all.blkread */ PMDA_PMID(CLUSTER_STAT,26),
+ /* disk.all.blkwrite */ PMDA_PMID(CLUSTER_STAT,27),
+ /* disk.all.blktotal */ PMDA_PMID(CLUSTER_STAT,37),
+ /* disk.all.read_bytes */ PMDA_PMID(CLUSTER_STAT,41),
+ /* disk.all.write_bytes */ PMDA_PMID(CLUSTER_STAT,42),
+ /* disk.all.total_bytes */ PMDA_PMID(CLUSTER_STAT,43),
+ /* disk.all.read_merge */ PMDA_PMID(CLUSTER_STAT,51),
+ /* disk.all.write_merge */ PMDA_PMID(CLUSTER_STAT,52),
+ /* disk.all.avactive */ PMDA_PMID(CLUSTER_STAT,44),
+ /* disk.all.aveq */ PMDA_PMID(CLUSTER_STAT,45),
+ /* disk.all.read_rawactive */ PMDA_PMID(CLUSTER_STAT,74),
+ /* disk.all.write_rawactive */ PMDA_PMID(CLUSTER_STAT,75),
+
+ /* disk.partitions.read */ PMDA_PMID(CLUSTER_PARTITIONS,0),
+ /* disk.partitions.write */ PMDA_PMID(CLUSTER_PARTITIONS,1),
+ /* disk.partitions.total */ PMDA_PMID(CLUSTER_PARTITIONS,2),
+ /* disk.partitions.blkread */ PMDA_PMID(CLUSTER_PARTITIONS,3),
+ /* disk.partitions.blkwrite */ PMDA_PMID(CLUSTER_PARTITIONS,4),
+ /* disk.partitions.blktotal */ PMDA_PMID(CLUSTER_PARTITIONS,5),
+ /* disk.partitions.read_bytes */ PMDA_PMID(CLUSTER_PARTITIONS,6),
+ /* disk.partitions.write_bytes */PMDA_PMID(CLUSTER_PARTITIONS,7),
+ /* disk.partitions.total_bytes */PMDA_PMID(CLUSTER_PARTITIONS,8),
+
+ /* hinv.ndisk */ PMDA_PMID(CLUSTER_STAT,33),
+
+ /* disk.dm.read */ PMDA_PMID(CLUSTER_DM,0),
+ /* disk.dm.write */ PMDA_PMID(CLUSTER_DM,1),
+ /* disk.dm.total */ PMDA_PMID(CLUSTER_DM,2),
+ /* disk.dm.blkread */ PMDA_PMID(CLUSTER_DM,3),
+ /* disk.dm.blkwrite */ PMDA_PMID(CLUSTER_DM,4),
+ /* disk.dm.blktotal */ PMDA_PMID(CLUSTER_DM,5),
+ /* disk.dm.read_bytes */ PMDA_PMID(CLUSTER_DM,6),
+ /* disk.dm.write_bytes */ PMDA_PMID(CLUSTER_DM,7),
+ /* disk.dm.total_bytes */ PMDA_PMID(CLUSTER_DM,8),
+ /* disk.dm.read_merge */ PMDA_PMID(CLUSTER_DM,9),
+ /* disk.dm.write_merge */ PMDA_PMID(CLUSTER_DM,10),
+ /* disk.dm.avactive */ PMDA_PMID(CLUSTER_DM,11),
+ /* disk.dm.aveq */ PMDA_PMID(CLUSTER_DM,12),
+ /* hinv.map.dmname */ PMDA_PMID(CLUSTER_DM,13),
+ /* disk.dm.read_rawactive */ PMDA_PMID(CLUSTER_DM,14),
+ /* disk.dm.write_rawactive */ PMDA_PMID(CLUSTER_DM,15),
+};
+
+int
+is_partitions_metric(pmID full_pmid)
+{
+ int i;
+ static pmID *p = NULL;
+ __pmID_int *idp = (__pmID_int *)&(full_pmid);
+ pmID pmid = PMDA_PMID(idp->cluster, idp->item);
+ int n = sizeof(disk_metric_table) / sizeof(disk_metric_table[0]);
+
+ if (p && *p == PMDA_PMID(idp->cluster, idp->item))
+ return 1;
+ for (p = disk_metric_table, i=0; i < n; i++, p++) {
+ if (*p == pmid)
+ return 1;
+ }
+ return 0;
+}
+
+char *
+_pm_ioscheduler(const char *device)
+{
+ FILE *fp;
+ char *p, *q;
+ static char buf[1024];
+ char path[MAXNAMELEN];
+
+ /*
+ * Extract scheduler from /sys/block/<device>/queue/scheduler.
+ * File format: "noop anticipatory [deadline] cfq"
+ * In older kernels (incl. RHEL5 and SLES10) this doesn't exist,
+ * but we can still look in /sys/block/<device>/queue/iosched to
+ * intuit the ones we know about (cfq, deadline, as, noop) based
+ * on the different status files they create.
+ */
+ sprintf(path, "%s/sys/block/%s/queue/scheduler", linux_statspath, device);
+ if ((fp = fopen(path, "r")) != NULL) {
+ p = fgets(buf, sizeof(buf), fp);
+ fclose(fp);
+ if (!p)
+ goto unknown;
+ for (p = q = buf; p && *p && *p != ']'; p++) {
+ if (*p == '[')
+ q = p+1;
+ }
+ if (q == buf)
+ goto unknown;
+ if (*p != ']')
+ goto unknown;
+ *p = '\0';
+ return q;
+ }
+ else {
+#define BLKQUEUE "%s/sys/block/%s/queue/"
+ /* sniff around, maybe we'll get lucky and find something */
+ sprintf(path, BLKQUEUE "iosched/quantum", linux_statspath, device);
+ if (access(path, F_OK) == 0)
+ return "cfq";
+ sprintf(path, BLKQUEUE "iosched/fifo_batch", linux_statspath, device);
+ if (access(path, F_OK) == 0)
+ return "deadline";
+ sprintf(path, BLKQUEUE "iosched/antic_expire", linux_statspath, device);
+ if (access(path, F_OK) == 0)
+ return "anticipatory";
+ /* punt. noop has no files to match on ... */
+ sprintf(path, BLKQUEUE "iosched", linux_statspath, device);
+ if (access(path, F_OK) == 0)
+ return "noop";
+ /* else fall though ... */
+#undef BLKQUEUE
+ }
+
+unknown:
+ return "unknown";
+}
+
+int
+proc_partitions_fetch(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int i;
+ partitions_entry_t *p = NULL;
+
+ if (inst != PM_IN_NULL) {
+ if (pmdaCacheLookup(mdesc->m_desc.indom, inst, NULL, (void **)&p) < 0)
+ return PM_ERR_INST;
+ }
+
+ switch (idp->cluster) {
+ case CLUSTER_STAT:
+ /*
+ * disk.{dev,all} remain in CLUSTER_STAT for backward compatibility
+ */
+ switch(idp->item) {
+ case 4: /* disk.dev.read */
+ if (p == NULL)
+ return PM_ERR_INST;
+ _pm_assign_ulong(atom, p->rd_ios);
+ break;
+ case 5: /* disk.dev.write */
+ if (p == NULL)
+ return PM_ERR_INST;
+ _pm_assign_ulong(atom, p->wr_ios);
+ break;
+ case 6: /* disk.dev.blkread */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->rd_sectors;
+ break;
+ case 7: /* disk.dev.blkwrite */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->wr_sectors;
+ break;
+ case 28: /* disk.dev.total */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->rd_ios + p->wr_ios;
+ break;
+ case 36: /* disk.dev.blktotal */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->rd_sectors + p->wr_sectors;
+ break;
+ case 38: /* disk.dev.read_bytes */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->rd_sectors / 2;
+ break;
+ case 39: /* disk.dev.write_bytes */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = p->wr_sectors / 2;
+ break;
+ case 40: /* disk.dev.total_bytes */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ull = (p->rd_sectors + p->wr_sectors) / 2;
+ break;
+ case 46: /* disk.dev.avactive ... already msec from /proc/diskstats */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ul = p->io_ticks;
+ break;
+ case 47: /* disk.dev.aveq ... already msec from /proc/diskstats */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ul = p->aveq;
+ break;
+ case 49: /* disk.dev.read_merge */
+ if (p == NULL)
+ return PM_ERR_INST;
+ _pm_assign_ulong(atom, p->rd_merges);
+ break;
+ case 50: /* disk.dev.write_merge */
+ if (p == NULL)
+ return PM_ERR_INST;
+ _pm_assign_ulong(atom, p->wr_merges);
+ break;
+ case 59: /* disk.dev.scheduler */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->cp = _pm_ioscheduler(p->namebuf);
+ break;
+ case 72: /* disk.dev.read_rawactive already ms from /proc/diskstats */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ul = p->rd_ticks;
+ break;
+ case 73: /* disk.dev.write_rawactive already ms from /proc/diskstats */
+ if (p == NULL)
+ return PM_ERR_INST;
+ atom->ul = p->wr_ticks;
+ break;
+ default:
+ /* disk.all.* is a singular instance domain */
+ atom->ull = 0;
+ for (pmdaCacheOp(INDOM(DISK_INDOM), PMDA_CACHE_WALK_REWIND);;) {
+ if ((i = pmdaCacheOp(INDOM(DISK_INDOM), PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ if (!pmdaCacheLookup(INDOM(DISK_INDOM), i, NULL, (void **)&p) || !p)
+ continue;
+ switch (idp->item) {
+ case 24: /* disk.all.read */
+ atom->ull += p->rd_ios;
+ break;
+ case 25: /* disk.all.write */
+ atom->ull += p->wr_ios;
+ break;
+ case 26: /* disk.all.blkread */
+ atom->ull += p->rd_sectors;
+ break;
+ case 27: /* disk.all.blkwrite */
+ atom->ull += p->wr_sectors;
+ break;
+ case 29: /* disk.all.total */
+ atom->ull += p->rd_ios + p->wr_ios;
+ break;
+ case 37: /* disk.all.blktotal */
+ atom->ull += p->rd_sectors + p->wr_sectors;
+ break;
+ case 41: /* disk.all.read_bytes */
+ atom->ull += p->rd_sectors / 2;
+ break;
+ case 42: /* disk.all.write_bytes */
+ atom->ull += p->wr_sectors / 2;
+ break;
+ case 43: /* disk.all.total_bytes */
+ atom->ull += (p->rd_sectors + p->wr_sectors) / 2;
+ break;
+ case 44: /* disk.all.avactive ... already msec from /proc/diskstats */
+ atom->ull += p->io_ticks;
+ break;
+ case 45: /* disk.all.aveq ... already msec from /proc/diskstats */
+ atom->ull += p->aveq;
+ break;
+ case 51: /* disk.all.read_merge */
+ atom->ull += p->rd_merges;
+ break;
+ case 52: /* disk.all.write_merge */
+ atom->ull += p->wr_merges;
+ break;
+ case 74: /* disk.all.read_rawactive ... already msec from /proc/diskstats */
+ atom->ull += p->rd_ticks;
+ break;
+ case 75: /* disk.all.write_rawactive ... already msec from /proc/diskstats */
+ atom->ull += p->wr_ticks;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ } /* loop */
+ }
+ break;
+
+ case CLUSTER_PARTITIONS:
+ if (p == NULL)
+ return PM_ERR_INST;
+ switch(idp->item) {
+ /* disk.partitions */
+ case 0: /* disk.partitions.read */
+ atom->ul = p->rd_ios;
+ break;
+ case 1: /* disk.partitions.write */
+ atom->ul = p->wr_ios;
+ break;
+ case 2: /* disk.partitions.total */
+ atom->ul = p->wr_ios +
+ p->rd_ios;
+ break;
+ case 3: /* disk.partitions.blkread */
+ atom->ul = p->rd_sectors;
+ break;
+ case 4: /* disk.partitions.blkwrite */
+ atom->ul = p->wr_sectors;
+ break;
+ case 5: /* disk.partitions.blktotal */
+ atom->ul = p->rd_sectors +
+ p->wr_sectors;
+ break;
+ case 6: /* disk.partitions.read_bytes */
+ atom->ul = p->rd_sectors / 2;
+ break;
+ case 7: /* disk.partitions.write_bytes */
+ atom->ul = p->wr_sectors / 2;
+ break;
+ case 8: /* disk.partitions.total_bytes */
+ atom->ul = (p->rd_sectors +
+ p->wr_sectors) / 2;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_DM:
+ if (p == NULL)
+ return PM_ERR_INST;
+ switch(idp->item) {
+ case 0: /* disk.dm.read */
+ atom->ull = p->rd_ios;
+ break;
+ case 1: /* disk.dm.write */
+ atom->ull = p->wr_ios;
+ break;
+ case 2: /* disk.dm.total */
+ atom->ull = p->rd_ios + p->wr_ios;
+ break;
+ case 3: /* disk.dm.blkread */
+ atom->ull = p->rd_sectors;
+ break;
+ case 4: /* disk.dm.blkwrite */
+ atom->ull = p->wr_sectors;
+ break;
+ case 5: /* disk.dm.blktotal */
+ atom->ull = p->rd_sectors + p->wr_sectors;
+ break;
+ case 6: /* disk.dm.read_bytes */
+ atom->ull = p->rd_sectors / 2;
+ break;
+ case 7: /* disk.dm.write_bytes */
+ atom->ull = p->wr_sectors / 2;
+ break;
+ case 8: /* disk.dm.total_bytes */
+ atom->ull = (p->rd_sectors + p->wr_sectors) / 2;
+ break;
+ case 9: /* disk.dm.read_merge */
+ atom->ull = p->rd_merges;
+ break;
+ case 10: /* disk.dm.write_merge */
+ atom->ull = p->wr_merges;
+ break;
+ case 11: /* disk.dm.avactive */
+ atom->ull = p->io_ticks;
+ break;
+ case 12: /* disk.dm.aveq */
+ atom->ull = p->aveq;
+ break;
+ case 13: /* hinv.map.dmname */
+ atom->cp = p->dmname;
+ break;
+ case 14: /* disk.dm.read_rawactive */
+ atom->ul = p->rd_ticks;
+ break;
+ case 15: /* disk.dm.write_rawactive */
+ atom->ul = p->wr_ticks;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ default: /* switch cluster */
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/linux/proc_partitions.h b/src/pmdas/linux/proc_partitions.h
new file mode 100644
index 0000000..9348cde
--- /dev/null
+++ b/src/pmdas/linux/proc_partitions.h
@@ -0,0 +1,44 @@
+/*
+ * Linux /proc/partitions metrics cluster
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ int id;
+ unsigned int major;
+ unsigned int minor;
+ unsigned long nr_blocks;
+ char *namebuf; /* from /proc/{partitions,diskstats} */
+ char *udevnamebuf; /* from udev if we have it, else NULL */
+ char *dmname; /* symlink from /dev/mapper, else NULL */
+ unsigned long rd_ios;
+ unsigned long rd_merges;
+ unsigned long long rd_sectors;
+ unsigned int rd_ticks;
+ unsigned long wr_ios;
+ unsigned long wr_merges;
+ unsigned long long wr_sectors;
+ unsigned int wr_ticks;
+ unsigned int ios_in_flight;
+ unsigned int io_ticks;
+ unsigned int aveq;
+} partitions_entry_t;
+
+extern int refresh_proc_partitions(pmInDom disk_indom, pmInDom partitions_indom, pmInDom dm_indom);
+extern int is_partitions_metric(pmID);
+extern int proc_partitions_fetch(pmdaMetric *, unsigned int, pmAtomValue *);
diff --git a/src/pmdas/linux/proc_scsi.c b/src/pmdas/linux/proc_scsi.c
new file mode 100644
index 0000000..2b3c952
--- /dev/null
+++ b/src/pmdas/linux/proc_scsi.c
@@ -0,0 +1,159 @@
+/*
+ * Linux Scsi Devices Cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_scsi.h"
+
+static char diskname[64];
+static char tapename[64];
+static char cdromname[64];
+
+int
+refresh_proc_scsi(proc_scsi_t *scsi)
+{
+ char buf[1024];
+ char name[1024];
+ int i;
+ int n;
+ FILE *fp;
+ char *sp;
+ static int have_devfs = -1;
+ static int next_id = -1;
+
+ if (next_id < 0) {
+ /* one trip initialization */
+ next_id = 0;
+
+ scsi->nscsi = 0;
+ scsi->scsi = (scsi_entry_t *)malloc(sizeof(scsi_entry_t));
+
+ /* scsi indom */
+ scsi->scsi_indom->it_numinst = 0;
+ scsi->scsi_indom->it_set = (pmdaInstid *)malloc(sizeof(pmdaInstid));
+
+ /* devfs naming convention */
+ have_devfs = access("/dev/.devfsd", F_OK) == 0;
+ if (have_devfs) {
+ strcpy(diskname, "scsi/host%d/bus%d/target%d/lun%d/disc");
+ strcpy(tapename, "st0");
+ strcpy(cdromname, "scd0");
+ }
+ else {
+ strcpy(diskname, "sda");
+ strcpy(tapename, "st0");
+ strcpy(cdromname, "scd0");
+ }
+ }
+
+ if ((fp = linux_statsfile("/proc/scsi/scsi", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ scsi_entry_t x = { 0 };
+
+ if (strncmp(buf, "Host:", 5) != 0)
+ continue;
+
+ n = sscanf(buf, "Host: scsi%d Channel: %d Id: %d Lun: %d",
+ &x.dev_host, &x.dev_channel, &x.dev_id, &x.dev_lun);
+ if (n != 4)
+ continue;
+ for (i=0; i < scsi->nscsi; i++) {
+ if (scsi->scsi[i].dev_host == x.dev_host &&
+ scsi->scsi[i].dev_channel == x.dev_channel &&
+ scsi->scsi[i].dev_id == x.dev_id &&
+ scsi->scsi[i].dev_lun == x.dev_lun)
+ break;
+ }
+
+ if (i == scsi->nscsi) {
+ scsi->nscsi++;
+ scsi->scsi = (scsi_entry_t *)realloc(scsi->scsi,
+ scsi->nscsi * sizeof(scsi_entry_t));
+ memcpy(&scsi->scsi[i], &x, sizeof(scsi_entry_t));
+ scsi->scsi[i].id = next_id++;
+ /* scan for the Vendor: and Type: strings */
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((sp = strstr(buf, "Type:")) != (char *)NULL) {
+ if (sscanf(sp, "Type: %s", name) == 1)
+ scsi->scsi[i].dev_type = strdup(name);
+ else
+ scsi->scsi[i].dev_type = strdup("unknown");
+ break;
+ }
+ }
+
+ if (strcmp(scsi->scsi[i].dev_type, "Direct-Access") == 0) {
+ if (have_devfs) {
+ scsi->scsi[i].dev_name = (char *)malloc(64);
+ sprintf(scsi->scsi[i].dev_name, diskname,
+ scsi->scsi[i].dev_host, scsi->scsi[i].dev_channel,
+ scsi->scsi[i].dev_id, scsi->scsi[i].dev_lun);
+ }
+ else {
+ scsi->scsi[i].dev_name = strdup(diskname);
+ diskname[2]++; /* sd[a-z] bump to next disk device name */
+ }
+ }
+ else
+ if (strcmp(scsi->scsi[i].dev_type, "Sequential-Access") == 0) {
+ scsi->scsi[i].dev_name = strdup(tapename);
+ tapename[2]++; /* st[0-9] bump to next tape device name */
+ }
+ else
+ if (strcmp(scsi->scsi[i].dev_type, "CD-ROM") == 0) {
+ scsi->scsi[i].dev_name = strdup(cdromname);
+ cdromname[3]++; /* scd[0-9] bump to next CDROM device name */
+ }
+ else
+ if (strcmp(scsi->scsi[i].dev_type, "Processor") == 0)
+ scsi->scsi[i].dev_name = strdup("SCSI Controller");
+ else
+ scsi->scsi[i].dev_name = strdup("Unknown SCSI device");
+
+ sprintf(name, "scsi%d:%d:%d:%d %s", scsi->scsi[i].dev_host,
+ scsi->scsi[i].dev_channel, scsi->scsi[i].dev_id, scsi->scsi[i].dev_lun, scsi->scsi[i].dev_type);
+ scsi->scsi[i].namebuf = strdup(name);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_proc_scsi: add host=scsi%d channel=%d id=%d lun=%d type=%s\n",
+ scsi->scsi[i].dev_host, scsi->scsi[i].dev_channel,
+ scsi->scsi[i].dev_id, scsi->scsi[i].dev_lun,
+ scsi->scsi[i].dev_type);
+ }
+#endif
+ }
+ }
+
+ /* refresh scsi indom */
+ if (scsi->scsi_indom->it_numinst != scsi->nscsi) {
+ scsi->scsi_indom->it_numinst = scsi->nscsi;
+ scsi->scsi_indom->it_set = (pmdaInstid *)realloc(scsi->scsi_indom->it_set,
+ scsi->nscsi * sizeof(pmdaInstid));
+ memset(scsi->scsi_indom->it_set, 0, scsi->nscsi * sizeof(pmdaInstid));
+ }
+ for (i=0; i < scsi->nscsi; i++) {
+ scsi->scsi_indom->it_set[i].i_inst = scsi->scsi[i].id;
+ scsi->scsi_indom->it_set[i].i_name = scsi->scsi[i].namebuf;
+ }
+
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_scsi.h b/src/pmdas/linux/proc_scsi.h
new file mode 100644
index 0000000..2ede392
--- /dev/null
+++ b/src/pmdas/linux/proc_scsi.h
@@ -0,0 +1,38 @@
+/*
+ * Linux /proc/scsi/scsi metrics cluster
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ int id; /* internal instance id */
+ char *namebuf; /* external name, i.e. host:channel:id:lun */
+ int dev_host;
+ int dev_channel;
+ int dev_id;
+ int dev_lun;
+ char *dev_type;
+ char *dev_name;
+} scsi_entry_t;
+
+typedef struct {
+ int nscsi;
+ scsi_entry_t *scsi;
+ pmdaIndom *scsi_indom;
+} proc_scsi_t;
+
+extern int refresh_proc_scsi(proc_scsi_t *);
diff --git a/src/pmdas/linux/proc_slabinfo.c b/src/pmdas/linux/proc_slabinfo.c
new file mode 100644
index 0000000..28dde3f
--- /dev/null
+++ b/src/pmdas/linux/proc_slabinfo.c
@@ -0,0 +1,237 @@
+/*
+ * Linux Memory Slab Cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_slabinfo.h"
+
+int
+refresh_proc_slabinfo(proc_slabinfo_t *slabinfo)
+{
+ char buf[1024];
+ slab_cache_t sbuf;
+ slab_cache_t *s;
+ FILE *fp;
+ int i, n;
+ int instcount;
+ char *w, *p;
+ int old_cache;
+ int err = 0;
+ static int next_id = -1;
+ static int major_version = -1;
+ static int minor_version = 0;
+
+ if (next_id < 0) {
+ /* one trip initialization */
+ next_id = 0;
+
+ slabinfo->ncaches = 0;
+ slabinfo->caches = (slab_cache_t *)malloc(sizeof(slab_cache_t));
+ slabinfo->indom->it_numinst = 0;
+ slabinfo->indom->it_set = (pmdaInstid *)malloc(sizeof(pmdaInstid));
+ }
+
+ if ((fp = linux_statsfile("/proc/slabinfo", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ for (i=0; i < slabinfo->ncaches; i++)
+ slabinfo->caches[i].seen = 0;
+
+ /* skip header */
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ /* oops, no header! */
+ err = -oserror();
+ goto out;
+ }
+
+ if (major_version < 0) {
+ major_version = minor_version = 0;
+ if (strstr(buf, "slabinfo - version:")) {
+ char *p;
+ for (p=buf; *p; p++) {
+ if (isdigit((int)*p)) {
+ sscanf(p, "%d.%d", &major_version, &minor_version);
+ break;
+ }
+ }
+ }
+ }
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ /* try to convert whitespace in cache names to underscores, */
+ /* by looking for alphabetic chars which follow whitespace. */
+ if (buf[0] == '#')
+ continue;
+ for (w = NULL, p = buf; *p != '\0'; p++) {
+ if (isspace((int)*p))
+ w = p;
+ else if (isdigit((int)*p))
+ break;
+ else if (isalpha((int)*p) && w) {
+ for (; w && w != p; w++)
+ *w = '_';
+ w = NULL;
+ }
+ }
+
+ memset(&sbuf, 0, sizeof(slab_cache_t));
+
+ if (major_version == 1 && minor_version == 0) {
+ /*
+ * <name> <active_objs> <num_objs>
+ * (generally 2.2 kernels)
+ */
+ n = sscanf(buf, "%s %lu %lu", sbuf.name,
+ (unsigned long *)&sbuf.num_active_objs,
+ (unsigned long *)&sbuf.total_objs);
+ if (n != 3) {
+ err = PM_ERR_APPVERSION;
+ goto out;
+ }
+ }
+ else if (major_version == 1 && minor_version == 1) {
+ /*
+ * <name> <active_objs> <num_objs> <objsize> <active_slabs> <num_slabs> <pagesperslab>
+ * (generally 2.4 kernels)
+ */
+ n = sscanf(buf, "%s %lu %lu %u %u %u %u", sbuf.name,
+ (unsigned long *)&sbuf.num_active_objs,
+ (unsigned long *)&sbuf.total_objs,
+ &sbuf.object_size,
+ &sbuf.num_active_slabs,
+ &sbuf.total_slabs,
+ &sbuf.pages_per_slab);
+ if (n != 7) {
+ err = PM_ERR_APPVERSION;
+ goto out;
+ }
+
+ sbuf.total_size = sbuf.pages_per_slab * sbuf.num_active_slabs * _pm_system_pagesize;
+ }
+ else if (major_version == 2 && minor_version >= 0 && minor_version <= 1) {
+ /*
+ * <name> <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> .. and more
+ * (generally for kernels up to at least 2.6.11)
+ */
+ n = sscanf(buf, "%s %lu %lu %u %u %u", sbuf.name,
+ (unsigned long *)&sbuf.num_active_objs,
+ (unsigned long *)&sbuf.total_objs,
+ &sbuf.object_size,
+ &sbuf.objects_per_slab,
+ &sbuf.pages_per_slab);
+ if (n != 6) {
+ err = PM_ERR_APPVERSION;
+ goto out;
+ }
+
+ sbuf.total_size = sbuf.pages_per_slab * sbuf.num_active_objs * _pm_system_pagesize / sbuf.objects_per_slab;
+ }
+ else {
+ /* no support */
+ err = PM_ERR_APPVERSION;
+ goto out;
+ }
+
+ old_cache = -1;
+ for (i=0; i < slabinfo->ncaches; i++) {
+ if (strcmp(slabinfo->caches[i].name, sbuf.name) == 0) {
+ if (slabinfo->caches[i].valid)
+ break;
+ else
+ old_cache = i;
+ }
+ }
+
+ if (i == slabinfo->ncaches) {
+ /* new cache has appeared */
+ if (old_cache >= 0) {
+ /* same cache as last time : reuse the id */
+ i = old_cache;
+ }
+ else {
+ slabinfo->ncaches++;
+ slabinfo->caches = (slab_cache_t *)realloc(slabinfo->caches,
+ slabinfo->ncaches * sizeof(slab_cache_t));
+ slabinfo->caches[i].id = next_id++;
+ }
+ slabinfo->caches[i].valid = 1;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_slabinfo: add \"%s\"\n", sbuf.name);
+ }
+#endif
+ }
+
+ s = &slabinfo->caches[i];
+ strcpy(s->name, sbuf.name);
+ s->num_active_objs = sbuf.num_active_objs;
+ s->total_objs = sbuf.total_objs;
+ s->object_size = sbuf.object_size;
+ s->num_active_slabs = sbuf.num_active_slabs;
+ s->total_slabs = sbuf.total_slabs;
+ s->pages_per_slab = sbuf.pages_per_slab;
+ s->objects_per_slab = sbuf.objects_per_slab;
+ s->total_size = sbuf.total_size;
+
+ s->seen = major_version * 10 + minor_version;
+ }
+
+ /* check for caches that have been deleted (eg. by rmmod) */
+ for (i=0, instcount=0; i < slabinfo->ncaches; i++) {
+ if (slabinfo->caches[i].valid) {
+ if (slabinfo->caches[i].seen == 0) {
+ slabinfo->caches[i].valid = 0;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_slabinfo: drop \"%s\"\n", slabinfo->caches[i].name);
+ }
+#endif
+ }
+ else {
+ instcount++;
+ }
+ }
+ }
+
+ /* refresh slabinfo indom */
+ if (slabinfo->indom->it_numinst != instcount) {
+ slabinfo->indom->it_numinst = instcount;
+ slabinfo->indom->it_set = (pmdaInstid *)realloc(slabinfo->indom->it_set,
+ instcount * sizeof(pmdaInstid));
+ memset(slabinfo->indom->it_set, 0, instcount * sizeof(pmdaInstid));
+ }
+ for (n=0, i=0; i < slabinfo->ncaches; i++) {
+ if (slabinfo->caches[i].valid) {
+ slabinfo->indom->it_set[n].i_inst = slabinfo->caches[i].id;
+ slabinfo->indom->it_set[n].i_name = slabinfo->caches[i].name;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_slabinfo: cache[%d] = \"%s\"\n",
+ n, slabinfo->indom->it_set[n].i_name);
+ }
+#endif
+ n++;
+ }
+ }
+
+out:
+ fclose(fp);
+ return err;
+}
diff --git a/src/pmdas/linux/proc_slabinfo.h b/src/pmdas/linux/proc_slabinfo.h
new file mode 100644
index 0000000..92d4e9c
--- /dev/null
+++ b/src/pmdas/linux/proc_slabinfo.h
@@ -0,0 +1,53 @@
+/*
+ * Linux /proc/slabinfo metrics cluster
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*** version 1.1
+ "cache-name" num-active-objs total-objs object-size num-active-slabs \
+ total-slabs num-pages-per-slab
+ + further values (not exported) on SMP and with statistics enabled
+ *** version 1.0
+ "cache-name" num-active-objs total-objs
+ ***/
+
+typedef struct {
+ int id;
+ int seen; /* have seen this time, and num values seen */
+ int valid;
+ char name[64];
+ __uint64_t num_active_objs;
+ __uint64_t total_objs;
+ __uint32_t object_size;
+ __uint64_t total_size;
+ __uint32_t num_active_slabs;
+ __uint32_t objects_per_slab;
+ __uint32_t total_slabs;
+ __uint32_t pages_per_slab;
+} slab_cache_t;
+
+typedef struct {
+ int ncaches;
+ slab_cache_t *caches;
+ pmdaIndom *indom;
+} proc_slabinfo_t;
+
+extern size_t _pm_system_pagesize;
+
+extern int refresh_proc_slabinfo(proc_slabinfo_t *);
+
diff --git a/src/pmdas/linux/proc_stat.c b/src/pmdas/linux/proc_stat.c
new file mode 100644
index 0000000..6a8a798
--- /dev/null
+++ b/src/pmdas/linux/proc_stat.c
@@ -0,0 +1,304 @@
+/*
+ * Linux /proc/stat metrics cluster
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include <dirent.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include "proc_cpuinfo.h"
+#include "proc_stat.h"
+
+int
+refresh_proc_stat(proc_cpuinfo_t *proc_cpuinfo, proc_stat_t *proc_stat)
+{
+ pmdaIndom *idp = PMDAINDOM(CPU_INDOM);
+ char buf[MAXPATHLEN];
+ char fmt[64];
+ static int fd = -1; /* kept open until exit() */
+ static int started;
+ static char *statbuf;
+ static int maxstatbuf;
+ static char **bufindex;
+ static int nbufindex;
+ static int maxbufindex;
+ int size;
+ int n;
+ int i;
+ int j;
+
+ if (fd >= 0) {
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return -oserror();
+ } else {
+ snprintf(buf, sizeof(buf), "%s/proc/stat", linux_statspath);
+ if ((fd = open(buf, O_RDONLY)) < 0)
+ return -oserror();
+ }
+
+ for (n=0;;) {
+ while (n >= maxstatbuf) {
+ size = maxstatbuf + 512;
+ if ((statbuf = (char *)realloc(statbuf, size)) == NULL)
+ return -ENOMEM;
+ maxstatbuf = size;
+ }
+ size = (statbuf + maxstatbuf) - (statbuf + n);
+ if ((i = read(fd, statbuf + n, size)) > 0)
+ n += i;
+ else
+ break;
+ }
+ statbuf[n] = '\0';
+
+ if (bufindex == NULL) {
+ size = 4 * sizeof(char *);
+ if ((bufindex = (char **)malloc(size)) == NULL)
+ return -ENOMEM;
+ maxbufindex = 4;
+ }
+
+ nbufindex = 0;
+ bufindex[nbufindex] = statbuf;
+ for (i=0; i < n; i++) {
+ if (statbuf[i] == '\n' || statbuf[i] == '\0') {
+ statbuf[i] = '\0';
+ if (nbufindex + 1 >= maxbufindex) {
+ size = (maxbufindex + 4) * sizeof(char *);
+ if ((bufindex = (char **)realloc(bufindex, size)) == NULL)
+ return -ENOMEM;
+ maxbufindex += 4;
+ }
+ bufindex[++nbufindex] = statbuf + i + 1;
+ }
+ }
+
+ if (!started) {
+ started = 1;
+ memset(proc_stat, 0, sizeof(*proc_stat));
+
+ /* hz of running kernel */
+ proc_stat->hz = sysconf(_SC_CLK_TCK);
+
+ /* scan ncpus */
+ for (i=0; i < nbufindex; i++) {
+ if (strncmp("cpu", bufindex[i], 3) == 0 && isdigit((int)bufindex[i][3]))
+ proc_stat->ncpu++;
+ }
+ if (proc_stat->ncpu == 0)
+ proc_stat->ncpu = 1; /* non-SMP kernel? */
+ proc_stat->cpu_indom = idp;
+ proc_stat->cpu_indom->it_numinst = proc_stat->ncpu;
+ proc_stat->cpu_indom->it_set = (pmdaInstid *)malloc(
+ proc_stat->ncpu * sizeof(pmdaInstid));
+ /*
+ * Map out the CPU instance domain.
+ *
+ * The first call to cpu_name() does initialization on the
+ * proc_cpuinfo structure.
+ */
+ for (i=0; i < proc_stat->ncpu; i++) {
+ proc_stat->cpu_indom->it_set[i].i_inst = i;
+ proc_stat->cpu_indom->it_set[i].i_name = cpu_name(proc_cpuinfo, i);
+ }
+
+ n = proc_stat->ncpu * sizeof(unsigned long long);
+ proc_stat->p_user = (unsigned long long *)calloc(1, n);
+ proc_stat->p_nice = (unsigned long long *)calloc(1, n);
+ proc_stat->p_sys = (unsigned long long *)calloc(1, n);
+ proc_stat->p_idle = (unsigned long long *)calloc(1, n);
+ proc_stat->p_wait = (unsigned long long *)calloc(1, n);
+ proc_stat->p_irq = (unsigned long long *)calloc(1, n);
+ proc_stat->p_sirq = (unsigned long long *)calloc(1, n);
+ proc_stat->p_steal = (unsigned long long *)calloc(1, n);
+ proc_stat->p_guest = (unsigned long long *)calloc(1, n);
+
+ n = proc_cpuinfo->node_indom->it_numinst * sizeof(unsigned long long);
+ proc_stat->n_user = calloc(1, n);
+ proc_stat->n_nice = calloc(1, n);
+ proc_stat->n_sys = calloc(1, n);
+ proc_stat->n_idle = calloc(1, n);
+ proc_stat->n_wait = calloc(1, n);
+ proc_stat->n_irq = calloc(1, n);
+ proc_stat->n_sirq = calloc(1, n);
+ proc_stat->n_steal = calloc(1, n);
+ proc_stat->n_guest = calloc(1, n);
+ }
+ else {
+ /* reset per-node stats */
+ n = proc_cpuinfo->node_indom->it_numinst * sizeof(unsigned long long);
+ memset(proc_stat->n_user, 0, n);
+ memset(proc_stat->n_nice, 0, n);
+ memset(proc_stat->n_sys, 0, n);
+ memset(proc_stat->n_idle, 0, n);
+ memset(proc_stat->n_wait, 0, n);
+ memset(proc_stat->n_irq, 0, n);
+ memset(proc_stat->n_sirq, 0, n);
+ memset(proc_stat->n_steal, 0, n);
+ memset(proc_stat->n_guest, 0, n);
+ }
+ /*
+ * cpu 95379 4 20053 6502503
+ * 2.6 kernels have 3 additional fields
+ * for wait, irq and soft_irq.
+ */
+ strcpy(fmt, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu");
+ n = sscanf((const char *)bufindex[0], fmt,
+ &proc_stat->user, &proc_stat->nice,
+ &proc_stat->sys, &proc_stat->idle,
+ &proc_stat->wait, &proc_stat->irq,
+ &proc_stat->sirq, &proc_stat->steal,
+ &proc_stat->guest);
+
+ /*
+ * per-cpu stats
+ * e.g. cpu0 95379 4 20053 6502503
+ * 2.6 kernels have 3 additional fields for wait, irq and soft_irq.
+ * More recent (2008) 2.6 kernels have an extra field for guest.
+ */
+ if (proc_stat->ncpu == 1) {
+ /*
+ * Don't bother scanning - the counters are the same
+ * as for "all" cpus, as already scanned above.
+ * This also handles the non-SMP code where
+ * there is no line starting with "cpu0".
+ */
+ proc_stat->p_user[0] = proc_stat->user;
+ proc_stat->p_nice[0] = proc_stat->nice;
+ proc_stat->p_sys[0] = proc_stat->sys;
+ proc_stat->p_idle[0] = proc_stat->idle;
+ proc_stat->p_wait[0] = proc_stat->wait;
+ proc_stat->p_irq[0] = proc_stat->irq;
+ proc_stat->p_sirq[0] = proc_stat->sirq;
+ proc_stat->p_steal[0] = proc_stat->steal;
+ proc_stat->p_guest[0] = proc_stat->guest;
+ }
+ else {
+ strcpy(fmt, "cpu%d %llu %llu %llu %llu %llu %llu %llu %llu %llu");
+ for (i=0; i < proc_stat->ncpu; i++) {
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp("cpu", bufindex[j], 3) == 0 && isdigit((int)bufindex[j][3])) {
+ int c;
+ int cpunum = atoi(&bufindex[j][3]);
+ int node;
+ if (cpunum >= 0 && cpunum < proc_stat->ncpu) {
+ n = sscanf(bufindex[j], fmt, &c,
+ &proc_stat->p_user[cpunum],
+ &proc_stat->p_nice[cpunum],
+ &proc_stat->p_sys[cpunum],
+ &proc_stat->p_idle[cpunum],
+ &proc_stat->p_wait[cpunum],
+ &proc_stat->p_irq[cpunum],
+ &proc_stat->p_sirq[cpunum],
+ &proc_stat->p_steal[cpunum],
+ &proc_stat->p_guest[cpunum]);
+ if ((node = proc_cpuinfo->cpuinfo[cpunum].node) != -1) {
+ proc_stat->n_user[node] += proc_stat->p_user[cpunum];
+ proc_stat->n_nice[node] += proc_stat->p_nice[cpunum];
+ proc_stat->n_sys[node] += proc_stat->p_sys[cpunum];
+ proc_stat->n_idle[node] += proc_stat->p_idle[cpunum];
+ proc_stat->n_wait[node] += proc_stat->p_wait[cpunum];
+ proc_stat->n_irq[node] += proc_stat->p_irq[cpunum];
+ proc_stat->n_sirq[node] += proc_stat->p_sirq[cpunum];
+ proc_stat->n_steal[node] += proc_stat->p_steal[cpunum];
+ proc_stat->n_guest[node] += proc_stat->p_guest[cpunum];
+ }
+ }
+ }
+ }
+ if (j == nbufindex)
+ break;
+ }
+ }
+
+ /*
+ * page 59739 34786
+ * Note: this has moved to /proc/vmstat in 2.6 kernels
+ */
+ strcpy(fmt, "page %u %u");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 5) == 0) {
+ sscanf((const char *)bufindex[j], fmt,
+ &proc_stat->page[0], &proc_stat->page[1]);
+ break;
+ }
+ }
+
+ /*
+ * swap 0 1
+ * Note: this has moved to /proc/vmstat in 2.6 kernels
+ */
+ strcpy(fmt, "swap %u %u");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 5) == 0) {
+ sscanf((const char *)bufindex[j], fmt,
+ &proc_stat->swap[0], &proc_stat->swap[1]);
+ break;
+ }
+ }
+
+ /*
+ * intr 32845463 24099228 2049 0 2 ....
+ * (just export the first number, which is total interrupts)
+ */
+ strcpy(fmt, "intr %llu");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 5) == 0) {
+ sscanf((const char *)bufindex[j], fmt, &proc_stat->intr);
+ break;
+ }
+ }
+
+ /*
+ * ctxt 1733480
+ */
+ strcpy(fmt, "ctxt %llu");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 5) == 0) {
+ sscanf((const char *)bufindex[j], fmt, &proc_stat->ctxt);
+ break;
+ }
+ }
+
+ /*
+ * btime 1733480
+ */
+ strcpy(fmt, "btime %lu");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 6) == 0) {
+ sscanf((const char *)bufindex[j], fmt, &proc_stat->btime);
+ break;
+ }
+ }
+
+ /*
+ * processes 2213
+ */
+ strcpy(fmt, "processes %lu");
+ for (j=0; j < nbufindex; j++) {
+ if (strncmp(fmt, bufindex[j], 10) == 0) {
+ sscanf((const char *)bufindex[j], fmt, &proc_stat->processes);
+ break;
+ }
+ }
+
+ /* success */
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_stat.h b/src/pmdas/linux/proc_stat.h
new file mode 100644
index 0000000..78b2c09
--- /dev/null
+++ b/src/pmdas/linux/proc_stat.h
@@ -0,0 +1,65 @@
+/*
+ * Linux /proc/stat metrics cluster
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ unsigned long long user;
+ unsigned long long sys;
+ unsigned long long nice;
+ unsigned long long idle;
+ unsigned long long wait;
+ unsigned long long irq;
+ unsigned long long sirq;
+ unsigned long long steal;
+ unsigned long long guest;
+ unsigned int ncpu;
+ /* per-cpu */
+ unsigned long long *p_user;
+ unsigned long long *p_sys;
+ unsigned long long *p_nice;
+ unsigned long long *p_idle;
+ unsigned long long *p_wait;
+ unsigned long long *p_irq;
+ unsigned long long *p_sirq;
+ unsigned long long *p_steal;
+ unsigned long long *p_guest;
+ /* per-node */
+ unsigned long long *n_user;
+ unsigned long long *n_sys;
+ unsigned long long *n_nice;
+ unsigned long long *n_idle;
+ unsigned long long *n_wait;
+ unsigned long long *n_irq;
+ unsigned long long *n_sirq;
+ unsigned long long *n_steal;
+ unsigned long long *n_guest;
+
+ unsigned int ndisk;
+ unsigned int page[2]; /* unused in 2.6 now in /proc/vmstat */
+ unsigned int swap[2]; /* unused in 2.6 now in /proc/vmstat */
+ unsigned long long intr;
+ unsigned long long ctxt;
+ unsigned long btime;
+ unsigned long processes;
+ pmdaIndom *cpu_indom;
+ unsigned int hz;
+} proc_stat_t;
+
+extern int refresh_proc_stat(proc_cpuinfo_t *, proc_stat_t *);
diff --git a/src/pmdas/linux/proc_sys_fs.c b/src/pmdas/linux/proc_sys_fs.c
new file mode 100644
index 0000000..021ae23
--- /dev/null
+++ b/src/pmdas/linux/proc_sys_fs.c
@@ -0,0 +1,80 @@
+/*
+ * Linux /proc/sys/fs metrics cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_sys_fs.h"
+
+int
+refresh_proc_sys_fs(proc_sys_fs_t *proc_sys_fs)
+{
+ static int err_reported;
+ char buf[MAXPATHLEN];
+ FILE *filesp = NULL;
+ FILE *inodep = NULL;
+ FILE *dentryp = NULL;
+
+ memset(proc_sys_fs, 0, sizeof(proc_sys_fs_t));
+
+ if ((filesp = linux_statsfile("/proc/sys/fs/file-nr", buf, sizeof(buf))) == NULL ||
+ (inodep = linux_statsfile("/proc/sys/fs/inode-state", buf, sizeof(buf))) == NULL ||
+ (dentryp = linux_statsfile("/proc/sys/fs/dentry-state", buf, sizeof(buf))) == NULL) {
+ proc_sys_fs->errcode = -oserror();
+ if (err_reported == 0)
+ fprintf(stderr, "Warning: vfs metrics are not available : %s\n",
+ osstrerror());
+ }
+ else {
+ proc_sys_fs->errcode = 0;
+ if (fscanf(filesp, "%d %d %d",
+ &proc_sys_fs->fs_files_count,
+ &proc_sys_fs->fs_files_free,
+ &proc_sys_fs->fs_files_max) != 3)
+ proc_sys_fs->errcode = PM_ERR_VALUE;
+ if (fscanf(inodep, "%d %d",
+ &proc_sys_fs->fs_inodes_count,
+ &proc_sys_fs->fs_inodes_free) != 2)
+ proc_sys_fs->errcode = PM_ERR_VALUE;
+ if (fscanf(dentryp, "%d %d",
+ &proc_sys_fs->fs_dentry_count,
+ &proc_sys_fs->fs_dentry_free) != 2)
+ proc_sys_fs->errcode = PM_ERR_VALUE;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ if (proc_sys_fs->errcode == 0)
+ fprintf(stderr, "refresh_proc_sys_fs: found vfs metrics\n");
+ else
+ fprintf(stderr, "refresh_proc_sys_fs: botch! missing vfs metrics\n");
+ }
+#endif
+ }
+ if (filesp)
+ fclose(filesp);
+ if (inodep)
+ fclose(inodep);
+ if (dentryp)
+ fclose(dentryp);
+
+ if (!err_reported)
+ err_reported = 1;
+
+ if (proc_sys_fs->errcode == 0)
+ return 0;
+ return -1;
+}
diff --git a/src/pmdas/linux/proc_sys_fs.h b/src/pmdas/linux/proc_sys_fs.h
new file mode 100644
index 0000000..d1a1ebf
--- /dev/null
+++ b/src/pmdas/linux/proc_sys_fs.h
@@ -0,0 +1,32 @@
+/*
+ * Linux /proc/sys/fs metrics cluster
+ *
+ * Copyright (c) 2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ int errcode; /* error from previous refresh */
+ int fs_files_count;
+ int fs_files_free;
+ int fs_files_max;
+ int fs_inodes_count;
+ int fs_inodes_free;
+ int fs_dentry_count;
+ int fs_dentry_free;
+} proc_sys_fs_t;
+
+extern int refresh_proc_sys_fs(proc_sys_fs_t *);
diff --git a/src/pmdas/linux/proc_uptime.c b/src/pmdas/linux/proc_uptime.c
new file mode 100644
index 0000000..0d56bfd
--- /dev/null
+++ b/src/pmdas/linux/proc_uptime.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Red Hat 2014.
+ * Copyright (c) International Business Machines Corp., 2002
+ * This code contributed by Mike Mason <mmlnx@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <fcntl.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_uptime.h"
+
+int
+refresh_proc_uptime(proc_uptime_t *proc_uptime)
+{
+ char buf[MAXPATHLEN];
+ int fd, n;
+ float uptime = 0.0, idletime = 0.0;
+
+ memset(proc_uptime, 0, sizeof(proc_uptime_t));
+ snprintf(buf, sizeof(buf), "%s/proc/uptime", linux_statspath);
+ if ((fd = open(buf, O_RDONLY)) < 0)
+ return -oserror();
+
+ n = read(fd, buf, sizeof(buf));
+ close(fd);
+ if (n < 0)
+ return -oserror();
+ else if (n > 0)
+ n--;
+ buf[n] = '\0';
+
+ sscanf((const char *)buf, "%f %f", &uptime, &idletime);
+ proc_uptime->uptime = (unsigned long) uptime;
+ proc_uptime->idletime = (unsigned long) idletime;
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_uptime.h b/src/pmdas/linux/proc_uptime.h
new file mode 100644
index 0000000..f4a658f
--- /dev/null
+++ b/src/pmdas/linux/proc_uptime.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+typedef struct {
+ unsigned long uptime;
+ unsigned long idletime;
+} proc_uptime_t;
+
+extern int refresh_proc_uptime(proc_uptime_t *);
+
diff --git a/src/pmdas/linux/proc_vmstat.c b/src/pmdas/linux/proc_vmstat.c
new file mode 100644
index 0000000..f56c3c3
--- /dev/null
+++ b/src/pmdas/linux/proc_vmstat.c
@@ -0,0 +1,299 @@
+/*
+ * Linux /proc/vmstat metrics cluster
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2007,2011 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "pmda.h"
+#include "indom.h"
+#include "proc_vmstat.h"
+
+static struct {
+ const char *field;
+ __uint64_t *offset;
+} vmstat_fields[] = {
+ /* sorted by name to make maintenance easier */
+ { .field = "allocstall",
+ .offset = &_pm_proc_vmstat.allocstall },
+ { .field = "compact_blocks_moved",
+ .offset = &_pm_proc_vmstat.compact_blocks_moved },
+ { .field = "compact_fail",
+ .offset = &_pm_proc_vmstat.compact_fail },
+ { .field = "compact_pagemigrate_failed",
+ .offset = &_pm_proc_vmstat.compact_pagemigrate_failed },
+ { .field = "compact_pages_moved",
+ .offset = &_pm_proc_vmstat.compact_pages_moved },
+ { .field = "compact_stall",
+ .offset = &_pm_proc_vmstat.compact_stall },
+ { .field = "compact_success",
+ .offset = &_pm_proc_vmstat.compact_success },
+ { .field = "htlb_buddy_alloc_fail",
+ .offset = &_pm_proc_vmstat.htlb_buddy_alloc_fail },
+ { .field = "htlb_buddy_alloc_success",
+ .offset = &_pm_proc_vmstat.htlb_buddy_alloc_success },
+ { .field = "kswapd_inodesteal",
+ .offset = &_pm_proc_vmstat.kswapd_inodesteal },
+ { .field = "kswapd_low_wmark_hit_quickly",
+ .offset = &_pm_proc_vmstat.kswapd_low_wmark_hit_quickly },
+ { .field = "kswapd_high_wmark_hit_quickly",
+ .offset = &_pm_proc_vmstat.kswapd_high_wmark_hit_quickly },
+ { .field = "kswapd_skip_congestion_wait",
+ .offset = &_pm_proc_vmstat.kswapd_skip_congestion_wait },
+ { .field = "kswapd_steal",
+ .offset = &_pm_proc_vmstat.kswapd_steal },
+ { .field = "nr_active_anon",
+ .offset = &_pm_proc_vmstat.nr_active_anon },
+ { .field = "nr_active_file",
+ .offset = &_pm_proc_vmstat.nr_active_file },
+ { .field = "nr_anon_pages",
+ .offset = &_pm_proc_vmstat.nr_anon_pages },
+ { .field = "nr_anon_transparent_hugepages",
+ .offset = &_pm_proc_vmstat.nr_anon_transparent_hugepages },
+ { .field = "nr_bounce",
+ .offset = &_pm_proc_vmstat.nr_bounce },
+ { .field = "nr_dirty",
+ .offset = &_pm_proc_vmstat.nr_dirty },
+ { .field = "nr_dirtied",
+ .offset = &_pm_proc_vmstat.nr_dirtied },
+ { .field = "nr_dirty_threshold",
+ .offset = &_pm_proc_vmstat.nr_dirty_threshold },
+ { .field = "nr_dirty_background_threshold",
+ .offset = &_pm_proc_vmstat.nr_dirty_background_threshold },
+ { .field = "nr_file_pages",
+ .offset = &_pm_proc_vmstat.nr_file_pages },
+ { .field = "nr_free_pages",
+ .offset = &_pm_proc_vmstat.nr_free_pages },
+ { .field = "nr_inactive_anon",
+ .offset = &_pm_proc_vmstat.nr_inactive_anon },
+ { .field = "nr_inactive_file",
+ .offset = &_pm_proc_vmstat.nr_inactive_file },
+ { .field = "nr_isolated_anon",
+ .offset = &_pm_proc_vmstat.nr_isolated_anon },
+ { .field = "nr_isolated_file",
+ .offset = &_pm_proc_vmstat.nr_isolated_file },
+ { .field = "nr_kernel_stack",
+ .offset = &_pm_proc_vmstat.nr_kernel_stack },
+ { .field = "nr_mapped",
+ .offset = &_pm_proc_vmstat.nr_mapped },
+ { .field = "nr_mlock",
+ .offset = &_pm_proc_vmstat.nr_mlock },
+ { .field = "nr_page_table_pages",
+ .offset = &_pm_proc_vmstat.nr_page_table_pages },
+ { .field = "nr_shmem",
+ .offset = &_pm_proc_vmstat.nr_shmem },
+ { .field = "nr_slab_reclaimable",
+ .offset = &_pm_proc_vmstat.nr_slab_reclaimable },
+ { .field = "nr_slab_unreclaimable",
+ .offset = &_pm_proc_vmstat.nr_slab_unreclaimable },
+ { .field = "nr_slab",
+ .offset = &_pm_proc_vmstat.nr_slab }, /* not in later kernels */
+ { .field = "nr_unevictable",
+ .offset = &_pm_proc_vmstat.nr_unevictable },
+ { .field = "nr_unstable",
+ .offset = &_pm_proc_vmstat.nr_unstable },
+ { .field = "nr_vmscan_write",
+ .offset = &_pm_proc_vmstat.nr_vmscan_write },
+ { .field = "nr_writeback",
+ .offset = &_pm_proc_vmstat.nr_writeback },
+ { .field = "nr_writeback_temp",
+ .offset = &_pm_proc_vmstat.nr_writeback_temp },
+ { .field = "nr_written",
+ .offset = &_pm_proc_vmstat.nr_written },
+ { .field = "numa_hit",
+ .offset = &_pm_proc_vmstat.numa_hit },
+ { .field = "numa_miss",
+ .offset = &_pm_proc_vmstat.numa_miss },
+ { .field = "numa_foreign",
+ .offset = &_pm_proc_vmstat.numa_foreign },
+ { .field = "numa_interleave",
+ .offset = &_pm_proc_vmstat.numa_interleave },
+ { .field = "numa_local",
+ .offset = &_pm_proc_vmstat.numa_local },
+ { .field = "numa_other",
+ .offset = &_pm_proc_vmstat.numa_other },
+ { .field = "pageoutrun",
+ .offset = &_pm_proc_vmstat.pageoutrun },
+ { .field = "pgactivate",
+ .offset = &_pm_proc_vmstat.pgactivate },
+ { .field = "pgalloc_dma",
+ .offset = &_pm_proc_vmstat.pgalloc_dma },
+ { .field = "pgalloc_dma32",
+ .offset = &_pm_proc_vmstat.pgalloc_dma32 },
+ { .field = "pgalloc_high",
+ .offset = &_pm_proc_vmstat.pgalloc_high },
+ { .field = "pgalloc_movable",
+ .offset = &_pm_proc_vmstat.pgalloc_movable },
+ { .field = "pgalloc_normal",
+ .offset = &_pm_proc_vmstat.pgalloc_normal },
+ { .field = "pgdeactivate",
+ .offset = &_pm_proc_vmstat.pgdeactivate },
+ { .field = "pgfault",
+ .offset = &_pm_proc_vmstat.pgfault },
+ { .field = "pgfree",
+ .offset = &_pm_proc_vmstat.pgfree },
+ { .field = "pginodesteal",
+ .offset = &_pm_proc_vmstat.pginodesteal },
+ { .field = "pgmajfault",
+ .offset = &_pm_proc_vmstat.pgmajfault },
+ { .field = "pgpgin",
+ .offset = &_pm_proc_vmstat.pgpgin },
+ { .field = "pgpgout",
+ .offset = &_pm_proc_vmstat.pgpgout },
+ { .field = "pgrefill_dma",
+ .offset = &_pm_proc_vmstat.pgrefill_dma },
+ { .field = "pgrefill_dma32",
+ .offset = &_pm_proc_vmstat.pgrefill_dma32 },
+ { .field = "pgrefill_high",
+ .offset = &_pm_proc_vmstat.pgrefill_high },
+ { .field = "pgrefill_movable",
+ .offset = &_pm_proc_vmstat.pgrefill_movable },
+ { .field = "pgrefill_normal",
+ .offset = &_pm_proc_vmstat.pgrefill_normal },
+ { .field = "pgrotated",
+ .offset = &_pm_proc_vmstat.pgrotated },
+ { .field = "pgscan_direct_dma",
+ .offset = &_pm_proc_vmstat.pgscan_direct_dma },
+ { .field = "pgscan_direct_dma32",
+ .offset = &_pm_proc_vmstat.pgscan_direct_dma32 },
+ { .field = "pgscan_direct_high",
+ .offset = &_pm_proc_vmstat.pgscan_direct_high },
+ { .field = "pgscan_direct_movable",
+ .offset = &_pm_proc_vmstat.pgscan_direct_movable },
+ { .field = "pgscan_direct_normal",
+ .offset = &_pm_proc_vmstat.pgscan_direct_normal },
+ { .field = "pgscan_kswapd_dma",
+ .offset = &_pm_proc_vmstat.pgscan_kswapd_dma },
+ { .field = "pgscan_kswapd_dma32",
+ .offset = &_pm_proc_vmstat.pgscan_kswapd_dma32 },
+ { .field = "pgscan_kswapd_high",
+ .offset = &_pm_proc_vmstat.pgscan_kswapd_high },
+ { .field = "pgscan_kswapd_movable",
+ .offset = &_pm_proc_vmstat.pgscan_kswapd_movable },
+ { .field = "pgscan_kswapd_normal",
+ .offset = &_pm_proc_vmstat.pgscan_kswapd_normal },
+ { .field = "pgsteal_dma",
+ .offset = &_pm_proc_vmstat.pgsteal_dma },
+ { .field = "pgsteal_dma32",
+ .offset = &_pm_proc_vmstat.pgsteal_dma32 },
+ { .field = "pgsteal_high",
+ .offset = &_pm_proc_vmstat.pgsteal_high },
+ { .field = "pgsteal_movable",
+ .offset = &_pm_proc_vmstat.pgsteal_movable },
+ { .field = "pgsteal_normal",
+ .offset = &_pm_proc_vmstat.pgsteal_normal },
+ { .field = "pswpin",
+ .offset = &_pm_proc_vmstat.pswpin },
+ { .field = "pswpout",
+ .offset = &_pm_proc_vmstat.pswpout },
+ { .field = "slabs_scanned",
+ .offset = &_pm_proc_vmstat.slabs_scanned },
+ { .field = "thp_collapse_alloc",
+ .offset = &_pm_proc_vmstat.thp_collapse_alloc },
+ { .field = "thp_collapse_alloc_failed",
+ .offset = &_pm_proc_vmstat.thp_collapse_alloc_failed },
+ { .field = "thp_fault_alloc",
+ .offset = &_pm_proc_vmstat.thp_fault_alloc },
+ { .field = "thp_fault_fallback",
+ .offset = &_pm_proc_vmstat.thp_fault_fallback },
+ { .field = "thp_split",
+ .offset = &_pm_proc_vmstat.thp_split },
+ { .field = "unevictable_pgs_cleared",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_cleared },
+ { .field = "unevictable_pgs_culled",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_culled },
+ { .field = "unevictable_pgs_mlocked",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_mlocked },
+ { .field = "unevictable_pgs_mlockfreed",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_mlockfreed },
+ { .field = "unevictable_pgs_munlocked",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_munlocked },
+ { .field = "unevictable_pgs_rescued",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_rescued },
+ { .field = "unevictable_pgs_scanned",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_scanned },
+ { .field = "unevictable_pgs_stranded",
+ .offset = &_pm_proc_vmstat.unevictable_pgs_stranded },
+ { .field = "zone_reclaim_failed",
+ .offset = &_pm_proc_vmstat.zone_reclaim_failed },
+
+ { .field = NULL, .offset = NULL }
+};
+
+#define VMSTAT_OFFSET(ii, pp) (int64_t *)((char *)pp + \
+ (__psint_t)vmstat_fields[ii].offset - (__psint_t)&_pm_proc_vmstat)
+
+void
+proc_vmstat_init(void)
+{
+ char buf[1024];
+
+ /*
+ * The swap metrics moved from /proc/stat to /proc/vmstat early in 2.6.
+ * In addition, the swap operation count was removed; the fetch routine
+ * needs to deal with these quirks and return something sensible based
+ * (initially) on whether the vmstat file exists.
+ *
+ * We'll re-evaluate this on each fetch of the mem.vmstat metrics, but
+ * that is not a problem. This routine makes sure any swap.xxx metric
+ * fetch without a preceding mem.vmstat fetch has the correct state.
+ */
+ snprintf(buf, sizeof(buf), "%s/proc/vmstat", linux_statspath);
+ _pm_have_proc_vmstat = (access(buf, R_OK) == 0);
+}
+
+int
+refresh_proc_vmstat(proc_vmstat_t *proc_vmstat)
+{
+ char buf[1024];
+ char *bufp;
+ int64_t *p;
+ int i;
+ FILE *fp;
+
+ for (i = 0; vmstat_fields[i].field != NULL; i++) {
+ p = VMSTAT_OFFSET(i, proc_vmstat);
+ *p = -1; /* marked as "no value available" */
+ }
+
+ if ((fp = linux_statsfile("/proc/vmstat", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ _pm_have_proc_vmstat = 1;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((bufp = strchr(buf, ' ')) == NULL)
+ continue;
+ *bufp = '\0';
+ for (i = 0; vmstat_fields[i].field != NULL; i++) {
+ if (strcmp(buf, vmstat_fields[i].field) != 0)
+ continue;
+ p = VMSTAT_OFFSET(i, proc_vmstat);
+ for (bufp++; *bufp; bufp++) {
+ if (isdigit((int)*bufp)) {
+ sscanf(bufp, "%llu", (unsigned long long *)p);
+ break;
+ }
+ }
+ }
+ }
+ fclose(fp);
+
+ if (proc_vmstat->nr_slab == -1) /* split apart in 2.6.18 */
+ proc_vmstat->nr_slab = proc_vmstat->nr_slab_reclaimable +
+ proc_vmstat->nr_slab_unreclaimable;
+ return 0;
+}
diff --git a/src/pmdas/linux/proc_vmstat.h b/src/pmdas/linux/proc_vmstat.h
new file mode 100644
index 0000000..adeac39
--- /dev/null
+++ b/src/pmdas/linux/proc_vmstat.h
@@ -0,0 +1,131 @@
+/*
+ * Linux /proc/vmstat metrics cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2007,2011 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * All fields (sorted!) in /proc/vmstat for 2.6.x
+ */
+typedef struct {
+ /* sorted by name to make maintenance easier */
+ __uint64_t allocstall;
+ __uint64_t compact_blocks_moved;
+ __uint64_t compact_fail;
+ __uint64_t compact_pagemigrate_failed;
+ __uint64_t compact_pages_moved;
+ __uint64_t compact_stall;
+ __uint64_t compact_success;
+ __uint64_t htlb_buddy_alloc_fail;
+ __uint64_t htlb_buddy_alloc_success;
+ __uint64_t kswapd_high_wmark_hit_quickly;
+ __uint64_t kswapd_inodesteal;
+ __uint64_t kswapd_low_wmark_hit_quickly;
+ __uint64_t kswapd_skip_congestion_wait;
+ __uint64_t kswapd_steal;
+ __uint64_t nr_active_anon;
+ __uint64_t nr_active_file;
+ __uint64_t nr_anon_pages;
+ __uint64_t nr_anon_transparent_hugepages;
+ __uint64_t nr_bounce;
+ __uint64_t nr_dirtied;
+ __uint64_t nr_dirty;
+ __uint64_t nr_dirty_threshold;
+ __uint64_t nr_dirty_background_threshold;
+ __uint64_t nr_file_pages;
+ __uint64_t nr_free_pages;
+ __uint64_t nr_inactive_anon;
+ __uint64_t nr_inactive_file;
+ __uint64_t nr_isolated_anon;
+ __uint64_t nr_isolated_file;
+ __uint64_t nr_kernel_stack;
+ __uint64_t nr_mapped;
+ __uint64_t nr_mlock;
+ __uint64_t nr_page_table_pages;
+ __uint64_t nr_shmem;
+ __uint64_t nr_slab; /* not in later kernels */
+ __uint64_t nr_slab_reclaimable;
+ __uint64_t nr_slab_unreclaimable;
+ __uint64_t nr_unevictable;
+ __uint64_t nr_unstable;
+ __uint64_t nr_vmscan_write;
+ __uint64_t nr_writeback;
+ __uint64_t nr_writeback_temp;
+ __uint64_t nr_written;
+ __uint64_t numa_foreign;
+ __uint64_t numa_hit;
+ __uint64_t numa_interleave;
+ __uint64_t numa_local;
+ __uint64_t numa_miss;
+ __uint64_t numa_other;
+ __uint64_t pageoutrun;
+ __uint64_t pgactivate;
+ __uint64_t pgalloc_dma;
+ __uint64_t pgalloc_dma32;
+ __uint64_t pgalloc_movable;
+ __uint64_t pgalloc_high;
+ __uint64_t pgalloc_normal;
+ __uint64_t pgdeactivate;
+ __uint64_t pgfault;
+ __uint64_t pgfree;
+ __uint64_t pginodesteal;
+ __uint64_t pgmajfault;
+ __uint64_t pgpgin;
+ __uint64_t pgpgout;
+ __uint64_t pgrefill_dma;
+ __uint64_t pgrefill_dma32;
+ __uint64_t pgrefill_high;
+ __uint64_t pgrefill_movable;
+ __uint64_t pgrefill_normal;
+ __uint64_t pgrotated;
+ __uint64_t pgscan_direct_dma;
+ __uint64_t pgscan_direct_dma32;
+ __uint64_t pgscan_direct_high;
+ __uint64_t pgscan_direct_movable;
+ __uint64_t pgscan_direct_normal;
+ __uint64_t pgscan_kswapd_dma;
+ __uint64_t pgscan_kswapd_dma32;
+ __uint64_t pgscan_kswapd_high;
+ __uint64_t pgscan_kswapd_movable;
+ __uint64_t pgscan_kswapd_normal;
+ __uint64_t pgsteal_dma;
+ __uint64_t pgsteal_dma32;
+ __uint64_t pgsteal_high;
+ __uint64_t pgsteal_movable;
+ __uint64_t pgsteal_normal;
+ __uint64_t pswpin;
+ __uint64_t pswpout;
+ __uint64_t slabs_scanned;
+ __uint64_t thp_fault_alloc;
+ __uint64_t thp_fault_fallback;
+ __uint64_t thp_collapse_alloc;
+ __uint64_t thp_collapse_alloc_failed;
+ __uint64_t thp_split;
+ __uint64_t unevictable_pgs_cleared;
+ __uint64_t unevictable_pgs_culled;
+ __uint64_t unevictable_pgs_mlocked;
+ __uint64_t unevictable_pgs_mlockfreed;
+ __uint64_t unevictable_pgs_munlocked;
+ __uint64_t unevictable_pgs_rescued;
+ __uint64_t unevictable_pgs_scanned;
+ __uint64_t unevictable_pgs_stranded;
+ __uint64_t zone_reclaim_failed;
+} proc_vmstat_t;
+
+extern void proc_vmstat_init(void);
+extern int refresh_proc_vmstat(proc_vmstat_t *);
+extern int _pm_have_proc_vmstat;
+extern proc_vmstat_t _pm_proc_vmstat;
+
diff --git a/src/pmdas/linux/root_linux b/src/pmdas/linux/root_linux
new file mode 100644
index 0000000..d66da28
--- /dev/null
+++ b/src/pmdas/linux/root_linux
@@ -0,0 +1,1005 @@
+/*
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ * Portions Copyright (c) International Business Machines Corp., 2002
+ * Portions Copyright (c) 2007-2009 Aconex. All Rights Reserved.
+ * Portions Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+root {
+ hinv
+ kernel
+ mem
+ swap
+ network
+ disk
+ filesys
+ swapdev
+ rpc
+ nfs
+ nfs3
+ nfs4
+ pmda
+ ipc
+ vfs
+ tmpfs
+ sysfs
+}
+
+hinv {
+ physmem 60:1:9
+ pagesize 60:1:11
+ ncpu 60:0:32
+ ndisk 60:0:33
+ nfilesys 60:5:0
+ ninterface 60:3:27
+ nlv 60:52:1
+ nnode 60:0:19
+ machine 60:18:7
+ map
+ cpu
+}
+
+hinv.map {
+ scsi 60:15:0
+ cpu_num 60:18:6
+ cpu_node 60:18:8
+ lvname 60:52:0
+ dmname 60:54:13
+}
+
+hinv.cpu {
+ clock 60:18:0
+ vendor 60:18:1
+ model 60:18:2
+ stepping 60:18:3
+ cache 60:18:4
+ bogomips 60:18:5
+ model_name 60:18:9
+ flags 60:18:10
+ cache_alignment 60:18:11
+}
+
+kernel {
+ all
+ percpu
+ pernode
+ uname
+}
+
+kernel.all {
+ cpu
+ load 60:2:0
+ intr 60:0:12
+ pswitch 60:0:13
+ sysfork 60:0:14
+ hz 60:0:48
+ uptime 60:26:0
+ idletime 60:26:1
+ nusers 60:25:0
+ lastpid 60:2:1
+ runnable 60:2:2
+ nprocs 60:2:3
+ interrupts
+}
+
+kernel.all.interrupts {
+ errors 60:4:3
+}
+
+kernel.all.cpu {
+ user 60:0:20
+ nice 60:0:21
+ sys 60:0:22
+ idle 60:0:23
+ intr 60:0:34
+ wait
+ irq
+ steal 60:0:55
+ guest 60:0:60
+ vuser 60:0:78
+}
+
+kernel.all.cpu.wait {
+ total 60:0:35
+}
+
+kernel.all.cpu.irq {
+ soft 60:0:53
+ hard 60:0:54
+}
+
+kernel.percpu {
+ interrupts 60:*:*
+ cpu
+}
+
+kernel.percpu.cpu {
+ user 60:0:0
+ nice 60:0:1
+ sys 60:0:2
+ idle 60:0:3
+ intr 60:0:31
+ wait
+ irq
+ steal 60:0:58
+ guest 60:0:61
+ vuser 60:0:76
+}
+
+kernel.percpu.cpu.wait {
+ total 60:0:30
+}
+
+kernel.percpu.cpu.irq {
+ soft 60:0:56
+ hard 60:0:57
+}
+
+kernel.pernode {
+ cpu
+}
+
+kernel.pernode.cpu {
+ user 60:0:62
+ nice 60:0:63
+ sys 60:0:64
+ idle 60:0:65
+ intr 60:0:66
+ wait
+ irq
+ steal 60:0:67
+ guest 60:0:68
+ vuser 60:0:77
+}
+
+kernel.pernode.cpu.wait {
+ total 60:0:69
+}
+
+kernel.pernode.cpu.irq {
+ soft 60:0:70
+ hard 60:0:71
+}
+
+kernel.uname {
+ release 60:12:0
+ version 60:12:1
+ sysname 60:12:2
+ machine 60:12:3
+ nodename 60:12:4
+ distro 60:12:7
+}
+
+ipc {
+ sem
+ msg
+ shm
+}
+
+ipc.sem {
+ max_semmap 60:21:0
+ max_semid 60:21:1
+ max_sem 60:21:2
+ num_undo 60:21:3
+ max_perid 60:21:4
+ max_ops 60:21:5
+ max_undoent 60:21:6
+ sz_semundo 60:21:7
+ max_semval 60:21:8
+ max_exit 60:21:9
+}
+
+ipc.msg {
+ sz_pool 60:22:0
+ mapent 60:22:1
+ max_msgsz 60:22:2
+ max_defmsgq 60:22:3
+ max_msgqid 60:22:4
+ max_msgseg 60:22:5
+ num_smsghdr 60:22:6
+ max_seg 60:22:7
+}
+
+ipc.shm {
+ max_segsz 60:23:0
+ min_segsz 60:23:1
+ max_seg 60:23:2
+ max_segproc 60:23:3
+ max_shmsys 60:23:4
+}
+
+pmda {
+ uname 60:12:5
+ version 60:12:6
+}
+
+disk {
+ dev
+ all
+ partitions
+ dm
+}
+
+disk.dev {
+ read 60:0:4
+ write 60:0:5
+ total 60:0:28
+ blkread 60:0:6
+ blkwrite 60:0:7
+ blktotal 60:0:36
+ read_bytes 60:0:38
+ write_bytes 60:0:39
+ total_bytes 60:0:40
+ read_merge 60:0:49
+ write_merge 60:0:50
+ avactive 60:0:46
+ read_rawactive 60:0:72
+ write_rawactive 60:0:73
+ aveq 60:0:47
+ scheduler 60:0:59
+}
+
+disk.all {
+ read 60:0:24
+ write 60:0:25
+ total 60:0:29
+ blkread 60:0:26
+ blkwrite 60:0:27
+ blktotal 60:0:37
+ read_bytes 60:0:41
+ write_bytes 60:0:42
+ total_bytes 60:0:43
+ read_merge 60:0:51
+ write_merge 60:0:52
+ avactive 60:0:44
+ read_rawactive 60:0:74
+ write_rawactive 60:0:75
+ aveq 60:0:45
+}
+
+disk.dm {
+ read 60:54:0
+ write 60:54:1
+ total 60:54:2
+ blkread 60:54:3
+ blkwrite 60:54:4
+ blktotal 60:54:5
+ read_bytes 60:54:6
+ write_bytes 60:54:7
+ total_bytes 60:54:8
+ read_merge 60:54:9
+ write_merge 60:54:10
+ avactive 60:54:11
+ aveq 60:54:12
+ read_rawactive 60:54:14
+ write_rawactive 60:54:15
+}
+
+disk.partitions {
+ read 60:10:0
+ write 60:10:1
+ total 60:10:2
+ blkread 60:10:3
+ blkwrite 60:10:4
+ blktotal 60:10:5
+ read_bytes 60:10:6
+ write_bytes 60:10:7
+ total_bytes 60:10:8
+}
+
+mem {
+ physmem 60:1:0
+ freemem 60:1:10
+ util
+ numa
+ slabinfo
+ vmstat
+}
+
+mem.util {
+ used 60:1:1
+ free 60:1:2
+ shared 60:1:3
+ bufmem 60:1:4
+ cached 60:1:5
+ other 60:1:12
+ swapCached 60:1:13
+ active 60:1:14
+ inactive 60:1:15
+ highTotal 60:1:16
+ highFree 60:1:17
+ lowTotal 60:1:18
+ lowFree 60:1:19
+ swapTotal 60:1:20
+ swapFree 60:1:21
+ dirty 60:1:22
+ writeback 60:1:23
+ mapped 60:1:24
+ slab 60:1:25
+ committed_AS 60:1:26
+ pageTables 60:1:27
+ reverseMaps 60:1:28
+ cache_clean 60:1:29
+ anonpages 60:1:30
+ commitLimit 60:1:31
+ bounce 60:1:32
+ NFS_Unstable 60:1:33
+ slabReclaimable 60:1:34
+ slabUnreclaimable 60:1:35
+ active_anon 60:1:36
+ inactive_anon 60:1:37
+ active_file 60:1:38
+ inactive_file 60:1:39
+ unevictable 60:1:40
+ mlocked 60:1:41
+ shmem 60:1:42
+ kernelStack 60:1:43
+ hugepagesTotal 60:1:44
+ hugepagesFree 60:1:45
+ hugepagesRsvd 60:1:46
+ hugepagesSurp 60:1:47
+ directMap4k 60:1:48
+ directMap2M 60:1:49
+ vmallocTotal 60:1:50
+ vmallocUsed 60:1:51
+ vmallocChunk 60:1:52
+ mmap_copy 60:1:53
+ quicklists 60:1:54
+ corrupthardware 60:1:55
+ anonhugepages 60:1:56
+ directMap1G 60:1:57
+ available 60:1:58
+}
+
+mem.numa {
+ util
+ alloc
+}
+
+mem.numa.util {
+ total 60:36:0
+ free 60:36:1
+ used 60:36:2
+ active 60:36:3
+ inactive 60:36:4
+ active_anon 60:36:5
+ inactive_anon 60:36:6
+ active_file 60:36:7
+ inactive_file 60:36:8
+ highTotal 60:36:9
+ highFree 60:36:10
+ lowTotal 60:36:11
+ lowFree 60:36:12
+ unevictable 60:36:13
+ mlocked 60:36:14
+ dirty 60:36:15
+ writeback 60:36:16
+ filePages 60:36:17
+ mapped 60:36:18
+ anonpages 60:36:19
+ shmem 60:36:20
+ kernelStack 60:36:21
+ pageTables 60:36:22
+ NFS_Unstable 60:36:23
+ bounce 60:36:24
+ writebackTmp 60:36:25
+ slab 60:36:26
+ slabReclaimable 60:36:27
+ slabUnreclaimable 60:36:28
+ hugepagesTotal 60:36:29
+ hugepagesFree 60:36:30
+ hugepagesSurp 60:36:31
+}
+
+mem.numa.alloc {
+ hit 60:36:32
+ miss 60:36:33
+ foreign 60:36:34
+ interleave_hit 60:36:35
+ local_node 60:36:36
+ other_node 60:36:37
+}
+
+swap {
+ pagesin 60:0:8
+ pagesout 60:0:9
+ in 60:0:10
+ out 60:0:11
+ free 60:1:8
+ length 60:1:6
+ used 60:1:7
+}
+
+network {
+ interface
+ sockstat
+ ip
+ icmp
+ icmpmsg
+ tcp
+ udp
+ udplite
+ tcpconn
+}
+
+network.interface {
+ collisions 60:3:13
+ in
+ out
+ total
+ mtu 60:3:21
+ speed 60:3:22
+ baudrate 60:3:23
+ duplex 60:3:24
+ up 60:3:25
+ running 60:3:26
+ inet_addr 60:33:0
+ ipv6_addr 60:33:1
+ ipv6_scope 60:33:2
+ hw_addr 60:33:3
+}
+
+network.interface.in {
+ bytes 60:3:0
+ packets 60:3:1
+ errors 60:3:2
+ drops 60:3:3
+ fifo 60:3:4
+ frame 60:3:5
+ compressed 60:3:6
+ mcasts 60:3:7
+}
+
+network.interface.out {
+ bytes 60:3:8
+ packets 60:3:9
+ errors 60:3:10
+ drops 60:3:11
+ fifo 60:3:12
+ carrier 60:3:14
+ compressed 60:3:15
+}
+
+network.interface.total {
+ bytes 60:3:16
+ packets 60:3:17
+ errors 60:3:18
+ drops 60:3:19
+ mcasts 60:3:20
+}
+
+network.sockstat {
+ tcp
+ udp
+ raw
+}
+
+network.sockstat.tcp {
+ inuse 60:11:0
+ highest 60:11:1
+ util 60:11:2
+}
+
+network.sockstat.udp {
+ inuse 60:11:3
+ highest 60:11:4
+ util 60:11:5
+}
+
+network.sockstat.raw {
+ inuse 60:11:6
+ highest 60:11:7
+ util 60:11:8
+}
+
+network.ip {
+ forwarding 60:14:00
+ defaultttl 60:14:01
+ inreceives 60:14:02
+ inhdrerrors 60:14:03
+ inaddrerrors 60:14:04
+ forwdatagrams 60:14:05
+ inunknownprotos 60:14:06
+ indiscards 60:14:07
+ indelivers 60:14:08
+ outrequests 60:14:09
+ outdiscards 60:14:10
+ outnoroutes 60:14:11
+ reasmtimeout 60:14:12
+ reasmreqds 60:14:13
+ reasmoks 60:14:14
+ reasmfails 60:14:15
+ fragoks 60:14:16
+ fragfails 60:14:17
+ fragcreates 60:14:18
+ innoroutes 60:53:00
+ intruncatedpkts 60:53:01
+ inmcastpkts 60:53:02
+ outmcastpkts 60:53:03
+ inbcastpkts 60:53:04
+ outbcastpkts 60:53:05
+ inoctets 60:53:06
+ outoctets 60:53:07
+ inmcastoctets 60:53:08
+ outmcastoctets 60:53:09
+ inbcastoctets 60:53:10
+ outbcastoctets 60:53:11
+ csumerrors 60:53:12
+ noectpkts 60:53:13
+ ect1pkts 60:53:14
+ ect0pkts 60:53:15
+ cepkts 60:53:16
+}
+
+network.icmp {
+ inmsgs 60:14:20
+ inerrors 60:14:21
+ indestunreachs 60:14:22
+ intimeexcds 60:14:23
+ inparmprobs 60:14:24
+ insrcquenchs 60:14:25
+ inredirects 60:14:26
+ inechos 60:14:27
+ inechoreps 60:14:28
+ intimestamps 60:14:29
+ intimestampreps 60:14:30
+ inaddrmasks 60:14:31
+ inaddrmaskreps 60:14:32
+ outmsgs 60:14:33
+ outerrors 60:14:34
+ outdestunreachs 60:14:35
+ outtimeexcds 60:14:36
+ outparmprobs 60:14:37
+ outsrcquenchs 60:14:38
+ outredirects 60:14:39
+ outechos 60:14:40
+ outechoreps 60:14:41
+ outtimestamps 60:14:42
+ outtimestampreps 60:14:43
+ outaddrmasks 60:14:44
+ outaddrmaskreps 60:14:45
+ incsumerrors 60:14:46
+}
+
+network.tcp {
+ rtoalgorithm 60:14:50
+ rtomin 60:14:51
+ rtomax 60:14:52
+ maxconn 60:14:53
+ activeopens 60:14:54
+ passiveopens 60:14:55
+ attemptfails 60:14:56
+ estabresets 60:14:57
+ currestab 60:14:58
+ insegs 60:14:59
+ outsegs 60:14:60
+ retranssegs 60:14:61
+ inerrs 60:14:62
+ outrsts 60:14:63
+ incsumerrors 60:14:64
+ syncookiessent 60:53:17
+ syncookiesrecv 60:53:18
+ syncookiesfailed 60:53:19
+ embryonicrsts 60:53:20
+ prunecalled 60:53:21
+ rcvpruned 60:53:22
+ ofopruned 60:53:23
+ outofwindowicmps 60:53:24
+ lockdroppedicmps 60:53:25
+ arpfilter 60:53:26
+ timewaited 60:53:27
+ timewaitrecycled 60:53:28
+ timewaitkilled 60:53:29
+ pawspassiverejected 60:53:30
+ pawsactiverejected 60:53:31
+ pawsestabrejected 60:53:32
+ delayedacks 60:53:33
+ delayedacklocked 60:53:34
+ delayedacklost 60:53:35
+ listenoverflows 60:53:36
+ listendrops 60:53:37
+ prequeued 60:53:38
+ directcopyfrombacklog 60:53:39
+ directcopyfromprequeue 60:53:40
+ prequeueddropped 60:53:41
+ hphits 60:53:42
+ hphitstouser 60:53:43
+ pureacks 60:53:44
+ hpacks 60:53:45
+ renorecovery 60:53:46
+ sackrecovery 60:53:47
+ sackreneging 60:53:48
+ fackreorder 60:53:49
+ sackreorder 60:53:50
+ renoreorder 60:53:51
+ tsreorder 60:53:52
+ fullundo 60:53:53
+ partialundo 60:53:54
+ dsackundo 60:53:55
+ lossundo 60:53:56
+ lostretransmit 60:53:57
+ renofailures 60:53:58
+ sackfailures 60:53:59
+ lossfailures 60:53:60
+ fastretrans 60:53:61
+ forwardretrans 60:53:62
+ slowstartretrans 60:53:63
+ timeouts 60:53:64
+ lossprobes 60:53:65
+ lossproberecovery 60:53:66
+ renorecoveryfail 60:53:67
+ sackrecoveryfail 60:53:68
+ schedulerfail 60:53:69
+ rcvcollapsed 60:53:70
+ dsackoldsent 60:53:71
+ dsackofosent 60:53:72
+ dsackrecv 60:53:73
+ dsackoforecv 60:53:74
+ abortondata 60:53:75
+ abortonclose 60:53:76
+ abortonmemory 60:53:77
+ abortontimeout 60:53:78
+ abortonlinger 60:53:79
+ abortfailed 60:53:80
+ memorypressures 60:53:81
+ sackdiscard 60:53:82
+ dsackignoredold 60:53:83
+ dsackignorednoundo 60:53:84
+ spuriousrtos 60:53:85
+ md5notfound 60:53:86
+ md5unexpected 60:53:87
+ sackshifted 60:53:88
+ sackmerged 60:53:89
+ sackshiftfallback 60:53:90
+ backlogdrop 60:53:91
+ minttldrop 60:53:92
+ deferacceptdrop 60:53:93
+ iprpfilter 60:53:94
+ timewaitoverflow 60:53:95
+ reqqfulldocookies 60:53:96
+ reqqfulldrop 60:53:97
+ retransfail 60:53:98
+ rcvcoalesce 60:53:99
+ ofoqueue 60:53:100
+ ofodrop 60:53:101
+ ofomerge 60:53:102
+ challengeack 60:53:103
+ synchallenge 60:53:104
+ fastopenactive 60:53:105
+ fastopenactivefail 60:53:106
+ fastopenpassive 60:53:107
+ fastopenpassivefail 60:53:108
+ fastopenlistenoverflow 60:53:109
+ fastopencookiereqd 60:53:110
+ spuriousrtxhostqueues 60:53:111
+ busypollrxpackets 60:53:112
+ autocorking 60:53:113
+ fromzerowindowadv 60:53:114
+ tozerowindowadv 60:53:115
+ wantzerowindowadv 60:53:116
+ synretrans 60:53:117
+ origdatasent 60:53:118
+}
+
+network.udp {
+ indatagrams 60:14:70
+ noports 60:14:71
+ inerrors 60:14:72
+ outdatagrams 60:14:74
+ recvbuferrors 60:14:75
+ sndbuferrors 60:14:76
+ incsumerrors 60:14:83
+}
+
+network.udplite {
+ indatagrams 60:14:77
+ noports 60:14:78
+ inerrors 60:14:79
+ outdatagrams 60:14:80
+ recvbuferrors 60:14:81
+ sndbuferrors 60:14:82
+ incsumerrors 60:14:84
+}
+
+network.icmpmsg {
+ intype 60:14:88
+ outtype 60:14:89
+}
+
+filesys {
+ capacity 60:5:1
+ used 60:5:2
+ free 60:5:3
+ maxfiles 60:5:4
+ usedfiles 60:5:5
+ freefiles 60:5:6
+ mountdir 60:5:7
+ full 60:5:8
+ blocksize 60:5:9
+ avail 60:5:10
+ readonly 60:5:11
+}
+
+tmpfs {
+ capacity 60:34:1
+ used 60:34:2
+ free 60:34:3
+ maxfiles 60:34:4
+ usedfiles 60:34:5
+ freefiles 60:34:6
+ full 60:34:7
+}
+
+swapdev {
+ free 60:6:0
+ length 60:6:1
+ maxswap 60:6:2
+ vlength 60:6:3
+ priority 60:6:4
+}
+
+nfs {
+ client
+ server
+}
+
+nfs.client {
+ calls 60:7:1
+ reqs 60:7:4
+}
+
+nfs.server {
+ calls 60:7:50
+ reqs 60:7:12
+}
+
+rpc {
+ client
+ server
+}
+
+rpc.client {
+ rpccnt 60:7:20
+ rpcretrans 60:7:21
+ rpcauthrefresh 60:7:22
+ netcnt 60:7:24
+ netudpcnt 60:7:25
+ nettcpcnt 60:7:26
+ nettcpconn 60:7:27
+}
+
+rpc.server {
+ rpccnt 60:7:30
+ rpcerr 60:7:31
+ rpcbadfmt 60:7:32
+ rpcbadauth 60:7:33
+ rpcbadclnt 60:7:34
+ rchits 60:7:35
+ rcmisses 60:7:36
+ rcnocache 60:7:37
+ fh_cached 60:7:38
+ fh_valid 60:7:39
+ fh_fixup 60:7:40
+ fh_lookup 60:7:41
+ fh_stale 60:7:42
+ fh_concurrent 60:7:43
+ netcnt 60:7:44
+ netudpcnt 60:7:45
+ nettcpcnt 60:7:46
+ nettcpconn 60:7:47
+ fh_anon 60:7:51
+ fh_nocache_dir 60:7:52
+ fh_nocache_nondir 60:7:53
+ io_read 60:7:54
+ io_write 60:7:55
+ th_cnt 60:7:56
+ th_fullcnt 60:7:57
+}
+
+nfs3 {
+ client
+ server
+}
+
+nfs3.client {
+ calls 60:7:60
+ reqs 60:7:61
+}
+
+nfs3.server {
+ calls 60:7:62
+ reqs 60:7:63
+}
+
+nfs4 {
+ client
+ server
+}
+
+nfs4.client {
+ calls 60:7:64
+ reqs 60:7:65
+}
+
+nfs4.server {
+ calls 60:7:66
+ reqs 60:7:67
+}
+
+network.tcpconn {
+ established 60:19:1
+ syn_sent 60:19:2
+ syn_recv 60:19:3
+ fin_wait1 60:19:4
+ fin_wait2 60:19:5
+ time_wait 60:19:6
+ close 60:19:7
+ close_wait 60:19:8
+ last_ack 60:19:9
+ listen 60:19:10
+ closing 60:19:11
+}
+
+mem.slabinfo {
+ objects
+ slabs
+}
+
+mem.slabinfo.objects {
+ active 60:20:0
+ total 60:20:1
+ size 60:20:2
+}
+
+mem.slabinfo.slabs {
+ active 60:20:3
+ total 60:20:4
+ pages_per_slab 60:20:5
+ objects_per_slab 60:20:6
+ total_size 60:20:7
+}
+
+mem.vmstat {
+ /* sorted by name to make maintenance easier */
+ allocstall 60:28:35
+ compact_blocks_moved 60:28:57
+ compact_fail 60:28:58
+ compact_pagemigrate_failed 60:28:59
+ compact_pages_moved 60:28:60
+ compact_stall 60:28:61
+ compact_success 60:28:62
+ htlb_buddy_alloc_fail 60:28:43
+ htlb_buddy_alloc_success 60:28:44
+ kswapd_inodesteal 60:28:33
+ kswapd_low_wmark_hit_quickly 60:28:87
+ kswapd_high_wmark_hit_quickly 60:28:88
+ kswapd_skip_congestion_wait 60:28:89
+ kswapd_steal 60:28:32
+ nr_active_anon 60:28:45
+ nr_active_file 60:28:46
+ nr_anon_pages 60:28:39
+ nr_anon_transparent_hugepages 60:28:90
+ nr_bounce 60:28:40
+ nr_dirtied 60:28:91
+ nr_dirty 60:28:0
+ nr_dirty_background_threshold 60:28:92
+ nr_dirty_threshold 60:28:93
+ nr_free_pages 60:28:47
+ nr_inactive_anon 60:28:48
+ nr_inactive_file 60:28:49
+ nr_isolated_anon 60:28:50
+ nr_isolated_file 60:28:51
+ nr_kernel_stack 60:28:52
+ nr_mapped 60:28:4
+ nr_mlock 60:28:53
+ nr_page_table_pages 60:28:3
+ nr_shmem 60:28:54
+ nr_slab 60:28:5
+ nr_slab_reclaimable 60:28:37
+ nr_slab_unreclaimable 60:28:38
+ nr_unevictable 60:28:55
+ nr_unstable 60:28:2
+ nr_vmscan_write 60:28:42
+ nr_writeback 60:28:1
+ nr_writeback_temp 60:28:56
+ nr_written 60:28:94
+ numa_foreign 60:28:95
+ numa_hit 60:28:96
+ numa_interleave 60:28:97
+ numa_local 60:28:98
+ numa_miss 60:28:99
+ numa_other 60:28:100
+ pageoutrun 60:28:34
+ pgactivate 60:28:14
+ pgalloc_dma 60:28:12
+ pgalloc_dma32 60:28:63
+ pgalloc_high 60:28:10
+ pgalloc_movable 60:28:64
+ pgalloc_normal 60:28:11
+ pgrefill_dma32 60:28:65
+ pgrefill_movable 60:28:66
+ pgdeactivate 60:28:15
+ pgfault 60:28:16
+ pgfree 60:28:13
+ pginodesteal 60:28:30
+ pgmajfault 60:28:17
+ pgpgin 60:28:6
+ pgpgout 60:28:7
+ pgrefill_dma 60:28:20
+ pgrefill_high 60:28:18
+ pgrefill_normal 60:28:19
+ pgrotated 60:28:36
+ pgscan_direct_dma 60:28:29
+ pgscan_direct_dma32 60:28:67
+ pgscan_direct_high 60:28:27
+ pgscan_direct_movable 60:28:68
+ pgscan_direct_normal 60:28:28
+ pgscan_kswapd_dma 60:28:26
+ pgscan_kswapd_dma32 60:28:69
+ pgscan_kswapd_high 60:28:24
+ pgscan_kswapd_movable 60:28:70
+ pgscan_kswapd_normal 60:28:25
+ pgsteal_dma 60:28:23
+ pgsteal_dma32 60:28:71
+ pgsteal_high 60:28:21
+ pgsteal_movable 60:28:72
+ pgsteal_normal 60:28:22
+ pswpin 60:28:8
+ pswpout 60:28:9
+ slabs_scanned 60:28:31
+ thp_fault_alloc 60:28:73
+ thp_fault_fallback 60:28:74
+ thp_collapse_alloc 60:28:75
+ thp_collapse_alloc_failed 60:28:76
+ thp_split 60:28:77
+ unevictable_pgs_cleared 60:28:78
+ unevictable_pgs_culled 60:28:79
+ unevictable_pgs_mlocked 60:28:80
+ unevictable_pgs_mlockfreed 60:28:81
+ unevictable_pgs_munlocked 60:28:82
+ unevictable_pgs_rescued 60:28:83
+ unevictable_pgs_scanned 60:28:84
+ unevictable_pgs_stranded 60:28:85
+ zone_reclaim_failed 60:28:86
+}
+
+vfs {
+ files
+ inodes
+ dentry
+}
+
+vfs.files {
+ count 60:27:0
+ free 60:27:1
+ max 60:27:2
+}
+
+vfs.inodes {
+ count 60:27:3
+ free 60:27:4
+}
+
+vfs.dentry {
+ count 60:27:5
+ free 60:27:6
+}
+
+sysfs {
+ kernel
+}
+
+sysfs.kernel {
+ uevent_seqnum 60:35:0
+}
+
diff --git a/src/pmdas/linux/sem_limits.c b/src/pmdas/linux/sem_limits.c
new file mode 100644
index 0000000..24e336e
--- /dev/null
+++ b/src/pmdas/linux/sem_limits.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ * This code contributed by Mike Mason <mmlnx@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define __USE_GNU 1 /* required for IPC_INFO define */
+#include <sys/ipc.h>
+#include <sys/sem.h>
+
+#include "pmapi.h"
+#include "sem_limits.h"
+
+int
+refresh_sem_limits(sem_limits_t *sem_limits)
+{
+ static int started;
+ static struct seminfo seminfo;
+ static union semun arg;
+
+ if (!started) {
+ started = 1;
+ memset(sem_limits, 0, sizeof(sem_limits_t));
+ arg.array = (unsigned short *) &seminfo;
+ }
+
+ if (semctl(0, 0, IPC_INFO, arg) < 0) {
+ return -oserror();
+ }
+
+ sem_limits->semmap = seminfo.semmap;
+ sem_limits->semmni = seminfo.semmni;
+ sem_limits->semmns = seminfo.semmns;
+ sem_limits->semmnu = seminfo.semmnu;
+ sem_limits->semmsl = seminfo.semmsl;
+ sem_limits->semopm = seminfo.semopm;
+ sem_limits->semume = seminfo.semume;
+ sem_limits->semusz = seminfo.semusz;
+ sem_limits->semvmx = seminfo.semvmx;
+ sem_limits->semaem = seminfo.semaem;
+ return 0;
+}
diff --git a/src/pmdas/linux/sem_limits.h b/src/pmdas/linux/sem_limits.h
new file mode 100644
index 0000000..ef0694e
--- /dev/null
+++ b/src/pmdas/linux/sem_limits.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Mike Mason (mmlnx@us.ibm.com)
+ */
+
+#ifdef _SEM_SEMUN_UNDEFINED
+/* glibc 2.1 no longer defines semun, instead it defines
+ * _SEM_SEMUN_UNDEFINED so users can define semun on their own.
+ */
+union semun {
+ int val;
+ struct semid_ds *buf;
+ unsigned short int *array;
+ struct seminfo *__buf;
+};
+#endif
+
+typedef struct {
+ unsigned int semmap; /* # of entries in semaphore map */
+ unsigned int semmni; /* max # of semaphore identifiers */
+ unsigned int semmns; /* max # of semaphores in system */
+ unsigned int semmnu; /* num of undo structures system wide */
+ unsigned int semmsl; /* max num of semaphores per id */
+ unsigned int semopm; /* max num of ops per semop call */
+ unsigned int semume; /* max num of undo entries per process */
+ unsigned int semusz; /* sizeof struct sem_undo */
+ unsigned int semvmx; /* semaphore maximum value */
+ unsigned int semaem; /* adjust on exit max value */
+} sem_limits_t;
+
+extern int refresh_sem_limits(sem_limits_t*);
+
diff --git a/src/pmdas/linux/shm_limits.c b/src/pmdas/linux/shm_limits.c
new file mode 100644
index 0000000..e1c80f0
--- /dev/null
+++ b/src/pmdas/linux/shm_limits.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ * This code contributed by Mike Mason <mmlnx@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define __USE_GNU 1 /* required for IPC_INFO define */
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include "pmapi.h"
+#include "shm_limits.h"
+
+int
+refresh_shm_limits(shm_limits_t *shm_limits)
+{
+ static int started;
+ static struct shminfo shminfo;
+
+ if (!started) {
+ started = 1;
+ memset(shm_limits, 0, sizeof(shm_limits_t));
+ }
+
+ if (shmctl(0, IPC_INFO, (struct shmid_ds *) &shminfo) < 0)
+ return -oserror();
+
+ shm_limits->shmmax = shminfo.shmmax;
+ shm_limits->shmmin = shminfo.shmmin;
+ shm_limits->shmmni = shminfo.shmmni;
+ shm_limits->shmseg = shminfo.shmseg;
+ shm_limits->shmall = shminfo.shmall;
+ return 0;
+}
diff --git a/src/pmdas/linux/shm_limits.h b/src/pmdas/linux/shm_limits.h
new file mode 100644
index 0000000..39d7f71
--- /dev/null
+++ b/src/pmdas/linux/shm_limits.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Mike Mason (mmlnx@us.ibm.com)
+ */
+
+typedef struct {
+ unsigned int shmmax; /* maximum shared segment size (bytes) */
+ unsigned int shmmin; /* minimum shared segment size (bytes) */
+ unsigned int shmmni; /* maximum number of segments system wide */
+ unsigned int shmseg; /* maximum shared segments per process */
+ unsigned int shmall; /* maximum shared memory system wide (pages) */
+} shm_limits_t;
+
+extern int refresh_shm_limits(shm_limits_t *);
+
diff --git a/src/pmdas/linux/swapdev.c b/src/pmdas/linux/swapdev.c
new file mode 100644
index 0000000..0b5521e
--- /dev/null
+++ b/src/pmdas/linux/swapdev.c
@@ -0,0 +1,72 @@
+/*
+ * Linux Swap Device Cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "swapdev.h"
+
+int
+refresh_swapdev(pmInDom swapdev_indom)
+{
+ char buf[MAXPATHLEN];
+ swapdev_t *swap;
+ FILE *fp;
+ char *path;
+ char *size;
+ char *used;
+ char *priority;
+ int sts;
+
+ pmdaCacheOp(swapdev_indom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = linux_statsfile("/proc/swaps", buf, sizeof(buf))) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (buf[0] != '/')
+ continue;
+ if ((path = strtok(buf, " \t")) == 0)
+ continue;
+ if ((/*type: */ strtok(NULL, " \t")) == NULL ||
+ (size = strtok(NULL, " \t")) == NULL ||
+ (used = strtok(NULL, " \t")) == NULL ||
+ (priority = strtok(NULL, " \t")) == NULL)
+ continue;
+ sts = pmdaCacheLookupName(swapdev_indom, path, NULL, (void **)&swap);
+ if (sts == PMDA_CACHE_ACTIVE) /* repeated line in /proc/swaps? */
+ continue;
+ if (sts == PMDA_CACHE_INACTIVE) { /* re-activate an old swap device */
+ pmdaCacheStore(swapdev_indom, PMDA_CACHE_ADD, path, swap);
+ }
+ else { /* new swap device */
+ if ((swap = malloc(sizeof(swapdev_t))) == NULL)
+ continue;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "refresh_swapdev: add \"%s\"\n", path);
+#endif
+ pmdaCacheStore(swapdev_indom, PMDA_CACHE_ADD, path, swap);
+ }
+ sscanf(size, "%u", &swap->size);
+ sscanf(used, "%u", &swap->used);
+ sscanf(priority, "%d", &swap->priority);
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux/swapdev.h b/src/pmdas/linux/swapdev.h
new file mode 100644
index 0000000..fa619a8
--- /dev/null
+++ b/src/pmdas/linux/swapdev.h
@@ -0,0 +1,28 @@
+/*
+ * Linux swap device Cluster
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct swapdev {
+ char *path;
+ unsigned int size;
+ unsigned int used;
+ int priority;
+} swapdev_t;
+
+extern int refresh_swapdev(pmInDom);
diff --git a/src/pmdas/linux/sysfs_kernel.c b/src/pmdas/linux/sysfs_kernel.c
new file mode 100644
index 0000000..8880634
--- /dev/null
+++ b/src/pmdas/linux/sysfs_kernel.c
@@ -0,0 +1,41 @@
+/*
+ * Linux sysfs_kernel cluster
+ *
+ * Copyright (c) 2009,2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "sysfs_kernel.h"
+#include "indom.h"
+
+int
+refresh_sysfs_kernel(sysfs_kernel_t *sk)
+{
+ char buf[MAXPATHLEN];
+ int fd, n;
+
+ snprintf(buf, sizeof(buf), "%s/sys/kernel/uevent_seqnum", linux_statspath);
+ if ((fd = open(buf, O_RDONLY)) < 0) {
+ sk->valid_uevent_seqnum = 0;
+ return -oserror();
+ }
+
+ if ((n = read(fd, buf, sizeof(buf))) <= 0)
+ sk->valid_uevent_seqnum = 0;
+ else {
+ buf[n-1] = '\0';
+ sscanf(buf, "%llu", (long long unsigned int *)&sk->uevent_seqnum);
+ sk->valid_uevent_seqnum = 1;
+ }
+ close(fd);
+ return 0;
+}
diff --git a/src/pmdas/linux/sysfs_kernel.h b/src/pmdas/linux/sysfs_kernel.h
new file mode 100644
index 0000000..b43445d
--- /dev/null
+++ b/src/pmdas/linux/sysfs_kernel.h
@@ -0,0 +1,34 @@
+/*
+ * Linux sysfs_kernel cluster
+ *
+ * Copyright (c) 2009, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _SYSFS_KERNEL_H
+#define _SYSFS_KERNEL_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <ctype.h>
+
+typedef struct {
+ int valid_uevent_seqnum;
+ uint64_t uevent_seqnum; /* /sys/kernel/uevent_seqnum */
+ /* TODO queue length, event type counters and other metrics */
+} sysfs_kernel_t;
+
+/* refresh sysfs_kernel */
+extern int refresh_sysfs_kernel(sysfs_kernel_t *);
+
+#endif /* _SYSFS_KERNEL_H */
diff --git a/src/pmdas/linux_proc/GNUmakefile b/src/pmdas/linux_proc/GNUmakefile
new file mode 100644
index 0000000..97dc518
--- /dev/null
+++ b/src/pmdas/linux_proc/GNUmakefile
@@ -0,0 +1,89 @@
+#
+# Copyright (c) 2000,2003,2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2007-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2013-2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = proc
+DOMAIN = PROC
+CMDTARGET = pmdaproc
+LIBTARGET = pmda_proc.so
+PMDAINIT = proc_init
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "proc 3 pipe binary $(PMDADIR)/$(CMDTARGET) -d 3"
+
+CFILES = pmda.c \
+ cgroups.c proc_pid.c proc_runq.c ksym.c getinfo.c contexts.c
+
+HFILES = clusters.h indom.h \
+ cgroups.h proc_pid.h proc_runq.h ksym.h getinfo.h contexts.h
+
+SCRIPTS = Install Remove
+VERSION_SCRIPT = exports
+HELPTARGETS = help.dir help.pag
+LSRCFILES = help root root_proc linux_proc_migrate.conf $(SCRIPTS)
+LDIRT = $(HELPTARGETS) domain.h $(VERSION_SCRIPT)
+
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+
+# Uncomment these flags for profiling
+# LCFLAGS += -pg
+# LLDFLAGS += -pg
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) $(HELPTARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help help.dir help.pag root root_proc $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 root_proc $(PCP_VAR_DIR)/pmns/root_proc
+ $(INSTALL) -m 644 linux_proc_migrate.conf $(PCP_VAR_DIR)/config/pmlogrewrite/linux_proc_migrate.conf
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(HELPTARGETS) : help
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_proc -v 2 -o help < help
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+cgroups.o pmda.o: clusters.h
+cgroups.o pmda.o: cgroups.h
+cgroups.o pmda.o proc_pid.o proc_runq.o: proc_pid.h
+pmda.o proc_runq.o: proc_runq.h
+indom.o pmda.o: indom.h
+ksym.o pmda.o: ksym.h
+pmda.o: domain.h
+pmda.o: getinfo.h
+pmda.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/linux_proc/Install b/src/pmdas/linux_proc/Install
new file mode 100755
index 0000000..74fa225
--- /dev/null
+++ b/src/pmdas/linux_proc/Install
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Linux per-process (proc) PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=proc
+pmda_interface=6
+daemon_opt=true
+pipe_opt=true
+pmns_source=root_proc
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/linux_proc/Remove b/src/pmdas/linux_proc/Remove
new file mode 100755
index 0000000..4befc73
--- /dev/null
+++ b/src/pmdas/linux_proc/Remove
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Linux per-process (proc) PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=proc
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/linux_proc/cgroups.c b/src/pmdas/linux_proc/cgroups.c
new file mode 100644
index 0000000..4994465
--- /dev/null
+++ b/src/pmdas/linux_proc/cgroups.c
@@ -0,0 +1,1146 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "cgroups.h"
+#include "clusters.h"
+#include "proc_pid.h"
+#include <sys/stat.h>
+#include <ctype.h>
+
+#define CGROUP_ROOT "cgroup.groups" /* root dynamic PMNS node */
+
+/* Add namespace entries and prepare values for one cgroupfs directory entry */
+struct cgroup_subsys;
+typedef int (*cgroup_prepare_t)(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+static int prepare_ull(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+static int prepare_string(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+static int prepare_named_ull(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+static int prepare_block_ull(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+static int prepare_blocks_ull(__pmnsTree *, const char *,
+ struct cgroup_subsys *, const char *, int, int, int);
+
+/*
+ * Critical data structures for cgroup subsystem in pmdaproc ...
+ * Initial comment for each struct talks about lifecycle of that
+ * data, in terms of what pmdaproc must do with it (esp. memory
+ * allocation related).
+ */
+
+typedef struct { /* contents depends on individual kernel cgroups */
+ int item; /* PMID == domain:cluster:[id:item] */
+ int dynamic; /* do we need an extra free (string) */
+ cgroup_prepare_t prepare; /* setup metric name(s) and value(s) */
+ char *suffix; /* cpus/mems/rss/... */
+} cgroup_metrics_t;
+
+typedef struct { /* some metrics are multi-valued, but most have only one */
+ int item; /* PMID == domain:cluster:[id:item] */
+ int atom_count;
+ pmAtomValue *atoms;
+} cgroup_values_t;
+
+typedef struct { /* contains data for each group users have created, if any */
+ int id; /* PMID == domain:cluster:[id:item] */
+ int refreshed; /* boolean: are values all uptodate */
+ proc_pid_list_t process_list;
+ cgroup_values_t *metric_values;
+} cgroup_group_t;
+
+typedef struct cgroup_subsys { /* contents covers the known kernel cgroups */
+ const char *name; /* cpuset/memory/... */
+ int cluster; /* PMID == domain:cluster:[id:item] */
+ int group_count; /* number of groups (dynamic) */
+ int metric_count; /* number of metrics (fixed) */
+ time_t previous_time; /* used to avoid repeated refresh */
+ cgroup_group_t *groups; /* array of groups (dynamic) */
+ cgroup_metrics_t *metrics; /* array of metrics (fixed) */
+} cgroup_subsys_t;
+
+static cgroup_metrics_t cpusched_metrics[] = {
+ { .suffix = "shares", .prepare = prepare_ull },
+};
+
+static cgroup_metrics_t cpuacct_metrics[] = {
+ { .suffix = "stat.user", .prepare = prepare_named_ull },
+ { .suffix = "stat.system", .prepare = prepare_named_ull },
+ { .suffix = "usage", .prepare = prepare_ull },
+ { .suffix = "usage_percpu", .prepare = prepare_ull },
+};
+
+static cgroup_metrics_t cpuset_metrics[] = {
+ { .suffix = "io_merged", .prepare = prepare_string },
+ { .suffix = "sectors", .prepare = prepare_string },
+};
+
+static cgroup_metrics_t memory_metrics[] = {
+ { .suffix = "stat.cache", .prepare = prepare_named_ull },
+ { .suffix = "stat.rss", .prepare = prepare_named_ull },
+ { .suffix = "stat.rss_huge", .prepare = prepare_named_ull },
+ { .suffix = "stat.mapped_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.writeback", .prepare = prepare_named_ull },
+ { .suffix = "stat.swap", .prepare = prepare_named_ull },
+ { .suffix = "stat.pgpgin", .prepare = prepare_named_ull },
+ { .suffix = "stat.pgpgout", .prepare = prepare_named_ull },
+ { .suffix = "stat.pgfault", .prepare = prepare_named_ull },
+ { .suffix = "stat.pgmajfault", .prepare = prepare_named_ull },
+ { .suffix = "stat.inactive_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.active_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.inactive_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.active_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.unevictable", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_cache", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_rss", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_rss_huge", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_mapped_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_writeback", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_swap", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_pgpgin", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_pgpgout", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_pgfault", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_pgmajfault", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_inactive_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_active_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_inactive_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_active_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.total_unevictable", .prepare = prepare_named_ull },
+ { .suffix = "stat.recent_rotated_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.recent_rotated_file", .prepare = prepare_named_ull },
+ { .suffix = "stat.recent_scanned_anon", .prepare = prepare_named_ull },
+ { .suffix = "stat.recent_scanned_file", .prepare = prepare_named_ull },
+};
+
+static cgroup_metrics_t netclass_metrics[] = {
+ { .suffix = "classid", .prepare = prepare_ull },
+};
+
+static cgroup_metrics_t blkio_metrics[] = {
+ { .suffix = "io_merged.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_merged.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_merged.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_merged.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_merged.total", .prepare = prepare_blocks_ull },
+ { .suffix = "io_queued.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_queued.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_queued.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_queued.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_queued.total", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_bytes.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_bytes.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_bytes.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_bytes.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_bytes.total", .prepare = prepare_blocks_ull },
+ { .suffix = "io_serviced.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_serviced.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_serviced.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_serviced.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_serviced.total", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_time.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_time.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_time.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_time.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_service_time.total", .prepare = prepare_blocks_ull },
+ { .suffix = "io_wait_time.read", .prepare = prepare_blocks_ull },
+ { .suffix = "io_wait_time.write", .prepare = prepare_blocks_ull },
+ { .suffix = "io_wait_time.sync", .prepare = prepare_blocks_ull },
+ { .suffix = "io_wait_time.async", .prepare = prepare_blocks_ull },
+ { .suffix = "io_wait_time.total", .prepare = prepare_blocks_ull },
+ { .suffix = "sectors", .prepare = prepare_block_ull },
+ { .suffix = "time", .prepare = prepare_block_ull },
+};
+
+static const char *block_stats_names[] = \
+ { "read", "write", "sync", "async", "total" };
+#define BLKIOS (sizeof(block_stats_names)/sizeof(block_stats_names[0]))
+
+static cgroup_subsys_t controllers[] = {
+ { .name = "cpu",
+ .cluster = CLUSTER_CPUSCHED_GROUPS,
+ .metrics = cpusched_metrics,
+ .metric_count = sizeof(cpusched_metrics) / sizeof(cgroup_metrics_t),
+ },
+ { .name = "cpuset",
+ .cluster = CLUSTER_CPUSET_GROUPS,
+ .metrics = cpuset_metrics,
+ .metric_count = sizeof(cpuset_metrics) / sizeof(cgroup_metrics_t),
+ },
+ { .name = "cpuacct",
+ .cluster = CLUSTER_CPUACCT_GROUPS,
+ .metrics = cpuacct_metrics,
+ .metric_count = sizeof(cpuacct_metrics) / sizeof(cgroup_metrics_t),
+ },
+ { .name = "memory",
+ .cluster = CLUSTER_MEMORY_GROUPS,
+ .metrics = memory_metrics,
+ .metric_count = sizeof(memory_metrics) / sizeof(cgroup_metrics_t),
+ },
+ { .name = "net_cls",
+ .cluster = CLUSTER_NET_CLS_GROUPS,
+ .metrics = netclass_metrics,
+ .metric_count = sizeof(netclass_metrics) / sizeof(cgroup_metrics_t),
+ },
+ { .name = "blkio",
+ .cluster = CLUSTER_BLKIO_GROUPS,
+ .metrics = blkio_metrics,
+ .metric_count = sizeof(blkio_metrics) / sizeof(cgroup_metrics_t),
+ },
+};
+
+/*
+ * Data structures used by individual cgroup subsystem controllers
+ */
+typedef struct {
+ __uint32_t major;
+ __uint32_t minor;
+ int inst;
+ char *name;
+} device_t;
+
+typedef struct {
+ device_t dev;
+ __uint64_t values[BLKIOS]; /* read, write, sync, async, total */
+} block_stats_t;
+
+typedef struct filesys {
+ int id;
+ char *device;
+ char *path;
+ char *options;
+} filesys_t;
+
+void
+refresh_cgroup_cpus(pmInDom indom)
+{
+ char buf[MAXPATHLEN];
+ char *space;
+ FILE *fp;
+
+ pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+ if ((fp = proc_statsfile("/proc/stat", buf, sizeof(buf))) == NULL)
+ return;
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "cpu", 3) == 0 && isdigit((int)buf[3])) {
+ if ((space = strchr(buf, ' ')) != NULL) {
+ *space = '\0';
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, buf, NULL);
+ }
+ }
+ }
+ fclose(fp);
+}
+
+static int
+_pm_isloop(char *dname)
+{
+ return strncmp(dname, "loop", 4) == 0;
+}
+
+static int
+_pm_isramdisk(char *dname)
+{
+ return strncmp(dname, "ram", 3) == 0;
+}
+
+/*
+ * For block devices we have one instance domain for dev_t
+ * based lookup, and another for (real) name lookup.
+ * The reason we need this is that the blkio cgroup stats
+ * are exported using the major:minor numbers, and not the
+ * device names - we must perform that mapping ourselves.
+ * In some places (value refresh) we need to lookup the blk
+ * name from device major/minor, in other places (instances
+ * refresh) we need the usual external instid:name lookup.
+ */
+void
+refresh_cgroup_devices(pmInDom diskindom)
+{
+ pmInDom devtindom = INDOM(DEVT_INDOM);
+ char buf[MAXPATHLEN];
+ static time_t before;
+ time_t now;
+ FILE *fp;
+
+ if ((now = time(NULL)) == before)
+ return;
+ before = now;
+
+ pmdaCacheOp(devtindom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(diskindom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = proc_statsfile("/proc/diskstats", buf, sizeof(buf))) == NULL)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ unsigned int major, minor, unused;
+ device_t *dev = NULL;
+ char namebuf[1024];
+ int inst;
+
+ if (sscanf(buf, "%u %u %s %u", &major, &minor, namebuf, &unused) != 4)
+ continue;
+ if (_pm_isloop(namebuf) || _pm_isramdisk(namebuf))
+ continue;
+ if (pmdaCacheLookupName(diskindom, namebuf, &inst, (void **)&dev) < 0 ||
+ dev == NULL) {
+ if (!(dev = (device_t *)malloc(sizeof(device_t)))) {
+ __pmNoMem("device", sizeof(device_t), PM_RECOV_ERR);
+ continue;
+ }
+ dev->major = major;
+ dev->minor = minor;
+ }
+ /* keeping track of all fields (major/minor/inst/name) */
+ pmdaCacheStore(diskindom, PMDA_CACHE_ADD, namebuf, dev);
+ pmdaCacheLookupName(diskindom, namebuf, &dev->inst, NULL);
+ pmdaCacheLookup(diskindom, dev->inst, &dev->name, NULL);
+
+ snprintf(buf, sizeof(buf), "%u:%u", major, minor);
+ pmdaCacheStore(devtindom, PMDA_CACHE_ADD, buf, (void *)dev);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "refresh_devices: \"%s\" \"%d:%d\" inst=%d\n",
+ dev->name, dev->major, dev->minor, dev->inst);
+ }
+ fclose(fp);
+}
+
+void
+refresh_cgroup_subsys(pmInDom indom)
+{
+ char buf[4096];
+ static time_t before;
+ time_t now;
+ FILE *fp;
+
+ if ((now = time(NULL)) == before)
+ return;
+ before = now;
+
+ if ((fp = proc_statsfile("/proc/cgroups", buf, sizeof(buf))) == NULL)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ unsigned int numcgroups, enabled;
+ char name[MAXPATHLEN];
+ long hierarchy;
+ long *data;
+ int sts;
+
+ /* skip lines starting with hash (header) */
+ if (buf[0] == '#')
+ continue;
+ if (sscanf(buf, "%s %ld %u %u", &name[0],
+ &hierarchy, &numcgroups, &enabled) != 4)
+ continue;
+ sts = pmdaCacheLookupName(indom, name, NULL, (void **)&data);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ if (*data != hierarchy) {
+ /*
+ * odd ... instance name repeated but different
+ * hierarchy ... we cannot support more than one hierarchy
+ * yet
+ */
+ fprintf(stderr, "refresh_cgroup_subsys: \"%s\": entries for hierarchy %ld ignored (hierarchy %ld seen first)\n", name, hierarchy, *data);
+ }
+ continue;
+ }
+ else if (sts != PMDA_CACHE_INACTIVE) {
+ if ((data = (long *)malloc(sizeof(long))) == NULL) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "refresh_cgroup_subsys: \"%s\": malloc failed\n", name);
+#endif
+ continue;
+ }
+ *data = hierarchy;
+ }
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, name, (void *)data);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "refresh_cgroup_subsys: add \"%s\" [hierarchy %ld]\n", name, hierarchy);
+#endif
+ }
+ fclose(fp);
+}
+
+void
+refresh_cgroup_filesys(pmInDom indom)
+{
+ char buf[MAXPATHLEN];
+ filesys_t *fs;
+ FILE *fp;
+ time_t now;
+ static time_t before;
+ char *path, *device, *type, *options;
+ int sts;
+
+ if ((now = time(NULL)) == before)
+ return;
+ before = now;
+
+ pmdaCacheOp(indom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = proc_statsfile("/proc/mounts", buf, sizeof(buf))) == NULL)
+ return;
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ device = strtok(buf, " ");
+ path = strtok(NULL, " ");
+ type = strtok(NULL, " ");
+ options = strtok(NULL, " ");
+ if (strcmp(type, "cgroup") != 0)
+ continue;
+
+ sts = pmdaCacheLookupName(indom, path, NULL, (void **)&fs);
+ if (sts == PMDA_CACHE_ACTIVE) /* repeated line in /proc/mounts? */
+ continue;
+ if (sts == PMDA_CACHE_INACTIVE) { /* re-activate an old mount */
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, path, fs);
+ if (strcmp(path, fs->path) != 0) { /* old device, new path */
+ free(fs->path);
+ fs->path = strdup(path);
+ }
+ if (strcmp(options, fs->options) != 0) { /* old device, new opts */
+ free(fs->options);
+ fs->options = strdup(options);
+ }
+ }
+ else { /* new mount */
+ if ((fs = malloc(sizeof(filesys_t))) == NULL)
+ continue;
+ fs->path = strdup(path);
+ fs->options = strdup(options);
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "refresh_filesys: add \"%s\" \"%s\"\n",
+ fs->path, device);
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, path, fs);
+ }
+ }
+ fclose(fp);
+}
+
+static char *
+scan_filesys_options(const char *options, const char *option)
+{
+ static char buffer[128];
+ char *s;
+
+ strncpy(buffer, options, sizeof(buffer));
+ buffer[sizeof(buffer)-1] = '\0';
+
+ s = strtok(buffer, ",");
+ while (s) {
+ if (strcmp(s, option) == 0)
+ return s;
+ s = strtok(NULL, ",");
+ }
+ return NULL;
+}
+
+static int
+read_values(char *buffer, int size, const char *path, const char *subsys,
+ const char *metric)
+{
+ int fd, count;
+
+ snprintf(buffer, size, "%s/%s.%s", path, subsys, metric);
+ if ((fd = open(buffer, O_RDONLY)) < 0)
+ return -oserror();
+ count = read(fd, buffer, size);
+ close(fd);
+ if (count < 0)
+ return -oserror();
+ buffer[count-1] = '\0';
+ return 0;
+}
+
+static pmID
+update_pmns(__pmnsTree *pmns, cgroup_subsys_t *subsys, const char *name,
+ cgroup_metrics_t *metrics, int group, int domain)
+{
+ char entry[MAXPATHLEN];
+ pmID pmid;
+
+ snprintf(entry, sizeof(entry), "%s.%s%s.%s",
+ CGROUP_ROOT, subsys->name, name, metrics->suffix);
+ pmid = cgroup_pmid_build(domain, subsys->cluster, group, metrics->item);
+ __pmAddPMNSNode(pmns, pmid, entry);
+ return pmid;
+}
+
+static int
+prepare_ull(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain)
+{
+ int count = 0;
+ unsigned long long value;
+ char buffer[MAXPATHLEN];
+ char *endp, *p = &buffer[0];
+ cgroup_group_t *groups = &subsys->groups[group];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+ pmAtomValue *atoms = groups->metric_values[metric].atoms;
+
+ if (read_values(p, sizeof(buffer), path, subsys->name, metrics->suffix) < 0)
+ return -oserror();
+
+ while (p && *p) {
+ value = strtoull(p, &endp, 0);
+ if ((atoms = realloc(atoms, (count + 1) * sizeof(pmAtomValue))) == NULL)
+ return -oserror();
+ atoms[count++].ull = value;
+ if (endp == '\0' || endp == p)
+ break;
+ p = endp;
+ while (p && isspace((int)*p))
+ p++;
+ }
+
+ groups->metric_values[metric].item = metric;
+ groups->metric_values[metric].atoms = atoms;
+ groups->metric_values[metric].atom_count = count;
+ update_pmns(pmns, subsys, name, metrics, group, domain);
+ return 0;
+}
+
+static int
+prepare_named_ull(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain)
+{
+ int i, count;
+ unsigned long long value;
+ char filename[64], buffer[MAXPATHLEN];
+ char *offset, *p = &buffer[0];
+ cgroup_group_t *groups = &subsys->groups[group];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+
+ /* metric => e.g. stat.user and stat.system - split it up first */
+ offset = index(metrics->suffix, '.');
+ if (!offset)
+ return PM_ERR_CONV;
+ count = (offset - metrics->suffix);
+ strncpy(filename, metrics->suffix, count);
+ filename[count] = '\0';
+
+ if (read_values(p, sizeof(buffer), path, subsys->name, filename) < 0)
+ return -oserror();
+
+ /* buffer contains <name> <value> pairs */
+ while (p && *p) {
+ char *endp, *field, *offset;
+
+ if ((field = index(p, ' ')) == NULL)
+ return PM_ERR_CONV;
+ offset = field + 1;
+ *field = '\0';
+ field = p; /* field now points to <name> */
+ p = offset;
+ value = strtoull(p, &endp, 0);
+ p = endp;
+ while (p && isspace((int)*p))
+ p++;
+
+ for (i = 0; i < subsys->metric_count; i++) {
+ pmAtomValue *atoms = groups->metric_values[i].atoms;
+ metrics = &subsys->metrics[i];
+
+ if (strcmp(field, metrics->suffix + count + 1) != 0)
+ continue;
+ if ((atoms = groups->metric_values[i].atoms) == NULL)
+ if ((atoms = calloc(1, sizeof(pmAtomValue))) == NULL)
+ return -oserror();
+ atoms[0].ull = value;
+
+ groups->metric_values[i].item = i;
+ groups->metric_values[i].atoms = atoms;
+ groups->metric_values[i].atom_count = 1;
+ update_pmns(pmns, subsys, name, metrics, group, domain);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int
+prepare_block(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain,
+ block_stats_t *stats, int value_count)
+{
+ pmID pmid;
+ char *iname;
+ char buf[MAXPATHLEN];
+ device_t *dev;
+ pmAtomValue *atoms;
+ int count, size, inst, sts, m, i, j;
+ pmInDom devtindom = INDOM(DEVT_INDOM);
+ cgroup_group_t *groups = &subsys->groups[group];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+
+ /* map major:minor to real device name via diskstats */
+ dev = &stats->dev;
+ snprintf(buf, sizeof(buf), "%u:%u", dev->major, dev->minor);
+
+ sts = pmdaCacheLookupName(devtindom, buf, NULL, (void **)&dev);
+ iname = dev->name;
+ inst = dev->inst;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "prepare_block: preparing %s found=%s (%s)\n",
+ buf, sts == PMDA_CACHE_ACTIVE ? "ok" : "no", iname);
+
+ /* batch update metric value(s) now, since we have 'em all */
+ for (j = 0; j < value_count; j++) {
+ m = metric + j;
+ atoms = groups->metric_values[m].atoms;
+ count = groups->metric_values[m].atom_count;
+
+ if (inst >= count) {
+ size = (inst + 1) * sizeof(pmAtomValue);
+ if ((atoms = realloc(atoms, size)) == NULL)
+ return -oserror();
+ for (i = count; i < inst + 1; i++)
+ atoms[i].ull = ULLONG_MAX;
+ count = inst + 1;
+ }
+ /* move on-stack value into global struct, add to PMNS */
+ atoms[inst].ull = stats->values[j];
+ pmid = update_pmns(pmns, subsys, name, metrics + j, group, domain);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "prepare_block: prepared "
+ "metric=%s inst=%s[%d] value=%llu\n",
+ pmIDStr(pmid), iname, inst,
+ (unsigned long long)atoms[inst].ull);
+
+ groups->metric_values[m].item = m;
+ groups->metric_values[m].atoms = atoms;
+ groups->metric_values[m].atom_count = count;
+ }
+ return 0;
+}
+
+static int
+prepare_block_ull(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain)
+{
+ char buf[MAXPATHLEN];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+ block_stats_t stats;
+ FILE *fp;
+ char *p;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "prepare_block_ull: %s metric=%d group=%d domain=%d\n",
+ path, metric, group, domain);
+
+ snprintf(buf, sizeof(buf), "%s/%s.%s", path, subsys->name, metrics->suffix);
+ if ((fp = fopen(buf, "r")) == NULL)
+ return -oserror();
+
+ memset(&stats, 0, sizeof(stats));
+ while ((fgets(buf, sizeof(buf), fp)) != NULL) {
+ if (sscanf(buf, "%u:%u ", &stats.dev.major, &stats.dev.minor) != 2)
+ continue;
+ for (p = buf; *p && !isspace(*p); p++) { } /* skip device number */
+ for (p = buf; *p && isspace(*p); p++) { } /* skip over spaces */
+ if (sscanf(p, "%llu", (unsigned long long *)&stats.values[0]) != 1)
+ stats.values[0] = 0;
+ prepare_block(pmns, path, subsys, name,
+ metric, group, domain, &stats, 1);
+ }
+ fclose(fp);
+ return 0;
+}
+
+static int
+prepare_blocks_ull(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain)
+{
+ char buf[MAXPATHLEN];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+ block_stats_t stats;
+ FILE *fp;
+ char *p;
+ int j;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "prepare_blocks_ull: %s metric=%d group=%d domain=%d\n",
+ path, metric, group, domain);
+
+ if (metric % BLKIOS != 0)
+ return 0;
+
+ snprintf(buf, sizeof(buf), "%s/%s.%s", path, subsys->name, metrics->suffix);
+ buf[strlen(buf) - sizeof("read")] = '\0';
+
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "prepare_blocks_ull: opening \"%s\"\n", buf);
+
+ if ((fp = fopen(buf, "r")) == NULL)
+ return -oserror();
+
+ memset(&stats, 0, sizeof(stats));
+ while ((fgets(buf, sizeof(buf), fp)) != NULL) {
+ if (sscanf(buf, "%u:%u ", &stats.dev.major, &stats.dev.minor) != 2)
+ continue;
+
+ /* iterate over read/write/sync/async/total (reverse for async) */
+ for (j = BLKIOS-1; j >= 0; j--) {
+ if ((p = strcasestr(buf, block_stats_names[j])) == NULL)
+ continue;
+ p += strlen(block_stats_names[j]) + 1;
+ if (sscanf(p, "%llu", (unsigned long long *)&stats.values[j]) != 1)
+ stats.values[j] = 0;
+ break;
+ }
+
+ if (j == BLKIOS - 1) { /* Total: last one, update incore structures */
+ prepare_block(pmns, path, subsys, name,
+ metric, group, domain, &stats, BLKIOS);
+ /* reset on-stack structure for next outer loop iteration */
+ memset(&stats, 0, sizeof(stats));
+ }
+ }
+ fclose(fp);
+ return 0;
+}
+
+static int
+prepare_string(__pmnsTree *pmns, const char *path, cgroup_subsys_t *subsys,
+ const char *name, int metric, int group, int domain)
+{
+ char buffer[MAXPATHLEN];
+ cgroup_group_t *groups = &subsys->groups[group];
+ cgroup_metrics_t *metrics = &subsys->metrics[metric];
+ pmAtomValue *atoms = groups->metric_values[metric].atoms;
+ char *p = &buffer[0];
+
+ if (read_values(p, sizeof(buffer), path, subsys->name, metrics->suffix) < 0)
+ return -oserror();
+
+ if ((atoms = malloc(sizeof(pmAtomValue))) == NULL)
+ return -oserror();
+ if ((atoms[0].cp = strdup(buffer)) == NULL) {
+ free(atoms);
+ return -oserror();
+ }
+ groups->metric_values[metric].item = metric;
+ groups->metric_values[metric].atoms = atoms;
+ groups->metric_values[metric].atom_count = 1;
+ update_pmns(pmns, subsys, name, metrics, group, domain);
+ return 0;
+}
+
+static void
+translate(char *dest, const char *src, size_t size)
+{
+ char *p;
+
+ if (*src != '\0') /* non-root */
+ *dest = '.';
+ strncpy(dest, src, size);
+ for (p = dest; *p; p++) {
+ if (*p == '/')
+ *p = '.';
+ }
+}
+
+static int
+namespace(__pmnsTree *pmns, cgroup_subsys_t *subsys,
+ const char *cgrouppath, const char *cgroupname, int domain)
+{
+ int i, id;
+ size_t size;
+ cgroup_values_t *cvp;
+ char group[128];
+
+ translate(&group[0], cgroupname, sizeof(group));
+
+ /* allocate space for this group */
+ size = (subsys->group_count + 1) * sizeof(cgroup_group_t);
+ subsys->groups = (cgroup_group_t *)realloc(subsys->groups, size);
+ if (subsys->groups == NULL)
+ return -oserror();
+
+ /* allocate space for all values up-front */
+ size = subsys->metric_count;
+ cvp = (cgroup_values_t *)calloc(size, sizeof(cgroup_values_t));
+ if (cvp == NULL)
+ return -oserror();
+
+ id = subsys->group_count++;
+ memset(&subsys->groups[id], 0, sizeof(cgroup_group_t));
+ subsys->groups[id].id = id;
+ subsys->groups[id].metric_values = cvp;
+
+ for (i = 0; i < size; i++) {
+ cgroup_metrics_t *metrics = &subsys->metrics[i];
+ metrics->prepare(pmns, cgrouppath, subsys, group, i, id, domain);
+ }
+ return 1;
+}
+
+char *
+cgroup_find_subsys(pmInDom indom, void *data)
+{
+ static char dunno[] = "?";
+ static char opts[256];
+ char buffer[256];
+ char *s, *out = NULL;
+ filesys_t *fs = (filesys_t *)data;
+
+ memset(opts, 0, sizeof(opts));
+ strncpy(buffer, fs->options, sizeof(buffer));
+
+ s = strtok(buffer, ",");
+ while (s) {
+ if (pmdaCacheLookupName(indom, s, NULL, NULL) == PMDA_CACHE_ACTIVE) {
+ if (out) { /* append option */
+ strcat(out, ",");
+ strcat(out, s);
+ out += strlen(s) + 1; /* +1 => cater for comma */
+ } else { /* first option */
+ strcat(opts, s);
+ out = opts + strlen(s);
+ }
+ }
+ s = strtok(NULL, ",");
+ }
+ if (out)
+ return opts;
+ return dunno;
+}
+
+/* Ensure cgroup name can be used as a PCP namespace entry, ignore it if not */
+static int
+valid_pmns_name(char *name)
+{
+ if (!isalpha((int)name[0]))
+ return 0;
+ for (; *name != '\0'; name++)
+ if (!isalnum((int)*name) && *name != '_')
+ return 0;
+ return 1;
+}
+
+static int
+cgroup_scan(const char *mnt, const char *path, cgroup_subsys_t *subsys,
+ int domain, __pmnsTree *pmns, int root)
+{
+ int sts, length;
+ DIR *dirp;
+ struct stat sbuf;
+ struct dirent *dp;
+ char *cgroupname;
+ char cgrouppath[MAXPATHLEN];
+
+ if (root) {
+ snprintf(cgrouppath, sizeof(cgrouppath), "%s%s", proc_statspath, mnt);
+ length = strlen(cgrouppath);
+ } else {
+ snprintf(cgrouppath, sizeof(cgrouppath), "%s%s/%s", proc_statspath, mnt, path);
+ length = strlen(proc_statspath) + strlen(mnt) + 1;
+ }
+
+ if ((dirp = opendir(cgrouppath)) == NULL)
+ return -oserror();
+
+ cgroupname = &cgrouppath[length];
+ sts = namespace(pmns, subsys, cgrouppath, cgroupname, domain);
+
+ /*
+ * readdir - descend into directories to find all cgroups, then
+ * populate namespace with <controller>[.<groupname>].<metrics>
+ */
+ while ((dp = readdir(dirp)) != NULL) {
+ int lsts;
+ if (!valid_pmns_name(dp->d_name))
+ continue;
+ if (path[0] == '\0')
+ snprintf(cgrouppath, sizeof(cgrouppath), "%s%s/%s",
+ proc_statspath, mnt, dp->d_name);
+ else
+ snprintf(cgrouppath, sizeof(cgrouppath), "%s%s/%s/%s",
+ proc_statspath, mnt, path, dp->d_name);
+ cgroupname = &cgrouppath[length];
+ if (stat(cgrouppath, &sbuf) < 0)
+ continue;
+ if (!(S_ISDIR(sbuf.st_mode)))
+ continue;
+
+ lsts = namespace(pmns, subsys, cgrouppath, cgroupname, domain);
+ if (lsts > 0)
+ sts = 1;
+
+ /*
+ * also scan for any child cgroups, but cgroup_scan() may return
+ * an error
+ */
+ lsts = cgroup_scan(mnt, cgroupname, subsys, domain, pmns, 0);
+ if (lsts > 0)
+ sts = 1;
+ }
+ closedir(dirp);
+ return sts;
+}
+
+static void
+reset_subsys_stats(cgroup_subsys_t *subsys)
+{
+ int g, k, a;
+
+ for (g = 0; g < subsys->group_count; g++) {
+ cgroup_group_t *group = &subsys->groups[g];
+ for (k = 0; k < subsys->metric_count; k++) {
+ pmAtomValue *atoms = group->metric_values[k].atoms;
+ if (subsys->metrics[k].dynamic)
+ for (a = 0; a < group->metric_values[k].atom_count; a++)
+ free(atoms[a].cp);
+ free(atoms);
+ }
+ free(group->metric_values);
+ if (group->process_list.size)
+ free(group->process_list.pids);
+ memset(group, 0, sizeof(cgroup_group_t));
+ }
+ subsys->group_count = 0;
+}
+
+int
+refresh_cgroups(pmdaExt *pmda, __pmnsTree **pmns)
+{
+ int i, sts, mtab = 0;
+ int domain = pmda->e_domain;
+ filesys_t *fs;
+ time_t now;
+ static time_t before;
+ static __pmnsTree *beforetree;
+ __pmnsTree *tree = pmns ? *pmns : NULL;
+ pmInDom mounts = INDOM(CGROUP_MOUNTS_INDOM);
+ pmInDom devices = INDOM(DISK_INDOM);
+
+ now = time(NULL);
+ if (tree) {
+ if (now == before) {
+ *pmns = beforetree;
+ return 0;
+ }
+ } else if (now == before)
+ return 0;
+
+ refresh_cgroup_filesys(mounts);
+ refresh_cgroup_devices(devices);
+
+ if (tree)
+ __pmFreePMNS(tree);
+
+ if ((sts = __pmNewPMNS(&tree)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n",
+ pmProgname, pmErrStr(sts));
+ return 0;
+ }
+
+ for (i = 0; i < sizeof(controllers)/sizeof(controllers[0]); i++) {
+ cgroup_subsys_t *subsys = &controllers[i];
+
+ /*
+ * Fetch latest state for subsystem and groups of the given clusters,
+ * by walking the cgroup mounts, finding the mounts of this subsystem
+ * type, and descending into all of the groups (subdirs)
+ */
+ reset_subsys_stats(subsys);
+
+ pmdaCacheOp(mounts, PMDA_CACHE_WALK_REWIND);
+ while ((sts = pmdaCacheOp(mounts, PMDA_CACHE_WALK_NEXT)) != -1) {
+ if (!pmdaCacheLookup(mounts, sts, NULL, (void **)&fs))
+ continue;
+ if (scan_filesys_options(fs->options, subsys->name) == NULL)
+ continue;
+ sts = cgroup_scan(fs->path, "", subsys, domain, tree, 1);
+ if (sts > 0)
+ mtab = 1;
+ }
+ }
+
+ if (pmns) {
+ *pmns = tree;
+ beforetree = tree;
+ before = now;
+ } else
+ __pmFreePMNS(tree);
+
+ return mtab;
+}
+
+/*
+ * Shared fetch callback for all cgroups metrics
+ */
+int
+cgroup_group_fetch(pmID pmid, unsigned int inst, pmAtomValue *atom)
+{
+ int i, j, k;
+ int gid, cluster, metric;
+
+ gid = cgroup_pmid_group(pmid);
+ metric = cgroup_pmid_metric(pmid);
+ cluster = proc_pmid_cluster(pmid);
+
+ for (i = 0; i < sizeof(controllers)/sizeof(controllers[0]); i++) {
+ cgroup_subsys_t *subsys = &controllers[i];
+
+ if (subsys->cluster != cluster)
+ continue;
+ for (j = 0; j < subsys->group_count; j++) {
+ cgroup_group_t *group = &subsys->groups[j];
+
+ if (group->id != gid)
+ continue;
+ for (k = 0; k < subsys->metric_count; k++) {
+ cgroup_values_t *cvp = &group->metric_values[k];
+
+ if (cvp->item != metric)
+ continue;
+ else if (cvp->atom_count <= 0)
+ return PM_ERR_VALUE;
+ else if (inst == PM_IN_NULL)
+ inst = 0;
+ else if (inst >= cvp->atom_count)
+ return PM_ERR_INST;
+ else if (cvp->atoms[inst].ull == ULLONG_MAX)
+ return PM_ERR_INST;
+ *atom = cvp->atoms[inst];
+ return 1;
+ }
+ }
+ }
+ return PM_ERR_PMID;
+}
+
+/*
+ * Needs to answer the question: how much extra space needs to be allocated
+ * in the metric table for (dynamic) cgroup metrics"? We have static entries
+ * for group ID zero - if we have any non-zero group IDs, we need entries to
+ * cover those. Return value is the number of additional entries needed.
+ */
+static void
+size_metrictable(int *total, int *trees)
+{
+ int i, g, maxgroup = 0, nmetrics = 0;
+
+ for (i = 0; i < sizeof(controllers)/sizeof(controllers[0]); i++) {
+ cgroup_subsys_t *subsys = &controllers[i];
+
+ for (g = 0; g < subsys->group_count; g++) {
+ cgroup_group_t *group = &subsys->groups[g];
+
+ if (group->id > maxgroup)
+ maxgroup = group->id;
+ }
+ nmetrics += subsys->metric_count + 0; /* +1 for task.pid */
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "size_metrictable: %d total x %d trees\n",
+ nmetrics, maxgroup);
+
+ *total = nmetrics;
+ *trees = maxgroup;
+}
+
+/*
+ * Create new metric table entry for a group based on an existing one.
+ */
+static void
+refresh_metrictable(pmdaMetric *source, pmdaMetric *dest, int gid)
+{
+ int domain = pmid_domain(source->m_desc.pmid);
+ int cluster = proc_pmid_cluster(source->m_desc.pmid);
+ int item = pmid_item(source->m_desc.pmid);
+
+ memcpy(dest, source, sizeof(pmdaMetric));
+ dest->m_desc.pmid = cgroup_pmid_build(domain, cluster, gid, item);
+
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "refresh_metrictable: (%p -> %p)\n", source, dest);
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "cgroup metric ID dup: %d.[%d.%d].%d - %d.[%d.%d].%d\n",
+ domain, cluster,
+ cgroup_pmid_group(source->m_desc.pmid),
+ cgroup_pmid_metric(source->m_desc.pmid),
+ pmid_domain(dest->m_desc.pmid),
+ proc_pmid_cluster(dest->m_desc.pmid),
+ cgroup_pmid_group(dest->m_desc.pmid),
+ cgroup_pmid_metric(dest->m_desc.pmid));
+}
+
+static int
+cgroup_text(pmdaExt *pmda, pmID pmid, int type, char **buf)
+{
+ return PM_ERR_TEXT;
+}
+
+static void
+cgroup_metrics_init(pmdaMetric *metrics, int nmetrics)
+{
+ int i, j, item, cluster = 0;
+
+ for (i = 0; i < sizeof(controllers)/sizeof(controllers[0]); i++) {
+ cgroup_subsys_t *subsys = &controllers[i];
+
+ /* set initial default values for controller metrics item field */
+ for (j = 0; j < subsys->metric_count; j++)
+ subsys->metrics[j].item = j;
+
+ /* set initial seed values for dynamic PMIDs in global metric table */
+ for (j = item = 0; j < nmetrics; j++) {
+ if (pmid_cluster(metrics[j].m_desc.pmid) == subsys->cluster) {
+ if (cluster != subsys->cluster) {
+ cluster = subsys->cluster;
+ item = 0;
+ }
+ metrics[j].m_desc.pmid = PMDA_PMID(cluster, item++);
+ }
+ }
+ }
+}
+
+void
+cgroup_init(pmdaMetric *metrics, int nmetrics)
+{
+ static int set[] = {
+ CLUSTER_BLKIO_GROUPS,
+ CLUSTER_CPUSET_GROUPS,
+ CLUSTER_CPUACCT_GROUPS,
+ CLUSTER_CPUSCHED_GROUPS,
+ CLUSTER_MEMORY_GROUPS,
+ CLUSTER_NET_CLS_GROUPS,
+ };
+
+ cgroup_metrics_init(metrics, nmetrics);
+
+ pmdaDynamicPMNS(CGROUP_ROOT,
+ set, sizeof(set) / sizeof(set[0]),
+ refresh_cgroups, cgroup_text,
+ refresh_metrictable, size_metrictable,
+ metrics, nmetrics);
+ pmdaDynamicSetClusterMask(CGROUP_ROOT, CGROUP_MASK);
+}
diff --git a/src/pmdas/linux_proc/cgroups.h b/src/pmdas/linux_proc/cgroups.h
new file mode 100644
index 0000000..d2ec430
--- /dev/null
+++ b/src/pmdas/linux_proc/cgroups.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _CGROUP_H
+#define _CGROUP_H
+
+/*
+ * Note: cgroup metrics have an "extra" component - the cluster part
+ * of the PMID (12 bits) is split into two (6 bits each): the bottom
+ * part contains the regular metric (cluster) ID while the top holds
+ * the cgroup ID (index - e.g. this is the 3rd cgroup we've seen for
+ * a particular subsystem).
+ */
+
+#define CGROUP_SPLIT 6
+#define CGROUP_MASK ((1 << CGROUP_SPLIT) - 1)
+
+static inline pmID
+cgroup_pmid_build(unsigned int domain, unsigned int cluster,
+ unsigned int gid, unsigned int metric)
+{
+ return pmid_build(domain, (gid << CGROUP_SPLIT) | cluster, metric);
+}
+
+static inline unsigned int
+cgroup_pmid_group(pmID id)
+{
+ return pmid_cluster(id) >> CGROUP_SPLIT;
+}
+
+static inline unsigned int
+proc_pmid_cluster(pmID id)
+{
+ return pmid_cluster(id) & CGROUP_MASK;
+}
+
+static inline unsigned int
+cgroup_pmid_metric(pmID id)
+{
+ return pmid_item(id);
+}
+
+/*
+ * General cgroup interfaces
+ */
+extern void cgroup_init(pmdaMetric *, int);
+extern char *cgroup_find_subsys(pmInDom, void *);
+extern int cgroup_group_fetch(pmID, unsigned int, pmAtomValue *);
+
+/*
+ * Metric name and value refresh interfaces
+ */
+extern int refresh_cgroups(pmdaExt *, __pmnsTree **);
+
+/*
+ * Indom-specific interfaces
+ */
+extern void refresh_cgroup_cpus(pmInDom);
+extern void refresh_cgroup_devices(pmInDom);
+extern void refresh_cgroup_filesys(pmInDom);
+extern void refresh_cgroup_subsys(pmInDom);
+
+#endif /* _CGROUP_H */
diff --git a/src/pmdas/linux_proc/clusters.h b/src/pmdas/linux_proc/clusters.h
new file mode 100644
index 0000000..e1c8c2a
--- /dev/null
+++ b/src/pmdas/linux_proc/clusters.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _CLUSTERS_H
+#define _CLUSTERS_H
+
+/*
+ * fetch cluster numbers ... to manage the PMID migration after the
+ * linux -> linux + proc PMDAs split, these need to match the enum
+ * assigned values for CLUSTER_* from the linux PMDA.
+ */
+#define CLUSTER_PID_STAT 8 /* /proc/<pid>/stat */
+#define CLUSTER_PID_STATM 9 /* /proc/<pid>/statm + /proc/<pid>/maps */
+#define CLUSTER_CONTROL 10 /* instance + value fetch control metrics */
+#define CLUSTER_PID_CGROUP 11 /* /proc/<pid>/cgroup */
+#define CLUSTER_PID_LABEL 12 /* /proc/<pid>/attr/current (label) */
+#define CLUSTER_PROC_RUNQ 13 /* number of processes in various states */
+#define CLUSTER_PID_STATUS 24 /* /proc/<pid>/status */
+#define CLUSTER_PID_SCHEDSTAT 31 /* /proc/<pid>/schedstat */
+#define CLUSTER_PID_IO 32 /* /proc/<pid>/io */
+#define CLUSTER_CGROUP_SUBSYS 37 /* /proc/cgroups control group subsystems */
+#define CLUSTER_CGROUP_MOUNTS 38 /* /proc/mounts active control groups */
+#define CLUSTER_CPUSET_GROUPS 39 /* cpuset control groups */
+#define CLUSTER_CPUACCT_GROUPS 41 /* cpu accounting control groups */
+#define CLUSTER_CPUSCHED_GROUPS 43 /* scheduler control groups */
+#define CLUSTER_MEMORY_GROUPS 45 /* memory control groups */
+#define CLUSTER_NET_CLS_GROUPS 47 /* network classification control groups */
+#define CLUSTER_BLKIO_GROUPS 49 /* blkio control groups */
+#define CLUSTER_PID_FD 51 /* /proc/<pid>/fd */
+ /* Note: do not use higher than (1 << CGROUP_SPLIT)-1 as cluster ID */
+
+#define MIN_CLUSTER 8 /* first cluster number we use here */
+#define NUM_CLUSTERS 52 /* one more than highest cluster number used */
+#define MAX_CLUSTER 63 /* last available - fill gaps if more needed */
+
+#endif /* _CLUSTERS_H */
diff --git a/src/pmdas/linux_proc/contexts.c b/src/pmdas/linux_proc/contexts.c
new file mode 100644
index 0000000..f213c14
--- /dev/null
+++ b/src/pmdas/linux_proc/contexts.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "contexts.h"
+
+static proc_perctx_t *ctxtab;
+static int num_ctx;
+static uid_t baseuid;
+static gid_t basegid;
+
+static void
+proc_ctx_clear(int ctx)
+{
+ ctxtab[ctx].state = CTX_INACTIVE;
+ ctxtab[ctx].uid = -1;
+ ctxtab[ctx].gid = -1;
+ ctxtab[ctx].threads = 1;
+ ctxtab[ctx].cgroups = NULL;
+}
+
+void
+proc_ctx_end(int ctx)
+{
+ if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE)
+ return;
+ if (ctxtab[ctx].state & CTX_CGROUPS)
+ free((void *)ctxtab[ctx].cgroups);
+ proc_ctx_clear(ctx);
+}
+
+static void
+proc_ctx_growtab(int ctx)
+{
+ size_t need;
+
+ if (ctx < num_ctx)
+ return;
+
+ need = (ctx + 1) * sizeof(ctxtab[0]);
+ ctxtab = (proc_perctx_t *)realloc(ctxtab, need);
+ if (ctxtab == NULL)
+ __pmNoMem("proc ctx table", need, PM_FATAL_ERR);
+ while (num_ctx <= ctx)
+ proc_ctx_clear(num_ctx++);
+}
+
+static void
+proc_ctx_set_userid(int ctx, const char *value)
+{
+ proc_ctx_growtab(ctx);
+ ctxtab[ctx].uid = atoi(value);
+ ctxtab[ctx].state |= (CTX_ACTIVE | CTX_USERID);
+}
+
+static void
+proc_ctx_set_groupid(int ctx, const char *value)
+{
+ proc_ctx_growtab(ctx);
+ ctxtab[ctx].gid = atoi(value);
+ ctxtab[ctx].state |= (CTX_ACTIVE | CTX_GROUPID);
+}
+
+int
+proc_ctx_attrs(int ctx, int attr, const char *value, int length, pmdaExt *pmda)
+{
+ if (pmDebug & DBG_TRACE_AUTH) {
+ char buffer[256];
+
+ if (!__pmAttrStr_r(attr, value, buffer, sizeof(buffer))) {
+ __pmNotifyErr(LOG_ERR, "Bad Attribute: ctx=%d, attr=%d\n", ctx, attr);
+ } else {
+ buffer[sizeof(buffer)-1] = '\0';
+ __pmNotifyErr(LOG_INFO, "Attribute: ctx=%d %s", ctx, buffer);
+ }
+ }
+
+ switch (attr) {
+ case PCP_ATTR_USERID:
+ proc_ctx_set_userid(ctx, value);
+ break;
+ case PCP_ATTR_GROUPID:
+ proc_ctx_set_groupid(ctx, value);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+void
+proc_ctx_init(void)
+{
+ baseuid = getuid();
+ basegid = getgid();
+}
+
+int
+proc_ctx_access(int ctx)
+{
+ proc_perctx_t *pp;
+ int accessible = 0;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return accessible;
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return accessible;
+
+ if (pp->state & CTX_GROUPID) {
+ accessible++;
+ if (basegid != pp->gid) {
+ if (setegid(pp->gid) < 0) {
+ __pmNotifyErr(LOG_ERR, "setegid(%d) access failed: %s\n",
+ pp->gid, osstrerror());
+ accessible--;
+ }
+ }
+ }
+ if (pp->state & CTX_USERID) {
+ accessible++;
+ if (baseuid != pp->uid) {
+ if (seteuid(pp->uid) < 0) {
+ __pmNotifyErr(LOG_ERR, "seteuid(%d) access failed: %s\n",
+ pp->uid, osstrerror());
+ accessible--;
+ }
+ }
+ }
+ return (accessible > 1);
+}
+
+int
+proc_ctx_revert(int ctx)
+{
+ proc_perctx_t *pp;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return 0;
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return 0;
+
+ if ((pp->state & CTX_USERID) && baseuid != pp->uid) {
+ if (seteuid(baseuid) < 0)
+ __pmNotifyErr(LOG_ERR, "seteuid(%d) revert failed: %s\n",
+ baseuid, osstrerror());
+ }
+ if ((pp->state & CTX_GROUPID) && basegid != pp->gid) {
+ if (setegid(basegid) < 0)
+ __pmNotifyErr(LOG_ERR, "setegid(%d) revert failed: %s\n",
+ basegid, osstrerror());
+ }
+ return 0;
+}
+
+unsigned int
+proc_ctx_threads(int ctx, unsigned int threads)
+{
+ proc_perctx_t *pp;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return threads; /* fallback to default */
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return threads; /* fallback to default */
+
+ if (pp->state & CTX_THREADS)
+ return pp->threads; /* client setting */
+
+ return threads; /* fallback to default */
+}
+
+int
+proc_ctx_set_threads(int ctx, unsigned int threads)
+{
+ proc_perctx_t *pp;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return PM_ERR_NOCONTEXT;
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return PM_ERR_NOCONTEXT;
+ if (threads > 1)
+ return PM_ERR_CONV;
+
+ pp->state |= CTX_THREADS;
+ pp->threads = threads;
+ return 0;
+}
+
+const char *
+proc_ctx_cgroups(int ctx, const char *cgroups)
+{
+ proc_perctx_t *pp;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return cgroups; /* fallback to default */
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return cgroups; /* fallback to default */
+
+ if (pp->state & CTX_CGROUPS)
+ return pp->cgroups; /* client setting */
+
+ return cgroups; /* fallback to default */
+}
+
+int
+proc_ctx_set_cgroups(int ctx, const char *cgroups)
+{
+ proc_perctx_t *pp;
+
+ if (ctx < 0 || ctx >= num_ctx)
+ return PM_ERR_NOCONTEXT;
+ pp = &ctxtab[ctx];
+ if (pp->state == CTX_INACTIVE)
+ return PM_ERR_NOCONTEXT;
+ if (cgroups == NULL || cgroups[0] == '\0')
+ return PM_ERR_CONV;
+
+ pp->state |= CTX_CGROUPS;
+ pp->cgroups = cgroups;
+ return 0;
+}
diff --git a/src/pmdas/linux_proc/contexts.h b/src/pmdas/linux_proc/contexts.h
new file mode 100644
index 0000000..c2abe8c
--- /dev/null
+++ b/src/pmdas/linux_proc/contexts.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _CONTEXTS_H
+#define _CONTEXTS_H
+
+/*
+ * Handle newly arriving clients, security attributes being set on 'em,
+ * switching to alternative accounts (temporarily) and back, and client
+ * termination. State maintained in a global table, with a high-water
+ * allocator and active/inactive entry tracking.
+ *
+ * The proc.control.perclient metrics also have state tracked here now.
+ */
+
+enum {
+ CTX_INACTIVE = 0x0,
+ CTX_ACTIVE = 0x1,
+ CTX_USERID = 0x2,
+ CTX_GROUPID = 0x4,
+ CTX_THREADS = 0x8,
+ CTX_CGROUPS = 0x10,
+};
+
+typedef struct {
+ unsigned int state;
+ uid_t uid;
+ gid_t gid;
+ unsigned int threads;
+ const char *cgroups;
+} proc_perctx_t;
+
+extern void proc_ctx_init(void);
+extern int proc_ctx_attrs(int, int, const char *, int, pmdaExt *);
+extern void proc_ctx_end(int);
+
+extern int proc_ctx_access(int);
+extern int proc_ctx_revert(int);
+
+extern unsigned int proc_ctx_threads(int, unsigned int);
+extern int proc_ctx_set_threads(int, unsigned int);
+
+extern const char *proc_ctx_cgroups(int, const char *);
+extern int proc_ctx_set_cgroups(int, const char *);
+
+#endif /* _CONTEXTS_H */
diff --git a/src/pmdas/linux_proc/getinfo.c b/src/pmdas/linux_proc/getinfo.c
new file mode 100644
index 0000000..b4633a5
--- /dev/null
+++ b/src/pmdas/linux_proc/getinfo.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include <sys/dir.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include "pmapi.h"
+
+char *
+get_ttyname_info(int pid, dev_t dev, char *ttyname)
+{
+ DIR *dir;
+ struct dirent *dp;
+ struct stat sbuf;
+ int found=0;
+ char procpath[MAXPATHLEN];
+ char ttypath[MAXPATHLEN];
+
+ sprintf(procpath, "/proc/%d/fd", pid);
+ if ((dir = opendir(procpath)) != NULL) {
+ while ((dp = readdir(dir)) != NULL) {
+ if (!isdigit((int)dp->d_name[0]))
+ continue;
+ sprintf(procpath, "/proc/%d/fd/%s", pid, dp->d_name);
+ if (realpath(procpath, ttypath) == NULL || stat(ttypath, &sbuf) < 0)
+ continue;
+ if (S_ISCHR(sbuf.st_mode) && dev == sbuf.st_rdev) {
+ found=1;
+ break;
+ }
+ }
+ closedir(dir);
+ }
+
+ if (!found)
+ strcpy(ttyname, "?");
+ else
+ /* skip the "/dev/" prefix */
+ strcpy(ttyname, &ttypath[5]);
+
+ return ttyname;
+}
diff --git a/src/pmdas/linux_proc/getinfo.h b/src/pmdas/linux_proc/getinfo.h
new file mode 100644
index 0000000..9006c00
--- /dev/null
+++ b/src/pmdas/linux_proc/getinfo.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+extern char *get_ttyname_info(int, dev_t, char *);
+
diff --git a/src/pmdas/linux_proc/help b/src/pmdas/linux_proc/help
new file mode 100644
index 0000000..6640a08
--- /dev/null
+++ b/src/pmdas/linux_proc/help
@@ -0,0 +1,220 @@
+#
+# Copyright (c) 2000,2004-2008 Silicon Graphics, Inc. All Rights Reserved.
+# Portions Copyright (c) International Business Machines Corp., 2002
+# Portions Copyright (c) 2007-2009 Aconex. All Rights Reserved.
+# Portions Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Linux proc PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ cgroup.subsys.hierarchy subsystem hierarchy from /proc/cgroups
+@ cgroup.subsys.count count of known subsystems in /proc/cgroups
+@ cgroup.mounts.subsys mount points for each cgroup subsystem
+@ cgroup.mounts.count count of cgroup filesystem mount points
+
+@ proc.nprocs instantaneous number of processes
+@ proc.psinfo.pid process identifier
+@ proc.psinfo.psargs full command string
+@ proc.psinfo.cmd command name
+@ proc.psinfo.sname process state identifier (see ps(1)). See also proc.runq metrics.
+@ proc.psinfo.ppid parent process identifier
+@ proc.psinfo.pgrp process group identifier
+@ proc.psinfo.session process session identifier
+@ proc.psinfo.tty controlling tty device number (zero if none)
+@ proc.psinfo.tty_pgrp controlling tty process group identifier
+@ proc.psinfo.flags process state flags, as a bitmap
+@ proc.psinfo.minflt count of minor page faults (i.e. reclaims)
+@ proc.psinfo.cmin_flt count of minor page faults (i.e. reclaims) of all exited children
+@ proc.psinfo.maj_flt count of page faults other than reclaims
+@ proc.psinfo.cmaj_flt count of page faults other than reclaims of all exited children
+@ proc.psinfo.utime time (in ms) spent executing user code since process started
+@ proc.psinfo.stime time (in ms) spent executing system code (calls) since process started
+@ proc.psinfo.cutime time (in ms) spent executing user code of all exited children
+@ proc.psinfo.cstime time (in ms) spent executing system code of all exited children
+@ proc.psinfo.priority priority value
+@ proc.psinfo.nice process nice value (negative nice values are lower priority)
+@ proc.psinfo.it_real_value current interval timer value (zero if none)
+@ proc.psinfo.start_time start time of the process relative to system boot time in seconds
+@ proc.psinfo.vsize virtual size of the process in Kbytes
+@ proc.psinfo.rss resident set size (i.e. physical memory) of the process
+@ proc.psinfo.rss_rlim limit on resident set size of process
+@ proc.psinfo.start_code address of the start of the code segment for the process
+@ proc.psinfo.end_code address of the end of the code segment for the process
+@ proc.psinfo.start_stack address of the stack segment for the process
+@ proc.psinfo.esp the value in the esp field of struct task_struct for the process
+@ proc.psinfo.eip the value in the eip field of struct task_struct for the process
+@ proc.psinfo.signal the value in the signal field of struct task_struct for the process
+@ proc.psinfo.blocked the value in the blocked field of struct task_struct for the process
+@ proc.psinfo.sigignore the value in the sigignore field of struct task_struct for the process
+@ proc.psinfo.sigcatch the value in the sigcatch field of struct task_struct for the process
+@ proc.psinfo.wchan wait channel, kernel address this process is blocked or sleeping on
+@ proc.psinfo.nswap count of page swap operations
+@ proc.psinfo.cnswap count of page swap operations of all exited children
+@ proc.psinfo.exit_signal the value in the exit_signal field of struct task_struct for the process
+@ proc.psinfo.ttyname name of controlling tty device, or "?" if none. See also proc.psinfo.tty.
+@ proc.psinfo.processor last CPU the process was running on
+@ proc.psinfo.wchan_s name of an event for which the process is sleeping (if blank, the process is running).
+This field needs access to a namelist file for proper
+address-to-symbol name translation. If no namelist file
+is available, the address is printed instead. The namelist
+file must match the current Linux kernel exactly.
+The search path for the namelist file is as follows:
+ /boot/System.map-`uname -r`
+ /boot/System.map
+ /lib/modules/`uname -r`/System.map
+ /usr/src/linux/System.map
+ /System.map
+@ proc.psinfo.signal_s pending signals mask in string form (from /proc/<pid>/status)
+@ proc.psinfo.blocked_s blocked signals mask in string form (from /proc/<pid>/status)
+@ proc.psinfo.sigignore_s ignored signals mask in string form (from /proc/<pid>/status)
+@ proc.psinfo.sigcatch_s caught signals mask in string form (from /proc/<pid>/status)
+@ proc.psinfo.threads number of threads (from /proc/<pid>/status)
+@ proc.psinfo.cgroups list of processes cgroups (from /proc/<pid>/cgroup)
+@ proc.psinfo.labels list of processes security labels (from /proc/<pid>/attr/current)
+@ proc.memory.size instantaneous virtual size of process, excluding page table and task structure.
+@ proc.memory.rss instantaneous resident size of process, excluding page table and task structure.
+@ proc.memory.share instantaneous amount of memory shared by this process with other processes
+@ proc.memory.textrss instantaneous resident size of process code segment in Kbytes
+@ proc.memory.librss instantaneous resident size of library code mapped by the process, in Kbytes
+@ proc.memory.datrss instantaneous resident size of process data segment, in Kbytes
+@ proc.memory.dirty instantaneous amount of memory that has been modified by the process, in Kbytes
+@ proc.memory.maps table of memory mapped by process in string form from /proc/<pid>/maps
+@ proc.memory.vmsize total virtual memory (from /proc/<pid>/status)
+@ proc.memory.vmlock locked virtual memory (from /proc/<pid>/status)
+@ proc.memory.vmrss resident virtual memory (from /proc/<pid>/status)
+@ proc.memory.vmdata virtual memory used for data (from /proc/<pid>/status)
+@ proc.memory.vmstack virtual memory used for stack (from /proc/<pid>/status)
+@ proc.memory.vmexe virtual memory used for non-library executable code (from /proc/<pid>/status)
+@ proc.memory.vmlib virtual memory used for libraries (from /proc/<pid>/status)
+@ proc.memory.vmswap virtual memory that has been brought in and out.
+@ proc.id.uid real user ID from /proc/<pid>/status
+@ proc.id.euid effective user ID from /proc/<pid>/status
+@ proc.id.suid saved user ID from /proc/<pid>/status
+@ proc.id.fsuid filesystem user ID from /proc/<pid>/status
+@ proc.id.gid real group ID from /proc/<pid>/status
+@ proc.id.egid effective group ID from /proc/<pid>/status
+@ proc.id.sgid saved group ID from /proc/<pid>/status
+@ proc.id.fsgid filesystem group ID from /proc/<pid>/status
+@ proc.id.uid_nm real user name based on real user ID from /proc/<pid>/status
+@ proc.id.euid_nm effective user name based on effective user ID from /proc/<pid>/status
+@ proc.id.suid_nm saved user name based on saved user ID from /proc/<pid>/status
+@ proc.id.fsuid_nm filesystem user name based on filesystem user ID from /proc/<pid>/status
+@ proc.id.gid_nm real group name based on real group ID from /proc/<pid>/status
+@ proc.id.egid_nm effective group name based on effective group ID from /proc/<pid>/status
+@ proc.id.sgid_nm saved group name based on saved group ID from /proc/<pid>/status
+@ proc.id.fsgid_nm filesystem group name based on filesystem group ID from /proc/<pid>/status
+
+@ proc.runq.runnable number of runnable (on run queue) processes
+Instantaneous number of runnable (on run queue) processes, state 'R' in ps
+@ proc.runq.blocked number of processes in uninterruptible sleep
+Instantaneous number of processes in uninterruptible sleep, state 'D' in ps
+@ proc.runq.sleeping number of processes sleeping
+Instantaneous number of processes sleeping, state 'S' in ps
+@ proc.runq.stopped number of traced, stopped or suspended processes
+Instantaneous number of traced, stopped or suspended processes, state
+'T' in ps
+@ proc.runq.swapped number of processes that are swapped
+Instantaneous number of processes (excluding kernel threads) that are
+swapped, state 'SW' in ps
+@ proc.runq.defunct number of defunct/zombie processes
+Instantaneous number of defunct/zombie processes, state 'Z' in ps
+@ proc.runq.unknown number of processes is an unknown state
+Instantaneous number of processes is an unknown state, including all
+kernel threads
+@ proc.runq.kernel number of kernel threads
+Instantaneous number of processes with virtual size of zero (kernel threads)
+
+@ proc.io.rchar read(), readv() and sendfile() receive bytes
+Extended accounting information - count of the number of bytes that
+have passed over the read(2), readv(2) and sendfile(2) syscalls by
+each process.
+
+@ proc.io.wchar write(), writev() and sendfile() send bytes
+Extended accounting information - count of the number of bytes that
+have passed over the write(2), writev(2) and sendfile(2) syscalls by
+each process.
+
+@ proc.io.syscr read(), readv() and sendfile() receive system calls
+Extended accounting information - count of number of calls to the
+read(2), readv(2) and sendfile(2) syscalls by each process.
+
+@ proc.io.syscw write(), writev() and sendfile() send system calls
+Extended accounting information - count of number of calls to the
+write(2), writev(2) and sendfile(2) syscalls by each process.
+
+@ proc.io.read_bytes physical device read bytes
+Number of bytes physically read on by devices on behalf of this process.
+@ proc.io.write_bytes physical device write bytes
+Number of bytes physically written to devices on behalf of this process.
+This must be reduced by any truncated I/O (proc.io.cancelled_write_bytes).
+@ proc.io.cancelled_write_bytes physical device write cancelled bytes
+Number of bytes cancelled via truncate by this process. Actual physical
+writes for an individual process can be calculated as:
+ proc.io.write_bytes - proc.io.cancelled_write_bytes.
+
+@ proc.schedstat.cpu_time runnable (scheduled) + run time
+Length of time in nanoseconds that a process has been running, including
+scheduling time.
+@ proc.schedstat.run_delay run queue time
+Length of time in nanoseconds that a process spent waiting to be scheduled
+to run in the run queue.
+@ proc.schedstat.pcount number of times a process is allowed to run
+Number of times a process has been scheduled to run on a CPU (this is
+incremented when a task actually reaches a CPU to run on, not simply
+when it is added to the run queue).
+
+@ proc.fd.count open file descriptors
+Number of file descriptors this process has open.
+
+@ proc.control.all.threads process indom includes threads
+If set to one, the process instance domain as reported by pmdaproc
+contains all threads as well as the processes that started them.
+If set to zero, the process instance domain contains only processes.
+
+This setting is persistent for the life of pmdaproc and affects all
+client tools that request instances and values from pmdaproc.
+Use either pmstore(1) or pmStore(3) to modify this metric.
+
+@ proc.control.perclient.threads for a client, process indom includes threads
+If set to one, the process instance domain as reported by pmdaproc
+contains all threads as well as the processes that started them.
+If set to zero, the process instance domain contains only processes.
+
+This setting is only visible to the active client context. In other
+words, storing into this metric has no effect for other monitoring
+tools. See proc.control.all.threads, if that is the desired outcome.
+Only pmStore(3) can effectively set this metric (pmstore(1) cannot).
+
+@ proc.control.perclient.cgroups for a client, process indom reflects specific cgroups
+If set to the empty string (the default), the process instance domain
+as reported by pmdaproc contains all processes. However, a cgroup
+name (full path) can be stored into this metric in order to restrict
+processes reported to only those within the specified cgroup. This
+set is further affected by the value of proc.control.perclient.threads.
+
+This setting is only visible to the active client context. In other
+words, storing into this metric has no effect for other monitoring
+tools. pmStore(3) must be used to set this metric (not pmstore(1)).
diff --git a/src/pmdas/linux_proc/indom.h b/src/pmdas/linux_proc/indom.h
new file mode 100644
index 0000000..9c928cd
--- /dev/null
+++ b/src/pmdas/linux_proc/indom.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _INDOM_H
+#define _INDOM_H
+
+/*
+ * indom serial numbers ... to manage the indom migration after the
+ * linux -> linux + proc PMDAs split, these need to match the enum
+ * assigned values for *_INDOM from the linux PMDA. Consequently,
+ * the proc indom table is sparse.
+ */
+#define CPU_INDOM 0 /* - percpu */
+#define DISK_INDOM 1 /* - disks (with normal names) */
+#define DEVT_INDOM 2 /* - disks (major:minor names) */
+#define PROC_INDOM 9 /* - processes */
+#define STRINGS_INDOM 10 /* - fake indom, string hash */
+#define CGROUP_SUBSYS_INDOM 20 /* - control group subsystems */
+#define CGROUP_MOUNTS_INDOM 21 /* - control group mounts */
+
+#define MIN_INDOM 0 /* first indom number we use here */
+#define NUM_INDOMS 22 /* one more than highest indom number we use here */
+
+extern pmInDom proc_indom(int);
+#define INDOM(i) proc_indom(i)
+
+/*
+ * Optional path prefix for all stats files, used for testing.
+ */
+extern char *proc_statspath;
+extern FILE *proc_statsfile(const char *, char *, int);
+
+/*
+ * static string dictionary - one copy of oft-repeated strings;
+ * implemented using STRINGS_INDOM and pmdaCache(3) routines.
+ */
+char *proc_strings_lookup(int);
+int proc_strings_insert(const char *);
+
+#endif /* _INDOM_H */
diff --git a/src/pmdas/linux_proc/ksym.c b/src/pmdas/linux_proc/ksym.c
new file mode 100644
index 0000000..1604c84
--- /dev/null
+++ b/src/pmdas/linux_proc/ksym.c
@@ -0,0 +1,564 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ * Copyright (c) 2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * This code originally contributed by Mike Mason <mmlnx@us.ibm.com>
+ * with hints from the procps and ksymoops projects.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <sys/utsname.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "ksym.h"
+#include "indom.h"
+
+static struct ksym *ksym_a;
+static size_t ksym_a_sz;
+
+static int
+find_index(__psint_t addr, int lo, int hi)
+{
+ int mid;
+
+ if (lo > hi) {
+ return -1;
+ }
+
+ mid = lo + ((hi - lo) / 2);
+ if (addr == ksym_a[mid].addr ||
+ (addr > ksym_a[mid].addr && addr < ksym_a[mid+1].addr)) {
+ return mid;
+ }
+
+ if (addr > ksym_a[mid].addr)
+ return find_index(addr, mid+1, hi);
+ else
+ return find_index(addr, lo, mid-1);
+}
+
+static char *
+find_name_by_addr(__psint_t addr)
+{
+ int ix = -1;
+
+ if (ksym_a)
+ ix = find_index(addr, 0, ksym_a_sz - 1);
+ if (ix < 0)
+ return NULL;
+
+ return ksym_a[ix].name;
+}
+
+static int
+find_dup_name(int maxix, __psint_t addr, char *name)
+{
+ int i, res;
+
+ for (i = 0; i < maxix; i++) {
+ if (ksym_a[i].name) {
+ res = strcmp(ksym_a[i].name, name);
+ if (res > 0)
+ break;
+ if (res == 0) {
+ if (addr == ksym_a[i].addr)
+ return KSYM_FOUND;
+ else
+ return KSYM_FOUND_MISMATCH;
+ }
+ }
+ }
+
+ return KSYM_NOT_FOUND;
+}
+
+/* Brute force linear search to determine if the kernel version
+ in System.map matches the running kernel version and returns
+ a tri-state result as follows:
+
+ 0 no match
+ 1 _end not found but version matched
+ 2 _end found and matched
+ */
+static int
+validate_sysmap(FILE *fp, char *version, __psint_t end_addr)
+{
+ __psint_t addr;
+ char type;
+ int ret = 0;
+ char kname[128];
+
+ while (fscanf(fp, "%p %c %s", (void **)&addr, &type, kname) != EOF) {
+ if (end_addr && strcmp(kname, "_end") == 0) {
+ ret = (end_addr == addr) ? 2 : 0;
+ break; /* no need to look any further */
+ }
+ if (strcmp(kname, version) == 0)
+ ret = 1;
+ }
+
+ return ret;
+}
+
+char *
+wchan(__psint_t addr)
+{
+ static char zero;
+ char *p = NULL;
+
+ if (addr == 0) /* 0 address means not in kernel space */
+ p = &zero;
+ else if ((p = find_name_by_addr(addr))) {
+ /* strip off "sys_" or leading "_"s if necessary */
+ if (strncmp(p, "sys_", 4) == 0)
+ p += 4;
+ while (*p == '_' && *p)
+ ++p;
+ }
+
+ return p;
+}
+
+static int
+ksym_compare_addr(const void *e1, const void *e2)
+{
+ struct ksym *ks1 = (struct ksym *) e1;
+ struct ksym *ks2 = (struct ksym *) e2;
+
+ if (ks1->addr < ks2->addr)
+ return -1;
+ if (ks1->addr > ks2->addr)
+ return 1;
+ return 0;
+}
+
+static int
+ksym_compare_name(const void *e1, const void *e2)
+{
+ struct ksym *ks1 = (struct ksym *) e1;
+ struct ksym *ks2 = (struct ksym *) e2;
+
+ return(strcmp(ks1->name, ks2->name));
+}
+
+static int
+read_ksyms(__psint_t *end_addr)
+{
+ char inbuf[256];
+ char *ip;
+ char *sp;
+ char *tp;
+ char *p;
+ int ix = 0;
+ int l = 0;
+ int len;
+ int err;
+ FILE *fp;
+ struct ksym *ksym_tmp;
+
+ *end_addr = 0;
+ if ((fp = proc_statsfile("/proc/ksyms", inbuf, sizeof(inbuf))) == NULL)
+ return -oserror();
+
+ while (fgets(inbuf, sizeof(inbuf), fp) != NULL) {
+ l++;
+
+ /*
+ * /proc/ksyms lines look like this on ia32 ...
+ *
+ * c8804060 __insmod_rtc_S.text_L4576 [rtc]
+ * c010a320 disable_irq_nosync
+ *
+ * else on ia64 ...
+ *
+ * a0000000003e0d28 debug [arsess]
+ * e002100000891140 disable_irq_nosync
+ */
+
+ if (strstr(inbuf, "\n") == NULL) {
+ fprintf(stderr, "read_ksyms: truncated /proc/ksyms line [%d]: %s\n", l-1, inbuf);
+ continue;
+ }
+
+ /* Increase array size, if necessary */
+ if (ksym_a_sz < ix+1) {
+ if (ksym_a_sz > 0)
+ ksym_a_sz += INCR_KSIZE;
+ else
+ ksym_a_sz = INIT_KSIZE;
+ ksym_tmp = (struct ksym *)realloc(ksym_a, ksym_a_sz * sizeof(struct ksym));
+ if (ksym_tmp == NULL) {
+ err = -oserror();
+ free(ksym_a);
+ fclose(fp);
+ return err;
+ }
+ ksym_a = ksym_tmp;
+ }
+
+ ip = inbuf;
+ /* parse over address */
+ while (isxdigit((int)*ip)) ip++;
+
+ if (!isspace((int)*ip) || ip-inbuf < 4) {
+ /* bad format line */
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "read_ksyms: bad addr? %c[%d] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf);
+ }
+#endif
+ continue;
+ }
+
+ sscanf(inbuf, "%p", (void **)&ksym_a[ix].addr);
+
+ while (isblank((int)*ip)) ip++;
+
+ /* next should be the symbol name */
+ sp = ip++;
+ while (!isblank((int)*ip) &&*ip != '\n') ip++;
+
+ /* strip off GPLONLY_ prefix, if found */
+ if (strncmp(sp, "GPLONLY_", 8) == 0)
+ sp += 8;
+
+ /*
+ * strip off symbol version suffix, if found ... looking for
+ * trailing pattern of the form _R.*[0-9a-fA-F]{8,}
+ * - find rightmost _R, if any
+ */
+ tp = sp;
+ while ((p = strstr(tp, "_R")) != NULL) tp = p+2;
+ if (tp > sp) {
+ /*
+ * found _R, need the last 8 digits to be hex
+ */
+ if (ip - tp + 1 >= 8) {
+ for (p = &ip[-8]; p < ip; p++) {
+ if (!isxdigit((int)*p)) {
+ tp = sp;
+ break;
+ }
+ }
+ }
+ else {
+ /* not enough characters for [0-9a-fA-f]{8,} at the end */
+ tp = sp;
+ }
+ }
+ if (tp > sp)
+ /* need to strip the trailing _R.*[0-9a-fA-f]{8,} */
+ len = tp - sp - 2;
+ else
+ len = ip - sp + 1;
+
+ ksym_a[ix].name = strndup(sp, len);
+ if (ksym_a[ix].name == NULL) {
+ err = -oserror();
+ fclose(fp);
+ return err;
+ }
+ ksym_a[ix].name[len-1] = '\0';
+
+ if (*end_addr == 0 && strcmp(ksym_a[ix].name, "_end") == 0)
+ *end_addr = ksym_a[ix].addr;
+
+ if (*ip == '\n')
+ /* nothing after the symbol name, so no module name */
+ goto next;
+
+ while (isblank((int)*ip)) ip++;
+
+ /* next expect module name */
+ if (*ip != '[') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "read_ksyms: bad start module name %c[%d] != [ line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf);
+ }
+#endif
+ free(ksym_a[ix].name);
+ continue;
+ }
+
+ sp = ++ip;
+ while (!isblank((int)*ip) && *ip != ']') ip++;
+
+ if (*ip != ']') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "read_ksyms: bad end module name %c[%d] != ] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf);
+ }
+#endif
+ free(ksym_a[ix].name);
+ continue;
+ }
+
+ ksym_a[ix].module = strndup(sp, ip - sp + 1);
+ if (ksym_a[ix].module == NULL) {
+ err = -oserror();
+ fclose(fp);
+ free(ksym_a[ix].name);
+ return err;
+ }
+ ksym_a[ix].module[ip - sp] = '\0';
+
+next:
+ ix++;
+ }
+
+ /* release unused ksym array entries */
+ if (ix) {
+ ksym_tmp = (struct ksym *)realloc(ksym_a, ix * sizeof(struct ksym));
+ if (ksym_tmp == NULL) {
+ free(ksym_a);
+ fclose(fp);
+ return -oserror();
+ }
+ ksym_a = ksym_tmp;
+ }
+
+ ksym_a_sz = ix;
+
+ qsort(ksym_a, ksym_a_sz, sizeof(struct ksym), ksym_compare_name);
+
+ fclose(fp);
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "symbols from ksyms ...\n");
+ for (ix = 0; ix < ksym_a_sz; ix++) {
+ fprintf(stderr, "ksym[%d] " PRINTF_P_PFX "%p %s", ix, (void *)ksym_a[ix].addr, ksym_a[ix].name);
+ if (ksym_a[ix].module != NULL) fprintf(stderr, " [%s]", ksym_a[ix].module);
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ return ksym_a_sz;
+}
+
+static int
+read_sysmap(const char *release, __psint_t end_addr)
+{
+ char inbuf[256], path[MAXPATHLEN], **fmt;
+ struct ksym *ksym_tmp;
+ __psint_t addr;
+ int ix, res, e;
+ int l = 0;
+ char *ip;
+ char *sp;
+ int major, minor, patch;
+ FILE *fp;
+ char *bestpath = NULL;
+ int ksym_mismatch_count;
+ char *sysmap_paths[] = { /* Paths to check for System.map file */
+ "%s/boot/System.map-%s",
+ "%s/boot/System.map",
+ "%s/lib/modules/%s/System.map",
+ "%s/usr/src/linux/System.map",
+ "%s/System.map",
+ NULL
+ };
+
+ /* Create version symbol name to look for in System.map */
+ if (sscanf(release, "%d.%d.%d", &major, &minor, &patch) < 3 )
+ return -1;
+ sprintf(inbuf, "Version_%u", KERNEL_VERSION(major, minor, patch));
+
+ /*
+ * Walk through System.map path list looking for one that matches
+ * either _end from /proc/ksyms or the uts version.
+ */
+ for (fmt = sysmap_paths; *fmt; fmt++) {
+ snprintf(path, MAXPATHLEN, *fmt, proc_statspath, release);
+ if ((fp = fopen(path, "r"))) {
+ if ((e = validate_sysmap(fp, inbuf, end_addr)) != 0) {
+ if (e == 2) {
+ /* matched _end, so this is the right System.map */
+ if (bestpath)
+ free(bestpath);
+ bestpath = strdup(path);
+ }
+ else
+ if (e == 1 && !bestpath)
+ bestpath = strdup(path);
+ }
+ fclose(fp);
+ if (e == 2) {
+ /* _end matched => don't look any further */
+ break;
+ }
+ }
+ }
+
+ if (bestpath)
+ fprintf(stderr, "NOTICE: using \"%s\" for kernel symbols map.\n", bestpath);
+ else {
+ /* Didn't find a valid System.map */
+ fprintf(stderr, "Warning: Valid System.map file not found!\n");
+ fprintf(stderr, "Warning: proc.psinfo.wchan_s symbol names cannot be derived!\n");
+ fprintf(stderr, "Warning: Addresses will be returned for proc.psinfo.wchan_s instead!\n");
+ /* Free symbol array */
+ for (ix = 0; ix < ksym_a_sz; ix++) {
+ if (ksym_a[ix].name)
+ free(ksym_a[ix].name);
+ if (ksym_a[ix].module)
+ free(ksym_a[ix].module);
+ }
+ free(ksym_a);
+ ksym_a = NULL;
+ ksym_a_sz = 0;
+ return -1;
+ }
+
+ /* scan the System map */
+ if ((fp = proc_statsfile(bestpath, path, sizeof(path))) == NULL)
+ return -oserror();
+
+ ix = ksym_a_sz;
+
+ /* Read each line in System.map */
+ ksym_mismatch_count = 0;
+ while (fgets(inbuf, sizeof(inbuf), fp) != NULL) {
+ /*
+ * System.map lines look like this on ia32 ...
+ *
+ * c010a320 T disable_irq_nosync
+ *
+ * else on ia64 ...
+ *
+ * e002000000014c80 T disable_irq_nosync
+ */
+
+ if (strstr(inbuf, "\n") == NULL) {
+ fprintf(stderr, "read_sysmap: truncated System.map line [%d]: %s\n", l-1, inbuf);
+ continue;
+ }
+
+ /* Increase array size, if necessary */
+ if (ksym_a_sz < ix+1) {
+ ksym_a_sz += INCR_KSIZE;
+ ksym_tmp = (struct ksym *)realloc(ksym_a, ksym_a_sz * sizeof(struct ksym));
+ if (ksym_tmp == NULL) {
+ free(ksym_a);
+ goto fail;
+ }
+ ksym_a = ksym_tmp;
+ }
+
+ ip = inbuf;
+ /* parse over address */
+ while (isxdigit((int)*ip)) ip++;
+
+ if (!isspace((int)*ip) || ip-inbuf < 4) {
+ /* bad format line */
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "read_sysmap: bad addr? %c[%d] line=\"%s\"\n", *ip, (int)(ip-inbuf), inbuf);
+ }
+#endif
+ continue;
+ }
+
+ sscanf(inbuf, "%p", (void **)&addr);
+
+ while (isblank((int)*ip)) ip++;
+
+ /* Only interested in symbol types that map to code addresses,
+ * so: t, T, W or A
+ */
+ if (*ip != 't' && *ip != 'T' && *ip != 'W' && *ip != 'A')
+ continue;
+
+ ip++;
+ while (isblank((int)*ip)) ip++;
+
+ /* next should be the symbol name */
+ sp = ip++;
+ while (!isblank((int)*ip) && *ip != '\n') ip++;
+ *ip = '\0';
+
+ /* Determine if symbol is already in ksym array.
+ If so, make sure the addresses match. */
+ res = find_dup_name(ix - 1, addr, sp);
+ if (res == KSYM_NOT_FOUND) { /* add it */
+ ksym_a[ix].name = strdup(sp);
+ if (ksym_a[ix].name == NULL)
+ goto fail;
+ ksym_a[ix].addr = addr;
+ ix++;
+ }
+ else if (res == KSYM_FOUND_MISMATCH) {
+ if (ksym_mismatch_count++ < KSYM_MISMATCH_MAX_ALLOWED) {
+ /*
+ * ia64 function pointer descriptors make this validation
+ * next to useless. So only report the first
+ * KSYM_MISMATCH_MAX_ALLOWED mismatches found.
+ */
+ fprintf(stderr, "Warning: mismatch for \"%s\" between System.map"
+ " and /proc/ksyms.\n", sp);
+ }
+ }
+ }
+
+ if (ksym_mismatch_count > KSYM_MISMATCH_MAX_ALLOWED) {
+ fprintf(stderr, "Warning: only reported first %d out of %d mismatches "
+ "between System.map and /proc/ksyms.\n",
+ KSYM_MISMATCH_MAX_ALLOWED, ksym_mismatch_count);
+ }
+
+ /* release unused ksym array entries */
+ ksym_tmp = (struct ksym *)realloc(ksym_a, ix * sizeof(struct ksym));
+ if (ksym_tmp == NULL) {
+ free(ksym_a);
+ goto fail;
+ }
+ ksym_a = ksym_tmp;
+ ksym_a_sz = ix;
+
+ qsort(ksym_a, ksym_a_sz, sizeof(struct ksym), ksym_compare_addr);
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "symbols from ksyms + sysmap ...\n");
+ for (ix = 0; ix < ksym_a_sz; ix++) {
+ fprintf(stderr, "ksym[%d] " PRINTF_P_PFX "%p %s", ix, (void *)ksym_a[ix].addr, ksym_a[ix].name);
+ if (ksym_a[ix].module != NULL) fprintf(stderr, " [%s]", ksym_a[ix].module);
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ fclose(fp);
+
+ return ksym_a_sz;
+
+fail:
+ e = -oserror();
+ if (fp)
+ fclose(fp);
+ return e;
+}
+
+void
+read_ksym_sources(const char *release)
+{
+ __psint_t end_addr;
+
+ if (read_ksyms(&end_addr) > 0) /* read /proc/ksyms first */
+ read_sysmap(release, end_addr); /* then System.map */
+}
diff --git a/src/pmdas/linux_proc/ksym.h b/src/pmdas/linux_proc/ksym.h
new file mode 100644
index 0000000..f328ca4
--- /dev/null
+++ b/src/pmdas/linux_proc/ksym.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2002
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * This code contributed by Mike Mason (mmlnx@us.ibm.com)
+ */
+#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+
+#define INIT_KSIZE 8192
+#define INCR_KSIZE 2048
+
+#define KSYM_FOUND_MISMATCH -1
+#define KSYM_NOT_FOUND 0
+#define KSYM_FOUND 1
+
+#define KSYM_MISMATCH_MAX_ALLOWED 10
+
+struct ksym {
+ __psint_t addr;
+ char *name;
+ char *module;
+};
+
+extern char *wchan(__psint_t);
+extern void read_ksym_sources(const char *);
+
diff --git a/src/pmdas/linux_proc/linux_proc_migrate.conf b/src/pmdas/linux_proc/linux_proc_migrate.conf
new file mode 100644
index 0000000..51190da
--- /dev/null
+++ b/src/pmdas/linux_proc/linux_proc_migrate.conf
@@ -0,0 +1,55 @@
+# Copyright 2012 Red Hat, Inc. All Rights Reserved
+#
+# pmlogrewrite configuration for migrating archives containing proc metrics
+# that were captured prior to the proc PMDA split-off from the Linux PMDA.
+#
+# Basically, the PMID domain changed from 60 (linux) to 3 (proc) but all
+# cluster and item numbers remain unchanged.
+#
+# Note that the CPU indom is not migrated, even though it is
+# used for cgroup.groups.cpuacct.[<group>.]usage_percpu and
+# cgroup.groups.cpuacct.usage_percpu because these metrics use a
+# the dynamic pmns. To migrate archives containing these metrics,
+# a script would be needed to generate the pmlogwrite config based
+# on the metric names actually present in the source archive.
+
+#
+# Migrate instance domains
+indom 60.9 { indom -> 3.9 } # per-process indom
+indom 60.20 { indom -> 3.20 } # cgroup hierarchy indom
+indom 60.21 { indom -> 3.21 } # cgroup mount subsys indom
+
+#
+# Migrate the pmid domain for each cluster
+metric 60.8.* { pmid -> 3.*.* } # CLUSTER_PID_STAT
+metric 60.9.* { pmid -> 3.*.* } # CLUSTER_PID_STATM
+metric 60.13.* { pmid -> 3.*.* } # CLUSTER_PROC_RUNQ
+metric 60.24.* { pmid -> 3.*.* } # CLUSTER_PID_STATUS
+metric 60.31.* { pmid -> 3.*.* } # CLUSTER_PID_SCHEDSTAT
+metric 60.32.* { pmid -> 3.*.* } # CLUSTER_PID_IO
+metric 60.51.* { pmid -> 3.*.* } # CLUSTER_PID_FD
+metric 60.37.* { pmid -> 3.*.* } # CLUSTER_CGROUP_SUBSYS
+metric 60.38.* { pmid -> 3.*.* } # CLUSTER_CGROUP_MOUNTS
+metric 60.39.* { pmid -> 3.*.* } # CLUSTER_CPUSET_GROUPS
+metric 60.40.* { pmid -> 3.*.* } # CLUSTER_CPUSET_PROCS
+metric 60.41.* { pmid -> 3.*.* } # CLUSTER_CPUACCT_GROUPS
+metric 60.42.* { pmid -> 3.*.* } # CLUSTER_CPUACCT_PROCS
+metric 60.43.* { pmid -> 3.*.* } # CLUSTER_CPUSCHED_GROUPS
+metric 60.44.* { pmid -> 3.*.* } # CLUSTER_CPUSCHED_PROCS
+metric 60.45.* { pmid -> 3.*.* } # CLUSTER_MEMORY_GROUPS
+metric 60.46.* { pmid -> 3.*.* } # CLUSTER_MEMORY_PROCS
+metric 60.47.* { pmid -> 3.*.* } # CLUSTER_NET_CLS_GROUPS
+metric 60.48.* { pmid -> 3.*.* } # CLUSTER_NET_CLS_PROCS
+
+#
+# These two proc.io metrics were incorrectly classified
+#
+metric proc.io.rchar {
+ sem -> counter
+ units -> 1,0,0,BYTE,0,0
+}
+
+metric proc.io.wchar {
+ sem -> counter
+ units -> 1,0,0,BYTE,0,0
+}
diff --git a/src/pmdas/linux_proc/pmda.c b/src/pmdas/linux_proc/pmda.c
new file mode 100644
index 0000000..2d40a54
--- /dev/null
+++ b/src/pmdas/linux_proc/pmda.c
@@ -0,0 +1,1896 @@
+/*
+ * proc PMDA
+ *
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ * Portions Copyright (c) 2002 International Business Machines Corp.
+ * Portions Copyright (c) 2007-2011 Aconex. All Rights Reserved.
+ * Portions Copyright (c) 2012-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "contexts.h"
+
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/vfs.h>
+#include <sys/stat.h>
+#include <sys/times.h>
+#include <sys/utsname.h>
+#include <utmp.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "../linux/convert.h"
+#include "clusters.h"
+#include "indom.h"
+
+#include "getinfo.h"
+#include "proc_pid.h"
+#include "proc_runq.h"
+#include "ksym.h"
+#include "cgroups.h"
+
+/* globals */
+static int _isDSO = 1; /* for local contexts */
+static proc_pid_t proc_pid;
+static struct utsname kernel_uname;
+static proc_runq_t proc_runq;
+static int all_access; /* =1 no access checks */
+static int have_access; /* =1 recvd uid/gid */
+static size_t _pm_system_pagesize;
+static unsigned int threads; /* control.all.threads */
+static char * cgroups; /* control.all.cgroups */
+
+char *proc_statspath = ""; /* optional path prefix for all stats files */
+
+/*
+ * The proc instance domain table is direct lookup and sparse.
+ * It is initialized in proc_init(), see below.
+ */
+static pmdaIndom indomtab[NUM_INDOMS];
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+static pmdaMetric metrictab[] = {
+
+/*
+ * proc/<pid>/stat cluster
+ */
+
+/* proc.nprocs */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,99), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.pid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,0), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.cmd */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,1), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.sname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,2), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.ppid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,3), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.pgrp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,4), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.session */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,5), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.tty */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,6), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.tty_pgrp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,7), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.flags */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,8), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.minflt */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,9), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.cmin_flt */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,10), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.maj_flt */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,11), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.cmaj_flt */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,12), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.utime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,13), KERNEL_ULONG, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+
+/* proc.psinfo.stime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,14), KERNEL_ULONG, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+
+/* proc.psinfo.cutime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,15), KERNEL_ULONG, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+
+/* proc.psinfo.cstime */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,16), KERNEL_ULONG, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+
+/* proc.psinfo.priority */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,17), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.nice */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,18), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+#if 0
+/* invalid field */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,19), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+#endif
+
+/* proc.psinfo.it_real_value */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,20), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.start_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,21), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) } },
+
+/* proc.psinfo.vsize */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,22), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.psinfo.rss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,23), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.psinfo.rss_rlim */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,24), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.psinfo.start_code */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,25), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.end_code */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,26), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.start_stack */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,27), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.esp */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,28), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.eip */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,29), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.signal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,30), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.blocked */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,31), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.sigignore */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,32), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.sigcatch */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,33), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.wchan */
+#if defined(HAVE_64BIT_PTR)
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,34), PM_TYPE_U64, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+#elif defined(HAVE_32BIT_PTR)
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,34), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+#else
+ error! unsupported pointer size
+#endif
+
+/* proc.psinfo.nswap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,35), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.cnswap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,36), PM_TYPE_U32, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.psinfo.exit_signal */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,37), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.processor -- added by Mike Mason <mmlnx@us.ibm.com> */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,38), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.ttyname */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,39), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+
+/* proc.psinfo.wchan_s -- added by Mike Mason <mmlnx@us.ibm.com> */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,40), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.psargs -- modified by Mike Mason <mmlnx@us.ibm.com> */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STAT,41), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * proc/<pid>/status cluster
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+
+/* proc.id.uid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,0), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.euid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,1), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.suid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,2), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.fsuid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,3), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.gid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,4), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.egid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,5), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.sgid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,6), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.fsgid */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,7), PM_TYPE_U32, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.uid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,8), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.euid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,9), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.suid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,10), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.fsuid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,11), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.gid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,12), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.egid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,13), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.sgid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,14), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.id.fsgid_nm */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,15), PM_TYPE_STRING, PROC_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.signal_s */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,16), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.blocked_s */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,17), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.sigignore_s */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,18), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.sigcatch_s */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,19), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.memory.vmsize */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,20), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmlock */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,21), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmrss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,22), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmdata */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,23), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmstack */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,24), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmexe */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,25), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmlib */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,26), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.memory.vmswap */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,27), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+/* proc.psinfo.threads */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATUS,28), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.cgroups */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_CGROUP,0), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/* proc.psinfo.labels */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_LABEL,0), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+
+/*
+ * proc/<pid>/statm cluster
+ */
+
+/* proc.memory.size */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,0), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.rss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,1), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.share */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,2), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.textrss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,3), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.librss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,4), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.datrss */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,5), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.dirty */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,6), PM_TYPE_U32, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+/* proc.memory.maps -- added by Mike Mason <mmlnx@us.ibm.com> */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_STATM,7), PM_TYPE_STRING, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+/*
+ * proc/<pid>/schedstat cluster
+ */
+
+/* proc.schedstat.cpu_time */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_SCHEDSTAT,0), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0)}},
+/* proc.schedstat.run_delay */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_SCHEDSTAT,1), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0)}},
+/* proc.schedstat.pcount */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_SCHEDSTAT,2), KERNEL_ULONG, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+
+/*
+ * proc/<pid>/io cluster
+ */
+/* proc.io.rchar */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,0), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+/* proc.io.wchar */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,1), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+/* proc.io.syscr */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,2), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+/* proc.io.syscw */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,3), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+/* proc.io.read_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,4), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+/* proc.io.write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,5), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+/* proc.io.cancelled_write_bytes */
+ { NULL,
+ { PMDA_PMID(CLUSTER_PID_IO,6), PM_TYPE_U64, PROC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+
+/*
+ * proc.runq cluster
+ */
+
+/* proc.runq.runnable */
+ { &proc_runq.runnable,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.blocked */
+ { &proc_runq.blocked,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.sleeping */
+ { &proc_runq.sleeping,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.stopped */
+ { &proc_runq.stopped,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.swapped */
+ { &proc_runq.swapped,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.defunct */
+ { &proc_runq.defunct,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.unknown */
+ { &proc_runq.unknown,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 6), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/* proc.runq.kernel */
+ { &proc_runq.kernel,
+ { PMDA_PMID(CLUSTER_PROC_RUNQ, 7), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/*
+ * control groups cluster
+ */
+ /* cgroups.subsys.hierarchy */
+ { NULL, {PMDA_PMID(CLUSTER_CGROUP_SUBSYS,0), PM_TYPE_U32,
+ CGROUP_SUBSYS_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* cgroups.subsys.count */
+ { NULL, {PMDA_PMID(CLUSTER_CGROUP_SUBSYS,1), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroups.mounts.subsys */
+ { NULL, {PMDA_PMID(CLUSTER_CGROUP_MOUNTS,0), PM_TYPE_STRING,
+ CGROUP_MOUNTS_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* cgroups.mounts.count */
+ { NULL, {PMDA_PMID(CLUSTER_CGROUP_MOUNTS,1), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.cpuset.[<group>.]cpus */
+ { NULL, {PMDA_PMID(CLUSTER_CPUSET_GROUPS,0), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* cgroup.groups.cpuset.[<group>.]mems */
+ { NULL, {PMDA_PMID(CLUSTER_CPUSET_GROUPS,0), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* cgroup.groups.cpuacct.[<group>.]stat.user */
+ { NULL, {PMDA_PMID(CLUSTER_CPUACCT_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+ /* cgroup.groups.cpuacct.[<group>.]stat.system */
+ { NULL, {PMDA_PMID(CLUSTER_CPUACCT_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+ /* cgroup.groups.cpuacct.[<group>.]usage */
+ { NULL, {PMDA_PMID(CLUSTER_CPUACCT_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.cpuacct.[<group>.]usage_percpu */
+ { NULL, {PMDA_PMID(CLUSTER_CPUACCT_GROUPS,0), PM_TYPE_U64,
+ CPU_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.cpusched.[<group>.]shares */
+ { NULL, {PMDA_PMID(CLUSTER_CPUSCHED_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.cache */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.rss */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.rss_huge */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.mapped_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.writeback */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.swap */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.pgpgin */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.pgpgout */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.pgfault */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.pgmajfault */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.inactive_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.active_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.inactive_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.active_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.unevictable */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_cache */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_rss */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_rss_huge */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_mapped_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_writeback */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_swap */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_pgpgin */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_pgpgout */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_pgfault */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_pgmajfault */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_inactive_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_active_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_inactive_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_active_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.total_unevictable */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.recent_rotated_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.recent_rotated_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.recent_scanned_anon */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.memory.[<group>.]stat.recent_scanned_file */
+ { NULL, {PMDA_PMID(CLUSTER_MEMORY_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.netclass.[<group>.]classid */
+ { NULL, {PMDA_PMID(CLUSTER_NET_CLS_GROUPS,0), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_merged.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_merged.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_merged.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_merged.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_merged.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_queued.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_queued.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_queued.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_queued.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_queued.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_bytes.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_bytes.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_bytes.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_bytes.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_bytes.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_serviced.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_serviced.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_serviced.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_serviced.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_serviced.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_time.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_time.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_time.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_time.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_service_time.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_wait_time.read */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_wait_time.write */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_wait_time.sync */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_wait_time.async */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]io_wait_time.total */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) }, },
+
+ /* cgroup.groups.blkio.[<group>.]sectors */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+ /* cgroup.groups.blkio.[<group>.]time */
+ { NULL, {PMDA_PMID(CLUSTER_BLKIO_GROUPS,0), PM_TYPE_U64,
+ DISK_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) }, },
+
+
+/*
+ * proc/<pid>/fd cluster
+ */
+
+ /* proc.fd.count */
+ { NULL, { PMDA_PMID(CLUSTER_PID_FD,0), PM_TYPE_U32,
+ PROC_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+/*
+ * Metrics control cluster
+ */
+
+ /* proc.control.all.threads */
+ { &threads, { PMDA_PMID(CLUSTER_CONTROL, 1), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) } },
+ /* proc.control.perclient.threads */
+ { NULL, { PMDA_PMID(CLUSTER_CONTROL, 2), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) } },
+ /* proc.control.perclient.cgroups */
+ { NULL, { PMDA_PMID(CLUSTER_CONTROL, 3), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) } },
+};
+
+pmInDom
+proc_indom(int serial)
+{
+ return indomtab[serial].it_indom;
+}
+
+FILE *
+proc_statsfile(const char *path, char *buffer, int size)
+{
+ snprintf(buffer, size, "%s%s", proc_statspath, path);
+ buffer[size-1] = '\0';
+ return fopen(buffer, "r");
+}
+
+static void
+proc_refresh(pmdaExt *pmda, int *need_refresh)
+{
+ int need_refresh_mtab = 0;
+
+ if (need_refresh[CLUSTER_CPUACCT_GROUPS])
+ refresh_cgroup_cpus(INDOM(CPU_INDOM));
+
+ if (need_refresh[CLUSTER_CGROUP_SUBSYS] ||
+ need_refresh[CLUSTER_CGROUP_MOUNTS] ||
+ need_refresh[CLUSTER_CPUSET_GROUPS] ||
+ need_refresh[CLUSTER_CPUACCT_GROUPS] ||
+ need_refresh[CLUSTER_CPUSCHED_GROUPS] ||
+ need_refresh[CLUSTER_BLKIO_GROUPS] ||
+ need_refresh[CLUSTER_NET_CLS_GROUPS] ||
+ need_refresh[CLUSTER_MEMORY_GROUPS]) {
+ refresh_cgroup_subsys(INDOM(CGROUP_SUBSYS_INDOM));
+ need_refresh_mtab |= refresh_cgroups(pmda, NULL);
+ }
+
+ if (need_refresh_mtab)
+ pmdaDynamicMetricTable(pmda);
+
+ if (need_refresh[CLUSTER_PID_STAT] ||
+ need_refresh[CLUSTER_PID_STATM] ||
+ need_refresh[CLUSTER_PID_STATUS] ||
+ need_refresh[CLUSTER_PID_IO] ||
+ need_refresh[CLUSTER_PID_LABEL] ||
+ need_refresh[CLUSTER_PID_CGROUP] ||
+ need_refresh[CLUSTER_PID_SCHEDSTAT] ||
+ need_refresh[CLUSTER_PID_FD]) {
+ refresh_proc_pid(&proc_pid,
+ proc_ctx_threads(pmda->e_context, threads),
+ proc_ctx_cgroups(pmda->e_context, cgroups));
+ }
+
+ if (need_refresh[CLUSTER_PROC_RUNQ])
+ refresh_proc_runq(&proc_runq);
+}
+
+static int
+proc_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ __pmInDom_int *indomp = (__pmInDom_int *)&indom;
+ int need_refresh[NUM_CLUSTERS] = { 0 };
+ char newname[16]; /* see Note below */
+ int sts;
+
+ switch (indomp->serial) {
+ case CPU_INDOM:
+ /*
+ * Used by cgroup.groups.cpuacct.[<group>.]usage_percpu
+ * and cgroup.groups.cpuacct.usage_percpu
+ */
+ need_refresh[CLUSTER_CPUACCT_GROUPS]++;
+ break;
+ case DISK_INDOM:
+ need_refresh[CLUSTER_BLKIO_GROUPS]++;
+ break;
+ case PROC_INDOM:
+ need_refresh[CLUSTER_PID_STAT]++;
+ need_refresh[CLUSTER_PID_STATM]++;
+ need_refresh[CLUSTER_PID_STATUS]++;
+ need_refresh[CLUSTER_PID_LABEL]++;
+ need_refresh[CLUSTER_PID_CGROUP]++;
+ need_refresh[CLUSTER_PID_SCHEDSTAT]++;
+ need_refresh[CLUSTER_PID_IO]++;
+ need_refresh[CLUSTER_PID_FD]++;
+ break;
+ case CGROUP_SUBSYS_INDOM:
+ need_refresh[CLUSTER_CGROUP_SUBSYS]++;
+ break;
+ case CGROUP_MOUNTS_INDOM:
+ need_refresh[CLUSTER_CGROUP_MOUNTS]++;
+ break;
+ /* no default label : pmdaInstance will pick up errors */
+ }
+
+ if (indomp->serial == PROC_INDOM && inst == PM_IN_NULL && name != NULL) {
+ /*
+ * For the proc indom, if the name is a pid (as a string), and it
+ * contains only digits (i.e. it's not a full instance name) then
+ * reformat it to be exactly six digits, with leading zeros.
+ *
+ * Note that although format %06d is used here and in proc_pid.c,
+ * the pid could be longer than this (in which case there
+ * are no leading zeroes. The size of newname[] is chosen
+ * to comfortably accommodate a 32-bit pid (Linux maximum),
+ * or max value of 4294967295 (10 digits)
+ */
+ char *p;
+ for (p = name; *p != '\0'; p++) {
+ if (!isdigit((int)*p))
+ break;
+ }
+ if (*p == '\0') {
+ snprintf(newname, sizeof(newname), "%06d", atoi(name));
+ name = newname;
+ }
+ }
+
+ sts = PM_ERR_PERMISSION;
+ have_access = proc_ctx_access(pmda->e_context) || all_access;
+ if (have_access || indomp->serial != PROC_INDOM) {
+ proc_refresh(pmda, need_refresh);
+ sts = pmdaInstance(indom, inst, name, result, pmda);
+ }
+ have_access = proc_ctx_revert(pmda->e_context);
+
+ return sts;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+
+static int
+proc_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int cluster = proc_pmid_cluster(mdesc->m_desc.pmid);
+ int sts;
+ unsigned long ul;
+ const char *cp;
+ char *f;
+ int *ip;
+ proc_pid_entry_t *entry;
+ void *fsp;
+ static long hz = -1;
+ char *tail;
+
+ if (hz == -1)
+ hz = sysconf(_SC_CLK_TCK);
+
+ if (mdesc->m_user != NULL) {
+ /*
+ * The metric value is extracted directly via the address specified
+ * in metrictab. Note: not all metrics support this - those that
+ * don't have NULL for the m_user field in their respective
+ * metrictab slot.
+ */
+
+ switch (mdesc->m_desc.type) {
+ case PM_TYPE_32:
+ atom->l = *(__int32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U32:
+ atom->ul = *(__uint32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_64:
+ atom->ll = *(__int64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U64:
+ atom->ull = *(__uint64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_FLOAT:
+ atom->f = *(float *)mdesc->m_user;
+ break;
+ case PM_TYPE_DOUBLE:
+ atom->d = *(double *)mdesc->m_user;
+ break;
+ case PM_TYPE_STRING:
+ cp = *(char **)mdesc->m_user;
+ atom->cp = (char *)(cp ? cp : "");
+ break;
+ default:
+ return 0;
+ }
+ }
+ else
+ switch (cluster) {
+
+ case CLUSTER_PID_STAT:
+ if (idp->item == 99) /* proc.nprocs */
+ atom->ul = proc_pid.indom->it_numinst;
+ else {
+ static char ttyname[MAXPATHLEN];
+
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if ((entry = fetch_proc_pid_stat(inst, &proc_pid)) == NULL)
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+
+
+ case PROC_PID_STAT_PID:
+ atom->ul = entry->id;
+ break;
+
+ case PROC_PID_STAT_TTYNAME:
+ if ((f = _pm_getfield(entry->stat_buf, PROC_PID_STAT_TTY)) == NULL)
+ atom->cp = "?";
+ else {
+ dev_t dev = (dev_t)atoi(f);
+ atom->cp = get_ttyname_info(inst, dev, ttyname);
+ }
+ break;
+
+ case PROC_PID_STAT_CMD:
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->cp = f + 1;
+ atom->cp[strlen(atom->cp)-1] = '\0';
+ break;
+
+ case PROC_PID_STAT_PSARGS:
+ atom->cp = entry->name + 7;
+ break;
+
+ case PROC_PID_STAT_STATE:
+ /*
+ * string
+ */
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->cp = f;
+ break;
+
+ case PROC_PID_STAT_VSIZE:
+ case PROC_PID_STAT_RSS_RLIM:
+ /*
+ * bytes converted to kbytes
+ */
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ atom->ul /= 1024;
+ break;
+
+ case PROC_PID_STAT_RSS:
+ /*
+ * pages converted to kbytes
+ */
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ atom->ul *= _pm_system_pagesize / 1024;
+ break;
+
+ case PROC_PID_STAT_UTIME:
+ case PROC_PID_STAT_STIME:
+ case PROC_PID_STAT_CUTIME:
+ case PROC_PID_STAT_CSTIME:
+ /*
+ * unsigned jiffies converted to unsigned milliseconds
+ */
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+
+ ul = (__uint32_t)strtoul(f, &tail, 0);
+ _pm_assign_ulong(atom, 1000 * (double)ul / hz);
+ break;
+
+ case PROC_PID_STAT_PRIORITY:
+ case PROC_PID_STAT_NICE:
+ /*
+ * signed decimal int
+ */
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->l = (__int32_t)strtol(f, &tail, 0);
+ break;
+
+ case PROC_PID_STAT_WCHAN:
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+#if defined(HAVE_64BIT_PTR)
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+#else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+#endif
+ break;
+
+ case PROC_PID_STAT_WCHAN_SYMBOL:
+ if (entry->wchan_buf) /* 2.6 kernel, /proc/<pid>/wchan */
+ atom->cp = entry->wchan_buf;
+ else { /* old school (2.4 kernels, at least) */
+ char *wc;
+ /*
+ * Convert address to symbol name if requested
+ * Added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ f = _pm_getfield(entry->stat_buf, PROC_PID_STAT_WCHAN);
+ if (f == NULL)
+ return PM_ERR_INST;
+#if defined(HAVE_64BIT_PTR)
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ if ((wc = wchan(atom->ull)))
+ atom->cp = wc;
+ else
+ atom->cp = atom->ull ? f : "";
+#else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ if ((wc = wchan((__psint_t)atom->ul)))
+ atom->cp = wc;
+ else
+ atom->cp = atom->ul ? f : "";
+#endif
+ }
+ break;
+
+ default:
+ /*
+ * unsigned decimal int
+ */
+ if (idp->item < NR_PROC_PID_STAT) {
+ if ((f = _pm_getfield(entry->stat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ }
+ else
+ return PM_ERR_PMID;
+ break;
+ }
+ }
+ break;
+
+ case CLUSTER_PID_STATM:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if (idp->item == PROC_PID_STATM_MAPS) { /* proc.memory.maps */
+ if ((entry = fetch_proc_pid_maps(inst, &proc_pid)) == NULL)
+ return PM_ERR_INST;
+ atom->cp = entry->maps_buf;
+ } else {
+ if ((entry = fetch_proc_pid_statm(inst, &proc_pid)) == NULL)
+ return PM_ERR_INST;
+
+ if (idp->item <= PROC_PID_STATM_DIRTY) {
+ /* unsigned int */
+ if ((f = _pm_getfield(entry->statm_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ atom->ul *= _pm_system_pagesize / 1024;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_PID_SCHEDSTAT:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if ((entry = fetch_proc_pid_schedstat(inst, &proc_pid)) == NULL)
+ return (oserror() == ENOENT) ? PM_ERR_APPVERSION : PM_ERR_INST;
+
+ if (idp->item < NR_PROC_PID_SCHED) {
+ if ((f = _pm_getfield(entry->schedstat_buf, idp->item)) == NULL)
+ return PM_ERR_INST;
+ if (idp->item == PROC_PID_SCHED_PCOUNT &&
+ mdesc->m_desc.type == PM_TYPE_U32)
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ else
+#if defined(HAVE_64BIT_PTR)
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+#else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+#endif
+ }
+ else
+ return PM_ERR_PMID;
+ break;
+
+ case CLUSTER_PID_IO:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if ((entry = fetch_proc_pid_io(inst, &proc_pid)) == NULL)
+ return (oserror() == ENOENT) ? PM_ERR_APPVERSION : PM_ERR_INST;
+
+ switch (idp->item) {
+
+ case PROC_PID_IO_RCHAR:
+ if ((f = _pm_getfield(entry->io_lines.rchar, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_WCHAR:
+ if ((f = _pm_getfield(entry->io_lines.wchar, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_SYSCR:
+ if ((f = _pm_getfield(entry->io_lines.syscr, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_SYSCW:
+ if ((f = _pm_getfield(entry->io_lines.syscw, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_READ_BYTES:
+ if ((f = _pm_getfield(entry->io_lines.readb, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_WRITE_BYTES:
+ if ((f = _pm_getfield(entry->io_lines.writeb, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+ case PROC_PID_IO_CANCELLED_BYTES:
+ if ((f = _pm_getfield(entry->io_lines.cancel, 1)) == NULL)
+ atom->ull = 0;
+ else
+ atom->ull = (__uint64_t)strtoull(f, &tail, 0);
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ /*
+ * Cluster added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ case CLUSTER_PID_STATUS:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if ((entry = fetch_proc_pid_status(inst, &proc_pid)) == NULL)
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+
+ case PROC_PID_STATUS_UID:
+ case PROC_PID_STATUS_EUID:
+ case PROC_PID_STATUS_SUID:
+ case PROC_PID_STATUS_FSUID:
+ case PROC_PID_STATUS_UID_NM:
+ case PROC_PID_STATUS_EUID_NM:
+ case PROC_PID_STATUS_SUID_NM:
+ case PROC_PID_STATUS_FSUID_NM:
+ {
+ struct passwd *pwe;
+
+ if ((f = _pm_getfield(entry->status_lines.uid, (idp->item % 4) + 1)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ if (idp->item > PROC_PID_STATUS_FSUID) {
+ if ((pwe = getpwuid((uid_t)atom->ul)) != NULL)
+ atom->cp = pwe->pw_name;
+ else
+ atom->cp = "UNKNOWN";
+ }
+ }
+ break;
+
+ case PROC_PID_STATUS_GID:
+ case PROC_PID_STATUS_EGID:
+ case PROC_PID_STATUS_SGID:
+ case PROC_PID_STATUS_FSGID:
+ case PROC_PID_STATUS_GID_NM:
+ case PROC_PID_STATUS_EGID_NM:
+ case PROC_PID_STATUS_SGID_NM:
+ case PROC_PID_STATUS_FSGID_NM:
+ {
+ struct group *gre;
+
+ if ((f = _pm_getfield(entry->status_lines.gid, (idp->item % 4) + 1)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ if (idp->item > PROC_PID_STATUS_FSGID) {
+ if ((gre = getgrgid((gid_t)atom->ul)) != NULL) {
+ atom->cp = gre->gr_name;
+ } else {
+ atom->cp = "UNKNOWN";
+ }
+ }
+ }
+ break;
+
+ case PROC_PID_STATUS_SIGNAL:
+ if ((atom->cp = _pm_getfield(entry->status_lines.sigpnd, 1)) == NULL)
+ return PM_ERR_INST;
+ break;
+
+ case PROC_PID_STATUS_BLOCKED:
+ if ((atom->cp = _pm_getfield(entry->status_lines.sigblk, 1)) == NULL)
+ return PM_ERR_INST;
+ break;
+
+ case PROC_PID_STATUS_SIGCATCH:
+ if ((atom->cp = _pm_getfield(entry->status_lines.sigcgt, 1)) == NULL)
+ return PM_ERR_INST;
+ break;
+
+ case PROC_PID_STATUS_SIGIGNORE:
+ if ((atom->cp = _pm_getfield(entry->status_lines.sigign, 1)) == NULL)
+ return PM_ERR_INST;
+ break;
+
+ case PROC_PID_STATUS_VMSIZE:
+ if ((f = _pm_getfield(entry->status_lines.vmsize, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMLOCK:
+ if ((f = _pm_getfield(entry->status_lines.vmlck, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMRSS:
+ if ((f = _pm_getfield(entry->status_lines.vmrss, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMDATA:
+ if ((f = _pm_getfield(entry->status_lines.vmdata, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMSTACK:
+ if ((f = _pm_getfield(entry->status_lines.vmstk, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMEXE:
+ if ((f = _pm_getfield(entry->status_lines.vmexe, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMLIB:
+ if ((f = _pm_getfield(entry->status_lines.vmlib, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_VMSWAP:
+ if ((f = _pm_getfield(entry->status_lines.vmswap, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ case PROC_PID_STATUS_THREADS:
+ if ((f = _pm_getfield(entry->status_lines.threads, 1)) == NULL)
+ atom->ul = 0;
+ else
+ atom->ul = (__uint32_t)strtoul(f, &tail, 0);
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_CGROUP_SUBSYS:
+ switch (idp->item) {
+ case 0: /* cgroup.subsys.hierarchy */
+ sts = pmdaCacheLookup(INDOM(CGROUP_SUBSYS_INDOM), inst, NULL, (void **)&ip);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ atom->ul = *ip;
+ break;
+
+ case 1: /* cgroup.subsys.count */
+ atom->ul = pmdaCacheOp(INDOM(CGROUP_SUBSYS_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ break;
+ }
+ break;
+
+ case CLUSTER_CGROUP_MOUNTS:
+ switch (idp->item) {
+ case 0: /* cgroup.mounts.subsys */
+ sts = pmdaCacheLookup(INDOM(CGROUP_MOUNTS_INDOM), inst, NULL, &fsp);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ atom->cp = cgroup_find_subsys(INDOM(CGROUP_SUBSYS_INDOM), fsp);
+ break;
+
+ case 1: /* cgroup.mounts.count */
+ atom->ul = pmdaCacheOp(INDOM(CGROUP_MOUNTS_INDOM), PMDA_CACHE_SIZE_ACTIVE);
+ break;
+ }
+ break;
+
+ case CLUSTER_CPUSET_GROUPS:
+ case CLUSTER_CPUACCT_GROUPS:
+ case CLUSTER_CPUSCHED_GROUPS:
+ case CLUSTER_MEMORY_GROUPS:
+ case CLUSTER_NET_CLS_GROUPS:
+ case CLUSTER_BLKIO_GROUPS:
+ return cgroup_group_fetch(mdesc->m_desc.pmid, inst, atom);
+
+ case CLUSTER_PID_FD:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if (idp->item > PROC_PID_FD_COUNT)
+ return PM_ERR_PMID;
+ if ((entry = fetch_proc_pid_fd(inst, &proc_pid)) == NULL)
+ return PM_ERR_INST;
+ atom->ul = entry->fd_count;
+ break;
+
+ case CLUSTER_PID_CGROUP:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if (idp->item > PROC_PID_CGROUP)
+ return PM_ERR_PMID;
+ if ((entry = fetch_proc_pid_cgroup(inst, &proc_pid)) == NULL) {
+ if (oserror() == ENOENT) return PM_ERR_APPVERSION;
+ if (oserror() != ENODATA) return PM_ERR_INST;
+ atom->cp = "";
+ } else {
+ atom->cp = proc_strings_lookup(entry->cgroup_id);
+ }
+ break;
+
+ case CLUSTER_PID_LABEL:
+ if (!have_access)
+ return PM_ERR_PERMISSION;
+ if (idp->item > PROC_PID_LABEL)
+ return PM_ERR_PMID;
+ if ((entry = fetch_proc_pid_label(inst, &proc_pid)) == NULL) {
+ if (oserror() == ENOENT) return PM_ERR_APPVERSION;
+ if (oserror() != ENODATA) return PM_ERR_INST;
+ atom->cp = "";
+ } else {
+ atom->cp = proc_strings_lookup(entry->label_id);
+ }
+ break;
+
+ case CLUSTER_CONTROL:
+ switch (idp->item) {
+ /* case 1: not reached -- proc.control.all.threads is direct */
+ case 2: /* proc.control.perclient.threads */
+ atom->ul = proc_ctx_threads(pmdaGetContext(), threads);
+ break;
+ case 3: /* proc.control.perclient.cgroups */
+ cp = proc_ctx_cgroups(pmdaGetContext(), cgroups);
+ atom->cp = (char *)(cp ? cp : "");
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ default: /* unknown cluster */
+ return PM_ERR_PMID;
+ }
+
+ return PMDA_FETCH_STATIC;
+}
+
+static int
+proc_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i, sts, cluster;
+ int need_refresh[NUM_CLUSTERS] = { 0 };
+
+ for (i = 0; i < numpmid; i++) {
+ cluster = proc_pmid_cluster(pmidlist[i]);
+ if (cluster >= MIN_CLUSTER && cluster < NUM_CLUSTERS)
+ need_refresh[cluster]++;
+ }
+
+ have_access = proc_ctx_access(pmda->e_context) || all_access;
+ proc_refresh(pmda, need_refresh);
+ sts = pmdaFetch(numpmid, pmidlist, resp, pmda);
+ have_access = proc_ctx_revert(pmda->e_context);
+ return sts;
+}
+
+static int
+proc_store(pmResult *result, pmdaExt *pmda)
+{
+ int i, sts = 0;
+
+ have_access = proc_ctx_access(pmda->e_context) || all_access;
+
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+ __pmID_int *idp = (__pmID_int *)&(vsp->pmid);
+ pmAtomValue av;
+
+ if (idp->cluster != CLUSTER_CONTROL)
+ sts = PM_ERR_PERMISSION;
+ else if (vsp->numval != 1)
+ sts = PM_ERR_INST;
+ else switch (idp->item) {
+ case 1: /* proc.control.all.threads */
+ if (!have_access)
+ sts = PM_ERR_PERMISSION;
+ else if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_U32, &av, PM_TYPE_U32)) >= 0) {
+ if (av.ul > 1) /* only zero or one allowed */
+ sts = PM_ERR_CONV;
+ else
+ threads = av.ul;
+ }
+ break;
+ case 2: /* proc.control.perclient.threads */
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_U32, &av, PM_TYPE_U32)) >= 0) {
+ sts = proc_ctx_set_threads(pmda->e_context, av.ul);
+ }
+ break;
+ case 3: /* proc.control.perclient.cgroups */
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_STRING, &av, PM_TYPE_STRING)) >= 0) {
+ if ((sts = proc_ctx_set_cgroups(pmda->e_context, av.cp)) < 0)
+ free(av.cp);
+ }
+ break;
+ default:
+ sts = PM_ERR_PERMISSION;
+ }
+ if (sts < 0)
+ break;
+ }
+
+ have_access = proc_ctx_revert(pmda->e_context);
+ return sts;
+}
+
+static int
+proc_text(int ident, int type, char **buf, pmdaExt *pmda)
+{
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ int sts = pmdaDynamicLookupText(ident, type, buf, pmda);
+ if (sts != -ENOENT)
+ return sts;
+ }
+ return pmdaText(ident, type, buf, pmda);
+}
+
+static int
+proc_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupName(pmda, name);
+ if (tree == NULL)
+ return PM_ERR_NAME;
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "proc_pmid: name=%s tree:\n", name);
+ __pmDumpNameNode(stderr, tree->root, 1);
+ }
+ return pmdaTreePMID(tree, name, pmid);
+}
+
+static int
+proc_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupPMID(pmda, pmid);
+ if (tree == NULL)
+ return PM_ERR_PMID;
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "proc_name: pmid=%s tree:\n", pmIDStr(pmid));
+ __pmDumpNameNode(stderr, tree->root, 1);
+ }
+ return pmdaTreeName(tree, pmid, nameset);
+}
+
+static int
+proc_children(const char *name, int flag, char ***kids, int **sts, pmdaExt *pmda)
+{
+ pmdaNameSpace *tree = pmdaDynamicLookupName(pmda, name);
+ if (tree == NULL)
+ return PM_ERR_NAME;
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "proc_children: name=%s flag=%d tree:\n", name, flag);
+ __pmDumpNameNode(stderr, tree->root, 1);
+ }
+ return pmdaTreeChildren(tree, name, flag, kids, sts);
+}
+
+/*
+ * Helper routines for accessing a generic static string dictionary
+ */
+
+char *
+proc_strings_lookup(int index)
+{
+ char *value;
+ pmInDom dict = INDOM(STRINGS_INDOM);
+
+ if (pmdaCacheLookup(dict, index, &value, NULL) == PMDA_CACHE_ACTIVE)
+ return value;
+ return "";
+}
+
+int
+proc_strings_insert(const char *buf)
+{
+ pmInDom dict = INDOM(STRINGS_INDOM);
+ return pmdaCacheStore(dict, PMDA_CACHE_ADD, buf, NULL);
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+
+void
+__PMDA_INIT_CALL
+proc_init(pmdaInterface *dp)
+{
+ int nindoms = sizeof(indomtab)/sizeof(indomtab[0]);
+ int nmetrics = sizeof(metrictab)/sizeof(metrictab[0]);
+ char *envpath;
+
+ _pm_system_pagesize = getpagesize();
+ if ((envpath = getenv("PROC_STATSPATH")) != NULL)
+ proc_statspath = envpath;
+
+ if (_isDSO) {
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(helppath, sizeof(helppath), "%s%c" "proc" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_6, "proc DSO", helppath);
+ }
+
+ if (dp->status != 0)
+ return;
+ dp->comm.flags |= PDU_FLAG_AUTH;
+
+ dp->version.six.instance = proc_instance;
+ dp->version.six.store = proc_store;
+ dp->version.six.fetch = proc_fetch;
+ dp->version.six.text = proc_text;
+ dp->version.six.pmid = proc_pmid;
+ dp->version.six.name = proc_name;
+ dp->version.six.children = proc_children;
+ dp->version.six.attribute = proc_ctx_attrs;
+ pmdaSetEndContextCallBack(dp, proc_ctx_end);
+ pmdaSetFetchCallBack(dp, proc_fetchCallBack);
+
+ /*
+ * Initialize the instance domain table.
+ */
+ indomtab[CPU_INDOM].it_indom = CPU_INDOM;
+ indomtab[DISK_INDOM].it_indom = DISK_INDOM;
+ indomtab[DEVT_INDOM].it_indom = DEVT_INDOM;
+ indomtab[PROC_INDOM].it_indom = PROC_INDOM;
+ indomtab[STRINGS_INDOM].it_indom = STRINGS_INDOM;
+ indomtab[CGROUP_SUBSYS_INDOM].it_indom = CGROUP_SUBSYS_INDOM;
+ indomtab[CGROUP_MOUNTS_INDOM].it_indom = CGROUP_MOUNTS_INDOM;
+
+ proc_pid.indom = &indomtab[PROC_INDOM];
+
+ /*
+ * Read System.map and /proc/ksyms. Used to translate wait channel
+ * addresses to symbol names.
+ * Added by Mike Mason <mmlnx@us.ibm.com>
+ */
+ read_ksym_sources(kernel_uname.release);
+
+ cgroup_init(metrictab, nmetrics);
+ proc_ctx_init();
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, indomtab, nindoms, metrictab, nmetrics);
+
+ /* string metrics use the pmdaCache API for value indexing */
+ pmdaCacheOp(INDOM(STRINGS_INDOM), PMDA_CACHE_STRINGS);
+
+ /* cgroup metrics use the pmdaCache API for indom indexing */
+ pmdaCacheOp(INDOM(CPU_INDOM), PMDA_CACHE_CULL);
+ pmdaCacheOp(INDOM(DISK_INDOM), PMDA_CACHE_CULL);
+ pmdaCacheOp(INDOM(CGROUP_SUBSYS_INDOM), PMDA_CACHE_CULL);
+ pmdaCacheOp(INDOM(CGROUP_MOUNTS_INDOM), PMDA_CACHE_CULL);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "no-access-checks", 0, 'A', 0, "no access checks will be performed (insecure, beware!)" },
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "with-threads", 0, 'L', 0, "include threads in the all-processes instance domain" },
+ { "from-cgroup", 1, 'r', "NAME", "restrict monitoring to processes in the named cgroup" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "AD:d:l:Lr:U:?",
+ .long_options = longopts,
+};
+
+int
+main(int argc, char **argv)
+{
+ int c, sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+ char *username = "root";
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ snprintf(helppath, sizeof(helppath), "%s%c" "proc" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_6, pmProgname, PROC, "proc.log", helppath);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &dispatch)) != EOF) {
+ switch (c) {
+ case 'A':
+ all_access = 1;
+ break;
+ case 'L':
+ threads = 1;
+ break;
+ case 'r':
+ cgroups = opts.optarg;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ __pmSetProcessIdentity(username);
+
+ proc_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/linux_proc/proc_pid.c b/src/pmdas/linux_proc/proc_pid.c
new file mode 100644
index 0000000..152d96c
--- /dev/null
+++ b/src/pmdas/linux_proc/proc_pid.c
@@ -0,0 +1,957 @@
+/*
+ * Linux proc/<pid>/{stat,statm,status,...} Clusters
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2000,2004,2006 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "proc_pid.h"
+#include "indom.h"
+
+static proc_pid_list_t pids;
+
+static int
+compare_pid(const void *pa, const void *pb)
+{
+ int a = *(int *)pa;
+ int b = *(int *)pb;
+ return a - b;
+}
+
+static void
+pidlist_append_pid(int pid)
+{
+ if (pids.count >= pids.size) {
+ pids.size += 64;
+ if (!(pids.pids = (int *)realloc(pids.pids, pids.size * sizeof(int)))) {
+ perror("pidlist_append: out of memory");
+ pids.size = pids.count = 0;
+ return; /* soldier on bravely */
+ }
+ }
+ pids.pids[pids.count++] = pid;
+}
+
+static void
+pidlist_append(const char *pidname)
+{
+ pidlist_append_pid(atoi(pidname));
+}
+
+static void
+tasklist_append(const char *pid)
+{
+ DIR *taskdirp;
+ struct dirent *tdp;
+ char taskpath[1024];
+
+ sprintf(taskpath, "%s/proc/%s/task", proc_statspath, pid);
+ if ((taskdirp = opendir(taskpath)) != NULL) {
+ while ((tdp = readdir(taskdirp)) != NULL) {
+ if (!isdigit((int)tdp->d_name[0]) || strcmp(pid, tdp->d_name) == 0)
+ continue;
+ pidlist_append(tdp->d_name);
+ }
+ closedir(taskdirp);
+ }
+}
+
+static int
+refresh_cgroup_pidlist(int want_threads, const char *cgroup)
+{
+ char path[MAXPATHLEN];
+ FILE *fp;
+ int pid;
+
+ /*
+ * We're running in cgroups mode where a subset of the processes is
+ * going to be returned based on the cgroup specified earlier via a
+ * store into the proc.control.{all,perclient}.cgroups metric.
+ *
+ * Use the "cgroup.procs" or "tasks" file depending on want_threads.
+ * Note that both these files are already sorted, ascending numeric.
+ */
+ if (want_threads)
+ snprintf(path, sizeof(path), "%s%s/tasks", proc_statspath, cgroup);
+ else
+ snprintf(path, sizeof(path), "%s%s/cgroup.procs", proc_statspath, cgroup);
+
+ if ((fp = fopen(path, "r")) != NULL) {
+ while (fscanf(fp, "%d\n", &pid) == 1)
+ pidlist_append_pid(pid);
+ fclose(fp);
+ }
+ return 0;
+}
+
+static int
+refresh_global_pidlist(int want_threads)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ char path[MAXPATHLEN];
+
+ snprintf(path, sizeof(path), "%s/proc", proc_statspath);
+ if ((dirp = opendir(path)) == NULL)
+ return -oserror();
+
+ /* note: readdir on /proc ignores threads */
+ while ((dp = readdir(dirp)) != NULL) {
+ if (isdigit((int)dp->d_name[0])) {
+ pidlist_append(dp->d_name);
+ if (want_threads)
+ tasklist_append(dp->d_name);
+ }
+ }
+ closedir(dirp);
+
+ qsort(pids.pids, pids.count, sizeof(int), compare_pid);
+ return 0;
+}
+
+static void
+refresh_proc_pidlist(proc_pid_t *proc_pid)
+{
+ int i;
+ int fd;
+ char *p;
+ char buf[MAXPATHLEN];
+ __pmHashNode *node, *next, *prev;
+ proc_pid_entry_t *ep;
+ pmdaIndom *indomp = proc_pid->indom;
+
+ if (indomp->it_numinst < pids.count)
+ indomp->it_set = (pmdaInstid *)realloc(indomp->it_set,
+ pids.count * sizeof(pmdaInstid));
+ indomp->it_numinst = pids.count;
+
+ /*
+ * invalidate all entries so we can harvest pids that have exited
+ */
+ for (i=0; i < proc_pid->pidhash.hsize; i++) {
+ for (node=proc_pid->pidhash.hash[i]; node != NULL; node = node->next) {
+ ep = (proc_pid_entry_t *)node->data;
+ ep->flags = 0;
+ }
+ }
+
+ /*
+ * walk pid list and add new pids to the hash table,
+ * marking entries valid as we go ...
+ */
+ for (i=0; i < pids.count; i++) {
+ node = __pmHashSearch(pids.pids[i], &proc_pid->pidhash);
+ if (node == NULL) {
+ int k = 0;
+
+ ep = (proc_pid_entry_t *)malloc(sizeof(proc_pid_entry_t));
+ memset(ep, 0, sizeof(proc_pid_entry_t));
+
+ ep->id = pids.pids[i];
+
+ snprintf(buf, sizeof(buf), "%s/proc/%d/cmdline", proc_statspath, pids.pids[i]);
+ if ((fd = open(buf, O_RDONLY)) >= 0) {
+ sprintf(buf, "%06d ", pids.pids[i]);
+ if ((k = read(fd, buf+7, sizeof(buf)-8)) > 0) {
+ p = buf + k +7;
+ *p-- = '\0';
+ /* Skip trailing nils, i.e. don't replace them */
+ while (buf+7 < p) {
+ if (*p-- != '\0') {
+ break;
+ }
+ }
+ /* Remove NULL terminators from cmdline string array */
+ /* Suggested by Mike Mason <mmlnx@us.ibm.com> */
+ while (buf+7 < p) {
+ if (*p == '\0') *p = ' ';
+ p--;
+ }
+ }
+ close(fd);
+ }
+
+ if (k == 0) {
+ /*
+ * If a process is swapped out, /proc/<pid>/cmdline
+ * returns an empty string so we have to get it
+ * from /proc/<pid>/status or /proc/<pid>/stat
+ */
+ sprintf(buf, "%s/proc/%d/status", proc_statspath, pids.pids[i]);
+ if ((fd = open(buf, O_RDONLY)) >= 0) {
+ /* We engage in a bit of a hanky-panky here:
+ * the string should look like "123456 (name)",
+ * we get it from /proc/XX/status as "Name: name\n...",
+ * to fit the 6 digits of PID and opening parenthesis,
+ * save 2 bytes at the start of the buffer.
+ * And don't forget to leave 2 bytes for the trailing
+ * parenthesis and the nil. Here is
+ * an example of what we're trying to achieve:
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | | | N| a| m| e| :|\t| i| n| i| t|\n| S|...
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ * | 0| 0| 0| 0| 0| 1| | (| i| n| i| t| )|\0|...
+ * +--+--+--+--+--+--+--+--+--+--+--+--+--+--+ */
+ if ((k = read(fd, buf+2, sizeof(buf)-4)) > 0) {
+ int bc;
+
+ if ((p = strchr(buf+2, '\n')) == NULL)
+ p = buf+k;
+ p[0] = ')';
+ p[1] = '\0';
+ bc = sprintf(buf, "%06d ", pids.pids[i]);
+ buf[bc] = '(';
+ }
+ close(fd);
+ }
+ }
+
+ if (k <= 0) {
+ /* hmm .. must be exiting */
+ sprintf(buf, "%06d <exiting>", pids.pids[i]);
+ }
+
+ ep->name = strdup(buf);
+
+ __pmHashAdd(pids.pids[i], (void *)ep, &proc_pid->pidhash);
+ // fprintf(stderr, "## ADDED \"%s\" to hash table\n", buf);
+ }
+ else
+ ep = (proc_pid_entry_t *)node->data;
+
+ /* mark pid as still existing */
+ ep->flags |= PROC_PID_FLAG_VALID;
+
+ /* refresh the indom pointer */
+ indomp->it_set[i].i_inst = ep->id;
+ indomp->it_set[i].i_name = ep->name;
+ }
+
+ /*
+ * harvest exited pids from the pid hash table
+ */
+ for (i=0; i < proc_pid->pidhash.hsize; i++) {
+ for (prev=NULL, node=proc_pid->pidhash.hash[i]; node != NULL;) {
+ next = node->next;
+ ep = (proc_pid_entry_t *)node->data;
+ // fprintf(stderr, "CHECKING key=%d node=" PRINTF_P_PFX "%p prev=" PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p ep=" PRINTF_P_PFX "%p valid=%d\n",
+ // ep->id, node, prev, node->next, ep, ep->valid);
+ if (!(ep->flags & PROC_PID_FLAG_VALID)) {
+ // fprintf(stderr, "DELETED key=%d name=\"%s\"\n", ep->id, ep->name);
+ if (ep->name != NULL)
+ free(ep->name);
+ if (ep->stat_buf != NULL)
+ free(ep->stat_buf);
+ if (ep->status_buf != NULL)
+ free(ep->status_buf);
+ if (ep->statm_buf != NULL)
+ free(ep->statm_buf);
+ if (ep->maps_buf != NULL)
+ free(ep->maps_buf);
+ if (ep->schedstat_buf != NULL)
+ free(ep->schedstat_buf);
+ if (ep->io_buf != NULL)
+ free(ep->io_buf);
+ if (ep->wchan_buf != NULL)
+ free(ep->wchan_buf);
+
+ if (prev == NULL)
+ proc_pid->pidhash.hash[i] = node->next;
+ else
+ prev->next = node->next;
+ free(ep);
+ free(node);
+ }
+ else {
+ prev = node;
+ }
+ if ((node = next) == NULL)
+ break;
+ }
+ }
+}
+
+int
+refresh_proc_pid(proc_pid_t *proc_pid, int threads, const char *cgroups)
+{
+ int sts;
+
+ pids.count = 0;
+ pids.threads = threads;
+
+ sts = (cgroups && cgroups[0] != '\0') ?
+ refresh_cgroup_pidlist(threads, cgroups) :
+ refresh_global_pidlist(threads);
+ if (sts < 0)
+ return sts;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr,
+ "refresh_proc_pid: %d pids (threads=%d, cgroups=\"%s\")\n",
+ sts, threads, cgroups ? cgroups : "");
+#endif
+
+ refresh_proc_pidlist(proc_pid);
+ return 0;
+}
+
+
+/*
+ * Open a proc file, taking into account that we may want thread info
+ * rather than process information.
+ *
+ * We make (ab)use of some obscure Linux procfs mechanisms here!
+ * Even though readdir(/proc) does not contain tasks, we can still open
+ * taskid directory files; on top of that, the tasks sub-directory in a
+ * task group has all (peer) tasks in that group, even for "children".
+ */
+static int
+proc_open(const char *base, proc_pid_entry_t *ep)
+{
+ int fd;
+ char buf[128];
+
+ if (pids.threads) {
+ sprintf(buf, "%s/proc/%d/task/%d/%s", proc_statspath, ep->id, ep->id, base);
+ if ((fd = open(buf, O_RDONLY)) >= 0)
+ return fd;
+ /* fallback to /proc path if task path open fails */
+ }
+ sprintf(buf, "%s/proc/%d/%s", proc_statspath, ep->id, base);
+ return open(buf, O_RDONLY);
+}
+
+static DIR *
+proc_opendir(const char *base, proc_pid_entry_t *ep)
+{
+ DIR *dir;
+ char buf[128];
+
+ if (pids.threads) {
+ sprintf(buf, "%s/proc/%d/task/%d/%s", proc_statspath, ep->id, ep->id, base);
+ if ((dir = opendir(buf)) != NULL)
+ return dir;
+ /* fallback to /proc path if task path opendir fails */
+ }
+ sprintf(buf, "%s/proc/%d/%s", proc_statspath, ep->id, base);
+ return opendir(buf);
+}
+
+/*
+ * fetch a proc/<pid>/stat entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_stat(int id, proc_pid_t *proc_pid)
+{
+ int fd;
+ int sts = 0;
+ int n;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+ char buf[1024];
+
+ if (node == NULL) {
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) == (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) {
+ char ibuf[1024];
+ fprintf(stderr, "fetch_proc_pid_stat: __pmHashSearch(%d, hash[%s]) -> NULL\n", id, pmInDomStr_r(proc_pid->indom->it_indom, ibuf, sizeof(ibuf)));
+ }
+#endif
+ return NULL;
+ }
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_STAT_FETCHED)) {
+ if ((fd = proc_open("stat", ep)) < 0) {
+ sts = -oserror();
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) == (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) {
+ char ibuf[1024];
+ char ebuf[1024];
+ fprintf(stderr, "fetch_proc_pid_stat: proc_open(\"stat\", ...) failed: id=%d, indom=%s, sts=%s\n", id, pmInDomStr_r(proc_pid->indom->it_indom, ibuf, sizeof(ibuf)), pmErrStr_r(sts, ebuf, sizeof(ebuf)));
+ }
+#endif
+ }
+ else {
+ if ((n = read(fd, buf, sizeof(buf))) < 0) {
+ sts = -oserror();
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) == (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) {
+ char ibuf[1024];
+ char ebuf[1024];
+ fprintf(stderr, "fetch_proc_pid_stat: read \"stat\" failed: id=%d, indom=%s, sts=%s\n", id, pmInDomStr_r(proc_pid->indom->it_indom, ibuf, sizeof(ibuf)), pmErrStr_r(sts, ebuf, sizeof(ebuf)));
+ }
+#endif
+ }
+ else {
+ if (n == 0) {
+ /* eh? */
+ sts = -1;
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) == (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) {
+ char ibuf[1024];
+ fprintf(stderr, "fetch_proc_pid_stat: read \"stat\" EOF?: id=%d, indom=%s\n", id, pmInDomStr_r(proc_pid->indom->it_indom, ibuf, sizeof(ibuf)));
+ }
+#endif
+ }
+ else {
+ if (ep->stat_buflen <= n) {
+ ep->stat_buflen = n;
+ ep->stat_buf = (char *)realloc(ep->stat_buf, n);
+ }
+ memcpy(ep->stat_buf, buf, n);
+ ep->stat_buf[n-1] = '\0';
+ sts = 0;
+ }
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_STAT_FETCHED;
+ }
+
+ if (!(ep->flags & PROC_PID_FLAG_WCHAN_FETCHED)) {
+ if ((fd = proc_open("wchan", ep)) < 0) {
+ /* ignore failure here, backwards compat */
+ ;
+ }
+ else {
+ if ((n = read(fd, buf, sizeof(buf)-1)) < 0) {
+ sts = -oserror();
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) == (DBG_TRACE_LIBPMDA|DBG_TRACE_DESPERATE)) {
+ char ibuf[1024];
+ char ebuf[1024];
+ fprintf(stderr, "fetch_proc_pid_stat: read \"wchan\" failed: id=%d, indom=%s, sts=%s\n", id, pmInDomStr_r(proc_pid->indom->it_indom, ibuf, sizeof(ibuf)), pmErrStr_r(sts, ebuf, sizeof(ebuf)));
+ }
+#endif
+ }
+ else {
+ if (n == 0) {
+ /* wchan is empty, nothing to add here */
+ ;
+ }
+ else {
+ n++; /* no terminating null (from kernel) */
+ if (ep->wchan_buflen <= n) {
+ ep->wchan_buflen = n;
+ ep->wchan_buf = (char *)realloc(ep->wchan_buf, n);
+ }
+ memcpy(ep->wchan_buf, buf, n-1);
+ ep->wchan_buf[n-1] = '\0';
+ }
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_WCHAN_FETCHED;
+ }
+
+ if (sts < 0)
+ return NULL;
+ return ep;
+}
+
+/*
+ * fetch a proc/<pid>/status entry for pid
+ * Added by Mike Mason <mmlnx@us.ibm.com>
+ */
+proc_pid_entry_t *
+fetch_proc_pid_status(int id, proc_pid_t *proc_pid)
+{
+ int sts = 0;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_STATUS_FETCHED)) {
+ int fd;
+ int n;
+ char buf[1024];
+ char *curline;
+
+ if ((fd = proc_open("status", ep)) < 0)
+ sts = -oserror();
+ else if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0)
+ sts = -1;
+ else {
+ if (ep->status_buflen < n) {
+ ep->status_buflen = n;
+ ep->status_buf = (char *)realloc(ep->status_buf, n);
+ }
+
+ if (ep->status_buf == NULL)
+ sts = -1;
+ else {
+ memcpy(ep->status_buf, buf, n);
+ ep->status_buf[n-1] = '\0';
+ }
+ }
+ }
+
+ if (sts == 0) {
+ /* assign pointers to individual lines in buffer */
+ curline = ep->status_buf;
+
+ while (strncmp(curline, "Uid:", 4)) {
+ curline = index(curline, '\n') + 1;
+ }
+
+ /* user & group IDs */
+ ep->status_lines.uid = strsep(&curline, "\n");
+ ep->status_lines.gid = strsep(&curline, "\n");
+
+ while (curline) {
+ if (strncmp(curline, "VmSize:", 7) == 0) {
+ /* memory info - these lines don't exist for kernel threads */
+ ep->status_lines.vmsize = strsep(&curline, "\n");
+ ep->status_lines.vmlck = strsep(&curline, "\n");
+ if (strncmp(curline, "VmRSS:", 6) != 0)
+ curline = index(curline, '\n') + 1; // Have VmPin: ?
+ if (strncmp(curline, "VmRSS:", 6) != 0)
+ curline = index(curline, '\n') + 1; // Have VmHWM: ?
+ ep->status_lines.vmrss = strsep(&curline, "\n");
+ ep->status_lines.vmdata = strsep(&curline, "\n");
+ ep->status_lines.vmstk = strsep(&curline, "\n");
+ ep->status_lines.vmexe = strsep(&curline, "\n");
+ ep->status_lines.vmlib = strsep(&curline, "\n");
+ curline = index(curline, '\n') + 1; // skip VmPTE
+ ep->status_lines.vmswap = strsep(&curline, "\n");
+ ep->status_lines.threads = strsep(&curline, "\n");
+ } else
+ if (strncmp(curline, "SigPnd:", 7) == 0) {
+ /* signal masks */
+ ep->status_lines.sigpnd = strsep(&curline, "\n");
+ ep->status_lines.sigblk = strsep(&curline, "\n");
+ ep->status_lines.sigign = strsep(&curline, "\n");
+ ep->status_lines.sigcgt = strsep(&curline, "\n");
+ break; /* we're done */
+ } else {
+ curline = index(curline, '\n') + 1;
+ }
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_STATUS_FETCHED;
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * fetch a proc/<pid>/statm entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_statm(int id, proc_pid_t *proc_pid)
+{
+ int sts = 0;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_STATM_FETCHED)) {
+ char buf[1024];
+ int fd, n;
+
+ if ((fd = proc_open("statm", ep)) < 0)
+ sts = -oserror();
+ else
+ if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0)
+ /* eh? */
+ sts = -1;
+ else {
+ if (ep->statm_buflen <= n) {
+ ep->statm_buflen = n;
+ ep->statm_buf = (char *)realloc(ep->statm_buf, n);
+ }
+ memcpy(ep->statm_buf, buf, n);
+ ep->statm_buf[n-1] = '\0';
+ }
+ }
+
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_STATM_FETCHED;
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+
+/*
+ * fetch a proc/<pid>/maps entry for pid
+ * WARNING: This can be very large! Only ask for it if you really need it.
+ * Added by Mike Mason <mmlnx@us.ibm.com>
+ */
+proc_pid_entry_t *
+fetch_proc_pid_maps(int id, proc_pid_t *proc_pid)
+{
+ int sts = 0;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+ char *maps_bufptr = NULL;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_MAPS_FETCHED)) {
+ int fd;
+
+ if ((fd = proc_open("maps", ep)) < 0)
+ sts = -oserror();
+ else {
+ char buf[1024];
+ int n, len = 0;
+
+ while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ len += n;
+ if (ep->maps_buflen <= len) {
+ ep->maps_buflen = len + 1;
+ ep->maps_buf = (char *)realloc(ep->maps_buf, ep->maps_buflen);
+ }
+ maps_bufptr = ep->maps_buf + len - n;
+ memcpy(maps_bufptr, buf, n);
+ }
+ ep->flags |= PROC_PID_FLAG_MAPS_FETCHED;
+ /* If there are no maps, make maps_buf point to a zero length string. */
+ if (ep->maps_buflen == 0) {
+ ep->maps_buf = (char *)malloc(1);
+ ep->maps_buflen = 1;
+ }
+ ep->maps_buf[ep->maps_buflen - 1] = '\0';
+ close(fd);
+ }
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * fetch a proc/<pid>/schedstat entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_schedstat(int id, proc_pid_t *proc_pid)
+{
+ int sts = 0;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_SCHEDSTAT_FETCHED)) {
+ int fd, n;
+ char buf[1024];
+
+ if ((fd = proc_open("schedstat", ep)) < 0)
+ sts = -oserror();
+ else
+ if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0)
+ /* eh? */
+ sts = -1;
+ else {
+ if (ep->schedstat_buflen <= n) {
+ ep->schedstat_buflen = n;
+ ep->schedstat_buf = (char *)realloc(ep->schedstat_buf, n);
+ }
+ memcpy(ep->schedstat_buf, buf, n);
+ ep->schedstat_buf[n-1] = '\0';
+ }
+ }
+ if (fd >= 0) {
+ close(fd);
+ }
+ ep->flags |= PROC_PID_FLAG_SCHEDSTAT_FETCHED;
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * fetch a proc/<pid>/io entry for pid
+ *
+ * Depends on kernel built with CONFIG_TASK_IO_ACCOUNTING=y
+ * which means the following must also be set:
+ * CONFIG_TASKSTATS=y
+ * CONFIG_TASK_DELAY_ACCT=y
+ * CONFIG_TASK_XACCT=y
+ */
+proc_pid_entry_t *
+fetch_proc_pid_io(int id, proc_pid_t *proc_pid)
+{
+ int sts = 0;
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_IO_FETCHED)) {
+ int fd, n;
+ char buf[1024];
+ char *curline;
+
+ if ((fd = proc_open("io", ep)) < 0)
+ sts = -oserror();
+ else if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0)
+ sts = -1;
+ else {
+ if (ep->io_buflen < n) {
+ ep->io_buflen = n;
+ ep->io_buf = (char *)realloc(ep->io_buf, n);
+ }
+
+ if (ep->io_buf == NULL)
+ sts = -1;
+ else {
+ memcpy(ep->io_buf, buf, n);
+ ep->io_buf[n-1] = '\0';
+ }
+ }
+ }
+
+ if (sts == 0) {
+ /* assign pointers to individual lines in buffer */
+ curline = ep->io_buf;
+ ep->io_lines.rchar = strsep(&curline, "\n");
+ ep->io_lines.wchar = strsep(&curline, "\n");
+ ep->io_lines.syscr = strsep(&curline, "\n");
+ ep->io_lines.syscw = strsep(&curline, "\n");
+ ep->io_lines.readb = strsep(&curline, "\n");
+ ep->io_lines.writeb = strsep(&curline, "\n");
+ ep->io_lines.cancel = strsep(&curline, "\n");
+ ep->flags |= PROC_PID_FLAG_IO_FETCHED;
+ }
+ if (fd >= 0)
+ close(fd);
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * fetch a proc/<pid>/fd entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_fd(int id, proc_pid_t *proc_pid)
+{
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_FD_FETCHED)) {
+ uint32_t de_count = 0;
+ DIR *dir = proc_opendir("fd", ep);
+
+ if (dir == NULL) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "failed to open fd path for pid %d\n", ep->id);
+#endif
+ return NULL;
+ }
+ while (readdir(dir) != NULL) {
+ de_count++;
+ }
+ closedir(dir);
+ ep->fd_count = de_count - 2; /* subtract cwd and parent entries */
+ ep->flags |= PROC_PID_FLAG_FD_FETCHED;
+ }
+
+ return ep;
+}
+
+/*
+ * From the kernel format for a single process cgroup set:
+ * 2:cpu:/
+ * 1:cpuset:/
+ *
+ * Produce the same one-line format string that "ps" uses:
+ * "cpu:/;cpuset:/"
+ */
+static void
+proc_cgroup_reformat(char *buf, int len, char *fmt)
+{
+ char *target = fmt, *p, *s = NULL;
+
+ *target = '\0';
+ for (p = buf; p - buf < len; p++) {
+ if (*p == '\0')
+ break;
+ if (*p == ':' && !s) /* position "s" at start */
+ s = p + 1;
+ if (*p != '\n' || !s) /* find end of this line */
+ continue;
+ if (target != fmt) /* not the first cgroup? */
+ strncat(target, ";", 2);
+ /* have a complete cgroup line now, copy it over */
+ strncat(target, s, (p - s));
+ target += (p - s);
+ s = NULL; /* reset it for new line */
+ }
+}
+
+/*
+ * fetch a proc/<pid>/cgroup entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_cgroup(int id, proc_pid_t *proc_pid)
+{
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+ int sts = 0;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_CGROUP_FETCHED)) {
+ char buf[1024];
+ char fmt[1024];
+ int n, fd;
+
+ if ((fd = proc_open("cgroup", ep)) < 0)
+ sts = -oserror();
+ else if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0) {
+ setoserror(ENODATA);
+ sts = -1;
+ }
+ else {
+ /* reformat the buffer to match "ps" output format, then hash */
+ proc_cgroup_reformat(&buf[0], n, &fmt[0]);
+ ep->cgroup_id = proc_strings_insert(fmt);
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_CGROUP_FETCHED;
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * fetch a proc/<pid>/attr/current entry for pid
+ */
+proc_pid_entry_t *
+fetch_proc_pid_label(int id, proc_pid_t *proc_pid)
+{
+ __pmHashNode *node = __pmHashSearch(id, &proc_pid->pidhash);
+ proc_pid_entry_t *ep;
+ int sts = 0;
+
+ if (node == NULL)
+ return NULL;
+ ep = (proc_pid_entry_t *)node->data;
+
+ if (!(ep->flags & PROC_PID_FLAG_LABEL_FETCHED)) {
+ char buf[1024];
+ int n, fd;
+
+ if ((fd = proc_open("attr/current", ep)) < 0)
+ sts = -oserror();
+ else if ((n = read(fd, buf, sizeof(buf))) < 0)
+ sts = -oserror();
+ else {
+ if (n == 0) {
+ setoserror(ENODATA);
+ sts = -1;
+ } else {
+ /* buffer matches "ps" output format, direct hash */
+ buf[sizeof(buf)-1] = '\0';
+ ep->label_id = proc_strings_insert(buf);
+ }
+ }
+ if (fd >= 0)
+ close(fd);
+ ep->flags |= PROC_PID_FLAG_LABEL_FETCHED;
+ }
+
+ return (sts < 0) ? NULL : ep;
+}
+
+/*
+ * Extract the ith (space separated) field from a char buffer.
+ * The first field starts at zero.
+ * BEWARE: return copy is in a static buffer.
+ */
+char *
+_pm_getfield(char *buf, int field)
+{
+ static int retbuflen = 0;
+ static char *retbuf = NULL;
+ char *p;
+ int i;
+
+ if (buf == NULL)
+ return NULL;
+
+ for (p=buf, i=0; i < field; i++) {
+ /* skip to the next space */
+ for (; *p && !isspace((int)*p); p++) {;}
+
+ /* skip to the next word */
+ for (; *p && isspace((int)*p); p++) {;}
+ }
+
+ /* return a null terminated copy of the field */
+ for (i=0; ; i++) {
+ if (isspace((int)p[i]) || p[i] == '\0' || p[i] == '\n')
+ break;
+ }
+
+ if (i >= retbuflen) {
+ retbuflen = i+4;
+ retbuf = (char *)realloc(retbuf, retbuflen);
+ }
+ memcpy(retbuf, p, i);
+ retbuf[i] = '\0';
+
+ return retbuf;
+}
diff --git a/src/pmdas/linux_proc/proc_pid.h b/src/pmdas/linux_proc/proc_pid.h
new file mode 100644
index 0000000..8835157
--- /dev/null
+++ b/src/pmdas/linux_proc/proc_pid.h
@@ -0,0 +1,289 @@
+/*
+ * Linux /proc/<pid>/... Clusters
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _PROC_PID_H
+#define _PROC_PID_H
+
+/*
+ * /proc/<pid>/stat metrics
+ */
+#define PROC_PID_STAT_PID 0
+#define PROC_PID_STAT_CMD 1
+#define PROC_PID_STAT_STATE 2
+#define PROC_PID_STAT_PPID 3
+#define PROC_PID_STAT_PGRP 4
+#define PROC_PID_STAT_SESSION 5
+#define PROC_PID_STAT_TTY 6
+#define PROC_PID_STAT_TTY_PGRP 7
+#define PROC_PID_STAT_FLAGS 8
+#define PROC_PID_STAT_MINFLT 9
+#define PROC_PID_STAT_CMIN_FLT 10
+#define PROC_PID_STAT_MAJ_FLT 11
+#define PROC_PID_STAT_CMAJ_FLT 12
+#define PROC_PID_STAT_UTIME 13
+#define PROC_PID_STAT_STIME 14
+#define PROC_PID_STAT_CUTIME 15
+#define PROC_PID_STAT_CSTIME 16
+#define PROC_PID_STAT_PRIORITY 17
+#define PROC_PID_STAT_NICE 18
+#define PROC_PID_STAT_REMOVED 19
+#define PROC_PID_STAT_IT_REAL_VALUE 20
+#define PROC_PID_STAT_START_TIME 21
+#define PROC_PID_STAT_VSIZE 22
+#define PROC_PID_STAT_RSS 23
+#define PROC_PID_STAT_RSS_RLIM 24
+#define PROC_PID_STAT_START_CODE 25
+#define PROC_PID_STAT_END_CODE 26
+#define PROC_PID_STAT_START_STACK 27
+#define PROC_PID_STAT_ESP 28
+#define PROC_PID_STAT_EIP 29
+#define PROC_PID_STAT_SIGNAL 30
+#define PROC_PID_STAT_BLOCKED 31
+#define PROC_PID_STAT_SIGIGNORE 32
+#define PROC_PID_STAT_SIGCATCH 33
+#define PROC_PID_STAT_WCHAN 34
+#define PROC_PID_STAT_NSWAP 35
+#define PROC_PID_STAT_CNSWAP 36
+#define PROC_PID_STAT_EXIT_SIGNAL 37
+#define PROC_PID_STAT_PROCESSOR 38
+#define PROC_PID_STAT_TTYNAME 39
+#define PROC_PID_STAT_WCHAN_SYMBOL 40
+#define PROC_PID_STAT_PSARGS 41
+
+/* number of fields in proc_pid_stat_entry_t */
+#define NR_PROC_PID_STAT 42
+
+/*
+ * metrics in /proc/<pid>/status
+ * Added by Mike Mason <mmlnx@us.ibm.com>
+ */
+#define PROC_PID_STATUS_UID 0
+#define PROC_PID_STATUS_EUID 1
+#define PROC_PID_STATUS_SUID 2
+#define PROC_PID_STATUS_FSUID 3
+#define PROC_PID_STATUS_GID 4
+#define PROC_PID_STATUS_EGID 5
+#define PROC_PID_STATUS_SGID 6
+#define PROC_PID_STATUS_FSGID 7
+#define PROC_PID_STATUS_UID_NM 8
+#define PROC_PID_STATUS_EUID_NM 9
+#define PROC_PID_STATUS_SUID_NM 10
+#define PROC_PID_STATUS_FSUID_NM 11
+#define PROC_PID_STATUS_GID_NM 12
+#define PROC_PID_STATUS_EGID_NM 13
+#define PROC_PID_STATUS_SGID_NM 14
+#define PROC_PID_STATUS_FSGID_NM 15
+#define PROC_PID_STATUS_SIGNAL 16
+#define PROC_PID_STATUS_BLOCKED 17
+#define PROC_PID_STATUS_SIGIGNORE 18
+#define PROC_PID_STATUS_SIGCATCH 19
+#define PROC_PID_STATUS_VMSIZE 20
+#define PROC_PID_STATUS_VMLOCK 21
+#define PROC_PID_STATUS_VMRSS 22
+#define PROC_PID_STATUS_VMDATA 23
+#define PROC_PID_STATUS_VMSTACK 24
+#define PROC_PID_STATUS_VMEXE 25
+#define PROC_PID_STATUS_VMLIB 26
+#define PROC_PID_STATUS_VMSWAP 27
+#define PROC_PID_STATUS_THREADS 28
+
+/* number of metrics from /proc/<pid>/status */
+#define NR_PROC_PID_STATUS 27
+
+/*
+ * metrics in /proc/<pid>/statm & /proc/<pid>/maps
+ */
+#define PROC_PID_STATM_SIZE 0
+#define PROC_PID_STATM_RSS 1
+#define PROC_PID_STATM_SHARE 2
+#define PROC_PID_STATM_TEXTRS 3
+#define PROC_PID_STATM_LIBRS 4
+#define PROC_PID_STATM_DATRS 5
+#define PROC_PID_STATM_DIRTY 6
+#define PROC_PID_STATM_MAPS 7
+
+/* number of fields in proc_pid_statm_entry_t */
+#define NR_PROC_PID_STATM 8
+
+/*
+ * metrics in /proc/<pid>/schedstat
+ */
+#define PROC_PID_SCHED_CPUTIME 0
+#define PROC_PID_SCHED_RUNDELAY 1
+#define PROC_PID_SCHED_PCOUNT 2
+#define NR_PROC_PID_SCHED 3
+
+/*
+ * metrics in /proc/<pid>/io
+ */
+#define PROC_PID_IO_RCHAR 0
+#define PROC_PID_IO_WCHAR 1
+#define PROC_PID_IO_SYSCR 2
+#define PROC_PID_IO_SYSCW 3
+#define PROC_PID_IO_READ_BYTES 4
+#define PROC_PID_IO_WRITE_BYTES 5
+#define PROC_PID_IO_CANCELLED_BYTES 6
+
+/*
+ * metrics in /proc/<pid>/fd
+ */
+#define PROC_PID_FD_COUNT 0
+
+
+/*
+ * metrics in /proc/<pid>/cgroup
+ */
+#define PROC_PID_CGROUP 0
+
+/*
+ * metrics in /proc/<pid>/attr/current
+ */
+#define PROC_PID_LABEL 0
+
+typedef struct { /* /proc/<pid>/status */
+ char *uid;
+ char *gid;
+ char *sigpnd;
+ char *sigblk;
+ char *sigign;
+ char *sigcgt;
+ char *vmsize;
+ char *vmlck;
+ char *vmrss;
+ char *vmdata;
+ char *vmstk;
+ char *vmexe;
+ char *vmlib;
+ char *vmswap;
+ char *threads;
+} status_lines_t;
+
+typedef struct { /* /proc/<pid>/io */
+ char *rchar;
+ char *wchar;
+ char *syscr;
+ char *syscw;
+ char *readb;
+ char *writeb;
+ char *cancel;
+} io_lines_t;
+
+enum {
+ PROC_PID_FLAG_VALID = 1<<0,
+ PROC_PID_FLAG_STAT_FETCHED = 1<<1,
+ PROC_PID_FLAG_STATM_FETCHED = 1<<2,
+ PROC_PID_FLAG_MAPS_FETCHED = 1<<3,
+ PROC_PID_FLAG_STATUS_FETCHED = 1<<4,
+ PROC_PID_FLAG_SCHEDSTAT_FETCHED = 1<<5,
+ PROC_PID_FLAG_IO_FETCHED = 1<<6,
+ PROC_PID_FLAG_WCHAN_FETCHED = 1<<7,
+ PROC_PID_FLAG_FD_FETCHED = 1<<8,
+ PROC_PID_FLAG_CGROUP_FETCHED = 1<<9,
+ PROC_PID_FLAG_LABEL_FETCHED = 1<<10,
+};
+
+typedef struct {
+ int id; /* pid, hash key and internal instance id */
+ int flags; /* combinations of PROC_PID_FLAG_* values */
+ char *name; /* external instance name (<pid> cmdline) */
+
+ /* /proc/<pid>/stat cluster */
+ int stat_buflen;
+ char *stat_buf;
+
+ /* /proc/<pid>/statm and /proc/<pid>/maps cluster */
+ int statm_buflen;
+ char *statm_buf;
+ int maps_buflen;
+ char *maps_buf;
+
+ /* /proc/<pid>/status cluster */
+ int status_buflen;
+ char *status_buf;
+ status_lines_t status_lines;
+
+ /* /proc/<pid>/schedstat cluster */
+ int schedstat_buflen;
+ char *schedstat_buf;
+
+ /* /proc/<pid>/io cluster */
+ int io_buflen;
+ char *io_buf;
+ io_lines_t io_lines;
+
+ /* /proc/<pid>/wchan cluster */
+ int wchan_buflen;
+ char *wchan_buf;
+
+ /* /proc/<pid>/fd cluster */
+ int fd_buflen;
+ uint32_t fd_count;
+ char *fd_buf;
+
+ /* /proc/<pid>/cgroup cluster */
+ int cgroup_id;
+
+ /* /proc/<pid>/attr/current cluster */
+ int label_id;
+} proc_pid_entry_t;
+
+typedef struct {
+ __pmHashCtl pidhash; /* hash table for current pids */
+ pmdaIndom *indom; /* instance domain table */
+} proc_pid_t;
+
+typedef struct {
+ int count; /* number of processes in the list */
+ int size; /* size of the buffer (pids) allocated */
+ int *pids; /* array of process identifiers */
+ int threads; /* /proc/PID/{xxx,task/PID/xxx} flag */
+} proc_pid_list_t;
+
+/* refresh the proc indom, reset all "fetched" flags */
+extern int refresh_proc_pid(proc_pid_t *, int, const char *);
+
+/* fetch a proc/<pid>/stat entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_stat(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/statm entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_statm(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/status entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_status(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/maps entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_maps(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/schedstat entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_schedstat(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/io entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_io(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/fd entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_fd(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/cgroup entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_cgroup(int, proc_pid_t *);
+
+/* fetch a proc/<pid>/attr/current entry for pid */
+extern proc_pid_entry_t *fetch_proc_pid_label(int, proc_pid_t *);
+
+/* extract the ith space separated field from a buffer */
+extern char *_pm_getfield(char *, int);
+
+#endif /* _PROC_PID_H */
diff --git a/src/pmdas/linux_proc/proc_runq.c b/src/pmdas/linux_proc/proc_runq.c
new file mode 100644
index 0000000..07b68dc
--- /dev/null
+++ b/src/pmdas/linux_proc/proc_runq.c
@@ -0,0 +1,123 @@
+/*
+ * Linux /proc/runq metrics cluster
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include "proc_pid.h"
+#include "proc_runq.h"
+
+int
+refresh_proc_runq(proc_runq_t *proc_runq)
+{
+ int sz;
+ int fd;
+ char *p;
+ int sname;
+ DIR *dir;
+ struct dirent *d;
+ char fullpath[MAXPATHLEN];
+ char buf[4096];
+
+ memset(proc_runq, 0, sizeof(proc_runq_t));
+ if ((dir = opendir("/proc")) == NULL)
+ return -oserror();
+
+ while((d = readdir(dir)) != NULL) {
+ if (!isdigit((int)d->d_name[0]))
+ continue;
+ sprintf(fullpath, "/proc/%s/stat", d->d_name);
+ if ((fd = open(fullpath, O_RDONLY)) < 0)
+ continue;
+ sz = read(fd, buf, sizeof(buf));
+ close(fd);
+ buf[sizeof(buf)-1] = '\0';
+
+ /*
+ * defunct (state name is 'Z')
+ */
+ if (sz <= 0 || (p = _pm_getfield(buf, PROC_PID_STAT_STATE)) == NULL) {
+ proc_runq->unknown++;
+ continue;
+ }
+ if ((sname = *p) == 'Z') {
+ proc_runq->defunct++;
+ continue;
+ }
+
+ /*
+ * kernel process (not defunct and virtual size is zero)
+ */
+ if ((p = _pm_getfield(buf, PROC_PID_STAT_VSIZE)) == NULL) {
+ proc_runq->unknown++;
+ continue;
+ }
+ if (strcmp(p, "0") == 0) {
+ proc_runq->kernel++;
+ continue;
+ }
+
+ /*
+ * swapped (resident set size is zero)
+ */
+ if ((p = _pm_getfield(buf, PROC_PID_STAT_RSS)) == NULL) {
+ proc_runq->unknown++;
+ continue;
+ }
+ if (strcmp(p, "0") == 0) {
+ proc_runq->swapped++;
+ continue;
+ }
+
+ /*
+ * All other states
+ */
+ switch (sname) {
+ case 'R':
+ proc_runq->runnable++;
+ break;
+ case 'S':
+ proc_runq->sleeping++;
+ break;
+ case 'T':
+ proc_runq->stopped++;
+ break;
+ case 'D':
+ proc_runq->blocked++;
+ break;
+ /* case 'Z':
+ break; -- already counted above */
+ default:
+ fprintf(stderr, "UNKNOWN %c : %s\n", sname, buf);
+ proc_runq->unknown++;
+ break;
+ }
+ }
+ closedir(dir);
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA) {
+ fprintf(stderr, "refresh_runq: runnable=%d sleeping=%d stopped=%d blocked=%d unknown=%d\n",
+ proc_runq->runnable, proc_runq->sleeping, proc_runq->stopped,
+ proc_runq->blocked, proc_runq->unknown);
+ }
+#endif
+
+ return 0;
+}
diff --git a/src/pmdas/linux_proc/proc_runq.h b/src/pmdas/linux_proc/proc_runq.h
new file mode 100644
index 0000000..9739208
--- /dev/null
+++ b/src/pmdas/linux_proc/proc_runq.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _PROC_RUNQ_H
+#define _PROC_RUNQ_H
+
+typedef struct {
+ int runnable;
+ int blocked;
+ int sleeping;
+ int stopped;
+ int swapped;
+ int kernel;
+ int defunct;
+ int unknown;
+} proc_runq_t;
+
+extern int refresh_proc_runq(proc_runq_t *);
+
+#endif /* _PROC_RUNQ_H */
diff --git a/src/pmdas/linux_proc/root b/src/pmdas/linux_proc/root
new file mode 100644
index 0000000..5f26a89
--- /dev/null
+++ b/src/pmdas/linux_proc/root
@@ -0,0 +1,6 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+#include "root_xfs"
diff --git a/src/pmdas/linux_proc/root_proc b/src/pmdas/linux_proc/root_proc
new file mode 100644
index 0000000..91b8654
--- /dev/null
+++ b/src/pmdas/linux_proc/root_proc
@@ -0,0 +1,181 @@
+/*
+ * Metrics for the Linux proc PMDA
+ *
+ * Note:
+ * names and pmids migrated from the Linux PMDA, with the domain
+ * number changed from LINUX (60) to 3 (3)
+ */
+
+#ifndef PROC
+#define PROC 3
+#endif
+
+root {
+ cgroup
+ proc
+}
+
+cgroup {
+ subsys
+ mounts
+ groups PROC:*:*
+}
+
+cgroup.subsys {
+ hierarchy PROC:37:0
+ count PROC:37:1
+}
+
+cgroup.mounts {
+ subsys PROC:38:0
+ count PROC:38:1
+}
+
+proc {
+ nprocs PROC:8:99
+ psinfo
+ memory
+ runq
+ id
+ io
+ schedstat
+ fd
+ control
+}
+
+proc.psinfo {
+ pid PROC:8:0
+ cmd PROC:8:1
+ sname PROC:8:2
+ ppid PROC:8:3
+ pgrp PROC:8:4
+ session PROC:8:5
+ tty PROC:8:6
+ tty_pgrp PROC:8:7
+ flags PROC:8:8
+ minflt PROC:8:9
+ cmin_flt PROC:8:10
+ maj_flt PROC:8:11
+ cmaj_flt PROC:8:12
+ utime PROC:8:13
+ stime PROC:8:14
+ cutime PROC:8:15
+ cstime PROC:8:16
+ priority PROC:8:17
+ nice PROC:8:18
+ /* not valid in 2.2.1 PROC:8:19 */
+ it_real_value PROC:8:20
+ start_time PROC:8:21
+ vsize PROC:8:22
+ rss PROC:8:23
+ rss_rlim PROC:8:24
+ start_code PROC:8:25
+ end_code PROC:8:26
+ start_stack PROC:8:27
+ esp PROC:8:28
+ eip PROC:8:29
+ signal PROC:8:30
+ blocked PROC:8:31
+ sigignore PROC:8:32
+ sigcatch PROC:8:33
+ wchan PROC:8:34
+ nswap PROC:8:35
+ cnswap PROC:8:36
+ exit_signal PROC:8:37
+ processor PROC:8:38
+ ttyname PROC:8:39
+ wchan_s PROC:8:40
+ psargs PROC:8:41
+ signal_s PROC:24:16
+ blocked_s PROC:24:17
+ sigignore_s PROC:24:18
+ sigcatch_s PROC:24:19
+ threads PROC:24:28
+ cgroups PROC:11:0
+ labels PROC:12:0
+}
+
+proc.id {
+ uid PROC:24:0
+ euid PROC:24:1
+ suid PROC:24:2
+ fsuid PROC:24:3
+ gid PROC:24:4
+ egid PROC:24:5
+ sgid PROC:24:6
+ fsgid PROC:24:7
+ uid_nm PROC:24:8
+ euid_nm PROC:24:9
+ suid_nm PROC:24:10
+ fsuid_nm PROC:24:11
+ gid_nm PROC:24:12
+ egid_nm PROC:24:13
+ sgid_nm PROC:24:14
+ fsgid_nm PROC:24:15
+}
+
+proc.memory {
+ size PROC:9:0
+ rss PROC:9:1
+ share PROC:9:2
+ textrss PROC:9:3
+ librss PROC:9:4
+ datrss PROC:9:5
+ dirty PROC:9:6
+ maps PROC:9:7
+ vmsize PROC:24:20
+ vmlock PROC:24:21
+ vmrss PROC:24:22
+ vmdata PROC:24:23
+ vmstack PROC:24:24
+ vmexe PROC:24:25
+ vmlib PROC:24:26
+ vmswap PROC:24:27
+}
+
+proc.runq {
+ runnable PROC:13:0
+ blocked PROC:13:1
+ sleeping PROC:13:2
+ stopped PROC:13:3
+ swapped PROC:13:4
+ defunct PROC:13:5
+ unknown PROC:13:6
+ kernel PROC:13:7
+}
+
+proc.io {
+ rchar PROC:32:0
+ wchar PROC:32:1
+ syscr PROC:32:2
+ syscw PROC:32:3
+ read_bytes PROC:32:4
+ write_bytes PROC:32:5
+ cancelled_write_bytes PROC:32:6
+}
+
+proc.schedstat {
+ cpu_time PROC:31:0
+ run_delay PROC:31:1
+ pcount PROC:31:2
+}
+
+proc.fd {
+ count PROC:51:0
+}
+
+proc.control {
+ all
+ perclient
+}
+
+proc.control.all {
+ threads PROC:10:1
+}
+
+proc.control.perclient {
+ threads PROC:10:2
+ cgroups PROC:10:3
+}
+
+#undef PROC
diff --git a/src/pmdas/linux_xfs/GNUmakefile b/src/pmdas/linux_xfs/GNUmakefile
new file mode 100644
index 0000000..211f651
--- /dev/null
+++ b/src/pmdas/linux_xfs/GNUmakefile
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2003,2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = xfs
+DOMAIN = XFS
+CMDTARGET = pmda$(IAM)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+PMDAINIT = $(IAM)_init
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "xfs 11 pipe binary $(PMDADIR)/$(CMDTARGET) -d 11"
+
+CFILES = proc_fs_xfs.c filesys.c pmda.c
+HFILES = proc_fs_xfs.h filesys.h clusters.h indom.h
+
+SCRIPTS = Install Remove
+VERSION_SCRIPT = exports
+HELPTARGETS = help.dir help.pag
+LSRCFILES = help root root_xfs linux_xfs_migrate.conf $(SCRIPTS)
+LDIRT = $(HELPTARGETS) domain.h $(VERSION_SCRIPT)
+
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) $(HELPTARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help help.dir help.pag root root_xfs $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 root_xfs $(PCP_VAR_DIR)/pmns/root_xfs
+ $(INSTALL) -m 644 linux_xfs_migrate.conf $(PCP_VAR_DIR)/config/pmlogrewrite/linux_xfs_migrate.conf
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(HELPTARGETS) : help
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_xfs -v 2 -o help < help
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+pmda.o: domain.h
+pmda.o proc_fs_xfs.o: proc_fs_xfs.h
+filesys.o pmda.o: filesys.h
+pmda.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/linux_xfs/Install b/src/pmdas/linux_xfs/Install
new file mode 100755
index 0000000..4f68af7
--- /dev/null
+++ b/src/pmdas/linux_xfs/Install
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Linux XFS PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=xfs
+pmda_interface=3
+daemon_opt=true
+pipe_opt=true
+pmns_source=root_xfs
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/linux_xfs/Remove b/src/pmdas/linux_xfs/Remove
new file mode 100755
index 0000000..1210f45
--- /dev/null
+++ b/src/pmdas/linux_xfs/Remove
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Linux XFS PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=xfs
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/linux_xfs/clusters.h b/src/pmdas/linux_xfs/clusters.h
new file mode 100644
index 0000000..a984343
--- /dev/null
+++ b/src/pmdas/linux_xfs/clusters.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _CLUSTERS_H
+#define _CLUSTERS_H
+
+/*
+ * PMID cluster values ... to manage the PMID migration after the
+ * linux -> linux + xfs PMDAs split, these need to match the enum
+ * assigned values for CLUSTER_* from the original Linux PMDA.
+ */
+#define CLUSTER_XFS 16 /* /proc/fs/xfs/stat */
+#define CLUSTER_XFSBUF 17 /* /proc/fs/pagebuf/stat */
+#define CLUSTER_QUOTA 30 /* quotactl() */
+
+#define MIN_CLUSTER 16 /* first cluster number we use here */
+#define NUM_CLUSTERS 31 /* one more than highest cluster number used */
+
+#endif /* _CLUSTERS_H */
diff --git a/src/pmdas/linux_xfs/filesys.c b/src/pmdas/linux_xfs/filesys.c
new file mode 100644
index 0000000..34f8b66
--- /dev/null
+++ b/src/pmdas/linux_xfs/filesys.c
@@ -0,0 +1,183 @@
+/*
+ * XFS Filesystems Cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2004,2007 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "filesys.h"
+#include "proc_fs_xfs.h"
+
+static void
+refresh_filesys_projects(pmInDom qindom, filesys_t *fs)
+{
+ char buffer[MAXPATHLEN];
+ project_t *qp;
+ fs_quota_stat_t s;
+ fs_disk_quota_t d;
+ size_t idsz, devsz;
+ FILE *projects;
+ char *p, *idend;
+ uint32_t prid;
+ int qcmd, sts;
+
+ qcmd = QCMD(Q_XGETQSTAT, XQM_PRJQUOTA);
+ if (quotactl(qcmd, fs->device, 0, (void*)&s) < 0)
+ return;
+
+ if (s.qs_flags & XFS_QUOTA_PDQ_ENFD)
+ fs->flags |= FSF_QUOT_PROJ_ENF;
+ if (s.qs_flags & XFS_QUOTA_PDQ_ACCT)
+ fs->flags |= FSF_QUOT_PROJ_ACC;
+ else
+ return;
+
+ if ((projects = xfs_statsfile("/etc/projects", "r")) == NULL)
+ return;
+
+ qcmd = QCMD(Q_XQUOTASYNC, XQM_PRJQUOTA);
+ quotactl(qcmd, fs->device, 0, NULL);
+
+ while (fgets(buffer, sizeof(buffer), projects)) {
+ if (buffer[0] == '#')
+ continue;
+
+ prid = strtol(buffer, &idend, 10);
+ idsz = idend - buffer;
+ qcmd = QCMD(Q_XGETQUOTA, XQM_PRJQUOTA);
+ if (!idsz || quotactl(qcmd, fs->device, prid, (void *)&d) < 0)
+ continue;
+
+ devsz = strlen(fs->device);
+ p = malloc(idsz+devsz+2);
+ if (!p)
+ continue;
+ memcpy(p, buffer, idsz);
+ p[idsz] = ':';
+ memcpy(&p[idsz+1], fs->device, devsz+1);
+
+ qp = NULL;
+ sts = pmdaCacheLookupName(qindom, p, NULL, (void **)&qp);
+ if (sts == PMDA_CACHE_ACTIVE) /* repeated line in /etc/projects? */
+ goto next;
+ if (sts != PMDA_CACHE_INACTIVE) {
+ qp = (project_t *)malloc(sizeof(project_t));
+ if (!qp)
+ goto next;
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "refresh_filesys_projects: add \"%s\"\n", p);
+ }
+ qp->space_hard = d.d_blk_hardlimit;
+ qp->space_soft = d.d_blk_softlimit;
+ qp->space_used = d.d_bcount;
+ qp->space_time_left = d.d_btimer;
+ qp->files_hard = d.d_ino_hardlimit;
+ qp->files_soft = d.d_ino_softlimit;
+ qp->files_used = d.d_icount;
+ qp->files_time_left = d.d_itimer;
+ pmdaCacheStore(qindom, PMDA_CACHE_ADD, p, (void *)qp);
+next:
+ free(p);
+ }
+ fclose(projects);
+}
+
+char *
+scan_filesys_options(const char *options, const char *option)
+{
+ static char buffer[128];
+ char *s;
+
+ strncpy(buffer, options, sizeof(buffer));
+ buffer[sizeof(buffer)-1] = '\0';
+
+ s = strtok(buffer, ",");
+ while (s) {
+ if (strcmp(s, option) == 0)
+ return s;
+ s = strtok(NULL, ",");
+ }
+ return NULL;
+}
+
+int
+refresh_filesys(pmInDom filesys_indom, pmInDom quota_indom)
+{
+ char buf[MAXPATHLEN];
+ char realdevice[MAXPATHLEN];
+ filesys_t *fs;
+ pmInDom indom = filesys_indom;
+ FILE *fp;
+ char *path, *device, *type, *options;
+ int sts;
+
+ pmdaCacheOp(quota_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(filesys_indom, PMDA_CACHE_INACTIVE);
+
+ if ((fp = xfs_statsfile("/proc/mounts", "r")) == NULL)
+ return -oserror();
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if ((device = strtok(buf, " ")) == 0)
+ continue;
+
+ path = strtok(NULL, " ");
+ type = strtok(NULL, " ");
+ options = strtok(NULL, " ");
+ if (strcmp(type, "xfs") != 0)
+ continue;
+ if (strncmp(device, "/dev", 4) != 0)
+ continue;
+ if (realpath(device, realdevice) != NULL)
+ device = realdevice;
+
+ sts = pmdaCacheLookupName(indom, device, NULL, (void **)&fs);
+ if (sts == PMDA_CACHE_ACTIVE) /* repeated line in /proc/mounts? */
+ continue;
+ if (sts == PMDA_CACHE_INACTIVE) { /* re-activate an old mount */
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, device, fs);
+ if (strcmp(path, fs->path) != 0) { /* old device, new path */
+ free(fs->path);
+ fs->path = strdup(path);
+ }
+ if (strcmp(options, fs->options) != 0) { /* old device, new opts */
+ free(fs->options);
+ fs->options = strdup(options);
+ }
+ }
+ else { /* new mount */
+ if ((fs = malloc(sizeof(filesys_t))) == NULL)
+ continue;
+ fs->device = strdup(device);
+ fs->path = strdup(path);
+ fs->options = strdup(options);
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "refresh_filesys: add \"%s\" \"%s\"\n",
+ fs->path, device);
+ pmdaCacheStore(indom, PMDA_CACHE_ADD, device, fs);
+ }
+ fs->flags = 0;
+ refresh_filesys_projects(quota_indom, fs);
+ }
+
+ /*
+ * success
+ * Note: we do not call statfs() here since only some instances
+ * may be requested (rather, we do it in xfs_fetch, see pmda.c).
+ */
+ fclose(fp);
+ return 0;
+}
diff --git a/src/pmdas/linux_xfs/filesys.h b/src/pmdas/linux_xfs/filesys.h
new file mode 100644
index 0000000..d1a2ac4
--- /dev/null
+++ b/src/pmdas/linux_xfs/filesys.h
@@ -0,0 +1,108 @@
+/*
+ * XFS Filesystem Cluster
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2004,2007 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/vfs.h>
+#include <sys/quota.h>
+
+#define XQM_CMD(x) (('X'<<8)+(x)) /* note: forms first QCMD argument */
+#define XQM_COMMAND(x) (((x) & (0xff<<8)) == ('X'<<8)) /* test if for XFS */
+#define XQM_PRJQUOTA 2
+#define Q_XGETQUOTA XQM_CMD(3) /* get disk limits and usage */
+#define Q_XGETQSTAT XQM_CMD(5) /* get quota subsystem status */
+#define Q_XQUOTASYNC XQM_CMD(7) /* delalloc flush, updates dquots */
+
+#define XFS_QUOTA_PDQ_ACCT (1<<4) /* project quota accounting */
+#define XFS_QUOTA_PDQ_ENFD (1<<5) /* project quota limits enforcement */
+
+#define FS_QSTAT_VERSION 1 /* fs_quota_stat.qs_version */
+
+/*
+ * Some basic information about 'quota files'.
+ */
+typedef struct fs_qfilestat {
+ uint64_t qfs_ino; /* inode number */
+ uint64_t qfs_nblks; /* number of BBs 512-byte-blks */
+ uint32_t qfs_nextents; /* number of extents */
+} fs_qfilestat_t;
+
+typedef struct fs_quota_stat {
+ char qs_version; /* version number for future changes */
+ uint16_t qs_flags; /* XFS_QUOTA_{U,P,G}DQ_{ACCT,ENFD} */
+ char qs_pad; /* unused */
+ fs_qfilestat_t qs_uquota; /* user quota storage information */
+ fs_qfilestat_t qs_gquota; /* group quota storage information */
+ uint32_t qs_incoredqs; /* number of dquots incore */
+ int32_t qs_btimelimit; /* limit for blks timer */
+ int32_t qs_itimelimit; /* limit for inodes timer */
+ int32_t qs_rtbtimelimit;/* limit for rt blks timer */
+ uint16_t qs_bwarnlimit; /* limit for num warnings */
+ uint16_t qs_iwarnlimit; /* limit for num warnings */
+} fs_quota_stat_t;
+
+#define FS_DQUOT_VERSION 1 /* fs_disk_quota.d_version */
+typedef struct fs_disk_quota {
+ char d_version; /* version of this structure */
+ char d_flags; /* XFS_{USER,PROJ,GROUP}_QUOTA */
+ uint16_t d_fieldmask; /* field specifier */
+ uint32_t d_id; /* user, project, or group ID */
+ uint64_t d_blk_hardlimit;/* absolute limit on disk blks */
+ uint64_t d_blk_softlimit;/* preferred limit on disk blks */
+ uint64_t d_ino_hardlimit;/* maximum # allocated inodes */
+ uint64_t d_ino_softlimit;/* preferred inode limit */
+ uint64_t d_bcount; /* # disk blocks owned by the user */
+ uint64_t d_icount; /* # inodes owned by the user */
+ int32_t d_itimer; /* zero if within inode limits */
+ int32_t d_btimer; /* similar to above; for disk blocks */
+ uint16_t d_iwarns; /* # warnings issued wrt num inodes */
+ uint16_t d_bwarns; /* # warnings issued wrt disk blocks */
+ int32_t d_padding2; /* padding2 - for future use */
+ uint64_t d_rtb_hardlimit;/* absolute limit on realtime blks */
+ uint64_t d_rtb_softlimit;/* preferred limit on RT disk blks */
+ uint64_t d_rtbcount; /* # realtime blocks owned */
+ int32_t d_rtbtimer; /* similar to above; for RT disk blks */
+ uint16_t d_rtbwarns; /* # warnings issued wrt RT disk blks */
+ int16_t d_padding3; /* padding3 - for future use */
+ char d_padding4[8]; /* yet more padding */
+} fs_disk_quota_t;
+
+typedef struct project {
+ int32_t space_time_left; /* seconds */
+ int32_t files_time_left; /* seconds */
+ uint64_t space_hard; /* blocks */
+ uint64_t space_soft; /* blocks */
+ uint64_t space_used; /* blocks */
+ uint64_t files_hard;
+ uint64_t files_soft;
+ uint64_t files_used;
+} project_t;
+
+/* Values for flags in filesys_t */
+#define FSF_FETCHED (1U << 0)
+#define FSF_QUOT_PROJ_ACC (1U << 1)
+#define FSF_QUOT_PROJ_ENF (1U << 2)
+
+typedef struct filesys {
+ int id;
+ unsigned int flags;
+ char *device;
+ char *path;
+ char *options;
+ struct statfs stats;
+} filesys_t;
+
+extern int refresh_filesys(pmInDom, pmInDom);
+extern char *scan_filesys_options(const char *, const char *);
diff --git a/src/pmdas/linux_xfs/help b/src/pmdas/linux_xfs/help
new file mode 100644
index 0000000..852165c
--- /dev/null
+++ b/src/pmdas/linux_xfs/help
@@ -0,0 +1,469 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004-2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Linux XFS PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ xfs.allocs.alloc_extent XFS extents allocated
+Number of file system extents allocated over all XFS filesystems
+@ xfs.allocs.alloc_block XFS blocks allocated
+Number of file system blocks allocated over all XFS filesystems
+@ xfs.allocs.free_extent XFS extents freed
+Number of file system extents freed over all XFS filesystems
+@ xfs.allocs.free_block XFS blocks freed
+Number of file system blocks freed over all XFS filesystems
+
+@ xfs.alloc_btree.lookup lookups in XFS alloc btrees
+Number of lookup operations in XFS filesystem allocation btrees
+@ xfs.alloc_btree.compare compares in XFS alloc btrees
+Number of compares in XFS filesystem allocation btree lookups
+@ xfs.alloc_btree.insrec insertions in XFS alloc btrees
+Number of extent records inserted into XFS filesystem allocation btrees
+@ xfs.alloc_btree.delrec deletions in XFS alloc btrees
+Number of extent records deleted from XFS filesystem allocation btrees
+
+@ xfs.block_map.read_ops block map read ops in XFS
+Number of block map for read operations performed on XFS files
+@ xfs.block_map.write_ops block map write ops in XFS
+Number of block map for write operations performed on XFS files
+@ xfs.block_map.unmap block unmap ops in XFS
+Number of block unmap (delete) operations performed on XFS files
+@ xfs.block_map.add_exlist extent list add ops in XFS
+Number of extent list insertion operations for XFS files
+@ xfs.block_map.del_exlist extent list delete ops in XFS
+Number of extent list deletion operations for XFS files
+@ xfs.block_map.look_exlist extent list lookup ops in XFS
+Number of extent list lookup operations for XFS files
+@ xfs.block_map.cmp_exlist extent list compare ops in XFS
+Number of extent list comparisons in XFS extent list lookups
+
+@ xfs.bmap_btree.lookup block map btree lookup ops in XFS
+Number of block map btree lookup operations on XFS files
+@ xfs.bmap_btree.compare block map btree compare ops in XFS
+Number of block map btree compare operations in XFS block map lookups
+@ xfs.bmap_btree.insrec block map btree insert ops in XFS
+Number of block map btree records inserted for XFS files
+@ xfs.bmap_btree.delrec block map btree delete ops in XFS
+Number of block map btree records deleted for XFS files
+
+@ xfs.dir_ops.lookup number of file name directory lookups
+This is a count of the number of file name directory lookups in XFS
+filesystems. It counts only those lookups which miss in the operating
+system's directory name lookup cache and must search the real directory
+structure for the name in question. The count is incremented once for
+each level of a pathname search that results in a directory lookup.
+
+@ xfs.dir_ops.create number of directory entry creation operations
+This is the number of times a new directory entry was created in XFS
+filesystems. Each time that a new file, directory, link, symbolic link,
+or special file is created in the directory hierarchy the count is
+incremented.
+
+@ xfs.dir_ops.remove number of directory entry remove operations
+This is the number of times an existing directory entry was removed in
+XFS filesystems. Each time that a file, directory, link, symbolic link,
+or special file is removed from the directory hierarchy the count is
+incremented.
+
+@ xfs.dir_ops.getdents number of times the getdents operation is performed
+This is the number of times the XFS directory getdents operation was
+performed. The getdents operation is used by programs to read the
+contents of directories in a file system independent fashion. This
+count corresponds exactly to the number of times the getdents(2) system
+call was successfully used on an XFS directory.
+
+@ xfs.transactions.sync number of synchronous meta-data transactions performed
+This is the number of meta-data transactions which waited to be
+committed to the on-disk log before allowing the process performing the
+transaction to continue. These transactions are slower and more
+expensive than asynchronous transactions, because they force the in
+memory log buffers to be forced to disk more often and they wait for
+the completion of the log buffer writes.
+
+@ xfs.transactions.async number of asynchronous meta-data transactions performed
+This is the number of meta-data transactions which did not wait to be
+committed to the on-disk log before allowing the process performing the
+transaction to continue. These transactions are faster and more
+efficient than synchronous transactions, because they commit their data
+to the in memory log buffers without forcing those buffers to be
+written to disk. This allows multiple asynchronous transactions to be
+committed to disk in a single log buffer write. Most transactions used
+in XFS file systems are asynchronous.
+
+@ xfs.transactions.empty number of meta-data transactions which committed without changing anything
+This is the number of meta-data transactions which did not actually
+change anything. These are transactions which were started for some
+purpose, but in the end it turned out that no change was necessary.
+
+@ xfs.inode_ops.ig_attempts number of in memory inode lookup operations
+This is the number of times the operating system looked for an XFS
+inode in the inode cache. Whether the inode was found in the cache or
+needed to be read in from the disk is not indicated here, but this can
+be computed from the ig_found and ig_missed counts.
+
+@ xfs.inode_ops.ig_found number of successful in memory inode lookup operations
+This is the number of times the operating system looked for an XFS
+inode in the inode cache and found it. The closer this count is to the
+ig_attempts count the better the inode cache is performing.
+
+@ xfs.inode_ops.ig_frecycle number of just missed in memory inode lookup operations
+This is the number of times the operating system looked for an XFS
+inode in the inode cache and saw that it was there but was unable to
+use the in memory inode because it was being recycled by another
+process.
+
+@ xfs.inode_ops.ig_missed number of failed in memory inode lookup operations
+This is the number of times the operating system looked for an XFS
+inode in the inode cache and the inode was not there. The further this
+count is from the ig_attempts count the better.
+
+@ xfs.inode_ops.ig_dup number of inode cache insertions that fail because the inode is there
+This is the number of times the operating system looked for an XFS
+inode in the inode cache and found that it was not there but upon
+attempting to add the inode to the cache found that another process had
+already inserted it.
+
+@ xfs.inode_ops.ig_reclaims number of in memory inode recycle operations
+This is the number of times the operating system recycled an XFS inode
+from the inode cache in order to use the memory for that inode for
+another purpose. Inodes are recycled in order to keep the inode cache
+from growing without bound. If the reclaim rate is high it may be
+beneficial to raise the vnode_free_ratio kernel tunable variable to
+increase the size of inode cache.
+
+@ xfs.inode_ops.ig_attrchg number of inode attribute change operations
+This is the number of times the operating system explicitly changed the
+attributes of an XFS inode. For example, this could be to change the
+inode's owner, the inode's size, or the inode's timestamps.
+
+@ xfs.log.writes number of buffer writes going to the disk from the log
+This variable counts the number of log buffer writes going to the
+physical log partitions of all XFS filesystems. Log data traffic is
+proportional to the level of meta-data updating. Log buffer writes get
+generated when they fill up or external syncs occur.
+
+@ xfs.log.blocks write throughput to the physical XFS log
+This variable counts the number of Kbytes of information being written
+to the physical log partitions of all XFS filesystems. Log data traffic
+is proportional to the level of meta-data updating. The rate with which
+log data gets written depends on the size of internal log buffers and
+disk write speed. Therefore, filesystems with very high meta-data
+updating may need to stripe the log partition or put the log partition
+on a separate drive.
+
+@ xfs.log.write_ratio ratio of count of XFS log blocks written to log writes
+The ratio of log blocks written to log writes. If block count isn't a
+"reasonable" multiple of writes, then many small log writes are being
+performed - this is suboptimal. Perfection is 64. Fine-grain control
+can be obtained when this metric is used in conjuntion with pmstore(1)
+and the xfs.control.reset metric.
+
+@ xfs.log.noiclogs count of failures for immediate get of buffered/internal
+This variable keeps track of times when a logged transaction can not
+get any log buffer space. When this occurs, all of the internal log
+buffers are busy flushing their data to the physical on-disk log.
+
+@ xfs.log.force value from xs_log_force field of struct xfsstats
+The number of times the in-core log is forced to disk. It is
+equivalent to the number of successful calls to the function
+xfs_log_force().
+
+@ xfs.log.force_sleep value from xs_log_force_sleep field of struct xfsstats
+This metric is exported from the xs_log_force_sleep field of struct xfsstats
+
+@ xfs.log_tail.try_logspace value from xs_try_logspace field of struct xfsstats
+This metric is exported from the xs_try_logspace field of struct xfsstats
+
+@ xfs.log_tail.sleep_logspace value from xs_sleep_logspace field of struct xfsstats
+This metric is exported from the xs_sleep_logspace field of struct xfsstats
+
+@ xfs.log_tail.push_ail.pushes number of times the AIL tail is moved forward
+The number of times the tail of the AIL is moved forward. It is
+equivalent to the number of successful calls to the function
+xfs_trans_push_ail().
+
+@ xfs.log_tail.push_ail.success value from xs_push_ail_success field of struct xfsstats
+@ xfs.log_tail.push_ail.pushbuf value from xs_push_ail_pushbuf field of struct xfsstats
+@ xfs.log_tail.push_ail.pinned value from xs_push_ail_pinned field of struct xfsstats
+@ xfs.log_tail.push_ail.locked value from xs_push_ail_locked field of struct xfsstats
+@ xfs.log_tail.push_ail.flushing value from xs_push_ail_flushing field of struct xfsstats
+@ xfs.log_tail.push_ail.restarts value from xs_push_ail_restarts field of struct xfsstats
+@ xfs.log_tail.push_ail.flush value from xs_push_ail_flush field of struct xfsstats
+
+@ xfs.xstrat.bytes number of bytes of data processed by the XFS daemons
+This is the number of bytes of file data flushed out by the XFS
+flushing daemons.
+
+@ xfs.xstrat.quick number of buffers processed by the XFS daemons written to contiguous space on disk
+This is the number of buffers flushed out by the XFS flushing daemons
+which are written to contiguous space on disk. The buffers handled by
+the XFS daemons are delayed allocation buffers, so this count gives an
+indication of the success of the XFS daemons in allocating contiguous
+disk space for the data being flushed to disk.
+
+@ xfs.xstrat.split number of buffers processed by the XFS daemons written to non-contiguous space on disk
+This is the number of buffers flushed out by the XFS flushing daemons
+which are written to non-contiguous space on disk. The buffers handled
+by the XFS daemons are delayed allocation buffers, so this count gives
+an indication of the failure of the XFS daemons in allocating
+contiguous disk space for the data being flushed to disk. Large values
+in this counter indicate that the file system has become fragmented.
+
+@ xfs.write number of XFS file system write operations
+This is the number of write(2) system calls made to files in
+XFS file systems.
+
+@ xfs.write_bytes number of bytes written in XFS file system write operations
+This is the number of bytes written via write(2) system calls to files
+in XFS file systems. It can be used in conjunction with the write_calls
+count to calculate the average size of the write operations to files in
+XFS file systems.
+
+@ xfs.read number of XFS file system read operations
+This is the number of read(2) system calls made to files in XFS file
+systems.
+
+@ xfs.read_bytes number of bytes read in XFS file system read operations
+This is the number of bytes read via read(2) system calls to files in
+XFS file systems. It can be used in conjunction with the read_calls
+count to calculate the average size of the read operations to files in
+XFS file systems.
+
+@ xfs.attr.get number of "get" operations on XFS extended file attributes
+The number of "get" operations performed on extended file attributes
+within XFS filesystems. The "get" operation retrieves the value of an
+extended attribute.
+
+@ xfs.attr.set number of "set" operations on XFS extended file attributes
+The number of "set" operations performed on extended file attributes
+within XFS filesystems. The "set" operation creates and sets the value
+of an extended attribute.
+
+@ xfs.attr.remove number of "remove" operations on XFS extended file attributes
+The number of "remove" operations performed on extended file attributes
+within XFS filesystems. The "remove" operation deletes an extended
+attribute.
+
+@ xfs.attr.list number of "list" operations on XFS extended file attributes
+The number of "list" operations performed on extended file attributes
+within XFS filesystems. The "list" operation retrieves the set of
+extended attributes associated with a file.
+
+@ xfs.quota.reclaims value from xs_qm_dqreclaims field of struct xfsstats
+@ xfs.quota.reclaim_misses value from xs_qm_dqreclaim_misses field of struct xfsstats
+@ xfs.quota.dquot_dups value from xs_qm_dquot_dups field of struct xfsstats
+@ xfs.quota.cachemisses value from xs_qm_dqcachemisses field of struct xfsstats
+@ xfs.quota.cachehits value from xs_qm_dqcachehits field of struct xfsstats
+@ xfs.quota.wants value from xs_qm_dqwants field of struct xfsstats
+@ xfs.quota.shake_reclaims value from xs_qm_dqshake_reclaims field of struct xfsstats
+@ xfs.quota.inact_reclaims value from xs_qm_dqinact_reclaims field of struct xfsstats
+
+@ xfs.iflush_count the number of calls to xfs_iflush
+This is the number of calls to xfs_iflush which gets called when an
+inode is being flushed (such as by bdflush or tail pushing).
+xfs_iflush searches for other inodes in the same cluster which are
+dirty and flushable.
+
+@ xfs.icluster_flushcnt value from xs_icluster_flushcnt field of struct xfsstats
+
+@ xfs.icluster_flushinode number of flushes of only one inode in cluster
+This is the number of times that the inode clustering was not able to
+flush anything but the one inode it was called with.
+
+@ xfs.buffer.get number of request buffer calls
+@ xfs.buffer.create number of buffers created
+@ xfs.buffer.get_locked number of requests for a locked buffer which succeeded
+@ xfs.buffer.get_locked_waited number of requests for a locked buffer which waited
+@ xfs.buffer.miss_locked number of requests for a locked buffer which failed due to no buffer
+@ xfs.buffer.busy_locked number of non-blocking requests for a locked buffer which failed
+@ xfs.buffer.page_retries number of retry attempts when allocating a page for insertion in a buffer
+@ xfs.buffer.page_found number of hits in the page cache when looking for a page
+@ xfs.buffer.get_read number of buffer get calls requiring immediate device reads
+@ xfs.vnodes.active number of vnodes not on free lists
+@ xfs.vnodes.alloc number of times vn_alloc called
+@ xfs.vnodes.get number of times vn_get called
+@ xfs.vnodes.hold number of times vn_hold called
+@ xfs.vnodes.rele number of times vn_rele called
+@ xfs.vnodes.reclaim number of times vn_reclaim called
+@ xfs.vnodes.remove number of times vn_remove called
+@ xfs.vnodes.free number of times vn_free called
+@ xfs.control.reset reset the values of all XFS metrics to zero
+
+@ quota.state.project.accounting 1 indicates quota accounting enabled, else 0
+@ quota.state.project.enforcement 1 indicates quotas enforced, else 0
+@ quota.project.space.hard hard limit for this project and filesys in Kbytes
+@ quota.project.space.soft soft limit for this project and filesys in Kbytes
+@ quota.project.space.used space used for this project and filesys in Kbytes
+@ quota.project.space.time_left when soft limit is exceeded, seconds until it is enacted
+@ quota.project.files.hard file count hard limit for this project and filesys
+@ quota.project.files.soft file count soft limit for this project and filesys
+@ quota.project.files.used file count for this project and filesys
+@ quota.project.files.time_left when soft limit is exceeded, seconds until it is enacted
+
+@ xfs.btree.alloc_blocks.lookup
+Number of free-space-by-block-number btree record lookups
+@ xfs.btree.alloc_blocks.compare
+Number of free-space-by-block-number btree record compares
+@ xfs.btree.alloc_blocks.insrec
+Number of free-space-by-block-number btree insert record operations executed
+@ xfs.btree.alloc_blocks.delrec
+Number of free-space-by-block-number btree delete record operations executed
+@ xfs.btree.alloc_blocks.newroot
+Number of times a new level is added to a free-space-by-block-number btree
+@ xfs.btree.alloc_blocks.killroot
+Number of times a level is removed from a free-space-by-block-number btree
+@ xfs.btree.alloc_blocks.increment
+Number of times a cursor has been moved forward one free-space-by-block-number
+btree record
+@ xfs.btree.alloc_blocks.decrement
+Number of times a cursor has been moved backward one free-space-by-block-number
+btree record
+@ xfs.btree.alloc_blocks.lshift
+Left shift block operations to make space for a new free-space-by-block-number
+btree record
+@ xfs.btree.alloc_blocks.rshift
+Right shift block operations to make space for a new free-space-by-block-number
+btree record
+@ xfs.btree.alloc_blocks.split
+Split block operations to make space for a new free-space-by-block-number
+btree record
+@ xfs.btree.alloc_blocks.join
+Merge block operations when deleting free-space-by-block-number btree records
+@ xfs.btree.alloc_blocks.alloc
+Btree block allocations during free-space-by-block-number btree operations
+@ xfs.btree.alloc_blocks.free
+Btree blocks freed during free-space-by-block-number btree operations
+@ xfs.btree.alloc_blocks.moves
+Records moved inside blocks during free-space-by-block-number btree operations
+
+@ xfs.btree.alloc_contig.lookup
+Number of free-space-by-size btree record lookups
+@ xfs.btree.alloc_contig.compare
+Number of free-space-by-size btree btree record compares
+@ xfs.btree.alloc_contig.insrec
+Number of free-space-by-size btree insert record operations executed
+@ xfs.btree.alloc_contig.delrec
+Number of free-space-by-size btree delete record operations executed
+@ xfs.btree.alloc_contig.newroot
+Number of times a new level is added to a free-space-by-size btree tree
+@ xfs.btree.alloc_contig.killroot
+Number of times a level is removed from a free-space-by-size btree tree
+@ xfs.btree.alloc_contig.increment
+Number of times a free-space-by-size btree cursor has been moved forward
+one record
+@ xfs.btree.alloc_contig.decrement
+Number of times a free-space-by-size btree cursor has been moved backward
+one record
+@ xfs.btree.alloc_contig.lshift
+Left shift block operations to make space for a new free-space-by-size
+btree record
+@ xfs.btree.alloc_contig.rshift
+Right shift block operations to make space for a new free-space-by-size
+btree record
+@ xfs.btree.alloc_contig.split
+Split block operations to make space for a new free-space-by-size btree
+record
+@ xfs.btree.alloc_contig.join
+Merge block operations when deleting free-space-by-size btree records
+@ xfs.btree.alloc_contig.alloc
+Btree block allocations during free-space-by-size btree operations
+@ xfs.btree.alloc_contig.free
+Btree blocks freed during free-space-by-size btree operations
+@ xfs.btree.alloc_contig.moves
+Records moved inside blocks during free-space-by-size btree operations
+
+@ xfs.btree.block_map.lookup
+Number of inode-block-map/extent btree record lookups
+@ xfs.btree.block_map.compare
+Number of inode-block-map/extent btree record compares
+@ xfs.btree.block_map.insrec
+Number of inode-block-map/extent btree insert record operations executed
+@ xfs.btree.block_map.delrec
+Number of inode-block-map/extent btree delete record operations executed
+@ xfs.btree.block_map.newroot
+Number of times a new level is added to an inode-block-map/extent btree
+@ xfs.btree.block_map.killroot
+Number of times a level is removed from an inode-block-map/extent btree
+@ xfs.btree.block_map.increment
+Number of times an inode-block-map/extent btree cursor has been moved
+forward one record
+@ xfs.btree.block_map.decrement
+Number of times an inode-block-map/extent btree cursor has been moved
+backward one record
+@ xfs.btree.block_map.lshift
+Left shift block operations to make space for a new inode-block-map/extent
+btree record
+@ xfs.btree.block_map.rshift
+Right shift block operations to make space for a new inode-block-map/extent
+btree record
+@ xfs.btree.block_map.split
+Split block operations to make space for a new inode-block-map/extent
+btree record
+@ xfs.btree.block_map.join
+Merge block operations when deleting inode-block-map/extent btree records
+@ xfs.btree.block_map.alloc
+Btree block allocations during inode-block-map/extent btree operations
+@ xfs.btree.block_map.free
+Btree blocks freed during inode-block-map/extent btree operations
+@ xfs.btree.block_map.moves
+Records moved inside blocks during inode-block-map/extent btree operations
+
+@ xfs.btree.inode.lookup
+Number of inode-allocation btree record lookups
+@ xfs.btree.inode.compare
+Number of inode-allocation btree record compares
+@ xfs.btree.inode.insrec
+Number of inode-allocation btree insert record operations executed
+@ xfs.btree.inode.delrec
+Number of inode-allocation btree delete record operations executed
+@ xfs.btree.inode.newroot
+Number of times a new level is added to an inode-allocation btree
+@ xfs.btree.inode.killroot
+Number of times a level is removed from an inode-allocation btree
+@ xfs.btree.inode.increment
+Number of times a cursor has been moved forward one inode-allocation
+btree record
+@ xfs.btree.inode.decrement
+Number of times a cursor has been moved backward one inode-allocation
+btree record
+@ xfs.btree.inode.lshift
+Left shift block operations to make space for a new inode-allocation
+btree record
+@ xfs.btree.inode.rshift
+Right shift block operations to make space for a new inode-allocation
+btree record
+@ xfs.btree.inode.split
+Split block operations to make space for a new inode-allocation btree record
+@ xfs.btree.inode.join
+Merge block operations when deleting inode-allocation btree records
+@ xfs.btree.inode.alloc
+Btree block allocations during inode-allocation btree operations
+@ xfs.btree.inode.free
+Btree blocks freed during inode-allocation btree operations
+@ xfs.btree.inode.moves
+Records moved inside blocks during inode-allocation btree operations
+
diff --git a/src/pmdas/linux_xfs/indom.h b/src/pmdas/linux_xfs/indom.h
new file mode 100644
index 0000000..a2f51d1
--- /dev/null
+++ b/src/pmdas/linux_xfs/indom.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc. All Rights Reserved.
+ * Copyright (c) 2005,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _INDOM_H
+#define _INDOM_H
+
+/*
+ * indom serial numbers ... to manage the indom migration after the
+ * linux -> linux + xfs PMDAs split, these need to match the enum
+ * assigned values for *_INDOM from the linux PMDA. Consequently,
+ * the xfs indom table is sparse.
+ */
+#define FILESYS_INDOM 5 /* mounted filesystems */
+#define QUOTA_PRJ_INDOM 16 /* project quota */
+
+#define MIN_INDOM 5 /* first indom number we use here */
+#define NUM_INDOMS 17 /* one more than highest indom number used */
+
+#endif /* _INDOM_H */
diff --git a/src/pmdas/linux_xfs/linux_xfs_migrate.conf b/src/pmdas/linux_xfs/linux_xfs_migrate.conf
new file mode 100644
index 0000000..59cbb64
--- /dev/null
+++ b/src/pmdas/linux_xfs/linux_xfs_migrate.conf
@@ -0,0 +1,16 @@
+#
+# Copyright 2013 Red Hat.
+#
+# pmlogrewrite configuration for migrating archives containing XFS metrics
+# that were captured prior to the XFS PMDA split-off from the Linux PMDA.
+# Basically, the PMID domain changed from 60 (linux) to 11 (xfs) but all
+# cluster and item numbers remain unchanged.
+#
+
+#
+# Migrate the domain field of the metric and indom identifiers
+#
+indom 60.16 { indom -> duplicate 11.16 } # need 11.16 and 60.16
+metric 60.16.* { pmid -> 11.*.* } # CLUSTER_XFS
+metric 60.17.* { pmid -> 11.*.* } # CLUSTER_XFSBUF
+metric 60.30.* { pmid -> 11.*.* indom -> 11.16 } # CLUSTER_QUOTA
diff --git a/src/pmdas/linux_xfs/pmda.c b/src/pmdas/linux_xfs/pmda.c
new file mode 100644
index 0000000..a1b937c
--- /dev/null
+++ b/src/pmdas/linux_xfs/pmda.c
@@ -0,0 +1,979 @@
+/*
+ * XFS PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "indom.h"
+#include "domain.h"
+#include "clusters.h"
+#include "filesys.h"
+#include "proc_fs_xfs.h"
+
+static int _isDSO = 1; /* for local contexts */
+static proc_fs_xfs_t proc_fs_xfs;
+static char * xfs_statspath = "";
+
+/*
+ * The XFS instance domain table is direct lookup and sparse.
+ * It is initialized in xfs_init(), see below.
+ */
+static pmdaIndom xfs_indomtab[NUM_INDOMS];
+#define INDOM(x) (xfs_indomtab[x].it_indom)
+
+static pmdaMetric xfs_metrictab[] = {
+
+/* xfs.allocs.alloc_extent */
+ { &proc_fs_xfs.xs_allocx,
+ { PMDA_PMID(CLUSTER_XFS,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.allocs.alloc_block */
+ { &proc_fs_xfs.xs_allocb,
+ { PMDA_PMID(CLUSTER_XFS,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.allocs.free_extent*/
+ { &proc_fs_xfs.xs_freex,
+ { PMDA_PMID(CLUSTER_XFS,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.allocs.free_block */
+ { &proc_fs_xfs.xs_freeb,
+ { PMDA_PMID(CLUSTER_XFS,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.alloc_btree.lookup */
+ { &proc_fs_xfs.xs_abt_lookup,
+ { PMDA_PMID(CLUSTER_XFS,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.alloc_btree.compare */
+ { &proc_fs_xfs.xs_abt_compare,
+ { PMDA_PMID(CLUSTER_XFS,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.alloc_btree.insrec */
+ { &proc_fs_xfs.xs_abt_insrec,
+ { PMDA_PMID(CLUSTER_XFS,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.alloc_btree.delrec */
+ { &proc_fs_xfs.xs_abt_delrec,
+ { PMDA_PMID(CLUSTER_XFS,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.block_map.read_ops */
+ { &proc_fs_xfs.xs_blk_mapr,
+ { PMDA_PMID(CLUSTER_XFS,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.write_ops */
+ { &proc_fs_xfs.xs_blk_mapw,
+ { PMDA_PMID(CLUSTER_XFS,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.unmap */
+ { &proc_fs_xfs.xs_blk_unmap,
+ { PMDA_PMID(CLUSTER_XFS,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.add_exlist */
+ { &proc_fs_xfs.xs_add_exlist,
+ { PMDA_PMID(CLUSTER_XFS,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.del_exlist */
+ { &proc_fs_xfs.xs_del_exlist,
+ { PMDA_PMID(CLUSTER_XFS,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.look_exlist */
+ { &proc_fs_xfs.xs_look_exlist,
+ { PMDA_PMID(CLUSTER_XFS,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.block_map.cmp_exlist */
+ { &proc_fs_xfs.xs_cmp_exlist,
+ { PMDA_PMID(CLUSTER_XFS,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.bmap_btree.lookup */
+ { &proc_fs_xfs.xs_bmbt_lookup,
+ { PMDA_PMID(CLUSTER_XFS,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.bmap_btree.compare */
+ { &proc_fs_xfs.xs_bmbt_compare,
+ { PMDA_PMID(CLUSTER_XFS,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.bmap_btree.insrec */
+ { &proc_fs_xfs.xs_bmbt_insrec,
+ { PMDA_PMID(CLUSTER_XFS,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.bmap_btree.delrec */
+ { &proc_fs_xfs.xs_bmbt_delrec,
+ { PMDA_PMID(CLUSTER_XFS,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.dir_ops.lookup */
+ { &proc_fs_xfs.xs_dir_lookup,
+ { PMDA_PMID(CLUSTER_XFS,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.dir_ops.create */
+ { &proc_fs_xfs.xs_dir_create,
+ { PMDA_PMID(CLUSTER_XFS,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.dir_ops.remove */
+ { &proc_fs_xfs.xs_dir_remove,
+ { PMDA_PMID(CLUSTER_XFS,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.dir_ops.getdents */
+ { &proc_fs_xfs.xs_dir_getdents,
+ { PMDA_PMID(CLUSTER_XFS,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.transactions.sync */
+ { &proc_fs_xfs.xs_trans_sync,
+ { PMDA_PMID(CLUSTER_XFS,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.transactions.async */
+ { &proc_fs_xfs.xs_trans_async,
+ { PMDA_PMID(CLUSTER_XFS,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.transactions.empty */
+ { &proc_fs_xfs.xs_trans_empty,
+ { PMDA_PMID(CLUSTER_XFS,25), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.inode_ops.ig_attempts */
+ { &proc_fs_xfs.xs_ig_attempts,
+ { PMDA_PMID(CLUSTER_XFS,26), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_found */
+ { &proc_fs_xfs.xs_ig_found,
+ { PMDA_PMID(CLUSTER_XFS,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_frecycle */
+ { &proc_fs_xfs.xs_ig_frecycle,
+ { PMDA_PMID(CLUSTER_XFS,28), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_missed */
+ { &proc_fs_xfs.xs_ig_missed,
+ { PMDA_PMID(CLUSTER_XFS,29), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_dup */
+ { &proc_fs_xfs.xs_ig_dup,
+ { PMDA_PMID(CLUSTER_XFS,30), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_reclaims */
+ { &proc_fs_xfs.xs_ig_reclaims,
+ { PMDA_PMID(CLUSTER_XFS,31), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.inode_ops.ig_attrchg */
+ { &proc_fs_xfs.xs_ig_attrchg,
+ { PMDA_PMID(CLUSTER_XFS,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.log.writes */
+ { &proc_fs_xfs.xs_log_writes,
+ { PMDA_PMID(CLUSTER_XFS,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log.blocks */
+ { &proc_fs_xfs.xs_log_blocks,
+ { PMDA_PMID(CLUSTER_XFS,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* xfs.log.noiclogs */
+ { &proc_fs_xfs.xs_log_noiclogs,
+ { PMDA_PMID(CLUSTER_XFS,35), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log.force */
+ { &proc_fs_xfs.xs_log_force,
+ { PMDA_PMID(CLUSTER_XFS,36), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log.force_sleep */
+ { &proc_fs_xfs.xs_log_force_sleep,
+ { PMDA_PMID(CLUSTER_XFS,37), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.log_tail.try_logspace */
+ { &proc_fs_xfs.xs_try_logspace,
+ { PMDA_PMID(CLUSTER_XFS,38), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.sleep_logspace */
+ { &proc_fs_xfs.xs_sleep_logspace,
+ { PMDA_PMID(CLUSTER_XFS,39), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.pushes */
+ { &proc_fs_xfs.xs_push_ail,
+ { PMDA_PMID(CLUSTER_XFS,40), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.success */
+ { &proc_fs_xfs.xs_push_ail_success,
+ { PMDA_PMID(CLUSTER_XFS,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.pushbuf */
+ { &proc_fs_xfs.xs_push_ail_pushbuf,
+ { PMDA_PMID(CLUSTER_XFS,42), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.pinned */
+ { &proc_fs_xfs.xs_push_ail_pinned,
+ { PMDA_PMID(CLUSTER_XFS,43), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.locked */
+ { &proc_fs_xfs.xs_push_ail_locked,
+ { PMDA_PMID(CLUSTER_XFS,44), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.flushing */
+ { &proc_fs_xfs.xs_push_ail_flushing,
+ { PMDA_PMID(CLUSTER_XFS,45), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.restarts */
+ { &proc_fs_xfs.xs_push_ail_restarts,
+ { PMDA_PMID(CLUSTER_XFS,46), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.log_tail.push_ail.flush */
+ { &proc_fs_xfs.xs_push_ail_flush,
+ { PMDA_PMID(CLUSTER_XFS,47), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.xstrat.bytes */
+ { &proc_fs_xfs.xpc.xs_xstrat_bytes,
+ { PMDA_PMID(CLUSTER_XFS,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* xfs.xstrat.quick */
+ { &proc_fs_xfs.xs_xstrat_quick,
+ { PMDA_PMID(CLUSTER_XFS,49), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.xstrat.split */
+ { &proc_fs_xfs.xs_xstrat_split,
+ { PMDA_PMID(CLUSTER_XFS,50), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.write */
+ { &proc_fs_xfs.xs_write_calls,
+ { PMDA_PMID(CLUSTER_XFS,51), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.write_bytes */
+ { &proc_fs_xfs.xpc.xs_write_bytes,
+ { PMDA_PMID(CLUSTER_XFS,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* xfs.read */
+ { &proc_fs_xfs.xs_read_calls,
+ { PMDA_PMID(CLUSTER_XFS,53), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.read_bytes */
+ { &proc_fs_xfs.xpc.xs_read_bytes,
+ { PMDA_PMID(CLUSTER_XFS,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+
+/* xfs.attr.get */
+ { &proc_fs_xfs.xs_attr_get,
+ { PMDA_PMID(CLUSTER_XFS,55), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.attr.set */
+ { &proc_fs_xfs.xs_attr_set,
+ { PMDA_PMID(CLUSTER_XFS,56), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.attr.remove */
+ { &proc_fs_xfs.xs_attr_remove,
+ { PMDA_PMID(CLUSTER_XFS,57), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.attr.list */
+ { &proc_fs_xfs.xs_attr_list,
+ { PMDA_PMID(CLUSTER_XFS,58), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.quota.reclaims */
+ { &proc_fs_xfs.xs_qm_dqreclaims,
+ { PMDA_PMID(CLUSTER_XFS,59), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.reclaim_misses */
+ { &proc_fs_xfs.xs_qm_dqreclaim_misses,
+ { PMDA_PMID(CLUSTER_XFS,60), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.dquot_dups */
+ { &proc_fs_xfs.xs_qm_dquot_dups,
+ { PMDA_PMID(CLUSTER_XFS,61), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.cachemisses */
+ { &proc_fs_xfs.xs_qm_dqcachemisses,
+ { PMDA_PMID(CLUSTER_XFS,62), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.cachehits */
+ { &proc_fs_xfs.xs_qm_dqcachehits,
+ { PMDA_PMID(CLUSTER_XFS,63), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.wants */
+ { &proc_fs_xfs.xs_qm_dqwants,
+ { PMDA_PMID(CLUSTER_XFS,64), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.shake_reclaims */
+ { &proc_fs_xfs.xs_qm_dqshake_reclaims,
+ { PMDA_PMID(CLUSTER_XFS,65), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.quota.inact_reclaims */
+ { &proc_fs_xfs.xs_qm_dqinact_reclaims,
+ { PMDA_PMID(CLUSTER_XFS,66), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.iflush_count */
+ { &proc_fs_xfs.xs_iflush_count,
+ { PMDA_PMID(CLUSTER_XFS,67), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.icluster_flushcnt */
+ { &proc_fs_xfs.xs_icluster_flushcnt,
+ { PMDA_PMID(CLUSTER_XFS,68), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.icluster_flushinode */
+ { &proc_fs_xfs.xs_icluster_flushinode,
+ { PMDA_PMID(CLUSTER_XFS,69), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.buffer.get */
+ { &proc_fs_xfs.xs_buf_get,
+ { PMDA_PMID(CLUSTER_XFSBUF,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.create */
+ { &proc_fs_xfs.xs_buf_create,
+ { PMDA_PMID(CLUSTER_XFSBUF,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.get_locked */
+ { &proc_fs_xfs.xs_buf_get_locked,
+ { PMDA_PMID(CLUSTER_XFSBUF,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.get_locked_waited */
+ { &proc_fs_xfs.xs_buf_get_locked_waited,
+ { PMDA_PMID(CLUSTER_XFSBUF,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.busy_locked */
+ { &proc_fs_xfs.xs_buf_busy_locked,
+ { PMDA_PMID(CLUSTER_XFSBUF,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.miss_locked */
+ { &proc_fs_xfs.xs_buf_miss_locked,
+ { PMDA_PMID(CLUSTER_XFSBUF,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.page_retries */
+ { &proc_fs_xfs.xs_buf_page_retries,
+ { PMDA_PMID(CLUSTER_XFSBUF,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.page_found */
+ { &proc_fs_xfs.xs_buf_page_found,
+ { PMDA_PMID(CLUSTER_XFSBUF,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.buffer.get_read */
+ { &proc_fs_xfs.xs_buf_get_read,
+ { PMDA_PMID(CLUSTER_XFSBUF,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.vnodes.active */
+ { &proc_fs_xfs.vnodes.vn_active,
+ { PMDA_PMID(CLUSTER_XFS,70), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* xfs.vnodes.alloc */
+ { &proc_fs_xfs.vnodes.vn_alloc,
+ { PMDA_PMID(CLUSTER_XFS,71), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.get */
+ { &proc_fs_xfs.vnodes.vn_get,
+ { PMDA_PMID(CLUSTER_XFS,72), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.hold */
+ { &proc_fs_xfs.vnodes.vn_hold,
+ { PMDA_PMID(CLUSTER_XFS,73), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.rele */
+ { &proc_fs_xfs.vnodes.vn_rele,
+ { PMDA_PMID(CLUSTER_XFS,74), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.reclaim */
+ { &proc_fs_xfs.vnodes.vn_reclaim,
+ { PMDA_PMID(CLUSTER_XFS,75), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.remove */
+ { &proc_fs_xfs.vnodes.vn_remove,
+ { PMDA_PMID(CLUSTER_XFS,76), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.vnodes.free */
+ { &proc_fs_xfs.vnodes.vn_free,
+ { PMDA_PMID(CLUSTER_XFS,77), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.log.write_ratio */
+ { &proc_fs_xfs.xs_log_write_ratio,
+ { PMDA_PMID(CLUSTER_XFS,78), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* xfs.control.reset */
+ { NULL,
+ { PMDA_PMID(CLUSTER_XFS,79), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+
+/* xfs.btree.alloc_blocks.lookup */
+ { &proc_fs_xfs.xs_abtb_2_lookup,
+ { PMDA_PMID(CLUSTER_XFS,80), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.compare */
+ { &proc_fs_xfs.xs_abtb_2_compare,
+ { PMDA_PMID(CLUSTER_XFS,81), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.insrec */
+ { &proc_fs_xfs.xs_abtb_2_insrec,
+ { PMDA_PMID(CLUSTER_XFS,82), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.delrec */
+ { &proc_fs_xfs.xs_abtb_2_delrec,
+ { PMDA_PMID(CLUSTER_XFS,83), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.newroot */
+ { &proc_fs_xfs.xs_abtb_2_newroot,
+ { PMDA_PMID(CLUSTER_XFS,84), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.killroot */
+ { &proc_fs_xfs.xs_abtb_2_killroot,
+ { PMDA_PMID(CLUSTER_XFS,85), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.increment */
+ { &proc_fs_xfs.xs_abtb_2_increment,
+ { PMDA_PMID(CLUSTER_XFS,86), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.decrement */
+ { &proc_fs_xfs.xs_abtb_2_decrement,
+ { PMDA_PMID(CLUSTER_XFS,87), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.lshift */
+ { &proc_fs_xfs.xs_abtb_2_lshift,
+ { PMDA_PMID(CLUSTER_XFS,88), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.rshift */
+ { &proc_fs_xfs.xs_abtb_2_rshift,
+ { PMDA_PMID(CLUSTER_XFS,89), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.split */
+ { &proc_fs_xfs.xs_abtb_2_split,
+ { PMDA_PMID(CLUSTER_XFS,90), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.join */
+ { &proc_fs_xfs.xs_abtb_2_join,
+ { PMDA_PMID(CLUSTER_XFS,91), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.alloc */
+ { &proc_fs_xfs.xs_abtb_2_alloc,
+ { PMDA_PMID(CLUSTER_XFS,92), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.free */
+ { &proc_fs_xfs.xs_abtb_2_free,
+ { PMDA_PMID(CLUSTER_XFS,93), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_blocks.moves */
+ { &proc_fs_xfs.xs_abtb_2_moves,
+ { PMDA_PMID(CLUSTER_XFS,94), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.btree.alloc_contig.lookup */
+ { &proc_fs_xfs.xs_abtc_2_lookup,
+ { PMDA_PMID(CLUSTER_XFS,95), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.compare */
+ { &proc_fs_xfs.xs_abtc_2_compare,
+ { PMDA_PMID(CLUSTER_XFS,96), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.insrec */
+ { &proc_fs_xfs.xs_abtc_2_insrec,
+ { PMDA_PMID(CLUSTER_XFS,97), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.delrec */
+ { &proc_fs_xfs.xs_abtc_2_delrec,
+ { PMDA_PMID(CLUSTER_XFS,98), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.newroot */
+ { &proc_fs_xfs.xs_abtc_2_newroot,
+ { PMDA_PMID(CLUSTER_XFS,99), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.killroot */
+ { &proc_fs_xfs.xs_abtc_2_killroot,
+ { PMDA_PMID(CLUSTER_XFS,100), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.increment */
+ { &proc_fs_xfs.xs_abtc_2_increment,
+ { PMDA_PMID(CLUSTER_XFS,101), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.decrement */
+ { &proc_fs_xfs.xs_abtc_2_decrement,
+ { PMDA_PMID(CLUSTER_XFS,102), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.lshift */
+ { &proc_fs_xfs.xs_abtc_2_lshift,
+ { PMDA_PMID(CLUSTER_XFS,103), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.rshift */
+ { &proc_fs_xfs.xs_abtc_2_rshift,
+ { PMDA_PMID(CLUSTER_XFS,104), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.split */
+ { &proc_fs_xfs.xs_abtc_2_split,
+ { PMDA_PMID(CLUSTER_XFS,105), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.join */
+ { &proc_fs_xfs.xs_abtc_2_join,
+ { PMDA_PMID(CLUSTER_XFS,106), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.alloc */
+ { &proc_fs_xfs.xs_abtc_2_alloc,
+ { PMDA_PMID(CLUSTER_XFS,107), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.free */
+ { &proc_fs_xfs.xs_abtc_2_free,
+ { PMDA_PMID(CLUSTER_XFS,108), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.alloc_contig.moves */
+ { &proc_fs_xfs.xs_abtc_2_moves,
+ { PMDA_PMID(CLUSTER_XFS,109), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.btree.block_map.lookup */
+ { &proc_fs_xfs.xs_bmbt_2_lookup,
+ { PMDA_PMID(CLUSTER_XFS,110), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.compare */
+ { &proc_fs_xfs.xs_bmbt_2_compare,
+ { PMDA_PMID(CLUSTER_XFS,111), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.insrec */
+ { &proc_fs_xfs.xs_bmbt_2_insrec,
+ { PMDA_PMID(CLUSTER_XFS,112), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.delrec */
+ { &proc_fs_xfs.xs_bmbt_2_delrec,
+ { PMDA_PMID(CLUSTER_XFS,113), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.newroot */
+ { &proc_fs_xfs.xs_bmbt_2_newroot,
+ { PMDA_PMID(CLUSTER_XFS,114), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.killroot */
+ { &proc_fs_xfs.xs_bmbt_2_killroot,
+ { PMDA_PMID(CLUSTER_XFS,115), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.increment */
+ { &proc_fs_xfs.xs_bmbt_2_increment,
+ { PMDA_PMID(CLUSTER_XFS,116), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.decrement */
+ { &proc_fs_xfs.xs_bmbt_2_decrement,
+ { PMDA_PMID(CLUSTER_XFS,117), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.lshift */
+ { &proc_fs_xfs.xs_bmbt_2_lshift,
+ { PMDA_PMID(CLUSTER_XFS,118), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.rshift */
+ { &proc_fs_xfs.xs_bmbt_2_rshift,
+ { PMDA_PMID(CLUSTER_XFS,119), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.split */
+ { &proc_fs_xfs.xs_bmbt_2_split,
+ { PMDA_PMID(CLUSTER_XFS,120), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.join */
+ { &proc_fs_xfs.xs_bmbt_2_join,
+ { PMDA_PMID(CLUSTER_XFS,121), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.alloc */
+ { &proc_fs_xfs.xs_bmbt_2_alloc,
+ { PMDA_PMID(CLUSTER_XFS,122), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.free */
+ { &proc_fs_xfs.xs_bmbt_2_free,
+ { PMDA_PMID(CLUSTER_XFS,123), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.block_map.moves */
+ { &proc_fs_xfs.xs_bmbt_2_moves,
+ { PMDA_PMID(CLUSTER_XFS,124), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* xfs.btree.inode.lookup */
+ { &proc_fs_xfs.xs_ibt_2_compare,
+ { PMDA_PMID(CLUSTER_XFS,125), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.compare */
+ { &proc_fs_xfs.xs_ibt_2_lookup,
+ { PMDA_PMID(CLUSTER_XFS,126), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.insrec */
+ { &proc_fs_xfs.xs_ibt_2_insrec,
+ { PMDA_PMID(CLUSTER_XFS,127), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.delrec */
+ { &proc_fs_xfs.xs_ibt_2_delrec,
+ { PMDA_PMID(CLUSTER_XFS,128), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.newroot */
+ { &proc_fs_xfs.xs_ibt_2_newroot,
+ { PMDA_PMID(CLUSTER_XFS,129), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.killroot */
+ { &proc_fs_xfs.xs_ibt_2_killroot,
+ { PMDA_PMID(CLUSTER_XFS,130), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.increment */
+ { &proc_fs_xfs.xs_ibt_2_increment,
+ { PMDA_PMID(CLUSTER_XFS,131), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.decrement */
+ { &proc_fs_xfs.xs_ibt_2_decrement,
+ { PMDA_PMID(CLUSTER_XFS,132), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.lshift */
+ { &proc_fs_xfs.xs_ibt_2_lshift,
+ { PMDA_PMID(CLUSTER_XFS,133), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.rshift */
+ { &proc_fs_xfs.xs_ibt_2_rshift,
+ { PMDA_PMID(CLUSTER_XFS,134), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.split */
+ { &proc_fs_xfs.xs_ibt_2_split,
+ { PMDA_PMID(CLUSTER_XFS,135), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.join */
+ { &proc_fs_xfs.xs_ibt_2_join,
+ { PMDA_PMID(CLUSTER_XFS,136), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.alloc */
+ { &proc_fs_xfs.xs_ibt_2_alloc,
+ { PMDA_PMID(CLUSTER_XFS,137), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.free */
+ { &proc_fs_xfs.xs_ibt_2_free,
+ { PMDA_PMID(CLUSTER_XFS,138), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* xfs.btree.inode.moves */
+ { &proc_fs_xfs.xs_ibt_2_moves,
+ { PMDA_PMID(CLUSTER_XFS,139), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+
+/* quota.state.project.accounting */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,0), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* quota.state.project.enforcement */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,1), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* quota.project.space.hard */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,6), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* quota.project.space.soft */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,7), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* quota.project.space.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,8), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* quota.project.space.time_left */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,9), PM_TYPE_32, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) }, },
+/* quota.project.files.hard */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,10), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* quota.project.files.soft */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,11), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* quota.project.files.used */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,12), PM_TYPE_U64, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* quota.project.files.time_left */
+ { NULL,
+ { PMDA_PMID(CLUSTER_QUOTA,13), PM_TYPE_32, QUOTA_PRJ_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) }, },
+};
+
+FILE *
+xfs_statsfile(const char *path, const char *mode)
+{
+ char buffer[MAXPATHLEN];
+
+ snprintf(buffer, sizeof(buffer), "%s%s", xfs_statspath, path);
+ buffer[MAXPATHLEN-1] = '\0';
+ return fopen(buffer, mode);
+}
+
+static void
+xfs_refresh(pmdaExt *pmda, int *need_refresh)
+{
+ if (need_refresh[CLUSTER_QUOTA])
+ refresh_filesys(INDOM(FILESYS_INDOM), INDOM(QUOTA_PRJ_INDOM));
+ if (need_refresh[CLUSTER_XFS] || need_refresh[CLUSTER_XFSBUF])
+ refresh_proc_fs_xfs(&proc_fs_xfs);
+}
+
+static int
+xfs_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ __pmInDom_int *indomp = (__pmInDom_int *)&indom;
+ int need_refresh[NUM_CLUSTERS] = { 0 };
+
+ if (indomp->serial == FILESYS_INDOM || indomp->serial == QUOTA_PRJ_INDOM)
+ need_refresh[CLUSTER_QUOTA]++;
+ xfs_refresh(pmda, need_refresh);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+xfs_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ struct filesys *fs;
+ int sts;
+
+ if (mdesc->m_user != NULL) {
+ if ((idp->cluster == CLUSTER_XFS || idp->cluster == CLUSTER_XFSBUF) &&
+ proc_fs_xfs.errcode != 0) {
+ /* no values available for XFS metrics */
+ return 0;
+ }
+
+ switch (mdesc->m_desc.type) {
+ case PM_TYPE_32:
+ atom->l = *(__int32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U32:
+ atom->ul = *(__uint32_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_64:
+ atom->ll = *(__int64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_U64:
+ atom->ull = *(__uint64_t *)mdesc->m_user;
+ break;
+ case PM_TYPE_FLOAT:
+ atom->f = *(float *)mdesc->m_user;
+ break;
+ case PM_TYPE_DOUBLE:
+ atom->d = *(double *)mdesc->m_user;
+ break;
+ case PM_TYPE_STRING:
+ atom->cp = (char *)mdesc->m_user;
+ break;
+ default:
+ return 0;
+ }
+ }
+ else
+ switch (idp->cluster) {
+
+ case CLUSTER_XFS:
+ switch (idp->item) {
+ case 79: /* xfs.control.reset */
+ atom->ul = 0;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_QUOTA:
+ if (idp->item <= 5) {
+ sts = pmdaCacheLookup(INDOM(FILESYS_INDOM), inst, NULL,
+ (void **)&fs);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ switch (idp->item) {
+ case 0: /* quota.state.project.accounting */
+ atom->ul = !!(fs->flags & FSF_QUOT_PROJ_ACC);
+ break;
+ case 1: /* quota.state.project.enforcement */
+ atom->ul = !!(fs->flags & FSF_QUOT_PROJ_ENF);
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ else if (idp->item <= 13) {
+ struct project *pp;
+ sts = pmdaCacheLookup(INDOM(QUOTA_PRJ_INDOM), inst, NULL,
+ (void **)&pp);
+ if (sts < 0)
+ return sts;
+ if (sts != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+ switch (idp->item) {
+ case 6: /* quota.project.space.hard */
+ atom->ull = pp->space_hard >> 1; /* BBs to KB */
+ break;
+ case 7: /* quota.project.space.soft */
+ atom->ull = pp->space_soft >> 1; /* BBs to KB */
+ break;
+ case 8: /* quota.project.space.used */
+ atom->ull = pp->space_used >> 1; /* BBs to KB */
+ break;
+ case 9: /* quota.project.space.time_left */
+ atom->l = pp->space_time_left;
+ break;
+ case 10: /* quota.project.files.hard */
+ atom->ull = pp->files_hard;
+ break;
+ case 11: /* quota.project.files.soft */
+ atom->ull = pp->files_soft;
+ break;
+ case 12: /* quota.project.files.used */
+ atom->ull = pp->files_used;
+ break;
+ case 13: /* quota.project.files.time_left */
+ atom->l = pp->files_time_left;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ else
+ return PM_ERR_PMID;
+ break;
+ }
+
+ return 1;
+}
+
+static int
+xfs_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i, need_refresh[NUM_CLUSTERS] = { 0 };
+
+ for (i = 0; i < numpmid; i++) {
+ __pmID_int *idp = (__pmID_int *)&(pmidlist[i]);
+ if (idp->cluster >= MIN_CLUSTER && idp->cluster < NUM_CLUSTERS)
+ need_refresh[idp->cluster]++;
+ }
+
+ xfs_refresh(pmda, need_refresh);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+procfs_zero(const char *filename, pmValueSet *vsp)
+{
+ FILE *fp;
+ int value;
+ int sts = 0;
+
+ value = vsp->vlist[0].value.lval;
+ if (value < 0)
+ return PM_ERR_SIGN;
+
+ fp = xfs_statsfile(filename, "w");
+ if (!fp) {
+ sts = PM_ERR_PERMISSION;
+ } else {
+ fprintf(fp, "%d\n", value);
+ fclose(fp);
+ }
+ return sts;
+}
+
+static int
+xfs_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ int sts = 0;
+ pmValueSet *vsp;
+ __pmID_int *pmidp;
+
+ for (i = 0; i < result->numpmid && !sts; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster == CLUSTER_XFS && pmidp->item == 79) {
+ if ((sts = procfs_zero("/proc/sys/fs/xfs/stats_clear", vsp)) < 0)
+ break;
+ } else {
+ sts = PM_ERR_PERMISSION;
+ break;
+ }
+ }
+ return sts;
+}
+
+void
+__PMDA_INIT_CALL
+xfs_init(pmdaInterface *dp)
+{
+ char *envpath;
+
+ if ((envpath = getenv("XFS_STATSPATH")) != NULL)
+ xfs_statspath = envpath;
+
+ if (_isDSO) {
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(helppath, sizeof(helppath), "%s%c" "xfs" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_3, "XFS DSO", helppath);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.any.fetch = xfs_fetch;
+ dp->version.any.store = xfs_store;
+ dp->version.any.instance = xfs_instance;
+ pmdaSetFetchCallBack(dp, xfs_fetchCallBack);
+
+ xfs_indomtab[FILESYS_INDOM].it_indom = FILESYS_INDOM;
+ xfs_indomtab[QUOTA_PRJ_INDOM].it_indom = QUOTA_PRJ_INDOM;
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, xfs_indomtab, sizeof(xfs_indomtab)/sizeof(xfs_indomtab[0]),
+ xfs_metrictab, sizeof(xfs_metrictab)/sizeof(xfs_metrictab[0]));
+ pmdaCacheOp(INDOM(FILESYS_INDOM), PMDA_CACHE_CULL);
+ pmdaCacheOp(INDOM(QUOTA_PRJ_INDOM), PMDA_CACHE_CULL);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:?",
+ .long_options = longopts,
+};
+
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ snprintf(helppath, sizeof(helppath), "%s%c" "xfs" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, XFS, "xfs.log", helppath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ pmdaOpenLog(&dispatch);
+ xfs_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/linux_xfs/proc_fs_xfs.c b/src/pmdas/linux_xfs/proc_fs_xfs.c
new file mode 100644
index 0000000..6ee83d4
--- /dev/null
+++ b/src/pmdas/linux_xfs/proc_fs_xfs.c
@@ -0,0 +1,278 @@
+/*
+ * Linux /proc/fs/xfs metrics cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "proc_fs_xfs.h"
+
+int
+refresh_proc_fs_xfs(proc_fs_xfs_t *proc_fs_xfs)
+{
+ char buf[4096];
+ FILE *fp;
+
+ memset(proc_fs_xfs, 0, sizeof(proc_fs_xfs_t));
+
+ if ((fp = xfs_statsfile("/proc/fs/xfs/stat", "r")) == NULL)
+ proc_fs_xfs->errcode = -oserror();
+ else {
+ proc_fs_xfs->errcode = 0;
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "extent_alloc ", 13) == 0)
+ sscanf(buf, "extent_alloc %u %u %u %u",
+ &proc_fs_xfs->xs_allocx,
+ &proc_fs_xfs->xs_allocb,
+ &proc_fs_xfs->xs_freex,
+ &proc_fs_xfs->xs_freeb);
+ else
+ if (strncmp(buf, "abt ", 4) == 0)
+ sscanf(buf, "abt %u %u %u %u",
+ &proc_fs_xfs->xs_abt_lookup,
+ &proc_fs_xfs->xs_abt_compare,
+ &proc_fs_xfs->xs_abt_insrec,
+ &proc_fs_xfs->xs_abt_delrec);
+ else
+ if (strncmp(buf, "blk_map ", 8) == 0)
+ sscanf(buf, "blk_map %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_blk_mapr,
+ &proc_fs_xfs->xs_blk_mapw,
+ &proc_fs_xfs->xs_blk_unmap,
+ &proc_fs_xfs->xs_add_exlist,
+ &proc_fs_xfs->xs_del_exlist,
+ &proc_fs_xfs->xs_look_exlist,
+ &proc_fs_xfs->xs_cmp_exlist);
+ else
+ if (strncmp(buf, "bmbt ", 5) == 0)
+ sscanf(buf, "bmbt %u %u %u %u",
+ &proc_fs_xfs->xs_bmbt_lookup,
+ &proc_fs_xfs->xs_bmbt_compare,
+ &proc_fs_xfs->xs_bmbt_insrec,
+ &proc_fs_xfs->xs_bmbt_delrec);
+ else
+ if (strncmp(buf, "dir ", 4) == 0)
+ sscanf(buf, "dir %u %u %u %u",
+ &proc_fs_xfs->xs_dir_lookup,
+ &proc_fs_xfs->xs_dir_create,
+ &proc_fs_xfs->xs_dir_remove,
+ &proc_fs_xfs->xs_dir_getdents);
+ else
+ if (strncmp(buf, "trans ", 6) == 0)
+ sscanf(buf, "trans %u %u %u",
+ &proc_fs_xfs->xs_trans_sync,
+ &proc_fs_xfs->xs_trans_async,
+ &proc_fs_xfs->xs_trans_empty);
+ else
+ if (strncmp(buf, "ig ", 3) == 0)
+ sscanf(buf, "ig %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_ig_attempts,
+ &proc_fs_xfs->xs_ig_found,
+ &proc_fs_xfs->xs_ig_frecycle,
+ &proc_fs_xfs->xs_ig_missed,
+ &proc_fs_xfs->xs_ig_dup,
+ &proc_fs_xfs->xs_ig_reclaims,
+ &proc_fs_xfs->xs_ig_attrchg);
+ else
+ if (strncmp(buf, "log ", 4) == 0) {
+ sscanf(buf, "log %u %u %u %u %u",
+ &proc_fs_xfs->xs_log_writes,
+ &proc_fs_xfs->xs_log_blocks,
+ &proc_fs_xfs->xs_log_noiclogs,
+ &proc_fs_xfs->xs_log_force,
+ &proc_fs_xfs->xs_log_force_sleep);
+ }
+ else
+ if (strncmp(buf, "push_ail ", 9) == 0)
+ sscanf(buf, "push_ail %u %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_try_logspace,
+ &proc_fs_xfs->xs_sleep_logspace,
+ &proc_fs_xfs->xs_push_ail,
+ &proc_fs_xfs->xs_push_ail_success,
+ &proc_fs_xfs->xs_push_ail_pushbuf,
+ &proc_fs_xfs->xs_push_ail_pinned,
+ &proc_fs_xfs->xs_push_ail_locked,
+ &proc_fs_xfs->xs_push_ail_flushing,
+ &proc_fs_xfs->xs_push_ail_restarts,
+ &proc_fs_xfs->xs_push_ail_flush);
+ else
+ if (strncmp(buf, "xstrat ", 7) == 0)
+ sscanf(buf, "xstrat %u %u",
+ &proc_fs_xfs->xs_xstrat_quick,
+ &proc_fs_xfs->xs_xstrat_split);
+ else
+ if (strncmp(buf, "rw ", 3) == 0)
+ sscanf(buf, "rw %u %u",
+ &proc_fs_xfs->xs_write_calls,
+ &proc_fs_xfs->xs_read_calls);
+ else
+ if (strncmp(buf, "attr ", 5) == 0)
+ sscanf(buf, "attr %u %u %u %u",
+ &proc_fs_xfs->xs_attr_get,
+ &proc_fs_xfs->xs_attr_set,
+ &proc_fs_xfs->xs_attr_remove,
+ &proc_fs_xfs->xs_attr_list);
+ else
+ if (strncmp(buf, "qm ", 3) == 0)
+ sscanf(buf, "qm %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_qm_dqreclaims,
+ &proc_fs_xfs->xs_qm_dqreclaim_misses,
+ &proc_fs_xfs->xs_qm_dquot_dups,
+ &proc_fs_xfs->xs_qm_dqcachemisses,
+ &proc_fs_xfs->xs_qm_dqcachehits,
+ &proc_fs_xfs->xs_qm_dqwants,
+ &proc_fs_xfs->xs_qm_dqshake_reclaims,
+ &proc_fs_xfs->xs_qm_dqinact_reclaims);
+ else
+ if (strncmp(buf, "icluster ", 9) == 0)
+ sscanf(buf, "icluster %u %u %u",
+ &proc_fs_xfs->xs_iflush_count,
+ &proc_fs_xfs->xs_icluster_flushcnt,
+ &proc_fs_xfs->xs_icluster_flushinode);
+ else
+ if (strncmp(buf, "buf ", 4) == 0) {
+ sscanf(buf, "buf %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_buf_get,
+ &proc_fs_xfs->xs_buf_create,
+ &proc_fs_xfs->xs_buf_get_locked,
+ &proc_fs_xfs->xs_buf_get_locked_waited,
+ &proc_fs_xfs->xs_buf_busy_locked,
+ &proc_fs_xfs->xs_buf_miss_locked,
+ &proc_fs_xfs->xs_buf_page_retries,
+ &proc_fs_xfs->xs_buf_page_found,
+ &proc_fs_xfs->xs_buf_get_read);
+ } else
+ if (strncmp(buf, "vnodes ", 7) == 0) {
+ sscanf(buf, "vnodes %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->vnodes.vn_active,
+ &proc_fs_xfs->vnodes.vn_alloc,
+ &proc_fs_xfs->vnodes.vn_get,
+ &proc_fs_xfs->vnodes.vn_hold,
+ &proc_fs_xfs->vnodes.vn_rele,
+ &proc_fs_xfs->vnodes.vn_reclaim,
+ &proc_fs_xfs->vnodes.vn_remove,
+ &proc_fs_xfs->vnodes.vn_free);
+ } else
+ if (strncmp(buf, "abtb2 ", 6) == 0) {
+ sscanf(buf, "abtb2 %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_abtb_2_lookup,
+ &proc_fs_xfs->xs_abtb_2_compare,
+ &proc_fs_xfs->xs_abtb_2_insrec,
+ &proc_fs_xfs->xs_abtb_2_delrec,
+ &proc_fs_xfs->xs_abtb_2_newroot,
+ &proc_fs_xfs->xs_abtb_2_killroot,
+ &proc_fs_xfs->xs_abtb_2_increment,
+ &proc_fs_xfs->xs_abtb_2_decrement,
+ &proc_fs_xfs->xs_abtb_2_lshift,
+ &proc_fs_xfs->xs_abtb_2_rshift,
+ &proc_fs_xfs->xs_abtb_2_split,
+ &proc_fs_xfs->xs_abtb_2_join,
+ &proc_fs_xfs->xs_abtb_2_alloc,
+ &proc_fs_xfs->xs_abtb_2_free,
+ &proc_fs_xfs->xs_abtb_2_moves);
+ } else
+ if (strncmp(buf, "abtc2 ", 6) == 0) {
+ sscanf(buf, "abtc2 %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_abtc_2_lookup,
+ &proc_fs_xfs->xs_abtc_2_compare,
+ &proc_fs_xfs->xs_abtc_2_insrec,
+ &proc_fs_xfs->xs_abtc_2_delrec,
+ &proc_fs_xfs->xs_abtc_2_newroot,
+ &proc_fs_xfs->xs_abtc_2_killroot,
+ &proc_fs_xfs->xs_abtc_2_increment,
+ &proc_fs_xfs->xs_abtc_2_decrement,
+ &proc_fs_xfs->xs_abtc_2_lshift,
+ &proc_fs_xfs->xs_abtc_2_rshift,
+ &proc_fs_xfs->xs_abtc_2_split,
+ &proc_fs_xfs->xs_abtc_2_join,
+ &proc_fs_xfs->xs_abtc_2_alloc,
+ &proc_fs_xfs->xs_abtc_2_free,
+ &proc_fs_xfs->xs_abtc_2_moves);
+ } else
+ if (strncmp(buf, "bmbt2 ", 6) == 0) {
+ sscanf(buf, "bmbt2 %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_bmbt_2_lookup,
+ &proc_fs_xfs->xs_bmbt_2_compare,
+ &proc_fs_xfs->xs_bmbt_2_insrec,
+ &proc_fs_xfs->xs_bmbt_2_delrec,
+ &proc_fs_xfs->xs_bmbt_2_newroot,
+ &proc_fs_xfs->xs_bmbt_2_killroot,
+ &proc_fs_xfs->xs_bmbt_2_increment,
+ &proc_fs_xfs->xs_bmbt_2_decrement,
+ &proc_fs_xfs->xs_bmbt_2_lshift,
+ &proc_fs_xfs->xs_bmbt_2_rshift,
+ &proc_fs_xfs->xs_bmbt_2_split,
+ &proc_fs_xfs->xs_bmbt_2_join,
+ &proc_fs_xfs->xs_bmbt_2_alloc,
+ &proc_fs_xfs->xs_bmbt_2_free,
+ &proc_fs_xfs->xs_bmbt_2_moves);
+ } else
+ if (strncmp(buf, "ibt2 ", 5) == 0) {
+ sscanf(buf, "ibt2 %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_ibt_2_lookup,
+ &proc_fs_xfs->xs_ibt_2_compare,
+ &proc_fs_xfs->xs_ibt_2_insrec,
+ &proc_fs_xfs->xs_ibt_2_delrec,
+ &proc_fs_xfs->xs_ibt_2_newroot,
+ &proc_fs_xfs->xs_ibt_2_killroot,
+ &proc_fs_xfs->xs_ibt_2_increment,
+ &proc_fs_xfs->xs_ibt_2_decrement,
+ &proc_fs_xfs->xs_ibt_2_lshift,
+ &proc_fs_xfs->xs_ibt_2_rshift,
+ &proc_fs_xfs->xs_ibt_2_split,
+ &proc_fs_xfs->xs_ibt_2_join,
+ &proc_fs_xfs->xs_ibt_2_alloc,
+ &proc_fs_xfs->xs_ibt_2_free,
+ &proc_fs_xfs->xs_ibt_2_moves);
+ } else
+ if (strncmp(buf, "xpc", 3) == 0)
+ sscanf(buf, "xpc %llu %llu %llu",
+ (unsigned long long *)&proc_fs_xfs->xpc.xs_xstrat_bytes,
+ (unsigned long long *)&proc_fs_xfs->xpc.xs_write_bytes,
+ (unsigned long long *)&proc_fs_xfs->xpc.xs_read_bytes);
+ }
+ fclose(fp);
+
+ if (proc_fs_xfs->xs_log_writes)
+ proc_fs_xfs->xs_log_write_ratio =
+ proc_fs_xfs->xs_log_blocks / proc_fs_xfs->xs_log_writes;
+ /*
+ * Bug #824382. xs_log_blocks is counted in units
+ * of 512 bytes/block, but PCP exports it as Kbytes.
+ */
+ proc_fs_xfs->xs_log_blocks >>= 1;
+
+ fp = xfs_statsfile("/proc/fs/xfs/xqmstat", "r");
+ if (fp != (FILE *)NULL) {
+ if (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (strncmp(buf, "qm", 2) == 0)
+ sscanf(buf, "qm %u %u %u %u %u %u %u %u",
+ &proc_fs_xfs->xs_qm_dqreclaims,
+ &proc_fs_xfs->xs_qm_dqreclaim_misses,
+ &proc_fs_xfs->xs_qm_dquot_dups,
+ &proc_fs_xfs->xs_qm_dqcachemisses,
+ &proc_fs_xfs->xs_qm_dqcachehits,
+ &proc_fs_xfs->xs_qm_dqwants,
+ &proc_fs_xfs->xs_qm_dqshake_reclaims,
+ &proc_fs_xfs->xs_qm_dqinact_reclaims);
+ }
+ fclose(fp);
+ }
+ }
+
+ if (proc_fs_xfs->errcode == 0)
+ return 0;
+ return -1;
+}
diff --git a/src/pmdas/linux_xfs/proc_fs_xfs.h b/src/pmdas/linux_xfs/proc_fs_xfs.h
new file mode 100644
index 0000000..bec0514
--- /dev/null
+++ b/src/pmdas/linux_xfs/proc_fs_xfs.h
@@ -0,0 +1,189 @@
+/*
+ * Linux /proc/fs/xfs metrics cluster
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+typedef struct {
+ int errcode; /* error from previous refresh */
+ unsigned int xs_allocx; /* allocs.alloc_extent */
+ unsigned int xs_allocb; /* allocs.alloc_block */
+ unsigned int xs_freex; /* allocs.free_extent */
+ unsigned int xs_freeb; /* allocs.free_block */
+
+ unsigned int xs_abt_lookup; /* alloc_btree.lookup */
+ unsigned int xs_abt_compare; /* alloc_btree.compare */
+ unsigned int xs_abt_insrec; /* alloc_btree.insrec */
+ unsigned int xs_abt_delrec; /* alloc_btree.delrec */
+ unsigned int xs_blk_mapr; /* block_map.read_ops */
+ unsigned int xs_blk_mapw; /* block_map.write_ops */
+ unsigned int xs_blk_unmap; /* block_map.unmap */
+ unsigned int xs_add_exlist; /* block_map.add_exlist */
+ unsigned int xs_del_exlist; /* block_map.del_exlist */
+ unsigned int xs_look_exlist; /* block_map.look_exlist */
+ unsigned int xs_cmp_exlist; /* block_map.cmp_exlist */
+ unsigned int xs_bmbt_lookup; /* bmap_btree.lookup */
+ unsigned int xs_bmbt_compare; /* bmap_btree.compare */
+ unsigned int xs_bmbt_insrec; /* bmap_btree.insrec */
+ unsigned int xs_bmbt_delrec; /* bmap_btree.delrec */
+
+ unsigned int xs_dir_lookup; /* dir_ops.lookup */
+ unsigned int xs_dir_create; /* dir_ops.create */
+ unsigned int xs_dir_remove; /* dir_ops.remove */
+ unsigned int xs_dir_getdents; /* dir_ops.getdents */
+
+ unsigned int xs_trans_sync; /* transactions.sync */
+ unsigned int xs_trans_async; /* transactions.async */
+ unsigned int xs_trans_empty; /* transactions.empty */
+
+ unsigned int xs_ig_attempts; /* inode_ops.ig_attempts */
+ unsigned int xs_ig_found; /* inode_ops.ig_found */
+ unsigned int xs_ig_frecycle; /* inode_ops.ig_frecycle */
+ unsigned int xs_ig_missed; /* inode_ops.ig_missed */
+ unsigned int xs_ig_dup; /* inode_ops.ig_dup */
+ unsigned int xs_ig_reclaims; /* inode_ops.ig_reclaims */
+ unsigned int xs_ig_attrchg; /* inode_ops.ig_attrchg */
+
+ unsigned int xs_log_writes; /* log.writes */
+ unsigned int xs_log_blocks; /* log.blocks */
+ float xs_log_write_ratio; /* log.write_ratio */
+ unsigned int xs_log_noiclogs; /* log.noiclogs */
+
+ unsigned int xs_xstrat_quick; /* xstrat.quick */
+ unsigned int xs_xstrat_split; /* xstrat.split */
+ unsigned int xs_write_calls; /* write */
+ unsigned int xs_read_calls; /* read */
+
+ unsigned int xs_attr_get; /* attr.get */
+ unsigned int xs_attr_set; /* attr.set */
+ unsigned int xs_attr_remove; /* attr.remove */
+ unsigned int xs_attr_list; /* attr.list */
+
+ unsigned int xs_log_force; /* log.force */
+ unsigned int xs_log_force_sleep; /* log.force_sleep */
+ unsigned int xs_try_logspace; /* log_tail.try_logspace */
+ unsigned int xs_sleep_logspace; /* log_tail.sleep_logspace */
+ unsigned int xs_push_ail; /* log_tail.push_ail.pushes */
+ unsigned int xs_push_ail_success; /* log_tail.push_ail.success */
+ unsigned int xs_push_ail_pushbuf; /* log_tail.push_ail.pushbuf */
+ unsigned int xs_push_ail_pinned; /* log_tail.push_ail.pinned */
+ unsigned int xs_push_ail_locked; /* log_tail.push_ail.locked */
+ unsigned int xs_push_ail_flushing; /* log_tail.push_ail.flushing */
+ unsigned int xs_push_ail_restarts; /* log_tail.push_ail.restarts */
+ unsigned int xs_push_ail_flush; /* log_tail.push_ail.flush */
+
+ unsigned int xs_qm_dqreclaims; /* quota.reclaims */
+ unsigned int xs_qm_dqreclaim_misses; /* quota.reclaim_misses */
+ unsigned int xs_qm_dquot_dups; /* quota.dquot_dups */
+ unsigned int xs_qm_dqcachemisses; /* quota.cachemisses */
+ unsigned int xs_qm_dqcachehits; /* quota.cachehits */
+ unsigned int xs_qm_dqwants; /* quota.wants */
+ unsigned int xs_qm_dqshake_reclaims; /* quota.shake_reclaims */
+ unsigned int xs_qm_dqinact_reclaims; /* quota.inact_reclaims */
+
+ unsigned int xs_iflush_count; /* iflush_count */
+ unsigned int xs_icluster_flushcnt; /* icluster_flushcnt */
+ unsigned int xs_icluster_flushinode; /* icluster_flushinode */
+
+ unsigned int xs_buf_get; /* buffer.get */
+ unsigned int xs_buf_create; /* buffer.create */
+ unsigned int xs_buf_get_locked; /* buffer.get_locked */
+ unsigned int xs_buf_get_locked_waited; /* buffer.get_locked_waited */
+ unsigned int xs_buf_busy_locked; /* buffer.busy_locked */
+ unsigned int xs_buf_miss_locked; /* buffer.miss_locked */
+ unsigned int xs_buf_page_retries; /* buffer.page_retries */
+ unsigned int xs_buf_page_found; /* buffer.page_found */
+ unsigned int xs_buf_get_read; /* buffer.get_read */
+
+ unsigned int xs_abtb_2_lookup; /* btree.alloc_blocks.lookup */
+ unsigned int xs_abtb_2_compare; /* btree.alloc_blocks.compare */
+ unsigned int xs_abtb_2_insrec; /* btree.alloc_blocks.insrec */
+ unsigned int xs_abtb_2_delrec; /* btree.alloc_blocks.delrec */
+ unsigned int xs_abtb_2_newroot; /* btree.alloc_blocks.newroot */
+ unsigned int xs_abtb_2_killroot; /* btree.alloc_blocks.killroot */
+ unsigned int xs_abtb_2_increment; /* btree.alloc_blocks.increment */
+ unsigned int xs_abtb_2_decrement; /* btree.alloc_blocks.decrement */
+ unsigned int xs_abtb_2_lshift; /* btree.alloc_blocks.lshift */
+ unsigned int xs_abtb_2_rshift; /* btree.alloc_blocks.rshift */
+ unsigned int xs_abtb_2_split; /* btree.alloc_blocks.split */
+ unsigned int xs_abtb_2_join; /* btree.alloc_blocks.join */
+ unsigned int xs_abtb_2_alloc; /* btree.alloc_blocks.alloc */
+ unsigned int xs_abtb_2_free; /* btree.alloc_blocks.free */
+ unsigned int xs_abtb_2_moves; /* btree.alloc_blocks.moves */
+ unsigned int xs_abtc_2_lookup; /* btree.alloc_contig.lookup */
+ unsigned int xs_abtc_2_compare; /* btree.alloc_contig.compare */
+ unsigned int xs_abtc_2_insrec; /* btree.alloc_contig.insrec */
+ unsigned int xs_abtc_2_delrec; /* btree.alloc_contig.delrec */
+ unsigned int xs_abtc_2_newroot; /* btree.alloc_contig.newroot */
+ unsigned int xs_abtc_2_killroot; /* btree.alloc_contig.killroot */
+ unsigned int xs_abtc_2_increment; /* btree.alloc_contig.increment */
+ unsigned int xs_abtc_2_decrement; /* btree.alloc_contig.decrement */
+ unsigned int xs_abtc_2_lshift; /* btree.alloc_contig.lshift */
+ unsigned int xs_abtc_2_rshift; /* btree.alloc_contig.rshift */
+ unsigned int xs_abtc_2_split; /* btree.alloc_contig.split */
+ unsigned int xs_abtc_2_join; /* btree.alloc_contig.join */
+ unsigned int xs_abtc_2_alloc; /* btree.alloc_contig.alloc */
+ unsigned int xs_abtc_2_free; /* btree.alloc_contig.free */
+ unsigned int xs_abtc_2_moves; /* btree.alloc_contig.moves */
+ unsigned int xs_bmbt_2_lookup; /* btree.block_map.lookup */
+ unsigned int xs_bmbt_2_compare; /* btree.block_map.compare */
+ unsigned int xs_bmbt_2_insrec; /* btree.block_map.insrec */
+ unsigned int xs_bmbt_2_delrec; /* btree.block_map.delrec */
+ unsigned int xs_bmbt_2_newroot; /* btree.block_map.newroot */
+ unsigned int xs_bmbt_2_killroot; /* btree.block_map.killroot */
+ unsigned int xs_bmbt_2_increment; /* btree.block_map.increment */
+ unsigned int xs_bmbt_2_decrement; /* btree.block_map.decrement */
+ unsigned int xs_bmbt_2_lshift; /* btree.block_map.lshift */
+ unsigned int xs_bmbt_2_rshift; /* btree.block_map.rshift */
+ unsigned int xs_bmbt_2_split; /* btree.block_map.split */
+ unsigned int xs_bmbt_2_join; /* btree.block_map.join */
+ unsigned int xs_bmbt_2_alloc; /* btree.block_map.alloc */
+ unsigned int xs_bmbt_2_free; /* btree.block_map.free */
+ unsigned int xs_bmbt_2_moves; /* btree.block_map.moves */
+ unsigned int xs_ibt_2_lookup; /* btree.inode.lookup */
+ unsigned int xs_ibt_2_compare; /* btree.inode.compare */
+ unsigned int xs_ibt_2_insrec; /* btree.inode.insrec */
+ unsigned int xs_ibt_2_delrec; /* btree.inode.delrec */
+ unsigned int xs_ibt_2_newroot; /* btree.inode.newroot */
+ unsigned int xs_ibt_2_killroot; /* btree.inode.killroot */
+ unsigned int xs_ibt_2_increment; /* btree.inode.increment */
+ unsigned int xs_ibt_2_decrement; /* btree.inode.decrement */
+ unsigned int xs_ibt_2_lshift; /* btree.inode.lshift */
+ unsigned int xs_ibt_2_rshift; /* btree.inode.rshift */
+ unsigned int xs_ibt_2_split; /* btree.inode.split */
+ unsigned int xs_ibt_2_join; /* btree.inode.join */
+ unsigned int xs_ibt_2_alloc; /* btree.inode.alloc */
+ unsigned int xs_ibt_2_free; /* btree.inode.free */
+ unsigned int xs_ibt_2_moves; /* btree.inode.moves */
+
+ struct vnodes {
+ unsigned int vn_active; /* vnodes.active */
+ unsigned int vn_alloc; /* vnodes.alloc */
+ unsigned int vn_get; /* vnodes.get */
+ unsigned int vn_hold; /* vnodes.hold */
+ unsigned int vn_rele; /* vnodes.rele */
+ unsigned int vn_reclaim; /* vnodes.reclaim */
+ unsigned int vn_remove; /* vnodes.remove */
+ unsigned int vn_free; /* vnodes.free */
+ } vnodes;
+ struct xpc {
+ __uint64_t xs_write_bytes; /* write_bytes */
+ __uint64_t xs_read_bytes; /* read_bytes */
+ __uint64_t xs_xstrat_bytes; /* xstrat_bytes */
+ } xpc;
+} proc_fs_xfs_t;
+
+extern FILE *xfs_statsfile(const char *, const char *);
+extern int refresh_proc_fs_xfs(proc_fs_xfs_t *);
diff --git a/src/pmdas/linux_xfs/root b/src/pmdas/linux_xfs/root
new file mode 100644
index 0000000..5f26a89
--- /dev/null
+++ b/src/pmdas/linux_xfs/root
@@ -0,0 +1,6 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+#include "root_xfs"
diff --git a/src/pmdas/linux_xfs/root_xfs b/src/pmdas/linux_xfs/root_xfs
new file mode 100644
index 0000000..16ebfd5
--- /dev/null
+++ b/src/pmdas/linux_xfs/root_xfs
@@ -0,0 +1,295 @@
+/*
+ * Portions Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2000,2004,2007-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef XFS
+#define XFS 11
+#endif
+
+root {
+ xfs
+ quota
+}
+
+xfs {
+ allocs
+ alloc_btree
+ block_map
+ bmap_btree
+ dir_ops
+ transactions
+ inode_ops
+ log
+ log_tail
+ xstrat
+ write XFS:16:51
+ write_bytes XFS:16:52
+ read XFS:16:53
+ read_bytes XFS:16:54
+ attr
+ quota
+ iflush_count XFS:16:67
+ icluster_flushcnt XFS:16:68
+ icluster_flushinode XFS:16:69
+ buffer
+ vnodes
+ control
+ btree
+}
+
+xfs.allocs {
+ alloc_extent XFS:16:0
+ alloc_block XFS:16:1
+ free_extent XFS:16:2
+ free_block XFS:16:3
+}
+
+
+xfs.alloc_btree {
+ lookup XFS:16:4
+ compare XFS:16:5
+ insrec XFS:16:6
+ delrec XFS:16:7
+}
+
+xfs.block_map {
+ read_ops XFS:16:8
+ write_ops XFS:16:9
+ unmap XFS:16:10
+ add_exlist XFS:16:11
+ del_exlist XFS:16:12
+ look_exlist XFS:16:13
+ cmp_exlist XFS:16:14
+}
+
+xfs.bmap_btree {
+ lookup XFS:16:15
+ compare XFS:16:16
+ insrec XFS:16:17
+ delrec XFS:16:18
+}
+
+xfs.dir_ops {
+ lookup XFS:16:19
+ create XFS:16:20
+ remove XFS:16:21
+ getdents XFS:16:22
+}
+
+xfs.transactions {
+ sync XFS:16:23
+ async XFS:16:24
+ empty XFS:16:25
+}
+
+xfs.inode_ops {
+ ig_attempts XFS:16:26
+ ig_found XFS:16:27
+ ig_frecycle XFS:16:28
+ ig_missed XFS:16:29
+ ig_dup XFS:16:30
+ ig_reclaims XFS:16:31
+ ig_attrchg XFS:16:32
+}
+
+xfs.log {
+ writes XFS:16:33
+ blocks XFS:16:34
+ write_ratio XFS:16:78
+ noiclogs XFS:16:35
+ force XFS:16:36
+ force_sleep XFS:16:37
+}
+
+xfs.log_tail {
+ try_logspace XFS:16:38
+ sleep_logspace XFS:16:39
+ push_ail
+}
+
+xfs.log_tail.push_ail {
+ pushes XFS:16:40
+ success XFS:16:41
+ pushbuf XFS:16:42
+ pinned XFS:16:43
+ locked XFS:16:44
+ flushing XFS:16:45
+ restarts XFS:16:46
+ flush XFS:16:47
+}
+
+xfs.xstrat {
+ bytes XFS:16:48
+ quick XFS:16:49
+ split XFS:16:50
+}
+
+xfs.attr {
+ get XFS:16:55
+ set XFS:16:56
+ remove XFS:16:57
+ list XFS:16:58
+}
+
+xfs.quota {
+ reclaims XFS:16:59
+ reclaim_misses XFS:16:60
+ dquot_dups XFS:16:61
+ cachemisses XFS:16:62
+ cachehits XFS:16:63
+ wants XFS:16:64
+ shake_reclaims XFS:16:65
+ inact_reclaims XFS:16:66
+}
+
+xfs.vnodes {
+ active XFS:16:70
+ alloc XFS:16:71
+ get XFS:16:72
+ hold XFS:16:73
+ rele XFS:16:74
+ reclaim XFS:16:75
+ remove XFS:16:76
+ free XFS:16:77
+}
+
+xfs.control {
+ reset XFS:16:79
+}
+
+xfs.buffer {
+ get XFS:17:0
+ create XFS:17:1
+ get_locked XFS:17:2
+ get_locked_waited XFS:17:3
+ busy_locked XFS:17:4
+ miss_locked XFS:17:5
+ page_retries XFS:17:6
+ page_found XFS:17:7
+ get_read XFS:17:8
+}
+
+xfs.btree {
+ alloc_blocks
+ alloc_contig
+ block_map
+ inode
+}
+
+xfs.btree.alloc_blocks {
+ lookup XFS:16:80
+ compare XFS:16:81
+ insrec XFS:16:82
+ delrec XFS:16:83
+ newroot XFS:16:84
+ killroot XFS:16:85
+ increment XFS:16:86
+ decrement XFS:16:87
+ lshift XFS:16:88
+ rshift XFS:16:89
+ split XFS:16:90
+ join XFS:16:91
+ alloc XFS:16:92
+ free XFS:16:93
+ moves XFS:16:94
+}
+
+xfs.btree.alloc_contig {
+ lookup XFS:16:95
+ compare XFS:16:96
+ insrec XFS:16:97
+ delrec XFS:16:98
+ newroot XFS:16:99
+ killroot XFS:16:100
+ increment XFS:16:101
+ decrement XFS:16:102
+ lshift XFS:16:103
+ rshift XFS:16:104
+ split XFS:16:105
+ join XFS:16:106
+ alloc XFS:16:107
+ free XFS:16:108
+ moves XFS:16:109
+}
+
+xfs.btree.block_map {
+ lookup XFS:16:110
+ compare XFS:16:111
+ insrec XFS:16:112
+ delrec XFS:16:113
+ newroot XFS:16:114
+ killroot XFS:16:115
+ increment XFS:16:116
+ decrement XFS:16:117
+ lshift XFS:16:118
+ rshift XFS:16:119
+ split XFS:16:120
+ join XFS:16:121
+ alloc XFS:16:122
+ free XFS:16:123
+ moves XFS:16:124
+}
+
+xfs.btree.inode {
+ lookup XFS:16:125
+ compare XFS:16:126
+ insrec XFS:16:127
+ delrec XFS:16:128
+ newroot XFS:16:129
+ killroot XFS:16:130
+ increment XFS:16:131
+ decrement XFS:16:132
+ lshift XFS:16:133
+ rshift XFS:16:134
+ split XFS:16:135
+ join XFS:16:136
+ alloc XFS:16:137
+ free XFS:16:138
+ moves XFS:16:139
+}
+
+quota {
+ state
+ project
+}
+
+quota.state {
+ project
+}
+
+quota.state.project {
+ accounting XFS:30:0
+ enforcement XFS:30:1
+}
+
+quota.project {
+ space
+ files
+}
+
+quota.project.space {
+ hard XFS:30:6
+ soft XFS:30:7
+ used XFS:30:8
+ time_left XFS:30:9
+}
+
+quota.project.files {
+ hard XFS:30:10
+ soft XFS:30:11
+ used XFS:30:12
+ time_left XFS:30:13
+}
+
+#undef XFS
diff --git a/src/pmdas/lmsensors/GNUmakefile b/src/pmdas/lmsensors/GNUmakefile
new file mode 100644
index 0000000..15df27c
--- /dev/null
+++ b/src/pmdas/lmsensors/GNUmakefile
@@ -0,0 +1,61 @@
+#
+# Original implementation by Troy Dawson (dawson@fnal.gov)
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = lmsensors
+DOMAIN = LMSENSORS
+TARGETS = $(IAM)
+CFILES = lmsensors.c
+HFILES = lmsensors.h
+SCRIPTS = Install Remove
+DFILES = README
+LSRCFILES = $(SCRIPTS) pmns help root $(DFILES)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log pmda$(IAM) pmda_$(IAM).so $(TARGETS) \
+ help.pag help.dir
+LLDLIBS = $(PCP_PMDALIB)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: $(TARGETS)
+
+install : default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) pmns help root domain.h $(PMDADIR)
+else
+build-me:
+install:
+endif
+
+$(IAM): $(OBJECTS)
+
+lmsensors.o: domain.h
+
+default_pcp: default
+
+install_pcp: install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/lmsensors/Install b/src/pmdas/lmsensors/Install
new file mode 100755
index 0000000..977f06e
--- /dev/null
+++ b/src/pmdas/lmsensors/Install
@@ -0,0 +1,35 @@
+#! /bin/sh
+#
+# Original implementation by Troy Dawson (dawson@fnal.gov)
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=lmsensors
+pmda_interface=2
+forced_restart=true
+
+# Do it
+#
+pmdaSetup
+
+dso_opt=true
+socket_opt=true
+socket_inet_def=2079
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/lmsensors/README b/src/pmdas/lmsensors/README
new file mode 100644
index 0000000..961d26a
--- /dev/null
+++ b/src/pmdas/lmsensors/README
@@ -0,0 +1,69 @@
+#
+# Original implementation by Troy Dawson (dawson@fnal.gov)
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+
+lmsensors PMDA
+==============
+
+This PMDA exports information about the lm sensors on
+compatible motherboards.
+
+This source code was contributed by Troy Dawson (dawson@fnal.gov)
+to the PCP open source project.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT lmsensors
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/lmsensors
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use
+ ($PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+ + Alternatively, to install just the Performance Metrics Name Space
+ for the lmsensors metrics on the local system, but not the lmsensors PMDA
+ (presumably because the local system is running PCP 1.x and you
+ wish to connect to a remote system where PCP 2.0 and the lmsensors PMDA
+ is running), make sure the Performance Metrics Domain defined in
+ ./domain.h matches the domain chosen for the lmsensors PMDA on the
+ remote system (check the second field in the corresponding line of
+ the $PCP_PMCDCONF_PATH file on the remote system), then
+
+ # ./Install -N
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/lmsensors
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/lmsensors.log) should be checked for any warnings or
+ errors.
diff --git a/src/pmdas/lmsensors/Remove b/src/pmdas/lmsensors/Remove
new file mode 100755
index 0000000..ddcbfc0
--- /dev/null
+++ b/src/pmdas/lmsensors/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Original implementation by Troy Dawson (dawson@fnal.gov)
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=lmsensors
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/lmsensors/help b/src/pmdas/lmsensors/help
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/src/pmdas/lmsensors/help
@@ -0,0 +1 @@
+#
diff --git a/src/pmdas/lmsensors/lmsensors.c b/src/pmdas/lmsensors/lmsensors.c
new file mode 100644
index 0000000..c8891f0
--- /dev/null
+++ b/src/pmdas/lmsensors/lmsensors.c
@@ -0,0 +1,953 @@
+/*
+ * lmsensors, configurable PMDA
+ *
+ * Original implementation by Troy Dawson (dawson@fnal.gov)
+ *
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "lmsensors.h"
+
+static char *username;
+static char buf[4096];
+static chips schips;
+
+/*
+ * lmsensors PMDA
+ *
+ */
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+static pmdaMetric metrictab[] = {
+/* n_total */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* n_lm75 */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* n_lm79 */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* n_lm87 */
+ { NULL,
+ { PMDA_PMID(0,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* n_w83781d */
+ { NULL,
+ { PMDA_PMID(0,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* n_mtp008 */
+ { NULL,
+ { PMDA_PMID(0,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm75 temp */
+ { NULL,
+ { PMDA_PMID(1,0), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 fan1 */
+ { NULL,
+ { PMDA_PMID(2,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 fan2 */
+ { NULL,
+ { PMDA_PMID(2,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 fan3 */
+ { NULL,
+ { PMDA_PMID(2,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 fan_div */
+ { NULL,
+ { PMDA_PMID(2,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 temp */
+ { NULL,
+ { PMDA_PMID(2,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 alarms */
+ { NULL,
+ { PMDA_PMID(2,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 VCore1 */
+ { NULL,
+ { PMDA_PMID(2,6), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 VCore2 */
+ { NULL,
+ { PMDA_PMID(2,7), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 p33V */
+ { NULL,
+ { PMDA_PMID(2,8), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 p5V */
+ { NULL,
+ { PMDA_PMID(2,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 p12V */
+ { NULL,
+ { PMDA_PMID(2,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 n12V */
+ { NULL,
+ { PMDA_PMID(2,11), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 n5V */
+ { NULL,
+ { PMDA_PMID(2,12), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm79 vid */
+ { NULL,
+ { PMDA_PMID(2,13), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 fan1 */
+ { NULL,
+ { PMDA_PMID(3,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 fan2 */
+ { NULL,
+ { PMDA_PMID(3,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 temp1 */
+ { NULL,
+ { PMDA_PMID(3,2), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 CPUtemp */
+ { NULL,
+ { PMDA_PMID(3,3), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 Vccp1 */
+ { NULL,
+ { PMDA_PMID(3,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 Vccp2 */
+ { NULL,
+ { PMDA_PMID(3,5), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 p25V */
+ { NULL,
+ { PMDA_PMID(3,6), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 p33V */
+ { NULL,
+ { PMDA_PMID(3,7), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 p5V */
+ { NULL,
+ { PMDA_PMID(3,8), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 p12V */
+ { NULL,
+ { PMDA_PMID(3,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* lm87 vid */
+ { NULL,
+ { PMDA_PMID(3,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d fan1 */
+ { NULL,
+ { PMDA_PMID(4,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d fan2 */
+ { NULL,
+ { PMDA_PMID(4,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d fan3 */
+ { NULL,
+ { PMDA_PMID(4,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d fan_div */
+ { NULL,
+ { PMDA_PMID(4,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d temp1 */
+ { NULL,
+ { PMDA_PMID(4,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d temp2 */
+ { NULL,
+ { PMDA_PMID(4,5), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d temp3 */
+ { NULL,
+ { PMDA_PMID(4,6), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d alarms */
+ { NULL,
+ { PMDA_PMID(4,7), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d beep */
+ { NULL,
+ { PMDA_PMID(4,8), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d VCore1 */
+ { NULL,
+ { PMDA_PMID(4,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d VCore2 */
+ { NULL,
+ { PMDA_PMID(4,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d p33V */
+ { NULL,
+ { PMDA_PMID(4,11), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d p5V */
+ { NULL,
+ { PMDA_PMID(4,12), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d p12V */
+ { NULL,
+ { PMDA_PMID(4,13), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d n12V */
+ { NULL,
+ { PMDA_PMID(4,14), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d n5V */
+ { NULL,
+ { PMDA_PMID(4,15), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* w83781d vid */
+ { NULL,
+ { PMDA_PMID(4,16), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 fan1 */
+ { NULL,
+ { PMDA_PMID(5,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 fan2 */
+ { NULL,
+ { PMDA_PMID(5,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 fan3 */
+ { NULL,
+ { PMDA_PMID(5,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 temp1 */
+ { NULL,
+ { PMDA_PMID(5,3), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 temp2 */
+ { NULL,
+ { PMDA_PMID(5,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 VCore1 */
+ { NULL,
+ { PMDA_PMID(5,5), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 VCore2 */
+ { NULL,
+ { PMDA_PMID(5,6), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 p33V */
+ { NULL,
+ { PMDA_PMID(5,7), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 p12V */
+ { NULL,
+ { PMDA_PMID(5,8), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 n12V */
+ { NULL,
+ { PMDA_PMID(5,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 vid */
+ { NULL,
+ { PMDA_PMID(5,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+/* mtp008 vtt */
+ { NULL,
+ { PMDA_PMID(5,11), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, },
+};
+
+
+w83781d get_w83781d()
+{
+ float f;
+ w83781d sensor= {0,0,0,0,00.01,00.01,00.01,0,0,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01};
+
+ if (schips.n_w83781d > 0) {
+
+/* fan1 */
+ get_file(schips.s_w83781d[0],"/fan1");
+ sensor.fan1 = get_int(buf,2);
+/* fan2 */
+ get_file(schips.s_w83781d[0],"/fan2");
+ sensor.fan2 = get_int(buf,2);
+/* fan3 */
+ get_file(schips.s_w83781d[0],"/fan3");
+ sensor.fan3 = get_int(buf,2);
+/* fan_div */
+ get_file(schips.s_w83781d[0],"/fan_div");
+ sensor.fan_div = get_int(buf,1);
+/* temp1 */
+ get_file(schips.s_w83781d[0],"/temp1");
+ sensor.temp1 = get_float(buf,3);
+/* temp2 */
+ get_file(schips.s_w83781d[0],"/temp2");
+ sensor.temp2 = get_float(buf,3);
+/* temp3 */
+ get_file(schips.s_w83781d[0],"/temp3");
+ sensor.temp3 = get_float(buf,3);
+/* alarms */
+ get_file(schips.s_w83781d[0],"/alarms");
+ sensor.alarms = get_int(buf,1);
+/* beep */
+ get_file(schips.s_w83781d[0],"/beep");
+ sensor.beep = get_int(buf,1);
+/* VCore1 */
+ get_file(schips.s_w83781d[0],"/in0");
+ sensor.VCore1 = get_float(buf,3);
+/* VCore2 */
+ get_file(schips.s_w83781d[0],"/in1");
+ sensor.VCore2 = get_float(buf,3);
+/* p33V */
+ get_file(schips.s_w83781d[0],"/in2");
+ sensor.p33V = get_float(buf,3);
+/* p5V */
+ get_file(schips.s_w83781d[0],"/in3");
+ f = get_float(buf,3);
+ sensor.p5V = f * ((6.80/10)+1);
+/* p12V */
+ get_file(schips.s_w83781d[0],"/in4");
+ f = get_float(buf,3);
+ sensor.p12V = f * ((28.00/10)+1);
+/* n12V */
+ get_file(schips.s_w83781d[0],"/in5");
+ f = get_float(buf,3);
+ sensor.n12V = -1 * f * (210/60.40);
+/* n5V */
+ get_file(schips.s_w83781d[0],"/in6");
+ f = get_float(buf,3);
+ sensor.n5V = -1 * f * (90.9/60.40);
+/* vid */
+ get_file(schips.s_w83781d[0],"/vid");
+ sensor.vid = get_float(buf,1);
+ }
+ return sensor;
+}
+
+mtp008 get_mtp008()
+{
+ float f;
+ mtp008 sensor= {0,0,0,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01};
+
+ if (schips.n_mtp008 > 0) {
+
+/* fan1 */
+ get_file(schips.s_mtp008[0],"/fan1");
+ sensor.fan1 = get_int(buf,2);
+/* fan2 */
+ get_file(schips.s_mtp008[0],"/fan2");
+ sensor.fan2 = get_int(buf,2);
+/* fan3 */
+ get_file(schips.s_mtp008[0],"/fan3");
+ sensor.fan3 = get_int(buf,2);
+/* temp1 */
+ get_file(schips.s_mtp008[0],"/temp1");
+ sensor.temp1 = get_float(buf,3);
+/* temp2 */
+ get_file(schips.s_mtp008[0],"/temp2");
+ sensor.temp2 = get_float(buf,3);
+/* VCore1 */
+ get_file(schips.s_mtp008[0],"/in0");
+ sensor.VCore1 = get_float(buf,3);
+/* VCore2 */
+ get_file(schips.s_mtp008[0],"/in3");
+ sensor.VCore2 = get_float(buf,3);
+/* p33V */
+ get_file(schips.s_mtp008[0],"/in1");
+ sensor.p33V = get_float(buf,3);
+/* p12V */
+ get_file(schips.s_mtp008[0],"/in2");
+ f = get_float(buf,3);
+ sensor.p12V = f * ((38.00/10)+1);
+/* n12V */
+ get_file(schips.s_mtp008[0],"/in5");
+ f = get_float(buf,3);
+ sensor.n12V = ( f * 36 - 118.61 ) / 7;
+/* vid */
+ get_file(schips.s_mtp008[0],"/vid");
+ sensor.vid = get_float(buf,1);
+/* vtt */
+ get_file(schips.s_mtp008[0],"/in6");
+ sensor.vid = get_float(buf,3);
+ }
+ return sensor;
+}
+
+lm79 get_lm79()
+{
+ float f;
+ lm79 sensor= {0,0,0,00.01,0,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01};
+
+ if (schips.n_lm79 > 0) {
+/* fan1 */
+ get_file(schips.s_lm79[0],"/fan1");
+ sensor.fan1 = get_int(buf,2);
+/* fan2 */
+ get_file(schips.s_lm79[0],"/fan2");
+ sensor.fan2 = get_int(buf,2);
+/* fan3 */
+ get_file(schips.s_lm79[0],"/fan3");
+ sensor.fan3 = get_int(buf,2);
+/* fan_div */
+ get_file(schips.s_lm79[0],"/fan_div");
+ sensor.fan_div = get_int(buf,1);
+/* temp */
+ get_file(schips.s_lm79[0],"/temp");
+ sensor.temp = get_float(buf,3);
+/* alarms */
+ get_file(schips.s_lm79[0],"/alarms");
+ sensor.alarms = get_int(buf,1);
+/* VCore1 */
+ get_file(schips.s_lm79[0],"/in0");
+ sensor.VCore1 = get_float(buf,3);
+/* VCore2 */
+ get_file(schips.s_lm79[0],"/in1");
+ sensor.VCore2 = get_float(buf,3);
+/* p33V */
+ get_file(schips.s_lm79[0],"/in2");
+ sensor.p33V = get_float(buf,3);
+/* p5V */
+ get_file(schips.s_lm79[0],"/in3");
+ f = get_float(buf,3);
+ sensor.p5V = f * ((6.80/10)+1);
+/* p12V */
+ get_file(schips.s_lm79[0],"/in4");
+ f = get_float(buf,3);
+ sensor.p12V = f * ((28.00/10)+1);
+/* n12V */
+ get_file(schips.s_lm79[0],"/in5");
+ f = get_float(buf,3);
+ sensor.n12V = -1 * f * (210/60.40);
+/* n5V */
+ get_file(schips.s_lm79[0],"/in6");
+ f = get_float(buf,3);
+ sensor.n5V = -1 * f * (90.9/60.40);
+/* vid */
+ get_file(schips.s_lm79[0],"/vid");
+ sensor.vid = get_float(buf,1);
+ }
+
+ return sensor;
+}
+
+lm87 get_lm87()
+{
+ lm87 sensor= {0,0,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01,00.01};
+
+ if (schips.n_lm87 > 0) {
+/* fan1 */
+ get_file(schips.s_lm87[0],"/fan");
+ sensor.fan1 = get_int(buf,2);
+/* fan2 */
+ get_file(schips.s_lm87[0],"/fan2");
+ sensor.fan2 = get_int(buf,2);
+/* temp1 */
+ get_file(schips.s_lm87[0],"/temp1");
+ sensor.temp1 = get_float(buf,3);
+/* CPUtemp */
+ get_file(schips.s_lm87[0],"/temp2");
+ sensor.CPUtemp = get_float(buf,3);
+/* Vccp1 */
+ get_file(schips.s_lm87[0],"/in1");
+ sensor.Vccp1 = get_float(buf,3);
+/* Vccp2 */
+ get_file(schips.s_lm87[0],"/in5");
+ sensor.Vccp2 = get_float(buf,3);
+/* p25V */
+ get_file(schips.s_lm87[0],"/in0");
+ sensor.p25V = get_float(buf,3);
+/* p33V */
+ get_file(schips.s_lm87[0],"/in2");
+ sensor.p33V = get_float(buf,3);
+/* p5V */
+ get_file(schips.s_lm87[0],"/in3");
+ sensor.p5V = get_float(buf,3);
+/* p12V */
+ get_file(schips.s_lm87[0],"/in4");
+ sensor.p12V = get_float(buf,3);
+/* vid */
+ get_file(schips.s_lm87[0],"/vid");
+ sensor.vid = get_float(buf,1);
+ }
+
+ return sensor;
+}
+
+lm75 get_lm75()
+{
+ lm75 sensor= {00.01};
+
+ if (schips.n_lm75 > 0) {
+ get_file(schips.s_lm75[0],"/temp");
+ sensor.temp = get_float(buf,3);
+ }
+ return sensor;
+}
+
+void
+get_chips()
+{
+ int i;
+ int n;
+ int nbufindex;
+ char *bufindex[64];
+ char *temp;
+
+ n = get_file("chips", "");
+
+ buf[sizeof(buf)-1] = '\0';
+
+ nbufindex = 0;
+ bufindex[nbufindex++] = &buf[0];
+ for (i=0; i < n; i++) {
+ if (buf[i] == '\n') {
+ buf[i] = '\0';
+ bufindex[nbufindex++] = buf + i + 1;
+ }
+ }
+
+ for ( i=0; i < nbufindex ; i++ ) {
+ temp="";
+ if (strncmp("lm75", bufindex[i]+4, 4) == 0 ) {
+ temp = strtok(bufindex[i]+4," ");
+ strcat(schips.s_lm75[schips.n_lm75], temp);
+ schips.total++;
+ schips.n_lm75++;
+ }
+ else if (strncmp("lm79", bufindex[i]+4, 4) == 0 ) {
+ temp = strtok(bufindex[i]+4," ");
+ strcat(schips.s_lm79[schips.n_lm79], temp);
+ schips.total++;
+ schips.n_lm79++;
+ }
+ else if (strncmp("lm87", bufindex[i]+4, 4) == 0 ) {
+ temp = strtok(bufindex[i]+4," ");
+ strcat(schips.s_lm87[schips.n_lm87], temp);
+ schips.total++;
+ schips.n_lm87++;
+ }
+ else if (strncmp("w83781d", bufindex[i]+4, 7) == 0 ) {
+ temp = strtok(bufindex[i]+4," ");
+ strcat(schips.s_w83781d[schips.n_w83781d], temp);
+ schips.total++;
+ schips.n_w83781d++;
+ }
+ else if (strncmp("mtp008", bufindex[i]+4, 6) == 0 ) {
+ temp = strtok(bufindex[i]+4," ");
+ strcat(schips.s_mtp008[schips.n_mtp008], temp);
+ schips.total++;
+ schips.n_mtp008++;
+ }
+ }
+}
+
+
+/*
+ * Get the contents of a file and return them
+ */
+int get_file(char *middle, char *end){
+
+ int fd;
+ int n;
+ char s[1024]="/proc/sys/dev/sensors/";
+
+/*
+ * create the new string, the end result being the actual file name
+ */
+ strcat(s,middle);
+ strcat(s,end);
+
+/*
+ * read in the file into the buffer buf
+ */
+ if ((fd = open(s, O_RDONLY)) < 0) {
+ return -1;
+ }
+ n = read(fd, buf, sizeof(buf));
+ close(fd);
+ return n;
+}
+
+/*
+ * Pull a certain float value out of a string of floats
+ */
+float get_float(char *s, int i){
+ char *temp;
+ float f;
+ int j;
+
+ temp = strtok(s," ");
+
+ for (j=1;j<i;j++)
+ temp = strtok(NULL," ");
+
+ f = atof(temp);
+ return f;
+}
+
+/*
+ * Pull a certain int value out of a string of int's
+ */
+int get_int(char *s, int i){
+ char *temp;
+ int f;
+ int j;
+
+ temp = strtok(s," ");
+
+ for (j=1;j<i;j++)
+ temp = strtok(NULL," ");
+
+ f = atoi(temp);
+ return f;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+lmsensors_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ mtp008 sensormtp008;
+ w83781d sensorw83781d;
+ lm87 sensor87;
+ lm79 sensor79;
+ lm75 sensor75;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster > 5)
+ return PM_ERR_PMID;
+ else if (inst != PM_IN_NULL)
+ return PM_ERR_INST;
+
+ if (idp->cluster == 0) { /*lmsensors*/
+ switch (idp->item) {
+ case 0:
+ atom->l = schips.total;
+ break ;
+ case 1:
+ atom->l = schips.n_lm75;
+ break ;
+ case 2:
+ atom->l = schips.n_lm79;
+ break ;
+ case 3:
+ atom->l = schips.n_lm87;
+ break ;
+ case 4:
+ atom->l = schips.n_w83781d;
+ break ;
+ case 5:
+ atom->l = schips.n_mtp008;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ if (idp->cluster == 1) { /*lmsensors.lm75*/
+ if (schips.n_lm75 > 0) {
+ sensor75=get_lm75();
+ switch (idp->item) {
+ case 0:
+ atom->f = sensor75.temp;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else atom->f=9999;
+ }
+ if (idp->cluster == 2) { /*lmsensors.lm79*/
+ if (schips.n_lm79 > 0) {
+ sensor79=get_lm79();
+ switch (idp->item) {
+ case 0:
+ atom->l = sensor79.fan1;
+ break ;
+ case 1:
+ atom->l = sensor79.fan2;
+ break ;
+ case 2:
+ atom->l = sensor79.fan3;
+ break ;
+ case 3:
+ atom->l = sensor79.fan_div;
+ break ;
+ case 4:
+ atom->f = sensor79.temp;
+ break ;
+ case 5:
+ atom->l = sensor79.alarms;
+ break ;
+ case 6:
+ atom->f = sensor79.VCore1;
+ break ;
+ case 7:
+ atom->f = sensor79.VCore2;
+ break ;
+ case 8:
+ atom->f = sensor79.p33V;
+ break ;
+ case 9:
+ atom->f = sensor79.p5V;
+ break ;
+ case 10:
+ atom->f = sensor79.p12V;
+ break ;
+ case 11:
+ atom->f = sensor79.n12V;
+ break ;
+ case 12:
+ atom->f = sensor79.n5V;
+ break ;
+ case 13:
+ atom->f = sensor79.vid;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else atom->f=9999;
+ }
+ if (idp->cluster == 3) { /*lmsensors.lm87*/
+ if (schips.n_lm87 > 0) {
+ sensor87=get_lm87();
+ switch (idp->item) {
+ case 0:
+ atom->l = sensor87.fan1;
+ break ;
+ case 1:
+ atom->l = sensor87.fan2;
+ break ;
+ case 2:
+ atom->f = sensor87.temp1;
+ break ;
+ case 3:
+ atom->f = sensor87.CPUtemp;
+ break ;
+ case 4:
+ atom->f = sensor87.Vccp1;
+ break ;
+ case 5:
+ atom->f = sensor87.Vccp2;
+ break ;
+ case 6:
+ atom->f = sensor87.p25V;
+ break ;
+ case 7:
+ atom->f = sensor87.p33V;
+ break ;
+ case 8:
+ atom->f = sensor87.p5V;
+ break ;
+ case 9:
+ atom->f = sensor87.p12V;
+ break ;
+ case 10:
+ atom->f = sensor87.vid;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else atom->f=9999;
+ }
+ if (idp->cluster == 4) { /*lmsensors.w83781d*/
+ if (schips.n_w83781d > 0) {
+ sensorw83781d=get_w83781d();
+ switch (idp->item) {
+ case 0:
+ atom->l = sensorw83781d.fan1;
+ break ;
+ case 1:
+ atom->l = sensorw83781d.fan2;
+ break ;
+ case 2:
+ atom->l = sensorw83781d.fan3;
+ break ;
+ case 3:
+ atom->l = sensorw83781d.fan_div;
+ break ;
+ case 4:
+ atom->f = sensorw83781d.temp1;
+ break ;
+ case 5:
+ atom->f = sensorw83781d.temp2;
+ break ;
+ case 6:
+ atom->f = sensorw83781d.temp3;
+ break ;
+ case 7:
+ atom->l = sensorw83781d.alarms;
+ break ;
+ case 8:
+ atom->l = sensorw83781d.beep;
+ break ;
+ case 9:
+ atom->f = sensorw83781d.VCore1;
+ break ;
+ case 10:
+ atom->f = sensorw83781d.VCore2;
+ break ;
+ case 11:
+ atom->f = sensorw83781d.p33V;
+ break ;
+ case 12:
+ atom->f = sensorw83781d.p5V;
+ break ;
+ case 13:
+ atom->f = sensorw83781d.p12V;
+ break ;
+ case 14:
+ atom->f = sensorw83781d.n12V;
+ break ;
+ case 15:
+ atom->f = sensorw83781d.n5V;
+ break ;
+ case 16:
+ atom->f = sensorw83781d.vid;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else atom->f=9999;
+ }
+ if (idp->cluster == 5) { /*lmsensors.mtp008*/
+ if (schips.n_mtp008 > 0) {
+ sensormtp008=get_mtp008();
+ switch (idp->item) {
+ case 0:
+ atom->l = sensormtp008.fan1;
+ break ;
+ case 1:
+ atom->l = sensormtp008.fan2;
+ break ;
+ case 2:
+ atom->l = sensormtp008.fan3;
+ break ;
+ case 3:
+ atom->f = sensormtp008.temp1;
+ break ;
+ case 4:
+ atom->f = sensormtp008.temp2;
+ break ;
+ case 5:
+ atom->f = sensormtp008.VCore1;
+ break ;
+ case 6:
+ atom->f = sensormtp008.VCore2;
+ break ;
+ case 7:
+ atom->f = sensormtp008.p33V;
+ break ;
+ case 8:
+ atom->f = sensormtp008.p12V;
+ break ;
+ case 9:
+ atom->f = sensormtp008.n12V;
+ break ;
+ case 10:
+ atom->f = sensormtp008.vid;
+ break ;
+ case 11:
+ atom->f = sensormtp008.vtt;
+ break ;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else atom->f=9999;
+ }
+
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+lmsensors_init(pmdaInterface *dp)
+{
+ get_chips();
+
+ __pmSetProcessIdentity(username);
+ pmdaSetFetchCallBack(dp, lmsensors_fetchCallBack);
+ pmdaInit(dp, NULL, 0,
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "lmsensors" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, LMSENSORS,
+ "lmsensors.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ lmsensors_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/lmsensors/lmsensors.h b/src/pmdas/lmsensors/lmsensors.h
new file mode 100644
index 0000000..14cbb9b
--- /dev/null
+++ b/src/pmdas/lmsensors/lmsensors.h
@@ -0,0 +1,113 @@
+/*
+ * Original implementation by Troy Dawson (dawson@fnal.gov)
+ *
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+typedef struct {
+ int total;
+ int n_lm75;
+ int n_lm79;
+ int n_lm87;
+ int n_w83781d;
+ int n_mtp008;
+ char s_lm75[2][1024] ;
+ char s_lm79[2][1024] ;
+ char s_lm87[2][1024] ;
+ char s_w83781d[2][1024] ;
+ char s_mtp008[2][1024] ;
+} chips;
+
+typedef struct {
+ float temp;
+} lm75;
+
+typedef struct {
+ int fan1;
+ int fan2;
+ int fan3;
+ int fan_div;
+ float temp;
+ int alarms;
+ float VCore1;
+ float VCore2;
+ float p33V;
+ float p5V;
+ float p12V;
+ float n12V;
+ float n5V;
+ float vid;
+} lm79;
+
+typedef struct {
+ int fan1;
+ int fan2;
+ float temp1;
+ float CPUtemp;
+ float Vccp1;
+ float Vccp2;
+ float p25V;
+ float p33V;
+ float p5V;
+ float p12V;
+ float vid;
+} lm87;
+
+typedef struct {
+ int fan1;
+ int fan2;
+ int fan3;
+ int fan_div;
+ float temp1;
+ float temp2;
+ float temp3;
+ int alarms;
+ int beep;
+ float VCore1;
+ float VCore2;
+ float p33V;
+ float p5V;
+ float p12V;
+ float n12V;
+ float n5V;
+ float vid;
+} w83781d;
+
+typedef struct {
+ int fan1;
+ int fan2;
+ int fan3;
+ float temp1;
+ float temp2;
+ float VCore1;
+ float VCore2;
+ float p33V;
+ float p12V;
+ float n12V;
+ float vid;
+ float vtt;
+} mtp008;
+
+extern void get_chips();
+extern lm75 get_lm75();
+extern lm79 get_lm79();
+extern lm87 get_lm87();
+extern w83781d get_w83781d();
+extern mtp008 get_mtp008();
+extern int get_file(char *, char *);
+extern int get_int(char * , int);
+extern float get_float(char * , int);
diff --git a/src/pmdas/lmsensors/pmns b/src/pmdas/lmsensors/pmns
new file mode 100644
index 0000000..2a41f07
--- /dev/null
+++ b/src/pmdas/lmsensors/pmns
@@ -0,0 +1,106 @@
+/*
+ * Metrics for lmsensors PMDA
+ *
+ * Original implementation by Troy Dawson (dawson@fnal.gov)
+ *
+ * Copyright (c) 2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+lmsensors {
+ n_total LMSENSORS:0:0
+ n_lm75 LMSENSORS:0:1
+ n_lm79 LMSENSORS:0:2
+ n_lm87 LMSENSORS:0:3
+ n_w83781d LMSENSORS:0:4
+ n_mtp008 LMSENSORS:0:5
+ lm75
+ lm79
+ lm87
+ w83781d
+ mtp008
+}
+
+lmsensors.lm75 {
+ temp LMSENSORS:1:0
+}
+
+lmsensors.lm79 {
+ fan1 LMSENSORS:2:0
+ fan2 LMSENSORS:2:1
+ fan3 LMSENSORS:2:2
+ fan_div LMSENSORS:2:3
+ temp LMSENSORS:2:4
+ alarms LMSENSORS:2:5
+ VCore1 LMSENSORS:2:6
+ VCore2 LMSENSORS:2:7
+ p33V LMSENSORS:2:8
+ p5V LMSENSORS:2:9
+ p12V LMSENSORS:2:10
+ n12V LMSENSORS:2:11
+ n5V LMSENSORS:2:12
+ vid LMSENSORS:2:13
+}
+
+lmsensors.lm87 {
+ fan1 LMSENSORS:3:0
+ fan2 LMSENSORS:3:1
+ temp1 LMSENSORS:3:2
+ CPUtemp LMSENSORS:3:3
+ Vccp1 LMSENSORS:3:4
+ Vccp2 LMSENSORS:3:5
+ p25V LMSENSORS:3:6
+ p33V LMSENSORS:3:7
+ p5V LMSENSORS:3:8
+ p12V LMSENSORS:3:9
+ vid LMSENSORS:3:10
+}
+
+lmsensors.w83781d {
+ fan1 LMSENSORS:4:0
+ fan2 LMSENSORS:4:1
+ fan3 LMSENSORS:4:2
+ fan_div LMSENSORS:4:3
+ temp1 LMSENSORS:4:4
+ temp2 LMSENSORS:4:5
+ temp3 LMSENSORS:4:6
+ alarms LMSENSORS:4:7
+ beep LMSENSORS:4:8
+ VCore1 LMSENSORS:4:9
+ VCore2 LMSENSORS:4:10
+ p33V LMSENSORS:4:11
+ p5V LMSENSORS:4:12
+ p12V LMSENSORS:4:13
+ n12V LMSENSORS:4:14
+ n5V LMSENSORS:4:15
+ vid LMSENSORS:4:16
+}
+
+lmsensors.mtp008 {
+ fan1 LMSENSORS:5:0
+ fan2 LMSENSORS:5:1
+ fan3 LMSENSORS:5:2
+ temp1 LMSENSORS:5:3
+ temp2 LMSENSORS:5:4
+ VCore1 LMSENSORS:5:5
+ VCore2 LMSENSORS:5:6
+ p33V LMSENSORS:5:7
+ p12V LMSENSORS:5:8
+ n12V LMSENSORS:5:9
+ vid LMSENSORS:5:10
+ vtt LMSENSORS:5:11
+}
+
diff --git a/src/pmdas/lmsensors/root b/src/pmdas/lmsensors/root
new file mode 100644
index 0000000..b17d559
--- /dev/null
+++ b/src/pmdas/lmsensors/root
@@ -0,0 +1,12 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ *
+ * Original implementation by Troy Dawson (dawson@fnal.gov)
+ */
+
+#include <stdpmid>
+
+root { lmsensors }
+
+#include "pmns"
+
diff --git a/src/pmdas/logger/GNUmakefile b/src/pmdas/logger/GNUmakefile
new file mode 100644
index 0000000..e5b52f1
--- /dev/null
+++ b/src/pmdas/logger/GNUmakefile
@@ -0,0 +1,56 @@
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2011 Nathan Scott. All Rights Reversed.
+# Copyright (c) 2011-2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmdalogger$(EXECSUFFIX)
+DFILES = README
+CFILES = event.c util.c logger.c
+HFILES = event.h util.h
+LLDLIBS = $(PCP_PMDALIB)
+LSRCFILES = Install Remove pmns help $(DFILES) root
+
+IAM = logger
+DOMAIN = LOGGER
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h *.o $(IAM).log $(CMDTARGET)
+
+default: domain.h $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET)
+
+logger.o: domain.h
+event.o logger.o: event.h
+util.o event.o logger.o: util.h
+
+.NOTPARALLEL:
+.ORDER: domain.h $(OBJECTS)
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/logger/Install b/src/pmdas/logger/Install
new file mode 100644
index 0000000..10deb91
--- /dev/null
+++ b/src/pmdas/logger/Install
@@ -0,0 +1,164 @@
+#! /bin/sh
+#
+# Copyright (c) 2011-2012 Red Hat.
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the logger PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=logger
+pmda_interface=5
+forced_restart=false
+
+pmdaSetup
+
+# be careful that mortals cannot write any configuration files, as
+# these would present a security problem
+#
+umask 022
+
+# PMDA variables
+#
+configfile=""
+
+_parsedefaults()
+{
+ echo "Extracting options from current installation ..."
+ while getopts D:d:l c
+ do
+ case $c in
+ \?) echo "Warning: Unrecognized option in $PCP_PMCDCONF_PATH"
+ echo " Remove line for the $iam PMDA in $PCP_PMCDCONF_PATH and re-run ./Install"
+ exit 2;;
+ * ) ;;
+ esac
+ done
+ eval configfile='$'$OPTIND
+}
+
+# Get logfile(s) to monitor
+if $do_pmda
+then
+ # set options from $PCP_PMCDCONF_PATH, if possible
+ #
+ ans=`$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+$1 == "'$iam'" { printf "%s",$6
+ for (i=7;i<=NF;i++) printf " %s",$i
+ print ""
+ }'`
+ if [ ! -z "$ans" ]
+ then
+ _parsedefaults $ans
+ fi
+
+ # go figure out which configuration file to use ...
+ #
+ #default_configfile=./sample.conf
+ default_configfile=''
+ pmdaChooseConfigFile
+ if [ ! -f "$configfile" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to enter logfile names and paths manually? [y] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = "Xy" -o "X$ans" = "XY" -o -z "$ans" ]
+ then
+ configfile="$configdir/$iam.conf"
+ if [ -f "$configfile" ]
+ then
+ echo "Removing old configuration file \"$configfile\""
+ rm -f "$configfile"
+ if [ -f "$configfile" ]
+ then
+ echo "Cannot remove \"$configfile\""
+ exit 1
+ fi
+ fi
+
+ echo
+ echo \
+'Enter the PMNS name and logfile path. If the path ends in "|", the
+filename is interpreted as a command which will output data.
+
+An empty line terminates the logfile selection process and there must
+be at least one logfile specified. '
+
+ args=""
+ touch "$configfile"
+ if [ ! -f "$configfile" ]
+ then
+ echo "Installation aborted."
+ exit 1
+ fi
+
+ while [ ! -s "$configfile" ]
+ do
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Logfile PMNS name: ""$PCP_ECHO_C"
+ read name
+ [ -z "$name" ] && break
+
+ # Check name for invalid chars.
+ if ! echo $name | grep "^[A-Za-z][_A-Za-z0-9]*$" > /dev/null
+ then
+ echo \
+"Invalid characters in PMNS name: \"$name\".
+Names must start with an alphabetic character ([a-zA-Z]). The rest of
+the characters in the name must be alphanumeric ([a-zA-Z0-9]) or an
+underscore ('_')."
+ continue
+ fi
+
+ # Make sure name isn't already in the logfile.
+ if grep "^${name}[ \t]" "$configfile" >/dev/null
+ then
+ echo "Sorry, logfile PMNS name \"$name\" already specified. Please try again."
+ continue
+ fi
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Logfile pathname: ""$PCP_ECHO_C"
+ read pathname
+ [ -z "$pathname" ] && break
+ if grep "[ \t]${pathname}$" "$configfile" >/dev/null
+ then
+ echo "Sorry, pathname \"$pathname\" already specified. Please try again."
+ continue
+ fi
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Restricted access [n]: ""$PCP_ECHO_C"
+ read restrict
+ if [ "$restrict" = "y" -o "$restrict" = "yes" ]; then
+ restrict="y"
+ else
+ restrict="n"
+ fi
+
+ echo "$name $restrict $pathname" >>"$configfile"
+ done
+ done
+ else
+ echo ""
+ echo "Error: Abandoning installation as no configuration file was specified."
+ exit 1
+ fi
+ fi
+
+ args="$configfile"
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/logger/README b/src/pmdas/logger/README
new file mode 100644
index 0000000..a19eeaf
--- /dev/null
+++ b/src/pmdas/logger/README
@@ -0,0 +1,51 @@
+Logger PMDA
+===========
+
+This PMDA exports information about the event status of log files
+specified in a config file. The default configuration file is
+$PCP_VAR_DIR/config/logger/logger.conf, which should contain one
+line for each file you wish to monitor.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT logger
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/logger
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/logger
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/logger.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/logger/Remove b/src/pmdas/logger/Remove
new file mode 100644
index 0000000..6c014ef
--- /dev/null
+++ b/src/pmdas/logger/Remove
@@ -0,0 +1,24 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2011 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the logger PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=logger
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/logger/event.c b/src/pmdas/logger/event.c
new file mode 100644
index 0000000..19ffda9
--- /dev/null
+++ b/src/pmdas/logger/event.c
@@ -0,0 +1,493 @@
+/*
+ * Event support for the Logger PMDA
+ *
+ * Copyright (c) 2011-2012 Red Hat.
+ * Copyright (c) 2011 Nathan Scott. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "event.h"
+#include "pmda.h"
+#include "util.h"
+#include <ctype.h>
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+
+static int numlogfiles;
+static event_logfile_t *logfiles;
+
+void
+event_init(pmID pmid)
+{
+ char cmd[MAXPATHLEN];
+ int i, fd;
+
+ for (i = 0; i < numlogfiles; i++) {
+ size_t pathlen = strlen(logfiles[i].pathname);
+
+ /*
+ * We support 2 kinds of PATHNAMEs:
+ * (1) Regular paths. These paths are opened normally.
+ * (2) Pipes. If the path ends in '|', the filename is
+ * interpreted as a command which pipes input to us.
+ */
+ if (logfiles[i].pathname[pathlen - 1] != '|') {
+ fd = open(logfiles[i].pathname, O_RDONLY|O_NONBLOCK);
+ if (fd < 0) {
+ if (logfiles[i].fd >= 0) /* log once only */
+ __pmNotifyErr(LOG_ERR, "open: %s - %s",
+ logfiles[i].pathname, strerror(errno));
+ } else {
+ if (fstat(fd, &logfiles[i].pathstat) < 0)
+ if (logfiles[i].fd >= 0) /* log once only */
+ __pmNotifyErr(LOG_ERR, "fstat: %s - %s",
+ logfiles[i].pathname, strerror(errno));
+ lseek(fd, 0, SEEK_END);
+ }
+ }
+ else {
+ strncpy(cmd, logfiles[i].pathname, sizeof(cmd));
+ cmd[pathlen - 1] = '\0'; /* get rid of the '|' */
+ rstrip(cmd); /* Remove all trailing whitespace. */
+ fd = start_cmd(cmd, &logfiles[i].pid);
+ if (fd < 0) {
+ if (logfiles[i].fd >= 0) /* log once only */
+ __pmNotifyErr(LOG_ERR, "pipe: %s - %s",
+ logfiles[i].pathname, strerror(errno));
+ } else {
+ if (fd > maxfd)
+ maxfd = fd;
+ FD_SET(fd, &fds);
+ }
+ }
+
+ logfiles[i].fd = fd; /* keep file descriptor (or error) */
+ logfiles[i].pmid = pmid; /* string param metric identifier */
+ logfiles[i].queueid = pmdaEventNewQueue(logfiles[i].pmnsname, maxmem);
+ }
+}
+
+void
+event_shutdown(void)
+{
+ int i;
+
+ __pmNotifyErr(LOG_INFO, "%s: Shutting down...", __FUNCTION__);
+
+ for (i = 0; i < numlogfiles; i++) {
+ if (logfiles[i].pid != 0) {
+ stop_cmd(logfiles[i].pid);
+ logfiles[i].pid = 0;
+ }
+ if (logfiles[i].fd > 0) {
+ close(logfiles[i].fd);
+ logfiles[i].fd = 0;
+ }
+ }
+}
+
+/*
+ * Ensure given name (identifier) can be used as a namespace entry.
+ */
+static int
+valid_pmns_name(char *name)
+{
+ if (!isalpha((int)name[0]))
+ return 0;
+ for (; *name != '\0'; name++)
+ if (!isalnum((int)*name) && *name != '_')
+ return 0;
+ return 1;
+}
+
+/*
+ * Parse the configuration file and do initial data structure setup.
+ */
+int
+event_config(const char *fname)
+{
+ FILE *configFile;
+ event_logfile_t *logfile;
+ int sts = 0;
+ size_t len;
+ char line[MAXPATHLEN * 2];
+ char *ptr, *name, *noaccess;
+
+ configFile = fopen(fname, "r");
+ if (configFile == NULL) {
+ __pmNotifyErr(LOG_ERR, "event_config: %s: %s", fname, strerror(errno));
+ return -1;
+ }
+
+ while (!feof(configFile)) {
+ if (fgets(line, sizeof(line), configFile) == NULL) {
+ if (feof(configFile))
+ break;
+ __pmNotifyErr(LOG_ERR, "event_config: fgets: %s", strerror(errno));
+ sts = -1;
+ break;
+ }
+
+ /*
+ * fgets() puts the '\n' at the end of the buffer. Remove
+ * it. If it isn't there, that must mean that the line is
+ * longer than our buffer.
+ */
+ len = strlen(line);
+ if (len == 0) /* Ignore empty strings. */
+ continue;
+ if (line[len - 1] != '\n') { /* String must be too long */
+ __pmNotifyErr(LOG_ERR, "event_config: config line too long: '%s'",
+ line);
+ sts = -1;
+ break;
+ }
+ line[len - 1] = '\0'; /* Remove the '\n'. */
+
+ /* Strip all trailing whitespace. */
+ rstrip(line);
+
+ /* If the string is now empty or a comment, just ignore the line. */
+ len = strlen(line);
+ if (len == 0)
+ continue;
+ if (line[0] == '#')
+ continue;
+
+ /* Skip past all leading whitespace to find the start of
+ * NAME. */
+ ptr = name = lstrip(line);
+
+ /* Now we need to split the line into 3 parts: NAME, ACCESS
+ * and PATHNAME. NAME can't have whitespace in it, so look
+ * for the first non-whitespace. */
+ while (*ptr != '\0' && ! isspace((int)*ptr)) {
+ ptr++;
+ }
+ /* If we're at the end, we didn't find any whitespace, so
+ * we've only got a NAME, with no ACCESS/PATHNAME. */
+ if (*ptr == '\0') {
+ __pmNotifyErr(LOG_ERR, "event_config: badly formatted "
+ " configuration file line: '%s'", line);
+ sts = -1;
+ break;
+ }
+ /* Terminate NAME at the 1st whitespace. */
+ *ptr++ = '\0';
+
+ /* Make sure NAME isn't too long. */
+ if (strlen(name) > MAXPATHLEN) {
+ __pmNotifyErr(LOG_ERR, "event_config: name too long: '%s'", name);
+ sts = -1;
+ break;
+ }
+
+ /* Make sure NAME is valid. */
+ if (valid_pmns_name(name) == 0) {
+ __pmNotifyErr(LOG_ERR, "event_config: invalid name: '%s'", name);
+ sts = -1;
+ break;
+ }
+
+ /* Skip past any extra whitespace between NAME and ACCESS */
+ ptr = noaccess = lstrip(ptr);
+
+ /* Look for the next whitespace, and that terminate ACCESS */
+ while (*ptr != '\0' && ! isspace((int)*ptr)) {
+ ptr++;
+ }
+
+ /* If we're at the end, we didn't find any whitespace, so
+ * we've only got NAME and ACCESS with no/PATHNAME. */
+ if (*ptr == '\0') {
+ __pmNotifyErr(LOG_ERR, "event_config: badly formatted "
+ " configuration file line: '%s'", line);
+ sts = -1;
+ break;
+ }
+ /* Terminate ACCESS at the 1st whitespace. */
+ *ptr++ = '\0';
+
+ /* Skip past any extra whitespace between ACCESS and PATHNAME */
+ ptr = lstrip(ptr);
+
+ /* Make sure PATHNAME (the rest of the line) isn't too long. */
+ if (strlen(ptr) > MAXPATHLEN) {
+ __pmNotifyErr(LOG_ERR, "event_config: path is too long: '%s'", ptr);
+ sts = -1;
+ break;
+ }
+
+ /* Now we've got a reasonable NAME/ACCESS/PATHNAME. Save them. */
+ len = (numlogfiles + 1) * sizeof(event_logfile_t);
+ logfiles = realloc(logfiles, len);
+ if (logfiles == NULL) {
+ __pmNoMem("event_config", len, PM_FATAL_ERR);
+ sts = -1;
+ break;
+ }
+ logfile = &logfiles[numlogfiles];
+ memset(logfile, 0, sizeof(*logfile));
+ logfile->noaccess = (noaccess[0] == 'y' || noaccess[0] == 'Y');
+ strncpy(logfile->pmnsname, name, sizeof(logfile->pmnsname));
+ logfile->pmnsname[sizeof(logfile->pmnsname)-1] = '\0';
+ strncpy(logfile->pathname, ptr, sizeof(logfile->pathname));
+ logfile->pathname[sizeof(logfile->pathname)-1] = '\0';
+ /* remaining fields filled in after pmdaInit() is called. */
+ numlogfiles++;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "event_config: new logfile %s (%s)",
+ logfile->pathname, logfile->pmnsname);
+ }
+
+ fclose(configFile);
+ if (sts < 0) {
+ free(logfiles);
+ return sts;
+ }
+ if (numlogfiles == 0) {
+ __pmNotifyErr(LOG_ERR, "event_config: no valid log files found");
+ return -1;
+ }
+ return numlogfiles;
+}
+
+static int
+event_create(event_logfile_t *logfile)
+{
+ int j;
+ char *s, *p;
+ size_t offset;
+ ssize_t bytes;
+ struct timeval timestamp;
+
+ static char *buffer;
+ static int bufsize;
+
+ /*
+ * Using a static (global) event buffer to hold initial read.
+ * The aim is to reduce memory allocation until we know we'll
+ * need to keep something.
+ */
+ if (!buffer) {
+ int sts = 0;
+ bufsize = 16 * getpagesize();
+#ifdef HAVE_POSIX_MEMALIGN
+ sts = posix_memalign((void **)&buffer, getpagesize(), bufsize);
+#else
+#ifdef HAVE_MEMALIGN
+ buffer = (char *)memalign(getpagesize(), bufsize);
+ if (buffer == NULL) sts = -1;
+#else
+ buffer = (char *)malloc(bufsize);
+ if (buffer == NULL) sts = -1;
+#endif
+#endif
+ if (sts != 0) {
+ __pmNotifyErr(LOG_ERR, "event buffer allocation failure");
+ return -1;
+ }
+ }
+
+ offset = 0;
+multiread:
+ if (logfile->fd < 0)
+ return 0;
+ bytes = read(logfile->fd, buffer + offset, bufsize - 1 - offset);
+ /*
+ * Ignore the error if:
+ * - we've got EOF (0 bytes read)
+ * - EBADF (fd isn't valid - most likely a closed pipe)
+ * - EAGAIN/EWOULDBLOCK (fd is marked nonblocking and read would block)
+ * - EINVAL/EISDIR (fd is a directory - config file botch)
+ */
+ if (bytes == 0)
+ return 0;
+ if (bytes < 0 && (errno == EBADF || errno == EISDIR || errno == EINVAL))
+ return 0;
+ if (bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
+ return 0;
+ if (bytes > maxmem)
+ return 0;
+ if (bytes < 0) {
+ __pmNotifyErr(LOG_ERR, "read failure on %s: %s",
+ logfile->pathname, strerror(errno));
+ return -1;
+ }
+
+ gettimeofday(&timestamp, NULL);
+ buffer[bufsize-1] = '\0';
+ for (s = p = buffer, j = 0; *s != '\0' && j < bufsize-1; s++, j++) {
+ if (*s != '\n')
+ continue;
+ *s = '\0';
+ bytes = (s+1) - p;
+ pmdaEventQueueAppend(logfile->queueid, p, bytes, &timestamp);
+ p = s + 1;
+ }
+ /* did we just do a full buffer read? */
+ if (p == buffer) {
+ char msg[64];
+ __pmNotifyErr(LOG_ERR, "Ignoring long (%d bytes) line: \"%s\"", (int)
+ bytes, __pmdaEventPrint(p, bytes, msg, sizeof(msg)));
+ } else if (j == bufsize - 1) {
+ offset = bufsize-1 - (p - buffer);
+ memmove(buffer, p, offset);
+ goto multiread; /* read rest of line */
+ }
+ return 1;
+}
+
+void
+event_refresh(void)
+{
+ struct event_logfile *logfile;
+ struct stat pathstat;
+ int i, fd, sts;
+
+ for (i = 0; i < numlogfiles; i++) {
+ logfile = &logfiles[i];
+
+ if (logfile->pid > 0) /* process pipe */
+ goto events;
+ if (stat(logfile->pathname, &pathstat) < 0) {
+ if (logfile->fd >= 0) {
+ close(logfile->fd);
+ logfile->fd = -1;
+ }
+ memset(&logfile->pathstat, 0, sizeof(logfile->pathstat));
+ } else {
+ /* reopen if no descriptor before, or log rotated (new file) */
+ if (logfile->fd < 0 ||
+ logfile->pathstat.st_ino != pathstat.st_ino ||
+ logfile->pathstat.st_dev != pathstat.st_dev) {
+ if (logfile->fd >= 0)
+ close(logfile->fd);
+ fd = open(logfile->pathname, O_RDONLY|O_NONBLOCK);
+ if (fd < 0 && logfile->fd >= 0) /* log once */
+ __pmNotifyErr(LOG_ERR, "open: %s - %s",
+ logfile->pathname, strerror(errno));
+ logfile->fd = fd;
+ } else {
+ if ((S_ISREG(pathstat.st_mode)) &&
+ (memcmp(&logfile->pathstat.st_mtime, &pathstat.st_mtime,
+ sizeof(pathstat.st_mtime))) == 0)
+ continue;
+ }
+ logfile->pathstat = pathstat;
+events:
+ do {
+ sts = event_create(logfile);
+ } while (sts != 0);
+ }
+ }
+}
+
+int
+event_logcount(void)
+{
+ return numlogfiles;
+}
+
+int
+event_queueid(int handle)
+{
+ if (handle < 0 || handle >= numlogfiles)
+ return 0;
+
+ /* if logfile unrestricted, allow this client access to this queue */
+ if (logfiles[handle].noaccess == 0)
+ pmdaEventSetAccess(pmdaGetContext(), logfiles[handle].queueid, 1);
+
+ return logfiles[handle].queueid;
+}
+
+__uint64_t
+event_pathsize(int handle)
+{
+ if (handle < 0 || handle >= numlogfiles)
+ return 0;
+ return logfiles[handle].pathstat.st_size;
+}
+
+const char *
+event_pathname(int handle)
+{
+ if (handle < 0 || handle >= numlogfiles)
+ return NULL;
+ return logfiles[handle].pathname;
+}
+
+const char *
+event_pmnsname(int handle)
+{
+ if (handle < 0 || handle >= numlogfiles)
+ return NULL;
+ return logfiles[handle].pmnsname;
+}
+
+pmID
+event_pmid(int handle)
+{
+ if (handle < 0 || handle >= numlogfiles)
+ return 0;
+ return logfiles[handle].pmid;
+}
+
+int
+event_decoder(int eventarray, void *buffer, size_t size,
+ struct timeval *timestamp, void *data)
+{
+ int sts, handle = *(int *)data;
+ pmID pmid = event_pmid(handle);
+ pmAtomValue atom;
+
+ sts = pmdaEventAddRecord(eventarray, timestamp, PM_EVENT_FLAG_POINT);
+ if (sts < 0)
+ return sts;
+ atom.cp = buffer;
+ sts = pmdaEventAddParam(eventarray, pmid, PM_TYPE_STRING, &atom);
+ if (sts < 0)
+ return sts;
+ return 1; /* simple decoder, added just one event array */
+}
+
+int
+event_regex_apply(void *rp, void *data, size_t size)
+{
+ regex_t *regex = (regex_t *)rp;
+ return regexec(regex, data, 0, NULL, 0) == REG_NOMATCH;
+}
+
+void
+event_regex_release(void *rp)
+{
+ regex_t *regex = (regex_t *)rp;
+ regfree(regex);
+}
+
+int
+event_regex_alloc(const char *string, void **filter)
+{
+ regex_t *regex = malloc(sizeof(regex_t));
+
+ if (regex == NULL)
+ return -ENOMEM;
+ if (regcomp(regex, string, REG_EXTENDED|REG_NOSUB) != 0) {
+ free(regex);
+ return PM_ERR_CONV;
+ }
+ *filter = (void *)regex;
+ return 0;
+}
diff --git a/src/pmdas/logger/event.h b/src/pmdas/logger/event.h
new file mode 100644
index 0000000..b1f176f
--- /dev/null
+++ b/src/pmdas/logger/event.h
@@ -0,0 +1,56 @@
+/*
+ * Event support for the Logger PMDA
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Nathan Scott. All rights reversed.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _EVENT_H
+#define _EVENT_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include <sys/stat.h>
+
+typedef struct event_logfile {
+ pmID pmid;
+ int fd;
+ pid_t pid;
+ int queueid;
+ int noaccess;
+ struct stat pathstat;
+ char pmnsname[MAXPATHLEN];
+ char pathname[MAXPATHLEN];
+} event_logfile_t;
+
+extern int maxfd;
+extern fd_set fds;
+extern long maxmem;
+
+extern void event_init(pmID pmid);
+extern void event_shutdown(void);
+extern void event_refresh(void);
+extern int event_config(const char *filename);
+
+extern int event_logcount(void);
+extern pmID event_pmid(int handle);
+extern int event_queueid(int handle);
+extern __uint64_t event_pathsize(int handle);
+extern const char *event_pathname(int handle);
+extern const char *event_pmnsname(int handle);
+extern int event_decoder(int arrayid, void *buffer, size_t size,
+ struct timeval *timestamp, void *data);
+extern int event_regex_alloc(const char *s, void **filter);
+extern int event_regex_apply(void *rp, void *data, size_t size);
+extern void event_regex_release(void *rp);
+
+#endif /* _EVENT_H */
diff --git a/src/pmdas/logger/help b/src/pmdas/logger/help
new file mode 100644
index 0000000..b7c1c6c
--- /dev/null
+++ b/src/pmdas/logger/help
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# logger PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ logger.numclients The number of attached clients
+The number of attached clients.
+@ logger.numlogfiles The number of monitored logfiles
+The number of monitored logfiles.
+@ logger.param_string String event data
+String event data.
+@ logger.maxmem Maximum number of queued event bytes
+Maximum number of queued event bytes for each log file.
diff --git a/src/pmdas/logger/logger.c b/src/pmdas/logger/logger.c
new file mode 100644
index 0000000..98689d7
--- /dev/null
+++ b/src/pmdas/logger/logger.c
@@ -0,0 +1,587 @@
+/*
+ * Logger, a configurable log file monitoring PMDA
+ *
+ * Copyright (c) 2011-2012 Red Hat.
+ * Copyright (c) 2011 Nathan Scott. All Rights Reserved.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Debug options
+ * APPL0 configfile processing and PMNS setup
+ * APPL1 loading event data from the log files
+ * APPL2 interaction with PMCD
+ */
+
+#include "domain.h"
+#include "event.h"
+#include "util.h"
+#include "pmda.h"
+
+/*
+ * Logger PMDA
+ *
+ * Metrics
+ * logger.numclients - number of attached clients
+ * logger.numlogfiles - number of monitored logfiles
+ * logger.param_string - string event data
+ * logger.perfile.{LOGFILE}.count - observed event count
+ * logger.perfile.{LOGFILE}.bytes - observed events size
+ * logger.perfile.{LOGFILE}.size - logfile size
+ * logger.perfile.{LOGFILE}.path - logfile path
+ * logger.perfile.{LOGFILE}.numclients - number of attached
+ * clients/logfile
+ * logger.perfile.{LOGFILE}.records - event records/logfile
+ */
+
+#define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */
+long maxmem;
+
+int maxfd;
+fd_set fds;
+static int interval_expired;
+static struct timeval interval = { 2, 0 };
+static char *username;
+
+static int nummetrics;
+static __pmnsTree *pmns;
+
+typedef struct dynamic_metric_info {
+ int handle;
+ int pmid_index;
+ const char *help_text;
+} dynamic_metric_info_t;
+static dynamic_metric_info_t *dynamic_metric_infotab;
+
+static pmdaMetric dynamic_metrictab[] = {
+/* perfile.{LOGFILE}.count */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* perfile.{LOGFILE}.bytes */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* perfile.{LOGFILE}.size */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* perfile.{LOGFILE}.path */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* perfile.{LOGFILE}.numclients */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* perfile.{LOGFILE}.records */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_EVENT, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* perfile.{LOGFILE}.queuemem */
+ { NULL, /* m_user gets filled in later */
+ { 0 /* pmid gets filled in later */, PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+};
+
+static char *dynamic_nametab[] = {
+/* perfile.{LOGFILE}.count */
+ "count",
+/* perfile.{LOGFILE}.bytes */
+ "bytes",
+/* perfile.{LOGFILE}.size */
+ "size",
+/* perfile.{LOGFILE}.path */
+ "path",
+/* perfile.{LOGFILE}.numclients */
+ "numclients",
+/* perfile.{LOGFILE}.records */
+ "records",
+/* perfile.{LOGFILE}.queuemem */
+ "queuemem",
+};
+
+static const char *dynamic_helptab[] = {
+/* perfile.{LOGFILE}.count */
+ "The cumulative number of events seen for this logfile.",
+/* perfile.{LOGFILE}.bytes */
+ "Cumulative number of bytes in events seen for this logfile.",
+/* perfile.{LOGFILE}.size */
+ "The current size of this logfile.",
+/* perfile.{LOGFILE}.path */
+ "The path for this logfile.",
+/* perfile.{LOGFILE}.numclients */
+ "The number of attached clients for this logfile.",
+/* perfile.{LOGFILE}.records */
+ "Event records for this logfile.",
+/* perfile.{LOGFILE}.queuemem */
+ "Amount of memory used for event data.",
+};
+
+static pmdaMetric static_metrictab[] = {
+/* numclients */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* numlogfiles */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* param_string */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* perfile.maxmem */
+ { NULL, /* m_user gets filled in later */
+ { PMDA_PMID(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+};
+
+static pmdaMetric *metrictab;
+
+static int
+logger_profile(__pmProfile *prof, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return 0;
+}
+
+static int
+logger_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+valid_pmid(unsigned int cluster, unsigned int item)
+{
+ if (cluster != 0 || item > nummetrics)
+ return PM_ERR_PMID;
+ return 0;
+}
+
+static int
+logger_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int sts;
+
+ if ((sts = valid_pmid(idp->cluster, idp->item)) < 0)
+ return sts;
+
+ sts = PMDA_FETCH_STATIC;
+ if (idp->item < 4) {
+ switch (idp->item) {
+ case 0: /* logger.numclients */
+ sts = pmdaEventClients(atom);
+ break;
+ case 1: /* logger.numlogfiles */
+ atom->ul = event_logcount();
+ break;
+ case 2: /* logger.param_string */
+ sts = PMDA_FETCH_NOVALUES;
+ break;
+ case 3: /* logger.maxmem */
+ atom->ull = (unsigned long long)maxmem;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ else {
+ dynamic_metric_info_t *pinfo;
+ int queue;
+
+ if ((pinfo = ((mdesc != NULL) ? mdesc->m_user : NULL)) == NULL)
+ return PM_ERR_PMID;
+ queue = event_queueid(pinfo->handle);
+
+ switch (pinfo->pmid_index) {
+ case 0: /* perfile.{LOGFILE}.count */
+ sts = pmdaEventQueueCounter(queue, atom);
+ break;
+ case 1: /* perfile.{LOGFILE}.bytes */
+ sts = pmdaEventQueueBytes(queue, atom);
+ break;
+ case 2: /* perfile.{LOGFILE}.size */
+ atom->ull = event_pathsize(pinfo->handle);
+ break;
+ case 3: /* perfile.{LOGFILE}.path */
+ atom->cp = (char *)event_pathname(pinfo->handle);
+ break;
+ case 4: /* perfile.{LOGFILE}.numclients */
+ sts = pmdaEventQueueClients(queue, atom);
+ break;
+ case 5: /* perfile.{LOGFILE}.records */
+ sts = pmdaEventQueueRecords(queue, atom, pmdaGetContext(),
+ event_decoder, &pinfo->handle);
+ break;
+ case 6: /* perfile.{LOGFILE}.queuemem */
+ sts = pmdaEventQueueMemory(queue, atom);
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ return sts;
+}
+
+static int
+logger_store(pmResult *result, pmdaExt *pmda)
+{
+ int i, j, sts;
+
+ pmdaEventNewClient(pmda->e_context);
+
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+ __pmID_int *idp = (__pmID_int *)&vsp->pmid;
+ dynamic_metric_info_t *pinfo = NULL;
+ void *filter;
+ int queueid;
+
+ if ((sts = valid_pmid(idp->cluster, idp->item)) < 0)
+ return sts;
+ for (j = 0; j < pmda->e_nmetrics; j++) {
+ if (vsp->pmid == pmda->e_metrics[j].m_desc.pmid) {
+ pinfo = pmda->e_metrics[j].m_user;
+ break;
+ }
+ }
+ if (pinfo == NULL)
+ return PM_ERR_PMID;
+ if (pinfo->pmid_index != 5)
+ return PM_ERR_PERMISSION;
+ queueid = event_queueid(pinfo->handle);
+
+ if (vsp->numval != 1 || vsp->valfmt != PM_VAL_SPTR)
+ return PM_ERR_CONV;
+
+ sts = event_regex_alloc(vsp->vlist[0].value.pval->vbuf, &filter);
+ if (sts < 0)
+ return sts;
+
+ sts = pmdaEventSetFilter(pmda->e_context, queueid, filter,
+ event_regex_apply, event_regex_release);
+ if (sts < 0 )
+ return sts;
+ }
+ return 0;
+}
+
+static void
+logger_end_contextCallBack(int context)
+{
+ pmdaEventEndClient(context);
+}
+
+static int
+logger_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+static int
+logger_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+static int
+logger_children(const char *name, int traverse, char ***kids, int **sts,
+ pmdaExt *pmda)
+{
+ pmdaEventNewClient(pmda->e_context);
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+static int
+logger_text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]);
+
+ pmdaEventNewClient(pmda->e_context);
+
+ if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) {
+ /* Lookup pmid in the metric table. */
+ int item = pmid_item(ident);
+
+ /* If the PMID item was for a dynamic metric... */
+ if (item >= numstatics && item < nummetrics
+ /* and the PMID matches... */
+ && metrictab[item].m_desc.pmid == (pmID)ident
+ /* and we've got user data... */
+ && metrictab[item].m_user != NULL) {
+ dynamic_metric_info_t *pinfo = metrictab[item].m_user;
+
+ /* Return the correct help text. */
+ *buffer = (char *)pinfo->help_text;
+ return 0;
+ }
+ }
+ return pmdaText(ident, type, buffer, pmda);
+}
+
+void
+logger_init(pmdaInterface *dp, const char *configfile)
+{
+ size_t size;
+ int i, j, sts, item, numloggers;
+ int numstatics = sizeof(static_metrictab)/sizeof(static_metrictab[0]);
+ int numdynamics = sizeof(dynamic_metrictab)/sizeof(dynamic_metrictab[0]);
+ pmdaMetric *pmetric;
+ char name[MAXPATHLEN * 2];
+ dynamic_metric_info_t *pinfo;
+
+ __pmSetProcessIdentity(username);
+
+ /* Read and parse config file. */
+ if ((numloggers = event_config(configfile)) < 0)
+ return;
+
+ /* Create the dynamic metric info table based on the logfile table */
+ size = sizeof(struct dynamic_metric_info) * numdynamics * numloggers;
+ if ((dynamic_metric_infotab = malloc(size)) == NULL) {
+ __pmNoMem("logger_init(dynamic)", size, PM_FATAL_ERR);
+ return;
+ }
+ pinfo = dynamic_metric_infotab;
+ for (i = 0; i < numloggers; i++) {
+ for (j = 0; j < numdynamics; j++) {
+ pinfo->handle = i;
+ pinfo->pmid_index = j;
+ pinfo->help_text = dynamic_helptab[j];
+ pinfo++;
+ }
+ }
+
+ /* Create the metric table based on the static and dynamic metric tables */
+ nummetrics = numstatics + (numloggers * numdynamics);
+ size = sizeof(pmdaMetric) * nummetrics;
+ if ((metrictab = malloc(size)) == NULL) {
+ free(dynamic_metric_infotab);
+ __pmNoMem("logger_init(static)", size, PM_FATAL_ERR);
+ return;
+ }
+ memcpy(metrictab, static_metrictab, sizeof(static_metrictab));
+ pmetric = &metrictab[numstatics];
+ pinfo = dynamic_metric_infotab;
+ item = numstatics;
+ for (i = 0; i < numloggers; i++) {
+ memcpy(pmetric, dynamic_metrictab, sizeof(dynamic_metrictab));
+ for (j = 0; j < numdynamics; j++) {
+ pmetric[j].m_desc.pmid = PMDA_PMID(0, item++);
+ pmetric[j].m_user = pinfo++;
+ }
+ pmetric += numdynamics;
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.fetch = logger_fetch;
+ dp->version.four.store = logger_store;
+ dp->version.four.profile = logger_profile;
+ dp->version.four.pmid = logger_pmid;
+ dp->version.four.name = logger_name;
+ dp->version.four.children = logger_children;
+ dp->version.four.text = logger_text;
+
+ pmdaSetFetchCallBack(dp, logger_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, logger_end_contextCallBack);
+
+ pmdaInit(dp, NULL, 0, metrictab, nummetrics);
+
+ /* Create the dynamic PMNS tree and populate it. */
+ if ((sts = __pmNewPMNS(&pmns)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n",
+ pmProgname, pmErrStr(sts));
+ pmns = NULL;
+ return;
+ }
+ pmetric = &metrictab[numstatics];
+ for (i = 0; i < numloggers; i++) {
+ const char *id = event_pmnsname(i);
+ for (j = 0; j < numdynamics; j++) {
+ snprintf(name, sizeof(name),
+ "logger.perfile.%s.%s", id, dynamic_nametab[j]);
+ __pmAddPMNSNode(pmns, pmetric[j].m_desc.pmid, name);
+ }
+ pmetric += numdynamics;
+ }
+ /* for reverse (pmid->name) lookups */
+ pmdaTreeRebuildHash(pmns, (numloggers * numdynamics));
+
+ /* initialise the event and client tracking code */
+ event_init(metrictab[2].m_desc.pmid);
+}
+
+static void
+logger_timer(int sig, void *ptr)
+{
+ interval_expired = 1;
+}
+
+void
+loggerMain(pmdaInterface *dispatch)
+{
+ fd_set readyfds;
+ int nready, pmcdfd;
+
+ pmcdfd = __pmdaInFd(dispatch);
+ if (pmcdfd > maxfd)
+ maxfd = pmcdfd;
+
+ FD_ZERO(&fds);
+ FD_SET(pmcdfd, &fds);
+
+ /* arm interval timer */
+ if (__pmAFregister(&interval, NULL, logger_timer) < 0) {
+ __pmNotifyErr(LOG_ERR, "registering event interval handler");
+ exit(1);
+ }
+
+ for (;;) {
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(maxfd+1, &readyfds, NULL, NULL, NULL);
+ if (pmDebug & DBG_TRACE_APPL2)
+ __pmNotifyErr(LOG_DEBUG, "select: nready=%d interval=%d",
+ nready, interval_expired);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror());
+ exit(1);
+ } else if (!interval_expired) {
+ continue;
+ }
+ }
+
+ __pmAFblock();
+ if (nready > 0 && FD_ISSET(pmcdfd, &readyfds)) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "processing pmcd PDU [fd=%d]", pmcdfd);
+ if (__pmdaMainPDU(dispatch) < 0) {
+ __pmAFunblock();
+ exit(1); /* fatal if we lose pmcd */
+ }
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "completed pmcd PDU [fd=%d]", pmcdfd);
+ }
+ if (interval_expired) {
+ interval_expired = 0;
+ event_refresh();
+ }
+ __pmAFunblock();
+ }
+}
+
+static void
+convertUnits(char **endnum, long *maxmem)
+{
+ switch ((int) **endnum) {
+ case 'b':
+ case 'B':
+ break;
+ case 'k':
+ case 'K':
+ *maxmem *= 1024;
+ break;
+ case 'm':
+ case 'M':
+ *maxmem *= 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ *maxmem *= 1024 * 1024 * 1024;
+ break;
+ }
+ (*endnum)++;
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [options] configfile\n\n"
+ "Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than the default\n"
+ " -m memory maximum memory used per logfile (default %ld bytes)\n"
+ " -s interval default delay between iterations (default %d sec)\n"
+ " -U username user account to run under (default \"pcp\")\n",
+ pmProgname, maxmem, (int)interval.tv_sec);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ static char helppath[MAXPATHLEN];
+ char *endnum;
+ pmdaInterface desc;
+ long minmem;
+ int c, err = 0, sep = __pmPathSeparator();
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ minmem = getpagesize();
+ maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM;
+ snprintf(helppath, sizeof(helppath), "%s%c" "logger" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_5, pmProgname, LOGGER,
+ "logger.log", helppath);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:l:m:s:U:?", &desc, &err)) != EOF) {
+ switch (c) {
+ case 'm':
+ maxmem = strtol(optarg, &endnum, 10);
+ if (*endnum != '\0')
+ convertUnits(&endnum, &maxmem);
+ if (*endnum != '\0' || maxmem < minmem) {
+ fprintf(stderr, "%s: invalid max memory '%s' (min=%ld)\n",
+ pmProgname, optarg, minmem);
+ err++;
+ }
+ break;
+
+ case 's':
+ if (pmParseInterval(optarg, &interval, &endnum) < 0) {
+ fprintf(stderr, "%s: -s requires a time interval: %s\n",
+ pmProgname, endnum);
+ free(endnum);
+ err++;
+ }
+ break;
+
+ case 'U':
+ username = optarg;
+ break;
+
+ default:
+ err++;
+ break;
+ }
+ }
+
+ if (err || optind != argc -1)
+ usage();
+
+ pmdaOpenLog(&desc);
+ logger_init(&desc, argv[optind]);
+ pmdaConnect(&desc);
+ loggerMain(&desc);
+ event_shutdown();
+ exit(0);
+}
diff --git a/src/pmdas/logger/pmns b/src/pmdas/logger/pmns
new file mode 100644
index 0000000..4b9827a
--- /dev/null
+++ b/src/pmdas/logger/pmns
@@ -0,0 +1,23 @@
+/*
+ * Metrics for logger PMDA
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+logger {
+ numclients LOGGER:0:0
+ numlogfiles LOGGER:0:1
+ param_string LOGGER:0:2
+ maxmem LOGGER:0:3
+ perfile LOGGER:*:*
+}
diff --git a/src/pmdas/logger/root b/src/pmdas/logger/root
new file mode 100644
index 0000000..51218c4
--- /dev/null
+++ b/src/pmdas/logger/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { logger }
+
+#include "pmns"
+
diff --git a/src/pmdas/logger/util.c b/src/pmdas/logger/util.c
new file mode 100644
index 0000000..c79787f
--- /dev/null
+++ b/src/pmdas/logger/util.c
@@ -0,0 +1,181 @@
+/*
+ * Utility functions for the logger PMDA.
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "util.h"
+
+void
+rstrip(char *str)
+{
+ char *ptr;
+
+ /* Remove all trailing whitespace. Set ptr to last char of
+ * string. */
+ ptr = str + strlen(str) - 1;
+ /* While trailing whitespace, move back. */
+ while (ptr >= str && isspace((int)*ptr)) {
+ --ptr;
+ }
+ *(ptr+1) = '\0'; /* Now set '\0' as terminal byte. */
+}
+
+char *
+lstrip(char *str)
+{
+ /* While leading whitespace, move forward. */
+ char *ptr = str;
+ while (*ptr != '\0' && isspace((int)*ptr)) {
+ ptr++;
+ }
+ return ptr;
+}
+
+int
+start_cmd(const char *cmd, pid_t *ppid)
+{
+ pid_t child_pid;
+ int rc;
+ int pipe_fds[2];
+#define PARENT_END 0 /* parent end of the pipe */
+#define CHILD_END 1 /* child end of the pipe */
+#define STDOUT_FD 1 /* stdout fd */
+
+ /* FIXME items:
+ * (1) Should we be looking to handle shell metachars
+ * differently? Perhaps we should just allow isalnum()||isspace()
+ * chars only.
+ * (2) Do we need to clean up the environment in the child before
+ * the exec()? Remove things like IFS, CDPATH, ENV, and BASH_ENV.
+ */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "%s: Trying to run command: %s", __FUNCTION__,
+ cmd);
+#endif
+
+ /* Create the pipes. */
+#if defined(HAVE_PIPE2)
+ rc = pipe2(pipe_fds, O_CLOEXEC|O_NONBLOCK);
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: pipe2() returned %s", __FUNCTION__,
+ strerror(-rc));
+ return rc;
+ }
+#else
+ rc = pipe(pipe_fds);
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: pipe() returned %s", __FUNCTION__,
+ strerror(-rc));
+ return rc;
+ }
+
+ /* Set the right flags on the pipes. */
+ if (fcntl(pipe_fds[PARENT_END], F_SETFL, O_NDELAY) < 0
+ || fcntl(pipe_fds[CHILD_END], F_SETFL, O_NDELAY) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: fcntl() returned %s", __FUNCTION__,
+ strerror(-rc));
+ return rc;
+ }
+ if (fcntl(pipe_fds[PARENT_END], F_SETFD, O_CLOEXEC) < 0
+ || fcntl(pipe_fds[CHILD_END], F_SETFD, O_CLOEXEC) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: fcntl() returned %s", __FUNCTION__,
+ strerror(-rc));
+ return rc;
+ }
+#endif
+
+ /* Create the new process. */
+ child_pid = fork();
+ if (child_pid == 0) { /* child process */
+ int i;
+
+ /* Duplicate our pipe fd onto stdout of the child. Note that
+ * this clears O_CLOEXEC, so the new stdout should stay open
+ * when we call exec(). */
+ if (pipe_fds[CHILD_END] != STDOUT_FD) {
+ if (dup2(pipe_fds[CHILD_END], STDOUT_FD) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: dup2() returned %s", __FUNCTION__,
+ strerror(errno));
+ _exit(127);
+ }
+ }
+
+ /* Close all other fds. */
+ for (i = 0; i <= pipe_fds[CHILD_END]; i++) {
+ if (i != STDOUT_FD) {
+ close(i);
+ }
+ }
+
+ /* Actually run the command. */
+ execl ("/bin/sh", "sh", "-c", cmd, (char *)NULL);
+ _exit (127);
+
+ }
+ else if (child_pid > 0) { /* parent process */
+ close (pipe_fds[CHILD_END]);
+ if (ppid != NULL) {
+ *ppid = child_pid;
+ }
+ }
+ else if (child_pid < 0) { /* fork error */
+ int errno_save = errno;
+
+ __pmNotifyErr(LOG_ERR, "%s: fork() returned %s", __FUNCTION__,
+ strerror(errno_save));
+ close (pipe_fds[PARENT_END]);
+ close (pipe_fds[CHILD_END]);
+
+ return -errno_save;
+ }
+
+ return pipe_fds[PARENT_END];
+}
+
+int
+stop_cmd(pid_t pid)
+{
+ int rc;
+ pid_t wait_pid;
+ int wstatus;
+
+ __pmNotifyErr(LOG_INFO, "%s: killing pid %" FMT_PID, __FUNCTION__, pid);
+
+ /* Send the TERM signal. */
+ rc = kill(pid, SIGTERM);
+ __pmNotifyErr(LOG_INFO, "%s: kill returned %d", __FUNCTION__, rc);
+
+ /* Wait for the process to go away. */
+ do {
+ wait_pid = waitpid (pid, &wstatus, 0);
+ __pmNotifyErr(LOG_INFO, "%s: waitpid returned %d", __FUNCTION__,
+ (int)wait_pid);
+ } while (wait_pid == -1 && errno == EINTR);
+
+ /* Return process exit status. */
+ return wstatus;
+}
diff --git a/src/pmdas/logger/util.h b/src/pmdas/logger/util.h
new file mode 100644
index 0000000..a26ac2b
--- /dev/null
+++ b/src/pmdas/logger/util.h
@@ -0,0 +1,25 @@
+/*
+ * Utility routines.
+ *
+ * Copyright (c) 2011 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _UTIL_H
+#define _UTIL_H
+
+extern char *lstrip(char *str);
+extern void rstrip(char *str);
+extern int start_cmd(const char *cmd, pid_t *ppid);
+extern int stop_cmd(pid_t pid);
+
+#endif /* _UTIL_H */
diff --git a/src/pmdas/lustrecomm/GNUmakefile b/src/pmdas/lustrecomm/GNUmakefile
new file mode 100644
index 0000000..d7ae6e0
--- /dev/null
+++ b/src/pmdas/lustrecomm/GNUmakefile
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# Author: Scott Emery <emery@sgi.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = lustrecomm
+DOMAIN = LUSTRECOMM
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+DFILES = README
+HFILES = libreadfiles.h
+CFILES = $(IAM).c file_indexed.c file_single.c refresh_file.c timespec_routines.c
+
+LIBTARGET = pmda_$(IAM).so
+CMDTARGET = pmda$(IAM)
+TARGETS = $(CMDTARGET)
+#TARGETS = $(LIBTARGET)
+
+LDOPTS =
+LSRCFILES = Install Remove README help pmns root
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_RT)
+LCFLAGS = -I.
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+build-me: domain.h $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET)
+ #$(INSTALL) -m 755 $(LIBTARGET) $(PMDADIR)/$(LIBTARGET)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root pmns domain.h help $(PMDADIR)
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/lustrecomm/Install b/src/pmdas/lustrecomm/Install
new file mode 100755
index 0000000..d712c24
--- /dev/null
+++ b/src/pmdas/lustrecomm/Install
@@ -0,0 +1,27 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# Author: Scott Emery <emery@sgi.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. /etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=lustrecomm
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/lustrecomm/README b/src/pmdas/lustrecomm/README
new file mode 100644
index 0000000..467cd80
--- /dev/null
+++ b/src/pmdas/lustrecomm/README
@@ -0,0 +1,60 @@
+lustrecomm PMDA
+===============
+
+This PMDA collects statistics common to all lustre implementations,
+the statistics found in /proc/lustre and /proc/lnet
+
+Note:
+ This PMDA may be remade from source and hence requires IDO (or
+ more specifically a C compiler) to be installed.
+
+ Uses of make(1) may fail (without removing or clobbering files)
+ if the C compiler cannot be found. This is most likely to
+ happen when running the PMDA ./Install script.
+
+ The only remedial action is to install the C compiler, or
+ hand-craft changes to the Makefile.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT lustrecomm
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/lustrecomm
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/lustrecomm
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/trivial.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/lustrecomm/Remove b/src/pmdas/lustrecomm/Remove
new file mode 100755
index 0000000..7ed7151
--- /dev/null
+++ b/src/pmdas/lustrecomm/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# Author: Scott Emery <emery@sgi.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# source the PCP configuration environment variables
+. /etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=lustrecomm
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/lustrecomm/TODO b/src/pmdas/lustrecomm/TODO
new file mode 100644
index 0000000..33b1860
--- /dev/null
+++ b/src/pmdas/lustrecomm/TODO
@@ -0,0 +1,3 @@
+
+v4 - add nis and peers
+
diff --git a/src/pmdas/lustrecomm/file_indexed.c b/src/pmdas/lustrecomm/file_indexed.c
new file mode 100644
index 0000000..2f11c88
--- /dev/null
+++ b/src/pmdas/lustrecomm/file_indexed.c
@@ -0,0 +1,96 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libreadfiles.h"
+
+
+/* int file_indexed (struct file_state *f_s,
+ * int type, int base, void *vpp, int index)
+ * - Get single value from file containing multiple values
+ * struct file_state *f_s - struct for access to single file
+ * type - PCP type described by the PMAPI PM_TYPE_* enum
+ * base - for integer types, base as stored in the file
+ * - for string and aggregate types, size of memory pointed to
+ * ---------------- size may be changed by realloc
+ * vpp - pointer to pointer to storage allocated to contain value
+ * - pointed to pointer may change
+ * ---------------- pointer may be changed by realloc
+ * index - value is the n'th strtok token in the file
+ * consider specifying token seperator, default whitespace is fine for now
+ */
+int file_indexed (struct file_state *f_s, int type, int *base, void **vpp, int index)
+{
+ int i;
+ char *cp, *sp;
+
+ /* reload file if it hasn't been reloaded in x */
+ if (refresh_file( f_s ) <0 ) {
+ __pmNotifyErr(LOG_ERR, "file_indexed: refresh_file error");
+ return -1;
+ }
+ /* file_indexed assumes that the file contains one line
+ * if the file may ever contain more than one line, consider file_arrayed
+ * (if that ever gets written)
+ * strtok to the appropriate spot
+ * strtok screws up the target string, so make a copy of the file data
+ */
+ cp = malloc ( f_s->datas );
+ strcpy ( cp, f_s->datap );
+ sp = strtok( cp, " " );
+ for (i = 0; i < index; i++){
+ sp = strtok( NULL, " ");
+ }
+ /* otherwise, a lot like file_single (see below)
+ * one would eventually write a configure script to make
+ * sure that the right routines are used below.. defining
+ * a STRTO32 and STRTOU32, etc.
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ *((long *)(*vpp)) = strtol(sp,0,*base);
+ break;
+ case PM_TYPE_U32:
+ *((unsigned long *)(*vpp)) = strtoul(sp,0,*base);
+ break;
+ case PM_TYPE_64:
+ *((long long *)(*vpp)) = strtoll(sp,0,*base);
+ break;
+ case PM_TYPE_U64:
+ *((unsigned long long *)(*vpp)) = strtoull(sp,0,*base);
+ break;
+ case PM_TYPE_FLOAT:
+ *((float *)(*vpp)) = strtof(sp,0);
+ break;
+ case PM_TYPE_DOUBLE:
+ *((double *)(*vpp)) = strtod(sp,0);
+ break;
+ default:
+ fprintf(stderr,"file_indexed: type %s not supported\n", pmTypeStr(type));
+ break;
+ }
+ /* so, write it up and slice and dice */
+ free (cp);
+
+ return 0;
+}
+
+
diff --git a/src/pmdas/lustrecomm/file_single.c b/src/pmdas/lustrecomm/file_single.c
new file mode 100644
index 0000000..2f135d2
--- /dev/null
+++ b/src/pmdas/lustrecomm/file_single.c
@@ -0,0 +1,104 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libreadfiles.h"
+
+
+/* file_single (char *filename, int type, int *base, void **vpp)
+ * - Get a value from a file containing single value
+ * filename - name of file
+ * type - PCP type described by the PMAPI PM_TYPE_* enum
+ * base - for integer types, base as stored in the file
+ * - for string and aggregate types, size of memory pointed to
+ * ---------------- size may be changed by realloc
+ * vpp - pointer to pointer to storage allocated to contain value
+ * - pointed to pointer may change
+ * ---------------- pointer may be changed by realloc
+ * allocating and deallocating space for value is the responsibility
+ * of the caller
+ */
+int file_single (char *filename, int type, int *base, void **vpp)
+{
+ int fd;
+ int n;
+ /* overlarge */
+ char b[80] = { 0 };
+
+ /* Read in the file into the buffer b */
+ if (( fd = open (filename, O_RDONLY)) < 0) {
+ perror("file_single: open");
+ return -1;
+ }
+ /* Okay this presents a problem vis a vis some lustre proc files.
+ * While the present scheme is adequate for lustre common in most
+ * cases, some lustre proc files have *ridiculously* large amounts
+ * of data stored in them. Need to come up with some dynamic
+ * memory buffer system for other lustre PMDAs
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ case PM_TYPE_FLOAT:
+ case PM_TYPE_DOUBLE:
+ if ((n = read (fd, b, sizeof(b))) < 0 ){
+ perror("file_single: read");
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ break;
+ default:
+ fprintf(stderr,"file_single: type %s not supported\n", pmTypeStr(type));
+ close(fd);
+ return -1;
+
+ }
+ /* One would eventually write a configure script to make
+ * sure that the right routines are used below.. defining
+ * a STRTO32 and STRTOU32, etc.
+ */
+ switch (type) {
+ case PM_TYPE_32:
+ *((long *)(*vpp)) = strtol(b,0,*base);
+ break;
+ case PM_TYPE_U32:
+ *((unsigned long *)(*vpp)) = strtoul(b,0,*base);
+ break;
+ case PM_TYPE_64:
+ *((long long *)(*vpp)) = strtoll(b,0,*base);
+ break;
+ case PM_TYPE_U64:
+ *((unsigned long long *)(*vpp)) = strtoull(b,0,*base);
+ break;
+ case PM_TYPE_FLOAT:
+ *((float *)(*vpp)) = strtof(b,0);
+ break;
+ case PM_TYPE_DOUBLE:
+ *((double *)(*vpp)) = strtod(b,0);
+ break;
+ }
+ return 0;
+}
+
+
diff --git a/src/pmdas/lustrecomm/help b/src/pmdas/lustrecomm/help
new file mode 100644
index 0000000..2d86915
--- /dev/null
+++ b/src/pmdas/lustrecomm/help
@@ -0,0 +1,106 @@
+#
+# Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# Author: Scott Emery <emery@sgi.com>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#
+# lustrecomm PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ lustrecomm.timeout contents of /proc/sys/lustre/timeout
+The time period that a client waits for a server to complete an RPC
+(default in 1.6 is 100s). Servers wait half this time for a normal
+client RPC to compelte and a quarter of this time for a single
+bulk request to complete. The client pings recoverable targets
+(MDS and OSTs) at one quarter of the timeout, and the server
+waits on and a half times the timeout before evicting a client
+for being "stale". (source: Lustre 1.6 Operations Manual)
+
+@ lustrecomm.ldlm_timeout contents of /proc/sys/lustre/ldlm_timeout
+This is the time period for which a server will wait for a client
+to reply to an initial AST (lock cancellation request). The default
+is 20s for an OST and 6s for an MDS. (source: Lustre 1.6 Operations
+Manual)
+
+@ lustrecomm.dump_on_timeout contents of /proc/sys/lustre/ldlm_timeout
+A 1 triggers dumps of the Lustre debug log when timeouts occur.
+Default value 0. (source: Lustre 1.6 Operations Manual)
+
+@ lustrecomm.lustre_memused contents of /proc/sys/lustre/memused
+lustre/obdclass/linux/linux-sysctl.c: &proc_memory_alloc
+lustre/include/obd_support.h: obd_memory_sum()
+Total bytes allocated by Lustre (inferred from lustre/include/obd_support.h)
+
+@ lustrecomm.lnet_memused contents of /proc/sys/lnet/memused
+lnet/libcfs/linux/linux-proc.c: (int *)&libcfs_kmemory.counter
+Total bytes allocated by LNET. (inferred from lustre/include/obd_support.h)
+
+@ lustrecomm.stats.msgs_alloc first number from /proc/sys/lnet/stats
+routerstat source file: messages currently allocated (first number after M)
+
+@ lustrecomm.stats.msgs_max second number from /proc/sys/lnet/stats
+routerstat source file: messages maximum (highwater mark) (second
+number after M)
+
+@ lustrecomm.stats.errors third number from /proc/sys/lnet/stats
+routerstat source file: errors (number after E)
+
+@ lustrecomm.stats.send_count fourth number from /proc/sys/lnet/stats
+routerstat source file: send_count (raw data from which second number
+after S is derived).
+
+@ lustrecomm.stats.recv_count fifth number from /proc/sys/lnet/stats
+routerstat source file: recv_count (raw data from which second number
+after R is derived)
+
+@ lustrecomm.stats.route_count sixth number from /proc/sys/lnet/stats
+routerstat source file: route_count (raw data from which second number
+after R is derived)
+
+@ lustrecomm.stats.drop_count seventh number from /proc/sys/lnet/stats
+routerstat source file: drop_count (raw data from which second number
+after D is derived)
+
+@ lustrecomm.stats.send_length eigth number from /proc/sys/lnet/stats
+routerstat source file: send_length (raw data from which first number
+after S is derived)
+
+@ lustrecomm.stats.recv_length ninth number from /proc/sys/lnet/stats
+routerstat source file: recv_length (raw data from which first number
+after S is derived)
+
+@ lustrecomm.stats.route_length tenth number from /proc/sys/lnet/stats
+routerstat source file: route_length (raw data from which first number
+after R is derived)
+
+@ lustrecomm.stats.drop_length eleventh number from /proc/sys/lnet/stats
+routerstat source file: drop_length (raw data from which first number
+after D is derived)
+
diff --git a/src/pmdas/lustrecomm/libreadfiles.h b/src/pmdas/lustrecomm/libreadfiles.h
new file mode 100644
index 0000000..121dcdf
--- /dev/null
+++ b/src/pmdas/lustrecomm/libreadfiles.h
@@ -0,0 +1,59 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#include <sys/types.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <time.h>
+
+struct file_state {
+ struct timespec ts;
+ char *filename;
+ int fd;
+ int datas;
+ void *datap;
+};
+
+#define BUFFERBLOCK 4096
+
+#ifndef FILE_TIME_OFFSET
+extern struct timespec file_time_offset;
+#endif
+
+/* timespec_routines.c */
+extern struct timespec timespec_add (struct timespec *a, struct timespec *b);
+extern int timespec_le ( struct timespec *lhs, struct timespec *rhs);
+/* refresh_file.c */
+extern int refresh_file( struct file_state *f_s );
+/* file_indexed.c */
+extern int file_indexed (struct file_state *f_s, int type, int *base, void **vpp, int index);
+/* file_single.c */
+extern int file_single (char *filename, int type, int *base, void **vpp);
+
+
diff --git a/src/pmdas/lustrecomm/lustrecomm.c b/src/pmdas/lustrecomm/lustrecomm.c
new file mode 100644
index 0000000..9b4b9df
--- /dev/null
+++ b/src/pmdas/lustrecomm/lustrecomm.c
@@ -0,0 +1,304 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Original author: Scott Emery <emery@sgi.com>
+ *
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "libreadfiles.h"
+#include <stdio.h>
+#include "domain.h"
+
+
+/*
+ * Lustrecomm PMDA
+ *
+ * This PMDA gathers Lustre statistics from /proc, it is constructed using
+ * libpcp_pmda.
+ *
+ *
+ * Metrics
+ * lustrecomm.time - time in seconds since the 1st of Jan, 1970.
+ */
+
+#define PROC_SYS_LNET_STATS 0
+#define PROC_SYS_LNET_NIS 1
+#define PROC_SYS_LNET_PEERS 2
+#define FILESTATETABSIZE 3
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+static pmdaMetric metrictab[] = {
+/* timeout */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+/* ldlm_timeout */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+/* dump_on_timeout */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+/* lustre_memused */
+ { NULL,
+ { PMDA_PMID(0,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* lnet_memused */
+ { NULL,
+ { PMDA_PMID(0,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* data pulled from /proc/sys/lnet/stats */
+/*0 42 0 22407486 23426580 0 0 135850271989 472430974209 0 0*/
+/* stats.msgs_alloc */
+ { NULL,
+ { PMDA_PMID(1,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.msgs_max */
+ { NULL,
+ { PMDA_PMID(1,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.errors */
+ { NULL,
+ { PMDA_PMID(1,2), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.send_count */
+ { NULL,
+ { PMDA_PMID(1,3), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.recv_count */
+ { NULL,
+ { PMDA_PMID(1,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.route_count */
+ { NULL,
+ { PMDA_PMID(1,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.drop_count */
+ { NULL,
+ { PMDA_PMID(1,6), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.send_length */
+ { NULL,
+ { PMDA_PMID(1,7), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.recv_length */
+ { NULL,
+ { PMDA_PMID(1,8), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.route_length */
+ { NULL,
+ { PMDA_PMID(1,9), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/* stats.drop_length */
+ { NULL,
+ { PMDA_PMID(1,10), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } }
+
+};
+
+struct file_state filestatetab[] = {
+ { { 0 , 0 } , "/proc/sys/lnet/stats", 0, 0, NULL},
+ { { 0 , 0 } , "/proc/sys/lnet/nis", 0, 0, NULL},
+ { { 0 , 0 } , "/proc/sys/lnet/peers", 0, 0, NULL}
+};
+
+static int isDSO = 1; /* =0 I am a daemon */
+static char mypath[MAXPATHLEN];
+static char *username;
+
+/*
+ * callback provided to pmdaFetch
+ */
+
+static int
+lustrecomm_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ void *vp;
+
+ /* check for PMID errors */
+ if ( !((idp->cluster == 0) && (idp->item <= 4)) &&
+ !((idp->cluster == 1) && (idp->item <= 10)) )
+ return PM_ERR_PMID;
+ else if (inst != PM_IN_NULL)
+ return PM_ERR_INST;
+
+ /* refresh and store data */
+ if (idp->cluster == 0) {
+ vp = malloc (4);
+ int aux = 10;
+ /* not saving any time fussing with "how recent", just do it */
+ switch (idp->item) {
+ case 0:
+ /* consider mdesc->m_desc.type */
+ /* problem: the way it's stored in PCP isn't necessarily */
+ /* the way it's presented by the OS */
+ file_single("/proc/sys/lustre/timeout", PM_TYPE_32, &aux, &vp);
+ atom->l = *((long *) vp);
+ break;
+ case 1:
+ file_single("/proc/sys/lustre/ldlm_timeout", PM_TYPE_32, &aux, &vp);
+ atom->l = *((long *) vp);
+ break;
+ case 2:
+ file_single("/proc/sys/lustre/dump_on_timeout", PM_TYPE_32, &aux, &vp);
+ atom->l = *((long *) vp);
+ break;
+ case 3:
+ file_single("/proc/sys/lustre/memused", PM_TYPE_32, &aux, &vp);
+ atom->l = *((long *) vp);
+ break;
+ case 4:
+ file_single("/proc/sys/lnet/lnet_memused", PM_TYPE_32, &aux, &vp);
+ atom->l = *((long *) vp);
+ break;
+ default:
+ printf ("PMID %d:%d non-existant\n",idp->cluster,idp->item);
+ break;
+ }
+ free (vp);
+ }
+ if (idp->cluster == 1) {
+ vp = malloc (4);
+ int aux = 10;
+
+ switch(idp->item) {
+ case 0:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 0);
+ atom->l = *((long *) vp);
+ break;
+ case 1:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 1);
+ atom->l = *((long *) vp);
+ break;
+ case 2:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 2);
+ atom->l = *((long *) vp);
+ break;
+ case 3:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 3);
+ atom->l = *((long *) vp);
+ break;
+ case 4:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 4);
+ atom->l = *((long *) vp);
+ break;
+ case 5:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 5);
+ atom->l = *((long *) vp);
+ break;
+ case 6:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_32,&aux, &vp, 6);
+ atom->l = *((long *) vp);
+ break;
+ case 7:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_64,&aux, &vp, 7);
+ atom->l = *((long *) vp);
+ break;
+ case 8:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_64,&aux, &vp, 8);
+ atom->l = *((long *) vp);
+ break;
+ case 9:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_64,&aux, &vp, 9);
+ atom->l = *((long *) vp);
+ break;
+ case 10:
+ file_indexed(&filestatetab[PROC_SYS_LNET_STATS], PM_TYPE_64,&aux, &vp, 10);
+ atom->l = *((long *) vp);
+ break;
+ default:
+ break;
+ }
+ free(vp);
+ }
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+lustrecomm_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "lustrecomm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "lustrecomm DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ pmdaSetFetchCallBack(dp, lustrecomm_fetchCallBack);
+
+ pmdaInit(dp, NULL, 0,
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "lustrecomm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, LUSTRECOMM,
+ "lustrecomm.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ lustrecomm_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/lustrecomm/pmns b/src/pmdas/lustrecomm/pmns
new file mode 100644
index 0000000..49a999f
--- /dev/null
+++ b/src/pmdas/lustrecomm/pmns
@@ -0,0 +1,47 @@
+/*
+ * Metrics for lustrecomm PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+lustrecomm {
+ timeout LUSTRECOMM:0:0
+ ldlm_timeout LUSTRECOMM:0:1
+ dump_on_timeout LUSTRECOMM:0:2
+ lustre_memused LUSTRECOMM:0:3
+ lnet_memused LUSTRECOMM:0:4
+ stats
+}
+
+lustrecomm.stats {
+/* data pulled from /proc/sys/lnet/stats */
+/*0 42 0 22407486 23426580 0 0 135850271989 472430974209 0 0*/
+ msgs_alloc LUSTRECOMM:1:0
+ msgs_max LUSTRECOMM:1:1
+ errors LUSTRECOMM:1:2
+ send_count LUSTRECOMM:1:3
+ recv_count LUSTRECOMM:1:4
+ route_count LUSTRECOMM:1:5
+ drop_count LUSTRECOMM:1:6
+ send_length LUSTRECOMM:1:7
+ recv_length LUSTRECOMM:1:8
+ route_length LUSTRECOMM:1:9
+ drop_length LUSTRECOMM:1:10
+}
+
diff --git a/src/pmdas/lustrecomm/pmns.v4 b/src/pmdas/lustrecomm/pmns.v4
new file mode 100644
index 0000000..e702751
--- /dev/null
+++ b/src/pmdas/lustrecomm/pmns.v4
@@ -0,0 +1,87 @@
+/*
+ * Metrics for lustrecomm PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+lustrecomm {
+ timeout LUSTRECOMM:0:0
+ ldlm_timeout LUSTRECOMM:0:1
+ dump_on_timeout LUSTRECOMM:0:2
+ lustre_memused LUSTRECOMM:0:3
+ lnet_memused LUSTRECOMM:0:4
+ stats
+}
+
+lustrecomm.stats {
+/* data pulled from /proc/sys/lnet/stats */
+/*0 42 0 22407486 23426580 0 0 135850271989 472430974209 0 0*/
+ msgs_alloc LUSTRECOMM:1:0
+ msgs_max LUSTRECOMM:1:1
+ errors LUSTRECOMM:1:2
+ send_count LUSTRECOMM:1:3
+ recv_count LUSTRECOMM:1:4
+ route_count LUSTRECOMM:1:5
+ drop_count LUSTRECOMM:1:6
+ send_length LUSTRECOMM:1:7
+ recv_length LUSTRECOMM:1:8
+ route_length LUSTRECOMM:1:9
+ drop_length LUSTRECOMM:1:10
+}
+
+lustrecomm.nis {
+/* data pulled from /proc/sys/lnet/nis */
+/* nid refs peer max tx min */
+/* 0@lo 2 0 0 0 0 */
+/* 10.149.0.1@o2ib 13 8 64 64 26 */
+
+ nid LUSTRECOMM:2:1
+ refs LUSTRECOMM:2:2
+ peer LUSTRECOMM:2:3
+ max LUSTRECOMM:2:4
+ tx LUSTRECOMM:2:5
+ tx_min LUSTRECOMM:2:6
+ active LUSTRECOMM:2:7
+/* subtracting max - tx yields the nubmer of sends currently active*/
+/* a large or increasing number of active sends may be a problem */
+}
+
+lustrecomm.peers {
+/* data pulled from /proc/sys/lnet/peers */
+/* nid refs state max rtr min tx min queue */
+/* 10.149.2.15@o2ib 1 ~rtr 8 8 8 8 0 0 */
+/* 10.149.2.29@o2ib 1 ~rtr 8 8 8 8 -26 0 */
+ nid LUSTRECOMM:3:0
+ refs LUSTRECOMM:3:1
+ state LUSTRECOMM:3:2
+ max LUSTRECOMM:3:3
+ rtr LUSTRECOMM:3:4
+ rtr_min LUSTRECOMM:3:5
+ tx LUSTRECOMM:3:6
+ tx_min LUSTRECOMM:3:7
+ queue LUSTRECOMM:3:8
+ in_progress LUSTRECOMM:3:9
+/* if rtr/tx is less than max, there are operations in progress. The number
+ of operations is equal to rtr or tx subtraced from max.
+*/
+ blocking LUSTRECOMM:2:10
+/* if rtr/tx is greater than max, there are operations blocking */
+}
+
+
diff --git a/src/pmdas/lustrecomm/refresh_file.c b/src/pmdas/lustrecomm/refresh_file.c
new file mode 100644
index 0000000..86ca16f
--- /dev/null
+++ b/src/pmdas/lustrecomm/refresh_file.c
@@ -0,0 +1,85 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+#define FILE_TIME_OFFSET 1
+
+#include "libreadfiles.h"
+
+static struct timespec file_time_offset = { 0 , 900000000 };
+
+
+int refresh_file( struct file_state *f_s ){
+ struct timespec now, tmp;
+ int i = 0;
+
+ if (f_s->datap) {
+ /* get time */
+ if ( clock_gettime(CLOCK_MONOTONIC, &now) < 0 ) {
+ /* if we don't know what time it is */
+ /* there's nothing we can do with this */
+ return 0;
+ }
+ /* if time since last refresh > delta */
+ tmp = timespec_add( &f_s->ts, &file_time_offset);
+ if ( timespec_le( &now, &tmp) ) {
+ /* file is recent */
+ return 0;
+ }
+ f_s->ts = now;
+ /* clear old data, make errors obvious, autoterm trailing strings */
+ memset ( f_s->datap, 0, f_s->datas );
+ }
+ /* if fd is null open file */
+ if ( f_s->fd <= 0) {
+ if (( f_s->fd = open (f_s->filename, O_RDONLY)) < 0) {
+ perror("refresh_file: open");
+ return -1;
+ }
+ }
+ if ( lseek (f_s->fd, 0, SEEK_SET) < 0 ) {
+ perror("refresh_file: initial seek");
+ return -1;
+ }
+ /* only grow, never shrink... what would be the point? */
+ while (f_s->datap && (i = read (f_s->fd, f_s->datap, f_s->datas)) >= f_s->datas ){
+ /* oh heck, what do I do if this fails? */
+ f_s->datas += BUFFERBLOCK;
+ if ((f_s->datap = realloc(f_s->datap, f_s->datas)) == NULL){
+ free((char *)f_s->datap);
+ perror("refresh_file: realloc");
+ return -1;
+ }
+ if ( lseek (f_s->fd, 0, SEEK_SET) < 0 ) {
+ perror("refresh_file: subsequent seek");
+ return -1;
+ }
+ }
+ if (i < 0 ){
+ /* read failed */
+ perror("refresh_file: file read failed");
+ return -1;
+ }
+ return 0;
+}
+
+
diff --git a/src/pmdas/lustrecomm/root b/src/pmdas/lustrecomm/root
new file mode 100644
index 0000000..bf7781c
--- /dev/null
+++ b/src/pmdas/lustrecomm/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { lustrecomm }
+
+#include "pmns"
+
diff --git a/src/pmdas/lustrecomm/timespec_routines.c b/src/pmdas/lustrecomm/timespec_routines.c
new file mode 100644
index 0000000..976be2a
--- /dev/null
+++ b/src/pmdas/lustrecomm/timespec_routines.c
@@ -0,0 +1,48 @@
+/*
+ * Lustre common /proc PMDA
+ *
+ * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * Author: Scott Emery <emery@sgi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <time.h>
+
+
+struct timespec timespec_add (struct timespec *a, struct timespec *b){
+ struct timespec tret;
+
+ tret.tv_nsec = (a->tv_nsec + b->tv_nsec) % 1000000000;
+ tret.tv_sec = a->tv_sec + b->tv_sec +
+ ((a->tv_nsec + b->tv_nsec)/1000000000);
+ return tret;
+}
+
+int timespec_le ( struct timespec *lhs, struct timespec *rhs) {
+ if (rhs->tv_sec < lhs->tv_sec) {
+ /* false */
+ return 0;
+ }
+ if (lhs->tv_sec == rhs->tv_sec) {
+ if (rhs->tv_nsec < lhs->tv_nsec) {
+ /* false */
+ return 0;
+ }
+ }
+ return 1;
+}
+
diff --git a/src/pmdas/mailq/GNUmakefile b/src/pmdas/mailq/GNUmakefile
new file mode 100644
index 0000000..1794f1a
--- /dev/null
+++ b/src/pmdas/mailq/GNUmakefile
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2000-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mailq
+DOMAIN = MAILQ
+
+CMDTARGET = pmdamailq$(EXECSUFFIX)
+CFILES = mailq.c
+DFILES = README
+LSRCFILES = Install Remove root help pmns $(DFILES) pmlogconf.summary
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_REGEX)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LDIRT = domain.h *.log *.dir *.pag so_locations
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns domain.h $(PMDADIR)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+
+mailq.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/mailq/Install b/src/pmdas/mailq/Install
new file mode 100644
index 0000000..efe1fd5
--- /dev/null
+++ b/src/pmdas/mailq/Install
@@ -0,0 +1,117 @@
+#! /bin/sh
+#
+# Copyright (c) 1997-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the mailq PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mailq
+pmda_interface=2
+forced_restart=false
+
+# Do it
+#
+pmdaSetup
+
+if $do_pmda
+then
+
+ dso_opt=false
+ socket_opt=false
+ pipe_opt=true
+
+ mqueue=""
+ chk=""
+
+ if [ -f /etc/sendmail.cf ]
+ then
+ chk=`sed -n '/^O *QueueDirectory *= */s///p' /etc/sendmail.cf`
+ [ -z "$chk" ] && chk=`sed -n '/^O *Q *\//s//\//p' /etc/sendmail.cf`
+ fi
+
+ if [ ! -z "$chk" -a -d "$chk" ]
+ then
+ mqueue="$chk"
+ else
+ mqueue=/var/spool/mqueue
+ fi
+
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Mail queue directory ['"$mqueue"'] '"$PCP_ECHO_C"
+ read ans
+ [ -z "$ans" ] && break
+ if [ -d "$ans" ]
+ then
+ mqueue="$ans"
+ break
+ fi
+ echo "Error: \"$ans\" is not a directory"
+ done
+
+ echo
+ regex=""
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Mail basename regex ['"$regex"'] '"$PCP_ECHO_C"
+ read regex
+ [ -z "$regex" ] || args="$args -r $regex"
+
+ echo
+ args="$args $mqueue"
+
+ while true
+ do
+ echo 'The default delay thresholds for grouping the pending mail items are:'
+ echo ' 1 hour, 4 hours, 8 hours, 1 day, 3 days and 7 days'
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Do you wish to use the default delay thresholds [y]? '"$PCP_ECHO_C"
+ read ans
+ if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ break
+ else
+ bucketlist=''
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Threshold? [return if no more] '"$PCP_ECHO_C"
+ read ans
+ [ -z "$ans" ] && break
+ # strip blanks so args in pmcd.conf get passed correctly to
+ # the mailqpmda binary
+ #
+ ans=`echo "$ans" | sed -e 's/ //g'`
+ if [ -z "$bucketlist" ]
+ then
+ bucketlist="$ans"
+ else
+ bucketlist="$bucketlist,$ans"
+ fi
+ done
+ if [ ! -z "$bucketlist" ]
+ then
+ args="$args -b $bucketlist"
+ break
+ fi
+ echo
+ echo 'Error: you must specify at least one threshold'
+ echo
+ fi
+ done
+ echo
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/mailq/README b/src/pmdas/mailq/README
new file mode 100644
index 0000000..08bbfb6
--- /dev/null
+++ b/src/pmdas/mailq/README
@@ -0,0 +1,48 @@
+Mailq PMDA
+==========
+
+This PMDA exports information about the sendmail(1) queue.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT mailq
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/mailq
+
+ + Check that there is no clash in the Performance Metrics Domain defined
+ in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/mailq
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/mailq.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/mailq/Remove b/src/pmdas/mailq/Remove
new file mode 100644
index 0000000..77da216
--- /dev/null
+++ b/src/pmdas/mailq/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the mailq PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=mailq
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/mailq/help b/src/pmdas/mailq/help
new file mode 100644
index 0000000..29a1792
--- /dev/null
+++ b/src/pmdas/mailq/help
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# mailq PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ MAILQ.1 Instance domain used to "bin" messages based on how long they have been deferred
+
+
+@ mailq.length Number of messages in the queue
+The total number of messages in the mail queue.
+
+@ mailq.deferred Histogram of pending messages based on how long they have been deferred
+Counts of the number of messages in the sendmail queue grouped by
+how long the mail delivery has been deferred.
+
+The groups are based on the how long a message has been in the
+queue:
+ 1-week at least a week
+ 3-days at least 3 days and less than a week
+ 1-day at least one day and less than 3 days
+ 8-hours more than 8 hours and less than one day
+ 4-hours between 4 and 8 hours
+ 1-hour between 1 and 4 hours
+ recent less than an hour
diff --git a/src/pmdas/mailq/mailq.c b/src/pmdas/mailq/mailq.c
new file mode 100644
index 0000000..8d82fcd
--- /dev/null
+++ b/src/pmdas/mailq/mailq.c
@@ -0,0 +1,401 @@
+/*
+ * Mailq PMDA
+ *
+ * Copyright (c) 2012,2014 Red Hat.
+ * Copyright (c) 1997-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+#include <sys/stat.h>
+
+/*
+ * histogram for binning messages based on queue time
+ */
+typedef struct {
+ long count; /* number in this bin */
+ time_t delay; /* in queue for at least this long (seconds) */
+} histo_t;
+
+static histo_t *histo;
+static int numhisto;
+static int queue;
+
+/*
+ * list of instances - indexes must match histo[] above
+ */
+static pmdaInstid *_delay;
+
+static char *queuedir = "/var/spool/mqueue";
+static char startdir[MAXPATHLEN];
+static char *username;
+
+static char *regexstring;
+static regex_t mq_regex;
+
+/*
+ * list of instance domains
+ */
+static pmdaIndom indomtab[] = {
+#define DELAY_INDOM 0
+ { DELAY_INDOM, 0, NULL },
+};
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+static pmdaMetric metrictab[] = {
+/* length */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* deferred */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_U32, DELAY_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+};
+
+static int
+mailq_histogram(char *option)
+{
+ struct timeval tv;
+ char *errmsg;
+ char *q;
+ int sts;
+
+ q = strtok(option, ",");
+ while (q != NULL) {
+ if ((sts = pmParseInterval((const char *)q, &tv, &errmsg)) < 0) {
+ pmprintf("%s: bad historgram bins argument:\n%s\n", pmProgname, errmsg);
+ free(errmsg);
+ return -EINVAL;
+ }
+ numhisto++;
+ histo = (histo_t *)realloc(histo, numhisto * sizeof(histo[0]));
+ if (histo == NULL)
+ __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR);
+ histo[numhisto-1].delay = tv.tv_sec;
+ q = strtok(NULL, ",");
+ }
+ return 0;
+}
+
+static int
+compare_delay(const void *a, const void *b)
+{
+ histo_t *ha = (histo_t *)a;
+ histo_t *hb = (histo_t *)b;
+
+ return hb->delay - ha->delay;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+mailq_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int b;
+
+ if (idp->cluster == 0) {
+ if (idp->item == 0) { /* mailq.length */
+ if (inst == PM_IN_NULL)
+ atom->ul = queue;
+ else
+ return PM_ERR_INST;
+ }
+ else if (idp->item == 1) { /* mailq.deferred */
+ /* inst is unsigned, so always >= 0 */
+ for (b = 0; b < numhisto; b++) {
+ if (histo[b].delay == inst) break;
+ }
+ if (b < numhisto)
+ atom->ul = histo[b].count;
+ else
+ return PM_ERR_INST;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else
+ return PM_ERR_PMID;
+
+ return 0;
+}
+
+/*
+ * wrapper for pmdaFetch which refreshes the metrics
+ */
+static int
+mailq_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ static int warn = 0;
+ int num;
+ int i;
+ int b;
+ struct stat sbuf;
+ time_t now;
+ static time_t last_refresh = 0;
+ time_t waiting;
+ char *p;
+ struct dirent **list;
+
+ time(&now);
+
+ /* clip refresh rate to at most once per 30 seconds */
+ if (now - last_refresh > 30) {
+ last_refresh = now;
+
+ queue = 0;
+ for (b = 0; b < numhisto; b++)
+ histo[b].count = 0;
+
+ if (chdir(queuedir) < 0) {
+ if (warn == 0) {
+ __pmNotifyErr(LOG_ERR, "chdir(\"%s\") failed: %s\n",
+ queuedir, osstrerror());
+ warn = 1;
+ }
+ }
+ else {
+ if (warn == 1) {
+ __pmNotifyErr(LOG_INFO, "chdir(\"%s\") success\n", queuedir);
+ warn = 0;
+ }
+
+ num = scandir(".", &list, NULL, NULL);
+
+ for (i = 0; i < num; i++) {
+ p = list[i]->d_name;
+ /* only file names that match the regular expression */
+ if (regexstring && regexec(&mq_regex, list[i]->d_name, 0, NULL, 0))
+ continue;
+ else if (!regexstring && (*p != 'd' || *(p+1) != 'f'))
+ continue;
+ if (stat(p, &sbuf) != 0) {
+ /*
+ * ENOENT expected sometimes if sendmail is doing its job
+ */
+ if (oserror() == ENOENT)
+ continue;
+ fprintf(stderr, "stat(\"%s\"): %s\n", p, osstrerror());
+ continue;
+ }
+ if (sbuf.st_size > 0 && S_ISREG(sbuf.st_mode)) {
+ /* really in the queue */
+#if defined(HAVE_ST_MTIME_WITH_E)
+ waiting = now - sbuf.st_mtime;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ waiting = now - sbuf.st_mtimespec.tv_sec;
+#else
+ waiting = now - sbuf.st_mtim.tv_sec;
+#endif
+ for (b = 0; b < numhisto; b++) {
+ if (waiting >= histo[b].delay) {
+ histo[b].count++;
+ break;
+ }
+ }
+ queue++;
+ }
+ }
+ for (i = 0; i < num; i++)
+ free(list[i]);
+ if (num > 0)
+ free(list);
+ }
+ if (chdir(startdir) < 0) {
+ __pmNotifyErr(LOG_ERR, "chdir(\"%s\") failed: %s\n",
+ startdir, osstrerror());
+ }
+ }
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * Initialise the agent (daemon only).
+ */
+void
+mailq_init(pmdaInterface *dp)
+{
+ if (dp->status != 0)
+ return;
+
+ __pmSetProcessIdentity(username);
+ dp->version.two.fetch = mailq_fetch;
+ pmdaSetFetchCallBack(dp, mailq_fetchCallBack);
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+pmdaInterface dispatch;
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "binlist", 1, 'b', "TIMES", "comma-separated histogram bins times" },
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ { "regex", 1, 'r', "RE", "regular expression for matching mail file names" },
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "b:D:d:l:r:U:?",
+ .long_options = longopts,
+ .short_usage = "[options] [queuedir]",
+};
+
+/*
+ * Set up the agent, running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ int c;
+ int i;
+ char namebuf[30];
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ if (getcwd(startdir, sizeof(startdir)) == NULL) {
+ fprintf(stderr, "%s: getcwd() failed: %s\n",
+ pmProgname, pmErrStr(-oserror()));
+ exit(1);
+ }
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "mailq" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, MAILQ,
+ "mailq.log", mypath);
+
+ while ((c = pmdaGetOptions(argc, argv, &opts, &dispatch)) != EOF) {
+ switch (c) {
+ case 'b':
+ if (mailq_histogram(opts.optarg) < 0)
+ opts.errors++;
+ break;
+
+ case 'r':
+ regexstring = opts.optarg;
+ c = regcomp(&mq_regex, regexstring, REG_EXTENDED | REG_NOSUB);
+ if (c != 0) {
+ regerror(c, &mq_regex, mypath, sizeof(mypath));
+ pmprintf("%s: cannot compile regular expression: %s\n",
+ pmProgname, mypath);
+ opts.errors++;
+ }
+ break;
+ }
+ }
+
+ if (opts.optind == argc - 1)
+ queuedir = argv[opts.optind];
+ else if (opts.optind != argc)
+ opts.errors++;
+
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.username)
+ username = opts.username;
+
+ if (histo == NULL) {
+ /* default histo bins, if not already done above ... */
+ numhisto = 7;
+ histo = (histo_t *)malloc(numhisto * sizeof(histo[0]));
+ if (histo == NULL) {
+ __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR);
+ }
+ histo[0].delay = 7 * 24 * 3600;
+ histo[1].delay = 3 * 24 * 3600;
+ histo[2].delay = 24 * 3600;
+ histo[3].delay = 8 * 3600;
+ histo[4].delay = 4 * 3600;
+ histo[5].delay = 1 * 3600;
+ histo[6].delay = 0;
+ }
+ else {
+ /* need to add last one and sort on descending time */
+ numhisto++;
+ histo = (histo_t *)realloc(histo, numhisto * sizeof(histo[0]));
+ if (histo == NULL) {
+ __pmNoMem("histo", numhisto * sizeof(histo[0]), PM_FATAL_ERR);
+ }
+ histo[numhisto-1].delay = 0;
+ qsort(histo, numhisto, sizeof(histo[0]), compare_delay);
+ }
+
+ _delay = (pmdaInstid *)malloc(numhisto * sizeof(_delay[0]));
+ if (_delay == NULL)
+ __pmNoMem("_delay", numhisto * sizeof(_delay[0]), PM_FATAL_ERR);
+
+ for (i = 0; i < numhisto; i++) {
+ time_t tmp;
+ _delay[i].i_inst = histo[i].delay;
+ histo[i].count = 0;
+ if (histo[i].delay == 0)
+ sprintf(namebuf, "recent");
+ else if (histo[i].delay < 60)
+ sprintf(namebuf, "%d-secs", (int)histo[i].delay);
+ else if (histo[i].delay < 60 * 60) {
+ tmp = histo[i].delay / 60;
+ if (tmp <= 1)
+ sprintf(namebuf, "1-min");
+ else
+ sprintf(namebuf, "%d-mins", (int)tmp);
+ }
+ else if (histo[i].delay < 24 * 60 * 60) {
+ tmp = histo[i].delay / (60 * 60);
+ if (tmp <= 1)
+ sprintf(namebuf, "1-hour");
+ else
+ sprintf(namebuf, "%d-hours", (int)tmp);
+ }
+ else {
+ tmp = histo[i].delay / (24 * 60 * 60);
+ if (tmp <= 1)
+ sprintf(namebuf, "1-day");
+ else
+ sprintf(namebuf, "%d-days", (int)tmp);
+ }
+ _delay[i].i_name = strdup(namebuf);
+ if (_delay[i].i_name == NULL) {
+ __pmNoMem("_delay[i].i_name", strlen(namebuf), PM_FATAL_ERR);
+ }
+ }
+
+ indomtab[DELAY_INDOM].it_numinst = numhisto;
+ indomtab[DELAY_INDOM].it_set = _delay;
+
+ pmdaOpenLog(&dispatch);
+ mailq_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/mailq/pmlogconf.summary b/src/pmdas/mailq/pmlogconf.summary
new file mode 100644
index 0000000..a0534a0
--- /dev/null
+++ b/src/pmdas/mailq/pmlogconf.summary
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident mailq PMDA summary information
+probe mailq.length exists ? include : exclude
+ mailq.length
+ mailq.deferred
diff --git a/src/pmdas/mailq/pmns b/src/pmdas/mailq/pmns
new file mode 100644
index 0000000..3de37aa
--- /dev/null
+++ b/src/pmdas/mailq/pmns
@@ -0,0 +1,24 @@
+/*
+ * Metrics for mailq PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+mailq {
+ length MAILQ:0:0
+ deferred MAILQ:0:1
+}
diff --git a/src/pmdas/mailq/root b/src/pmdas/mailq/root
new file mode 100644
index 0000000..eab4db5
--- /dev/null
+++ b/src/pmdas/mailq/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { mailq }
+
+#include "pmns"
+
diff --git a/src/pmdas/memcache/GNUmakefile b/src/pmdas/memcache/GNUmakefile
new file mode 100644
index 0000000..7edf21c
--- /dev/null
+++ b/src/pmdas/memcache/GNUmakefile
@@ -0,0 +1,54 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = memcache
+DOMAIN = MEMCACHE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl client.pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/memcache/Install b/src/pmdas/memcache/Install
new file mode 100755
index 0000000..a538702
--- /dev/null
+++ b/src/pmdas/memcache/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the memcached PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=memcache
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/memcache/Remove b/src/pmdas/memcache/Remove
new file mode 100755
index 0000000..4c39094
--- /dev/null
+++ b/src/pmdas/memcache/Remove
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the memcached PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=memcache
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/memcache/client.pl b/src/pmdas/memcache/client.pl
new file mode 100755
index 0000000..bcd3480
--- /dev/null
+++ b/src/pmdas/memcache/client.pl
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cache::Memcached;
+use vars qw( $memd $val );
+
+$memd = new Cache::Memcached {
+ 'servers' => [ "127.0.0.1:11211" ],
+ 'debug' => 0,
+ 'compress_threshold' => 10_000,
+};
+
+$memd->set("my_key", "Some value");
+$memd->set("object_key", { 'complex' => [ "object", 2, 4 ]});
+
+$val = $memd->get("my_key");
+$val = $memd->get("object_key");
+if ($val) { print $val->{'complex'}->[2]; }
+
+$memd->incr("key");
+$memd->decr("key");
+$memd->incr("key", 2);
+
diff --git a/src/pmdas/memcache/pmdamemcache.pl b/src/pmdas/memcache/pmdamemcache.pl
new file mode 100644
index 0000000..de232b2
--- /dev/null
+++ b/src/pmdas/memcache/pmdamemcache.pl
@@ -0,0 +1,307 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# The memcached PMDA
+# NOTE: Not all metrics are exported at the moment, in particular,
+# the per-slab and per-item statistics are not. It may be better to
+# manage instances differently as these values are dynamic - perhaps
+# have the monitored memcaches (the current indom) in the namespace,
+# like the old DBMS PMDAs.
+#
+use strict;
+use warnings;
+use PCP::PMDA;
+use vars qw( $pmda $id $n %caches );
+
+my $memcache_delay = 5; # refresh rate in seconds
+my @memcache_instances = ( 0 => '127.0.0.1:11211',
+ # 1 => '127.0.0.1:11212',
+ # 2 => '192.168.5.76:11211',
+ );
+# Configuration files for overriding the above settings
+for my $file ( pmda_config('PCP_PMDAS_DIR') . '/memcache/memcache.conf',
+ './memcache.conf' ) {
+ eval `cat $file` unless ! -f $file;
+}
+
+my $memcache_indom = 0; # one for each memcached
+my $query = "stats\r\nstats slabs\r\nstats items\r\n"; # sent to memcached
+
+sub memcache_stats_callback
+{
+ ( $id, $_ ) = @_;
+ # $pmda->log("memcache_stats_callback: id $id");
+
+ if (/^STAT items:(\d+):(\w+) (\d+)/) { # stats items
+ # $caches{$id}{"item$1"}{$2} = $3;
+ }
+ elsif (/^STAT (\d+):(\w+) (\d+)/) { # stats slabs
+ # $caches{$id}{"slab$1"}{$2} = $3;
+ }
+ elsif (/^STAT (\w+) (\d+)/) { # stats
+ $caches{$id}{$1} = $2;
+ }
+ elsif (!(/^END/)) { # unknown
+ $pmda->log("Eh?: $_");
+ }
+}
+
+sub memcache_connect
+{
+ # $pmda->log("memcache_connect: $#memcache_instances");
+
+ for ($id = 0; $id < $#memcache_instances; $id += 2) {
+ my ($host, $port) = split(/:/, $memcache_instances[$id+1]);
+ $pmda->add_sock($host, $port, \&memcache_stats_callback, $id);
+ }
+}
+
+sub memcache_timer_callback
+{
+ # $pmda->log("memcache_timer_callback");
+
+ for ($id = 0; $id < $#memcache_instances; $id += 2) {
+ $pmda->put_sock($id, $query);
+ }
+}
+
+sub memcache_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ # $pmda->log("memcache_fetch_callback: $cluster:$item ($inst)");
+
+ return (PM_ERR_INST, 0) unless ($inst != PM_IN_NULL);
+ return (PM_ERR_INST, 0) unless ($inst < $#memcache_instances);
+ $id = $memcache_instances[$inst];
+ return (PM_ERR_AGAIN, 0) unless defined($caches{$id});
+
+ if ($cluster == 0) {
+ if ($item == 0) { return ($caches{$id}{'pid'}, 1); }
+ elsif ($item == 1) { return ($caches{$id}{'uptime'}, 1); }
+ elsif ($item == 2) { return ($caches{$id}{'curr_items'}, 1); }
+ elsif ($item == 3) { return ($caches{$id}{'total_items'}, 1); }
+ elsif ($item == 4) { return ($caches{$id}{'bytes'}, 1); }
+ elsif ($item == 5) { return ($caches{$id}{'curr_connections'}, 1); }
+ elsif ($item == 6) { return ($caches{$id}{'total_connections'}, 1); }
+ elsif ($item == 7) { return ($caches{$id}{'connection_structures'}, 1); }
+ elsif ($item == 8) { return ($caches{$id}{'cmd_get'}, 1); }
+ elsif ($item == 9) { return ($caches{$id}{'cmd_set'}, 1); }
+ elsif ($item == 10) { return ($caches{$id}{'get_hits'}, 1); }
+ elsif ($item == 11) { return ($caches{$id}{'get_misses'}, 1); }
+ elsif ($item == 12) { return ($caches{$id}{'bytes_read'}, 1); }
+ elsif ($item == 13) { return ($caches{$id}{'bytes_written'}, 1); }
+ elsif ($item == 14) { return ($caches{$id}{'limit_maxbytes'}, 1); }
+ }
+# elsif ($cluster == 1) {
+# # many different slabs (X..Y), and 7 metrics in this cluster
+# if ($item > 7 * 11) { return (PM_ERR_PMID, 0); }
+# $id = int($item / 7) + 6;
+# $item %= 7;
+# my $slab = "slab$id";
+#
+# return (PM_ERR_AGAIN, 0) unless defined($caches{$id}{$slab});
+# if ($item == 0) { return ($caches{$id}{$slab}{'chunk_size'}, 1); }
+# elsif ($item == 1) { return ($caches{$id}{$slab}{'chunks_per_page'}, 1); }
+# elsif ($item == 2) { return ($caches{$id}{$slab}{'total_pages'}, 1); }
+# elsif ($item == 3) { return ($caches{$id}{$slab}{'total_chunks'}, 1); }
+# elsif ($item == 4) { return ($caches{$id}{$slab}{'used_chunks'}, 1); }
+# elsif ($item == 5) { return ($caches{$id}{$slab}{'free_chunks'}, 1); }
+# elsif ($item == 6) { return ($caches{$id}{$slab}{'free_chunks_end'}, 1); }
+# }
+ elsif ($cluster == 2) {
+ if ($item == 0) { return ($caches{$id}{'active_slabs'}, 1); }
+ elsif ($item == 1) { return ($caches{$id}{'total_malloced'}, 1); }
+ }
+# elsif ($cluster == 3) {
+# # many different slabs (X..Y), and 2 metrics in this cluster
+# if ($item > 2 * [Y]) { return (PM_ERR_PMID, 0); }
+# $id = int($item / 2) + [X];
+# $item %= 2;
+# my $itemid = "item$id";
+#
+# return (PM_ERR_AGAIN, 0) unless defined($caches{$id}{$itemid});
+# if ($item == 0) { return ($caches{$id}{$itemid}{'count'}, 1); }
+# elsif ($item == 1) { return ($caches{$id}{$itemid}{'age'}, 1); }
+# }
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('memcache', 89);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.pid', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.uptime', '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.current_items', '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.total_items', '', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U64, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'memcache.bytes', '', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.current_connections', '', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'memcache.total_connections', '', '');
+$pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.connection_structures', '', '');
+$pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'memcache.gets', '', '');
+$pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'memcache.sets', '', '');
+$pmda->add_metric(pmda_pmid(0,10), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'memcache.hits', '', '');
+$pmda->add_metric(pmda_pmid(0,11), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'memcache.misses', '', '');
+$pmda->add_metric(pmda_pmid(0,12), PM_TYPE_U64, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'memcache.bytes_read', '', '');
+$pmda->add_metric(pmda_pmid(0,13), PM_TYPE_U64, $memcache_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'memcache.bytes_written', '', '');
+$pmda->add_metric(pmda_pmid(0,14), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'memcache.limit_maxbytes', '', '');
+# $id = 0;
+# foreach $n (6 .. 17) { # stats slabs (N=6-17)
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+# "memcache.slabs.slab$n.chunk_size", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.chunks_per_page", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.total_pages", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.total_chunks", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.used_chunks", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.free_chunks", '', '');
+# $pmda->add_metric(pmda_pmid(1,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+# "memcache.slabs.slab$n.free_chunks_end", '', '');
+# }
+
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'memcache.active_slabs', '', '');
+$pmda->add_metric(pmda_pmid(2,1), PM_TYPE_U32, $memcache_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'memcache.total_malloced', '', '');
+
+# $id = 0;
+# foreach $n (6 .. 17) { # stats items (N=6-17)
+# $pmda->add_metric(pmda_pmid(3,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+# "memcache.items.item$n.count", '', '');
+# $pmda->add_metric(pmda_pmid(3,$id++), PM_TYPE_U32, $memcache_indom,
+# PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+# "memcache.items.item$n.age", '', '');
+# }
+
+$pmda->add_indom($memcache_indom, \@memcache_instances,
+ 'Instance domain exporting each memcache daemon', '');
+
+$pmda->add_timer($memcache_delay, \&memcache_timer_callback, 0);
+$pmda->set_fetch_callback(\&memcache_fetch_callback);
+
+&memcache_connect;
+&memcache_timer_callback;
+
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdamemcache - memcache performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+This PMDA extracts performance data from memcached, a distributed memory
+caching daemon commonly used to improve web serving performance. A farm
+of memcached processes over multiple servers can be utilised by a single
+web application, increasing the total available object cache size, and
+decreasing the database load associated with smaller cache sizes. This
+system is described in detail at http://www.danga.com/memcached.
+
+=head1 INSTALLATION
+
+Configure B<pmdamemcache> to extract the values from set of hosts
+used in the memcache farm. These hosts can be listed in the
+$PCP_PMDAS_DIR/memcache/memcache.conf file, in the format (i.e.
+Perl array) described at the top of pmdamemcache.pl. A custom
+refresh rate can also be configured using this mechanism.
+
+ # cd $PCP_PMDAS_DIR/memcache
+ # [ edit memcache.conf ]
+
+Once this is setup, you can access the names and values for the
+memcache performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/memcache
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/memcache
+ # ./Remove
+
+B<pmdamemcache> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/memcache/memcache.conf
+
+configuration file listing monitored memcache instances
+
+=item $PCP_PMDAS_DIR/memcache/Install
+
+installation script for the B<pmdamemcache> agent
+
+=item $PCP_PMDAS_DIR/memcache/Remove
+
+undo installation script for the B<pmdamemcache> agent
+
+=item $PCP_LOG_DIR/pmcd/memcache.log
+
+default log file for error messages from B<pmdamemcache>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and memcached(1).
diff --git a/src/pmdas/mmv/GNUmakefile b/src/pmdas/mmv/GNUmakefile
new file mode 100644
index 0000000..e674bd1
--- /dev/null
+++ b/src/pmdas/mmv/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mmv
+TARGET = mmvdump$(EXECSUFFIX)
+CFILES = mmvdump.c
+
+SUBDIRS = src
+
+LCFILES = acme.c
+LTARGET = acme$(EXECSUFFIX)
+LSRCFILES = $(LCFILES) README.demos Makefile.demos
+TARGETS = $(TARGET) $(LTARGET)
+
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_mmv/src -L$(TOPDIR)/src/libpcp/src
+LLDLIBS = -lpcp_mmv $(PCPLIB)
+LDIRT = mmvdump acme
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+DEMODIR = $(PCP_DEMOS_DIR)/$(IAM)
+DEMOFILES = README.demos Makefile.demos
+CONF_LINE = "mmv 70 dso mmv_init $(PCP_PMDAS_DIR)/mmv/pmda_mmv.$(DSOSUFFIX)"
+
+default_pcp default :: $(TARGETS)
+
+default_pcp default :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+include $(BUILDRULES)
+
+install_pcp install :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp install :: $(SUBDIRS)
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(TARGET) $(PMDADIR)/$(TARGET)
+ $(INSTALL) -m 755 -d $(DEMODIR)
+ $(INSTALL) -m 644 Makefile.demos $(DEMODIR)/Makefile
+ $(INSTALL) -m 644 README.demos $(DEMODIR)/README
+ $(INSTALL) -m 644 $(CFILES) $(LCFILES) $(DEMODIR)
+
+# check-build only, binary not installed (but source is)
+$(LTARGET): acme.c
+ $(CCF) -o $@ $^ $(LDFLAGS) $(LDLIBS)
diff --git a/src/pmdas/mmv/Makefile.demos b/src/pmdas/mmv/Makefile.demos
new file mode 100644
index 0000000..5c6341c
--- /dev/null
+++ b/src/pmdas/mmv/Makefile.demos
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = /bin/sh
+CC = cc
+CFLAGS = -g
+
+TARGETS = acme mmvdump
+
+default: $(TARGETS)
+
+mmvdump: mmvdump.c
+ $(CC) $(CFLAGS) -o $@ $^ -lpcp
+
+acme: acme.c
+ $(CC) $(CFLAGS) -o $@ $^ -lpcp_mmv -lpcp
+
+clean:
+ rm -f *.o
+
+clobber: clean
+ rm -f $(TARGETS)
diff --git a/src/pmdas/mmv/README.demos b/src/pmdas/mmv/README.demos
new file mode 100644
index 0000000..5ed4ef9
--- /dev/null
+++ b/src/pmdas/mmv/README.demos
@@ -0,0 +1,26 @@
+sample pcp_mmv applications
+===========================
+
+acme.c
+ is a sample application that is instrumented with the pcp_mmv
+interface to generate metrics available from the MMV PMDA
+(Performance Metrics Domain Agent). It runs without exiting,
+and continually updates the mmv.acme.* metric values. Detailed
+discussion about the workings of this application and each of
+the instrumentation API calls it uses, is available as part of
+the "Performance Co-Pilot Programmer's Guide".
+
+mmvdump.c
+ an example program that dumps the contents of a (memory mapped)
+mmv(5) format file, as created by the pcp_mmv library and read by
+the MMV PMDA. The binary is also shipped as part of pcp and can
+be found installed below ${PCP_PMDAS_DIR}/mmv.
+
+
+All source is shipped as part of pcp as well and is installed in
+${PCP_DEMOS_DIR}/mmv. If you have a C toolchain installed, the
+sources and Makefile in this directory may be used to create the
+functionally equivalent binaries, by entering the command
+
+ % make acme mmvdump
+
diff --git a/src/pmdas/mmv/acme.c b/src/pmdas/mmv/acme.c
new file mode 100644
index 0000000..212ce39
--- /dev/null
+++ b/src/pmdas/mmv/acme.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/mmv_stats.h>
+
+static mmv_instances_t products[] = {
+ { .internal = 0, .external = "Anvils" },
+ { .internal = 1, .external = "Rockets" },
+ { .internal = 2, .external = "Giant_Rubber_Bands" },
+};
+#define ACME_PRODUCTS_INDOM 61
+#define ACME_PRODUCTS_COUNT (sizeof(products)/sizeof(products[0]))
+
+static mmv_indom_t indoms[] = {
+ { .serial = ACME_PRODUCTS_INDOM,
+ .count = ACME_PRODUCTS_COUNT,
+ .instances = products,
+ .shorttext = "Acme products",
+ .helptext = "Most popular products produced by the Acme Corporation",
+ },
+};
+
+static mmv_metric_t metrics[] = {
+ { .name = "products.count",
+ .item = 7,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,0,1,0,0,PM_COUNT_ONE),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Acme factory product throughput",
+ .helptext =
+"Monotonic increasing counter of products produced in the Acme Corporation\n"
+"factory since starting the Acme production application. Quality guaranteed.",
+ },
+ { .name = "products.time",
+ .item = 8,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,1,0,0,PM_TIME_USEC,0),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Machine time spent producing Acme products",
+ .helptext =
+"Machine time spent producing Acme Corporation products. Does not include\n"
+"time in queues waiting for production machinery.",
+ },
+ { .name = "products.queuetime",
+ .item = 10,
+ .type = MMV_TYPE_U64,
+ .semantics = MMV_SEM_COUNTER,
+ .dimension = MMV_UNITS(0,1,0,0,PM_TIME_USEC,0),
+ .indom = ACME_PRODUCTS_INDOM,
+ .shorttext = "Queued time while producing Acme products",
+ .helptext =
+"Time spent in the queue waiting to build Acme Corporation products,\n"
+"while some other Acme product was being built instead of this one.",
+ },
+};
+
+#define INDOM_COUNT (sizeof(indoms)/sizeof(indoms[0]))
+#define METRIC_COUNT (sizeof(metrics)/sizeof(metrics[0]))
+#define ACME_CLUSTER 321 /* PMID cluster identifier */
+
+int
+main(int argc, char * argv[])
+{
+ void *base;
+ pmAtomValue *count[ACME_PRODUCTS_COUNT];
+ pmAtomValue *machine[ACME_PRODUCTS_COUNT];
+ pmAtomValue *inqueue[ACME_PRODUCTS_COUNT];
+ unsigned int working;
+ unsigned int product;
+ unsigned int i;
+
+ base = mmv_stats_init("acme", ACME_CLUSTER, 0,
+ metrics, METRIC_COUNT, indoms, INDOM_COUNT);
+ if (!base) {
+ perror("mmv_stats_init");
+ return 1;
+ }
+
+ for (i = 0; i < ACME_PRODUCTS_COUNT; i++) {
+ count[i] = mmv_lookup_value_desc(base,
+ "products.count", products[i].external);
+ machine[i] = mmv_lookup_value_desc(base,
+ "products.time", products[i].external);
+ inqueue[i] = mmv_lookup_value_desc(base,
+ "products.queuetime", products[i].external);
+ }
+
+ while (1) {
+ /* choose a random number between 0-N -> product */
+ product = rand() % ACME_PRODUCTS_COUNT;
+
+ /* assign a time spent "working" on this product */
+ working = rand() % 50000;
+
+ /* pretend to "work" so process doesn't burn CPU */
+ usleep(working);
+
+ /* update the memory mapped values for this one: */
+ /* one more product produced and work time spent */
+ mmv_inc_value(base, machine[product], working); /* API */
+ count[product]->ull += 1; /* or direct mmap update */
+
+ /* all other products are "queued" for this time */
+ for (i = 0; i < ACME_PRODUCTS_COUNT; i++)
+ if (i != product)
+ mmv_inc_value(base, inqueue[i], working);
+ }
+
+ mmv_stats_stop("acme", base);
+ return 0;
+}
diff --git a/src/pmdas/mmv/mmvdump.c b/src/pmdas/mmv/mmvdump.c
new file mode 100644
index 0000000..0943618
--- /dev/null
+++ b/src/pmdas/mmv/mmvdump.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2013 Red Hat.
+ * Copyright (C) 2009 Aconex. All Rights Reserved.
+ * Copyright (C) 2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/mmv_stats.h>
+#include <pcp/mmv_dev.h>
+#include <pcp/impl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+
+void
+dump_indoms(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string;
+ mmv_disk_indom_t * indom = (mmv_disk_indom_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, indoms offset %" PRIu64 " (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ __uint64_t off = offset + i * sizeof(mmv_disk_indom_t);
+ printf(" [%u/%"PRIi64"] %d instances, starting at offset %"PRIi64"\n",
+ indom[i].serial, off, indom[i].count, indom[i].offset);
+ if (indom[i].shorttext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + indom[i].shorttext);
+ printf(" shorttext=%s\n", string->payload);
+ }
+ else
+ printf(" (no shorttext)\n");
+ if (indom[i].helptext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + indom[i].helptext);
+ printf(" helptext=%s\n", string->payload);
+ }
+ else
+ printf(" (no helptext)\n");
+ }
+}
+
+void
+dump_insts(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_instance_t * inst = (mmv_disk_instance_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, instances offset %"PRIi64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ mmv_disk_indom_t * indom = (mmv_disk_indom_t *)
+ ((char *)addr + inst[i].indom);
+ printf(" [%u/%"PRIi64"] instance = [%d or \"%s\"]\n",
+ indom->serial,
+ (offset + i * sizeof(mmv_disk_instance_t)),
+ inst[i].internal, inst[i].external);
+ }
+}
+
+static char *
+metrictype(int mtype)
+{
+ char *type;
+
+ switch (mtype) {
+ case MMV_TYPE_I32:
+ type = "32-bit int";
+ break;
+ case MMV_TYPE_U32:
+ type = "32-bit unsigned int";
+ break;
+ case MMV_TYPE_I64:
+ type = "64-bit int";
+ break;
+ case MMV_TYPE_U64:
+ type = "64-bit unsigned int";
+ break;
+ case MMV_TYPE_FLOAT:
+ type = "float";
+ break;
+ case MMV_TYPE_DOUBLE:
+ type = "double";
+ break;
+ case MMV_TYPE_STRING:
+ type = "string";
+ break;
+ case MMV_TYPE_ELAPSED:
+ type = "elapsed";
+ break;
+ default:
+ type = "?";
+ break;
+ }
+ return type;
+}
+
+static char *
+metricsem(int msem)
+{
+ char *sem;
+
+ switch (msem) {
+ case PM_SEM_COUNTER:
+ sem = "counter";
+ break;
+ case PM_SEM_INSTANT:
+ sem = "instant";
+ break;
+ case PM_SEM_DISCRETE:
+ sem = "discrete";
+ break;
+ default:
+ sem = "?";
+ break;
+ }
+ return sem;
+}
+
+void
+dump_metrics(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: toc offset %ld, metrics offset %"PRIi64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ __uint64_t off = offset + i * sizeof(mmv_disk_metric_t);
+ printf(" [%u/%"PRIi64"] %s\n", m[i].item, off, m[i].name);
+ printf(" type=%s (0x%x), sem=%s (0x%x), pad=0x%x\n",
+ metrictype(m[i].type), m[i].type,
+ metricsem(m[i].semantics), m[i].semantics,
+ m[i].padding);
+ printf(" units=%s\n", pmUnitsStr(&m[i].dimension));
+ if (m[i].indom != PM_INDOM_NULL && m[i].indom != 0)
+ printf(" indom=%d\n", m[i].indom);
+ else
+ printf(" (no indom)\n");
+ if (m[i].shorttext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + m[i].shorttext);
+ printf(" shorttext=%s\n", string->payload);
+ }
+ else
+ printf(" (no shorttext)\n");
+ if (m[i].helptext) {
+ string = (mmv_disk_string_t *)
+ ((char *)addr + m[i].helptext);
+ printf(" helptext=%s\n", string->payload);
+ }
+ else
+ printf(" (no helptext)\n");
+ }
+}
+
+void
+dump_values(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_value_t * vals = (mmv_disk_value_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, values offset %"PRIu64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ mmv_disk_string_t * string;
+ mmv_disk_metric_t * m = (mmv_disk_metric_t *)
+ ((char *)addr + vals[i].metric);
+ __uint64_t off = offset + i * sizeof(mmv_disk_value_t);
+
+ printf(" [%u/%"PRIu64"] %s", m->item, off, m->name);
+ if (m->indom && m->indom != PM_IN_NULL) {
+ mmv_disk_instance_t *indom = (mmv_disk_instance_t *)
+ ((char *)addr + vals[i].instance);
+ printf("[%d or \"%s\"]",
+ indom->internal, indom->external);
+ }
+
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ printf(" = %d", vals[i].value.l);
+ break;
+ case MMV_TYPE_U32:
+ printf(" = %u", vals[i].value.ul);
+ break;
+ case MMV_TYPE_I64:
+ printf(" = %" PRIi64, vals[i].value.ll);
+ break;
+ case MMV_TYPE_U64:
+ printf(" = %" PRIu64, vals[i].value.ull);
+ break;
+ case MMV_TYPE_FLOAT:
+ printf(" = %f", vals[i].value.f);
+ break;
+ case MMV_TYPE_DOUBLE:
+ printf(" = %lf", vals[i].value.d);
+ break;
+ case MMV_TYPE_STRING:
+ string = (mmv_disk_string_t *)((char *)addr + vals[i].extra);
+ printf(" = \"%s\"", string->payload);
+ break;
+ case MMV_TYPE_ELAPSED: {
+ struct timeval tv;
+ __int64_t t;
+
+ __pmtimevalNow(&tv);
+ t = vals[i].value.ll;
+ if (vals[i].extra < 0)
+ t += ((tv.tv_sec*1e6 + tv.tv_usec) + vals[i].extra);
+ printf(" = %"PRIi64" (value=%"PRIi64"/extra=%"PRIi64")",
+ t, vals[i].value.ll, vals[i].extra);
+ if (vals[i].extra > 0)
+ printf("Bad ELAPSED 'extra' value found!");
+ break;
+ }
+ default:
+ printf("Unknown type %d", m->type);
+ }
+ putchar('\n');
+ }
+}
+
+void
+dump_strings(void *addr, int idx, long base, __uint64_t offset, __int32_t count)
+{
+ int i;
+ mmv_disk_string_t * string = (mmv_disk_string_t *)
+ ((char *)addr + offset);
+
+ printf("\nTOC[%d]: offset %ld, string offset %"PRIu64" (%d entries)\n",
+ idx, base, offset, count);
+
+ for (i = 0; i < count; i++) {
+ printf(" [%u/%"PRIu64"] %s\n",
+ i+1, offset + i * sizeof(mmv_disk_string_t),
+ string[i].payload);
+ }
+}
+
+int
+dump(const char *file, void *addr)
+{
+ int i;
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *) addr;
+ mmv_disk_toc_t * toc = (mmv_disk_toc_t *)
+ ((char *)addr + sizeof(mmv_disk_header_t));
+
+ if (strcmp(hdr->magic, "MMV")) {
+ printf("Bad magic: %c%c%c\n",
+ hdr->magic[0], hdr->magic[1], hdr->magic[2]);
+ return 1;
+ }
+ if (hdr->version != MMV_VERSION) {
+ printf("version %d not supported\n", hdr->version);
+ return 1;
+ }
+
+ printf("MMV file = %s\n", file);
+ printf("Version = %d\n", hdr->version);
+ printf("Generated = %"PRIu64"\n", hdr->g1);
+ if (hdr->g1 != hdr->g2) {
+ printf("Generated2 = %"PRIu64"\n", hdr->g2);
+ printf("Mismatched generation numbers\n");
+ return 1;
+ }
+ printf("TOC count = %u\n", hdr->tocs);
+ printf("Cluster = %u\n", hdr->cluster);
+ printf("Process = %d\n", hdr->process);
+ printf("Flags = 0x%x\n", hdr->flags);
+
+ for (i = 0; i < hdr->tocs; i++) {
+ __uint64_t base = ((char *)&toc[i] - (char *)addr);
+ switch (toc[i].type) {
+ case MMV_TOC_INDOMS:
+ dump_indoms(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_INSTANCES:
+ dump_insts(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_VALUES:
+ dump_values(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_METRICS:
+ dump_metrics(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ case MMV_TOC_STRINGS:
+ dump_strings(addr, i, base, toc[i].offset, toc[i].count);
+ break;
+ default:
+ printf("Unrecognised TOC[%d] type: 0x%x\n", i, toc[i].type);
+ }
+ }
+ return 0;
+}
+
+int
+main(int argc, char * argv[])
+{
+ int fd;
+ char file[MAXPATHLEN];
+
+ if (argc > 2) {
+ printf("USAGE: %s <filename>\n", argv[0]);
+ exit(1);
+ }
+ if (argc > 1)
+ strncpy(file, argv[1], MAXPATHLEN);
+ else
+ snprintf(file, MAXPATHLEN, "%s%cmmv%ctest",
+ pmGetConfig("PCP_TMP_DIR"),
+ __pmPathSeparator(), __pmPathSeparator());
+ file[MAXPATHLEN-1] = '\0';
+
+ if ((fd = open(file, O_RDONLY)) < 0)
+ perror(file);
+ else {
+ struct stat s;
+ void * addr;
+
+ if (fstat(fd, &s) < 0)
+ perror(file);
+ else if ((addr = __pmMemoryMap(fd, s.st_size, 0)) != NULL)
+ return dump(file, addr);
+ }
+ return 1;
+}
diff --git a/src/pmdas/mmv/src/GNUmakefile b/src/pmdas/mmv/src/GNUmakefile
new file mode 100644
index 0000000..421c38b
--- /dev/null
+++ b/src/pmdas/mmv/src/GNUmakefile
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2000-2001,2009 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mmv
+DOMAIN = MMV
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+PMDAINIT = $(IAM)_init
+TARGETS = $(CMDTARGET) $(LIBTARGET)
+
+CFILES = mmv.c
+VERSION_SCRIPT = exports
+LSRCFILES = Install Remove root_mmv
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+LDIRT = domain.h *.log pmns $(VERSION_SCRIPT)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+default_pcp default: $(TARGETS) pmns
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 755 $(TARGETS) Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmns $(PMDADIR)/root_mmv
+ $(INSTALL) -m 644 root_mmv $(PCP_VAR_DIR)/pmns/root_mmv
+
+$(CMDTARGET): $(OBJECTS)
+
+$(IAM).o : domain.h
+$(LIBTARGET) : $(VERSION_SCRIPT)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+pmns :
+ $(LN_S) -f root_mmv pmns
diff --git a/src/pmdas/mmv/src/Install b/src/pmdas/mmv/src/Install
new file mode 100755
index 0000000..d8f148b
--- /dev/null
+++ b/src/pmdas/mmv/src/Install
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# Copyright (C) 1997,2009 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (C) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mmv
+dso_opt=true
+socket_opt=true
+socket_inet_def=2081
+forced_restart=false
+pmda_interface=4
+pmns_source=root_mmv
+
+if [ ! -e "$PCP_TMP_DIR/mmv" ]
+then
+ echo "creating $PCP_TMP_DIR/mmv"
+ mkdir -p -m 1777 "$PCP_TMP_DIR/mmv"
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/mmv/src/Remove b/src/pmdas/mmv/src/Remove
new file mode 100755
index 0000000..a463371
--- /dev/null
+++ b/src/pmdas/mmv/src/Remove
@@ -0,0 +1,23 @@
+#! /bin/sh
+#
+# Copyright (C) 1997,2009 Silicon Graphics, Inc., All Rights Reserved.
+# Copyright (C) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mmv
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/mmv/src/mmv.c b/src/pmdas/mmv/src/mmv.c
new file mode 100644
index 0000000..ecc97ed
--- /dev/null
+++ b/src/pmdas/mmv/src/mmv.c
@@ -0,0 +1,915 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2009-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2000,2009 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ *
+ * MMV PMDA
+ *
+ * This PMDA uses specially formatted files in either /var/tmp/mmv or some
+ * other directory, as specified on the command line. Each file represents
+ * a separate "cluster" of values with flat name structure for each cluster.
+ * Names for the metrics are optionally prepended with mmv and then the name
+ * of the file (by default - this can be changed).
+ */
+
+#include "pmapi.h"
+#include "mmv_stats.h"
+#include "mmv_dev.h"
+#include "impl.h"
+#include "pmda.h"
+#include "./domain.h"
+#include <sys/stat.h>
+#include <ctype.h>
+
+static int isDSO = 1;
+static char *username;
+
+/* command line option handling - both short and long options */
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+static pmdaMetric * metrics;
+static int mcnt;
+static pmdaIndom * indoms;
+static int incnt;
+
+static int reload;
+static __pmnsTree * pmns;
+static int statsdir_code; /* last statsdir stat code */
+static time_t statsdir_ts; /* last statsdir timestamp */
+static char * prefix = "mmv";
+
+static char * pcptmpdir; /* probably /var/tmp */
+static char * pcpvardir; /* probably /var/pcp */
+static char * pcppmdasdir; /* probably /var/pcp/pmdas */
+static char pmnsdir[MAXPATHLEN]; /* pcpvardir/pmns */
+static char statsdir[MAXPATHLEN]; /* pcptmpdir/<prefix> */
+
+typedef struct {
+ char * name; /* strdup client name */
+ void * addr; /* mmap */
+ mmv_disk_value_t * values; /* values in mmap */
+ mmv_disk_metric_t * metrics; /* metric descs in mmap */
+ int vcnt; /* number of values */
+ int mcnt; /* number of metrics */
+ pid_t pid; /* process identifier */
+ int cluster; /* cluster identifier */
+ __int64_t len; /* mmap region len */
+ __uint64_t gen; /* generation number on open */
+} stats_t;
+
+static stats_t * slist;
+static int scnt;
+
+/*
+ * Choose an unused cluster ID while honouring specific requests.
+ * If a specific (non-zero) cluster is requested we always use it.
+ */
+static int
+choose_cluster(int requested, const char *path)
+{
+ int i;
+
+ if (!requested) {
+ int next_cluster = 1;
+
+ for (i = 0; i < scnt; i++) {
+ if (slist[i].cluster == next_cluster) {
+ next_cluster++;
+ i = 0; /* restart, we're filling holes */
+ }
+ }
+ return next_cluster;
+ }
+
+ for (i = 0; i < scnt; i++) {
+ if (slist[i].cluster == requested) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "MMV: %s: duplicate cluster %d in use",
+ pmProgname, requested);
+ break;
+ }
+ }
+ return requested;
+}
+
+static int
+create_client_stat(const char *client, const char *path, size_t size)
+{
+ int fd;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_client_stat: %s, %s", client, path);
+
+ if ((fd = open(path, O_RDONLY)) >= 0) {
+ void *m = __pmMemoryMap(fd, size, 0);
+
+ close(fd);
+ if (m != NULL) {
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *)m;
+ int cluster;
+
+ if (strncmp(hdr->magic, "MMV", 4)) {
+ __pmMemoryUnmap(m, size);
+ return -EINVAL;
+ }
+
+ if (hdr->version != MMV_VERSION) {
+ __pmNotifyErr(LOG_ERR, "%s: %s client version %d "
+ "not supported (current is %d)",
+ pmProgname, prefix, hdr->version, MMV_VERSION);
+ __pmMemoryUnmap(m, size);
+ return -ENOSYS;
+ }
+
+ if (!hdr->g1 || hdr->g1 != hdr->g2) {
+ /* still in flux, wait till next time */
+ __pmMemoryUnmap(m, size);
+ return -EAGAIN;
+ }
+
+ /* optionally verify the creator PID is running */
+ if (hdr->process && (hdr->flags & MMV_FLAG_PROCESS) &&
+ !__pmProcessExists((pid_t)hdr->process)) {
+ __pmMemoryUnmap(m, size);
+ return -ESRCH;
+ }
+
+ /* all checks out, we'll use this one */
+ cluster = choose_cluster(hdr->cluster, path);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: %s: loading %s client: %d \"%s\"",
+ pmProgname, prefix, cluster, path);
+
+ slist = realloc(slist, sizeof(stats_t)*(scnt+1));
+ if (slist != NULL) {
+ slist[scnt].name = strdup(client);
+ slist[scnt].addr = m;
+ slist[scnt].pid = (pid_t)((hdr->flags & MMV_FLAG_PROCESS)? hdr->process : 0);
+ slist[scnt].cluster = cluster;
+ slist[scnt].mcnt = 0;
+ slist[scnt].gen = hdr->g1;
+ slist[scnt].len = size;
+ scnt++;
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: client \"%s\" out of memory - %s",
+ pmProgname, client, osstrerror());
+ __pmMemoryUnmap(m, size);
+ scnt = 0;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: failed to memory map \"%s\" - %s",
+ pmProgname, path, osstrerror());
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: failed to open client file \"%s\" - %s",
+ pmProgname, client, osstrerror());
+ }
+ return 0;
+}
+
+/* check validity of client metric name, return non-zero if bad or duplicate */
+static int
+verify_metric_name(const char *name, int pos, stats_t *s)
+{
+ const char *p = name;
+ pmID pmid;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_metric_name: %s", name);
+
+ if (p == NULL || *p == '\0' || !isalpha((int)*p)) {
+ __pmNotifyErr(LOG_WARNING, "Invalid metric[%d] name start in %s, ignored",
+ pos, s->name);
+ return -EINVAL;
+ }
+ for (++p; (p != NULL && *p != '\0'); p++) {
+ if (isalnum((int)*p) || *p == '_' || *p == '.')
+ continue;
+ __pmNotifyErr(LOG_WARNING, "invalid metric[%d] name in %s (@%c), ignored",
+ pos, s->name, *p);
+ return -EINVAL;
+ }
+ if (pmdaTreePMID(pmns, name, &pmid) == 0)
+ return -EEXIST;
+ return 0;
+}
+
+/* check client item number validity - must not be too large to fit in PMID! */
+static int
+verify_metric_item(unsigned int item, char *name, stats_t *s)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_metric_item: %u - %s", item, name);
+
+ if (pmid_item(item) != item) {
+ __pmNotifyErr(LOG_WARNING, "invalid item %u (%s) in %s, ignored",
+ item, name, s->name);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int
+create_metric(pmdaExt *pmda, stats_t *s, mmv_disk_metric_t *m, char *name, pmID pmid)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_metric: %s - %s", name, pmIDStr(pmid));
+
+ metrics = realloc(metrics, sizeof(pmdaMetric) * (mcnt + 1));
+ if (metrics == NULL) {
+ __pmNotifyErr(LOG_ERR, "cannot grow MMV metric list: %s", s->name);
+ return -ENOMEM;
+ }
+
+ metrics[mcnt].m_user = NULL;
+ metrics[mcnt].m_desc.pmid = pmid;
+
+ if (m->type == MMV_TYPE_ELAPSED) {
+ pmUnits unit = PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0);
+ metrics[mcnt].m_desc.sem = PM_SEM_COUNTER;
+ metrics[mcnt].m_desc.type = MMV_TYPE_I64;
+ metrics[mcnt].m_desc.units = unit;
+ } else {
+ if (m->semantics)
+ metrics[mcnt].m_desc.sem = m->semantics;
+ else
+ metrics[mcnt].m_desc.sem = PM_SEM_COUNTER;
+ metrics[mcnt].m_desc.type = m->type;
+ memcpy(&metrics[mcnt].m_desc.units, &m->dimension, sizeof(pmUnits));
+ }
+ metrics[mcnt].m_desc.indom = (!m->indom || m->indom == PM_INDOM_NULL) ?
+ PM_INDOM_NULL : pmInDom_build(pmda->e_domain,
+ (s->cluster << 11) | m->indom);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: map_stats adding metric[%d] %s %s from %s\n",
+ mcnt, name, pmIDStr(pmid), s->name);
+
+ mcnt++;
+ __pmAddPMNSNode(pmns, pmid, name);
+
+ return 0;
+}
+
+/* check client serial number validity, and check for a duplicate */
+static int
+verify_indom_serial(pmdaExt *pmda, int serial, stats_t *s, pmInDom *p, pmdaIndom **i)
+{
+ int index;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: verify_indom_serial: %u", serial);
+
+ if (pmInDom_serial(serial) != serial) {
+ __pmNotifyErr(LOG_WARNING, "invalid serial %u in %s, ignored",
+ serial, s->name);
+ return -EINVAL;
+ }
+
+ *p = pmInDom_build(pmda->e_domain, (s->cluster << 11) | serial);
+ for (index = 0; index < incnt; index++) {
+ *i = &indoms[index];
+ if (indoms[index].it_indom == *p)
+ return -EEXIST;
+ }
+ *i = NULL;
+ return 0;
+}
+
+static int
+update_indom(pmdaExt *pmda, stats_t *s, mmv_disk_indom_t *id, pmdaIndom *ip)
+{
+ int i, j, size, newinsts = 0;
+ mmv_disk_instance_t *in = (mmv_disk_instance_t *)((char *)s->addr + id->offset);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: update_indom: %u (%d insts)",
+ id->serial, ip->it_numinst);
+
+ /* first calculate how many new instances, so we know what to alloc */
+ for (i = 0; i < id->count; i++) {
+ for (j = 0; j < ip->it_numinst; j++)
+ if (ip->it_set[j].i_inst == in[i].internal)
+ continue;
+ if (j == ip->it_numinst)
+ newinsts++;
+ }
+
+ if (!newinsts)
+ return 0;
+
+ /* allocate memory, then append new instances to the known set */
+ size = sizeof(pmdaInstid) * (ip->it_numinst + newinsts);
+ ip->it_set = (pmdaInstid *)realloc(ip->it_set, size);
+ if (ip->it_set != NULL) {
+ for (i = 0; i < id->count; i++) {
+ for (j = 0; j < ip->it_numinst; j++)
+ if (ip->it_set[j].i_inst == in[j].internal)
+ continue;
+ if (j == ip->it_numinst) {
+ ip->it_set[j].i_inst = in[i].internal;
+ ip->it_set[j].i_name = in[i].external;
+ ip->it_numinst++;
+ }
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: cannot get memory for instance list in %s",
+ pmProgname, s->name);
+ ip->it_numinst = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static int
+create_indom(pmdaExt *pmda, stats_t *s, mmv_disk_indom_t *id, pmInDom indom)
+{
+ int i;
+ pmdaIndom *ip;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: create_indom: %u", id->serial);
+
+ indoms = realloc(indoms, sizeof(pmdaIndom) * (incnt + 1));
+ if (indoms == NULL) {
+ __pmNotifyErr(LOG_ERR, "%s: cannot grow indom list in %s",
+ pmProgname, s->name);
+ return -ENOMEM;
+ }
+ ip = &indoms[incnt++];
+ ip->it_indom = indom;
+ ip->it_set = (pmdaInstid *)calloc(id->count, sizeof(pmdaInstid));
+ if (ip->it_set != NULL) {
+ mmv_disk_instance_t * in = (mmv_disk_instance_t *)
+ ((char *)s->addr + id->offset);
+ ip->it_numinst = id->count;
+ for (i = 0; i < ip->it_numinst; i++) {
+ ip->it_set[i].i_inst = in[i].internal;
+ ip->it_set[i].i_name = in[i].external;
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: cannot get memory for instance list in %s",
+ pmProgname, s->name);
+ ip->it_numinst = 0;
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+static void
+map_stats(pmdaExt *pmda)
+{
+ struct dirent **files;
+ char name[64];
+ int need_reload = 0;
+ int i, j, k, sts, num;
+
+ if (pmns)
+ __pmFreePMNS(pmns);
+
+ if ((sts = __pmNewPMNS(&pmns)) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: failed to create new pmns: %s\n",
+ pmProgname, pmErrStr(sts));
+ pmns = NULL;
+ return;
+ }
+
+ /* hard-coded metrics (not from mmap'd files */
+ snprintf(name, sizeof(name), "%s.reload", prefix);
+ __pmAddPMNSNode(pmns, pmid_build(pmda->e_domain, 0, 0), name);
+ snprintf(name, sizeof(name), "%s.debug", prefix);
+ __pmAddPMNSNode(pmns, pmid_build(pmda->e_domain, 0, 1), name);
+ mcnt = 2;
+
+ if (indoms != NULL) {
+ for (i = 0; i < incnt; i++)
+ free(indoms[i].it_set);
+ free(indoms);
+ indoms = NULL;
+ incnt = 0;
+ }
+
+ if (slist != NULL) {
+ for (i = 0; i < scnt; i++) {
+ free(slist[i].name);
+ __pmMemoryUnmap(slist[i].addr, slist[i].len);
+ }
+ free(slist);
+ slist = NULL;
+ scnt = 0;
+ }
+
+ num = scandir(statsdir, &files, NULL, NULL);
+ for (i = 0; i < num; i++) {
+ struct stat statbuf;
+ char path[MAXPATHLEN];
+ char *client;
+
+ if (files[i]->d_name[0] == '.')
+ continue;
+
+ client = files[i]->d_name;
+ sprintf(path, "%s%c%s", statsdir, __pmPathSeparator(), client);
+
+ if (stat(path, &statbuf) >= 0 && S_ISREG(statbuf.st_mode))
+ if (create_client_stat(client, path, statbuf.st_size) == -EAGAIN)
+ need_reload = 1;
+ }
+
+ for (i = 0; i < num; i++)
+ free(files[i]);
+ if (num > 0)
+ free(files);
+
+ for (i = 0; slist && i < scnt; i++) {
+ stats_t * s = slist + i;
+ mmv_disk_header_t * hdr = (mmv_disk_header_t *)s->addr;
+ mmv_disk_toc_t * toc = (mmv_disk_toc_t *)
+ ((char *)s->addr + sizeof(mmv_disk_header_t));
+
+ for (j = 0; j < hdr->tocs; j++) {
+ switch (toc[j].type) {
+ case MMV_TOC_METRICS: {
+ mmv_disk_metric_t *ml = (mmv_disk_metric_t *)
+ ((char *)s->addr + toc[j].offset);
+
+ s->metrics = ml;
+ s->mcnt = toc[j].count;
+
+ for (k = 0; k < toc[j].count; k++) {
+ char name[MAXPATHLEN];
+ pmID pmid;
+
+ /* build name, check its legitimate and unique */
+ if (hdr->flags & MMV_FLAG_NOPREFIX)
+ sprintf(name, "%s.", prefix);
+ else
+ sprintf(name, "%s.%s.", prefix, s->name);
+ strcat(name, ml[k].name);
+ if (verify_metric_name(name, k, s) != 0)
+ continue;
+ if (verify_metric_item(ml[k].item, name, s) != 0)
+ continue;
+
+ pmid = pmid_build(pmda->e_domain, s->cluster, ml[k].item);
+ create_metric(pmda, s, &ml[k], name, pmid);
+ }
+ break;
+ }
+
+ case MMV_TOC_INDOMS: {
+ mmv_disk_indom_t * id = (mmv_disk_indom_t *)
+ ((char *)s->addr + toc[j].offset);
+
+ for (k = 0; k < toc[j].count; k++) {
+ int sts, serial = id[k].serial;
+ pmInDom pmindom;
+ pmdaIndom *ip;
+
+ sts = verify_indom_serial(pmda, serial, s, &pmindom, &ip);
+ if (sts == -EINVAL)
+ continue;
+ else if (sts == -EEXIST)
+ /* see if we have new instances to add here */
+ update_indom(pmda, s, &id[k], ip);
+ else
+ /* first time we've observed this indom */
+ create_indom(pmda, s, &id[k], pmindom);
+ }
+ break;
+ }
+
+ case MMV_TOC_VALUES: {
+ s->vcnt = toc[j].count;
+ s->values = (mmv_disk_value_t *)
+ ((char *)s->addr + toc[j].offset);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ pmdaTreeRebuildHash(pmns, mcnt); /* for reverse (pmid->name) lookups */
+ reload = need_reload;
+}
+
+static int
+mmv_lookup_stat_metric_value(pmID pmid, unsigned int inst,
+ stats_t **sout, mmv_disk_metric_t **mout, mmv_disk_value_t **vout)
+{
+ __pmID_int * id = (__pmID_int *)&pmid;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+ int si, mi, vi;
+ int sts = PM_ERR_PMID;
+
+ for (si = 0; si < scnt; si++) {
+ s = &slist[si];
+ if (s->cluster != id->cluster)
+ continue;
+
+ m = s->metrics;
+ for (mi = 0; mi < s->mcnt; mi++) {
+ if (m[mi].item != id->item)
+ continue;
+
+ sts = PM_ERR_INST;
+ v = s->values;
+ for (vi = 0; vi < s->vcnt; vi++) {
+ mmv_disk_metric_t * mt = (mmv_disk_metric_t *)
+ ((char *)s->addr + v[vi].metric);
+ mmv_disk_instance_t * is = (mmv_disk_instance_t *)
+ ((char *)s->addr + v[vi].instance);
+
+ if ((mt == &m[mi]) &&
+ (mt->indom == PM_INDOM_NULL || mt->indom == 0 ||
+ inst == PM_IN_NULL || is->internal == inst)) {
+ *sout = s;
+ *mout = &m[mi];
+ *vout = &v[vi];
+ return 0;
+ }
+ }
+ }
+ }
+ return sts;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+mmv_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int * id = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (id->cluster == 0) {
+ if (id->item == 0) {
+ atom->l = reload;
+ return 1;
+ }
+ if (id->item == 1) {
+ atom->l = pmDebug;
+ return 1;
+ }
+ return PM_ERR_PMID;
+
+ } else if (scnt > 0) { /* We have at least one source of metrics */
+ mmv_disk_string_t * str;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+ int rv;
+
+ rv = mmv_lookup_stat_metric_value(mdesc->m_desc.pmid, inst, &s, &m, &v);
+ if (rv < 0)
+ return rv;
+
+ switch (m->type) {
+ case MMV_TYPE_I32:
+ case MMV_TYPE_U32:
+ case MMV_TYPE_I64:
+ case MMV_TYPE_U64:
+ case MMV_TYPE_FLOAT:
+ case MMV_TYPE_DOUBLE:
+ memcpy(atom, &v->value, sizeof(pmAtomValue));
+ break;
+ case MMV_TYPE_ELAPSED: {
+ atom->ll = v->value.ll;
+ if (v->extra < 0) { /* inside a timed section */
+ struct timeval tv;
+ __pmtimevalNow(&tv);
+ atom->ll += (tv.tv_sec * 1e6 + tv.tv_usec) + v->extra;
+ }
+ break;
+ }
+ case MMV_TYPE_STRING: {
+ str = (mmv_disk_string_t *)((char *)s->addr + v->extra);
+ atom->cp = str->payload;
+ break;
+ }
+ case MMV_TYPE_NOSUPPORT:
+ return PM_ERR_APPVERSION;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+mmv_reload_maybe(pmdaExt *pmda)
+{
+ int i;
+ struct stat s;
+ int need_reload = reload;
+
+ /* check if generation numbers changed or monitored process exited */
+ for (i = 0; i < scnt; i++) {
+ mmv_disk_header_t *hdr = (mmv_disk_header_t *)slist[i].addr;
+ if (hdr->g1 != slist[i].gen || hdr->g2 != slist[i].gen) {
+ need_reload++;
+ break;
+ }
+ if (slist[i].pid && !__pmProcessExists(slist[i].pid)) {
+ need_reload++;
+ break;
+ }
+ }
+
+ /*
+ * check if the directory has been modified, reload if so;
+ * note modification may involve removal or newly appeared,
+ * a change in permissions from accessible to not (or vice-
+ * versa), and so on.
+ */
+ if (stat(statsdir, &s) >= 0) {
+ if (s.st_mtime != statsdir_ts) {
+ need_reload++;
+ statsdir_code = 0;
+ statsdir_ts = s.st_mtime;
+ }
+ } else {
+ i = oserror();
+ if (statsdir_code != i) {
+ statsdir_code = i;
+ statsdir_ts = 0;
+ need_reload++;
+ }
+ }
+
+ if (need_reload) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "MMV: %s: reloading", pmProgname);
+ map_stats(pmda);
+
+ pmda->e_indoms = indoms;
+ pmda->e_nindoms = incnt;
+ pmdaRehash(pmda, metrics, mcnt);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG,
+ "MMV: %s: %d metrics and %d indoms after reload",
+ pmProgname, mcnt, incnt);
+ }
+}
+
+/* Intercept request for descriptor and check if we'd have to reload */
+static int
+mmv_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ mmv_reload_maybe(ep);
+ return pmdaDesc(pmid, desc, ep);
+}
+
+static int
+mmv_text(int ident, int type, char **buffer, pmdaExt *ep)
+{
+ if (type & PM_TEXT_INDOM)
+ return PM_ERR_TEXT;
+
+ mmv_reload_maybe(ep);
+ if (pmid_cluster(ident) == 0) {
+ if (pmid_item(ident) == 0) {
+ static char reloadoneline[] = "Control maps reloading";
+ static char reloadtext[] =
+"Writing anything other then 0 to this metric will result in\n"
+"re-reading directory and re-mapping files.\n";
+
+ *buffer = (type & PM_TEXT_ONELINE) ? reloadoneline : reloadtext;
+ return 0;
+ }
+ else if (pmid_item(ident) == 1) {
+ static char debugoneline[] = "Debug flag";
+ static char debugtext[] =
+"See pmdbg(1). pmstore into this metric to change the debug value.\n";
+
+ *buffer = (type & PM_TEXT_ONELINE) ? debugoneline : debugtext;
+ return 0;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else {
+ mmv_disk_string_t * str;
+ mmv_disk_metric_t * m;
+ mmv_disk_value_t * v;
+ stats_t * s;
+
+ if (mmv_lookup_stat_metric_value(ident, PM_IN_NULL, &s, &m, &v) != 0)
+ return PM_ERR_PMID;
+
+ if ((type & PM_TEXT_ONELINE) && m->shorttext) {
+ str = (mmv_disk_string_t *)((char *)s->addr + m->shorttext);
+ *buffer = str->payload;
+ return 0;
+ }
+ if ((type & PM_TEXT_HELP) && m->helptext) {
+ str = (mmv_disk_string_t *)((char *)s->addr + m->helptext);
+ *buffer = str->payload;
+ return 0;
+ }
+ }
+
+ return PM_ERR_TEXT;
+}
+
+static int
+mmv_instance(pmInDom indom, int inst, char *name,
+ __pmInResult **result, pmdaExt *ep)
+{
+ mmv_reload_maybe(ep);
+ return pmdaInstance(indom, inst, name, result, ep);
+}
+
+static int
+mmv_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+mmv_store(pmResult *result, pmdaExt *ep)
+{
+ int i, m;
+
+ mmv_reload_maybe(ep);
+
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet * vsp = result->vset[i];
+ __pmID_int * id = (__pmID_int *)&vsp->pmid;
+
+ if (id->cluster == 0) {
+ for (m = 0; m < mcnt; m++) {
+ __pmID_int * mid = (__pmID_int *)&(metrics[m].m_desc.pmid);
+
+ if (mid->cluster == 0 && mid->item == id->item) {
+ pmAtomValue atom;
+ int sts;
+
+ if (vsp->numval != 1 )
+ return PM_ERR_CONV;
+
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_32, &atom, PM_TYPE_32)) < 0)
+ return sts;
+ if (id->item == 0)
+ reload = atom.l;
+ else if (id->item == 1)
+ pmDebug = atom.l;
+ else
+ return PM_ERR_PERMISSION;
+ }
+ }
+ }
+ else
+ return PM_ERR_PERMISSION;
+ }
+ return 0;
+}
+
+static int
+mmv_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+static int
+mmv_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+static int
+mmv_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda)
+{
+ mmv_reload_maybe(pmda);
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+void
+__PMDA_INIT_CALL
+mmv_init(pmdaInterface *dp)
+{
+ int m;
+ int sep = __pmPathSeparator();
+
+ if (isDSO) {
+ pmdaDSO(dp, PMDA_INTERFACE_4, "mmv", NULL);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ pcptmpdir = pmGetConfig("PCP_TMP_DIR");
+ pcpvardir = pmGetConfig("PCP_VAR_DIR");
+ pcppmdasdir = pmGetConfig("PCP_PMDAS_DIR");
+
+ snprintf(statsdir, sizeof(statsdir), "%s%c%s", pcptmpdir, sep, prefix);
+ snprintf(pmnsdir, sizeof(pmnsdir), "%s%c" "pmns", pcpvardir, sep);
+ statsdir[sizeof(statsdir)-1] = '\0';
+ pmnsdir[sizeof(pmnsdir)-1] = '\0';
+
+ /* Initialize internal dispatch table */
+ if (dp->status == 0) {
+ /*
+ * number of hard-coded metrics here has to match initializer
+ * cases below, and pmns initialization in map_stats()
+ */
+ mcnt = 2;
+ if ((metrics = malloc(mcnt*sizeof(pmdaMetric))) != NULL) {
+ /*
+ * all the hard-coded metrics have the same semantics
+ */
+ for (m = 0; m < mcnt; m++) {
+ if (m == 0)
+ metrics[m].m_user = &reload;
+ else if (m == 1)
+ metrics[m].m_user = &pmDebug;
+ metrics[m].m_desc.pmid = pmid_build(dp->domain, 0, m);
+ metrics[m].m_desc.type = PM_TYPE_32;
+ metrics[m].m_desc.indom = PM_INDOM_NULL;
+ metrics[m].m_desc.sem = PM_SEM_INSTANT;
+ memset(&metrics[m].m_desc.units, 0, sizeof(pmUnits));
+ }
+ } else {
+ __pmNotifyErr(LOG_ERR, "%s: pmdaInit - out of memory\n",
+ pmProgname);
+ if (isDSO)
+ return;
+ exit(0);
+ }
+
+ dp->version.four.fetch = mmv_fetch;
+ dp->version.four.store = mmv_store;
+ dp->version.four.desc = mmv_desc;
+ dp->version.four.text = mmv_text;
+ dp->version.four.instance = mmv_instance;
+ dp->version.four.pmid = mmv_pmid;
+ dp->version.four.name = mmv_name;
+ dp->version.four.children = mmv_children;
+ pmdaSetFetchCallBack(dp, mmv_fetchCallBack);
+
+ pmdaSetFlags(dp, PMDA_EXT_FLAG_HASHED);
+ pmdaInit(dp, indoms, incnt, metrics, mcnt);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char logfile[32];
+ pmdaInterface dispatch = { 0 };
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ if (strncmp(pmProgname, "pmda", 4) == 0 && strlen(pmProgname) > 4)
+ prefix = pmProgname + 4;
+ snprintf(logfile, sizeof(logfile), "%s.log", prefix);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_4, pmProgname, MMV, logfile, NULL);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ mmv_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/mmv/src/root_mmv b/src/pmdas/mmv/src/root_mmv
new file mode 100644
index 0000000..28c810a
--- /dev/null
+++ b/src/pmdas/mmv/src/root_mmv
@@ -0,0 +1,13 @@
+/*
+ * MMV metrics name space
+ */
+
+#ifndef MMV
+#define MMV 70
+#endif
+
+root {
+ mmv MMV:*:*
+}
+
+#undef MMV
diff --git a/src/pmdas/mounts/GNUmakefile b/src/pmdas/mounts/GNUmakefile
new file mode 100644
index 0000000..278350b
--- /dev/null
+++ b/src/pmdas/mounts/GNUmakefile
@@ -0,0 +1,64 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mounts
+DOMAIN = MOUNTS
+TARGETS = $(IAM)$(EXECSUFFIX)
+CFILES = mounts.c
+SCRIPTS = Install Remove
+DFILES = README
+LSRCFILES= $(SCRIPTS) pmns help root $(DFILES) mounts.conf
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so $(TARGETS) \
+ help.pag help.dir
+LLDLIBS = $(PCP_PMDALIB)
+
+default: build-me
+
+include $(BUILDRULES)
+
+# This PMDA is only valid on platforms with a mount table (e.g. /proc/mounts)
+ifeq "$(findstring $(TARGET_OS),mingw darwin)" ""
+build-me: $(TARGETS)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) pmns help root domain.h $(PMDADIR)
+ $(INSTALL) -m 644 mounts.conf $(PMDADIR)/mounts.conf
+else
+build-me:
+install:
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+default_pcp: default
+
+install_pcp: install
+
+mounts.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/mounts/Install b/src/pmdas/mounts/Install
new file mode 100755
index 0000000..e690a1f
--- /dev/null
+++ b/src/pmdas/mounts/Install
@@ -0,0 +1,30 @@
+#! /bin/sh
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the mounts PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mounts
+pmda_interface=2
+forced_restart=false
+pmdaSetup
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/mounts/README b/src/pmdas/mounts/README
new file mode 100644
index 0000000..2bf3c6a
--- /dev/null
+++ b/src/pmdas/mounts/README
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+
+Mounts PMDA
+============
+
+This PMDA exports information about the mount status of file
+systems specified in a config file. The default config file is
+$PCP_PMDAS_DIR/mounts/mounts.conf, which should contain one line
+for each file system you wish to monitor.
+
+This source code was contributed by Alan Bailey (abailey@ncsa.uiuc.edu)
+to the PCP open source project.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT mounts
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/mounts
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use
+ ($PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+ + Alternatively, to install just the Performance Metrics Name Space
+ for the mounts metrics on the local system, but not the mounts PMDA
+ (presumably because the local system is running PCP 1.x and you
+ wish to connect to a remote system where PCP 2.0 and the mounts PMDA
+ is running), make sure the Performance Metrics Domain defined in
+ ./domain.h matches the domain chosen for the mounts PMDA on the
+ remote system (check the second field in the corresponding line of
+ the $PCP_PMCDCONF_PATH file on the remote system), then
+
+ # ./Install -N
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/mounts
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/mounts.log) should be checked for any warnings or
+ errors.
diff --git a/src/pmdas/mounts/Remove b/src/pmdas/mounts/Remove
new file mode 100755
index 0000000..fac32b9
--- /dev/null
+++ b/src/pmdas/mounts/Remove
@@ -0,0 +1,41 @@
+#! /bin/sh
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the mounts PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=mounts
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/mounts/help b/src/pmdas/mounts/help
new file mode 100644
index 0000000..d8cbbf4
--- /dev/null
+++ b/src/pmdas/mounts/help
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# mounts PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ mounts.device Tracks the current mounts
+Tracks the current mounts
+
+@ mounts.type Tracks the types of current mounts
+Tracks the types of current mounts
+
+@ mounts.options Tracks the mount options of current mounts
+Tracks the mount options of current mounts
+
+@ mounts.up Simply tells that the mount is up
+Simply tells that the mount is up
diff --git a/src/pmdas/mounts/mounts.c b/src/pmdas/mounts/mounts.c
new file mode 100644
index 0000000..44ec6d7
--- /dev/null
+++ b/src/pmdas/mounts/mounts.c
@@ -0,0 +1,384 @@
+/*
+ * Mounts, info on current mounts
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2001,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include <dirent.h>
+#include <sys/stat.h>
+
+/*
+ * Mounts PMDA
+ *
+ * Metrics
+ * mounts.device
+ * The device which the mount is mounted on
+ * mounts.type
+ * The type of filesystem
+ * mounts.options
+ * The mounting options
+ * mounts.up
+ * always equals 1
+ */
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+#ifdef IS_SOLARIS
+#define MOUNT_FILE "/etc/vfstab"
+#else
+#define MOUNT_FILE "/proc/mounts"
+#endif
+
+static pmdaInstid *mounts;
+
+static pmdaIndom indomtab[] = {
+#define MOUNTS_INDOM 0
+ { MOUNTS_INDOM, 0, NULL }
+};
+
+static pmdaMetric metrictab[] = {
+/* mounts.device */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_STRING, MOUNTS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* mounts.type */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_STRING, MOUNTS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* mounts.options */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_STRING, MOUNTS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* mounts.up */
+ { NULL,
+ { PMDA_PMID(0,3), PM_TYPE_DOUBLE, MOUNTS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+typedef struct {
+ int up;
+ char device[100];
+ char type[100];
+ char options[100];
+} mountinfo;
+
+static mountinfo *mount_list;
+static struct stat file_change;
+static int isDSO = 1;
+static char mypath[MAXPATHLEN];
+static char *username;
+
+static void mounts_clear_config_info(void);
+static void mounts_grab_config_info(void);
+static void mounts_config_file_check(void);
+static void mounts_refresh_mounts(void);
+
+static void
+mounts_config_file_check(void)
+{
+ struct stat statbuf;
+ static int last_error;
+ int sep = __pmPathSeparator();
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "mounts" "%c" "mounts.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if (stat(mypath, &statbuf) == -1) {
+ if (oserror() != last_error) {
+ last_error = oserror();
+ __pmNotifyErr(LOG_WARNING, "stat failed on %s: %s\n",
+ mypath, pmErrStr(last_error));
+ }
+ } else {
+ last_error = 0;
+#if defined(HAVE_ST_MTIME_WITH_E)
+ if (statbuf.st_mtime != file_change.st_mtime)
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if (statbuf.st_mtimespec.tv_sec != file_change.st_mtimespec.tv_sec ||
+ statbuf.st_mtimespec.tv_nsec != file_change.st_mtimespec.tv_nsec)
+#else
+ if (statbuf.st_mtim.tv_sec != file_change.st_mtim.tv_sec ||
+ statbuf.st_mtim.tv_nsec != file_change.st_mtim.tv_nsec)
+#endif
+ {
+ mounts_clear_config_info();
+ mounts_grab_config_info();
+ file_change = statbuf;
+ }
+ }
+}
+
+static void
+mounts_clear_config_info(void)
+{
+ int i;
+
+ /* Free the memory holding the mount name */
+ for (i = 0; i < indomtab[MOUNTS_INDOM].it_numinst; i++) {
+ free(mounts[i].i_name);
+ mounts[i].i_name = NULL;
+ }
+
+ /* Free the mounts structure */
+ if (mounts)
+ free(mounts);
+
+ /* Free the mount_list structure */
+ if (mount_list)
+ free(mount_list);
+
+ mount_list = NULL;
+ indomtab[MOUNTS_INDOM].it_set = mounts = NULL;
+ indomtab[MOUNTS_INDOM].it_numinst = 0;
+}
+
+/*
+ * This routine opens the config file and stores the information in the
+ * mounts structure. The mounts structure must be reallocated as
+ * necessary, and also the num_procs structure needs to be reallocated
+ * as we define new mounts. When all of that is done, we fill in the
+ * values in the indomtab structure, those being the number of instances
+ * and the pointer to the mounts structure.
+ */
+static void
+mounts_grab_config_info(void)
+{
+ FILE *fp;
+ char mount_name[MAXPATHLEN];
+ char *q;
+ size_t size;
+ int mount_number = 0;
+ int sep = __pmPathSeparator();
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "mounts" "%c" "mounts.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if ((fp = fopen(mypath, "r")) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fopen on %s failed: %s\n",
+ mypath, pmErrStr(-oserror()));
+ if (mounts) {
+ free(mounts);
+ mounts = NULL;
+ mount_number = 0;
+ }
+ goto done;
+ }
+
+ while (fgets(mount_name, sizeof(mount_name), fp) != NULL) {
+ if (mount_name[0] == '#')
+ continue;
+ /* Remove the newline */
+ if ((q = strchr(mount_name, '\n')) != NULL) {
+ *q = '\0';
+ } else {
+ /* This means the line was too long */
+ __pmNotifyErr(LOG_WARNING, "line %d in the config file too long\n",
+ mount_number+1);
+ }
+ size = (mount_number + 1) * sizeof(pmdaInstid);
+ if ((mounts = realloc(mounts, size)) == NULL)
+ __pmNoMem("process", size, PM_FATAL_ERR);
+ mounts[mount_number].i_name = malloc(strlen(mount_name) + 1);
+ strcpy(mounts[mount_number].i_name, mount_name);
+ mounts[mount_number].i_inst = mount_number;
+ mount_number++;
+ }
+ fclose(fp);
+
+done:
+ if (mounts == NULL)
+ __pmNotifyErr(LOG_WARNING, "\"mounts\" instance domain is empty");
+ indomtab[MOUNTS_INDOM].it_set = mounts;
+ indomtab[MOUNTS_INDOM].it_numinst = mount_number;
+ mount_list = realloc(mount_list, (mount_number)*sizeof(mountinfo));
+}
+
+static void
+mounts_refresh_mounts(void)
+{
+ FILE *fd;
+ char device[100];
+ char mount[100];
+ char type[100];
+ char options[100];
+ char junk[10];
+ int item;
+ int mount_name;
+
+ /* Clear the variables */
+ for(item = 0; item < indomtab[MOUNTS_INDOM].it_numinst; item++) {
+ strcpy(mount_list[item].device, "none");
+ strcpy(mount_list[item].type, "none");
+ strcpy(mount_list[item].options, "none");
+ mount_list[item].up = 0;
+ }
+
+ if ((fd = fopen(MOUNT_FILE, "r")) != NULL) {
+#ifdef IS_SOLARIS
+ char device_to_fsck[100];
+ char fsck_pass[100];
+ char mount_at_boot[100];
+
+ while ((fscanf(fd, "%s %s %s %s %s %s %s",
+ device, device_to_fsck, mount, type, fsck_pass,
+ mount_at_boot, options)) == 7)
+#else
+ while ((fscanf(fd, "%s %s %s %s", device, mount, type, options)) == 4)
+#endif
+ {
+ if (fgets(junk, sizeof(junk), fd) == NULL) {
+ /* early EOF? will be caught in next iteration */
+ ;
+ }
+
+ for (mount_name = 0;
+ mount_name < indomtab[MOUNTS_INDOM].it_numinst;
+ mount_name++) {
+ if (strcmp(mount, (mounts[mount_name]).i_name) == 0) {
+ strcpy(mount_list[mount_name].device, device);
+ strcpy(mount_list[mount_name].type, type);
+ strcpy(mount_list[mount_name].options, options);
+ mount_list[mount_name].up = 1;
+ }
+ }
+ memset(device, 0, sizeof(device));
+ memset(mount, 0, sizeof(mount));
+ memset(type, 0, sizeof(type));
+ memset(options, 0, sizeof(options));
+ }
+ fclose(fd);
+ }
+}
+
+/*
+ * This is the wrapper over the pmdaFetch routine, to handle the problem
+ * of varying instance domains. All this does is delete the previous
+ * mount list, and then get the current one, by calling
+ * mounts_refresh_mounts.
+ */
+static int
+mounts_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ mounts_config_file_check();
+ mounts_refresh_mounts();
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+mounts_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+ if (inst >= indomtab[MOUNTS_INDOM].it_numinst)
+ return PM_ERR_INST;
+
+ if (idp->item == 0)
+ atom->cp = (mount_list[inst]).device;
+ else if (idp->item == 1)
+ atom->cp = (mount_list[inst]).type;
+ else if (idp->item == 2)
+ atom->cp = (mount_list[inst]).options;
+ else if (idp->item == 3)
+ atom->d = (mount_list[inst]).up;
+ else
+ return PM_ERR_PMID;
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+mounts_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "mounts" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "mounts DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.fetch = mounts_fetch;
+ pmdaSetFetchCallBack(dp, mounts_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+
+ /* Let's grab the info right away just to make sure it's there. */
+ mounts_grab_config_info();
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "mounts" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, MOUNTS,
+ "mounts.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ mounts_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/mounts/mounts.conf b/src/pmdas/mounts/mounts.conf
new file mode 100644
index 0000000..217607f
--- /dev/null
+++ b/src/pmdas/mounts/mounts.conf
@@ -0,0 +1,10 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+/
+/boot
+/afs
+/not-here
diff --git a/src/pmdas/mounts/pmns b/src/pmdas/mounts/pmns
new file mode 100644
index 0000000..b328773
--- /dev/null
+++ b/src/pmdas/mounts/pmns
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Metrics for mounts PMDA
+ */
+
+mounts {
+ device MOUNTS:0:0
+ type MOUNTS:0:1
+ options MOUNTS:0:2
+ up MOUNTS:0:3
+}
diff --git a/src/pmdas/mounts/root b/src/pmdas/mounts/root
new file mode 100644
index 0000000..1dbb4a3
--- /dev/null
+++ b/src/pmdas/mounts/root
@@ -0,0 +1,15 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ *
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+#include <stdpmid>
+
+root { mounts }
+
+#include "pmns"
+
diff --git a/src/pmdas/mssql/GNUmakefile b/src/pmdas/mssql/GNUmakefile
new file mode 100644
index 0000000..c76670e
--- /dev/null
+++ b/src/pmdas/mssql/GNUmakefile
@@ -0,0 +1,53 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mssql
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/mssql/Install b/src/pmdas/mssql/Install
new file mode 100644
index 0000000..bd222fa
--- /dev/null
+++ b/src/pmdas/mssql/Install
@@ -0,0 +1,34 @@
+#! /bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the MSSQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mssql
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+perl -e "use DBI" 2>/dev/null
+if test $? -ne 0; then
+ echo "Perl database interface (DBI) is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/mssql/Remove b/src/pmdas/mssql/Remove
new file mode 100644
index 0000000..1408c07
--- /dev/null
+++ b/src/pmdas/mssql/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the MSSQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mssql
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/mssql/pmdamssql.pl b/src/pmdas/mssql/pmdamssql.pl
new file mode 100644
index 0000000..e82fb62
--- /dev/null
+++ b/src/pmdas/mssql/pmdamssql.pl
@@ -0,0 +1,315 @@
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use DBI;
+
+my $server = 'localhost';
+my $database = 'PCP';
+my $username = 'dbmonitor';
+my $password = 'dbmonitor';
+
+# Configuration files for overriding the above settings
+for my $file ( '/etc/pcpdbi.conf', # system defaults (lowest priority)
+ pmda_config('PCP_PMDAS_DIR') . '/mssql/mssql.conf',
+ './mssql.conf' ) { # current directory (high priority)
+ eval `cat $file` unless ! -f $file;
+}
+
+use vars qw( $pmda $dbh );
+use vars qw( $sth_os_memory_clerks );
+use vars qw( $sth_virtual_file_stats @virtual_file_stats );
+use vars qw( $sth_total_running_user_processes @total_running_user_processes );
+use vars qw( $sth_os_memory_clerks @os_memory_clerks );
+use vars qw( $sth_os_workers_waiting_cpu @os_workers_waiting_cpu );
+my $database_indom = 0;
+my @database_instances;
+
+sub mssql_connection_setup
+{
+ #$pmda->log("mssql_connection_setup\n");
+
+ if (!defined($dbh)) {
+ $dbh = DBI->connect("DBI:Sybase:server=$server", $username, $password);
+ if (defined($dbh)) {
+ $pmda->log("MSSQL connection established\n");
+ $sth_virtual_file_stats = $dbh->prepare(
+ "select db_name(database_id), cast(num_of_reads as numeric), cast(num_of_bytes_read as numeric)," .
+ " cast(io_stall_read_ms as numeric), cast(num_of_writes as numeric)," .
+ " cast(num_of_bytes_written as numeric), cast(io_stall_write_ms as numeric)," .
+ " cast(size_on_disk_bytes as numeric) " .
+ "from sys.dm_io_virtual_file_stats(DB_ID(''),1)");
+ $sth_os_memory_clerks = $dbh->prepare(
+ "SELECT SUM(multi_pages_kb + virtual_memory_committed_kb + shared_memory_committed_kb + awe_allocated_kb)" .
+ " from sys.dm_os_memory_clerks WHERE type IN " .
+ "('MEMORYCLERK_SQLBUFFERPOOL', 'MEMORYCLERK_SQLQUERYCOMPILE'," .
+ " 'MEMORYCLERK_SQLQUERYEXEC', 'MEMORYCLERK_SQLQUERYPLAN')" .
+ " group by type order by type");
+ $sth_total_running_user_processes = $dbh->prepare(
+ "SELECT count(*) FROM sys.dm_exec_requests " .
+ "WHERE session_id >= 51 AND status = 'running'");
+ $sth_os_workers_waiting_cpu = $dbh->prepare(
+ "SELECT ISNULL(COUNT(*),0) FROM sys.dm_os_workers AS workers " .
+ "INNER JOIN sys.dm_os_schedulers AS schedulers " .
+ "ON workers.scheduler_address = schedulers.scheduler_address " .
+ "WHERE workers.state = 'RUNNABLE' AND schedulers.scheduler_id < 255");
+ }
+ }
+}
+
+sub mssql_os_memory_clerks_refresh
+{
+ #$pmda->log("mssql_os_memory_clerks_refresh\n");
+
+ @os_memory_clerks = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_os_memory_clerks->execute();
+ my $result = $sth_os_memory_clerks->fetchall_arrayref();
+ for my $i (0 .. $#{$result}) {
+ $os_memory_clerks[$i] = $result->[$i][0];
+ }
+ }
+}
+
+sub mssql_virtual_file_stats_refresh
+{
+ #$pmda->log("mssql_virtual_file_stats_refresh\n");
+
+ @virtual_file_stats = (); # clear any previous contents
+ @database_instances = ();
+
+ if (defined($dbh)) {
+ $sth_virtual_file_stats->execute();
+ my $result = $sth_virtual_file_stats->fetchall_arrayref();
+
+ for my $i (0 .. $#{$result}) {
+ $database_instances[($i*2)] = $i;
+ $database_instances[($i*2)+1] = "$result->[$i][0]";
+ $virtual_file_stats[$i] = $result->[$i];
+ }
+
+ $pmda->replace_indom( $database_indom, \@database_instances );
+ }
+}
+
+sub mssql_total_running_user_processes
+{
+ #$pmda->log("mssql_total_running_user_processes\n");
+
+ @total_running_user_processes = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_total_running_user_processes->execute();
+ my $result = $sth_total_running_user_processes->fetchall_arrayref();
+ @total_running_user_processes = ( $result->[0][0] );
+ }
+}
+
+sub mssql_os_workers_waiting_cpu_refresh
+{
+ #$pmda->log("mssql_os_workers_refresh\n");
+
+ @os_workers_waiting_cpu = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_os_workers_waiting_cpu->execute();
+ my $result = $sth_os_workers_waiting_cpu->fetchall_arrayref();
+ @os_workers_waiting_cpu = ( $result->[0][0] );
+ }
+}
+
+sub mssql_refresh
+{
+ my ($cluster) = @_;
+
+ #$pmda->log("mssql_refresh $cluster\n");
+
+ if ($cluster == 0) { mssql_virtual_file_stats_refresh; }
+ elsif ($cluster == 1) { mssql_os_memory_clerks_refresh; }
+ elsif ($cluster == 2) { mssql_total_running_user_processes; }
+ elsif ($cluster == 3) { mssql_os_workers_waiting_cpu_refresh; }
+}
+
+sub mssql_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my ($value, @vfstats);
+
+ #$pmda->log("mssql_fetch_callback $cluster:$item ($inst)\n");
+
+ if ($cluster == 0) {
+ if ($item > 6) { return (PM_ERR_PMID, 0); }
+ if ($inst < 0) { return (PM_ERR_INST, 0); }
+ if ($inst > @database_instances) { return (PM_ERR_INST, 0); }
+ $value = $virtual_file_stats[$inst];
+ if (!defined($value)) { return (PM_ERR_INST, 0); }
+ @vfstats = @$value;
+ if (!defined($vfstats[$item+1])) { return (PM_ERR_AGAIN, 0); }
+ return ($vfstats[$item+1], 1);
+ }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($cluster == 1) {
+ if ($item > 3) { return (PM_ERR_PMID, 0); }
+ if (!defined($os_memory_clerks[$item])) { return (PM_ERR_AGAIN, 0); }
+ return ($os_memory_clerks[$item], 1);
+ }
+ if ($cluster == 2) {
+ if ($item > 0) { return (PM_ERR_PMID, 0); }
+ if (!defined($total_running_user_processes[$item])) { return (PM_ERR_AGAIN, 0); }
+ return ($total_running_user_processes[$item], 1);
+ }
+ if ($cluster == 3) {
+ if ($item > 0) { return (PM_ERR_PMID, 0); }
+ if (!defined($os_workers_waiting_cpu[$item])) { return (PM_ERR_AGAIN, 0); }
+ return ($os_workers_waiting_cpu[$item], 1);
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('mssql', 109);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mssql.virtual_file.read', 'Number of bytes reads issued on data file', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mssql.virtual_file.read_bytes', 'Total number of bytes read on the data file', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'mssql.virtual_file.read_io_stall_time', 'Total time in ms that the users waited for reads issued on the file', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mssql.virtual_file.write', 'Number of writes made on the data file', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mssql.virtual_file.write_bytes', 'Total number of bytes written to the data file', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U64, $database_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'mssql.virtual_file.write_io_stall_time', 'Total time in ms that users waited for writes to be completed o the file', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U64, $database_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mssql.virtual_file.size', 'Number of bytes used on the disk from the data file', '');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'mssql.os_memory_clerks.bufferpool', '', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'mssql.os_memory_clerks.querycompile', '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'mssql.os_memory_clerks.queryexec', '', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'mssql.os_memory_clerks.queryplan', '', '');
+
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mssql.running_user_process.total', 'Total number of running user process belonging to aconexsq', '');
+
+$pmda->add_metric(pmda_pmid(3,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mssql.os_workers_waiting_cpu.count', 'Total number of queries waiting for cpu', '');
+
+$pmda->add_indom($database_indom, \@database_instances,
+ 'Instance domain exporting each MSSQL database', '');
+
+$pmda->set_fetch_callback(\&mssql_fetch_callback);
+$pmda->set_fetch(\&mssql_connection_setup);
+$pmda->set_refresh(\&mssql_refresh);
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdamssql - Microsoft SQL database PMDA
+
+=head1 DESCRIPTION
+
+B<pmdamssql> is a Performance Co-Pilot PMDA which extracts
+live performance data from a running SQL Server database.
+These metrics are typically sourced from Dynamic Management
+Views (DMVs), augmenting the SQL server metrics exported by
+the Windows PMDA.
+
+=head1 INSTALLATION
+
+B<pmdamssql> uses a configuration file from (in this order):
+
+=over
+
+=item * /etc/pcpdbi.conf
+
+=item * $PCP_PMDAS_DIR/mssql/mssql.conf
+
+=back
+
+This file can contain overridden values (Perl code) for the settings
+listed at the start of pmdamssql.pl, namely:
+
+=over
+
+=item * database name (see DBI(3) for details)
+
+=item * database user name
+
+=item * database pass word
+
+=back
+
+Once this is setup, you can access the names and values for the
+mysql performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/mssql
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/mssql
+ # ./Remove
+
+B<pmdamssql> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /etc/pcpdbi.conf
+
+configuration file for all PCP database monitors
+
+=item $PCP_PMDAS_DIR/mssql/mssql.conf
+
+configuration file for B<pmdamssql>
+
+=item $PCP_PMDAS_DIR/mssql/Install
+
+installation script for the B<pmdamssql> agent
+
+=item $PCP_PMDAS_DIR/mssql/Remove
+
+undo installation script for the B<pmdamssql> agent
+
+=item $PCP_LOG_DIR/pmcd/mssql.log
+
+default log file for error messages from B<pmdamssql>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), pmdadbping.pl(1) and DBI(3).
diff --git a/src/pmdas/mysql/GNUmakefile b/src/pmdas/mysql/GNUmakefile
new file mode 100644
index 0000000..b836e0c
--- /dev/null
+++ b/src/pmdas/mysql/GNUmakefile
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = mysql
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl pmlogconf.summary migrate.conf README
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 README $(PMDADIR)/README
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+ $(INSTALL) -m 644 migrate.conf $(PCP_VAR_DIR)/config/pmlogrewrite/mysql_migrate.conf
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/mysql/Install b/src/pmdas/mysql/Install
new file mode 100755
index 0000000..c98edce
--- /dev/null
+++ b/src/pmdas/mysql/Install
@@ -0,0 +1,34 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the MySQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mysql
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+perl -e "use DBI" 2>/dev/null
+if test $? -ne 0; then
+ echo "Perl database interface (DBI) is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/mysql/README b/src/pmdas/mysql/README
new file mode 100644
index 0000000..971046c
--- /dev/null
+++ b/src/pmdas/mysql/README
@@ -0,0 +1,79 @@
+Performance Co-Pilot PMDA for Monitoring MySQL Databases
+========================================================
+
+This PMDA exports activity and performance metrics from a MySQL
+database server on the local system.
+
+The PMDA collects its data from the SQL commands:
+ show variables;
+ show global status;
+ show processlist;
+ show slave status;
+
+Metrics
+=======
+
+Once the PMDA has been installed, the following command will list all of
+the available metrics:
+
+ + # $ pminfo -f mysql
+
+Database Setup
+==============
+
+The PMDA needs access to the mysql database. If you use the PMDA
+as shipped, this implies a MySQL user 'dbmonitor' with password
+'dbmonitor' has been created and has access to the mysql database.
+
+Specifically, this means the following has been done:
+
+ + # mysql -uroot -p...
+ mysql> create user 'dbmonitor'@'localhost' identified by 'dbmonitor';
+ mysql> grant select on mysql.* to 'dbmonitor'@'localhost';
+ mysql> grant replication client on *.* to 'dbmonitor'@'localhost';
+
+If this username and password combination does not suit, choose
+some other, but you'll have to change these intializations in
+pmdamysql.pl:
+
+ my $username = 'dbmonitor';
+ my $password = 'dbmonitor';
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/mysql
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDA's currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number (This should only be an issue on installations with
+ third party PMDA's installed as the domain number given has been
+ reserved for the mysql PMDA with base PCP installations).
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the “collector” and “monitor” installation
+ configuration options.
+
+De-Installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/mysql
+ #./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/PMCD/mysql.log) should be checked for any warnings or
+ errors.
+
+ + In an event where no values are being returned for most of the
+ metrics check ensure that the username and password in pmdamysql.pl
+ match the local MySQL setup.
diff --git a/src/pmdas/mysql/Remove b/src/pmdas/mysql/Remove
new file mode 100755
index 0000000..77999bb
--- /dev/null
+++ b/src/pmdas/mysql/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the MySQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=mysql
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/mysql/migrate.conf b/src/pmdas/mysql/migrate.conf
new file mode 100644
index 0000000..de35280
--- /dev/null
+++ b/src/pmdas/mysql/migrate.conf
@@ -0,0 +1,23 @@
+# Copyright 2013 Ken McDonell. All Rights Reserved
+#
+# pmlogrewrite configuration for migrating archives containing MySQL metrics
+# across various changes in the metadata supplied by the PMDA
+#
+
+# was instantaneous, now counter as per commit 1ec908d on 24 Jun 2013
+#
+metric mysql.status.connections {
+ sem -> counter
+ units -> 0,0,1,0,0,ONE
+}
+
+# uptime metrics were counters, now instantaneous as per commit 8de75d1
+# on Aug 28 2014
+metric mysql.status.uptime {
+ sem -> instant
+}
+metric mysql.status.uptime_since_flush_status {
+ sem -> instant
+}
+
+
diff --git a/src/pmdas/mysql/pmdamysql.pl b/src/pmdas/mysql/pmdamysql.pl
new file mode 100644
index 0000000..56f7093
--- /dev/null
+++ b/src/pmdas/mysql/pmdamysql.pl
@@ -0,0 +1,1911 @@
+#
+# Copyright (c) 2012-2013 Chandana De Silva.
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use DBI;
+
+my $database = 'DBI:mysql:mysql';
+my $username = 'dbmonitor';
+my $password = 'dbmonitor';
+
+# Configuration files for overriding the above settings
+for my $file ( '/etc/pcpdbi.conf', # system defaults (lowest priority)
+ pmda_config('PCP_PMDAS_DIR') . '/mysql/mysql.conf',
+ './mysql.conf' ) { # current directory (high priority)
+ eval `cat $file` unless ! -f $file;
+}
+
+use vars qw( $pmda %status %variables @processes %slave_status );
+use vars qw( $dbh $sth_variables $sth_status $sth_processes $sth_slave_status );
+my $process_indom = 0;
+my @process_instances;
+
+# translate yes/no true/false on/off to 1/0
+sub mysql_txt2num {
+ my ($value) = lc($_[0]);
+
+ if (!defined($value)) {
+ return (PM_ERR_AGAIN, 0);
+ }
+ elsif ($value eq "yes" || $value eq "true" || $value eq "on") {
+ return 1;
+ }
+ elsif ($value eq "no" || $value eq "false" || $value eq "off") {
+ return 0;
+ }
+ else {
+ return -1;
+ }
+}
+
+sub mysql_connection_setup
+{
+ # $pmda->log("mysql_connection_setup\n");
+
+ if (!defined($dbh)) {
+ $dbh = DBI->connect($database, $username, $password);
+ if (defined($dbh)) {
+ # set the db handle to undef in case of any failure
+ # this will force a database reconnect
+ $dbh->{HandleError} = sub { $dbh = undef; };
+ $pmda->log("MySQL connection established\n");
+ $sth_variables = $dbh->prepare('show variables');
+ $sth_status = $dbh->prepare('show global status');
+ $sth_processes = $dbh->prepare('show processlist');
+ $sth_slave_status = $dbh->prepare('show slave status');
+ }
+ }
+}
+
+sub mysql_variables_refresh
+{
+ # $pmda->log("mysql_variables_refresh\n");
+
+ %variables = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_variables->execute();
+ my $result = $sth_variables->fetchall_arrayref();
+ for my $i (0 .. $#{$result}) {
+ $variables{$result->[$i][0]} = $result->[$i][1];
+ }
+ }
+}
+
+sub mysql_status_refresh
+{
+ # $pmda->log("mysql_status_refresh\n");
+
+ %status = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_status->execute();
+ my $result = $sth_status->fetchall_arrayref();
+ my $txtnum;
+ my $txtnumvar;
+ for my $i (0 .. $#{$result}) {
+ my $key = lcfirst $result->[$i][0];
+ $status{$key} = $result->[$i][1];
+ # if this status value has a yes/no type value, get it translated
+ $txtnum = mysql_txt2num($result->[$i][1]);
+ if ($txtnum ge 0) {
+ $txtnumvar=$key . "_num";
+ $status{$txtnumvar} = $txtnum;
+ }
+ }
+ }
+}
+
+sub mysql_process_refresh
+{
+ # $pmda->log("mysql_process_refresh\n");
+
+ @processes = (); # clear any previous contents
+ @process_instances = (); # refresh indom too
+
+ if (defined($dbh)) {
+ $sth_processes->execute();
+ my $result = $sth_processes->fetchall_arrayref();
+ for my $i (0 .. $#{$result}) {
+ $process_instances[($i*2)] = $i;
+ $process_instances[($i*2)+1] = "$result->[$i][0]";
+ $processes[$i] = $result->[$i];
+ }
+ }
+
+ $pmda->replace_indom($process_indom, \@process_instances);
+}
+
+sub mysql_slave_status_refresh
+{
+ # $pmda->log("mysql_slave_status_refresh\n");
+
+ %slave_status = (); # clear any previous contents
+ if (defined($dbh)) {
+ $sth_slave_status->execute();
+ my $result = $sth_slave_status->fetchrow_hashref();
+ my $txtnum;
+ my $txtnumvar;
+ while ( my ($key, $value) = each(%$result) ) {
+ $slave_status{lc $key} = $value;
+ # if this status value has a yes/no type value, get it translated
+ $txtnum = mysql_txt2num($value);
+ if ($txtnum ge 0) {
+ $txtnumvar=lc($key) . "_num";
+ $slave_status{$txtnumvar} = $txtnum;
+ }
+ }
+ }
+}
+
+sub mysql_refresh
+{
+ my ($cluster) = @_;
+
+ # $pmda->log("mysql_refresh $cluster\n");
+ if ($cluster == 0) { mysql_status_refresh; }
+ elsif ($cluster == 1) { mysql_variables_refresh; }
+ elsif ($cluster == 2) { mysql_process_refresh; }
+ elsif ($cluster == 3) { mysql_slave_status_refresh; }
+}
+
+sub mysql_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ($mysql_name, $value, @procs);
+
+ # $pmda->log("mysql_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+ $mysql_name = $metric_name;
+
+ if ($cluster == 2) {
+ if ($inst < 0) { return (PM_ERR_INST, 0); }
+ if ($inst > @process_instances) { return (PM_ERR_INST, 0); }
+ $value = $processes[$inst];
+ if (!defined($value)) { return (PM_ERR_INST, 0); }
+ @procs = @$value;
+ if (!defined($procs[$item]) && $item == 6) { return ("?", 1); }
+ if (!defined($procs[$item])) { return (PM_ERR_APPVERSION, 0); }
+ return ($procs[$item], 1);
+ }
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($cluster == 0) {
+ $mysql_name =~ s/^mysql\.status\.//;
+ $value = $status{$mysql_name};
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ return ($value, 1);
+ }
+ elsif ($cluster == 1) {
+ $mysql_name =~ s/^mysql\.variables\.//;
+ $value = $variables{$mysql_name};
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ return ($value, 1);
+ }
+ elsif ($cluster == 3) {
+ $mysql_name =~ s/^mysql\.slave_status\.//;
+ $value = $slave_status{$mysql_name};
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ return ($value, 1);
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('mysql', 66);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.aborted_clients', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.aborted_connects', '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.binlog_cache_disk_use', '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.binlog_cache_use', '', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.bytes_received', '', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.bytes_sent', '', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_admin_commands', '', '');
+$pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_alter_db', '', '');
+$pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_alter_table', '', '');
+$pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_analyze', '', '');
+$pmda->add_metric(pmda_pmid(0,10), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_backup_table', '', '');
+$pmda->add_metric(pmda_pmid(0,11), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_begin', '', '');
+$pmda->add_metric(pmda_pmid(0,12), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_call_procedure', '', '');
+$pmda->add_metric(pmda_pmid(0,13), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_change_db', '', '');
+$pmda->add_metric(pmda_pmid(0,14), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_change_master', '', '');
+$pmda->add_metric(pmda_pmid(0,15), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_check', '', '');
+$pmda->add_metric(pmda_pmid(0,16), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_checksum', '', '');
+$pmda->add_metric(pmda_pmid(0,17), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_commit', '', '');
+$pmda->add_metric(pmda_pmid(0,18), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_create_db', '', '');
+$pmda->add_metric(pmda_pmid(0,19), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_create_function', '', '');
+$pmda->add_metric(pmda_pmid(0,20), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_create_index', '', '');
+$pmda->add_metric(pmda_pmid(0,21), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_create_table', '', '');
+$pmda->add_metric(pmda_pmid(0,22), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_create_user', '', '');
+$pmda->add_metric(pmda_pmid(0,23), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_dealloc_sql', '', '');
+$pmda->add_metric(pmda_pmid(0,24), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_delete', '', '');
+$pmda->add_metric(pmda_pmid(0,25), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_delete_multi', '', '');
+$pmda->add_metric(pmda_pmid(0,26), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_do', '', '');
+$pmda->add_metric(pmda_pmid(0,27), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_drop_db', '', '');
+$pmda->add_metric(pmda_pmid(0,28), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_drop_function', '', '');
+$pmda->add_metric(pmda_pmid(0,29), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_drop_index', '', '');
+$pmda->add_metric(pmda_pmid(0,30), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_drop_table', '', '');
+$pmda->add_metric(pmda_pmid(0,31), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_drop_user', '', '');
+$pmda->add_metric(pmda_pmid(0,32), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_execute_sql', '', '');
+$pmda->add_metric(pmda_pmid(0,33), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_flush', '', '');
+$pmda->add_metric(pmda_pmid(0,34), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_grant', '', '');
+$pmda->add_metric(pmda_pmid(0,35), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_ha_close', '', '');
+$pmda->add_metric(pmda_pmid(0,36), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_ha_open', '', '');
+$pmda->add_metric(pmda_pmid(0,37), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_ha_read', '', '');
+$pmda->add_metric(pmda_pmid(0,38), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_help', '', '');
+$pmda->add_metric(pmda_pmid(0,39), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_insert', '', '');
+$pmda->add_metric(pmda_pmid(0,40), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_insert_select', '', '');
+$pmda->add_metric(pmda_pmid(0,41), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_kill', '', '');
+$pmda->add_metric(pmda_pmid(0,42), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_load', '', '');
+$pmda->add_metric(pmda_pmid(0,43), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_load_master_data', '', '');
+$pmda->add_metric(pmda_pmid(0,44), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_load_master_table', '', '');
+$pmda->add_metric(pmda_pmid(0,45), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_lock_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,46), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_optimize', '', '');
+$pmda->add_metric(pmda_pmid(0,47), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_preload_keys', '', '');
+$pmda->add_metric(pmda_pmid(0,48), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_prepare_sql', '', '');
+$pmda->add_metric(pmda_pmid(0,49), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_purge', '', '');
+$pmda->add_metric(pmda_pmid(0,50), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_purge_before_date', '', '');
+$pmda->add_metric(pmda_pmid(0,51), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_rename_table', '', '');
+$pmda->add_metric(pmda_pmid(0,52), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_repair', '', '');
+$pmda->add_metric(pmda_pmid(0,53), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_replace', '', '');
+$pmda->add_metric(pmda_pmid(0,54), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_replace_select', '', '');
+$pmda->add_metric(pmda_pmid(0,55), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_reset', '', '');
+$pmda->add_metric(pmda_pmid(0,56), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_restore_table', '', '');
+$pmda->add_metric(pmda_pmid(0,57), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_revoke', '', '');
+$pmda->add_metric(pmda_pmid(0,58), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_revoke_all', '', '');
+$pmda->add_metric(pmda_pmid(0,59), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_rollback', '', '');
+$pmda->add_metric(pmda_pmid(0,60), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_savepoint', '', '');
+$pmda->add_metric(pmda_pmid(0,61), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_select', '', '');
+$pmda->add_metric(pmda_pmid(0,62), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_set_option', '', '');
+$pmda->add_metric(pmda_pmid(0,63), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_binlog_events', '', '');
+$pmda->add_metric(pmda_pmid(0,64), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_binlogs', '', '');
+$pmda->add_metric(pmda_pmid(0,65), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_charsets', '', '');
+$pmda->add_metric(pmda_pmid(0,66), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_collations', '', '');
+$pmda->add_metric(pmda_pmid(0,67), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_column_types', '', '');
+$pmda->add_metric(pmda_pmid(0,68), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_create_db', '', '');
+$pmda->add_metric(pmda_pmid(0,69), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_create_table', '', '');
+$pmda->add_metric(pmda_pmid(0,70), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_databases', '', '');
+$pmda->add_metric(pmda_pmid(0,71), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_errors', '', '');
+$pmda->add_metric(pmda_pmid(0,72), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_fields', '', '');
+$pmda->add_metric(pmda_pmid(0,73), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_grants', '', '');
+$pmda->add_metric(pmda_pmid(0,74), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_innodb_status', '', '');
+$pmda->add_metric(pmda_pmid(0,75), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_keys', '', '');
+$pmda->add_metric(pmda_pmid(0,76), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_logs', '', '');
+$pmda->add_metric(pmda_pmid(0,77), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_master_status', '', '');
+$pmda->add_metric(pmda_pmid(0,78), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_ndb_status', '', '');
+$pmda->add_metric(pmda_pmid(0,79), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_new_master', '', '');
+$pmda->add_metric(pmda_pmid(0,80), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_open_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,81), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_privileges', '', '');
+$pmda->add_metric(pmda_pmid(0,82), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_processlist', '', '');
+$pmda->add_metric(pmda_pmid(0,83), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_slave_hosts', '', '');
+$pmda->add_metric(pmda_pmid(0,84), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_slave_status', '', '');
+$pmda->add_metric(pmda_pmid(0,85), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_status', '', '');
+$pmda->add_metric(pmda_pmid(0,86), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_storage_engines', '', '');
+$pmda->add_metric(pmda_pmid(0,87), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,88), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_triggers', '', '');
+$pmda->add_metric(pmda_pmid(0,89), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_variables', '', '');
+$pmda->add_metric(pmda_pmid(0,90), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_show_warnings', '', '');
+$pmda->add_metric(pmda_pmid(0,91), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_slave_start', '', '');
+$pmda->add_metric(pmda_pmid(0,92), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_slave_stop', '', '');
+$pmda->add_metric(pmda_pmid(0,93), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_close', '', '');
+$pmda->add_metric(pmda_pmid(0,94), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_execute', '', '');
+$pmda->add_metric(pmda_pmid(0,95), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_fetch', '', '');
+$pmda->add_metric(pmda_pmid(0,96), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_prepare', '', '');
+$pmda->add_metric(pmda_pmid(0,97), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_reset', '', '');
+$pmda->add_metric(pmda_pmid(0,98), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_stmt_send_long_data', '', '');
+$pmda->add_metric(pmda_pmid(0,99), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_truncate', '', '');
+$pmda->add_metric(pmda_pmid(0,100), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_unlock_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,101), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_update', '', '');
+$pmda->add_metric(pmda_pmid(0,102), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_update_multi', '', '');
+$pmda->add_metric(pmda_pmid(0,103), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_commit', '', '');
+$pmda->add_metric(pmda_pmid(0,104), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_end', '', '');
+$pmda->add_metric(pmda_pmid(0,105), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_prepare', '', '');
+$pmda->add_metric(pmda_pmid(0,106), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_recover', '', '');
+$pmda->add_metric(pmda_pmid(0,107), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_rollback', '', '');
+$pmda->add_metric(pmda_pmid(0,108), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.com_xa_start', '', '');
+$pmda->add_metric(pmda_pmid(0,109), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.compression', '', '');
+$pmda->add_metric(pmda_pmid(0,110), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.connections', '', '');
+$pmda->add_metric(pmda_pmid(0,111), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.created_tmp_disk_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,112), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.created_tmp_files', '', '');
+$pmda->add_metric(pmda_pmid(0,113), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.created_tmp_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,114), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.delayed_errors', '', '');
+$pmda->add_metric(pmda_pmid(0,115), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.delayed_insert_threads', '', '');
+$pmda->add_metric(pmda_pmid(0,116), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.delayed_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,117), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.flush_commands', '', '');
+$pmda->add_metric(pmda_pmid(0,118), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_commit', '', '');
+$pmda->add_metric(pmda_pmid(0,119), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_delete', '', '');
+$pmda->add_metric(pmda_pmid(0,120), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_discover', '', '');
+$pmda->add_metric(pmda_pmid(0,121), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_prepare', '', '');
+$pmda->add_metric(pmda_pmid(0,122), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_first', '', '');
+$pmda->add_metric(pmda_pmid(0,123), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_key', '', '');
+$pmda->add_metric(pmda_pmid(0,124), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_next', '', '');
+$pmda->add_metric(pmda_pmid(0,125), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_prev', '', '');
+$pmda->add_metric(pmda_pmid(0,126), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_rnd', '', '');
+$pmda->add_metric(pmda_pmid(0,127), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_read_rnd_next', '', '');
+$pmda->add_metric(pmda_pmid(0,128), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_rollback', '', '');
+$pmda->add_metric(pmda_pmid(0,129), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_savepoint', '', '');
+$pmda->add_metric(pmda_pmid(0,130), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_savepoint_rollback', '', '');
+$pmda->add_metric(pmda_pmid(0,131), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_update', '', '');
+$pmda->add_metric(pmda_pmid(0,132), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.handler_write', '', '');
+$pmda->add_metric(pmda_pmid(0,133), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_data', '', '');
+$pmda->add_metric(pmda_pmid(0,134), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_dirty', '', '');
+$pmda->add_metric(pmda_pmid(0,135), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_flushed', '', '');
+$pmda->add_metric(pmda_pmid(0,136), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_free', '', '');
+$pmda->add_metric(pmda_pmid(0,137), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_latched', '', '');
+$pmda->add_metric(pmda_pmid(0,138), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_misc', '', '');
+$pmda->add_metric(pmda_pmid(0,139), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_pages_total', '', '');
+$pmda->add_metric(pmda_pmid(0,140), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_read_ahead_rnd', '', '');
+$pmda->add_metric(pmda_pmid(0,141), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_read_ahead_seq', '', '');
+$pmda->add_metric(pmda_pmid(0,142), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_read_requests', '', '');
+$pmda->add_metric(pmda_pmid(0,143), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_reads', '', '');
+$pmda->add_metric(pmda_pmid(0,144), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_wait_free', '', '');
+$pmda->add_metric(pmda_pmid(0,145), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_buffer_pool_write_requests', '', '');
+$pmda->add_metric(pmda_pmid(0,146), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_fsyncs', '', '');
+$pmda->add_metric(pmda_pmid(0,147), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_pending_fsyncs', '', '');
+$pmda->add_metric(pmda_pmid(0,148), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_pending_reads', '', '');
+$pmda->add_metric(pmda_pmid(0,149), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_pending_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,150), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.innodb_data_read', '', '');
+$pmda->add_metric(pmda_pmid(0,151), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_reads', '', '');
+$pmda->add_metric(pmda_pmid(0,152), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_data_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,153), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.innodb_data_written', '', '');
+$pmda->add_metric(pmda_pmid(0,154), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_dblwr_pages_written', '', '');
+$pmda->add_metric(pmda_pmid(0,155), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_dblwr_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,156), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_log_waits', '', '');
+$pmda->add_metric(pmda_pmid(0,157), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_log_write_requests', '', '');
+$pmda->add_metric(pmda_pmid(0,158), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_log_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,159), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_os_log_fsyncs', '', '');
+$pmda->add_metric(pmda_pmid(0,160), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_os_log_pending_fsyncs', '', '');
+$pmda->add_metric(pmda_pmid(0,161), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_os_log_pending_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,162), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.innodb_os_log_written', '', '');
+$pmda->add_metric(pmda_pmid(0,163), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.innodb_page_size', '', '');
+$pmda->add_metric(pmda_pmid(0,164), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_pages_created', '', '');
+$pmda->add_metric(pmda_pmid(0,165), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_pages_read', '', '');
+$pmda->add_metric(pmda_pmid(0,166), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_pages_written', '', '');
+$pmda->add_metric(pmda_pmid(0,167), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_row_lock_current_waits', '', '');
+$pmda->add_metric(pmda_pmid(0,168), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'mysql.status.innodb_row_lock_time', '', '');
+$pmda->add_metric(pmda_pmid(0,169), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'mysql.status.innodb_row_lock_time_avg', '', '');
+$pmda->add_metric(pmda_pmid(0,170), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'mysql.status.innodb_row_lock_time_max', '', '');
+$pmda->add_metric(pmda_pmid(0,171), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_row_lock_waits', '', '');
+$pmda->add_metric(pmda_pmid(0,172), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_rows_deleted', '', '');
+$pmda->add_metric(pmda_pmid(0,173), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_rows_inserted', '', '');
+$pmda->add_metric(pmda_pmid(0,174), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_rows_read', '', '');
+$pmda->add_metric(pmda_pmid(0,175), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.innodb_rows_updated', '', '');
+$pmda->add_metric(pmda_pmid(0,176), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_blocks_not_flushed', '', '');
+$pmda->add_metric(pmda_pmid(0,177), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_blocks_unused', '', '');
+$pmda->add_metric(pmda_pmid(0,178), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_blocks_used', '', '');
+$pmda->add_metric(pmda_pmid(0,179), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_read_requests', '', '');
+$pmda->add_metric(pmda_pmid(0,180), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_reads', '', '');
+$pmda->add_metric(pmda_pmid(0,181), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_write_requests', '', '');
+$pmda->add_metric(pmda_pmid(0,182), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.key_writes', '', '');
+$pmda->add_metric(pmda_pmid(0,183), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.last_query_cost', '', '');
+$pmda->add_metric(pmda_pmid(0,184), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.max_used_connections', '', '');
+$pmda->add_metric(pmda_pmid(0,185), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ndb_cluster_node_id', '', '');
+$pmda->add_metric(pmda_pmid(0,186), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ndb_config_from_host', '', '');
+$pmda->add_metric(pmda_pmid(0,187), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ndb_config_from_port', '', '');
+$pmda->add_metric(pmda_pmid(0,188), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ndb_number_of_data_nodes', '', '');
+$pmda->add_metric(pmda_pmid(0,189), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.not_flushed_delayed_rows', '', '');
+$pmda->add_metric(pmda_pmid(0,190), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.open_files', '', '');
+$pmda->add_metric(pmda_pmid(0,191), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.open_streams', '', '');
+$pmda->add_metric(pmda_pmid(0,192), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.open_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,193), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.opened_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,194), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.prepared_stmt_count', '', '');
+$pmda->add_metric(pmda_pmid(0,195), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.qcache_free_blocks', '', '');
+$pmda->add_metric(pmda_pmid(0,196), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.status.qcache_free_memory', '', '');
+$pmda->add_metric(pmda_pmid(0,197), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_hits', '', '');
+$pmda->add_metric(pmda_pmid(0,198), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_inserts', '', '');
+$pmda->add_metric(pmda_pmid(0,199), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_lowmem_prunes', '', '');
+$pmda->add_metric(pmda_pmid(0,200), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_not_cached', '', '');
+$pmda->add_metric(pmda_pmid(0,201), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_queries_in_cache', '', '');
+$pmda->add_metric(pmda_pmid(0,202), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.qcache_total_blocks', '', '');
+$pmda->add_metric(pmda_pmid(0,203), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.questions', '', '');
+$pmda->add_metric(pmda_pmid(0,204), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.rpl_status', '', '');
+$pmda->add_metric(pmda_pmid(0,205), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.select_full_join', '', '');
+$pmda->add_metric(pmda_pmid(0,206), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.select_full_range_join', '', '');
+$pmda->add_metric(pmda_pmid(0,207), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.select_range', '', '');
+$pmda->add_metric(pmda_pmid(0,208), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.select_range_check', '', '');
+$pmda->add_metric(pmda_pmid(0,209), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.select_scan', '', '');
+$pmda->add_metric(pmda_pmid(0,210), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.slave_open_temp_tables', '', '');
+$pmda->add_metric(pmda_pmid(0,211), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.slave_retried_transactions', '', '');
+$pmda->add_metric(pmda_pmid(0,212), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.slave_running', '', '');
+$pmda->add_metric(pmda_pmid(0,213), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.slow_launch_threads', '', '');
+$pmda->add_metric(pmda_pmid(0,214), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.slow_queries', '', '');
+$pmda->add_metric(pmda_pmid(0,215), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.sort_merge_passes', '', '');
+$pmda->add_metric(pmda_pmid(0,216), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.sort_range', '', '');
+$pmda->add_metric(pmda_pmid(0,217), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.sort_rows', '', '');
+$pmda->add_metric(pmda_pmid(0,218), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.sort_scan', '', '');
+$pmda->add_metric(pmda_pmid(0,219), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_accept_renegotiates', '', '');
+$pmda->add_metric(pmda_pmid(0,220), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_accepts', '', '');
+$pmda->add_metric(pmda_pmid(0,221), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_callback_cache_hits', '', '');
+$pmda->add_metric(pmda_pmid(0,222), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_cipher', '', '');
+$pmda->add_metric(pmda_pmid(0,223), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_cipher_list', '', '');
+$pmda->add_metric(pmda_pmid(0,224), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_client_connects', '', '');
+$pmda->add_metric(pmda_pmid(0,225), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_connect_renegotiates', '', '');
+$pmda->add_metric(pmda_pmid(0,226), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_ctx_verify_depth', '', '');
+$pmda->add_metric(pmda_pmid(0,227), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_ctx_verify_mode', '', '');
+$pmda->add_metric(pmda_pmid(0,228), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.status.ssl_default_timeout', '', '');
+$pmda->add_metric(pmda_pmid(0,229), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_finished_accepts', '', '');
+$pmda->add_metric(pmda_pmid(0,230), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_finished_connects', '', '');
+$pmda->add_metric(pmda_pmid(0,231), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_session_cache_hits', '', '');
+$pmda->add_metric(pmda_pmid(0,232), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_session_cache_misses', '', '');
+$pmda->add_metric(pmda_pmid(0,233), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_session_cache_mode', '', '');
+$pmda->add_metric(pmda_pmid(0,234), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_session_cache_overflows', '', '');
+$pmda->add_metric(pmda_pmid(0,235), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_session_cache_size', '', '');
+$pmda->add_metric(pmda_pmid(0,236), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_session_cache_timeouts', '', '');
+$pmda->add_metric(pmda_pmid(0,237), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.ssl_sessions_reused', '', '');
+$pmda->add_metric(pmda_pmid(0,238), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_used_session_cache_entries', '', '');
+$pmda->add_metric(pmda_pmid(0,239), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_verify_depth', '', '');
+$pmda->add_metric(pmda_pmid(0,240), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_verify_mode', '', '');
+$pmda->add_metric(pmda_pmid(0,241), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.ssl_version', '', '');
+$pmda->add_metric(pmda_pmid(0,242), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.table_locks_immediate', '', '');
+$pmda->add_metric(pmda_pmid(0,243), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.table_locks_waited', '', '');
+$pmda->add_metric(pmda_pmid(0,244), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.tc_log_max_pages_used', '', '');
+$pmda->add_metric(pmda_pmid(0,245), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.tc_log_page_size', '', '');
+$pmda->add_metric(pmda_pmid(0,246), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.status.tc_log_page_waits', '', '');
+$pmda->add_metric(pmda_pmid(0,247), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.threads_cached', '', '');
+$pmda->add_metric(pmda_pmid(0,248), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.threads_connected', '', '');
+$pmda->add_metric(pmda_pmid(0,249), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.threads_created', '', '');
+$pmda->add_metric(pmda_pmid(0,250), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.threads_running', '', '');
+$pmda->add_metric(pmda_pmid(0,251), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.status.uptime', '', '');
+$pmda->add_metric(pmda_pmid(0,252), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.status.uptime_since_flush_status', '', '');
+$pmda->add_metric(pmda_pmid(0,253), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.slave_running_num', '', '');
+$pmda->add_metric(pmda_pmid(0,254), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.status.compression_num', '', '');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.auto_increment_increment', '', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.auto_increment_offset', '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.automatic_sp_privileges', '', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.back_log', '', '');
+$pmda->add_metric(pmda_pmid(1,4), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.basedir', '', '');
+$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.binlog_cache_size', '', '');
+$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.bulk_insert_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,6), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_client', '', '');
+$pmda->add_metric(pmda_pmid(1,7), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_connection', '', '');
+$pmda->add_metric(pmda_pmid(1,8), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_database', '', '');
+$pmda->add_metric(pmda_pmid(1,9), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_filesystem', '', '');
+$pmda->add_metric(pmda_pmid(1,10), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_results', '', '');
+$pmda->add_metric(pmda_pmid(1,11), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_server', '', '');
+$pmda->add_metric(pmda_pmid(1,12), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_set_system', '', '');
+$pmda->add_metric(pmda_pmid(1,13), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.character_sets_dir', '', '');
+$pmda->add_metric(pmda_pmid(1,14), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.collation_connection', '', '');
+$pmda->add_metric(pmda_pmid(1,15), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.collation_database', '', '');
+$pmda->add_metric(pmda_pmid(1,16), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.collation_server', '', '');
+$pmda->add_metric(pmda_pmid(1,17), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.completion_type', '', '');
+$pmda->add_metric(pmda_pmid(1,18), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.concurrent_insert', '', '');
+$pmda->add_metric(pmda_pmid(1,19), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.connect_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,20), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.datadir', '', '');
+$pmda->add_metric(pmda_pmid(1,21), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.date_format', '', '');
+$pmda->add_metric(pmda_pmid(1,22), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.datetime_format', '', '');
+$pmda->add_metric(pmda_pmid(1,23), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.default_week_format', '', '');
+$pmda->add_metric(pmda_pmid(1,24), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.delay_key_write', '', '');
+$pmda->add_metric(pmda_pmid(1,25), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.delayed_insert_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,26), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.delayed_insert_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,27), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.delayed_queue_size', '', '');
+$pmda->add_metric(pmda_pmid(1,28), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.div_precision_increment', '', '');
+$pmda->add_metric(pmda_pmid(1,29), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.keep_files_on_create', '', '');
+$pmda->add_metric(pmda_pmid(1,30), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.engine_condition_pushdown', '', '');
+$pmda->add_metric(pmda_pmid(1,31), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.expire_logs_days', '', '');
+$pmda->add_metric(pmda_pmid(1,32), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.flush', '', '');
+$pmda->add_metric(pmda_pmid(1,33), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.flush_time', '', '');
+$pmda->add_metric(pmda_pmid(1,34), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ft_boolean_syntax', '', '');
+$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ft_max_word_len', '', '');
+$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ft_min_word_len', '', '');
+$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ft_query_expansion_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,36), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ft_stopword_file', '', '');
+$pmda->add_metric(pmda_pmid(1,37), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.group_concat_max_len', '', '');
+$pmda->add_metric(pmda_pmid(1,38), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_archive', '', '');
+$pmda->add_metric(pmda_pmid(1,39), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_bdb', '', '');
+$pmda->add_metric(pmda_pmid(1,40), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_blackhole_engine', '', '');
+$pmda->add_metric(pmda_pmid(1,41), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_compress', '', '');
+$pmda->add_metric(pmda_pmid(1,42), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_crypt', '', '');
+$pmda->add_metric(pmda_pmid(1,43), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_csv', '', '');
+$pmda->add_metric(pmda_pmid(1,44), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_dynamic_loading', '', '');
+$pmda->add_metric(pmda_pmid(1,45), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_example_engine', '', '');
+$pmda->add_metric(pmda_pmid(1,46), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_federated_engine', '', '');
+$pmda->add_metric(pmda_pmid(1,47), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_geometry', '', '');
+$pmda->add_metric(pmda_pmid(1,48), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_innodb', '', '');
+$pmda->add_metric(pmda_pmid(1,49), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_isam', '', '');
+$pmda->add_metric(pmda_pmid(1,50), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_merge_engine', '', '');
+$pmda->add_metric(pmda_pmid(1,51), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_ndbcluster', '', '');
+$pmda->add_metric(pmda_pmid(1,52), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_openssl', '', '');
+$pmda->add_metric(pmda_pmid(1,53), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_ssl', '', '');
+$pmda->add_metric(pmda_pmid(1,54), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_query_cache', '', '');
+$pmda->add_metric(pmda_pmid(1,55), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_raid', '', '');
+$pmda->add_metric(pmda_pmid(1,56), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_rtree_keys', '', '');
+$pmda->add_metric(pmda_pmid(1,57), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.have_symlink', '', '');
+$pmda->add_metric(pmda_pmid(1,58), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.hostname', '', '');
+$pmda->add_metric(pmda_pmid(1,59), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.init_connect', '', '');
+$pmda->add_metric(pmda_pmid(1,60), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.init_file', '', '');
+$pmda->add_metric(pmda_pmid(1,61), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.init_slave', '', '');
+$pmda->add_metric(pmda_pmid(1,63), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_additional_mem_pool_size', '', '');
+$pmda->add_metric(pmda_pmid(1,64), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_autoextend_increment', '', '');
+$pmda->add_metric(pmda_pmid(1,65), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_MBYTE,0,0),
+ 'mysql.variables.innodb_buffer_pool_awe_mem_mb', '', '');
+$pmda->add_metric(pmda_pmid(1,66), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_buffer_pool_size', '', '');
+$pmda->add_metric(pmda_pmid(1,67), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_checksums', '', '');
+$pmda->add_metric(pmda_pmid(1,68), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_commit_concurrency', '', '');
+$pmda->add_metric(pmda_pmid(1,68), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_concurrency_tickets', '', '');
+$pmda->add_metric(pmda_pmid(1,69), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_data_file_path', '', '');
+$pmda->add_metric(pmda_pmid(1,70), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_data_home_dir', '', '');
+$pmda->add_metric(pmda_pmid(1,71), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_doublewrite', '', '');
+$pmda->add_metric(pmda_pmid(1,72), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_fast_shutdown', '', '');
+$pmda->add_metric(pmda_pmid(1,73), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_file_io_threads', '', '');
+$pmda->add_metric(pmda_pmid(1,74), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_file_per_table', '', '');
+$pmda->add_metric(pmda_pmid(1,75), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_flush_log_at_trx_commit', '', '');
+$pmda->add_metric(pmda_pmid(1,76), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_flush_method', '', '');
+$pmda->add_metric(pmda_pmid(1,77), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_force_recovery', '', '');
+$pmda->add_metric(pmda_pmid(1,78), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_lock_wait_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,79), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_locks_unsafe_for_binlog', '', '');
+$pmda->add_metric(pmda_pmid(1,80), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_arch_dir', '', '');
+$pmda->add_metric(pmda_pmid(1,81), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_archive', '', '');
+$pmda->add_metric(pmda_pmid(1,82), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,83), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_file_size', '', '');
+$pmda->add_metric(pmda_pmid(1,84), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_files_in_group', '', '');
+$pmda->add_metric(pmda_pmid(1,85), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_log_group_home_dir', '', '');
+$pmda->add_metric(pmda_pmid(1,86), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_max_dirty_pages_pct', '', '');
+$pmda->add_metric(pmda_pmid(1,87), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_max_purge_lag', '', '');
+$pmda->add_metric(pmda_pmid(1,88), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_mirrored_log_groups', '', '');
+$pmda->add_metric(pmda_pmid(1,89), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_open_files', '', '');
+$pmda->add_metric(pmda_pmid(1,90), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_rollback_on_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,91), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_support_xa', '', '');
+$pmda->add_metric(pmda_pmid(1,92), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_sync_spin_loops', '', '');
+$pmda->add_metric(pmda_pmid(1,93), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_table_locks', '', '');
+$pmda->add_metric(pmda_pmid(1,94), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_thread_concurrency', '', '');
+$pmda->add_metric(pmda_pmid(1,95), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.innodb_thread_sleep_delay', '', '');
+$pmda->add_metric(pmda_pmid(1,96), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.interactive_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,97), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.join_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,98), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.key_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,99), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.key_cache_age_threshold', '', '');
+$pmda->add_metric(pmda_pmid(1,100), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.key_cache_block_size', '', '');
+$pmda->add_metric(pmda_pmid(1,101), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.key_cache_division_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,102), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.language', '', '');
+$pmda->add_metric(pmda_pmid(1,103), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.large_files_support', '', '');
+$pmda->add_metric(pmda_pmid(1,104), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.large_page_size', '', '');
+$pmda->add_metric(pmda_pmid(1,105), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.large_pages', '', '');
+$pmda->add_metric(pmda_pmid(1,106), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.lc_time_names', '', '');
+$pmda->add_metric(pmda_pmid(1,107), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.license', '', '');
+$pmda->add_metric(pmda_pmid(1,108), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.local_infile', '', '');
+$pmda->add_metric(pmda_pmid(1,109), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.locked_in_memory', '', '');
+$pmda->add_metric(pmda_pmid(1,110), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log', '', '');
+$pmda->add_metric(pmda_pmid(1,111), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_bin', '', '');
+$pmda->add_metric(pmda_pmid(1,112), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_bin_trust_function_creators', '', '');
+$pmda->add_metric(pmda_pmid(1,113), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_error', '', '');
+$pmda->add_metric(pmda_pmid(1,114), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_queries_not_using_indexes', '', '');
+$pmda->add_metric(pmda_pmid(1,115), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_slave_updates', '', '');
+$pmda->add_metric(pmda_pmid(1,116), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_slow_queries', '', '');
+$pmda->add_metric(pmda_pmid(1,117), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.log_warnings', '', '');
+$pmda->add_metric(pmda_pmid(1,118), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.long_query_time', '', '');
+$pmda->add_metric(pmda_pmid(1,119), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.low_priority_updates', '', '');
+$pmda->add_metric(pmda_pmid(1,120), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.lower_case_file_system', '', '');
+$pmda->add_metric(pmda_pmid(1,121), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.lower_case_table_names', '', '');
+$pmda->add_metric(pmda_pmid(1,122), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.max_allowed_packet', '', '');
+$pmda->add_metric(pmda_pmid(1,124), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.max_binlog_cache_size', '', '');
+$pmda->add_metric(pmda_pmid(1,125), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.max_binlog_size', '', '');
+$pmda->add_metric(pmda_pmid(1,126), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_connect_errors', '', '');
+$pmda->add_metric(pmda_pmid(1,127), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_connections', '', '');
+$pmda->add_metric(pmda_pmid(1,128), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_delayed_threads', '', '');
+$pmda->add_metric(pmda_pmid(1,129), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_error_count', '', '');
+$pmda->add_metric(pmda_pmid(1,130), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_heap_table_size', '', '');
+$pmda->add_metric(pmda_pmid(1,131), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_insert_delayed_threads', '', '');
+$pmda->add_metric(pmda_pmid(1,132), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_join_size', '', '');
+$pmda->add_metric(pmda_pmid(1,133), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_length_for_sort_data', '', '');
+$pmda->add_metric(pmda_pmid(1,134), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_prepared_stmt_count', '', '');
+$pmda->add_metric(pmda_pmid(1,135), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_relay_log_size', '', '');
+$pmda->add_metric(pmda_pmid(1,136), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_seeks_for_key', '', '');
+$pmda->add_metric(pmda_pmid(1,137), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_sort_length', '', '');
+$pmda->add_metric(pmda_pmid(1,138), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_sp_recursion_depth', '', '');
+$pmda->add_metric(pmda_pmid(1,139), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_tmp_tables', '', '');
+$pmda->add_metric(pmda_pmid(1,140), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_user_connections', '', '');
+$pmda->add_metric(pmda_pmid(1,141), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.max_write_lock_count', '', '');
+$pmda->add_metric(pmda_pmid(1,142), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.multi_range_count', '', '');
+$pmda->add_metric(pmda_pmid(1,143), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.myisam_data_pointer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,144), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.myisam_max_sort_file_size', '', '');
+$pmda->add_metric(pmda_pmid(1,145), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.myisam_recover_options', '', '');
+$pmda->add_metric(pmda_pmid(1,146), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.myisam_repair_threads', '', '');
+$pmda->add_metric(pmda_pmid(1,147), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.myisam_sort_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,148), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.myisam_stats_method', '', '');
+$pmda->add_metric(pmda_pmid(1,149), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.ndb_autoincrement_prefetch_sz', '', '');
+$pmda->add_metric(pmda_pmid(1,150), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ndb_force_send', '', '');
+$pmda->add_metric(pmda_pmid(1,151), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ndb_use_exact_count', '', '');
+$pmda->add_metric(pmda_pmid(1,152), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ndb_use_transactions', '', '');
+$pmda->add_metric(pmda_pmid(1,153), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.ndb_cache_check_time', '', '');
+$pmda->add_metric(pmda_pmid(1,154), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ndb_connectstring', '', '');
+$pmda->add_metric(pmda_pmid(1,155), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.net_buffer_length', '', '');
+$pmda->add_metric(pmda_pmid(1,156), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.net_read_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,157), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.net_retry_count', '', '');
+$pmda->add_metric(pmda_pmid(1,158), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.net_write_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,159), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.new', '', '');
+$pmda->add_metric(pmda_pmid(1,159), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.old_passwords', '', '');
+$pmda->add_metric(pmda_pmid(1,160), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.open_files_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,161), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.optimizer_prune_level', '', '');
+$pmda->add_metric(pmda_pmid(1,162), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.optimizer_search_depth', '', '');
+$pmda->add_metric(pmda_pmid(1,163), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.pid_file', '', '');
+$pmda->add_metric(pmda_pmid(1,164), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.port', '', '');
+$pmda->add_metric(pmda_pmid(1,165), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.preload_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,166), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.profiling', '', '');
+$pmda->add_metric(pmda_pmid(1,167), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.profiling_history_size', '', '');
+$pmda->add_metric(pmda_pmid(1,168), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.protocol_version', '', '');
+$pmda->add_metric(pmda_pmid(1,169), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.query_alloc_block_size', '', '');
+$pmda->add_metric(pmda_pmid(1,170), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.query_cache_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,171), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.query_cache_min_res_unit', '', '');
+$pmda->add_metric(pmda_pmid(1,172), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.query_cache_size', '', '');
+$pmda->add_metric(pmda_pmid(1,173), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.query_cache_type', '', '');
+$pmda->add_metric(pmda_pmid(1,174), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.query_cache_wlock_invalidate', '', '');
+$pmda->add_metric(pmda_pmid(1,175), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.query_prealloc_size', '', '');
+$pmda->add_metric(pmda_pmid(1,176), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.range_alloc_block_size', '', '');
+$pmda->add_metric(pmda_pmid(1,177), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.read_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,178), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.read_only', '', '');
+$pmda->add_metric(pmda_pmid(1,179), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.read_rnd_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,180), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.relay_log_purge', '', '');
+$pmda->add_metric(pmda_pmid(1,181), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.relay_log_space_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,182), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.rpl_recovery_rank', '', '');
+$pmda->add_metric(pmda_pmid(1,183), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.secure_auth', '', '');
+$pmda->add_metric(pmda_pmid(1,184), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.secure_file_priv', '', '');
+$pmda->add_metric(pmda_pmid(1,185), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.server_id', '', '');
+$pmda->add_metric(pmda_pmid(1,186), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.skip_external_locking', '', '');
+$pmda->add_metric(pmda_pmid(1,187), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.skip_networking', '', '');
+$pmda->add_metric(pmda_pmid(1,188), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.skip_show_database', '', '');
+$pmda->add_metric(pmda_pmid(1,189), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.slave_compressed_protocol', '', '');
+$pmda->add_metric(pmda_pmid(1,190), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.slave_load_tmpdir', '', '');
+$pmda->add_metric(pmda_pmid(1,191), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.slave_skip_errors', '', '');
+$pmda->add_metric(pmda_pmid(1,192), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.slave_net_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,193), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.slave_transaction_retries', '', '');
+$pmda->add_metric(pmda_pmid(1,194), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.slow_launch_time', '', '');
+$pmda->add_metric(pmda_pmid(1,195), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.socket', '', '');
+$pmda->add_metric(pmda_pmid(1,196), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.sort_buffer_size', '', '');
+$pmda->add_metric(pmda_pmid(1,197), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sql_big_selects', '', '');
+$pmda->add_metric(pmda_pmid(1,198), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sql_mode', '', '');
+$pmda->add_metric(pmda_pmid(1,199), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sql_notes', '', '');
+$pmda->add_metric(pmda_pmid(1,200), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sql_warnings', '', '');
+$pmda->add_metric(pmda_pmid(1,201), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ssl_ca', '', '');
+$pmda->add_metric(pmda_pmid(1,202), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ssl_capath', '', '');
+$pmda->add_metric(pmda_pmid(1,203), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ssl_cert', '', '');
+$pmda->add_metric(pmda_pmid(1,204), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ssl_cipher', '', '');
+$pmda->add_metric(pmda_pmid(1,205), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.ssl_key', '', '');
+$pmda->add_metric(pmda_pmid(1,206), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.storage_engine', '', '');
+$pmda->add_metric(pmda_pmid(1,207), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sync_binlog', '', '');
+$pmda->add_metric(pmda_pmid(1,208), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.sync_frm', '', '');
+$pmda->add_metric(pmda_pmid(1,209), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.system_time_zone', '', '');
+$pmda->add_metric(pmda_pmid(1,210), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.table_cache', '', '');
+$pmda->add_metric(pmda_pmid(1,211), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.table_lock_wait_timeout', '', '');
+$pmda->add_metric(pmda_pmid(1,212), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.table_type', '', '');
+$pmda->add_metric(pmda_pmid(1,211), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.thread_cache_size', '', '');
+$pmda->add_metric(pmda_pmid(1,212), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.thread_stack', '', '');
+$pmda->add_metric(pmda_pmid(1,213), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.time_format', '', '');
+$pmda->add_metric(pmda_pmid(1,214), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.time_zone', '', '');
+$pmda->add_metric(pmda_pmid(1,215), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.timed_mutexes', '', '');
+$pmda->add_metric(pmda_pmid(1,216), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.variables.tmp_table_size', '', '');
+$pmda->add_metric(pmda_pmid(1,217), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.tmpdir', '', '');
+$pmda->add_metric(pmda_pmid(1,218), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.transaction_alloc_block_size', '', '');
+$pmda->add_metric(pmda_pmid(1,219), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.variables.transaction_prealloc_size', '', '');
+$pmda->add_metric(pmda_pmid(1,220), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.tx_isolation', '', '');
+$pmda->add_metric(pmda_pmid(1,221), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.updatable_views_with_limit', '', '');
+$pmda->add_metric(pmda_pmid(1,222), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.version', '', '');
+$pmda->add_metric(pmda_pmid(1,223), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.version_comment', '', '');
+$pmda->add_metric(pmda_pmid(1,224), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.version_compile_machine', '', '');
+$pmda->add_metric(pmda_pmid(1,225), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.variables.version_compile_os', '', '');
+$pmda->add_metric(pmda_pmid(1,226), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.variables.wait_timeout', '', '');
+
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.id', '', '');
+$pmda->add_metric(pmda_pmid(2,1), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.user', '', '');
+$pmda->add_metric(pmda_pmid(2,2), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.host', '', '');
+$pmda->add_metric(pmda_pmid(2,3), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.db', '', '');
+$pmda->add_metric(pmda_pmid(2,4), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.command', '', '');
+$pmda->add_metric(pmda_pmid(2,5), PM_TYPE_U32, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.processlist.time', '', '');
+$pmda->add_metric(pmda_pmid(2,6), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.state', '', '');
+$pmda->add_metric(pmda_pmid(2,7), PM_TYPE_STRING, $process_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.processlist.info', '', '');
+
+$pmda->add_metric(pmda_pmid(3,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.slave_io_state', '', '');
+$pmda->add_metric(pmda_pmid(3,1), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.slave_io_running', '', '');
+$pmda->add_metric(pmda_pmid(3,2), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.slave_sql_running', '', '');
+$pmda->add_metric(pmda_pmid(3,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.slave_status.seconds_behind_master', '', '');
+$pmda->add_metric(pmda_pmid(3,4), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_log_file', '', '');
+$pmda->add_metric(pmda_pmid(3,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.slave_status.read_master_log_pos', '', '');
+$pmda->add_metric(pmda_pmid(3,6), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.relay_master_log_file', '', '');
+$pmda->add_metric(pmda_pmid(3,7), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.slave_status.exec_master_log_pos', '', '');
+$pmda->add_metric(pmda_pmid(3,8), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.relay_log_file', '', '');
+$pmda->add_metric(pmda_pmid(3,9), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.slave_status.relay_log_pos', '', '');
+$pmda->add_metric(pmda_pmid(3,10), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.until_log_file', '', '');
+$pmda->add_metric(pmda_pmid(3,11), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.slave_status.until_log_pos', '', '');
+$pmda->add_metric(pmda_pmid(3,12), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_cipher', '', '');
+$pmda->add_metric(pmda_pmid(3,13), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_ca_file', '', '');
+$pmda->add_metric(pmda_pmid(3,14), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'mysql.slave_status.skip_counter', '', '');
+$pmda->add_metric(pmda_pmid(3,15), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'mysql.slave_status.relay_log_space', '', '');
+$pmda->add_metric(pmda_pmid(3,16), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.until_condition', '', '');
+$pmda->add_metric(pmda_pmid(3,17), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'mysql.slave_status.connect_retry', '', '');
+$pmda->add_metric(pmda_pmid(3,18), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_host', '', '');
+$pmda->add_metric(pmda_pmid(3,19), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.last_errno', '', '');
+$pmda->add_metric(pmda_pmid(3,20), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_cert', '', '');
+$pmda->add_metric(pmda_pmid(3,21), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_do_db', '', '');
+$pmda->add_metric(pmda_pmid(3,22), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_ignore_db', '', '');
+$pmda->add_metric(pmda_pmid(3,23), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_user', '', '');
+$pmda->add_metric(pmda_pmid(3,24), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_do_table', '', '');
+$pmda->add_metric(pmda_pmid(3,25), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_wild_do_table', '', '');
+$pmda->add_metric(pmda_pmid(3,26), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_wild_ignore_table', '', '');
+$pmda->add_metric(pmda_pmid(3,27), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.replicate_ignore_table', '', '');
+$pmda->add_metric(pmda_pmid(3,28), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_allowed', '', '');
+$pmda->add_metric(pmda_pmid(3,29), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_ca_path', '', '');
+$pmda->add_metric(pmda_pmid(3,30), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_port', '', '');
+$pmda->add_metric(pmda_pmid(3,31), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_key', '', '');
+$pmda->add_metric(pmda_pmid(3,32), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.slave_io_running_num', '', '');
+$pmda->add_metric(pmda_pmid(3,33), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.slave_sql_running_num', '', '');
+$pmda->add_metric(pmda_pmid(3,34), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'mysql.slave_status.master_ssl_allowed_num', '','');
+
+$pmda->add_indom($process_indom, \@process_instances,
+ 'Instance domain exporting each MySQL process', '');
+
+$pmda->set_fetch_callback(\&mysql_fetch_callback);
+$pmda->set_fetch(\&mysql_connection_setup);
+$pmda->set_refresh(\&mysql_refresh);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdamysql - MySQL database PMDA
+
+=head1 DESCRIPTION
+
+B<pmdamysql> is a Performance Co-Pilot PMDA which extracts
+live performance data from a running MySQL database.
+
+=head1 INSTALLATION
+
+B<pmdamysql> uses a configuration file from (in this order):
+
+=over
+
+=item * /etc/pcpdbi.conf
+
+=item * $PCP_PMDAS_DIR/mysql/mysql.conf
+
+=back
+
+This file can contain overridden values (Perl code) for the settings
+listed at the start of pmdamysql.pl, namely:
+
+=over
+
+=item * database name (see DBI(3) for details)
+
+=item * database user name
+
+=item * database pass word
+
+=back
+
+Once this is setup, you can access the names and values for the
+mysql performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/mysql
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/mysql
+ # ./Remove
+
+B<pmdamysql> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 Binary Status values in text
+
+Some of the status values are in the form of YES/NO or ON/OFF.
+
+Since these cannot be intepreted by tools like PMIE,
+they have been duplicated with a _num extension
+and the values of 1 (YES/ON) or 0 (NO/OFF).
+
+=head2 Eg:
+
+=over
+
+=item * mysql.slave_status.slave_io_running
+
+=item * mysql.slave_status.slave_io_running_num
+
+=back
+
+=head1 FILES
+
+=over
+
+=item /etc/pcpdbi.conf
+
+configuration file for all PCP database monitors
+
+=item $PCP_PMDAS_DIR/mysql/mysql.conf
+
+configuration file for B<pmdamysql>
+
+=item $PCP_PMDAS_DIR/mysql/Install
+
+installation script for the B<pmdamysql> agent
+
+=item $PCP_PMDAS_DIR/mysql/Remove
+
+undo installation script for the B<pmdamysql> agent
+
+=item $PCP_LOG_DIR/pmcd/mysql.log
+
+default log file for error messages from B<pmdamysql>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), pmdadbping.pl(1) and DBI(3).
+# vi: sw=4 ts=4 et:
diff --git a/src/pmdas/mysql/pmlogconf.summary b/src/pmdas/mysql/pmlogconf.summary
new file mode 100644
index 0000000..d10aaed
--- /dev/null
+++ b/src/pmdas/mysql/pmlogconf.summary
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident mysql summary information
+probe mysql.status exists ? include : exclude
+ mysql.status
diff --git a/src/pmdas/named/GNUmakefile b/src/pmdas/named/GNUmakefile
new file mode 100644
index 0000000..26591c1
--- /dev/null
+++ b/src/pmdas/named/GNUmakefile
@@ -0,0 +1,48 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = named
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/named/Install b/src/pmdas/named/Install
new file mode 100755
index 0000000..2f83fe9
--- /dev/null
+++ b/src/pmdas/named/Install
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the named PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=named
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+d="/var/named/data"
+f="named_stats.txt"
+if test ! -f "$d/$f" -a ! -f "/var/named/chroot/$d/$f"
+then
+ echo "named does not appear to be exporting statistics (no $d/$f found)"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/named/Remove b/src/pmdas/named/Remove
new file mode 100755
index 0000000..d50ad7b
--- /dev/null
+++ b/src/pmdas/named/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the named PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=named
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/named/pmdanamed.pl b/src/pmdas/named/pmdanamed.pl
new file mode 100644
index 0000000..a0f03ba
--- /dev/null
+++ b/src/pmdas/named/pmdanamed.pl
@@ -0,0 +1,190 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my $pmda = PCP::PMDA->new('named', 100);
+my @paths = ( '/var/named/chroot/var/named/data', '/var/named/data' );
+my $filename = 'named_stats.txt';
+my $statscmd = 'rdnc stats'; # writes to $paths/$filename
+my ( $statsdir, $statsfile );
+my $interval = 10; # time (in seconds) between runs of $statscmd
+my %values; # hash with all values mapped to metric names
+
+sub named_update
+{
+ #$pmda->log('Updating values in named statistics file');
+ system 'rndc', 'stats';
+}
+
+# Parser formats (PMDA internal numbering scheme):
+# 0: not yet known what ondisk format is
+# 1: bind 8,9.[0-4] (<=rhel5.5)
+# 2: bind 9.5+ (>=rhel5.6)
+my $version = 0;
+
+sub named_parser
+{
+ ( undef, $_ ) = @_;
+
+ #$pmda->log("named_parser got line: $_");
+
+ if (m|^\+\+\+ Statistics Dump \+\+\+ \(\d+\)$|) {
+ $version = 1; # all observed formats have this
+ } elsif (m|^\+\+ Incoming Requests \+\+$|) {
+ $version = 2; # but, new format has this also.
+ }
+
+ if ($version == 2) {
+ if (m|^\s+(\d+) responses sent$|) {
+ $values{'named.nameserver.responses.sent'} = $1;
+ } elsif (m|^\s+(\d+) IPv4 requests received$|) {
+ $values{'named.nameserver.requests.IPv4'} = $1;
+ } elsif (m|^\s+(\d+) queries resulted in successful answer$|) {
+ $values{'named.nameserver.queries.successful'} = $1;
+ } elsif (m|^\s+(\d+) queries resulted in authoritative answer$|) {
+ $values{'named.nameserver.queries.authoritative'} = $1;
+ } elsif (m|^\s+(\d+) queries resulted in non authoritative answer$|) {
+ $values{'named.nameserver.queries.non_authoritative'} = $1;
+ } elsif (m|^\s+(\d+) queries resulted in nxrrset$|) {
+ $values{'named.nameserver.queries.nxrrset'} = $1;
+ } elsif (m|^\s+(\d+) queries resulted in NXDOMAIN$|) {
+ $values{'named.nameserver.queries.nxdomain'} = $1;
+ } elsif (m|^\s+(\d+) queries caused recursion$|) {
+ $values{'named.nameserver.queries.recursion'} = $1;
+ }
+ }
+ elsif ($version == 1) {
+ if (m|^success (\d+)$|) { $values{'named.success'} = $1; }
+ elsif (m|^referral (\d+)$|) { $values{'named.referral'} = $1; }
+ elsif (m|^nxrrset (\d+)$|) { $values{'named.nxrrset'} = $1; }
+ elsif (m|^nxdomain (\d+)$|) { $values{'named.nxdomain'} = $1; }
+ elsif (m|^recursion (\d+)$|) { $values{'named.recursion'} = $1; }
+ elsif (m|^failure (\d+)$|) { $values{'named.failure'} = $1; }
+ }
+}
+
+sub named_metrics
+{
+ my $id = 0;
+
+ open STATS, $statsfile || die "Cannot open statistics file: $statsfile\n";
+ while (<STATS>) { named_parser(undef, $_); }
+ close STATS;
+
+ $pmda->add_metric(pmda_pmid(0,$id++), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'named.interval',
+ 'Time interval between invocations of rndc stats command', '');
+ foreach my $key (sort keys %values) {
+ $pmda->add_metric(pmda_pmid(0,$id++), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ $key, '', '');
+ #$pmda->log("named_metrics adding $key metric");
+ }
+}
+
+sub named_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if ($cluster != 0) { return (PM_ERR_PMID, 0); }
+ if ($item == 0) { return ($interval, 1); }
+
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my $value = $values{$metric_name};
+ #$pmda->log("named_fetch_callback for $metric_name: $cluster.$item");
+
+ return ($value, 1) if (defined($value));
+ return (PM_ERR_PMID, 0);
+}
+
+# PMDA starts here
+foreach $statsdir ( @paths ) {
+ $statsfile = $statsdir . '/' . $filename;
+ last if ( -f $statsfile );
+}
+die "Cannot find a valid named statistics file\n" unless -f $statsfile;
+named_update(); # push some values into the statistics file
+
+$pmda->set_fetch_callback(\&named_fetch_callback);
+$pmda->add_tail($statsfile, \&named_parser, 0);
+$pmda->add_timer($interval, \&named_update, 0);
+$pmda->set_user('named');
+
+named_metrics(); # fetch/parse the stats file, create metrics
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdanamed - BIND (named) PMDA
+
+=head1 DESCRIPTION
+
+B<pmdanamed> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from the BIND DNS server.
+Further details on BIND can be found at http://isc.org/.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the named performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/named
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/named
+ # ./Remove
+
+B<pmdanamed> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /var/named/data/named_stats.txt
+
+statistics file showing values exported from named
+
+=item /var/named/chroot/var/named/data/named_stats.txt
+
+chroot variant of statistics file showing values exported from named
+
+=item $PCP_PMDAS_DIR/named/Install
+
+installation script for the B<pmdanamed> agent
+
+=item $PCP_PMDAS_DIR/named/Remove
+
+undo installation script for the B<pmdanamed> agent
+
+=item $PCP_LOG_DIR/pmcd/named.log
+
+default log file for error messages from B<pmdanamed>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), named.conf(5), named(8).
diff --git a/src/pmdas/netbsd/GNUmakefile b/src/pmdas/netbsd/GNUmakefile
new file mode 100644
index 0000000..c904009
--- /dev/null
+++ b/src/pmdas/netbsd/GNUmakefile
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2000,2003,2004,2008 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2007-2010 Aconex. All Rights Reserved.
+# Copyright (c) 2012 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = netbsd
+DOMAIN = NETBSD
+CMDTARGET = pmdanetbsd
+LIBTARGET = pmda_netbsd.so
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+CONF_LINE = "netbsd 116 dso netbsd_init $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = netbsd.c disk.c netif.c
+
+HFILES = netbsd.h
+
+LSRCFILES = help root_netbsd
+LDIRT = help.dir help.pag help.sed help.tmp domain.h $(IAM).log
+
+LLDLIBS = $(PCP_PMDALIB) -lkvm
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "netbsd"
+build-me: domain.h $(LIBTARGET) $(CMDTARGET) help.dir help.pag
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help help.dir help.pag $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_netbsd $(PCP_VAR_DIR)/pmns/root_netbsd
+else
+build-me:
+install:
+endif
+
+help.dir help.pag : domain.h help
+ $(SED) <domain.h >help.sed -n -e '/#define/s/#define[ ]*\([A-Z][A-Z]*\)[ ]*\([0-9][0-9]*\)/s@\1@\2@/p'
+ $(SED) -f help.sed <help >help.tmp
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_netbsd -v 2 -o help <help.tmp
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(OBJECTS): domain.h netbsd.h
diff --git a/src/pmdas/netbsd/disk.c b/src/pmdas/netbsd/disk.c
new file mode 100644
index 0000000..b188ae4
--- /dev/null
+++ b/src/pmdas/netbsd/disk.c
@@ -0,0 +1,216 @@
+/*
+ * NetBSD Kernel PMDA - disk metrics
+ *
+ * Copyright (c) 2012,2013 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "netbsd.h"
+
+#if 0
+#include <devstat.h>
+struct devinfo devinfo = { 0 };
+struct statinfo statinfo;
+#endif
+
+void
+refresh_disk_metrics(void)
+{
+#if 0
+ static int init_done = 0;
+ int i;
+ int sts;
+
+ if (!init_done) {
+ sts = devstat_checkversion(NULL);
+ if (sts != 0) {
+ fprintf(stderr, "refresh_disk_metrics: devstat_checkversion: failed! %s\n", devstat_errbuf);
+ exit(1);
+ }
+ statinfo.dinfo = &devinfo;
+ init_done = 1;
+ }
+
+ sts = devstat_getdevs(NULL, &statinfo);
+ if (sts < 0) {
+ fprintf(stderr, "refresh_disk_metrics: devstat_getdevs: %s\n", strerror(errno));
+ exit(1);
+ }
+ else if (sts == 1) {
+ /*
+ * First call, else devstat[] list has changed
+ */
+ struct devstat *dsp;
+ char iname[DEVSTAT_NAME_LEN+6];
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ for (i = 0; i < devinfo.numdevs; i++) {
+ dsp = &devinfo.devices[i];
+ /*
+ * Skip entries that are not interesting ... only include
+ * "da" (direct access) disks at this stage
+ */
+ if (strcmp(dsp->device_name, "da") != 0)
+ continue;
+ snprintf(iname, sizeof(iname), "%s%d", dsp->device_name, dsp->unit_number);
+ sts = pmdaCacheLookupName(indomtab[DISK_INDOM].it_indom, iname, NULL, NULL);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ int j;
+ fprintf(stderr, "refresh_disk_metrics: Warning: duplicate name (%s) in disk indom\n", iname);
+ for (j = 0; j < devinfo.numdevs; j++) {
+ dsp = &devinfo.devices[j];
+ fprintf(stderr, " devinfo[%d]: %s%d\n", j, dsp->device_name, dsp->unit_number);
+ }
+ continue;
+ }
+ else {
+ /* new entry or reactivate an existing one */
+ pmdaCacheStore(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_ADD, iname, (void *)dsp);
+ }
+ }
+ }
+#endif
+
+}
+
+int
+do_disk_metrics(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+#if 0
+ struct devstat *dsp;
+ int sts;
+
+ if (inst != PM_IN_NULL) {
+ /*
+ * per-disk metrics
+ */
+ sts = pmdaCacheLookup(indomtab[DISK_INDOM].it_indom, inst, NULL, (void **)&dsp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ sts = 1;
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 0: /* disk.dev.read */
+ atom->ull = dsp->operations[DEVSTAT_READ];
+ break;
+
+ case 1: /* disk.dev.write */
+ atom->ull = dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 2: /* disk.dev.total */
+ atom->ull = dsp->operations[DEVSTAT_READ] + dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 3: /* disk.dev.read_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_READ];
+ break;
+
+ case 4: /* disk.dev.write_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 5: /* disk.dev.total_bytes */
+ atom->ull = dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 12: /* disk.dev.blkread */
+ atom->ull = dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_READ] / dsp->block_size;
+ break;
+
+ case 13: /* disk.dev.blkwrite */
+ atom->ull = dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_WRITE] / dsp->block_size;
+ break;
+
+ case 14: /* disk.dev.blktotal */
+ atom->ull = dsp->block_size == 0 ? 0 : (dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE]) / dsp->block_size;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else
+ sts = 0;
+ }
+ else {
+ /*
+ * all-disk summary metrics
+ */
+ int i;
+ atom->ull = 0;
+ sts = 1;
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_WALK_REWIND);
+ while (sts == 1 && (i = pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_WALK_NEXT)) >= 0) {
+ int lsts;
+ lsts = pmdaCacheLookup(indomtab[DISK_INDOM].it_indom, i, NULL, (void **)&dsp);
+ if (lsts == PMDA_CACHE_ACTIVE) {
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 6: /* disk.all.read */
+ atom->ull += dsp->operations[DEVSTAT_READ];
+ break;
+
+ case 7: /* disk.all.write */
+ atom->ull += dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 8: /* disk.all.total */
+ atom->ull += dsp->operations[DEVSTAT_READ] + dsp->operations[DEVSTAT_WRITE];
+ break;
+
+ case 9: /* disk.all.read_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_READ];
+ break;
+
+ case 10: /* disk.all.write_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 11: /* disk.all.total_bytes */
+ atom->ull += dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE];
+ break;
+
+ case 15: /* disk.all.blkread */
+ atom->ull += dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_READ] / dsp->block_size;
+ break;
+
+ case 16: /* disk.all.blkwrite */
+ atom->ull += dsp->block_size == 0 ? 0 : dsp->bytes[DEVSTAT_WRITE] / dsp->block_size;
+ break;
+
+ case 17: /* disk.all.blktotal */
+ atom->ull += dsp->block_size == 0 ? 0 : (dsp->bytes[DEVSTAT_READ] + dsp->bytes[DEVSTAT_WRITE]) / dsp->block_size;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ }
+ if (i < 0 && i != -1)
+ /* not end of indom from cache walk, some other error */
+ sts = i;
+ }
+
+ return sts;
+#else
+ return PM_ERR_PMID;
+#endif
+}
diff --git a/src/pmdas/netbsd/help b/src/pmdas/netbsd/help
new file mode 100644
index 0000000..50cf1ea
--- /dev/null
+++ b/src/pmdas/netbsd/help
@@ -0,0 +1,95 @@
+@ NETBSD.0 Instance domain for load average
+Universally 3 instances, "1 minute" (1), "5 minute" (5) and
+"15 minute" (15).
+
+@ NETBSD.1 CPU Instance domain for kernel.percpu metrics
+One instance for each physical CPU.
+
+@ NETBSD.2 DISK Instance domain for disk.dev metrics
+One instance for each physical "direct access" device.
+
+@ kernel.all.hz system hz rate
+Microseconds per "hz" tick.
+@ kernel.all.load 1, 5 and 15 minute load average
+@ kernel.all.pswitch count of context switches
+@ kernel.all.syscall count of system calls
+@ kernel.all.intr count of interrupts serviced
+@ kernel.all.cpu.user total user CPU time for all CPUs
+@ kernel.all.cpu.nice total nice user CPU time for all CPUs
+@ kernel.all.cpu.sys total sys CPU time for all CPUs
+@ kernel.all.cpu.intr total interrupt CPU time for all CPUs
+@ kernel.all.cpu.idle total idle CPU time for all CPUs
+@ kernel.percpu.cpu.user user CPU time for each CPU
+@ kernel.percpu.cpu.nice nice user CPU time for each CPU
+@ kernel.percpu.cpu.sys sys CPU time for each CPU
+@ kernel.percpu.cpu.intr interrupt CPU time for each CPU
+@ kernel.percpu.cpu.idle idle CPU time for each CPU
+
+@ hinv.ncpu number of CPUs in the system
+@ hinv.ndisk number of disks in the system
+@ hinv.physmem total system memory
+@ hinv.pagesize kernel page size
+@ hinv.cpu.vendor system's CPU vendor
+@ hinv.cpu.model system's CPU model
+@ hinv.cpu.arch system's machine dependent CPU architecture type
+
+@ swap.length total swap space size
+@ swap.used reserved (or allocated) swap space
+@ swap.free available swap space
+
+@ swap.pagesin pages read from external storage to service page faults
+@ swap.pagesout dirty pages written to swap devices
+When the rate of page writes is non-zero, this is the most useful
+indication severe demand for physical memory.
+@ swap.in number of swap in operations
+@ swap.out number of swap out operations
+
+@ disk.dev.read Count of read operations per disk
+@ disk.dev.write Count of write operations per disk
+@ disk.dev.total Count of read or write operations (IOPs) per disk
+@ disk.dev.read_bytes Count of bytes read from each disk
+@ disk.dev.write_bytes Count of bytes written to each disk
+@ disk.dev.total_bytes Count of bytes transferred to or from each disk
+@ disk.dev.blkread Count of blocks read from each disk
+@ disk.dev.blkwrite Count of blocks written to each disk
+@ disk.dev.blktotal Count of blocks transferred to or from each disk
+@ disk.all.read Count of read operations across all disks
+@ disk.all.write Count of write operations across all disks
+@ disk.all.total Count of read or write operations (IOPs) across all disks
+@ disk.all.read_bytes Count of bytes read from all disks
+@ disk.all.write_bytes Count of bytes written to all disks
+@ disk.all.total_bytes Count of bytes transferred to or from all disks
+@ disk.all.blkread Count of blocks read from all disks
+@ disk.all.blkwrite Count of blocks written to all disks
+@ disk.all.blktotal Count of blocks transferred to or from all disks
+
+@ mem.util.all Total memory managed by the system
+@ mem.util.used Memory that is actively in use
+Equals "all" minus "free" minus "inactive" minus "cached".
+@ mem.util.free Unallocated and free memory
+@ mem.util.bufmem Memory associated with active buffer I/O
+@ mem.util.cached Cached memory
+Unmodified (clean and cached) pages from files in filesystems.
+@ mem.util.wired Wired or pinned memory that cannot be paged out
+@ mem.util.active Recently accessed memory
+@ mem.util.inactive Memory that is in use, but has not be accessed recently
+@ mem.util.avail Available memory
+Free plus inactive plus cached.
+
+@ network.interface.mtu Maximum Transfer Unit for each network interface
+@ network.interface.up "UP" state for each network interface
+@ network.interface.baudrate Data baudrate for each network interface
+@ network.interface.in.bytes Bytes received on each network interface
+@ network.interface.in.packets Packets received on each network interface
+@ network.interface.in.mcasts Multicast packets received on each network interface
+@ network.interface.in.errors Input errors on each network interface
+@ network.interface.in.drops Dropped packets on each network interface
+@ network.interface.out.bytes Bytes transmitted on each network interface
+@ network.interface.out.packets Packets transmitted on each network interface
+@ network.interface.out.mcasts Multicast packets transmitted on each network interface
+@ network.interface.out.errors Output errors on each network interface
+@ network.interface.out.collisions Output collisions on each network interface
+@ network.interface.total.bytes Bytes received or transmitted on each network interface
+@ network.interface.total.packets Packets received or transmitted on each network interface
+@ network.interface.total.mcasts Multicast packets received or transmitted on each network interface
+@ network.interface.total.errors Input or output errors on each network interface
diff --git a/src/pmdas/netbsd/netbsd.c b/src/pmdas/netbsd/netbsd.c
new file mode 100644
index 0000000..d0c68bc
--- /dev/null
+++ b/src/pmdas/netbsd/netbsd.c
@@ -0,0 +1,981 @@
+/*
+ * NetBSD Kernel PMDA
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2012,2013 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <uvm/uvm_param.h>
+
+#include "domain.h"
+#include "netbsd.h"
+
+/* static instances */
+static pmdaInstid loadav_indom[] = {
+ { 1, "1 minute" }, { 5, "5 minute" }, { 15, "15 minute" }
+};
+
+/* instance domains */
+pmdaIndom indomtab[] = {
+ { LOADAV_INDOM, sizeof(loadav_indom)/sizeof(loadav_indom[0]), loadav_indom },
+ { CPU_INDOM, 0, NULL },
+ { DISK_INDOM, 0, NULL },
+ { NETIF_INDOM, 0, NULL },
+};
+static int indomtablen = sizeof(indomtab) / sizeof(indomtab[0]);
+
+#define CL_SYSCTL 0
+#define CL_SPECIAL 1
+#define CL_DISK 2
+#define CL_NETIF 3
+
+/*
+ * All the PCP metrics.
+ *
+ * For sysctl metrics, m_user (the first field) is set the the PCP
+ * name of the metric, and during initialization this is replaced by
+ * a pointer to the corresponding entry in mib[] (based on matching,
+ * or prefix matching (see matchname()) the PCP name here with
+ * m_pcpname[] in the mib[] entries.
+ *
+ * cluster map
+ * CL_SYSCTL simple sysctl() metrics, either one metric per mib, or
+ * one struct per mib
+ * CL_SPECIAL trickier sysctl() metrics involving synthesis or arithmetic
+ * or other methods
+ * CL_DISK disk metrics
+ * CL_NETIF network interface metrics
+ */
+
+static pmdaMetric metrictab[] = {
+ { (void *)"hinv.ncpu",
+ { PMDA_PMID(CL_SYSCTL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.physmem",
+ { PMDA_PMID(CL_SYSCTL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"kernel.all.load",
+ { PMDA_PMID(CL_SYSCTL,2), PM_TYPE_FLOAT, LOADAV_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"kernel.all.cpu.user",
+ { PMDA_PMID(CL_SYSCTL,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.nice",
+ { PMDA_PMID(CL_SYSCTL,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.sys",
+ { PMDA_PMID(CL_SYSCTL,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.intr",
+ { PMDA_PMID(CL_SYSCTL,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.cpu.idle",
+ { PMDA_PMID(CL_SYSCTL,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.user",
+ { PMDA_PMID(CL_SYSCTL,8), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.nice",
+ { PMDA_PMID(CL_SYSCTL,9), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.sys",
+ { PMDA_PMID(CL_SYSCTL,10), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.intr",
+ { PMDA_PMID(CL_SYSCTL,11), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.percpu.cpu.idle",
+ { PMDA_PMID(CL_SYSCTL,12), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
+ { (void *)"kernel.all.hz",
+ { PMDA_PMID(CL_SYSCTL,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) } },
+ { (void *)"hinv.cpu.vendor",
+ { PMDA_PMID(CL_SYSCTL,15), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.cpu.model",
+ { PMDA_PMID(CL_SYSCTL,16), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"hinv.cpu.arch",
+ { PMDA_PMID(CL_SYSCTL,17), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { (void *)"swap.pagesin",
+ { PMDA_PMID(CL_SYSCTL,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.pagesout",
+ { PMDA_PMID(CL_SYSCTL,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.in",
+ { PMDA_PMID(CL_SYSCTL,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.in",
+ { PMDA_PMID(CL_SYSCTL,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.pswitch",
+ { PMDA_PMID(CL_SYSCTL,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.syscall",
+ { PMDA_PMID(CL_SYSCTL,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"kernel.all.intr",
+ { PMDA_PMID(CL_SYSCTL,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { (void *)"swap.length",
+ { PMDA_PMID(CL_SYSCTL,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"swap.used",
+ { PMDA_PMID(CL_SYSCTL,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+
+ { NULL, /* hinv.ndisk */
+ { PMDA_PMID(CL_SPECIAL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ /*
+ * swap.free is the difference between sysctl variables vm.swap_total
+ * and vm.swap_reserved, so it is important they are kept together
+ * in mib[] below
+ */
+ { (void *)"swap.length", /* swap.free */
+ { PMDA_PMID(CL_SPECIAL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* hinv.pagesize */
+ { PMDA_PMID(CL_SPECIAL,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { (void *)"mem.util.all",
+ { PMDA_PMID(CL_SPECIAL,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ /*
+ * mem.util.used is computed from several of the vm.stats.vm.v_*_count
+ * sysctl metrics, so it is important they are kept together in mib[]
+ * below
+ */
+ { (void *)"mem.util.all", /* mem.util.used */
+ { PMDA_PMID(CL_SPECIAL,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.free",
+ { PMDA_PMID(CL_SPECIAL,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.bufmem",
+ { PMDA_PMID(CL_SPECIAL,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.cached",
+ { PMDA_PMID(CL_SPECIAL,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.wired",
+ { PMDA_PMID(CL_SPECIAL,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.active",
+ { PMDA_PMID(CL_SPECIAL,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ { (void *)"mem.util.inactive",
+ { PMDA_PMID(CL_SPECIAL,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+ /*
+ * mem.util.avail is computed from several of the vm.stats.vm.v_*_count
+ * sysctl metrics, so it is important they are kept together in mib[]
+ * below
+ */
+ { (void *)"mem.util.all", /* mem.util.avail */
+ { PMDA_PMID(CL_SPECIAL,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
+
+ { NULL, /* disk.dev.read */
+ { PMDA_PMID(CL_DISK,0), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.write */
+ { PMDA_PMID(CL_DISK,1), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.total */
+ { PMDA_PMID(CL_DISK,2), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.read_bytes */
+ { PMDA_PMID(CL_DISK,3), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.write_bytes */
+ { PMDA_PMID(CL_DISK,4), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.total_bytes */
+ { PMDA_PMID(CL_DISK,5), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.read */
+ { PMDA_PMID(CL_DISK,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.write */
+ { PMDA_PMID(CL_DISK,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.total */
+ { PMDA_PMID(CL_DISK,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.read_bytes */
+ { PMDA_PMID(CL_DISK,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.write_bytes */
+ { PMDA_PMID(CL_DISK,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.all.total_bytes */
+ { PMDA_PMID(CL_DISK,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* disk.dev.blkread */
+ { PMDA_PMID(CL_DISK,12), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.blkwrite */
+ { PMDA_PMID(CL_DISK,13), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.dev.blktotal */
+ { PMDA_PMID(CL_DISK,14), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blkread */
+ { PMDA_PMID(CL_DISK,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blkwrite */
+ { PMDA_PMID(CL_DISK,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* disk.all.blktotal */
+ { PMDA_PMID(CL_DISK,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+
+ { NULL, /* network.interface.mtu */
+ { PMDA_PMID(CL_NETIF,0), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.up */
+ { PMDA_PMID(CL_NETIF,1), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.baudrate */
+ { PMDA_PMID(CL_NETIF,2), PM_TYPE_U64, NETIF_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+ { NULL, /* network.interface.in.bytes */
+ { PMDA_PMID(CL_NETIF,3), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.in.packets */
+ { PMDA_PMID(CL_NETIF,4), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.mcasts */
+ { PMDA_PMID(CL_NETIF,5), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.errors */
+ { PMDA_PMID(CL_NETIF,6), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.in.drops */
+ { PMDA_PMID(CL_NETIF,7), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.bytes */
+ { PMDA_PMID(CL_NETIF,8), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.out.packets */
+ { PMDA_PMID(CL_NETIF,9), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.mcasts */
+ { PMDA_PMID(CL_NETIF,10), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.errors */
+ { PMDA_PMID(CL_NETIF,11), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.out.collisions */
+ { PMDA_PMID(CL_NETIF,12), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.bytes */
+ { PMDA_PMID(CL_NETIF,13), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
+ { NULL, /* network.interface.total.packets */
+ { PMDA_PMID(CL_NETIF,14), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.mcasts */
+ { PMDA_PMID(CL_NETIF,15), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+ { NULL, /* network.interface.total.errors */
+ { PMDA_PMID(CL_NETIF,16), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+};
+static int metrictablen = sizeof(metrictab) / sizeof(metrictab[0]);
+
+/*
+ * mapping between PCP metrics and sysctl metrics
+ *
+ * initialization note ... all elments after m_pcpname and m_name are OK
+ * to be 0 or NULL
+ */
+typedef struct {
+ const char *m_pcpname; /* PCP metric name or prefix (see matchname() */
+ const char *m_name; /* sysctl metric name */
+ size_t m_miblen; /* number of elements in m_mib[] */
+ int *m_mib;
+ int m_fetched; /* 1 if m_data is current and valid */
+ size_t m_datalen; /* number of bytes in m_data[] */
+ void *m_data; /* value from sysctl */
+} mib_t;
+static mib_t map[] = {
+ { "hinv.ncpu", "hw.ncpu" },
+ { "hinv.physmem", "hw.physmem" },
+ { "hinv.cpu.vendor", "hw.machine" },
+ { "hinv.cpu.model", "hw.model" },
+ { "hinv.cpu.arch", "hw.machine_arch" },
+ { "kernel.all.load", "vm.loadavg" },
+ { "kernel.all.hz", "kern.clockrate" },
+ { "kernel.all.cpu.*", "kern.cp_time" },
+ // need special logic for kern.cp_time.0 ? { "kernel.percpu.cpu.*", "kern.cp_times." },
+ // not here? { "swap.pagesin", "vm.stats.vm.v_swappgsin" },
+ // not here? { "swap.pagesout", "vm.stats.vm.v_swappgsout" },
+ // not here? { "swap.in", "vm.stats.vm.v_swapin" },
+ { "swap.out", "vm.swapout" },
+ // not here? { "kernel.all.pswitch", "vm.stats.sys.v_swtch" },
+ // not here? { "kernel.all.syscall", "vm.stats.sys.v_syscall" },
+ // not here? { "kernel.all.intr", "vm.stats.sys.v_intr" },
+ { "mem.util.bufmem", "vfs.bufspace" },
+/*
+ * DO NOT MOVE next 2 entries ... see note above for swap.free
+ */
+ // not here? { "swap.length", "vm.swap_total" },
+ // not here? { "swap.used", "vm.swap_reserved" },
+/*
+ * DO NOT MOVE next 6 entries ... see note above for mem.util.avail
+ * and mem.util.used
+ */
+ // not here? { "mem.util.all", "vm.stats.vm.v_page_count" },
+ // not here? { "mem.util.free", "vm.stats.vm.v_free_count" },
+ // not here? { "mem.util.cached", "vm.stats.vm.v_cache_count" },
+ // not here? { "mem.util.wired", "vm.stats.vm.v_wire_count" },
+ // not here? { "mem.util.active", "vm.stats.vm.v_active_count" },
+ // not here? { "mem.util.inactive", "vm.stats.vm.v_inactive_count" },
+
+};
+static int maplen = sizeof(map) / sizeof(map[0]);
+static mib_t bad_mib = { "bad.mib", "bad.mib", 0, NULL, 0, 0, NULL };
+
+static char *username;
+static int isDSO = 1; /* =0 I am a daemon */
+static int cpuhz; /* frequency for CPU time metrics */
+static int ncpu; /* number of cpus in kern.cp_times.* data */
+static int pagesize; /* vm page size */
+
+/*
+ * Fetch values from sysctl()
+ *
+ * Expect the result to be xpect bytes to match the PCP data size or
+ * anticipated structure size, unless xpect is ==0 in which case the
+ * size test is skipped.
+ */
+static int
+do_sysctl(mib_t *mp, size_t xpect)
+{
+ /*
+ * Note zero trip if mp->m_data and mp->datalen are already valid
+ * and current
+ */
+ for ( ; mp->m_fetched == 0; ) {
+ int sts;
+ sts = sysctl(mp->m_mib, (u_int)mp->m_miblen, mp->m_data, &mp->m_datalen, NULL, 0);
+ fprintf(stderr, "sysctl(%s%s) -> %d (datalen=%d)\n", mp->m_name, mp->m_data == NULL ? " firstcall" : "", sts, (int)mp->m_datalen);
+ if (sts == 0 && mp->m_data != NULL) {
+ mp->m_fetched = 1;
+ break;
+ }
+ if ((sts == -1 && errno == ENOMEM) || (sts == 0 && mp->m_data == NULL)) {
+ /* first call for this one, or data changed size */
+ mp->m_data = realloc(mp->m_data, mp->m_datalen);
+ if (mp->m_data == NULL) {
+ fprintf(stderr, "Error: %s: buffer alloc failed for sysctl metric \"%s\"\n", mp->m_pcpname, mp->m_name);
+ __pmNoMem("do_sysctl", mp->m_datalen, PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+ else
+ return -errno;
+ }
+ if (xpect > 0 && mp->m_datalen != xpect) {
+ fprintf(stderr, "Error: %s: sysctl(%s) datalen=%d not %d!\n", mp->m_pcpname, mp->m_name, (int)mp->m_datalen, (int)xpect);
+ return 0;
+ }
+ return mp->m_datalen;
+}
+
+/*
+ * kernel memory reader setup
+ */
+struct nlist symbols[] = {
+ { .n_name = "_ifnet" },
+ { .n_name = NULL }
+};
+kvm_t *kvmp;
+
+static void
+kmemread_init(void)
+{
+ int sts;
+ int i;
+ char errmsg[_POSIX2_LINE_MAX];
+
+ /*
+ * If we're running as a daemon PMDA, assume we're setgid kmem,
+ * so we can open /dev/kmem, and downgrade privileges after the
+ * kvm_open().
+ * For a DSO PMDA, we have to assume pmcd has the required
+ * privileges and don't dink with them.
+ */
+ kvmp = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errmsg);
+ if (!isDSO)
+ setgid(getgid());
+ if (kvmp == NULL) {
+ fprintf(stderr, "kmemread_init: kvm_openfiles failed: %s\n", errmsg);
+ return;
+ }
+
+ sts = kvm_nlist(kvmp, symbols);
+ if (sts < 0) {
+ fprintf(stderr, "kmemread_init: kvm_nlist failed: %s\n", pmErrStr(-errno));
+ for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++)
+ symbols[i].n_value = 0;
+ return;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++) {
+ fprintf(stderr, "Info: kernel symbol %s found at 0x%08lx\n", symbols[i].n_name, symbols[i].n_value);
+ }
+ }
+#endif
+
+}
+
+/*
+ * Callback provided to pmdaFetch ... come here once per metric-instance
+ * pair in each pmFetch().
+ */
+static int
+netbsd_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ int sts = PM_ERR_PMID;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ mib_t *mp;
+
+ mp = (mib_t *)mdesc->m_user;
+ if (idp->cluster == CL_SYSCTL) {
+ /* sysctl() simple cases */
+ switch (idp->item) {
+ /* 32-bit integer values */
+ case 0: /* hinv.ncpu */
+ case 18: /* swap.pagesin */
+ case 19: /* swap.pagesout */
+ case 20: /* swap.in */
+ case 21: /* swap.out */
+ case 22: /* kernel.all.pswitch */
+ case 23: /* kernel.all.syscall */
+ case 24: /* kernel.all.intr */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data);
+ sts = 1;
+ }
+ break;
+
+ /* 64-bit integer values */
+ case 1: /* hinv.physmem */
+ case 25: /* swap.length */
+ case 26: /* swap.used */
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull = *((__uint64_t *)mp->m_data);
+ sts = 1;
+ }
+ break;
+
+ /* string values */
+ case 15: /* hinv.cpu.vendor */
+ case 16: /* hinv.cpu.model */
+ case 17: /* hinv.cpu.arch */
+ sts = do_sysctl(mp, (size_t)0);
+ if (sts > 0) {
+ atom->cp = (char *)mp->m_data;
+ sts = 1;
+ }
+ break;
+
+ /* structs and aggregates */
+ case 2: /* kernel.all.load */
+ sts = do_sysctl(mp, 3*sizeof(atom->d));
+ if (sts > 0) {
+ int i;
+ struct loadavg *lp = (struct loadavg *)mp->m_data;
+ if (inst == 1)
+ i = 0;
+ else if (inst == 5)
+ i = 1;
+ else if (inst == 15)
+ i = 2;
+ else
+ return PM_ERR_INST;
+ atom->f = (float)((double)lp->ldavg[i] / lp->fscale);
+ sts = 1;
+ }
+ break;
+
+ case 3: /* kernel.all.cpu.user */
+ case 4: /* kernel.all.cpu.nice */
+ case 5: /* kernel.all.cpu.sys */
+ case 6: /* kernel.all.cpu.intr */
+ case 7: /* kernel.all.cpu.idle */
+ sts = do_sysctl(mp, CPUSTATES*sizeof(atom->ull));
+ if (sts > 0) {
+ /*
+ * PMID assignment is important in the "-3" below so
+ * that metrics map to consecutive elements of the
+ * returned value in the order defined for CPUSTATES,
+ * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
+ * CP_IDLE
+ */
+ atom->ull = 1000*((__uint64_t *)mp->m_data)[idp->item-3]/cpuhz;
+ sts = 1;
+ }
+ break;
+
+ case 8: /* kernel.percpu.cpu.user */
+ case 9: /* kernel.percpu.cpu.nice */
+ case 10: /* kernel.percpu.cpu.sys */
+ case 11: /* kernel.percpu.cpu.intr */
+ case 12: /* kernel.percpu.cpu.idle */
+ sts = do_sysctl(mp, ncpu*CPUSTATES*sizeof(atom->ull));
+ if (sts > 0) {
+ /*
+ * PMID assignment is important in the "-8" below so
+ * that metrics map to consecutive elements of the
+ * returned value in the order defined for CPUSTATES,
+ * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
+ * CP_IDLE, and then there is one such set for each
+ * CPU up to the maximum number of CPUs installed in
+ * the system.
+ */
+ atom->ull = 1000*((__uint64_t *)mp->m_data)[inst * CPUSTATES + idp->item-8]/cpuhz;
+ sts = 1;
+ }
+ break;
+
+ case 13: /* kernel.all.hz */
+ sts = do_sysctl(mp, sizeof(struct clockinfo));
+ if (sts > 0) {
+ struct clockinfo *cp = (struct clockinfo *)mp->m_data;
+ atom->ul = cp->hz;
+ sts = 1;
+ }
+ break;
+
+ }
+ }
+ else if (idp->cluster == CL_SPECIAL) {
+ /* special cases */
+ switch (idp->item) {
+ case 0: /* hinv.ndisk */
+ refresh_disk_metrics();
+ atom->ul = pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_SIZE_ACTIVE);
+ sts = 1;
+ break;
+
+ case 1: /* swap.free */
+ /* first vm.swap_total */
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull = *((__uint64_t *)mp->m_data);
+ /*
+ * now subtract vm.swap_reserved ... assumes consecutive
+ * mib[] entries
+ */
+ mp++;
+ sts = do_sysctl(mp, sizeof(atom->ull));
+ if (sts > 0) {
+ atom->ull -= *((__uint64_t *)mp->m_data);
+ sts = 1;
+ }
+ }
+ break;
+
+ case 3: /* hinv.pagesize */
+ atom->ul = pagesize;
+ sts = 1;
+ break;
+
+ case 4: /* mem.util.all */
+ case 6: /* mem.util.free */
+ case 8: /* mem.util.cached */
+ case 9: /* mem.util.wired */
+ case 10: /* mem.util.active */
+ case 11: /* mem.util.inactive */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data) * (pagesize / 1024);
+ sts = 1;
+ }
+ break;
+
+ case 7: /* mem.util.bufmem */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data) / 1024;
+ sts = 1;
+ }
+ break;
+
+ case 5: /* mem.util.used */
+ /*
+ * mp-> v_page_count entry in mib[]
+ * assuming consecutive mib[] entries, we want
+ * v_page_count mp[0] - v_free_count mp[1] -
+ * v_cache_count mp[2] - v_inactive_count mp[5]
+ */
+ sts = do_sysctl(mp, sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp->m_data);
+ sts = do_sysctl(&mp[1], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[1].m_data);
+ sts = do_sysctl(&mp[2], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[2].m_data);
+ sts = do_sysctl(&mp[5], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul -= *((__uint32_t *)mp[5].m_data);
+ atom->ul *= (pagesize / 1024);
+ sts = 1;
+ }
+ }
+ }
+ }
+ break;
+
+ case 12: /* mem.util.avail */
+ /*
+ * mp-> v_page_count entry in mib[]
+ * assuming consecutive mib[] entries, we want
+ * v_free_count mp[1] + v_cache_count mp[2] +
+ * v_inactive_count mp[5]
+ */
+ sts = do_sysctl(&mp[1], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul = *((__uint32_t *)mp[1].m_data);
+ sts = do_sysctl(&mp[2], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul += *((__uint32_t *)mp[2].m_data);
+ sts = do_sysctl(&mp[5], sizeof(atom->ul));
+ if (sts > 0) {
+ atom->ul += *((__uint32_t *)mp[5].m_data);
+ atom->ul *= (pagesize / 1024);
+ sts = 1;
+ }
+ }
+ }
+ break;
+
+ }
+ }
+ else if (idp->cluster == CL_DISK) {
+ /* disk metrics */
+ sts = do_disk_metrics(mdesc, inst, atom);
+ }
+ else if (idp->cluster == CL_NETIF) {
+ /* network interface metrics */
+ sts = do_netif_metrics(mdesc, inst, atom);
+ }
+
+ return sts;
+}
+
+/*
+ * wrapper for pmdaFetch ... force value caches to be reloaded if needed,
+ * then do the fetch
+ */
+static int
+netbsd_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i;
+ int done_disk = 0;
+ int done_netif = 0;
+
+ for (i = 0; i < maplen; i++) {
+ map[i].m_fetched = 0;
+ }
+
+ /*
+ * pre-fetch all metrics if needed, and update instance domains if
+ * they have changed
+ */
+ for (i = 0; !done_disk && !done_netif && i < numpmid; i++) {
+ if (pmid_cluster(pmidlist[i]) == CL_DISK) {
+ refresh_disk_metrics();
+ done_disk = 1;
+ }
+ else if (pmid_cluster(pmidlist[i]) == CL_NETIF) {
+ refresh_netif_metrics();
+ done_netif = 1;
+ }
+ }
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * wrapper for pmdaInstance ... refresh required instance domain first
+ */
+static int
+netbsd_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ /*
+ * indomtab[] instance names and ids are not used for some indoms,
+ * ensure pmdaCache is current
+ */
+ if (indom == indomtab[DISK_INDOM].it_indom)
+ refresh_disk_metrics();
+ if (indom == indomtab[NETIF_INDOM].it_indom)
+ refresh_netif_metrics();
+
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+/*
+ * PCP metric name matching for linking metrictab[] entries to mib[]
+ * entries.
+ *
+ * Return 1 if prefix[] is equal to, or a prefix of name[]
+ *
+ * prefix[] of the form "a.bc" or "a.bc*" matches a name[] like "a.bc"
+ * or "a.bcanything", to improve readability of the initializers in
+ * mib[], and asterisk is a "match all" special case, so "a.b.*" matches
+ * "a.b.anything"
+ */
+static int
+matchname(const char *prefix, const char *name)
+{
+ while (*prefix != '\0' && *name != '\0' && *prefix == *name) {
+ prefix++;
+ name++;
+ }
+ if (*prefix == '\0' || *prefix == '*')
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ *
+ * Do mapping from sysclt(3) names to mibs.
+ * Collect some global constants.
+ * Build the system-specific, but not dynamic, instance domains,
+ * e.g. CPU_INDOM.
+ * Initialize the kernel memory reader.
+ */
+void
+netbsd_init(pmdaInterface *dp)
+{
+ int i;
+ int m;
+ int sts;
+ struct clockinfo clockrates;
+ size_t sz;
+ int mib[CTL_MAXNAME]; /* enough for longest mib key */
+ char iname[16]; /* enough for cpuNN.. */
+
+ if (isDSO) {
+ char mypath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "netbsd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "netbsd DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.four.fetch = netbsd_fetch;
+ dp->version.four.instance = netbsd_instance;
+
+ pmdaSetFetchCallBack(dp, netbsd_fetchCallBack);
+
+ pmdaInit(dp, indomtab, indomtablen, metrictab, metrictablen);
+
+ /*
+ * Link metrictab[] entries via m_user to map[] entries based on
+ * matching sysctl(3) name
+ *
+ * also translate the sysctl(3) name to a mib
+ */
+ for (m = 0; m < metrictablen; m++) {
+ if (metrictab[m].m_user == NULL) {
+ /* not using sysctl(3) */
+ continue;
+ }
+ for (i = 0; i < maplen; i++) {
+ if (matchname(map[i].m_pcpname, (char *)metrictab[m].m_user)) {
+ if (map[i].m_mib == NULL) {
+ /*
+ * multiple metrictab[] entries may point to the same
+ * mib[] entry, but this is the first time for this
+ * mib[] entry ...
+ */
+ map[i].m_miblen = sizeof(mib);
+ sts = sysctlnametomib(map[i].m_name, mib, &map[i].m_miblen);
+ if (sts == 0) {
+ map[i].m_mib = (int *)malloc(map[i].m_miblen*sizeof(map[i].m_mib[0]));
+ if (map[i].m_mib == NULL) {
+ fprintf(stderr, "Error: %s (%s): failed mib alloc for sysctl metric \"%s\"\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
+ __pmNoMem("netbsd_init: mib", map[i].m_miblen*sizeof(map[i].m_mib[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ memcpy(map[i].m_mib, mib, map[i].m_miblen*sizeof(map[i].m_mib[0]));
+ }
+ else {
+ fprintf(stderr, "Error: %s (%s): failed sysctlnametomib(\"%s\", ...): %s\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name, pmErrStr(-errno));
+ metrictab[m].m_user = (void *)&bad_mib;
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ int p;
+ fprintf(stderr, "Info: %s (%s): sysctl metric \"%s\" -> ", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
+ for (p = 0; p < map[i].m_miblen; p++) {
+ if (p > 0) fputc('.', stderr);
+ fprintf(stderr, "%d", map[i].m_mib[p]);
+ }
+ fputc('\n', stderr);
+ }
+#endif
+ metrictab[m].m_user = (void *)&map[i];
+ break;
+ }
+ }
+ if (i == maplen) {
+ fprintf(stderr, "Error: %s (%s): cannot match name in sysctl map[]\n", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid));
+ metrictab[m].m_user = (void *)&bad_mib;
+ }
+ }
+
+ /*
+ * Collect some global constants needed later ...
+ */
+ sz = sizeof(clockrates);
+ sts = sysctlbyname("kern.clockrate", &clockrates, &sz, NULL, 0);
+ if (sts < 0) {
+ fprintf(stderr, "Fatal Error: sysctlbyname(\"kern.clockrate\", ...) failed: %s\n", pmErrStr(-errno));
+ exit(1);
+ }
+ cpuhz = clockrates.stathz;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: CPU time \"hz\" = %d\n", cpuhz);
+#endif
+
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ sz = sizeof(ncpu);
+ sts = sysctl(mib, 2, &ncpu, &sz, NULL, 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: ncpu = %d\n", ncpu);
+#endif
+
+ sz = sizeof(pagesize);
+ sts = sysctlbyname("hw.pagesize", &pagesize, &sz, NULL, 0);
+ if (sts < 0) {
+ fprintf(stderr, "Fatal Error: sysctlbyname(\"hw.pagesize\", ...) failed: %s\n", pmErrStr(-errno));
+ exit(1);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Info: VM pagesize = %d\n", pagesize);
+#endif
+
+ /*
+ * Build some instance domains ...
+ */
+ indomtab[CPU_INDOM].it_numinst = ncpu;
+ indomtab[CPU_INDOM].it_set = (pmdaInstid *)malloc(ncpu * sizeof(pmdaInstid));
+ if (indomtab[CPU_INDOM].it_set == NULL) {
+ __pmNoMem("netbsd_init: CPU_INDOM it_set", ncpu * sizeof(pmdaInstid), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (i = 0; i < ncpu; i++) {
+ indomtab[CPU_INDOM].it_set[i].i_inst = i;
+ snprintf(iname, sizeof(iname), "cpu%d", i);
+ indomtab[CPU_INDOM].it_set[i].i_name = strdup(iname);
+ if (indomtab[CPU_INDOM].it_set[i].i_name == NULL) {
+ __pmNoMem("netbsd_init: CPU_INDOM strdup iname", strlen(iname), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ }
+
+ kmemread_init();
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -U username user account to run under (default \"pcp\")\n"
+ "\nExactly one of the following options may appear:\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ stderr);
+ exit(1);
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int c, err = 0;
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char mypath[MAXPATHLEN];
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "netbsd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmProgname, NETBSD,
+ "netbsd.log", mypath);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:i:l:pu:U:6:?", &dispatch, &err)) != EOF) {
+ switch(c) {
+ case 'U':
+ username = optarg;
+ break;
+ default:
+ err++;
+ }
+ }
+ if (err)
+ usage();
+
+ pmdaOpenLog(&dispatch);
+ netbsd_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/netbsd/netbsd.h b/src/pmdas/netbsd/netbsd.h
new file mode 100644
index 0000000..c6b9b72
--- /dev/null
+++ b/src/pmdas/netbsd/netbsd.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright (c) 2012 Ken McDonell All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * instance domains control
+ */
+#define LOADAV_INDOM 0
+#define CPU_INDOM 1
+#define DISK_INDOM 2
+#define NETIF_INDOM 3
+extern pmdaIndom indomtab[];
+
+extern void refresh_disk_metrics(void);
+extern int do_disk_metrics(pmdaMetric *, unsigned int, pmAtomValue *);
+
+extern void refresh_netif_metrics(void);
+extern int do_netif_metrics(pmdaMetric *, unsigned int, pmAtomValue *);
+
+/*
+ * kernel memory reader pieces
+ */
+#include <kvm.h>
+#include <nlist.h>
+
+extern kvm_t *kvmp;
+
+#define KERN_IFNET 0
+extern struct nlist symbols[];
diff --git a/src/pmdas/netbsd/netif.c b/src/pmdas/netbsd/netif.c
new file mode 100644
index 0000000..19737bb
--- /dev/null
+++ b/src/pmdas/netbsd/netif.c
@@ -0,0 +1,233 @@
+/*
+ * NetBSD Kernel PMDA - network interface metrics
+ *
+ * Copyright (c) 2012,2013 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#if 0
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#endif
+
+#include "netbsd.h"
+
+#define WARN_INIT 1
+#define WARN_READ_HEAD 2
+
+void
+refresh_netif_metrics(void)
+{
+#if 0
+ int i;
+ int sts;
+ unsigned long kaddr;
+ struct ifnethead ifnethead;
+ struct ifnet ifnet;
+ struct ifnet *ifp;
+ static int warn = 0; /* warn once control */
+
+ /*
+ * Not sure that the order of chained netif structs is invariant,
+ * especially if interfaces are added to the configuration after
+ * initial system boot ... so mark all the instances as inactive
+ * and re-match based on the interface name
+ */
+ pmdaCacheOp(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+
+ kaddr = symbols[KERN_IFNET].n_value;
+ if (kvmp == NULL || kaddr == 0) {
+ /* no network interface metrics for us today ... */
+ if ((warn & WARN_INIT) == 0) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: cannot get any network interface metrics\n");
+ warn |= WARN_INIT;
+ }
+ return;
+ }
+
+ /*
+ * Kernel data structures for the linked list of network interface
+ * information.
+ *
+ * _ifnet -> struct ifnethead {
+ * struct ifnet *tqh_first;
+ * struct ifnet **tqh_last;
+ * ...
+ * }
+ *
+ * and within an ifnet struct (declared in <net/if_var.h>) we find
+ * the linked list maintained in if_link, the external interface
+ * name in if_xname[] and if_data which is a nested if_data stuct
+ * (declared in <net/if.h>) that contains many of the goodies we're
+ * after, e.g. u_char ifi_type, u_long ifi_mtu, u_long ifi_baudrate,
+ * u_long ifi_ipackets, u_long ifi_opackets, u_long ifi_ibytes,
+ * u_long ifi_obytes, etc.
+ */
+ if (kvm_read(kvmp, kaddr, (char *)&ifnethead, sizeof(ifnethead)) != sizeof(ifnethead)) {
+ if ((warn & WARN_READ_HEAD) == 0) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: kvm_read: ifnethead: %s\n", kvm_geterr(kvmp));
+ warn |= WARN_READ_HEAD;
+ }
+ return;
+ }
+
+ for (i = 0; ; i++) {
+ if (i == 0)
+ kaddr = (unsigned long)TAILQ_FIRST(&ifnethead);
+ else
+ kaddr = (unsigned long)TAILQ_NEXT(&ifnet, if_link);
+
+ if (kaddr == 0)
+ break;
+
+ if (kvm_read(kvmp, kaddr, (char *)&ifnet, sizeof(ifnet)) != sizeof(ifnet)) {
+ fprintf(stderr, "refresh_netif_metrics: Error: kvm_read: ifnet[%d]: %s\n", i, kvm_geterr(kvmp));
+ return;
+ }
+
+ /* skip network interfaces that are not interesting ... */
+ if (strcmp(ifnet.if_xname, "lo0") == 0)
+ continue;
+
+ sts = pmdaCacheLookupName(indomtab[NETIF_INDOM].it_indom, ifnet.if_xname, NULL, (void **)&ifp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ fprintf(stderr, "refresh_netif_metrics: Warning: duplicate name (%s) in network interface indom\n", ifnet.if_xname);
+ continue;
+ }
+ else if (sts == PMDA_CACHE_INACTIVE) {
+ /* reactivate an existing entry */
+ pmdaCacheStore(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_ADD, ifnet.if_xname, (void *)ifp);
+ }
+ else {
+ /* new entry */
+ ifp = (struct ifnet *)malloc(sizeof(*ifp));
+ if (ifp == NULL) {
+ fprintf(stderr, "Error: struct ifnet alloc failed for network interface \"%s\"\n", ifnet.if_xname);
+ __pmNoMem("refresh_netif_metrics", sizeof(*ifp), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ pmdaCacheStore(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_ADD, ifnet.if_xname, (void *)ifp);
+ }
+ memcpy((void *)ifp, (void *)&ifnet, sizeof(*ifp));
+ }
+#endif
+}
+
+int
+do_netif_metrics(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+#if 0
+ struct ifnet *ifp;
+ int sts;
+
+ if (inst != PM_IN_NULL) {
+ /*
+ * per-network interface metrics
+ */
+ sts = pmdaCacheLookup(indomtab[NETIF_INDOM].it_indom, inst, NULL, (void **)&ifp);
+ if (sts == PMDA_CACHE_ACTIVE) {
+ sts = 1;
+ /* cluster and domain already checked, just need item ... */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 0: /* network.interface.mtu */
+ atom->ul = (__uint32_t)ifp->if_data.ifi_mtu;
+ break;
+
+ case 1: /* network.interface.up */
+ atom->ul = (ifp->if_flags & IFF_UP) == IFF_UP;
+ break;
+
+ case 2: /* network.interface.baudrate */
+ atom->ull = ifp->if_data.ifi_baudrate;
+ break;
+
+ case 3: /* network.interface.in.bytes */
+ atom->ull = ifp->if_data.ifi_ibytes;
+ break;
+
+ case 4: /* network.interface.in.packets */
+ atom->ull = ifp->if_data.ifi_ipackets;
+ break;
+
+ case 5: /* network.interface.in.mcasts */
+ atom->ull = ifp->if_data.ifi_imcasts;
+ break;
+
+ case 6: /* network.interface.in.errors */
+ atom->ull = ifp->if_data.ifi_ierrors;
+ break;
+
+ case 7: /* network.interface.in.drops */
+ atom->ull = ifp->if_data.ifi_iqdrops;
+ break;
+
+ case 8: /* network.interface.out.bytes */
+ atom->ull = ifp->if_data.ifi_obytes;
+ break;
+
+ case 9: /* network.interface.out.packets */
+ atom->ull = ifp->if_data.ifi_opackets;
+ break;
+
+ case 10: /* network.interface.out.mcasts */
+ atom->ull = ifp->if_data.ifi_omcasts;
+ break;
+
+ case 11: /* network.interface.out.errors */
+ atom->ull = ifp->if_data.ifi_oerrors;
+ break;
+
+ case 12: /* network.interface.out.collisions */
+ atom->ull = ifp->if_data.ifi_collisions;
+ break;
+
+ case 13: /* network.interface.total.bytes */
+ atom->ull = ifp->if_data.ifi_ibytes + ifp->if_data.ifi_obytes;
+ break;
+
+ case 14: /* network.interface.total.packets */
+ atom->ull = ifp->if_data.ifi_ipackets + ifp->if_data.ifi_opackets;
+ break;
+
+ case 15: /* network.interface.total.mcasts */
+ atom->ull = ifp->if_data.ifi_imcasts + ifp->if_data.ifi_omcasts;
+ break;
+
+ case 16: /* network.interface.total.errors */
+ atom->ull = ifp->if_data.ifi_ierrors + ifp->if_data.ifi_oerrors;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else
+ sts = 0;
+ }
+
+ return sts;
+#else
+ return PM_ERR_PMID;
+#endif
+}
diff --git a/src/pmdas/netbsd/root_netbsd b/src/pmdas/netbsd/root_netbsd
new file mode 100644
index 0000000..c05ce4a
--- /dev/null
+++ b/src/pmdas/netbsd/root_netbsd
@@ -0,0 +1,172 @@
+/*
+ * Metrics for NetBSD kernel PMDA
+ *
+ * Copyright (c) 2012 Ken McDonell All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * the domain for the NETBSD PMDA ...
+ */
+#ifndef NETBSD
+#define NETBSD 116
+#endif
+
+root {
+ hinv
+ kernel
+ disk
+ mem
+ network
+ swap
+}
+
+hinv {
+ ncpu NETBSD:0:0
+ ndisk NETBSD:1:0
+ physmem NETBSD:0:1
+ pagesize NETBSD:1:3
+ cpu
+}
+
+hinv.cpu {
+ vendor NETBSD:0:15
+ model NETBSD:0:16
+ arch NETBSD:0:17
+}
+
+kernel {
+ all
+ percpu
+}
+
+kernel.all {
+ pswitch NETBSD:0:22
+ syscall NETBSD:0:23
+ intr NETBSD:0:24
+ hz NETBSD:0:13
+ load NETBSD:0:2
+ cpu
+}
+
+kernel.all.cpu {
+ user NETBSD:0:3
+ nice NETBSD:0:4
+ sys NETBSD:0:5
+ intr NETBSD:0:6
+ idle NETBSD:0:7
+}
+
+kernel.percpu {
+ cpu
+}
+
+kernel.percpu.cpu {
+ user NETBSD:0:8
+ nice NETBSD:0:9
+ sys NETBSD:0:10
+ intr NETBSD:0:11
+ idle NETBSD:0:12
+}
+
+disk {
+ dev
+ all
+}
+
+disk.dev {
+ read NETBSD:2:0
+ write NETBSD:2:1
+ total NETBSD:2:2
+ read_bytes NETBSD:2:3
+ write_bytes NETBSD:2:4
+ total_bytes NETBSD:2:5
+ blkread NETBSD:2:12
+ blkwrite NETBSD:2:13
+ blktotal NETBSD:2:14
+}
+
+disk.all {
+ read NETBSD:2:6
+ write NETBSD:2:7
+ total NETBSD:2:8
+ read_bytes NETBSD:2:9
+ write_bytes NETBSD:2:10
+ total_bytes NETBSD:2:11
+ blkread NETBSD:2:15
+ blkwrite NETBSD:2:16
+ blktotal NETBSD:2:17
+}
+
+mem {
+ util
+}
+
+mem.util {
+ all NETBSD:1:4
+ used NETBSD:1:5
+ free NETBSD:1:6
+ bufmem NETBSD:1:7
+ cached NETBSD:1:8
+ wired NETBSD:1:9
+ active NETBSD:1:10
+ inactive NETBSD:1:11
+ avail NETBSD:1:12
+}
+
+network {
+ interface
+}
+
+network.interface {
+ mtu NETBSD:3:0
+ up NETBSD:3:1
+ baudrate NETBSD:3:2
+ in
+ out
+ total
+}
+
+network.interface.in {
+ bytes NETBSD:3:3
+ packets NETBSD:3:4
+ mcasts NETBSD:3:5
+ errors NETBSD:3:6
+ drops NETBSD:3:7
+}
+
+network.interface.out {
+ bytes NETBSD:3:8
+ packets NETBSD:3:9
+ mcasts NETBSD:3:10
+ errors NETBSD:3:11
+ collisions NETBSD:3:12
+}
+
+network.interface.total {
+ bytes NETBSD:3:13
+ packets NETBSD:3:14
+ mcasts NETBSD:3:15
+ errors NETBSD:3:16
+}
+
+swap {
+ pagesin NETBSD:0:18
+ pagesout NETBSD:0:19
+ in NETBSD:0:20
+ out NETBSD:0:21
+ length NETBSD:0:25
+ used NETBSD:0:26
+ free NETBSD:1:1
+}
+
+#undef NETBSD
diff --git a/src/pmdas/netfilter/GNUmakefile b/src/pmdas/netfilter/GNUmakefile
new file mode 100644
index 0000000..53e9051
--- /dev/null
+++ b/src/pmdas/netfilter/GNUmakefile
@@ -0,0 +1,55 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = netfilter
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl pmlogconf.config pmlogconf.summary
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "linux"
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+ $(INSTALL) -m 644 pmlogconf.config $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/config
+else
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/netfilter/Install b/src/pmdas/netfilter/Install
new file mode 100755
index 0000000..621f639
--- /dev/null
+++ b/src/pmdas/netfilter/Install
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the NetFilter PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=netfilter
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+if ! test -d /proc/sys/net/ipv4/netfilter; then
+ echo "IP connection tracking not enabled in your kernel"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/netfilter/Remove b/src/pmdas/netfilter/Remove
new file mode 100755
index 0000000..77f002c
--- /dev/null
+++ b/src/pmdas/netfilter/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the NetFilter PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=netfilter
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/netfilter/pmdanetfilter.pl b/src/pmdas/netfilter/pmdanetfilter.pl
new file mode 100644
index 0000000..21aafa0
--- /dev/null
+++ b/src/pmdas/netfilter/pmdanetfilter.pl
@@ -0,0 +1,100 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my $pmda = PCP::PMDA->new('netfilter', 97);
+my $procfs = '/proc/sys/net/ipv4/';
+
+sub netfilter_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ($path, $name, $value, $fh, @vals);
+
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+
+ $metric_name =~ s/\./\//;
+ $name = $procfs . $metric_name;
+ open($fh, $name) || return (PM_ERR_APPVERSION, 0);
+ $value = <$fh>;
+ close $fh;
+ chomp $value;
+
+ return ($value, 1);
+}
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'netfilter.ip_conntrack_max', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'netfilter.ip_conntrack_count', '', '');
+
+$pmda->set_fetch_callback(\&netfilter_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdanetfilter - Linux netfilter IP connection tracking performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdanetfilter> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from IP connection tracking module in the Linux
+kernel.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the netfilter performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/netfilter
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/netfilter
+ # ./Remove
+
+B<pmdanetfilter> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/netfilter/Install
+
+installation script for the B<pmdanetfilter> agent
+
+=item $PCP_PMDAS_DIR/netfilter/Remove
+
+undo installation script for the B<pmdanetfilter> agent
+
+=item $PCP_LOG_DIR/pmcd/netfilter.log
+
+default log file for error messages from B<pmdanetfilter>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/netfilter/pmlogconf.config b/src/pmdas/netfilter/pmlogconf.config
new file mode 100644
index 0000000..d699905
--- /dev/null
+++ b/src/pmdas/netfilter/pmlogconf.config
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident Netfilter configuration
+probe netfilter.ip_conntrack_max exists ? include : exclude
+delta once
+ netfilter.ip_conntrack_max
diff --git a/src/pmdas/netfilter/pmlogconf.summary b/src/pmdas/netfilter/pmlogconf.summary
new file mode 100644
index 0000000..91669c4
--- /dev/null
+++ b/src/pmdas/netfilter/pmlogconf.summary
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident Netfilter summary information
+probe netfilter.ip_conntrack_count exists ? include : exclude
+ netfilter.ip_conntrack_count
diff --git a/src/pmdas/news/GNUmakefile b/src/pmdas/news/GNUmakefile
new file mode 100644
index 0000000..cd58095
--- /dev/null
+++ b/src/pmdas/news/GNUmakefile
@@ -0,0 +1,53 @@
+#!gmake
+#
+# Copyright (c) 2000-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = news
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove README active pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ $(INSTALL) -m 644 README active $(PMDADIR)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/news/Install b/src/pmdas/news/Install
new file mode 100644
index 0000000..79032ea
--- /dev/null
+++ b/src/pmdas/news/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the news PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=news
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/news/README b/src/pmdas/news/README
new file mode 100644
index 0000000..6f8466b
--- /dev/null
+++ b/src/pmdas/news/README
@@ -0,0 +1,58 @@
+Usenet News PMDA
+================
+
+This PMDA is a sample, that illustrates how a simple PMDA might be
+constructed using the Perl PMDA API.
+
+Although the metrics supported are simple, the framework in the script
+pmdanews is quite general, and could be re-used to implement other
+PMDAs.
+
+Metrics
+=======
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT news
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/news
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ + By default the script pmdanews operates on a small test-case copy a
+ news "active" file, installed from here into
+ $PCP_PMDAS_DIR/news/active. If you wish to use a more realistic base
+ of Usenet data, edit pmdanews.pl to make it use your "live active"
+ groups, then
+
+ # ./Install.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/news
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/news.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/news/Remove b/src/pmdas/news/Remove
new file mode 100644
index 0000000..a14cbc2
--- /dev/null
+++ b/src/pmdas/news/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the news PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=news
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/news/active b/src/pmdas/news/active
new file mode 100644
index 0000000..30ce528
--- /dev/null
+++ b/src/pmdas/news/active
@@ -0,0 +1,12 @@
+# fake for testing
+# the metrics should be as follows
+# news.articles.total 1000
+# news.articles.last 100 0 0 200 1101 3795 7130
+# news.articles.count 0 0 0 100 100 700 100
+comp.sys.sgi 0100 0100 y
+comp.sys.sgi.graphics 0000 0000 y
+comp.sys.sgi.hardware 0000 0000 y
+sgi.bad-attitude 3795 3095 y
+sgi.bugs.sherwood 1101 1001 y
+sgi.engr.all 0200 0100 y
+sgi.general 7130 7030 y
diff --git a/src/pmdas/news/pmdanews.pl b/src/pmdas/news/pmdanews.pl
new file mode 100644
index 0000000..9d59178
--- /dev/null
+++ b/src/pmdas/news/pmdanews.pl
@@ -0,0 +1,196 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my @newsgroups = (
+ 1, 'comp.sys.sgi',
+ 2, 'comp.sys.sgi.graphics',
+ 3, 'comp.sys.sgi.hardware',
+ 4, 'sgi.bad-attitude',
+ 5, 'sgi.engr.all',
+);
+
+use vars qw( $total $news_regex %news_hash @news_count @news_last );
+my ($nnrpd_count, $rn_count, $trn_count, $xrn_count, $vn_count) = (0,0,0,0,0);
+my $news_file = pmda_config('PCP_PMDAS_DIR') . '/news/active';
+my $news_indom = 0;
+my $pmda;
+
+sub news_fetch # called once per ``fetch'' pdu, before callbacks
+{
+ my ( $group, $cmd );
+
+ $total = 0;
+ seek ACTIVE,0,0;
+ while (<ACTIVE>) {
+ next unless /$news_regex/;
+ $group = $news_hash{$1} - 1;
+ $news_last[$group] = $2;
+ $total += $news_count[$group] = $2 - $3;
+ }
+
+ ($nnrpd_count, $rn_count, $trn_count, $xrn_count, $vn_count) = (0,0,0,0,0);
+ if (open(READERS, 'ps -ef |')) {
+ while (<READERS>) {
+ s/\b(:?\d\d:){2}\d\d\b/ Mmm DD /; # replace times with dates
+ s/\s*?(\S+?\s+?){8}//; # nuke the first eight fields
+ next unless /(\S+)\s*?/; # prepare $cmd with command name
+ $cmd = $1;
+ ($cmd eq 'in.nnrpd' || $cmd =~ /\/in\.nnrpd$/) && $nnrpd_count++;
+ ($cmd eq 'rn' || $cmd =~ /\/rn$/) && $rn_count++;
+ ($cmd eq 'trn' || $cmd =~ /\/trn$/) && $trn_count++;
+ ($cmd eq 'xrn' || $cmd =~ /\/xrn$/) && $xrn_count++;
+ ($cmd eq 'vn' || $cmd =~ /\/vrn$/) && $vn_count++;
+ }
+ close READERS;
+ }
+ else {
+ $pmda->log("Cannot execute 'ps' to count news reader processes");
+ }
+}
+
+sub news_fetch_callback # must return array of value,status
+{
+ my ($cluster, $item, $inst) = @_;
+
+ return (PM_ERR_INST, 0) unless ( $inst == PM_IN_NULL ||
+ ( $cluster == 0 && ($item == 301 || $item == 302) &&
+ $inst > 0 && $inst <= ($#newsgroups+1)/2 ) );
+ if ($cluster == 0) {
+ if ($item == 201) { ($total, 1); } # articles.total
+ elsif ($item == 301) { ($news_count[$inst-1], 1); } # articles.count
+ elsif ($item == 302) { ($news_last[$inst-1], 1); } # articles.last
+ elsif ($item == 101) { ($nnrpd_count, 1); } # readers.nnrpd
+ elsif ($item == 111) { ($rn_count, 1); } # readers.rn
+ elsif ($item == 112) { ($trn_count, 1); } # readers.trn
+ elsif ($item == 113) { ($xrn_count, 1); } # readers.xrn
+ elsif ($item == 114) { ($vn_count, 1); } # readers.vn
+ else { (PM_ERR_PMID, 0); }
+ }
+ else { (PM_ERR_PMID, 0); }
+}
+
+sub news_init
+{
+ ($#newsgroups > 0 && $#newsgroups % 2 != 0)
+ || die "Invalid newsgroups array has been specified\n";
+ open(ACTIVE, $news_file) || die "Can't open $news_file: $!\n";
+
+ # build regex using the given newsgroup names
+ $_ = "^($newsgroups[1]";
+ $news_hash{$newsgroups[1]} = $newsgroups[0];
+ for (my $i = 2; $i < $#newsgroups; $i += 2) {
+ $_ .= "|$newsgroups[$i+1]";
+ $news_hash{$newsgroups[$i+1]} = $newsgroups[$i];
+ }
+ $news_regex = $_ . ") (\\d+) (\\d+) y\$";
+}
+
+$pmda = PCP::PMDA->new('news', 28);
+
+$pmda->add_metric(pmda_pmid(0,201), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.articles.total',
+ 'Total number of articles received for each newsgroup',
+'Total number of articles received for each newsgroup.
+Note this is the historical running total, see news.articles.count for
+the current total of un-expired articles by newsgroup.');
+
+$pmda->add_metric(pmda_pmid(0,301), PM_TYPE_U32, $news_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.articles.count',
+ 'Total number of un-expired articles in each newsgroup', '');
+
+$pmda->add_metric(pmda_pmid(0,302), PM_TYPE_U32, $news_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.articles.last', '', '');
+$pmda->add_metric(pmda_pmid(0,101), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.readers.nnrpd', '', '');
+$pmda->add_metric(pmda_pmid(0,111), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.readers.rn', '', '');
+$pmda->add_metric(pmda_pmid(0,112), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.readers.trn', '', '');
+$pmda->add_metric(pmda_pmid(0,113), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.readers.xrn', '', '');
+$pmda->add_metric(pmda_pmid(0,114), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'news.readers.vn', '', '');
+
+$pmda->add_indom($news_indom, \@newsgroups, '', '');
+
+$pmda->set_fetch(\&news_fetch);
+$pmda->set_fetch_callback(\&news_fetch_callback);
+
+&news_init;
+
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdanews - sample Usenet news performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdanews> is an example Performance Metrics Domain Agent (PMDA) which
+exports metric values related to a set of newsgroups.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the news performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/news
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/news
+ # ./Remove
+
+B<pmdanews> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/news/Install
+
+installation script for the B<pmdanews> agent
+
+=item $PCP_PMDAS_DIR/news/Remove
+
+undo installation script for the B<pmdanews> agent
+
+=item $PCP_LOG_DIR/pmcd/news.log
+
+default log file for error messages from B<pmdanews>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/nfsclient/GNUmakefile b/src/pmdas/nfsclient/GNUmakefile
new file mode 100644
index 0000000..cb149f9
--- /dev/null
+++ b/src/pmdas/nfsclient/GNUmakefile
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2011 SGI.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = nfsclient
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain : ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/nfsclient/Install b/src/pmdas/nfsclient/Install
new file mode 100755
index 0000000..3e3677d
--- /dev/null
+++ b/src/pmdas/nfsclient/Install
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 SGI.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=nfsclient
+pmda_interface=2
+dso_opt=false
+daemon_opt=false
+perl_opt=true
+socket_opt=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/nfsclient/Remove b/src/pmdas/nfsclient/Remove
new file mode 100755
index 0000000..4a26c90
--- /dev/null
+++ b/src/pmdas/nfsclient/Remove
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 SGI.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=nfsclient
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/nfsclient/pmdanfsclient.pl b/src/pmdas/nfsclient/pmdanfsclient.pl
new file mode 100644
index 0000000..380b1cd
--- /dev/null
+++ b/src/pmdas/nfsclient/pmdanfsclient.pl
@@ -0,0 +1,1181 @@
+#
+# Copyright (c) 2011 SGI.
+# All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda @all_ops @common_ops @v3only_ops @v4_ops @v41_ops);
+
+# The instance domain is 0 for all nfs client stats
+our $nfsclient_indom = 0;
+
+# mountstats or other file to use
+our $MOUNTSTATS_PATH = "/proc/self/mountstats";
+
+# Configuration files for overriding the location of mountstats, mostly for testing purposes
+for my $file (pmda_config('PCP_PMDAS_DIR') . '/nfsclient/nfsclient.conf', 'nfsclient.conf') {
+ eval `cat $file` unless ! -f $file;
+}
+
+# Check env variable for mountstats file to use
+if ( defined $ENV{"NFSCLIENT_MOUNTSTATS_PATH"} ) {
+ $MOUNTSTATS_PATH = $ENV{"NFSCLIENT_MOUNTSTATS_PATH"}
+}
+
+our $noval = PM_ERR_APPVERSION;
+
+# The stats hash is keyed on the 'mount' string (the mountpoint string), e.g.
+# '/home'. The value is a hash ref, and that hash contains all of
+# the stats data keyed on pmid_name
+our %h = ();
+
+#
+# Parse /proc/self/mountstats and store the stats, taking one pass through
+# the file.
+#
+sub nfsclient_parse_proc_mountstats {
+ # mountstats output has a section for each mounted filesystems on the
+ # system. Each section starts with a line like: 'device X mounted on
+ # Y with fstype Z'. Some filesystems (like nfs) print additional
+ # output that we need to capture.
+ open STATS, '<', $MOUNTSTATS_PATH ||
+ ( $pmda->err("pmdanfsclient failed to open $MOUNTSTATS_PATH: $!") &&
+ die "Can't open $MOUNTSTATS_PATH: $!\n") ;
+
+ my $export;
+ my $nfsinst;
+ while (<STATS>) {
+ my $line = $_;
+
+ # does this line represent a mount?
+ if ($line =~ /^device (\S*) mounted on (\S*) with fstype.*/) {
+ $export = $1;
+ my $mtpt = $2;
+ $nfsinst = $mtpt;
+
+ # is it an nfs mount?
+ if ($line =~ /.*nfs(4)? statvers=.*/) {
+ $h{$nfsinst} = {}; # {} is an anonymous hash ref
+ $h{$nfsinst}->{'nfsclient.export'} = $export;
+ $h{$nfsinst}->{'nfsclient.mountpoint'} = $mtpt;
+ } else {
+ # the following lines aren't nfs so undef
+ # $nfsinst so that they are ignored below
+ undef $nfsinst;
+ next;
+ }
+ }
+
+ # skip lines that do not belong to nfs mounts
+ if (not defined $nfsinst) {
+ next;
+ }
+
+ # opts
+ if ($line =~ /^\topts:\t(.*)$/) {
+
+ # Need to differentiate between options that are not available based on nfs vers, vs options that are not set and therefore default values ?
+ # Prepopulate based on this constraint
+ # Try to stay away from the "no" names
+ # Guess reasonable defaults based on mapping below
+ #
+ $h{$nfsinst}->{'nfsclient.options.readmode'} = "rw";
+ $h{$nfsinst}->{'nfsclient.options.sync'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.atime'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.diratime'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.vers'} = "3";
+ $h{$nfsinst}->{'nfsclient.options.rsize'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.wsize'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.bsize'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.namlen'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.acregmin'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.acregmax'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.acdirmin'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.acdirmax'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.recovery'} = "hard";
+ $h{$nfsinst}->{'nfsclient.options.posix'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.cto'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.ac'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.lock'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.acl'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.rdirplus'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.sharecache'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.resvport'} = 1;
+ $h{$nfsinst}->{'nfsclient.options.proto'} = "tcp";
+ $h{$nfsinst}->{'nfsclient.options.port'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.timeo'} = 600;
+ $h{$nfsinst}->{'nfsclient.options.retrans'} = 3;
+ $h{$nfsinst}->{'nfsclient.options.sec'} = "sys";
+
+ # NFS 3 only options but can be NULL also
+ $h{$nfsinst}->{'nfsclient.options.mountaddr'} = "unspecified";
+ $h{$nfsinst}->{'nfsclient.options.mountvers'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.mountport'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.mountproto'} = "";
+
+ # NFS 4 only
+ $h{$nfsinst}->{'nfsclient.options.clientaddr'} = "";
+
+ # All
+ $h{$nfsinst}->{'nfsclient.options.fsc'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.migration'} = 0;
+ $h{$nfsinst}->{'nfsclient.options.lookupcache'} = "";
+ $h{$nfsinst}->{'nfsclient.options.local_lock'} = "none";
+
+ $h{$nfsinst}->{'nfsclient.options.string'} = $1; # Keep this???
+
+ my @mountopts = split(',', $1);
+
+ foreach my $mountopt (@mountopts) {
+ chomp $mountopt;
+ # Is there a better way to do this ??
+ # Started with a regex for the full line but got ugly
+ #
+ if( $mountopt =~ /^(ro|rw)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.readmode'} = "$1";
+ } elsif ( $mountopt =~ /^(sync)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.sync'} = 1;
+ } elsif ( $mountopt =~ /^(noatime)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.atime'} = 0;
+ } elsif ( $mountopt =~ /^(nodirtime)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.diratime'} = 0;
+ } elsif ( $mountopt =~ /^vers=([2-4](\.[0-9])?)/ ){
+ $h{$nfsinst}->{'nfsclient.options.vers'} = "$1";
+ } elsif ( $mountopt =~ /^rsize=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.rsize'} = $1;
+ } elsif ( $mountopt =~ /^wsize=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.wsize'} = $1;
+ } elsif ( $mountopt =~ /^bsize=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.bsize'} = $1;
+ } elsif ( $mountopt =~ /^namlen=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.namlen'} = $1;
+ } elsif ( $mountopt =~ /^acregmin=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.acregmin'} = $1;
+ } elsif ( $mountopt =~ /^acregmax=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.acregmax'} = $1;
+ } elsif ( $mountopt =~ /^acdirmin=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.acdirmin'} = $1;
+ } elsif ( $mountopt =~ /^acdirmax=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.acdirmax'} = $1;
+ } elsif ( $mountopt =~ /^(soft|hard)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.recovery'} = "$1";
+ } elsif ( $mountopt =~ /^(posix)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.posix'} = 1;
+ } elsif ( $mountopt =~ /^(nocto)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.cto'} = 0;
+ } elsif ( $mountopt =~ /^(noac)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.ac'} = 0;
+ } elsif ( $mountopt =~ /^(nolock)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.lock'} = 0;
+ } elsif ( $mountopt =~ /^(noacl)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.acl'} = 0;
+ } elsif ( $mountopt =~ /^(nordirplus)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.rdirplus'} = 0;
+ } elsif ( $mountopt =~ /^(nosharecache)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.sharecache'} = 0;
+ } elsif ( $mountopt =~ /^(noresvport)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.resvport'} = 0;
+ } elsif ( $mountopt =~ /^proto=(tcp|udp|rdma)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.proto'} = "$1";
+ } elsif ( $mountopt =~ /^port=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.port'} = $1;
+ } elsif ( $mountopt =~ /^timeo=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.timeo'} = $1;
+ } elsif ( $mountopt =~ /^retrans=(\d+)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.retrans'} = $1;
+ } elsif ( $mountopt =~ /^sec=(null|sys|krb5|krb5i|krb5p|lkey|lkeyi|lkeyp|spkm|spkmi|spkmp|unknown)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.sec'} = "$1";
+ } elsif ( $mountopt =~ /^mountaddr=(.*)$/ ){ # NFS3 only
+ $h{$nfsinst}->{'nfsclient.options.mountaddr'} = "$1";
+ } elsif ( $mountopt =~ /^mountvers=(\d+)$/ ){ # NFS3 only
+ $h{$nfsinst}->{'nfsclient.options.mountvers'} = $1;
+ } elsif ( $mountopt =~ /^mountport=(\d+)$/ ){ # NFS3 only
+ $h{$nfsinst}->{'nfsclient.options.mountport'} = $1;
+ } elsif ( $mountopt =~ /^mountproto=(udp|tcp|auto|udp6|tcp6)$/ ){ # NFS3 only
+ $h{$nfsinst}->{'nfsclient.options.mountproto'} = "$1"
+ } elsif ( $mountopt =~ /^clientaddr=(.*)$/ ){ # NFS4 only
+ $h{$nfsinst}->{'nfsclient.options.clientaddr'} = "$1";
+ } elsif ( $mountopt =~ /^(fsc)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.fsc'} = 1;
+ } elsif ( $mountopt =~ /^(migration)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.migration'} = 1;
+ } elsif ( $mountopt =~ /^lookupcache=(none|pos)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.lookupcache'} = "$1";
+ } elsif ( $mountopt =~ /^local_lock=(none|all|flock|posix)$/ ){
+ $h{$nfsinst}->{'nfsclient.options.local_lock'} = "$1";
+ }
+ }
+ }
+ #age
+ if ($line =~ /^\tage:\t(.*)$/) {
+ $h{$nfsinst}->{'nfsclient.age'} = $1;
+ }
+ #caps
+ if ($line =~ /^\tcaps:\t(.*)$/) {
+ $h{$nfsinst}->{'nfsclient.capabilities'} = $1;
+ }
+ #sec
+ if ($line =~ /^\tsec:\t(.*)$/) {
+ $h{$nfsinst}->{'nfsclient.security'} = $1;
+ }
+ # events
+ if ($line =~ /^\tevents:\t(.*)$/) {
+ ($h{$nfsinst}->{'nfsclient.events.inoderevalidate'},
+ $h{$nfsinst}->{'nfsclient.events.dentryrevalidate'},
+ $h{$nfsinst}->{'nfsclient.events.datainvalidate'},
+ $h{$nfsinst}->{'nfsclient.events.attrinvalidate'},
+ $h{$nfsinst}->{'nfsclient.events.vfsopen'},
+ $h{$nfsinst}->{'nfsclient.events.vfslookup'},
+ $h{$nfsinst}->{'nfsclient.events.vfsaccess'},
+ $h{$nfsinst}->{'nfsclient.events.vfsupdatepage'},
+ $h{$nfsinst}->{'nfsclient.events.vfsreadpage'},
+ $h{$nfsinst}->{'nfsclient.events.vfsreadpages'},
+ $h{$nfsinst}->{'nfsclient.events.vfswritepage'},
+ $h{$nfsinst}->{'nfsclient.events.vfswritepages'},
+ $h{$nfsinst}->{'nfsclient.events.vfsgetdents'},
+ $h{$nfsinst}->{'nfsclient.events.vfssetattr'},
+ $h{$nfsinst}->{'nfsclient.events.vfsflush'},
+ $h{$nfsinst}->{'nfsclient.events.vfsfsync'},
+ $h{$nfsinst}->{'nfsclient.events.vfslock'},
+ $h{$nfsinst}->{'nfsclient.events.vfsrelease'},
+ $h{$nfsinst}->{'nfsclient.events.congestionwait'},
+ $h{$nfsinst}->{'nfsclient.events.setattrtrunc'},
+ $h{$nfsinst}->{'nfsclient.events.extendwrite'},
+ $h{$nfsinst}->{'nfsclient.events.sillyrename'},
+ $h{$nfsinst}->{'nfsclient.events.shortread'},
+ $h{$nfsinst}->{'nfsclient.events.shortwrite'},
+ $h{$nfsinst}->{'nfsclient.events.delay'}) =
+ split(/ /, $1);
+ }
+
+ # bytes
+ if ($line =~ /\tbytes:\t(.*)$/) {
+ ($h{$nfsinst}->{'nfsclient.bytes.read.normal'},
+ $h{$nfsinst}->{'nfsclient.bytes.write.normal'},
+ $h{$nfsinst}->{'nfsclient.bytes.read.direct'},
+ $h{$nfsinst}->{'nfsclient.bytes.write.direct'},
+ $h{$nfsinst}->{'nfsclient.bytes.read.server'},
+ $h{$nfsinst}->{'nfsclient.bytes.write.server'},
+ $h{$nfsinst}->{'nfsclient.pages.read'},
+ $h{$nfsinst}->{'nfsclient.pages.write'}) =
+ split(/ /, $1);
+ }
+
+ # xprt
+ if ($line =~ /\txprt:\t(.*)$/) {
+ my @stats = split(/ /, $1);
+ my $xprt_prot = shift(@stats);
+
+ if( $xprt_prot eq "tcp"){
+
+ ($h{$nfsinst}->{'nfsclient.xprt.srcport'},
+ $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
+ $h{$nfsinst}->{'nfsclient.xprt.connect_count'},
+ $h{$nfsinst}->{'nfsclient.xprt.connect_time'},
+ $h{$nfsinst}->{'nfsclient.xprt.idle_time'},
+ $h{$nfsinst}->{'nfsclient.xprt.sends'},
+ $h{$nfsinst}->{'nfsclient.xprt.recvs'},
+ $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
+ $h{$nfsinst}->{'nfsclient.xprt.req_u'},
+ $h{$nfsinst}->{'nfsclient.xprt.bklog_u'}) =
+ @stats;
+
+ # Unused RDMA Elements
+ $h{$nfsinst}->{'nfsclient.xprt.read_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.write_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.pullup'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.fixup'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.hardway'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.bad_reply'} = 0;
+
+ } elsif ( $xprt_prot eq "udp"){
+ ($h{$nfsinst}->{'nfsclient.xprt.srcport'},
+ $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
+ $h{$nfsinst}->{'nfsclient.xprt.sends'},
+ $h{$nfsinst}->{'nfsclient.xprt.recvs'},
+ $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
+ $h{$nfsinst}->{'nfsclient.xprt.req_u'},
+ $h{$nfsinst}->{'nfsclient.xprt.bklog_u'}) =
+ @stats;
+
+ # Unused TCP elements
+ $h{$nfsinst}->{'nfsclient.xprt.connect_count'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.connect_time'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.idle_time'} = 0;
+
+ # Unused RDMA Elements
+ $h{$nfsinst}->{'nfsclient.xprt.read_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.write_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.pullup'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.fixup'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.hardway'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'} = 0;
+ $h{$nfsinst}->{'nfsclient.xprt.bad_reply'} = 0;
+
+ } elsif ( $xprt_prot eq "rdma"){
+ ($h{$nfsinst}->{'nfsclient.xprt.srcport'},
+ $h{$nfsinst}->{'nfsclient.xprt.bind_count'},
+ $h{$nfsinst}->{'nfsclient.xprt.connect_count'},
+ $h{$nfsinst}->{'nfsclient.xprt.connect_time'},
+ $h{$nfsinst}->{'nfsclient.xprt.idle_time'},
+ $h{$nfsinst}->{'nfsclient.xprt.sends'},
+ $h{$nfsinst}->{'nfsclient.xprt.recvs'},
+ $h{$nfsinst}->{'nfsclient.xprt.bad_xids'},
+ $h{$nfsinst}->{'nfsclient.xprt.req_u'},
+ $h{$nfsinst}->{'nfsclient.xprt.bklog_u'},
+ $h{$nfsinst}->{'nfsclient.xprt.read_chunks'},
+ $h{$nfsinst}->{'nfsclient.xprt.write_chunks'},
+ $h{$nfsinst}->{'nfsclient.xprt.reply_chunks'},
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_req'},
+ $h{$nfsinst}->{'nfsclient.xprt.total_rdma_rep'},
+ $h{$nfsinst}->{'nfsclient.xprt.pullup'},
+ $h{$nfsinst}->{'nfsclient.xprt.fixup'},
+ $h{$nfsinst}->{'nfsclient.xprt.hardway'},
+ $h{$nfsinst}->{'nfsclient.xprt.failed_marshal'},
+ $h{$nfsinst}->{'nfsclient.xprt.bad_reply'}) =
+ @stats;
+
+ } else {
+ next;
+ }
+ }
+
+ # per-op statistics
+ # pre-populate all possible ops from v3, v4, v4.1 and set to noval
+ # ops defined below
+ for my $opname (@all_ops) {
+ $h{$nfsinst}->{"nfsclient.ops.$opname.ops"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.ntrans"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.timeouts"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.bytes_sent"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.bytes_recv"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.queue"} = $noval;
+ $h{$nfsinst}->{"nfsclient.ops.$opname.rtt"} = $noval;
+ $h{$nfsinst}->{"nfsclinet.ops.$opname.execute"} = $noval;
+ }
+
+ if ($line =~ /\tper-op statistics$/) {
+ # We'll do these a bit differently since they are not
+ # all on the same line. Just loop until we don't match
+ # anymore.
+ # v4 ops can have underscore
+ while (<STATS> =~
+ /^\s*([A-Z_]*): (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*) (\d*)$/) {
+ my $op_name = "\L$1";
+ ($h{$nfsinst}->{"nfsclient.ops.$op_name.ops"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.ntrans"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.timeouts"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.bytes_sent"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.bytes_recv"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.queue"},
+ $h{$nfsinst}->{"nfsclient.ops.$op_name.rtt"},
+ $h{$nfsinst}->{"nfsclinet.ops.$op_name.execute"}) =
+ ($2, $3, $4, $5, $6, $7, $8, $9);
+ }
+ }
+ }
+
+ close STATS;
+}
+
+#
+# fetch is called once by pcp for each refresh and then the fetch callback is
+# called to query each statistic individually
+#
+sub nfsclient_fetch {
+ nfsclient_parse_proc_mountstats();
+
+ our $pmda->replace_indom($nfsclient_indom, \%h);
+}
+
+sub nfsclient_fetch_callback {
+ my ($cluster, $item, $inst) = @_;
+
+ my $lookup = pmda_inst_lookup($nfsclient_indom, $inst);
+ return (PM_ERR_INST, 0) unless defined($lookup);
+
+ my $pmid_name = pmda_pmid_name($cluster, $item)
+ or die "Unknown metric name: cluster $cluster item $item\n";
+
+ return ($lookup->{$pmid_name}, 1);
+
+}
+
+# the PCP::PMDA->new line is parsed by the check_domain rule of the PMDA build
+# process, so there are special requirements: no comments, the domain has to
+# be a bare number.
+#
+our $pmda = PCP::PMDA->new('nfsclient', 62);
+
+# metrics go here, with full descriptions
+
+# general - cluster 0
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.export',
+ 'Export',
+ '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.mountpoint',
+ 'Mount Point',
+ '');
+
+# opts - cluster 1
+#
+# Options in order:
+# NULL is not present
+# from super.c in nfs_show_mount_options
+#
+# ro | rw
+# sync | NULL
+# noatime | NULL
+# nodiratime | NULL
+# vers=([2-4](\.[0-9])?)
+# rsize=%u
+# wsize=%u
+# bsize=%u | NULL
+# namlen=%u
+# acregmin=%u | NULL
+# acregmax=%u | NULL
+# acdirmin=%u | NULL
+# acdirmax=%u | NULL
+# soft | hard
+# posix | NULL
+# nocto | NULL
+# noac | NULL
+# nolock | NULL
+# noacl | NULL
+# nordirplus | NULL
+# nosharecache | NULL
+# noresvport | NULL
+# proto=(tcp|udp|rdma)
+# port=%u | NULL
+# timeo=%lu
+# retrans=%u
+# sec=(null|sys|krb5|krb5i|krb5p|lkey|lkeyi|lkeyp|spkm|spkmi|spkmp|unknown) # null and unknown are those exact literal strings
+
+# NFS 3 only
+# Below block may not exist at all if NFS_MOUNT_LEGACY_INTERFACE
+# mountaddr="ip4addr|ip6addr|unspecified"
+# mountvers=%u | NULL
+# mountport=%u | NULL
+# mountproto=(udp|tcp|auto|udp6|tcp6|NULL) # NULL if showdefaults is false
+
+# NFS 4 Only
+# clientaddr=(ip4address|ip6address)
+
+# fsc | NULL
+# migration | NULL
+# lookupcache=(none|pos) | NULL
+# local_lock=(none|all|flock|posix)
+
+# TODO
+# deprecated after 2.6.25, do we care about these ?
+# intr|nointr
+
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.string',
+ 'Full Options String',
+ '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.readmode',
+ 'rw or ro mount mode',
+ '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.sync',
+ 'boolean sync mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,4), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.atime',
+ 'boolean atime mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,5), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.diratime',
+ 'boolean diratime mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,6), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.vers',
+ 'nfs version',
+ '');
+$pmda->add_metric(pmda_pmid(1,7), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.rsize',
+ 'rsize mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,8), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.wsize',
+ 'wsize mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,9), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.bsize',
+ 'bsize mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,10), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.namlen',
+ 'namlen mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,11), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.acregmin',
+ 'acregmin mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,12), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.acregmax',
+ 'acregmax mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,13), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.acdirmin',
+ 'acdirmin mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,14), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.acdirmax',
+ 'acdirmax mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,15), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.recovery',
+ 'hard or soft recovery behavior',
+ '');
+$pmda->add_metric(pmda_pmid(1,16), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.posix',
+ 'boolean posix mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,17), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.cto',
+ 'boolean cto mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,18), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.ac',
+ 'boolean ac mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,19), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.lock',
+ 'boolean lock mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,20), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.acl',
+ 'boolean acl mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,21), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.rdirplus',
+ 'boolean rdirplus mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,22), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.sharecache',
+ 'boolean sharecache mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,23), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.resvport',
+ 'boolean resvport mount option used',
+ '');
+$pmda->add_metric(pmda_pmid(1,24), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.proto',
+ 'nfs protocol: udp|tcp|rdma',
+ '');
+$pmda->add_metric(pmda_pmid(1,25), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.port',
+ 'port mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,26), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.timeo',
+ 'timeo mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,27), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.retrans',
+ 'retrans mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,28), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.sec',
+ 'sec mount parameter',
+ '');
+# NFS3 only
+$pmda->add_metric(pmda_pmid(1,29), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.mountaddr',
+ 'mountaddr mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,30), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.mountvers',
+ 'mountvers mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,31), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.mountport',
+ 'mountport mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,32), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.mountproto',
+ 'mountproto mount parameter',
+ '');
+# NFS4 only
+$pmda->add_metric(pmda_pmid(1,33), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.clientaddr',
+ 'clientaddr mount parameter',
+ '');
+# All
+$pmda->add_metric(pmda_pmid(1,34), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.fsc',
+ 'fsc mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,35), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.migration',
+ 'migration mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,36), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.lookupcache',
+ 'lookupcache mount parameter',
+ '');
+$pmda->add_metric(pmda_pmid(1,37), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.options.local_lock',
+ 'local_lock mount parameter',
+ '');
+
+# age - cluster 2
+$pmda->add_metric(pmda_pmid(2,1), pmda_ulong, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'nfsclient.age',
+ 'Age in Seconds',
+ '');
+# caps - cluster 3 - Any use in parsing this ?
+$pmda->add_metric(pmda_pmid(3,1), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.capabilities',
+ 'Capabilities',
+ '');
+
+# sec - cluster 4 - Any use in parsing this ?
+$pmda->add_metric(pmda_pmid(4,1), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.security',
+ 'Security Flavor',
+ '');
+
+# events - cluster 5
+$pmda->add_metric(pmda_pmid(5,1), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.inoderevalidate',
+ 'NFSIOS_INODEREVALIDATE',
+'incremented in __nfs_revalidate_inode whenever an inode is revalidated');
+
+$pmda->add_metric(pmda_pmid(5,2), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.dentryrevalidate',
+ 'NFSIOS_DENTRYREVALIDATE',
+'incremented in nfs_lookup_revalidate whenever a dentry is revalidated');
+
+$pmda->add_metric(pmda_pmid(5,3), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.datainvalidate',
+ 'NFSIOS_DATAINVALIDATE',
+'incremented in nfs_invalidate_mapping_nolock when data cache for an inode ' .
+'is invalidated');
+
+$pmda->add_metric(pmda_pmid(5,4), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.attrinvalidate',
+ 'NFSIOS_ATTRINVALIDATE',
+'incremented in nfs_zap_caches_locked and nfs_update_inode when an the ' .
+'attribute cache for an inode has been invalidated');
+
+$pmda->add_metric(pmda_pmid(5,5), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsopen',
+ 'NFSIOS_VFSOPEN',
+'incremented in nfs_file_open and nfs_opendir whenever a file or directory ' .
+'is opened');
+
+$pmda->add_metric(pmda_pmid(5,6), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfslookup',
+ 'NFSIOS_VFSLOOKUP',
+'incremented in nfs_lookup on every lookup');
+
+$pmda->add_metric(pmda_pmid(5,7), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsaccess',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,8), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsupdatepage',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,9), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsreadpage',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,10), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsreadpages',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,11), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfswritepage',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,12), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfswritepages',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,13), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsgetdents',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,14), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfssetattr',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,15), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsflush',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,16), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsfsync',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,17), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfslock',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,18), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.vfsrelease',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,19), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.congestionwait',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,20), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.setattrtrunc',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,21), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.extendwrite',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,22), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.sillyrename',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,23), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.shortread',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,24), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.shortwrite',
+ '', '');
+
+$pmda->add_metric(pmda_pmid(5,25), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.events.delay',
+ '', '');
+
+# bytes - cluster 6
+$pmda->add_metric(pmda_pmid(6,1), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.read.normal',
+ 'NFSIOS_NORMALREADBYTES',
+'the number of bytes read by applications via the read system call interface');
+
+$pmda->add_metric(pmda_pmid(6,2), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.write.normal',
+ 'NFSIOS_NORMALWRITTENBYTES',
+'the number of bytes written by applications via the write system call ' .
+'interface');
+
+$pmda->add_metric(pmda_pmid(6,3), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.read.direct',
+ 'NFSIOS_DIRECTREADBYTES',
+'the number of bytes read by applications from files opened with the ' .
+'O_DIRECT flag');
+
+$pmda->add_metric(pmda_pmid(6,4), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.write.direct',
+ 'NFSIOS_DIRECTWRITTENBYTES',
+'the number of bytes written by applications to files opened with the ' .
+'O_DIRECT flag');
+
+$pmda->add_metric(pmda_pmid(6,5), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.read.server',
+ 'NFSIOS_SERVERREADBYTES',
+'the number of bytes read from the nfs server by the nfs client via nfs ' .
+'read requests');
+
+$pmda->add_metric(pmda_pmid(6,6), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'nfsclient.bytes.write.server',
+ 'NFSIOS_SERVERWRITTENBYTES',
+'the number of bytes written to the nfs server by the nfs client via nfs ' .
+'write requests');
+
+$pmda->add_metric(pmda_pmid(6,7), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.pages.read',
+ 'NFSIOS_READPAGES',
+'the number of pages read via nfs_readpage or nfs_readpages');
+
+$pmda->add_metric(pmda_pmid(6,8), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.pages.write',
+ 'NFSIOS_WRITEPAGES',
+'the number of pages written via nfs_writepage or nfs_writepages');
+
+# xprt - cluster 7
+#
+# We have three possible transports: udp, tcp, rdma.
+# Fill in 0 for ones that dont apply. Types for RDMA from net/sunrpc/xprtrdma/transport.c : xprt_rdma_print_stats
+#
+$pmda->add_metric(pmda_pmid(7,1), PM_TYPE_STRING, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nfsclient.xprt.srcport',
+ 'tcp source port',
+'source port on the client');
+
+$pmda->add_metric(pmda_pmid(7,2), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.bind_count',
+ 'count of rpcbind get_port calls',
+'incremented in rpcb_getport_async: \"obtain the port for a given RPC ' .
+'service on a given host\"');
+
+$pmda->add_metric(pmda_pmid(7,3), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.connect_count',
+ 'count of tcp connects',
+'incremented in xs_tcp_finish_connecting and xprt_connect_status. This is ' .
+'a count of the number of tcp (re)connects (only of which is active at a ' .
+'time) for this mount.');
+
+$pmda->add_metric(pmda_pmid(7,4), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.connect_time',
+ 'jiffies waiting for connect',
+'Summed in xprt_connect_status, it is stored and printed in jiffies. This ' .
+'the sum of all connection attempts: how long was spent waiting to connect.');
+
+$pmda->add_metric(pmda_pmid(7,5), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'nfsclient.xprt.idle_time',
+ 'transport idle time',
+'How long has it been since the transport has been used. Stored and ' .
+'calculated in jiffies and printed in seconds.');
+
+$pmda->add_metric(pmda_pmid(7,6), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.sends',
+ 'count of tcp transmits',
+'Incremented in xprt_transmit upon transmit completion of each rpc.');
+
+$pmda->add_metric(pmda_pmid(7,7), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.recvs',
+ 'count of tcp receives',
+'Incremented in xprt_complete_rqst when reply processing is complete.');
+
+$pmda->add_metric(pmda_pmid(7,8), PM_TYPE_U32, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.bad_xids',
+ 'count of bad transaction identifiers',
+'When processing an rpc reply it is necessary to look up the original ' .
+'rpc_rqst using xprt_lookup_rqst. If the rpc_rqst that prompted the reply ' .
+'cannot be found on the transport bad_xids is incremented.');
+
+$pmda->add_metric(pmda_pmid(7,9), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.req_u',
+ 'average requests on the wire',
+'FIXME: The comment in struct stat says: \"average requests on the wire\", ' .
+'but the actual calculation in xprt_transmit is: ' .
+'xprt->stat.req_u += xprt->stat.sends - xprt->stat.recvs;\n' .
+'This stat may be broken.');
+
+$pmda->add_metric(pmda_pmid(7,10), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.backlog_u',
+ 'backlog queue utilization',
+'FIXME: here is the calculation in xprt_transmit: ' .
+'xprt->stat.bklog_u += xprt->backlog.qlen;\n ' .
+'qlen is incremented in __rpc_add_wait_queue and decremented in ' .
+'__rpc_remove_wait_queue.');
+
+$pmda->add_metric(pmda_pmid(7,11), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.read_chunks',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,12), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.write_chunks',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,13), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.reply_chunks',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,14), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.total_rdma_req',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,15), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.total_rdma_rep',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,16), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.pullup',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,17), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.fixup',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,18), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.hardway',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,19), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.failed_marshal',
+ '',
+ '');
+
+$pmda->add_metric(pmda_pmid(7,20), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nfsclient.xprt.bad_reply',
+ '',
+ '');
+
+# ops - cluster 8
+#
+# Different versions have different ops. We just List them all and fill in
+# only what is appropriate for this version. Non matching ones will return noval
+# from above.
+
+# ops available in v3, v4 and v4.1
+#
+our @common_ops = ('null', 'getattr', 'setattr', 'lookup', 'access', 'readlink',
+ 'read', 'write', 'create', 'symlink', 'remove',
+ 'rename', 'link', 'readdir',
+ 'fsinfo', 'pathconf', 'commit');
+
+# ops only in v3
+#
+our @v3only_ops = ('fsstat', 'mkdir', 'mknod', 'readdirplus', 'rmdir');
+
+# ops ADDED in v4, some v3 ops are invalid : fsstat, mkdir, mknod, readdirplus, rmdir
+#
+our @v4_ops = ('close', 'create_session', 'delegreturn', 'destroy_session', 'exchange_id',
+ 'fs_locations', 'getacl', 'getdeviceinfo', 'get_lease_time', 'layoutcommit',
+ 'layoutget', 'layoutreturn', 'lock', 'lockt', 'locku', 'lookup_root',
+ 'open', 'open_confirm', 'open_downgrade', 'open_noattr', 'reclaim_complete',
+ 'release_lockowner', 'renew', 'secinfo', 'sequence', 'server_caps', 'setacl',
+ 'setclientid', 'setclientid_confirm', 'statfs');
+
+# ops ADDED in v4.1, same v3 ops removed as in v4, all v4 ops should exist
+#
+our @v41_ops = ('bind_conn_to_session', 'destroy_clientid', 'free_stateid', 'getdevicelist',
+ 'secinfo_no_name', 'test_stateid');
+
+our @all_ops = ( @common_ops, @v3only_ops, @v4_ops, @v41_ops );
+
+my $item = 1;
+for my $op_name (@all_ops) {
+ $pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "nfsclient.ops.$op_name.ops",
+ 'count of operations',
+'rpc count for this op, only bumped in rpc_count_iostats');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "nfsclient.ops.$op_name.ntrans",
+ 'count of transmissions',
+'there can be more than one transmission per rpc');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), pmda_ulong, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "nfsclient.ops.$op_name.timeouts",
+ 'count of major timeouts',
+'XXX fill me in');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ "nfsclient.ops.$op_name.bytes_sent",
+ 'count of bytes out',
+'How many bytes are sent for this procedure type. This indicates how much ' .
+'load this procedure is putting on the network. It includes the RPC and ULP' .
+'headers, and the request payload');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ "nfsclient.ops.$op_name.bytes_recv",
+ 'count of bytes in',
+'How many bytes are received for this procedure type. This indicates how ' .
+'much load this procedure is putting on the network. It includes RPC and ' .
+'ULP headers, and the request payload');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ "nfsclient.ops.$op_name.queue",
+ 'milliseconds queued for transmit',
+'The length of time an RPC request waits in queue before transmission in ' .
+' milliseconds.');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ "nfsclient.ops.$op_name.rtt",
+ 'milliseconds for rpc round trip time',
+'The network + server latency of the request in milliseconds.');
+
+ $pmda->add_metric(pmda_pmid(8, $item++), PM_TYPE_U64, $nfsclient_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ "nfsclient.ops.$op_name.execute",
+ 'milliseconds for rpc execution',
+'The total time the request spent from init to release in milliseconds.');
+}
+
+&nfsclient_parse_proc_mountstats;
+
+$nfsclient_indom = $pmda->add_indom($nfsclient_indom, {}, '', '');
+
+$pmda->set_fetch(\&nfsclient_fetch);
+$pmda->set_fetch_callback(\&nfsclient_fetch_callback);
+
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdanfsclient - nfs client statistics performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdanfsclient> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from the /proc/self/mountstats interface to provide information
+on nfs mounts.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the nfsclient performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/nfsclient
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/nfsclient
+ # ./Remove
+
+B<pmdanfsclient> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/nfsclient/Install
+
+installation script for the B<pmdanfsclient> agent
+
+=item $PCP_PMDAS_DIR/nfsclient/Remove
+
+undo installation script for the B<pmdanfsclient> agent
+
+=item $PCP_LOG_DIR/pmcd/nfsclient.log
+
+default log file for error messages from B<pmdanfsclient>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and nfs(5) and mountstats(8).
+
diff --git a/src/pmdas/nginx/GNUmakefile b/src/pmdas/nginx/GNUmakefile
new file mode 100644
index 0000000..ddc5a5a
--- /dev/null
+++ b/src/pmdas/nginx/GNUmakefile
@@ -0,0 +1,55 @@
+#!gmake
+#
+# Copyright (c) 2013 Ryan Doyle.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = nginx
+DOMAIN = NGINX
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl $(IAM).conf
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ $(INSTALL) -m 644 $(IAM).conf $(PMDADIR)/$(IAM).conf
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/nginx/Install b/src/pmdas/nginx/Install
new file mode 100755
index 0000000..8ad005c
--- /dev/null
+++ b/src/pmdas/nginx/Install
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2013 Ryan Doyle.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=nginx
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+perl -e "use LWP::UserAgent" 2>/dev/null
+if test $? -ne 0
+then
+ echo "LWP::UserAgent perl module is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/nginx/Remove b/src/pmdas/nginx/Remove
new file mode 100755
index 0000000..a5705a3
--- /dev/null
+++ b/src/pmdas/nginx/Remove
@@ -0,0 +1,23 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Ryan Doyle.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=nginx
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/nginx/nginx.conf b/src/pmdas/nginx/nginx.conf
new file mode 100644
index 0000000..3a4f664
--- /dev/null
+++ b/src/pmdas/nginx/nginx.conf
@@ -0,0 +1,2 @@
+#$nginx_status_url = "http://localhost/nginx_status";
+#$nginx_fetch_timeout = 1;
diff --git a/src/pmdas/nginx/pmdanginx.pl b/src/pmdas/nginx/pmdanginx.pl
new file mode 100755
index 0000000..3ec8b65
--- /dev/null
+++ b/src/pmdas/nginx/pmdanginx.pl
@@ -0,0 +1,146 @@
+#
+# Copyright (c) 2013 Ryan Doyle.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use LWP::UserAgent;
+
+my @nginx_status = ();
+my $nginx_status_url = "http://localhost/nginx_status";
+my $nginx_fetch_timeout = 1;
+my $http_client = LWP::UserAgent->new;
+
+# Configuration files for overriding the above settings
+for my $file (pmda_config('PCP_PMDAS_DIR') . '/nginx/nginx.conf', 'nginx.conf') {
+ eval `cat $file` unless ! -f $file;
+}
+
+$http_client->agent('pmdanginx');
+$http_client->timeout($nginx_fetch_timeout);
+
+sub update_nginx_status
+{
+ my $response = $http_client->get($nginx_status_url);
+ if ($response->is_success) {
+ # All the content on the status page are digits. Map the array
+ # index to the metric item ID.
+ @nginx_status = ($response->decoded_content =~ m/(\d+)/gm);
+ } else {
+ @nginx_status = undef;
+ }
+}
+
+sub nginx_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ if ($cluster == 0 && defined($nginx_status[$item])){
+ return ($nginx_status[$item], 1);
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+my $pmda = PCP::PMDA->new('nginx', 117);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nginx.active',
+ 'Number of active connections', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nginx.accepts_count',
+ 'Total number of accepted connections', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nginx.handled_count',
+ 'Total number of handled connections', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'nginx.requests_count',
+ 'Total number of requests', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nginx.reading',
+ 'Reading the request header', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nginx.writing',
+ 'Reading the request body, processing the request or writing response', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'nginx.waiting',
+ 'Keepalive connections', '');
+
+$pmda->set_fetch_callback(\&nginx_fetch_callback);
+$pmda->set_refresh(\&update_nginx_status);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdanginx - nginx performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdanginx> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from nginx.
+
+
+=head1 INSTALLATION
+
+This PMDA requires that the nginx stub_status module is active and
+available at http://localhost/nginx_status
+
+
+Install the nginx PMDA by using the B<Install> script as root:
+
+ # cd $PCP_PMDAS_DIR/nginx
+ # ./Install
+
+To uninstall, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/nginx
+ # ./Remove
+
+B<pmdanginx> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/nginx/nginx.conf
+
+optional configuration file for B<pmdanginx>
+
+=item $PCP_PMDAS_DIR/nginx/Install
+
+installation script for the B<pmdanginx> agent
+
+=item $PCP_PMDAS_DIR/nginx/Remove
+
+undo installation script for the B<pmdanginx> agent
+
+=item $PCP_LOG_DIR/pmcd/nginx.log
+
+default log file for error messages from B<pmdanginx>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/nvidia/GNUmakefile b/src/pmdas/nvidia/GNUmakefile
new file mode 100644
index 0000000..622c726
--- /dev/null
+++ b/src/pmdas/nvidia/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = nvidia
+DOMAIN = NVML
+
+CMDTARGET = pmdanvidia$(EXECSUFFIX)
+LIBTARGET = pmda_nvidia.$(DSOSUFFIX)
+CFILES = localnvml.c nvidia.c
+HFILES = localnvml.h
+DFILES = README
+LSRCFILES = Install Remove root help pmns $(DFILES)
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_DLOPEN)
+LCFLAGS += -DDSOSUFFIX=\"$(DSOSUFFIX)\"
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LDIRT = domain.h *.log *.dir *.pag so_locations
+
+default: $(LIBTARGET) $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns domain.h $(PMDADIR)
+
+nvidia.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/nvidia/Install b/src/pmdas/nvidia/Install
new file mode 100755
index 0000000..6fd401e
--- /dev/null
+++ b/src/pmdas/nvidia/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the trivial PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=nvidia
+pmda_interface=2
+dso_opt=true
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/nvidia/README b/src/pmdas/nvidia/README
new file mode 100755
index 0000000..114896d
--- /dev/null
+++ b/src/pmdas/nvidia/README
@@ -0,0 +1,7 @@
+Readme
+NVIDIA PMDA
+===========
+
+The NVIDIA PMDA is a PCP module for gathering metrics on the performance of
+NVIDIA graphics cards. It uses the NVIDIA Management Library (NVML) to query
+the states of attached cards.
diff --git a/src/pmdas/nvidia/Remove b/src/pmdas/nvidia/Remove
new file mode 100755
index 0000000..5e28c15
--- /dev/null
+++ b/src/pmdas/nvidia/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the nvidia PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=nvidia
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/nvidia/help b/src/pmdas/nvidia/help
new file mode 100644
index 0000000..38f7b4a
--- /dev/null
+++ b/src/pmdas/nvidia/help
@@ -0,0 +1,72 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# NVIDIA PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ nvidia.numcards Number of Graphics Cards
+The number of NVIDIA Graphics cards installed in this system
+
+@ nvidia.gpuid GPU ID
+Zero indexed id of this NVIDIA card
+
+@ nvidia.cardname GPU Name
+The name of the graphics card
+
+@ nvidia.busid Card Bus ID
+The Bus ID as reported by the NVIDIA tools, not lspci
+
+@ nvidia.temp The temperature of the card
+The Temperature of the GPU on the NVIDIA card in degrees celcius.
+
+@ nvidia.fanspeed Fanspeed
+Speed of the GPU fan as a percentage of the maximum
+
+@ nvidia.perfstate NVIDIA performance state
+The PX performance state as reported from NVML. Value is an integer
+which should range from 0 (maximum performance) to 15 (minimum). If
+the state is unknown the reported value will be 32, however.
+
+@ nvidia.gpuactive Percentage of GPU utilization
+Percentage of time over the past sample period during which one or more
+kernels was executing on the GPU.
+
+@ nvidia.memactive Percentage of time spent accessing memory
+Percent of time over the past sample period during which global (device)
+memory was being read or written. This metric shows if the memory is
+actively being accesed, and is not correlated to storage amount used.
+
+@ nvidia.memused Allocated FB memory
+Amount of GPU FB memory that has currently been allocated, in bytes.
+Note that the driver/GPU always sets aside a small amount of memory
+for bookkeeping.
+
+@ nvidia.memtotal Total FB memory available
+The total amount of GPU FB memory available on the card, in bytes.
+
+@ nvidia.memfree Unallocated FB memory
+Amount of GPU FB memory that is not currently allocated, in bytes.
diff --git a/src/pmdas/nvidia/localnvml.c b/src/pmdas/nvidia/localnvml.c
new file mode 100644
index 0000000..2cadeb9
--- /dev/null
+++ b/src/pmdas/nvidia/localnvml.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#if defined(HAVE_DLFCN_H)
+#include <dlfcn.h>
+#endif
+#include "localnvml.h"
+
+/*
+ * Implements NVML interfaces based on:
+ * http://docs.nvidia.com/deploy/nvml-api/index.html
+ * ... using either a dlopen'd 3rd party or "no values available".
+ */
+
+struct {
+ const char *symbol;
+ void *handle;
+} nvml_symtab[] = {
+ { .symbol = "nvmlInit" },
+ { .symbol = "nvmlShutdown" },
+ { .symbol = "nvmlDeviceGetCount" },
+ { .symbol = "nvmlDeviceGetHandleByIndex" },
+ { .symbol = "nvmlDeviceGetName" },
+ { .symbol = "nvmlDeviceGetPciInfo" },
+ { .symbol = "nvmlDeviceGetFanSpeed" },
+ { .symbol = "nvmlDeviceGetTemperature" },
+ { .symbol = "nvmlDeviceGetUtilizationRates" },
+ { .symbol = "nvmlDeviceGetMemoryInfo" },
+ { .symbol = "nvmlDeviceGetPerformanceState" },
+};
+enum {
+ NVML_INIT,
+ NVML_SHUTDOWN,
+ NVML_DEVICE_GET_COUNT,
+ NVML_DEVICE_GET_HANDLEBYINDEX,
+ NVML_DEVICE_GET_NAME,
+ NVML_DEVICE_GET_PCIINFO,
+ NVML_DEVICE_GET_FANSPEED,
+ NVML_DEVICE_GET_TEMPERATURE,
+ NVML_DEVICE_GET_UTILIZATIONRATES,
+ NVML_DEVICE_GET_MEMORYINFO,
+ NVML_DEVICE_GET_PERFORMANCESTATE,
+ NVML_SYMBOL_COUNT
+};
+typedef int (*local_init_t)(void);
+typedef int (*local_shutdown_t)(void);
+typedef int (*local_dev_get_count_t)(unsigned int *);
+typedef int (*local_dev_get_handlebyindex_t)(unsigned int, nvmlDevice_t *);
+typedef int (*local_dev_get_name_t)(nvmlDevice_t, char *, unsigned int);
+typedef int (*local_dev_get_pciinfo_t)(nvmlDevice_t, nvmlPciInfo_t *);
+typedef int (*local_dev_get_fanspeed_t)(nvmlDevice_t, unsigned int *);
+typedef int (*local_dev_get_temperature_t)(nvmlDevice_t, nvmlTemperatureSensors_t, unsigned int *);
+typedef int (*local_dev_get_utilizationrates_t)(nvmlDevice_t, nvmlUtilization_t *);
+typedef int (*local_dev_get_memoryinfo_t)(nvmlDevice_t, nvmlMemory_t *);
+typedef int (*local_dev_get_performancestate_t)(nvmlDevice_t, nvmlPstates_t *);
+
+static int
+resolve_symbols(void)
+{
+ static void *nvml_dso;
+ int i;
+
+ if (nvml_dso != NULL)
+ return 0;
+ if ((nvml_dso = dlopen("libnvidia-ml." DSOSUFFIX, RTLD_NOW)) == NULL)
+ return NVML_ERROR_LIBRARY_NOT_FOUND;
+ __pmNotifyErr(LOG_INFO, "Successfully loaded NVIDIA NVML library");
+ for (i = 0; i < NVML_SYMBOL_COUNT; i++)
+ nvml_symtab[i].handle = dlsym(nvml_dso, nvml_symtab[i].symbol);
+ return 0;
+}
+
+int
+localNvmlInit(void)
+{
+ local_init_t init;
+ void *func;
+ int sts = resolve_symbols();
+
+ if (sts != 0)
+ return sts;
+ if ((func = nvml_symtab[NVML_INIT].handle) == NULL)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ init = (local_init_t)func;
+ return init();
+}
+
+int
+localNvmlShutdown(void)
+{
+ local_shutdown_t shutdown;
+ void *func = nvml_symtab[NVML_SHUTDOWN].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ shutdown = (local_shutdown_t)func;
+ return shutdown();
+}
+
+int
+localNvmlDeviceGetCount(unsigned int *count)
+{
+ local_dev_get_count_t dev_get_count;
+ void *func = nvml_symtab[NVML_DEVICE_GET_COUNT].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_count = (local_dev_get_count_t)func;
+ return dev_get_count(count);
+}
+
+int
+localNvmlDeviceGetHandleByIndex(unsigned int index, nvmlDevice_t *device)
+{
+ local_dev_get_handlebyindex_t dev_get_handlebyindex;
+ void *func = nvml_symtab[NVML_DEVICE_GET_HANDLEBYINDEX].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_handlebyindex = (local_dev_get_handlebyindex_t)func;
+ return dev_get_handlebyindex(index, device);
+}
+
+int
+localNvmlDeviceGetName(nvmlDevice_t device, char *name, unsigned int size)
+{
+ local_dev_get_name_t dev_get_name;
+ void *func = nvml_symtab[NVML_DEVICE_GET_NAME].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_name = (local_dev_get_name_t)func;
+ return dev_get_name(device, name, size);
+}
+
+int
+localNvmlDeviceGetPciInfo(nvmlDevice_t device, nvmlPciInfo_t *info)
+{
+ local_dev_get_pciinfo_t dev_get_pciinfo;
+ void *func = nvml_symtab[NVML_DEVICE_GET_PCIINFO].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_pciinfo = (local_dev_get_pciinfo_t)func;
+ return dev_get_pciinfo(device, info);
+}
+
+int
+localNvmlDeviceGetFanSpeed(nvmlDevice_t device, unsigned int *speed)
+{
+ local_dev_get_fanspeed_t dev_get_fanspeed;
+ void *func = nvml_symtab[NVML_DEVICE_GET_FANSPEED].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_fanspeed = (local_dev_get_fanspeed_t)func;
+ return dev_get_fanspeed(device, speed);
+}
+
+int
+localNvmlDeviceGetTemperature(nvmlDevice_t device, nvmlTemperatureSensors_t code, unsigned int *temp)
+{
+ local_dev_get_temperature_t dev_get_temperature;
+ void *func = nvml_symtab[NVML_DEVICE_GET_TEMPERATURE].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_temperature = (local_dev_get_temperature_t)func;
+ return dev_get_temperature(device, code, temp);
+}
+
+int
+localNvmlDeviceGetUtilizationRates(nvmlDevice_t device, nvmlUtilization_t *util)
+{
+ local_dev_get_utilizationrates_t dev_get_utilizationrates;
+ void *func = nvml_symtab[NVML_DEVICE_GET_UTILIZATIONRATES].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_utilizationrates = (local_dev_get_utilizationrates_t)func;
+ return dev_get_utilizationrates(device, util);
+}
+
+int
+localNvmlDeviceGetMemoryInfo(nvmlDevice_t device, nvmlMemory_t *memory)
+{
+ local_dev_get_memoryinfo_t dev_get_memoryinfo;
+ void *func = nvml_symtab[NVML_DEVICE_GET_MEMORYINFO].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_memoryinfo = (local_dev_get_memoryinfo_t)func;
+ return dev_get_memoryinfo(device, memory);
+}
+
+int
+localNvmlDeviceGetPerformanceState(nvmlDevice_t device, nvmlPstates_t *state)
+{
+ local_dev_get_performancestate_t dev_get_performancestate;
+ void *func = nvml_symtab[NVML_DEVICE_GET_PERFORMANCESTATE].handle;
+
+ if (!func)
+ return NVML_ERROR_FUNCTION_NOT_FOUND;
+ dev_get_performancestate = (local_dev_get_performancestate_t)func;
+ return dev_get_performancestate(device, state);
+}
+
+const char *
+localNvmlErrStr(nvmlReturn_t sts)
+{
+ int i;
+ static const char *unknown = "No such error code";
+ static struct {
+ int code;
+ const char *msg;
+ } table[] = { {
+ NVML_SUCCESS,
+"The operation was successful" }, {
+ NVML_ERROR_UNINITIALIZED,
+"NVML was not first initialized with nvmlInit()" }, {
+ NVML_ERROR_INVALID_ARGUMENT,
+"A supplied argument is invalid" }, {
+ NVML_ERROR_NOT_SUPPORTED,
+"The requested operation is not available on target device" }, {
+ NVML_ERROR_NO_PERMISSION,
+"The current user does not have permission for operation" }, {
+ NVML_ERROR_ALREADY_INITIALIZED,
+"Deprecated error code (5)" }, {
+ NVML_ERROR_NOT_FOUND,
+"A query to find an object was unsuccessful" }, {
+ NVML_ERROR_INSUFFICIENT_SIZE,
+"An input argument is not large enough" }, {
+ NVML_ERROR_INSUFFICIENT_POWER,
+"A device's external power cables are not properly attached" }, {
+ NVML_ERROR_DRIVER_NOT_LOADED,
+"NVIDIA driver is not loaded" }, {
+ NVML_ERROR_TIMEOUT,
+"User provided timeout passed" }, {
+ NVML_ERROR_IRQ_ISSUE,
+"NVIDIA Kernel detected an interrupt issue with a GPU" }, {
+ NVML_ERROR_LIBRARY_NOT_FOUND,
+"NVML Shared Library couldn't be found or loaded" }, {
+ NVML_ERROR_FUNCTION_NOT_FOUND,
+"Local version of NVML doesn't implement this function" }, {
+ NVML_ERROR_CORRUPTED_INFOROM,
+"infoROM is corrupted" }, {
+ NVML_ERROR_GPU_IS_LOST,
+"The GPU has fallen off the bus or has otherwise become inaccessible" }, {
+ NVML_ERROR_UNKNOWN,
+"An internal driver error occurred"
+ } };
+
+ for (i = 0; i < (sizeof(table)/sizeof(table[0])); i++) {
+ if (table[i].code == sts)
+ return table[i].msg;
+ }
+ return unknown;
+}
diff --git a/src/pmdas/nvidia/localnvml.h b/src/pmdas/nvidia/localnvml.h
new file mode 100644
index 0000000..3d108e5
--- /dev/null
+++ b/src/pmdas/nvidia/localnvml.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LOCAL_NVML_H
+#define _LOCAL_NVML_H
+
+/*
+ * NVML interfaces and data structures, based on:
+ * http://docs.nvidia.com/deploy/nvml-api/index.html
+ */
+
+#define NVML_DEVICE_NAME_BUFFER_SIZE 64
+#define NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE 16
+
+typedef void *nvmlDevice_t; /* used as an opaque handle */
+typedef int nvmlPstates_t; /* performance state (0-15) */
+
+/* Error codes */
+typedef enum {
+ NVML_SUCCESS = 0,
+ NVML_ERROR_UNINITIALIZED = 1,
+ NVML_ERROR_INVALID_ARGUMENT = 2,
+ NVML_ERROR_NOT_SUPPORTED = 3,
+ NVML_ERROR_NO_PERMISSION = 4,
+ NVML_ERROR_ALREADY_INITIALIZED = 5,
+ NVML_ERROR_NOT_FOUND = 6,
+ NVML_ERROR_INSUFFICIENT_SIZE = 7,
+ NVML_ERROR_INSUFFICIENT_POWER = 8,
+ NVML_ERROR_DRIVER_NOT_LOADED = 9,
+ NVML_ERROR_TIMEOUT = 10,
+ NVML_ERROR_IRQ_ISSUE = 11,
+ NVML_ERROR_LIBRARY_NOT_FOUND = 12,
+ NVML_ERROR_FUNCTION_NOT_FOUND = 13,
+ NVML_ERROR_CORRUPTED_INFOROM = 14,
+ NVML_ERROR_GPU_IS_LOST = 15,
+ NVML_ERROR_UNKNOWN = 999
+} nvmlReturn_t;
+
+typedef enum {
+ NVML_TEMPERATURE_GPU = 0,
+ NVML_TEMPERATURE_COUNT
+} nvmlTemperatureSensors_t;
+
+typedef struct {
+ char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];
+ unsigned int domain;
+ unsigned int bus;
+ unsigned int device;
+ unsigned int pciDeviceId;
+ unsigned int pciSubSystemId;
+ unsigned int reserved[4];
+} nvmlPciInfo_t;
+
+typedef struct {
+ unsigned int gpu;
+ unsigned int memory;
+} nvmlUtilization_t;
+
+typedef struct {
+ unsigned long long total;
+ unsigned long long free;
+ unsigned long long used;
+} nvmlMemory_t;
+
+extern int localNvmlInit(void);
+extern int localNvmlShutdown(void);
+extern const char *localNvmlErrStr(nvmlReturn_t);
+
+extern int localNvmlDeviceGetCount(unsigned int *);
+extern int localNvmlDeviceGetHandleByIndex(unsigned int, nvmlDevice_t *);
+extern int localNvmlDeviceGetName(nvmlDevice_t, char *, unsigned int);
+extern int localNvmlDeviceGetPciInfo(nvmlDevice_t, nvmlPciInfo_t *);
+extern int localNvmlDeviceGetFanSpeed(nvmlDevice_t, unsigned int *);
+extern int localNvmlDeviceGetTemperature(nvmlDevice_t, nvmlTemperatureSensors_t, unsigned int *);
+extern int localNvmlDeviceGetUtilizationRates(nvmlDevice_t, nvmlUtilization_t *);
+extern int localNvmlDeviceGetMemoryInfo(nvmlDevice_t, nvmlMemory_t *);
+extern int localNvmlDeviceGetPerformanceState(nvmlDevice_t, nvmlPstates_t *);
+
+#endif /* _LOCAL_NVML_H */
diff --git a/src/pmdas/nvidia/nvidia.c b/src/pmdas/nvidia/nvidia.c
new file mode 100644
index 0000000..849f51f
--- /dev/null
+++ b/src/pmdas/nvidia/nvidia.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "localnvml.h"
+
+/* InDom table (just one row - corresponding to the set of graphics cards) */
+enum { GCARD_INDOM = 0 };
+pmdaIndom indomtab[] = {
+ { GCARD_INDOM, 0, NULL },
+};
+
+/* List of metric item numbers - increasing from zero, no holes */
+enum {
+ NVIDIA_NUMCARDS = 0,
+ NVIDIA_CARDID,
+ NVIDIA_CARDNAME,
+ NVIDIA_BUSID,
+ NVIDIA_TEMP,
+ NVIDIA_FANSPEED,
+ NVIDIA_PERFSTATE,
+ NVIDIA_GPUACTIVE,
+ NVIDIA_MEMACTIVE,
+ NVIDIA_MEMUSED,
+ NVIDIA_MEMTOTAL,
+ NVIDIA_MEMFREE,
+
+ NVIDIA_METRIC_COUNT
+};
+
+/* Table of metrics exported by this PMDA */
+static pmdaMetric metrictab[] = {
+ { NULL, { PMDA_PMID(0, NVIDIA_NUMCARDS), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_CARDID), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_CARDNAME), PM_TYPE_STRING, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_BUSID), PM_TYPE_STRING, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_TEMP), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_FANSPEED), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_PERFSTATE), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_GPUACTIVE), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_MEMACTIVE), PM_TYPE_U32, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_MEMUSED), PM_TYPE_U64, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_MEMTOTAL), PM_TYPE_U64, GCARD_INDOM,
+ PM_SEM_DISCRETE, PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+ { NULL, { PMDA_PMID(0, NVIDIA_MEMFREE), PM_TYPE_U64, GCARD_INDOM,
+ PM_SEM_INSTANT, PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+};
+
+/* GCARD_INDOM struct, stats that are per card */
+typedef struct {
+ int cardid;
+ int failed[NVIDIA_METRIC_COUNT];
+ char *name;
+ char *busid;
+ int temp;
+ int fanspeed;
+ int perfstate;
+ nvmlUtilization_t active;
+ nvmlMemory_t memory;
+} nvinfo_t;
+
+/* overall struct, holds instance values, indom and instance struct arrays */
+typedef struct {
+ int numcards;
+ int maxcards;
+ nvinfo_t *nvinfo;
+ pmdaIndom *nvindom;
+} pcp_nvinfo_t;
+
+static pcp_nvinfo_t pcp_nvinfo;
+static char mypath[MAXPATHLEN];
+static int isDSO = 1;
+static int nvmlDSO_loaded;
+
+static int
+setup_gcard_indom(void)
+{
+ unsigned int device_count = 0;
+ pmdaIndom *idp = &indomtab[GCARD_INDOM];
+ char gpuname[32], *name;
+ size_t size;
+ int i, sts;
+
+ /* Initialize instance domain and instances. */
+ if ((sts = localNvmlDeviceGetCount(&device_count)) != NVML_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "nvmlDeviceGetCount: %s",
+ localNvmlErrStr(sts));
+ return sts;
+ }
+
+ pcp_nvinfo.nvindom = idp;
+ pcp_nvinfo.nvindom->it_numinst = 0;
+
+ size = device_count * sizeof(pmdaInstid);
+ pcp_nvinfo.nvindom->it_set = (pmdaInstid *)malloc(size);
+ if (!pcp_nvinfo.nvindom->it_set) {
+ __pmNoMem("gcard indom", size, PM_RECOV_ERR);
+ return -ENOMEM;
+ }
+
+ size = device_count * sizeof(nvinfo_t);
+ if ((pcp_nvinfo.nvinfo = (nvinfo_t *)malloc(size)) == NULL) {
+ __pmNoMem("gcard values", size, PM_RECOV_ERR);
+ free(pcp_nvinfo.nvindom->it_set);
+ return -ENOMEM;
+ }
+ memset(pcp_nvinfo.nvinfo, 0, size);
+
+ for (i = 0; i < device_count; i++) {
+ pcp_nvinfo.nvindom->it_set[i].i_inst = i;
+ snprintf(gpuname, sizeof(gpuname), "gpu%d", i);
+ if ((name = strdup(gpuname)) == NULL) {
+ __pmNoMem("gcard instname", strlen(gpuname), PM_RECOV_ERR);
+ while (--i)
+ free(pcp_nvinfo.nvindom->it_set[i].i_name);
+ free(pcp_nvinfo.nvindom->it_set);
+ free(pcp_nvinfo.nvinfo);
+ return -ENOMEM;
+ }
+ pcp_nvinfo.nvindom->it_set[i].i_name = name;
+ }
+
+ pcp_nvinfo.numcards = 0;
+ pcp_nvinfo.maxcards = device_count;
+ pcp_nvinfo.nvindom->it_numinst = device_count;
+ return 0;
+}
+
+static int
+refresh(pcp_nvinfo_t *pcp_nvinfo)
+{
+ unsigned int device_count;
+ nvmlDevice_t device;
+ char name[NVML_DEVICE_NAME_BUFFER_SIZE];
+ nvmlPciInfo_t pci;
+ unsigned int fanspeed;
+ unsigned int temperature;
+ nvmlUtilization_t utilization;
+ nvmlMemory_t memory;
+ nvmlPstates_t pstate;
+ int i, sts;
+
+ if (!nvmlDSO_loaded) {
+ if (localNvmlInit() == NVML_ERROR_LIBRARY_NOT_FOUND)
+ return 0;
+ setup_gcard_indom();
+ nvmlDSO_loaded = 1;
+ }
+
+ if ((sts = localNvmlDeviceGetCount(&device_count)) != 0) {
+ __pmNotifyErr(LOG_ERR, "nvmlDeviceGetCount: %s",
+ localNvmlErrStr(sts));
+ return sts;
+ }
+ pcp_nvinfo->numcards = device_count;
+
+ for (i = 0; i < device_count && i < pcp_nvinfo->maxcards; i++) {
+ pcp_nvinfo->nvinfo[i].cardid = i;
+ if ((sts = localNvmlDeviceGetHandleByIndex(i, &device))) {
+ __pmNotifyErr(LOG_ERR, "nvmlDeviceGetHandleByIndex: %s",
+ localNvmlErrStr(sts));
+ memset(pcp_nvinfo->nvinfo[i].failed, 1, NVIDIA_METRIC_COUNT);
+ continue;
+ }
+ memset(pcp_nvinfo->nvinfo[i].failed, 0, NVIDIA_METRIC_COUNT);
+ if ((sts = localNvmlDeviceGetName(device, name, sizeof(name))))
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_CARDNAME] = 1;
+ if ((sts = localNvmlDeviceGetPciInfo(device, &pci)))
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_BUSID] = 1;
+ if ((sts = localNvmlDeviceGetFanSpeed(device, &fanspeed)))
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_FANSPEED] = 1;
+ if ((sts = localNvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &temperature)))
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_TEMP] = 1;
+ if ((sts = localNvmlDeviceGetUtilizationRates(device, &utilization))) {
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_GPUACTIVE] = 1;
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_MEMACTIVE] = 1;
+ }
+ if ((sts = localNvmlDeviceGetMemoryInfo(device, &memory))) {
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_MEMUSED] = 1;
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_MEMTOTAL] = 1;
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_MEMFREE] = 1;
+ }
+ if ((sts = localNvmlDeviceGetPerformanceState(device, &pstate)))
+ pcp_nvinfo->nvinfo[i].failed[NVIDIA_PERFSTATE] = 1;
+
+ if (pcp_nvinfo->nvinfo[i].name == NULL)
+ pcp_nvinfo->nvinfo[i].name = strdup(name);
+ if (pcp_nvinfo->nvinfo[i].busid == NULL)
+ pcp_nvinfo->nvinfo[i].busid = strdup(pci.busId);
+ pcp_nvinfo->nvinfo[i].temp = temperature;
+ pcp_nvinfo->nvinfo[i].fanspeed = fanspeed;
+ pcp_nvinfo->nvinfo[i].perfstate = pstate;
+ pcp_nvinfo->nvinfo[i].active = utilization; /* struct copy */
+ pcp_nvinfo->nvinfo[i].memory = memory; /* struct copy */
+ }
+
+ return 0;
+}
+
+/*
+ * Wrapper for pmdaFetch which refresh the set of values once per fetch
+ * PDU. The fetchCallback is then called once per-metric/instance pair
+ * to perform the actual filling of the pmResult (via each pmAtomValue).
+ */
+static int
+nvidia_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ refresh(&pcp_nvinfo);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static int
+nvidia_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+ if (idp->item != 0 && inst > indomtab[GCARD_INDOM].it_numinst)
+ return PM_ERR_INST;
+
+ switch (idp->item) {
+ case NVIDIA_NUMCARDS:
+ atom->ul = pcp_nvinfo.numcards;
+ break;
+ case NVIDIA_CARDID:
+ atom->ul = pcp_nvinfo.nvinfo[inst].cardid;
+ break;
+ case NVIDIA_CARDNAME:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_CARDNAME])
+ return PM_ERR_VALUE;
+ atom->cp = pcp_nvinfo.nvinfo[inst].name;
+ break;
+ case NVIDIA_BUSID:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_BUSID])
+ return PM_ERR_VALUE;
+ atom->cp = pcp_nvinfo.nvinfo[inst].busid;
+ break;
+ case NVIDIA_TEMP:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_TEMP])
+ return PM_ERR_VALUE;
+ atom->ul = pcp_nvinfo.nvinfo[inst].temp;
+ break;
+ case NVIDIA_FANSPEED:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_FANSPEED])
+ return PM_ERR_VALUE;
+ atom->ul = pcp_nvinfo.nvinfo[inst].fanspeed;
+ break;
+ case NVIDIA_PERFSTATE:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_PERFSTATE])
+ return PM_ERR_VALUE;
+ atom->ul = pcp_nvinfo.nvinfo[inst].perfstate;
+ break;
+ case NVIDIA_GPUACTIVE:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_GPUACTIVE])
+ return PM_ERR_VALUE;
+ atom->ul = pcp_nvinfo.nvinfo[inst].active.gpu;
+ break;
+ case NVIDIA_MEMACTIVE:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_MEMACTIVE])
+ return PM_ERR_VALUE;
+ atom->ul = pcp_nvinfo.nvinfo[inst].active.memory;
+ break;
+ case NVIDIA_MEMUSED:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_MEMUSED])
+ return PM_ERR_VALUE;
+ atom->ull = pcp_nvinfo.nvinfo[inst].memory.used;
+ break;
+ case NVIDIA_MEMTOTAL:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_MEMTOTAL])
+ return PM_ERR_VALUE;
+ atom->ull = pcp_nvinfo.nvinfo[inst].memory.total;
+ break;
+ case NVIDIA_MEMFREE:
+ if (pcp_nvinfo.nvinfo[inst].failed[NVIDIA_MEMFREE])
+ return PM_ERR_VALUE;
+ atom->ull = pcp_nvinfo.nvinfo[inst].memory.free;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return 0;
+}
+
+/**
+ * Initializes the path to the help file for this PMDA.
+ */
+static void
+initializeHelpPath()
+{
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "nvidia" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+}
+
+void
+__PMDA_INIT_CALL
+nvidia_init(pmdaInterface *dp)
+{
+ int sts;
+
+ if (isDSO) {
+ initializeHelpPath();
+ pmdaDSO(dp, PMDA_INTERFACE_2, "nvidia DSO", mypath);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ if ((sts = localNvmlInit()) == NVML_SUCCESS) {
+ setup_gcard_indom();
+ nvmlDSO_loaded = 1;
+ }
+ else {
+ /*
+ * This is OK, just continue on until it *is* installed;
+ * until that time, simply report "no values available".
+ */
+ __pmNotifyErr(LOG_INFO, "NVIDIA NVML library currently unavailable");
+ }
+
+ dp->version.any.fetch = nvidia_fetch;
+ pmdaSetFetchCallBack(dp, nvidia_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:l:?",
+ .long_options = longopts,
+};
+
+int
+main(int argc, char **argv)
+{
+ pmdaInterface desc;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+
+ initializeHelpPath();
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, NVML,
+ "nvidia.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ pmdaOpenLog(&desc);
+ pmdaConnect(&desc);
+ nvidia_init(&desc);
+ pmdaMain(&desc);
+
+ exit(0);
+}
diff --git a/src/pmdas/nvidia/pmns b/src/pmdas/nvidia/pmns
new file mode 100644
index 0000000..b496cae
--- /dev/null
+++ b/src/pmdas/nvidia/pmns
@@ -0,0 +1,30 @@
+/*
+ * Metrics for nvidia GPU PMDA
+ *
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+nvidia {
+ numcards NVML:0:0
+ gpuid NVML:0:1
+ cardname NVML:0:2
+ busid NVML:0:3
+ temp NVML:0:4
+ fanspeed NVML:0:5
+ perfstate NVML:0:6
+ gpuactive NVML:0:7
+ memactive NVML:0:8
+ memused NVML:0:9
+ memtotal NVML:0:10
+ memfree NVML:0:11
+}
diff --git a/src/pmdas/nvidia/root b/src/pmdas/nvidia/root
new file mode 100644
index 0000000..fe12bc2
--- /dev/null
+++ b/src/pmdas/nvidia/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { nvidia }
+
+#include "pmns"
+
diff --git a/src/pmdas/oracle/GNUmakefile b/src/pmdas/oracle/GNUmakefile
new file mode 100644
index 0000000..052cf82
--- /dev/null
+++ b/src/pmdas/oracle/GNUmakefile
@@ -0,0 +1,49 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = oracle
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/oracle/Install b/src/pmdas/oracle/Install
new file mode 100755
index 0000000..a64aac0
--- /dev/null
+++ b/src/pmdas/oracle/Install
@@ -0,0 +1,63 @@
+#! /bin/sh
+#
+# Copyright (c) 2009,2012 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Oracle PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=oracle
+user=$iam
+domain=32
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+perl -e "use DBI" 2>/dev/null
+if test $? -ne 0; then
+ echo "Perl database interface (DBI) is not installed"
+ exit 1
+fi
+
+perl -e "use DBD::Oracle" 2>/dev/null
+if test $? -ne 0; then
+ echo "Oracle database driver (DBD::Oracle) is not installed"
+ exit 1
+fi
+
+su -c id $user >/dev/null 2>&1
+if test $? -ne 0; then
+ echo "Cannot change user to $user, sorry this is fatal"
+ exit 1
+fi
+
+su -c mkdir -p "$PCP_VAR_DIR/config/pmda" 2>/dev/null
+indoms="0 1 2 3 4 5 6 7 8 9 10"
+for indom in $indoms
+do
+ touch "$PCP_VAR_DIR/config/pmda/$domain.$indom" >/dev/null 2>&1
+ if test $? -ne 0; then
+ echo "Cannot create indom persistance files as user $user"
+ echo "Failed on: $PCP_VAR_DIR/config/pmda/$domain.$indom"
+ exit 1
+ fi
+done
+
+# TODO: need to ask for user/pass and SIDs, write $PCP_VAR_DIR/config/oracle/oracle.conf
+# (unless it exists)
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/oracle/Remove b/src/pmdas/oracle/Remove
new file mode 100755
index 0000000..131c6e0
--- /dev/null
+++ b/src/pmdas/oracle/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Oracle PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=oracle
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/oracle/pmdaoracle.pl b/src/pmdas/oracle/pmdaoracle.pl
new file mode 100644
index 0000000..b45be2f
--- /dev/null
+++ b/src/pmdas/oracle/pmdaoracle.pl
@@ -0,0 +1,2364 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2009,2012 Aconex. All Rights Reserved.
+# Copyright (c) 1998 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use DBI;
+
+my $username = 'SYSTEM';
+my $password = 'manager';
+my @sids = ( 'master' );
+
+# Configuration files for overriding the above settings
+for my $file ( '/etc/pcpdbi.conf', # system defaults (lowest priority)
+ pmda_config('PCP_PMDAS_DIR') . '/oracle/oracle.conf',
+ pmda_config('PCP_VAR_DIR') . '/config/oracle/oracle.conf',
+ './oracle.conf' ) { # current directory (high priority)
+ eval `cat $file` unless ! -f $file;
+}
+
+use vars qw( $pmda %sid_instances );
+use vars qw( %latch_instances %file_instances %rollback_instances );
+use vars qw( %reqdist_instances %rowcache_instances %session_instances );
+use vars qw( %object_cache_instances %system_event_instances );
+use vars qw( %librarycache_instances %waitstat_instances );
+
+my $latch_indom = 0;
+my $file_indom = 1;
+my $rollback_indom = 2;
+my $reqdist_indom = 3;
+my $rowcache_indom = 4;
+my $session_indom = 5;
+my $object_cache_indom = 6;
+my $system_event_indom = 7;
+my $librarycache_indom = 8;
+my $waitstat_indom = 9;
+my $sid_indom = 10;
+
+my @novalues = ();
+my %object_cache_instances = (
+ 'INDEX' => \@novalues, 'TABLE' => \@novalues,
+ 'CLUSTER' => \@novalues, 'VIEW' => \@novalues,
+ 'SET' => \@novalues, 'SYNONYM' => \@novalues,
+ 'SEQUENCE' => \@novalues, 'PROCEDURE' => \@novalues,
+ 'FUNCTION' => \@novalues, 'PACKAGE' => \@novalues,
+ 'PACKAGE_BODY' => \@novalues, 'TRIGGER' => \@novalues,
+ 'CLASS' => \@novalues, 'OBJECT' => \@novalues,
+ 'USER' => \@novalues, 'DBLINK' => \@novalues,
+ 'NON-EXISTENT' => \@novalues, 'NOT_LOADED' => \@novalues,
+ 'OTHER' => \@novalues );
+
+my %sids_by_name;
+my %tables_by_name = (
+ 'sysstat' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ # insts => SID indom is a static array
+ fetch => 'SELECT statistic#, value FROM v$sysstat' },
+ 'license' => {
+ insts_handle => undef, fetch_handle => undef, values => {}, },
+ 'latch' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT latch#, name FROM v$latch',
+ fetch => 'SELECT latch#, name FROM v$latch' },
+ 'filestat' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT file#, name FROM v$datafile',
+ fetch => 'SELECT file#, phyrds, phywrts, phyblkrd,' .
+ ' phyblkwrt, readtim, writetim' .
+ ' FROM v$filestat' },
+ 'rollstat' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT usn, name FROM v$rollname',
+ fetch => 'SELECT usn, rssize, writes, xacts, gets, waits, hwmsize,' .
+ ' shrinks, wraps, extends, aveshrink, aveactive' .
+ ' FROM v$rollstat' },
+ 'reqdist' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT bucket FROM v$reqdist',
+ fetch => 'SELECT bucket, count FROM v$reqdist' },
+ 'backup' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT file# FROM v$backup',
+ fetch => 'SELECT file#, status FROM v$backup' },
+ 'rowcache' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT cache#, subordinate#, parameter FROM v$rowcache',
+ fetch => 'SELECT cache#, subordinate#, count, gets, getmisses,' .
+ ' scans, scanmisses' .
+ ' FROM v$rowcache' },
+ 'sesstat' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT sid FROM v$session',
+ fetch => 'SELECT sid, statistic#, value FROM v$sesstat' },
+ 'object_cache' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ # insts => object_cache indom is a static array
+ fetch => 'SELECT type, sharable_mem, loads, locks, pins' .
+ ' FROM v$db_object_cache' },
+ 'system_event' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT event#,name FROM v$event_name',
+ fetch => 'SELECT event_id, event, total_waits, total_timeouts,' .
+ ' time_waited, average_wait' .
+ ' FROM v$system_event' },
+ 'version' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ fetch => 'SELECT DISTINCT banner INTO :pc_version' .
+ ' FROM v$version WHERE banner LIKE \'Oracle%\'' },
+ 'librarycache' => {
+ insts_handle => undef, fetch_handle => undef, values => {},
+ insts => 'SELECT namespace FROM v$librarycache',
+ fetch => 'SELECT namespace, gets, gethits, gethitratio, pins,' .
+ ' pinhits, pinhitratio, reloads, invalidations' .
+ ' FROM v$librarycache' },
+);
+
+my %tables_by_cluster = (
+ '0' => {
+ name => 'sysstat',
+ setup => \&setup_sysstat,
+ indom => $sid_indom,
+ values => \&sysstat_values },
+ '1' => {
+ name => 'license',
+ setup => \&setup_license,
+ indom => $sid_indom,
+ values => \&license_values },
+ '2' => {
+ name => 'latch',
+ setup => \&setup_latch,
+ indom => $latch_indom,
+ values => \&latch_values },
+ '3' => {
+ name => 'filestat',
+ setup => \&setup_filestat,
+ indom => $file_indom,
+ values => \&filestat_values },
+ '4' => {
+ name => 'rollstat',
+ setup => \&setup_rollstat,
+ indom => $rollback_indom,
+ values => \&rollstat_values },
+ '5' => {
+ name => 'reqdist',
+ setup => \&setup_reqdist,
+ indom => $reqdist_indom,
+ values => \&reqdist_values },
+ '6' => {
+ name => 'backup',
+ setup => \&setup_backup,
+ indom => 'backup',
+ values => \&backup_values },
+ '7' => {
+ name => 'rowcache',
+ setup => \&setup_rowcache,
+ indom => $rowcache_indom,
+ values => \&rowcache_values },
+# '8' => {
+# name => 'sesstat',
+# setup => \&setup_sesstat,
+# indom => $session_indom,
+# values => \&sesstat_values },
+ '9' => {
+ name => 'object_cache',
+ setup => \&setup_object_cache,
+ indom => $object_cache_indom,
+ values => \&object_cache_values },
+ '10' => {
+ name => 'system_event',
+ setup => \&setup_system_event,
+ indom => $system_event_indom,
+ insts => \&system_event_insts,
+ values => \&system_event_values },
+ '11' => {
+ name => 'version',
+ setup => \&setup_version,
+ indom => $sid_indom,
+ values => \&version_values },
+ '12' => {
+ name => 'librarycache',
+ setup => \&setup_librarycache,
+ indom => $librarycache_indom,
+ values => \&librarycache_values },
+ '13' => {
+ name => 'waitstat',
+ setup => \&setup_waitstat,
+ indom => $waitstat_indom,
+ values => \&waitstat_values },
+);
+
+sub oracle_connection_setup
+{
+ foreach my $sid (@sids) {
+ my $db = $sids_by_name{$sid}{db_handle};
+ $db = oracle_sid_connection_setup($sid, $db);
+ $sids_by_name{$sid}{db_handle} = $db;
+ }
+}
+
+sub oracle_sid_connection_setup
+{
+ my $sid = shift;
+ my $dbh = shift;
+
+ if (!defined($dbh)) {
+ $dbh = DBI->connect("dbi:Oracle:$sid", $username, $password);
+ if (defined($dbh)) {
+ $pmda->log("Oracle connection established\n");
+
+ foreach my $key (keys %tables_by_name) {
+ my ( $query, $insts_query, $fetch_query );
+
+ $insts_query = $tables_by_name{$key}{insts};
+ $fetch_query = $tables_by_name{$key}{fetch};
+
+ if (defined($insts_query)) {
+ $query = $dbh->prepare($fetch_query);
+ $tables_by_name{$key}{insts_handle} = $query
+ unless (!defined($query));
+ }
+ if (defined($fetch_query)) {
+ $query = $dbh->prepare($fetch_query);
+ $tables_by_name{$key}{fetch_handle} = $query
+ unless (!defined($query));
+ }
+ }
+ }
+ }
+ return $dbh;
+}
+
+sub oracle_refresh
+{
+ my ($cluster) = @_;
+
+ foreach my $sid (@sids) {
+ my $db = $sids_by_name{$sid}{db_handle};
+ my $name = $tables_by_cluster{"$cluster"}{name};
+ my $refresh = $tables_by_cluster{"$cluster"}{values};
+ &$refresh($db, $sid, $tables_by_name{$name}{fetch_handle});
+ }
+}
+
+sub oracle_fetch_callback
+{
+ my ( $cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ( $indom, $table, $value, $valueref, @columns );
+
+ return (PM_ERR_PMID, 0) unless defined($metric_name);
+ $table = $metric_name;
+ $table =~ s/^oracle\.//;
+ $table =~ s/\.*$//;
+
+ # $pmda->log("fetch_cb $metric_name $cluster:$item ($inst) - $table\n");
+ $indom = $tables_by_cluster{"$cluster"}{indom};
+ $valueref = pmda_inst_lookup($indom, $inst);
+ return (PM_ERR_INST, 0) unless defined($valueref);
+ @columns = @$valueref;
+ $value = $columns[$item];
+ return (PM_ERR_APPVERSION, 0) unless defined($value);
+
+ return ($value, 1);
+}
+
+#
+# Refresh routines - one per table (cluster) - format database query
+# result set for later use by the generic fetch callback routine.
+#
+sub refresh_results
+{
+ my ( $dbh, $handle ) = @_;
+
+ return undef
+ unless (defined($dbh) && defined($handle) && defined($handle->execute()));
+ return $handle->fetchall_arrayref();
+}
+
+sub system_event_values
+{
+ my ( $dbh, $sid, $handle ) = @_;
+ my $result = refresh_results($dbh, $handle);
+
+ %system_event_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $eventid = $result->[$i][0];
+ my $eventname = $result->[$i][1];
+ my $instname = "$sid/$eventid $eventname";
+ my @values = @$result[$i];
+ $system_event_instances{$instname} = \@values;
+ }
+ }
+ $pmda->replace_indom($system_event_indom, \%system_event_instances);
+}
+
+sub system_event_insts
+{
+ my ( $dbh, $sid, $handle ) = @_;
+ my $result = refresh_results($dbh, $handle);
+
+ %system_event_instances = ();
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $eventid = $result->[$i][0];
+ my $eventname = $result->[$i][1];
+ my $instname = "$sid/$eventid $eventname";
+ $system_event_instances{$instname} = \@novalues;
+ }
+ }
+ $pmda->replace_indom($system_event_indom, \%system_event_instances);
+}
+
+sub license_values { }
+sub license_insts { }
+
+
+
+sub oracle_indoms_setup
+{
+ $pmda->add_indom($latch_indom, \%latch_instances,
+ 'Instance domain "latch" from Oracle PMDA',
+'The latches used by the RDBMS. The latch instance domain does not
+change. Latches are simple, low-level serialization mechanisms which
+protect access to structures in the system global area (SGA).');
+
+ $pmda->add_indom($file_indom, \%file_instances,
+ 'Instance domain "file" from Oracle PMDA',
+'The collection of data files that make up the database. This instance
+domain may change during database operation as files are added to or
+removed.');
+
+ $pmda->add_indom($rollback_indom, \%rollback_instances,
+ 'Instance domain "rollback" from Oracle PMDA',
+'The collection of rollback segments for the database. This instance
+domain may change during database operation as segments are added to or
+removed.');
+
+ $pmda->add_indom($reqdist_indom, \%reqdist_instances,
+ 'RDBMS Request Distribution from Oracle PMDA',
+'Each instance is one of the buckets in the histogram of RDBMS request
+service times. The instances are named according to the longest
+service time that will be inserted into its bucket. The instance
+domain does not change.');
+
+ $pmda->add_indom($rowcache_indom, \%rowcache_instances,
+ 'Instance domain "rowcache" from Oracle PMDA',
+'Each instance is a type of data dictionary cache. The names are
+derived from the database parameters that define the number of entries
+in the particular cache. In some cases subordinate caches exist.
+Names for such sub-caches are composed of the subordinate cache
+parameter name prefixed with parent cache name with a "." as a
+separator. Each cache has an identifying number which appears in
+parentheses after the textual portion of the cache name to resolve
+naming ambiguities. The rowcache instance domain does not change.');
+
+ $pmda->add_indom($session_indom, \%session_instances,
+ 'Instance domain "session" from Oracle PMDA',
+'Each instance is a session to the Oracle database. Sessions may come
+and go rapidly. The instance names correspond to the numeric Oracle
+session identifiers.');
+
+ $pmda->add_indom($object_cache_indom, \%object_cache_instances,
+ 'Instance domain "cache objects" from Oracle PMDA',
+'The various types of objects in the database object cache. This
+includes such objects as indices, tables, procedures, packages, users
+and dblink. Any object types not recognized by the Oracle PMDA are
+grouped together into a special instance named "other". The instance
+domain may change as various types of objects are bought into and
+flushed out of the database object cache.');
+
+ $pmda->add_indom($system_event_indom, \%system_event_instances,
+ 'Instance domain "system events" from Oracle PMDA',
+'The various system events which the database may wait on. This
+includes events such as interprocess communication, control file I/O,
+log file I/O, timers.');
+
+ $pmda->add_indom($librarycache_indom, \%librarycache_instances,
+ 'Instance domain "librarycache" from Oracle PMDA', '');
+ $pmda->add_indom($waitstat_indom, \%waitstat_instances,
+ 'Instance domain "wait statistics" from Oracle PMDA', '');
+
+ $pmda->add_indom($sid_indom, \%sid_instances,
+ 'Instance domain "SID" from Oracle PMDA',
+'The system identifiers used by the RDBMS and monitored by this PMDA.');
+}
+
+sub oracle_metrics_setup
+{
+ foreach my $cluster (sort (keys %tables_by_cluster)) {
+ my $setup = $tables_by_cluster{"$cluster"}{setup};
+ my $indom = $tables_by_cluster{"$cluster"}{indom};
+ &$setup($cluster, $indom, $tables_by_cluster{"$cluster"}{params});
+ }
+}
+
+#
+# Setup routines - one per cluster, add metrics to PMDA
+#
+
+sub setup_waitstat # block contention stats from v$waitstat
+{
+ $pmda->add_metric(pmda_pmid(13,0), PM_TYPE_U32, $waitstat_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.waitstat.count',
+ 'Number of waits for each block class',
+'The number of waits for each class of block. This value is obtained
+from the COUNT column of the V$WAITSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(13,1), PM_TYPE_U32, $waitstat_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.waitstat.time',
+ 'Sum of all wait times for each block class',
+'The sum of all wait times for each block class. This value is obtained
+from the TIME column of the V$WAITSTAT view.');
+}
+
+sub setup_version # version data from the v$version view
+{
+ $pmda->add_metric(pmda_pmid(11,0), PM_TYPE_STRING, $sid_indom,
+ PM_SEM_DISCRETE, pmda_units(0,0,0,0,0,0),
+ 'oracle.version',
+ 'Oracle component name and version number', '');
+}
+
+sub setup_system_event # statistics from v$system_event
+{
+ $pmda->add_metric(pmda_pmid(10,0), PM_TYPE_U32, $system_event_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.event.waits',
+ 'Number of waits for various system events',
+'The total number of waits for various system events. This value is
+obtained from the TOTAL_WAITS column of the V$SYSTEM_EVENT view.');
+
+ $pmda->add_metric(pmda_pmid(10,1), PM_TYPE_U32, $system_event_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.event.timeouts',
+ 'Number of timeouts for various system events',
+'The total number of timeouts for various system events. This value is
+obtained from the TOTAL_TIMEOUTS column of the V$SYSTEM_EVENT view.');
+
+ $pmda->add_metric(pmda_pmid(10,2), PM_TYPE_U32, $system_event_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.event.time_waited',
+ 'Total time waited for various system events',
+'The total amount of time waited for various system events. This value
+is obtained from the TIME_WAITED column of the V$SYSTEM_EVENT view and
+converted to units of milliseconds.');
+
+ $pmda->add_metric(pmda_pmid(10,3), PM_TYPE_FLOAT, $system_event_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.event.average_wait',
+ 'Average time waited for various system events',
+'The average time waited for various system events. This value is
+obtained from the AVERAGE_WAIT column of the V$SYSTEM_EVENT view
+and converted to units of milliseconds.');
+}
+
+sub setup_sysstat ## statistics from v$sysstat
+{
+ $pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.logons', 'Total cumulative logons',
+'The "logons cumulative" statistic from the V$SYSSTAT view. This is the
+total number of logons since the instance started.');
+
+ $pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.curlogons', 'Total current logons',
+'The "logons current" statistic from the V$SYSSTAT view. This is the
+total number of current logons.');
+
+ $pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.opencursors', 'Total cumulative opened cursors',
+'The "opened cursors cumulative" statistic from the V$SYSSTAT view.
+This is the total number of cursors opened since the instance started.');
+
+ $pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.current_cursors', 'Total current open cursors',
+'The "opened cursors current" statistic from the V$SYSSTAT view. This
+is the total number of current open cursors.');
+
+ $pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.user_commits', 'Total user commits',
+'The "user commits" statistic from the V$SYSSTAT view. When a user
+commits a transaction, the redo generated that reflects the changes
+made to database blocks must be written to disk. Commits often
+represent the closest thing to a user transaction rate.');
+
+ $pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.user_rollbacks', 'Total user rollbacks',
+'The "user rollbacks" statistic from the V$SYSSTAT view. This statistic
+stores the number of times users manually issue the ROLLBACK statement
+or an error occurs during users\' transactions.');
+
+ $pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.user_calls', 'Total user calls',
+'The "user calls" statistic from the V$SYSSTAT view. Oracle allocates
+resources (Call State Objects) to keep track of relevant user call data
+structures every time you log in, parse or execute. When determining
+activity, the ratio of user calls to RPI calls, gives you an indication
+of how much internal work gets generated as a result of the type of
+requests the user is sending to Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.recursecalls', 'Total recursive calls',
+'The "recursive calls" statistic from the V$SYSSTAT view. Oracle
+maintains tables used for internal processing. When Oracle needs to
+make a change to these tables, it internally generates an SQL
+statement. These internal SQL statements generate recursive calls.');
+
+ $pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.recursecpu', 'Total recursive cpu usage',
+'The "recursive cpu usage" statistic from the V$SYSSTAT view. The total
+CPU time used by non-user calls (recursive calls). Subtract this value
+from oracle.sysstat.sessioncpu to determine how much CPU time was used
+by the user calls. Units are milliseconds of CPU time.');
+
+ $pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.session.lreads', 'Total session logical reads',
+'The "session logical reads" statistic from the V$SYSSTAT view. This
+statistic is basically the sum of oracle.systat.dbbgets and
+oracle.sysstat.consgets. Refer to the help text for these
+individual metrics for more information.');
+
+ $pmda->add_metric(pmda_pmid(0,10), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.session.procspace',
+'Total session stored procedure space',
+'The "session stored procedure space" statistic from the V$SYSSTAT
+view. This metric shows the amount of memory that this session is
+using for stored procedures.');
+
+ $pmda->add_metric(pmda_pmid(0,11), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.cpucall', 'CPU used when call started',
+'The "CPU used when call started" statistic from the V$SYSSTAT view.
+This is the session CPU when current call started. Units are
+milliseconds of CPU time.');
+
+ $pmda->add_metric(pmda_pmid(0,12), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.session.cpu', 'Total CPU used by this session',
+'The "CPU used by this session" statistic from the V$SYSSTAT view. This
+is the amount of CPU time used by a session between when a user call
+started and ended. Units for the exported metric are milliseconds, but
+Oracle uses an internal resolution of tens of milliseconds and some
+user calls can complete within 10 milliseconds, resulting in the start
+and end user-call times being the same. In this case, zero
+milliseconds are added to the statistic.');
+
+ $pmda->add_metric(pmda_pmid(0,13), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'oracle.sysstat.session.contime', 'Session connect time',
+'The "session connect time" statistic from the V$SYSSTAT view.
+Wall clock time of when session logon occured. Units are seconds
+since the epoch.');
+
+ $pmda->add_metric(pmda_pmid(0,14), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'oracle.sysstat.procidle', 'Total process last non-idle time',
+'The "process last non-idle time" statistic from the V$SYSSTAT view.
+This is the last time this process was not idle. Units are seconds
+since the epoch.');
+
+ $pmda->add_metric(pmda_pmid(0,15), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.session.mem', 'Session UGA memory',
+'The "session UGA memory" statistic from the V$SYSSTAT view. This
+shows the current session UGA (User Global Area) memory size.');
+
+ $pmda->add_metric(pmda_pmid(0,16), PM_TYPE_U32, $sid_indom,
+ PM_SEM_DISCRETE, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.session.maxmem', 'Maximum session UGA memory',
+'The "session UGA memory max" statistic from the V$SYSSTAT view. This
+shows the maximum session UGA (User Global Area) memory size.');
+
+ $pmda->add_metric(pmda_pmid(0,17), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.msgxmit', 'Total messages sent',
+'The "messages sent" statistic from the V$SYSSTAT view. This is the
+total number of messages sent between Oracle processes. A message is
+sent when one Oracle process wants to post another to perform some
+action.');
+
+ $pmda->add_metric(pmda_pmid(0,18), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.msgrecv', 'Total messages received',
+'The "messages received" statistic from the V$SYSSTAT view. This is the
+total number of messages received. A message is sent when one Oracle
+process wants to post another to perform some action.');
+
+ $pmda->add_metric(pmda_pmid(0,19), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.bgtimeouts', 'Total background timeouts',
+'The "background timeouts" statistic from the V$SYSSTAT view. This is
+a count of the times where a background process has set an alarm for
+itself and the alarm has timed out rather than the background process
+being posted by another process to do some work.');
+
+ $pmda->add_metric(pmda_pmid(0,20), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.sepgamem', 'Session PGA memory',
+'The "session PGA memory" statistic from the V$SYSSTAT view. This
+shows the current session PGA (Process Global Area) memory size.');
+
+ $pmda->add_metric(pmda_pmid(0,21), PM_TYPE_U32, $sid_indom,
+ PM_SEM_DISCRETE, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.session.maxpgamem', 'Maximum session PGA memory',
+'The "session PGA memory max" statistic from the V$SYSSTAT view. This
+shows the maximum session PGA (Process Global Area) memory size.');
+
+ $pmda->add_metric(pmda_pmid(0,22), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.timeouts', 'Total enqueue timeouts',
+'The "enqueue timeouts" statistic from the V$SYSSTAT view. This is
+the total number of enqueue operations (get and convert) that timed
+out before they could complete.');
+
+ $pmda->add_metric(pmda_pmid(0,23), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.waits', 'Total enqueue waits',
+'The "enqueue waits" statistic from the V$SYSSTAT view. This is the
+total number of waits that happened during an enqueue convert or get
+because the enqueue could not be immediately granted.');
+
+ $pmda->add_metric(pmda_pmid(0,24), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.deadlocks', 'Total enqueue deadlocks',
+'The "enqueue deadlocks" statistic from the V$SYSSTAT view. This is
+the total number of enqueue deadlocks between different sessions.');
+
+ $pmda->add_metric(pmda_pmid(0,25), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.requests', 'Total enqueue requests',
+'The "enqueue requests" statistic from the V$SYSSTAT view. This is
+the total number of enqueue gets.');
+
+ $pmda->add_metric(pmda_pmid(0,26), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.conversions', 'Total enqueue conversions',
+'The "enqueue conversions" statistic from the V$SYSSTAT view. This is
+the total number of enqueue converts.');
+
+ $pmda->add_metric(pmda_pmid(0,27), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.enqueue.releases', 'Total enqueue releases',
+'The "enqueue releases" statistic from the V$SYSSTAT view. This is
+the total number of enqueue releases.');
+
+ $pmda->add_metric(pmda_pmid(0,28), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.gets', 'Total global lock gets (sync)',
+'The "global lock gets (sync)" statistic from the V$SYSSTAT view. This
+is the total number of synchronous global lock gets.');
+
+ $pmda->add_metric(pmda_pmid(0,29), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.agets', 'Total global lock gets (async)',
+'The "global lock gets (async)" statistic from the V$SYSSTAT view.
+This is the total number of asynchronous global lock gets.');
+
+ $pmda->add_metric(pmda_pmid(0,30), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.globlock.gettime', 'Total global lock get time',
+'The "global lock get time" statistic from the V$SYSSTAT view. This is
+the total elapsed time of all synchronous global lock gets.');
+
+ $pmda->add_metric(pmda_pmid(0,31), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.cvts', 'Total global lock converts (sync)',
+'The "global lock converts (sync)" statistic from the V$SYSSTAT view.
+This is the total number of synchronous global lock converts.');
+
+ $pmda->add_metric(pmda_pmid(0,32), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.acvts', 'Total global lock converts (async)',
+'The "global lock converts (async)" statistic from the V$SYSSTAT view.
+This is the total number of asynchronous global lock converts.');
+
+ $pmda->add_metric(pmda_pmid(0,33), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.globlock.cvttime', 'Total global lock convert time',
+'The "global lock convert time" statistic from the V$SYSSTAT view.
+This is the total elapsed time of all synchronous global lock converts.');
+
+ $pmda->add_metric(pmda_pmid(0,34), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.rels', 'Total global lock releases (sync)',
+'The "global lock releases (sync)" statistic from the V$SYSSTAT view.
+This is the total number of synchronous global lock releases.');
+
+ $pmda->add_metric(pmda_pmid(0,35), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globlock.arels', 'Total global lock releases (async)',
+'The "global lock releases (async)" statistic from the V$SYSSTAT view.
+This is the total number of asynchronous global lock releases.');
+
+ $pmda->add_metric(pmda_pmid(0,36), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.globlock.reltime', 'Total global lock release time',
+'The "global lock release time" statistic from the V$SYSSTAT view.
+This is the elapsed time of all synchronous global lock releases.');
+
+ $pmda->add_metric(pmda_pmid(0,37), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbbgets', 'Total db block gets',
+'The "db block gets" statistic from the V$SYSSTAT view. This tracks
+the number of blocks obtained in CURRENT mode.');
+
+ $pmda->add_metric(pmda_pmid(0,38), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consgets', 'Total consistent gets',
+'The "consistent gets" statistic from the V$SYSSTAT view. This is the
+number of times a consistent read was requested for a block. Also see
+the help text for oracle.sysstat.conschanges.');
+
+ $pmda->add_metric(pmda_pmid(0,39), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.preads', 'Total physical reads',
+'The "physical reads" statistic from the V$SYSSTAT view. This is the
+number of I/O requests to the operating system to retrieve a database
+block from the disk subsystem. This is a buffer cache miss.
+Logical reads = oracle.sysstat.consgets + oracle.sysstat.dbbgets.
+Logical reads and physical reads are used to calculate the buffer hit
+ratio.');
+
+ $pmda->add_metric(pmda_pmid(0,40), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.pwrites', 'Total physical writes',
+'The "physical writes" statistic from the V$SYSSTAT view. This is the
+number of I/O requests to the operating system to write a database
+block to the disk subsystem. The bulk of the writes are performed
+either by DBWR or LGWR.');
+
+ $pmda->add_metric(pmda_pmid(0,41), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.wreqs', 'Total write requests',
+'The "write requests" statistic from the V$SYSSTAT view. This is the
+number of times DBWR has flushed sets of dirty buffers to disk.');
+
+ $pmda->add_metric(pmda_pmid(0,42), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'oracle.sysstat.dirtyqlen', 'Total summed dirty queue length',
+'The "summed dirty queue length" statistic from the V$SYSSTAT view.
+This is the sum of the dirty LRU queue length after every write
+request.
+Divide by the write requests (oracle.sysstat.wreqs) to get the
+average queue length after write completion. For more information see
+the help text associated with oracle.sysstat.wreqs.');
+
+ $pmda->add_metric(pmda_pmid(0,43), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbbchanges', 'Total db block changes',
+'The "db block changes" statistic from the V$SYSSTAT view. This metric
+is closely related to "consistent changes"
+(oracle.sysstat.conschanges) and counts the total number of
+changes made to all blocks in the SGA that were part of an update or
+delete operation. These are the changes that are generating redo log
+entries and hence will be permanent changes to the database if the
+transaction is committed.
+This metric is a rough indication of total database work and indicates
+(possibly on a per-transaction level) the rate at which buffers are
+being dirtied.');
+
+ $pmda->add_metric(pmda_pmid(0,44), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.chwrtime', 'Total change write time',
+'The "change write time" statistic from the V$SYSSTAT view. This is
+the elapsed time for redo write for changes made to CURRENT blocks.');
+
+ $pmda->add_metric(pmda_pmid(0,45), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.conschanges', 'Total consistent changes',
+'The "consistent changes" statistic from the V$SYSSTAT view. This is
+the number of times a database block has applied rollback entries to
+perform a consistent read on the block.');
+
+ $pmda->add_metric(pmda_pmid(0,46), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.syncwr', 'Total redo sync writes',
+'The "redo sync writes" statistic from the V$SYSSTAT view. Usually,
+redo that is generated and copied into the log buffer need not be
+flushed out to disk immediately. The log buffer is a circular buffer
+that LGWR periodically flushes. This metric is incremented when
+changes being applied must be written out to disk due to commit.');
+
+ $pmda->add_metric(pmda_pmid(0,47), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.redo.synctime', 'Total redo sync time',
+'The "redo sync time" statistic from the V$SYSSTAT view. This is the
+elapsed time of all redo sync writes (oracle.sysstat.redo.syncwr).');
+
+$pmda->add_metric(pmda_pmid(0,48), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.exdeadlocks', 'Total exchange deadlocks',
+'The "exchange deadlocks" statistic from the V$SYSSTAT view. This is
+the number of times that a process detected a potential deadlock when
+exchanging two buffers and raised an internal, restartable error.
+Index scans are currently the only operations which perform exchanges.');
+
+$pmda->add_metric(pmda_pmid(0,49), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.freereqs', 'Total free buffer requested',
+'The "free buffer requested" statistic from the V$SYSSTAT view. This is
+the number of times a reusable buffer or a free buffer was requested to
+create or load a block.');
+
+ $pmda->add_metric(pmda_pmid(0,50), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.dirtyinsp', 'Total dirty buffers inspected',
+'The "dirty buffers inspected" statistic from the V$SYSSTAT view.
+This is the number of dirty buffers found by the foreground while
+the foreground is looking for a buffer to reuse.');
+
+ $pmda->add_metric(pmda_pmid(0,51), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.freeinsp', 'Total free buffer inspected',
+'The "free buffer inspected" statistic from the V$SYSSTAT view. This is
+the number of buffers skipped over from the end of an LRU queue in
+order to find a reusable buffer. The difference between this metric
+and the oracle.sysstat.buffer.dirtyinsp metric is the number of
+buffers that could not be used because they were either busy, needed to
+be written after rapid aging out, or they have a user, a waiter, or are
+being read/written. Refer to the oracle.sysstat.buffer.dirtyinsp
+help text also.');
+
+ $pmda->add_metric(pmda_pmid(0,52), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.timeouts', 'Total DBWR timeouts',
+'The "DBWR timeouts" statistic from the V$SYSSTAT view. This is the
+number of times that the DBWR has been idle since the last timeout.
+These are the times that the DBWR looked for buffers to idle write.');
+
+ $pmda->add_metric(pmda_pmid(0,53), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.mkfreereqs', 'Total DBWR make free requests',
+'The "DBWR make free requests" statistic from the V$SYSSTAT view.
+This is the number of messages received requesting DBWR to make
+some more free buffers for the LRU.');
+
+ $pmda->add_metric(pmda_pmid(0,54), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.freebuffnd', 'Total DBWR free buffers found',
+'The "DBWR free buffers found" statistic from the V$SYSSTAT view.
+This is the number of buffers that DBWR found to be clean when it
+was requested to make free buffers. Divide this by
+oracle.sysstat.dbwr.mkfreereqs to find the average number of
+reusable buffers at the end of each LRU.');
+
+ $pmda->add_metric(pmda_pmid(0,55), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.lruscans', 'Total DBWR lru scans',
+'The "DBWR lru scans" statistic from the V$SYSSTAT view. This is the
+number of times that DBWR does a scan of the LRU queue looking for
+buffers to write. This includes times when the scan is to fill a batch
+being written for another purpose such as a checkpoint. This metric\'s
+value is always greater than oracle.sysstat.dbwr.mkfreereqs.');
+
+ $pmda->add_metric(pmda_pmid(0,56), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.sumscandepth', 'Total DBWR summed scan depth',
+'The "DBWR summed scan depth" statistic from the V$SYSSTAT view. The
+current scan depth (number of buffers scanned by DBWR) is added to this
+metric every time DBWR scans the LRU for dirty buffers. Divide by
+oracle.sysstat.dbwr.lruscans to find the average scan depth.');
+
+ $pmda->add_metric(pmda_pmid(0,57), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.bufsscanned', 'Total DBWR buffers scanned',
+'The "DBWR buffers scanned" statistic from the V$SYSSTAT view.
+This is the total number of buffers looked at when scanning each
+LRU set for dirty buffers to clean. This count includes both dirty
+and clean buffers. Divide by oracle.sysstat.dbwr.lruscans to
+find the average number of buffers scanned.');
+
+ $pmda->add_metric(pmda_pmid(0,58), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.checkpoints', 'Total DBWR checkpoints',
+'The "DBWR checkpoints" statistic from the V$SYSSTAT view.
+This is the number of times the DBWR was asked to scan the cache
+and write all blocks marked for a checkpoint.');
+
+ $pmda->add_metric(pmda_pmid(0,59), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.xinstwrites', 'Total DBWR cross instance writes',
+'The "DBWR cross instance writes" statistic from the V$SYSSTAT view.
+This is the total number of blocks written for other instances so that
+they can access the buffers.');
+
+ $pmda->add_metric(pmda_pmid(0,60), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.remote.instundowr',
+ 'Total remote instance undo writes',
+'The "remote instance undo writes" statistic from the V$SYSSTAT view.
+This is the number of times this instance performed a dirty undo write
+so that another instance could read that data.');
+
+ $pmda->add_metric(pmda_pmid(0,61), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.remote.instundoreq',
+ 'Total remote instance undo requests',
+'The "remote instance undo requests" statistic from the V$SYSSTAT view.
+This is the number of times this instance requested undo from another
+instance so it could be read CR.');
+
+ $pmda->add_metric(pmda_pmid(0,62), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.xinstcrrd', 'Total cross instance CR read',
+'The "cross instance CR read" statistic from the V$SYSSTAT view. This
+is the number of times this instance made a cross instance call to
+write a particular block due to timeout on an instance lock get. The
+call allowed the blocks to be read CR rather than CURRENT.');
+
+ $pmda->add_metric(pmda_pmid(0,63), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmg.cscalls', 'Total calls to kcmgcs',
+'The "calls to kcmgcs" statistic from the V$SYSSTAT view. This is the
+total number of calls to get the current System Commit Number (SCN).');
+
+$pmda->add_metric(pmda_pmid(0,64), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmg.rscalls', 'Total calls to kcmgrs',
+'The "calls to kcmgrs" statistic from the V$SYSSTAT view. This is the
+total number of calls to get a recent System Commit Number (SCN).');
+
+ $pmda->add_metric(pmda_pmid(0,65), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmg.ascalls', 'Total calls to kcmgas',
+'The "calls to kcmgas" statistic from the V$SYSSTAT view. This is the
+total number of calls that Get and Advance the System Commit Number
+(SCN). Also used when getting a Batch of SCN numbers.');
+
+ $pmda->add_metric(pmda_pmid(0,66), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.nodlmscnsgets',
+ 'Total next scns gotten without going to DLM',
+'The "next scns gotten without going to DLM" statistic from the
+V$SYSSTAT view. This is the number of SCNs (System Commit Numbers)
+obtained without going to the DLM (Distributed Lock Manager).');
+
+ $pmda->add_metric(pmda_pmid(0,67), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.entries', 'Total redo entries',
+'The "redo entries" statistic from the V$SYSSTAT view. This metric
+is incremented each time redo entries are copied into the redo log
+buffer.');
+
+ $pmda->add_metric(pmda_pmid(0,68), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.redo.size', 'Total redo size',
+'The "redo size" statistic from the V$SYSSTAT view.
+This is the number of bytes of redo generated.');
+
+ $pmda->add_metric(pmda_pmid(0,69), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.entslin', 'Total redo entries linearized',
+'The "redo entries linearized" statistic from the V$SYSSTAT view. This
+is the number of entries of size <= REDO_ENTRY_PREBUILD_THRESHOLD.
+Building these entries increases CPU time but may increase concurrency.');
+
+ $pmda->add_metric(pmda_pmid(0,70), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.bufallret',
+ 'Total redo buffer allocation retries',
+'The "redo buffer allocation retries" statistic from the V$SYSSTAT
+view. This is the total number of retries necessary to allocate space
+in the redo buffer. Retries are needed because either the redo writer
+has gotten behind, or because an event (such as log switch) is
+occuring.');
+
+ $pmda->add_metric(pmda_pmid(0,71), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.smallcpys', 'Total redo small copies',
+'The "redo small copies" statistic from the V$SYSSTAT view. This is the
+total number of entries where size <= LOG_SMALL_ENTRY_MAX_SIZE. These
+entries are copied using the protection of the allocation latch,
+eliminating the overhead of getting the copy latch. This is generally
+only useful for multi-processor systems.');
+
+ $pmda->add_metric(pmda_pmid(0,72), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.redo.wastage', 'Total redo wastage',
+'The "redo wastage" statistic from the V$SYSSTAT view. This is the
+number of bytes wasted because redo blocks needed to be written before
+they are completely full. Early writing may be needed to commit
+transactions, to be able to write a database buffer or to switch logs.');
+
+ $pmda->add_metric(pmda_pmid(0,73), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.redo.wrlatchtime', 'Total redo writer latching time',
+'The "redo writer latching time" statistic from the V$SYSSTAT view.
+This is the elapsed time needed by LGWR to obtain and release each copy
+latch. This is only used if the LOG_SIMULTANEOUS_COPIES initialization
+parameter is greater than zero.');
+
+ $pmda->add_metric(pmda_pmid(0,74), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.writes', 'Total redo writes',
+'The "redo writes" statistic from the V$SYSSTAT view.
+This is the total number of writes by LGWR to the redo log files.');
+
+ $pmda->add_metric(pmda_pmid(0,75), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.bwrites', 'Total redo blocks written',
+'The "redo blocks written" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,76), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.redo.wrtime', 'Total redo write time',
+'The "redo write time" statistic from the V$SYSSTAT view. This is the
+total elapsed time of the write from the redo log buffer to the current
+redo log file.');
+
+ $pmda->add_metric(pmda_pmid(0,77), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.logspreqs', 'Total redo log space requests',
+'The "redo log space requests" statistic from the V$SYSSTAT view. The
+active log file is full and Oracle is waiting for disk space to be
+allocated for the redo log entries. Space is created by performing a
+log switch.
+Small log files in relation to the size of the SGA or the commit rate
+of the work load can cause problems. When the log switch occurs,
+Oracle must ensure that all committed dirty buffers are written to disk
+before switching to a new log file. If you have a large SGA full of
+dirty buffers and small redo log files, a log switch must wait for DBWR
+to write dirty buffers to disk before continuing.');
+
+ $pmda->add_metric(pmda_pmid(0,78), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.redo.logspwaittime', 'Total redo log space wait time',
+'The "redo log space wait time" statistic from the V$SYSSTAT view. This
+is the total elapsed time spent waiting for redo log space requests
+(refer to the oracle.sysstat.redo.logspreqs metric).');
+
+ $pmda->add_metric(pmda_pmid(0,79), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.logswintrs', 'Total redo log switch interrupts',
+'The "redo log switch interrupts" statistic from the V$SYSSTAT view.
+This is the number of times that another instance asked this instance
+to advance to the next log file.');
+
+ $pmda->add_metric(pmda_pmid(0,80), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.redo.ordermarks', 'Total redo ordering marks',
+'The "redo ordering marks" statistic from the V$SYSSTAT view. This is
+the number of times that an SCN (System Commit Number) had to be
+allocated to force a redo record to have a higher SCN than a record
+generated in another thread using the same block.');
+
+ $pmda->add_metric(pmda_pmid(0,81), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.hashlwgets', 'Total hash latch wait gets',
+'The "hash latch wait gets" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,82), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.bgchkpts.started',
+ 'Total background checkpoints started',
+'The "background checkpoints started" statistic from the V$SYSSTAT
+view. This is the number of checkpoints started by the background. It
+can be larger than the number completed if a new checkpoint overrides
+an incomplete checkpoint. This only includes checkpoints of the
+thread, not individual file checkpoints for operations such as offline
+or begin backup. This statistic does not include the checkpoints
+performed in the foreground, such as ALTER SYSTEM CHECKPOINT LOCAL.');
+
+ $pmda->add_metric(pmda_pmid(0,83), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.bgchkpts.completed',
+ 'Total background checkpoints completed',
+'The "background checkpoints completed" statistic from the V$SYSSTAT
+view. This is the number of checkpoints completed by the background.
+This statistic is incremented when the background successfully advances
+the thread checkpoint.');
+
+ $pmda->add_metric(pmda_pmid(0,84), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.tranlock.fgreqs',
+ 'Total transaction lock foreground requests',
+'The "transaction lock foreground requests" statistic from the V$SYSSTAT
+view. For parallel server this is incremented on each call to ktugil()
+"Kernel Transaction Get Instance Lock". For single instance this has
+no meaning.');
+
+ $pmda->add_metric(pmda_pmid(0,85), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.tranlock.fgwaittime',
+ 'Total transaction lock foreground wait time',
+'The "transaction lock foreground wait time" statistic from the
+V$SYSSTAT view. This is the total time spent waiting for a transaction
+instance lock.');
+
+ $pmda->add_metric(pmda_pmid(0,86), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.tranlock.bggets',
+ 'Total transaction lock background gets',
+'The "transaction lock background gets" statistic from the V$SYSSTAT
+view. For parallel server this is incremented on each call to ktuglb()
+"Kernel Transaction Get lock in Background".');
+
+ $pmda->add_metric(pmda_pmid(0,87), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.tranlock.bggettime',
+ 'Total transaction lock background get time',
+'The "transaction lock background get time" statistic from the V$SYSSTAT
+view. Total time spent waiting for a transaction instance lock in
+Background.');
+
+ $pmda->add_metric(pmda_pmid(0,88), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.shortscans', 'Total table scans (short tables)',
+'The "table scans (short tables)" statistic from the V$SYSSTAT view.
+Long (or conversely short) tables can be defined by optimizer hints
+coming down into the row source access layer of Oracle. The table must
+have the CACHE option set.');
+
+ $pmda->add_metric(pmda_pmid(0,89), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.longscans', 'Total table scans (long tables)',
+'The "table scans (long tables)" statistic from the V$SYSSTAT view.
+Long (or conversely short) tables can be defined as tables that do not
+meet the short table criteria described in the help text for the
+oracle.sysstat.table.shortscans metric.');
+
+ $pmda->add_metric(pmda_pmid(0,90), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.scanrows', 'Total table scan rows gotten',
+'The "table scan rows gotten" statistic from the V$SYSSTAT view. This
+is collected during a scan operation, but instead of counting the
+number of database blocks (see oracle.sysstat.table.scanblocks),
+it counts the rows being processed.');
+
+ $pmda->add_metric(pmda_pmid(0,91), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.scanblocks', 'Total table scan blocks gotten',
+'The "table scan blocks gotten" statistic from the V$SYSSTAT view.
+During scanning operations, each row is retrieved sequentially by
+Oracle. This metric is incremented for each block encountered during
+the scan.
+This informs you of the number of database blocks that you had to get
+from the buffer cache for the purpose of scanning. Compare the value
+of this parameter to the value of oracle.sysstat.consgets
+(consistent gets) to get a feel for how much of the consistent read
+activity can be attributed to scanning.');
+
+ $pmda->add_metric(pmda_pmid(0,92), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.rowidfetches', 'Total table fetch by rowid',
+'The "table fetch by rowid" statistic from the V$SYSSTAT view. When
+rows are fetched using a ROWID (usually from an index), each row
+returned increments this counter.
+This metric is an indication of row fetch operations being performed
+with the aid of an index. Because doing table scans usually indicates
+either non-optimal queries or tables without indices, this metric
+should increase as the above issues have been addressed in the
+application.');
+
+ $pmda->add_metric(pmda_pmid(0,93), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.contfetches', 'Total table fetch continued row',
+'The "table fetch continued row" statistic from the V$SYSSTAT view.
+This metric is incremented when a row that spans more than one block is
+encountered during a fetch.
+Retrieving rows that span more than one block increases the logical I/O
+by a factor that corresponds to the number of blocks that need to be
+accessed. Exporting and re-importing may eliminate this problem. Also
+take a closer look at the STORAGE parameters PCT_FREE and PCT_USED.
+This problem cannot be fixed if rows are larger than database blocks
+(for example, if the LONG datatype is used and the rows are extremely
+large).');
+
+ $pmda->add_metric(pmda_pmid(0,94), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.clustkey.scans', 'Total cluster key scans',
+'The "cluster key scans" statistic from the V$SYSSTAT view.
+This is the number of cluster scans that were started.');
+
+ $pmda->add_metric(pmda_pmid(0,95), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.clustkey.scanblocks',
+ 'Total cluster key scan block gets',
+'The "cluster key scan block gets" statistic from the V$SYSSTAT view.
+This is the number of blocks obtained in a cluster scan.');
+
+ $pmda->add_metric(pmda_pmid(0,96), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.sql.parsecpu', 'Total parse time cpu',
+'The "parse time cpu" statistic from the V$SYSSTAT view. This is the
+total CPU time used for parsing (hard and soft parsing). Units are
+milliseconds of CPU time.');
+
+ $pmda->add_metric(pmda_pmid(0,97), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.sql.parsereal', 'Total parse time elapsed',
+'The "parse time elapsed" statistic from the V$SYSSTAT view.
+This is the total elapsed time for parsing. Subtracting
+oracle.sysstat.sql.parsecpu from this metric gives the total
+waiting time for parse resources. Units are milliseconds.');
+
+ $pmda->add_metric(pmda_pmid(0,98), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sql.parsed', 'Total parse count',
+'The "parse count (total)" statistic from the V$SYSSTAT view. This is
+the total number of parse calls (hard and soft). A soft parse is a
+check to make sure that the permissions on the underlying objects have
+not changed.');
+
+ $pmda->add_metric(pmda_pmid(0,99), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sql.executed', 'Total execute count',
+'The "execute count" statistic from the V$SYSSTAT view.
+This is the total number of calls (user and recursive) that
+execute SQL statements.');
+
+ $pmda->add_metric(pmda_pmid(0,100), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sql.memsorts', 'Total sorts (memory)',
+'The "sorts (memory)" statistic from the V$SYSSTAT view. If the number
+of disk writes is zero, then the sort was performed completely in
+memory and this metric is incremented.
+This is more an indication of sorting activity in the application
+workload. You cannot do much better than memory sorts, except for no
+sorts at all. Sorting is usually caused by selection criteria
+specifications within table join SQL operations.');
+
+ $pmda->add_metric(pmda_pmid(0,101), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sql.disksorts', 'Total sorts (disk)',
+'The "sorts (disk)" statistic from the V$SYSSTAT view. If the number
+of disk writes is non-zero for a given sort operation, then this metric
+is incremented.
+Sorts that require I/O to disk are quite resource intensive.
+Try increasing the size of the Oracle initialization parameter
+SORT_AREA_SIZE.');
+
+ $pmda->add_metric(pmda_pmid(0,102), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sql.rowsorts', 'Total sorts (rows)',
+'The "sorts (rows)" statistic from the V$SYSSTAT view.
+This is the total number of rows sorted.');
+
+ $pmda->add_metric(pmda_pmid(0,103), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sccachehits', 'Total session cursor cache hits',
+'The "session cursor cache hits" statistic from the V$SYSSTAT view.
+This is the count of the number of hits in the session cursor cache.
+A hit means that the SQL statement did not have to be reparsed.
+By subtracting this metric from oracle.sysstat.sql.parsed one can
+determine the real number of parses that have been performed.');
+
+ $pmda->add_metric(pmda_pmid(0,104), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cursauths', 'Total cursor authentications',
+'The "cursor authentications" statistic from the V$SYSSTAT view. This
+is the total number of cursor authentications. The number of times
+that cursor privileges have been verified, either for a SELECT or
+because privileges were revoked from an object, causing all users of
+the cursor to be re-authenticated.');
+
+ $pmda->add_metric(pmda_pmid(0,105), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.recovery.breads', 'Total recovery blocks read',
+'The "recovery blocks read" statistic from the V$SYSSTAT view.
+This is the number of blocks read during recovery.');
+
+ $pmda->add_metric(pmda_pmid(0,106), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.recovery.areads', 'Total recovery array reads',
+'The "recovery array reads" statistic from the V$SYSSTAT view. This is
+the number of reads performed during recovery.');
+
+ $pmda->add_metric(pmda_pmid(0,107), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.recovery.areadtime', 'Total recovery array read time',
+'The "recovery array read time" statistic from the V$SYSSTAT view.
+This is the elapsed time of I/O while doing recovery.');
+
+ $pmda->add_metric(pmda_pmid(0,108), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.rowidrngscans',
+ 'Total table scans (rowid ranges)',
+'The "table scans (rowid ranges)" statistic from the V$SYSSTAT view.
+This is a count of the table scans with specified ROWID endpoints.
+These scans are performed for Parallel Query.');
+
+ $pmda->add_metric(pmda_pmid(0,109), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.cachepartscans',
+ 'Total table scans (cache partitions)',
+'The "table scans (cache partitions)" statistic from the V$SYSSTAT
+view. This is a count of range scans on tables that have the CACHE
+option enabled.');
+
+ $pmda->add_metric(pmda_pmid(0,110), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cr.createblk', 'Total CR blocks created',
+'The "CR blocks created" statistic from the V$SYSSTAT view.
+A buffer in the buffer cache was cloned. The most common reason
+for cloning is that the buffer is held in an incompatible mode.');
+
+ $pmda->add_metric(pmda_pmid(0,111), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cr.convcurrblk',
+ 'Total Current blocks converted for CR',
+'The "Current blocks converted for CR" statistic from the V$SYSSTAT
+view. A CURRENT buffer (shared or exclusive) is made CR before it can
+be used.');
+
+ $pmda->add_metric(pmda_pmid(0,112), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.unnecprocclnscn',
+ 'Total Unnecessary process cleanup for SCN batching',
+'The "Unnecessary process cleanup for SCN batching" statistic from the
+V$SYSSTAT view. This is the total number of times that the process
+cleanup was performed unnecessarily because the session/process did not
+get the next batched SCN (System Commit Number). The next batched SCN
+went to another session instead.');
+
+ $pmda->add_metric(pmda_pmid(0,113), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consread.transtable.undo',
+ 'Total transaction tables consistent reads - undo records applied',
+'The "transaction tables consistent reads - undo records applied"
+statistic from the V$SYSSTAT view. This is the number of UNDO records
+applied to get CR images of data blocks.');
+
+ $pmda->add_metric(pmda_pmid(0,114), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consread.transtable.rollback',
+ 'Total transaction tables consistent read rollbacks',
+'The "transaction tables consistent read rollbacks" statistic from the
+V$SYSSTAT view. This is the total number of times transaction tables
+are CR rolled back.');
+
+ $pmda->add_metric(pmda_pmid(0,115), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.datablkundo',
+ 'Total data blocks consistent reads - undo records applied',
+'The "data blocks consistent reads - undo records applied" statistic
+from the V$SYSSTAT view. This is the total number of UNDO records
+applied to get CR images of data blocks.');
+
+ $pmda->add_metric(pmda_pmid(0,116), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.noworkgets', 'Total no work - consistent read gets',
+'The "no work - consistent read gets" statistic from the V$SYSSTAT
+view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,117), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consread.cleangets',
+ 'Total cleanouts only - consistent read gets',
+'The "cleanouts only - consistent read gets" statistic from the
+V$SYSSTAT view. The number of times a CR get required a block
+cleanout ONLY and no application of undo.');
+
+ $pmda->add_metric(pmda_pmid(0,118), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consread.rollbackgets',
+ 'Total rollbacks only - consistent read gets',
+'The "rollbacks only - consistent read gets" statistic from the
+V$SYSSTAT view. This is the total number of CR operations requiring
+UNDO to be applied but no block cleanout.');
+
+ $pmda->add_metric(pmda_pmid(0,119), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.consread.cleanrollbackgets',
+ 'Total cleanouts and rollbacks - consistent read gets',
+'The "cleanouts and rollbacks - consistent read gets" statistic from the
+V$SYSSTAT view. This is the total number of CR gets requiring BOTH
+block cleanout and subsequent rollback to get to the required snapshot
+time.');
+
+ $pmda->add_metric(pmda_pmid(0,120), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.rollbackchangeundo',
+ 'Total rollback changes - undo records applied',
+'The "rollback changes - undo records applied" statistic from the
+V$SYSSTAT view. This is the total number of undo records applied to
+blocks to rollback real changes. Eg: as a result of a rollback command
+and *NOT* in the process of getting a CR block image.
+Eg: commit;
+ insert into mytab values (10);
+ insert into mytab values (20);
+ rollback;
+should increase this statistic by 2 (assuming no recursive operations).');
+
+ $pmda->add_metric(pmda_pmid(0,121), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.transrollbacks', 'Total transaction rollbacks',
+'The "transaction rollbacks" statistic from the V$SYSSTAT view. This is
+the actual transaction rollbacks that involve undoing real changes.
+Contrast with oracle.sysstat.user_rollbacks which only indicates the
+number of ROLLBACK statements received.');
+
+ $pmda->add_metric(pmda_pmid(0,122), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cleanout.immedcurr',
+ 'Total immediate (CURRENT) block cleanout applications',
+'The "immediate (CURRENT) block cleanout applications" statistic from
+the V$SYSSTAT view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,123), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cleanout.immedcr',
+ 'Total immediate (CR) block cleanout applications',
+'The "immediate (CR) block cleanout applications" statistic from the
+V$SYSSTAT view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,124), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cleanout.defercurr',
+ 'Total deferred (CURRENT) block cleanout applications',
+'The "deferred (CURRENT) block cleanout applications" statistic from
+the V$SYSSTAT view. This is the number of times cleanout records are
+deferred. Deferred changes are piggybacked with real changes.');
+
+ $pmda->add_metric(pmda_pmid(0,125), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.table.dirreadscans', 'Total table scans (direct read)',
+'The "table scans (direct read)" statistic from the V$SYSSTAT view.
+This is a count of table scans performed with direct read (bypassing
+the buffer cache).');
+
+ $pmda->add_metric(pmda_pmid(0,126), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sccachecount', 'Total session cursor cache count',
+'The "session cursor cache count" statistic from the V$SYSSTAT view.
+This is the total number of cursors cached. This is only incremented
+if SESSION_CACHED_CURSORS is greater than zero. This metric is the
+most useful in V$SESSTAT. If the value for this statistic is close to
+the setting of the initialization parameter SESSION_CACHED_CURSORS, the
+value of the initialization parameter should be increased.');
+
+ $pmda->add_metric(pmda_pmid(0,127), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.totalfileopens', 'Total file opens',
+'The "total file opens" statistic from the V$SYSSTAT view. This is the
+total number of file opens being performed by the instance. Each
+process needs a number of files (control file, log file, database file)
+in order to work against the database.');
+
+ $pmda->add_metric(pmda_pmid(0,128), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cachereplaceopens',
+ 'Opens requiring cache replacement',
+'The "opens requiring cache replacement" statistic from the V$SYSSTAT
+view. This is the total number of file opens that caused a current
+file to be closed in the process file cache.');
+
+ $pmda->add_metric(pmda_pmid(0,129), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.replacedfileopens', 'Opens of replaced files',
+'The "opens of replaced files" statistic from the V$SYSSTAT view. This
+is the total number of files that needed to be reopened because they
+were no longer in the process file cache.');
+
+ $pmda->add_metric(pmda_pmid(0,130), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.total', 'Total commit cleanout calls',
+'The "commit cleanouts" statistic from the V$SYSSTAT view. This is the
+number of times that the cleanout block at commit time function was
+performed.');
+
+ $pmda->add_metric(pmda_pmid(0,131), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.completed',
+ 'Successful commit cleanouts',
+'The "commit cleanouts successfully completed" metric from the V$SYSSTAT
+view. This is the number of times the cleanout block at commit time
+function successfully completed.');
+
+ $pmda->add_metric(pmda_pmid(0,132), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.writedisabled',
+ 'Commits when writes disabled',
+'The "commit cleanout failures: write disabled" statistic from the
+V$SYSSTAT view. This is the number of times that a cleanout at commit
+time was performed but the writes to the database had been temporarily
+disabled.');
+
+ $pmda->add_metric(pmda_pmid(0,133), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.hotbackup',
+ 'Commit attempts during hot backup',
+'The "commit cleanout failures: hot backup in progress" statistic
+from the V$SYSSTAT view. This is the number of times that cleanout
+at commit was attempted during hot backup.');
+
+ $pmda->add_metric(pmda_pmid(0,134), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.bufferwrite',
+ 'Commits while buffer being written',
+'The "commit cleanout failures: buffer being written" statistic from the
+V$SYSSTAT view. This is the number of times that a cleanout at commit
+time was attempted but the buffer was being written at the time.');
+
+ $pmda->add_metric(pmda_pmid(0,135), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.callbackfail',
+ 'Commit callback fails',
+'The "commit cleanout failures: callback failure" statistic from the
+V$SYSSTAT view. This is the number of times that the cleanout callback
+function returned FALSE (failed).');
+
+ $pmda->add_metric(pmda_pmid(0,136), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.blocklost',
+ 'Commit fails due to lost block',
+'The "commit cleanout failures: block lost" statistic from the V$SYSSTAT
+view. This is the number of times that a cleanout at commit was
+attempted but could not find the correct block due to forced write,
+replacement, or switch CURRENT.');
+
+ $pmda->add_metric(pmda_pmid(0,137), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitcleanouts.failures.cannotpin',
+ 'Commit fails due to block pinning',
+'The "commit cleanout failures: cannot pin" statistic from the V$SYSSTAT
+view. This is the number of times that a commit cleanout was performed
+but failed because the block could not be pinned.');
+
+ $pmda->add_metric(pmda_pmid(0,138), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.skiphotwrites', 'Total DBWR hot writes skipped',
+'The "DBWR skip hot writes" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,139), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.ckptbufwrites',
+ 'Total DBWR checkpoint buffers written',
+'The "DBWR checkpoint buffers written" statistic from the V$SYSSTAT
+view. This is the number of times the DBWR was asked to scan the cache
+and write all blocks marked for checkpoint.');
+
+ $pmda->add_metric(pmda_pmid(0,140), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.transwrites',
+ 'Total DBWR transaction table writes',
+'The "DBWR transaction table writes" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,141), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.undoblockwrites', 'Total DBWR undo block writes',
+'The "DBWR undo block writes" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,142), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.ckptwritereq',
+ 'Total DBWR checkpoint write requests',
+'The "DBWR checkpoint write requests" statistic from the V$SYSSTAT
+view. This is the number of times the DBWR was asked to scan the cache
+and write all blocks.');
+
+ $pmda->add_metric(pmda_pmid(0,143), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.incrckptwritereq',
+ 'Total DBWR incr checkpoint write requests',
+'The "DBWR incr. ckpt. write requests" statistic from the V$SYSSTAT
+view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,144), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.revisitbuf',
+ 'Total DBWR being-written buffer revisits',
+'The "DBWR revisited being-written buffer" statistic from the V$SYSSTAT
+view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,145), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.xinstflushcalls',
+ 'Total DBWR cross instance flush calls',
+'The "DBWR Flush object cross instance calls" statistic from the
+V$SYSSTAT view. This is the number of times DBWR received a flush by
+object number cross instance call (from a remote instance). This
+includes both checkpoint and invalidate object.');
+
+ $pmda->add_metric(pmda_pmid(0,146), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.dbwr.nodirtybufs',
+ 'DBWR flush calls finding no dirty buffers',
+'The "DBWR Flush object call found no dirty buffers" statistic from the
+V$SYSSTAT view. DBWR didn\'t find any dirty buffers for an object that
+was flushed from the cache.');
+
+ $pmda->add_metric(pmda_pmid(0,147), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.remote.instundoblockwr',
+ 'Remote instance undo block writes',
+'The "remote instance undo block writes" statistic from the V$SYSSTAT
+view. This is the number of times this instance wrote a dirty undo
+block so that another instance could read it.');
+
+ $pmda->add_metric(pmda_pmid(0,148), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.remote.instundoheaderwr',
+ 'Remote instance undo header writes',
+'The "remote instance undo header writes" statistic from the V$SYSSTAT
+view. This is the number of times this instance wrote a dirty undo
+header block so that another instance could read it.');
+
+ $pmda->add_metric(pmda_pmid(0,149), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmgss_snapshotscn',
+ 'Total calls to get snapshot SCN: kcmgss',
+'The "calls to get snapshot scn: kcmgss" statistic from the V$SYSSTAT
+view. This is the number of times a snap System Commit Number (SCN)
+was allocated. The SCN is allocated at the start of a transaction.');
+
+ $pmda->add_metric(pmda_pmid(0,150), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmgss_batchwait', 'Total kcmgss waits for batching',
+'The "kcmgss waited for batching" statistic from the V$SYSSTAT view.
+This is the number of times the kernel waited on a snapshot System
+Commit Number (SCN).');
+
+ $pmda->add_metric(pmda_pmid(0,151), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmgss_nodlmscnread',
+ 'Total kcmgss SCN reads with using DLM',
+'The "kcmgss read scn without going to DLM" statistic from the V$SYSSTAT
+view. This is the number of times the kernel casually confirmed the
+System Commit Number (SCN) without using the Distributed Lock Manager
+(DLM).');
+
+ $pmda->add_metric(pmda_pmid(0,152), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.kcmccs_currentscn',
+ 'Total kcmccs calls to get current SCN',
+'The "kcmccs called get current scn" statistic from the V$SYSSTAT view.
+This is the number of times the kernel got the CURRENT SCN (System
+Commit Number) when there was a need to casually confirm the SCN.');
+
+ $pmda->add_metric(pmda_pmid(0,153), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.serializableaborts', 'Total serializable aborts',
+'The "serializable aborts" statistic from the V$SYSSTAT view. This is
+the number of times a SQL statement in serializable isolation level had
+to abort.');
+
+ $pmda->add_metric(pmda_pmid(0,154), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globalcache.hashlatchwaits',
+ 'Global cache hash latch waits',
+'The "global cache hash latch waits" statistic from the V$SYSSTAT view.
+This is the number of times that the buffer cache hash chain latch
+couldn\'t be acquired immediately, when processing a lock element.');
+
+ $pmda->add_metric(pmda_pmid(0,155), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globalcache.freelistwaits',
+ 'Global cache freelist waits',
+'The "global cache freelist waits" statistic from the V$SYSSTAT view.
+This is the number of pings for free lock elements (when all release
+locks are in use).');
+
+ $pmda->add_metric(pmda_pmid(0,156), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.globalcache.defers',
+ 'Global cache ping request defers',
+'The "global cache defers" statistic from the V$SYSSTAT view.
+This is the number of times a ping request was deferred until later.');
+
+ $pmda->add_metric(pmda_pmid(0,157), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.instrecoverdbfreeze',
+ 'Instance recovery database freezes',
+'The "instance recovery database freeze count" statistic from the
+V$SYSSTAT view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,158), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.commitscncached', 'Commit SCN cached',
+'The "Commit SCN cached" statistic from the V$SYSSTAT view. The System
+Commit Number (SCN) is used to serialize time within a single instance,
+and across all instances. This lock resource caches the current value
+of the SCN - the value is incremented in response to many database
+events, but most notably COMMIT WORK. Access to the SCN lock value to
+get and store the SCN is batched on most cluster implementations, so
+that every process that needs a new SCN gets one and stores a new value
+back on one instance, before the SCN lock is released so that it may be
+granted to another instance. Processes get the SC lock once and then
+use conversion operations to manipulate the lock value.');
+
+ $pmda->add_metric(pmda_pmid(0,159), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.cachedscnreferenced', 'Cached Commit SCN referenced',
+'The "Cached Commit SCN referenced" statistic from the V$SYSSTAT view.
+The SCN (System Commit Number), is generally a timing mechanism Oracle
+uses to guarantee ordering of transactions and to enable correct
+recovery from failure. They are used for guaranteeing
+read-consistency, and checkpointing.');
+
+ $pmda->add_metric(pmda_pmid(0,160), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.hardparsed', 'Total number of hard parses performed',
+'The "parse count (hard)" statistic from the V$SYSSTAT view. This is
+the total number of parse calls (real parses). A hard parse means
+allocating a workheap and other memory structures, and then building a
+parse tree. A hard parse is a very expensive operation in terms of
+memory use.');
+
+ $pmda->add_metric(pmda_pmid(0,161), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.sqlnet.clientrecvs',
+ 'Total bytes from client via SQL*Net',
+'The "bytes received via SQL*Net from client" statistic from the
+V$SYSSTAT view. This is the total number of bytes received from the
+client over SQL*Net.');
+
+ $pmda->add_metric(pmda_pmid(0,162), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.sqlnet.clientsends',
+ 'Total bytes to client via SQL*Net',
+'The "bytes sent via SQL*Net to client" statistic from the V$SYSSTAT
+view. This is the total number of bytes sent to the client over
+SQL*Net.');
+
+ $pmda->add_metric(pmda_pmid(0,163), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sqlnet.clientroundtrips',
+ 'Total client SQL*Net roundtrips',
+'The "SQL*Net roundtrips to/from client" statistic from the V$SYSSTAT
+view. This is the total number of network messages sent to and
+received from the client.');
+
+ $pmda->add_metric(pmda_pmid(0,164), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.sqlnet.dblinkrecvs',
+ 'Total bytes from dblink via SQL*Net',
+'The "bytes received via SQL*Net from dblink" statistic from the
+V$SYSSTAT view. This is the total number of bytes received from
+the database link over SQL*Net.');
+
+ $pmda->add_metric(pmda_pmid(0,165), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.sysstat.sqlnet.dblinksends',
+ 'Total bytes to dblink via SQL*Net',
+'The "bytes sent via SQL*Net to dblink" statistic from the V$SYSSTAT
+view. This is the total number of bytes sent over a database link.');
+
+ $pmda->add_metric(pmda_pmid(0,166), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.sqlnet.dblinkroundtrips',
+ 'Total dblink SQL*Net roundtrips',
+'The "SQL*Net roundtrips to/from dblink" statistic from the V$SYSSTAT
+view. This is the total number of network messages sent to and
+received from a database link.');
+
+ $pmda->add_metric(pmda_pmid(0,167), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.parallel.queries', 'Total queries parallelized',
+'The "queries parallelized" statistic from the V$SYSSTAT view. This is
+the number of SELECT statements which have been parallelized.');
+
+ $pmda->add_metric(pmda_pmid(0,168), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.parallel.DMLstatements',
+ 'Total DML statements parallelized',
+'The "DML statements parallelized" statistic from the V$SYSSTAT view.
+This is the number of Data Manipulation Language (DML) statements which
+have been parallelized.');
+
+ $pmda->add_metric(pmda_pmid(0,169), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.parallel.DDLstatements',
+ 'Total DDL statements parallelized',
+'The "DDL statements parallelized" statistic from the V$SYSSTAT view.
+This is the number of Data Definition Language (DDL) statements which
+have been parallelized.');
+
+ $pmda->add_metric(pmda_pmid(0,170), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.PX.localsends', 'PX local messages sent',
+'The "PX local messages sent" statistic from the V$SYSSTAT view.
+This is the number of local messages sent for Parallel Execution.');
+
+ $pmda->add_metric(pmda_pmid(0,171), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.PX.localrecvs', 'PX local messages received',
+'The "PX local messages recv\'d" statistic from the V$SYSSTAT view.
+This is the number of local messages received for Parallel Execution.');
+
+ $pmda->add_metric(pmda_pmid(0,172), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.PX.remotesends', 'PX remote messages sent',
+'The "PX remote messages sent" statistic from the V$SYSSTAT view.
+This is the number of remote messages sent for Parallel Execution.');
+
+ $pmda->add_metric(pmda_pmid(0,173), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.PX.remoterecvs', 'PX remote messages received',
+'The "PX remote messages recvd" statistic from the V$SYSSTAT view.
+This is the number of remote messages received for Parallel Execution.');
+
+ $pmda->add_metric(pmda_pmid(0,174), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.pinned', 'Total pinned buffers',
+'The "buffer is pinned count" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,175), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.notpinned', 'Total not pinned buffers',
+'The "buffer is not pinned count" statistic from the V$SYSSTAT view.
+This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,176), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.sysstat.buffer.nonetopin', 'No buffer to keep pinned count',
+'The "no buffer to keep pinned count" statistic from the V$SYSSTAT
+view. This metric is not documented by Oracle.');
+
+ $pmda->add_metric(pmda_pmid(0,177), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.OS.utime', 'OS User time used',
+'The "OS User time used" statistic from the V$SYSSTAT view.
+Units are milliseconds of CPU user time.');
+
+ $pmda->add_metric(pmda_pmid(0,178), PM_TYPE_U32, $sid_indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'oracle.sysstat.OS.stime', 'OS System time used',
+'The "OS System time used" statistic from the V$SYSSTAT view.
+Units are milliseconds of CPU system time.');
+}
+
+sub setup_rowcache ## row cache statistics from v$rowcache
+{
+ $pmda->add_metric(pmda_pmid(7,0), PM_TYPE_U32, $rowcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rowcache.count',
+ 'Number of entries in this data dictionary cache',
+'The total number of data dictionary cache entries, broken down by data
+type. This is extracted from the COUNT column of the V$ROWCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(7,1), PM_TYPE_U32, $rowcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rowcache.gets',
+ 'Number of requests for cached information on data dictionary objects',
+'The total number of valid data dictionary cache entries, broken down by
+data type. This is extracted from the GETS column of the V$ROWCACHE
+view.');
+
+ $pmda->add_metric(pmda_pmid(7,2), PM_TYPE_U32, $rowcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rowcache.getmisses',
+ 'Number of data requests resulting in cache misses',
+'The total number of data dictionary requests that resulted in cache
+misses, broken down by data type. This is extracted from the GETMISSES
+column of the V$ROWCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(7,3), PM_TYPE_U32, $rowcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rowcache.scans', 'Number of scan requests',
+'The total number of data dictionary cache scans, broken down by data
+type. This is extracted from the SCANS column of the V$ROWCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(7,4), PM_TYPE_U32, $rowcache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rowcache.scanmisses',
+ 'Number of data dictionary cache misses',
+'The total number of times data dictionary cache scans failed to find
+data in the cache, broken down by data type. This is extracted from
+the SCANMISSES column of the V$ROWCACHE view.');
+}
+
+sub setup_rollstat ## rollback I/O statistics from v$rollstat
+{
+ $pmda->add_metric(pmda_pmid(4,0), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.rollback.rssize', 'Size of rollback segment',
+'Size in bytes of the rollback segment. This value is obtained from the
+RSSIZE column in the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,1), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.writes',
+ 'Number of bytes written to rollback segment',
+'The total number of bytes written to rollback segment. This value is
+obtained from the WRITES column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,2), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.xacts', 'Number of active transactions',
+'The number of active transactions. This value is obtained from the
+XACTS column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,3), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.gets',
+ 'Number of header gets for rollback segment',
+'The number of header gets for the rollback segment. This value is
+obtained from the GETS column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,4), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.waits',
+ 'Number of header waits for rollback segment',
+'The number of header gets for the rollback segment. This value is
+obtained from the WAIT column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,5), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.rollback.hwmsize',
+ 'High water mark of rollback segment size',
+'High water mark of rollback segment size. This value is obtained from
+the HWMSIZE column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,6), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.shrinks',
+ 'Number of times rollback segment shrank',
+'The number of times the size of the rollback segment decreased,
+eliminating additional extents. This value is obtained from the
+SHRINKS column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,7), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.wraps',
+ 'Number of times rollback segment wrapped',
+'The number of times the rollback segment wrapped from one extent
+to another. This value is obtained from the WRAPS column of the
+V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,8), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.rollback.extends',
+ 'Number of times rollback segment size extended',
+'The number of times the size of the rollback segment grew to include
+another extent. This value is obtained from the EXTENDS column of the
+V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,9), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.rollback.avshrink', 'Average shrink size',
+'Average of freed extent size for rollback segment. This value is
+obtained from the AVESHRINK column of the V$ROLLSTAT view.');
+
+ $pmda->add_metric(pmda_pmid(4,10), PM_TYPE_U32, $rollback_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_BYTE,0,0),
+ 'oracle.rollback.avactive',
+ 'Current size of active entents averaged over time',
+'Current average size of extents with uncommitted transaction data.
+This value is obtained from the AVEACTIVE column from the V$ROLLSTAT
+view.');
+}
+
+sub setup_reqdist ## request time histogram from v$reqdist
+{
+ $pmda->add_metric(pmda_pmid(5,0), PM_TYPE_U32, $reqdist_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.reqdist', 'Histogram of database operation request times',
+'A histogram of database request times divided into twelve buckets (time
+ranges). This is extracted from the V$REQDIST table.
+NOTE:
+ The TIMED_STATISTICS database parameter must be TRUE or this metric
+ will not return any values.');
+}
+
+sub setup_object_cache ## cache statistics from v$db_object_cache
+{
+ $pmda->add_metric(pmda_pmid(9,0), PM_TYPE_U32, $object_cache_indom,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'oracle.object_cache.sharemem',
+ 'Sharable memory usage in database cache pool by object types',
+'The amount of sharable memory in the shared pool consumed by various
+objects, divided into object types. The valid object types are:
+INDEX, TABLE, CLUSTER, VIEW, SET, SYNONYM, SEQUENCE, PROCEDURE,
+FUNCTION, PACKAGE, PACKAGE BODY, TRIGGER, CLASS, OBJECT, USER, DBLINK,
+NON_EXISTENT, NOT LOADED and OTHER.
+The values for each of these object types are obtained from the
+SHARABLE_MEM column of the V$DB_OBJECT_CACHE view.');
+
+ $pmda->add_metric(pmda_pmid(9,1), PM_TYPE_U32, $object_cache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.object_cache.loads', 'Number of times object loaded',
+'The number of times the object has been loaded. This count also
+increases when and object has been invalidated. These values are
+obtained from the LOADS column of the V$DB_OBJECT_CACHE view.');
+
+ $pmda->add_metric(pmda_pmid(9,2), PM_TYPE_U32, $object_cache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.object_cache.locks',
+ 'Number of users currently locking this object',
+'The number of users currently locking this object. These values are
+obtained from the LOCKS column of the V$DB_OBJECT_CACHE view.');
+
+ $pmda->add_metric(pmda_pmid(9,3), PM_TYPE_U32, $object_cache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.object_cache.pins',
+ 'Number of users currently pinning this object',
+'The number of users currently pinning this object. These values are
+obtained from the PINS column of the V$DB_OBJECT_CACHE view.');
+}
+
+sub setup_license ## licence data from v$license
+{
+ $pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.license.maxsess',
+ 'Maximum number of concurrent user sessions',
+'The maximum number of concurrent user sessions permitted for the
+instance. This value is obtained from the SESSIONS_MAX column of
+the V$LICENSE view.');
+
+ $pmda->add_metric(
+ pmda_pmid(1,1), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.license.cursess',
+ 'Current number of concurrent user sessions',
+'The current number of concurrent user sessions for the instance.
+This value is obtained from the SESSIONS_CURRENT column of the
+V$LICENSE view.');
+
+ $pmda->add_metric(
+ pmda_pmid(1,2), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.license.warnsess',
+ 'Warning limit for concurrent user sessions',
+'The warning limit for concurrent user sessions for this instance.
+This value is obtained from the SESSIONS_WARNING column of the
+V$LICENSE view.');
+
+ $pmda->add_metric(
+ pmda_pmid(1,3), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.license.highsess',
+ 'Highest number of concurrent user sessions since instance started',
+'The highest number of concurrent user sessions since the instance
+started. This value is obtained from the SESSIONS_HIGHWATER column of
+the V$LICENSE view.');
+
+ $pmda->add_metric(pmda_pmid(1,4), PM_TYPE_U32, $sid_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.license.maxusers',
+ 'Maximum number of named users permitted',
+'The maximum number of named users allowed for the database. This value
+is obtained from the USERS_MAX column of the V$LICENSE view.');
+}
+
+sub setup_librarycache ## statistics from v$librarycache
+{
+ $pmda->add_metric(pmda_pmid(12,0), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.gets',
+ 'Number of lock requests for each namespace object',
+'The number of times a lock was requested for objects of this
+namespace. This value is obtained from the GETS column of the
+V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,1), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.gethits',
+ 'Number of times objects handle found in memory',
+'The number of times an object\'s handle was found in memory. This value
+is obtained from the GETHITS column of the V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,2), PM_TYPE_FLOAT, $librarycache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.gethitratio',
+ 'Ratio of gethits to hits',
+'The ratio of GETHITS to HITS. This value is obtained from the
+GETHITRATIO column of the V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,3), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.pins',
+ 'Number of times a pin was requested for each namespace object',
+'The number of times a PIN was requested for each object of the library
+cache namespace. This value is obtained from the PINS column of the
+V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,4), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.pinhits',
+ 'Number of times all metadata found in memory',
+'The number of times that all of the meta data pieces of the library
+object were found in memory. This value is obtained from the PINHITS
+column of the V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,5), PM_TYPE_FLOAT, $librarycache_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.pinhitratio', 'Ratio of pins to pinhits',
+'The ratio of PINS to PINHITS. This value is obtained from the
+PINHITRATIO column of the V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,6), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.reloads', 'Number of disk reloads required',
+'Any PIN of an object that is not the first PIN performed since the
+object handle was created, and which requires loading the object from
+the disk. This value is obtained from the RELOADS column of the
+V$LIBRARYCACHE view.');
+
+ $pmda->add_metric(pmda_pmid(12,7), PM_TYPE_U32, $librarycache_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.librarycache.invalidations',
+ 'Invalidations due to dependent object modifications',
+'The total number of times objects in the library cache namespace were
+marked invalid due to a dependent object having been modified. This
+value is obtained from the INVALIDATIONS column of the V$LIBRARYCACHE
+view.');
+}
+
+sub setup_latch ## latch statistics from v$latch
+{
+ $pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.gets',
+ 'Number of times obtained a wait',
+'The number of times latch obtained a wait. These values are obtained
+from the GETS column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,1), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.misses',
+ 'Number of times obtained a wait but failed on first try',
+'The number of times obtained a wait but failed on the first try. These
+values are obtained from the MISSES column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,2), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.sleeps',
+ 'Number of times slept when wanted a wait',
+'The number of times slept when wanted a wait. These values are
+obtained from the SLEEPS column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,3), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.imgets',
+ 'Number of times obtained without a wait',
+'The number of times latch obtained without a wait. These values are
+obtained from the IMMEDIATE_GETS column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,4), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.immisses',
+ 'Number of times failed to get latch without a wait',
+'The number of times failed to get latch without a wait. These values
+are obtained from the IMMEDIATE_MISSES column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,5), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.wakes',
+ 'Number of times a wait was awakened',
+'The number of times a wait was awakened. These values are obtained
+from the WAITERS_WOKEN column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,6), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.holds',
+ 'Number of waits while holding a different latch',
+'The number of waits while holding a different latch. These values are
+obtained from the WAITS_HOLDING_LATCH column of the V$LATCH view.');
+
+ $pmda->add_metric(pmda_pmid(2,7), PM_TYPE_U32, $latch_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.latch.spingets',
+ 'Gets that missed first try but succeeded on spin',
+'Gets that missed first try but succeeded on spin. These values are
+obtained from the SPIN_GETS column of the V$LATCH view.');
+}
+
+sub setup_backup ## file backup status from v$backup
+{
+ $pmda->add_metric(pmda_pmid(6,0), PM_TYPE_U32, $file_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'oracle.backup.status',
+ 'Backup status of online datafiles',
+'The Backup status of online datafiles. The status is encoded as an
+ASCII character:
+ not active - ( 45)
+ active + ( 43)
+ offline o (111)
+ normal n (110)
+ error E ( 69)
+This value is extracted from the STATUS column of the V$BACKUP view.');
+}
+
+sub setup_filestat ## file I/O statistics from v$filestat
+{
+ $pmda->add_metric(pmda_pmid(3,0), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.file.phyrds',
+ 'Physical reads from database files',
+'The number of physical reads from each database file. These values
+are obtained from the PHYRDS column in the V$FILESTAT view.');
+
+ $pmda->add_metric(pmda_pmid(3,1), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.file.phywrts',
+ 'Physical writes to database files',
+'The number of times the DBWR process is required to write to each of
+the database files. These values are obtained from the PHYWRTS column
+in the V$FILESTAT view.');
+
+ $pmda->add_metric(pmda_pmid(3,2), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.file.phyblkrd',
+ 'Physical blocks read from database files',
+'The number of physical blocks read from each database file. These
+values are obtained from the PHYBLKRDS column in the V$FILESTAT view.');
+
+ $pmda->add_metric(pmda_pmid(3,3), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'oracle.file.phyblkwrt',
+ 'Physical blocks written to database files',
+'The number of physical blocks written to each database file. These
+values are obtained from the PHYBLKWRT column in the V$FILESTAT view.');
+
+ $pmda->add_metric(pmda_pmid(3,4), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'oracle.file.readtim',
+ 'Time spent reading from database files',
+'The number of milliseconds spent doing reads if the TIMED_STATISTICS
+database parameter is true. If this parameter is false, then the
+metric will have a value of zero. This value is obtained from the
+READTIM column of the V$FILESTAT view.');
+
+ $pmda->add_metric(pmda_pmid(3,5), PM_TYPE_U32, $file_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'oracle.file.writetim',
+ 'Time spent writing to database files',
+'The number of milliseconds spent doing writes if the TIMED_STATISTICS
+database parameter is true. If this parameter is false, then the
+metric will have a value of zero. This value is obtained from the
+WRITETIM column of the V$FILESTAT view.');
+}
+
+$pmda = PCP::PMDA->new('oracle', 32);
+oracle_metrics_setup();
+oracle_indoms_setup();
+
+$pmda->set_fetch_callback(\&oracle_fetch_callback);
+$pmda->set_fetch(\&oracle_connection_setup);
+$pmda->set_refresh(\&oracle_refresh);
+$pmda->set_user('oracle');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdaoracle - performance metrics domain agent for Oracle databases
+
+=head1 DESCRIPTION
+
+B<pmdaoracle> is a Performance Metrics Domain Agent (PMDA) that obtains
+performance metrics from an Oracle database instance and makes them
+available to users of the Performance Co-Pilot (PCP) monitor tools.
+
+B<pmdaoracle> retrieves information from the database by querying the
+dynamic performance (V$...) views.
+Queries are performed only when metrics are requested from the PMDA to
+minimize impact on the database.
+
+B<pmdaoracle> can monitor multiple Oracle database instances.
+If multiple database instances are to be monitored with PCP, each must
+be configured during installation.
+
+The Performance Metrics Collector Daemon, B<pmcd> launches B<pmdaoracle>;
+it should not be executed directly. See the installation section below
+for instructions on how to configure and start the agent.
+
+=head1 INSTALLATION
+
+B<pmdaoracle> uses a configuration file from (in this order):
+
+=over
+
+=item * /etc/pcpdbi.conf
+
+=item * $PCP_PMDAS_DIR/oracle/oracle.conf
+
+=back
+
+This file can contain overridden values (Perl code) for the settings
+listed at the start of pmdaoracle.pl, namely:
+
+=over
+
+=item * database name (see DBI(3) for details)
+
+=item * database user name
+
+=item * database pass word
+
+=back
+
+Once this is setup, you can access the names and values for the
+oracle performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/oracle
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/oracle
+ # ./Remove
+
+B<pmdaoracle> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /etc/pcpdbi.conf
+
+configuration file for all PCP database monitors
+
+=item $PCP_VAR_DIR/config/oracle/oracle.conf
+
+configuration file for B<pmdaoracle>
+
+=item $PCP_PMDAS_DIR/oracle/oracle.conf
+
+alternate configuration file for B<pmdaoracle>
+
+=item $PCP_PMDAS_DIR/oracle/Install
+
+installation script for the B<pmdaoracle> agent
+
+=item $PCP_PMDAS_DIR/oracle/Remove
+
+undo installation script for the B<pmdaoracle> agent
+
+=item $PCP_LOG_DIR/pmcd/oracle.log
+
+default log file for error messages from B<pmdaoracle>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), pmdadbping.pl(1) and DBI(3).
diff --git a/src/pmdas/papi/GNUmakefile b/src/pmdas/papi/GNUmakefile
new file mode 100644
index 0000000..d2e3774
--- /dev/null
+++ b/src/pmdas/papi/GNUmakefile
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2012-2014 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = papi
+DOMAIN = PAPI
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMDAINIT = $(IAM)_init
+
+LLDLIBS = -lpapi $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY) -I.
+
+CFILES = papi.c
+DFILES = README help
+
+LDIRT = domain.h *.o $(IAM).log $(CMDTARGET) $(LIBTARGET)
+
+
+default_pcp default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_PAPI)" "enable_papi"
+
+build-me: domain.h $(CMDTARGET) $(LIBTARGET)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(CMDTARGET) $(LIBTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root pmns domain.h $(DFILES) $(PMDADIR)
+else
+build-me:
+install_pcp install:
+endif
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+papi.o: domain.h
+papi.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/papi/Install b/src/pmdas/papi/Install
new file mode 100644
index 0000000..aad3276
--- /dev/null
+++ b/src/pmdas/papi/Install
@@ -0,0 +1,27 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the PAPI PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=papi
+pmda_interface=6
+forced_restart=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/papi/README b/src/pmdas/papi/README
new file mode 100644
index 0000000..4c1cbf1
--- /dev/null
+++ b/src/pmdas/papi/README
@@ -0,0 +1,54 @@
+Papi PMDA
+=========
+
+This PMDA exports Performance API (PAPI) eventsets related to memory
+usage and instruction statistics.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list
+all the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT papi
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/papi
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ If you choose the "default" installation, appropriate values will
+ be assigned to those parameters that control the customiztion of
+ the PMDA. Otherwise consult the pmdapapi(1) man page for a
+ descript of the customiztion parameters.
+
+Removal
+=======
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/papi
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/papi.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/papi/Remove b/src/pmdas/papi/Remove
new file mode 100755
index 0000000..8d04be2
--- /dev/null
+++ b/src/pmdas/papi/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the papi PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=papi
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/papi/help b/src/pmdas/papi/help
new file mode 100644
index 0000000..2981ee5
--- /dev/null
+++ b/src/pmdas/papi/help
@@ -0,0 +1,74 @@
+#
+# Copyright (c) 2014 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# papi PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+@ papi.TOT_INS total instruction completed
+
+@ papi.TOT_CYC total cycles completed
+
+@ papi.L1_DCM Level 1 data cache misses
+
+@ papi.L1_ICM Level 1 instruction cache misses
+
+@ papi.L2_DCM Level 2 data cache misses
+
+@ papi.L2_ICM Level 2 instruction cache misses
+
+@ papi.L3_DCM Level 3 data cache misses
+
+@ papi.L3_ICM Level 3 instruction cache misses
+
+@ papi.L1_TCM Level 1 total cache misses
+
+@ papi.L2_TCM Level 2 total cache misses
+
+@ papi.L3_TCM Level 3 total cache misses
+
+@ papi.TLB_DM Data translation lookaside buffer misses
+
+@ papi.TLB_IM Instruction translation lookaside buffer misses
+
+@ papi.TLB_TL Total translation lookaside buffer misses
+
+@ papi.L1_LDM Level 1 load misses
+
+@ papi.L1_STM Level 1 store misses
+
+@ papi.L2_LDM Level 2 load misses
+
+@ papi.L2_STM Level 2 store misses
+
+@ papi.control.enable A list of metrics to enable and count
+
+@ papi.control.reset Reset the counter values
+
+@ papi.control.disable A list of metrics to disable counting
+
+@ papi.control.status A string of papi counters current state
+
+@ papi.available.num_counters Number of hardware counters available for use
+
diff --git a/src/pmdas/papi/papi.c b/src/pmdas/papi/papi.c
new file mode 100644
index 0000000..4977bb1
--- /dev/null
+++ b/src/pmdas/papi/papi.c
@@ -0,0 +1,1497 @@
+/*
+ * PAPI PMDA
+ *
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include <papi.h>
+#include <assert.h>
+#if defined(HAVE_GRP_H)
+#include <grp.h>
+#endif
+#include <string.h>
+
+
+enum {
+ CLUSTER_PAPI = 0, // hardware event counters
+ CLUSTER_CONTROL, // control variables
+ CLUSTER_AVAILABLE, // available hardware
+};
+
+typedef struct {
+ unsigned int papi_event_code; //the PAPI_ eventcode
+ char papi_string_code[8];
+ int position;
+ int pmns_position;
+} papi_m_user_tuple;
+
+static papi_m_user_tuple *papi_info;
+
+static char *enable_string;
+static char *disable_string;
+static char isDSO = 1; /* == 0 if I am a daemon */
+static int EventSet = PAPI_NULL;
+static long_long *values;
+struct uid_gid_tuple {
+ char uid_p; char gid_p; /* uid/gid received flags. */
+ int uid; int gid; /* uid/gid received from PCP_ATTR_* */
+};
+static struct uid_gid_tuple *ctxtab;
+static int ctxtab_size;
+static int number_of_counters;
+static unsigned int number_of_active_counters;
+static unsigned int size_of_active_counters;
+static unsigned int number_of_events;
+
+static void
+set_pmns_position(unsigned int i)
+{
+ switch (papi_info[i].papi_event_code) {
+ case PAPI_TOT_INS:
+ papi_info[i].pmns_position = 0;
+ break;
+ case PAPI_TOT_CYC:
+ papi_info[i].pmns_position = 1;
+ break;
+ case PAPI_L1_DCM:
+ papi_info[i].pmns_position = 2;
+ break;
+ case PAPI_L1_ICM:
+ papi_info[i].pmns_position = 3;
+ break;
+ case PAPI_L2_DCM:
+ papi_info[i].pmns_position = 4;
+ break;
+ case PAPI_L2_ICM:
+ papi_info[i].pmns_position = 5;
+ break;
+ case PAPI_L3_DCM:
+ papi_info[i].pmns_position = 6;
+ break;
+ case PAPI_L3_ICM:
+ papi_info[i].pmns_position = 7;
+ break;
+ case PAPI_L1_TCM:
+ papi_info[i].pmns_position = 8;
+ break;
+ case PAPI_L2_TCM:
+ papi_info[i].pmns_position = 9;
+ break;
+ case PAPI_L3_TCM:
+ papi_info[i].pmns_position = 10;
+ break;
+ case PAPI_TLB_DM:
+ papi_info[i].pmns_position = 11;
+ break;
+ case PAPI_TLB_IM:
+ papi_info[i].pmns_position = 12;
+ break;
+ case PAPI_TLB_TL:
+ papi_info[i].pmns_position = 13;
+ break;
+ case PAPI_L1_LDM:
+ papi_info[i].pmns_position = 14;
+ break;
+ case PAPI_L1_STM:
+ papi_info[i].pmns_position = 15;
+ break;
+ case PAPI_L2_LDM:
+ papi_info[i].pmns_position = 16;
+ break;
+ case PAPI_L2_STM:
+ papi_info[i].pmns_position = 17;
+ break;
+ case PAPI_CA_SNP:
+ papi_info[i].pmns_position = 18;
+ break;
+ case PAPI_CA_SHR:
+ papi_info[i].pmns_position = 19;
+ break;
+ case PAPI_CA_CLN:
+ papi_info[i].pmns_position = 20;
+ break;
+ case PAPI_CA_INV:
+ papi_info[i].pmns_position = 21;
+ break;
+ case PAPI_CA_ITV:
+ papi_info[i].pmns_position = 22;
+ break;
+ case PAPI_L3_LDM:
+ papi_info[i].pmns_position = 23;
+ break;
+ case PAPI_L3_STM:
+ papi_info[i].pmns_position = 24;
+ break;
+ case PAPI_BRU_IDL:
+ papi_info[i].pmns_position = 25;
+ break;
+ case PAPI_FXU_IDL:
+ papi_info[i].pmns_position = 26;
+ break;
+ case PAPI_FPU_IDL:
+ papi_info[i].pmns_position = 27;
+ break;
+ case PAPI_LSU_IDL:
+ papi_info[i].pmns_position = 28;
+ break;
+ case PAPI_BTAC_M:
+ papi_info[i].pmns_position = 29;
+ break;
+ case PAPI_PRF_DM:
+ papi_info[i].pmns_position = 30;
+ break;
+ case PAPI_L3_DCH:
+ papi_info[i].pmns_position = 31;
+ break;
+ case PAPI_TLB_SD:
+ papi_info[i].pmns_position = 32;
+ break;
+ case PAPI_CSR_FAL:
+ papi_info[i].pmns_position = 33;
+ break;
+ case PAPI_CSR_SUC:
+ papi_info[i].pmns_position = 34;
+ break;
+ case PAPI_CSR_TOT:
+ papi_info[i].pmns_position = 35;
+ break;
+ case PAPI_MEM_SCY:
+ papi_info[i].pmns_position = 36;
+ break;
+ case PAPI_MEM_RCY:
+ papi_info[i].pmns_position = 37;
+ break;
+ case PAPI_MEM_WCY:
+ papi_info[i].pmns_position = 38;
+ break;
+ case PAPI_STL_ICY:
+ papi_info[i].pmns_position = 39;
+ break;
+ case PAPI_FUL_ICY:
+ papi_info[i].pmns_position = 40;
+ break;
+ case PAPI_STL_CCY:
+ papi_info[i].pmns_position = 41;
+ break;
+ case PAPI_FUL_CCY:
+ papi_info[i].pmns_position = 42;
+ break;
+ case PAPI_HW_INT:
+ papi_info[i].pmns_position = 43;
+ break;
+ case PAPI_BR_UCN:
+ papi_info[i].pmns_position = 44;
+ break;
+ case PAPI_BR_CN:
+ papi_info[i].pmns_position = 45;
+ break;
+ case PAPI_BR_TKN:
+ papi_info[i].pmns_position = 46;
+ break;
+ case PAPI_BR_NTK:
+ papi_info[i].pmns_position = 47;
+ break;
+ case PAPI_BR_MSP:
+ papi_info[i].pmns_position = 48;
+ break;
+ case PAPI_BR_PRC:
+ papi_info[i].pmns_position = 49;
+ break;
+ case PAPI_FMA_INS:
+ papi_info[i].pmns_position = 50;
+ break;
+ case PAPI_TOT_IIS:
+ papi_info[i].pmns_position = 51;
+ break;
+ case PAPI_INT_INS:
+ papi_info[i].pmns_position = 52;
+ break;
+ case PAPI_FP_INS:
+ papi_info[i].pmns_position = 53;
+ break;
+ case PAPI_LD_INS:
+ papi_info[i].pmns_position = 54;
+ break;
+ case PAPI_SR_INS:
+ papi_info[i].pmns_position = 55;
+ break;
+ case PAPI_BR_INS:
+ papi_info[i].pmns_position = 56;
+ break;
+ case PAPI_VEC_INS:
+ papi_info[i].pmns_position = 57;
+ break;
+ case PAPI_RES_STL:
+ papi_info[i].pmns_position = 58;
+ break;
+ case PAPI_FP_STAL:
+ papi_info[i].pmns_position = 59;
+ break;
+ case PAPI_LST_INS:
+ papi_info[i].pmns_position = 60;
+ break;
+ case PAPI_SYC_INS:
+ papi_info[i].pmns_position = 61;
+ break;
+ case PAPI_L1_DCH:
+ papi_info[i].pmns_position = 62;
+ break;
+ case PAPI_L2_DCH:
+ papi_info[i].pmns_position = 63;
+ break;
+ case PAPI_L1_DCA:
+ papi_info[i].pmns_position = 64;
+ break;
+ case PAPI_L2_DCA:
+ papi_info[i].pmns_position = 65;
+ break;
+ case PAPI_L3_DCA:
+ papi_info[i].pmns_position = 66;
+ break;
+ case PAPI_L1_DCR:
+ papi_info[i].pmns_position = 67;
+ break;
+ case PAPI_L2_DCR:
+ papi_info[i].pmns_position = 68;
+ break;
+ case PAPI_L3_DCR:
+ papi_info[i].pmns_position = 69;
+ break;
+ case PAPI_L1_DCW:
+ papi_info[i].pmns_position = 70;
+ break;
+ case PAPI_L2_DCW:
+ papi_info[i].pmns_position = 71;
+ break;
+ case PAPI_L3_DCW:
+ papi_info[i].pmns_position = 72;
+ break;
+ case PAPI_L1_ICH:
+ papi_info[i].pmns_position = 73;
+ break;
+ case PAPI_L2_ICH:
+ papi_info[i].pmns_position = 74;
+ break;
+ case PAPI_L3_ICH:
+ papi_info[i].pmns_position = 75;
+ break;
+ case PAPI_L1_ICA:
+ papi_info[i].pmns_position = 76;
+ break;
+ case PAPI_L2_ICA:
+ papi_info[i].pmns_position = 77;
+ break;
+ case PAPI_L3_ICA:
+ papi_info[i].pmns_position = 78;
+ break;
+ case PAPI_L1_ICR:
+ papi_info[i].pmns_position = 79;
+ break;
+ case PAPI_L2_ICR:
+ papi_info[i].pmns_position = 80;
+ break;
+ case PAPI_L3_ICR:
+ papi_info[i].pmns_position = 81;
+ break;
+ case PAPI_L1_ICW:
+ papi_info[i].pmns_position = 82;
+ break;
+ case PAPI_L2_ICW:
+ papi_info[i].pmns_position = 83;
+ break;
+ case PAPI_L3_ICW:
+ papi_info[i].pmns_position = 84;
+ break;
+ case PAPI_L1_TCH:
+ papi_info[i].pmns_position = 85;
+ break;
+ case PAPI_L2_TCH:
+ papi_info[i].pmns_position = 86;
+ break;
+ case PAPI_L3_TCH:
+ papi_info[i].pmns_position = 87;
+ break;
+ case PAPI_L1_TCA:
+ papi_info[i].pmns_position = 88;
+ break;
+ case PAPI_L2_TCA:
+ papi_info[i].pmns_position = 89;
+ break;
+ case PAPI_L3_TCA:
+ papi_info[i].pmns_position = 90;
+ break;
+ case PAPI_L1_TCR:
+ papi_info[i].pmns_position = 91;
+ break;
+ case PAPI_L2_TCR:
+ papi_info[i].pmns_position = 92;
+ break;
+ case PAPI_L3_TCR:
+ papi_info[i].pmns_position = 93;
+ break;
+ case PAPI_L1_TCW:
+ papi_info[i].pmns_position = 94;
+ break;
+ case PAPI_L2_TCW:
+ papi_info[i].pmns_position = 95;
+ break;
+ case PAPI_L3_TCW:
+ papi_info[i].pmns_position = 96;
+ break;
+ case PAPI_FML_INS:
+ papi_info[i].pmns_position = 97;
+ break;
+ case PAPI_FAD_INS:
+ papi_info[i].pmns_position = 98;
+ break;
+ case PAPI_FDV_INS:
+ papi_info[i].pmns_position = 99;
+ break;
+ case PAPI_FSQ_INS:
+ papi_info[i].pmns_position = 100;
+ break;
+ case PAPI_FNV_INS:
+ papi_info[i].pmns_position = 101;
+ break;
+ case PAPI_FP_OPS:
+ papi_info[i].pmns_position = 102;
+ break;
+ case PAPI_SP_OPS:
+ papi_info[i].pmns_position = 103;
+ break;
+ case PAPI_DP_OPS:
+ papi_info[i].pmns_position = 104;
+ break;
+ case PAPI_VEC_SP:
+ papi_info[i].pmns_position = 105;
+ break;
+ case PAPI_VEC_DP:
+ papi_info[i].pmns_position = 106;
+ break;
+#ifdef PAPI_REF_CYC
+ case PAPI_REF_CYC:
+ papi_info[i].pmns_position = 107;
+ break;
+#endif
+ default:
+ papi_info[i].pmns_position = -1;
+ break;
+ }
+}
+
+static int
+permission_check(int context)
+{
+ if ((ctxtab[context].uid_p && ctxtab[context].uid == 0) ||
+ (ctxtab[context].gid_p && ctxtab[context].gid == 0))
+ return 1;
+ return 0;
+}
+
+static void
+expand_papi_info(int size)
+{
+ if (number_of_events <= size) {
+ size_t new_size = (size + 1) * sizeof(papi_m_user_tuple);
+ papi_info = realloc(papi_info, new_size);
+ if (papi_info == NULL)
+ __pmNoMem("papi_info tuple", new_size, PM_FATAL_ERR);
+ while (number_of_events <= size)
+ memset(&papi_info[number_of_events++], 0, sizeof(papi_m_user_tuple));
+ }
+}
+
+static void
+expand_values(int size)
+{
+ if (size_of_active_counters <= size) {
+ size_t new_size = (size + 1) * sizeof(long_long);
+ values = realloc(values, new_size);
+ if (values == NULL)
+ __pmNoMem("values", new_size, PM_FATAL_ERR);
+ while (size_of_active_counters <= size) {
+ memset(&values[size_of_active_counters++], 0, sizeof(long_long));
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmNotifyErr(LOG_DEBUG, "memsetting to zero, %d counters\n",
+ size_of_active_counters);
+ }
+ }
+ }
+}
+
+static void
+enlarge_ctxtab(int context)
+{
+ /* Grow the context table if necessary. */
+ if (ctxtab_size /* cardinal */ <= context /* ordinal */) {
+ size_t need = (context + 1) * sizeof(struct uid_gid_tuple);
+ ctxtab = realloc(ctxtab, need);
+ if (ctxtab == NULL)
+ __pmNoMem("papi ctx table", need, PM_FATAL_ERR);
+ /* Blank out new entries. */
+ while (ctxtab_size <= context)
+ memset(&ctxtab[ctxtab_size++], 0, sizeof(struct uid_gid_tuple));
+ }
+}
+
+static int
+check_papi_state(int state)
+{
+ int retval;
+ retval = PAPI_state(EventSet, &state);
+ if (retval != PAPI_OK)
+ return PM_ERR_NODATA;
+ return state;
+}
+
+static char *
+papi_string_status(void)
+{
+ int state, retval;
+
+ retval = PAPI_state(EventSet, &state);
+ if (retval != PAPI_OK)
+ return "PAPI_state error.";
+ switch (state) {
+ case PAPI_STOPPED:
+ return "Papi is stopped.";
+ case PAPI_RUNNING:
+ return "Papi is running.";
+ case PAPI_PAUSED:
+ return "Papi is paused";
+ case PAPI_NOT_INIT:
+ return "Papi eventset is defined but not initialized.";
+ case PAPI_OVERFLOWING:
+ return "Papi eventset has overflowing enabled";
+ case PAPI_PROFILING:
+ return "Papi eventset has profiling enabled";
+ case PAPI_MULTIPLEXING:
+ return "Papi eventset has multiplexing enabled";
+ case PAPI_ATTACHED:
+ return "Papi is attached to another process/thread";
+ case PAPI_CPU_ATTACHED:
+ return "Papi is attached to a specific CPU.";
+ default:
+ return "PAPI_state error.";
+ }
+}
+
+/*
+ * A list of all the papi metrics we support -
+ */
+static pmdaMetric metrictab[] = {
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TOT_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TOT_CYC */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_DCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_ICM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_DCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_ICM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_DCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_ICM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_TCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_TCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_TCM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TLB_DM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TLB_IM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TLB_TL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_LDM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_STM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_LDM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_STM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CA_SNP */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CA_SHR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CA_CLN */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CA_INV */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CA_ITV */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_LDM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,24), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_STM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BRU_IDL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FXU_IDL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FPU_IDL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.LSU_IDL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BTAC_M */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.PRF_DM */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_DCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TLB_SD */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CSR_FAL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CSR_SUC */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.CSR_TOT */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.MEM_SCY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.MEM_RCY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,38), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.MEM_WCY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,39), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.STL_ICY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FUL_ICY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,41), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.STL_CCY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,42), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FUL_CCY */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,43), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.HW_INT */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,44), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_UCN */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_CN */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_TKN */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_NTK */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_MSP */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_PRC */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FMA_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.TOT_IIS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.INT_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FP_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.LD_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.SR_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.BR_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.VEC_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.RES_STL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FP_STAL */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.LST_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,61), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.SYC_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,62), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_DCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,63), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_DCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,64), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_DCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,65), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_DCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,66), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_DCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,67), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_DCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,68), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_DCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,69), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_DCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,70), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_DCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_DCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_DCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,73), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_ICH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_ICH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_ICH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_ICA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,77), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_ICA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,78), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_ICA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,79), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_ICR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,80), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_ICR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,81), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_ICR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,82), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_ICW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,83), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_ICW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,84), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_ICW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,85), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_TCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,86), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_TCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,87), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_TCH */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,88), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_TCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,89), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_TCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,90), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_TCA */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,91), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_TCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,92), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_TCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,93), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_TCR */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,94), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L1_TCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,95), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L2_TCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,96), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.L3_TCW */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,97), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FML_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,98), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FAD_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,99), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FDV_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,100), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FSQ_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,101), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FNV_INS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,102), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.FP_OPS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,103), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.SP_OPS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,104), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.DP_OPS */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,105), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.VEC_SP */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,106), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.VEC_DP */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_PAPI,107), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.REF_CYC */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_CONTROL,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.control.enable */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_CONTROL,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.control.reset */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_CONTROL,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.control.disable */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_CONTROL,3), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.control.status */
+
+ { NULL,
+ { PMDA_PMID(CLUSTER_AVAILABLE,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } }, /* papi.available.num_counters */
+
+};
+
+static void
+papi_endContextCallBack(int context)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "end context %d received\n", context);
+
+ /* ensure clients re-using this slot re-authenticate */
+ if (context >= 0 && context < ctxtab_size) {
+ ctxtab[context].uid_p = 0;
+ ctxtab[context].gid_p = 0;
+ }
+}
+
+static int
+papi_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ int running = 0;
+ int sts = 0;
+ int i;
+
+ sts = check_papi_state(sts);
+ if (sts == PAPI_RUNNING && idp->cluster == CLUSTER_PAPI) {
+ sts = PAPI_read(EventSet, values);
+ if (sts != PAPI_OK) {
+ __pmNotifyErr(LOG_ERR, "PAPI_read: %s\n", PAPI_strerror(sts));
+ return PM_ERR_VALUE;
+ }
+ running = 1;
+ }
+
+ switch (idp->cluster) {
+ case CLUSTER_PAPI:
+ if (!running)
+ return PMDA_FETCH_NOVALUES;
+ if (idp->item >= 0 && idp->item <= 107) {
+ // the 'case' && 'idp->item' value we get is the pmns_position
+ for (i = 0; i < number_of_events; i++) {
+ if (papi_info[i].pmns_position == idp->item) {
+ if(papi_info[i].position >= 0 && papi_info[i].papi_event_code){
+ atom->ull = values[papi_info[i].position];
+ return PMDA_FETCH_STATIC;
+ }
+ else
+ return PMDA_FETCH_NOVALUES;
+ }
+ }
+ }
+ return PM_ERR_PMID;
+
+ case CLUSTER_CONTROL:
+ switch (idp->item) {
+ case 0:
+ atom->cp = enable_string; /* papi.control.enable */
+ return PMDA_FETCH_STATIC;
+
+ case 1:
+ // break; /* papi.control.reset */
+ // atom->cp = reset_string;
+ return PM_ERR_NYI;
+
+ case 2:
+ if ((sts = check_papi_state(sts)) == PAPI_RUNNING) {
+ atom->cp = disable_string; /* papi.control.disable */
+ return PMDA_FETCH_STATIC;
+ }
+ return 0;
+
+ case 3:
+ atom->cp = papi_string_status(); /* papi.control.status */
+ return PMDA_FETCH_STATIC;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ break;
+
+ case CLUSTER_AVAILABLE:
+ if (idp->item == 0) {
+ atom->ul = number_of_counters; /* papi.available.num_counters */
+ return PMDA_FETCH_STATIC;
+ }
+ return PM_ERR_PMID;
+
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return PMDA_FETCH_NOVALUES;
+}
+
+static int
+papi_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ if (permission_check(pmda->e_context))
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+ return PM_ERR_PERMISSION;
+}
+
+static int
+remove_metric(unsigned int event, int position)
+{
+ int retval = 0;
+ int state = 0;
+ int restart = 0; // bool to restart running values at the end
+ int i;
+ long_long new_values[size_of_active_counters];
+
+ retval = PAPI_query_event(event);
+ if (retval != PAPI_OK){
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "event not found on this hardware, skipping\n");
+ return retval;
+ }
+
+ /* check to make sure papi is running, otherwise do nothing */
+ state = check_papi_state(state);
+ if (state == PAPI_RUNNING) {
+ restart = 1;
+ retval = PAPI_stop(EventSet, values);
+ if(retval != PAPI_OK)
+ return retval;
+ }
+ state = check_papi_state(state);
+ if (state == PAPI_STOPPED) {
+ /* first, copy the values over to new array */
+ for (i = 0; i < number_of_events; i++)
+ new_values[papi_info[i].position] = values[papi_info[i].position];
+
+ /* workaround a papi bug: fully destroy the eventset and restart it */
+ memset(values, 0, sizeof(values[0])*size_of_active_counters);
+ retval = PAPI_cleanup_eventset(EventSet);
+ if (retval != PAPI_OK)
+ return retval;
+
+ retval = PAPI_destroy_eventset(&EventSet);
+ if (retval != PAPI_OK)
+ return retval;
+
+ number_of_active_counters--;
+ retval = PAPI_create_eventset(&EventSet);
+ if (retval != PAPI_OK)
+ return retval;
+
+ // run through all metrics and adjust position variable as needed
+ for (i = 0; i < number_of_events; i++) {
+ // set event we're removing position to -1
+ if (papi_info[i].position == position) {
+ new_values[papi_info[i].position] = 0;
+ papi_info[i].position = -1;
+ }
+ }
+
+ for (i = 0; i < number_of_events; i++) {
+ if (papi_info[i].position < position)
+ values[papi_info[i].position] = new_values[papi_info[i].position];
+
+ if (papi_info[i].position > position) {
+ papi_info[i].position--;
+ values[papi_info[i].position] = new_values[papi_info[i].position+1];
+ }
+ if (papi_info[i].position >= 0 && papi_info[i].papi_event_code) {
+ retval = PAPI_add_event(EventSet, papi_info[i].papi_event_code);
+ if (retval != PAPI_OK)
+ return retval;
+ }
+ }
+ if (restart && (number_of_active_counters > 0)) {
+ retval = PAPI_start(EventSet);
+ if (retval != PAPI_OK)
+ return retval;
+ }
+ return retval;
+ }
+ return PM_ERR_VALUE;
+}
+
+static int
+add_metric(unsigned int event)
+{
+ int retval = 0;
+ int state = 0;
+ long_long new_values[size_of_active_counters];
+ int i;
+
+ retval = PAPI_query_event(event);
+ if (retval != PAPI_OK){
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "event not found on this hardware, skipping\n");
+ return retval;
+ }
+ /* check status of papi */
+ state = check_papi_state(state);
+ /* add check with number_of_counters */
+ /* stop papi if running? */
+ if (state == PAPI_RUNNING) {
+ for (i = 0; i < size_of_active_counters; i++){
+ if(papi_info[i].position >= 0 && papi_info[i].papi_event_code)
+ new_values[papi_info[i].position] = values[papi_info[i].position];
+ }
+ retval = PAPI_stop(EventSet, values);
+ if (retval != PAPI_OK)
+ return retval;
+ }
+ state = check_papi_state(state);
+ if (state == PAPI_STOPPED) {
+ /* add metric */
+ retval = PAPI_add_event(EventSet, event); //XXX possibly switch this to add_events
+ if (retval != PAPI_OK)
+ return retval;
+
+ for (i = 0; i < size_of_active_counters; i++){
+ if(papi_info[i].position >= 0 && papi_info[i].papi_event_code)
+ values[papi_info[i].position] = new_values[papi_info[i].position];
+ }
+ number_of_active_counters++;
+ retval = PAPI_start(EventSet);
+ return retval;
+ }
+ return PM_ERR_VALUE;
+}
+
+static int
+papi_store(pmResult *result, pmdaExt *pmda)
+{
+ int sts;
+ int i, j, len;
+ const char *delim = " ,";
+ char *substring;
+
+ if (!permission_check(pmda->e_context))
+ return PM_ERR_PERMISSION;
+ for (i = 0; i < result->numpmid; i++) {
+ pmValueSet *vsp = result->vset[i];
+ __pmID_int *idp = (__pmID_int *)&(vsp->pmid);
+ pmAtomValue av;
+
+ if (idp->cluster != CLUSTER_CONTROL)
+ return PM_ERR_PERMISSION;
+
+ switch (idp->item) {
+ case 0: //papi.enable
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_STRING, &av, PM_TYPE_STRING)) < 0)
+ return sts;
+ free(enable_string);
+ enable_string = av.cp;
+ len = strlen(enable_string);
+ substring = strtok(enable_string, delim);
+ while (substring != NULL) {
+ for (j = 0; j < number_of_events; j++) {
+ if (!strcmp(substring, papi_info[j].papi_string_code) && papi_info[j].position < 0) {
+ // add the metric to the set if it's not already there
+ sts = add_metric(papi_info[j].papi_event_code);
+ if (sts == PAPI_OK)
+ papi_info[j].position = number_of_active_counters-1;
+ }
+ }
+ substring = strtok(NULL, delim);
+ }
+ for (j = 0; j < len-1; j++) { // recover from tokenisation
+ if (enable_string[j] == '\0')
+ enable_string[j] = delim[0];
+ }
+ break;
+
+ case 1: //papi.reset
+#if 0 /* not yet implemented */
+ sts = check_papi_state(sts);
+ if (sts == PAPI_RUNNING) {
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_STRING, &av, PM_TYPE_STRING)) < 0)
+ return sts;
+ }
+ sts = PAPI_reset(EventSet);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "reset: %d\n", sts);
+ if (sts != PAPI_OK)
+ return PM_ERR_VALUE;
+ break;
+#else
+ return PM_ERR_NYI;
+#endif
+
+ case 2: //papi.disable
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0],
+ PM_TYPE_STRING, &av, PM_TYPE_STRING)) < 0)
+ return sts;
+ free(disable_string);
+ disable_string = av.cp;
+ len = strlen(disable_string);
+ substring = strtok(disable_string, delim);
+ while (substring != NULL) {
+ for (j = 0; j < size_of_active_counters; j++) {
+ if (!strcmp(substring, papi_info[j].papi_string_code)) {
+ // remove the metric from the set
+ sts = remove_metric(papi_info[j].papi_event_code, papi_info[j].position);
+ if (sts == PAPI_OK)
+ papi_info[j].position = -1;
+ break; //we've found the correct metric, break;
+ }
+ }
+ if (j == size_of_active_counters) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "metric name %s does not match any known metrics\n", substring);
+ sts = 1;
+ }
+ substring = strtok(NULL, delim);
+ }
+ for (j = 0; j < len-1; j++) { // recover from tokenisation
+ if (disable_string[j] == '\0')
+ disable_string[j] = delim[0];
+ }
+ if (sts)
+ return PM_ERR_CONV;
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ return 0;
+}
+
+static int
+papi_text(int ident, int type, char **buffer, pmdaExt *ep)
+{
+ int ec;
+ int i;
+ int position = -1;
+ PAPI_event_info_t info;
+ __pmID_int *pmidp = (__pmID_int*)&ident;
+
+ /* no indoms - we only deal with metric help text */
+ if ((type & PM_TEXT_PMID) != PM_TEXT_PMID)
+ return PM_ERR_TEXT;
+
+ ec = 0 | PAPI_PRESET_MASK;
+ PAPI_enum_event(&ec, PAPI_ENUM_FIRST);
+ for (i = 0; i < number_of_events; i++) {
+ if (pmidp->item == papi_info[i].pmns_position) {
+ position = i;
+ break;
+ }
+ }
+
+ do {
+ if (PAPI_get_event_info(ec, &info) == PAPI_OK) {
+ if (info.event_code == papi_info[position].papi_event_code) {
+ if (type & PM_TEXT_ONELINE)
+ *buffer = info.short_descr;
+ else
+ *buffer = info.long_descr;
+ return 0;
+ }
+ }
+ } while (PAPI_enum_event(&ec, 0) == PAPI_OK);
+
+ return pmdaText(ident, type, buffer, ep);
+}
+
+static int
+papi_internal_init(void)
+{
+ int ec;
+ int sts;
+ int addunderscore;
+ PAPI_event_info_t info;
+ char *substr;
+ char concatstr[10] = {};
+ unsigned int i = 0;
+
+ number_of_counters = PAPI_num_counters();
+ if (number_of_counters < 0){
+ __pmNotifyErr(LOG_ERR, "hardware does not support hardware counters\n");
+ return 1;
+ }
+
+ ec = 0 | PAPI_PRESET_MASK;
+ sts = PAPI_library_init(PAPI_VER_CURRENT);
+ if (sts != PAPI_VER_CURRENT) {
+ __pmNotifyErr(LOG_DEBUG, "PAPI_library_init error!\n");
+ return PM_ERR_GENERIC;
+ }
+
+ sts = PAPI_set_domain(PAPI_DOM_ALL);
+ if (sts != PAPI_OK) {
+ __pmNotifyErr(LOG_DEBUG, "Cannot set the domain to PAPI_DOM_ALL.\n");
+ return PM_ERR_GENERIC;
+ }
+
+ enable_string = (char *)calloc(1, 1);
+ disable_string = (char *)calloc(1, 1);
+ PAPI_enum_event(&ec, PAPI_ENUM_FIRST);
+ do {
+ if (PAPI_get_event_info(ec, &info) == PAPI_OK) {
+ i++;
+ expand_papi_info(i);
+ papi_info[i-1].papi_event_code = info.event_code;
+ substr = strtok(info.symbol, "_");
+ while (substr != NULL) {
+ addunderscore = 0;
+ if (strcmp("PAPI",substr)) {
+ addunderscore = 1;
+ strcat(concatstr, substr);
+ }
+ substr = strtok(NULL, "_");
+ if (substr != NULL && addunderscore) {
+ strcat(concatstr, "_");
+ }
+ }
+ strcpy(papi_info[i-1].papi_string_code, concatstr);
+ memset(&concatstr[0], 0, sizeof(concatstr));
+ papi_info[i-1].position = -1;
+ set_pmns_position(i-1);
+ }
+ } while(PAPI_enum_event(&ec, 0) == PAPI_OK);
+ expand_values(i);
+ if (PAPI_create_eventset(&EventSet) != PAPI_OK) {
+ __pmNotifyErr(LOG_ERR, "PAPI_create_eventset error!\n");
+ return PM_ERR_GENERIC;
+ }
+ return 0;
+
+}
+
+/* use documented in pmdaAttribute(3) */
+static int
+papi_contextAttributeCallBack(int context, int attr,
+ const char *value, int length, pmdaExt *pmda)
+{
+ int id = -1;
+
+ enlarge_ctxtab(context);
+ assert(ctxtab != NULL && context < ctxtab_size);
+
+ switch (attr) {
+ case PCP_ATTR_USERID:
+ ctxtab[context].uid_p = 1;
+ id = atoi(value);
+ ctxtab[context].uid = id;
+ break;
+
+ case PCP_ATTR_GROUPID:
+ ctxtab[context].gid_p = 1;
+ id = atoi(value);
+ ctxtab[context].gid = id;
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (id != 0) {
+ if (pmDebug & DBG_TRACE_AUTH)
+ __pmNotifyErr(LOG_DEBUG, "access denied attr=%d id=%d\n", attr, id);
+ return PM_ERR_PERMISSION;
+ }
+ else if (pmDebug & DBG_TRACE_AUTH)
+ __pmNotifyErr(LOG_DEBUG, "access granted attr=%d id=%d\n", attr, id);
+
+ return 0;
+}
+
+void
+__PMDA_INIT_CALL
+papi_init(pmdaInterface *dp)
+{
+ int nummetrics = sizeof(metrictab)/sizeof(metrictab[0]);
+ int sts;
+
+ if (isDSO) {
+ char mypath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "papi" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_6, "papi DSO", mypath);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->comm.flags |= PDU_FLAG_AUTH;
+
+ if ((sts = papi_internal_init()) != 0) {
+ __pmNotifyErr(LOG_ERR, "papi_internal_init returned %d\n", sts);
+ dp->status = PM_ERR_GENERIC;
+ return;
+ }
+
+ dp->version.six.fetch = papi_fetch;
+ dp->version.six.store = papi_store;
+ dp->version.six.attribute = papi_contextAttributeCallBack;
+ dp->version.any.text = papi_text;
+ pmdaSetFetchCallBack(dp, papi_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, papi_endContextCallBack);
+ pmdaInit(dp, NULL, 0, metrictab, nummetrics);
+}
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+static pmdaOptions opts = {
+ .short_options = "D:d:l:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up agent if running as daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "papi" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_6, pmProgname, PAPI, "papi.log", helppath);
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ pmdaOpenLog(&dispatch);
+ papi_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ free(ctxtab);
+ free(papi_info);
+ free(values);
+
+ exit(0);
+}
diff --git a/src/pmdas/papi/pmns b/src/pmdas/papi/pmns
new file mode 100644
index 0000000..d4b8eac
--- /dev/null
+++ b/src/pmdas/papi/pmns
@@ -0,0 +1,139 @@
+/*
+ * Metrics for papi PMDA
+ *
+ * Copyright (c) 2014 Red Hat, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+papi {
+ TOT_INS PAPI:0:0
+ TOT_CYC PAPI:0:1
+ L1_DCM PAPI:0:2
+ L1_ICM PAPI:0:3
+ L2_DCM PAPI:0:4
+ L2_ICM PAPI:0:5
+ L3_DCM PAPI:0:6
+ L3_ICM PAPI:0:7
+ L1_TCM PAPI:0:8
+ L2_TCM PAPI:0:9
+ L3_TCM PAPI:0:10
+ TLB_DM PAPI:0:11
+ TLB_IM PAPI:0:12
+ TLB_TL PAPI:0:13
+ L1_LDM PAPI:0:14
+ L1_STM PAPI:0:15
+ L2_LDM PAPI:0:16
+ L2_STM PAPI:0:17
+ CA_SNP PAPI:0:18
+ CA_SHR PAPI:0:19
+ CA_CLN PAPI:0:20
+ CA_INV PAPI:0:21
+ CA_ITV PAPI:0:22
+ L3_LDM PAPI:0:23
+ L3_STM PAPI:0:24
+ BRU_IDL PAPI:0:25
+ FXU_IDL PAPI:0:26
+ FPU_IDL PAPI:0:27
+ LSU_IDL PAPI:0:28
+ BTAC_M PAPI:0:29
+ PRF_DM PAPI:0:30
+ L3_DCH PAPI:0:31
+ TLB_SD PAPI:0:32
+ CSR_FAL PAPI:0:33
+ CSR_SUC PAPI:0:34
+ CSR_TOT PAPI:0:35
+ MEM_SCY PAPI:0:36
+ MEM_RCY PAPI:0:37
+ MEM_WCY PAPI:0:38
+ STL_ICY PAPI:0:39
+ FUL_ICY PAPI:0:40
+ STL_CCY PAPI:0:41
+ FUL_CCY PAPI:0:42
+ HW_INT PAPI:0:43
+ BR_UCN PAPI:0:44
+ BR_CN PAPI:0:45
+ BR_TKN PAPI:0:46
+ BR_NTK PAPI:0:47
+ BR_MSP PAPI:0:48
+ BR_PRC PAPI:0:49
+ FMA_INS PAPI:0:50
+ TOT_IIS PAPI:0:51
+ INT_INS PAPI:0:52
+ FP_INS PAPI:0:53
+ LD_INS PAPI:0:54
+ SR_INS PAPI:0:55
+ BR_INS PAPI:0:56
+ VEC_INS PAPI:0:57
+ RES_STL PAPI:0:58
+ FP_STAL PAPI:0:59
+ LST_INS PAPI:0:60
+ SYC_INS PAPI:0:61
+ L1_DCH PAPI:0:62
+ L2_DCH PAPI:0:63
+ L1_DCA PAPI:0:64
+ L2_DCA PAPI:0:65
+ L3_DCA PAPI:0:66
+ L1_DCR PAPI:0:67
+ L2_DCR PAPI:0:68
+ L3_DCR PAPI:0:69
+ L1_DCW PAPI:0:70
+ L2_DCW PAPI:0:71
+ L3_DCW PAPI:0:72
+ L1_ICH PAPI:0:73
+ L2_ICH PAPI:0:74
+ L3_ICH PAPI:0:75
+ L1_ICA PAPI:0:76
+ L2_ICA PAPI:0:77
+ L3_ICA PAPI:0:78
+ L1_ICR PAPI:0:79
+ L2_ICR PAPI:0:80
+ L3_ICR PAPI:0:81
+ L1_ICW PAPI:0:82
+ L2_ICW PAPI:0:83
+ L3_ICW PAPI:0:84
+ L1_TCH PAPI:0:85
+ L2_TCH PAPI:0:86
+ L3_TCH PAPI:0:87
+ L1_TCA PAPI:0:88
+ L2_TCA PAPI:0:89
+ L3_TCA PAPI:0:90
+ L1_TCR PAPI:0:91
+ L2_TCR PAPI:0:92
+ L3_TCR PAPI:0:93
+ L1_TCW PAPI:0:94
+ L2_TCW PAPI:0:95
+ L3_TCW PAPI:0:96
+ FML_INS PAPI:0:97
+ FAD_INS PAPI:0:98
+ FDV_INS PAPI:0:99
+ FSQ_INS PAPI:0:100
+ FNV_INS PAPI:0:101
+ FP_OPS PAPI:0:102
+ SP_OPS PAPI:0:103
+ DP_OPS PAPI:0:104
+ VEC_SP PAPI:0:105
+ VEC_DP PAPI:0:106
+ REF_CYC PAPI:0:107
+ control
+ available
+}
+
+papi.control {
+ enable PAPI:1:0
+ reset PAPI:1:1
+ disable PAPI:1:2
+ status PAPI:1:3
+}
+
+papi.available {
+ num_counters PAPI:2:0
+}
diff --git a/src/pmdas/papi/root b/src/pmdas/papi/root
new file mode 100644
index 0000000..d4dfbed
--- /dev/null
+++ b/src/pmdas/papi/root
@@ -0,0 +1,9 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { papi }
+
+#include "pmns"
diff --git a/src/pmdas/pdns/GNUmakefile b/src/pmdas/pdns/GNUmakefile
new file mode 100644
index 0000000..d5496d3
--- /dev/null
+++ b/src/pmdas/pdns/GNUmakefile
@@ -0,0 +1,48 @@
+#!gmake
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = pdns
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/pdns/Install b/src/pmdas/pdns/Install
new file mode 100644
index 0000000..afc09ea
--- /dev/null
+++ b/src/pmdas/pdns/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the pdns PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=pdns
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/pdns/Remove b/src/pmdas/pdns/Remove
new file mode 100644
index 0000000..372e843
--- /dev/null
+++ b/src/pmdas/pdns/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the pdns PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=pdns
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/pdns/pmdapdns.pl b/src/pmdas/pdns/pmdapdns.pl
new file mode 100644
index 0000000..a6177d0
--- /dev/null
+++ b/src/pmdas/pdns/pmdapdns.pl
@@ -0,0 +1,395 @@
+#
+# Copyright (c) 2009-2011 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use Time::HiRes qw ( time );
+
+use vars qw( $pmda );
+my $pdns_control = 'pdns_control';
+my $rec_control = 'rec_control';
+
+my $pdns_disable = 0;
+my $rec_disable = 0;
+
+my $cached = 0;
+my %vals = ();
+my %vals_rec_answers = ();
+
+sub pdns_fetch
+{
+ my $now = time;
+
+ if ($now - $cached > 1.0) {
+ # $pmda->log("pdns_fetch_callback update now:$now cached:$cached\n");
+
+ %vals = ();
+ %vals_rec_answers = ();
+
+ # get the authorative server stats
+ if ($pdns_disable == 0 && open(PIPE, "$pdns_control list |")) {
+ $_ = <PIPE>;
+ close PIPE;
+
+ $_ =~ s/-/_/g;
+ $_ =~ s/,$//;
+ for my $kv (split(/,/, $_)) {
+ if ("$kv" eq '') {
+ last;
+ }
+
+ my ($k, $v) = split(/=/, $kv);
+ $vals{$k} = $v;
+ }
+ } else {
+ $pdns_disable = 1;
+ }
+
+ # get the recursive server stats
+ if ($rec_disable == 0 && open(PIPE, "$rec_control get-all |")) {
+ while($_ = <PIPE>) {
+ $_ =~ s/-/_/g;
+ my ($k, $v) = split(/\t/, $_);
+
+ if ($k eq 'answers0_1') {
+ $vals_rec_answers{0} = $v;
+ } elsif ($k eq 'answers1_10') {
+ $vals_rec_answers{1} = $v;
+ } elsif ($k eq 'answers10_100') {
+ $vals_rec_answers{2} = $v;
+ } elsif ($k eq 'answers100_1000') {
+ $vals_rec_answers{3} = $v;
+ } elsif ($k eq 'answers_slow') {
+ $vals_rec_answers{4} = $v;
+ } else {
+ $vals{"recursor.$k"} = $v;
+ }
+ }
+ close PIPE;
+ } else {
+ $rec_disable = 1;
+ }
+
+ $cached = $now;
+ }
+}
+
+sub pdns_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+
+ # $pmda->log("pdns_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+
+ $metric_name =~ s/^pdns\.//;
+
+ if ($metric_name eq 'recursor.answers') {
+ if ($inst == PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ return ($vals_rec_answers{$inst}, 1) if (defined($vals_rec_answers{$inst}));
+ } else {
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+ return ($vals{$metric_name}, 1) if (defined($vals{$metric_name}));
+ }
+ return (PM_ERR_APPVERSION, 0);
+}
+
+$pmda = PCP::PMDA->new('pdns', 101);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.corrupt_packets", '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.deferred_cache_inserts", '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.deferred_cache_lookup", '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_USEC,0),
+ "pdns.latency", '', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.packetcache_hit", '', '');
+$pmda->add_metric(pmda_pmid(0,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.packetcache_miss", '', '');
+$pmda->add_metric(pmda_pmid(0,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.packetcache_size", '', '');
+$pmda->add_metric(pmda_pmid(0,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.qsize_q", '', '');
+$pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.query_cache_hit", '', '');
+$pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.query_cache_miss", '', '');
+$pmda->add_metric(pmda_pmid(0,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursing_answers", '', '');
+$pmda->add_metric(pmda_pmid(0,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursing_questions", '', '');
+$pmda->add_metric(pmda_pmid(0,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.servfail_packets", '', '');
+$pmda->add_metric(pmda_pmid(0,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.tcp_answers", '', '');
+$pmda->add_metric(pmda_pmid(0,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.tcp_queries", '', '');
+$pmda->add_metric(pmda_pmid(0,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.timedout_packets", '', '');
+$pmda->add_metric(pmda_pmid(0,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp_answers", '', '');
+$pmda->add_metric(pmda_pmid(0,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp_queries", '', '');
+$pmda->add_metric(pmda_pmid(0,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp4_answers", '', '');
+$pmda->add_metric(pmda_pmid(0,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp4_queries", '', '');
+$pmda->add_metric(pmda_pmid(0,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp6_answers", '', '');
+$pmda->add_metric(pmda_pmid(0,21), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.udp6_queries", '', '');
+
+my $recursor_answers_indom = 1;
+my @recursor_answers_dom = (
+ 0 => '0-1 ms',
+ 1 => '1-10 ms',
+ 2 => '10-100 ms',
+ 3 => '100-1000 ms',
+ 4 => '1000+ ms',
+ );
+
+$pmda->add_metric(pmda_pmid(1,0),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.all_outqueries",
+ 'counts the number of outgoing UDP queries since starting', '');
+$pmda->add_metric(pmda_pmid(1,1),PM_TYPE_U64, $recursor_answers_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.answers",
+ 'counts the number of queries answered within X miliseconds', '');
+$pmda->add_metric(pmda_pmid(1,2),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.cache_entries",
+ 'the number of entries in the cache', '');
+$pmda->add_metric(pmda_pmid(1,3),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.cache_hits",
+ 'counts the number of cache hits since starting', '');
+$pmda->add_metric(pmda_pmid(1,4),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.cache_misses",
+ 'counts the number of cache misses since starting', '');
+$pmda->add_metric(pmda_pmid(1,5),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.chain_resends",
+ 'number of queries chained to existing outstanding query', '');
+$pmda->add_metric(pmda_pmid(1,6),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.client_parse_errors",
+ 'counts number of client packets that could not be parsed', '');
+$pmda->add_metric(pmda_pmid(1,7),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.concurrent_queries",
+ 'shows the number of MThreads currently running', '');
+$pmda->add_metric(pmda_pmid(1,8),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.dlg_only_drops",
+ 'number of records dropped because of delegation only setting', '');
+$pmda->add_metric(pmda_pmid(1,9),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.dont_outqueries",
+ 'number of outgoing queries dropped because of "dont-query" setting', '');
+$pmda->add_metric(pmda_pmid(1,10),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.ipv6_outqueries",
+ 'number of outgoing queries over IPv6', '');
+$pmda->add_metric(pmda_pmid(1,11),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.negcache_entries",
+ 'shows the number of entries in the Negative answer cache', '');
+$pmda->add_metric(pmda_pmid(1,12),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.noerror_answers",
+ 'counts the number of times it answered NOERROR since starting', '');
+$pmda->add_metric(pmda_pmid(1,13),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.nsspeeds_entries",
+ 'shows the number of entries in the NS speeds map', '');
+$pmda->add_metric(pmda_pmid(1,14),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.nsset_invalidations",
+ 'number of times an nsset was dropped because it no longer worked', '');
+$pmda->add_metric(pmda_pmid(1,15),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.nxdomain_answers",
+ 'counts the number of times it answered NXDOMAIN since starting', '');
+$pmda->add_metric(pmda_pmid(1,16),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.outgoing_timeouts",
+ 'counts the number of timeouts on outgoing UDP queries since starting', '');
+$pmda->add_metric(pmda_pmid(1,17),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.over_capacity_drops",
+ 'Questions dropped because over maximum concurrent query limit', '');
+$pmda->add_metric(pmda_pmid(1,18),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.packetcache_entries",
+ 'Size of packet cache', '');
+$pmda->add_metric(pmda_pmid(1,19),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.packetcache_hits",
+ 'Packet cache hits', '');
+$pmda->add_metric(pmda_pmid(1,20),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.packetcache_misses",
+ 'Packet cache misses', '');
+$pmda->add_metric(pmda_pmid(1,21),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_USEC,0),
+ "pdns.recursor.qa_latency",
+ 'shows the current latency average, in microseconds', '');
+$pmda->add_metric(pmda_pmid(1,22),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.questions",
+ 'counts all End-user initiated queries with the RD bit set', '');
+$pmda->add_metric(pmda_pmid(1,23),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.resource_limits",
+ 'counts number of queries that could not be performed because of resource limits', '');
+$pmda->add_metric(pmda_pmid(1,24),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.server_parse_errors",
+ 'counts number of server replied packets that could not be parsed', '');
+$pmda->add_metric(pmda_pmid(1,25),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.servfail_answers",
+ 'counts the number of times it answered SERVFAIL since starting', '');
+$pmda->add_metric(pmda_pmid(1,26),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.spoof_prevents",
+ 'number of times PowerDNS considered itself spoofed, and dropped the data', '');
+$pmda->add_metric(pmda_pmid(1,27),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ "pdns.recursor.sys_msec",
+ 'number of CPU milliseconds spent in "system" mode', '');
+$pmda->add_metric(pmda_pmid(1,28),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.tcp_client_overflow",
+ 'number of times an IP address was denied TCP access because it already had too many connections', '');
+$pmda->add_metric(pmda_pmid(1,29),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.tcp_outqueries",
+ 'counts the number of outgoing TCP queries since starting', '');
+$pmda->add_metric(pmda_pmid(1,30),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.tcp_questions",
+ 'counts all incoming TCP queries (since starting)', '');
+$pmda->add_metric(pmda_pmid(1,31),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.throttled_out",
+ 'counts the number of throttled outgoing UDP queries since starting', '');
+$pmda->add_metric(pmda_pmid(1,32),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0),
+ "pdns.recursor.throttle_entries",
+ 'shows the number of entries in the throttle map', '');
+$pmda->add_metric(pmda_pmid(1,33),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.unauthorized_tcp",
+ 'number of TCP questions denied because of allow-from restrictions', '');
+$pmda->add_metric(pmda_pmid(1,34),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.unauthorized_udp",
+ 'number of UDP questions denied because of allow-from restrictions', '');
+$pmda->add_metric(pmda_pmid(1,35),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.unexpected_packets",
+ 'number of answers from remote servers that were unexpected (might point to spoofing)', '');
+$pmda->add_metric(pmda_pmid(1,36),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "pdns.recursor.uptime",
+ 'number of seconds process has been running', '');
+$pmda->add_metric(pmda_pmid(1,37),PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ "pdns.recursor.user_msec",
+ 'number of CPU milliseconds spent in "user" mode', '');
+
+$pmda->add_indom($recursor_answers_indom, \@recursor_answers_dom, '', '');
+$pmda->set_fetch_callback(\&pdns_fetch_callback);
+$pmda->set_fetch(\&pdns_fetch);
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdapdns - PowerDNS performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdapdns> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from the PowerDNS authorative daemon as well as the recursive
+resolver.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the PowerDNS performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/pdns
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/pdns
+ # ./Remove
+
+B<pmdapdns> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/pdns/Install
+
+installation script for the B<pmdapdns> agent
+
+=item $PCP_PMDAS_DIR/pdns/Remove
+
+undo installation script for the B<pmdapdns> agent
+
+=item $PCP_LOG_DIR/pmcd/pdns.log
+
+default log file for error messages from B<pmdapdns>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), pdns_control(8), and rec_control(1).
diff --git a/src/pmdas/pmcd/GNUmakefile b/src/pmdas/pmcd/GNUmakefile
new file mode 100644
index 0000000..1534531
--- /dev/null
+++ b/src/pmdas/pmcd/GNUmakefile
@@ -0,0 +1,55 @@
+#!gmake
+#
+# Copyright (c) 2000-2001,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# pmcd PMDA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+DFILES = help
+LTARGETS = help.dir
+LDIRT = *.log *.dir *.pag domain.h so_locations
+
+TARGETS = help.dir
+LSRCFILES = help root_pmcd
+
+SUBDIRS = src
+PMDADIR = $(PCP_PMDAS_DIR)/pmcd
+CONF_LINE = "pmcd 2 dso pmcd_init $(PCP_PMDAS_DIR)/pmcd/pmda_pmcd.$(DSOSUFFIX)"
+
+default_pcp default :: $(TARGETS)
+
+default_pcp default :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install_pcp install :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp install :: $(SUBDIRS)
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 help.dir help.pag $(PMDADIR)
+ $(INSTALL) -m 644 root_pmcd $(PCP_VAR_DIR)/pmns/root_pmcd
+
+help.dir: help root_pmcd
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -v 2 -n root_pmcd -o help < help
+
+include $(BUILDRULES)
diff --git a/src/pmdas/pmcd/help b/src/pmdas/pmcd/help
new file mode 100644
index 0000000..402ecf2
--- /dev/null
+++ b/src/pmdas/pmcd/help
@@ -0,0 +1,533 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pmcd PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help text goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ 2.1 Instance domain "pmloggers" from PMCD PMDA
+This is the list of currently active pmlogger instances on the same
+machine as this PMCD. The instance names are the process ids of the
+pmlogger instances. The primary pmlogger has an extra instance with the
+instance name "primary" and an instance id of zero (in addition to its
+normal process id instance).
+
+@ 2.2 pmcd control register Instance Domain
+One instance per pmcd control register.
+
+The internal instance identifiers are the numbers 0 to 15.
+The external instance names are he ASCII equivalent of the internal
+instance identifiers.
+
+@ 2.3 PMDA Instance Domain
+One instance per PMDA managed by PMCD. The external and internal instance
+identifiers are taken from the first two fields of the PMDA specification
+in $PCP_PMCDCONF_PATH.
+
+@ 2.4 pmie Instance Domain
+One instance per running pmie process. The internal and external instance
+identifiers are the process ids of the pmie instances.
+
+@ 2.5 buffer pool Instance Domain
+The instances are as follows:
+
+ 1024 1024-byte PDU buffers managed by __pmFindPDUBuf, __pmPinPDUBuf
+ and __pmUnpinPDUBuf
+ 2048 2-Kbyte PDU buffers managed by __pmFindPDUBuf, __pmPinPDUBuf
+ and __pmUnpinPDUBuf
+ 4096 3-Kbyte or 4-Kbyte PDU buffers managed by __pmFindPDUBuf,
+ __pmPinPDUBuf and __pmUnpinPDUBuf
+ 8192 5-Kbyte, 6-Kbyte, 7-Kbyte or 8-Kbyte PDU buffers managed by
+ __pmFindPDUBuf, __pmPinPDUBuf and __pmUnpinPDUBuf
+ 8192+ PDU buffers larger that 8-Kbyte managed by __pmFindPDUBuf,
+ __pmPinPDUBuf and __pmUnpinPDUBuf
+
+@ pmcd.numagents Number of agents (PMDAs) currently connected to PMCD
+The number of agents (PMDAs) currently connected to PMCD. This may differ
+from the number of agents configured in $PCP_PMCDCONF_PATH if agents have
+terminated and/or been timed-out by PMCD.
+
+@ pmcd.numclients Number of clients currently connected to PMCD
+The number of connections open to client programs retrieving information
+from PMCD.
+
+@ pmcd.datasize Space allocated for PMCD and DSO agents' data segment (K)
+This metric returns the amount of memory in kilobytes allocated for the
+data segment of PMCD and any DSO agents (PMDAs) that it has loaded.
+
+This is handy for tracing memory utilization (and leaks) in DSOs during
+development.
+
+@ pmcd.buf.alloc Allocated buffers in internal memory pools
+This metric returns the number of allocated buffers for the various buffer
+pools used by pmcd.
+
+This is handy for tracing memory utilization (and leaks) in DSOs during
+development.
+
+@ pmcd.buf.free Free buffers in internal memory pools
+This metric returns the number of free buffers for the various buffer
+pools used by pmcd.
+
+This is handy for tracing memory utilization (and leaks) in DSOs during
+development.
+
+@ pmcd.control.timeout Timeout interval for slow/hung agents (PMDAs)
+PDU exchanges with agents (PMDAs) managed by PMCD are subject to timeouts
+which detect and clean up slow or disfunctional agents. This metric
+returns the current timeout period in seconds being used for the agents.
+If the value is zero, timeouts are not being used. This corresponds to
+the -t option described in the man page, pmcd(1).
+
+It is possible to store a new timeout value into this metric. Storing zero
+will turn off timeouts. Subsequent storing of a non-zero value will turn
+on the timeouts again.
+
+@ pmcd.control.debug Current value of PMCD debug flags
+The current value of the PMCD debug flags. This is a bit-wise OR of the
+flags described in the output of pmdbg -l. The PMCD-specific flags are:
+
+ DBG_TRACE_APPL0 2048 Trace agent & client I/O and termination
+ DBG_TRACE_APPL1 4096 Trace host access control
+ DBG_TRACE_APPL2 8192 Trace config file scanner and parser
+
+It is possible to store values into this metric. Diagnostic output is
+written to the PMCD log file (usually $PCP_LOG_DIR/pmcd/pmcd.log).
+
+Setting this metric to -1 terminates PMCD.
+
+@ pmcd.pdu_in.total Total PDUs received by PMCD
+Running total of all BINARY mode PDUs received by the PMCD from clients
+and agents.
+
+@ pmcd.pdu_in.error ERROR PDUs received by PMCD
+Running total of BINARY mode ERROR PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.result RESULT PDUs received by PMCD
+Running total of BINARY mode RESULT PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.profile PROFILE PDUs received by PMCD
+Running total of BINARY mode PROFILE PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.fetch FETCH PDUs received by PMCD
+Running total of BINARY mode FETCH PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.desc_req DESC_REQ PDUs received by PMCD
+Running total of BINARY mode DESC_REQ PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.desc DESC PDUs received by PMCD
+Running total of BINARY mode DESC PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.instance_req INSTANCE_REQ PDUs received by PMCD
+Running total of BINARY mode INSTANCE_REQ PDUs received by the PMCD
+from clients and agents.
+
+@ pmcd.pdu_in.instance INSTANCE PDUs received by PMCD
+Running total of BINARY mode INSTANCE PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.text_req TEXT_REQ PDUs received by PMCD
+Running total of BINARY mode TEXT_REQ PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.text TEXT PDUs received by PMCD
+Running total of BINARY mode TEXT PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.control_req CONTROL_REQ PDUs received by PMCD
+Running total of BINARY mode CONTROL_REQ PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.creds CREDS PDUs received by PMCD
+Running total of BINARY mode CREDS PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.pmns_ids PMNS_IDS PDUs received by PMCD
+Running total of BINARY mode PMNS_IDS PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.pmns_names PMNS_NAMES PDUs received by PMCD
+Running total of BINARY mode PMNS_NAMES PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.pmns_child PMNS_CHILD PDUs received by PMCD
+Running total of BINARY mode PMNS_CHILD PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.pmns_traverse PMNS_TRAVERSE PDUs received by PMCD
+Running total of BINARY mode PMNS_TRAVERSE PDUs received by the PMCD from
+clients and agents.
+
+@ pmcd.pdu_in.auth AUTH PDUs received by PMCD
+Running total of BINARY mode AUTH PDUs received by the PMCD from
+clients and agents. These PDUs are used for authentication.
+
+@ pmcd.pdu_out.total Total PDUs sent by PMCD
+Running total of all BINARY mode PDUs sent by the PMCD to clients and
+agents.
+
+@ pmcd.pdu_out.error ERROR PDUs sent by PMCD
+Running total of BINARY mode ERROR PDUs sent by the PMCD to clients and
+agents.
+
+@ pmcd.pdu_out.result RESULT PDUs sent by PMCD
+Running total of BINARY mode RESULT PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.profile PROFILE PDUs sent by PMCD
+Running total of BINARY mode PROFILE PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.fetch FETCH PDUs sent by PMCD
+Running total of BINARY mode FETCH PDUs sent by the PMCD to clients and
+agents.
+
+@ pmcd.pdu_out.desc_req DESC_REQ PDUs sent by PMCD
+Running total of BINARY mode DESC_REQ PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.desc DESC PDUs sent by PMCD
+Running total of BINARY mode DESC PDUs sent by the PMCD to clients and
+agents.
+
+@ pmcd.pdu_out.instance_req INSTANCE_REQ PDUs sent by PMCD
+Running total of BINARY mode INSTANCE_REQ PDUs sent by the PMCD to
+clients and agents.
+
+@ pmcd.pdu_out.instance INSTANCE PDUs sent by PMCD
+Running total of BINARY mode INSTANCE PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.text_req TEXT_REQ PDUs sent by PMCD
+Running total of BINARY mode TEXT_REQ PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.text TEXT PDUs sent by PMCD
+Running total of BINARY mode TEXT PDUs sent by the PMCD to clients and
+agents.
+
+@ pmcd.pdu_out.control_req CONTROL_REQ PDUs sent by PMCD
+Running total of BINARY mode CONTROL_REQ PDUs sent by the PMCD to
+clients and agents.
+
+@ pmcd.pdu_out.creds CREDS PDUs sent by PMCD
+Running total of BINARY mode CREDS PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.pmns_ids PMNS_IDS PDUs sent by PMCD
+Running total of BINARY mode PMNS_IDS PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.pmns_names PMNS_NAMES PDUs sent by PMCD
+Running total of BINARY mode PMNS_NAMES PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.pmns_child PMNS_CHILD PDUs sent by PMCD
+Running total of BINARY mode PMNS_CHILD PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.pmns_traverse PMNS_TRAVERSE PDUs sent by PMCD
+Running total of BINARY mode PMNS_TRAVERSE PDUs sent by the PMCD to clients
+and agents.
+
+@ pmcd.pdu_out.auth AUTH PDUs sent by PMCD
+Running total of BINARY mode AUTH PDUs sent by the PMCD to clients
+and agents. These PDUs are used for authentication.
+
+@ pmcd.pmlogger.host host where active pmlogger is running
+The fully qualified domain name of the host on which a pmlogger
+instance is running.
+
+The instance names are process ids of the active pmloggers. The
+primary pmlogger has an extra instance with the instance name "primary"
+and an instance id of zero (in addition to its normal process id
+instance).
+
+@ pmcd.pmlogger.port control port for active pmlogger
+Each pmlogger instance has a port for receiving log control
+information. This metric is a list of the active pmlogger control
+ports on the same machine as this PMCD (i.e. the host identified in the
+corresponding pmcd.pmlogger.host metric).
+
+The instance names are process ids of the active pmloggers. The
+primary pmlogger has an extra instance with the instance name "primary"
+and an instance id of zero (in addition to its normal process id
+instance).
+
+@ pmcd.pmlogger.archive full pathname to archive basename for active pmlogger
+The full pathname through the filesystem on the corresponding host
+(pmcd.pmlogger.host) that is the base name for the archive log files.
+
+The instance names are process ids of the active pmloggers. The
+primary pmlogger has an extra instance with the instance name "primary"
+and an instance id of zero (in addition to its normal process id
+instance).
+
+@ pmcd.pmlogger.pmcd_host host from which active pmlogger is fetching metrics
+The fully qualified domain name of the host from which a pmlogger
+instance is fetching metrics to be archived.
+
+The instance names are process ids of the active pmloggers. The
+primary pmlogger has an extra instance with the instance name "primary"
+and an instance id of zero (in addition to its normal process id
+instance).
+
+@ pmcd.timezone local $TZ
+Value for the $TZ environment variable where the PMCD is running.
+Enables determination of "local" time for timestamps returned via
+PMCD from a remote host.
+
+@ pmcd.hostname local hostname
+A reasonably unique identifier of the PMCD installation, for use
+by pmlogger or other tools to identify the source principal of
+the data (as distinct from identifying the connection/protocol
+used to reach it).
+
+@ pmcd.simabi Procedure call model and ABI version of this PMCD
+SIM is the subprogram interface model (originally from the MIPS object
+code formats), and ABI is the application binary interface. Both
+relate to the way the PMCD binary was compiled and linked.
+
+Usually DSO PMDAs must be compiled and linked in the same way before
+they can be used with PMCD.
+
+On some platforms this metric is not available.
+
+@ pmcd.version PMCD version
+
+@ pmcd.control.register a vector of registers that may be set by users
+A vector of 16 32-bit registers that are identified by the instance
+identifiers 0 through 15.
+
+The register contents are initially zero, but may be subsequently
+modified to be an arbitrary value using pmStore(3) or pmstore(1).
+
+The values are not used internally, but rather act as a repository into
+which operational information might be stored, and then exported to
+modify the behavior of client programs, e.g. inhibit pmie(1) rule
+firing, or trigger a status indicator. In this way,
+pmcd.control.register acts like a primitive bulletin board.
+
+Example use might be as follows
+ register[0] telephone no. of person assigned to current system problem
+ register[1] telephone no. of person assigned to current network problem
+ register[2] ORACLE database is down
+ register[3] backup in progress
+ register[4] shopping days to Christmas
+
+@ pmcd.control.traceconn control PMCD connection event tracing
+Set to 1 to enable PMCD event tracing for all connection-related
+events for clients and PMDAs.
+
+Set to 0 to disable PMCD connection event tracing.
+
+@ pmcd.control.tracepdu control PMCD PDU event tracing
+Set to 1 to enable PMCD event tracing for all PDUs sent and received
+by PMCD.
+
+Set to 0 to disable PMCD PDU event tracing.
+
+@ pmcd.control.tracenobuf control buffering of PMCD event tracing
+Set to 1 to enable unbuffered PMCD event tracing, where each event is
+reported as it happens.
+
+Set to 0 to enable buffering of PMCD event traces (this is the default),
+and event traces will only be dumped or reported when an error occurs or
+a value is stored into the PCP metric pmcd.control.dumptrace.
+
+@ pmcd.control.tracebufs number of buffers for PMCD event tracing
+Defaults to 20. May be changed dynamically.
+
+@ pmcd.control.dumptrace force dump of PMCD event tracing buffers
+Storing any value into this metric causes the PMCD event trace buffers to
+be dumped to PMCD's log file.
+
+@ pmcd.control.sighup force PMCD reset via SIGHUP
+Storing any value into this metric causes PMCD to be reset by sending
+itself a SIGHUP signal.
+
+On reset (either by storing into pmcd.control.sighup or by sending PMCD a
+SIGHUP directly), PMCD will restart any failed PMDAs and reload the PMNS
+if it has been changed.
+
+@ pmcd.control.dumpconn force dump of PMCD client connections
+Storing any value into this metric causes the details of the current PMCD
+client connections to be dumped to PMCD's log file.
+
+@ pmcd.agent.type PMDA type
+From $PCP_PMCDCONF_PATH, this metric encodes the PMDA type as follows:
+ (x << 1) | y
+where "x" is the IPC type between PMCD and the PMDA, i.e. 0 for DSO, 1
+for socket or 2 for pipe, and "y" is the message passing style, i.e.
+0 for binary or 1 for ASCII.
+
+@ pmcd.agent.status PMDA status
+This metric encodes the current status of each PMDA. The default value
+is 0 if the PMDA is active.
+
+Other values encode various degrees of PMDA difficulty in three bit fields
+(bit 0 is the low-order bit) as follows:
+
+bits 7..0
+ 1 the PMDA is connected, but not yet "ready" to accept requests
+ from the PMDA
+ 2 the PMDA has exited of its own accord
+ 4 some error prevented the PMDA being started
+ 8 PMCD stopped communication with the PMDA due to a protocol or
+ timeout error
+
+bits 15..8
+ the exit() status from the PMDA
+
+bits 23..16
+ the number of the signal that terminated the PMDA
+
+@ pmcd.services running PCP services on the local host
+A space-separated string representing all running PCP services with PID
+files in $PCP_RUN_DIR (such as pmcd itself, pmproxy and a few others).
+
+@ pmcd.openfds highest PMCD file descriptor
+The highest file descriptor index used by PMCD for a Client or PMDA
+connection.
+
+@ pmcd.pmie.numrules number of rules being evaluated
+The total number of rules being evaluated by each pmie process.
+
+@ pmcd.pmie.eval.true count of pmie predicates evaluated to true
+The predicate part of a pmie rule can be said to evaluate to either true,
+false, or not known. This metric is a cumulative count of the number of
+rules which have evaluated to true for each pmie instance.
+
+@ pmcd.pmie.eval.false count of pmie predicates evaluated to false
+The predicate part of a pmie rule can be said to evaluate to either true,
+false, or not known. This metric is a cumulative count of the number of
+rules which have evaluated to false for each pmie instance.
+
+@ pmcd.pmie.eval.unknown count of pmie predicates not evaluated
+The predicate part of a pmie rule can be said to evaluate to either true,
+false, or not known. This metric is a cumulative count of the number of
+rules which have not been successfully evaluated. This could be due to not
+yet having sufficient values to evaluate the rule, or a metric fetch may
+have been unsuccessful in retrieving current values for metrics required
+for evaluation of the rule.
+
+@ pmcd.pmie.eval.expected expected rate of rule evaluations
+This is the expected rate of evaluation of pmie rules. The value is
+calculated once when pmie starts, and is the number of pmie rules divided
+by the average time interval over which they are to be evaluated.
+
+@ pmcd.pmie.eval.actual count of actual rule evaluations
+A cumulative count of the pmie rules which have been evaluated.
+
+This value is incremented once for each evaluation of each rule.
+
+@ pmcd.pmie.actions count of rules evaluating to true
+A cumulative count of the evaluated pmie rules which have evaluated to true.
+
+This value is incremented once each time an action is executed. This value
+will always be less than or equal to pmcd.pmie.eval.true because predicates
+which have evaluated to true may be suppressed in the action part of the
+pmie rule, in which case this counter will not be incremented.
+
+@ pmcd.pmie.configfile configuration file name
+The full path in the filesystem to the configuration file containing the
+rules being evaluated by each pmie instance.
+
+If the configuration file was supplied on the standard input, then this
+metric will have the value "<stdin>". If multiple configuration files were
+given to pmie, then the value of this metric will be the first configuration
+file specified.
+
+@ pmcd.pmie.pmcd_host default hostname for pmie instance
+The default host from which pmie is fetching metrics. This is either the
+hostname given to pmie on the command line or the local host. Note that this
+does not consider host names specified in the pmie configuration file (these
+are considered non-default and can be more than one per pmie instance).
+All daemon pmie instances started through pmie_check(1) will have their
+default host passed in on their command line.
+
+@ pmcd.pmie.logfile filename of pmie instance event log
+The file to which each instance of pmie is writting events. No two pmie
+instances can share the same log file. If no logfile was specified when
+pmie was started, this metrics has the value "<none>". All daemon pmie
+instances started through pmie_check(1) must have an associated log file.
+
+@ pmcd.build build version for installed PCP package
+Minor part of the PCP build version numbering. For example on Linux
+with RPM packaging, if the PCP RPM version is pcp-2.5.99-20070323 then
+pmcd.build returns the string "20070323".
+
+@ pmcd.client.whoami optional identification information for clients of pmcd
+This metric is defined over an instance domain containing one entry
+per active client of pmcd. The instance number is a sequence number
+for each client (restarts at 0 each time pmcd is restarted). The value
+of the metric by default is the IP address of the client.
+
+Clients can optionally use pmStore to modify their own "whoami" string
+to provide more useful information about the client.
+
+@ pmcd.client.start_date date and time client connected to pmcd
+The date and time in ctime(2) format on which the client connected
+to pmcd.
+
+@ pmcd.cputime.total CPU time used by pmcd and DSO PMDAs
+Sum of user and system time since pmcd started.
+
+@ pmcd.cputime.per_pdu_in average CPU time per PDU received by pmcd
+When first requested it is the average since pmcd started, so
+pmcd.cputime.total divided by pmcd.pdu_in.total.
+
+Subsequent fetches by a PMAPI client will return the average CPU
+time per PDU received by pmcd (for all clients) since the last time
+the PMAPI client fetched this metric.
+
+@ pmcd.feature.secure status of secure_sockets protocol feature in pmcd
+A value of zero indicates no support, one indicates actively available
+(including configuration and validity of the server side certificates).
+
+@ pmcd.feature.compress status of protocol compression feature in pmcd
+A value of zero indicates no support, one indicates actively available.
+
+@ pmcd.feature.ipv6 status of Internet Protocol Version 6 support in pmcd
+A value of zero indicates no support, one indicates actively available.
+
+@ pmcd.feature.authentication status of per-user authentication support
+A value of zero indicates no support, one indicates actively available.
+
+@ pmcd.feature.creds_required status of required credentials support
+A value of zero indicates no support, one indicates actively available.
+
+@ pmcd.feature.unix_domain_sockets status of unix domain socket support
+A value of zero indicates no support, one indicates actively available.
+
+@ pmcd.feature.service_discovery status of service advertising and discovery
+A value of zero indicates no support, one indicates actively available.
diff --git a/src/pmdas/pmcd/root_pmcd b/src/pmdas/pmcd/root_pmcd
new file mode 100644
index 0000000..010df41
--- /dev/null
+++ b/src/pmdas/pmcd/root_pmcd
@@ -0,0 +1,153 @@
+/*
+ * PMCD metrics name space
+ */
+
+root {
+ pmcd
+}
+
+/*
+ * the domain for the pmcd PMDA ...
+ */
+#ifndef PMCD
+#define PMCD 2
+#endif
+
+pmcd {
+ control
+ pdu_in
+ pdu_out
+ datasize PMCD:0:1
+ numagents PMCD:0:2
+ agent
+ numclients PMCD:0:3
+ pmlogger
+ timezone PMCD:0:5
+ simabi PMCD:0:6
+ version PMCD:0:7
+ services PMCD:0:16
+ openfds PMCD:0:17
+ build PMCD:0:20
+ hostname PMCD:0:21
+ pmie
+ buf
+ client
+ cputime
+ feature
+}
+
+pmcd.control {
+ debug PMCD:0:0
+ timeout PMCD:0:4
+ register PMCD:0:8
+ traceconn PMCD:0:9
+ tracepdu PMCD:0:10
+ tracenobuf PMCD:0:14
+ tracebufs PMCD:0:11
+ dumptrace PMCD:0:12
+ dumpconn PMCD:0:13
+ sighup PMCD:0:15
+}
+
+/*
+ * Note: strange numbering for pmcd.pdu_{in,out}.total for
+ * compatibility with earlier PCP versions
+ */
+
+pmcd.pdu_in {
+ error PMCD:1:0
+ result PMCD:1:1
+ profile PMCD:1:2
+ fetch PMCD:1:3
+ desc_req PMCD:1:4
+ desc PMCD:1:5
+ instance_req PMCD:1:6
+ instance PMCD:1:7
+ text_req PMCD:1:8
+ text PMCD:1:9
+ control_req PMCD:1:10
+ creds PMCD:1:12
+ pmns_ids PMCD:1:13
+ pmns_names PMCD:1:14
+ pmns_child PMCD:1:15
+ total PMCD:1:16
+ pmns_traverse PMCD:1:17
+ auth PMCD:1:18
+}
+
+pmcd.pdu_out {
+ error PMCD:2:0
+ result PMCD:2:1
+ profile PMCD:2:2
+ fetch PMCD:2:3
+ desc_req PMCD:2:4
+ desc PMCD:2:5
+ instance_req PMCD:2:6
+ instance PMCD:2:7
+ text_req PMCD:2:8
+ text PMCD:2:9
+ control_req PMCD:2:10
+ creds PMCD:2:12
+ pmns_ids PMCD:2:13
+ pmns_names PMCD:2:14
+ pmns_child PMCD:2:15
+ total PMCD:2:16
+ pmns_traverse PMCD:2:17
+ auth PMCD:2:18
+}
+
+pmcd.pmlogger {
+ host PMCD:3:3
+ port PMCD:3:0
+ archive PMCD:3:2
+ pmcd_host PMCD:3:1
+}
+
+pmcd.agent {
+ type PMCD:4:0
+ status PMCD:4:1
+}
+
+pmcd.pmie {
+ configfile PMCD:5:0
+ logfile PMCD:5:1
+ pmcd_host PMCD:5:2
+ numrules PMCD:5:3
+ actions PMCD:5:4
+ eval
+}
+
+pmcd.pmie.eval {
+ true PMCD:5:5
+ false PMCD:5:6
+ unknown PMCD:5:7
+ expected PMCD:5:8
+ actual PMCD:5:9
+}
+
+pmcd.buf {
+ alloc PMCD:0:18
+ free PMCD:0:19
+}
+
+pmcd.client {
+ whoami PMCD:6:0
+ start_date PMCD:6:1
+}
+
+pmcd.cputime {
+ total PMCD:7:0
+ per_pdu_in PMCD:7:1
+}
+
+pmcd.feature {
+ secure PMCD:8:0
+ compress PMCD:8:1
+ ipv6 PMCD:8:2
+ authentication PMCD:8:3
+ creds_required PMCD:8:4
+ unix_domain_sockets PMCD:8:5
+ service_discovery PMCD:8:6
+}
+
+#undef PMCD
diff --git a/src/pmdas/pmcd/src/GNUmakefile b/src/pmdas/pmcd/src/GNUmakefile
new file mode 100644
index 0000000..ff5b4ff
--- /dev/null
+++ b/src/pmdas/pmcd/src/GNUmakefile
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+LIBTARGET = pmda_pmcd.$(DSOSUFFIX)
+PMDAINIT = pmcd_init
+
+CFILES = pmcd.c
+LSRCFILES = objstyle
+VERSION_SCRIPT = exports
+LDIRT = $(VERSION_SCRIPT)
+
+# Add to CFLAGS to find files in pmcd/src...
+LCFLAGS = -I$(TOPDIR)/src
+LCFLAGS += -I$(TOPDIR)/src/pmie/src
+
+ifneq (, $(filter linux kfreebsd gnu, $(TARGET_OS)))
+ABI = $(shell ./objstyle)
+LCFLAGS += -DSIM_ABI=\""$(ABI)"\"
+endif
+ifeq "$(TARGET_OS)" "darwin"
+ABI = $(shell ./objstyle)
+LCFLAGS += -DSIM_ABI=\"$(ABI)\"
+endif
+ifdef PACKAGE_BUILD
+BUILD = $(PACKAGE_BUILD)
+else
+BUILD = unknown
+endif
+
+LCFLAGS += -DBUILD=\"$(BUILD)\"
+LCFLAGS += $(INVISIBILITY)
+
+LLDLIBS = $(PCP_PMDALIB)
+ifeq "$(TARGET_OS)" "mingw"
+LLDLIBS += -lpcp_pmcd
+PCPLIB_LDFLAGS += -L$(TOPDIR)/src/libpcp_pmcd/src
+endif
+
+default: $(LIBTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PCP_PMDAS_DIR)/pmcd
+ $(INSTALL) -m 755 $(LIBTARGET) $(PCP_PMDAS_DIR)/pmcd/$(LIBTARGET)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+$(LIBTARGET): $(VERSION_SCRIPT)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
diff --git a/src/pmdas/pmcd/src/objstyle b/src/pmdas/pmcd/src/objstyle
new file mode 100755
index 0000000..5929bab
--- /dev/null
+++ b/src/pmdas/pmcd/src/objstyle
@@ -0,0 +1,88 @@
+#! /bin/sh
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# identify the objstyle of linux platforms other than ia32 and ia64.
+
+echo 'int main(){return 0;}' >dummy.c
+cc -c dummy.c
+
+# we've had bad experience with file(1) and compiled "magic" files
+# on assorted Linux versions ... try to use the uncompiled magic
+# file if possible
+#
+# if you need to modify this, make consistent changes in
+# src/pmdas/pmcd/src/objstyle
+# qa/605
+#
+magic=''
+for file in /usr/share/misc/magic /usr/share/file/magic /usr/share/magic \
+ /etc/magic
+do
+ if [ -f "$file" ]
+ then
+ # found a file, check it contains some definitions ...
+ nl=`sed -e '/^#/d' -e '/^[ ]*$/d' <"$file" | wc -l | sed -e 's/ //g'`
+ if [ "$nl" -gt 0 ]
+ then
+ magic=$file
+ break
+ fi
+ fi
+done
+
+# sample file output
+#
+# ia32 laptop
+# dummy.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
+#
+# Mac OS X
+# pmcd.o: Mach-O universal binary with 2 architectures
+# pmcd.o (for architecture i386): Mach-O object i386
+# pmcd.o (for architecture x86_64): Mach-O 64-bit object x86_64
+#
+# dummy.o: Mach-O 64-bit object x86_64
+#
+# SLES10
+# dummy.o: ELF 64-bit LSB relocatable, IA-64 (Intel 64 bit architecture), version 1 (SYSV), not stripped
+#
+
+if [ -n "$magic" ]
+then
+ file -m $magic dummy.o
+else
+ file dummy.o
+fi \
+| ( sed -n \
+ -e '/ ELF /{
+s/^[^,]*, //
+s/, .*//
+s/ *([^)]*)//
+s/ //
+s/x86-64/x86_64/
+p
+}' \
+ -e '/object/{
+s/[ ]*$//
+s/.*[ ]//
+p
+}' \
+| tr '[\012]' '[+]' \
+; echo ) \
+| sed -e 's/+$//'
+
+rm -f dummy.[co]
diff --git a/src/pmdas/pmcd/src/pmcd.c b/src/pmdas/pmcd/src/pmcd.c
new file mode 100644
index 0000000..fecfd28
--- /dev/null
+++ b/src/pmdas/pmcd/src/pmcd.c
@@ -0,0 +1,1869 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2001,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "stats.h"
+#include "pmcd/src/pmcd.h"
+#include "pmcd/src/client.h"
+#include <sys/stat.h>
+#if defined(IS_SOLARIS)
+#include <sys/systeminfo.h>
+#endif
+
+/*
+ * Note: strange numbering for pmcd.pdu_{in,out}.total for
+ * compatibility with earlier PCP versions ... this is the "item"
+ * field of the PMID
+ */
+#define _TOTAL 16
+
+/*
+ * all metrics supported in this PMD - one table entry for each
+ */
+static pmDesc desctab[] = {
+/* control.debug */
+ { PMDA_PMID(0,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* datasize */
+ { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* numagents */
+ { PMDA_PMID(0,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* numclients */
+ { PMDA_PMID(0,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.timeout */
+ { PMDA_PMID(0,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* timezone -- local $TZ -- for pmlogger */
+ { PMDA_PMID(0,5), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* simabi -- Subprogram Interface Model, ABI version of this pmcd (normally PM_TYPE_STRING) */
+ { PMDA_PMID(0,6), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* version -- pcp version */
+ { PMDA_PMID(0,7), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.register -- bulletin board */
+ { PMDA_PMID(0,8), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.traceconn -- trace connections */
+ { PMDA_PMID(0,9), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.tracepdu -- trace PDU traffic */
+ { PMDA_PMID(0,10), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.tracebufs -- number of trace buffers */
+ { PMDA_PMID(0,11), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.dumptrace -- push-button, pmStore to dump trace */
+ { PMDA_PMID(0,12), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.dumpconn -- push-button, pmStore to dump connections */
+ { PMDA_PMID(0,13), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.tracenobuf -- unbuffered tracing */
+ { PMDA_PMID(0,14), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* control.sighup -- push-button, pmStore to SIGHUP pmcd */
+ { PMDA_PMID(0,15), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* services -- locally running PCP services */
+ { PMDA_PMID(0,16), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* openfds -- number of open file descriptors */
+ { PMDA_PMID(0,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* buf.alloc */
+ { PMDA_PMID(0,18), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* buf.free */
+ { PMDA_PMID(0,19), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* build -- pcp build number */
+ { PMDA_PMID(0,20), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* hostname -- local hostname -- for pmlogger */
+ { PMDA_PMID(0,21), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/* pdu_in.error */
+ { PMDA_PMID(1,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.result */
+ { PMDA_PMID(1,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.profile */
+ { PMDA_PMID(1,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.fetch */
+ { PMDA_PMID(1,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.desc_req */
+ { PMDA_PMID(1,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.desc */
+ { PMDA_PMID(1,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.instance_req */
+ { PMDA_PMID(1,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.instance */
+ { PMDA_PMID(1,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.text_req */
+ { PMDA_PMID(1,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.text */
+ { PMDA_PMID(1,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.control_req */
+ { PMDA_PMID(1,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.creds */
+ { PMDA_PMID(1,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.pmns_ids */
+ { PMDA_PMID(1,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.pmns_names */
+ { PMDA_PMID(1,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.pmns_child */
+ { PMDA_PMID(1,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.total */
+ { PMDA_PMID(1,_TOTAL), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.pmns_traverse */
+ { PMDA_PMID(1,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_in.auth */
+ { PMDA_PMID(1,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+
+/* pdu_out.error */
+ { PMDA_PMID(2,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.result */
+ { PMDA_PMID(2,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.profile */
+ { PMDA_PMID(2,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.fetch */
+ { PMDA_PMID(2,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.desc_req */
+ { PMDA_PMID(2,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.desc */
+ { PMDA_PMID(2,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.instance_req */
+ { PMDA_PMID(2,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.instance */
+ { PMDA_PMID(2,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.text_req */
+ { PMDA_PMID(2,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.text */
+ { PMDA_PMID(2,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.control_req */
+ { PMDA_PMID(2,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.creds */
+ { PMDA_PMID(2,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.pmns_ids */
+ { PMDA_PMID(2,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.pmns_names */
+ { PMDA_PMID(2,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.pmns_child */
+ { PMDA_PMID(2,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.total */
+ { PMDA_PMID(2,_TOTAL), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.pmns_traverse */
+ { PMDA_PMID(2,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pdu_out.auth */
+ { PMDA_PMID(2,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+
+/* pmlogger.port */
+ { PMDA_PMID(3,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmlogger.pmcd_host */
+ { PMDA_PMID(3,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmlogger.archive */
+ { PMDA_PMID(3,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmlogger.host */
+ { PMDA_PMID(3,3), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/* agent.type */
+ { PMDA_PMID(4,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* agent.status */
+ { PMDA_PMID(4,1), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/* pmie.configfile */
+ { PMDA_PMID(5,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmie.logfile */
+ { PMDA_PMID(5,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmie.pmcd_host */
+ { PMDA_PMID(5,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmie.numrules */
+ { PMDA_PMID(5,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmie.actions */
+ { PMDA_PMID(5,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pmie.eval.true */
+ { PMDA_PMID(5,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pmie.eval.false */
+ { PMDA_PMID(5,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pmie.eval.unknown */
+ { PMDA_PMID(5,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* pmie.eval.expected */
+ { PMDA_PMID(5,8), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) },
+/* pmie.eval.actual */
+ { PMDA_PMID(5,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+
+/* client.whoami */
+ { PMDA_PMID(6,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* client.start_date */
+ { PMDA_PMID(6,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/* pmcd.cputime.total */
+ { PMDA_PMID(7,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) },
+/* pmcd.cputime.per_pdu_in */
+ { PMDA_PMID(7,1), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,-1,0,PM_TIME_USEC,PM_COUNT_ONE) },
+
+/* pmcd.feature.secure */
+ { PMDA_PMID(8,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.compress */
+ { PMDA_PMID(8,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.ipv6 */
+ { PMDA_PMID(8,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.authentication */
+ { PMDA_PMID(8,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.creds_required */
+ { PMDA_PMID(8,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.unix_domain_sockets */
+ { PMDA_PMID(8,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pmcd.feature.service_discovery */
+ { PMDA_PMID(8,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/* End-of-List */
+ { PM_ID_NULL, 0, 0, 0, PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }
+};
+static int ndesc = sizeof(desctab)/sizeof(desctab[0]);
+
+static __pmProfile *_profile; /* last received profile */
+
+/* there are four instance domains: pmlogger, register, PMDA, and pmie */
+#define INDOM_PMLOGGERS 1
+static pmInDom logindom;
+#define INDOM_REGISTER 2
+static pmInDom regindom;
+#define INDOM_PMDAS 3
+static pmInDom pmdaindom;
+#define INDOM_PMIES 4
+static pmInDom pmieindom;
+#define INDOM_POOL 5
+static pmInDom bufindom;
+#define INDOM_CLIENT 6
+static pmInDom clientindom;
+
+#define NUMREG 16
+static int reg[NUMREG];
+
+typedef struct {
+ pid_t pid;
+ int size;
+ char *name;
+ void *mmap;
+} pmie_t;
+static pmie_t *pmies;
+static unsigned int npmies;
+
+static struct {
+ int inst;
+ char *iname;
+} bufinst[] = {
+ { 12, "0012" },
+ { 20, "0020" },
+ { 1024, "1024" },
+ { 2048, "2048" },
+ { 4196, "4196" },
+ { 8192, "8192" },
+ { 8193, "8192+" },
+};
+static int nbufsz = sizeof(bufinst) / sizeof(bufinst[0]);
+
+typedef struct {
+ int id; /* index into client[] */
+ int seq;
+ char *value;
+} whoami_t;
+static whoami_t *whoamis;
+static unsigned int nwhoamis;
+
+typedef struct {
+ int state;
+ double last_cputime;
+ __uint64_t last_pdu_in;
+} perctx_t;
+
+/* values for per context state */
+#define CTX_INACTIVE 0
+#define CTX_ACTIVE 1
+
+static perctx_t *ctxtab = NULL;
+static int num_ctx = 0;
+
+/*
+ * expand and initialize the per client context table
+ */
+static void
+grow_ctxtab(int ctx)
+{
+ ctxtab = (perctx_t *)realloc(ctxtab, (ctx+1)*sizeof(ctxtab[0]));
+ if (ctxtab == NULL) {
+ __pmNoMem("grow_ctxtab", (ctx+1)*sizeof(ctxtab[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ while (num_ctx <= ctx) {
+ ctxtab[num_ctx].state = CTX_INACTIVE;
+ num_ctx++;
+ }
+ ctxtab[ctx].state = CTX_INACTIVE;
+}
+
+/*
+ * this routine is called at initialization to patch up any parts of the
+ * desctab that cannot be statically initialized, and to optionally
+ * modify our Performance Metrics Domain Id (dom)
+ */
+static void
+init_tables(int dom)
+{
+ int i;
+ __pmID_int *pmidp;
+ __pmInDom_int *indomp;
+
+ /* set domain in instance domain correctly */
+ indomp = (__pmInDom_int *)&logindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_PMLOGGERS;
+ indomp = (__pmInDom_int *)&regindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_REGISTER;
+ indomp = (__pmInDom_int *)&pmdaindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_PMDAS;
+ indomp = (__pmInDom_int *)&pmieindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_PMIES;
+ indomp = (__pmInDom_int *)&bufindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_POOL;
+ indomp = (__pmInDom_int *)&clientindom;
+ indomp->flag = 0;
+ indomp->domain = dom;
+ indomp->serial = INDOM_CLIENT;
+
+ /* merge performance domain id part into PMIDs in pmDesc table */
+ for (i = 0; desctab[i].pmid != PM_ID_NULL; i++) {
+ pmidp = (__pmID_int *)&desctab[i].pmid;
+ pmidp->domain = dom;
+ if (pmidp->cluster == 0 && pmidp->item == 8)
+ desctab[i].indom = regindom;
+ else if (pmidp->cluster == 0 && (pmidp->item == 18 || pmidp->item == 19))
+ desctab[i].indom = bufindom;
+ else if (pmidp->cluster == 3)
+ desctab[i].indom = logindom;
+ else if (pmidp->cluster == 4)
+ desctab[i].indom = pmdaindom;
+ else if (pmidp->cluster == 5)
+ desctab[i].indom = pmieindom;
+ else if (pmidp->cluster == 6)
+ desctab[i].indom = clientindom;
+ }
+ ndesc--;
+}
+
+
+static int
+pmcd_profile(__pmProfile *prof, pmdaExt *pmda)
+{
+ _profile = prof;
+ return 0;
+}
+
+static void
+remove_pmie_indom(void)
+{
+ int n;
+
+ for (n = 0; n < npmies; n++) {
+ free(pmies[n].name);
+ __pmMemoryUnmap(pmies[n].mmap, pmies[n].size);
+ }
+ free(pmies);
+ pmies = NULL;
+ npmies = 0;
+}
+
+static int
+stat_time_differs(struct stat *statbuf, struct stat *lastsbuf)
+{
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ if (statbuf->st_mtime != lastsbuf->st_mtime)
+ return 1;
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if ((statbuf->st_mtimespec.tv_sec != lastsbuf->st_mtimespec.tv_sec) ||
+ (statbuf->st_mtimespec.tv_nsec != lastsbuf->st_mtimespec.tv_nsec))
+ return 1;
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ if ((statbuf->st_mtim.tv_sec != lastsbuf->st_mtim.tv_sec) ||
+ (statbuf->st_mtim.tv_nsec != lastsbuf->st_mtim.tv_nsec))
+ return 1;
+#else
+!bozo!
+#endif
+ return 0;
+}
+
+/* use a static timestamp, stat PMIE_SUBDIR, if changed update "pmies" */
+static unsigned int
+refresh_pmie_indom(void)
+{
+ static struct stat lastsbuf;
+ pid_t pmiepid;
+ struct dirent *dp;
+ struct stat statbuf;
+ size_t size;
+ char *endp;
+ char fullpath[MAXPATHLEN];
+ void *ptr;
+ DIR *pmiedir;
+ int fd;
+ int sep = __pmPathSeparator();
+
+ snprintf(fullpath, sizeof(fullpath), "%s%c%s",
+ pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR);
+ if (stat(fullpath, &statbuf) == 0) {
+ if (stat_time_differs(&statbuf, &lastsbuf)) {
+
+ lastsbuf = statbuf;
+
+ /* tear down the old instance domain */
+ if (pmies)
+ remove_pmie_indom();
+
+ /* open the directory iterate through mmaping as we go */
+ if ((pmiedir = opendir(fullpath)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "pmcd pmda cannot open %s: %s",
+ fullpath, osstrerror());
+ return 0;
+ }
+ /* NOTE: all valid files are already mmapped by pmie */
+ while ((dp = readdir(pmiedir)) != NULL) {
+ size = (npmies+1) * sizeof(pmie_t);
+ pmiepid = (pid_t)strtoul(dp->d_name, &endp, 10);
+ if (*endp != '\0') /* skips over "." and ".." here */
+ continue;
+ if (!__pmProcessExists(pmiepid))
+ continue;
+ snprintf(fullpath, sizeof(fullpath), "%s%c%s%c%s",
+ pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR, sep,
+ dp->d_name);
+ if (stat(fullpath, &statbuf) < 0) {
+ __pmNotifyErr(LOG_WARNING, "pmcd pmda cannot stat %s: %s",
+ fullpath, osstrerror());
+ continue;
+ }
+ if (statbuf.st_size != sizeof(pmiestats_t))
+ continue;
+ if ((endp = strdup(dp->d_name)) == NULL) {
+ __pmNoMem("pmie iname", strlen(dp->d_name), PM_RECOV_ERR);
+ continue;
+ }
+ if ((pmies = (pmie_t *)realloc(pmies, size)) == NULL) {
+ __pmNoMem("pmie instlist", size, PM_RECOV_ERR);
+ free(endp);
+ continue;
+ }
+ if ((fd = open(fullpath, O_RDONLY)) < 0) {
+ __pmNotifyErr(LOG_WARNING, "pmcd pmda cannot open %s: %s",
+ fullpath, osstrerror());
+ free(endp);
+ continue;
+ }
+ ptr = __pmMemoryMap(fd, statbuf.st_size, 0);
+ close(fd);
+ if (ptr == NULL) {
+ __pmNotifyErr(LOG_ERR, "pmcd pmda memmap of %s failed: %s",
+ fullpath, osstrerror());
+ free(endp);
+ continue;
+ }
+ else if (((pmiestats_t *)ptr)->version != 1) {
+ __pmNotifyErr(LOG_WARNING, "incompatible pmie version: %s",
+ fullpath);
+ __pmMemoryUnmap(ptr, statbuf.st_size);
+ free(endp);
+ continue;
+ }
+ pmies[npmies].pid = pmiepid;
+ pmies[npmies].name = endp;
+ pmies[npmies].size = statbuf.st_size;
+ pmies[npmies].mmap = ptr;
+ npmies++;
+ }
+ closedir(pmiedir);
+ }
+ }
+ else {
+ remove_pmie_indom();
+ }
+ setoserror(0);
+ return npmies;
+}
+
+static int
+pmcd_instance_reg(int inst, char *name, __pmInResult **result)
+{
+ __pmInResult *res;
+ int i;
+ char idx[3]; /* ok for NUMREG <= 99 */
+
+ res = (__pmInResult *)malloc(sizeof(__pmInResult));
+ if (res == NULL)
+ return -oserror();
+
+ if (name == NULL && inst == PM_IN_NULL)
+ res->numinst = NUMREG;
+ else
+ res->numinst = 1;
+
+ if (inst == PM_IN_NULL) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ free(res);
+ return -oserror();
+ }
+ }
+ else
+ res->instlist = NULL;
+
+ if (name == NULL) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (i = 0; i < res->numinst; i++)
+ res->namelist[0] = NULL;
+ }
+ else
+ res->namelist = NULL;
+
+ if (name == NULL && inst == PM_IN_NULL) {
+ /* return inst and name for everything */
+ for (i = 0; i < res->numinst; i++) {
+ res->instlist[i] = i;
+ snprintf(idx, sizeof(idx), "%d", i);
+ if ((res->namelist[i] = strdup(idx)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ }
+ else if (name == NULL) {
+ /* given an inst, return the name */
+ if (0 <= inst && inst < NUMREG) {
+ snprintf(idx, sizeof(idx), "%d", inst);
+ if ((res->namelist[0] = strdup(idx)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ else {
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+ }
+ else if (inst == PM_IN_NULL) {
+ /* given a name, return an inst */
+ char *endp;
+ i = (int)strtol(name, &endp, 10);
+ if (*endp == '\0' && 0 <= i && i < NUMREG)
+ res->instlist[0] = i;
+ else {
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+ }
+
+ *result = res;
+ return 0;
+}
+
+static int
+pmcd_instance_pool(int inst, char *name, __pmInResult **result)
+{
+ __pmInResult *res;
+ int i;
+
+ res = (__pmInResult *)malloc(sizeof(__pmInResult));
+ if (res == NULL)
+ return -oserror();
+
+ if (name == NULL && inst == PM_IN_NULL)
+ res->numinst = nbufsz;
+ else
+ res->numinst = 1;
+
+ if (inst == PM_IN_NULL) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ free(res);
+ return -oserror();
+ }
+ }
+ else
+ res->instlist = NULL;
+
+ if (name == NULL) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (i = 0; i < res->numinst; i++)
+ res->namelist[0] = NULL;
+ }
+ else
+ res->namelist = NULL;
+
+ if (name == NULL && inst == PM_IN_NULL) {
+ /* return inst and name for everything */
+ for (i = 0; i < nbufsz; i++) {
+ res->instlist[i] = bufinst[i].inst;
+ if ((res->namelist[i] = strdup(bufinst[i].iname)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ }
+ else if (name == NULL) {
+ /* given an inst, return the name */
+ for (i = 0; i < nbufsz; i++) {
+ if (inst == bufinst[i].inst) {
+ if ((res->namelist[0] = strdup(bufinst[i].iname)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ break;
+ }
+ }
+ if (i == nbufsz) {
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+ }
+ else if (inst == PM_IN_NULL) {
+ /* given a name, return an inst */
+ for (i = 0; i < nbufsz; i++) {
+ if (strcmp(name, bufinst[i].iname) == 0) {
+ res->instlist[0] = bufinst[i].inst;
+ break;
+ }
+ }
+ if (i == nbufsz) {
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+ }
+
+ *result = res;
+ return 0;
+}
+
+static int
+pmcd_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ int sts = 0;
+ __pmInResult *res;
+ int getall = 0;
+ int getname = 0; /* initialize to pander to gcc */
+ int nports = 0; /* initialize to pander to gcc */
+ __pmLogPort *ports;
+ unsigned int pmiecount = 0; /* initialize to pander to gcc */
+ int i;
+
+ if (indom == regindom)
+ return pmcd_instance_reg(inst, name, result);
+ else if (indom == bufindom)
+ return pmcd_instance_pool(inst, name, result);
+ else if (indom == logindom || indom == pmdaindom || indom == pmieindom || indom == clientindom) {
+ res = (__pmInResult *)malloc(sizeof(__pmInResult));
+ if (res == NULL)
+ return -oserror();
+ res->instlist = NULL;
+ res->namelist = NULL;
+
+ if (indom == logindom) {
+ /* use the wildcard behaviour of __pmLogFindPort to find
+ * all pmlogger ports on localhost. Note that
+ * __pmLogFindPort will not attempt to contact pmcd if
+ * localhost is specified---this means we don't get a
+ * recursive call to pmcd which would hang!
+ */
+ if ((nports = __pmLogFindPort("localhost", PM_LOG_ALL_PIDS, &ports)) < 0) {
+ free(res);
+ return nports;
+ }
+ }
+ else if (indom == pmieindom)
+ pmiecount = refresh_pmie_indom();
+
+ if (name == NULL && inst == PM_IN_NULL) {
+ getall = 1;
+
+ if (indom == logindom)
+ res->numinst = nports;
+ else if (indom == pmdaindom)
+ res->numinst = nAgents;
+ else if (indom == pmieindom)
+ res->numinst = pmiecount;
+ else if (indom == clientindom) {
+ res->numinst = 0;
+ for (i = 0; i < nClients; i++) {
+ if (client[i].status.connected) res->numinst++;
+ }
+ }
+ }
+ else {
+ getname = name == NULL;
+ res->numinst = 1;
+ }
+
+ if (getall || !getname) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(int))) == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance instlist", res->numinst * sizeof(int), PM_RECOV_ERR);
+ __pmFreeInResult(res);
+ return sts;
+ }
+ }
+ if (getall || getname) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(char *))) == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance namelist", res->numinst * sizeof(char *), PM_RECOV_ERR);
+ free(res->instlist);
+ __pmFreeInResult(res);
+ return sts;
+ }
+ }
+ }
+ else
+ return PM_ERR_INDOM;
+
+ if (indom == logindom) {
+ res->indom = logindom;
+
+ if (getall) { /* get instance ids and names */
+ for (i = 0; i < nports; i++) {
+ res->instlist[i] = ports[i].pid;
+ res->namelist[i] = strdup(ports[i].name);
+ if (res->namelist[i] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmGetInDom",
+ strlen(ports[i].name), PM_RECOV_ERR);
+ /* ensure pmFreeInResult only gets valid pointers */
+ res->numinst = i;
+ break;
+ }
+ }
+ }
+ else if (getname) { /* given id, get name */
+ for (i = 0; i < nports; i++) {
+ if (inst == ports[i].pid)
+ break;
+ }
+ if (i == nports) {
+ sts = PM_ERR_INST;
+ res->namelist[0] = NULL;
+ }
+ else {
+ res->namelist[0] = strdup(ports[i].name);
+ if (res->namelist[0] == NULL) {
+ __pmNoMem("pmcd_instance pmNameInDom",
+ strlen(ports[i].name), PM_RECOV_ERR);
+ sts = -oserror();
+ }
+ }
+ }
+ else { /* given name, get id */
+ for (i = 0; i < nports; i++) {
+ if (strcmp(name, ports[i].name) == 0)
+ break;
+ }
+ if (i == nports)
+ sts = PM_ERR_INST;
+ else
+ res->instlist[0] = ports[i].pid;
+ }
+ }
+ else if (indom == pmieindom) {
+ res->indom = pmieindom;
+
+ if (getall) { /* get instance ids and names */
+ for (i = 0; i < pmiecount; i++) {
+ res->instlist[i] = pmies[i].pid;
+ res->namelist[i] = strdup(pmies[i].name);
+ if (res->namelist[i] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmie_instance pmGetInDom",
+ strlen(pmies[i].name), PM_RECOV_ERR);
+ /* ensure pmFreeInResult only gets valid pointers */
+ res->numinst = i;
+ break;
+ }
+ }
+ }
+ else if (getname) { /* given id, get name */
+ for (i = 0; i < pmiecount; i++) {
+ if (inst == pmies[i].pid)
+ break;
+ }
+ if (i == pmiecount) {
+ sts = PM_ERR_INST;
+ res->namelist[0] = NULL;
+ }
+ else {
+ res->namelist[0] = strdup(pmies[i].name);
+ if (res->namelist[0] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmNameInDom",
+ strlen(pmies[i].name), PM_RECOV_ERR);
+ }
+ }
+ }
+ else { /* given name, get id */
+ for (i = 0; i < pmiecount; i++) {
+ if (strcmp(name, pmies[i].name) == 0)
+ break;
+ }
+ if (i == pmiecount)
+ sts = PM_ERR_INST;
+ else
+ res->instlist[0] = pmies[i].pid;
+ }
+ }
+ else if (indom == pmdaindom) {
+ res->indom = pmdaindom;
+
+ if (getall) { /* get instance ids and names */
+ for (i = 0; i < nAgents; i++) {
+ res->instlist[i] = agent[i].pmDomainId;
+ res->namelist[i] = strdup(agent[i].pmDomainLabel);
+ if (res->namelist[i] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmGetInDom",
+ strlen(agent[i].pmDomainLabel), PM_RECOV_ERR);
+ /* ensure pmFreeInResult only gets valid pointers */
+ res->numinst = i;
+ break;
+ }
+ }
+ }
+ else if (getname) { /* given id, get name */
+ for (i = 0; i < nAgents; i++) {
+ if (inst == agent[i].pmDomainId)
+ break;
+ }
+ if (i == nAgents) {
+ sts = PM_ERR_INST;
+ res->namelist[0] = NULL;
+ }
+ else {
+ res->namelist[0] = strdup(agent[i].pmDomainLabel);
+ if (res->namelist[0] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmNameInDom",
+ strlen(agent[i].pmDomainLabel), PM_RECOV_ERR);
+ }
+ }
+ }
+ else { /* given name, get id */
+ for (i = 0; i < nAgents; i++) {
+ if (strcmp(name, agent[i].pmDomainLabel) == 0)
+ break;
+ }
+ if (i == nAgents)
+ sts = PM_ERR_INST;
+ else
+ res->instlist[0] = agent[i].pmDomainId;
+ }
+ }
+ else if (indom == clientindom) {
+ res->indom = clientindom;
+
+ if (getall) { /* get instance ids and names */
+ int k = 0;
+ for (i = 0; i < nClients; i++) {
+ char buf[11]; /* enough for 32-bit client seq number */
+ if (!client[i].status.connected)
+ continue;
+ res->instlist[k] = client[i].seq;
+ snprintf(buf, sizeof(buf), "%u", client[i].seq);
+ res->namelist[k] = strdup(buf);
+ if (res->namelist[k] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmGetInDom",
+ strlen(buf), PM_RECOV_ERR);
+ /* ensure pmFreeInResult only gets valid pointers */
+ res->numinst = i;
+ break;
+ }
+ k++;
+ }
+ }
+ else if (getname) { /* given id, get name */
+ for (i = 0; i < nClients; i++) {
+ if (client[i].status.connected && inst == client[i].seq)
+ break;
+ }
+ if (i == nClients) {
+ sts = PM_ERR_INST;
+ res->namelist[0] = NULL;
+ }
+ else {
+ char buf[11]; /* enough for 32-bit client seq number */
+ snprintf(buf, sizeof(buf), "%u", (unsigned int)inst);
+ res->namelist[0] = strdup(buf);
+ if (res->namelist[0] == NULL) {
+ sts = -oserror();
+ __pmNoMem("pmcd_instance pmNameInDom",
+ strlen(buf), PM_RECOV_ERR);
+ }
+ }
+ }
+ else { /* given name, get id */
+ char buf[11]; /* enough for 32-bit client seq number */
+ for (i = 0; i < nClients; i++) {
+ if (!client[i].status.connected)
+ continue;
+ snprintf(buf, sizeof(buf), "%u", client[i].seq);
+ if (strcmp(name, buf) == 0)
+ break;
+ }
+ if (i == nClients)
+ sts = PM_ERR_INST;
+ else
+ res->instlist[0] = client[i].seq;
+ }
+ }
+
+ if (sts < 0) {
+ __pmFreeInResult(res);
+ return sts;
+ }
+
+ *result = res;
+ return 0;
+}
+
+/*
+ * numval != 1, so re-do vset[i] allocation
+ */
+static int
+vset_resize(pmResult *rp, int i, int onumval, int numval)
+{
+ int expect = numval;
+
+ if (rp->vset[i] != NULL) {
+ free(rp->vset[i]);
+ }
+
+ if (numval < 0)
+ expect = 0;
+
+ rp->vset[i] = (pmValueSet *)malloc(sizeof(pmValueSet) + (expect-1)*sizeof(pmValue));
+
+ if (rp->vset[i] == NULL) {
+ if (i) {
+ /* we're doomed ... reclaim pmValues 0, 1, ... i-1 */
+ rp->numpmid = i;
+ __pmFreeResultValues(rp);
+ }
+ return -1;
+ }
+
+ rp->vset[i]->numval = numval;
+ return 0;
+}
+
+static char *
+simabi()
+{
+#if defined(__linux__) || defined(IS_GNU)
+# if defined(__i386__)
+ return "ia32";
+# elif defined(__ia64__) || defined(__ia64)
+ return "ia64";
+# else
+ return SIM_ABI; /* SIM_ABI is defined in the linux Makefile */
+# endif /* __linux__ */
+#elif defined(IS_SOLARIS)
+ static char abi[32];
+ if (sysinfo(SI_ARCHITECTURE_NATIVE, abi, sizeof(abi)) < 0) {
+ return "unknown";
+ } else {
+ return abi;
+ }
+#elif defined(IS_FREEBSD) || defined(IS_NETBSD)
+ return "elf";
+#elif defined(IS_DARWIN)
+ return "Mach-O " SIM_ABI;
+#elif defined(IS_MINGW)
+ return "x86_64";
+#elif defined(IS_AIX)
+ return "powerpc";
+#else
+ !!! bozo : dont know which executable format pmcd should be!!!
+#endif
+}
+
+static char *
+tzinfo(void)
+{
+ /*
+ * __pmTimezone() caches its result in $TZ - pmcd is long running,
+ * however, and we *really* want to see changes in the timezone or
+ * daylight savings state via pmcd.timezone, so we clear TZ first.
+ */
+#ifdef HAVE_UNSETENV
+ unsetenv("TZ");
+#else /* MINGW */
+ putenv("TZ=");
+#endif
+ return __pmTimezone();
+}
+
+static int
+extract_service(const char *path, char *name, pid_t *pid)
+{
+ int length, sep = __pmPathSeparator();
+ char fullpath[MAXPATHLEN];
+ char buffer[64];
+ FILE *fp;
+
+ /* check basename has a ".pid" suffix */
+ if ((length = strlen(name)) < 5)
+ return 0;
+ length -= 4;
+ if (strcmp(&name[length], ".pid") != 0)
+ return 0;
+
+ /* extract PID lurking within the file */
+ snprintf(fullpath, sizeof(fullpath), "%s%c%s", path, sep, name);
+ if ((fp = fopen(fullpath, "r")) == NULL)
+ return 0;
+ sep = fscanf(fp, "%63s", buffer);
+ fclose(fp);
+ if (sep != 1)
+ return 0;
+ *pid = atoi(buffer);
+
+ /* finally setup service name to return */
+ name[length] = '\0';
+ return length;
+}
+
+char *
+services(void)
+{
+ static char servicelist[128];
+ static struct stat lastsbuf;
+ pid_t pid;
+ struct dirent *dp;
+ struct stat statbuf;
+ char *path;
+ DIR *rundir;
+ int length, offset;
+
+ path = pmGetConfig("PCP_RUN_DIR");
+ if (stat(path, &statbuf) == 0) {
+ if (stat_time_differs(&statbuf, &lastsbuf)) {
+ lastsbuf = statbuf;
+
+ /* by definition, pmcd is currently running */
+ strcpy(servicelist, PM_SERVER_SERVICE_SPEC);
+ offset = sizeof(PM_SERVER_SERVICE_SPEC) - 1;
+
+ /* iterate through directory, building up services string */
+ if ((rundir = opendir(path)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "pmcd pmda cannot open %s: %s",
+ path, osstrerror());
+ return servicelist;
+ }
+ while ((dp = readdir(rundir)) != NULL) {
+ if (dp->d_name[0] == '.')
+ continue;
+ length = sizeof(PM_SERVER_SERVICE_SPEC) - 1;
+ if (strncmp(dp->d_name, PM_SERVER_SERVICE_SPEC, length) == 0)
+ continue;
+ if ((length = extract_service(path, dp->d_name, &pid)) <= 0)
+ continue;
+ if (!__pmProcessExists(pid))
+ continue;
+ if (offset + 1 + length + 1 > sizeof(servicelist))
+ continue;
+ servicelist[offset++] = ' ';
+ strcpy(&servicelist[offset], dp->d_name);
+ offset += length;
+ }
+ closedir(rundir);
+ }
+ } else {
+ strcpy(servicelist, PM_SERVER_SERVICE_SPEC);
+ }
+ return servicelist;
+}
+
+static char *
+hostnameinfo(void)
+{
+ static char host[MAXHOSTNAMELEN];
+ char *name;
+
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ name = host;
+
+ return name;
+}
+
+static int
+fetch_feature(int item, pmAtomValue *avp)
+{
+ if (item < 0 || item >= PM_SERVER_FEATURES)
+ return PM_ERR_PMID;
+ avp->ul = __pmServerHasFeature((__pmServerFeature)item);
+ return 0;
+}
+
+static int
+fetch_cputime(int item, int ctx, pmAtomValue *avp)
+{
+ double usr, sys;
+ double cputime;
+
+ if (item < 0 || item > 1) {
+ return PM_ERR_PMID;
+ }
+ if (ctx < 0) {
+ /* should not happen */
+ return PM_ERR_NOTCONN;
+ }
+ __pmProcessRunTimes(&usr, &sys);
+ cputime = (usr+sys)*1000;
+ if (ctx >= num_ctx)
+ grow_ctxtab(ctx);
+ if (item == 0) { /* pmcd.cputime.total */
+ avp->ull = (__uint64_t)cputime;
+ }
+ else if (item == 1) { /* pmcd.cputime.per_pdu_in */
+ int j;
+ int pdu_in;
+ for (pdu_in = j = 0; j <= PDU_MAX; j++)
+ pdu_in += __pmPDUCntIn[j];
+ if (ctxtab[ctx].state == CTX_INACTIVE) {
+ /* first call for this context */
+ ctxtab[ctx].state = CTX_ACTIVE;
+ avp->d = cputime*1000/pdu_in;
+ }
+ else {
+ if (pdu_in > ctxtab[ctx].last_pdu_in)
+ avp->d = 1000*(cputime-ctxtab[ctx].last_cputime)/(pdu_in-ctxtab[ctx].last_pdu_in);
+ else {
+ /* should not happen, as you need another pdu to get here */
+ avp->d = 0;
+ }
+ }
+ ctxtab[ctx].last_cputime = cputime;
+ ctxtab[ctx].last_pdu_in = pdu_in;
+ }
+ return 0;
+}
+
+static void
+end_context(int ctx)
+{
+ if (ctx >= 0 && ctx < num_ctx && ctxtab[ctx].state == CTX_ACTIVE) {
+ ctxtab[ctx].state = CTX_INACTIVE;
+ }
+}
+
+static int
+pmcd_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i; /* over pmidlist[] */
+ int j;
+ int sts, nports;
+ int need;
+ int numval;
+ int valfmt;
+ unsigned long datasize;
+ static pmResult *res = NULL;
+ static int maxnpmids = 0;
+ char *host = NULL; /* refresh max once per fetch */
+ pmiestats_t *pmie;
+ pmValueSet *vset;
+ pmDesc *dp = NULL; /* initialize to pander to gcc */
+ __pmID_int *pmidp;
+ pmAtomValue atom;
+ __pmLogPort *lpp;
+
+ if (numpmid > maxnpmids) {
+ if (res != NULL)
+ free(res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *);
+ if ((res = (pmResult *) malloc(need)) == NULL)
+ return -ENOMEM;
+ maxnpmids = numpmid;
+ }
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+ for (i = 0; i < numpmid; i++) {
+ /* Allocate a pmValueSet with room for just one value. Even for the
+ * pmlogger port metric which has an instance domain, most of the time
+ * there will only be one logger running (i.e. one instance). For the
+ * infrequent cases resize the value set later.
+ */
+ res->vset[i] = NULL;
+ if (vset_resize(res, i, 0, 1) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ vset->vlist[0].inst = PM_IN_NULL;
+
+ for (j = 0; j < ndesc; j++) {
+ if (desctab[j].pmid == pmidlist[i]) {
+ dp = &desctab[j];
+ break;
+ }
+ }
+ if (j == ndesc) {
+ /* Error, need a smaller vset */
+ if (vset_resize(res, i, 1, PM_ERR_PMID) == -1)
+ return -ENOMEM;
+ res->vset[i]->pmid = pmidlist[i];
+ continue;
+ }
+
+ valfmt = -1;
+ sts = 0;
+
+ pmidp = (__pmID_int *)&pmidlist[i];
+ switch (pmidp->cluster) {
+
+ case 0: /* global metrics */
+ switch (pmidp->item) {
+ case 0: /* control.debug */
+ atom.l = pmDebug;
+ break;
+ case 1: /* datasize */
+ __pmProcessDataSize(&datasize);
+ atom.ul = datasize;
+ break;
+ case 2: /* numagents */
+ atom.ul = 0;
+ for (j = 0; j < nAgents; j++)
+ if (agent[j].status.connected)
+ atom.ul++;
+ break;
+ case 3: /* numclients */
+ atom.ul = 0;
+ for (j = 0; j < nClients; j++)
+ if (client[j].status.connected)
+ atom.ul++;
+ break;
+ case 4: /* control.timeout */
+ atom.ul = _pmcd_timeout;
+ break;
+ case 5: /* timezone $TZ */
+ atom.cp = tzinfo();
+ break;
+ case 6: /* simabi (pmcd calling convention) */
+ atom.cp = simabi();
+ break;
+ case 7: /* version */
+ atom.cp = PCP_VERSION;
+ break;
+ case 8: /* register */
+ for (j = numval = 0; j < NUMREG; j++) {
+ if (__pmInProfile(regindom, _profile, j))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < NUMREG; j++) {
+ if (!__pmInProfile(regindom, _profile, j))
+ continue;
+ vset->vlist[numval].inst = j;
+ atom.l = reg[j];
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ break;
+ case 9: /* traceconn */
+ atom.l = (_pmcd_trace_mask & TR_MASK_CONN) ? 1 : 0;
+ break;
+ case 10: /* tracepdu */
+ atom.l = (_pmcd_trace_mask & TR_MASK_PDU) ? 1 : 0;
+ break;
+ case 11: /* tracebufs */
+ atom.l = _pmcd_trace_nbufs;
+ break;
+ case 12: /* dumptrace ... always 0 */
+ atom.l = 0;
+ break;
+ case 13: /* dumpconn ... always 0 */
+ atom.l = 0;
+ break;
+ case 14: /* tracenobuf */
+ atom.l = (_pmcd_trace_mask & TR_MASK_NOBUF) ? 1 : 0;
+ break;
+ case 15: /* sighup ... always 0 */
+ atom.l = 0;
+ break;
+ case 16: /* services */
+ atom.cp = services();
+ break;
+ case 17: /* openfds */
+ atom.ul = (unsigned int)pmcd_hi_openfds;
+ break;
+ case 18: /* buf.alloc */
+ case 19: /* buf.free */
+ for (j = numval = 0; j < nbufsz; j++) {
+ if (__pmInProfile(bufindom, _profile, bufinst[j].inst))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < nbufsz; j++) {
+ int alloced;
+ int free;
+ int xtra_alloced;
+ int xtra_free;
+ if (!__pmInProfile(bufindom, _profile, bufinst[j].inst))
+ continue;
+ vset->vlist[numval].inst = bufinst[j].inst;
+ /* PDUBuf pool */
+ __pmCountPDUBuf(bufinst[j].inst, &alloced, &free);
+ /*
+ * the 2K buffer count also includes
+ * the 3K, 4K, ... buffers, so sub
+ * these ... which are reported as
+ * the 3K buffer count
+ */
+ __pmCountPDUBuf(bufinst[j].inst + 1024, &xtra_alloced, &xtra_free);
+ alloced -= xtra_alloced;
+ free -= xtra_free;
+ if (pmidp->item == 18)
+ atom.l = alloced;
+ else
+ atom.l = free;
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ break;
+
+ case 20: /* build */
+ atom.cp = BUILD;
+ break;
+
+ case 21: /* hostname */
+ if (_pmcd_hostname) {
+ atom.cp = _pmcd_hostname;
+ } else {
+ if (!host)
+ host = hostnameinfo();
+ atom.cp = host;
+ }
+ break;
+ default:
+ sts = atom.l = PM_ERR_PMID;
+ break;
+ }
+ break;
+
+ case 1: /* PDUs received */
+ if (pmidp->item == _TOTAL) {
+ /* total */
+ atom.ul = 0;
+ for (j = 0; j <= PDU_MAX; j++)
+ atom.ul += __pmPDUCntIn[j];
+ }
+ else if (pmidp->item > PDU_MAX+1)
+ sts = atom.l = PM_ERR_PMID;
+ else if (pmidp->item < _TOTAL)
+ atom.ul = __pmPDUCntIn[pmidp->item];
+ else
+ atom.ul = __pmPDUCntIn[pmidp->item-1];
+ break;
+
+ case 2: /* PDUs sent */
+ if (pmidp->item == _TOTAL) {
+ /* total */
+ atom.ul = 0;
+ for (j = 0; j <= PDU_MAX; j++)
+ atom.ul += __pmPDUCntOut[j];
+ }
+ else if (pmidp->item > PDU_MAX+1)
+ sts = atom.l = PM_ERR_PMID;
+ else if (pmidp->item < _TOTAL)
+ atom.ul = __pmPDUCntOut[pmidp->item];
+ else
+ atom.ul = __pmPDUCntOut[pmidp->item-1];
+ break;
+
+ case 3: /* pmlogger control port, pmcd_host, archive and host */
+ /* find all ports. localhost => no recursive pmcd access */
+ nports = __pmLogFindPort("localhost", PM_LOG_ALL_PIDS, &lpp);
+ if (nports < 0) {
+ sts = nports;
+ break;
+ }
+ for (j = numval = 0; j < nports; j++) {
+ if (__pmInProfile(logindom, _profile, lpp[j].pid))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < nports; j++) {
+ if (!__pmInProfile(logindom, _profile, lpp[j].pid))
+ continue;
+ vset->vlist[numval].inst = lpp[j].pid;
+ switch (pmidp->item) {
+ case 0: /* pmlogger.port */
+ atom.ul = lpp[j].port;
+ break;
+ case 1: /* pmlogger.pmcd_host */
+ atom.cp = lpp[j].pmcd_host ?
+ lpp[j].pmcd_host : "";
+ break;
+ case 2: /* pmlogger.archive */
+ atom.cp = lpp[j].archive ? lpp[j].archive : "";
+ break;
+ case 3: /* pmlogger.host */
+ if (!host)
+ host = hostnameinfo();
+ atom.cp = host;
+ break;
+ default:
+ sts = atom.l = PM_ERR_PMID;
+ break;
+ }
+ if (sts >= 0)
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ break;
+
+ case 4: /* PMDA metrics */
+ for (j = numval = 0; j < nAgents; j++) {
+ if (__pmInProfile(pmdaindom, _profile, agent[j].pmDomainId))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < nAgents; j++) {
+ if (!__pmInProfile(pmdaindom, _profile, agent[j].pmDomainId))
+ continue;
+ vset->vlist[numval].inst = agent[j].pmDomainId;
+ switch (pmidp->item) {
+ case 0: /* agent.type */
+ atom.ul = agent[j].ipcType << 1;
+ break;
+ case 1: /* agent.status */
+ if (agent[j].status.notReady)
+ atom.l = 1;
+ else if (agent[j].status.connected)
+ atom.l = 0;
+ else
+ atom.l = agent[j].reason;
+ break;
+ default:
+ sts = atom.l = PM_ERR_PMID;
+ break;
+ }
+ if (sts >= 0)
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ if (numval > 0) {
+ pmResult sortme;
+ sortme.numpmid = 1;
+ sortme.vset[0] = vset;
+ pmSortInstances(&sortme);
+ }
+ break;
+
+
+ case 5: /* pmie metrics */
+ refresh_pmie_indom();
+ for (j = numval = 0; j < npmies; j++) {
+ if (__pmInProfile(pmieindom, _profile, pmies[j].pid))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < npmies; ++j) {
+ if (!__pmInProfile(pmieindom, _profile, pmies[j].pid))
+ continue;
+ vset->vlist[numval].inst = pmies[j].pid;
+ pmie = (pmiestats_t *)pmies[j].mmap;
+ switch (pmidp->item) {
+ case 0: /* pmie.configfile */
+ atom.cp = pmie->config;
+ break;
+ case 1: /* pmie.logfile */
+ atom.cp = pmie->logfile;
+ break;
+ case 2: /* pmie.pmcd_host */
+ atom.cp = pmie->defaultfqdn;
+ break;
+ case 3: /* pmie.numrules */
+ atom.ul = pmie->numrules;
+ break;
+ case 4: /* pmie.actions */
+ atom.ul = pmie->actions;
+ break;
+ case 5: /* pmie.eval.true */
+ atom.ul = pmie->eval_true;
+ break;
+ case 6: /* pmie.eval.false */
+ atom.ul = pmie->eval_false;
+ break;
+ case 7: /* pmie.eval.unknown */
+ atom.ul = pmie->eval_unknown;
+ break;
+ case 8: /* pmie.eval.expected */
+ atom.f = pmie->eval_expected;
+ break;
+ case 9: /* pmie.eval.actual */
+ atom.ul = pmie->eval_actual;
+ break;
+ default:
+ sts = atom.l = PM_ERR_PMID;
+ break;
+ }
+ if (sts >= 0)
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ if (numval > 0) {
+ pmResult sortme;
+ sortme.numpmid = 1;
+ sortme.vset[0] = vset;
+ pmSortInstances(&sortme);
+ }
+ break;
+
+ case 6: /* client metrics */
+ for (j = numval = 0; j < nClients; j++) {
+ if (!client[j].status.connected)
+ continue;
+ if (__pmInProfile(clientindom, _profile, client[j].seq))
+ numval++;
+ }
+ if (numval != 1) {
+ /* need a different vset size */
+ if (vset_resize(res, i, 1, numval) == -1)
+ return -ENOMEM;
+ vset = res->vset[i];
+ vset->pmid = pmidlist[i];
+ }
+ for (j = numval = 0; j < nClients; ++j) {
+ int k;
+ char ctim[sizeof("Thu Nov 24 18:22:48 1986\n")];
+ if (!client[j].status.connected)
+ continue;
+ if (!__pmInProfile(clientindom, _profile, client[j].seq))
+ continue;
+ vset->vlist[numval].inst = client[j].seq;
+ switch (pmidp->item) {
+ case 0: /* client.whoami */
+ for (k = 0; k < nwhoamis; k++) {
+ if (whoamis[k].seq == client[j].seq) {
+ atom.cp = whoamis[k].value;
+ break;
+ }
+ }
+ if (k == nwhoamis)
+ /* no id registered, so no value */
+ atom.cp = "";
+ break;
+ case 1: /* client.start_date */
+ atom.cp = strcpy(ctim, ctime(&client[j].start));
+ /* trim trailing \n */
+ k = strlen(atom.cp);
+ atom.cp[k-1] = '\0';
+ break;
+ default:
+ sts = atom.l = PM_ERR_PMID;
+ break;
+ }
+ if (sts >= 0)
+ sts = __pmStuffValue(&atom, &vset->vlist[numval], dp->type);
+ if (sts < 0)
+ break;
+ valfmt = sts;
+ numval++;
+ }
+ if (numval > 0) {
+ pmResult sortme;
+ sortme.numpmid = 1;
+ sortme.vset[0] = vset;
+ pmSortInstances(&sortme);
+ }
+ break;
+
+ case 7: /* cputime metrics */
+ sts = fetch_cputime(pmidp->item, pmda->e_context, &atom);
+ break;
+
+ case 8: /* feature metrics */
+ sts = fetch_feature(pmidp->item, &atom);
+ break;
+ }
+
+ if (sts == 0 && valfmt == -1 && vset->numval == 1)
+ sts = valfmt = __pmStuffValue(&atom, &vset->vlist[0], dp->type);
+
+ if (sts < 0) {
+ /* failure, encode status in numval, need a different vset size */
+ if (vset_resize(res, i, vset->numval, sts) == -1)
+ return -ENOMEM;
+ }
+ else
+ vset->valfmt = valfmt;
+ }
+ *resp = res;
+
+ return 0;
+}
+
+static int
+pmcd_desc(pmID pmid, pmDesc *desc, pmdaExt *pmda)
+{
+ int i;
+
+ for (i = 0; i < ndesc; i++) {
+ if (desctab[i].pmid == pmid) {
+ *desc = desctab[i];
+ return 0;
+ }
+ }
+ return PM_ERR_PMID;
+}
+
+static int
+pmcd_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ pmValueSet *vsp;
+ int sts = 0;
+ __pmID_int *pmidp;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+ if (pmidp->cluster == 0) {
+ if (pmidp->item == 0) { /* pmcd.control.debug */
+ pmDebug = vsp->vlist[0].value.lval;
+ }
+ else if (pmidp->item == 4) { /* pmcd.control.timeout */
+ int val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ break;
+ }
+ if (val != _pmcd_timeout) {
+ _pmcd_timeout = val;
+ }
+ }
+ else if (pmidp->item == 8) { /* pmcd.control.register */
+ int j;
+ for (j = 0; j < vsp->numval; j++) {
+ if (0 <= vsp->vlist[j].inst && vsp->vlist[j].inst < NUMREG)
+ reg[vsp->vlist[j].inst] = vsp->vlist[j].value.lval;
+ else {
+ sts = PM_ERR_INST;
+ break;
+ }
+ }
+ }
+ else if (pmidp->item == 9) { /* pmcd.control.traceconn */
+ int val = vsp->vlist[0].value.lval;
+ if (val == 0)
+ _pmcd_trace_mask &= (~TR_MASK_CONN);
+ else if (val == 1)
+ _pmcd_trace_mask |= TR_MASK_CONN;
+ else {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ }
+ else if (pmidp->item == 10) { /* pmcd.control.tracepdu */
+ int val = vsp->vlist[0].value.lval;
+ if (val == 0)
+ _pmcd_trace_mask &= (~TR_MASK_PDU);
+ else if (val == 1)
+ _pmcd_trace_mask |= TR_MASK_PDU;
+ else {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ }
+ else if (pmidp->item == 11) { /* pmcd.control.tracebufs */
+ int val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ break;
+ }
+ pmcd_init_trace(val);
+ }
+ else if (pmidp->item == 12) { /* pmcd.control.dumptrace */
+ pmcd_dump_trace(stderr);
+ }
+ else if (pmidp->item == 13) { /* pmcd.control.dumpconn */
+ time_t now;
+ time(&now);
+ fprintf(stderr, "\n->Current PMCD clients at %s", ctime(&now));
+ ShowClients(stderr);
+ }
+ else if (pmidp->item == 14) { /* pmcd.control.tracenobuf */
+ int val = vsp->vlist[0].value.lval;
+ if (val == 0)
+ _pmcd_trace_mask &= (~TR_MASK_NOBUF);
+ else if (val == 1)
+ _pmcd_trace_mask |= TR_MASK_NOBUF;
+ else {
+ sts = PM_ERR_CONV;
+ break;
+ }
+ }
+ else if (pmidp->item == 15) { /* pmcd.control.sighup */
+#ifdef HAVE_SIGHUP
+ /*
+ * send myself SIGHUP
+ */
+ __pmNotifyErr(LOG_INFO, "pmcd reset via pmcd.control.sighup");
+ raise(SIGHUP);
+#endif
+ }
+ else {
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else if (pmidp->cluster == 6) {
+ if (pmidp->item == 0) { /* pmcd.client.whoami */
+ /*
+ * Expect one value for one instance (PM_IN_NULL)
+ *
+ * Use the value from the pmResult to change the value
+ * for the client[] that matches the current pmcd client.
+ */
+ char *cp = vsp->vlist[0].value.pval->vbuf;
+ int j;
+ int last_free = -1;
+
+ if (vsp->numval != 1 || vsp->vlist[0].inst != PM_IN_NULL) {
+ return PM_ERR_INST;
+ }
+ for (j = 0; j < nwhoamis; j++) {
+ if (whoamis[j].id == -1) {
+ /* slot in whoamis[] not in use */
+ last_free = j;
+ continue;
+ }
+ if (whoamis[j].id == this_client_id &&
+ whoamis[j].seq == client[this_client_id].seq) {
+ /* found the one to replace */
+ free(whoamis[j].value);
+ break;
+ }
+ if (!client[whoamis[j].id].status.connected ||
+ client[whoamis[j].id].seq != whoamis[j].seq) {
+ /* old whoamis[] entry, mark as available for reuse */
+ free(whoamis[j].value);
+ whoamis[j].id = -1;
+ last_free = j;
+ }
+ }
+ if (j == nwhoamis) {
+ if (last_free != -1) {
+ j = last_free;
+ }
+ else {
+ nwhoamis++;
+ if ((whoamis = (whoami_t *)realloc(whoamis, nwhoamis*sizeof(whoamis[0]))) == NULL) {
+ __pmNoMem("pmstore whoami", nwhoamis*sizeof(whoamis[0]), PM_RECOV_ERR);
+ nwhoamis = 0;
+ return -ENOMEM;
+ }
+ }
+ whoamis[j].id = this_client_id;
+ whoamis[j].seq = client[this_client_id].seq;
+ }
+ whoamis[j].value = strdup(cp);
+ }
+ else {
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else {
+ /* not one of the metrics we are willing to change */
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+
+ return sts;
+}
+
+void
+__PMDA_INIT_CALL
+pmcd_init(pmdaInterface *dp)
+{
+ char helppath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "pmcd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "pmcd", helppath);
+
+ dp->version.four.profile = pmcd_profile;
+ dp->version.four.fetch = pmcd_fetch;
+ dp->version.four.desc = pmcd_desc;
+ dp->version.four.instance = pmcd_instance;
+ dp->version.four.store = pmcd_store;
+ dp->version.four.ext->e_endCallBack = end_context;
+
+ init_tables(dp->domain);
+
+ pmdaInit(dp, NULL, 0, NULL, 0);
+}
diff --git a/src/pmdas/postfix/GNUmakefile b/src/pmdas/postfix/GNUmakefile
new file mode 100644
index 0000000..a38b0c3
--- /dev/null
+++ b/src/pmdas/postfix/GNUmakefile
@@ -0,0 +1,48 @@
+#!gmake
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = postfix
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/postfix/Install b/src/pmdas/postfix/Install
new file mode 100644
index 0000000..07fe957
--- /dev/null
+++ b/src/pmdas/postfix/Install
@@ -0,0 +1,34 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the postfix PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=postfix
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+which qshape >/dev/null 2>&1
+if test $? -ne 0; then
+ echo "Postfix qshape utility is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/postfix/Remove b/src/pmdas/postfix/Remove
new file mode 100644
index 0000000..5d06c62
--- /dev/null
+++ b/src/pmdas/postfix/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the postfix PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=postfix
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/postfix/pmdapostfix.pl b/src/pmdas/postfix/pmdapostfix.pl
new file mode 100644
index 0000000..4acb985
--- /dev/null
+++ b/src/pmdas/postfix/pmdapostfix.pl
@@ -0,0 +1,266 @@
+#
+# Copyright (c) 2012,2014 Red Hat.
+# Copyright (c) 2009-2010 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use Time::HiRes qw ( time );
+
+use vars qw( $pmda );
+use vars qw( %caches );
+use vars qw( %logstats );
+my @logfiles = ( '/var/log/mail.log', '/var/log/maillog' );
+my $logfile;
+my $qshape = 'qshape -b 10 -t 5';
+my $refresh = 5.0; # 5 seconds between qshape refreshes
+
+my $cached = 0;
+
+my $postfix_queues_indom = 0;
+my @postfix_queues_dom = (
+ 0 => 'total',
+ 1 => '0-5 mins',
+ 2 => '5-10 mins',
+ 3 => '10-20 mins',
+ 4 => '20-40 mins',
+ 5 => '40-80 mins',
+ 6 => '80-160 mins',
+ 7 => '160-320 mins',
+ 8 => '320-640 mins',
+ 9 => '640-1280 mins',
+ 10=> '1280+ mins',
+ );
+
+my $postfix_sent_indom = 1;
+my @postfix_sent_dom = (
+ 0 => 'smtp',
+ 1 => 'local',
+ );
+
+my $postfix_received_indom = 2;
+my @postfix_received_dom = (
+ 0 => 'local',
+ 1 => 'smtp',
+ );
+
+sub postfix_do_refresh
+{
+ QUEUE:
+ foreach my $qname ("maildrop", "incoming", "hold", "active", "deferred") {
+ unless (open(PIPE, "$qshape $qname |")) {
+ $pmda->log("couldn't execute '$qshape $qname'");
+ next QUEUE;
+ }
+ while(<PIPE>) {
+ last if (/^[\t ]*TOTAL /);
+ }
+ close PIPE;
+
+ unless (/^[\t ]*TOTAL /) {
+ $pmda->log("malformed output for '$qshape $qname': $_");
+ next QUEUE;
+ }
+
+ s/^[\t ]*//;
+ s/[\t ]+/ /g;
+
+ my @items = split(/ /);
+
+ $caches{$qname}{0} = $items[1];
+ $caches{$qname}{1} = $items[2];
+ $caches{$qname}{2} = $items[3];
+ $caches{$qname}{3} = $items[4];
+ $caches{$qname}{4} = $items[5];
+ $caches{$qname}{5} = $items[6];
+ $caches{$qname}{6} = $items[7];
+ $caches{$qname}{7} = $items[8];
+ $caches{$qname}{8} = $items[9];
+ $caches{$qname}{9} = $items[10];
+ $caches{$qname}{10} = $items[11];
+ }
+}
+
+sub postfix_log_parser
+{
+ ( undef, $_ ) = @_;
+
+ if (/status=sent/) {
+ return unless (/ postfix\//);
+
+ my $relay = "";
+
+ if (/relay=([^,]+)/o) {
+ $relay = $1;
+ }
+
+ if ($relay !~ /\[/o) {
+ # if we are about to define a new instance, let's add it to the
+ # domain as well
+ my $idx = 0;
+ my $key;
+ my %tmp = @postfix_sent_dom;
+
+ foreach $key (sort keys %tmp) {
+ last if ($relay eq $tmp{$key});
+ $idx += 1;
+ }
+
+ if ((2 * $idx) == @postfix_sent_dom) {
+ push(@postfix_sent_dom, $idx=>$relay);
+ $pmda->replace_indom($postfix_sent_indom, \@postfix_sent_dom);
+ }
+
+ $logstats{"sent"}{$idx} += 1;
+ } else {
+ $logstats{"sent"}{0} += 1;
+ }
+ } elsif (/smtpd.*client=/) {
+ $logstats{"received"}{1} += 1;
+ } elsif (/pickup.*(sender|uid)=/) {
+ $logstats{"received"}{0} += 1;
+ }
+}
+
+sub postfix_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+
+ my $now = time;
+
+ #$pmda->log("postfix_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if (!defined($metric_name)) { return (PM_ERR_PMID, 0); }
+
+ if ($cluster == 0) {
+ my $qname;
+
+ if ($now - $cached > $refresh) {
+ postfix_do_refresh();
+ $cached = $now;
+ }
+
+ $qname = $metric_name;
+ $qname =~ s/^postfix\.queues\.//;
+
+ return (PM_ERR_AGAIN, 0) unless defined($caches{$qname});
+ return ($caches{$qname}{$inst}, 1);
+ } elsif ($cluster == 1) {
+ my $dir = $metric_name;
+ $dir =~ s/^postfix\.//;
+
+ return (PM_ERR_AGAIN, 0) unless defined($logstats{$dir});
+ return (PM_ERR_AGAIN, 0) unless defined($logstats{$dir}{$inst});
+ return ($logstats{$dir}{$inst}, 1);
+ }
+
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('postfix', 103);
+$pmda->connect_pmcd;
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, $postfix_queues_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.queues.maildrop', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, $postfix_queues_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.queues.incoming', '', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, $postfix_queues_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.queues.hold', '', '');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U32, $postfix_queues_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.queues.active', '', '');
+$pmda->add_metric(pmda_pmid(0,4), PM_TYPE_U32, $postfix_queues_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.queues.deferred', '', '');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U32, $postfix_sent_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.sent', '', '');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, $postfix_received_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postfix.received', '', '');
+
+$logstats{"sent"}{0} = 0;
+$logstats{"received"}{0} = 0;
+$logstats{"received"}{1} = 0;
+
+foreach my $file ( @logfiles ) {
+ if ( -r $file ) {
+ $logfile = $file;
+ }
+}
+die 'No Postfix log file found' unless defined($logfile);
+
+$pmda->add_indom($postfix_queues_indom, \@postfix_queues_dom, '', '');
+$pmda->add_indom($postfix_sent_indom, \@postfix_sent_dom, '', '');
+$pmda->add_indom($postfix_received_indom, \@postfix_received_dom, '', '');
+$pmda->add_tail($logfile, \&postfix_log_parser, 0);
+$pmda->set_fetch_callback(\&postfix_fetch_callback);
+$pmda->set_user('postfix');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdapostfix - Postfix performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdapostfix> is a Performance Metrics Domain Agent (PMDA) which exports
+mail queue sizes as reported by qshape(1), as well as aggregate statistics
+collected from mail.log.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the Postfix performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/postfix
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/postfix
+ # ./Remove
+
+B<pmdapostfix> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/postfix/Install
+
+installation script for the B<pmdapostfix> agent
+
+=item $PCP_PMDAS_DIR/postfix/Remove
+
+undo installation script for the B<pmdapostfix> agent
+
+=item $PCP_LOG_DIR/pmcd/postfix.log
+
+default log file for error messages from B<pmdapostfix>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and qshape(1).
diff --git a/src/pmdas/postgresql/GNUmakefile b/src/pmdas/postgresql/GNUmakefile
new file mode 100644
index 0000000..114c0cf
--- /dev/null
+++ b/src/pmdas/postgresql/GNUmakefile
@@ -0,0 +1,51 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = postgresql
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl pmlogconf.summary
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/postgresql/Install b/src/pmdas/postgresql/Install
new file mode 100755
index 0000000..3cf31be
--- /dev/null
+++ b/src/pmdas/postgresql/Install
@@ -0,0 +1,40 @@
+#! /bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the PostgreSQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=postgresql
+perl_opt=true
+daemon_opt=false
+forced_restart=true
+
+perl -e "use DBI" 2>/dev/null
+if test $? -ne 0; then
+ echo "Perl database interface (DBI) is not installed"
+ exit 1
+fi
+
+perl -e "use DBD::Pg" 2>/dev/null
+if test $? -ne 0; then
+ echo "Postgres database driver (DBD::Pg) is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/postgresql/Remove b/src/pmdas/postgresql/Remove
new file mode 100755
index 0000000..db3f251
--- /dev/null
+++ b/src/pmdas/postgresql/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the PostgreSQL PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=postgresql
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/postgresql/pmdapostgresql.pl b/src/pmdas/postgresql/pmdapostgresql.pl
new file mode 100644
index 0000000..9290981
--- /dev/null
+++ b/src/pmdas/postgresql/pmdapostgresql.pl
@@ -0,0 +1,1546 @@
+#
+# Copyright (c) 2011 Nathan Scott. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use DBI;
+
+my $database = 'dbi:Pg:dbname=postgres';
+my $username = 'postgres';
+my $password = ''; # DBI parameter, typically unused for postgres
+
+# Configuration files for overriding the above settings
+for my $file ( '/etc/pcpdbi.conf', # system defaults (lowest priority)
+ pmda_config('PCP_PMDAS_DIR') . '/postgresql/postgresql.conf',
+ './postgresql.conf' ) { # current directory (high priority)
+ eval `cat $file` unless ! -f $file;
+}
+
+use vars qw( $pmda $dbh );
+my $pm_in_null = PM_IN_NULL;
+my $all_rel_indom = 0; my @all_rel_instances;
+my $sys_rel_indom = 1; my @sys_rel_instances;
+my $user_rel_indom = 2; my @user_rel_instances;
+my $process_indom = 3; my @process_instances;
+my $function_indom = 4; my @function_instances;
+my $database_indom = 5; my @database_instances;
+my $all_index_indom = 6; my @all_index_instances;
+my $sys_index_indom = 7; my @sys_index_instances;
+my $user_index_indom = 8; my @user_index_instances;
+my $all_seq_indom = 9; my @all_seq_instances;
+my $sys_seq_indom = 10; my @sys_seq_instances;
+my $user_seq_indom = 11; my @user_seq_instances;
+my $replicant_indom = 12; my @replicant_instances;
+
+# hash of hashes holding DB handle, last values, and min version indexed by table name
+my %tables_by_name = (
+ pg_stat_activity => { handle => undef, values => {}, version => undef },
+ pg_stat_bgwriter => { handle => undef, values => {}, version => undef },
+ pg_stat_database => { handle => undef, values => {}, version => undef },
+ pg_stat_database_conflicts => { handle => undef, values => {}, version => 9.1 },
+ pg_stat_replication => { handle => undef, values => {}, version => 9.1 },
+ pg_stat_all_tables => { handle => undef, values => {}, version => undef },
+ pg_stat_sys_tables => { handle => undef, values => {}, version => undef },
+ pg_stat_user_tables => { handle => undef, values => {}, version => undef },
+ pg_stat_all_indexes => { handle => undef, values => {}, version => undef },
+ pg_stat_sys_indexes => { handle => undef, values => {}, version => undef },
+ pg_stat_user_indexes => { handle => undef, values => {}, version => undef },
+ pg_statio_all_tables => { handle => undef, values => {}, version => undef },
+ pg_statio_sys_tables => { handle => undef, values => {}, version => undef },
+ pg_statio_user_tables => { handle => undef, values => {}, version => undef },
+ pg_statio_all_indexes => { handle => undef, values => {}, version => undef },
+ pg_statio_sys_indexes => { handle => undef, values => {}, version => undef },
+ pg_statio_user_indexes => { handle => undef, values => {}, version => undef },
+ pg_statio_all_sequences => { handle => undef, values => {}, version => undef },
+ pg_statio_sys_sequences => { handle => undef, values => {}, version => undef },
+ pg_statio_user_sequences => { handle => undef, values => {}, version => undef },
+ pg_stat_user_functions => { handle => undef, values => {}, version => undef },
+ pg_stat_xact_user_functions => { handle => undef, values => {}, version => 9.1 },
+ pg_stat_xact_all_tables => { handle => undef, values => {}, version => 9.1 },
+ pg_stat_xact_sys_tables => { handle => undef, values => {}, version => 9.1 },
+ pg_stat_xact_user_tables => { handle => undef, values => {}, version => 9.1 },
+ pg_active => { handle => undef, values => {}, version => undef, sql => 'select pg_is_in_recovery(), pg_current_xlog_location()' },
+ pg_recovery => { handle => undef, values => {}, version => undef, sql => 'select pg_is_in_recovery(), pg_last_xlog_receive_location(), pg_last_xlog_replay_location()' },
+);
+
+# hash of hashes holding setup and refresh function, indexed by PMID cluster
+my %tables_by_cluster = (
+ '0' => {
+ name => 'pg_stat_activity',
+ setup => \&setup_activity,
+ indom => $process_indom,
+ refresh => \&refresh_activity },
+ '1' => {
+ name => 'pg_stat_bgwriter',
+ setup => \&setup_bgwriter,
+ indom => PM_INDOM_NULL,
+ refresh => \&refresh_bgwriter },
+ '2' => {
+ name => 'pg_stat_database',
+ setup => \&setup_database,
+ indom => $database_indom,
+ refresh => \&refresh_database },
+ '3' => {
+ name => 'pg_stat_user_functions',
+ setup => \&setup_user_functions,
+ indom => $function_indom,
+ refresh => \&refresh_user_functions },
+ '4' => {
+ name => 'pg_stat_xact_user_functions',
+ setup => \&setup_xact_user_functions,
+ indom => $function_indom,
+ refresh => \&refresh_user_functions }, # identical refresh routine
+ '5' => {
+ name => 'pg_stat_database_conflicts',
+ setup => \&setup_database_conflicts,
+ indom => $database_indom,
+ refresh => \&refresh_database }, # identical refresh routine
+ '6' => {
+ name => 'pg_stat_replication',
+ setup => \&setup_replication,
+ indom => $replicant_indom,
+ refresh => \&refresh_replication },
+ '7' => {
+ name => 'pg_active',
+ setup => \&setup_active_functions,
+ indom => PM_INDOM_NULL,
+ refresh => \&refresh_active_functions },
+ '8' => {
+ name => 'pg_recovery',
+ setup => \&setup_recovery_functions,
+ indom => PM_INDOM_NULL,
+ refresh => \&refresh_recovery_functions },
+ '10' => {
+ name => 'pg_stat_all_tables',
+ setup => \&setup_stat_tables,
+ indom => $all_rel_indom,
+ params => 'all_tables',
+ refresh => \&refresh_all_tables },
+ '11' => {
+ name => 'pg_stat_sys_tables',
+ setup => \&setup_stat_tables,
+ indom => $sys_rel_indom,
+ params => 'sys_tables',
+ refresh => \&refresh_sys_tables },
+ '12' => {
+ name => 'pg_stat_user_tables',
+ setup => \&setup_stat_tables,
+ indom => $user_rel_indom,
+ params => 'user_tables',
+ refresh => \&refresh_user_tables },
+ '13' => {
+ name => 'pg_stat_all_indexes',
+ setup => \&setup_stat_indexes,
+ indom => $all_index_indom,
+ params => 'all_indexes',
+ refresh => \&refresh_all_indexes },
+ '14' => {
+ name => 'pg_stat_sys_indexes',
+ setup => \&setup_stat_indexes,
+ params => 'sys_indexes',
+ indom => $sys_index_indom,
+ refresh => \&refresh_sys_indexes },
+ '15' => {
+ name => 'pg_stat_user_indexes',
+ setup => \&setup_stat_indexes,
+ indom => $user_index_indom,
+ params => 'user_indexes',
+ refresh => \&refresh_user_indexes },
+ '16' => {
+ name => 'pg_stat_xact_all_tables',
+ setup => \&setup_stat_xact_tables,
+ indom => $all_rel_indom,
+ params => 'all_tables',
+ refresh => \&refresh_xact_all_tables },
+ '17' => {
+ name => 'pg_stat_xact_sys_tables',
+ setup => \&setup_stat_xact_tables,
+ indom => $sys_rel_indom,
+ params => 'sys_tables',
+ refresh => \&refresh_xact_sys_tables },
+ '18' => {
+ name => 'pg_stat_xact_user_tables',
+ setup => \&setup_stat_xact_tables,
+ indom => $user_rel_indom,
+ params => 'user_tables',
+ refresh => \&refresh_xact_user_tables },
+ '30' => {
+ name => 'pg_statio_all_tables',
+ setup => \&setup_statio_tables,
+ indom => $all_rel_indom,
+ params => 'all_tables',
+ refresh => \&refresh_io_all_tables },
+ '31' => {
+ name => 'pg_statio_sys_tables',
+ setup => \&setup_statio_tables,
+ indom => $sys_rel_indom,
+ params => 'sys_tables',
+ refresh => \&refresh_io_sys_tables },
+ '32' => {
+ name => 'pg_statio_user_tables',
+ setup => \&setup_statio_tables,
+ indom => $user_rel_indom,
+ params => 'user_tables',
+ refresh => \&refresh_io_user_tables },
+ '33' => {
+ name => 'pg_statio_all_indexes',
+ setup => \&setup_statio_indexes,
+ indom => $all_index_indom,
+ params => 'all_indexes',
+ refresh => \&refresh_io_all_indexes },
+ '34' => {
+ name => 'pg_statio_sys_indexes',
+ setup => \&setup_statio_indexes,
+ indom => $sys_index_indom,
+ params => 'sys_indexes',
+ refresh => \&refresh_io_all_indexes },
+ '35' => {
+ name => 'pg_statio_user_indexes',
+ setup => \&setup_statio_indexes,
+ indom => $user_index_indom,
+ params => 'user_indexes',
+ refresh => \&refresh_io_all_indexes },
+ '36' => {
+ name => 'pg_statio_all_sequences',
+ setup => \&setup_statio_sequences,
+ indom => $all_seq_indom,
+ params => 'all_sequences',
+ refresh => \&refresh_io_all_sequences },
+ '37' => {
+ name => 'pg_statio_sys_sequences',
+ setup => \&setup_statio_sequences,
+ indom => $sys_seq_indom,
+ params => 'sys_sequences',
+ refresh => \&refresh_io_sys_sequences },
+ '38' => {
+ name => 'pg_statio_user_sequences',
+ setup => \&setup_statio_sequences,
+ params => 'user_sequences',
+ indom => $user_seq_indom,
+ refresh => \&refresh_io_user_sequences },
+);
+
+sub postgresql_version_query
+{
+ my $handle = $dbh->prepare("select VERSION()");
+
+ if (defined($handle->execute())) {
+ my $result = $handle->fetchall_arrayref();
+ return 0 unless defined($result);
+ my $version = $result->[0][0];
+ $version =~ s/^PostgreSQL (\d+\.\d+)\.\d+ .*/$1/g;
+ return $version;
+ }
+ return 0;
+}
+
+sub postgresql_connection_setup
+{
+ if (!defined($dbh)) {
+ $dbh = DBI->connect($database, $username, $password,
+ {AutoCommit => 1, pg_bool_tf => 0});
+ if (defined($dbh)) {
+ $pmda->log("PostgreSQL connection established");
+ my $version = postgresql_version_query();
+
+ foreach my $key (keys %tables_by_name) {
+ my $minversion = $tables_by_name{$key}{version};
+ my $sqlquery = $tables_by_name{$key}{sql};
+ my $query;
+
+ $minversion = 0 unless defined($minversion);
+ if ($minversion > $version) {
+ $pmda->log("Skipping table $key, not supported on $version");
+ } elsif (defined($sqlquery)) {
+ $query = $dbh->prepare($sqlquery);
+ } else {
+ $query = $dbh->prepare("select * from $key");
+ }
+ $tables_by_name{$key}{handle} = $query unless !defined($query);
+ }
+ }
+ }
+}
+
+sub postgresql_indoms_setup
+{
+ $all_rel_indom = $pmda->add_indom($all_rel_indom, \@all_rel_instances,
+ 'Instance domain for PostgreSQL relations, all tables', '');
+ $sys_rel_indom = $pmda->add_indom($sys_rel_indom, \@sys_rel_instances,
+ 'Instance domain for PostgreSQL relations, system tables', '');
+ $user_rel_indom = $pmda->add_indom($user_rel_indom, \@user_rel_instances,
+ 'Instance domain for PostgreSQL relations, user tables', '');
+
+ $function_indom = $pmda->add_indom($function_indom, \@function_instances,
+ 'Instance domain exporting PostgreSQL user functions', '');
+
+ $process_indom = $pmda->add_indom($process_indom, \@process_instances,
+ 'Instance domain exporting each PostgreSQL client process', '');
+
+ $database_indom = $pmda->add_indom($database_indom, \@database_instances,
+ 'Instance domain exporting each PostgreSQL database', '');
+
+ $replicant_indom = $pmda->add_indom($replicant_indom, \@replicant_instances,
+ 'Instance domain exporting PostgreSQL replication processes', '');
+
+ $all_index_indom = $pmda->add_indom($all_index_indom, \@all_index_instances,
+ 'Instance domain for PostgreSQL indexes, all tables', '');
+ $sys_index_indom = $pmda->add_indom($sys_index_indom, \@sys_index_instances,
+ 'Instance domain for PostgreSQL indexes, system tables', '');
+ $user_index_indom = $pmda->add_indom($user_index_indom, \@user_index_instances,
+ 'Instance domain for PostgreSQL indexes, user tables', '');
+
+ $all_seq_indom = $pmda->add_indom($all_seq_indom, \@all_seq_instances,
+ 'Instance domain for PostgreSQL sequences, all tables', '');
+ $sys_seq_indom = $pmda->add_indom($sys_seq_indom, \@sys_seq_instances,
+ 'Instance domain for PostgreSQL sequences, system tables', '');
+ $user_seq_indom = $pmda->add_indom($user_seq_indom, \@user_seq_instances,
+ 'Instance domain for PostgreSQL sequences, user tables', '');
+}
+
+sub postgresql_metrics_setup
+{
+ foreach my $cluster (sort (keys %tables_by_cluster)) {
+ my $setup = $tables_by_cluster{"$cluster"}{setup};
+ my $indom = $tables_by_cluster{"$cluster"}{indom};
+ &$setup($cluster, $indom, $tables_by_cluster{"$cluster"}{params});
+ }
+}
+
+sub postgresql_refresh
+{
+ my ($cluster) = @_;
+
+ my $name = $tables_by_cluster{"$cluster"}{name};
+ my $refresh = $tables_by_cluster{"$cluster"}{refresh};
+ &$refresh($tables_by_name{$name});
+}
+
+sub postgresql_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ my ($key, $value, $valueref, @columns );
+
+ return (PM_ERR_PMID, 0) unless defined($metric_name);
+ $key = $metric_name;
+ $key =~ s/^postgresql\./pg_/;
+ $key =~ s/\.[a-zA-Z0-9_]*$//;
+ $key =~ s/\./_/g;
+
+ # $pmda->log("fetch_cb $metric_name $cluster:$item ($inst) - $key");
+
+ $valueref = $tables_by_name{$key}{values}{"$inst"};
+ if (!defined($valueref)) {
+ return (PM_ERR_INST, 0) unless ($inst == PM_IN_NULL);
+ return (PM_ERR_VALUE, 0);
+ }
+
+ @columns = @$valueref;
+ $value = $columns[$item];
+ return (PM_ERR_APPVERSION, 0) unless defined($value);
+
+ return ($value, 1);
+}
+
+#
+# Refresh routines - one per table (cluster) - format database query
+# result set for later use by the generic fetch callback routine.
+#
+sub refresh_results
+{
+ my $tableref = shift;
+ my %table = %$tableref;
+ my ( $handle, $valuesref ) = ( $table{handle}, $table{values} );
+ my %values = %$valuesref;
+
+ %values = (); # clear any previous values
+
+ if (defined($dbh) && defined($handle)) {
+ if (defined($handle->execute())) {
+ return $handle->fetchall_arrayref();
+ }
+ $table{handle} = undef;
+ }
+ return undef;
+}
+
+sub refresh_activity
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @process_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $process_instances[($i*2)] = "$result->[$i][2]";
+ $tableref = $result->[$i];
+ $process_instances[($i*2)+1] = "$tableref->[2] $tableref->[5]";
+ # 9.0 does not have client_hostname, deal with that first
+ splice @$tableref, 7, 0, (undef) unless (@$tableref > 13);
+ # special case needed for 'client_*' columns (6 -> 8), may be null
+ for my $j (6 .. 8) { # for each special case column
+ $tableref->[$j] = '' unless (defined($tableref->[$j]));
+ }
+ $table{values}{$instid} = $tableref;
+ }
+ }
+ $pmda->replace_indom($process_indom, \@process_instances);
+}
+
+sub refresh_replication
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @replicant_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $replicant_instances[($i*2)] = "$result->[$i][2]";
+ $replicant_instances[($i*2)+1] = "$result->[$i][2] $result->[$i][5]";
+ # special case needed for 'client_*' columns (4 -> 5)
+ for my $j (4 .. 5) { # for each special case column
+ $result->[$i][$j] = '' unless (defined($result->[$i][$j]));
+ }
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($replicant_indom, \@replicant_instances);
+}
+
+sub refresh_bgwriter
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ $table{values}{"$pm_in_null"} = \@{$result->[0]} unless (!defined($result));
+}
+
+sub refresh_active_functions
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ # Results from SQL (2 values; 2nd may well contain undef):
+ # select pg_is_in_recovery(), pg_current_xlog_location(),
+ # We need to split out the log identifier and log record offset in the
+ # final value, and save separately into the cached result table.
+ #
+ if (defined($result)) {
+ my $arrayref = $result->[0];
+ my @values = @$arrayref;
+ my @massaged = ( $values[0], undef,undef );
+ my $moffset = 1; # index for @massaged array
+
+ if (defined($values[1])) {
+ my @pieces = split /\//, $values[1];
+ $massaged[$moffset++] = hex($pieces[0]);
+ $massaged[$moffset++] = hex($pieces[1]);
+ }
+ $table{values}{"$pm_in_null"} = \@massaged;
+ }
+}
+
+sub refresh_recovery_functions
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ # Results from SQL (3 values; 3rd,4th may well contain undef):
+ # select pg_is_in_recovery(),
+ # pg_last_xlog_receive_location(), pg_last_xlog_replay_location()
+ # We need to split out the log identifier and log record offset in the
+ # final values, and save separately into the cached result table.
+ #
+ if (defined($result)) {
+ my $arrayref = $result->[0];
+ my @values = @$arrayref;
+ my @massaged = ( $values[0], undef,undef, undef,undef );
+ my $moffset = 1; # index for @massaged array
+
+ foreach my $voffset (1..2) { # index for @values array
+ if (defined($values[$voffset])) {
+ my @pieces = split /\//, $values[$voffset];
+ $massaged[$moffset++] = hex($pieces[0]);
+ $massaged[$moffset++] = hex($pieces[1]);
+ }
+ }
+ $table{values}{"$pm_in_null"} = \@massaged;
+ }
+}
+
+sub refresh_database
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @database_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $database_instances[($i*2)] = $result->[$i][0];
+ $database_instances[($i*2)+1] = $result->[$i][1];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($database_indom, \@database_instances);
+}
+
+sub refresh_user_functions
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @function_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $function_instances[($i*2)] = $result->[$i][0];
+ $function_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($function_indom, \@function_instances);
+}
+
+sub refresh_all_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_rel_instances[($i*2)] = $result->[$i][0];
+ $all_rel_instances[($i*2)+1] = $result->[$i][2];
+ # special case needed for 'last_*' columns (13 -> 16)
+ for my $j (13 .. 16) { # for each special case column
+ $result->[$i][$j] = '' unless (defined($result->[$i][$j]));
+ }
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_rel_indom, \@all_rel_instances);
+}
+
+sub refresh_sys_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_rel_instances[($i*2)] = $result->[$i][0];
+ $sys_rel_instances[($i*2)+1] = $result->[$i][2];
+ # special case needed for 'last_*' columns (13 -> 16)
+ for my $j (13 .. 16) { # for each special case column
+ $result->[$i][$j] = '' unless (defined($result->[$i][$j]));
+ }
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_rel_indom, \@sys_rel_instances);
+}
+
+sub refresh_user_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_rel_instances[($i*2)] = $result->[$i][0];
+ $user_rel_instances[($i*2)+1] = $result->[$i][2];
+ # special case needed for 'last_*' columns (13 -> 16)
+ for my $j (13 .. 16) { # for each special case column
+ $result->[$i][$j] = '' unless (defined($result->[$i][$j]));
+ }
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_rel_indom, \@user_rel_instances);
+}
+
+sub refresh_xact_all_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_rel_instances[($i*2)] = $result->[$i][0];
+ $all_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_rel_indom, \@all_rel_instances);
+}
+
+sub refresh_xact_sys_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_rel_instances[($i*2)] = $result->[$i][0];
+ $sys_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_rel_indom, \@sys_rel_instances);
+}
+
+sub refresh_xact_user_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_rel_instances[($i*2)] = $result->[$i][0];
+ $user_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_rel_indom, \@user_rel_instances);
+}
+
+sub refresh_all_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_index_instances[($i*2)] = $result->[$i][1];
+ $all_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_index_indom, \@all_index_instances);
+}
+
+sub refresh_sys_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_index_instances[($i*2)] = $result->[$i][1];
+ $sys_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_index_indom, \@sys_index_instances);
+}
+
+sub refresh_user_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_index_instances[($i*2)] = $result->[$i][1];
+ $user_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_index_indom, \@user_index_instances);
+}
+
+sub refresh_io_all_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_rel_instances[($i*2)] = $result->[$i][0];
+ $all_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_rel_indom, \@all_rel_instances);
+}
+
+sub refresh_io_sys_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_rel_instances[($i*2)] = $result->[$i][0];
+ $sys_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_rel_indom, \@sys_rel_instances);
+}
+
+sub refresh_io_user_tables
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_rel_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_rel_instances[($i*2)] = $result->[$i][0];
+ $user_rel_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_rel_indom, \@user_rel_instances);
+}
+
+sub refresh_io_all_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_index_instances[($i*2)] = $result->[$i][1];
+ $all_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_index_indom, \@all_index_instances);
+}
+
+sub refresh_io_sys_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_index_instances[($i*2)] = $result->[$i][1];
+ $sys_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_index_indom, \@sys_index_instances);
+}
+
+sub refresh_io_user_indexes
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_index_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_index_instances[($i*2)] = $result->[$i][1];
+ $user_index_instances[($i*2)+1] = $result->[$i][4];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_index_indom, \@user_index_instances);
+}
+
+sub refresh_io_all_sequences
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @all_seq_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $all_seq_instances[($i*2)] = $result->[$i][0];
+ $all_seq_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($all_seq_indom, \@all_seq_instances);
+}
+
+sub refresh_io_sys_sequences
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @sys_seq_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $sys_seq_instances[($i*2)] = $result->[$i][0];
+ $sys_seq_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($sys_seq_indom, \@sys_seq_instances);
+}
+
+sub refresh_io_user_sequences
+{
+ my $tableref = shift;
+ my $result = refresh_results($tableref);
+ my %table = %$tableref;
+
+ @user_seq_instances = (); # refresh indom too
+ if (defined($result)) {
+ for my $i (0 .. $#{$result}) { # for each row (instance) returned
+ my $instid = $user_seq_instances[($i*2)] = $result->[$i][0];
+ $user_seq_instances[($i*2)+1] = $result->[$i][2];
+ $table{values}{$instid} = $result->[$i];
+ }
+ }
+ $pmda->replace_indom($user_seq_indom, \@user_seq_instances);
+}
+
+#
+# Setup routines - one per cluster, add metrics to PMDA
+#
+# For help text, see
+# http://www.postgresql.org/docs/9.2/static/monitoring-stats.html
+#
+# and the Postgres Licence:
+# Portions Copyright (c) 1996-2012, The PostgreSQL Global Development Group
+#
+# Portions Copyright (c) 1994, The Regents of the University of California
+#
+# Permission to use, copy, modify, and distribute this software
+# and its documentation for any purpose, without fee, and without a
+# written agreement is hereby granted, provided that the above copyright
+# notice and this paragraph and the following two paragraphs appear in
+# all copies.
+#
+# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
+# FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+# INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND
+# ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
+# PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY
+# OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
+# UPDATES, ENHANCEMENTS, OR MODIFICATIONS
+#
+
+sub setup_activity
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: procpid + application_name
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.datid',
+ 'OID of the database this backend is connected to', '');
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.datname',
+ 'Name of the database this backend is connected to', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.usesysid',
+ 'OID of the user logged into this backend', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.usename',
+ 'Name of the user logged into this backend', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.application_name',
+ 'Name of the application that is connected to this backend', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.client_addr',
+ 'Client IP address',
+'IP address of the client connected to this backend. If this field is null,
+it indicates either that the client is connected via a Unix socket on the
+server machine or that this is an internal process such as autovacuum.');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.client_hostname',
+ 'Host name of the connected client',
+'Client host name is derived from reverse DNS lookup of
+postgresql.stat.activity.client_addr. This field will only be non-null
+for IP connections, and only when log_hostname is enabled.');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.client_port',
+ 'Client TCP port number',
+'TCP port number that the client is using for communication with this
+backend. Value will be -1 if a Unix socket is used.');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.backend_start',
+ 'Time when this process was started',
+'Time when this client process connected to the server.');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.xact_start',
+ 'Transaction start time',
+'Time when this process' . "'" . ' current transaction was started.
+The value will be null if no transaction is active. If the
+current query is the first of its transaction, value is equal to
+postgresql.stat.activity.query_start.');
+ $pmda->add_metric(pmda_pmid($cluster,11), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.query_start',
+ 'Query start time',
+'Time when the currently active query was started, or if state is not
+active, when the last query was started');
+ $pmda->add_metric(pmda_pmid($cluster,12), PM_TYPE_32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.waiting',
+ 'True if this backend is currently waiting on a lock', '');
+ $pmda->add_metric(pmda_pmid($cluster,13), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.activity.current_query',
+ 'Most recent query',
+'Text of this backend' . "'" . 's most recent query. If state is active this field
+shows the currently executing query. In all other states, it shows the
+last query that was executed.');
+}
+
+sub setup_replication
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: procpid + application_name
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.usesysid',
+ 'OID of the user logged into this WAL sender process', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.usename',
+ 'Name of the user logged into this WAL sender process', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.application_name',
+ 'Name of the application that is connected to this WAL sender', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.client_addr',
+ 'WAL client IP address',
+'IP address of the client connected to this WAL sender. If this field is
+null, it indicates that the client is connected via a Unix socket on the
+server machine.');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.client_hostname',
+ 'WAL client host name',
+'Host name of the connected client, as reported by a reverse DNS lookup of
+postgresql.stat.replication.client_addr. This field will only be non-null
+for IP connections, and only when log_hostname is enabled.');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.client_port',
+ 'WAL client TCP port',
+'TCP port number that the client is using for communication with this WAL
+sender, or -1 if a Unix socket is used.');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.backend_start',
+ 'Time when when the client connected to this WAL sender', '');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.state',
+ 'Current WAL sender state', '');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.sent_location',
+ 'Last transaction log position sent on this connection', '');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.write_location',
+ 'Last transaction log position written to disk by this standby server', '');
+ $pmda->add_metric(pmda_pmid($cluster,11), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.flush_location',
+ 'Last transaction log position flushed to disk by this standby server', '');
+ $pmda->add_metric(pmda_pmid($cluster,12), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.replay_location',
+ 'Last transaction log position replayed into the database on this standby server', '');
+ $pmda->add_metric(pmda_pmid($cluster,13), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.sync_priority',
+ 'Priority of this standby server for being chosen as the synchronous standby', '');
+ $pmda->add_metric(pmda_pmid($cluster,14), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.replication.sync_state',
+ 'Synchronous state of this standby server', '');
+}
+
+sub setup_stat_xact_tables
+{
+ my ($cluster, $indom, $tables) = @_;
+
+ # indom: relid + relname
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.schemaname",
+ 'Name of the schema that this table is in',
+'');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.seq_scan",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.seq_tup_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.idx_scan",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.idx_tup_fetch",
+ 'Number of rows fetched by queries in this database', '');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.n_tup_ins",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.n_tup_upd",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.n_tup_del",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.xact.$tables.n_tup_hot_upd",
+ '', '');
+}
+
+sub setup_stat_tables
+{
+ my ($cluster, $indom, $tables) = @_;
+
+ # indom: relid + relname
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.schemaname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.seq_scan",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.seq_tup_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.idx_scan",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.idx_tup_fetch",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_tup_ins",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_tup_upd",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_tup_del",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_tup_hot_upd",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,11), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_live_tup",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,12), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.n_dead_tup",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,13), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.last_vacuum",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,14), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.last_autovacuum",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,15), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.last_analyze",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,16), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$tables.last_autoanalyze",
+ '', '');
+}
+
+sub setup_bgwriter
+{
+ my ($cluster, $indom) = @_;
+
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.checkpoints_timed',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.checkpoints_req',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.buffers_checkpoints',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.buffers_clean',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.maxwritten_clean',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.buffers_backend',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.bgwriter.buffers_alloc',
+ '', '');
+}
+
+sub setup_active_functions
+{
+ my ($cluster, $indom) = @_;
+
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.active.is_in_recovery',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.active.xlog_current_location_log_id',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.active.xlog_current_location_offset',
+ '', '');
+}
+
+sub setup_recovery_functions
+{
+ my ($cluster, $indom) = @_;
+
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.recovery.is_in_recovery',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.recovery.xlog_receive_location_log_id',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.recovery.xlog_receive_location_offset',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.recovery.xlog_replay_location_log_id',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U64, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.recovery.xlog_replay_location_offset',
+ '', '');
+}
+
+sub setup_database_conflicts
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: datid + datname
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database_conflicts.tablespace',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database_conflicts.lock',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database_conflicts.snapshot',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database_conflicts.bufferpin',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database_conflicts.deadlock',
+ '', '');
+
+}
+sub setup_database
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: datid + datname
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_U32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.database.numbackends',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.xact_commit',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.xact_rollback',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.blks_read',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.blks_hit',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.tup_returned',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.tup_fetched',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.tup_inserted',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.tup_updated',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,11), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.tup_deleted',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,12), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'postgresql.stat.database.conflicts',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,13), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.database.stats_reset',
+ '', '');
+}
+
+sub setup_stat_indexes
+{
+ my ($cluster, $indom, $indexes) = @_;
+
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$indexes.relid",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$indexes.schemaname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.stat.$indexes.relname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.stat.$indexes.idx_scan",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.stat.$indexes.idx_tup_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.stat.$indexes.idx_tup_fetch",
+ '', '');
+}
+
+sub setup_statio_tables
+{
+ my ($cluster, $indom, $tables) = @_;
+
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.statio.$tables.schemaname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.heap_blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.heap_blks_hit",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.idx_blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.idx_blks_hit",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,7), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.toast_blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,8), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.toast_blks_hit",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,9), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.tidx_blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,10), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$tables.tidx_blks_hit",
+ '', '');
+}
+
+sub setup_statio_indexes
+{
+ my ($cluster, $indom, $indexes) = @_;
+
+ # indom: indexrelid + indexrelname
+ $pmda->add_metric(pmda_pmid($cluster,0), PM_TYPE_32, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.statio.$indexes.relid",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,2), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.statio.$indexes.schemaname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.statio.$indexes.relname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$indexes.idx_blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,6), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$indexes.idx_blks_hit",
+ '', '');
+}
+
+sub setup_statio_sequences
+{
+ my ($cluster, $indom, $sequences) = @_;
+
+ # indom: relid + relname
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ "postgresql.statio.$sequences.schemaname",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$sequences.blks_read",
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U32, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ "postgresql.statio.$sequences.blks_hit",
+ '', '');
+}
+
+sub setup_xact_user_functions
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: funcid + funcname
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.xact.user_functions.schemaname',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.xact.user_functions.calls',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'postgresql.stat.xact.user_functions.total_time',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'postgresql.stat.xact.user_functions.self_time',
+ '', '');
+}
+
+sub setup_user_functions
+{
+ my ($cluster, $indom) = @_;
+
+ # indom: funcid + funcname
+ $pmda->add_metric(pmda_pmid($cluster,1), PM_TYPE_STRING, $indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.user_functions.schemaname',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,3), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,0,0,0,0,0),
+ 'postgresql.stat.user_functions.calls',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,4), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'postgresql.stat.user_functions.total_time',
+ '', '');
+ $pmda->add_metric(pmda_pmid($cluster,5), PM_TYPE_U64, $indom,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'postgresql.stat.user_functions.self_time',
+ '', '');
+}
+
+#
+# Main PMDA thread of execution starts here - setup metrics and callbacks
+# drop root priveleges, then call PMDA 'run' routine to talk to pmcd.
+#
+$pmda = PCP::PMDA->new('postgresql', 110);
+postgresql_metrics_setup();
+postgresql_indoms_setup();
+
+$pmda->set_fetch_callback(\&postgresql_fetch_callback);
+$pmda->set_fetch(\&postgresql_connection_setup);
+$pmda->set_refresh(\&postgresql_refresh);
+$pmda->set_user('postgres');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdapostgresql - PostgreSQL database PMDA
+
+=head1 DESCRIPTION
+
+B<pmdapostgresql> is a Performance Co-Pilot PMDA which extracts
+live performance data from a running PostgreSQL database.
+
+=head1 INSTALLATION
+
+B<pmdapostgresql> uses a configuration file from (in this order):
+
+=over
+
+=item * /etc/pcpdbi.conf
+
+=item * $PCP_PMDAS_DIR/postgresql/postgresql.conf
+
+=back
+
+This file can contain overridden values (Perl code) for the settings
+listed at the start of pmdapostgresql.pl, namely:
+
+=over
+
+=item * database name (see DBI(3) for details)
+
+=item * database username
+
+=back
+
+Once this is setup, you can access the names and values for the
+postgresql performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/postgresql
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/postgresql
+ # ./Remove
+
+B<pmdapostgresql> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /etc/pcpdbi.conf
+
+configuration file for all PCP database monitors
+
+=item $PCP_PMDAS_DIR/postgresql/postgresql.conf
+
+configuration file for B<pmdapostgresql>
+
+=item $PCP_PMDAS_DIR/postgresql/Install
+
+installation script for the B<pmdapostgresql> agent
+
+=item $PCP_PMDAS_DIR/postgresql/Remove
+
+undo installation script for the B<pmdapostgresql> agent
+
+=item $PCP_LOG_DIR/pmcd/postgresql.log
+
+default log file for error messages from B<pmdapostgresql>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), pmdadbping.pl(1) and DBI(3).
diff --git a/src/pmdas/postgresql/pmlogconf.summary b/src/pmdas/postgresql/pmlogconf.summary
new file mode 100644
index 0000000..d278918
--- /dev/null
+++ b/src/pmdas/postgresql/pmlogconf.summary
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident postgresql summary information
+probe postgresql.stat.all_tables.seq_scan ? include : exclude
+ postgresql.stat.database
+ postgresql.stat.all_tables
+ postgresql.stat.all_indexes
+ postgresql.statio.all_tables
+ postgresql.statio.all_indexes
diff --git a/src/pmdas/process/GNUmakefile b/src/pmdas/process/GNUmakefile
new file mode 100644
index 0000000..b04c78f
--- /dev/null
+++ b/src/pmdas/process/GNUmakefile
@@ -0,0 +1,65 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+
+IAM = process
+DOMAIN = PROCESS
+TARGETS = $(IAM)
+CFILES = process.c
+SCRIPTS = Install Remove
+DFILES = README
+LSRCFILES= $(SCRIPTS) pmns help root $(DFILES) process.conf
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so $(TARGETS) \
+ help.dir help.pag
+LLDLIBS = $(PCP_PMDALIB)
+
+default: build-me
+
+include $(BUILDRULES)
+
+# This PMDA is only valid on platforms with a procfs
+# It is also superceded by the cgroup functionality on Linux
+# thus has not been built for some time, for reference only.
+#build-me: $(TARGETS)
+#install install_pcp : default
+# $(INSTALL) -m 755 -d $(PMDADIR)
+# $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+# $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+# $(INSTALL) -m 644 $(DFILES) pmns help root domain.h $(PMDADIR)
+# $(INSTALL) -m 644 process.conf $(PMDADIR)/process.conf
+#else
+build-me:
+install:
+#endif
+
+$(IAM): $(OBJECTS)
+
+default_pcp : default
+
+install_pcp : install
+
+process.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/process/Install b/src/pmdas/process/Install
new file mode 100755
index 0000000..7bc54b9
--- /dev/null
+++ b/src/pmdas/process/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2001 Alan Bailey. All Rights Reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the process PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=process
+pmda_interface=2
+forced_restart=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/process/README b/src/pmdas/process/README
new file mode 100644
index 0000000..78579e8
--- /dev/null
+++ b/src/pmdas/process/README
@@ -0,0 +1,74 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+#
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+
+Process PMDA
+============
+
+This PMDA monitors root processes to see if they are still running.
+The processes that are monitored are stored in a config file,
+and the PMDA returns the number of root processes with that name
+running. In this way, you can check to see if the number of processes
+is in a range, is 1, is 0, or some other combination.
+
+This source code was contributed by Alan Bailey (abailey@ncsa.uiuc.edu)
+to the PCP open source project.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT process
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/process
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use
+ ($PCP_PMCDCONF_PATH). If there is, edit ./domain.h
+ to choose another domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+ + Alternatively, to install just the Performance Metrics Name Space
+ for the process metrics on the local system, but not the process
+ PMDA (presumably because the local system is running PCP 1.x and
+ you wish to connect to a remote system where PCP 2.0 and the process
+ PMDA is running), make sure the Performance Metrics Domain defined
+ in ./domain.h matches the domain chosen for the process PMDA on the
+ remote system (check the second field in the corresponding line of the
+ $PCP_PMCDCONF_PATH file on the remote system), then
+
+ # ./Install -N
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/process
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/process.log) should be checked for any warnings or
+ errors.
diff --git a/src/pmdas/process/Remove b/src/pmdas/process/Remove
new file mode 100755
index 0000000..b90a863
--- /dev/null
+++ b/src/pmdas/process/Remove
@@ -0,0 +1,41 @@
+#! /bin/sh
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the process PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=process
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/process/help b/src/pmdas/process/help
new file mode 100644
index 0000000..acf2a37
--- /dev/null
+++ b/src/pmdas/process/help
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# process PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ process.running Tool for tracking running root processes
+This tracks the number of running root processes for processes you define
diff --git a/src/pmdas/process/pmns b/src/pmdas/process/pmns
new file mode 100644
index 0000000..8df2327
--- /dev/null
+++ b/src/pmdas/process/pmns
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Metrics for process PMDA
+ */
+
+process {
+ running PROCESS:0:0
+}
diff --git a/src/pmdas/process/process.c b/src/pmdas/process/process.c
new file mode 100644
index 0000000..00f9d80
--- /dev/null
+++ b/src/pmdas/process/process.c
@@ -0,0 +1,413 @@
+/*
+ * The process PMDA
+ *
+ * This code monitors root processes to see if they are still
+ * running. The processes that are monitored are stored in a config file,
+ * and the PMDA returns the number of root processes with that name
+ * running. In this way, you can check to see if the number of processes
+ * is in a range, is 1, is 0, or some other combination.
+ *
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ *
+ * Copyright (c) 2001,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+/*
+ * Process PMDA
+ *
+ * Metrics
+ * process.running
+ * The number of root processes running for each name
+ * The process names are stored in
+ * PCP_VAR_DIR/pmdas/process/process.conf
+ */
+
+static pmdaInstid *processes;
+
+static pmdaIndom indomtab[] = {
+#define PROC_INDOM 0
+ { PROC_INDOM, 0, NULL }
+};
+
+static pmdaMetric metrictab[] = {
+/* process.running */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_DOUBLE, PROC_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+/* Variables needed for this file */
+static int *num_procs;
+static struct stat file_change;
+static int npidlist;
+static int maxpidlist;
+static int *pidlist;
+static int isDSO = 1;
+static char mypath[MAXPATHLEN];
+
+/* Routines in this file */
+static void process_clear_config_info(void);
+static void process_grab_config_info(void);
+static void process_config_file_check(void);
+static int process_refresh_pidlist(void);
+
+static void
+process_config_file_check(void)
+{
+ struct stat statbuf;
+ static int last_error;
+ int sep = __pmPathSeparator();
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "process" "%c" "process.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if (stat(mypath, &statbuf) == -1) {
+ if (oserror() != last_error) {
+ last_error = oserror();
+ __pmNotifyErr(LOG_WARNING, "stat failed on %s: %s\n",
+ mypath, pmErrStr(-oserror()));
+ }
+ } else {
+ last_error = 0;
+#if defined(HAVE_ST_MTIME_WITH_E)
+ if (memcmp(&statbuf.st_mtime, &file_change.st_mtime, sizeof(statbuf.st_mtime)) != 0)
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if (memcmp(&statbuf.st_mtimespec, &file_change.st_mtimespec, sizeof(statbuf.st_mtimespec)) != 0)
+#else
+ if (memcmp(&statbuf.st_mtim, &file_change.st_mtim, sizeof(statbuf.st_mtim)) != 0)
+#endif
+ {
+ process_clear_config_info();
+ process_grab_config_info();
+ file_change = statbuf;
+ }
+ }
+}
+
+/*
+ * This function is called when the config file needs to be read in
+ * again, so we can define new instances and such. This frees the
+ * previous memory, if any (on startup, there won't be any), and sets
+ * the two variables to NULL for fun.
+ */
+static void
+process_clear_config_info(void)
+{
+ int i;
+
+ /* Free the memory holding the process name */
+ for (i = 0; i < indomtab[PROC_INDOM].it_numinst; i++)
+ free(processes[i].i_name);
+
+ /* Free the processes structure */
+ if (processes)
+ free(processes);
+
+ /* Free the process counter structure */
+ if (num_procs)
+ free(num_procs);
+ num_procs = NULL;
+
+ indomtab[PROC_INDOM].it_set = processes = NULL;
+ indomtab[PROC_INDOM].it_numinst = 0;
+}
+
+/*
+ * This routine opens the config file and stores the information in the
+ * processes structure. The processes structure must be reallocated as
+ * necessary, and also the num_procs structure needs to be reallocated
+ * as we define new processes. When all of that is done, we fill in the
+ * values in the indomtab structure, those being the number of instances
+ * and the pointer to the processes structure.
+ *
+ * A lot of this code was taken from the simple.c code, but in this
+ * case, I changed a lot of it to fit my purposes...
+ */
+static void
+process_grab_config_info(void)
+{
+ FILE *fp;
+ char process_name[1024];
+ char *q;
+ size_t size;
+ int process_number = 0;
+ int sep = __pmPathSeparator();
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "process" "%c" "process.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if ((fp = fopen(mypath, "r")) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fopen on %s failed: %s\n",
+ mypath, pmErrStr(-oserror()));
+ if (processes != NULL) {
+ free(processes);
+ processes = NULL;
+ process_number = 0;
+ }
+ goto done;
+ }
+
+ while (fgets(process_name, sizeof(process_name), fp) != NULL) {
+ if (process_name[0] == '#')
+ continue;
+ /* Remove the newline */
+ if ((q = strchr(process_name, '\n')) != NULL) {
+ *q = '\0';
+ } else {
+ /* This means the line was too long */
+ __pmNotifyErr(LOG_WARNING, "line %d in config file too long\n",
+ process_number + 1);
+ }
+ size = (process_number + 1) * sizeof(pmdaInstid);
+ if ((processes = realloc(processes, size)) == NULL)
+ __pmNoMem("process", size, PM_FATAL_ERR);
+ processes[process_number].i_name = malloc(strlen(process_name) + 1);
+ strcpy(processes[process_number].i_name, process_name);
+ processes[process_number].i_inst = process_number;
+ process_number++;
+ }
+ fclose(fp);
+
+done:
+ if (processes == NULL)
+ __pmNotifyErr(LOG_WARNING, "\"process\" instance domain is empty");
+
+ indomtab[PROC_INDOM].it_set = processes;
+ indomtab[PROC_INDOM].it_numinst = process_number;
+
+ num_procs = realloc(num_procs, (process_number)*sizeof(int));
+}
+
+/*
+ * This code refreshes the count of all the processes that we want to
+ * monitor. It does this by calling process_refresh_pidlist, which just
+ * grabs a list of all the running processes (as PIDs). We then loop
+ * through each of these processes, and check to see if it is a root
+ * process. If so, we then open up the /proc/<PID>/status file,
+ * and see what the process name is. If it is one of our special
+ * processes that we want to track, then we up the count for that
+ * process in num_procs. Then we're done.
+ */
+static void
+process_refresh_pid_checks(void)
+{
+ int proc;
+ char proc_path[30];
+ char cmd_line[200];
+ FILE *fd;
+ int proc_name;
+ int item;
+ struct stat statbuf;
+
+ process_refresh_pidlist();
+
+ /* Clear the variables */
+ for (item = 0; item < indomtab[PROC_INDOM].it_numinst; item++)
+ num_procs[item] = 0;
+
+ for (proc = 0; proc < npidlist; proc++) {
+ sprintf(proc_path, "/proc/%d/status", pidlist[proc]);
+ if (stat(proc_path, &statbuf) == -1) {
+ /* We can't stat it, let's assume the process isn't running anymore */
+ continue;
+ } else {
+ /* This checks to make sure the process is a root process */
+ if (statbuf.st_uid != 0) {
+ continue;
+ }
+ }
+
+ if ((fd = fopen(proc_path, "r")) != NULL) {
+ /*
+ * matching process by name is platform specific ... this code
+ * works for Linux when /proc/<pid>/status is ascii and the
+ * first line is of the form:
+ * Name: <cmdname>
+ */
+ if (fgets(cmd_line, sizeof(cmd_line), fd) == NULL) {
+ /*
+ * not expected, but not a disaster ... we'll just not try
+ * to match this one
+ */
+ fclose(fd);
+ continue;
+ }
+ fclose(fd);
+
+ for (proc_name = 0; proc_name < indomtab[PROC_INDOM].it_numinst; proc_name++) {
+ if (strstr(cmd_line, (processes[proc_name]).i_name)) {
+ (num_procs[proc_name])++;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Read in the list of all processes running.
+ */
+int
+process_refresh_pidlist()
+{
+ DIR *dirp = opendir("/proc");
+ struct dirent *dp;
+
+ if (dirp == NULL)
+ return -oserror();
+
+ npidlist = 0;
+ while ((dp = readdir(dirp)) != (struct dirent *)NULL) {
+ if (isdigit((int)dp->d_name[0])) {
+ if (npidlist >= maxpidlist) {
+ maxpidlist += 16;
+ pidlist = (int *)realloc(pidlist, maxpidlist * sizeof(int));
+ }
+ pidlist[npidlist++] = atoi(dp->d_name);
+ }
+ }
+ closedir(dirp);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "process_refresh_pidlist: found %d PIDs:", npidlist);
+ if (npidlist > 0) {
+ fprintf(stderr, " %d", pidlist[0]);
+ if (npidlist > 1) {
+ fprintf(stderr, " %d", pidlist[1]);
+ if (npidlist == 3)
+ fprintf(stderr, " %d", pidlist[2]);
+ else if (npidlist == 4)
+ fprintf(stderr, " %d %d", pidlist[2], pidlist[3]);
+ else if (npidlist > 4)
+ fprintf(stderr, " ... %d %d", pidlist[npidlist-2],
+ pidlist[npidlist-1]);
+ }
+ }
+ fprintf(stderr, "\n");
+ }
+#endif
+
+ return npidlist;
+}
+
+static int
+process_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ /*
+ * This is the wrapper over the pmdaFetch routine, to handle the problem
+ * of varying instance domains. All this does is check to see if the
+ * config file has been updated, and update the instance domain if so,
+ * and refresh the pid count for the processes.
+ */
+ process_config_file_check();
+ process_refresh_pid_checks();
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+process_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+ if (idp->item != 0)
+ return PM_ERR_PMID;
+
+ /* We have the values stored, simply grab it and return it. */
+ atom->d = num_procs[inst];
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+process_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "process" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "process DSO", mypath);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.fetch = process_fetch;
+
+ /* Let's grab the info right away just to make sure it's there. */
+ process_grab_config_info();
+
+ pmdaSetFetchCallBack(dp, process_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "process" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, PROCESS,
+ "process.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+
+ pmdaOpenLog(&desc);
+ process_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+ exit(0);
+}
diff --git a/src/pmdas/process/process.conf b/src/pmdas/process/process.conf
new file mode 100644
index 0000000..a02879d
--- /dev/null
+++ b/src/pmdas/process/process.conf
@@ -0,0 +1,11 @@
+# Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+# for the portions of the code supporting the initial agent functionality.
+# All rights reserved.
+# Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+sshd
+crond
+inetd
+nfsd
+afsd
+test
diff --git a/src/pmdas/process/root b/src/pmdas/process/root
new file mode 100644
index 0000000..f2fba58
--- /dev/null
+++ b/src/pmdas/process/root
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2001 Alan Bailey (bailey@mcs.anl.gov or abailey@ncsa.uiuc.edu)
+ * for the portions of the code supporting the initial agent functionality.
+ * All rights reserved.
+ * Copyright (c) 2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+#include <stdpmid>
+
+root { process }
+
+#include "pmns"
+
diff --git a/src/pmdas/roomtemp/GNUmakefile b/src/pmdas/roomtemp/GNUmakefile
new file mode 100644
index 0000000..e749011
--- /dev/null
+++ b/src/pmdas/roomtemp/GNUmakefile
@@ -0,0 +1,66 @@
+#
+# Copyright (c) 2000-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+HFILES = dsread.h
+CFILES = roomtemp.c dsread.c
+HFILES = dsread.h
+CMDTARGET = pmdaroomtemp
+LLDFLAGS = -L.
+LLDLIBS = -lmlan $(PCP_PMDALIB)
+LCFLAGS = -I.
+DFILES = README help
+LSRCFILES = Install Remove pmns $(DFILES) root
+
+SUBDIRS = mlan
+
+IAM = roomtemp
+DOMAIN = ROOMTEMP
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log pmda$(IAM) pmda_$(IAM).so libmlan.a
+
+default: build-me
+
+include $(BUILDRULES)
+
+.NOTPARALLEL:
+
+ifeq "$(findstring $(TARGET_OS),solaris linux)" ""
+build-me:
+ $(SUBDIRS_MAKERULE)
+
+install:
+else
+build-me: domain.h libmlan.a $(CMDTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root pmns domain.h $(PMDADIR)
+endif
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+libmlan.a : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ rm -f libmlan.a
+ ln -sf mlan/libmlan.a .
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmdas/roomtemp/Install b/src/pmdas/roomtemp/Install
new file mode 100644
index 0000000..02c6759
--- /dev/null
+++ b/src/pmdas/roomtemp/Install
@@ -0,0 +1,55 @@
+#! /bin/sh
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the roomtemp PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=roomtemp
+pmda_interface=2
+forced_restart=true
+pmdaSetup
+
+if $do_pmda
+then
+ if [ -c /dev/ttyf1 ]
+ then
+ # reasonable first guess for IRIX
+ #
+ tty=/dev/ttyf1
+ elif [ -c /dev/ttyS0 ]
+ then
+ # reasonable first guess for COM port on a Linux laptop
+ #
+ tty=/dev/ttyS0
+ fi
+
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Serial line for Dallas device? ""$PCP_ECHO_C"
+ [ ! -z "$tty" ] && $PCP_ECHO_PROG $PCP_ECHO_N "[$tty] ""$PCP_ECHO_C"
+ read ans
+ [ ! -z "$ans" ] && tty="$ans"
+ [ ! -z "$tty" ] && break
+ echo
+ echo "Ahem, you must answer the question ..."
+ done
+ args="$tty"
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/roomtemp/README b/src/pmdas/roomtemp/README
new file mode 100644
index 0000000..d347308
--- /dev/null
+++ b/src/pmdas/roomtemp/README
@@ -0,0 +1,74 @@
+Roomtemp PMDA
+=============
+
+This PMDA exports the temperature from one or more sensors built using
+the DS2480 and DS1280 chipsets and MicroLAN (aka 1-Wire) technology
+from Dallas Semiconductor Corporation. See http://www.dalsemi.com/
+
+You'll need the following hardware:
+
+DS2480 Serial 1-Wire Line Driver
+ http://www.dalsemi.com/datasheets/pdfs/2480.pdf
+
+ Not needed if you obtain one of the integrated packages like the
+ DS9097U.
+
+DS9097U Universal 1-Wire COM Port Adapter
+ http://www.dalsemi.com/datasheets/pdfs/9097u.pdf
+
+ This includes the DS2480 and I have the DS9097U-S09 variant that
+ is a standard DB-9 female package.
+
+DS1820 1-Wire Digital Thermometer
+ http://www.dalsemi.com/datasheets/pdfs/1820.pdf
+
+After you've got at least one DS9097U and one DS1820 you'll need a
+spare serial port on the host running PMCD, a soldering iron and some
+cable (telco or similar) with an RJ-11 male plug attached at one end.
+
+Use the ./probe application to exercise all of the hardware before
+you try and install the PMDA.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT roomtemp
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/roomtemp
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/roomtemp
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/roomtemp.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/roomtemp/Remove b/src/pmdas/roomtemp/Remove
new file mode 100644
index 0000000..8dd557a
--- /dev/null
+++ b/src/pmdas/roomtemp/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the roomtemp PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=roomtemp
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/roomtemp/dsread.c b/src/pmdas/roomtemp/dsread.c
new file mode 100644
index 0000000..5ac8474
--- /dev/null
+++ b/src/pmdas/roomtemp/dsread.c
@@ -0,0 +1,117 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "mlan/mlan.h"
+
+//----------------------------------------------------------------------
+// Read the temperature of a DS1820
+//
+// 'SerialNum' - Serial Number of DS1820 to read temperature from
+// 'Temp ' - pointer to variable where that temperature will be
+// returned
+//
+// Returns: TRUE(1) temperature has been read and verified
+// FALSE(0) could not read the temperature, perhaps device is not
+// in contact
+//
+int ReadTemperature(uchar SerialNum[8], float *Temp)
+{
+ int rt=FALSE;
+ uchar send_block[30];
+ int send_cnt=0, tsht, i;
+ float tmp,cr,cpc;
+ DOWCRC = 0;
+
+ // set the device serial number to the counter device
+ MLanSerialNum(SerialNum,FALSE);
+
+ // access the device
+ if (MLanAccess())
+ {
+ // send the convert temperature command
+ MLanTouchByte(0x44);
+
+ // set the MicroLAN to strong pull-up
+ if (MLanLevel(MODE_STRONG5) != MODE_STRONG5)
+ return FALSE;
+
+ // sleep for 1 second
+ msDelay(1000);
+
+ // turn off the MicroLAN strong pull-up
+ if (MLanLevel(MODE_NORMAL) != MODE_NORMAL)
+ return FALSE;
+
+ // access the device
+ if (MLanAccess())
+ {
+ // create a block to send that reads the temperature
+ // read scratchpad command
+ send_block[send_cnt++] = 0xBE;
+ // now add the read bytes for data bytes and crc8
+ for (i = 0; i < 9; i++)
+ send_block[send_cnt++] = 0xFF;
+
+ // now send the block
+ if (MLanBlock(FALSE,send_block,send_cnt))
+ {
+ // perform the CRC8 on the last 8 bytes of packet
+ for (i = send_cnt - 9; i < send_cnt; i++)
+ dowcrc(send_block[i]);
+
+ // verify CRC8 is correct
+ if (DOWCRC == 0x00)
+ {
+ // calculate the high-res temperature
+ tsht = send_block[1];
+ if (send_block[2] & 0x01)
+ tsht |= -256;
+ tmp = (float)(tsht/2);
+ cr = send_block[7];
+ cpc = send_block[8];
+ if (cpc == 0)
+ return FALSE;
+ else
+ tmp = tmp - (float)0.25 + (cpc - cr)/cpc;
+
+ *Temp = tmp;
+ // success
+ rt = TRUE;
+ }
+ }
+ }
+ }
+
+ // return the result flag rt
+ return rt;
+}
+
+/*
+ * find next temperature sensor
+ */
+unsigned char *
+nextsensor(void)
+{
+ static unsigned char sn[8];
+ static int first = 1;
+
+ if (first) {
+ // set the search to first find for temperature family code
+ MLanFamilySearchSetup(TEMP_FAMILY);
+ first = 0;
+ }
+
+ for ( ; ; ) {
+ // perform the search
+ if (!MLanNext(TRUE, FALSE)) {
+ return NULL;
+ }
+ // get serial number and verify the family code
+ MLanSerialNum(sn, TRUE);
+ if (sn[0] == TEMP_FAMILY)
+ return sn;
+ }
+}
diff --git a/src/pmdas/roomtemp/dsread.h b/src/pmdas/roomtemp/dsread.h
new file mode 100644
index 0000000..8251baa
--- /dev/null
+++ b/src/pmdas/roomtemp/dsread.h
@@ -0,0 +1,4 @@
+#include "mlan/mlan.h"
+
+extern int ReadTemperature(uchar *, float *);
+unsigned char *nextsensor(void);
diff --git a/src/pmdas/roomtemp/help b/src/pmdas/roomtemp/help
new file mode 100644
index 0000000..75a3e4d
--- /dev/null
+++ b/src/pmdas/roomtemp/help
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# roomtemp PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ ROOMTEMP.0 Instance domain "device" for roomtemp PMDA
+One instance for each temperature sensor device. The external instance
+identifiers are the serial numbers (in hexadecimal) of the DS1280 chips
+that were discovered when the MicroLAN was probed.
+
+@ roomtemp.celsius Temperature in degrees celsius.
+@ roomtemp.fahrenheit Temperature in degrees fahrenheit.
+
diff --git a/src/pmdas/roomtemp/mlan/GNUmakefile b/src/pmdas/roomtemp/mlan/GNUmakefile
new file mode 100644
index 0000000..f6e33ae
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/GNUmakefile
@@ -0,0 +1,45 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES=mlannetu.c mlansesu.c mlanllu.c linuxlnk.c ds2480ut.c mlantrnu.c
+HFILES=mlan.h ds2480.h
+STATICLIBTARGET = libmlan.a
+LDIRT = libmlan.a
+LCFLAGS = -g
+
+default : $(STATICLIBTARGET)
+
+ifeq "$(findstring $(TARGET_OS),solaris linux)" ""
+default:
+else
+default : $(STATICLIBTARGET)
+endif
+
+.NOTPARALLEL:
+
+install :
+
+default_pcp : default
+
+install_pcp : install
+
+include $(BUILDRULES)
diff --git a/src/pmdas/roomtemp/mlan/ds2480.h b/src/pmdas/roomtemp/mlan/ds2480.h
new file mode 100644
index 0000000..139e2c4
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/ds2480.h
@@ -0,0 +1,183 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// DS2480.H - This file contains the DS2480 constants
+//
+// Version: 1.03
+// History: 1.02 -> 1.03 Make sure uchar is not defined twice.
+//
+//
+
+// typedefs
+#ifndef UCHAR
+ #define UCHAR
+ typedef unsigned char uchar;
+#endif
+
+// Mode Commands
+#define MODE_DATA 0xE1
+#define MODE_COMMAND 0xE3
+#define MODE_STOP_PULSE 0xF1
+
+// Return byte value
+#define RB_CHIPID_MASK 0x1C
+#define RB_RESET_MASK 0x03
+#define RB_1WIRESHORT 0x00
+#define RB_PRESENCE 0x01
+#define RB_ALARMPRESENCE 0x02
+#define RB_NOPRESENCE 0x03
+
+#define RB_BIT_MASK 0x03
+#define RB_BIT_ONE 0x03
+#define RB_BIT_ZERO 0x00
+
+// Masks for all bit ranges
+#define CMD_MASK 0x80
+#define FUNCTSEL_MASK 0x60
+#define BITPOL_MASK 0x10
+#define SPEEDSEL_MASK 0x0C
+#define MODSEL_MASK 0x02
+#define PARMSEL_MASK 0x70
+#define PARMSET_MASK 0x0E
+
+// Command or config bit
+#define CMD_COMM 0x81
+#define CMD_CONFIG 0x01
+
+// Function select bits
+#define FUNCTSEL_BIT 0x00
+#define FUNCTSEL_SEARCHON 0x30
+#define FUNCTSEL_SEARCHOFF 0x20
+#define FUNCTSEL_RESET 0x40
+#define FUNCTSEL_CHMOD 0x60
+
+// Bit polarity/Pulse voltage bits
+#define BITPOL_ONE 0x10
+#define BITPOL_ZERO 0x00
+#define BITPOL_5V 0x00
+#define BITPOL_12V 0x10
+
+// One Wire speed bits
+#define SPEEDSEL_STD 0x00
+#define SPEEDSEL_FLEX 0x04
+#define SPEEDSEL_OD 0x08
+#define SPEEDSEL_PULSE 0x0C
+
+// Data/Command mode select bits
+#define MODSEL_DATA 0x00
+#define MODSEL_COMMAND 0x02
+
+// 5V Follow Pulse select bits (If 5V pulse
+// will be following the next byte or bit.)
+#define PRIME5V_TRUE 0x02
+#define PRIME5V_FALSE 0x00
+
+// Parameter select bits
+#define PARMSEL_PARMREAD 0x00
+#define PARMSEL_SLEW 0x10
+#define PARMSEL_12VPULSE 0x20
+#define PARMSEL_5VPULSE 0x30
+#define PARMSEL_WRITE1LOW 0x40
+#define PARMSEL_SAMPLEOFFSET 0x50
+#define PARMSEL_ACTIVEPULLUPTIME 0x60
+#define PARMSEL_BAUDRATE 0x70
+
+// Pull down slew rate.
+#define PARMSET_Slew15Vus 0x00
+#define PARMSET_Slew2p2Vus 0x02
+#define PARMSET_Slew1p65Vus 0x04
+#define PARMSET_Slew1p37Vus 0x06
+#define PARMSET_Slew1p1Vus 0x08
+#define PARMSET_Slew0p83Vus 0x0A
+#define PARMSET_Slew0p7Vus 0x0C
+#define PARMSET_Slew0p55Vus 0x0E
+
+// 12V programming pulse time table
+#define PARMSET_32us 0x00
+#define PARMSET_64us 0x02
+#define PARMSET_128us 0x04
+#define PARMSET_256us 0x06
+#define PARMSET_512us 0x08
+#define PARMSET_1024us 0x0A
+#define PARMSET_2048us 0x0C
+#define PARMSET_infinite 0x0E
+
+// 5V strong pull up pulse time table
+#define PARMSET_16p4ms 0x00
+#define PARMSET_65p5ms 0x02
+#define PARMSET_131ms 0x04
+#define PARMSET_262ms 0x06
+#define PARMSET_524ms 0x08
+#define PARMSET_1p05s 0x0A
+#define PARMSET_2p10s 0x0C
+#define PARMSET_infinite 0x0E
+
+// Write 1 low time
+#define PARMSET_Write8us 0x00
+#define PARMSET_Write9us 0x02
+#define PARMSET_Write10us 0x04
+#define PARMSET_Write11us 0x06
+#define PARMSET_Write12us 0x08
+#define PARMSET_Write13us 0x0A
+#define PARMSET_Write14us 0x0C
+#define PARMSET_Write15us 0x0E
+
+// Data sample offset and Write 0 recovery time
+#define PARMSET_SampOff3us 0x00
+#define PARMSET_SampOff4us 0x02
+#define PARMSET_SampOff5us 0x04
+#define PARMSET_SampOff6us 0x06
+#define PARMSET_SampOff7us 0x08
+#define PARMSET_SampOff8us 0x0A
+#define PARMSET_SampOff9us 0x0C
+#define PARMSET_SampOff10us 0x0E
+
+// Active pull up on time
+#define PARMSET_PullUp0p0us 0x00
+#define PARMSET_PullUp0p5us 0x02
+#define PARMSET_PullUp1p0us 0x04
+#define PARMSET_PullUp1p5us 0x06
+#define PARMSET_PullUp2p0us 0x08
+#define PARMSET_PullUp2p5us 0x0A
+#define PARMSET_PullUp3p0us 0x0C
+#define PARMSET_PullUp3p5us 0x0E
+
+// Baud rate bits
+#define PARMSET_9600 0x00
+#define PARMSET_19200 0x02
+#define PARMSET_57600 0x04
+#define PARMSET_115200 0x06
+
+// DS2480 program voltage available
+#define DS2480PROG_MASK 0x20
+
+// mode bit flags
+#define MODE_NORMAL 0x00
+#define MODE_OVERDRIVE 0x01
+#define MODE_STRONG5 0x02
+#define MODE_PROGRAM 0x04
+#define MODE_BREAK 0x08
+
diff --git a/src/pmdas/roomtemp/mlan/ds2480ut.c b/src/pmdas/roomtemp/mlan/ds2480ut.c
new file mode 100644
index 0000000..d353743
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/ds2480ut.c
@@ -0,0 +1,206 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// ds2480ut.c - DS2480 utility functions.
+//
+// Version: 1.03
+//
+// History: 1.00 -> 1.01 Default PDSRC changed from 0.83 to 1.37V/us
+// in DS2480Detect. Changed to use msDelay instead
+// of Delay.
+//
+// 1.01 -> 1.02 Changed global declarations from 'uchar' to 'int'.
+// Changed DSO/WORT from 7 to 10us in DS2480Detect.
+//
+// 1.02 -> 1.03 Removed caps in #includes for Linux capatibility
+
+#include "ds2480.h"
+#include "mlan.h"
+
+// external COM functions required
+extern void FlushCOM(void);
+extern int WriteCOM(int, uchar *);
+extern int ReadCOM(int, uchar *);
+extern void BreakCOM(void);
+//extern void SetBaudCOM(uchar);
+extern void msDelay(int);
+
+// exportable functions
+int DS2480Detect(void);
+int DS2480ChangeBaud(uchar);
+
+// global DS2480 state
+int UMode = MODSEL_COMMAND; // current DS2480 command or data mode stateuchar
+int UBaud = PARMSET_9600; // current DS2480 baud rate
+int USpeed = SPEEDSEL_FLEX; // current DS2480 MicroLAN communication speed
+int ULevel = MODE_NORMAL; // current DS2480 MicroLAN level
+
+//---------------------------------------------------------------------------
+// Attempt to resyc and detect a DS2480
+//
+// Returns: TRUE - DS2480 detected successfully
+// FALSE - Could not detect DS2480
+//
+int DS2480Detect(void)
+{
+ uchar sendpacket[10],readbuffer[10];
+ short sendlen=0;
+
+ // reset modes
+ UMode = MODSEL_COMMAND;
+ UBaud = PARMSET_9600;
+ USpeed = SPEEDSEL_FLEX;
+
+ // set the baud rate to 9600
+ SetBaudCOM((uchar)UBaud);
+
+ // send a break to reset the DS2480
+ BreakCOM();
+
+ // delay to let line settle
+ msDelay(2);
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the timing byte
+ sendpacket[0] = 0xC1;
+ if (WriteCOM(1,sendpacket) != 1)
+ return FALSE;
+
+ // set the FLEX configuration parameters
+ // default PDSRC = 1.37Vus
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_SLEW | PARMSET_Slew1p37Vus;
+ // default W1LT = 12us
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_WRITE1LOW | PARMSET_Write12us;
+ // default DSO/WORT = 10us
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_SAMPLEOFFSET | PARMSET_SampOff10us;
+
+ // construct the command to read the baud rate (to test command block)
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_PARMREAD | (PARMSEL_BAUDRATE >> 3);
+
+ // also do 1 bit operation (to test 1-Wire block)
+ sendpacket[sendlen++] = CMD_COMM | FUNCTSEL_BIT | UBaud | BITPOL_ONE;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the response
+ if (ReadCOM(5,readbuffer) == 5)
+ {
+ // look at the baud rate and bit operation
+ // to see if the response makes sense
+ if (((readbuffer[3] & 0xF1) == 0x00) &&
+ ((readbuffer[3] & 0x0E) == UBaud) &&
+ ((readbuffer[4] & 0xF0) == 0x90) &&
+ ((readbuffer[4] & 0x0C) == UBaud))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+//---------------------------------------------------------------------------
+// Change the DS2480 from the current baud rate to the new baud rate.
+//
+// 'newbaud' - the new baud rate to change to, defined as:
+// PARMSET_9600 0x00
+// PARMSET_19200 0x02
+// PARMSET_57600 0x04
+// PARMSET_115200 0x06
+//
+// Returns: current DS2480 baud rate.
+//
+int DS2480ChangeBaud(uchar newbaud)
+{
+ int rt=FALSE;
+ uchar readbuffer[5],sendpacket[5],sendpacket2[5];
+ int sendlen=0,sendlen2=0;
+
+ // see if diffenent then current baud rate
+ if (UBaud == newbaud)
+ return TRUE;
+ else
+ {
+ // build the command packet
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+ // build the command
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_BAUDRATE | newbaud;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (!WriteCOM(sendlen,sendpacket))
+ rt = FALSE;
+ else
+ {
+ // make sure buffer is flushed
+ msDelay(5);
+
+ // change our baud rate
+ SetBaudCOM(newbaud);
+ UBaud = newbaud;
+
+ // wait for things to settle
+ msDelay(5);
+
+ // build a command packet to read back baud rate
+ sendpacket2[sendlen2++] = CMD_CONFIG | PARMSEL_PARMREAD | (PARMSEL_BAUDRATE >> 3);
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen2,sendpacket2))
+ {
+ // read back the 1 byte response
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // verify correct baud
+ if (((readbuffer[0] & 0x0E) == (sendpacket[sendlen-1] & 0x0E)))
+ rt = TRUE;
+ }
+ }
+ }
+ }
+
+ // if lost communication with DS2480 then reset
+ if (rt != TRUE)
+ DS2480Detect();
+
+ return UBaud;
+}
diff --git a/src/pmdas/roomtemp/mlan/linuxlnk.c b/src/pmdas/roomtemp/mlan/linuxlnk.c
new file mode 100644
index 0000000..aa20695
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/linuxlnk.c
@@ -0,0 +1,443 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// TODO.C - COM functions required by MLANLL.C, MLANTRNU, MLANNETU.C and
+// MLanFile.C for MLANU to communicate with the DS2480 based
+// Universal Serial Adapter 'U'. Fill in the platform specific code.
+//
+// Version: 1.02
+//
+// History: 1.00 -> 1.01 Added function msDelay.
+//
+// 1.01 -> 1.02 Changed to generic OpenCOM/CloseCOM for easier
+// use with other platforms.
+//
+
+//--------------------------------------------------------------------------
+// Copyright (C) 1998 Andrea Chambers and University of Newcastle upon Tyne,
+// All Rights Reserved.
+//--------------------------------------------------------------------------
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE UNIVERSITY OF NEWCASTLE UPON TYNE OR ANDREA CHAMBERS
+// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
+// THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//---------------------------------------------------------------------------
+//
+// LinuxLNK.C - COM functions required by MLANLLU.C, MLANTRNU.C, MLANNETU.C
+// and MLanFile.C for MLANU to communicate with the DS2480 based
+// Universal Serial Adapter 'U'. Platform specific code.
+//
+// Version: 1.03
+// History: 1.00 -> 1.03 modifications by David Smiczek
+// Changed to use generic OpenCOM/CloseCOM
+// Pass port name to OpenCOM instead of hard coded
+// Changed msDelay to handle long delays
+// Reformatted to look like 'TODO.C'
+// Added #include "ds2480.h" to use constants.
+// Added function SetBaudCOM()
+// Added function msGettick()
+// Removed delay from WriteCOM(), used tcdrain()
+// Added wait for byte available with timeout using
+// select() in ReadCOM()
+/*
+ cfmakeraw function from nut-0.45.0 package
+ common.c - common useful functions
+
+ Copyright (C) 2000 Russell Kroll <rkroll@exploits.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <termios.h>
+#include <sys/time.h>
+
+#include "mlan.h"
+#include "ds2480.h"
+#include "pmapi.h"
+
+// Exportable functions required for MLANLL.C, MLANTRNU.C, or MLANNETU.C
+void FlushCOM(void);
+int WriteCOM(int, unsigned char*);
+int ReadCOM(int, unsigned char*);
+void BreakCOM(void);
+void msDelay(int);
+long msGettick(void);
+
+// Exportable functions for opening/closing serial port
+int OpenCOM(char *);
+void CloseCOM(void);
+
+// LinuxLNK global
+int fd;
+
+#ifdef IS_SOLARIS
+int cfmakeraw(struct termios *termios_p)
+{
+ termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
+ |INLCR|IGNCR|ICRNL|IXON);
+ termios_p->c_oflag &= ~OPOST;
+ termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ termios_p->c_cflag &= ~(CSIZE|PARENB);
+ termios_p->c_cflag |= CS8;
+ return 0;
+}
+#endif
+
+//--------------------------------------------------------------------------
+// Write an array of bytes to the COM port, verify that it was
+// sent out. Assume that baud rate has been set.
+//
+// Returns 1 for success and 0 for failure
+//
+int WriteCOM(int outlen, uchar *outbuf)
+{
+ long count = outlen;
+ int i;
+ int sts;
+
+ if (MLanDebug) {
+ fprintf(stderr, "WriteCOM: calling write: %d bytes:", outlen);
+ for (i = 0; i < outlen; i++) fprintf(stderr, " %02x", 0xff & outbuf[i]);
+ fputc('\n', stderr);
+ fflush(stderr);
+ }
+
+ i = write(fd, outbuf, outlen);
+
+ if (MLanDebug) {
+ fprintf(stderr, "WriteCOM: write returns %d\nWriteCOM: calling tcdrain\n", i);
+ fflush(stderr);
+ }
+
+ sts = tcdrain(fd);
+
+ if (MLanDebug) {
+ fprintf(stderr, "WriteCOM: tcdrain returns %d\n", sts);
+ fflush(stderr);
+ }
+
+ return (i == count);
+}
+
+
+//--------------------------------------------------------------------------
+// Read an array of bytes to the COM port, verify that it was
+// sent out. Assume that baud rate has been set.
+//
+// Returns number of characters read
+//
+int ReadCOM(int inlen, uchar *inbuf)
+{
+ fd_set filedescr;
+ struct timeval tval;
+ int cnt;
+ int sts;
+
+ if (MLanDebug) {
+ fprintf(stderr, "ReadCOM: calling read: want %d bytes:", inlen);
+ fflush(stderr);
+ }
+
+ // loop to wait until each byte is available and read it
+ for (cnt = 0; cnt < inlen; cnt++)
+ {
+ // set a descriptor to wait for a character available
+ FD_ZERO(&filedescr);
+ FD_SET(fd,&filedescr);
+ // set timeout to 10ms
+ tval.tv_sec = 0;
+ tval.tv_usec = 10000;
+
+ // if byte available read or return bytes read
+ if (0 != select(fd+1,&filedescr,NULL,NULL,&tval)) {
+ if ((sts = read(fd,&inbuf[cnt],1)) != 1) {
+ if (MLanDebug) {
+ fprintf(stderr, ": read returns %d, got %d bytes\n", sts, cnt);
+ fflush(stderr);
+ }
+ return cnt;
+ }
+ if (MLanDebug) {
+ fprintf(stderr, " %02x", 0xff & inbuf[cnt]);
+ fflush(stderr);
+ }
+ }
+ else {
+ if (MLanDebug) {
+ fprintf(stderr, ": select timeout, got %d bytes\n", cnt);
+ fflush(stderr);
+ }
+ return cnt;
+ }
+
+ }
+
+ // success, so return desired length
+ if (MLanDebug) {
+ fprintf(stderr, ": got 'em all\n");
+ fflush(stderr);
+ }
+ return inlen;
+}
+
+
+//---------------------------------------------------------------------------
+// Description:
+// flush the rx and tx buffers
+//
+void FlushCOM(void)
+{
+ tcflush (fd, TCIOFLUSH);
+}
+
+
+//--------------------------------------------------------------------------
+// Description:
+// Delay for at least 'len' ms
+//
+void msDelay(int len)
+{
+ struct timespec s; // Set aside memory space on the stack
+
+ s.tv_sec = len / 1000;
+ s.tv_nsec = (len - (s.tv_sec * 1000)) * 1000000;
+ nanosleep(&s, NULL);
+}
+
+
+//--------------------------------------------------------------------------
+// Description:
+// Send a break on the com port for at least 2 ms
+//
+void BreakCOM(void)
+{
+ int duration = 0; // see man termios break may be
+ tcsendbreak(fd, duration); // too long
+}
+
+
+//---------------------------------------------------------------------------
+// Attempt to open a com port.
+// Set the starting baud rate to 9600.
+//
+// 'port_zstr' - zero terminate port name. Format is platform
+// dependent.
+//
+// Returns: TRUE - success, COM port opened
+//
+int OpenCOM(char *port_zstr)
+{
+ struct termios t; // see man termios - declared as above
+ int rc;
+
+ fd = open(port_zstr, O_RDWR|O_NONBLOCK);
+ if (fd<0) return fd;
+ rc = tcgetattr (fd, &t);
+ if (rc < 0)
+ {
+ int tmp;
+ tmp = oserror();
+ close(fd);
+ setoserror(tmp);
+ return rc;
+ }
+ if (MLanDebug) {
+ fprintf(stderr, "OpenCOM: initial tty settings\niflag: %07o oflag: %07o lflag: %07o cflag: %07o\n", (unsigned int)t.c_iflag, (unsigned int)t.c_oflag, (unsigned int)t.c_lflag, (unsigned int)t.c_cflag);
+ fflush(stderr);
+ }
+
+ cfsetospeed(&t, B9600);
+ cfsetispeed (&t, B9600);
+
+#ifdef IRIX
+// IRIX tty games ...
+//
+// per the Linux man page cfmakeraw sets the terminal attributes as follows:
+//
+ t.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+ t.c_iflag &= ~(ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+ t.c_oflag &= ~OPOST;
+ t.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+ t.c_cflag &= ~(CSIZE|PARENB);
+ t.c_cflag |= CS8;
+// to this we have to clear the additional IRIX goodies like:
+//
+ t.c_iflag &= ~(IXANY|IGNPAR);
+ t.c_oflag &= ~(TAB3);
+// and finally, CLOCAL is in c_cflag, not c_lflag
+//
+ t.c_cflag |= CLOCAL; // ignore modem signals
+#else
+// assumed to be Linux
+//
+ cfmakeraw(&t); // don't generate signals, translate or echo
+ t.c_lflag |= CLOCAL; // ignore modem signals
+#endif
+
+ if (MLanDebug) {
+ fprintf(stderr, "OpenCOM: new tty settings\niflag: %07o oflag: %07o lflag: %07o cflag: %07o\n", (unsigned int)t.c_iflag, (unsigned int)t.c_oflag, (unsigned int)t.c_lflag, (unsigned int)t.c_cflag);
+ fprintf(stderr, "OpenCOM: calling tcsetattr\n");
+ fflush(stderr);
+ }
+ rc = tcsetattr (fd, TCSAFLUSH, &t);
+ if (MLanDebug) {
+ fprintf(stderr, "OpenCOM: tcsetattr returns %d\n", rc);
+ fflush(stderr);
+ }
+ if (rc < 0)
+ {
+ int tmp;
+ tmp = oserror();
+ close(fd);
+ setoserror(tmp);
+ return rc;
+ }
+
+ return fd;
+}
+
+
+//---------------------------------------------------------------------------
+// Closes the connection to the port.
+//
+void CloseCOM(void)
+{
+ FlushCOM();
+ close(fd);
+}
+
+
+//--------------------------------------------------------------------------
+// Set the baud rate on the com port. The possible baud rates for
+// 'new_baud' are:
+//
+// PARMSET_9600 0x00
+// PARMSET_19200 0x02
+// PARMSET_57600 0x04
+// PARMSET_115200 0x06
+//
+void SetBaudCOM(int new_baud)
+{
+ struct termios t;
+ int rc;
+ speed_t baud = B0;
+
+ // read the attribute structure
+ rc = tcgetattr(fd, &t);
+ if (rc < 0)
+ {
+ close(fd);
+ return;
+ }
+
+ // convert parameter to linux baud rate
+ switch(new_baud)
+ {
+ case PARMSET_9600:
+ baud = B9600;
+ break;
+ case PARMSET_19200:
+ baud = B19200;
+ break;
+ case PARMSET_57600:
+#ifdef B57600
+ baud = B57600;
+ break;
+#else
+#define ERR_MSG_57600 "SetBaudCOM: no support for 57600 baud, sorry!"
+ write(2, ERR_MSG_57600, strlen(ERR_MSG_57600));
+ exit(1);
+#endif
+ case PARMSET_115200:
+#ifdef B115200
+ baud = B115200;
+ break;
+#else
+#define ERR_MSG_115200 "SetBaudCOM: no support for 115200 baud, sorry!"
+ write(2, ERR_MSG_115200, strlen(ERR_MSG_115200));
+ exit(1);
+#endif
+ }
+
+ // set baud in structure
+ cfsetospeed(&t, baud);
+ cfsetispeed(&t, baud);
+
+ // change baud on port
+ rc = tcsetattr(fd, TCSAFLUSH, &t);
+ if (rc < 0)
+ close(fd);
+}
+
+
+//--------------------------------------------------------------------------
+// Get the current millisecond tick count. Does not have to represent
+// an actual time, it just needs to be an incrementing timer.
+//
+long msGettick(void)
+{
+ struct timezone tmzone;
+ struct timeval tmval;
+ long ms;
+
+ gettimeofday(&tmval,&tmzone);
+ ms = (tmval.tv_sec & 0xFFFF) * 1000 + tmval.tv_usec / 1000;
+ return ms;
+}
+
diff --git a/src/pmdas/roomtemp/mlan/mlan.h b/src/pmdas/roomtemp/mlan/mlan.h
new file mode 100644
index 0000000..4ca742b
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/mlan.h
@@ -0,0 +1,94 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// MLan.H - Include file for MicroLAN library
+//
+// Version: 1.03
+// History: 1.02 -> 1.03 Make sure uchar is not defined twice.
+//
+
+// Typedefs
+#ifndef UCHAR
+ #define UCHAR
+ typedef unsigned char uchar;
+#endif
+#if 0
+// not needed most places - kenmcd
+typedef unsigned short ushort;
+typedef unsigned long ulong;
+#endif
+
+// general defines
+#define WRITE_FUNCTION 1
+#define READ_FUNCTION 0
+
+// error codes
+#define READ_ERROR -1
+#define INVALID_DIR -2
+#define NO_FILE -3
+#define WRITE_ERROR -4
+#define WRONG_TYPE -5
+#define FILE_TOO_BIG -6
+
+// Misc
+#define FALSE 0
+#define TRUE 1
+
+// mode bit flags
+#define MODE_NORMAL 0x00
+#define MODE_OVERDRIVE 0x01
+#define MODE_STRONG5 0x02
+#define MODE_PROGRAM 0x04
+#define MODE_BREAK 0x08
+
+// product families
+#define TEMP_FAMILY 0x10
+#define SWITCH_FAMILY 0x12
+#define COUNT_FAMILY 0x1D
+#define DIR_FAMILY 0x01
+
+// externs
+extern uchar DOWCRC;
+
+// debugging
+extern int MLanDebug;
+
+// function prototypes
+extern int Aquire1WireNet(char *, char *);
+extern void Release1WireNet(char *);
+extern int MLanAccess(void);
+extern int MLanBlock(int DoReset, uchar *TransferBuffer, int TransferLen);
+extern void MLanFamilySearchSetup(int SearchFamily);
+extern int MLanLevel(int NewLevel);
+extern int MLanNext(int DoReset, int OnlyAlarmingDevices);
+extern void MLanSerialNum(uchar *SerialNumBuf, int DoRead);
+extern int MLanTouchByte(int sendbyte);
+extern int MLanVerify(int OnlyAlarmingDevices);
+extern uchar dowcrc(uchar x);
+extern void msDelay(int len);
+extern int DS2480ChangeBaud(uchar newbaud);
+extern void SetBaudCOM(int new_baud);
+
diff --git a/src/pmdas/roomtemp/mlan/mlanllu.c b/src/pmdas/roomtemp/mlan/mlanllu.c
new file mode 100644
index 0000000..91b23f0
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/mlanllu.c
@@ -0,0 +1,499 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// MLanLLU.C - Link Layer MicroLAN functions using the DS2480 (U)
+// serial interface chip.
+//
+// Version: 1.03
+//
+// History: 1.00 -> 1.01 DS2480 version number now ignored in
+// MLanTouchReset.
+// 1.02 -> 1.03 Removed caps in #includes for Linux capatibility
+// Removed #include <windows.h>
+// Add #include "mlan.h" to define TRUE,FALSE
+
+#include "ds2480.h"
+#include "mlan.h"
+
+// external COM functions required
+extern void FlushCOM(void);
+extern int WriteCOM(int, uchar *);
+extern int ReadCOM(int, uchar *);
+//extern void SetBaudCOM(int);
+
+// external DS2480 utility function
+extern int DS2480Detect(void);
+//extern int DS2480ChangeBaud(int);
+
+// local functions
+int MLanTouchReset(void);
+int MLanTouchBit(int sendbit);
+int MLanTouchByte(int sendbyte);
+int MLanWriteByte(int sendbyte);
+int MLanReadByte(void);
+int MLanSpeed(int);
+int MLanLevel(int);
+int MLanProgramPulse(void);
+
+// external globals
+extern int UMode; // current DS2480 command or data mode state
+extern int UBaud; // current DS2480 baud rate
+extern int USpeed; // current DS2480 MicroLAN communication speed
+extern int ULevel; // current DS2480 MicroLAN level
+
+// local varable flag, true if program voltage available
+static int ProgramAvailable=FALSE;
+
+
+//--------------------------------------------------------------------------
+// Reset all of the devices on the MicroLAN and return the result.
+//
+// Returns: TRUE(1): presense pulse(s) detected, device(s) reset
+// FALSE(0): no presense pulses detected
+//
+// WARNING: This routine will not function correctly on some
+// Alarm reset types of the DS1994/DS1427/DS2404 with
+// Rev 1 and 2 of the DS2480.
+//
+int MLanTouchReset(void)
+{
+ uchar readbuffer[10],sendpacket[10];
+ int sendlen=0;
+
+ // make sure normal level
+ MLanLevel(MODE_NORMAL);
+
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // construct the command
+ sendpacket[sendlen++] = (uchar)(CMD_COMM | FUNCTSEL_RESET | USpeed);
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 1 byte response
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // make sure this byte looks like a reset byte
+ if (((readbuffer[0] & RB_RESET_MASK) == RB_PRESENCE) ||
+ ((readbuffer[0] & RB_RESET_MASK) == RB_ALARMPRESENCE))
+ {
+ // check if programming voltage available
+ ProgramAvailable = ((readbuffer[0] & 0x20) == 0x20);
+ return TRUE;
+ }
+ else
+ return FALSE;
+ }
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Send 1 bit of communication to the MicroLAN and return the
+// result 1 bit read from the MicroLAN. The parameter 'sendbit'
+// least significant bit is used and the least significant bit
+// of the result is the return bit.
+//
+// 'sendbit' - the least significant bit is the bit to send
+//
+// Returns: 0: 0 bit read from sendbit
+// 1: 1 bit read from sendbit
+//
+int MLanTouchBit(int sendbit)
+{
+ uchar readbuffer[10],sendpacket[10];
+ int sendlen=0;
+
+ // make sure normal level
+ MLanLevel(MODE_NORMAL);
+
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // construct the command
+ sendpacket[sendlen] = (sendbit != 0) ? BITPOL_ONE : BITPOL_ZERO;
+ sendpacket[sendlen++] |= CMD_COMM | FUNCTSEL_BIT | USpeed;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the response
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // interpret the response
+ if (((readbuffer[0] & 0xE0) == 0x80) &&
+ ((readbuffer[0] & RB_BIT_MASK) == RB_BIT_ONE))
+ return 1;
+ else
+ return 0;
+ }
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------
+// Send 8 bits of communication to the MicroLAN and verify that the
+// 8 bits read from the MicroLAN is the same (write operation).
+// The parameter 'sendbyte' least significant 8 bits are used.
+//
+// 'sendbyte' - 8 bits to send (least significant byte)
+//
+// Returns: TRUE: bytes written and echo was the same
+// FALSE: echo was not the same
+//
+int MLanWriteByte(int sendbyte)
+{
+ return (MLanTouchByte(sendbyte) == sendbyte) ? TRUE : FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Send 8 bits of read communication to the MicroLAN and and return the
+// result 8 bits read from the MicroLAN.
+//
+// Returns: 8 bytes read from MicroLAN
+//
+int MLanReadByte(void)
+{
+ return MLanTouchByte(0xFF);
+}
+
+
+//--------------------------------------------------------------------------
+// Send 8 bits of communication to the MicroLAN and return the
+// result 8 bits read from the MicroLAN. The parameter 'sendbyte'
+// least significant 8 bits are used and the least significant 8 bits
+// of the result is the return byte.
+//
+// 'sendbyte' - 8 bits to send (least significant byte)
+//
+// Returns: 8 bytes read from sendbyte
+//
+int MLanTouchByte(int sendbyte)
+{
+ uchar readbuffer[10],sendpacket[10];
+ int sendlen=0;
+
+ // make sure normal level
+ MLanLevel(MODE_NORMAL);
+
+ // check if correct mode
+ if (UMode != MODSEL_DATA)
+ {
+ UMode = MODSEL_DATA;
+ sendpacket[sendlen++] = MODE_DATA;
+ }
+
+ // add the byte to send
+ sendpacket[sendlen++] = (uchar)sendbyte;
+
+ // check for duplication of data that looks like COMMAND mode
+ if (sendbyte == MODE_COMMAND)
+ sendpacket[sendlen++] = (uchar)sendbyte;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 1 byte response
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // return the response
+ return (int)readbuffer[0];
+ }
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------
+// Set the MicroLAN communucation speed.
+//
+// 'NewSpeed' - new speed defined as
+// MODE_NORMAL 0x00
+// MODE_OVERDRIVE 0x01
+//
+// Returns: current MicroLAN speed
+//
+int MLanSpeed(int NewSpeed)
+{
+ uchar sendpacket[5];
+ short sendlen=0;
+ int rt = FALSE;
+
+ // check if change from current mode
+ if (((NewSpeed == MODE_OVERDRIVE) &&
+ (USpeed != SPEEDSEL_OD)) ||
+ ((NewSpeed == MODE_NORMAL) &&
+ (USpeed != SPEEDSEL_FLEX)))
+ {
+ if (NewSpeed == MODE_OVERDRIVE)
+ {
+ // if overdrive then switch to 115200 baud
+ if (DS2480ChangeBaud(PARMSET_57600) == PARMSET_57600)
+ {
+ USpeed = SPEEDSEL_OD;
+ rt = TRUE;
+ }
+ }
+ else if (NewSpeed == MODE_NORMAL)
+ {
+ // else normal so set to 9600 baud
+ if (DS2480ChangeBaud(PARMSET_9600) == PARMSET_9600)
+ {
+ USpeed = SPEEDSEL_FLEX;
+ rt = TRUE;
+ }
+ }
+
+ // if baud rate is set correctly then change DS2480 speed
+ if (rt)
+ {
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // proceed to set the DS2480 communication speed
+ sendpacket[sendlen++] = CMD_COMM | FUNCTSEL_SEARCHOFF | USpeed;
+
+ // send the packet
+ if (!WriteCOM(sendlen,sendpacket))
+ {
+ rt = FALSE;
+ // lost communication with DS2480 then reset
+ DS2480Detect();
+ }
+ }
+ }
+
+ // return the current speed
+ return (USpeed == SPEEDSEL_OD) ? MODE_OVERDRIVE : MODE_NORMAL;
+}
+
+
+//--------------------------------------------------------------------------
+// Set the MicroLAN line level. The values for NewLevel are
+// as follows:
+//
+// 'NewLevel' - new level defined as
+// MODE_NORMAL 0x00
+// MODE_STRONG5 0x02
+// MODE_PROGRAM 0x04
+// MODE_BREAK 0x08 (not supported)
+//
+// Returns: current MicroLAN level
+//
+int MLanLevel(int NewLevel)
+{
+ uchar sendpacket[10],readbuffer[10];
+ short sendlen=0;
+ short rt=FALSE;
+
+ // check if need to change level
+ if (NewLevel != ULevel)
+ {
+ // check if just putting back to normal
+ if (NewLevel == MODE_NORMAL)
+ {
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // stop pulse command
+ sendpacket[sendlen++] = MODE_STOP_PULSE;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 1 byte response
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // check response byte
+ if ((readbuffer[0] & 0xE0) == 0xE0)
+ {
+ rt = TRUE;
+ ULevel = MODE_NORMAL;
+ }
+ }
+ }
+ }
+ // set new level
+ else
+ {
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // strong 5 volts
+ if (NewLevel == MODE_STRONG5)
+ {
+ // set the SPUD time value
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_5VPULSE | PARMSET_infinite;
+ // add the command to begin the pulse
+ sendpacket[sendlen++] = CMD_COMM | FUNCTSEL_CHMOD | SPEEDSEL_PULSE | BITPOL_5V;
+ }
+ // 12 volts
+ else if (NewLevel == MODE_PROGRAM)
+ {
+ // check if programming voltage available
+ if (!ProgramAvailable)
+ return MODE_NORMAL;
+
+ // set the PPD time value
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_12VPULSE | PARMSET_infinite;
+ // add the command to begin the pulse
+ sendpacket[sendlen++] = CMD_COMM | FUNCTSEL_CHMOD | SPEEDSEL_PULSE | BITPOL_12V;
+ }
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 1 byte response from setting time limit
+ if (ReadCOM(1,readbuffer) == 1)
+ {
+ // check response byte
+ if ((readbuffer[0] & 0x81) == 0)
+ {
+ ULevel = NewLevel;
+ rt = TRUE;
+ }
+ }
+ }
+ }
+
+ // if lost communication with DS2480 then reset
+ if (rt != TRUE)
+ DS2480Detect();
+ }
+
+ // return the current level
+ return ULevel;
+}
+
+
+//--------------------------------------------------------------------------
+// This procedure creates a fixed 480 microseconds 12 volt pulse
+// on the MicroLAN for programming EPROM iButtons.
+//
+// Returns: TRUE successful
+// FALSE program voltage not available
+//
+int MLanProgramPulse(void)
+{
+ uchar sendpacket[10],readbuffer[10];
+ short sendlen=0;
+
+ // check if programming voltage available
+ if (!ProgramAvailable)
+ return FALSE;
+
+ // make sure normal level
+ MLanLevel(MODE_NORMAL);
+
+ // check if correct mode
+ if (UMode != MODSEL_COMMAND)
+ {
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+ }
+
+ // set the SPUD time value
+ sendpacket[sendlen++] = CMD_CONFIG | PARMSEL_12VPULSE | PARMSET_512us;
+
+ // pulse command
+ sendpacket[sendlen++] = CMD_COMM | FUNCTSEL_CHMOD | BITPOL_12V | SPEEDSEL_PULSE;
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 2 byte response
+ if (ReadCOM(2,readbuffer) == 2)
+ {
+ // check response byte
+ if (((readbuffer[0] | CMD_CONFIG) ==
+ (CMD_CONFIG | PARMSEL_12VPULSE | PARMSET_512us)) &&
+ ((readbuffer[1] & 0xFC) ==
+ (0xFC & (CMD_COMM | FUNCTSEL_CHMOD | BITPOL_12V | SPEEDSEL_PULSE))))
+ return TRUE;
+ }
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ return FALSE;
+}
+
diff --git a/src/pmdas/roomtemp/mlan/mlannetu.c b/src/pmdas/roomtemp/mlan/mlannetu.c
new file mode 100644
index 0000000..88ddbbd
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/mlannetu.c
@@ -0,0 +1,599 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// MLanNetU.C - Network functions for MicroLAN 1-Wire devices
+// using the DS2480 (U) serial interface chip.
+//
+// Version: 1.03
+//
+// 1.02 -> 1.03 Removed caps in #includes for Linux capatibility
+//
+
+#include "ds2480.h"
+#include "mlan.h"
+
+// external MicroLAN functions required
+extern int MLanTouchReset(void);
+extern int MLanTouchBit(int);
+extern int MLanWriteByte(int sendbyte);
+extern int MLanReadByte(void);
+extern int MLanSpeed(int);
+extern int MLanLevel(int);
+extern int MLanBlock(int, uchar *, int);
+
+// external COM functions required
+extern void FlushCOM(void);
+extern int WriteCOM(int, uchar *);
+extern int ReadCOM(int, uchar *);
+
+// external DS2480 utility function
+extern int DS2480Detect(void);
+
+// exportable functions
+int MLanFirst(int,int);
+int MLanNext(int,int);
+void MLanSerialNum(uchar *, int);
+void MLanFamilySearchSetup(int);
+void MLanSkipFamily(void);
+int MLanAccess(void);
+int MLanVerify(int);
+int MLanOverdriveAccess(void);
+
+// local functions
+int bitacc(int, int, int, uchar *);
+uchar dowcrc(uchar);
+
+// global variables for this module to hold search state information
+static int LastDiscrepancy;
+static int LastFamilyDiscrepancy;
+static int LastDevice;
+uchar DOWCRC;
+uchar SerialNum[8];
+
+// external globals
+extern int UMode;
+extern int UBaud;
+extern int USpeed;
+
+//--------------------------------------------------------------------------
+// The 'MLanFirst' finds the first device on the MicroLAN This function
+// contains one parameter 'OnlyAlarmingDevices'. When
+// 'OnlyAlarmingDevices' is TRUE (1) the find alarm command 0xEC is
+// sent instead of the normal search command 0xF0.
+// Using the find alarm command 0xEC will limit the search to only
+// 1-Wire devices that are in an 'alarm' state.
+//
+// 'DoReset' - TRUE (1) perform reset before search, FALSE (0) do not
+// perform reset before search.
+// 'OnlyAlarmDevices' - TRUE (1) the find alarm command 0xEC is
+// sent instead of the normal search command 0xF0
+//
+// Returns: TRUE (1) : when a 1-Wire device was found and it's
+// Serial Number placed in the global SerialNum
+// FALSE (0): There are no devices on the MicroLAN.
+//
+int MLanFirst(int DoReset, int OnlyAlarmingDevices)
+{
+ // reset the search state
+ LastDiscrepancy = 0;
+ LastDevice = FALSE;
+ LastFamilyDiscrepancy = 0;
+
+ return MLanNext(DoReset, OnlyAlarmingDevices);
+}
+
+//--------------------------------------------------------------------------
+// The 'MLanNext' function does a general search. This function
+// continues from the previos search state. The search state
+// can be reset by using the 'MLanFirst' function.
+// This function contains one parameter 'OnlyAlarmingDevices'.
+// When 'OnlyAlarmingDevices' is TRUE (1) the find alarm command
+// 0xEC is sent instead of the normal search command 0xF0.
+// Using the find alarm command 0xEC will limit the search to only
+// 1-Wire devices that are in an 'alarm' state.
+//
+// 'DoReset' - TRUE (1) perform reset before search, FALSE (0) do not
+// perform reset before search.
+// 'OnlyAlarmDevices' - TRUE (1) the find alarm command 0xEC is
+// sent instead of the normal search command 0xF0
+//
+// Returns: TRUE (1) : when a 1-Wire device was found and it's
+// Serial Number placed in the global SerialNum
+// FALSE (0): when no new device was found. Either the
+// last search was the last device or there
+// are no devices on the MicroLAN.
+//
+int MLanNext(int DoReset, int OnlyAlarmingDevices)
+{
+ int i,TempLastDescrepancy,pos;
+ uchar TempSerialNum[8];
+ uchar readbuffer[20],sendpacket[40];
+ int sendlen=0;
+
+ // if the last call was the last one
+ if (LastDevice)
+ {
+ // reset the search
+ LastDiscrepancy = 0;
+ LastDevice = FALSE;
+ LastFamilyDiscrepancy = 0;
+ return FALSE;
+ }
+
+ // check if reset first is requested
+ if (DoReset)
+ {
+ // reset the 1-wire
+ // if there are no parts on 1-wire, return FALSE
+ if (!MLanTouchReset())
+ {
+ // reset the search
+ LastDiscrepancy = 0;
+ LastFamilyDiscrepancy = 0;
+ return FALSE;
+ }
+ }
+
+ // build the command stream
+ // call a function that may add the change mode command to the buff
+ // check if correct mode
+ if (UMode != MODSEL_DATA)
+ {
+ UMode = MODSEL_DATA;
+ sendpacket[sendlen++] = MODE_DATA;
+ }
+
+ // search command
+ if (OnlyAlarmingDevices)
+ sendpacket[sendlen++] = 0xEC; // issue the alarming search command
+ else
+ sendpacket[sendlen++] = 0xF0; // issue the search command
+
+ // change back to command mode
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+
+ // search mode on
+ sendpacket[sendlen++] = (uchar)(CMD_COMM | FUNCTSEL_SEARCHON | USpeed);
+
+ // change back to data mode
+ UMode = MODSEL_DATA;
+ sendpacket[sendlen++] = MODE_DATA;
+
+ // set the temp Last Descrep to none
+ TempLastDescrepancy = 0xFF;
+
+ // add the 16 bytes of the search
+ pos = sendlen;
+ for (i = 0; i < 16; i++)
+ sendpacket[sendlen++] = 0;
+
+ // only modify bits if not the first search
+ if (LastDiscrepancy != 0xFF)
+ {
+ // set the bits in the added buffer
+ for (i = 0; i < 64; i++)
+ {
+ // before last discrepancy
+ if (i < (LastDiscrepancy - 1))
+ bitacc(WRITE_FUNCTION,
+ bitacc(READ_FUNCTION,0,i,&SerialNum[0]),
+ (short)(i * 2 + 1),
+ &sendpacket[pos]);
+ // at last discrepancy
+ else if (i == (LastDiscrepancy - 1))
+ bitacc(WRITE_FUNCTION,1,
+ (short)(i * 2 + 1),
+ &sendpacket[pos]);
+ // after last discrepancy so leave zeros
+ }
+ }
+
+ // change back to command mode
+ UMode = MODSEL_COMMAND;
+ sendpacket[sendlen++] = MODE_COMMAND;
+
+ // search OFF
+ sendpacket[sendlen++] = (uchar)(CMD_COMM | FUNCTSEL_SEARCHOFF | USpeed);
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the 1 byte response
+ if (ReadCOM(17,readbuffer) == 17)
+ {
+ // interpret the bit stream
+ for (i = 0; i < 64; i++)
+ {
+ // get the SerialNum bit
+ bitacc(WRITE_FUNCTION,
+ bitacc(READ_FUNCTION,0,(short)(i * 2 + 1),&readbuffer[1]),
+ i,
+ &TempSerialNum[0]);
+ // check LastDiscrepancy
+ if ((bitacc(READ_FUNCTION,0,(short)(i * 2),&readbuffer[1]) == 1) &&
+ (bitacc(READ_FUNCTION,0,(short)(i * 2 + 1),&readbuffer[1]) == 0))
+ {
+ TempLastDescrepancy = i + 1;
+ // check LastFamilyDiscrepancy
+ if (i < 8)
+ LastFamilyDiscrepancy = i + 1;
+ }
+ }
+
+ // do dowcrc
+ DOWCRC = 0;
+ for (i = 0; i < 8; i++)
+ dowcrc(TempSerialNum[i]);
+
+ // check results
+ if ((DOWCRC != 0) || (LastDiscrepancy == 63) || (TempSerialNum[0] == 0))
+ {
+ // error during search
+ // reset the search
+ LastDiscrepancy = 0;
+ LastDevice = FALSE;
+ LastFamilyDiscrepancy = 0;
+ return FALSE;
+ }
+ // successful search
+ else
+ {
+ // check for lastone
+ if ((TempLastDescrepancy == LastDiscrepancy) || (TempLastDescrepancy == 0xFF))
+ LastDevice = TRUE;
+
+ // copy the SerialNum to the buffer
+ for (i = 0; i < 8; i++)
+ SerialNum[i] = TempSerialNum[i];
+
+ // set the count
+ LastDiscrepancy = TempLastDescrepancy;
+ return TRUE;
+ }
+ }
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ // reset the search
+ LastDiscrepancy = 0;
+ LastDevice = FALSE;
+ LastFamilyDiscrepancy = 0;
+
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// The 'MLanSerialNum' function either reads or sets the SerialNum buffer
+// that is used in the search functions 'MLanFirst' and 'MLanNext'.
+// This function contains two parameters, 'SerialNumBuf' is a pointer
+// to a buffer provided by the caller. 'SerialNumBuf' should point to
+// an array of 8 unsigned chars. The second parameter is a flag called
+// 'DoRead' that is TRUE (1) if the operation is to read and FALSE
+// (0) if the operation is to set the internal SerialNum buffer from
+// the data in the provided buffer.
+//
+// 'SerialNumBuf' - buffer to that contains the serial number to set
+// when DoRead = FALSE (0) and buffer to get the serial
+// number when DoRead = TRUE (1).
+// 'DoRead' - flag to indicate reading (1) or setting (0) the current
+// serial number.
+//
+void MLanSerialNum(uchar *SerialNumBuf, int DoRead)
+{
+ int i;
+
+ // read the internal buffer and place in 'SerialNumBuf'
+ if (DoRead)
+ {
+ for (i = 0; i < 8; i++)
+ SerialNumBuf[i] = SerialNum[i];
+ }
+ // set the internal buffer from the data in 'SerialNumBuf'
+ else
+ {
+ for (i = 0; i < 8; i++)
+ SerialNum[i] = SerialNumBuf[i];
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Setup the search algorithm to find a certain family of devices
+// the next time a search function is called 'MLanNext'.
+//
+// 'SearchFamily' - family code type to set the search algorithm to find
+// next.
+//
+void MLanFamilySearchSetup(int SearchFamily)
+{
+ int i;
+
+ // set the search state to find SearchFamily type devices
+ SerialNum[0] = (uchar)SearchFamily;
+ for (i = 1; i < 8; i++)
+ SerialNum[i] = 0;
+ LastDiscrepancy = 64;
+ LastDevice = FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Set the current search state to skip the current family code.
+//
+void MLanSkipFamily(void)
+{
+ // set the Last discrepancy to last family discrepancy
+ LastDiscrepancy = LastFamilyDiscrepancy;
+
+ // check for end of list
+ if (LastDiscrepancy == 0)
+ LastDevice = TRUE;
+}
+
+
+//--------------------------------------------------------------------------
+// The 'MLanAccess' function resets the 1-Wire and sends a MATCH Serial
+// Number command followed by the current SerialNum code. After this
+// function is complete the 1-Wire device is ready to accept device-specific
+// commands.
+//
+// Returns: TRUE (1) : reset indicates present and device is ready
+// for commands.
+// FALSE (0): reset does not indicate presence or echos 'writes'
+// are not correct.
+//
+int MLanAccess(void)
+{
+ uchar TranBuf[9];
+ int i;
+
+ // reset the 1-wire
+ if (MLanTouchReset())
+ {
+ // create a buffer to use with block function
+ // match Serial Number command 0x55
+ TranBuf[0] = 0x55;
+ // Serial Number
+ for (i = 1; i < 9; i++)
+ TranBuf[i] = SerialNum[i-1];
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(FALSE,TranBuf,9))
+ {
+ // verify that the echo of the writes was correct
+ for (i = 1; i < 9; i++)
+ if (TranBuf[i] != SerialNum[i-1])
+ return FALSE;
+ if (TranBuf[0] != 0x55)
+ return FALSE;
+ else
+ return TRUE;
+ }
+ }
+
+ // reset or match echo failed
+ return FALSE;
+}
+
+
+//----------------------------------------------------------------------
+// The function 'MLanVerify' verifies that the current device
+// is in contact with the MicroLAN.
+// Using the find alarm command 0xEC will verify that the device
+// is in contact with the MicroLAN and is in an 'alarm' state.
+//
+// 'OnlyAlarmingDevices' - TRUE (1) the find alarm command 0xEC
+// is sent instead of the normal search
+// command 0xF0.
+//
+// Returns: TRUE (1) : when the 1-Wire device was verified
+// to be on the MicroLAN
+// with OnlyAlarmingDevices == FALSE
+// or verified to be on the MicroLAN
+// AND in an alarm state when
+// OnlyAlarmingDevices == TRUE.
+// FALSE (0): the 1-Wire device was not on the
+// MicroLAN or if OnlyAlarmingDevices
+// == TRUE, the device may be on the
+// MicroLAN but in a non-alarm state.
+//
+int MLanVerify(int OnlyAlarmingDevices)
+{
+ int i,TranCnt=0,goodbits=0,cnt=0,s,tst;
+ uchar TranBuf[50];
+
+ // construct the search rom
+ if (OnlyAlarmingDevices)
+ TranBuf[TranCnt++] = 0xEC; // issue the alarming search command
+ else
+ TranBuf[TranCnt++] = 0xF0; // issue the search command
+ // set all bits at first
+ for (i = 1; i <= 24; i++)
+ TranBuf[TranCnt++] = 0xFF;
+ // now set or clear apropriate bits for search
+ for (i = 0; i < 64; i++)
+ bitacc(WRITE_FUNCTION,bitacc(READ_FUNCTION,0,i,&SerialNum[0]),(int)((i+1)*3-1),&TranBuf[1]);
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(TRUE,TranBuf,TranCnt))
+ {
+ // check results to see if it was a success
+ for (i = 0; i < 192; i += 3)
+ {
+ tst = (bitacc(READ_FUNCTION,0,i,&TranBuf[1]) << 1) |
+ bitacc(READ_FUNCTION,0,(int)(i+1),&TranBuf[1]);
+
+ s = bitacc(READ_FUNCTION,0,cnt++,&SerialNum[0]);
+
+ if (tst == 0x03) // no device on line
+ {
+ goodbits = 0; // number of good bits set to zero
+ break; // quit
+ }
+
+ if (((s == 0x01) && (tst == 0x02)) ||
+ ((s == 0x00) && (tst == 0x01)) ) // correct bit
+ goodbits++; // count as a good bit
+ }
+
+ // check too see if there were enough good bits to be successful
+ if (goodbits >= 8)
+ return TRUE;
+ }
+
+ // block fail or device not present
+ return FALSE;
+}
+
+
+//----------------------------------------------------------------------
+// Perform a overdrive MATCH command to select the 1-Wire device with
+// the address in the ID data register.
+//
+// Returns: TRUE: If the device is present on the MicroLAN and
+// can do overdrive then the device is selected.
+// FALSE: Device is not present or not capable of overdrive.
+//
+// *Note: This function could be converted to send DS2480
+// commands in one packet.
+//
+int MLanOverdriveAccess(void)
+{
+ uchar TranBuf[8];
+ int i, EchoBad = FALSE;
+
+ // make sure normal level
+ MLanLevel(MODE_NORMAL);
+
+ // force to normal communication speed
+ MLanSpeed(MODE_NORMAL);
+
+ // call the MicroLAN reset function
+ if (MLanTouchReset())
+ {
+ // send the match command 0x69
+ if (MLanWriteByte(0x69))
+ {
+ // switch to overdrive communication speed
+ MLanSpeed(MODE_OVERDRIVE);
+
+ // create a buffer to use with block function
+ // Serial Number
+ for (i = 0; i < 8; i++)
+ TranBuf[i] = SerialNum[i];
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(FALSE,TranBuf,8))
+ {
+ // verify that the echo of the writes was correct
+ for (i = 0; i < 8; i++)
+ if (TranBuf[i] != SerialNum[i])
+ EchoBad = TRUE;
+ // if echo ok then success
+ if (!EchoBad)
+ return TRUE;
+ }
+ }
+ }
+
+ // failure, force back to normal communication speed
+ MLanSpeed(MODE_NORMAL);
+
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Update the Dallas Semiconductor One Wire CRC (DOWCRC) from the global
+// variable DOWCRC and the argument.
+//
+// 'x' - data byte to calculate the 8 bit crc from
+//
+// Returns: the updated DOWCRC.
+//
+uchar dscrc_table[] = {
+ 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
+ 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220,
+ 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98,
+ 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
+ 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7,
+ 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154,
+ 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36,
+ 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185,
+ 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
+ 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
+ 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
+ 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
+ 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139,
+ 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
+ 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
+ 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53};
+
+uchar dowcrc(uchar x)
+{
+ DOWCRC = dscrc_table[DOWCRC ^ x];
+ return DOWCRC;
+}
+
+
+//--------------------------------------------------------------------------
+// Bit utility to read and write a bit in the buffer 'buf'.
+//
+// 'op' - operation (1) to set and (0) to read
+// 'state' - set (1) or clear (0) if operation is write (1)
+// 'loc' - bit number location to read or write
+// 'buf' - pointer to array of bytes that contains the bit
+// to read or write
+//
+// Returns: 1 if operation is set (1)
+// 0/1 state of bit number 'loc' if operation is reading
+//
+int bitacc(int op, int state, int loc, uchar *buf)
+{
+ int nbyt,nbit;
+
+ nbyt = (loc / 8);
+ nbit = loc - (nbyt * 8);
+
+ if (op == WRITE_FUNCTION)
+ {
+ if (state)
+ buf[nbyt] |= (0x01 << nbit);
+ else
+ buf[nbyt] &= ~(0x01 << nbit);
+
+ return 1;
+ }
+ else
+ return ((buf[nbyt] >> nbit) & 0x01);
+}
diff --git a/src/pmdas/roomtemp/mlan/mlansesu.c b/src/pmdas/roomtemp/mlan/mlansesu.c
new file mode 100644
index 0000000..b46c349
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/mlansesu.c
@@ -0,0 +1,102 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// MLanSesU.C - Aquire and release a Session on the 1-Wire Net.
+//
+// Version: 1.03
+//
+
+#include "pmapi.h"
+#include "mlan.h"
+
+// external function prototypes
+extern int OpenCOM(char *);
+extern void CloseCOM(void);
+extern int DS2480Detect(void);
+
+// local function prototypes
+int Aquire1WireNet(char *, char *);
+void Release1WireNet(char *);
+
+// keep port name for later message when closing
+char portname[128];
+
+// debugging
+int MLanDebug = 0;
+
+//---------------------------------------------------------------------------
+// Attempt to aquire a 1-Wire net using a com port and a DS2480 based
+// adapter.
+//
+// 'port_zstr' - zero terminated port name. For this platform
+// use format COMX where X is the port number.
+// 'return_msg' - zero terminated return message.
+//
+// Returns: TRUE - success, COM port opened
+//
+int Aquire1WireNet(char *port_zstr, char *return_msg)
+{
+ int cnt=0;
+ portname[0] = 0;
+
+ // attempt to open the communications port
+ if (OpenCOM(port_zstr) >= 0)
+ cnt += sprintf(&return_msg[cnt],"%s opened\n",port_zstr);
+ else
+ {
+ cnt += sprintf(&return_msg[cnt],"Could not open port %s: %s,"
+ " aborting.\nClosing port %s.\n",port_zstr,osstrerror(),port_zstr);
+ return FALSE;
+ }
+
+ // detect DS2480
+ if (DS2480Detect())
+ cnt += sprintf(&return_msg[cnt],"DS2480-based adapter detected\n");
+ else
+ {
+ cnt += sprintf(&return_msg[cnt],"DS2480-based adapter not detected, aborting program\n");
+ cnt += sprintf(&return_msg[cnt],"Closing port %s.\n",port_zstr);
+ CloseCOM();
+ return FALSE;
+ }
+
+ // success
+ sprintf(portname,"%s",port_zstr);
+ return TRUE;
+}
+
+
+//---------------------------------------------------------------------------
+// Release the previously aquired a 1-Wire net.
+//
+// 'return_msg' - zero terminated return message.
+//
+void Release1WireNet(char *return_msg)
+{
+ // close the communications port
+ sprintf(return_msg,"Closing port %s.\n",portname);
+ CloseCOM();
+}
diff --git a/src/pmdas/roomtemp/mlan/mlantrnu.c b/src/pmdas/roomtemp/mlan/mlantrnu.c
new file mode 100644
index 0000000..5a0b376
--- /dev/null
+++ b/src/pmdas/roomtemp/mlan/mlantrnu.c
@@ -0,0 +1,579 @@
+//---------------------------------------------------------------------------
+// Copyright (C) 1999 Dallas Semiconductor Corporation, All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the "Software"),
+// to deal in the Software without restriction, including without limitation
+// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+// and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
+// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name of Dallas Semiconductor
+// shall not be used except as stated in the Dallas Semiconductor
+// Branding Policy.
+//---------------------------------------------------------------------------
+//
+// MLanTranU.C - Transport functions for MicroLAN 1-Wire devices
+// using the DS2480 (U) serial interface chip.
+//
+// Version: 1.03
+//
+// 1.02 -> 1.03 Removed caps in #includes for Linux capatibility
+//
+
+#include "ds2480.h"
+#include "mlan.h"
+
+// external low-level functions required
+extern int MLanTouchReset(void);
+extern int MLanWriteByte(int);
+extern int MLanReadByte(void);
+extern int MLanProgramPulse(void);
+
+// external network-level functions required
+extern int MLanAccess();
+
+// external COM functions required
+extern void FlushCOM(void);
+extern int WriteCOM(int, uchar *);
+extern int ReadCOM(int, uchar *);
+
+// other external functions
+extern int DS2480Detect(void);
+extern uchar dowcrc(uchar);
+
+// external globals
+extern int UMode;
+extern int UBaud;
+extern int USpeed;
+extern uchar SerialNum[8];
+extern uchar DOWCRC;
+
+// local exportable functions
+int MLanBlock(int, uchar *, int);
+int MLanReadPacketStd(int, int, uchar *);
+int MLanWritePacketStd(int, uchar *, int, int, int);
+int MLanProgramByte(int, int, int, int, int);
+
+// local functions
+static int Write_Scratchpad(uchar *, int, int);
+static int Copy_Scratchpad(int, int);
+unsigned short crc16(int);
+
+// global variable
+unsigned short CRC16;
+
+
+//--------------------------------------------------------------------------
+// The 'MLanBlock' transfers a block of data to and from the
+// MicroLAN with an optional reset at the begining of communication.
+// The result is returned in the same buffer.
+//
+// 'DoReset' - cause a MLanTouchReset to occure at the begining of
+// communication TRUE(1) or not FALSE(0)
+// 'TransferBuffer' - pointer to a block of unsigned
+// chars of length 'TranferLength' that will be sent
+// to the MicroLAN
+// 'TranferLength' - length in bytes to transfer
+
+// Supported devices: all
+//
+// Returns: TRUE (1) : The optional reset returned a valid
+// presence (DoReset == TRUE) or there
+// was no reset required.
+// FALSE (0): The reset did not return a valid prsence
+// (DoReset == TRUE).
+//
+// The maximum TransferLength is 64
+//
+int MLanBlock(int DoReset, uchar *TransferBuffer, int TransferLen)
+{
+ uchar sendpacket[150];
+ int sendlen=0,i;
+
+ // check for a block too big
+ if (TransferLen > 64)
+ return FALSE;
+
+ // check if need to do a MLanTouchReset first
+ if (DoReset)
+ {
+ if (!MLanTouchReset())
+ return FALSE;
+ }
+
+ // construct the packet to send to the DS2480
+ // check if correct mode
+ if (UMode != MODSEL_DATA)
+ {
+ UMode = MODSEL_DATA;
+ sendpacket[sendlen++] = MODE_DATA;
+ }
+
+ // add the bytes to send
+ for (i = 0; i < TransferLen; i++)
+ {
+ sendpacket[sendlen++] = TransferBuffer[i];
+
+ // check for duplication of data that looks like COMMAND mode
+ if (TransferBuffer[i] == MODE_COMMAND)
+ sendpacket[sendlen++] = TransferBuffer[i];
+ }
+
+ // flush the buffers
+ FlushCOM();
+
+ // send the packet
+ if (WriteCOM(sendlen,sendpacket))
+ {
+ // read back the response
+ if (ReadCOM(TransferLen,TransferBuffer) == TransferLen)
+ return TRUE;
+ }
+
+ // an error occured so re-sync with DS2480
+ DS2480Detect();
+
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Read a Universal Data Packet from a standard NVRAM iButton
+// and return it in the provided buffer. The page that the
+// packet resides on is 'StartPage'. Note that this function is limited
+// to single page packets. The buffer 'ReadBuffer' must be at least
+// 29 bytes long.
+//
+// The Universal Data Packet always start on page boundaries but
+// can end anywhere. The length is the number of data bytes not
+// including the length byte and the CRC16 bytes. There is one
+// length byte. The CRC16 is first initialized to the starting
+// page number. This provides a check to verify the page that
+// was intended is being read. The CRC16 is then calculated over
+// the length and data bytes. The CRC16 is then inverted and stored
+// low byte first followed by the high byte.
+//
+// Supported devices: DS1992, DS1993, DS1994, DS1995, DS1996, DS1982,
+// DS1985, DS1986, DS2407, and DS1971.
+//
+// 'DoAccess' - flag to indicate if an 'MLanAccess' should be
+// peformed at the begining of the read. This may
+// be FALSE (0) if the previous call was to read the
+// previous page (StartPage-1).
+// 'StartPage' - page number to start the read from
+// 'ReadBuffer' - pointer to a location to store the data read
+//
+// Returns: >=0 success, number of data bytes in the buffer
+// -1 failed to read a valid UDP
+//
+//
+int MLanReadPacketStd(int DoAccess, int StartPage, uchar *ReadBuffer)
+{
+ int i,length,TranCnt=0,HeadLen=0;
+ uchar TranBuf[50];
+
+ // check if access header is done
+ // (only use if in sequention read with one access at begining)
+ if (DoAccess)
+ {
+ // match command
+ TranBuf[TranCnt++] = 0x55;
+ for (i = 0; i < 8; i++)
+ TranBuf[TranCnt++] = SerialNum[i];
+ // read memory command
+ TranBuf[TranCnt++] = 0xF0;
+ // write the target address
+ TranBuf[TranCnt++] = ((StartPage << 5) & 0xFF);
+ TranBuf[TranCnt++] = (StartPage >> 3);
+ // check for DS1982 exception (redirection byte)
+ if (SerialNum[0] == 0x09)
+ TranBuf[TranCnt++] = 0xFF;
+ // record the header length
+ HeadLen = TranCnt;
+ }
+ // read the entire page length byte
+ for (i = 0; i < 32; i++)
+ TranBuf[TranCnt++] = 0xFF;
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(DoAccess,TranBuf,TranCnt))
+ {
+ // seed crc with page number
+ CRC16 = StartPage;
+
+ // attempt to read UDP from TranBuf
+ length = TranBuf[HeadLen];
+ crc16(length);
+
+ // verify length is not too large
+ if (length <= 29)
+ {
+ // loop to read packet including CRC
+ for (i = 0; i < length; i++)
+ {
+ ReadBuffer[i] = TranBuf[i+1+HeadLen];
+ crc16(ReadBuffer[i]);
+ }
+
+ // read and compute the CRC16
+ crc16(TranBuf[i+1+HeadLen]);
+ crc16(TranBuf[i+2+HeadLen]);
+
+ // verify the CRC16 is correct
+ if (CRC16 == 0xB001)
+ return length; // return number of byte in record
+ }
+ }
+
+ // failed block or incorrect CRC
+ return -1;
+}
+
+
+//--------------------------------------------------------------------------
+// Write a Universal Data Packet onto a standard NVRAM 1-Wire device
+// on page 'StartPage'. This function is limited to UDPs that
+// fit on one page. The data to write is provided as a buffer
+// 'WriteBuffer' with a length 'WriteLength'.
+//
+// The Universal Data Packet always start on page boundaries but
+// can end anywhere. The length is the number of data bytes not
+// including the length byte and the CRC16 bytes. There is one
+// length byte. The CRC16 is first initialized to the starting
+// page number. This provides a check to verify the page that
+// was intended is being read. The CRC16 is then calculated over
+// the length and data bytes. The CRC16 is then inverted and stored
+// low byte first followed by the high byte.
+//
+// Supported devices: DeviceEPROM=0
+// DS1992, DS1993, DS1994, DS1995, DS1996
+// DeviceEPROM=1, EPROMCRCType=0(CRC8)
+// DS1982
+// DeviceEPROM=1, EPROMCRCType=1(CRC16)
+// DS1985, DS1986, DS2407
+//
+// 'StartPage' - page number to write packet to
+// 'WriteBuffer' - pointer to buffer containing data to write
+// 'WriteLength' - number of data byte in WriteBuffer
+// 'DeviceEPROM' - flag set if device is an EPROM (1 EPROM, 0 NVRAM)
+// 'EPROMCRCType' - if DeviceEPROM=1 then indicates CRC type
+// (0 CRC8, 1 CRC16)
+//
+// Returns: TRUE(1) success, packet written
+// FALSE(0) failure to write, contact lost or device locked
+//
+//
+int MLanWritePacketStd(int StartPage, uchar *WriteBuffer,
+ int WriteLength, int DeviceEPROM, int EPROMCRCType)
+{
+ uchar construct_buffer[32];
+ int i,buffer_cnt=0,start_address,do_access;
+
+ // check to see if data too long to fit on device
+ if (WriteLength > 29)
+ return FALSE;
+
+ // seed crc with page number
+ CRC16 = StartPage;
+
+ // set length byte
+ construct_buffer[buffer_cnt++] = (uchar)(WriteLength);
+ crc16(WriteLength);
+
+ // fill in the data to write
+ for (i = 0; i < WriteLength; i++)
+ {
+ crc16(WriteBuffer[i]);
+ construct_buffer[buffer_cnt++] = WriteBuffer[i];
+ }
+
+ // add the crc
+ construct_buffer[buffer_cnt++] = (uchar)(~(CRC16 & 0xFF));
+ construct_buffer[buffer_cnt++] = (uchar)(~((CRC16 & 0xFF00) >> 8));
+
+ // check if not EPROM
+ if (!DeviceEPROM)
+ {
+ // write the page
+ if (!Write_Scratchpad(construct_buffer,StartPage,buffer_cnt))
+ return FALSE;
+
+ // copy the scratchpad
+ if (!Copy_Scratchpad(StartPage,buffer_cnt))
+ return FALSE;
+
+ // copy scratch pad was good then success
+ return TRUE;
+ }
+ // is EPROM
+ else
+ {
+ // calculate the start address
+ start_address = ((StartPage >> 3) << 8) | ((StartPage << 5) & 0xFF);
+ do_access = TRUE;
+ // loop to program each byte
+ for (i = 0; i < buffer_cnt; i++)
+ {
+ if (MLanProgramByte(construct_buffer[i], start_address + i,
+ 0x0F, EPROMCRCType, do_access) != construct_buffer[i])
+ return FALSE;
+ do_access = FALSE;
+ }
+ return TRUE;
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Write a byte to an EPROM 1-Wire device.
+//
+// Supported devices: CRCType=0(CRC8)
+// DS1982
+// CRCType=1(CRC16)
+// DS1985, DS1986, DS2407
+//
+// 'WRByte' - byte to program
+// 'Addr' - address of byte to program
+// 'WriteCommand' - command used to write (0x0F reg mem, 0x55 status)
+// 'CRCType' - CRC used (0 CRC8, 1 CRC16)
+// 'DoAccess' - Flag to access device for each byte
+// (0 skip access, 1 do the access)
+// WARNING, only use DoAccess=0 if programing the NEXT
+// byte immediatly after the previous byte.
+//
+// Returns: >=0 success, this is the resulting byte from the program
+// effort
+// -1 error, device not connected or program pulse voltage
+// not available
+//
+int MLanProgramByte(int WRByte, int Addr, int WriteCommand,
+ int CRCType, int DoAccess)
+{
+ // optionally access the device
+ if (DoAccess)
+ {
+ if (!MLanAccess())
+ return -1;
+
+ // send the write command
+ if (!MLanWriteByte(WriteCommand))
+ return -1;
+
+ // send the address
+ if (!MLanWriteByte(Addr & 0xFF))
+ return -1;
+ if (!MLanWriteByte(Addr >> 8))
+ return -1;
+ }
+
+ // send the data to write
+ if (!MLanWriteByte(WRByte))
+ return -1;
+
+ // read the CRC
+ if (CRCType == 0)
+ {
+ // calculate CRC8
+ if (DoAccess)
+ {
+ DOWCRC = 0;
+ dowcrc((uchar)WriteCommand);
+ dowcrc((uchar)(Addr & 0xFF));
+ dowcrc((uchar)(Addr >> 8));
+ }
+ else
+ DOWCRC = (uchar)(Addr & 0xFF);
+
+ dowcrc((uchar)WRByte);
+ // read and calculate the read crc
+ dowcrc((uchar)MLanReadByte());
+ // crc should now be 0x00
+ if (DOWCRC != 0)
+ return -1;
+ }
+ else
+ {
+ // CRC16
+ if (DoAccess)
+ {
+ CRC16 = 0;
+ crc16(WriteCommand);
+ crc16(Addr & 0xFF);
+ crc16(Addr >> 8);
+ }
+ else
+ CRC16 = Addr;
+ crc16(WRByte);
+ // read and calculate the read crc
+ crc16(MLanReadByte());
+ crc16(MLanReadByte());
+ // crc should now be 0xB001
+ if (CRC16 != 0xB001)
+ return -1;
+ }
+
+ // send the program pulse
+ if (!MLanProgramPulse())
+ return -1;
+
+ // read back and return the resulting byte
+ return MLanReadByte();
+}
+
+
+//--------------------------------------------------------------------------
+// Write the scratchpad of a standard NVRam device such as the DS1992,3,4
+// and verify its contents.
+//
+// 'WriteBuffer' - pointer to buffer containing data to write
+// 'StartPage' - page number to write packet to
+// 'WriteLength' - number of data byte in WriteBuffer
+//
+// Returns: TRUE(1) success, the data was written and verified
+// FALSE(0) failure, the data could not be written
+//
+//
+int Write_Scratchpad(uchar *WriteBuffer, int StartPage, int WriteLength)
+{
+ int i,TranCnt=0;
+ uchar TranBuf[50];
+
+ // match command
+ TranBuf[TranCnt++] = 0x55;
+ for (i = 0; i < 8; i++)
+ TranBuf[TranCnt++] = SerialNum[i];
+ // write scratchpad command
+ TranBuf[TranCnt++] = 0x0F;
+ // write the target address
+ TranBuf[TranCnt++] = ((StartPage << 5) & 0xFF);
+ TranBuf[TranCnt++] = (StartPage >> 3);
+
+ // write packet bytes
+ for (i = 0; i < WriteLength; i++)
+ TranBuf[TranCnt++] = WriteBuffer[i];
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(TRUE,TranBuf,TranCnt))
+ {
+ // now attempt to read back to check
+ TranCnt = 0;
+ // match command
+ TranBuf[TranCnt++] = 0x55;
+ for (i = 0; i < 8; i++)
+ TranBuf[TranCnt++] = SerialNum[i];
+ // read scratchpad command
+ TranBuf[TranCnt++] = 0xAA;
+ // read the target address, offset and data
+ for (i = 0; i < (WriteLength + 3); i++)
+ TranBuf[TranCnt++] = 0xFF;
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(TRUE,TranBuf,TranCnt))
+ {
+ // check address and offset of scratchpad read
+ if ((TranBuf[10] != (int)((StartPage << 5) & 0xFF)) ||
+ (TranBuf[11] != (int)(StartPage >> 3)) ||
+ (TranBuf[12] != (int)(WriteLength - 1)))
+ return FALSE;
+
+ // verify each data byte
+ for (i = 0; i < WriteLength; i++)
+ if (TranBuf[i+13] != WriteBuffer[i])
+ return FALSE;
+
+ // must have verified
+ return TRUE;
+ }
+ }
+
+ // failed a block tranfer
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Copy the contents of the scratchpad to its intended nv ram page. The
+// page and length of the data is needed to build the authorization bytes
+// to copy.
+//
+// 'StartPage' - page number to write packet to
+// 'WriteLength' - number of data bytes that are being copied
+//
+// Returns: TRUE(1) success
+// FALSE(0) failure
+//
+int Copy_Scratchpad(int StartPage, int WriteLength)
+{
+ int i,TranCnt=0;
+ uchar TranBuf[50];
+
+ // match command
+ TranBuf[TranCnt++] = 0x55;
+ for (i = 0; i < 8; i++)
+ TranBuf[TranCnt++] = SerialNum[i];
+ // copy scratchpad command
+ TranBuf[TranCnt++] = 0x55;
+ // write the target address
+ TranBuf[TranCnt++] = ((StartPage << 5) & 0xFF);
+ TranBuf[TranCnt++] = (StartPage >> 3);
+ TranBuf[TranCnt++] = WriteLength - 1;
+ // read copy result
+ TranBuf[TranCnt++] = 0xFF;
+
+ // send/recieve the transfer buffer
+ if (MLanBlock(TRUE,TranBuf,TranCnt))
+ {
+ // check address and offset of scratchpad read
+ if ((TranBuf[10] != (int)((StartPage << 5) & 0xFF)) ||
+ (TranBuf[11] != (int)(StartPage >> 3)) ||
+ (TranBuf[12] != (int)(WriteLength - 1)) ||
+ (TranBuf[13] & 0xF0))
+ return FALSE;
+ else
+ return TRUE;
+ }
+
+ // failed a block tranfer
+ return FALSE;
+}
+
+
+//--------------------------------------------------------------------------
+// Calculate a new CRC16 from the input data integer. Return the current
+// CRC16 and also update the global variable CRC16.
+//
+// 'data' - data to perform a CRC16 on
+//
+// Returns: the current CRC16
+//
+static short oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
+
+unsigned short crc16(int data)
+{
+ data = (data ^ (CRC16 & 0xff)) & 0xff;
+ CRC16 >>= 8;
+
+ if (oddparity[data & 0xf] ^ oddparity[data >> 4])
+ CRC16 ^= 0xc001;
+
+ data <<= 6;
+ CRC16 ^= data;
+ data <<= 1;
+ CRC16 ^= data;
+
+ return CRC16;
+}
+
+
diff --git a/src/pmdas/roomtemp/pmns b/src/pmdas/roomtemp/pmns
new file mode 100644
index 0000000..7843f5c
--- /dev/null
+++ b/src/pmdas/roomtemp/pmns
@@ -0,0 +1,24 @@
+/*
+ * Metrics for roomtemp PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+roomtemp {
+ celsius ROOMTEMP:0:0
+ fahrenheit ROOMTEMP:0:1
+}
diff --git a/src/pmdas/roomtemp/roomtemp.c b/src/pmdas/roomtemp/roomtemp.c
new file mode 100644
index 0000000..510cf7c
--- /dev/null
+++ b/src/pmdas/roomtemp/roomtemp.c
@@ -0,0 +1,211 @@
+/*
+ * Roomtemp PMDA
+ *
+ * Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "dsread.h"
+
+/*
+ * Roomtemp PMDA
+ *
+ * This PMDA exports the temperature from one or more sensors built using
+ * the DS2480 and DS1280 chipsets and MicroLAN technology from Dallas
+ * Semiconductor Corporation.
+ */
+
+/*
+ * Serial device
+ */
+static char *tty;
+
+/*
+ * list of instances
+ */
+
+static pmdaInstid *device = NULL;
+
+/*
+ * list of instance domains
+ */
+
+static pmdaIndom indomtab[] = {
+#define DEVICE 0
+ { DEVICE, 0, NULL },
+};
+
+typedef struct {
+ unsigned char sn[8];
+} sn_t;
+
+sn_t *sntab = NULL;
+
+/*
+ * All metrics supported in this PMDA - one table entry for each.
+ * The 4th field specifies the serial number of the instance domain
+ * for the metric, and must be either PM_INDOM_NULL (denoting a
+ * metric that only ever has a single value), or the serial number
+ * of one of the instance domains declared in the instance domain table
+ * (i.e. in indomtab, above).
+ */
+
+static pmdaMetric metrictab[] = {
+/* roomtemp.celsius */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_FLOAT, DEVICE, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* roomtemp.fahrenheit */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_FLOAT, DEVICE, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+roomtemp_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ char return_msg[128];
+ int numval = 0;
+
+ if (idp->cluster == 0) {
+ if (inst >= indomtab[DEVICE].it_numinst)
+ return PM_ERR_INST;
+ switch (idp->item) {
+ case 0: /* roomtemp.celsius */
+ case 1: /* roomtemp.fahrenheit */
+ if (!Aquire1WireNet(tty, return_msg)) {
+ fputs(return_msg, stderr);
+ exit(1);
+ }
+ if (ReadTemperature(sntab[inst].sn, &atom->f))
+ numval = 1;
+ Release1WireNet(return_msg);
+ if (idp->item == 1)
+ /* convert to fahrenheit */
+ atom->f = atom->f * 9 / 5 + 32;
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ else
+ return PM_ERR_PMID;
+
+ return numval;
+}
+
+/*
+ * Initialise the agent
+ */
+void
+roomtemp_init(pmdaInterface *dp)
+{
+ int i;
+ char return_msg[128];
+ unsigned char *p;
+
+ if (dp->status != 0)
+ return;
+
+ pmdaSetFetchCallBack(dp, roomtemp_fetchCallBack);
+
+ if (!Aquire1WireNet(tty, return_msg)) {
+ fputs(return_msg, stderr);
+ exit(1);
+ }
+ for (i = 0; ; i++) {
+ if ((p = nextsensor()) == NULL)
+ break;
+ if ((sntab = (sn_t *)realloc(sntab, (i+1) * sizeof(sn_t))) == NULL) {
+ __pmNoMem("roomtemp_init: realloc sntab", (i+1) * sizeof(sn_t), PM_FATAL_ERR);
+ }
+ if ((device = (pmdaInstid *)realloc(device, (i+1) * sizeof(pmdaInstid))) == NULL) {
+ __pmNoMem("roomtemp_init: realloc device", (i+1) * sizeof(pmdaInstid), PM_FATAL_ERR);
+ }
+ if ((device[i].i_name = (char *)malloc(17)) == NULL) {
+ __pmNoMem("roomtemp_init: malloc name", 17, PM_FATAL_ERR);
+ }
+ memcpy(sntab[i].sn, p, 8); /* SN for later fetch */
+ device[i].i_inst = i; /* internal name is ordinal number */
+ /* external name is SN in hex */
+ sprintf(device[i].i_name, "%02X%02X%02X%02X%02X%02X%02X%02X",
+ p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+ fprintf(stderr, "Found temp sensor SN %s\n", device[i].i_name);
+ }
+ Release1WireNet(return_msg);
+ indomtab[DEVICE].it_numinst = i;
+ indomtab[DEVICE].it_set = device;
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options] tty ...\n\n", pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ "\nExactly one of the following options may appear:\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ stderr);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int err = 0;
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "roomtemp" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, ROOMTEMP,
+ "roomtemp.log", mypath);
+
+ if (pmdaGetOpt(argc, argv, "D:d:i:l:pu:6:?", &dispatch, &err) != EOF)
+ err++;
+ if (err)
+ usage();
+ if (argc != optind+1)
+ usage();
+ tty = argv[optind];
+
+ pmdaOpenLog(&dispatch);
+ roomtemp_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/roomtemp/root b/src/pmdas/roomtemp/root
new file mode 100644
index 0000000..2035fb5
--- /dev/null
+++ b/src/pmdas/roomtemp/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { roomtemp }
+
+#include "pmns"
+
diff --git a/src/pmdas/rpm/GNUmakefile b/src/pmdas/rpm/GNUmakefile
new file mode 100644
index 0000000..29738bf
--- /dev/null
+++ b/src/pmdas/rpm/GNUmakefile
@@ -0,0 +1,67 @@
+#
+# Copyright (c) 2013, 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = rpm
+DOMAIN = RPM
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMDAINIT = $(IAM)_init
+
+HFILES = rpm.h timer.h
+CFILES = rpm.c timer.c
+
+SCRIPTS = Install Remove
+VERSION_SCRIPT = exports
+LSRCFILES = Install Remove pmns root help
+LDIRT = domain.h $(IAM).log $(VERSION_SCRIPT)
+
+LIB_FOR_RPM = -lrpm
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_RPM) $(LIB_FOR_PTHREADS)
+LCFLAGS = $(INVISIBILITY)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq ($(HAVE_RPMLIB),1)
+build-me: domain.h $(CMDTARGET) $(LIBTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 root pmns domain.h help $(PMDADIR)
+ $(INSTALL) -m 755 $(CMDTARGET) $(LIBTARGET) $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 migrate.conf $(PCP_VAR_DIR)/config/pmlogrewrite/rpm_migrate.conf
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+rpm.o: rpm.h
+rpm.o timer.o: timer.h
+rpm.o: $(VERSION_SCRIPT)
diff --git a/src/pmdas/rpm/Install b/src/pmdas/rpm/Install
new file mode 100644
index 0000000..8f11751
--- /dev/null
+++ b/src/pmdas/rpm/Install
@@ -0,0 +1,31 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the rpm PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rpm
+pmda_interface=5
+forced_restart=false
+
+dso_opt=true
+pipe_opt=true
+daemon_opt=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/rpm/Remove b/src/pmdas/rpm/Remove
new file mode 100644
index 0000000..73e423f
--- /dev/null
+++ b/src/pmdas/rpm/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the rpm PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rpm
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/rpm/help b/src/pmdas/rpm/help
new file mode 100644
index 0000000..2a0320a
--- /dev/null
+++ b/src/pmdas/rpm/help
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2013-2014 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# rpm PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ rpm.arch package architecture
+@ rpm.buildhost package build host
+@ rpm.buildtime package buildtime
+@ rpm.description package description
+@ rpm.epoch package install epoch
+@ rpm.group group of the package
+@ rpm.installtime package install time
+@ rpm.license package license
+@ rpm.packager entity responsible for packaging
+@ rpm.release release list of the package
+@ rpm.size size of the package in bytes
+@ rpm.sourcerpm package source rpm
+@ rpm.summary summary of the package
+@ rpm.url url of the package
+@ rpm.vendor package vendor
+@ rpm.version package version
+@ rpm.name package name
+
+@ rpm.refresh.count Cumulative count of rpmdb scans performed
+@ rpm.refresh.time.user Cumulative count of user mode scan time
+@ rpm.refresh.time.sys Cumulative count of kernel mode scan time
+@ rpm.refresh.time.elapsed Cumulative count of elapsed scan time
+@ rpm.datasize Space allocated for pmdarpms data segment (K)
+This metric returns the amount of memory in kilobytes allocated for the
+data segment of pmdarpm. This is handy for tracking memory utilization.
+
+@ rpm.total.count Count of packages returned in last rpmdb scan
+@ rpm.total.bytes Size of all packages from the last rpmdb scan
diff --git a/src/pmdas/rpm/migrate.conf b/src/pmdas/rpm/migrate.conf
new file mode 100644
index 0000000..1eb4086
--- /dev/null
+++ b/src/pmdas/rpm/migrate.conf
@@ -0,0 +1,8 @@
+# Copyright 2014 Red Hat.
+#
+# pmlogrewrite configuration for migrating archives containing old
+# 32 bit rpm values to the 64 bit variants, matching up
+# with changes in the metadata supplied by the PMDA.
+#
+
+metric 123.1.10 { type -> U64 }
diff --git a/src/pmdas/rpm/pmns b/src/pmdas/rpm/pmns
new file mode 100644
index 0000000..84aa5b3
--- /dev/null
+++ b/src/pmdas/rpm/pmns
@@ -0,0 +1,38 @@
+rpm {
+ arch RPM:1:0
+ buildhost RPM:1:1
+ buildtime RPM:1:2
+ description RPM:1:3
+ epoch RPM:1:4
+ group RPM:1:5
+ installtime RPM:1:6
+ license RPM:1:7
+ packager RPM:1:8
+ release RPM:1:9
+ size RPM:1:10
+ sourcerpm RPM:1:11
+ summary RPM:1:12
+ url RPM:1:13
+ vendor RPM:1:14
+ version RPM:1:15
+ name RPM:1:16
+ refresh
+ datasize RPM:0:4
+ total
+}
+
+rpm.refresh {
+ count RPM:0:0
+ time
+}
+
+rpm.refresh.time {
+ user RPM:0:1
+ sys RPM:0:2
+ elapsed RPM:0:3
+}
+
+rpm.total {
+ count RPM:2:0
+ bytes RPM:2:1
+}
diff --git a/src/pmdas/rpm/root b/src/pmdas/rpm/root
new file mode 100644
index 0000000..fb869d5
--- /dev/null
+++ b/src/pmdas/rpm/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { rpm }
+
+#include "pmns"
+
diff --git a/src/pmdas/rpm/rpm.c b/src/pmdas/rpm/rpm.c
new file mode 100644
index 0000000..348d9c1
--- /dev/null
+++ b/src/pmdas/rpm/rpm.c
@@ -0,0 +1,704 @@
+/*
+ * RPM Package Manager PMDA
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/stat.h>
+#include <pthread.h>
+#include <search.h>
+#include <sys/inotify.h>
+#include <rpm/rpmlib.h>
+#include <rpm/header.h>
+#include <rpm/rpmts.h>
+#include <rpm/rpmdb.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include "timer.h"
+#include "rpm.h"
+
+static pmdaIndom indomtab[] = {
+ {RPM_INDOM, 0, NULL},
+ {CACHE_INDOM, 1, NULL},
+ {STRINGS_INDOM, 2, NULL},
+};
+
+static pmdaMetric metrictab[] = {
+ /* PMDA internals metrics - timing, count of refreshes, memory */
+ { NULL, { PMDA_PMID(0, REFRESH_COUNT_ID), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_USER_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_KERNEL_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, REFRESH_TIME_ELAPSED_ID), PM_TYPE_DOUBLE,
+ PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(0, DATASIZE_ID), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0)}},
+
+ /* rpm package metrics */
+ { NULL, { PMDA_PMID(1, ARCH_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, BUILDHOST_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, BUILDTIME_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(1, DESCRIPTION_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, EPOCH_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, GROUP_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, INSTALLTIME_ID), PM_TYPE_U32,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0)}},
+ { NULL, { PMDA_PMID(1, LICENSE_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, PACKAGER_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, RELEASE_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, SIZE_ID), PM_TYPE_U64,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+ { NULL, { PMDA_PMID(1, SOURCERPM_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, SUMMARY_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, URL_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, VENDOR_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, VERSION_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+ { NULL, { PMDA_PMID(1, NAME_ID), PM_TYPE_STRING,
+ RPM_INDOM, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0)}},
+
+ /* cumulative rpm metrics - total package count, size */
+ { NULL, { PMDA_PMID(2, TOTAL_COUNT_ID), PM_TYPE_U32,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)}},
+ { NULL, { PMDA_PMID(2, TOTAL_BYTES_ID), PM_TYPE_U64,
+ PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0)}},
+};
+
+static pthread_t inotify_thread; /* runs all librpm queries, esp. when the rpmdb changes */
+static unsigned long long numrefresh; /* updated by background thread, protected by indom_mutex */
+static unsigned long long packagesize; /* sum of sizes of all packages */
+static unsigned long numpackages; /* total count for all packages */
+
+static pthread_mutex_t indom_mutex;
+
+static int isDSO = 1; /* invoked as shlib or daemon */
+static char *username;
+static char *dbpath = "/var/lib/rpm/Packages";
+
+static pmInDom
+INDOM(int serial)
+{
+ return indomtab[serial].it_indom;
+}
+
+static char *
+dict_lookup(int index)
+{
+ char *value;
+ pmInDom dict = INDOM(STRINGS_INDOM);
+
+ if (pmdaCacheLookup(dict, index, &value, NULL) == PMDA_CACHE_ACTIVE)
+ return value;
+ return "";
+}
+
+static int
+dict_insert(const char *string)
+{
+ pmInDom dict = INDOM(STRINGS_INDOM);
+ if (!string)
+ string = "";
+ return pmdaCacheStore(dict, PMDA_CACHE_ADD, string, NULL);
+}
+
+static int
+rpm_fetch_pmda(int item, pmAtomValue *atom)
+{
+ int sts = PMDA_FETCH_STATIC;
+ unsigned long datasize;
+
+ switch (item) {
+ case REFRESH_COUNT_ID: /* rpm.refresh.count */
+ atom->ull = numrefresh; /* XXX: unlocked */
+ break;
+ case REFRESH_TIME_USER_ID: /* rpm.refresh.time.user */
+ atom->d = get_user_timer();
+ break;
+ case REFRESH_TIME_KERNEL_ID: /* rpm.refresh.time.kernel */
+ atom->d = get_kernel_timer();
+ break;
+ case REFRESH_TIME_ELAPSED_ID: /* rpm.refresh.time.elapsed */
+ atom->d = get_elapsed_timer();
+ break;
+ case DATASIZE_ID: /* rpm.datasize */
+ __pmProcessDataSize(&datasize);
+ atom->ul = datasize;
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetch_package(int item, unsigned int inst, pmAtomValue *atom)
+{
+ package *p;
+ char *name;
+ int sts;
+
+ sts = pmdaCacheLookup(INDOM(RPM_INDOM), inst, &name, (void **)&p);
+ if (sts < 0 || sts == PMDA_CACHE_INACTIVE)
+ return PM_ERR_INST;
+
+ sts = PMDA_FETCH_STATIC;
+ switch (item) {
+ case ARCH_ID:
+ atom->cp = dict_lookup(p->values.arch);
+ break;
+ case BUILDHOST_ID:
+ atom->cp = dict_lookup(p->values.buildhost);
+ break;
+ case BUILDTIME_ID:
+ atom->ul = p->values.buildtime;
+ break;
+ case DESCRIPTION_ID:
+ atom->cp = dict_lookup(p->values.description);
+ break;
+ case EPOCH_ID:
+ atom->ul = p->values.epoch;
+ break;
+ case GROUP_ID:
+ atom->cp = dict_lookup(p->values.group);
+ break;
+ case INSTALLTIME_ID:
+ atom->ul = p->values.installtime;
+ break;
+ case LICENSE_ID:
+ atom->cp = dict_lookup(p->values.license);
+ break;
+ case PACKAGER_ID:
+ atom->cp = dict_lookup(p->values.packager);
+ break;
+ case RELEASE_ID:
+ atom->cp = dict_lookup(p->values.release);
+ break;
+ case SIZE_ID:
+ atom->ull = p->values.longsize;
+ break;
+ case SOURCERPM_ID:
+ atom->cp = dict_lookup(p->values.sourcerpm);
+ break;
+ case SUMMARY_ID:
+ atom->cp = dict_lookup(p->values.summary);
+ break;
+ case URL_ID:
+ atom->cp = dict_lookup(p->values.url);
+ break;
+ case VENDOR_ID:
+ atom->cp = dict_lookup(p->values.vendor);
+ break;
+ case VERSION_ID:
+ atom->cp = dict_lookup(p->values.version);
+ break;
+ case NAME_ID:
+ atom->cp = dict_lookup(p->values.name);
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetch_totals(int item, pmAtomValue *atom)
+{
+ int sts = PMDA_FETCH_STATIC;
+
+ switch (item) {
+ case TOTAL_COUNT_ID: /* rpm.total.count */
+ atom->ul = numpackages;
+ break;
+ case TOTAL_BYTES_ID: /* rpm.total.bytes */
+ atom->ull = packagesize;
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ return sts;
+}
+
+static int
+rpm_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *) &mdesc->m_desc.pmid;
+ int sts;
+
+ pthread_mutex_lock(&indom_mutex);
+ switch (idp->cluster) {
+ case 0:
+ if (inst != PM_IN_NULL)
+ sts = PM_ERR_INST;
+ else
+ sts = rpm_fetch_pmda(idp->item, atom);
+ break;
+ case 1:
+ sts = rpm_fetch_package(idp->item, inst, atom);
+ break;
+ case 2:
+ sts = rpm_fetch_totals(idp->item, atom);
+ break;
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ pthread_mutex_unlock(&indom_mutex);
+ return sts;
+}
+
+/*
+ * Sync the active rpm package instances with the reference database
+ * maintained by the background threads.
+ */
+static void
+rpm_indom_refresh(unsigned long long refresh)
+{
+ pmInDom rpmdb, cache;
+ package *p;
+ char *name;
+ int sts;
+
+ rpmdb = INDOM(RPM_INDOM);
+ cache = INDOM(CACHE_INDOM);
+
+ pmdaCacheOp(rpmdb, PMDA_CACHE_INACTIVE);
+
+ pthread_mutex_lock(&indom_mutex);
+ for (pmdaCacheOp(cache, PMDA_CACHE_WALK_REWIND);;) {
+ if ((sts = pmdaCacheOp(cache, PMDA_CACHE_WALK_NEXT)) < 0)
+ break;
+ if ((pmdaCacheLookup(cache, sts, &name, (void **)&p) < 0) || !p)
+ continue;
+ if (p->refresh < refresh)
+ continue;
+ pmdaCacheStore(rpmdb, PMDA_CACHE_ADD, name, (void *)p);
+ }
+ pthread_mutex_unlock(&indom_mutex);
+}
+
+/*
+ * Sync up with the (initial) indom loading thread
+ */
+static int
+notready(pmdaExt *pmda)
+{
+ unsigned iterations = 0;
+
+ __pmSendError(pmda->e_outfd, FROM_ANON, PM_ERR_PMDANOTREADY);
+
+ /*
+ * We need to wait for at least the initial rpm_update_cache()
+ * cycle to have finished. We could use a pthread condition
+ * variable, except that those have timing constraints on
+ * wait-precede-signal that we cannot enforce. So we poll.
+ */
+ while (1) {
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh > 0)
+ break;
+
+ if (iterations++ > 30) { /* Complain every 30 seconds. */
+ __pmNotifyErr(LOG_WARNING, "notready waited too long");
+ iterations = 0; /* XXX: or exit? */
+ }
+ sleep(1);
+ }
+
+ return PM_ERR_PMDAREADY;
+}
+
+/*
+ * Called once for each pmFetch(3) operation
+ */
+static int
+rpm_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh == 0)
+ return notready(pmda);
+ rpm_indom_refresh(refresh);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * Called once for each pmGetInDom(3) operation
+ */
+static int
+rpm_instance(pmInDom id, int i, char *name, __pmInResult **in, pmdaExt *pmda)
+{
+ unsigned long long refresh;
+
+ pthread_mutex_lock(&indom_mutex);
+ refresh = numrefresh;
+ pthread_mutex_unlock(&indom_mutex);
+
+ if (refresh == 0)
+ return notready(pmda);
+ rpm_indom_refresh(refresh);
+ return pmdaInstance(id, i, name, in, pmda);
+}
+
+static const char *
+rpm_extract_string(rpmtd td, Header h, int tag)
+{
+ headerGet(h, tag, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ /*
+ * RPM_STRING_ARRAY_TYPE being the alternative, e.g. filenames
+ * (which we never expect to see, for the metrics we export).
+ */
+ if (td->type == RPM_STRING_ARRAY_TYPE)
+ __pmNotifyErr(LOG_ERR,
+ "rpm_extract_string: unexpected string array: %d", tag);
+
+ return rpmtdGetString(td);
+}
+
+static __uint64_t
+rpm_extract_value(rpmtd td, Header h, int tag)
+{
+ __uint64_t value;
+
+ headerGet(h, tag, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ switch (td->type) {
+ case RPM_INT8_TYPE:
+ value = ((char *)(td->data))[0];
+ break;
+ case RPM_INT16_TYPE:
+ value = ((short *)(td->data))[0];
+ break;
+ case RPM_INT32_TYPE:
+ value = ((int *)(td->data))[0];
+ break;
+ case RPM_INT64_TYPE:
+ value = ((long long *)(td->data))[0];
+ break;
+ default:
+ value = 0;
+ break;
+ }
+ return value;
+}
+
+static void
+rpm_extract_metadata(const char *name, rpmtd td, Header h, metadata *m)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_INFO, "updating package %s metadata", name);
+
+ m->name = dict_insert(rpm_extract_string(td, h, RPMTAG_NAME));
+ m->arch = dict_insert(rpm_extract_string(td, h, RPMTAG_ARCH));
+ m->buildhost = dict_insert(rpm_extract_string(td, h, RPMTAG_BUILDHOST));
+ m->buildtime = rpm_extract_value(td, h, RPMTAG_BUILDTIME);
+ m->description = dict_insert(rpm_extract_string(td, h, RPMTAG_DESCRIPTION));
+ m->epoch = rpm_extract_value(td, h, RPMTAG_EPOCH);
+ m->group = dict_insert(rpm_extract_string(td, h, RPMTAG_GROUP));
+ m->installtime = rpm_extract_value(td, h, RPMTAG_INSTALLTIME);
+ m->license = dict_insert(rpm_extract_string(td, h, RPMTAG_LICENSE));
+ m->packager = dict_insert(rpm_extract_string(td, h, RPMTAG_PACKAGER));
+ m->release = dict_insert(rpm_extract_string(td, h, RPMTAG_RELEASE));
+ m->longsize = rpm_extract_value(td, h, RPMTAG_LONGSIZE);
+ m->sourcerpm = dict_insert(rpm_extract_string(td, h, RPMTAG_SOURCERPM));
+ m->summary = dict_insert(rpm_extract_string(td, h, RPMTAG_SUMMARY));
+ m->url = dict_insert(rpm_extract_string(td, h, RPMTAG_URL));
+ m->vendor = dict_insert(rpm_extract_string(td, h, RPMTAG_VENDOR));
+ m->version = dict_insert(rpm_extract_string(td, h, RPMTAG_VERSION));
+}
+
+/*
+ * Refresh the RPM package names and values in the cache.
+ * This is to be only ever invoked from a single thread.
+ */
+void *
+rpm_update_cache(void *ptr)
+{
+ rpmtd td;
+ rpmts ts;
+ Header h;
+ rpmdbMatchIterator mi;
+ unsigned long long refresh;
+ unsigned long long totalsize = 0;
+ unsigned long packages = 0;
+ static int rpmReadConfigFiles_p = 0;
+
+ pthread_mutex_lock(&indom_mutex);
+ start_timing();
+ refresh = numrefresh + 1; /* current iteration */
+ pthread_mutex_unlock(&indom_mutex);
+
+ /*
+ * It appears unnecessary to check the return value from these functions,
+ * since the only (?) thing that can fail is memory allocation, which
+ * rpmlib internally maps to an exit(1).
+ */
+ td = rpmtdNew();
+ ts = rpmtsCreate();
+
+ if (rpmReadConfigFiles_p == 0) {
+ int sts = rpmReadConfigFiles(NULL, NULL);
+ if (sts == -1)
+ __pmNotifyErr(LOG_WARNING, "rpm_update_cache: rpmReadConfigFiles failed: %d", sts);
+ rpmReadConfigFiles_p = 1;
+ }
+
+ /* Iterate through the entire list of RPMs, extract names and values */
+ mi = rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0);
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ headerGet(h, RPMTAG_NEVRA, td, HEADERGET_EXT | HEADERGET_MINMEM);
+ const char *name = rpmtdGetString(td);
+ metadata meta;
+ package *pp = NULL;
+ int sts, err = 0;
+
+ /* extract an on-stack copy of the package metadata, may do I/O */
+ rpm_extract_metadata(name, td, h, &meta);
+
+ /* update cumulative counts */
+ totalsize += meta.longsize;
+ packages++;
+
+ /* we now have our data and cannot need more I/O; lock and load */
+ pthread_mutex_lock(&indom_mutex);
+ sts = pmdaCacheLookupName(INDOM(CACHE_INDOM), name, NULL, (void **)&pp);
+ if (sts == PM_ERR_INST || (sts >= 0 && pp == NULL)) {
+ /* allocate space for new package entry for the cache */
+ if ((pp = calloc(1, sizeof(package))) == NULL)
+ err = 1;
+ } else if (sts < 0) {
+ err = 1;
+ }
+
+ if (!err) {
+ /* update values in cache entry for this package (locked) */
+ pp->refresh = refresh;
+ memcpy(&pp->values, &meta, sizeof(metadata));
+ pmdaCacheStore(INDOM(CACHE_INDOM), PMDA_CACHE_ADD, name, (void *)pp);
+ } else {
+ /* ensure the logfile isn't spammed over and over */
+ static int cache_err = 0;
+ if (cache_err++ < 10) {
+ fprintf(stderr, "rpm_refresh_cache: "
+ "pmdaCacheLookupName(%s, %s, ... %p) failed: %s\n",
+ pmInDomStr(INDOM(CACHE_INDOM)), name, pp, pmErrStr(sts));
+ }
+ }
+ pthread_mutex_unlock(&indom_mutex);
+ }
+
+ rpmdbFreeIterator(mi);
+ rpmtsFree(ts);
+
+ pthread_mutex_lock(&indom_mutex);
+ stop_timing();
+ numrefresh = refresh; /* current iteration complete */
+ packagesize = totalsize;
+ numpackages = packages;
+ pthread_mutex_unlock(&indom_mutex);
+ return NULL;
+}
+
+/*
+ * Notice when the rpm database changes and reload the instances.
+ */
+void *
+rpm_inotify(void *ptr)
+{
+ char buffer[EVENT_BUF_LEN]; /* space for lots of events */
+ int fd;
+ int sts;
+
+ /* Update it the first time. */
+ rpm_update_cache(ptr);
+
+ /*
+ * By this time, the global refresh counter should be >= 1, even
+ * if some rpm* or other api failure occurred.
+ */
+ fd = inotify_init();
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "rpm_inotify: failed to create inotify fd");
+ return NULL;
+ }
+
+ sts = inotify_add_watch(fd, dbpath, IN_CLOSE_WRITE);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "rpm_inotify: failed to inotify-watch dbpath %s", dbpath);
+ close(fd);
+ return NULL;
+ }
+
+ while (1) {
+ int read_count;
+
+ /* Wait for changes in the rpm database */
+ read_count = read(fd, buffer, EVENT_BUF_LEN);
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_INFO, "rpm_inotify: read_count=%d", read_count);
+
+ /*
+ * No need to check the contents of the buffer; having
+ * received an event at all indicates need to refresh.
+ */
+ if (read_count <= 0) {
+ __pmNotifyErr(LOG_WARNING, "rpm_inotify: read_count=%d", read_count);
+ continue;
+ }
+
+ rpm_update_cache(ptr);
+
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_INFO, "rpm_inotify: refresh done");
+ }
+
+ /* NOTREACHED */
+ return NULL;
+}
+
+/*
+ * Initialize the daemon/.so agent.
+ */
+
+void
+__PMDA_INIT_CALL
+rpm_init(pmdaInterface * dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ char helppath[MAXPATHLEN];
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "rpm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_5, "rpm DSO", helppath);
+ }
+ else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.any.fetch = rpm_fetch;
+ dp->version.any.instance = rpm_instance;
+ pmdaSetFetchCallBack(dp, rpm_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab) / sizeof(indomtab[0]),
+ metrictab, sizeof(metrictab) / sizeof(metrictab[0]));
+
+ pmdaCacheOp(INDOM(STRINGS_INDOM), PMDA_CACHE_STRINGS);
+
+ pthread_mutex_init(&indom_mutex, NULL);
+ /* Monitor changes to the rpm database */
+ pthread_create(&inotify_thread, NULL, rpm_inotify, NULL);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fprintf(stderr, "Options:\n"
+ " -C parse the RPM database, and exit\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -r path path to directory containing RPM database (default %s)\n"
+ " -U username user account to run under (default \"pcp\")\n"
+ "\nExactly one of the following options may appear:\n"
+ " -i port expect PMCD to connect on given inet port (number or name)\n"
+ " -p expect PMCD to supply stdin/stdout (pipe)\n"
+ " -u socket expect PMCD to connect on given unix domain socket\n"
+ " -6 port expect PMCD to connect on given ipv6 port (number or name)\n",
+ dbpath);
+ exit(1);
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+
+int
+main(int argc, char **argv)
+{
+ int c, err = 0;
+ int Cflag = 0, sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char helppath[MAXPATHLEN];
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmProcessDataSize(NULL);
+ __pmGetUsername(&username);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "rpm" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmProgname, RPM,
+ "rpm.log", helppath);
+
+ while ((c =
+ pmdaGetOpt(argc, argv, "CD:d:i:l:pr:u:6:U:?", &dispatch,
+ &err)) != EOF) {
+ switch (c) {
+ case 'C':
+ Cflag++;
+ break;
+ case 'U':
+ username = optarg;
+ break;
+ case 'r':
+ dbpath = optarg;
+ break;
+ default:
+ err++;
+ }
+ }
+ if (err)
+ usage();
+
+ pmdaOpenLog(&dispatch);
+ rpm_init(&dispatch);
+ if (Cflag) {
+ rpm_update_cache(NULL);
+ exit(0);
+ }
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/rpm/rpm.h b/src/pmdas/rpm/rpm.h
new file mode 100644
index 0000000..117f197
--- /dev/null
+++ b/src/pmdas/rpm/rpm.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef RPM_H
+#define RPM_H
+
+/*
+ * Instance domain handling
+ */
+enum {
+ RPM_INDOM = 0, /* active RPM packages */
+ CACHE_INDOM = 1, /* pseudo-indom for refreshing */
+ STRINGS_INDOM = 2, /* pseudo-indom for string sharing */
+};
+
+/*
+ * Metrics describing internals of pmdarpm operation (Cluster 0)
+ */
+enum {
+ REFRESH_COUNT_ID = 0,
+ REFRESH_TIME_USER_ID = 1,
+ REFRESH_TIME_KERNEL_ID = 2,
+ REFRESH_TIME_ELAPSED_ID = 3,
+ DATASIZE_ID = 4,
+};
+
+/*
+ * List of metrics corresponding to rpm --querytags (Cluster 1)
+ */
+enum {
+ ARCH_ID = 0,
+ BUILDHOST_ID = 1,
+ BUILDTIME_ID = 2,
+ DESCRIPTION_ID = 3,
+ EPOCH_ID = 4,
+ GROUP_ID = 5,
+ INSTALLTIME_ID = 6,
+ LICENSE_ID = 7,
+ PACKAGER_ID = 8,
+ RELEASE_ID = 9,
+ SIZE_ID = 10,
+ SOURCERPM_ID = 11,
+ SUMMARY_ID = 12,
+ URL_ID = 13,
+ VENDOR_ID = 14,
+ VERSION_ID = 15,
+ NAME_ID = 16,
+};
+
+/*
+ * Metrics describing cumulative pmdarpm totals (Cluster 2)
+ */
+enum {
+ TOTAL_COUNT_ID = 0,
+ TOTAL_BYTES_ID = 1,
+};
+
+/*
+ * Package metadata stored for each installed RPM
+ *
+ * A "refresh" count is stored to indicate whether this entry
+ * is out of date with respect to the global "refresh" count.
+ * If its value is greater-than-or-equal-to a global refresh
+ * count, the entry is current - otherwise it is out-of-date
+ * and must not be reported in the active instance domain.
+ *
+ * Note that many of the structure entries (below) are string
+ * dictionary keys (int), allowing sharing of the memory used
+ * to hold the values. It also further reduces the footprint
+ * on 64 bit systems, instead of storing 64bit pointers.
+ */
+
+typedef struct metadata {
+ int name;
+ int arch;
+ int buildhost;
+ int buildtime;
+ int description;
+ int epoch;
+ int group;
+ int installtime;
+ int license;
+ int packager;
+ int release;
+ __uint64_t longsize;
+ int sourcerpm;
+ int summary;
+ int url;
+ int vendor;
+ int version;
+} metadata;
+
+typedef struct package {
+ __uint64_t refresh;
+ metadata values;
+} package;
+
+#define EVENT_SIZE ( sizeof (struct inotify_event) )
+#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
+
+#endif /* RPM_H */
diff --git a/src/pmdas/rpm/timer.c b/src/pmdas/rpm/timer.c
new file mode 100644
index 0000000..89cd8aa
--- /dev/null
+++ b/src/pmdas/rpm/timer.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pmapi.h>
+#include <impl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static struct rusage start_rsrc, final_rsrc;
+static struct timeval start_time, final_time;
+static double user, kernel, elapsed;
+
+double get_user_timer() { return user; }
+double get_kernel_timer() { return kernel; }
+double get_elapsed_timer() { return elapsed; }
+
+void
+start_timing(void)
+{
+ getrusage(RUSAGE_SELF, &start_rsrc);
+ gettimeofday(&start_time, NULL);
+}
+
+void
+stop_timing(void)
+{
+ gettimeofday(&final_time, NULL);
+ getrusage(RUSAGE_SELF, &final_rsrc);
+
+ /* accumulate the totals as we go */
+ user += __pmtimevalSub(&final_rsrc.ru_utime, &start_rsrc.ru_utime);
+ kernel += __pmtimevalSub(&final_rsrc.ru_stime, &start_rsrc.ru_stime);
+ elapsed += __pmtimevalSub(&final_time, &start_time);
+}
diff --git a/src/pmdas/rpm/timer.h b/src/pmdas/rpm/timer.h
new file mode 100644
index 0000000..529ca33
--- /dev/null
+++ b/src/pmdas/rpm/timer.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TIMER_H
+#define TIMER_H
+
+extern void start_timing(void);
+extern void stop_timing(void);
+
+extern double get_user_timer(void);
+extern double get_kernel_timer(void);
+extern double get_elapsed_timer(void);
+
+#endif /* TIMER_H */
diff --git a/src/pmdas/rsyslog/GNUmakefile b/src/pmdas/rsyslog/GNUmakefile
new file mode 100644
index 0000000..db3914a
--- /dev/null
+++ b/src/pmdas/rsyslog/GNUmakefile
@@ -0,0 +1,48 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = rsyslog
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/rsyslog/Install b/src/pmdas/rsyslog/Install
new file mode 100755
index 0000000..33d2ac8
--- /dev/null
+++ b/src/pmdas/rsyslog/Install
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the rsyslog PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rsyslog
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+statsfile="$PCP_LOG_DIR/rsyslog/stats"
+statsdir=`dirname "$statsfile"`
+
+if ! test -d "$statsdir"; then
+ echo "Creating rsyslog statistics file directory: $statsdir"
+ mkdir "$statsdir"
+ [ $? -eq 0 ] || exit 1
+fi
+
+if ! test -p "$statsfile"; then
+ echo "Creating rsyslog statistics file: $statsfile"
+ mkfifo "$statsfile"
+ [ $? -eq 0 ] || exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/rsyslog/Remove b/src/pmdas/rsyslog/Remove
new file mode 100755
index 0000000..d460669
--- /dev/null
+++ b/src/pmdas/rsyslog/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the rsyslog PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=rsyslog
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/rsyslog/pmdarsyslog.pl b/src/pmdas/rsyslog/pmdarsyslog.pl
new file mode 100644
index 0000000..e3972f9
--- /dev/null
+++ b/src/pmdas/rsyslog/pmdarsyslog.pl
@@ -0,0 +1,249 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my $pmda = PCP::PMDA->new('rsyslog', 107);
+my $statsfile = pmda_config('PCP_LOG_DIR') . '/rsyslog/stats';
+my ($es_connfail, $es_submits, $es_failed, $es_success) = (0,0,0,0);
+my ($ux_submitted, $ux_discarded, $ux_ratelimiters) = (0,0,0);
+my ($interval, $lasttime) = (0,0);
+
+my $queue_indom = 0;
+my @queue_insts = ();
+use vars qw(%queue_ids %queue_values);
+
+# .* rsyslogd-pstats:
+# imuxsock: submitted=37 ratelimit.discarded=0 ratelimit.numratelimiters=22
+# elasticsearch: connfail=0 submits=0 failed=0 success=0
+# [main Q]: size=1 enqueued=1436 full=0 maxqsize=3
+
+sub rsyslog_parser
+{
+ ( undef, $_ ) = @_;
+
+ #$pmda->log("rsyslog_parser got line: $_");
+ if (m|rsyslogd-pstats:|) {
+ my $timenow = time;
+ if ($lasttime != 0) {
+ if ($timenow > $lasttime) {
+ $interval = $timenow - $lasttime;
+ $lasttime = $timenow;
+ }
+ } else {
+ $lasttime = $timenow;
+ }
+ }
+ if (m|imuxsock: submitted=(\d+) ratelimit.discarded=(\d+) ratelimit.numratelimiters=(\d+)|) {
+ ($ux_submitted, $ux_discarded, $ux_ratelimiters) = ($1,$2,$3);
+ }
+ elsif (m|elasticsearch: connfail=(\d+) submits=(\d+) failed=(\d+) success=(\d+)|) {
+ ($es_connfail, $es_submits, $es_failed, $es_success) = ($1,$2,$3,$4);
+ }
+ elsif (m|stats: (.+): size=(\d+) enqueued=(\d+) full=(\d+) maxqsize=(\d+)|) {
+ my ($qname, $qid) = ($1, undef);
+
+ if (!defined($queue_ids{$qname})) {
+ $qid = @queue_insts / 2;
+ $queue_ids{$qname} = $qid;
+ push @queue_insts, ($qid, $qname);
+ $pmda->replace_indom($queue_indom, \@queue_insts);
+ }
+ $queue_values{$qname} = [ $2, $3, $4, $5 ];
+ }
+}
+
+sub rsyslog_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ #$pmda->log("rsyslog_fetch_callback for PMID: $cluster.$item ($inst)");
+
+ return (PM_ERR_AGAIN,0) unless ($interval != 0);
+
+ if ($cluster == 0) {
+ return (PM_ERR_INST, 0) unless ($inst == PM_IN_NULL);
+ if ($item == 0) { return ($interval, 1); }
+ if ($item == 1) { return ($ux_submitted, 1); }
+ if ($item == 2) { return ($ux_discarded, 1); }
+ if ($item == 3) { return ($ux_ratelimiters, 1); }
+ if ($item == 8) { return ($es_connfail, 1); }
+ if ($item == 9) { return ($es_submits, 1); }
+ if ($item == 10){ return ($es_failed, 1); }
+ if ($item == 11){ return ($es_success, 1); }
+ }
+ elsif ($cluster == 1) { # queues
+ return (PM_ERR_INST, 0) unless ($inst != PM_IN_NULL);
+ return (PM_ERR_INST, 0) unless ($inst <= @queue_insts);
+ my $qname = $queue_insts[$inst * 2 + 1];
+ my $qvref = $queue_values{$qname};
+ my @qvals;
+
+ return (PM_ERR_INST, 0) unless defined ($qvref);
+ @qvals = @$qvref;
+
+ if ($item == 0) { return ($qvals[0], 1); }
+ if ($item == 1) { return ($qvals[1], 1); }
+ if ($item == 2) { return ($qvals[2], 1); }
+ if ($item == 3) { return ($qvals[3], 1); }
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+die "Cannot find a valid rsyslog statistics named pipe\n" unless -p $statsfile;
+
+$pmda->connect_pmcd;
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_SEC,0), 'rsyslog.interval',
+ 'Time interval observed between samples', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.imuxsock.submitted',
+ 'Cumulative count of unix domain socket input messages queued',
+ "Cumulative count of messages successfully queued to the rsyslog\n" .
+ "main message queueing core that arrived on unix domain sockets.");
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.imuxsock.discarded',
+ 'Count of unix domain socket messages discarded due to rate limiting',
+ "Cumulative count of messages that are were discarded due to their\n" .
+ "priority being at or below rate-limit-severity and their sending\n" .
+ "process being deemed to be sending messages too quickly (refer to\n" .
+ "parameters ratelimitburst, ratelimitinterval and ratelimitseverity");
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,0,0,0,0), 'rsyslog.imuxsock.numratelimiters',
+ 'Count of messages received that could be subject to rate limiting',
+ "Cumulative count of messages that rsyslog received and performed a\n" .
+ "credentials (PID) lookup for subsequent rate limiting decisions.\n" .
+ "The message would have to be at rate-limit-severity or lower, with\n" .
+ "rate limiting enabled, in order for this count to be incremented.");
+$pmda->add_metric(pmda_pmid(0,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.elasticsearch.connfail',
+ 'Count of failed connections while attempting to send events', '');
+$pmda->add_metric(pmda_pmid(0,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.elasticsearch.submits',
+ 'Count of valid submissions of events to elasticsearch indexer', '');
+$pmda->add_metric(pmda_pmid(0,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.elasticsearch.failed',
+ 'Count of failed attempts to send events to elasticsearch',
+ 'This count is often a good indicator of malformed JSON messages');
+$pmda->add_metric(pmda_pmid(0,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.elasticsearch.success',
+ 'Count of successfully acknowledged events from elasticsearch', '');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U64, $queue_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'rsyslog.queues.size',
+ 'Current queue depth for each rsyslog queue',
+ "As messages arrive they are enqueued to the main message queue\n" .
+ "(for example) -this counter is incremented for each such message.");
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U64, $queue_indom, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.queues.enqueued',
+ 'Cumulative count of nessages enqueued to individual queues',
+ "As messages arrive they are added to the main message processing\n" .
+ "queue, either individually or in batches in the case of messages\n" .
+ "arriving on the network.");
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_U64, $queue_indom, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.queues.full',
+ 'Cumulative count of message arrivals with a full queue',
+ "When messages are enqueued, a check is first made to ensure the\n" .
+ "queue is not full. If it is, this counter is incremented. The\n" .
+ "full-queue-handling logic will wait for a configurable time for\n" .
+ "the queue congestion to ease, failing which the message will be\n" .
+ "discarded. Worth keeping an eye on this metric, as it indicates\n" .
+ "rsyslog is not able to process messages quickly enough given the\n" .
+ "current arrival rate.");
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_U64, $queue_indom, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'rsyslog.queues.maxsize',
+ 'Maximum depth reached by an individual queue',
+ "When messages arrive (for example) they are enqueued to the main\n" .
+ "message queue - if the queue length on arrival is now greater than\n" .
+ "ever before observed, we set this value to the current queue size");
+
+$pmda->add_indom($queue_indom, \@queue_insts,
+ 'Instance domain exporting each rsyslog queue', '');
+
+$pmda->add_tail($statsfile, \&rsyslog_parser, 0);
+$pmda->set_fetch_callback(\&rsyslog_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdarsyslog - rsyslog (reliable and extended syslog) PMDA
+
+=head1 DESCRIPTION
+
+B<pmdarsyslog> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from the rsyslogd(8) server.
+Further details about rsyslog can be found at http://www.rsyslog.com/.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the rsyslog performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/rsyslog
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/rsyslog
+ # ./Remove
+
+B<pmdarsyslog> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+In order to use this agent, rsyslog stats gathering must be enabled.
+This is done by adding the lines:
+
+ $ModLoad impstats
+ $PStatsInterval 5 # log every 5 seconds
+ syslog.info |/var/log/pcp/rsyslog/stats
+
+to your rsyslog.conf(5) configuration file after installing the PMDA.
+Take care to ensure the syslog.info messages do not get logged in any
+other file, as this could unexpectedly fill your filesystem. Syntax
+useful for this is syslog.!=info for explicitly excluding these.
+
+=head1 FILES
+
+=over
+
+=item /var/log/pcp/rsyslog/stats
+
+named pipe containing statistics exported from rsyslog,
+usually created by the PMDA Install script.
+
+=item $PCP_PMDAS_DIR/rsyslog/Install
+
+installation script for the B<pmdarsyslog> agent
+
+=item $PCP_PMDAS_DIR/rsyslog/Remove
+
+undo installation script for the B<pmdarsyslog> agent
+
+=item $PCP_LOG_DIR/pmcd/rsyslog.log
+
+default log file for error messages from B<pmdarsyslog>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), rsyslog.conf(5), rsyslogd(8).
diff --git a/src/pmdas/samba/GNUmakefile b/src/pmdas/samba/GNUmakefile
new file mode 100644
index 0000000..e5602e2
--- /dev/null
+++ b/src/pmdas/samba/GNUmakefile
@@ -0,0 +1,52 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = samba
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+else
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/samba/Install b/src/pmdas/samba/Install
new file mode 100755
index 0000000..8a1aa7f
--- /dev/null
+++ b/src/pmdas/samba/Install
@@ -0,0 +1,43 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Samba PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=samba
+perl_opt=true
+daemon_opt=false
+
+if ! test -x /usr/sbin/smbd; then
+ echo "Samba \"smbd\" daemon is not installed" && exit 1
+fi
+/usr/sbin/smbd -b | egrep 'WITH_PROFILE|HAVE_PROFILE' >/dev/null
+if test $? -ne 0; then
+ echo "Samba \"smbd\" not built with profiling support" && exit 1
+fi
+
+if ! test -x /usr/bin/smbcontrol; then
+ echo "Samba \"smbcontrol\" tool is not installed" && exit 1
+fi
+/usr/bin/smbcontrol smbd profile on
+if test $? -ne 0; then
+ echo "Samba \"smbcontrol\" failed to enable profiling" && exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/samba/Remove b/src/pmdas/samba/Remove
new file mode 100755
index 0000000..9e92afe
--- /dev/null
+++ b/src/pmdas/samba/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Samba PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=samba
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/samba/pmdasamba.pl b/src/pmdas/samba/pmdasamba.pl
new file mode 100644
index 0000000..f10d7e1
--- /dev/null
+++ b/src/pmdas/samba/pmdasamba.pl
@@ -0,0 +1,198 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda %metrics );
+
+#
+# This is the main workhorse routine, both value extraction and
+# namespace population is under-pinned by this. The approach we
+# use here is to extract profile output, construct a hash (keyed
+# by metric ID), containing name and value pairs (array refs).
+#
+sub samba_fetch
+{
+ my $item = 0;
+ my $cluster = 0;
+ my $prefix = '';
+ my $generated_cluster = 20; # start well above hard-coded ones
+
+ # work around smbstatus / libpopt adverse reaction to these variables
+ delete $ENV{'POSIXLY_CORRECT'};
+ delete $ENV{'POSIX_ME_HARDER'};
+
+ my $smbstats = "smbstatus --profile";
+ open(STATS, "$smbstats |") ||
+ $pmda->err("pmdasamba failed to open $smbstats pipe: $!");
+
+ while (<STATS>) {
+ if (m/^\*\*\*\*\s+(\w+[^*]*)\**$/) {
+ my $heading = $1;
+ $heading =~ s/ +$//g;
+ $item = 0;
+ if ($heading eq 'System Calls') {
+ $cluster = 1; $prefix = 'syscalls';
+ } elsif ($heading eq 'Stat Cache') {
+ $cluster = 2; $prefix = 'statcache';
+ } elsif ($heading eq 'Write Cache') {
+ $cluster = 3; $prefix = 'writecache';
+ } elsif ($heading eq 'SMB Calls') {
+ $cluster = 4; $prefix = 'smb';
+ } elsif ($heading eq 'Pathworks Calls') {
+ $cluster = 5; $prefix = 'pathworks';
+ } elsif ($heading eq 'Trans2 Calls') {
+ $cluster = 6; $prefix = 'trans2';
+ } elsif ($heading eq 'NT Transact Calls') {
+ $cluster = 7; $prefix = 'NTtransact';
+ } elsif ($heading eq 'ACL Calls') {
+ $cluster = 8; $prefix = 'acl';
+ } elsif ($heading eq 'NMBD Calls') {
+ $cluster = 9; $prefix = 'nmb';
+ } else {
+ # samba 4.1 renames several clusters of statistics.
+ # Let's generate cluster names instead of hard-coding them.
+ $cluster = $generated_cluster++;
+ $prefix = $heading;
+ $prefix =~ s/ /_/g;
+ $prefix =~ tr/A-Z/a-z/;
+ }
+ # $pmda->log("metric cluster: $cluster = $prefix");
+ }
+ # we've found a real name/value pair, work out PMID and hash it
+ elsif (m/^([\[\]\w]+):\s+(\d+)$/) {
+ my @metric = ( $1, $2 );
+ my $pmid;
+
+ $metric[0] =~ tr/\[\]/_/d;
+
+ if ($cluster == 0) {
+ $metric[0] = "samba.$metric[0]";
+ } else {
+ $metric[0] = "samba.$prefix.$metric[0]";
+ }
+ $pmid = pmda_pmid($cluster,$item++);
+ $metrics{$pmid} = \@metric;
+ # $pmda->log("metric: $metric[0], ID = $pmid, value = $metric[1]");
+ }
+ else {
+ $pmda->log("pmdasamba failed to parse line $_");
+ }
+ }
+ close STATS;
+}
+
+sub samba_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $pmid = pmda_pmid($cluster, $item);
+ my $value;
+
+# $pmda->log("samba_fetch_callback $metric_name $cluster:$item ($inst)\n");
+
+ if ($inst != PM_IN_NULL) { return (PM_ERR_INST, 0); }
+
+ # hash lookup based on PMID, value is $metrics{$pmid}[1]
+ $value = $metrics{$pmid};
+ if (!defined($value)) { return (PM_ERR_APPVERSION, 0); }
+ return ($value->[1], 1);
+}
+
+$pmda = PCP::PMDA->new('samba', 76);
+
+samba_fetch(); # extract names and values into %metrics, keyed on PMIDs
+
+# hash iterate, keys are PMIDs, names and values are in @metrics{$pmid}.
+foreach my $pmid (sort(keys %metrics)) {
+ my $name = $metrics{$pmid}[0];
+ if ($name eq 'samba.writecache.num_write_caches' ||
+ $name eq 'samba.writecache.allocated_caches') {
+ $pmda->add_metric($pmid, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), $name, '', '');
+ } elsif ($name =~ /_time$/) {
+ $pmda->add_metric($pmid, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,1,0,0,PM_TIME_USEC,0), $name, '', '');
+ } else {
+ $pmda->add_metric($pmid, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), $name, '', '');
+ }
+ # $pmda->log("pmdasamba added metric $name\n");
+}
+# close STATS;
+
+$pmda->set_fetch(\&samba_fetch);
+$pmda->set_fetch_callback(\&samba_fetch_callback);
+# NB: needs to run as root, as smb usually does
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdasamba - Samba performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdasamba> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from Samba, a Windows SMB/CIFS server for UNIX.
+
+In order for values to be made available by this PMDA, Samba must have
+been built with profiling support (WITH_PROFILE in "smbd -b" output).
+This PMDA dynamically enumerates much of its metric hierarchy, based on
+the contents of "smbstatus --profile".
+
+When the agent is installed (see below), the Install script will attempt
+to enable Samba statistics gathering, using "smbcontrol --profile".
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the samba performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/samba
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/samba
+ # ./Remove
+
+B<pmdasamba> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/samba/Install
+
+installation script for the B<pmdasamba> agent
+
+=item $PCP_PMDAS_DIR/samba/Remove
+
+undo installation script for the B<pmdasamba> agent
+
+=item $PCP_LOG_DIR/pmcd/samba.log
+
+default log file for error messages from B<pmdasamba>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1), smbd(1) and samba(7).
diff --git a/src/pmdas/sample/GNUmakefile b/src/pmdas/sample/GNUmakefile
new file mode 100644
index 0000000..2c30757
--- /dev/null
+++ b/src/pmdas/sample/GNUmakefile
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2014 Red Hat. All Rights Reserved.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+DFILES = README help
+LSRCFILES = root pmns $(DFILES) Install Remove Sample.pmchart get_next_pmid
+LDIRT = *.dir *.pag
+
+SUBDIRS = src
+PMDADIR = $(PCP_PMDAS_DIR)/sample
+
+default_pcp default :: $(SUBDIRS)
+
+default_pcp default :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp install :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp install :: default
+ $(INSTALL) -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) pmns root $(PMDADIR)
+ $(INSTALL) -m 644 Sample.pmchart $(PCP_VAR_DIR)/config/pmchart/Sample
+
+include $(BUILDRULES)
diff --git a/src/pmdas/sample/Install b/src/pmdas/sample/Install
new file mode 100755
index 0000000..fd6bee6
--- /dev/null
+++ b/src/pmdas/sample/Install
@@ -0,0 +1,74 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the sample PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=sample
+forced_restart=false
+pmda_interface=2
+
+# Do it for sample
+#
+pmdaSetup
+
+echo "======================"
+echo "= sample daemon PMDA ="
+echo "======================"
+
+daemon_opt=true # can install as daemon
+dso_opt=false
+pipe_opt=true # supports pipe IPC
+socket_opt=true # supports socket IPC
+socket_inet_def=2077 # default TCP port for Internet socket IPC
+
+pmdaInstall
+
+# The name of the PMDA for sampledso
+#
+iam=sampledso
+
+# Do it for sampledso ... no pmdaSetup
+#
+
+echo "======================"
+echo "= sampledso DSO PMDA ="
+echo "======================"
+
+domain=30
+SYMDOM=SAMPLEDSO
+sed -e 's/sample/sampledso/' -e 's/SAMPLE/SAMPLEDSO/' <pmns >$tmp/pmns
+pmns_source=$tmp/pmns
+pmns_name=sampledso
+
+if $do_pmda
+then
+ sed -e 's/sample/sampledso/' -e 's/SAMPLE/SAMPLEDSO/' <help >dsohelp
+ help_source=dsohelp
+fi
+
+dso_opt=true
+daemon_opt=false
+dso_suffix=so
+[ "$PCP_PLATFORM" = "mingw" ] && dso_suffix=dll
+[ "$PCP_PLATFORM" = "darwin" ] && dso_suffix=dylib
+dso_name=$PCP_PMDAS_DIR/sample/pmda_sample.$dso_suffix
+dso_entry=sample_init
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/sample/README b/src/pmdas/sample/README
new file mode 100644
index 0000000..6e15fcf
--- /dev/null
+++ b/src/pmdas/sample/README
@@ -0,0 +1,63 @@
+Sample PMDA
+===========
+
+This PMDA supports a synthetic collection of metrics that are designed
+to exercise various facilities of the Performance Co-Pilot. The most
+common reasons for installing this PMDA are
+ a. for internal product QA, or
+ b. as part of one of the animated tutorials or demonstrations that
+ accompany the PCP
+
+Two variants of the PMDA are installed, namely "sample" as a daemon
+with an IPC channel to PMCD, and "sampledso" that is attached as a
+Dynamic Shared Object (DSO) by PMCD and called directly.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT sample sampledso
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/sample
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ Note that sampledso uses the next domain number after the one in
+ ./domain.h, so you must check for its uniqueness as well.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted to choose the IPC channel for the daemon
+ implementation of the sample PMDA -- everything else is automated
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/sample
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/sample.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/sample/Remove b/src/pmdas/sample/Remove
new file mode 100755
index 0000000..5b547f9
--- /dev/null
+++ b/src/pmdas/sample/Remove
@@ -0,0 +1,45 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the sample PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=sample
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+# and again
+#
+iam=sampledso
+pmns_name=sampledso
+domain=30
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/sample/Sample.pmchart b/src/pmdas/sample/Sample.pmchart
new file mode 100644
index 0000000..188ef1c
--- /dev/null
+++ b/src/pmdas/sample/Sample.pmchart
@@ -0,0 +1,10 @@
+#pmchart
+Version 1.2 host dynamic
+
+Chart Style plot
+ Plot Color #-cycle Host * Metric sample.drift
+Chart Style plot
+ Plot Color #-cycle Host * Metric sample.step
+
+#
+# Created by pmchart Mon Jul 15 08:52:18 1996
diff --git a/src/pmdas/sample/domain.h b/src/pmdas/sample/domain.h
new file mode 100644
index 0000000..0aa641b
--- /dev/null
+++ b/src/pmdas/sample/domain.h
@@ -0,0 +1,4 @@
+/*
+ * built from ../../pmns/stdpmid
+ */
+#define SAMPLE 29
diff --git a/src/pmdas/sample/get_next_pmid b/src/pmdas/sample/get_next_pmid
new file mode 100755
index 0000000..5443a7d
--- /dev/null
+++ b/src/pmdas/sample/get_next_pmid
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# find next unallocated PMIDs for the sample PMDA
+#
+
+if [ $# -gt 1 ]
+then
+ echo "Usage: $0 [N]"
+ echo "N defaults to 1 (PMID to generate)"
+ exit 1
+fi
+want=1
+[ $# -eq 1 ] && want="$1"
+
+root=root
+if [ ! -f $root ]
+then
+ root=../root
+fi
+if [ ! -f $root ]
+then
+ echo "Error: cannot find PMNS in root nor ../root"
+ exit 1
+fi
+
+pminfo -m -n $root \
+| sed -n -e '/ 29/s/\(.*\): \(.*\)/\2 \1/p' \
+| sort -t . -n -k 1,1 -k 2,2 -k 3,3 \
+| sed -e 's/ .*//' -e 's/\./ /g' \
+| awk '
+BEGIN { lastidx = -1; found = 0 }
+NF != 3 { next }
+$2 != 0 { next }
+ { if (lastidx+1 != $3) {
+ for (i = lastidx+1; i < $3; i++) {
+ print "SAMPLE:0:" i
+ found++
+ if (found == '"$want"') exit
+ }
+ }
+ lastidx = $3
+ }
+END { while (found < '"$want"') {
+ lastidx++
+ print "SAMPLE:0:" lastidx
+ found++
+ }
+ }'
diff --git a/src/pmdas/sample/help b/src/pmdas/sample/help
new file mode 100644
index 0000000..756c374
--- /dev/null
+++ b/src/pmdas/sample/help
@@ -0,0 +1,602 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# sample PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ SAMPLE.1 Instance domain "colour" for sample PMDA
+Universally 3 instances, "red" (0), "green" (1) and "blue" (3).
+
+@ SAMPLE.2 Instance domain "bin" for sample PMDA
+Universally 9 instances numbered 100 .. 900 in steps of 100, and named
+"bin-100" .. "bin-900"
+
+@ SAMPLE.3 Instance domain "mirage" for sample PMDA
+Random number of instances, that change with time. Instance "m-00" (0)
+is always present, while the others are numbered 1 .. 49 and named "m-01"
+.. "m-99"
+
+@ SAMPLE.4 Instance domain "family" for sample PMDA.
+A fixed set of instances:
+ "colleen", "terry", "emma", "cathy" and "fat bald bastard"
+
+@ SAMPLE.7 Instance domain "many" for sample PMDA.
+A varable size set of instances controlled by sample.many.count
+
+@ sample.control A control variable for the "sample" PMDA
+This control variable may be modified using pmStore().
+The allowed values are
+ 0 disable debugging output in the PMDA
+ >0 set pmDebug to this value to enable debugging output
+ -1 force the PMDA to terminate
+
+@ sample.daemon_pid Process id of PMDA daemon
+The process id of PMDA daemon, -1 if the daemon is a DSO.
+
+@ sample.seconds Elapsed time (seconds)
+The elapsed time since the PMDA started, in seconds, i.e. as returned
+by time(2).
+
+@ sample.milliseconds Elapsed time (milliseconds)
+The elapsed time since the PMDA started, in milliseconds, i.e. as
+returned by gettimeofday(2), and then adjusted from microseconds
+to milliseconds.
+
+@ sample.load Hypothetical load
+The hypothetical load is always 42!
+
+@ sample.colour Metrics with a "saw-tooth" trend over time
+This metric has 3 instances, designated "red", "green" and "blue".
+
+The value of the metric is monotonic increasing in the range N to
+N+100, then back to N. The different instances have different N values,
+namely 100 (red), 200 (green) and 300 (blue).
+
+@ sample.darkness No values available
+Defined over the same instance domain as sample.colour, but this metric
+returns the "No values available" error for every fetch.
+
+@ sample.bin Several constant instances
+9 instances labelled "bin-100" thru "bin-900", each with a constant
+value of 100 thru 900.
+
+@ sample.bucket Several constant instances
+9 instances labelled "bin-100" thru "bin-900", each with a constant
+value of 100 thru 900. This is an alias for sample.bin, but with
+a different PMID.
+
+@ sample.part_bin Several constant instances
+5 instances labelled "bin-100" thru "bin-900", each with a constant
+value of 100 thru 900. This is defined over the same domain as
+sample.part, but half of the instances are missing.
+
+@ sample.bogus_bin Several constant instances
+9 instances labelled "bin-100" thru "bin-900", each with a constant
+value of 100 thru 900. This is defined over the same domain as
+sample.part, half the values are for instances not in the instance
+domain.
+
+@ sample.drift A random trended metric
+This metric returns a random value (expected mean is approximately 200),
+subject to a trending pattern such that the sequence is mainly monotonic,
+with a change in direction after on average 4 consecutive samples.
+
+Use pmStore() to modifiy the instantaneous value, which becomes the new
+expected mean.
+
+@ sample.step A step function (instantaneous)
+This metric changes magnitude every 30 seconds, between a base value and
+3 times the base value.
+
+The metric has "instantaneous" semantics. See also sample.step_counter.
+
+Use pmStore() to modify the base value.
+
+@ sample.step_counter A step function (counter)
+This metric changes magnitude every 30 seconds, between a base value and
+3 times the base value.
+
+The metric has "counter" semantics. See also sample.step.
+
+Use pmStore() to modify the base value.
+
+@ sample.needprofile Metrics that need an explicit profile
+Simulate behaviour similar to the "proc" PMDA where metrics values are
+only available if an explicit instance profile is provided.
+
+@ sample.lights Traffic lights.
+A singular metric that has a discrete string value, namely "red",
+"yellow" or "green". There is some persistance in the value, so
+consecutive fetches are likely to return the same value, however over a
+long period of time all values are equally probable.
+
+@ sample.magnitude Powers of two.
+A singular metric that has a discrete integer value, namely 1, 2, 4, 8,
+16, 32 or 64. There is some persistance in the value, so consecutive
+fetches are likely to return the same value, however over a long period
+of time all values are equally probable.
+
+@ sample.pdu Total PDU count
+Count of PDUs received or transmitted.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.recv_pdu Count of PDUs received
+Count of PDUs received.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.xmit_pdu Count of PDUs transmitted
+Count of PDUs transmitted.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.mirage Simple saw-tooth rate, but instances come and go
+The metric is a rate (Kbytes/sec) that varies in a saw-tooth distribution
+over time. Different instances of the metric have different baselines
+for the saw-tooth, but all have an max-to-min range of 100.
+
+What makes this metric interesting is that instances come and go (not
+more often than once every 10 seconds however). Instance 0 is always
+present, but the other instances 1 thru 49 come and go in a cyclic
+pattern with a large random component influencing when each instance
+appears and disappears.
+
+@ sample.mirage_longlong Simple saw-tooth rate, but instances come and go
+The metric is a rate (bytes/msec) that varies in a saw-tooth distribution
+over time. Different instances of the metric have different baselines
+for the saw-tooth, but all have an max-to-min range of 100,000,000.
+
+What makes this metric interesting is that instances come and go (not
+more often than once every 10 seconds however). Instance 0 is always
+present, but the other instances 1 thru 49 come and go in a cyclic
+pattern with a large random component influencing when each instance
+appears and disappears.
+
+@ sample.write_me Modifiable, but otherwise constant.
+This metric has a 32-bit integer value of 2, unless changed via pmStore.
+The metric has semantics of rate, and units of events per second.
+
+@ sample.sysinfo Aggregate containing system accounting structures
+This metric has an aggregate value containing the following struct:
+ struct {
+ int len;
+ struct sysinfo sysinfo;
+ };
+
+The len field contains the size of the structure enclosing it.
+The sysinfo field contains various system accounting structures, summed over
+all CPUs, as returned by
+ sysmp(MP_SAGET, MPSA_SINFO, ...);
+
+See /usr/include/sys/sysinfo.h for the definition of the sysinfo struct.
+
+@ sample.noinst No instance available
+For testing, only. This metric is known, but no value is ever available
+
+@ sample.long.one 1 as a 32-bit integer
+@ sample.long.ten 10 as a 32-bit integer
+@ sample.long.hundred 100 as a 32-bit integer
+@ sample.long.million 1000000 as a 32-bit integer
+@ sample.long.write_me a 32-bit integer that can be modified
+@ sample.long.bin like sample.bin but type 32
+@ sample.long.bin_ctr like sample.bin but type 32, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.ulong.one 1 as a 32-bit unsigned integer
+@ sample.ulong.ten 10 as a 32-bit unsigned integer
+@ sample.ulong.hundred 100 as a 32-bit unsigned integer
+@ sample.ulong.million 1000000 as a 32-bit unsigned integer
+@ sample.ulong.write_me a 32-bit unsigned integer that can be modified
+@ sample.ulong.bin like sample.bin but type U32
+@ sample.ulong.bin_ctr like sample.bin but type U32, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.ulong.count.base count scale is 1, value is 42,000,000
+@ sample.ulong.count.deca count scale is 10, value is 4,200,000
+@ sample.ulong.count.hecto count scale is 10, value is 420,000
+@ sample.ulong.count.kilo count scale is 10, value is 42,000
+@ sample.ulong.count.mega count scale is 10, value is 42
+
+@ sample.longlong.one 1 as a 64-bit integer
+@ sample.longlong.ten 10 as a 64-bit integer
+@ sample.longlong.hundred 100 as a 64-bit integer
+@ sample.longlong.million 1000000 as a 64-bit integer
+@ sample.longlong.write_me a 64-bit integer that can be modified
+@ sample.longlong.bin like sample.bin but type 64
+@ sample.longlong.bin_ctr like sample.bin but type 64, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.ulonglong.one 1 as a 64-bit unsigned integer
+@ sample.ulonglong.ten 10 as a 64-bit unsigned integer
+@ sample.ulonglong.hundred 100 as a 64-bit unsigned integer
+@ sample.ulonglong.million 1000000 as a 64-bit unsigned integer
+@ sample.ulonglong.write_me a 64-bit unsigned integer that can be modified
+@ sample.ulonglong.bin like sample.bin but type U64
+@ sample.ulonglong.bin_ctr like sample.bin but type U64, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.float.one 1 as a 32-bit floating point value
+@ sample.float.ten 10 as a 32-bit floating point value
+@ sample.float.hundred 100 as a 32-bit floating point value
+@ sample.float.million 1000000 as a 32-bit floating point value
+@ sample.float.write_me a 32-bit floating-point value that can be modified
+@ sample.float.bin like sample.bin but type FLOAT
+@ sample.float.bin_ctr like sample.bin but type FLOAT, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.double.one 1 as a 64-bit floating point value
+@ sample.double.ten 10 as a 64-bit floating point value
+@ sample.double.hundred 100 as a 64-bit floating point value
+@ sample.double.million 1000000 as a 64-bit floating point value
+@ sample.double.write_me a 64-bit floating-point value that can be modified
+@ sample.double.bin like sample.bin but type DOUBLE
+@ sample.double.bin_ctr like sample.bin but type DOUBLE, SEM_COUNTER and SPACE_KBYTE
+
+@ sample.string.null a zero length string
+@ sample.string.hullo K&R have a lot to answer for
+@ sample.string.write_me a string value that can be modified
+
+@ sample.aggregate.null a zero length aggregate
+@ sample.aggregate.hullo K&R have a lot to answer for
+@ sample.aggregate.write_me a aggregate value that can be modified
+
+@ sample.hordes.one 500 instances
+Value of the metric is the instance identifier.
+@ sample.hordes.two 500 instances
+Value of the metric is 500 - the instance identifier.
+
+@ sample.bad.unknown Not known to the PMDA
+In the PMNS, but the sample agent pretends it does not know about this one.
+@ sample.bad.nosupport Not supported in this version of the PMDA
+Type is PM_NOSUPPORT, fetch returns PM_ERR_APPVERSION
+@ sample.bad.novalues Scalar with no values, ever
+
+@ sample.not_ready interval (in seconds) during which PMDA does not respond to PDUs
+Store a positive number of seconds as the value of this metric. The
+following PDU received will result in the following sequence of events:
+ 1. return an error PDU with PM_ERR_PMDANOTREADY to pmcd
+ 2. sleep for the given number of seconds
+ 3. sends an error PDU with PM_ERR_PMDAREADY to pmcd
+If everything went as planned, sample.not_ready returns to 0, otherwise it
+has a negative error code as value.
+
+@ sample.wrap.long long counter that wraps
+The metric value increments by INT_MAX / 2 - 1 (from <limits.h>) every
+time it is fetched.
+
+@ sample.wrap.ulong unsigned long counter that wraps
+The metric value increments by UINT_MAX / 2 - 1 (from <limits.h>) every
+time it is fetched.
+
+@ sample.wrap.longlong long long counter that wraps
+The metric value increments by LONGLONG_MAX / 2 - 1 (from <limits.h>)
+every time it is fetched.
+
+@ sample.wrap.ulonglong unsigned long long counter that wraps
+The metric value increments by ULONGLONG_MAX / 2 - 1 (from <limits.h>)
+every time it is fetched.
+
+@ sample.dodgey.value 5 unreliable instances
+The metric is a set of 5 instantaneous values, drawn at random from the
+range 0 to 100. The number of instances "visible" is controlled by
+sample.dodgey.control.
+
+@ sample.dodgey.control control values retured for sample.dodgey.value
+If sample.dodgey.control is <= 0, then this is returned as the "numval"
+component in the pmResult (0 => no values available, less than 0 =>
+various errors).
+
+If sample.dodgey.control is between 1 and 5 (inclusive), then this many
+of the values will be "visible". The values will be selected in order
+from the underlying 5 instances.
+
+If sample.dodgey.control is > 5, then at random times (between 1 and
+sample.dodgey.control fetches of the metric), the number of instances
+available is changed according to the following probabilities ...
+ 0.9 some number of instances in the range 0 to 5, selected at random
+ from the underlying 5 instances.
+ 0.1 error (PM_ERR_NOAGENT or PM_ERR_AGAIN or PM_ERR_APPVERSION)
+
+@ sample.rapid count very quickly
+Base counter increments by 8*10^7 per fetch. Result is 10 x base counter.
+
+@ sample.scale_step.bytes_up count up by powers of 2, wrap back to one at 10 Tbytes
+
+@ sample.scale_step.bytes_down count down by powers of 2, wrap back to 10 Tbytes at 1
+
+@ sample.scale_step.count_up count up by powers of 10, wrap back to 1 at 10e12
+
+@ sample.scale_step.count_down count down by powers of 10, wrap back to 10e12 at 1
+
+@ sample.scale_step.time_up_secs count up seconds by multiples of 10, wrap back to 1 second at 1 day
+
+@ sample.scale_step.time_up_nanosecs count up nanoseconds by multiples of 10, wrap back to 1 nanosecond at 1 day
+
+@ sample.scale_step.none_up count up dimensionless by multiples of 10, wrap back to 1 at 10 million
+
+@ sample.const_rate.value constant rate counter
+A counter that changes with constant rate between fetches.
+
+The rate is set by storing the desired rate (counts per second)
+into sample.const_rate.gradient
+
+@ sample.const_rate.gradient rate per second to set sample.const_rate.value, writable
+
+@ sample.error_code Arbitrary PMAPI error code for sample.error_check
+The metrics sample.error_code and sample.error_check are used in tandem
+as follows:
+ if sample.error_code is < 0, then any attempt to retrieve
+ information about sample.error_check will return a
+ sample.error_code as a PMAPI error from the PMDA.
+
+Use pmstore(1) to change sample.error_code.
+
+@ sample.error_check Return PMAPI error code from sample.error_code
+The metrics sample.error_code and sample.error_check are used in tandem
+as follows:
+ if sample.error_code is < 0, then any attempt to retrieve
+ information about sample.error_check will return a
+ sample.error_code as a PMAPI error from the PMDA.
+
+Otherwise sample.error_check is a boring metric that always has
+the value 0.
+
+@ sample.dynamic.counter counter metric with dynamic indom
+Instances come from $PCP_PMDAS_DIR/sample/dynamic.indom, if it exists.
+Each line in this file is
+ internal_id external_id
+
+This metric increments each time this instance has been seen when scanning
+the dynamic.indom file, and resets to zero each time the instance appears.
+
+@ sample.dynamic.discrete discrete metric with dynamic indom
+Instances come from $PCP_PMDAS_DIR/sample/dynamic.indom, if it exists.
+Each line in this file is
+ internal_id external_id
+
+This metric increments each time this instance has been seen when scanning
+the dynamic.indom file, and resets to zero each time the instance appears.
+
+@ sample.dynamic.instant instant metric with dynamic indom
+Instances come from $PCP_PMDAS_DIR/sample/dynamic.indom, if it exists.
+Each line in this file is
+ internal_id external_id
+
+This metric increments each time this instance has been seen when scanning
+the dynamic.indom file, and resets to zero each time the instance appears.
+
+@ sample.many.count number of instances in sample.many.int's domain
+store a value in sample.many.count to change the number of instances
+that appear in sample.many.int's instance domain
+
+@ sample.many.int variable sized instance domain
+store a value in sample.many.count to change the number of instances
+that appear in sample.many.int's instance domain
+
+@ sample.bigid a metric with item number bigger then 2^9
+
+@ sample.byte_ctr counter byte counter
+value increments randomly in the range (0,1023) bytes per fetch
+
+@ sample.byte_rate instantaneous bytes/second
+random value in the range (0,1023), so avg value is 512 bytes/second
+
+@ sample.kbyte_ctr counter Kbytes/second
+value increments randomly in the range (0,1023) Kbytes per fetch
+
+@ sample.kbyte_rate instantaneous Kbytes/second
+random value in the range (0,1023), so avg value is 512 Kbytes/second
+
+@ sample.byte_rate_perhour instantaneous bytes/hour
+random value in the range (0,1023), so avg value is 512 bytes/hour
+
+@ sample.dynamic.meta.metric metric with modifiable metadata
+See sample.dynamic.meta.pmdesc for the metrics that can be modified to
+change the metadata for this metric.
+The value of this metric is always 42.
+
+@ sample.dynamic.meta.pmdesc.type pmDesc.type for sample.dynamic.meta.metric
+One of these values:
+PM_TYPE_NOSUPPORT -1 /* not implemented in this version */
+PM_TYPE_32 0 /* 32-bit signed integer */
+PM_TYPE_U32 1 /* 32-bit unsigned integer */
+PM_TYPE_64 2 /* 64-bit signed integer */
+PM_TYPE_U64 3 /* 64-bit unsigned integer */
+PM_TYPE_FLOAT 4 /* 32-bit floating point */
+PM_TYPE_DOUBLE 5 /* 64-bit floating point */
+PM_TYPE_STRING 6 /* array of char */
+PM_TYPE_AGGREGATE 7 /* arbitrary binary data (aggregate) */
+PM_TYPE_AGGREGATE_STATIC 8 /* static pointer to aggregate */
+PM_TYPE_UNKNOWN 255 /* used in pmValueBlock, not pmDesc */
+
+Defaults to PM_TYPE_32.
+
+@ sample.dynamic.meta.pmdesc.indom pmDesc.indom for sample.dynamic.meta.metric
+Defaults to PM_INDOM_NULL (0xffffffff).
+
+@ sample.dynamic.meta.pmdesc.sem pmDesc.sem for sample.dynamic.meta.metric
+One of these values:
+PM_SEM_COUNTER 1 /* cumulative counter (monotonic increasing) */
+PM_SEM_INSTANT 3 /* instantaneous value, continuous domain */
+PM_SEM_DISCRETE 4 /* instantaneous value, discrete domain */
+
+Defaults to PM_SEM_DISCRETE.
+
+@ sample.dynamic.meta.pmdesc.units pmDesc.units for sample.dynamic.meta.metric
+6 x 4-bit values, from least-significant bit to most-significant bit:
+dimSpace:
+ -1, 0, 1
+dimTime:
+ -1, 0, 1
+dimCount:
+ 0, 1
+scaleSpace:
+ PM_SPACE_BYTE 0 /* bytes */
+ PM_SPACE_KBYTE 1 /* Kilobytes (1024) */
+ PM_SPACE_MBYTE 2 /* Megabytes (1024^2) */
+ PM_SPACE_GBYTE 3 /* Gigabytes (1024^3) */
+ PM_SPACE_TBYTE 4 /* Terabytes (1024^4) */
+ PM_SPACE_PBYTE 5 /* Petabytes (1024^5) */
+ PM_SPACE_EBYTE 6 /* Exabytes (1024^6) */
+scaleTime:
+ PM_TIME_NSEC 0 /* nanoseconds */
+ PM_TIME_USEC 1 /* microseconds */
+ PM_TIME_MSEC 2 /* milliseconds */
+ PM_TIME_SEC 3 /* seconds */
+ PM_TIME_MIN 4 /* minutes */
+ PM_TIME_HOUR 5 /* hours */
+scaleCount:
+ PM_COUNT_ONE 0 /* 1 */
+
+Defaults to { 1, -1, 0, PM_SPACE_BYTE, PM_TIME_SEC, 0 }
+
+@ sample.datasize Space allocated for PMDA's data segment
+This metric returns the amount of memory in kilobytes allocated for the
+data segment of the PMDA.
+
+This is handy for tracing memory utilization (and leaks) in libpcp_pmda.
+
+@ SAMPLE.0.1000 dynamic *.secret.bar metric
+Value "foo".
+
+@ SAMPLE.0.1001 dynamic *.secret.foo.one metric
+Value 1.
+
+@ SAMPLE.0.1002 dynamic *.secret.foo.two metric
+Value 2.
+
+@ SAMPLE.0.1003 dynamic *.secret.foo.bar.three metric
+Value 3.
+
+@ SAMPLE.0.1004 dynamic *.secret.foo.bar.four metric
+Value 4.
+
+@ SAMPLE.0.1005 dynamic *.secret.foo.bar.grunt.five metric
+Value 5.
+
+@ SAMPLE.0.1006 dynamic *.secret.foo.bar.grunt.snort.six metric
+Value 6.
+
+@ SAMPLE.0.1007 dynamic *.secret.foo.bar.grunt.snort.huff.puff.seven metric
+Value 7.
+
+@ sample.scramble.bin Several constant instances, instances scrambled
+Like sample.bin, except
+1. instances are missing with probability 0.33
+2. order of the instances from pmFetch is random
+
+Designed to help testing instance matching between pmFetch calls
+for PCP clients.
+
+@ sample.scramble.version Current state version and reset for sample.scramble.bin
+To make the order of instances seen from sample.scramble.bin
+deterministic, use pmstore(1) to trigger a reset.
+
+@ sample.percontext.control.ctx Number of PMAPI contexts seen
+One more than the highest PMAPI context number from PMCD.
+
+@ sample.percontext.control.active Number of active PMAPI contexts
+
+@ sample.percontext.control.start Number of new PMAPI contexts seen
+Incremented each time a new PMAPI context is seen from PMCD.
+
+sample.percontext.control.start - sample.percontext.control.end
+should equal sample.percontext.control.active.
+
+@ sample.percontext.control.end Number of PMAPI contexts closed
+Incremented each time PMCD closes a PMAPI context.
+
+sample.percontext.control.start - sample.percontext.control.end
+should equal sample.percontext.control.active.
+
+@ sample.percontext.pdu Total PDU count for the client context
+Count of PDUs received from or transmitted to the current PMAPI client
+context.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.percontext.recv_pdu Count of PDUs received from the client context
+Count of PDUs received from the current PMAPI client context.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.percontext.xmit_pdu Count of PDUs transmitted
+Count of PDUs transmitted to the current PMAPI client context.
+
+Use pmStore() to reset the counter to 0, independent of the value passed
+to pmStore().
+
+@ sample.event.records Dummy event records
+Dummy event records are generated in a fixed pattern to help QA.
+
+Once all setups have been returned, the cycle is repeated.
+
+See event.reset to exert explicit control over the next batch of event
+records to be returned.
+
+@ sample.event.no_indom_records More dummy event records
+Like sample.event.records but without the instance domain.
+
+@ sample.event.reset reset event record state
+Used for QA, should take one of the values 0, 1, 2 or 3
+to determine which of the dummy event record setups will
+be returned for the next fetch of event.records.
+
+@ sample.event.highres_records Dummy highres timestamp event records
+Dummy high resolution event records generated in a fixed pattern to help QA.
+
+Once all setups have been returned, the cycle is repeated.
+
+See event.reset_highres to exert explicit control over the next batch of event
+records to be returned.
+
+@ sample.event.reset_highres reset highres event record state
+Used for QA, should take one of the values 0, 1, 2 or 3
+to determine which of the dummy event record setups will
+be returned for the next fetch of event.highres_records.
+
+@ sample.event.type event type parameter for event records
+
+@ sample.event.param_32 32 parameter for event records
+
+@ sample.event.param_u32 U32 parameter for event records
+
+@ sample.event.param_64 64 parameter for event records
+
+@ sample.event.param_u64 U64 parameter for event records
+
+@ sample.event.param_float FLOAT parameter for event records
+
+@ sample.event.param_double DOUBLE parameter for event records
+
+@ sample.event.param_string STRING parameter for event records
+
+@ sample.event.param_aggregate AGGREGATE parameter for event records
diff --git a/src/pmdas/sample/pmns b/src/pmdas/sample/pmns
new file mode 100644
index 0000000..87c7507
--- /dev/null
+++ b/src/pmdas/sample/pmns
@@ -0,0 +1,260 @@
+/*
+ * Metrics for sample PMDA
+ *
+ * Next pmid ... use get_next_pmid
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+sample {
+ control SAMPLE:0:0
+ daemon_pid SAMPLE:0:1
+ seconds SAMPLE:0:2
+ milliseconds SAMPLE:0:3
+ load SAMPLE:0:4
+ colour SAMPLE:0:5
+ darkness SAMPLE:0:92
+ bin SAMPLE:0:6
+ bucket SAMPLE:0:48
+ part_bin SAMPLE:0:50
+ bogus_bin SAMPLE:0:51
+ drift SAMPLE:0:7
+ step SAMPLE:0:8
+ step_counter SAMPLE:0:63
+ mirage SAMPLE:0:37
+ mirage_longlong SAMPLE:0:38
+ write_me SAMPLE:0:36
+ lights SAMPLE:0:46
+ magnitude SAMPLE:0:47
+ sysinfo SAMPLE:0:39
+ pdu SAMPLE:0:40
+ recv_pdu SAMPLE:0:41
+ xmit_pdu SAMPLE:0:42
+ noinst SAMPLE:0:9
+ needprofile SAMPLE:0:49
+ long
+ ulong
+ longlong
+ ulonglong
+ float
+ double
+ string
+ aggregate
+ hordes
+ bad
+ not_ready SAMPLE:0:56
+ wrap
+ dodgey
+ dynamic
+ rapid SAMPLE:0:64
+ scale_step
+ const_rate
+ error_code SAMPLE:0:74
+ error_check SAMPLE:0:75
+ bigid SAMPLE:0:1023
+ many
+ byte_ctr SAMPLE:0:81
+ byte_rate SAMPLE:0:82
+ kbyte_ctr SAMPLE:0:83
+ kbyte_rate SAMPLE:0:84
+ byte_rate_perhour SAMPLE:0:85
+ datasize SAMPLE:0:91
+ secret SAMPLE:*:*
+ scramble
+ percontext
+ event
+}
+
+sample.long {
+ one SAMPLE:0:10
+ ten SAMPLE:0:11
+ hundred SAMPLE:0:12
+ million SAMPLE:0:13
+ write_me SAMPLE:0:14
+ bin SAMPLE:0:103
+ bin_ctr SAMPLE:0:104
+}
+
+sample.ulong {
+ one SAMPLE:0:93
+ ten SAMPLE:0:94
+ hundred SAMPLE:0:95
+ million SAMPLE:0:96
+ write_me SAMPLE:0:97
+ bin SAMPLE:0:105
+ bin_ctr SAMPLE:0:106
+ count
+}
+
+sample.ulong.count {
+ base SAMPLE:0:115
+ deca SAMPLE:0:116
+ hecto SAMPLE:0:117
+ kilo SAMPLE:0:118
+ mega SAMPLE:0:119
+}
+
+sample.float {
+ one SAMPLE:0:15
+ ten SAMPLE:0:16
+ hundred SAMPLE:0:17
+ million SAMPLE:0:18
+ write_me SAMPLE:0:19
+ bin SAMPLE:0:107
+ bin_ctr SAMPLE:0:108
+}
+
+sample.longlong {
+ one SAMPLE:0:20
+ ten SAMPLE:0:21
+ hundred SAMPLE:0:22
+ million SAMPLE:0:23
+ write_me SAMPLE:0:24
+ bin SAMPLE:0:109
+ bin_ctr SAMPLE:0:110
+}
+
+sample.ulonglong {
+ one SAMPLE:0:98
+ ten SAMPLE:0:99
+ hundred SAMPLE:0:100
+ million SAMPLE:0:101
+ write_me SAMPLE:0:102
+ bin SAMPLE:0:111
+ bin_ctr SAMPLE:0:112
+}
+
+sample.double {
+ one SAMPLE:0:25
+ ten SAMPLE:0:26
+ hundred SAMPLE:0:27
+ million SAMPLE:0:28
+ write_me SAMPLE:0:29
+ bin SAMPLE:0:113
+ bin_ctr SAMPLE:0:114
+}
+
+sample.string {
+ null SAMPLE:0:30
+ hullo SAMPLE:0:31
+ write_me SAMPLE:0:32
+}
+
+sample.aggregate {
+ null SAMPLE:0:33
+ hullo SAMPLE:0:34
+ write_me SAMPLE:0:35
+}
+
+sample.hordes {
+ one SAMPLE:0:52
+ two SAMPLE:0:53
+}
+
+sample.bad {
+ unknown SAMPLE:0:54
+ nosupport SAMPLE:0:55
+ novalues SAMPLE:0:138
+}
+
+sample.wrap {
+ long SAMPLE:0:57
+ ulong SAMPLE:0:58
+ longlong SAMPLE:0:59
+ ulonglong SAMPLE:0:60
+}
+
+sample.dodgey {
+ control SAMPLE:0:61
+ value SAMPLE:0:62
+}
+
+sample.scale_step {
+ bytes_up SAMPLE:0:65
+ bytes_down SAMPLE:0:66
+ count_up SAMPLE:0:67
+ count_down SAMPLE:0:68
+ time_up_secs SAMPLE:0:69
+ time_up_nanosecs SAMPLE:0:70
+ none_up SAMPLE:0:71
+}
+
+sample.const_rate {
+ value SAMPLE:0:72
+ gradient SAMPLE:0:73
+}
+
+sample.dynamic {
+ counter SAMPLE:0:76
+ discrete SAMPLE:0:77
+ instant SAMPLE:0:78
+ meta
+}
+
+sample.dynamic.meta {
+ metric SAMPLE:0:86
+ pmdesc
+}
+
+sample.dynamic.meta.pmdesc {
+ type SAMPLE:0:87
+ indom SAMPLE:0:88
+ sem SAMPLE:0:89
+ units SAMPLE:0:90
+}
+
+sample.many {
+ count SAMPLE:0:79
+ int SAMPLE:0:80
+}
+
+sample.scramble {
+ version SAMPLE:0:120
+ bin SAMPLE:0:121
+}
+
+sample.percontext {
+ control
+ pdu SAMPLE:0:43
+ recv_pdu SAMPLE:0:44
+ xmit_pdu SAMPLE:0:45
+}
+
+sample.percontext.control {
+ ctx SAMPLE:0:122
+ active SAMPLE:0:123
+ start SAMPLE:0:124
+ end SAMPLE:0:125
+}
+
+sample.event {
+ records SAMPLE:0:136
+ highres_records SAMPLE:0:139
+ no_indom_records SAMPLE:0:137
+ reset SAMPLE:0:126
+ reset_highres SAMPLE:0:140
+ type SAMPLE:0:127
+ param_32 SAMPLE:0:128
+ param_u32 SAMPLE:0:129
+ param_64 SAMPLE:0:130
+ param_u64 SAMPLE:0:131
+ param_float SAMPLE:0:132
+ param_double SAMPLE:0:133
+ param_string SAMPLE:0:134
+ param_aggregate SAMPLE:0:135
+}
diff --git a/src/pmdas/sample/root b/src/pmdas/sample/root
new file mode 100644
index 0000000..4f87c8e
--- /dev/null
+++ b/src/pmdas/sample/root
@@ -0,0 +1,20 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root {
+ sample
+ sampledso
+}
+
+#include "pmns"
+
+#define sample sampledso
+#ifdef SAMPLE
+#undef SAMPLE
+#endif
+#define SAMPLE 30
+#include "pmns"
+
diff --git a/src/pmdas/sample/src/GNUmakefile b/src/pmdas/sample/src/GNUmakefile
new file mode 100644
index 0000000..78ed5ad
--- /dev/null
+++ b/src/pmdas/sample/src/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+#
+# The TARGETS macro must be set to the name of the executable we
+# are building.
+#
+IAM = sample
+DOMAIN = SAMPLE
+CMDTARGET = pmda$(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+TARGETS = $(CMDTARGET) $(LIBTARGET)
+
+PMDAINIT = $(IAM)_init
+PMDADIR = $(PCP_PMDAS_DIR)/sample
+
+CFILES = pmda.c sample.c percontext.c events.c
+HFILES = percontext.h events.h
+VERSION_SCRIPT = exports
+
+LDIRT = domain.h sample.log $(TARGETS) $(VERSION_SCRIPT)
+LCFLAGS += $(INVISIBILITY)
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_RT)
+LSRCFILES = GNUmakefile.install
+
+default: domain.h $(TARGETS)
+
+pmda.o sample.o percontext.o: percontext.h
+sample.o events.o: events.h
+pmda.o: $(VERSION_SCRIPT)
+
+domain.h: ../../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(CFILES) $(HFILES) $(PMDADIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMDADIR)/Makefile
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/sample/src/GNUmakefile.install b/src/pmdas/sample/src/GNUmakefile.install
new file mode 100644
index 0000000..37191d0
--- /dev/null
+++ b/src/pmdas/sample/src/GNUmakefile.install
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = sh
+
+ifdef PCP_CONF
+include $(PCP_CONF)
+else
+include $(PCP_DIR)/etc/pcp.conf
+endif
+include $(PCP_INC_DIR)/builddefs
+
+# remove -Lpath and -Ipath options from builddefs CFLAGS value
+#
+PCP_LIBS =
+TMP := $(CFLAGS:-I%=)
+ifdef PCP_DIR
+# put -Ipath and -Lpath back but use paths for run-time environment
+#
+CFLAGS = $(TMP) -I$(PCP_INC_DIR)/..
+LDFLAGS = -L$(PCP_LIB_DIR)
+else
+CFLAGS = $(TMP)
+endif
+
+IAM = sample
+CFILES = pmda.c sample.c percontext.c events.c
+HFILES = percontext.h events.h
+
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+CMDTARGET = pmda$(IAM)
+TARGETS = $(LIBTARGET) $(CMDTARGET)
+
+LLDLIBS = -lpcp_pmda -lpcp $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS) $(LIB_FOR_RT)
+LDIRT = *.log help.dir help.pag
+
+default: $(TARGETS)
+
+install: default
+
+include $(PCP_INC_DIR)/buildrules
diff --git a/src/pmdas/sample/src/events.c b/src/pmdas/sample/src/events.c
new file mode 100644
index 0000000..cc1cf62
--- /dev/null
+++ b/src/pmdas/sample/src/events.c
@@ -0,0 +1,477 @@
+/*
+ * The "event" records here are all fake. But the logic does show how
+ * a real PMDA could deliver values for metrics of type PM_TYPE_EVENT
+ * and PM_TYPE_HIGHRES_EVENT.
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "events.h"
+
+static int nfetch;
+static int xnfetch;
+static int myarray[2];
+
+static int nhrfetch;
+static int xnhrfetch;
+static int hrarray[2];
+
+/* event parameters */
+static pmValueBlock *aggr;
+static char aggrval[] = { '\01', '\03', '\07', '\017', '\037', '\077', '\177', '\377' };
+static pmID pmid_type = PMDA_PMID(0,127); /* event.type */
+static pmID pmid_32 = PMDA_PMID(0,128); /* event.param_32 */
+static pmID pmid_u32 = PMDA_PMID(0,129); /* event.param_u32 */
+static pmID pmid_64 = PMDA_PMID(0,130); /* event.param_64 */
+static pmID pmid_u64 = PMDA_PMID(0,131); /* event.param_u64 */
+static pmID pmid_float = PMDA_PMID(0,132); /* event.param_float */
+static pmID pmid_double = PMDA_PMID(0,133); /* event.param_double */
+static pmID pmid_string = PMDA_PMID(0,134); /* event.param_string */
+static pmID pmid_aggregate = PMDA_PMID(0,135); /* event.param_aggregate */
+
+void
+init_events(int domain)
+{
+ int i, sts;
+
+ /*
+ * fix the domain field in the event parameter PMIDs ...
+ * note these PMIDs must match the corresponding metrics in
+ * desctab[] and this cannot easily be done automatically
+ */
+ ((__pmID_int *)&pmid_type)->domain = domain;
+ ((__pmID_int *)&pmid_32)->domain = domain;
+ ((__pmID_int *)&pmid_u32)->domain = domain;
+ ((__pmID_int *)&pmid_64)->domain = domain;
+ ((__pmID_int *)&pmid_u64)->domain = domain;
+ ((__pmID_int *)&pmid_float)->domain = domain;
+ ((__pmID_int *)&pmid_double)->domain = domain;
+ ((__pmID_int *)&pmid_string)->domain = domain;
+ ((__pmID_int *)&pmid_aggregate)->domain = domain;
+
+ /* build pmValueBlock for aggregate value */
+ aggr = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE + sizeof(aggrval));
+ aggr->vtype = PM_TYPE_AGGREGATE;
+ aggr->vlen = PM_VAL_HDR_SIZE + sizeof(aggrval);
+ memcpy(aggr->vbuf, (void *)aggrval, sizeof(aggrval));
+
+ /* setup event arrays for 2 instances */
+ for (i = 0; i < 2; i++) {
+ if ((sts = myarray[i] = pmdaEventNewArray()) < 0)
+ fprintf(stderr, "pmdaEventNewArray: %s\n", pmErrStr(sts));
+ if ((sts = hrarray[i] = pmdaEventNewHighResArray()) < 0)
+ fprintf(stderr, "pmdaEventNewHighResArray: %s\n", pmErrStr(sts));
+ }
+}
+
+int
+event_get_fetch_count(void)
+{
+ return nfetch % 4;
+}
+
+void
+event_set_fetch_count(int c)
+{
+ xnfetch = nfetch = c;
+}
+
+int
+sample_fetch_events(pmValueBlock **vbpp, int inst)
+{
+ int c;
+ int sts;
+ int flags;
+ struct timeval stamp;
+ pmAtomValue atom;
+
+ if (nfetch >= 0)
+ c = nfetch % 4;
+ else /* one of the error injection cases */
+ c = nfetch;
+
+ if (inst == PM_IN_NULL || inst > 1 || inst < 0)
+ inst = 1;
+
+ pmdaEventResetArray(myarray[inst]);
+ gettimeofday(&stamp, NULL);
+
+ if (inst == 0) {
+ /* original instance ... */
+ /* rebase event records 10 secs in past, add 1 sec for each new record */
+ stamp.tv_sec -= 10;
+
+ switch (c) {
+ case 0:
+ /*
+ * 1st fetch
+ * No events
+ */
+ break;
+ case 1:
+ /*
+ * 2nd fetch
+ * 1 event with NO parameters
+ */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ break;
+ case 2:
+ /*
+ * 3rd fetch
+ * 1 event with one U32 parameter
+ * 1 event with 2 parameters(U32 and 64 types)
+ */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 1;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 2;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ll = -3;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_64, PM_TYPE_64, &atom)) < 0)
+ return sts;
+ break;
+ case 3:
+ /*
+ * 4th fetch
+ * 1 event start with 3 parameters (U32, U64 and STRING types)
+ * 1 event with 3 parameters (U32 and 2 DOUBLE types)
+ * 1 event end with 6 (U32, U64, STRING, STRING, 32 and U32 types)
+ * 7 "missed" events
+ * 1 event with 3 parameters (U32, FLOAT and AGGREGATE types)
+ */
+ flags = PM_EVENT_FLAG_START|PM_EVENT_FLAG_ID|PM_EVENT_FLAG_PARENT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 4;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ull = 5;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_u64, PM_TYPE_U64, &atom)) < 0)
+ return sts;
+ atom.cp = "6";
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 7;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.d = 8;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_double, PM_TYPE_DOUBLE, &atom)) < 0)
+ return sts;
+ atom.d = -9;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_double, PM_TYPE_DOUBLE, &atom)) < 0)
+ return sts;
+ flags = PM_EVENT_FLAG_END;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 10;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ull = 11;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_u64, PM_TYPE_U64, &atom)) < 0)
+ return sts;
+ atom.cp = "twelve";
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ atom.cp = "thirteen";
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ atom.l = -14;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_32, PM_TYPE_32, &atom)) < 0)
+ return sts;
+ atom.ul = 15;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_u32, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ /* "missed" 7 records */
+ if ((sts = pmdaEventAddMissedRecord(myarray[inst], &stamp, 7)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 16;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.f = -17;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_float, PM_TYPE_FLOAT, &atom)) < 0)
+ return sts;
+ atom.vbp = aggr;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_aggregate, PM_TYPE_AGGREGATE, &atom)) < 0)
+ return sts;
+ break;
+ case -1:
+ /* error injection */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = c;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ /* pmid that is not in PMNS and not known to the PMDA */
+ if ((sts = pmdaEventAddParam(myarray[inst], PMDA_PMID(100,200), PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ break;
+ }
+ nfetch++;
+ }
+ else {
+ /*
+ * new, boring instance ..., either instance ["bogus"] for
+ * sample.events.rrecord or singular instance for
+ * sample.events.no_indom_records
+ */
+ static char hrecord1[20];
+ static char hrecord2[] = "bingo!";
+
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ snprintf(hrecord1, sizeof(hrecord1), "fetch #%d", xnfetch);
+ atom.cp = hrecord1;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ if ((xnfetch % 3) == 0) {
+ if ((sts = pmdaEventAddRecord(myarray[inst], &stamp, flags)) < 0)
+ return sts;
+ atom.cp = hrecord2;
+ if ((sts = pmdaEventAddParam(myarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ }
+ xnfetch++;
+ }
+
+ *vbpp = (pmValueBlock *)pmdaEventGetAddr(myarray[inst]);
+
+ return 0;
+}
+
+int
+event_get_highres_fetch_count(void)
+{
+ return nhrfetch % 4;
+}
+
+void
+event_set_highres_fetch_count(int c)
+{
+ xnhrfetch = nhrfetch = c;
+}
+
+int
+sample_fetch_highres_events(pmValueBlock **vbpp, int inst)
+{
+ int c;
+ int sts;
+ int flags;
+ struct timespec stamp;
+ pmAtomValue atom;
+
+ if (nhrfetch >= 0)
+ c = nhrfetch % 4;
+ else /* one of the error injection cases */
+ c = nhrfetch;
+
+ if (inst == PM_IN_NULL || inst > 1 || inst < 0)
+ inst = 1;
+
+ pmdaEventResetHighResArray(hrarray[inst]);
+ __pmGetTimespec(&stamp);
+
+ if (inst == 0) {
+ /* original instance ... */
+ /* rebase event records 10 secs in past, add 1 sec for each new record */
+ stamp.tv_sec -= 10;
+
+ switch (c) {
+ case 0:
+ /*
+ * 1st fetch
+ * No events
+ */
+ break;
+ case 1:
+ /*
+ * 2nd fetch
+ * 1 event with NO parameters
+ */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ break;
+ case 2:
+ /*
+ * 3rd fetch
+ * 1 event with one U32 parameter
+ * 1 event with 2 parameters(U32 and 64 types)
+ */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 1;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 2;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ll = -3;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_64, PM_TYPE_64, &atom)) < 0)
+ return sts;
+ break;
+ case 3:
+ /*
+ * 4th fetch
+ * 1 event start with 3 parameters (U32, U64 and STRING types)
+ * 1 event with 3 parameters (U32 and 2 DOUBLE types)
+ * 1 event end with 6 (U32, U64, STRING, STRING, 32 and U32 types)
+ * 7 "missed" events
+ * 1 event with 3 parameters (U32, FLOAT and AGGREGATE types)
+ */
+ flags = PM_EVENT_FLAG_START|PM_EVENT_FLAG_ID|PM_EVENT_FLAG_PARENT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 4;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ull = 5;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_u64, PM_TYPE_U64, &atom)) < 0)
+ return sts;
+ atom.cp = "6";
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 7;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.d = 8;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_double, PM_TYPE_DOUBLE, &atom)) < 0)
+ return sts;
+ atom.d = -9;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_double, PM_TYPE_DOUBLE, &atom)) < 0)
+ return sts;
+ flags = PM_EVENT_FLAG_END;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 10;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.ull = 11;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_u64, PM_TYPE_U64, &atom)) < 0)
+ return sts;
+ atom.cp = "twelve";
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ atom.cp = "thirteen";
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ atom.l = -14;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_32, PM_TYPE_32, &atom)) < 0)
+ return sts;
+ atom.ul = 15;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_u32, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ /* "missed" 7000 records */
+ if ((sts = pmdaEventAddHighResMissedRecord(hrarray[inst], &stamp, 7000)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = 16;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ atom.f = -17;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_float, PM_TYPE_FLOAT, &atom)) < 0)
+ return sts;
+ atom.vbp = aggr;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_aggregate, PM_TYPE_AGGREGATE, &atom)) < 0)
+ return sts;
+ break;
+ case -1:
+ /* error injection */
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ stamp.tv_sec++;
+ atom.ul = c;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_type, PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ /* pmid that is not in PMNS and not known to the PMDA */
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], PMDA_PMID(100,200), PM_TYPE_U32, &atom)) < 0)
+ return sts;
+ break;
+ }
+ nhrfetch++;
+ }
+ else {
+ /*
+ * new, boring instance ..., either instance ["bogus"] for
+ * sample.events.rrecord or singular instance for
+ * sample.events.no_indom_records
+ */
+ static char record1[20];
+ static char record2[] = "bingo!";
+
+ flags = PM_EVENT_FLAG_POINT;
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ snprintf(record1, sizeof(record1), "fetch #%d", xnhrfetch);
+ atom.cp = record1;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ if ((xnhrfetch % 3) == 0) {
+ if ((sts = pmdaEventAddHighResRecord(hrarray[inst], &stamp, flags)) < 0)
+ return sts;
+ atom.cp = record2;
+ if ((sts = pmdaEventHighResAddParam(hrarray[inst], pmid_string, PM_TYPE_STRING, &atom)) < 0)
+ return sts;
+ }
+ xnhrfetch++;
+ }
+
+ *vbpp = (pmValueBlock *)pmdaEventHighResGetAddr(hrarray[inst]);
+
+ return 0;
+}
diff --git a/src/pmdas/sample/src/events.h b/src/pmdas/sample/src/events.h
new file mode 100644
index 0000000..d7dd1b5
--- /dev/null
+++ b/src/pmdas/sample/src/events.h
@@ -0,0 +1,33 @@
+/*
+ * The "event" records here are all fake. But the logic does show how
+ * a real PMDA could deliver values for metrics of type PM_TYPE_EVENT.
+ * and PM_TYPE_HIGHRES_EVENT.
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _EVENTS_H
+#define _EVENTS_H
+
+extern void init_events(int);
+
+extern int sample_fetch_events(pmValueBlock **, int);
+extern void event_set_fetch_count(int);
+extern int event_get_fetch_count(void);
+
+extern int sample_fetch_highres_events(pmValueBlock **, int);
+extern void event_set_highres_fetch_count(int);
+extern int event_get_highres_fetch_count(void);
+
+#endif /* _EVENTS_H */
diff --git a/src/pmdas/sample/src/percontext.c b/src/pmdas/sample/src/percontext.c
new file mode 100644
index 0000000..0c549df
--- /dev/null
+++ b/src/pmdas/sample/src/percontext.c
@@ -0,0 +1,256 @@
+/*
+ * Some functions become per-client (of pmcd) with the introduction
+ * of PMDA_INTERFACE_5.
+ *
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "percontext.h"
+
+typedef struct {
+ int state; /* active or inactive context */
+ int recv_pdu; /* count of PDUs received from this context */
+ int xmit_pdu; /* count of PDUs sent to this context */
+} perctx_t;
+
+/* values for state */
+#define CTX_INACTIVE 0
+#define CTX_ACTIVE 1
+
+static perctx_t *ctxtab;
+static int num_ctx;
+static int num_start; /* count of new contexts noted */
+static int num_end; /* count of end context events */
+static int num_recv_pdu; /* recv count from closed contexts */
+static int num_xmit_pdu; /* xmit count from closed contexts */
+
+void
+sample_clr_recv(int ctx)
+{
+ if (ctx == CTX_ALL) {
+ int i;
+ for (i = 0; i < num_ctx; i++) {
+ if (ctxtab[i].state == CTX_ACTIVE)
+ ctxtab[i].recv_pdu = 0;
+ }
+ num_recv_pdu = 0;
+ }
+ else if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ fprintf(stderr, "Botch: sample_clr_recv(%d) num_ctx=%d", ctx, num_ctx);
+ if (ctx >= 0 && ctx < num_ctx)
+ fprintf(stderr, " ctxtab[] is inactive");
+ fputc('\n', stderr);
+ }
+ else
+ ctxtab[ctx].recv_pdu = 0;
+}
+
+void
+sample_clr_xmit(int ctx)
+{
+ if (ctx == CTX_ALL) {
+ int i;
+ for (i = 0; i < num_ctx; i++) {
+ if (ctxtab[i].state == CTX_ACTIVE)
+ ctxtab[i].xmit_pdu = 0;
+ }
+ num_xmit_pdu = 0;
+ }
+ else if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ fprintf(stderr, "Botch: sample_clr_xmit(%d) num_ctx=%d", ctx, num_ctx);
+ if (ctx >= 0 && ctx < num_ctx)
+ fprintf(stderr, " ctxtab[] is inactive");
+ fputc('\n', stderr);
+ }
+ else
+ ctxtab[ctx].xmit_pdu = 0;
+}
+
+int
+sample_get_recv(int ctx)
+{
+ if (ctx == CTX_ALL) {
+ int i;
+ int ans = num_recv_pdu;
+ for (i = 0; i < num_ctx; i++) {
+ if (ctxtab[i].state == CTX_ACTIVE)
+ ans += ctxtab[i].recv_pdu;
+ }
+ return ans;
+ }
+ else if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ return PM_ERR_NOCONTEXT;
+ }
+ else
+ return ctxtab[ctx].recv_pdu;
+}
+
+int
+sample_get_xmit(int ctx)
+{
+ if (ctx == CTX_ALL) {
+ int i;
+ int ans = num_xmit_pdu;
+ for (i = 0; i < num_ctx; i++) {
+ if (ctxtab[i].state == CTX_ACTIVE)
+ ans += ctxtab[i].xmit_pdu;
+ }
+ return ans;
+ }
+ else if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ return PM_ERR_NOCONTEXT;
+ }
+ else
+ return ctxtab[ctx].xmit_pdu;
+}
+
+static void
+growtab(int ctx)
+{
+ ctxtab = (perctx_t *)realloc(ctxtab, (ctx+1)*sizeof(ctxtab[0]));
+ if (ctxtab == NULL) {
+ __pmNoMem("growtab", (ctx+1)*sizeof(ctxtab[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ while (num_ctx <= ctx) {
+ ctxtab[num_ctx].state = CTX_INACTIVE;
+ ctxtab[num_ctx].recv_pdu = 0;
+ ctxtab[num_ctx].xmit_pdu = 0;
+ num_ctx++;
+ }
+ ctxtab[ctx].state = CTX_INACTIVE;
+}
+
+void
+sample_inc_recv(int ctx)
+{
+ if (ctx < 0) {
+ fprintf(stderr, "Botch: sample_inc_recv(%d)!\n", ctx);
+ return;
+ }
+ if (ctx >= num_ctx) growtab(ctx);
+ if (ctxtab[ctx].state == CTX_INACTIVE) {
+ num_start++;
+ ctxtab[ctx].state = CTX_ACTIVE;
+ ctxtab[ctx].recv_pdu = 0;
+ ctxtab[ctx].xmit_pdu = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "sample_inc_recv(%d) [new context, num_ctx=%d]\n", ctx, num_ctx);
+ }
+#endif
+ }
+ ctxtab[ctx].recv_pdu++;
+}
+
+void
+sample_inc_xmit(int ctx)
+{
+ if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ fprintf(stderr, "Botch: sample_inc_xmit(%d) num_ctx=%d", ctx, num_ctx);
+ if (ctx >= 0 && ctx < num_ctx)
+ fprintf(stderr, " ctxtab[] is inactive");
+ fputc('\n', stderr);
+ return;
+ }
+ if (ctx >= num_ctx)
+ growtab(ctx);
+ ctxtab[ctx].xmit_pdu++;
+}
+
+int
+sample_ctx_fetch(int ctx, int item)
+{
+ if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ fprintf(stderr, "Botch: sample_ctx_fetch(%d, %d) num_ctx=%d", ctx, item, num_ctx);
+ if (ctx >= 0 && ctx < num_ctx)
+ fprintf(stderr, " ctxtab[] is inactive");
+ fputc('\n', stderr);
+ return PM_ERR_NOCONTEXT;
+ }
+
+ if (item == 43) {
+ /* percontext.pdu */
+ return ctxtab[ctx].recv_pdu + ctxtab[ctx].xmit_pdu;
+ }
+ else if (item == 44) {
+ /* percontext.recv-pdu */
+ return ctxtab[ctx].recv_pdu;
+ }
+ else if (item == 45) {
+ /* percontext.xmit-pdu */
+ return ctxtab[ctx].xmit_pdu;
+ }
+ else if (item == 122) {
+ /* percontext.control.ctx */
+ return num_ctx;
+ }
+ else if (item == 123) {
+ /* percontext.control.active */
+ int i;
+ int ans = 0;
+ for (i = 0; i < num_ctx; i++) {
+ if (ctxtab[i].state == CTX_ACTIVE)
+ ans++;
+ }
+ return ans;
+ }
+ else if (item == 124) {
+ /* percontext.control.start */
+ return num_start;
+ }
+ else if (item == 125) {
+ /* percontext.control.end */
+ return num_end;
+ }
+
+ fprintf(stderr, "Botch: sample_ctx_fetch(%d, %d): item bad!\n", ctx, item);
+ return PM_ERR_PMID;
+}
+
+void
+sample_ctx_end(int ctx)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "sample_ctx_end(%d) [context is ", ctx);
+ if (ctx < 0 || ctx >= num_ctx)
+ fprintf(stderr, "unknown, num_ctx=%d", num_ctx);
+ else if (ctxtab[ctx].state == CTX_ACTIVE)
+ fprintf(stderr, "active");
+ else if (ctxtab[ctx].state == CTX_INACTIVE)
+ fprintf(stderr, "inactive");
+ else
+ fprintf(stderr, "botched state, %d", ctxtab[ctx].state);
+ fprintf(stderr, "]\n");
+ }
+#endif
+ if (ctx < 0 || ctx >= num_ctx || ctxtab[ctx].state == CTX_INACTIVE) {
+ /*
+ * This is expected ... when a context is closed in pmcd
+ * (or for a local context or for dbpmda or ...) all the
+ * PMDAs with a registered pmdaEndContextCallBack will be
+ * called end some of the PMDAs may not have not serviced
+ * any previous requests for that context.
+ */
+ return;
+ }
+ num_end++;
+ num_recv_pdu += ctxtab[ctx].recv_pdu;
+ num_xmit_pdu += ctxtab[ctx].xmit_pdu;
+ ctxtab[ctx].state = CTX_INACTIVE;
+}
+
diff --git a/src/pmdas/sample/src/percontext.h b/src/pmdas/sample/src/percontext.h
new file mode 100644
index 0000000..151fba6
--- /dev/null
+++ b/src/pmdas/sample/src/percontext.h
@@ -0,0 +1,31 @@
+/*
+ * Some functions become per-client (of pmcd) with the introduction
+ * of PMDA_INTERFACE_5.
+ *
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PERCONTEXT_H
+#define _PERCONTEXT_H
+
+extern int sample_get_recv(int);
+extern int sample_get_xmit(int);
+extern void sample_clr_recv(int);
+extern void sample_clr_xmit(int);
+extern void sample_inc_recv(int);
+extern void sample_inc_xmit(int);
+extern int sample_ctx_fetch(int, int);
+extern void sample_ctx_end(int);
+
+#define CTX_ALL -1
+
+#endif /* _PERCONTEXT_H */
diff --git a/src/pmdas/sample/src/pmda.c b/src/pmdas/sample/src/pmda.c
new file mode 100644
index 0000000..c1449fb
--- /dev/null
+++ b/src/pmdas/sample/src/pmda.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Generic driver for a daemon-based PMDA
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include "percontext.h"
+
+extern void sample_init(pmdaInterface *);
+extern int sample_done;
+extern int not_ready;
+extern int _isDSO;
+
+static pmdaInterface dispatch;
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_TEXT("\nExactly one of the following options may appear:"),
+ PMDAOPT_INET,
+ PMDAOPT_PIPE,
+ PMDAOPT_UNIX,
+ PMDAOPT_IPV6,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:i:l:pu:U:6:?",
+ .long_options = longopts,
+};
+
+/*
+ * simulate PMDA busy (not responding to PDUs)
+ */
+int
+limbo(void)
+{
+ __pmSendError(dispatch.version.two.ext->e_outfd, FROM_ANON, PM_ERR_PMDANOTREADY);
+ while (not_ready > 0)
+ not_ready = sleep(not_ready);
+ return PM_ERR_PMDAREADY;
+}
+
+/*
+ * callback from pmdaMain
+ */
+static int
+check(void)
+{
+ if (access("/tmp/sample.unavail", F_OK) == 0)
+ return PM_ERR_AGAIN;
+ return 0;
+}
+
+/*
+ * callback from pmdaMain
+ */
+static void
+done(void)
+{
+ if (sample_done)
+ exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ char helppath[MAXPATHLEN];
+ char *username;
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "sample" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_LATEST, pmProgname, SAMPLE,
+ "sample.log", helppath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ pmdaOpenLog(&dispatch);
+ if (opts.username)
+ username = opts.username;
+ __pmSetProcessIdentity(username);
+
+ sample_init(&dispatch);
+ pmdaSetCheckCallBack(&dispatch, check);
+ pmdaSetDoneCallBack(&dispatch, done);
+ pmdaConnect(&dispatch);
+
+#ifdef HAVE_SIGHUP
+ /*
+ * Non-DSO agents should ignore gratuitous SIGHUPs, e.g. from a
+ * terminal when launched by the PCP Tutorial!
+ */
+ signal(SIGHUP, SIG_IGN);
+#endif
+
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/sample/src/sample.c b/src/pmdas/sample/src/sample.c
new file mode 100644
index 0000000..13a1328
--- /dev/null
+++ b/src/pmdas/sample/src/sample.c
@@ -0,0 +1,2748 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <limits.h>
+#include <sys/stat.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "percontext.h"
+#include "events.h"
+#include "domain.h"
+#ifdef HAVE_SYSINFO
+/*
+ * On Solaris, need <sys/systeminfo.h> and sysinfo() is different.
+ * Other platforms need <sys/sysinfo.h>
+ */
+#ifdef IS_SOLARIS
+#include <sys/systeminfo.h>
+#define MAX_SYSNAME 257
+#else
+#include <sys/sysinfo.h>
+#endif
+#else
+static struct sysinfo {
+ char dummy[64];
+} si = { {
+'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
+'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '9', '[', ']', '.'
+} };
+#endif
+
+static int need_mirage; /* only do mirage glop is someone asks for it */
+static int need_dynamic;/* only do dynamic glop is someone asks for it */
+
+/* from pmda.c: simulate PMDA busy */
+extern int limbo(void);
+
+/*
+ * all metrics supported in this PMD - one table entry for each
+ */
+
+static pmDesc desctab[] = {
+/* control */
+ { PMDA_PMID(0,0), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* daemon-pid */
+ { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* seconds */
+ { PMDA_PMID(0,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) },
+/* milliseconds */
+ { PMDA_PMID(0,3), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) },
+/* load */
+ { PMDA_PMID(0,4), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* colour */
+ { PMDA_PMID(0,5), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bin */
+ { PMDA_PMID(0,6), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* drift */
+ { PMDA_PMID(0,7), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* step */
+ { PMDA_PMID(0,8), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* noinst */
+ { PMDA_PMID(0,9), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.one */
+ { PMDA_PMID(0,10), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.ten */
+ { PMDA_PMID(0,11), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.hundred */
+ { PMDA_PMID(0,12), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.million */
+ { PMDA_PMID(0,13), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.write_me */
+ { PMDA_PMID(0,14), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.one */
+ { PMDA_PMID(0,15), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.ten */
+ { PMDA_PMID(0,16), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.hundred */
+ { PMDA_PMID(0,17), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.million */
+ { PMDA_PMID(0,18), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.write_me */
+ { PMDA_PMID(0,19), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.one */
+ { PMDA_PMID(0,20), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.ten */
+ { PMDA_PMID(0,21), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.hundred */
+ { PMDA_PMID(0,22), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.million */
+ { PMDA_PMID(0,23), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.write_me */
+ { PMDA_PMID(0,24), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.one */
+ { PMDA_PMID(0,25), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.ten */
+ { PMDA_PMID(0,26), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.hundred */
+ { PMDA_PMID(0,27), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.million */
+ { PMDA_PMID(0,28), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.write_me */
+ { PMDA_PMID(0,29), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* string.null */
+ { PMDA_PMID(0,30), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* string.hullo */
+ { PMDA_PMID(0,31), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* string.write_me */
+ { PMDA_PMID(0,32), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* aggregate.null */
+ { PMDA_PMID(0,33), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* aggregate.hullo */
+ { PMDA_PMID(0,34), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* aggregate.write_me */
+ { PMDA_PMID(0,35), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* write_me */
+ { PMDA_PMID(0,36), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) },
+/* mirage */
+ { PMDA_PMID(0,37), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0) },
+/* mirage-longlong */
+ { PMDA_PMID(0,38), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_MSEC,0) },
+/* sysinfo */
+ { PMDA_PMID(0,39), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* pdu */
+ { PMDA_PMID(0,40), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* recv-pdu */
+ { PMDA_PMID(0,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* xmit-pdu */
+ { PMDA_PMID(0,42), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.pdu */
+ { PMDA_PMID(0,43), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.recv-pdu */
+ { PMDA_PMID(0,44), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.xmit-pdu */
+ { PMDA_PMID(0,45), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* lights */
+ { PMDA_PMID(0,46), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* magnitude */
+ { PMDA_PMID(0,47), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bucket - alias for bin, but different PMID */
+ { PMDA_PMID(0,48), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* needprofile - need explicit instance profile */
+ { PMDA_PMID(0,49), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* part_bin - bin, minus an instance or two */
+ { PMDA_PMID(0,50), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bogus_bin - bin, plus an instance or two */
+ { PMDA_PMID(0,51), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* hordes.one */
+ { PMDA_PMID(0,52), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* hordes.two */
+ { PMDA_PMID(0,53), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bad.unknown */
+ { PMDA_PMID(0,54), 0, 0, 0, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bad.nosupport */
+ { PMDA_PMID(0,55), PM_TYPE_NOSUPPORT, PM_INDOM_NULL, 0, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* not_ready */
+ { PMDA_PMID(0,56), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* wrap.long */
+ { PMDA_PMID(0,57), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* wrap.ulong */
+ { PMDA_PMID(0,58), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* wrap.longlong */
+ { PMDA_PMID(0,59), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* wrap.ulonglong */
+ { PMDA_PMID(0,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* dodgey.control */
+ { PMDA_PMID(0,61), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* dodgey.value */
+ { PMDA_PMID(0,62), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* step_counter */
+ { PMDA_PMID(0,63), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* rapid */
+ { PMDA_PMID(0,64), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* scale_step.bytes_up */
+ { PMDA_PMID(0,65), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) },
+/* scale_step.bytes_down */
+ { PMDA_PMID(0,66), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) },
+/* scale_step.count_up */
+ { PMDA_PMID(0,67), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE) },
+/* scale_step.count_down */
+ { PMDA_PMID(0,68), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* scale_step.time_up_secs */
+ { PMDA_PMID(0,69), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) },
+/* scale_step.time_up_nanosecs */
+ { PMDA_PMID(0,70), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,1,0,0,PM_TIME_NSEC,0) },
+/* scale_step.none_up */
+ { PMDA_PMID(0,71), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* const_rate.value */
+ { PMDA_PMID(0,72), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* const_rate.gradient */
+ { PMDA_PMID(0,73), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* error_code */
+ { PMDA_PMID(0,74), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* error_check */
+ { PMDA_PMID(0,75), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* dynamic.counter */
+ { PMDA_PMID(0,76), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* dynamic.discrete */
+ { PMDA_PMID(0,77), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* dynamic.instant */
+ { PMDA_PMID(0,78), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* many.count */
+ { PMDA_PMID(0,79), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,0) },
+/* many.int */
+ { PMDA_PMID(0,80), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,0) },
+/* byte_ctr */
+ { PMDA_PMID(0,81), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) },
+/* byte_rate */
+ { PMDA_PMID(0,82), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) },
+/* kbyte_ctr */
+ { PMDA_PMID(0,83), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* kbyte_rate */
+ { PMDA_PMID(0,84), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0) },
+/* byte_rate_per_hour */
+ { PMDA_PMID(0,85), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_HOUR,0) },
+/* dynamic.meta.metric - pmDesc here is a fake, use magic */
+ { PMDA_PMID(0,86), 0, 0, 0, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* dynamic.meta.pmdesc.type */
+ { PMDA_PMID(0,87), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* dynamic.meta.pmdesc.indom */
+ { PMDA_PMID(0,88), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* dynamic.meta.pmdesc.sem */
+ { PMDA_PMID(0,89), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* dynamic.meta.pmdesc.units */
+ { PMDA_PMID(0,90), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* datasize */
+ { PMDA_PMID(0,91), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* darkness */
+ { PMDA_PMID(0,92), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.one */
+ { PMDA_PMID(0,93), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.ten */
+ { PMDA_PMID(0,94), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.hundred */
+ { PMDA_PMID(0,95), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.million */
+ { PMDA_PMID(0,96), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.write_me */
+ { PMDA_PMID(0,97), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.one */
+ { PMDA_PMID(0,98), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.ten */
+ { PMDA_PMID(0,99), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.hundred */
+ { PMDA_PMID(0,100), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.million */
+ { PMDA_PMID(0,101), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.write_me */
+ { PMDA_PMID(0,102), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.bin */
+ { PMDA_PMID(0,103), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* long.bin_ctr */
+ { PMDA_PMID(0,104), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* ulong.bin */
+ { PMDA_PMID(0,105), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulong.bin_ctr */
+ { PMDA_PMID(0,106), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* float.bin */
+ { PMDA_PMID(0,107), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* float.bin_ctr */
+ { PMDA_PMID(0,108), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* longlong.bin */
+ { PMDA_PMID(0,109), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* longlong.bin_ctr */
+ { PMDA_PMID(0,110), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* ulonglong.bin */
+ { PMDA_PMID(0,111), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* ulonglong.bin_ctr */
+ { PMDA_PMID(0,112), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* double.bin */
+ { PMDA_PMID(0,113), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* double.bin_ctr */
+ { PMDA_PMID(0,114), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) },
+/* sample.ulong.count.base */
+ { PMDA_PMID(0,115), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(-1,0,1,PM_SPACE_MBYTE,0,PM_COUNT_ONE) },
+/* sample.ulong.count.deca */
+ { PMDA_PMID(0,116), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(-1,0,1,PM_SPACE_MBYTE,0,PM_COUNT_ONE+1) },
+/* sample.ulong.count.hecto */
+ { PMDA_PMID(0,117), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(-1,0,1,PM_SPACE_MBYTE,0,PM_COUNT_ONE+2) },
+/* sample.ulong.count.kilo */
+ { PMDA_PMID(0,118), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(-1,0,1,PM_SPACE_MBYTE,0,PM_COUNT_ONE+3) },
+/* sample.ulong.count.mega */
+ { PMDA_PMID(0,119), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(-1,0,1,PM_SPACE_MBYTE,0,PM_COUNT_ONE+6) },
+/* scramble.version */
+ { PMDA_PMID(0,120), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* scramble.bin */
+ { PMDA_PMID(0,121), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* percontext.control.ctx */
+ { PMDA_PMID(0,122), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.control.active */
+ { PMDA_PMID(0,123), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.control.start */
+ { PMDA_PMID(0,124), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* percontext.control.end */
+ { PMDA_PMID(0,125), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* event.reset */
+ { PMDA_PMID(0,126), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.type */
+ { PMDA_PMID(0,127), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_32 */
+ { PMDA_PMID(0,128), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_u32 */
+ { PMDA_PMID(0,129), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_64 */
+ { PMDA_PMID(0,130), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_u64 */
+ { PMDA_PMID(0,131), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_float */
+ { PMDA_PMID(0,132), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_double */
+ { PMDA_PMID(0,133), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_string */
+ { PMDA_PMID(0,134), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.param_aggregate */
+ { PMDA_PMID(0,135), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.records */
+ { PMDA_PMID(0,136), PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.no_indom_records */
+ { PMDA_PMID(0,137), PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* bad.novalues */
+ { PMDA_PMID(0,138), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.highres_records */
+ { PMDA_PMID(0,139), PM_TYPE_HIGHRES_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* event.reset */
+ { PMDA_PMID(0,140), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+
+/*
+ * dynamic PMNS ones
+ * secret.bar
+ */
+ { PMDA_PMID(0,1000), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) },
+/* secret.foo.one */
+ { PMDA_PMID(0,1001), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.two */
+ { PMDA_PMID(0,1002), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.bar.three */
+ { PMDA_PMID(0,1003), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.bar.four */
+ { PMDA_PMID(0,1004), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.bar.grunt.five */
+ { PMDA_PMID(0,1005), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.bar.grunt.snort.six */
+ { PMDA_PMID(0,1006), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+/* secret.foo.bar.grunt.snort.seven */
+ { PMDA_PMID(0,1007), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+
+/* bigid */
+ { PMDA_PMID(0,1023), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) },
+
+/* End-of-List */
+ { PM_ID_NULL, 0, 0, 0, { 0, 0, 0, 0, 0, 0 } }
+};
+static int direct_map = 1;
+static int ndesc = sizeof(desctab)/sizeof(desctab[0]);
+
+static pmDesc magic =
+ { PMDA_PMID(0,86), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(1,-1,0,PM_SPACE_BYTE,PM_TIME_SEC,0) };
+
+static pmdaInstid _colour[] = {
+ { 0, "red" }, { 1, "green" }, { 2, "blue" }
+};
+
+static pmdaInstid _bin[] = {
+ { 100, "bin-100" }, { 200, "bin-200" }, { 300, "bin-300" },
+ { 400, "bin-400" }, { 500, "bin-500" }, { 600, "bin-600" },
+ { 700, "bin-700" }, { 800, "bin-800" }, { 900, "bin-900" }
+};
+
+static pmdaInstid _scramble[] = {
+ { 100, "bin-100" }, { 200, "bin-200" }, { 300, "bin-300" },
+ { 400, "bin-400" }, { 500, "bin-500" }, { 600, "bin-600" },
+ { 700, "bin-700" }, { 800, "bin-800" }, { 900, "bin-900" }
+};
+
+static long scramble_ver = 0;
+
+static pmdaInstid _family[] = {
+ { 0, "colleen" }, { 1, "terry" }, { 2, "emma" }, { 3, "cathy" }, { 4, "fat bald bastard" }
+};
+
+static pmdaInstid _dodgey[] = {
+ { 1, NULL}, { 2, NULL }, { 3, NULL }, { 4, NULL }, { 5, NULL }
+};
+
+static pmdaInstid _hordes[] = {
+ { 0, "0" }, { 1, "1" }, { 2, "2" }, { 3, "3" }, { 4, "4" },
+ { 5, "5" }, { 6, "6" }, { 7, "7" }, { 8, "8" }, { 9, "9" },
+ { 10, "10" }, { 11, "11" }, { 12, "12" }, { 13, "13" }, { 14, "14" },
+ { 15, "15" }, { 16, "16" }, { 17, "17" }, { 18, "18" }, { 19, "19" },
+ { 20, "20" }, { 21, "21" }, { 22, "22" }, { 23, "23" }, { 24, "24" },
+ { 25, "25" }, { 26, "26" }, { 27, "27" }, { 28, "28" }, { 29, "29" },
+ { 30, "30" }, { 31, "31" }, { 32, "32" }, { 33, "33" }, { 34, "34" },
+ { 35, "35" }, { 36, "36" }, { 37, "37" }, { 38, "38" }, { 39, "39" },
+ { 40, "40" }, { 41, "41" }, { 42, "42" }, { 43, "43" }, { 44, "44" },
+ { 45, "45" }, { 46, "46" }, { 47, "47" }, { 48, "48" }, { 49, "49" },
+ { 50, "50" }, { 51, "51" }, { 52, "52" }, { 53, "53" }, { 54, "54" },
+ { 55, "55" }, { 56, "56" }, { 57, "57" }, { 58, "58" }, { 59, "59" },
+ { 60, "60" }, { 61, "61" }, { 62, "62" }, { 63, "63" }, { 64, "64" },
+ { 65, "65" }, { 66, "66" }, { 67, "67" }, { 68, "68" }, { 69, "69" },
+ { 70, "70" }, { 71, "71" }, { 72, "72" }, { 73, "73" }, { 74, "74" },
+ { 75, "75" }, { 76, "76" }, { 77, "77" }, { 78, "78" }, { 79, "79" },
+ { 80, "80" }, { 81, "81" }, { 82, "82" }, { 83, "83" }, { 84, "84" },
+ { 85, "85" }, { 86, "86" }, { 87, "87" }, { 88, "88" }, { 89, "89" },
+ { 90, "90" }, { 91, "91" }, { 92, "92" }, { 93, "93" }, { 94, "94" },
+ { 95, "95" }, { 96, "96" }, { 97, "97" }, { 98, "98" }, { 99, "99" },
+ {100, "100" }, {101, "101" }, {102, "102" }, {103, "103" }, {104, "104" },
+ {105, "105" }, {106, "106" }, {107, "107" }, {108, "108" }, {109, "109" },
+ {110, "110" }, {111, "111" }, {112, "112" }, {113, "113" }, {114, "114" },
+ {115, "115" }, {116, "116" }, {117, "117" }, {118, "118" }, {119, "119" },
+ {120, "120" }, {121, "121" }, {122, "122" }, {123, "123" }, {124, "124" },
+ {125, "125" }, {126, "126" }, {127, "127" }, {128, "128" }, {129, "129" },
+ {130, "130" }, {131, "131" }, {132, "132" }, {133, "133" }, {134, "134" },
+ {135, "135" }, {136, "136" }, {137, "137" }, {138, "138" }, {139, "139" },
+ {140, "140" }, {141, "141" }, {142, "142" }, {143, "143" }, {144, "144" },
+ {145, "145" }, {146, "146" }, {147, "147" }, {148, "148" }, {149, "149" },
+ {150, "150" }, {151, "151" }, {152, "152" }, {153, "153" }, {154, "154" },
+ {155, "155" }, {156, "156" }, {157, "157" }, {158, "158" }, {159, "159" },
+ {160, "160" }, {161, "161" }, {162, "162" }, {163, "163" }, {164, "164" },
+ {165, "165" }, {166, "166" }, {167, "167" }, {168, "168" }, {169, "169" },
+ {170, "170" }, {171, "171" }, {172, "172" }, {173, "173" }, {174, "174" },
+ {175, "175" }, {176, "176" }, {177, "177" }, {178, "178" }, {179, "179" },
+ {180, "180" }, {181, "181" }, {182, "182" }, {183, "183" }, {184, "184" },
+ {185, "185" }, {186, "186" }, {187, "187" }, {188, "188" }, {189, "189" },
+ {190, "190" }, {191, "191" }, {192, "192" }, {193, "193" }, {194, "194" },
+ {195, "195" }, {196, "196" }, {197, "197" }, {198, "198" }, {199, "199" },
+ {200, "200" }, {201, "201" }, {202, "202" }, {203, "203" }, {204, "204" },
+ {205, "205" }, {206, "206" }, {207, "207" }, {208, "208" }, {209, "209" },
+ {210, "210" }, {211, "211" }, {212, "212" }, {213, "213" }, {214, "214" },
+ {215, "215" }, {216, "216" }, {217, "217" }, {218, "218" }, {219, "219" },
+ {220, "220" }, {221, "221" }, {222, "222" }, {223, "223" }, {224, "224" },
+ {225, "225" }, {226, "226" }, {227, "227" }, {228, "228" }, {229, "229" },
+ {230, "230" }, {231, "231" }, {232, "232" }, {233, "233" }, {234, "234" },
+ {235, "235" }, {236, "236" }, {237, "237" }, {238, "238" }, {239, "239" },
+ {240, "240" }, {241, "241" }, {242, "242" }, {243, "243" }, {244, "244" },
+ {245, "245" }, {246, "246" }, {247, "247" }, {248, "248" }, {249, "249" },
+ {250, "250" }, {251, "251" }, {252, "252" }, {253, "253" }, {254, "254" },
+ {255, "255" }, {256, "256" }, {257, "257" }, {258, "258" }, {259, "259" },
+ {260, "260" }, {261, "261" }, {262, "262" }, {263, "263" }, {264, "264" },
+ {265, "265" }, {266, "266" }, {267, "267" }, {268, "268" }, {269, "269" },
+ {270, "270" }, {271, "271" }, {272, "272" }, {273, "273" }, {274, "274" },
+ {275, "275" }, {276, "276" }, {277, "277" }, {278, "278" }, {279, "279" },
+ {280, "280" }, {281, "281" }, {282, "282" }, {283, "283" }, {284, "284" },
+ {285, "285" }, {286, "286" }, {287, "287" }, {288, "288" }, {289, "289" },
+ {290, "290" }, {291, "291" }, {292, "292" }, {293, "293" }, {294, "294" },
+ {295, "295" }, {296, "296" }, {297, "297" }, {298, "298" }, {299, "299" },
+ {300, "300" }, {301, "301" }, {302, "302" }, {303, "303" }, {304, "304" },
+ {305, "305" }, {306, "306" }, {307, "307" }, {308, "308" }, {309, "309" },
+ {310, "310" }, {311, "311" }, {312, "312" }, {313, "313" }, {314, "314" },
+ {315, "315" }, {316, "316" }, {317, "317" }, {318, "318" }, {319, "319" },
+ {320, "320" }, {321, "321" }, {322, "322" }, {323, "323" }, {324, "324" },
+ {325, "325" }, {326, "326" }, {327, "327" }, {328, "328" }, {329, "329" },
+ {330, "330" }, {331, "331" }, {332, "332" }, {333, "333" }, {334, "334" },
+ {335, "335" }, {336, "336" }, {337, "337" }, {338, "338" }, {339, "339" },
+ {340, "340" }, {341, "341" }, {342, "342" }, {343, "343" }, {344, "344" },
+ {345, "345" }, {346, "346" }, {347, "347" }, {348, "348" }, {349, "349" },
+ {350, "350" }, {351, "351" }, {352, "352" }, {353, "353" }, {354, "354" },
+ {355, "355" }, {356, "356" }, {357, "357" }, {358, "358" }, {359, "359" },
+ {360, "360" }, {361, "361" }, {362, "362" }, {363, "363" }, {364, "364" },
+ {365, "365" }, {366, "366" }, {367, "367" }, {368, "368" }, {369, "369" },
+ {370, "370" }, {371, "371" }, {372, "372" }, {373, "373" }, {374, "374" },
+ {375, "375" }, {376, "376" }, {377, "377" }, {378, "378" }, {379, "379" },
+ {380, "380" }, {381, "381" }, {382, "382" }, {383, "383" }, {384, "384" },
+ {385, "385" }, {386, "386" }, {387, "387" }, {388, "388" }, {389, "389" },
+ {390, "390" }, {391, "391" }, {392, "392" }, {393, "393" }, {394, "394" },
+ {395, "395" }, {396, "396" }, {397, "397" }, {398, "398" }, {399, "399" },
+ {400, "400" }, {401, "401" }, {402, "402" }, {403, "403" }, {404, "404" },
+ {405, "405" }, {406, "406" }, {407, "407" }, {408, "408" }, {409, "409" },
+ {410, "410" }, {411, "411" }, {412, "412" }, {413, "413" }, {414, "414" },
+ {415, "415" }, {416, "416" }, {417, "417" }, {418, "418" }, {419, "419" },
+ {420, "420" }, {421, "421" }, {422, "422" }, {423, "423" }, {424, "424" },
+ {425, "425" }, {426, "426" }, {427, "427" }, {428, "428" }, {429, "429" },
+ {430, "430" }, {431, "431" }, {432, "432" }, {433, "433" }, {434, "434" },
+ {435, "435" }, {436, "436" }, {437, "437" }, {438, "438" }, {439, "439" },
+ {440, "440" }, {441, "441" }, {442, "442" }, {443, "443" }, {444, "444" },
+ {445, "445" }, {446, "446" }, {447, "447" }, {448, "448" }, {449, "449" },
+ {450, "450" }, {451, "451" }, {452, "452" }, {453, "453" }, {454, "454" },
+ {455, "455" }, {456, "456" }, {457, "457" }, {458, "458" }, {459, "459" },
+ {460, "460" }, {461, "461" }, {462, "462" }, {463, "463" }, {464, "464" },
+ {465, "465" }, {466, "466" }, {467, "467" }, {468, "468" }, {469, "469" },
+ {470, "470" }, {471, "471" }, {472, "472" }, {473, "473" }, {474, "474" },
+ {475, "475" }, {476, "476" }, {477, "477" }, {478, "478" }, {479, "479" },
+ {480, "480" }, {481, "481" }, {482, "482" }, {483, "483" }, {484, "484" },
+ {485, "485" }, {486, "486" }, {487, "487" }, {488, "488" }, {489, "489" },
+ {490, "490" }, {491, "491" }, {492, "492" }, {493, "493" }, {494, "494" },
+ {495, "495" }, {496, "496" }, {497, "497" }, {498, "498" }, {499, "499" }
+};
+
+static pmdaInstid _events[] = {
+ { 0, "fungus" }, { 1, "bogus" }
+};
+
+/* all domains supported in this PMDA - one entry each */
+static pmdaIndom indomtab[] = {
+#define COLOUR_INDOM 0
+ { 0, 3, _colour },
+#define BIN_INDOM 1
+ { 0, 9, _bin },
+#define MIRAGE_INDOM 2
+ { 0, 0, NULL },
+#define FAMILY_INDOM 3
+ { 0, 5, _family },
+#define HORDES_INDOM 4
+ { 0, 500, _hordes },
+#define DODGEY_INDOM 5
+ { 0, 5, _dodgey },
+#define DYNAMIC_INDOM 6
+ { 0, 0, NULL },
+#define MANY_INDOM 7
+ { 0, 5, NULL },
+#define SCRAMBLE_INDOM 8
+ { 0, 9, _scramble },
+#define EVENT_INDOM 9
+ { 0, 2, _events },
+
+ { PM_INDOM_NULL, 0, 0 }
+};
+
+static struct timeval _then; /* time we started */
+static time_t _start; /* ditto */
+static __pmProfile *_profile; /* last received profile */
+static int _x;
+static pmdaIndom *_idp;
+static int _singular = -1; /* =0 for singular values */
+static int _ordinal = -1; /* >=0 for non-singular values */
+static int _control; /* the control variable */
+static int _mypid;
+static int _drift = 200; /* starting value for drift */
+static int _sign = -1; /* up/down for drift */
+static int _step = 20; /* magnitude of step */
+static int _write_me = 2; /* constant, but modifiable */
+static __int32_t _long = 13; /* long.write_me */
+static __uint32_t _ulong = 13; /* ulong.write_me */
+static __int64_t _longlong = 13; /* longlong.write_me */
+static __uint64_t _ulonglong = 13;/* ulonglong.write_me */
+static float _float = 13; /* float.write_me */
+static double _double = 13; /* double.write_me */
+static char *_string; /* string.write_me */
+static pmValueBlock *_aggr33; /* aggregate.null */
+static pmValueBlock *_aggr34; /* aggregate.hullo */
+static pmValueBlock *_aggr35; /* aggregate.write_me */
+static long _col46; /* lights */
+static int _n46; /* sample count for lights */
+static long _mag47; /* magnitude */
+static int _n47; /* sample count for magnitude */
+static __uint32_t _rapid; /* counts @ 8x10^8 per fetch */
+static int _dyn_max = -1;
+static int *_dyn_ctr;
+static int many_count = 5;
+
+static pmValueBlock *sivb=NULL;
+
+static __int32_t _wrap = 0; /* wrap.long */
+static __uint32_t _u_wrap = 0; /* wrap.ulong */
+static __int64_t _ll_wrap = 0; /* wrap.longlong */
+static __uint64_t _ull_wrap = 0; /* wrap.ulonglong */
+
+static int _error_code = 0;/* return this! */
+
+static int dodgey = 5; /* dodgey.control */
+static int tmp_dodgey = 5;
+static int new_dodgey = 0;
+
+static double scale_step_bytes_up = 1;
+static double scale_step_bytes_down = 1;
+static double scale_step_count_up = 1;
+static double scale_step_count_down = 1;
+static double scale_step_time_up_secs = 1;
+static double scale_step_time_up_nanosecs = 1;
+static double scale_step_none_up = 1;
+static int scale_step_number[7] = {0,0,0,0,0,0,0};
+
+static __uint32_t const_rate_gradient = 0;
+static __uint32_t const_rate_value = 10485760;
+static struct timeval const_rate_timestamp = {0,0};
+
+/* this needs to be visible in pmda.c */
+int not_ready = 0; /* sleep interval in seconds */
+int sample_done = 0;/* pending request to terminate, see sample_store() */
+
+int _isDSO = 1; /* =0 I am a daemon */
+
+/*
+ * dynamic PMNS metrics ... nothing to do with redo_dynamic() and dynamic
+ * InDoms
+ */
+static struct {
+ char *name;
+ pmID pmid;
+ int mark;
+} dynamic_ones[] = {
+ { "secret.foo.bar.max.redirect", PMDA_PMID(0,0) },
+ { "secret.bar", PMDA_PMID(0,1000) },
+ { "secret.foo.one", PMDA_PMID(0,1001) },
+ { "secret.foo.two", PMDA_PMID(0,1002) },
+ { "secret.foo.bar.three", PMDA_PMID(0,1003) },
+ { "secret.foo.bar.four", PMDA_PMID(0,1004) },
+ { "secret.foo.bar.grunt.five", PMDA_PMID(0,1005) },
+ { "secret.foo.bar.grunt.snort.six", PMDA_PMID(0,1006) },
+ { "secret.foo.bar.grunt.snort.huff.puff.seven", PMDA_PMID(0,1007) }
+};
+static int numdyn = sizeof(dynamic_ones)/sizeof(dynamic_ones[0]);
+
+static int
+redo_dynamic(void)
+{
+ int err;
+ int i;
+ int sep = __pmPathSeparator();
+ static struct stat lastsbuf;
+ struct stat statbuf;
+ pmdaIndom *idp = &indomtab[DYNAMIC_INDOM];
+ char mypath[MAXPATHLEN];
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "sample" "%c" "dynamic.indom",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+
+ if (stat(mypath, &statbuf) == 0) {
+#if defined(HAVE_ST_MTIME_WITH_E) && defined(HAVE_STAT_TIME_T)
+ if (statbuf.st_mtime != lastsbuf.st_mtime)
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if ((statbuf.st_mtimespec.tv_sec != lastsbuf.st_mtimespec.tv_sec) ||
+ (statbuf.st_mtimespec.tv_nsec != lastsbuf.st_mtimespec.tv_nsec))
+#elif defined(HAVE_STAT_TIMESTRUC) || defined(HAVE_STAT_TIMESPEC) || defined(HAVE_STAT_TIMESPEC_T)
+ if ((statbuf.st_mtim.tv_sec != lastsbuf.st_mtim.tv_sec) ||
+ (statbuf.st_mtim.tv_nsec != lastsbuf.st_mtim.tv_nsec))
+#else
+!bozo!
+#endif
+ {
+ FILE *fspec;
+ int newinst;
+ char newname[100]; /* hack, secret max */
+ int numinst;
+
+ lastsbuf = statbuf;
+ if ((fspec = fopen(mypath, "r")) != NULL) {
+ for (i = 0; i < idp->it_numinst; i++) {
+ free(idp->it_set[i].i_name);
+ }
+ for (i = 0; i <= _dyn_max; i++) {
+ _dyn_ctr[i] = -_dyn_ctr[i];
+ }
+ free(idp->it_set);
+ idp->it_numinst = 0;
+ idp->it_set = NULL;
+ numinst = 0;
+ for ( ; ; ) {
+ if (fscanf(fspec, "%d %s", &newinst, newname) != 2)
+ break;
+ numinst++;
+ if ((idp->it_set = (pmdaInstid *)realloc(idp->it_set, numinst * sizeof(pmdaInstid))) == NULL) {
+ err = -oserror();
+ fclose(fspec);
+ return err;
+ }
+ idp->it_set[numinst-1].i_inst = newinst;
+ if ((idp->it_set[numinst-1].i_name = strdup(newname)) == NULL) {
+ err = -oserror();
+ free(idp->it_set);
+ idp->it_set = NULL;
+ fclose(fspec);
+ return err;
+ }
+ if (newinst > _dyn_max) {
+ if ((_dyn_ctr = (int *)realloc(_dyn_ctr, (newinst+1)*sizeof(_dyn_ctr[0]))) == NULL) {
+ err = -oserror();
+ free(idp->it_set);
+ idp->it_set = NULL;
+ fclose(fspec);
+ return err;
+ }
+ for (i = _dyn_max+1; i <= newinst; i++)
+ _dyn_ctr[i] = 0;
+ _dyn_max = newinst;
+ }
+ _dyn_ctr[newinst] = -_dyn_ctr[newinst];
+ }
+ fclose(fspec);
+ idp->it_numinst = numinst;
+
+ for (i = 0; i <= _dyn_max; i++) {
+ if (_dyn_ctr[i] < 0)
+ _dyn_ctr[i] = 0;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "redo instance domain for dynamic: numinst: %d\n", idp->it_numinst);
+ for (i = 0; i < idp->it_numinst; i++) {
+ fprintf(stderr, " %d \"%s\"", idp->it_set[i].i_inst, idp->it_set[i].i_name);
+ }
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ }
+ }
+ else {
+ /* control file is not present, empty indom if not already so */
+ if (idp->it_set != NULL) {
+ for (i = 0; i < idp->it_numinst; i++) {
+ free(idp->it_set[i].i_name);
+ }
+ free(idp->it_set);
+ idp->it_set = NULL;
+ idp->it_numinst = 0;
+ for (i = 0; i <= _dyn_max; i++) {
+ _dyn_ctr[i] = 0;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "redo instance domain for dynamic: numinst: 0 (no control file)\n");
+#endif
+ }
+ }
+
+ for (i = 0; i < idp->it_numinst; i++) {
+ _dyn_ctr[idp->it_set[i].i_inst]++;
+ }
+
+ return 0;
+}
+
+#define MANY_MAX_LEN 10
+
+static int
+redo_many(void)
+{
+ pmdaIndom *idp;
+ int a;
+ static char *tags=NULL;
+ char *tag;
+
+ /* sanity check, range clip */
+
+ if (many_count<0) many_count=0;
+ if (many_count>999999) many_count=999999;
+
+ idp = &indomtab[MANY_INDOM];
+
+ /* realloc instances buffer */
+
+ idp->it_set = realloc(idp->it_set, many_count*sizeof(pmdaInstid));
+ if (!idp->it_set) {
+ idp->it_numinst=0;
+ many_count=0;
+ return -oserror();
+ }
+
+ /* realloc string buffer */
+
+ tags = realloc(tags, many_count*MANY_MAX_LEN);
+ if (!idp->it_set) {
+ idp->it_numinst=0;
+ many_count=0;
+ return -oserror();
+ }
+
+ /* set number of instances */
+
+ idp->it_numinst=many_count;
+
+ /* generate instances */
+
+ tag=tags;
+ for (a=0;a<many_count;a++) {
+ idp->it_set[a].i_inst=a;
+ idp->it_set[a].i_name=tag;
+ tag+=sprintf(tag,"i-%d",a)+1;
+ }
+
+ return 0;
+}
+
+static int
+redo_mirage(void)
+{
+ static time_t doit = 0;
+ time_t now;
+ int i;
+ int j;
+ static int newinst = 0;
+ pmdaIndom *idp;
+
+ now = time(NULL);
+ if (now < doit)
+ return 0;
+
+ idp = &indomtab[MIRAGE_INDOM];
+ if (idp->it_set == NULL) {
+ /* first time */
+ if ((idp->it_set = (pmdaInstid *)malloc(sizeof(pmdaInstid))) == NULL)
+ return -oserror();
+ if ((idp->it_set[0].i_name = (char *)malloc(5)) == NULL) {
+ idp->it_set = NULL;
+ return -oserror();
+ }
+ idp->it_numinst = 1;
+ idp->it_set[0].i_inst = 0;
+ sprintf(idp->it_set[0].i_name, "m-%02d", 0);
+ }
+ else {
+ int numinst;
+ int cull;
+
+ numinst = 1;
+ cull = idp->it_numinst > 12 ? idp->it_numinst/2 : idp->it_numinst;
+ for (i = 1; i < idp->it_numinst; i++) {
+ if (lrand48() % 1000 < 1000 / cull) {
+ /* delete this one */
+ free(idp->it_set[i].i_name);
+ continue;
+ }
+ idp->it_set[numinst++] = idp->it_set[i];
+ }
+ if (numinst != idp->it_numinst) {
+ if ((idp->it_set = (pmdaInstid *)realloc(idp->it_set, numinst * sizeof(pmdaInstid))) == NULL) {
+ idp->it_set = NULL;
+ idp->it_numinst = 0;
+ return -oserror();
+ }
+ idp->it_numinst = numinst;
+ }
+ for (i = 0; i < 2; i++) {
+ if (lrand48() % 1000 < 500) {
+ /* add a new one */
+ numinst++;
+ if ((idp->it_set = (pmdaInstid *)realloc(idp->it_set, numinst * sizeof(pmdaInstid))) == NULL) {
+ idp->it_set = NULL;
+ idp->it_numinst = 0;
+ return -oserror();
+ }
+ if ((idp->it_set[numinst-1].i_name = (char *)malloc(5)) == NULL) {
+ idp->it_set = NULL;
+ return -oserror();
+ }
+ for ( ; ; ) {
+ newinst = (newinst + 1) % 50;
+ for (j = 0; j < idp->it_numinst; j++) {
+ if (idp->it_set[j].i_inst == newinst)
+ break;
+ }
+ if (j == idp->it_numinst)
+ break;
+ }
+ idp->it_numinst = numinst;
+ idp->it_set[numinst-1].i_inst = newinst;
+ sprintf(idp->it_set[numinst-1].i_name, "m-%02d", newinst);
+ }
+ }
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "redo instance domain for mirage: numinst: %d\n", idp->it_numinst);
+ for (i = 0; i < idp->it_numinst; i++) {
+ fprintf(stderr, " %d \"%s\"", idp->it_set[i].i_inst, idp->it_set[i].i_name);
+ }
+ fputc('\n', stderr);
+ }
+#endif
+
+ doit = now + 10; /* no more than once every 10 seconds */
+ return 0;
+}
+
+static void
+redo_dodgey(void)
+{
+ int j;
+ int k;
+
+ if (dodgey <= 5) {
+ tmp_dodgey = dodgey;
+ new_dodgey = 0;
+ /* re-build full instance table */
+ for (j = 0; j < 5; j++) {
+ _dodgey[j].i_inst = j+1;
+ _dodgey[j].i_name[1] = '0' + j+1;
+ }
+ indomtab[DODGEY_INDOM].it_numinst = 5;
+ }
+ else {
+ j = (int)(lrand48() % 1000);
+ if (j < 33)
+ tmp_dodgey = PM_ERR_NOAGENT;
+ else if (j < 66)
+ tmp_dodgey = PM_ERR_AGAIN;
+ else if (j < 99)
+ tmp_dodgey = PM_ERR_APPVERSION;
+ else {
+ /*
+ * create partial instance table, instances appear
+ * at random with prob = 0.5
+ */
+ k = 0;
+ for (j = 0; j < 5; j++) {
+ if (lrand48() % 100 < 49) {
+ _dodgey[k].i_inst = j+1;
+ _dodgey[k].i_name[1] = '0' + j+1;
+ k++;
+ }
+ }
+ tmp_dodgey = indomtab[DODGEY_INDOM].it_numinst = k;
+ }
+ /* fetches before re-setting */
+ new_dodgey = (int)(lrand48() % dodgey);
+ }
+}
+
+/*
+ * count the number of instances in an instance domain
+ */
+static int
+cntinst(pmInDom indom)
+{
+ pmdaIndom *idp;
+
+ if (indom == PM_INDOM_NULL)
+ return 1;
+ for (idp = indomtab; idp->it_indom != PM_INDOM_NULL; idp++) {
+ if (idp->it_indom == indom)
+ return idp->it_numinst;
+ }
+ __pmNotifyErr(LOG_WARNING, "cntinst: unknown pmInDom 0x%x", indom);
+ return 0;
+}
+
+/*
+ * commence a new round of instance selection
+ * flag == 1 for prefetch instance counting
+ * flag == 0 for iteration over instances to retrieve values
+ */
+static void
+startinst(pmInDom indom, int flag)
+{
+ _ordinal = _singular = -1;
+ if (indom == PM_INDOM_NULL) {
+ /* singular value */
+ _singular = 0;
+ return;
+ }
+ for (_idp = indomtab; _idp->it_indom != PM_INDOM_NULL; _idp++) {
+ if (_idp->it_indom == indom) {
+ /* multiple values are possible */
+ _ordinal = 0;
+ if (flag == 1 && _idp == &indomtab[SCRAMBLE_INDOM]) {
+ /*
+ * indomtab[BIN_INDOM].it_set[] is the same size as
+ * indomtab[SCRAMBLE_INDOM].it_set[] (maxnuminst
+ * entries)
+ */
+ int i;
+ int k = 0;
+ int maxnuminst = indomtab[BIN_INDOM].it_numinst;
+ srand48((scramble_ver << 10) + 13);
+ scramble_ver++;
+ for (i = 0; i < maxnuminst; i++)
+ indomtab[SCRAMBLE_INDOM].it_set[i].i_inst = PM_IN_NULL;
+ for (i = 0; i < maxnuminst; i++) {
+ /* skip 1/3 of instances */
+ if ((lrand48() % 100) < 33) continue;
+ /* order of instances is random */
+ for ( ; ; ) {
+ k = lrand48() % maxnuminst;
+ if (indomtab[SCRAMBLE_INDOM].it_set[k].i_inst != PM_IN_NULL)
+ continue;
+ indomtab[SCRAMBLE_INDOM].it_set[k].i_inst = indomtab[BIN_INDOM].it_set[i].i_inst;
+ indomtab[SCRAMBLE_INDOM].it_set[k].i_name = indomtab[BIN_INDOM].it_set[i].i_name;
+ break;
+ }
+ }
+ /* pack to remove skipped instances */
+ k = 0;
+ for (i = 0; i < maxnuminst; i++) {
+ if (indomtab[SCRAMBLE_INDOM].it_set[i].i_inst == PM_IN_NULL)
+ continue;
+ if (k < i) {
+ indomtab[SCRAMBLE_INDOM].it_set[k].i_inst = indomtab[SCRAMBLE_INDOM].it_set[i].i_inst;
+ indomtab[SCRAMBLE_INDOM].it_set[k].i_name = indomtab[SCRAMBLE_INDOM].it_set[i].i_name;
+ }
+ k++;
+ }
+ indomtab[SCRAMBLE_INDOM].it_numinst = k;
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * find next selected instance, if any
+ *
+ * EXCEPTION PCP 2.1.1: make use of __pmProfile much smarter, particularly when state for
+ * this indom is PM_PROFILE_EXCLUDE, then only need to consider inst
+ * values in the profile - this is a performance enhancement, and
+ * the simple method is functionally complete, particularly for
+ * stable (non-varying) instance domains
+ */
+static int
+nextinst(int *inst)
+{
+ int j;
+
+ if (_singular == 0) {
+ /* PM_INDOM_NULL ... just the one value */
+ *inst = 0;
+ _singular = -1;
+ return 1;
+ }
+ if (_ordinal >= 0) {
+ /* scan for next value in the profile */
+ for (j = _ordinal; j < _idp->it_numinst; j++) {
+ if (__pmInProfile(_idp->it_indom, _profile, _idp->it_set[j].i_inst)) {
+ *inst = _idp->it_set[j].i_inst;
+ _ordinal = j+1;
+ return 1;
+ }
+ }
+ _ordinal = -1;
+ }
+ return 0;
+}
+
+/*
+ * this routine is called at initialization to patch up any parts of the
+ * desctab that cannot be statically initialized, and to optionally
+ * modify our Performance Metrics Domain Id (dom)
+ */
+static void
+init_tables(int dom)
+{
+ int i;
+ __pmInDom_int b_indom;
+ __pmInDom_int *indomp;
+ __pmID_int *pmidp;
+ pmDesc *dp;
+
+ /* serial numbering is arbitrary, but must be unique in this PMD */
+ b_indom.flag = 0;
+ b_indom.domain = dom;
+ b_indom.serial = 1;
+ indomp = (__pmInDom_int *)&indomtab[COLOUR_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[BIN_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[MIRAGE_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[FAMILY_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[HORDES_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[DODGEY_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[DYNAMIC_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[MANY_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[SCRAMBLE_INDOM].it_indom;
+ *indomp = b_indom;
+ b_indom.serial++;
+ indomp = (__pmInDom_int *)&indomtab[EVENT_INDOM].it_indom;
+ *indomp = b_indom;
+
+ /* rewrite indom in desctab[] */
+ for (dp = desctab; dp->pmid != PM_ID_NULL; dp++) {
+ switch (dp->pmid) {
+ case PMDA_PMID(0,5): /* colour */
+ case PMDA_PMID(0,92): /* darkness */
+ dp->indom = indomtab[COLOUR_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,6): /* bin */
+ case PMDA_PMID(0,48): /* bucket */
+ case PMDA_PMID(0,50): /* part_bin */
+ case PMDA_PMID(0,51): /* bogus_bin */
+ case PMDA_PMID(0,103): /* long.bin */
+ case PMDA_PMID(0,104): /* long.bin_ctr */
+ case PMDA_PMID(0,105): /* ulong.bin */
+ case PMDA_PMID(0,106): /* ulong.bin_ctr */
+ case PMDA_PMID(0,107): /* float.bin */
+ case PMDA_PMID(0,108): /* float.bin_ctr */
+ case PMDA_PMID(0,109): /* longlong.bin */
+ case PMDA_PMID(0,110): /* longlong.bin_ctr */
+ case PMDA_PMID(0,111): /* ulonglong.bin */
+ case PMDA_PMID(0,112): /* ulonglong.bin_ctr */
+ case PMDA_PMID(0,113): /* double.bin */
+ case PMDA_PMID(0,114): /* double.bin_ctr */
+ dp->indom = indomtab[BIN_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,37): /* mirage */
+ dp->indom = indomtab[MIRAGE_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,38): /* mirage-longlong */
+ dp->indom = indomtab[MIRAGE_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,49): /* needprofile */
+ dp->indom = indomtab[FAMILY_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,52): /* hordes.one */
+ case PMDA_PMID(0,53): /* hordes.two */
+ dp->indom = indomtab[HORDES_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,62): /* dodgey.value */
+ dp->indom = indomtab[DODGEY_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,76): /* dynamic.counter */
+ case PMDA_PMID(0,77): /* dynamic.discrete */
+ case PMDA_PMID(0,78): /* dynamic.instant */
+ dp->indom = indomtab[DYNAMIC_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,80): /* many.int */
+ dp->indom = indomtab[MANY_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,121): /* scramble.bin */
+ dp->indom = indomtab[SCRAMBLE_INDOM].it_indom;
+ break;
+ case PMDA_PMID(0,136): /* event.records */
+ case PMDA_PMID(0,139): /* event.highres_records */
+ dp->indom = indomtab[EVENT_INDOM].it_indom;
+ break;
+ }
+ }
+
+ /* merge performance domain id part into PMIDs in pmDesc table */
+ for (i = 0; desctab[i].pmid != PM_ID_NULL; i++) {
+ pmidp = (__pmID_int *)&desctab[i].pmid;
+ pmidp->domain = dom;
+ if (direct_map && pmidp->item != i) {
+ direct_map = 0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmNotifyErr(LOG_WARNING, "sample_init: direct map disabled @ desctab[%d]", i);
+ }
+#endif
+ }
+ }
+ ndesc--;
+ pmidp = (__pmID_int *)&magic.pmid;
+ pmidp->domain = dom;
+
+ /* local hacks */
+ _string = (char *)calloc(1, 8);
+ strncpy(_string, "13", sizeof("13"));
+ _aggr33 = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE);
+ _aggr33->vlen = PM_VAL_HDR_SIZE + 0;
+ _aggr33->vtype = PM_TYPE_AGGREGATE;
+ _aggr34 = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE+strlen("hullo world!"));
+ _aggr34->vlen = PM_VAL_HDR_SIZE + strlen("hullo world!");
+ _aggr34->vtype = PM_TYPE_AGGREGATE;
+ memcpy(_aggr34->vbuf, "hullo world!", strlen("hullo world!"));
+ _aggr35 = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE+strlen("13"));
+ _aggr35->vlen = PM_VAL_HDR_SIZE + strlen("13");
+ _aggr35->vtype = PM_TYPE_AGGREGATE;
+ memcpy(_aggr35->vbuf, "13", strlen("13"));
+
+ (void)redo_many();
+}
+
+static int
+sample_profile(__pmProfile *prof, pmdaExt *ep)
+{
+ sample_inc_recv(ep->e_context);
+ _profile = prof;
+ return 0;
+}
+
+static int
+sample_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *ep)
+{
+ int i;
+ __pmInResult *res;
+ pmdaIndom *idp;
+ int err = 0;
+
+ sample_inc_recv(ep->e_context);
+ sample_inc_xmit(ep->e_context);
+
+ if (not_ready > 0) {
+ return limbo();
+ }
+
+ if (need_mirage && (i = redo_mirage()) < 0)
+ return i;
+ if (need_dynamic && (i = redo_dynamic()) < 0)
+ return i;
+
+ /*
+ * check this is an instance domain we know about -- code below
+ * assumes this test is complete
+ */
+ for (idp = indomtab; idp->it_indom != PM_INDOM_NULL; idp++) {
+ if (idp->it_indom == indom)
+ break;
+ }
+ if (idp->it_indom == PM_INDOM_NULL)
+ return PM_ERR_INDOM;
+
+ if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL)
+ return -oserror();
+ res->indom = indom;
+
+ if (name == NULL && inst == PM_IN_NULL)
+ res->numinst = cntinst(indom);
+ else
+ res->numinst = 1;
+
+ if (inst == PM_IN_NULL) {
+ if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) {
+ free(res);
+ return -oserror();
+ }
+ }
+ else
+ res->instlist = NULL;
+
+ if (name == NULL) {
+ if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (i = 0; i < res->numinst; i++)
+ res->namelist[0] = NULL;
+ }
+ else
+ res->namelist = NULL;
+
+ if (name == NULL && inst == PM_IN_NULL) {
+ /* return inst and name for everything */
+ for (i = 0; i < res->numinst; i++) {
+ res->instlist[i] = idp->it_set[i].i_inst;
+ if ((res->namelist[i] = strdup(idp->it_set[i].i_name)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ }
+ }
+ else if (name == NULL) {
+ /* given an inst, return the name */
+ for (i = 0; i < idp->it_numinst; i++) {
+ char *p;
+ if (inst == idp->it_set[i].i_inst) {
+ if ((res->namelist[0] = strdup(idp->it_set[i].i_name)) == NULL) {
+ __pmFreeInResult(res);
+ return -oserror();
+ }
+ for (p = res->namelist[0]; *p; p++) {
+ if (*p == ' ') {
+ *p = '\0';
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (i == idp->it_numinst)
+ err = 1;
+ }
+ else if (inst == PM_IN_NULL) {
+ /* given a name, return an inst */
+ char *p;
+ long len;
+ for (p = name; *p; p++) {
+ if (*p == ' ')
+ break;
+ }
+ len = p - name;
+ for (i = 0; i < idp->it_numinst; i++) {
+ if (strncmp(name, idp->it_set[i].i_name, len) == 0 &&
+ strlen(idp->it_set[i].i_name) >= len &&
+ (idp->it_set[i].i_name[len] == '\0' || idp->it_set[i].i_name[len] == ' ')) {
+ res->instlist[0] = idp->it_set[i].i_inst;
+ break;
+ }
+ }
+ if (i == idp->it_numinst)
+ err = 1;
+ }
+ else
+ err = 1;
+ if (err == 1) {
+ /* bogus arguments or instance id/name */
+ __pmFreeInResult(res);
+ return PM_ERR_INST;
+ }
+
+ *result = res;
+ return 0;
+}
+
+static int
+sample_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ int i;
+ const char *p;
+
+ /* skip the sample. or sampledso. part */
+ for (p = name; *p != '.' && *p; p++)
+ ;
+ if (*p == '.') p++;
+
+ for (i = 0; i < numdyn; i++) {
+ if (strcmp(p, dynamic_ones[i].name) == 0) {
+ *pmid = dynamic_ones[i].pmid;
+ return 0;
+ }
+ }
+
+ return PM_ERR_NAME;
+}
+
+static int
+sample_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ size_t len = 0;
+ int nmatch = 0;
+ int i;
+ char *pfx;
+ char *p;
+ char **list;
+
+ if (_isDSO)
+ pfx = "sampledso.";
+ else
+ pfx = "sample.";
+
+ for (i = 0; i < numdyn; i++) {
+ if (dynamic_ones[i].pmid == pmid) {
+ nmatch++;
+ len += strlen(pfx)+strlen(dynamic_ones[i].name)+1;
+ }
+ }
+
+ if (nmatch == 0)
+ return PM_ERR_PMID;
+
+ len += nmatch*sizeof(char *); /* pointers to names */
+
+ if ((list = (char **)malloc(len)) == NULL)
+ return -oserror();
+
+ p = (char *)&list[nmatch];
+ nmatch = 0;
+ for (i = 0; i < numdyn; i++) {
+ if (dynamic_ones[i].pmid == pmid) {
+ list[nmatch++] = p;
+ strcpy(p, pfx);
+ p += strlen(pfx);
+ strcpy(p, dynamic_ones[i].name);
+ p += strlen(dynamic_ones[i].name);
+ *p++ = '\0';
+ }
+ }
+ *nameset = list;
+
+ return nmatch;
+}
+
+static int
+sample_children(const char *name, int traverse, char ***offspring, int **status, pmdaExt *pmda)
+{
+ int i;
+ int j;
+ int nmatch;
+ int pfxlen;
+ int namelen;
+ const char *p;
+ char *q;
+ char *qend = NULL;
+ char **chn = NULL;
+ int *sts = NULL;
+ size_t len = 0;
+ size_t tlen = 0;
+
+ /* skip the sample. or sampledso. part */
+ for (p = name; *p != '.' && *p; p++)
+ ;
+ pfxlen = p - name;
+ if (*p == '.') p++;
+ namelen = strlen(p);
+
+ nmatch = 0;
+ for (i = 0; i < numdyn; i++) {
+ q = dynamic_ones[i].name;
+ if (strncmp(p, q, namelen) != 0) {
+ /* no prefix match */
+ dynamic_ones[i].mark = 0;
+ continue;
+ }
+ if (traverse == 0 && q[namelen] != '.') {
+ /* cannot be a child of name */
+ dynamic_ones[i].mark = 0;
+ continue;
+ }
+ if (traverse == 1 && q[namelen] != '.' && q[namelen] != '\0') {
+ /* cannot be name itself, not a child of name */
+ dynamic_ones[i].mark = 0;
+ continue;
+ }
+ if (traverse == 0) {
+ qend = &q[namelen+1];
+ while (*qend && *qend != '.')
+ qend++;
+ tlen = qend - &q[namelen+1];
+ for (j = 0; j < nmatch; j++) {
+ if (strncmp(&q[namelen+1], chn[j], tlen) == 0) {
+ /* already seen this child ... skip it */
+ break;
+ }
+ }
+ }
+ else {
+ /* traversal ... need this one */
+ j = nmatch;
+ }
+ if (j == nmatch) {
+ nmatch++;
+ if ((chn = (char **)realloc(chn, nmatch*sizeof(chn[0]))) == NULL) {
+ j = -oserror();
+ goto fail;
+ }
+ if ((sts = (int *)realloc(sts, nmatch*sizeof(sts[0]))) == NULL) {
+ j = -oserror();
+ goto fail;
+ }
+ if (traverse == 0) {
+ /*
+ * descendents only ... just want the next component of
+ * PMNS name
+ */
+ if ((chn[nmatch-1] = (char *)malloc(tlen+1)) == NULL) {
+ j = -oserror();
+ goto fail;
+ }
+ strncpy(chn[nmatch-1], &q[namelen+1], tlen);
+ chn[nmatch-1][tlen] = '\0';
+ if (*qend == '.')
+ sts[nmatch-1] = PMNS_NONLEAF_STATUS;
+ else
+ sts[nmatch-1] = PMNS_LEAF_STATUS;
+ }
+ else {
+ /*
+ * traversal ... want the whole name including the prefix
+ * part
+ */
+ tlen = pfxlen + strlen(dynamic_ones[i].name) + 2;
+ if ((chn[nmatch-1] = malloc(tlen)) == NULL) {
+ j = -oserror();
+ goto fail;
+ }
+ strncpy(chn[nmatch-1], name, pfxlen);
+ chn[nmatch-1][pfxlen] = '.';
+ chn[nmatch-1][pfxlen+1] = '\0';
+ strcat(chn[nmatch-1], dynamic_ones[i].name);
+ sts[nmatch-1] = PMNS_LEAF_STATUS;
+ }
+ len += tlen + 1;
+ }
+ }
+ if (nmatch == 0) {
+ *offspring = NULL;
+ *status = NULL;
+ }
+ else {
+ if ((chn = (char **)realloc(chn, nmatch*sizeof(chn[0])+len)) == NULL) {
+ j = -oserror();
+ goto fail;
+ }
+ q = (char *)&chn[nmatch];
+ for (j = 0; j < nmatch; j++) {
+ strcpy(q, chn[j]);
+ free(chn[j]);
+ chn[j] = q;
+ q += strlen(chn[j])+1;
+ }
+ *offspring = chn;
+ *status = sts;
+ }
+ return nmatch;
+
+fail:
+ /*
+ * come here with j as negative error code, and some allocation failure for
+ * sts[] or chn[] or chn[nmatch-1][]
+ */
+ if (sts != NULL) free(sts);
+ if (chn != NULL) {
+ for (i = 0; i < nmatch-1; i++) {
+ if (chn[i] != NULL) free(chn[i]);
+ }
+ free(chn);
+ }
+ return j;
+}
+
+static int
+sample_attribute(int ctx, int attr, const char *value, int length, pmdaExt *pmda)
+{
+ /*
+ * We have no special security or other requirements, so we're just
+ * going to log any connection attribute messages we happen to get
+ * from pmcd (handy for demo and testing purposes).
+ */
+ if (pmDebug & DBG_TRACE_AUTH) {
+ char buffer[256];
+
+ if (!__pmAttrStr_r(attr, value, buffer, sizeof(buffer))) {
+ __pmNotifyErr(LOG_ERR, "Bad Attribute: ctx=%d, attr=%d\n", ctx, attr);
+ } else {
+ buffer[sizeof(buffer)-1] = '\0';
+ __pmNotifyErr(LOG_INFO, "Attribute: ctx=%d %s", ctx, buffer);
+ }
+ }
+ return 0;
+}
+
+/*
+ * high precision counter
+ */
+typedef union {
+ __uint32_t half[2];
+ __uint64_t full;
+} pmHPC_t;
+
+#ifdef HAVE_NETWORK_BYTEORDER
+#define PM_HPC_TOP 0
+#define PM_HPC_BOTTOM 1
+#else
+#define PM_HPC_TOP 1
+#define PM_HPC_BOTTOM 0
+#endif
+
+void
+_pmHPCincr(pmHPC_t *ctr, __uint32_t val)
+{
+ if (val < ctr->half[PM_HPC_BOTTOM])
+ /* assume single overflow */
+ ctr->half[PM_HPC_TOP]++;
+ ctr->half[PM_HPC_BOTTOM] = val;
+}
+
+static pmHPC_t rapid_ctr;
+
+static int
+sample_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *ep)
+{
+ int i; /* over pmidlist[] */
+ int j; /* over vset->vlist[] */
+ int sts;
+ int need;
+ int inst;
+ int numval;
+ static pmResult *res;
+ static int maxnpmids;
+ static int nbyte;
+ __uint32_t *ulp;
+ unsigned long ul;
+ struct timeval now;
+ pmValueSet *vset;
+ pmDesc *dp;
+ __pmID_int *pmidp;
+ pmAtomValue atom;
+ int type;
+
+ sample_inc_recv(ep->e_context);
+ sample_inc_xmit(ep->e_context);
+
+ if (not_ready > 0) {
+ return limbo();
+ }
+
+ if (numpmid > maxnpmids) {
+ if (res != NULL)
+ free(res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *);
+ if ((res = (pmResult *)malloc(need)) == NULL)
+ return -oserror();
+ maxnpmids = numpmid;
+ }
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+ if (need_mirage && (j = redo_mirage()) < 0)
+ return j;
+ if (need_dynamic && (j = redo_dynamic()) < 0)
+ return j;
+
+ if (new_dodgey < 0)
+ redo_dodgey();
+
+ for (i = 0; i < numpmid; i++) {
+ pmidp = (__pmID_int *)&pmidlist[i];
+
+ if (direct_map) {
+ j = pmidp->item;
+ if (j < ndesc && desctab[j].pmid == pmidlist[i]) {
+ dp = &desctab[j];
+ goto doit;
+ }
+ }
+ for (dp = desctab; dp->pmid != PM_ID_NULL; dp++) {
+ if (dp->pmid == pmidlist[i])
+ break;
+ }
+doit:
+
+ if (dp->pmid != PM_ID_NULL) {
+ /* the special cases */
+ if (pmidp->cluster == 0 && pmidp->item == 86) {
+ dp = &magic;
+ numval = 1;
+ }
+ else if (pmidp->cluster == 0 && pmidp->item == 54)
+ numval = PM_ERR_PMID;
+ else if (pmidp->cluster == 0 && pmidp->item == 92) /* darkness */
+ numval = 0;
+ else if (pmidp->cluster == 0 && pmidp->item == 138) /* bad.novalues */
+ numval = 0;
+ else if (pmidp->cluster == 0 &&
+ (pmidp->item == 127 || /* event.type */
+ pmidp->item == 128 || /* event.param_32 */
+ pmidp->item == 129 || /* event.param_u32 */
+ pmidp->item == 130 || /* event.param_64 */
+ pmidp->item == 131 || /* event.param_u64 */
+ pmidp->item == 132 || /* event.param_float */
+ pmidp->item == 133 || /* event.param_double */
+ pmidp->item == 134 || /* event.param_string */
+ pmidp->item == 135)) /* event.param_aggregate */
+ numval = 0;
+ else if (dp->type == PM_TYPE_NOSUPPORT)
+ numval = PM_ERR_APPVERSION;
+ else if (dp->indom != PM_INDOM_NULL) {
+ /* count instances in the profile */
+ numval = 0;
+ /* special case(s) */
+ if (pmidp->cluster == 0 && pmidp->item == 49) {
+ int kp;
+ /* needprofile - explict instances required */
+
+ numval = PM_ERR_PROFILE;
+ for (kp = 0; kp < _profile->profile_len; kp++) {
+ if (_profile->profile[kp].indom != dp->indom)
+ continue;
+ if (_profile->profile[kp].state == PM_PROFILE_EXCLUDE &&
+ _profile->profile[kp].instances_len != 0)
+ numval = 0;
+ break;
+ }
+ }
+ else if (pmidp->cluster == 0 && (pmidp->item == 76 || pmidp->item == 77 || pmidp->item == 78)) {
+ /*
+ * if $(PCP_VAR_DIR)/pmdas/sample/dynamic.indom is not present,
+ * then numinst will be zero after the redo_dynamic() call
+ * in sample_init(), which makes zero loops through the
+ * fetch loop, so cannot set need_dynamic there ...
+ * do it here if not already turned on
+ */
+ if (need_dynamic == 0) {
+ need_dynamic = 1;
+ if ((j = redo_dynamic()) < 0)
+ return j;
+ }
+ }
+ if (numval == 0) {
+ /* count instances in indom */
+ startinst(dp->indom, 1);
+ while (nextinst(&inst)) {
+ /* special case ... not all here for part_bin */
+ if (pmidp->cluster == 0 && pmidp->item == 50 && (inst % 200) == 0)
+ continue;
+ numval++;
+ }
+ }
+ }
+ else {
+ /* special case(s) for singular instance domains */
+ if (pmidp->cluster == 0 && pmidp->item == 9) {
+ /* surprise! no value available */
+ numval = 0;
+ }
+ else
+ numval = 1;
+ }
+ }
+ else
+ numval = 0;
+
+ /* Must use individual malloc()s because of pmFreeResult() */
+ if (numval >= 1)
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ (numval - 1)*sizeof(pmValue));
+ else
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+ if (vset == NULL) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = numval;
+ vset->valfmt = PM_VAL_INSITU;
+ if (vset->numval <= 0)
+ continue;
+
+ if (dp->indom == PM_INDOM_NULL)
+ inst = PM_IN_NULL;
+ else {
+ startinst(dp->indom, 0);
+ nextinst(&inst);
+ }
+ type = dp->type;
+ j = 0;
+ do {
+ if (pmidp->cluster == 0 && pmidp->item == 50 && inst % 200 == 0)
+ goto skip;
+ if (pmidp->cluster == 0 && pmidp->item == 51 && inst % 200 == 0)
+ inst += 50;
+ if (j == numval) {
+ /* more instances than expected! */
+ numval++;
+ res->vset[i] = vset = (pmValueSet *)realloc(vset,
+ sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue));
+ if (vset == NULL) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ }
+ vset->vlist[j].inst = inst;
+ /*
+ * we mostly have cluster 0, metric already found in desctab[]
+ * so no checking needed
+ */
+ if (pmidp->cluster == 0) {
+ switch (pmidp->item) {
+ case 0:
+ atom.l = _control;
+ break;
+ case 1:
+ if (_mypid == 0) _mypid = (int)getpid();
+ atom.ul = _mypid;
+ break;
+ case 2:
+ atom.ul = time(NULL) - _start;
+ break;
+ case 3:
+ __pmtimevalNow(&now);
+ atom.d = 1000 * __pmtimevalSub(&now, &_then);
+ break;
+ case 4:
+ atom.l = 42;
+ break;
+ case 5:
+ switch (inst) {
+ case 0: /* "red" */
+ _x = (_x + 1) % 100;
+ atom.l = _x + 100;
+ break;
+ case 1: /* "green" */
+ _x = (_x + 1) % 100;
+ atom.l = _x + 200;
+ break;
+ case 2: /* "blue" */
+ _x = (_x + 1) % 100;
+ atom.l = _x + 300;
+ break;
+ }
+ break;
+ case 6:
+ case 48:
+ case 50:
+ case 51:
+ case 103: /* long.bin & long.bin_ctr */
+ case 104:
+ case 121: /* scramble.bin */
+ /* the value is the instance identifier (sic) */
+ atom.l = inst;
+ break;
+ /* and ditto for all the other type variants of "bin" */
+ case 105: /* ulong.bin & ulong.bin_ctr */
+ case 106:
+ atom.ul = inst;
+ break;
+ case 107: /* float.bin & float.bin_ctr */
+ case 108:
+ atom.f = inst;
+ break;
+ case 109: /* longlong.bin & longlong.bin_ctr */
+ case 110:
+ atom.ll = inst;
+ break;
+ case 111: /* ulonglong.bin & ulonglong.bin_ctr */
+ case 112:
+ atom.ull = inst;
+ break;
+ case 113: /* double.bin & double.bin_ctr */
+ case 114:
+ atom.d = inst;
+ break;
+ case 7:
+ /* drift */
+ _drift = _drift + _sign * (int)(lrand48() % 50);
+ if (_drift < 0) _drift = 0;
+ atom.l = _drift;
+ if ((lrand48() % 100) < 20) {
+ if (_sign == 1)
+ _sign = -1;
+ else
+ _sign = 1;
+ }
+ break;
+ case 63: /* step_counter */
+ case 8: /* step every 30 seconds */
+ atom.l = (1 + (time(NULL) - _start) / 30) * _step;
+ break;
+ case 40:
+ /* total pdu count for all contexts */
+ atom.ll = (__int64_t)sample_get_recv(CTX_ALL) + (__int64_t)sample_get_xmit(CTX_ALL);
+ break;
+ case 41:
+ /* recv pdu count for all contexts */
+ atom.l = sample_get_recv(CTX_ALL);
+ break;
+ case 42:
+ /* xmit pdu count for all contexts */
+ atom.l = sample_get_xmit(CTX_ALL);
+ break;
+ case 43:
+ case 44:
+ case 45:
+ case 122:
+ case 123:
+ case 124:
+ case 125:
+ /* percontext.pdu */
+ /* percontext.recv-pdu */
+ /* percontext.xmit-pdu */
+ /* percontext.control.ctx */
+ /* percontext.control.active */
+ /* percontext.control.start */
+ /* percontext.control.end */
+ atom.l = sample_ctx_fetch(ep->e_context, pmidp->item);
+ break;
+ case 37:
+ /* mirage */
+ _x = (_x + 1) % 100;
+ atom.l = (inst + 1) * 100 - _x;
+ need_mirage = 1;
+ break;
+ case 36:
+ /* write_me */
+ atom.l = _write_me;
+ break;
+ case 39:
+ /* sysinfo */
+ if (!sivb) {
+ /* malloc and init the pmValueBlock for
+ * sysinfo first type around */
+
+ int size = sizeof(pmValueBlock) - sizeof(int);
+
+#ifdef IS_SOLARIS
+ size += MAX_SYSNAME;
+#else
+ size += sizeof (struct sysinfo);
+#endif
+
+ if ((sivb = calloc(1, size)) == NULL )
+ return PM_ERR_GENERIC;
+
+ sivb->vlen = size;
+ sivb->vtype = PM_TYPE_AGGREGATE;
+ }
+
+#ifdef HAVE_SYSINFO
+#ifdef IS_SOLARIS
+ sysinfo(SI_SYSNAME, sivb->vbuf, MAX_SYSNAME);
+#else
+ sysinfo((struct sysinfo *)sivb->vbuf);
+#endif
+#else
+ strncpy((char *)sivb->vbuf, si.dummy, sizeof(struct sysinfo));
+#endif
+ atom.vbp = sivb;
+
+ /*
+ * pv:782029 The actual type must be PM_TYPE_AGGREGATE,
+ * but we have to tell pmStuffValue it's a
+ * PM_TYPE_AGGREGATE_STATIC
+ */
+ type = PM_TYPE_AGGREGATE_STATIC;
+ break;
+ case 46:
+ if (_n46 == 0) {
+ _col46 = lrand48() % 3;
+ _n46 = 1 + (int)(lrand48() % 10);
+ }
+ _n46--;
+ switch (_col46) {
+ case 0:
+ atom.cp = "red";
+ break;
+ case 1:
+ atom.cp = "yellow";
+ break;
+ case 2:
+ atom.cp = "green";
+ break;
+ }
+ break;
+ case 47:
+ if (_n47 == 0) {
+ _mag47 = 1 << (1 + (int)(lrand48() % 6));
+ _n47 = 1 + (int)(lrand48() % 5);
+ }
+ _n47--;
+ atom.l = (__int32_t)_mag47;
+ break;
+ case 38:
+ /* mirage-longlong */
+ _x = (_x + 1) % 100;
+ atom.ll = (inst + 1) * 100 - _x;
+ atom.ll *= 1000000;
+ need_mirage = 1;
+ break;
+ case 49:
+ /* need profile */
+ switch (inst) {
+ case 0: /* "colleen" */
+ atom.f = 3.05;
+ break;
+ case 1: /* "terry" */
+ atom.f = 12.05;
+ break;
+ case 2: /* "emma" */
+ case 3: /* "cathy" */
+ atom.f = 11.09;
+ break;
+ case 4: /* "alexi" */
+ atom.f = 5.26;
+ break;
+ }
+ break;
+ case 10: /* long.* group */
+ atom.l = 1;
+ break;
+ case 11:
+ atom.l = 10;
+ break;
+ case 12:
+ atom.l = 100;
+ break;
+ case 13:
+ atom.l = 1000000;
+ break;
+ case 14:
+ atom.l = (__int32_t)_long;
+ break;
+ case 20: /* longlong.* group */
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ll = 1;
+#else
+ atom.ll = 1LL;
+#endif
+ break;
+ case 21:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ll = 10;
+#else
+ atom.ll = 10LL;
+#endif
+ break;
+ case 22:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ll = 100;
+#else
+ atom.ll = 100LL;
+#endif
+ break;
+ case 23:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ll = 1000000;
+#else
+ atom.ll = 1000000LL;
+#endif
+ break;
+ case 24:
+ atom.ll = _longlong;
+ break;
+ case 15: /* float.* group */
+ atom.f = 1;
+ break;
+ case 16:
+ atom.f = 10;
+ break;
+ case 17:
+ atom.f = 100;
+ break;
+ case 18:
+ atom.f = 1000000;
+ break;
+ case 19:
+ atom.f = _float;
+ break;
+ case 25: /* double.* group */
+ atom.d = 1;
+ break;
+ case 26:
+ atom.d = 10;
+ break;
+ case 27:
+ atom.d = 100;
+ break;
+ case 28:
+ atom.d = 1000000;
+ break;
+ case 29:
+ atom.d = _double;
+ break;
+ case 30:
+ atom.cp = "";
+ break;
+ case 31:
+ atom.cp = "hullo world!";
+ break;
+ case 32:
+ atom.cp = _string;
+ break;
+ case 33:
+ atom.vbp = _aggr33;
+ break;
+ case 34:
+ atom.vbp = _aggr34;
+ break;
+ case 35:
+ atom.vbp = _aggr35;
+ break;
+ case 52:
+ atom.l = inst;
+ break;
+ case 53:
+ atom.l = 499 - inst;
+ break;
+ case 56:
+ atom.l = not_ready;
+ break;
+ case 57:
+ _wrap += INT_MAX / 2 - 1;
+ atom.l = _wrap;
+ break;
+ case 58:
+ _u_wrap += UINT_MAX / 2 - 1;
+ atom.ul = _u_wrap;
+ break;
+ case 59:
+ _ll_wrap += LONGLONG_MAX / 2 - 1;
+ atom.ll = _ll_wrap;
+ break;
+ case 60:
+ _ull_wrap += ULONGLONG_MAX / 2 - 1;
+ atom.ull = _ull_wrap;
+ break;
+ case 61:
+ atom.l = dodgey;
+ break;
+ case 62:
+ if (dodgey > 5 && j == 0)
+ new_dodgey--;
+ if (tmp_dodgey <= 0) {
+ j = tmp_dodgey;
+ goto done;
+ }
+ else if (tmp_dodgey <= 5) {
+ if (inst > tmp_dodgey)
+ goto skip;
+ }
+ atom.l = (int)(lrand48() % 101);
+ break;
+ case 64:
+ _rapid += 80000000;
+ _pmHPCincr(&rapid_ctr, _rapid);
+ atom.ul = (__uint32_t)(rapid_ctr.full * 10);
+ break;
+ case 65: /* scale_step.bytes_up */
+ atom.d = scale_step_bytes_up;
+ if (++scale_step_number[0] % 5 == 0) {
+ if (scale_step_bytes_up < 1024.0*1024.0*1024.0*1024.0)
+ scale_step_bytes_up *= 2;
+ else
+ scale_step_bytes_up = 1;
+ }
+ break;
+ case 66: /* scale_step.bytes_down */
+ atom.d = scale_step_bytes_down;
+ if (++scale_step_number[1] % 5 == 0) {
+ if (scale_step_bytes_down > 1)
+ scale_step_bytes_down /= 2;
+ else
+ scale_step_bytes_down = 1024.0*1024.0*1024.0*1024.0;
+ }
+ break;
+ case 67: /* scale_step.count_up */
+ atom.d = scale_step_count_up;
+ if (++scale_step_number[2] % 5 == 0) {
+ if (scale_step_count_up < 1.0e12)
+ scale_step_count_up *= 10;
+ else
+ scale_step_count_up = 1;
+ }
+ break;
+ case 68: /* scale_step.count_down */
+ atom.d = scale_step_count_down;
+ if (++scale_step_number[3] % 5 == 0) {
+ if (scale_step_count_down > 1)
+ scale_step_count_down /= 10;
+ else
+ scale_step_count_down = 1.0e12;
+ }
+ break;
+ case 69: /* scale_step.time_up_secs */
+ atom.d = scale_step_time_up_secs;
+ if (++scale_step_number[4] % 5 == 0) {
+ if (scale_step_time_up_secs < 60*60*24)
+ scale_step_time_up_secs *= 10;
+ else
+ scale_step_time_up_secs = 1;
+ }
+ break;
+ case 70: /* scale_step.time_up_nanosecs */
+ atom.d = scale_step_time_up_nanosecs;
+ if (++scale_step_number[5] % 5 == 0) {
+ if (scale_step_time_up_nanosecs < 1e9*60*60*24)
+ scale_step_time_up_nanosecs *= 10;
+ else
+ scale_step_time_up_nanosecs = 1;
+ }
+ break;
+ case 71: /* scale_step.none_up */
+ atom.d = scale_step_none_up;
+ if (++scale_step_number[6] % 5 == 0) {
+ if (scale_step_none_up < 10000000)
+ scale_step_none_up *= 10;
+ else
+ scale_step_none_up = 1;
+ }
+ break;
+ case 72: /* const_rate.value */
+ __pmtimevalNow(&now);
+ atom.ul = const_rate_value + const_rate_gradient * __pmtimevalSub(&now, &const_rate_timestamp);
+ const_rate_timestamp = now;
+ const_rate_value = atom.ul;
+ break;
+ case 73: /* const_rate.gradient */
+ atom.ul = const_rate_gradient;
+ break;
+ case 74: /* error_code */
+ atom.l = _error_code;
+ break;
+ case 75: /* error_check */
+ if (_error_code < 0)
+ return _error_code;
+ atom.l = 0;
+ break;
+ case 76: /* dynamic.counter */
+ case 77: /* dynamic.discrete */
+ case 78: /* dynamic.instant */
+ if (inst > _dyn_max) {
+ /* bad instance! */
+ goto done;
+ }
+ atom.l = _dyn_ctr[inst];
+ break;
+ case 79: /* many.count */
+ atom.l=many_count;
+ break;
+ case 80: /* many.int */
+ atom.l = inst;
+ break;
+ case 81: /* byte_ctr */
+ nbyte += lrand48() % 1024;
+ atom.l = nbyte;
+ break;
+ case 82: /* byte_rate */
+ atom.l = (int)(lrand48() % 1024);
+ break;
+ case 83: /* kbyte_ctr */
+ nbyte += lrand48() % 1024;
+ atom.l = nbyte;
+ break;
+ case 84: /* kbyte_rate */
+ atom.l = (int)(lrand48() % 1024);
+ break;
+ case 85: /* byte_rate_per_hour */
+ atom.l = (int)(lrand48() % 1024);
+ break;
+ case 86: /* dynamic.meta.metric */
+ switch (magic.type) {
+ case PM_TYPE_32:
+ atom.l = 42;
+ break;
+ case PM_TYPE_U32:
+ atom.ul = 42;
+ break;
+ case PM_TYPE_64:
+ atom.ll = 42;
+ break;
+ case PM_TYPE_U64:
+ atom.ull = 42;
+ break;
+ case PM_TYPE_FLOAT:
+ atom.f = 42;
+ break;
+ case PM_TYPE_DOUBLE:
+ atom.d = 42;
+ break;
+ default:
+ /* do nothing in other cases ... return garbage */
+ break;
+ }
+ break;
+ case 87: /* dynamic.meta.pmdesc.type */
+ atom.ul = magic.type;
+ break;
+ case 88: /* dynamic.meta.pmdesc.indom */
+ atom.ul = magic.indom;
+ break;
+ case 89: /* dynamic.meta.pmdesc.sem */
+ atom.ul = magic.sem;
+ break;
+ case 90: /* dynamic.meta.pmdesc.units */
+ ulp = (__uint32_t *)&magic.units;
+ atom.ul = *ulp;
+ break;
+ case 91: /* datasize */
+ __pmProcessDataSize(&ul);
+ atom.ul = ul;
+ break;
+ /* no case 92 for darkeness, handled above */
+ case 93: /* ulong.* group */
+ atom.ul = 1;
+ break;
+ case 94:
+ atom.ul = 10;
+ break;
+ case 95:
+ atom.ul = 100;
+ break;
+ case 96:
+ atom.ul = 1000000;
+ break;
+ case 97:
+ atom.ul = (__int32_t)_ulong;
+ break;
+ case 98: /* ulonglong.* group */
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ull = 1;
+#else
+ atom.ull = 1ULL;
+#endif
+ break;
+ case 99:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ull = 10;
+#else
+ atom.ull = 10ULL;
+#endif
+ break;
+ case 100:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ull = 100;
+#else
+ atom.ull = 100ULL;
+#endif
+ break;
+ case 101:
+#if !defined(HAVE_CONST_LONGLONG)
+ atom.ull = 1000000;
+#else
+ atom.ull = 1000000ULL;
+#endif
+ break;
+ case 102:
+ atom.ull = _ulonglong;
+ break;
+ case 115: /* ulong.count.base */
+ atom.ul = 42000000;
+ break;
+ case 116: /* ulong.count.deca */
+ atom.ul = 4200000;
+ break;
+ case 117: /* ulong.count.hecto */
+ atom.ul = 420000;
+ break;
+ case 118: /* ulong.count.kilo */
+ atom.ul = 42000;
+ break;
+ case 119: /* ulong.count.mega */
+ atom.ul = 42;
+ break;
+ case 120: /* scramble.version */
+ atom.ll = scramble_ver;
+ break;
+ case 126: /* event.reset */
+ atom.l = event_get_fetch_count();
+ break;
+ case 136: /* event.records */
+ case 137: /* event.no_indom_records */
+ if ((sts = sample_fetch_events(&atom.vbp, inst)) < 0)
+ return sts;
+ break;
+ case 139: /* event.highres_records */
+ if ((sts = sample_fetch_highres_events(&atom.vbp, inst)) < 0)
+ return sts;
+ break;
+ case 140: /* event.reset_highres */
+ atom.l = event_get_highres_fetch_count();
+ break;
+ case 1000: /* secret.bar */
+ atom.cp = "foo";
+ break;
+ case 1001: /* secret.foo.one */
+ atom.l = 1;
+ break;
+ case 1002: /* secret.foo.two */
+ atom.l = 2;
+ break;
+ case 1003: /* secret.foo.bar.three */
+ atom.l = 3;
+ break;
+ case 1004: /* secret.foo.bar.four */
+ atom.l = 4;
+ break;
+ case 1005: /* secret.foo.bar.grunt.five */
+ atom.l = 5;
+ break;
+ case 1006: /* secret.foo.bar.grunt.snort.six */
+ atom.l = 6;
+ break;
+ case 1007: /* secret.foo.bar.grunt.snort.seven */
+ atom.l = 7;
+ break;
+ case 1023: /* bigid */
+ atom.l = 4194303;
+ break;
+ }
+ }
+ if ((sts = __pmStuffValue(&atom, &vset->vlist[j], type)) < 0) {
+ __pmFreeResultValues(res);
+ return sts;
+ }
+ vset->valfmt = sts;
+ j++; /* next element in vlist[] for next instance */
+
+skip:
+ ;
+ } while (dp->indom != PM_INDOM_NULL && nextinst(&inst));
+done:
+ vset->numval = j;
+ }
+ *resp = res;
+ return PMDA_FETCH_STATIC;
+}
+
+static int
+sample_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ int i;
+ __pmID_int *pmidp = (__pmID_int *)&pmid;
+
+ sample_inc_recv(ep->e_context);
+ sample_inc_xmit(ep->e_context);
+
+ if (not_ready > 0) {
+ return limbo();
+ }
+
+ if (direct_map) {
+ i = pmidp->item;
+ if (i < ndesc && desctab[i].pmid == pmid)
+ goto doit;
+ }
+ for (i = 0; desctab[i].pmid != PM_ID_NULL; i++) {
+ if (desctab[i].pmid == pmid) {
+doit:
+ /* the special cases */
+ if (pmidp->item == 54)
+ return PM_ERR_PMID;
+ else if (pmidp->item == 75 && _error_code < 0)
+ /* error_check and error_code armed */
+ return _error_code;
+ else if (pmidp->item == 86)
+ *desc = magic;
+ else
+ *desc = desctab[i];
+ return 0;
+ }
+ }
+ return PM_ERR_PMID;
+}
+
+static int
+sample_text(int ident, int type, char **buffer, pmdaExt *ep)
+{
+ int sts;
+
+ sample_inc_recv(ep->e_context);
+ sample_inc_xmit(ep->e_context);
+
+ if (not_ready > 0) {
+ return limbo();
+ }
+
+ if (ident & PM_TEXT_PMID) {
+ __pmID_int *pmidp = (__pmID_int *)&ident;
+ int i;
+
+ if (direct_map) {
+ i = pmidp->item;
+ if (i < ndesc && desctab[i].pmid == (pmID)ident)
+ goto doit;
+ }
+ for (i = 0; desctab[i].pmid != PM_ID_NULL; i++) {
+ if (desctab[i].pmid == (pmID)ident) {
+doit:
+ /* the special cases */
+ if (pmidp->item == 75 && _error_code < 0)
+ /* error_check and error_code armed */
+ return _error_code;
+ break;
+ }
+ }
+ }
+
+ sts = pmdaText(ident, type, buffer, ep);
+
+ return sts;
+}
+
+static int
+sample_store(pmResult *result, pmdaExt *ep)
+{
+ int i;
+ pmValueSet *vsp;
+ pmDesc *dp;
+ __pmID_int *pmidp;
+ int sts = 0;
+ __int32_t *lp;
+ pmAtomValue av;
+
+ sample_inc_recv(ep->e_context);
+ sample_inc_xmit(ep->e_context);
+
+ if (not_ready > 0) {
+ return limbo();
+ }
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ for (dp = desctab; dp->pmid != PM_ID_NULL; dp++) {
+ if (dp->pmid == vsp->pmid)
+ break;
+ }
+ if (dp->pmid == PM_ID_NULL) {
+ /* not one of our metrics */
+ sts = PM_ERR_PMID;
+ break;
+ }
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster != 0) {
+ sts = PM_ERR_PMID;
+ break;
+ }
+
+ /*
+ * for this PMD, the metrics that support modification
+ * via pmStore() mostly demand a single value, encoded in
+ * the result structure as PM_VAL_INSITU format
+ */
+ switch (pmidp->item) {
+ case 24: /* longlong.write_me */
+ case 29: /* double.write_me */
+ case 32: /* string.write_me */
+ case 35: /* aggregate.write_me */
+ case 102: /* ulonglong.write_me */
+ case 120: /* scramble.ver */
+ if (vsp->numval != 1 || vsp->valfmt == PM_VAL_INSITU)
+ sts = PM_ERR_CONV;
+ break;
+
+ case 74: /* error_code */
+ case 73: /* const_rate.gradient */
+ case 61: /* dodgey.control */
+ case 56: /* not_ready */
+ case 36:
+ case 42:
+ case 41:
+ case 14: /* long.write_me */
+ case 8: /* step */
+ case 7: /* drift */
+ case 0: /* control */
+ case 79: /* many.count */
+ case 87: /* dynamic.meta.pmdesc.type */
+ case 88: /* dynamic.meta.pmdesc.indom */
+ case 89: /* dynamic.meta.pmdesc.sem */
+ case 90: /* dynamic.meta.pmdesc.units */
+ case 97: /* ulong.write_me */
+ case 126: /* event.reset */
+ case 140: /* event.reset_highres */
+ if (vsp->numval != 1 || vsp->valfmt != PM_VAL_INSITU)
+ sts = PM_ERR_CONV;
+ break;
+
+ case 19: /* float.write_me */
+ if (vsp->numval != 1)
+ sts = PM_ERR_CONV;
+ /* accommodate both old and new encoding styles for floats */
+ break;
+
+ case 40: /* pdu */
+ /* value is ignored, so valfmt does not matter */
+ if (vsp->numval != 1)
+ sts = PM_ERR_CONV;
+ break;
+
+ default:
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ }
+ if (sts != 0)
+ break;
+
+ if ((sts = pmExtractValue(vsp->valfmt, &vsp->vlist[0], dp->type, &av, dp->type)) < 0)
+ break;
+
+ /*
+ * we only have cluster 0, metric already found in desctab[],
+ * so no checking needed nor outer case on pmidp->cluster
+ */
+ switch (pmidp->item) {
+ case 0: /* control */
+ _control = av.l;
+ switch (_control) {
+ case -1:
+ /* terminate, if we are not a DSO implementation */
+ sample_done = 1;
+ break;
+ default:
+ pmDebug = _control;
+ break;
+ }
+ break;
+ case 7: /* drift */
+ _drift = av.l;
+ break;
+ case 8: /* step */
+ _step = av.l;
+ break;
+ case 14: /* long.write_me */
+ _long = av.l;
+ break;
+ case 24: /* longlong.write_me */
+ _longlong = av.ll;
+ break;
+ case 19: /* float.write_me */
+ _float = av.f;
+ break;
+ case 40: /* pdu */
+ /*
+ * for the pdu group, the value is ignored, and the only
+ * operation is to reset the counter(s)
+ */
+ sample_clr_recv(CTX_ALL);
+ sample_clr_xmit(CTX_ALL);
+ break;
+ case 41:
+ sample_clr_recv(CTX_ALL);
+ break;
+ case 42:
+ sample_clr_xmit(CTX_ALL);
+ break;
+ case 36:
+ _write_me = av.l;
+ break;
+ case 29: /* double.write_me */
+ _double = av.d;
+ break;
+ case 32: /* string.write_me */
+ free(_string);
+ _string = av.cp;
+ break;
+ case 35: /* aggregate.write_me */
+ free(_aggr35);
+ _aggr35 = av.vbp;
+ break;
+ case 56: /* not_ready */
+ not_ready = av.l;
+ break;
+ case 61: /* dodgey.control */
+ dodgey = av.l;
+ redo_dodgey();
+ break;
+ case 73: /* const_rate.gradient */
+ const_rate_gradient = av.ul;
+ break;
+ case 74: /* error_code */
+ _error_code = av.l;
+ break;
+ case 79: /* many.count */
+ many_count = av.l;
+ /* change the size of the many instance domain */
+ _error_code = redo_many();
+ break;
+ case 87: /* dynamic.meta.pmdesc.type */
+ magic.type = av.l;
+ break;
+ case 88: /* dynamic.meta.pmdesc.indom */
+ magic.indom = av.l;
+ break;
+ case 89: /* dynamic.meta.pmdesc.sem */
+ magic.sem = av.l;
+ break;
+ case 90: /* dynamic.meta.pmdesc.units */
+ lp = (__int32_t *)&magic.units;
+ *lp = av.l;
+ break;
+ case 97: /* ulong.write_me */
+ _ulong = av.ul;
+ break;
+ case 102: /* ulonglong.write_me */
+ _ulonglong = av.ull;
+ break;
+ case 120: /* scramble.version */
+ scramble_ver = 0;
+ for (i = 0; i < indomtab[BIN_INDOM].it_numinst; i++) {
+ indomtab[SCRAMBLE_INDOM].it_set[i].i_inst = indomtab[BIN_INDOM].it_set[i].i_inst;
+ indomtab[SCRAMBLE_INDOM].it_set[i].i_name = indomtab[BIN_INDOM].it_set[i].i_name;
+ }
+ indomtab[SCRAMBLE_INDOM].it_numinst = indomtab[BIN_INDOM].it_numinst;
+ break;
+ case 126: /* event.reset */
+ event_set_fetch_count(av.l);
+ break;
+ case 140: /* event.reset_highres */
+ event_set_highres_fetch_count(av.l);
+ break;
+ default:
+ sts = PM_ERR_PERMISSION;
+ break;
+ }
+ }
+
+ return sts;
+}
+
+void
+__PMDA_INIT_CALL
+sample_init(pmdaInterface *dp)
+{
+ char helppath[MAXPATHLEN];
+ int i;
+
+ if (_isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(helppath, sizeof(helppath), "%s%c" "sample" "%c" "dsohelp",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_LATEST, "sample DSO", helppath);
+ }
+ else {
+ __pmProcessDataSize(NULL);
+ }
+
+ if (dp->status != 0)
+ return;
+ dp->comm.flags |= PDU_FLAG_AUTH;
+
+ dp->version.any.fetch = sample_fetch;
+ dp->version.any.desc = sample_desc;
+ dp->version.any.instance = sample_instance;
+ dp->version.any.text = sample_text;
+ dp->version.any.store = sample_store;
+ dp->version.any.profile = sample_profile;
+ dp->version.four.pmid = sample_pmid;
+ dp->version.four.name = sample_name;
+ dp->version.four.children = sample_children;
+ dp->version.six.attribute = sample_attribute;
+ pmdaSetEndContextCallBack(dp, sample_ctx_end);
+
+ pmdaInit(dp, NULL, 0, NULL, 0); /* don't use indomtab or metrictab */
+
+ __pmtimevalNow(&_then);
+ _start = time(NULL);
+ init_tables(dp->domain);
+ init_events(dp->domain);
+ redo_mirage();
+ redo_dynamic();
+
+ /* initialization of domain in PMIDs for dynamic PMNS entries */
+ for (i = 0; i < numdyn; i++) {
+ ((__pmID_int *)&dynamic_ones[i].pmid)->domain = dp->domain;
+ }
+ /*
+ * Max Matveev wanted this sort of redirection, so first entry is
+ * actually a redirect to PMID 2.4.1 (pmcd.agent.status)
+ */
+ ((__pmID_int *)&dynamic_ones[0].pmid)->domain = 2;
+ ((__pmID_int *)&dynamic_ones[0].pmid)->cluster = 4;
+ ((__pmID_int *)&dynamic_ones[0].pmid)->item = 1;
+
+ /*
+ * for gcc/egcs, statically initializing these cased the strings
+ * to be read-only, causing SEGV in redo_dynamic ... so do the
+ * initialization dynamically here.
+ */
+ _dodgey[0].i_name = strdup("d1");
+ _dodgey[1].i_name = strdup("d2");
+ _dodgey[2].i_name = strdup("d3");
+ _dodgey[3].i_name = strdup("d4");
+ _dodgey[4].i_name = strdup("d5");
+}
diff --git a/src/pmdas/sendmail/GNUmakefile b/src/pmdas/sendmail/GNUmakefile
new file mode 100644
index 0000000..171c7c0
--- /dev/null
+++ b/src/pmdas/sendmail/GNUmakefile
@@ -0,0 +1,58 @@
+#
+# Copyright (c) 2000-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = sendmail
+DOMAIN = SENDMAIL
+CMDTARGET = $(IAM)$(EXECSUFFIX)
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+CFILES = sendmail.c
+SCRIPTS = Install Remove
+DFILES = README
+LSRCFILES= $(SCRIPTS) pmns help root Sendmail.pmchart $(DFILES)
+
+VERSION_SCRIPT = exports
+PMDAINIT = $(IAM)_init
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+
+LDIRT = domain.h *.o $(IAM).log $(LIBTARGET) $(CMDTARGET) $(VERSION_SCRIPT)
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = $(INVISIBILITY)
+
+default_pcp default: $(CMDTARGET) $(LIBTARGET)
+
+include $(BUILDRULES)
+
+install install_pcp: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(PMDADIR)/$(LIBTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/pmda$(IAM)$(EXECSUFFIX)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) pmns help root domain.h $(PMDADIR)
+ $(INSTALL) -m 644 Sendmail.pmchart $(PMCHART)/Sendmail
+
+$(CMDTARGET): $(OBJECTS)
+
+$(LIBTARGET): $(OBJECTS) $(VERSION_SCRIPT)
+
+sendmail.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
diff --git a/src/pmdas/sendmail/Install b/src/pmdas/sendmail/Install
new file mode 100644
index 0000000..5ce3c57
--- /dev/null
+++ b/src/pmdas/sendmail/Install
@@ -0,0 +1,27 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the sendmail PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=sendmail
+pmda_interface=3
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/sendmail/README b/src/pmdas/sendmail/README
new file mode 100644
index 0000000..1a89adc
--- /dev/null
+++ b/src/pmdas/sendmail/README
@@ -0,0 +1,50 @@
+Sendmail PMDA
+=============
+
+Export information from the sendmail statistics file.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT sendmail
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/sendmail
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ Everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/sendmail
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/sendmail.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/sendmail/Remove b/src/pmdas/sendmail/Remove
new file mode 100644
index 0000000..c0275d7
--- /dev/null
+++ b/src/pmdas/sendmail/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the sendmail PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=sendmail
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/sendmail/Sendmail.pmchart b/src/pmdas/sendmail/Sendmail.pmchart
new file mode 100644
index 0000000..446decf
--- /dev/null
+++ b/src/pmdas/sendmail/Sendmail.pmchart
@@ -0,0 +1,10 @@
+#pmchart
+Version 2.0 host dynamic
+
+Chart Style plot
+ Plot Color #137bfe Host * Metric sendmail.total.bytes_from
+ Plot Color #fefa1a Host * Metric sendmail.total.bytes_to
+Chart Style plot
+ Plot Color #1e1cfe Host * Metric sendmail.total.msgs_from
+ Plot Color #fe9913 Host * Metric sendmail.total.msgs_to
+
diff --git a/src/pmdas/sendmail/help b/src/pmdas/sendmail/help
new file mode 100644
index 0000000..593469d
--- /dev/null
+++ b/src/pmdas/sendmail/help
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# sendmail PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ SENDMAIL.0 Instance domain "mailer" for sendmail PMDA
+The mailers 0 (prog), 1 (*file*), and 2 (*include*) are fixed. Other
+mailers are defined by the order of any additional "M" records in the
+sendmail.cf file.
+
+@ sendmail.start_date Date on which sendmail stats file was created
+The date in ctime(2) format on which the sendmail statistics file was
+first created. All statistics are cumulative from that time.
+
+The sendmail statistics file is /var/sendmailst by default, but may be
+redefined by an "OS" or "O StatusFile" record in the sendmail.cf file.
+
+@ sendmail.permailer.msgs_from Messages received from each mailer
+Count of messages received from each "mailer" defined in sendmail's
+configuration file (/etc/sendmail.cf).
+
+@ sendmail.permailer.bytes_from Kbytes received from each mailer
+Count of Kbytes summed over all messages received from each "mailer"
+defined in sendmail's configuration file (/etc/sendmail.cf).
+
+@ sendmail.permailer.msgs_to Messages sent to each mailer
+Count of messages sent to each "mailer" defined in sendmail's
+configuration file (/etc/sendmail.cf).
+
+@ sendmail.permailer.bytes_to Kbytes sent to each mailer
+Count of Kbytes summed over all messages sent to each "mailer" defined
+in sendmail's configuration file (/etc/sendmail.cf).
+
+@ sendmail.total.msgs_from Messages received from all mailers
+Count of messages received by sendmail.
+
+@ sendmail.total.bytes_from Kbytes received from all mailers
+Count of Kbytes summed over all messages received by sendmail.
+
+@ sendmail.total.msgs_to Messages sent to all mailers
+Count of messages sent by sendmail.
+
+@ sendmail.total.bytes_to Kbytes sent to all mailers
+Count of Kbytes summed over all messages sent by sendmail.
+
diff --git a/src/pmdas/sendmail/pmns b/src/pmdas/sendmail/pmns
new file mode 100644
index 0000000..856448f
--- /dev/null
+++ b/src/pmdas/sendmail/pmns
@@ -0,0 +1,39 @@
+/*
+ * Metrics for sendmail PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+sendmail {
+ start_date SENDMAIL:0:0
+ permailer
+ total
+}
+
+sendmail.permailer {
+ msgs_from SENDMAIL:1:0
+ bytes_from SENDMAIL:1:1
+ msgs_to SENDMAIL:1:2
+ bytes_to SENDMAIL:1:3
+}
+
+sendmail.total {
+ msgs_from SENDMAIL:2:0
+ bytes_from SENDMAIL:2:1
+ msgs_to SENDMAIL:2:2
+ bytes_to SENDMAIL:2:3
+}
diff --git a/src/pmdas/sendmail/root b/src/pmdas/sendmail/root
new file mode 100644
index 0000000..ead8f5a
--- /dev/null
+++ b/src/pmdas/sendmail/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { sendmail }
+
+#include "pmns"
+
diff --git a/src/pmdas/sendmail/sendmail.c b/src/pmdas/sendmail/sendmail.c
new file mode 100644
index 0000000..7b2b542
--- /dev/null
+++ b/src/pmdas/sendmail/sendmail.c
@@ -0,0 +1,524 @@
+/*
+ * Sendmail PMDA
+ *
+ * Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include <sys/stat.h>
+
+/*
+ * Sendmail PMDA
+ *
+ * This PMDA uses the statistics file that sendmail optionally maintains
+ * -- see "OS<file>" or "O StatusFile=<file>" in sendmail.cf and sendmail(1)
+ *
+ * This file (defaults to /var/sendmail.st) must be created before sendmail
+ * will update any statistics.
+ */
+
+/*
+ * list of instances
+ */
+
+static pmdaIndom indomtab[] = {
+#define MAILER_INDOM 0
+ { MAILER_INDOM, 0, NULL },
+};
+
+static char *statsfile = "/var/sendmail.st";
+static char *username;
+static int nmailer;
+static void *ptr;
+static struct stat laststatbuf;
+static time_t *start_date;
+static __uint32_t *msgs_from;
+static __uint32_t *kbytes_from;
+static __uint32_t *msgs_to;
+static __uint32_t *kbytes_to;
+
+static pmdaMetric metrictab[] = {
+/* start_date */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* permailer.msgs_from */
+ { NULL,
+ { PMDA_PMID(1,0), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* permailer.bytes_from */
+ { NULL,
+ { PMDA_PMID(1,1), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* permailer.msgs_to */
+ { NULL,
+ { PMDA_PMID(1,2), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* permailer.bytes_to */
+ { NULL,
+ { PMDA_PMID(1,3), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* total.msgs_from */
+ { NULL,
+ { PMDA_PMID(2,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* total.bytes_from */
+ { NULL,
+ { PMDA_PMID(2,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+/* total.msgs_to */
+ { NULL,
+ { PMDA_PMID(2,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* total.bytes_to */
+ { NULL,
+ { PMDA_PMID(2,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, },
+};
+
+static void
+map_stats(void)
+{
+ struct stat statbuf;
+ static int fd;
+
+ static int notified = 0;
+#define MAPSTATS_NULL 0x01
+#define MAPSTATS_NOTV2STRUCT 0x02
+#define MAPSTATS_MAPFAIL 0x04
+
+ /* From mailstats.h in sendmail(1) 8.x... */
+ struct smstat_s
+ {
+#define MAXMAILERS 25
+#define STAT_VERSION 2
+#define STAT_MAGIC 0x1B1DE
+ int stat_magic; /* magic number */
+ int stat_version; /* stat file version */
+ time_t stat_itime; /* file initialization time */
+ short stat_size; /* size of this structure */
+ long stat_nf[MAXMAILERS]; /* # msgs from each mailer */
+ long stat_bf[MAXMAILERS]; /* kbytes from each mailer */
+ long stat_nt[MAXMAILERS]; /* # msgs to each mailer */
+ long stat_bt[MAXMAILERS]; /* kbytes to each mailer */
+ long stat_nr[MAXMAILERS]; /* # rejects by each mailer */
+ long stat_nd[MAXMAILERS]; /* # discards by each mailer */
+ } *smstat;
+
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: Entering:\n", pmProgname);
+ fprintf(stderr, "%s: map_stats: Check: ptr = " PRINTF_P_PFX "%p\n", pmProgname, ptr);
+ fprintf(stderr, "%s: map_stats: Check: statsfile = " PRINTF_P_PFX "%p\n", pmProgname, statsfile);
+ if (statsfile != NULL)
+ fprintf(stderr, "%s: map_stats: = %s\n", pmProgname, statsfile);
+ }
+#endif
+
+ if (statsfile == NULL || stat(statsfile, &statbuf) < 0) {
+ /* if sendmail not collecting stats this is expected */
+ if (ptr != NULL) {
+ /* must have gone away */
+ __pmMemoryUnmap(ptr, laststatbuf.st_size);
+ close(fd);
+ ptr = NULL;
+ notified &= ~MAPSTATS_NOTV2STRUCT;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: (Maybe) stat() < 0; pmunmap() called\n", pmProgname);
+ }
+#endif
+ }
+ return;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: Check: statbuf.st_ino = %lu\n", pmProgname, (unsigned long)statbuf.st_ino);
+ fprintf(stderr, "%s: map_stats: Check: statbuf.st_dev = %lu\n", pmProgname, (unsigned long)statbuf.st_dev);
+ fprintf(stderr, "%s: map_stats: Check: laststatbuf.st_ino = %lu\n", pmProgname, (unsigned long)laststatbuf.st_ino);
+ fprintf(stderr, "%s: map_stats: Check: laststatbuf.st_dev = %lu\n", pmProgname, (unsigned long)laststatbuf.st_dev);
+ }
+#endif
+ if (statbuf.st_ino != laststatbuf.st_ino ||
+ statbuf.st_dev != laststatbuf.st_dev ||
+ ptr == NULL) {
+ /*
+ * Not the same as the file we saw last time, or statsfile is
+ * not mapped into memory (because it was zero length).
+ *
+ * The file can change due to rotation or restarting sendmail...
+ * note the times (st_atim, st_mtim and st_ctim) are all expected
+ * to change as sendmail updates the file, hence we must use dev
+ * and ino.
+ *
+ * ino is guaranteed to change for different instances of the
+ * sendmail stats file, since a mmap()'d file is never closed
+ * until after it's munmap()'d.
+ */
+
+ if (ptr != NULL) {
+ __pmMemoryUnmap(ptr, laststatbuf.st_size);
+ close(fd);
+ ptr = NULL;
+ notified &= ~MAPSTATS_NOTV2STRUCT;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: statbuf.st_[dev|ido] changed; pmunmap() called\n", pmProgname);
+ }
+#endif
+ }
+
+ if ((fd = open(statsfile, O_RDONLY)) < 0) {
+ __pmNotifyErr(LOG_WARNING, "%s: map_stats: cannot open(\"%s\",...): %s",
+ pmProgname, statsfile, osstrerror());
+ return;
+ }
+ ptr = __pmMemoryMap(fd, statbuf.st_size, 0);
+ if (ptr == NULL) {
+ if (!(notified & MAPSTATS_MAPFAIL)) {
+ __pmNotifyErr(LOG_ERR, "%s: map_stats: memmap of %s failed: %s",
+ pmProgname, statsfile, osstrerror());
+ }
+ close(fd);
+ ptr = NULL;
+ notified |= MAPSTATS_MAPFAIL;
+ return;
+ }
+
+ laststatbuf = statbuf; /* struct assignment */
+ notified &= ~(MAPSTATS_NULL | MAPSTATS_MAPFAIL);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: mmap() called, succeeded\n", pmProgname);
+ }
+#endif
+
+ /* Check for a statistics file from sendmail(1) 8.x: */
+ smstat = (struct smstat_s *)ptr;
+ if (smstat->stat_magic != STAT_MAGIC ||
+ smstat->stat_version != STAT_VERSION) {
+ if (! (notified & MAPSTATS_NOTV2STRUCT)) {
+ __pmNotifyErr(LOG_WARNING, "%s: map_stats: cannot find magic number in file %s; assuming version 1 format",
+ pmProgname, statsfile);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: map_stats: smstat_s contents:\n", pmProgname);
+ fprintf(stderr, "%s: map_stats: Version 2 format:\n", pmProgname);
+ fprintf(stderr, "%s: map_stats: Check: stat_magic = 0x%x\n", pmProgname, smstat->stat_magic);
+ fprintf(stderr, "%s: map_stats: Check: stat_version = 0x%x\n", pmProgname, smstat->stat_version);
+ fprintf(stderr, "%s: map_stats: Check: stat_itime = %s", pmProgname, ctime(&(smstat->stat_itime)));
+ fprintf(stderr, "%s: map_stats: Check: stat_size = %d\n", pmProgname, smstat->stat_size);
+
+ /* We're being difficult here... using smstat_s the wrong way! */
+ fprintf(stderr, "%s: map_stats: Version 1 format:\n", pmProgname);
+ fprintf(stderr, "%s: map_stats: Check: stat_itime = %s", pmProgname, ctime((time_t *)&(smstat->stat_magic)));
+ fprintf(stderr, "%s: map_stats: Check: stat_size = %d\n", pmProgname, *((short *)&(smstat->stat_version)));
+ }
+#endif
+ notified |= MAPSTATS_NOTV2STRUCT;
+ }
+
+ /* Could be older version of stats file... here is the original code
+ that dealt with that case */
+ /*
+ * format of [older version] sendmail stats file:
+ * word[0] time_t file first created
+ * word[1] N/A
+ * word[2] .. word[K+2] msgs_from mailers 0 .. K
+ * word[K+3] .. word[2*K+3] kbytes_from mailers 0 .. K
+ * word[2*K+3] .. word[3*K+4] msgs_to mailers 0 .. K
+ * word[3*K+4] .. word[4*K+5] kbytes_to mailers 0 .. K
+ */
+ nmailer = (statbuf.st_size - sizeof(__int32_t) - sizeof(__int32_t)) / (4 * sizeof(__uint32_t));
+ msgs_from = &((__uint32_t *)ptr)[2];
+ kbytes_from = &msgs_from[nmailer];
+ msgs_to = &kbytes_from[nmailer];
+ kbytes_to = &msgs_to[nmailer];
+ start_date = (time_t *)ptr;
+ }
+ else {
+ /* Assign pointers to point to parts of the v2 struct */
+ nmailer = ((char *)smstat->stat_bf - (char *)smstat->stat_nf) / sizeof(long);
+ msgs_from = (__uint32_t *)&(smstat->stat_nf);
+ kbytes_from = (__uint32_t *)&(smstat->stat_bf);
+ msgs_to = (__uint32_t *)&(smstat->stat_nt);
+ kbytes_to = (__uint32_t *)&(smstat->stat_bt);
+ start_date = &(smstat->stat_itime);
+ }
+ }
+}
+
+/*
+ * logic here is similar to that used by mailstats(1)
+ */
+static void
+do_sendmail_cf(void)
+{
+ FILE *fp;
+ char buf[MAXPATHLEN+20];
+ char *bp;
+ int i;
+ int lineno = 0;
+
+ if ((fp = fopen("/etc/sendmail.cf", "r")) == NULL) {
+ if ((fp = fopen("/etc/mail/sendmail.cf", "r")) == NULL) {
+ /* this is pretty serious! */
+ nmailer = 0;
+ statsfile = NULL;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Warning: cannot find sendmail.cf, so no stats!\n");
+#endif
+ return;
+ }
+ }
+
+ nmailer = 3;
+ indomtab[MAILER_INDOM].it_set = (pmdaInstid *)malloc(nmailer * sizeof(pmdaInstid));
+ indomtab[MAILER_INDOM].it_set[0].i_inst = 0;
+ indomtab[MAILER_INDOM].it_set[0].i_name = "prog";
+ indomtab[MAILER_INDOM].it_set[1].i_inst = 1;
+ indomtab[MAILER_INDOM].it_set[1].i_name = "*file*";
+ indomtab[MAILER_INDOM].it_set[2].i_inst = 2;
+ indomtab[MAILER_INDOM].it_set[2].i_name = "*include*";
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ lineno++;
+ bp = buf;
+
+ if (*bp == 'M') {
+ /* mailer definition */
+ bp++;
+ while (*bp != ',' && !isspace((int)*bp) && *bp != '\0')
+ bp++;
+ *bp = '\0';
+ for (i = 0; i < nmailer; i++) {
+ if (strcmp(&buf[1], indomtab[MAILER_INDOM].it_set[i].i_name) == 0)
+ break;
+ }
+ if (i == nmailer) {
+ indomtab[MAILER_INDOM].it_set = (pmdaInstid *)realloc(indomtab[MAILER_INDOM].it_set, (nmailer+1) * sizeof(pmdaInstid));
+ indomtab[MAILER_INDOM].it_set[nmailer].i_name = strdup(&buf[1]);
+ indomtab[MAILER_INDOM].it_set[nmailer].i_inst = nmailer;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "sendmail.cf[%d]: mailer \"%s\" inst=%d\n",
+ lineno, &buf[1], nmailer);
+#endif
+ nmailer++;
+ }
+ }
+ else if (*bp == 'O') {
+ char *tp;
+
+ if (strncasecmp(++bp, " StatusFile", 11) == 0 &&
+ !isalnum((int)bp[11])) {
+ bp = strchr(bp, '=');
+ if (bp == NULL)
+ continue;
+ while (isspace((int)*++bp))
+ continue;
+ }
+ else if (*bp == 'S')
+ bp++;
+ else
+ continue;
+
+ tp = bp++;
+ while (*bp && !isspace((int)*bp) && *bp != '#')
+ bp++;
+ *bp = '\0';
+
+ statsfile = strdup(tp);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "sendmail.cf[%d]: statsfile \"%s\"\n",
+ lineno, tp);
+#endif
+ }
+ }
+ fclose(fp);
+
+ indomtab[MAILER_INDOM].it_numinst = nmailer;
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+sendmail_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (ptr == NULL)
+ return 0;
+
+ if (idp->cluster == 0) {
+ if (idp->item == 0) {
+ /* sendmail.start_date */
+ atom->cp = ctime(start_date);
+ atom->cp[24] = '\0'; /* no newline */
+ return 1;
+ }
+ }
+ else if (idp->cluster == 1) {
+ if (inst >= nmailer)
+ return 0;
+
+ if (msgs_from[inst] == 0 && msgs_to[inst] == 0) {
+ return 0;
+ }
+
+ switch (idp->item) {
+ case 0: /* sendmail.permailer.msgs_from */
+ atom->ul = msgs_from[inst];
+ break;
+
+ case 1: /* sendmail.permailer.bytes_from */
+ atom->ul = kbytes_from[inst];
+ break;
+
+ case 2: /* sendmail.permailer.msgs_to */
+ atom->ul = msgs_to[inst];
+ break;
+
+ case 3: /* sendmail.permailer.bytes_to */
+ atom->ul = kbytes_to[inst];
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+ }
+ else if (idp->cluster == 2) {
+ int i;
+
+ atom->ul = 0;
+
+ switch (idp->item) {
+ case 0: /* sendmail.total.msgs_from */
+ for (i = 0; i < nmailer; i++)
+ atom->ul += msgs_from[i];
+ break;
+
+ case 1: /* sendmail.total.bytes_from */
+ for (i = 0; i < nmailer; i++)
+ atom->ul += kbytes_from[i];
+ break;
+
+ case 2: /* sendmail.total.msgs_to */
+ for (i = 0; i < nmailer; i++)
+ atom->ul += msgs_to[i];
+ break;
+
+ case 3: /* sendmail.total.bytes_to */
+ for (i = 0; i < nmailer; i++)
+ atom->ul += kbytes_to[i];
+ break;
+
+ default:
+ return PM_ERR_PMID;
+ }
+
+ return 1;
+ }
+
+ return PM_ERR_PMID;
+}
+
+static int
+sendmail_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ map_stats();
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * Initialise the agent
+ */
+void
+__PMDA_INIT_CALL
+sendmail_init(pmdaInterface *dp)
+{
+ if (dp->status != 0)
+ return;
+
+ if (username)
+ __pmSetProcessIdentity(username);
+
+ do_sendmail_cf();
+ map_stats();
+
+ dp->version.two.fetch = sendmail_fetch;
+
+ pmdaSetFetchCallBack(dp, sendmail_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "sendmail" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, SENDMAIL,
+ "sendmail.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ sendmail_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/shping/GNUmakefile b/src/pmdas/shping/GNUmakefile
new file mode 100644
index 0000000..73b88da
--- /dev/null
+++ b/src/pmdas/shping/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = shping
+DOMAIN = SHPING
+
+CMDTARGETS = $(IAM)$(EXECSUFFIX)
+HFILES = shping.h
+CFILES = shping.c pmda.c
+OTHERS = README root help pmns sample.conf
+CHARTS = shping.CPUTime.pmchart shping.RealTime.pmchart
+PMIECS = shping.status.pmie shping.response.pmie
+
+LSRCFILES = $(OTHERS) $(CHARTS) $(PMIECS) Install Remove pmlogconf.summary
+LLDLIBS = $(PCP_PMDALIB) $(LIB_FOR_PTHREADS)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMCHART = $(PCP_VAR_DIR)/config/pmchart
+PMIE = $(PCP_VAR_DIR)/config/pmieconf/$(IAM)
+LDIRT = *.log *.dir *.pag so_locations a.out domain.h $(CMDTARGETS)
+
+default: build-me
+
+include $(TOPDIR)/src/include/buildrules
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGETS)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(OTHERS) domain.h $(PMDADIR)
+ $(INSTALL) -m 644 shping.CPUTime.pmchart $(PMCHART)/shping.CPUTime
+ $(INSTALL) -m 644 shping.RealTime.pmchart $(PMCHART)/shping.RealTime
+ $(INSTALL) -m 755 -d $(PMIE)
+ $(INSTALL) -m 644 shping.status.pmie $(PMIE)/status
+ $(INSTALL) -m 644 shping.response.pmie $(PMIE)/response
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.summary $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/summary
+else
+build-me:
+install:
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+shping.o pmda.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/shping/Install b/src/pmdas/shping/Install
new file mode 100755
index 0000000..bcca1b1
--- /dev/null
+++ b/src/pmdas/shping/Install
@@ -0,0 +1,222 @@
+#! /bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the shping PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=shping
+pmda_interface=2
+forced_restart=false
+
+# Do it
+#
+pmdaSetup
+
+# controls for installation procedures
+#
+daemon_opt=true # can install as daemon
+dso_opt=false
+pipe_opt=true # supports pipe IPC
+socket_opt=false # force pipe IPC
+check_delay=10 # give the PMDA a chance to set itself up
+
+# be careful that mortals cannot write any configuration files, as
+# these would present a security problem
+#
+umask 022
+
+
+# PMDA variables
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+configfile=""
+cycle=120
+timeout=20
+debug=0
+
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+_quit()
+{
+ rm -rf $tmp
+ exit $1
+}
+
+do_debug=false
+
+_parsedefaults()
+{
+ echo "Extracting options from current installation ..."
+ while getopts D:I:d:l:t: c
+ do
+ case $c in
+ \?) echo "Warning: Unrecognized option in $PCP_PMCDCONF_PATH"
+ echo " Remove line for the $iam PMDA in $PCP_PMCDCONF_PATH and re-run ./Install"
+ _quit 2;;
+ D ) debug=$OPTARG;;
+ I ) cycle=$OPTARG;;
+ t ) timeout=$OPTARG;;
+ * ) ;;
+ esac
+ done
+ eval configfile='$'$OPTIND
+}
+
+if $do_pmda
+then
+
+ # set options from $PCP_PMCDCONF_PATH, if possible
+ #
+ ans=`$PCP_AWK_PROG <$PCP_PMCDCONF_PATH '
+$1 == "'$iam'" { printf "%s",$6
+ for (i=7;i<=NF;i++) printf " %s",$i
+ print ""
+ }'`
+ if [ ! -z "$ans" ]
+ then
+ _parsedefaults $ans
+ fi
+
+ default_configfile=./sample.conf
+ if fgrep "CONFIGURE-ME-PLEASE" $default_configfile >/dev/null
+ then
+ # nslookup(1) may be hiding, like for OpenIndiana
+ #
+ export PATH=$PATH:/usr/sbin
+ nslookup=`which nslookup 2>/dev/null`
+ if [ -z "$nslookup" ]
+ then
+ echo "Warning: cannot find nslookup"
+ nslookup=nslookup
+ fi
+ # sample configuration file needs a little customization
+ #
+ if [ -f /etc/resolv.conf ]
+ then
+ my_dns_server=`$PCP_AWK_PROG </etc/resolv.conf '$1 == "nameserver" { print $2; exit }'`
+ else
+ my_dns_server=`hostname`
+ fi
+ sed <$default_configfile >$tmp/tmp \
+ -e '/CONFIGURE-ME-PLEASE/d' \
+ -e "s@DEFAULT-DNS-SERVER@$my_dns_server@" \
+ -e "s@NSLOOKUP@$nslookup@"
+ cp $tmp/tmp $default_configfile
+ fi
+
+ # go figure out which configuration file to use ...
+ #
+ pmdaChooseConfigFile
+
+ if [ ! -f "$configfile" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to enter commands to create a new configuration file? [y] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = "Xy" -o "X$ans" = "XY" -o -z "$ans" ]
+ then
+ configfile="$configdir/$iam.conf"
+ if [ -f $configfile ]
+ then
+ echo "Removing old configuration file \"$configfile\""
+ rm -f $configfile
+ if [ -f $configfile ]
+ then
+ echo "Cannot remove \"$configfile\""
+ _quit 1
+ fi
+ fi
+
+ echo
+ echo \
+'Enter one ping specification per line, in the format
+
+tag command line details
+
+where the "tag" is a single unique word (no spaces) and the "command line
+details" are the corresponding sh(1) command. For example
+
+dns-self nslookup `hostname`
+
+An empty line terminates the specification process and there must be at
+least one specification.
+'
+
+ args=""
+ touch $configfile
+ if [ ! -f $configfile ]
+ then
+ echo "Installation aborted."
+ _quit 1
+ fi
+
+ while [ ! -s "$configfile" ]
+ do
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Tag Command: ""$PCP_ECHO_C"
+ read tag cmd
+ [ -z "$tag" ] && break
+ if grep "^$tag " $configfile >/dev/null
+ then
+ echo "Sorry, tag \"$tag\" already in use. Please try again."
+ continue
+ fi
+ echo "$tag $cmd" >>$configfile
+ done
+ done
+ else
+ echo ""
+ echo "Error: Abandoning installation as no configuration file was specified."
+ _quit 1
+ fi
+ fi
+
+ echo
+ echo "All commands are run one after another as a group and the group is run"
+ $PCP_ECHO_PROG $PCP_ECHO_N "once per \"cycle\" time. Enter the cycle time in seconds [$cycle] ""$PCP_ECHO_C"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ cycle=$ans
+ fi
+
+ echo
+ echo "Each command must complete within a timeout period, or it will be aborted"
+ $PCP_ECHO_PROG $PCP_ECHO_N "by the \"$iam\" PMDA. Enter the timeout period (in seconds) [$timeout] ""$PCP_ECHO_C"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ timeout=$ans
+ fi
+
+ if [ "$do_debug" = true ]
+ then
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Enter the debugging flag (see pmdbg(1)) [$debug] ""$PCP_ECHO_C"
+ read ans
+ if [ ! -z "$ans" ]
+ then
+ debug=$ans
+ fi
+ fi
+
+ args="-I $cycle -t $timeout -D $debug $configfile"
+fi
+
+pmdaInstall
+
+_quit 0
diff --git a/src/pmdas/shping/README b/src/pmdas/shping/README
new file mode 100644
index 0000000..a071cbc
--- /dev/null
+++ b/src/pmdas/shping/README
@@ -0,0 +1,91 @@
+Performance Co-Pilot shping PMDA for General Performance Monitoring
+===================================================================
+
+This PMDA is designed to be configurable to monitor elapsed time and
+CPU time (user and system) for arbitrary applications that can be run
+from the Bourne shell. Each application is assumed to run to completion
+to probe or ping a particular service or dimension of system performance.
+
+The metrics exported from the shping PMDA may be used to quantify of
+service or service availability for both critical system services and
+tasks that well correlated to performance as perceived by end-users.
+
+The sample configuration file includes examples to "ping":
+
+ + sh(1) start up and exit
+ + a simple task, date(1)
+ + sum(1) for some simple user-mode computation
+ + compilation and execution of an antipodean variant of the
+ generic "hullo world" C program
+ + DNS (default server, trivial and error cases)
+ + yp service via ypcat(1)
+ + rpcinfo(1) for RPC registration from portmap/rpcbind
+ + mail delivery (telnet tcp port 25)
+ + Usenet news from nntp (telnet tcp port 119)
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT shping
+
+Installation of the shping PMDA
+===============================
+
+ + # cd $PCP_PMDAS_DIR/shping
+
+ + Check that there is no clash with the Performance Metrics Domain
+ number defined in ./domain.h and the other PMDAs currently in use
+ (see $PCP_PMCDCONF_PATH). If there is, edit ./domain.h and choose
+ another domain number.
+
+ + Then run the Install script (as root)
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ Answer the questions, which include the option to specify new or
+ alternate commands to be run. See $PCP_PMDAS_DIR/shping/sample.conf
+ for example specifications of commands.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/shping
+ # ./Remove
+
+Changing the settings
+=====================
+
+The cycle time and timeout period can be dynamically modified using
+pmstore(1) for the metrics shping.control.cycletime and
+shping.control.timeout respectively.
+
+To make permanent changes, re-run the Install script.
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/shping.log) should be checked for any warnings
+ or errors.
+
+ + If the Install script reports some warnings when checking the
+ metrics, the problem should be listed in one of the log files.
+
+ + Additional information can be logged if there appears to be
+ problems. The PCP application debug flags will cause the PMDA to
+ report additional information in $PCP_LOG_DIR/pmcd/shping.log. For
+ details about the agent's debug flags, use the comand
+
+ $ pminfo -T shping.control.debug
diff --git a/src/pmdas/shping/Remove b/src/pmdas/shping/Remove
new file mode 100644
index 0000000..b991ac5
--- /dev/null
+++ b/src/pmdas/shping/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the shping PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=shping
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/shping/help b/src/pmdas/shping/help
new file mode 100644
index 0000000..69d758c
--- /dev/null
+++ b/src/pmdas/shping/help
@@ -0,0 +1,137 @@
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# shping PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ 19.0 shping command instance domain
+There is one instance for each command run by the shping PMDA.
+
+The external instance name comes from the "tag" in the shping PMDA
+configuration file. The internal instance number is the ordinal
+command number in the configuration file.
+
+@ shping.error command execution error code for shping PMDA
+As each command is executed, if there is a problem, the error
+code or cause is stored in shping.error.
+
+The interpretation of the value for shping.error depends on
+shping.status as follows:
+
+ If shping.status is 1 (the command was run but returned a non-zero
+ exit status) then shping.error is the exit status.
+
+ If shping.status is 2 (the command was run but was terminated by
+ a signal) then shping.error is the signal number.
+
+ If shping.status is 3 (the command did not complete) then
+ shping.error is a PCP error codes: see pmerr(1). Of particular
+ relevance is -1008 (PM_ERR_TIMEOUT) when the command failed to
+ complete in the time specified by shping.control.timeout.
+
+ If shping.status is 4 (the commands was not run) then shping.error
+ is the value of the system error code.
+
+ Otherwise shping.error will be zero.
+
+@ shping.status command execution status for shping PMDA
+As each command is executed, the success or failure is encoded in
+shping.status, using the following values:
+
+ -1 PMDA is initializing and command has not been run yet
+ 0 command completed and exit status was 0
+ 1 command completed and exit status was non-zero
+ 2 command was run but terminated by a signal
+ 3 command was run but did not complete (usually a timeout)
+ 4 command was not run due to some system error or resource
+ availability
+
+@ shping.time.real elapsed time for a command
+This metric records the elapsed time in milliseconds for the most recent
+execution of each command to be run by the shping PMDA.
+
+Care should be used when interpreting the value if the corresponding
+value for shping.status is non-zero, as the command may not have run to
+completion. If the command timed out, shping.time.real will be -1.
+
+@ shping.time.cpu_usr user mode CPU time for a command
+This metric records the user mode CPU time in milliseconds for the most
+recent execution of each command to be run by the shping PMDA.
+
+Care should be used when interpreting the value if the corresponding
+value for shping.status is non-zero, as the command may not have run to
+completion. If the command timed out, shping.time.cpu_usr will be -1.
+
+@ shping.time.cpu_sys system mode CPU time for a command
+This metric records the system mode CPU time in milliseconds for the most
+recent execution of each command to be run by the shping PMDA.
+
+Care should be used when interpreting the value if the corresponding
+value for shping.status is non-zero, as the command may not have run to
+completion. If the command timed out, shping.time.cpu_sys will be -1.
+
+@ shping.cmd commands run by shping PMDA
+The text of each sh(1) command run by the shping PMDA.
+
+@ shping.control.numcmd number of commands in the group to be run by the shping PMDA
+
+@ shping.control.cycles number of times the command group has been run by the shping PMDA
+
+@ shping.control.cycletime shping PMDA cycle time
+All commands are run by the shping PMDA are executed one after another
+in a group, and the group is run once per "cycle" time. This metric
+reports the cycle time in seconds.
+
+The cycle time may be changed dynamically by modifying this metric
+with pmstore(1).
+
+@ shping.control.timeout shping PMDA timeout period
+The number of seconds the shping PMDA is willing to wait before
+considering a single command to have timed out and killing it off.
+
+The time out interval may be changed dynamically by modifying this
+metric with pmstore(1).
+
+@ shping.control.debug shping PMDA debug flag
+The debug flag for the shping PMDA (see pmdbg(1)). All trace and
+diagnostic files are created in $PCP_LOG_DIR/pmcd.
+
+The debug flags DBG_TRACE_APPL0 (2048) and DBG_TRACE_APPL1 (4096)
+may be used as follows:
+
+DBG_TRACE_APPL0 - additional trace messages associated with the running
+ of each command appear in shping.log
+
+DBG_TRACE_APPL1 - the standard output and standard error of each command
+ is appended to shping.out (instead of the default
+ /dev/null)
+
+The debug flags may be changed dynamically by modifying this
+metric with pmstore(1), e.g.
+ $ pmstore shping.control.debug 6144
+would enable both of the diagnostic traces associated with
+DBG_TRACE_APPL0 and DBG_TRACE_APPL1.
+
diff --git a/src/pmdas/shping/pmda.c b/src/pmdas/shping/pmda.c
new file mode 100644
index 0000000..648cec3
--- /dev/null
+++ b/src/pmdas/shping/pmda.c
@@ -0,0 +1,257 @@
+/*
+ * sh(1) ping PMDA
+ *
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "shping.h"
+#include "domain.h"
+
+__uint32_t cycletime = 120; /* default cycle time, 2 minutes */
+__uint32_t timeout = 20; /* response timeout in seconds */
+static char *username; /* user account to run under */
+cmd_t *cmdlist;
+
+#ifdef HAVE_SPROC
+
+/* Signals are only used with sproc, threads will never generate SIGGHLD */
+void
+onchld(int s)
+{
+ int done;
+ int waitStatus;
+ int die = 0;
+
+ while ((done = waitpid(-1, &waitStatus, WNOHANG)) > 0) {
+
+ if (done != sprocpid)
+ continue;
+ die = 1;
+
+ if (WIFEXITED(waitStatus)) {
+
+ if (WEXITSTATUS(waitStatus) == 0)
+ logmessage(LOG_INFO,
+ "Sproc (pid=%d) exited normally\n",
+ done);
+ else
+ logmessage(LOG_CRIT,
+ "Sproc (pid=%d) exited with status = %d\n",
+ done, WEXITSTATUS(waitStatus));
+ }
+ else if (WIFSIGNALED(waitStatus)) {
+
+ if (WCOREDUMP(waitStatus))
+ logmessage(LOG_CRIT,
+ "Sproc (pid=%d) received signal = %d and dumped core\n",
+ done, WTERMSIG(waitStatus));
+ else
+ logmessage(LOG_CRIT,
+ "Sproc (pid=%d) received signal = %d\n",
+ done, WTERMSIG(waitStatus));
+ }
+ else {
+ logmessage(LOG_CRIT,
+ "Sproc (pid=%d) died, reason unknown\n", done);
+ }
+ }
+
+ if (die) {
+ logmessage(LOG_INFO, "Main process exiting\n");
+ exit(0);
+ }
+}
+#endif
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [options] configfile\n\n\
+Options:\n\
+ -C parse configuration file and exit\n\
+ -d domain use domain (numeric) for metrics domain of PMDA\n\
+ -I interval cycle time in seconds between subsequent executions of each\n\
+ command [default 120 seconds]\n\
+ -l logfile write log into logfile rather than using the default log\n\
+ -t timeout time in seconds before aborting the wait for individual\n\
+ commands to complete [default 20 seconds]\n\
+ -U username run the agent and commands as alternate user [default \"pcp\"]\n",
+ pmProgname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ pmdaInterface dispatch;
+ int n = 0;
+ int i;
+ int err = 0;
+ int sep = __pmPathSeparator();
+ int line;
+ int numcmd = 0;
+ int parseonly = 0;
+ char *configfile;
+ FILE *conf;
+ char *endnum;
+ char *p;
+ char *tag;
+ char lbuf[256];
+ char mypath[MAXPATHLEN];
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "shping" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, SHPING,
+ "shping.log", mypath);
+
+ while ((n = pmdaGetOpt(argc, argv,"CD:d:I:l:t:U:?",
+ &dispatch, &err)) != EOF) {
+ switch (n) {
+
+ case 'C':
+ parseonly = 1;
+ break;
+
+ case 'I':
+ cycletime = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr,
+ "%s: -I requires number of seconds as argument\n",
+ pmProgname);
+ err++;
+ }
+ break;
+
+ case 't':
+ timeout = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr,
+ "%s: -t requires number of seconds as argument\n",
+ pmProgname);
+ err++;
+ }
+ break;
+
+ case 'U':
+ username = optarg;
+ break;
+
+ case '?':
+ err++;
+ }
+ }
+
+ if (err || optind != argc -1) {
+ usage();
+ }
+
+ configfile = argv[optind];
+ if ((conf = fopen(configfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Unable to open config file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ line = 0;
+
+ for ( ; ; ) {
+ if (fgets(lbuf, sizeof(lbuf), conf) == NULL)
+ break;
+
+ line++;
+ p = lbuf;
+ while (*p && isspace((int)*p))
+ p++;
+ if (*p == '\0' || *p == '\n' || *p == '#')
+ continue;
+ tag = p++;
+ while (*p && !isspace((int)*p))
+ p++;
+ if (*p)
+ *p++ = '\0';
+ while (*p && isspace((int)*p))
+ p++;
+ if (*p == '\0' || *p == '\n') {
+ fprintf(stderr, "[%s:%d] missing command after tag \"%s\"\n",
+ configfile, line, tag);
+ exit(1);
+ }
+
+ numcmd++;
+ if (parseonly)
+ continue;
+ if ((cmdlist = (cmd_t *)realloc(cmdlist, numcmd * sizeof(cmd_t))) == NULL) {
+ __pmNoMem("main:cmdlist", numcmd * sizeof(cmd_t),
+ PM_FATAL_ERR);
+ }
+
+ cmdlist[numcmd-1].tag = strdup(tag);
+ cmdlist[numcmd-1].cmd = strdup(p);
+ cmdlist[numcmd-1].status = STATUS_NOTYET;
+ cmdlist[numcmd-1].error = 0;
+ cmdlist[numcmd-1].real = cmdlist[numcmd-1].usr = cmdlist[numcmd-1].sys = -1;
+
+ /* trim trailing newline */
+ p = cmdlist[numcmd-1].cmd;
+ while (*p && *p != '\n')
+ p++;
+ *p = '\0';
+ }
+
+ fclose(conf);
+
+ if (numcmd == 0) {
+ fprintf(stderr, "%s: No commands in config file \"%s\"?\n",
+ pmProgname, configfile);
+ exit(1);
+ }
+ else if (parseonly)
+ exit(0);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "Parsed %d commands\n", numcmd);
+ fprintf(stderr, "Tag\tCommand\n");
+ for (i = 0; i < numcmd; i++) {
+ fprintf(stderr, "[%s]\t%s\n", cmdlist[i].tag, cmdlist[i].cmd);
+ }
+ }
+#endif
+
+ /* set up indom description */
+ indomtab.it_numinst = numcmd;
+ if ((indomtab.it_set = (pmdaInstid *)malloc(numcmd*sizeof(pmdaInstid))) == NULL) {
+ __pmNoMem("main.indomtab", numcmd * sizeof(pmdaInstid), PM_FATAL_ERR);
+ }
+ for (i = 0; i < numcmd; i++) {
+ indomtab.it_set[i].i_inst = i;
+ indomtab.it_set[i].i_name = cmdlist[i].tag;
+ }
+
+#ifdef HAVE_SPROC
+ signal(SIGCHLD, onchld);
+#endif
+
+ pmdaOpenLog(&dispatch);
+ __pmSetProcessIdentity(username);
+
+ shping_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/shping/pmlogconf.summary b/src/pmdas/shping/pmlogconf.summary
new file mode 100644
index 0000000..709865e
--- /dev/null
+++ b/src/pmdas/shping/pmlogconf.summary
@@ -0,0 +1,6 @@
+#pmlogconf-setup 2.0
+ident shping PMDA summary information
+probe shping.status exists ? include : exclude
+ shping.status
+ shping.time
+ shping.error
diff --git a/src/pmdas/shping/pmns b/src/pmdas/shping/pmns
new file mode 100644
index 0000000..70003e6
--- /dev/null
+++ b/src/pmdas/shping/pmns
@@ -0,0 +1,41 @@
+/*
+ * Metrics for shping PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+shping {
+ status SHPING:0:6
+ error SHPING:0:10
+ time
+ cmd SHPING:0:7
+ control
+}
+
+shping.time {
+ real SHPING:0:0
+ cpu_usr SHPING:0:1
+ cpu_sys SHPING:0:2
+}
+
+shping.control {
+ numcmd SHPING:0:3
+ cycles SHPING:0:9
+ cycletime SHPING:0:4
+ timeout SHPING:0:5
+ debug SHPING:0:8
+}
diff --git a/src/pmdas/shping/root b/src/pmdas/shping/root
new file mode 100644
index 0000000..a0e1b52
--- /dev/null
+++ b/src/pmdas/shping/root
@@ -0,0 +1,9 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { shping }
+
+#include "pmns"
diff --git a/src/pmdas/shping/sample.conf b/src/pmdas/shping/sample.conf
new file mode 100644
index 0000000..028a196
--- /dev/null
+++ b/src/pmdas/shping/sample.conf
@@ -0,0 +1,46 @@
+# sample configuration file for shping PMDA
+#
+# Warning: these commands will be run as "root" using sh(1) with the
+# current directory set to $PCP_LOG_DIR/pmcd and the
+# following set in the environment:
+# IFS=" \t\n"
+# PATH=... as per $PCP_DIR/etc/pcp.env ...
+#
+
+# Specification format, one per line ("Tags" must be unique)
+# Tag Shell command
+
+# minimal effort here, no stress for the shell ... just start and quit
+null exit 0
+
+# not too much work, date(1) is pretty light-weight
+date date
+
+# compile and run the generic simple program ... requires a C compiler
+# to be installed
+cc cd /tmp; rm -f $$.[oc] $$; echo "main(){printf(\"g'day world\\\\n\");}" >/tmp/$$.c; cc -o $$ $$.c; ./$$; rm -f $$.[oc] $$
+
+# Is the default DNS server responding?
+# CONFIGURE-ME-PLEASE - local customization required
+# CONFIGURE-ME-PLEASE - DEFAULT-DNS-SERVER will be changed by Install to
+# CONFIGURE-ME-PLEASE - be the hostname for the default DNS server
+# CONFIGURE-ME-PLEASE - and NSLOOKUP is the path of the nslookup(1) command
+dns NSLOOKUP - DEFAULT-DNS-SERVER </dev/null
+
+# DNS lookup for localhost
+dns-self NSLOOKUP `hostname`
+
+# DNS lookup that will fail ...
+dns-err NSLOOKUP foo.bar.no.host.com
+
+# ypserv ... if you have yp running
+ypserv ypcat hosts | grep `hostname`
+
+# contact portmap/rpcbind for registered RPC programs
+rpcbind rpcinfo -p
+
+# if smtpd is running here, a simple test
+smtp ( echo "expn root"; echo quit ) | telnet-probe localhost 25
+
+# NNTP ... need to customize nntp server host
+nntp ( echo "listgroup comp.sys.sgi"; echo quit ) | telnet-probe tokyo.engr.sgi.com 119
diff --git a/src/pmdas/shping/shping.CPUTime.pmchart b/src/pmdas/shping/shping.CPUTime.pmchart
new file mode 100644
index 0000000..cce7e88
--- /dev/null
+++ b/src/pmdas/shping/shping.CPUTime.pmchart
@@ -0,0 +1,11 @@
+#pmchart
+Version 2.0 host dynamic
+
+Chart Title "User CPU Time for shping Commands" Style plot
+ Plot Color #-cycle Host * Metric shping.time.cpu_usr Matching .*
+
+Chart Title "System CPU Time for shping Commands" Style plot
+ Plot Color #-cycle Host * Metric shping.time.cpu_sys Matching .*
+
+#
+# CPU time (user and system) for commands run by the shping PMDA
diff --git a/src/pmdas/shping/shping.RealTime.pmchart b/src/pmdas/shping/shping.RealTime.pmchart
new file mode 100644
index 0000000..05d5436
--- /dev/null
+++ b/src/pmdas/shping/shping.RealTime.pmchart
@@ -0,0 +1,9 @@
+#pmchart
+Version 2.0 host dynamic
+
+Chart Title "Elapsed Time for shping Commands" Style plot
+ Plot Color #-cycle Host * Metric shping.time.real Matching .*
+
+#
+# Elapsed time (or real time, or response time) for commands run by the
+# shping PMDA
diff --git a/src/pmdas/shping/shping.c b/src/pmdas/shping/shping.c
new file mode 100644
index 0000000..f14be03
--- /dev/null
+++ b/src/pmdas/shping/shping.c
@@ -0,0 +1,679 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "shping.h"
+#include "domain.h"
+#include <sys/stat.h>
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_SYS_RESOURCE_H)
+#include <sys/resource.h>
+#endif
+#if defined(HAVE_SYS_PRCTL_H)
+#include <sys/prctl.h>
+#endif
+#if defined(HAVE_SCHED_H)
+#include <sched.h>
+#endif
+#if defined(HAVE_PTHREAD_H)
+#include <pthread.h>
+#endif
+
+#define LOG_PRI(p) ((p) & LOG_PRIMASK)
+
+static int cycles; /* completed cycles */
+static int numskip; /* skipped cycles */
+static int numcmd; /* number of commands */
+static int timedout; /* command timed out */
+static pid_t shpid; /* for /sbin/sh running command */
+
+#if defined(HAVE_PTHREAD_H)
+static pthread_t sprocpid;
+#elif defined(HAVE_SCHED_H)
+pid_t sprocpid; /* for refresh() */
+#else
+#error "Need pthreads or sproc"
+#endif
+
+/*
+ * only one instance domain here ...
+ */
+#define SHPING_INDOM 0
+pmdaIndom indomtab = { 0, 0, NULL };
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+static pmdaMetric metrics[] =
+{
+/* time.real */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_FLOAT, SHPING_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0), }, },
+/* time.cpu_usr */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_FLOAT, SHPING_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0), }, },
+/* time.cpu_sys */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_FLOAT, SHPING_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0), }, },
+/* control.numcmd */
+ { NULL,
+ { PMDA_PMID(0,3),PM_TYPE_U32,PM_INDOM_NULL,PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0), }, },
+/* control.cycletime */
+ { NULL,
+ { PMDA_PMID(0,4),PM_TYPE_U32,PM_INDOM_NULL,PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0), }, },
+/* control.timeout */
+ { NULL,
+ { PMDA_PMID(0,5),PM_TYPE_U32,PM_INDOM_NULL,PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0), }, },
+/* status */
+ { NULL,
+ { PMDA_PMID(0,6),PM_TYPE_32,SHPING_INDOM,PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0), }, },
+/* cmd */
+ { NULL,
+ { PMDA_PMID(0,7),PM_TYPE_STRING,SHPING_INDOM,PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0), }, },
+/* control.debug */
+ { NULL,
+ { PMDA_PMID(0,8),PM_TYPE_32,PM_INDOM_NULL,PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0), }, },
+/* control.cycles */
+ { NULL,
+ { PMDA_PMID(0,9),PM_TYPE_U32,PM_INDOM_NULL,PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE), }, },
+/* error */
+ { NULL,
+ { PMDA_PMID(0,10),PM_TYPE_32,SHPING_INDOM,PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0), }, },
+
+};
+
+static int nummetric = (sizeof(metrics)/sizeof(metrics[0]));
+
+void
+logmessage(int priority, const char *format, ...)
+{
+ va_list arglist;
+ char buffer[2048];
+ char *level;
+ char *p;
+ time_t now;
+
+ buffer[0] = '\0';
+ time(&now);
+
+ priority = LOG_PRI(priority);
+ switch (priority) {
+ case LOG_EMERG :
+ level = "Emergency";
+ break;
+ case LOG_ALERT :
+ level = "Alert";
+ break;
+ case LOG_CRIT :
+ level = "Critical";
+ break;
+ case LOG_ERR :
+ level = "Error";
+ break;
+ case LOG_WARNING :
+ level = "Warning";
+ break;
+ case LOG_NOTICE :
+ level = "Notice";
+ break;
+ case LOG_INFO :
+ level = "Info";
+ break;
+ case LOG_DEBUG :
+ level = "Debug";
+ break;
+ default:
+ level = "???";
+ break;
+ }
+
+ va_start (arglist, format);
+ vsnprintf (buffer, sizeof(buffer), format, arglist);
+
+ /* strip unwanted '\n' at end of text so's not to double up */
+ for (p = buffer; *p; p++);
+ if (*(--p) == '\n') *p = '\0';
+
+ fprintf (stderr, "[%.19s] %s(%" FMT_PID ") %s: %s\n", ctime(&now), pmProgname, getpid(), level, buffer) ;
+ va_end (arglist) ;
+}
+
+
+static void
+onhup(int dummy)
+{
+ _exit(0);
+}
+
+static void
+onalarm(int dummy)
+{
+ timedout = 1;
+ if (shpid > 1) { /* something other than error, self or init */
+ kill(-shpid, SIGTERM); /* nuke process group */
+ sleep(1);
+ kill(-shpid, SIGKILL);
+ }
+ signal(SIGALRM, onalarm);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Timeout!\n");
+#endif
+}
+
+/*
+ * the sproc starts here to refresh the metric values periodically
+ */
+static void
+refresh(void *dummy)
+{
+ int i;
+ int sts;
+ int waittime;
+ struct timeval startcycle;
+ struct timeval now;
+ struct timeval then;
+ struct rusage cpu_now;
+ struct rusage cpu_then;
+ char *argv[4];
+
+ signal(SIGHUP, onhup);
+#if defined (PR_TERMCHILD)
+ prctl(PR_TERMCHILD); /* SIGHUP when the parent dies */
+#elif defined (PR_SET_PDEATHSIG)
+ prctl(PR_SET_PDEATHSIG, SIGTERM);
+#elif defined(IS_SOLARIS) || defined(IS_DARWIN) || defined(IS_MINGW) || defined(IS_AIX) || defined(IS_FREEBSD) || defined(IS_GNU) || defined(IS_NETBSD)
+ /* no signals here for child exit */
+#else
+!bozo: cant decide between PR_TERMCHILD and PR_SET_PDEATHSIG
+#endif
+#ifndef NOFILE
+#define NOFILE 7
+#endif
+
+ signal(SIGALRM, onalarm);
+
+ putenv("IFS= \t\n");
+ putenv("PATH=/usr/sbin:/usr/bsd:/sbin:/usr/bin:/bin");
+
+ argv[0] = "sh";
+ argv[1] = "-c";
+ argv[2] = "";
+ argv[3] = NULL;
+
+ for ( ; ; ) {
+ cycles++;
+ __pmtimevalNow(&startcycle);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "\nStart cycle @ %s", ctime(&startcycle.tv_sec));
+#endif
+ for (i = 0; i < numcmd; i++) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "[%s] %s ->\n", cmdlist[i].tag, cmdlist[i].cmd);
+#endif
+ getrusage(RUSAGE_CHILDREN, &cpu_then);
+ __pmtimevalNow(&then);
+ fflush(stderr);
+ fflush(stdout);
+ shpid = fork();
+ if (shpid == 0) {
+ int j;
+ setsid(); /* make new process group */
+ for (j = 0; j < NOFILE; j++)
+ close(j);
+ open("/dev/null", O_RDONLY, 0);
+ sts = -1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ FILE *f;
+ if ((f = fopen("shping.out", "a")) != NULL) {
+ fprintf(f, "\n[%s] %s\n", cmdlist[i].tag, cmdlist[i].cmd);
+ fclose(f);
+ }
+ sts = open("shping.out", O_WRONLY|O_APPEND|O_CREAT, 0644);
+ }
+#endif
+ if (sts == -1)
+ open("/dev/null", O_WRONLY, 0);
+ if (dup(1) == -1) {
+ fprintf(stderr, "Warning: dup() failed: %s\n", pmErrStr(-oserror()));
+ }
+ argv[2] = cmdlist[i].cmd;
+ sts = execv("/bin/sh", argv);
+ exit(-1);
+ }
+ else if (shpid < 0) {
+ logmessage(LOG_CRIT, "refresh: fork() failed: %s", osstrerror());
+ cmdlist[i].status = STATUS_SYS;
+ cmdlist[i].error = oserror();
+ cmdlist[i].real = cmdlist[i].usr = cmdlist[i].sys = -1;
+ continue;
+
+ }
+ timedout = 0;
+ alarm(timeout);
+ waitpid(shpid, &sts, 0);
+ __pmtimevalNow(&now);
+ getrusage(RUSAGE_CHILDREN, &cpu_now);
+ alarm(0);
+
+ if (timedout) {
+ cmdlist[i].error = PM_ERR_TIMEOUT;
+ cmdlist[i].status = STATUS_TIMEOUT;
+ cmdlist[i].real = cmdlist[i].usr = cmdlist[i].sys = -1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_INFO, "[%s] timeout\n", cmdlist[i].tag);
+#endif
+ }
+ else {
+ if (WIFEXITED(sts)) {
+ cmdlist[i].error = WEXITSTATUS(sts);
+ if (cmdlist[i].error == 0)
+ cmdlist[i].status = STATUS_OK;
+ else {
+ cmdlist[i].status = STATUS_EXIT;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_INFO, "[%s] exit status: %d\n",
+ cmdlist[i].tag, cmdlist[i].error);
+#endif
+ }
+ }
+ else if (WIFSIGNALED(sts)) {
+ cmdlist[i].error = WTERMSIG(sts);
+ cmdlist[i].status = STATUS_SIG;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_INFO, "[%s] killed signal: %d\n",
+ cmdlist[i].tag, cmdlist[i].error);
+#endif
+ }
+ else {
+ /* assume WIFSTOPPED(sts) */
+ cmdlist[i].error = WSTOPSIG(sts);
+ cmdlist[i].status = STATUS_SIG;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_INFO, "[%s] stopped signal: %d\n",
+ cmdlist[i].tag, cmdlist[i].error);
+#endif
+ }
+ cmdlist[i].real = 1000 * (float)(now.tv_sec - then.tv_sec) +
+ (float)(now.tv_usec - then.tv_usec) / 1000;
+ cmdlist[i].usr = 1000 * (float)(cpu_now.ru_utime.tv_sec - cpu_then.ru_utime.tv_sec) +
+ (float)(cpu_now.ru_utime.tv_usec - cpu_then.ru_utime.tv_usec) / 1000;
+ cmdlist[i].sys = 1000 * (float)(cpu_now.ru_stime.tv_sec - cpu_then.ru_stime.tv_sec) +
+ (float)(cpu_now.ru_stime.tv_usec - cpu_then.ru_stime.tv_usec) / 1000;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "status: %d error: %d real: %.3f usr: %.3f sys: %.3f\n\n",
+ cmdlist[i].status, cmdlist[i].error,
+ cmdlist[i].real, cmdlist[i].usr, cmdlist[i].sys);
+#endif
+ }
+
+ /*
+ * harvest delinquent children ... includes those who were fork()'d
+ * above, and timed out.
+ */
+ for ( ; ; ) {
+ sts = waitpid(-1, NULL, WNOHANG);
+ if (sts <= 0)
+ break;
+ }
+
+ __pmtimevalNow(&now);
+ if (cycletime) {
+ waittime = (int)cycletime - now.tv_sec + startcycle.tv_sec;
+ if (waittime < 0) {
+ /* can't keep up */
+ while (waittime < 0) {
+ numskip++;
+ waittime += cycletime;
+ }
+ }
+ sleep(waittime);
+ }
+ }
+}
+
+static int
+shping_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *ext)
+{
+ int i; /* over pmidlist[] */
+ int j; /* over vset->vlist[] */
+ int sts;
+ int need;
+ int inst;
+ int numval;
+ static pmResult *res = NULL;
+ static int maxnpmids = 0;
+ pmValueSet *vset;
+ __pmID_int *pmidp;
+ pmAtomValue atom;
+ pmDesc *dp = NULL;
+ int type;
+
+#ifndef HAVE_SPROC
+ /* In the pthread world we don't have asyncronous notification that
+ * a thread has died, so we use pthread_kill to check is refresh
+ * is still running. */
+ int err;
+
+ if ( (err = pthread_kill (sprocpid, 0)) != 0 ) {
+ logmessage (LOG_CRIT, "'refresh' thread is not responding: %s\n",
+ strerror (err));
+ exit (1);
+ }
+#endif
+
+
+ if (numpmid > maxnpmids)
+ {
+ if (res != NULL)
+ free(res);
+
+/* (numpmid - 1) because there's room for one valueSet in a pmResult */
+
+ need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *);
+ if ((res = (pmResult *) malloc(need)) == NULL)
+ return -oserror();
+ maxnpmids = numpmid;
+ }
+
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+
+ for (i = 0; i < numpmid; i++) {
+
+ pmidp = (__pmID_int*)&pmidlist[i];
+ dp = NULL;
+
+ if (ext->e_direct) {
+ if (pmidp->cluster == 0 && pmidp->item < nummetric)
+ dp = &metrics[pmidp->item].m_desc;
+ }
+ else {
+ for (j = 1; j<nummetric; j++) {
+ if (pmidp->cluster == 0 &&
+ metrics[j].m_desc.pmid == pmidlist[i]) {
+ dp = &metrics[j].m_desc;
+ break;
+ }
+ }
+ }
+
+ if (dp == NULL)
+ numval = PM_ERR_PMID;
+ else {
+ if (dp->indom != PM_INDOM_NULL) {
+ numval = 0;
+ __pmdaStartInst(dp->indom, ext);
+ while(__pmdaNextInst(&inst, ext)) {
+ numval++;
+ }
+ }
+ else {
+ numval = 1;
+ }
+ }
+
+ if (numval >= 1)
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ (numval - 1)*sizeof(pmValue));
+ else
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+
+ if (vset == NULL) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ vset->pmid = pmidlist[i];
+ vset->numval = numval;
+ vset->valfmt = PM_VAL_INSITU;
+ if (vset->numval <= 0)
+ continue;
+
+ if (dp->indom == PM_INDOM_NULL)
+ inst = PM_IN_NULL;
+ else {
+ __pmdaStartInst(dp->indom, ext);
+ __pmdaNextInst(&inst, ext);
+ }
+
+ type = dp->type;
+ pmidp = (__pmID_int *)&pmidlist[i];
+ j = 0;
+
+ do {
+ if (j == numval) {
+ /* more instances than expected! */
+ numval++;
+ res->vset[i] = vset = (pmValueSet *)realloc(vset,
+ sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue));
+ if (vset == NULL) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ }
+ vset->vlist[j].inst = inst;
+
+ if (pmidp->cluster == 0) {
+ switch (pmidp->item) {
+
+ case 0: /* shping.time.real PMID: ...0.0 */
+ atom.f = cmdlist[inst].real;
+ break;
+
+ case 1: /* shping.time.cpu_usr PMID: ...0.1 */
+ atom.f = cmdlist[inst].usr;
+ break;
+
+ case 2: /* shping.time.cpu_sys PMID: ...0.2 */
+ atom.f = cmdlist[inst].sys;
+ break;
+
+ case 3: /* shping.control.numcmd PMID: ...0.3 */
+ atom.ul = numcmd;
+ break;
+
+ case 4: /* shping.control.cycletime PMID: ...0.4 */
+ atom.ul = cycletime;
+ break;
+
+ case 5: /* shping.control.timeout PMID: ...0.5 */
+ atom.ul = timeout;
+ break;
+
+ case 6: /* shping.status PMID: ...0.6 */
+ atom.l = cmdlist[inst].status;
+ break;
+
+ case 7: /* shping.cmd PMID: ...0.7 */
+ atom.cp = cmdlist[inst].cmd;
+ break;
+
+ case 8: /* shping.control.debug PMID: ...0.8 */
+ atom.l = pmDebug;
+ break;
+
+ case 9: /* shping.control.cycles PMID: ...0.9 */
+ atom.ul = cycles;
+ break;
+
+ case 10: /* shping.error PMID: ...0.10 */
+ atom.ul = cmdlist[inst].error;
+ break;
+
+ default:
+ j = PM_ERR_PMID;
+ break;
+ }
+ }
+ if (j < 0)
+ break;
+
+ sts = __pmStuffValue(&atom, &vset->vlist[j], type);
+ if (sts < 0) {
+ __pmFreeResultValues(res);
+ return sts;
+ }
+
+ vset->valfmt = sts;
+ j++; /* next element in vlist[] for next instance */
+ } while (dp->indom != PM_INDOM_NULL && __pmdaNextInst(&inst, ext));
+
+ vset->numval = j;
+ }
+ *resp = res;
+ return 0;
+}
+
+static int
+shping_store(pmResult *result, pmdaExt *ext)
+{
+ int i;
+ pmValueSet *vsp;
+ int sts = 0;
+ int ival;
+ __pmID_int *pmidp;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+ if (pmidp->cluster == 0) {
+ switch (pmidp->item) {
+ case 4: /* shping.control.cycletime PMID: ...0.4 */
+ ival = vsp->vlist[0].value.lval;
+ if (ival < 0) {
+ sts = PM_ERR_SIGN;
+ break;
+ }
+ cycletime = ival;
+ break;
+
+ case 5: /* shping.control.timeout PMID: ...0.5 */
+ ival = vsp->vlist[0].value.lval;
+ if (ival < 0) {
+ sts = PM_ERR_SIGN;
+ break;
+ }
+ timeout = ival;
+ break;
+
+ case 8: /* shping.control.debug PMID: ...0.8 */
+ ival = vsp->vlist[0].value.lval;
+ if (ival < 0) {
+ sts = PM_ERR_SIGN;
+ break;
+ }
+ pmDebug = ival;
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else {
+ /* not one of the metrics we are willing to change */
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ return sts;
+}
+
+void
+shping_init(pmdaInterface *dp)
+{
+ if (dp->status != 0)
+ return;
+
+ unlink("shping.out"); /* just in case */
+
+ dp->version.two.fetch = shping_fetch;
+ dp->version.two.store = shping_store;
+
+ pmdaInit(dp, &indomtab, 1, metrics, nummetric);
+
+ if (dp->version.two.ext->e_direct == 0) {
+ logmessage(LOG_CRIT, "Metric tables require direct mapping.\n");
+ dp->status = -1;
+ return;
+ }
+
+ numcmd = indomtab.it_numinst;
+
+ /* start the sproc for async fetches */
+#ifdef HAVE_SPROC
+ if ( (sprocpid = sproc(refresh, PR_SADDR, NULL)) < 0 ) {
+ logmessage(LOG_CRIT, "sproc failed: %s\n", osstrerror());
+ dp->status = sprocpid;
+ }
+ else {
+ dp->status = 0;
+ logmessage(LOG_INFO, "Started sproc (spid=%" FMT_PID ")\n", sprocpid);
+ }
+
+ /* we're talking to pmcd ... no timeout's for us thanks */
+ signal(SIGALRM, SIG_IGN);
+
+#elif defined (HAVE_PTHREAD_H)
+ {
+ int err = pthread_create(&sprocpid, NULL, (void (*))refresh, NULL);
+ if ( err != 0 ) {
+ logmessage (LOG_CRIT, "Cannot spawn a new thread: %s\n",
+ strerror (err));
+ dp->status = err;
+ } else {
+ dp->status = 0;
+ logmessage (LOG_INFO, "Started refresh thread\n");
+ }
+ }
+#else
+#error "Need pthreads or sproc"
+#endif
+}
diff --git a/src/pmdas/shping/shping.h b/src/pmdas/shping/shping.h
new file mode 100644
index 0000000..1c266a1
--- /dev/null
+++ b/src/pmdas/shping/shping.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+typedef struct {
+ char *tag;
+ char *cmd;
+ int status;
+ int error;
+ float real;
+ float usr;
+ float sys;
+} cmd_t;
+
+#define STATUS_NOTYET -1
+#define STATUS_OK 0
+#define STATUS_EXIT 1
+#define STATUS_SIG 2
+#define STATUS_TIMEOUT 3
+#define STATUS_SYS 4
+
+extern cmd_t *cmdlist;
+
+extern __uint32_t cycletime; /* seconds per command cycle */
+extern __uint32_t timeout; /* response timeout in seconds */
+#ifdef HAVE_SPROC
+extern pid_t sprocpid; /* for refresh() */
+#endif
+extern pmdaIndom indomtab; /* cmd tag indom */
+
+extern int pmDebug;
+extern char *pmProgname;
+
+extern void shping_init(pmdaInterface *);
+
+extern void logmessage(int, const char *, ...);
diff --git a/src/pmdas/shping/shping.response.pmie b/src/pmdas/shping/shping.response.pmie
new file mode 100644
index 0000000..f77457a
--- /dev/null
+++ b/src/pmdas/shping/shping.response.pmie
@@ -0,0 +1,67 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+
+rule shping.response
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ shping.time.real $hosts$ > $threshold$ &&
+ shping.status $hosts$ == 0
+)"
+ enabled = no
+ version = 1
+ help =
+"A monitored application or service probe from the shping PMDA
+has taken more than threshold milliseconds to complete.
+
+If this rule is enabled, the shping PMDA should be installed; see
+$PCP_PMDAS_DIR/shping/README and pmdashping(1).
+
+If some application or service is not available then the
+corresponding line should be commented out of the shping
+configuration file ($PCP_PMDAS_DIR/shping/shping.conf by default)
+and the shping PMDA restarted.";
+
+string rule
+ default = "Shell-ping PMDA slow application or service response"
+ modify = no
+ display = no;
+
+unsigned threshold
+ default = 3000
+ help =
+"Threshold time, in milliseconds, for command to run to completion.";
+
+string action_expand
+ default = %vmsec[%i]
+ display = no
+ modify = no;
+
+string email_expand
+ default = "service: %i response time: %vmsec"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200095"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = shping.response
+ display = no
+ modify = no;
+string enln_units
+ default = msec[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmdas/shping/shping.status.pmie b/src/pmdas/shping/shping.status.pmie
new file mode 100644
index 0000000..199c600
--- /dev/null
+++ b/src/pmdas/shping/shping.status.pmie
@@ -0,0 +1,63 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+
+rule shping.status
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ shping.status $hosts$ @0 > 0 && shping.status $hosts$ @1 == 0
+)"
+ enabled = no
+ version = 1
+ help =
+"An application or service being monitored by the shping PMDA was
+previously probed successfully and the probe has either failed, or
+not responded within a timeout period (as defined by the PCP metric
+shping.control.timeout) during the last sample interval.
+
+If this rule is enabled, the shping PMDA should be installed; see
+$PCP_PMDAS_DIR/shping/README and pmdashping(1).
+
+If some application or service is not available then the
+corresponding line should be commented out of the shping
+configuration file ($PCP_PMDAS_DIR/shping/shping.conf by default)
+and the shping PMDA restarted.";
+
+string rule
+ default = "Shell-ping PMDA application or service probe failure"
+ modify = no
+ display = no;
+
+string action_expand
+ default = %i
+ display = no
+ modify = no;
+
+string email_expand
+ default = "application or service: %i"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200090"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = shping.status
+ display = no
+ modify = no;
+string enln_units
+ default = exit_status
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmdas/simple/GNUmakefile b/src/pmdas/simple/GNUmakefile
new file mode 100644
index 0000000..a0c50bb
--- /dev/null
+++ b/src/pmdas/simple/GNUmakefile
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = simple.c
+CMDTARGET = pmdasimple$(EXECSUFFIX)
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = -I.
+DFILES = README help
+SCRIPTS = pmdasimple.perl pmdasimple.python
+LSRCFILES = Install Remove pmns root $(DFILES) $(SCRIPTS) \
+ simple.conf GNUmakefile.install
+
+IAM = simple
+DOMAIN = SIMPLE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so
+
+default_pcp default: domain.h $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMDADIR)/Makefile
+ $(INSTALL) -m 644 root pmns domain.h simple.conf $(PMDADIR)
+ $(INSTALL) -m 644 $(CFILES) $(DFILES) $(SCRIPTS) $(PMDADIR)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/simple/GNUmakefile.install b/src/pmdas/simple/GNUmakefile.install
new file mode 100644
index 0000000..2df8bb4
--- /dev/null
+++ b/src/pmdas/simple/GNUmakefile.install
@@ -0,0 +1,53 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = sh
+
+ifdef PCP_CONF
+include $(PCP_CONF)
+else
+PCP_DIR = $(shell echo $$PCP_DIR)
+include $(PCP_DIR)/etc/pcp.conf
+endif
+include $(PCP_INC_DIR)/builddefs
+
+# remove -Lpath and -Ipath options from builddefs CFLAGS value
+#
+PCP_LIBS =
+TMP := $(CFLAGS:-I%=)
+ifdef PCP_DIR
+# put -Ipath and -Lpath back but use paths for run-time environment
+#
+CFLAGS = $(TMP) -I$(PCP_INC_DIR)/..
+LDFLAGS = -L$(PCP_LIB_DIR)
+else
+CFLAGS = $(TMP)
+endif
+
+IAM = simple
+CFILES = $(IAM).c
+
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+CMDTARGET = pmda$(IAM)
+TARGETS = $(LIBTARGET) $(CMDTARGET)
+
+LLDLIBS = -lpcp_pmda -lpcp $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+LDIRT = *.log help.dir help.pag
+
+default: $(TARGETS)
+
+install: default
+
+include $(PCP_INC_DIR)/buildrules
diff --git a/src/pmdas/simple/Install b/src/pmdas/simple/Install
new file mode 100644
index 0000000..0d3ead3
--- /dev/null
+++ b/src/pmdas/simple/Install
@@ -0,0 +1,53 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the simple PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=simple
+pmda_interface=2
+forced_restart=false
+
+dso_opt=true
+perl_opt=true
+python_opt=true
+socket_opt=true
+socket_inet_def=2078
+
+# Set up the simple PMDA (domain 253) InDom cache
+#
+domain=`sed -n <domain.h -e '/define SIMPLE/{
+s/[ ]*$//
+s/.*[ ]//
+p
+}'`
+if [ -z "$domain" ]
+then
+ echo "Arrgh ... cannot extract domain number from domain.h"
+ exit 1
+fi
+if [ -d $PCP_VAR_DIR/config/pmda ]
+then
+ touch $PCP_VAR_DIR/config/pmda/$domain.1
+ chown $PCP_USER:$PCP_GROUP $PCP_VAR_DIR/config/pmda/$domain.1
+ chmod 644 $PCP_VAR_DIR/config/pmda/$domain.1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/simple/README b/src/pmdas/simple/README
new file mode 100644
index 0000000..d30dc63
--- /dev/null
+++ b/src/pmdas/simple/README
@@ -0,0 +1,67 @@
+Simple PMDA
+===========
+
+This PMDA is an example, that illustrates how a simple PMDA might be
+constructed.
+
+Although the metrics supported as simple, the framework is quite general,
+and could be extended to implement a much more complex PMDA.
+
+Note:
+ This PMDA may be remade from source and hence requires IDO (or
+ more specifically a C compiler) to be installed.
+
+ Uses of make(1) may fail (without removing or clobbering files)
+ if the C compiler cannot be found. This is most likely to
+ happen when running the PMDA ./Install script.
+
+ The only remedial action is to install the C compiler, or
+ hand-craft changes to the Makefile.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT simple
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/simple
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted to choose either a daemon implementation
+ or a DSO implementation of the PMDA, and in the case of the daemon
+ variant to select an IPC method -- everything else is automated
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/simple
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/simple.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/simple/Remove b/src/pmdas/simple/Remove
new file mode 100644
index 0000000..44c4d1e
--- /dev/null
+++ b/src/pmdas/simple/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the simple PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=simple
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/simple/help b/src/pmdas/simple/help
new file mode 100644
index 0000000..e31ad0e
--- /dev/null
+++ b/src/pmdas/simple/help
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# simple PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ SIMPLE.0 Instance domain "colour" for simple PMDA
+Universally 3 instances, "red" (0), "green" (1) and "blue" (3).
+
+@ SIMPLE.1 Dynamic instance domain "time" for simple PMDA
+An instance domain which is computed on-the-fly for exporting current time
+information. Refer to the help text for simple.now for a more complete
+explanation.
+
+@ simple.numfetch Number of pmFetch operations.
+The cumulative number of pmFetch operations directed to the "simple"
+PMDA.
+
+This counter may be modified with pmstore(1).
+
+@ simple.color Metrics which increment with each fetch
+This metric has 3 instances, designated "red", "green" and "blue".
+
+The value of the metric is monotonic increasing in the range 0 to
+255, then back to 0. The different instances have different starting
+values, namely 0 (red), 100 (green) and 200 (blue).
+
+The metric values my be altered using pmstore(1).
+
+@ simple.time.user Time agent has spent executing user code
+The time in seconds that the CPU has spent executing user code for the
+agent.
+
+@ simple.time.sys Time agent has spent executing system code
+The time in seconds that the CPU has spent executing system code for
+the agent.
+
+@ simple.now Time of day with a configurable instance domain
+The value reflects the current time of day through a dynamically
+reconfigurable instance domain. On each metric value fetch request,
+the agent checks to see whether the configuration file in
+$PCP_PMDAS_DIR/simple/simple.conf has been modified - if it has then
+the file is re-parsed and the instance domain for this metric is again
+constructed according to its contents.
+
+This configuration file contains a single line of comma-separated time
+tokens from this set:
+ "sec" (seconds after the minute),
+ "min" (minutes after the hour),
+ "hour" (hour since midnight).
+
+An example configuration file could be: sec,min,hour
+and in this case the simple.now metric would export values
+for the three instances "sec", "min" and "hour" corresponding
+respectively to the components seconds, minutes and hours of the
+current time of day.
+
+The instance domain reflects each token present in the file, and the
+values reflect the time at which the PMDA processes the fetch.
diff --git a/src/pmdas/simple/pmdasimple.perl b/src/pmdas/simple/pmdasimple.perl
new file mode 100644
index 0000000..f6d2da4
--- /dev/null
+++ b/src/pmdas/simple/pmdasimple.perl
@@ -0,0 +1,158 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008,2012 Aconex. All Rights Reserved.
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda $red $green $blue $user $system );
+my ( $numfetch, $oldfetch ) = ( 0, -1 );
+my ( $color_indom, $now_indom ) = ( 0, 1 );
+my ( $red, $green, $blue ) = ( 0, 100, 200 );
+
+# simple.now instance domain stuff...
+my $simple_config = pmda_config('PCP_PMDAS_DIR') . '/simple/simple.conf';
+my %timeslices;
+my $file_error = 0;
+
+sub simple_instance # called once per ``instance request'' pdu
+{
+ &simple_timenow_check;
+}
+
+sub simple_fetch # called once per ``fetch'' pdu, before callbacks
+{
+ $numfetch++;
+ &simple_timenow_check;
+}
+
+sub simple_fetch_callback # must return array of value,status
+{
+ my ($cluster, $item, $inst) = @_;
+
+ return (PM_ERR_INST, 0) unless ( $inst == PM_IN_NULL
+ || ($cluster == 0 && $item == 1)
+ || ($cluster == 2 && $item == 4) );
+ if ($cluster == 0) {
+ if ($item == 0) { return ($numfetch, 1); }
+ elsif ($item == 1) {
+ if ($inst == 0) { return ($red = ($red+1) % 255, 1); }
+ elsif ($inst == 1) { return ($green = ($green+1) % 255, 1); }
+ elsif ($inst == 2) { return ($blue = ($blue+1) % 255, 1); }
+ else { return (PM_ERR_INST, 0); }
+ } else { return (PM_ERR_PMID, 0); }
+ }
+ elsif ($cluster == 1) {
+ if ($oldfetch < $numfetch) { # get current values, if needed
+ ($user, $system, undef, undef) = times;
+ $oldfetch = $numfetch;
+ }
+ if ($item == 2) { return ($user, 1); }
+ elsif ($item == 3) { return ($system, 1); }
+ else { return (PM_ERR_PMID, 0); }
+ }
+ elsif ($cluster == 2 && $item == 4) {
+ my $value = pmda_inst_lookup($now_indom, $inst);
+ return (PM_ERR_INST, 0) unless defined($value);
+ return ($value, 1);
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+sub simple_store_callback # must return a single value (scalar context)
+{
+ my ($cluster, $item, $inst, $val) = @_;
+ my $sts = 0;
+
+ if ($cluster == 0) {
+ if ($item == 0) {
+ if ($val < 0) { $val = 0; $sts = PM_ERR_SIGN; }
+ $numfetch = $val;
+ }
+ elsif ($item == 1) {
+ if ($val < 0) { $sts = PM_ERR_SIGN; $val = 0; }
+ elsif ($val > 255) { $sts = PM_ERR_CONV; $val = 255; }
+
+ if ($inst == 0) { $red = $val; }
+ elsif ($inst == 1) { $green = $val; }
+ elsif ($inst == 2) { $blue = $val; }
+ else { $sts = PM_ERR_INST; }
+ }
+ else { $sts = PM_ERR_PMID; }
+ return $sts;
+ }
+ elsif ( ($cluster == 1 && ($item == 2 || $item == 3))
+ || ($cluster == 2 && $item == 4) ) {
+ return PM_ERR_PERMISSION;
+ }
+ return PM_ERR_PMID;
+}
+
+sub simple_timenow_check
+{
+ %timeslices = ();
+
+ if (open(CONFIG, $simple_config)) {
+ my %values;
+
+ ($values{'sec'}, $values{'min'}, $values{'hour'},
+ undef,undef,undef,undef,undef) = localtime;
+ $_ = <CONFIG>;
+ chomp; # avoid possible \n on last field
+ foreach my $spec (split(/,/)) {
+ $timeslices{$spec} = $values{$spec};
+ }
+ close CONFIG;
+ $file_error = 0;
+ }
+ else {
+ unless ($file_error == $!) {
+ $pmda->log("read failed on $simple_config: $!");
+ $file_error = $!;
+ }
+ }
+ $pmda->replace_indom( $now_indom, \%timeslices);
+}
+
+$pmda = PCP::PMDA->new('simple', 253);
+$pmda->connect_pmcd;
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.numfetch', '', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_32, $color_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.color', '', '');
+$pmda->add_metric(pmda_pmid(1,2), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'simple.time.user', '', '');
+$pmda->add_metric(pmda_pmid(1,3), PM_TYPE_DOUBLE, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'simple.time.sys', '', '');
+$pmda->add_metric(pmda_pmid(2,4), PM_TYPE_U32, $now_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'simple.now', '', '');
+
+$pmda->add_indom($color_indom, [0 => 'red', 1 => 'green', 2 => 'blue'], '', '');
+$now_indom = $pmda->add_indom($now_indom, {}, '', ''); # initialized on-the-fly
+$pmda->set_fetch( \&simple_fetch );
+$pmda->set_instance( \&simple_instance );
+$pmda->set_fetch_callback( \&simple_fetch_callback );
+$pmda->set_store_callback( \&simple_store_callback );
+
+$pmda->set_user('pcp');
+&simple_timenow_check;
+$pmda->run;
diff --git a/src/pmdas/simple/pmdasimple.python b/src/pmdas/simple/pmdasimple.python
new file mode 100644
index 0000000..5d0295a
--- /dev/null
+++ b/src/pmdas/simple/pmdasimple.python
@@ -0,0 +1,244 @@
+'''
+Python implementation of the "simple" Performance Metrics Domain Agent.
+'''
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+import os
+import time
+import ctypes
+from ctypes import c_int, POINTER, cast
+import cpmapi as c_api
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom, pmdaInstid
+from pcp.pmapi import pmUnits, pmContext as PCP
+
+class SimplePMDA(PMDA):
+ '''
+ A simple Performance Metrics Domain Agent with very simple metrics.
+ Install it and make basic use of it, as follows:
+
+ # $PCP_PMDAS_DIR/simple/Install
+ [select python option]
+ $ pminfo -fmdtT simple
+
+ Then experiment with the simple.conf file (see simple.now metrics).
+ '''
+
+ # simple.color instance domain
+ red = 0
+ green = 100
+ blue = 200
+ colors = [pmdaInstid(0, 'red'),
+ pmdaInstid(1, 'green'),
+ pmdaInstid(2, 'blue')]
+
+ # simple.now instance domain
+ configfile = ''
+ timeslices = {}
+ times = ()
+
+ # simple.numfetch properties
+ numfetch = 0
+ oldfetch = -1
+
+
+ def simple_instance(self, serial):
+ ''' Called once per "instance request" PDU '''
+ # self.log("instance update for %d" % serial)
+ if (serial == 1):
+ self.simple_timenow_check()
+
+
+ def simple_fetch(self):
+ ''' Called once per "fetch" PDU, before callbacks '''
+ self.numfetch += 1
+ self.simple_timenow_check()
+
+ def simple_fetch_color_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for color cluster
+ Helper for the fetch callback
+ '''
+ if (item == 0):
+ if (inst != c_api.PM_IN_NULL):
+ return [c_api.PM_ERR_INST, 0]
+ return [self.numfetch, 1]
+ elif (item == 1):
+ if (inst == 0):
+ self.red = (self.red + 1) % 255
+ return [self.red, 1]
+ elif (inst == 1):
+ self.green = (self.green + 1) % 255
+ return [self.green, 1]
+ elif (inst == 2):
+ self.blue = (self.blue + 1) % 255
+ return [self.blue, 1]
+ else:
+ return [c_api.PM_ERR_INST, 0]
+ else:
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_times_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for times cluster
+ Helper for the fetch callback
+ '''
+ if (inst != c_api.PM_IN_NULL):
+ return [c_api.PM_ERR_INST, 0]
+ if (self.oldfetch < self.numfetch): # get current values, if needed
+ self.times = os.times()
+ self.oldfetch = self.numfetch
+ if (item == 2):
+ return [self.times[0], 1]
+ elif (item == 3):
+ return [self.times[1], 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_now_callback(self, item, inst):
+ '''
+ Returns a list of value,status (single pair) for "now" cluster
+ Helper for the fetch callback
+ '''
+ if (item == 4):
+ voidp = self.inst_lookup(self.now_indom, inst)
+ if (voidp == None):
+ return [c_api.PM_ERR_INST, 0]
+ valuep = cast(voidp, POINTER(c_int))
+ return [valuep.contents.value, 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+ def simple_fetch_callback(self, cluster, item, inst):
+ '''
+ Main fetch callback, defers to helpers for each cluster.
+ Returns a list of value,status (single pair) for requested pmid/inst
+ '''
+ # self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
+ if (cluster == 0):
+ return self.simple_fetch_color_callback(item, inst)
+ elif (cluster == 1):
+ return self.simple_fetch_times_callback(item, inst)
+ elif (cluster == 2):
+ return self.simple_fetch_now_callback(item, inst)
+ return [c_api.PM_ERR_PMID, 0]
+
+
+ def simple_store_count_callback(self, val):
+ ''' Helper for the store callback, handles simple.numfetch '''
+ sts = 0
+ if (val < 0):
+ sts = c_api.PM_ERR_SIGN
+ val = 0
+ self.numfetch = val
+ return sts
+
+ def simple_store_color_callback(self, val, inst):
+ ''' Helper for the store callback, handles simple.color '''
+ sts = 0
+ if (val < 0):
+ sts = c_api.PM_ERR_SIGN
+ val = 0
+ elif (val > 255):
+ sts = c_api.PM_ERR_CONV
+ val = 255
+
+ if (inst == 0):
+ self.red = val
+ elif (inst == 1):
+ self.green = val
+ elif (inst == 2):
+ self.blue = val
+ else:
+ sts = c_api.PM_ERR_INST
+ return sts
+
+ def simple_store_callback(self, cluster, item, inst, val):
+ '''
+ Store callback, executed when a request to write to a metric happens
+ Defers to helpers for each storable metric. Returns a single value.
+ '''
+ if (cluster == 0):
+ if (item == 0):
+ return self.simple_store_count_callback(val)
+ elif (item == 1):
+ return self.simple_store_color_callback(val, inst)
+ else:
+ return c_api.PM_ERR_PMID
+ elif ((cluster == 1 and (item == 2 or item == 3))
+ or (cluster == 2 and item == 4)):
+ return c_api.PM_ERR_PERMISSION
+ return c_api.PM_ERR_PMID
+
+
+ def simple_timenow_check(self):
+ '''
+ Read our configuration file and update instance domain
+ '''
+ self.timeslices.clear()
+ try:
+ cfg = open(self.configfile)
+ fields = time.localtime()
+ values = {'sec': c_int(fields[5]),
+ 'min': c_int(fields[4]),
+ 'hour': c_int(fields[3])}
+ config = cfg.readline().strip()
+ for key in config.split(','):
+ if (key != '' and values[key] != None):
+ self.timeslices[key] = values[key]
+ finally:
+ cfg.close()
+ self.replace_indom(self.now_indom, self.timeslices)
+
+
+ def __init__(self, name, domain):
+ PMDA.__init__(self, name, domain)
+
+ self.configfile = PCP.pmGetConfig('PCP_PMDAS_DIR')
+ self.configfile += '/' + name + '/' + name + '.conf'
+
+ self.connect_pmcd();
+
+ self.color_indom = self.indom(0)
+ self.add_indom(pmdaIndom(self.color_indom, self.colors))
+
+ self.now_indom = self.indom(1)
+ self.add_indom(pmdaIndom(self.now_indom, self.timeslices))
+
+ self.add_metric(name + '.numfetch', pmdaMetric(self.pmid(0, 0),
+ c_api.PM_TYPE_U32, c_api.PM_INDOM_NULL, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(name + '.color', pmdaMetric(self.pmid(0, 1),
+ c_api.PM_TYPE_32, self.color_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+ self.add_metric(name + '.time.user', pmdaMetric(self.pmid(1, 2),
+ c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
+ self.add_metric(name + '.time.sys', pmdaMetric(self.pmid(1, 3),
+ c_api.PM_TYPE_DOUBLE, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 1, 0, 0, c_api.PM_TIME_SEC, 0)))
+ self.add_metric(name + '.now', pmdaMetric(self.pmid(2, 4),
+ c_api.PM_TYPE_U32, self.now_indom, c_api.PM_SEM_INSTANT,
+ pmUnits(0, 0, 0, 0, 0, 0)))
+
+ self.set_fetch(self.simple_fetch)
+ self.set_instance(self.simple_instance)
+ self.set_fetch_callback(self.simple_fetch_callback)
+ self.set_store_callback(self.simple_store_callback)
+ self.set_user(PCP.pmGetConfig('PCP_USER'))
+ self.simple_timenow_check()
+
+
+if __name__ == '__main__':
+
+ SimplePMDA('simple', 253).run()
+
diff --git a/src/pmdas/simple/pmns b/src/pmdas/simple/pmns
new file mode 100644
index 0000000..ab987f4
--- /dev/null
+++ b/src/pmdas/simple/pmns
@@ -0,0 +1,27 @@
+/*
+ * Metrics for simple PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+simple {
+ numfetch SIMPLE:0:0
+ color SIMPLE:0:1
+ time
+ now SIMPLE:2:4
+}
+
+simple.time {
+ user SIMPLE:1:2
+ sys SIMPLE:1:3
+}
diff --git a/src/pmdas/simple/root b/src/pmdas/simple/root
new file mode 100644
index 0000000..d139152
--- /dev/null
+++ b/src/pmdas/simple/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { simple }
+
+#include "pmns"
+
diff --git a/src/pmdas/simple/simple.c b/src/pmdas/simple/simple.c
new file mode 100644
index 0000000..d0eca16
--- /dev/null
+++ b/src/pmdas/simple/simple.c
@@ -0,0 +1,517 @@
+/*
+ * Simple, configurable PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+#include <sys/stat.h>
+
+/*
+ * Simple PMDA
+ *
+ * This PMDA is a sample that illustrates how a simple PMDA might be
+ * constructed using libpcp_pmda.
+ *
+ * Although the metrics supported are simple, the framework is quite general,
+ * and could be extended to implement a much more complex PMDA.
+ *
+ * Metrics
+ * simple.numfetch - number of fetches from this PMDA,
+ * may be re-set using pmStore
+ * simple.colors - 3 instances ("red", "green" and "blue")
+ * of a "saw-tooth" sequence
+ * simple.time.user - time in seconds spent executing user code
+ * simple.time.sys - time in seconds spent executing system code
+ * simple.now - current time of day across a dynamically
+ * re-configurable instance domain.
+ */
+
+/*
+ * list of instances
+ */
+
+static pmdaInstid color[] = {
+ { 0, "red" }, { 1, "green" }, { 2, "blue" }
+};
+
+/*
+ * instance domains
+ * COLOR_INDOM uses the classical indomtab[] method
+ * NOW_INDOM uses the more recent pmdaCache methods, but also appears in
+ * indomtab[] so that the initialization of the pmInDom and the pmDescs
+ * in metrictab[] is completed by pmdaInit
+ */
+
+static pmdaIndom indomtab[] = {
+#define COLOR_INDOM 0 /* serial number for "color" instance domain */
+ { COLOR_INDOM, sizeof(color)/sizeof(color[0]), color },
+#define NOW_INDOM 1 /* serial number for "now" instance domain */
+ { NOW_INDOM, 0, NULL },
+};
+
+/* this is merely a convenience */
+static pmInDom *now_indom = &indomtab[NOW_INDOM].it_indom;
+
+/*
+ * All metrics supported in this PMDA - one table entry for each.
+ * The 4th field specifies the serial number of the instance domain
+ * for the metric, and must be either PM_INDOM_NULL (denoting a
+ * metric that only ever has a single value), or the serial number
+ * of one of the instance domains declared in the instance domain table
+ * (i.e. in indomtab, above).
+ */
+
+static pmdaMetric metrictab[] = {
+/* numfetch */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* color */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_32, COLOR_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* time.user */
+ { NULL,
+ { PMDA_PMID(1,2), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) }, },
+/* time.sys */
+ { NULL,
+ { PMDA_PMID(1,3), PM_TYPE_DOUBLE, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) }, },
+/* now */
+ { NULL,
+ { PMDA_PMID(2,4), PM_TYPE_U32, NOW_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+};
+
+static int numfetch = 0; /* number of pmFetch operations */
+static int red = 0; /* current red value */
+static int green = 100; /* current green value */
+static int blue = 200; /* current blue value */
+static int isDSO = 1; /* =0 I am a daemon */
+static char *username;
+
+/* data and function prototypes for dynamic instance domain handling */
+static struct timeslice {
+ int tm_field;
+ int inst_id;
+ char *tm_name;
+} timeslices[] = {
+ { 0, 1, "sec" }, { 0, 60, "min" }, { 0, 3600, "hour" }
+};
+static int num_timeslices = sizeof(timeslices)/sizeof(timeslices[0]);
+
+#define SIMPLE_BUFSIZE 256
+static struct stat file_change; /* has time of last configuration change */
+static void simple_timenow_clear(void);
+static void simple_timenow_init(void);
+static void simple_timenow_refresh(void);
+static void simple_timenow_check(void);
+
+static char mypath[MAXPATHLEN];
+
+/* command line option handling - both short and long options */
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_TEXT("\nExactly one of the following options may appear:"),
+ PMDAOPT_INET,
+ PMDAOPT_PIPE,
+ PMDAOPT_UNIX,
+ PMDAOPT_IPV6,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:i:l:pu:U:6:?",
+ .long_options = longopts,
+};
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+simple_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ int sts;
+ static int oldfetch;
+ static double usr, sys;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (inst != PM_IN_NULL &&
+ !(idp->cluster == 0 && idp->item == 1) &&
+ !(idp->cluster == 2 && idp->item == 4))
+ return PM_ERR_INST;
+
+ if (idp->cluster == 0) {
+ if (idp->item == 0) { /* simple.numfetch */
+ atom->l = numfetch;
+ }
+ else if (idp->item == 1) { /* simple.color */
+ switch (inst) {
+ case 0: /* red */
+ red = (red + 1) % 256;
+ atom->l = red;
+ break;
+ case 1: /* green */
+ green = (green + 1) % 256;
+ atom->l = green;
+ break;
+ case 2: /* blue */
+ blue = (blue + 1) % 256;
+ atom->l = blue;
+ break;
+ default:
+ return PM_ERR_INST;
+ }
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else if (idp->cluster == 1) { /* simple.time */
+ if (oldfetch < numfetch) {
+ __pmProcessRunTimes(&usr, &sys);
+ oldfetch = numfetch;
+ }
+ if (idp->item == 2) /* simple.time.user */
+ atom->d = usr;
+ else if (idp->item == 3) /* simple.time.sys */
+ atom->d = sys;
+ else
+ return PM_ERR_PMID;
+ }
+ else if (idp->cluster == 2) {
+ if (idp->item == 4) { /* simple.now */
+ struct timeslice *tsp;
+ if ((sts = pmdaCacheLookup(*now_indom, inst, NULL, (void *)&tsp)) != PMDA_CACHE_ACTIVE) {
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "pmdaCacheLookup failed: inst=%d: %s", inst, pmErrStr(sts));
+ return PM_ERR_INST;
+ }
+ atom->l = tsp->tm_field;
+ }
+ else
+ return PM_ERR_PMID;
+ }
+ else
+ return PM_ERR_PMID;
+
+ return 0;
+}
+
+/*
+ * wrapper for pmdaFetch which increments the fetch count and checks for
+ * a change to the NOW instance domain.
+ *
+ * This routine is called once for each pmFetch(3) operation, so is a
+ * good place to do once-per-fetch functions, such as value caching or
+ * instance domain evaluation (as we do in simple_timenow_check).
+ */
+static int
+simple_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ numfetch++;
+ simple_timenow_check();
+ simple_timenow_refresh();
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * wrapper for pmdaInstance which we need to ensure is called with the
+ * _current_ contents of the NOW instance domain.
+ */
+static int
+simple_instance(pmInDom indom, int foo, char *bar, __pmInResult **iresp, pmdaExt *pmda)
+{
+ simple_timenow_check();
+ return pmdaInstance(indom, foo, bar, iresp, pmda);
+}
+
+/*
+ * Re-evaluate the NOW instance domain.
+ *
+ * Refer to the help text for simple.now for an explanation of how
+ * this indom can be modified, or just read the code ...
+ */
+static void
+simple_timenow_check(void)
+{
+ struct stat statbuf;
+ static int last_error = 0;
+ int sep = __pmPathSeparator();
+
+ /* stat the file & check modification time has changed */
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "simple.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if (stat(mypath, &statbuf) == -1) {
+ if (oserror() != last_error) {
+ last_error = oserror();
+ __pmNotifyErr(LOG_ERR, "stat failed on %s: %s\n",
+ mypath, pmErrStr(-last_error));
+ }
+ simple_timenow_clear();
+ }
+ else {
+ last_error = 0;
+#if defined(HAVE_ST_MTIME_WITH_E)
+ if (statbuf.st_mtime != file_change.st_mtime) {
+#elif defined(HAVE_ST_MTIME_WITH_SPEC)
+ if (statbuf.st_mtimespec.tv_sec != file_change.st_mtimespec.tv_sec ||
+ statbuf.st_mtimespec.tv_nsec != file_change.st_mtimespec.tv_nsec) {
+#else
+ if (statbuf.st_mtim.tv_sec != file_change.st_mtim.tv_sec ||
+ statbuf.st_mtim.tv_nsec != file_change.st_mtim.tv_nsec) {
+#endif
+ simple_timenow_clear();
+ simple_timenow_init();
+ file_change = statbuf;
+ }
+ }
+}
+
+/*
+ * get values for time.now metric instances
+ */
+static void
+simple_timenow_refresh(void)
+{
+ time_t t = time(NULL);
+ struct tm *tptr;
+
+ tptr = localtime(&t);
+ timeslices[0].tm_field = tptr->tm_sec;
+ timeslices[1].tm_field = tptr->tm_min;
+ timeslices[2].tm_field = tptr->tm_hour;
+}
+
+/*
+ * clear the time.now metric instance domain
+ */
+static void
+simple_timenow_clear(void)
+{
+ int sts;
+
+ sts = pmdaCacheOp(*now_indom, PMDA_CACHE_INACTIVE);
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "pmdaCacheOp(INACTIVE) failed: indom=%s: %s",
+ pmInDomStr(*now_indom), pmErrStr(sts));
+#ifdef DESPERATE
+ __pmdaCacheDump(stderr, *now_indom, 1);
+#endif
+}
+
+/*
+ * parse the configuration file for the time.now metric instance domain
+ */
+static void
+simple_timenow_init(void)
+{
+ int i;
+ int sts;
+ int sep = __pmPathSeparator();
+ FILE *fp;
+ char *p, *q;
+ char buf[SIMPLE_BUFSIZE];
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "simple.conf",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ if ((fp = fopen(mypath, "r")) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fopen on %s failed: %s\n",
+ mypath, pmErrStr(-oserror()));
+ return;
+ }
+ if ((p = fgets(&buf[0], SIMPLE_BUFSIZE, fp)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "fgets on %s found no data\n", mypath);
+ fclose(fp);
+ return;
+ }
+ if ((q = strchr(p, '\n')) != NULL)
+ *q = '\0'; /* remove eol character */
+
+ q = strtok(p, ","); /* and refresh using the updated file */
+ while (q != NULL) {
+ for (i = 0; i < num_timeslices; i++) {
+ if (strcmp(timeslices[i].tm_name, q) == 0) {
+ sts = pmdaCacheStore(*now_indom, PMDA_CACHE_ADD, q, &timeslices[i]);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "pmdaCacheStore failed: %s", pmErrStr(sts));
+ fclose(fp);
+ return;
+ }
+ break;
+ }
+ }
+ if (i == num_timeslices)
+ __pmNotifyErr(LOG_WARNING, "ignoring \"%s\" in %s", q, mypath);
+ q = strtok(NULL, ",");
+ }
+#ifdef DESPERATE
+ __pmdaCacheDump(stderr, *now_indom, 1);
+#endif
+ if (pmdaCacheOp(*now_indom, PMDA_CACHE_SIZE_ACTIVE) < 1)
+ __pmNotifyErr(LOG_WARNING, "\"timenow\" instance domain is empty");
+
+ fclose(fp);
+}
+
+/*
+ * support the storage of a value into the number of fetches count
+ */
+static int
+simple_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ int j;
+ int val;
+ int sts = 0;
+ pmValueSet *vsp = NULL;
+ __pmID_int *pmidp = NULL;
+
+ /* a store request may affect multiple metrics at once */
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster == 0) { /* all storable metrics are cluster 0 */
+
+ switch (pmidp->item) {
+ case 0: /* simple.numfetch */
+ val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 0;
+ }
+ numfetch = val;
+ break;
+
+ case 1: /* simple.color */
+ /* a store request may affect multiple instances at once */
+ for (j = 0; j < vsp->numval && sts == 0; j++) {
+
+ val = vsp->vlist[j].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 0;
+ }
+ if (val > 255) {
+ sts = PM_ERR_CONV;
+ val = 255;
+ }
+
+ switch (vsp->vlist[j].inst) {
+ case 0: /* red */
+ red = val;
+ break;
+ case 1: /* green */
+ green = val;
+ break;
+ case 2: /* blue */
+ blue = val;
+ break;
+ default:
+ sts = PM_ERR_INST;
+ }
+ }
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else if ((pmidp->cluster == 1 &&
+ (pmidp->item == 2 || pmidp->item == 3)) ||
+ (pmidp->cluster == 2 && pmidp->item == 4)) {
+ sts = PM_ERR_PERMISSION;
+ break;
+ }
+ else {
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ return sts;
+}
+
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+simple_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "simple DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.any.fetch = simple_fetch;
+ dp->version.any.store = simple_store;
+ dp->version.any.instance = simple_instance;
+
+ pmdaSetFetchCallBack(dp, simple_fetchCallBack);
+
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface dispatch;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "simple" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, SIMPLE,
+ "simple.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+ pmdaConnect(&dispatch);
+ simple_init(&dispatch);
+ simple_timenow_check();
+ pmdaMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/simple/simple.conf b/src/pmdas/simple/simple.conf
new file mode 100644
index 0000000..a1927dc
--- /dev/null
+++ b/src/pmdas/simple/simple.conf
@@ -0,0 +1 @@
+sec,min,hour
diff --git a/src/pmdas/snmp/GNUmakefile b/src/pmdas/snmp/GNUmakefile
new file mode 100644
index 0000000..8c50814
--- /dev/null
+++ b/src/pmdas/snmp/GNUmakefile
@@ -0,0 +1,53 @@
+#!gmake
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = snmp
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl snmp.conf
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ $(INSTALL) -m 644 snmp.conf $(PMDADIR)/snmp.conf
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/snmp/Install b/src/pmdas/snmp/Install
new file mode 100755
index 0000000..999f0a6
--- /dev/null
+++ b/src/pmdas/snmp/Install
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the SNMP gateway PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=snmp
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/snmp/README b/src/pmdas/snmp/README
new file mode 100644
index 0000000..15fe1d3
--- /dev/null
+++ b/src/pmdas/snmp/README
@@ -0,0 +1,52 @@
+The PCP SNMP gateway PMDA is a plugin that allows PCP clients to query
+for data from SNMP agents.
+
+Depends on perl modules:
+PCP::PMDA libpcp-pmda-perl
+Net::SNMP libnet-snmp-perl
+
+The PCP names that are exported are (currently)
+
+ snmp.host.$hostname.$oid
+ snmp.host.$hostname.$oid[$rownr]
+
+In the future I hope to propose an extension to the PCP agent protocol
+to allow the use of virtual hostnames, which would change the names
+exported.
+
+I also hope in the future to load in full MIB names, which would also
+change the exported names to make them more readable.
+
+Also as a future target, as OIDs are queried for by the PCP client,
+this gateway will dynamically create mappings between the PCP internal
+ID and the SNMP OID. This creates limitations on the total number of
+mappings available.
+
+SNMP uses an open ended hierachical namespace with a potentially unlimited
+number of values. MIB files on the SNMP client are used to map between
+dotted names and dotted numbers. These MIB files also provide type
+information and help text for each OID
+
+PCP uses a 64bit value to uniquely identify each available value. This is
+divided into:
+ 9 bits - "domain" number (./src/pmns/Make.stdpmid gives 56-58 to SNMP)
+ 12 bits - "cluster" number
+ 10 bits - "item" number (cluster+item identify the metric)
+ 32 bits - "instance" of the metric
+PCP uses an open ended dotted text name to translate to this fixed 64bit value.
+The PCP agent sends the text to number mapping to the PCP client as needed.
+
+This gateway is configured with a config file:
+* define each SNMP hostname and credentials
+* define a list of MIB namespaces to load (future)
+* define a list of static OID -> PCP mappings (including room for tables..)
+* a static mapping of a tree (and the max static size of that tree?) (future)
+* a static "table" mapping for special use of the "instance" value
+* define a "high water mark" for static mappings (future)
+
+If a query comes in for a static mapping, then the PCP value from the
+config is used every time. If a query for a new mapping is received
+then the gateway dynamically allocates the next free ID (starting from
+MAXINT working downwards). This dynamic mapping might change on gateway
+restarts or on idle timeouts.
+
diff --git a/src/pmdas/snmp/Remove b/src/pmdas/snmp/Remove
new file mode 100755
index 0000000..7a412d9
--- /dev/null
+++ b/src/pmdas/snmp/Remove
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Remove the SNMP PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=snmp
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/snmp/pmdasnmp.pl b/src/pmdas/snmp/pmdasnmp.pl
new file mode 100755
index 0000000..058659d
--- /dev/null
+++ b/src/pmdas/snmp/pmdasnmp.pl
@@ -0,0 +1,413 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2011-2012 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use FileHandle;
+use PCP::PMDA;
+use Net::SNMP qw(:asn1);
+
+use Data::Dumper;
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Sortkeys = 1;
+$Data::Dumper::Quotekeys = 0;
+$Data::Dumper::Useqq = 1; # PMDA log doesnt like binary :-(
+
+our $VERSION='0.3';
+my $db = {};
+my $option = {
+ max_row => 100, # default maximum number of rows for a table
+ pmid_per_host => 100, # default number of pmid's for each host
+};
+
+# SNMP string type name to numeric type number
+#
+my $snmptype2val = {
+ INTEGER => INTEGER32,
+ INTEGER32 => INTEGER32,
+ OCTET_STRING => OCTET_STRING,
+ STRING => OCTET_STRING,
+ OBJECT_IDENTIFIER => OBJECT_IDENTIFIER,
+ IPADDRESS => IPADDRESS,
+ COUNTER => COUNTER32,
+ COUNTER32 => COUNTER32,
+ GAUGE => GAUGE32,
+ GAUGE32 => GAUGE32,
+ UNSIGNED32 => UNSIGNED32,
+ TIMETICKS => TIMETICKS,
+ OPAQUE => OPAQUE,
+ COUNTER64 => COUNTER64,
+};
+
+# SNMP numeric type number to PCP type number
+#
+my $snmptype2pcp = {
+ 0x02 => { type=> PM_TYPE_32, sem=> PM_SEM_INSTANT }, # INTEGER32
+ 0x04 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OCTET_STRING
+ 0x06 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OBJECT_IDENTIFIER
+ 0x40 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # IPADDRESS
+ 0x41 => { type=> PM_TYPE_U32, sem=> PM_SEM_COUNTER }, # COUNTER32
+ 0x42 => { type=> PM_TYPE_32, sem=> PM_SEM_INSTANT }, # GAUGE32
+ 0x42 => { type=> PM_TYPE_U32, sem=> PM_SEM_INSTANT }, # UNSIGNED32
+ 0x43 => { type=> PM_TYPE_64, sem=> PM_SEM_COUNTER }, # TIMETICKS
+ 0x44 => { type=> PM_TYPE_STRING, sem=> PM_SEM_DISCRETE }, # OPAQUE
+ 0x46 => { type=> PM_TYPE_64, sem=> PM_SEM_COUNTER }, # COUNTER64
+};
+
+my $dom_rows = 0; # this indom nr used for generic row numbers
+
+my $pmda = PCP::PMDA->new('snmp', 56);
+
+# Read in the config file(s)
+#
+sub load_config {
+ my $db = shift;
+
+ if (!defined $db->{hosts}) {
+ $db->{hosts} = {};
+ }
+ if (!defined $db->{map}) {
+ $db->{map} = {};
+ $db->{map}{hosts} = [];
+ $db->{map}{oids} = [];
+ }
+
+ for my $filename (@_) {
+ my $fh = FileHandle->new($filename);
+ if (!defined $fh) {
+ warn "opening $filename $!";
+ next;
+ }
+
+ while(<$fh>) {
+ chomp; s/\r//g;
+
+ # strip whitespace at the beginning and end of the line
+ s/^\s+//;
+ s/\s+$//;
+
+ # strip comments
+ s/^#.*//; # line starts with a comment char
+ s/[^\\]#.*//; # non quoted comment char
+
+ if (m/^$/) {
+ # empty lines, or lines that were all comment
+ next;
+ }
+
+ if (m/^set\s+(\w+)\s+(.*)$/) {
+ # set an option
+ my $key = $1;
+ my $val = $2;
+
+ $option->{$key}=$val;
+ } elsif (m/^host\s+(\S+)\s+(.*)$/) {
+ my $e = {};
+ $e->{hostname}=$1;
+ $e->{community}=$2;
+
+ # The reversed dotted hostname is used in the metric name
+ $e->{revname} = join('.',reverse(split('\.',$1)));
+
+ # TODO - lazy create snmp sessions on first use
+ my ($session,$error) = Net::SNMP->session(
+ -hostname =>$e->{hostname},
+ -community=>$e->{community},
+ );
+ if (!$session) {
+ warn("SNMP session to $e->{hostname}: $error");
+ $e->{error}=$error;
+ } else {
+ $e->{snmp}=$session;
+ $e->{snmp}->translate([-timeticks=>0]);
+ }
+ $db->{hosts}{$1} = $e;
+ my $id = scalar @{$db->{map}{hosts}};
+ # TODO - allow this pmid 'index base' to be set
+ $e->{id} = $id;
+ @{$db->{map}{hosts}}[$id]=$e;
+ } elsif (m/^map\s+(single|column)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
+ my $snmptype = $snmptype2val->{$3};
+ if (!defined $snmptype) {
+ warn("Invalid SNMP type '$3' on oid '$2'\n");
+ next;
+ }
+ my $e = {};
+ my $id = $4;
+ if ($id eq '+') {
+ # select the next available number
+ $id = scalar @{$db->{map}{oids}};
+ }
+ if ($id > $option->{pmid_per_host}) {
+ warn("More metrics than allowed by pmid_per_host");
+ next;
+ }
+ $e->{type}=$1;
+ $e->{oid}=$2;
+ $e->{snmptype}=$snmptype;
+ $e->{id}=$id;
+ $e->{text}=$5;
+ @{$db->{map}{oids}}[$id]=$e;
+ } else {
+ warn("Unrecognised config line: $_\n");
+ }
+ # TODO - add map tree, mib load
+ }
+ }
+
+ $db->{max}{hosts} = scalar keys %{$db->{hosts}};
+ $db->{max}{oids} = scalar @{$db->{map}{oids}};
+ $db->{max}{static} = $db->{max}{hosts} * $option->{pmid_per_host};
+ # any PMID above max static is available for dynamicly created mappings
+
+ return $db;
+}
+
+# Create the fake generic rows indom
+# TODO - demand create the rows indoms
+sub db_create_indom {
+ my ($db) = @_;
+
+ my @dom;
+ for my $row (0..$option->{max_row}) {
+ # first is id, second is string description
+ # for now, both are the same
+ # TODO - populate the indom with rational names from an SNMP column
+ push @dom,$row,$row;
+ }
+ $pmda->add_indom($dom_rows,\@dom,'SNMP rows','');
+}
+
+# Using the mappings, define all the metrics
+#
+sub db_add_metrics {
+ my ($db) = @_;
+
+ # TODO - nuke the PMDA.xs current list of metrics here
+ # (there is a clear_metrics() in the xs that might be adapted to work)
+
+ # add our version
+ $pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING,
+ PM_INDOM_NULL, PM_SEM_DISCRETE,
+ pmda_units(0,0,0,0,0,0), "snmp.version", '', '');
+
+ for my $host (@{$db->{map}{hosts}}) {
+ # calculate the pmid for the first metric for this host
+ my $hostbase = $host->{id} * $option->{pmid_per_host};
+
+ # skip hosts that did not setup their snmp session
+ next if (!$host->{snmp});
+
+ for my $e (@{$db->{map}{oids}}) {
+ # for each predefined static mapping, register a metric
+
+ if (!defined $e) {
+ next;
+ }
+ my $id = $hostbase + $e->{id};
+
+ # hack around the too transparent opaque datatype
+ my $cluster = int($id /1024);
+ my $item = $id %1024;
+
+ my $type = $snmptype2pcp->{$e->{snmptype}};
+ if (!defined $type) {
+ warn("Unknown type=$type for id=$e->{id}\n");
+ next;
+ }
+
+ my $indom;
+ if ($e->{type} eq 'single') {
+ $indom = PM_INDOM_NULL;
+ } elsif ($e->{type} eq 'column') {
+ # TODO - use metric specific indom, for now, just use generic
+ $indom = $dom_rows;
+ $e->{indom} = $indom;
+ } else {
+ warn("Unknown map type = $e->{type}\n");
+ next;
+ }
+ $pmda->add_metric(pmda_pmid($cluster,$item),
+ $type->{type},
+ $indom, $type->{sem},
+ pmda_units(0,0,0,0,0,0),
+ 'snmp.host.'.$host->{revname}.'.'.$e->{oid}, $e->{text}, ''
+ );
+ }
+ }
+}
+
+# debug when fetch is called
+# fetch_func is called with no params during a "fetch", after refreshing the
+# PMNS before calling refresh_func
+#
+sub fetch {
+ if ($option->{debug}) {
+ $pmda->log("fetch");
+ }
+}
+
+# debug when instance is called
+# instance_func is called with "indom" param during a "instance", after
+# refreshing the PMNS before calling pmdaInstance
+#
+sub instance {
+ my ($indom) = @_;
+ if ($option->{debug}) {
+ $pmda->log("instance $indom");
+ }
+}
+
+sub fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+ my $id = $cluster*1024 + $item;
+
+ if ($option->{debug}) {
+ my $metric_name = pmda_pmid_name($cluster, $item);
+ $pmda->log("fetch_callback $metric_name $cluster:$item ($inst)");
+ }
+
+ if ($id == 0) {
+ return ($VERSION,1);
+ }
+
+ my $hostnr = int($id / $option->{pmid_per_host});
+ my $host = @{$db->{map}{hosts}}[$hostnr];
+ if (!defined $host) {
+ return (PM_ERR_NOTHOST, 0);
+ }
+
+ my $map = @{$db->{map}{oids}}[$id % $option->{pmid_per_host}];
+ if (!defined $map) {
+ return (PM_ERR_PMID, 0);
+ }
+ my $oid = $map->{oid};
+
+ if (defined $map->{indom}) {
+ # only metrics with rows have an indom
+ $oid.='.'.$inst;
+ }
+
+ # TODO - maybe check if a map single has been called with an inst other
+ # than PM_INDOM_NULL
+
+ if ($option->{debug}) {
+ $pmda->log("fetch_callback hostnr=$hostnr rownr=$inst");
+ }
+
+ if (!defined $host->{snmp}) {
+ # We have no snmp object for this host
+ # FIXME - a better errno?
+ return (PM_ERR_EOF, 0);
+ }
+ my $snmp = $host->{snmp};
+
+ my $result = $snmp->get_request(
+ -varbindlist=>[
+ $oid,
+ ]
+ );
+
+ if (!$result) {
+ # We didnt get a valid snmp response
+ return (PM_ERR_PMID, 0);
+ }
+
+ my $types = $snmp->var_bind_types();
+ if ($map->{snmptype} != $types->{$oid}) {
+ return (PM_ERR_CONV, 0);
+ }
+ return ($result->{$oid},1);
+}
+
+load_config($db,
+ pmda_config('PCP_PMDAS_DIR').'/snmp/snmp.conf',
+# 'snmp.conf'
+);
+
+db_create_indom($db);
+
+db_add_metrics($db);
+
+$pmda->set_fetch(\&fetch);
+$pmda->set_instance(\&instance);
+$pmda->set_fetch_callback(\&fetch_callback);
+
+if ($option->{debug}) {
+ $pmda->log("db=".Dumper($db)."\n");
+ $pmda->log("option=".Dumper($option)."\n");
+}
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdasnmp - Gateway from SNMP to PCP (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdasnmp> is a Performance Metrics Domain Agent (PMDA) which
+provides a generic gateway from PCP queries from a PCP client to SNMP queries
+to one or more SNMP agents.
+
+=head1 INSTALLATION
+
+If you want access to the SNMP gateway performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/snmp
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/snmp
+ # ./Remove
+
+B<pmdasnmp> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 CONFIGURATION
+
+TODO: define config file format here - map/set/host/... etc
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/snmp/snmp.conf
+
+optional configuration file for B<pmdasnmp>
+
+=item $PCP_PMDAS_DIR/snmp/Install
+
+installation script for the B<pmdasnmp> agent
+
+=item $PCP_PMDAS_DIR/snmp/Remove
+
+undo installation script for the B<pmdasnmp> agent
+
+=item $PCP_LOG_DIR/pmcd/snmp.log
+
+default log file for error and warn() messages from B<pmdasnmp>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and SNMP
diff --git a/src/pmdas/snmp/snmp.conf b/src/pmdas/snmp/snmp.conf
new file mode 100644
index 0000000..ed7b88b
--- /dev/null
+++ b/src/pmdas/snmp/snmp.conf
@@ -0,0 +1,44 @@
+# Simple, first pass at a config file. I expect to need a more expressive
+# file format in the future
+
+# hashes begin comments
+# comments can begin in the # middle of a line
+
+# define a host
+#host $hostname $community
+# TODO - allow the pmid 'index base' to be set for each host
+# currently only community based auth is supported
+host localhost public
+
+# load a mib (future) - gives name to number oid mappings
+#mib $mibfilename
+
+# static mapping
+# single maps a single oid
+# column maps a simple table column where the last octet in the oid is the row
+# id's start at 1 and redefininitions result in the last define winning
+# the snmptype is used to calculate the pcp metric type to use
+# the text is used as the metric short help text
+#map (single|column) $oid $snmptype $id $text
+
+map single 1.3.6.1.2.1.1.3.0 TIMETICKS 1 sysUpTime
+map single 1.3.6.1.2.1.1.5.0 STRING 2 sysName
+
+map column 1.3.6.1.2.1.2.2.1.2 STRING 10 ifDescr
+map column 1.3.6.1.2.1.2.2.1.5 GAUGE32 + ifSpeed
+map column 1.3.6.1.2.1.2.2.1.10 COUNTER32 + ifInOctets
+
+# TODO - work out some kind of static walk define
+#map tree $oid $id_start $id_max $text_prefix
+
+# For the moment, table oids are limited to this max number
+set max_row 50
+
+# Set the maximum number of metrics per host. This is used to create the
+# metric ID and thus if changed will affect merging data with older archives.
+set pmid_per_host 1000
+
+# set an option
+#set key val
+set debug 1
+
diff --git a/src/pmdas/solaris/GNUmakefile b/src/pmdas/solaris/GNUmakefile
new file mode 100644
index 0000000..e5f858c
--- /dev/null
+++ b/src/pmdas/solaris/GNUmakefile
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = solaris
+DOMAIN = SOLARIS
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+PMDAINIT = solaris_init
+CMDTARGET = pmdasolaris
+LIBTARGET = pmda_solaris.so
+CONF_LINE = "solaris 75 dso $(PMDAINIT) $(PMDADIR)/$(LIBTARGET)"
+
+CFILES = solaris.c data.c sysinfo.c disk.c zpool.c zfs.c \
+ zpool_perdisk.c netmib2.c netlink.c kvm.c arcstats.c vnops.c
+
+BARE_NS = disk kernel mem network hinv zpool zfs zpool_perdisk
+PMNS = $(BARE_NS:%=pmns.%)
+
+LSRCFILES = $(PMNS) help root common.h clusters.h netmib2.h
+HELPTARGETS = help.dir help.pag
+VERSION_SCRIPT = exports
+
+LDIRT = domain.h *.log $(HELPTARGETS) root_solaris
+
+LLDLIBS = $(PCP_PMDALIB) -lkstat -lzfs -lnvpair -lkvm -ldevinfo
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "solaris"
+build-me: root_solaris domain.h $(LIBTARGET) $(CMDTARGET) $(HELPTARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(HELPTARGETS) $(PMDADIR)
+ $(INSTALL) -m 755 $(LIBTARGET) $(CMDTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 root_solaris $(PCP_VAR_DIR)/pmns/root_solaris
+else
+build-me:
+install:
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+$(OBJECTS): common.h
+
+$(HELPTARGETS): help root_solaris
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_solaris -v 2 -o help < help
+
+root_solaris: ../../pmns/stdpmid $(PMNS) root
+ rm -f root_solaris
+ sed -e 's;<stdpmid>;"../../pmns/stdpmid";' <root \
+ | $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp \
+ | sed -e '/^#/d' -e '/^$$/d' >root_solaris
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(VERSION_SCRIPT):
+ $(VERSION_SCRIPT_MAKERULE)
+
+$(LIBTARGET): $(VERSION_SCRIPT)
diff --git a/src/pmdas/solaris/arcstats.c b/src/pmdas/solaris/arcstats.c
new file mode 100644
index 0000000..75cd17c
--- /dev/null
+++ b/src/pmdas/solaris/arcstats.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Extract information about ZFS' Adjustable Replacement Cache
+ *
+ * The stats are in the sections called "arc_stats" of module zfs
+ */
+
+#include <kstat.h>
+#include "common.h"
+
+static kstat_t *arcstats;
+int arcstats_fresh;
+
+void
+arcstats_refresh(void)
+{
+ kstat_ctl_t *kc;
+ arcstats_fresh = 0;
+ if ((kc = kstat_ctl_update()) == NULL)
+ return;
+ if ((arcstats = kstat_lookup(kc, "zfs", -1, "arcstats")) != NULL)
+ arcstats_fresh = kstat_read(kc, arcstats, NULL) != -1;
+}
+
+int
+arcstats_fetch(pmdaMetric *pm, int inst, pmAtomValue *av)
+{
+ metricdesc_t *md = pm->m_user;
+ char *metric = (char *)md->md_offset;
+ kstat_named_t *kn;
+
+ if (!arcstats_fresh)
+ return 0;
+
+ if ((kn = kstat_data_lookup(arcstats, metric)) != NULL)
+ return kstat_named_to_pmAtom(kn, av);
+
+ return 0;
+}
diff --git a/src/pmdas/solaris/clusters.h b/src/pmdas/solaris/clusters.h
new file mode 100644
index 0000000..9e44ccb
--- /dev/null
+++ b/src/pmdas/solaris/clusters.h
@@ -0,0 +1,20 @@
+#ifndef __PMDA_SOLARIS_CLUSTERS_H
+#define __PMDA_SOLARIS_CLUSTERS_H
+
+/*
+ * PMID cluster numbers
+ *
+ * Clusters are used to index method[] table and shall be contigious
+ */
+#define SCLR_SYSINFO 0
+#define SCLR_DISK 1
+#define SCLR_NETIF 2
+#define SCLR_ZPOOL 3
+#define SCLR_ZFS 4
+#define SCLR_ZPOOL_PERDISK 5
+#define SCLR_NETLINK 6
+#define SCLR_FSFLUSH 7
+#define SCLR_ARCSTATS 8
+#define SCLR_FILESYS 9
+
+#endif
diff --git a/src/pmdas/solaris/common.h b/src/pmdas/solaris/common.h
new file mode 100644
index 0000000..469725e
--- /dev/null
+++ b/src/pmdas/solaris/common.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __PMDASOLARIS_COMMON_H
+#define __PMDASOLARIS_COMMON_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "clusters.h"
+
+#include <kstat.h>
+#include <sys/sysinfo.h>
+
+typedef struct {
+ const char *m_name;
+ void (*m_init)(int);
+ void (*m_prefetch)(void);
+ int (*m_fetch)(pmdaMetric *, int, pmAtomValue *);
+ int m_fetched;
+ uint64_t m_elapsed;
+ uint64_t m_hits;
+} method_t;
+
+extern method_t methodtab[];
+extern const int methodtab_sz;
+
+extern void init_data(int);
+
+extern void sysinfo_init(int);
+extern void sysinfo_prefetch(void);
+extern int sysinfo_fetch(pmdaMetric *, int, pmAtomValue *);
+
+extern void disk_init(int);
+extern void disk_prefetch(void);
+extern int disk_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void zpool_init(int);
+void zpool_refresh(void);
+int zpool_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void zfs_init(int);
+void zfs_refresh(void);
+int zfs_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void zpool_perdisk_init(int);
+void zpool_perdisk_refresh(void);
+int zpool_perdisk_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void netlink_init(int);
+void netlink_refresh(void);
+int netlink_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void kvm_init(int);
+void kvm_refresh(void);
+int kvm_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void arcstats_refresh(void);
+int arcstats_fetch(pmdaMetric *, int, pmAtomValue *);
+
+void vnops_init(int);
+void vnops_refresh(void);
+int vnops_fetch(pmdaMetric *, int, pmAtomValue *);
+
+/*
+ * metric descriptions
+ */
+typedef struct {
+ const char *md_name;
+ pmDesc md_desc; // PMDA's idea of the semantics
+ ptrdiff_t md_offset; // offset into kstat stats structure
+ uint64_t md_elapsed;
+ uint64_t md_hits;
+} metricdesc_t;
+
+extern metricdesc_t metricdesc[];
+extern pmdaMetric *metrictab;
+extern int metrictab_sz;
+
+#define DISK_INDOM 0
+#define CPU_INDOM 1
+#define NETIF_INDOM 2
+#define ZPOOL_INDOM 3
+#define ZFS_INDOM 4
+#define ZPOOL_PERDISK_INDOM 5
+#define NETLINK_INDOM 6
+#define ZFS_SNAP_INDOM 7
+#define LOADAVG_INDOM 8
+#define PREFETCH_INDOM 9
+#define METRIC_INDOM 10
+#define FILESYS_INDOM 11
+#define FSTYPE_INDOM 12
+
+extern pmdaIndom indomtab[];
+extern int indomtab_sz;
+
+/*
+ * kstat() control
+ */
+kstat_ctl_t *kstat_ctl_update(void);
+void kstat_ctl_needs_update(void);
+int kstat_named_to_pmAtom(const kstat_named_t *, pmAtomValue *);
+int kstat_named_to_typed_atom(const kstat_named_t *, int, pmAtomValue *);
+
+/* Snarfed from usr/src/uts/common/fs/fsflush.c in OpenSolaris source tree */
+typedef struct {
+ ulong_t fsf_scan; /* number of pages scanned */
+ ulong_t fsf_examined; /* number of page_t's actually examined, can */
+ /* be less than fsf_scan due to large pages */
+ ulong_t fsf_locked; /* pages we actually page_lock()ed */
+ ulong_t fsf_modified; /* number of modified pages found */
+ ulong_t fsf_coalesce; /* number of page coalesces done */
+ ulong_t fsf_time; /* nanoseconds of run time */
+ ulong_t fsf_releases; /* number of page_release() done */
+} fsf_stat_t;
+
+#endif
diff --git a/src/pmdas/solaris/data.c b/src/pmdas/solaris/data.c
new file mode 100644
index 0000000..6d09a0e
--- /dev/null
+++ b/src/pmdas/solaris/data.c
@@ -0,0 +1,1462 @@
+/*
+ * Data structures that define metrics and control the Solaris PMDA
+ *
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2010 Max Matveev. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+#include "netmib2.h"
+#include <ctype.h>
+#include <libzfs.h>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#endif
+
+method_t methodtab[] = {
+ { "sysinfo", sysinfo_init, sysinfo_prefetch, sysinfo_fetch },
+ { "disk", disk_init, disk_prefetch, disk_fetch },
+ { "netmib2", netmib2_init, netmib2_refresh, netmib2_fetch },
+ { "zpool", zpool_init, zpool_refresh, zpool_fetch },
+ { "zfs", zfs_init, zfs_refresh, zfs_fetch },
+ { "zpool_vdev", zpool_perdisk_init, zpool_perdisk_refresh, zpool_perdisk_fetch },
+ { "netlink", netlink_init, netlink_refresh, netlink_fetch },
+ { "kvm", kvm_init, kvm_refresh, kvm_fetch },
+ { "zfs_arc", NULL, arcstats_refresh, arcstats_fetch },
+ { "filesystem", vnops_init, vnops_refresh, vnops_fetch }
+};
+
+const int methodtab_sz = ARRAY_SIZE(methodtab);
+static pmdaInstid prefetch_insts[ARRAY_SIZE(methodtab)];
+
+static pmdaInstid loadavg_insts[] = {
+ {1, "1 minute"},
+ {5, "5 minute"},
+ {15, "15 minute"}
+};
+
+pmdaMetric *metrictab;
+
+#define SYSINFO_OFF(field) ((ptrdiff_t)&((cpu_stat_t *)0)->cpu_sysinfo.field)
+#define KSTAT_IO_OFF(field) ((ptrdiff_t)&((kstat_io_t *)0)->field)
+#define VDEV_OFFSET(field) ((ptrdiff_t)&((vdev_stat_t *)0)->field)
+#define NM2_UDP_OFFSET(field) ((ptrdiff_t)&(nm2_udp.field))
+#define NM2_NETIF_OFFSET(field) ((ptrdiff_t)&((nm2_netif_stats_t *)0)->field)
+#define FSF_STAT_OFFSET(field) ((ptrdiff_t)&((fsf_stat_t *)0)->field)
+
+/*
+ * all metrics supported in this PMDA - one table entry for each metric
+ */
+metricdesc_t metricdesc[] = {
+
+ { "kernel.all.cpu.idle",
+ { PMDA_PMID(SCLR_SYSINFO,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_IDLE]) },
+
+ { "kernel.all.cpu.user",
+ { PMDA_PMID(SCLR_SYSINFO,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_USER]) },
+
+ { "kernel.all.cpu.sys",
+ { PMDA_PMID(SCLR_SYSINFO,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_KERNEL]) },
+
+ { "kernel.all.cpu.wait.total",
+ { PMDA_PMID(SCLR_SYSINFO,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_WAIT]) },
+
+ { "kernel.percpu.cpu.idle",
+ { PMDA_PMID(SCLR_SYSINFO,4), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_IDLE]) },
+
+ { "kernel.percpu.cpu.user",
+ { PMDA_PMID(SCLR_SYSINFO,5), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_USER]) },
+
+ { "kernel.percpu.cpu.sys",
+ { PMDA_PMID(SCLR_SYSINFO,6), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_KERNEL]) },
+
+ { "kernel.percpu.cpu.wait.total",
+ { PMDA_PMID(SCLR_SYSINFO,7), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(cpu[CPU_WAIT]) },
+
+ { "kernel.all.cpu.wait.io",
+ { PMDA_PMID(SCLR_SYSINFO,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_IO]) },
+
+ { "kernel.all.cpu.wait.pio",
+ { PMDA_PMID(SCLR_SYSINFO,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_PIO]) },
+
+ { "kernel.all.cpu.wait.swap",
+ { PMDA_PMID(SCLR_SYSINFO,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_SWAP]) },
+
+ { "kernel.percpu.cpu.wait.io",
+ { PMDA_PMID(SCLR_SYSINFO,11), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_IO]) },
+
+ { "kernel.percpu.cpu.wait.pio",
+ { PMDA_PMID(SCLR_SYSINFO,12), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_PIO]) },
+
+ { "kernel.percpu.cpu.wait.swap",
+ { PMDA_PMID(SCLR_SYSINFO,13), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, SYSINFO_OFF(wait[W_SWAP]) },
+
+ { "kernel.all.io.bread",
+ { PMDA_PMID(SCLR_SYSINFO,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(bread) },
+
+ { "kernel.all.io.bwrite",
+ { PMDA_PMID(SCLR_SYSINFO,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(bwrite) },
+
+ { "kernel.all.io.lread",
+ { PMDA_PMID(SCLR_SYSINFO,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(lread) },
+
+ { "kernel.all.io.lwrite",
+ { PMDA_PMID(SCLR_SYSINFO,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(lwrite) },
+
+ { "kernel.percpu.io.bread",
+ { PMDA_PMID(SCLR_SYSINFO,18), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(bread) },
+
+ { "kernel.percpu.io.bwrite",
+ { PMDA_PMID(SCLR_SYSINFO,19), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(bwrite) },
+
+ { "kernel.percpu.io.lread",
+ { PMDA_PMID(SCLR_SYSINFO,20), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(lread) },
+
+ { "kernel.percpu.io.lwrite",
+ { PMDA_PMID(SCLR_SYSINFO,21), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(lwrite) },
+
+ { "kernel.all.syscall",
+ { PMDA_PMID(SCLR_SYSINFO,22), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(syscall) },
+
+ { "kernel.all.pswitch",
+ { PMDA_PMID(SCLR_SYSINFO,23), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(pswitch) },
+
+ { "kernel.percpu.syscall",
+ { PMDA_PMID(SCLR_SYSINFO,24), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(syscall) },
+
+ { "kernel.percpu.pswitch",
+ { PMDA_PMID(SCLR_SYSINFO,25), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(pswitch) },
+
+ { "kernel.all.io.phread",
+ { PMDA_PMID(SCLR_SYSINFO,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(phread) },
+
+ { "kernel.all.io.phwrite",
+ { PMDA_PMID(SCLR_SYSINFO,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(phwrite) },
+
+ { "kernel.all.io.intr",
+ { PMDA_PMID(SCLR_SYSINFO,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(intr) },
+
+ { "kernel.percpu.io.phread",
+ { PMDA_PMID(SCLR_SYSINFO,29), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(phread) },
+
+ { "kernel.percpu.io.phwrite",
+ { PMDA_PMID(SCLR_SYSINFO,30), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(phwrite) },
+
+ { "kernel.percpu.io.intr",
+ { PMDA_PMID(SCLR_SYSINFO,31), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(intr) },
+
+ { "kernel.all.trap",
+ { PMDA_PMID(SCLR_SYSINFO,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(trap) },
+
+ { "kernel.all.sysexec",
+ { PMDA_PMID(SCLR_SYSINFO,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysexec) },
+
+ { "kernel.all.sysfork",
+ { PMDA_PMID(SCLR_SYSINFO,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysfork) },
+
+ { "kernel.all.sysvfork",
+ { PMDA_PMID(SCLR_SYSINFO,35), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysvfork) },
+
+ { "kernel.all.sysread",
+ { PMDA_PMID(SCLR_SYSINFO,36), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysread) },
+
+ { "kernel.all.syswrite",
+ { PMDA_PMID(SCLR_SYSINFO,37), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(syswrite) },
+
+ { "kernel.percpu.trap",
+ { PMDA_PMID(SCLR_SYSINFO,38), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(trap) },
+
+ { "kernel.percpu.sysexec",
+ { PMDA_PMID(SCLR_SYSINFO,39), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysexec) },
+
+ { "kernel.percpu.sysfork",
+ { PMDA_PMID(SCLR_SYSINFO,40), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysfork) },
+
+ { "kernel.percpu.sysvfork",
+ { PMDA_PMID(SCLR_SYSINFO,41), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysvfork) },
+
+ { "kernel.percpu.sysread",
+ { PMDA_PMID(SCLR_SYSINFO,42), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(sysread) },
+
+ { "kernel.percpu.syswrite",
+ { PMDA_PMID(SCLR_SYSINFO,43), PM_TYPE_U32, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, SYSINFO_OFF(syswrite) },
+
+ { "disk.all.read",
+ { PMDA_PMID(SCLR_DISK,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(reads) },
+
+ { "disk.all.write",
+ { PMDA_PMID(SCLR_DISK,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(writes) },
+
+ { "disk.all.total",
+ { PMDA_PMID(SCLR_DISK,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, -1},
+
+ { "disk.all.read_bytes",
+ { PMDA_PMID(SCLR_DISK,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, KSTAT_IO_OFF(nread) },
+
+ { "disk.all.write_bytes",
+ { PMDA_PMID(SCLR_DISK,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, KSTAT_IO_OFF(nwritten) },
+
+ { "disk.all.total_bytes",
+ { PMDA_PMID(SCLR_DISK,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, -1},
+
+ { "disk.dev.read",
+ { PMDA_PMID(SCLR_DISK,10), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(reads) },
+
+
+ { "disk.dev.write",
+ { PMDA_PMID(SCLR_DISK,11), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(writes) },
+
+ { "disk.dev.total",
+ { PMDA_PMID(SCLR_DISK,12), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, -1},
+
+ { "disk.dev.read_bytes",
+ { PMDA_PMID(SCLR_DISK,13), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, KSTAT_IO_OFF(nread) },
+
+ { "disk.dev.write_bytes",
+ { PMDA_PMID(SCLR_DISK,14), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, KSTAT_IO_OFF(nwritten) },
+
+ { "disk.dev.total_bytes",
+ { PMDA_PMID(SCLR_DISK,15), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, -1},
+
+ { "hinv.ncpu",
+ { PMDA_PMID(SCLR_SYSINFO,56), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1},
+
+ { "hinv.ndisk",
+ { PMDA_PMID(SCLR_DISK,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1},
+
+ { "hinv.nfilesys",
+ { PMDA_PMID(SCLR_FILESYS,1023), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1},
+
+ { "pmda.uname",
+ { PMDA_PMID(SCLR_SYSINFO,107), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1 },
+
+ { "hinv.pagesize",
+ { PMDA_PMID(SCLR_SYSINFO,108), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, -1 },
+
+ { "hinv.physmem",
+ { PMDA_PMID(SCLR_SYSINFO,109), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0)
+ }, -1 },
+
+ { "zpool.capacity",
+ { PMDA_PMID(SCLR_ZPOOL,2), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_space) },
+ { "zpool.used",
+ { PMDA_PMID(SCLR_ZPOOL,3), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_alloc) },
+ { "zpool.checksum_errors",
+ { PMDA_PMID(SCLR_ZPOOL,4), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_checksum_errors) },
+ { "zpool.self_healed",
+ { PMDA_PMID(SCLR_ZPOOL,5), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_self_healed) },
+ { "zpool.in.bytes",
+ { PMDA_PMID(SCLR_ZPOOL,6), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_bytes[ZIO_TYPE_READ]) },
+ { "zpool.in.ops",
+ { PMDA_PMID(SCLR_ZPOOL,7), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_READ]) },
+ { "zpool.in.errors",
+ { PMDA_PMID(SCLR_ZPOOL,8), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_read_errors) },
+ { "zpool.out.bytes",
+ { PMDA_PMID(SCLR_ZPOOL,9), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_bytes[ZIO_TYPE_WRITE]) },
+ { "zpool.out.ops",
+ { PMDA_PMID(SCLR_ZPOOL,10), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_WRITE]) },
+ { "zpool.out.errors",
+ { PMDA_PMID(SCLR_ZPOOL,11), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_write_errors) },
+ { "zpool.ops.noops",
+ { PMDA_PMID(SCLR_ZPOOL,12), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_NULL]) },
+ { "zpool.ops.ioctls",
+ { PMDA_PMID(SCLR_ZPOOL,13), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_WRITE]) },
+ { "zpool.ops.claims",
+ { PMDA_PMID(SCLR_ZPOOL,14), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_WRITE]) },
+ { "zpool.ops.frees",
+ { PMDA_PMID(SCLR_ZPOOL,15), PM_TYPE_U64, ZPOOL_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_ops[ZIO_TYPE_WRITE]) },
+ { "zfs.used.total",
+ { PMDA_PMID(SCLR_ZFS,10), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USED },
+ { "zfs.available",
+ { PMDA_PMID(SCLR_ZFS,0), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_AVAILABLE },
+ { "zfs.quota",
+ { PMDA_PMID(SCLR_ZFS,1), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_QUOTA },
+ { "zfs.reservation",
+ { PMDA_PMID(SCLR_ZFS,2), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_RESERVATION },
+ { "zfs.compression",
+ { PMDA_PMID(SCLR_ZFS,3), PM_TYPE_DOUBLE, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, ZFS_PROP_COMPRESSRATIO },
+ { "zfs.copies",
+ { PMDA_PMID(SCLR_ZFS,4), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, ZFS_PROP_COPIES },
+ { "zfs.used.byme",
+ { PMDA_PMID(SCLR_ZFS,11), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USEDDS },
+ { "zfs.used.bysnapshots",
+ { PMDA_PMID(SCLR_ZFS,12), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USEDSNAP },
+ { "zfs.used.bychildren",
+ { PMDA_PMID(SCLR_ZFS,13), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USEDCHILD },
+
+ { "network.udp.ipackets",
+ { PMDA_PMID(SCLR_NETIF,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(ipackets) },
+ { "network.udp.opackets",
+ { PMDA_PMID(SCLR_NETIF,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(opackets) },
+ { "network.udp.ierrors",
+ { PMDA_PMID(SCLR_NETIF,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(ierrors) },
+ { "network.udp.oerrors",
+ { PMDA_PMID(SCLR_NETIF,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(oerrors) },
+
+ { "network.interface.mtu",
+ { PMDA_PMID(SCLR_NETIF,0), PM_TYPE_U32, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, NM2_NETIF_OFFSET(mtu) },
+ { "network.interface.in.packets",
+ { PMDA_PMID(SCLR_NETIF,2), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(ipackets) },
+ { "network.interface.in.bytes",
+ { PMDA_PMID(SCLR_NETIF,3), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, NM2_NETIF_OFFSET(ibytes) },
+ { "network.interface.in.bcasts",
+ { PMDA_PMID(SCLR_NETIF,4), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(ibcast) },
+ { "network.interface.in.mcasts",
+ { PMDA_PMID(SCLR_NETIF,5), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(imcast) },
+ { "network.interface.out.packets",
+ { PMDA_PMID(SCLR_NETIF,9), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(opackets) },
+ { "network.interface.out.bytes",
+ { PMDA_PMID(SCLR_NETIF,10), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, NM2_NETIF_OFFSET(obytes) },
+ { "network.interface.out.bcasts",
+ { PMDA_PMID(SCLR_NETIF,11), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(obcast) },
+ { "network.interface.out.mcasts",
+ { PMDA_PMID(SCLR_NETIF,12), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(omcast) },
+ { "network.interface.in.errors",
+ { PMDA_PMID(SCLR_NETIF,1), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(ierrors) },
+ { "network.interface.out.errors",
+ { PMDA_PMID(SCLR_NETIF,8), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(oerrors) },
+ { "network.interface.in.drops",
+ { PMDA_PMID(SCLR_NETIF,6), PM_TYPE_U32, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(idrops) },
+ { "network.interface.out.drops",
+ { PMDA_PMID(SCLR_NETIF,13), PM_TYPE_U32, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(odrops) },
+ { "network.interface.in.delivers",
+ { PMDA_PMID(SCLR_NETIF,7), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_NETIF_OFFSET(delivered) },
+ { "network.udp.noports",
+ { PMDA_PMID(SCLR_NETIF,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(noports) },
+ { "network.udp.overflows",
+ { PMDA_PMID(SCLR_NETIF,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, NM2_UDP_OFFSET(overflows) },
+
+ { "zpool.state",
+ { PMDA_PMID(SCLR_ZPOOL,0), PM_TYPE_STRING, ZPOOL_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, 0 },
+
+ { "zpool.state_int",
+ { PMDA_PMID(SCLR_ZPOOL,1), PM_TYPE_U32, ZPOOL_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, 0 },
+ { "zpool.perdisk.state",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,0), PM_TYPE_STRING, ZPOOL_PERDISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, VDEV_OFFSET(vs_state) },
+ { "zpool.perdisk.state_int",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,1), PM_TYPE_U32, ZPOOL_PERDISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, 0 },
+ { "zpool.perdisk.checksum_errors",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,2), PM_TYPE_U64, ZPOOL_PERDISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_checksum_errors) },
+ { "zpool.perdisk.self_healed",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,3), PM_TYPE_U64, ZPOOL_PERDISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, VDEV_OFFSET(vs_self_healed) },
+ { "zpool.perdisk.in.errors",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,4), PM_TYPE_U64, ZPOOL_PERDISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_read_errors) },
+ { "zpool.perdisk.out.errors",
+ { PMDA_PMID(SCLR_ZPOOL_PERDISK,5), PM_TYPE_U64, ZPOOL_PERDISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, VDEV_OFFSET(vs_write_errors) },
+
+ { "network.link.in.errors",
+ { PMDA_PMID(SCLR_NETLINK,4), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ierrors" },
+ { "network.link.in.packets",
+ { PMDA_PMID(SCLR_NETLINK,5), PM_TYPE_U64, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ipackets64" },
+ { "network.link.in.bytes",
+ { PMDA_PMID(SCLR_NETLINK,6), PM_TYPE_U64, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"rbytes64" },
+ { "network.link.in.bcasts",
+ { PMDA_PMID(SCLR_NETLINK,7), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"brdcstrcv" },
+ { "network.link.in.mcasts",
+ { PMDA_PMID(SCLR_NETLINK,8), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"multircv" },
+ { "network.link.in.nobufs",
+ { PMDA_PMID(SCLR_NETLINK,9), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"norcvbuf" },
+ { "network.link.out.errors",
+ { PMDA_PMID(SCLR_NETLINK,10), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"oerrors" },
+ { "network.link.out.packets",
+ { PMDA_PMID(SCLR_NETLINK,11), PM_TYPE_U64, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"opackets64" },
+ { "network.link.out.bytes",
+ { PMDA_PMID(SCLR_NETLINK,12), PM_TYPE_U64, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"obytes64" },
+ { "network.link.out.bcasts",
+ { PMDA_PMID(SCLR_NETLINK,13), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"brdcstxmt" },
+ { "network.link.out.mcasts",
+ { PMDA_PMID(SCLR_NETLINK,14), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"multixmt" },
+ { "network.link.out.nobufs",
+ { PMDA_PMID(SCLR_NETLINK,15), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"noxmtbuf" },
+ { "network.link.collisions",
+ { PMDA_PMID(SCLR_NETLINK,0), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"collisions" },
+ { "network.link.state",
+ { PMDA_PMID(SCLR_NETLINK,1), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"link_state" },
+ { "network.link.duplex",
+ { PMDA_PMID(SCLR_NETLINK,2), PM_TYPE_U32, NETLINK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"link_duplex" },
+ { "network.link.speed",
+ { PMDA_PMID(SCLR_NETLINK,3), PM_TYPE_U64, NETLINK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ifspeed" },
+
+ { "zfs.recordsize",
+ { PMDA_PMID(SCLR_ZFS,5), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_RECORDSIZE },
+ { "zfs.refquota",
+ { PMDA_PMID(SCLR_ZFS,6), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_REFQUOTA },
+ { "zfs.refreservation",
+ { PMDA_PMID(SCLR_ZFS,7), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_REFRESERVATION },
+ { "zfs.used.byrefreservation",
+ { PMDA_PMID(SCLR_ZFS,14), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USEDREFRESERV },
+ { "zfs.referenced",
+ { PMDA_PMID(SCLR_ZFS,8), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_REFERENCED },
+ { "zfs.nsnapshots",
+ { PMDA_PMID(SCLR_ZFS,9), PM_TYPE_U64, ZFS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, -1 },
+ { "zfs.snapshot.used",
+ { PMDA_PMID(SCLR_ZFS,15), PM_TYPE_U64, ZFS_SNAP_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_USED },
+ { "zfs.snapshot.referenced",
+ { PMDA_PMID(SCLR_ZFS,16), PM_TYPE_U64, ZFS_SNAP_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, ZFS_PROP_REFERENCED },
+ { "zfs.snapshot.compression",
+ { PMDA_PMID(SCLR_ZFS,17), PM_TYPE_DOUBLE, ZFS_SNAP_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, ZFS_PROP_COMPRESSRATIO },
+ { "kernel.all.load",
+ { PMDA_PMID(SCLR_SYSINFO,135), PM_TYPE_FLOAT, LOADAVG_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, 0 },
+
+ { "kernel.fsflush.scanned",
+ { PMDA_PMID(SCLR_FSFLUSH,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_scan) },
+ { "kernel.fsflush.examined",
+ { PMDA_PMID(SCLR_FSFLUSH,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_examined) },
+ { "kernel.fsflush.locked",
+ { PMDA_PMID(SCLR_FSFLUSH,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_locked) },
+ { "kernel.fsflush.modified",
+ { PMDA_PMID(SCLR_FSFLUSH,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_modified) },
+ { "kernel.fsflush.coalesced",
+ { PMDA_PMID(SCLR_FSFLUSH,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_coalesce) },
+ { "kernel.fsflush.released",
+ { PMDA_PMID(SCLR_FSFLUSH,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, FSF_STAT_OFFSET(fsf_releases) },
+ { "kernel.fsflush.time",
+ { PMDA_PMID(SCLR_FSFLUSH,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, FSF_STAT_OFFSET(fsf_time) },
+
+ { "mem.physmem",
+ { PMDA_PMID(SCLR_SYSINFO,136), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, -1},
+ { "mem.freemem",
+ { PMDA_PMID(SCLR_SYSINFO,137), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, -1},
+ { "mem.lotsfree",
+ { PMDA_PMID(SCLR_SYSINFO,138), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, -1},
+ { "mem.availrmem",
+ { PMDA_PMID(SCLR_SYSINFO,139), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, -1},
+
+ { "zfs.arc.size",
+ { PMDA_PMID(SCLR_ARCSTATS,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"size"},
+ { "zfs.arc.min_size",
+ { PMDA_PMID(SCLR_ARCSTATS,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"c_min"},
+ { "zfs.arc.max_size",
+ { PMDA_PMID(SCLR_ARCSTATS,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"c_max"},
+ { "zfs.arc.mru_size",
+ { PMDA_PMID(SCLR_ARCSTATS,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"p"},
+ { "zfs.arc.target_size",
+ { PMDA_PMID(SCLR_ARCSTATS,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"c"},
+ { "zfs.arc.misses.total",
+ { PMDA_PMID(SCLR_ARCSTATS,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"misses"},
+ { "zfs.arc.misses.demand_data",
+ { PMDA_PMID(SCLR_ARCSTATS,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"demand_data_misses"},
+ { "zfs.arc.misses.demand_metadata",
+ { PMDA_PMID(SCLR_ARCSTATS,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"demand_metadata_misses"},
+ { "zfs.arc.misses.prefetch_data",
+ { PMDA_PMID(SCLR_ARCSTATS,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"prefetch_data_misses"},
+ { "zfs.arc.misses.prefetch_metadata",
+ { PMDA_PMID(SCLR_ARCSTATS,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"prefetch_metadata_misses"},
+ { "zfs.arc.hits.total",
+ { PMDA_PMID(SCLR_ARCSTATS,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"hits"},
+ { "zfs.arc.hits.mfu",
+ { PMDA_PMID(SCLR_ARCSTATS,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"mfu_hits"},
+ { "zfs.arc.hits.mru",
+ { PMDA_PMID(SCLR_ARCSTATS,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"mru_hits"},
+ { "zfs.arc.hits.mfu_ghost",
+ { PMDA_PMID(SCLR_ARCSTATS,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"mfu_ghost_hits"},
+ { "zfs.arc.hits.mru_ghost",
+ { PMDA_PMID(SCLR_ARCSTATS,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"mru_ghost_hits"},
+ { "zfs.arc.hits.demand_data",
+ { PMDA_PMID(SCLR_ARCSTATS,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"demand_data_hits"},
+ { "zfs.arc.hits.demand_metadata",
+ { PMDA_PMID(SCLR_ARCSTATS,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"demand_metadata_hits"},
+ { "zfs.arc.hits.prefetch_data",
+ { PMDA_PMID(SCLR_ARCSTATS,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"prefetch_data_hits"},
+ { "zfs.arc.hits.prefetch_metadata",
+ { PMDA_PMID(SCLR_ARCSTATS,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"prefetch_metadata_hits"},
+ { "pmda.prefetch.time",
+ { PMDA_PMID(4095,0), PM_TYPE_U64, PREFETCH_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, -1 },
+ { "pmda.prefetch.count",
+ { PMDA_PMID(4095,1), PM_TYPE_U64, PREFETCH_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1 },
+ { "pmda.metric.time",
+ { PMDA_PMID(4095,2), PM_TYPE_U64, METRIC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, -1 },
+ { "pmda.metric.count",
+ { PMDA_PMID(4095,3), PM_TYPE_U64, METRIC_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1 },
+ { "disk.dev.wait.time",
+ { PMDA_PMID(SCLR_DISK,16), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, KSTAT_IO_OFF(wtime)},
+ { "disk.dev.wait.count",
+ { PMDA_PMID(SCLR_DISK,17), PM_TYPE_U32, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(wcnt)},
+ { "disk.dev.run.time",
+ { PMDA_PMID(SCLR_DISK,18), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, KSTAT_IO_OFF(rtime)},
+ { "disk.dev.run.count",
+ { PMDA_PMID(SCLR_DISK,19), PM_TYPE_U32, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, KSTAT_IO_OFF(rcnt)},
+
+ { "disk.all.wait.time",
+ { PMDA_PMID(SCLR_DISK,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, KSTAT_IO_OFF(wtime)},
+ { "disk.all.wait.count",
+ { PMDA_PMID(SCLR_DISK,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, -1},
+ { "disk.all.run.time",
+ { PMDA_PMID(SCLR_DISK,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_NSEC, 0)
+ }, KSTAT_IO_OFF(rtime)},
+ { "disk.all.run.count",
+ { PMDA_PMID(SCLR_DISK,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, -1},
+
+ { "kernel.fs.read_bytes",
+ { PMDA_PMID(SCLR_FILESYS,0), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"read_bytes"},
+ { "kernel.fs.readdir_bytes",
+ { PMDA_PMID(SCLR_FILESYS,1), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"readdir_bytes"},
+ { "kernel.fs.write_bytes",
+ { PMDA_PMID(SCLR_FILESYS,2), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"write_bytes"},
+ { "kernel.fs.vnops.access",
+ { PMDA_PMID(SCLR_FILESYS,3), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"naccess"},
+ { "kernel.fs.vnops.addmap",
+ {PMDA_PMID(SCLR_FILESYS,4), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"naddmap"},
+ { "kernel.fs.vnops.close",
+ {PMDA_PMID(SCLR_FILESYS,5), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nclose"},
+ { "kernel.fs.vnops.cmp",
+ {PMDA_PMID(SCLR_FILESYS,6), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ncmp"},
+ { "kernel.fs.vnops.create",
+ {PMDA_PMID(SCLR_FILESYS,7), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ncreate"},
+ { "kernel.fs.vnops.delmap",
+ {PMDA_PMID(SCLR_FILESYS,8), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndelmap"},
+ { "kernel.fs.vnops.dispose",
+ {PMDA_PMID(SCLR_FILESYS,9), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndispose"},
+ { "kernel.fs.vnops.dump",
+ {PMDA_PMID(SCLR_FILESYS,10), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndump"},
+ { "kernel.fs.vnops.dumpctl",
+ {PMDA_PMID(SCLR_FILESYS,11), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndumpctl"},
+ { "kernel.fs.vnops.fid",
+ {PMDA_PMID(SCLR_FILESYS,12), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfid"},
+ { "kernel.fs.vnops.frlock",
+ {PMDA_PMID(SCLR_FILESYS,13), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfrlock"},
+ { "kernel.fs.vnops.fsync",
+ {PMDA_PMID(SCLR_FILESYS,14), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfsync"},
+ { "kernel.fs.vnops.getattr",
+ {PMDA_PMID(SCLR_FILESYS,15), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetattr"},
+ { "kernel.fs.vnops.getpage",
+ {PMDA_PMID(SCLR_FILESYS,16), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetpage"},
+ { "kernel.fs.vnops.getsecattr",
+ {PMDA_PMID(SCLR_FILESYS,17), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetsecattr"},
+ { "kernel.fs.vnops.inactive",
+ {PMDA_PMID(SCLR_FILESYS,18), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ninactive"},
+ { "kernel.fs.vnops.ioctl",
+ {PMDA_PMID(SCLR_FILESYS,19), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nioctl"},
+ { "kernel.fs.vnops.link",
+ {PMDA_PMID(SCLR_FILESYS,20), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nlink"},
+ { "kernel.fs.vnops.lookup",
+ {PMDA_PMID(SCLR_FILESYS,21), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nlookup"},
+ { "kernel.fs.vnops.map",
+ {PMDA_PMID(SCLR_FILESYS,22), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nmap"},
+ { "kernel.fs.vnops.mkdir",
+ {PMDA_PMID(SCLR_FILESYS,23), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nmkdir"},
+ { "kernel.fs.vnops.open",
+ {PMDA_PMID(SCLR_FILESYS,24), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nopen"},
+ { "kernel.fs.vnops.pageio",
+ {PMDA_PMID(SCLR_FILESYS,25), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npageio"},
+ { "kernel.fs.vnops.pathconf",
+ {PMDA_PMID(SCLR_FILESYS,26), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npathconf"},
+ { "kernel.fs.vnops.poll",
+ {PMDA_PMID(SCLR_FILESYS,27), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npoll"},
+ { "kernel.fs.vnops.putpage",
+ {PMDA_PMID(SCLR_FILESYS,28), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nputpage"},
+ { "kernel.fs.vnops.read",
+ {PMDA_PMID(SCLR_FILESYS,29), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nread"},
+ { "kernel.fs.vnops.readdir",
+ {PMDA_PMID(SCLR_FILESYS,30), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nreaddir"},
+ { "kernel.fs.vnops.readlink",
+ {PMDA_PMID(SCLR_FILESYS,31), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nreadlink"},
+ { "kernel.fs.vnops.realvp",
+ {PMDA_PMID(SCLR_FILESYS,32), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrealvp"},
+ { "kernel.fs.vnops.remove",
+ {PMDA_PMID(SCLR_FILESYS,33), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nremove"},
+ { "kernel.fs.vnops.rename",
+ {PMDA_PMID(SCLR_FILESYS,34), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrename"},
+ { "kernel.fs.vnops.rmdir",
+ {PMDA_PMID(SCLR_FILESYS,35), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrmdir"},
+ { "kernel.fs.vnops.rwlock",
+ {PMDA_PMID(SCLR_FILESYS,36), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrwlock"},
+ { "kernel.fs.vnops.rwunlock",
+ {PMDA_PMID(SCLR_FILESYS,37), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrwunlock"},
+ { "kernel.fs.vnops.seek",
+ {PMDA_PMID(SCLR_FILESYS,38), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nseek"},
+ { "kernel.fs.vnops.setattr",
+ {PMDA_PMID(SCLR_FILESYS,39), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetattr"},
+ { "kernel.fs.vnops.setfl",
+ {PMDA_PMID(SCLR_FILESYS,40), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetfl"},
+ { "kernel.fs.vnops.setsecattr",
+ {PMDA_PMID(SCLR_FILESYS,41), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetsecattr"},
+ { "kernel.fs.vnops.shrlock",
+ {PMDA_PMID(SCLR_FILESYS,42), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nshrlock"},
+ { "kernel.fs.vnops.space",
+ {PMDA_PMID(SCLR_FILESYS,43), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nspace"},
+ { "kernel.fs.vnops.symlink",
+ {PMDA_PMID(SCLR_FILESYS,44), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsymlink"},
+ { "kernel.fs.vnops.vnevent",
+ {PMDA_PMID(SCLR_FILESYS,45), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nvnevent"},
+ { "kernel.fs.vnops.write",
+ {PMDA_PMID(SCLR_FILESYS,46), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nwrite"},
+
+ { "kernel.fstype.read_bytes",
+ { PMDA_PMID(SCLR_FILESYS,47), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"read_bytes"},
+ { "kernel.fstype.readdir_bytes",
+ { PMDA_PMID(SCLR_FILESYS,48), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"readdir_bytes"},
+ { "kernel.fstype.write_bytes",
+ { PMDA_PMID(SCLR_FILESYS,49), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"write_bytes"},
+ { "kernel.fstype.vnops.access",
+ { PMDA_PMID(SCLR_FILESYS,50), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"naccess"},
+ { "kernel.fstype.vnops.addmap",
+ {PMDA_PMID(SCLR_FILESYS,51), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"naddmap"},
+ { "kernel.fstype.vnops.close",
+ {PMDA_PMID(SCLR_FILESYS,52), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nclose"},
+ { "kernel.fstype.vnops.cmp",
+ {PMDA_PMID(SCLR_FILESYS,53), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ncmp"},
+ { "kernel.fstype.vnops.create",
+ {PMDA_PMID(SCLR_FILESYS,54), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ncreate"},
+ { "kernel.fstype.vnops.delmap",
+ {PMDA_PMID(SCLR_FILESYS,55), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndelmap"},
+ { "kernel.fstype.vnops.dispose",
+ {PMDA_PMID(SCLR_FILESYS,56), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndispose"},
+ { "kernel.fstype.vnops.dump",
+ {PMDA_PMID(SCLR_FILESYS,57), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndump"},
+ { "kernel.fstype.vnops.dumpctl",
+ {PMDA_PMID(SCLR_FILESYS,58), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ndumpctl"},
+ { "kernel.fstype.vnops.fid",
+ {PMDA_PMID(SCLR_FILESYS,59), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfid"},
+ { "kernel.fstype.vnops.frlock",
+ {PMDA_PMID(SCLR_FILESYS,60), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfrlock"},
+ { "kernel.fstype.vnops.fsync",
+ {PMDA_PMID(SCLR_FILESYS,61), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nfsync"},
+ { "kernel.fstype.vnops.getattr",
+ {PMDA_PMID(SCLR_FILESYS,62), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetattr"},
+ { "kernel.fstype.vnops.getpage",
+ {PMDA_PMID(SCLR_FILESYS,63), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetpage"},
+ { "kernel.fstype.vnops.getsecattr",
+ {PMDA_PMID(SCLR_FILESYS,64), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ngetsecattr"},
+ { "kernel.fstype.vnops.inactive",
+ {PMDA_PMID(SCLR_FILESYS,65), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"ninactive"},
+ { "kernel.fstype.vnops.ioctl",
+ {PMDA_PMID(SCLR_FILESYS,66), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nioctl"},
+ { "kernel.fstype.vnops.link",
+ {PMDA_PMID(SCLR_FILESYS,67), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nlink"},
+ { "kernel.fstype.vnops.lookup",
+ {PMDA_PMID(SCLR_FILESYS,68), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nlookup"},
+ { "kernel.fstype.vnops.map",
+ {PMDA_PMID(SCLR_FILESYS,69), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nmap"},
+ { "kernel.fstype.vnops.mkdir",
+ {PMDA_PMID(SCLR_FILESYS,70), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nmkdir"},
+ { "kernel.fstype.vnops.open",
+ {PMDA_PMID(SCLR_FILESYS,71), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nopen"},
+ { "kernel.fstype.vnops.pageio",
+ {PMDA_PMID(SCLR_FILESYS,72), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npageio"},
+ { "kernel.fstype.vnops.pathconf",
+ {PMDA_PMID(SCLR_FILESYS,73), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npathconf"},
+ { "kernel.fstype.vnops.poll",
+ {PMDA_PMID(SCLR_FILESYS,74), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"npoll"},
+ { "kernel.fstype.vnops.putpage",
+ {PMDA_PMID(SCLR_FILESYS,75), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nputpage"},
+ { "kernel.fstype.vnops.read",
+ {PMDA_PMID(SCLR_FILESYS,76), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nread"},
+ { "kernel.fstype.vnops.readdir",
+ {PMDA_PMID(SCLR_FILESYS,77), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nreaddir"},
+ { "kernel.fstype.vnops.readlink",
+ {PMDA_PMID(SCLR_FILESYS,78), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nreadlink"},
+ { "kernel.fstype.vnops.realvp",
+ {PMDA_PMID(SCLR_FILESYS,79), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrealvp"},
+ { "kernel.fstype.vnops.remove",
+ {PMDA_PMID(SCLR_FILESYS,80), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nremove"},
+ { "kernel.fstype.vnops.rename",
+ {PMDA_PMID(SCLR_FILESYS,81), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrename"},
+ { "kernel.fstype.vnops.rmdir",
+ {PMDA_PMID(SCLR_FILESYS,82), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrmdir"},
+ { "kernel.fstype.vnops.rwlock",
+ {PMDA_PMID(SCLR_FILESYS,83), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrwlock"},
+ { "kernel.fstype.vnops.rwunlock",
+ {PMDA_PMID(SCLR_FILESYS,84), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nrwunlock"},
+ { "kernel.fstype.vnops.seek",
+ {PMDA_PMID(SCLR_FILESYS,85), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nseek"},
+ { "kernel.fstype.vnops.setattr",
+ {PMDA_PMID(SCLR_FILESYS,86), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetattr"},
+ { "kernel.fstype.vnops.setfl",
+ {PMDA_PMID(SCLR_FILESYS,87), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetfl"},
+ { "kernel.fstype.vnops.setsecattr",
+ {PMDA_PMID(SCLR_FILESYS,88), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsetsecattr"},
+ { "kernel.fstype.vnops.shrlock",
+ {PMDA_PMID(SCLR_FILESYS,89), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nshrlock"},
+ { "kernel.fstype.vnops.space",
+ {PMDA_PMID(SCLR_FILESYS,90), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nspace"},
+ { "kernel.fstype.vnops.symlink",
+ {PMDA_PMID(SCLR_FILESYS,91), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nsymlink"},
+ { "kernel.fstype.vnops.vnevent",
+ {PMDA_PMID(SCLR_FILESYS,92), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nvnevent"},
+ { "kernel.fstype.vnops.write",
+ {PMDA_PMID(SCLR_FILESYS,93), PM_TYPE_U64, FSTYPE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, (ptrdiff_t)"nwrite"},
+
+ { "hinv.cpu.maxclock",
+ {PMDA_PMID(SCLR_SYSINFO,147), PM_TYPE_64, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, -1, 1, 0, PM_TIME_SEC, 6)
+ }, (ptrdiff_t)"clock_MHz"},
+ { "hinv.cpu.clock",
+ {PMDA_PMID(SCLR_SYSINFO,148), PM_TYPE_U64, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, -1, 1, 0, PM_TIME_SEC, 0)
+ }, (ptrdiff_t)"current_clock_Hz"},
+ { "hinv.cpu.brand",
+ {PMDA_PMID(SCLR_SYSINFO, 149), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"brand"},
+ { "hinv.cpu.frequencies",
+ {PMDA_PMID(SCLR_SYSINFO, 150), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"supported_frequencies_Hz"},
+ { "hinv.cpu.implementation",
+ {PMDA_PMID(SCLR_SYSINFO, 151), PM_TYPE_STRING, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"implementation"},
+ { "hinv.cpu.chip_id",
+ {PMDA_PMID(SCLR_SYSINFO, 152), PM_TYPE_64, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"chip_id"},
+ { "hinv.cpu.clog_id",
+ {PMDA_PMID(SCLR_SYSINFO, 153), PM_TYPE_32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"clog_id"},
+ { "hinv.cpu.core_id",
+ {PMDA_PMID(SCLR_SYSINFO, 154), PM_TYPE_64, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"core_id"},
+ { "hinv.cpu.pkg_core_id",
+ {PMDA_PMID(SCLR_SYSINFO, 155), PM_TYPE_64, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"pkg_core_id"},
+ { "hinv.cpu.cstate",
+ {PMDA_PMID(SCLR_SYSINFO, 156), PM_TYPE_32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"current_cstate"},
+ { "hinv.cpu.maxcstates",
+ {PMDA_PMID(SCLR_SYSINFO, 157), PM_TYPE_32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"supported_max_cstates"},
+ { "hinv.cpu.ncores",
+ {PMDA_PMID(SCLR_SYSINFO, 158), PM_TYPE_32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"ncore_per_chip"},
+ { "hinv.cpu.ncpus",
+ {PMDA_PMID(SCLR_SYSINFO, 159), PM_TYPE_32, CPU_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"ncpu_per_chip"},
+
+ { "disk.dev.errors.soft",
+ {PMDA_PMID(SCLR_DISK, 21), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Soft Errors"},
+ { "disk.dev.errors.hard",
+ {PMDA_PMID(SCLR_DISK, 22), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Hard Errors"},
+ { "disk.dev.errors.transport",
+ {PMDA_PMID(SCLR_DISK, 23), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Transport Errors"},
+ { "disk.dev.errors.media",
+ {PMDA_PMID(SCLR_DISK, 24), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Media Error"},
+ { "disk.dev.errors.recoverable",
+ {PMDA_PMID(SCLR_DISK, 25), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Recoverable"},
+ { "disk.dev.errors.notready",
+ {PMDA_PMID(SCLR_DISK, 26), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Device Not Ready"},
+ { "disk.dev.errors.nodevice",
+ {PMDA_PMID(SCLR_DISK, 27), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"No Device"},
+ { "disk.dev.errors.badrequest",
+ {PMDA_PMID(SCLR_DISK, 28), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Illegal Request"},
+ { "disk.dev.errors.pfa",
+ {PMDA_PMID(SCLR_DISK, 29), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE),
+ }, (ptrdiff_t)"Predictive Failure Analysis"},
+ { "hinv.disk.vendor",
+ {PMDA_PMID(SCLR_DISK, 30), PM_TYPE_STRING, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"Vendor"},
+ { "hinv.disk.product",
+ {PMDA_PMID(SCLR_DISK, 31), PM_TYPE_STRING, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"Product"},
+ { "hinv.disk.revision",
+ {PMDA_PMID(SCLR_DISK, 32), PM_TYPE_STRING, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"Revision"},
+ { "hinv.disk.serial",
+ {PMDA_PMID(SCLR_DISK, 33), PM_TYPE_STRING, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, (ptrdiff_t)"Serial No"},
+ { "hinv.disk.capacity",
+ { PMDA_PMID(SCLR_DISK,34), PM_TYPE_U64, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, (ptrdiff_t)"Size" },
+ { "hinv.disk.devlink",
+ {PMDA_PMID(SCLR_DISK, 35), PM_TYPE_STRING, DISK_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, -1}
+
+ /* remember to add trailing comma before adding more entries ... */
+};
+int metrictab_sz = ARRAY_SIZE(metricdesc);
+
+pmdaInstid metric_insts[ARRAY_SIZE(metricdesc)];
+
+/*
+ * List of instance domains ... we expect the *_INDOM macros
+ * to index into this table.
+ */
+pmdaIndom indomtab[] = {
+ { DISK_INDOM, 0, NULL },
+ { CPU_INDOM, 0, NULL },
+ { NETIF_INDOM, 0, NULL },
+ { ZPOOL_INDOM, 0, NULL },
+ { ZFS_INDOM, 0, NULL },
+ { ZPOOL_PERDISK_INDOM, 0, NULL },
+ { NETLINK_INDOM},
+ { ZFS_SNAP_INDOM },
+ { LOADAVG_INDOM, ARRAY_SIZE(loadavg_insts), loadavg_insts},
+ { PREFETCH_INDOM, ARRAY_SIZE(prefetch_insts), prefetch_insts},
+ { METRIC_INDOM, ARRAY_SIZE(metric_insts), metric_insts},
+ { FILESYS_INDOM },
+ { FSTYPE_INDOM }
+};
+
+int indomtab_sz = sizeof(indomtab) / sizeof(indomtab[0]);
+
+static kstat_ctl_t *kc;
+static int kstat_chains_updated;
+
+kstat_ctl_t *
+kstat_ctl_update(void)
+{
+ if (!kstat_chains_updated) {
+ if (kstat_chain_update(kc) == -1) {
+ kstat_chains_updated = 0;
+ return NULL;
+ }
+ kstat_chains_updated = 1;
+ }
+ return kc;
+}
+
+void
+kstat_ctl_needs_update(void)
+{
+ kstat_chains_updated = 0;
+}
+
+void
+init_data(int domain)
+{
+ int i;
+ int serial;
+ __pmID_int *ip;
+
+ /*
+ * set up kstat() handle ... failure is fatal
+ */
+ if ((kc = kstat_open()) == NULL) {
+ fprintf(stderr, "init_data: kstat_open failed: %s\n", osstrerror());
+ exit(1);
+ }
+
+ /*
+ * Create the PMDA's metrictab[] version of the per-metric table.
+ *
+ * Also do domain initialization for each pmid and indom element of
+ * the metricdesc[] table ... the PMDA table is fixed up in
+ * libpcp_pmda
+ */
+ if ((metrictab = (pmdaMetric *)malloc(metrictab_sz * sizeof(pmdaMetric))) == NULL) {
+ fprintf(stderr, "init_data: Error: malloc metrictab [%d] failed: %s\n",
+ (int)(metrictab_sz * sizeof(pmdaMetric)), osstrerror());
+ exit(1);
+ }
+ for (i = 0; i < metrictab_sz; i++) {
+ metrictab[i].m_user = &metricdesc[i];
+ metrictab[i].m_desc = metricdesc[i].md_desc;
+ ip = (__pmID_int *)&metricdesc[i].md_desc.pmid;
+ ip->domain = domain;
+
+ if (metricdesc[i].md_desc.indom != PM_INDOM_NULL) {
+ serial = metricdesc[i].md_desc.indom;
+ metricdesc[i].md_desc.indom = pmInDom_build(domain, serial);
+ }
+ metric_insts[i].i_inst = i+1;
+ metric_insts[i].i_name = (char *)metricdesc[i].md_name;
+ }
+
+ /* Bless indoms with our own domain - usually pmdaInit will do it for
+ * us but we need properly setup indoms for pmdaCache which means that
+ * we have to do it ourselves */
+ for (i = 0; i < indomtab_sz; i++) {
+ __pmindom_int(&indomtab[i].it_indom)->domain = domain;
+ }
+
+ /*
+ * initialize each of the methods
+ */
+ for (i = 0; i < methodtab_sz; i++) {
+ if (methodtab[i].m_init) {
+ methodtab[i].m_init(1);
+ }
+
+ prefetch_insts[i].i_inst = i + 1;
+ prefetch_insts[i].i_name = (char *)methodtab[i].m_name;
+ }
+}
diff --git a/src/pmdas/solaris/disk.c b/src/pmdas/solaris/disk.c
new file mode 100644
index 0000000..70261c2
--- /dev/null
+++ b/src/pmdas/solaris/disk.c
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2010 Max Matveev. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+#include <libdevinfo.h>
+
+#define SOLARIS_PMDA_TRACE (DBG_TRACE_APPL0|DBG_TRACE_APPL2)
+
+typedef struct {
+ int fetched;
+ int err;
+ kstat_t *ksp;
+ kstat_io_t iostat;
+ kstat_t *sderr;
+ int sderr_fresh;
+} ctl_t;
+
+static di_devlink_handle_t devlink_hndl = DI_LINK_NIL;
+static di_node_t di_root = DI_NODE_NIL;
+
+static ctl_t *
+getDiskCtl(pmInDom dindom, const char *name)
+{
+ ctl_t *ctl = NULL;
+ int inst;
+ int rv = pmdaCacheLookupName(dindom,name, &inst, (void **)&ctl);
+
+ if (rv == PMDA_CACHE_ACTIVE)
+ return ctl;
+
+ if ((rv == PMDA_CACHE_INACTIVE) && ctl) {
+ rv = pmdaCacheStore(dindom, PMDA_CACHE_ADD, name, ctl);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot reactivate cached data for disk '%s': %s\n",
+ name, pmErrStr(rv));
+ return NULL;
+ }
+ } else {
+ if ((ctl = (ctl_t *)calloc(1, sizeof(ctl_t))) == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "Out of memory to keep state for disk '%s'\n",
+ name);
+ return NULL;
+ }
+
+ rv = pmdaCacheStore(dindom, PMDA_CACHE_ADD, name, ctl);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot cache data for disk '%s': %s\n",
+ name, pmErrStr(rv));
+ free(ctl);
+ return NULL;
+ }
+ }
+ return ctl;
+}
+
+static void
+disk_walk_chains(pmInDom dindom)
+{
+ kstat_t *ksp;
+ kstat_ctl_t *kc;
+
+ if ((kc = kstat_ctl_update()) == NULL)
+ return;
+
+ for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
+ ctl_t *ctl;
+
+ if ((strcmp(ksp->ks_class, "disk") == 0) &&
+ (ksp->ks_type == KSTAT_TYPE_IO)) {
+ if ((ctl = getDiskCtl(dindom, ksp->ks_name)) == NULL)
+ continue;
+
+ ctl->ksp = ksp;
+ ctl->fetched = 0;
+ } else if (strcmp(ksp->ks_class, "device_error") == 0) {
+ char *comma;
+ char modname[KSTAT_STRLEN];
+
+ strcpy(modname, ksp->ks_name);
+ if ((comma = strchr(modname, ',')) == NULL)
+ continue;
+
+ *comma = '\0';
+ if ((ctl = getDiskCtl(dindom, modname)) == NULL)
+ continue;
+ ctl->sderr = ksp;
+ ctl->sderr_fresh = 0;
+ }
+ }
+}
+
+void
+disk_init(int first)
+{
+ pmInDom dindom = indomtab[DISK_INDOM].it_indom;
+
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+
+ pmdaCacheOp(dindom, PMDA_CACHE_LOAD);
+ disk_walk_chains(dindom);
+ pmdaCacheOp(dindom, PMDA_CACHE_SAVE);
+}
+
+void
+disk_prefetch(void)
+{
+ if (di_root != DI_NODE_NIL) {
+ di_fini(di_root);
+ di_root = DI_NODE_NIL;
+ }
+
+ if (devlink_hndl != DI_LINK_NIL) {
+ di_devlink_fini(&devlink_hndl);
+ devlink_hndl = DI_LINK_NIL;
+ }
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ disk_walk_chains(indomtab[DISK_INDOM].it_indom);
+ pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_SAVE);
+}
+
+static __uint64_t
+disk_derived(pmdaMetric *mdesc, int inst, const kstat_io_t *iostat)
+{
+ pmID pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint64_t val;
+
+ pmid = mdesc->m_desc.pmid;
+ ip->domain = 0;
+
+// from kstat_io_t ...
+//
+// u_longlong_t nread; /* number of bytes read */
+// u_longlong_t nwritten; /* number of bytes written */
+// uint_t reads; /* number of read operations */
+// uint_t writes; /* number of write operations */
+//
+
+ switch (pmid) {
+ case PMDA_PMID(SCLR_DISK,2): /* disk.all.total */
+ case PMDA_PMID(SCLR_DISK,12): /* disk.dev.total */
+ val = iostat->reads + iostat->writes;
+ break;
+
+ case PMDA_PMID(SCLR_DISK,5): /* disk.all.total_bytes */
+ case PMDA_PMID(SCLR_DISK,15): /* disk.dev.total_bytes */
+ val = iostat->nread + iostat->nwritten;
+ break;
+
+ /* iostat->wcnt and iostat->rcnt are 32 bit intergers,
+ * these two metrics must be derived because the metrics
+ * are using 64 bit integers to avoid overflows during
+ * accumultion */
+ case PMDA_PMID(SCLR_DISK,7): /* disk.all.wait.count */
+ val = iostat->wcnt;
+ break;
+ case PMDA_PMID(SCLR_DISK,9): /* disk.all.run.time */
+ val = iostat->rcnt;
+ break;
+
+ default:
+ fprintf(stderr, "disk_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ /* desperate */
+ fprintf(stderr, "disk_derived: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, (unsigned long long)val);
+ }
+#endif
+
+ return val;
+}
+
+static int
+fetch_disk_data(kstat_ctl_t *kc, const pmdaMetric *mdesc, ctl_t *ctl,
+ const char *diskname)
+{
+ if (ctl->fetched == 1)
+ return 1;
+
+ if (ctl->ksp == NULL)
+ return 0;
+
+ if ((kstat_read(kc, ctl->ksp, &ctl->iostat) == -1)) {
+ if (ctl->err == 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Error: disk_fetch(pmid=%s disk=%s ...) - "
+ "kstat_read(kc=%p, ksp=%p, ...) failed: %s\n",
+ pmIDStr(mdesc->m_desc.pmid), diskname,
+ kc, ctl->ksp, osstrerror());
+ }
+ ctl->err++;
+ ctl->fetched = -1;
+ return 0;
+ }
+
+ ctl->fetched = 1;
+ if (ctl->err != 0) {
+ __pmNotifyErr(LOG_INFO,
+ "Success: disk_fetch(pmid=%s disk=%s ...) "
+ "after %d errors as previously reported\n",
+ pmIDStr(mdesc->m_desc.pmid), diskname, ctl->err);
+ ctl->err = 0;
+ }
+
+ return 1;
+}
+
+static int
+get_devlink_path(di_devlink_t devlink, void *arg)
+{
+ const char **p = arg;
+ *p = di_devlink_path(devlink);
+ return DI_WALK_TERMINATE;
+}
+
+static int
+fetch_disk_devlink(const kstat_t *ksp, pmAtomValue *atom)
+{
+ di_node_t n;
+
+ if (di_root == DI_NODE_NIL) {
+ if ((di_root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL)
+ return 0;
+ }
+
+ if (devlink_hndl == DI_LINK_NIL) {
+ if ((devlink_hndl = di_devlink_init(NULL, DI_MAKE_LINK)) == DI_LINK_NIL)
+ return 0;
+ }
+
+ if ((n = di_drv_first_node(ksp->ks_module, di_root)) == DI_NODE_NIL) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ fprintf(stderr,"No nodes for %s: %s\n",
+ ksp->ks_name, osstrerror());
+ }
+#endif
+ return 0;
+ }
+
+ do {
+ if (di_instance(n) == ksp->ks_instance) {
+ di_minor_t minor = di_minor_next(n, DI_MINOR_NIL);
+ char *path;
+ char *devlink = NULL;
+
+ if (minor == DI_MINOR_NIL) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ fprintf (stderr, "No minors of %s: %s\n",
+ ksp->ks_name, osstrerror());
+ }
+#endif
+ return 0;
+ }
+ path = di_devfs_minor_path(minor);
+ di_devlink_walk(devlink_hndl, NULL, path, 0, &devlink,
+ get_devlink_path);
+ di_devfs_path_free(path);
+
+ if (devlink) {
+ atom->cp = devlink;
+ return 1;
+ }
+ return 0;
+ }
+ n = di_drv_next_node(n);
+ } while (n != DI_NODE_NIL);
+ return 0;
+}
+
+static int
+get_instance_value(pmdaMetric *mdesc, pmInDom dindom, int inst,
+ pmAtomValue *atom)
+{
+ ctl_t *ctl;
+ char *diskname;
+ uint64_t ull;
+ ptrdiff_t offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+ kstat_ctl_t *kc;
+
+ if ((kc = kstat_ctl_update()) == NULL)
+ return 0;
+
+ if (pmdaCacheLookup(dindom, inst, &diskname,
+ (void **)&ctl) != PMDA_CACHE_ACTIVE) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ fprintf(stderr,
+ "Unexpected cache result - instance %d "
+ "is not active in disk indom cache\n",
+ inst);
+ }
+#endif
+ return 0;
+ }
+
+ if (offset == -1) {
+ if (pmid_item(mdesc->m_desc.pmid) == 35) { /* hinv.disk.devlink */
+ return fetch_disk_devlink(ctl->ksp, atom);
+ }
+ if (!fetch_disk_data(kc, mdesc, ctl, diskname))
+ return 0;
+ ull = disk_derived(mdesc, inst, &ctl->iostat);
+ } else if (offset > sizeof(ctl->iostat)) { /* device_error */
+ if (ctl->sderr) {
+ kstat_named_t *kn;
+ char * m = (char *)offset;
+
+ if (!ctl->sderr_fresh) {
+ ctl->sderr_fresh = (kstat_read(kc, ctl->sderr, NULL) != -1);
+
+ if (!ctl->sderr_fresh)
+ return 0;
+ }
+
+ if ((kn = kstat_data_lookup(ctl->sderr, m)) == NULL) {
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE)
+ fprintf(stderr, "No %s in %s\n", m, diskname);
+#endif
+ return 0;
+ }
+
+ return kstat_named_to_pmAtom(kn, atom);
+ }
+ return 0;
+ } else {
+ char *iop = ((char *)&ctl->iostat) + offset;
+ if (!fetch_disk_data(kc, mdesc, ctl, diskname))
+ return 0;
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ __uint64_t *ullp = (__uint64_t *)iop;
+ ull = *ullp;
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ /* desperate */
+ fprintf(stderr, "disk_fetch: pmid %s inst %d val %llu\n",
+ pmIDStr(mdesc->m_desc.pmid), inst,
+ (unsigned long long)*ullp);
+ }
+#endif
+ }
+ else {
+ __uint32_t *ulp = (__uint32_t *)iop;
+ ull = *ulp;
+#ifdef PCP_DEBUG
+ if ((pmDebug & SOLARIS_PMDA_TRACE) == SOLARIS_PMDA_TRACE) {
+ /* desperate */
+ fprintf(stderr, "disk_fetch: pmid %s inst %d val %u\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, *ulp);
+ }
+#endif
+ }
+ }
+
+ if (mdesc->m_desc.type == PM_TYPE_U64) {
+ /* export as 64-bit value */
+ atom->ull += ull;
+ }
+ else {
+ /* else export as a 32-bit */
+ atom->ul += (__uint32_t)ull;
+ }
+
+ return 1;
+}
+
+int
+disk_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ int i;
+ pmInDom dindom = indomtab[DISK_INDOM].it_indom;
+
+ if (pmid_item(mdesc->m_desc.pmid) == 20) { /* hinv.ndisk */
+ i = pmdaCacheOp(dindom, PMDA_CACHE_SIZE_ACTIVE);
+ if (i < 0) {
+ return 0;
+ } else {
+ atom->ul = i;
+ return 1;
+ }
+ }
+
+ memset(atom, 0, sizeof(*atom));
+
+ if (inst == PM_IN_NULL) {
+ pmdaCacheOp(dindom,PMDA_CACHE_WALK_REWIND);
+ while ((i = pmdaCacheOp(dindom, PMDA_CACHE_WALK_NEXT)) != -1) {
+ if (get_instance_value(mdesc, dindom, i, atom) == 0)
+ return 0;
+ }
+ return 1;
+ }
+
+ return get_instance_value(mdesc, dindom, inst, atom);
+}
diff --git a/src/pmdas/solaris/help b/src/pmdas/solaris/help
new file mode 100644
index 0000000..4940247
--- /dev/null
+++ b/src/pmdas/solaris/help
@@ -0,0 +1,729 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Solaris PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ kernel.all.cpu.idle Amount of time CPUs were idle
+@ kernel.all.cpu.user Amount of time spent executing userspace tasks
+@ kernel.all.cpu.sys Amount of time spent executing kernel code
+@ kernel.all.cpu.wait.total Amount of time CPU spent waiting for events
+@ kernel.percpu.cpu.user Amount of time spent executing userspace tasks by each CPU
+@ kernel.percpu.cpu.idle Amount of time each CPU was idle
+@ kernel.percpu.cpu.sys Amount of time each CPU spent executing kernel code
+@ kernel.percpu.cpu.wait.total Amount of time each CPU spent waiting for events
+@ disk.all.read Number of read requests aggregated across all disks
+@ disk.all.write Number of write requests aggregated across all disks
+@ disk.all.total Number of IO requests aggregated across all disks
+@ disk.all.read_bytes Number of bytes read from all disks
+@ disk.all.write_bytes Number of bytes written to all disk
+@ disk.all.total_bytes Number of bytes transferred to and from all disks
+@ disk.dev.read Number of read requests for each individual disk
+@ disk.dev.write Number of write requests for each individual disk
+@ disk.dev.total Number of IO requests for each individual disk
+@ disk.dev.read_bytes Number of bytes read from each individual disk
+@ disk.dev.write_bytes Number of bytes written to each individual disk
+@ disk.dev.total_bytes Number of bytes transferred to and from each individual disk
+@ network.interface.mtu Maximum Transmission Unit of a network interface
+Maximum Transmision Unit is the largest size of IP datagram which can be
+transferred over the data link.
+@ network.interface.in.bytes Number of bytes received by a network interface
+@ network.interface.in.errors Number of receive errors per network interface
+Number of receive errors per network interface. The errors counted towards
+this metric are: IP header errors, packets larger then the link MTU, packets
+delivered to the unknown address, packets sent to the unknown IP protocol,
+truncated packets, packets discarded due to not having a route for
+the destination.
+@ network.interface.in.drops Number of packets droped by a network interface
+Number of packets discared due to lack of space during the input processing.
+@ network.interface.in.delivers Number of packets delivered to ULPs
+Number of packets delivered for further processing by the upper-layer
+protocols.
+@ network.interface.in.bcasts Number of broadcast packets received by a network interface
+@ network.interface.in.packets Number of IP packets received by a network interface
+@ network.interface.in.mcasts Number of multicast packets received by a network interface
+@ network.interface.out.packets Number of packets sent by a network interface
+@ network.interface.out.bytes Number of bytes sent by a network interface
+@ network.interface.out.errors Number of send errors per network interface
+@ network.interface.out.bcasts Number of broadcast packets sent by a network interface
+@ network.interface.out.mcasts Number of multicast packets sent by a network interface
+@ network.interface.out.drops Number of packets discared by a network interface
+Number of packets discared due to lack of space during the output processing.
+@ network.udp.ipackets Number of UDP packets received
+@ network.udp.opackets Number of UDP packets sent
+@ network.udp.ierrors Number of receive errors in UDP processing
+@ network.udp.oerrors Number of send erros in UDP processing
+@ network.udp.noports Number of UDP packets received on unknown UDP port
+Number of UDP packets received for which here is no port can be found.
+This counter is reported on the per-interface basis and aggregated by the PMDA.
+
+@ network.udp.overflows Number of UDP packets droped due to queue overflow
+Number of UDP packets droped due to queue overflow.
+This counter is reported on the per-interface basis and aggregated by the PMDA.
+
+@zpool.capacity Total capacity of a zpool in bytes
+@zpool.used Total space used on a pool
+@zpool.checksum_errors Number of checksum errors per zpool
+@zpool.self_healed Number of bytes healed
+@zpool.in.bytes Counter of bytes read from a zpool
+@zpool.out.bytes Counter of bytes written to a zpool
+@zpool.in.ops Counter of reads per zpool
+@zpool.out.ops Counter of writes per zpool
+@zpool.in.errors Counter of read errors per zpool
+@zpool.out.errors Counter of write errors per zpool
+@zpool.state Current state of zpool
+@zpool.state_int vs_aux << 8 | vs_state
+
+@zfs.available Amount of space available to the dataset
+The amount of space available to the dataset (a filesystem,
+a snapshot or a volume) and all its children. This is usually
+the amount of space available in the zpool which houses the
+dataset.
+
+@zfs.used.total Amount of space used by the dataset and its children
+The amount of space consumed by the filesystem, snapshot or
+volume and all its children. This amount does not include
+any reservation made by the dataset itself but do include
+the reservation of the children.
+
+@zfs.used.byme Amount of space used by the dataset itself.
+This amount exclude any space used by the children of this dataset
+or any of its snapshots.
+
+@zfs.used.bysnapshots Amount of space used by the snapshots of the dataset
+The amount of space consumed by the snapshots of this dataset.
+
+@zfs.used.bychildren Amount of space used by decendents of the dataset
+The amount of space consumed by all the decendants of this dataset.
+
+@zfs.quota Maximum amount of space a dataset can use
+Quotas are used to restrict the growth of the datasets. If
+the quota is set to 0 then the size of the dataset is limited only
+by the size of the pool which houses this dataset.
+
+@zfs.reservation Minimum amount of space guaranteed to a dataset
+The amount of space which dataset and its decendents are guaranteed
+to be available for them to use. This amount of taken off the quota
+of the parent of the dataset.
+
+@zfs.compression Compression ratio of the dataset
+Compression ratio is expressed as multiplier. To estimate how much data
+will be used by the uncompressed data multiply the amount of space used
+by the dataset by the compression ratio.
+
+@zfs.copies Number of redundant copies of data
+The number of redundant copies does not include any copies made as
+part of the pool redundancy.
+
+@zfs.recordsize Recommendend block size for files in filesystems
+By using recommended block size applications which deal with fixed size
+records can improve I/O performance.
+
+@zfs.used.byrefreservation Space used by refreservation
+The amount of space used by a refreservation set on this
+filesystem, which would be freed if the refreservation was
+removed.
+
+@zfs.refreservation Minimum amount of space guaranteed to a filesystem
+The minimum amount of space guaranteed to a dataset, not
+including its descendents. Unlike reservation refreservation is
+counted towards the total used space of a dataset.
+
+@zfs.refquota Amount of space a filesystem can consume
+The hard limit on the amount of space a filesystem but not its descendants
+can consume from the pool.
+
+@zfs.referenced Amount of space referenced by the filesystem
+The amount of data that is accessible by the filesystem. The data
+may be shared with other datasets in the pool.
+
+@zfs.nsnapshots Number of snapshots in the filesystem
+
+@zfs.snapshot.compression Compression ratio of the data in the snapshot
+Compression ratio is expressed as multiplier. To estimate how much data
+will be used by the uncompressed data multiply the amount of space used
+by the snapshot by the compression ratio.
+
+@zfs.snapshot.used Amount of space used by the snapshot
+
+@zfs.snapshot.referenced Amount of space referenced by the snapshot
+The amount of data that is accessible by the snapshot. The data
+may be shared with other datasets in the filesystem.
+
+
+@zpool.perdisk.state Current state per disk in zpool
+@zpool.perdisk.state_int vs_aux << 8 | vs_state
+@zpool.perdisk.checksum_errors Number of checksum errors per disk in zpool
+@zpool.perdisk.self_healed Number of bytes healed per disk in zpool
+@zpool.perdisk.in.errors Counter of read errors per disk in zpool
+@zpool.perdisk.out.errors Counter of write errors per disk in zpool
+
+@network.link.in.errors Number of input errors per link
+Counts input errors per link
+@network.link.in.packets Numbe of datagrams received by a link
+@network.link.in.bytes Number of bytes received by a link
+Counts number of bytes received by a link. For the physical links
+this is the raw counter of bytes received, for the aggregated links
+this is the number of bytes received by all links in the aggregation
+group
+@network.link.in.bcasts Number of broadcast datagrams received by a link
+@network.link.in.mcasts Number of multicast datagrams
+Counts multicast datagram recieved by a link.
+@network.link.in.nobufs Number of inpit packets discared
+Counts number of packets discared because of failure to allocate buffers
+@network.link.out.errors Number of output errors per link
+@network.link.out.packets Number of packets sent from a link
+@network.link.out.bytes Number of bytes sent from a link
+@network.link.out.bcasts Number of broadcast datagrams sent from a link
+@network.link.out.mcasts Number of multicast datagrams sent from a link
+@network.link.out.nobufs Number of output packets discared
+Counts number of packets discared because of failure to allocate buffers
+@network.link.collisions Number of collisions detected per link
+@network.link.state Link state
+1 - Link is up, 2 - Link is down, 0 - unknown state
+@network.link.duplex Link duplex
+1 - Half duplex, 2 - Full duplex
+@network.link.speed Link speed in bytes per second
+@hinv.pagesize Memory page size
+The memory page size of the running kernel in bytes.
+@hinv.physmem Total physical system memory
+Total physical system memory size rounded down to the nearest page size
+boundary
+@pmda.uname identity and type of current system
+Identity and type of current system. The concatenation of the values
+returned from utsname(2), also similar to uname -a.
+@kernel.fsflush.scanned Number of pages scanned by fsflush daemon
+@kernel.fsflush.examined Number of pages examined by fsflush daemon
+@kernel.fsflush.coalesced Number of pages coalesced into larger page
+@kernel.fsflush.modified Number of modified pages written to disk
+@kernel.fsflush.locked Number of pages locked by fsflush daemon
+Pages which were considered to be on interest for further examination
+are locked before deciding if they could be coalesced, released or flushed
+to disk.
+@kernel.fsflush.released Number of free pages released by fsflush daemon
+@kernel.fsflush.time Amount of time fsflush daemon spent doing its work
+@mem.physmem Total physical system memory
+Total physical system memory size rounded down to the nearest page size
+boundary. This metric is the same as hinv.physmem but uses different
+units.
+@mem.freemem Amount of free memory in the system
+@mem.lotsfree Paging theshold
+If freemem fails below the lostfree threshold then paging out daemon
+starts its activity. Default value for lotsfree is 1/64 of physical memory
+or 512K (which ever is larger).
+@mem.availrmem Amount of resident memory in the system
+
+@kernel.all.io.bread Physical block reads across all CPUs
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.all.io.bwrite Physical block writes across all CPUs
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.all.io.lread Logical block reads across all CPUs
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.all.io.lwrite Logical block writes across all CPUs
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.all.io.phread Raw I/O reads across all CPUs
+@kernel.all.io.phwrite Raw I/O writes across all CPUs
+@kernel.all.io.intr Device interrupts across all CPUs
+
+@kernel.percpu.io.bread Physical block reads
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.percpu.io.bwrite Physical block writes
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.percpu.io.lread Logical block reads
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.percpu.io.lwrite Logical block writes
+This metric is only updated if reading or writing to UFS mounted filesystems,
+reads and writes to ZFS do not update this metric.
+@kernel.percpu.io.phread Raw I/O reads
+@kernel.percpu.io.phwrite Raw I/O writes
+@kernel.percpu.io.intr Device interrupts
+
+@hinv.ncpu Number of CPUs in the system
+@hinv.ndisk Number of disks in the system
+
+@kernel.all.trap Traps across all CPUs
+@kernel.all.pswitch Context switches across all CPUs
+@kernel.all.syscall Total number of system calls across all CPUs
+@kernel.all.sysexec Total number of calls from exec(2) family across all CPUs
+@kernel.all.sysfork Total number of new processes created across all CPUs
+@kernel.all.sysvfork Total number of new processes created across all CPUs
+Unlike fork vfork does not copy all the virtual memory of the parent
+process into the child process and is mostly used to create new system context
+for execve(2). vfork(2) calls are not counted towards kernel.all.sysfork.
+@kernel.all.sysread Total number of system calls from read(2) family across all CPUs
+@kernel.all.syswrite Total number of system calls from write (2) family across all CPUs
+
+@kernel.percpu.trap Traps on each CPUs
+@kernel.percpu.pswitch Context switches on each CPUs
+@kernel.percpu.syscall Total number of system calls on each CPU
+@kernel.percpu.sysexec Total number of calls from exec(2) family on each CPU
+@kernel.percpu.sysfork Total number of new processes created on each CPU
+@kernel.percpu.sysvfork Total number of new processes created on each CPU
+Unlike fork vfork does not copy all the virtual memory of the parent
+process into the child process and is mostly used to create new system context
+for execve(2). vfork(2) calls are not counted towards kernel.percpu.sysfork.
+@kernel.percpu.sysread Total number of system calls from read(2) family on each CPU
+@kernel.percpu.syswrite Total number of system calls from write (2) family on each CPU
+
+@kernel.all.load Classic load average for 1, 5 and 15 minute intervals
+
+@kernel.all.cpu.wait.io Time spent waiting for I/O across all CPUs
+This metric is not updated by OpenSolaris kernel.
+@kernel.all.cpu.wait.pio Time spent wait for polled I/O across all CPUs
+This metric is not updated by OpenSolaris kernel.
+@kernel.all.cpu.wait.swap Time spent wait for swap across all CPUs
+This metric is not updated by OpenSolaris kernel.
+@kernel.percpu.cpu.wait.io Time spent waiting for I/O on per-CPU basis
+This metric is not updated by OpenSolaris kernel.
+@kernel.percpu.cpu.wait.pio Time spent waiting for polled I/O on per-CPU basis
+This metric is not updated by OpenSolaris kernel.
+@kernel.percpu.cpu.wait.swap Time spent waiting swap on per-CPU basis
+This metric is not updated by OpenSolaris kernel.
+
+@zfs.arc.size Total amount of memory used by ZFS ARC
+@zfs.arc.min_size Lower limit of them amount of memory for ZFS ARC
+@zfs.arc.max_size Upper limit of the amount of memory for ZFS ARC
+The default is to use 7/8 of total physical memory.
+@zfs.arc.mru_size Amount of memory used by the most recently used pages
+@zfs.arc.target_size "Ideal" size of the cached based on aging
+@zfs.arc.hits.total Number of times data is found in the cache
+@zfs.arc.hits.mfu Number of times data is found in the most frequently used buffers
+@zfs.arc.hits.mru Number of times data is found in the most recently used buffers
+@zfs.arc.hits.mfu_ghost Number of times MFU ghost buffer is accessed
+A ghost buffer is a buffer which is no longer cached but is still
+linked into the hash.
+@zfs.arc.hits.mru_ghost Number of times MRU ghost buffer is accessed
+A ghost buffer is a buffer which is no longer cached but is still
+linked into the hash.
+@zfs.arc.hits.demand_data Number of times file data is found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.hits.demand_metadata Number of times filesystem metadata is found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.hits.prefetch_data Number of times speculative request for data is satisfied from the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.hits.prefetch_metadata Number of times speculative request for metadata is satisfied from the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.misses.total Number of times the data is not found in the cache
+@zfs.arc.misses.demand_data Number of times file data is not found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.misses.demand_metadata Number of times filesystem metadata is not found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.misses.prefetch_data Number of times speculatively accessed file data is not found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@zfs.arc.misses.prefetch_metadata Number of times speculatively accessed filesystem metadata is not found in the cache
+ARC statistics provide separate counters for demand vs prefetch
+and data vs metadata accesses: demand is a result of the direct
+request for a particular data, prefetch is a result of speculative
+request for a particular data.
+@pmda.prefetch.time Amount of time spent extracting information about a group of metrics
+Each metric belongs to a prefetch group. When a client asks for the metric
+to be fetched the information for the group must be extracted from the kernel.
+@pmda.prefetch.count Number of times each group of metrics was updated
+
+@pmda.metric.time Amount of time spent extracting information about individual metrics
+Requesting multiple instances of the same metrics counts against the metric
+itself and not against the individual instances
+@pmda.metric.count Number of times individual metrics have been fetched
+Requesting multiple instances of the same metrics counts as multiple hits
+against the metric itself
+
+@disk.all.wait.time Amount of time IO requests spent waiting for service
+Amount of time IO transactions spent waiting to be serviced, i.e. the
+transaction has been accepted for processing but for which the processing
+has not yet begun. Each transaction waiting for processing adds to
+to the total time which means that if multiple transactions are waiting then
+total time for the sampling interval may be larger then the interval.
+
+@disk.dev.wait.time Amount of time IO requests spent waiting for service
+Amount of time IO transactions spent waiting to be serviced, i.e. the
+transaction has been accepted for processing but for which the processing
+has not yet begun. Each transaction waiting for processing adds to
+to the total time which means that if multiple transactions are waiting then
+total time for the sampling interval may be larger then the interval.
+
+@disk.all.wait.count Number of transactions waiting to be serviced
+Number of transactions accepted for processing but for which the processing
+has not yet begun.
+@disk.dev.wait.count Number of transactions waiting to be serviced
+Number of transactions accepted for processing but for which the processing
+has not yet begun.
+
+@disk.all.run.time Amount of time spent processing IO requests
+@disk.dev.run.time Amount of time spent processing IO requests
+@disk.all.run.count Number of transactions being processed
+@disk.dev.run.count Number of transactions being processed
+
+
+# from i86pc/os/cpuid.c
+# /*
+# * 8bit APIC IDs on dual core Pentiums
+# * look like this:
+# *
+# * +-----------------------+------+------+
+# * | Physical Package ID | MC | HT |
+# * +-----------------------+------+------+
+# * <------- chipid -------->
+# * <------- coreid --------------->
+# * <--- clogid -->
+# * <------>
+# * pkgcoreid
+# *
+# * Where the number of bits necessary to
+# * represent MC and HT fields together equals
+# * to the minimum number of bits necessary to
+# * store the value of cpi->cpi_ncpu_per_chip.
+# * Of those bits, the MC part uses the number
+# * of bits necessary to store the value of
+# * cpi->cpi_ncore_per_chip.
+# */
+#
+@hinv.cpu.brand Marketing name of CPU
+@hinv.cpu.clock Current CPU clock frequency
+On CPUs which support dynamic clock frequency changes current clock frequency
+could differ from the nominal ("maximum") clock frequency specified by
+the manufacturer.
+@hinv.cpu.maxclock Maximum clock frequency supported by CPU
+Nominal CPU clock frequency as specified by the manufacturer.
+@hinv.cpu.frequencies List of clock frequencies supported by CPU
+@hinv.cpu.implementation Details of CPU implementation
+@hinv.cpu.chip_id Chip or Socket identifier of the CPU
+Logical CPUs can share single chip identifier
+@hinv.cpu.clog_id Logical core identifier
+Logical cores identifier combines identifiers of the CPU core and
+virtual CPU identifier (aka hyperthread identifier).
+@hinv.cpu.core_id CPU core identifier
+CPU core identifire combines chip identifier and per-chip core
+identifier. If cores support more the one virtual CPU per core
+then same core identifier is shared across several virtual
+CPUs.
+@hinv.cpu.pkg_core_id Per-chip core identifier
+This identifier is used to identify individual cores within the
+package. If a core support more the one virtual CPU per core
+then same core identifier is shared across several virtual
+CPUs.
+@hinv.cpu.cstate Current CPU idle state
+@hinv.cpu.maxcstates Maximum number of idle state supported by the CPU
+Information about cstate is available in kstat(1m).
+@hinv.cpu.ncores Number of CPU cores per physical chip
+@hinv.cpu.ncpus Number of virtual CPUs per physical chip
+
+@disk.dev.errors.soft Number of soft errors per device
+@disk.dev.errors.hard Number of hard errors per device
+@disk.dev.errors.transport Number of transport errors per device
+@disk.dev.errors.media Number of media errors per device
+@disk.dev.errors.recoverable Number of recoverable errors per device
+@disk.dev.errors.notready Number of times device reported as not ready
+@disk.dev.errors.nodevice Number of times device was found missing
+@disk.dev.errors.badrequest Number of illegal requests per device
+@disk.dev.errors.pfa Number of times failure prediction threshold has been exceeded
+@hinv.disk.vendor Device Vendor
+Can be reported as ATA if SATA device is behind SAS expander
+@hinv.disk.product Device name
+Vendor's device name (up-to 16 characters long)
+@hinv.disk.revision Device Revision
+@hinv.disk.serial Device Serial Number
+@hinv.disk.capacity Device Capacity
+For removable devices capacity of the media is reported.
+
+@kernel.fs.vnops.access Number of VOP_ACCESS calls per filesystem
+VOP_ACCESS is used by access(2) system call.
+@kernel.fs.vnops.addmap Number of VOP_ADDMAP calls per filesystem
+VOP_ADDMAP is used to manage reference counting of the vnode used by
+mmap(2) operations.
+@kernel.fs.vnops.close Number of VOP_CLOSE calls per filesystem
+VOP_CLOSE is called every time a close(2) system call is called
+@kernel.fs.vnops.cmp Number of VOP_CMP calls per filesystem
+VOP_CMP is used to check if two vnodes are "equal" to each other, i.e.
+both refer to the same filesystem object.
+@kernel.fs.vnops.create Number of VOP_CREATE calls per filesystem
+VOP_CREATE is used to create regular files and device or FIFO nodes.
+@kernel.fs.vnops.delmap Number of VOP_DELMAP calls
+VOP_DELMAP is used to destroy a previously created memory-mapped region
+of a file.
+@kernel.fs.vnops.dispose Number ot VOP_DISPOSE calls per filesystem
+VOP_DISPOSE is used to dispose(free or invalidate) of a page associated
+with a file.
+@kernel.fs.vnops.dump Number of VOP_DUMP calls per filesystem
+VOP_DUMP is used to transfer data from the frozen kernel directly
+to the dump device
+@kernel.fs.vnops.dumpctl Number of VOP_DUMPCTL calls per filesystem
+VOP_DUMPCTL sets up context used by VOP_DUMP call. It is used to
+allocate, free or search for data blocks on the dump device.
+@kernel.fs.vnops.fid Number of VOP_FID calls per filesystem
+VOP_FID is used to get file identifier which can be used instead of the
+file name in some operations. NFS server is one known user of this vnode
+operation.
+@kernel.fs.vnops.frlock Number of VOP_FRLOCK calls per filesystem
+VOP_FRLOCK is used to implement file record locking used by flock(2)
+@kernel.fs.vnops.fsync Number of VOP_FSYNC calls per filesystem
+VOP_FSYNC is used to implement fsync(2) system call which flushes
+data for a specific file to disk.
+@kernel.fs.vnops.getattr Number of VOP_GETATTR calls per filesystem
+VOP_GETATTR is used to extract vnode attributes. It use used as part of
+many system calls which manipulate file attributes, e.g. chmod(2), stat(2),
+utimes(2) etc.
+@kernel.fs.vnops.getpage Number of VOP_GETPAGE calls per filesystem
+VOP_GETPAGE is used to allocate pages (could be several at a time) to cover
+a region in a file.
+@kernel.fs.vnops.getsecattr Number of VOP_GETSECATTR calls per filesystem
+VOP_GETSECATTR used to extract ACL entires associated with a file.
+@kernel.fs.vnops.inactive Number of VOP_INACTIVE calls per filesystem
+VOP_INACTIVE is used to destroy vnode before it is removed from the
+cache or reused.
+@kernel.fs.vnops.ioctl Number of VOP_IOCTL calls per filesystem
+VOP_IOCTL is used to implement ioctl(2) system call.
+@kernel.fs.vnops.link Number of VOP_LINK calls per filesystem
+VOP_LINK is used to implement support for hard links
+@kernel.fs.vnops.lookup Number of VOP_LOOKUP calls per filesystem
+VOP_LOOKUP is used to translate filename to vnode.
+@kernel.fs.vnops.map Number of VOP_MAP calls per filesystem
+VOP_MAP is used to create a new memory-mapped region of a file
+@kernel.fs.vnops.mkdir Number of VOP_MKDIR calls per filesystem
+VOP_MKDIR is used to create directories
+@kernel.fs.vnops.open Number of VOP_OPEN calls per filesystem
+VOP_OPEN is called every time open(2) system call is called.
+@kernel.fs.vnops.pageio Number of VOP_PAGEIO calls per filesystem
+VOP_PAGEIO is similar to VOP_GETPAGE and VOP_PUTPAGE and can be used when
+either of the other two are less efficient, e.g. in the case when pages
+will be reused after the IO is done.
+@kernel.fs.vnops.pathconf Number of VOP_PATHCONF calls per filesystem
+VOP_PATHCONF is used to obtain information about filesystem's parameters
+reported by pathconf(2) system call
+@kernel.fs.vnops.poll Number of VOP_POLL calls per filesystem
+VOP_POLL is used to implement pool(2) system call
+@kernel.fs.vnops.putpage Number of VOP_PUTPAGE calls per filesystem
+VOP_PUTPAGE is used to release pages which have been used to hold
+data from a file
+@kernel.fs.vnops.read Number of VOP_READ calls per filesystem
+VOP_READ is used to implement read(2) system call
+@kernel.fs.vnops.readdir Number of VOP_READDIR calls per filesystem
+VOP_READDIR is used to read directory entries
+@kernel.fs.vnops.readlink Number of VOP_READLINK calls per filesystem
+VOP_READLINK is used to read the information about the target of the symbolic
+link
+@kernel.fs.vnops.realvp Number of VOP_REALVP calls per filesystem
+VOP_REALVP is used to traverse stacking filesystems and extract information
+about the vnode which refers to the "real" filesystem object.
+@kernel.fs.vnops.remove Number of VOP_REMOVE calls per filesystem
+VOP_REMOVE is used to remove entires from a directory.
+@kernel.fs.vnops.rename Number of VOP_RENAME calls per filesystem
+VOP_RENAME is used to implement rename(2) system call
+@kernel.fs.vnops.rmdir Number of VOP_RMDIR calls per filesystem
+VOP_RMDIR is used to implement rmdir(2) system call
+@kernel.fs.vnops.rwlock Number of VOP_RWLOCK calls per filesystem
+VOP_RWLOCK and VOP_RWUNLOCK are used to protect access to vnode data.
+@kernel.fs.vnops.rwunlock Number of VOP_RWUNLOCK calls per filesystem
+VOP_RWLOCK and VOP_RWUNLOCK are used to protect access to vnode data.
+@kernel.fs.vnops.seek Number of VOP_SEEK calls per filesystem
+VOP_SEEK is used by lseek(2). Because vnodes can be shared across multiple
+instances of vfile VOP_SEEK does not usually change the position of the
+file pointer, it instead used to verify the offset before it is changed.
+@kernel.fs.vnops.setattr Number of VOP_SETATTR calls per filesystem
+VOP_SETATTR is used to change vnode attributes which are modified by system
+calls like chmod(2), chown(2), utimes(2) etc.
+@kernel.fs.vnops.setfl Number of VOP_SETFL calls per filesystem
+VOP_SETFL is used to implement fcntl(2) F_SETFL option.
+Currently only sockfs pseudo filesystem is implementing this vnode operation.
+@kernel.fs.vnops.setsecattr Number of VOP_SETSECATTR calls per filesystem
+VOP_SETSECATTR is used to change ACL entries
+@kernel.fs.vnops.shrlock Number of VOP_SHRLOCK calls per filesystem
+VOP_SHRLOCK is usually used to implement CIFS and NLMv3 shared reservations.
+@kernel.fs.vnops.space Number of VOP_SPACE calls per filesystem
+VOP_SPACE is used to provide optimized support for growing and shrinking the files.
+F_FREESP option of fcntl(2) is using this vnode operation to implment ftruncate(3c)
+function.
+@kernel.fs.vnops.symlink Number of VOP_SYMLINK calls per filesystem
+VOP_SYMLINK is used to create symbolic links.
+@kernel.fs.vnops.vnevent Number of VOP_VNEVENT calls per filesystem
+VIP_VNEVENT is used to check if a filesystem support vnode event
+notifications for operations which change the names of the files.
+@kernel.fs.vnops.write Number of VOP_WRITE calls per filesystem
+VOP_WRITE is used to implement write(2) system call
+@kernel.fs.read_bytes Number of bytes read from a specific filesystem
+@kernel.fs.readdir_bytes Number of bytes containting directory entires read from a specific filesystem
+@kernel.fs.write_bytes Number of bytes written to a specific filesystem
+
+@kernel.fstype.vnops.access Number of VOP_ACCESS calls for all filesystems of a given type
+VOP_ACCESS is used by access(2) system call.
+@kernel.fstype.vnops.addmap Number of VOP_ADDMAP calls for all filesystems of a given type
+VOP_ADDMAP is used to manage reference counting of the vnode used by
+mmap(2) operations.
+@kernel.fstype.vnops.close Number of VOP_CLOSE calls for the specific filesystem
+VOP_CLOSE is called every time a close(2) system call is called
+@kernel.fstype.vnops.cmp Number of VOP_CMP calls for the specific filesystem
+VOP_CMP is used to check if two vnodes are "equal" to each other, i.e.
+both refer to the same filesystem object.
+@kernel.fstype.vnops.create Number of VOP_CREATE calls for all filesystems of a given type
+VOP_CREATE is used to create regular files and device or FIFO nodes.
+@kernel.fstype.vnops.delmap Number of VOP_DELMAP was called
+VOP_DELMAP is used to destroy a previously created memory-mapped region
+of a file.
+@kernel.fstype.vnops.dispose Number ot VOP_DISPOSE calls for all filesystems of a given type
+VOP_DISPOSE is used to dispose(free or invalidate) of a page associated
+with a file.
+@kernel.fstype.vnops.dump Number of VOP_DUMP calls for all filesystems of a given type
+VOP_DUMP is used to transfer data from the frozen kernel directly
+to the dump device
+@kernel.fstype.vnops.dumpctl Number of VOP_DUMPCTL calls for all filesystems of a given type
+VOP_DUMPCTL sets up context used by VOP_DUMP call. It is used to
+allocate, free or search for data blocks on the dump device.
+@kernel.fstype.vnops.fid Number of VOP_FID calls for all filesystems of a given type
+VOP_FID is used to get file identifier which can be used instead of the
+file name in some operations. NFS server is one known user of this vnode
+operation.
+@kernel.fstype.vnops.frlock Number of time VOP_FRLOCK calls for all filesystems of a given type
+VOP_FRLOCK is used to implement file record locking used by flock(2)
+@kernel.fstype.vnops.fsync Number of VOP_FSYNC calls for all filesystems of a given type
+VOP_FSYNC is used to implement fsync(2) system call which flushes
+data for a specific file to disk.
+@kernel.fstype.vnops.getattr Number of VOP_GETATTR calls for all filesystems of a given type
+VOP_GETATTR is used to extract vnode attributes. It use used as part of
+many system calls which manipulate file attributes, e.g. chmod(2), stat(2),
+utimes(2) etc.
+@kernel.fstype.vnops.getpage Number of VOP_GETPAGE calls for all filesystems of a given type
+VOP_GETPAGE is used to allocate pages (could be several at a time) to cover
+a region in a file.
+@kernel.fstype.vnops.getsecattr Number of VOP_GETSECATTR calls for all filesystems of a given type
+VOP_GETSECATTR used to extract ACL entires associated with a file.
+@kernel.fstype.vnops.inactive Number of VOP_INACTIVE calls for all filesystems of a given type
+VOP_INACTIVE is used to destroy vnode before it is removed from the
+cache or reused.
+@kernel.fstype.vnops.ioctl Number of VOP_IOCTL calls for all filesystems of a given type
+VOP_IOCTL is used to implement ioctl(2) system call.
+@kernel.fstype.vnops.link Number of VOP_LINK calls for all filesystems of a given type
+VOP_LINK is used to implement support for hard links
+@kernel.fstype.vnops.lookup Number of VOP_LOOKUP calls for all filesystems of a given type
+VOP_LOOKUP is used to translate filename to vnode.
+@kernel.fstype.vnops.map Number of VOP_MAP calls for all filesystems of a given type
+VOP_MAP is used to create a new memory-mapped region of a file
+@kernel.fstype.vnops.mkdir Number of VOP_MKDIR calls for all filesystems of a given type
+VOP_MKDIR is used to create directories
+@kernel.fstype.vnops.open Number of VOP_OPEN calls for all filesystems of a given type
+VOP_OPEN is called every time open(2) system call is called.
+@kernel.fstype.vnops.pageio Number of VOP_PAGEIO calls for all filesystems of a given type
+VOP_PAGEIO is similar to VOP_GETPAGE and VOP_PUTPAGE and can be used when
+either of the other two are less efficient, e.g. in the case when pages
+will be reused after the IO is done.
+@kernel.fstype.vnops.pathconf Number of VOP_PATHCONF calls for all filesystems of a given type
+VOP_PATHCONF is used to obtain information about filesystem's parameters
+reported by pathconf(2) system call
+@kernel.fstype.vnops.poll Number of VOP_POLL calls for all filesystems of a given type
+VOP_POLL is used to implement pool(2) system call
+@kernel.fstype.vnops.putpage Number of VOP_PUTPAGE calls for all filesystems of a given type
+VOP_PUTPAGE is used to release pages which have been used to hold
+data from a file
+@kernel.fstype.vnops.read Number of VOP_READ calls for all filesystems of a given type
+VOP_READ is used to implement read(2) system call
+@kernel.fstype.vnops.readdir Number of VOP_READDIR calls for all filesystems of a given type
+VOP_READDIR is used to read directory entries
+@kernel.fstype.vnops.readlink Number of VOP_READLINK calls for all filesystems of a given type
+VOP_READLINK is used to read the information about the target of the symbolic
+link
+@kernel.fstype.vnops.realvp Number of VOP_REALVP calls for all filesystems of a given type
+VOP_REALVP is used to traverse stacking filesystems and extract information
+about the vnode which refers to the "real" filesystem object.
+@kernel.fstype.vnops.remove Number of VOP_REMOVE calls for all filesystems of a given type
+VOP_REMOVE is used to remove entires from a directory.
+@kernel.fstype.vnops.rename Number of VOP_RENAME calls for all filesystems of a given type
+VOP_RENAME is used to implement rename(2) system call
+@kernel.fstype.vnops.rmdir Number of VOP_RMDIR calls for all filesystems of a given type
+VOP_RMDIR is used to implement rmdir(2) system call
+@kernel.fstype.vnops.rwlock Number of VOP_RWLOCK calls for all filesystems of a given type
+VOP_RWLOCK and VOP_RWUNLOCK are used to protect access to vnode data.
+@kernel.fstype.vnops.rwunlock Number of VOP_RWUNLOCK calls for all filesystems of a given type
+VOP_RWLOCK and VOP_RWUNLOCK are used to protect access to vnode data.
+@kernel.fstype.vnops.seek Number of VOP_SEEK calls for all filesystems of a given type
+VOP_SEEK is used by lseek(2). Because vnodes can be shared across multiple
+instances of vfile VOP_SEEK does not usually change the position of the
+file pointer, it instead used to verify the offset before it is changed.
+@kernel.fstype.vnops.setattr Number of VOP_SETATTR calls for all filesystems of a given type
+VOP_SETATTR is used to change vnode attributes which are modified by system
+calls like chmod(2), chown(2), utimes(2) etc.
+@kernel.fstype.vnops.setfl Number of VOP_SETFL calls for all filesystems of a given type
+VOP_SETFL is used to implement fcntl(2) F_SETFL option.
+Currently only sockfs pseudo filesystem is implementing this vnode operation.
+@kernel.fstype.vnops.setsecattr Number of VOP_SETSECATTR calls for all filesystems of a given type
+VOP_SETSECATTR is used to change ACL entries
+@kernel.fstype.vnops.shrlock Number of VOP_SHRLOCK calls for all filesystems of a given type
+VOP_SHRLOCK is usually used to implement CIFS and NLMv3 shared reservations.
+@kernel.fstype.vnops.space Number of VOP_SPACE calls for all filesystems of a given type
+VOP_SPACE is used to provide optimized support for growing and shrinking the files.
+F_FREESP option of fcntl(2) is using this vnode operation to implment ftruncate(3c)
+function.
+@kernel.fstype.vnops.symlink Number of VOP_SYMLINK calls for all filesystems of a given type
+VOP_SYMLINK is used to create symbolic links.
+@kernel.fstype.vnops.vnevent Number of VOP_VNEVENT calls for all filesystems of a given type
+VIP_VNEVENT is used to check if a filesystem support vnode event
+notifications for operations which change the names of the files.
+@kernel.fstype.vnops.write Number of VOP_WRITE calls for all filesystems of a given type
+VOP_WRITE is used to implement write(2) system call
+@kernel.fstype.read_bytes Bytes read from all filesystems of a given type
+@kernel.fstype.readdir_bytes Bytes read for directory entries from all filesystems of a given type
+@kernel.fstype.write_bytes Bytes written to all filesystems of a given type
+
+@hinv.disk.devlink Disk name in the descriptive format
+Solaris uses symbolic links under /dev to provide access to device nodes via
+"descriptive" names like /dev/dsk/cXtYdZsN. This metrics provides a
+translation from a "descriptive" name to instances in the disk instance
+domain.
+
+The name is always the name of the first minor device for a particular disk
+and includes the slice information.
+
+NOTE! Fetching this metric is expensive - several system calls are made
+ to fetch each instance.
diff --git a/src/pmdas/solaris/kvm.c b/src/pmdas/solaris/kvm.c
new file mode 100644
index 0000000..5aa674a
--- /dev/null
+++ b/src/pmdas/solaris/kvm.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <kvm.h>
+#include <nlist.h>
+
+#include "common.h"
+
+
+static kvm_t *kvm;
+struct nlist kvm_names[] = {
+ {.n_name = "fsf_total"},
+ {.n_name = NULL}
+};
+
+fsf_stat_t s = {0};
+static int fresh;
+
+void
+kvm_init(int ignore)
+{
+ kvm = kvm_open(NULL, NULL, NULL, O_RDONLY, "pmdasolaris");
+ if (kvm && kvm_nlist(kvm, kvm_names))
+ fprintf(stderr, "Cannot extract addresses\n");
+}
+
+void
+kvm_refresh(void)
+{
+ fresh = kvm &&
+ (kvm_kread(kvm, kvm_names[0].n_value, &s, sizeof(s)) == sizeof(s));
+}
+
+int
+kvm_fetch(pmdaMetric *pm, int inst, pmAtomValue *v)
+{
+ metricdesc_t *md = pm->m_user;
+ char *p = (char *)&s;
+
+ if (!fresh)
+ return 0;
+
+ memcpy(&v->ull, p + md->md_offset, sizeof(v->ull));
+ return 1;
+}
diff --git a/src/pmdas/solaris/netlink.c b/src/pmdas/solaris/netlink.c
new file mode 100644
index 0000000..d3b61bf
--- /dev/null
+++ b/src/pmdas/solaris/netlink.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Extract per-link network information via kstat.
+ *
+ * Link stats are in the sections called "link" and the stat name
+ * is the same as the link names */
+
+#include <kstat.h>
+#include "common.h"
+
+int
+netlink_fetch(pmdaMetric *pm, int inst, pmAtomValue *av)
+{
+ char *lname;
+ metricdesc_t *md = pm->m_user;
+ kstat_t *k;
+ char *stat = (char *)md->md_offset;
+
+ if (pmdaCacheLookup(indomtab[NETLINK_INDOM].it_indom, inst, &lname,
+ (void **)&k) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (k) {
+ kstat_named_t *kn = kstat_data_lookup(k, stat);
+
+ if (kn == NULL) {
+ fprintf(stderr, "No kstat called %s for %s\n", stat, lname);
+ return 0;
+ }
+
+ switch (pm->m_desc.type) {
+ case PM_TYPE_32:
+ if (kn->data_type == KSTAT_DATA_INT32) {
+ av->l = kn->value.i32;
+ return 1;
+ }
+ break;
+ case PM_TYPE_U32:
+ if (kn->data_type == KSTAT_DATA_UINT32) {
+ av->ul = kn->value.ui32;
+ return 1;
+ }
+ break;
+ case PM_TYPE_64:
+ if (kn->data_type == KSTAT_DATA_INT64) {
+ av->ll = kn->value.i64;
+ return 1;
+ }
+ break;
+ case PM_TYPE_U64:
+ if (kn->data_type == KSTAT_DATA_UINT64) {
+ av->ull = kn->value.ui64;
+ return 1;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void
+netlink_update_stats(int fetch)
+{
+ kstat_t *k;
+ kstat_ctl_t *kc;
+ pmInDom indom = indomtab[NETLINK_INDOM].it_indom;
+
+ if ((kc = kstat_ctl_update()) == NULL)
+ return;
+
+ for (k = kc->kc_chain; k != NULL; k = k->ks_next) {
+ if (strcmp(k->ks_module, "link") == 0) {
+ int rv;
+ kstat_t *cached;
+
+ if (pmdaCacheLookupName(indom, k->ks_name, &rv,
+ (void **)&cached) != PMDA_CACHE_ACTIVE) {
+ rv = pmdaCacheStore(indom, PMDA_CACHE_ADD, k->ks_name, k);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot create instance for "
+ "network link '%s': %s\n",
+ k->ks_name, pmErrStr(rv));
+ continue;
+ }
+ }
+
+ if (fetch)
+ kstat_read(kc, k, NULL);
+ }
+ }
+}
+
+void
+netlink_refresh(void)
+{
+ pmdaCacheOp(indomtab[NETLINK_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ netlink_update_stats(1);
+ pmdaCacheOp(indomtab[NETLINK_INDOM].it_indom, PMDA_CACHE_SAVE);
+}
+
+void
+netlink_init(int first)
+{
+ pmdaCacheOp(indomtab[NETLINK_INDOM].it_indom, PMDA_CACHE_LOAD);
+ netlink_update_stats(0);
+ pmdaCacheOp(indomtab[NETLINK_INDOM].it_indom, PMDA_CACHE_SAVE);
+}
diff --git a/src/pmdas/solaris/netmib2.c b/src/pmdas/solaris/netmib2.c
new file mode 100644
index 0000000..aab417a
--- /dev/null
+++ b/src/pmdas/solaris/netmib2.c
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/* Extract network-related information from the kernel using MIB2
+ * interfaces. MIB2 structures are described by RFC 4113, 4293,
+ * 4001. IPv6 specific MIB structures are described in RFC 2465, 2466.
+ */
+
+#include <fcntl.h>
+#include <stropts.h>
+#include <inet/mib2.h>
+#include <sys/tihdr.h>
+#include <sys/socket.h>
+#include <net/if.h>
+
+#include "common.h"
+#include "netmib2.h"
+
+static int afd = -1;
+static int data_valid;
+static int netif_added;
+
+nm2_udp_stats_t nm2_udp;
+
+static nm2_netif_stats_t *
+netif_cache_inst(const char *ifname)
+{
+ pmInDom indom = indomtab[NETIF_INDOM].it_indom;
+ nm2_netif_stats_t *ist;
+ int rv;
+
+ if (pmdaCacheLookupName(indom, ifname, &rv,
+ (void **)&ist) != PMDA_CACHE_ACTIVE) {
+ ist = malloc(sizeof(*ist));
+ if (ist == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "Out of memory for stats on network interface '%s'\n",
+ ifname);
+ return NULL;
+ }
+
+ rv = pmdaCacheStore(indom, PMDA_CACHE_ADD, ifname, ist);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot create instance for '%s': %s\n",
+ ifname, pmErrStr(rv));
+ free(ist);
+ return NULL;
+ }
+ netif_added++;
+ }
+
+ return ist;
+}
+
+static void
+ipv4_stats (const void *data, int sz)
+{
+ const mib2_ipAddrEntry_t *ipa = data;
+
+ while (sz > 0) {
+ nm2_netif_stats_t *ist = netif_cache_inst(ipa->ipAdEntIfIndex.o_bytes);
+
+ if (ist) {
+ ist->mtu = ipa->ipAdEntInfo.ae_mtu;
+ /* We get byte count and other stuff from Traffic stats */
+ }
+ sz -= sizeof(*ipa);
+ ipa++;
+ }
+}
+
+static void
+ipv4_ifstats(const void *data, int sz)
+{
+ const mib2_ipIfStatsEntry_t *ips = data;
+
+ nm2_udp.noports = 0;
+ nm2_udp.overflows = 0;
+
+ while (sz > 0) {
+ /* index 0 is a pseudo-interface */
+ if (ips->ipIfStatsIfIndex) {
+ nm2_netif_stats_t *ist;
+ char name[64];
+
+ if ((if_indextoname(ips->ipIfStatsIfIndex, name) != NULL) &&
+ ((ist = netif_cache_inst(name)) != NULL)) {
+
+ ist->ibytes = ips->ipIfStatsHCInOctets;
+ ist->obytes = ips->ipIfStatsHCOutOctets;
+ ist->ipackets = ips->ipIfStatsHCInReceives;
+ ist->opackets = ips->ipIfStatsHCOutTransmits;
+ ist->imcast = ips->ipIfStatsHCInMcastPkts;
+ ist->omcast = ips->ipIfStatsHCOutMcastPkts;
+ ist->ibcast = ips->ipIfStatsHCInBcastPkts;
+ ist->obcast = ips->ipIfStatsHCOutBcastPkts;
+ ist->delivered = ips->ipIfStatsHCInDelivers;
+ ist->idrops = ips->ipIfStatsInDiscards;
+ ist->odrops = ips->ipIfStatsOutDiscards;
+ ist->ierrors =
+ + (uint64_t)ips->ipIfStatsInHdrErrors
+ + ips->ipIfStatsInTooBigErrors
+ + ips->ipIfStatsInNoRoutes
+ + ips->ipIfStatsInAddrErrors
+ + ips->ipIfStatsInUnknownProtos
+ + ips->ipIfStatsInTruncatedPkts;
+
+ ist->oerrors = ips->ipIfStatsOutFragFails;
+ }
+ }
+
+ nm2_udp.noports += ips->udpNoPorts;
+ nm2_udp.overflows += ips->udpInOverflows;
+
+ sz -= sizeof(*ips);
+ ips++;
+ }
+}
+
+void
+netmib2_refresh(void)
+{
+ struct strbuf ctrl;
+ struct opthdr *oh;
+ uint64_t buf[64]; /* Arbitrary size, just large enough to fit req + opthdr */
+ struct T_optmgmt_req *omreq = (struct T_optmgmt_req *)buf;
+ struct T_optmgmt_ack *omack = (struct T_optmgmt_ack *)buf;
+
+ omreq->PRIM_type = T_SVR4_OPTMGMT_REQ;
+ omreq->OPT_offset = sizeof (*omreq);
+ omreq->OPT_length = sizeof (*oh);
+ omreq->MGMT_flags = T_CURRENT;
+
+ oh = (struct opthdr *)(omreq + 1);
+ oh->level = /*EXPER_IP_AND_TESTHIDDEN*/MIB2_IP;
+ oh->name = 0;
+ oh->len = 0;
+
+ ctrl.buf = (char *)buf;
+ ctrl.len = omreq->OPT_length + omreq->OPT_offset;
+
+ data_valid = 0;
+
+ if (putmsg(afd, &ctrl, NULL, 0) == -1) {
+ __pmNotifyErr(LOG_ERR, "Failed to push message down stream: %s\n",
+ osstrerror());
+ return;
+ }
+
+ oh = (struct opthdr *)(omack + 1);
+ ctrl.maxlen = sizeof(buf);
+
+ netif_added = 0;
+
+ for (;;) {
+ int flags = 0;
+ struct strbuf data;
+ int rv;
+
+ rv = getmsg(afd, &ctrl, NULL, &flags);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_ERR, "netmib2: failed to get a response: %s\n",
+ osstrerror());
+ break;
+ }
+
+ if ((rv == 0) && (ctrl.len >= sizeof(*omack)) &&
+ (omack->PRIM_type == T_OPTMGMT_ACK) &&
+ (omack->MGMT_flags == T_SUCCESS) && (oh->len == 0)) {
+ data_valid = 1;
+ break;
+ }
+
+ if ((rv != MOREDATA) || (ctrl.len < sizeof(*omack)) ||
+ (omack->PRIM_type != T_OPTMGMT_ACK) ||
+ (omack->MGMT_flags != T_SUCCESS)) {
+ __pmNotifyErr(LOG_ERR, "netmib2: Unexpected message received\n");
+ break;
+ }
+
+ memset(&data, 0, sizeof(data));
+ data.buf = malloc(oh->len);
+ if (data.buf == NULL) {
+ __pmNotifyErr(LOG_ERR, "netmib2: Out of memory\n");
+ break;
+ }
+
+ data.maxlen = oh->len;
+ flags = 0;
+
+ rv = getmsg(afd, NULL, &data, &flags);
+ if (rv) {
+ __pmNotifyErr(LOG_ERR,
+ "net2mib: Failed to get additional data: %s\n",
+ osstrerror());
+ break;
+ }
+
+ switch (oh->level) {
+ case MIB2_IP:
+ switch(oh->name) {
+ case 0: /* Overall statistic */
+ break;
+
+ case MIB2_IP_ADDR:
+ ipv4_stats(data.buf, data.len);
+ break;
+
+ case MIB2_IP_TRAFFIC_STATS:
+ ipv4_ifstats(data.buf, data.len);
+ break;
+ }
+ break;
+
+ case MIB2_IP6:
+ break;
+
+ case MIB2_UDP:
+ if (oh->name == 0) {
+ mib2_udp_t *m2u = (mib2_udp_t *)data.buf;
+
+#ifdef EXPER_IP_AND_TESTHIDDEN
+ nm2_udp.ipackets = m2u->udpHCInDatagrams;
+ nm2_udp.opackets = m2u->udpHCOutDatagrams;
+#else
+ nm2_udp.ipackets = m2u->udpInDatagrams;
+ nm2_udp.opackets = m2u->udpOutDatagrams;
+#endif
+ nm2_udp.ierrors = m2u->udpInErrors;
+ nm2_udp.oerrors = m2u->udpOutErrors;
+ }
+ break;
+
+ case MIB2_TCP:
+ break;
+ }
+
+ free(data.buf);
+ }
+
+ if (netif_added) {
+ pmdaCacheOp(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
+
+int
+netmib2_fetch(pmdaMetric *pm, int inst, pmAtomValue *av)
+{
+ char *fsname;
+ metricdesc_t *md = pm->m_user;
+ char *ist;
+
+ if (pm->m_desc.indom == PM_INDOM_NULL) {
+ switch (pm->m_desc.type) {
+ case PM_TYPE_U32:
+ av->ul = *(uint32_t *)md->md_offset;
+ return 1;
+
+ case PM_TYPE_U64:
+ av->ull = *(uint64_t *)md->md_offset;
+ return 1;
+ }
+
+ return PM_ERR_APPVERSION;
+ }
+
+ if (pmdaCacheLookup(indomtab[NETIF_INDOM].it_indom, inst, &fsname,
+ (void **)&ist) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (ist) {
+ switch (pm->m_desc.type) {
+ case PM_TYPE_U32:
+ av->ul = *(uint32_t *)(ist + md->md_offset);
+ return 1;
+
+ case PM_TYPE_U64:
+ av->ull = *(uint64_t *)(ist + md->md_offset);
+ return 1;
+ }
+
+ return PM_ERR_APPVERSION;
+ }
+
+ /* Even if we've copied the values don't admit they're good unless
+ * the update was problem-free. */
+ return data_valid;
+}
+
+void
+netmib2_init(int first)
+{
+ char *mods[] = {"tcp", "udp", "icmp"};
+ int i;
+
+ if (afd >= 0)
+ return;
+
+ afd = open("/dev/arp", O_RDWR);
+ if (afd < 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot open /dev/arp: %s\n", osstrerror());
+ return;
+ }
+
+ for (i = 0; i < 3; i++ ) {
+ if (ioctl(afd, I_PUSH, mods[i]) < 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot push %s into /dev/arp: %s\n",
+ mods[i], osstrerror());
+ close(afd);
+ afd = -1;
+ return;
+ }
+ }
+
+ pmdaCacheOp(indomtab[NETIF_INDOM].it_indom, PMDA_CACHE_LOAD);
+ netmib2_refresh();
+}
diff --git a/src/pmdas/solaris/netmib2.h b/src/pmdas/solaris/netmib2.h
new file mode 100644
index 0000000..e506c28
--- /dev/null
+++ b/src/pmdas/solaris/netmib2.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __PMDA_SOLARIS_NETMIB2_H
+#define __PMDA_SOLARIS_NETMIB2_H
+
+typedef struct nm2_udp_stats {
+ uint64_t ipackets;
+ uint64_t opackets;
+ int32_t ierrors;
+ int32_t oerrors;
+ uint32_t noports;
+ uint32_t overflows;
+} nm2_udp_stats_t;
+
+extern nm2_udp_stats_t nm2_udp;
+
+typedef struct nm2_netif_stats {
+ uint64_t ipackets;
+ uint64_t opackets;
+ uint64_t ibytes;
+ uint64_t obytes;
+ uint64_t delivered;
+ uint64_t imcast;
+ uint64_t omcast;
+ uint64_t ibcast;
+ uint64_t obcast;
+ uint64_t ierrors;
+ uint64_t oerrors;
+ int32_t idrops;
+ int32_t odrops;
+ int mtu;
+} nm2_netif_stats_t;
+
+void netmib2_init(int);
+void netmib2_refresh(void);
+int netmib2_fetch(pmdaMetric *, int, pmAtomValue *);
+
+#endif
diff --git a/src/pmdas/solaris/pmns.disk b/src/pmdas/solaris/pmns.disk
new file mode 100644
index 0000000..1875a25
--- /dev/null
+++ b/src/pmdas/solaris/pmns.disk
@@ -0,0 +1,58 @@
+disk {
+ all
+ dev
+}
+
+disk.all {
+ read SOLARIS:SCLR_DISK:0
+ write SOLARIS:SCLR_DISK:1
+ total SOLARIS:SCLR_DISK:2
+ read_bytes SOLARIS:SCLR_DISK:3
+ write_bytes SOLARIS:SCLR_DISK:4
+ total_bytes SOLARIS:SCLR_DISK:5
+ wait
+ run
+}
+
+disk.all.wait {
+ time SOLARIS:SCLR_DISK:6
+ count SOLARIS:SCLR_DISK:7
+}
+
+disk.all.run {
+ time SOLARIS:SCLR_DISK:8
+ count SOLARIS:SCLR_DISK:9
+}
+
+disk.dev {
+ read SOLARIS:SCLR_DISK:10
+ write SOLARIS:SCLR_DISK:11
+ total SOLARIS:SCLR_DISK:12
+ read_bytes SOLARIS:SCLR_DISK:13
+ write_bytes SOLARIS:SCLR_DISK:14
+ total_bytes SOLARIS:SCLR_DISK:15
+ wait
+ run
+ errors
+}
+
+disk.dev.wait {
+ time SOLARIS:SCLR_DISK:16
+ count SOLARIS:SCLR_DISK:17
+}
+disk.dev.run {
+ time SOLARIS:SCLR_DISK:18
+ count SOLARIS:SCLR_DISK:19
+}
+
+disk.dev.errors {
+ soft SOLARIS:SCLR_DISK:21
+ hard SOLARIS:SCLR_DISK:22
+ transport SOLARIS:SCLR_DISK:23
+ media SOLARIS:SCLR_DISK:24
+ recoverable SOLARIS:SCLR_DISK:25
+ notready SOLARIS:SCLR_DISK:26
+ nodevice SOLARIS:SCLR_DISK:27
+ badrequest SOLARIS:SCLR_DISK:28
+ pfa SOLARIS:SCLR_DISK:29
+}
diff --git a/src/pmdas/solaris/pmns.hinv b/src/pmdas/solaris/pmns.hinv
new file mode 100644
index 0000000..964e166
--- /dev/null
+++ b/src/pmdas/solaris/pmns.hinv
@@ -0,0 +1,34 @@
+hinv {
+ ncpu SOLARIS:SCLR_SYSINFO:56
+ ndisk SOLARIS:SCLR_DISK:20
+ nfilesys SOLARIS:SCLR_FILESYS:1023
+ pagesize SOLARIS:SCLR_SYSINFO:108
+ physmem SOLARIS:SCLR_SYSINFO:109
+ cpu
+ disk
+}
+
+hinv.cpu {
+ maxclock SOLARIS:SCLR_SYSINFO:147
+ clock SOLARIS:SCLR_SYSINFO:148
+ brand SOLARIS:SCLR_SYSINFO:149
+ frequencies SOLARIS:SCLR_SYSINFO:150
+ implementation SOLARIS:SCLR_SYSINFO:151
+ chip_id SOLARIS:SCLR_SYSINFO:152
+ clog_id SOLARIS:SCLR_SYSINFO:153
+ core_id SOLARIS:SCLR_SYSINFO:154
+ pkg_core_id SOLARIS:SCLR_SYSINFO:155
+ cstate SOLARIS:SCLR_SYSINFO:156
+ maxcstates SOLARIS:SCLR_SYSINFO:157
+ ncores SOLARIS:SCLR_SYSINFO:158
+ ncpus SOLARIS:SCLR_SYSINFO:159
+}
+
+hinv.disk {
+ vendor SOLARIS:SCLR_DISK:30
+ product SOLARIS:SCLR_DISK:31
+ revision SOLARIS:SCLR_DISK:32
+ serial SOLARIS:SCLR_DISK:33
+ capacity SOLARIS:SCLR_DISK:34
+ devlink SOLARIS:SCLR_DISK:35
+}
diff --git a/src/pmdas/solaris/pmns.kernel b/src/pmdas/solaris/pmns.kernel
new file mode 100644
index 0000000..d6722ae
--- /dev/null
+++ b/src/pmdas/solaris/pmns.kernel
@@ -0,0 +1,200 @@
+kernel {
+ all
+ percpu
+ fsflush
+ fs
+ fstype
+}
+
+kernel.all {
+ cpu
+ io
+ trap SOLARIS:SCLR_SYSINFO:32
+ pswitch SOLARIS:SCLR_SYSINFO:23
+ syscall SOLARIS:SCLR_SYSINFO:22
+ sysexec SOLARIS:SCLR_SYSINFO:33
+ sysfork SOLARIS:SCLR_SYSINFO:34
+ sysvfork SOLARIS:SCLR_SYSINFO:35
+ sysread SOLARIS:SCLR_SYSINFO:36
+ syswrite SOLARIS:SCLR_SYSINFO:37
+ load SOLARIS:SCLR_SYSINFO:135
+}
+
+kernel.all.cpu {
+ idle SOLARIS:SCLR_SYSINFO:0
+ user SOLARIS:SCLR_SYSINFO:1
+ sys SOLARIS:SCLR_SYSINFO:2
+ wait
+}
+
+kernel.all.cpu.wait {
+ total SOLARIS:SCLR_SYSINFO:3
+ io SOLARIS:SCLR_SYSINFO:8
+ pio SOLARIS:SCLR_SYSINFO:9
+ swap SOLARIS:SCLR_SYSINFO:10
+}
+
+kernel.all.io {
+ bread SOLARIS:SCLR_SYSINFO:14
+ bwrite SOLARIS:SCLR_SYSINFO:15
+ lread SOLARIS:SCLR_SYSINFO:16
+ lwrite SOLARIS:SCLR_SYSINFO:17
+ phread SOLARIS:SCLR_SYSINFO:26
+ phwrite SOLARIS:SCLR_SYSINFO:27
+ intr SOLARIS:SCLR_SYSINFO:28
+}
+
+kernel.percpu {
+ cpu
+ io
+ trap SOLARIS:SCLR_SYSINFO:38
+ pswitch SOLARIS:SCLR_SYSINFO:25
+ syscall SOLARIS:SCLR_SYSINFO:24
+ sysexec SOLARIS:SCLR_SYSINFO:39
+ sysfork SOLARIS:SCLR_SYSINFO:40
+ sysvfork SOLARIS:SCLR_SYSINFO:41
+ sysread SOLARIS:SCLR_SYSINFO:42
+ syswrite SOLARIS:SCLR_SYSINFO:43
+}
+
+kernel.percpu.cpu {
+ idle SOLARIS:SCLR_SYSINFO:4
+ user SOLARIS:SCLR_SYSINFO:5
+ sys SOLARIS:SCLR_SYSINFO:6
+ wait
+}
+
+kernel.percpu.cpu.wait {
+ total SOLARIS:SCLR_SYSINFO:7
+ io SOLARIS:SCLR_SYSINFO:11
+ pio SOLARIS:SCLR_SYSINFO:12
+ swap SOLARIS:SCLR_SYSINFO:13
+}
+
+kernel.percpu.io {
+ bread SOLARIS:SCLR_SYSINFO:18
+ bwrite SOLARIS:SCLR_SYSINFO:19
+ lread SOLARIS:SCLR_SYSINFO:20
+ lwrite SOLARIS:SCLR_SYSINFO:21
+ phread SOLARIS:SCLR_SYSINFO:29
+ phwrite SOLARIS:SCLR_SYSINFO:30
+ intr SOLARIS:SCLR_SYSINFO:31
+}
+
+kernel.fsflush {
+ scanned SOLARIS:SCLR_FSFLUSH:0
+ examined SOLARIS:SCLR_FSFLUSH:1
+ locked SOLARIS:SCLR_FSFLUSH:2
+ modified SOLARIS:SCLR_FSFLUSH:3
+ coalesced SOLARIS:SCLR_FSFLUSH:4
+ released SOLARIS:SCLR_FSFLUSH:5
+ time SOLARIS:SCLR_FSFLUSH:6
+}
+
+kernel.fs {
+ vnops
+ read_bytes SOLARIS:SCLR_FILESYS:0
+ readdir_bytes SOLARIS:SCLR_FILESYS:1
+ write_bytes SOLARIS:SCLR_FILESYS:2
+}
+
+kernel.fs.vnops {
+ access SOLARIS:SCLR_FILESYS:3
+ addmap SOLARIS:SCLR_FILESYS:4
+ close SOLARIS:SCLR_FILESYS:5
+ cmp SOLARIS:SCLR_FILESYS:6
+ create SOLARIS:SCLR_FILESYS:7
+ delmap SOLARIS:SCLR_FILESYS:8
+ dispose SOLARIS:SCLR_FILESYS:9
+ dump SOLARIS:SCLR_FILESYS:10
+ dumpctl SOLARIS:SCLR_FILESYS:11
+ fid SOLARIS:SCLR_FILESYS:12
+ frlock SOLARIS:SCLR_FILESYS:13
+ fsync SOLARIS:SCLR_FILESYS:14
+ getattr SOLARIS:SCLR_FILESYS:15
+ getpage SOLARIS:SCLR_FILESYS:16
+ getsecattr SOLARIS:SCLR_FILESYS:17
+ inactive SOLARIS:SCLR_FILESYS:18
+ ioctl SOLARIS:SCLR_FILESYS:19
+ link SOLARIS:SCLR_FILESYS:20
+ lookup SOLARIS:SCLR_FILESYS:21
+ map SOLARIS:SCLR_FILESYS:22
+ mkdir SOLARIS:SCLR_FILESYS:23
+ open SOLARIS:SCLR_FILESYS:24
+ pageio SOLARIS:SCLR_FILESYS:25
+ pathconf SOLARIS:SCLR_FILESYS:26
+ poll SOLARIS:SCLR_FILESYS:27
+ putpage SOLARIS:SCLR_FILESYS:28
+ read SOLARIS:SCLR_FILESYS:29
+ readdir SOLARIS:SCLR_FILESYS:30
+ readlink SOLARIS:SCLR_FILESYS:31
+ realvp SOLARIS:SCLR_FILESYS:32
+ remove SOLARIS:SCLR_FILESYS:33
+ rename SOLARIS:SCLR_FILESYS:34
+ rmdir SOLARIS:SCLR_FILESYS:35
+ rwlock SOLARIS:SCLR_FILESYS:36
+ rwunlock SOLARIS:SCLR_FILESYS:37
+ seek SOLARIS:SCLR_FILESYS:38
+ setattr SOLARIS:SCLR_FILESYS:39
+ setfl SOLARIS:SCLR_FILESYS:40
+ setsecattr SOLARIS:SCLR_FILESYS:41
+ shrlock SOLARIS:SCLR_FILESYS:42
+ space SOLARIS:SCLR_FILESYS:43
+ symlink SOLARIS:SCLR_FILESYS:44
+ vnevent SOLARIS:SCLR_FILESYS:45
+ write SOLARIS:SCLR_FILESYS:46
+}
+
+kernel.fstype {
+ vnops
+ read_bytes SOLARIS:SCLR_FILESYS:47
+ readdir_bytes SOLARIS:SCLR_FILESYS:48
+ write_bytes SOLARIS:SCLR_FILESYS:49
+}
+
+kernel.fstype.vnops {
+ access SOLARIS:SCLR_FILESYS:50
+ addmap SOLARIS:SCLR_FILESYS:51
+ close SOLARIS:SCLR_FILESYS:52
+ cmp SOLARIS:SCLR_FILESYS:53
+ create SOLARIS:SCLR_FILESYS:54
+ delmap SOLARIS:SCLR_FILESYS:55
+ dispose SOLARIS:SCLR_FILESYS:56
+ dump SOLARIS:SCLR_FILESYS:57
+ dumpctl SOLARIS:SCLR_FILESYS:58
+ fid SOLARIS:SCLR_FILESYS:59
+ frlock SOLARIS:SCLR_FILESYS:60
+ fsync SOLARIS:SCLR_FILESYS:61
+ getattr SOLARIS:SCLR_FILESYS:62
+ getpage SOLARIS:SCLR_FILESYS:63
+ getsecattr SOLARIS:SCLR_FILESYS:64
+ inactive SOLARIS:SCLR_FILESYS:65
+ ioctl SOLARIS:SCLR_FILESYS:66
+ link SOLARIS:SCLR_FILESYS:67
+ lookup SOLARIS:SCLR_FILESYS:68
+ map SOLARIS:SCLR_FILESYS:69
+ mkdir SOLARIS:SCLR_FILESYS:70
+ open SOLARIS:SCLR_FILESYS:71
+ pageio SOLARIS:SCLR_FILESYS:72
+ pathconf SOLARIS:SCLR_FILESYS:73
+ poll SOLARIS:SCLR_FILESYS:74
+ putpage SOLARIS:SCLR_FILESYS:75
+ read SOLARIS:SCLR_FILESYS:76
+ readdir SOLARIS:SCLR_FILESYS:77
+ readlink SOLARIS:SCLR_FILESYS:78
+ realvp SOLARIS:SCLR_FILESYS:79
+ remove SOLARIS:SCLR_FILESYS:80
+ rename SOLARIS:SCLR_FILESYS:81
+ rmdir SOLARIS:SCLR_FILESYS:82
+ rwlock SOLARIS:SCLR_FILESYS:83
+ rwunlock SOLARIS:SCLR_FILESYS:84
+ seek SOLARIS:SCLR_FILESYS:85
+ setattr SOLARIS:SCLR_FILESYS:86
+ setfl SOLARIS:SCLR_FILESYS:87
+ setsecattr SOLARIS:SCLR_FILESYS:88
+ shrlock SOLARIS:SCLR_FILESYS:89
+ space SOLARIS:SCLR_FILESYS:90
+ symlink SOLARIS:SCLR_FILESYS:91
+ vnevent SOLARIS:SCLR_FILESYS:92
+ write SOLARIS:SCLR_FILESYS:93
+}
diff --git a/src/pmdas/solaris/pmns.mem b/src/pmdas/solaris/pmns.mem
new file mode 100644
index 0000000..04fb6f1
--- /dev/null
+++ b/src/pmdas/solaris/pmns.mem
@@ -0,0 +1,88 @@
+/*
+ * TODO
+ *
+ * These are the IRIX names, for reference
+ * mem.freemem
+ * mem.availsmem
+ * mem.availrmem
+ * mem.ravailrmem
+ * mem.bufmem
+ * mem.physmem
+ * mem.dchunkpages
+ * mem.pmapmem
+ * mem.strmem
+ * mem.chunkpages
+ * mem.dpages
+ * mem.emptymem
+ * mem.freeswap
+ * mem.halloc
+ * mem.heapmem
+ * mem.hfree
+ * mem.hovhd
+ * mem.hunused
+ * mem.zfree
+ * mem.zonemem
+ * mem.zreq
+ * mem.iclean
+ * mem.bsdnet
+ * mem.palloc
+ * mem.unmodfl
+ * mem.unmodsw
+ * mem.min_file_pages
+ * mem.min_free_pages
+ * mem.bufs.fs_metadata
+ * mem.bufs.fs_data
+ * mem.bufs.empty
+ * mem.bufs.inact
+ * mem.fault.prot.total
+ * mem.fault.prot.cow
+ * mem.fault.prot.steal
+ * mem.fault.addr.total
+ * mem.fault.addr.cache
+ * mem.fault.addr.demand
+ * mem.fault.addr.file
+ * mem.fault.addr.swap
+ * mem.tlb.flush
+ * mem.tlb.invalid
+ * mem.tlb.rfault
+ * mem.tlb.sync
+ * mem.tlb.tfault
+ * mem.tlb.purge
+ * mem.tlb.idnew
+ * mem.tlb.idwrap
+ * mem.tlb.kvmwrap
+ * mem.paging.reclaim
+ * mem.system.sptalloc
+ * mem.system.sptfree
+ * mem.system.sptclean
+ * mem.system.sptdirty
+ * mem.system.sptintrans
+ * mem.system.sptaged
+ * mem.system.sptbp
+ * mem.system.sptheap
+ * mem.system.sptzone
+ * mem.system.sptpt
+ * mem.lpage.faults
+ * mem.lpage.allocs
+ * mem.lpage.downgrade
+ * mem.lpage.page_splits
+ * mem.lpage.basesize
+ * mem.lpage.maxsize
+ * mem.lpage.maxenabled
+ * mem.lpage.enabled
+ * mem.lpage.coalesce.scans
+ * mem.lpage.coalesce.success
+ * mem.util.kernel
+ * mem.util.fs_ctl
+ * mem.util.fs_dirty
+ * mem.util.fs_clean
+ * mem.util.free
+ * mem.util.user
+ */
+
+mem {
+ physmem SOLARIS:SCLR_SYSINFO:136
+ freemem SOLARIS:SCLR_SYSINFO:137
+ lotsfree SOLARIS:SCLR_SYSINFO:138
+ availrmem SOLARIS:SCLR_SYSINFO:139
+}
diff --git a/src/pmdas/solaris/pmns.network b/src/pmdas/solaris/pmns.network
new file mode 100644
index 0000000..e9fe9f8
--- /dev/null
+++ b/src/pmdas/solaris/pmns.network
@@ -0,0 +1,302 @@
+/*
+ * TODO
+ *
+ * These are the IRIX names, for reference
+ * network.icmp.error
+ * network.icmp.oldshort
+ * network.icmp.oldicmp
+ * network.icmp.badcode
+ * network.icmp.tooshort
+ * network.icmp.checksum
+ * network.icmp.badlen
+ * network.icmp.reflect
+ * network.icmp.inhist.echoreply
+ * network.icmp.inhist.unreach
+ * network.icmp.inhist.sourcequench
+ * network.icmp.inhist.redirect
+ * network.icmp.inhist.echo
+ * network.icmp.inhist.routeradvert
+ * network.icmp.inhist.routersolicit
+ * network.icmp.inhist.timxceed
+ * network.icmp.inhist.paramprob
+ * network.icmp.inhist.tstamp
+ * network.icmp.inhist.tstampreply
+ * network.icmp.inhist.ireq
+ * network.icmp.inhist.ireqreply
+ * network.icmp.inhist.maskreq
+ * network.icmp.inhist.maskreply
+ * network.icmp.outhist.echoreply
+ * network.icmp.outhist.unreach
+ * network.icmp.outhist.sourcequench
+ * network.icmp.outhist.redirect
+ * network.icmp.outhist.echo
+ * network.icmp.outhist.routeradvert
+ * network.icmp.outhist.routersolicit
+ * network.icmp.outhist.timxceed
+ * network.icmp.outhist.paramprob
+ * network.icmp.outhist.tstamp
+ * network.icmp.outhist.tstampreply
+ * network.icmp.outhist.ireq
+ * network.icmp.outhist.ireqreply
+ * network.icmp.outhist.maskreq
+ * network.icmp.outhist.maskreply
+ * network.igmp.rcv_total
+ * network.igmp.rcv_tooshort
+ * network.igmp.rcv_badsum
+ * network.igmp.rcv_queries
+ * network.igmp.rcv_badqueries
+ * network.igmp.rcv_reports
+ * network.igmp.rcv_badreports
+ * network.igmp.rcv_ourreports
+ * network.igmp.snd_reports
+ * network.ip.badhlen
+ * network.ip.badlen
+ * network.ip.badoptions
+ * network.ip.badsum
+ * network.ip.cantforward
+ * network.ip.cantfrag
+ * network.ip.delivered
+ * network.ip.forward
+ * network.ip.fragdropped
+ * network.ip.fragmented
+ * network.ip.fragments
+ * network.ip.fragtimeout
+ * network.ip.localout
+ * network.ip.noproto
+ * network.ip.noroute
+ * network.ip.odropped
+ * network.ip.ofragments
+ * network.ip.reassembled
+ * network.ip.redirect
+ * network.ip.tooshort
+ * network.ip.toosmall
+ * network.ip.badvers
+ * network.ip.rawout
+ * network.ip.strictreassoverlapfrags
+ * network.ip.strictreassgapfrags
+ * network.ip.total
+ * network.tcp.connattempt
+ * network.tcp.accepts
+ * network.tcp.connects
+ * network.tcp.drops
+ * network.tcp.conndrops
+ * network.tcp.closed
+ * network.tcp.segstimed
+ * network.tcp.rttupdated
+ * network.tcp.delack
+ * network.tcp.timeoutdrop
+ * network.tcp.rexmttimeo
+ * network.tcp.persisttimeo
+ * network.tcp.keeptimeo
+ * network.tcp.keepprobe
+ * network.tcp.keepdrops
+ * network.tcp.sndtotal
+ * network.tcp.sndpack
+ * network.tcp.sndbyte
+ * network.tcp.sndrexmitpack
+ * network.tcp.sndrexmitbyte
+ * network.tcp.sndacks
+ * network.tcp.sndprobe
+ * network.tcp.sndurg
+ * network.tcp.sndwinup
+ * network.tcp.sndctrl
+ * network.tcp.sndrst
+ * network.tcp.rcvtotal
+ * network.tcp.rcvpack
+ * network.tcp.rcvbyte
+ * network.tcp.rcvbadsum
+ * network.tcp.rcvbadoff
+ * network.tcp.rcvshort
+ * network.tcp.rcvduppack
+ * network.tcp.rcvdupbyte
+ * network.tcp.rcvpartduppack
+ * network.tcp.rcvpartdupbyte
+ * network.tcp.rcvoopack
+ * network.tcp.rcvoobyte
+ * network.tcp.rcvpackafterwin
+ * network.tcp.rcvbyteafterwin
+ * network.tcp.rcvafterclose
+ * network.tcp.rcvwinprobe
+ * network.tcp.rcvdupack
+ * network.tcp.rcvacktoomuch
+ * network.tcp.rcvackpack
+ * network.tcp.rcvackbyte
+ * network.tcp.rcvwinupd
+ * network.tcp.pcbcachemiss
+ * network.tcp.predack
+ * network.tcp.preddat
+ * network.tcp.pawsdrop
+ * network.tcp.badsyn
+ * network.tcp.listendrop
+ * network.tcp.persistdrop
+ * network.tcp.synpurge
+ * network.udp.ipackets
+ * network.udp.hdrops
+ * network.udp.badsum
+ * network.udp.badlen
+ * network.udp.noport
+ * network.udp.noportbcast
+ * network.udp.fullsock
+ * network.udp.opackets
+ * network.udp.pcbcachemiss
+
+ * network.interface.collisions
+ * network.interface.mtu
+ * network.interface.noproto
+ * network.interface.baudrate
+ * network.interface.in.errors
+ * network.interface.in.packets
+ * network.interface.in.bytes
+ * network.interface.in.mcasts
+ * network.interface.in.drops
+
+ * network.interface.out.errors
+ * network.interface.out.packets
+ * network.interface.out.bytes
+ * network.interface.out.mcasts
+ * network.interface.out.drops
+ * network.interface.out.qdrops
+ * network.interface.out.qlength
+ * network.interface.out.qmax
+ * network.interface.total.errors
+ * network.interface.total.packets
+ * network.interface.total.bytes
+ * network.interface.total.mcasts
+ * network.interface.total.drops
+ * network.mbuf.alloc
+ * network.mbuf.typealloc
+ * network.mbuf.clustalloc
+ * network.mbuf.clustfree
+ * network.mbuf.failed
+ * network.mbuf.waited
+ * network.mbuf.drained
+ * network.mbuf.pcb.total
+ * network.mbuf.pcb.bytes
+ * network.mbuf.mcb.total
+ * network.mbuf.mcb.bytes
+ * network.mbuf.mcb.fail
+ * network.mcr.mfc_lookups
+ * network.mcr.mfc_misses
+ * network.mcr.upcalls
+ * network.mcr.no_route
+ * network.mcr.bad_tunnel
+ * network.mcr.cant_tunnel
+ * network.mcr.wrong_if
+ * network.mcr.upq_ovflw
+ * network.mcr.cache_cleanups
+ * network.mcr.drop_sel
+ * network.mcr.q_overflow
+ * network.mcr.pkt2large
+ * network.mcr.upq_sockfull
+ * network.socket.type
+ * network.socket.state
+ * network.st.connattempt
+ * network.st.accepts
+ * network.st.connects
+ * network.st.drops
+ * network.st.connfails
+ * network.st.closed
+ * network.st.txtotal
+ * network.st.datatxtotal
+ * network.st.rxtotal
+ * network.st.datarxtotal
+ * network.st.cksumbad
+ * network.st.oototal
+ * network.st.keyrejects
+ * network.st.txrejects
+ * network.st.rxrejects
+ * network.st.slotdrops
+ * network.is.in_window
+ * network.is.in_underflow
+ * network.is.in_overlap
+ * network.is.up_disordered
+ * network.is.up_ordered
+ * network.is.outq_full
+ * network.is.outq_wakeups
+ * network.is.outq_drains
+ * network.is.reorder_wakeups
+ * network.is.reorder_drains
+ * network.is.drain_underflow
+ * network.is.drain_loop
+ * network.is.drain_empty
+ * network.is.window_stalls
+ * network.is.window_flush_null
+ * network.is.window_seqno_fixup
+ * network.is.window_flush_skipped
+ * network.is.window_flush_nlinks
+ * network.is.link_quota_oflows
+ * network.is.link_empty_headers
+ * network.is.link_header_allocs
+ * network.is.link_soft_cksums
+ * network.is.link_sync_seqno
+ * network.is.err_bad_version
+ * network.is.err_input_no_link
+ */
+
+network {
+ interface
+ link
+ udp
+}
+
+network.interface {
+ in
+ out
+ mtu SOLARIS:SCLR_NETIF:0
+}
+
+network.interface.in {
+ errors SOLARIS:SCLR_NETIF:1
+ packets SOLARIS:SCLR_NETIF:2
+ bytes SOLARIS:SCLR_NETIF:3
+ bcasts SOLARIS:SCLR_NETIF:4
+ mcasts SOLARIS:SCLR_NETIF:5
+ drops SOLARIS:SCLR_NETIF:6
+ delivers SOLARIS:SCLR_NETIF:7
+}
+
+network.interface.out {
+ errors SOLARIS:SCLR_NETIF:8
+ packets SOLARIS:SCLR_NETIF:9
+ bytes SOLARIS:SCLR_NETIF:10
+ bcasts SOLARIS:SCLR_NETIF:11
+ mcasts SOLARIS:SCLR_NETIF:12
+ drops SOLARIS:SCLR_NETIF:13
+}
+
+network.udp {
+ ipackets SOLARIS:SCLR_NETIF:14
+ opackets SOLARIS:SCLR_NETIF:15
+ ierrors SOLARIS:SCLR_NETIF:16
+ oerrors SOLARIS:SCLR_NETIF:17
+ noports SOLARIS:SCLR_NETIF:18
+ overflows SOLARIS:SCLR_NETIF:19
+}
+
+network.link {
+ in
+ out
+ collisions SOLARIS:SCLR_NETLINK:0
+ state SOLARIS:SCLR_NETLINK:1
+ duplex SOLARIS:SCLR_NETLINK:2
+ speed SOLARIS:SCLR_NETLINK:3
+}
+
+network.link.in {
+ errors SOLARIS:SCLR_NETLINK:4
+ packets SOLARIS:SCLR_NETLINK:5
+ bytes SOLARIS:SCLR_NETLINK:6
+ bcasts SOLARIS:SCLR_NETLINK:7
+ mcasts SOLARIS:SCLR_NETLINK:8
+ nobufs SOLARIS:SCLR_NETLINK:9
+}
+
+network.link.out {
+ errors SOLARIS:SCLR_NETLINK:10
+ packets SOLARIS:SCLR_NETLINK:11
+ bytes SOLARIS:SCLR_NETLINK:12
+ bcasts SOLARIS:SCLR_NETLINK:13
+ mcasts SOLARIS:SCLR_NETLINK:14
+ nobufs SOLARIS:SCLR_NETLINK:15
+}
+
diff --git a/src/pmdas/solaris/pmns.zfs b/src/pmdas/solaris/pmns.zfs
new file mode 100644
index 0000000..6686d80
--- /dev/null
+++ b/src/pmdas/solaris/pmns.zfs
@@ -0,0 +1,60 @@
+zfs {
+ arc
+ used
+ snapshot
+ available SOLARIS:SCLR_ZFS:0
+ quota SOLARIS:SCLR_ZFS:1
+ reservation SOLARIS:SCLR_ZFS:2
+ compression SOLARIS:SCLR_ZFS:3
+ copies SOLARIS:SCLR_ZFS:4
+ recordsize SOLARIS:SCLR_ZFS:5
+ refquota SOLARIS:SCLR_ZFS:6
+ refreservation SOLARIS:SCLR_ZFS:7
+ referenced SOLARIS:SCLR_ZFS:8
+ nsnapshots SOLARIS:SCLR_ZFS:9
+}
+
+zfs.used {
+ total SOLARIS:SCLR_ZFS:10
+ byme SOLARIS:SCLR_ZFS:11
+ bysnapshots SOLARIS:SCLR_ZFS:12
+ bychildren SOLARIS:SCLR_ZFS:13
+ byrefreservation SOLARIS:SCLR_ZFS:14
+}
+
+zfs.snapshot {
+ used SOLARIS:SCLR_ZFS:15
+ referenced SOLARIS:SCLR_ZFS:16
+ compression SOLARIS:SCLR_ZFS:17
+}
+
+zfs.arc {
+ size SOLARIS:SCLR_ARCSTATS:0
+ min_size SOLARIS:SCLR_ARCSTATS:1
+ max_size SOLARIS:SCLR_ARCSTATS:2
+ mru_size SOLARIS:SCLR_ARCSTATS:3
+ target_size SOLARIS:SCLR_ARCSTATS:4
+ hits
+ misses
+
+}
+
+zfs.arc.misses {
+ total SOLARIS:SCLR_ARCSTATS:5
+ demand_data SOLARIS:SCLR_ARCSTATS:6
+ demand_metadata SOLARIS:SCLR_ARCSTATS:7
+ prefetch_data SOLARIS:SCLR_ARCSTATS:8
+ prefetch_metadata SOLARIS:SCLR_ARCSTATS:9
+}
+
+zfs.arc.hits {
+ total SOLARIS:SCLR_ARCSTATS:10
+ mfu SOLARIS:SCLR_ARCSTATS:11
+ mru SOLARIS:SCLR_ARCSTATS:12
+ mfu_ghost SOLARIS:SCLR_ARCSTATS:13
+ mru_ghost SOLARIS:SCLR_ARCSTATS:14
+ demand_data SOLARIS:SCLR_ARCSTATS:15
+ demand_metadata SOLARIS:SCLR_ARCSTATS:16
+ prefetch_data SOLARIS:SCLR_ARCSTATS:17
+ prefetch_metadata SOLARIS:SCLR_ARCSTATS:18
+}
diff --git a/src/pmdas/solaris/pmns.zpool b/src/pmdas/solaris/pmns.zpool
new file mode 100644
index 0000000..2fcae14
--- /dev/null
+++ b/src/pmdas/solaris/pmns.zpool
@@ -0,0 +1,31 @@
+zpool {
+ state SOLARIS:SCLR_ZPOOL:0
+ state_int SOLARIS:SCLR_ZPOOL:1
+ capacity SOLARIS:SCLR_ZPOOL:2
+ used SOLARIS:SCLR_ZPOOL:3
+ checksum_errors SOLARIS:SCLR_ZPOOL:4
+ self_healed SOLARIS:SCLR_ZPOOL:5
+ perdisk
+ in
+ out
+ ops
+}
+
+zpool.in {
+ bytes SOLARIS:SCLR_ZPOOL:6
+ ops SOLARIS:SCLR_ZPOOL:7
+ errors SOLARIS:SCLR_ZPOOL:8
+}
+
+zpool.out {
+ bytes SOLARIS:SCLR_ZPOOL:9
+ ops SOLARIS:SCLR_ZPOOL:10
+ errors SOLARIS:SCLR_ZPOOL:11
+}
+
+zpool.ops {
+ noops SOLARIS:SCLR_ZPOOL:12
+ ioctls SOLARIS:SCLR_ZPOOL:13
+ claims SOLARIS:SCLR_ZPOOL:14
+ frees SOLARIS:SCLR_ZPOOL:15
+}
diff --git a/src/pmdas/solaris/pmns.zpool_perdisk b/src/pmdas/solaris/pmns.zpool_perdisk
new file mode 100644
index 0000000..5c6dfa1
--- /dev/null
+++ b/src/pmdas/solaris/pmns.zpool_perdisk
@@ -0,0 +1,16 @@
+zpool.perdisk {
+ state SOLARIS:SCLR_ZPOOL_PERDISK:0
+ state_int SOLARIS:SCLR_ZPOOL_PERDISK:1
+ checksum_errors SOLARIS:SCLR_ZPOOL_PERDISK:2
+ self_healed SOLARIS:SCLR_ZPOOL_PERDISK:3
+ in
+ out
+}
+
+zpool.perdisk.in {
+ errors SOLARIS:SCLR_ZPOOL_PERDISK:4
+}
+
+zpool.perdisk.out {
+ errors SOLARIS:SCLR_ZPOOL_PERDISK:5
+}
diff --git a/src/pmdas/solaris/root b/src/pmdas/solaris/root
new file mode 100644
index 0000000..7131985
--- /dev/null
+++ b/src/pmdas/solaris/root
@@ -0,0 +1,42 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+#include "clusters.h"
+
+root {
+ kernel
+ disk
+ mem
+ network
+ hinv
+ zpool
+ zfs
+ pmda
+}
+
+pmda {
+ uname SOLARIS:0:107
+ prefetch
+ metric
+}
+
+pmda.prefetch {
+ time SOLARIS:4095:0
+ count SOLARIS:4095:1
+}
+
+pmda.metric {
+ time SOLARIS:4095:2
+ count SOLARIS:4095:3
+}
+
+#include "pmns.kernel"
+#include "pmns.disk"
+#include "pmns.mem"
+#include "pmns.network"
+#include "pmns.hinv"
+#include "pmns.zpool"
+#include "pmns.zfs"
+#include "pmns.zpool_perdisk"
diff --git a/src/pmdas/solaris/solaris.c b/src/pmdas/solaris/solaris.c
new file mode 100644
index 0000000..2deb6fd
--- /dev/null
+++ b/src/pmdas/solaris/solaris.c
@@ -0,0 +1,216 @@
+/*
+ * Solaris PMDA
+ *
+ * Collect performance data from the Solaris kernel using kstat() for
+ * the most part.
+ *
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2010 Max Matveev. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <time.h>
+#include <sys/time.h>
+#include "common.h"
+
+static int _isDSO = 1;
+static char mypath[MAXPATHLEN];
+
+/*
+ * wrapper for pmdaFetch which primes the methods ready for
+ * the next fetch
+ * ... real callback is fetch_callback()
+ */
+static int
+solaris_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int i;
+
+ kstat_ctl_needs_update();
+
+ for (i = 0; i < methodtab_sz; i++) {
+ methodtab[i].m_fetched = 0;
+ }
+
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+solaris_fetch_callback(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ metricdesc_t *mdp = (metricdesc_t *)mdesc->m_user;
+ int cluster = pmid_cluster(mdesc->m_desc.pmid);
+ method_t *m = methodtab + cluster;
+ hrtime_t start;
+ int rv;
+ __pmID_int *id = __pmid_int(&mdesc->m_desc.pmid);
+
+ if (cluster == 4095) {
+ switch (id->item) {
+ case 0: /* pmda.prefetch.time */
+ if ((inst <= 0) || (inst > methodtab_sz+1))
+ return PM_ERR_INST;
+ atom->ull = methodtab[inst-1].m_elapsed;
+ return 1;
+ case 1: /* pmda.prefetch.count */
+ if ((inst <= 0) || (inst > methodtab_sz+1))
+ return PM_ERR_INST;
+ atom->ull = methodtab[inst-1].m_hits;
+ return 1;
+ case 2: /* pmda.metric.time */
+ if ((inst <= 0) || (inst > metrictab_sz+1))
+ return PM_ERR_INST;
+ atom->ull = metricdesc[inst-1].md_elapsed;
+ return 1;
+ case 3: /* pmda.metric.count */
+ if ((inst <= 0) || (inst > metrictab_sz+1))
+ return PM_ERR_INST;
+ atom->ull = metricdesc[inst-1].md_hits;
+ return 1;
+ default:
+ return PM_ERR_PMID;
+ }
+ } else if (cluster >= methodtab_sz) {
+ return PM_ERR_PMID;
+ }
+
+ if (!m->m_fetched && m->m_prefetch) {
+ start = gethrtime();
+ m->m_prefetch();
+ m->m_elapsed = gethrtime() - start;
+ m->m_hits++;
+ m->m_fetched = 1;
+ }
+ start = gethrtime();
+ rv = m->m_fetch(mdesc, inst, atom);
+ mdp->md_elapsed = gethrtime() - start;
+ mdp->md_hits++;
+ return rv;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+__PMDA_INIT_CALL
+solaris_init(pmdaInterface *dp)
+{
+ if (_isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "solaris" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_3, "Solaris DSO", mypath);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.fetch = solaris_fetch;
+ pmdaSetFetchCallBack(dp, solaris_fetch_callback);
+ init_data(dp->domain);
+ pmdaInit(dp, indomtab, indomtab_sz, metrictab, metrictab_sz);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
+ fputs("Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -N namespace verify consistency of internal metrics with the namespace\n",
+ stderr);
+ exit(1);
+}
+
+static void
+checkname(const char *mname)
+{
+ int i;
+ for (i = 0; i < metrictab_sz; i++) {
+ if (strcmp(mname, metricdesc[i].md_name) == 0)
+ return;
+ }
+ printf ("Cannot find %s in the code\n", mname);
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int err = 0;
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+ int c;
+ char *namespace = NULL;
+
+ _isDSO = 0;
+ __pmSetProgname(argv[0]);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "solaris" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_3, pmProgname, SOLARIS,
+ "solaris.log", mypath);
+
+ while ((c = pmdaGetOpt(argc, argv, "N:D:d:l:?", &desc, &err)) != EOF) {
+ switch (c) {
+ case 'N':
+ namespace = optarg;
+ break;
+ default:
+ err++;
+ break;
+ }
+ }
+ if (err)
+ usage();
+
+ if (namespace) {
+ if (pmLoadNameSpace(namespace))
+ exit(1);
+
+ for (c = 0; c < metrictab_sz; c++) {
+ char *name;
+ int e;
+ __pmID_int *id = __pmid_int(&metricdesc[c].md_desc.pmid);
+ id->domain = desc.domain;
+
+ if ((e = pmNameID(metricdesc[c].md_desc.pmid, &name)) != 0) {
+ printf ("Cannot find %s(%s) in %s: %s\n",
+ metricdesc[c].md_name,
+ pmIDStr(metricdesc[c].md_desc.pmid),
+ namespace, pmErrStr(e));
+ } else {
+ if (strcmp(name, metricdesc[c].md_name)) {
+ printf ("%s is %s in the %s but %s in code\n",
+ pmIDStr(metricdesc[c].md_desc.pmid),
+ name, namespace,metricdesc[c].md_name);
+ }
+ }
+ }
+
+ pmTraversePMNS("", checkname);
+ exit (0);
+ }
+
+ pmdaOpenLog(&desc);
+ solaris_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+
+ exit(0);
+}
diff --git a/src/pmdas/solaris/sysinfo.c b/src/pmdas/solaris/sysinfo.c
new file mode 100644
index 0000000..1937eed
--- /dev/null
+++ b/src/pmdas/solaris/sysinfo.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2010 Max Matveev. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "common.h"
+#include <sys/utsname.h>
+#include <sys/loadavg.h>
+
+typedef struct {
+ int fetched;
+ int err;
+ kstat_t *ksp;
+ cpu_stat_t cpustat;
+ kstat_t *info;
+ int info_is_good;
+} ctl_t;
+
+static int ncpu;
+static int hz;
+static long pagesize;
+static ctl_t *ctl;
+static char uname_full[SYS_NMLN * 5];
+static int nloadavgs;
+static double loadavgs[3];
+
+void
+sysinfo_init(int first)
+{
+ kstat_t *ksp;
+ int i;
+ char buf[10]; /* cpuXXXXX */
+ kstat_ctl_t *kc;
+
+ if (!first)
+ /* TODO ... not sure if/when we'll use this re-init hook */
+ return;
+
+ if ((kc = kstat_ctl_update()) == NULL)
+ return;
+
+ for (ncpu = 0; ; ncpu++) {
+ ksp = kstat_lookup(kc, "cpu_stat", ncpu, NULL);
+ if (ksp == NULL) break;
+ if ((ctl = (ctl_t *)realloc(ctl, (ncpu+1) * sizeof(ctl_t))) == NULL) {
+ fprintf(stderr, "sysinfo_init: ctl realloc[%d] @ cpu=%d failed: %s\n",
+ (int)((ncpu+1) * sizeof(ctl_t)), ncpu, osstrerror());
+ exit(1);
+ }
+ ctl[ncpu].info = kstat_lookup(kc, "cpu_info", ncpu, NULL);
+ ctl[ncpu].ksp = ksp;
+ ctl[ncpu].err = 0;
+ }
+
+ indomtab[CPU_INDOM].it_numinst = ncpu;
+ indomtab[CPU_INDOM].it_set = (pmdaInstid *)malloc(ncpu * sizeof(pmdaInstid));
+ /* TODO check? */
+
+ for (i = 0; i < ncpu; i++) {
+ indomtab[CPU_INDOM].it_set[i].i_inst = i;
+ snprintf(buf, sizeof(buf), "cpu%d", i);
+ indomtab[CPU_INDOM].it_set[i].i_name = strdup(buf);
+ /* TODO check? */
+ }
+
+ hz = (int)sysconf(_SC_CLK_TCK);
+ pagesize = sysconf(_SC_PAGESIZE);
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "sysinfo: ncpu=%d hz=%d\n", ncpu, hz);
+ }
+#endif
+}
+
+static __uint32_t
+sysinfo_derived(pmdaMetric *mdesc, int inst)
+{
+ pmID pmid = mdesc->m_desc.pmid;
+ __pmID_int *ip = (__pmID_int *)&pmid;
+ __uint32_t val;
+
+ ip->domain = 0;
+
+ switch (pmid) {
+
+ case PMDA_PMID(SCLR_SYSINFO,56): /* hinv.ncpu */
+ if (inst == 0)
+ val = ncpu;
+ else
+ val = 0;
+ break;
+
+ default:
+ fprintf(stderr, "cpu_derived: Botch: no method for pmid %s\n",
+ pmIDStr(mdesc->m_desc.pmid));
+ val = 0;
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "cpu_derived: pmid %s inst %d val %d\n",
+ pmIDStr(mdesc->m_desc.pmid), inst, val);
+ }
+#endif
+
+ return val;
+}
+
+void
+sysinfo_prefetch(void)
+{
+ int i;
+
+ nloadavgs = -1;
+ for (i = 0; i < ncpu; i++) {
+ ctl[i].fetched = 0;
+ ctl[i].info_is_good = 0;
+ }
+}
+
+int
+kstat_named_to_pmAtom(const kstat_named_t *kn, pmAtomValue *atom)
+{
+ static char chardat[sizeof(kn->value.c) + 1];
+
+ switch (kn->data_type) {
+ case KSTAT_DATA_UINT64:
+ atom->ull = kn->value.ui64;
+ return 1;
+ case KSTAT_DATA_INT64:
+ atom->ull = kn->value.i64;
+ return 1;
+ case KSTAT_DATA_UINT32:
+ atom->ull = kn->value.ui32;
+ return 1;
+ case KSTAT_DATA_INT32:
+ atom->ull = kn->value.i32;
+ return 1;
+ case KSTAT_DATA_STRING:
+ atom->cp = kn->value.str.addr.ptr;
+ return 1;
+ case KSTAT_DATA_CHAR:
+ memcpy(chardat, kn->value.c, sizeof(kn->value.c));
+ chardat[sizeof(chardat)-1] = '\0';
+ atom->cp = chardat;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int
+kstat_fetch_named(kstat_ctl_t *kc, pmAtomValue *atom, char *metric,
+ int shift_bits)
+{
+ kstat_t *ks;
+
+ if ((ks = kstat_lookup(kc, "unix", -1, "system_pages")) != NULL) {
+ kstat_named_t *kn;
+
+ if (kstat_read(kc, ks, NULL) == -1)
+ return 0;
+
+ if (((kn = kstat_data_lookup(ks, metric)) != NULL) &&
+ kstat_named_to_pmAtom(kn, atom)) {
+ atom->ull = (atom->ull * pagesize) >> shift_bits;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+kstat_named_to_typed_atom(const kstat_named_t *kn, int pmtype,
+ pmAtomValue *atom)
+{
+ static char chardat[sizeof(kn->value.c) + 1];
+
+ switch (pmtype) {
+ case PM_TYPE_32:
+ if (kn->data_type == KSTAT_DATA_INT32) {
+ atom->l = kn->value.i32;
+ return 1;
+ }
+ break;
+ case PM_TYPE_U32:
+ if (kn->data_type == KSTAT_DATA_UINT32) {
+ atom->ul = kn->value.ui32;
+ return 1;
+ }
+ break;
+ case PM_TYPE_64:
+ if (kn->data_type == KSTAT_DATA_INT64) {
+ atom->ll = kn->value.i64;
+ return 1;
+ }
+ break;
+ case PM_TYPE_U64:
+ if (kn->data_type == KSTAT_DATA_UINT64) {
+ atom->ull = kn->value.ui64;
+ return 1;
+ }
+ break;
+ case PM_TYPE_STRING:
+ switch(kn->data_type) {
+ case KSTAT_DATA_STRING:
+ atom->cp = kn->value.str.addr.ptr;
+ return 1;
+ case KSTAT_DATA_CHAR:
+ memcpy(chardat, kn->value.c, sizeof(kn->value.c));
+ chardat[sizeof(chardat)-1] = '\0';
+ atom->cp = chardat;
+ return 1;
+ }
+ break;
+ }
+ return 0;
+}
+
+int
+sysinfo_fetch(pmdaMetric *mdesc, int inst, pmAtomValue *atom)
+{
+ __uint64_t ull;
+ int i;
+ int ok;
+ ptrdiff_t offset;
+ struct utsname u;
+ kstat_ctl_t *kc;
+
+ if ((kc = kstat_ctl_update()) == NULL)
+ return 0;
+
+ /* Special processing of metrics which notionally belong
+ * to sysinfo category */
+ switch (pmid_item(mdesc->m_desc.pmid)) {
+ case 109: /* hinv.physmem */
+ return kstat_fetch_named(kc, atom, "physmem", 20);
+ case 136: /* mem.physmem */
+ return kstat_fetch_named(kc, atom, "physmem", 10);
+ case 137: /* mem.freemem */
+ return kstat_fetch_named(kc, atom, "freemem", 10);
+ case 138: /* mem.lotsfree */
+ return kstat_fetch_named(kc, atom, "lotsfree", 10);
+ case 139: /* mem.availrmem */
+ return kstat_fetch_named(kc, atom, "availrmem", 10);
+
+ case 108: /* hinv.pagesize */
+ atom->ul = pagesize;
+ return 1;
+
+ case 107: /* pmda.uname */
+ if (uname(&u) < 0)
+ return 0;
+
+ snprintf(uname_full, sizeof(uname_full), "%s %s %s %s %s",
+ u.sysname, u.nodename, u.release, u.version, u.machine);
+ atom->cp = uname_full;
+ return 1;
+ case 135: /* kernel.all.load */
+ if (nloadavgs < 0) {
+ if ((nloadavgs = getloadavg(loadavgs, 3)) < 0)
+ return 0;
+ }
+
+ switch (inst) {
+ case 1:
+ atom->f = (float)loadavgs[LOADAVG_1MIN];
+ return nloadavgs > LOADAVG_1MIN;
+ case 5:
+ atom->f = (float)loadavgs[LOADAVG_5MIN];
+ return nloadavgs > LOADAVG_5MIN;
+ case 15:
+ atom->f = (float)loadavgs[LOADAVG_15MIN];
+ return nloadavgs > LOADAVG_15MIN;
+ }
+ return PM_ERR_INST;
+ }
+
+ ok = 1;
+ for (i = 0; i < ncpu; i++) {
+ if (inst == PM_IN_NULL || inst == i) {
+ if (!ctl[i].info_is_good) {
+ ctl[i].info_is_good = (ctl[i].info &&
+ (kstat_read(kc, ctl[i].info,
+ NULL) != -1));
+ }
+ if (ctl[i].fetched == 1)
+ continue;
+ if (kstat_read(kc, ctl[i].ksp, &ctl[i].cpustat) == -1) {
+ if (ctl[i].err == 0) {
+ fprintf(stderr, "Error: sysinfo_fetch(pmid=%s cpu=%d ...)\n", pmIDStr(mdesc->m_desc.pmid), i);
+ fprintf(stderr, "kstat_read(kc=%p, ksp=%p, ...) failed: %s\n", kc, ctl[i].ksp, osstrerror());
+ }
+ ctl[i].err++;
+ ctl[i].fetched = -1;
+ ok = 0;
+ }
+ else {
+ ctl[i].fetched = 1;
+ if (ctl[i].err != 0) {
+ fprintf(stderr, "Success: sysinfo_fetch(pmid=%s cpu=%d ...) after %d errors as previously reported\n",
+ pmIDStr(mdesc->m_desc.pmid), i, ctl[i].err);
+ ctl[i].err = 0;
+ }
+ }
+ }
+ }
+
+ if (!ok)
+ return 0;
+
+ ull = 0;
+ for (i = 0; i < ncpu; i++) {
+ if (inst == PM_IN_NULL || inst == i) {
+ offset = ((metricdesc_t *)mdesc->m_user)->md_offset;
+
+ if (offset == -1) {
+ ull += sysinfo_derived(mdesc, i);
+ } else if (offset > sizeof(ctl[i].cpustat)) {
+ char *stat = (char *)offset;
+ kstat_named_t *kn;
+
+ if (!ctl[i].info_is_good)
+ return 0;
+
+ if ((kn = kstat_data_lookup(ctl[i].info, stat)) == NULL) {
+ fprintf(stderr, "No kstat called %s for CPU %d\n", stat, i);
+ return 0;
+ }
+
+ return kstat_named_to_typed_atom(kn, mdesc->m_desc.type, atom);
+ } else {
+ /* all the kstat fields are 32-bit unsigned */
+ __uint32_t *ulp;
+ ulp = (__uint32_t *)&((char *)&ctl[i].cpustat)[offset];
+ ull += *ulp;
+#ifdef PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) == (DBG_TRACE_APPL0|DBG_TRACE_APPL2)) {
+ /* desperate */
+ fprintf(stderr, "sysinfo_fetch: pmid %s inst %d val %u\n",
+ pmIDStr(mdesc->m_desc.pmid), i, *ulp);
+ }
+#endif
+ }
+ }
+ }
+
+ if (mdesc->m_desc.units.dimTime == 1) {
+ /* sysinfo times are in ticks, and we export as 64-bit msec */
+ atom->ull = ull * 1000 / hz;
+ }
+ else if (mdesc->m_desc.type == PM_TYPE_U64) {
+ /* export as 64-bit value */
+ atom->ull = ull;
+ }
+ else {
+ /* else export as a 32-bit */
+ atom->ul = (__uint32_t)ull;
+ }
+
+ return 1;
+}
diff --git a/src/pmdas/solaris/vnops.c b/src/pmdas/solaris/vnops.c
new file mode 100644
index 0000000..69054d8
--- /dev/null
+++ b/src/pmdas/solaris/vnops.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2010 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* kstat has counters for vnode operations for each filesystem.
+ *
+ * Unfortunately the counters for mounted fileystems are mixed with counters
+ * for the filesystem types and there is no obvious way to distinguish
+ * between the two except by trying to convert the kstat's name to the number
+ * and see if works */
+
+#include <stdio.h>
+#include <kstat.h>
+#include <sys/mnttab.h>
+#include <sys/stat.h>
+#include "common.h"
+
+struct mountpoint {
+ struct mountpoint *next;
+ dev_t dev;
+ char mountpoint[];
+};
+
+static struct mountpoint *mountpoints;
+static struct timespec mtime;
+
+/* NB! The order of entires in mountopoints list is important:
+ * lofs mounts use the same device number but appear later
+ * in /etc/mnttab then the target filesystem - keeping the
+ * order the same as /etc/mnttab means that more "logical"
+ * mountpoints are reported, in particular the counters
+ * for "/" are not reported as /lib/libc.so.1 */
+static void
+cache_mnttab(void)
+{
+ FILE *f;
+ struct mnttab m;
+ struct mountpoint *mp;
+ struct stat sb;
+ struct mountpoint **tail = &mountpoints;
+
+ if (stat("/etc/mnttab", &sb) < 0)
+ return;
+
+ if (mountpoints &&
+ (sb.st_mtim.tv_sec == mtime.tv_sec) &&
+ (sb.st_mtim.tv_nsec == mtime.tv_nsec))
+ return;
+
+ if ((f = fopen("/etc/mnttab", "r")) == NULL)
+ return;
+
+ for (mp = mountpoints; mp; mp = mountpoints) {
+ mountpoints = mp->next;
+ free(mp);
+ }
+
+ while(getmntent(f, &m) == 0) {
+ char *devop= hasmntopt(&m, "dev");
+ if (devop) {
+ char *end;
+ dev_t d = strtoul(devop+4, &end, 16);
+
+ if ((end != devop+4) && (*end != '\0')) {
+ fprintf(stderr, "Bogus device number %s for filesystem %s\n",
+ devop+4, m.mnt_mountp);
+ continue;
+ }
+
+ mp = malloc(sizeof(*mp) + strlen(m.mnt_mountp) + 1);
+ if (mp == NULL) {
+ fprintf(stderr,
+ "Cannot allocate memory for cache entry of %s\n",
+ m.mnt_mountp);
+ continue;
+ }
+ mp->next = NULL;
+ mp->dev = d;
+ strcpy(mp->mountpoint, m.mnt_mountp);
+ *tail = mp;
+ tail = &mp->next;
+ }
+ }
+ fclose(f);
+ mtime = sb.st_mtim;
+}
+
+static const char *
+mountpoint_bydev(dev_t dev)
+{
+ int i;
+ for (i=0; i < 2; i++) {
+ struct mountpoint *mp = mountpoints;
+ while(mp) {
+ if (mp->dev == dev)
+ return mp->mountpoint;
+ mp = mp->next;
+ }
+ cache_mnttab();
+ }
+ return NULL;
+}
+
+int
+vnops_fetch(pmdaMetric *pm, int inst, pmAtomValue *av)
+{
+ char *fsname;
+ metricdesc_t *md = pm->m_user;
+ kstat_t *k;
+ char *stat = (char *)md->md_offset;
+
+ if (pmid_item(pm->m_desc.pmid) == 1023) { /* hinv.nfilesys */
+ int sts;
+ sts = pmdaCacheOp(indomtab[FILESYS_INDOM].it_indom, PMDA_CACHE_SIZE_ACTIVE);
+ if (sts < 0)
+ return 0;
+ else {
+ av->ul = sts;
+ return 1;
+ }
+ }
+
+ if (pmdaCacheLookup(pm->m_desc.indom, inst, &fsname,
+ (void **)&k) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (k) {
+ kstat_named_t *kn = kstat_data_lookup(k, stat);
+
+ if (kn == NULL) {
+ fprintf(stderr, "No kstat called %s for %s\n", stat, fsname);
+ return 0;
+ }
+
+ return kstat_named_to_typed_atom(kn, pm->m_desc.type, av);
+ }
+
+ return 0;
+}
+
+static void
+vnops_update_stats(int fetch)
+{
+ kstat_t *k;
+ kstat_ctl_t *kc = kstat_ctl_update();
+
+ if (kc == NULL)
+ return;
+
+ for (k = kc->kc_chain; k != NULL; k = k->ks_next) {
+ int rv;
+ kstat_t *cached;
+ const char *key;
+ dev_t dev;
+ char *end;
+ pmInDom indom;
+
+ if (strcmp(k->ks_module, "unix") ||
+ strncmp(k->ks_name, "vopstats_", sizeof("vopstats_")-1))
+ continue;
+
+ key = k->ks_name + 9;
+ dev = strtoul(key, &end, 16);
+ if ((end != key) && (*end == '\0')) {
+ indom = indomtab[FILESYS_INDOM].it_indom;
+ if ((key = mountpoint_bydev(dev)) == NULL)
+ continue;
+ } else {
+ indom = indomtab[FSTYPE_INDOM].it_indom;
+ }
+
+ if (pmdaCacheLookupName(indom, key, &rv,
+ (void **)&cached) != PMDA_CACHE_ACTIVE) {
+ rv = pmdaCacheStore(indom, PMDA_CACHE_ADD, key, k);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot create instance for "
+ "filesystem '%s': %s\n",
+ key, pmErrStr(rv));
+ continue;
+ }
+ }
+
+ if (fetch)
+ kstat_read(kc, k, NULL);
+ }
+}
+
+void
+vnops_refresh(void)
+{
+ pmdaCacheOp(indomtab[FILESYS_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(indomtab[FSTYPE_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ vnops_update_stats(1);
+ pmdaCacheOp(indomtab[FILESYS_INDOM].it_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(indomtab[FSTYPE_INDOM].it_indom, PMDA_CACHE_SAVE);
+}
+
+void
+vnops_init(int first)
+{
+ pmdaCacheOp(indomtab[FILESYS_INDOM].it_indom, PMDA_CACHE_LOAD);
+ pmdaCacheOp(indomtab[FSTYPE_INDOM].it_indom, PMDA_CACHE_LOAD);
+ vnops_update_stats(0);
+ pmdaCacheOp(indomtab[FILESYS_INDOM].it_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(indomtab[FSTYPE_INDOM].it_indom, PMDA_CACHE_SAVE);
+}
diff --git a/src/pmdas/solaris/zfs.c b/src/pmdas/solaris/zfs.c
new file mode 100644
index 0000000..9f4bc55
--- /dev/null
+++ b/src/pmdas/solaris/zfs.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <libzfs.h>
+
+#include "common.h"
+
+static libzfs_handle_t *zh;
+static int zf_added;
+
+struct zfs_data {
+ zfs_handle_t *zh;
+ uint64_t nsnaps;
+};
+
+/*
+ * For each filesystem or snapshot check if the name is in the
+ * corresponding instance cache. If it's not there then add it to the
+ * cache. If we've cached the new instance then we keep the zfs_handle
+ * which we've received in the argument, otherwise we need to close it
+ * - zfs_iter_root() expects that from us.
+ *
+ * For filesystems iterate over their snapshots and update snapshot
+ * count which is stored in the cached data for the instances in ZFS_INDOM
+ * domain.
+ */
+static int
+zfs_cache_inst(zfs_handle_t *zf, void *arg)
+{
+ const char *fsname = zfs_get_name(zf);
+ pmInDom zfindom;
+ int inst, rv;
+ struct zfs_data *zdata = NULL;
+ uint64_t *snapcnt = arg;
+
+ switch (zfs_get_type(zf)) {
+ case ZFS_TYPE_FILESYSTEM:
+ zfindom = indomtab[ZFS_INDOM].it_indom;
+ break;
+ case ZFS_TYPE_SNAPSHOT:
+ (*snapcnt)++;
+ zfindom = indomtab[ZFS_SNAP_INDOM].it_indom;
+ break;
+ default:
+ zfs_close(zf);
+ return 0;
+ }
+
+ if ((rv = pmdaCacheLookupName(zfindom, fsname, &inst,
+ (void **)&zdata)) == PMDA_CACHE_ACTIVE) {
+ zfs_close(zf);
+ zfs_refresh_properties(zdata->zh);
+ zf = zdata->zh;
+ } else if ((rv == PMDA_CACHE_INACTIVE) && zdata) {
+ rv = pmdaCacheStore(zfindom, PMDA_CACHE_ADD, fsname, zdata);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot reactivate cached data for '%s': %s\n",
+ fsname, pmErrStr(rv));
+ zfs_close(zf);
+ return 0;
+ }
+ zfs_close(zf);
+ zfs_refresh_properties(zdata->zh);
+ zf = zdata->zh;
+ } else {
+ if ((zdata = calloc(1, sizeof(*zdata))) == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "Out of memory for data of %s\n", fsname);
+ zfs_close(zf);
+ return 0;
+ }
+ zdata->zh = zf;
+ rv = pmdaCacheStore(zfindom, PMDA_CACHE_ADD, fsname, zdata);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot cache data for '%s': %s\n",
+ fsname, pmErrStr(rv));
+ zfs_close(zf);
+ return 0;
+ }
+ zf_added++;
+ }
+
+ zfs_iter_filesystems(zf, zfs_cache_inst, NULL);
+ if (zfs_get_type(zf) == ZFS_TYPE_FILESYSTEM) {
+ zdata->nsnaps = 0;
+ zfs_iter_snapshots(zf, zfs_cache_inst, &zdata->nsnaps);
+ }
+
+ return 0;
+}
+
+void
+zfs_refresh(void)
+{
+ zf_added = 0;
+
+ pmdaCacheOp(indomtab[ZFS_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ pmdaCacheOp(indomtab[ZFS_SNAP_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ zfs_iter_root(zh, zfs_cache_inst, NULL);
+
+ if (zf_added) {
+ pmdaCacheOp(indomtab[ZFS_INDOM].it_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(indomtab[ZFS_SNAP_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
+
+int
+zfs_fetch(pmdaMetric *pm, int inst, pmAtomValue *atom)
+{
+ char *fsname;
+ metricdesc_t *md = pm->m_user;
+ struct zfs_data *zdata;
+ uint64_t v;
+
+ if (pmdaCacheLookup(pm->m_desc.indom, inst, &fsname,
+ (void **)&zdata) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (md->md_offset == -1) { /* nsnapshot */
+ atom->ull = zdata->nsnaps;
+ return 1;
+ }
+
+ v = zfs_prop_get_int(zdata->zh, md->md_offset);
+
+ /* Special processing - compression ratio is in precent, we export
+ * it as multiplier */
+ switch (md->md_offset) {
+ case ZFS_PROP_COMPRESSRATIO:
+ atom->d = v / 100.0;
+ break;
+ default:
+ atom->ull = v;
+ break;
+ }
+
+ return 1;
+}
+
+void
+zfs_init(int first)
+{
+ if (zh)
+ return;
+
+ zh = libzfs_init();
+ if (zh) {
+ pmdaCacheOp(indomtab[ZFS_INDOM].it_indom, PMDA_CACHE_LOAD);
+ pmdaCacheOp(indomtab[ZFS_SNAP_INDOM].it_indom, PMDA_CACHE_LOAD);
+ zfs_iter_root(zh, zfs_cache_inst, &first);
+ pmdaCacheOp(indomtab[ZFS_INDOM].it_indom, PMDA_CACHE_SAVE);
+ pmdaCacheOp(indomtab[ZFS_SNAP_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
diff --git a/src/pmdas/solaris/zpool.c b/src/pmdas/solaris/zpool.c
new file mode 100644
index 0000000..2f402f2
--- /dev/null
+++ b/src/pmdas/solaris/zpool.c
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <libzfs.h>
+
+#include "common.h"
+
+struct zpool_stats {
+ int vdev_stats_fresh;
+ vdev_stat_t vds;
+};
+
+static libzfs_handle_t *zh;
+static int zp_added;
+
+/*
+ * For each zpool check the name in the instance cache, if it's not there then
+ * add it to the cache. Regardless if it's the first time we've seen this one
+ * or if it was in the cache before refresh the stats
+ */
+static int
+zp_cache_pool(zpool_handle_t *zp, void *arg)
+{
+ nvlist_t *cfg = zpool_get_config(zp, NULL);
+ char *zpname = (char *)zpool_get_name(zp);
+ struct zpool_stats *zps = NULL;
+ pmInDom zpindom = indomtab[ZPOOL_INDOM].it_indom;
+ uint_t cnt = 0;
+ vdev_stat_t *vds;
+ int rv;
+ int inst;
+ nvlist_t *vdt;
+
+ if ((rv = pmdaCacheLookupName(zpindom, zpname, &inst,
+ (void **)&zps)) != PMDA_CACHE_ACTIVE) {
+ int newpool = (zps == NULL);
+
+ if (rv != PMDA_CACHE_INACTIVE || zps == NULL) {
+ zps = malloc(sizeof(*zps));
+ if (zps == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot allocate memory to hold stats for "
+ "zpool '%s'\n",
+ zpname);
+ goto done;
+ }
+ }
+
+ rv = pmdaCacheStore(zpindom, PMDA_CACHE_ADD, zpname, zps);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot add '%s' to the cache "
+ "for instance domain %s: %s\n",
+ zpname, pmInDomStr(zpindom), pmErrStr(rv));
+ free(zps);
+ goto done;
+ }
+ zp_added += newpool;
+ }
+
+ rv = nvlist_lookup_nvlist(cfg, ZPOOL_CONFIG_VDEV_TREE, &vdt);
+ if (rv != 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot get vdev tree for '%s': %d %d\n",
+ zpname, rv, oserror());
+ zps->vdev_stats_fresh = 0;
+ } else {
+ /* accommodate zpool api changes ... */
+#ifdef ZPOOL_CONFIG_VDEV_STATS
+ rv = nvlist_lookup_uint64_array(vdt, ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t **)&vds, &cnt);
+#else
+ rv = nvlist_lookup_uint64_array(vdt, ZPOOL_CONFIG_STATS,
+ (uint64_t **)&vds, &cnt);
+#endif
+ if (rv == 0) {
+ memcpy(&zps->vds, vds, sizeof(zps->vds));
+ zps->vdev_stats_fresh = 1;
+ } else {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot get zpool stats for '%s': %d %d\n",
+ zpname, rv, oserror());
+ zps->vdev_stats_fresh = 0;
+ }
+ }
+
+done:
+ zpool_close(zp);
+ return 0;
+}
+
+void
+zpool_refresh(void)
+{
+ zp_added = 0;
+
+ pmdaCacheOp(indomtab[ZPOOL_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ zpool_iter(zh, zp_cache_pool, NULL);
+
+ if (zp_added) {
+ pmdaCacheOp(indomtab[ZPOOL_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
+
+int
+zpool_fetch(pmdaMetric *pm, int inst, pmAtomValue *atom)
+{
+ struct zpool_stats *zps;
+ char *zpname;
+ metricdesc_t *md = pm->m_user;
+
+ if (pmdaCacheLookup(indomtab[ZPOOL_INDOM].it_indom, inst, &zpname,
+ (void **)&zps) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (zps->vdev_stats_fresh) {
+ switch (pmid_item(md->md_desc.pmid)) {
+ case 0: /* zpool.state */
+ atom->cp = zpool_state_to_name(zps->vds.vs_state, zps->vds.vs_aux);
+ break;
+ case 1: /* zpool.state_int */
+ atom->ul = (zps->vds.vs_aux << 8) | zps->vds.vs_state;
+ break;
+ default:
+ memcpy(&atom->ull, ((char *)&zps->vds) + md->md_offset,
+ sizeof(atom->ull));
+ }
+ }
+ return zps->vdev_stats_fresh;
+}
+
+void
+zpool_init(int first)
+{
+ if (zh)
+ return;
+
+ zh = libzfs_init();
+ if (zh) {
+ pmdaCacheOp(indomtab[ZPOOL_INDOM].it_indom, PMDA_CACHE_LOAD);
+ zpool_iter(zh, zp_cache_pool, NULL);
+ pmdaCacheOp(indomtab[ZPOOL_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
diff --git a/src/pmdas/solaris/zpool_perdisk.c b/src/pmdas/solaris/zpool_perdisk.c
new file mode 100644
index 0000000..f9646e9
--- /dev/null
+++ b/src/pmdas/solaris/zpool_perdisk.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2009 Max Matveev. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <libzfs.h>
+
+#include "common.h"
+
+struct vdev_stats {
+ int vdev_stats_fresh;
+ vdev_stat_t vds;
+};
+
+static libzfs_handle_t *zh;
+static int vdev_added;
+
+static char *
+make_vdev_name(zpool_handle_t *zp, const char *pname, nvlist_t *child)
+{
+ char *name = NULL;
+ char *cname = zpool_vdev_name(zh, zp, child, B_FALSE);
+ uint_t size;
+
+ if (cname == NULL) {
+ __pmNotifyErr(LOG_WARNING, "Cannot get the name of %s\'s "
+ "child\n", pname);
+ goto out;
+ }
+ size = strlen(pname) + strlen(cname) + 2;
+ name = malloc(size);
+ if (name == NULL) {
+ __pmNotifyErr(LOG_WARNING, "Cannot allocate memory for %s.%s\n",
+ pname, cname);
+ goto free_out;
+ }
+ snprintf(name, size, "%s.%s", pname, cname);
+free_out:
+ free(cname);
+out:
+ return name;
+}
+
+/*
+ * get the names and stats of those vdevs in the pool that are disks and are
+ * either children, cache or spare devices
+ */
+static int
+zp_get_vdevs(zpool_handle_t *zp, char *zpname, nvlist_t *vdt,
+ char ***vdev_names,
+ vdev_stat_t ***vds, int *num)
+{
+ int rv = 0;
+ uint_t cnt;
+ char **new_vdev_names;
+ vdev_stat_t **new_vds;
+ int nelem = *num;
+
+ char *name;
+ vdev_stat_t *stats;
+ nvlist_t **children;
+ uint_t nchildren;
+
+ static const char *prop[] = {
+ ZPOOL_CONFIG_CHILDREN,
+ ZPOOL_CONFIG_L2CACHE,
+ ZPOOL_CONFIG_SPARES
+ };
+ int i;
+ int j;
+ char *vdev_type;
+
+ rv = nvlist_lookup_string(vdt, ZPOOL_CONFIG_TYPE, &vdev_type);
+ /* we've found disk, look no further */
+ if (rv == 0 && strcmp(vdev_type, "disk") == 0) {
+
+ /* accommodate zpool api changes ... */
+#ifdef ZPOOL_CONFIG_VDEV_STATS
+ rv = nvlist_lookup_uint64_array(vdt, ZPOOL_CONFIG_VDEV_STATS,
+ (uint64_t **)&stats, &cnt);
+#else
+ rv = nvlist_lookup_uint64_array(vdt, ZPOOL_CONFIG_STATS,
+ (uint64_t **)&stats, &cnt);
+#endif
+ if (rv != 0) {
+ __pmNotifyErr(LOG_WARNING, "Cannot get the stats of %s\'s "
+ "child\n", zpname);
+ goto out;
+ }
+ name = make_vdev_name(zp, zpname, vdt);
+ if (name == NULL) {
+ __pmNotifyErr(LOG_WARNING, "Cannot get the name of a %s\'s "
+ "disk\n", zpname);
+ goto out;
+ }
+ nelem++;
+ new_vdev_names = realloc(*vdev_names, nelem *
+ sizeof(*new_vdev_names));
+ if (new_vdev_names == NULL) {
+ __pmNotifyErr(LOG_WARNING, "Cannot realloc memory for %s\n",
+ name);
+ goto free_out;
+ }
+ new_vdev_names[nelem - 1] = NULL;
+ *vdev_names = new_vdev_names;
+
+ new_vds = realloc(*vds, nelem * sizeof(*new_vds));
+ if (new_vds == NULL) {
+ __pmNotifyErr(LOG_WARNING, "Cannot realloc memory for vds %s\n",
+ name);
+ goto free_out;
+ }
+ new_vds[nelem - 1] = stats;
+ new_vdev_names[nelem - 1] = name;
+
+ *vds = new_vds;
+ *num = nelem;
+ goto out;
+ }
+
+ /* not a disk, traversing children until we find all the disks */
+ for (i = 0; i < sizeof(prop) / sizeof(prop[0]); i++) {
+ rv = nvlist_lookup_nvlist_array(vdt, prop[i], &children,
+ &nchildren);
+ if (rv != 0)
+ nchildren = 0;
+ for (j = 0; j < nchildren; j++) {
+ zp_get_vdevs(zp, zpname, children[j], vdev_names, vds, num);
+ }
+ }
+ return 0;
+out:
+ return rv;
+free_out:
+ free(name);
+ return rv;
+}
+
+/*
+ * For each zpool, check the leaf vdev names that are disks in the instance
+ * cache, if one is not there then add it to the cache. Regardless if it's the
+ * first time we've seen it or if it was in the cache before refresh the stats
+ */
+static int
+zp_cache_vdevs(zpool_handle_t *zp, void *arg)
+{
+ nvlist_t *cfg = zpool_get_config(zp, NULL);
+ char *zpname = (char *)zpool_get_name(zp);
+ struct vdev_stats *zps = NULL;
+ pmInDom zpindom = indomtab[ZPOOL_PERDISK_INDOM].it_indom;
+ int rv;
+ int inst;
+ nvlist_t *vdt;
+
+ int i;
+ char **vdev_names = NULL;
+ vdev_stat_t **vds = NULL;
+ int num = 0;
+
+ rv = nvlist_lookup_nvlist(cfg, ZPOOL_CONFIG_VDEV_TREE, &vdt);
+ if (rv != 0) {
+ __pmNotifyErr(LOG_ERR, "Cannot get vdev tree for '%s': %d %d\n",
+ zpname, rv, oserror());
+ goto done;
+ }
+
+ rv = zp_get_vdevs(zp, zpname, vdt, &vdev_names, &vds, &num);
+ if (rv != 0) {
+ __pmNotifyErr(LOG_WARNING, "Cannot get vdevs for zpool '%s'\n",
+ zpname);
+ goto free_done;
+ }
+
+ for (i = 0; i < num; i++) {
+ if (vdev_names[i] == NULL)
+ continue;
+ rv = pmdaCacheLookupName(zpindom, vdev_names[i], &inst,
+ (void **)&zps);
+ if (rv != PMDA_CACHE_ACTIVE) {
+ int new_vdev = (zps == NULL);
+
+ if (rv != PMDA_CACHE_INACTIVE || new_vdev) {
+ zps = malloc(sizeof(*zps));
+ if (zps == NULL) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot allocate memory to hold stats for "
+ "vdev '%s'\n", vdev_names[i]);
+ goto free_done;
+ }
+ }
+
+ rv = pmdaCacheStore(zpindom, PMDA_CACHE_ADD, vdev_names[i],
+ zps);
+ if (rv < 0) {
+ __pmNotifyErr(LOG_WARNING,
+ "Cannot add '%s' to the cache "
+ "for instance domain %s: %s\n",
+ vdev_names[i], pmInDomStr(zpindom),
+ pmErrStr(rv));
+ free(zps);
+ goto free_done;
+ }
+ vdev_added += new_vdev;
+ }
+
+ if (rv >= 0) {
+ memcpy(&zps->vds, vds[i], sizeof(zps->vds));
+ zps->vdev_stats_fresh = 1;
+ } else {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot get stats for '%s': %d %d\n",
+ vdev_names[i], rv, oserror());
+ zps->vdev_stats_fresh = 0;
+ }
+ }
+free_done:
+ for (i = 0; i < num; i++)
+ free(vdev_names[i]);
+ free(vdev_names);
+ free(vds);
+done:
+ zpool_close(zp);
+ return 0;
+}
+
+void
+zpool_perdisk_refresh(void)
+{
+ vdev_added = 0;
+
+ pmdaCacheOp(indomtab[ZPOOL_PERDISK_INDOM].it_indom, PMDA_CACHE_INACTIVE);
+ zpool_iter(zh, zp_cache_vdevs, NULL);
+
+ if (vdev_added) {
+ pmdaCacheOp(indomtab[ZPOOL_PERDISK_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
+
+int
+zpool_perdisk_fetch(pmdaMetric *pm, int inst, pmAtomValue *atom)
+{
+ struct vdev_stats *stats;
+ char *vdev_name;
+ metricdesc_t *md = pm->m_user;
+
+ if (pmdaCacheLookup(indomtab[ZPOOL_PERDISK_INDOM].it_indom, inst,
+ &vdev_name, (void **)&stats) != PMDA_CACHE_ACTIVE)
+ return PM_ERR_INST;
+
+ if (stats->vdev_stats_fresh) {
+ switch (pmid_item(md->md_desc.pmid)) {
+ case 0: /* zpool.perdisk.state */
+ atom->cp = zpool_state_to_name(stats->vds.vs_state,
+ stats->vds.vs_aux);
+ break;
+ case 1: /* zpool.perdisk.state_int */
+ atom->ul = (stats->vds.vs_aux << 8) | stats->vds.vs_state;
+ break;
+ default:
+ memcpy(&atom->ull, ((char *)&stats->vds) + md->md_offset,
+ sizeof(atom->ull));
+ }
+ }
+
+ return stats->vdev_stats_fresh;
+}
+
+void
+zpool_perdisk_init(int first)
+{
+ if (zh)
+ return;
+
+ zh = libzfs_init();
+ if (zh) {
+ pmdaCacheOp(indomtab[ZPOOL_PERDISK_INDOM].it_indom, PMDA_CACHE_LOAD);
+ zpool_iter(zh, zp_cache_vdevs, NULL);
+ pmdaCacheOp(indomtab[ZPOOL_PERDISK_INDOM].it_indom, PMDA_CACHE_SAVE);
+ }
+}
diff --git a/src/pmdas/summary/GNUmakefile b/src/pmdas/summary/GNUmakefile
new file mode 100644
index 0000000..092e30d
--- /dev/null
+++ b/src/pmdas/summary/GNUmakefile
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = summary
+DOMAIN = SYSSUMMARY
+TARGETS = $(IAM)$(EXECSUFFIX)
+
+HFILES = summary.h
+CFILES = summary.c pmda.c mainloop.c
+
+LSRCFILES = Install README Remove help pmns root summary.pmie
+LLDFLAGS= -L$(TOPDIR)/src/libpcp/src -L$(TOPDIR)/src/libpcp_pmda/src
+LLDLIBS = $(PCP_PMDALIB)
+LDIRT = domain.h *.log *.dir *.pag $(TARGETS)
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+default: build-me
+
+include $(TOPDIR)/src/include/buildrules
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(TARGETS)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 root README help pmns domain.h $(PMDADIR)
+ $(INSTALL) -m 644 summary.pmie $(PMDADIR)/expr.pmie
+else
+build-me:
+install:
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+mainloop.o summary.o pmda.o: summary.h
+summary.o pmda.o: domain.h
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_%: default
+ @true
+
+install_%: install
+ @true
diff --git a/src/pmdas/summary/Install b/src/pmdas/summary/Install
new file mode 100644
index 0000000..590247a
--- /dev/null
+++ b/src/pmdas/summary/Install
@@ -0,0 +1,51 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the summary PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=summary
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+
+if $do_pmda
+then
+ if [ ! -x $PCP_BIN_DIR/pmie ]
+ then
+ echo \
+'Error: The "summary" PMDA requires the pmie(1) application but this
+ does not appear to be installed.'
+ exit 1
+ fi
+
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Interval between summary expression evaluation (seconds)? [10] ""$PCP_ECHO_C"
+ read delta
+ [ -z "$delta" ] && delta=10
+ [ -z "`echo $delta | tr -d '[0-9]'`" ] && break
+ echo "Error: interval \"$delta\" must be an integer, please try again"
+ done
+ args="$PCP_BIN_DIR/pmie -x -t $delta $PCP_PMDAS_DIR/summary/expr.pmie"
+ check_delay=15
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/summary/README b/src/pmdas/summary/README
new file mode 100644
index 0000000..c07fddd
--- /dev/null
+++ b/src/pmdas/summary/README
@@ -0,0 +1,255 @@
+Performance Co-Pilot PMDA for Exporting Metric Summaries
+========================================================
+
+This Performance Metrics Domain Agent (PMDA) is capable of collecting
+performance metrics values from other PMDAs, computing derived
+(summary) values, and exporting these derived values as performance
+metrics.
+
+This agent uses the Performance Metrics Inference Engine pmie(1) to
+periodically collect the data and compute the summary values. These
+derived values are typically computed by expressions that aggregate a
+number of base performance values, perhaps from a number of subsystems
+on the one host or even from multiple hosts, and perhaps over an
+extended period of time.
+
+All of the exported metrics have a singular instance and the values are
+"instantaneous", i.e. the exported value is the value as of the last
+time the summary was computed. Refer to the PMAPI(3) man page for more
+information about these terms.
+
+Metrics
+=======
+
+See the file ./help, or install the agent and execute the command
+
+ $ pminfo -fT summary
+
+Note that customization of the metrics made available by the summary
+PMDA is possible, as described below.
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/summary
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + This PMDA caches the most recent value for the performance metrics
+ computed by pmie(1). The cached values are the ones returned via
+ the Performance Metrics Collection Demon pmcd(1) to clients. By
+ default pmie(1) evaluates the expressions once every 10 seconds.
+ The installation procedure will offer you the option to change this
+ interval.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted for the necessary information to set up
+ the summary agent.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/summary
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/summary.log) should be checked for any warnings
+ or errors.
+
+Customization
+=============
+
+New summary metrics may be added as follows.
+
+ + Choose new Performance Metric Name Space (PMNS) names for the new
+ metrics. These must begin with "summary." and follow the rules
+ described in pmns(4).
+
+ For example summary.fs.wr_cache_hit and summary.fs.rd_cache_hit
+
+ + Edit the file ./pmns to add the new PMNS names in the format
+ described in pmns(4). You must choose a unique Performance Metric
+ Id (PMID) for each metric ... in the ./pmns file these will appear
+ as SYSSUMMARY:0:x for some x that is arbitrary in the range 0 to
+ 1023 and unique in this file.
+
+ For example
+
+ summary {
+ cpu
+ disk
+ netif
+ /*new*/ fs
+ }
+
+ ...
+
+ summary.fs {
+ wr_cache_hit SYSSUMMARY:0:10
+ rd_cache_hit SYSSUMMARY:0:11
+ }
+
+ + Use the local fake PMNS ./root and validate that the PMNS changes
+ are correct.
+
+ For example
+
+ $ pminfo -n root -m summary.fs
+ summary.fs.wr_cache_hit PMID: 27.0.10
+ summary.fs.rd_cache_hit PMID: 27.0.11
+
+ + Create a file (./expr.pmie in the examples below) containing the
+ new expressions. If the name to the left of the assignment
+ operator (=) is one of the PMNS names, then the pmie(1) expression
+ to the right will be evaluated and returned by the summary PMDA.
+ The expression must return a numeric value, which is exported as a
+ double precision floating point number.
+
+ If the expression has a set value, then only the first value is
+ exported (in most cases, the pmie aggregate operators should be used
+ to produce a scalar sum or average from a set of numeric values).
+
+ The exported metric has the dimension (space, time and count) of
+ the expression, and the scale is the canonical scale used by
+ pmie(1), namely bytes, seconds and counts.
+
+ For example
+
+ // filesystem buffer cache hit percentages
+ prefix = "kernel.all.io"; // variable, not exported
+ summary.fs.wr_cache_hit =
+ 100 - 100 * $prefix.bwrite / $prefix.lwrite;
+ summary.fs.rd_cache_hit =
+ 100 - 100 * $prefix.bread / $prefix.lread;
+
+ + Run pmie in debug mode to verify the expressions are being
+ evaluated correctly, and the values make sense.
+
+ For example
+
+ $ pmie -t2 -v expr.pmie
+ summary.fs.wr_cache_hit: ?
+ summary.fs.rd_cache_hit: ?
+
+ summary.fs.wr_cache_hit: 45.83
+ summary.fs.rd_cache_hit: 83.2
+
+ summary.fs.wr_cache_hit: 39.22
+ summary.fs.rd_cache_hit: 84.51
+
+ Once you are happy with the new expressions, add them to the existing
+ expressions in the file ./summary.pmie.
+
+ + Edit the ./help file to add help text for the new metrics ... see
+ newhelp(1) for a description of the syntax.
+
+ For example
+
+ @ summary.fs.wr_cache_hit Filesystem cache read hit ratio
+ Percentage of filesystem block writes that involve a block currently
+ found in the filesystem cache.
+
+ @ summary.fs.rd_cache_hit Filesystem cache write hit ratio
+ Percentage of filesystem block reads that involve a block currently
+ found in the filesystem cache, and thereby avoid a physical read.
+
+ + Install the new PMDA
+
+ For example
+
+ # ./Install
+
+ You will need to choose an appropriate configuration for installation of
+ the "summary" Performance Metrics Domain Agent (PMDA).
+
+ collector collect performance statistics on this system
+ monitor allow this system to monitor local and/or remote systems
+ both collector and monitor configuration for this system
+ Please enter c(ollector) or m(onitor) or b(oth) [b]
+
+ Updating the Performance Metrics Name Space ...
+ Installing pmchart view(s) ...
+
+ Interval between summary expression evaluation (seconds)? [10]
+ Terminate PMDA if already installed ...
+ Installing files ..
+ rm -f help.pag help.dir
+ $PCP_BINADM_DIR/newhelp help
+ Updating the PMCD control file, and notifying PMCD ...
+ Wait 15 seconds for the agent to initialize ...
+ Check summary metrics have appeared ... 8 metrics and 8 values
+
+ + Check the metrics ...
+
+ For example
+
+ $ pminfo -fT summary.fs
+
+ summary.fs.wr_cache_hit
+ Help:
+ Percentage of filesystem block writes that involve a block currently
+ found in the filesystem cache.
+ value 11.97916666666666
+
+ summary.fs.rd_cache_hit
+ Help:
+ Percentage of filesystem block reads that involve a block currently
+ found in the filesystem cache, and thereby avoid a physical read.
+ value 74.31192660550458
+
+ $ pmval -t5 -s4 summary.fs.wr_cache_hit
+
+ metric: summary.fs.wr_cache_hit
+ host: localhost
+ semantics: instantaneous value
+ units: none
+ samples: 8
+ interval: 5.00 sec
+
+ 63.60132158590308
+ 62.71878646441073
+ 62.71878646441073
+ 58.73968492123031
+ 58.73968492123031
+ 65.33822758259046
+ 65.33822758259046
+ 72.6099706744868
+
+ Note the values are being sampled here by pmval(1) every 5 seconds,
+ but pmie(1) is only passing new values to the sample PMDA every
+ 10 seconds. Both rates could be changed to suit the dynamics of
+ your new metrics.
+
+ + Create pmchart(1) views, pmview(1) scenes and pmlogger(1)
+ configurations to monitor and archive your new performance
+ metrics.
+
+ For example, a pmchart view could be created using pmchart and
+ the View->Save Configuration menu option. Copy the view from
+ $HOME/.pcp/pmchart into $PCP_PMDAS_DIR/summary and rename it to have
+ the suffix ".pmchart", and a prefix that identifies the view and
+ is unique amongst the view names in $PCP_VAR_DIR/config/pmchart,
+ e.g. Summary.FScache.pmchart
+
+ Then
+ # cp Summary.FScache.pmchart $PCP_VAR_DIR/config/pmchart/Summary.FScache
+
+ will install the view in a place where pmchart(1) will be able
+ to find it. Provided the name of the file ends in ".pmchart" in
+ $PCP_PMDAS_DIR/summary the view will be re-installed as a side-effect
+ of any subsequent ./Install of the summary PMDA.
diff --git a/src/pmdas/summary/Remove b/src/pmdas/summary/Remove
new file mode 100644
index 0000000..0ab6344
--- /dev/null
+++ b/src/pmdas/summary/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the summary PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=summary
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/summary/help b/src/pmdas/summary/help
new file mode 100644
index 0000000..5130a10
--- /dev/null
+++ b/src/pmdas/summary/help
@@ -0,0 +1,56 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# summary PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ summary.cpu.util CPU utilization
+Fraction of time spent executing in user or system mode, averaged
+over all CPUs. Any time not accountered for here is spent either
+idle or waiting for I/O. Value in the range 0 to 1.
+
+@ summary.cpu.busy Proportion of the CPUs that are busy
+Fraction of the CPUs busy executing in user and/or system mode for more
+than 70% of the time. Value in the range 0 to 1.
+
+@ summary.disk.iops Average disk IOPS
+Average disk throughput in IO operations per second.
+
+@ summary.disk.busy Proportion of the disks that are busy
+Fraction of the disks busy serving at least 30 IOPs. Value in the
+range 0 to 1.
+
+@ summary.netif.packets Average network interface throughput
+Average network interface throughput in packets per second.
+
+@ summary.netif.busy Proportion of the network interfaces that are busy
+Fraction of network interfaces busy serving at least 375 packets per
+second). Value in the range 0 to 1.
+
diff --git a/src/pmdas/summary/mainloop.c b/src/pmdas/summary/mainloop.c
new file mode 100644
index 0000000..e59a467
--- /dev/null
+++ b/src/pmdas/summary/mainloop.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "summary.h"
+
+static void (*freeResultCallback)(pmResult *) = __pmFreeResultValues;
+
+void
+mainLoopFreeResultCallback(void (*callback)(pmResult *res))
+{
+ freeResultCallback = callback;
+}
+
+void
+summaryMainLoop(char *pmdaname, int clientfd, pmdaInterface *dtp)
+{
+ __pmPDU *pb_pmcd;
+ __pmPDU *pb_client;
+ int sts;
+ pmID pmid;
+ pmDesc desc;
+ int npmids;
+ pmID *pmidlist;
+ pmResult *result;
+ int ctxnum;
+ __pmTimeval when;
+ int ident;
+ int type;
+ pmInDom indom;
+ int inst;
+ char *name;
+ __pmInResult *inres;
+ char *buffer;
+ __pmProfile *profile;
+ __pmProfile *saveprofile = NULL;
+ static fd_set readFds;
+ int maxfd;
+ int clientReady, pmcdReady;
+ int infd, outfd;
+
+ if (dtp->comm.pmda_interface != PMDA_INTERFACE_2) {
+ __pmNotifyErr(LOG_CRIT,
+ "summaryMainLoop supports PMDA protocol version 2 only, "
+ "not %d\n", dtp->comm.pmda_interface);
+ exit(1);
+ } else {
+ infd = dtp->version.two.ext->e_infd;
+ outfd = dtp->version.two.ext->e_outfd;
+ }
+
+ maxfd = infd+1;
+ if (clientfd >= maxfd)
+ maxfd = clientfd+1;
+
+ for ( ;; ) {
+ FD_ZERO(&readFds);
+ FD_SET(infd, &readFds);
+ FD_SET(clientfd, &readFds);
+
+ /* select here : block if nothing to do */
+ sts = select(maxfd, &readFds, NULL, NULL, NULL);
+
+ clientReady = FD_ISSET(clientfd, &readFds);
+ pmcdReady = FD_ISSET(infd, &readFds);
+
+ if (sts < 0)
+ break;
+ if (sts == 0)
+ continue;
+
+ if (clientReady) {
+ /*
+ * Service the command/client
+ */
+ sts = __pmGetPDU(clientfd, ANY_SIZE, TIMEOUT_NEVER, &pb_client);
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "client __pmGetPDU: %s\n", pmErrStr(sts));
+ if (sts <= 0)
+ /* End of File or error */
+ goto done;
+
+ service_client(pb_client);
+ __pmUnpinPDUBuf(pb_client);
+ }
+
+ if (pmcdReady) {
+ /* service pmcd */
+ sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_NEVER, &pb_pmcd);
+
+ if (sts < 0)
+ __pmNotifyErr(LOG_ERR, "__pmGetPDU: %s\n", pmErrStr(sts));
+ if (sts <= 0)
+ /* End of File or error */
+ goto done;
+
+ switch (sts) {
+
+ case PDU_PROFILE:
+ /*
+ * can ignore ctxnum, since pmcd has already used this to send
+ * the correct profile, if required
+ */
+ if ((sts = __pmDecodeProfile(pb_pmcd, &ctxnum, &profile)) >= 0)
+ sts = dtp->version.two.profile(profile,
+ dtp->version.two.ext);
+ if (sts < 0)
+ __pmSendError(outfd, FROM_ANON, sts);
+ else {
+ if (saveprofile != NULL)
+ free(saveprofile);
+ /*
+ * need to keep the last valid one around, as the DSO
+ * routine just remembers the address
+ */
+ saveprofile = profile;
+ }
+ break;
+
+ case PDU_FETCH:
+ /*
+ * can ignore ctxnum, since pmcd has already used this to send
+ * the correct profile, if required
+ */
+ sts = __pmDecodeFetch(pb_pmcd, &ctxnum, &when, &npmids, &pmidlist);
+
+ /* Ignore "when"; pmcd should intercept archive log requests */
+ if (sts >= 0) {
+ sts = dtp->version.two.fetch(npmids, pmidlist, &result,
+ dtp->version.two.ext);
+ __pmUnpinPDUBuf(pmidlist);
+ }
+ if (sts < 0)
+ __pmSendError(outfd, FROM_ANON, sts);
+ else {
+ int st;
+ st =__pmSendResult(outfd, FROM_ANON, result);
+ if (st < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "Cannot send fetch result: %s\n",
+ pmErrStr(st));
+ }
+ (*freeResultCallback)(result);
+ }
+ break;
+
+ case PDU_DESC_REQ:
+ if ((sts = __pmDecodeDescReq(pb_pmcd, &pmid)) >= 0) {
+ sts = dtp->version.two.desc(pmid, &desc,
+ dtp->version.two.ext);
+ }
+ if (sts < 0)
+ __pmSendError(outfd, FROM_ANON, sts);
+ else
+ __pmSendDesc(outfd, FROM_ANON, &desc);
+ break;
+
+ case PDU_INSTANCE_REQ:
+ if ((sts = __pmDecodeInstanceReq(pb_pmcd, &when, &indom, &inst, &name)) >= 0) {
+ /*
+ * Note: when is ignored.
+ * If we get this far, we are _only_ dealing
+ * with current data (pmcd handles the other
+ * cases).
+ */
+ sts = dtp->version.two.instance(indom, inst, name,
+ &inres,
+ dtp->version.two.ext);
+ }
+ if (sts < 0)
+ __pmSendError(outfd, FROM_ANON, sts);
+ else {
+ __pmSendInstance(outfd, FROM_ANON, inres);
+ __pmFreeInResult(inres);
+ }
+ break;
+
+ case PDU_TEXT_REQ:
+ if ((sts = __pmDecodeTextReq(pb_pmcd, &ident, &type)) >= 0) {
+ sts = dtp->version.two.text(ident, type, &buffer,
+ dtp->version.two.ext);
+ }
+ if (sts < 0)
+ __pmSendError(outfd, FROM_ANON, sts);
+ else
+ __pmSendText(outfd, FROM_ANON, ident, buffer);
+ break;
+
+ case PDU_RESULT:
+ if ((sts = __pmDecodeResult(pb_pmcd, &result)) >= 0)
+ sts = dtp->version.two.store(result,
+ dtp->version.two.ext);
+ __pmSendError(outfd, FROM_ANON, sts);
+ pmFreeResult(result);
+ break;
+
+ case PDU_ERROR:
+ /* end of context from PMCD ... we don't care */
+ break;
+
+ default:
+ fprintf(stderr, "%s: bogus pdu type: 0x%0x?\n", pmdaname, sts);
+ __pmSendError(outfd, FROM_ANON, PM_ERR_NYI);
+ break;
+ }
+ __pmUnpinPDUBuf(pb_pmcd);
+ }
+ }
+
+done:
+ return;
+}
diff --git a/src/pmdas/summary/pmda.c b/src/pmdas/summary/pmda.c
new file mode 100644
index 0000000..2a8828c
--- /dev/null
+++ b/src/pmdas/summary/pmda.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <sys/stat.h>
+#include "summary.h"
+#include "domain.h"
+
+extern void summary_init(pmdaInterface *);
+extern void summary_done(void);
+
+pid_t clientPID;
+
+int
+main(int argc, char **argv)
+{
+ int errflag = 0;
+ int sep = __pmPathSeparator();
+ char **commandArgv;
+ pmdaInterface dispatch;
+ int i;
+ int len, c;
+ int clientPipe[2];
+ char helpfile[MAXPATHLEN];
+ int cmdpipe; /* metric source/cmd pipe */
+ char *command = NULL;
+ char *username;
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+ __pmSetInternalState(PM_STATE_PMCS); /* we are below the PMAPI */
+
+ snprintf(helpfile, sizeof(helpfile), "%s%c" "summary" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon (&dispatch, PMDA_INTERFACE_2, pmProgname, SYSSUMMARY,
+ "summary.log", helpfile);
+
+ while ((c = pmdaGetOpt(argc, argv, "H:h:D:d:l:U:",
+ &dispatch, &errflag)) != EOF) {
+ switch (c) {
+
+ case 'H': /* backwards compatibility, synonym for -h */
+ dispatch.version.two.ext->e_helptext = optarg;
+ break;
+
+ case 'U':
+ username = optarg;
+ break;
+
+ case '?':
+ errflag++;
+ break;
+ }
+ }
+
+ for (len=0, i=optind; i < argc; i++) {
+ len += strlen(argv[i]) + 1;
+ }
+ if (len == 0) {
+ fprintf(stderr, "%s: a command must be given after the options\n",
+ pmProgname);
+ errflag++;
+ }
+ else {
+ command = (char *)malloc(len+2);
+ command[0] = '\0';
+ for (i=optind; i < argc; i++) {
+ if (i > optind)
+ strcat(command, " ");
+ strcat(command, argv[i]);
+ }
+ }
+ commandArgv = argv + optind;
+
+ if (errflag) {
+ fprintf(stderr, "Usage: %s [options] command [arg ...]\n\n",
+ pmProgname);
+ fputs("Options:\n"
+ " -h helpfile help text file\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -U username user account to run under (default \"pcp\")\n",
+ stderr);
+ exit(1);
+ }
+
+ /* force errors from here on into the log */
+ pmdaOpenLog(& dispatch);
+
+ /* switch to alternate user account now */
+ __pmSetProcessIdentity(username);
+
+ /* initialize */
+ summary_init(&dispatch);
+
+ pmdaConnect(&dispatch);
+ if (dispatch.status) {
+ fprintf (stderr, "Cannot connect to pmcd: %s\n",
+ pmErrStr(dispatch.status));
+ exit (1);
+ }
+
+ /*
+ * open a pipe to the command
+ */
+ if (pipe1(clientPipe) < 0) {
+ perror("pipe");
+ exit(oserror());
+ }
+
+ if ((clientPID = fork()) == 0) {
+ /* child */
+ char cmdpath[MAXPATHLEN+5];
+ close(clientPipe[0]);
+ if (dup2(clientPipe[1], fileno(stdout)) < 0) {
+ perror("dup");
+ exit(oserror());
+ }
+ close(clientPipe[1]);
+
+ snprintf (cmdpath, sizeof(cmdpath), "exec %s", commandArgv[0]);
+ execv(commandArgv[0], commandArgv);
+
+ perror(cmdpath);
+ exit(oserror());
+ }
+
+ fprintf(stderr, "clientPID = %" FMT_PID "\n", clientPID);
+
+ close(clientPipe[1]);
+ cmdpipe = clientPipe[0]; /* parent/agent reads from here */
+ __pmSetVersionIPC(cmdpipe, PDU_VERSION2);
+
+ summaryMainLoop(pmProgname, cmdpipe, &dispatch);
+
+ summary_done();
+ exit(0);
+}
diff --git a/src/pmdas/summary/pmns b/src/pmdas/summary/pmns
new file mode 100644
index 0000000..44f89fe
--- /dev/null
+++ b/src/pmdas/summary/pmns
@@ -0,0 +1,24 @@
+/*
+ * Metrics for summary PMDA
+ */
+
+summary {
+ cpu
+ disk
+ netif
+}
+
+summary.cpu {
+ util SYSSUMMARY:0:1
+ busy SYSSUMMARY:0:2
+}
+
+summary.disk {
+ iops SYSSUMMARY:0:3
+ busy SYSSUMMARY:0:4
+}
+
+summary.netif {
+ packets SYSSUMMARY:0:6
+ busy SYSSUMMARY:0:7
+}
diff --git a/src/pmdas/summary/root b/src/pmdas/summary/root
new file mode 100644
index 0000000..12754f3
--- /dev/null
+++ b/src/pmdas/summary/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { summary }
+
+#include "pmns"
+
diff --git a/src/pmdas/summary/summary.c b/src/pmdas/summary/summary.c
new file mode 100644
index 0000000..a3da84a
--- /dev/null
+++ b/src/pmdas/summary/summary.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 1995,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <signal.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "summary.h"
+#include "domain.h"
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+int nmeta;
+meta_t *meta;
+pmResult *cachedResult;
+static int *freeList;
+
+static int
+summary_desc(pmID pmid, pmDesc *desc, pmdaExt * ex)
+{
+ static int i=0;
+
+ if ( i < nmeta && pmid == meta[i].desc.pmid) {
+found:
+ if (meta[i].desc.type == PM_TYPE_NOSUPPORT)
+ return PM_ERR_AGAIN; /* p76 rules ok */
+ *desc = meta[i].desc; /* struct assignment */
+ return 0; /* success */
+ }
+
+ for (i = 0; i < nmeta; i++) {
+ if (pmid == meta[i].desc.pmid)
+ goto found;
+ }
+ return PM_ERR_PMID;
+}
+
+void
+service_client(__pmPDU *pb)
+{
+ int n;
+ int i;
+ int j;
+ pmDesc desc;
+ pmDesc foundDesc;
+ pmResult *resp;
+ pmValueSet *vsp;
+ __pmPDUHdr *ph = (__pmPDUHdr *)pb;
+
+ switch (ph->type) {
+
+ case PDU_DESC:
+ if ((n = __pmDecodeDesc(pb, &desc)) < 0) {
+ fprintf(stderr, "service_client: __pmDecodeDesc failed: %s\n",
+ pmErrStr(n));
+ exit(1);
+ }
+
+ if (desc.indom != PM_INDOM_NULL) {
+ fprintf(stderr, "service_client: Warning: ignored desc for pmid=%s: indom is not singular\n", pmIDStr(desc.pmid));
+ return;
+ }
+
+ if (summary_desc(desc.pmid, &foundDesc, NULL) == 0) {
+ /* already in table */
+ fprintf(stderr,
+ "service_client: Warning: duplicate desc for pmid=%s\n",
+ pmIDStr(desc.pmid));
+ return;
+ }
+
+ nmeta++;
+ if ((meta = (meta_t *)realloc(meta, nmeta * sizeof(meta_t))) == NULL) {
+ __pmNoMem("service_client: meta realloc", nmeta * sizeof(meta_t), PM_FATAL_ERR);
+ }
+ memcpy(&meta[nmeta-1].desc, &desc, sizeof(pmDesc));
+
+ break;
+
+ case PDU_RESULT:
+ if ((n = __pmDecodeResult(pb, &resp)) < 0) {
+ fprintf(stderr, "service_client: __pmDecodeResult failed: %s\n", pmErrStr(n));
+ exit(1);
+ }
+
+ if (cachedResult == NULL) {
+ int need;
+ need = (int)sizeof(pmResult) - (int)sizeof(pmValueSet *);
+ if ((cachedResult = (pmResult *)malloc(need)) == NULL) {
+ __pmNoMem("service_client: result malloc", need, PM_FATAL_ERR);
+ }
+ cachedResult->numpmid = 0;
+ }
+
+ /*
+ * swap values from resp with those in cachedResult, expanding
+ * cachedResult if there are metrics we've not seen before
+ */
+ for (i = 0; i < resp->numpmid; i++) {
+ for (j = 0; j < cachedResult->numpmid; j++) {
+ if (resp->vset[i]->pmid == cachedResult->vset[j]->pmid) {
+ /* found matching PMID, update this value */
+ break;
+ }
+ }
+
+ if (j == cachedResult->numpmid) {
+ /* new PMID, expand cachedResult and initialize vset */
+ int need;
+ cachedResult->numpmid++;
+ need = (int)sizeof(pmResult) +
+ (cachedResult->numpmid-1) * (int)sizeof(pmValueSet *);
+ if ((cachedResult = (pmResult *)realloc(cachedResult, need)) == NULL) {
+ __pmNoMem("service_client: result realloc", need, PM_FATAL_ERR);
+ }
+ if ((cachedResult->vset[j] = (pmValueSet *)malloc(sizeof(pmValueSet))) == NULL) {
+ __pmNoMem("service_client: vset[]", sizeof(pmValueSet), PM_FATAL_ERR);
+ }
+ cachedResult->vset[j]->pmid = resp->vset[i]->pmid;
+ cachedResult->vset[j]->numval = 0;
+ }
+
+ /*
+ * swap vsets
+ */
+ vsp = cachedResult->vset[j];
+ cachedResult->vset[j] = resp->vset[i];
+ resp->vset[i] = vsp;
+
+ }
+
+ pmFreeResult(resp);
+ break;
+
+ case PDU_ERROR:
+ if ((n = __pmDecodeError(pb, &i)) < 0) {
+ fprintf(stderr, "service_client: __pmDecodeError failed: %s\n", pmErrStr(n));
+ exit(1);
+ }
+ fprintf(stderr, "service_client: Error PDU! %s\n", pmErrStr(i));
+ break;
+
+ default:
+ fprintf(stderr, "service_client: Bogus PDU type %d\n", ph->type);
+ exit(1);
+ }
+}
+
+static int
+summary_profile(__pmProfile *prof, pmdaExt * ex)
+{
+ /*
+ * doesn't make sense since summary metrics
+ * always have a singular instance domain.
+ */
+ return 0;
+}
+
+static int
+summary_instance(pmInDom indom, int inst, char *name, __pmInResult **result,
+ pmdaExt * ex)
+{
+ return PM_ERR_INDOM;
+}
+
+static void
+freeResultCallback(pmResult *res)
+{
+ int i;
+
+ /*
+ * pmResult has now been sent to pmcd. Only free the
+ * value sets that had no values available because
+ * the valid ones were reused from the cachedResult.
+ */
+ for (i=0; i < res->numpmid; i++) {
+ if (freeList[i])
+ free(res->vset[i]);
+ }
+ return;
+}
+
+
+static int
+summary_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt * ex)
+{
+ int i; /* over pmidlist[] */
+ int j; /* over vset->vlist[] */
+ int sts;
+ int need;
+ int validpmid;
+ pmID pmid;
+ pmDesc desc;
+ static pmResult *res = NULL;
+ static int maxnpmids = 0;
+
+ if (numpmid > maxnpmids) {
+ maxnpmids = numpmid;
+ if (res != NULL)
+ free(res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *);
+ if ((res = (pmResult *)malloc(need)) == NULL)
+ return -oserror();
+
+ if (freeList != NULL)
+ free(freeList);
+ if ((freeList = (int *)malloc(numpmid * sizeof(int))) == NULL)
+ return -oserror();
+ }
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+ for (i = 0; i < numpmid; i++) {
+ pmid = pmidlist[i];
+
+ /*
+ * do we know about the descriptor for this pmid?
+ * If not, then the error is PM_ERR_PMID
+ * regardless of whether there is an entry in
+ * the cached result.
+ */
+ sts = summary_desc(pmid, &desc, NULL);
+ validpmid = (sts == 0);
+
+ res->vset[i] = NULL;
+ freeList[i] = 1;
+
+ if (validpmid && cachedResult != NULL) {
+ for (j=0; j < cachedResult->numpmid; j++) {
+ if (pmid == cachedResult->vset[j]->pmid) {
+ res->vset[i] = cachedResult->vset[j];
+ freeList[i] = 0;
+ break;
+ }
+ }
+ }
+
+ if (!validpmid || res->vset[i] == NULL) {
+ /* no values available or the metric has no descriptor */
+ if ((res->vset[i] = (pmValueSet *)malloc(sizeof(pmValueSet))) == NULL)
+ return -oserror();
+ res->vset[i]->pmid = pmid;
+ res->vset[i]->valfmt = PM_VAL_INSITU;
+ res->vset[i]->numval = validpmid ? PM_ERR_VALUE : sts;
+ }
+ }
+ *resp = res;
+
+ return numpmid;
+}
+
+static int
+summary_store(pmResult *result, pmdaExt * ex)
+{
+ return PM_ERR_PERMISSION;
+}
+
+void
+summary_init(pmdaInterface *dp)
+{
+ void (*callback)() = freeResultCallback;
+
+ dp->version.two.profile = summary_profile;
+ dp->version.two.fetch = summary_fetch;
+ dp->version.two.desc = summary_desc;
+ dp->version.two.instance = summary_instance;
+ dp->version.two.store = summary_store;
+
+ mainLoopFreeResultCallback(callback);
+
+ pmdaInit(dp, NULL, 0, NULL, 0);
+}
+
+void
+summary_done(void)
+{
+ int st;
+
+ fprintf(stderr, "summary agent pid=%" FMT_PID " done\n", getpid());
+ kill(clientPID, SIGINT);
+ waitpid(clientPID, &st, 0);
+}
diff --git a/src/pmdas/summary/summary.h b/src/pmdas/summary/summary.h
new file mode 100644
index 0000000..ccc0613
--- /dev/null
+++ b/src/pmdas/summary/summary.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * exported summary metrics
+ */
+typedef struct {
+ char *name; /* name space path */
+ pmDesc desc; /* descriptor, inc. pmid */
+} meta_t;
+
+extern meta_t *meta;
+extern int nmeta;
+extern char *command;
+extern char *helpfile;
+extern int cmdpipe;
+extern pid_t clientPID;
+
+extern void summaryMainLoop(char *, int, pmdaInterface *);
+extern void mainLoopFreeResultCallback(void (*)(pmResult *));
+extern void service_client(__pmPDU *);
+
+
diff --git a/src/pmdas/summary/summary.pmie b/src/pmdas/summary/summary.pmie
new file mode 100644
index 0000000..bedb824
--- /dev/null
+++ b/src/pmdas/summary/summary.pmie
@@ -0,0 +1,40 @@
+//
+// summary metrics
+//
+// these expressions are evaluated by pmie(1)
+//
+// use the default interval between expression evaluation (currently
+// 10 seconds), and re-configure via -t command line arg to pmie
+// (see Install)
+
+// CPU utilization
+//
+cpuse = "(kernel.percpu.cpu.sys + kernel.percpu.cpu.user)";
+ncpu = "hinv.ncpu";
+
+// average CPU utilization
+summary.cpu.util = avg_inst $cpuse;
+
+// proportion of CPUs that are busy
+summary.cpu.busy = (count_inst $cpuse > 0.7) / $ncpu;
+
+// Disk utilization
+//
+diskio = "disk.dev.total";
+ndisk = "hinv.ndisk";
+
+// average spindle activity
+summary.disk.iops = avg_inst ($diskio);
+
+// proportion of disk spindles that are busy
+summary.disk.busy = (count_inst $diskio > 40) / $ndisk;
+
+// Network interface utilization
+//
+netio = "network.interface.total.packets";
+
+// average network interface activity
+summary.netif.packets = avg_inst ($netio);
+
+// proportion of network interfaces that are busy
+summary.netif.busy = (count_inst $netio > 400) / (count_inst $netio >= 0);
diff --git a/src/pmdas/systemd/GNUmakefile b/src/pmdas/systemd/GNUmakefile
new file mode 100644
index 0000000..9805c1d
--- /dev/null
+++ b/src/pmdas/systemd/GNUmakefile
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmdasystemd$(EXECSUFFIX)
+DFILES = README
+CFILES = systemd.c
+LCFLAGS = $(SYSTEMD_CFLAGS)
+LLDLIBS = $(PCP_PMDALIB) $(SYSTEMD_LIBS)
+LSRCFILES = Install Remove pmns help $(DFILES) root
+
+IAM = systemd
+DOMAIN = SYSTEMD
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h *.o $(IAM).log $(CMDTARGET)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(PMDA_SYSTEMD)" ""
+build-me: domain.h $(CMDTARGET)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns $(PMDADIR)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET)
+else
+build-me:
+install:
+endif
+
+systemd.o: domain.h
+
+.NOTPARALLEL:
+.ORDER: domain.h $(OBJECTS)
+
+default_pcp : default
+
+install_pcp : install
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/systemd/Install b/src/pmdas/systemd/Install
new file mode 100755
index 0000000..2757725
--- /dev/null
+++ b/src/pmdas/systemd/Install
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2011-2013 Red Hat Inc.
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the systemd PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=systemd
+pmda_interface=6
+pipe_opt=true
+daemon_opt=true
+pmdaSetup
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/systemd/README b/src/pmdas/systemd/README
new file mode 100644
index 0000000..3125da1
--- /dev/null
+++ b/src/pmdas/systemd/README
@@ -0,0 +1,64 @@
+Systemd PMDA
+===========
+
+This PMDA exports events from the systemd journal [1] as they occur.
+There is no configuration required. In the default (daemon) PMDA
+mode, the daemon switches to userid 'adm', so it can report systemwide
+journal entries.
+
+[1] http://www.freedesktop.org/wiki/Software/systemd
+
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT systemd
+
+The interesting metrics are systemd.journal.records and
+systemd.journal.records_raw. When a PCP client such as pmevent
+monitors them, each new journal entry is reported as a PCP event
+tuple. Each field of the journal entry is transcribed as a string or
+blob, including the FIELDNAME= prefix. See [2] for more details about
+interpretation of the fields.
+
+[2] http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/systemd
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/systemd
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/systemd.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/systemd/Remove b/src/pmdas/systemd/Remove
new file mode 100755
index 0000000..00e48aa
--- /dev/null
+++ b/src/pmdas/systemd/Remove
@@ -0,0 +1,24 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2011-2012 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the systemd PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+iam=systemd
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/systemd/help b/src/pmdas/systemd/help
new file mode 100644
index 0000000..1a40f7a
--- /dev/null
+++ b/src/pmdas/systemd/help
@@ -0,0 +1,52 @@
+#
+# Copyright (c) 2012 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# systemd PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ systemd.numclients The number of attached clients
+The number of attached clients.
+@ systemd.maxmem Maximum number of queued event bytes.
+Maximum number of queued event bytes (apprx. 128 bytes per cursor string).
+
+@ systemd.journal.field.cursor The cursor, an implicit journald field.
+This is the journal entry's permanent, globally unique cursor string.
+@ systemd.journal.field.string A journal field that may be a string.
+A journal field copied verbatim, as a PM_TYPE_STRING object, presumed as
+a valid string (in some encoding), if the field did not contain any \0 characters.
+@ systemd.journal.field.blob A journal field copied verbatim.
+A journal field copied verbatim, as a PM_TYPE_AGGREGATE object.
+
+@ systemd.journal.records Journal entries, encoded as strings and blobs.
+Each new journald event field is given a systemd.parameters.cursor string
+to identify it, and a collection of string and blob fields (as appropriate).
+
+@ systemd.journal.records_raw Journal entries, encoded as blob parameters only.
+Each new journald event field is given a systemd.parameters.cursor string
+to identify it, and a blob fields the reproduce the FIELD=value bit-for-bit.
+
+@ systemd.journal.count Count of journal entries observed
+@ systemd.journal.bytes Sum of sizes of all journal entries observed
diff --git a/src/pmdas/systemd/pmns b/src/pmdas/systemd/pmns
new file mode 100644
index 0000000..b9e6487
--- /dev/null
+++ b/src/pmdas/systemd/pmns
@@ -0,0 +1,35 @@
+/*
+ * Metrics for systemd PMDA
+ *
+ * Copyright (c) 2012-2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+systemd {
+ numclients SYSTEMD:0:0
+ maxmem SYSTEMD:0:1
+ journal
+}
+
+systemd.journal {
+ records SYSTEMD:2:0
+ records_raw SYSTEMD:2:1
+ field
+ count SYSTEMD:2:2
+ bytes SYSTEMD:2:3
+}
+
+systemd.journal.field {
+ cursor SYSTEMD:1:0
+ string SYSTEMD:1:1 /* possibly a string */
+ blob SYSTEMD:1:2 /* a binary blob */
+}
diff --git a/src/pmdas/systemd/root b/src/pmdas/systemd/root
new file mode 100644
index 0000000..cac7f3a
--- /dev/null
+++ b/src/pmdas/systemd/root
@@ -0,0 +1,11 @@
+#include <stdpmid>
+
+root {
+ systemd
+}
+
+#ifndef SYSTEMD
+#define SYSTEMD 114
+#endif
+
+#include "pmns"
diff --git a/src/pmdas/systemd/systemd.c b/src/pmdas/systemd/systemd.c
new file mode 100644
index 0000000..c67cb66
--- /dev/null
+++ b/src/pmdas/systemd/systemd.c
@@ -0,0 +1,791 @@
+/*
+ * systemd support for the systemd PMDA
+ *
+ * Copyright (c) 2012-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Structure based upon the logger pmda.
+ */
+
+#define _POSIX_C_SOURCE 200112L /* for strtoull */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+
+#include <systemd/sd-journal.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <grp.h>
+
+#define DEFAULT_MAXMEM (2 * 1024 * 1024) /* 2 megabytes */
+long maxmem;
+int maxfd;
+fd_set fds;
+static int interval_expired;
+static struct timeval interval = { 60, 0 };
+static sd_journal *journald_context; /* Used for monitoring only. */
+static sd_journal *journald_context_seeky; /* Used for event detail extraction,
+ involving seeks. */
+static int queue_entries = -1;
+static char *username = "adm";
+
+
+/* Track per-context PCP_ATTR_USERID | _GROUPID, so we
+ can filter event records for that context. */
+static int uid_gid_filter_p = 1;
+struct uid_gid_tuple {
+ char wildcard_p; /* do not filter for this context. */
+ char uid_p; char gid_p; /* uid/gid received flags. */
+ int uid; int gid; }; /* uid/gid received from PCP_ATTR_* */
+static struct uid_gid_tuple *ctxtab = NULL;
+int ctxtab_size = 0;
+
+
+static pmdaMetric metrictab[] = {
+/* numclients */
+#define METRICTAB_NUMCLIENTS_PMID metrictab[0].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* maxmem */
+#define METRICTAB_MAXMEM_PMID metrictab[1].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+/* journal.field.cursor */
+#define METRICTAB_JOURNAL_CURSOR_PMID metrictab[2].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(1,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* journal.field.string */
+#define METRICTAB_JOURNAL_STRING_PMID metrictab[3].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(1,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* journal.field.blob */
+#define METRICTAB_JOURNAL_BLOB_PMID metrictab[4].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(1,2), PM_TYPE_AGGREGATE, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* journal.records */
+#define METRICTAB_JOURNAL_RECORDS_PMID metrictab[5].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(2,0), PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* journal.records_raw */
+#define METRICTAB_JOURNAL_RECORDS_RAW_PMID metrictab[6].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(2,1), PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, },
+/* journal.count */
+#define METRICTAB_JOURNAL_COUNT_PMID metrictab[7].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(2,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, },
+/* journal.bytes */
+#define METRICTAB_JOURNAL_BYTES_PMID metrictab[8].m_desc.pmid
+ { NULL,
+ { PMDA_PMID(2,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, },
+};
+
+
+void systemd_shutdown(void)
+{
+ if (journald_context != 0)
+ sd_journal_close (journald_context);
+
+ if (journald_context_seeky != 0)
+ sd_journal_close (journald_context_seeky);
+
+ /* XXX: pmdaEvent zap queues? */
+}
+
+
+/* Return a strndup (or NULL) of a field of the current journal entry,
+ since sd_journal_get_data returns data that is not
+ \0-terminated. */
+char *
+my_sd_journal_get_data(sd_journal *j, const char *field)
+{
+ int rc;
+ const char* str;
+ size_t str_len;
+
+ assert (j != NULL);
+ assert (field != NULL);
+
+ rc = sd_journal_get_data(j, field,
+ (const void**) & str, & str_len);
+ if (rc < 0)
+ return NULL;
+
+ return strndup (str, str_len);
+}
+
+
+void systemd_refresh(void)
+{
+ /* Absorb any changes such as inotify() messages. */
+ (void) sd_journal_process(journald_context);
+ (void) sd_journal_process(journald_context_seeky);
+
+ while (1) {
+ char *cursor = NULL;
+ char *timestamp_str = NULL;
+ struct timeval timestamp;
+
+ int rc = sd_journal_next(journald_context);
+
+ if (rc == 0) /* No recent entries. */
+ break;
+
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_next failure: %s", strerror(-rc));
+ break;
+ }
+
+ /* NB: we enqueue the journal cursor string, rather than the
+ actual journal records. */
+ rc = sd_journal_get_cursor(journald_context, &cursor);
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_get_cursor failure: %s",
+ strerror(-rc));
+ break;
+ }
+
+ /* Extract a timestamp from the journald event fields. */
+ timestamp_str = my_sd_journal_get_data(journald_context,
+ "_SOURCE_REALTIME_TIMESTAMP");
+ if (timestamp_str == NULL)
+ timestamp_str = my_sd_journal_get_data(journald_context,
+ "__REALTIME_TIMESTAMP");
+ if (timestamp_str == NULL)
+ rc = -ENOMEM;
+ else {
+ const char* curse;
+ unsigned long long epoch_us;
+ /* defined in systemd.journal-fields(7) as
+ FIELD_NAME=NNNN, where NNNN is decimal us since epoch. */
+ curse = strchr (timestamp_str, '=');
+ if (curse == NULL)
+ rc = -EINVAL;
+ else {
+ curse ++;
+ epoch_us = strtoull (curse, NULL, 10);
+ timestamp.tv_sec = epoch_us / 1000000;
+ timestamp.tv_usec = epoch_us % 1000000;
+ }
+ free (timestamp_str);
+ }
+ /* Improvise. */
+ if (rc < 0)
+ gettimeofday (& timestamp, NULL);
+
+ /* Enqueue it to fresh visitors. */
+ rc = pmdaEventQueueAppend(queue_entries,
+ cursor, strlen(cursor)+1 /* \0 */, &timestamp);
+ free(cursor); /* Already copied. */
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "pmdaEventQueueAppend failure: %s", pmErrStr(rc));
+ break;
+ }
+ }
+}
+
+
+
+enum journald_field_encoding {
+ JFE_STRING_BLOB_AUTO,
+ JFE_BLOB_ONLY
+};
+
+
+
+int
+systemd_journal_event_filter (void *rp, void *data, size_t size)
+{
+ int rc;
+ struct uid_gid_tuple* ugt = rp;
+
+ assert (ugt == & ctxtab[pmdaGetContext()]);
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "filter (%d) uid=%d gid=%d data=%p bytes=%u\n",
+ pmdaGetContext(), ugt->uid, ugt->gid, data, (unsigned)size);
+
+ /* The data/size pair gives the object in the event queue, i.e.,
+ the systemd journal cursor string. It has not yet been turned
+ into a PM_TYPE_EVENT tuple yet, and if we have our way, it won't
+ be (for non-participating clients). */
+
+ /* The general filtering idea is to only feed journal records to clients
+ if their uid matches the _UID=NNN field -or- gid matches _GID=MMM
+ -or- the client is highly authenticated (wildcard_p) -or- per-uid filtering
+ was turned off at the pmda level. */
+
+ /* Reminder: function rc == 0 passes the filter. */
+
+ /* Unfiltered? Everyone gets egg soup! */
+ if (! uid_gid_filter_p)
+ return 0;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "filter (%d) uid%s%d gid%s%d wildcard=%d\n",
+ pmdaGetContext(),
+ ugt->uid_p?"=":"?", ugt->uid,
+ ugt->gid_p?"=":"?", ugt->gid,
+ ugt->wildcard_p);
+
+ /* Superuser? May we offer some goulash? */
+ if (ugt->wildcard_p)
+ return 0;
+
+ /* Unauthenticated context? No soup for you! */
+ if (! ugt->uid_p && ! ugt->gid_p)
+ return 1;
+
+ /* OK, we need to take a look at the journal record in question. */
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "filter cursor=%s\n", (const char*) data);
+
+ (void) size; /* already known \0-terminated */
+ rc = sd_journal_seek_cursor(journald_context_seeky, (char*) data);
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "filter cannot seek to cursor=%s\n",
+ (const char*) data);
+ return 1; /* No point trying again in systemd_journal_decoder. */
+ }
+
+ rc = sd_journal_next(journald_context_seeky);
+ if (rc < 0) {
+ __pmNotifyErr(LOG_ERR, "filter cannot advance to next\n");
+ return 1; /* No point trying again in systemd_journal_decoder. */
+ }
+
+ if (ugt->uid_p) {
+ char *uid_str = my_sd_journal_get_data(journald_context_seeky, "_UID");
+ if (uid_str) {
+ int uid = atoi (& uid_str[5]); /* skip over _UID= */
+ free (uid_str);
+ if (uid == ugt->uid)
+ return 0; /* You're a somebody. Here's a bowl of stew. */
+ }
+ }
+
+ if (ugt->gid_p) {
+ char *gid_str = my_sd_journal_get_data(journald_context_seeky, "_GID");
+ if (gid_str) {
+ int gid = atoi (& gid_str[5]); /* skip over _GID= */
+ free (gid_str);
+ if (gid == ugt->gid)
+ return 0; /* You're with pals. Here's a bowl of miso. */
+ }
+ }
+
+ /* No soup for you! */
+ return 1;
+}
+
+
+void
+systemd_journal_event_filter_release (void *rp)
+{
+ /* NB: We have nothing to release, as we don't do memory allocation
+ for the filter per se - we clean up during end-context time.
+ We can't send a NULL to pmdaEventSetFilter for release purposes
+ (since it'll blindly call it), so need this dummy function. */
+
+ (void) rp;
+}
+
+
+int
+systemd_journal_decoder(int eventarray, void *buffer, size_t size,
+ struct timeval *timestamp, void *data)
+{
+ int sts;
+ pmAtomValue atom;
+ enum journald_field_encoding jfe = * (enum journald_field_encoding *) data;
+
+ sts = pmdaEventAddRecord(eventarray, timestamp, PM_EVENT_FLAG_POINT);
+ if (sts < 0)
+ return sts;
+
+ /* Go to the cursor point enqueued for this client. The buffer is already
+ \0-terminated. */
+ sts = sd_journal_seek_cursor(journald_context_seeky, (char*) buffer);
+ if (sts < 0) {
+ /* But see RHBZ #876654. */
+ return /* sts */ 0;
+ }
+
+ sts = sd_journal_next(journald_context_seeky);
+ if (sts < 0)
+ return sts;
+ if (sts == 0)
+ return -ENODATA; /* event got lost between cursor-recording and now */
+
+ /* Add the _CURSOR implicit journal field. */
+ atom.cp = buffer;
+ sts = pmdaEventAddParam(eventarray, METRICTAB_JOURNAL_CURSOR_PMID,
+ PM_TYPE_STRING, &atom);
+
+ /* Add all the explicit journal fields. */
+ while (1) {
+ const void *data;
+ size_t data_len;
+
+ if (sts < 0)
+ break;
+ sts = sd_journal_enumerate_data(journald_context_seeky, &data, &data_len);
+ if (sts <= 0)
+ break;
+
+ /* Infer string upon absence of embedded \0's. */
+ if (jfe == JFE_STRING_BLOB_AUTO && (memchr (data, '\0', data_len) == NULL)) {
+ /* Unfortunately, data may not be \0-terminated, so we can't simply pass
+ it to atom.cp. We need to copy the bad boy first. */
+ atom.cp = strndup(data, data_len);
+ if (atom.cp == NULL)
+ sts = -ENOMEM;
+ else {
+ sts = pmdaEventAddParam(eventarray, METRICTAB_JOURNAL_STRING_PMID,
+ PM_TYPE_STRING, &atom);
+ free (atom.cp);
+ }
+ /* NB: we assume libpcp_pmda will not free() the field. */
+ } else {
+ pmValueBlock *aggr = (pmValueBlock *)malloc(PM_VAL_HDR_SIZE + data_len);
+ if (aggr == NULL)
+ sts = -ENOMEM;
+ else {
+ aggr->vtype = PM_TYPE_AGGREGATE;
+ if (PM_VAL_HDR_SIZE + data_len >= 1<<24)
+ aggr->vlen = (1U<<24) - 1; /* vlen is a :24 bit field */
+ else
+ aggr->vlen = PM_VAL_HDR_SIZE + data_len;
+ memcpy (aggr->vbuf, data, data_len);
+ atom.vbp = aggr;
+ sts = pmdaEventAddParam(eventarray, METRICTAB_JOURNAL_BLOB_PMID,
+ PM_TYPE_AGGREGATE, &atom);
+ /* NB: we assume libpcp_pmda will free() aggr. */
+ }
+ }
+ }
+
+ return sts < 0 ? sts : 1; /* added one event array */
+}
+
+
+void enlarge_ctxtab(int context)
+{
+ /* Grow the context table if necessary. */
+ if (ctxtab_size /* cardinal */ <= context /* ordinal */) {
+ size_t need = (context + 1) * sizeof(struct uid_gid_tuple);
+ ctxtab = realloc (ctxtab, need);
+ if (ctxtab == NULL)
+ __pmNoMem("systemd ctx table", need, PM_FATAL_ERR);
+ /* Blank out new entries. */
+ while (ctxtab_size <= context)
+ memset (& ctxtab[ctxtab_size++], 0, sizeof(struct uid_gid_tuple));
+ }
+}
+
+
+static int
+systemd_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ int sts;
+ (void) pmdaEventNewClient(pmda->e_context);
+ enlarge_ctxtab(pmda->e_context);
+ sts = pmdaEventSetFilter(pmda->e_context, queue_entries,
+ & ctxtab[pmda->e_context], /* any non-NULL value */
+ systemd_journal_event_filter,
+ systemd_journal_event_filter_release /* NULL */);
+ if (sts < 0)
+ return sts;
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+
+static int
+systemd_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ pmID id = mdesc->m_desc.pmid;
+ int sts;
+
+ if (id == METRICTAB_NUMCLIENTS_PMID) {
+ sts = pmdaEventClients(atom);
+ } else if (id == METRICTAB_MAXMEM_PMID) {
+ atom->ul = (unsigned long)maxmem;
+ sts = PMDA_FETCH_STATIC;
+ } else if (id == METRICTAB_JOURNAL_CURSOR_PMID) {
+ sts = PMDA_FETCH_NOVALUES;
+ } else if (id == METRICTAB_JOURNAL_STRING_PMID) {
+ sts = PMDA_FETCH_NOVALUES;
+ } else if (id == METRICTAB_JOURNAL_BLOB_PMID) {
+ sts = PMDA_FETCH_NOVALUES;
+ } else if (id == METRICTAB_JOURNAL_COUNT_PMID) {
+ sts = pmdaEventQueueCounter(queue_entries, atom);
+ } else if (id == METRICTAB_JOURNAL_BYTES_PMID) {
+ sts = pmdaEventQueueBytes(queue_entries, atom);
+ } else if (id == METRICTAB_JOURNAL_RECORDS_PMID) {
+ enum journald_field_encoding jfe = JFE_STRING_BLOB_AUTO;
+ sts = pmdaEventSetAccess(pmdaGetContext(), queue_entries, 1);
+ if (sts == 0)
+ sts = pmdaEventQueueRecords(queue_entries, atom, pmdaGetContext(),
+ systemd_journal_decoder, & jfe);
+ } else if (id == METRICTAB_JOURNAL_RECORDS_RAW_PMID) {
+ enum journald_field_encoding jfe = JFE_BLOB_ONLY;
+ sts = pmdaEventSetAccess(pmdaGetContext(), queue_entries, 1);
+ if (sts == 0)
+ sts = pmdaEventQueueRecords(queue_entries, atom, pmdaGetContext(),
+ systemd_journal_decoder, & jfe);
+ } else {
+ sts = PM_ERR_PMID;
+ }
+ return sts;
+}
+
+
+static int
+systemd_contextAttributeCallBack(int context,
+ int attr, const char *value, int length, pmdaExt *pmda)
+{
+ static int rootlike_gids_found = 0;
+ static int adm_gid = -1;
+ static int wheel_gid = -1;
+ static int systemd_journal_gid = -1;
+ int id;
+
+ /* Look up root-like gids if needed. A later PCP client that
+ matches any of these group-id's is treated as if root/adm,
+ i.e., journal records are not filtered for them (wildcard_p).
+ XXX: we could examine group-membership lists and check against
+ uid to also set wildcard_p. */
+ if (! rootlike_gids_found) {
+ struct group *grp;
+ grp = getgrnam("adm");
+ if (grp) adm_gid = grp->gr_gid;
+ grp = getgrnam("wheel");
+ if (grp) wheel_gid = grp->gr_gid;
+ grp = getgrnam("systemd-journal");
+ if (grp) systemd_journal_gid = grp->gr_gid;
+ rootlike_gids_found = 1;
+ }
+
+ enlarge_ctxtab(context);
+ assert (ctxtab != NULL && context < ctxtab_size);
+
+ /* NB: we maintain separate uid_p and gid_p for filtering
+ purposes; it's possible that a pcp client might send only
+ PCP_ATTR_USERID, leaving gid=0, possibly leading us to
+ misinterpret that as GROUPID=0 (root) and sending back _GID=0
+ records. */
+ switch (attr) {
+ case PCP_ATTR_USERID:
+ ctxtab[context].uid_p = 1;
+ id = atoi(value);
+ ctxtab[context].uid = id;
+ if (id == 0) /* root */
+ ctxtab[context].wildcard_p = 1;
+ break;
+
+ case PCP_ATTR_GROUPID:
+ ctxtab[context].gid_p = 1;
+ id = atoi(value);
+ ctxtab[context].gid = id;
+ if (id == adm_gid ||
+ id == wheel_gid ||
+ id == systemd_journal_gid)
+ ctxtab[context].wildcard_p = 1;
+ break;
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "attrib (%d) uid%s%d gid%s%d wildcard=%d\n",
+ context,
+ ctxtab[context].uid_p?"=":"?", ctxtab[context].uid,
+ ctxtab[context].gid_p?"=":"?", ctxtab[context].gid,
+ ctxtab[context].wildcard_p);
+
+ return 0;
+}
+
+
+static void
+systemd_end_contextCallBack(int context)
+{
+ pmdaEventEndClient(context);
+
+ /* assert (ctxtab != NULL && context < ctxtab_size); */
+
+ /* NB: don't do that; this callback may be hit without any fetch
+ calls having been performed, this ctxtab not stretching all the
+ way to [context]. */
+
+ if (context < ctxtab_size)
+ memset (& ctxtab[context], 0, sizeof(struct uid_gid_tuple));
+}
+
+
+static int
+systemd_desc(pmID pmid, pmDesc *desc, pmdaExt *pmda)
+{
+ return pmdaDesc(pmid, desc, pmda);
+}
+
+
+static int
+systemd_text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ return pmdaText(ident, type, buffer, pmda);
+}
+
+
+void
+systemd_init(pmdaInterface *dp)
+{
+ int sts;
+ int journal_fd;
+
+ dp->version.six.desc = systemd_desc;
+ dp->version.six.fetch = systemd_fetch;
+ dp->version.six.text = systemd_text;
+ dp->version.six.attribute = systemd_contextAttributeCallBack;
+ pmdaSetFetchCallBack(dp, systemd_fetchCallBack);
+ pmdaSetEndContextCallBack(dp, systemd_end_contextCallBack);
+ pmdaInit(dp, NULL, 0, metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+
+ /* Initialize the systemd side. This is failure-tolerant. */
+ /* XXX: SD_JOURNAL_{LOCAL|RUNTIME|SYSTEM}_ONLY */
+ sts = sd_journal_open(& journald_context, 0);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_open failure: %s",
+ strerror(-sts));
+ dp->status = sts;
+ return;
+ }
+
+ sts = sd_journal_open(& journald_context_seeky, 0);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_open #2 failure: %s",
+ strerror(-sts));
+ dp->status = sts;
+ return;
+ }
+
+ sts = sd_journal_seek_tail(journald_context);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_seek_tail failure: %s",
+ strerror(-sts));
+ }
+
+ /* Work around RHBZ979487. */
+ sts = sd_journal_previous_skip(journald_context, 1);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_previous_skip failure: %s",
+ strerror(-sts));
+ }
+
+ /* Arrange to wake up for journal events. */
+ journal_fd = sd_journal_get_fd(journald_context);
+ if (journal_fd < 0) {
+ __pmNotifyErr(LOG_ERR, "sd_journal_get_fd failure: %s",
+ strerror(-journal_fd));
+ /* NB: not a fatal error; the select() loop will still time out and
+ periodically poll. This makes it ok for sd_journal_reliable_fd()
+ to be 0. */
+ } else {
+ FD_SET(journal_fd, &fds);
+ if (journal_fd > maxfd) maxfd = journal_fd;
+ }
+
+ /* NB: One queue is used for both .records and .records_raw; they
+ just use different decoder callbacks. */
+ queue_entries = pmdaEventNewQueue("systemd", maxmem);
+ if (queue_entries < 0)
+ __pmNotifyErr(LOG_ERR, "pmdaEventNewQueue failure: %s",
+ pmErrStr(queue_entries));
+}
+
+
+void
+systemdMain(pmdaInterface *dispatch)
+{
+ int pmcdfd;
+
+ pmcdfd = __pmdaInFd(dispatch);
+ if (pmcdfd > maxfd)
+ maxfd = pmcdfd;
+
+ FD_SET(pmcdfd, &fds);
+
+ for (;;) {
+ fd_set readyfds;
+ int nready;
+ struct timeval select_timeout = interval;
+
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(maxfd+1, &readyfds, NULL, NULL, & select_timeout);
+ if (pmDebug & DBG_TRACE_APPL2)
+ __pmNotifyErr(LOG_DEBUG, "select: nready=%d interval=%d",
+ nready, interval_expired);
+ if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror());
+ exit(1);
+ } else if (!interval_expired) {
+ continue;
+ }
+ }
+
+ if (nready > 0 && FD_ISSET(pmcdfd, &readyfds)) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "processing pmcd PDU [fd=%d]", pmcdfd);
+ if (__pmdaMainPDU(dispatch) < 0) {
+ exit(1); /* fatal if we lose pmcd */
+ }
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "completed pmcd PDU [fd=%d]", pmcdfd);
+ }
+ systemd_refresh();
+ }
+}
+
+
+static void
+convertUnits(char **endnum, long *maxmem)
+{
+ switch ((int) **endnum) {
+ case 'b':
+ case 'B':
+ break;
+ case 'k':
+ case 'K':
+ *maxmem *= 1024;
+ break;
+ case 'm':
+ case 'M':
+ *maxmem *= 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ *maxmem *= 1024 * 1024 * 1024;
+ break;
+ }
+ (*endnum)++;
+}
+
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [options]\n\n"
+ "Options:\n"
+ " -d domain use domain (numeric) for metrics domain of PMDA\n"
+ " -l logfile write log into logfile rather than using default log name\n"
+ " -m memory maximum memory used per queue (default %ld bytes)\n"
+ " -s interval default delay between iterations (default %d sec)\n"
+ " -U username user account to run under (default \"adm\")\n"
+ " -f disable per-uid/gid record filtering (default on)\n",
+ pmProgname, maxmem, (int)interval.tv_sec);
+ exit(1);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ static char helppath[MAXPATHLEN];
+ char *endnum;
+ pmdaInterface desc;
+ long minmem;
+ int c, err = 0, sep = __pmPathSeparator();
+
+ minmem = getpagesize();
+ maxmem = (minmem > DEFAULT_MAXMEM) ? minmem : DEFAULT_MAXMEM;
+ __pmSetProgname(argv[0]);
+ snprintf(helppath, sizeof(helppath), "%s%c" "systemd" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_6, pmProgname, SYSTEMD,
+ "systemd.log", helppath);
+
+ while ((c = pmdaGetOpt(argc, argv, "D:d:l:m:s:U:f?", &desc, &err)) != EOF) {
+ switch (c) {
+ case 'm':
+ maxmem = strtol(optarg, &endnum, 10);
+ if (*endnum != '\0')
+ convertUnits(&endnum, &maxmem);
+ if (*endnum != '\0' || maxmem < minmem) {
+ fprintf(stderr, "%s: invalid max memory '%s' (min=%ld)\n",
+ pmProgname, optarg, minmem);
+ err++;
+ }
+ break;
+
+ case 's':
+ if (pmParseInterval(optarg, &interval, &endnum) < 0) {
+ fprintf(stderr, "%s: -s requires a time interval: %s\n",
+ pmProgname, endnum);
+ free(endnum);
+ err++;
+ }
+ break;
+
+ case 'U':
+ username = optarg;
+ break;
+
+ case 'f':
+ uid_gid_filter_p = 0;
+ break;
+
+ default:
+ err++;
+ break;
+ }
+ }
+
+ if (err)
+ usage();
+
+ FD_ZERO (&fds);
+ pmdaOpenLog(&desc);
+
+ /* The systemwide journal may be accessed by the adm user (group);
+ root access is not necessary. */
+ __pmSetProcessIdentity(username);
+ desc.comm.flags |= PDU_FLAG_AUTH;
+ pmdaConnect(&desc);
+ // After this point, systemd_init is allowed to take some extra time.
+ systemd_init(&desc); // sets some fds
+ systemdMain(&desc); // sets some more fds
+ systemd_shutdown();
+ exit(0);
+}
+
+/*
+ Local Variables:
+ c-basic-offset: 4
+ End:
+*/
diff --git a/src/pmdas/systemtap/GNUmakefile b/src/pmdas/systemtap/GNUmakefile
new file mode 100644
index 0000000..ad7d51d
--- /dev/null
+++ b/src/pmdas/systemtap/GNUmakefile
@@ -0,0 +1,56 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = systemtap
+DOMAIN = SYSTEMTAP
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl probes.stp
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ifeq "$(TARGET_OS)" "linux"
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 probes.stp pmda$(IAM).pl $(PMDADIR)
+ @$(INSTALL_MAN)
+endif
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/systemtap/Install b/src/pmdas/systemtap/Install
new file mode 100755
index 0000000..bf6dce4
--- /dev/null
+++ b/src/pmdas/systemtap/Install
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the SystemTap PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=systemtap
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+if ! test -x /usr/bin/stap; then
+ echo "SystemTap \"stap\" tool is not installed" && exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/systemtap/README b/src/pmdas/systemtap/README
new file mode 100644
index 0000000..ddb92c4
--- /dev/null
+++ b/src/pmdas/systemtap/README
@@ -0,0 +1,59 @@
+SystemTap PMDA
+==============
+
+This PMDA uses the SystemTap Linux kernel trace infrastructure to obtain
+performance data. Both SystemTap and this PMDA are easily configurable,
+allowing arbitrary trace points to be monitored. The PMDA itself is all
+Perl code, and thus easily extended to monitor additional types of trace
+information from SystemTap.
+
+The file $PCP_PMDAS_DIR/probes.stp contains the SystemTap script which
+will be run by the stap(1) command to insert the kernel instrumentation.
+It is intended that once suitable instrumentation has been found running
+SystemTap interactively, that a modified probes.stp and PMDA would then
+be installed to export the interesting data to PCP clients (for logging,
+charting, etc).
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT systemtap
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/systemtap
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit pmdasystemtap.pl to use
+ a different domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/systemtap
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/systemtap.log) should be checked for any
+ warnings or errors.
diff --git a/src/pmdas/systemtap/Remove b/src/pmdas/systemtap/Remove
new file mode 100755
index 0000000..6b25342
--- /dev/null
+++ b/src/pmdas/systemtap/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the SystemTap PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=systemtap
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/systemtap/pmdasystemtap.pl b/src/pmdas/systemtap/pmdasystemtap.pl
new file mode 100644
index 0000000..801b788
--- /dev/null
+++ b/src/pmdas/systemtap/pmdasystemtap.pl
@@ -0,0 +1,168 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+use vars qw( $pmda $id );
+my $probe_indom = 0;
+my $probe_script = pmda_config('PCP_PMDAS_DIR') . '/systemtap/probes.stp';
+my $probe_command = "/usr/bin/stap -m pmdasystemtap $probe_script";
+my @probe_instances = ( 0 => 'sync', 1 => 'readdir' );
+my ( $sync_count, $sync_pid, $sync_cmd ) = ( 0, 0, "(none)" );
+my ( $readdir_count, $readdir_pid, $readdir_cmd ) = ( 0, 0, "(none)" );
+
+sub systemtap_input_callback
+{
+ ( $id, $_ ) = @_;
+ # $pmda->log($_);
+
+ if (/^readdir: \((\d+)\) (.*)$/) {
+ ( $readdir_pid, $readdir_cmd ) = ( $1, $2 );
+ $readdir_count++;
+ }
+ elsif (/^sync: \((\d+)\) (.*)$/) {
+ ( $sync_pid, $sync_cmd ) = ( $1, $2 );
+ $sync_count++;
+ }
+}
+
+sub systemtap_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ if ($inst < 0 || $inst > 1) { return (PM_ERR_INST, 0); }
+ if ($cluster == 0) {
+ if ($item == 0) {
+ if ($inst == 0) { return ($sync_count, 1); }
+ else { return ($readdir_count, 1); }
+ }
+ elsif ($item == 1) {
+ if ($inst == 0) { return ($sync_pid, 1); }
+ else { return ($readdir_pid, 1); }
+ }
+ elsif ($item == 2) {
+ if ($inst == 0) { return ($sync_cmd, 1); }
+ else { return ($readdir_cmd, 1); }
+ }
+ }
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda = PCP::PMDA->new('systemtap', 88);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, $probe_indom,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'systemtap.probes.count',
+ 'Number of times the probe has been observed', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_32, $probe_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'systemtap.probes.pid',
+ 'The PID of the last process to pass the probe point', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_STRING, $probe_indom,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'systemtap.probes.cmd',
+ 'The name of the last process to pass the probe point', '');
+
+$pmda->add_indom($probe_indom, \@probe_instances,
+ 'Instance domain exporting each SystemTap probe', '');
+
+$pmda->set_fetch_callback(\&systemtap_fetch_callback);
+$pmda->add_pipe($probe_command, \&systemtap_input_callback, 0);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdasystemtap - Systemtap performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdasystemtap> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from the Linux Systemtap dynamic tracing toolkit.
+
+This implementation uses the stap(1) tool, which is a front-end to
+the Systemtap toolkit.
+
+=head1 INSTALLATION
+
+In order to access performance data exported by Systemtap from
+with PCP, it is necessary to perform two configuration steps:
+
+=over
+
+=item 1.
+
+Configure Systemtap probes, and verify them with stap(1).
+These should be produced in a format that is easily parsed,
+and then stored in the $PCP_PMDAS_DIR/systemtap/probes.stp
+file.
+
+=item 2.
+
+Configure B<pmdasystemtap> to extract the values from the text
+produced by stap. Two example probes are implemented in the
+default systemtap PMDA script - readdir and sync traces (see
+$PCP_PMDAS_DIR/systemtap/pmdasystemtap.pl for details).
+
+=back
+
+ # cd $PCP_PMDAS_DIR/systemtap
+ # [ edit probes.stp, test /usr/bin/stap probes.stp ]
+ # [ edit pmdasystemtap.pl ]
+
+Once this is setup, you can access the names and values for the
+systemtap performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/systemtap
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/systemtap
+ # ./Remove
+
+B<pmdasystemtap> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/systemtap/probes.stp
+
+probe configuration file for stap(1), run by B<pmdasystemtap>
+
+=item $PCP_PMDAS_DIR/systemtap/Install
+
+installation script for the B<pmdasystemtap> agent
+
+=item $PCP_PMDAS_DIR/systemtap/Remove
+
+undo installation script for the B<pmdasystemtap> agent
+
+=item $PCP_LOG_DIR/pmcd/systemtap.log
+
+default log file for error messages from B<pmdasystemtap>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1) and stap(1).
diff --git a/src/pmdas/systemtap/probes.stp b/src/pmdas/systemtap/probes.stp
new file mode 100644
index 0000000..fbb1923
--- /dev/null
+++ b/src/pmdas/systemtap/probes.stp
@@ -0,0 +1,6 @@
+probe kernel.function("vfs_readdir") {
+ printf ("readdir: (%d) %s\n", pid(), execname())
+}
+probe kernel.function("sys_sync") {
+ printf ("sync: (%d) %s\n", pid(), execname())
+}
diff --git a/src/pmdas/trace/GNUmakefile b/src/pmdas/trace/GNUmakefile
new file mode 100644
index 0000000..64d2512
--- /dev/null
+++ b/src/pmdas/trace/GNUmakefile
@@ -0,0 +1,109 @@
+#
+# Copyright (c) 2000-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = trace
+DOMAIN = TRACE
+
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+DEMODIR = $(PCP_DEMOS_DIR)/$(IAM)
+
+SCRIPTS = Install Remove
+OTHERS = pmns root
+DFILES = help README
+DEMOS = app1.c app2.c app3.c fapp1.f japp1.java
+DEMOFILES = README.demos Makefile.proto GNUmakefile.stub stub.c
+APPS = app1$(EXECSUFFIX) app2$(EXECSUFFIX) app3$(EXECSUFFIX)
+
+LCFLAGS = -I.
+LLDFLAGS= -L$(TOPDIR)/src/libpcp/src -L$(TOPDIR)/src/libpcp_trace/src
+LLDLIBS = $(PCP_TRACELIB)
+
+LDIRT = *.log *.dir *.pag *.o $(APPS) tmp.c \
+ pmtrace.c Makefile.demos
+
+LSRCFILES= $(SCRIPTS) $(OTHERS) $(DEMOS) $(DEMOFILES) $(DFILES)
+
+SUBDIRS = src
+
+default: $(SUBDIRS) Makefile.demos build-me
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: demos
+
+install: $(SUBDIRS) Makefile.demos
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(OTHERS) $(DFILES) $(PMDADIR)
+ $(INSTALL) -m 755 -d $(DEMODIR)
+ $(INSTALL) -m 644 Makefile.demos $(DEMODIR)/Makefile
+ $(INSTALL) -m 644 README.demos $(DEMODIR)/README
+ $(INSTALL) -m 644 GNUmakefile.stub $(DEMODIR)/Makefile.stub
+ $(INSTALL) -m 644 stub.c pmtrace.c $(DEMOS) $(DEMODIR)
+
+else
+build-me:
+install: $(SUBDIRS)
+endif
+
+demos: $(APPS) pmtrace.c
+
+pmtrace.c: $(TOPDIR)/src/pmtrace/pmtrace.c
+ rm -f $@ && cp $< $@
+
+app1.o: app1.c $(TOPDIR)/src/include/pcp/pmapi.h
+ @rm -f tmp.c
+ sed -e 's;<pcp/\(.*\)>;"\1";' app1.c >tmp.c
+ $(CCF) -c -o $@ tmp.c
+ @rm -f tmp.c
+
+app2.o: app2.c $(TOPDIR)/src/include/pcp/pmapi.h
+ @rm -f tmp.c
+ sed -e 's;<pcp/\(.*\)>;"\1";' app2.c >tmp.c
+ $(CCF) -c -o $@ tmp.c
+ @rm -f tmp.c
+
+app3.o: app3.c $(TOPDIR)/src/include/pcp/pmapi.h
+ @rm -f tmp.c
+ sed -e 's;<pcp/\(.*\)>;"\1";' app3.c >tmp.c
+ $(CCF) -c -o $@ tmp.c
+ @rm -f tmp.c
+
+app1$(EXECSUFFIX): app1.o
+ $(CCF) -o $@ $(LDFLAGS) app1.o $(LDLIBS)
+
+app2$(EXECSUFFIX): app2.o
+ $(CCF) -o $@ $(LDFLAGS) app2.o $(LDLIBS)
+
+app3$(EXECSUFFIX): app3.o
+ $(CCF) -o $@ $(LDFLAGS) app3.o $(LDLIBS) $(LIB_FOR_PTHREADS)
+
+default_pcp: default
+
+install_pcp: install
+
+.NOTPARALLEL:
+Makefile.demos: Makefile.proto
+ rm -f $@
+ sed \
+ -e 's/PTHREAD_LIB/$(LIB_FOR_PTHREADS)/' \
+ -e 's/DLOPEN_LIB/$(LIB_FOR_DLOPEN)/' \
+ -e 's/MATH_LIB/$(LIB_FOR_MATH)/' \
+ <$^ >$@
diff --git a/src/pmdas/trace/GNUmakefile.stub b/src/pmdas/trace/GNUmakefile.stub
new file mode 100644
index 0000000..8ac95ae
--- /dev/null
+++ b/src/pmdas/trace/GNUmakefile.stub
@@ -0,0 +1,69 @@
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+include $(PCP_ETC_DIR)/pcp.conf
+
+# need to deal with these ...
+# ELF style
+# /usr/lib/libpcp_trace.a /usr/lib/libpcp_trace.so /usr/lib/libpcp_trace.so.2
+# Mac OS X style
+# /usr/lib/libpcp_trace.2.dylib
+#
+DSO_SUFFIX = $(shell ls $(PCP_LIB_DIR)/libpcp_trace.* | sed -e '/\.a$$/d' -e 's/.*libpcp_trace//' -e 's/\.[0-9][0-9]*//' -e 's/^\.//' | sed -e 1q)
+ifeq "$(DSO_SUFFIX)" ""
+$(error cannot set DSO_SUFFIX on this platform)
+endif
+
+# Note: DSO_VERSION includes (starts with or ends with) $(DSO_SUFFIX)
+#
+DSO_VERSION = $(shell ls $(PCP_LIB_DIR)/libpcp_trace.* | sed -n -e '/\.a$$/d' -e 's/.*libpcp_trace\.//' -e '/\.$(DSO_SUFFIX)/p' -e '/$(DSO_SUFFIX)\./p' | sed -e 's/^\.//' -e 1q)
+ifeq "$(DSO_VERSION)" ""
+$(error cannot set DSO_VERSION on this platform)
+endif
+
+SHELL = /bin/sh
+CC = cc
+TARGETS = lib/libpcp_trace.$(DSO_VERSION)
+ifeq "$(shell [ -f $(PCP_LIB_DIR)/libpcp_trace.$(DSO_SUFFIX) ] && echo 1)" "1"
+TARGETS += lib/libpcp_trace.$(DSO_SUFFIX)
+endif
+CFILES = stub.c
+CFLAGS += -DPMTRACE_DEBUG
+CFLAGS += -fPIC -fno-strict-aliasing
+LDIRT = lib lib32 lib64
+
+default: $(TARGETS)
+
+lib/libpcp_trace.$(DSO_VERSION): stub.c
+ -[ ! -d lib ] && mkdir lib
+ rm -f $@
+ cd lib; $(CC) $(CFLAGS) -shared ../stub.c -o libpcp_trace.$(DSO_VERSION)
+
+lib/libpcp_trace.$(DSO_SUFFIX): lib/libpcp_trace.$(DSO_VERSION)
+ rm -f $@
+ cd lib; ln -s libpcp_trace.$(DSO_VERSION) libpcp_trace.$(DSO_SUFFIX)
+
+clean:
+ rm -rf $(LDIRT)
+
+clobber:
+ rm -rf $(LDIRT) $(TARGETS)
+
+debug:
+ @echo "DSO_SUFFIX=$(DSO_SUFFIX)"
+ @echo "DSO_VERSION=$(DSO_VERSION)"
+ @echo "TARGETS=$(TARGETS)"
diff --git a/src/pmdas/trace/Install b/src/pmdas/trace/Install
new file mode 100644
index 0000000..f092d4f
--- /dev/null
+++ b/src/pmdas/trace/Install
@@ -0,0 +1,275 @@
+#! /bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Trace PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=trace
+pmda_interface=2
+forced_restart=false
+
+# Override interactive dialog from pmdaSetup in pmdaproc.sh
+#
+__choose_mode()
+{
+ echo "Installing the \"$iam\" Performance Metrics Domain Agent (PMDA) ..."
+ echo
+}
+
+numeric=0
+
+getnumeric()
+{
+ if [ "X$2" = "X" ]
+ then
+ numeric=0
+ elif [ "X`expr 0 + $2 2>/dev/null`" != "X$2" ]
+ then
+ echo "-- Sorry, $1 must be numeric (not $2) --"
+ return 1
+ else
+ numeric=$2
+ fi
+ return 0
+}
+
+getunits()
+{
+ metric_name=$1
+ option_name=$2
+
+ # Default dimension and scale values
+ #
+ dimspace=0
+ dimtime=0
+ dimcount=0
+ scalespace=0
+ scaletime=0
+ scalecount=0
+
+ cat - <<EOF
+
+The dimension and scale for the ${metric_name} metrics may be expressed
+in terms of Space, Time and Count (i.e. events or messages). The default
+dimension and scale is "None". Do you wish to accept the default dimension
+EOF
+ $PCP_ECHO_PROG $PCP_ECHO_N "and scale [y]? ""$PCP_ECHO_C"
+
+ read ans
+ unitschange=false
+ case $ans in
+ N|n|NO|No|no) unitschange=true;;
+ *) unitschange=false;;
+ esac
+
+ if [ $unitschange = true ]
+ then
+ echo
+echo "The dimension is expressed in terms of powers of Space, Time and Count."
+ echo "For example, bytes per second would be 1 -1 0, and milliseconds per message "
+ echo "would be 0 1 -1."
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Enter the dimension for Space, Time and Count [0 0 0]: ""$PCP_ECHO_C"
+ dimspace=0; dimtime=0; dimcount=0
+ read s t c
+
+ getnumeric 'Scale dimension' $s
+ [ $? -eq 1 ] && continue
+ dimspace=$numeric
+
+ getnumeric 'Time dimension' $t
+ [ $? -eq 1 ] && continue
+ dimtime=$numeric
+
+ getnumeric 'Count dimension' $c
+ [ $? -eq 1 ] && continue
+ dimcount=$numeric
+
+ break
+ done
+
+ if [ "$dimspace" != 0 ]
+ then
+ while true
+ do
+ echo "Scale for the Space dimension is expressed as:"
+ echo " 0 (bytes)"
+ echo " 1 (kilobytes)"
+ echo " 2 (megabytes)"
+ echo " 3 (gigabytes)"
+ echo " 4 (terabytes)"
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Enter the scale for the Space dimension [0]: '"$PCP_ECHO_C"
+ read scale
+ scalespace=0
+
+ getnumeric 'Space scale' $scale
+ [ $? -eq 1 ] && continue
+ if [ $numeric -lt 0 -o $numeric -gt 4 ]
+ then
+ echo "-- Sorry, Space scale must be between 0 and 4 --"
+ continue
+ fi
+ scalespace=$numeric
+ break
+ done
+ fi
+
+ if [ "$dimtime" != 0 ]
+ then
+ while true
+ do
+ echo "Scale for the Time dimension is expressed as:"
+ echo " 0 (nanoseconds)"
+ echo " 1 (microseconds)"
+ echo " 2 (milliseconds)"
+ echo " 3 (seconds)"
+ echo " 4 (minutes)"
+ echo " 5 (hours)"
+ $PCP_ECHO_PROG $PCP_ECHO_N "Enter the scale for the Time dimension: ""$PCP_ECHO_C"
+ read scale
+ scaletime=0
+
+ getnumeric 'Time scale' $scale
+ [ $? -eq 1 ] && continue
+ if [ $numeric -lt 0 -o $numeric -gt 5 ]
+ then
+ echo "-- Sorry, Time scale must be between 0 and 5 --"
+ continue
+ fi
+ scaletime=$numeric
+ break
+ done
+ fi
+
+ if [ "$dimcount" != 0 ]
+ then
+ while true
+ do
+ echo "Scale for the Count dimension is expressed:"
+ $PCP_ECHO_PROG $PCP_ECHO_N " as a power of 10 (e.g. 6 for 10^6, or -3 for 10^-3) [0]: ""$PCP_ECHO_C"
+ read scale
+ scalecount=0
+
+ getnumeric 'Count scale' $scale
+ [ $? -eq 1 ] && continue
+ scalecount=$numeric
+ break
+ done
+ fi
+ fi
+
+ # show what units we're going with ..
+ echo
+ echo "Using the following units for ${metric_name}:"
+ echo " Dimensions: space=$dimspace time=$dimtime count=$dimcount"
+ echo " Scale: space=$scalespace time=$scaletime count=$scalecount"
+ echo
+
+ if [ $unitschange = true ]
+ then
+ uarg="$dimspace,$dimtime,$dimcount,$scalespace,$scaletime,$scalecount"
+ args="$args ${option_name} $uarg"
+ fi
+}
+
+
+pmdaSetup
+
+$PCP_ECHO_PROG $PCP_ECHO_N "Use the default installation [y]? ""$PCP_ECHO_C"
+read nogo
+goforit=false
+case $nogo in
+ n|no|N|NO|No) goforit=true;;
+ *) goforit=false;;
+esac
+echo
+if [ $goforit = false ]
+then
+ :
+elif $do_pmda
+then
+ args=""
+ value=""
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Trace period (in seconds) [60]? ""$PCP_ECHO_C"
+ read value
+ getnumeric 'Trace period' $value >/dev/null
+ [ $? -ne 1 -a $numeric -ne 0 ] && args="-T $numeric"
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Number of buckets [5]? ""$PCP_ECHO_C"
+ read value
+ getnumeric 'Number of buckets' $value >/dev/null
+ [ $? -ne 1 -a $numeric -ne 0 ] && args="$args -N $numeric"
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Port number for client connections [4323]? ""$PCP_ECHO_C"
+ read value
+ getnumeric 'Port number' $value >/dev/null
+ [ $? -ne 1 -a $numeric -ne 0 ] && args="$args -I $numeric"
+
+ getunits trace.observe.value -U
+ getunits trace.counter.value -V
+
+ while true
+ do
+ access=""
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Client host access - (A)llow/(D)isallow [Enter to complete install]? ""$PCP_ECHO_C"
+ read access
+ access=`echo $access | tr -d ' '`
+ [ "X$access" = "X" ] && break
+ case $access in
+ A|a|Allow|allow)
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Host specification (IP mask/Enter to cancel): ""$PCP_ECHO_C"
+ read access
+ access=`echo $access | tr -d ' '`
+ if [ "X$access" != "X" ]
+ then
+ maxconns=""
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Maximum number of connections from $access (Enter for no limit): ""$PCP_ECHO_C"
+ read maxconns
+ maxconns=`echo $maxconns | tr -d ' '`
+ [ "X$maxconns" = "X" ] && maxconns=0
+ args="$args"" -A ""allow:$access:$maxconns"
+ fi;;
+ D|d|Disallow|disallow)
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Host specification (IP mask/Enter to cancel): ""$PCP_ECHO_C"
+ read access
+ access=`echo $access | tr -d ' '`
+ [ "X$access" != "X" ] && args="$args"" -A ""disallow:$access";;
+ *) echo 'Try again, "'$access'" not supported.';;
+ esac
+ done
+ echo
+fi
+
+# for debugging the PMDA, uncomment this line ...
+#
+#args="-D appl0,appl1,pdu $args"
+
+# Do it ...
+#
+pmdaInstall
+echo Note: some warnings are expected until trace API calls are made - refer to
+echo " the man pages for pmtrace(1) and pmdatrace(3) for further details."
+
+exit 0
diff --git a/src/pmdas/trace/Makefile.proto b/src/pmdas/trace/Makefile.proto
new file mode 100644
index 0000000..397035f
--- /dev/null
+++ b/src/pmdas/trace/Makefile.proto
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = /bin/sh
+CC = cc
+JC = javac
+F77C = f77
+F90C = f90
+
+TARGETS = app1 app2 app3 fapp1.f77 fapp1.f90 pmtrace japp1.class
+
+CDEMOS = app1 app2 app3 pmtrace
+F77DEMO = fapp1.f77
+F90DEMO = fapp1.f90
+JDEMO = japp1.class
+
+CFLAGS = -DPMTRACE_DEBUG -I$(PCP_INC_DIR)
+FFLAGS =
+JFLAGS =
+
+default: $(CDEMOS)
+fortran77: $(F77DEMO)
+fortran90: $(F90DEMO)
+java: $(JDEMO)
+
+pmtrace: pmtrace.c
+ rm -f $@
+ $(CC) $(CFLAGS) -o $@ pmtrace.c -lpcp -lpcp_trace
+
+app1: app1.c
+ rm -f $@
+ $(CC) $(CFLAGS) -o $@ app1.c -lpcp -lpcp_trace
+
+app2: app2.c
+ rm -f $@
+ $(CC) $(CFLAGS) -o $@ app2.c -lpcp -lpcp_trace
+
+app3: app3.c
+ rm -f $@
+ $(CC) $(CFLAGS) -o $@ app3.c -lpcp -lpcp_trace PTHREAD_LIB DLOPEN_LIB MATH_LIB
+
+fapp1.77: fapp1.f
+ rm -f $@
+ $(F77C) $(FFLAGS) -o $@ fapp1.f -lpcp -lpcp_trace
+
+fapp1.90: fapp1.f
+ rm -f $@
+ $(F90C) $(FFLAGS) -o $@ fapp1.f -lpcp -lpcp_trace
+
+japp1.class: japp1.java
+ rm -f $@
+ $(JC) $(JFLAGS) japp1.java
+
+clean:
+ rm -f *.o
+
+clobber: clean
+ rm -f $(TARGETS)
diff --git a/src/pmdas/trace/README b/src/pmdas/trace/README
new file mode 100644
index 0000000..0acc1a7
--- /dev/null
+++ b/src/pmdas/trace/README
@@ -0,0 +1,62 @@
+Trace PMDA
+==========
+
+This PMDA exports application-level transaction and event statistics.
+
+The PMDA needs to be used in conjunction with the pcp_trace library,
+which provides the pmtracebegin, pmtraceend, pmtracepoint, ... functions
+to user-level programs.
+
+For information about the use of the pcp_trace dynamic library and its
+function call interface, see pmdatrace(1) and pmdatrace(3). Also, the
+example programs - $PCP_VAR_DIR/demos/trace/*.c are useful as starting
+points.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT trace
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/trace
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ If you choose the "default" installation, appropriate values will
+ be assigned to those parameters that control the customization of
+ the PMDA. Otherwise consult the pmdatrace(1) man page for a
+ description of the customization parameters.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/trace
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/trace.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/trace/README.demos b/src/pmdas/trace/README.demos
new file mode 100644
index 0000000..15227a6
--- /dev/null
+++ b/src/pmdas/trace/README.demos
@@ -0,0 +1,71 @@
+sample pcp_trace applications
+=============================
+
+pmtrace
+ is a sample application that uses the pcp_trace interface to send
+trace data to the trace PMDA (Performance Metrics Domain Agent).
+
+The binary is shipped as part of pcp and should be installed in
+$PCP_BIN_DIR/pmtrace. A pmtrace(1) man page is available.
+
+The source is shipped as part of pcp as well and is installed in
+$PCP_DEMOS_DIR/trace. If you have the C compiler installed, the
+source and Makefile in this directory may be used to create a
+functionally equivalent binary, simply by entering the command
+
+ % make pmtrace
+
+The source in pmtrace.c demonstrates many of the trace services.
+
+
+The C interface ( pmtrace.c, app1.c, app2.c, and app3.c )
+===============
+ The default Makefile rules build the C applications only, so
+these applications can be built simply by using the command
+
+ % make
+
+
+The Fortran Interface ( fapp1.f )
+=====================
+ To build the sample Fortran program, using either the f77 or
+f90 compilers, use one of these commands
+
+ % make fortran77
+ % make fortran90
+
+
+The Java Interface ( japp1.java )
+==================
+ To build the sample Java program, and provided you have the
+java compiler installed, use the command
+
+ % make java
+
+ Setting the environment variable $CLASSPATH to include the full
+path to the trace.class file (/usr/java/classes/com/sgi/pcp) allows
+the application to compile and run successfuly.
+To run the demo application, after compilation type
+
+ % java japp1
+
+which passes the compiled class file into the java interpreter for
+subsequent execution.
+
+
+The pcp_trace "stub" library
+============================
+ To ensure that applications linked with the pcp_trace library are
+not locked into being SGI-specific, a "stub" library which has all of
+the pcp_trace entry points defined and simple debug switching enabled,
+is provided (stub.c). This shared library can be built using
+
+ % make -f Makefile.stub
+
+and is intended to be simple to port to other platforms.
+
+
+Related manual pages
+====================
+ pmdatrace(1), pmtrace(1), and pmdatrace(3).
+
diff --git a/src/pmdas/trace/Remove b/src/pmdas/trace/Remove
new file mode 100644
index 0000000..a0fa0a3
--- /dev/null
+++ b/src/pmdas/trace/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the Trace PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=trace
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/trace/app1.c b/src/pmdas/trace/app1.c
new file mode 100644
index 0000000..d08daf2
--- /dev/null
+++ b/src/pmdas/trace/app1.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * app1.c
+ *
+ * Simple program to demonstrate use of the PCP trace performance metrics
+ * domain agent (PMDA(3)). This agent needs to be installed before metrics
+ * can be made available via the performance metrics namespace (PMNS(4)),
+ * and the Performance Metrics Collector Daemon (PMCD(1)).
+ *
+ * Once this program is running, the trace PMDA metrics & instances can be
+ * viewed through PCP monitor tools such as pmchart(1), pmgadgets(1), and
+ * pmview(1). To view the help text associated with each of these metrics,
+ * use:
+ * $ pminfo -tT trace
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pcp/trace.h>
+
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ char *prog;
+
+ prog = argv[0];
+ sts = pmtracestate(PMTRACE_STATE_API|PMTRACE_STATE_COMMS|PMTRACE_STATE_PDU);
+ fprintf(stderr, "%s: start: %s (state=0x%x)\n", prog,
+ pmtraceerrstr(0), sts); /* force call to all library symbols */
+
+ if ((sts = pmtracebegin("simple")) < 0) {
+ fprintf(stderr, "%s: pmtracebegin error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+ if (sleep(2) != 0) {
+ fprintf(stderr, "%s: sleep prematurely awaken\n", prog);
+ pmtraceabort("simple");
+ }
+ if ((sts = pmtraceend("simple")) < 0) {
+ fprintf(stderr, "%s: pmtraceend error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmtracebegin("ascanbe")) < 0) {
+ fprintf(stderr, "%s: pmtracebegin error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+ sleep(1);
+ if ((sts = pmtraceend("ascanbe")) < 0) {
+ fprintf(stderr, "%s: pmtraceend error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmtraceobs("observe", 101.0)) < 0) {
+ fprintf(stderr, "%s: pmtraceobs error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmtracecounter("counter", 101.1)) < 0) {
+ fprintf(stderr, "%s: pmtracecounter error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmtracepoint("imouttahere")) < 0) {
+ fprintf(stderr, "%s: pmtracepoint error: %s\n",
+ prog, pmtraceerrstr(sts));
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/trace/app2.c b/src/pmdas/trace/app2.c
new file mode 100644
index 0000000..540c4ce
--- /dev/null
+++ b/src/pmdas/trace/app2.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * app2.c
+ *
+ * Sample program to demonstrate use of the PCP trace performance metrics
+ * domain agent (PMDA(3)). This agent needs to be installed before metrics
+ * can be made available via the performance metrics namespace (PMNS(4)),
+ * and the Performance Metrics Collector Daemon (PMCD(1)).
+ *
+ * Once this program is running, the trace PMDA metrics & instances can be
+ * viewed through PCP monitor tools such as pmchart(1), pmgadgets(1), and
+ * pmview(1). To view the help text associated with each of these metrics,
+ * use:
+ * $ pminfo -tT trace
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+#include <pcp/trace.h>
+
+
+#define IO_UPPER_LIMIT 1000 /* I/O ops */
+#define CPU_UPPER_LIMIT 0xffff /* iterations */
+#define TIME_UPPER_LIMIT 10 /* seconds */
+
+static void io_sucker(void);
+static void cpu_sucker(void);
+static void time_sucker(void);
+static char *prog;
+
+int
+main(int argc, char **argv)
+{
+ int i, sts;
+
+ prog = argv[0];
+ srand48(time(0));
+ /* uncomment this for debugging information */
+ /* pmtracestate(PMTRACE_STATE_API|PMTRACE_STATE_COMMS|PMTRACE_STATE_PDU); */
+ /* uncomment this to use the asynchronous protocol */
+ /* pmtracestate(PMTRACE_STATE_ASYNC); */
+
+ for (i = 0;; i++) {
+ if ((sts = pmtracepoint("mainloop")) < 0) {
+ fprintf(stderr, "%s: mainloop point trace failed (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ exit(1);
+ }
+ switch(i % 3) {
+ case 0:
+ time_sucker();
+ break;
+ case 1:
+ io_sucker();
+ break;
+ case 2:
+ cpu_sucker();
+ break;
+ }
+ }
+}
+
+
+static void
+cpu_sucker(void)
+{
+ int i, j, sts;
+ double array[100];
+ long iterations;
+
+ if ((sts = pmtracebegin("cpu_sucker")) < 0) {
+ fprintf(stderr, "%s: cpu_sucker begin (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+
+ iterations = lrand48() % CPU_UPPER_LIMIT;
+ memset((void *)array, 0, 100*sizeof(double));
+
+ for (i = 0; i < iterations; i++)
+ for (j = 0; j < 100; j++)
+ array[j] = (double)(j*iterations);
+
+ if ((sts = pmtraceend("cpu_sucker")) < 0) {
+ fprintf(stderr, "%s: cpu_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+}
+
+static void
+time_sucker(void)
+{
+ long seconds;
+ int sts;
+
+ if ((sts = pmtracebegin("time_sucker")) < 0) {
+ fprintf(stderr, "%s: time_sucker start (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+
+ seconds = lrand48() % TIME_UPPER_LIMIT;
+ sleep((unsigned int)seconds);
+
+ if ((sts = pmtraceend("time_sucker")) < 0) {
+ fprintf(stderr, "%s: time_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+}
+
+static void
+io_sucker(void)
+{
+ long characters;
+ FILE *foo;
+ int i, sts;
+
+ if ((sts = pmtracebegin("io_sucker")) < 0) {
+ fprintf(stderr, "%s: io_sucker start (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+
+ if ((foo = fopen("/dev/null", "rw")) == NULL) {
+ fprintf(stderr, "%s: io_sucker can't open /dev/null.\n", prog);
+ return;
+ }
+
+ characters = lrand48() % IO_UPPER_LIMIT;
+ for (i = 0; i < characters; i++) {
+ fgetc(foo);
+ fputc('!', foo);
+ }
+ fclose(foo);
+
+ if ((sts = pmtraceend("io_sucker")) < 0) {
+ fprintf(stderr, "%s: io_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return;
+ }
+}
diff --git a/src/pmdas/trace/app3.c b/src/pmdas/trace/app3.c
new file mode 100644
index 0000000..dc0e9c5
--- /dev/null
+++ b/src/pmdas/trace/app3.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * app3.c
+ *
+ * Parallel program to demonstrate use of the PCP trace performance metrics
+ * domain agent (PMDA(3)). This agent needs to be installed before metrics
+ * can be made available via the performance metrics namespace (PMNS(4)),
+ * and the Performance Metrics Collector Daemon (PMCD(1)).
+ *
+ * Once this program is running, the trace PMDA metrics & instances can be
+ * viewed through PCP monitor tools such as pmchart(1), pmgadgets(1), and
+ * pmview(1). To view the help text associated with each of these metrics,
+ * use:
+ * $ pminfo -tT trace
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <pthread.h>
+#include <pcp/trace.h>
+
+
+#define IO_UPPER_LIMIT 1000 /* I/O ops */
+#define CPU_UPPER_LIMIT 0xffff /* iterations */
+#define TIME_UPPER_LIMIT 10 /* seconds */
+
+static void * pio_sucker(void *);
+static void * pcpu_sucker(void *);
+static void * ptime_sucker(void *);
+static char *prog;
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ pthread_t p[3];
+
+ prog = argv[0];
+
+ pthread_create(p, NULL, pio_sucker, NULL);
+ pthread_create(p+1, NULL, pcpu_sucker, NULL);
+ pthread_create(p+2, NULL, ptime_sucker, NULL);
+
+ for (i=0; i < 3; i++) {
+ wait(NULL);
+ fprintf(stderr, "%s: reaped sproc #%d\n", prog, i);
+ }
+
+ exit(0);
+}
+
+
+static void *
+pcpu_sucker(void *dummy)
+{
+ int i, j, loops, sts;
+ double array[100];
+ long iterations;
+
+ for (loops = 0; loops < 10; loops++) {
+ if ((sts = pmtracebegin("pcpu_sucker")) < 0) {
+ fprintf(stderr, "%s: pcpu_sucker begin (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+
+ iterations = lrand48() % CPU_UPPER_LIMIT;
+ memset((void *)array, 0, 100*sizeof(double));
+
+ for (i = 0; i < iterations; i++)
+ for (j = 0; j < 100; j++)
+ array[j] = (double)(j*iterations);
+
+ if ((sts = pmtraceend("pcpu_sucker")) < 0) {
+ fprintf(stderr, "%s: pcpu_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+ }
+ fprintf(stderr, "%s: finished %d cpu-bound iterations.\n", prog, loops);
+ return NULL;
+}
+
+static void *
+ptime_sucker(void *dummy)
+{
+ long seconds;
+ int loops, sts;
+
+ for (loops = 0; loops < 10; loops++) {
+ if ((sts = pmtracebegin("ptime_sucker")) < 0) {
+ fprintf(stderr, "%s: ptime_sucker start (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+
+ seconds = lrand48() % TIME_UPPER_LIMIT;
+ sleep((unsigned int)seconds);
+
+ if ((sts = pmtraceend("ptime_sucker")) < 0) {
+ fprintf(stderr, "%s: ptime_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+ }
+ fprintf(stderr, "%s: finished %d timer iterations.\n", prog, loops);
+ return NULL;
+}
+
+static void *
+pio_sucker(void *dummy)
+{
+ long characters;
+ FILE *foo;
+ int i, loops, sts;
+
+ for (loops = 0; loops < 10; loops++) {
+ if ((sts = pmtracebegin("pio_sucker")) < 0) {
+ fprintf(stderr, "%s: pio_sucker start (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+
+ if ((foo = fopen("/dev/null", "rw")) == NULL) {
+ fprintf(stderr, "%s: pio_sucker can't open /dev/null.\n", prog);
+ return NULL;
+ }
+
+ characters = lrand48() % IO_UPPER_LIMIT;
+ for (i = 0; i < characters; i++) {
+ fgetc(foo);
+ fputc('!', foo);
+ }
+ fclose(foo);
+
+ if ((sts = pmtraceend("pio_sucker")) < 0) {
+ fprintf(stderr, "%s: pio_sucker end (%d): %s\n",
+ prog, sts, pmtraceerrstr(sts));
+ return NULL;
+ }
+ }
+ fprintf(stderr, "%s: finished %d io-bound iterations.\n", prog, loops);
+ return NULL;
+}
diff --git a/src/pmdas/trace/fapp1.f b/src/pmdas/trace/fapp1.f
new file mode 100644
index 0000000..9fc68e8
--- /dev/null
+++ b/src/pmdas/trace/fapp1.f
@@ -0,0 +1,102 @@
+ program fapp1
+
+C Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+C
+C This program is free software; you can redistribute it and/or modify it
+C under the terms of the GNU General Public License as published by the
+C Free Software Foundation; either version 2 of the License, or (at your
+C option) any later version.
+C
+C This program is distributed in the hope that it will be useful, but
+C WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+C or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+C for more details.
+C
+C You should have received a copy of the GNU General Public License along
+C with this program; if not, write to the Free Software Foundation, Inc.,
+C 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+C fapp1.f
+C
+C Simple program to demonstrate use of the PCP trace performance metrics
+C domain agent (PMDA(3)). This agent needs to be installed before metrics
+C can be made available via the performance metrics namespace (PMNS(4)),
+C and the Performance Metrics Collector Daemon (PMCD(1)).
+C
+C Once this program is running, the trace PMDA metrics & instances can be
+C viewed through PCP monitor tools such as pmchart(1), pmgadgets(1), and
+C pmview(1). To view the help text associated with each of these metrics,
+C use:
+C $ pminfo -tT trace
+C
+C The pmtracestate constants are defined in /usr/include/pcp/trace.h
+C
+ external pmtracebegin, pmtraceend, pmtracepoint, pmtraceerrstr, pmtracestate
+ integer pmtracebegin, pmtraceend, pmtracepoint
+ integer sts
+ integer debug
+ character*5 prog
+ character*40 emesg
+ real*8 value
+ integer dbg_noagent, dbg_api, dbg_comms, dbg_pdu
+ parameter (dbg_noagent = 1, dbg_api = 2, dbg_comms = 4, dbg_pdu = 8)
+
+ prog='fapp1'
+
+C Addition below is the equivalent to the C 'logical or' operator as
+C trace API constants are all disjoint and the high bit is never set.
+ debug = (dbg_api + dbg_comms + dbg_pdu)
+ call pmtracestate(debug)
+
+ sts = pmtracebegin('simple')
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtracebegin error: ',emesg
+ stop 1
+ endif
+ call sleep(2)
+ sts = pmtraceend('simple')
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtraceend error: ',emesg
+ stop 1
+ endif
+
+ sts = pmtracebegin('ascanbe')
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtracebegin error: ',emesg
+ stop 1
+ endif
+ call sleep(1)
+ sts = pmtraceend('ascanbe')
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtraceend error: ',emesg
+ stop 1
+ endif
+
+ sts = pmtracepoint('imouttahere')
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtracepoint error: ',emesg
+ stop 1
+ endif
+
+ value = 340.5
+ sts = pmtraceobs('end point', value)
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtraceobs error: ',emesg
+ stop 1
+ endif
+
+ value = 340.6
+ sts = pmtracecounter('new end point', value)
+ if (sts .lt. 0) then
+ call pmtraceerrstr(sts, emesg)
+ print *,prog,': pmtracecounter error: ',emesg
+ stop 1
+ endif
+
+ end
diff --git a/src/pmdas/trace/help b/src/pmdas/trace/help
new file mode 100644
index 0000000..d753046
--- /dev/null
+++ b/src/pmdas/trace/help
@@ -0,0 +1,156 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# trace PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ TRACE.0 Instance domain "trace tag name" for trace PMDA
+There is one instance for each transaction which has been seen by
+the trace PMDA during the reporting period.
+
+@ trace.transact.count count by transaction
+Count by transaction tag of transactions serviced since the trace
+PMDA was started.
+
+@ trace.transact.rate transaction completion rate over time period
+The rate at which each transaction is completed, calculated over the
+trace PMDAs aggregation period.
+
+The exported value is equal to the count of transactions completed
+during the aggregation interval, divided by the aggregation interval.
+
+@ trace.transact.ave_time average transaction time over time period
+The average time taken to complete each transaction, calculated over
+the trace PMDAs aggregation period.
+
+The exported value is equal to the sum of the transaction completion
+times seen during the aggregation interval divided by the number of
+transactions completed during the aggregation interval.
+
+@ trace.transact.max_time maximum time per transaction
+Maximum recorded service time per transaction tag within the current
+reporting period.
+
+@ trace.transact.min_time minimum time per transaction
+Minimum recorded service time per transaction tag within the current
+reporting period.
+
+@ trace.transact.total_time total time per transaction
+Cumulative time spent processing each transaction since the trace PMDA
+was started.
+
+@ trace.point.count count of point function executions
+Count by point tag of marked application points serviced since the
+trace PMDA was started.
+
+@ trace.point.rate point rate over time period
+The rate at which execution points are completed, calculated over the
+aggregation period in use by the trace PMDA.
+
+The exported value is equal to the count of successful pmtracepoint(3)
+calls made during the aggregation interval, divided by the aggregation
+interval.
+
+@ trace.counter.count count of counter values received
+Count, by counter tag, of `counter' values received since the trace
+PMDA was started.
+
+@ trace.counter.rate counter value received rate over time period
+The rate at which execution counters are received, calculated over
+the aggregation period in use by the trace PMDA.
+
+The exported value is equal to the count of pmtracecounter(3) calls made
+during the aggregation interval, divided by the aggregation interval.
+
+@ trace.counter.value counter value at last observation
+The numeric counter value associated with the last seen counter tag,
+since the trace PMDA was started.
+
+@ trace.observe.count count of observations
+Count, by observation tag, of application `observe' points serviced
+since the trace PMDA was started.
+
+@ trace.observe.rate observation rate over time period
+The rate at which execution observations are completed, calculated
+over the aggregation period in use by the trace PMDA.
+
+The exported value is equal to the count of pmtraceobs(3) calls made
+during the aggregation interval, divided by the aggregation interval.
+
+@ trace.observe.value value at last observation
+The numeric value associated with the last seen observation, since
+the trace PMDA was started.
+
+@ trace.control.period reporting time period
+Time (in seconds) over which trace performance data will be gathered.
+Any transaction or point trace data seen by the trace PMDA during the
+period will be included in the set of exported values.
+
+@ trace.control.interval update interval within time period
+The update interval (within the overall period) at which the current
+working set of performance data maintained within the trace PMDA will
+be switched into the set of historical data buffers, which are then
+used to calculate the exported performance metric values.
+This value is directly calculated from the overall time period and the
+number of buckets of historical data being maintained within the trace
+PMDA.
+
+For example, if the overall period is 60 seconds and the number of
+historical data buckets being maintained is 12, then the buffers will
+be rotated once every 5 seconds.
+
+@ trace.control.buckets number of historical buffers
+The number of buffers of historical data maintained by the trace PMDA.
+Each bucket contains all transaction and event performance data seen
+over a set time interval within the overall reporting time period.
+
+@ trace.control.port port number for client connections
+The TCP/IP port number which the trace PMDA is waiting for client
+connections on.
+
+This has been set as either the default (4322), via the command line,
+or via the PMDA_TRACE_PORT environment variable when the trace PMDA
+was started.
+
+@ trace.control.reset clear all tags known to the trace PMDA
+Storing any value into this metric with pmstore(1) will cause the trace
+PMDA to clear all tags for all metrics from its historical buffers and
+begin afresh.
+
+This is most useful when the instance domains of the trace metrics have
+become cluttered with unwanted instances, and the instance domains need
+to be refreshed without restarting pmcd(1).
+
+@ trace.control.debug set the debug level in the trace PMDA
+Storing values into this metric with pmstore(1) allows the level of
+diagnostic output from the trace PMDA to be controlled.
+
+By default, the diagnostic output will be written to the file
+$PCP_LOG_DIR/pmcd/trace.log.
diff --git a/src/pmdas/trace/japp1.java b/src/pmdas/trace/japp1.java
new file mode 100644
index 0000000..8c557d4
--- /dev/null
+++ b/src/pmdas/trace/japp1.java
@@ -0,0 +1,46 @@
+//
+// Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 2 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+// for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+public class japp1 {
+ public static void main(String[] args) {
+ int sts;
+ trace pcp = new trace();
+
+ pcp.pmtracestate(pcp.PMTRACE_STATE_API);
+
+ sts = pcp.pmtracebegin("transacting in java");
+ if (sts < 0)
+ System.out.println("pmtracebegin error: " + pcp.pmtraceerrstr(sts));
+
+ sts = pcp.pmtraceend("transacting in java");
+ if (sts < 0)
+ System.out.println("pmtraceend error: " + pcp.pmtraceerrstr(sts));
+
+ sts = pcp.pmtracepoint("java point");
+ if (sts < 0)
+ System.out.println("pmtracepoint error: " + pcp.pmtraceerrstr(sts));
+
+ sts = pcp.pmtraceobs("observing from java", 789.034018);
+ if (sts < 0)
+ System.out.println("pmtraceobs error: " + pcp.pmtraceerrstr(sts));
+
+ sts = pcp.pmtracecounter("counter from java", 789.034019);
+ if (sts < 0)
+ System.out.println("pmtracecounter error: " + pcp.pmtraceerrstr(sts));
+ }
+}
diff --git a/src/pmdas/trace/pmns b/src/pmdas/trace/pmns
new file mode 100644
index 0000000..c7ef847
--- /dev/null
+++ b/src/pmdas/trace/pmns
@@ -0,0 +1,62 @@
+/*
+ * Metrics for trace PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+trace {
+ transact
+ point
+ observe
+ counter
+ control
+}
+
+trace.transact {
+ count TRACE:0:0
+ rate TRACE:0:1
+ ave_time TRACE:0:2
+ min_time TRACE:0:3
+ max_time TRACE:0:4
+ total_time TRACE:0:5
+}
+
+trace.point {
+ count TRACE:0:6
+ rate TRACE:0:7
+}
+
+trace.observe {
+ count TRACE:0:8
+ rate TRACE:0:9
+ value TRACE:0:10
+}
+
+trace.control {
+ period TRACE:0:11
+ interval TRACE:0:12
+ buckets TRACE:0:13
+ port TRACE:0:14
+ reset TRACE:0:15
+ debug TRACE:0:16
+}
+
+trace.counter {
+ count TRACE:0:17
+ rate TRACE:0:18
+ value TRACE:0:19
+}
diff --git a/src/pmdas/trace/root b/src/pmdas/trace/root
new file mode 100644
index 0000000..1ba8c5a
--- /dev/null
+++ b/src/pmdas/trace/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { trace }
+
+#include "pmns"
+
diff --git a/src/pmdas/trace/src/GNUmakefile b/src/pmdas/trace/src/GNUmakefile
new file mode 100644
index 0000000..efb5e43
--- /dev/null
+++ b/src/pmdas/trace/src/GNUmakefile
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = trace
+DOMAIN = TRACE
+CMDTARGET = pmdatrace$(EXECSUFFIX)
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+CFILES = trace.c client.c comms.c data.c pmda.c
+HFILES = data.h client.h comms.h
+
+LCFLAGS = -I$(TOPDIR)/src/libpcp_trace/src
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_trace/src
+LLDLIBS = -lpcp_trace $(PCP_PMDALIB)
+
+LDIRT = *.log *.dir *.pag domain.h $(TARGETS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGET)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PMDADIR)/$(CMDTARGET)
+ $(INSTALL) -m 644 domain.h $(PMDADIR)/domain.h
+else
+build-me:
+install:
+endif
+
+$(IAM)$(EXECSUFFIX): $(OBJECTS)
+
+comms.o trace.o pmda.o: domain.h
+
+domain.h: ../../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/trace/src/client.c b/src/pmdas/trace/src/client.c
new file mode 100644
index 0000000..c3224dd
--- /dev/null
+++ b/src/pmdas/trace/src/client.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "trace_dev.h"
+#include "client.h"
+#include "comms.h"
+
+extern fd_set fds;
+
+int nclients; /* number of entries in array */
+int maxfd; /* largest fd currently in use */
+client_t *clients; /* array of clients */
+
+#define MIN_CLIENTS_ALLOC 8
+static int clientsize;
+
+static int newClient(void);
+
+client_t *
+acceptClient(int reqfd)
+{
+ int i, fd;
+ __pmSockLen addrlen;
+
+ i = newClient();
+ addrlen = __pmSockAddrSize();
+ fd = __pmAccept(reqfd, clients[i].addr, &addrlen);
+ if (fd == -1) {
+ __pmNotifyErr(LOG_ERR, "acceptClient(%d) accept: %s",
+ reqfd, netstrerror());
+ return NULL;
+ }
+ if (fd > maxfd)
+ maxfd = fd;
+ FD_SET(fd, &fds);
+ clients[i].fd = fd;
+ clients[i].status.connected = 1;
+ clients[i].status.padding = 0;
+ clients[i].status.protocol = 1; /* sync */
+ return &clients[i];
+}
+
+static int
+newClient(void)
+{
+ int i, j;
+
+ for (i = 0; i < nclients; i++)
+ if (!clients[i].status.connected)
+ break;
+
+ if (i == clientsize) {
+ clientsize = clientsize ? clientsize * 2 : MIN_CLIENTS_ALLOC;
+ clients = (client_t *) realloc(clients, sizeof(client_t)*clientsize);
+ if (clients == NULL)
+ __pmNoMem("newClient", sizeof(client_t)*clientsize, PM_FATAL_ERR);
+ for (j = i; j < clientsize; j++)
+ clients[j].addr = NULL;
+ }
+ clients[i].addr = __pmSockAddrAlloc();
+ if (clients[i].addr == NULL)
+ __pmNoMem("newClient", __pmSockAddrSize(), PM_FATAL_ERR);
+ if (i >= nclients)
+ nclients = i + 1;
+ return i;
+}
+
+void
+deleteClient(client_t *cp)
+{
+ int i;
+
+ for (i = 0; i < nclients; i++)
+ if (cp == &clients[i])
+ break;
+
+ if (i == nclients) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ __pmNotifyErr(LOG_ERR, "deleteClient: tried to delete non-existent client");
+ }
+#endif
+ return;
+ }
+ if (cp->fd != -1) {
+ __pmtracenomoreinput(cp->fd);
+ FD_CLR(cp->fd, &fds);
+ close(cp->fd);
+ }
+ if (cp->fd == maxfd) {
+ maxfd = -1;
+ for (i = 0; i < nclients; i++)
+ if (clients[i].fd > maxfd)
+ maxfd = clients[i].fd;
+ }
+ __pmSockAddrFree(cp->addr);
+ cp->addr = NULL;
+ cp->status.connected = 0;
+ cp->status.padding = 0;
+ cp->status.protocol = 1; /* sync */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "deleteClient: client removed (fd=%d)", cp->fd);
+#endif
+ cp->fd = -1;
+}
+
+void
+showClients(void)
+{
+ int i;
+
+ fprintf(stderr, "%s: %d connected clients:\n", pmProgname, nclients);
+ fprintf(stderr, " fd type conn client connection from\n"
+ " == ===== ==== ======================\n");
+ for (i=0; i < nclients; i++) {
+ char *hostName;
+ char *hostAddr;
+
+ fprintf(stderr, " %3d", clients[i].fd);
+ fprintf(stderr, " %s ", clients[i].status.protocol == 1 ? "sync ":"async");
+ fprintf(stderr, "%s ", clients[i].status.connected == 1 ? "up ":"down");
+ hostName = __pmGetNameInfo(clients[i].addr);
+ if (hostName == NULL) {
+ hostAddr = __pmSockAddrToString(clients[i].addr);
+ fprintf(stderr, "%s", hostAddr);
+ free(hostAddr);
+ } else {
+ fprintf(stderr, "%-40.40s", hostName);
+ free(hostName);
+ }
+ if (clients[i].denyOps != 0) {
+ fprintf(stderr, " ");
+ if (clients[i].denyOps & TR_OP_SEND)
+ fprintf(stderr, "send ");
+ }
+
+ fputc('\n', stderr);
+ }
+ fputc('\n', stderr);
+}
diff --git a/src/pmdas/trace/src/client.h b/src/pmdas/trace/src/client.h
new file mode 100644
index 0000000..9cb3ad2
--- /dev/null
+++ b/src/pmdas/trace/src/client.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+typedef struct {
+ int fd; /* socket descriptor */
+ __pmSockAddr *addr; /* address of client */
+ struct { /* connection status */
+ unsigned int connected : 1; /* client connected */
+ unsigned int version : 8; /* client pdu version */
+ unsigned int protocol : 1; /* synchronous or not */
+ unsigned int padding :22; /* currently unused */
+ } status;
+ unsigned int denyOps;
+} client_t;
+
+extern client_t *acceptClient(int);
+extern void deleteClient(client_t *);
+extern void showClients(void);
+
+#endif /* CLIENT_H */
diff --git a/src/pmdas/trace/src/comms.c b/src/pmdas/trace/src/comms.c
new file mode 100644
index 0000000..4432c05
--- /dev/null
+++ b/src/pmdas/trace/src/comms.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat. All Rights Reserved.
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "trace.h"
+#include "trace_dev.h"
+#include "domain.h"
+#include "client.h"
+#include "comms.h"
+
+extern struct timeval interval;
+extern int readData(int, int *);
+extern void timerUpdate(void);
+
+extern int maxfd;
+extern int nclients;
+extern client_t *clients;
+extern int ctlport; /* control port number */
+static int ctlfd; /* fd for control port */
+static int pmcdfd; /* fd for pmcd */
+
+void alarming(int, void *);
+static void hangup(int);
+static int getcport(void);
+
+/* currently in-use fd mask */
+fd_set fds;
+
+/* the AF event number */
+int afid = -1;
+
+void
+traceMain(pmdaInterface *dispatch)
+{
+ client_t *cp;
+ fd_set readyfds;
+ int nready, i, pdutype, sts, protocol;
+
+ ctlfd = getcport();
+ pmcdfd = __pmdaInFd(dispatch);
+ maxfd = (ctlfd > pmcdfd) ? (ctlfd):(pmcdfd);
+ FD_ZERO(&fds);
+ FD_SET(ctlfd, &fds);
+ FD_SET(pmcdfd, &fds);
+
+ signal(SIGHUP, hangup);
+
+ /* arm interval timer */
+ if ((afid = __pmAFregister(&interval, NULL, alarming)) < 0) {
+ __pmNotifyErr(LOG_ERR, "error registering asynchronous event handler");
+ exit(1);
+ }
+
+ for (;;) {
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(maxfd+1, &readyfds, NULL, NULL, NULL);
+
+ if (nready == 0)
+ continue;
+ else if (nready < 0) {
+ if (neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "select failure: %s", netstrerror());
+ exit(1);
+ }
+ continue;
+ }
+
+ __pmAFblock();
+ if (FD_ISSET(pmcdfd, &readyfds)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "processing pmcd request [fd=%d]", pmcdfd);
+#endif
+ if (__pmdaMainPDU(dispatch) < 0) {
+ __pmAFunblock();
+ exit(1); /* fatal if we lose pmcd */
+ }
+ }
+ /* handle request on control port */
+ if (FD_ISSET(ctlfd, &readyfds)) {
+ if ((cp = acceptClient(ctlfd)) != NULL) {
+ sts = __pmAccAddClient(cp->addr, &cp->denyOps);
+ if (sts == PM_ERR_PERMISSION)
+ sts = PMTRACE_ERR_PERMISSION;
+ else if (sts == PM_ERR_CONNLIMIT)
+ sts = PMTRACE_ERR_CONNLIMIT;
+ else if (sts >= 0)
+ sts = TRACE_PDU_VERSION;
+ __pmtracesendack(cp->fd, sts);
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *hostAddr = __pmSockAddrToString(cp->addr);
+ __pmNotifyErr(LOG_DEBUG, "client %s [fd=%d]: connect refused, denyOps=0x%x: %s",
+ hostAddr, cp->fd, cp->denyOps, pmtraceerrstr(sts));
+ free(hostAddr);
+ }
+#endif
+ deleteClient(cp);
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *hostAddr = __pmSockAddrToString(cp->addr);
+ __pmNotifyErr(LOG_DEBUG, "client %s [fd=%d]: new connection, denyOps=0x%x",
+ hostAddr, cp->fd, cp->denyOps);
+ free(hostAddr);
+ }
+#endif
+ ;
+ }
+ }
+ }
+ for (i = 0; i < nclients; i++) {
+ if (!clients[i].status.connected)
+ continue;
+ if (clients[i].denyOps & TR_OP_SEND) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *hostAddr = __pmSockAddrToString(clients[i].addr);
+ __pmNotifyErr(LOG_DEBUG, "client %s [fd=%d]: send denied, denyOps=0x%x",
+ hostAddr, clients[i].fd, clients[i].denyOps);
+ free(hostAddr);
+ }
+#endif
+ __pmtracesendack(clients[i].fd, PMTRACE_ERR_PERMISSION);
+ __pmAccDelClient(clients[i].addr);
+ deleteClient(&clients[i]);
+ }
+ else if (FD_ISSET(clients[i].fd, &readyfds)) {
+ protocol = 1; /* default to synchronous */
+ do {
+ if ((pdutype = readData(clients[i].fd, &protocol)) < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *hostAddr = __pmSockAddrToString(clients[i].addr);
+ __pmNotifyErr(LOG_DEBUG, "client %s [fd=%d]: close connection",
+ hostAddr, clients[i].fd);
+ free(hostAddr);
+ }
+#endif
+ __pmAccDelClient(clients[i].addr);
+ deleteClient(&clients[i]);
+ }
+ else {
+ clients[i].status.protocol = protocol;
+ if (clients[i].status.connected) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *hostAddr = __pmSockAddrToString(clients[i].addr);
+ __pmNotifyErr(LOG_DEBUG, "client %s [fd=%d]: %s ACK (type=%d)",
+ hostAddr, clients[i].fd,
+ protocol ? "sending" : "no", pdutype);
+ free(hostAddr);
+ }
+#endif
+ if (protocol == 1) {
+ sts = __pmtracesendack(clients[i].fd, pdutype);
+ if (sts < 0) {
+ char *hostAddr = __pmSockAddrToString(clients[i].addr);
+ __pmNotifyErr(LOG_ERR, "client %s [fd=%d]: ACK send failed (type=%d): %s",
+ hostAddr, clients[i].fd,
+ pdutype, pmtraceerrstr(sts));
+ free(hostAddr);
+ }
+ }
+ }
+ }
+ } while (__pmtracemoreinput(clients[i].fd));
+ }
+ }
+ __pmAFunblock();
+ }
+}
+
+
+void
+alarming(int sig, void *ptr)
+{
+ timerUpdate();
+}
+
+static void
+hangup(int sig)
+{
+ showClients();
+ signal(SIGHUP, hangup);
+}
+
+/*
+ * Create socket for incoming connections and bind to it an address for
+ * clients to use. Only returns if it succeeds (exits on failure).
+ */
+static int
+getcport(void)
+{
+ int fd;
+ int i=1, one=1, sts;
+ struct sockaddr_in myAddr;
+ struct linger noLinger = {1, 0};
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ __pmNotifyErr(LOG_ERR, "getcport: socket: %s", netstrerror());
+ exit(1);
+ }
+ /* avoid 200 ms delay */
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &i,
+ (__pmSockLen)sizeof(i)) < 0) {
+ __pmNotifyErr(LOG_ERR, "getcport: setsockopt(nodelay): %s",
+ netstrerror());
+ exit(1);
+ }
+ /* don't linger on close */
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &noLinger,
+ (__pmSockLen)sizeof(noLinger)) < 0) {
+ __pmNotifyErr(LOG_ERR, "getcport: setsockopt(nolinger): %s",
+ netstrerror());
+ exit(1);
+ }
+#ifndef IS_MINGW
+ /* ignore dead client connections */
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_ERR, "getcport: setsockopt(reuseaddr): %s",
+ netstrerror());
+ exit(1);
+ }
+#else
+ /* see MSDN tech note: "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" */
+ if (setsockopt(sfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *) &one,
+ (__pmSockLen)sizeof(one)) < 0) {
+ __pmNotifyErr(LOG_ERR, "getcport: setsockopt(excladdruse): %s",
+ netstrerror());
+ exit(1);
+ }
+#endif
+
+ if (ctlport == -1) {
+ /*
+ * check for port info in the environment
+ */
+ char *env_str;
+ if ((env_str = getenv(TRACE_ENV_PORT)) != NULL) {
+ char *end_ptr;
+
+ ctlport = (int)strtol(env_str, &end_ptr, 0);
+ if (*end_ptr != '\0' || ctlport < 0) {
+ __pmNotifyErr(LOG_WARNING, "env port is bogus (%s)", env_str);
+ ctlport = TRACE_PORT;
+ }
+ }
+ else
+ ctlport = TRACE_PORT;
+ }
+
+ /* TODO: IPv6 */
+ memset(&myAddr, 0, sizeof(myAddr));
+ myAddr.sin_family = AF_INET;
+ myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
+ myAddr.sin_port = htons(ctlport);
+
+ sts = bind(fd, (const struct sockaddr *)&myAddr, sizeof(myAddr));
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "bind(%d): %s", ctlport, netstrerror());
+ exit(1);
+ }
+ sts = listen(fd, 5); /* Max. of 5 pending connection requests */
+ if (sts == -1) {
+ __pmNotifyErr(LOG_ERR, "listen: %s", netstrerror());
+ exit(1);
+ }
+
+ return fd;
+}
diff --git a/src/pmdas/trace/src/comms.h b/src/pmdas/trace/src/comms.h
new file mode 100644
index 0000000..b564f4d
--- /dev/null
+++ b/src/pmdas/trace/src/comms.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef COMMS_H
+#define COMMS_H
+
+#define TR_OP_NONE 0x0
+#define TR_OP_SEND 0x1
+#define TR_OP_ALL 0x1
+
+#endif /* COMMS_H */
diff --git a/src/pmdas/trace/src/data.c b/src/pmdas/trace/src/data.c
new file mode 100644
index 0000000..2db8fb4
--- /dev/null
+++ b/src/pmdas/trace/src/data.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "data.h"
+
+int
+instcmp(void *a, void *b)
+{
+ instdata_t *aa = (instdata_t *)a;
+ instdata_t *bb = (instdata_t *)b;
+
+ if (aa == NULL || bb == NULL)
+ return 0;
+ if (aa->type != bb->type)
+ return 0;
+ return !strcmp(aa->tag, bb->tag);
+}
+
+void
+instdel(void *a)
+{
+ instdata_t *k = (instdata_t *)a;
+
+ if (k != NULL) {
+ if (k->tag != NULL)
+ free(k->tag);
+ free(k);
+ }
+}
+
+void
+instprint(__pmHashTable *t, void *e)
+{
+ instdata_t *i = (instdata_t *)e;
+
+ __pmNotifyErr(LOG_DEBUG, "Instance history table entry\n"
+ "Name: '%s'\n type: %d\n inst: %d\n",
+ i->tag, i->type, i->instid);
+}
+
+
+int
+datacmp(void *a, void *b)
+{
+ hashdata_t *aa = (hashdata_t *)a;
+ hashdata_t *bb = (hashdata_t *)b;
+
+ if (aa == NULL || bb == NULL)
+ return 0;
+ if (aa->tracetype != bb->tracetype)
+ return 0;
+ return !strcmp(aa->tag, bb->tag);
+}
+
+void
+datadel(void *a)
+{
+ hashdata_t *k = (hashdata_t *)a;
+
+ if (k != NULL) {
+ if (k->tag != NULL)
+ free(k->tag);
+ free(k);
+ }
+}
+
+void
+dataprint(__pmHashTable *t, void *e)
+{
+ hashdata_t *h = (hashdata_t *)e;
+
+ __pmNotifyErr(LOG_DEBUG, "PMDA hash table entry\n"
+ "Name: '%s'\n filedes: %d\n"
+ " type: %d\n length: %d\n"
+ " padding: %d\n count: %d\n"
+ " txmin: %f\n txmax: %f\n"
+ " txsum: %f\nSize: %d\n"
+ "-----------\n",
+ h->tag, h->fd, h->tracetype, h->taglength, h->padding,
+ (int)h->txcount, h->txmin, h->txmax, h->txsum, (int)sizeof(*h));
+}
+
+
+#ifdef PCP_DEBUG
+void
+debuglibrary(int flag)
+{
+ extern int __pmstate;
+ int state;
+
+ state = pmtracestate(0);
+ if (flag & DBG_TRACE_APPL0)
+ state |= PMTRACE_STATE_COMMS;
+ if (flag & DBG_TRACE_PDU)
+ state |= PMTRACE_STATE_PDU;
+ if (flag & DBG_TRACE_PDUBUF)
+ state |= PMTRACE_STATE_PDUBUF;
+ if (flag == 0)
+ __pmstate = 0;
+ else
+ pmtracestate(state);
+}
+#endif
+
+
diff --git a/src/pmdas/trace/src/data.h b/src/pmdas/trace/src/data.h
new file mode 100644
index 0000000..e4c0352
--- /dev/null
+++ b/src/pmdas/trace/src/data.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef TRACE_DATA_H
+#define TRACE_DATA_H
+
+#include "hash.h"
+#include "trace.h"
+#include "trace_dev.h"
+
+typedef struct {
+ char *tag;
+ unsigned int type;
+ unsigned int instid;
+} instdata_t;
+
+typedef struct __pmHashTab hashtable_t;
+
+int instcmp(void *a, void *b);
+void instdel(void *a);
+void instprint(hashtable_t *t, void *e);
+
+typedef struct {
+ char *tag;
+ unsigned int id;
+ int fd;
+ unsigned int tracetype : 8;
+ unsigned int taglength : 8;
+ unsigned int padding : 16;
+ __uint64_t realcount; /* real total seen by the PMDA */
+ double realtime; /* total time for transactions */
+ __int32_t txcount; /* count this interval or -1 */
+ double txmin; /* minimum value this interval */
+ double txmax; /* maximum value this interval */
+ double txsum; /* summed across the interval */
+} hashdata_t;
+
+int datacmp(void *a, void *b);
+void datadel(void *a);
+void dataprint(hashtable_t *t, void *e);
+
+typedef struct {
+ unsigned int numstats : 8; /* number of entries in this table */
+ unsigned int working : 1; /* this the current working table? */
+ hashtable_t *stats;
+} statlist_t;
+
+typedef struct {
+ statlist_t *ring; /* points to all statistics */
+ unsigned int level; /* controls reporting level */
+} ringbuf_t;
+
+#ifdef PCP_DEBUG
+void debuglibrary(int);
+#endif
+
+#endif /* TRACE_DATA_H */
diff --git a/src/pmdas/trace/src/pmda.c b/src/pmdas/trace/src/pmda.c
new file mode 100644
index 0000000..36560aa
--- /dev/null
+++ b/src/pmdas/trace/src/pmda.c
@@ -0,0 +1,228 @@
+/*
+ * Trace PMDA - process level transaction monitoring for libpcp_trace processes
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "trace.h"
+#include "trace_dev.h"
+#include "comms.h"
+
+#define DEFAULT_TIMESPAN 60 /* one minute */
+#define DEFAULT_BUFSIZE 5 /* twelve second update */
+
+struct timeval timespan = { DEFAULT_TIMESPAN, 0 };
+struct timeval interval;
+unsigned int rbufsize = DEFAULT_BUFSIZE;
+int ctlport = -1;
+char *ctlsock;
+
+static char mypath[MAXPATHLEN];
+static char *username;
+
+extern void traceInit(pmdaInterface *dispatch);
+extern void traceMain(pmdaInterface *dispatch);
+extern int updateObserveValue(const char *);
+extern int updateCounterValue(const char *);
+extern void debuglibrary(int);
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"Usage: %s [options]\n\
+\n\
+Options:\n\
+ -d domain use domain (numeric) for metrics domain of PMDA\n\
+ -l logfile write log into logfile rather than using default file\n\
+ -A access host based access control\n\
+ -I port expect programs to connect on given inet port (number/name)\n\
+ -M username user account to run under (default \"pcp\")\n\
+ -N buckets number of historical data buffers maintained\n\
+ -T period time over which samples are considered (default 60 seconds)\n\
+ -U units export observation values using the given units\n\
+ -V units export counter values using the given units\n",
+ pmProgname);
+ exit(1);
+}
+
+static char *
+squash(char *str, int *offset)
+{
+ char *hspec = NULL;
+ char *p = str;
+ int i = 0;
+
+ hspec = strdup(str); /* make sure we have space */
+ *offset = 0;
+ while (isspace((int)*p)) { p++; (*offset)++; }
+ while (p && *p != ':' && *p != '\0') {
+ hspec[i++] = *p;
+ p++;
+ }
+ hspec[i] = '\0';
+ *offset += i;
+ return hspec;
+}
+
+static int
+parseAuth(char *spec)
+{
+ static int first = 1;
+ int offset, maxconn, specops = TR_OP_ALL, denyops;
+ char *p, *endnum;
+
+ if (first) {
+ if (__pmAccAddOp(TR_OP_SEND) < 0) {
+ __pmNotifyErr(LOG_ERR, "failed to add send auth operation");
+ return -1;
+ }
+ first = 0;
+ }
+
+ if (strncasecmp(spec, "disallow:", 9) == 0) {
+ p = squash(&spec[9], &offset);
+ if (p == NULL || p[0] == '\0') {
+ fprintf(stderr, "%s: invalid disallow (%s)\n", pmProgname, spec);
+ if (p)
+ free(p);
+ return -1;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "deny: host '%s'\n", p);
+#endif
+ denyops = TR_OP_SEND;
+ if (__pmAccAddHost(p, specops, denyops, 0) < 0)
+ __pmNotifyErr(LOG_ERR, "failed to add authorisation (%s)", p);
+ free(p);
+ }
+ else if (strncasecmp(spec, "allow:", 6) == 0) {
+ p = squash(&spec[6], &offset);
+ if (p == NULL || p[0] == '\0') {
+ fprintf(stderr, "%s: invalid allow (%s)\n", pmProgname, spec);
+ if (p)
+ free(p);
+ return -1;
+ }
+ offset += 7;
+ maxconn = (int)strtol(&spec[offset], &endnum, 10);
+ if (*endnum != '\0' || maxconn < 0) {
+ fprintf(stderr, "%s: bogus max connection in '%s'\n", pmProgname,
+ &spec[offset]);
+ free(p);
+ return -1;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "allow: host '%s', maxconn=%d\n", p, maxconn);
+#endif
+ denyops = TR_OP_NONE;
+ if (__pmAccAddHost(p, specops, denyops, maxconn) < 0)
+ __pmNotifyErr(LOG_ERR, "failed to add authorisation (%s)", p);
+ free(p);
+ }
+ else {
+ fprintf(stderr, "%s: access spec is invalid (%s)\n", pmProgname, spec);
+ return -1;
+ }
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ pmdaInterface dispatch;
+ char *endnum;
+ int err = 0;
+ int sep = __pmPathSeparator();
+ int c = 0;
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "trace" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, TRACE,
+ "trace.log", mypath);
+
+ /* need - port, as well as time interval and time span for averaging */
+ while ((c = pmdaGetOpt(argc, argv, "A:D:d:I:l:T:M:N:U:V:?",
+ &dispatch, &err)) != EOF) {
+ switch(c) {
+ case 'A':
+ if (parseAuth(optarg) < 0)
+ err++;
+ /* add optarg to access control list */
+ break;
+ case 'I':
+ ctlport = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0' || ctlport < 0)
+ ctlsock = optarg;
+ break;
+ case 'M':
+ username = optarg;
+ break;
+ case 'N':
+ rbufsize = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0' || rbufsize < 1) {
+ fprintf(stderr, "%s: -N requires a positive number.\n", pmProgname);
+ err++;
+ }
+ break;
+ case 'T':
+ if (pmParseInterval(optarg, &timespan, &endnum) < 0) {
+ fprintf(stderr, "%s: -T requires a time interval: %s\n",
+ pmProgname, endnum);
+ free(endnum);
+ err++;
+ }
+ break;
+ case 'U':
+ if (updateObserveValue(optarg) < 0)
+ err++;
+ break;
+ case 'V':
+ if (updateCounterValue(optarg) < 0)
+ err++;
+ break;
+ default:
+ err++;
+ }
+ }
+
+ if (err)
+ usage();
+
+ interval.tv_sec = (int)(timespan.tv_sec / rbufsize);
+ interval.tv_usec = (long)((timespan.tv_sec % rbufsize) * 1000000);
+ rbufsize++; /* reserve space for the `working' buffer */
+
+#ifdef PCP_DEBUG
+ debuglibrary(pmDebug);
+#endif
+
+ pmdaOpenLog(&dispatch);
+ __pmSetProcessIdentity(username);
+ traceInit(&dispatch);
+ pmdaConnect(&dispatch);
+ traceMain(&dispatch);
+
+ exit(0);
+}
diff --git a/src/pmdas/trace/src/trace.c b/src/pmdas/trace/src/trace.c
new file mode 100644
index 0000000..f57c4bb
--- /dev/null
+++ b/src/pmdas/trace/src/trace.c
@@ -0,0 +1,1151 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "domain.h"
+#include "data.h"
+#include "trace_dev.h"
+
+static pmdaIndom indomtab[] = { /* list of trace metric instance domains */
+#define TRANSACT_INDOM 0
+ { TRANSACT_INDOM, 0, NULL }, /* dynamically updated */
+#define POINT_INDOM 1
+ { POINT_INDOM, 0, NULL }, /* dynamically updated */
+#define OBSERVE_INDOM 2
+ { OBSERVE_INDOM, 0, NULL }, /* dynamically updated */
+#define COUNTER_INDOM 3
+ { COUNTER_INDOM, 0, NULL }, /* dynamically updated */
+};
+
+static int transacts; /* next instance# to allocate */
+static int points; /* next instance# to allocate */
+static int counters; /* next instance# to allocate */
+static int observes; /* next instance# to allocate */
+static int tsortflag; /* need sort on next request? */
+static int psortflag; /* need sort on next request? */
+static int csortflag; /* need sort on next request? */
+static int osortflag; /* need sort on next request? */
+
+/* all metrics supported in this PMDA - one table entry for each */
+static pmdaMetric metrictab[] = {
+/* transact.count */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U64, TRANSACT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1, 0,0,PM_COUNT_ONE)} },
+/* transact.rate */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_FLOAT, TRANSACT_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,-1,1, 0,PM_TIME_SEC,PM_COUNT_ONE)} },
+/* transact.ave_time */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_FLOAT, TRANSACT_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,-1, 0,PM_TIME_SEC,PM_COUNT_ONE)} },
+/* transact.min_time */
+ { NULL,
+ { PMDA_PMID(0,3), PM_TYPE_FLOAT, TRANSACT_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0, 0,PM_TIME_SEC,0)} },
+/* transact.max_time */
+ { NULL,
+ { PMDA_PMID(0,4), PM_TYPE_FLOAT, TRANSACT_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0, 0,PM_TIME_SEC,0)} },
+/* transact.total_time */
+ { NULL,
+ { PMDA_PMID(0,5), PM_TYPE_DOUBLE, TRANSACT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,1,0, 0,PM_TIME_SEC,0)} },
+/* point.count */
+ { NULL,
+ { PMDA_PMID(0,6), PM_TYPE_U64, POINT_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1, 0,0,PM_COUNT_ONE)} },
+/* point.rate */
+ { NULL,
+ { PMDA_PMID(0,7), PM_TYPE_FLOAT, POINT_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,-1,1, 0,PM_TIME_SEC,PM_COUNT_ONE)} },
+/* observe.count */
+ { NULL,
+ { PMDA_PMID(0,8), PM_TYPE_U64, OBSERVE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1, 0,0,PM_COUNT_ONE)} },
+/* observe.rate */
+ { NULL,
+ { PMDA_PMID(0,9), PM_TYPE_FLOAT, OBSERVE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,-1,1, 0,PM_TIME_SEC,PM_COUNT_ONE)} },
+/* observe.value */
+ { NULL,
+ { PMDA_PMID(0,10), PM_TYPE_DOUBLE, OBSERVE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0, 0,0,0)} }, /* this may be modified at startup */
+/* control.timespan */
+ { NULL,
+ { PMDA_PMID(0,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0, 0,PM_TIME_SEC,0)} },
+/* control.interval */
+ { NULL,
+ { PMDA_PMID(0,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,1,0, 0,PM_TIME_SEC,0)} },
+/* control.buckets */
+ { NULL,
+ { PMDA_PMID(0,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0, 0,0,0)} },
+/* control.port */
+ { NULL,
+ { PMDA_PMID(0,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0, 0,0,0)} },
+/* control.reset */
+ { NULL,
+ { PMDA_PMID(0,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0, 0,0,0)} },
+/* control.debug */
+ { NULL,
+ { PMDA_PMID(0,16), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0, 0,0,0) }, },
+/* counter.count */
+ { NULL,
+ { PMDA_PMID(0,17), PM_TYPE_U64, COUNTER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1, 0,0,PM_COUNT_ONE) }, },
+/* counter.rate */
+ { NULL,
+ { PMDA_PMID(0,18), PM_TYPE_FLOAT, COUNTER_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,-1,1, 0,PM_TIME_SEC,PM_COUNT_ONE) }, },
+/* counter.value */
+ { NULL,
+ { PMDA_PMID(0,19), PM_TYPE_DOUBLE, COUNTER_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,0, 0,0,0) }, }, /* this may be modified at startup */
+};
+
+extern void __pmdaStartInst(pmInDom indom, pmdaExt *pmda);
+
+extern int ctlport;
+extern unsigned int rbufsize;
+extern struct timeval timespan;
+extern struct timeval interval;
+
+static ringbuf_t ringbuf; /* *THE* ring buffer of trace data */
+static hashtable_t summary; /* globals + recent ringbuf summary */
+static hashtable_t history; /* holds every instance seen so far */
+static unsigned int rpos; /* `working' buffer, within ringbuf */
+static unsigned int dosummary = 1; /* summary refreshed this interval? */
+static unsigned int tindomsize = 0; /* updated local to fetch only */
+static unsigned int pindomsize = 0; /* updated local to fetch only */
+static unsigned int oindomsize = 0; /* updated local to fetch only */
+static unsigned int cindomsize = 0; /* updated local to fetch only */
+ /* note: {t,p,o,c}indomsize are only valid when dosummary equals zero */
+
+
+/* allow configuration of trace.observe.value/trace.counter.value units */
+static int
+updateValueUnits(const char *str, int offset)
+{
+ int units[6], i, sts = 0;
+ char *s, *sptr, *endp;
+
+ if ((sptr = strdup(str)) == NULL)
+ return -oserror();
+ s = sptr;
+
+ for (i = 0; i < 6; i++) {
+ if ((s = strtok((i==0 ? sptr : NULL), ",")) == NULL) {
+ fprintf(stderr, "%s: token parse error in string \"%s\"\n",
+ pmProgname, str);
+ sts = -1;
+ goto leaving;
+ }
+ units[i] = (int)strtol(s, &endp, 10);
+ if (*endp) {
+ fprintf(stderr, "%s: integer parse error for substring \"%s\"\n",
+ pmProgname, s);
+ sts = -1;
+ goto leaving;
+ }
+ }
+
+ /* update table entry for this value metric */
+ metrictab[offset].m_desc.units.dimSpace = units[0];
+ metrictab[offset].m_desc.units.dimTime = units[1];
+ metrictab[offset].m_desc.units.dimCount = units[2];
+ metrictab[offset].m_desc.units.scaleSpace = units[3];
+ metrictab[offset].m_desc.units.scaleTime = units[4];
+ metrictab[offset].m_desc.units.scaleCount = units[5];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "%s: value metric units updated using \"%s\"\n"
+ "dimSpace=%d, dimTime=%d, dimCount=%d, scaleSpace=%d, "
+ "scaleTime=%d, scaleCount=%d\n", pmProgname, str,
+ units[0], units[1], units[2], units[3], units[4], units[5]);
+ }
+#endif
+
+leaving:
+ if (sptr != NULL)
+ free(sptr);
+ return sts;
+}
+
+int updateObserveValue(const char *str) { return updateValueUnits(str, 10); }
+int updateCounterValue(const char *str) { return updateValueUnits(str, 19); }
+
+
+static int
+compareInstance(const void *a, const void *b)
+{
+ pmdaInstid *aa = (pmdaInstid *)a;
+ pmdaInstid *bb = (pmdaInstid *)b;
+ return aa->i_inst - bb->i_inst;
+}
+
+/*
+ * sort reset instance domains to be friendly to pmclients
+ * (only PMDA knows when optimal time to sort these infrequently-changing sets)
+ */
+static void
+indomSortCheck(void)
+{
+ if (tsortflag) {
+ qsort(indomtab[TRANSACT_INDOM].it_set,
+ indomtab[TRANSACT_INDOM].it_numinst,
+ sizeof(pmdaInstid), compareInstance);
+ tsortflag = 0;
+ }
+ if (psortflag) {
+ qsort(indomtab[POINT_INDOM].it_set,
+ indomtab[POINT_INDOM].it_numinst,
+ sizeof(pmdaInstid), compareInstance);
+ psortflag = 0;
+ }
+ if (osortflag) {
+ qsort(indomtab[OBSERVE_INDOM].it_set,
+ indomtab[OBSERVE_INDOM].it_numinst,
+ sizeof(pmdaInstid), compareInstance);
+ osortflag = 0;
+ }
+ if (csortflag) {
+ qsort(indomtab[COUNTER_INDOM].it_set,
+ indomtab[COUNTER_INDOM].it_numinst,
+ sizeof(pmdaInstid), compareInstance);
+ csortflag = 0;
+ }
+}
+
+/*
+ * wrapper for pmdaInstance which we need to ensure is called with the
+ * _sorted_ contents of the instance domain.
+ */
+static int
+traceInstance(pmInDom indom, int foo, char *bar, __pmInResult **iresp, pmdaExt
+ *pmda)
+{
+ indomSortCheck();
+ return pmdaInstance(indom, foo, bar, iresp, pmda);
+}
+
+/*
+ * `summary' table deletion may add to the `history' table.
+ */
+void
+summarydel(void *a)
+{
+ hashdata_t *k = (hashdata_t *)a;
+ instdata_t check;
+
+ check.tag = k->tag;
+ check.type = k->tracetype;
+ check.instid = k->id;
+ if (__pmhashlookup(&history, check.tag, &check) == NULL) {
+ if (__pmhashinsert(&history, check.tag, &check) < 0)
+ __pmNotifyErr(LOG_ERR, "history table insert failure - '%s' "
+ "instance will not maintain its instance number.", check.tag);
+ }
+ if (k != NULL)
+ free(k); /* don't free k->tag - its in the history table */
+}
+
+/*
+ * Processes data from pcp_trace-linked client programs.
+ *
+ * Return negative only on fd-related errors, as that connection will
+ * later be closed. Other errors - report in log file but continue.
+ */
+int
+readData(int clientfd, int *protocol)
+{
+ __pmTracePDU *result;
+ double data;
+ hashdata_t newhash;
+ hashdata_t *hptr;
+ hashdata_t hash;
+ char *tag;
+ int type, taglen, sts;
+ int freeflag=0;
+
+ if ((sts = __pmtracegetPDU(clientfd, TRACE_TIMEOUT_NEVER, &result)) < 0) {
+ __pmNotifyErr(LOG_ERR, "bogus PDU read - %s", pmtraceerrstr(sts));
+ return -1;
+ }
+ else if (sts == TRACE_PDU_DATA) {
+ if ((sts = __pmtracedecodedata(result, &tag, &taglen,
+ &type, protocol, &data)) < 0)
+ return -1;
+ if (type < TRACE_FIRST_TYPE || type > TRACE_LAST_TYPE) {
+ __pmNotifyErr(LOG_ERR, "unknown trace type for '%s' (%d)", tag, type);
+ free(tag);
+ return -1;
+ }
+ newhash.tag = tag;
+ newhash.taglength = taglen;
+ newhash.tracetype = type;
+ }
+ else if (sts == 0) { /* client has exited - cleanup in mainloop */
+ return -1;
+ }
+ else { /* unknown PDU type - bail & later kill connection */
+ __pmNotifyErr(LOG_ERR, "unknown PDU - expected data PDU"
+ " (not type #%d)", sts);
+ return -1;
+ }
+
+ /*
+ * First, update the global summary table with this new data
+ */
+ if ((hptr = __pmhashlookup(&summary, tag, &newhash)) == NULL) {
+ instdata_t check, *iptr;
+ int size, index, indom;
+
+ check.tag = newhash.tag;
+ check.type = newhash.tracetype;
+ if ((iptr = __pmhashlookup(&history, check.tag, &check)) != NULL) {
+ newhash.id = iptr->instid; /* reuse pre-reset instance ID */
+ if (iptr->type == TRACE_TYPE_TRANSACT) tsortflag++;
+ else if (iptr->type == TRACE_TYPE_POINT) psortflag++;
+ else if (iptr->type == TRACE_TYPE_COUNTER) csortflag++;
+ else /*(iptr->type == TRACE_TYPE_OBSERVE)*/ osortflag++;
+ }
+ else if (type == TRACE_TYPE_TRANSACT)
+ newhash.id = ++transacts;
+ else if (type == TRACE_TYPE_POINT)
+ newhash.id = ++points;
+ else if (type == TRACE_TYPE_COUNTER)
+ newhash.id = ++counters;
+ else /* TRACE_TYPE_OBSERVE */
+ newhash.id = ++observes;
+ newhash.txcount = -1; /* first time since reset or start */
+ newhash.padding = 0;
+ newhash.realcount = 1;
+ newhash.realtime = data;
+ newhash.fd = clientfd;
+ newhash.txmin = newhash.txmax = data;
+ newhash.txsum = data;
+ hptr = &newhash;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "'%s' is new to the summary table!",
+ hptr->tag);
+#endif
+ if (__pmhashinsert(&summary, hptr->tag, hptr) < 0) {
+ __pmNotifyErr(LOG_ERR, "summary table insert failure - '%s' "
+ "data ignored", hptr->tag);
+ free(hptr->tag);
+ return -1;
+ }
+ /* also update the instance domain */
+ if (hptr->tracetype == TRACE_TYPE_TRANSACT)
+ indom = TRANSACT_INDOM;
+ else if (hptr->tracetype == TRACE_TYPE_POINT)
+ indom = POINT_INDOM;
+ else if (hptr->tracetype == TRACE_TYPE_COUNTER)
+ indom = COUNTER_INDOM;
+ else /*(hptr->tracetype == TRACE_TYPE_OBSERVE)*/
+ indom = OBSERVE_INDOM;
+
+#ifdef DESPERATE
+ /* walk the indom table - if we find this new tag in it already, then
+ * something is badly busted.
+ */
+ for (sts = 0; sts < indomtab[indom].it_numinst; sts++) {
+ if (strcmp(indomtab[indom].it_set[sts].i_name, hptr->tag) == 0) {
+ fprintf(stderr, "'%s' (inst=%d, type=%d) entry in indomtab already!!!\n",
+ hptr->tag, indomtab[indom].it_set[sts].i_inst, hptr->tracetype);
+ abort();
+ }
+ }
+#endif
+
+ index = indomtab[indom].it_numinst;
+ size = (index+1)*(int)sizeof(pmdaInstid);
+ if ((indomtab[indom].it_set = (pmdaInstid *)
+ realloc(indomtab[indom].it_set, size)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "dropping instance '%s': %s", hptr->tag,
+ osstrerror());
+ free(hptr->tag);
+ return -1;
+ }
+ indomtab[indom].it_set[index].i_inst = hptr->id;
+ indomtab[indom].it_set[index].i_name = hptr->tag;
+ indomtab[indom].it_numinst++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Updated indom #%d:\n", indom);
+ for (index=0; index < indomtab[indom].it_numinst; index++)
+ fprintf(stderr, " Instance %d='%s'\n",
+ indomtab[indom].it_set[index].i_inst,
+ indomtab[indom].it_set[index].i_name);
+ }
+#endif
+ }
+ else { /* update an existing entry */
+ freeflag++; /* wont need tag afterwards, so mark for deletion */
+ if (hptr->taglength != newhash.taglength) {
+ __pmNotifyErr(LOG_ERR, "hash table update failure - '%s' "
+ "data ignored (bad tag length)", tag);
+ free(newhash.tag);
+ return -1;
+ }
+ else { /* update existing entries free running counter */
+ hptr->realcount++;
+ if (hptr->tracetype == TRACE_TYPE_TRANSACT)
+ hptr->realtime += data;
+ /* keep running total of time attributed to transactions */
+ else if (hptr->tracetype == TRACE_TYPE_COUNTER)
+ hptr->txsum = data;
+ /* counters are 'permanent' and immediately available */
+ else if (hptr->tracetype == TRACE_TYPE_OBSERVE)
+ hptr->txsum = data;
+ /* observations are 'permanent' and immediately available */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "'%s' real count updated (%d)",
+ hptr->tag, (int)hptr->realcount);
+#endif
+ }
+ }
+
+ /*
+ * Next, update the current ring buffer entry with new trace data
+ */
+ if ((hptr = __pmhashlookup(ringbuf.ring[rpos].stats, tag,
+ &newhash)) == NULL) {
+ hash.tag = strdup(tag);
+ hash.tracetype = type;
+ hash.id = 0; /* the ring buffer is never used to resolve indoms */
+ hash.padding = 0;
+ hash.realcount = 1;
+ hash.realtime = data;
+ hash.taglength = (unsigned int)taglen;
+ hash.fd = clientfd;
+ hash.txcount = 1;
+ hash.txmin = hash.txmax = data;
+ hash.txsum = data;
+ hptr = &hash;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "fresh interval data on fd=%d rpos=%d "
+ "('%s': len=%d type=%d value=%d)", clientfd, rpos,
+ hash.tag, taglen, type, (int)data);
+#endif
+ if (__pmhashinsert(ringbuf.ring[rpos].stats, hash.tag, hptr) < 0) {
+ __pmNotifyErr(LOG_ERR, "ring buffer insert failure - '%s' "
+ "data ignored", hash.tag);
+ free(hash.tag);
+ return -1;
+ }
+ }
+ else { /* update existing entry */
+ hptr->txcount++;
+ if (hptr->tracetype == TRACE_TYPE_TRANSACT) {
+ if (data < hptr->txmin)
+ hptr->txmin = data;
+ if (data > hptr->txmax)
+ hptr->txmax = data;
+ hptr->txsum += data;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "Updating data on fd=%d ('%s': type=%d "
+ "count=%d min=%f max=%f sum=%f)",
+ clientfd, hptr->tag, hptr->tracetype,
+ hptr->txcount, hptr->txmin, hptr->txmax, hptr->txsum);
+#endif
+ }
+ if (freeflag) {
+ free(newhash.tag);
+ newhash.tag = NULL;
+ }
+
+ return hptr->tracetype;
+}
+
+static void
+clearTable(hashtable_t *t, void *entry)
+{
+ hashdata_t *h = (hashdata_t *)entry;
+ h->txcount = -1; /* flag as out-of-date */
+}
+
+/*
+ * Goes off at set time interval. The old `tail' becomes the new `head'
+ * of the ring buffer & is marked as currently in-progess (and this data
+ * is not exported until the next timer event).
+ */
+void
+timerUpdate(void)
+{
+ /* summary table must be reset for next fetch */
+ if (dosummary == 0) {
+ __pmhashtraverse(&summary, clearTable);
+ dosummary = 1;
+ }
+
+ if (ringbuf.ring[rpos].working == 0) {
+ __pmNotifyErr(LOG_ERR, "buffered I/O error - ignoring timer event");
+ return;
+ }
+
+ ringbuf.ring[rpos].working = 0;
+ if (rpos == rbufsize-1)
+ rpos = 0; /* return to start of buffer */
+ else
+ rpos++;
+ __pmhashtrunc(ringbuf.ring[rpos].stats); /* new working set */
+ ringbuf.ring[rpos].working = 1;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "Alarm - working buffer is now %d/%d",
+ rpos+1, rbufsize);
+#endif
+}
+
+static void
+summariseDataAux(hashtable_t *t, void *entry)
+{
+ hashdata_t *hptr;
+ hashdata_t *base = (hashdata_t *)entry;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "summariseDataAux: looking up '%s'", base->tag);
+#endif
+ /* update summary hash table */
+ if ((hptr = __pmhashlookup(&summary, base->tag, base)) == NULL)
+ __pmNotifyErr(LOG_ERR, "summariseDataAux: entry in ring buffer, "
+ "but not summary ('%s')", base->tag);
+ else { /* update an existing summary */
+ if (hptr->tracetype == TRACE_TYPE_TRANSACT) {
+ if (hptr->txcount == -1) { /* reset coz flagged as out-of-date */
+ tindomsize++;
+ hptr->txcount = base->txcount;
+ hptr->txmin = base->txmin;
+ hptr->txmax = base->txmax;
+ hptr->txsum = base->txsum;
+ }
+ else {
+ hptr->txcount += base->txcount;
+ if (base->txmin < hptr->txmin)
+ hptr->txmin = base->txmin;
+ if (base->txmax > hptr->txmax)
+ hptr->txmax = base->txmax;
+ hptr->txsum += base->txsum;
+ }
+ }
+ else {
+ if (hptr->txcount == -1) { /* reset coz flagged as out-of-date */
+ if (hptr->tracetype == TRACE_TYPE_POINT)
+ pindomsize++;
+ else if (hptr->tracetype == TRACE_TYPE_COUNTER)
+ cindomsize++;
+ else if (hptr->tracetype == TRACE_TYPE_OBSERVE)
+ oindomsize++;
+ else {
+ __pmNotifyErr(LOG_ERR,
+ "bogus trace type - skipping '%s'", hptr->tag);
+ return;
+ }
+ hptr->txcount = base->txcount;
+ }
+ else {
+ hptr->txcount += base->txcount;
+ }
+ }
+ }
+}
+
+
+/*
+ * Create the summary hash table, as well as the instance list for
+ * this set of intervals.
+ */
+static void
+summariseData(void)
+{
+ int count;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "summariseData: resummarising");
+#endif
+ /* initialise counters */
+ tindomsize = pindomsize = oindomsize = 0;
+
+ /* create the new summary table */
+ for (count=0; count < rbufsize; count++) {
+ if (ringbuf.ring[count].working == 1)
+ continue;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "ring buffer table %d/%d has %d entries.\n",
+ count, rbufsize, ringbuf.ring[count].stats->entries);
+#endif
+ __pmhashtraverse(ringbuf.ring[count].stats, summariseDataAux);
+ }
+
+ /* summary now holds correct data for the rest of this interval */
+ dosummary = 0;
+}
+
+/*
+ * Try to keep trace fetching similar to libpcp_pmda pmdaFetch()
+ * we need a different way to get at the instances though
+ * so, we can still use __pmdaStartInst, but trace iterator
+ * is a bit different.
+ */
+static pmdaInstid *
+nextTraceInst(pmdaExt *pmda)
+{
+ static pmdaInstid in = { PM_INDOM_NULL, NULL };
+ pmdaInstid *iptr = NULL;
+
+ if (pmda->e_singular == 0) {
+ /* PM_INDOM_NULL ... just the one value */
+ iptr = &in;
+ pmda->e_singular = -1;
+ }
+ if (pmda->e_ordinal >= 0) {
+ int j;
+ for (j = pmda->e_ordinal; j < pmda->e_idp->it_numinst; j++) {
+ if (__pmInProfile(pmda->e_idp->it_indom, pmda->e_prof,
+ pmda->e_idp->it_set[j].i_inst)) {
+ iptr = &pmda->e_idp->it_set[j];
+ pmda->e_ordinal = j+1;
+ break;
+ }
+ }
+ }
+ return iptr;
+}
+
+static int
+auxFetch(int inst, __pmID_int *idp, char *tag, pmAtomValue *atom)
+{
+ if (inst != PM_IN_NULL && idp->cluster != 0)
+ return PM_ERR_INST;
+
+ /* transaction, point, counter and observe trace values and control data */
+ if (idp->cluster == 0) {
+ hashdata_t hash;
+ hashdata_t *hptr;
+
+ hash.tag = tag;
+
+ switch (idp->item) {
+ case 0: /* trace.transact.count */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ atom->ull = hptr->realcount;
+ break;
+ case 1: /* trace.transact.rate */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ atom->f = (float)((double)hptr->txcount/(double)timespan.tv_sec);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "rate=%f=%f/%f('%s')\n", (float)atom->f,
+ (double)hptr->txcount, (double)timespan.tv_sec, tag);
+#endif
+ break;
+ case 2: /* trace.transact.ave_time */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ else if (hptr->txcount == 0)
+ atom->f = 0;
+ else {
+ atom->f = (float)((double)hptr->txsum/(double)hptr->txcount);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "ave_time=%f=%f/%f('%s')\n", (float)atom->f,
+ (double)hptr->txsum, (double)hptr->txcount, tag);
+#endif
+ }
+ break;
+ case 3: /* trace.transact.min_time */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ atom->f = (float)hptr->txmin;
+ break;
+ case 4: /* trace.transact.max_time */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ atom->f = (float)hptr->txmax;
+ break;
+ case 5: /* trace.transact.total_time */
+ hash.tracetype = TRACE_TYPE_TRANSACT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ atom->d = hptr->realtime;
+ break;
+ case 6: /* trace.point.count */
+ hash.tracetype = TRACE_TYPE_POINT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ atom->ull = hptr->realcount;
+ break;
+ case 7: /* trace.point.rate */
+ hash.tracetype = TRACE_TYPE_POINT;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ atom->f = (float)((double)hptr->txcount/(double)timespan.tv_sec);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "rate=%f=%f/%f('%s')\n", (float)atom->f,
+ (double)hptr->txcount, (double)timespan.tv_sec, tag);
+#endif
+ break;
+ case 8: /* trace.observe.count */
+ case 17: /* trace.counter.count */
+ hash.tracetype = (idp->item == 8)?
+ TRACE_TYPE_OBSERVE : TRACE_TYPE_COUNTER;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ atom->ull = hptr->realcount;
+ break;
+ case 9: /* trace.observe.rate */
+ case 18: /* trace.counter.rate */
+ hash.tracetype = (idp->item == 9)?
+ TRACE_TYPE_OBSERVE : TRACE_TYPE_COUNTER;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ if (hptr->txcount < 0) /* not in current time period */
+ return 0;
+ atom->f = (float)((double)hptr->txcount/(double)timespan.tv_sec);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ __pmNotifyErr(LOG_DEBUG, "rate=%f=%f/%f('%s')\n", (float)atom->f,
+ (double)hptr->txcount, (double)timespan.tv_sec, tag);
+#endif
+ break;
+ case 10: /* trace.observe.value */
+ case 19: /* trace.counter.value */
+ hash.tracetype = (idp->item == 10)?
+ TRACE_TYPE_OBSERVE : TRACE_TYPE_COUNTER;
+ if ((hptr = __pmhashlookup(&summary, hash.tag, &hash)) == NULL)
+ return PM_ERR_INST;
+ atom->d = hptr->txsum;
+ break;
+ case 11: /* trace.control.timespan */
+ atom->ul = timespan.tv_sec;
+ break;
+ case 12: /* trace.control.interval */
+ atom->ul = interval.tv_sec;
+ break;
+ case 13: /* trace.control.buckets */
+ atom->ul = rbufsize-1;
+ break;
+ case 14: /* trace.control.port */
+ atom->ul = ctlport;
+ break;
+ case 15: /* trace.control.reset */
+ atom->ul = 1;
+ break;
+ case 16: /* trace.control.debug */
+ atom->ul = pmDebug;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+ else
+ return PM_ERR_PMID;
+
+ return 1;
+}
+
+static int
+getIndomSize(__pmID_int *pmidp)
+{
+ int size;
+
+ if (pmidp->cluster != 0)
+ return 1;
+
+ switch (pmidp->item) {
+ case 0: /* uses summary's real counters (transact) */
+ case 5:
+ size = indomtab[TRANSACT_INDOM].it_numinst;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4: /* susceptible to ring buffer updates */
+ size = tindomsize;
+ break;
+ case 6: /* uses summary's real counters (point) */
+ size = indomtab[POINT_INDOM].it_numinst;
+ break;
+ case 7: /* susceptible to ring buffer updates */
+ size = pindomsize;
+ break;
+ case 8:
+ case 10: /* uses summary's real counters & data (obs) */
+ size = indomtab[OBSERVE_INDOM].it_numinst;
+ break;
+ case 9: /* susceptible to ring buffer updates */
+ size = oindomsize;
+ break;
+ case 17:
+ case 19: /* uses summary's real counters & data (ctr) */
+ size = indomtab[COUNTER_INDOM].it_numinst;
+ break;
+ case 18: /* susceptible to ring buffer updates */
+ size = cindomsize;
+ break;
+ default:
+ size = 0;
+ }
+ return size;
+}
+
+
+static int
+traceFetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ static int maxnpmids = 0;
+ static pmResult *res = NULL;
+ pmValueSet *vset;
+ pmDesc *dp;
+ __pmID_int *pmidp;
+ pmdaMetric *metap;
+ pmAtomValue atom;
+ pmdaInstid *ins;
+ pmdaInstid noinst = { PM_IN_NULL, NULL };
+ int numval;
+ int sts, i, j, need;
+
+ indomSortCheck();
+ pmda->e_idp = indomtab;
+
+ if (numpmid > maxnpmids) {
+ if (res != NULL)
+ free(res);
+ /* (numpmid - 1) because there's room for one valueSet in a pmResult */
+ need = (int)sizeof(pmResult) + (numpmid-1)*(int)sizeof(pmValueSet *);
+ if ((res = (pmResult *) malloc(need)) == NULL) {
+ return -oserror();
+ }
+ maxnpmids = numpmid;
+ }
+
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+ for (i = 0; i < numpmid; i++) {
+ dp = NULL;
+ metap = NULL;
+ pmidp = (__pmID_int *)&pmidlist[i];
+ if (pmda->e_direct) {
+ if (pmidp->item < pmda->e_nmetrics &&
+ pmidlist[i] == pmda->e_metrics[pmidp->item].m_desc.pmid) {
+ metap = &pmda->e_metrics[pmidp->item];
+ dp = &(metap->m_desc);
+ }
+ }
+ else { /* search for it */
+ for (j = 0; j < pmda->e_nmetrics; j++) {
+ if (pmidlist[i] == pmda->e_metrics[j].m_desc.pmid) {
+ metap = &pmda->e_metrics[j];
+ dp = &(metap->m_desc);
+ break;
+ }
+ }
+ }
+ if (dp == NULL) {
+ __pmNotifyErr(LOG_ERR, "traceFetch: Requested metric %s is not "
+ "defined", pmIDStr(pmidlist[i]));
+ numval = PM_ERR_PMID;
+ }
+ else if (dp->indom != PM_INDOM_NULL) {
+ /*
+ * Only summarise when you have to, so check if data has
+ * already been summarised within this interval.
+ */
+ if (dosummary == 1)
+ summariseData();
+ numval = getIndomSize(pmidp);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "instance domain for %s numval=%d",
+ pmIDStr(dp->pmid), numval);
+#endif
+ }
+ else
+ numval = 1;
+
+ /* Must use individual malloc()s because of pmFreeResult() */
+ if (numval >= 1)
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet)+
+ (numval - 1)*sizeof(pmValue));
+ else
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet)-
+ sizeof(pmValue));
+ if (vset == NULL) {
+ if ((res->numpmid = i) > 0)
+ __pmFreeResultValues(res);
+ return -oserror();
+ }
+
+ vset->pmid = pmidlist[i];
+ vset->numval = numval;
+ vset->valfmt = PM_VAL_INSITU;
+ if (vset->numval <= 0)
+ continue;
+
+ if (dp->indom == PM_INDOM_NULL)
+ ins = &noinst;
+ else {
+ __pmdaStartInst(dp->indom, pmda);
+ ins = nextTraceInst(pmda);
+ }
+ j = 0;
+ do {
+ if (ins == NULL) {
+ __pmNotifyErr(LOG_ERR, "bogus instance ignored (pmid=%s)",
+ pmIDStr(dp->pmid));
+ if ((res->numpmid = i) > 0)
+ __pmFreeResultValues(res);
+ return PM_ERR_INST;
+ }
+ if (j == numval) {
+ numval++;
+ res->vset[i] = vset = (pmValueSet *)realloc(vset,
+ sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue));
+ if (vset == NULL) {
+ if ((res->numpmid = i) > 0)
+ __pmFreeResultValues(res);
+ return -oserror();
+ }
+ }
+ vset->vlist[j].inst = ins->i_inst;
+ if ((sts = auxFetch(ins->i_inst, pmidp, ins->i_name, &atom)) < 0) {
+ if (sts == PM_ERR_PMID)
+ __pmNotifyErr(LOG_ERR, "unknown PMID requested - '%s'",
+ pmIDStr(dp->pmid));
+ else if (sts == PM_ERR_INST)
+ __pmNotifyErr(LOG_ERR, "unknown instance requested - %d "
+ "(pmid=%s)", ins->i_inst, pmIDStr(dp->pmid));
+ else
+ __pmNotifyErr(LOG_ERR, "fetch error (pmid=%s): %s",
+ pmIDStr(dp->pmid), pmErrStr(sts));
+ }
+ else if (sts == 0) { /* not current, so don't use */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "Instance is dated %s (pmid=%s)",
+ ins->i_name, pmIDStr(dp->pmid));
+#endif
+ }
+ else if ((sts = __pmStuffValue(&atom, &vset->vlist[j], dp->type)) == PM_ERR_TYPE)
+ __pmNotifyErr(LOG_ERR, "bad desc type (%d) for metric %s",
+ dp->type, pmIDStr(dp->pmid));
+ else if (sts >= 0) {
+ vset->valfmt = sts;
+ j++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "Instance is good! %s (pmid=%s)",
+ ins->i_name, pmIDStr(dp->pmid));
+#endif
+ }
+ } while (dp->indom != PM_INDOM_NULL &&
+ (ins = nextTraceInst(pmda)) != NULL);
+ if (j == 0)
+ vset->numval = sts;
+ else
+ vset->numval = j;
+ }
+ *resp = res;
+
+ return 0;
+}
+
+
+static int
+traceStore(pmResult *result, pmdaExt *pmda)
+{
+ int i, j;
+ int sts = 0;
+ pmValueSet *vsp = NULL;
+ __pmID_int *pmidp = NULL;
+ pmAtomValue av;
+ extern int afid;
+ extern void alarming(int, void *);
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster != 0)
+ return PM_ERR_PMID;
+
+ if (pmidp->item == 15) { /* trace.control.reset */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ __pmNotifyErr(LOG_DEBUG, "resetting trace metrics");
+#endif
+ /* reset the interval timer */
+ if (afid >= 0) {
+ __pmAFunregister(afid);
+ if ((afid = __pmAFregister(&interval, NULL, alarming)) < 0) {
+ __pmNotifyErr(LOG_ERR, "__pmAFregister failed");
+ exit(1);
+ }
+ }
+
+ /* reset summary and ring buffer hash tables */
+ __pmhashtrunc(&summary);
+ for (j = 0; j < rbufsize; j++) {
+ __pmhashtrunc(ringbuf.ring[j].stats);
+ ringbuf.ring[j].numstats = 0;
+ }
+
+ /* clear all the instance domain entries */
+ if (indomtab[TRANSACT_INDOM].it_set) {
+ free(indomtab[TRANSACT_INDOM].it_set);
+ indomtab[TRANSACT_INDOM].it_set = NULL;
+ }
+ if (indomtab[OBSERVE_INDOM].it_set) {
+ free(indomtab[OBSERVE_INDOM].it_set);
+ indomtab[OBSERVE_INDOM].it_set = NULL;
+ }
+ if (indomtab[COUNTER_INDOM].it_set) {
+ free(indomtab[COUNTER_INDOM].it_set);
+ indomtab[COUNTER_INDOM].it_set = NULL;
+ }
+ if (indomtab[POINT_INDOM].it_set) {
+ free(indomtab[POINT_INDOM].it_set);
+ indomtab[POINT_INDOM].it_set = NULL;
+ }
+ indomtab[TRANSACT_INDOM].it_numinst = 0;
+ indomtab[COUNTER_INDOM].it_numinst = 0;
+ indomtab[OBSERVE_INDOM].it_numinst = 0;
+ indomtab[POINT_INDOM].it_numinst = 0;
+ tindomsize = pindomsize = oindomsize = 0;
+ /* definately need to recompute the summary next fetch */
+ dosummary = 1;
+ __pmNotifyErr(LOG_INFO, "PMDA reset");
+ }
+ else if (pmidp->item == 16) { /* trace.control.debug */
+ if (vsp->numval != 1 || vsp->valfmt != PM_VAL_INSITU)
+ sts = PM_ERR_CONV;
+ else if (sts >= 0 && ((sts = pmExtractValue(vsp->valfmt,
+ &vsp->vlist[0], PM_TYPE_32, &av, PM_TYPE_32)) >= 0)) {
+ if (pmDebug != av.l) {
+ pmDebug = av.l;
+ __pmNotifyErr(LOG_INFO, "debug level set to %d", pmDebug);
+ debuglibrary(pmDebug);
+ }
+ }
+ }
+ else
+ sts = PM_ERR_PMID;
+ }
+ return sts;
+}
+
+
+/*
+ * Initialise the agent
+ */
+void
+traceInit(pmdaInterface *dp)
+{
+ int rsize, sts;
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.fetch = traceFetch;
+ dp->version.two.store = traceStore;
+ dp->version.two.instance = traceInstance;
+ dp->version.two.ext->e_direct = 0;
+ pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+
+ /* initialise ring buffer */
+ rsize = (int)(sizeof(statlist_t) * rbufsize);
+ if ((ringbuf.ring = (statlist_t *)malloc(rsize)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "failed during ring buffer initialise: %s",
+ osstrerror());
+ exit(1);
+ }
+ for (rsize=0; rsize < rbufsize; rsize++) {
+ if ((ringbuf.ring[rsize].stats = (hashtable_t *)
+ malloc(sizeof(hashtable_t))) == NULL) {
+ __pmNotifyErr(LOG_ERR, "ring buffer initialise failed: %s",
+ osstrerror());
+ exit(1);
+ }
+ if ((sts = __pmhashinit(ringbuf.ring[rsize].stats, 0, sizeof(hashdata_t),
+ datacmp, datadel)) < 0) {
+ __pmNotifyErr(LOG_ERR, "ring buffer initialisation failed: %s",
+ osstrerror());
+ exit(1);
+ }
+ ringbuf.ring[rsize].working = 0;
+ }
+ rpos = 0;
+ ringbuf.ring[rpos].working = 1;
+
+ /* initialise summary & associated instance domain */
+ indomtab[TRANSACT_INDOM].it_numinst = 0;
+ indomtab[TRANSACT_INDOM].it_set = NULL;
+ indomtab[POINT_INDOM].it_numinst = 0;
+ indomtab[POINT_INDOM].it_set = NULL;
+ dp->version.two.ext->e_idp = indomtab;
+ if ((sts = __pmhashinit(&summary, 0, sizeof(hashdata_t),
+ datacmp, summarydel)) < 0) {
+ __pmNotifyErr(LOG_ERR, "summary table initialisation failed: %s",
+ osstrerror());
+ exit(1);
+ }
+ /* initialise list of reserved instance domains (for store recovery) */
+ if ((sts = __pmhashinit(&history, 0, sizeof(instdata_t),
+ instcmp, instdel)) < 0) {
+ __pmNotifyErr(LOG_ERR, "history table initialisation failed: %s",
+ osstrerror());
+ exit(1);
+ }
+}
diff --git a/src/pmdas/trace/stub.c b/src/pmdas/trace/stub.c
new file mode 100644
index 0000000..08790be
--- /dev/null
+++ b/src/pmdas/trace/stub.c
@@ -0,0 +1,136 @@
+/*
+ * stub.c - libpcp_trace stubs
+ *
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pcp/trace.h>
+
+int __pmstate = 0;
+
+int
+pmtracebegin(const char *tag)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtracebegin: start of transaction '%s'\n", tag);
+#endif
+ return 0;
+}
+
+int
+pmtraceend(const char *tag)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtraceend: end of transaction '%s'\n", tag);
+#endif
+ return 0;
+}
+
+int
+pmtraceabort(const char *tag)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtraceabort: transaction '%s' aborted\n", tag);
+#endif
+ return 0;
+}
+
+int
+pmtraceobs(const char *label, double value)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtraceobs: observation '%s', value=%f\n", label, value);
+#endif
+ return 0;
+}
+
+int
+pmtracecounter(const char *label, double value)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtracecounter: counter '%s', value=%f\n", label, value);
+#endif
+ return 0;
+}
+
+int
+pmtracepoint(const char *label)
+{
+#ifdef PMTRACE_DEBUG
+ if (__pmstate & PMTRACE_STATE_API)
+ fprintf(stderr, "pmtracepoint: trace point '%s' reached\n", label);
+#endif
+ return 0;
+}
+
+int
+pmtracestate(int code)
+{
+ return(__pmstate |= code);
+}
+
+char *
+pmtraceerrstr(int code)
+{
+ static const struct {
+ int code;
+ char *msg;
+ } errtab[] = {
+ { PMTRACE_ERR_TAGNAME,
+ "Invalid tag name - tag names cannot be NULL" },
+ { PMTRACE_ERR_INPROGRESS,
+ "Transaction is already in progress - cannot begin" },
+ { PMTRACE_ERR_NOPROGRESS,
+ "Transaction is not currently in progress - cannot end" },
+ { PMTRACE_ERR_NOSUCHTAG,
+ "Transaction tag was not successfully initialised" },
+ { PMTRACE_ERR_TAGTYPE,
+ "Tag is already in use for a different type of tracing" },
+ { PMTRACE_ERR_TAGLENGTH,
+ "Tag name is too long (maximum 256 characters)" },
+ { PMTRACE_ERR_IPC,
+ "IPC protocol failure" },
+ { PMTRACE_ERR_ENVFORMAT,
+ "Unrecognised environment variable format" },
+ { PMTRACE_ERR_TIMEOUT,
+ "Application timed out connecting to the PMDA" },
+ { PMTRACE_ERR_VERSION,
+ "Incompatible versions between application and PMDA" },
+ { PMTRACE_ERR_PERMISSION,
+ "Cannot connect to PMDA - permission denied" },
+ { PMTRACE_ERR_CONNLIMIT,
+ "Cannot connect to PMDA - connection limit reached" },
+ { 0, "" }
+ };
+
+ if ((code < 0) && (code > -PMTRACE_ERR_BASE)) /* catch intro(2) errors */
+ return strerror(-code);
+ else if (code == 0)
+ return "No error";
+ else {
+ int i;
+ for (i=0; errtab[i].code; i++) {
+ if (errtab[i].code == code)
+ return errtab[i].msg;
+ }
+ }
+ return "Unknown error code";
+}
diff --git a/src/pmdas/trivial/GNUmakefile b/src/pmdas/trivial/GNUmakefile
new file mode 100644
index 0000000..dfa9d8c
--- /dev/null
+++ b/src/pmdas/trivial/GNUmakefile
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+DFILES = README
+CFILES = trivial.c
+CMDTARGET = pmdatrivial$(EXECSUFFIX)
+LLDLIBS = $(PCP_PMDALIB)
+LCFLAGS = -I.
+LSRCFILES = Install Remove pmns help $(DFILES) root GNUmakefile.install
+
+IAM = trivial
+DOMAIN = TRIVIAL
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h *.o $(IAM).log pmda$(IAM) pmda_$(IAM).so
+
+default_pcp default: domain.h $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install install_pcp: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMDADIR)/Makefile
+ $(INSTALL) -m 644 $(DFILES) root help pmns domain.h $(CFILES) $(PMDADIR)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
diff --git a/src/pmdas/trivial/GNUmakefile.install b/src/pmdas/trivial/GNUmakefile.install
new file mode 100644
index 0000000..92f1351
--- /dev/null
+++ b/src/pmdas/trivial/GNUmakefile.install
@@ -0,0 +1,51 @@
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = sh
+
+ifdef PCP_CONF
+include $(PCP_CONF)
+else
+include $(PCP_DIR)/etc/pcp.conf
+endif
+include $(PCP_INC_DIR)/builddefs
+
+# remove -Lpath and -Ipath options from builddefs CFLAGS value
+#
+PCP_LIBS =
+TMP := $(CFLAGS:-I%=)
+ifdef PCP_DIR
+# put -Ipath and -Lpath back but use paths for run-time environment
+#
+CFLAGS = $(TMP) -I$(PCP_INC_DIR)/..
+LDFLAGS = -L$(PCP_LIB_DIR)
+else
+CFLAGS = $(TMP)
+endif
+
+IAM = trivial
+CFILES = $(IAM).c
+
+LIBTARGET = pmda_$(IAM).$(DSOSUFFIX)
+CMDTARGET = pmda$(IAM)
+TARGETS = $(LIBTARGET) $(CMDTARGET)
+
+LLDLIBS = -lpcp_pmda -lpcp $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+LDIRT = *.log help.dir help.pag
+
+default: $(TARGETS)
+
+install: default
+
+include $(PCP_INC_DIR)/buildrules
diff --git a/src/pmdas/trivial/Install b/src/pmdas/trivial/Install
new file mode 100644
index 0000000..02fb13a
--- /dev/null
+++ b/src/pmdas/trivial/Install
@@ -0,0 +1,27 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the trivial PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=trivial
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/trivial/README b/src/pmdas/trivial/README
new file mode 100644
index 0000000..9561cc6
--- /dev/null
+++ b/src/pmdas/trivial/README
@@ -0,0 +1,63 @@
+Trivial PMDA
+============
+
+This PMDA is a sample that illustrates how a simple PMDA might be
+constructed using libpcp_pmda.
+
+Although the metrics supported as simple, the framework is quite
+general, and could be extended to implement a much more complex PMDA.
+
+Note:
+ This PMDA may be remade from source and hence requires IDO (or
+ more specifically a C compiler) to be installed.
+
+ Uses of make(1) may fail (without removing or clobbering files)
+ if the C compiler cannot be found. This is most likely to
+ happen when running the PMDA ./Install script.
+
+ The only remedial action is to install the C compiler, or
+ hand-craft changes to the Makefile.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT trivial
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/trivial
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/trivial
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/trivial.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/trivial/Remove b/src/pmdas/trivial/Remove
new file mode 100644
index 0000000..7de9372
--- /dev/null
+++ b/src/pmdas/trivial/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the trivial PMDA
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=trivial
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/trivial/help b/src/pmdas/trivial/help
new file mode 100644
index 0000000..300bcd4
--- /dev/null
+++ b/src/pmdas/trivial/help
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# trivial PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ trivial.time The time in seconds since 1 Jan 1970
+The time in seconds since the 1st of January, 1970.
diff --git a/src/pmdas/trivial/pmns b/src/pmdas/trivial/pmns
new file mode 100644
index 0000000..f7a6167
--- /dev/null
+++ b/src/pmdas/trivial/pmns
@@ -0,0 +1,23 @@
+/*
+ * Metrics for trivial PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+trivial {
+ time TRIVIAL:0:0
+}
diff --git a/src/pmdas/trivial/root b/src/pmdas/trivial/root
new file mode 100644
index 0000000..579f567
--- /dev/null
+++ b/src/pmdas/trivial/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { trivial }
+
+#include "pmns"
+
diff --git a/src/pmdas/trivial/trivial.c b/src/pmdas/trivial/trivial.c
new file mode 100644
index 0000000..b395538
--- /dev/null
+++ b/src/pmdas/trivial/trivial.c
@@ -0,0 +1,138 @@
+/*
+ * Trivial, configurable PMDA
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include "domain.h"
+
+/*
+ * Trivial PMDA
+ *
+ * This PMDA is a sample that illustrates how a trivial PMDA might be
+ * constructed using libpcp_pmda.
+ *
+ * Although the metrics supported are trivial, the framework is quite general,
+ * and could be extended to implement a much more complex PMDA.
+ *
+ * Metrics
+ * trivial.time - time in seconds since the 1st of Jan, 1970.
+ */
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+static pmdaMetric metrictab[] = {
+/* time */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } }
+};
+
+static char *username;
+static char mypath[MAXPATHLEN];
+static int isDSO = 1; /* ==0 if I am a daemon */
+
+static pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+static pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+};
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+trivial_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+
+ if (idp->cluster != 0 || idp->item != 0)
+ return PM_ERR_PMID;
+ else if (inst != PM_IN_NULL)
+ return PM_ERR_INST;
+
+ atom->ul = time(NULL);
+ return 0;
+}
+
+/*
+ * Initialise the agent (both daemon and DSO).
+ */
+void
+trivial_init(pmdaInterface *dp)
+{
+ if (isDSO) {
+ int sep = __pmPathSeparator();
+ snprintf(mypath, sizeof(mypath), "%s%c" "trivial" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_2, "trivial DSO", mypath);
+ } else {
+ __pmSetProcessIdentity(username);
+ }
+
+ if (dp->status != 0)
+ return;
+
+ pmdaSetFetchCallBack(dp, trivial_fetchCallBack);
+
+ pmdaInit(dp, NULL, 0,
+ metrictab, sizeof(metrictab)/sizeof(metrictab[0]));
+}
+
+/*
+ * Set up the agent if running as a daemon.
+ */
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ pmdaInterface desc;
+
+ isDSO = 0;
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "trivial" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, TRIVIAL,
+ "trivial.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &desc);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&desc);
+ trivial_init(&desc);
+ pmdaConnect(&desc);
+ pmdaMain(&desc);
+
+ exit(0);
+}
diff --git a/src/pmdas/txmon/GNUmakefile b/src/pmdas/txmon/GNUmakefile
new file mode 100644
index 0000000..7c423ba
--- /dev/null
+++ b/src/pmdas/txmon/GNUmakefile
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+DFILES = README
+CFILES = txmon.c txrecord.c
+HFILES = txmon.h
+TARGETS = txmon$(EXECSUFFIX) txrecord$(EXECSUFFIX)
+
+LCFLAGS = -I.
+LLDLIBS = $(PCP_PMDALIB)
+
+SCRIPTS = Install Remove genload
+OTHERS = pmns help root
+LSRCFILES= $(SCRIPTS) $(OTHERS) GNUmakefile.install $(DFILES)
+
+IAM = txmon
+DOMAIN = TXMON
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = *.o *.log *.dir *.pag domain.h $(TARGETS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(TARGETS)
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root $(HFILES) $(PMDADIR)
+ $(INSTALL) -m 644 help pmns domain.h $(CFILES) $(PMDADIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMDADIR)/Makefile
+else
+build-me:
+install:
+endif
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(OBJECTS): domain.h
+
+txmon$(EXECSUFFIX): txmon.o
+ $(CCF) -o $@ $(LDFLAGS) txmon.o $(LDLIBS)
+
+txrecord$(EXECSUFFIX): txrecord.o
+ $(CCF) -o $@ $(LDFLAGS) txrecord.o $(LDLIBS)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/txmon/GNUmakefile.install b/src/pmdas/txmon/GNUmakefile.install
new file mode 100644
index 0000000..1f264c0
--- /dev/null
+++ b/src/pmdas/txmon/GNUmakefile.install
@@ -0,0 +1,57 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+SHELL = sh
+
+ifdef PCP_CONF
+include $(PCP_CONF)
+else
+PCP_DIR = $(shell echo $$PCP_DIR)
+include $(PCP_DIR)/etc/pcp.conf
+endif
+include $(PCP_INC_DIR)/builddefs
+
+# remove -Lpath and -Ipath options from builddefs CFLAGS value
+#
+PCP_LIBS =
+TMP := $(CFLAGS:-I%=)
+ifdef PCP_DIR
+# put -Ipath and -Lpath back but use paths for run-time environment
+#
+CFLAGS = $(TMP) -I$(PCP_INC_DIR)/..
+LDFLAGS = -L$(PCP_LIB_DIR)
+else
+CFLAGS = $(TMP)
+endif
+
+IAM = txmon
+CFILES = $(IAM).c
+CMDTARGET = pmda$(IAM)
+TARGETS = txrecord $(CMDTARGET)
+LLDLIBS = -lpcp_pmda -lpcp $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+LDIRT = *.log help.dir help.pag txrecord
+
+default: $(TARGETS)
+
+install: default
+
+pmda$(IAM): txmon.h domain.h $(CFILES)
+
+txrecord.o: txmon.h txrecord.c
+
+txrecord: txrecord.o
+ $(CC) $(CFLAGS) -o $@ txrecord.o $(LDFLAGS) $(LDLIBS)
+
+include $(PCP_INC_DIR)/buildrules
diff --git a/src/pmdas/txmon/Install b/src/pmdas/txmon/Install
new file mode 100755
index 0000000..b8e1fcf
--- /dev/null
+++ b/src/pmdas/txmon/Install
@@ -0,0 +1,56 @@
+#! /bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the txmon PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=txmon
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+
+if $do_pmda
+then
+ # special txmon PMDA args
+ #
+ echo 'Welcome to the Install script for the demonstration "txmon" PMDA.
+This PMDA will establish a shared memory segment with one statistics
+structure per transaction type.
+
+You must define the names of the transaction types (the names are
+arbitrary strings with no embedded white space, e.g. mytx#1).
+'
+
+ args=""
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Name for transaction type? [return if no more] ""$PCP_ECHO_C"
+ read ans
+ [ -z "$ans" ] && break
+ args="$args $ans"
+ done
+ if [ -z "$args" ]
+ then
+ echo "You need to specify at least one transaction type name!"
+ exit 1
+ fi
+fi
+
+pmdaInstall
+
+exit 0
diff --git a/src/pmdas/txmon/README b/src/pmdas/txmon/README
new file mode 100644
index 0000000..97fea32
--- /dev/null
+++ b/src/pmdas/txmon/README
@@ -0,0 +1,77 @@
+txmon PMDA
+===========
+
+This PMDA is a sample that illustrates how a simple transaction monitor
+PMDA might be constructed, using a shared memory segment to transfer
+information about transaction activity from the applications that
+submit (or process) transactions and the txmon PMDA.
+
+Although the metrics supported are simple, the framework is quite
+general and could be extended to implement a much more complex PMDA.
+
+Note:
+ This PMDA may be remade from source and hence requires IDO (or
+ more specifically a C compiler) to be installed.
+
+ Uses of make(1) may fail (without removing or clobbering files)
+ if the C compiler cannot be found. This is most likely to
+ happen when running the PMDA ./Install script.
+
+ The only remedial action is to install the C compiler, or
+ hand-craft changes to the Makefile.
+
+Metrics
+=======
+
+The file ./help contains descriptions for all of the metrics exported
+by this PMDA.
+
+Once the PMDA has been installed, the following command will list all
+the available metrics and their explanatory "help" text:
+
+ $ pminfo -fT txmon
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/txmon
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options.
+
+ You will be prompted to define the names of the transaction types
+ to be monitored -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/txmon
+ # ./Remove
+
+Making something happen
+=======================
+
+The application ./txrecord updates the shared memory segment to add
+new information about transactions and their service times. Usually
+this would be run from the ./genload script that will continue to
+update the shared memory segment with data drawn from some synthetic
+distributions.
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/txmon.log) should be checked for any warnings
+ or errors.
diff --git a/src/pmdas/txmon/Remove b/src/pmdas/txmon/Remove
new file mode 100644
index 0000000..5b71f70
--- /dev/null
+++ b/src/pmdas/txmon/Remove
@@ -0,0 +1,38 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the txmon PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=txmon
+
+# Do it
+#
+pmdaSetup
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/txmon/genload b/src/pmdas/txmon/genload
new file mode 100755
index 0000000..d906eaa
--- /dev/null
+++ b/src/pmdas/txmon/genload
@@ -0,0 +1,120 @@
+#! /bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# micky mouse transaction load generator ...
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+
+NUMTX=-1
+while getopts n:\? c
+do
+ case $c
+ in
+ n) NUMTX=$OPTARG;;
+ \?) echo "Usage: genload [-n numtx] [tps [avg_serv]]"
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# target TPS [default 10]
+#
+TPS=${1-10}
+
+# mean of the mean service times [default 10 seconds]
+#
+AVG=${2-10}
+
+if [ ! -x ./txrecord ]
+then
+ echo 'Error: no txrecord binary here ... try "make txrecord"?'
+ exit 1
+fi
+
+# get tx types from txmon PMDA via pmcd on the localhost, then choose tx
+# type with Zipfian distribution, and artifically chosen service times
+# ... pipe them to a record-then-sleep droid in the while loop at the
+# end ... rate throttling relies on O/S pipe-full synchronization
+# (crude, but simple and effective)
+#
+pminfo -f txmon.count \
+| tr -d '"\]\[' \
+| $PCP_AWK_PROG '
+BEGIN { i = 0; w = 2; s = 0; numtx='$NUMTX' }
+/value/ { name[i] = $4; zipf[i] = s + 1 / w; s = zipf[i]
+ i++; w *= 2
+ }
+END { if (i == 0) {
+ print "e Cannot determine transaction type names ... is the txmon PMDA running locally?"
+ exit
+ }
+ # compute mean, drawn from normal distn with mean and std dev AVG
+ for (j=0; j<i; j++) {
+ while (1) { # the csw method
+ y = rand()
+ d = 1 - y
+ h = 0.14 / d
+ x = 1.22 * y * (1.0 + h)
+ t = log(rand() / (1.0 + h/d)) + 0.5 * x*x + 0.1576
+ if (t <= 0) {
+ if (t >= -0.69314706) x = -x
+ m[j] = '$AVG' + '$AVG' * x
+ if (m[j] >= 0)
+ break
+ }
+ }
+ print "i Mean service time for " name[j] " tx: " m[j]
+ }
+ zipf[i-1] = 1 # force upper bound of last interval
+ while (numtx != 0) {
+ printf "w "
+ for (k=0; k < '$TPS' && numtx != 0; k++) {
+ r = rand()
+ for (j=0; j<i; j++) {
+ if (r <= zipf[j])
+ break
+ }
+ printf " %s %f",name[j],rand() * m[j]
+ if (numtx > 0)
+ numtx--
+ }
+ print ""
+ print "s"
+ }
+ }' \
+| while read action arg
+do
+ case $action
+ in
+ i) echo "$arg"
+ ;;
+ e) echo "Error: $arg"
+ exit 1
+ ;;
+ s) sleep 1
+ ;;
+ w) ./txrecord $arg
+ ;;
+ esac
+done
+
+
diff --git a/src/pmdas/txmon/help b/src/pmdas/txmon/help
new file mode 100644
index 0000000..83e2300
--- /dev/null
+++ b/src/pmdas/txmon/help
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# txmon PMDA help file in the ASCII format
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+
+@ TXMON.0 Instance domain "transaction type name" for txmon PMDA
+There is one instance for each transaction type.
+
+@ txmon.count count by transaction type
+Count by transaction type of transactions serviced since the txmon PMDA
+started.
+
+@ txmon.reset_count reset count by transaction type
+Count by transaction type of transactions serviced since the last time
+the average and maximum summaries were reset.
+
+@ txmon.ave_time average time per transaction type
+Average recorded service time per transaction type since the last time
+the statistics were reset.
+
+@ txmon.max_time maximum time per transaction type
+Maximum recorded service time per transaction type since the last time
+the statistics were reset.
+
+@ txmon.control.level statistics control levels
+0 => no statistics are collected
+1 => transaction counts are enabled
+2 => transaction counts and service times are enabled
+
+@ txmon.control.reset reset statistics
+This metric is a toggle-switch ... any pmStore() operation for this
+metric will reset all statistics except txmon.count.
+
+The value for this metric is always 1.
diff --git a/src/pmdas/txmon/pmns b/src/pmdas/txmon/pmns
new file mode 100644
index 0000000..9535a4d
--- /dev/null
+++ b/src/pmdas/txmon/pmns
@@ -0,0 +1,32 @@
+/*
+ * Metrics for txmon PMDA
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+txmon {
+ count TXMON:0:0
+ ave_time TXMON:0:1
+ max_time TXMON:0:2
+ reset_count TXMON:0:3
+ control
+}
+
+txmon.control {
+ level TXMON:0:4
+ reset TXMON:0:5
+}
diff --git a/src/pmdas/txmon/root b/src/pmdas/txmon/root
new file mode 100644
index 0000000..3d85304
--- /dev/null
+++ b/src/pmdas/txmon/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { txmon }
+
+#include "pmns"
+
diff --git a/src/pmdas/txmon/txmon.c b/src/pmdas/txmon/txmon.c
new file mode 100644
index 0000000..bdfbf7a
--- /dev/null
+++ b/src/pmdas/txmon/txmon.c
@@ -0,0 +1,383 @@
+/*
+ * txmon PMDA
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <pcp/pmda.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include "domain.h"
+
+#define CACHELINE 32
+#define RND_TO_CACHE_LINE(x) (((x + CACHELINE - 1) / CACHELINE) * CACHELINE)
+
+/*
+ * txmon PMDA
+ *
+ * This PMDA is a sample that illustrates how a PMDA might be
+ * constructed with libpcp_pmda to use a System V shared memory (shm)
+ * segment to transfer performance data between the collectors on the
+ * application side (see ./txrecord and ./genload for a simple example)
+ * and this PCP PMDA
+ */
+
+/*
+ * list of instances ... created dynamically, after parsing cmd line options
+ */
+static pmdaInstid *tx_indom;
+
+/*
+ * list of instance domains ... initialized after parsing cmd line options
+ */
+static pmdaIndom indomtab[] = {
+#define TX_INDOM 0
+ { TX_INDOM, 0, NULL },
+};
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+static pmdaMetric metrictab[] = {
+/* count */
+ { NULL,
+ { PMDA_PMID(0,0), PM_TYPE_U32, TX_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* ave_time */
+ { NULL,
+ { PMDA_PMID(0,1), PM_TYPE_FLOAT, TX_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,-1,0,PM_TIME_SEC,PM_COUNT_ONE) } },
+/* max_time */
+ { NULL,
+ { PMDA_PMID(0,2), PM_TYPE_FLOAT, TX_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0 ) } },
+/* reset_count */
+ { NULL,
+ { PMDA_PMID(0,3), PM_TYPE_U32, TX_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
+/* control.level */
+ { NULL,
+ { PMDA_PMID(0,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+/* control.reset */
+ { NULL,
+ { PMDA_PMID(0,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) } },
+};
+
+#include "./txmon.h"
+
+static int shmid = -1;
+
+static char mypath[MAXPATHLEN];
+static char *username;
+
+/*
+ * callback provided to pmdaFetch
+ */
+static int
+txmon_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ stat_t *sp;
+ __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid);
+ unsigned int real_count;
+
+ if (inst != PM_IN_NULL && mdesc->m_desc.indom == PM_INDOM_NULL)
+ return PM_ERR_INST;
+
+ if (idp->cluster != 0)
+ return PM_ERR_PMID;
+
+ if (idp->item <= 3) {
+ if (inst >= control->n_tx)
+ return PM_ERR_INST;
+
+ sp = (stat_t *)((__psint_t)control + control->index[inst]);
+
+ switch (idp->item) {
+ case 0: /* txmon.count */
+ if (control->level < 1)
+ return PM_ERR_AGAIN;
+ atom->ul = sp->count;
+ break;
+ case 1: /* txmon.ave_time */
+ if (control->level < 2)
+ return PM_ERR_AGAIN;
+ real_count = sp->count - sp->reset_count;
+ atom->f = real_count > 0 ? sp->sum_time / real_count : -1;
+ break;
+ case 2: /* txmon.max_time */
+ if (control->level < 2)
+ return PM_ERR_AGAIN;
+ atom->f = sp->max_time;
+ break;
+ case 3: /* txmon.reset_count */
+ if (control->level < 1)
+ return PM_ERR_AGAIN;
+ atom->ul = sp->count - sp->reset_count;
+ break;
+ }
+ }
+ else {
+ switch (idp->item) {
+
+ case 4: /* txmon.control.level */
+ atom->ul = control->level;
+ break;
+ case 5: /* txmon.control.reset */
+ atom->ul = 1;
+ break;
+ default:
+ return PM_ERR_PMID;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * support the storage of a value into the control metrics
+ */
+static int
+txmon_store(pmResult *result, pmdaExt *pmda)
+{
+ int i;
+ int n;
+ int val;
+ int sts = 0;
+ pmValueSet *vsp = NULL;
+ __pmID_int *pmidp = NULL;
+ stat_t *sp;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+
+ if (pmidp->cluster == 0) { /* all storable metrics are cluster 0 */
+
+ switch (pmidp->item) {
+ case 0: /* no store for these ones */
+ case 1:
+ case 2:
+ case 3:
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ case 4: /* txmon.control.level */
+ val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 0;
+ }
+ control->level = val;
+ break;
+
+ case 5: /* txmon.control.reset */
+ for (n = 0; n < control->n_tx; n++) {
+ sp = (stat_t *)((__psint_t)control + control->index[n]);
+ sp->reset_count = sp->count;
+ sp->sum_time = 0;
+ sp->max_time = -1;
+ }
+ break;
+
+ default:
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else
+ sts = PM_ERR_PMID;
+ }
+ return sts;
+}
+
+
+/*
+ * Initialise the agent
+ */
+void
+txmon_init(pmdaInterface *dp)
+{
+ if (dp->status != 0)
+ return;
+
+ __pmSetProcessIdentity(username);
+
+ dp->version.two.store = txmon_store;
+
+ pmdaSetFetchCallBack(dp, txmon_fetchCallBack);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "after pmdaSetFetchCallBack() control @ %p\n", control);
+ }
+#endif
+
+ pmdaInit(dp, indomtab, 1, metrictab,
+ sizeof(metrictab)/sizeof(metrictab[0]));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "after pmdaInit() control @ %p\n", control);
+ }
+#endif
+}
+
+/*
+ * come here on exit()
+ */
+static void
+done(void)
+{
+ if (shmid != -1)
+ /* remove the shm segment */
+ shmctl(shmid, IPC_RMID, NULL);
+}
+
+pmLongOptions longopts[] = {
+ PMDA_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMDAOPT_DOMAIN,
+ PMDAOPT_LOGFILE,
+ PMDAOPT_USERNAME,
+ PMOPT_HELP,
+ PMDA_OPTIONS_END
+};
+
+pmdaOptions opts = {
+ .short_options = "D:d:l:U:?",
+ .long_options = longopts,
+ .short_usage = "[options] tx_type [...]",
+};
+
+/*
+ * Set up the agent.
+ */
+int
+main(int argc, char **argv)
+{
+ int n, sep = __pmPathSeparator();
+ char *p;
+ pmdaInterface dispatch;
+ size_t index_size;
+ size_t shm_size;
+ stat_t *sp;
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&username);
+
+ snprintf(mypath, sizeof(mypath), "%s%c" "txmon" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_2, pmProgname, TXMON,
+ "txmon.log", mypath);
+
+ pmdaGetOptions(argc, argv, &opts, &dispatch);
+ if (opts.errors) {
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ n = argc - opts.optind;
+ if (n < 1) {
+ pmprintf("No transaction types specified\n\n");
+ pmdaUsageMessage(&opts);
+ exit(1);
+ }
+ if (opts.username)
+ username = opts.username;
+
+ pmdaOpenLog(&dispatch);
+
+ /*
+ * create the instance domain table ... one entry per transaction type
+ */
+ if ((tx_indom = (pmdaInstid *)malloc(n * sizeof(pmdaInstid))) == NULL) {
+ fprintf(stderr, "malloc(%d): %s\n",
+ n * (int)sizeof(pmdaInstid), osstrerror());
+ exit(1);
+ }
+ indomtab[0].it_numinst = n;
+ indomtab[0].it_set = tx_indom;
+
+ /*
+ * size shm segment so each stat_t starts on a cache line boundary
+ */
+ index_size =
+ RND_TO_CACHE_LINE(sizeof(control->level) +
+ sizeof(control->n_tx) +
+ n * sizeof(control->index[0]));
+ shm_size = index_size + n * RND_TO_CACHE_LINE(sizeof(stat_t));
+
+ /*
+ * create the shm segment, and install exit() handler to remove it
+ */
+ if ((shmid = shmget(KEY, shm_size, IPC_CREAT|0666)) < 0) {
+ fprintf(stderr, "shmid: %s\n", osstrerror());
+ exit(1);
+ }
+ atexit(done);
+ if ((control = (control_t *)shmat(shmid, NULL, 0)) == (control_t *)-1) {
+ fprintf(stderr, "shmat: %s\n", osstrerror());
+ exit(1);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "shmat -> control @ %p\n", control);
+ }
+#endif
+
+ /*
+ * set up the shm control info and directory
+ */
+ control->n_tx = n;
+ control->level = 2; /* arbitrary default stats level */
+ p = (char *)control;
+ p = &p[index_size];
+ for (n = 0; n < control->n_tx; n++) {
+ /*
+ * Note: it is important that the index[] entries are byte
+ * offsets from the start of the shm segment ... using
+ * pointers may cause problems for 32-bit and 64-bit apps
+ * attaching to the shm segment
+ */
+ control->index[n] = p - (char *)control;
+ sp = (stat_t *)p;
+ strncpy(sp->type, argv[opts.optind++], MAXNAMESIZE);
+ sp->type[MAXNAMESIZE-1] = '\0'; /* ensure null terminated */
+ sp->reset_count = 0;
+ sp->count = 0;
+ sp->max_time = -1.0;
+ sp->sum_time = 0.0;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "index[%d]=%d @ %p name=\"%s\"\n", n, control->index[n], p, sp->type);
+ }
+#endif
+ p += RND_TO_CACHE_LINE(sizeof(stat_t));
+
+ /*
+ * and set up the corresponding indom table entries
+ */
+ tx_indom[n].i_inst = n;
+ tx_indom[n].i_name = sp->type;
+ }
+
+ /*
+ * the real work is done below here ...
+ */
+ txmon_init(&dispatch);
+ pmdaConnect(&dispatch);
+ pmdaMain(&dispatch);
+ exit(0);
+}
diff --git a/src/pmdas/txmon/txmon.h b/src/pmdas/txmon/txmon.h
new file mode 100644
index 0000000..d7a74e8
--- /dev/null
+++ b/src/pmdas/txmon/txmon.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * the txmon shm segment
+ *
+ * control comes at the beginning, with index[] expanded to match the
+ * number of transaction types (control->n_tx)
+ *
+ * then follows one stat_t per transaction type, subject to the
+ * constraint that each stat_t is hardware cache aligned to avoid
+ * anti-social bus traffic
+ */
+
+typedef struct {
+ int level; /* controls stats collection levels */
+ int n_tx; /* # of tx types */
+ int index[1]; /* will be expanded when allocated */
+} control_t;
+
+static control_t *control;
+
+#define MAXNAMESIZE 20
+
+typedef struct {
+
+ /* managed by txmon PMDA ... do not fiddle with these! */
+ char type[MAXNAMESIZE]; /* tx type name */
+ unsigned int reset_count; /* tx count @ last reset */
+
+ /* initialized and then read by txmon PMDA, updated by txrecord */
+ unsigned int count; /* tx count since epoch */
+ float max_time; /* maximum elapsed time */
+ double sum_time; /* aggregate elapsed time */
+} stat_t;
+
+/* arbitrary shm key */
+#define KEY (key_t)0xdeadbeef
diff --git a/src/pmdas/txmon/txrecord.c b/src/pmdas/txmon/txrecord.c
new file mode 100644
index 0000000..0289a9d
--- /dev/null
+++ b/src/pmdas/txmon/txrecord.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include "txmon.h"
+
+/*
+ * Update the shm segment the is used to export metrics via the txmon PMDA
+ */
+int
+main(int argc, char **argv)
+{
+ int shmid;
+ int n;
+ char *p;
+ stat_t *sp = NULL; /* initialize to pander to gcc */
+
+ if (argc == 1 || (argc == 2 && strcmp(argv[1], "-?") == 0)) {
+ fprintf(stderr, "Usage: txrecord tx_type servtime [tx_type servtime ...]\n"
+ " txrecord -l\n");
+ exit(1);
+ }
+
+ /*
+ * attach to the txmon PMDA shm segment ...
+ */
+ if ((shmid = shmget(KEY, 0, 0)) < 0) {
+ fprintf(stderr, "Cannot attach to shm segment, shmid: %s\n", osstrerror());
+ fprintf(stderr, "Is the txmon PMDA configured and running?\n");
+ exit(1);
+ }
+ if ((control = (control_t *)shmat(shmid, NULL, 0)) == (control_t *)-1) {
+ fprintf(stderr, "Cannot attach to shm segment, shmat: %s\n", osstrerror());
+ fprintf(stderr, "Is the txmon PMDA configured and running?\n");
+ exit(1);
+ }
+
+ if (control->level == 0) {
+ fprintf(stderr, "Stats collection disabled\n");
+ exit(1);
+ }
+ else if (control->level == 1)
+ fprintf(stderr, "Warning: stats time collection disabled\n");
+
+ if (argc == 2 && strcmp(argv[1], "-l") == 0) {
+ printf("txmon shared memory segment summary\n");
+ printf(" tx reset total maximum\n");
+ printf("index offset count count time time name\n");
+ for (n = 0; n < control->n_tx; n++) {
+ sp = (stat_t *)((__psint_t)control + control->index[n]);
+ printf("%5d %6d %6d %6d %9.3f %7.3f %s\n",
+ n, control->index[n], sp->count, sp->reset_count,
+ (float)sp->sum_time, sp->max_time, sp->type);
+ }
+ exit(0);
+ }
+
+ while (argc > 1) {
+
+ for (n = 0; n < control->n_tx; n++) {
+ sp = (stat_t *)((__psint_t)control + control->index[n]);
+ if (strcmp(argv[1], sp->type) == 0)
+ break;
+ }
+ argc--;
+ argv++;
+ if (argc == 1) {
+ fprintf(stderr, "Missing time for tx type \"%s\"?\n", argv[0]);
+ exit(1);
+ }
+
+ if (n == control->n_tx)
+ fprintf(stderr, "Unknown tx type \"%s\" ... skipped\n", argv[0]);
+
+ else {
+ double e;
+ e = strtod(argv[1], &p);
+ if (*p != '\0')
+ fprintf(stderr, "Time value (%s) for tx type \"%s\" is bogus ... skipped\n", argv[1], argv[0]);
+ else {
+ sp->count++;
+ if (control->level == 2) {
+ sp->sum_time += (float)e;
+ if ((float)e > sp->max_time)
+ sp->max_time = e;
+ }
+ }
+ }
+ argc--;
+ argv++;
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/vmware/GNUmakefile b/src/pmdas/vmware/GNUmakefile
new file mode 100644
index 0000000..d4cde6b
--- /dev/null
+++ b/src/pmdas/vmware/GNUmakefile
@@ -0,0 +1,54 @@
+#!gmake
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = vmware
+DOMAIN = VMWARE
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/vmware/Install b/src/pmdas/vmware/Install
new file mode 100755
index 0000000..e834b40
--- /dev/null
+++ b/src/pmdas/vmware/Install
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the VMware PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=vmware
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+perl -e "use VMware::VIRuntime" 2>/dev/null
+if test $? -ne 0; then
+ echo "VMware Infrastructure Perl Toolkit is not installed"
+ exit 1
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/vmware/Remove b/src/pmdas/vmware/Remove
new file mode 100755
index 0000000..1291de4
--- /dev/null
+++ b/src/pmdas/vmware/Remove
@@ -0,0 +1,29 @@
+#! /bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the VMware PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=vmware
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/vmware/pmdavmware.pl b/src/pmdas/vmware/pmdavmware.pl
new file mode 100644
index 0000000..7e79924
--- /dev/null
+++ b/src/pmdas/vmware/pmdavmware.pl
@@ -0,0 +1,456 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+use VMware::VIRuntime;
+
+use vars qw( $host $server $username $password );
+use vars qw( $pmda $entity $view $interval $metric_info %metric_values );
+
+# Configuration files for setting VMware host connection parameters
+for my $file ( pmda_config('PCP_PMDAS_DIR') . '/vmware/vmware.conf',
+ './vmware.conf' ) {
+ eval `cat $file` unless ! -f $file;
+}
+die 'VMware host not setup, stopped' unless defined($host);
+die 'VMware server not setup, stopped' unless defined($server);
+die 'VMware username not setup, stopped' unless defined($username);
+die 'VMware password not setup, stopped' unless defined($password);
+
+Opts::set_option('server' => $server);
+Opts::set_option('username' => $username);
+Opts::set_option('password' => $password);
+Opts::parse();
+
+sub vmware_connect
+{
+ $Util::script_version = '1.0';
+ Util::connect();
+ $entity = Vim::find_entity_view(view_type => 'HostSystem',
+ filter => {'name' => $host});
+ return unless defined($entity);
+
+ $view = Vim::get_view(mo_ref => Vim::get_service_content()->perfManager);
+ my $summary = $view->QueryPerfProviderSummary(entity => $entity);
+ $interval = $summary->refreshRate; # absolute, in seconds
+ $interval = 20 unless defined($interval); # the vmware default
+}
+
+sub vmware_timer_callback
+{
+ return unless !defined($entity);
+ $pmda->log("Connecting to VMware services on $host ($server)");
+ vmware_connect();
+ return unless defined($entity);
+ $pmda->log("Successfully connected.");
+}
+
+sub vmware_disconnect
+{
+ Util::disconnect();
+}
+
+sub vmware_metric_ids
+{
+ my @filtered_list;
+ my $counter_info = $view->perfCounter;
+ my $available_id = $view->QueryAvailablePerfMetric(entity => $entity);
+
+ foreach (@$counter_info) {
+ my $key = $_->key;
+ $metric_info->{$key} = $_;
+ }
+
+ foreach (@$available_id) {
+ my $id = $_->counterId;
+ if (defined $metric_info->{$id}) {
+ my $metric = PerfMetricId->new(counterId => $id, instance => '');
+ push @filtered_list, $metric;
+ }
+ }
+ return \@filtered_list;
+}
+
+sub vmware_fetch
+{
+ return unless defined($entity);
+
+ my @metric_ids = vmware_metric_ids();
+ my $query_spec = PerfQuerySpec->new(entity => $entity,
+ metricId => @metric_ids,
+ 'format' => 'normal',
+ intervalId => $interval,
+ maxSample => 1);
+ my $values;
+ eval {
+ $values = $view->QueryPerf(querySpec => $query_spec);
+ };
+ if ($@) {
+ if (ref($@) eq 'SoapFault') {
+ if (ref($@->detail) eq 'InvalidArgument') {
+ $pmda->log('QueryPerf parameters are not correct');
+ } else {
+ $pmda->log('QueryPerf failed - Soap protocol error');
+ }
+ } else {
+ $pmda->log('QueryPerf failed - cause unknown - good luck!');
+ }
+ }
+ elsif (!@$values) {
+ $pmda->log('VMware performance data unavailable');
+ }
+
+ foreach (@$values) {
+ my $value_array = $_->value;
+ foreach (@$value_array) {
+ my $counter_id = $_->id->counterId;
+ my $counter = $metric_info->{$counter_id};
+ my $key = $counter->nameInfo->label;
+ $metric_values{$key} = $_;
+ # $pmda->log("Value found: $key maps to $counter_id\n");
+ }
+ }
+}
+
+sub vmware_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ if ($cluster == 0) {
+ if ($item == 0) { return ($host, 1); }
+ if ($item == 1) { return ($interval, 1); }
+ }
+
+ return (PM_ERR_NOTCONN, 0) unless defined($entity);
+
+ my $key = pmda_pmid_text($cluster, $item);
+ return (PM_ERR_PMID, 0) unless defined($key);
+
+ # $pmda->log("vmware_fetch_callback $key $cluster:$item ($inst)\n");
+
+ my $pmvalue = $metric_values{$key};
+ return (PM_ERR_AGAIN, 0) unless defined($pmvalue);
+ my $counters = ($pmvalue->value)[0];
+
+ if ($cluster == 0 && $item == 3) {
+ my $uptime = pmda_uptime($counters->[0]);
+ return ($uptime, 1);
+ }
+ return ($counters->[0], 1);
+}
+
+
+$pmda = PCP::PMDA->new('vmware', 90);
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.sys.host',
+ 'Name of monitored VMware host', '');
+$pmda->add_metric(pmda_pmid(0,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'vmware.sys.interval',
+ 'Interval at which VMware internally refreshes values', '');
+$pmda->add_metric(pmda_pmid(0,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_SEC,0),
+ 'vmware.sys.uptime', 'Uptime',
+ 'Total time elapsed since last startup');
+$pmda->add_metric(pmda_pmid(0,3), PM_TYPE_STRING, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.sys.uptime_s', 'Uptime',
+ 'Total time elapsed since last startup');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0),
+ 'vmware.net.usage', 'Network Usage (Average/Rate)',
+ 'Aggregated network performance statistics.');
+
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0),
+ 'vmware.disk.usage', 'Disk Usage (Average/Rate)',
+ 'Aggregated storage performance statistics.');
+
+$pmda->add_metric(pmda_pmid(3,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.mem.usage', 'Memory Usage (Average/Absolute)',
+ 'Memory usage as percentage of total configured or available memory');
+$pmda->add_metric(pmda_pmid(3,1), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_MBYTE,0,0),
+ 'vmware.mem.reserved_capacity', 'Memory Reserved Capacity',
+ 'Amount of memory reserved by the virtual machines');
+$pmda->add_metric(pmda_pmid(3,2), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.swap_in', 'Memory Swap In (Average/Absolute)',
+ 'Amount of memory that is swapped in');
+$pmda->add_metric(pmda_pmid(3,3), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.balloon', 'Memory Balloon (Average/Absolute)',
+ 'Amount of memory used by memory control');
+$pmda->add_metric(pmda_pmid(3,4), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.swap_out', 'Memory Swap Out (Average/Absolute)',
+ 'Amount of memory that is swapped out');
+$pmda->add_metric(pmda_pmid(3,5), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.unreserved',
+ 'Memory Unreserved (Average/Absolute)',
+ 'Amount of memory that is unreserved');
+$pmda->add_metric(pmda_pmid(3,6), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.heap', 'Memory Heap (Average/Absolute)',
+ 'Amount of memory allocated for heap');
+$pmda->add_metric(pmda_pmid(3,7), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.overhead', 'Memory Overhead (Average/Absolute)',
+ 'Amount of additional host memory allocated to the virtual machine');
+$pmda->add_metric(pmda_pmid(3,8), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.zeroed', 'Memory Zero (Average/Absolute)',
+ 'Amount of memory that is zeroed out');
+$pmda->add_metric(pmda_pmid(3,9), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.active', 'Memory Active (Average/Absolute)',
+ 'Amount of memory that is actively used');
+$pmda->add_metric(pmda_pmid(3,10), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.swap_used', 'Memory Swap Used (Average/Absolute)',
+ 'Amount of memory that is used by swap');
+$pmda->add_metric(pmda_pmid(3,11), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.shared', 'Memory Shared (Average/Absolute)',
+ 'Amount of memory that is shared');
+$pmda->add_metric(pmda_pmid(3,12), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.granted', 'Memory Granted (Average/Absolute)',
+ 'Amount of memory granted.');
+$pmda->add_metric(pmda_pmid(3,13), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.consumed', 'Memory Consumed (Average/Absolute)',
+ 'Amount of host memory consumed by the virtual machine for guest memory');
+$pmda->add_metric(pmda_pmid(3,14), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.heap_free', 'Memory Heap Free (Average/Absolute)',
+ 'Free space in memory heap');
+$pmda->add_metric(pmda_pmid(3,15), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.state', 'Memory State',
+ 'Memory State');
+$pmda->add_metric(pmda_pmid(3,16), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.shared_common',
+ 'Memory Shared Common (Average/Absolute)',
+ 'Amount of memory that is shared by common');
+$pmda->add_metric(pmda_pmid(3,17), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(1,0,0,PM_SPACE_KBYTE,0,0),
+ 'vmware.mem.vmkernel', 'Memory Used by vmkernel',
+ 'Amount of memory used by the vmkernel');
+
+$pmda->add_metric(pmda_pmid(4,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE),
+ 'vmware.cpu.usage', 'CPU Usage (Average/Rate)',
+ 'CPU usage as a percentage over the collected interval');
+$pmda->add_metric(pmda_pmid(4,1), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE),
+ 'vmware.cpu.usage_mhz', 'CPU Usage in MHz (Average/Rate)',
+ 'CPU usage in MHz over the collected interval.');
+$pmda->add_metric(pmda_pmid(4,2), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE),
+ 'vmware.cpu.reserved_capacity', 'CPU Reserved Capacity',
+ 'Total CPU capacity reserved by the virtual machines');
+
+$pmda->add_metric(pmda_pmid(5,0), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.throttled.one_min_average',
+ 'CPU Throttled (1 min. average)',
+ 'Amount of CPU resources over the limit that were refused, average over 1 minute');
+$pmda->add_metric(pmda_pmid(5,1), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.throttled.five_min_average',
+ 'CPU Throttled (5 min. average)',
+ 'Amount of CPU resources over the limit that were refused, average over 5 minutes');
+$pmda->add_metric(pmda_pmid(5,2), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.throttled.fifteen_min_average',
+ 'CPU Throttled (15 min. average)',
+ "Amount of CPU resources over the limit that were refused,\n"
+ . "average over 15 minutes\n");
+$pmda->add_metric(pmda_pmid(5,3), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.one_min_peak',
+ 'CPU running peak over 1 minute',
+ '');
+$pmda->add_metric(pmda_pmid(5,4), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.five_min_peak',
+ 'CPU running peak over 5 minutes',
+ '');
+$pmda->add_metric(pmda_pmid(5,5), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.fifteen_min_peak',
+ 'CPU running peak over 15 minutes',
+ '');
+$pmda->add_metric(pmda_pmid(5,6), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_COUNTER, pmda_units(0,0,1,0,0,PM_COUNT_ONE),
+ 'vmware.rescpu.group_sample_count',
+ 'Group CPU Sample Count', 'Group CPU sample count');
+$pmda->add_metric(pmda_pmid(5,7), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.one_min_peak',
+ 'CPU Active (1 min. peak)',
+ 'CPU active peak over 1 minute');
+$pmda->add_metric(pmda_pmid(5,8), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.five_min_peak',
+ 'CPU Active (5 min. peak)',
+ 'CPU active peak over 5 minutes');
+$pmda->add_metric(pmda_pmid(5,9), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.fifteen_min_peak',
+ 'CPU Active (15 min. peak)',
+ 'CPU active peak over 15 minutes');
+$pmda->add_metric(pmda_pmid(5,10), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.one_min_average',
+ 'CPU Running (1 min. average)',
+ 'CPU running average over 1 minute');
+$pmda->add_metric(pmda_pmid(5,11), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.five_min_average',
+ 'CPU Running (5 min. average)',
+ 'CPU running average over 5 minutes');
+$pmda->add_metric(pmda_pmid(5,12), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.fifteen_min_average',
+ 'CPU Running (15 min. average)',
+ 'CPU running average over 15 minutes');
+$pmda->add_metric(pmda_pmid(5,13), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.one_min_peak',
+ 'CPU Running (1 min. peak)',
+ 'CPU running peak over 1 minute');
+$pmda->add_metric(pmda_pmid(5,14), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.five_min_peak',
+ 'CPU Running (5 min. peak)',
+ 'CPU running peak over 5 minutes');
+$pmda->add_metric(pmda_pmid(5,15), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.running.fifteen_min_peak',
+ 'CPU Running (15 min. peak)',
+ 'CPU running peak over 15 minutes');
+$pmda->add_metric(pmda_pmid(5,16), PM_TYPE_U64, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,1,0,0,PM_TIME_MSEC,0),
+ 'vmware.rescpu.group_sample_period',
+ 'Group CPU Sample Period', 'Group CPU sample period');
+$pmda->add_metric(pmda_pmid(5,17), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.one_min_average',
+ 'CPU Active (1 min. average)',
+ 'CPU active average over 1 minute');
+$pmda->add_metric(pmda_pmid(5,18), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.five_min_average',
+ 'CPU Active (5 min. average)',
+ 'CPU active average over 5 minutes');
+$pmda->add_metric(pmda_pmid(5,19), PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_INSTANT, pmda_units(0,0,0,0,0,0),
+ 'vmware.rescpu.active.fifteen_min_average',
+ 'CPU Active (15 min. average)',
+ 'CPU active average over 15 minutes');
+
+$pmda->set_fetch(\&vmware_fetch);
+$pmda->set_fetch_callback(\&vmware_fetch_callback);
+$pmda->add_timer(5, \&vmware_timer_callback, 0);
+$pmda->set_user('pcp');
+$pmda->run;
+vmware_disconnect();
+
+=pod
+
+=head1 NAME
+
+pmdavmware - VMware performance metrics domain agent (PMDA)
+
+=head1 DESCRIPTION
+
+B<pmdavmware> is a Performance Metrics Domain Agent (PMDA) which exports
+metric values from a (possibly remote) VMware virtualisation host.
+
+This implementation uses the VMare Perl API (refer to the online
+docs at http://www.vmware.com/support/developer/viperltoolkit).
+VIPerl is a prerequisite for this PMDA, it needs to be installed
+and configured before attempting to use this agent. It is highly
+recommended that you test your VIPerl installation using the
+demo programs that are shipped with VIPerl, before attempting to
+use this PMDA.
+
+=head1 INSTALLATION
+
+In order to access performance data using the VIPerl API, it is
+necessary to be able to login to the metrics source. Hence, a
+valid VMware server name, user name and pass word are needed by
+the PMDA. These can be passed in on the command line (via the
+pmcd.conf file) or via a vmware.conf file in the PMDA directory.
+
+ # cd $PCP_PMDAS_DIR/vmware
+ # [ edit vmware.conf ]
+
+This file should contain three lines, such as:
+
+ $server = 'vm.server.net';
+ $username = 'XXXX';
+ $password = 'YYYY';
+
+Once this is setup, you can access the names and values for the
+vmware performance metrics by doing the following as root:
+
+ # cd $PCP_PMDAS_DIR/vmware
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/vmware
+ # ./Remove
+
+B<pmdavmware> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item $PCP_PMDAS_DIR/vmware/vmware.conf
+
+configuration file for the B<pmdavmware> agent
+
+=item $PCP_PMDAS_DIR/vmware/Install
+
+installation script for the B<pmdavmware> agent
+
+=item $PCP_PMDAS_DIR/vmware/Remove
+
+undo installation script for the B<pmdavmware> agent
+
+=item $PCP_LOG_DIR/pmcd/vmware.log
+
+default log file for error messages from B<pmdavmware>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/weblog/GNUmakefile b/src/pmdas/weblog/GNUmakefile
new file mode 100644
index 0000000..aa68c72
--- /dev/null
+++ b/src/pmdas/weblog/GNUmakefile
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2000-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = weblog
+DOMAIN = WEBSERVER
+TARGETS = $(IAM)$(EXECSUFFIX) check_match$(EXECSUFFIX)
+CFILES = weblog.c pmda.c sproc.c
+HFILES = weblog.h
+SCRIPTS = Install Remove server.sh weblogconv.sh
+CHARTS = Web.Alarms.pmchart Web.Requests.pmchart Web.Volume.pmchart \
+ Web.Allservers.pmchart Web.Perserver.Bytes.pmchart \
+ Web.Perserver.Requests.pmchart
+DFILES = README
+LSRCFILES = pmns help $(DFILES) root $(SCRIPTS) check_match.c $(CHARTS)
+
+LDIRT = domain.h $(TARGETS) check_match.o
+
+PMDADIR = $(PCP_PMDAS_DIR)/weblog
+PMCHARTDIR = $(PCP_VAR_DIR)/config/pmchart
+CONFDIR = $(PCP_VAR_DIR)/config/web
+
+LDLIBS = $(PCP_PMDALIB) $(LIB_FOR_PTHREADS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifneq "$(TARGET_OS)" "mingw"
+build-me: $(TARGETS)
+
+install: build-me
+ # $(INSTALL) -d $(CONFDIR)
+ # $(INSTALL) -m 644 weblog.conf $(CONFDIR)/weblog.conf
+ $(INSTALL) -d $(PMDADIR)
+ $(INSTALL) -m 755 $(IAM) $(PMDADIR)/pmda$(IAM)
+ $(INSTALL) -m 755 check_match $(SCRIPTS) $(PMDADIR)
+ $(INSTALL) -m 644 $(DFILES) root help pmns domain.h $(PMDADIR)
+ $(INSTALL) -m 644 Web.Alarms.pmchart $(PMCHARTDIR)/Web.Alarms
+ $(INSTALL) -m 644 Web.Requests.pmchart $(PMCHARTDIR)/Web.Requests
+ $(INSTALL) -m 644 Web.Volume.pmchart $(PMCHARTDIR)/Web.Volume
+ $(INSTALL) -m 755 Web.Allservers.pmchart $(PMCHARTDIR)/Web.Allservers
+ $(INSTALL) -m 755 Web.Perserver.Bytes.pmchart $(PMCHARTDIR)/Web.Perserver.Bytes
+ $(INSTALL) -m 755 Web.Perserver.Requests.pmchart $(PMCHARTDIR)/Web.Perserver.Requests
+else
+build-me:
+install:
+endif
+
+weblog$(EXECSUFFIX): $(OBJECTS)
+
+weblog.o: domain.h
+
+check_match$(EXECSUFFIX): check_match.o
+ $(CCF) -o $@ $(LDFLAGS) check_match.o $(LDLIBS)
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdas/weblog/Install b/src/pmdas/weblog/Install
new file mode 100644
index 0000000..dcc86a1
--- /dev/null
+++ b/src/pmdas/weblog/Install
@@ -0,0 +1,694 @@
+#! /bin/sh
+#
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the weblog PMDA and/or PMNS
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# Override function from pmdaproc.sh
+__choose_mode()
+{
+ if [ -n "$QUIET_INSTALL" ] ; then
+ do_pmda=true
+ else
+ __def=m
+ $do_pmda && __def=b
+ echo \
+'You will need to choose an appropriate configuration for installation of
+the "'$iam'" Performance Metrics Domain Agent (PMDA).
+
+ collector collect performance statistics on this system
+ monitor allow this system to monitor local and/or remote systems
+ both collector and monitor configuration for this system
+'
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N 'Please enter c(ollector) or m(onitor) or b(oth) ['$__def'] '"$PCP_ECHO_C"
+ read ans
+ case "$ans"
+ in
+ "") break
+ ;;
+ c|collector|b|both)
+ do_pmda=true
+ break
+ ;;
+ m|monitor)
+ do_pmda=false
+ break
+ ;;
+ *) echo "Sorry, that is not acceptable response ..."
+ ;;
+ esac
+ done
+ echo
+
+ fi
+}
+
+iam=weblog
+pmda_interface=2
+forced_restart=false
+
+pmdaSetup
+
+pmns_name=web # metric names differ from PMDA name
+daemon_opt=true # can install as daemon
+dso_opt=false
+pipe_opt=true # pipe IPC - YES
+socket_opt=false # socket IPC - NO
+socket_inet_def=2080 # default TCP port for Internet socket IPC
+check_delay=10 # give the PMDA a chance to set itself up
+
+# PMDA specific constants
+#
+configDir=$PCP_VAR_DIR/config/web
+
+# PMDA variables
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+debugFlag=0
+do_debug=false
+
+configFile=""
+delay=15
+chkDelay=20
+maxserv=80
+
+
+# --- start functions ---
+#
+_parseDefaults()
+{
+ echo "Extracting options from current installation ..."
+ while getopts D:d:i:l:n:pS:t:u: c
+ do
+ case $c in
+ \?) echo "Warning: Unrecognized option in $PCP_PMCDCONF_PATH"
+ echo " Remove line for pmdaweblog in $PCP_PMCDCONF_PATH and re-run ./Install"
+ exit 2;;
+ D) debugFlag=$OPTARG;;
+ n) chkDelay=$OPTARG;;
+ t) delay=$OPTARG;;
+ S) maxserv=$OPTARG;;
+ *) # old or boring flags, silently ignore
+ ;;
+ esac
+ done
+ shift `expr $OPTIND - 1`
+ if [ $# -eq 1 ]
+ then
+ configFile=$1
+ elif [ $# -eq 0 ]
+ then
+ configFile=""
+ else
+ echo "Warning: unrecognized format for old specification in $PCP_PMCDCONF_PATH"
+ echo " Remove line for pmdaweblog in $PCP_PMCDCONF_PATH and re-run ./Install"
+ exit 2
+ fi
+}
+
+_defaultRegex()
+{
+ touch $1
+ echo '
+# Common regular expressions specifications for parsing access and error logs
+# Each regular expression specification should have a name (one word),
+# specify the order of regex parameters (method and size), and
+# a regular expression. Regular expressions for access logs require two
+# arguments to be set while errors logs require only a match.
+#
+# Set the online HTML Users and Administrators Guide, pmdaweblog(1) and
+# regexec(3) for more details.
+#
+
+# pattern for CERN, NCSA, Netscape, Apache etc Access Logs
+regex_posix CERN method,size ][ \\]+"([A-Za-z][-A-Za-z]+) [^"]*" [-0-9]+ ([-0-9]+)
+# pattern for CERN, NCSA, Netscape etc Error Logs
+regex_posix CERN_err - .
+# pattern for Proxy Server Extended Log Format
+regex_posix NS_PROXY 1,3,2,4 ][ ]+"([A-Za-z][-A-Za-z]+) [^"]*" ([-0-9]+) ([-0-9]+) ([-0-9]+)
+# pattern for Squid Cache logs
+regex_posix SQUID 4,3,2,1 [0-9]+\.[0-9]+[ ]+[0-9]+ [a-zA-Z0-9\.]+ ([_A-Z]+)\/([0-9]+) ([0-9]+) ([A-Z]+)
+# pattern for Netscape SOCKS Server Access logs
+regex_posix NS_SOCKS method,size (sockd)\[.*, ([0-9]+) bytes from .* \(http\)
+# pattern for Netscape SOCKS Server Error logs
+regex_posix NS_SOCKS_err - .
+# pattern for FTP through a Netscape SOCKS Server Access log
+regex_posix NS_FTP method,size (sockd)\[.*, ([0-9]+) bytes from .* \([0-9]+\)
+# pattern for FTP through a Netscape SOCKS Server Error logs
+regex_posix NS_FTP_err - .
+# pattern for FTP Server access logs (normally in SYSLOG)
+regex_posix SYSLOG_FTP method,size ftpd\[.*\]: ([gp][-A-Za-z]+)( )
+# pattern for FTP Server error logs (normally in SYSLOG)
+regex_posix SYSLOG_FTP_err - FTP LOGIN FAILED
+# pattern for WU_FTP Server access logs (normally in xferlog)
+regex_posix WU_FTP size,method :[0-9][0-9] [0-9]+ [0-9]+ .+ ([0-9]+) .+ [ba] .+ ([io]) [arg]
+# pattern for WU_FTP Server error logs (normally in SYSLOG/messages)
+regex_posix WU_FTP_err - failed login
+
+# Server specifications. The format of each specification is
+# "server" serverName on|off accessRegex accessFile errorRegex errorFile
+#
+# Set the online HTML Users and Administrators Guide and pmdaweblog(1)
+# for more details.
+#' >> $1
+}
+
+_parse_server()
+{
+ egrep "^server" | $PCP_AWK_PROG '
+ { i=index($2, ":");
+ if (i == 0) {
+ name = $2;
+ port = "";
+ }
+ else {
+ name = substr($2,1,i-1);
+ port = sprintf("Port %d", substr($2, i+1, length($2) - i));
+ }
+ printf("Server %s %s\n", name, port);
+ printf(" Access Log: %s (%s)\n", $5, $4);
+ printf(" Error Log: %s (%s)\n\n", $7, $6);
+ }'
+}
+
+_default_config ()
+{
+ rm -f $tmp/conf
+ touch $tmp/conf
+ _defaultRegex $tmp/conf
+ ./server.sh -q -l $tmp/conf
+ egrep "^server" $tmp/conf > /dev/null 2>&1
+ _st=$?
+ if [ $_st -eq 0 ]
+ then
+ ./pmdaweblog -C $tmp/conf >$tmp/out 2>&1
+ _st=$?
+ if [ $_st -eq 0 ] ; then
+ if [ -z "$configFile" ]
+ then
+ configFile=$configDir/$iam.conf
+ fi
+ rm -f $configFile
+ cp $tmp/conf $configFile
+ args="-D $debugFlag -t $delay -n $chkDelay -S $maxserv $configFile"
+ socket_opt=false
+ fi
+ fi
+ return $_st
+}
+
+#
+# --- end functions ---
+
+if $do_pmda
+then
+
+ [ ! -d $configDir ] && mkdir -p $configDir
+
+ if [ -n "$QUIET_INSTALL" ] ; then
+ _default_config
+ if [ $? -eq 0 ] ; then
+ pmdaInstall
+ exit $?
+ else
+ exit 1
+ fi
+ else
+ echo "----------------------------------------------------------------"
+ echo
+ echo "The default installation of the weblog PMDA will search for known"
+ echo "Web server configurations on this host and will setup the weblog"
+ echo "PMDA to monitor all associated Web server log files."
+ echo
+ echo "Otherwise, you will be prompted for the required information."
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want a default weblog PMDA installation [y] ""$PCP_ECHO_C"
+ read ans
+ echo
+ if [ "X$ans" = X -o "X$ans" = Xy -o "X$ans" = XY ]
+ then
+ _default_config
+ if [ $? -eq 0 ] ; then
+ pmdaInstall
+ exit $?
+ else
+ echo
+ echo "Unable to find any Web servers!"
+ echo "Reverting to detailed installation..."
+ fi
+ fi
+
+ echo "----------------------------------------------------------------"
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Checking for a previous PMDA installation ...""$PCP_ECHO_C"
+
+ # weblogs -> weblog can be removed once all 1.0 betas are known to
+ # have gone away
+ ans=`$PCP_AWK_PROG < $PCP_PMCDCONF_PATH '
+ $1 == "'$iam'" {
+ printf "%s",$6
+ for (i=7;i<=NF;i++) printf " %s",$i
+ print ""
+ }'`
+ if [ -n "$ans" ]
+ then
+ echo " found"
+ _parseDefaults $ans
+ else
+ echo " appears to be a first-time install"
+ fi
+
+ if [ -n "$configFile" ]
+ then
+ if [ -f "$configFile" ]
+ then
+ if [ $PCP_PLATFORM = linux ] && \
+ egrep '^regex ' $configFile > /dev/null
+ then
+ echo "Warning: previous configuration file \"$configFile\""
+ echo " appears to be an incompatible version."
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to automatically update the configuration file? [y] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = X -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ ./weblogconv.sh $configFile $tmp/conf
+ if ./pmdaweblog -C $tmp/conf > /dev/null 2>&1
+ then
+ cp $tmp/conf $configFile
+ else
+ echo "Warning: automatic conversion failed."
+ echo "You can either continue, and use the default configuration file or exit"
+ echo "this install procedure to manually update your existing configuration."
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to continue with the default configuration? [n] ""$PCP_ECHO_C"
+ read ans
+ if [ "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ configFile=""
+ else
+ exit 1
+ fi
+ fi
+ fi
+ else
+ echo "Using previous configuration file \"$configFile\""
+ fi
+ else
+ echo "Warning: previous configuration file \"$configFile\" no longer"
+ echo " exists, reverting to default"
+ configFile=""
+ fi
+ fi
+
+ if [ "X$configFile" = X -a -f $configDir/$iam.conf ]
+ then
+ configFile=$configDir/$iam.conf
+ echo "Using previous configuration file \"$configFile\""
+ fi
+
+ if [ "X$configFile" != X ]
+ then
+ if [ -f $configFile ]
+ then
+ echo "The inital configuration file contains the following Web server details:"
+ echo
+ cat $configFile | _parse_server | ${PAGER-more}
+ echo
+ echo "------------------------------------------------------------------------------"
+
+ if ./pmdaweblog -C $configFile >$tmp/out 2>&1
+ then
+ :
+ else
+ echo "Warning: parsing this configuration file produced the following errors,"
+ echo " and this file will be ignored."
+
+ cat $tmp/out
+ echo
+ if [ "X$configFile" = "X$tmp/default" ]
+ then
+ echo "Arrgh ... this is the default configuration, I cannot recover from here!"
+ exit 1
+ fi
+ configFile=""
+ fi
+ fi
+ fi
+
+ echo
+ echo "A configuration file can be automatically generated. This can"
+ echo "be used to compare or replace an existing configuration file."
+ echo
+
+ if [ "X$configFile" = X ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want a configuration file to be automatically generated [y] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = X ]
+ then
+ ans="y"
+ fi
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want a configuration file to be automatically generated [n] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = X ]
+ then
+ ans="n"
+ fi
+ fi
+
+ if [ "X$ans" = "Xy" -o "X$ans" = "XY" ]
+ then
+ echo
+ echo "Now scanning for Web servers ..."
+ echo
+
+ if [ ! -x ./server.sh ]
+ then
+ echo "Unable to scan for Web servers as ./server.sh is missing!"
+ else
+ rm -f $tmp/conf
+ touch $tmp/conf
+ _defaultRegex $tmp/conf
+ ./server.sh -l $tmp/conf
+ if egrep "^server" $tmp/conf > /dev/null 2>&1
+ then
+ echo
+ echo "This is a possible configuration file for your system:"
+ echo
+ cat $tmp/conf | _parse_server | ${PAGER-more}
+ echo
+ echo "------------------------------------------------------------------------------"
+ echo
+
+ if ./pmdaweblog -C $tmp/conf > /dev/null 2>&1
+ then
+ if [ "X$configFile" = X ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Would you like to use this configuration file [y] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = "Xy" -o "X$ans" = "XY" -o "X$ans" = X ]
+ then
+ cp $tmp/conf $configDir/$iam.conf
+ configFile=$configDir/$iam.conf
+ fi
+ else
+ echo "Would you like to replace your existing configuration file with"
+ $PCP_ECHO_PROG $PCP_ECHO_N "the generated file [n] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != "Xn" -a "X$and" != "XN" -a "X$ans" != X ]
+ then
+ cp $tmp/conf $configFile
+ fi
+ fi
+ else
+ echo "Automated configuration file generation is broken!"
+ if [ "X$configFile" = X ]
+ then
+ echo "Please consult the manual on how to create a configuration file."
+ echo "Installation failed."
+ exit 1
+ else
+ echo "Ignoring this file."
+ fi
+ fi
+ else
+ echo
+ echo "I could not find any Web servers."
+ fi
+ echo
+ fi
+ fi
+
+ echo "------------------------------------------------------------------------------"
+
+ echo
+ if [ "X$configFile" = X ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want to specify some Web servers [n]: ""$PCP_ECHO_C"
+ serverAdded="false"
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want to specify some more Web servers [n]: ""$PCP_ECHO_C"
+ serverAdded="true"
+ fi
+
+ read ans
+ while [ "X$ans" = "Xy" -o "X$ans" = "XY" ]
+ do
+ if [ "X$configFile" = X ]
+ then
+ if [ "X$configFile" = X -a -f $configDir/$iam.conf ]
+ then
+ echo "Replacing existing configuration file $configDir/$iam.conf"
+ rm -f $configDir/$iam.conf
+ else
+ echo "Creating configuration file $configDir/$iam.conf"
+ fi
+ _defaultRegex $configDir/$iam.conf
+ configFile="$configDir/$iam.conf"
+ fi
+
+ echo
+ serverName=`hostname`
+ $PCP_ECHO_PROG $PCP_ECHO_N "The name of the Web server [$serverName]: ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = X ]
+ then
+ serverName=`hostname`
+ else
+ serverName=$ans
+ fi
+
+ echo
+ accessPath=""
+ while [ "X$accessPath" = X ]
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "The path to the access log:
+ ""$PCP_ECHO_C"
+ read accessPath
+ if [ "X$accessPath" != X ]
+ then
+ if [ -f $accessPath ]
+ then
+ :
+ else
+ echo "$accessPath does not exist or is not a regular file"
+ accessPath=""
+ fi
+ fi
+ done
+
+ echo
+ errorPath=""
+ while [ "X$errorPath" = X ]
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "The path to the error log:
+ ""$PCP_ECHO_C"
+ read errorPath
+ if [ "X$errorPath" != X ]
+ then
+ if [ -f $errorPath ]
+ then
+ :
+ else
+ echo "$errorPath does not exist or is not a regular file"
+ errorPath=""
+ fi
+ fi
+ done
+
+ echo
+ echo "The configuration file contains these specifications:"
+ echo
+ ${PAGER-more} $configFile
+ echo
+ echo "Does the configuration file contain appropriate regular expressions"
+ $PCP_ECHO_PROG $PCP_ECHO_N "for the \"$serverName\" Web server [y]: ""$PCP_ECHO_C"
+ read ans
+ echo
+ if [ "X$ans" = "Xn" -o "X$ans" = "XN" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to quit the installation to add new regular expressions [y]: ""$PCP_ECHO_C"
+ read ans
+ if [ "$Xans" = "Xy" -o "X$ans" = "XY" -o "X$ans" = X ]
+ then
+ echo "Edit $configFile and then rerun this Install script."
+ exit 1
+ echo
+ echo "Skipping $serverName ..."
+ fi
+ else
+ accessRegex=""
+ while [ "X$accessRegex" = X ]
+ do
+ if egrep "^regex_posix CERN " $configFile > /dev/null 2>&1
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "The regex for the access log [CERN]: ""$PCP_ECHO_C"
+ accessRegex="CERN"
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "The regex for the access log: ""$PCP_ECHO_C"
+ accessRegex=""
+ fi
+ read ans
+ if [ "X$ans" != X ]
+ then
+ accessRegex=$ans
+ fi
+ if [ "X$accessRegex" != X ]
+ then
+ if egrep "^regex_posix $accessRegex " $configFile > /dev/null 2>&1
+ then
+ :
+ else
+ echo "Could not find $accessRegex in $configFile"
+ accessRegex=""
+ fi
+ fi
+ done
+
+ echo
+ errorRegex=""
+ while [ "X$errorRegex" = X ]
+ do
+ if egrep "^regex_posix CERN_err " $configFile > /dev/null 2>&1
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "The regex for the error log [CERN_err]: ""$PCP_ECHO_C"
+ errorRegex="CERN_err"
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "The regex for the error log: ""$PCP_ECHO_C"
+ errorRegex=""
+ fi
+ read ans
+ if [ "X$ans" != X ]
+ then
+ errorRegex=$ans
+ fi
+ if [ "X$errorRegex" != X ]
+ then
+ if egrep "^regex_posix $errorRegex " $configFile > /dev/null 2>&1
+ then
+ :
+ else
+ echo "Could not find $errorRegex in $configFile"
+ errorRegex=""
+ fi
+ fi
+ done
+
+ echo
+ echo "You have specified the following Web server:"
+ echo
+ server="server $serverName on $accessRegex $accessPath $errorRegex $errorPath"
+ echo "$server"
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Is this correct [y]:
+ ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" = "Xy" -o "X$ans" = "XY" -o "X$ans" = X ]
+ then
+ echo >> $configFile
+ echo "# User configured server called \"$serverName\"" >> $configFile
+ echo $server >> $configFile
+ serverAdded="true"
+ fi
+ fi
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to specify another Web Server [n]: ""$PCP_ECHO_C"
+ read ans
+ echo
+ done
+
+ if [ "$serverAdded" = "false" ]
+ then
+ rm -f $configFile
+ configFile=""
+ fi
+
+ if [ "X$configFile" = X ]
+ then
+ echo "Please consult the manual on how to create a configuration file."
+ echo "Installation failed as no servers were specified."
+ exit 1
+ fi
+
+ echo
+ echo "You may modify the configuration file by hand and add servers"
+ echo "that are not currently listed, change their names, etc."
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you wish to exit and modify the configuration file [n] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != "Xn" -a "X$ans" != "XN" -a "X$ans" != X ]
+ then
+ echo
+ echo "Edit $configFile and then rerun this Install script."
+ exit 1
+ fi
+
+ echo
+ echo "------------------------------------------------------------------------------"
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "The delay in seconds between forced reads of the log files [$delay] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != X ]
+ then
+ delay=$ans
+ fi
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Number of seconds of inactivity before checking for log rotation [$chkDelay] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != X ]
+ then
+ chkDelay=$ans
+ fi
+
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "The maximum number of servers per agent process [$maxserv] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != X ]
+ then
+ maxserv=$ans
+ fi
+
+ if [ "$do_debug" = true ]
+ then
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "the Debugging Flag (see pmdbg(1)) [$debugFlag] ""$PCP_ECHO_C"
+ read ans
+ if [ "X$ans" != X ]
+ then
+ debugFlag=$ans
+ fi
+ fi
+
+ args="-D $debugFlag -t $delay -n $chkDelay -S $maxserv $configFile"
+
+ echo
+ echo "------------------------------------------------------------------------------"
+ echo
+ fi
+fi
+
+pmdaInstall
+
+exit 0
+
diff --git a/src/pmdas/weblog/README b/src/pmdas/weblog/README
new file mode 100644
index 0000000..611a188
--- /dev/null
+++ b/src/pmdas/weblog/README
@@ -0,0 +1,205 @@
+Performance Co-Pilot Weblog PMDA for Monitoring of Web Server logs
+==================================================================
+
+This PMDA is capable of monitoring the activity of multiple Web servers,
+in terms of requests and bytes, in real time. The PMDA can also monitor
+proxy server, SOCKS server and ftpd logs.
+
+Site configuration is discussed in the online HTML documentation located
+at $PCP_DOC_DIR/pcpweb. This should be read before proceeding any further
+with installing this PMDA. The file $PCP_DOC_DIR/pcpweb/README contains
+instructions for installing this documentation.
+
+During the installation process, you may be prompted for several
+parameters which will affect the behavior of the weblog PMDA. These
+are discussed in the pmdaweblog(1) man page.
+
+
+Installation of the Weblog PMDA
+===============================
+
+1. Check that there is no clash with the Performance Metrics Domain
+ number defined in domain.h and the other PMDAs currently in use
+ (see $PCP_PMCDCONF_PATH). If there is, edit domain.h and choose
+ another domain number.
+
+2. Ensure that the web server control files can be correctly located as
+ follows.
+
+ Web Server Default Directory Environment Search for Config
+ Type Variable File(s) and/or Logs
+ Below the Default
+ Directory
+
+ Netscape /usr/ns-home $NSROOTPATH httpd-*/obj.conf
+ and httpd-*/magnus.conf
+ /var/netscape/suitespot https-*/obj.conf
+ https-*/magnus.conf
+ proxy-*/obj.conf
+ proxy-*/magnus.conf
+
+ Netscape /usr/ns-home $NSROOTPATH proxy-server/logs/sockd
+ Proxy
+
+ Netscape /var/ns-proxy $NSPROXYPATH logs/access
+ Proxy logs/errors
+ logs/sockd
+
+ Outbox /var/www/htdocs/outbox $OUTBOXPATH logs/access
+ logs/errors
+
+ NCSA /var/www $NCSAPATH server/logs/access_log
+ server/logs/error_log
+
+ Zeus /usr/local/zeus $ZEUSPATH server.ini
+ log/transfer
+ log/errors
+
+ Apache /usr/apache $APACHEPATH conf/httpd.conf
+ conf/srm.conf
+ log/access_log
+ log/error_log
+
+ Anon FTP /etc/passwd $PASSWDPATH [file, not dir] for ~ftp
+ /var/adm/SYSLOG $SYSLOGPATH [file, not dir] for
+ access and errors
+
+ To over-ride the Default Directory for a particular type of Web
+ server, set the corresponding Environment Variable to the absolute
+ pathname of the directory. As a special case $NSROOTPATH for the
+ non-proxy Netscape Web server can be set to a colon (:) separated
+ list of directory names to be searched (in the style of the $PATH
+ for /bin/sh).
+
+
+3. Then run the Install script (as root)
+
+ # cd $PCP_PMDAS_DIR/weblog
+ # ./Install
+
+4. The installation script will prompt if this is a collector and/or
+ monitor installation. Briefly:
+
+ o if there are Web servers on this host, then this is a collector host.
+
+ o if monitoring tools (pmchart(1), pmlogger(1) etc.) will be run on
+ this host, then this is a monitoring host.
+
+ Consult the HTML documentation for more details. A monitoring host
+ installation will install only the namespace and some application
+ configuration files.
+
+5. The next prompt will ask if this is a default installation. The
+ default installation will search for known Web server configurations
+ and install the PMDA to monitor any logs that are found. This is
+ appropriate for first time installations. The non-default
+ installation is described in points 6 to 8.
+
+6. The configuration file for the weblog PMDA must be found and
+ checked. The Install script will look in the likely places for an
+ existing file and prompt for confirmation. Otherwise, a
+ configuration file can be automatically generated by searching known
+ Web server configuration files and directories.
+
+7. The second stage of the Install script prompts for the pmdaweblog(1)
+ parameters. The default values should be adequate for an initial
+ installation.
+
+8. The final stage will install the agent and restart PMCD (the
+ Performance Metrics Collection Daemon). The Install script should
+ report that the Metrics are OK.
+
+
+De-installation
+===============
+
+Simply use (as root)
+
+ # cd $PCP_PMDAS_DIR/weblog
+ # ./Remove
+
+
+Changing the settings
+=====================
+
+The safest way to alter any settings that were entered in the Install
+script is to re-run the Install script. Changes to the weblog.conf file
+can be also be registered by running the Install script.
+
+To quickly test changes to the configuration files, the agent and pmcd
+can be restarted as follows:
+
+ To register any changes made to the weblog.conf file, the agent
+ must be killed and restarted:
+
+ # pmsignal -a -s KILL pmdaweblog
+ # pmsignal -a -s HUP pmcd
+
+ To register any changes to the $PCP_PMCDCONF_PATH file you must
+ restart PMCD:
+
+ # $PCP_RC_DIR/pcp start
+
+
+Troubleshooting
+===============
+
+0. If there is trouble locating the Web server access and error logs,
+ try running the server.sh script with diagnostics:
+
+ $ cd $PCP_PMDAS_DIR/weblog
+ $ ./server.sh -q -v </dev/null
+
+1. After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/weblog.log) should be checked for any warnings
+ or errors.
+
+2. If the Install script reports some warnings when checking the
+ metrics, the problem should be listed in one of the log files.
+
+3. If the PMDA is configured to look at the correct access logs, and
+ the Web server is demonstrably updating those access logs, but the
+ exported performance metrics are not being updated, then the problem
+ may be in the pattern matching. To diagnose this:
+
+ Find the corresponding "server" line in
+ $PCP_VAR_DIR/config/web/weblog.conf, e.g.
+
+ server ha2.melbourne.sgi.com:80 on \
+ CERN /usr/ns-home/httpd-ha2/logs/access \
+ CERN_err /usr/ns-home/httpd-ha2/logs/errors
+
+ the pattern is symbolicly named after the word "on" (CERN above) and
+ the path to the access log follows
+ (/usr/ns-home/httpd-ha2/logs/access above). These two are used as
+ the last two arguments to check_match below:
+
+ $ cd $PCP_PMDAS_DIR/weblog
+ $ ./check_match $PCP_VAR_DIR/config/web/weblog.conf \
+ CERN /usr/ns-home/httpd-ha2/logs/access
+
+ If things are working OK, expect to see lines like:
+
+ [1] match: method="GET" size="17198"
+ [2] match: method="GET" size="-"
+ [3] match: method="GET" size="27102"
+ [4] match: method="POST" size="4503"
+ [5] match: method="HEAD" size="-"
+
+ If this does not happen, you need to review the format of the lines
+ in the access logs and modify the pattern by reference to the
+ regcmp(3) man page.
+
+4. Additional information can be logged if there appears to be problems
+ with the monitoring of server log files. Running the Install script
+ with the -D flag will add a prompt for a debugging flag. This can be
+ a combination of bits given by pmdbg -l:
+
+ # pmdbg -l
+
+ The application flags will cause the PMDA to report additional
+ information in $PCP_LOG_DIR/pmcd/weblog.log. DBG_TRACE_APPL0 reports
+ the least information and DBG_TRACE_APPL2 may report too much if the
+ server is handling many requests.
+
diff --git a/src/pmdas/weblog/Remove b/src/pmdas/weblog/Remove
new file mode 100644
index 0000000..50beaa2
--- /dev/null
+++ b/src/pmdas/weblog/Remove
@@ -0,0 +1,43 @@
+#! /bin/sh
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Remove the weblog PMDA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Get the common procedures and variable assignments
+#
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+# The name of the PMDA
+#
+iam=weblog
+
+# Do it
+#
+#_setup
+pmdaSetup
+
+pmns_name=web # metric names differ from PMDA name
+
+#_remove
+pmdaRemove
+
+exit 0
diff --git a/src/pmdas/weblog/Web.Alarms.pmchart b/src/pmdas/weblog/Web.Alarms.pmchart
new file mode 100644
index 0000000..d346f80
--- /dev/null
+++ b/src/pmdas/weblog/Web.Alarms.pmchart
@@ -0,0 +1,22 @@
+#pmchart
+#
+# Web statistics (error rates)
+#
+# This file is installed by the script $PCP_PMDAS_DIR/weblog/Install
+#
+Version 2.0 host dynamic
+
+Chart Title "Web Alarms" Style stacking
+ Plot Color #-cycle Host * Metric web.allservers.errors
+ Plot Color #-cycle Host * Metric network.tcp.drops
+ Plot Color #-cycle Host * Metric network.tcp.conndrops
+ Plot Color #-cycle Host * Metric network.tcp.timeoutdrop
+ Plot Color #-cycle Host * Metric network.tcp.sndrexmitpack
+ Plot Color #-cycle Host * Metric network.tcp.rcvbadsum
+ Plot Color #-cycle Host * Metric network.tcp.rexmttimeo
+ Plot Color #-cycle Host * Metric network.mbuf.failed
+ Plot Color #-cycle Host * Metric network.mbuf.waited
+ Plot Color #-cycle Host * Metric swap.pagesout
+
+#
+# Created Thu Jul 2 10:48:36 1998
diff --git a/src/pmdas/weblog/Web.Allservers.pmchart b/src/pmdas/weblog/Web.Allservers.pmchart
new file mode 100755
index 0000000..4185e1b
--- /dev/null
+++ b/src/pmdas/weblog/Web.Allservers.pmchart
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+echo "/\"/s///g" >$tmp/sed
+
+pmprobe -I $* web.perserver.bytes.cached.total web.perserver.bytes.total > $tmp/pmprobe
+l1=`head -n 1 $tmp/pmprobe`
+l2=`tail -n 1 $tmp/pmprobe`
+
+num_caches=`echo $l1 | cut -f2 -d\ `
+num_servers=`echo $l2 | cut -f2 -d\ `
+if [ $num_servers -gt 0 ]
+then
+ caches=`echo $l1 | cut -f3- -d\ `
+ servers=`echo $l2 | cut -f3- -d\ `
+# hostname=`echo $servers | cut -f1 -d: | sed -f $tmp/sed`
+
+ if [ $num_caches -le 0 ]
+ then
+ # an old pmda - quietly handle all servers as if they were CERN - show only totals
+ caches="NeVeR_MaTcH"
+ num_caches=0
+ fi
+elif [ $num_servers -eq 0 ]
+then
+ $PCP_XCONFIRM_PROG -c -B OK -header "No Active Servers - cannot continue" \
+ -t "$message" \
+ -icon info > /dev/null
+ exit
+else
+ message=`pmerr $num_servers | cut -f5- -d\ `
+ $PCP_XCONFIRM_PROG -c -B OK -header "Fatal error - cannot continue" \
+ -t "$message" \
+ -icon error > /dev/null
+ exit
+fi
+
+#
+# if too many instances, turn off all legends
+#
+legendp=on
+if [ $num_servers -gt 6 ]
+then
+ legendp=off
+fi
+
+if [ $num_servers -gt 12 ]
+then
+ $PCP_XCONFIRM_PROG -c -B Cancel -b Continue -header \
+ "Too many charts" \
+ -t "There is 1 chart per server, more than can reasonably be displayed on the screen" \
+ -icon warning | grep Cancel >/dev/null
+if [ $? -eq 0 ]
+then
+ exit
+fi
+fi
+
+# chart preamble
+#
+cat > $tmp/base <<End-of-File
+#pmchart
+Version 2.0 host dynamic
+
+End-of-File
+
+if [ $num_caches -ne $num_servers ]
+then
+ echo Chart Title \"Total Requests serviced by all servers \" Style bars Legend off>> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.allservers.requests.total >> $tmp/base
+ echo Chart Title \"Total Bytes sent by all servers \" Style bars Legend off>> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.allservers.bytes.total >> $tmp/base
+fi
+if [ $num_caches -gt 0 ]
+then
+ echo Chart Title \"Total Requests serviced by caching servers \" Style stacking Legend $legendp >> $tmp/base
+ echo Plot Color \#FFFF30 Host \* Metric web.allservers.requests.client.total >> $tmp/base
+ echo Plot Color \#3030FF Host \* Metric web.allservers.requests.cached.total >> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.allservers.requests.uncached.total >> $tmp/base
+ echo Chart Title \"Total Bytes sent by caching servers \" Style stacking Legend $legendp >> $tmp/base
+ echo Plot Color \#3030FF Host \* Metric web.allservers.bytes.cached.total >> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.allservers.bytes.uncached.total >> $tmp/base
+fi
+
+cat $tmp/base
+rm -rf $tmp
diff --git a/src/pmdas/weblog/Web.Perserver.Bytes.pmchart b/src/pmdas/weblog/Web.Perserver.Bytes.pmchart
new file mode 100755
index 0000000..1f57a27
--- /dev/null
+++ b/src/pmdas/weblog/Web.Perserver.Bytes.pmchart
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+echo "/\"/s///g" >$tmp/sed
+
+pmprobe -I $* web.perserver.bytes.cached.total web.perserver.bytes.total > $tmp/pmprobe
+l1=`head -n 1 $tmp/pmprobe`
+l2=`tail -n 1 $tmp/pmprobe`
+
+num_caches=`echo $l1 | cut -f2 -d\ `
+num_servers=`echo $l2 | cut -f2 -d\ `
+if [ $num_servers -gt 0 ]
+then
+ caches=`echo $l1 | cut -f3- -d\ `
+ servers=`echo $l2 | cut -f3- -d\ `
+
+ if [ $num_caches -lt 0 ]
+ then
+ # an old pmda - quietly handle all servers as if they were CERN - show only totals
+ caches="NeVeR_MaTcH"
+ num_caches=0
+ fi
+elif [ $num_servers -eq 0 ]
+then
+ $PCP_XCONFIRM_PROG -c -B OK -header "No Active Servers - cannot continue" \
+ -t "$message" \
+ -icon info > /dev/null
+ exit
+else
+ message=`pmerr $num_servers | cut -f5- -d\ `
+ $PCP_XCONFIRM_PROG -c -B OK -header "Fatal error - cannot continue" \
+ -t "$message" \
+ -icon error > /dev/null
+ exit
+fi
+
+#
+# if too many instances, turn off all legends
+#
+legendp=on
+if [ $num_servers -gt 6 ]
+then
+ legendp=off
+fi
+
+if [ $num_servers -gt 12 ]
+then
+ $PCP_XCONFIRM_PROG -c -B Cancel -b Continue -header \
+ "Too many charts" \
+ -t "There is 1 chart per server, more than can reasonably be displayed on the screen" \
+ -icon warning | grep Cancel >/dev/null
+if [ $? -eq 0 ]
+then
+ exit
+fi
+fi
+
+# chart preamble
+#
+cat > $tmp/base <<End-of-File
+#pmchart
+Version 2.0 host dynamic
+
+End-of-File
+
+i=1
+while [ $i -le $num_servers ]
+do
+server=`echo $servers | cut -f$i -d\ `
+echo $caches | grep $server >/dev/null
+if [ $? -eq 0 ]
+then
+ j=`echo $server | sed -f $tmp/sed`
+ echo Chart Title \"Bytes sent by $j\" Style stacking Legend $legendp >> $tmp/base
+ echo Plot Color \#3030FF Host \* Metric web.perserver.bytes.cached.total Instance $j >> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.perserver.bytes.uncached.total Instance $j >> $tmp/base
+else
+ j=`echo $server | sed -f $tmp/sed`
+ echo Chart Title \"Total Bytes sent by $j\" Style bars Legend off>> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.perserver.bytes.total Instance $j >> $tmp/base
+fi
+i=`expr $i + 1`
+done
+
+cat $tmp/base
+rm -rf $tmp
diff --git a/src/pmdas/weblog/Web.Perserver.Requests.pmchart b/src/pmdas/weblog/Web.Perserver.Requests.pmchart
new file mode 100755
index 0000000..77ff366
--- /dev/null
+++ b/src/pmdas/weblog/Web.Perserver.Requests.pmchart
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+echo "/\"/s///g" >$tmp/sed
+
+pmprobe -I $* web.perserver.bytes.cached.total web.perserver.bytes.total > $tmp/pmprobe
+l1=`head -n 1 $tmp/pmprobe`
+l2=`tail -n 1 $tmp/pmprobe`
+
+num_caches=`echo $l1 | cut -f2 -d\ `
+num_servers=`echo $l2 | cut -f2 -d\ `
+if [ $num_servers -gt 0 ]
+then
+ caches=`echo $l1 | cut -f3- -d\ `
+ servers=`echo $l2 | cut -f3- -d\ `
+
+ if [ $num_caches -lt 0 ]
+ then
+ # an old pmda - quietly handle all servers as if they were CERN - show only totals
+ caches="NeVeR_MaTcH"
+ num_caches=0
+ fi
+elif [ $num_servers -eq 0 ]
+then
+ $PCP_XCONFIRM_PROG -c -B OK -header "No Active Servers - cannot continue" \
+ -t "$message" \
+ -icon info > /dev/null
+ exit
+else
+ message=`pmerr $num_servers | cut -f5- -d\ `
+ $PCP_XCONFIRM_PROG -c -B OK -header "Fatal error - cannot continue" \
+ -t "$message" \
+ -icon error > /dev/null
+ exit
+fi
+
+#
+# if too many instances, turn off all legends
+#
+legendp=on
+if [ $num_servers -gt 6 ]
+then
+ legendp=off
+fi
+
+if [ $num_servers -gt 12 ]
+then
+ $PCP_XCONFIRM_PROG -c -B Cancel -b Continue -header \
+ "Too many charts" \
+ -t "There is 1 chart per server, more than can reasonably be displayed on the screen" \
+ -icon warning | grep Cancel >/dev/null
+if [ $? -eq 0 ]
+then
+ exit
+fi
+fi
+
+# chart preamble
+#
+cat > $tmp/base <<End-of-File
+#pmchart
+Version 2.0 host dynamic
+
+End-of-File
+
+i=1
+while [ $i -le $num_servers ]
+do
+server=`echo $servers | cut -f$i -d\ `
+echo $caches | grep $server >/dev/null
+if [ $? -eq 0 ]
+then
+ j=`echo $server | sed -f $tmp/sed`
+ echo Chart Title \"Requests satisfied by $j\" Style stacking Legend $legendp >> $tmp/base
+ echo Plot Color \#FFFF30 Host \* Metric web.perserver.requests.client.total Instance $j >> $tmp/base
+ echo Plot Color \#3030FF Host \* Metric web.perserver.requests.cached.total Instance $j >> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.perserver.requests.uncached.total Instance $j >> $tmp/base
+else
+ j=`echo $server | sed -f $tmp/sed`
+ echo Chart Title \"Total Requests satisfied by $j\" Style bars Legend off>> $tmp/base
+ echo Plot Color \#FF3030 Host \* Metric web.perserver.requests.total Instance $j >> $tmp/base
+fi
+i=`expr $i + 1`
+done
+
+cat $tmp/base
+rm -rf $tmp
diff --git a/src/pmdas/weblog/Web.Requests.pmchart b/src/pmdas/weblog/Web.Requests.pmchart
new file mode 100644
index 0000000..7775fd5
--- /dev/null
+++ b/src/pmdas/weblog/Web.Requests.pmchart
@@ -0,0 +1,27 @@
+#pmchart
+#
+# Web statistics (request rates)
+#
+# This file is installed by the script $PCP_PMDAS_DIR/weblog/Install
+#
+Version 2.0 host dynamic
+
+Chart Title "Requests by HTTP method" Style stacking
+ Plot Color rgbi:1.0/1.0/0.0 Host * Metric web.allservers.requests.get
+ Plot Color rgbi:0.0/1.0/1.0 Host * Metric web.allservers.requests.post
+ Plot Color rgbi:1.0/0.0/1.0 Host * Metric web.allservers.requests.head
+ Plot Color rgbi:1.0/1.0/0.6 Host * Metric web.allservers.requests.other
+Chart Title "Requests by request size" Style stacking
+ Plot Color rgbi:1.0/0.8/0.6 Host * Metric web.allservers.requests.size.zero
+ Plot Color rgbi:0.6/1.0/0.6 Host * Metric web.allservers.requests.size.le3k
+ Plot Color rgbi:0.8/0.6/1.0 Host * Metric web.allservers.requests.size.le10k
+ Plot Color rgbi:1.0/0.65/0.3 Host * Metric web.allservers.requests.size.le30k
+ Plot Color rgbi:0.3/1.0/0.3 Host * Metric web.allservers.requests.size.le100k
+ Plot Color rgbi:0.65/0.3/1.0 Host * Metric web.allservers.requests.size.le300k
+ Plot Color rgbi:1.0/0.5/0.0 Host * Metric web.allservers.requests.size.le1m
+ Plot Color rgbi:0.0/1.0/0.0 Host * Metric web.allservers.requests.size.le3m
+ Plot Color rgbi:0.6/0.0/0.9 Host * Metric web.allservers.requests.size.gt3m
+ Plot Color rgbi:1.0/0.35/0.0 Host * Metric web.allservers.requests.size.unknown
+
+#
+# Created Thu Jul 2 10:48:19 1998
diff --git a/src/pmdas/weblog/Web.Volume.pmchart b/src/pmdas/weblog/Web.Volume.pmchart
new file mode 100644
index 0000000..569abd9
--- /dev/null
+++ b/src/pmdas/weblog/Web.Volume.pmchart
@@ -0,0 +1,25 @@
+#pmchart
+#
+# Web Statistics (data volume)
+#
+# This file is installed by the script $PCP_PMDAS_DIR/weblog/Install
+#
+Version 2.0 host dynamic
+
+Chart Title "Bytes sent by HTTP method" Style stacking
+ Plot Color rgbi:1.0/1.0/0.0 Host * Metric web.allservers.bytes.get
+ Plot Color rgbi:0.0/1.0/1.0 Host * Metric web.allservers.bytes.post
+ Plot Color rgbi:1.0/0.0/1.0 Host * Metric web.allservers.bytes.head
+ Plot Color rgbi:1.0/1.0/0.6 Host * Metric web.allservers.bytes.other
+Chart Title "Bytes sent by request size" Style stacking
+ Plot Color rgbi:0.6/1.0/0.6 Host * Metric web.allservers.bytes.size.le3k
+ Plot Color rgbi:0.8/0.6/1.0 Host * Metric web.allservers.bytes.size.le10k
+ Plot Color rgbi:1.0/0.65/0.3 Host * Metric web.allservers.bytes.size.le30k
+ Plot Color rgbi:0.3/1.0/0.3 Host * Metric web.allservers.bytes.size.le100k
+ Plot Color rgbi:0.65/0.3/1.0 Host * Metric web.allservers.bytes.size.le300k
+ Plot Color rgbi:1.0/0.5/0.0 Host * Metric web.allservers.bytes.size.le1m
+ Plot Color rgbi:0.0/1.0/0.0 Host * Metric web.allservers.bytes.size.le3m
+ Plot Color rgbi:0.6/0.0/0.9 Host * Metric web.allservers.bytes.size.gt3m
+
+#
+# Created Thu Jul 2 10:47:51 1998
diff --git a/src/pmdas/weblog/check_match.c b/src/pmdas/weblog/check_match.c
new file mode 100644
index 0000000..5166e75
--- /dev/null
+++ b/src/pmdas/weblog/check_match.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright (c) 2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * Uses the same regular expression logic as pmdaweblog, but extracted
+ * here so new patterns and access logs can be tested
+ *
+ * Usage: check_match configfile pat_name [input]
+ * configfile regex spec file as used by pmdaweblog
+ * pat_name use only this names regex from configfile
+ * input test input to try and match, defaults to stdin
+ */
+
+#include <ctype.h>
+#include <pmapi.h>
+#if defined(HAVE_REGEX_H)
+#include <regex.h>
+#endif
+#include <sys/types.h>
+
+int
+main(int argc, char *argv[])
+{
+ FILE *fc;
+ char buf[1024];
+ char *p;
+ char *q;
+#ifdef HAVE_REGEX
+ char *comp = NULL;
+ char sub0[1024];
+ char sub1[1024];
+ char sub2[1024];
+ char sub3[1024];
+#endif
+ int lno = 0;
+ int regex_posix = 0;
+ int cern_format = 0;
+ int common_extended_format = 0;
+ int squid_format = 0;
+ int methodpos = 1, c_statuspos = 2, sizepos = 2, s_statuspos = 2;
+ long client_cache_hits, proxy_cache_hits, remote_fetches;
+ double proxy_bytes, remote_bytes;
+#if (defined HAVE_REGEXEC) && (defined HAVE_REGCOMP)
+ regex_t re = {0};
+ regmatch_t pmatch[5];
+ size_t nmatch = 5;
+#endif
+
+
+ if (argc < 3 || argc > 4) {
+ fprintf(stderr, "Usage: check_match configfile pat_name [input]\n");
+ exit(1);
+ }
+
+ if ((fc = fopen(argv[1], "r")) == NULL) {
+ fprintf(stderr, "check_match: cannot open configfile \"%s\": %s\n", argv[1], osstrerror());
+ exit(1);
+ }
+
+ if (argc == 4) {
+ if (freopen(argv[3], "r", stdin) == NULL) {
+ fprintf(stderr, "check_match: cannot open input \"%s\": %s\n", argv[3], osstrerror());
+ exit(1);
+ }
+ }
+
+ while (fgets(buf, sizeof(buf), fc) != NULL) {
+ lno++;
+
+ if (strncmp(buf, "regex", 5) != 0) continue;
+ if (strncmp(buf, "regex_posix", 11) == 0) {
+ regex_posix = 1;
+ p = &buf[11];
+ }
+ else {
+ regex_posix = 0;
+ p = &buf[5];
+ }
+
+ while (*p && isspace((int)*p)) p++;
+ if (*p == '\0') continue;
+ q = p++;
+ while (*p && !isspace((int)*p)) p++;
+ if (*p == '\0') continue;
+ *p = '\0';
+
+ cern_format = squid_format = common_extended_format = 0;
+
+ if (strcmp(q, argv[2]) == 0) {
+ if(regex_posix) {
+
+ q = ++p;
+ while (*p && !isspace((int)*p)) p++;
+ if (*p == '\0') continue;
+ *p = '\0';
+ fprintf(stderr, "args are (%s)\n", q);
+ if(strncmp(q, "method,size", 11) == 0) {
+ cern_format = 1;
+ methodpos = 1;
+ sizepos = 2;
+ }
+ else if(strncmp(q, "size,method", 11) == 0) {
+ methodpos = 2;
+ sizepos = 1;
+ }
+ else {
+ char *str;
+ int pos;
+
+ pos = 1;
+ str=q;
+ do {
+ switch(str[0]) {
+ case '1':
+ methodpos = pos++;
+ break;
+ case '2':
+ sizepos = pos++;
+ break;
+ case '3':
+ c_statuspos = pos++;
+ break;
+ case '4':
+ s_statuspos = pos++;
+ break;
+ case '-':
+ methodpos = 1;
+ sizepos = 2;
+ str[0] = '\0';
+ break;
+ case ',':
+ case '\0':
+ break;
+ default:
+ fprintf(stderr,
+ "could figure out arg order params (%s)\n",
+ str);
+ exit(1);
+ }
+ } while ( *str++ );
+
+ if(c_statuspos > 0 && s_statuspos > 0) {
+ if(strcmp(argv[2], "SQUID") == 0)
+ squid_format = 1;
+ else
+ common_extended_format = 1;
+ } else
+ cern_format = 1;
+ }
+
+ fprintf(stderr, "cern: %d, cef: %d, squid: %d, MP: %d, SP: %d, CSP: %d, SSP: %d\n",
+ cern_format, common_extended_format, squid_format,
+ methodpos, sizepos, c_statuspos, s_statuspos);
+ }
+
+ q = ++p;
+ while (*p && *p != '\n') p++;
+ while (p >= q && isspace((int)*p)) p--;
+ p[1] = '\0';
+ if(regex_posix) {
+#ifdef HAVE_REGCOMP
+ fprintf(stderr, "%s[%d]: regex_posix: %s\n", argv[1], lno, q);
+ fclose(fc);
+ if(regcomp(&re, q, REG_EXTENDED) != 0 ) {
+ fprintf(stderr, "Error: bad regular expression\n");
+ exit(1);
+ }
+#else
+ fprintf(stderr, "%s[%d]: no support for POSIX regexp\n",
+ argv[1], lno);
+#endif
+ }
+ else {
+#ifdef HAVE_REGCMP
+ if(strcmp(argv[2], "CERN") == 0)
+ cern_format = 1;
+ else if (strcmp(argv[2], "NS_PROXY") == 0)
+ common_extended_format = 1;
+ else if (strcmp(argv[2], "SQUID") == 0)
+ squid_format = 1;
+
+ fprintf(stderr, "%s[%d]: regex: %s\n", argv[1], lno, q);
+ fclose(fc);
+ comp = regcmp(q, NULL);
+ if (comp == NULL) {
+ fprintf(stderr, "Error: bad regular expression\n");
+ exit(1);
+ }
+#else
+ fprintf(stderr, "%s[%d]: regcmp is not available\n",
+ argv[1], lno);
+#endif
+ }
+ break;
+ }
+ }
+
+ lno = 0;
+ remote_fetches = proxy_cache_hits = client_cache_hits = 0;
+ remote_bytes = proxy_bytes = 0.0;
+ while (fgets(buf, sizeof(buf), stdin) != NULL) {
+ lno++;
+ if(regex_posix) {
+#ifdef HAVE_REGEXEC
+ if(regexec(&re, buf, nmatch, pmatch, 0) == 0) {
+ buf[pmatch[methodpos].rm_eo] = '\0';
+ buf[pmatch[sizepos].rm_eo] = '\0';
+ if(common_extended_format || squid_format) {
+ buf[pmatch[c_statuspos].rm_eo] = '\0';
+ buf[pmatch[s_statuspos].rm_eo] = '\0';
+ }
+
+ if(common_extended_format) {
+ fprintf(stderr,"[%d] M: %s, S: %s, CS: %s, SS: %s\n",
+ lno,
+ &buf[pmatch[methodpos].rm_so],
+ &buf[pmatch[sizepos].rm_so],
+ &buf[pmatch[c_statuspos].rm_so],
+ &buf[pmatch[s_statuspos].rm_so]);
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "200") == 0 &&
+ strcmp(&buf[pmatch[s_statuspos].rm_so], "200") == 0) {
+ fprintf(stderr,"\tREMOTE fetch of %.0f bytes\n",
+ atof(&buf[pmatch[sizepos].rm_so]));
+ remote_fetches++;
+ remote_bytes += atof(&buf[pmatch[sizepos].rm_so]);
+ }
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "200") == 0 &&
+ (strcmp(&buf[pmatch[s_statuspos].rm_so], "304") == 0 ||
+ strcmp(&buf[pmatch[s_statuspos].rm_so], "-") == 0)) {
+ fprintf(stderr,"\tCACHE return of %.0f bytes\n",
+ atof(&buf[pmatch[sizepos].rm_so]));
+ proxy_cache_hits++;
+ proxy_bytes += atof(&buf[pmatch[sizepos].rm_so]);
+
+ }
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "304") == 0 &&
+ (strcmp(&buf[pmatch[s_statuspos].rm_so], "304") == 0 ||
+ strcmp(&buf[pmatch[s_statuspos].rm_so], "-") == 0)) {
+ fprintf(stderr,"\tCLIENT hit of %.0f bytes\n",
+ atof(&buf[pmatch[sizepos].rm_so]));
+ client_cache_hits++;
+ }
+ } else if(squid_format) {
+ fprintf(stderr,"[%d] M: %s, S: %s, CS: %s, SS: %s\n",
+ lno,
+ &buf[pmatch[methodpos].rm_so],
+ &buf[pmatch[sizepos].rm_so],
+ &buf[pmatch[c_statuspos].rm_so],
+ &buf[pmatch[s_statuspos].rm_so]);
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "200") == 0 &&
+ (strstr(&buf[pmatch[s_statuspos].rm_so],
+ "_MISS")!=NULL ||
+ strstr(&buf[pmatch[s_statuspos].rm_so],
+ "_CLIENT_REFRESH")!=NULL ||
+ strstr(&buf[pmatch[s_statuspos].rm_so],
+ "_SWAPFAIL")!=NULL)){
+ fprintf(stderr,"\tREMOTE fetch of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(&buf[pmatch[sizepos].rm_so]),
+ &buf[pmatch[c_statuspos].rm_so], &buf[pmatch[s_statuspos].rm_so]);
+ remote_fetches++;
+ remote_bytes += atof(&buf[pmatch[sizepos].rm_so]);
+ }
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "200") == 0 &&
+ strstr(&buf[pmatch[s_statuspos].rm_so], "_HIT") != NULL) {
+ fprintf(stderr,"\tCACHE return of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(&buf[pmatch[sizepos].rm_so]),
+ &buf[pmatch[c_statuspos].rm_so], &buf[pmatch[s_statuspos].rm_so]);
+ proxy_cache_hits++;
+ proxy_bytes += atof(&buf[pmatch[sizepos].rm_so]);
+
+ }
+ if(strcmp(&buf[pmatch[c_statuspos].rm_so], "304") == 0 &&
+ strstr(&buf[pmatch[s_statuspos].rm_so], "_HIT") != NULL) {
+ fprintf(stderr,"\tCLIENT hit of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(&buf[pmatch[sizepos].rm_so]),
+ &buf[pmatch[c_statuspos].rm_so], &buf[pmatch[s_statuspos].rm_so]);
+ client_cache_hits++;
+ }
+ } else {
+ fprintf(stderr, "[%d] match: method=\"%s\" size=\"%s\"\n", lno,
+ &buf[pmatch[methodpos].rm_so], &buf[pmatch[sizepos].rm_so]);
+ }
+ }
+ else
+ fprintf(stderr, "[%d] no match: %s\n", lno, buf);
+#else
+ fprintf(stderr, "[%d] - no regexec()\n", lno);
+#endif
+ }
+ else {
+#ifdef HAVE_REGEX
+ if (regex(comp, buf, sub0, sub1, sub2, sub3) != NULL) {
+ if(common_extended_format) {
+
+ fprintf(stderr,"[%d] M: %s, S: %s, CS: %s, SS: %s\n",
+ lno, sub0, sub1, sub2, sub3);
+
+ if(strcmp(sub2, "200") == 0 &&
+ strcmp(sub3, "200") == 0 ) {
+ fprintf(stderr,"\tREMOTE fetch of %s bytes\n", sub1);
+ remote_fetches++;
+ remote_bytes += atof(sub1);
+ }
+ if(strcmp(sub2, "200") == 0 &&
+ (strcmp(sub3, "304") == 0 || strcmp(sub3, "-") == 0)) {
+ fprintf(stderr,"\tCACHE return of %s bytes\n", sub1);
+ proxy_cache_hits++;
+ proxy_bytes += atof(sub1);
+ }
+ if(strcmp(sub2, "304") == 0 &&
+ (strcmp(sub3, "304") == 0 || strcmp(sub3, "-") == 0)) {
+ fprintf(stderr,"\tCLIENT hit of %s bytes\n", sub1);
+ client_cache_hits++;
+ }
+ } else if(squid_format) {
+
+ fprintf(stderr,"[%d] M: %s, S: %s, CS: %s, SS: %s\n",
+ lno, sub0, sub1, sub2, sub3);
+
+ if(strcmp(sub2, "200") == 0 &&
+ (strstr(sub3, "_MISS") != NULL ||
+ strstr(sub3, "_CLIENT_REFRESH")!= NULL ||
+ strstr(sub3, "_SWAPFAIL") != NULL)){
+
+ fprintf(stderr,"\tREMOTE fetch of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(sub1),
+ sub2, sub3);
+
+ remote_fetches++;
+ remote_bytes += atof(sub1);
+ }
+ if(strcmp(sub2, "200") == 0 &&
+ strstr(sub3, "_HIT") != NULL) {
+
+ fprintf(stderr,"\tCACHE return of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(sub1),
+ sub2, sub3);
+
+ proxy_cache_hits++;
+ proxy_bytes += atof(sub1);
+
+ }
+ if(strcmp(sub2, "304") == 0 &&
+ strstr(sub3, "_HIT") != NULL) {
+
+ fprintf(stderr,"\tCLIENT hit of %.0f bytes (code: %s, Squid result code: %s)\n",
+ atof(sub3),
+ sub2, sub3);
+
+ client_cache_hits++;
+ }
+ } else {
+ fprintf(stderr, "[%d] match: method=\"%s\" size=\"%s\"\n", lno,
+ sub0, sub1);
+ }
+ }
+ else
+ fprintf(stderr, "[%d] no match: %s\n", lno, buf);
+#else
+ fprintf(stderr, "[%d] - no regex()\n", lno);
+#endif
+ }
+ }
+
+ if(common_extended_format || squid_format) {
+ fprintf(stderr,"Proxy Cache Summary Report\n\n");
+
+ fprintf(stderr,
+ "# requests %ld\n# client cache hits %ld\n# cache hits %ld\n# remote fetches %ld\n",
+ (client_cache_hits + proxy_cache_hits + remote_fetches),
+ client_cache_hits, proxy_cache_hits, remote_fetches);
+ fprintf(stderr,
+ "\nTotal Mbytes %f bytes\nFrom proxy cache %f Mbytes\nFrom remote sites %f Mbytes\n\n",
+ (proxy_bytes + remote_bytes)/1000000.0,
+ proxy_bytes/1000000.0, remote_bytes/1000000.0);
+
+ fprintf(stderr,
+ "Client Cache %% hit rate: %.2f\n",
+ 100.0*(float)client_cache_hits/(float)(client_cache_hits + proxy_cache_hits + remote_fetches));
+ fprintf(stderr,
+ "Proxy Cache %% hit rate: %.2f\n",
+ 100.0*(float)proxy_cache_hits/(float)(client_cache_hits + proxy_cache_hits + remote_fetches));
+ fprintf(stderr,
+ "Local Cache %% hit rate: %.2f\n",
+ 100.0*(float)(client_cache_hits + proxy_cache_hits)/
+ (float)(client_cache_hits + proxy_cache_hits + remote_fetches));
+
+ fprintf(stderr,
+ "\nAverage fetch size: Proxy -> Client: %.2f Kb\n",
+ proxy_bytes/proxy_cache_hits/1000.0);
+ fprintf(stderr,
+ "Average fetch size: Remote -> Client : %.2f Kb\n",
+ remote_bytes/remote_fetches/1000.0);
+
+ fprintf(stderr,"\nClient Cache bandwidth reduction effectiveness: UNKNOWN\n");
+ fprintf(stderr,
+ "Proxy Cache bandwidth reduction effectiveness: %f%%\n",
+ 100.0*proxy_bytes/(proxy_bytes + remote_bytes));
+
+ }
+
+ exit(0);
+}
diff --git a/src/pmdas/weblog/help b/src/pmdas/weblog/help
new file mode 100644
index 0000000..3a523e6
--- /dev/null
+++ b/src/pmdas/weblog/help
@@ -0,0 +1,654 @@
+#
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+@ web.config.numservers number of servers in configuration file
+The number of Web servers specified in the configuration file.
+The log files for these Web servers may or may not be monitored,
+see web.perserver.watched and web.allservers.numwatched.
+
+@ web.config.catchup maximum time (secs) before Web server logs are probed
+The time in seconds after which monitored Web server logs will be
+examined, even if there have been no requests for performance metrics
+from those logs. The "catch up" process spreads the load and minimizes
+the latency at the first requests for metrics that have not been
+requested for a long time.
+
+This metric has the initial value of the -t delay option to
+pmdaweblog, and may be altered using pmStore(1).
+
+@ web.config.catchuptime time (secs) to perform catchup
+Accumulated elapsed time in which the Web logs PMDA has been performing
+the "catch up" process to examine all Web server logs.
+
+@ web.config.check time (secs) after which stationary logs will be re-opened
+Web server log files that are not changing are periodically closed and
+re-opened to detect possible log file rotation. This metric controls
+how often a stationary log file will be re-opened.
+
+This metric has the initial value of the -n idlesec option to
+pmdaweblog, and may be altered using pmStore(1).
+
+@ web.allservers.numwatched number of servers being monitored
+The number of Web servers that are being monitored, as opposed
+the number specified in the configuration file.
+
+See also web.config.numservers and web.perserver.watched.
+
+@ web.allservers.numalive number of watched servers that are alive
+The number of servers that are being watchedly watched that have both
+logs files.
+
+@ web.allservers.errors number of errors reported by all watched servers
+The number of errors reported by all watched servers.
+
+@ web.allservers.requests.total requests processed by all servers
+The total number of HTTP requests processed by all watched servers.
+
+@ web.allservers.bytes.total bytes sent by all servers
+The total number of bytes sent by all watched servers.
+
+@ web.allservers.requests.get GET requests handled by all watched servers
+The number of HTTP GET requests that were processed by all watched servers.
+
+@ web.allservers.bytes.get bytes sent in reply by all servers to GET requests
+The number of bytes that have been sent by all watched servers in reply
+to HTTP GET requests.
+
+@ web.allservers.requests.head HEAD requests handled by all watched servers
+The number of HTTP HEAD requests that were processed by all watched
+servers.
+
+@ web.allservers.bytes.head bytes sent in reply by all servers to HEAD requests
+The number of bytes that have been sent by all watched servers in reply
+to HTTP HEAD requests.
+
+@ web.allservers.requests.post POST requests handled by all watched servers
+The number of HTTP POST requests that were processed by all watched
+servers.
+
+@ web.allservers.bytes.post bytes sent in reply by all servers to POST requests
+The number of bytes that have been sent by all watched servers in reply
+to HTTP POST requests.
+
+@ web.allservers.requests.other other requests handled by all watch servers
+The number of HTTP requests, other than GET, HEAD and POST, that were
+processed by all watched servers.
+
+@ web.allservers.bytes.other bytes sent in reply by servers to other requests
+The number of bytes that have been sent by this server in reply to HTTP
+requests other than GET, HEAD or POST.
+
+@ web.allservers.requests.size.zero replies of 0 bytes sent by all servers
+The total number of HTTP requests that required a response of 0 bytes from
+all watched servers.
+
+@ web.allservers.bytes.size.zero total bytes sent in 0k replies
+The total number of bytes sent in replies of 0k by all watched servers.
+This metric is always zero and is provided for consistency only.
+
+@ web.allservers.requests.size.le3k replies of <= 3k sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 3k from all watched servers.
+
+@ web.allservers.bytes.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent in replies of less than or equal to 3k in
+size by all watched servers.
+
+@ web.allservers.requests.size.le10k replies of <= 10k sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 10k from all watched servers.
+
+@ web.allservers.bytes.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent in replies of less than or equal to 10k in
+size by all watched servers.
+
+@ web.allservers.requests.size.le30k replies of <= 30k sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 30k from all watched servers.
+
+@ web.allservers.bytes.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent in replies of less than or equal to 30k in
+size by all watched servers.
+
+@ web.allservers.requests.size.le100k replies of <= 100k sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 100k from all watched servers.
+
+@ web.allservers.bytes.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent in replies of less than or equal to 100k in
+size by all watched servers.
+
+@ web.allservers.requests.size.le300k replies of <= 300k sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 300k from all watched servers.
+
+@ web.allservers.bytes.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent in replies of less than or equal to 300k in
+size by all watched servers.
+
+@ web.allservers.requests.size.le1m replies of <= 1M sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 1M from all watched servers.
+
+@ web.allservers.bytes.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent in replies of less than or equal to 1M in
+size by all watched servers.
+
+@ web.allservers.requests.size.le3m replies of <= 3M sent by all servers
+The number of HTTP requests that required a response of less than or equal
+to 3M from all watched servers.
+
+@ web.allservers.bytes.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent in replies of less than or equal to 3M in
+size by all watched servers.
+
+@ web.allservers.requests.size.gt3m replies of > 3M sent by all servers
+The number of HTTP requests that required a response of greater than
+3M from all watched servers.
+
+@ web.allservers.bytes.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent in replies of greater than 3M in size by
+all watched servers.
+
+@ web.allservers.requests.size.unknown replies of unknown size by all servers
+The number of HTTP requests that required a response of unknown size
+from all watched servers.
+
+@ web.allservers.requests.client.total requests satisfied by client caches for all cacheing servers
+The total number of HTTP GET/IMS requests that resulted in "Not Modified"
+responses from cache (and remote if checked). These are client cache hits.
+
+@ web.allservers.requests.cached.total requests satisfied by server caches for all cacheing servers
+The total number of HTTP GET/IMS requests that resulted in "Not Modified"
+responses from the remote site or were deemed cache hits via other
+mechanisms such as recency. These are server cache hits and result in
+data transferred from cache to client.
+
+@ web.allservers.requests.cached.size.zero replies of 0 bytes sent by all caches
+The number of HTTP GET cache hits that required a response of 0 bytes from
+all watched caches.
+
+@ web.allservers.requests.cached.size.le3k replies of <= 3k sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 3k from all watched caches.
+
+@ web.allservers.requests.cached.size.le10k replies of <= 10k sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 10k from all watched caches.
+
+@ web.allservers.requests.cached.size.le30k replies of <= 30k sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 30k from all watched caches.
+
+@ web.allservers.requests.cached.size.le100k replies of <= 100k sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 100k from all watched caches.
+
+@ web.allservers.requests.cached.size.le300k replies of <= 300k sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 300k from all watched caches.
+
+@ web.allservers.requests.cached.size.le1m replies of <= 1M sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 1M from all watched caches.
+
+@ web.allservers.requests.cached.size.le3m replies of <= 3M sent by all caches
+The number of HTTP GET cache hits that required a response of less than
+or equal to 3M from all watched caches.
+
+@ web.allservers.requests.cached.size.gt3m replies of > 3M sent by all caches
+The number of HTTP GET cache hits that required a response of greater than
+3M from all watched caches.
+
+@ web.allservers.requests.cached.size.unknown replies of unknown size by all caches
+The number of HTTP GET cache hits that required a response of unknown
+size from all watched caches.
+
+@ web.allservers.requests.uncached.total requests satisfied by remote server for all cacheing servers
+The total number of HTTP GET/IMS requests that resulted in a real data
+transfer from the remote server. These are either cache misses, or the
+remote file had been modified since the cache entry was made.
+
+@ web.allservers.requests.uncached.size.zero replies of 0 bytes sent by all caches
+The number of HTTP GET remote fetches that required a response of 0
+bytes from all watched caches.
+
+@ web.allservers.requests.uncached.size.le3k replies of <= 3k sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 3k through all watched caches.
+
+@ web.allservers.requests.uncached.size.le10k replies of <= 10k sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 10k through all watched caches.
+
+@ web.allservers.requests.uncached.size.le30k replies of <= 30k sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 30k through all watched caches.
+
+@ web.allservers.requests.uncached.size.le100k replies of <= 100k sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 100k through all watched caches.
+
+@ web.allservers.requests.uncached.size.le300k replies of <= 300k sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 300k through all watched caches.
+
+@ web.allservers.requests.uncached.size.le1m replies of <= 1M sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 1M through all watched caches.
+
+@ web.allservers.requests.uncached.size.le3m replies of <= 3M sent by all caches
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 3M through all watched caches.
+
+@ web.allservers.requests.uncached.size.gt3m replies of > 3M sent by all caches
+The number of HTTP GET remote fetches that required a response of greater
+than 3M through all watched caches.
+
+@ web.allservers.requests.uncached.size.unknown replies of unknown size by all caches
+The number of HTTP GET requests that required a response of unknown size
+through all watched caches.
+
+@ web.allservers.bytes.cached.total bytes sent by caches as a result of cache hits for all cacheing servers
+The total number of bytes sent to client due to HTTP GET/IMS requests
+that resulted in "Not Modified"responses from the remote site or were
+deemed cache hits via other mechanisms such as recency.
+
+@ web.allservers.bytes.cached.size.zero total bytes sent in 0k replies
+The total number of bytes sent to client by cache hit replies of 0k
+by all watched caches. This metric is always zero and is provided for
+consistency only.
+
+@ web.allservers.bytes.cached.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 3k in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 10k in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 30k in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 100k in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 300k in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 1M in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 3M in size by all watched caches.
+
+@ web.allservers.bytes.cached.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent to client by cache hit replies of greater
+than 3M in size by all watched caches.
+
+@ web.allservers.bytes.uncached.total bytes sent by remote servers as a result of cache misses for all cacheing servers
+The total number of bytes sent to client from the remote server. These
+are either cache misses, or the remote file had been modified since
+the cache entry was made.
+
+@ web.allservers.bytes.uncached.size.zero total bytes sent in 0k replies
+The total number of bytes sent to client from the remote server of 0k
+by all watched caches. This metric is always zero and is provided for
+consistency only.
+
+@ web.allservers.bytes.uncached.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 3k in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 10k in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 30k in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 100k in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 300k in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 1M in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 3M in size by all watched caches.
+
+@ web.allservers.bytes.uncached.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent to client from the remote server of
+greater than 3M in size by all watched caches.
+
+@ web.perserver.watched flag set to 1 if monitoring this server
+A flag which is set to 1 if this server is being monitored.
+This metric may be altered using pmStore(1).
+
+@ web.perserver.numlogs number of readable log files for this server
+The number of log files that are readable for the server.
+
+@ web.perserver.errors number of logged errors by this server
+The number of errors (and other administrative messages) that have been
+logged in the error log by this server.
+
+@ web.perserver.requests.total requests processed by this server
+The number of HTTP requests processed by this server.
+
+@ web.perserver.bytes.total bytes sent by this server
+The number of bytes this server has sent.
+
+@ web.perserver.requests.get GET requests handled by server
+The number of HTTP GET requests that were processed by this server.
+
+@ web.perserver.bytes.get bytes sent in reply to GET requests
+The number of bytes that have been sent by this server in reply to HTTP
+GET requests.
+
+@ web.perserver.requests.head HEAD requests handled by server
+The number of HTTP HEAD requests that were processed by this server.
+
+@ web.perserver.bytes.head bytes sent in reply to HEAD requests
+The number of bytes that have been sent by this server in reply to HTTP
+HEAD requests.
+
+@ web.perserver.requests.post POST requests handled by server
+The number of HTTP POST requests that were processed by this server.
+
+@ web.perserver.bytes.post bytes sent in reply to POST requests
+The number of bytes that have been sent by this server in reply to HTTP
+POST requests.
+
+@ web.perserver.requests.other other requests handled by server
+The number of HTTP requests, other than GET, HEAD and POST, that were
+processed by this server.
+
+@ web.perserver.bytes.other bytes sent in reply to other requests
+The number of bytes that have been sent by this server in reply to HTTP
+requests other than GET, HEAD or POST.
+
+@ web.perserver.requests.size.zero requests requiring 0 bytes in reply
+The number of HTTP requests that required a response of 0 bytes from this
+server.
+
+@ web.perserver.bytes.size.zero total bytes sent in 0k replies.
+The total number of bytes sent in replies of 0k by this server. This metric
+is always 0 and is provided for consistency only.
+
+@ web.perserver.requests.size.le3k requests requiring <= 3k replies
+The number of HTTP requests that required a response of less than or equal
+to 3k from this server.
+
+@ web.perserver.bytes.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent in replies of less than or equal to 3k in
+size.
+
+@ web.perserver.requests.size.le10k requests requiring <= 10k replies
+The number of HTTP requests that required a response of less than or equal
+to 10k from this server.
+
+@ web.perserver.bytes.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent in replies of less than or equal to 10k in
+size.
+
+@ web.perserver.requests.size.le30k requests requiring <= 30k replies
+The number of HTTP requests that required a response of less than or equal
+to 30k from this server.
+
+@ web.perserver.bytes.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent in replies of less than or equal to 30k in
+size.
+
+@ web.perserver.requests.size.le100k requests requiring <= 100k replies
+The number of HTTP requests that required a response of less than or equal
+to 100k from this server.
+
+@ web.perserver.bytes.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent in replies of less than or equal to 100k in
+size.
+
+@ web.perserver.requests.size.le300k requests requiring <= 300k replies
+The number of HTTP requests that required a response of less than or equal
+to 300k from this server.
+
+@ web.perserver.bytes.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent in replies of less than or equal to 300k in
+size.
+
+@ web.perserver.requests.size.le1m requests requiring <= 1M replies
+The number of HTTP requests that required a response of less than or equal
+to 1M from this server.
+
+@ web.perserver.bytes.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent in replies of less than or equal to 1M in
+size.
+
+@ web.perserver.requests.size.le3m requests requiring <= 3M replies
+The number of HTTP requests that required a response of less than or equal
+to 3M from this server.
+
+@ web.perserver.bytes.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent in replies of less than or equal to 3M in
+size.
+
+@ web.perserver.requests.size.gt3m requests requiring > 3M replies
+The number of HTTP requests that required a response of greater than
+3M from this server.
+
+@ web.perserver.bytes.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent in replies of greater than 3M in size.
+
+@ web.perserver.requests.size.unknown requests of unknown size
+The number of HTTP requests that required a response of unknown size from
+this server.
+
+@ web.perserver.logidletime seconds since log last modified
+The number of seconds since the access log for this server was modified.
+
+@ web.perserver.requests.client.total requests satisfied by client caches for this cache
+The total number of HTTP GET/IMS requests that resulted in "Not Modified"
+responses from cache (and remote if checked). These are client cache hits.
+
+@ web.perserver.requests.cached.total requests satisfied by server caches for this cache
+The total number of HTTP GET/IMS requests that resulted in "Not Modified"
+responses from the remote site or were deemed cache hits via other
+mechanisms such as recency. These are server cache hits and result in
+data transferred from cache to client.
+
+@ web.perserver.requests.cached.size.zero replies of 0 bytes sent by this cache
+The number of HTTP GET cache hits that required a response of 0 bytes from
+this cache.
+
+@ web.perserver.requests.cached.size.le3k replies of <= 3k sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 3k from this cache.
+
+@ web.perserver.requests.cached.size.le10k replies of <= 10k sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 10k from this cache.
+
+@ web.perserver.requests.cached.size.le30k replies of <= 30k sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 30k from this cache.
+
+@ web.perserver.requests.cached.size.le100k replies of <= 100k sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 100k from this cache.
+
+@ web.perserver.requests.cached.size.le300k replies of <= 300k sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 300k from this cache.
+
+@ web.perserver.requests.cached.size.le1m replies of <= 1M sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 1M from this cache.
+
+@ web.perserver.requests.cached.size.le3m replies of <= 3M sent by this cache
+The number of HTTP GET cache hits that required a response of less than
+or equal to 3M from this cache.
+
+@ web.perserver.requests.cached.size.gt3m replies of > 3M sent by this cache
+The number of HTTP GET cache hits that required a response of greater than
+3M from this cache.
+
+@ web.perserver.requests.cached.size.unknown replies of unknown size by this cache
+The number of HTTP GET cache hits that required a response of unknown
+size from this cache.
+
+@ web.perserver.requests.uncached.total requests satisfied by remote server for this cache
+The total number of HTTP GET/IMS requests that resulted in a real data
+transfer from the remote server. These are either cache misses, or the
+remote file had been modified since the cache entry was made.
+
+@ web.perserver.requests.uncached.size.zero replies of 0 bytes sent by this cache
+The number of HTTP GET remote fetches that required a response of 0
+bytes from this cache.
+
+@ web.perserver.requests.uncached.size.le3k replies of <= 3k sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 3k through this cache.
+
+@ web.perserver.requests.uncached.size.le10k replies of <= 10k sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 10k through this cache.
+
+@ web.perserver.requests.uncached.size.le30k replies of <= 30k sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 30k through this cache.
+
+@ web.perserver.requests.uncached.size.le100k replies of <= 100k sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 100k through this cache.
+
+@ web.perserver.requests.uncached.size.le300k replies of <= 300k sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 300k through this cache.
+
+@ web.perserver.requests.uncached.size.le1m replies of <= 1M sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 1M through this cache.
+
+@ web.perserver.requests.uncached.size.le3m replies of <= 3M sent by this cache
+The number of HTTP GET remote fetches that required a response of less
+than or equal to 3M through this cache.
+
+@ web.perserver.requests.uncached.size.gt3m replies of > 3M sent by this cache
+The number of HTTP GET remote fetches that required a response of greater
+than 3M through this cache.
+
+@ web.perserver.requests.uncached.size.unknown replies of unknown size by this cache
+The number of HTTP GET requests that required a response of unknown size
+through this cache.
+
+@ web.perserver.bytes.cached.total bytes sent by caches as a result of cache hits for this cache
+The total number of bytes sent to client due to HTTP GET/IMS requests
+that resulted in "Not Modified"responses from the remote site or were
+deemed cache hits via other mechanisms such as recency.
+
+@ web.perserver.bytes.cached.size.zero total bytes sent in 0k replies
+The total number of bytes sent to client by cache hit replies of 0k by
+this cache. This metric is always zero and is provided for consistency
+only.
+
+@ web.perserver.bytes.cached.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 3k in size by this cache.
+
+@ web.perserver.bytes.cached.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 10k in size by this cache.
+
+@ web.perserver.bytes.cached.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 30k in size by this cache.
+
+@ web.perserver.bytes.cached.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 100k in size by this cache.
+
+@ web.perserver.bytes.cached.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 300k in size by this cache.
+
+@ web.perserver.bytes.cached.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 1M in size by this cache.
+
+@ web.perserver.bytes.cached.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent to client by cache hit replies of less
+than or equal to 3M in size by this cache.
+
+@ web.perserver.bytes.cached.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent to client by cache hit replies of greater
+than 3M in size by this cache.
+
+@ web.perserver.bytes.uncached.total bytes sent by remote servers as a result of cache misses for this cache
+The total number of bytes sent to client from the remote server. These
+are either cache misses, or the remote file had been modified since
+the cache entry was made.
+
+@ web.perserver.bytes.uncached.size.zero total bytes sent in 0k replies
+The total number of bytes sent to client from the remote server of 0k by
+this cache. This metric is always zero and is provided for consistency
+only.
+
+@ web.perserver.bytes.uncached.size.le3k total bytes sent in <= 3k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 3k in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le10k total bytes sent in <= 10k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 10k in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le30k total bytes sent in <= 30k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 30k in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le100k total bytes sent in <= 100k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 100k in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le300k total bytes sent in <= 300k replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 300k in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le1m total bytes sent in <= 1M replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 1M in size by this cache.
+
+@ web.perserver.bytes.uncached.size.le3m total bytes sent in <= 3M replies
+The total number of bytes sent to client from the remote server of less
+than or equal to 3M in size by this cache.
+
+@ web.perserver.bytes.uncached.size.gt3m total bytes sent > 3M replies
+The total number of bytes sent to client from the remote server of
+greater than 3M in size by this cache.
+
diff --git a/src/pmdas/weblog/pmda.c b/src/pmdas/weblog/pmda.c
new file mode 100644
index 0000000..6c1967f
--- /dev/null
+++ b/src/pmdas/weblog/pmda.c
@@ -0,0 +1,1205 @@
+/*
+ * Web PMDA, based on generic driver for a daemon-based PMDA
+ *
+ * Copyright (c) 2012 Red Hat.
+ * Copyright (c) 2000-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "weblog.h"
+#include "domain.h"
+#if defined(HAVE_REGEX_H)
+#include <regex.h>
+#endif
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#if defined(HAVE_SCHED_H)
+#include <sched.h>
+#endif
+
+#ifdef IS_SOLARIS
+#define CLONE_VM 0x00000100
+#elif !defined(CLONE_VM)
+#define CLONE_VM 0x0
+#endif
+
+#ifndef HAVE_SPROC
+int sproc (void (*entry) (void *), int flags, void *arg);
+#endif
+
+/* path to the configuration file */
+static char *configFileName = (char*)0;
+
+/* line number of configuration file */
+static int line = 0;
+
+/* number of errors in configuration file */
+static int err = 0;
+
+/* configured for num of servers */
+__uint32_t wl_numServers = 0;
+
+/* number of active servers */
+__uint32_t wl_numActive = 0;
+
+/* check logs every 15 seconds by default */
+__uint32_t wl_refreshDelay = 15;
+
+/* re-open logs if unchanged in this number of seconds */
+__uint32_t wl_chkDelay = 20;
+
+/* max servers per sproc */
+__uint32_t wl_sprocThresh = 80;
+
+/* number of sprocs spawned */
+__uint32_t wl_numSprocs = 0;
+
+/* number of regex parsed */
+__uint32_t wl_numRegex = 0;
+
+/* list of web servers */
+WebServer *wl_servers = (WebServer*)0;
+
+/* list of regular expressions */
+WebRegex *wl_regexTable = (WebRegex*)0;
+
+/* instance table of web servers */
+pmdaInstid *wl_serverInst = (pmdaInstid*)0;
+
+/* list of sprocs spawned from the main process */
+WebSproc *wl_sproc;
+
+/* default name for log file */
+char *wl_logFile = "weblog.log";
+
+/* default path to help file */
+char wl_helpFile[MAXPATHLEN];
+
+/* default user name for PMDA */
+char *wl_username;
+
+/*
+ * Usage Information
+ */
+
+void
+usage(void)
+{
+ fprintf(stderr,
+ "Usage: %s [options] configfile\n\
+\n\
+Options\n\
+ -C check configuration and exit\n\
+ -d domain PMDA domain number\n\
+ -h helpfile get help text from helpfile rather than default path\n\
+ -i port expect PMCD to connect on given inet port (number or name)\n\
+ -l logfile redirect diagnostics and trace output (default weblog.log)\n\
+ -n idlesec number of seconds of weblog inactivity before checking for\n\
+ log rotation\n\
+ -p expect PMCD to supply stdin/stdout (pipe)\n\
+ -S num number of web servers per sproc\n\
+ -t delay maximum number of seconds between reading weblog files\n\
+ -u socket expect PMCD to connect on given unix domain socket\n\
+ -U username user account to run under (default \"pcp\")\n\
+ -6 port expect PMCD to connect on given ipv6 port (number or name)\n\
+\n\
+If none of the -i, -p or -u options are given, the configuration file is\n\
+checked and then %s terminates.\n", pmProgname, pmProgname);
+ exit(1);
+}
+
+void
+logmessage(int priority, const char *format, ...)
+{
+ va_list arglist;
+ char buffer[2048];
+ char *level;
+ char *p;
+ time_t now;
+
+ buffer[0] = '\0';
+ time(&now);
+
+ switch (priority) {
+ case LOG_EMERG :
+ level = "Emergency";
+ break;
+ case LOG_ALERT :
+ level = "Alert";
+ break;
+ case LOG_CRIT :
+ level = "Critical";
+ break;
+ case LOG_ERR :
+ level = "Error";
+ break;
+ case LOG_WARNING :
+ level = "Warning";
+ break;
+ case LOG_NOTICE :
+ level = "Notice";
+ break;
+ case LOG_INFO :
+ level = "Info";
+ break;
+ case LOG_DEBUG :
+ level = "Debug";
+ break;
+ default:
+ level = "???";
+ break;
+ }
+
+ va_start (arglist, format);
+ vsnprintf (buffer, sizeof(buffer), format, arglist);
+ for (p = buffer; *p; p++);
+ if (*(--p) == '\n') *p = '\0';
+ fprintf (stderr, "[%.19s] %s(%" FMT_PID ") %s: %s\n", ctime(&now), pmProgname, getpid(), level, buffer) ;
+ va_end (arglist) ;
+}
+
+/*
+ * Errors message during parsing of config file
+ */
+
+static void
+yyerror(char *s)
+{
+ fprintf(stderr, "[%s:%d] Error: %s\n", configFileName, line, s);
+ err++;
+}
+
+/*
+ * Warning message during parsing of config file
+ */
+
+static void
+yywarn(char *s)
+{
+ fprintf(stderr, "[%s:%d] Warning: %s\n", configFileName, line, s);
+}
+
+/*
+ * skip remaining characters on this line
+ */
+
+static void
+skip_to_eol(FILE *f)
+{
+ int c;
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\n')
+ return;
+ }
+ return;
+}
+
+/*
+ * Are we at the end of the line (sucks up spaces and tabs which may preceed
+ * EOL)
+ */
+
+static void
+check_to_eol(FILE *f)
+{
+ int c;
+ int i = 0;
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\n')
+ break;
+ if (c == ' ' || c == '\t')
+ continue;
+ i++;
+ }
+ if (i)
+ yywarn("additional words in line, ignored");
+
+ return;
+}
+
+/*
+ * Get a word. A word if any text until a whitespace
+ */
+
+static int
+getword(FILE *f, char *buf, int len)
+{
+ int c;
+ char *bend = &buf[len-1];
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == ' ' || c == '\t')
+ continue;
+ ungetc(c, f);
+ break;
+ }
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == ' ' || c == '\t')
+ break;
+ if (c == '\n') {
+ ungetc(c, f);
+ break;
+ }
+ if (buf < bend) {
+ *buf++ = c;
+ continue;
+ }
+ else {
+ yyerror("word too long, remainder of line ignored");
+ return -1;
+ }
+ }
+ *buf = '\0';
+
+ return c == EOF ? 0 : 1;
+}
+
+/*
+ * Get the next line from buffer
+ */
+
+static int
+get_to_eol(FILE *f, char *buf, int len)
+{
+ int c;
+ char *bend = &buf[len-1];
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == ' ' || c == '\t')
+ continue;
+ ungetc(c, f);
+ break;
+ }
+
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '\n')
+ break;
+ if (buf < bend) {
+ *buf++ = c;
+ continue;
+ }
+ else {
+ yyerror("list of words too long, remainder of line ignored");
+ return -1;
+ }
+ }
+ *buf = '\0';
+ return c == EOF ? 0 : 1;
+}
+
+/*
+ * Replacement for pmdaMainLoop
+ * Has a select loop on pipe from PMCD, reads in PDUs and acts on them
+ * appropriately.
+ */
+
+static void
+receivePDUs(pmdaInterface *dispatch)
+{
+ int nfds = 0;
+ time_t interval = 0;
+ int sts = 0;
+ struct timeval timeout;
+ fd_set rfds;
+
+
+ FD_ZERO(&rfds);
+ nfds = fileno(stdin)+1;
+
+ for (;;) {
+
+ FD_SET(fileno(stdin), &rfds);
+ __pmtimevalNow(&timeout);
+ timeout.tv_usec = 0;
+ interval = (time_t)wl_refreshDelay - (timeout.tv_sec % (time_t)wl_refreshDelay);
+ timeout.tv_sec = interval;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "Select set for %d seconds\n",
+ interval);
+#endif
+
+ sts = select(nfds, &rfds, (fd_set*)0, (fd_set*)0, &timeout);
+ if (sts < 0) {
+ logmessage(LOG_ERR, "Error on fetch select: %s", netstrerror());
+ exit(1);
+ }
+
+ if (sts == 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "Select timed out\n");
+#endif
+
+ refreshAll();
+ continue;
+ }
+
+ if (__pmdaMainPDU(dispatch) < 0){
+ exit(1);
+ }
+
+ if (interval == 0) {
+ refreshAll();
+ }
+
+ }
+}
+
+/*
+ * Catch an SPROC dying, report what we know, and exit
+ * -- when main exits, other sprocs will get SIGHUP and exit quietly
+ */
+static void
+onchld(int dummy)
+{
+ int done;
+ int waitStatus;
+ int sprocNum;
+
+ while ((done = waitpid(-1, &waitStatus, WNOHANG)) > 0) {
+ for (sprocNum = 1;
+ wl_sproc[sprocNum].pid != done && sprocNum <= wl_numSprocs;
+ sprocNum++);
+
+ if (sprocNum > wl_numSprocs)
+ {
+ logmessage(LOG_INFO,
+ "Unexpected child process (pid=%d) died!\n",
+ done);
+ continue;
+ }
+
+ if (WIFEXITED(waitStatus)) {
+
+ if (WEXITSTATUS(waitStatus) == 0)
+ logmessage(LOG_INFO,
+ "Sproc %d (pid=%d) exited normally\n",
+ sprocNum, done);
+ else
+ logmessage(LOG_INFO,
+ "Sproc %d (pid=%d) exited with status = %d\n",
+ sprocNum, done, WEXITSTATUS(waitStatus));
+ }
+ else if (WIFSIGNALED(waitStatus)) {
+
+#ifdef WCOREDUMP
+ if (WCOREDUMP(waitStatus))
+ logmessage(LOG_INFO,
+ "Sproc %d (pid=%d) received signal = %d and dumped core\n",
+ sprocNum, done, WTERMSIG(waitStatus));
+#endif
+ logmessage(LOG_INFO,
+ "Sproc %d (pid=%d) received signal = %d\n",
+ sprocNum, done, WTERMSIG(waitStatus));
+ }
+ else {
+ logmessage(LOG_INFO,
+ "Sproc %d (pid=%d) died, reason unknown\n",
+ sprocNum, done);
+ }
+
+ logmessage(LOG_INFO,
+ "Sproc %d managed servers %d to %d\n",
+ sprocNum,
+ wl_sproc[sprocNum].firstServer,
+ wl_sproc[sprocNum].lastServer);
+ }
+
+ logmessage(LOG_INFO, "Main process exiting\n");
+ exit(0);
+}
+
+/*
+ * Parse command line args and the configuration file. Also sets up and fires
+ * off the required sprocs
+ */
+
+int
+main(int argc, char **argv)
+{
+ WebServer *server = (WebServer *)0;
+ WebSproc *proc = (WebSproc *)0;
+
+ char *endnum = (char*)0;
+ char buf1[FILENAME_MAX];
+ char buf2[FILENAME_MAX];
+ char emess[120];
+ char *pstart, *pend;
+ char argsDone, argFound;
+ char *err_msg;
+
+ int i = 0;
+ int argCount = 0;
+ int checkOnly = 0;
+ int sts = 0;
+ int sep = __pmPathSeparator();
+ int n = 0;
+ int serverTableSize = 0;
+ int regexTableSize = 0;
+
+ FILE *configFile = (FILE*)0;
+ FILE *tmpFp = (FILE*)0;
+
+ pmdaInterface desc;
+ struct timeval delta;
+
+ struct {
+ int *argPos;
+ char *argString;
+ } regexargs[2];
+
+#ifdef PCP_DEBUG
+ struct timeval start;
+ struct timeval end;
+ double startTime;
+#endif
+
+ __pmSetProgname(argv[0]);
+ __pmGetUsername(&wl_username);
+
+#ifdef PCP_DEBUG
+ __pmtimevalNow(&start);
+#endif
+
+ wl_isDSO = 0;
+
+ snprintf(wl_helpFile, sizeof(wl_helpFile), "%s%c" "weblog" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDaemon(&desc, PMDA_INTERFACE_2, pmProgname, WEBSERVER,
+ wl_logFile, wl_helpFile);
+
+ while ((n = pmdaGetOpt(argc, argv, "CD:d:h:i:l:n:pS:t:u:U:6:?",
+ &desc, &err)) != EOF) {
+ switch (n) {
+
+ case 'C':
+ checkOnly = 1;
+ break;
+
+ case 'S':
+ wl_sprocThresh = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ fprintf(stderr, "%s: -S requires numeric argument\n",
+ pmProgname);
+ err++;
+ }
+ break;
+
+ case 'n':
+ if (pmParseInterval(optarg, &delta, &err_msg) < 0) {
+ (void)fprintf(stderr,
+ "%s: -n requires a time interval: %s\n",
+ err_msg, pmProgname);
+ free(err_msg);
+ err++;
+ }
+ else {
+ wl_chkDelay = delta.tv_sec;
+ }
+ break;
+
+ case 't':
+ if (pmParseInterval(optarg, &delta, &err_msg) < 0) {
+ (void)fprintf(stderr,
+ "%s: -t requires a time interval: %s\n",
+ err_msg, pmProgname);
+ free(err_msg);
+ err++;
+ }
+ else {
+ wl_refreshDelay = delta.tv_sec;
+ }
+ break;
+
+ case 'U':
+ wl_username = optarg;
+ break;
+
+ default:
+ fprintf(stderr, "%s: Unknown option \"-%c\"", pmProgname, (char)n);
+ err++;
+ break;
+ }
+ }
+
+ if (err || optind != argc-1) {
+ usage();
+ }
+
+ line = 0;
+ configFileName = argv[optind];
+ configFile = fopen(configFileName, "r");
+
+ if (configFile == (FILE*)0) {
+ fprintf(stderr, "Unable to open config file %s\n", configFileName);
+ usage();
+ }
+
+ if (checkOnly == 0) {
+ /*
+ * if doing more than just parsing, force errors from here
+ * on into the logfile
+ */
+ pmdaOpenLog(&desc);
+ __pmSetProcessIdentity(wl_username);
+ }
+
+ /*
+ * Parse the configuration file
+ */
+
+ /* These settings should be reflected below */
+ regexargs[0].argString = strdup("method");
+ regexargs[1].argString = strdup("size");
+
+ while(!feof(configFile)) {
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts == 0) {
+ /* End of File */
+ break;
+ }
+
+ line++;
+
+ if (sts < 0) {
+ /* error, reported in getword() */
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ if (buf1[0] == '\0' || buf1[0] == '#') {
+ /* comment, or nothing in the line, next line please */
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ if (strcasecmp(buf1, "regex_posix") == 0) {
+ /*
+ * Parse a regex specification
+ */
+
+ if (wl_numRegex == regexTableSize) {
+ regexTableSize += 2;
+ wl_regexTable = (WebRegex*)realloc(wl_regexTable,
+ regexTableSize * sizeof(WebRegex));
+ if (wl_regexTable == (WebRegex*)0) {
+ __pmNoMem("main.wl_regexInst",
+ (wl_numRegex + 1) * sizeof(WebRegex),
+ PM_FATAL_ERR);
+ }
+ }
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract regex name");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ wl_regexTable[wl_numRegex].name = strdup(buf1);
+
+ if (wl_numRegex) {
+ for (n = 0; n < wl_numRegex; n++) {
+ if (strcmp(wl_regexTable[n].name,
+ wl_regexTable[wl_numRegex].name) == 0) {
+
+ snprintf(emess, sizeof(emess), "duplicate regex name (%s)",
+ wl_regexTable[wl_numRegex].name);
+ yyerror(emess);
+ break;
+ }
+ }
+ if (n < wl_numRegex) {
+ skip_to_eol(configFile);
+ continue;
+ }
+ }
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract regex match parameters");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ regexargs[0].argPos = &(wl_regexTable[wl_numRegex].methodPos);
+ regexargs[1].argPos = &(wl_regexTable[wl_numRegex].sizePos);
+ wl_regexTable[wl_numRegex].methodPos = 0;
+ wl_regexTable[wl_numRegex].sizePos = 0;
+ wl_regexTable[wl_numRegex].sizePos = 0;
+ wl_regexTable[wl_numRegex].s_statusPos = 0;
+
+ pstart = buf1;
+ argCount = 0;
+ do {
+ argFound = 0;
+ argsDone = 1;
+ argCount++;
+ for(pend = pstart; *pend; pend++) {
+ if(*pend == ',') {
+ *pend = '\0';
+ argsDone = 0;
+ break;
+ }
+ }
+ for(i = 0; i < sizeof(regexargs) / sizeof(regexargs[0]); i++) {
+ if(strcmp(pstart, regexargs[i].argString) == 0) {
+ *regexargs[i].argPos = argCount;
+ argFound = 1;
+ break;
+ }
+ }
+ if(!argFound) {
+ /* not the old method,size style */
+ switch(pstart[0]) {
+ case '1':
+ wl_regexTable[wl_numRegex].methodPos = argCount;
+ argFound = 1;
+ break;
+ case '2':
+ wl_regexTable[wl_numRegex].sizePos = argCount;
+ argFound = 1;
+ break;
+ case '3':
+ wl_regexTable[wl_numRegex].c_statusPos = argCount;
+ argFound = 1;
+ break;
+ case '4':
+ wl_regexTable[wl_numRegex].s_statusPos = argCount;
+ argFound = 1;
+ break;
+ case '-':
+ wl_regexTable[wl_numRegex].methodPos = argCount++;
+ wl_regexTable[wl_numRegex].sizePos = argCount;
+ argFound = 1;
+ argsDone = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ pstart = pend + 1;
+ } while(argsDone == 0 && argFound != 0);
+
+ if(argFound == 0) {
+ yyerror("invalid keyword in regex match parameters");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ sts = get_to_eol(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract regex");
+ else
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ wl_regexTable[wl_numRegex].regex = malloc(sizeof(*wl_regexTable[wl_numRegex].regex));
+ if(wl_regexTable[wl_numRegex].regex == NULL) {
+ __pmNoMem("main.wl_regex",
+ sizeof(*wl_regexTable[wl_numRegex].regex),
+ PM_FATAL_ERR);
+ }
+
+ if (regcomp(wl_regexTable[wl_numRegex].regex, buf1, REG_EXTENDED) != 0) {
+ yyerror("unable to compile regex");
+ continue;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG, "%d regex %s: %s\n",
+ wl_numRegex, wl_regexTable[wl_numRegex].name, buf1);
+#endif
+
+ wl_regexTable[wl_numRegex].posix_regexp = 1;
+ wl_numRegex++;
+ }
+#ifdef NON_POSIX_REGEX
+ else if (strcasecmp(buf1, "regex") == 0) {
+ /*
+ * Parse a regex specification
+ */
+
+ if (wl_numRegex == regexTableSize) {
+ regexTableSize += 2;
+ wl_regexTable = (WebRegex*)realloc(wl_regexTable,
+ regexTableSize * sizeof(WebRegex));
+ if (wl_regexTable == (WebRegex*)0) {
+ __pmNoMem("main.wl_regexInst",
+ (wl_numRegex + 1) * sizeof(WebRegex),
+ PM_FATAL_ERR);
+ }
+ }
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract regex name");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ wl_regexTable[wl_numRegex].name = strdup(buf1);
+
+ if (wl_numRegex) {
+ for (n = 0; n < wl_numRegex; n++) {
+ if (strcmp(wl_regexTable[n].name,
+ wl_regexTable[wl_numRegex].name) == 0) {
+
+ snprintf(emess, sizeof(emess), "duplicate regex name (%s)",
+ wl_regexTable[wl_numRegex].name);
+ yyerror(emess);
+ break;
+ }
+ }
+ if (n < wl_numRegex) {
+ skip_to_eol(configFile);
+ continue;
+ }
+ }
+
+ sts = get_to_eol(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract regex");
+ else
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ if(strstr(buf1, "$2") != NULL && strstr(buf1, "$3") != NULL ) {
+ /*
+ * extended caching server format
+ *
+ * although these aren't used in the non-regex code, they
+ * are a good enough placeholder until server->counts.extendedp
+ * is set below
+ */
+ wl_regexTable[wl_numRegex].c_statusPos = 1;
+ wl_regexTable[wl_numRegex].s_statusPos = 1;
+
+ }
+ wl_regexTable[wl_numRegex].np_regex = regcmp(buf1, (char*)0);
+
+ if (wl_regexTable[wl_numRegex].np_regex == (char*)0) {
+ yyerror("unable to compile regex");
+ continue;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG, "%d NON POSIX regex %s: %s\n",
+ wl_numRegex, wl_regexTable[wl_numRegex].name, buf1);
+#endif
+
+ wl_regexTable[wl_numRegex].posix_regexp = 0;
+ wl_numRegex++;
+ }
+#endif
+ else if (strcasecmp(buf1, "server") == 0) {
+ /*
+ * Parse a server specification
+ */
+
+ if (wl_numServers == serverTableSize) {
+ serverTableSize += 4;
+ wl_serverInst = (pmdaInstid*)realloc(wl_serverInst,
+ serverTableSize * sizeof(pmdaInstid));
+ if (wl_serverInst == (pmdaInstid*)0) {
+ __pmNoMem("main.wl_serverInst",
+ (wl_numServers + 1) * sizeof(pmdaInstid),
+ PM_FATAL_ERR);
+ }
+
+ wl_servers = (WebServer*)realloc(wl_servers,
+ serverTableSize * sizeof(WebServer));
+ if (wl_servers == (WebServer*)0) {
+ __pmNoMem("main.wl_servers",
+ (wl_numServers + 1) * sizeof(WebServer),
+ PM_FATAL_ERR);
+ }
+ }
+
+ /* Get server name */
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract server name");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ if (wl_numServers) {
+ for (n = 0; n < wl_numServers; n++) {
+ if (strcmp(buf1, wl_serverInst[n].i_name) == 0) {
+ snprintf(emess, sizeof(emess), "duplicate server name (%s)", buf1);
+ yyerror(emess);
+ break;
+ }
+ }
+ if (n < wl_numServers) {
+ skip_to_eol(configFile);
+ continue;
+ }
+ }
+
+ wl_serverInst[wl_numServers].i_name = strdup(buf1);
+ wl_serverInst[wl_numServers].i_inst = wl_numServers;
+
+ server = &(wl_servers[wl_numServers]);
+ memset(server, 0, sizeof(*server));
+ server->access.filePtr = -1;
+ server->error.filePtr = -1;
+
+ /* Get server active flag */
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract active flag");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ if (strcasecmp(buf1, "on") == 0) {
+ server->counts.active = 1;
+ }
+ else if (strcasecmp(buf1, "off") == 0) {
+ server->counts.active = 0;
+ }
+ else {
+ yyerror("illegal active flag");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ /* Get access log regex and file name */
+
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract access log regex");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ sts = getword(configFile, buf2, sizeof(buf2));
+
+ if (sts <= 0 || buf2[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract access log name");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ for (n = 0; n < wl_numRegex; n++)
+ if (strcmp(buf1, wl_regexTable[n].name) == 0)
+ break;
+
+ if (n == wl_numRegex) {
+ snprintf(emess, sizeof(emess), "access log regex \"%s\" not defined", buf1);
+ yyerror(emess);
+ skip_to_eol(configFile);
+ continue;
+ } else if(wl_regexTable[n].c_statusPos > 0 &&
+ wl_regexTable[n].s_statusPos > 0) {
+ /* common extended format or one that uses the same codes */
+ server->counts.extendedp = 1;
+ if(strcmp(wl_regexTable[n].name, "SQUID") == 0) {
+ /*
+ * default squid format - uses text codes not numerics
+ * so it *has* to be a special case
+ */
+ server->counts.extendedp = 2;
+ }
+ }
+
+ server->access.format = n;
+ server->access.fileName = strdup(buf2);
+
+ if (server->counts.active) {
+ tmpFp = fopen(server->access.fileName, "r");
+ if (tmpFp == (FILE*)0) {
+ snprintf(emess, sizeof(emess), "cannot open access log \"%s\"", buf2);
+ yywarn(emess);
+ server->access.filePtr = -1;
+ }
+ else
+ fclose(tmpFp);
+ }
+
+ /* Get error log regex and file name */
+
+ sts = getword(configFile, buf1, sizeof(buf1));
+
+ if (sts <= 0 || buf1[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract error log regex");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ sts = getword(configFile, buf2, sizeof(buf2));
+
+ if (sts <= 0 || buf2[0] == '\0') {
+ if (sts >= 0)
+ yyerror("unable to extract error log name");
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ for (n = 0; n < wl_numRegex; n++)
+ if (strcmp(buf1, wl_regexTable[n].name) == 0)
+ break;
+
+ if (n == wl_numRegex) {
+ snprintf(emess, sizeof(emess), "error log regex \"%s\" not defined", buf1);
+ yyerror(emess);
+ skip_to_eol(configFile);
+ continue;
+ }
+
+ server->error.format = n;
+ server->error.fileName = strdup(buf2);
+
+ if (server->counts.active) {
+ tmpFp = fopen(server->error.fileName, "r");
+ if (tmpFp == (FILE*)0) {
+ snprintf(emess, sizeof(emess), "cannot open error log \"%s\"", buf2);
+ yywarn(emess);
+ server->error.filePtr = -1;
+ }
+ else
+ fclose(tmpFp);
+ }
+
+ check_to_eol(configFile);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ logmessage(LOG_DEBUG, "%d Server %s, %d, %d, %s, %d, %s\n",
+ wl_numServers,
+ wl_serverInst[wl_numServers].i_name,
+ server->counts.active,
+ server->access.format,
+ server->access.fileName,
+ server->error.format,
+ server->error.fileName);
+ }
+#endif
+
+ if (server->counts.active)
+ wl_numActive++;
+
+ wl_numServers++;
+ }
+ else {
+ snprintf(emess, sizeof(emess), "illegal keyword \"%s\"", buf1);
+ yyerror(emess);
+ skip_to_eol(configFile);
+ continue;
+ }
+ }
+
+ if (wl_numServers == 0) {
+ yyerror("no servers were specified in the configuration file!");
+ }
+
+ fclose(configFile);
+
+ if (checkOnly || err) {
+ /* errors, or parse only, no PMCD communication option */
+ exit(err);
+ }
+
+ wl_indomTable[0].it_numinst = wl_numServers;
+ wl_indomTable[0].it_set = wl_serverInst;
+
+ web_init(&desc);
+ pmdaConnect(&desc);
+
+ /* catch any sprocs dying */
+
+ signal(SIGCHLD, onchld);
+
+ /* fire off all the sprocs that we need */
+
+ wl_numSprocs = (wl_numServers-1) / wl_sprocThresh;
+ wl_sproc = (WebSproc*)malloc((wl_numSprocs+1) * sizeof(WebSproc));
+ if (wl_sproc == NULL) {
+ logmessage(LOG_ERR,
+ "wl_numServers = %d, wl_sprocThresh = %d",
+ wl_numServers,
+ wl_sprocThresh);
+ __pmNoMem("main.wl_sproc",
+ (wl_numSprocs+1) * sizeof(WebSproc),
+ PM_FATAL_ERR);
+ }
+
+
+ for (n = 0; n <= wl_numSprocs; n++)
+ {
+ proc = &wl_sproc[n];
+ proc->pid = -1;
+ proc->methodStr = (char *)0;
+ proc->sizeStr = (char *)0;
+ proc->c_statusStr = (char *)0;
+ proc->s_statusStr = (char *)0;
+ proc->strLength = 0;
+ }
+
+ if (wl_numSprocs) {
+
+ for (n=1; n<=wl_numSprocs; n++) {
+ proc = &wl_sproc[n];
+
+ sts = pipe1(proc->inFD);
+ if (sts) {
+ logmessage(LOG_ERR,
+ "Cannot allocate fileDes 1 for sproc[%d]",
+ n);
+ exit(1);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG,
+ "Creating in pipe (in=%d, out=%d) for sproc %d\n",
+ proc->inFD[0],
+ proc->inFD[1],
+ n);
+#endif
+
+ sts = pipe1(proc->outFD);
+ if (sts) {
+ logmessage(LOG_ERR,
+ "Cannot allocate fileDes 2 for sproc[%d]",
+ n);
+ exit(1);
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG,
+ "Creating out pipe (in=%d, out=%d) for sproc %d\n",
+ proc->outFD[0],
+ proc->outFD[1],
+ n);
+#endif
+
+ proc->firstServer = (n)*wl_sprocThresh;
+ if (n != wl_numSprocs)
+ proc->lastServer = proc->firstServer +
+ wl_sprocThresh - 1;
+ else
+ proc->lastServer = wl_numServers - 1;
+
+ logmessage(LOG_INFO,
+ "Creating sproc [%d] for servers %d to %d\n",
+ n, proc->firstServer, proc->lastServer);
+
+ proc->id = n;
+
+#ifndef HAVE_SPROC
+ proc->pid = sproc(sprocMain, CLONE_VM, (void*)(&proc->id));
+#else
+ proc->pid = sproc(sprocMain, PR_SADDR, (void*)(&proc->id));
+#endif
+
+ if (proc->pid < 0) {
+ logmessage(LOG_ERR, "main: error creating sproc %d: %s\n",
+ n, osstrerror());
+ exit(1);
+ }
+
+#ifdef PCP_DEBUG
+ if(pmDebug & DBG_TRACE_APPL0) {
+ logmessage(LOG_INFO,
+ "main: created sproc %d: pid %" FMT_PID "\n",
+ n,
+ proc->pid);
+ }
+#endif
+
+ /* close off unwanted pipes */
+
+ if(close(proc->inFD[0]) < 0) {
+ logmessage(LOG_WARNING,
+ "main: pipe close(fd=%d) failed: %s\n",
+ proc->inFD[0], osstrerror());
+ }
+ if(close(proc->outFD[1]) < 0) {
+ logmessage(LOG_WARNING,
+ "main: pipe close(fd=%d) failed: %s\n",
+ proc->outFD[1], osstrerror());
+ }
+ }
+ }
+
+ wl_sproc[0].firstServer = 0;
+ wl_sproc[0].lastServer = (wl_numServers <= wl_sprocThresh) ?
+ wl_numServers - 1 : wl_sprocThresh - 1;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG,
+ "Main process will monitor servers 0 to %d\n",
+ wl_sproc[0].lastServer);
+#endif
+
+ for (n=0; n <= wl_sproc[0].lastServer; n++) {
+ if (wl_servers[n].counts.active) {
+ openLogFile(&(wl_servers[n].access));
+ openLogFile(&(wl_servers[n].error));
+ }
+ }
+
+#ifdef PCP_DEBUG
+ __pmtimevalNow(&end);
+ startTime = (end.tv_sec - start.tv_sec) +
+ ((end.tv_usec - start.tv_usec) / 1000000.0);
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG, "Agent started in %f seconds", startTime);
+#endif
+
+ receivePDUs(&desc);
+
+ logmessage(LOG_INFO, "Connection to PMCD closed by PMCD\n");
+ logmessage(LOG_INFO, "Last fetch took %d msec\n", wl_catchupTime);
+ logmessage(LOG_INFO, "Exiting...\n");
+
+ return 0;
+}
diff --git a/src/pmdas/weblog/pmns b/src/pmdas/weblog/pmns
new file mode 100644
index 0000000..fa2038d
--- /dev/null
+++ b/src/pmdas/weblog/pmns
@@ -0,0 +1,306 @@
+/*
+ * Web Performance Metric Domain (PMD) Identifiers
+ *
+ * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Cluster numbers are used by agent to indicate:
+ *
+ * 0 - Does not require any servers to be updated
+ * 1 - Requires all servers to be updated
+ * 2 - Requires only this server to be updated
+ *
+ * Changes to this file must also be carried into the help file and weblog.c
+ * (especially the meta table, web_fetch and web_store).
+ *
+ */
+
+web {
+ config
+ allservers
+ perserver
+}
+
+web.config {
+ numservers WEBSERVER:0:0 /* in the PMDA configuration file */
+ catchup WEBSERVER:0:1 /* maximum catch-up period (secs) */
+ catchuptime WEBSERVER:0:2 /* time spent in catchup (msecs) */
+ check WEBSERVER:0:3 /* inactivity period (secs), before
+ trying to re-open files */
+}
+
+web.allservers {
+ numwatched WEBSERVER:0:4 /* number to be watched */
+ numalive WEBSERVER:0:5 /* number of the watched servers
+ that are apparently alive */
+ requests
+ bytes
+ errors WEBSERVER:1:6
+}
+
+web.allservers.requests {
+ total WEBSERVER:1:7
+ get WEBSERVER:1:8
+ head WEBSERVER:1:9
+ post WEBSERVER:1:10
+ other WEBSERVER:1:11
+ size
+ client
+ cached
+ uncached
+}
+
+web.allservers.bytes {
+ total WEBSERVER:1:12
+ get WEBSERVER:1:13
+ head WEBSERVER:1:14
+ post WEBSERVER:1:15
+ other WEBSERVER:1:16
+ size
+ cached
+ uncached
+}
+
+web.allservers.requests.size {
+ zero WEBSERVER:1:17
+ le3k WEBSERVER:1:18
+ le10k WEBSERVER:1:19
+ le30k WEBSERVER:1:20
+ le100k WEBSERVER:1:21
+ le300k WEBSERVER:1:22
+ le1m WEBSERVER:1:23
+ le3m WEBSERVER:1:24
+ gt3m WEBSERVER:1:25
+ unknown WEBSERVER:1:66
+}
+
+web.allservers.bytes.size {
+ zero WEBSERVER:1:26
+ le3k WEBSERVER:1:27
+ le10k WEBSERVER:1:28
+ le30k WEBSERVER:1:29
+ le100k WEBSERVER:1:30
+ le300k WEBSERVER:1:31
+ le1m WEBSERVER:1:32
+ le3m WEBSERVER:1:33
+ gt3m WEBSERVER:1:34
+}
+
+web.allservers.requests.client {
+ total WEBSERVER:3:1
+}
+
+web.allservers.requests.cached {
+ total WEBSERVER:3:11
+ size
+}
+
+web.allservers.requests.cached.size {
+ zero WEBSERVER:3:12
+ le3k WEBSERVER:3:13
+ le10k WEBSERVER:3:14
+ le30k WEBSERVER:3:15
+ le100k WEBSERVER:3:16
+ le300k WEBSERVER:3:17
+ le1m WEBSERVER:3:18
+ le3m WEBSERVER:3:19
+ gt3m WEBSERVER:3:20
+ unknown WEBSERVER:3:21
+}
+
+web.allservers.requests.uncached {
+ total WEBSERVER:3:31
+ size
+}
+
+web.allservers.requests.uncached.size {
+ zero WEBSERVER:3:32
+ le3k WEBSERVER:3:33
+ le10k WEBSERVER:3:34
+ le30k WEBSERVER:3:35
+ le100k WEBSERVER:3:36
+ le300k WEBSERVER:3:37
+ le1m WEBSERVER:3:38
+ le3m WEBSERVER:3:39
+ gt3m WEBSERVER:3:40
+ unknown WEBSERVER:3:41
+}
+
+web.allservers.bytes.cached {
+ total WEBSERVER:3:51
+ size
+}
+
+web.allservers.bytes.cached.size {
+ zero WEBSERVER:3:52
+ le3k WEBSERVER:3:53
+ le10k WEBSERVER:3:54
+ le30k WEBSERVER:3:55
+ le100k WEBSERVER:3:56
+ le300k WEBSERVER:3:57
+ le1m WEBSERVER:3:58
+ le3m WEBSERVER:3:59
+ gt3m WEBSERVER:3:60
+}
+
+web.allservers.bytes.uncached {
+ total WEBSERVER:3:71
+ size
+}
+
+web.allservers.bytes.uncached.size {
+ zero WEBSERVER:3:72
+ le3k WEBSERVER:3:73
+ le10k WEBSERVER:3:74
+ le30k WEBSERVER:3:75
+ le100k WEBSERVER:3:76
+ le300k WEBSERVER:3:77
+ le1m WEBSERVER:3:78
+ le3m WEBSERVER:3:79
+ gt3m WEBSERVER:3:80
+}
+
+web.perserver {
+ watched WEBSERVER:0:35 /* is this server being watched? */
+ numlogs WEBSERVER:2:36 /* number of logs I can read */
+ requests
+ bytes
+ errors WEBSERVER:2:37
+ logidletime WEBSERVER:2:68
+}
+
+web.perserver.requests {
+ total WEBSERVER:2:38 /* per server */
+ get WEBSERVER:2:39
+ head WEBSERVER:2:40
+ post WEBSERVER:2:41
+ other WEBSERVER:2:42
+ size
+ client
+ cached
+ uncached
+}
+
+web.perserver.bytes {
+ total WEBSERVER:2:43 /* per server */
+ get WEBSERVER:2:44
+ head WEBSERVER:2:45
+ post WEBSERVER:2:46
+ other WEBSERVER:2:47
+ size
+ cached
+ uncached
+}
+
+web.perserver.requests.size {
+ zero WEBSERVER:2:48
+ le3k WEBSERVER:2:49
+ le10k WEBSERVER:2:50
+ le30k WEBSERVER:2:51
+ le100k WEBSERVER:2:52
+ le300k WEBSERVER:2:53
+ le1m WEBSERVER:2:54
+ le3m WEBSERVER:2:55
+ gt3m WEBSERVER:2:56
+ unknown WEBSERVER:2:67
+}
+
+web.perserver.bytes.size {
+ zero WEBSERVER:2:57
+ le3k WEBSERVER:2:58
+ le10k WEBSERVER:2:59
+ le30k WEBSERVER:2:60
+ le100k WEBSERVER:2:61
+ le300k WEBSERVER:2:62
+ le1m WEBSERVER:2:63
+ le3m WEBSERVER:2:64
+ gt3m WEBSERVER:2:65
+}
+
+web.perserver.requests.client {
+ total WEBSERVER:4:1
+}
+
+web.perserver.requests.cached {
+ total WEBSERVER:4:11
+ size
+}
+
+web.perserver.requests.cached.size {
+ zero WEBSERVER:4:12
+ le3k WEBSERVER:4:13
+ le10k WEBSERVER:4:14
+ le30k WEBSERVER:4:15
+ le100k WEBSERVER:4:16
+ le300k WEBSERVER:4:17
+ le1m WEBSERVER:4:18
+ le3m WEBSERVER:4:19
+ gt3m WEBSERVER:4:20
+ unknown WEBSERVER:4:21
+}
+
+web.perserver.requests.uncached {
+ total WEBSERVER:4:31
+ size
+}
+
+web.perserver.requests.uncached.size {
+ zero WEBSERVER:4:32
+ le3k WEBSERVER:4:33
+ le10k WEBSERVER:4:34
+ le30k WEBSERVER:4:35
+ le100k WEBSERVER:4:36
+ le300k WEBSERVER:4:37
+ le1m WEBSERVER:4:38
+ le3m WEBSERVER:4:39
+ gt3m WEBSERVER:4:40
+ unknown WEBSERVER:4:41
+}
+
+web.perserver.bytes.cached {
+ total WEBSERVER:4:51
+ size
+}
+
+web.perserver.bytes.cached.size {
+ zero WEBSERVER:4:52
+ le3k WEBSERVER:4:53
+ le10k WEBSERVER:4:54
+ le30k WEBSERVER:4:55
+ le100k WEBSERVER:4:56
+ le300k WEBSERVER:4:57
+ le1m WEBSERVER:4:58
+ le3m WEBSERVER:4:59
+ gt3m WEBSERVER:4:60
+}
+
+web.perserver.bytes.uncached {
+ total WEBSERVER:4:71
+ size
+}
+
+web.perserver.bytes.uncached.size {
+ zero WEBSERVER:4:72
+ le3k WEBSERVER:4:73
+ le10k WEBSERVER:4:74
+ le30k WEBSERVER:4:75
+ le100k WEBSERVER:4:76
+ le300k WEBSERVER:4:77
+ le1m WEBSERVER:4:78
+ le3m WEBSERVER:4:79
+ gt3m WEBSERVER:4:80
+}
+
diff --git a/src/pmdas/weblog/root b/src/pmdas/weblog/root
new file mode 100644
index 0000000..de85ad7
--- /dev/null
+++ b/src/pmdas/weblog/root
@@ -0,0 +1,10 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root { web }
+
+#include "pmns"
+
diff --git a/src/pmdas/weblog/server.sh b/src/pmdas/weblog/server.sh
new file mode 100755
index 0000000..fc63dfa
--- /dev/null
+++ b/src/pmdas/weblog/server.sh
@@ -0,0 +1,1228 @@
+#! /bin/sh
+#
+# Copyright (c) 2000-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+# Setup some default paths
+_logdir=/var/log
+_msgfil=messages
+
+#
+# Every supported server defines 10 variables which are used by addServer()
+# and installFiles() to create a server specification for the weblog PMDA
+# and a URL for the webping PMDA, respectively.
+#
+# access The full path to the access log
+# accessRegex The regex for the access log
+# errors The full path to the error log
+# errorRegex The regex for the error log
+# serverPath The server path
+# serverDesc A desciption of the type of server
+# serverName The name for the server (must be unique)
+# serverPort Port the server is bound to
+# docs The full path to the document root
+# "$noDocs" - indicates there is no document root
+# http The URL for the server
+# files How to put the HTML files into the doc root
+# "link" - create a soft link
+# "copy" - copy the files
+# "skip" - do not create URLs for this server
+#
+
+do_logs=false
+do_files=false
+do_auto=false
+do_verbose=false
+debug=false
+noDocs="???"
+unknownDocs=""
+docsDir="$PCP_DOC_DIR/pcpweb/"
+link="pcpweb"
+file1="index.html"
+file2="planning.html"
+file3="tasks.html"
+pfx=" "
+skipping="${pfx}skipping..."
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+# Duplicate entry check file
+#
+rm -f $tmp/dup
+touch $tmp/dup
+
+# pv 816562 - try to work around $PCP_DOC_DIR confusion
+[ ! -x $docsDir -a -x /usr/pcp/doc/pcpweb/ ] && docsDir=/usr/pcp/doc/pcpweb/
+
+LOCALHOSTNAME=`hostname`
+
+# web server attribute names (global)
+#
+ATTRLIST="serverPort serverName docs access errors"
+
+# look for netscape stuff here
+#
+NSROOTPATH="${NSROOTPATH-/usr/ns-home:/var/netscape/suitespot:/var/netscape/fasttrack}"
+
+# the Netscape server types we know how to handle
+#
+NSTYPE="httpd https proxy"
+
+# look for old outbox
+#
+OUTBOXPATH="${OUTBOXPATH-/var/mc-httpd}"
+
+# look for old Netscape Proxy Servers here
+#
+NSPROXYPATH="${NSPROXYPATH-/var/ns-proxy}"
+
+# look for NCSA servers here
+#
+NCSAPATH="${NCSAPATH-/var/www}"
+
+# look for Zeus servers here
+#
+ZEUSPATH="${ZEUSPATH-/usr/local/zeus}"
+
+# look for Squid Object caches here
+#
+SQUIDPATH="${SQUIDPATH-/usr/local/squid}"
+
+# look for Apache servers here
+#
+APACHEPATH="${APACHEPATH-/etc/apache2:/etc/httpd}"
+
+# look for anonymous ftp here
+#
+FTPPATH="${FTPPATH:-$_logdir}"
+
+# look for password file here (to determine ~ftp)
+#
+PASSWDPATH="${PASSWDPATH-/etc/passwd}"
+
+# look for SYSLOG here
+#
+SYSLOGPATH="${SYSLOGPATH:-$_logdir/$_msgfil}"
+
+# look for xferlog here (for wu_ftp)
+# XFERLOG is default for wu_ftp
+XFERLOG="${XFERLOG:-$_logdir/xferlog}"
+# WUFTPLOG is used in sgi freeware wu_ftp
+WUFTPLOG="${WUFTPLOG-$_logdir/wu-ftpd.log}"
+
+_getLogFiles()
+{
+ $PCP_ECHO_PROG $PCP_ECHO_N "Full path to access log [$access] ""$PCP_ECHO_C"
+ read ans
+ if [ -n "$ans" ]
+ then
+ access="$ans"
+ if [ ! -f "$access" ]
+ then
+ echo "Warning: $access does not exist at this time."
+ fi
+ fi
+
+ $PCP_ECHO_PROG $PCP_ECHO_N "Full path to error log [$errors] ""$PCP_ECHO_C"
+ read ans
+ if [ -n "$ans" ]
+ then
+ errors="$ans"
+ if [ ! -f "$errors" ]
+ then
+ echo "Warning: $errors does not exist at this time."
+ fi
+ fi
+}
+
+# _addServer_check
+# 1: servername
+# 2: other args for log file
+# 3: server description
+_addServer_check()
+{
+ if grep "$1" $tmp/dup > /dev/null 2>&1
+ then
+ echo "${pfx}Error: already found a server with the name \"$1\""
+ if $do_auto
+ then
+ echo "$skipping"
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "New name for server (or return to skip this server): ""$PCP_ECHO_C"
+ read ans
+ if [ -n "$ans" ]
+ then
+ if grep "$ans" $tmp/dup > /dev/null 2>&1
+ then
+ echo "${pfx}Error: new server name already exists"
+ echo "$skipping"
+ else
+ echo >> $logFile
+ echo "# $3." >> $logFile
+ echo "server $ans $2" >> $logFile
+ echo "$1" >> $tmp/dup
+ fi
+ else
+ echo "$skipping"
+ fi
+ fi
+ else
+ echo >> $logFile
+ echo "# $3." >> $logFile
+ echo "server $1 $2" >> $logFile
+ echo "$1" >> $tmp/dup
+ fi
+}
+
+_addServer()
+{
+ echo "Found $serverDesc at $serverPath"
+ if [ \( -f "$access" -o -c "$access" \) -a \( -f "$errors" -o -c "$errors" \) ]
+ then
+ found="true"
+ if [ -z "$serverPort" ]
+ then
+ echo "${pfx}identified as $serverName"
+ _addServer_check "$serverName" "on $accessRegex $access $errorRegex $errors" "$serverDesc"
+ else
+ echo "${pfx}identified as $serverName:$serverPort"
+ _addServer_check "$serverName:$serverPort" "on $accessRegex $access $errorRegex $errors" "$serverDesc"
+ fi
+ echo
+ else
+ echo "${pfx}Error: log files are not in the expected place:"
+ echo "${pfx} $access"
+ echo "${pfx} $errors"
+ if $do_auto
+ then
+ echo "$skipping"
+ else
+ echo
+ echo "Do you want to specify an alternate location for the log files"
+ $PCP_ECHO_PROG $PCP_ECHO_N "(otherwise this server will be ignored) [y] ""$PCP_ECHO_C"
+ read ans
+ if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ _getLogFiles
+ if [ -z "$serverPort" ]
+ then
+ _addServer_check "$serverName" "on $accessRegex $access $errorRegex $errors" "$serverDesc"
+ else
+ _addServer_check "$serverName:$serverPort" "on $accessRegex $access $errorRegex $errors" "$serverDesc"
+ fi
+ else
+ echo "$skipping"
+ fi
+ fi
+ echo
+ fi
+}
+
+_installFiles()
+{
+
+ echo
+ echo "Found $serverDesc at $serverPath"
+ if [ "$docs" != "$noDocs" ]
+ then
+ if [ "$docs" = "$unknownDocs" ]
+ then
+ echo "${pfx}Error: unable to determine document root"
+ if $do_auto
+ then
+ docs=$unknownDocs
+ echo "$skipping"
+ else
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Path to document root (return to skip HTML link for this server): ""$PCP_ECHO_C"
+ read ans
+ if [ -n "$ans" ]
+ then
+ if [ -d "$ans" ]
+ then
+ docs="$ans"
+ else
+ echo "\"$ans\" is not a directory."
+ echo "No link to HTML files will be created for this server."
+ fi
+ fi
+ fi
+ elif [ ! -d "$docs" ]
+ then
+ echo "${pfx}Error: document root cannot be found at:"
+ echo "${pfx} $docs"
+ if $do_auto
+ then
+ docs=$unknownDocs
+ echo "$skipping"
+ else
+ echo
+ echo "Path to document root (return to skip HTML link for this server)"
+ $PCP_ECHO_PROG $PCP_ECHO_N "$docs: ""$PCP_ECHO_C"
+ read ans
+ if [ -n "$ans" ]
+ then
+ if [ -d $ans ]
+ then
+ docs=$ans
+ else
+ echo "${pfx}Error: \"$ans\" is not a directory."
+ docs=$unknownDocs
+ echo "$skipping"
+ fi
+ else
+ docs=$unknownDocs
+ fi
+ fi
+ else
+ echo "${pfx}document root found at:"
+ echo "${pfx}$docs"
+ fi
+
+ if [ "$docs" != "$unknownDocs" ]
+ then
+ if [ ! -f $docs/$link/$file1 -o ! -f $docs/$link/$file2 -o ! -f $docs/$link/$file3 ]
+ then
+ if [ "$files" = "link" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want a link to some sample HTML files created in this directory [y] ""$PCP_ECHO_C"
+ elif [ "$files" = "copy" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want some sample HTML files installed in this directory [y] ""$PCP_ECHO_C"
+ fi
+
+ read ans
+ if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ if [ "$files" = "link" ]
+ then
+ [ -L $docs/$link ] && rm -f $docs/$link
+ if [ -f $docs/$link ]
+ then
+ echo "${pfx}Error: $docs/$link already exists."
+ echo "$skipping"
+ else
+ ln -s $docsDir $docs/$link
+ fi
+ elif [ "$files" = "copy" ]
+ then
+ if [ ! -f $docs/$link -o -d $docs/$link ]
+ then
+ cp $docsDir/$file1 $docs/$link/$file1
+ cp $docsDir/$file2 $docs/$link/$file2
+ cp $docsDir/$file3 $docs/$link/$file3
+ else
+ echo "${pfx}Error: $docs/$link is not a directory."
+ echo "$skipping"
+ fi
+ fi
+
+ if [ -z "$http" ]
+ then
+ echo "$http/$link/$file1" >> $logFile
+ echo "$http/$link/$file2" >> $logFile
+ echo "$http/$link/$file3" >> $logFile
+ fi
+ fi
+ else
+ echo "${pfx}Note: the link to the sample HTML files already exists."
+ if [ -n "$http" ]
+ then
+ echo "$http/$link/$file1" >> $logFile
+ echo "$http/$link/$file2" >> $logFile
+ echo "$http/$link/$file3" >> $logFile
+ fi
+ fi
+ else
+ if [ "$docs" != "$unknownDocs" ]
+ then
+ echo "$skipping"
+ fi
+ fi
+ else
+ echo "${pfx}Error: no document root, cannot install files."
+ echo "$skipping"
+ fi
+ echo
+}
+
+_switchAction()
+{
+ if $do_verbose
+ then
+ echo "------------------------------------------------------------"
+ echo "access = $access"
+ echo "accessRegex = $accessRegex"
+ echo "errors = $errors"
+ echo "errorRegex = $errorRegex"
+ echo "serverPath = $serverPath"
+ echo "serverDesc = $serverDesc"
+ echo "serverName = $serverName"
+ echo "serverPort = $serverPort"
+ echo "docs = $docs"
+ echo "http = $http"
+ echo "files = $files"
+ echo "------------------------------------------------------------"
+ echo
+ fi
+
+ if $do_logs
+ then
+ _addServer
+ else
+ _installFiles
+ fi
+}
+
+_scan_config()
+{
+ serverPort=
+ serverName=
+ errors=
+ access=
+ docs=
+
+ eval `cat $* 2>/dev/null | $PCP_AWK_PROG '
+NF == 2 && tolower($1) == "port" { print "serverPort=" $2; next }
+NF == 2 && tolower($1) == "errorlog" { print "errors=" $2; next }
+NF == 2 && tolower($1) == "servername" { print "serverName=" $2; next }
+tolower($1) == "init" { for (i=1; i<=NF; i++) {
+ if (match($i, "^access=")) print $i
+ if (match($i, "^global=")) printf "access=%s ",substr($i,8,length($i)-7)
+ }
+ next
+ }
+tolower($0) ~ /fn="document-root"/ || tolower($0) ~ /fn=document-root/ {
+ for (i=1; i<=NF; i++) {
+ if (match($i, "^root="))
+ printf "docs=%s ",substr($i,6,length($i)-5)
+ }
+ next
+ }'`
+}
+
+_netscape_extract()
+{
+ here=`pwd`
+ echo >$tmp/ns
+ for root in `echo "$NSROOTPATH" | sed -e 's/:/ /g'`
+ do
+ for type in $NSTYPE
+ do
+ for dir in $root/$type-*
+ do
+ [ -L "$dir" ] && continue
+ if [ -d "$dir" ]
+ then
+ cd $dir
+ check=`pwd`
+ cd $here
+ match=`grep "^$check " $tmp/ns`
+ if [ ! -z "$match" ]
+ then
+ echo "$match $dir" | $PCP_AWK_PROG '
+ { if ($1 == $2)
+ printf "The server at %s appears to be a link to %s which is already monitored as %s\n", $3, $2, $1
+ else if ($1 == $3) {
+ printf "The server at %s was already detected,\n", $1
+ printf "using the link %s. ", $2
+ }
+ else {
+ printf "The server at %s was already detected,\n", $1
+ printf "using the link %s. The link %s,\n", $2, $3
+ printf "which is also to this server, will be ignored.\n"
+ }
+ }'
+ echo "$skipping"
+ continue
+ fi
+ echo "$check $dir" >>$tmp/ns
+ access=
+ errors=
+ docs=
+ serverName=
+ serverPort=
+
+ if [ ! -f $dir/config/obj.conf ]
+ then
+ echo "Found Netscape $type Server at $dir"
+ echo "${pfx}Error: unable to find configuration file:"
+ echo "${pfx} $dir/config/obj.conf"
+ echo "$skipping"
+ echo
+ continue
+ elif [ ! -f $dir/config/magnus.conf ]
+ then
+ echo "Found Netscape $type Server at $dir"
+ echo "${pfx}Error: unable to find configuration file:"
+ echo "${pfx} $dir/config/magnus.conf"
+ echo "$skipping"
+ echo
+ continue
+ fi
+
+ _scan_config $dir/config/obj.conf $dir/config/magnus.conf
+
+ # fix server name as Netscape often adds a trailing '.'
+ serverName=`echo $serverName | sed -e 's/\.$//'`
+
+ if [ -z "$serverName" -o -z "$serverPort" ]
+ then
+ echo "Found Netscape $type Server at $dir"
+ echo "${pfx}Error: unable to determine server name or port from:"
+ echo "${pfx} $dir/config/magnus.conf"
+ echo "$skipping"
+ echo
+ continue
+ fi
+
+ if [ -z "$access" ]
+ then
+ access=$dir/logs/access
+ echo "Found Netscape $type Server at $dir"
+ echo "${pfx}Warning: unable to determine access log name, assuming:"
+ echo "${pfx} $access"
+ echo
+ fi
+
+ if [ -z "$errors" ]
+ then
+ errors=$dir/logs/errors
+ echo "Found Netscape $type Server at $dir"
+ echo "${pfx}Warning: unable to determine error log name, assuming:"
+ echo "${pfx} $errors"
+ echo
+ fi
+
+ #
+ # figure out if this is a caching server or not
+ #
+ if [ -f $access ]
+ then
+ num_lines=`wc -l $access | $PCP_AWK_PROG '{print $1}'`
+ if [ $num_lines -gt 1 ]
+ then
+ num_fields=`tail -n 1 $access | cut -f3 -d\" | wc -w`
+ if [ $num_fields -eq 11 ]
+ then
+ accessRegex="NS_PROXY"
+ else
+ accessRegex="CERN"
+ fi
+ else
+ accessRegex="CERN"
+ fi
+ fi
+
+ errorRegex="CERN_err"
+ serverPath="$dir"
+ serverDesc="Netscape $type Server"
+ http="GET http://$serverName:$serverPort"
+ files="link"
+
+ _switchAction
+ fi
+ done
+ done
+ done
+}
+
+_zeus_extract()
+{
+ if [ -d $ZEUSPATH ]
+ then
+ if [ -f $ZEUSPATH/server.ini ]
+ then
+ rm -f $tmp/zeus
+ touch $tmp/zeus
+ $PCP_AWK_PROG < $ZEUSPATH/server.ini -v hostname=$LOCALHOSTNAME -v out=$tmp/zeus -v ini=$ZEUSPATH/server.ini -F'=' '
+BEGIN { mode = 0;
+ port = 0;
+ name = "";
+ access = "???";
+ errors = "???";
+ docs = "???";
+ host = hostname;
+ }
+
+/^port/ { if (mode == 0)
+ port=$2;
+ next
+ }
+$1 ~ /\[Admin/ { mode = 1; next }
+$1 ~ /\[Server/ { if (mode == 2) {
+ printf("%s %s %s %d %s %s\n", name, access, errors, port, host, docs) > out;
+ name=""; access="???"; errors="???"; docs="???";
+ host = hostname
+ }
+ else
+ mode = 2;
+
+ i = match($1, " .*]");
+ if (i == 0) {
+ mode = 1
+ }
+ else {
+ name = substr($1, RSTART+1, RLENGTH-2);
+ }
+ next
+ }
+/^logdir/ { if (mode == 2) {
+ access = sprintf("%s/transfer", $2);
+ errors = sprintf("%s/errors", $2);
+ }
+ next
+ }
+/^docroot/ { if (mode == 2)
+ docs = $2;
+ next
+ }
+/^ipname/ { if (mode == 2)
+ host = $2;
+ next
+ }
+END { if (mode == 2)
+ printf("%s %s %s %d %s %s\n", name, access, errors, port, host, docs) > out;
+ }'
+
+ if [ -f $tmp/zeus -a -s $tmp/zeus ]
+ then
+ accessRegex=CERN
+ errorRegex=CERN_err
+ serverPath=$ZEUSPATH
+ files="link"
+ lines=`wc -l $tmp/zeus | $PCP_AWK_PROG '{ print $1 }'`
+ count=1
+ while [ $count -le $lines ]
+ do
+ eval `$PCP_AWK_PROG < $tmp/zeus -v line=$count '
+NR == line { printf("serverName=%s\naccess=%s\nerrors=%s\nserverPort=%d\nhttp=%s\ndocs=%s\n", $1, $2, $3, $4, $5, $6); exit }'`
+
+ serverDesc="Zeus Server $serverName"
+ serverName="zeus-$serverName"
+ http="GET http://$http:$serverPort"
+
+ if [ "$access" = "???" ]
+ then
+ access=$ZEUSPATH/log/transfer
+ echo "Found $serverDesc at $serverPath"
+ echo "${pfx}Warning: unable to determine access log name, assuming:"
+ echo "${pfx} $access"
+ echo
+ fi
+ if [ "$errors" = "???" ]
+ then
+ errors=$ZEUSPATH/log/errors
+ echo "Found $serverDesc at $serverPath"
+ echo "${pfx}Warning: unable to determine error log name, assuming:"
+ echo "${pfx} $errors"
+ echo
+ fi
+ if [ "$docs" = "???" ]
+ then
+ docs=$ZEUSPATH/docroot
+ echo "Found $serverDesc at $serverPath"
+ echo "${pfx}Warning: unable to determine document root, assuming:"
+ echo "${pfx} $docs"
+ echo
+ fi
+
+ _switchAction
+ count=`expr $count + 1`
+ done
+ else
+ echo "Found Zeus Server/s at $ZEUSPATH"
+ echo "${pfx}Error: could not detect any servers in configuration file:"
+ echo "${pfx} $ZEUSPATH/server.ini"
+ echo "$skipping"
+ fi
+
+ else
+ echo "Found Zeus Server at $ZEUSPATH"
+ echo "${pfx}Error: unable to find configuration file:"
+ echo "${pfx} $ZEUSPATH/server.ini"
+ echo "$skipping"
+ fi
+ fi
+}
+
+_squid_extract()
+{
+ if [ -d $SQUIDPATH ]
+ then
+ if [ -f $SQUIDPATH/etc/squid.conf ]
+ then
+ rm -f $tmp/squid
+ touch $tmp/squid
+ $PCP_AWK_PROG < $SQUIDPATH/etc/squid.conf -v hostname=${LOCALHOSTNAME} -v out=$tmp/squid '
+BEGIN { port = 3128;
+ name = hostname;
+ access = "/usr/local/squid/logs/access.log";
+ errors = "/dev/null";
+ host = hostname;
+ }
+
+$1 == "emulate_httpd_log" { if ( $2 == "on" )
+ mode = 1;
+ }
+$1 == "cache_access_log" { access = $2;
+ }
+$1 == "http_port" { port = $2;
+ }
+$1 == "visible_hostname" { name = $2;
+ }
+END { if (mode == 0)
+ printf("SQUID %s %s %s %d %s\n", name, access, errors, port, host) > out;
+ else
+ printf("CERN %s %s %s %d %s\n", name, access, errors, port, host) > out;
+ }'
+
+ if [ -f $tmp/squid -a -s $tmp/squid ]
+ then
+ grep CERN $tmp/squid >/dev/null
+ if [ $? -eq 0 ]
+ then
+ accessRegex=CERN
+ else
+ accessRegex=SQUID
+ fi
+ errorRegex=CERN_err
+ serverPath=$SQUIDPATH
+ files="skip"
+ eval `$PCP_AWK_PROG < $tmp/squid '
+{ printf("serverName=%s\naccess=%s\nerrors=%s\nserverPort=%d\nhttp=%s\n", $2, $3, $4, $5, $6); exit }'`
+
+ serverDesc="Squid Server $serverName"
+ serverName="squid-$serverName"
+ http="GET http://$http:$serverPort"
+ docs=$noDocs
+
+ if [ "$access" = "???" ]
+ then
+ access=$SQUIDPATH/log/transfer
+ echo "Found $serverDesc at $serverPath"
+ echo "${pfx}Warning: unable to determine access log name, assuming:"
+ echo "${pfx} $access"
+ echo
+ fi
+ if [ "$errors" = "???" ]
+ then
+ errors=$SQUIDPATH/log/errors
+ echo "Found $serverDesc at $serverPath"
+ echo "${pfx}Warning: unable to determine error log name, assuming:"
+ echo "${pfx} $errors"
+ echo
+ fi
+
+ _switchAction
+ else
+ echo "Found Squid Server/s at $SQUIDPATH"
+ echo "${pfx}Error: could not detect any servers in configuration file:"
+ echo "${pfx} $SQUIDPATH/squid.conf"
+ echo "$skipping"
+ fi
+
+ else
+ echo "Found Squid Server at $SQUIDPATH"
+ echo "${pfx}Error: unable to find configuration file:"
+ echo "${pfx} $SQUIDPATH/squid.conf"
+ echo "$skipping"
+ fi
+ fi
+}
+
+_apache_extract()
+{
+ for apchroot in `echo "$APACHEPATH" | sed -e 's/:/ /g'`
+ do
+ $debug && echo "_apache_extract: apachroot=$apchroot"
+ if [ -d "$apchroot/sites-available" ]
+ then
+ config="$apchroot/sites-available/"
+ elif [ -d "$apchroot/vhosts.d" ]
+ then
+ config="$apchroot/vhosts.d/"
+ elif [ -d "$apchroot/conf" ]
+ then
+ config="$apchroot/conf/"
+ elif [ -f "$apchroot/httpd.conf" ]
+ then
+ config="$apchroot/httpd.conf"
+ else
+ continue
+ fi
+
+ for config in `echo ${config}*`
+ do
+ $debug && echo "_apache_extract: config=$config"
+ [ -f "$config" ] || continue
+
+ cat $config \
+ | sed -e's/#.*//' -e'/^$/d' \
+ | $PCP_AWK_PROG -v def=`hostname` '
+ BEGIN {
+ curnam=def;
+ names[def] = curnam;
+ ports[def] = 80;
+ }
+ $1 == "<VirtualHost" {
+ sub(">", "", $2);
+ n = split ($2, nm, ":");
+ if ( n == 1 ) {
+ curnam=$2;
+ port=ports[def];
+ } else {
+ if ( length (nm[1]) && nm[1] != "*" ) {
+ curnam = nm[1];
+ } else {
+ curnam = def;
+ }
+ if ( length (nm[2]) ) {
+ port = nm[2];
+ } else {
+ port = ports[def];
+ }
+ }
+ cn=sprintf("%s:%d", curnam, port);
+ names[cn] = curnam;
+ ports[cn] = port;
+ curnam = cn;
+ }
+ $1 == "ServerName" { names[curnam] = $2; }
+ $1 == "Port" { ports[curnam] = $2; }
+ $1 == "</VirtualHost>" { curnam=def; }
+ $1 == "ErrorLog" {
+ path = $2
+ sub (/\$\{APACHE_LOG_DIR\}/, "/var/log/apache2", path)
+ if ( match (path, "/") != 1 ) {
+ erlog[curnam] = sprintf ("%s/%s", "'$apchroot'", path);
+ } else {
+ erlog[curnam] = path;
+ }
+ }
+ $1 == "DocumentRoot" {
+ path = $2
+ sub (/\$\{APACHE_LOG_DIR\}/, "/var/log/apache2", path)
+ if ( match (path, "/") != 1 ) {
+ docs[curnam] = sprintf ("%s/%s", "'$apchroot'", path);
+ } else {
+ docs[curnam] = path;
+ }
+ }
+ $1 == "TransferLog" {
+ path = $2
+ sub (/\$\{APACHE_LOG_DIR\}/, "/var/log/apache2", path)
+ if ( match (path, "/") != 1 ) {
+ tlog[curnam] = sprintf ("%s/%s", "'$apchroot'", path);
+ } else {
+ tlog[curnam] = path;
+ }
+ }
+ $1 == "CustomLog" && ($3 == "common" || $3 == "combined") {
+ path = $2
+ sub (/\$\{APACHE_LOG_DIR\}/, "/var/log/apache2", path)
+ if ( match (path, "/") != 1 ) {
+ tlog[curnam] = sprintf ("%s/%s", "'$apchroot'", path);
+ } else {
+ tlog[curnam] = path;
+ }
+ }
+ END {
+ for ( n in names ) {
+ print names[n], ports[n], tlog[n], erlog[n], docs[n];
+ }
+ }'\
+ | while read serverName serverPort access errors docs ; do
+ $debug && echo "_apache_extract: serverName=$serverName serverPort=$serverPort access=$access errors=$errors docs=$docs"
+
+ accessRegex=CERN
+ errorRegex=CERN_err
+ serverPath="$apchroot"
+ serverDesc="Apache Server"
+ files="copy"
+
+ _switchAction
+ done
+ done
+ done
+}
+
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+
+ -d) # debug
+ debug=true
+ ;;
+
+ -f) # install dummy HTML files in server document root
+ do_files=true
+ if [ $# -gt 1 ]
+ then
+ shift
+ logFile=$1
+ else
+ echo "-f requires the name of log file"
+ exit 1
+ fi
+ ;;
+
+ -l) # generate pmdaweblog configuration file
+ do_logs=true
+ if [ $# -gt 1 ]
+ then
+ shift
+ logFile=$1
+ else
+ echo "-l requires the name of log file"
+ exit 1
+ fi
+ ;;
+
+ -q) # do not prompt for misconfigured servers
+ do_auto=true
+ ;;
+
+ -v) # verbose
+ do_verbose=true
+ ;;
+
+ *) # USAGE
+ echo "Usage: server.sh [-dqv] [-f logFile] [-l logFile]"
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+if [ "$do_logs" = "true" -a "$do_files" = "true" ]
+then
+ echo "May only perform one of the two options at any one time"
+ exit 1
+fi
+
+if $do_files
+then
+ if [ ! -f $docsDir/$file1 -o ! -f $docsDir/$file2 -o ! -f $docsDir/$file3 ]
+ then
+ echo "Some or all of the sample HTML files ($file1, $file2, $file3)"
+ echo "are missing. Cannot continue!"
+ exit 1
+ fi
+fi
+
+_netscape_extract
+
+# Another common place for Netscape servers
+
+if [ -d $OUTBOXPATH ]
+then
+ access=$OUTBOXPATH/logs/access
+ accessRegex="CERN"
+ errors=$OUTBOXPATH/logs/errors
+ errorRegex="CERN_err"
+ serverPath=$OUTBOXPATH
+ serverDesc="Outbox Server"
+ serverName="outbox-$LOCALHOSTNAME"
+ serverPort=
+ docs="$OUTBOXPATH/html"
+ http="GET http://$LOCALHOSTNAME"
+ files="link"
+ _switchAction
+fi
+
+# Netscape Proxy Server
+
+if [ -d $NSPROXYPATH/logs ]
+then
+ access=$NSPROXYPATH/logs/access
+ accessRegex="CERN"
+ errors=$NSPROXYPATH/logs/errors
+ errorRegex="CERN_err"
+ serverPath=$NSPROXYPATH
+ serverDesc="Old Netscape proxy Server"
+ serverName="proxy-$LOCALHOSTNAME"
+ serverPort=
+ docs=$noDocs
+ http=""
+ files="skip"
+ _switchAction
+fi
+
+# Netscape SOCKS Proxy Server
+
+if [ -f $NSROOTPATH/proxy-server/logs/sockd ]
+then
+ access=$NSROOTPATH/proxy-server/logs/sockd
+ accessRegex="NS_SOCKS"
+ errors=/dev/null
+ errorRegex="NS_SOCKS_err"
+ serverPath=$NSROOTPATH/proxy-server
+ serverDesc="Netscape SOCKS Proxy Server"
+ serverName="socks-$LOCALHOSTNAME"
+ serverPort=
+ docs=$noDocs
+ http=""
+ files="skip"
+ _switchAction
+
+ if [ "$do_logs" = "true" ]
+ then
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Would you like to log SOCKS ftp transactions [y] ""$PCP_ECHO_C"
+ read ans
+ if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ access=$NSROOTPATH/proxy-server/logs/sockd
+ accessRegex="NS_FTP"
+ errors=/dev/null
+ errorRegex="NS_FTP_err"
+ serverPath=$NSROOTPATH/proxy-server
+ serverDesc="FTP through Netscape SOCKS Server"
+ serverName="ftp-socks-$LOCALHOSTNAME"
+ serverPort=
+ docs=$noDocs
+ http=""
+ files="skip"
+ _switchAction
+ fi
+ fi
+fi
+
+if [ -f $NSPROXYPATH/logs/sockd ]
+then
+ access=$NSPROXYPATH/logs/sockd
+ accessRegex="NS_SOCKS"
+ errors=/dev/null
+ errorRegex="NS_SOCKS_err"
+ serverPath=$NSPROXYPATH
+ serverDesc="Netscape SOCKS Proxy Server"
+ serverName="socks-$LOCALHOSTNAME"
+ serverPort=
+ docs=$noDocs
+ http=""
+ files="skip"
+ _switchAction
+
+ if [ "$do_logs" = "true" ]
+ then
+ echo
+ $PCP_ECHO_PROG $PCP_ECHO_N "Would you like to log SOCKS ftp transactions [y] ""$PCP_ECHO_C"
+ read ans
+ if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ access=$NSPROXYPATH/logs/sockd
+ accessRegex="NS_FTP"
+ errors=/dev/null
+ errorRegex="NS_FTP_err"
+ serverPath=$NSPROXYPATH
+ serverDesc="FTP through Netscape SOCKS Server"
+ serverName="ftp-socks-$LOCALHOSTNAME"
+ serverPort=
+ docs=$noDocs
+ http=""
+ files="skip"
+ _switchAction
+ fi
+ fi
+fi
+
+# NCSA (or derived) Server
+
+if [ -d $NCSAPATH/server ]
+then
+ access=$NCSAPATH/server/logs/access_log
+ accessRegex="CERN"
+ errors=$NCSAPATH/server/logs/error_log
+ errorRegex="CERN_err"
+ serverPath=$NCSAPATH/server
+ serverDesc="NCSA (or derived) Server"
+ serverName="ncsa-$LOCALHOSTNAME"
+ serverPort=
+ docs="$NCSAPATH/htdocs"
+ http="GET http://$LOCALHOSTNAME"
+ files="link"
+ _switchAction
+fi
+
+# Zeus Server
+
+_zeus_extract
+
+# Squid Server
+
+_squid_extract
+
+# Apache Server
+
+_apache_extract
+
+# Oracle Webserver
+
+if [ -n "$ORACLE_HOME" ]
+then
+ if [ -d $ORACLE_HOME/ows ]
+ then
+ serverPath=$ORACLE_HOME/ows
+ for i in `ls $serverPath/log/sv*.log*`
+ do
+ serverPort=`basename $i | sed 's/sv//' | sed 's/\.log//'`
+ access=$i
+ accessRegex="CERN"
+ errors="$serverPath/ows/log/sv$serverPort.err"
+ errorRegex="CERN_err"
+ serverDesc="Oracle Webserver"
+ serverName="ows-$serverPort"
+ docs="$serverPath/doc"
+ http="GET http://${LOCALHOSTNAME}:$serverPort"
+ files="link"
+ _switchAction
+ done
+ fi
+fi
+
+# Harvest Cache
+
+if [ -n "$HARVEST_HOME" ]
+then
+ serverPath=$HARVEST_HOME
+ serverPort=80
+ access=$serverPath/cache.access.log
+ accessRegex="CERN"
+ errors="$serverPath/cache.log"
+ errorRegex="CERN_err"
+ serverDesc="Harvest Cache"
+ serverName="harvest-cache"
+ docs="$noDocs"
+ http=""
+ files="skip"
+ _switchAction
+elif [ -d /usr/local/harvest ]
+then
+ serverPath=/usr/local/harvest
+ serverPort=80
+ access=$serverPath/cache.access.log
+ accessRegex="CERN"
+ errors="$serverPath/cache.log"
+ errorRegex="CERN_err"
+ serverDesc="Harvest Cache"
+ serverName="harvest-cache"
+ docs="$noDocs"
+ http=""
+ files="skip"
+ _switchAction
+fi
+
+# FTP
+if [ -n "$QUIET_INSTALL" ]; then
+ ans=y
+else
+ echo
+ if [ "$do_logs" = "true" ]
+ then
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you also want to monitor ftp transactions [y] ""$PCP_ECHO_C"
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "Would you like the sample HTML files installed for anonymous ftp [y] ""$PCP_ECHO_C"
+ fi
+ read ans
+fi
+
+if [ -z "$ans" -o "$ans" = "y" -o "$ans" = "Y" ]
+then
+ echo
+ if [ -f $PASSWDPATH ]
+ then
+ if [ -n "$QUIET_INSTALL" ] ; then
+ ans=y
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N "Do you want to monitor wu_ftp [n] ""$PCP_ECHO_C"
+ read ans
+ echo
+ fi
+ if [ "$ans" = "y" -o "$ans" = "Y" ]
+ then
+ wulog="$WUFTPLOG"
+ if [ -f "$WUFTPLOG" ]
+ then
+ wulog="$WUFTPLOG"
+ elif [ -f "$XFERLOG" ]
+ then
+ wulog="$XFERLOG"
+ else
+ if $do_auto
+ then
+ echo "${pfx}Error: log files are not in the expected place:"
+ echo "$pfx $XFERLOG"
+ echo "$pfx or $WUFTPLOG"
+ echo "$skipping"
+ wulog=""
+ fi
+ fi
+
+ access="$wulog"
+ accessRegex="WU_FTP"
+ errors="$SYSLOGPATH"
+ errorRegex="WU_FTP_err"
+ serverDesc="WU_FTP Server"
+ serverName="wu_ftp"
+ serverPath=$FTPPATH
+ serverPort=
+ else
+ access=$SYSLOGPATH
+ accessRegex="SYSLOG_FTP"
+ errors=$SYSLOGPATH
+ errorRegex="SYSLOG_FTP_err"
+ serverDesc="FTP Server"
+ serverName="ftpd"
+ serverPath=$FTPPATH
+ serverPort=
+ fi
+
+ if [ ! -z "$access" ]
+ then
+ docs=""
+ docs=`$PCP_AWK_PROG -F: '$1 == "ftp" { print $6 "/pub"; exit }' < ${PASSWDPATH}`
+ if [ -z "$docs" ]
+ then
+ echo "Found FTP Server at $FTPPATH"
+ echo "${pfx}Error: user ftp is not listed in the password file:"
+ echo "${pfx} $PASSWDPATH"
+ echo "$skipping"
+ else
+ http="GET ftp://$LOCALHOSTNAME/pub"
+ files="copy"
+ _switchAction
+ fi
+ fi
+ else
+ echo "Found FTP Server at $FTPPATH"
+ echo "${pfx}Error: unable to find password file:"
+ echo "${pfx} $PASSWDPATH"
+ echo "$skipping"
+ fi
+fi
+
+if $do_logs
+then
+ :
+else
+ [ -f "$logFile" ] && sort -u $logFile -o $logFile
+fi
diff --git a/src/pmdas/weblog/sproc.c b/src/pmdas/weblog/sproc.c
new file mode 100644
index 0000000..e9fdc7d
--- /dev/null
+++ b/src/pmdas/weblog/sproc.c
@@ -0,0 +1,39 @@
+/*
+ * Web PMDA, based on generic driver for a daemon-based PMDA
+ *
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "weblog.h"
+#if defined(HAVE_PTHREAD_H)
+#include <pthread.h>
+#endif
+#if defined(HAVE_SCHED_H)
+#include <sched.h>
+#endif
+
+#if defined(HAVE_PTHREAD_H)
+static pthread_t sproc_thread;
+
+int sproc (void (*entry) (void *), int flags, void *arg)
+{
+ int retval;
+
+ retval = pthread_create(&sproc_thread, NULL, (void (*))entry, NULL);
+ return retval;
+}
+#endif
diff --git a/src/pmdas/weblog/weblog.c b/src/pmdas/weblog/weblog.c
new file mode 100644
index 0000000..570d104
--- /dev/null
+++ b/src/pmdas/weblog/weblog.c
@@ -0,0 +1,3132 @@
+/*
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "weblog.h"
+#include "domain.h"
+#include <ctype.h>
+#include <sys/stat.h>
+#if defined(HAVE_SYS_RESOURCE_H)
+#include <sys/resource.h>
+#endif
+#if defined(HAVE_SYS_PRCTL_H)
+#include <sys/prctl.h>
+#endif
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+
+/*
+ * Types of metrics, used by fetch to more efficiently calculate metrics
+ */
+
+enum MetaType {
+ wl_globalPtr, wl_offset32, wl_offset64, wl_totalAggregate,
+ wl_serverAggregate, wl_requestMethod, wl_bytesMethod,
+ wl_requestSize, wl_requestCachedSize, wl_requestUncachedSize,
+ wl_bytesSize, wl_bytesCachedSize, wl_bytesUncachedSize,
+ wl_watched, wl_numAlive, wl_nosupport, wl_numMetaTypes
+};
+
+/*
+ * Return code of checkLogFile()
+ */
+
+enum LogFileCode {
+ wl_ok, wl_opened, wl_reopened, wl_closed, wl_unableToOpen, wl_unableToStat,
+ wl_irregularFile, wl_dormant
+};
+
+static WebCount dummyCount;
+
+/*
+ * Instance domain table
+ * This is completed when parsing the config file
+ */
+
+pmdaIndom wl_indomTable[] =
+{
+#define WEBLOG_INDOM 0
+ { WEBLOG_INDOM, 0, 0 }
+};
+
+/*
+ * Metric specific data to help identify each metric during a fetch
+ *
+ * MG: Note: must be in the same order as wl_metric table below.
+ *
+ */
+
+typedef struct {
+ int m_type;
+ __psint_t m_offset;
+} WebMetric;
+
+WebMetric wl_metricInfo[] =
+{
+/* config.numservers */
+ { wl_globalPtr, (__psint_t)&wl_numServers },
+/* config.catchup */
+ { wl_globalPtr, (__psint_t)&wl_refreshDelay },
+/* config.catchuptime */
+ { wl_globalPtr, (__psint_t)&wl_catchupTime },
+/* config.check */
+ { wl_globalPtr, (__psint_t)&wl_chkDelay },
+/* allserves.numwatched */
+ { wl_globalPtr, (__psint_t)&wl_numActive },
+/* allserves.numalive */
+ { wl_numAlive, (__psint_t)0 },
+/* allservers.errors */
+ { wl_totalAggregate, (__psint_t)0 },
+/* allservers.requests.total */
+ { wl_totalAggregate, (__psint_t)1 },
+/* allservers.requests.get */
+ { wl_requestMethod, (__psint_t)wl_httpGet },
+/* allservers.requests.head */
+ { wl_requestMethod, (__psint_t)wl_httpHead },
+/* allservers.requests.post */
+ { wl_requestMethod, (__psint_t)wl_httpPost },
+/* allservers.requests.other */
+ { wl_requestMethod, (__psint_t)wl_httpOther },
+/* allservers.bytes.total */
+ { wl_totalAggregate, (__psint_t)2 },
+/* allservers.bytes.get */
+ { wl_bytesMethod, (__psint_t)wl_httpGet },
+/* allservers.bytes.head */
+ { wl_bytesMethod, (__psint_t)wl_httpHead },
+/* allservers.bytes.post */
+ { wl_bytesMethod, (__psint_t)wl_httpPost },
+/* allservers.bytes.other */
+ { wl_bytesMethod, (__psint_t)wl_httpOther },
+/* allservers.requests.size.zero */
+ { wl_requestSize, (__psint_t)wl_zero },
+/* allservers.requests.size.le3k */
+ { wl_requestSize, (__psint_t)wl_le3k },
+/* allservers.requests.size.le10k */
+ { wl_requestSize, (__psint_t)wl_le10k },
+/* allservers.requests.size.le30k */
+ { wl_requestSize, (__psint_t)wl_le30k },
+/* allservers.requests.size.le100k */
+ { wl_requestSize, (__psint_t)wl_le100k },
+/* allservers.requests.size.le300k */
+ { wl_requestSize, (__psint_t)wl_le300k },
+/* allservers.requests.size.le1m */
+ { wl_requestSize, (__psint_t)wl_le1m },
+/* allservers.requests.size.le3m */
+ { wl_requestSize, (__psint_t)wl_le3m },
+/* allservers.requests.size.gt3m */
+ { wl_requestSize, (__psint_t)wl_gt3m },
+/* allservers.requests.size.unknown */
+ { wl_requestSize, (__psint_t)wl_unknownSize },
+/* allservers.requests.client.total */
+ { wl_totalAggregate, (__psint_t)3 },
+/* allservers.requests.cached.total */
+ { wl_totalAggregate, (__psint_t)4 },
+/* allservers.requests.cached.size.zero */
+ { wl_requestCachedSize, (__psint_t)wl_zero },
+/* allservers.requests.cached.size.le3k */
+ { wl_requestCachedSize, (__psint_t)wl_le3k },
+/* allservers.requests.cached.size.le10k */
+ { wl_requestCachedSize, (__psint_t)wl_le10k },
+/* allservers.requests.cached.size.le30k */
+ { wl_requestCachedSize, (__psint_t)wl_le30k },
+/* allservers.requests.cached.size.le100k */
+ { wl_requestCachedSize, (__psint_t)wl_le100k },
+/* allservers.requests.cached.size.le300k */
+ { wl_requestCachedSize, (__psint_t)wl_le300k },
+/* allservers.requests.cached.size.le1m */
+ { wl_requestCachedSize, (__psint_t)wl_le1m },
+/* allservers.requests.cached.size.le3m */
+ { wl_requestCachedSize, (__psint_t)wl_le3m },
+/* allservers.requests.cached.size.gt3m */
+ { wl_requestCachedSize, (__psint_t)wl_gt3m },
+/* allservers.requests.cached.size.unknown */
+ { wl_requestCachedSize, (__psint_t)wl_unknownSize },
+/* allservers.requests.uncached.total */
+ { wl_totalAggregate, (__psint_t)5 },
+/* allservers.requests.uncached.size.zero */
+ { wl_requestUncachedSize, (__psint_t)wl_zero },
+/* allservers.requests.uncached.size.le3k */
+ { wl_requestUncachedSize, (__psint_t)wl_le3k },
+/* allservers.requests.uncached.size.le10k */
+ { wl_requestUncachedSize, (__psint_t)wl_le10k },
+/* allservers.requests.uncached.size.le30k */
+ { wl_requestUncachedSize, (__psint_t)wl_le30k },
+/* allservers.requests.uncached.size.le100k */
+ { wl_requestUncachedSize, (__psint_t)wl_le100k },
+/* allservers.requests.uncached.size.le300k */
+ { wl_requestUncachedSize, (__psint_t)wl_le300k },
+/* allservers.requests.uncached.size.le1m */
+ { wl_requestUncachedSize, (__psint_t)wl_le1m },
+/* allservers.requests.uncached.size.le3m */
+ { wl_requestUncachedSize, (__psint_t)wl_le3m },
+/* allservers.requests.uncached.size.gt3m */
+ { wl_requestUncachedSize, (__psint_t)wl_gt3m },
+/* allservers.requests.uncached.size.unknown */
+ { wl_requestUncachedSize, (__psint_t)wl_unknownSize },
+/* allservers.bytes.size.zero */
+ { wl_bytesSize, (__psint_t)wl_zero },
+/* allservers.bytes.size.le3k */
+ { wl_bytesSize, (__psint_t)wl_le3k },
+/* allservers.bytes.size.le10k */
+ { wl_bytesSize, (__psint_t)wl_le10k },
+/* allservers.bytes.size.le30k */
+ { wl_bytesSize, (__psint_t)wl_le30k },
+/* allservers.bytes.size.le100k */
+ { wl_bytesSize, (__psint_t)wl_le100k },
+/* allservers.bytes.size.le300k */
+ { wl_bytesSize, (__psint_t)wl_le300k },
+/* allservers.bytes.size.le1m */
+ { wl_bytesSize, (__psint_t)wl_le1m },
+/* allservers.bytes.size.le3m */
+ { wl_bytesSize, (__psint_t)wl_le3m },
+/* allservers.bytes.size.gt3m */
+ { wl_bytesSize, (__psint_t)wl_gt3m },
+/* allservers.bytes.cached.total */
+ { wl_totalAggregate, (__psint_t)6 },
+/* allservers.bytes.cached.size.zero */
+ { wl_bytesCachedSize, (__psint_t)wl_zero },
+/* allservers.bytes.cached.size.le3k */
+ { wl_bytesCachedSize, (__psint_t)wl_le3k },
+/* allservers.bytes.cached.size.le10k */
+ { wl_bytesCachedSize, (__psint_t)wl_le10k },
+/* allservers.bytes.cached.size.le30k */
+ { wl_bytesCachedSize, (__psint_t)wl_le30k },
+/* allservers.bytes.cached.size.le100k */
+ { wl_bytesCachedSize, (__psint_t)wl_le100k },
+/* allservers.bytes.cached.size.le300k */
+ { wl_bytesCachedSize, (__psint_t)wl_le300k },
+/* allservers.bytes.cached.size.le1m */
+ { wl_bytesCachedSize, (__psint_t)wl_le1m },
+/* allservers.bytes.cached.size.le3m */
+ { wl_bytesCachedSize, (__psint_t)wl_le3m },
+/* allservers.bytes.cached.size.gt3m */
+ { wl_bytesCachedSize, (__psint_t)wl_gt3m },
+/* allservers.bytes.uncached.total */
+ { wl_totalAggregate, (__psint_t)7 },
+/* allservers.bytes.uncached.size.zero */
+ { wl_bytesUncachedSize, (__psint_t)wl_zero },
+/* allservers.bytes.uncached.size.le3k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le3k },
+/* allservers.bytes.uncached.size.le10k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le10k },
+/* allservers.bytes.uncached.size.le30k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le30k },
+/* allservers.bytes.uncached.size.le100k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le100k },
+/* allservers.bytes.uncached.size.le300k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le300k },
+/* allservers.bytes.uncached.size.le1m */
+ { wl_bytesUncachedSize, (__psint_t)wl_le1m },
+/* allservers.bytes.uncached.size.le3m */
+ { wl_bytesUncachedSize, (__psint_t)wl_le3m },
+/* allservers.bytes.uncached.size.gt3m */
+ { wl_bytesUncachedSize, (__psint_t)wl_gt3m },
+/* perserver.watched */
+ { wl_watched, (__psint_t)&dummyCount.active },
+/* perserver.numlogs */
+ { wl_offset32, (__psint_t)&dummyCount.numLogs },
+/* perserver.errors */
+ { wl_offset32, (__psint_t)&dummyCount.errors },
+/* perserver.requests.total */
+ { wl_serverAggregate, (__psint_t)0 },
+/* perserver.requests.get */
+ { wl_requestMethod, (__psint_t)wl_httpGet },
+/* perserver.requests.head */
+ { wl_requestMethod, (__psint_t)wl_httpHead },
+/* perserver.requests.post */
+ { wl_requestMethod, (__psint_t)wl_httpPost },
+/* perserver.requests.other */
+ { wl_requestMethod, (__psint_t)wl_httpOther },
+/* perserver.bytes.total */
+ { wl_serverAggregate, (__psint_t)1 },
+/* perserver.bytes.get */
+ { wl_bytesMethod, (__psint_t)wl_httpGet },
+/* perserver.bytes.head */
+ { wl_bytesMethod, (__psint_t)wl_httpHead },
+/* perserver.bytes.post */
+ { wl_bytesMethod, (__psint_t)wl_httpPost },
+/* perserver.bytes.other */
+ { wl_bytesMethod, (__psint_t)wl_httpOther },
+/* perserver.requests.size.zero */
+ { wl_requestSize, (__psint_t)wl_zero },
+/* perserver.requests.size.le3k */
+ { wl_requestSize, (__psint_t)wl_le3k },
+/* perserver.requests.size.le10k */
+ { wl_requestSize, (__psint_t)wl_le10k },
+/* perserver.requests.size.le30k */
+ { wl_requestSize, (__psint_t)wl_le30k },
+/* perserver.requests.size.le100k */
+ { wl_requestSize, (__psint_t)wl_le100k },
+/* perserver.requests.size.le300k */
+ { wl_requestSize, (__psint_t)wl_le300k },
+/* perserver.requests.size.le1m */
+ { wl_requestSize, (__psint_t)wl_le1m },
+/* perserver.requests.size.le3m */
+ { wl_requestSize, (__psint_t)wl_le3m },
+/* perserver.requests.size.gt3m */
+ { wl_requestSize, (__psint_t)wl_gt3m },
+/* perserver.requests.size.unknown */
+ { wl_requestSize, (__psint_t)wl_unknownSize },
+/* perserver.requests.client.total */
+ { wl_serverAggregate, (__psint_t)2 },
+/* perserver.requests.cached.total */
+ { wl_serverAggregate, (__psint_t)3 },
+/* perserver.requests.cached.size.zero */
+ { wl_requestCachedSize, (__psint_t)wl_zero },
+/* perserver.requests.cached.size.le3k */
+ { wl_requestCachedSize, (__psint_t)wl_le3k },
+/* perserver.requests.cached.size.le10k */
+ { wl_requestCachedSize, (__psint_t)wl_le10k },
+/* perserver.requests.cached.size.le30k */
+ { wl_requestCachedSize, (__psint_t)wl_le30k },
+/* perserver.requests.cached.size.le100k */
+ { wl_requestCachedSize, (__psint_t)wl_le100k },
+/* perserver.requests.cached.size.le300k */
+ { wl_requestCachedSize, (__psint_t)wl_le300k },
+/* perserver.requests.cached.size.le1m */
+ { wl_requestCachedSize, (__psint_t)wl_le1m },
+/* perserver.requests.cached.size.le3m */
+ { wl_requestCachedSize, (__psint_t)wl_le3m },
+/* perserver.requests.cached.size.gt3m */
+ { wl_requestCachedSize, (__psint_t)wl_gt3m },
+/* perserver.requests.cached.size.unknown */
+ { wl_requestCachedSize, (__psint_t)wl_unknownSize },
+/* perserver.requests.uncached.total */
+ { wl_serverAggregate, (__psint_t)4 },
+/* perserver.requests.uncached.size.zero */
+ { wl_requestUncachedSize, (__psint_t)wl_zero },
+/* perserver.requests.uncached.size.le3k */
+ { wl_requestUncachedSize, (__psint_t)wl_le3k },
+/* perserver.requests.uncached.size.le10k */
+ { wl_requestUncachedSize, (__psint_t)wl_le10k },
+/* perserver.requests.uncached.size.le30k */
+ { wl_requestUncachedSize, (__psint_t)wl_le30k },
+/* perserver.requests.uncached.size.le100k */
+ { wl_requestUncachedSize, (__psint_t)wl_le100k },
+/* perserver.requests.uncached.size.le300k */
+ { wl_requestUncachedSize, (__psint_t)wl_le300k },
+/* perserver.requests.uncached.size.le1m */
+ { wl_requestUncachedSize, (__psint_t)wl_le1m },
+/* perserver.requests.uncached.size.le3m */
+ { wl_requestUncachedSize, (__psint_t)wl_le3m },
+/* perserver.requests.uncached.size.gt3m */
+ { wl_requestUncachedSize, (__psint_t)wl_gt3m },
+/* perserver.requests.uncached.size.unknown */
+ { wl_requestUncachedSize, (__psint_t)wl_unknownSize },
+/* perserver.bytes.size.zero */
+ { wl_bytesSize, (__psint_t)wl_zero },
+/* perserver.bytes.size.le3k */
+ { wl_bytesSize, (__psint_t)wl_le3k },
+/* perserver.bytes.size.le10k */
+ { wl_bytesSize, (__psint_t)wl_le10k },
+/* perserver.bytes.size.le30k */
+ { wl_bytesSize, (__psint_t)wl_le30k },
+/* perserver.bytes.size.le100k */
+ { wl_bytesSize, (__psint_t)wl_le100k },
+/* perserver.bytes.size.le300k */
+ { wl_bytesSize, (__psint_t)wl_le300k },
+/* perserver.bytes.size.le1m */
+ { wl_bytesSize, (__psint_t)wl_le1m },
+/* perserver.bytes.size.le3m */
+ { wl_bytesSize, (__psint_t)wl_le3m },
+/* perserver.bytes.size.gt3m */
+ { wl_bytesSize, (__psint_t)wl_gt3m },
+/* perserver.bytes.cached.total */
+ { wl_serverAggregate, (__psint_t)5 },
+/* perserver.bytes.cached.size.zero */
+ { wl_bytesCachedSize, (__psint_t)wl_zero },
+/* perserver.bytes.cached.size.le3k */
+ { wl_bytesCachedSize, (__psint_t)wl_le3k },
+/* perserver.bytes.cached.size.le10k */
+ { wl_bytesCachedSize, (__psint_t)wl_le10k },
+/* perserver.bytes.cached.size.le30k */
+ { wl_bytesCachedSize, (__psint_t)wl_le30k },
+/* perserver.bytes.cached.size.le100k */
+ { wl_bytesCachedSize, (__psint_t)wl_le100k },
+/* perserver.bytes.cached.size.le300k */
+ { wl_bytesCachedSize, (__psint_t)wl_le300k },
+/* perserver.bytes.cached.size.le1m */
+ { wl_bytesCachedSize, (__psint_t)wl_le1m },
+/* perserver.bytes.cached.size.le3m */
+ { wl_bytesCachedSize, (__psint_t)wl_le3m },
+/* perserver.bytes.cached.size.gt3m */
+ { wl_bytesCachedSize, (__psint_t)wl_gt3m },
+/* perserver.bytes.uncached.total */
+ { wl_serverAggregate, (__psint_t)6 },
+/* perserver.bytes.uncached.size.zero */
+ { wl_bytesUncachedSize, (__psint_t)wl_zero },
+/* perserver.bytes.uncached.size.le3k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le3k },
+/* perserver.bytes.uncached.size.le10k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le10k },
+/* perserver.bytes.uncached.size.le30k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le30k },
+/* perserver.bytes.uncached.size.le100k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le100k },
+/* perserver.bytes.uncached.size.le300k */
+ { wl_bytesUncachedSize, (__psint_t)wl_le300k },
+/* perserver.bytes.uncached.size.le1m */
+ { wl_bytesUncachedSize, (__psint_t)wl_le1m },
+/* perserver.bytes.uncached.size.le3m */
+ { wl_bytesUncachedSize, (__psint_t)wl_le3m },
+/* perserver.bytes.uncached.size.gt3m */
+ { wl_bytesUncachedSize, (__psint_t)wl_gt3m },
+/* perserver.logidletime */
+ { wl_offset32, (__psint_t)&dummyCount.modTime },
+};
+
+/*
+ * all metrics supported in this PMDA - one table entry for each
+ */
+
+static pmdaMetric wl_metrics[] = {
+
+/*
+ * web.config
+ */
+
+/* config.numservers */
+{ (void *)0,
+ { PMDA_PMID(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, 0) } },
+
+/* config.catchup */
+{ (void *)0,
+ { PMDA_PMID(0,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } },
+
+/* config.catchuptime */
+{ (void *)0,
+ { PMDA_PMID(0,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } },
+
+/* config.check */
+{ (void *)0,
+ { PMDA_PMID(0,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } },
+
+/*
+ * web.allservers
+ */
+
+/* allserves.numwatched */
+{ (void *)0,
+ { PMDA_PMID(0,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, 0) } },
+
+/* allserves.numalive */
+{ (void *)0,
+ { PMDA_PMID(0,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, 0) } },
+
+/* allservers.errors */
+{ (void *)0,
+ { PMDA_PMID(1,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.total */
+{ (void *)0,
+ { PMDA_PMID(1,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.get */
+{ (void *)0,
+ { PMDA_PMID(1,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.head */
+{ (void *)0,
+ { PMDA_PMID(1,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.post */
+{ (void *)0,
+ { PMDA_PMID(1,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.other */
+{ (void *)0,
+ { PMDA_PMID(1,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.bytes.total */
+{ (void *)0,
+ { PMDA_PMID(1,12), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.get */
+{ (void *)0,
+ { PMDA_PMID(1,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.head */
+{ (void *)0,
+ { PMDA_PMID(1,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.post */
+{ (void *)0,
+ { PMDA_PMID(1,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.other */
+{ (void *)0,
+ { PMDA_PMID(1,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.requests.size.zero */
+{ (void *)0,
+ { PMDA_PMID(1,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(1,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(1,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(1,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(1,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(1,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(1,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(1,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(1,25), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(1,66), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.client.total */
+{ (void *)0,
+ { PMDA_PMID(3,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.total */
+{ (void *)0,
+ { PMDA_PMID(3,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(3,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(3,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(3,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(3,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(3,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(3,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(3,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(3,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(3,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.cached.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(3,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.total */
+{ (void *)0,
+ { PMDA_PMID(3,31), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(3,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(3,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(3,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(3,35), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(3,36), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(3,37), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(3,38), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(3,39), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(3,40), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.requests.uncached.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(3,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* allservers.bytes.size.zero */
+{ (void *)0,
+ { PMDA_PMID(1,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(1,27), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(1,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(1,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(1,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(1,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(1,32), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(1,33), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(1,34), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.total */
+{ (void *)0,
+ { PMDA_PMID(3,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(3,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(3,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(3,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(3,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(3,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(3,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(3,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(3,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.cached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(3,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.total */
+{ (void *)0,
+ { PMDA_PMID(3,71), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(3,72), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(3,73), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(3,74), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(3,75), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(3,76), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(3,77), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(3,78), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(3,79), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* allservers.bytes.uncached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(3,80), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/*
+ * web.perserver
+ */
+
+/* perserver.watched */
+{ (void *)0,
+ { PMDA_PMID(0,35), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) } },
+
+/* perserver.numlogs */
+{ (void *)0,
+ { PMDA_PMID(2,36), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.errors */
+{ (void *)0,
+ { PMDA_PMID(2,37), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.total */
+{ (void *)0,
+ { PMDA_PMID(2,38), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.get */
+{ (void *)0,
+ { PMDA_PMID(2,39), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.head */
+{ (void *)0,
+ { PMDA_PMID(2,40), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.post */
+{ (void *)0,
+ { PMDA_PMID(2,41), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.other */
+{ (void *)0,
+ { PMDA_PMID(2,42), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.bytes.total */
+{ (void *)0,
+ { PMDA_PMID(2,43), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.get */
+{ (void *)0,
+ { PMDA_PMID(2,44), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.head */
+{ (void *)0,
+ { PMDA_PMID(2,45), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.post */
+{ (void *)0,
+ { PMDA_PMID(2,46), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.other */
+{ (void *)0,
+ { PMDA_PMID(2,47), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.requests.size.zero */
+{ (void *)0,
+ { PMDA_PMID(2,48), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(2,49), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(2,50), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(2,51), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(2,52), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(2,53), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(2,54), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(2,55), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(2,56), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(2,67), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.client.total */
+{ (void *)0,
+ { PMDA_PMID(4,1), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.total */
+{ (void *)0,
+ { PMDA_PMID(4,11), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(4,12), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(4,13), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(4,14), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(4,15), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(4,16), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(4,17), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(4,18), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(4,19), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(4,20), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.cached.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(4,21), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.total */
+{ (void *)0,
+ { PMDA_PMID(4,31), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(4,32), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(4,33), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(4,34), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(4,35), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(4,36), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(4,37), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(4,38), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(4,39), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(4,40), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.requests.uncached.size.unknown */
+{ (void *)0,
+ { PMDA_PMID(4,41), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) } },
+
+/* perserver.bytes.size.zero */
+{ (void *)0,
+ { PMDA_PMID(2,57), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(2,58), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(2,59), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(2,60), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(2,61), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(2,62), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(2,63), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(2,64), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(2,65), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.total */
+{ (void *)0,
+ { PMDA_PMID(4,51), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(4,52), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(4,53), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(4,54), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(4,55), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(4,56), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(4,57), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(4,58), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(4,59), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.cached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(4,60), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.total */
+{ (void *)0,
+ { PMDA_PMID(4,71), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.zero */
+{ (void *)0,
+ { PMDA_PMID(4,72), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le3k */
+{ (void *)0,
+ { PMDA_PMID(4,73), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le10k */
+{ (void *)0,
+ { PMDA_PMID(4,74), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le30k */
+{ (void *)0,
+ { PMDA_PMID(4,75), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le100k */
+{ (void *)0,
+ { PMDA_PMID(4,76), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le300k */
+{ (void *)0,
+ { PMDA_PMID(4,77), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le1m */
+{ (void *)0,
+ { PMDA_PMID(4,78), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.le3m */
+{ (void *)0,
+ { PMDA_PMID(4,79), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+
+/* perserver.bytes.uncached.size.gt3m */
+{ (void *)0,
+ { PMDA_PMID(4,80), PM_TYPE_U64, WEBLOG_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) } },
+/*
+ * Added in PCPWEB 1.1.1
+ */
+
+/* perserver.logidletime */
+{ (void *)0,
+ { PMDA_PMID(2,68), PM_TYPE_U32, WEBLOG_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0) } },
+
+};
+
+/* number of metrics */
+static int numMetrics = (sizeof(wl_metrics)/sizeof(wl_metrics[0]));
+
+/* number of instance domains */
+static int numIndoms = sizeof(wl_indomTable)/sizeof(wl_indomTable[0]);
+
+/* mask to get the cluster from a PMID */
+static int _clusterMask = (1<<22) - (1<<10);
+
+/* refresh all logs if wl_updateAll != 0 */
+int wl_updateAll = 0;
+
+/* time in milliseconds to update all the logs */
+__uint32_t wl_catchupTime = 0;
+
+/* time log refresh was started */
+time_t wl_timeOfRefresh = 0;
+
+/* flag to indicate DSO or Daemon */
+int wl_isDSO = 0;
+
+/* request size categories */
+long wl_sizes[] = {
+ 0, 3*1024, 10*1024, 30*1024, 100*1024, 300*1024, 1024*1024, 3*1024*1024, 0
+};
+
+#define BUFFER_LEN 2048
+
+static pmdaExt *extp; /* set in web_init() */
+
+#ifdef HAVE_SIGHUP
+/*
+ * Signal handler for an sproc receiving TERM (probably from parent)
+ */
+static void
+onhup(int s)
+{
+ _exit(s != SIGHUP);
+}
+#endif
+
+/*
+ * Replacement for fgets using the FileInfo structure
+ */
+
+int
+wl_gets(FileInfo *fip, char **line)
+{
+ char *p;
+ int nch;
+ int sts;
+
+ if (fip->filePtr < 0) {
+ return -1;
+ }
+
+ p = fip->bp;
+
+more:
+ while (p < fip->bend) {
+ if (*p == '\n') {
+ /* newline, we are done */
+ *p++ = '\0';
+ *line = fip->bp;
+ fip->bp = p;
+ return p - *line;
+ }
+ p++;
+ }
+
+ /* out the end of the buffer, and no newline */
+ nch = fip->bend - fip->bp;
+ if (nch == FIBUFSIZE) {
+ /* buffer full, and no newline! ... truncate and return */
+ fip->buf[FIBUFSIZE-1] = '\n';
+ p = &fip->buf[FIBUFSIZE-1];
+ goto more;
+ }
+ if (nch)
+ /* shuffle partial line to start of buffer */
+ memcpy(fip->buf, fip->bp, nch);
+ fip->bp = fip->buf;
+ fip->bend = &fip->buf[nch];
+
+ /* refill */
+ sts = read(fip->filePtr, fip->bend, FIBUFSIZE-nch);
+ if (sts <= 0) {
+ /* no more, either terminate last line, or really return status */
+ if (nch) {
+ *fip->bend = '\n';
+ sts = 1;
+ }
+ else {
+ return sts;
+ }
+ }
+ p = fip->bend;
+ fip->bend = &fip->bend[sts];
+ goto more;
+}
+
+/*
+ * Open a log file and seek to the end
+ */
+
+int
+openLogFile(FileInfo *theFile)
+{
+ int diff = theFile->filePtr;
+ char *line = (char *)0;
+
+ theFile->filePtr = open(theFile->fileName, O_RDONLY);
+
+ if (theFile->filePtr == -1) {
+ if (theFile->filePtr != diff) {
+ logmessage(LOG_ERR, "openLogFile: open %s: %s\n",
+ theFile->fileName, osstrerror());
+ }
+ return -1;
+ }
+
+ if (fstat(theFile->filePtr, &(theFile->fileStat)) < 0) {
+ logmessage(LOG_ERR, "openLogFile: stat for %s: %s\n",
+ theFile->fileName, osstrerror());
+ wl_close(theFile->filePtr);
+ return -1;
+ }
+
+ logmessage(LOG_INFO, "%s opened (fd=%d, inode=%d)\n",
+ theFile->fileName,
+ theFile->filePtr,
+ theFile->fileStat.st_ino);
+
+ /* throw away last line in file */
+ if (theFile->fileStat.st_size != 0) {
+ lseek(theFile->filePtr, -2L, SEEK_END);
+ wl_gets(theFile, &line);
+ }
+
+ if (fstat(theFile->filePtr, &(theFile->fileStat)) < 0) {
+ logmessage(LOG_ERR, "openLogFile: update stat for %s: %s\n",
+ theFile->fileName, osstrerror());
+ wl_close(theFile->filePtr);
+ return -1;
+ }
+
+/*
+* Check and warn if a log file has not been modified in the last 24 hours,
+* as this may indicate something is wrong with the PMDA's configuration,
+* or the Web server's configuration
+*/
+
+ diff = time((time_t*)0) - theFile->fileStat.st_mtime;
+ if (diff > DORMANT_WARN) {
+ logmessage(LOG_WARNING,
+ "log file %s has not been modified for at least %d days",
+ theFile->fileName,
+ diff / DORMANT_WARN);
+ }
+
+ return 0;
+}
+
+/*
+ * Check the log file is still the correct log file.
+ *
+ * If the file has not been modified in wl_chkDelay seconds, then reopen
+ * the file and compare the inodes.
+ * Otherwise the current inode and size of the file are checked.
+ *
+ * Returns a LogFileCode indicating the status of the log file.
+ */
+
+static int
+checkLogFile(FileInfo *theFile,
+ struct stat *tmpStat)
+{
+ int tmpFd = -1;
+ int result = wl_ok;
+
+/*
+ * File is closed, if enough time has elasped since last attempt, try to
+ * open it
+ */
+
+ if (theFile->filePtr < 0)
+ {
+ if (wl_timeOfRefresh - theFile->lastActive > wl_chkDelay)
+ {
+ theFile->lastActive = wl_timeOfRefresh;
+ if (openLogFile(theFile) < 0)
+ result = wl_unableToOpen;
+ else
+ result = wl_opened;
+ }
+ else
+ result = wl_closed;
+ }
+
+/* Get the file stat info on the open file */
+
+ if (theFile->filePtr >= 0) {
+ if (fstat(theFile->filePtr, tmpStat) < 0) {
+ logmessage(LOG_ERR, "checkLogFile: stat on open %s: %s\n",
+ theFile->fileName, osstrerror());
+ wl_close(theFile->filePtr);
+ result = wl_unableToStat;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ logmessage(LOG_DEBUG,
+ "checkLogFile: could not stat %s\n",
+ theFile->fileName);
+ }
+#endif
+
+ }
+ }
+
+/*
+ * Check that we are dealing with a regular file. If is a character
+ * device or directory etc just ignore it.
+ */
+
+ if (result == wl_ok && !(theFile->fileStat.st_mode & S_IFREG)) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG,
+ "%s is not a regular file. Skipping...\n",
+ theFile->fileName);
+#endif
+
+ result = wl_irregularFile;
+ }
+
+/*
+ * Check that the size hasn't gotten any smaller e.g. from ftruncate(2)
+ */
+
+ if (result == wl_ok && tmpStat->st_size < theFile->fileStat.st_size) {
+
+ logmessage(LOG_WARNING,
+ "%s stat - inode %d, size %d -> %d\n",
+ theFile->fileName,
+ theFile->fileStat.st_ino,
+ theFile->fileStat.st_size,
+ tmpStat->st_size);
+
+ result = wl_reopened;
+ }
+
+/*
+ * File was already open, check to see if it hasn't been modified, and
+ * that the last time we checked it was > wl_chkDelay ago.
+ */
+
+ if (result == wl_ok &&
+ tmpStat->st_mtime == theFile->fileStat.st_mtime &&
+ wl_timeOfRefresh - theFile->lastActive > wl_chkDelay) {
+
+ tmpFd = open(theFile->fileName, O_RDONLY);
+
+ if (tmpFd < 0) {
+ logmessage(LOG_ERR,
+ "checkLogFile: 2nd open to %s: %s\n",
+ theFile->fileName,
+ osstrerror());
+ wl_close(theFile->filePtr);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ logmessage(LOG_DEBUG,
+ "checkLogFile: could not check %s\n",
+ theFile->fileName);
+ }
+#endif
+
+ }
+ else if (fstat(tmpFd, tmpStat) < 0) {
+ logmessage(LOG_ERR,
+ "checkLogFile: stat on inactive %s: %s\n",
+ theFile->fileName,
+ osstrerror());
+ wl_close(theFile->filePtr);
+ result = wl_unableToStat;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ logmessage(LOG_DEBUG,
+ "checkLogFile: could not stat inactive %s\n",
+ theFile->fileName);
+ }
+#endif
+
+ }
+ else if (tmpStat->st_ino != theFile->fileStat.st_ino) {
+
+ logmessage(LOG_WARNING,
+ "%s inactive - inode %d -> %d\n",
+ theFile->fileName,
+ theFile->fileStat.st_ino,
+ tmpStat->st_ino);
+
+ result = wl_reopened;
+ }
+ else
+ theFile->lastActive = wl_timeOfRefresh;
+
+
+ if (tmpFd >= 0)
+ close(tmpFd);
+ }
+
+
+/*
+ * File needs to be reopened due to change in inode, smaller size, or lack
+ * of activity
+ */
+
+ if (result == wl_reopened) {
+
+ theFile->lastActive = wl_timeOfRefresh;
+
+ wl_close(theFile->filePtr);
+
+ if (openLogFile(theFile) < 0) {
+
+ logmessage(LOG_WARNING,
+ "checkLogFile: unable to reopen %s\n",
+ theFile->fileName);
+ result = wl_unableToOpen;
+ }
+
+/* update the stat information using new file desc */
+
+ else if (fstat(theFile->filePtr, tmpStat) < 0) {
+ logmessage(LOG_ERR,
+ "checkLogFile - stat on reopened %s: %s\n",
+ theFile->fileName,
+ osstrerror());
+ wl_close(theFile->filePtr);
+ result = wl_unableToStat;
+ }
+ }
+
+/* if the size has increased in the logs, then change the lastActive time */
+
+ if (result == wl_ok && tmpStat->st_mtime > theFile->fileStat.st_mtime) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "%s grew %d bytes\n",
+ theFile->fileName,
+ tmpStat->st_size - theFile->fileStat.st_size);
+#endif
+ theFile->lastActive = wl_timeOfRefresh;
+ }
+
+ return result;
+}
+
+/*
+ * Main function for sprocs. Contains an infinite loop selecting on the pipe from the
+ * main process. Anything on the pipe indicates a refresh is required.
+ */
+
+void
+sprocMain(void *sprocNum)
+{
+ int mySprocNum = *((int*)sprocNum);
+ int i = 0;
+ int sts = 0;
+ WebSproc *sprocData = &wl_sproc[mySprocNum];
+ WebServer *server = (WebServer*)0;
+
+ /* Pause a sec' so the output log doesn't get mucked up */
+ sleep(1);
+
+#ifdef HAVE_SIGHUP
+ /* SIGHUP when the parent dies */
+ signal(SIGHUP, onhup);
+#endif
+
+#ifdef HAVE_PRCTL
+#ifdef HAVE_PR_TERMCHILD
+ prctl(PR_TERMCHILD);
+#elif HAVE_PR_SET_PDEATHSIG
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+#endif
+#endif
+
+/* close channel to pmcd */
+ if (__pmSocketIPC(extp->e_infd))
+ __pmCloseSocket(extp->e_infd);
+ else if (close(extp->e_infd) < 0) {
+ logmessage(LOG_ERR, "sprocMain: pmcd ch. close(fd=%d) failed: %s\n",
+ extp->e_infd, osstrerror());
+ }
+ if (__pmSocketIPC(extp->e_outfd))
+ __pmCloseSocket(extp->e_outfd);
+ else if (close(extp->e_outfd) < 0) {
+ logmessage(LOG_ERR, "sprocMain: pmcd ch. close(fd=%d) failed: %s\n",
+ extp->e_outfd, osstrerror());
+ }
+
+/* close pipes to main process which are not to be used */
+
+ if(close(sprocData->inFD[1]) < 0) {
+ logmessage(LOG_ERR, "sprocMain[%d]: pipe close(fd=%d) failed: %s\n",
+ mySprocNum, sprocData->inFD[1], osstrerror());
+ }
+ if(close(sprocData->outFD[0]) < 0) {
+ logmessage(LOG_ERR, "sprocMain[%d]: pipe close(fd=%d) failed: %s\n",
+ mySprocNum, sprocData->outFD[0], osstrerror());
+ }
+
+/* open up all file descriptors */
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Sproc %d started for servers %d to %d\n",
+ mySprocNum,
+ sprocData->firstServer,
+ sprocData->lastServer);
+#endif
+
+ for (i=sprocData->firstServer; i<=sprocData->lastServer; i++)
+ {
+ server = &wl_servers[i];
+ if (server->counts.active) {
+ openLogFile(&(server->access));
+ openLogFile(&(server->error));
+ }
+ }
+
+/* wait for message from pmda to probe files */
+
+ for (;;) {
+ sts = read(sprocData->inFD[0], &i, sizeof(i));
+ if (sts <= 0) {
+ logmessage(LOG_ERR, "Sproc[%d] read(fd=%d) failed: %s\n",
+ mySprocNum, sprocData->inFD[0], osstrerror());
+ exit(1);
+ }
+ refresh(sprocData);
+ sts = write(sprocData->outFD[1], &i, sizeof(i));
+ if (sts <= 0) {
+ logmessage(LOG_ERR, "Sproc[%d] write(fd=%d) failed: %s\n",
+ sprocData->outFD[1], mySprocNum, osstrerror());
+ exit(1);
+ }
+ }
+}
+
+/*
+ * Refresh all the server log files that this process monitors.
+ * Any entries are parsed, categorised and added to the appropriate metrics.
+ */
+
+void
+refresh(WebSproc* proc)
+{
+ struct stat tmpStat;
+
+ WebServer *server = (WebServer *)0;
+ WebCount *count = (WebCount *)0;
+ FileInfo *accessFile = (FileInfo *)0;
+ FileInfo *errorFile = (FileInfo *)0;
+
+ char *line = (char *)0;
+ char *end = (char *)0;
+ int httpMethod = 0;
+ long size = 0;
+ int sizeIndex = 0;
+ int newLength = 0;
+ int i = 0;
+ int sts = 0;
+ int result = wl_ok;
+ int ok = 0;
+ time_t currentTime;
+ size_t nmatch = 5;
+ regmatch_t pmatch[5];
+
+
+ currentTime = time((time_t*)0);
+
+/* iterate through each flagged server */
+
+ for (i=proc->firstServer; i<=proc->lastServer; i++) {
+
+ server = &(wl_servers[i]);
+ accessFile = &(server->access);
+ errorFile = &(server->error);
+
+ if ((server->update || wl_updateAll) && server->counts.active) {
+
+ server->counts.numLogs = 0;
+
+/* check access log still exists */
+
+ result = checkLogFile(accessFile, &tmpStat);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG,
+ "checkLogFile returned %d for server %d (access)\n",
+ result,
+ i);
+#endif
+
+/* scan access log */
+
+ if (result == wl_ok || result == wl_reopened ||
+ result == wl_opened) {
+
+ server->counts.numLogs++;
+ server->counts.modTime = (__uint32_t)(currentTime -
+ tmpStat.st_mtime);
+
+ while (accessFile->fileStat.st_size < tmpStat.st_size) {
+
+ sts = wl_gets(accessFile, &line);
+ if (sts <= 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG,
+ "Short read of %s by %d bytes\n",
+ accessFile->fileName,
+ tmpStat.st_size - accessFile->fileStat.st_size);
+#endif
+
+ if (sts == 0) {
+ logmessage(LOG_WARNING,
+ "refresh %s: unexpected eof\n",
+ accessFile->fileName);
+ }
+ else {
+ logmessage(LOG_ERR, "refresh %s: %s\n",
+ accessFile->fileName, osstrerror());
+ }
+
+ wl_close(accessFile->filePtr);
+ accessFile->lastActive -= wl_chkDelay;
+ break;
+ }
+
+ accessFile->fileStat.st_size += sts;
+
+ if (proc->strLength == 0 || proc->strLength <= sts)
+ newLength = sts > 255 ? ((sts / 256) + 1) * 256 : 256;
+ else
+ newLength = proc->strLength;
+
+ if (newLength > proc->strLength)
+ {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Resizing strings from %d to %d bytes\n",
+ proc->strLength,
+ newLength);
+ }
+#endif
+ proc->methodStr = (char*)realloc(proc->methodStr,
+ newLength * sizeof(char));
+ proc->sizeStr = (char*)realloc(proc->sizeStr,
+ newLength * sizeof(char));
+ proc->c_statusStr = (char*)realloc(proc->c_statusStr,
+ newLength * sizeof(char));
+ proc->s_statusStr = (char*)realloc(proc->s_statusStr,
+ newLength * sizeof(char));
+ proc->strLength = newLength;
+ }
+
+ if (proc->methodStr == (char *)0 ||
+ proc->sizeStr == (char *)0 ||
+ proc->c_statusStr == (char *)0 ||
+ proc->s_statusStr == (char *)0 ) {
+ logmessage(LOG_ERR,
+ "Unable to allocate %d bytes to strings",
+ newLength);
+ proc->strLength = 0;
+ if (proc->methodStr != (char *)0)
+ free(proc->methodStr);
+ if (proc->sizeStr != (char *)0)
+ free(proc->sizeStr);
+ if (proc->c_statusStr != (char *)0)
+ free(proc->c_statusStr);
+ if (proc->s_statusStr != (char *)0)
+ free(proc->s_statusStr);
+
+ break;
+ }
+
+ ok = 0;
+
+ if (wl_regexTable[accessFile->format].posix_regexp) {
+ if (regexec(wl_regexTable[accessFile->format].regex,
+ line, nmatch, pmatch, 0) == 0) {
+
+ if(pmatch[1].rm_so < 0 || pmatch[2].rm_so < 0) {
+ logmessage(LOG_ERR,
+ "failed to match method and size: %s\n",
+ line);
+ continue;
+ }
+
+ if(server->counts.extendedp) {
+ if(pmatch[3].rm_so < 0 || pmatch[4].rm_so < 0) {
+ logmessage(LOG_ERR,
+ "failed to match status codes: %s\n",
+ line);
+ continue;
+ }
+ }
+
+ line[pmatch[wl_regexTable[accessFile->format].methodPos].rm_eo] = '\0';
+ strncpy(proc->methodStr, &line[pmatch[wl_regexTable[accessFile->format].methodPos].rm_so],
+ (pmatch[wl_regexTable[accessFile->format].methodPos].rm_eo -
+ pmatch[wl_regexTable[accessFile->format].methodPos].rm_so) + 1);
+
+ line[pmatch[wl_regexTable[accessFile->format].sizePos].rm_eo] = '\0';
+ strncpy(proc->sizeStr, &line[pmatch[wl_regexTable[accessFile->format].sizePos].rm_so],
+ (pmatch[wl_regexTable[accessFile->format].sizePos].rm_eo -
+ pmatch[wl_regexTable[accessFile->format].sizePos].rm_so) + 1);
+
+ if(server->counts.extendedp) {
+ line[pmatch[wl_regexTable[accessFile->format].c_statusPos].rm_eo] = '\0';
+ strncpy(proc->c_statusStr, &line[pmatch[wl_regexTable[accessFile->format].c_statusPos].rm_so],
+ (pmatch[wl_regexTable[accessFile->format].c_statusPos].rm_eo -
+ pmatch[wl_regexTable[accessFile->format].c_statusPos].rm_so) + 1);
+
+ line[pmatch[wl_regexTable[accessFile->format].s_statusPos].rm_eo] = '\0';
+ strncpy(proc->s_statusStr, &line[pmatch[wl_regexTable[accessFile->format].s_statusPos].rm_so],
+ (pmatch[wl_regexTable[accessFile->format].s_statusPos].rm_eo -
+ pmatch[wl_regexTable[accessFile->format].s_statusPos].rm_so) + 1);
+ } else {
+ proc->c_statusStr[0] = '\0';
+ proc->s_statusStr[0] = '\0';
+ }
+ ok = 1;
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Regex failed on %s\n", line);
+#endif
+ }
+#ifdef NON_POSIX_REGEX
+ else if (regex(wl_regexTable[accessFile->format].np_regex,
+ line, proc->methodStr, proc->sizeStr, proc->c_statusStr, proc->s_statusStr) != NULL) {
+ ok = 1;
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Regex failed on %s\n", line);
+#endif
+#endif
+ if ( ok ) {
+
+ for (line = proc->methodStr; *line; line++)
+ *line = toupper((int)*line);
+
+ httpMethod = wl_httpOther;
+ switch(proc->methodStr[0]) {
+ case 'G':
+ if(strcmp(proc->methodStr, "GET") == 0) {
+ httpMethod = wl_httpGet;
+ }
+ break;
+ case 'O':
+ if(strcmp(proc->methodStr, "O") == 0) {
+ httpMethod = wl_httpGet;
+ }
+ break;
+ case 'H':
+ if(strcmp(proc->methodStr, "HEAD") == 0) {
+ httpMethod = wl_httpHead;
+ }
+ break;
+ case 'P':
+ if(strcmp(proc->methodStr, "POST") == 0 ||
+ strcmp(proc->methodStr, "PUT") == 0) {
+ httpMethod = wl_httpPost;
+ }
+ break;
+ case 'I':
+ if(strcmp(proc->methodStr, "I") == 0) {
+ httpMethod = wl_httpPost;
+ }
+ break;
+ }
+
+ if (strcmp(proc->sizeStr, "-") == 0 ||
+ strcmp(proc->sizeStr, " ") == 0) {
+ size = 0;
+ sizeIndex = wl_unknownSize;
+ }
+ else {
+ size = strtol(proc->sizeStr, &end, 10);
+ if (*end != '\0') {
+ logmessage(LOG_ERR, "Bad size (%s) @ %s",
+ proc->sizeStr,
+ line);
+ continue;
+ }
+
+ for (sizeIndex = 0;
+ sizeIndex < wl_gt3m && size > wl_sizes[sizeIndex];
+ sizeIndex++);
+ }
+
+ count = &(server->counts);
+ count->methodReq[httpMethod]++;
+ count->methodBytes[httpMethod] += size;
+
+ count->sizeReq[sizeIndex]++;
+ count->sizeBytes[sizeIndex] += size;
+
+ count->sumReq++;
+ count->sumBytes += size;
+
+ if(server->counts.extendedp == 1) {
+ /* common extended format */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, line=%s [CEF]\n M: %s S: %s CS: %s, SS: %s",
+ i,
+ line,
+ proc->methodStr,
+ proc->sizeStr,
+ proc->c_statusStr,
+ proc->s_statusStr);
+ }
+#endif
+
+ /*
+ * requested page is not in client/browser cache, nor in the
+ * server's cache so it has been fetched from the remote server
+ */
+ if(strcmp(proc->c_statusStr, "200") == 0 &&
+ strcmp(proc->s_statusStr, "200") == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, REMOTE fetch: of %.0f bytes\n",
+ i,
+ atof(proc->sizeStr));
+ }
+#endif
+ /*
+ * now bucket the size
+ */
+ if (strcmp(proc->sizeStr, "-") == 0 ||
+ strcmp(proc->sizeStr, " ") == 0) {
+ size = 0;
+ sizeIndex = wl_unknownSize;
+ }
+ else {
+ size = strtol(proc->sizeStr, &end, 10);
+ if (*end != '\0') {
+ logmessage(LOG_ERR, "Bad size (%s) @ %s",
+ proc->sizeStr,
+ line);
+ continue;
+ }
+
+ for (sizeIndex = 0;
+ sizeIndex < wl_gt3m && size > wl_sizes[sizeIndex];
+ sizeIndex++);
+ }
+ count->uncached_sumReq++;
+ count->uncached_sumBytes += size;
+ count->uncached_sizeReq[sizeIndex]++;
+ count->uncached_sizeBytes[sizeIndex] += size;
+
+ }
+
+ /*
+ * requested page is not in client/browser cache, but is in the
+ * server's cache so it is just returned to the client (a cache hit)
+ */
+ if(strcmp(proc->c_statusStr, "200") == 0 &&
+ (strcmp(proc->s_statusStr, "304") == 0 ||
+ strcmp(proc->s_statusStr, "-") == 0)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, CACHE return: of %.0f bytes\n",
+ i,
+ atof(proc->sizeStr));
+ }
+#endif
+ /*
+ * now bucket the size
+ */
+ if (strcmp(proc->sizeStr, "-") == 0 ||
+ strcmp(proc->sizeStr, " ") == 0) {
+ size = 0;
+ sizeIndex = wl_unknownSize;
+ }
+ else {
+ size = strtol(proc->sizeStr, &end, 10);
+ if (*end != '\0') {
+ logmessage(LOG_ERR, "Bad size (%s) @ %s",
+ proc->sizeStr,
+ line);
+ continue;
+ }
+
+ for (sizeIndex = 0;
+ sizeIndex < wl_gt3m && size > wl_sizes[sizeIndex];
+ sizeIndex++);
+ }
+ count->cached_sumReq++;
+ count->cached_sumBytes += size;
+ count->cached_sizeReq[sizeIndex]++;
+ count->cached_sizeBytes[sizeIndex] += size;
+
+ }
+
+ /*
+ * requested page is in client/browser cache
+ */
+ if(strcmp(proc->c_statusStr, "304") == 0 &&
+ (strcmp(proc->s_statusStr, "304") == 0 ||
+ strcmp(proc->s_statusStr, "-") == 0)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, CLIENT hit\n",
+ i);
+ }
+#endif
+ count->client_sumReq++;
+ }
+ } else if(server->counts.extendedp == 2) {
+ /* default squid format */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, line=%s [squid]\n M: %s S: %s CS: %s, SS: %s",
+ i,
+ line,
+ proc->methodStr,
+ proc->sizeStr,
+ proc->c_statusStr,
+ proc->s_statusStr);
+ }
+#endif
+
+ /*
+ * requested page is not in client/browser cache, nor in the
+ * server's cache so it has been fetched from the remote server
+ */
+ if(strcmp(proc->c_statusStr, "200") == 0 &&
+ (strstr(proc->s_statusStr, "_MISS") != NULL ||
+ strstr(proc->s_statusStr, "_CLIENT_REFRESH") != NULL ||
+ strstr(proc->s_statusStr, "_SWAPFAIL") != NULL)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, REMOTE fetch: of %.0f bytes\n",
+ i,
+ atof(proc->sizeStr));
+ }
+#endif
+ /*
+ * now bucket the size
+ */
+ if (strcmp(proc->sizeStr, "-") == 0 ||
+ strcmp(proc->sizeStr, " ") == 0) {
+ size = 0;
+ sizeIndex = wl_unknownSize;
+ }
+ else {
+ size = strtol(proc->sizeStr, &end, 10);
+ if (*end != '\0') {
+ logmessage(LOG_ERR, "Bad size (%s) @ %s",
+ proc->sizeStr,
+ line);
+ continue;
+ }
+
+ for (sizeIndex = 0;
+ sizeIndex < wl_gt3m && size > wl_sizes[sizeIndex];
+ sizeIndex++);
+ }
+ count->uncached_sumReq++;
+ count->uncached_sumBytes += size;
+ count->uncached_sizeReq[sizeIndex]++;
+ count->uncached_sizeBytes[sizeIndex] += size;
+
+ }
+
+ /*
+ * requested page is not in client/browser cache, but is in the
+ * server's cache so it is just returned to the client (a cache hit)
+ */
+ if(strcmp(proc->c_statusStr, "200") == 0 &&
+ strstr(proc->s_statusStr, "_HIT") != NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, CACHE return: of %.0f bytes\n",
+ i,
+ atof(proc->sizeStr));
+ }
+#endif
+ /*
+ * now bucket the size
+ */
+ if (strcmp(proc->sizeStr, "-") == 0 ||
+ strcmp(proc->sizeStr, " ") == 0) {
+ size = 0;
+ sizeIndex = wl_unknownSize;
+ }
+ else {
+ size = strtol(proc->sizeStr, &end, 10);
+ if (*end != '\0') {
+ logmessage(LOG_ERR, "Bad size (%s) @ %s",
+ proc->sizeStr,
+ line);
+ continue;
+ }
+
+ for (sizeIndex = 0;
+ sizeIndex < wl_gt3m && size > wl_sizes[sizeIndex];
+ sizeIndex++);
+ }
+ count->cached_sumReq++;
+ count->cached_sumBytes += size;
+ count->cached_sizeReq[sizeIndex]++;
+ count->cached_sizeBytes[sizeIndex] += size;
+ }
+
+ /*
+ * requested page is in client/browser cache
+ */
+ if(strcmp(proc->c_statusStr, "304") == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, CLIENT hit\n",
+ i);
+ }
+#endif
+ count->client_sumReq++;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG,
+ "Access: Server=%d, line=%s\n method=%s [%d], size=%s=%d [%d]\n",
+ i,
+ line,
+ proc->methodStr,
+ httpMethod,
+ proc->sizeStr,
+ size,
+ sizeIndex);
+ }
+#endif
+
+ }
+ }
+ accessFile->fileStat = tmpStat;
+ }
+
+ result = checkLogFile(errorFile, &tmpStat);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG,
+ "checkLogFile returned %d for server %d (error)\n",
+ result,
+ i);
+#endif
+
+ /* scan error log */
+
+ if (result == wl_ok || result == wl_reopened ||
+ result == wl_opened) {
+
+ server->counts.numLogs++;
+
+ while (errorFile->fileStat.st_size < tmpStat.st_size) {
+ sts = wl_gets(errorFile, &line);
+ if (sts <= 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ logmessage(LOG_DEBUG, "%s was %d bytes short\n",
+ errorFile->fileName,
+ tmpStat.st_size -
+ errorFile->fileStat.st_size);
+#endif
+
+ if (sts < 0) {
+ logmessage(LOG_ERR, "refresh %s: %s\n",
+ errorFile->fileName, osstrerror());
+ }
+ else {
+ logmessage(LOG_WARNING,
+ "refresh %s: unexpected eof\n",
+ errorFile->fileName);
+ }
+
+ wl_close(errorFile->filePtr);
+ errorFile->lastActive -= wl_chkDelay;
+ break;
+ }
+
+ errorFile->fileStat.st_size += sts;
+
+ if(wl_regexTable[errorFile->format].posix_regexp) {
+ if (regexec(wl_regexTable[errorFile->format].regex,
+ line, nmatch, pmatch, 0) == 0) {
+ server->counts.errors++;
+ }
+#ifdef NON_POSIX_REGEX
+ } else {
+ if (regex(wl_regexTable[errorFile->format].np_regex,
+ line, proc->methodStr, proc->sizeStr) != NULL) {
+ server->counts.errors++;
+ }
+#endif
+ }
+ }
+ errorFile->fileStat = tmpStat;
+ }
+ }
+
+ /* check to see if a server is inactive but has a file open. It may
+ have just been deactivated */
+
+ else if ((server->update || wl_updateAll) && !server->counts.active) {
+
+ if (accessFile->filePtr >= 0) {
+
+ logmessage(LOG_WARNING,
+ "Closing inactive server %d access file: %s\n",
+ i,
+ accessFile->fileName);
+
+ wl_close(accessFile->filePtr);
+ }
+
+ if (errorFile->filePtr >= 0) {
+
+ logmessage(LOG_WARNING,
+ "Closing inactive server %d error file: %s\n",
+ i,
+ errorFile->fileName);
+
+ wl_close(errorFile->filePtr);
+ }
+ }
+ }
+}
+
+/*
+ * Initialise the indom and meta tables
+ * Check that we can do direct mapping.
+ */
+
+/*
+ * Mark servers that are required in the latest profile.
+ */
+static int
+web_profile(__pmProfile *prof, pmdaExt *ext)
+{
+ pmdaIndom *idp = wl_indomTable;
+ int j;
+
+ ext->e_prof = prof;
+ for (j = 0; j < idp->it_numinst; j++) {
+ if (__pmInProfile(idp->it_indom, prof, idp->it_set[j].i_inst))
+ wl_servers[j].update = 1;
+ else
+ wl_servers[j].update = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Probe servers for log file changes.
+ * Only those servers that are marked will be requsted. Therefore, if an sproc does
+ * not have any marked servers, it will not be signalled.
+ * NOTE: The main process completes its refresh before signalling the other sprocs.
+ */
+
+void
+probe(void)
+{
+ int i = 0;
+ int j = 0;
+ int sts = 0;
+ int dummy = 1;
+ int sprocsUsed = 0;
+ int nfds = 0;
+ fd_set rfds;
+ fd_set tmprfds;
+ int thisFD;
+ WebSproc *sprocData = (WebSproc*)0;
+ struct timeval theTime;
+
+ __pmtimevalNow(&theTime);
+
+ wl_timeOfRefresh = theTime.tv_sec;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "Starting probe at %d\n", wl_timeOfRefresh);
+#endif
+
+ FD_ZERO(&rfds);
+
+/*
+ * Determine which sprocs have servers that must be refreshed.
+ * Add those sprocs pipes to the file descriptor list.
+ */
+
+ for (i=1; i<=wl_numSprocs; i++) {
+ sprocData = &wl_sproc[i];
+
+ if (!wl_updateAll) {
+ for (j=sprocData->firstServer; j<=sprocData->lastServer; j++)
+ if (wl_servers[j].update)
+ break;
+ }
+ else {
+ for (j=sprocData->firstServer; j<=sprocData->lastServer; j++)
+ if (wl_servers[j].counts.active)
+ break;
+ }
+
+ if (j > sprocData->lastServer) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Skipping sproc %d\n", i);
+#endif
+ continue;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG,
+ "Told sproc %d to probe for at least server %d\n",
+ i,
+ j);
+#endif
+
+ sprocsUsed++;
+ thisFD = sprocData->outFD[0];
+ sts = write(sprocData->inFD[1], &dummy, sizeof(dummy));
+ if (sts < 0) {
+ logmessage(LOG_ERR, "Error on fetch write(fd=%d): %s",
+ sprocData->inFD[1], osstrerror());
+ exit(1);
+ }
+
+ FD_SET(thisFD, &rfds);
+ nfds = nfds < (thisFD + 1) ? thisFD + 1 : nfds;
+ }
+
+/*
+ * Check that we have to update the main process servers
+ */
+
+ sprocData = &wl_sproc[0];
+ if (!wl_updateAll) {
+ for (j=sprocData->firstServer; j<=sprocData->lastServer; j++)
+ if (wl_servers[j].update)
+ break;
+ }
+ else {
+ for (j=sprocData->firstServer; j<=sprocData->lastServer; j++)
+ if (wl_servers[j].counts.active)
+ break;
+ }
+
+ if (j <= sprocData->lastServer) {
+ refresh(&wl_sproc[0]);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Done probe for 0 to %d\n",
+ sprocData->lastServer);
+#endif
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL2)
+ logmessage(LOG_DEBUG, "Skipping refresh of main process\n");
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ logmessage(LOG_DEBUG, "Waiting for reply from %d out of %d sprocs\n",
+ sprocsUsed,
+ wl_numSprocs);
+ }
+#endif
+
+/*
+ * Wait for all sprocs to reply
+ * Note: This could get into a hard select loop if an sproc losses it
+ */
+
+ for (i=0; i<sprocsUsed;) {
+ memcpy(&tmprfds, &rfds, sizeof(tmprfds));
+ sts = select(nfds, &tmprfds, (fd_set*)0, (fd_set*)0,
+ (struct timeval*)0);
+ if (sts < 0) {
+ logmessage(LOG_ERR, "Error on fetch select: %s", netstrerror());
+ exit(1);
+ }
+ else if (sts == 0)
+ continue;
+
+ i += sts;
+ for (j=1; j<=wl_numSprocs; j++) {
+ sprocData = &wl_sproc[j];
+ thisFD = sprocData->outFD[0];
+
+ if (FD_ISSET(thisFD, &tmprfds)) {
+ FD_CLR(sprocData->outFD[0], &rfds);
+ sts = read(thisFD, &dummy, sizeof(dummy));
+ if (sts < 0) {
+ logmessage(LOG_ERR, "Error on fetch read: %s",
+ osstrerror());
+ exit(1);
+ }
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "Finished probe\n");
+#endif
+}
+
+/*
+ * Refresh all servers
+ * Usually called if no fetches have been received after a set time
+ */
+
+void
+refreshAll(void)
+{
+ struct timeval before;
+ struct timeval after;
+
+ wl_updateAll = 1;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ logmessage(LOG_DEBUG, "Starting a refreshAll\n");
+ }
+#endif
+
+ __pmtimevalNow(&before);
+ probe();
+
+ __pmtimevalNow(&after);
+ wl_catchupTime = (after.tv_sec - before.tv_sec) * 1000;
+ wl_catchupTime += (after.tv_usec - before.tv_usec) / 1000;
+
+ wl_updateAll = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "Probed all logs, took %d msec\n",
+ wl_catchupTime);
+#endif
+
+}
+
+/*
+ * Build a pmResult table of the requested metrics
+ */
+
+static int
+web_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *ext)
+{
+ int i; /* over pmidlist[] */
+ int j; /* over vset->vlist[] */
+ int s; /* over server */
+ int sts;
+ int need;
+ int inst;
+ int numval;
+ static pmResult *res = (pmResult *)0;
+ static int maxnpmids = 0;
+ pmValueSet *vset = (pmValueSet *)0;
+ pmDesc *dp = (pmDesc *)0;
+ __pmID_int *pmidp;
+ pmAtomValue atom;
+ int haveValue = 0;
+ int type;
+ __psint_t m_offset = 0; /* initialize to pander to gcc */
+ int m_type = 0; /* initialize to pander to gcc */
+ int cluster;
+ __uint32_t tmp32;
+ __uint64_t tmp64;
+
+/* determine if the total aggregates are required, which forces a refresh
+ of all servers, and if a probe is require at all */
+
+ j = 0;
+ for (i = 0; i < numpmid; i++) {
+ pmidp = (__pmID_int *)&pmidlist[i];
+ if (pmidp->cluster == 1)
+ break;
+ else
+ j += pmidp->cluster;
+ }
+
+ if (i < numpmid) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "web_fetch: refreshAll\n");
+#endif
+ refreshAll();
+ }
+ else if (j) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "web_fetch: probe\n");
+#endif
+ probe();
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL1)
+ logmessage(LOG_DEBUG, "web_fetch: no probes required\n");
+#endif
+
+
+ if (numpmid > maxnpmids) {
+ if (res != (pmResult *)0)
+ free(res);
+
+/* (numpmid - 1) because there's room for one valueSet in a pmResult */
+
+ need = sizeof(pmResult) + (numpmid - 1) * sizeof(pmValueSet *);
+ if ((res = (pmResult *) malloc(need)) == (pmResult *)0)
+ return -oserror();
+ maxnpmids = numpmid;
+ }
+
+ res->timestamp.tv_sec = 0;
+ res->timestamp.tv_usec = 0;
+ res->numpmid = numpmid;
+
+/*
+ * Get each corresponding metric from the meta table.
+ * Check that the metric has the correct cluster
+ */
+
+ for (i = 0; i < numpmid; i++) {
+
+ pmidp = (__pmID_int*)&pmidlist[i];
+ dp = (pmDesc *)0;
+
+ if (ext->e_direct) {
+
+ if (pmidp->item < numMetrics &&
+ pmidlist[i] == wl_metrics[pmidp->item].m_desc.pmid) {
+
+ dp = &wl_metrics[pmidp->item].m_desc;
+ m_offset = wl_metricInfo[pmidp->item].m_offset;
+ m_type = wl_metricInfo[pmidp->item].m_type;
+ }
+ }
+ else {
+ for (j = 0; j<numMetrics; j++) {
+ if (wl_metrics[j].m_desc.pmid == pmidlist[i]) {
+
+ dp = &wl_metrics[j].m_desc;
+ m_offset = wl_metricInfo[j].m_offset;
+ m_type = wl_metricInfo[j].m_type;
+ break;
+ }
+ }
+ }
+
+/*
+ * count the number of instances in profile
+ */
+
+ if (dp == (pmDesc *)0)
+ numval = PM_ERR_PMID;
+ else {
+ if (dp->indom != PM_INDOM_NULL) {
+ numval = 0;
+ __pmdaStartInst(dp->indom, ext);
+ while(__pmdaNextInst(&inst, ext)) {
+ numval++;
+ }
+ }
+ else {
+ numval = 1;
+ }
+ }
+
+
+ /* Must use individual malloc()s because of pmFreeResult() */
+
+ if (numval >= 1)
+ res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ (numval - 1)*sizeof(pmValue));
+ else
+ res->vset[i] = vset = (pmValueSet*)malloc(sizeof(pmValueSet) -
+ sizeof(pmValue));
+
+ if (vset == (pmValueSet *)0) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+
+ vset->pmid = pmidlist[i];
+ vset->numval = numval;
+ vset->valfmt = PM_VAL_INSITU;
+ if (vset->numval <= 0)
+ continue;
+
+ if (dp->indom == PM_INDOM_NULL)
+ inst = PM_IN_NULL;
+ else {
+ __pmdaStartInst(dp->indom, ext);
+ __pmdaNextInst(&inst, ext);
+ }
+
+ type = dp->type;
+ pmidp = (__pmID_int *)&pmidlist[i];
+ j = 0;
+
+ do {
+ if (j == numval) {
+
+ /* more instances than expected! */
+
+ numval++;
+ res->vset[i] = vset = (pmValueSet *)realloc(vset,
+ sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue));
+ if (vset == (pmValueSet *)0) {
+ if (i) {
+ res->numpmid = i;
+ __pmFreeResultValues(res);
+ }
+ return -oserror();
+ }
+ }
+
+ vset->vlist[j].inst = inst;
+
+ cluster = (dp->pmid & _clusterMask) >> 10;
+ haveValue = 1;
+
+ switch(m_type) {
+ case wl_globalPtr:
+ atom.ul = *(__uint32_t *)(m_offset);
+ break;
+
+ case wl_offset32:
+ if (wl_servers[inst].counts.active)
+ atom.ul = *((__uint32_t *)(((__psint_t)(&(wl_servers[inst].counts))) + m_offset));
+ else
+ haveValue = 0;
+ break;
+
+ case wl_offset64:
+ if (wl_servers[inst].counts.active)
+ atom.ull = *((__uint64_t *)(((__psint_t)(&(wl_servers[inst].counts))) + m_offset));
+ else
+ haveValue = 0;
+ break;
+
+ case wl_totalAggregate:
+
+ if (wl_numActive == 0)
+ haveValue = 0;
+ else {
+ switch(m_offset) {
+ case 0:
+ /* errors */
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active) {
+ tmp32 += wl_servers[s].counts.errors;
+ }
+ }
+ atom.ul = tmp32;
+ break;
+ case 1:
+ /* requests */
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active) {
+ tmp32 += wl_servers[s].counts.sumReq;
+ }
+ }
+ atom.ul = tmp32;
+ break;
+ case 2:
+ /* bytes */
+ tmp64 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active) {
+ tmp64 += wl_servers[s].counts.sumBytes;
+ }
+ }
+ atom.ull = tmp64;
+ break;
+ case 3:
+ /* client hit requests */
+ tmp32 = haveValue = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp32 += wl_servers[s].counts.client_sumReq;
+ }
+ }
+ atom.ul = tmp32;
+ break;
+ case 4:
+ /* cached hit requests */
+ tmp32 = haveValue = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp32 += wl_servers[s].counts.cached_sumReq;
+ }
+ }
+ atom.ul = tmp32;
+ break;
+ case 5:
+ /* cached hit requests */
+ tmp32 = haveValue = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp32 += wl_servers[s].counts.uncached_sumReq;
+ }
+ }
+ atom.ul = tmp32;
+ break;
+ case 6:
+ /* cached hit bytes */
+ tmp64 = haveValue = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp64 += wl_servers[s].counts.cached_sumBytes;
+ }
+ }
+ atom.ull = tmp64;
+ break;
+ case 7:
+ /* uncached bytes */
+ tmp64 = haveValue = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp64 += wl_servers[s].counts.uncached_sumBytes;
+ }
+ }
+ atom.ull = tmp64;
+ break;
+ default:
+ break;
+ }
+
+ }
+ break;
+
+ case wl_serverAggregate:
+
+ if (wl_servers[inst].counts.active == 0)
+ haveValue = 0;
+ else switch(m_offset) {
+ case 0:
+ /* requests */
+ atom.ul = wl_servers[inst].counts.sumReq;
+ break;
+ case 1:
+ /* bytes */
+ atom.ull = wl_servers[inst].counts.sumBytes;
+ break;
+ case 2:
+ /* client hit requests */
+ if( wl_servers[inst].counts.extendedp ) {
+ atom.ul = wl_servers[inst].counts.client_sumReq;
+ } else
+ haveValue = 0;
+ break;
+ case 3:
+ /* cache hit requests */
+ if( wl_servers[inst].counts.extendedp ) {
+ atom.ul = wl_servers[inst].counts.cached_sumReq;
+ } else
+ haveValue = 0;
+ break;
+ case 4:
+ /* uncached requests */
+ if( wl_servers[inst].counts.extendedp ) {
+ atom.ul = wl_servers[inst].counts.uncached_sumReq;
+ } else
+ haveValue = 0;
+ break;
+ case 5:
+ /* cached bytes */
+ if( wl_servers[inst].counts.extendedp ) {
+ atom.ull = wl_servers[inst].counts.cached_sumBytes;
+ } else
+ haveValue = 0;
+ break;
+ case 6:
+ /* uncached bytes */
+ if( wl_servers[inst].counts.extendedp ) {
+ atom.ull = wl_servers[inst].counts.uncached_sumBytes;
+ } else
+ haveValue = 0;
+ default:
+ break;
+ }
+ break;
+
+ case wl_requestMethod:
+
+ if (cluster == 1) { /* all servers */
+ if (wl_numActive == 0)
+ haveValue = 0;
+ else {
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++)
+ if (wl_servers[s].counts.active)
+ tmp32 += wl_servers[s].counts.methodReq[m_offset];
+ atom.ul = tmp32;
+ }
+ }
+ else if (wl_servers[inst].counts.active)
+ atom.ul = wl_servers[inst].counts.methodReq[m_offset];
+ else
+ haveValue = 0;
+
+ break;
+
+ case wl_bytesMethod:
+ if (cluster == 1) { /* all servers */
+ if (wl_numActive == 0)
+ haveValue = 0;
+ else {
+ tmp64 = 0;
+ for (s = 0; s < wl_numServers; s++)
+ if (wl_servers[s].counts.active)
+ tmp64 += wl_servers[s].counts.methodBytes[m_offset];
+ atom.ull = tmp64;
+ }
+ }
+ else if (wl_servers[inst].counts.active)
+ atom.ull = wl_servers[inst].counts.methodBytes[m_offset];
+ else
+ haveValue = 0;
+ break;
+
+ case wl_requestSize:
+ if (cluster == 1) { /* all servers */
+ if (wl_numActive == 0)
+ haveValue = 0;
+ else {
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++)
+ if (wl_servers[s].counts.active)
+ tmp32 += wl_servers[s].counts.sizeReq[m_offset];
+ atom.ul = tmp32;
+ }
+ }
+ else if (wl_servers[inst].counts.active)
+ atom.ul = wl_servers[inst].counts.sizeReq[m_offset];
+ else
+ haveValue = 0;
+ break;
+
+ case wl_bytesSize:
+ if (cluster == 1) { /* all servers */
+ if (wl_numActive == 0)
+ haveValue = 0;
+ else {
+ tmp64 = 0;
+ for (s = 0; s < wl_numServers; s++)
+ if (wl_servers[s].counts.active)
+ tmp64 += wl_servers[s].counts.sizeBytes[m_offset];
+ atom.ull = tmp64;
+ }
+ }
+ else if (wl_servers[inst].counts.active)
+ atom.ull = wl_servers[inst].counts.sizeBytes[m_offset];
+ else
+ haveValue = 0;
+ break;
+
+ case wl_requestCachedSize:
+ haveValue = 0;
+ if (cluster == 3) { /* all servers */
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++)
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp32 += wl_servers[s].counts.cached_sizeReq[m_offset];
+ }
+ atom.ul = tmp32;
+ }
+ else if (wl_servers[inst].counts.active &&
+ wl_servers[inst].counts.extendedp) {
+ haveValue = 1;
+ atom.ul = wl_servers[inst].counts.cached_sizeReq[m_offset];
+ }
+ break;
+
+ case wl_bytesCachedSize:
+ haveValue = 0;
+ if (cluster == 3) { /* all servers */
+ tmp64 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp64 += wl_servers[s].counts.cached_sizeBytes[m_offset];
+ }
+ }
+ atom.ull = tmp64;
+ }
+ else if (wl_servers[inst].counts.active &&
+ wl_servers[inst].counts.extendedp) {
+ haveValue = 1;
+ atom.ull = wl_servers[inst].counts.cached_sizeBytes[m_offset];
+ }
+ break;
+
+ case wl_requestUncachedSize:
+ haveValue = 0;
+ if (cluster == 3) { /* all servers */
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp ) {
+ haveValue = 1;
+ tmp32 += wl_servers[s].counts.uncached_sizeReq[m_offset];
+ }
+ }
+ atom.ul = tmp32;
+ }
+ else if (wl_servers[inst].counts.active &&
+ wl_servers[inst].counts.extendedp) {
+ haveValue = 1;
+ atom.ul = wl_servers[inst].counts.uncached_sizeReq[m_offset];
+ }
+ break;
+
+ case wl_bytesUncachedSize:
+ haveValue = 0;
+ if (cluster == 3) { /* all servers */
+ tmp64 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active &&
+ wl_servers[s].counts.extendedp) {
+ haveValue = 1;
+ tmp64 += wl_servers[s].counts.uncached_sizeBytes[m_offset];
+ }
+ }
+ atom.ull = tmp64;
+ }
+ else if (wl_servers[inst].counts.active &&
+ wl_servers[inst].counts.extendedp) {
+ haveValue = 1;
+ atom.ull = wl_servers[inst].counts.uncached_sizeBytes[m_offset];
+ }
+ break;
+
+ case wl_watched:
+ atom.ul = *((__uint32_t *)(((__psint_t)(&(wl_servers[inst].counts))) + m_offset));
+ break;
+ case wl_numAlive:
+ tmp32 = 0;
+ for (s = 0; s < wl_numServers; s++) {
+ if (wl_servers[s].counts.active)
+ tmp32 += wl_servers[s].counts.numLogs > 0 ?1:0;
+ }
+ atom.ul = tmp32;
+ break;
+
+ case wl_nosupport:
+ haveValue = 0;
+ break;
+
+ default:
+ logmessage(LOG_CRIT,
+ "Illegal Meta Type (%d) for metric %d\n",
+ m_type,
+ pmidp->item);
+ exit(1);
+ }
+
+ if (haveValue) {
+ sts = __pmStuffValue(&atom, &vset->vlist[j], type);
+ if (sts < 0) {
+ __pmFreeResultValues(res);
+ return sts;
+ }
+
+ vset->valfmt = sts;
+ j++; /* next element in vlist[] for next instance */
+ }
+
+ } while (dp->indom != PM_INDOM_NULL && __pmdaNextInst(&inst, ext));
+
+ vset->numval = j;
+ }
+ *resp = res;
+ return 0;
+}
+
+/*
+ * Store into one of three metrics:
+ * web.activity.config.catchup, web.activity.config.check and web.activity.server.watched
+ */
+
+static int
+web_store(pmResult *result, pmdaExt *ext)
+{
+ int i;
+ int j;
+ pmValueSet *vsp;
+ int sts = 0;
+ __pmID_int *pmidp;
+ WebServer *server = (WebServer*)0;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmidp = (__pmID_int *)&vsp->pmid;
+ if (pmidp->cluster == 0) {
+ if (pmidp->item == 1) { /* web.activity.config.catchup */
+ int val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 20;
+ }
+ wl_refreshDelay = val;
+ }
+ else if (pmidp->item == 3) {/* web.activity.config.check */
+ int val = vsp->vlist[0].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 20;
+ }
+ wl_chkDelay = val;
+ }
+ else if (pmidp->item == 35) {/* web.activity.server.watched */
+ for (j = 0; j < vsp->numval; j++) {
+ int val = vsp->vlist[j].value.lval;
+ if (val < 0) {
+ sts = PM_ERR_SIGN;
+ val = 1;
+ }
+
+ server = &wl_servers[vsp->vlist[j].inst];
+ if (val > 0 && server->counts.active == 0) {
+ wl_numActive++;
+ server->counts.active = 1;
+ }
+ else if (val == 0 && server->counts.active > 0){
+ wl_numActive--;
+ server->counts.active = 0;
+ }
+ }
+ }
+ else {
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ else {
+ /* not one of the metrics we are willing to change */
+ sts = PM_ERR_PMID;
+ break;
+ }
+ }
+ return sts;
+}
+
+/*
+ * Initialise the callback table, and open the help text.
+ * This also calls the routine that to initialise the indom and meta tables.
+ */
+
+void
+web_init(pmdaInterface *dp)
+{
+ int m;
+ int type;
+
+ extp = dp->version.two.ext;
+
+ if (wl_isDSO)
+ pmdaDSO(dp, PMDA_INTERFACE_2, "weblog DSO", wl_helpFile);
+
+ if (dp->status != 0)
+ return;
+
+ dp->version.two.profile = web_profile;
+ dp->version.two.fetch = web_fetch;
+ dp->version.two.store = web_store;
+
+ if (numMetrics != (sizeof(wl_metricInfo)/sizeof(wl_metricInfo[0]))) {
+ logmessage(LOG_CRIT,
+ "Metric and Metric Info tables are not the same size\n");
+ dp->status = -1;
+ return;
+ }
+
+ pmdaInit(dp, wl_indomTable, numIndoms, wl_metrics, numMetrics);
+
+ for (m = 0; m < numMetrics; m++) {
+ type = wl_metricInfo[m].m_type;
+ if (type == wl_offset32 || type == wl_offset64 ||
+ type == wl_watched) {
+ wl_metricInfo[m].m_offset -= (__psint_t)&dummyCount;
+ }
+ }
+
+ return;
+}
+
diff --git a/src/pmdas/weblog/weblog.h b/src/pmdas/weblog/weblog.h
new file mode 100644
index 0000000..a2c99dd
--- /dev/null
+++ b/src/pmdas/weblog/weblog.h
@@ -0,0 +1,140 @@
+
+/*
+ * Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef _WEBLOG_H
+#define _WEBLOG_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <regex.h>
+#include <sys/stat.h>
+
+enum HTTP_Methods {
+ wl_httpGet, wl_httpHead, wl_httpPost, wl_httpOther, wl_numMethods
+};
+
+enum HistSizes {
+ wl_zero, wl_le3k, wl_le10k, wl_le30k, wl_le100k, wl_le300k, wl_le1m,
+ wl_le3m, wl_gt3m, wl_unknownSize, wl_numSizes
+};
+
+#define FIBUFSIZE 16*1024
+#define DORMANT_WARN 86400
+
+typedef struct {
+ char* fileName;
+ int filePtr;
+ struct stat fileStat;
+ char buf[FIBUFSIZE];
+ char *bp;
+ char *bend;
+ u_int format; /* index into regex for parsing file */
+ time_t lastActive; /* time in sec when last active */
+} FileInfo;
+
+typedef struct {
+ __uint32_t methodReq[wl_numMethods];
+ __uint64_t methodBytes[wl_numMethods];
+ __uint32_t sizeReq[wl_numSizes];
+ __uint64_t sizeBytes[wl_numSizes];
+ __uint32_t cached_sizeReq[wl_numSizes];
+ __uint64_t cached_sizeBytes[wl_numSizes];
+ __uint32_t uncached_sizeReq[wl_numSizes];
+ __uint64_t uncached_sizeBytes[wl_numSizes];
+ __uint32_t sumReq;
+ __uint32_t client_sumReq;
+ __uint32_t cached_sumReq;
+ __uint32_t uncached_sumReq;
+ __uint64_t sumBytes;
+ __uint64_t cached_sumBytes;
+ __uint64_t uncached_sumBytes;
+ __uint32_t errors;
+ __uint32_t active;
+ __uint32_t numLogs;
+ __uint32_t modTime;
+ __uint32_t extendedp;
+} WebCount;
+
+typedef struct {
+ int update; /* flag for updating this server */
+ WebCount counts;
+ FileInfo access;
+ FileInfo error;
+} WebServer;
+
+typedef struct {
+ int id;
+ pid_t pid;
+ int firstServer;
+ int lastServer;
+ int inFD[2];
+ int outFD[2];
+ char *methodStr;
+ char *sizeStr;
+ char *c_statusStr;
+ char *s_statusStr;
+ int strLength;
+} WebSproc;
+
+typedef struct {
+ char* name;
+#ifdef NON_POSIX_REGEX
+ char *np_regex;
+#endif
+ regex_t* regex;
+ int methodPos;
+ int sizePos;
+ int c_statusPos;
+ int s_statusPos;
+ int posix_regexp;
+} WebRegex;
+
+extern WebServer *wl_servers;
+extern WebSproc *wl_sproc;
+extern WebRegex *wl_regexTable;
+extern pmdaInstid *wl_serverInst;
+extern pmdaIndom wl_indomTable[];
+
+extern __uint32_t wl_numServers;
+extern __uint32_t wl_numActive;
+extern __uint32_t wl_refreshDelay;
+extern __uint32_t wl_chkDelay;
+extern __uint32_t wl_sprocThresh;
+extern __uint32_t wl_numSprocs;
+extern __uint32_t wl_catchupTime;
+extern __uint32_t wl_numRegex;
+
+extern int wl_updateAll;
+extern time_t wl_timeOfProbe;
+extern char *wl_logFile;
+extern char wl_helpFile[];
+extern int wl_isDSO;
+
+int openLogFile(FileInfo*);
+void probe(void);
+void refresh(WebSproc*);
+void refreshAll(void);
+void sprocMain(void*);
+void web_init(pmdaInterface*);
+void logmessage(int, const char *, ...);
+
+#define wl_close(fd) do { if (fd >= 0) close(fd); fd = -1; } while (0)
+
+#endif /* _WEBLOG_H */
diff --git a/src/pmdas/weblog/weblogconv.sh b/src/pmdas/weblog/weblogconv.sh
new file mode 100755
index 0000000..d62d2d2
--- /dev/null
+++ b/src/pmdas/weblog/weblogconv.sh
@@ -0,0 +1,62 @@
+#! /bin/sh
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+#
+# weblogconv: convert weblog.conf files to posix regex
+#
+
+# only needed on Linux
+
+progname=$0
+
+if [ $# -gt 0 -a "$1" = "-?" ]; then
+ echo "Usage: $progname infile [outfile]"
+ exit 0
+fi
+
+if [ $# -gt 2 ]; then
+ echo "$progname: Too many arguments."
+ exit 1
+fi
+
+if [ $# -lt 1 ] ; then
+ infile=""
+else
+ infile=$1
+fi
+
+if [ $# -lt 2 ]; then
+ outfile=""
+else
+ outfile="> $2"
+fi
+
+if [ -n "$infile" -a ! -r "$infile" ]; then
+ echo "$progname: cannot read $infile"
+ exit 1
+fi
+
+sed \
+ -e '/)\$[01]/!s/^regex[ \t][ \t]*\([^ \t][^ \t]*\)[ \t][ \t]*/regex_posix \1 - /' \
+ -e 's/^regex[ \t][ \t]*\([^ \t][^ \t]*\)[ \t][ \t]*\(.*$0.*$1.*\)/regex_posix \1 method,size \2/' \
+ -e 's/^regex[ \t][ \t]*\([^ \t][^ \t]*\)[ \t][ \t]*\(.*$1.*$0.*\)/regex_posix \1 size,method \2/' \
+ -e 's/)$0/)/g' \
+ -e 's/)$1/)/g' $infile | eval cat $outfile
+
+exit 0
diff --git a/src/pmdas/windows/GNUmakefile b/src/pmdas/windows/GNUmakefile
new file mode 100644
index 0000000..aef7163
--- /dev/null
+++ b/src/pmdas/windows/GNUmakefile
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+# Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+WINDIR = $(TOPDIR)/src/win32ctl
+
+IAM = windows
+DOMAIN = WINDOWS
+CFILES = pmda.c error.c open.c instance.c fetch.c helptext.c
+HFILES = hypnotoad.h
+LCFLAGS = -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0500 \
+ -I$(WINDIR)/include
+LLDFLAGS = -L$(WINDIR)/lib
+LLDLIBS = $(PCP_PMDALIB) -lpcp_pdh
+PMNS = pmns.disk pmns.kernel pmns.mem pmns.network \
+ pmns.sqlserver pmns.filesys pmns.hinv pmns.pmda \
+ pmns.process
+LSRCFILES = $(PMNS) root help README pdhlist.c pdhmatch.sh
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LIBTARGET = pmda_windows.dll
+EXTRATARGETS = pdhlist.exe help.dir help.pag
+
+LDIRT = root_windows domain.h $(IAM).log pmda$(IAM) \
+ $(EXTRATARGETS) pdhlist.o
+
+CONF_LINE = "windows 79 dso windows_init $(PCP_PMDAS_DIR)/windows/pmda_windows.dll"
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me: root_windows $(LSRCFILES) $(LIBTARGET) $(EXTRATARGETS)
+ @if [ `grep -c $(CONF_LINE) ../pmcd.conf` -eq 0 ]; then \
+ echo $(CONF_LINE) >> ../pmcd.conf ; \
+ fi
+
+install: build-me
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 pdhlist.exe $(LIBTARGET) $(PMDADIR)
+ $(INSTALL) -m 644 README root $(PMNS) $(PMDADIR)
+ $(INSTALL) -m 644 domain.h help.dir help.pag help $(PMDADIR)
+ $(INSTALL) -m 644 root_windows $(PCP_VAR_DIR)/pmns/root_windows
+else
+build-me:
+install:
+endif
+
+help.dir help.pag : help root_windows
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/newhelp/newhelp -n root_windows -v 2 -o help < help
+
+default_pcp: default
+
+install_pcp: install
+
+root_windows: ../../pmns/stdpmid $(PMNS)
+ rm -f root_windows
+ sed -e 's;<stdpmid>;"../../pmns/stdpmid";' <root \
+ | $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp \
+ | sed -e '/^#/d' -e '/^$$/d' >root_windows
+
+domain.h: ../../pmns/stdpmid
+ $(DOMAIN_MAKERULE)
+
+$(OBJECTS): hypnotoad.h domain.h
+
+pdhlist.exe: pdhlist.o error.o
+ $(CCF) -o pdhlist.exe pdhlist.o error.o $(LLDFLAGS) -lpcp_pdh
diff --git a/src/pmdas/windows/README b/src/pmdas/windows/README
new file mode 100644
index 0000000..a6847e7
--- /dev/null
+++ b/src/pmdas/windows/README
@@ -0,0 +1,171 @@
+Windows PMDA
+============
+
+This PMDA collects performance data from a Microsoft Windows kernel.
+
+Metrics
+=======
+
+The help text is exported from the kernel via the PDH (Performance
+Data Helper) APIs. To view the help text, install the PMDA, then
+the following command will list all the available metrics and their
+explanatory "help" text:
+
+ $ pminfo -fT kernel disk mem network filesys sqlserver hinv pmda process
+
+Installation
+============
+
+ + # cd $PCP_PMDAS_DIR/windows
+
+ + Check that there is no clash in the Performance Metrics Domain
+ defined in ./domain.h and the other PMDAs currently in use (see
+ $PCP_PMCDCONF_PATH). If there is, edit ./domain.h to choose another
+ domain number.
+
+ + Then simply use
+
+ # ./Install
+
+ and choose both the "collector" and "monitor" installation
+ configuration options -- everything else is automated.
+
+De-installation
+===============
+
+ + Simply use
+
+ # cd $PCP_PMDAS_DIR/windows
+ # ./Remove
+
+Troubleshooting
+===============
+
+ + After installing or restarting the agent, the PMCD log file
+ ($PCP_LOG_DIR/pmcd/pmcd.log) and the PMDA log file
+ ($PCP_LOG_DIR/pmcd/windows.log) should be checked for any warnings
+ or errors.
+
+Adding New Metrics
+==================
+
+The following steps should be followed when adding new metrics ... this
+assumes the MinGW gcc compiler is being used.
+
+a. Make sure you know what you've currently got, so on the target system
+ $ ./pdhlist | ./pdhmatch >status-quo
+
+b. Pick the Pdh paths you're interested in (drop the hostname prefix,
+ add the (*/*#*) or * patterns as appropriate. Beware that metrics
+ with multiple instances are reported multiple times ... for example
+
+ SQLServer:Cache Manager\Cache Hit Ratio
+ SQLServer:Cache Manager(_Total)\Cache Hit Ratio
+ SQLServer:Cache Manager(*/*#*)\Cache Hit Ratio
+
+ of these, the first pattern must not be used ... use the second one
+ if you want the "over all instances" totals, and the last one gives
+ the per instance metrics.
+
+c. Choose a PMID ... use the next unused number in sequence ... check
+ metricdesc[] in pmda.c, you want the PMDA_PMID(0,x) macro from the
+ last entry in the table, and the number you're after is x+1 (x+1 =
+ 81 for my example).
+
+d. Choose a name and where to put the new metric in the namespace ...
+ edit the appropriate pmns.* file (let's assume you're adding to one
+ of the existing Pdh groups, rather than creating a new one which is
+ more complicated), adding an appropriate entry using x+1 from above
+ for the last component of the PMID. Something like this in
+ pmns.sqlserver
+
+ sqlserver {
+ ...
+ cache_mgr // new line
+ }
+
+ ...
+
+ // all new lines from here down
+
+ sqlserver.cache_mgr {
+ all
+ percache
+ }
+
+ sqlserver.cache_mgr.all {
+ cache_hit_ratio WINDOWS:0:81
+ }
+
+ sqlserver.cache_mgr.cache {
+ cache_hit_ratio WINDOWS:0:82
+ }
+
+
+ Write the modified pmns file.
+
+ Check you've got it right with
+
+ $ pminfo -m -n root sqlserver.cache_mgr
+ sqlserver.cache_mgr.all.cache_hit_ratio PMID: 79.0.81
+ sqlserver.cache_mgr.cache.cache_hit_ratio PMID: 79.0.82
+
+e. Metric semantics ... you'll need a complete metricdesc[i]
+ initializer from pmda.c ... pick one that is similar, copy, paste
+ at the end of the table initialization and update PMDA_PMID(0,y)
+ with x+1 from step a.
+
+ To help with "similar" here are some examples:
+
+ kernel.all.cpu.user - unsigned 64-bit counter in units of microseconds
+
+ network.interface.in.bytes - unsigned 32-bit counter in units of
+ bytes with one value per network interface
+
+ mem.available - unsigned 64-bit instantaneous value in units of
+ Mbytes
+
+ sqlserver.buf_mgr.cache_hit_ratio - floating point value
+ representing a cache hit ratio, returned in the range 0.0 to
+ 1.0
+
+ Be careful with '\' in the Pdh patterns. Replace them with '\\' in the
+ string initializer used here.
+
+ My new metrics are cache hit ratios
+
+ /* sqlserver.cache_mgr.all.cache_hit_ratio */
+ { { PMDA_PMID(0,81), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(_Total)\\Cache Hit Ratio"
+ },
+ /* sqlserver.cache_mgr.cache.cache_hit_ratio */
+ { { PMDA_PMID(0,82), PM_TYPE_FLOAT, SQL_CACHE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(*/*#*)\\Cache Hit Ratio"
+ },
+
+f. If you've added a new instance domain (SQL_CACHE_INDOM in the case
+ above, this needs to be handled):
+ i. add a new header #define line (use the next ordinal number), e.g.
+ #define SQL_CACHE_INDOM 5
+ ii. add a new entry to indomtab[] in pmda.c, e.g.
+ { SQL_CACHE_INDOM, 0, NULL },
+ iii. add a new entry to indomtab[] in instance.c, e.g.
+ SQL_CACHE_INDOM,
+ iv. add new code in the switch of check_instance() in instance.c
+ to parse the Pdh instance name to extract a PCP instance name
+ and assign an instance number ... it is strongly suggested
+ that you study the ones already there, steal and modify the
+ one that is closest to your new instance
+
+g. Make and upgrade (assuming you're on the target machine and not
+ interested in packaging)
+
+ $ make
+ $ su
+ # /etc/pcp stop
+ # make install
+ # cd $PCP_VAR_DIR/pmdas/windows
+ # ./Install
+
diff --git a/src/pmdas/windows/error.c b/src/pmdas/windows/error.c
new file mode 100644
index 0000000..9961d78
--- /dev/null
+++ b/src/pmdas/windows/error.c
@@ -0,0 +1,130 @@
+/*
+ * Error code -> message map comes from
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/perfmon/base/pdh_error_codes.asp
+ *
+ * Copyright (c) 2008 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "hypnotoad.h"
+
+static struct {
+ int code;
+ char *msg;
+} errtab[] = {
+ { PDH_CSTATUS_VALID_DATA, "The returned data is valid." },
+ { PDH_CSTATUS_NEW_DATA, "The return data value is valid and different from the last sample." },
+ { PDH_CSTATUS_NO_MACHINE, "Unable to connect to specified machine or machine is off line." },
+ { PDH_CSTATUS_NO_INSTANCE, "The specified instance is not present." },
+ { PDH_MORE_DATA, "There is more data to return than would fit in the supplied buffer. Allocate a larger buffer and call the function again." },
+ { PDH_CSTATUS_ITEM_NOT_VALIDATED, "The data item has been added to the query but has not been validated nor accessed. No other status information on this data item is available." },
+ { PDH_RETRY, "The selected operation should be retried." },
+ { PDH_NO_DATA, "No data to return." },
+ { PDH_CALC_NEGATIVE_DENOMINATOR, "A counter with a negative denominator value was detected." },
+ { PDH_CALC_NEGATIVE_TIMEBASE, "A counter with a negative time base value was detected." },
+ { PDH_CALC_NEGATIVE_VALUE, "A counter with a negative value was detected." },
+ { PDH_DIALOG_CANCELLED, "The user canceled the dialog box." },
+ { PDH_END_OF_LOG_FILE, "The end of the log file was reached." },
+ { PDH_ASYNC_QUERY_TIMEOUT, "Time out while waiting for asynchronous counter collection thread to end." },
+ { PDH_CANNOT_SET_DEFAULT_REALTIME_DATASOURCE, "Cannot change set default real-time data source. There are real-time query sessions collecting counter data." },
+ { PDH_CSTATUS_NO_OBJECT, "The specified object is not found on the system." },
+ { PDH_CSTATUS_NO_COUNTER, "The specified counter could not be found." },
+ { PDH_CSTATUS_INVALID_DATA, "The returned data is not valid." },
+ { PDH_MEMORY_ALLOCATION_FAILURE, "A PDH function could not allocate enough temporary memory to complete the operation. Close some applications or extend the page file and retry the function." },
+ { PDH_INVALID_HANDLE, "The handle is not a valid PDH object." },
+ { PDH_INVALID_ARGUMENT, "A required argument is missing or incorrect." },
+ { PDH_FUNCTION_NOT_FOUND, "Unable to find the specified function." },
+ { PDH_CSTATUS_NO_COUNTERNAME, "No counter was specified." },
+ { PDH_CSTATUS_BAD_COUNTERNAME, "Unable to parse the counter path. Check the format and syntax of the specified path." },
+ { PDH_INVALID_BUFFER, "The buffer passed by the caller is invalid." },
+ { PDH_INSUFFICIENT_BUFFER, "The requested data is larger than the buffer supplied. Unable to return the requested data." },
+ { PDH_CANNOT_CONNECT_MACHINE, "Unable to connect to the requested machine." },
+ { PDH_INVALID_PATH, "The specified counter path could not be interpreted." },
+ { PDH_INVALID_INSTANCE, "The instance name could not be read from the specified counter path." },
+ { PDH_INVALID_DATA, "The data is not valid." },
+ { PDH_NO_DIALOG_DATA, "The dialog box data block was missing or invalid." },
+ { PDH_CANNOT_READ_NAME_STRINGS, "Unable to read the counter and/or explain text from the specified machine." },
+ { PDH_LOG_FILE_CREATE_ERROR, "Unable to create the specified log file." },
+ { PDH_LOG_FILE_OPEN_ERROR, "Unable to open the specified log file." },
+ { PDH_LOG_TYPE_NOT_FOUND, "The specified log file type has not been installed on this system." },
+ { PDH_NO_MORE_DATA, "No more data is available." },
+ { PDH_ENTRY_NOT_IN_LOG_FILE, "The specified record was not found in the log file." },
+ { PDH_DATA_SOURCE_IS_LOG_FILE, "The specified data source is a log file." },
+ { PDH_DATA_SOURCE_IS_REAL_TIME, "The specified data source is the current activity." },
+ { PDH_UNABLE_READ_LOG_HEADER, "The log file header could not be read." },
+ { PDH_FILE_NOT_FOUND, "Unable to find the specified file." },
+ { PDH_FILE_ALREADY_EXISTS, "There is already a file with the specified file name." },
+ { PDH_NOT_IMPLEMENTED, "The function referenced has not been implemented." },
+ { PDH_STRING_NOT_FOUND, "Unable to find the specified string in the list of performance name and explain text strings." },
+ { PDH_UNABLE_MAP_NAME_FILES, "Unable to map to the performance counter name data files. The data will be read from the registry and stored locally." },
+ { PDH_UNKNOWN_LOG_FORMAT, "The format of the specified log file is not recognized by the PDH DLL." },
+ { PDH_UNKNOWN_LOGSVC_COMMAND, "The specified Log Service command value is not recognized." },
+ { PDH_LOGSVC_QUERY_NOT_FOUND, "The specified Query from the Log Service could not be found or could not be opened." },
+ { PDH_LOGSVC_NOT_OPENED, "The Performance Data Log Service key could not be opened. This may be due to insufficient privilege or because the service has not been installed." },
+ { PDH_WBEM_ERROR, "An error occurred while accessing the WBEM data store." },
+ { PDH_ACCESS_DENIED, "Unable to access the desired machine or service. Check the permissions and authentication of the log service or the interactive user session against those on the machine or service being monitored." },
+ { PDH_LOG_FILE_TOO_SMALL, "The maximum log file size specified is too small to log the selected counters. No data will be recorded in this log file. Specify a smaller set of counters to log or a larger file size and retry this call." },
+ { PDH_INVALID_DATASOURCE, "Cannot connect to ODBC DataSource Name." },
+ { PDH_INVALID_SQLDB, "SQL Database does not contain a valid set of tables for Perfmon; use PdhCreateSQLTables." },
+ { PDH_NO_COUNTERS, "No counters were found for this Perfmon SQL Log Set." },
+ { PDH_SQL_ALLOC_FAILED, "Call to SQLAllocStmt failed with %1." },
+ { PDH_SQL_ALLOCCON_FAILED, "Call to SQLAllocConnect failed with %1." },
+ { PDH_SQL_EXEC_DIRECT_FAILED, "Call to SQLExecDirect failed with %1." },
+ { PDH_SQL_FETCH_FAILED, "Call to SQLFetch failed with %1." },
+ { PDH_SQL_ROWCOUNT_FAILED, "Call to SQLRowCount failed with %1." },
+ { PDH_SQL_MORE_RESULTS_FAILED, "Call to SQLMoreResults failed with %1." },
+ { PDH_SQL_CONNECT_FAILED, "Call to SQLConnect failed with %1." },
+ { PDH_SQL_BIND_FAILED, "Call to SQLBindCol failed with %1." },
+ { PDH_CANNOT_CONNECT_WMI_SERVER, "Unable to connect to the WMI server on requested machine." },
+ { PDH_PLA_COLLECTION_ALREADY_RUNNING, "Collection %1s is already running." },
+ { PDH_PLA_ERROR_SCHEDULE_OVERLAP, "The specified start time is after the end time." },
+ { PDH_PLA_COLLECTION_NOT_FOUND, "Collection %1 does not exist." },
+ { PDH_PLA_ERROR_SCHEDULE_ELAPSED, "The specified end time has already elapsed." },
+ { PDH_PLA_ERROR_NOSTART, "Collection %1 did not start check the application event log for any errors." },
+ { PDH_PLA_ERROR_ALREADY_EXISTS, "Collection %1 already exists." },
+ { PDH_PLA_ERROR_TYPE_MISMATCH, "There is a mismatch in the settings type." },
+ { PDH_PLA_ERROR_FILEPATH, "The information specified does not resolve to a valid path name." },
+ { PDH_PLA_SERVICE_ERROR, "The 'Performance Logs & Alerts' service did not respond." },
+ { PDH_PLA_VALIDATION_ERROR, "The information passed is not valid." },
+ { PDH_PLA_VALIDATION_WARNING, "The information passed is not valid." },
+ { PDH_PLA_ERROR_NAME_TOO_LONG, "The name supplied is too long." },
+ { PDH_INVALID_SQL_LOG_FORMAT, "SQL log format is incorrect. Correct format is 'SQL:<DSN-name>!<LogSet-Name>'." },
+ { PDH_COUNTER_ALREADY_IN_QUERY, "Performance counter in PdhAddCounter call has already been added in the performance query. This counter is ignored." },
+ { PDH_BINARY_LOG_CORRUPT, "Unable to read counter information and data from input binary log files." },
+ { PDH_LOG_SAMPLE_TOO_SMALL, "At least one of the input binary log files contain fewer than two data samples." },
+ { PDH_OS_LATER_VERSION, "The version of the operating system on the computer named %1 is later than that on the local computer. This operation is not available from the local computer." },
+ { PDH_OS_EARLIER_VERSION, "supports %2 or later. Check the operating system version on the computer named %3." },
+ { PDH_INCORRECT_APPEND_TIME, "The output file must contain earlier data than the file to be appended." },
+ { PDH_UNMATCHED_APPEND_COUNTER, "Both files must have identical counters in order to append." },
+ { PDH_SQL_ALTER_DETAIL_FAILED, "Cannot alter CounterDetail table layout in SQL database." },
+ { PDH_QUERY_PERF_DATA_TIMEOUT, "System is busy. Timeout when collecting counter data. Please retry later or increase the CollectTime registry value." }
+};
+
+static int sz_errtab = sizeof(errtab) / sizeof(errtab[0]);
+static char *buf = "eh? 0x........";
+
+char *pdherrstr(int code)
+{
+ int i;
+
+ for (i = 0; i < sz_errtab; i++) {
+ if (code == errtab[i].code)
+ return errtab[i].msg;
+ }
+ sprintf(buf, "eh? 0x%08x", code);
+ return buf;
+}
diff --git a/src/pmdas/windows/fetch.c b/src/pmdas/windows/fetch.c
new file mode 100644
index 0000000..cc43955
--- /dev/null
+++ b/src/pmdas/windows/fetch.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hypnotoad.h"
+
+MEMORYSTATUSEX windows_memstat;
+
+void
+windows_fetch_memstat(void)
+{
+ ZeroMemory(&windows_memstat, sizeof(MEMORYSTATUSEX));
+ windows_memstat.dwLength = sizeof(MEMORYSTATUSEX);
+ GlobalMemoryStatusEx(&windows_memstat);
+}
+
+/*
+ * Instantiate a value for a single metric-instance pair
+ */
+int
+windows_collect_metric(pdh_metric_t *mp, LPSTR pat, pdh_value_t *vp)
+{
+ PDH_STATUS pdhsts;
+ PDH_HQUERY queryhdl = NULL;
+ PDH_HCOUNTER counthdl = NULL;
+ int sts = -1;
+
+ if (mp->flags & M_NOVALUES)
+ return sts;
+
+ pdhsts = PdhOpenQueryA(NULL, 0, &queryhdl);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhOpenQueryA failed: %s\n",
+ pdherrstr(pdhsts));
+ return sts;
+ }
+
+ pdhsts = PdhAddCounterA(queryhdl, pat, vp->inst, &counthdl);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: PdhAddCounterA "
+ "@ pmid=%s pat=\"%s\": %s\n",
+ pmIDStr(mp->desc.pmid), pat, pdherrstr(pdhsts));
+ PdhCloseQuery(queryhdl);
+ return sts;
+ }
+
+ pdhsts = PdhCollectQueryData(queryhdl);
+ if (pdhsts != ERROR_SUCCESS) {
+ if ((vp->flags & V_ERROR_SEEN) == 0) {
+ __pmNotifyErr(LOG_ERR, "pdh_fetch: Error: PdhCollectQueryData "
+ "failed for metric %s pat %s: %s\n",
+ pmIDStr(mp->desc.pmid), pat, pdherrstr(pdhsts));
+ vp->flags |= V_ERROR_SEEN;
+ }
+ } else if ((mp->ctype == PERF_ELAPSED_TIME) ||
+ mp->ctype == PERF_LARGE_RAW_FRACTION) {
+ PDH_FMT_COUNTERVALUE fmt;
+ DWORD type;
+
+ if (mp->ctype == PERF_ELAPSED_TIME)
+ type = PDH_FMT_LARGE;
+ else /* PERF_LARGE_RAW_FRACTION */
+ type = PDH_FMT_DOUBLE;
+
+ pdhsts = PdhGetFormattedCounterValue(counthdl, type, NULL, &fmt);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "Error: PdhGetFormattedCounterValue "
+ "failed for metric %s inst %d: %s\n",
+ pmIDStr(mp->desc.pmid), vp->inst, pdherrstr(pdhsts));
+ vp->flags = V_NONE; /* no values for you! */
+ } else if (mp->ctype == PERF_ELAPSED_TIME) {
+ vp->atom.ull = fmt.largeValue;
+ sts = 0;
+ } else { /* PERF_LARGE_RAW_FRACTION */
+ vp->atom.d = fmt.doubleValue;
+ sts = 0;
+ }
+ } else {
+ PDH_RAW_COUNTER raw;
+
+ pdhsts = PdhGetRawCounterValue(counthdl, NULL, &raw);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "pdh_fetch: Error: PdhGetRawCounterValue "
+ "failed for metric %s inst %d: %s\n",
+ pmIDStr(mp->desc.pmid), vp->inst, pdherrstr(pdhsts));
+ vp->flags = V_NONE; /* no values for you! */
+ } else {
+ switch (mp->ctype) {
+ case PERF_COUNTER_COUNTER:
+ case PERF_COUNTER_RAWCOUNT:
+ /* these counters are only 32-bit */
+ vp->atom.ul = (__uint32_t)raw.FirstValue;
+ break;
+
+ case PERF_100NSEC_TIMER:
+ case PERF_PRECISION_100NS_TIMER:
+ /* convert 100nsec units to usec */
+ vp->atom.ull = raw.FirstValue / 10;
+ break;
+
+ case PERF_RAW_FRACTION:
+ /* v1 / v2 as percentage */
+ vp->atom.f = (float)raw.FirstValue / raw.SecondValue;
+ break;
+
+ case PERF_COUNTER_BULK_COUNT:
+ case PERF_COUNTER_LARGE_RAWCOUNT:
+ default:
+ vp->atom.ull = raw.FirstValue;
+ }
+ sts = 0;
+ }
+ }
+ PdhRemoveCounter(counthdl);
+ PdhCloseQuery(queryhdl);
+ return sts;
+}
+
+void
+windows_collect_callback(pdh_metric_t *pmp, LPTSTR pat, pdh_value_t *pvp)
+{
+ windows_verify_callback(pmp, pat, pvp);
+
+ if (!(pvp->flags & V_COLLECTED))
+ if (windows_collect_metric(pmp, pat, pvp) == 0)
+ pvp->flags |= V_COLLECTED;
+}
+
+/*
+ * Called before each PMDA fetch ... force value refreshes for
+ * requested metrics here; and special case any derived metrics.
+ */
+void
+windows_fetch_refresh(int numpmid, pmID pmidlist[], pmdaExt *pmda)
+{
+ int i, j, extra_filesys = 0, extra_memstat = 0;
+ int extra_hinv_ncpu = -1, extra_hinv_ndisk = -1;
+ int extra_network = -1;
+
+ for (i = 0; i < NUMINDOMS; i++)
+ windows_indom_reset[i] = 0;
+
+ for (i = 0; i < metricdesc_sz; i++)
+ for (j = 0; j < metricdesc[i].num_vals; j++)
+ metricdesc[i].vals[j].flags = V_NONE;
+
+ for (i = 0; i < numpmid; i++) {
+ __pmID_int *pmidp = (__pmID_int *)&pmidlist[i];
+ int cluster = pmidp->cluster;
+ int item = pmidp->item;
+
+ if (cluster == 1)
+ extra_memstat = 1;
+ else if (cluster != 0)
+ continue;
+ else if (item == 106)
+ extra_memstat = 1;
+ else if (item == 107 && extra_hinv_ncpu == -1)
+ extra_hinv_ncpu = 1;
+ else if (item == 108 && extra_hinv_ndisk == -1)
+ extra_hinv_ndisk = 1;
+ else if (item >= 117 && item <= 119)
+ extra_filesys = 1;
+ else if (item >= 236 && item <= 237 && extra_network == -1)
+ extra_network = 1;
+ else {
+ if (item >= 4 && item <= 7)
+ extra_hinv_ncpu = 0;
+ else if ((item >= 21 && item <= 26) || item == 68 ||
+ (item >= 217 && item <= 219) || item == 101 ||
+ (item >= 226 && item <= 231) || item == 133)
+ extra_hinv_ndisk = 0;
+ else if (item == 235)
+ extra_network = 0;
+
+ windows_visit_metric(&metricdesc[item], windows_collect_callback);
+ }
+ }
+
+ if (extra_memstat)
+ windows_fetch_memstat();
+ if (extra_hinv_ncpu == 1)
+ windows_visit_metric(&metricdesc[4], NULL);
+ if (extra_hinv_ndisk == 1)
+ windows_visit_metric(&metricdesc[21], NULL);
+ if (extra_filesys) {
+ windows_visit_metric(&metricdesc[120], windows_collect_callback);
+ windows_visit_metric(&metricdesc[121], windows_collect_callback);
+ }
+ if (extra_network == 1)
+ windows_visit_metric(&metricdesc[235], windows_collect_callback);
+
+ for (i = 0; i < NUMINDOMS; i++) {
+ /* Do we want to persist this instance domain to disk? */
+ if (windows_indom_reset[i] && windows_indom_fixed(i))
+ pmdaCacheOp(INDOM(pmda->e_domain, i), PMDA_CACHE_SAVE);
+ }
+}
diff --git a/src/pmdas/windows/help b/src/pmdas/windows/help
new file mode 100644
index 0000000..b58a1ea
--- /dev/null
+++ b/src/pmdas/windows/help
@@ -0,0 +1,65 @@
+#
+# Copyright (c) 2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Windows PMDA help file in the ASCII format. Only metrics which are
+# not using PDH regular expressions for extraction should be contained
+# here - the rest have their help text auto-generated by Windows PDH.
+#
+# lines beginning with a # are ignored
+# lines beginning @ introduce a new entry of the form
+# @ metric_name oneline-text
+# help test goes
+# here over multiple lines
+# ...
+#
+# the metric_name is decoded against the default PMNS -- as a special case,
+# a name of the form NNN.MM (for numeric NNN and MM) is interpreted as an
+# instance domain identification, and the text describes the instance domain
+#
+# blank lines before the @ line are ignored
+#
+@ kernel.uname.release release level of the running kernel
+@ kernel.uname.version version level (build number) and build date of the running kernel
+@ kernel.uname.sysname name of the implementation of the operating system
+@ kernel.uname.machine name of the hardware type the system is running on
+@ kernel.uname.nodename host name of this node on the network
+@ kernel.uname.distro Windows distribution name
+The Windows distribution name, from the GetVersionEx Win32 API.
+
+@ network.interface.speed interface speed in megabytes per second
+The linespeed on the network interface, as reported by the kernel,
+scaled from Megabits/second to Megabytes/second.
+See also network.interface.baudrate for the bytes/second value.
+
+@ network.interface.baudrate interface speed in bytes per second
+The linespeed on the network interface, as reported by the kernel,
+scaled from bits/second and divided by 8 to convert to bytes/second.
+See also network.interface.speed for the Megabytes/second value.
+
+@ pmda.uname identity and type of current system
+Identity and type of current system.
+
+See also the kernel.uname.* metrics
+
+@ pmda.version build version of Windows PMDA
+
+@ hinv.physmem total system memory metric
+@ hinv.pagesize Memory page size
+The memory page size of the running kernel in bytes.
+@ hinv.ncpu number of CPUs in the system
+@ hinv.ndisk number of disks in the syste (excluding floppy drives)
+@ hinv.nfilesys number of (local) file systems currently mounted
+
+@ filesys.capacity Total capacity of mounted filesystem (Kbytes)
+@ filesys.used Total space used on mounted filesystem (Kbytes)
+@ filesys.free Total space free on mounted filesystem (Kbytes)
diff --git a/src/pmdas/windows/helptext.c b/src/pmdas/windows/helptext.c
new file mode 100644
index 0000000..d1876e1
--- /dev/null
+++ b/src/pmdas/windows/helptext.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "hypnotoad.h"
+
+static char *text; /* filled in by iterator callback routine */
+static char texts[MAX_M_TEXT_LEN]; /* static callback buffer */
+
+/*
+ * Replace backslashes in the help string returned from Pdh APIs.
+ * Everything done "in-place" so no change to size of the string.
+ */
+static char *
+windows_fmt(char *text)
+{
+ char *p;
+ int n;
+
+ for (p = text, n = 0; p && *p != '\0'; p++, n++) {
+ if (!isprint((int)*p)) /* toss any dodgey characters */
+ *p = '?';
+ else if (*p == '\r') /* remove Windows line ending */
+ *p = '\n';
+ if (n < 70 || !isspace((int)*p)) /* very simple line wrapping */
+ continue;
+ *p = '\n';
+ n = 0;
+ }
+ return text;
+}
+
+static void
+windows_helptext_metric(pdh_metric_t *mp, PDH_COUNTER_INFO_A *infop)
+{
+ text = infop->szExplainText;
+}
+
+static void
+windows_helptext_callback(pdh_metric_t *pmp, LPSTR pat, pdh_value_t *pvp)
+{
+ windows_inform_metric(pmp, pat, pvp, TRUE, windows_helptext_metric);
+}
+
+int
+windows_help(int ident, int type, char **buf, pmdaExt *pmda)
+{
+ pmID pmid = (pmID)ident;
+ int i;
+
+ if ((type & PM_TEXT_PMID) != PM_TEXT_PMID)
+ return pmdaText(ident, type, buf, pmda);
+
+ for (i = 0; i < metricdesc_sz; i++)
+ if (pmid == metricdesc[i].desc.pmid)
+ break;
+ if (i == metricdesc_sz)
+ return PM_ERR_PMID;
+
+ if (type & PM_TEXT_ONELINE) {
+ if (metricdesc[i].pat[0] == '\0')
+ return pmdaText(ident, type, buf, pmda);
+ *buf = windows_fmt(strncpy(texts, &metricdesc[i].pat[0], sizeof(texts)));
+ } else {
+ text = NULL;
+ windows_visit_metric(&metricdesc[i], windows_helptext_callback);
+ if (!text)
+ return pmdaText(ident, type, buf, pmda);
+ *buf = windows_fmt(strncpy(texts, text, sizeof(texts)));
+ }
+ return 0;
+}
diff --git a/src/pmdas/windows/hypnotoad.h b/src/pmdas/windows/hypnotoad.h
new file mode 100644
index 0000000..ea75430
--- /dev/null
+++ b/src/pmdas/windows/hypnotoad.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef HYPNOTOAD_H
+#define HYPNOTOAD_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include "pdh.h"
+#include "pdhmsg.h"
+#include "domain.h"
+
+#define MAX_M_PATH_LEN 80 /* pattern passed to PdhExpandCounterPath */
+#define MAX_M_TEXT_LEN 512 /* longest long-text string that we allow */
+#define INDOM(x,y) (((x)<<22)|(y)) /* pmdaCache interfaces use indom */
+
+enum {
+ DISK_INDOM,
+ CPU_INDOM,
+ NETIF_INDOM,
+ FILESYS_INDOM,
+ SQL_LOCK_INDOM,
+ SQL_CACHE_INDOM,
+ SQL_DB_INDOM,
+ PROCESS_INDOM,
+ THREAD_INDOM,
+ SQL_USER_INDOM,
+ NUMINDOMS
+};
+
+typedef enum {
+ V_NONE,
+ V_ERROR_SEEN = 0x1,
+ V_COLLECTED = 0x2, /* if PdhGetRawCounterValue was successful */
+} pdh_valueflags_t;
+
+typedef struct {
+ int inst; /* PM_IN_NULL or instance identifier */
+ pdh_valueflags_t flags;
+ pmAtomValue atom;
+} pdh_value_t;
+
+typedef enum {
+ M_NONE,
+ M_EXPANDED = 0x1, /* pattern has been expanded */
+ M_REDO = 0x2, /* redo pattern expansion on each fetch */
+ M_NOVALUES = 0x4, /* setup failed, don't bother with the fetch */
+ M_OPTIONAL = 0x8, /* optional component, no values is expected */
+ M_VERIFIED = 0x10, /* has this metrics semantics been checked */
+ M_AUTO64 = 0x20, /* allow auto-modification on 64/32bit type */
+} pdh_metricflag_t;
+
+typedef struct {
+ pmDesc desc; /* metric descriptor */
+ pdh_metricflag_t flags; /* state of this metric */
+ int ctype; /* PDH counter type */
+ int num_alloc; /* high water allocation mark */
+ int num_vals; /* one or more metric values */
+ pdh_value_t *vals;
+ char pat[MAX_M_PATH_LEN]; /* for PdhExpandCounterPath */
+} pdh_metric_t;
+
+extern pdh_metric_t metricdesc[];
+extern int metricdesc_sz;
+
+extern char *windows_uname;
+extern char *windows_build;
+extern char *windows_machine;
+extern int windows_indom_setup[];
+extern int windows_indom_reset[];
+extern unsigned long windows_pagesize;
+extern MEMORYSTATUSEX windows_memstat;
+extern void windows_fetch_memstat(void);
+
+extern void windows_open(int);
+extern int windows_indom_fixed(int);
+extern char *pdherrstr(int);
+
+typedef void (*pdh_metric_inform_t)(pdh_metric_t *, PDH_COUNTER_INFO_A *);
+typedef void (*pdh_metric_visitor_t)(pdh_metric_t *, LPSTR, pdh_value_t *);
+extern int windows_visit_metric(pdh_metric_t *, pdh_metric_visitor_t);
+extern int windows_inform_metric(pdh_metric_t *, LPTSTR, pdh_value_t *,
+ BOOLEAN, pdh_metric_inform_t);
+
+extern void windows_instance_refresh(pmInDom);
+extern int windows_lookup_instance(char *, pdh_metric_t *);
+extern void windows_fetch_refresh(int numpmid, pmID pmidlist[], pmdaExt *);
+extern void windows_verify_callback(pdh_metric_t *, LPSTR, pdh_value_t *);
+
+extern int windows_help(int, int, char **, pmdaExt *);
+
+#endif /* HYPNOTOAD_H */
diff --git a/src/pmdas/windows/instance.c b/src/pmdas/windows/instance.c
new file mode 100644
index 0000000..fbbb083
--- /dev/null
+++ b/src/pmdas/windows/instance.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Parts of this file contributed by Ken McDonell
+ * (kenj At internode DoT on DoT net)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "hypnotoad.h"
+#include <ctype.h>
+
+int
+windows_indom_fixed(int serial)
+{
+ return (serial != PROCESS_INDOM && serial != THREAD_INDOM);
+}
+
+void
+windows_instance_refresh(pmInDom indom)
+{
+ int i, index, setup;
+
+ index = pmInDom_serial(indom);
+ windows_indom_reset[index] = 0;
+ setup = windows_indom_setup[index];
+
+ for (i = 0; i < metricdesc_sz; i++) {
+ pdh_metric_t *mp = &metricdesc[i];
+
+ if (indom != mp->desc.indom || mp->pat[0] != '\\')
+ continue;
+ if (!setup || (mp->flags & M_REDO))
+ windows_visit_metric(mp, NULL);
+ break;
+ }
+
+ /* Do we want to persist this instance domain to disk? */
+ if (windows_indom_reset[index] && windows_indom_fixed(index))
+ pmdaCacheOp(indom, PMDA_CACHE_SAVE);
+}
+
+int
+windows_lookup_instance(char *path, pdh_metric_t *mp)
+{
+ __pmInDom_int *ip;
+ static void *seen = (void *)0xfeedbabe;
+ void *sp;
+ char *p, *q, *name = NULL;
+ int sts, ok = 0;
+
+ if (mp->desc.indom == PM_INDOM_NULL)
+ return PM_IN_NULL;
+
+ ip = (__pmInDom_int *)&mp->desc.indom;
+ switch (ip->serial) {
+ /*
+ * Examples:
+ * \\WINBUILD\PhysicalDisk(0 C:)\Disk Reads/sec
+ * \\SOMEHOST\PhysicalDisk(0 C: D: E:)\Disk Transfers/sec
+ * \\WINBUILD\PhysicalDisk(_Total)\Disk Write Bytes/sec
+ */
+ case DISK_INDOM:
+ p = strchr(path, '('); // skip hostname and metric name
+ if (p != NULL) {
+ p++;
+ if (strncmp(p, "_Total)", 7) == 0) {
+ /*
+ * The totals get enumerated in the per disk path
+ * expansion, just skip 'em here
+ */
+ return -1;
+ }
+ while (isascii((int)*p) && isdigit((int)*p)) p++;
+ if (*p == ' ') {
+ p++;
+ q = strchr(p, ')');
+ if (q != NULL) {
+ name = (char *)malloc(q - p + 1);
+ if (name != NULL) {
+ strncpy(name, p, q - p);
+ name[q - p] = '\0';
+ ok = 1;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: "
+ "Error: DISK_INDOM malloc[%d] failed "
+ "path=%s\n", q - p + 1, path);
+ return -1;
+ }
+ /*
+ * If more than one drive letter maps to the same
+ * logical disk (e.g. mirrored root),, the name
+ * contains spaces, e.g.
+ * "C: D:" ... replace ' ' by '_' to play by the
+ * PCP rules for instance names
+ */
+ for (p = name; *p; p++) {
+ if (*p == ' ') *p = '_';
+ }
+ }
+ }
+ }
+ /*
+ * expecting something like ...\PhysicalDisk(N name)...
+ * we will just have to ignore this one! (might be due
+ * to: http://support.microsoft.com/kb/974878 - fail as
+ * entries like "17" and "17#1" are not useable anyway)
+ */
+ if (!ok) {
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized disk instance: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ /*
+ * Examples:
+ * \\WINBUILD\Processor(0)\% User Time
+ * \\WINBUILD\Processor(_Total)\Interrupts/sec
+ */
+ case CPU_INDOM:
+ p = strchr(path, '('); // skip hostname and metric name
+ if (p != NULL) {
+ p++;
+ if (strncmp(p, "_Total)", 7) == 0) {
+ /*
+ * The totals get enumerated in the per cpu path
+ * expansion, just skip 'em here
+ */
+ return -1;
+ }
+ int inst = atoi(p);
+ name = (char *)malloc(8); // "cpuNNNN"
+ if (name != NULL)
+ sprintf(name, "cpu%d", inst);
+ ok = 1;
+ }
+ /*
+ * expecting something like ...\Processor(N)...
+ * don't know what to do with this one!
+ */
+ if (!ok) {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized cpu instance: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ /*
+ * Examples:
+ * \\WINNT\Network Interface(MS TCP Loopback interface)\Bytes Total/sec
+ */
+ case NETIF_INDOM:
+ p = strchr(path, '('); // skip hostname and metric name
+ if (p != NULL) {
+ p++;
+ q = strchr(p, ')');
+ if (q != NULL) {
+ name = (char *)malloc(q - p + 1);
+ if (name != NULL) {
+ strncpy(name, p, q - p);
+ name[q - p] = '\0';
+ ok = 1;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "malloc[%d] failed for NETIF_INDOM "
+ "path=%s\n", q - p + 1, path);
+ return -1;
+ }
+ /*
+ * The network interface names have many spaces and are
+ * not unique up to the first space by any means. So,
+ * replace ' 's to play by the PCP instance name rules.
+ */
+ for (p = name; *p; p++) {
+ if (*p == ' ') *p = '_';
+ }
+ }
+ }
+ /*
+ * expecting something like ...\Network Interface(...)...
+ * don't know what to do with this one!
+ */
+ if (!ok) {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized network interface instance: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ /*
+ * Examples:
+ * \\TOWER\LogicalDisk(C:)\% Free Space
+ */
+ case FILESYS_INDOM:
+ p = strchr(path, '('); // skip hostname and metric name
+ if (p != NULL) {
+ p++;
+ if (strncmp(p, "_Total)", 7) == 0) {
+ /*
+ * The totals value makes no semantic sense,
+ * just skip it here
+ */
+ return -1;
+ }
+ while (isascii((int)*p) && isdigit((int)*p))
+ p++;
+ if (*p == ' ')
+ p++;
+ q = strchr(p, ')');
+ if (q != NULL) {
+ name = (char *)malloc(q - p + 1);
+ if (name != NULL) {
+ strncpy(name, p, q - p);
+ name[q - p] = '\0';
+ ok = 1;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "malloc[%d] failed for LDISK_INDOM path=%s\n",
+ q - p + 1, path);
+ return -1;
+ }
+ }
+ }
+ /*
+ * expecting something like ...\LogicalDisk(C:)...
+ * don't know what to do with this one!
+ */
+ if (!ok) {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized logical disk instance: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ /*
+ * SQLServer instance domains all have similar syntax
+ *
+ * Examples:
+ * \\TOWER\SQLServer:Locks(Table)\Average Wait Time (ms)
+ * \\TOWER\SQLServer:Cache Manager(Cursors)\Cache Hit Ratio
+ * \\TOWER\SQLServer:Databases(ACONEX_SYS)\Transactions/sec
+ */
+ case SQL_LOCK_INDOM:
+ case SQL_CACHE_INDOM:
+ case SQL_DB_INDOM:
+ case SQL_USER_INDOM:
+ p = strchr(path, '('); // skip hostname and metric name
+ if (p != NULL) {
+ p++;
+ if (strncmp(p, "_Total)", 7) == 0) {
+ /*
+ * The totals are done as independent metrics,
+ * just skip them here
+ */
+ return -1;
+ }
+ q = strchr(p, ')');
+ if (q != NULL) {
+ name = (char *)malloc(q - p + 1);
+ if (name != NULL) {
+ strncpy(name, p, q - p);
+ name[q - p] = '\0';
+ ok = 1;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "malloc[%d] failed, SQL_INDOM path=%s\n",
+ q - p + 1, path);
+ return -1;
+ }
+
+ /*
+ * The user counter names have many spaces and are
+ * not unique up to the first space by any means. So,
+ * replace ' 's to play by the PCP instance name rules.
+ */
+ if (ip->serial == SQL_USER_INDOM) {
+ for (p = name; *p; p++)
+ if (*p == ' ') *p = '_';
+ }
+ }
+ }
+ /*
+ * expecting something like ... \SQLServer:...(...)\...
+ * don't know what to do with this one!
+ */
+ if (!ok) {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized SQLServer instance: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ /*
+ * Per-process and per-thread instance domain
+ *
+ * Examples:
+ * \\TOWER\Process(svchost#6)\% Processor Time
+ * \\TOWER\Thread(svchost/1#1)\% Processor Time
+ * \\TOWER\Thread(Idle/0)\ID Process
+ * \\TOWER\Thread(Idle/0)\ID Thread
+ */
+ case PROCESS_INDOM:
+ case THREAD_INDOM:
+ p = strchr(path, '('); // skip hostname and Process/Thread
+ if (p != NULL) {
+ p++;
+ if ((strncmp(p, "_Total)", 7) == 0) ||
+ (strncmp(p, "_Total/", 7) == 0)) {
+ /*
+ * The totals are done as independent metrics,
+ * just skip them here
+ */
+ return -1;
+ }
+ q = strchr(p, ')');
+ if (q != NULL) {
+ name = (char *)malloc(q - p + 1);
+ if (name != NULL) {
+ strncpy(name, p, q - p);
+ name[q - p] = '\0';
+ ok = 1;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "malloc[%d] failed, process/thread path=%s\n",
+ q - p + 1, path);
+ return -1;
+ }
+ }
+ }
+ /*
+ * expecting something like ... \Process(...)\...
+ * don't know what to do with this one!
+ */
+ if (!ok) {
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "unrecognized process/thread name: %s\n", path);
+ free(name);
+ return -1;
+ }
+ break;
+
+ default:
+ __pmNotifyErr(LOG_ERR, "windows_check_instance: Error: "
+ "pmInDom %s is unknown for metric %s\n",
+ pmInDomStr(mp->desc.indom), pmIDStr(mp->desc.pmid));
+ return -1;
+ }
+
+ sts = pmdaCacheLookupName(mp->desc.indom, name, &ok, &sp);
+ if (sts != PMDA_CACHE_ACTIVE) {
+ if (sp != seen) /* new instance, never seen before, mark it */
+ windows_indom_reset[pmInDom_serial(mp->desc.indom)] = 1;
+ ok = pmdaCacheStore(mp->desc.indom, PMDA_CACHE_ADD, name, seen);
+ }
+ free(name);
+ return ok;
+}
diff --git a/src/pmdas/windows/open.c b/src/pmdas/windows/open.c
new file mode 100644
index 0000000..65be00d
--- /dev/null
+++ b/src/pmdas/windows/open.c
@@ -0,0 +1,769 @@
+/*
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ * Parts of this file contributed by Ken McDonell
+ * (kenj At internode DoT on DoT net)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "hypnotoad.h"
+#include <winbase.h>
+
+#define roundup(x,y) ((((x) + ((y) - 1)) / (y)) * (y))
+
+char *windows_uname;
+char *windows_build;
+char *windows_machine;
+unsigned long windows_pagesize;
+int windows_indom_setup[NUMINDOMS]; /* initial setup done on instance */
+int windows_indom_reset[NUMINDOMS]; /* instances changed on refresh */
+
+/*
+ * This block of functionality is required to map counter types from
+ * their Windows semantics to equivalent PCP semantics.
+ */
+
+static struct {
+ int type;
+ char *desc;
+} ctypetab[] = {
+ { PERF_100NSEC_MULTI_TIMER, "PERF_100NSEC_MULTI_TIMER" },
+ { PERF_100NSEC_MULTI_TIMER_INV, "PERF_100NSEC_MULTI_TIMER_INV" },
+ { PERF_100NSEC_TIMER, "PERF_100NSEC_TIMER" },
+ { PERF_100NSEC_TIMER_INV, "PERF_100NSEC_TIMER_INV" },
+ { PERF_AVERAGE_BASE, "PERF_AVERAGE_BASE" },
+ { PERF_AVERAGE_BULK, "PERF_AVERAGE_BULK" },
+ { PERF_AVERAGE_TIMER, "PERF_AVERAGE_TIMER" },
+ { PERF_COUNTER_100NS_QUEUELEN_TYPE, "PERF_COUNTER_100NS_QUEUELEN_TYPE" },
+ { PERF_COUNTER_BULK_COUNT, "PERF_COUNTER_BULK_COUNT" },
+ { PERF_COUNTER_COUNTER, "PERF_COUNTER_COUNTER" },
+ { PERF_COUNTER_DELTA, "PERF_COUNTER_DELTA" },
+ { PERF_COUNTER_LARGE_DELTA, "PERF_COUNTER_LARGE_DELTA" },
+ { PERF_COUNTER_LARGE_QUEUELEN_TYPE, "PERF_COUNTER_LARGE_QUEUELEN_TYPE" },
+ { PERF_COUNTER_LARGE_RAWCOUNT, "PERF_COUNTER_LARGE_RAWCOUNT" },
+ { PERF_COUNTER_LARGE_RAWCOUNT_HEX, "PERF_COUNTER_LARGE_RAWCOUNT_HEX" },
+ { PERF_COUNTER_MULTI_BASE, "PERF_COUNTER_MULTI_BASE" },
+ { PERF_COUNTER_MULTI_TIMER, "PERF_COUNTER_MULTI_TIMER" },
+ { PERF_COUNTER_MULTI_TIMER_INV, "PERF_COUNTER_MULTI_TIMER_INV" },
+ { PERF_COUNTER_NODATA, "PERF_COUNTER_NODATA" },
+ { PERF_COUNTER_QUEUELEN_TYPE, "PERF_COUNTER_QUEUELEN_TYPE" },
+ { PERF_COUNTER_RAWCOUNT, "PERF_COUNTER_RAWCOUNT" },
+ { PERF_COUNTER_RAWCOUNT_HEX, "PERF_COUNTER_RAWCOUNT_HEX" },
+ { PERF_COUNTER_TEXT, "PERF_COUNTER_TEXT" },
+ { PERF_COUNTER_TIMER, "PERF_COUNTER_TIMER" },
+ { PERF_COUNTER_TIMER_INV, "PERF_COUNTER_TIMER_INV" },
+ { PERF_ELAPSED_TIME, "PERF_ELAPSED_TIME" },
+ { PERF_LARGE_RAW_BASE, "PERF_LARGE_RAW_BASE" },
+ { PERF_OBJ_TIME_TIMER, "PERF_OBJ_TIME_TIMER" },
+ { PERF_PRECISION_100NS_TIMER, "PERF_PRECISION_100NS_TIMER" },
+ { PERF_PRECISION_OBJECT_TIMER, "PERF_PRECISION_OBJECT_TIMER" },
+ { PERF_PRECISION_SYSTEM_TIMER, "PERF_PRECISION_SYSTEM_TIMER" },
+ { PERF_RAW_BASE, "PERF_RAW_BASE" },
+ { PERF_RAW_FRACTION, "PERF_RAW_FRACTION" },
+ { PERF_LARGE_RAW_FRACTION, "PERF_LARGE_RAW_FRACTION" },
+ { PERF_SAMPLE_BASE, "PERF_SAMPLE_BASE" },
+ { PERF_SAMPLE_COUNTER, "PERF_SAMPLE_COUNTER" },
+ { PERF_SAMPLE_FRACTION, "PERF_SAMPLE_FRACTION" }
+};
+
+static int ctypetab_sz = sizeof(ctypetab) / sizeof(ctypetab[0]);
+
+static char *
+decode_ctype(DWORD ctype)
+{
+ static char unknown[20];
+ int i;
+
+ for (i = 0; i < ctypetab_sz; i++)
+ if (ctype == ctypetab[i].type)
+ return ctypetab[i].desc;
+ sprintf(unknown, "0x%08x unknown", (int)ctype);
+ return unknown;
+}
+
+static char *
+string_append(char *name, char *suff)
+{
+ if (name == NULL) {
+ name = (char *)strdup(suff);
+ }
+ else {
+ name = (char *)realloc(name, strlen(name)+strlen(suff)+1);
+ strcat(name, suff);
+ }
+ return name;
+}
+
+static char *
+_semstr(int sem)
+{
+ static char msg[20];
+ if (sem == PM_SEM_COUNTER)
+ return "COUNTER";
+ else if (sem == PM_SEM_INSTANT)
+ return "INSTANT";
+ else if (sem == PM_SEM_DISCRETE)
+ return "DISCRETE";
+ else {
+ sprintf(msg, "UNKNOWN! (%d)", sem);
+ return msg;
+ }
+}
+
+static char *
+_typestr(int type)
+{
+ static char msg[20];
+ if (type == PM_TYPE_32)
+ return "PM_TYPE_32";
+ else if (type == PM_TYPE_U32)
+ return "PM_TYPE_U32";
+ else if (type == PM_TYPE_64)
+ return "PM_TYPE_64";
+ else if (type == PM_TYPE_U64)
+ return "PM_TYPE_U64";
+ else if (type == PM_TYPE_FLOAT)
+ return "PM_TYPE_FLOAT";
+ else if (type == PM_TYPE_DOUBLE)
+ return "PM_TYPE_DOUBLE";
+ else {
+ sprintf(msg, "UNKNOWN! (%d)", type);
+ return msg;
+ }
+}
+
+#if 0 // debugging
+static char *
+_ctypestr(int ctype)
+{
+ if (ctype == PERF_COUNTER_COUNTER)
+ return "PERF_COUNTER_COUNTER";
+ else if (ctype == PERF_RAW_FRACTION)
+ return "PERF_RAW_FRACTION";
+ else if (ctype == PERF_LARGE_RAW_FRACTION)
+ return "PERF_LARGE_RAW_FRACTION";
+ else if (ctype == PERF_COUNTER_LARGE_RAWCOUNT_HEX)
+ return "PERF_COUNTER_LARGE_RAWCOUNT_HEX";
+ else if (ctype == PERF_COUNTER_LARGE_RAWCOUNT)
+ return "PERF_COUNTER_LARGE_RAWCOUNT";
+ else if (ctype == PERF_PRECISION_100NS_TIMER)
+ return "PERF_PRECISION_100NS_TIMER";
+ else if (ctype == PERF_100NSEC_TIMER)
+ return "PERF_100NSEC_TIMER";
+ else if (ctype == PERF_COUNTER_BULK_COUNT)
+ return "PERF_COUNTER_BULK_COUNT";
+ else if (ctype == PERF_COUNTER_RAWCOUNT_HEX)
+ return "PERF_COUNTER_RAWCOUNT_HEX";
+ else if (ctype == PERF_COUNTER_RAWCOUNT)
+ return "PERF_COUNTER_RAWCOUNT";
+ else if (ctype == PERF_COUNTER_COUNTER)
+ return "PERF_COUNTER_COUNTER";
+ else
+ return "UNKNOWN";
+}
+#endif
+
+/*
+ * Based on documentation from ...
+ * http://msdn.microsoft.com/library/default.asp?
+ * url=/library/en-us/sysinfo/base/osversioninfoex_str.asp
+ */
+static void
+windows_format_uname(OSVERSIONINFOEX osv)
+{
+ char tbuf[80];
+ char *name = NULL;
+
+ switch (osv.dwPlatformId) {
+ case VER_PLATFORM_WIN32_NT:
+ if (osv.dwMajorVersion == 6 && osv.dwMinorVersion == 1) {
+ if (osv.wProductType == VER_NT_WORKSTATION)
+ name = string_append(name, "Windows 7");
+ else
+ name = string_append(name, "Windows Server 2008 R2");
+ }
+ else if (osv.dwMajorVersion == 6 && osv.dwMinorVersion == 0) {
+ if (osv.wProductType == VER_NT_WORKSTATION)
+ name = string_append(name, "Windows Vista");
+ else
+ name = string_append(name, "Windows Server 2008");
+ }
+ else if (osv.dwMajorVersion == 5 && osv.dwMinorVersion == 2)
+ name = string_append(name, "Windows Server 2003");
+ else if (osv.dwMajorVersion == 5 && osv.dwMinorVersion == 1)
+ name = string_append(name, "Windows XP");
+ else if (osv.dwMajorVersion == 5 && osv.dwMinorVersion == 0)
+ name = string_append(name, "Windows 2000");
+ else if (osv.dwMajorVersion <= 4)
+ name = string_append(name, "Windows NT");
+ else {
+ sprintf(tbuf, "Windows Unknown (%ld.%ld)",
+ osv.dwMajorVersion, osv.dwMinorVersion);
+ name = string_append(name, tbuf);
+ }
+
+ /* service pack and build number etc */
+ if (osv.szCSDVersion[0] != '\0') {
+ name = string_append(name, " ");
+ name = string_append(name, osv.szCSDVersion);
+ }
+ sprintf(tbuf, " Build %ld", osv.dwBuildNumber & 0xFFFF);
+ windows_build = name + strlen(name) + 1;
+ windows_uname = string_append(name, tbuf);
+ break;
+
+ default:
+ windows_uname = "Windows - Platform Unknown";
+ windows_build = "Unknown Build";
+ break;
+ }
+}
+
+void
+windows_setup_globals(void)
+{
+ SYSTEM_INFO sysinfo;
+ OSVERSIONINFOEX osversion;
+
+ ZeroMemory(&sysinfo, sizeof(SYSTEM_INFO));
+ GetSystemInfo(&sysinfo);
+ windows_pagesize = sysinfo.dwPageSize;
+
+ switch (sysinfo.wProcessorArchitecture) {
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ windows_machine = "x86_64";
+ break;
+ case PROCESSOR_ARCHITECTURE_IA64:
+ windows_machine = "ia64";
+ break;
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ windows_machine = "i686";
+ break;
+ default:
+ windows_machine = "Unknown";
+ break;
+ }
+
+ ZeroMemory(&osversion, sizeof(OSVERSIONINFOEX));
+ osversion.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ GetVersionEx((OSVERSIONINFO *)&osversion);
+ windows_format_uname(osversion);
+}
+
+static void
+windows_verify_metric(pdh_metric_t *mp, PDH_COUNTER_INFO_A *infop)
+{
+ char *ctr_type;
+
+ mp->ctype = infop->dwType;
+
+ switch (mp->ctype) {
+ /*
+ * Pdh metric sematics ... from WinPerf.h
+ *
+ * SIZE
+ * DWORD 32-bit
+ * LARGE 64-bit
+ * ZERO no support here
+ * VARIABLE_LEN no support here
+ *
+ * TYPE
+ * NUMBER PM_SEM_INSTANT
+ * HEX display in hex (no support here)
+ * DECIMAL display as decimal
+ * DEC_1000 display as value / 1000
+ * (no support here)
+ * COUNTER PM_SEM_COUNTER
+ * VALUE display value (no support here)
+ * RATE time rate converted
+ * FRACTION divide value by BASE
+ * BASE used for FRACTION
+ * QUEUELEN magic internal queuelen() routines
+ * (you're joking, right?)
+ * HISTOGRAM counter begins or ends a histo (?)
+ * (definitely no support here)
+ * PRECISION divide counter by private clock (?)
+ * (definitely no support here)
+ * TEXT no support here
+ * ZERO no support here
+ */
+
+ /*
+ * Known 32-bit counters
+ */
+ case PERF_COUNTER_COUNTER:
+ /* 32-bit PM_SEM_COUNTER */
+ if (mp->desc.type != PM_TYPE_32 && mp->desc.type != PM_TYPE_U32) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR,
+ "windows_open: PERF_COUNTER_COUNTER: "
+ "metric %s: rewrite type from %s to PM_TYPE_U32\n",
+ pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U32;
+ }
+ if (mp->desc.sem != PM_SEM_COUNTER) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PERF_COUNTER_COUNTER: "
+ "metric %s: semantics %s (expected %s)\n",
+ pmIDStr(mp->desc.pmid), _semstr(mp->desc.sem),
+ _semstr(PM_SEM_COUNTER));
+ }
+ break;
+
+ case PERF_COUNTER_RAWCOUNT:
+ case PERF_COUNTER_RAWCOUNT_HEX:
+ if (mp->ctype == PERF_COUNTER_RAWCOUNT)
+ ctr_type = "PERF_COUNTER_RAWCOUNT";
+ else
+ ctr_type = "PERF_COUNTER_RAWCOUNT_HEX";
+ /* 32-bit PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.type != PM_TYPE_32 && mp->desc.type != PM_TYPE_U32) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR,
+ "windows_open: Warning: %s: metric %s: "
+ "rewrite type from %s to PM_TYPE_U32\n",
+ ctr_type, pmIDStr(mp->desc.pmid),
+ _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U32;
+ }
+ break;
+
+ /*
+ * Known 64-bit counters
+ */
+ case PERF_COUNTER_BULK_COUNT:
+ /* 64-bit PM_SEM_COUNTER */
+ if (mp->desc.type != PM_TYPE_64 && mp->desc.type != PM_TYPE_U64) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR,
+ "windows_open: PERF_COUNTER_BULK_COUNT:"
+ " metric %s: rewrite type from %s to PM_TYPE_U64\n",
+ pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U64;
+ }
+ if (mp->desc.sem != PM_SEM_COUNTER) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PERF_COUNTER_BULK_COUNT:"
+ " metric %s: semantics %s (expected %s)\n",
+ pmIDStr(mp->desc.pmid), _semstr(mp->desc.sem),
+ _semstr(PM_SEM_COUNTER));
+ mp->desc.sem = PM_SEM_COUNTER;
+ }
+ break;
+
+ case PERF_100NSEC_TIMER:
+ case PERF_PRECISION_100NS_TIMER:
+ if (mp->ctype == PERF_100NSEC_TIMER)
+ ctr_type = "PERF_100NSEC_TIMER";
+ else
+ ctr_type = "PERF_PRECISION_100NS_TIMER";
+ /*
+ * 64-bit PM_SEM_COUNTER, units are 100's of nanosecs,
+ * we shall export 'em as microseconds
+ */
+ if (mp->desc.type != PM_TYPE_64 && mp->desc.type != PM_TYPE_U64) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: rewrite type from %s to PM_TYPE_U64\n",
+ ctr_type, pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U64;
+ }
+ if (mp->desc.sem != PM_SEM_COUNTER) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: semantics %s (expected %s)\n",
+ ctr_type, pmIDStr(mp->desc.pmid), _semstr(mp->desc.sem),
+ _semstr(PM_SEM_COUNTER));
+ mp->desc.sem = PM_SEM_COUNTER;
+ }
+ if (mp->desc.units.dimSpace != 0 ||
+ mp->desc.units.dimTime != 1 ||
+ mp->desc.units.dimCount != 0 ||
+ mp->desc.units.scaleTime != PM_TIME_USEC) {
+ pmUnits units = mp->desc.units;
+ mp->desc.units.dimSpace = mp->desc.units.dimCount = 0;
+ mp->desc.units.scaleSpace = mp->desc.units.scaleCount = 0;
+ mp->desc.units.dimTime = 1;
+ mp->desc.units.scaleTime = PM_TIME_USEC;
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: rewrite dimension and scale from %s to %s",
+ ctr_type, pmIDStr(mp->desc.pmid), pmUnitsStr(&units),
+ pmUnitsStr(&mp->desc.units));
+ }
+ break;
+
+ case PERF_COUNTER_LARGE_RAWCOUNT:
+ case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
+ /* 64-bit PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.type != PM_TYPE_64 &&
+ mp->desc.type != PM_TYPE_U64) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: "
+ "PERF_COUNTER_LARGE_RAWCOUNT: metric %s: "
+ "rewrite type from %s to PM_TYPE_U64\n",
+ pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U64;
+ }
+ break;
+
+ case PERF_RAW_FRACTION:
+ /* Float PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.type != PM_TYPE_FLOAT) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: "
+ "PERF_RAW_FRACTION: metric %s: "
+ "rewrite type from %s to PM_TYPE_FLOAT\n",
+ pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_FLOAT;
+ }
+ break;
+
+ case PERF_LARGE_RAW_FRACTION:
+ /* Double PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.type != PM_TYPE_DOUBLE) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: "
+ "PERF_LARGE_RAW_FRACTION: metric %s: "
+ "rewrite type from %s to PM_TYPE_DOUBLE\n",
+ pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_DOUBLE;
+ }
+ break;
+
+ case PERF_AVERAGE_BULK:
+ case PERF_AVERAGE_TIMER:
+ if (mp->ctype == PERF_AVERAGE_BULK)
+ ctr_type = "PERF_AVERAGE_BULK";
+ else
+ ctr_type = "PERF_AVERAGE_TIMER";
+ /* 64-bit PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.sem != PM_SEM_INSTANT && mp->desc.sem != PM_SEM_DISCRETE) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: semantics %s (expected %s)\n",
+ ctr_type, pmIDStr(mp->desc.pmid),
+ _semstr(mp->desc.sem), _semstr(PM_SEM_INSTANT));
+ mp->desc.sem = PM_SEM_INSTANT;
+ }
+ if (mp->desc.type != PM_TYPE_64 && mp->desc.type != PM_TYPE_U64) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s "
+ "metric %s: rewrite type from %s to PM_TYPE_U64\n",
+ ctr_type, pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U64;
+ }
+ break;
+
+ case PERF_SAMPLE_COUNTER:
+ ctr_type = "PERF_SAMPLE_COUNTER";
+ /* floating point PM_SEM_INSTANT or PM_SEM_DISCRETE */
+ if (mp->desc.sem != PM_SEM_INSTANT && mp->desc.sem != PM_SEM_DISCRETE) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: semantics %s (expected %s)\n",
+ ctr_type, pmIDStr(mp->desc.pmid),
+ _semstr(mp->desc.sem), _semstr(PM_SEM_INSTANT));
+ mp->desc.sem = PM_SEM_INSTANT;
+ }
+ if (mp->desc.type != PM_TYPE_FLOAT && mp->desc.type != PM_TYPE_DOUBLE) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s "
+ "metric %s: rewrite type from %s to PM_TYPE_FLOAT\n",
+ ctr_type, pmIDStr(mp->desc.pmid), _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_FLOAT;
+ }
+ break;
+
+ case PERF_ELAPSED_TIME:
+ ctr_type = "PERF_ELAPSED_TIME";
+ if (mp->desc.units.dimSpace != 0 ||
+ mp->desc.units.dimTime != 1 ||
+ mp->desc.units.dimCount != 0) {
+ pmUnits units = mp->desc.units;
+ mp->desc.units.dimSpace = mp->desc.units.dimCount = 0;
+ mp->desc.units.scaleSpace = mp->desc.units.scaleCount = 0;
+ mp->desc.units.dimTime = 1;
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s: "
+ "metric %s: rewrite dimension and scale from %s to %s",
+ ctr_type, pmIDStr(mp->desc.pmid), pmUnitsStr(&units),
+ pmUnitsStr(&mp->desc.units));
+ }
+ if (mp->desc.type != PM_TYPE_64 && mp->desc.type != PM_TYPE_U64) {
+ if (!(mp->flags & M_AUTO64))
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: %s "
+ "metric %s: rewrite type from %s to PM_TYPE_U64\n",
+ ctr_type, pmIDStr(mp->desc.pmid),
+ _typestr(mp->desc.type));
+ mp->desc.type = PM_TYPE_U64;
+ }
+ break;
+
+ default:
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: metric %s: "
+ "unexpected counter type: %s\n",
+ pmIDStr(mp->desc.pmid), decode_ctype(infop->dwType));
+ }
+ mp->flags |= M_EXPANDED;
+}
+
+int
+windows_inform_metric(pdh_metric_t *pmp, LPTSTR p, pdh_value_t *pvp,
+ BOOLEAN getExplainText, pdh_metric_inform_t informer)
+{
+ int sts = -1;
+ PDH_STATUS pdhsts;
+ PDH_HQUERY queryhdl = NULL;
+ PDH_HCOUNTER counterhdl = NULL;
+ DWORD result_sz;
+ static DWORD info_sz = 0;
+ static LPSTR info = NULL;
+
+ pdhsts = PdhOpenQueryA(NULL, 0, &queryhdl);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhOpenQueryA failed: %s\n",
+ pdherrstr(pdhsts));
+ return sts;
+ }
+
+ pdhsts = PdhAddCounterA(queryhdl, p, pvp->inst, &counterhdl);
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: PdhAddCounterA "
+ "@ pmid=%s pat=\"%s\": %s\n",
+ pmIDStr(pmp->desc.pmid), p, pdherrstr(pdhsts));
+ PdhCloseQuery(queryhdl);
+ return sts;
+ }
+
+ /*
+ * check PCP metric semantics against PDH info
+ */
+ if (info_sz == 0) {
+ /*
+ * We've observed an initial call to PdhGetCounterInfoA()
+ * hang with a zero sized buffer ... pander to this with
+ * an initial buffer allocation ... (size is a 100% guess).
+ */
+ info_sz = 256;
+ if ((info = (LPSTR)malloc(info_sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhGetCounterInfoA "
+ "malloc (%d) failed @ metric %s: ",
+ (int)info_sz, pmIDStr(pmp->desc.pmid));
+ goto done;
+ }
+ }
+ result_sz = info_sz;
+ pdhsts = PdhGetCounterInfoA(counterhdl, getExplainText, &result_sz,
+ (PDH_COUNTER_INFO_A *)info);
+ if (pdhsts == PDH_MORE_DATA) {
+ info_sz = result_sz;
+ if ((info = (LPSTR)realloc(info, info_sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhGetCounterInfoA "
+ "realloc (%d) failed @ metric %s: ",
+ (int)info_sz, pmIDStr(pmp->desc.pmid));
+ goto done;
+ }
+ pdhsts = PdhGetCounterInfoA(counterhdl, getExplainText, &result_sz,
+ (PDH_COUNTER_INFO_A *)info);
+ }
+ if (pdhsts != ERROR_SUCCESS) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhGetCounterInfoA "
+ "failed @ metric %s: %s\n",
+ pmIDStr(pmp->desc.pmid), pdherrstr(pdhsts));
+ goto done;
+ }
+ else {
+ informer(pmp, (PDH_COUNTER_INFO_A *)info);
+ sts = 0;
+ }
+
+done:
+ PdhRemoveCounter(counterhdl);
+ PdhCloseQuery(queryhdl);
+ return sts;
+}
+
+void
+windows_verify_callback(pdh_metric_t *pmp, LPSTR pat, pdh_value_t *pvp)
+{
+ int v;
+
+ if (!(pmp->flags & M_VERIFIED)) {
+ v = windows_inform_metric(pmp, pat, pvp, FALSE, windows_verify_metric);
+ if (v == 0)
+ pmp->flags |= M_VERIFIED;
+ }
+}
+
+
+/*
+ * General purpose metric regex iterator, call out on each instance
+ */
+int
+windows_visit_metric(pdh_metric_t *pmp, pdh_metric_visitor_t visitor)
+{
+ size_t size;
+ int index = 0;
+ PDH_STATUS pdhsts;
+ DWORD result_sz;
+ static DWORD pattern_sz = 0;
+ static LPSTR pattern = NULL;
+ LPSTR p;
+
+ if (pmp->desc.indom != PM_INDOM_NULL) {
+ index = pmInDom_serial(pmp->desc.indom);
+ pmdaCacheOp(pmp->desc.indom, PMDA_CACHE_INACTIVE);
+ }
+
+ pmp->flags &= ~(M_EXPANDED|M_NOVALUES);
+ memset(pmp->vals, 0, pmp->num_alloc * sizeof(pdh_value_t));
+ pmp->num_vals = 0;
+
+ result_sz = 0;
+ pdhsts = PdhExpandCounterPathA(pmp->pat, NULL, &result_sz);
+ if (pdhsts == PDH_MORE_DATA) {
+ if (result_sz >= pattern_sz) {
+ pattern_sz = roundup(result_sz, 64);
+ if ((pattern = (LPSTR)realloc(pattern, pattern_sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhExpandCounterPathA "
+ "realloc (%ld) failed @ metric %s: ",
+ pattern_sz, pmIDStr(pmp->desc.pmid));
+ return -1;
+ }
+ }
+ result_sz = pattern_sz;
+ pdhsts = PdhExpandCounterPathA(pmp->pat, pattern, &result_sz);
+ }
+ if (pdhsts != PDH_CSTATUS_VALID_DATA) {
+ if (pmp->pat[0] != '\\') {
+ /*
+ * Skip metrics that are derived and do not have an explicit
+ * PDH API retrieval needed ... do nothing here.
+ */
+ ;
+ }
+ else if (pmp->flags & M_OPTIONAL) {
+ pmp->flags |= M_NOVALUES;
+ return 0;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "windows_open: PdhExpandCounterPathA "
+ "failed @ metric pmid=%s pattern=\"%s\": %s\n",
+ pmIDStr(pmp->desc.pmid), pmp->pat, pdherrstr(pdhsts));
+ }
+ pmp->flags |= M_NOVALUES;
+ return -1;
+ }
+
+ /*
+ * PdhExpandCounterPathA is apparently busted ... the length
+ * returned includes one byte _after_ the last NULL byte
+ * string terminator, but the final byte is apparently
+ * not being set ... force the issue
+ */
+ pattern[result_sz-1] = '\0';
+ for (p = pattern; *p; p += lstrlen(p) + 1) {
+ pdh_value_t *pvp;
+
+ pmp->num_vals++;
+ if (pmp->num_vals > pmp->num_alloc) {
+ size = pmp->num_vals * sizeof(pdh_value_t);
+ if ((pmp->vals = (pdh_value_t *)realloc(pmp->vals, size)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "windows_open: Error: values realloc "
+ "(%d x %d) failed @ metric %s [%s]: ",
+ pmp->num_vals, sizeof(pdh_value_t),
+ pmIDStr(pmp->desc.pmid), p);
+ pmp->num_alloc = 0;
+ return -1;
+ }
+ pmp->num_alloc = pmp->num_vals;
+ }
+
+ pvp = &pmp->vals[pmp->num_vals-1];
+ if (pmp->desc.indom == PM_INDOM_NULL) {
+ /* singular instance */
+ pvp->inst = PM_IN_NULL;
+ if (pmp->num_vals > 1) {
+ char *q;
+ int k;
+
+ /*
+ * report only once per pattern
+ */
+ __pmNotifyErr(LOG_ERR, "windows_open: Warning: singular "
+ "metric %s has more than one instance ...\n",
+ pmIDStr(pmp->desc.pmid));
+ fprintf(stderr, " pattern: \"%s\"\n", pmp->pat);
+ for (k = 0, q = pattern; *q; q += lstrlen(q) + 1, k++)
+ fprintf(stderr, " match[%d]: \"%s\"\n", k, q);
+ fprintf(stderr, "... skip this counter\n");
+
+ /* next realloc() will be a NOP */
+ pmp->num_vals--;
+
+ /* no more we can do here, onto next metric-pattern */
+ break;
+ }
+ }
+ else {
+ /*
+ * if metric has instance domain, parse pattern using
+ * indom type to extract instance name and number, and
+ * add into indom cache data structures as needed.
+ */
+ if ((pvp->inst = windows_lookup_instance(p, pmp)) < 0) {
+ /*
+ * error reported in windows_check_instance() ...
+ * we cannot return any values for this instance if
+ * we don't recognize the name ... skip this one,
+ * the next realloc() (if any) will be a NOP
+ */
+ pmp->num_vals--;
+
+ /* move onto next instance */
+ continue;
+ }
+ windows_indom_setup[index] = 1;
+ }
+
+ if (visitor)
+ visitor(pmp, p, pvp);
+ }
+
+ return 0;
+}
+
+void
+windows_open(int domain)
+{
+ int i;
+
+ windows_setup_globals();
+
+ for (i = 0; i < NUMINDOMS; i++) {
+ if (windows_indom_fixed(i))
+ pmdaCacheOp(INDOM(domain, i), PMDA_CACHE_LOAD);
+ windows_indom_reset[i] = 0;
+ }
+
+ /*
+ * This initialisation can take a long time - we have many metrics
+ * now for Windows. Better to delay this until we need to do it,
+ * and then only for the metrics needed. However, we cannot delay
+ * for those metrics that may change descriptors depending on the
+ * type of platform (64/32 bit, kernel version, etc), so those we
+ * verify up-front.
+ */
+ for (i = 0; i < metricdesc_sz; i++) {
+ if ((metricdesc[i].flags & M_AUTO64) || (pmDebug & DBG_TRACE_LIBPMDA))
+ windows_visit_metric(&metricdesc[i], windows_verify_callback);
+ }
+
+ for (i = 0; i < NUMINDOMS; i++) {
+ /* Do we want to persist this instance domain to disk? */
+ if (windows_indom_reset[i] && windows_indom_fixed(i))
+ pmdaCacheOp(INDOM(domain, i), PMDA_CACHE_SAVE);
+ }
+}
diff --git a/src/pmdas/windows/pdhlist.c b/src/pmdas/windows/pdhlist.c
new file mode 100644
index 0000000..387f139
--- /dev/null
+++ b/src/pmdas/windows/pdhlist.c
@@ -0,0 +1,85 @@
+/*
+ * List Windows performance counters on the current platform.
+ *
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <windows.h>
+#include <pdh.h>
+#include <pdhmsg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static int verbose;
+extern char *pdherrstr(int);
+#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
+
+void
+expand(char *pat)
+{
+ LPTSTR ptr;
+ LPSTR buf = NULL;
+ DWORD bufsz = 0;
+ int i;
+ PDH_STATUS pdhsts;
+
+ // Iterate because size grows in first couple of attempts!
+ for (i = 0; i < 5; i++) {
+ if (bufsz && (buf = (LPSTR)malloc(bufsz)) == NULL) {
+ fprintf(stderr, "malloc %ld failed for pattern: %s\n", bufsz, pat);
+ return;
+ }
+ if (verbose)
+ fprintf(stderr, "ExpandCounters pattern: %s\n", pat);
+ if ((pdhsts = PdhExpandCounterPathA(pat, buf, &bufsz)) == PDH_MORE_DATA) {
+ // bufsz has the required length (minus the last NULL)
+ bufsz = roundup(bufsz + 1, 64);
+ free(buf);
+ }
+ else
+ break;
+ }
+
+ if (pdhsts == PDH_CSTATUS_VALID_DATA) {
+ // success, print all counters
+ ptr = buf;
+ while (*ptr) {
+ printf("%s\n", ptr);
+ ptr += strlen(ptr) + 1;
+ }
+ }
+ else {
+ fprintf(stderr, "PdhExpandCounterPathA failed: %s\n", pdherrstr(pdhsts));
+ if (pdhsts == PDH_MORE_DATA)
+ fprintf(stderr, "still need to resize buffer to %ld\n", bufsz);
+ }
+
+ fflush(stderr);
+ fflush(stdin);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+
+ if (argc == 1) {
+ expand("\\*\\*");
+ expand("\\*(*)\\*");
+ return 0;
+ }
+
+ for (i = 1; i < argc; i++)
+ expand(argv[i]);
+ return 0;
+}
diff --git a/src/pmdas/windows/pdhmatch.sh b/src/pmdas/windows/pdhmatch.sh
new file mode 100644
index 0000000..33c2e98
--- /dev/null
+++ b/src/pmdas/windows/pdhmatch.sh
@@ -0,0 +1,192 @@
+#!/bin/sh
+#
+# take the output from pdhlist.exe
+# - remove hostname
+# - collapse known instance domains to a symbolic representation
+# - match up against patterns in data.c
+#
+
+#debug# tmp=/var/tmp/$$
+#debug# trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+tmp=`pwd`/tmp
+
+# Examples of instance domains to collapse from pdhlist.exe
+# output
+#
+# SQLServer:Buffer Partition(0)\Free pages
+# Job Object Details(Winlogon Job 0-57c89/logon.scr)\% User Time
+# Job Object Details(Winlogon Job 0-57c89/_Total)\% User Time
+# Job Object Details(WmiProviderSubSystemHostJob/wmiprvse)\% User Time
+# Job Object Details(WmiProviderSubSystemHostJob/_Total)\% User Time
+# [not] Job Object Details(_Total/_Total)\% User Time
+# Job Object(WmiProviderSubSystemHostJob)\Current % User Mode Time
+# [not] Job Object(_Total)\Current % User Mode Time
+# Thread(Idle/0)\Context Switches/sec
+# Thread(csrss/0#1)\Context Switches/sec
+# LogicalDisk(C:)\% Free Space
+# [not] LogicalDisk(_Total)\% Free Space
+# Network Interface(Intel[R] PRO_1000 MT Dual Port Network # Connection _2)\Bytes Received/sec
+# PhysicalDisk(0 C:)\% Disk Read Time
+# [not] PhysicalDisk(_Total)\% Disk Read Time
+# Print Queue(Canon LBP-3260 PCL6 on SCRIBE (from LUKE) in session 1)\Add Network Printer Calls
+# [not] Print Queue(_Total)\Add Network Printer Calls
+# Process(Idle)\% Privileged Time
+# [not] Process(_Total)\% Privileged Time
+# Processor(0)\% Idle Time
+# [not] Processor(_Total)\% Idle Time
+# RAS Port(LPT1)\Alignment Errors
+# SQLServer:Databases(alice)\Active Transactions
+# [not] SQLServer:Databases(_Total)\Active Transactions
+# SQLServer:Locks(Database)\Average Wait Time (ms)
+# [not] SQLServer:Locks(_Total)\Lock Requests/sec
+# Server Work Queues(3)\Active Threads
+# SQLServer:Cache Manager(Adhoc Sql Plans)\Cache Hit Ratio
+# Terminal Services Session(Console)\% Privileged Time
+#
+
+if [ $# -eq 1 ]
+then
+ cat $1
+elif [ $# -eq 0 ]
+then
+ cat
+else
+ echo "Usage: $0 [output-file-from-pdhlist]" >&2
+ exit 1
+fi \
+| dos2unix \
+| sed >$tmp.tmp \
+ -e 's/^\\\\[^\]*\\//' \
+ -e '/^SQLServer:Buffer Partition(/s/([0-9]*)\\/(<n>)\\/' \
+ -e '/^Job Object Details(/{
+/(_Total\//!s/(.*\/_Total)\\/(<job>\/_Total)\\/
+/\/_Total)/!s/(.*\/.*)\\/(<job>\/<?>)\\/
+}' \
+ -e '/^Job Object(/{
+/(_Total)/!s/(.*)\\/(<job>)\\/
+}' \
+ -e '/^Thread(/{
+s/([^/]*\/[0-9]*)\\/(<name>\/<pid>)\\/
+s/([^/]*\/[0-9]*#[0-9]*)\\/(<name>\/<pid>#<tid>)\\/
+}' \
+ -e '/^LogicalDisk(/s/([A-Z]:)\\/(<drive>)\\/' \
+ -e '/^Network Interface(/s/([^)]*)\\/(<if>)\\/' \
+ -e '/^PhysicalDisk(/s/([0-9][0-9]* [A-Z]:)\\/(<dev>)\\/' \
+ -e '/^Print Queue(/{
+/(_Total)/!s/(.*)\\/(<queue>)\\/
+}' \
+ -e '/^Process(/{
+/(_Total)/!s/(.*)\\/(<pname>)\\/
+}' \
+ -e '/^Processor(/{
+/(_Total)/!s/(.*)\\/(<cpu>)\\/
+}' \
+ -e '/^RAS Port(/s/(.*)\\/(<port>)\\/' \
+ -e '/^SQLServer:Databases(/{
+/(_Total)/!s/(.*)\\/(<db>)\\/
+}' \
+ -e '/^SQLServer:Locks(/{
+/(_Total)/!s/(.*)\\/(<type>)\\/
+}' \
+ -e '/^Server Work Queues(/s/(.*)\\/(<queue>)\\/' \
+ -e '/^SQLServer:Cache Manager(/s/(.*)\\/(<cache>)\\/' \
+ -e '/^Terminal Services Session(/s/(.*)\\/(<tty>)\\/'
+
+# This step tries to deal with this class of cases ...
+# pdhlist reports stuff like
+# SQLServer:Locks\Average Wait Time (ms)
+# SQLServer:Locks(_Total)\Average Wait Time (ms)
+# SQLServer:Locks(*/*#*)\Average Wait Time (ms)
+# but the first one is in fact bogus (only the second 2 forms
+# can be looked up.
+#
+sed <$tmp.tmp \
+ -e '/^.NET CLR Exceptions\\/d' \
+ -e '/^.NET CLR Interop\\/d' \
+ -e '/^.NET CLR Jit\\/d' \
+ -e '/^.NET CLR Loading\\/d' \
+ -e '/^.NET CLR LocksAndThreads\\/d' \
+ -e '/^.NET CLR Memory\\/d' \
+ -e '/^.NET CLR Remoting\\/d' \
+ -e '/^.NET CLR Security\\/d' \
+ -e '/^NBT Connection\\/d' \
+ -e '/^Paging File\\/d' \
+ -e '/^SQLServer:User Settable\\/d' \
+ -e '/^Server Work Queues\\/d' \
+ -e '/^SQLServer:Buffer Partition\\/d' \
+ -e '/^Job Object Details\\/d' \
+ -e '/^Job Object\\/d' \
+ -e '/^Thread\\/d' \
+ -e '/^LogicalDisk\\/d' \
+ -e '/^Network Interface\\/d' \
+ -e '/^PhysicalDisk\\/d' \
+ -e '/^Print Queue\\/d' \
+ -e '/^Process\\/d' \
+ -e '/^Processor\\/d' \
+ -e '/^RAS Port\\/d' \
+ -e '/^SQLServer:Databases\\/d' \
+ -e '/^SQLServer:Locks\\/d' \
+| LC_COLLATE=POSIX sort \
+| uniq >$tmp.munged
+
+# extract patterns from PMDA source
+#
+if [ -f data.c ]
+then
+ sed -n <data.c \
+ -e '/"\\\\/{
+s/"[ ]*$//
+s/.*"//
+s/\\\\/\\/g
+s/^\\//
+p
+}' \
+ | sed \
+ -e '/^Network Interface(/s/(\*\/\*#\*)\\/(<if>)\\/' \
+ -e '/^PhysicalDisk(/s/(\*\/\*#\*)\\/(<dev>)\\/' \
+ -e '/^Processor(/s/(\*\/\*#\*)\\/(<cpu>)\\/' \
+ -e '/^SQLServer:Locks(/s/(\*\/\*#\*)\\/(<type>)\\/' \
+ -e '/^LogicalDisk(/s/(\*\/\*#\*)\\/(<drive>)\\/' \
+ | LC_COLLATE=POSIX sort \
+ | uniq >$tmp.pmda
+
+else
+ echo "Warning: no data.c, cannot match metrics up with PMDA patterns" >&2
+ sed -e 's/^/? /' <$tmp.munged
+fi
+
+# match 'em up
+#
+
+comm -23 $tmp.pmda $tmp.munged >$tmp.tmp
+if [ -s $tmp.tmp ]
+then
+ echo "============================================"
+ echo "=== Warning: These current PMDA patterns do NOT match ANY metrics ..."
+ echo "============================================"
+ cat $tmp.tmp
+ echo
+fi
+
+comm -12 $tmp.pmda $tmp.munged >$tmp.tmp
+if [ -s $tmp.tmp ]
+then
+ echo "============================================"
+ echo "=== Metrics supported in the current PMDA ..."
+ echo "============================================"
+ cat $tmp.tmp
+else
+ echo "============================================"
+ echo "=== Warning: The current PMDA patterns match NO metric!"
+ echo "============================================"
+fi
+
+comm -13 $tmp.pmda $tmp.munged >$tmp.tmp
+if [ -s $tmp.tmp ]
+then
+ echo
+ echo "============================================"
+ echo "=== Metrics NOT supported in the current PMDA"
+ echo "============================================"
+ cat $tmp.tmp
+fi
diff --git a/src/pmdas/windows/pmda.c b/src/pmdas/windows/pmda.c
new file mode 100644
index 0000000..0c5009a
--- /dev/null
+++ b/src/pmdas/windows/pmda.c
@@ -0,0 +1,1601 @@
+/*
+ * Windows PMDA
+ *
+ * Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "hypnotoad.h"
+#include <ctype.h>
+
+/*
+ * Array of all metrics - the PMID item field indexes this directly.
+ */
+pdh_metric_t metricdesc[] = {
+/* kernel.all.cpu.user */
+ { { PMDA_PMID(0,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(_Total)\\% User Time"
+ },
+/* kernel.all.cpu.idle */
+ { { PMDA_PMID(0,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(_Total)\\% Idle Time"
+ },
+/* kernel.all.cpu.sys */
+ { { PMDA_PMID(0,2), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(_Total)\\% Privileged Time"
+ },
+/* kernel.all.cpu.intr */
+ { { PMDA_PMID(0,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(_Total)\\% Interrupt Time"
+ },
+/* kernel.percpu.cpu.user */
+ { { PMDA_PMID(0,4), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(*)\\% User Time"
+ },
+/* kernel.percpu.cpu.idle */
+ { { PMDA_PMID(0,5), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(*)\\% Idle Time"
+ },
+/* kernel.percpu.cpu.sys */
+ { { PMDA_PMID(0,6), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(*)\\% Privileged Time"
+ },
+/* kernel.percpu.cpu.intr */
+ { { PMDA_PMID(0,7), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Processor(*)\\% Interrupt Time"
+ },
+/* kernel.num_processes */
+ { { PMDA_PMID(0,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\Processes"
+ },
+/* kernel.num_threads */
+ { { PMDA_PMID(0,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\Threads"
+ },
+/* kernel.all.pswitch */
+ { { PMDA_PMID(0,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\Context Switches/sec"
+ },
+/* kernel.all.file.read */
+ { { PMDA_PMID(0,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\File Read Operations/sec"
+ },
+/* kernel.all.file.write */
+ { { PMDA_PMID(0,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\File Write Operations/sec"
+ },
+/* kernel.all.file.read_bytes */
+ { { PMDA_PMID(0,13), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\File Read Bytes/sec"
+ },
+/* kernel.all.file.write_bytes */
+ { { PMDA_PMID(0,14), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\System\\File Write Bytes/sec"
+ },
+/* disk.all.read */
+ { { PMDA_PMID(0,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Reads/sec"
+ },
+/* disk.all.write */
+ { { PMDA_PMID(0,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Writes/sec"
+ },
+/* disk.all.total */
+ { { PMDA_PMID(0,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Transfers/sec"
+ },
+/* disk.all.read_bytes */
+ { { PMDA_PMID(0,18), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Read Bytes/sec"
+ },
+/* disk.all.write_bytes */
+ { { PMDA_PMID(0,19), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Write Bytes/sec"
+ },
+/* disk.all.total_bytes */
+ { { PMDA_PMID(0,20), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Disk Bytes/sec"
+ },
+/* disk.dev.read */
+ { { PMDA_PMID(0,21), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Reads/sec"
+ },
+/* disk.dev.write */
+ { { PMDA_PMID(0,22), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Writes/sec"
+ },
+/* disk.dev.total */
+ { { PMDA_PMID(0,23), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Transfers/sec"
+ },
+/* disk.dev.read_bytes */
+ { { PMDA_PMID(0,24), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Read Bytes/sec"
+ },
+/* disk.dev.write_bytes */
+ { { PMDA_PMID(0,25), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Write Bytes/sec"
+ },
+/* disk.dev.total_bytes */
+ { { PMDA_PMID(0,26), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Disk Bytes/sec"
+ },
+/* mem.page_faults */
+ { { PMDA_PMID(0,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Page Faults/sec"
+ },
+/* mem.available */
+ { { PMDA_PMID(0,28), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Available MBytes"
+ },
+/* mem.committed_bytes */
+ { { PMDA_PMID(0,29), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Committed Bytes"
+ },
+/* mem.pool.paged_bytes */
+ { { PMDA_PMID(0,30), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pool Paged Bytes"
+ },
+/* mem.pool.non_paged_bytes */
+ { { PMDA_PMID(0,31), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pool Nonpaged Bytes"
+ },
+/* mem.cache.lazy_writes */
+ { { PMDA_PMID(0,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\Lazy Write Flushes/sec"
+ },
+/* mem.cache.lazy_write_pages */
+ { { PMDA_PMID(0,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\Lazy Write Pages/sec"
+ },
+/* mem.cache.mdl.read */
+ { { PMDA_PMID(0,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\MDL Reads/sec"
+ },
+/* mem.cache.read_ahead */
+ { { PMDA_PMID(0,35), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\Read Aheads/sec"
+ },
+/* mem.cache.mdl.sync_read */
+ { { PMDA_PMID(0,36), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\Sync MDL Reads/sec"
+ },
+/* mem.cache.mdl.async_read */
+ { { PMDA_PMID(0,37), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Cache\\Async MDL Reads/sec"
+ },
+/* network.interface.in.packets */
+ { { PMDA_PMID(0,38), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Packets Received/sec"
+ },
+/* network.interface.in.bytes */
+ { { PMDA_PMID(0,39), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0,0, PM_SPACE_BYTE, 0,0)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Bytes Received/sec"
+ },
+/* network.interface.in.errors */
+ { { PMDA_PMID(0,40), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Packets Received Errors"
+ },
+/* network.interface.out.packets */
+ { { PMDA_PMID(0,41), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Packets Sent/sec"
+ },
+/* network.interface.out.bytes */
+ { { PMDA_PMID(0,42), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0,0, PM_SPACE_BYTE, 0,0)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Bytes Sent/sec"
+ },
+/* network.interface.out.errors */
+ { { PMDA_PMID(0,43), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Packets Outbound Errors"
+ },
+/* network.interface.total.packets */
+ { { PMDA_PMID(0,44), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_NONE | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Packets/sec"
+ },
+/* network.interface.total.bytes */
+ { { PMDA_PMID(0,45), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) },
+ M_NONE, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Bytes Total/sec"
+ },
+/* sqlserver.buf_mgr.cache_hit_ratio */
+ { { PMDA_PMID(0,46), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Buffer cache hit ratio"
+ },
+/* sqlserver.buf_mgr.page_lookups */
+ { { PMDA_PMID(0,47), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Page lookups/sec"
+ },
+/* sqlserver.buf_mgr.free_list_stalls */
+ { { PMDA_PMID(0,48), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Free list stalls/sec"
+ },
+/* sqlserver.buf_mgr.free_pages */
+ { { PMDA_PMID(0,49), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Free pages"
+ },
+/* sqlserver.buf_mgr.total_pages */
+ { { PMDA_PMID(0,50), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Total pages"
+ },
+/* sqlserver.buf_mgr.target_pages */
+ { { PMDA_PMID(0,51), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Target pages"
+ },
+/* sqlserver.buf_mgr.database_pages */
+ { { PMDA_PMID(0,52), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Database pages"
+ },
+/* sqlserver.buf_mgr.reserved_pages */
+ { { PMDA_PMID(0,53), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Reserved pages"
+ },
+/* sqlserver.buf_mgr.stolen_pages */
+ { { PMDA_PMID(0,54), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Stolen pages"
+ },
+/* sqlserver.buf_mgr.lazy_writes */
+ { { PMDA_PMID(0,55), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Lazy writes/sec"
+ },
+/* sqlserver.buf_mgr.readahead_pages */
+ { { PMDA_PMID(0,56), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Readahead pages/sec"
+ },
+/* sqlserver.buf_mgr.procedure_cache_pages */
+ { { PMDA_PMID(0,57), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Procedure cache pages"
+ },
+/* sqlserver.buf_mgr.page_reads */
+ { { PMDA_PMID(0,58), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Page reads/sec"
+ },
+/* sqlserver.buf_mgr.page_writes */
+ { { PMDA_PMID(0,59), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Page writes/sec"
+ },
+/* sqlserver.buf_mgr.checkpoint_pages */
+ { { PMDA_PMID(0,60), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Checkpoint pages/sec"
+ },
+/* sqlserver.buf_mgr.awe.lookup_maps */
+ { { PMDA_PMID(0,61), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\AWE lookup maps/sec"
+ },
+/* sqlserver.buf_mgr.awe.stolen_maps */
+ { { PMDA_PMID(0,62), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\AWE stolen maps/sec"
+ },
+/* sqlserver.buf_mgr.awe.write_maps */
+ { { PMDA_PMID(0,63), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\AWE write maps/sec"
+ },
+/* sqlserver.buf_mgr.awe.unmap_calls */
+ { { PMDA_PMID(0,64), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\AWE unmap calls/sec"
+ },
+/* sqlserver.buf_mgr.awe.unmap_pages */
+ { { PMDA_PMID(0,65), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\AWE unmap pages/sec"
+ },
+/* sqlserver.buf_mgr.page_life_expectancy */
+ { { PMDA_PMID(0,66), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_SEC, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Buffer Manager\\Page life expectancy"
+ },
+/* filesys.full */
+ { { PMDA_PMID(0,67), PM_TYPE_FLOAT, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\LogicalDisk(*)\\% Free Space"
+ },
+/* disk.dev.idle */
+ { { PMDA_PMID(0,68), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\% Idle Time"
+ },
+/* sqlserver.locks.all.requests */
+ { { PMDA_PMID(0,69), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Lock Requests/sec"
+ },
+/* sqlserver.locks.all.waits */
+ { { PMDA_PMID(0,70), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Lock Waits/sec"
+ },
+/* sqlserver.locks.all.deadlocks */
+ { { PMDA_PMID(0,71), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Number of Deadlocks/sec"
+ },
+/* sqlserver.locks.all.timeouts */
+ { { PMDA_PMID(0,72), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Lock Timeouts/sec"
+ },
+/* sqlserver.locks.all.wait_time */
+ { { PMDA_PMID(0,73), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Lock Wait Time (ms)"
+ },
+/* sqlserver.locks.all.avg_wait_time */
+ { { PMDA_PMID(0,74), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(_Total)\\Average Wait Time (ms)"
+ },
+/* sqlserver.locks.region.requests */
+ { { PMDA_PMID(0,75), PM_TYPE_U32, SQL_LOCK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Lock Requests/sec"
+ },
+/* sqlserver.locks.region.waits */
+ { { PMDA_PMID(0,76), PM_TYPE_U32, SQL_LOCK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Lock Waits/sec"
+ },
+/* sqlserver.locks.region.deadlocks */
+ { { PMDA_PMID(0,77), PM_TYPE_U32, SQL_LOCK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Number of Deadlocks/sec"
+ },
+/* sqlserver.locks.region.timeouts */
+ { { PMDA_PMID(0,78), PM_TYPE_U32, SQL_LOCK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Lock Timeouts/sec"
+ },
+/* sqlserver.locks.region.wait_time */
+ { { PMDA_PMID(0,79), PM_TYPE_U32, SQL_LOCK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Lock Wait Time (ms)"
+ },
+/* sqlserver.locks.region.avg_wait */
+ { { PMDA_PMID(0,80), PM_TYPE_FLOAT, SQL_LOCK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Locks(*)\\Average Wait Time (ms)"
+ },
+/* sqlserver.cache_mgr.all.cache_hit_ratio */
+ { { PMDA_PMID(0,81), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(_Total)\\Cache Hit Ratio"
+ },
+/* sqlserver.cache_mgr.cache.cache_hit_ratio */
+ { { PMDA_PMID(0,82), PM_TYPE_FLOAT, SQL_CACHE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(*)\\Cache Hit Ratio"
+ },
+/* sqlserver.connections */
+ { { PMDA_PMID(0,83), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:General Statistics\\User Connections"
+ },
+/* sqlserver.databases.all.transactions */
+ { { PMDA_PMID(0,84), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Transactions/sec"
+ },
+/* sqlserver.databases.db.transactions */
+ { { PMDA_PMID(0,85), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Transactions/sec"
+ },
+/* sqlserver.sql.batch_requests */
+ { { PMDA_PMID(0,86), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:SQL Statistics\\Batch Requests/sec"
+ },
+/* sqlserver.latches.waits */
+ { { PMDA_PMID(0,87), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Latches\\Latch Waits/sec"
+ },
+/* sqlserver.latches.wait_time */
+ { { PMDA_PMID(0,88), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Latches\\Total Latch Wait Time (ms)"
+ },
+/* sqlserver.latches.avg_wait_time */
+ { { PMDA_PMID(0,89), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_MSEC, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Latches\\Average Latch Wait Time (ms)"
+ },
+/* sqlserver.databases.all.data_file_size */
+ { { PMDA_PMID(0,90), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Data File(s) Size (KB)"
+ },
+/* sqlserver.databases.all.log_file_size */
+ { { PMDA_PMID(0,91), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Log File(s) Size (KB)"
+ },
+/* sqlserver.databases.all.log_file_used */
+ { { PMDA_PMID(0,92), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Log File(s) Used Size (KB)"
+ },
+/* sqlserver.databases.db.data_file_size */
+ { { PMDA_PMID(0,93), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Data File(s) Size (KB)"
+ },
+/* sqlserver.databases.db.log_file_size */
+ { { PMDA_PMID(0,94), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Log File(s) Size (KB)"
+ },
+/* sqlserver.databases.db.log_file_used */
+ { { PMDA_PMID(0,95), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Log File(s) Used Size (KB)"
+ },
+/* sqlserver.sql.compilations */
+ { { PMDA_PMID(0,96), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:SQL Statistics\\SQL Compilations/sec"
+ },
+/* sqlserver.sql.re_compilations */
+ { { PMDA_PMID(0,97), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:SQL Statistics\\SQL Re-Compilations/sec"
+ },
+/* sqlserver.access.full_scans */
+ { { PMDA_PMID(0,98), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Access Methods\\Full Scans/sec"
+ },
+/* sqlserver.access.pages_allocated */
+ { { PMDA_PMID(0,99), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Access Methods\\Pages Allocated/sec"
+ },
+/* sqlserver.access.table_lock_escalations */
+ { { PMDA_PMID(0,100), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Access Methods\\Table Lock Escalations/sec"
+ },
+/* disk.dev.queuelen */
+ { { PMDA_PMID(0,101), PM_TYPE_U32, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Current Disk Queue Length"
+ },
+/* sqlserver.databases.all.log_flushes */
+ { { PMDA_PMID(0,102), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Log Flushes/sec"
+ },
+/* sqlserver.databases.db.log_flushes */
+ { { PMDA_PMID(0,103), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Log Flushes/sec"
+ },
+/* sqlserver.databases.all.log_bytes_flushed */
+ { { PMDA_PMID(0,104), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Log Bytes Flushed/sec"
+ },
+/* sqlserver.databases.db.log_bytes_flushed */
+ { { PMDA_PMID(0,105), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Log Bytes Flushed/sec"
+ },
+/* hinv.physmem */
+ { { PMDA_PMID(0,106), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_MBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* hinv.ncpu */
+ { { PMDA_PMID(0,107), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* hinv.ndisk */
+ { { PMDA_PMID(0,108), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.distro */
+ { { PMDA_PMID(0,109), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.release */
+ { { PMDA_PMID(0,110), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.version */
+ { { PMDA_PMID(0,111), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.sysname */
+ { { PMDA_PMID(0,112), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.machine */
+ { { PMDA_PMID(0,113), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* kernel.uname.nodename */
+ { { PMDA_PMID(0,114), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* pmda.uname */
+ { { PMDA_PMID(0,115), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* pmda.version */
+ { { PMDA_PMID(0,116), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+
+/* filesys.capacity */
+ { { PMDA_PMID(0,117), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, M_REDO, 0, 0, 0, NULL, ""
+ },
+/* filesys.used */
+ { { PMDA_PMID(0,118), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, M_REDO, 0, 0, 0, NULL, ""
+ },
+/* filesys.free */
+ { { PMDA_PMID(0,119), PM_TYPE_U64, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, M_REDO, 0, 0, 0, NULL, ""
+ },
+/* dummy - filesys.free_space */
+ { { PMDA_PMID(0,120), PM_TYPE_U32, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_MBYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\LogicalDisk(*)\\Free Megabytes"
+ },
+/* dummy - filesys.free_percent */
+ { { PMDA_PMID(0,121), PM_TYPE_FLOAT, FILESYS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\LogicalDisk(*)\\% Free Space"
+ },
+/* sqlserver.access.page_splits */
+ { { PMDA_PMID(0,122), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Access Methods\\Page Splits/sec"
+ },
+/* network.tcp.activeopens */
+ { { PMDA_PMID(0,123), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Connections Active"
+ },
+/* network.tcp.passiveopens */
+ { { PMDA_PMID(0,124), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Connections Passive"
+ },
+/* network.tcp.attemptfails */
+ { { PMDA_PMID(0,125), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Connection Failures"
+ },
+/* network.tcp.estabresets */
+ { { PMDA_PMID(0,126), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Connections Reset"
+ },
+/* network.tcp.currestab */
+ { { PMDA_PMID(0,127), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Connections Established"
+ },
+/* network.tcp.insegs */
+ { { PMDA_PMID(0,128), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Segments Received/sec"
+ },
+/* network.tcp.outsegs */
+ { { PMDA_PMID(0,129), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Segments Sent/sec"
+ },
+/* network.tcp.totalsegs */
+ { { PMDA_PMID(0,130), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Segments/sec"
+ },
+/* network.tcp.retranssegs */
+ { { PMDA_PMID(0,131), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\TCPv4\\Segments Retransmitted/sec"
+ },
+
+/* disk.all.split_io */
+ { { PMDA_PMID(0,132), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Split IO/Sec"
+ },
+/* disk.dev.split_io */
+ { { PMDA_PMID(0,133), PM_TYPE_U32, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Split IO/Sec"
+ },
+
+/* sqlserver.databases.all.active_transactions */
+ { { PMDA_PMID(0,134), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(_Total)\\Active Transactions"
+ },
+/* sqlserver.databases.db.active_transactions */
+ { { PMDA_PMID(0,135), PM_TYPE_U32, SQL_DB_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE)
+ }, M_REDO | M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Databases(*)\\Active Transactions"
+ },
+
+/* mem.commit_limit */
+ { { PMDA_PMID(0,136), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Commit Limit"
+ },
+/* mem.write_copies */
+ { { PMDA_PMID(0,137), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Write Copies/sec"
+ },
+/* mem.transition_faults */
+ { { PMDA_PMID(0,138), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Transition Faults/sec"
+ },
+/* mem.cache.faults */
+ { { PMDA_PMID(0,139), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Cache Faults/sec"
+ },
+/* mem.demand_zero_faults */
+ { { PMDA_PMID(0,140), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Demand Zero Faults/sec"
+ },
+/* mem.pages_total */
+ { { PMDA_PMID(0,141), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pages/sec"
+ },
+/* mem.page_reads */
+ { { PMDA_PMID(0,142), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Page Reads/sec"
+ },
+/* mem.pages_output */
+ { { PMDA_PMID(0,143), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pages Output/sec"
+ },
+/* mem.page_writes */
+ { { PMDA_PMID(0,144), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Page Writes/sec"
+ },
+/* mem.pool.paged_allocs */
+ { { PMDA_PMID(0,145), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pool Paged Allocs"
+ },
+/* mem.pool.nonpaged_allocs */
+ { { PMDA_PMID(0,146), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pool Nonpaged Allocs"
+ },
+/* mem.system.free_ptes */
+ { { PMDA_PMID(0,147), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Free System Page Table Entries"
+ },
+/* mem.cache.bytes */
+ { { PMDA_PMID(0,148), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Page Faults/sec"
+ },
+/* mem.cache.bytes_peak */
+ { { PMDA_PMID(0,149), PM_TYPE_64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Cache Bytes Peak"
+ },
+/* mem.pool.paged_resident_bytes */
+ { { PMDA_PMID(0,150), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\Pool Paged Resident Bytes"
+ },
+/* mem.system.total_code_bytes */
+ { { PMDA_PMID(0,151), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\System Code Total Bytes"
+ },
+/* mem.system.resident_code_bytes */
+ { { PMDA_PMID(0,152), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Memory\\System Code Resident Bytes"
+ },
+
+/* sqlserver.mem_mgr.connection_memory */
+ { { PMDA_PMID(0,153), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Connection Memory (KB)"
+ },
+/* sqlserver.mem_mgr.granted_workspace */
+ { { PMDA_PMID(0,154), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Granted Workspace Memory (KB)"
+ },
+/* sqlserver.mem_mgr.lock_memory */
+ { { PMDA_PMID(0,155), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Lock Memory (KB)"
+ },
+/* sqlserver.mem_mgr.lock_blocks_allocated */
+ { { PMDA_PMID(0,156), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Lock Blocks Allocated"
+ },
+/* sqlserver.mem_mgr.lock_owner_blocks_allocated */
+ { { PMDA_PMID(0,157), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Lock Owner Blocks Allocated"
+ },
+/* sqlserver.mem_mgr.lock_blocks */
+ { { PMDA_PMID(0,158), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Lock Blocks"
+ },
+/* sqlserver.mem_mgr.lock_owner_blocks */
+ { { PMDA_PMID(0,159), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Lock Owner Blocks"
+ },
+/* sqlserver.mem_mgr.maximum_workspace_memory */
+ { { PMDA_PMID(0,160), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Maximum Workspace Memory (KB)"
+ },
+/* sqlserver.mem_mgr.memory_grants_outstanding */
+ { { PMDA_PMID(0,161), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Memory Grants Outstanding"
+ },
+/* sqlserver.mem_mgr.memory_grants_pending */
+ { { PMDA_PMID(0,162), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Memory Grants Pending"
+ },
+/* sqlserver.mem_mgr.optimizer_memory */
+ { { PMDA_PMID(0,163), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Optimizer Memory (KB)"
+ },
+/* sqlserver.mem_mgr.sql_cache_memory */
+ { { PMDA_PMID(0,164), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\SQL Cache Memory (KB)"
+ },
+/* sqlserver.mem_mgr.target_server_memory */
+ { { PMDA_PMID(0,165), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Target Server Memory(KB)"
+ },
+/* sqlserver.mem_mgr.total_server_memory */
+ { { PMDA_PMID(0,166), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,PM_SPACE_KBYTE,0,0,0)
+ }, M_OPTIONAL | M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:Memory Manager\\Total Server Memory (KB)"
+ },
+/* sqlserver.cache_mgr.all.cache_pages */
+ { { PMDA_PMID(0,167), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(_Total)\\Cache Pages"
+ },
+/* sqlserver.cache_mgr.all.cache_object_count */
+ { { PMDA_PMID(0,168), PM_TYPE_32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(_Total)\\Cache Object Counts"
+ },
+/* sqlserver.cache_mgr.all.cache_use */
+ { { PMDA_PMID(0,169), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(_Total)\\Cache Use Counts/sec"
+ },
+/* sqlserver.cache_mgr.cache.cache_pages */
+ { { PMDA_PMID(0,170), PM_TYPE_U32, SQL_CACHE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(*)\\Cache Pages"
+ },
+/* sqlserver.cache_mgr.cache.cache_object_count */
+ { { PMDA_PMID(0,171), PM_TYPE_32, SQL_CACHE_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(*)\\Cache Object Counts"
+ },
+/* sqlserver.cache_mgr.cache.cache_use */
+ { { PMDA_PMID(0,172), PM_TYPE_U32, SQL_CACHE_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, M_OPTIONAL, 0, 0, 0, NULL,
+ "\\SQLServer:Cache Manager(*)\\Cache Use Counts/sec"
+ },
+/* process.count */
+ { { PMDA_PMID(0,173), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\Objects\\Processes"
+ },
+/* process.psinfo.pid */
+ { { PMDA_PMID(0,174), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\ID Process"
+ },
+/* process.psinfo.ppid */
+ { { PMDA_PMID(0,175), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Creating Process ID"
+ },
+/* process.psinfo.cpu_time */
+ { { PMDA_PMID(0,176), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Process(*)\\% Processor Time"
+ },
+/* process.psinfo.elapsed_time */
+ { { PMDA_PMID(0,177), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Elapsed Time"
+ },
+/* process.psinfo.utime */
+ { { PMDA_PMID(0,178), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\% User Time"
+ },
+/* process.psinfo.stime */
+ { { PMDA_PMID(0,179), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\% Privileged Time"
+ },
+/* process.psinfo.nthreads */
+ { { PMDA_PMID(0,180), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Thread Count"
+ },
+/* process.psinfo.priority_base */
+ { { PMDA_PMID(0,181), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Priority Base"
+ },
+/* process.psinfo.nhandles */
+ { { PMDA_PMID(0,182), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Handle Count"
+ },
+/* process.psinfo.page_faults */
+ { { PMDA_PMID(0,183), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Page Faults/sec"
+ },
+/* process.memory.size */
+ { { PMDA_PMID(0,184), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Pool Paged Bytes"
+ },
+/* process.memory.rss */
+ { { PMDA_PMID(0,185), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Working Set"
+ },
+/* process.memory.rss_peak */
+ { { PMDA_PMID(0,186), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Working Set Peak"
+ },
+/* process.memory.virtual */
+ { { PMDA_PMID(0,187), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Virtual Bytes"
+ },
+/* process.memory.virtual_peak */
+ { { PMDA_PMID(0,188), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Virtual Bytes Peak"
+ },
+/* process.memory.page_file */
+ { { PMDA_PMID(0,189), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Page File Bytes"
+ },
+/* process.memory.page_file_peak */
+ { { PMDA_PMID(0,190), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Page File Bytes Peak"
+ },
+/* process.memory.private */
+ { { PMDA_PMID(0,191), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Private Bytes"
+ },
+/* process.memory.pool_paged */
+ { { PMDA_PMID(0,192), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Pool Paged Bytes"
+ },
+/* process.memory.pool_nonpaged */
+ { { PMDA_PMID(0,193), PM_TYPE_U32, PROCESS_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\Pool Nonpaged Bytes"
+ },
+/* process.io.reads */
+ { { PMDA_PMID(0,194), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Read Operations/sec"
+ },
+/* process.io.writes */
+ { { PMDA_PMID(0,195), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Write Operations/sec"
+ },
+/* process.io.data */
+ { { PMDA_PMID(0,196), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Data Operations/sec"
+ },
+/* process.io.other */
+ { { PMDA_PMID(0,197), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Other Operations/sec"
+ },
+/* process.io.read_bytes */
+ { { PMDA_PMID(0,198), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Read Bytes/sec"
+ },
+/* process.io.write_bytes */
+ { { PMDA_PMID(0,199), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Write Bytes/sec"
+ },
+/* process.io.data_bytes */
+ { { PMDA_PMID(0,200), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Data Bytes/sec"
+ },
+/* process.io.other_bytes */
+ { { PMDA_PMID(0,201), PM_TYPE_U64, PROCESS_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Process(*)\\IO Other Bytes/sec"
+ },
+/* process.thread.context_switches */
+ { { PMDA_PMID(0,202), PM_TYPE_U32, THREAD_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Context Switches/sec"
+ },
+/* process.thread.cpu_time */
+ { { PMDA_PMID(0,203), PM_TYPE_U64, THREAD_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\% Processor Time"
+ },
+/* process.thread.utime */
+ { { PMDA_PMID(0,204), PM_TYPE_U64, THREAD_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\% User Time"
+ },
+/* process.thread.stime */
+ { { PMDA_PMID(0,205), PM_TYPE_U64, THREAD_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\% Privileged Time"
+ },
+/* process.thread.elapsed_time */
+ { { PMDA_PMID(0,206), PM_TYPE_U64, THREAD_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Elapsed Time"
+ },
+/* process.thread.priority */
+ { { PMDA_PMID(0,207), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Priority Current"
+ },
+/* process.thread.priority_base */
+ { { PMDA_PMID(0,208), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Priority Base"
+ },
+/* process.thread.start_address */
+ { { PMDA_PMID(0,209), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Start Address"
+ },
+/* process.thread.state */
+ { { PMDA_PMID(0,210), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Thread State"
+ },
+/* process.thread.wait_reason */
+ { { PMDA_PMID(0,211), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\Thread Wait Reason"
+ },
+/* process.thread.process_id */
+ { { PMDA_PMID(0,212), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\ID Process"
+ },
+/* process.thread.thread_id */
+ { { PMDA_PMID(0,213), PM_TYPE_U32, THREAD_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\Thread(*)\\ID Thread"
+ },
+
+/* disk.all.read_time */
+ { { PMDA_PMID(0,214), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\% Disk Read Time"
+ },
+/* disk.all.write_time */
+ { { PMDA_PMID(0,215), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\% Disk Write Time"
+ },
+/* disk.all.total_time */
+ { { PMDA_PMID(0,216), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\% Disk Time"
+ },
+/* disk.dev.read_time */
+ { { PMDA_PMID(0,217), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\% Disk Read Time"
+ },
+/* disk.dev.write_time */
+ { { PMDA_PMID(0,218), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\% Disk Write Time"
+ },
+/* disk.dev.total_time */
+ { { PMDA_PMID(0,219), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\% Disk Time"
+ },
+/* disk.all.average.read_bytes */
+ { { PMDA_PMID(0,220), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Read"
+ },
+/* disk.all.average.write_bytes */
+ { { PMDA_PMID(0,221), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Write"
+ },
+/* disk.all.average.total_bytes */
+ { { PMDA_PMID(0,222), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk Bytes/Transfer"
+ },
+/* disk.all.average.read_time */
+ { { PMDA_PMID(0,223), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk sec/Read"
+ },
+/* disk.all.average.write_time */
+ { { PMDA_PMID(0,224), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk sec/Write"
+ },
+/* disk.all.average.total_time */
+ { { PMDA_PMID(0,225), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_NONE, 0, 0, 0, NULL,
+ "\\PhysicalDisk(_Total)\\Avg. Disk sec/Transfer"
+ },
+/* disk.dev.average.read_bytes */
+ { { PMDA_PMID(0,226), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk Bytes/Read"
+ },
+/* disk.dev.write_bytes */
+ { { PMDA_PMID(0,227), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk Bytes/Write"
+ },
+/* disk.dev.total_bytes */
+ { { PMDA_PMID(0,228), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_BYTE, 0, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk Bytes/Transfer"
+ },
+/* disk.dev.average.read_time */
+ { { PMDA_PMID(0,229), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk sec/Read"
+ },
+/* disk.dev.average.write_time */
+ { { PMDA_PMID(0,230), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk sec/Write"
+ },
+/* disk.dev.average.total_time */
+ { { PMDA_PMID(0,231), PM_TYPE_U64, DISK_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 1, 0, 0, PM_TIME_USEC, 0) }, M_REDO, 0, 0, 0, NULL,
+ "\\PhysicalDisk(*)\\Avg. Disk sec/Transfer"
+ },
+
+/* hinv.nfilesys */
+ { { PMDA_PMID(0,232), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0, 0, 1, 0, 0, PM_COUNT_ONE) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+/* hinv.pagesize */
+ { { PMDA_PMID(0,233), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_NONE, 0, 0, 0, NULL, ""
+ },
+
+/* kernel.all.uptime */
+ { { PMDA_PMID(0,234), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,1,0,0,PM_TIME_SEC,0) }, M_REDO, 0, 0, 0, NULL,
+ "\\System\\System Up Time"
+ },
+
+/* network.interface.bandwidth */
+ { { PMDA_PMID(0,235), PM_TYPE_U32, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, -1, 0, 0, PM_TIME_SEC, 0)
+ }, M_REDO | M_AUTO64, 0, 0, 0, NULL,
+ "\\Network Interface(*)\\Current Bandwidth"
+ },
+/* network.interface.speed */
+ { { PMDA_PMID(0,236), PM_TYPE_FLOAT, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, -1, 0, PM_SPACE_MBYTE, PM_TIME_SEC, 0)
+ }, M_REDO, 0, 0, 0, NULL, ""
+ },
+/* network.interface.baudrate */
+ { { PMDA_PMID(0,237), PM_TYPE_U32, NETIF_INDOM, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, -1, 0, PM_SPACE_BYTE, PM_TIME_SEC, 0)
+ }, M_REDO, 0, 0, 0, NULL, ""
+ },
+
+/* sqlserver.user_settable.query */
+ { { PMDA_PMID(0,238), PM_TYPE_U32, SQL_USER_INDOM, PM_SEM_INSTANT,
+ PMDA_PMUNITS(0,0,0,0,0,0) }, M_AUTO64, 0, 0, 0, NULL,
+ "\\SQLServer:User Settable(*)\\Query"
+ },
+
+/* mem.physmem */
+ { { PMDA_PMID(1,0), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "The amount of actual physical memory"
+ },
+/* mem.freemem */
+ { { PMDA_PMID(1,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "The amount of physical memory currently available"
+ },
+/* mem.util.load */
+ { { PMDA_PMID(1,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(0, 0, 0, 0, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "Approximate percentage of physical memory in use"
+ },
+/* mem.util.used */
+ { { PMDA_PMID(1,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "Amount of physical memory in use"
+ },
+/* mem.util.free */
+ { { PMDA_PMID(1,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "Amount of physical memory currently available"
+ },
+/* swap.length */
+ { { PMDA_PMID(1,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "The current committed memory limit for the system"
+ },
+/* swap.used */
+ { { PMDA_PMID(1,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "The current committed memory for the system"
+ },
+/* swap.free */
+ { { PMDA_PMID(1,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
+ PMDA_PMUNITS(1, 0, 0, PM_SPACE_KBYTE, 0, 0) }, M_NONE, 0, 0, 0, NULL,
+ "The maximum amount of memory the system can commit"
+ },
+};
+int metricdesc_sz = sizeof(metricdesc) / sizeof(metricdesc[0]);
+
+static int
+windows_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
+{
+ windows_instance_refresh(indom);
+ return pmdaInstance(indom, inst, name, result, pmda);
+}
+
+static int
+windows_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
+{
+ windows_fetch_refresh(numpmid, pmidlist, pmda);
+ return pmdaFetch(numpmid, pmidlist, resp, pmda);
+}
+
+static pdh_value_t *
+find_instance_value(unsigned int item, unsigned int inst)
+{
+ pdh_metric_t *mp = &metricdesc[item];
+ int i;
+
+ /* fast check for direct mapped instance ID */
+ if (inst < mp->num_vals && mp->vals[inst].inst == inst)
+ return (mp->vals[inst].flags & V_COLLECTED) ? &mp->vals[inst] : NULL;
+
+ /* scan iteratively through instance IDs looking for this one */
+ for (i = 0; i < mp->num_vals; i++) {
+ if (mp->vals[i].inst != inst)
+ continue;
+ if (!(mp->vals[i].flags & V_COLLECTED))
+ break;
+ return &mp->vals[i];
+ }
+ return NULL;
+}
+
+static int
+filesys_fetch_callback(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ pdh_value_t *vp;
+ unsigned long long used, avail, capacity;
+ float used_space, free_space, free_percent;
+
+ /*
+ * Special case handling for the derived filesystem metrics
+ * which map the PDH services semantics for some metrics to
+ * the usual metrics from other platforms.
+ * 67 filesys.full
+ * 117 filesys.capacity
+ * 118 filesys.used
+ * 119 filesys.free
+ * 120 dummy metric, metricdesc holds FreeMB
+ * 121 dummy metric, metricdesc holds %Free
+ */
+ if (item == 67) { /* filesys.full, metricdesc holds %Free */
+ vp = find_instance_value(item, inst);
+ if (!vp)
+ return 0;
+ atom->f = (1.0 - vp->atom.f) * 100.0;
+ return 1;
+ }
+
+ vp = find_instance_value(120,inst); /* dummy, metricdesc holds FreeMB */
+ if (!vp)
+ return 0;
+ free_space = ((float)vp->atom.ul);
+
+ vp = find_instance_value(121,inst); /* dummy, metricdesc holds %Free */
+ if (!vp)
+ return 0;
+ free_percent = vp->atom.f;
+
+ used_space = (free_space / free_percent) - free_space;
+ used = 1024 * (unsigned long long)used_space; /* MB to KB */
+ avail = 1024 * (unsigned long long)free_space; /* MB to KB */
+ capacity = used + avail;
+
+ if (item == 117) /* filesys.capacity */
+ atom->ull = capacity;
+ else if (item == 118) /* filesys.used */
+ atom->ull = used;
+ else if (item == 119) /* filesys.free */
+ atom->ull = avail;
+ return 1;
+}
+
+static int
+network_fetch_callback(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ pdh_value_t *vp;
+
+ /*
+ * Special case handling for the derived network bandwidth metrics
+ * 235 network.interface.bandwidth (base, no derived)
+ * 236 network.interface.speed (mbytes, float)
+ * 237 network.interface.baudrate (in bytes)
+ */
+ vp = find_instance_value(235,inst);
+ if (!vp)
+ return 0;
+ if (item == 236)
+ atom->f = ((float)vp->atom.ull / 8 / 1024 / 1024);
+ else if (item == 237)
+ atom->ul = (vp->atom.ull / 8);
+ return 1;
+}
+
+static int
+memstat_fetch_callback(unsigned int item, unsigned int inst, pmAtomValue *atom)
+{
+ if (inst == PM_INDOM_NULL) {
+ switch (item) {
+ case 0: /* mem.physmem */
+ atom->ull = windows_memstat.ullTotalPhys / 1024;
+ return 1;
+ case 1: /* mem.freemem */
+ case 4: /* mem.util.free */
+ atom->ull = windows_memstat.ullAvailPhys / 1024;
+ return 1;
+ case 2: /* mem.util.load */
+ atom->ul = windows_memstat.dwMemoryLoad;
+ return 1;
+ case 3: /* mem.util.used */
+ atom->ull = windows_memstat.ullTotalPhys;
+ atom->ull =- windows_memstat.ullAvailPhys;
+ atom->ull /= 1024;
+ return 1;
+ case 5: /* swap.length */
+ atom->ull = windows_memstat.ullTotalPageFile / 1024;
+ return 1;
+ case 6: /* swap.used */
+ atom->ull = windows_memstat.ullTotalPageFile;
+ atom->ull -= windows_memstat.ullAvailPageFile;
+ atom->ull /= 1024;
+ return 1;
+ case 7: /* swap.free */
+ atom->ull = windows_memstat.ullAvailPageFile / 1024;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+windows_fetch_callback(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
+{
+ __pmID_int *pmidp = (__pmID_int *)&mdesc->m_desc.pmid;
+ pdh_value_t *vp;
+
+ if (pmidp->cluster == 1)
+ return memstat_fetch_callback(pmidp->item, inst, atom);
+
+ if (pmidp->cluster != 0 || pmidp->item > metricdesc_sz ||
+ (pmidp->item == 120 || pmidp->item == 121)) /* dummies */
+ return PM_ERR_PMID;
+
+ /*
+ * Check if its one of the derived metrics, or one that doesn't use PDH
+ */
+ switch (pmidp->item) {
+ case 106: /* hinv.physmem */
+ atom->ul = (windows_memstat.ullTotalPhys / (1024 * 1024));
+ return 1;
+ case 107: /* hinv.ncpu */
+ atom->ul = pmdaCacheOp(INDOM(pmidp->domain, CPU_INDOM),
+ PMDA_CACHE_SIZE_ACTIVE);
+ return 1;
+ case 108: /* hinv.ndisk */
+ atom->ul = pmdaCacheOp(INDOM(pmidp->domain, DISK_INDOM),
+ PMDA_CACHE_SIZE_ACTIVE);
+ return 1;
+ case 109: /* kernel.uname.distro */
+ atom->cp = windows_uname;
+ return 1;
+ case 110: /* kernel.uname.release */
+ atom->cp = windows_build;
+ return 1;
+ case 111: /* kernel.uname.version */
+ atom->cp = windows_build;
+ return 1;
+ case 112: /* kernel.uname.sysname */
+ atom->cp = "Windows";
+ return 1;
+ case 113: /* kernel.uname.machine */
+ atom->cp = windows_machine;
+ return 1;
+ case 114: /* kernel.uname.nodename */
+ atom->cp = "?";
+ return 1;
+ case 115: /* pmda.uname */
+ atom->cp = windows_uname;
+ return 1;
+ case 116: /* pmda.version */
+ atom->cp = pmGetConfig("PCP_VERSION");
+ return 1;
+ case 67: case 117: case 118: case 119:
+ return filesys_fetch_callback(pmidp->item, inst, atom);
+ case 232: /* hinv.nfilesys */
+ atom->ul = pmdaCacheOp(INDOM(pmidp->domain, FILESYS_INDOM),
+ PMDA_CACHE_SIZE_ACTIVE);
+ return 1;
+ case 233: /* hinv.pagesize */
+ atom->ul = windows_pagesize;
+ return 1;
+ case 236: case 237:
+ return network_fetch_callback(pmidp->item, inst, atom);
+ }
+
+ /*
+ * All other (most) metrics will go through this path
+ */
+ vp = find_instance_value(pmidp->item, inst);
+ if (!vp)
+ return 0;
+ *atom = vp->atom;
+ return 1;
+}
+
+/*
+ * Initialise the agent.
+ */
+void
+windows_init(pmdaInterface *dp)
+{
+ static pmdaMetric *metrictab;
+ char helppath[MAXPATHLEN];
+ int metrictab_sz = metricdesc_sz;
+ int i, sep = __pmPathSeparator();
+
+ snprintf(helppath, sizeof(helppath), "%s%c" "windows" "%c" "help",
+ pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
+ pmdaDSO(dp, PMDA_INTERFACE_3, "windows DSO", helppath);
+ if (dp->status != 0)
+ return;
+
+ /* Create the PMDA's metrictab[] version of the per-metric table */
+ metrictab = (pmdaMetric *)malloc(metrictab_sz * sizeof(pmdaMetric));
+ if (metrictab == NULL) {
+ fprintf(stderr, "Error: malloc metrictab [%d] failed: %s\n",
+ metrictab_sz * sizeof(pmdaMetric), osstrerror());
+ return;
+ }
+
+ /* rewrite pmid & indom, now that we know what the domain number is */
+ for (i = 0; i < metrictab_sz; i++) {
+ pdh_metric_t *mp = &metricdesc[i];
+ pmID pmid = mp->desc.pmid;
+ mp->desc.pmid = pmid_build(dp->domain, pmid_cluster(pmid), pmid_item(pmid));
+ if (mp->desc.indom != PM_INDOM_NULL)
+ mp->desc.indom = INDOM(dp->domain, mp->desc.indom);
+ }
+
+ windows_open(dp->domain);
+
+ /* write the metrictab entry for this metric, descriptor now setup */
+ for (i = 0; i < metrictab_sz; i++) {
+ metrictab[i].m_desc = metricdesc[i].desc;
+ metrictab[i].m_user = NULL;
+ }
+
+ dp->version.two.fetch = windows_fetch;
+ dp->version.two.instance = windows_instance;
+ dp->version.two.text = windows_help;
+ pmdaSetFetchCallBack(dp, windows_fetch_callback);
+ pmdaInit(dp, NULL, 0, metrictab, metrictab_sz);
+}
diff --git a/src/pmdas/windows/pmns.disk b/src/pmdas/windows/pmns.disk
new file mode 100644
index 0000000..9849740
--- /dev/null
+++ b/src/pmdas/windows/pmns.disk
@@ -0,0 +1,52 @@
+disk {
+ all
+ dev
+}
+
+disk.all {
+ read WINDOWS:0:15
+ write WINDOWS:0:16
+ total WINDOWS:0:17
+ split_io WINDOWS:0:132
+ read_bytes WINDOWS:0:18
+ write_bytes WINDOWS:0:19
+ total_bytes WINDOWS:0:20
+ read_time WINDOWS:0:214
+ write_time WINDOWS:0:215
+ total_time WINDOWS:0:216
+ average
+}
+
+disk.dev {
+ read WINDOWS:0:21
+ write WINDOWS:0:22
+ total WINDOWS:0:23
+ split_io WINDOWS:0:133
+ read_bytes WINDOWS:0:24
+ write_bytes WINDOWS:0:25
+ total_bytes WINDOWS:0:26
+ idle WINDOWS:0:68
+ queue_len WINDOWS:0:101
+ read_time WINDOWS:0:217
+ write_time WINDOWS:0:218
+ total_time WINDOWS:0:219
+ average
+}
+
+disk.all.average {
+ read_bytes WINDOWS:0:220
+ write_bytes WINDOWS:0:221
+ total_bytes WINDOWS:0:222
+ read_time WINDOWS:0:223
+ write_time WINDOWS:0:224
+ total_time WINDOWS:0:225
+}
+
+disk.dev.average {
+ read_bytes WINDOWS:0:226
+ write_bytes WINDOWS:0:227
+ total_bytes WINDOWS:0:228
+ read_time WINDOWS:0:229
+ write_time WINDOWS:0:230
+ total_time WINDOWS:0:231
+}
diff --git a/src/pmdas/windows/pmns.filesys b/src/pmdas/windows/pmns.filesys
new file mode 100644
index 0000000..20137c3
--- /dev/null
+++ b/src/pmdas/windows/pmns.filesys
@@ -0,0 +1,6 @@
+filesys {
+ full WINDOWS:0:67
+ capacity WINDOWS:0:117
+ used WINDOWS:0:118
+ free WINDOWS:0:119
+}
diff --git a/src/pmdas/windows/pmns.hinv b/src/pmdas/windows/pmns.hinv
new file mode 100644
index 0000000..d5cd0d0
--- /dev/null
+++ b/src/pmdas/windows/pmns.hinv
@@ -0,0 +1,7 @@
+hinv {
+ physmem WINDOWS:0:106
+ ncpu WINDOWS:0:107
+ ndisk WINDOWS:0:108
+ nfilesys WINDOWS:0:232
+ pagesize WINDOWS:0:233
+}
diff --git a/src/pmdas/windows/pmns.kernel b/src/pmdas/windows/pmns.kernel
new file mode 100644
index 0000000..631e397
--- /dev/null
+++ b/src/pmdas/windows/pmns.kernel
@@ -0,0 +1,48 @@
+kernel {
+ all
+ percpu
+ num_processes WINDOWS:0:8
+ num_threads WINDOWS:0:9
+ uname
+}
+
+kernel.all {
+ cpu
+ file
+ pswitch WINDOWS:0:10
+ uptime WINDOWS:0:234
+}
+
+kernel.all.cpu {
+ user WINDOWS:0:0
+ idle WINDOWS:0:1
+ sys WINDOWS:0:2
+ intr WINDOWS:0:3
+}
+
+kernel.percpu {
+ cpu
+}
+
+kernel.percpu.cpu {
+ user WINDOWS:0:4
+ idle WINDOWS:0:5
+ sys WINDOWS:0:6
+ intr WINDOWS:0:7
+}
+
+kernel.all.file {
+ read WINDOWS:0:11
+ write WINDOWS:0:12
+ read_bytes WINDOWS:0:13
+ write_bytes WINDOWS:0:14
+}
+
+kernel.uname {
+ distro WINDOWS:0:109
+ release WINDOWS:0:110
+ version WINDOWS:0:111
+ sysname WINDOWS:0:112
+ machine WINDOWS:0:113
+ nodename WINDOWS:0:114
+}
diff --git a/src/pmdas/windows/pmns.mem b/src/pmdas/windows/pmns.mem
new file mode 100644
index 0000000..be71ec6
--- /dev/null
+++ b/src/pmdas/windows/pmns.mem
@@ -0,0 +1,61 @@
+mem {
+ page_faults WINDOWS:0:27
+ available WINDOWS:0:28
+ committed_bytes WINDOWS:0:29
+ pool
+ cache
+ commit_limit WINDOWS:0:136
+ write_copies WINDOWS:0:137
+ transition_faults WINDOWS:0:138
+ demand_zero_faults WINDOWS:0:140
+ pages_total WINDOWS:0:141
+ page_reads WINDOWS:0:142
+ pages_output WINDOWS:0:143
+ page_writes WINDOWS:0:144
+ system
+ physmem WINDOWS:1:0
+ freemem WINDOWS:1:1
+ util
+}
+
+mem.util {
+ load WINDOWS:1:2
+ used WINDOWS:1:3
+ free WINDOWS:1:4
+}
+
+swap {
+ length WINDOWS:1:5
+ used WINDOWS:1:6
+ free WINDOWS:1:7
+}
+
+mem.pool {
+ paged_bytes WINDOWS:0:30
+ non_paged_bytes WINDOWS:0:31
+ paged_allocs WINDOWS:0:145
+ nonpaged_allocs WINDOWS:0:146
+ paged_resident_bytes WINDOWS:0:150
+}
+
+mem.cache {
+ read_ahead WINDOWS:0:35
+ lazy_writes WINDOWS:0:32
+ lazy_write_pages WINDOWS:0:33
+ mdl
+ faults WINDOWS:0:139
+ bytes WINDOWS:0:148
+ bytes_peak WINDOWS:0:149
+}
+
+mem.cache.mdl {
+ read WINDOWS:0:34
+ sync_read WINDOWS:0:36
+ async_read WINDOWS:0:37
+}
+
+mem.system {
+ free_ptes WINDOWS:0:147
+ total_code_bytes WINDOWS:0:151
+ resident_code_bytes WINDOWS:0:152
+}
diff --git a/src/pmdas/windows/pmns.network b/src/pmdas/windows/pmns.network
new file mode 100644
index 0000000..7439944
--- /dev/null
+++ b/src/pmdas/windows/pmns.network
@@ -0,0 +1,42 @@
+network {
+ interface
+ tcp
+}
+
+network.interface {
+ in
+ out
+ total
+ bandwidth WINDOWS:0:235
+ speed WINDOWS:0:236
+ baudrate WINDOWS:0:237
+}
+
+network.interface.in {
+ packets WINDOWS:0:38
+ bytes WINDOWS:0:39
+ errors WINDOWS:0:40
+}
+
+network.interface.out {
+ packets WINDOWS:0:41
+ bytes WINDOWS:0:42
+ errors WINDOWS:0:43
+}
+
+network.interface.total {
+ packets WINDOWS:0:44
+ bytes WINDOWS:0:45
+}
+
+network.tcp {
+ activeopens WINDOWS:0:123
+ passiveopens WINDOWS:0:124
+ attemptfails WINDOWS:0:125
+ estabresets WINDOWS:0:126
+ currestab WINDOWS:0:127
+ insegs WINDOWS:0:128
+ outsegs WINDOWS:0:129
+ totalsegs WINDOWS:0:130
+ retranssegs WINDOWS:0:131
+}
diff --git a/src/pmdas/windows/pmns.pmda b/src/pmdas/windows/pmns.pmda
new file mode 100644
index 0000000..2eb3323
--- /dev/null
+++ b/src/pmdas/windows/pmns.pmda
@@ -0,0 +1,4 @@
+pmda {
+ uname WINDOWS:0:115
+ version WINDOWS:0:116
+}
diff --git a/src/pmdas/windows/pmns.process b/src/pmdas/windows/pmns.process
new file mode 100644
index 0000000..a03999a
--- /dev/null
+++ b/src/pmdas/windows/pmns.process
@@ -0,0 +1,59 @@
+process {
+ count WINDOWS:0:173
+ psinfo
+ memory
+ io
+ thread
+}
+
+process.psinfo {
+ pid WINDOWS:0:174
+ ppid WINDOWS:0:175
+ cpu_time WINDOWS:0:176
+ elapsed_time WINDOWS:0:177
+ utime WINDOWS:0:178
+ stime WINDOWS:0:179
+ nthreads WINDOWS:0:180
+ priority_base WINDOWS:0:181
+ nhandles WINDOWS:0:182
+ page_faults WINDOWS:0:183
+}
+
+process.memory {
+ size WINDOWS:0:184
+ rss WINDOWS:0:185
+ rss_peak WINDOWS:0:186
+ virtual WINDOWS:0:187
+ virtual_peak WINDOWS:0:188
+ page_file WINDOWS:0:189
+ page_file_peak WINDOWS:0:190
+ private WINDOWS:0:191
+ pool_paged WINDOWS:0:192
+ pool_nonpaged WINDOWS:0:193
+}
+
+process.io {
+ reads WINDOWS:0:194
+ writes WINDOWS:0:195
+ data WINDOWS:0:196
+ other WINDOWS:0:197
+ read_bytes WINDOWS:0:198
+ write_bytes WINDOWS:0:199
+ data_bytes WINDOWS:0:200
+ other_bytes WINDOWS:0:201
+}
+
+process.thread {
+ context_switches WINDOWS:0:202
+ cpu_time WINDOWS:0:203
+ utime WINDOWS:0:204
+ stime WINDOWS:0:205
+ elapsed_time WINDOWS:0:206
+ priority WINDOWS:0:207
+ priority_base WINDOWS:0:208
+ start_address WINDOWS:0:209
+ state WINDOWS:0:210
+ wait_reason WINDOWS:0:211
+ process_id WINDOWS:0:212
+ thread_id WINDOWS:0:213
+}
diff --git a/src/pmdas/windows/pmns.sqlserver b/src/pmdas/windows/pmns.sqlserver
new file mode 100644
index 0000000..ac29095
--- /dev/null
+++ b/src/pmdas/windows/pmns.sqlserver
@@ -0,0 +1,147 @@
+sqlserver {
+ access
+ buf_mgr
+ cache_mgr
+ databases
+ latches
+ locks
+ sql
+ connections WINDOWS:0:83
+ mem_mgr
+ user_settable
+}
+
+sqlserver.buf_mgr {
+ cache_hit_ratio WINDOWS:0:46
+ page_lookups WINDOWS:0:47
+ free_list_stalls WINDOWS:0:48
+ free_pages WINDOWS:0:49
+ total_pages WINDOWS:0:50
+ target_pages WINDOWS:0:51
+ database_pages WINDOWS:0:52
+ reserved_pages WINDOWS:0:53
+ stolen_pages WINDOWS:0:54
+ lazy_writes WINDOWS:0:55
+ readahead_pages WINDOWS:0:56
+ procedure_cache_pages WINDOWS:0:57
+ page_reads WINDOWS:0:58
+ page_writes WINDOWS:0:59
+ checkpoint_pages WINDOWS:0:60
+ awe
+ page_life_expectancy WINDOWS:0:66
+}
+
+sqlserver.buf_mgr.awe {
+ lookup_maps WINDOWS:0:61
+ stolen_maps WINDOWS:0:62
+ write_maps WINDOWS:0:63
+ unmap_calls WINDOWS:0:64
+ unmap_pages WINDOWS:0:65
+}
+
+sqlserver.locks {
+ all
+ region
+}
+
+sqlserver.locks.all {
+ requests WINDOWS:0:69
+ waits WINDOWS:0:70
+ deadlocks WINDOWS:0:71
+ timeouts WINDOWS:0:72
+ wait_time WINDOWS:0:73
+ avg_wait_time WINDOWS:0:74
+}
+
+sqlserver.locks.region {
+ requests WINDOWS:0:75
+ waits WINDOWS:0:76
+ deadlocks WINDOWS:0:77
+ timeouts WINDOWS:0:78
+ wait_time WINDOWS:0:79
+ avg_wait_time WINDOWS:0:80
+}
+
+sqlserver.cache_mgr {
+ all
+ cache
+}
+
+sqlserver.cache_mgr.all {
+ cache_hit_ratio WINDOWS:0:81
+ cache_pages WINDOWS:0:167
+ cache_object_count WINDOWS:0:168
+ cache_use WINDOWS:0:169
+}
+
+sqlserver.cache_mgr.cache {
+ cache_hit_ratio WINDOWS:0:82
+ cache_pages WINDOWS:0:170
+ cache_object_count WINDOWS:0:171
+ cache_use WINDOWS:0:172
+}
+
+sqlserver.databases {
+ all
+ db
+}
+
+sqlserver.databases.all {
+ transactions WINDOWS:0:84
+ log_flushes WINDOWS:0:102
+ log_bytes_flushed WINDOWS:0:104
+ data_file_size WINDOWS:0:90
+ log_file_size WINDOWS:0:91
+ log_file_used WINDOWS:0:92
+ active_transactions WINDOWS:0:134
+}
+
+sqlserver.databases.db {
+ transactions WINDOWS:0:85
+ log_flushes WINDOWS:0:103
+ log_bytes_flushed WINDOWS:0:105
+ data_file_size WINDOWS:0:93
+ log_file_size WINDOWS:0:94
+ log_file_used WINDOWS:0:95
+ active_transactions WINDOWS:0:135
+}
+
+sqlserver.latches {
+ waits WINDOWS:0:87
+ wait_time WINDOWS:0:88
+ avg_wait_time WINDOWS:0:89
+}
+
+sqlserver.sql {
+ batch_requests WINDOWS:0:86
+ compilations WINDOWS:0:96
+ re_compilations WINDOWS:0:97
+}
+
+sqlserver.access {
+ full_scans WINDOWS:0:98
+ pages_allocated WINDOWS:0:99
+ table_lock_escalations WINDOWS:0:100
+ page_splits WINDOWS:0:122
+}
+
+sqlserver.mem_mgr {
+ connection_memory WINDOWS:0:153
+ granted_workspace WINDOWS:0:154
+ lock_memory WINDOWS:0:155
+ lock_blocks_allocated WINDOWS:0:156
+ lock_owner_blocks_allocated WINDOWS:0:157
+ lock_blocks WINDOWS:0:158
+ lock_owner_blocks WINDOWS:0:159
+ maximum_workspace_memory WINDOWS:0:160
+ memory_grants_outstanding WINDOWS:0:161
+ memory_grants_pending WINDOWS:0:162
+ optimizer_memory WINDOWS:0:163
+ sql_cache_memory WINDOWS:0:164
+ target_server_memory WINDOWS:0:165
+ total_server_memory WINDOWS:0:166
+}
+
+sqlserver.user_settable {
+ query WINDOWS:0:238
+}
diff --git a/src/pmdas/windows/root b/src/pmdas/windows/root
new file mode 100644
index 0000000..2b4e3dc
--- /dev/null
+++ b/src/pmdas/windows/root
@@ -0,0 +1,29 @@
+/*
+ * fake "root" for validating the local PMNS subtree
+ */
+
+#include <stdpmid>
+
+root {
+ hinv
+ kernel
+ disk
+ filesys
+ mem
+ swap
+ network
+ sqlserver
+ pmda
+ process
+}
+
+#include "pmns.hinv"
+#include "pmns.kernel"
+#include "pmns.disk"
+#include "pmns.filesys"
+#include "pmns.mem"
+#include "pmns.network"
+#include "pmns.sqlserver"
+#include "pmns.pmda"
+#include "pmns.process"
+
diff --git a/src/pmdas/zimbra/GNUmakefile b/src/pmdas/zimbra/GNUmakefile
new file mode 100644
index 0000000..76d69a6
--- /dev/null
+++ b/src/pmdas/zimbra/GNUmakefile
@@ -0,0 +1,51 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = zimbra
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+LSRCFILES = Install Remove pmda$(IAM).pl zimbraprobe.sh pmlogconf.all
+LDIRT = domain.h root pmns *.log $(MAN_PAGES)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = pmda$(IAM).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: check_domain $(MAN_PAGES)
+
+pmda$(IAM).1: pmda$(IAM).pl
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 755 zimbraprobe.sh $(PMDADIR)/zimbraprobe
+ $(INSTALL) -m 644 pmda$(IAM).pl $(PMDADIR)/pmda$(IAM).pl
+ @$(INSTALL_MAN)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)
+ $(INSTALL) -m 644 pmlogconf.all $(PCP_VAR_DIR)/config/pmlogconf/$(IAM)/all
+
+default_pcp : default
+
+install_pcp : install
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PERLRULE)
diff --git a/src/pmdas/zimbra/Install b/src/pmdas/zimbra/Install
new file mode 100755
index 0000000..8bff154
--- /dev/null
+++ b/src/pmdas/zimbra/Install
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the Zimbra PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=zimbra
+perl_opt=true
+daemon_opt=false
+forced_restart=false
+
+if ! test -d /opt/zimbra/zmstat ; then
+ echo "Zimbra does not appear to be installed (no /opt/zimbra/zmstat)"
+ echo "Pushing on regardless, but no metric values exist initially."
+fi
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/zimbra/Remove b/src/pmdas/zimbra/Remove
new file mode 100755
index 0000000..beeefe1
--- /dev/null
+++ b/src/pmdas/zimbra/Remove
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Zimbra PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=zimbra
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/zimbra/pmdazimbra.pl b/src/pmdas/zimbra/pmdazimbra.pl
new file mode 100644
index 0000000..f23c2e6
--- /dev/null
+++ b/src/pmdas/zimbra/pmdazimbra.pl
@@ -0,0 +1,905 @@
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+use PCP::PMDA;
+
+my $pmda = PCP::PMDA->new('zimbra', 98);
+my $probe = pmda_config('PCP_PMDAS_DIR') . '/zimbra/zimbraprobe';
+my $stats = '/opt/zimbra/zmstat/';
+
+# Zimbra instrumentation is exported through a series of CSV files.
+# Several of these are system-level files that we already have PMDAs
+# for, extracted more accurately and efficiently, so we ignore those
+# (cpu,vm,mysql).
+# For the rest, we use the PMDA.pm file "tail" mechanism to monitor
+# appends to each of the CSV files. For all data received, we parse
+# the line and extract values. These we store in fixed-size arrays,
+# one per metric cluster, then the fetch callback just passes out the
+# most recently seen value.
+
+my ( $imap_domain, $soap_domain ) = ( 0, 1 );
+my @imap_indom = (
+ 0 => 'CAPABILITY', 1 => 'UID', 2 => 'STATUS',
+ 3 => 'SUBSCRIBE', 4 => 'LIST', 5 => 'SELECT',
+ 6 => 'LOGIN', 7 => 'NAMESPACE', 8 => 'APPEND',
+ 9 => 'OTHER' );
+my @soap_indom = (
+ 0 => 'AuthRequest', 1 => 'DelegateAuthRequest',
+ 2 => 'GetAccountInfoRequest', 3 => 'GetAllServersRequest',
+ 4 => 'GetDomainRequest', 5 => 'GetDomainInfoRequest',
+ 6 => 'GetCosRequest', 7 => 'GetServiceStatusRequest',
+ 8 => 'GetVersionInfoRequest', 9 => 'GetAccountMembershipRequest',
+ 10 => 'GetLDAPEntriesRequest', 11 => 'GetAdminSavedSearchesRequest',
+ 12 => 'GetAdminExtensionZimletsRequest', 13 => 'GetAllConfigRequest',
+ 14 => 'GetLicenseRequest', 15 => 'SearchDirectoryRequest',
+ 16 => 'Other' );
+
+sub zimbra_array_lookup
+{
+ my ( $indomref, $name ) = @_;
+ my @indom = @$indomref;
+ my $index;
+
+ for ($index = 1; $index < $#indom; $index += 2) {
+ return (($index-1) / 2) unless ($indom[$index] ne $name);
+ }
+ return (($#indom-1) / 2); # return the "other" bucket
+}
+
+use vars qw( @fd_values @imap_time_values @imap_count_values @soap_time_values
+ @soap_count_values @mailbox_values @mtaqueue_values @proc_values
+ @threads_values @probe_values );
+use vars qw( $fd_timestamp $imap_timestamp $soap_timestamp
+ $mailbox_timestamp $mtaqueue_timestamp $proc_timestamp
+ $threads_timestamp $probe_timestamp );
+$fd_timestamp = $imap_timestamp = $soap_timestamp = $mailbox_timestamp =
+ $mtaqueue_timestamp = $proc_timestamp = $threads_timestamp =
+ $probe_timestamp = 0;
+
+sub zimbra_fd_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_fd_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d), ||) {
+ chomp;
+ $fd_timestamp = $1;
+ @fd_values = split /,/;
+ }
+}
+
+sub zimbra_imap_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_imap_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d),||) {
+ chomp;
+ my $timestamp = $1;
+ my @values = split /,/;
+
+ if ($timestamp ne $imap_timestamp) {
+ $imap_timestamp = $timestamp;
+ @imap_count_values = ();
+ $imap_count_values[($#imap_indom-1)/2] = 0;
+ @imap_time_values = ();
+ $imap_time_values[($#imap_indom-1)/2] = 0;
+ }
+
+ my $index = zimbra_array_lookup(\@imap_indom, $values[0]);
+ $imap_count_values[$index] = $values[1];
+ $imap_time_values[$index] = $values[2];
+ }
+}
+
+sub zimbra_soap_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_soap_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d),||) {
+ chomp;
+ my $timestamp = $1;
+ my @values = split /,/;
+
+ if ($timestamp ne $soap_timestamp) {
+ $soap_timestamp = $timestamp;
+ @soap_count_values = ();
+ $soap_count_values[($#soap_indom-1)/2] = 0;
+ @soap_time_values = ();
+ $soap_time_values[($#soap_indom-1)/2] = 0;
+ }
+
+ my $index = zimbra_array_lookup(\@soap_indom, $values[0]);
+ $soap_count_values[$index] = $values[1];
+ $soap_time_values[$index] = $values[2];
+ }
+}
+
+sub zimbra_mailbox_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_mailbox_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d),||) {
+ chomp;
+ $mailbox_timestamp = $1;
+ @mailbox_values = split /,/;
+ }
+}
+
+sub zimbra_mtaqueue_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_mtaqueue_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d), ||) {
+ chomp;
+ $mtaqueue_timestamp = $1;
+ @mtaqueue_values = split /,/;
+ }
+}
+
+sub zimbra_proc_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_proc_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d), ||) {
+ chomp;
+ $proc_timestamp = $1;
+ @proc_values = ();
+ my @values = split /,/;
+ splice(@values, 0, 5); # ditch "system" values
+
+ # 7 values for mailbox,mysql,convertd,ldap,postfix,amavis,clam
+ # seems sometimes not all are installed/available/whatever, so
+ # take care to handle that case.
+
+ while ($#values >= 7) {
+ my $offset = 0;
+ my @chunk = splice(@values, 0, 8);
+ if (defined($chunk[0])) {
+ $chunk[0] =~ s/\s//g;
+ }
+ if (!defined($chunk[0])) {
+ $pmda->log("zimbra_proc_parser unexpected input: $_");
+ }
+ elsif ($chunk[0] eq 'mailbox') { $offset = 0; }
+ elsif ($chunk[0] eq 'mysql') { $offset = 7; }
+ elsif ($chunk[0] eq 'convertd') { $offset = 14; }
+ elsif ($chunk[0] eq 'ldap') { $offset = 21; }
+ elsif ($chunk[0] eq 'postfix') { $offset = 28; }
+ elsif ($chunk[0] eq 'amavis') { $offset = 35; }
+ elsif ($chunk[0] eq 'clam') { $offset = 42; }
+ else {
+ $pmda->log("zimbra_proc_parser unexpected data: $chunk[0]");
+ }
+ shift @chunk; # remove the chunk name - then add values
+ splice(@proc_values, $offset, 7, @chunk); # to correct spot
+ }
+ }
+ else {
+ # This includes the initial header line, so just debug:
+ # $pmda->log("zimbra_proc_parser could not parse line: $_");
+ }
+}
+
+sub zimbra_threads_parser
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_threads_parser got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d),||) {
+ chomp;
+ $threads_timestamp = $1;
+ @threads_values = split /,/;
+ }
+}
+
+sub zimbra_probe_callback
+{
+ ( undef, $_ ) = @_;
+
+ # $pmda->log("zimbra_probe_callback got line: $_");
+ if (s|^(\d\d/\d\d/\d\d\d\d \d\d:\d\d:\d\d)$||) {
+ $probe_timestamp = $1;
+ } else {
+ my ($service, $status) = split;
+
+ return unless defined($status);
+
+ $status = ($status eq 'Running') ? 1 : 0;
+ $probe_values[ 0] = $status unless ($service ne 'antivirus');
+ $probe_values[ 1] = $status unless ($service ne 'antispam');
+ $probe_values[ 2] = $status unless ($service ne 'archiving');
+ $probe_values[ 3] = $status unless ($service ne 'convertd');
+ $probe_values[ 4] = $status unless ($service ne 'mta');
+ $probe_values[ 5] = $status unless ($service ne 'mailbox');
+ $probe_values[ 6] = $status unless ($service ne 'logger');
+ $probe_values[ 7] = $status unless ($service ne 'snmp');
+ $probe_values[ 8] = $status unless ($service ne 'ldap');
+ $probe_values[ 9] = $status unless ($service ne 'spell');
+ $probe_values[10] = $status unless ($service ne 'imapproxy');
+ $probe_values[11] = $status unless ($service ne 'stats');
+ }
+}
+
+sub zimbra_fetch_callback
+{
+ my ($cluster, $item, $inst) = @_;
+
+ #$pmda->log("zimbra_fetch_callback for PMID: $cluster.$item ($inst)");
+ if ($inst != PM_IN_NULL && $cluster != 1 && $cluster != 2) {
+ return (PM_ERR_INST, 0);
+ }
+
+ if ($cluster == 0) { # fd.csv
+ if ($item >= 0 && $item <= 0) {
+ return (PM_ERR_AGAIN, 0) unless defined($fd_values[$item]);
+ return ($fd_values[$item], 1);
+ }
+ }
+ elsif ($cluster == 1 && $item == 0) { # imap.csv
+ if ($inst >= 0 && $inst <= $#imap_indom) {
+ return (PM_ERR_AGAIN, 0) unless defined($imap_count_values[$inst]);
+ return ($imap_count_values[$inst], 1);
+ }
+ }
+ elsif ($cluster == 1 && $item == 1) { # imap.csv
+ if ($inst >= 0 && $inst < $#imap_indom) {
+ return (PM_ERR_AGAIN, 0) unless defined($imap_time_values[$inst]);
+ return ($imap_time_values[$inst], 1);
+ }
+ }
+ elsif ($cluster == 2 && $item == 0) { # soap.csv
+ if ($inst >= 0 && $inst < $#soap_indom) {
+ return (PM_ERR_AGAIN, 0) unless defined($soap_count_values[$inst]);
+ return ($soap_count_values[$inst], 1);
+ }
+ }
+ elsif ($cluster == 2 && $item == 1) { # soap.csv
+ if ($inst >= 0 && $inst < $#soap_indom) {
+ return (PM_ERR_AGAIN, 0) unless defined($soap_time_values[$inst]);
+ return ($soap_time_values[$inst], 1);
+ }
+ }
+ elsif ($cluster == 3) { # mailboxd.csv
+ if ($item >= 0 && $item <= 60) {
+ return (PM_ERR_AGAIN, 0) unless defined($mailbox_values[$item]);
+ if ($mailbox_values[$item] eq '') {
+ $mailbox_values[$item] = 0;
+ }
+ return ($mailbox_values[$item], 1);
+ }
+ }
+ elsif ($cluster == 4) { # mtaqueue.csv
+ if ($item >= 0 && $item <= 1) {
+ return (PM_ERR_AGAIN, 0) unless defined($mtaqueue_values[$item]);
+ return ($mtaqueue_values[$item], 1);
+ }
+ }
+ elsif ($cluster == 5) { # proc.csv
+ if ($item >= 0 && $item <= 48) {
+ return (PM_ERR_AGAIN, 0) unless defined($proc_values[$item]);
+ return ($proc_values[$item], 1);
+ }
+ }
+ elsif ($cluster == 6) { # threads.csv
+ if ($item >= 0 && $item <= 13) {
+ return (PM_ERR_AGAIN, 0) unless defined($threads_values[$item]);
+ return ($threads_values[$item], 1);
+ }
+ }
+ elsif ($cluster == 7) { # timestamps
+ return ($fd_timestamp, 1) unless ($item != 0);
+ return ($imap_timestamp, 1) unless ($item != 1);
+ return ($soap_timestamp, 1) unless ($item != 2);
+ return ($mailbox_timestamp, 1) unless ($item != 3);
+ return ($mtaqueue_timestamp, 1) unless ($item != 4);
+ return ($proc_timestamp, 1) unless ($item != 5);
+ return ($threads_timestamp, 1) unless ($item != 6);
+ return ($probe_timestamp, 1) unless ($item != 7);
+ }
+ elsif ($cluster == 8) { # status probe
+ if ($item >= 0 && $item <= 11) {
+ return (PM_ERR_APPVERSION, 0) unless defined($probe_values[$item]);
+ return ($probe_values[$item], 1);
+ }
+ }
+
+ return (PM_ERR_PMID, 0);
+}
+
+$pmda->add_metric(pmda_pmid(0,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.fd_count',
+ '/opt/zimbra/zmstat/fd.csv', 'Open file descriptors');
+
+$pmda->add_metric(pmda_pmid(1,0), PM_TYPE_U32, $imap_domain, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.imap.count',
+ '/opt/zimbra/zmstat/imap.csv',
+ 'Count of Internet Message Access Protocol commands');
+$pmda->add_metric(pmda_pmid(1,1), PM_TYPE_U32, $imap_domain, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.imap.avgtime',
+ '/opt/zimbra/zmstat/imap.csv',
+ 'Time spent executing Internet Message Access Protocol commands');
+
+$pmda->add_metric(pmda_pmid(2,0), PM_TYPE_U32, $soap_domain, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.soap.count',
+ '/opt/zimbra/zmstat/soap.csv',
+ 'Count of Simple Object Access Protocol commands');
+$pmda->add_metric(pmda_pmid(2,1), PM_TYPE_U32, $soap_domain, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.soap.avgtime',
+ '/opt/zimbra/zmstat/soap.csv',
+ 'Time spent executing Simple Object Access Protocol commands');
+
+$pmda->add_metric(pmda_pmid(3,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.lmtp.rcvd_msgs',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Received mail messages');
+$pmda->add_metric(pmda_pmid(3,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.lmtp.rcvd_bytes',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Received bytes');
+$pmda->add_metric(pmda_pmid(3,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.lmtp.rcvd_rcpt',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Received receipts');
+$pmda->add_metric(pmda_pmid(3,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.lmtp.dlvd_msgs',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Delivered messages');
+$pmda->add_metric(pmda_pmid(3,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.lmtp.dlvd_bytes',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Delivered bytes');
+$pmda->add_metric(pmda_pmid(3,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.db_conn.count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Database connection count');
+$pmda->add_metric(pmda_pmid(3,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.db_conn.time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Database connection time');
+$pmda->add_metric(pmda_pmid(3,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.ldap.dc_count',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.ldap.dc_time',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.mbox.add_msg_count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox message add count');
+$pmda->add_metric(pmda_pmid(3,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.mbox.add_msg_time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox message add average time');
+$pmda->add_metric(pmda_pmid(3,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.mbox.get_count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox get count');
+$pmda->add_metric(pmda_pmid(3,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.mbox.get_time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox average get time');
+$pmda->add_metric(pmda_pmid(3,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.mbox_cache',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox cache');
+$pmda->add_metric(pmda_pmid(3,14), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.mbox_msg_cache',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox message cache');
+$pmda->add_metric(pmda_pmid(3,15), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.mbox_item_cache',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Mailbox item cache');
+$pmda->add_metric(pmda_pmid(3,16), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.soap.count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Simple Object Access Protocol count');
+$pmda->add_metric(pmda_pmid(3,17), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.soap.time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Simple Object Access Protocol time');
+$pmda->add_metric(pmda_pmid(3,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.imap.count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Internet Message Access Protocol count');
+$pmda->add_metric(pmda_pmid(3,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.imap.time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Internet Message Access Protocol time');
+$pmda->add_metric(pmda_pmid(3,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.pop.count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Post Office Protocol count');
+$pmda->add_metric(pmda_pmid(3,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.pop.time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Post Office Protocol time');
+$pmda->add_metric(pmda_pmid(3,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.idx.wrt_avg',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Lucene Index Writer count');
+$pmda->add_metric(pmda_pmid(3,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.idx.wrt_opened',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.idx.wrt_opened_cache_hit',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,25), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.calcache.hit',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,26), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.calcache.mem_hit',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.calcache.lru_size',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,28), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.idx.bytes_written',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,29), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.idx.bytes_written_avg',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,30), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.idx.bytes_read',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,31), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.idx.bytes_read_avg',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,32), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.bis.read',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,33), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.bis.seek_rate',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.mailboxd.db_pool_size',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,35), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.innodb_bp_hit_rate',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,36), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.pop.conn',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,37), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.pop.ssl_conn',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,38), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.imap.conn',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,39), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.imap.ssl_conn',
+ '/opt/zimbra/zmstat/mailboxd.csv', '');
+$pmda->add_metric(pmda_pmid(3,40), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.soap.sessions',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Simple Object Access Protocol session count');
+# Zimbra duplicates four metrics here, so skip four for direct indexing
+$pmda->add_metric(pmda_pmid(3,45), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.gc.minor_count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java minor garbage collection count');
+$pmda->add_metric(pmda_pmid(3,46), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.gc.minor_time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java minor garbage collection time');
+$pmda->add_metric(pmda_pmid(3,47), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.mailboxd.gc.major_count',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java major garbage collection count');
+$pmda->add_metric(pmda_pmid(3,48), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mailboxd.gc.major_time',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java major garbage collection time');
+$pmda->add_metric(pmda_pmid(3,49), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.code_cache_used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java code cache space used');
+$pmda->add_metric(pmda_pmid(3,50), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.code_cache_free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java code cache space free');
+$pmda->add_metric(pmda_pmid(3,51), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.eden_space_used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java Eden area space used');
+$pmda->add_metric(pmda_pmid(3,52), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.eden_space_free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java Eden area space free');
+$pmda->add_metric(pmda_pmid(3,53), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.survivor_space_used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java survivor area space used');
+$pmda->add_metric(pmda_pmid(3,54), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.survivor_space_free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java survivor area space free');
+$pmda->add_metric(pmda_pmid(3,55), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.old_gen_space_used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java old generation space used');
+$pmda->add_metric(pmda_pmid(3,56), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.old_gen_space_free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java old generation space free');
+$pmda->add_metric(pmda_pmid(3,57), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.perm_gen_space_used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java permanent generation space used');
+$pmda->add_metric(pmda_pmid(3,58), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.mempool.perm_gen_space_free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java permanent generation space free');
+$pmda->add_metric(pmda_pmid(3,59), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.heap.used',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java heap space used');
+$pmda->add_metric(pmda_pmid(3,60), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_BYTE,0,0), 'zimbra.mailboxd.heap.free',
+ '/opt/zimbra/zmstat/mailboxd.csv', 'Java heap space free');
+
+$pmda->add_metric(pmda_pmid(4,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_KBYTE,0,0), 'zimbra.mtaqueue.size',
+ '/opt/zimbra/zmstat/mtaqueue.csv',
+ 'Number of kilobytes queued to the Mail Transfer Agent');
+$pmda->add_metric(pmda_pmid(4,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,1,0,0,PM_TIME_MSEC,0), 'zimbra.mtaqueue.requests',
+ '/opt/zimbra/zmstat/mtaqueue.csv',
+ 'Number of requests queued to the Mail Transfer Agent');
+
+$pmda->add_metric(pmda_pmid(5,0), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mailbox.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the mailbox process');
+$pmda->add_metric(pmda_pmid(5,1), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mailbox.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the mailbox process');
+$pmda->add_metric(pmda_pmid(5,2), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mailbox.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the mailbox process');
+$pmda->add_metric(pmda_pmid(5,3), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mailbox.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the mailbox processes');
+$pmda->add_metric(pmda_pmid(5,4), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mailbox.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the mailbox processes');
+$pmda->add_metric(pmda_pmid(5,5), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mailbox.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the mailbox processes');
+$pmda->add_metric(pmda_pmid(5,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.mailbox.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of mailbox processes');
+
+$pmda->add_metric(pmda_pmid(5,7), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mysql.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the MySQL process');
+$pmda->add_metric(pmda_pmid(5,8), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mysql.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the MySQL process');
+$pmda->add_metric(pmda_pmid(5,9), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.mysql.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the MySQL process');
+$pmda->add_metric(pmda_pmid(5,10), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mysql.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the MySQL processes');
+$pmda->add_metric(pmda_pmid(5,11), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mysql.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the MySQL processes');
+$pmda->add_metric(pmda_pmid(5,12), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.mysql.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the MySQL processes');
+$pmda->add_metric(pmda_pmid(5,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.mysql.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of MySQL processes');
+
+$pmda->add_metric(pmda_pmid(5,14), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.convertd.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the convertd process');
+$pmda->add_metric(pmda_pmid(5,15), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.convertd.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the convertd process');
+$pmda->add_metric(pmda_pmid(5,16), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.convertd.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the convertd process');
+$pmda->add_metric(pmda_pmid(5,17), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.convertd.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the convertd processes');
+$pmda->add_metric(pmda_pmid(5,18), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.convertd.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the convertd processes');
+$pmda->add_metric(pmda_pmid(5,19), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.convertd.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the convertd processes');
+$pmda->add_metric(pmda_pmid(5,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.convertd.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of convertd processes');
+
+$pmda->add_metric(pmda_pmid(5,21), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.ldap.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the LDAP process');
+$pmda->add_metric(pmda_pmid(5,22), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.ldap.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the LDAP process');
+$pmda->add_metric(pmda_pmid(5,23), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.ldap.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the LDAP process');
+$pmda->add_metric(pmda_pmid(5,24), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.ldap.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the LDAP processes');
+$pmda->add_metric(pmda_pmid(5,25), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.ldap.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the LDAP processes');
+$pmda->add_metric(pmda_pmid(5,26), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.ldap.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the LDAP processes');
+$pmda->add_metric(pmda_pmid(5,27), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.ldap.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of LDAP processes');
+
+$pmda->add_metric(pmda_pmid(5,28), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.postfix.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the postfix process');
+$pmda->add_metric(pmda_pmid(5,29), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.postfix.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the postfix process');
+$pmda->add_metric(pmda_pmid(5,30), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.postfix.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the postfix process');
+$pmda->add_metric(pmda_pmid(5,31), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.postfix.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the postfix processes');
+$pmda->add_metric(pmda_pmid(5,32), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.postfix.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the postfix processes');
+$pmda->add_metric(pmda_pmid(5,33), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.postfix.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the postfix processes');
+$pmda->add_metric(pmda_pmid(5,34), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.postfix.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of postfix processes');
+
+$pmda->add_metric(pmda_pmid(5,35), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.amavis.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the virus scanner');
+$pmda->add_metric(pmda_pmid(5,36), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.amavis.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the virus scanner');
+$pmda->add_metric(pmda_pmid(5,37), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.amavis.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the virus scanner');
+$pmda->add_metric(pmda_pmid(5,38), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.amavis.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the virus scanner process');
+$pmda->add_metric(pmda_pmid(5,39), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.amavis.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the virus scanner process');
+$pmda->add_metric(pmda_pmid(5,40), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.amavis.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the virus scanner processes');
+$pmda->add_metric(pmda_pmid(5,41), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.amavis.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of virus scanner processes');
+
+$pmda->add_metric(pmda_pmid(5,42), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.clam.cputime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total time as a percentage spent executing the anti-virus process');
+$pmda->add_metric(pmda_pmid(5,43), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.clam.utime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total user time as a percentage spent executing the anti-virus process');
+$pmda->add_metric(pmda_pmid(5,44), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.proc.clam.stime',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total systime as a percentage spent executing the anti-virus process');
+$pmda->add_metric(pmda_pmid(5,45), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.clam.total',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Total virtual memory footprint of the anti-virus processes');
+$pmda->add_metric(pmda_pmid(5,46), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.clam.rss',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Resident set size of the clam processes');
+$pmda->add_metric(pmda_pmid(5,47), PM_TYPE_FLOAT, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(1,0,0,PM_SPACE_MBYTE,0,0), 'zimbra.proc.clam.shared',
+ '/opt/zimbra/zmstat/proc.csv',
+ 'Shared memory space used by the anti-virus processes');
+$pmda->add_metric(pmda_pmid(5,48), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.proc.clam.count',
+ '/opt/zimbra/zmstat/proc.csv', 'Total number of anti-virus processes');
+
+$pmda->add_metric(pmda_pmid(6,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.btpool',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.pool',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.lmtp',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.imap',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.pop3',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.scheduled_tasks',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.timer',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.anonymous_io',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.flap_processor',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.gc',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.socket_acceptor',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.thread',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.other',
+ '/opt/zimbra/zmstat/threads.csv', '');
+$pmda->add_metric(pmda_pmid(6,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,1,0,0,PM_COUNT_ONE), 'zimbra.threads.total',
+ '/opt/zimbra/zmstat/threads.csv', '');
+
+$pmda->add_metric(pmda_pmid(7,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.fd.timestamp',
+ 'Timestamp for completion of last fd value fetch', '');
+$pmda->add_metric(pmda_pmid(7,1), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.imap.timestamp',
+ 'Timestamp for completion of last imap value fetch', '');
+$pmda->add_metric(pmda_pmid(7,2), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.soap.timestamp',
+ 'Timestamp for completion of last soap value fetch', '');
+$pmda->add_metric(pmda_pmid(7,3), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.mailbox.timestamp',
+ 'Timestamp for completion of last mailbox value fetch', '');
+$pmda->add_metric(pmda_pmid(7,4), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.mtaqueue.timestamp',
+ 'Timestamp for completion of last mtaqueue value fetch', '');
+$pmda->add_metric(pmda_pmid(7,5), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.proc.timestamp',
+ 'Timestamp for completion of last process value fetch', '');
+$pmda->add_metric(pmda_pmid(7,6), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.threads.timestamp',
+ 'Timestamp for completion of last threads value fetch', '');
+$pmda->add_metric(pmda_pmid(7,7), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.control.status.timestamp',
+ 'Timestamp for completion of last status probe', '');
+
+$pmda->add_metric(pmda_pmid(8,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.antivirus',
+ 'Status for Zimbra antivirus service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.antispam',
+ 'Status for Zimbra antispam service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.archiving',
+ 'Status for Zimbra archiving service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.convertd',
+ 'Status for Zimbra convertd service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.mta',
+ 'Status for Zimbra MTA service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.mailbox',
+ 'Status for Zimbra mailbox service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.logger',
+ 'Status for Zimbra logger service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.snmp',
+ 'Status for Zimbra SNMP service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.ldap',
+ 'Status for Zimbra LDAP service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.spell',
+ 'Status for Zimbra spell service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.imapproxy',
+ 'Status for Zimbra imapproxy service - 0=stopped, 1=running', '');
+$pmda->add_metric(pmda_pmid(8,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
+ pmda_units(0,0,0,0,0,0), 'zimbra.status.stats',
+ 'Status for Zimbra stats service - 0=stopped, 1=running', '');
+
+$pmda->add_indom($imap_domain, \@imap_indom, 'IMAP operations',
+ 'Internet Message Access Protocol operations');
+$pmda->add_indom($soap_domain, \@soap_indom, 'SOAP operation',
+ 'Simple Object Access Protocol operations');
+
+$pmda->add_tail($stats . 'fd.csv', \&zimbra_fd_parser, 0);
+$pmda->add_tail($stats . 'imap.csv', \&zimbra_imap_parser, 0);
+$pmda->add_tail($stats . 'mailboxd.csv', \&zimbra_mailbox_parser, 0);
+$pmda->add_tail($stats . 'mtaqueue.csv', \&zimbra_mtaqueue_parser, 0);
+$pmda->add_tail($stats . 'proc.csv', \&zimbra_proc_parser, 0);
+$pmda->add_tail($stats . 'soap.csv', \&zimbra_soap_parser, 0);
+$pmda->add_tail($stats . 'threads.csv', \&zimbra_threads_parser, 0);
+
+$pmda->add_pipe($probe, \&zimbra_probe_callback, 0);
+
+$pmda->set_fetch_callback(\&zimbra_fetch_callback);
+$pmda->set_user('pcp');
+$pmda->run;
+
+=pod
+
+=head1 NAME
+
+pmdazimbra - Zimbra Collaboration Suite (ZCS) PMDA
+
+=head1 DESCRIPTION
+
+B<pmdazimbra> is a Performance Metrics Domain Agent (PMDA) which
+exports metric values from several subsystems of the Zimbra Suite.
+Further details on Zimbra can be found at http://www.zimbra.com/.
+
+=head1 INSTALLATION
+
+If you want access to the names and values for the zimbra performance
+metrics, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/zimbra
+ # ./Install
+
+If you want to undo the installation, do the following as root:
+
+ # cd $PCP_PMDAS_DIR/zimbra
+ # ./Remove
+
+B<pmdazimbra> is launched by pmcd(1) and should never be executed
+directly. The Install and Remove scripts notify pmcd(1) when
+the agent is installed or removed.
+
+=head1 FILES
+
+=over
+
+=item /opt/zimbra/zmstat/*
+
+comma-separated value files containing Zimbra performance data
+
+=item $PCP_PMDAS_DIR/zimbra/Install
+
+installation script for the B<pmdazimbra> agent
+
+=item $PCP_PMDAS_DIR/zimbra/Remove
+
+undo installation script for the B<pmdazimbra> agent
+
+=item $PCP_LOG_DIR/pmcd/zimbra.log
+
+default log file for error messages from B<pmdazimbra>
+
+=back
+
+=head1 SEE ALSO
+
+pmcd(1).
diff --git a/src/pmdas/zimbra/pmlogconf.all b/src/pmdas/zimbra/pmlogconf.all
new file mode 100644
index 0000000..3c3f14d
--- /dev/null
+++ b/src/pmdas/zimbra/pmlogconf.all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident All Zimbra information
+probe zimbra.fd_count
+ zimbra
diff --git a/src/pmdas/zimbra/zimbraprobe.sh b/src/pmdas/zimbra/zimbraprobe.sh
new file mode 100755
index 0000000..a238e27
--- /dev/null
+++ b/src/pmdas/zimbra/zimbraprobe.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+delay=${1:-10}
+
+# First one produces any errors to PMDA logfile
+su -c 'zmcontrol status' - zimbra
+
+while true
+do
+ date +'%d/%m/%Y %H:%M:%S'
+ sleep $delay
+ su -c 'zmcontrol status' - zimbra 2>/dev/null
+done
diff --git a/src/pmdas/zswap/GNUmakefile b/src/pmdas/zswap/GNUmakefile
new file mode 100644
index 0000000..e36bfa8
--- /dev/null
+++ b/src/pmdas/zswap/GNUmakefile
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+IAM = zswap
+PYSCRIPT = pmda$(IAM).python
+LSRCFILES = Install Remove $(PYSCRIPT)
+
+DOMAIN = ZSWAP
+PMDADIR = $(PCP_PMDAS_DIR)/$(IAM)
+
+LDIRT = domain.h $(IAM).log
+
+default_pcp default: check_domain
+
+include $(BUILDRULES)
+
+install_pcp install: default
+ $(INSTALL) -m 755 -d $(PMDADIR)
+ $(INSTALL) -m 755 Install Remove $(PMDADIR)
+ $(INSTALL) -m 644 $(PYSCRIPT) $(PMDADIR)/$(PYSCRIPT)
+
+check_domain: ../../pmns/stdpmid
+ $(DOMAIN_PYTHONRULE)
diff --git a/src/pmdas/zswap/Install b/src/pmdas/zswap/Install
new file mode 100644
index 0000000..0ba70c2
--- /dev/null
+++ b/src/pmdas/zswap/Install
@@ -0,0 +1,28 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Install the zswap PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=zswap
+python_opt=true
+daemon_opt=false
+forced_restart=true
+
+pmdaSetup
+pmdaInstall
+exit 0
diff --git a/src/pmdas/zswap/Remove b/src/pmdas/zswap/Remove
new file mode 100644
index 0000000..c85a578
--- /dev/null
+++ b/src/pmdas/zswap/Remove
@@ -0,0 +1,25 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Remove the Linux zswap PMDA
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmdaproc.sh
+
+iam=zswap
+
+pmdaSetup
+pmdaRemove
+exit 0
diff --git a/src/pmdas/zswap/pmdazswap.python b/src/pmdas/zswap/pmdazswap.python
new file mode 100644
index 0000000..ef06d21
--- /dev/null
+++ b/src/pmdas/zswap/pmdazswap.python
@@ -0,0 +1,189 @@
+'''
+Performance Metrics Domain Agent exporting Linux compressed swap metrics.
+'''
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+import cpmapi as c_api
+from pcp.pmapi import pmUnits
+from pcp.pmda import PMDA, pmdaMetric, pmdaIndom
+from resource import getpagesize
+from os import getenv, listdir
+
+ZSWAP_PAGESIZE = getpagesize()
+ZSWAP_STATS_PATH = '/sys/kernel/debug/zswap'
+
+class ZswapPMDA(PMDA):
+ '''
+ Performance Metrics Domain Agent exporting compressed swap metrics.
+ Install it and make basic use of it if you use zswap, as follows:
+
+ # $PCP_PMDAS_DIR/zswap/Install
+ $ pminfo -fmdtT zswap
+ '''
+
+ def zswap_fetch_single(self, name):
+ ''' Extract one value from a single zswap statistics file. '''
+ fullpath = self.path + '/' + name
+ try:
+ with open(fullpath) as file:
+ value = file.readline()
+ self.values[name] = long(value)
+ except:
+ # use a simple approach to avoiding errors on every fetch
+ if self.fileerrors < self.nmetrics:
+ self.err("fetch_single: failed to read %s" % fullpath)
+ self.fileerrors += 1
+ pass
+
+ def zswap_fetch(self):
+ '''
+ Called once per PCP "fetch" PDU from pmcd(1)
+ Iterates over the (sysfs debug) path holding zswap statistics,
+ building a hash of values (one value extracted from each file).
+ '''
+ # self.log("fetch: %s" % self.path)
+ try:
+ self.patherrors = 0
+ files = listdir(self.path)
+ for name in files:
+ self.zswap_fetch_single(name)
+ except:
+ # the kernel module may simply not be loaded currently
+ self.patherrors = 1
+ pass
+
+
+ def zswap_fetch_callback(self, cluster, item, inst):
+ '''
+ Main fetch callback - looks up value associated with requested PMID
+ (from zswap_fetch), converts units if needed, then returns a list of
+ value,status (single pair) for the requested pmid
+ '''
+ # self.log("fetch callback for %d.%d[%d]" % (cluster, item, inst))
+ if inst != c_api.PM_IN_NULL:
+ return [c_api.PM_ERR_INST, 0]
+ elif cluster != 0:
+ return [c_api.PM_ERR_PMID, 0]
+ if item >= 0 and item < self.nmetrics and self.patherrors == 1:
+ return [c_api.PM_ERR_AGAIN, 0]
+
+ if item == 0:
+ return [self.values['pool_limit_hit'], 1]
+ elif item == 1:
+ return [self.values['reject_reclaim_fail'], 1]
+ elif item == 2:
+ return [self.values['reject_alloc_fail'], 1]
+ elif item == 3:
+ return [self.values['reject_kmemcache_fail'], 1]
+ elif item == 4:
+ return [self.values['reject_compress_poor'], 1]
+ elif item == 5:
+ return [self.values['written_back_pages'] * self.pagesize, 1]
+ elif item == 6:
+ return [self.values['duplicate_entry'], 1]
+ elif item == 7:
+ return [self.values['pool_pages'] * self.pagesize, 1]
+ elif item == 8:
+ return [self.values['stored_pages'] * self.pagesize, 1]
+ return [c_api.PM_ERR_PMID, 0]
+
+
+ def setup_zswap_metrics(self, name):
+ '''
+ Setup the metric table - ensure a values hash entry is setup for
+ each metric; that way we don't need to worry about KeyErrors in
+ the fetch callback routine (e.g. if the kernel interface changes).
+ '''
+ self.values['pool_limit_hit'] = 0
+ self.add_metric(name + '.pool_limit_hit', pmdaMetric(
+ self.pmid(0, 0),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Pool limit was hit (see zswap_max_pool_percent module parameter)')
+
+ self.values['reject_reclaim_fail'] = 0
+ self.add_metric(name + '.reject_reclaim_fail', pmdaMetric(
+ self.pmid(0, 1),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Store failed due to a reclaim failure after pool limit was reached')
+
+ self.values['reject_alloc_fail'] = 0
+ self.add_metric(name + '.reject_alloc_fail', pmdaMetric(
+ self.pmid(0, 2),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Store failed because underlying allocator could not get memory')
+
+ self.values['reject_kmemcache_fail'] = 0
+ self.add_metric(name + '.reject_kmemcache_fail', pmdaMetric(
+ self.pmid(0, 3),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Store failed because the entry metadata could not be allocated (rare)')
+
+ self.values['reject_compress_poor'] = 0
+ self.add_metric(name + '.reject_compress_poor', pmdaMetric(
+ self.pmid(0, 4),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Compressed page was too big for the allocator to (optimally) store')
+
+ self.values['written_back_pages'] = 0
+ self.add_metric(name + '.written_back_pages', pmdaMetric(
+ self.pmid(0, 5),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
+ 'Pages written back when pool limit was reached')
+
+ self.values['duplicate_entry'] = 0
+ self.add_metric(name + '.duplicate_entry', pmdaMetric(
+ self.pmid(0, 6),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(0, 0, 1, 0, 0, c_api.PM_COUNT_ONE)),
+ 'Duplicate store was encountered (rare)')
+
+ self.values['pool_pages'] = 0
+ self.add_metric(name + '.pool_pages', pmdaMetric(
+ self.pmid(0, 7),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
+ 'Memory pages used by the compressed pool')
+
+ self.values['stored_pages'] = 0
+ self.add_metric(name + '.stored_pages', pmdaMetric(
+ self.pmid(0, 8),
+ c_api.PM_TYPE_U64, c_api.PM_INDOM_NULL, c_api.PM_SEM_COUNTER,
+ pmUnits(1, 0, 0, c_api.PM_SPACE_KBYTE, 0, 0)),
+ 'Compressed pages currently stored in zswap')
+
+
+ def __init__(self, name, domain):
+ PMDA.__init__(self, name, domain)
+ self.patherrors = 0
+ self.fileerrors = 0
+ self.pagesize = int(getenv('ZSWAP_PAGESIZE', ZSWAP_PAGESIZE))
+ self.path = str(getenv('ZSWAP_STATS_PATH', ZSWAP_STATS_PATH))
+
+ self.values = {}
+ self.setup_zswap_metrics(name)
+ self.nmetrics = len(self.values)
+
+ self.set_fetch(self.zswap_fetch)
+ self.set_fetch_callback(self.zswap_fetch_callback)
+
+
+if __name__ == '__main__':
+ ZswapPMDA('zswap', 125).run()
diff --git a/src/pmdate/GNUmakefile b/src/pmdate/GNUmakefile
new file mode 100644
index 0000000..b5ede4f
--- /dev/null
+++ b/src/pmdate/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmdate.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmdate$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdate/pmdate.c b/src/pmdate/pmdate.c
new file mode 100644
index 0000000..acec4a6
--- /dev/null
+++ b/src/pmdate/pmdate.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Display offset date
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+#define usage "Usage: pmdate { +valueS | -valueS } ... format\n\
+\n\
+where the scale \"S\" is one of: S (seconds), M (minutes), H (hours), \n\
+d (days), m (months) or y (years)\n"
+
+int
+main(int argc, char *argv[])
+{
+ time_t now;
+ int need;
+ char *buf;
+ char *p;
+ char *pend;
+ struct tm *tmp;
+ int sgn;
+ int val;
+ int mo_delta = 0;
+ int yr_delta = 0;
+
+ __pmSetProgname(argv[0]);
+
+ if (argc < 2) {
+ fprintf(stderr, usage);
+ exit(1);
+ }
+
+ if (strcmp(argv[1], "-?") == 0 || strcmp(argv[1], "--help") == 0) {
+ fprintf(stderr, usage);
+ exit(1);
+ }
+
+ time(&now);
+
+ while (argc > 2) {
+ p = argv[1];
+ if (*p == '+')
+ sgn = 1;
+ else if (*p == '-')
+ sgn = -1;
+ else {
+ fprintf(stderr, "%s: incorrect leading sign for offset (%s), must be \"+\" or \"-\"\n",
+ pmProgname, argv[1]);
+ exit(1);
+ }
+ p++;
+
+ val = (int)strtol(p, &pend, 10);
+ switch (*pend) {
+ case 'S':
+ now += sgn * val;
+ break;
+ case 'M':
+ now += sgn * val * 60;
+ break;
+ case 'H':
+ now += sgn * val * 60 * 60;
+ break;
+ case 'd':
+ now += sgn * val * 24 * 60 * 60;
+ break;
+ case 'm':
+ mo_delta += sgn*val;
+ break;
+ case 'y':
+ yr_delta += sgn*val;
+ break;
+ case '\0':
+ fprintf(stderr, "%s: missing scale after offset (%s)\n", pmProgname, argv[1]);
+ exit(1);
+ case '?':
+ fprintf(stderr, usage);
+ exit (1);
+ default:
+ fprintf(stderr, "%s: unknown scale after offset (%s)\n", pmProgname, argv[1]);
+ exit(1);
+ }
+
+ argv++;
+ argc--;
+ }
+
+ tmp = localtime(&now);
+
+ if (yr_delta) {
+ /*
+ * tm_year is years since 1900 and yr_delta is relative (not
+ * absolute), so this is Y2K safe
+ */
+ tmp->tm_year += yr_delta;
+ }
+ if (mo_delta) {
+ /*
+ * tm_year is years since 1900 and the tm_year-- and
+ * tm_year++ is adjusting for underflow and overflow in
+ * tm_mon as a result of relative month delta, so this
+ * is Y2K safe
+ */
+ tmp->tm_mon += mo_delta;
+ while (tmp->tm_mon < 0) {
+ tmp->tm_mon += 12;
+ tmp->tm_year--;
+ }
+ while (tmp->tm_mon > 12) {
+ tmp->tm_mon -= 12;
+ tmp->tm_year++;
+ }
+ }
+
+ /*
+ * Note: 256 is _more_ than enough to accommodate the longest
+ * value for _every_ %? lexicon that strftime() understands
+ */
+ need = strlen(argv[1]) + 256;
+ if ((buf = (char *)malloc(need)) == NULL) {
+ fprintf(stderr, "%s: malloc failed\n", pmProgname);
+ exit(1);
+ }
+
+ if (strftime(buf, need, argv[1], tmp) == 0) {
+ fprintf(stderr, "%s: format too long\n", pmProgname);
+ exit(1);
+ }
+ else {
+ buf[need-1] = '\0';
+ printf("%s\n", buf);
+ exit(0);
+ }
+
+}
diff --git a/src/pmdbg/GNUmakefile b/src/pmdbg/GNUmakefile
new file mode 100644
index 0000000..9330ae9
--- /dev/null
+++ b/src/pmdbg/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmdbg.c
+CMDTARGET = pmdbg$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdbg/pmdbg.c b/src/pmdbg/pmdbg.c
new file mode 100644
index 0000000..4879eaa
--- /dev/null
+++ b/src/pmdbg/pmdbg.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995,2002-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * pmdbg - help for PCP debug flags
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static struct {
+ int flag;
+ char *name;
+ char *text;
+} foo[] = {
+ { DBG_TRACE_PDU, "PDU",
+ "Trace PDU traffic at the Xmit and Recv level" },
+ { DBG_TRACE_FETCH, "FETCH",
+ "Dump results from pmFetch" },
+ { DBG_TRACE_PROFILE, "PROFILE",
+ "Trace changes and xmits for instance profile" },
+ { DBG_TRACE_VALUE, "VALUE",
+ "Diags for metric value extraction and conversion" },
+ { DBG_TRACE_CONTEXT, "CONTEXT",
+ "Trace changes to contexts" },
+ { DBG_TRACE_INDOM, "INDOM",
+ "Low-level instance profile xfers" },
+ { DBG_TRACE_PDUBUF, "PDUBUF",
+ "Trace pin/unpin ops for PDU buffers" },
+ { DBG_TRACE_LOG, "LOG",
+ "Diags for archive log manipulations" },
+ { DBG_TRACE_LOGMETA, "LOGMETA",
+ "Diags for meta-data operations on archive logs" },
+ { DBG_TRACE_OPTFETCH, "OPTFETCH",
+ "Trace optFetch magic" },
+ { DBG_TRACE_AF, "AF",
+ "Trace asynchronous event scheduling" },
+ { DBG_TRACE_APPL0, "APPL0",
+ "Application-specific flag 0" },
+ { DBG_TRACE_APPL1, "APPL1",
+ "Application-specific flag 1" },
+ { DBG_TRACE_APPL2, "APPL2",
+ "Application-specific flag 2" },
+ { DBG_TRACE_PMNS, "PMNS",
+ "Diags for PMNS manipulations" },
+ { DBG_TRACE_LIBPMDA, "LIBPMDA",
+ "Trace PMDA callbacks in libpcp_pmda" },
+ { DBG_TRACE_TIMECONTROL, "TIMECONTROL",
+ "Trace Time Control API" },
+ { DBG_TRACE_PMC, "PMC",
+ "Trace metrics class operations" },
+ { DBG_TRACE_DERIVE, "DERIVE",
+ "Derived metrics operations" },
+ { DBG_TRACE_LOCK, "LOCK",
+ "Trace locks (if multi-threading enabled)" },
+ { DBG_TRACE_INTERP, "INTERP",
+ "Diags for value interpolation in archives" },
+ { DBG_TRACE_CONFIG, "CONFIG",
+ "Trace config initialization from pmGetConfig" },
+ { DBG_TRACE_LOOP, "LOOP",
+ "Diags for pmLoop* services" },
+ { DBG_TRACE_FAULT, "FAULT",
+ "Trace fault injection (if enabled)" },
+ { DBG_TRACE_AUTH, "AUTH",
+ "Authentication services (if enabled)" },
+ { DBG_TRACE_DISCOVERY, "DISCOVERY",
+ "Service discovery (if enabled)" },
+ { DBG_TRACE_DESPERATE, "DESPERATE",
+ "Desperate/verbose level" },
+};
+
+static int nfoo = sizeof(foo) / sizeof(foo[0]);
+
+static char *fmt = "DBG_TRACE_%-11.11s %7d %s\n";
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ { "list", 0, 'l', 0, "displays mnemonic and decimal values of PCP debug bitfields" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "l?",
+ .long_options = longopts,
+ .short_usage = "[options] [code ..]",
+};
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ int c;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'l': /* list all flags */
+ printf("Performance Co-Pilot Debug Flags\n");
+ printf("#define Value Meaning\n");
+ for (i = 0; i < nfoo; i++)
+ printf(fmt, foo[i].name, foo[i].flag, foo[i].text);
+ exit(0);
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind >= argc) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ /* non-flag args are argv[opts.optind] ... argv[argc-1] */
+ while (opts.optind < argc) {
+ char *p = argv[opts.optind];
+ for (p = argv[opts.optind]; *p && isdigit((int)*p); p++)
+ ;
+ if (*p == '\0')
+ sscanf(argv[opts.optind], "%d", &c);
+ else {
+ char *q;
+ p = argv[opts.optind];
+ if (*p == '0' && (p[1] == 'x' || p[1] == 'X'))
+ p = &p[2];
+ for (q = p; isxdigit((int)*q); q++)
+ ;
+ if (*q != '\0' || sscanf(p, "%x", &c) != 1) {
+ printf("Cannot decode \"%s\" - neither decimal nor hexadecimal\n", argv[opts.optind]);
+ goto next;
+ }
+ }
+ printf("Performance Co-Pilot -- pmDebug value = %d (0x%x)\n", c, c);
+ printf("#define Value Meaning\n");
+ for (i = 0; i < nfoo; i++) {
+ if (c & foo[i].flag)
+ printf(fmt, foo[i].name, foo[i].flag, foo[i].text);
+ }
+
+next:
+ opts.optind++;
+ }
+
+ return 0;
+}
diff --git a/src/pmdumplog/GNUmakefile b/src/pmdumplog/GNUmakefile
new file mode 100644
index 0000000..db1e1c0
--- /dev/null
+++ b/src/pmdumplog/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmdumplog.c
+CMDTARGET = pmdumplog$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdumplog/pmdumplog.c b/src/pmdumplog/pmdumplog.c
new file mode 100644
index 0000000..eac8c1e
--- /dev/null
+++ b/src/pmdumplog/pmdumplog.c
@@ -0,0 +1,901 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+static struct timeval tv;
+static char timebuf[32]; /* for pmCtime result + .xxx */
+static int numpmid;
+static pmID *pmid;
+static pmID pmid_flags;
+static pmID pmid_missed;
+static int sflag;
+static int xflag; /* for -x (long timestamps) */
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "all", 0, 'a', 0, "dump everything" },
+ { "descs", 0, 'd', 0, "dump metric descriptions" },
+ { "insts", 0, 'i', 0, "dump instance domain descriptions" },
+ { "", 0, 'L', 0, "more verbose form of archive label dump" },
+ { "label", 0, 'l', 0, "dump the archive label" },
+ { "metrics", 0, 'm', 0, "dump values of the metrics (default)" },
+ PMOPT_NAMESPACE,
+ { "reverse", 0, 'r', 0, "process archive in reverse chronological order" },
+ PMOPT_START,
+ { "sizes", 0, 's', 0, "report size of data records in archive" },
+ PMOPT_FINISH,
+ { "", 0, 't', 0, "dump the temporal index" },
+ { "", 1, 'v', "FILE", "verbose hex dump of a physical file in raw format" },
+ { "", 0, 'x', 0, "include date in reported timestamps" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static int overrides(int, pmOptions *);
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_DONE | PM_OPTFLAG_STDOUT_TZ | PM_OPTFLAG_BOUNDARIES,
+ .short_options = "aD:dilLmn:rS:sT:tv:xZ:z?",
+ .long_options = longopts,
+ .short_usage = "[options] [archive [metricname ...]]",
+ .override = overrides,
+};
+
+/*
+ * return -1, 0 or 1 as the struct timeval's compare
+ * a < b, a == b or a > b
+ */
+static int
+tvcmp(struct timeval a, struct timeval b)
+{
+ if (a.tv_sec < b.tv_sec)
+ return -1;
+ if (a.tv_sec > b.tv_sec)
+ return 1;
+ if (a.tv_usec < b.tv_usec)
+ return -1;
+ if (a.tv_usec > b.tv_usec)
+ return 1;
+ return 0;
+}
+
+static int
+do_size(pmResult *rp)
+{
+ int nbyte = 0;
+ int i;
+ int j;
+ /*
+ * Externally the log record looks like this ...
+ * :----------:-----------:..........:---------:
+ * | int len | timestamp | pmResult | int len |
+ * :----------:-----------:..........:---------:
+ *
+ * start with sizes of the header len, timestamp, numpmid, and
+ * trailer len
+ */
+ nbyte = sizeof(int) + sizeof(__pmTimeval) + sizeof(int);
+ /* len + timestamp + len */
+ nbyte += sizeof(int);
+ /* numpmid */
+ for (i = 0; i < rp->numpmid; i++) {
+ pmValueSet *vsp = rp->vset[i];
+ nbyte += sizeof(pmID) + sizeof(int); /* + pmid[i], numval */
+ if (vsp->numval > 0) {
+ nbyte += sizeof(int); /* + valfmt */
+ for (j = 0; j < vsp->numval; j++) {
+ nbyte += sizeof(__pmValue_PDU); /* + pmValue[j] */
+ if (vsp->valfmt != PM_VAL_INSITU)
+ /* + pmValueBlock */
+ /* rounded up */
+ nbyte += PM_PDU_SIZE_BYTES(vsp->vlist[j].value.pval->vlen);
+ }
+ }
+ }
+
+ return nbyte;
+}
+
+static void
+setup_event_derived_metrics(void)
+{
+ int sts;
+
+ if (pmid_flags == 0) {
+ /*
+ * get PMID for event.flags and event.missed
+ * note that pmUnpackEventRecords() will have called
+ * __pmRegisterAnon(), so the anon metrics
+ * should now be in the PMNS
+ */
+ char *name_flags = "event.flags";
+ char *name_missed = "event.missed";
+
+ if ((sts = pmLookupName(1, &name_flags, &pmid_flags)) < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_flags, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_flags)->item = 1;
+ }
+ sts = pmLookupName(1, &name_missed, &pmid_missed);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_missed, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_missed)->item = 1;
+ }
+ }
+}
+
+static int
+dump_nrecords(int nrecords, int nmissed)
+{
+ printf("%d", nrecords);
+ if (nmissed > 0)
+ printf(" (and %d missed)", nmissed);
+ if (nrecords + nmissed == 1)
+ printf(" event record\n");
+ else
+ printf(" event records\n");
+ return 0;
+}
+
+static int
+dump_nparams(int numpmid)
+{
+ if (numpmid == 0) {
+ printf(" ---\n");
+ printf(" No parameters\n");
+ return -1;
+ }
+ if (numpmid < 0) {
+ printf(" ---\n");
+ printf(" Error: illegal number of parameters (%d)\n",
+ numpmid);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+dump_parameter(pmValueSet *xvsp, int index, int *flagsp)
+{
+ int sts, flags = *flagsp;
+ pmDesc desc;
+ char *name;
+
+ if (pmNameID(xvsp->pmid, &name) >= 0) {
+ if (index == 0) {
+ if (xvsp->pmid == pmid_flags) {
+ flags = *flagsp = xvsp->vlist[0].value.lval;
+ printf(" flags 0x%x", flags);
+ printf(" (%s) ---\n", pmEventFlagsStr(flags));
+ free(name);
+ return;
+ }
+ printf(" ---\n");
+ }
+ if ((flags & PM_EVENT_FLAG_MISSED) && index == 1 &&
+ (xvsp->pmid == pmid_missed)) {
+ printf(" ==> %d missed event records\n",
+ xvsp->vlist[0].value.lval);
+ free(name);
+ return;
+ }
+ printf(" %s (%s):", pmIDStr(xvsp->pmid), name);
+ free(name);
+ }
+ else
+ printf(" PMID: %s:", pmIDStr(xvsp->pmid));
+ if ((sts = pmLookupDesc(xvsp->pmid, &desc)) < 0) {
+ printf(" pmLookupDesc: %s\n", pmErrStr(sts));
+ return;
+ }
+ printf(" value ");
+ pmPrintValue(stdout, xvsp->valfmt, desc.type, &xvsp->vlist[0], 1);
+ putchar('\n');
+}
+
+static void
+dump_event(const char *name, pmValueSet *vsp, int index, int indom, int type)
+{
+ int r; /* event records */
+ int p; /* event parameters */
+ int flags;
+ int nrecords;
+ int nmissed = 0;
+ int highres = (type == PM_TYPE_HIGHRES_EVENT);
+ char *iname;
+ pmValue *vp = &vsp->vlist[index];
+
+ if (index > 0)
+ printf(" ");
+ printf(" %s (%s", pmIDStr(vsp->pmid), name);
+ if (indom != PM_INDOM_NULL) {
+ putchar('[');
+ if (pmNameInDom(indom, vp->inst, &iname) < 0)
+ printf("%d or ???])", vp->inst);
+ else {
+ printf("%d or \"%s\"])", vp->inst, iname);
+ free(iname);
+ }
+ }
+ else {
+ printf(")");
+ }
+ printf(": ");
+
+ if (highres) {
+ pmHighResResult **hr;
+
+ if ((nrecords = pmUnpackHighResEventRecords(vsp, index, &hr)) < 0)
+ return;
+ if (nrecords == 0) {
+ printf("No event records\n");
+ pmFreeHighResEventResult(hr);
+ return;
+ }
+ setup_event_derived_metrics();
+
+ for (r = 0; r < nrecords; r++) {
+ if (hr[r]->numpmid == 2 && hr[r]->vset[0]->pmid == pmid_flags &&
+ (hr[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
+ hr[r]->vset[1]->pmid == pmid_missed) {
+ nmissed += hr[r]->vset[1]->vlist[0].value.lval;
+ }
+ }
+ dump_nrecords(nrecords, nmissed);
+
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintHighResStamp(stdout, &hr[r]->timestamp);
+ if (dump_nparams(hr[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < hr[r]->numpmid; p++)
+ dump_parameter(hr[r]->vset[p], p, &flags);
+ }
+ pmFreeHighResEventResult(hr);
+ }
+ else {
+ pmResult **res;
+
+ if ((nrecords = pmUnpackEventRecords(vsp, index, &res)) < 0)
+ return;
+ if (nrecords == 0) {
+ printf("No event records\n");
+ pmFreeEventResult(res);
+ return;
+ }
+ setup_event_derived_metrics();
+
+ for (r = 0; r < nrecords; r++) {
+ if (res[r]->numpmid == 2 && res[r]->vset[0]->pmid == pmid_flags &&
+ (res[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
+ res[r]->vset[1]->pmid == pmid_missed) {
+ nmissed += res[r]->vset[1]->vlist[0].value.lval;
+ }
+ }
+ dump_nrecords(nrecords, nmissed);
+
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintStamp(stdout, &res[r]->timestamp);
+ if (dump_nparams(res[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < res[r]->numpmid; p++)
+ dump_parameter(res[r]->vset[p], p, &flags);
+ }
+ pmFreeEventResult(res);
+ }
+}
+
+static void
+dump_metric(const char *mname, pmValueSet *vsp, int index, int indom, int type)
+{
+ pmValue *vp = &vsp->vlist[index];
+ char *iname;
+
+ if (index == 0) {
+ printf(" %s (%s):", pmIDStr(vsp->pmid), mname);
+ if (vsp->numval > 1) {
+ putchar('\n');
+ printf(" ");
+ }
+ }
+ else
+ printf(" ");
+
+ if (indom != PM_INDOM_NULL) {
+ printf(" inst [");
+ if (pmNameInDom(indom, vp->inst, &iname) < 0)
+ printf("%d or ???]", vp->inst);
+ else {
+ printf("%d or \"%s\"]", vp->inst, iname);
+ free(iname);
+ }
+ }
+ printf(" value ");
+ pmPrintValue(stdout, vsp->valfmt, type, vp, 1);
+ putchar('\n');
+}
+
+static void
+dump_result(pmResult *resp)
+{
+ int i;
+ int j;
+ int n;
+ char *mname = NULL;
+ pmDesc desc;
+
+ if (sflag) {
+ int nbyte;
+ nbyte = do_size(resp);
+ printf("[%d bytes]\n", nbyte);
+ }
+
+ if (xflag) {
+ char *ddmm;
+ char *yr;
+
+ ddmm = pmCtime(&resp->timestamp.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf("%s ", ddmm);
+ __pmPrintStamp(stdout, &resp->timestamp);
+ printf(" %4.4s", yr);
+ }
+ else
+ __pmPrintStamp(stdout, &resp->timestamp);
+
+ if (resp->numpmid == 0) {
+ printf(" <mark>\n");
+ return;
+ }
+
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+
+ if (i > 0)
+ printf(" ");
+ if ((n = pmNameID(vsp->pmid, &mname)) < 0)
+ mname = strdup("<noname>");
+ if (vsp->numval == 0) {
+ printf(" %s (%s): No values returned!\n", pmIDStr(vsp->pmid), mname);
+ goto next;
+ }
+ else if (vsp->numval < 0) {
+ printf(" %s (%s): %s\n", pmIDStr(vsp->pmid), mname, pmErrStr(vsp->numval));
+ goto next;
+ }
+
+ if (pmLookupDesc(vsp->pmid, &desc) < 0) {
+ /* don't know, so punt on the most common cases */
+ desc.indom = PM_INDOM_NULL;
+ if (vsp->valfmt == PM_VAL_INSITU)
+ desc.type = PM_TYPE_32;
+ else
+ desc.type = PM_TYPE_AGGREGATE;
+ }
+
+ for (j = 0; j < vsp->numval; j++) {
+ if (desc.type == PM_TYPE_EVENT ||
+ desc.type == PM_TYPE_HIGHRES_EVENT)
+ dump_event(mname, vsp, j, desc.indom, desc.type);
+ else
+ dump_metric(mname, vsp, j, desc.indom, desc.type);
+ }
+next:
+ if (mname)
+ free(mname);
+ mname = NULL;
+ }
+}
+
+static void
+dumpDesc(__pmContext *ctxp)
+{
+ int i;
+ int sts;
+ char *p;
+ __pmHashNode *hp;
+ pmDesc *dp;
+
+ printf("\nDescriptions for Metrics in the Log ...\n");
+ for (i = 0; i < ctxp->c_archctl->ac_log->l_hashpmid.hsize; i++) {
+ for (hp = ctxp->c_archctl->ac_log->l_hashpmid.hash[i]; hp != NULL; hp = hp->next) {
+ dp = (pmDesc *)hp->data;
+ sts = pmNameID(dp->pmid, &p);
+ if (sts < 0)
+ printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), "<noname>");
+ else {
+ printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), p);
+ free(p);
+ }
+ __pmPrintDesc(stdout, dp);
+ }
+ }
+}
+
+static void
+dumpInDom(__pmContext *ctxp)
+{
+ int i;
+ int j;
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+ __pmLogInDom *ldp;
+
+ printf("\nInstance Domains in the Log ...\n");
+ for (i = 0; i < ctxp->c_archctl->ac_log->l_hashindom.hsize; i++) {
+ for (hp = ctxp->c_archctl->ac_log->l_hashindom.hash[i]; hp != NULL; hp = hp->next) {
+ printf("InDom: %s\n", pmInDomStr((pmInDom)hp->key));
+ /*
+ * in reverse chronological order, so iteration is a bit funny
+ */
+ ldp = NULL;
+ for ( ; ; ) {
+ for (idp = (__pmLogInDom *)hp->data; idp->next != ldp; idp =idp->next)
+ ;
+ tv.tv_sec = idp->stamp.tv_sec;
+ tv.tv_usec = idp->stamp.tv_usec;
+ __pmPrintStamp(stdout, &tv);
+ printf(" %d instances\n", idp->numinst);
+ for (j = 0; j < idp->numinst; j++) {
+ printf(" %d or \"%s\"\n",
+ idp->instlist[j], idp->namelist[j]);
+ }
+ if (idp == (__pmLogInDom *)hp->data)
+ break;
+ ldp = idp;
+ }
+ }
+ }
+}
+
+static void
+dumpTI(__pmContext *ctxp)
+{
+ int i;
+ char path[MAXPATHLEN];
+ off_t meta_size = -1; /* initialize to pander to gcc */
+ off_t log_size = -1; /* initialize to pander to gcc */
+ struct stat sbuf;
+ __pmLogTI *tip;
+ __pmLogTI *lastp;
+
+ printf("\nTemporal Index\n");
+ printf(" Log Vol end(meta) end(log)\n");
+ lastp = NULL;
+ for (i = 0; i < ctxp->c_archctl->ac_log->l_numti; i++) {
+ tip = &ctxp->c_archctl->ac_log->l_ti[i];
+ tv.tv_sec = tip->ti_stamp.tv_sec;
+ tv.tv_usec = tip->ti_stamp.tv_usec;
+ __pmPrintStamp(stdout, &tv);
+ printf(" %4d %11d %11d\n", tip->ti_vol, tip->ti_meta, tip->ti_log);
+ if (i == 0) {
+ sprintf(path, "%s.meta", opts.archives[0]);
+ if (stat(path, &sbuf) == 0)
+ meta_size = sbuf.st_size;
+ else
+ meta_size = -1;
+ }
+ if (lastp == NULL || tip->ti_vol != lastp->ti_vol) {
+ sprintf(path, "%s.%d", opts.archives[0], tip->ti_vol);
+ if (stat(path, &sbuf) == 0)
+ log_size = sbuf.st_size;
+ else {
+ log_size = -1;
+ printf(" Warning: file missing or compressed for log volume %d\n", tip->ti_vol);
+ }
+ }
+ /*
+ * Integrity Errors
+ *
+ * this(tv_sec) < 0
+ * this(tv_usec) < 0 || this(tv_usec) > 999999
+ * this(timestamp) < last(timestamp)
+ * this(vol) < last(vol)
+ * this(vol) == last(vol) && this(meta) <= last(meta)
+ * this(vol) == last(vol) && this(log) <= last(log)
+ * file_exists(<base>.meta) && this(meta) > file_size(<base>.meta)
+ * file_exists(<base>.this(vol)) &&
+ * this(log) > file_size(<base>.this(vol))
+ *
+ * Integrity Warnings
+ *
+ * this(vol) != last(vol) && !file_exists(<base>.this(vol))
+ */
+ if (tip->ti_stamp.tv_sec < 0 ||
+ tip->ti_stamp.tv_usec < 0 || tip->ti_stamp.tv_usec > 999999)
+ printf(" Error: illegal timestamp value (%d sec, %d usec)\n",
+ tip->ti_stamp.tv_sec, tip->ti_stamp.tv_usec);
+ if (meta_size != -1 && tip->ti_meta > meta_size)
+ printf(" Error: offset to meta file past end of file (%ld)\n",
+ (long)meta_size);
+ if (log_size != -1 && tip->ti_log > log_size)
+ printf(" Error: offset to log file past end of file (%ld)\n",
+ (long)log_size);
+ if (i > 0) {
+ if (tip->ti_stamp.tv_sec < lastp->ti_stamp.tv_sec ||
+ (tip->ti_stamp.tv_sec == lastp->ti_stamp.tv_sec &&
+ tip->ti_stamp.tv_usec < lastp->ti_stamp.tv_usec))
+ printf(" Error: timestamp went backwards in time %d.%06d -> %d.%06d\n",
+ (int)lastp->ti_stamp.tv_sec, (int)lastp->ti_stamp.tv_usec,
+ (int)tip->ti_stamp.tv_sec, (int)tip->ti_stamp.tv_usec);
+ if (tip->ti_vol < lastp->ti_vol)
+ printf(" Error: volume number decreased\n");
+ if (tip->ti_vol == lastp->ti_vol && tip->ti_meta < lastp->ti_meta)
+ printf(" Error: offset to meta file decreased\n");
+ if (tip->ti_vol == lastp->ti_vol && tip->ti_log < lastp->ti_log)
+ printf(" Error: offset to log file decreased\n");
+ }
+ lastp = tip;
+ }
+}
+
+static void
+dumpLabel(int verbose)
+{
+ pmLogLabel label;
+ char *ddmm;
+ char *yr;
+ int sts;
+
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: Cannot get archive label record: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ printf("Log Label (Log Format Version %d)\n", label.ll_magic & 0xff);
+ printf("Performance metrics from host %s\n", label.ll_hostname);
+
+ ddmm = pmCtime(&label.ll_start.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" commencing %s ", ddmm);
+ __pmPrintStamp(stdout, &label.ll_start);
+ printf(" %4.4s\n", yr);
+
+ if (opts.finish.tv_sec == INT_MAX) {
+ /* pmGetArchiveEnd() failed! */
+ printf(" ending UNKNOWN\n");
+ }
+ else {
+ ddmm = pmCtime(&opts.finish.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" ending %s ", ddmm);
+ __pmPrintStamp(stdout, &opts.finish);
+ printf(" %4.4s\n", yr);
+ }
+
+ if (verbose) {
+ printf("Archive timezone: %s\n", label.ll_tz);
+ printf("PID for pmlogger: %" FMT_PID "\n", label.ll_pid);
+ }
+}
+
+static void
+rawdump(FILE *f)
+{
+ long old;
+ int len;
+ int check;
+ int i;
+ int sts;
+
+ old = ftell(f);
+ fseek(f, (long)0, SEEK_SET);
+
+ while ((sts = fread(&len, 1, sizeof(len), f)) == sizeof(len)) {
+ len = ntohl(len);
+ printf("Dump ... record len: %d @ offset: %ld", len, ftell(f) - sizeof(len));
+ len -= 2 * sizeof(len);
+ for (i = 0; i < len; i++) {
+ check = fgetc(f);
+ if (check == EOF) {
+ printf("Unexpected EOF\n");
+ break;
+ }
+ if (i % 32 == 0) putchar('\n');
+ if (i % 4 == 0) putchar(' ');
+ printf("%02x", check & 0xff);
+ }
+ putchar('\n');
+ if ((sts = fread(&check, 1, sizeof(check), f)) != sizeof(check)) {
+ if (sts == 0)
+ printf("Unexpected EOF\n");
+ break;
+ }
+ check = ntohl(check);
+ len += 2 * sizeof(len);
+ if (check != len) {
+ printf("Trailer botch: %d != %d\n", check, len);
+ break;
+ }
+ }
+ if (sts < 0)
+ printf("fread fails: %s\n", osstrerror());
+ fseek(f, old, SEEK_SET);
+}
+
+static void
+dometric(const char *name)
+{
+ int sts;
+
+ if (*name == '\0') {
+ printf("PMNS appears to be empty!\n");
+ return;
+ }
+ numpmid++;
+ pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
+ if ((sts = pmLookupName(1, (char **)&name, &pmid[numpmid-1])) < 0) {
+ fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, name, pmErrStr(sts));
+ numpmid--;
+ }
+}
+
+static int
+overrides(int opt, pmOptions *opts)
+{
+ if (opt == 'a' || opt == 'L' || opt == 's' || opt == 't')
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ char *rawfile = NULL;
+ int i;
+ int ctxid;
+ int first = 1;
+ int dflag = 0;
+ int iflag = 0;
+ int Lflag = 0;
+ int lflag = 0;
+ int mflag = 0;
+ int tflag = 0;
+ int vflag = 0;
+ int mode = PM_MODE_FORW;
+ __pmContext *ctxp;
+ pmResult *result;
+ struct timeval done;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'a': /* dump everything */
+ dflag = iflag = lflag = mflag = sflag = tflag = 1;
+ break;
+
+ case 'd': /* dump pmDesc structures */
+ dflag = 1;
+ break;
+
+ case 'i': /* dump instance domains */
+ iflag = 1;
+ break;
+
+ case 'L': /* dump label, verbose */
+ Lflag = 1;
+ lflag = 1;
+ break;
+
+ case 'l': /* dump label */
+ lflag = 1;
+ break;
+
+ case 'm': /* dump metrics in log */
+ mflag = 1;
+ break;
+
+ case 'r': /* read log in reverse chornological order */
+ mode = PM_MODE_BACK;
+ break;
+
+ case 's': /* report data size in log */
+ sflag = 1;
+ break;
+
+ case 't': /* dump temporal index */
+ tflag = 1;
+ break;
+
+ case 'v': /* verbose, dump in raw format */
+ vflag = 1;
+ rawfile = opts.optarg;
+ break;
+
+ case 'x': /* report Ddd Mmm DD <timestamp> YYYY */
+ xflag = 1;
+ break;
+ }
+ }
+
+ if (opts.errors ||
+ (vflag && opts.optind != argc) ||
+ (!vflag && opts.optind > argc - 1)) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (vflag) {
+ FILE *f;
+ if ((f = fopen(rawfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open \"%s\": %s\n", pmProgname, rawfile, osstrerror());
+ exit(1);
+ }
+ printf("Raw dump of physical archive file \"%s\" ...\n", rawfile);
+ rawdump(f);
+ exit(0);
+ }
+
+ if (dflag + iflag + lflag + mflag + tflag == 0)
+ mflag = 1; /* default */
+
+ /* delay option end processing until now that we have the archive name */
+ __pmAddOptArchive(&opts, argv[opts.optind]);
+ opts.flags &= ~PM_OPTFLAG_DONE;
+ __pmEndOptions(&opts);
+
+ if ((sts = ctxid = pmNewContext(PM_CONTEXT_ARCHIVE, opts.archives[0])) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, argv[opts.optind], pmErrStr(sts));
+ exit(1);
+ }
+ /* complete TZ and time window option (origin) setup */
+ if (pmGetContextOptions(ctxid, &opts)) {
+ pmflush();
+ exit(1);
+ }
+
+ opts.optind++;
+ numpmid = argc - opts.optind;
+ if (numpmid) {
+ numpmid = 0;
+ pmid = NULL;
+ for (i = 0; opts.optind < argc; i++, opts.optind++) {
+ numpmid++;
+ pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
+ if ((sts = pmLookupName(1, &argv[opts.optind], &pmid[numpmid-1])) < 0) {
+ if (sts == PM_ERR_NONLEAF) {
+ numpmid--;
+ if ((sts = pmTraversePMNS(argv[opts.optind], dometric)) < 0)
+ fprintf(stderr, "%s: pmTraversePMNS(%s): %s\n",
+ pmProgname, argv[opts.optind], pmErrStr(sts));
+ }
+ else
+ fprintf(stderr, "%s: pmLookupName(%s): %s\n",
+ pmProgname, argv[opts.optind], pmErrStr(sts));
+ if (sts < 0)
+ numpmid--;
+ }
+ }
+ if (numpmid == 0) {
+ fprintf(stderr, "No metric names can be translated, dump abandoned\n");
+ exit(1);
+ }
+ }
+
+ /*
+ * Note: ctxp->c_lock remains locked throughout ... __pmHandleToPtr()
+ * is only called once, and a single context is used throughout
+ * ... so there is no PM_UNLOCK(ctxp->c_lock) anywhere in the
+ * pmdumplog code.
+ * This works because ctxp->c_lock is a recursive lock and
+ * pmdumplog is single-threaded.
+ */
+ if ((ctxp = __pmHandleToPtr(ctxid)) == NULL) {
+ fprintf(stderr, "%s: botch: __pmHandleToPtr(%d) returns NULL!\n",
+ pmProgname, ctxid);
+ exit(1);
+ }
+
+ pmSetMode(mode, &opts.start, 0);
+
+ if (lflag)
+ dumpLabel(Lflag);
+
+ if (dflag)
+ dumpDesc(ctxp);
+
+ if (iflag)
+ dumpInDom(ctxp);
+
+ if (tflag)
+ dumpTI(ctxp);
+
+ if (mflag) {
+ if (mode == PM_MODE_FORW) {
+ if (opts.start_optarg != NULL || opts.finish_optarg != NULL) {
+ /* -S or -T */
+ sts = pmSetMode(mode, &opts.start, 0);
+ done = opts.finish;
+ }
+ else {
+ /* read the whole archive */
+ done.tv_sec = 0;
+ done.tv_usec = 0;
+ sts = pmSetMode(mode, &done, 0);
+ done.tv_sec = INT_MAX;
+ }
+ }
+ else {
+ if (opts.start_optarg != NULL || opts.finish_optarg != NULL) {
+ /* -S or -T */
+ done.tv_sec = INT_MAX;
+ done.tv_usec = 0;
+ sts = pmSetMode(mode, &done, 0);
+ done = opts.start;
+ }
+ else {
+ /* read the whole archive backwards */
+ done.tv_sec = INT_MAX;
+ done.tv_usec = 0;
+ sts = pmSetMode(mode, &done, 0);
+ done.tv_sec = 0;
+ }
+ }
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmSetMode: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ sts = 0;
+ for ( ; ; ) {
+ if (numpmid == 0)
+ sts = pmFetchArchive(&result);
+ else
+ sts = pmFetch(numpmid, pmid, &result);
+ if (sts < 0)
+ break;
+ if (first && mode == PM_MODE_BACK) {
+ first = 0;
+ printf("\nLog finished at %24.24s - dump in reverse order\n",
+ pmCtime(&result->timestamp.tv_sec, timebuf));
+ }
+ if ((mode == PM_MODE_FORW && tvcmp(result->timestamp, done) > 0) ||
+ (mode == PM_MODE_BACK && tvcmp(result->timestamp, done) < 0)) {
+ sts = PM_ERR_EOL;
+ break;
+ }
+ putchar('\n');
+ dump_result(result);
+ pmFreeResult(result);
+ }
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ exit(0);
+}
diff --git a/src/pmdumptext/GNUmakefile b/src/pmdumptext/GNUmakefile
new file mode 100644
index 0000000..f7b7ddf
--- /dev/null
+++ b/src/pmdumptext/GNUmakefile
@@ -0,0 +1,36 @@
+TOPDIR = ../..
+COMMAND = pmdumptext
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+QRCFILE = $(COMMAND).qrc
+UIFILES = $(shell echo *.ui)
+SOURCES = pmdumptext.cpp
+LSRCFILES = $(PROJECT) $(SOURCES)
+LDIRT = $(COMMAND)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me:
+ $(QTMAKE)
+ $(LNMAKE)
+
+install: default
+ifeq ($(WINDOW),mac)
+ $(call INSTALL_QT_FRAMEWORKS,$(BINARY))
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+ rm $(BINARY)
+else
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+endif
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmdumptext/pmdumptext.cpp b/src/pmdumptext/pmdumptext.cpp
new file mode 100644
index 0000000..8d99bae
--- /dev/null
+++ b/src/pmdumptext/pmdumptext.cpp
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1997,2004-2006 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2007 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <math.h>
+#include <float.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <QTextStream>
+#include <QStringList>
+#include <qmc_group.h>
+#include <qmc_metric.h>
+#include <qmc_context.h>
+
+// Temporary buffer
+static char buffer[256];
+
+// List of metrics
+static QmcGroup *group;
+static QList<QmcMetric*> metrics;
+static bool isLive = false;
+static int numValues;
+static int doMetricType = PM_CONTEXT_HOST;
+static bool doMetricFlag = true;
+static double doMetricScale = 0.0;
+static QString doMetricSource;
+
+// Command line options
+static bool dumpFlag = true;
+static bool metricFlag;
+static bool niceFlag;
+static bool unitFlag;
+static bool sourceFlag;
+static bool timeFlag = true;
+static bool timeOffsetFlag;
+static bool rawFlag;
+static bool shortFlag;
+static bool descFlag;
+static bool widthFlag;
+static bool precFlag;
+static bool normFlag;
+static bool headerFlag;
+static bool fullFlag;
+static bool fullXFlag;
+
+static QString errStr = "?";
+static QString timeFormat;
+static char delimiter = '\t';
+static int precision = 3;
+static int width = 6;
+static int sampleCount;
+static int repeatLines;
+
+static pmLongOptions longopts[] = {
+ PMAPI_GENERAL_OPTIONS,
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "config", 1, 'c', "FILE", "read list of metrics from FILE" },
+ { "check", 0, 'C', 0, "exit before dumping any values" },
+ { "delimiter", 1, 'd', "CHAR", "character separating each column" },
+ { "time-format", 1, 'f', "FMT", "time format string" },
+ { "fixed", 0, 'F', 0, "print fixed width values" },
+ { "scientific", 0, 'G', 0, "print values in scientific format if shorter" },
+ { "headers", 0, 'H', 0, "show all headers" },
+ { "interactive", 0, 'i', 0, "format columns for interactive use" },
+ { "source", 0, 'l', 0, "show source of metrics" },
+ { "metrics", 0, 'm', 0, "show metric names" },
+ { "", 0, 'M', 0, "show complete metrics names" },
+ { "", 0, 'N', 0, "show normalizing factor" },
+ { "offset", 0, 'o', 0, "prefix timestamp with offset in seconds" },
+ { "precision", 1, 'P', "N", "floating point precision [default 3]" },
+ { "repeat", 1, 'R', "N", "repeat the header after every N samples" },
+ { "raw", 0, 'r', 0, "output raw values, no rate conversion" },
+ { "unavailable", 1, 'U', "STR", "unavailable value string [default \"?\"]" },
+ { "units", 0, 'u', 0, "show metric units" },
+ { "width", 1, 'w', "N", "set column width" },
+ { "extended", 0, 'X', 0, "show complete metrics names (extended form)" },
+ PMAPI_OPTIONS_END
+};
+
+// Collection start time
+static struct timeval logStartTime;
+
+// This may be putenv, so make it static
+static QString tzEnv = "TZ=";
+
+static QTextStream cerr(stderr);
+static QTextStream cout(stdout);
+
+static void
+checkUnits(QmcMetric *metric)
+{
+ pmUnits units;
+ const pmDesc &desc = metric->desc().desc();
+
+ // Only scale units if interactive and not raw
+ if (rawFlag || !niceFlag)
+ return;
+
+ // Change to canonical bytes
+ if (desc.units.dimTime == 0 &&
+ desc.units.dimSpace == 1 &&
+ desc.units.dimCount == 0 &&
+ desc.units.scaleSpace != PM_SPACE_BYTE) {
+ units.dimSpace = 1;
+ units.scaleSpace = PM_SPACE_BYTE;
+ units.dimTime = units.dimCount = units.scaleTime =
+ units.scaleCount = 0;
+ metric->setScaleUnits(units);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "checkUnits: Changing " << metric->name()
+ << " to use bytes" << endl;
+ }
+ }
+ // Change to canonical count
+ else if (desc.units.dimTime == 0 &&
+ desc.units.dimSpace == 0 &&
+ desc.units.dimCount == 1 &&
+ desc.units.scaleCount != PM_COUNT_ONE) {
+ units.dimCount = 1;
+ units.scaleCount = PM_COUNT_ONE;
+ units.dimTime = units.dimSpace = units.scaleTime =
+ units.scaleSpace = 0;
+ metric->setScaleUnits(units);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "checkUnits: Changing " << metric->name()
+ << " to use counts" << endl;
+ }
+ }
+ else if (metric->desc().desc().sem == PM_SEM_COUNTER) {
+
+ // Do time utilisation?
+ if (desc.units.dimTime == 1 &&
+ desc.units.dimSpace == 0 &&
+ desc.units.dimCount == 0) {
+ units.dimTime = 1;
+ units.scaleTime = PM_TIME_SEC;
+ units.dimSpace = units.dimCount = units.scaleSpace =
+ units.scaleCount = 0;
+ metric->setScaleUnits(units);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "checkUnits: Changing " << metric->name()
+ << " to use time utilization" << endl;
+ }
+ }
+ }
+}
+
+static void
+dometric(const char *name)
+{
+ QString fullname = doMetricSource;
+
+ if (fullname.length()) {
+ if (doMetricType == PM_CONTEXT_ARCHIVE)
+ fullname.append(QChar('/'));
+ else
+ fullname.append(QChar(':'));
+ }
+ fullname.append(name);
+
+ QmcMetric* metric = group->addMetric((const char *)fullname.toAscii(),
+ doMetricScale);
+ if (metric->status() >= 0) {
+ checkUnits(metric);
+ metrics.append(metric);
+ numValues += metric->numValues();
+ }
+ else
+ doMetricFlag = false;
+}
+
+static int
+traverse(const char *str, double scale)
+{
+ pmMetricSpec *theMetric;
+ char *msg;
+ int sts = 0;
+
+ sts = pmParseMetricSpec((char *)str, 0, (char *)0, &theMetric, &msg);
+ if (sts < 0) {
+ pmprintf("%s: Error: Unable to parse metric spec:\n%s\n",
+ pmProgname, msg);
+ free(msg);
+ return sts;
+ }
+
+ // If the metric has instances, then it cannot be traversed
+ if (theMetric->ninst) {
+ QmcMetric *metric = group->addMetric(theMetric, scale);
+ if (metric->status() >= 0) {
+ checkUnits(metric);
+ metrics.append(metric);
+ numValues += metric->numValues();
+ }
+ else
+ sts = -1;
+ }
+ else {
+ if (theMetric->isarch == 0)
+ doMetricType = PM_CONTEXT_HOST;
+ else if (theMetric->isarch == 1)
+ doMetricType = PM_CONTEXT_ARCHIVE;
+ else if (theMetric->isarch == 2)
+ doMetricType = PM_CONTEXT_LOCAL;
+ else {
+ pmprintf("%s: Error: invalid metric source (%d): %s\n",
+ pmProgname, theMetric->isarch, theMetric->metric);
+ sts = -1;
+ }
+ doMetricSource = theMetric->source;
+ if (sts >= 0)
+ sts = group->use(doMetricType, doMetricSource);
+ if (sts >= 0) {
+ doMetricScale = scale;
+ sts = pmTraversePMNS(theMetric->metric, dometric);
+ if (sts >= 0 && doMetricFlag == false)
+ sts = -1;
+ else if (sts < 0) {
+ pmprintf("%s: Error: %s: %s\n",
+ pmProgname, theMetric->metric, pmErrStr(sts));
+ }
+ }
+ }
+
+ free(theMetric);
+
+ return sts;
+}
+
+//
+// parseConfig: parse list of metrics with optional scaling factor
+//
+static int
+parseConfig(QString const& configName, FILE *configFile)
+{
+ char buf[1024];
+ char *last;
+ char *msg;
+ double scale = 0.0;
+ int line = 0;
+ int len = 0;
+ int err = 0;
+
+ while (!feof(configFile)) {
+ if (fgets(buf, sizeof(buf), configFile) == NULL)
+ break;
+ len = strlen(buf);
+ if (len == 0 || buf[0] == '#' || buf[0] == '\n') {
+ line++;
+ continue;
+ }
+ last = &buf[len-1];
+ if (*last != '\n' && !feof(configFile)) {
+ pmprintf("%s: Line %d of %s was too long, skipping.\n",
+ pmProgname, line, (const char *)configName.toAscii());
+ while(buf[len-1] != '\n') {
+ if (fgets(buf, sizeof(buf), configFile) == NULL)
+ break;
+ len = strlen(buf);
+ }
+ err++;
+ continue;
+ }
+ if (*last == '\n')
+ *last = '\0';
+ line++;
+
+ last = strrchr(buf, ']');
+ if (last == NULL) { // No instances
+ for (last = buf; *last != '\0' && isspace(*last); last++) { ; }
+ if (*last == '\0')
+ continue;
+ for (; *last != '\0' && !isspace(*last); last++) { ; }
+ last--;
+ }
+ if (*(last + 1) == '\0') {
+ scale = 0.0;
+ }
+ else {
+ *(last+1)='\0';
+ scale = strtod(last+2, &msg);
+
+ if (*msg != '\0') {
+ pmprintf("%s: Line %d of %s has an illegal scaling factor, assuming 0.\n",
+ pmProgname, line, (const char *)configName.toAscii());
+ err++;
+ scale = 0.0;
+ }
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "parseConfig: Adding metric '" << buf << "' with scale = "
+ << scale << " from line " << line << endl;
+
+ if (traverse(buf, scale) < 0)
+ err++;
+ }
+
+ if (configFile != stdin)
+ fclose(configFile);
+
+ return err;
+}
+
+static const char *
+dumpTime(struct timeval const &curPos)
+{
+ time_t curTime = (time_t)(curPos.tv_sec);
+ char *p;
+
+ if (timeOffsetFlag) {
+ double o = __pmtimevalSub(&curPos, &logStartTime);
+ if (o < 10)
+ sprintf(buffer, "%.2f ", o);
+ else if (o < 100)
+ sprintf(buffer, "%.1f ", o);
+ else
+ sprintf(buffer, "%.0f ", o);
+ for (p = buffer; *p != ' '; p++)
+ ;
+ *p++ = delimiter;
+ }
+ else
+ p = buffer;
+
+ if (timeFormat.length() > 0)
+ strftime(p, sizeof(buffer) - (p-buffer),
+ (const char *)(timeFormat.toAscii()), localtime(&curTime));
+ else {
+ // Use ctime as we have put the timezone into the environment
+ strcpy(p, ctime(&curTime));
+ p[19] = '\0';
+ }
+ return buffer;
+}
+
+static void
+dumpHeader()
+{
+ static bool fullOnce = false;
+
+ QmcMetric *metric;
+ bool instFlag = false;
+ QString noneStr = "none";
+ QString srcStr = "Source";
+ QString metricStr = "Metric";
+ QString instStr = "Inst";
+ QString normStr = "Normal";
+ QString unitStr = "Units";
+ QString columnStr = "Column";
+ const char *timeStr;
+ int m;
+ int i;
+ int c;
+ int v;
+ int p;
+ int len = 0;
+
+ if (niceFlag) {
+ struct timeval pos = { 0, 0 };
+ timeStr = dumpTime(pos);
+ len = strlen(timeStr);
+ }
+
+ if (fullFlag) {
+ fullOnce = true;
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ for (i = 0; i < metric->numValues(); i++, v++) {
+ cout << '[' << qSetFieldWidth(2) << v
+ << qSetFieldWidth(0) << "] "
+ << metric->spec(sourceFlag, true, i) << endl;
+ }
+ }
+ cout << endl;
+ }
+
+ if (fullOnce) {
+ if (timeFlag) {
+ if (len < columnStr.length()) {
+ columnStr.remove(len, columnStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << columnStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ for (i = 0; i < metric->numValues(); i++) {
+ cout << qSetFieldWidth(width) << v << qSetFieldWidth(0);
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+
+ if (niceFlag && sourceFlag) {
+ if (timeFlag) {
+ if (len < srcStr.length()) {
+ srcStr.remove(len, srcStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << srcStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ QString const& str = metric->context()->source().host();
+ strncpy(buffer, (const char *)str.toAscii(), width);
+ buffer[width] = '\0';
+ for (i = 0; i < metric->numValues(); i++) {
+ cout << qSetFieldWidth(width) << buffer << qSetFieldWidth(0);
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+
+ if (metricFlag || (sourceFlag && !niceFlag)) {
+ if (timeFlag) {
+ if (niceFlag) {
+ if (len < metricStr.length()) {
+ metricStr.remove(len, metricStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << metricStr << qSetFieldWidth(0);
+ cout << delimiter;
+ }
+ else
+ cout << "Time" << delimiter;
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ if (niceFlag && !instFlag && metric->hasInstances())
+ instFlag = true;
+ for (i = 0; i < metric->numValues(); i++) {
+ if (niceFlag) {
+ QString const &str = metric->spec(false, false, i);
+ p = str.length() - width;
+ if (p > 0) {
+ for (c = (p - 1 > 0 ? p - 1 : 0); c < str.length();
+ c++) {
+ if (str[c] == '.') {
+ c++;
+ break;
+ }
+ }
+ if (c < str.length())
+ cout << qSetFieldWidth(width)
+ << ((const char *)str.toAscii() + c)
+ << qSetFieldWidth(0);
+ else
+ cout << qSetFieldWidth(width)
+ << ((const char *)str.toAscii() + p)
+ << qSetFieldWidth(0);
+ }
+ else {
+ cout << qSetFieldWidth(width) << str
+ << qSetFieldWidth(0);
+ }
+ }
+ else
+ cout << metric->spec(sourceFlag, true, i);
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+
+ if (instFlag) {
+ if (timeFlag) {
+ if (niceFlag) {
+ if (len < instStr.length()) {
+ instStr.remove(len, instStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << instStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+ else {
+ cout << qSetFieldWidth(len) << errStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ for (i = 0; i < metric->numValues(); i++) {
+ if (metric->hasInstances()) {
+ QString const &str = metric->instName(i);
+ strncpy(buffer, (const char *)str.toAscii(), width);
+ buffer[width] = '\0';
+ cout << qSetFieldWidth(width) << buffer
+ << qSetFieldWidth(0);
+ }
+ else
+ cout << qSetFieldWidth(width) << "n/a"
+ << qSetFieldWidth(0);
+
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+
+ if (normFlag) {
+ if (timeFlag) {
+ if (niceFlag) {
+ if (len < normStr.length()) {
+ normStr.remove(len, normStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << normStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+ else
+ cout << errStr << delimiter;
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ for (i = 0; i < metric->numValues(); i++) {
+ if (shortFlag)
+ cout << qSetRealNumberPrecision(precision)
+ << qSetFieldWidth(width)
+ << metric->scale()
+ << qSetFieldWidth(0);
+ else if (descFlag)
+ cout << qSetFieldWidth(width)
+ << QmcMetric::formatNumber(metric->scale())
+ << qSetFieldWidth(0);
+ else
+ cout << fixed
+ << qSetRealNumberPrecision(precision)
+ << qSetFieldWidth(width)
+ << metric->scale()
+ << qSetFieldWidth(0);
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+
+ if (unitFlag) {
+ if (timeFlag) {
+ if (niceFlag) {
+ if (len < unitStr.length()) {
+ unitStr.remove(len, unitStr.length() - len);
+ }
+ cout << qSetFieldWidth(len) << unitStr
+ << qSetFieldWidth(0) << delimiter;
+ }
+ else
+ cout << noneStr << delimiter;
+ }
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+ QString const &str = (niceFlag ? metric->desc().shortUnits()
+ : metric->desc().units());
+ for (i = 0; i < metric->numValues(); i++) {
+ if (niceFlag)
+ if (str.length() > width)
+ cout << qSetFieldWidth(width)
+ << ((const char *)str.toAscii() + str.length() - width)
+ << qSetFieldWidth(0);
+ else
+ cout << qSetFieldWidth(width) << str
+ << qSetFieldWidth(0);
+ else
+ cout << str;
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+ }
+}
+
+/*
+ * Get Extended Time Base interval and Units from a timeval
+ */
+#define SECS_IN_24_DAYS 2073600.0
+
+static int
+getXTBintervalFromTimeval(int *mode, struct timeval *tval)
+{
+ double tmp_ival = tval->tv_sec + tval->tv_usec / 1000000.0;
+
+ if (tmp_ival > SECS_IN_24_DAYS) {
+ *mode = (*mode & 0x0000ffff) | PM_XTB_SET(PM_TIME_SEC);
+ return ((int)tmp_ival);
+ }
+ else {
+ *mode = (*mode & 0x0000ffff) | PM_XTB_SET(PM_TIME_MSEC);
+ return ((int)(tmp_ival * 1000.0));
+ }
+}
+
+static struct timeval
+tadd(struct timeval t1, struct timeval t2)
+{
+ t1.tv_sec += t2.tv_sec;
+ t1.tv_usec += t2.tv_usec;
+ if (t1.tv_usec > 1000000) {
+ (t1.tv_sec)++;
+ t1.tv_usec -= 1000000;
+ }
+ return t1;
+}
+
+static struct timeval
+tsub(struct timeval t1, struct timeval t2)
+{
+ t1.tv_usec -= t2.tv_usec;
+ if (t1.tv_usec < 0) {
+ t1.tv_usec += 1000000;
+ t1.tv_sec--;
+ }
+ t1.tv_sec -= t2.tv_sec;
+ return t1;
+}
+
+static struct timespec *
+tospec(struct timeval tv, struct timespec *ts)
+{
+ ts->tv_nsec = tv.tv_usec * 1000;
+ ts->tv_sec = tv.tv_sec;
+ return ts;
+}
+
+static void
+sleeptill(struct timeval sched)
+{
+ int sts;
+ struct timeval curr; /* current time */
+ struct timespec delay; /* interval to sleep */
+ struct timespec left; /* remaining sleep time */
+
+ __pmtimevalNow(&curr);
+ tospec(tsub(sched, curr), &delay);
+ for (;;) { /* loop to catch early wakeup by nanosleep */
+ sts = nanosleep(&delay, &left);
+ if (sts == 0 || (sts < 0 && errno != EINTR))
+ break;
+ delay = left;
+ }
+}
+
+static int
+override(int opt, pmOptions *opts)
+{
+ (void)opts;
+ if (opt == 'H' || opt == 'a' || opt == 'N')
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *endnum = NULL;
+ int sts = 0;
+ int c, l, m, i, v;
+ int lines = 0;
+
+ // Metrics
+ QmcMetric *metric;
+ double value = 0;
+
+ // Config file
+ QString configName;
+ FILE *configFile = NULL;
+
+ // Timing
+ QString tzLabel;
+ QString tzString;
+ struct timeval logEndTime;
+ double endTime;
+ double delay;
+ double pos;
+
+ // Parse command line options
+ //
+ pmOptions opts;
+ memset(&opts, 0, sizeof(opts));
+ opts.flags = PM_OPTFLAG_MULTI;
+ opts.short_options = PMAPI_OPTIONS "c:Cd:f:FGHilmMNoP:rR:uU:w:X";
+ opts.long_options = longopts;
+ opts.short_usage = "[options] [metrics ...]";
+ opts.override = override;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'a': // archive name
+ endnum = strtok(opts.optarg, ", \t");
+ while (endnum) {
+ __pmAddOptArchive(&opts, endnum);
+ endnum = strtok(NULL, ", \t");
+ }
+ break;
+
+ case 'c': // config file
+ configName = opts.optarg;
+ break;
+
+ case 'C': // parse config, output metrics and units only
+ dumpFlag = false;
+ break;
+
+ case 'd': // delimiter
+ if (strlen(opts.optarg) == 2 && opts.optarg[0] == '\\') {
+ switch (opts.optarg[1]) {
+ case 'n':
+ delimiter = '\n';
+ break;
+ case 't':
+ delimiter = '\t';
+ break;
+ default:
+ delimiter = ' ';
+ }
+ }
+ else if (strlen(opts.optarg) > 1) {
+ pmprintf("%s: delimiter must be one character\n", pmProgname);
+ opts.errors++;
+ }
+ else
+ delimiter = opts.optarg[0];
+ break;
+
+ case 'f': // Time format
+ timeFormat = opts.optarg;
+ if (timeFormat.length() == 0)
+ timeFlag = false;
+ else
+ timeFlag = true;
+ break;
+
+ case 'F': // Fixed width values
+ if (shortFlag) {
+ pmprintf("%s: -F and -G options may not be used together\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else
+ descFlag = true;
+ break;
+
+ case 'G': // Shortest format
+ if (descFlag) {
+ pmprintf("%s: -F and -G may not be used together\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else if (niceFlag) {
+ pmprintf("%s: -i and -G may not be used togther\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else
+ shortFlag = true;
+ break;
+
+ case 'H': // show all headers
+ headerFlag = true;
+ break;
+
+ case 'i': // abbreviate metric names
+ if (precFlag) {
+ pmprintf("%s: -i and -P may not be used togther\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else if (shortFlag) {
+ pmprintf("%s: -i and -G may not be used togther\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else
+ niceFlag = true;
+ break;
+
+ case 'l': // show source of metrics
+ sourceFlag = true;
+ break;
+
+ case 'm': // show metric names
+ metricFlag = true;
+ break;
+
+ case 'M': // show full metric names
+ fullFlag = true;
+ break;
+
+ case 'X': // show full metric names (extended mode)
+ fullFlag = true;
+ fullXFlag = true;
+ break;
+
+ case 'N': // show normalization values
+ normFlag = true;
+ break;
+
+ case 'o': // report timeOffset
+ timeOffsetFlag = true;
+ break;
+
+ case 'P': // precision
+ if (widthFlag) {
+ pmprintf("%s: -P and -w may not be used together\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else if (niceFlag) {
+ pmprintf("%s: -i and -P may not be used together\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else {
+ precision = (int)strtol(opts.optarg, &endnum, 10);
+ precFlag = true;
+ if (*endnum != '\0' || precision < 0) {
+ pmprintf("%s: -P requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ }
+ break;
+
+
+ case 'r': // output raw values
+ rawFlag = true;
+ break;
+
+ case 'R': // repeat header
+ repeatLines = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || repeatLines <= 0) {
+ pmprintf("%s: -R requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'u': // show units
+ unitFlag = true;
+ break;
+
+ case 'U': // error string
+ errStr = opts.optarg;
+ break;
+
+ case 'w': // width
+ if (precFlag) {
+ pmprintf("%s: -P and -w may not be used together\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else {
+ width = (int)strtol(opts.optarg, &endnum, 10);
+ widthFlag = true;
+ if (*endnum != '\0' || width < 0) {
+ pmprintf("%s: -w requires a positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else if (width < 3) {
+ pmprintf("%s: -w must be greater than 2\n", pmProgname);
+ opts.errors++;
+ }
+ }
+ break;
+ }
+ }
+
+ if (opts.context == PM_CONTEXT_HOST) {
+ if (opts.nhosts > 1) {
+ pmprintf("%s: only one host may be specified\n", pmProgname);
+ opts.errors++;
+ }
+ }
+
+ if (opts.errors > 0) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ // Default update interval is 1 second
+ if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0)
+ opts.interval.tv_sec = 1;
+
+ if (headerFlag)
+ metricFlag = unitFlag = sourceFlag = normFlag = true;
+
+ if (fullXFlag)
+ niceFlag = true;
+
+ // Create the metric fetch group
+ group = new QmcGroup(true);
+
+ // Create archive contexts
+ if (opts.narchives > 0) {
+ for (c = 0; c < opts.narchives; c++)
+ if (group->use(PM_CONTEXT_ARCHIVE, opts.archives[c]) < 0)
+ opts.errors++;
+ }
+ // Create live context
+ else if (opts.nhosts > 0) {
+ if (group->use(PM_CONTEXT_HOST, opts.hosts[0]) < 0)
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmflush();
+ exit(1);
+ }
+
+ // Set up cout to use the required formatting
+ //
+ if (niceFlag) {
+ width = width < 6 ? 6 : width;
+ widthFlag = true;
+ descFlag = true;
+ }
+
+ if (shortFlag) {
+ if (widthFlag) {
+ width = width < 3 ? 3 : width;
+ precision = width - 1;
+ }
+ else {
+ precision = precision < 2 ? 2 : precision;
+ width = precision + 1;
+ }
+ }
+ else {
+ if (widthFlag) {
+ width = width < 3 ? 3 : width;
+ precision = width - 2;
+ }
+ else {
+ precision = precision < 2 ? 2 : precision;
+ width = precision + 2;
+ }
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "main: optind = " << opts.optind << ", argc = " << argc
+ << ", width = " << width << ", precision = " << precision
+ << endl;
+
+ if (opts.optind == argc) {
+ if (configName.length() == 0) {
+ configFile = stdin;
+ configName = "(stdin)";
+ }
+ else {
+ configFile = fopen((const char *)configName.toAscii(), "r");
+ if (configFile == NULL) {
+ pmprintf("%s: Unable to open %s: %s\n", pmProgname,
+ (const char *)configName.toAscii(), strerror(errno));
+ pmflush();
+ exit(1);
+ }
+ }
+ }
+ else if (configName.length()) {
+ pmprintf("%s: configuration file cannot be specified with metrics\n",
+ pmProgname);
+ exit(1);
+ }
+
+ if (configFile != NULL) {
+ opts.errors = parseConfig(configName, configFile);
+ }
+ else {
+ for (c = opts.optind; c < argc; c++) {
+ if (traverse(argv[c], 0.0) < 0)
+ opts.errors++;
+ }
+ }
+
+ if (metrics.size() == 0 || numValues == 0) {
+ pmprintf("%s: no valid metrics, exiting.\n", pmProgname);
+ pmflush();
+ exit(1);
+ }
+ else if (opts.errors)
+ pmprintf("%s: Warning: Some metrics ignored, continuing with valid metrics\n",
+ pmProgname);
+
+ pmflush();
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "main: parsed " << metrics.size() << " metrics"
+ << endl;
+
+ group->useDefault();
+
+ if (group->context()->source().type() != PM_CONTEXT_ARCHIVE)
+ isLive = true;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "main: default source is " << *(group->context()) << endl;
+
+ if (opts.tzflag)
+ group->useTZ();
+ else if (opts.timezone) {
+ sts = group->useTZ(opts.timezone);
+ if ((sts = pmNewZone(opts.timezone)) < 0) {
+ pmprintf("%s: cannot set timezone to \"%s\": %s\n", pmProgname,
+ opts.timezone, pmErrStr(sts));
+ pmflush();
+ exit(1);
+ }
+ }
+
+ group->defaultTZ(tzLabel, tzString);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "main: Using timezone \"" << tzString << "\" from " << tzLabel
+ << endl;
+ }
+
+ // putenv timezone into TZ as we may use strftime or ctime later
+ //
+ if (group->defaultTZ() != QmcGroup::localTZ) {
+ tzEnv.append(tzString);
+ sts = putenv(strdup((const char *)tzEnv.toAscii()));
+ if (sts < 0) {
+ pmprintf("%s: Warning: Unable to set timezone in environment\n",
+ pmProgname);
+ sts = 0;
+ }
+ else if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "main: Changed environment with \""
+ << tzEnv << '"' << endl;
+ }
+
+ if (isLive) {
+ __pmtimevalNow(&logStartTime);
+ logEndTime.tv_sec = INT_MAX;
+ logEndTime.tv_usec = INT_MAX;
+ }
+ else {
+ group->updateBounds();
+
+ logStartTime = group->logStart();
+ logEndTime = group->logEnd();
+ if (__pmtimevalToReal(&logEndTime) <= __pmtimevalToReal(&logStartTime)) {
+ logEndTime.tv_sec = INT_MAX;
+ logEndTime.tv_usec = INT_MAX;
+ }
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "main: start = "
+ << __pmtimevalToReal(&logStartTime) << ", end = "
+ << __pmtimevalToReal(&logEndTime)
+ << endl;
+ }
+
+ sts = pmParseTimeWindow(opts.start_optarg, opts.finish_optarg,
+ opts.align_optarg, opts.origin_optarg,
+ &logStartTime, &logEndTime, &opts.start,
+ &opts.finish, &opts.origin, &endnum);
+ if (sts < 0) {
+ pmprintf("%s\n", endnum);
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ pos = __pmtimevalToReal(&opts.origin);
+ endTime = __pmtimevalToReal(&opts.finish);
+ delay = (int)(__pmtimevalToReal(&opts.interval) * 1000.0);
+
+ if (endTime < pos && opts.finish_optarg == NULL)
+ endTime = DBL_MAX;
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "main: realStartTime = " << __pmtimevalToReal(&opts.start)
+ << ", endTime = " << endTime << ", pos = " << pos
+ << ", delay = " << delay << endl;
+ }
+
+ pmflush();
+ dumpHeader();
+
+ // Only dump full names once
+ if (fullXFlag == false)
+ fullFlag = false;
+
+ if (!dumpFlag)
+ exit(0);
+
+ if (!isLive) {
+ int tmp_mode = PM_MODE_INTERP;
+ int tmp_delay = getXTBintervalFromTimeval(&tmp_mode, &opts.interval);
+ group->setArchiveMode(tmp_mode, &opts.origin, tmp_delay);
+ }
+
+ if (shortFlag) {
+ cout.setRealNumberPrecision(precision);
+ }
+ else if (!descFlag) {
+ cout.setRealNumberPrecision(precision);
+ cout.setRealNumberNotation(QTextStream::FixedNotation);
+ }
+
+ while (pos <= endTime &&
+ ((opts.samples > 0 && sampleCount < opts.samples) ||
+ opts.samples == 0)) {
+
+ group->fetch();
+ sampleCount++;
+
+ if (timeFlag)
+ cout << dumpTime(opts.origin) << delimiter;
+
+ for (m = 0, v = 1; m < metrics.size(); m++) {
+ metric = metrics[m];
+
+ for (i = 0; i < metric->numValues(); i++) {
+ if (rawFlag) {
+ if (metric->currentError(i) < 0) {
+ if (niceFlag)
+ cout << qSetFieldWidth(width) << errStr
+ << qSetFieldWidth(0);
+ else
+ cout << errStr;
+ goto next;
+ }
+ else if (metric->real())
+ value = metric->currentValue(i);
+ }
+ else if (metric->error(i) < 0) {
+ if (niceFlag)
+ cout << qSetFieldWidth(width) << errStr
+ << qSetFieldWidth(0);
+ else
+ cout << errStr;
+ goto next;
+ }
+ else if (metric->real())
+ value = metric->value(i);
+
+ if (metric->real()) {
+ if (descFlag)
+ if (niceFlag)
+ cout << qSetFieldWidth(width)
+ << QmcMetric::formatNumber(value)
+ << qSetFieldWidth(0);
+ else
+ cout << QmcMetric::formatNumber(value);
+ else if (niceFlag)
+ cout << qSetFieldWidth(width) << value
+ << qSetFieldWidth(0);
+ else
+ cout << value;
+ }
+ // String
+ else {
+ l = metric->stringValue(i).length();
+ buffer[0] = '\"';
+ if (niceFlag) {
+ if (l > width - 2) {
+ strncpy(buffer+1, (const char *)metric->stringValue(i).toAscii(),
+ width - 2);
+ buffer[width - 1] = '\"';
+ buffer[width] = '\0';
+ cout << qSetFieldWidth(width) << buffer
+ << qSetFieldWidth(0);
+ }
+ else {
+ strcpy(buffer+1, (const char *)metric->stringValue(i).toAscii());
+ buffer[l + 1] = '\"';
+ buffer[l + 2] = '\0';
+ cout << qSetFieldWidth(width) << buffer;
+ }
+ }
+ else if (widthFlag) {
+ if (l > width - 2 && width > 5) {
+ strncpy(buffer+1, (const char *)metric->stringValue(i).toAscii(),
+ width - 5);
+ strcpy(buffer + width - 4, "...\"");
+ buffer[width] = '\0';
+ cout << qSetFieldWidth(width) << buffer
+ << qSetFieldWidth(0);
+ }
+ else {
+ strncpy(buffer+1, (const char *)metric->stringValue(i).toAscii(),
+ width - 2);
+ buffer[width - 1] = '\"';
+ buffer[width] = '\0';
+ cout << qSetFieldWidth(width) << buffer
+ << qSetFieldWidth(0);
+ }
+ }
+ else
+ cout << '\"' << metric->stringValue(i) << '\"';
+ }
+
+ next:
+ if (v < numValues) {
+ cout << delimiter;
+ v++;
+ }
+ }
+ }
+ cout << endl;
+
+// if (opts.samples > 0 && sampleCount == opts.samples)
+// continue; /* do not sleep needlessly */
+
+ opts.origin = tadd(opts.origin, opts.interval);
+
+ if (isLive)
+ sleeptill(opts.origin);
+
+ pos = __pmtimevalToReal(&opts.origin);
+ lines++;
+ if (repeatLines > 0 && repeatLines == lines) {
+ cout << endl;
+ dumpHeader();
+ lines = 0;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/pmdumptext/pmdumptext.pro b/src/pmdumptext/pmdumptext.pro
new file mode 100644
index 0000000..d629717
--- /dev/null
+++ b/src/pmdumptext/pmdumptext.pro
@@ -0,0 +1,11 @@
+TEMPLATE = app
+LANGUAGE = C++
+SOURCES = pmdumptext.cpp
+CONFIG += qt console warn_on
+INCLUDEPATH += ../include ../libpcp_qmc/src
+release:DESTDIR = build/debug
+debug:DESTDIR = build/release
+LIBS += -L../libpcp/src
+LIBS += -L../libpcp_qmc/src -L../libpcp_qmc/src/$$DESTSIR
+LIBS += -lpcp_qmc -lpcp
+QT -= gui
diff --git a/src/pmerr/GNUmakefile b/src/pmerr/GNUmakefile
new file mode 100644
index 0000000..854103a
--- /dev/null
+++ b/src/pmerr/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmerr.c
+CMDTARGET = pmerr$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmerr/pmerr.c b/src/pmerr/pmerr.c
new file mode 100644
index 0000000..2bcc36a
--- /dev/null
+++ b/src/pmerr/pmerr.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include "pmapi.h"
+#include <ctype.h>
+
+extern void __pmDumpErrTab(FILE *);
+
+int
+main(int argc, char **argv)
+{
+ int code;
+ int sts;
+ char *p;
+ char *q;
+
+ if (argc > 1 &&
+ (strcmp(argv[1], "-l") == 0 || strcmp(argv[1], "--list") == 0)) {
+ __pmDumpErrTab(stdout);
+ exit(1);
+ }
+ else if (argc > 1 &&
+ (strcmp(argv[1], "-?") == 0 || strcmp(argv[1], "--help") == 0)) {
+ argc = 0;
+ }
+ else if (argc > 1 && argv[1][0] == '-' && !isxdigit((int)argv[1][1])) {
+ fprintf(stderr, "Illegal option -- %s\n", &argv[1][1]);
+ argc = 0;
+ }
+
+ if (argc == 0) {
+ fprintf(stderr,
+"Usage: pmerr [options] [code]\n\n"
+" -l, --list causes all known error codes to be listed\n");
+ exit(1);
+ }
+
+ while (argc > 1) {
+ sts = 0;
+ p = argv[1];
+ if (*p == '0' && (p[1] == 'x' || p[1] == 'X')) {
+ p = &p[2];
+ for (q = p; isxdigit((int)*q); q++)
+ ;
+ if (*q == '\0')
+ sts = sscanf(p, "%x", &code);
+ }
+ if (sts < 1)
+ sts = sscanf(argv[1], "%d", &code);
+ if (sts != 1) {
+ printf("Cannot decode \"%s\" - neither decimal nor hexadecimal\n", argv[1]);
+ goto next;
+ }
+
+ if (code > 0) {
+ code = -code;
+ printf("Code is positive, assume you mean %d\n", code);
+ }
+
+ printf("Code: %d 0x%x Text: %s\n", code, code, pmErrStr(code));
+
+next:
+ argc--;
+ argv++;
+ }
+
+ return 0;
+}
diff --git a/src/pmevent/GNUmakefile b/src/pmevent/GNUmakefile
new file mode 100644
index 0000000..8efc8b4
--- /dev/null
+++ b/src/pmevent/GNUmakefile
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmevent.c doargs.c
+HFILES = pmevent.h
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_gui/src
+LLDLIBS = $(PCP_GUILIB) $(LIB_FOR_MATH)
+
+CMDTARGET = pmevent$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/pmevent/doargs.c b/src/pmevent/doargs.c
new file mode 100644
index 0000000..0692c31
--- /dev/null
+++ b/src/pmevent/doargs.c
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmevent.h"
+
+static int setupEventTrace(int, char **, int, char *);
+static char *options = "a:D:gh:K:LO:p:S:s:T:t:vx:zZ:?";
+static char usage[] =
+ "Usage: %s [options] metricname ...\n\n"
+ "Options:\n"
+ " -a archive metrics source is a PCP archive\n"
+ " -g start in GUI mode with new time control\n"
+ " -h host metrics source is PMCD on host\n"
+ " -K spec optional additional PMDA spec for local connection\n"
+ " spec is of the form op,domain,dso-path,init-routine\n"
+ " -L metrics source is a local context\n"
+ " -O offset initial offset into the reporting time window\n"
+ " -p port port number for connection to existing time control\n"
+ " -S starttime start of the reporting time window\n"
+ " -s samples terminate after this many samples\n"
+ " -T endtime end of the reporting time window\n"
+ " -t interval sample interval [default 1 second]\n"
+ " -v increase diagnostic output\n"
+ " -x filter optionally enable and filter the event stream\n"
+ " -Z timezone set reporting timezone\n"
+ " -z set reporting timezone to local timezone of metrics source\n";
+
+/* process command line options and flags - exits on error */
+void
+doargs(int argc, char **argv)
+{
+ int c;
+ long d;
+ int errflag = 0;
+ int m;
+ int src = 0;
+ int have_context = 0;
+ int sts;
+ char *endnum;
+ char *errmsg;
+ char *Sflag = NULL; /* argument of -S flag */
+ char *Tflag = NULL; /* argument of -T flag */
+ char *Oflag = NULL; /* argument of -O flag */
+ char *xflag = NULL; /* argument of -x flag */
+ int zflag = 0; /* for -z */
+ char *tz = NULL; /* for -Z timezone */
+ int tzh; /* initial timezone handle */
+ struct timeval logStart;
+ metric_t *mp;
+ pmMetricSpec *msp;
+ char *msg;
+ static pmLogLabel label;
+ static char *default_host_conn = "local:";
+ char *host_conn = default_host_conn; /* argument of -h */
+ const char *tmp;
+
+ delta.tv_sec = 1;
+ delta.tv_usec = 0;
+ samples = ALL_SAMPLES;
+
+ /* extract command-line arguments */
+ while ((c = getopt(argc, argv, options)) != EOF) {
+ switch (c) {
+
+ case 'a': /* archive */
+ if (++src > 1) {
+ fprintf(stderr, "%s: at most one of -a/-h/-L allowed\n", pmProgname);
+ errflag++;
+ }
+ ahtype = PM_CONTEXT_ARCHIVE;
+ archive = optarg;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(optarg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, optarg);
+ errflag++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'g': /* use "gui" mode */
+ if (port != -1) {
+ fprintf(stderr, "%s: at most one of -g and -p allowed\n", pmProgname);
+ errflag++;
+ }
+ else
+ gui = 1;
+ break;
+
+ case 'h': /* host name */
+ if (++src > 1) {
+ fprintf(stderr, "%s: at most one of -a/-h/-L allowed\n", pmProgname);
+ errflag++;
+ }
+ ahtype = PM_CONTEXT_HOST;
+ host_conn = optarg;
+ break;
+
+ case 'K': /* update local PMDA table */
+ if ((errmsg = __pmSpecLocalPMDA(optarg)) != NULL) {
+ fprintf(stderr, "%s: illegal -K argument\n", pmProgname);
+ fputs(errmsg, stderr);
+ fputc('\n', stderr);
+ errflag++;
+ }
+ break;
+
+ case 'L': /* use local context */
+ if (++src > 1) {
+ fprintf(stderr, "%s: at most one of -a/-h/-L allowed\n", pmProgname);
+ errflag++;
+ }
+ ahtype = PM_CONTEXT_LOCAL;
+ break;
+
+ case 'O': /* start report time offset */
+ Oflag = optarg;
+ break;
+
+ case 'p': /* port for slave of existing time control */
+ if (gui == 1) {
+ fprintf(stderr, "%s: at most one of -g and -p allowed\n", pmProgname);
+ errflag++;
+ }
+ else {
+ port = (int)strtol(optarg, &endnum, 10);
+ if (*endnum != '\0' || port < 0) {
+ fprintf(stderr, "%s: -p requires a positive numeric argument\n", pmProgname);
+ port = -1;
+ errflag++;
+ }
+ }
+ break;
+
+ case 's': /* sample count */
+ d = (int)strtol(optarg, &endnum, 10);
+ if (Tflag) {
+ fprintf(stderr, "%s: at most one of -s and -T allowed\n", pmProgname);
+ errflag++;
+ }
+ else if (*endnum != '\0' || d < 0) {
+ fprintf(stderr, "%s: -s requires a positive numeric argument\n", pmProgname);
+ errflag++;
+ }
+ else
+ samples = d;
+ break;
+
+ case 'S': /* start report time */
+ Sflag = optarg;
+ break;
+
+ case 't': /* sampling interval */
+ if (pmParseInterval(optarg, &delta, &msg) < 0) {
+ fprintf(stderr, "%s: illegal -t argument\n", pmProgname);
+ fputs(msg, stderr);
+ free(msg);
+ errflag++;
+ }
+ break;
+
+ case 'T': /* end reporting time */
+ if (samples != ALL_SAMPLES) {
+ fprintf(stderr, "%s: at most one of -s and -T allowed\n", pmProgname);
+ errflag++;
+ }
+ Tflag = optarg;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ case 'x':
+ xflag = optarg;
+ break;
+
+ case 'z': /* timezone from metrics source */
+ if (tz != NULL) {
+ fprintf(stderr, "%s: at most one of -Z and/or -z allowed\n", pmProgname);
+ errflag++;
+ }
+ zflag++;
+ break;
+
+ case 'Z': /* $TZ timezone */
+ if (zflag) {
+ fprintf(stderr, "%s: at most one of -Z and/or -z allowed\n", pmProgname);
+ errflag++;
+ }
+ tz = optarg;
+ break;
+
+ case '?':
+ fprintf(stderr, usage, pmProgname);
+ exit(EXIT_FAILURE);
+
+ default:
+ errflag++;
+ }
+ }
+
+ if (errflag) {
+ fprintf(stderr, usage, pmProgname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (optind >= argc) {
+ fprintf(stderr, "Error: no metricname specified\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* parse uniform metric spec */
+ for ( ; optind < argc; optind++) {
+ if (ahtype == PM_CONTEXT_ARCHIVE)
+ sts = pmParseMetricSpec(argv[optind], 1, archive, &msp, &msg);
+ else
+ sts = pmParseMetricSpec(argv[optind], 0, host_conn, &msp, &msg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: bad metric specification\n", pmProgname);
+ fputs(msg, stderr);
+ free(msg);
+ exit(EXIT_FAILURE);
+ }
+
+ if (msp->isarch == 0) {
+ if (ahtype == -1) {
+ ahtype = PM_CONTEXT_HOST;
+ host_conn = msp->source;
+ }
+ else if ((ahtype == PM_CONTEXT_ARCHIVE) ||
+ (ahtype == PM_CONTEXT_LOCAL &&
+ (strcmp(msp->source, default_host_conn)))) {
+ fprintf(stderr, "%s: %s: only one type of metric source allowed\n", pmProgname, argv[optind]);
+ exit(EXIT_FAILURE);
+ }
+ else if (strcmp(host_conn, msp->source) != 0) {
+ fprintf(stderr, "%s: %s: only one metric source allowed, found hosts %s and %s\n", pmProgname, argv[optind], host_conn, msp->source);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (msp->isarch == 1) {
+ if (ahtype == -1) {
+ ahtype = PM_CONTEXT_ARCHIVE;
+ archive = msp->source;
+ }
+ else if (ahtype != PM_CONTEXT_ARCHIVE) {
+ fprintf(stderr, "%s: %s: only one type of metric source allowed\n", pmProgname, argv[optind]);
+ exit(EXIT_FAILURE);
+ }
+ else if (strcmp(archive, msp->source) != 0) {
+ fprintf(stderr, "%s: %s: only one metric source allowed, found archives %s and %s\n", pmProgname, argv[optind], archive, msp->source);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (msp->isarch == 2) {
+ if (ahtype == -1) {
+ ahtype = PM_CONTEXT_LOCAL;
+ }
+ else if (ahtype != PM_CONTEXT_LOCAL) {
+ fprintf(stderr, "%s: %s: only one type of metric source allowed\n", pmProgname, argv[optind]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (!have_context) {
+ if (ahtype == PM_CONTEXT_ARCHIVE) {
+ /* open connection to archive */
+ if ((sts = pmNewContext(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, msp->source, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ ctxhandle = sts;
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: Cannot get archive label record: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ have_context = 1;
+ logStart = label.ll_start;
+ host = label.ll_hostname;
+ if ((sts = pmGetArchiveEnd(&last)) < 0) {
+ fprintf(stderr, "%s: Cannot determine end of archive: %s",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ }
+ else {
+ /* open connection to host or local context */
+ if ((sts = pmNewContext(ahtype, host_conn)) < 0) {
+ if (ahtype == PM_CONTEXT_HOST)
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, msp->source, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot establish local context: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ ctxhandle = sts;
+ have_context = 1;
+ __pmtimevalNow(&logStart);
+ }
+ }
+
+ /* Look up the host name according to the pmcd or the archive. */
+ tmp = pmGetContextHostName(ctxhandle);
+ if (strlen(tmp) == 0) {
+ fprintf(stderr, "%s: pmGetContextHostName(%d) failed\n",
+ pmProgname, ctxhandle);
+ exit(EXIT_FAILURE);
+ }
+ if ((host = strdup(tmp)) == NULL)
+ __pmNoMem("host name copy", strlen(tmp)+1, PM_FATAL_ERR);
+
+ for (m = 0; m < nmetric; m++) {
+ if (strcmp(msp->metric, metrictab[m].name) == 0)
+ break;
+ }
+ if (m < nmetric)
+ mp = &metrictab[m];
+ else {
+ nmetric++;
+ metrictab = (metric_t *)realloc(metrictab, nmetric*sizeof(metrictab[0]));
+ if (metrictab == NULL) {
+ __pmNoMem("metrictab", nmetric*sizeof(metrictab[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ mp = &metrictab[nmetric-1];
+ mp->name = msp->metric;
+ if ((sts = pmLookupName(1, &mp->name, &mp->pmid)) < 0) {
+ fprintf(stderr, "%s: pmLookupName: %s: %s\n", pmProgname, mp->name, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ if ((sts = pmLookupDesc(mp->pmid, &mp->desc)) < 0) {
+ fprintf(stderr, "%s: pmLookupDesc: %s: %s\n", pmProgname, mp->name, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ if (mp->desc.type != PM_TYPE_EVENT &&
+ mp->desc.type != PM_TYPE_HIGHRES_EVENT) {
+ fprintf(stderr, "%s: %s: metrics must be of event type\n", pmProgname, mp->name);
+ exit(EXIT_FAILURE);
+ }
+ mp->ninst = 0;
+ mp->iname = NULL;
+ mp->inst = NULL;
+ mp->ihash.nodes = 0;
+ mp->ihash.hsize = 0;
+ mp->ihash.hash = NULL;
+ }
+
+ if (msp->ninst > 0) {
+ int i;
+ int j;
+ if (mp->desc.indom == PM_INDOM_NULL) {
+ fprintf(stderr, "%s: %s: singular metrics do not have instances\n", pmProgname, argv[optind]);
+ exit(EXIT_FAILURE);
+ }
+ i = mp->ninst;
+ mp->ninst += msp->ninst;
+ mp->iname = (char **)realloc(mp->iname, mp->ninst*sizeof(mp->iname[0]));
+ if (mp->iname == NULL) {
+ __pmNoMem("iname[]", mp->ninst*sizeof(mp->iname[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ mp->inst = (int *)realloc(mp->inst, mp->ninst*sizeof(mp->inst[0]));
+ if (mp->inst == NULL) {
+ __pmNoMem("inst[]", mp->ninst*sizeof(mp->inst[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (j = 0; j < msp->ninst; j++, i++) {
+ mp->iname[i] = msp->inst[j];
+ if (ahtype == PM_CONTEXT_ARCHIVE)
+ sts = pmLookupInDomArchive(mp->desc.indom, mp->iname[i]);
+ else
+ sts = pmLookupInDom(mp->desc.indom, mp->iname[i]);
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmLookupInDom: %s[%s]: %s\n", pmProgname, mp->name, mp->iname[i], pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ mp->inst[i] = sts;
+ }
+ }
+
+ /*
+ * don't call pmFreeMetricSpec(msp) because we retain pointers into
+ * the structure
+ */
+ }
+
+ if (zflag) {
+ if ((tzh = pmNewContextZone()) < 0) {
+ fprintf(stderr, "%s: Cannot set context timezone: %s\n",
+ pmProgname, pmErrStr(tzh));
+ exit(EXIT_FAILURE);
+ }
+ if (ahtype == PM_CONTEXT_ARCHIVE) {
+ printf("Note: timezone set to local timezone of host \"%s\" from archive\n\n",
+ host);
+ }
+ else {
+ printf("Note: timezone set to local timezone of host \"%s\"\n\n", host);
+ }
+ }
+ else if (tz != NULL) {
+ if ((tzh = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, tz, pmErrStr(tzh));
+ exit(EXIT_FAILURE);
+ }
+ printf("Note: timezone set to \"TZ=%s\"\n\n", tz);
+ }
+
+ if (pmParseTimeWindow(Sflag, Tflag, NULL, Oflag,
+ &logStart, &last, &first, &last, &now, &msg) < 0) {
+ fprintf(stderr, "%s", msg);
+ exit(EXIT_FAILURE);
+ }
+
+ if (setupEventTrace(argc, argv, ahtype, xflag) < 0)
+ exit(EXIT_FAILURE);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char timebuf[26];
+ char *tp;
+ int i;
+ fprintf(stderr, "first=%.6f", __pmtimevalToReal(&first));
+ tp = pmCtime(&first.tv_sec, timebuf);
+ /*
+ * tp -> Ddd Mmm DD HH:MM:SS YYYY\n
+ * 0 4 8 1 1 2 2 2
+ * 1 8 0 3 4
+ */
+ fprintf(stderr, " [%24.24s]\n", tp);
+ fprintf(stderr, "now=%.6f", __pmtimevalToReal(&now));
+ tp = pmCtime(&now.tv_sec, timebuf);
+ fprintf(stderr, " [%24.24s]\n", tp);
+ fprintf(stderr, "last=%.6f", __pmtimevalToReal(&last));
+ tp = pmCtime(&last.tv_sec, timebuf);
+ fprintf(stderr, " [%24.24s]\n", tp);
+ fprintf(stderr, "delta=%.6f\n", __pmtimevalToReal(&delta));
+ if (samples != ALL_SAMPLES)
+ fprintf(stderr, "samples=%ld\n", samples);
+ for (m = 0; m < nmetric; m++) {
+ fprintf(stderr, "[%d] metric: %s", m, metrictab[m].name);
+ if (metrictab[m].ninst > 0) {
+ fprintf(stderr, " instance:");
+ for (i = 0; i < metrictab[m].ninst; i++) {
+ if (i == 0)
+ fputc(' ', stderr);
+ else
+ fprintf(stderr, ", ");
+ fprintf(stderr, "%s (%d)", metrictab[m].iname[i], metrictab[m].inst[i]);
+ }
+ fputc('\n', stderr);
+ }
+ }
+ }
+}
+
+int
+setupEventTrace(int argc, char **argv, int ahtype, char *xflag)
+{
+ pmValueSet pmvs;
+ pmValueBlock *pmvbp;
+ pmResult store = { .numpmid = 1 };
+ int m, vlen;
+
+ if (ahtype == PM_CONTEXT_ARCHIVE)
+ return 0; /* nothing to do at this stage */
+
+ if (ahtype == PM_CONTEXT_HOST)
+ __pmSetClientIdArgv(argc, argv);
+
+ /* build pmResult for pmStore call if we're explicitly enabling events */
+ if (xflag != NULL) {
+ vlen = PM_VAL_HDR_SIZE + strlen(xflag) + 1;
+ pmvbp = (pmValueBlock *)malloc(vlen);
+ if (!pmvbp)
+ __pmNoMem("store", vlen, PM_FATAL_ERR);
+ pmvbp->vtype = PM_TYPE_STRING;
+ pmvbp->vlen = vlen;
+ strcpy(pmvbp->vbuf, xflag);
+
+ store.vset[0] = &pmvs;
+ for (m = 0; m < nmetric; m++) {
+ pmvs.pmid = metrictab[m].pmid;
+ pmvs.numval = 1;
+ pmvs.valfmt = PM_VAL_SPTR;
+ pmvs.vlist[0].inst = PM_IN_NULL;
+ pmvs.vlist[0].value.pval = pmvbp;
+
+ pmStore(&store);
+ }
+
+ free(pmvbp);
+ }
+ return 0;
+}
diff --git a/src/pmevent/pmevent.c b/src/pmevent/pmevent.c
new file mode 100644
index 0000000..d8221b3
--- /dev/null
+++ b/src/pmevent/pmevent.c
@@ -0,0 +1,531 @@
+/*
+ * pmevent - event record dumper
+ * (based on pmval)
+ *
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * TODO
+ * + -g and -p - nothing has been checked
+ */
+
+#include "pmevent.h"
+
+static int amode = PM_MODE_FORW; /* archive scan mode */
+static pmTime *pmtime;
+
+char *host; /* hostname according to pmGetContextHostName */
+char *archive; /* archive source */
+int ahtype = -1; /* archive or host or local context */
+int ctxhandle = -1; /* handle for the active context */
+int verbose; /* verbose diagnostic output */
+struct timeval now; /* current reporting time */
+struct timeval first; /* start reporting time */
+struct timeval last = {INT_MAX, 999999}; /* end reporting time */
+struct timeval delta; /* sample interval */
+long samples; /* number of samples */
+int gui; /* set if -g */
+int port = -1; /* pmtime port number from -p */
+pmTimeControls controls;
+
+metric_t *metrictab; /* metrics from cmd line */
+int nmetric;
+pmID *pmidlist;
+static pmID pmid_flags;
+static pmID pmid_missed;
+
+/* performance metrics in the hash list */
+typedef struct {
+ char *name; /* name of metric */
+ pmDesc desc; /* metric description */
+} hash_t;
+
+/* Fetch metric values. */
+static int
+getvals(pmResult **result)
+{
+ pmResult *rp = NULL;
+ int sts;
+ int i;
+ int m;
+
+ if (archive != NULL) {
+ /*
+ * for archives read until we find a pmResult with at least
+ * one of the pmids we are after
+ */
+ for ( ; ; ) {
+ sts = pmFetchArchive(&rp);
+ if (sts < 0)
+ break;
+
+ if (rp->numpmid == 0)
+ /* skip mark records */
+ continue;
+
+ /*
+ * scan for any of the metrics of interest ... keep skipping
+ * archive records until one found
+ */
+ for (i = 0; i < rp->numpmid; i++) {
+ for (m = 0; m < nmetric; m++) {
+ if (rp->vset[i]->pmid == metrictab[m].pmid) {
+ /* match */
+ goto done;
+ }
+ }
+ }
+ pmFreeResult(rp);
+ rp = NULL;
+ }
+ }
+ else
+ sts = pmFetch(nmetric, pmidlist, &rp);
+
+done:
+ if (sts >= 0)
+ *result = rp;
+ else if (rp)
+ pmFreeResult(rp);
+
+ return sts;
+}
+
+static void
+timestep(struct timeval newdelta)
+{
+ /* time moved, may need to wait for previous value again */
+ // TODO ?
+}
+
+
+/***************************************************************************
+ * output
+ ***************************************************************************/
+
+/* Print parameter values as output header. */
+static void
+printhdr(void)
+{
+ char timebuf[26];
+
+ if (archive == NULL) {
+ printf("host: %s\n", host);
+ } else {
+ printf("archive: %s\n", archive);
+ printf("host: %s\n", host);
+ printf("start: %s", pmCtime(&first.tv_sec, timebuf));
+ if (last.tv_sec != INT_MAX)
+ printf("end: %s", pmCtime(&last.tv_sec, timebuf));
+ }
+
+ /* sample count and interval */
+ if (samples == ALL_SAMPLES) printf("samples: all\n");
+ else printf("samples: %ld\n", samples);
+ if (samples != ALL_SAMPLES && samples > 1 && ahtype != PM_CONTEXT_ARCHIVE)
+ printf("interval: %1.2f sec\n", __pmtimevalToReal(&delta));
+}
+
+/*
+ * cache all of the most recently requested
+ * pmInDom ...
+ */
+static char *
+lookup(pmInDom indom, int inst)
+{
+ static pmInDom last = PM_INDOM_NULL;
+ static int numinst = -1;
+ static int *instlist;
+ static char **namelist;
+ int i;
+
+ if (indom != last) {
+ if (numinst > 0) {
+ free(instlist);
+ free(namelist);
+ }
+ if (archive == NULL)
+ numinst = pmGetInDom(indom, &instlist, &namelist);
+ else
+ numinst = pmGetInDomArchive(indom, &instlist, &namelist);
+ last = indom;
+ }
+
+ for (i = 0; i < numinst; i++) {
+ if (instlist[i] == inst)
+ return namelist[i];
+ }
+
+ return NULL;
+}
+
+static void myeventdump(pmValueSet *, int, int);
+
+static void
+mydump(const char *name, pmDesc *dp, pmValueSet *vsp)
+{
+ int j;
+ char *p;
+
+ if (vsp->numval == 0) {
+ if (verbose)
+ printf("%s: No value(s) available!\n", name);
+ return;
+ }
+ else if (vsp->numval < 0) {
+ printf("%s: Error: %s\n", name, pmErrStr(vsp->numval));
+ return;
+ }
+
+ printf(" %s", name);
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (dp->indom != PM_INDOM_NULL) {
+ if (vsp->numval > 1)
+ printf("\n ");
+ if ((p = lookup(dp->indom, vp->inst)) == NULL)
+ printf("[%d]", vp->inst);
+ else
+ printf("[\"%s\"]", p);
+ }
+ putchar(' ');
+
+ switch (dp->type) {
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC: {
+ /*
+ * pinched from pmPrintValue, just without the preamble of
+ * floating point values
+ */
+ char *p;
+ int i;
+ putchar('[');
+ p = &vp->value.pval->vbuf[0];
+ for (i = 0; i < vp->value.pval->vlen - PM_VAL_HDR_SIZE; i++, p++)
+ printf("%02x", *p & 0xff);
+ putchar(']');
+ putchar('\n');
+ break;
+ }
+ case PM_TYPE_EVENT:
+ case PM_TYPE_HIGHRES_EVENT:
+ /* odd, nested event type! */
+ myeventdump(vsp, j, dp->type != PM_TYPE_EVENT);
+ break;
+ default:
+ pmPrintValue(stdout, vsp->valfmt, dp->type, vp, 1);
+ putchar('\n');
+ }
+ }
+}
+
+static void
+myvaluesetdump(pmValueSet *xvsp, int idx, int *flagsp)
+{
+ int sts, flags = *flagsp;
+ hash_t *hp;
+ __pmHashNode *hnp;
+ static __pmHashCtl hash = { 0, 0, NULL };
+
+ if ((hnp = __pmHashSearch((unsigned int)xvsp->pmid, &hash)) == NULL) {
+ /* first time for this pmid */
+ hp = (hash_t *)malloc(sizeof(hash_t));
+ if (hp == NULL) {
+ __pmNoMem("hash_t", sizeof(hash_t), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ if ((sts = pmNameID(xvsp->pmid, &hp->name)) < 0) {
+ printf(" %s: pmNameID: %s\n", pmIDStr(xvsp->pmid), pmErrStr(sts));
+ free(hp);
+ return;
+ }
+ else {
+ if (xvsp->pmid != pmid_flags &&
+ xvsp->pmid != pmid_missed &&
+ (sts = pmLookupDesc(xvsp->pmid, &hp->desc)) < 0) {
+ printf(" %s: pmLookupDesc: %s\n", hp->name, pmErrStr(sts));
+ free(hp->name);
+ free(hp);
+ return;
+ }
+ if ((sts = __pmHashAdd((unsigned int)xvsp->pmid, (void *)hp, &hash)) < 0) {
+ printf(" %s: __pmHashAdd: %s\n", hp->name, pmErrStr(sts));
+ free(hp->name);
+ free(hp);
+ return;
+ }
+ }
+ }
+ else
+ hp = (hash_t *)hnp->data;
+
+ if (idx == 0) {
+ if (xvsp->pmid == pmid_flags) {
+ flags = *flagsp = xvsp->vlist[0].value.lval;
+ printf(" flags 0x%x", flags);
+ printf(" (%s) ---\n", pmEventFlagsStr(flags));
+ return;
+ }
+ else
+ printf(" ---\n");
+ }
+ if ((flags & PM_EVENT_FLAG_MISSED) &&
+ (idx == 1) &&
+ (xvsp->pmid == pmid_missed)) {
+ printf(" ==> %d missed event records\n",
+ xvsp->vlist[0].value.lval);
+ return;
+ }
+ mydump(hp->name, &hp->desc, xvsp);
+}
+
+static void
+myeventdump(pmValueSet *vsp, int idx, int highres)
+{
+ int r; /* event records */
+ int p; /* event parameters */
+ int flags;
+ int numpmid;
+ int nrecords;
+ pmResult **res = NULL;
+ pmHighResResult **hres = NULL;
+
+ if (highres) {
+ if ((nrecords = pmUnpackHighResEventRecords(vsp, idx, &hres)) < 0) {
+ printf(" pmUnpackEventRecords: %s\n", pmErrStr(nrecords));
+ return;
+ }
+ }
+ else {
+ if ((nrecords = pmUnpackEventRecords(vsp, idx, &res)) < 0) {
+ printf(" pmUnpackEventRecords: %s\n", pmErrStr(nrecords));
+ return;
+ }
+ }
+ printf(" %d event records\n", nrecords);
+
+ if (pmid_flags == 0) {
+ /*
+ * get PMID for event.flags and event.missed
+ * note that pmUnpackEventRecords() will have called
+ * __pmRegisterAnon(), so the anonymous metrics
+ * should now be in the PMNS
+ */
+ char *name_flags = "event.flags";
+ char *name_missed = "event.missed";
+ int sts;
+
+ sts = pmLookupName(1, &name_flags, &pmid_flags);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_flags, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_flags)->item = 1;
+ }
+ sts = pmLookupName(1, &name_missed, &pmid_missed);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_missed, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_missed)->item = 1;
+ }
+ }
+
+ for (r = 0; r < nrecords; r++) {
+ printf(" ");
+ if (highres) {
+ numpmid = hres[r]->numpmid;
+ __pmPrintHighResStamp(stdout, &hres[r]->timestamp);
+ }
+ else {
+ numpmid = res[r]->numpmid;
+ __pmPrintStamp(stdout, &res[r]->timestamp);
+ }
+
+ printf(" --- event record [%d]", r);
+ if (numpmid == 0) {
+ printf(" ---\n");
+ printf(" ==> No parameters\n");
+ continue;
+ }
+ if (numpmid < 0) {
+ printf(" ---\n");
+ printf(" Error: illegal number of parameters (%d)\n", numpmid);
+ continue;
+ }
+ flags = 0;
+ if (highres) {
+ for (p = 0; p < numpmid; p++)
+ myvaluesetdump(hres[r]->vset[p], p, &flags);
+ }
+ else {
+ for (p = 0; p < numpmid; p++)
+ myvaluesetdump(res[r]->vset[p], p, &flags);
+ }
+ }
+ if (highres)
+ pmFreeHighResEventResult(hres);
+ if (res)
+ pmFreeEventResult(res);
+}
+
+
+/***************************************************************************
+ * main
+ ***************************************************************************/
+int
+main(int argc, char **argv)
+{
+ pmResult *rp = NULL; /* current values */
+ int forever;
+ int sts;
+ int j;
+ int m;
+
+ __pmSetProgname(argv[0]);
+ setlinebuf(stdout);
+
+ doargs(argc, argv);
+
+ pmidlist = (pmID *)malloc(nmetric*sizeof(pmidlist[0]));
+ if (pmidlist == NULL) {
+ __pmNoMem("metrictab", nmetric*sizeof(pmidlist[0]), PM_FATAL_ERR);
+ /*NOTREACHED*/
+ }
+ for (m = 0 ; m < nmetric; m++)
+ pmidlist[m] = metrictab[m].pmid;
+
+ if (gui || port != -1) {
+ char *rpt_tz;
+ /* set up pmtime control */
+ pmWhichZone(&rpt_tz);
+ pmtime = pmTimeStateSetup(&controls, ahtype, port, delta, now,
+ first, last, rpt_tz, host);
+ controls.stepped = timestep;
+ gui = 1; /* we're using pmtime control from here on */
+ }
+ else if (ahtype == PM_CONTEXT_ARCHIVE) /* no time control, go it alone */
+ pmTimeStateMode(amode, delta, &now);
+
+ forever = (samples == ALL_SAMPLES || gui);
+
+ printhdr();
+
+ /* wait till time for first sample */
+ if (archive == NULL)
+ __pmtimevalPause(now);
+
+ /* main loop fetching and printing sample values */
+ while (forever || (samples-- > 0)) {
+ if (gui)
+ pmTimeStateVector(&controls, pmtime);
+
+ /* wait till time for sample */
+ if (!gui && archive == NULL)
+ __pmtimevalSleep(delta);
+
+ /* next sample */
+ sts = getvals(&rp);
+ if (gui)
+ pmTimeStateAck(&controls, pmtime);
+
+ if (sts < 0) {
+ if (sts == PM_ERR_EOL && gui) {
+ pmTimeStateBounds(&controls, pmtime);
+ continue;
+ }
+ if (sts == PM_ERR_EOL)
+ break;
+ if (archive == NULL)
+ fprintf(stderr, "\n%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ else
+ fprintf(stderr, "\n%s: pmFetchArchive: %s\n", pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
+ if ((double)rp->timestamp.tv_sec + (double)rp->timestamp.tv_usec/1000000 >
+ (double)last.tv_sec + (double)last.tv_usec/1000000)
+ break;
+
+ for (j = 0; j < rp->numpmid; j++) {
+ for (m = 0; m < nmetric; m++) {
+ metric_t *mp = &metrictab[m];
+ if (rp->vset[j]->pmid == mp->pmid) {
+ if (gui || archive != NULL) {
+ __pmPrintStamp(stdout, &rp->timestamp);
+ printf(" ");
+ }
+ if (rp->vset[j]->numval == 0) {
+ if (verbose)
+ printf("%s: No values available\n", mp->name);
+ } else if (rp->vset[j]->numval < 0) {
+ printf("%s: Error: %s\n", mp->name, pmErrStr(rp->vset[j]->numval));
+ } else {
+ int highres = (mp->desc.type != PM_TYPE_EVENT);
+ int i;
+
+ for (i = 0; i < rp->vset[j]->numval; i++) {
+ if (rp->vset[j]->vlist[i].inst == PM_IN_NULL)
+ printf("%s:", mp->name);
+ else {
+ int k;
+ char *iname = NULL;
+ if (mp->ninst > 0) {
+ for (k = 0; k < mp->ninst; k++) {
+ if (mp->inst[k] == rp->vset[j]->vlist[i].inst) {
+ iname = mp->iname[k];
+ break;
+ }
+ }
+ }
+ else {
+ /* all instances selected */
+ __pmHashNode *hnp;
+ hnp = __pmHashSearch((unsigned int)rp->vset[j]->vlist[i].inst, &mp->ihash);
+ if (hnp == NULL) {
+ if (archive != NULL)
+ sts = pmNameInDomArchive(mp->desc.indom, rp->vset[j]->vlist[i].inst, &iname);
+ else
+ sts = pmNameInDom(mp->desc.indom, rp->vset[j]->vlist[i].inst, &iname);
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmNameInDom: %s[%d]: %s\n", pmProgname, mp->name, rp->vset[j]->vlist[i].inst, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ if ((sts = __pmHashAdd((unsigned int)rp->vset[j]->vlist[i].inst, (void *)iname, &mp->ihash)) < 0) {
+ printf("%s: __pmHashAdd: %s[%s (%d)]: %s\n", pmProgname, mp->name, iname, rp->vset[j]->vlist[i].inst, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+ }
+ else
+ iname = (char *)hnp->data;
+ }
+ if (iname == NULL)
+ continue;
+ printf("%s[%s]:", mp->name, iname);
+ }
+ myeventdump(rp->vset[j], i, highres);
+ }
+ }
+ break;
+ }
+ }
+ }
+ pmFreeResult(rp);
+ }
+
+ return 0;
+}
diff --git a/src/pmevent/pmevent.h b/src/pmevent/pmevent.h
new file mode 100644
index 0000000..c0d234a
--- /dev/null
+++ b/src/pmevent/pmevent.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 2011 Red Hat Inc.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmtime.h"
+
+#define ALL_SAMPLES -1
+
+/* performance metrics from the command line */
+typedef struct {
+ char *name; /* name of metric */
+ pmID pmid; /* metric identifier */
+ pmDesc desc; /* metric description */
+ int ninst; /* number of instances, 0 for all instances */
+ char **iname; /* instance names */
+ int *inst; /* instance ids */
+ __pmHashCtl ihash; /* mapping when all instances requested */
+} metric_t;
+
+void doargs(int, char **);
+
+/*
+ * globals ... see declarations in pmevent.c for explanations
+ */
+extern char *host; /* as per pmGetContextHostName */
+extern char *archive;
+extern int ahtype;
+extern int ctxhandle;
+extern int verbose;
+extern struct timeval now;
+extern struct timeval first;
+extern struct timeval last;
+extern struct timeval delta;
+extern long samples;
+extern int gui;
+extern int port;
+extern pmTimeControls controls;
+
+extern metric_t *metrictab;
+extern int nmetric;
+extern pmID *pmidlist;
diff --git a/src/pmfind/GNUmakefile b/src/pmfind/GNUmakefile
new file mode 100644
index 0000000..62108e8
--- /dev/null
+++ b/src/pmfind/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmfind.c
+CMDTARGET = pmfind$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default : $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install : default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmfind/pmfind.c b/src/pmfind/pmfind.c
new file mode 100644
index 0000000..f5ece20
--- /dev/null
+++ b/src/pmfind/pmfind.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <signal.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static int quiet;
+static char *mechanism;
+static char *options;
+static unsigned discoveryFlags;
+
+static int override(int, pmOptions *);
+
+#ifndef IS_MINGW
+static void
+handleInterrupt(int sig)
+{
+ discoveryFlags |= PM_SERVICE_DISCOVERY_INTERRUPTED;
+}
+
+static void
+setupSignals(void)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = &handleInterrupt;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, SIGPIPE);
+ sigaddset(&sa.sa_mask, SIGINT);
+ sigaddset(&sa.sa_mask, SIGTERM);
+ sigaddset(&sa.sa_mask, SIGXFSZ);
+ sigaddset(&sa.sa_mask, SIGXCPU);
+ sa.sa_flags = SA_RESTART;
+
+ sigaction(SIGHUP, &sa, NULL);
+ sigaction(SIGPIPE, &sa, NULL);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+ sigaction(SIGXFSZ, &sa, NULL);
+ sigaction(SIGXCPU, &sa, NULL);
+}
+#else
+#define setupSignals() do { } while (0)
+#endif
+
+static const char *services[] = {
+ PM_SERVER_SERVICE_SPEC,
+ PM_SERVER_PROXY_SPEC,
+ PM_SERVER_WEBD_SPEC,
+};
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Discovery options"),
+ PMOPT_DEBUG,
+ { "mechanism", 1, 'm', "NAME", "set the discovery method to use [avahi|probe=<subnet>|all]" },
+ { "resolve", 0, 'r', 0, "resolve addresses" },
+ { "service", 1, 's', "NAME", "discover services [pmcd|pmproxy|pmwebd|...|all]" },
+ { "timeout", 1, 't', "N.N", "timeout in seconds" },
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "quiet", 0, 'q', 0, "quiet mode, do not write to stdout" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:m:rs:t:q?",
+ .long_options = longopts,
+ .override = override,
+};
+
+static int
+override(int opt, pmOptions *opts)
+{
+ (void)opts;
+ return (opt == 's' || opt == 't');
+}
+
+static int
+addOption(const char *option, const char *arg)
+{
+ size_t existingLen, optionLen, argLen;
+ size_t commaLen, equalLen;
+
+ /* The existing length and space for a comma. */
+ if (options == NULL) {
+ existingLen = 0;
+ commaLen = 0;
+ }
+ else {
+ existingLen = strlen(options);
+ commaLen = 1;
+ }
+
+ /*
+ * Additional space needed.
+ * We need space for the new option name and an optional argument,
+ * separated by an '='.
+ */
+ optionLen = strlen(option);
+ if (arg != NULL) {
+ equalLen = 1;
+ argLen = strlen(arg);
+ }
+ else {
+ equalLen = 0;
+ argLen = 0;
+ }
+
+ /* Make room for the existing options plus the new option */
+ options = realloc(options, existingLen + commaLen + optionLen + equalLen + argLen);
+ if (options == NULL)
+ return -ENOMEM;
+
+ /* Add the new option. */
+ sprintf(options + existingLen, "%s%s%s%s",
+ commaLen != 0 ? "," : "", option,
+ equalLen != 0 ? "=" : "",
+ argLen != 0 ? arg : "");
+
+ return 0;
+}
+
+static int
+discovery(const char *spec)
+{
+ int i, sts;
+ char **urls;
+
+ sts = __pmDiscoverServicesWithOptions(spec, mechanism, options,
+ &discoveryFlags, &urls);
+ if (sts < 0) {
+ fprintf(stderr, "%s: service %s discovery failure: %s\n",
+ pmProgname, spec, pmErrStr(sts));
+ return 2;
+ }
+ if (sts == 0) {
+ if (!quiet)
+ printf("No %s servers discovered\n", spec);
+ return 1;
+ }
+
+ if (!quiet) {
+ printf("Discovered %s servers:\n", spec);
+ for (i = 0; i < sts; ++i)
+ printf(" %s\n", urls[i]);
+ }
+ free(urls);
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ char *service = NULL;
+ int c, sts, total;
+
+ /*
+ * Set up a handler to catch routine signals, to allow for
+ * interruption of the discovery process.
+ */
+ setupSignals();
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'm': /* discovery mechanism */
+ if (strcmp(opts.optarg, "all") == 0)
+ mechanism = NULL;
+ else
+ mechanism = opts.optarg;
+ break;
+ case 'q': /* no stdout messages */
+ quiet = 1;
+ break;
+ case 'r': /* resolve addresses */
+ discoveryFlags |= PM_SERVICE_DISCOVERY_RESOLVE;
+ break;
+ case 's': /* local services */
+ if (strcmp(opts.optarg, "all") == 0)
+ service = NULL;
+ else
+ service = opts.optarg;
+ break;
+ case 't': /* timeout */
+ addOption("timeout", opts.optarg);
+ break;
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.optind != argc)
+ opts.errors++;
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (service)
+ return discovery(service);
+
+ for (c = sts = total = 0; c < sizeof(services)/sizeof(services[0]); c++) {
+ if ((discoveryFlags & PM_SERVICE_DISCOVERY_INTERRUPTED) != 0)
+ break;
+ sts |= discovery(services[c]);
+ total += (sts != 0);
+ }
+
+ /*
+ * Exit status indicates total failure - success indicates
+ * something (any service, any mechanism) was discovered.
+ */
+ return total == sizeof(services)/sizeof(services[0]) ? sts : 0;
+}
diff --git a/src/pmgadgets/GNUmakefile b/src/pmgadgets/GNUmakefile
new file mode 100644
index 0000000..833bff7
--- /dev/null
+++ b/src/pmgadgets/GNUmakefile
@@ -0,0 +1,71 @@
+TOPDIR = ../..
+COMMAND = pmgadgets
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+WRAPPER = $(COMMAND).sh
+QRCFILE = $(COMMAND).qrc
+ICNFILE = $(COMMAND).icns
+XMLFILE = $(COMMAND).info
+HEADERS = pmgadgets.h tokens.h
+SOURCES = $(HEADERS:.h=.cpp) main.cpp parse.cpp
+CONFFILES = $(PROJECT)
+LDIRT = $(COMMAND) $(ICNFILE) $(WRAPPER) $(SCRIPTS) $(XMLFILE) *.yy.c images
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me:: images wrappers
+ $(QTMAKE)
+ $(LNMAKE)
+
+build-me:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+ifeq ($(WINDOW),mac)
+PKG_MAC_DIR = /Library/PCP/$(COMMAND).app/Contents
+PKG_SUB_DIR = $(PKG_MAC_DIR)/MacOS
+wrappers: $(WRAPPER) $(SCRIPTS)
+else
+PKG_SUB_DIR = $(PKG_BIN_DIR)
+wrappers: $(WRAPPER) $(SCRIPTS)
+endif
+
+$(WRAPPER): $(WRAPPER).IN
+ @ $(SED) -e '/\# .*/b' -e 's;PKG_BIN_DIR;$(PKG_SUB_DIR);g' < $< > $@
+
+install: default
+ $(INSTALL) -m 755 -d $(PKG_BIN_DIR)
+ifneq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(BINARY) $(PKG_BIN_DIR)/$(COMMAND)
+endif
+ifeq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(WRAPPER) $(PKG_BIN_DIR)/$(COMMAND)
+ $(call INSTALL_DIRECTORY_HIERARCHY,$(PKG_MAC_DIR),/Library)
+ $(INSTALL) -m 644 $(XMLFILE) $(PKG_MAC_DIR)/Info.plist
+ $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS
+ $(call INSTALL_QT_FRAMEWORKS,$(BINARY))
+ $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND)
+ rm $(BINARY)
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources
+ $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE)
+ $(call INSTALL_QT_RESOURCES,$(PKG_MAC_DIR)/Resources)
+endif
+
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+images: $(ICNFILE)
+ $(LN_S) $(TOPDIR)/images images
+
+$(ICNFILE):
+ $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE)
diff --git a/src/pmgadgets/global.h b/src/pmgadgets/global.h
new file mode 100644
index 0000000..269d22b
--- /dev/null
+++ b/src/pmgadgets/global.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1996-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _GLOBAL_H
+#define _GLOBAL_H
+
+// Global data from resources/command line options
+struct AppData {
+ int zoom; // Zoom factor
+ double delta; // Update interval (seconds)
+ char * defaultFont; // Default font for label gadgets
+};
+
+extern AppData appData; // Global options/resources
+
+#endif /* _GLOBAL_H */
diff --git a/src/pmgadgets/lex.l b/src/pmgadgets/lex.l
new file mode 100644
index 0000000..52047b5
--- /dev/null
+++ b/src/pmgadgets/lex.l
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 1996-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+
+#include <stdlib.h>
+#include <string.h>
+#include "tokens.h"
+
+extern unsigned lineCount;
+
+int dostring();
+
+#ifndef FLEX_SCANNER
+#ifdef output
+#undef output
+#define output(c)
+#endif
+#else
+#define YY_NO_UNPUT
+#endif
+
+%}
+
+%%
+
+[-]?[0-9]+ {
+ /*
+ * bigger than, 999,999,999 (or smaller than
+ * -99,999,999), let's force it to be a real, to
+ avoid overflow issues
+ */
+ if (strlen(yytext) > 9) {
+ tokenRealVal = atof(yytext);
+ return(TOK_REAL);
+ }
+ tokenIntVal = atoi(yytext);
+ return(TOK_INTEGER);
+ }
+[-]?[0-9]+\.[0-9]+ {
+ tokenRealVal = atof(yytext);
+ return(TOK_REAL);
+ }
+
+\( return TOK_LPAREN;
+\) return TOK_RPAREN;
+\[ return TOK_LBRACKET;
+\] return TOK_RBRACKET;
+\: return TOK_COLON;
+
+_line return TOK_LINE;
+_label return TOK_LABEL;
+_bar return TOK_BAR;
+_multibar return TOK_MULTIBAR;
+_bargraph return TOK_BARGRAPH;
+_led return TOK_LED;
+
+_legend return TOK_LEGEND;
+_colour return TOK_COLOUR;
+_color return TOK_COLOUR;
+_colourlist return TOK_COLOURLIST;
+_colorlist return TOK_COLOURLIST;
+_actions return TOK_ACTIONLIST;
+
+_update return TOK_UPDATE;
+_history return TOK_HISTORY;
+_noborder return TOK_NOBORDER;
+_metric return TOK_METRIC;
+_horizontal return TOK_HORIZONTAL;
+_vertical return TOK_VERTICAL;
+_metrics return TOK_METRICS;
+_min return TOK_MIN;
+_mainimum return TOK_MIN;
+_max return TOK_MAX;
+_maximum return TOK_MAX;
+_default return TOK_DEFAULT;
+_fixed return TOK_FIXED;
+
+_[a-zA-Z0-9_.] return TOK_BAD_RES_WORD;
+
+\# {
+ while (input() != '\n')
+ ;
+ nLines++;
+ }
+
+\"[^\"\n][^\"\n]*\" return dostring();
+
+\n nLines++;
+
+[A-Za-z][A-Za-z0-9_.\-]* {
+ tokenStringVal = strdup(yytext);
+ return TOK_IDENTIFIER;
+ }
+
+[0-9\-]+[A-Za-z_.\-]+[A-Za-z0-9.\-]* {
+ tokenStringVal = strdup(yytext);
+ return TOK_IDENTIFIER;
+ }
+
+[ \t]* { }
+%%
+
+int
+yywrap(void)
+{
+ return 1;
+}
+
+int
+dostring(void)
+{
+ size_t szResult;
+ char* result;
+
+ szResult = yyleng - 1; /* ignore 1st '"', 2nd clobbered by '\0' */
+ result = (char*)malloc(szResult);
+ strncpy(result, yytext + 1, szResult - 1);
+ result[szResult - 1] = '\0';
+ tokenStringVal = result;
+ return TOK_STRING;
+}
diff --git a/src/pmgadgets/main.cpp b/src/pmgadgets/main.cpp
new file mode 100644
index 0000000..e4e6f0f
--- /dev/null
+++ b/src/pmgadgets/main.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QApplication>
+#include "pmgadgets.h"
+#include "global.h"
+
+AppData appData; // Global options/resources
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ PmGadgets gadgets;
+ return gadgets.exec();
+}
+
+#if 0
+#include <QDesktopWidget>
+#include <QCursor>
+static char usage[] =
+ "Usage: pmgadgets [options] [message...]\n\n"
+ "Options:\n"
+ " -? | -help display this usage message\n"
+ " -header title set window title\n"
+ " -noslider do not display a text box slider\n"
+ " -noframe do not display a frame around the text box\n";
+
+ char *option;
+ char *filename = NULL;
+ char *defaultname = NULL;
+
+ int errflag = 0;
+ if (errflag) {
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ }
+
+ if (nearmouseflag)
+ grid.move(QCursor::pos());
+ else if (centerflag) {
+ int x = (a.desktop()->screenGeometry().width() / 2) - (q.width() / 2);
+ int y = (a.desktop()->screenGeometry().height() / 2) - (q.height() / 2);
+ grid.move(x > 0 ? x : 0, y > 0 ? y : 0);
+ }
+#endif
diff --git a/src/pmgadgets/parse.cpp b/src/pmgadgets/parse.cpp
new file mode 100644
index 0000000..923baef
--- /dev/null
+++ b/src/pmgadgets/parse.cpp
@@ -0,0 +1,1339 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1996 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include <setjmp.h>
+#include <values.h>
+#include "global.h"
+#include "tokens.h"
+
+#include "qed.h"
+#include "qed_bar.h"
+#include "qed_led.h"
+#include "qed_line.h"
+#include "qed_label.h"
+#include "qed_legend.h"
+#include "qed_gadget.h"
+#include "qed_colorlist.h"
+#include "qed_actionlist.h"
+#include <qnumeric.h>
+
+extern "C" {
+int yylex();
+}
+
+// Variables manipulated by the lexical analyser
+//
+unsigned nLines = 1;
+int tokenIntVal;
+double tokenRealVal;
+char *tokenStringVal;
+
+// Parser variables
+//
+static int token;
+static int eofOK = 1;
+static unsigned nErrors;
+static unsigned nWarnings;
+
+static QList<QedColorList*> colorLists;
+static QList<QedLegend*> legends;
+static QList<QedActionList*> actionLists;
+static QList<QedGadget*> gadgets;
+static QWidget *parent;
+
+class MetricData
+{
+public:
+ MetricData(char* h, char* m, char* i) { host = h; metric = m; inst = i; }
+ char *host; // NULL => localhost
+ char *metric; // metric name
+ char *inst; // instance name (may be NULL)
+};
+
+int AddMetrics(double a, QList<MetricData*>&b, QedGadget *c) { return 0; /* TODO */ }
+
+typedef struct {
+ int id;
+ const char* string;
+} TokenList;
+
+static TokenList tokenStrings[] = {
+ { TOK_LINE, "line" },
+ { TOK_LABEL, "label" },
+ { TOK_BAR, "bar" },
+ { TOK_MULTIBAR, "multibar" },
+ { TOK_BARGRAPH, "bargraph" },
+ { TOK_LED, "led" },
+
+ { TOK_LEGEND, "legend" },
+ { TOK_COLOURLIST, "colourlist" },
+ { TOK_ACTIONLIST, "actionlist" },
+
+ { TOK_BAD_RES_WORD, "<bad reserved word>" },
+ { TOK_UPDATE, "update" },
+ { TOK_METRIC, "metric" },
+ { TOK_HORIZONTAL, "horizontal" },
+ { TOK_VERTICAL, "vertical" },
+ { TOK_METRICS, "metrics" },
+ { TOK_MIN, "min" },
+ { TOK_MAX, "max" },
+ { TOK_DEFAULT, "default" },
+ { TOK_FIXED, "fixed" },
+ { TOK_COLOUR, "colour" },
+ { TOK_HISTORY, "history" },
+ { TOK_NOBORDER, "noborder" },
+
+ { TOK_IDENTIFIER, "<identifier>" },
+ { TOK_INTEGER, "<integer>" },
+ { TOK_REAL, "<real>" },
+ { TOK_STRING, "<string>" },
+ { TOK_LPAREN, "(" },
+ { TOK_RPAREN, ")" },
+ { TOK_LBRACKET, "[" },
+ { TOK_RBRACKET, "]" },
+ { TOK_COLON, ":" },
+
+ { TOK_EOF, "EOF" },
+ { 0, "raw EOF" },
+};
+const unsigned nTokenStrings = sizeof(tokenStrings) / sizeof(tokenStrings[0]);
+
+static const char*
+TokenToString(int tok)
+{
+ for (unsigned i = 0; i < nTokenStrings; i++)
+ if (tokenStrings[i].id == tok)
+ return tokenStrings[i].string;
+ return "???";
+}
+
+static jmp_buf scannerEofEnv;
+static int stashedToken = -1;
+
+static int
+NextToken()
+{
+ static int atEOF = 0;
+
+ if (atEOF)
+ return token;
+
+ if (stashedToken < 0) {
+ if ((token = yylex()) == 0) {
+ atEOF = 1;
+ token = TOK_EOF;
+ if (!eofOK) {
+ nErrors++;
+ pmprintf("Error, line %d: unexpected EOF, giving up\n", nLines);
+ longjmp(scannerEofEnv, 0);
+ }
+ }
+ }
+ else {
+ token = stashedToken;
+ stashedToken = -1;
+ }
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "input: %s (%d)\n", TokenToString(token), token);
+ return token;
+}
+
+static void
+FindNewStatement()
+{
+ eofOK = 1;
+ for(;;)
+ switch (token) {
+ case TOK_EOF:
+ case TOK_LINE:
+ case TOK_LABEL:
+ case TOK_BAR:
+ case TOK_MULTIBAR:
+ case TOK_BARGRAPH:
+ case TOK_LED:
+ case TOK_LEGEND:
+ case TOK_COLOURLIST:
+ return;
+
+ default:
+ NextToken();
+ break;
+ }
+}
+
+static int
+ParseUpdate(double& update, int eofMayFollow)
+{
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_INTEGER)
+ update = tokenIntVal;
+ else if (token == TOK_REAL)
+ update = tokenRealVal;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: update interval must be a number\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParseHistory(int& history, int eofMayFollow)
+{
+ int err = 0;
+
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_INTEGER) {
+ if ((history = tokenIntVal) < 0)
+ err = 1;
+ }
+ else
+ err = 1;
+
+ if (err) {
+ nErrors++;
+ pmprintf("Error, line %d: history must be a positive integer\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParsePosition(int& x, int& y, int eofMayFollow)
+{
+ eofOK = 0;
+ if (token == TOK_INTEGER)
+ x = tokenIntVal * appData.zoom;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: X coordinate must be an integer\n", nLines);
+ return -1;
+ }
+ NextToken();
+ if (token == TOK_INTEGER)
+ y = tokenIntVal * appData.zoom;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: Y coordinate must be an integer\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParseSize(unsigned& w, unsigned& h, int eofMayFollow)
+{
+ eofOK = 0;
+ if (token == TOK_INTEGER && tokenIntVal > 0)
+ w = tokenIntVal * appData.zoom;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: width must be a positive integer\n", nLines);
+ return -1;
+ }
+ NextToken();
+ if (token == TOK_INTEGER && tokenIntVal > 0)
+ h = tokenIntVal * appData.zoom;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: height must be a positive integer\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParseGeometry(int& x, int& y, unsigned& w, unsigned& h, int eofMayFollow)
+{
+ int sts;
+
+ eofOK = 0;
+ if ((sts = ParsePosition(x, y, 0)) < 0)
+ return sts;
+ sts = ParseSize(w, h, eofMayFollow);
+ return sts;
+}
+
+static int
+ParseActionListActions(QedActionList *alp, int eofMayFollow)
+{
+ unsigned int n = 0;
+
+ eofOK = 0;
+ NextToken();
+ while (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ if (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ alp->addName(tokenStringVal);
+ NextToken();
+ }
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: action name expected\n", nLines);
+ return -1;
+ }
+ if (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ alp->addAction(tokenStringVal);
+ NextToken();
+ }
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: action expected\n", nLines);
+ return -1;
+ }
+ if (token == TOK_DEFAULT) {
+ if (alp->defaultPos() == -1)
+ alp->setDefaultPos(n);
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: only one default action permitted in list\n", nLines);
+ }
+ NextToken();
+ }
+ n++;
+ }
+ eofOK = eofMayFollow;
+ if (token == TOK_RPAREN)
+ NextToken();
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: `)' expected at end of action list\n", nLines);
+ return -1;
+ }
+ return 0;
+}
+
+typedef enum {
+ AL_NEED_NAME = 1,
+ AL_MAY_BE_REFERENCE = 2
+} ActionListFlags;
+
+// This parses 3 types of action list:
+//
+// _actions name (...) definition of action list
+// _actions name reference to defined action list
+// _actions (...) anonymous action list
+//
+static int
+ParseActionList(
+ int flags, // AL_* flags above
+ int eofMayFollow)
+{
+ char *name;
+ char anon[64];
+ int ai, sts;
+ int existingActions = -1;
+ QedActionList *alp;
+ static int count;
+
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_IDENTIFIER) {
+ name = tokenStringVal;
+ for (ai = 0; ai < actionLists.size(); ai++) {
+ alp = actionLists.at(ai);
+ if (strcmp(alp->identity(), name) == 0)
+ break;
+ }
+ if (ai != actionLists.size())
+ existingActions = ai;
+ else
+ alp = new QedActionList(name);
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ else {
+ if (flags & AL_NEED_NAME) {
+ nErrors++;
+ pmprintf("Error, line %d: name expected for actions list\n", nLines);
+ return -1;
+ }
+ snprintf(anon, sizeof(anon), "anonymous#%d", count++);
+ alp = new QedActionList(anon);
+ name = anon;
+ }
+
+ sts = 0;
+ if (token == TOK_LPAREN) {
+ // This is an action list definition (the name may be omitted)
+ //
+ eofOK = 0;
+ if (existingActions != -1) {
+ nErrors++;
+ pmprintf("Error, line %d: an actions list named \"%s\" already exists\n",
+ nLines, name);
+ return -1;
+ }
+ sts = ParseActionListActions(alp, eofMayFollow);
+ }
+ else {
+ // This should be a reference to an already defined action list
+ //
+ if (name == NULL) {
+ nErrors++;
+ pmprintf("Error, line %d: name or ( expected for actions list\n", nLines);
+ return -1;
+ }
+ if (!(flags & AL_MAY_BE_REFERENCE)) {
+ nErrors++;
+ pmprintf("Error, line %d: `(' expected for actions list definition\n", nLines);
+ return -1;
+ }
+ if (existingActions == -1) {
+ nErrors++;
+ pmprintf("Error, line %d: no action list named %s defined\n",
+ nLines, name);
+ return -1;
+ }
+ }
+
+ if (sts >= 0 && existingActions == -1)
+ actionLists.append(alp);
+
+ return sts;
+}
+
+static int
+ParseLine(int eofMayFollow)
+{
+ int x, y, sts;
+ unsigned w, h;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParseGeometry(x, y, w, h, eofMayFollow);
+ if (sts < 0)
+ return sts;
+
+ if (token == TOK_UPDATE) {
+ nErrors++;
+ pmprintf("Error, line %d: lines may not have an update interval\n",
+ nLines);
+ return -1;
+ }
+
+ // Parse optional actions list
+ //
+ if (token == TOK_ACTIONLIST) {
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ QedLine *l = new QedLine(parent, x, y, w, h);
+ if (l) {
+ gadgets.append(l);
+ } else {
+ perror("no memory for line gadget");
+ exit(1);
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ l->dump(stderr);
+ return 0;
+}
+
+static int
+ParseLabel(int eofMayFollow)
+{
+ int x, y, sts;
+ char* text;
+ char* font = appData.defaultFont;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParsePosition(x, y, 0);
+ if (sts < 0)
+ return sts;
+
+ if (token == TOK_UPDATE) {
+ nErrors++;
+ pmprintf("Error, line %d: labels may not have an update interval\n",
+ nLines);
+ return -1;
+ }
+
+ if (token != TOK_STRING && token != TOK_IDENTIFIER) {
+ nErrors++;
+ pmprintf("Error, line %d: label string expected\n", nLines);
+ return -1;
+ }
+ text = tokenStringVal;
+ eofOK = eofMayFollow;
+ NextToken();
+ if (token == TOK_STRING || token == TOK_IDENTIFIER) {
+ font = tokenStringVal;
+ NextToken();
+ }
+
+ if (token == TOK_ACTIONLIST)
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+
+ if (sts >= 0) {
+ sts = 0;
+ // note: constructor specifies baseline pos
+ QedLabel* l = new QedLabel(parent, x, y, text, font);
+ if (l) {
+ gadgets.append(l);
+ } else {
+ perror("no memory for label gadget");
+ exit(1);
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ l->dump(stderr);
+ }
+
+ if (text)
+ free(text);
+ if (font && font != appData.defaultFont)
+ free(font);
+ return sts;
+}
+
+// A metric spec is one of
+//
+// <metric>
+// <metric>[<inst>]
+// <host>:<metric>
+// <host>:<metric>[<inst>]
+//
+static int
+ParseMetric(MetricData** resultPtr, int eofMayFollow)
+{
+ char* host;
+ char* metric;
+ char* inst;
+ char* hostOrMetric = NULL;
+
+ eofOK = 0;
+ if (token == TOK_IDENTIFIER)
+ hostOrMetric = tokenStringVal;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: performance metric expected\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ if (token == TOK_COLON) { // Have "<host>:<metric>"
+ host = hostOrMetric;
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_IDENTIFIER)
+ metric = tokenStringVal;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: performance metric expected\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ else { // Optional "<host>:" omitted
+ host = NULL;
+ metric = hostOrMetric;
+ }
+ if (token == TOK_LBRACKET) {
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_IDENTIFIER || token == TOK_STRING)
+ inst = tokenStringVal;
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: performance metric instance expected\n",
+ nLines);
+ return -1;
+ }
+ NextToken();
+ if (token != TOK_RBRACKET) {
+ nErrors++;
+ pmprintf("Error, line %d: `]' for performance metric instance missing\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ else
+ inst = NULL;
+
+ *resultPtr = new MetricData(host, metric, inst);
+ if (*resultPtr == NULL) {
+ perror("No memory for metric spec");
+ exit(1);
+ }
+ return 0;
+}
+
+static int
+ParseColor(char** colourName, int eofMayFollow)
+{
+ eofOK = 0;
+ NextToken();
+ if (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ *colourName = strdup(tokenStringVal);
+ if (*colourName == NULL) {
+ perror("No memory for color name");
+ exit(1);
+ }
+ }
+ else {
+ nErrors++;
+ pmprintf("Error, line %d: color name missing\n", nLines);
+ return -1;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParseBar(int eofMayFollow)
+{
+ int x, y, sts;
+ unsigned w, h;
+ MetricData* metricData;
+ double min = qQNaN(), max = qQNaN();
+ int isVertical = 0;
+ double update = appData.delta;
+ char* colour = NULL;
+ int fixMax = 0;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParseGeometry(x, y, w, h, eofMayFollow);
+ if (sts < 0)
+ return sts;
+
+ if (token == TOK_UPDATE) {
+ sts = ParseUpdate(update, 0);
+ if (sts < 0)
+ return sts;
+ }
+
+ if (token != TOK_METRIC) {
+ nErrors++;
+ pmprintf("Error, line %d: _metric expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ sts = ParseMetric(&metricData, eofMayFollow);
+ if (sts < 0)
+ return sts;
+
+ while (token == TOK_HORIZONTAL ||
+ token == TOK_VERTICAL ||
+ token == TOK_FIXED ||
+ token == TOK_MIN ||
+ token == TOK_MAX) {
+
+ if (token == TOK_FIXED) {
+ eofOK = 0;
+ NextToken();
+ if (token != TOK_MAX) {
+ nErrors++;
+ pmprintf("Error, line %d: _fixed _max expected\n", nLines);
+ return -1;
+ }
+ fixMax = 1;
+ }
+
+ if (token == TOK_HORIZONTAL) {
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ else if (token == TOK_VERTICAL) {
+ eofOK = eofMayFollow;
+ NextToken();
+ isVertical = 1;
+ }
+ else if (token == TOK_MIN) {
+ eofOK = 0;
+ NextToken();
+ if (token != TOK_INTEGER && token != TOK_REAL) {
+ nErrors++;
+ pmprintf("Error, line %d: minimum must be a number\n", nLines);
+ return -1;
+ }
+ min = (token == TOK_INTEGER) ? tokenIntVal : tokenRealVal;
+
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ else if (token == TOK_MAX) {
+ eofOK = 0;
+ NextToken();
+ if (token != TOK_INTEGER && token != TOK_REAL) {
+ nErrors++;
+ pmprintf("Error, line %d: maximum must be a number\n", nLines);
+ return -1;
+ }
+ max = (token == TOK_INTEGER) ? tokenIntVal : tokenRealVal;
+
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+ }
+
+ if (token == TOK_COLOUR) {
+ sts = ParseColor(&colour, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ // Parse optional actions list
+ //
+ if (token == TOK_ACTIONLIST) {
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ QedBar *b = new QedBar(parent, x, y, w, h);
+ QList<MetricData*> mlp;
+ if (b) {
+ if (min != qQNaN())
+ b->setMinimum(min);
+ if (max != qQNaN())
+ b->setMaximum(max);
+ if (isVertical)
+ b->setOrientation(Qt::Vertical);
+ if (fixMax)
+ b->setScaleRange(0);
+ gadgets.append(b);
+ mlp.append(metricData);
+ } else {
+ perror("no memory for bar gadget");
+ exit(1);
+ }
+ if (colour)
+ b->setColor(colour);
+
+ if (AddMetrics(update, mlp, b))
+ nWarnings++;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ b->dump(stderr);
+ return 0;
+}
+
+static int
+ParseMultibar(int eofMayFollow)
+{
+ int x, y, sts, ci;
+ unsigned w, h;
+ MetricData* metricData;
+ int isVertical = 0;
+ int noborder = 0;
+ int history = 0;
+ double update = appData.delta;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParseGeometry(x, y, w, h, eofMayFollow);
+
+ if (token == TOK_UPDATE) {
+ sts = ParseUpdate(update, 0);
+ if (sts < 0)
+ return sts;
+ }
+
+ if (token == TOK_NOBORDER) {
+ noborder = 1;
+ NextToken();
+ }
+
+ if (token == TOK_HISTORY) {
+ sts = ParseHistory(history, 0);
+ if (sts < 0)
+ return sts;
+ }
+
+ if (token != TOK_METRICS) {
+ nErrors++;
+ pmprintf("Error, line %d: _metrics (list) expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ if (token != TOK_LPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `(' expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ QList<MetricData*> mlp;
+ do {
+ sts = ParseMetric(&metricData, 0);
+ if (sts < 0)
+ return sts;
+ mlp.append(metricData);
+ } while (token == TOK_IDENTIFIER);
+
+ if (token != TOK_RPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `)' expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ // By default a multibar has a floating maximum initially set to a small
+ // value. If a max of zero is specified, the multibar is a utilisation.
+ // The optional _fixed keyword before the maximum can be used to specify
+ // clipping.
+ //
+ int clip = 0;
+ double max = DBL_MAX;
+ if (token == TOK_FIXED) {
+ clip = 1;
+ NextToken();
+ if (token != TOK_MAX) {
+ nErrors++;
+ pmprintf("Error, line %d: _maximum expected\n", nLines);
+ return -1;
+ }
+ }
+ if (token == TOK_MAX) {
+ NextToken();
+ if (token != TOK_INTEGER && token != TOK_REAL) {
+ nErrors++;
+ pmprintf("Error, line %d: a maximum must be a number\n", nLines);
+ return -1;
+ }
+ max = (token == TOK_INTEGER) ? tokenIntVal : tokenRealVal;
+ if (max < 0.0 || (clip && max == 0.0)) {
+ nErrors++;
+ pmprintf("Error, line %d: a fixed maximum must be positive\n",
+ nLines);
+ return -1;
+ }
+ NextToken();
+ }
+
+ if (token != TOK_COLOURLIST) {
+ nErrors++;
+ pmprintf("Error, line %d: _colourlist expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ if (token != TOK_IDENTIFIER) {
+ nErrors++;
+ pmprintf("Error, line %d: colourlist name expected\n", nLines);
+ return -1;
+ }
+
+ for (ci = 0; ci < colorLists.size(); ci++) {
+ if (strcmp(colorLists.at(ci)->identity(), tokenStringVal) == 0)
+ break;
+ }
+ if (ci == colorLists.size()) {
+ nErrors++;
+ pmprintf("Error, line %d: no colourlist named %s exists\n",
+ nLines, tokenStringVal);
+ return -1;
+ }
+
+ QedColorList *colorList = colorLists.at(ci);
+ unsigned int nColours = colorList->length();
+ unsigned int nMetrics = mlp.length();
+
+ if (nColours != nMetrics) {
+ nErrors++;
+ pmprintf("Error, line %d: %d colours don't match %d metrics\n",
+ nLines, nColours, nMetrics);
+ return -1;
+ }
+
+ eofOK = eofMayFollow;
+ NextToken();
+
+ while (token == TOK_HORIZONTAL || token == TOK_VERTICAL) {
+ isVertical = (token == TOK_VERTICAL);
+ NextToken();
+ }
+
+ // Parse optional actions list
+ //
+ if (token == TOK_ACTIONLIST) {
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ QedMultiBar* m = new QedMultiBar(parent, x, y, w, h, colorList, history);
+ if (m) {
+ if (isVertical)
+ m->setOrientation(Qt::Vertical);
+ if (max > 0.0)
+ m->setMaximum(max, clip);
+ m->setOutline(!noborder);
+ gadgets.append(m);
+ } else {
+ perror("no memory to allocate multibar gadget");
+ exit(1);
+ }
+
+ if (AddMetrics(update, mlp, m))
+ nWarnings++;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ m->dump(stderr);
+ return 0;
+}
+
+static int
+ParseBarGraph(int eofMayFollow)
+{
+ int x, y, sts;
+ unsigned w, h;
+ MetricData* metricData;
+ double min = qQNaN(), max = qQNaN();
+ double update = appData.delta;
+ int fixMax = 0;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParseGeometry(x, y, w, h, eofMayFollow);
+ if (sts < 0)
+ return sts;
+
+ if (token == TOK_UPDATE) {
+ sts = ParseUpdate(update, 0);
+ if (sts < 0)
+ return sts;
+ }
+
+ if (token != TOK_METRIC) {
+ nErrors++;
+ pmprintf("Error, line %d: _metric expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ // There's only ever one metric for a bargraph, use chunkSize = 1
+ QList<MetricData*> mlp;
+ sts = ParseMetric(&metricData, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ mlp.append(metricData);
+
+ while (token == TOK_MIN || token == TOK_MAX || token == TOK_FIXED) {
+
+ eofOK = 0;
+ if (token == TOK_FIXED) {
+ NextToken();
+ if (token != TOK_MAX) {
+ nErrors++;
+ pmprintf("Error, line %d: _fixed _max expected\n", nLines);
+ return -1;
+ }
+ fixMax = 1;
+ }
+
+ if (token == TOK_MIN) {
+ NextToken();
+ if (token != TOK_INTEGER && token != TOK_REAL) {
+ nErrors++;
+ pmprintf("Error, line %d: minimum must be a number\n", nLines);
+ return -1;
+ }
+ min = (token == TOK_INTEGER) ? tokenIntVal : tokenRealVal;
+ }
+ else {
+ NextToken();
+ if (token != TOK_INTEGER && token != TOK_REAL) {
+ nErrors++;
+ pmprintf("Error, line %d: maximum must be a number\n", nLines);
+ return -1;
+ }
+ max = (token == TOK_INTEGER) ? tokenIntVal : tokenRealVal;
+ }
+ eofOK = eofMayFollow;
+ NextToken();
+ }
+
+ // Parse optional actions list
+ //
+ if (token == TOK_ACTIONLIST) {
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ QedBarGraph* b = new QedBarGraph(parent, x, y, w, h, w - 1);
+ if (b) {
+ if (min != qQNaN())
+ b->setMinimum(min);
+ if (max != qQNaN())
+ b->setMaximum(max);
+ if (fixMax)
+ b->clipRange();
+ gadgets.append(b);
+ } else {
+ perror("no memory for bargraph gadget");
+ exit(1);
+ }
+
+ if (AddMetrics(update, mlp, b))
+ nWarnings++;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ b->dump(stderr);
+ return 0;
+}
+
+static int
+ParseLed(int eofMayFollow)
+{
+ int x, y, sts, li;
+ unsigned w, h;
+ MetricData* metricData;
+ double update = appData.delta;
+
+ eofOK = 0;
+ NextToken();
+
+ sts = ParseGeometry(x, y, w, h, eofMayFollow);
+ if (sts < 0)
+ return sts;
+
+ if (token == TOK_UPDATE) {
+ sts = ParseUpdate(update, 0);
+ if (sts < 0)
+ return sts;
+ }
+
+ if (token != TOK_METRIC) {
+ nErrors++;
+ pmprintf("Error, line %d: _metric expected\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ QList<MetricData*> mlp;
+ sts = ParseMetric(&metricData, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ mlp.append(metricData);
+
+ if (token != TOK_LEGEND) {
+ nErrors++;
+ pmprintf("Error, line %d: _legend expected for _led\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ if (token != TOK_IDENTIFIER) {
+ nErrors++;
+ pmprintf("Error, line %d: legend name expected\n", nLines);
+ return -1;
+ }
+
+ for (li = 0; li < legends.size(); li++) {
+ if (strcmp(legends.at(li)->identity(), tokenStringVal) == 0)
+ break;
+ }
+ if (li == colorLists.size()) {
+ nErrors++;
+ pmprintf("Error, line %d: no legend named %s exists\n",
+ nLines, tokenStringVal);
+ return -1;
+ }
+ QedLegend *legend = legends.at(li);
+
+ eofOK = eofMayFollow;
+ NextToken();
+
+ // Parse optional actions list
+ //
+ if (token == TOK_ACTIONLIST) {
+ sts = ParseActionList(AL_MAY_BE_REFERENCE, eofMayFollow);
+ if (sts < 0)
+ return sts;
+ }
+
+ QedRoundLED* l = new QedRoundLED(parent, x, y, w, h, legend);
+ if (l) {
+ gadgets.append(l);
+ } else {
+ perror("no memory for led gadget");
+ exit(1);
+ }
+
+ if (AddMetrics(update, mlp, l))
+ nWarnings++;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ l->dump(stderr);
+ return 0;
+}
+
+static int
+ParseLegend(int eofMayFollow)
+{
+ eofOK = 0;
+
+ NextToken();
+ if (token != TOK_IDENTIFIER) {
+ nErrors++;
+ pmprintf("Error, line %d: name expected for legend\n", nLines);
+ return -1;
+ }
+ QedLegend* legend = new QedLegend(tokenStringVal);
+ if (legend == NULL) {
+ nErrors++;
+ fprintf(stderr,
+ "Error, line %d: out of memory allocating legend\n",
+ nLines);
+ return -1;
+ }
+ legends.append(legend);
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "legend:\n name \"%s\"\n", tokenStringVal);
+
+ NextToken();
+ if (token != TOK_LPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `(' expected for legend\n", nLines);
+ delete legend;
+ return -1;
+ }
+ NextToken();
+
+ while (token == TOK_INTEGER ||
+ token == TOK_REAL ||
+ token == TOK_DEFAULT) {
+ double val = 0.0;
+ int prevToken;
+ if (token != TOK_DEFAULT)
+ val = (token == TOK_REAL) ? tokenRealVal : tokenIntVal;
+ prevToken = token;
+ NextToken();
+ if (token != TOK_IDENTIFIER && token != TOK_STRING) {
+ nErrors++;
+ pmprintf("Error, line %d: colour name expected in legend\n", nLines);
+ delete legend;
+ return -1;
+ }
+ if (prevToken != TOK_DEFAULT)
+ legend->addThreshold(val, tokenStringVal);
+ else
+ legend->setDefaultColor(tokenStringVal);
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, " %.2f = %s\n", val, tokenStringVal);
+ NextToken();
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ putc('\n', stderr);
+
+ if (token != TOK_RPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `)' or threshold expected for legend\n",
+ nLines);
+ delete legend;
+ return -1;
+ }
+
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+static int
+ParseColorList(int eofMayFollow)
+{
+ eofOK = 0;
+ NextToken();
+ if (token != TOK_IDENTIFIER) {
+ nErrors++;
+ pmprintf("Error, line %d: name expected for colourlist\n", nLines);
+ return -1;
+ }
+ char *name = tokenStringVal;
+ NextToken();
+
+ if (token != TOK_LPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `(' expected for colourlist\n", nLines);
+ return -1;
+ }
+ NextToken();
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "colourlist: name \"%s\"\n", name);
+
+ QedColorList *colorList = new QedColorList(name);
+ if (colorList == NULL) {
+ nErrors++;
+ fprintf(stderr,
+ "Error, line %d: out of memory allocating colorList\n",
+ nLines);
+ return -1;
+ }
+ while (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ colorList->addColor(tokenStringVal);
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, " %s\n", tokenStringVal);
+ NextToken();
+ }
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ putc('\n', stderr);
+
+ if (token != TOK_RPAREN) {
+ nErrors++;
+ pmprintf("Error, line %d: `)' expected for colourlist\n", nLines);
+ return -1;
+ }
+ colorLists.append(colorList);
+ eofOK = eofMayFollow;
+ NextToken();
+ return 0;
+}
+
+// Config files are typically created by programs rather than specified by the
+// user. In the config file header, after the version number, such programs
+// should put their argv so that the desktop can restart pmgadgets
+// (indirectly) by re-running the program that created the config file.
+//
+QStringList configArgv;
+
+void ParseVersion()
+{
+ if ((token == TOK_IDENTIFIER || token == TOK_STRING) &&
+ strcmp(tokenStringVal, "pmgadgets") == 0) {
+ eofOK = 0;
+ NextToken();
+ if (token != TOK_INTEGER) {
+ pmprintf("Error, line %d: bad pmgadgets config file version\n",
+ nLines);
+ pmflush();
+ exit(1);
+ }
+ if (tokenIntVal > 1) {
+ pmprintf("Error, line %d: you need a newer version pmgadgets to run this\n",
+ nLines);
+ pmflush();
+ exit(1);
+ }
+ else if (tokenIntVal < 1) {
+ pmprintf("Error, line %d: ludicrous pmgadgets config file version\n",
+ nLines);
+ pmflush();
+ exit(1);
+ }
+ eofOK = 1;
+ NextToken();
+ while (token == TOK_IDENTIFIER || token == TOK_STRING) {
+ configArgv.append(strdup(tokenStringVal));
+ NextToken();
+ }
+ }
+}
+
+int
+Parse(void)
+{
+ int sts;
+ NextToken();
+ setjmp(scannerEofEnv); // come back here on unexpected EOF
+
+ ParseVersion();
+ while (token != TOK_EOF) {
+ eofOK = 0;
+ switch (token) {
+ case TOK_LINE:
+ sts = ParseLine(1);
+ break;
+
+ case TOK_LABEL:
+ sts = ParseLabel(1);
+ break;
+
+ case TOK_BAR:
+ sts = ParseBar(1);
+ break;
+
+ case TOK_MULTIBAR:
+ sts = ParseMultibar(1);
+ break;
+
+ case TOK_BARGRAPH:
+ sts = ParseBarGraph(1);
+ break;
+
+ case TOK_LED:
+ sts = ParseLed(1);
+ break;
+
+ case TOK_LEGEND:
+ sts = ParseLegend(1);
+ break;
+
+ case TOK_COLOURLIST:
+ sts = ParseColorList(1);
+ break;
+
+ case TOK_ACTIONLIST:
+ sts = ParseActionList(AL_NEED_NAME, 1);
+ break;
+
+ default:
+ sts = -1;
+ nErrors++;
+ pmprintf("Error, line %u: Bad gadget type\n", nLines);
+ break;
+ }
+ if (sts == -1) {
+ eofOK = 1;
+ FindNewStatement();
+ }
+ const unsigned maxErrors = 10;
+ if (nErrors > maxErrors) {
+ pmprintf("Too many errors, giving up!\n");
+ break;
+ }
+ }
+ if (nErrors) {
+ pmprintf("%s: configuration file has errors "
+ "(%d lines parsed, %d errors)\n", pmProgname, nLines, nErrors);
+ pmflush();
+ return -1;
+ }
+ if (nWarnings)
+ pmflush();
+ return 0;
+}
diff --git a/src/pmgadgets/pmgadgets-args.sh b/src/pmgadgets/pmgadgets-args.sh
new file mode 100644
index 0000000..78c6fbc
--- /dev/null
+++ b/src/pmgadgets/pmgadgets-args.sh
@@ -0,0 +1,381 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1996-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+#
+# Note on error handling
+#
+# We assume this is sourced from a shell script that uses $status to pass
+# the exit status back to the parent.
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+prog=`basename $0`
+
+#
+# Standard usage and command-line argument parsing for pmgadgets front ends.
+# This file should be included by pmgadgets front end scripts to present a
+# consistent interface. See pmgadgets(1) for more information.
+#
+
+#
+# The front end scripts should call _pmgadgets_usage after their own usage
+# information in a subroutine called _usage. The _usage subroutine may be
+# called by either _pmgadgets_note or _pmgadgets_args.
+#
+
+_pmgadgets_usage()
+{
+ echo '
+ -C check configuration file and exit
+ -h host metrics source is PMCD on host
+ -n pmnsfile use an alternative PMNS
+ -t interval sample interval [default 2.0 seconds]
+ -z set reporting timezone to local time of metrics source
+ -Z timezone set reporting timezone
+
+ -zoom factor make the gadgets bigger by a factor of 1, 2, 3 or 4
+ -infofont fontname use fontname for text in info dialogs
+ -defaultfont fontname use fontname for label gadgets
+
+ -display display-string
+ -geometry geometry-string
+ -name name-string
+ -title title-string'
+}
+
+#
+# One of the first actions of a front end script should be to call
+# _pmgadgets_args. It sets the following variables:
+#
+# host the host specified with -h.
+# interval the update interval specified by the user, 0 indicates it
+# was not specified. The caller must pass this on to pmgadgets,
+# unlike $host which is included in $args.
+# args The list of args that pmgadgets will comprehend and use.
+# otherArgs The arguments pmgadgets will not understand and should be
+# handled by the front end script.
+# titleArg The title the user prefers. If empty, the title should be
+# provided by the front end script.
+# prog The name of the program.
+# namespace The namespace (including the flag) if specified, else empty
+# eg "-n foo"
+# msource The metrics source, whether live or an archive, include the
+# flag. eg "-h blah"
+#
+
+_pmgadgets_args()
+{
+
+host=""
+args=""
+otherArgs=""
+titleArg=""
+namespace=""
+interval=0
+msource=""
+
+if [ $# -eq 1 -a '$1' = '-\?' ]
+then
+ _usage
+ status=0
+ exit
+fi
+
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+ -g*|-d*|-fg|-bg|-name)
+ # assume an X11 argument
+ if [ $# -lt 2 ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: X-11 option $1 requires one argument"
+ #NOTREACHED
+ fi
+ args="$args $1 '$2'"
+ shift
+ ;;
+
+ -title)
+ # assume an X11 argument
+ if [ $# -lt 2 ]
+ then
+ echo "$prog: $1 requires one argument"
+ _pmgadgets_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ titleArg="$2"
+ shift
+ ;;
+
+ -D|-Z|-delta|-zoom|-infofont|-defaultfont)
+ if [ $# -lt 2 ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ args="$args $1 '$2'"
+ shift
+ ;;
+
+ -D*|-Z*|-z|-C)
+ args="$args $1"
+ ;;
+
+ -h)
+ if [ "X$host" != X ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: Only one -h option may be specified"
+ #NOTREACHED
+ fi
+ if [ $# -lt 2 ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ host=$2
+ args="$args -h $2"
+ msource="-h $host"
+ shift
+ ;;
+
+ -n)
+ if [ $# -lt 2 ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ namespace="-n $2"
+ args="$args -n $2"
+ shift
+ ;;
+
+ -t)
+ if [ $# -lt 2 ]
+ then
+ _pmgadgets_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ interval=$2
+ shift
+ ;;
+
+ *)
+ otherArgs="$otherArgs $1"
+ ;;
+
+ esac
+ shift
+done
+
+if [ -z "$host" ]
+then
+ host=`pmhostname`
+ msource="-h $host"
+fi
+}
+
+# standard fatal error reporting
+# Usage: _pmgadgets_error message goes in here
+# _pmgadgets_error -f file
+#
+_pmgadgets_error()
+{
+ _pmgadgets_note Error error $*
+}
+
+# standard warning
+# Usage: _pmgadgets_warning message goes in here
+# _pmgadgets_warning -f file
+#
+_pmgadgets_warning()
+{
+ _pmgadgets_note Warning warning $*
+}
+
+# standard info
+# Usage: _pmgadgets_info message goes in here
+# _pmgadgets_info -f file
+#
+_pmgadgets_info()
+{
+ _pmgadgets_note Info info $*
+}
+
+# generic notifier
+# Usage: _pmgadgets_note tag icon args ...
+#
+_pmgadgets_note()
+{
+ tag=$1; shift
+ icon=$1; shift
+ button=""
+ [ $tag = Error ] && button="-B Quit"
+ [ $tag = Usage-Error ] && button="-B Quit -b Usage"
+
+ [ X"$PCP_STDERR" = XDISPLAY -a -z "$DISPLAY" ] && unset PCP_STDERR
+
+ if [ $# -eq 2 -a "X$1" = X-f ]
+ then
+ case "$PCP_STDERR"
+ in
+ DISPLAY)
+ ans=`$PCP_XCONFIRM_PROG -icon $icon -file $2 -useslider -header "$tag $prog" $button 2>&1`
+ ;;
+ '')
+ echo "$tag:" >&2
+ cat $2 >&2
+ ans=Quit
+ ;;
+ *)
+ echo "$tag:" >>$PCP_STDERR
+ cat $2 >>$PCP_STDERR
+ ans=Quit
+ ;;
+ esac
+ else
+ case "$PCP_STDERR"
+ in
+ DISPLAY)
+ ans=`$PCP_XCONFIRM_PROG -icon $icon -t "$*" -noframe -header "$tag $prog" $button 2>&1`
+ ;;
+ '')
+ echo "$tag: $*" >&2
+ ans=Quit
+ ;;
+ *)
+ echo "$tag: $*" >>$PCP_STDERR
+ ans=Quit
+ ;;
+ esac
+ fi
+
+ if [ $tag = Usage-Error ]
+ then
+ [ $ans = Usage ] && _usage
+ tag=Error
+ fi
+
+ if [ $tag = Error ]
+ then
+ status=1
+ exit
+ fi
+}
+
+# Fetch metrics
+#
+# $1 - pmprobe flag
+# $2 - metric name
+# number - number of values
+# $tmp/pmgadgets_result - values
+#
+_pmgadgets_fetch()
+{
+ if pmprobe $namespace $msource $1 $2 > $tmp/pmgadgets_fetch 2>&1
+ then
+ tr < $tmp/pmgadgets_fetch ' ' '\012' \
+ | tee $tmp/pmgadgets_output \
+ | sed \
+ -e 's/"//g' \
+ -e '1,2d' \
+ > $tmp/pmgadgets_result
+
+ number=`sed -n -e 2p < $tmp/pmgadgets_output`
+ [ $number -le 0 ] && return 1
+ else
+ number=0
+ rm -f $tmp/pmgadgets_output $tmp/pmgadgets_result
+ return 1
+ fi
+
+ return 0
+}
+
+# Fetch the metric values
+#
+# $1 - metric name
+# number - number of values
+# $tmp/pmgadgets_result - values
+#
+_pmgadgets_fetch_values()
+{
+ _pmgadgets_fetch -v $1
+ return $?
+}
+
+# Fetch the metric instance list
+#
+# $1 - metric name
+# number - number of instances
+# $tmp/pmgadgets_result - instances
+#
+_pmgadgets_fetch_indom()
+{
+ _pmgadgets_fetch -I $1
+ return $?
+}
+
+# Convert pmprobe/pminfo error message into something useful and
+# consistent
+#
+_pmgadgets_fetch_mesg()
+{
+ $PCP_AWK_PROG '
+$1 == "pmprobe:" { $1 = "'$prog':"; print; exit }
+$1 == "pminfo:" { $1 = "'$prog':"; print; exit }
+$1 == "Error:" { $1 = ":";
+ printf("%s: %s%s\n", "'$prog'", metric, $0); exit }
+$1 == "inst" { exit }
+NF == 1 { metric = $1; next }
+NF == 0 { next }
+NF == 2 && $2 == "0" { printf("%s: %s: No values available\n", "'$prog'", $1); exit}
+ { $2 = ":"; print "'$prog': " $0; exit}' \
+ | sed "s/ : /: /" \
+ | fmt
+}
+
+# Generate error metric for failed fetch
+#
+_pmgadgets_fetch_fail()
+{
+ cat $tmp/pmgadgets_fetch | _pmgadgets_fetch_mesg >> $tmp/msg
+ echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp/msg
+ _pmgadgets_error -f $tmp/msg
+ # NOTREACHED
+}
+
+# Generate warning message for failed fetch
+#
+_pmgadgets_fetch_warn()
+{
+ cat $tmp/pmgadgets_fetch | _pmgadgets_fetch_mesg >> $tmp/msg
+ echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp/msg
+ _pmgadgets_warning -f $tmp/msg
+}
+
+# Check that $OPTARG for option $1 is a positive integer
+# ...note the creative use of unary - to prevent leading signs
+#
+_pmgadgets_unsigned()
+{
+ if [ "X-$OPTARG" != "X`expr 0 + -$OPTARG 2>/dev/null`" ]
+ then
+ _pmgadgets_error "$prog: -$1 option must have a positive integral argument"
+ # NOTREACHED
+ fi
+}
diff --git a/src/pmgadgets/pmgadgets.cpp b/src/pmgadgets/pmgadgets.cpp
new file mode 100644
index 0000000..f0a1977
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QtGui>
+#include "pmgadgets.h"
+#include "qed_led.h"
+#include "qed_line.h"
+
+PmGadgets::PmGadgets(QWidget *parent) : QDialog(parent)
+{
+ QedRoundLED *redLED = new QedRoundLED(this, Qt::red);
+ QedSquareLED *blueLED = new QedSquareLED(this, Qt::blue);
+ QedRoundLED *greenLED = new QedRoundLED(this, Qt::green);
+ QedSquareLED *grayLED = new QedSquareLED(this, Qt::darkGray);
+ QedLine *horizontal = new QedLine(this, 2, Qt::Horizontal);
+
+ my.mainLayout = new QGridLayout;
+ my.mainLayout->addWidget(redLED, 0, 0);
+ my.mainLayout->addWidget(blueLED, 0, 1);
+ my.mainLayout->addWidget(greenLED, 0, 2);
+ my.mainLayout->addWidget(grayLED, 1, 0);
+ my.mainLayout->addWidget(horizontal, 1, 1);
+
+ setLayout(my.mainLayout);
+ setWindowsFlags();
+}
+
+void PmGadgets::setWindowsFlags(void)
+{
+ Qt::WindowFlags flags = windowFlags();
+ // removal
+ flags &= ~Qt::WindowMinimizeButtonHint;
+ flags &= ~Qt::WindowCloseButtonHint;
+ // addition
+ flags |= Qt::WindowStaysOnTopHint;
+ //flags |= Qt::FramelessWindowHint;
+ setWindowFlags(flags);
+}
+
+void PmGadgets::help()
+{
+ QMessageBox::information(this, tr("PmGadgets Help"),
+ tr("Performance Co-Pilot graphical gadgets and great gizmos."));
+}
diff --git a/src/pmgadgets/pmgadgets.h b/src/pmgadgets/pmgadgets.h
new file mode 100644
index 0000000..8ecc3f9
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMGADGETS_H
+#define PMGADGETS_H
+
+#include <QGridLayout>
+#include <QDialog>
+#include <QList>
+
+class PmGadgets : public QDialog
+{
+ Q_OBJECT
+
+public:
+ PmGadgets(QWidget *parent = 0);
+
+private slots:
+ void help();
+
+private:
+ void setWindowsFlags();
+
+ struct {
+ QList<QWidget *> widgets;
+ QGridLayout *mainLayout;
+ } my;
+};
+
+#endif // PMGADGETS_H
diff --git a/src/pmgadgets/pmgadgets.info.in b/src/pmgadgets/pmgadgets.info.in
new file mode 100644
index 0000000..5ace93b
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.info.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>pmgadgets.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>@pkg_version@</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>pmgadgets</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.redhat.pmgadgets</string>
+</dict>
+</plist>
diff --git a/src/pmgadgets/pmgadgets.pro b/src/pmgadgets/pmgadgets.pro
new file mode 100644
index 0000000..72c53b1
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.pro
@@ -0,0 +1,23 @@
+TEMPLATE = app
+LANGUAGE = C++
+HEADERS = pmgadgets.h tokens.h
+SOURCES = pmgadgets.cpp main.cpp parse.cpp
+FLEXSOURCES = lex.l
+ICON = pmgadgets.icns
+RESOURCES = pmgadgets.qrc
+CONFIG += qt warn_on
+INCLUDEPATH += ../include ../libpcp_qed/src
+LIBS += -L../libpcp_qed/src -L../libpcp_qmc/src/$$DESTDIR
+LIBS += -lpcp -lpcp_qed
+win32:LIBS += -lwsock32
+QT += network
+QMAKE_INFO_PLIST = pmgadgets.info
+QMAKE_EXTRA_COMPILERS += flex
+QMAKE_CXXFLAGS += $$(PCP_CFLAGS)
+
+flex.commands = flex ${QMAKE_FILE_IN}
+flex.input = FLEXSOURCES
+flex.output = lex.yy.c
+flex.variable_out = SOURCES
+flex.depends = tokens.h
+flex.name = flex
diff --git a/src/pmgadgets/pmgadgets.qrc b/src/pmgadgets/pmgadgets.qrc
new file mode 100644
index 0000000..3e34d21
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.qrc
@@ -0,0 +1,6 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/aboutpcp.png</file>
+ <file>images/pmtime.png</file>
+</qresource>
+</RCC>
diff --git a/src/pmgadgets/pmgadgets.sh.IN b/src/pmgadgets/pmgadgets.sh.IN
new file mode 100644
index 0000000..1e02c63
--- /dev/null
+++ b/src/pmgadgets/pmgadgets.sh.IN
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PKG_MAC_DIR/MacOS/pmgadgets "$@"
diff --git a/src/pmgadgets/pmgcisco.sh b/src/pmgadgets/pmgcisco.sh
new file mode 100755
index 0000000..8d57d80
--- /dev/null
+++ b/src/pmgadgets/pmgcisco.sh
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -fr $tmp; exit \$status" 0 1 2 3 15
+
+# usage - print out the usage of program
+#
+_usage()
+{
+ echo >$tmp/msg "Usage: $prog [options] [pmgadgets options]"
+ echo >>$tmp/msg '
+options:
+ -t interval sample interval [default 120.0 seconds]
+ -V verbose/diagnostic output
+ -x pixels width of each bar graph [default 31]
+ -y pixels height of each bar graph [default 31]
+
+pmgadgets(1) options:'
+ _pmgadgets_usage >>$tmp/msg
+ echo >>$tmp/msg
+ _pmgadgets_info -f $tmp/msg
+}
+
+# default variables
+#
+verbose=false
+plot_y=2
+bar_y=31
+bar_x=31
+
+# keep the original command line for restart after logout/login
+#
+echo -n "pmgadgets 1 \"pmgcisco\"" >$tmp/conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp/conf
+done
+echo "" >> $tmp/conf
+
+# get arguments
+#
+. $PCP_SHARE_DIR/lib/pmgadgets-args
+
+_pmgadgets_args "$@"
+[ "$interval" = 0 ] && interval=120sec
+args="$args -t $interval"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "Vx:y:?" c $otherArgs
+ do
+ case $c
+ in
+ V)
+ verbose=true
+ ;;
+ x) # -x pixels (width of bar graph)
+ bar_x=$OPTARG
+ ;;
+ y) # -y pixels (height of bar graph)
+ bar_y=$OPTARG
+ ;;
+ ?)
+ _usage
+ status=1
+ exit
+ ;;
+ esac
+ done
+
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+
+ if [ $# -gt 0 ]
+ then
+ _usage
+ status=1
+ exit
+ fi
+fi
+
+# check on metric availability
+#
+if _pmgadgets_fetch_values cisco.bandwidth
+then
+ if [ ! -s $tmp/pmgadgets_result -o "$number" -lt 1 ]
+ then
+ _pmgadgets_fetch_fail "get Cisco metrics"
+ #NOTREACHED
+ fi
+else
+ _pmgadgets_fetch_fail "get Cisco metrics"
+ #NOTREACHED
+fi
+
+# output the config file
+#
+pminfo $msource -f cisco.bandwidth \
+| sed -n \
+ -e 's/:/ /' \
+ -e '/ value /{
+s/"] value / /
+s/.*"//
+p
+}' \
+| sort >$tmp/data
+
+# $tmp/data has lines of the form <hostname> <interface> <bandwidth>
+# try to truncate the <hostname> by dropping components from the
+# right hand end, but retain uniqueness ... the truncated name is used
+# for the label gadget
+#
+sed -e 's/ .*//' <$tmp/data | sort -u >$tmp/hostnames
+trim=1
+rm -f $tmp/done
+while true
+do
+ $PCP_AWK_PROG -F . <$tmp/hostnames >$tmp/tmp '
+NR == 1 { last_part=NF-'$trim'+1
+ if (last_part < 2) { fail=1; exit }
+ want=$last_part
+ }
+NF >= last_part { if ($last_part != want) { fail=2; exit }
+ next
+ }
+ { fail=3; exit }
+END { # print "fail=" fail
+ if (fail) print "" >"'$tmp/done'"
+ }'
+ [ -f $tmp/done ] && break
+ trim=`expr $trim + 1`
+done
+
+$PCP_AWK_PROG -F . <$tmp/hostnames >$tmp/map '
+ { printf "%s",$0
+ for (i=1; i<=NF-'$trim'+1; i++) {
+ if (i == 1) printf "\t%s",$i
+ else printf ".%s",$i
+ }
+ print ""
+ }'
+
+# this produces <hostname> <shortname> <interface> <bandwidth>
+#
+join $tmp/map $tmp/data \
+| $PCP_AWK_PROG >>$tmp/conf '
+
+BEGIN { y = 15; step_y = '$bar_y'+31; step_x = '$bar_x'+16 }
+ { if (!seen[$4]) {
+ print "_legend link" $4 " ("
+ print " _default \"#608080\""
+ print " " int(0.10*$4) " green"
+ print " " int(0.70*$4) " yellow"
+ print " " int(0.80*$4) " orange"
+ print " " int(0.90*$4) " red"
+ print ")"
+ print ""
+ seen[$4] = 1
+ }
+
+ print "_label",5,y,"\"" $2 ":" $3 "\" \"7x13bold\""
+
+ print "_label",5,y+11,"\"in\" \"7x13\""
+ print "_led",5+'$bar_x'-6,y+4,8,8
+ print "_metric cisco.bytes_in[\"" $1 ":" $3 "\"]"
+ print "_legend link" $4
+
+ print "_bargraph",5,y+14,'$bar_x,$bar_y'
+ print "_metric cisco.bytes_in[\"" $1 ":" $3 "\"]"
+ print "_max " $4
+
+ print "_label",step_x+5,y+11,"\"out\" \"7x13\""
+ print "_led",step_x+5+'$bar_x'-6,y+4,8,8
+ print "_metric cisco.bytes_out[\"" $1 ":" $3 "\"]"
+ print "_legend link" $4
+
+ print "_bargraph",step_x+5,y+14,'$bar_x,$bar_y'
+ print "_metric cisco.bytes_out[\"" $1 ":" $3 "\"]"
+ print "_max " $4
+
+ print ""
+ y += step_y
+ }
+END { value='"`echo $interval | sed -e 's/[^0-9.].*/; scale=\"&\"/' | tr A-Z a-z`"'
+ # tt is total time for plot in seconds, each bar is 1 pixel wide
+ tt = value * ('$bar_x' - 1)
+ if (scale ~ /^min/ || scale == "m") tt *= 60
+ else if (scale ~ /^hour/ || scale == "h") tt *= 3600
+ else if (scale ~ /^day/ || scale == "d") tt *= 24*3600
+ printf "_label 5 %d \"History: ",y
+ if (tt > 24*3600) {
+ xxx = int(tt/24*3600)
+ printf "%dday",xxx
+ if (xxx > 1) printf "s"
+ rem = int(0.5+(tt%(24*3600)/3600))
+ if (rem > 1)
+ printf " %dhours",rem
+ else if (rem == 1)
+ printf " 1hour"
+ }
+ else if (tt > 3600) {
+ xxx = int(tt/3600)
+ printf "%dhour",xxx
+ if (xxx > 1) printf "s"
+ rem = int(0.5+(tt%(3600)/60))
+ if (rem > 1)
+ printf " %dmin",rem
+ else if (rem == 1)
+ printf " 1min"
+ }
+ else if (tt > 60) {
+ xxx = int(tt/60)
+ printf "%dmin",xxx
+ if (xxx > 1) printf "s"
+ rem = int(0.5+tt%60)
+ if (rem > 1)
+ printf " %dsecs",rem
+ else if (rem == 1)
+ printf " 1sec"
+ }
+ else {
+ if (tt > 1)
+ printf "%dsecs",tt
+ else if (tt == 1)
+ printf "1sec"
+ }
+ print "\""
+ }'
+
+$verbose && cat $tmp/conf
+eval pmgadgets <$tmp/conf $args
+
+status=$?
+exit
diff --git a/src/pmgadgets/pmgcluster.sh b/src/pmgadgets/pmgcluster.sh
new file mode 100755
index 0000000..ddd2229
--- /dev/null
+++ b/src/pmgadgets/pmgcluster.sh
@@ -0,0 +1,281 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# This script uses pmgsys to layout gadgets for a series of hosts and then
+# combines the resulting config files into one big one and invokes pmgadgets
+# on the result.
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -fr $tmp; exit \$status" 0 1 2 3 15
+verbose="false"
+prog=`basename $0`
+nhosts=0
+hosts=""
+
+# usage - print out the usage of program
+#
+_usage()
+{
+ echo >$tmp/msg "Usage: $prog [options] [pmgadgets options] [host ...]"
+ echo >>$tmp/msg '
+
+Default hosts are specified in /etc/nodes (or /etc/ace/nodes).
+
+options:
+ -H nodes file specifying nodes in cluster
+ [default $PCP_CLUSTER_CONFIG or /etc/nodes]
+ -r rows number of rows for layout
+ -l suppress host name labels
+ -L label title label for pmgagdets layout
+ -V verbose (print pmgadgets configuration)
+
+pmgadgets(1) options:'
+ _pmgadgets_usage | sed -e '/.*-h host.*/d' >>$tmp/msg
+ _pmgadgets_info -f $tmp/msg
+}
+
+# keep the original command line for restart after logout/login
+#
+echo -n "pmgadgets 1 \"pmgcluster\"" >$tmp/conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp/conf
+done
+echo >>$tmp/conf
+
+# get arguments
+#
+. $PCP_SHARE_DIR/lib/pmgadgets-args
+
+
+# Have to pre-parse the options for -L coz getopts can't handle multiword
+# strings when using $otherArgs
+#
+if [ -z "$PCP_CLUSTER_CONFIG" ]
+then
+ nodesfile=/etc/nodes
+ [ ! -f "$nodesfile" ] && nodesfile=/etc/ace/nodes
+else
+ nodesfile="$PCP_CLUSTER_CONFIG"
+ if [ ! -f "$nodesfile" ]
+ then
+ echo "Error: \"$nodesfile\" specified in \$PCP_CLUSTER_CONFIG: file not found"
+ _usage
+ status=1
+ exit
+ fi
+fi
+
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+ -h) hosts="$2"
+ nhosts=1
+ shift
+ ;;
+ -L) titlelabel="$2"
+ shift
+ ;;
+ *) pmgargs="$pmgargs $1"
+ ;;
+ esac
+ shift
+done
+set -- $pmgargs
+
+_pmgadgets_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "H:r:lV?" c $otherArgs
+ do
+ case $c
+ in
+ H) nodesfile=$OPTARG
+ if [ ! -f "$nodesfile" ]
+ then
+ echo "$prog Error: \"$nodesfile\" for -H file not found"
+ _usage
+ status=1
+ exit
+ fi
+ ;;
+ r) rows=$OPTARG
+ ;;
+ l) pmgsysargs="$pmgsysargs -l"
+ ;;
+ V) verbose="true"
+ ;;
+ ?) _usage
+ status=1
+ exit
+ ;;
+ esac
+ done
+
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+else
+ set --
+fi
+
+if [ "$interval" != "0" ]
+then
+ args="$args -t $interval"
+fi
+
+if [ ! -z "$titleArg" ]
+then
+ args="$args -title $titleArg"
+fi
+
+ytitle=0
+if [ ! -z "$titlelabel" ]
+then
+ ytitle=16
+ echo "_label 6 13 \"$titlelabel\" \"7x13bold\"" >> $tmp/conf
+fi
+
+if [ "$nhosts" -eq 0 ]
+then
+ nhosts=$#
+ if [ $nhosts -eq 0 ]
+ then
+ if [ -f "$nodesfile" ]
+ then
+ hosts=`sed -e 's/[# ].*$//' $nodesfile`
+ nhosts=`echo $hosts | wc -w`
+ fi
+ else
+ hosts=$*
+ fi
+fi
+
+if [ $nhosts -eq 0 ]
+then
+ _usage
+ status=1
+ exit
+fi
+
+
+ox=0
+oy=$ytitle
+row=0
+col=0
+ygap=20
+xgap=50
+xmax=0
+ymax=0
+hostnum=0
+
+if [ -z "$rows" ]
+then
+ rows=`echo "sqrt($nhosts)" | bc`
+fi
+
+cols=`expr $nhosts / $rows`
+
+for host in $hosts
+do
+ if ! pmgsys $pmgsysargs -C -V -h $host > $tmp/pmgsys 2> $tmp/pmgsys.err
+ then
+ sed -e "s/pmgsys/$prog/g" $tmp/pmgsys.err >&2
+ continue
+ fi
+
+ sed -e 's/^pmgadgets/# pmgadgets/' \
+ -e "/.*_update.*/d" \
+ -e "s/-C//g" \
+ -e "s/cpuActions/"$hostnum"_cpuActions/g" \
+ -e "s/loadActions/"$hostnum"_loadActions/g" \
+ -e "s/netActions/"$hostnum"_netActions/g" \
+ -e "s/diskActions/"$hostnum"_diskActions/g" \
+ -e "s/diskLegend/"$hostnum"_diskLegend/g" \
+ -e "s/cpuColours/"$hostnum"_cpuColours/g" \
+ -e "s/netColours/"$hostnum"_netColours/g" \
+ -e "s/kernel\./"$host":kernel./g" \
+ -e "s/disk\./"$host":disk./g" \
+ -e "s/mem\./"$host":mem./g" \
+ -e "s/swap\./"$host":swap./g" \
+ -e "s/network\./"$host":network./g" \
+ $tmp/pmgsys \
+ | $PCP_AWK_PROG '
+ /^_/ && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ {
+ printf "%s %d %d ", $1, $2 + '$ox', $3 + '$oy'
+ for (i=4; i <= NF; i++)
+ printf "%s ", $i
+ printf "\n"
+ next
+ }
+ {
+ print
+ }' | tee $tmp/$host >> $tmp/conf
+
+ #
+ # set xmax and ymax for this host
+ #
+ eval `$PCP_AWK_PROG '
+ /^_/ && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ {
+ if (xmax < $2)
+ xmax = $2
+ if (ymax < $3)
+ ymax = $3
+ }
+ END {
+ printf "host_xmax=%d; ymax=%d", xmax, ymax
+ }' $tmp/$host`
+
+ [ $host_xmax -gt $xmax ] && xmax=$host_xmax
+
+ row=`expr $row + 1`
+ if [ $row -eq $rows ]
+ then
+ row=0
+ oy=$ytitle
+ ox=`expr $xmax + $xgap`
+ col=`expr $col + 1`
+ else
+ oy=`expr $ymax + $ygap`
+ fi
+
+ rm -f $tmp/$host
+ hostnum=`expr $hostnum + 1`
+done
+
+rm -f $tmp/pmgsys $tmp/pmgsys.err
+
+if $verbose
+then
+ cat $tmp/conf
+fi
+
+if [ $hostnum -le 0 ]
+then
+ echo "$prog: unable to monitor any hosts" >&2
+ status=1
+ exit
+fi
+
+eval pmgadgets $args <$tmp/conf
+
+status=$?
+exit
diff --git a/src/pmgadgets/pmgshping.sh b/src/pmgadgets/pmgshping.sh
new file mode 100755
index 0000000..455b979
--- /dev/null
+++ b/src/pmgadgets/pmgshping.sh
@@ -0,0 +1,242 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -fr $tmp; exit \$status" 0 1 2 3 15
+
+# usage - print out the usage of program
+#
+_usage()
+{
+ echo >$tmp/msg "Usage: $prog [options] [pmgadgets options]"
+ echo >>$tmp/msg '
+options:
+ -e use espping metrics, instead of shping
+ -G gap set distance between columns (pixels)
+ -V verbose/diagnostic output
+
+pmgadgets(1) options:'
+ _pmgadgets_usage >>$tmp/msg
+ echo >>$tmp/msg
+ _pmgadgets_info -f $tmp/msg
+}
+
+# keep the original command line for restart after logout/login
+#
+echo -n "pmgadgets 1 \"pmgshping\"" >$tmp/conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp/conf
+done
+echo "" >>$tmp/conf
+
+# get arguments
+#
+. $PCP_SHARE_DIR/lib/pmgadgets-args
+
+# default variables
+#
+gap=""
+ping=shping
+verbose=false
+
+_pmgadgets_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "eG:V?" c $otherArgs
+ do
+ case $c
+ in
+ e)
+ ping=espping
+ ;;
+ G)
+ gap=$OPTARG
+ ;;
+ V)
+ verbose=true
+ ;;
+ ?)
+ _usage
+ status=1
+ exit
+ ;;
+ esac
+ done
+
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+
+ if [ $# -gt 0 ]
+ then
+ _usage
+ status=1
+ exit
+ fi
+fi
+
+# check on metric availability
+#
+if _pmgadgets_fetch_indom ${ping}.time.real
+then
+ if [ ! -s $tmp/pmgadgets_result -o "$number" -lt 1 ]
+ then
+ _pmgadgets_fetch_fail "get $ping metrics"
+ #NOTREACHED
+ fi
+ # save number of instances of ${ping}.time.real to be used below
+ #
+ mynumber=$number
+else
+ _pmgadgets_fetch_fail "get $ping metrics"
+ #NOTREACHED
+fi
+
+cp $tmp/pmgadgets_result $tmp/info
+
+# default is update at the same frequency as the $ping PMDA
+# runs the commands
+#
+if [ $interval = 0 ]
+then
+ result=''
+ if _pmgadgets_fetch_values ${ping}.control.cycletime
+ then
+ if [ ! -s $tmp/pmgadgets_result -o "$number" -lt 1 ]
+ then
+ _pmgadgets_fetch_fail "get $ping cycle time"
+ #NOTREACHED
+ fi
+ else
+ _pmgadgets_fetch_fail "get $ping cycle time"
+ #NOTREACHED
+ fi
+
+ interval=`cat $tmp/pmgadgets_result`
+ if [ -z "$interval" ]
+ then
+ # if you can't get this, probably doomed, but use 10sec as a fallback
+ #
+ interval=10sec
+ else
+ interval="${interval}sec"
+ fi
+fi
+args="$args -t $interval"
+
+# $ping PMDA may not be installed here ... fake out $ping.RealTime
+# config file for pmchart and use stacked bar instead of line plot
+# because the time interval may be quite long
+pmchart_config=$tmp/config
+cat >$tmp/config <<End-of-File
+#pmchart
+Version 2.0 host dynamic
+Chart Title "Elapsed Time for $ping Commands" Style stacking
+ Plot Color #-cycle Host * Metric $ping.time.real Matching .*
+End-of-File
+
+# output the config file
+#
+start_x=8 # starting x co-ord
+start_y=8 # starting y co-ord
+diam=10 # LED diameter
+if [ -z "$gap" ]
+then
+ gap=50 # column width
+ [ "$ping" = "espping" ] && gap=150 # pmdaespping instance names are long
+fi
+
+pmchart_opts="-t $interval -c $pmchart_config"
+[ ! -z "$host" ] && pmchart_opts="$pmchart_opts -h $host"
+[ ! -z "$namespace" ] && pmchart_opts="$pmchart_opts $namespace"
+
+cat <<End-of-File >>$tmp/conf
+_actions Actions (
+"pmchart" "/usr/bin/X11/xconfirm -icon info -t 'Values plotted by pmchart will not appear' -t 'until after the first time interval ($interval)' -B Dismiss >/dev/null & pmchart $pmchart_opts"
+)
+
+_legend Status (
+ _default "#608080"
+ 1 yellow
+ 2 orange
+ 3 red
+ 4 purple
+)
+
+_legend Response (
+ _default "#608080"
+ 0 green3
+ 1000 yellow
+ 5000 orange
+ 10000 red
+)
+End-of-File
+
+# get instance information from metrics
+#
+for tag in `cat $tmp/info`
+do
+ echo "$tag"
+done \
+| $PCP_AWK_PROG -v ping=$ping >>$tmp/conf '
+BEGIN { x = '$start_x'; y = '$start_y'; diam = '$diam'; gap = '$gap'; left = 1
+ printf "_label %d %d \"%s\"\n",x,y+8,"'$host'"
+ y += 12
+ printf "_label %d %d \"%s\"\n",x,y+8,"S"
+ x = x + diam + 2
+ printf "_label %d %d \"%s\"\n",x,y+8,"Response"
+ if ('$mynumber' > 1) {
+ x = x + diam + 2
+ x = x + gap
+ printf "_label %d %d \"%s\"\n",x,y+8,"S"
+ x = x + diam + 2
+ printf "_label %d %d \"%s\"\n",x,y+8,"Response"
+ }
+ y += 12
+ }
+ {
+ if (left) {
+ print ""
+ x = '$start_x'
+ }
+ else
+ x = x + gap
+ print "_led " x " " y " " diam " " diam
+ print " _metric " ping".status[\"" $1 "\"]"
+ print " _legend Status"
+ x = x + diam + 2
+ print "_led " x " " y " " diam " " diam
+ print " _metric " ping ".time.real[\"" $1 "\"]"
+ print " _legend Response"
+ print " _actions Actions"
+ x = x + diam + 2
+ printf "_label %d %d \"%s\"\n",x,y+diam-2,$1
+ if (!left) {
+ y = y + diam + 2
+ }
+ left = 1-left
+ }'
+
+$verbose && cat $tmp/conf
+eval pmgadgets <$tmp/conf $args
+
+status=$?
+exit
diff --git a/src/pmgadgets/pmgsys.py b/src/pmgadgets/pmgsys.py
new file mode 100755
index 0000000..5f8a1df
--- /dev/null
+++ b/src/pmgadgets/pmgsys.py
@@ -0,0 +1,380 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+""" Rewrite of pmgsys (originally a C++ application) in python
+
+ Needs to handle the following options at some point:
+ -h (host), -cpudelta (interval), -l (label), -v (verbose),
+ -c (config), -zoom (factor), ... rest through to pmgadgets.
+"""
+
+from sys import argv
+from pcp import pmapi
+from math import sqrt
+from cpmapi import PM_TYPE_U32
+
+PCPARGS = "" # for pmgadgets-launched child processes to use
+
+# magic numbers, from original pmgsys algorithms
+CPUDELTA = 0.5
+LOADDELTA = 5
+FONTASCENT = 7
+DOLABEL = 1
+HSPACE = 5
+VSPACE = 2
+CPUWIDTH = 30
+CPUHEIGHT = 4
+LOADWIDTH = CPUWIDTH
+LOADHEIGHT = 4 * CPUHEIGHT + 3 * VSPACE
+MEMWIDTH = CPUHEIGHT
+MEMHEIGHT = LOADHEIGHT
+DISKSIZE = 6
+NETWIDTH = CPUWIDTH
+NETHEIGHT = CPUHEIGHT
+
+class Machine(object):
+ def __init__(self, context):
+ # pcp context
+ self.context = context
+ # hardware bits
+ self.ncpu = 0
+ self.ndisk = 0
+ self.niface = 0
+ self.ndiskmaps = 0
+ self.memory = 0
+ # external names
+ self.cpus = []
+ self.disks = []
+ self.parts = []
+ self.ifaces = []
+ self.diskmaps = []
+
+ def get_hinv(self):
+ """ Extract counts of CPUs, disks, interfaces and memory size
+ """
+ hinv = ('hinv.ncpu', 'hinv.ndisk', 'hinv.ninterface', 'hinv.physmem')
+ pmids = self.context.pmLookupName(hinv)
+ descs = self.context.pmLookupDescs(pmids)
+ result = self.context.pmFetch(pmids)
+ hardware = [0, 0, 0, 0]
+ for x in xrange(4):
+ atom = self.context.pmExtractValue(
+ result.contents.get_valfmt(x),
+ result.contents.get_vlist(x, 0),
+ descs[x].contents.type, PM_TYPE_U32)
+ hardware[x] = atom.ul
+ context.pmFreeResult(result)
+ self.ncpu = hardware[0]
+ self.ndisk = hardware[1]
+ self.niface = hardware[2]
+ self.memory = hardware[3]
+
+ def get_names(self):
+ """ Extract names of CPUs, disks and network interfaces.
+ """
+ inst = ('kernel.percpu.cpu.user', # expand CPU names
+ 'disk.dev.total', # expand disk names
+ 'disk.partitions.total', # expand disk partition names
+ 'network.interface.total.bytes') # expand network interface names
+ pmids = self.context.pmLookupName(inst)
+ descs = self.context.pmLookupDescs(pmids)
+
+ (inst, self.cpus) = self.context.pmGetInDom(descs[0])
+ (inst, self.disks) = self.context.pmGetInDom(descs[1])
+ (inst, self.parts) = self.context.pmGetInDom(descs[2])
+ (inst, self.ifaces) = self.context.pmGetInDom(descs[3])
+
+ def details(self):
+ print "CPUs: ", self.ncpu
+ print "CPU names: ", self.cpus
+ print "Disks: ", self.ndisk
+ print "Disk names: ", self.disks
+ print "Partition names: ", self.parts
+ print "Interfaces: ", self.niface
+ print "Interface names: ", self.ifaces
+ print "Memory: ", self.memory
+
+ def get_diskmaps(self):
+ """ Produce disk -> partition mappings
+ This means grouping "sda1 sda2 sda3 sdb1" into two mappings
+ - "sda" -> (sda1, sda2, sda3) and "sdb" -> (sdb1); done via
+ the disk.dev.total and disk.partitions.total metrics.
+ (original: controller -> disk mappings, but ENODATA)
+ """
+ return 0
+
+ def inventory(self):
+ """ Wrap calls to getting counts and subsystem names
+ """
+ self.get_hinv()
+ self.get_names()
+ self.get_diskmaps()
+
+ def gadgetize(self):
+ """ Generate a pmgadgets configuration for this host
+ """
+ print "pmgadgets 1", # follow with command line
+ for arg in argv:
+ print "\"%s\"" % (arg),
+ print
+
+ rows = 1
+ ctiles = int((self.ncpu - 1) / 4 + 1) # always at least one cpu
+ ntiles = int((self.niface - 1) / 4 + 1)
+ if ctiles > 3:
+ cr = int(math.sqrt(ctiles))
+ if cr > rows:
+ rows = cr
+ if ntiles > 3:
+ nr = int(math.sqrt(ntiles))
+ if nr > rows:
+ rows = nr
+
+ baseY = VSPACE
+ if DOLABEL == 1:
+ y = FONTASCENT + VSPACE
+ hostname = self.context.pmGetContextHostName()
+ print "_label %d %d \"%s\"" % (HSPACE, y, hostname)
+ baseY += y
+ baseX = maxX = HSPACE
+ maxY = baseY
+
+ print "_actions cpuActions ("
+ print " \"pmchart\"\t\t\"pmchart -c CPU%s\"" % (PCPARGS)
+ print " \"mpvis *\"\t\t\"mpvis%s\" _default" % (PCPARGS)
+ # original had IRIX gr_top and gr_osview tools next;
+ # perhaps some fine day we could implement these as
+ # pmgadgets front-end tools (certainly the latter)
+ print ")"
+
+ y = baseY + FONTASCENT
+ x = baseX
+ print "_label %d %d \"CPU\"" % (x, y)
+ print " _actions cpuActions\n"
+ # original: "these should match the colours in mpvis"
+ print "_colourlist cpuColours (blue3 red3 yellow3 cyan3 green3)"
+
+ # place the CPU bars
+ y += VSPACE
+ ccols = int((ctiles + rows - 1) / rows)
+
+ cpu = 0
+ for rc in range(0, self.ncpu):
+ for ct in range(0, ccols):
+ tc = 0
+ while (cpu < self.ncpu and tc < 4):
+ print "_multibar %d %d %d %d" % (x, y, CPUWIDTH, CPUHEIGHT)
+ print(" _update %f" % (CPUDELTA)).rstrip('0').rstrip('.')
+ print " _metrics ("
+ print "\tkernel.percpu.cpu.user[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.sys[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.intr[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.wait.total[\"%s\"]" % (self.cpus[cpu])
+ print "\tkernel.percpu.cpu.idle[\"%s\"]" % (self.cpus[cpu])
+ print " )"
+ print " _maximum 0.0\n"
+ print " _colourlist cpuColours"
+ print " _actions cpuActions\n"
+ cpu += 1
+ tc += 1
+ y += VSPACE + CPUHEIGHT
+
+ if maxY < y:
+ maxY = y
+ y -= (VSPACE + CPUHEIGHT) * tc
+ x += HSPACE + CPUWIDTH
+
+ y += (CPUHEIGHT + VSPACE) * 4 + VSPACE
+ if (maxX < x):
+ maxX = x
+ x = baseX
+
+ baseX += (HSPACE + CPUWIDTH) * ccols
+
+ # The load gadget and its label
+ print "_actions loadActions ("
+ print " \"pmchart *\"",
+ print "\t\"pmchart -c LoadAvg%s\" _default" % (PCPARGS)
+ # original had IRIX gr_top here
+ print ")"
+ print "_label %d %d \"Load\"" % (baseX, baseY + FONTASCENT)
+ print " _actions loadActions"
+ print
+
+ y = VSPACE + baseY + FONTASCENT
+
+ i = y + LOADHEIGHT
+ if (i > maxY):
+ maxY = i
+
+ print "_bargraph %d %d %d %d" % (baseX, y, LOADWIDTH, LOADHEIGHT)
+ print(" _update %f" % (LOADDELTA)).rstrip('0').rstrip('.')
+ print " _metric kernel.all.load[\"1 minute\"]"
+ print " _max 1.0"
+ print " _actions loadActions"
+
+ # For more than one row, stack LoadAvg on top of Memory.
+ #
+ # Move baseX just after the right hand side of the memory, so
+ # we don't have to do anything special for the netifs. For the
+ # sake of argument, consider total width occupied by memory
+ # gauges equal to total width of loadavg graph
+ if (rows > 1):
+ y += LOADHEIGHT + VSPACE
+ baseX += LOADWIDTH + HSPACE
+ else:
+ y += baseY
+ baseX += LOADWIDTH * 2 + HSPACE * 2
+
+ # The memory gadgets and their label (platform-specific!)
+ x = baseX - LOADWIDTH - HSPACE
+ y += FONTASCENT
+ print "_label %d %d \"Mem\"\n" % (x, y)
+ print "_colourlist memColours (cyan1 red yellow green)\n"
+ y += VSPACE
+ print "_multibar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
+ print " _update 0.5"
+ print " _metrics ("
+ print "\tmem.util.cached"
+ print "\tmem.util.bufmem"
+ print "\tmem.util.other"
+ print "\tmem.util.free"
+ print " )"
+ print " _colourlist memColours"
+ x += HSPACE + MEMWIDTH
+ print "_bar %d %d %d %d" % (x, y, MEMWIDTH, MEMHEIGHT)
+ print " _metric swap.pagesout"
+ print " _vertical"
+
+ # Check for the max horizontal offset
+ i = y + MEMHEIGHT
+ if (i > maxY):
+ maxY = i
+
+ # The network bars and their label
+ print "_colourlist netColours (aquamarine orange)"
+ print "_actions netActions ("
+ print " \"pmchart-packets *\"",
+ print "\t\"pmchart -c NetPackets%s\" _default" % (PCPARGS)
+ print " \"pmchart-bytes\"",
+ print "\t\t\"pmchart -c NetBytes%s\"" % (PCPARGS)
+ # original had netstat within an xterm here, next
+ print ")"
+
+ y = baseY + FONTASCENT
+ x = baseX
+
+ print "_label %d %d \"Net\"" % (x, y)
+ print " _actions netActions\n"
+
+ y += VSPACE
+
+ ncols = int((ntiles + rows - 1) / rows)
+
+ ni = 0
+ while ni < self.niface:
+ for nt in range(0, ncols):
+ tc = 0
+ while ni < self.niface and tc < 4:
+ print "_multibar %d %d %d %d" % (x, y, NETWIDTH, NETHEIGHT)
+ print " _metrics ("
+ print "\tnetwork.interface.in.bytes[\"%s\"]" % (self.ifaces[ni])
+ print "\tnetwork.interface.out.bytes[\"%s\"]" % (self.ifaces[ni])
+ print " )"
+ print "_colourlist netColours"
+ print " _actions netActions"
+ y += NETHEIGHT + VSPACE
+
+ tc += 1
+ ni += 1
+
+ if maxY < y:
+ maxY = y
+ y -= (NETHEIGHT + VSPACE) * tc
+ x += NETWIDTH + HSPACE
+ if maxX < x:
+ maxX = x
+ y += (NETHEIGHT + VSPACE) * 4 + VSPACE
+ x = baseX
+ print
+
+ # Disks
+ dir = 1
+
+ print "_actions diskActions ("
+ print " \"pmchart\"",
+ print "\t\t\"pmchart -c Disk%s\"" % (PCPARGS)
+ print " \"dkvis *\"",
+ print "\t\t\"dkvis%s\" _default" % (PCPARGS)
+ print ")"
+
+ x = HSPACE
+ y = maxY + FONTASCENT + 2 * VSPACE
+ print "_label %d %d \"Disk\"" % (x, y)
+ print " _actions diskActions\n"
+ print "_legend diskLegend ("
+ print " _default green3"
+ print " 15 yellow"
+ print " 40 orange"
+ print " 75 red"
+ print ")"
+
+ x += CPUWIDTH + HSPACE
+ # this only works if FONTASCENT >= ledSize
+ y -= DISKSIZE
+ thickness = 4
+ halfDiskSize = int((DISKSIZE - thickness) / 2)
+
+ for i in range(0, self.ndiskmaps):
+ mapping = self.diskmaps[i]
+ for j in range(0, len(mappings)):
+ if j > 0:
+ if oldX < x: # moved to right
+ lx = x - VSPACE - 1
+ ly = y + halfDiskSize
+ lw = VSPACE + 2
+ lh = thickness
+ elif oldX > x: # moved to left
+ lx = oldX - VSPACE - 1
+ ly = y + halfDiskSize
+ lw = VSPACE + 2
+ lh = thickness
+ else: # moved down
+ lx = x + halfDiskSize
+ ly = oldY + DISKSIZE - 1
+ lw = thickness
+ lh = VSPACE + 2
+ print "_line %d %d %d %d" % (lx, ly, lw, lh)
+ print "_led %d %d %d %d" % (x, y, DISKSIZE, DISKSIZE)
+ print " _metric disk.dev.total[\"%s\"]" % (mapping.name())
+ print " _legend diskLegend"
+ print " _actions diskActions"
+ oldX = x
+ oldY = y
+ xStep = dir * (DISKSIZE + VSPACE) # use VSPACE (tighter packing)
+ x += xStep
+ if x > maxX - DISKSIZE or x <= HSPACE:
+ x -= xStep
+ y += DISKSIZE + VSPACE
+ dir = -dir
+
+
+if __name__ == '__main__':
+ context = pmapi.pmContext()
+ machine = Machine(context)
+ machine.inventory()
+ # machine.details()
+ machine.gadgetize()
+
diff --git a/src/pmgadgets/tokens.h b/src/pmgadgets/tokens.h
new file mode 100644
index 0000000..b299bf8
--- /dev/null
+++ b/src/pmgadgets/tokens.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1996 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/* gadget tokens */
+#define TOK_LINE 1
+#define TOK_LABEL 2
+#define TOK_BAR 3
+#define TOK_MULTIBAR 4
+#define TOK_BARGRAPH 5
+#define TOK_LED 6
+
+/* gadget building block tokens */
+#define TOK_LEGEND 100
+#define TOK_COLOURLIST 101
+#define TOK_ACTIONLIST 102
+
+/* other reserved words' tokens */
+#define TOK_BAD_RES_WORD 200
+#define TOK_UPDATE 201
+#define TOK_METRIC 202
+#define TOK_HORIZONTAL 203
+#define TOK_VERTICAL 204
+#define TOK_METRICS 205
+#define TOK_MIN 206
+#define TOK_MAX 207
+#define TOK_DEFAULT 208
+#define TOK_FIXED 209
+#define TOK_COLOUR 210
+#define TOK_HISTORY 211
+#define TOK_NOBORDER 212
+
+/* other lexical symbols' tokens */
+#define TOK_IDENTIFIER 300
+#define TOK_INTEGER 301
+#define TOK_REAL 302
+#define TOK_STRING 303
+#define TOK_LPAREN 304
+#define TOK_RPAREN 305
+#define TOK_LBRACKET 306
+#define TOK_RBRACKET 307
+#define TOK_COLON 308
+
+/* end of file */
+#define TOK_EOF 666
+
+extern unsigned nLines;
+extern int tokenIntVal;
+extern double tokenRealVal;
+extern char* tokenStringVal;
diff --git a/src/pmgenmap/GNUmakefile b/src/pmgenmap/GNUmakefile
new file mode 100644
index 0000000..bd4b042
--- /dev/null
+++ b/src/pmgenmap/GNUmakefile
@@ -0,0 +1,34 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = pmgenmap.sh
+
+default:
+
+include $(BUILDRULES)
+
+install:
+ $(INSTALL) -m 755 pmgenmap.sh $(PCP_BIN_DIR)/pmgenmap
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmgenmap/pmgenmap.sh b/src/pmgenmap/pmgenmap.sh
new file mode 100755
index 0000000..f6e6b4c
--- /dev/null
+++ b/src/pmgenmap/pmgenmap.sh
@@ -0,0 +1,103 @@
+#! /bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Generate a Performance Metrics Name Space Map from a specification file
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+prog=`basename $0`
+if [ $# -gt 1 -o "X$1" = "X-?" ]
+then
+ echo "Usage: $prog [infile]"
+ exit 1
+fi
+if [ $# -eq 0 ]
+then
+ pathnamename="<stdin>"
+ name="<stdin>"
+else
+ if [ ! -f $1 ]
+ then
+ echo "$prog: cannot open \"$1\""
+ exit 1
+ fi
+ pathname=`pwd`/$1
+ name=$1
+fi
+
+cat <<End-of-File
+/*
+ * Performance Metrics Name Space Map
+ * Built by $prog from the file
+ * $name
+ * on `date`
+ *
+ * Do not edit this file!
+ */
+
+End-of-File
+
+# Deal with either Windows or Unix variants of text files
+cat $1 | tr '\r\n' '\n' | $PCP_AWK_PROG '
+$1 == "#" { if (comment) text = text "\n *"
+ comment++
+ for (i = 2; i <= NF; i++)
+ text = text " " $i
+ next
+ }
+comment > 0 { if (comment == 1)
+ print "/*" text " */"
+ else
+ print "/*\n *" text "\n */"
+ comment = 0
+ text = ""
+ }
+NF == 0 { print; next }
+NF == 2 && $2 == "{" { if (state != 0) {
+ printf "[%s:%d] nested group?\n","'$1'",NR
+ exit 1
+ }
+ printf "char *%s[] = {\n",$1
+ state = 1
+ ord = 0
+ next
+ }
+NF == 2 && state == 1 { printf "#define %s %d\n",$2,ord
+ ord++
+ printf " \"%s\",\n",$1
+ next
+ }
+NF == 1 && $1 == "}" { printf "\n"
+ printf "};\n\n"
+ state = 0
+ next
+ }
+ { printf "[%s:%d] syntax error\n","'$name'",NR
+ exit 1
+ }
+END { if (state) {
+ printf "[%s:%d] unterminated group?\n","'$name'",NR
+ exit 1
+ }
+ if (comment == 1)
+ print "/*" text " */"
+ else if (comment > 1)
+ print "/*\n *" text "\n */"
+ }'
diff --git a/src/pmgetopt/GNUmakefile b/src/pmgetopt/GNUmakefile
new file mode 100644
index 0000000..6fff032
--- /dev/null
+++ b/src/pmgetopt/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmgetopt.c
+CMDTARGET = pmgetopt$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default : $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install : default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmgetopt/pmgetopt.c b/src/pmgetopt/pmgetopt.c
new file mode 100644
index 0000000..f3a6121
--- /dev/null
+++ b/src/pmgetopt/pmgetopt.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static int lineno;
+static int count;
+static char buffer[4096];
+
+static inline char *
+skip_whitespace(char *buffer)
+{
+ while (buffer[0] && isspace(buffer[0]))
+ buffer++;
+ return buffer;
+}
+
+static inline char *
+skip_nonwhitespace(char *buffer)
+{
+ while (buffer[0] && !isspace(buffer[0]))
+ buffer++;
+ return buffer;
+}
+
+static inline char *
+seek_character(char *buffer, int seek)
+{
+ for (; *buffer; buffer++)
+ if (seek == (int)(*buffer))
+ return buffer;
+ return buffer;
+}
+
+/*
+ * Parse non-option commands: getopts, (short)usage, or end.
+ * The return code indicates whether the end directive was observed.
+ */
+static int
+command(pmOptions *opts, char *buffer)
+{
+ char *start, *finish;
+
+ start = skip_whitespace(buffer);
+
+ if (strncasecmp(start, "getopt", sizeof("getopt")-1) == 0) {
+ start = skip_whitespace(skip_nonwhitespace(start));
+ finish = skip_nonwhitespace(start);
+ *finish = '\0';
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s: getopt command: '%s'\n", pmProgname, start);
+ if ((opts->short_options = strdup(start)) == NULL)
+ __pmNoMem("short_options", strlen(start), PM_FATAL_ERR);
+ return 0;
+ }
+
+ if (strncasecmp(start, "usage", sizeof("usage")-1) == 0) {
+ start = skip_whitespace(skip_nonwhitespace(start));
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s: usage command: '%s'\n", pmProgname, start);
+ if ((opts->short_usage = strdup(start)) == NULL)
+ __pmNoMem("short_usage", strlen(start), PM_FATAL_ERR);
+ return 0;
+ }
+
+ if (strncasecmp(start, "end", sizeof("end")-1) == 0) {
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s: end command\n", pmProgname);
+ return 1;
+ }
+
+ fprintf(stderr, "%s: unrecognized command: '%s'\n", pmProgname, buffer);
+ return 0;
+}
+
+static int
+append_option(pmOptions *opts, pmLongOptions *longopt)
+{
+ pmLongOptions *entry;
+ size_t size = sizeof(pmLongOptions);
+
+ /* space for existing entries, new entry and the sentinal */
+ size = (count + 1) * sizeof(pmLongOptions) + sizeof(pmLongOptions);
+ if ((entry = realloc(opts->long_options, size)) == NULL)
+ __pmNoMem("append", size, PM_FATAL_ERR);
+ opts->long_options = entry;
+ entry += count++;
+ /* if not first entry: find current sentinal, overwrite with new option */
+ memcpy(entry, longopt, sizeof(pmLongOptions));
+ memset(entry + 1, 0, sizeof(pmLongOptions)); /* insert new sentinal */
+ return 0;
+}
+
+static int
+append_text(pmOptions *opts, char *buffer, size_t length)
+{
+ pmLongOptions text = PMAPI_OPTIONS_TEXT("");
+
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s: append: '%s'\n", pmProgname, buffer);
+ if ((text.message = strdup(buffer)) == NULL)
+ __pmNoMem("append_text", length, PM_FATAL_ERR);
+ return append_option(opts, &text);
+}
+
+static pmLongOptions *
+search_long_options(pmLongOptions *stock, const char *name)
+{
+ pmLongOptions *entry;
+
+ for (entry = stock; entry->long_opt != NULL; entry++)
+ if (strcmp(entry->long_opt, name) == 0)
+ return entry;
+ return NULL;
+}
+
+static pmLongOptions *
+search_short_options(pmLongOptions *stock, int opt)
+{
+ pmLongOptions *entry;
+
+ for (entry = stock; entry->long_opt != NULL; entry++)
+ if (entry->short_opt == opt)
+ return entry;
+ return NULL;
+}
+
+static int
+standard_options(pmOptions *opts, char *start)
+{
+ pmLongOptions stock[] = {
+ PMAPI_GENERAL_OPTIONS,
+ PMOPT_SPECLOCAL,
+ PMOPT_LOCALPMDA,
+ PMOPT_HOSTSFILE,
+ PMOPT_HOST_LIST,
+ PMOPT_ARCHIVE_LIST,
+ PMOPT_ARCHIVE_FOLIO,
+ PMAPI_OPTIONS_END
+ };
+ pmLongOptions *entry;
+
+ entry = (start[1] == '-') ?
+ search_long_options(stock, start + 2) :
+ search_short_options(stock, (int)start[1]);
+ if (entry)
+ return append_option(opts, entry);
+ fprintf(stderr, "%s: cannot find PCP option \"%s\", line %d ignored\n",
+ pmProgname, start, lineno);
+ return -EINVAL;
+}
+
+static int
+options(pmOptions *opts, char *buffer, size_t length)
+{
+ char *start, *finish, *token;
+ pmLongOptions option = { 0 };
+
+ /*
+ * Two cases to deal with here - a "standard" option e.g. --host
+ * which is presented as a single word (no description) OR a full
+ * description of a command line option, in one of these forms:
+ *
+ * --label dump the archive label
+ * --background=COLOR render background with given color
+ * -d, --desc get and print metric description
+ * -b=N, --batch=N fetch N metrics at a time
+ * -L use a local context connection
+ * -X=N offset resulting values by N units
+ */
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "%s: parsing option: '%s'", pmProgname, buffer);
+
+ start = skip_whitespace(skip_nonwhitespace(buffer));
+ finish = skip_nonwhitespace(start);
+ if (start[0] != '-') {
+ *finish = '\0';
+ return append_text(opts, buffer, length);
+ }
+
+ token = skip_whitespace(finish);
+ *finish = '\0';
+
+ /* if a single word, this is the standard PCP option case - find it */
+ if (!token || *token == '\0')
+ return standard_options(opts, start);
+
+ /* handle the first two example cases above -- long option only */
+ if (start[1] == '-') {
+ token = seek_character(start, '=');
+ if (*token == '=') {
+ *token = '\0'; /* e.g. --background=COLOR render ... */
+ token++;
+ finish = skip_nonwhitespace(token);
+ *finish = '\0';
+ if ((option.argname = strdup(token)) == NULL)
+ __pmNoMem("argname", strlen(token), PM_FATAL_ERR);
+ option.has_arg = 1;
+ } /* else e.g. --label dump the archive label */
+ if ((option.long_opt = strdup(start + 2)) == NULL)
+ __pmNoMem("longopt", strlen(start), PM_FATAL_ERR);
+ token = skip_whitespace(finish + 1);
+ if ((option.message = strdup(token)) == NULL)
+ __pmNoMem("message", strlen(token), PM_FATAL_ERR);
+ return append_option(opts, &option);
+ }
+
+ /* handle next two example cases above -- both long and short options */
+ token = seek_character(start, ',');
+ if (*token == ',') {
+ /* e.g. -b=N, --batch=N fetch N metrics at a time */
+ option.short_opt = (int)start[1];
+
+ /* move onto extracting --batch, [=N], and "fetch..." */
+ token++; /* move past the comma */
+ if (*token == '\0' && token - buffer < length) /* move past a null */
+ token++;
+ token = skip_whitespace(token);
+ if ((token = seek_character(token, '-')) == NULL ||
+ (token - buffer >= length) || (token[1] != '-')) {
+ fprintf(stderr, "%s: expected long option at \"%s\", line %d ignored\n",
+ pmProgname, token, lineno);
+ return -EINVAL;
+ }
+ start = token + 2; /* skip double-dash */
+ if ((token = seek_character(start, '=')) != NULL && *token == '=') {
+ *token++ = '\0';
+ option.has_arg = 1; /* now extract the argument name */
+ finish = skip_nonwhitespace(token);
+ *finish = '\0';
+ if ((option.argname = strdup(token)) == NULL)
+ __pmNoMem("argname", strlen(token), PM_FATAL_ERR);
+ } else {
+ finish = skip_nonwhitespace(start);
+ *finish = '\0';
+ }
+ if ((option.long_opt = strdup(start)) == NULL)
+ __pmNoMem("longopt", strlen(start), PM_FATAL_ERR);
+ start = skip_whitespace(finish + 1);
+ if ((option.message = strdup(start)) == NULL)
+ __pmNoMem("message", strlen(start), PM_FATAL_ERR);
+ return append_option(opts, &option);
+ }
+
+ /* handle final two example cases above -- short options only */
+ if (isspace(start[1])) {
+ fprintf(stderr, "%s: expected short option at \"%s\", line %d ignored\n",
+ pmProgname, start, lineno);
+ return -EINVAL;
+ }
+ option.long_opt = "";
+ option.short_opt = start[1];
+ if ((token = seek_character(start, '=')) != NULL && *token == '=') {
+ *token++ = '\0';
+ option.has_arg = 1; /* now extract the argument name */
+ finish = skip_nonwhitespace(token);
+ *finish = '\0';
+ if ((option.argname = strdup(token)) == NULL)
+ __pmNoMem("argname", strlen(token), PM_FATAL_ERR);
+ /* e.g. -X=N offset resulting values by N units */
+ start = skip_whitespace(finish + 2);
+ } else {
+ /* e.g. -L use a local context connection */
+ start = skip_whitespace(start + 3);
+ }
+ if ((option.message = strdup(start)) == NULL)
+ __pmNoMem("message", strlen(start), PM_FATAL_ERR);
+ return append_option(opts, &option);
+}
+
+static char *
+build_short_options(pmOptions *opts)
+{
+ pmLongOptions *entry;
+ char *shortopts;
+ size_t size;
+ int opt, index = 0;
+
+ /* allocate for maximal case - every entry has a short opt and an arg */
+ size = 1 + sizeof(char) * 2 * count;
+ if ((shortopts = malloc(size)) == NULL)
+ __pmNoMem("shortopts", size, PM_FATAL_ERR);
+
+ for (entry = opts->long_options; entry && entry->long_opt; entry++) {
+ if ((opt = entry->short_opt) == 0)
+ continue;
+ if (opt == '-' || opt == '|' || opt == '"')
+ continue;
+ shortopts[index++] = (char)opt;
+ if (entry->has_arg)
+ shortopts[index++] = ':';
+ }
+ shortopts[index] = '\0';
+ return shortopts;
+}
+
+static int
+setup(char *filename, pmOptions *opts)
+{
+ FILE *fp;
+ size_t length;
+ int sts = 0, ended = 0;
+
+ if (filename)
+ fp = fopen(filename, "r");
+ else
+ fp = fdopen(STDIN_FILENO, "r");
+ if (!fp) {
+ fprintf(stderr, "%s: cannot open %s for reading configuration\n",
+ pmProgname, filename? filename : "<stdin>");
+ return -oserror();
+ }
+
+ while (fgets(buffer, sizeof(buffer)-1, fp) != NULL) {
+ lineno++;
+
+ length = strlen(buffer);
+ if (length > 0 && buffer[length-1] == '\n')
+ buffer[--length] = '\0';
+
+ /*
+ * Check for special command character (hash) - if found process
+ * the few commands it can represent -> getopts/usage/end.
+ * If we're finished already, we just tack the text on unchanged.
+ * Otherwise, we must deal with regular long options/headers/text.
+ */
+ if (ended)
+ sts = append_text(opts, buffer, length);
+ else if (buffer[0] == '#')
+ sts = ended = command(opts, buffer + 1);
+ else
+ sts = options(opts, buffer, length);
+ if (sts < 0)
+ break;
+ }
+ fclose(fp);
+
+ /* if not given a getopt string with short options, just make one */
+ if (sts >= 0 && !opts->short_options)
+ opts->short_options = build_short_options(opts);
+
+ return sts;
+}
+
+static char *
+shell_string(const char *string)
+{
+ const char *p;
+ int i = 0;
+
+ buffer[i] = '\0';
+ for (p = string; *p != '\0' && i < sizeof(buffer)-6; p++) {
+ if (*p == '\'') {
+ buffer[i++] = '\'';
+ buffer[i++] = '\\';
+ buffer[i++] = '\'';
+ buffer[i++] = '\'';
+ } else {
+ buffer[i++] = *p;
+ }
+ buffer[i] = '\0';
+ }
+ return buffer;
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "usage", 0, 'u', 0, "generate usage message for calling script" },
+ { "config", 1, 'c', "FILE", "read usage configuration from given FILE" },
+ { "progname", 1, 'p', 0, "program name of calling script, for error reporting" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions localopts = {
+ .flags = PM_OPTFLAG_POSIX,
+ .short_options = "c:D:p:u?",
+ .long_options = longopts,
+ .short_usage = "[options] -- [progname arguments]",
+};
+
+int
+main(int argc, char **argv)
+{
+ pmOptions opts = { .flags = PM_OPTFLAG_POSIX };
+ char *progname = NULL;
+ char *config = NULL;
+ int c, i, usage = 0;
+
+ /* first parse our own arguments, up to a double-hyphen */
+ while ((c = pmgetopt_r(argc, argv, &localopts)) != EOF) {
+ switch (c) {
+ case 'D':
+ if ((c = __pmParseDebug(localopts.optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, localopts.optarg);
+ localopts.errors++;
+ } else {
+ pmDebug |= c;
+ }
+ break;
+ case 'c':
+ config = localopts.optarg;
+ break;
+ case 'p':
+ progname = localopts.optarg;
+ break;
+ case 'u':
+ usage = 1;
+ break;
+ case '?':
+ localopts.errors++;
+ break;
+ }
+ }
+
+ if (localopts.errors) {
+ pmUsageMessage(&localopts);
+ exit(1);
+ }
+
+ if (setup(config, &opts) < 0)
+ exit(1);
+ argc -= (localopts.optind - 1);
+ argv += (localopts.optind - 1);
+ argv[0] = progname ? progname : pmProgname;
+
+ if (usage) {
+ if (progname)
+ pmProgname = progname;
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ i = 0;
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ if (i++)
+ putc(' ', stdout);
+ if (c == 0) {
+ if (opts.optarg)
+ printf("--%s '%s'", opts.long_options[opts.index].long_opt,
+ shell_string(opts.optarg));
+ else
+ printf("--%s", opts.long_options[opts.index].long_opt);
+ }
+ else if (opts.optarg)
+ printf("-%c '%s'", c, shell_string(opts.optarg));
+ else
+ printf("-%c", c);
+ }
+
+ /* finally we report any remaining arguments (after a double-dash) */
+ if (opts.optind < argc) {
+ if (i)
+ putc(' ', stdout);
+ printf("--");
+ for (i = opts.optind; i < argc; i++)
+ printf(" '%s'", shell_string(argv[i]));
+ }
+ printf("\n");
+ return 0;
+}
diff --git a/src/pmhostname/GNUmakefile b/src/pmhostname/GNUmakefile
new file mode 100644
index 0000000..8d178ae
--- /dev/null
+++ b/src/pmhostname/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmhostname.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmhostname$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmhostname/pmhostname.c b/src/pmhostname/pmhostname.c
new file mode 100644
index 0000000..5c635fc
--- /dev/null
+++ b/src/pmhostname/pmhostname.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:?",
+ .long_options = longopts,
+ .short_usage = "[hostname]",
+};
+
+int
+main(int argc, char **argv)
+{
+ char *name, *hename;
+ char host[MAXHOSTNAMELEN];
+ __pmHostEnt *hep;
+
+ while (pmGetOptions(argc, argv, &opts) != EOF)
+ opts.errors++;
+
+ if (opts.errors || argc > opts.optind + 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (argc == opts.optind) {
+ if (gethostname(host, MAXHOSTNAMELEN) < 0) {
+ fprintf(stderr, "%s: gethostname failure\n", pmProgname);
+ exit(1);
+ }
+ name = host;
+ }
+ else
+ name = argv[opts.optind];
+
+ hep = __pmGetAddrInfo(name);
+ if (hep == NULL) {
+ printf("%s\n", name);
+ }
+ else {
+ hename = __pmHostEntGetName(hep);
+ printf("%s\n", hename ? hename : name);
+ }
+
+ exit(0);
+}
diff --git a/src/pmie/GNUmakefile b/src/pmie/GNUmakefile
new file mode 100644
index 0000000..48c46ec
--- /dev/null
+++ b/src/pmie/GNUmakefile
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src examples
+OTHERS = control stomp rc_pmie pmie2col.sh pmie_check.sh pmie_daily.sh
+
+LSRCFILES = $(OTHERS) crontab.in
+LDIRT = crontab pmie.service
+
+ifeq ($(TARGET_OS),linux)
+CRONTAB_USER = $(PCP_USER)
+CRONTAB_PATH = $(PCP_ETC_DIR)/cron.d/pcp-pmie
+else
+CRONTAB_USER =
+CRONTAB_PATH = $(PCP_SYSCONF_DIR)/pmie/crontab
+endif
+
+default:: crontab pmie.service
+
+default:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: default
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_SYSCONF_DIR)/pmie
+ $(INSTALL) -m 664 -o $(PCP_USER) -g $(PCP_GROUP) control $(PCP_PMIECONTROL_PATH)
+ $(INSTALL) -m 755 pmie_check.sh $(PCP_BINADM_DIR)/pmie_check$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmie_daily.sh $(PCP_BINADM_DIR)/pmie_daily$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmie2col.sh $(PCP_BIN_DIR)/pmie2col$(SHELLSUFFIX)
+ $(INSTALL) -m 755 rc_pmie $(PCP_RC_DIR)/pmie
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmie.service $(PCP_SYSTEMDUNIT_DIR)/pmie.service
+endif
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)/pmie
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_TMP_DIR)/pmie
+ifeq ($(TARGET_OS),linux)
+ $(INSTALL) -m 755 -d `dirname $(CRONTAB_PATH)`
+endif
+ $(INSTALL) -m 644 crontab $(CRONTAB_PATH)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+pmie.service : pmie.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+crontab: crontab.in
+ $(SED) -e 's;@user@;'$(CRONTAB_USER)';' -e 's;@path@;'$(PCP_BINADM_DIR)';g' $< > $@
diff --git a/src/pmie/config.default b/src/pmie/config.default
new file mode 100644
index 0000000..2d92240
--- /dev/null
+++ b/src/pmie/config.default
@@ -0,0 +1,20 @@
+// pmieconf-pmie 1 /var/pcp/config/pmieconf
+// end
+//
+// Note: only make changes to this file after END GENERATED SECTION
+//
+// --- START GENERATED SECTION (do not change this section) ---
+// generated by pmieconf on: Wed Jul 12 10:10:20 2000
+//
+
+// 1 cpu.load_average
+delta = 2 min;
+cpu.load_average =
+some_host (
+ ( kernel.all.load #'1 minute' )
+ > hinv.ncpu * 3 &&
+ ( kernel.all.load #'1 minute' )
+ > 4
+) -> syslog 10 min "High 1-minute load average" " %vload@%h";
+
+// --- END GENERATED SECTION (changes below will be preserved) ---
diff --git a/src/pmie/control b/src/pmie/control
new file mode 100644
index 0000000..2dcfd1f
--- /dev/null
+++ b/src/pmie/control
@@ -0,0 +1,46 @@
+#
+# PCP inference engine configuration/control
+#
+# This file is used by a number of the PCP inference engine administrative
+# tools to perform maintenance on the pmie instances running on the local host.
+#
+# This file contains one line per host to be monitored, fields are
+# Host name of host to be monitored
+# S(ocks)? should this pmie be launched with pmsocks? y or n
+# Log File full pathname to file where pmie activity log is to be
+# maintained ... note all scripts "cd" the directory housing
+# this file as a first step
+# Arguments optional additional arguments to pmie
+#
+
+
+# === VARIABLE ASSIGNMENTS ===
+#
+$version=1.0
+
+# if pmsocks is being used, edit the IP address for $SOCKS_SERVER and
+# uncomment the next line
+#$SOCKS_SERVER=123.456.789.123; export SOCKS_SERVER
+
+# if remote pmie instances are run over a WAN with potentially long delays,
+# adjust the following and uncomment
+#$PMCD_CONNECT_TIMEOUT=20; export PMCD_CONNECT_TIMEOUT
+#$PMCD_REQUEST_TIMEOUT=15; export PMCD_REQUEST_TIMEOUT
+
+
+# === PMIE CONTROL SPECIFICATIONS ===
+#
+# Note: - if multiple pmie instances for the same host, then they MUST use
+# different log files;
+# - any occurence of LOCALHOSTNAME will be replaced by local hostname;
+# - pmie's configuration file search path is "./:$PCP_SYSCONF_DIR/pmie/",
+# and the working directory ('.') is the dirname of the Log File.
+#
+#Host S? Log File Arguments
+LOCALHOSTNAME n PCP_LOG_DIR/pmie/LOCALHOSTNAME/pmie.log -c config.default
+
+# remote host
+#remote n PCP_LOG_DIR/pmie/remote/pmie.log -c config.remote
+
+# thru the firewall via socks
+#distant y PCP_LOG_DIR/pmie/distant/pmie.log -c config.distant
diff --git a/src/pmie/crontab.in b/src/pmie/crontab.in
new file mode 100644
index 0000000..90d96af
--- /dev/null
+++ b/src/pmie/crontab.in
@@ -0,0 +1,8 @@
+#
+# Performance Co-Pilot crontab entries for a monitored site
+# with one or more pmie instances running
+#
+# daily processing of pmie logs (with compression enabled)
+08 0 * * * @user@ @path@/pmie_daily -X xz -x 3
+# every 30 minutes, check pmie instances are running
+28,58 * * * * @user@ @path@/pmie_check -C
diff --git a/src/pmie/examples/GNUmakefile b/src/pmie/examples/GNUmakefile
new file mode 100644
index 0000000..f4db24f
--- /dev/null
+++ b/src/pmie/examples/GNUmakefile
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2000-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+UAG_SOURCE = uag.head $(sort $(wildcard uag.[0-9][0-9]))
+UPM_SOURCE = upm.head $(sort $(wildcard upm.[0-9][0-9]))
+CPU_SOURCE = cpu.head $(sort $(wildcard cpu.[0-9][0-9]))
+DISK_SOURCE = disk.head $(sort $(wildcard disk.[0-9][0-9]))
+FILESYS_SOURCE = filesys.head $(sort $(wildcard filesys.[0-9][0-9]))
+RAS_SOURCE = ras.head $(sort $(wildcard ras.[0-9][0-9]))
+SWAP_SOURCE = swap.head $(sort $(wildcard swap.[0-9][0-9]))
+NETWORK_SOURCE = network.head $(sort $(wildcard network.[0-9][0-9]))
+ENVIRON_SOURCE = environ.head $(sort $(wildcard environ.[0-9][0-9]))
+WEBREPORT_SOURCE= webreport.head $(sort $(wildcard webreport.[0-9][0-9]))
+
+TARGETS = UAG UPM CPU DISK FILESYS RAS SWAP NETWORK ENVIRON \
+ WEBREPORT
+
+LDIRT = $(TARGETS)
+
+LSRCFILES = README $(UAG_SOURCE) $(UPM_SOURCE) $(CPU_SOURCE) \
+ $(DISK_SOURCE) $(FILESYS_SOURCE) $(RAS_SOURCE) $(SWAP_SOURCE) \
+ $(NETWORK_SOURCE) $(ENVIRON_SOURCE) $(WEBREPORT_SOURCE)
+EX_DIR = $(PCP_SHARE_DIR)/examples/pmie
+
+default: $(TARGETS) README
+
+install: default
+ $(INSTALL) -m 755 -d $(EX_DIR)
+ $(INSTALL) -m 644 $(TARGETS) README $(EX_DIR)
+
+UAG: $(UAG_SOURCE)
+ rm -f UAG
+ for file in $(UAG_SOURCE); do cat $$file >>UAG; echo >>UAG; done
+
+UPM: $(UPM_SOURCE)
+ rm -f UPM
+ for file in $(UPM_SOURCE); do cat $$file >>UPM; echo >>UPM; done
+
+CPU: $(CPU_SOURCE)
+ rm -f CPU
+ for file in $(CPU_SOURCE); do cat $$file >>CPU; echo >>CPU; done
+
+DISK: $(DISK_SOURCE)
+ rm -f DISK DISK.in
+ for file in $(DISK_SOURCE); do cat $$file >>DISK.in; echo >>DISK.in; done
+ sed -e "s@/usr/pcp/bin/pmpost@$(PCP_BINADM_DIR)/pmpost@" <DISK.in >DISK
+ rm -f DISK.in
+
+FILESYS: $(FILESYS_SOURCE)
+ rm -f FILESYS
+ for file in $(FILESYS_SOURCE); do cat $$file >>FILESYS; echo >>FILESYS; done
+
+RAS: $(RAS_SOURCE)
+ rm -f RAS
+ for file in $(RAS_SOURCE); do cat $$file >>RAS; echo >>RAS; done
+
+SWAP: $(SWAP_SOURCE)
+ rm -f SWAP
+ for file in $(SWAP_SOURCE); do cat $$file >>SWAP; echo >>SWAP; done
+
+NETWORK: $(NETWORK_SOURCE)
+ rm -f NETWORK
+ for file in $(NETWORK_SOURCE); do cat $$file >>NETWORK; echo >>NETWORK; done
+
+ENVIRON: $(ENVIRON_SOURCE)
+ rm -f ENVIRON
+ for file in $(ENVIRON_SOURCE); do cat $$file >>ENVIRON; echo >>ENVIRON; done
+
+WEBREPORT: $(WEBREPORT_SOURCE)
+ rm -f WEBREPORT
+ for file in $(WEBREPORT_SOURCE); do cat $$file >>WEBREPORT; echo >>WEBREPORT; done
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmie/examples/README b/src/pmie/examples/README
new file mode 100644
index 0000000..1bdf4a6
--- /dev/null
+++ b/src/pmie/examples/README
@@ -0,0 +1,27 @@
+Example Expressions and Rules for pmie(1)
+
+The files in this directory contain a number of sample rules
+for the Performance Co-Pilot inference engine pmie(1).
+
+In some cases the rules could be used directly. In others the
+rules would require customization (host names, thresholds, rule
+evaluation frequency, choose a suitable alarm/action, etc) before
+they could be used at a particular site.
+
+Each file contains a set of related rules as follows:
+
+CPU general CPU utilization and saturation monitoring
+
+DISK general disk utilization and saturation monitoring
+
+FILESYS general file system monitoring
+
+RAS simple reliability and availability monitoring
+ (see also the shping PMDA, pmdashping(1))
+
+UAG examples from the Performance Co-Pilot User's and
+ Administrator's Guide
+
+UPM examples from the Performance Co-Pilot Programmer's
+ Guide
+
diff --git a/src/pmie/examples/cpu.00 b/src/pmie/examples/cpu.00
new file mode 100644
index 0000000..403dfa4
--- /dev/null
+++ b/src/pmie/examples/cpu.00
@@ -0,0 +1,9 @@
+//
+// Unusual usr-sys split when some CPU is more than 20% in usr mode
+// and sys mode is at least 1.5 times usr mode
+//
+cpu_usr_sys =
+ some_inst (
+ $percpu.cpu.sys > $percpu.cpu.user * 1.5 && $percpu.cpu.user > 0.2
+ )
+ -> alarm "Unusual sys time: " "%i ";
diff --git a/src/pmie/examples/cpu.01 b/src/pmie/examples/cpu.01
new file mode 100644
index 0000000..799aa99
--- /dev/null
+++ b/src/pmie/examples/cpu.01
@@ -0,0 +1,15 @@
+//
+// Over all CPUs, syscall_rate > 1000 * no_of_cpus
+//
+cpu_syscall =
+ $all.syscall > 1000 count/sec * hinv.ncpu
+ -> print "high aggregate syscalls: %v";
+
+// Sustained high syscall rate on a single CPU
+//
+delta = 30 sec;
+percpu_syscall =
+ some_inst (
+ $percpu.syscall > 2000 count/sec
+ )
+ -> syslog "Sustained syscalls per second? " "[%i] %v ";
diff --git a/src/pmie/examples/cpu.02 b/src/pmie/examples/cpu.02
new file mode 100644
index 0000000..d5b1fbb
--- /dev/null
+++ b/src/pmie/examples/cpu.02
@@ -0,0 +1,10 @@
+//
+// the 1 minute load average exceeds 5 * number of CPUs on any host
+//
+
+hosts = ":gonzo :moomba"; // change as required
+delta = 1 minute; // no need to evaluate more often than this
+high_load =
+ some_host (
+ $all.load $hosts #'1 minute' > 5 * hinv.ncpu $hosts
+ ) -> alarm "High Load Average? " "%h: %v ";
diff --git a/src/pmie/examples/cpu.head b/src/pmie/examples/cpu.head
new file mode 100644
index 0000000..52a493f
--- /dev/null
+++ b/src/pmie/examples/cpu.head
@@ -0,0 +1,12 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The CPU Group
+//
+
+delta = 2 sec; // more often for demonstration purposes
+
+// common prefixes
+//
+percpu = "kernel.percpu";
+all = "kernel.all";
diff --git a/src/pmie/examples/disk.00 b/src/pmie/examples/disk.00
new file mode 100644
index 0000000..a69a8d7
--- /dev/null
+++ b/src/pmie/examples/disk.00
@@ -0,0 +1,28 @@
+//
+// Any disk performing more than 40 I/Os per second, sustained over
+// at least 30 seconds is probably busy
+//
+delta = 30 seconds;
+disk_busy =
+ some_inst (
+ $disk.dev.total > 40 count/sec
+ )
+ -> shell 15 mins "Mail -s 'Heavy sustained disk traffic' sysadm </dev/null";
+
+// Try and catch bursts of activity ... more than 60 I/Os per second
+// for at least 25% of 8 consecutive 3 second samples
+//
+delta = 3 sec;
+disk_burst =
+ some_inst (
+ 25%_sample (
+ $disk.dev.total @0..7 > 60 count/sec
+ )
+ )
+ -> alarm 5 mins "Disk Burst? " "%i ";
+
+// any SCSI disk controller performing more than 3 Mbytes per sec is busy
+//
+some_inst $disk.ctl.blktotal * 0.5 > 3 Mbyte/sec
+ -> alarm "Busy Disk Controller: " "%i ";
+
diff --git a/src/pmie/examples/disk.10 b/src/pmie/examples/disk.10
new file mode 100644
index 0000000..9cbf50d
--- /dev/null
+++ b/src/pmie/examples/disk.10
@@ -0,0 +1,23 @@
+//
+// A subset of the disks on a particular host are either busy
+// (more than 30 I/Os per second averaged over these disks) or one
+// disk is busy (more than 50 I/Os per second) with write-dominated
+// (more than 75%) activity
+
+delta = 10 sec;
+
+myhost = "moomba"; // the host of interest
+mydisks = "#dks1d1 #dks1d2 #dks3d2"; // the disks of interest on this host
+
+metric = "disk.dev";
+
+disk_group_busy =
+ (
+ avg_inst ( $metric.total :$myhost $mydisks ) > 10 count/sec ||
+ some_inst (
+ $metric.total :$myhost $mydisks > 50 count/sec &&
+ $metric.write :$myhost $mydisks >
+ 3 * $metric.write :$myhost $mydisks
+ )
+ )
+ -> alarm "Busy disks: $mydisks on host: $myhost)";
diff --git a/src/pmie/examples/disk.20 b/src/pmie/examples/disk.20
new file mode 100644
index 0000000..ec086e5
--- /dev/null
+++ b/src/pmie/examples/disk.20
@@ -0,0 +1,13 @@
+//
+// Assume the / and /usr file systems are on different partitions
+// of the same disk (/dev/dsk0d1 in the example below).
+// Add an entry to the file $PCP_LOG_DIR/NOTICES when this disk is
+// busy and either of the file systems is more than 90% full.
+//
+// Suggestion from: Steve Daniels (steve@houdini.denver.sgi.com)
+
+delta = 60;
+
+( filesys.full #'/dev/root' > 90 || filesys.full #'/dev/usr' > 90 )
+&& disk.dev.total #'dks0d1' > 40 count/sec
+ -> shell 15min "/usr/pcp/bin/pmpost 'dks0d1 busy when / or /usr nearly full'";
diff --git a/src/pmie/examples/disk.head b/src/pmie/examples/disk.head
new file mode 100644
index 0000000..20f133e
--- /dev/null
+++ b/src/pmie/examples/disk.head
@@ -0,0 +1,11 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The Disk Group
+//
+
+delta = 15 sec; // often enough for disks?
+
+// common prefixes
+//
+disk = "disk";
diff --git a/src/pmie/examples/environ.00 b/src/pmie/examples/environ.00
new file mode 100644
index 0000000..85e5597
--- /dev/null
+++ b/src/pmie/examples/environ.00
@@ -0,0 +1,18 @@
+//
+// Absolute temperature ceiling.
+//
+// Rules donated by Kevin Wang at Silicon Graphics
+//
+
+some_host ( environ.temp $HOSTS > 33 )
+-> print 10 min "absolute temperature alarm! " "%h: %v degrees ";
+
+//
+// Watch the machine room temperature. If it rises more than 2 degrees
+// every $delta, danger!
+// This is different from the absolute rule above ... this one
+// gives early warning of sustained temperature increases.
+//
+some_host (
+ environ.temp $HOSTS @0 - environ.temp $HOSTS @1 > 2
+) -> print "temperature rise alarm: " "%h: %v degree rise in $DELTA_STR ";
diff --git a/src/pmie/examples/environ.head b/src/pmie/examples/environ.head
new file mode 100644
index 0000000..78936b9
--- /dev/null
+++ b/src/pmie/examples/environ.head
@@ -0,0 +1,17 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The Environ Group
+//
+// Note: need environ PMDA installed on a Challenge L or XL for
+// required metrics to be available
+
+// replace with your hosts
+HOSTS = ":localhost :foo";
+
+// replace this with your e-mail address
+MINDER = "root@localhost";
+
+// 1 minute rulesets in this group
+delta = 1 min; // numbers are diff than strings
+DELTA_STR = "1 minute"; // strings are diff than numbers
diff --git a/src/pmie/examples/filesys.00 b/src/pmie/examples/filesys.00
new file mode 100644
index 0000000..937e123
--- /dev/null
+++ b/src/pmie/examples/filesys.00
@@ -0,0 +1,14 @@
+//
+// Either the /tmp or the /usr filesystem being
+// more than 95% full
+//
+
+delta = 5 mins; // often enough for file system fullness?
+
+tmp_full =
+ $fsys.free #'/dev/root' / $fsys.capacity #'/dev/root' < 0.05
+ -> syslog "/dev/root filesystem (almost) full";
+
+usr_full =
+ $fsys.free #'/dev/usr' / $fsys.capacity #'/dev/usr' < 0.05
+ -> syslog "/dev/usr filesystem (almost) full";
diff --git a/src/pmie/examples/filesys.10 b/src/pmie/examples/filesys.10
new file mode 100644
index 0000000..2cf9488
--- /dev/null
+++ b/src/pmie/examples/filesys.10
@@ -0,0 +1,14 @@
+//
+// Some read activity through the buffer cache and the cache read
+// hit ratio is less than 80%
+// (lots of file system reads causing physical I/O)
+//
+
+delta = 1 min; // check every minute
+
+blkio = "kernel.all.io";
+poor_read_hits =
+ (($blkio.lread - $blkio.bread) / $blkio.lread) < 0.8 && $blkio.lread > 100
+ -> alarm 20 min "poor buffer cache read hit ratio (%v)";
+ // Note: %v in alarm string is bound to the left most
+ // expression in the predicate
diff --git a/src/pmie/examples/filesys.20 b/src/pmie/examples/filesys.20
new file mode 100644
index 0000000..856d8ed
--- /dev/null
+++ b/src/pmie/examples/filesys.20
@@ -0,0 +1,14 @@
+//
+// at least $threshold full and at the current rate of growth will fill
+// the file system in less than $lead_time
+// ie. used + $lead_time * growth-rate > capacity
+
+delta = 1 min; // check every minute
+threshold = 40; // must be at least this full now (percentage)
+lead_time = "15min"; // lead time before the filesystem will be full
+
+some_inst (
+ 100 * filesys.used / filesys.capacity > $threshold &&
+ filesys.used + $lead_time * ( rate filesys.used ) >
+ filesys.capacity
+) -> print "filesystem will be full within $lead_time:" " %i";
diff --git a/src/pmie/examples/filesys.head b/src/pmie/examples/filesys.head
new file mode 100644
index 0000000..de4c703
--- /dev/null
+++ b/src/pmie/examples/filesys.head
@@ -0,0 +1,10 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The File System Group
+//
+
+// common prefixes
+//
+fsys = "filesys";
+
diff --git a/src/pmie/examples/network.00 b/src/pmie/examples/network.00
new file mode 100644
index 0000000..44c2183
--- /dev/null
+++ b/src/pmie/examples/network.00
@@ -0,0 +1,10 @@
+//
+// Report when some interface has seen more than 15 errors per second
+// on at least 3 of the last 4 observations
+//
+// Rule donated by Kevin Wang at Silicon Graphics
+//
+
+some_host some_inst 75%_sample (
+ network.interface.total.errors $HOSTS @0..3 > 15
+) -> print 5 min "high network interface errors" "%h[%i] %v errors/sec ";
diff --git a/src/pmie/examples/network.head b/src/pmie/examples/network.head
new file mode 100644
index 0000000..909e773
--- /dev/null
+++ b/src/pmie/examples/network.head
@@ -0,0 +1,15 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The Network Group
+//
+
+// replace with your hosts
+HOSTS = ":localhost :foo";
+
+// replace this with your e-mail address
+MINDER = "root@localhost";
+
+// 10 second rulesets in this group
+delta = 10 sec; // numbers are diff than strings
+DELTA_STR = "10 seconds"; // strings are diff than numbers
diff --git a/src/pmie/examples/ras.00 b/src/pmie/examples/ras.00
new file mode 100644
index 0000000..930b964
--- /dev/null
+++ b/src/pmie/examples/ras.00
@@ -0,0 +1,11 @@
+//
+// For Origin systems, sequence number errors are not indicative of
+// a problem, but persistent checkbit and/or retry errors may indicate
+// a CrayLink interconnect problem.
+//
+some_inst ( all_sample (
+ hw.router.perport.cb_errors @0..2 > 0 ||
+ hw.router.perport.retry_errors @0..2 > 0
+) )
+ -> alarm 30mins "CrayLink SN and/or Retry errors: " "%i ";
+
diff --git a/src/pmie/examples/ras.head b/src/pmie/examples/ras.head
new file mode 100644
index 0000000..d571913
--- /dev/null
+++ b/src/pmie/examples/ras.head
@@ -0,0 +1,5 @@
+//
+// Some System Reliability, Availability and Serviceability (RAS) Checks
+//
+
+delta = 20 sec;
diff --git a/src/pmie/examples/swap.00 b/src/pmie/examples/swap.00
new file mode 100644
index 0000000..2e3f309
--- /dev/null
+++ b/src/pmie/examples/swap.00
@@ -0,0 +1,23 @@
+//
+// report when swap > 50-75% full and when swap > 75% full
+//
+// Rules donated by Kevin Wang at Silicon Graphics
+//
+// note: the sort hack '9999999' to keep the header first; later
+// removed by sed
+// note: -o option to ps(1) requires IRIX 6.2 or later ... for IRIX 5.3
+// this would have to be re-written using ps -el
+
+SWAP="swap";
+some_host (
+ ($SWAP.free $HOSTS / $SWAP.length $HOSTS) * 100 < 50 &&
+ ($SWAP.free $HOSTS / $SWAP.length $HOSTS) * 100 >= 25
+) -> print 10 min "swap more than half-full: " "%h: %v% free " &
+ shell 10 min "rsh -n guest@%h /sbin/ps -eo 'ruser=UID,pid=PID,ppid=PPID,pcpu=%CPU,sz=9999999SZ,rss=RSS,stime=STIME,time=TIME,args=CMD' | sort +4 -nr | sed -e 's/9999999SZ / SZ:/' | /usr/sbin/Mail -s '%h swap more than half-full (%v% free)' $MINDER &";
+
+some_host (
+ ($SWAP.free $HOSTS / $SWAP.length $HOSTS) * 100 < 25
+) -> print 10 min "swap almost full: " "%h: %v% free " &
+ shell 10 min "rsh -n guest@%h /sbin/ps -eo 'ruser=UID,pid=PID,ppid=PPID,pcpu=%CPU,sz=9999999SZ,rss=RSS,stime=STIME,time=TIME,args=CMD' | sort +4 -nr | sed -e 's/9999999SZ / SZ:/' | /usr/sbin/Mail -s '%h swap almost full (%v% free)' $MINDER &";
+
+
diff --git a/src/pmie/examples/swap.head b/src/pmie/examples/swap.head
new file mode 100644
index 0000000..254cf4a
--- /dev/null
+++ b/src/pmie/examples/swap.head
@@ -0,0 +1,13 @@
+//
+// The Swap Group
+//
+
+// replace with your hosts
+HOSTS = ":localhost :foo";
+
+// replace this with your e-mail address
+MINDER = "root@localhost";
+
+// 20 second rulesets in this group
+delta = 20 sec; // numbers are diff than strings
+DELTA_STR = "20 seconds"; // strings are diff than numbers
diff --git a/src/pmie/examples/uag.00 b/src/pmie/examples/uag.00
new file mode 100644
index 0000000..6cb9638
--- /dev/null
+++ b/src/pmie/examples/uag.00
@@ -0,0 +1,5 @@
+//
+// a simple expression, with multiple values
+//
+iops = disk.dev.total;
+
diff --git a/src/pmie/examples/uag.01 b/src/pmie/examples/uag.01
new file mode 100644
index 0000000..7e6d691
--- /dev/null
+++ b/src/pmie/examples/uag.01
@@ -0,0 +1,4 @@
+//
+// total disk write percentage
+//
+wrt_pct = (disk.all.write / disk.all.total) * 100;
diff --git a/src/pmie/examples/uag.02 b/src/pmie/examples/uag.02
new file mode 100644
index 0000000..6127209
--- /dev/null
+++ b/src/pmie/examples/uag.02
@@ -0,0 +1,8 @@
+//
+// some varied expressions
+//
+pct_wrt = (disk.all.write / disk.all.total) * 100;
+busy_wrt = disk.dev.total > 10 &&
+ disk.dev.write > disk.dev.read;
+busy = some_inst disk.dev.total > 60 -> print "[%i] high disk i/o ";
+
diff --git a/src/pmie/examples/uag.03 b/src/pmie/examples/uag.03
new file mode 100644
index 0000000..01e4d7c
--- /dev/null
+++ b/src/pmie/examples/uag.03
@@ -0,0 +1,7 @@
+//
+// simple use of a macro
+//
+disk = "disk.all";
+pct_wrt = ($disk.write / $disk.total) * 100;
+
+
diff --git a/src/pmie/examples/uag.04 b/src/pmie/examples/uag.04
new file mode 100644
index 0000000..e13a5ef
--- /dev/null
+++ b/src/pmie/examples/uag.04
@@ -0,0 +1,52 @@
+//
+// perverse example to show all possible choices of units for numeric
+// constants
+//
+mem.freemem > 1 byte;
+mem.freemem > 1 Kbyte;
+mem.freemem > 1 Mbyte;
+mem.freemem > 1 Gbyte;
+mem.freemem > 1 Tbyte;
+
+disk.dev.blktotal > 1 Mbyte / nsec;
+disk.dev.blktotal > 1 Mbyte / nanosecond;
+disk.dev.blktotal > 1 Mbyte / usec;
+disk.dev.blktotal > 1 Mbyte / microsecond;
+disk.dev.blktotal > 1 Mbyte / msec;
+disk.dev.blktotal > 1 Mbyte / millisecond;
+disk.dev.blktotal > 1 Mbyte / sec;
+disk.dev.blktotal > 1 Mbyte / second;
+disk.dev.blktotal > 1 Mbyte / min;
+disk.dev.blktotal > 1 Mbyte / minute;
+disk.dev.blktotal > 1 Mbyte / hour;
+
+hinv.ncpu > 1 count;
+hinv.ncpu > 1 Kcount;
+hinv.ncpu > 1 count;
+hinv.ncpu > 1 Gcount;
+hinv.ncpu > 1 Tcount;
+
+mem.freemem > 1 bytes;
+mem.freemem > 1 Kbytes;
+mem.freemem > 1 Mbytes;
+mem.freemem > 1 Gbytes;
+mem.freemem > 1 Tbytes;
+
+disk.dev.blktotal > 1 Mbyte / nsecs;
+disk.dev.blktotal > 1 Mbyte / nanoseconds;
+disk.dev.blktotal > 1 Mbyte / usecs;
+disk.dev.blktotal > 1 Mbyte / microseconds;
+disk.dev.blktotal > 1 Mbyte / msecs;
+disk.dev.blktotal > 1 Mbyte / milliseconds;
+disk.dev.blktotal > 1 Mbyte / secs;
+disk.dev.blktotal > 1 Mbyte / seconds;
+disk.dev.blktotal > 1 Mbyte / mins;
+disk.dev.blktotal > 1 Mbyte / minutes;
+disk.dev.blktotal > 1 Mbyte / hours;
+
+hinv.ncpu > 1 counts;
+hinv.ncpu > 1 Kcounts;
+hinv.ncpu > 1 counts;
+hinv.ncpu > 1 Gcounts;
+hinv.ncpu > 1 Tcounts;
+
diff --git a/src/pmie/examples/uag.10 b/src/pmie/examples/uag.10
new file mode 100644
index 0000000..7135f6b
--- /dev/null
+++ b/src/pmie/examples/uag.10
@@ -0,0 +1,32 @@
+//
+// metric expressions
+
+// all instances
+//
+enet = network.interface.total.packets;
+
+// restricted instance (loopback interface only)
+//
+enet_r = network.interface.total.packets #lo0;
+
+// restricted instances with weird instance names ...
+// note instance names are "identifiers" in the grammar, so single
+// quotes required for tricky characters, like /, spaces, etc, _not_
+// double quotes
+//
+root_n_usr = filesys.free #'/dev/root' #'/dev/usr';
+
+// multiple hosts
+//
+num_cpu = hinv.ncpu :babylon.engr.sgi.com :gonzo :sandpit;
+
+// multiple sample times
+//
+mem_trend = mem.freemem @0..3;
+
+// multi-dimension variations
+//
+
+// missing instance for non-singular instance domain, plus multiple hosts
+//
+net_view = network.interface.total.packets :gonzo :moomba;
diff --git a/src/pmie/examples/uag.11 b/src/pmie/examples/uag.11
new file mode 100644
index 0000000..7d4b86b
--- /dev/null
+++ b/src/pmie/examples/uag.11
@@ -0,0 +1,7 @@
+//
+// relational (logical) expressions
+//
+hosts = ":gonzo";
+intfs = "#ec0 #ec2";
+all_intf = network.interface.in.packets
+ $hosts $intfs @0..2 > 300 count/sec;
diff --git a/src/pmie/examples/uag.12 b/src/pmie/examples/uag.12
new file mode 100644
index 0000000..3850168
--- /dev/null
+++ b/src/pmie/examples/uag.12
@@ -0,0 +1,12 @@
+//
+// quantification examples
+//
+
+// some_instance
+all_intf = network.interface.in.packets
+ #ec0 #ec2 @0..2 > 300 count/sec;
+any_sample = some_sample
+ network.interface.in.packets
+ #ec0 #ec2 @0..2 > 300 count/sec;
+
+
diff --git a/src/pmie/examples/uag.13 b/src/pmie/examples/uag.13
new file mode 100644
index 0000000..9990407
--- /dev/null
+++ b/src/pmie/examples/uag.13
@@ -0,0 +1,33 @@
+//
+// nested quantification
+//
+
+Servers = ":moomba :gonzo"; // change as desired
+
+// read and write rate per disk per host
+//
+rd = disk.dev.read $Servers;
+wr = disk.dev.write $Servers;
+
+// one value per host, true if 20% or more of the disks are doing
+// significant reading or writing
+//
+rd_20 = 20%_inst disk.dev.read $Servers > 40;
+wr_20 = 20%_inst disk.dev.write $Servers > 40;
+
+// single truth value: more than 20% of the disks busy reading or writing
+// on all hosts?
+//
+summary = all_host (
+ 20%_inst disk.dev.read $Servers > 40 ||
+ 20%_inst disk.dev.write $Servers > 40
+ );
+
+// alternate form
+//
+summary2 = all_host (
+ 20%_inst (
+ disk.dev.read $Servers > 40 ||
+ disk.dev.write $Servers > 40
+ )
+ );
diff --git a/src/pmie/examples/uag.20 b/src/pmie/examples/uag.20
new file mode 100644
index 0000000..475017c
--- /dev/null
+++ b/src/pmie/examples/uag.20
@@ -0,0 +1,7 @@
+//
+// a rule expression with multiple actions and %-binding in the
+// arguments for the action methods
+//
+some_inst ( disk.dev.total > 60 )
+ -> syslog 10 mins "[%i] busy, %v IOPS " &
+ shell 1 hour "echo 'Disk %i is REALLY busy. Running at %v I/Os per second' | Mail -s 'pmie alarm' sysadm";
diff --git a/src/pmie/examples/uag.21 b/src/pmie/examples/uag.21
new file mode 100644
index 0000000..c841ff2
--- /dev/null
+++ b/src/pmie/examples/uag.21
@@ -0,0 +1,9 @@
+//
+// a rule expression with multiple actions and %-binding in the
+// arguments for the action methods ... use some creative string
+// composition for the final message
+//
+some_inst ( disk.dev.total > 50 )
+ -> syslog 10 mins "Busy disks: " "%i @ %v IOPS " &
+ shell 1 hour "echo 'REALLY busy disks: " "%i @ %v I/Os per second " "' | Mail -s 'pmie alarm' sysadm";
+
diff --git a/src/pmie/examples/uag.30 b/src/pmie/examples/uag.30
new file mode 100644
index 0000000..b15a781
--- /dev/null
+++ b/src/pmie/examples/uag.30
@@ -0,0 +1,17 @@
+//
+// intrinsic operators
+//
+
+m = mem.freemem;
+rate_m = rate mem.freemem;
+
+// At least 2 CPUs doing some reasonable amount of work
+//
+poke = ":moomba :'mac-larry' :bitbucket"; // note '' to escape - in host name
+u = kernel.percpu.cpu.user $poke;
+s = kernel.percpu.cpu.sys $poke;
+some_host (
+ count_inst ( kernel.percpu.cpu.user $poke +
+ kernel.percpu.cpu.sys $poke > 0.7 ) >= 2
+ )
+ -> alarm "2 or more busy CPUs";
diff --git a/src/pmie/examples/uag.head b/src/pmie/examples/uag.head
new file mode 100644
index 0000000..9ba4f40
--- /dev/null
+++ b/src/pmie/examples/uag.head
@@ -0,0 +1,3 @@
+//
+// Examples from the Performance Co-Pilot User's and Administrator's Guide
+//
diff --git a/src/pmie/examples/upm.00 b/src/pmie/examples/upm.00
new file mode 100644
index 0000000..1b9e04c
--- /dev/null
+++ b/src/pmie/examples/upm.00
@@ -0,0 +1,6 @@
+//
+// If the total context switch rate exceeds 10000 per second per CPU
+// then display an alarm notifier
+//
+kernel.all.pswitch / hinv.ncpu > 10000 count/sec
+ -> alarm "high context switch rate %v";
diff --git a/src/pmie/examples/upm.01 b/src/pmie/examples/upm.01
new file mode 100644
index 0000000..d83bd79
--- /dev/null
+++ b/src/pmie/examples/upm.01
@@ -0,0 +1,4 @@
+all_sample (
+ kernel.all.pswitch @0..9 > 10 Kcount/sec * hinv.ncpu
+) -> shell 5 min "xwsh -e 'top'";
+
diff --git a/src/pmie/examples/upm.02 b/src/pmie/examples/upm.02
new file mode 100644
index 0000000..eaa0c42
--- /dev/null
+++ b/src/pmie/examples/upm.02
@@ -0,0 +1,19 @@
+delta = 5 sec; // force evaluation once every 5 seconds from here on
+
+// If for any disk, for all 4 samples (20 seconds), the disk is performing
+// more than 40 I/Os per second, then print a message to standard output and
+// then launch dkvis(1)
+//
+some_inst all_sample
+ disk.dev.total @0..3 > 40 count/sec
+ -> print "disks busy for 20 sec:" " %i" &
+ shell 5 min "dkvis";
+
+// If any disk is performing more than 60 I/Os per second, then
+// print a message identifying the busy disk to standard output and
+// launch dkvis(1)
+some_inst (
+ disk.dev.total > 60 count/sec
+) -> print "busy disks:" " %i" &
+ shell 5 min "dkvis";
+
diff --git a/src/pmie/examples/upm.03 b/src/pmie/examples/upm.03
new file mode 100644
index 0000000..ff7501e
--- /dev/null
+++ b/src/pmie/examples/upm.03
@@ -0,0 +1,8 @@
+//
+// Refine the preceding rule to apply only between the hours of 9am and 5pm,
+// and to require that just 3 of the four samples exceed the threshold
+//
+$hour >= 9 && $hour <= 17 && some_inst 75 %_sample
+ disk.dev.total @0..3 > 40 count/sec
+ -> print "disk busy for 20 sec" &
+ shell 5 min "dkvis";
diff --git a/src/pmie/examples/upm.04 b/src/pmie/examples/upm.04
new file mode 100644
index 0000000..36c9699
--- /dev/null
+++ b/src/pmie/examples/upm.04
@@ -0,0 +1,10 @@
+//
+// Refine the preceding rule further to print the host name and disk name
+// for which the threshold is exceeded
+//
+$hour >= 9 && $hour <= 17 &&
+some_inst (
+ 75 %_sample (
+ disk.dev.total @0..3 > 40 count/sec
+ )
+) -> print "disks busy for 20 sec:" " [%h]%i";
diff --git a/src/pmie/examples/upm.05 b/src/pmie/examples/upm.05
new file mode 100644
index 0000000..702747e
--- /dev/null
+++ b/src/pmie/examples/upm.05
@@ -0,0 +1,9 @@
+//
+// Macro for use ...
+//
+bc = "buffer_cache";
+// Using the above macro; If the buffer cache is in use (more than 50 read
+// requests) with hit ratio less than 90%, then popup an alarm
+//
+$bc.getblks > 50 && $bc.getfound / $bc.getblks < 0.9
+ -> alarm "poor buffer cache hit rate";
diff --git a/src/pmie/examples/upm.06 b/src/pmie/examples/upm.06
new file mode 100644
index 0000000..dbf9da9
--- /dev/null
+++ b/src/pmie/examples/upm.06
@@ -0,0 +1,10 @@
+delta = 10 mins; // force evaluation once every 10 minutes from here on
+
+// If either the / or the /usr filesystem is more than 95% full, display
+// an alarm popup, but not if it has already been displayed during the last
+// 24 hours
+//
+filesys.free #'/dev/root' / filesys.capacity #'/dev/root' < 0.05
+ -> alarm 24 hour "root filesystem (almost) full";
+filesys.free #'/dev/usr' / filesys.capacity #'/dev/usr' < 0.05
+ -> alarm 24 hour "/usr filesystem (almost) full";
diff --git a/src/pmie/examples/upm.07 b/src/pmie/examples/upm.07
new file mode 100644
index 0000000..f325ee6
--- /dev/null
+++ b/src/pmie/examples/upm.07
@@ -0,0 +1,8 @@
+//
+// The following rule requires a machine that supports the PCP environment
+// metrics. If the machine environment temperature rises more than 2
+// degrees over a 10 minute interval, write an entry in the system log
+//
+environ.temp @1 - environ.temp @0 > 2
+ -> alarm "temperature rising fast" &
+ syslog "machine room temperature rise alarm";
diff --git a/src/pmie/examples/upm.08 b/src/pmie/examples/upm.08
new file mode 100644
index 0000000..8ab0e49
--- /dev/null
+++ b/src/pmie/examples/upm.08
@@ -0,0 +1,13 @@
+//
+// Something interesting if you have performance problems with
+// your Oracle data base ...
+//
+db = "oracle.ptg1";
+host = ":moomba.melbourne.sgi.com";
+lru = "#'cache buffers lru chain'";
+gets = "$db.latch.gets $host $lru";
+total = "$db.latch.gets $host $lru + $db.latch.misses $host $lru +
+ $db.latch.immisses $host $lru";
+
+$total > 100 && $gets / $total < 0.2
+ -> alarm "high LRU latch contention";
diff --git a/src/pmie/examples/upm.09 b/src/pmie/examples/upm.09
new file mode 100644
index 0000000..e2f12cf
--- /dev/null
+++ b/src/pmie/examples/upm.09
@@ -0,0 +1,11 @@
+// Busy disk?
+
+delta = 20 sec; // force evaluation once every 20 seconds from here on
+
+// If any disk is performing more than 60 I/Os per second, then
+// print a message to standard output and launch dkvis(1)
+//
+some_inst
+ disk.dev.total > 60 count/sec
+ -> print "disk busy for 20 sec" "%v IOPS %i@%h" &
+ shell 5 min "dkvis";
diff --git a/src/pmie/examples/upm.10 b/src/pmie/examples/upm.10
new file mode 100644
index 0000000..ce40c1a
--- /dev/null
+++ b/src/pmie/examples/upm.10
@@ -0,0 +1,11 @@
+delta = 1 minute;
+ruleset
+ kernel.all.load #'1 minute' > 10 * hinv.ncpu ->
+ print "extreme load average %v"
+else kernel.all.load #'1 minute' > 2 * hinv.ncpu ->
+ print "moderate load average %v"
+unknown ->
+ print "load average unavailable"
+otherwise ->
+ print "load average OK"
+;
diff --git a/src/pmie/examples/upm.head b/src/pmie/examples/upm.head
new file mode 100644
index 0000000..3a286e9
--- /dev/null
+++ b/src/pmie/examples/upm.head
@@ -0,0 +1,5 @@
+//
+// Examples from the pmie(1) man page
+//
+
+delta = 1 sec; // force evaluation once per second
diff --git a/src/pmie/examples/webreport.00 b/src/pmie/examples/webreport.00
new file mode 100644
index 0000000..70e04d8
--- /dev/null
+++ b/src/pmie/examples/webreport.00
@@ -0,0 +1,8 @@
+//
+// Request rate throughput (requests per second) summaries
+//
+
+// you may replace the metric below with any of the other
+// web.allservers.requests metrics
+
+request_rate = web.allservers.requests.total;
diff --git a/src/pmie/examples/webreport.01 b/src/pmie/examples/webreport.01
new file mode 100644
index 0000000..cab9272
--- /dev/null
+++ b/src/pmie/examples/webreport.01
@@ -0,0 +1,8 @@
+//
+// Data throughput (Kbytes per minute) summaries
+//
+
+// you may replace the metric below with any of the other
+// web.allservers.bytes metrics
+
+data_rate = web.allservers.bytes.total * 60 / 1024;
diff --git a/src/pmie/examples/webreport.head b/src/pmie/examples/webreport.head
new file mode 100644
index 0000000..91deb82
--- /dev/null
+++ b/src/pmie/examples/webreport.head
@@ -0,0 +1,11 @@
+//
+// Some Common Performance Monitoring Scenarios
+//
+// The WEBREPORT Group
+//
+// Intended to be used with archive logs to produce summaries, e.g.
+// pmie -v -A 1hour -S @10:00am -T @2:00pm -a somearchive WEBREPORT
+// produces hourly summaries for the hours 10am to 2pm
+//
+
+delta = 1 hour; // change to suit, else use -t from the command line
diff --git a/src/pmie/pmie.service.in b/src/pmie/pmie.service.in
new file mode 100644
index 0000000..28d228b
--- /dev/null
+++ b/src/pmie/pmie.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Performance Metrics Inference Engine
+Documentation=man:pmie(1)
+After=local-fs.target network.target
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmie start
+ExecStop=@path@/pmie stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmie/pmie2col.sh b/src/pmie/pmie2col.sh
new file mode 100755
index 0000000..fafe542
--- /dev/null
+++ b/src/pmie/pmie2col.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+#
+# A crude ascii reporting tool, convert pmie to column output
+#
+# The pmie rules need to be of the form:
+# load_1 = kernel.all.load #'1 minute';
+# idle = kernel.all.cpu.idle;
+# column_name=some other expression;
+# ...
+#
+# Each pmie expression has to produce a singular value.
+#
+# With timestamps (pmie -e or pmie output from a PCP archive), lines look like
+# metric (Tue Feb 13 05:01:19 2001): value
+# load_1 (Tue Dec 23 12:20:45 2003): 0.24
+# the first sed step in the filter sorts this out.
+#
+# First e-mailed to Patrick Aland <paland@stetson.edu> and pcp@oss.sgi.com
+# on Wed, 24 Jan 2001.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+# For interactive use, works better with line buffered output from sed(1)
+# and awk(1).
+#
+case "$PCP_PLATFORM"
+in
+ linux|mingw|kfreebsd|gnu)
+ SED="sed -u"
+ ;;
+ freebsd|darwin)
+ SED="sed -l"
+ ;;
+ *)
+ SED=sed
+ ;;
+esac
+
+echo > $tmp/usage
+cat >> $tmp/usage <<EOF
+Options:
+ -d=CHAR,--delimiter=CHAR set the output delimiter character
+ -p=N,--precision=N set the output floating point precision
+ -w=N,--width=N set the width of each column of output
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit 1
+}
+
+pre=2 # floating point precision
+wid=7 # reporting column width
+delim=' '
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -w) wid="$2"
+ shift
+ ;;
+ -p) pre="$2"
+ shift
+ ;;
+ -d) delim="$2"
+ shift
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ ;;
+ esac
+ shift
+done
+
+[ $# -eq 0 ] || _usage
+
+# culled output at the start is produced by pmie and/or pmafm
+$SED \
+ -e '/^pmie: timezone set to/d' \
+ -e '/^Note: running pmie serially, once per archive$/d' \
+ -e '/^Host: [A-Za-z0-9]* Archive: /d' \
+ -e '/^[^ ][^ ]* ([A-Z][a-z][a-z] [A-Z][a-z][a-z] *[0-9][0-9]* [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [0-9][0-9][0-9][0-9]): /{
+s/ (/|/
+s/): /|/
+}' \
+ -e '/^\([^ ][^ ]*\):/s//\1||/' \
+| awk -F\| -v delim="$delim" '
+NF == 0 { if (state == 0) {
+ ncol = i
+ print ""
+ }
+ if (stamp != "") printf "%24s ",stamp
+ for (i = 0; i < ncol; i++) {
+ if (v[i] == "?")
+ # no value
+ printf "%s%'$wid'.'$wid's",delim,"?"
+ else if (v[i]+0 == v[i])
+ # number
+ printf "%s%'$wid'.'$pre'f",delim,v[i]
+ else
+ # string
+ printf "%s%'$wid'.'$wid's",delim,v[i]
+ v[i] = "?"
+ }
+ print ""
+ i = 0
+ stamp = ""
+ state = 1
+ next
+ }
+NF == 3 && stamp == "" { stamp = $2 }
+state == 0 { if (i == 0 && stamp != "") printf "%24s ",""
+ printf "%s%'$wid'.'$wid's",delim,$1 }
+ { v[i++] = $NF }'
+
+status=0
+exit
diff --git a/src/pmie/pmie_check.sh b/src/pmie/pmie_check.sh
new file mode 100644
index 0000000..273903b
--- /dev/null
+++ b/src/pmie/pmie_check.sh
@@ -0,0 +1,691 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1998-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Administrative script to check pmie processes are alive, and restart
+# them as required.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMIE=pmie
+PMIECONF="$PCP_BIN_DIR/pmieconf"
+
+# error messages should go to stderr, not the GUI notifiers
+unset PCP_STDERR
+
+# added to handle problem when /var/log/pcp is a symlink, as first
+# reported by Micah_Altman@harvard.edu in Nov 2001
+#
+_unsymlink_path()
+{
+ [ -z "$1" ] && return
+ __d=`dirname $1`
+ __real_d=`cd $__d 2>/dev/null && $PWDCMND`
+ if [ -z "$__real_d" ]
+ then
+ echo $1
+ else
+ echo $__real_d/`basename $1`
+ fi
+}
+
+# constant setup
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+echo >$tmp/lock
+trap "rm -rf \`[ -f $tmp/lock ] && cat $tmp/lock\` $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+# control file for pmie administration ... edit the entries in this
+# file to reflect your local configuration
+#
+CONTROL=$PCP_PMIECONTROL_PATH
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the third column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+[ -z "$PWDCMND" ] && PWDCMND=/bin/pwd
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+# determine whether we can automatically enable any events sinks
+CONFARGS="-cF"
+if which esplogger >/dev/null 2>&1
+then
+ CONFARGS='m global syslog_prefix $esp_prefix$'
+fi
+
+# option parsing
+#
+SHOWME=false
+MV=mv
+RM=rm
+CP=cp
+KILL=pmsignal
+TERSE=false
+VERBOSE=false
+VERY_VERBOSE=false
+CHECK_RUNLEVEL=false
+START_PMIE=true
+
+echo > $tmp/usage
+cat >> $tmp/usage << EOF
+Options:
+ -c=FILE,--control=FILE configuration of pmie instances to manage
+ -C query system service runlevel information
+ -N,--showme perform a dry run, showing what would be done
+ -s,--stop stop pmie processes instead of starting them
+ -T,--terse produce a terser form of output
+ -V,--verbose increase diagnostic verbosity
+ --help
+EOF
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -C) CHECK_RUNLEVEL=true
+ ;;
+ -N) SHOWME=true
+ MV="echo + mv"
+ RM="echo + rm"
+ CP="echo + cp"
+ KILL="echo + kill"
+ ;;
+ -s) START_PMIE=false
+ ;;
+ -T) TERSE=true
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 0 ]
+then
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+fi
+
+_error()
+{
+ echo "$prog: [$CONTROL:$line]"
+ echo "Error: $1"
+ echo "... automated performance reasoning for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_warning()
+{
+ echo "$prog [$CONTROL:$line]"
+ echo "Warning: $1"
+}
+
+_message()
+{
+ case $1
+ in
+ 'restart')
+ $PCP_ECHO_PROG $PCP_ECHO_N "Restarting pmie for host \"$host\" ...""$PCP_ECHO_C"
+ ;;
+ esac
+}
+
+_lock()
+{
+ # demand mutual exclusion
+ #
+ rm -f $tmp/stamp
+ delay=200 # tenths of a second
+ while [ $delay -ne 0 ]
+ do
+ if pmlock -v $logfile.lock >$tmp/out
+ then
+ echo $logfile.lock >$tmp/lock
+ break
+ else
+ if [ ! -f $tmp/stamp ]
+ then
+ touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ fi
+ if [ -n "`find $logfile.lock ! -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ _warning "removing lock file older than 30 minutes"
+ ls -l $logfile.lock
+ rm -f $logfile.lock
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ done
+
+ if [ $delay -eq 0 ]
+ then
+ # failed to gain mutex lock
+ #
+ if [ -f $logfile.lock ]
+ then
+ _warning "is another PCP cron job running concurrently?"
+ ls -l $logfile.lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($logfile.lock) ..."
+ continue
+ fi
+}
+
+_unlock()
+{
+ rm -f $logfile.lock
+ echo >$tmp/lock
+}
+
+_check_logfile()
+{
+ if [ ! -f $logfile ]
+ then
+ echo "$prog: Error: cannot find pmie output file at \"$logfile\""
+ if $TERSE
+ then
+ :
+ else
+ logdir=`dirname $logfile`
+ echo "Directory (`cd $logdir; $PWDCMND`) contents:"
+ LC_TIME=POSIX ls -la $logdir
+ fi
+ else
+ echo "Contents of pmie output file \"$logfile\" ..."
+ cat $logfile
+ fi
+}
+
+_check_pmie()
+{
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " [process $1] ""$PCP_ECHO_C"
+
+ # wait until pmie process starts, or exits
+ #
+ delay=5
+ [ ! -z "$PMCD_CONNECT_TIMEOUT" ] && delay=$PMCD_CONNECT_TIMEOUT
+ x=5
+ [ ! -z "$PMCD_REQUEST_TIMEOUT" ] && x=$PMCD_REQUEST_TIMEOUT
+
+ # wait for maximum time of a connection and 20 requests
+ #
+ delay=`expr \( $delay + 20 \* $x \) \* 10` # tenths of a second
+ while [ $delay -ne 0 ]
+ do
+ if [ -f $logfile ]
+ then
+ # $logfile was previously removed, if it has appeared again then
+ # we know pmie has started ... if not just sleep and try again
+ #
+ if ls "$PCP_TMP_DIR/pmie/$1" >$tmp/out 2>&1
+ then
+ if grep "No such file or directory" $tmp/out >/dev/null
+ then
+ :
+ else
+ $VERBOSE && echo " done"
+ return 0
+ fi
+ fi
+
+ _plist=`_get_pids_by_name pmie`
+ _found=false
+ for _p in `echo $_plist`
+ do
+ [ $_p -eq $1 ] && _found=true
+ done
+
+ if $_found
+ then
+ # process still here, just hasn't created its status file
+ # yet, try again
+ :
+ else
+ $VERBOSE || _message restart
+ echo " process exited!"
+ if $TERSE
+ then
+ :
+ else
+ echo "$prog: Error: failed to restart pmie"
+ echo "Current pmie processes:"
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS | tee $tmp/tmp | sed -n -e 1p
+ for _p in `echo $_plist`
+ do
+ sed -n -e "/^[ ]*[^ ]* [ ]*$_p /p" < $tmp/tmp
+ done
+ echo
+ fi
+ _check_logfile
+ return 1
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ $VERBOSE && [ `expr $delay % 10` -eq 0 ] && \
+ $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ done
+ $VERBOSE || _message restart
+ echo " timed out waiting!"
+ if $TERSE
+ then
+ :
+ else
+ sed -e 's/^/ /' $tmp/out
+ fi
+ _check_logfile
+ return 1
+}
+
+_get_configfile()
+{
+ # extract the pmie configuration file (-c) from a list of arguments
+ #
+ echo $@ | sed -n \
+ -e 's/^/ /' \
+ -e 's/[ ][ ]*/ /g' \
+ -e 's/-c /-c/' \
+ -e 's/.* -c\([^ ]*\).*/\1/p'
+}
+
+_configure_pmie()
+{
+ # update a pmie configuration file if it should be created/modified
+ #
+ configfile="$1"
+
+ if [ -f "$configfile" ]
+ then
+ # look for "magic" string at start of file, and ensure we created it
+ sed 1q "$configfile" | grep '^// pmieconf-pmie [0-9]' >/dev/null
+ magic=$?
+ grep '^// Auto-generated by pmieconf' "$configfile" >/dev/null
+ owned=$?
+ if [ $magic -eq 0 -a $owned -eq 0 ]
+ then
+ # pmieconf file, see if re-generation is needed
+ cp "$configfile" $tmp/pmie
+ if $PMIECONF -f $tmp/pmie $CONFARGS >$tmp/diag 2>&1
+ then
+ grep -v "generated by pmieconf" "$configfile" >$tmp/old
+ grep -v "generated by pmieconf" $tmp/pmie >$tmp/new
+ if ! diff $tmp/old $tmp/new >/dev/null
+ then
+ if [ -w $configfile ]
+ then
+ $VERBOSE && echo "Reconfigured: \"$configfile\" (pmieconf)"
+ eval $CP $tmp/pmie "$configfile"
+ else
+ _warning "no write access to pmieconf file \"$configfile\", skip reconfiguration"
+ ls -l "$configfile"
+ fi
+ fi
+ else
+ _warning "pmieconf failed to reconfigure \"$configfile\""
+ cat "s;$tmp/pmie;$configfile;g" $tmp/diag
+ echo "=== start pmieconf file ==="
+ cat $tmp/pmie
+ echo "=== end pmieconf file ==="
+ fi
+ fi
+ elif [ ! -e "$configfile" ]
+ then
+ # file does not exist, generate it, if possible
+ if $SHOWME
+ then
+ echo "+ $PMIECONF -f $configfile $CONFARGS"
+ elif ! $PMIECONF -f "$configfile" $CONFARGS >$tmp/diag 2>&1
+ then
+ _warning "pmieconf failed to generate \"$configfile\""
+ cat $tmp/diag
+ echo "=== start pmieconf file ==="
+ cat "$configfile"
+ echo "=== end pmieconf file ==="
+ else
+ chown $PCP_USER:$PCP_GROUP "$configfile" >/dev/null 2>&1
+ fi
+ fi
+}
+
+QUIETLY=false
+if [ $CHECK_RUNLEVEL = true ]
+then
+ # determine whether to start/stop based on runlevel settings - we
+ # need to do this when running unilaterally from cron, else we'll
+ # always start pmie up (even when we shouldn't).
+ #
+ QUIETLY=true
+ if is_chkconfig_on pmie
+ then
+ START_PMIE=true
+ else
+ START_PMIE=false
+ fi
+fi
+
+if [ $START_PMIE = false ]
+then
+ # if pmie has never been started, there's no work to do to stop it
+ [ ! -d "$PCP_TMP_DIR/pmie" ] && exit
+ $QUIETLY || $PCP_BINADM_DIR/pmpost "stop pmie from $prog"
+fi
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+# 1.0 is the first release, and the version is set in the control file
+# with a $version=x.y line
+#
+version=1.0
+eval `grep '^version=' "$CONTROL" | sort -rn`
+if [ $version != "1.0" ]
+then
+ _error "unsupported version (got $version, expected 1.0)"
+ status=1
+ exit
+fi
+
+echo >$tmp/dir
+rm -f $tmp/err $tmp/pmies
+
+line=0
+cat "$CONTROL" \
+ | sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+ | while read host socks logfile args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ logfile_hostname=`hostname || echo localhost`
+ logfile=`echo $logfile | sed -e "s;LOCALHOSTNAME;$logfile_hostname;"`
+ logfile=`_unsymlink_path $logfile`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $socks $logfile $args"
+ cmd=`echo "$host $socks $logfile $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$socks" -o -z "$logfile" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ $VERY_VERBOSE && echo "Check pmie -h $host -l $logfile ..."
+
+ # make sure output directory exists
+ #
+ dir=`dirname $logfile`
+ if [ ! -d "$dir" ]
+ then
+ mkdir -p -m 755 "$dir" >$tmp/err 2>&1
+ if [ ! -d "$dir" ]
+ then
+ cat $tmp/err
+ _error "cannot create directory ($dir) for pmie log file"
+ continue
+ fi
+ chown $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ fi
+
+ cd "$dir"
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ # ensure pcp user will be able to write there
+ #
+ chown -R $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ if [ ! -w "$dir" ]
+ then
+ _warning "no write access in $dir, skip lock file processing"
+ ls -ld "$dir"
+ else
+ _lock
+ fi
+
+ # match $logfile from control file to running pmies
+ pid=""
+ for file in $PCP_TMP_DIR/pmie/[0-9]*
+ do
+ [ "$file" = "$PCP_TMP_DIR/pmie/[0-9]*" ] && continue
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "... try $file: ""$PCP_ECHO_C"
+
+ p_id=`echo $file | sed -e 's,.*/,,'`
+ p_logfile=""
+ p_pmcd_host=""
+
+ # throw away stderr in case $file has been removed by now
+ eval `$PCP_BINADM_DIR/pmiestatus $file 2>/dev/null | $PCP_AWK_PROG '
+NR == 2 { printf "p_logfile=\"%s\"\n", $0; next }
+NR == 3 { printf "p_pmcd_host=\"%s\"\n", $0; next }
+ { next }'`
+
+ p_logfile=`_unsymlink_path $p_logfile`
+ if [ "$p_logfile" != $logfile ]
+ then
+ $VERY_VERBOSE && echo "different logfile, skip"
+ $VERY_VERBOSE && echo " $p_logfile differs to $logfile"
+ elif _get_pids_by_name pmie | grep "^$p_id\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "pmie process $p_id identified, OK"
+ pid=$p_id
+ break
+ else
+ $VERY_VERBOSE && echo "pmie process $p_id not running, skip"
+ $VERY_VERBOSE && _get_pids_by_name pmie
+ fi
+ done
+
+ if $VERY_VERBOSE
+ then
+ if [ -z "$pid" ]
+ then
+ echo "No current pmie process exists for:"
+ else
+ echo "Found pmie process $pid monitoring:"
+ fi
+ echo " host = $host"
+ echo " log file = $logfile"
+ fi
+
+ if [ -z "$pid" -a $START_PMIE = true ]
+ then
+ configfile=`_get_configfile $args`
+ if [ ! -z "$configfile" ]
+ then
+ # if this is a relative path and not relative to cwd,
+ # substitute in the default pmie search location.
+ #
+ if [ ! -f "$configfile" -a "`basename $configfile`" = "$configfile" ]
+ then
+ configfile="$PCP_SYSCONF_DIR/pmie/$configfile"
+ fi
+
+ # check configuration file exists and is up to date
+ _configure_pmie "$configfile" "$host"
+ fi
+
+ args="-h $host -l $logfile $args"
+
+ $VERBOSE && _message restart
+
+ sock_me=''
+ if [ "$socks" = y ]
+ then
+ # only check for pmsocks if it's specified in the control file
+ have_pmsocks=false
+ if which pmsocks >/dev/null 2>&1
+ then
+ # check if pmsocks has been set up correctly
+ if pmsocks ls >/dev/null 2>&1
+ then
+ have_pmsocks=true
+ fi
+ fi
+
+ if $have_pmsocks
+ then
+ sock_me="pmsocks "
+ else
+ echo "$prog: Warning: no pmsocks available, would run without"
+ sock_me=""
+ fi
+ fi
+
+ [ -f "$logfile" ] && eval $MV -f "$logfile" "$logfile.prior"
+
+ if $SHOWME
+ then
+ $VERBOSE && echo
+ echo "+ ${sock_me}$PMIE -b $args"
+ _unlock
+ continue
+ else
+ # since this is launched as a sort of daemon, any output should
+ # go on pmie's stderr, i.e. $logfile ... use -b for this
+ #
+ $VERY_VERBOSE && ( echo; $PCP_ECHO_PROG $PCP_ECHO_N "+ ${sock_me}$PMIE -b $args""$PCP_ECHO_C"; echo "..." )
+ $PCP_BINADM_DIR/pmpost "start pmie from $prog for host $host"
+ ${sock_me}$PMIE -b $args &
+ pid=$!
+ fi
+
+ # wait for pmie to get started, and check on its health
+ _check_pmie $pid
+
+ elif [ ! -z "$pid" -a $START_PMIE = false ]
+ then
+ # Send pmie a SIGTERM, which is noted as a pending shutdown.
+ # Add pid to list of pmies sent SIGTERM - may need SIGKILL later.
+ #
+ $VERY_VERBOSE && echo "+ $KILL -s TERM $pid"
+ eval $KILL -s TERM $pid
+ $PCP_ECHO_PROG $PCP_ECHO_N "$pid ""$PCP_ECHO_C" >> $tmp/pmies
+ fi
+
+ _unlock
+done
+
+# check all the SIGTERM'd pmies really died - if not, use a bigger hammer.
+#
+if $SHOWME
+then
+ :
+elif [ $START_PMIE = false -a -s $tmp/pmies ]
+then
+ pmielist=`cat $tmp/pmies`
+ if ps -p "$pmielist" >/dev/null 2>&1
+ then
+ $VERY_VERBOSE && ( echo; $PCP_ECHO_PROG $PCP_ECHO_N "+ $KILL -KILL `cat $tmp/pmies` ...""$PCP_ECHO_C" )
+ eval $KILL -s KILL $pmielist >/dev/null 2>&1
+ delay=30 # tenths of a second
+ while ps -f -p "$pmielist" >$tmp/alive 2>&1
+ do
+ if [ $delay -gt 0 ]
+ then
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ continue
+ fi
+ echo "$prog: Error: pmie process(es) will not die"
+ cat $tmp/alive
+ status=1
+ break
+ done
+ fi
+fi
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmie/pmie_daily.sh b/src/pmie/pmie_daily.sh
new file mode 100644
index 0000000..3e386c0
--- /dev/null
+++ b/src/pmie/pmie_daily.sh
@@ -0,0 +1,540 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2007 Aconex. All Rights Reserved.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Daily administrative script for pmie logfiles
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+# added to handle problem when /var/log/pcp is a symlink, as first
+# reported by Micah_Altman@harvard.edu in Nov 2001
+#
+_unsymlink_path()
+{
+ [ -z "$1" ] && return
+ __d=`dirname $1`
+ __real_d=`cd $__d 2>/dev/null && $PWDCMND`
+ if [ -z "$__real_d" ]
+ then
+ echo $1
+ else
+ echo $__real_d/`basename $1`
+ fi
+}
+
+# error messages should go to stderr, not the GUI notifiers
+#
+unset PCP_STDERR
+
+# constant setup
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+echo >$tmp/lock
+trap "rm -rf \`[ -f $tmp/lock ] && cat $tmp/lock\` $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+if is_chkconfig_on pmie
+then
+ PMIE_CTL=on
+else
+ PMIE_CTL=off
+fi
+
+# control file for pmie administration ... edit the entries in this
+# file to reflect your local configuration (see also -c option below)
+#
+CONTROL=$PCP_PMIECONTROL_PATH
+
+# default number of days to keep pmie logfiles
+#
+CULLAFTER=14
+
+# default compression program and days until starting compression
+#
+COMPRESS=xz
+COMPRESSAFTER=""
+COMPRESSREGEX="\.(meta|index|Z|gz|bz2|zip|xz|lzma|lzo|lz4)$"
+
+# mail addresses to send daily logfile summary to
+#
+MAILME=""
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the third column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+# (see BugWorks ID #595416).
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+if [ -z "$PWDCMND" ]
+then
+ # Looks like we have no choice here...
+ # force it to a known IRIX location
+ PWDCMND=/bin/pwd
+fi
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+echo > $tmp/usage
+cat >> $tmp/usage <<EOF
+Options:
+ -c=FILE,--control=FILE pmie control file
+ -k=N,--discard=N remove pmie log files after N days
+ -m=ADDRs,--mail=ADDRs send daily log files to email addresses
+ -N,--showme perform a dry run, showing what would be done
+ -V,--verbose verbose output (multiple times for very verbose)
+ -x=N,--compress-after=N compress pmie log files after N days
+ -X=PROGRAM,--compressor=PROGRAM use PROGRAM for pmie log file compression
+ -Y=REGEX,--regex=REGEX egrep filter when compressing files ["$COMPRESSREGEX"]
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ status=1
+ exit
+}
+
+# option parsing
+#
+SHOWME=false
+RM=rm
+KILL=pmsignal
+VERBOSE=false
+VERY_VERBOSE=false
+MYARGS=""
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -k) CULLAFTER="$2"
+ shift
+ check=`echo "$CULLAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" -a X"$check" != Xforever ]
+ then
+ echo "Error: -k option ($CULLAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -m) MAILME="$2"
+ shift
+ ;;
+ -N) SHOWME=true
+ RM="echo + rm"
+ KILL="echo + kill"
+ MYARGS="$MYARGS -N"
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ MYARGS="$MYARGS -V"
+ ;;
+ -x) COMPRESSAFTER="$2"
+ shift
+ check=`echo "$COMPRESSAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" ]
+ then
+ echo "Error: -x option ($COMPRESSAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -X) COMPRESS="$2"
+ shift
+ ;;
+ -Y) COMPRESSREGEX="$2"
+ shift
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ ;;
+ esac
+ shift
+done
+
+[ $# -ne 0 ] && _usage
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+SUMMARY_LOGNAME=`pmdate -1d %Y%m%d`
+
+_error()
+{
+ _report Error "$1"
+}
+
+_warning()
+{
+ _report Warning "$1"
+}
+
+_report()
+{
+ echo "$prog: $1: $2"
+ echo "[$CONTROL:$line] ... inference engine for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_unlock()
+{
+ rm -f lock
+ echo >$tmp/lock
+}
+
+# filter for pmie log files in working directory -
+# pass in the number of days to skip over (backwards) from today
+#
+# pv:821339 too many sed commands for IRIX ... split into groups
+# of at most 200 days
+#
+_date_filter()
+{
+ # start with all files whose names match the patterns used by
+ # the PCP pmie log file management scripts ... this list may be
+ # reduced by the sed filtering later on
+ #
+ ls | sed -n >$tmp/in -e '/[-.][12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]/p'
+
+ i=0
+ while [ $i -le $1 ]
+ do
+ dmax=`expr $i + 200`
+ [ $dmax -gt $1 ] && dmax=$1
+ echo "/[-.][12][0-9][0-9][0-9][0-1][0-9][0-3][0-9]/{" >$tmp/sed1
+ while [ $i -le $dmax ]
+ do
+ x=`pmdate -${i}d %Y%m%d`
+ echo "/[-.]$x/d" >>$tmp/sed1
+ i=`expr $i + 1`
+ done
+ echo "p" >>$tmp/sed1
+ echo "}" >>$tmp/sed1
+
+ # cull file names with matching dates, keep other file names
+ #
+ sed -n -f $tmp/sed1 <$tmp/in >$tmp/tmp
+ mv $tmp/tmp $tmp/in
+ done
+
+ cat $tmp/in
+}
+
+
+rm -f $tmp/err $tmp/mail
+line=0
+version=''
+cat $CONTROL \
+| sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+| while read host socks logfile args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ logfile_hostname=`hostname || echo localhost`
+ logfile=`echo $logfile | sed -e "s;LOCALHOSTNAME;$logfile_hostname;"`
+ logfile=`_unsymlink_path $logfile`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ $VERY_VERBOSE && echo "[control:$line] host=\"$host\" socks=\"$socks\" log=\"$logfile\" args=\"$args\""
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $socks $logfile $args"
+ cmd=`echo "$host $socks $logfile $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$socks" -o -z "$logfile" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ dir=`dirname $logfile`
+ $VERY_VERBOSE && echo "Check pmie -h $host ... in $dir ..."
+
+ if [ ! -d "$dir" ]
+ then
+ if [ "$PMIE_CTL" = "on" ]
+ then
+ _error "logfile directory ($dir) does not exist"
+ fi
+ continue
+ fi
+
+ cd $dir
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ if $VERBOSE
+ then
+ echo
+ echo "=== daily maintenance of pmie log files for host $host ==="
+ echo
+ fi
+
+ if [ ! -w $dir ]
+ then
+ echo "$prog: Warning: no write access in $dir, skip lock file processing"
+ else
+ # demand mutual exclusion
+ #
+ fail=true
+ rm -f $tmp/stamp
+ for try in 1 2 3 4
+ do
+ if pmlock -v lock >$tmp/out
+ then
+ echo $dir/lock >$tmp/lock
+ fail=false
+ break
+ else
+ if [ ! -f $tmp/stamp ]
+ then
+ touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ fi
+ if [ ! -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ :
+ else
+ echo "$prog: Warning: removing lock file older than 30 minutes"
+ LC_TIME=POSIX ls -l $dir/lock
+ rm -f lock
+ fi
+ fi
+ sleep 5
+ done
+
+ if $fail
+ then
+ # failed to gain mutex lock
+ #
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: is another PCP cron job running concurrently?"
+ LC_TIME=POSIX ls -l $dir/lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($dir/lock) ..."
+ continue
+ fi
+ fi
+
+ # match $logfile from control file to running pmies
+ pid=""
+ $VERY_VERBOSE && echo "Looking for logfile=$logfile"
+ for file in `ls "$PCP_TMP_DIR/pmie"`
+ do
+ p_id=$file
+ file="$PCP_TMP_DIR/pmie/$file"
+ p_logfile=""
+ p_pmcd_host=""
+
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "Check p_id=$p_id ... ""$PCP_ECHO_C"
+ if ps -p "$p_id" >/dev/null 2>&1
+ then
+ eval `$PCP_BINADM_DIR/pmiestatus $file | $PCP_AWK_PROG '
+NR == 2 { printf "p_logfile=\"%s\"\n", $0; next }
+NR == 3 { printf "p_pmcd_host=\"%s\"\n", $0; next }
+ { next }'`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "p_pmcd_host=$p_pmcd_host p_logfile=$p_logfile""$PCP_ECHO_C"
+ p_logfile=`_unsymlink_path $p_logfile`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "->$p_logfile ... ""$PCP_ECHO_C"
+ if [ "$p_logfile" = $logfile ]
+ then
+ pid=$p_id
+ $VERY_VERBOSE && $PCP_ECHO_PROG match
+ break
+ fi
+ $VERY_VERBOSE && $PCP_ECHO_PROG "no match"
+ else
+ # ignore, its not a running process
+ eval $RM -f $file
+ $VERY_VERBOSE && $PCP_ECHO_PROG "process has vanished"
+ fi
+ done
+
+ if [ -z "$pid" ]
+ then
+ if [ "$PMIE_CTL" = "on" ]
+ then
+ _error "no pmie instance running for host \"$host\""
+ fi
+ else
+ # now move current logfile name aside and SIGHUP to "roll the logs"
+ # creating a new logfile with the old name in the process.
+ #
+ $SHOWME && echo "+ mv $logfile ${logfile}.{SUMMARY_LOGNAME}"
+ if mv $logfile ${logfile}.${SUMMARY_LOGNAME}
+ then
+ $VERY_VERBOSE && echo "+ $KILL -s HUP $pid"
+ eval $KILL -s HUP $pid
+ echo ${logfile}.${SUMMARY_LOGNAME} >> $tmp/mail
+ else
+ _error "problems moving logfile \"$logfile\" for host \"$host\""
+ touch $tmp/err
+ fi
+ fi
+
+ # and cull old logfiles
+ #
+ if [ X"$CULLAFTER" != X"forever" ]
+ then
+ _date_filter $CULLAFTER >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Log files older than $CULLAFTER days being removed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + rm -f
+ else
+ cat $tmp/list | xargs rm -f
+ fi
+ fi
+ fi
+
+ # finally, compress old log files
+ # (after cull - don't compress unnecessarily)
+ #
+ if [ ! -z "$COMPRESSAFTER" ]
+ then
+ _date_filter $COMPRESSAFTER | egrep -v "$COMPRESSREGEX" >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Log files older than $COMPRESSAFTER days being compressed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + $COMPRESS
+ else
+ cat $tmp/list | xargs $COMPRESS
+ fi
+ fi
+ fi
+
+ _unlock
+
+done
+
+if [ -n "$MAILME" -a -s $tmp/mail ]
+then
+ logs=""
+ for file in `cat $tmp/mail`
+ do
+ [ -f $file ] && logs="$logs $file"
+ done
+ egrep -v '( OK | OK$|^$|^Log |^pmie: PID)' $logs > $tmp/logmail
+ if [ ! -s "$tmp/logmail" ]
+ then
+ :
+ elif [ ! -z "$MAIL" ]
+ then
+ egrep -v '( OK | OK$|^$)' $logs | \
+ $MAIL -s "PMIE summary for $LOCALHOSTNAME" $MAILME
+ else
+ echo "$prog: PMIE summary for $LOCALHOSTNAME ..."
+ egrep -v '( OK | OK$|^$)' $logs
+ fi
+ rm -f $tmp/mail $tmp/logmail
+fi
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmie/rc_pmie b/src/pmie/rc_pmie
new file mode 100644
index 0000000..acef291
--- /dev/null
+++ b/src/pmie/rc_pmie
@@ -0,0 +1,273 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 94 06
+# description: pmie is a performance inference engine for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmie
+# Required-Start: $remote_fs
+# Should-Start: $local_fs $network $syslog $time $pmcd
+# Required-Stop: $remote_fs
+# Should-Stop: $local_fs $network $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmie (performance inference engine for PCP)
+# Description: Configure and control pmie (the performance inference engine for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMIECTRL=$PCP_PMIECONTROL_PATH
+pmprog=$PCP_RC_DIR/pmie
+prog=$PCP_RC_DIR/`basename $0`
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if [ $pmprog = $prog ]
+then
+ VERBOSE_CTL=on
+else
+ VERBOSE_CTL=off
+fi
+
+id=`id | sed -e "s/(.*//" -e "s/.*=//"`
+if [ "$id" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop pmie.'
+ exit
+ fi
+fi
+
+_usage()
+{
+ echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+_reboot_setup()
+{
+ # base directories and house-keeping for pmie instances
+ #
+ if [ ! -d "$PCP_TMP_DIR/pmie" ]
+ then
+ mkdir -p -m 0775 "$PCP_TMP_DIR/pmie"
+ chown $PCP_USER:$PCP_GROUP "$PCP_TMP_DIR/pmie"
+ else
+ rm -rf $tmp/ent $tmp/pid
+ here=`pwd`
+ cd "$PCP_TMP_DIR/pmie"
+ _get_pids_by_name pmie | sort >$tmp/pid
+ ls [0-9]* 2>&1 | sed -e '/\[0-9]\*/d' \
+ | sed -e 's/[ ][ ]*//g' | sort >$tmp/ent
+ # remove entries without a pmie process
+ rm -f `comm -23 $tmp/ent $tmp/pid`
+ rm -f $tmp/ent $tmp/pid
+ cd "$here"
+ fi
+}
+
+# Note: _start_pmie is running in the background, in parallel with
+# the rest of the script. It might complete well after the caller
+# so tmpfile handling is especially problematic. Goal is to speed
+# bootup by starting potentially slow (remote monitoring) processes
+# in the background.
+#
+_start_pmie()
+{
+ bgstatus=0
+ bgtmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+ trap "rm -rf $bgtmp; exit \$bgstatus" 0 1 2 3 15
+
+ wait_option=''
+ [ ! -z "$PMCD_WAIT_TIMEOUT" ] && wait_option="-t $PMCD_WAIT_TIMEOUT"
+
+ if pmcd_wait $wait_option
+ then
+ pmie_check >$bgtmp/pmie 2>&1
+ bgstatus=$?
+ if [ -s $bgtmp/pmie ]
+ then
+ $PCP_BINADM_DIR/pmpost "pmie_check start failed in $prog, mailing output to root"
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "pmie_check start failed in $prog" root <$bgtmp/pmie >/dev/null 2>&1
+ else
+ echo "$prog: pmie_check start failed ..."
+ cat $bgtmp/pmie
+ fi
+ fi
+ else
+ bgstatus=$?
+ $PCP_BINADM_DIR/pmpost "pmcd_wait failed in $prog: exit status: $bgstatus"
+ if [ ! -z "$MAIL" ]
+ then
+ echo "pmcd_wait: exit status: $bgstatus" | $MAIL -s "pmcd_wait failed in $prog" root
+ else
+ echo "$prog: pmcd_wait failed ..."
+ echo "pmcd_wait: exit status: $bgstatus"
+ fi
+ fi
+ exit $bgstatus # co-process is now complete
+}
+
+_shutdown()
+{
+ _get_pids_by_name pmie >$tmp/pmies 2>&1
+ if [ ! -s $tmp/pmies ]
+ then
+ [ "$1" = verbose ] && echo "$pmprog: PMIE not running"
+ return 0
+ fi
+
+ [ "$1" = quietly ] || \
+ $ECHO $PCP_ECHO_N "Waiting for pmie process(es) to terminate ..." "$PCP_ECHO_C"
+
+ pmie_check -s >$tmp/pmie 2>&1
+ if [ -s $tmp/pmie ]
+ then
+ $PCP_BINADM_DIR/pmpost "pmie_check stop failed in $prog, mailing output to root"
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "pmie_check stop failed in $prog" root <$tmp/pmie
+ else
+ echo "$prog: pmie_check stop failed ..."
+ cat $tmp/pmie
+ fi
+ fi
+
+ true
+ $RC_STATUS -v
+ rm -fr "$tmp/pmie" "$PCP_TMP_DIR"/pmie/*
+ $PCP_BINADM_DIR/pmpost "stop pmie from $pmprog"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+else # For a quiet startup and shutdown
+ ECHO=:
+fi
+
+case "$1" in
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmie
+ then
+ status=0
+ exit
+ fi
+
+ _shutdown quietly
+
+ # messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ _reboot_setup
+
+ if which pmie >/dev/null 2>&1
+ then
+ if is_chkconfig_on pmie
+ then
+ if which pmie_check >/dev/null 2>&1
+ then
+ if [ ! -f $PMIECTRL ]
+ then
+ echo "$prog:"'
+Error: PCP inference engine control file '$PMIECTRL'
+ is missing! Cannot start any Performance Co-Pilot inference engine(s).'
+ else
+ $ECHO $PCP_ECHO_N "Starting pmie ..." "$PCP_ECHO_C"
+ _start_pmie &
+ $RC_STATUS -v
+ fi
+ fi
+ elif [ "$0" = "$PCP_RC_DIR/pmie" ]
+ then
+ echo "$prog: Warning: Performance Co-Pilot Inference Engine (pmie) is disabled."
+ chkconfig_on_msg pmie
+ fi
+ fi
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown verbose
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmie:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC pmie
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC pmie
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmie/src/GNUmakefile b/src/pmie/src/GNUmakefile
new file mode 100644
index 0000000..86984bb
--- /dev/null
+++ b/src/pmie/src/GNUmakefile
@@ -0,0 +1,79 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+TARGET = pmie$(EXECSUFFIX)
+
+CFILES = pmie.c symbol.c dstruct.c lexicon.c syntax.c pragmatics.c eval.c \
+ show.c match_inst.c systemlog.c stomp.c andor.c
+
+HFILES = fun.h dstruct.h eval.h lexicon.h pragmatics.h stats.h \
+ show.h symbol.h syntax.h systemlog.h stomp.h andor.h
+
+SKELETAL = hdr.sk fetch.sk misc.sk aggregate.sk unary.sk binary.sk \
+ merge.sk act.sk
+
+LSRCFILES = $(SKELETAL) meta logger.h
+
+LDIRT += $(YFILES:%.y=%.tab.?) fun.c fun.o $(TARGET) grammar.h
+
+LLDLIBS = $(PCPLIB) $(LIB_FOR_MATH) $(LIB_FOR_REGEX)
+
+LCFLAGS += $(PIECFLAGS)
+LLDFLAGS += $(PIELDFLAGS)
+
+default: $(TARGET)
+
+YFILES = grammar.y
+
+.NOTPARALLEL:
+grammar.h grammar.tab.c: grammar.y
+ $(YACC) -d -b `basename $< .y` $< && cp `basename $@ .h`.tab.h $@
+
+pmie$(EXECSUFFIX): $(OBJECTS) fun.o
+ $(CCF) -o $@ $(LDFLAGS) $(OBJECTS) fun.o $(LDLIBS)
+
+install: default
+ $(INSTALL) -m 755 $(TARGET) $(PCP_BIN_DIR)/$(TARGET)
+
+lexicon.o syntax.o: grammar.h
+
+fun.o: fun.h
+
+fun.c: $(SKELETAL) meta
+ @echo $@
+ ./meta
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
+
+fun.h: andor.h
+andor.o dstruct.o eval.o fun.o grammar.tab.o lexicon.o match_inst.o pmie.o pragmatics.o show.o syntax.o systemlog.o: dstruct.h
+dstruct.o eval.o pmie.o pragmatics.o syntax.o systemlog.o: eval.h
+andor.o dstruct.o eval.o fun.o match_inst.o: fun.h
+lexicon.o syntax.o: grammar.h
+grammar.tab.o lexicon.o show.o syntax.o: lexicon.h
+systemlog.o: logger.h
+andor.o dstruct.o eval.o fun.o grammar.tab.o lexicon.o pmie.o pragmatics.o show.o syntax.o: pragmatics.h
+andor.o dstruct.o eval.o fun.o grammar.tab.o match_inst.o pmie.o show.o syntax.o: show.h
+andor.o fun.o grammar.tab.o pmie.o stomp.o: stomp.h
+andor.o dstruct.o eval.o fun.o grammar.tab.o lexicon.o match_inst.o pmie.o pragmatics.o show.o symbol.o syntax.o systemlog.o: symbol.h
+grammar.tab.o lexicon.o pmie.o syntax.o systemlog.o: syntax.h
+fun.o grammar.tab.o systemlog.o: systemlog.h
+
diff --git a/src/pmie/src/README.DEBUG b/src/pmie/src/README.DEBUG
new file mode 100644
index 0000000..52eca13
--- /dev/null
+++ b/src/pmie/src/README.DEBUG
@@ -0,0 +1,19 @@
+Notes to assist in debugging pmie
+
+pmie command line debug flags
+ APPL0 - lexical scanning
+ APPL1 - parse/expression tree construction
+ APPL2 - expression execution
+
+macro EVALARG(x) expands to
+ if ((x)->op < NOP) ((x)->eval)(x);
+
+The source file fun.c is generated by expansion of all of the *.sk
+"skeletal" files ... so changes need to be made in the *.sk files and
+fun.c will be recreated in the make.
+
+The Expr structure (defined in dstruct.h) is at the core of the pmie
+expression evaluation.
+ valid must be > 0 for there to be any values able to be
+ used in the expression evaluation
+
diff --git a/src/pmie/src/act.sk b/src/pmie/src/act.sk
new file mode 100644
index 0000000..358ef53
--- /dev/null
+++ b/src/pmie/src/act.sk
@@ -0,0 +1,376 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: act.sk - actions
+ *
+ ***********************************************************************/
+
+/*
+ * operator: actAnd
+ */
+void
+actAnd(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ *(Boolean *)x->ring = (*(Boolean *)arg1->ring == B_TRUE) && (*(Boolean *)arg2->ring == B_TRUE);
+}
+
+
+/*
+ * operator: actOr
+ */
+void
+actOr(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+
+ EVALARG(arg1)
+ if (*(Boolean *)arg1->ring == B_FALSE) {
+ EVALARG(arg2)
+ *(Boolean *)x->ring = *(Boolean *)arg2->ring;
+ }
+ else *(Boolean *)x->ring = B_TRUE;
+}
+
+
+/*
+ * operator: actShell
+ */
+void
+actShell(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+#ifndef IS_MINGW
+ pid_t pid;
+#endif
+ int sts;
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ EVALARG(arg1)
+ fflush(stdout);
+ fflush(stderr);
+#ifdef IS_MINGW
+ putenv("IFS=\t\n");
+ sts = system((char *)arg1->ring);
+ need_wait = 1;
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "spawn for shell failed\n");
+ *(Boolean *)x->ring = B_FALSE;
+ }
+ else {
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ x->valid = 0;
+ }
+#else /*POSIX*/
+ pid = fork();
+ if (pid == 0) {
+ /* child, run the command */
+ setsid();
+ putenv("PATH=/usr/sbin:/sbin:/usr/bin:/bin");
+ putenv("IFS=\t\n");
+ sts = system((char *)arg1->ring);
+ _exit(WEXITSTATUS(sts)); /* avoid atexit() handler */
+ }
+ else if (pid > 0) {
+ /* parent, wait for child to exit to catch status */
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "actShell: fork: pid=%" FMT_PID "\n", pid);
+ }
+#endif
+ sts = waitpid(pid, &x->valid, 0);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "actShell: wait: pid=%" FMT_PID " status=0x%x", pid, x->valid);
+ if (WIFEXITED(x->valid))
+ fprintf(stderr, " exit=%d", WEXITSTATUS(x->valid));
+ if (WIFSIGNALED(x->valid))
+ fprintf(stderr, " signal=%d", WTERMSIG(x->valid));
+ fprintf(stderr, " (wait returns %d)\n", sts);
+ }
+#endif
+ if (WIFEXITED(x->valid))
+ x->valid = WEXITSTATUS(x->valid);
+ else
+ /* if no exit, then assume non-zero exit, hence failure! */
+ x->valid = 1;
+ if (sts < 0 || x->valid != 0)
+ *(Boolean *)x->ring = B_FALSE;
+ else
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "fork for shell failed\n");
+ *(Boolean *)x->ring = B_FALSE;
+ }
+#endif
+ perf->actions++;
+ }
+}
+
+
+/*
+ * operator: actAlarm
+ */
+void
+actAlarm(Expr *x)
+{
+ static char *alarmv[] = {
+ NULL, /* path to PCP_XCONFIRM_PROG inserted here */
+ "-header", "Performance Co-Pilot Alarm",
+ "-b", "Cancel",
+ "-icon", "warning",
+ "-t", NULL,
+ "-t", NULL,
+ NULL};
+
+ char ctime[26];
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ time_t clock;
+ int sts;
+
+ if (alarmv[0] == NULL) {
+ /*
+ * one trip to get path for xconfirm(1)
+ */
+ alarmv[0] = pmGetConfig("PCP_XCONFIRM_PROG");
+ if (strcmp(alarmv[0], "") == 0) {
+ __pmNotifyErr(LOG_ERR, "PCP_XCONFIRM_PROG not found, using echo(1)\n");
+ alarmv[0] = "/bin/echo";
+ }
+ }
+
+#ifndef IS_MINGW
+ /* if old alarm still active, don't post new one */
+ if (x->valid != 0) {
+ pid_t pid;
+ pid = waitpid((pid_t)x->valid, &sts, WNOHANG);
+ if (pid <= 0) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "actAlarm: wait: pid=%d not done (wait returns %" FMT_PID ")\n", x->valid, pid);
+ }
+#endif
+ return;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "actAlarm: wait: pid=%d done status=0x%x", x->valid, sts);
+ if (WIFEXITED(sts))
+ fprintf(stderr, " exit=%d", WEXITSTATUS(sts));
+ if (WIFSIGNALED(sts))
+ fprintf(stderr, " signal=%d", WTERMSIG(sts));
+ fprintf(stderr, " (wait returns %" FMT_PID ")\n", pid);
+ }
+#endif
+ x->valid = 0;
+ }
+#endif
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ EVALARG(arg1)
+ clock = (time_t)(now+0.5);
+ pmCtime(&clock, ctime);
+#ifdef IS_MINGW
+ alarmv[8] = ctime;
+ alarmv[10] = (char *)arg1->ring;
+ sts = spawnvp(_P_DETACH, alarmv[0], (const char * const *)alarmv);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "spawn for PCP_XCONFIRM_PROG failed\n");
+ *(Boolean *)x->ring = B_FALSE;
+ }
+ else {
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ x->valid = 0;
+ }
+#else
+ sts = fork();
+ if (sts == 0) {
+ alarmv[8] = ctime;
+ alarmv[10] = (char *)arg1->ring;
+ setsid();
+ if (strcmp(alarmv[0], "/bin/echo") != 0) {
+ /* only echo needs stdio, when xconfirm cannot be found */
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+ }
+ execvp(alarmv[0], alarmv);
+ _exit(1); /* avoid atexit() handler */
+ }
+ else if (sts > 0) {
+ need_wait = 1;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "actAlarm: fork: pid=%d\n", sts);
+ }
+#endif
+ x->valid = sts;
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ }
+ else {
+ __pmNotifyErr(LOG_ERR, "fork for alarm failed\n");
+ *(Boolean *)x->ring = B_FALSE;
+ }
+#endif
+ perf->actions++;
+ }
+}
+
+
+/*
+ * operator: actSyslog
+ */
+void
+actSyslog(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ int *pri;
+ char *tag;
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ pri = (int *)arg1->arg2->ring;
+ tag = &((char *)arg1->arg2->ring)[sizeof(int)];
+ EVALARG(arg1)
+ openlog(tag, LOG_PID|LOG_CONS, LOG_DAEMON);
+ if (arg1->ring == NULL)
+ syslog(*pri, "%s", "");
+ else
+ syslog(*pri, "%s", (char *)arg1->ring);
+ closelog();
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ perf->actions++;
+ }
+}
+
+
+/*
+ * operator: actPrint
+ */
+void
+actPrint(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ time_t clock = (time_t)now;
+ char bfr[26];
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ EVALARG(arg1)
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ pmCtime(&clock, bfr);
+ bfr[24] = '\0';
+ printf("%s: %s\n", bfr, (char *)arg1->ring);
+ fflush(stdout);
+ perf->actions++;
+ }
+}
+
+
+/*
+ * operator: actStomp
+ */
+void
+actStomp(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ EVALARG(arg1)
+ x->smpls[0].stamp = now;
+ if (stompSend((const char *)arg1->ring) != 0)
+ *(Boolean *)x->ring = B_FALSE;
+ else
+ *(Boolean *)x->ring = B_TRUE;
+ perf->actions++;
+ }
+}
+
+
+/*
+ * action argument handling ... including %h, %v and %i substitution
+ */
+void
+actArg(Expr *x)
+{
+ Expr *sp = x->arg1;
+ char *string = (char *)0;
+ size_t length = 0;
+
+ for (sp = x->arg1; sp != NULL; sp = sp->arg1)
+ length = formatSatisfyingValue((char *)sp->ring, length, &string);
+
+ newStringBfr(x, length, string);
+}
+
+
+/*
+ * fake actions for archive mode
+ */
+void
+actFake(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ time_t clock = (time_t)now;
+ char bfr[26];
+
+ if ((arg2 == NULL) ||
+ (x->smpls[0].stamp == 0) ||
+ (now >= *(RealTime *)arg2->ring + x->smpls[0].stamp))
+ {
+ EVALARG(arg1)
+ *(Boolean *)x->ring = B_TRUE;
+ x->smpls[0].stamp = now;
+ pmCtime(&clock, bfr);
+ bfr[24] = '\0';
+ printf("%s %s: %s\n", opStrings(x->op), bfr, (char *)arg1->ring);
+ }
+}
+
diff --git a/src/pmie/src/aggregate.sk b/src/pmie/src/aggregate.sk
new file mode 100644
index 0000000..98bf4c0
--- /dev/null
+++ b/src/pmie/src/aggregate.sk
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: aggregate.sk - aggregation and quantification
+ ***********************************************************************/
+
+/***********************************************************************
+ * operator: @FUN
+ ***********************************************************************/
+
+void
+@FUN_host(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is = &arg1->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip;
+ @OTYPE *op;
+ @TTYPE a;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid && arg1->hdom > 0) {
+ ip = (@ITYPE *)is->ptr;
+ op = (@OTYPE *)os->ptr;
+ n = arg1->hdom;
+ @TOP
+ for (i = 1; i < n; i++) {
+ ip++;
+ @LOOP
+ }
+ @BOT
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else {
+ x->valid = 0;
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_host(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_inst(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is = &arg1->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip;
+ @OTYPE *op;
+ @TTYPE a;
+ Metric *m;
+ int n;
+ int i, j;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid && x->hdom != 0) {
+ ip = (@ITYPE *)is->ptr;
+ op = (@OTYPE *)os->ptr;
+ if (abs(x->hdom) == 1) {
+ n = arg1->e_idom;
+ if (n < 1) {
+ @NOTVALID
+ goto done;
+ }
+ @TOP
+ for (i = 1; i < n; i++) {
+ ip++;
+ @LOOP
+ }
+ @BOT
+ }
+ else {
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+ n = m->m_idom;
+ if (n < 1) {
+ @NOTVALID
+ goto done;
+ }
+ @TOP
+ for (j = 1; j < n; j++){
+ /* Note! no break allowed in this loop */
+ ip++;
+ @LOOP
+ }
+ @BOT
+ m++;
+ }
+ }
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else {
+ x->valid = 0;
+ }
+
+done:
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_inst(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_time(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is = &arg1->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ring = (@ITYPE *)arg1->ring;
+ @ITYPE *ip;
+ @OTYPE *op;
+ @TTYPE a;
+ int n = arg1->tdom;
+ int tspan;
+ int i, j;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid >= n && x->tspan > 0 && arg1->tdom > 0) {
+ op = (@OTYPE *)os->ptr;
+ tspan = x->tspan;
+ for (i = 0; i < tspan; i++) {
+ ip = ring + i;
+ @TOP
+ for (j = 1; j < n; j++){
+ ip += tspan;
+ @LOOP
+ }
+ @BOT
+ }
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else {
+ x->valid = 0;
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_time(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
diff --git a/src/pmie/src/andor.c b/src/pmie/src/andor.c
new file mode 100644
index 0000000..d0e2696
--- /dev/null
+++ b/src/pmie/src/andor.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * andor.c
+ *
+ * These functions were originally generated from skeletons <file>.sk
+ * by the shell-script './meta', then modified to support the semantics
+ * of the boolean AND/OR operators correctly. These are different to
+ * every other operator in that they do not always require both sides
+ * of the expression to be available in order to be evaluated, i.e.
+ * OR: if either side of the expression is true, expr is true
+ * AND: if either side of the expression is false, expr is false
+ ***********************************************************************/
+
+#include "pmapi.h"
+#include "dstruct.h"
+#include "pragmatics.h"
+#include "fun.h"
+#include "show.h"
+#include "stomp.h"
+
+
+/*
+ * operator: cndOr
+ */
+
+#define OR(x,y) (((x) == B_TRUE || (y) == B_TRUE) ? B_TRUE : (((x) == B_FALSE && (y) == B_FALSE) ? B_FALSE : B_UNKNOWN))
+#define OR1(x) ((x) == B_TRUE ? B_TRUE : B_UNKNOWN)
+
+void
+cndOr_n_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean *ip1;
+ Boolean *ip2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR(*ip1, *ip2);
+ ip1++;
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR1(*ip1);
+ ip1++;
+ }
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid && x->tspan > 0) {
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR1(*ip2);
+ ip2++;
+ }
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndOr_n_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndOr_n_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean *ip1;
+ Boolean iv2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ iv2 = *(Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR(*ip1, iv2);
+ ip1++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR1(*ip1);
+ ip1++;
+ }
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid && x->tspan > 0) {
+ *(Boolean *)os->ptr = OR1(*(Boolean *)is2->ptr);
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndOr_n_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndOr_1_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean iv1;
+ Boolean *ip2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0) {
+ iv1 = *(Boolean *)is1->ptr;
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR(iv1, *ip2);
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid && x->tspan > 0) {
+ *(Boolean *)os->ptr = OR1(*(Boolean *)is1->ptr);
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid && x->tspan > 0) {
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = OR1(*ip2);
+ ip2++;
+ }
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndOr_1_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndOr_1_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid) {
+ *(Boolean *)os->ptr = OR(*(Boolean *)is1->ptr, *(Boolean *)is2->ptr);
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid) {
+ *(Boolean *)os->ptr = OR1(*(Boolean *)is1->ptr);
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid) {
+ *(Boolean *)os->ptr = OR1(*(Boolean *)is2->ptr);
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndOr_1_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+/*
+ * operator: cndAnd
+ */
+
+#define AND(x,y) (((x) == B_TRUE && (y) == B_TRUE) ? B_TRUE : (((x) == B_FALSE || (y) == B_FALSE) ? B_FALSE : B_UNKNOWN))
+#define AND1(x) (((x) == B_FALSE) ? B_FALSE : B_UNKNOWN)
+
+void
+cndAnd_n_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean *ip1;
+ Boolean *ip2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid) {
+ ip1 = (Boolean *)is1->ptr;
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND(*ip1, *ip2);
+ ip1++;
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid) {
+ ip1 = (Boolean *)is1->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND1(*ip1);
+ ip1++;
+ }
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid) {
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND1(*ip2);
+ ip2++;
+ }
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndAnd_n_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndAnd_n_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean *ip1;
+ Boolean iv2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ iv2 = *(Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND(*ip1, iv2);
+ ip1++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid && x->tspan > 0) {
+ ip1 = (Boolean *)is1->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND1(*ip1);
+ ip1++;
+ }
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid && x->tspan > 0) {
+ *(Boolean *)os->ptr = AND1(*(Boolean *)is2->ptr);
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndAnd_n_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndAnd_1_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ Boolean iv1;
+ Boolean *ip2;
+ Boolean *op;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0) {
+ iv1 = *(Boolean *)is1->ptr;
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND(iv1, *ip2);
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid && x->tspan > 0) {
+ *(Boolean *)os->ptr = AND1(*(Boolean *)is1->ptr);
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid && x->tspan > 0) {
+ ip2 = (Boolean *)is2->ptr;
+ op = (Boolean *)os->ptr;
+ for (i = 0; i < x->tspan; i++) {
+ *op++ = AND1(*ip2);
+ ip2++;
+ }
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndAnd_1_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+cndAnd_1_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid) {
+ *(Boolean *)os->ptr = AND(*(Boolean *)is1->ptr, *(Boolean *)is2->ptr);
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else if (arg1->valid) {
+ *(Boolean *)os->ptr = AND1(*(Boolean *)is1->ptr);
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else if (arg2->valid) {
+ *(Boolean *)os->ptr = AND1(*(Boolean *)is2->ptr);
+ os->stamp = is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndAnd_1_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
diff --git a/src/pmie/src/andor.h b/src/pmie/src/andor.h
new file mode 100644
index 0000000..bc213b8
--- /dev/null
+++ b/src/pmie/src/andor.h
@@ -0,0 +1,34 @@
+/***********************************************************************
+ * andor.h - Logical AND/OR expression evaluator functions
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef ANDOR_H
+#define ANDOR_H
+
+/* expression evaluator function prototypes */
+void cndOr_n_n(Expr *);
+void cndOr_1_n(Expr *);
+void cndOr_n_1(Expr *);
+void cndOr_1_1(Expr *);
+void cndAnd_n_n(Expr *);
+void cndAnd_1_n(Expr *);
+void cndAnd_n_1(Expr *);
+void cndAnd_1_1(Expr *);
+
+#endif /* ANDOR_H */
diff --git a/src/pmie/src/binary.sk b/src/pmie/src/binary.sk
new file mode 100644
index 0000000..26ada2a
--- /dev/null
+++ b/src/pmie/src/binary.sk
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: binary.sk - binary operator
+ ***********************************************************************/
+
+/*
+ * operator: @FUN
+ */
+
+#define @OP
+
+void
+@FUN_n_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip1;
+ @ITYPE *ip2;
+ @OTYPE *op;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0 && x->tspan == arg1->tspan && x->tspan == arg2->tspan) {
+ ip1 = (@ITYPE *)is1->ptr;
+ ip2 = (@ITYPE *)is2->ptr;
+ op = (@OTYPE *)os->ptr;
+ n = x->tspan;
+ for (i = 0; i < n; i++) {
+ *op++ = OP(*ip1, *ip2);
+ ip1++;
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_n_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_n_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip1;
+ @ITYPE iv2;
+ @OTYPE *op;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0 && x->tspan == arg1->tspan) {
+ ip1 = (@ITYPE *)is1->ptr;
+ iv2 = *(@ITYPE *)is2->ptr;
+ op = (@OTYPE *)os->ptr;
+ n = x->tspan;
+ for (i = 0; i < n; i++) {
+ *op++ = OP(*ip1, iv2);
+ ip1++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_n_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_1_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE iv1;
+ @ITYPE *ip2;
+ @OTYPE *op;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid && x->tspan > 0 && x->tspan == arg2->tspan) {
+ iv1 = *(@ITYPE *)is1->ptr;
+ ip2 = (@ITYPE *)is2->ptr;
+ op = (@OTYPE *)os->ptr;
+ n = x->tspan;
+ for (i = 0; i < n; i++) {
+ *op++ = OP(iv1, *ip2);
+ ip2++;
+ }
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_1_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_1_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg2->smpls[0];
+ Sample *os = &x->smpls[0];
+
+ EVALARG(arg1)
+ EVALARG(arg2)
+ ROTATE(x)
+
+ if (arg1->valid && arg2->valid) {
+ *(@OTYPE *)os->ptr = OP(*(@ITYPE *)is1->ptr, *(@ITYPE *)is2->ptr);
+ os->stamp = (is1->stamp > is2->stamp) ? is1->stamp : is2->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_1_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+#undef OP
+
diff --git a/src/pmie/src/dstruct.c b/src/pmie/src/dstruct.c
new file mode 100644
index 0000000..5d9b8bb
--- /dev/null
+++ b/src/pmie/src/dstruct.c
@@ -0,0 +1,1307 @@
+/***********************************************************************
+ * dstruct.c - central data structures and associated operations
+ ***********************************************************************
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include <math.h>
+#include <ctype.h>
+#include <limits.h>
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include "dstruct.h"
+#include "symbol.h"
+#include "pragmatics.h"
+#include "fun.h"
+#include "eval.h"
+#include "show.h"
+
+#if defined(HAVE_VALUES_H)
+#include <values.h>
+#endif
+#if defined(HAVE_IEEEFP_H)
+#include <ieeefp.h>
+#endif
+
+/***********************************************************************
+ * constants
+ ***********************************************************************/
+
+double mynan; /* not-a-number run time initialized */
+
+
+/***********************************************************************
+ * user supplied parameters
+ ***********************************************************************/
+
+char *pmnsfile = PM_NS_DEFAULT; /* alternate name space */
+Archive *archives; /* list of open archives */
+RealTime first = -1; /* archive starting point */
+RealTime last = 0.0; /* archive end point */
+char *dfltHostConn; /* default host connection string */
+char *dfltHostName; /* default host name */
+RealTime dfltDelta = DELTA_DFLT; /* default sample interval */
+char *startFlag; /* start time specified? */
+char *stopFlag; /* end time specified? */
+char *alignFlag; /* align time specified? */
+char *offsetFlag; /* offset time specified? */
+RealTime runTime; /* run time interval */
+int hostZone; /* timezone from host? */
+char *timeZone; /* timezone from command line */
+int verbose; /* verbosity 0, 1 or 2 */
+int interactive; /* interactive mode, -d */
+int isdaemon; /* run as a daemon */
+int agent; /* secret agent mode? */
+int applet; /* applet mode? */
+int dowrap; /* counter wrap? default no */
+int doexit; /* time to exit stage left? */
+int dorotate; /* is a log rotation pending? */
+pmiestats_t *perf; /* live performance data */
+pmiestats_t instrument; /* used if no mmap (archive) */
+
+
+/***********************************************************************
+ * this is where the action is
+ ***********************************************************************/
+
+Task *taskq = NULL; /* evaluator task queue */
+Expr *curr; /* current executing rule expression */
+
+SymbolTable hosts; /* currently known hosts */
+SymbolTable metrics; /* currently known metrics */
+SymbolTable rules; /* currently known rules */
+SymbolTable vars; /* currently known variables */
+
+
+/***********************************************************************
+ * time
+ ***********************************************************************/
+
+RealTime now; /* current time */
+RealTime start; /* start evaluation time */
+RealTime stop; /* stop evaluation time */
+
+Symbol symDelta; /* current sample interval */
+Symbol symMinute; /* minutes after the hour 0..59 */
+Symbol symHour; /* hours since midnight 0..23 */
+Symbol symDay; /* day of the month 1..31 */
+Symbol symMonth; /* month of the year 1..12 */
+Symbol symYear; /* year 1996.. */
+Symbol symWeekday; /* days since Sunday 0..6 */
+
+static double delta; /* current sample interval */
+static double second; /* seconds after the minute 0..59 */
+static double minute; /* minutes after the hour 0..59 */
+static double hour; /* hours since midnight 0..23 */
+static double day; /* day of the month 1..31 */
+static double month; /* month of the year 1..12 */
+static double year; /* year 1996.. */
+static double weekday; /* days since Sunday 0..6 */
+
+/***********************************************************************
+ * process creation control
+ ***********************************************************************/
+int need_wait;
+
+/* return real time */
+RealTime
+getReal(void)
+{
+ struct timeval t;
+
+ __pmtimevalNow(&t);
+ return realize(t);
+}
+
+
+/* update time variables to reflect current time */
+void
+reflectTime(RealTime d)
+{
+ static time_t then = 0; /* previous time */
+ int skip = now - then;
+ struct tm tm;
+
+ then = (time_t)now;
+
+ /* sample interval */
+ delta = d;
+
+ /* try short path for current time */
+ if (skip >= 0 && skip < 24 * 60 * 60) {
+ second += skip;
+ if (second < 60)
+ return;
+ skip = (int)(second / 60);
+ second -= (double)(60 * skip);
+ minute += (double)skip;
+ if (minute < 60)
+ return;
+ skip = (int)(minute / 60);
+ minute -= (double)(60 * skip);
+ hour += (double)skip;
+ if (hour < 24)
+ return;
+ }
+
+ /* long path for current time */
+ pmLocaltime(&then, &tm);
+ second = (double) tm.tm_sec;
+ minute = (double) tm.tm_min;
+ hour = (double) tm.tm_hour;
+ day = (double) tm.tm_mday;
+ month = (double) tm.tm_mon;
+ /* tm_year is years since 1900, so this is Y2K safe */
+ year = (double) tm.tm_year + 1900;
+ weekday = (double) tm.tm_wday;
+}
+
+
+/* convert RealTime to timeval */
+void
+unrealize(RealTime rt, struct timeval *tv)
+{
+ tv->tv_sec = (time_t)rt;
+ tv->tv_usec = (int)(1000000 * (rt - tv->tv_sec));
+}
+
+
+/* convert RealTime to timespec */
+void
+unrealizenano(RealTime rt, struct timespec *ts)
+{
+ ts->tv_sec = (time_t)rt;
+ ts->tv_nsec = (int)(1000000000 * (rt - ts->tv_sec));
+}
+
+#define SLEEP_EVAL 0
+#define SLEEP_RETRY 1
+
+/* sleep until eval or retry RealTime */
+void
+sleepTight(Task *t, int type)
+{
+ RealTime sched;
+ RealTime delay; /* interval to sleep */
+ int sts;
+ RealTime cur_entry = getReal();
+#ifdef HAVE_WAITPID
+ pid_t pid;
+
+ if (need_wait) {
+ /* harvest terminated children */
+ while ((pid = waitpid(-1, &sts, WNOHANG)) > (pid_t)0) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "sleepTight: wait: pid=%" FMT_PID " done status=0x%x", pid, sts);
+ if (WIFEXITED(sts))
+ fprintf(stderr, " exit=%d", WEXITSTATUS(sts));
+ if (WIFSIGNALED(sts))
+ fprintf(stderr, " signal=%d", WTERMSIG(sts));
+ fprintf(stderr, "\n");
+ }
+#endif
+ ;
+ }
+ need_wait = 0;
+ }
+#endif
+
+ if (!archives) {
+ struct timespec ts, tleft;
+ static RealTime last_sched = -1;
+ static Task *last_t;
+ static int last_type;
+ RealTime cur = getReal();
+
+ sched = type == SLEEP_EVAL ? t->eval : t->retry;
+
+ delay = sched - cur;
+ if (delay < 0) {
+ int show_detail = 0;
+ if (delay <= -1) {
+ fprintf(stderr, "sleepTight: negative delay (%f). sched=%f, cur=%f\n",
+ delay, sched, cur);
+ show_detail = 1;
+ }
+#if PCP_DEBUG
+ else {
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "sleepTight: small negative delay (%f). sched=%f, cur=%f\n",
+ delay, sched, cur);
+ show_detail = 1;
+ }
+ }
+#endif
+ if (show_detail) {
+ if (last_sched > 0) {
+ fprintf(stderr, "Last sleepTight (%s) until: ", last_type == SLEEP_EVAL ? "eval" : "retry");
+ showFullTime(stderr, last_sched);
+ fputc('\n', stderr);
+ fprintf(stderr, "Last ");
+ dumpTask(last_t);
+ }
+ fprintf(stderr, "This sleepTight() entry: ");
+ showFullTime(stderr, cur_entry);
+ fputc('\n', stderr);
+ fprintf(stderr, "Harvest children done: ");
+ showFullTime(stderr, cur);
+ fputc('\n', stderr);
+ fprintf(stderr, "Want sleepTight (%s) until: ", type == SLEEP_EVAL ? "eval" : "retry");
+ showFullTime(stderr, sched);
+ fputc('\n', stderr);
+ fprintf(stderr, "This ");
+ dumpTask(t);
+ }
+ }
+ else {
+ unrealizenano(delay, &ts);
+ for (;;) { /* loop to catch early wakeup from nanosleep */
+ if (ts.tv_sec < 0 || ts.tv_nsec > 999999999) {
+ fprintf(stderr, "sleepTight: invalid args: %ld %ld\n",
+ ts.tv_sec, ts.tv_nsec);
+ break;
+ }
+ sts = nanosleep(&ts, &tleft);
+ /* deferred signal handling done immediately */
+ if (doexit)
+ exit(doexit);
+ if (dorotate) {
+ logRotate();
+ dorotate = 0;
+ }
+ if (sts == 0 || (sts < 0 && oserror() != EINTR))
+ break;
+ ts = tleft;
+ }
+ }
+ last_t = t;
+ last_type = type;
+ last_sched = sched;
+ }
+}
+
+
+/***********************************************************************
+ * ring buffer management
+ ***********************************************************************/
+
+void
+newRingBfr(Expr *x)
+{
+ size_t sz;
+ char *p;
+ int i;
+
+ sz = ((x->sem == SEM_BOOLEAN) || (x->sem == SEM_CHAR)) ?
+ sizeof(char) * x->tspan :
+ sizeof(double) * x->tspan;
+ if (x->ring) free(x->ring);
+ x->ring = alloc(x->nsmpls * sz);
+ p = (char *) x->ring;
+ for (i = 0; i < x->nsmpls; i++) {
+ x->smpls[i].ptr = (void *) p;
+ p += sz;
+ }
+}
+
+
+void
+newStringBfr(Expr *x, size_t length, char *bfr)
+{
+ if (x->e_idom != (int)length) {
+ x->e_idom = (int)length;
+ x->tspan = (int)length;
+ x->nvals = (int)length;
+ }
+ if (x->ring)
+ free(x->ring);
+ x->ring = bfr;
+ x->smpls[0].ptr = (void *) bfr;
+}
+
+
+/* Rotate ring buffer - safe to call only if x->nsmpls > 1 */
+void
+rotate(Expr *x)
+{
+ int n = x->nsmpls-1;
+ Sample *q = &x->smpls[n];
+ Sample *p = q - 1;
+ void *t = q->ptr;
+ int i;
+
+ for (i = n; i > 0; i--)
+ *q-- = *p--;
+ x->smpls[0].ptr = t;
+}
+
+
+/***********************************************************************
+ * memory allocation
+ ***********************************************************************/
+
+void *
+alloc(size_t size)
+{
+ void *p;
+
+ if ((p = malloc(size)) == NULL) {
+ __pmNoMem("pmie.alloc", size, PM_FATAL_ERR);
+ }
+ return p;
+}
+
+
+void *
+zalloc(size_t size)
+{
+ void *p;
+
+ if ((p = calloc(1, size)) == NULL) {
+ __pmNoMem("pmie.zalloc", size, PM_FATAL_ERR);
+ }
+ return p;
+}
+
+
+void *
+aalloc(size_t align, size_t size)
+{
+ void *p;
+ int sts = 0;
+#ifdef HAVE_POSIX_MEMALIGN
+ sts = posix_memalign(&p, align, size);
+#else
+#ifdef HAVE_MEMALIGN
+ p = memalign(align, size);
+ if (p == NULL) sts = -1;
+#else
+ p = malloc(size);
+ if (p == NULL) sts = -1;
+#endif
+#endif
+ if (sts != 0) {
+ __pmNoMem("pmie.aalloc", size, PM_FATAL_ERR);
+ }
+ return p;
+}
+
+
+void *
+ralloc(void *p, size_t size)
+{
+ void *q;
+
+ if ((q = realloc(p, size)) == NULL) {
+ __pmNoMem("pmie.ralloc", size, PM_FATAL_ERR);
+ }
+ return q;
+}
+
+char *
+sdup(char *p)
+{
+ char *q;
+
+ if ((q = strdup(p)) == NULL) {
+ __pmNoMem("pmie.sdup", strlen(p), PM_FATAL_ERR);
+ }
+ return q;
+}
+
+
+Expr *
+newExpr(int op, Expr *arg1, Expr *arg2,
+ int hdom, int idom, int tdom, int nsmpls,
+ int sem)
+{
+ Expr *x;
+ Expr *arg;
+
+ x = (Expr *) zalloc(sizeof(Expr) + (nsmpls - 1) * sizeof(Sample));
+ x->op = op;
+ if (arg1) {
+ x->arg1 = arg1;
+ arg1->parent = x;
+ }
+ if (arg2) {
+ x->arg2 = arg2;
+ arg2->parent = x;
+ }
+ x->hdom = hdom;
+ x->e_idom = idom;
+ x->tdom = tdom;
+ x->nsmpls = nsmpls;
+ x->tspan = (x->e_idom >= 0) ? x->e_idom : abs(x->hdom);
+ x->nvals = x->tspan * nsmpls;
+ if (arg1) {
+ arg = primary(arg1, arg2);
+ x->metrics = arg->metrics;
+ }
+ if (sem == SEM_NUMVAR || sem == SEM_NUMCONST || sem == SEM_BOOLEAN ||
+ sem == SEM_CHAR || sem == SEM_REGEX)
+ x->units = noUnits;
+ else {
+ x->units = noUnits;
+ SET_UNITS_UNKNOWN(x->units);
+ }
+ x->sem = sem;
+ return x;
+}
+
+
+Profile *
+newProfile(Fetch *owner, pmInDom indom)
+{
+ Profile *p = (Profile *) zalloc(sizeof(Profile));
+ p->indom = indom;
+ p->fetch = owner;
+ return p;
+}
+
+
+Fetch *
+newFetch(Host *owner)
+{
+ Fetch *f = (Fetch *) zalloc(sizeof(Fetch));
+ f->host = owner;
+ return f;
+}
+
+
+Host *
+newHost(Task *owner, Symbol name)
+{
+ Host *h = (Host *) zalloc(sizeof(Host));
+
+ h->name = symCopy(name);
+ h->task = owner;
+ return h;
+}
+
+
+Task *
+newTask(RealTime delta, int nth)
+{
+ Task *t = (Task *) zalloc(sizeof(Task));
+ t->nth = nth;
+ t->delta = delta;
+ return t;
+}
+
+/* translate new metric name to internal pmid for agent mode */
+static pmID
+agentId(char *name)
+{
+ int sts;
+ pmID pmid;
+
+ if ((sts = pmLookupName(1, &name, &pmid)) < 0) {
+ fprintf(stderr, "%s: agentId: metric %s not found in namespace: %s\n",
+ pmProgname, name, pmErrStr(sts));
+ exit(1);
+ }
+ return pmid;
+}
+
+
+void
+newResult(Task *t)
+{
+ pmResult *rslt;
+ Symbol *sym;
+ pmValueSet *vset;
+ pmValueBlock *vblk;
+ int i;
+ int len;
+
+ /* allocate pmResult */
+ rslt = (pmResult *) zalloc(sizeof(pmResult) + (t->nrules - 1) * sizeof(pmValueSet *));
+ rslt->numpmid = t->nrules;
+
+ /* allocate pmValueSet's */
+ sym = t->rules;
+ for (i = 0; i < t->nrules; i++) {
+ vset = (pmValueSet *)alloc(sizeof(pmValueSet));
+ vset->pmid = agentId(symName(*sym));
+ vset->numval = 0;
+ vset->valfmt = PM_VAL_DPTR;
+ vset->vlist[0].inst = PM_IN_NULL;
+ len = PM_VAL_HDR_SIZE + sizeof(double);
+ vblk = (pmValueBlock *)zalloc(len);
+ vblk->vlen = len;
+ vblk->vtype = PM_TYPE_DOUBLE;
+ vset->vlist[0].value.pval = vblk;
+ rslt->vset[i] = vset;
+ sym++;
+ }
+
+ t->rslt = rslt;
+}
+
+
+/***********************************************************************
+ * memory deallocation
+ *
+ * IMPORTANT: These functions free the argument structure plus any
+ * structures it owns below it in the expression tree.
+ ***********************************************************************/
+
+void
+freeTask(Task *t)
+{
+ if ((t->hosts == NULL) && (t->rules == NULL)) {
+ if (t->next) t->next->prev = t->prev;
+ if (t->prev) t->prev->next = t->next;
+ else taskq = t->next;
+ free(t);
+ }
+}
+
+
+void
+freeHost(Host *h)
+{
+ if ((h->fetches == NULL) && (h->waits == NULL)) {
+ if (h->next) h->next->prev = h->prev;
+ if (h->prev) h->prev->next = h->next;
+ else {
+ h->task->hosts = h->next;
+ freeTask(h->task);
+ }
+ symFree(h->name);
+ free(h);
+ }
+}
+
+
+void
+freeFetch(Fetch *f)
+{
+ if (f->profiles == NULL) {
+ if (f->next) f->next->prev = f->prev;
+ if (f->prev) f->prev->next = f->next;
+ else {
+ f->host->fetches = f->next;
+ freeHost(f->host);
+ }
+ pmDestroyContext(f->handle);
+ if (f->result) pmFreeResult(f->result);
+ if (f->pmids) free(f->pmids);
+ free(f);
+ }
+}
+
+
+void
+FreeProfile(Profile *p)
+{
+ if (p->metrics == NULL) {
+ if (p->next) p->next->prev = p->prev;
+ if (p->prev) p->prev->next = p->next;
+ else {
+ p->fetch->profiles = p->next;
+ freeFetch(p->fetch);
+ }
+ free(p);
+ }
+}
+
+
+void
+freeMetric(Metric *m)
+{
+ int numinst;
+
+ /* Metric is on fetch list */
+ if (m->profile) {
+ if (m->prev) {
+ m->prev->next = m->next;
+ if (m->next) m->next->prev = m->prev;
+ }
+ else {
+ m->host->waits = m->next;
+ if (m->next) m->next->prev = NULL;
+ }
+ if (m->host) freeHost(m->host);
+ }
+
+ symFree(m->hname);
+ numinst = m->specinst == 0 ? m->m_idom : m->specinst;
+ if (numinst > 0 && m->inames) {
+ int i;
+ for (i = 0; i < numinst; i++) {
+ if (m->inames[i] != NULL) free(m->inames[i]);
+ }
+ free(m->inames);
+ }
+ if (numinst && m->iids) free(m->iids);
+ if (m->vals) free(m->vals);
+}
+
+
+void
+freeExpr(Expr *x)
+{
+ Metric *m;
+ int i;
+
+ if (x) {
+ if (x->arg1 && x->arg1->parent == x)
+ freeExpr(x->arg1);
+ if (x->arg2 && x->arg2->parent == x)
+ freeExpr(x->arg2);
+ if (x->metrics && x->op == CND_FETCH) {
+ for (m = x->metrics, i = 0; i < x->hdom; m++, i++)
+ freeMetric(m);
+ /*
+ * x->metrics allocated in a block, one element per host, so
+ * free as one after all other freeing has been done.
+ */
+ free(x->metrics);
+ }
+ if (x->ring) free(x->ring);
+ free(x);
+ }
+}
+
+
+/***********************************************************************
+ * comparison functions (for use by qsort)
+ ***********************************************************************/
+
+/* Compare two instance identifiers.
+ - This function is passed as an argument to qsort, hence the casts. */
+int /* -1 less, 0 equal, 1 greater */
+compid(const void *i1, const void *i2)
+{
+ if (*(int *)i1 < *(int *)i2) return -1;
+ if (*(int *)i1 > *(int *)i2) return 1;
+ return 0;
+}
+
+
+/* Compare two pmValue's on their inst fields
+ - This function is passed as an argument to qsort, hence the casts. */
+int /* -1 less, 0 equal, 1 greater */
+compair(const void *pmv1, const void *pmv2)
+{
+ if (((pmValue *)pmv1)->inst < ((pmValue *)pmv2)->inst) return -1;
+ if (((pmValue *)pmv1)->inst > ((pmValue *)pmv2)->inst) return 1;
+ return 0;
+}
+
+
+/***********************************************************************
+ * Expr manipulation
+ ***********************************************************************/
+
+/* Decide primary argument for inheritance of Expr attributes */
+Expr *
+primary(Expr *arg1, Expr *arg2)
+{
+ if (arg2 == NULL || arg1->nvals > 1)
+ return arg1;
+ if (arg2->nvals > 1)
+ return arg2;
+ if (arg1->metrics &&
+ (arg1->hdom != -1 || arg1->e_idom != -1 || arg1->tdom != -1))
+ return arg1;
+ if (arg2->metrics &&
+ (arg2->hdom != -1 || arg2->e_idom != -1 || arg2->tdom != -1))
+ return arg2;
+ return arg1;
+}
+
+
+/* change number of samples allocated in ring buffer */
+void
+changeSmpls(Expr **p, int nsmpls)
+{
+ Expr *x = *p;
+ Metric *m;
+ int i;
+
+ if (nsmpls == x->nsmpls) return;
+ *p = x = (Expr *) ralloc(x, sizeof(Expr) + (nsmpls - 1) * sizeof(Sample));
+ x->nsmpls = nsmpls;
+ x->nvals = x->tspan * nsmpls;
+ x->valid = 0;
+ if (x->op == CND_FETCH) {
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+ m->expr = x;
+ m++;
+ }
+ }
+ newRingBfr(x);
+}
+
+
+/* propagate instance domain, semantics and units from
+ argument expressions to parents */
+static void
+instExpr(Expr *x)
+{
+ int up = 0;
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Expr *arg = primary(arg1, arg2);
+
+ /* semantics ... */
+ if (x->sem == SEM_UNKNOWN) {
+ if (arg2 == NULL) {
+ /* unary expression */
+ if (arg1->sem != SEM_UNKNOWN) {
+ up = 1;
+ x->sem = arg1->sem;
+ }
+ }
+ else if ((arg1->sem != SEM_UNKNOWN) &&
+ (arg2->sem != SEM_UNKNOWN)) {
+ /* binary expression with known args */
+ up = 1;
+ x->sem = arg->sem;
+ }
+ /* binary expression with unknown arg */
+ else
+ return;
+ }
+
+ /* units ... */
+ if (UNITS_UNKNOWN(x->units)) {
+ if (arg2 == NULL) {
+ /* unary expression */
+ if (!UNITS_UNKNOWN(arg1->units)) {
+ up = 1;
+ x->units = arg1->units;
+ }
+ }
+ else if (!UNITS_UNKNOWN(arg1->units) &&
+ !UNITS_UNKNOWN(arg2->units)) {
+ /* binary expression with known args */
+ up = 1;
+ x->units = arg->units;
+ if (x->op == CND_MUL) {
+ x->units.dimSpace = arg1->units.dimSpace + arg2->units.dimSpace;
+ x->units.dimTime = arg1->units.dimTime + arg2->units.dimTime;
+ x->units.dimCount = arg1->units.dimCount + arg2->units.dimCount;
+ }
+ else if (x->op == CND_DIV) {
+ x->units.dimSpace = arg1->units.dimSpace - arg2->units.dimSpace;
+ x->units.dimTime = arg1->units.dimTime - arg2->units.dimTime;
+ x->units.dimCount = arg1->units.dimCount - arg2->units.dimCount;
+ }
+ }
+ }
+
+ /* instance domain */
+ if ((x->e_idom != -1) && (x->e_idom != arg->e_idom)) {
+ up = 1;
+ x->e_idom = arg->e_idom;
+ x->tspan = (x->e_idom >= 0) ? x->e_idom : abs(x->hdom);
+ x->nvals = x->tspan * x->nsmpls;
+ x->valid = 0;
+ newRingBfr(x);
+ }
+
+ if (up && x->parent)
+ instExpr(x->parent);
+}
+
+
+/* propagate instance domain, semantics and units from given
+ fetch expression to its parents */
+void
+instFetchExpr(Expr *x)
+{
+ Metric *m;
+ int ninst;
+ int up = 0;
+ int i;
+
+ /* update semantics and units */
+ if (x->sem == SEM_UNKNOWN) {
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+ if (m->desc.sem != SEM_UNKNOWN) {
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ x->sem = PM_SEM_INSTANT;
+ x->units = canon(m->desc.units);
+ x->units.dimTime--;
+ }
+ else {
+ x->sem = m->desc.sem;
+ x->units = canon(m->desc.units);
+ }
+ up = 1;
+ break;
+ }
+ }
+ }
+
+ /*
+ * update number of instances ... need to be careful because may be more
+ * than one host, and instances may not be fully available ...
+ * m_idom < 0 => no idea how many instances there might be (cannot
+ * contact pmcd, unknown metric, can't get indom, ...)
+ * m_idom == 0 => no values, but otherwise OK
+ * m_idom > 0 => have this many values (and hence instances)
+ */
+ m = x->metrics;
+ ninst = -1;
+ for (i = 0; i < x->hdom; i++) {
+ m->offset = ninst;
+ if (m->m_idom >= 0) {
+ if (ninst == -1)
+ ninst = m->m_idom;
+ else
+ ninst += m->m_idom;
+ }
+ m++;
+ }
+ if (x->e_idom != ninst) {
+ /* number of instances is different */
+ x->e_idom = ninst;
+ x->tspan = (x->e_idom >= 0) ? x->e_idom : abs(x->hdom);
+ x->nvals = x->nsmpls * x->tspan;
+ x->valid = 0;
+ newRingBfr(x);
+ up = 1;
+ }
+ if (x->parent) {
+ /* do we need to propagate changes? */
+ if (up ||
+ (UNITS_UNKNOWN(x->parent->units) && !UNITS_UNKNOWN(x->units))) {
+ instExpr(x->parent);
+ }
+ }
+}
+
+
+/***********************************************************************
+ * compulsory initialization
+ ***********************************************************************/
+
+void dstructInit(void)
+{
+ Expr *x;
+ double zero = 0.0;
+
+ /* not-a-number initialization */
+ mynan = zero / zero;
+
+ /* don't initialize dfltHost*; let pmie.c do it after getopt. */
+
+ /* set up symbol tables */
+ symSetTable(&hosts);
+ symSetTable(&metrics);
+ symSetTable(&rules);
+ symSetTable(&vars);
+
+ /* set yp inter-sample interval (delta) symbol */
+ symDelta = symIntern(&vars, "delta");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &delta;
+ x->valid = 1;
+ symValue(symDelta) = x;
+
+ /* set up time symbols */
+ symMinute = symIntern(&vars, "minute");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &minute;
+ x->valid = 1;
+ symValue(symMinute) = x;
+ symHour = symIntern(&vars, "hour");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &hour;
+ x->valid = 1;
+ symValue(symHour) = x;
+ symDay = symIntern(&vars, "day");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &day;
+ x->valid = 1;
+ symValue(symDay) = x;
+ symMonth = symIntern(&vars, "month");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &month;
+ x->valid = 1;
+ symValue(symMonth) = x;
+ symYear = symIntern(&vars, "year");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &year;
+ x->valid = 1;
+ symValue(symYear) = x;
+ symWeekday = symIntern(&vars, "day_of_week");
+ x = newExpr(OP_VAR, NULL,NULL, -1, -1, -1, 1, SEM_NUMVAR);
+ x->smpls[0].ptr = &weekday;
+ x->valid = 1;
+ symValue(symWeekday) = x;
+}
+
+
+/* get ready to run evaluator */
+void
+agentInit(void)
+{
+ Task *t;
+ int sts;
+
+ /* Set up local name space for agent */
+ /* Only load PMNS if it's default and hence not already loaded */
+ if (pmnsfile == PM_NS_DEFAULT && (sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ fprintf(stderr, "%s: agentInit: cannot load metric namespace: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* allocate pmResult's and send pmDescs for secret agent mode */
+ t = taskq;
+ while (t) {
+ newResult(t);
+ sendDescs(t);
+ t = t->next;
+ }
+}
+
+/*
+ * useful for diagnostics and with dbx
+ */
+
+static struct {
+ void (*addr)(Expr *);
+ char *name;
+} fn_map[] = {
+ { actAlarm, "actAlarm" },
+ { actAnd, "actAnd" },
+ { actArg, "actArg" },
+ { actFake, "actFake" },
+ { actOr, "actOr" },
+ { actPrint, "actPrint" },
+ { actShell, "actShell" },
+ { actStomp, "actStomp" },
+ { actSyslog, "actSyslog" },
+ { cndAdd_1_1, "cndAdd_1_1" },
+ { cndAdd_1_n, "cndAdd_1_n" },
+ { cndAdd_n_1, "cndAdd_n_1" },
+ { cndAdd_n_n, "cndAdd_n_n" },
+ { cndAll_host, "cndAll_host" },
+ { cndAll_inst, "cndAll_inst" },
+ { cndAll_time, "cndAll_time" },
+ { cndAnd_1_1, "cndAnd_1_1" },
+ { cndAnd_1_n, "cndAnd_1_n" },
+ { cndAnd_n_1, "cndAnd_n_1" },
+ { cndAnd_n_n, "cndAnd_n_n" },
+ { cndAvg_host, "cndAvg_host" },
+ { cndAvg_inst, "cndAvg_inst" },
+ { cndAvg_time, "cndAvg_time" },
+ { cndCount_host, "cndCount_host" },
+ { cndCount_inst, "cndCount_inst" },
+ { cndCount_time, "cndCount_time" },
+ { cndDelay_1, "cndDelay_1" },
+ { cndDelay_n, "cndDelay_n" },
+ { cndDiv_1_1, "cndDiv_1_1" },
+ { cndDiv_1_n, "cndDiv_1_n" },
+ { cndDiv_n_1, "cndDiv_n_1" },
+ { cndDiv_n_n, "cndDiv_n_n" },
+ { cndEq_1_1, "cndEq_1_1" },
+ { cndEq_1_n, "cndEq_1_n" },
+ { cndEq_n_1, "cndEq_n_1" },
+ { cndEq_n_n, "cndEq_n_n" },
+ { cndFall_1, "cndFall_1" },
+ { cndFall_n, "cndFall_n" },
+ { cndFetch_1, "cndFetch_1" },
+ { cndFetch_all, "cndFetch_all" },
+ { cndFetch_n, "cndFetch_n" },
+ { cndGt_1_1, "cndGt_1_1" },
+ { cndGt_1_n, "cndGt_1_n" },
+ { cndGt_n_1, "cndGt_n_1" },
+ { cndGt_n_n, "cndGt_n_n" },
+ { cndGte_1_1, "cndGte_1_1" },
+ { cndGte_1_n, "cndGte_1_n" },
+ { cndGte_n_1, "cndGte_n_1" },
+ { cndGte_n_n, "cndGte_n_n" },
+ { cndLt_1_1, "cndLt_1_1" },
+ { cndLt_1_n, "cndLt_1_n" },
+ { cndLt_n_1, "cndLt_n_1" },
+ { cndLt_n_n, "cndLt_n_n" },
+ { cndLte_1_1, "cndLte_1_1" },
+ { cndLte_1_n, "cndLte_1_n" },
+ { cndLte_n_1, "cndLte_n_1" },
+ { cndLte_n_n, "cndLte_n_n" },
+ { cndMax_host, "cndMax_host" },
+ { cndMax_inst, "cndMax_inst" },
+ { cndMax_time, "cndMax_time" },
+ { cndMin_host, "cndMin_host" },
+ { cndMin_inst, "cndMin_inst" },
+ { cndMin_time, "cndMin_time" },
+ { cndMul_1_1, "cndMul_1_1" },
+ { cndMul_1_n, "cndMul_1_n" },
+ { cndMul_n_1, "cndMul_n_1" },
+ { cndMul_n_n, "cndMul_n_n" },
+ { cndNeg_1, "cndNeg_1" },
+ { cndNeg_n, "cndNeg_n" },
+ { cndNeq_1_1, "cndNeq_1_1" },
+ { cndNeq_1_n, "cndNeq_1_n" },
+ { cndNeq_n_1, "cndNeq_n_1" },
+ { cndNeq_n_n, "cndNeq_n_n" },
+ { cndNot_1, "cndNot_1" },
+ { cndNot_n, "cndNot_n" },
+ { cndOr_1_1, "cndOr_1_1" },
+ { cndOr_1_n, "cndOr_1_n" },
+ { cndOr_n_1, "cndOr_n_1" },
+ { cndOr_n_n, "cndOr_n_n" },
+ { cndPcnt_host, "cndPcnt_host" },
+ { cndPcnt_inst, "cndPcnt_inst" },
+ { cndPcnt_time, "cndPcnt_time" },
+ { cndRate_1, "cndRate_1" },
+ { cndRate_n, "cndRate_n" },
+ { cndRise_1, "cndRise_1" },
+ { cndRise_n, "cndRise_n" },
+ { cndSome_host, "cndSome_host" },
+ { cndSome_inst, "cndSome_inst" },
+ { cndSome_time, "cndSome_time" },
+ { cndSub_1_1, "cndSub_1_1" },
+ { cndSub_1_n, "cndSub_1_n" },
+ { cndSub_n_1, "cndSub_n_1" },
+ { cndSub_n_n, "cndSub_n_n" },
+ { cndSum_host, "cndSum_host" },
+ { cndSum_inst, "cndSum_inst" },
+ { cndSum_time, "cndSum_time" },
+ { rule, "rule" },
+ { NULL, NULL },
+};
+
+static struct {
+ int val;
+ char *name;
+} sem_map[] = {
+ { SEM_UNKNOWN, "UNKNOWN" },
+ { SEM_NUMVAR, "NUMVAR" },
+ { SEM_NUMCONST, "NUMCONST" },
+ { SEM_BOOLEAN, "TRUTH" },
+ { SEM_CHAR, "CHAR" },
+ { SEM_REGEX, "REGEX" },
+ { PM_SEM_COUNTER, "COUNTER" },
+ { PM_SEM_INSTANT, "INSTANT" },
+ { PM_SEM_DISCRETE, "DISCRETE" },
+ { 0, NULL },
+};
+
+void
+__dumpExpr(int level, Expr *x)
+{
+ int i;
+ int j;
+ int k;
+
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, "Expr dump @ " PRINTF_P_PFX "%p\n", x);
+ if (x == NULL) return;
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " op=%d (%s) arg1=" PRINTF_P_PFX "%p arg2=" PRINTF_P_PFX "%p parent=" PRINTF_P_PFX "%p\n",
+ x->op, opStrings(x->op), x->arg1, x->arg2, x->parent);
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " eval=");
+ for (j = 0; fn_map[j].addr; j++) {
+ if (x->eval == fn_map[j].addr) {
+ fprintf(stderr, "%s", fn_map[j].name);
+ break;
+ }
+ }
+ if (fn_map[j].addr == NULL)
+ fprintf(stderr, "" PRINTF_P_PFX "%p()", x->eval);
+ fprintf(stderr, " metrics=" PRINTF_P_PFX "%p ring=" PRINTF_P_PFX "%p\n", x->metrics, x->ring);
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " valid=%d cardinality[H,I,T]=[%d,%d,%d] tspan=%d\n",
+ x->valid, x->hdom, x->e_idom, x->tdom, x->tspan);
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " nsmpls=%d nvals=%d sem=", x->nsmpls, x->nvals);
+ for (j = 0; sem_map[j].name; j++) {
+ if (x->sem == sem_map[j].val) {
+ fprintf(stderr, "%s", sem_map[j].name);
+ break;
+ }
+ }
+ if (sem_map[j].name == NULL)
+ fprintf(stderr, "%d", x->sem);
+ if (UNITS_UNKNOWN(x->units))
+ fprintf(stderr, " units=UNKNOWN\n");
+ else
+ fprintf(stderr, " units=%s\n", pmUnitsStr(&x->units));
+ if (x->valid > 0) {
+ if (x->sem == SEM_BOOLEAN || x->sem == SEM_CHAR ||
+ x->sem == SEM_NUMVAR || x->sem == SEM_NUMCONST ||
+ x->sem == PM_SEM_COUNTER || x->sem == PM_SEM_INSTANT ||
+ x->sem == PM_SEM_DISCRETE) {
+ for (j = 0; j < x->nsmpls; j++) {
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " smpls[%d].ptr " PRINTF_P_PFX "%p ", j, x->smpls[j].ptr);
+ for (k = 0; k < x->tspan; k++) {
+ if (x->tspan > 1 && x->sem != SEM_CHAR) {
+ if (k > 0)
+ fprintf(stderr, ", ");
+ fprintf(stderr, "{%d} ", k);
+ }
+ if (x->sem == SEM_BOOLEAN) {
+ char c = *((char *)x->smpls[j].ptr+k);
+ if ((int)c == B_TRUE)
+ fprintf(stderr, "true");
+ else if ((int)c == B_FALSE)
+ fprintf(stderr, "false");
+ else if ((int)c == B_UNKNOWN)
+ fprintf(stderr, "unknown");
+ else
+ fprintf(stderr, "bogus (0x%x)", c & 0xff);
+ }
+ else if (x->sem == SEM_CHAR) {
+ if (k == 0)
+ fprintf(stderr, "\"%s\"", (char *)x->smpls[j].ptr);
+ }
+ else {
+ double v = *((double *)x->smpls[j].ptr+k);
+ int fp_bad = 0;
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(v) == FP_NAN;
+#else
+#ifdef HAVE_ISNAN
+ fp_bad = isnan(v);
+#endif
+#endif
+ if (fp_bad)
+ fputc('?', stderr);
+ else
+ fprintf(stderr, "%g", v);
+ }
+ }
+ fputc('\n', stderr);
+ }
+ }
+ else if (x->sem == SEM_REGEX) {
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, " handle=" PRINTF_P_PFX "%p\n", x->ring);
+ }
+ }
+}
+
+void
+__dumpMetric(int level, Metric *m)
+{
+ int i;
+ int j;
+ int numinst;
+
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, "Metric dump @ " PRINTF_P_PFX "%p\n", m);
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, "expr=" PRINTF_P_PFX "%p profile=" PRINTF_P_PFX "%p host=" PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p prev=" PRINTF_P_PFX "%p\n",
+ m->expr, m->profile, m->host, m->next, m->prev);
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, "metric=%s host=%s conv=%g specinst=%d m_indom=%d\n",
+ symName(m->mname), symName(m->hname), m->conv, m->specinst, m->m_idom);
+ if (m->desc.indom != PM_INDOM_NULL) {
+ numinst = m->specinst == 0 ? m->m_idom : m->specinst;
+ for (j = 0; j < numinst; j++) {
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fprintf(stderr, "[%d] iid=", j);
+ if (m->iids[j] == PM_IN_NULL)
+ fprintf(stderr, "?missing");
+ else
+ fprintf(stderr, "%d", m->iids[j]);
+ fprintf(stderr, " iname=\"%s\"\n", m->inames[j]);
+ }
+ }
+ for (i = 0; i < level; i++) fprintf(stderr, ".. ");
+ fputc('\n', stderr);
+
+#if 0
+ pmDesc desc; /* pmAPI metric description */
+ RealTime stamp; /* time stamp for current values */
+ pmValueSet *vset; /* current values */
+ RealTime stomp; /* previous time stamp for rate calculation */
+ double *vals; /* vector of values for rate computation */
+ int offset; /* offset within sample in expr ring buffer */
+... Metric;
+#endif
+
+}
+
+
+void
+__dumpTree(int level, Expr *x)
+{
+ __dumpExpr(level, x);
+ if (x->arg1 != NULL) __dumpTree(level+1, x->arg1);
+ if (x->arg2 != NULL) __dumpTree(level+1, x->arg2);
+}
+
+void
+dumpTree(Expr *x)
+{
+ __dumpTree(0, x);
+}
+
+void
+dumpRules(void)
+{
+ Task *t;
+ Symbol *s;
+ int i;
+
+ for (t = taskq; t != NULL; t = t->next) {
+ s = t->rules;
+ for (i = 0; i < t->nrules; i++, s++) {
+ fprintf(stderr, "\nRule: %s\n", symName(*s));
+ dumpTree((Expr *)symValue(*s));
+ }
+ }
+}
+
+void
+dumpExpr(Expr *x)
+{
+ __dumpExpr(0, x);
+}
+
+void
+dumpMetric(Metric *m)
+{
+ __dumpMetric(0, m);
+}
+
+void
+dumpTask(Task *t)
+{
+ int i;
+ fprintf(stderr, "Task dump @ " PRINTF_P_PFX "%p\n", t);
+ fprintf(stderr, " nth=%d delta=%.3f tick=%d next=" PRINTF_P_PFX "%p prev=" PRINTF_P_PFX "%p\n", t->nth, t->delta, t->tick, t->next, t->prev);
+ fprintf(stderr, " eval time: ");
+ showFullTime(stderr, t->eval);
+ fputc('\n', stderr);
+ fprintf(stderr, " retry time: ");
+ showFullTime(stderr, t->retry);
+ fputc('\n', stderr);
+ if (t->hosts == NULL)
+ fprintf(stderr, " host=<null>\n");
+ else
+ fprintf(stderr, " host=%s (%s)\n", symName(t->hosts->name), t->hosts->down ? "down" : "up");
+ fprintf(stderr, " rules:\n");
+ for (i = 0; i < t->nrules; i++) {
+ fprintf(stderr, " %s\n", symName(t->rules[i]));
+ }
+}
diff --git a/src/pmie/src/dstruct.h b/src/pmie/src/dstruct.h
new file mode 100644
index 0000000..919de35
--- /dev/null
+++ b/src/pmie/src/dstruct.h
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/***********************************************************************
+ * dstruct.h - central data structures snd associated operations
+ ***********************************************************************/
+
+#ifndef DSTRUCT_H
+#define DSTRUCT_H
+
+#include <stddef.h>
+#include <sys/types.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "symbol.h"
+#include "stats.h"
+
+
+/***********************************************************************
+ * forward reference
+ ***********************************************************************/
+
+struct expr;
+struct metric;
+struct profile;
+struct fetch;
+struct host;
+struct task;
+
+
+/***********************************************************************
+ * (Kleene) 3-valued boolean values
+ ***********************************************************************/
+
+typedef char Boolean;
+#define B_FALSE 0
+#define B_TRUE 1
+#define B_UNKNOWN 2
+
+extern double mynan; /* definitely not-a-number */
+
+
+/***********************************************************************
+ * time
+ ***********************************************************************/
+
+typedef unsigned int TickTime; /* time counted in deltas */
+
+/* following in usec */
+typedef double RealTime; /* wall clock time or interval */
+#define MINUTE 60 /* one minute of real time */
+#define DELAY_MAX 32 /* maximum initial evaluation delay */
+#define RETRY 5 /* retry interval */
+#define DELTA_DFLT 10 /* default sample interval */
+#define DELTA_MIN 0.1 /* minimum sample interval */
+
+
+/***********************************************************************
+ * evaluator functions
+ ***********************************************************************/
+
+/* evaluator function */
+typedef void (Eval)(struct expr *);
+
+
+/***********************************************************************
+ * internal representation of rule expressions and their values
+ ***********************************************************************/
+
+/* pointer to and timestamp for sample in ring buffer */
+typedef struct {
+ void *ptr; /* pointer into value ring buffer */
+ RealTime stamp; /* timestamp for sample */
+} Sample;
+
+/* Expression Tree Node:
+ * The parser fills in most of the fields, pragmatics analysis
+ * fills in the rest. The parser may leave 0 in e_idom or nvals
+ * and NULL in ring to indicate it did not have enough information
+ * to fill in the correct values. These values are then patched
+ * up during pragmatics analysis and dynamically if/when the number
+ * of instances changes.
+ *
+ * Warning: The semantics of the hdom, e_idom and tdom fields are
+ * quite subtle. A value of 1 or greater is the cardinality of
+ * the corresponding domain. -1 indicates that the corresponding
+ * domain has collapsed, and there is one value. 0 indicates
+ * that there is no value (only used for e_idom to indicate an
+ * empty instance domain).
+ */
+typedef struct expr {
+ /* expression syntax */
+ int op; /* operator */
+ struct expr *arg1; /* NULL || (Expr *) */
+ struct expr *arg2; /* NULL || (Expr *) */
+ struct expr *parent; /* parent of this Expr */
+
+ /* evaluator */
+ Eval *eval; /* evaluator function */
+ int valid; /* number of valid samples */
+
+ /* description of value matrix */
+ int hdom; /* cardinality of host dimension */
+ int e_idom; /* cardinality of instance dimension */
+ int tdom; /* cardinality of time dimension */
+ int tspan; /* number of values per sample */
+ int nsmpls; /* number of samples in ring buffer */
+ int nvals; /* total number of values in ring buffer */
+ struct metric *metrics; /* array of per host metric info */
+
+ /* description of single value */
+ int sem; /* value semantics, see below */
+ pmUnits units; /* value units, as in pmDesc */
+
+ /* value buffer */
+ void *ring; /* base address of value ring buffer */
+ Sample smpls[1]; /* array dynamically allocated */
+} Expr;
+
+/* per-host description of a performance metric */
+typedef struct metric {
+ struct expr *expr; /* Expr owning this Metric */
+ struct profile *profile; /* Profile owning this Metric */
+ struct host *host; /* Host owning this Metric */
+ struct metric *next; /* fetch/wait list forward pointer */
+ struct metric *prev; /* fetch/wait list backward pointer */
+ Symbol mname; /* metric name */
+ Symbol hname; /* host name */
+ pmDesc desc; /* pmAPI metric description */
+ double conv; /* conversion factor into canonical units */
+ int specinst; /* count of specific instances in rule and */
+ /* 0 if all instances are to be considered */
+ int m_idom; /* cardinality of available instance domain */
+ char **inames; /* array of instance names */
+ int *iids; /* array of instance ids */
+ RealTime stamp; /* time stamp for current values */
+ pmValueSet *vset; /* current values */
+ RealTime stomp; /* previous time stamp for rate calculation */
+ double *vals; /* vector of values for rate computation */
+ int offset; /* offset within sample in expr ring buffer */
+} Metric;
+
+/*
+ * Note on instances in Metric:
+ *
+ * if specinst == 0, then m_idom, inames[] and iids[] are the
+ * currently available instances
+ * otherwise, m_idom is the number of the specified instances currently
+ * available (and identified in inames[] and iids[] for 0 .. m_idom-1)
+ * and the unavailable instances are after that, i.e. elements
+ * m_idom ... specinst-1 of inames[] and iids[]
+ */
+
+/* per instance-domain part of bundled fetch request */
+typedef struct profile {
+ struct metric *metrics; /* list of Metrics for this Profile */
+ struct fetch *fetch; /* Fetch bundle owning this Profile */
+ struct profile *next; /* Profile list forward link */
+ struct profile *prev; /* Profile list backward link */
+ pmInDom indom; /* instance domain */
+ int need_all; /* all instances required */
+} Profile;
+
+/* bundled fetch request for multiple metrics */
+typedef struct fetch {
+ struct profile *profiles; /* list of Profiles for this Fetch */
+ struct host *host; /* Host owning this Fetch */
+ struct fetch *next; /* fetch list forward pointer */
+ struct fetch *prev; /* fetch list backward pointer */
+ int handle; /* PMCS context handle */
+ int npmids; /* number of metrics in fetch */
+ pmID *pmids; /* array of metric ids to fetch */
+ pmResult *result; /* result of fetch */
+} Fetch;
+
+/* set of bundled fetches for single host (may be archive or live):
+ The field waits contains a list of Metrics for which descriptors
+ were not available during pragmatics analysis. */
+typedef struct host {
+ struct fetch *fetches; /* list of Fetches for this Host */
+ struct task *task; /* Task owning this host */
+ struct host *next; /* Host list forward pointer */
+ struct host *prev; /* Host list backward pointer */
+ Symbol name; /* host machine */
+ int down; /* host is not delivering metrics */
+ Metric *waits; /* wait list of Metrics */
+ Metric *duds; /* bad Metrics discovered during evaluation */
+} Host;
+
+/* element of evaluator task queue */
+typedef struct task {
+ int nth; /* initial (syntactic) position in task queue */
+ struct task *next; /* task list forward link */
+ struct task *prev; /* task list backward link */
+ RealTime epoch; /* bottom-line for timing calculations */
+ RealTime delta; /* sample interval */
+ TickTime tick; /* count up deltas */
+ RealTime eval; /* scheduled evaluation time */
+ RealTime retry; /* scheduled retry down Hosts and Metrics */
+ int nrules; /* number of rules in this task */
+ Symbol *rules; /* array of rules to be evaluated */
+ Host *hosts; /* fetches to be executed and waiting */
+ pmResult *rslt; /* for secret agent mode */
+} Task;
+
+/* value semantics - as in pmDesc plus following */
+#define SEM_UNKNOWN 0 /* semantics not yet available */
+#define SEM_NUMVAR 10 /* numeric variable value */
+#define SEM_BOOLEAN 11 /* boolean (3-state) value */
+#define SEM_CHAR 12 /* character (string) */
+#define SEM_NUMCONST 13 /* numeric constant value */
+#define SEM_REGEX 14 /* compiled regular expression */
+
+/* Expr operator (op) tokens */
+typedef int Op;
+#define RULE 0
+/* basic conditions */
+#define CND_FETCH 1
+#define CND_DELAY 2
+#define CND_RATE 3
+/* arithmetic */
+#define CND_NEG 4
+#define CND_ADD 5
+#define CND_SUB 6
+#define CND_MUL 7
+#define CND_DIV 8
+/* aggregation */
+#define CND_SUM_HOST 10
+#define CND_SUM_INST 11
+#define CND_SUM_TIME 12
+#define CND_AVG_HOST 13
+#define CND_AVG_INST 14
+#define CND_AVG_TIME 15
+#define CND_MAX_HOST 16
+#define CND_MAX_INST 17
+#define CND_MAX_TIME 18
+#define CND_MIN_HOST 19
+#define CND_MIN_INST 20
+#define CND_MIN_TIME 21
+/* relational */
+#define CND_EQ 30
+#define CND_NEQ 31
+#define CND_LT 32
+#define CND_LTE 33
+#define CND_GT 34
+#define CND_GTE 35
+/* boolean */
+#define CND_NOT 40
+#define CND_RISE 41
+#define CND_FALL 42
+#define CND_AND 43
+#define CND_OR 44
+#define CND_MATCH 45
+#define CND_NOMATCH 46
+#define CND_RULESET 47
+#define CND_OTHER 48
+/* quantification */
+#define CND_ALL_HOST 50
+#define CND_ALL_INST 51
+#define CND_ALL_TIME 52
+#define CND_SOME_HOST 53
+#define CND_SOME_INST 54
+#define CND_SOME_TIME 55
+#define CND_PCNT_HOST 56
+#define CND_PCNT_INST 57
+#define CND_PCNT_TIME 58
+#define CND_COUNT_HOST 59
+#define CND_COUNT_INST 60
+#define CND_COUNT_TIME 61
+/* actions */
+#define ACT_SEQ 70
+#define ACT_ALT 71
+#define ACT_SHELL 72
+#define ACT_ALARM 73
+#define ACT_SYSLOG 74
+#define ACT_PRINT 75
+#define ACT_ARG 76
+#define ACT_STOMP 77
+/* no operation (extension) */
+#define NOP 80
+/* dereferenced variable */
+#define OP_VAR 90
+
+int unary(Op); /* unary operator */
+int binary(Op); /* binary operator */
+
+/***********************************************************************
+ * archives
+ ***********************************************************************/
+
+typedef struct archive {
+ struct archive *next; /* list link */
+ char *fname; /* file name */
+ char *hname; /* host name */
+ RealTime first; /* timestamp for first pmResult */
+ RealTime last; /* timestamp for last pmResult */
+} Archive;
+
+
+/***********************************************************************
+ * memory allocation / deallocation
+ ***********************************************************************/
+
+void *alloc(size_t);
+void *zalloc(size_t);
+void *ralloc(void *, size_t);
+void *aalloc(size_t, size_t);
+char *sdup(char *);
+
+Expr *newExpr(int, Expr *, Expr *, int, int, int, int, int);
+Profile *newProfile(Fetch *, pmInDom);
+Fetch *newFetch(Host *);
+Host *newHost(Task *, Symbol);
+Task *newTask(RealTime, int);
+void newResult(Task *);
+
+void freeExpr(Expr *);
+
+void freeMetric(Metric *);
+
+void FreeProfile(Profile *);
+void freeFetch(Fetch *);
+void freeTask(Task *);
+
+
+/***********************************************************************
+ * ring buffer management
+ ***********************************************************************/
+
+void newRingBfr(Expr *);
+void newStringBfr(Expr *, size_t, char *);
+void rotate(Expr *);
+
+
+/***********************************************************************
+ * Expr manipulation
+ ***********************************************************************/
+
+Expr *primary(Expr *, Expr *);
+void changeSmpls(Expr **, int);
+void instFetchExpr(Expr *);
+
+/***********************************************************************
+ * time methods
+ ***********************************************************************/
+
+/* convert timeval to RealTime */
+#define realize(t) (1.0e-6 * (RealTime)(t).tv_usec + (RealTime)(t).tv_sec)
+/* convert RealTime to timeval */
+void unrealize(RealTime, struct timeval *);
+RealTime getReal(void); /* return current time */
+void reflectTime(RealTime); /* update time vars to reflect now */
+#define SLEEP_EVAL 0
+#define SLEEP_RETRY 1
+void sleepTight(Task *, int); /* sleep until retry or eval time */
+void logRotate(void); /* close current, start a new log */
+
+/*
+ * diagnostic tracing
+ */
+void dumpRules(void);
+void dumpExpr(Expr *);
+void dumpTree(Expr *);
+void dumpMetric(Metric *);
+void dumpTask(Task *);
+void __dumpExpr(int, Expr *);
+void __dumpTree(int, Expr *);
+void __dumpMetric(int, Metric *);
+
+/***********************************************************************
+ * comparison functions (for use by qsort)
+ ***********************************************************************/
+
+/* compare two instance identifiers. */
+int compid(const void *, const void *);
+
+/* compare two pmValue's on their inst fields */
+int compair(const void *, const void *);
+
+
+/***********************************************************************
+ * global data structures
+ ***********************************************************************/
+
+extern char *pmnsfile; /* alternate namespace */
+extern Archive *archives; /* archives given on command line */
+extern RealTime first; /* archive starting point */
+extern RealTime last; /* archive end point */
+extern char *dfltHostConn; /* default PM_CONTEXT_HOST parameter */
+extern char *dfltHostName; /* pmContextGetHostName of host name */
+extern RealTime dfltDelta; /* default sample interval */
+extern RealTime runTime; /* run time interval */
+extern int hostZone; /* timezone from host? */
+extern char *timeZone; /* timezone from command line */
+extern int verbose; /* verbosity 0, 1 or 2 */
+extern int interactive; /* interactive mode, -d */
+extern int isdaemon; /* run as a daemon */
+extern int agent; /* secret agent mode? */
+extern int applet; /* applet mode? */
+extern int dowrap; /* counter wrap? default no */
+extern int doexit; /* signalled its time to exit */
+extern int dorotate; /* log rotation was requested */
+extern pmiestats_t *perf; /* pmie performance data ptr */
+extern pmiestats_t instrument; /* pmie performance data struct */
+
+
+extern SymbolTable rules; /* currently known rules */
+extern SymbolTable vars; /* currently known variables */
+extern SymbolTable hosts; /* currently known hosts */
+extern SymbolTable metrics; /* currently known metrics */
+
+extern Task *taskq; /* evaluator task queue */
+extern Expr *curr; /* current executing rule expression */
+
+extern RealTime now; /* current time */
+extern RealTime start; /* start evaluation */
+extern RealTime stop; /* stop evaluation */
+
+
+/***********************************************************************
+ * reserved symbols
+ ***********************************************************************/
+
+extern Symbol symDelta; /* current sample interval */
+extern Symbol symMinute; /* minutes after the hour 0..59 */
+extern Symbol symHour; /* hours since midnight 0..23 */
+extern Symbol symDay; /* day of the month 1..31 */
+extern Symbol symMonth; /* month of the year 1..12 */
+extern Symbol symYear; /* year 1996.. */
+extern Symbol symWeekday; /* days since Sunday 0..6 */
+
+
+/***********************************************************************
+ * compulsory initialization
+ ***********************************************************************/
+
+void dstructInit(void); /* initialize central data structures */
+void timeInit(void); /* initialize time keeping data structures */
+void agentInit(void); /* initialize evaluation parameters */
+
+/***********************************************************************
+ * unknown units handling
+ * We don't have a good sentinal value, but 1 / count x 10 ^ 7 does
+ * not appear in any PMDA and cannot come from the pmie lexical scanner
+ ***********************************************************************/
+#define SET_UNITS_UNKNOWN(u) { u.dimCount = -1; u.scaleCount = 7; }
+#define UNITS_UNKNOWN(u) (u.dimCount == -1 && u.scaleCount == 7)
+
+#endif /* DSTRUCT_H */
diff --git a/src/pmie/src/eval.c b/src/pmie/src/eval.c
new file mode 100644
index 0000000..e119868
--- /dev/null
+++ b/src/pmie/src/eval.c
@@ -0,0 +1,808 @@
+/***********************************************************************
+ * eval.c - task scheduling and expression evaluation
+ ***********************************************************************
+ *
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <limits.h>
+#include "dstruct.h"
+#include "eval.h"
+#include "fun.h"
+#include "pragmatics.h"
+#include "show.h"
+
+/***********************************************************************
+ * scheduling
+ ***********************************************************************/
+
+/* enter Task into task queue */
+static void
+enque(Task *t)
+{
+ Task *q;
+ RealTime tt;
+ RealTime qt;
+
+ q = taskq;
+ if (q == NULL) {
+ taskq = t;
+ t->next = NULL;
+ t->prev = NULL;
+ }
+ else {
+ tt = (t->retry && t->retry < t->eval) ? t->retry : t->eval;
+ while (q) {
+ qt = (q->retry && q->retry < q->eval) ? q->retry : q->eval;
+ if (tt <= qt) {
+ t->next = q;
+ t->prev = q->prev;
+ if (q->prev) q->prev->next = t;
+ else taskq = t;
+ q->prev = t;
+ break;
+ }
+ if (q->next == NULL) {
+ q->next = t;
+ t->next = NULL;
+ t->prev = q;
+ break;
+ }
+ q = q->next;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * reconnect
+ ***********************************************************************/
+
+/* any hosts down or unavailable metrics in this task? */
+static int
+waiting(Task *t)
+{
+ Host *h;
+
+ h = t->hosts;
+ while (h) {
+ if (h->down || h->waits)
+ return 1;
+ h = h->next;
+ }
+ return 0;
+}
+
+/*
+ * state values
+ * STATE_INIT
+ * STATE_FAILINIT
+ * STATE_RECONN
+ * STATE_LOSTCONN
+ */
+
+typedef struct hstate {
+ struct hstate *next;
+ char *name;
+ int state;
+} hstate_t;
+
+static hstate_t *host_map = NULL;
+
+int
+host_state_changed(char *host, int state)
+{
+ hstate_t *hsp;
+
+ for (hsp = host_map; hsp != NULL; hsp = hsp->next) {
+ if (strcmp(host, hsp->name) == 0)
+ break;
+ }
+
+ if (hsp == NULL) {
+ hsp = (hstate_t *)alloc(sizeof(*hsp));
+ hsp->next = host_map;
+ hsp->name = sdup(host);
+ hsp->state = STATE_INIT;
+ host_map = hsp;
+ }
+
+ if (state == hsp->state) return 0;
+
+ if (state == STATE_FAILINIT)
+ __pmNotifyErr(LOG_INFO, "Cannot connect to pmcd on host %s\n", host);
+ else if (state == STATE_RECONN && hsp->state != STATE_INIT)
+ __pmNotifyErr(LOG_INFO, "Re-established connection to pmcd on host %s\n", host);
+ else if (state == STATE_LOSTCONN)
+ __pmNotifyErr(LOG_INFO, "Lost connection to pmcd on host %s\n", host);
+
+ hsp->state = state;
+ return 1;
+}
+
+/* try to reconnect to hosts and initialize missing metrics */
+static void
+enable(Task *t)
+{
+ Host *h;
+ Metric *m;
+ Metric **p;
+
+ h = t->hosts;
+ while (h) {
+
+ /* reconnect to host */
+ if (h->down) {
+ if (reconnect(h)) {
+ h->down = 0;
+ host_state_changed(symName(h->name), STATE_RECONN);
+ }
+ }
+
+ /* reinitialize waiting Metrics */
+ if ((! h->down) && (h->waits)) {
+ p = &h->waits;
+ m = *p;
+ while (m) {
+ switch (reinitMetric(m)) {
+ case 1:
+ *p = m->next;
+ unwaitMetric(m);
+ bundleMetric(h,m);
+ break;
+ case 0:
+ p = &m->next;
+ break;
+ case -1:
+ *p = m->next;
+ m->next = h->duds;
+ h->duds = m;
+ break;
+ }
+ m = *p;
+ }
+ }
+
+ h = h->next;
+ }
+}
+
+
+/***********************************************************************
+ * evaluation
+ ***********************************************************************/
+
+int showTimeFlag = 0; /* set when -e used on the command line */
+
+/* evaluate Task */
+static void
+eval(Task *task)
+{
+ Symbol *s;
+ pmValueSet *vset;
+ int i;
+
+ /* fetch metrics */
+ taskFetch(task);
+
+ /* evaluate rule expressions */
+ s = task->rules;
+ for (i = 0; i < task->nrules; i++) {
+ curr = symValue(*s);
+ if (curr->op < NOP) {
+ (curr->eval)(curr);
+ perf->eval_actual++;
+ }
+ s++;
+ }
+
+ if (verbose) {
+
+ /* send binary values */
+ if (agent) {
+ int sts;
+ s = task->rules;
+ for (i = 0; i < task->nrules; i++) {
+ vset = task->rslt->vset[i];
+ fillVSet(symValue(*s), vset);
+ s++;
+ }
+ __pmOverrideLastFd(PDU_OVERRIDE2);
+ sts = __pmSendResult(STDOUT_FILENO, pmWhichContext(), task->rslt);
+ if (sts < 0) {
+ fprintf(stderr, "Error: __pmSendResult to summary agent failed: %s\n", pmErrStr(sts));
+ exit(0);
+ }
+
+ }
+
+ /* send values to applet */
+ else if (applet) {
+ s = task->rules;
+ for (i = 0; i < task->nrules; i++) {
+ showValue(stdout, symValue(*s));
+ putchar(' ');
+ s++;
+ }
+ putchar('\n');
+ }
+
+ /* print values in ASCII */
+ else {
+ s = task->rules;
+ for (i = 0; i < task->nrules; i++) {
+ printf("%s", symName(*s));
+ if (archives || showTimeFlag) {
+ printf(" (");
+ showTime(stdout, now);
+ putchar(')');
+ }
+ printf(": ");
+ switch (verbose) {
+ case 1:
+ showValue(stdout, symValue(*s));
+ break;
+ case 2:
+ showAnnotatedValue(stdout, symValue(*s));
+ break;
+ case 3:
+ showSatisfyingValue(stdout, symValue(*s));
+ break;
+ }
+ putchar('\n');
+ s++;
+ }
+ putchar('\n');
+ }
+ }
+}
+
+
+/* Mark expression as having invalid values */
+void
+clobber(Expr *x)
+{
+ int i;
+ Boolean *t;
+ double *d;
+
+ if (x->op < NOP) {
+ if (x->arg1)
+ clobber(x->arg1);
+ if (x->arg2)
+ clobber(x->arg2);
+ x->valid = 0;
+ /*
+ * numeric variable or variable?
+ */
+ if (x->sem == PM_SEM_COUNTER ||
+ x->sem == PM_SEM_INSTANT || x->sem == PM_SEM_DISCRETE ||
+ x->sem == SEM_NUMVAR) {
+ d = (double *) x->ring;
+ for (i = 0; i < x->nvals; i++)
+ *d++ = mynan;
+ }
+ else if (x->sem == SEM_BOOLEAN) {
+ t = (Boolean *) x->ring;
+ for (i = 0; i < x->nvals; i++)
+ *t++ = B_UNKNOWN;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * exported functions
+ ***********************************************************************/
+
+/* fill in appropriate evaluator function for given Expr */
+void
+findEval(Expr *x)
+{
+ int arity = 0;
+ Metric *m;
+
+ /*
+ * arity values constructed from bit masks
+ * 1 arg1 has tspan 1, and must always have one metric value
+ * 2 arg2 has tspan 1, and must always have one metric value
+ */
+ if (x->arg1 && x->arg1->tspan == 1) {
+ for (m = x->arg1->metrics; m; m = m->next) {
+ if (m->desc.indom == PM_INDOM_NULL) continue;
+ if (m->specinst == 0) break;
+ }
+ if (m == NULL) arity |= 1;
+ }
+ if (x->arg2 && x->arg2->tspan == 1) {
+ for (m = x->arg2->metrics; m; m = m->next) {
+ if (m->desc.indom == PM_INDOM_NULL) continue;
+ if (m->specinst == 0) break;
+ }
+ if (m == NULL) arity |= 2;
+ }
+
+ /*
+ * never come here with x->op == NULL or OP_VAR
+ */
+ switch (x->op) {
+
+ case RULE:
+ x->eval = rule;
+ break;
+
+ case CND_RULESET:
+ x->eval = ruleset;
+ break;
+
+ case CND_FETCH:
+ if (x->metrics->desc.indom == PM_INDOM_NULL ||
+ x->metrics->conv == 0)
+ x->eval = cndFetch_1;
+ else if (x->metrics->specinst == 0)
+ x->eval = cndFetch_all;
+ else
+ x->eval = cndFetch_n;
+ break;
+
+ case CND_SUM_HOST:
+ x->eval = cndSum_host;
+ break;
+
+ case CND_SUM_INST:
+ x->eval = cndSum_inst;
+ break;
+
+ case CND_SUM_TIME:
+ x->eval = cndSum_time;
+ break;
+
+ case CND_AVG_HOST:
+ x->eval = cndAvg_host;
+ break;
+
+ case CND_AVG_INST:
+ x->eval = cndAvg_inst;
+ break;
+
+ case CND_AVG_TIME:
+ x->eval = cndAvg_time;
+ break;
+
+ case CND_MAX_HOST:
+ x->eval = cndMax_host;
+ break;
+
+ case CND_MAX_INST:
+ x->eval = cndMax_inst;
+ break;
+
+ case CND_MAX_TIME:
+ x->eval = cndMax_time;
+ break;
+
+ case CND_MIN_HOST:
+ x->eval = cndMin_host;
+ break;
+
+ case CND_MIN_INST:
+ x->eval = cndMin_inst;
+ break;
+
+ case CND_MIN_TIME:
+ x->eval = cndMin_time;
+ break;
+
+ case CND_ALL_HOST:
+ x->eval = cndAll_host;
+ break;
+
+ case CND_ALL_INST:
+ x->eval = cndAll_inst;
+ break;
+
+ case CND_ALL_TIME:
+ x->eval = cndAll_time;
+ break;
+
+ case CND_SOME_HOST:
+ x->eval = cndSome_host;
+ break;
+
+ case CND_SOME_INST:
+ x->eval = cndSome_inst;
+ break;
+
+ case CND_SOME_TIME:
+ x->eval = cndSome_time;
+ break;
+
+ case CND_PCNT_HOST:
+ x->eval = cndPcnt_host;
+ break;
+
+ case CND_PCNT_INST:
+ x->eval = cndPcnt_inst;
+ break;
+
+ case CND_PCNT_TIME:
+ x->eval = cndPcnt_time;
+ break;
+
+ case CND_COUNT_HOST:
+ x->eval = cndCount_host;
+ break;
+
+ case CND_COUNT_INST:
+ x->eval = cndCount_inst;
+ break;
+
+ case CND_COUNT_TIME:
+ x->eval = cndCount_time;
+ break;
+
+ case ACT_SEQ:
+ x->eval = actAnd;
+ break;
+
+ case ACT_ALT:
+ x->eval = actOr;
+ break;
+
+ case ACT_SHELL:
+ x->eval = actShell;
+ break;
+
+ case ACT_ALARM:
+ x->eval = actAlarm;
+ break;
+
+ case ACT_STOMP:
+ x->eval = actStomp;
+ break;
+
+ case ACT_SYSLOG:
+ x->eval = actSyslog;
+ break;
+
+ case ACT_PRINT:
+ x->eval = actPrint;
+ break;
+
+ case ACT_ARG:
+ x->eval = actArg;
+ break;
+
+ case CND_DELAY:
+ if (arity & 1)
+ x->eval = cndDelay_1;
+ else
+ x->eval = cndDelay_n;
+ break;
+
+ case CND_RATE:
+ if (arity & 1)
+ x->eval = cndRate_1;
+ else
+ x->eval = cndRate_n;
+ break;
+
+ case CND_NEG:
+ if (arity & 1)
+ x->eval = cndNeg_1;
+ else
+ x->eval = cndNeg_n;
+ break;
+
+ case CND_NOT:
+ if (arity & 1)
+ x->eval = cndNot_1;
+ else
+ x->eval = cndNot_n;
+ break;
+
+ case CND_RISE:
+ if (arity & 1)
+ x->eval = cndRise_1;
+ else
+ x->eval = cndRise_n;
+ break;
+
+ case CND_FALL:
+ if (arity & 1)
+ x->eval = cndFall_1;
+ else
+ x->eval = cndFall_n;
+ break;
+
+ case CND_ADD:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndAdd_1_1;
+ else
+ x->eval = cndAdd_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndAdd_n_1;
+ else
+ x->eval = cndAdd_n_n;
+ }
+ break;
+
+ case CND_SUB:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndSub_1_1;
+ else
+ x->eval = cndSub_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndSub_n_1;
+ else
+ x->eval = cndSub_n_n;
+ }
+ break;
+
+ case CND_MUL:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndMul_1_1;
+ else
+ x->eval = cndMul_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndMul_n_1;
+ else
+ x->eval = cndMul_n_n;
+ }
+ break;
+
+ case CND_DIV:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndDiv_1_1;
+ else
+ x->eval = cndDiv_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndDiv_n_1;
+ else
+ x->eval = cndDiv_n_n;
+ }
+ break;
+
+ case CND_EQ:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndEq_1_1;
+ else
+ x->eval = cndEq_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndEq_n_1;
+ else
+ x->eval = cndEq_n_n;
+ }
+ break;
+
+ case CND_NEQ:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndNeq_1_1;
+ else
+ x->eval = cndNeq_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndNeq_n_1;
+ else
+ x->eval = cndNeq_n_n;
+ }
+ break;
+
+ case CND_LT:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndLt_1_1;
+ else
+ x->eval = cndLt_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndLt_n_1;
+ else
+ x->eval = cndLt_n_n;
+ }
+ break;
+
+ case CND_LTE:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndLte_1_1;
+ else
+ x->eval = cndLte_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndLte_n_1;
+ else
+ x->eval = cndLte_n_n;
+ }
+ break;
+
+ case CND_GT:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndGt_1_1;
+ else
+ x->eval = cndGt_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndGt_n_1;
+ else
+ x->eval = cndGt_n_n;
+ }
+ break;
+
+ case CND_GTE:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndGte_1_1;
+ else
+ x->eval = cndGte_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndGte_n_1;
+ else
+ x->eval = cndGte_n_n;
+ }
+ break;
+
+ case CND_AND:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndAnd_1_1;
+ else
+ x->eval = cndAnd_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndAnd_n_1;
+ else
+ x->eval = cndAnd_n_n;
+ }
+ break;
+
+ case CND_OR:
+ if (arity & 1) {
+ if (arity & 2)
+ x->eval = cndOr_1_1;
+ else
+ x->eval = cndOr_1_n;
+ }
+ else {
+ if (arity & 2)
+ x->eval = cndOr_n_1;
+ else
+ x->eval = cndOr_n_n;
+ }
+ break;
+
+ case CND_MATCH:
+ case CND_NOMATCH:
+ x->eval = cndMatch_inst;
+ break;
+
+ case CND_OTHER:
+ /* OTHER is not really evaluated in this sense, see ruleset() */
+ x->eval = NULL;
+ break;
+
+ default:
+ __pmNotifyErr(LOG_ERR, "findEval: internal error: bad op (%d) %s\n", x->op, opStrings(x->op));
+ dumpExpr(x);
+ exit(1);
+ }
+
+ /* patch in fake actions for archive mode */
+ if (archives &&
+ (x->op == ACT_SHELL || x->op == ACT_ALARM || x->op == ACT_SYSLOG ||
+ x->op == ACT_PRINT || x->op == ACT_STOMP)) {
+ x->eval = actFake;
+ }
+}
+
+
+/* run evaluator */
+void
+run(void)
+{
+ Task *t;
+
+ /* empty task queue */
+ if (taskq == NULL)
+ return;
+
+ /* initialize task scheduling */
+ t = taskq;
+ while (t) {
+ t->eval = t->epoch = start;
+ t->retry = 0;
+ t->tick = 0;
+ t = t->next;
+ }
+
+ /* evaluate and reschedule */
+ t = taskq;
+ for (;;) {
+ if (t->retry && t->retry < t->eval) {
+ now = t->retry;
+ if (now > stop)
+ break;
+ sleepTight(t, SLEEP_RETRY);
+ enable(t);
+ t->retry = waiting(t) ? now + RETRY : 0;
+ }
+ else {
+ now = t->eval;
+ if (now > stop)
+ break;
+ reflectTime(t->delta);
+ sleepTight(t, SLEEP_EVAL);
+ eval(t);
+ t->tick++;
+ t->eval = t->epoch + t->tick * t->delta;
+ if ((! t->retry) && waiting(t))
+ t->retry = now + RETRY;
+ }
+ taskq = t->next;
+ if (taskq) taskq->prev = NULL;
+ enque(t);
+ t = taskq;
+ }
+ __pmNotifyErr(LOG_INFO, "evaluator exiting\n");
+}
+
+
+/* invalidate all expressions being evaluated
+ i.e. mark values as unknown */
+void
+invalidate(void)
+{
+ Task *t;
+ Expr *x;
+ Symbol *s;
+ int i;
+
+ t = taskq;
+ while (t) {
+ s = t->rules;
+ for (i = 0; i < t->nrules; i++) {
+ x = symValue(*s);
+ clobber(x);
+ s++;
+ }
+ t = t->next;
+ }
+}
diff --git a/src/pmie/src/eval.h b/src/pmie/src/eval.h
new file mode 100644
index 0000000..0333725
--- /dev/null
+++ b/src/pmie/src/eval.h
@@ -0,0 +1,46 @@
+/***********************************************************************
+ * eval.h
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef EVAL_H
+#define EVAL_H
+
+#include "dstruct.h"
+
+/* fill in apprpriate evaluator function for given Expr */
+void findEval(Expr *);
+
+/* run evaluator until specified time reached */
+void run(void);
+
+/* invalidate one expression (and descendents) */
+void clobber(Expr *);
+
+/* invalidate all expressions being evaluated */
+void invalidate(void);
+
+/* report changes in pmcd connection state */
+#define STATE_INIT 0
+#define STATE_FAILINIT 1
+#define STATE_RECONN 2
+#define STATE_LOSTCONN 3
+int host_state_changed(char *, int);
+
+#endif /* EVAL_H */
+
diff --git a/src/pmie/src/fetch.sk b/src/pmie/src/fetch.sk
new file mode 100644
index 0000000..76bcb74
--- /dev/null
+++ b/src/pmie/src/fetch.sk
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: fetch.sk - fetch metric values
+ ***********************************************************************/
+
+/*
+ * operator: cndFetch
+ */
+
+static int
+indom_changed(Metric *m)
+{
+ int changed = 0;
+ int j;
+
+ /* check for changes in the instance domain */
+ if (m->vset == NULL || m->vset->numval <= 0) {
+ if (m->m_idom > 0)
+ changed = 1;
+ }
+ else {
+ if (m->vset->numval != m->m_idom)
+ changed = 1;
+ else {
+ for (j = 0; j < m->m_idom; j++) {
+ if (m->iids[j] != m->vset->vlist[j].inst) {
+ changed = 1;
+ break;
+ }
+ }
+ }
+ }
+
+ if (changed) {
+ int old;
+ int new;
+ int numval;
+ char **inames;
+ int *iids;
+ int sts;
+ int handle = -1;
+ int old_handle = -1;
+ char **get_inames;
+ int *get_iids;
+ int numinst = -1;
+
+ if (m->vset == NULL || m->vset->numval <= 0)
+ numval = 0;
+ else
+ numval = m->vset->numval;
+
+ /* build new inames[] and iids[] */
+ if (numval > 0) {
+ inames = (char **)alloc(numval * sizeof(char *));
+ iids = (int *)alloc(numval * sizeof(int));
+ if (numval > m->m_idom && m->desc.sem == PM_SEM_COUNTER) {
+ /* more instances, so expand the val array */
+ m->vals = (double *)ralloc(m->vals, numval * sizeof(double));
+ for (j = m->m_idom; j < numval; j++)
+ m->vals[j] = 0;
+ }
+ }
+ else {
+ inames = NULL;
+ iids = NULL;
+ }
+
+ for (new = 0; new < numval; new++) {
+ for (old = 0; old < m->m_idom; old++) {
+ if (m->iids[old] == m->vset->vlist[new].inst)
+ break;
+ }
+ if (old < m->m_idom) {
+ /* in both lists */
+ inames[new] = m->inames[old];
+ m->inames[old] = NULL;
+ iids[new] = m->iids[old];
+ if (m->desc.sem == PM_SEM_COUNTER && new != old) {
+ /* swap vals[] */
+ double d;
+ d = m->vals[new];
+ m->vals[new] = m->vals[old];
+ m->vals[old] = d;
+ }
+ }
+ else {
+ /* new one */
+ inames[new] = NULL;
+ iids[new] = m->vset->vlist[new].inst;
+ }
+ }
+
+ /*
+ * clean up old inames and iids, the install new ones
+ */
+ if (m->m_idom > 0 && m->inames != NULL) {
+ for (old = 0; old < m->m_idom; old++) {
+ if (m->inames[old] != NULL) free(m->inames[old]);
+ }
+ }
+ if (m->inames != NULL)
+ free(m->inames);
+ if (m->iids != NULL)
+ free(m->iids);
+ m->inames = inames;
+ m->iids = iids;
+ m->m_idom = numval;
+
+ for (new = 0; new < m->m_idom; new++) {
+ if (m->inames[new] == NULL) {
+ if (handle < 0) {
+ /* set up temporary context */
+ if (old_handle < 0)
+ old_handle = pmWhichContext();
+ handle = newContext(symName(m->hname));
+ }
+ if (handle < 0) {
+ sts = -1;
+ }
+ else {
+ if (archives) {
+ if ((sts = pmNameInDomArchive(m->desc.indom, m->iids[new], &m->inames[new])) < 0) {
+ __pmNotifyErr(LOG_ERR, "metric %s from %s: instance domain not "
+ "available in archive\npmNameInDomArchive failed: %s\n",
+ symName(m->mname), findsource(symName(m->hname)), pmErrStr(sts));
+ }
+ }
+ else {
+ if (numinst == -1) {
+ if ((sts = pmGetInDom(m->desc.indom, &get_iids, &get_inames)) < 0) {
+ __pmNotifyErr(LOG_ERR, "metric %s from %s: instance domain not (currently) available\n"
+ "pmGetInDom failed: %s\n",
+ symName(m->mname), findsource(symName(m->hname)), pmErrStr(sts));
+ }
+ else
+ numinst = sts;
+ }
+ sts = -1;
+ for (j = 0; j < numinst; j++) {
+ if (m->iids[new] == get_iids[j]) {
+ m->inames[new] = sdup(get_inames[j]);
+ sts = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (sts < 0) {
+ /* ugly, but not much choice */
+ m->inames[new] = sdup("inst#xxxxxxxxxxxx?");
+ sprintf(m->inames[new], "inst#%d?", m->iids[new]);
+ }
+
+ if (m->desc.sem == PM_SEM_COUNTER)
+ m->vals[new] = 0;
+ }
+ }
+ if (handle >= 0) {
+ pmDestroyContext(handle);
+ if (old_handle >= 0)
+ pmUseContext(old_handle);
+ }
+ if (numinst > 0) {
+ /* pmGetInDom returned some instances above */
+ free(get_iids);
+ free(get_inames);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "indom_changed: %s from %s\n",
+ symName(m->mname), symName(m->hname));
+ if (m->m_idom < 1) fprintf(stderr, " %d instances!\n", m->m_idom);
+ for (j = 0; j < m->m_idom; j++) {
+ fprintf(stderr, " indom[%d] %d \"%s\"\n",
+ j, m->iids[j], m->inames[j]);
+ }
+ }
+#endif
+ }
+
+ return changed;
+}
+
+/* null instance domain - so 1 instance only */
+void
+cndFetch_1(Expr *x)
+{
+ Metric *m = x->metrics;
+ double *op;
+ RealTime stamp = 0;
+ pmAtomValue a;
+ double t;
+ int i, j;
+
+ ROTATE(x)
+ x->valid++;
+ op = (double *)x->smpls[0].ptr;
+
+ for (i = 0; i < x->hdom; i++) {
+ /* extract value */
+ if (m->vset && m->vset->numval == 1) {
+ pmExtractValue(m->vset->valfmt, &m->vset->vlist[0], m->desc.type, &a, PM_TYPE_DOUBLE);
+ *op++ = m->conv * a.d;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndFetch_1: %s from %s = %g",
+ symName(m->mname), symName(m->hname), m->conv * a.d);
+ if (m->conv != 1) fprintf(stderr, " (unconv = %g)", a.d);
+ fputc('\n', stderr);
+ }
+#endif
+ }
+
+ /* no value */
+ else {
+ x->valid = 0;
+ for (j = i; j < x->hdom; j++) {
+ m->stomp = 0;
+ m->vset = NULL;
+ m++;
+ }
+ return;
+ }
+ m->vset = NULL;
+
+ /* rate computation */
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ op--;
+ if (m->m_idom > 0) {
+ t = *op - m->vals[0];
+ if (t < 0.0 && dowrap) {
+ switch (m->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ t += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ t += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+ t /= (m->stamp - m->stomp);
+ m->vals[0] = *op;
+ if (t < 0.0) x->valid = 0;
+ else *op = t;
+ op++;
+ }
+ if (m->stomp == 0) x->valid = 0;
+ m->stomp = m->stamp;
+ }
+
+ /* pick up most recent timestamp */
+ if (m->stamp > stamp) stamp = m->stamp;
+
+ m++;
+ }
+ x->smpls[0].stamp = stamp;
+}
+
+void
+cndFetch_all(Expr *x)
+{
+ Metric *m = x->metrics;
+ double *op;
+ RealTime stamp = 0;
+ pmAtomValue a;
+ double t;
+ int fix_idom = 0;
+ int i, j;
+
+ ROTATE(x)
+
+ /* preliminary scan through Metrics */
+ for (i = 0; i < x->hdom; i++) {
+ /* check for different instances */
+ if (indom_changed(m)) {
+ fix_idom = 1;
+ m->stomp = 0;
+ }
+ m++;
+ }
+
+ if (fix_idom) {
+ /*
+ * propagate indom changes up the expression tree
+ * and reshape the ring buffer if required
+ */
+ instFetchExpr(x);
+ }
+
+ /*
+ * even if ring buffer reshaped in instFetchExpr(), we're
+ * about to populate it with another set of values, so bump
+ * valid counter
+ */
+ x->valid++;
+
+ /* extract values */
+ op = (double *)x->smpls[0].ptr;
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+
+ /* extract values from m->vset */
+ for (j = 0; j < m->m_idom; j++) {
+ pmExtractValue(m->vset->valfmt, &m->vset->vlist[j], m->desc.type, &a, PM_TYPE_DOUBLE);
+ *op++ = m->conv * a.d;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndFetch_all: %s[%s] from %s = %g",
+ symName(m->mname), m->inames[j], symName(m->hname), m->conv * a.d);
+ if (m->conv != 1) fprintf(stderr, " (unconv = %g)", a.d);
+ fputc('\n', stderr);
+ }
+#endif
+ }
+ m->vset = NULL;
+
+ /* rate computation */
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ op -= m->m_idom;
+ for (j = 0; j < m->m_idom; j++) {
+ t = *op - m->vals[j];
+ if (t < 0.0 && dowrap) {
+ switch (m->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ t += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ t += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+ t /= (m->stamp - m->stomp);
+ m->vals[j] = *op;
+ if (t < 0.0) x->valid = 0;
+ else *op = t;
+ op++;
+ }
+ if (m->stomp == 0) x->valid = 0;
+ m->stomp = m->stamp;
+ }
+
+ /* pick up most recent timestamp */
+ if (m->stamp > stamp) stamp = m->stamp;
+
+ m++;
+ }
+ x->smpls[0].stamp = stamp;
+}
+
+void
+cndFetch_n(Expr *x)
+{
+ Metric *m = x->metrics;
+ double *op;
+ RealTime stamp = 0;
+ pmAtomValue a;
+ double t;
+ int i, j, k;
+
+ ROTATE(x)
+ x->valid++;
+ op = (double *)x->smpls[0].ptr;
+
+ for (i = 0; i < x->hdom; i++) {
+ /* no values */
+ if ((m->vset == NULL) || (m->vset->numval < 0)) {
+ x->valid = 0;
+ for (j = i; j < x->hdom; j++) {
+ m->stomp = 0;
+ m->vset = NULL;
+ m++;
+ }
+ return;
+ }
+
+ /* extract values */
+ for (j = 0; j < m->m_idom; j++) {
+ for (k = 0; k < m->vset->numval; k++) {
+ if (m->iids[j] == m->vset->vlist[k].inst) {
+ pmExtractValue(m->vset->valfmt, &m->vset->vlist[k], m->desc.type, &a, PM_TYPE_DOUBLE);
+ *op++ = m->conv * a.d;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndFetch_n: %s[%s] from %s = %g",
+ symName(m->mname), m->inames[j], symName(m->hname), m->conv * a.d);
+ if (m->conv != 1) fprintf(stderr, " (unconv = %g)", a.d);
+ fputc('\n', stderr);
+ }
+#endif
+ break;
+ }
+ }
+
+ /* missing value */
+ if (k == m->vset->numval) {
+ x->valid = 0;
+ m->stomp = 0;
+ m->vset = NULL;
+ return;
+ }
+ }
+ m->vset = NULL;
+
+ /* rate computation */
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ op -= m->m_idom;
+ for (j = 0; j < m->m_idom; j++) {
+ t = *op - m->vals[j];
+ if (t < 0.0 && dowrap) {
+ switch (m->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ t += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ t += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+ t /= (m->stamp - m->stomp);
+ m->vals[j] = *op;
+ if (t < 0.0) x->valid = 0;
+ else *op = t;
+ op++;
+ }
+ if (m->stomp == 0) x->valid = 0;
+ m->stomp = m->stamp;
+ }
+
+ /* pick up most recent timestamp */
+ if (m->stamp > stamp) stamp = m->stamp;
+
+ m++;
+ }
+ x->smpls[0].stamp = stamp;
+}
+
+
diff --git a/src/pmie/src/fun.h b/src/pmie/src/fun.h
new file mode 100644
index 0000000..532b4af
--- /dev/null
+++ b/src/pmie/src/fun.h
@@ -0,0 +1,124 @@
+/***********************************************************************
+ * fun.h - expression evaluator functions
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef FUN_H
+#define FUN_H
+
+#include "dstruct.h"
+#include "andor.h"
+
+#define ROTATE(x) if ((x)->nsmpls > 1) rotate(x);
+#define EVALARG(x) if ((x)->op < NOP) ((x)->eval)(x);
+
+/* expression evaluator function prototypes */
+void rule(Expr *);
+void ruleset(Expr *);
+void cndFetch_all(Expr *);
+void cndFetch_n(Expr *);
+void cndFetch_1(Expr *);
+void cndDelay_n(Expr *);
+void cndDelay_1(Expr *);
+void cndRate_n(Expr *);
+void cndRate_1(Expr *);
+void cndSum_host(Expr *);
+void cndSum_inst(Expr *);
+void cndSum_time(Expr *);
+void cndAvg_host(Expr *);
+void cndAvg_inst(Expr *);
+void cndAvg_time(Expr *);
+void cndMax_host(Expr *);
+void cndMax_inst(Expr *);
+void cndMax_time(Expr *);
+void cndMin_host(Expr *);
+void cndMin_inst(Expr *);
+void cndMin_time(Expr *);
+void cndNeg_n(Expr *);
+void cndNeg_1(Expr *);
+void cndAdd_n_n(Expr *);
+void cndAdd_1_n(Expr *);
+void cndAdd_n_1(Expr *);
+void cndAdd_1_1(Expr *);
+void cndSub_n_n(Expr *);
+void cndSub_1_n(Expr *);
+void cndSub_n_1(Expr *);
+void cndSub_1_1(Expr *);
+void cndMul_n_n(Expr *);
+void cndMul_1_n(Expr *);
+void cndMul_n_1(Expr *);
+void cndMul_1_1(Expr *);
+void cndDiv_n_n(Expr *);
+void cndDiv_1_n(Expr *);
+void cndDiv_n_1(Expr *);
+void cndDiv_1_1(Expr *);
+void cndEq_n_n(Expr *);
+void cndEq_1_n(Expr *);
+void cndEq_n_1(Expr *);
+void cndEq_1_1(Expr *);
+void cndNeq_n_n(Expr *);
+void cndNeq_1_n(Expr *);
+void cndNeq_n_1(Expr *);
+void cndNeq_1_1(Expr *);
+void cndLt_n_n(Expr *);
+void cndLt_1_n(Expr *);
+void cndLt_n_1(Expr *);
+void cndLt_1_1(Expr *);
+void cndLte_n_n(Expr *);
+void cndLte_1_n(Expr *);
+void cndLte_n_1(Expr *);
+void cndLte_1_1(Expr *);
+void cndGt_n_n(Expr *);
+void cndGt_1_n(Expr *);
+void cndGt_n_1(Expr *);
+void cndGt_1_1(Expr *);
+void cndGte_n_n(Expr *);
+void cndGte_1_n(Expr *);
+void cndGte_n_1(Expr *);
+void cndGte_1_1(Expr *);
+void cndNot_n(Expr *);
+void cndNot_1(Expr *);
+void cndRise_n(Expr *);
+void cndRise_1(Expr *);
+void cndFall_n(Expr *);
+void cndFall_1(Expr *);
+void cndMatch_inst(Expr *);
+void cndAll_host(Expr *);
+void cndAll_inst(Expr *);
+void cndAll_time(Expr *);
+void cndSome_host(Expr *);
+void cndSome_inst(Expr *);
+void cndSome_time(Expr *);
+void cndPcnt_host(Expr *);
+void cndPcnt_inst(Expr *);
+void cndPcnt_time(Expr *);
+void cndCount_host(Expr *);
+void cndCount_inst(Expr *);
+void cndCount_time(Expr *);
+void actAnd(Expr *);
+void actOr(Expr *);
+void actShell(Expr *);
+void actAlarm(Expr *);
+void actSyslog(Expr *);
+void actPrint(Expr *);
+void actStomp(Expr *);
+void actArg(Expr *);
+void actFake(Expr *);
+
+#endif /* FUN_H */
+
diff --git a/src/pmie/src/grammar.y b/src/pmie/src/grammar.y
new file mode 100644
index 0000000..0760488
--- /dev/null
+++ b/src/pmie/src/grammar.y
@@ -0,0 +1,666 @@
+/***********************************************************************
+ * grammar.y - yacc grammar for rule language
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include "dstruct.h"
+#include "syntax.h"
+#include "lexicon.h"
+#include "pragmatics.h"
+#include "systemlog.h"
+#include "stomp.h"
+#include "show.h"
+
+/* strings for error reporting */
+char precede[] = "precede";
+char follow[] = "follow";
+char act_str[] = "action";
+char bexp_str[] = "logical expression";
+char aexp_str[] = "arithmetic expression";
+char quant_str[] = "quantifier";
+char aggr_str[] = "aggregation operator";
+char pcnt_str[] = "percentage quantifier";
+char host_str[] = "host name";
+char inst_str[] = "instance name";
+char sample_str[] = "sample number(s)";
+char tstr_str[] = "(time interval optional) and string";
+char num_str[] = "number";
+
+/* report grammatical error */
+static void
+gramerr(char *phrase, char *pos, char *op)
+{
+ fprintf(stderr, "%s expected to %s %s\n", phrase, pos, op);
+ lexSync();
+}
+
+
+%}
+
+/***********************************************************************
+ * yacc token and operator declarations
+ ***********************************************************************/
+
+%expect 184
+%start stmnt
+
+%token ARROW
+%token SHELL
+%token ALARM
+%token SYSLOG
+%token PRINT
+%token STOMP
+%token SOME_QUANT
+%token ALL_QUANT
+%token PCNT_QUANT
+%token LEQ_REL
+%token GEQ_REL
+%token NEQ_REL
+%token EQ_REL
+%token AND
+%token SEQ
+%token OR
+%token ALT
+%token NOT
+%token RISE
+%token FALL
+%token MATCH
+%token NOMATCH
+%token RULESET
+%token ELSE
+%token UNKNOWN
+%token OTHERWISE
+%token MIN_AGGR
+%token MAX_AGGR
+%token AVG_AGGR
+%token SUM_AGGR
+%token COUNT_AGGR
+%token TIME_DOM
+%token INST_DOM
+%token HOST_DOM
+%token UNIT_SLASH
+%token INTERVAL
+%token <u> EVENT_UNIT
+%token <u> TIME_UNIT
+%token <u> SPACE_UNIT
+%token <d> NUMBER
+%token <s> IDENT
+%token <s> STRING
+%token TRU
+%token FALS
+%token <x> VAR
+
+%type <x> exp
+%type <x> rule
+%type <x> ruleset
+%type <x> rulelist
+%type <x> rulesetopt
+%type <x> act
+%type <x> bexp
+%type <x> rexp
+%type <x> actarg
+%type <x> arglist
+%type <x> aexp
+%type <x> quant
+%type <x> aggr
+%type <x> num
+%type <x> str
+%type <i> dom
+%type <x> fetch
+%type <s> metric
+%type <sa> hosts
+%type <sa> insts
+%type <t> times
+%type <u> units
+%type <u> unit
+
+%left NAME_DELIM
+%left ARROW
+%left AND OR SEQ ALT
+%left NOT RISE FALL
+%left ALL_QUANT SOME_QUANT PCNT_QUANT
+%left MATCH NOMATCH
+%left '>' '<' EQ_REL NEQ_REL GEQ_REL LEQ_REL
+%left '+' '-'
+%left '*' '/'
+%left UMINUS RATE
+%left SUM_AGGR AVG_AGGR MAX_AGGR MIN_AGGR COUNT_AGGR
+%left SHELL ALARM SYSLOG PRINT STOMP
+%left ':' '#' '@'
+%left UNIT_SLASH INTERVAL
+
+%%
+
+/***********************************************************************
+ * yacc productions
+ ***********************************************************************/
+
+stmnt : /* empty */
+ { parse = NULL; }
+ | IDENT '=' exp
+ { parse = statement($1, $3);
+ if ((agent || applet) && $3 != NULL &&
+ ($3->op == RULE || $3->op == ACT_SEQ ||
+ $3->op == ACT_ALT || $3->op == ACT_SHELL ||
+ $3->op == ACT_ALARM || $3->op == ACT_SYSLOG ||
+ $3->op == ACT_PRINT || $3->op == ACT_STOMP)) {
+ synerr();
+ fprintf(stderr, "operator %s not allowed in agent "
+ "mode\n", opStrings($3->op));
+ parse = NULL;
+ }
+ }
+ | exp
+ { parse = statement(NULL, $1);
+ if (agent) {
+ synerr();
+ fprintf(stderr, "expressions must be named in agent "
+ "mode\n");
+ parse = NULL;
+ }
+ }
+ ;
+
+exp : rule
+ { $$ = $1; }
+ | ruleset
+ { $$ = $1; }
+ | bexp
+ { $$ = $1; }
+ | aexp
+ { $$ = $1; }
+ | act
+ { $$ = $1; }
+ | str
+ { $$ = $1; }
+ ;
+
+rule : '(' rule ')'
+ { $$ = $2; }
+ | bexp ARROW act
+ { $$ = ruleExpr($1, $3); }
+
+ /* error reporting */
+ | error ARROW
+ { gramerr(bexp_str, precede, opStrings(RULE));
+ $$ = NULL; }
+ | bexp ARROW error
+ { gramerr(act_str, follow, opStrings(RULE));
+ $$ = NULL; }
+ ;
+
+ruleset : RULESET rulelist rulesetopt
+ { $$ = binaryExpr(CND_RULESET, $2, $3); }
+ ;
+
+rulelist : rule
+ { $$ = $1; }
+ | rule ELSE rulelist
+ /*
+ * use right recursion here so rules appear l-to-r in
+ * the expression tree to match the required order of
+ * evaluation
+ */
+ { $$ = binaryExpr(CND_OR, $1, $3); }
+ ;
+
+rulesetopt : UNKNOWN ARROW act
+ { $$ = binaryExpr(CND_OTHER, ruleExpr(boolConst(B_TRUE), $3), boolConst(B_FALSE)); }
+ | OTHERWISE ARROW act
+ { $$ = binaryExpr(CND_OTHER, boolConst(B_FALSE), ruleExpr(boolConst(B_TRUE), $3)); }
+ | UNKNOWN ARROW act OTHERWISE ARROW act
+ { $$ = binaryExpr(CND_OTHER, ruleExpr(boolConst(B_TRUE), $3), ruleExpr(boolConst(B_TRUE), $6)); }
+ | /* empty */
+ { $$ = NULL; }
+ ;
+
+act : '(' act ')'
+ { $$ = $2; }
+ | act SEQ act
+ { $$ = actExpr(ACT_SEQ, $1, $3); }
+ | act ALT act
+ { $$ = actExpr(ACT_ALT, $1, $3); }
+ | SHELL actarg
+ { $$ = actExpr(ACT_SHELL, $2, NULL); }
+ | SHELL num actarg /* holdoff format */
+ { $$ = actExpr(ACT_SHELL, $3, $2); }
+ | ALARM actarg
+ { $$ = actExpr(ACT_ALARM, $2, NULL); }
+ | ALARM num actarg /* holdoff format */
+ { $$ = actExpr(ACT_ALARM, $3, $2); }
+ | SYSLOG actarg
+ { do_syslog_args($2);
+ $$ = actExpr(ACT_SYSLOG, $2, NULL);
+ }
+ | SYSLOG num actarg /* holdoff format */
+ {
+ do_syslog_args($3);
+ $$ = actExpr(ACT_SYSLOG, $3, $2);
+ }
+ | PRINT actarg
+ { $$ = actExpr(ACT_PRINT, $2, NULL); }
+ | PRINT num actarg /* holdoff format */
+ { $$ = actExpr(ACT_PRINT, $3, $2); }
+ | STOMP actarg
+ {
+ stomping = 1;
+ $$ = actExpr(ACT_STOMP, $2, NULL);
+ }
+ | STOMP num actarg /* holdoff format */
+ {
+ stomping = 1;
+ $$ = actExpr(ACT_STOMP, $3, $2);
+ }
+
+ /* error reporting */
+ | error SEQ
+ { gramerr(act_str, precede, opStrings(ACT_SEQ));
+ $$ = NULL; }
+/*** following cause harmless shift/reduce conflicts ***/
+ | act SEQ error
+ { gramerr(act_str, follow, opStrings(ACT_SEQ));
+ $$ = NULL; }
+ | error ALT
+ { gramerr(act_str, precede, opStrings(ACT_ALT));
+ $$ = NULL; }
+ | act ALT error
+ { gramerr(act_str, follow, opStrings(ACT_ALT));
+ $$ = NULL; }
+/*** preceding cause harmless shift/reduce conflicts ***/
+ | SHELL error
+ { gramerr(tstr_str, follow, opStrings(ACT_SHELL));
+ $$ = NULL; }
+ | ALARM error
+ { gramerr(tstr_str, follow, opStrings(ACT_ALARM));
+ $$ = NULL; }
+ | SYSLOG error
+ { gramerr(tstr_str, follow, opStrings(ACT_SYSLOG));
+ $$ = NULL; }
+ | PRINT error
+ { gramerr(tstr_str, follow, opStrings(ACT_PRINT));
+ $$ = NULL; }
+ | STOMP error
+ { gramerr(tstr_str, follow, opStrings(ACT_STOMP));
+ $$ = NULL; }
+ ;
+
+actarg : arglist
+ { $$ = actArgExpr($1, NULL); }
+ ;
+
+arglist : STRING
+ { $$ = actArgList(NULL, $1); }
+ | STRING arglist
+ { $$ = actArgList($2, $1); }
+ ;
+
+bexp : '(' bexp ')'
+ { $$ = $2; }
+ | rexp
+ { $$ = $1; }
+ | quant
+ { $$ = $1; }
+ | TRU
+ { $$ = boolConst(B_TRUE); }
+ | FALS
+ { $$ = boolConst(B_FALSE); }
+ | NOT bexp
+ { $$ = unaryExpr(CND_NOT, $2); }
+ | RISE bexp
+ { $$ = boolMergeExpr(CND_RISE, $2); }
+ | FALL bexp
+ { $$ = boolMergeExpr(CND_FALL, $2); }
+ | bexp AND bexp
+ { $$ = binaryExpr(CND_AND, $1, $3); }
+ | bexp OR bexp
+ { $$ = binaryExpr(CND_OR, $1, $3); }
+ | MATCH str bexp
+ { /*
+ * note args are reversed so bexp is to the "left"
+ * of the operand node in the expr tree
+ */
+ $$ = binaryExpr(CND_MATCH, $3, $2); }
+ | NOMATCH str bexp
+ { $$ = binaryExpr(CND_NOMATCH, $3, $2); }
+
+ /* error reporting */
+ | NOT error
+ { gramerr(bexp_str, follow, opStrings(CND_NOT));
+ $$ = NULL; }
+ | RISE error
+ { gramerr(bexp_str, follow, opStrings(CND_RISE));
+ $$ = NULL; }
+ | FALL error
+ { gramerr(bexp_str, follow, opStrings(CND_FALL));
+ $$ = NULL; }
+ | MATCH error
+ { gramerr("regular expression", follow, opStrings(CND_MATCH));
+ $$ = NULL; }
+ | MATCH str error
+ { gramerr(bexp_str, follow, "regular expression");
+ $$ = NULL; }
+ | NOMATCH error
+ { gramerr("regular expression", follow, opStrings(CND_NOMATCH));
+ $$ = NULL; }
+ | NOMATCH str error
+ { gramerr(bexp_str, follow, "regular expression");
+ $$ = NULL; }
+/*** following cause harmless shift/reduce conflicts ***/
+ | error AND
+ { gramerr(bexp_str, precede, opStrings(CND_AND));
+ $$ = NULL; }
+ | bexp AND error
+ { gramerr(bexp_str, follow, opStrings(CND_AND));
+ $$ = NULL; }
+ | error OR
+ { gramerr(bexp_str, precede, opStrings(CND_OR));
+ $$ = NULL; }
+ | bexp OR error
+ { gramerr(bexp_str, follow, opStrings(CND_OR));
+ $$ = NULL; }
+/*** preceding cause harmless shift/reduce conflicts ***/
+ ;
+
+quant : ALL_QUANT dom bexp
+ { $$ = domainExpr(CND_ALL_HOST, $2, $3); }
+ | SOME_QUANT dom bexp
+ { $$ = domainExpr(CND_SOME_HOST, $2, $3); }
+ | NUMBER PCNT_QUANT dom bexp
+ { $$ = percentExpr($1, $3, $4); }
+
+ /* error reporting */
+ | ALL_QUANT dom error
+ { gramerr(bexp_str, follow, quant_str);
+ $$ = NULL; }
+ | SOME_QUANT dom error
+ { gramerr(bexp_str, follow, quant_str);
+ $$ = NULL; }
+ | NUMBER PCNT_QUANT dom error
+ { gramerr(bexp_str, follow, quant_str);
+ $$ = NULL; }
+ | error PCNT_QUANT
+ { gramerr(num_str, precede, pcnt_str);
+ $$ = NULL; }
+ ;
+
+rexp : aexp EQ_REL aexp
+ { $$ = relExpr(CND_EQ, $1, $3); }
+ | aexp NEQ_REL aexp
+ { $$ = relExpr(CND_NEQ, $1, $3); }
+ | aexp '<' aexp
+ { $$ = relExpr(CND_LT, $1, $3); }
+ | aexp '>' aexp
+ { $$ = relExpr(CND_GT, $1, $3); }
+ | aexp LEQ_REL aexp
+ { $$ = relExpr(CND_LTE, $1, $3); }
+ | aexp GEQ_REL aexp
+ { $$ = relExpr(CND_GTE, $1, $3); }
+
+ /* error reporting */
+ | error EQ_REL
+ { gramerr(aexp_str, precede, opStrings(CND_EQ));
+ $$ = NULL; }
+ | aexp EQ_REL error
+ { gramerr(aexp_str, follow, opStrings(CND_EQ));
+ $$ = NULL; }
+ | error NEQ_REL
+ { gramerr(aexp_str, precede, opStrings(CND_NEQ));
+ $$ = NULL; }
+ | aexp NEQ_REL error
+ { gramerr(aexp_str, follow, opStrings(CND_NEQ));
+ $$ = NULL; }
+ | error '<'
+ { gramerr(aexp_str, precede, opStrings(CND_LT));
+ $$ = NULL; }
+ | aexp '<' error
+ { gramerr(aexp_str, follow, opStrings(CND_LT));
+ $$ = NULL; }
+ | error '>'
+ { gramerr(aexp_str, precede, opStrings(CND_GT));
+ $$ = NULL; }
+ | aexp '>' error
+ { gramerr(aexp_str, follow, opStrings(CND_GT));
+ $$ = NULL; }
+ | error LEQ_REL
+ { gramerr(aexp_str, precede, opStrings(CND_LTE));
+ $$ = NULL; }
+ | aexp LEQ_REL error
+ { gramerr(aexp_str, follow, opStrings(CND_LTE));
+ $$ = NULL; }
+ | error GEQ_REL
+ { gramerr(aexp_str, precede, opStrings(CND_GTE));
+ $$ = NULL; }
+ | aexp GEQ_REL error
+ { gramerr(aexp_str, follow, opStrings(CND_GTE));
+ $$ = NULL; }
+ ;
+
+aexp : '(' aexp ')'
+ { $$ = $2; }
+ | fetch
+ { $$ = $1; }
+ | num
+ { $$ = $1; }
+ | VAR
+ { $$ = $1; }
+ | aggr
+ { $$ = $1; }
+ | RATE aexp
+ { $$ = numMergeExpr(CND_RATE, $2); }
+ | '-' aexp %prec UMINUS
+ { $$ = unaryExpr(CND_NEG, $2); }
+ | aexp '+' aexp
+ { $$ = binaryExpr(CND_ADD, $1, $3); }
+ | aexp '-' aexp
+ { $$ = binaryExpr(CND_SUB, $1, $3); }
+ | aexp '*' aexp
+ { $$ = binaryExpr(CND_MUL, $1, $3); }
+ | aexp '/' aexp
+ { $$ = binaryExpr(CND_DIV, $1, $3); }
+
+ /* error reporting */
+ | RATE error
+ { gramerr(aexp_str, follow, opStrings(CND_RATE));
+ $$ = NULL; }
+ | '-' error %prec UMINUS
+ { gramerr(aexp_str, follow, opStrings(CND_NEG));
+ $$ = NULL; }
+/*** following cause harmless shift/reduce conflicts ***/
+ | error '+'
+ { gramerr(aexp_str, precede, opStrings(CND_ADD));
+ $$ = NULL; }
+ | aexp '+' error
+ { gramerr(aexp_str, follow, opStrings(CND_ADD));
+ $$ = NULL; }
+ | error '-'
+ { gramerr(aexp_str, precede, opStrings(CND_SUB));
+ $$ = NULL; }
+ | aexp '-' error
+ { gramerr(aexp_str, follow, opStrings(CND_SUB));
+ $$ = NULL; }
+ | error '*'
+ { gramerr(aexp_str, precede, opStrings(CND_MUL));
+ $$ = NULL; }
+ | aexp '*' error
+ { gramerr(aexp_str, follow, opStrings(CND_MUL));
+ $$ = NULL; }
+ | error '/'
+ { gramerr(aexp_str, precede, opStrings(CND_DIV));
+ $$ = NULL; }
+ | aexp '/' error
+ { gramerr(aexp_str, follow, opStrings(CND_DIV));
+ $$ = NULL; }
+/*** preceding cause harmless shift/reduce conflicts ***/
+ ;
+
+aggr : SUM_AGGR dom aexp
+ { $$ = domainExpr(CND_SUM_HOST, $2, $3); }
+ | AVG_AGGR dom aexp
+ { $$ = domainExpr(CND_AVG_HOST, $2, $3); }
+ | MAX_AGGR dom aexp
+ { $$ = domainExpr(CND_MAX_HOST, $2, $3); }
+ | MIN_AGGR dom aexp
+ { $$ = domainExpr(CND_MIN_HOST, $2, $3); }
+ | COUNT_AGGR dom bexp
+ { $$ = domainExpr(CND_COUNT_HOST, $2, $3); }
+
+ /* error reporting */
+ | SUM_AGGR dom error
+ { gramerr(aexp_str, follow, aggr_str);
+ $$ = NULL; }
+ | AVG_AGGR dom error
+ { gramerr(aexp_str, follow, aggr_str);
+ $$ = NULL; }
+ | MAX_AGGR dom error
+ { gramerr(aexp_str, follow, aggr_str);
+ $$ = NULL; }
+ | MIN_AGGR dom error
+ { gramerr(aexp_str, follow, aggr_str);
+ $$ = NULL; }
+ ;
+
+dom : HOST_DOM
+ { $$ = HOST_DOM; }
+ | INST_DOM
+ { $$ = INST_DOM; }
+ | TIME_DOM
+ { $$ = TIME_DOM; }
+ ;
+
+fetch : metric hosts insts times
+ { $$ = fetchExpr($1, $2, $3, $4); }
+ ;
+
+metric : IDENT
+ { $$ = $1; }
+ ;
+
+hosts : /* empty */
+ { $$.n = 0;
+ $$.ss = NULL; }
+ | hosts ':' IDENT
+ { $$.n = $1.n + 1;
+ $$.ss = (char **) ralloc($1.ss, $$.n * sizeof(char *));
+ $$.ss[$1.n] = $3; }
+
+ /* error reporting */
+ | hosts ':' error
+ { gramerr(host_str, follow, ":");
+ $$.n = 0;
+ $$.ss = NULL; }
+ ;
+
+insts : /* empty */
+ { $$.n = 0;
+ $$.ss = NULL; }
+ | insts '#' IDENT
+ { $$.n = $1.n + 1;
+ $$.ss = (char **) ralloc($1.ss, $$.n * sizeof(char *));
+ $$.ss[$1.n] = $3; }
+
+ /* error reporting */
+ | insts '#' error
+ { gramerr(inst_str, follow, "#");
+ $$.n = 0;
+ $$.ss = NULL; }
+ ;
+
+times : /* empty */
+ { $$.t1 = 0;
+ $$.t2 = 0; }
+ | '@' NUMBER
+ { $$.t1 = $2;
+ $$.t2 = $2; }
+ | '@' NUMBER INTERVAL NUMBER
+ { if ($2 <= $4) {
+ $$.t1 = $2;
+ $$.t2 = $4;
+ }
+ else {
+ $$.t1 = $4;
+ $$.t2 = $2;
+ } }
+
+ /* error reporting */
+ | '@' error
+ { gramerr(sample_str, follow, "@");
+ $$.t1 = 0;
+ $$.t2 = 0; }
+ ;
+
+num : NUMBER units
+ { $$ = numConst($1, $2); }
+ ;
+
+units : /* empty */
+ { $$ = noUnits; }
+ | units unit
+ { $$ = $1;
+ if ($2.dimSpace) {
+ $$.dimSpace = $2.dimSpace;
+ $$.scaleSpace = $2.scaleSpace;
+ }
+ else if ($2.dimTime) {
+ $$.dimTime = $2.dimTime;
+ $$.scaleTime = $2.scaleTime;
+ }
+ else {
+ $$.dimCount = $2.dimCount;
+ $$.scaleCount = $2.scaleCount;
+ } }
+ | units UNIT_SLASH unit
+ { $$ = $1;
+ if ($3.dimSpace) {
+ $$.dimSpace = -$3.dimSpace;
+ $$.scaleSpace = $3.scaleSpace;
+ }
+ else if ($3.dimTime) {
+ $$.dimTime = -$3.dimTime;
+ $$.scaleTime = $3.scaleTime;
+ }
+ else {
+ $$.dimCount = -$3.dimCount;
+ $$.scaleCount = $3.scaleCount;
+ } }
+ ;
+
+unit : SPACE_UNIT
+ { $$ = $1; }
+ | SPACE_UNIT '^' NUMBER
+ { $$ = $1;
+ $$.dimSpace = $3; }
+ | TIME_UNIT
+ { $$ = $1; }
+ | TIME_UNIT '^' NUMBER
+ { $$ = $1;
+ $$.dimTime = $3; }
+ | EVENT_UNIT
+ { $$ = $1; }
+ | EVENT_UNIT '^' NUMBER
+ { $$ = $1;
+ $$.dimCount = $3; }
+ ;
+
+str : STRING
+ { $$ = strConst($1); }
+ ;
+
+%%
+
diff --git a/src/pmie/src/hdr.sk b/src/pmie/src/hdr.sk
new file mode 100644
index 0000000..98cd92b
--- /dev/null
+++ b/src/pmie/src/hdr.sk
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * fun.c
+ *
+ * These functions are generated from skeletons <file>.sk
+ * by the shell-script './meta'.
+ ***********************************************************************/
+
+#include "pmapi.h"
+#include "impl.h"
+#if defined(HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+#include "dstruct.h"
+#include "pragmatics.h"
+#include "fun.h"
+#include "show.h"
+#include "stomp.h"
+
+
diff --git a/src/pmie/src/lexicon.c b/src/pmie/src/lexicon.c
new file mode 100644
index 0000000..898205e
--- /dev/null
+++ b/src/pmie/src/lexicon.c
@@ -0,0 +1,925 @@
+/***********************************************************************
+ * lexicon.c - lexical scanner
+ *
+ * There is enough lookahead to enable use of '/' as both an arithmetic
+ * and units operator. Nested macro expansion is supported using a stack
+ * of input contexts (see definition of LexIn in file syntax.y).
+ ***********************************************************************
+ *
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <math.h>
+
+#include "dstruct.h"
+#include "syntax.h"
+#include "grammar.h"
+#include "lexicon.h"
+#include "pragmatics.h"
+
+
+/***********************************************************************
+ * type definitions
+ ***********************************************************************/
+
+/* for table of lexical items */
+typedef struct {
+ char *key;
+ int token;
+} LexEntry1;
+
+/* for another table of lexical items */
+typedef struct {
+ char *key;
+ int token;
+ int scale;
+} LexEntry2;
+
+
+/***********************************************************************
+ * constants
+ ***********************************************************************/
+
+static LexEntry1 domtab[] = {
+ {"host", HOST_DOM},
+ {"inst", INST_DOM},
+ {"sample", TIME_DOM},
+ {NULL, 0}
+};
+
+static LexEntry1 quantab[] = {
+ {"sum", SUM_AGGR},
+ {"avg", AVG_AGGR},
+ {"max", MAX_AGGR},
+ {"min", MIN_AGGR},
+ {"count", COUNT_AGGR},
+ {"all", ALL_QUANT},
+ {"some", SOME_QUANT},
+ {"%", PCNT_QUANT},
+ {NULL, 0}
+};
+
+static LexEntry1 optab[] = {
+ {"true", TRU},
+ {"false", FALS},
+ {"rate", RATE},
+ {"shell", SHELL},
+ {"alarm", ALARM},
+ {"syslog", SYSLOG},
+ {"print", PRINT},
+ {"stomp", STOMP},
+ {"rising", RISE},
+ {"falling", FALL},
+ {"match_inst", MATCH},
+ {"nomatch_inst",NOMATCH},
+ {"ruleset", RULESET},
+ {"else", ELSE},
+ {"unknown", UNKNOWN},
+ {"otherwise", OTHERWISE},
+ {NULL, 0}
+};
+
+static LexEntry2 unitab[] = {
+ {"byte", SPACE_UNIT, PM_SPACE_BYTE},
+ {"Kbyte", SPACE_UNIT, PM_SPACE_KBYTE},
+ {"Mbyte", SPACE_UNIT, PM_SPACE_MBYTE},
+ {"Gbyte", SPACE_UNIT, PM_SPACE_GBYTE},
+ {"Tbyte", SPACE_UNIT, PM_SPACE_TBYTE},
+ {"nsec", TIME_UNIT, PM_TIME_NSEC},
+ {"usec", TIME_UNIT, PM_TIME_USEC},
+ {"msec", TIME_UNIT, PM_TIME_MSEC},
+ {"sec", TIME_UNIT, PM_TIME_SEC},
+ {"min", TIME_UNIT, PM_TIME_MIN},
+ {"hour", TIME_UNIT, PM_TIME_HOUR},
+ {"count", EVENT_UNIT, 0},
+ {"Kcount", EVENT_UNIT, 3},
+ {"Mcount", EVENT_UNIT, 6},
+ {"nanosec", TIME_UNIT, PM_TIME_NSEC},
+ {"nanosecond", TIME_UNIT, PM_TIME_NSEC},
+ {"microsec", TIME_UNIT, PM_TIME_USEC},
+ {"microsecond", TIME_UNIT, PM_TIME_USEC},
+ {"millisec", TIME_UNIT, PM_TIME_MSEC},
+ {"millisecond", TIME_UNIT, PM_TIME_MSEC},
+ {"second", TIME_UNIT, PM_TIME_SEC},
+ {"minute", TIME_UNIT, PM_TIME_MIN},
+ {NULL, 0, 0}
+};
+
+
+
+/***********************************************************************
+ * variables
+ ***********************************************************************/
+
+ LexIn *lin; /* current input context */
+static char *token; /* current token buffer */
+
+
+
+/***********************************************************************
+ * local functions
+ ***********************************************************************/
+
+/* unwind input context */
+static void
+unwind(void)
+{
+ LexIn *tmp;
+
+ if (lin->name) {
+ free(lin->name);
+ if (! lin->macro)
+ fclose(lin->stream);
+ }
+ tmp = lin;
+ lin = lin->prev;
+ free(tmp);
+}
+
+
+/* next input character */
+static int
+nextc(void)
+{
+ int c = '\0';
+
+ if (lin) {
+ if (lin->lookin != lin->lookout) {
+ c = lin->look[lin->lookout++];
+ if (lin->lookout >= LEX_MAX + 2)
+ lin->lookout = 0;
+ }
+ else {
+ if (lin->macro)
+ c = *lin->macro++;
+ else {
+ c = getc(lin->stream);
+ if (c == EOF)
+ c = '\0';
+ }
+ if (c == '\0') {
+ unwind();
+ return nextc();
+ }
+ lin->cno++;
+ if (c == '\n') {
+ lin->lno++;
+ lin->cno = 0;
+ }
+ }
+#if PCP_DEBUG && PCP_DESPERATE
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "nextc() -> \'%c\'\n", c);
+#endif
+ return c;
+ }
+ return EOF;
+}
+
+
+/* new file input context */
+static int
+inFile(char *name)
+{
+ FILE *f;
+ LexIn *t;
+
+ t = (LexIn *) zalloc(sizeof(LexIn));
+
+ if (name == NULL)
+ f = stdin;
+ else {
+ if ((f = fopen(name, "r")) == NULL) {
+ fprintf(stderr, "%s: cannot open config file %s\n", pmProgname, name);
+ free(t);
+ return 0;
+ }
+ t->name = (char *) alloc(strlen(name) + 1);
+ strcpy(t->name, name);
+ }
+
+ t->stream = f;
+ t->lno = 1;
+ t->cno = 0;
+ t->prev = lin;
+ lin = t;
+ return 1;
+}
+
+#define DEREF_ERROR 0
+#define DEREF_STRING 1
+#define DEREF_BOOL 2
+#define DEREF_NUMBER 3
+/*
+ * dereference macro ... return one of the DEREF_* values
+ * for DEREF_ERROR, error is reported here
+ */
+static int
+varDeref(char *name)
+{
+ Symbol s;
+ Expr *x;
+ LexIn *t;
+
+ /* lookup macro name */
+ if ((s = symLookup(&vars, name)) == NULL) {
+ fprintf(stderr, "undefined macro name $%s\n", name);
+ return DEREF_ERROR;
+ }
+ x = symValue(s);
+
+ /* string macro */
+ if (x->sem == SEM_CHAR) {
+ t = (LexIn *) zalloc(sizeof(LexIn));
+ t->prev = lin;
+ lin = t;
+ lin->name = (char *) alloc(strlen(name) + 1);
+ strcpy(lin->name, name);
+ lin->macro = (char *) x->ring;
+ lin->lno = 1;
+ lin->cno = 0;
+ return DEREF_STRING;
+ }
+
+ /* boolean valued macro */
+ if (x->sem == SEM_BOOLEAN) {
+ yylval.x = x;
+ return DEREF_BOOL;
+ }
+
+ /* constant numeric valued macro */
+ if (x->sem == SEM_NUMCONST) {
+ /*
+ * need to copy the Expr as the one returned here may be freed
+ * later after constant folding, and we need the real macro's
+ * value to be available for use in later rules
+ */
+ yylval.x = newExpr(NOP, NULL, NULL, -1, -1, -1, 1, SEM_NUMCONST);
+ yylval.x->smpls[0].ptr = x->smpls[0].ptr;
+ yylval.x->valid = 1;
+ return DEREF_NUMBER;
+ }
+
+ /* variable numeric valued macro */
+ if (x->sem == SEM_NUMVAR) {
+ yylval.x = x;
+ return DEREF_NUMBER;
+ }
+
+ fprintf(stderr, "varDeref(%s): internal botch sem=%d?\n", name, x->sem);
+ dumpExpr(x);
+ exit(1);
+}
+
+
+/* push character into lookahead queue */
+static void
+prevc(int c)
+{
+ if (lin) {
+ lin->look[lin->lookin++] = c;
+ if (lin->lookin >= LEX_MAX + 2)
+ lin->lookin = 0;
+ }
+}
+
+
+/* push string into lookahead queue */
+static void
+prevs(char *s)
+{
+ while (*s != '\0') {
+ lin->look[lin->lookin++] = *s++;
+ if (lin->lookin >= LEX_MAX + 2)
+ lin->lookin = 0;
+ }
+}
+
+/* get IDENT after '$' ... match rules for IDENT in main scanner */
+static int
+get_ident(char *namebuf)
+{
+ int c;
+ int d = 0;
+ int i;
+ char *namebuf_start = namebuf;
+
+ c = nextc();
+ if (c == '\'') {
+ d = c;
+ c = nextc();
+ }
+ if (!isalpha(c)) {
+ fprintf(stderr, "macro name must begin with an alphabetic (not '%c')\n", c);
+ lexSync();
+ return 0;
+ }
+ i = 0;
+ do {
+ if (c == '\\') c = nextc();
+ *namebuf++ = c;
+ i++;
+ c = nextc();
+ } while (i < LEX_MAX && c != EOF &&
+ (isalpha(c) || isdigit(c) || c == '_' || (d && c != d)));
+
+ if (i == LEX_MAX) {
+ namebuf_start[20] = '\0';
+ fprintf(stderr, "macro name too long: $%s...\n", namebuf_start);
+ lexSync();
+ return 0;
+ }
+ if (c == EOF) {
+ *namebuf = '\0';
+ fprintf(stderr, "unexpected end of file in macro name: $%s\n", namebuf_start);
+ lexSync();
+ return 0;
+ }
+
+ if (!d)
+ prevc(c);
+
+ *namebuf = '\0';
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "get_ident() -> macro name \"%s\"\n", namebuf_start);
+#endif
+
+ return 1;
+}
+
+
+/***********************************************************************
+ * exported functions
+ ***********************************************************************/
+
+/* initialize scan of new input stream */
+int
+lexInit(char *fname)
+{
+ lin = NULL;
+ if (! inFile(fname))
+ return 0;
+ token = (char *) alloc(LEX_MAX + 1);
+ return 1;
+}
+
+
+/* finalize scan of input stream */
+void
+lexFinal(void)
+{
+ free(token);
+}
+
+
+/* not end of input stream? */
+int lexMore(void)
+{
+ return (lin != NULL);
+}
+
+
+/* discard input to next ';' or EOF */
+void
+lexSync(void)
+{
+ int c;
+
+ do
+ c = nextc();
+ while (c != ';' && c != EOF)
+ ;
+ prevc(c);
+}
+
+
+/* scanner main function */
+int
+yylex(void)
+{
+ int c, d; /* current character */
+ int esc = 0; /* for escape processing */
+ static int ahead = 0; /* lookahead token */
+ int behind = 0; /* lookbehind token */
+ LexEntry1 *lt1; /* scans through lexbuf1 */
+ LexEntry2 *lt2; /* scans through lexbuf2 */
+ char *p, *q;
+ int i;
+ char nbuf[LEX_MAX+1]; /* for getting macro name */
+
+ /* token from previous invocation */
+ if (ahead) {
+ c = ahead;
+ ahead = 0;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> TOKEN (ahead) \'%c\'\n", c);
+#endif
+ return c;
+ }
+
+ /* scan token from input */
+ c = nextc();
+ while (c != EOF) {
+
+ /* skip blanks */
+ while (isspace(c))
+ c = nextc();
+
+ /* scan C style comment */
+ if (c == '/') {
+ if ((d = nextc()) != '*')
+ prevc(d);
+ else {
+ c = nextc();
+ while (c != EOF) {
+ d = nextc();
+ if ((c == '*') && (d == '/')) {
+ c = nextc();
+ break;
+ }
+ c = d;
+ }
+ continue;
+ }
+ }
+
+ /* scan C++ style comment */
+ if (c == '/') {
+ if ((d = nextc()) != '/')
+ prevc(d);
+ else {
+ do c = nextc();
+ while (( c != '\n') && (c != EOF));
+ continue;
+ }
+ }
+
+ /* scan alphanumeric */
+ if (isalpha(c) || (c == '\'') || (c == '%')) {
+ d = c;
+ if (d == '\'') c = nextc();
+ i = 0;
+ do {
+ if (c == '$') {
+ /* macro embedded in identifier */
+ int sts;
+ if (!get_ident(nbuf))
+ return EOF;
+ sts = varDeref(nbuf);
+ if (sts == DEREF_ERROR) {
+ /* error reporting in varDeref() */
+ lexSync();
+ return EOF;
+ }
+ else if (sts != DEREF_STRING) {
+ synerr();
+ fprintf(stderr, "macro $%s not string valued as expected\n", nbuf);
+ lexSync();
+ return EOF;
+ }
+ c = nextc();
+ }
+ else {
+ if (c == '\\') c = nextc();
+ token[i++] = c;
+ c = nextc();
+ }
+ } while ((isalpha(c) || isdigit(c) ||
+ c == '.' || c == '_' || c == '\\' || c == '$' ||
+ (d == '\'' && c != d)) &&
+ (i < LEX_MAX));
+ token[i] = '\0';
+ if (d == '\'') c = nextc();
+
+ /*
+ * recognize keywords associated with units of space, time
+ * and count, see unitab[]
+ */
+ if (d != '\'') {
+ lt2 = &unitab[0];
+ if (i > 0 && token[i-1] == 's')
+ i--;
+ do {
+ if (strlen(lt2->key) == i &&
+ strncmp(token, lt2->key, i) == 0) {
+
+ /* if looking ahead after '/', return UNIT_SLASH */
+ if (behind == '/') {
+ prevs(&token[0]);
+ prevc(c);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"/\"\n");
+#endif
+ return UNIT_SLASH;
+ }
+ prevc(c);
+
+ yylval.u = noUnits;
+ switch (lt2->token) {
+ case SPACE_UNIT:
+ yylval.u.dimSpace = 1;
+ yylval.u.scaleSpace = lt2->scale;
+ break;
+ case TIME_UNIT:
+ yylval.u.dimTime = 1;
+ yylval.u.scaleTime = lt2->scale;
+ break;
+ case EVENT_UNIT:
+ yylval.u.dimCount = 1;
+ yylval.u.scaleCount = lt2->scale;
+ break;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> UNITS \"%s\"\n", pmUnitsStr(&yylval.u));
+#endif
+ return lt2->token;
+ }
+ lt2++;
+ } while (lt2->key);
+ }
+
+ /* if looking ahead, return previous token */
+ if (behind) {
+ prevs(&token[0]);
+ prevc(c);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> TOKEN (behind) \'%c\'\n", behind);
+#endif
+ return behind;
+ }
+ prevc(c);
+
+ /* recognize aggregation and quantification */
+ if (d != '\'') {
+ if ((p = strchr(token, '_')) != NULL) {
+ *p = '\0';
+ lt1 = &quantab[0];
+ do {
+ if (strcmp(&token[0], lt1->key) == 0) {
+ c = lt1->token;
+ q = p + 1;
+ lt1 = &domtab[0];
+ do {
+ if (strcmp(q, lt1->key) == 0) {
+ ahead = lt1->token;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"%s\'\n", token);
+#endif
+ return c;
+ }
+ lt1++;
+ } while (lt1->key);
+ break;
+ }
+ lt1++;
+ } while (lt1->key);
+ *p = '_';
+ }
+
+ /* recognize other reserved word */
+ lt1 = &optab[0];
+ do {
+ if (strcmp(&token[0], lt1->key) == 0) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> RESERVED-WORD \"%s\"\n", token);
+#endif
+ return lt1->token;
+ }
+ lt1++;
+ } while (lt1->key);
+ }
+
+ /* recognize identifier */
+ yylval.s = (char *) alloc(strlen(&token[0]) + 1);
+ strcpy(yylval.s, &token[0]);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> IDENT \"%s\"\n", token);
+#endif
+ return IDENT;
+ }
+
+ /* if looking ahead, return preceding token */
+ if (behind) {
+ prevc(c);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> TOKEN (behind) \'%c\'\n", behind);
+#endif
+ return behind;
+ }
+
+ /* special case for .[0-9]... number without leading [0-9] */
+ if (c == '.') {
+ c = nextc();
+ if (isdigit(c)) {
+ /* note prevc() implements a FIFO, not a stack! */
+ prevc('.'); /* push back period */
+ prevc(c); /* push back digit */
+ c = '0'; /* start with fake leading zero */
+ }
+ else
+ prevc(c); /* not a digit after period, push back */
+ }
+
+ /* scan NUMBER */
+ if (isdigit(c)) {
+ int flote = 0;
+ i = 0;
+ do {
+ token[i++] = c;
+ c = nextc();
+ if ((flote == 0) && (c == '.') && (i < LEX_MAX)) {
+ c = nextc();
+ if (c == '.')
+ prevc(c); /* INTERVAL token */
+ else {
+ flote = 1;
+ token[i++] = '.';
+ }
+ }
+ if ((flote <= 1) && (i < LEX_MAX) && ((c == 'e') || (c == 'E'))) {
+ flote = 2;
+ token[i++] = c;
+ c = nextc();
+ }
+ if ((flote <= 2) && (c == '-') && (i < LEX_MAX)) {
+ flote = 3;
+ token[i++] = c;
+ c = nextc();
+ }
+ } while (isdigit(c) && (i < LEX_MAX));
+ prevc(c);
+ token[i] = '\0';
+ yylval.d = strtod(&token[0], NULL);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> NUMBER %g\n", yylval.d);
+#endif
+ return NUMBER;
+ }
+
+ /* scan string */
+ if (c == '"') {
+ yylval.s = NULL;
+ i = 0;
+ c = nextc();
+ while ((c != '"') && (c != EOF) && (i < LEX_MAX)) {
+
+ /* escape character */
+ if (c == '\\') {
+ esc = 1;
+ c = nextc();
+ switch (c) {
+ case 'n':
+ c = '\n';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'v':
+ c = '\v';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'a':
+ c = '\a';
+ break;
+ }
+ }
+ else
+ esc = 0;
+
+ /* macro embedded in string */
+ if (c == '$' && !esc) {
+ int sts;
+ if (!get_ident(nbuf))
+ return EOF;
+ sts = varDeref(nbuf);
+ if (sts == DEREF_ERROR) {
+ /* error reporting in varDeref() */
+ lexSync();
+ return EOF;
+ }
+ else if (sts != DEREF_STRING) {
+ synerr();
+ fprintf(stderr, "macro $%s not string valued as expected\n", nbuf);
+ lexSync();
+ return EOF;
+ }
+ c = nextc();
+ }
+
+ /* add character to string */
+ yylval.s = (char *) ralloc(yylval.s, i+2);
+ yylval.s[i++] = c;
+ c = nextc();
+ }
+ if (i == 0) {
+ /* special case for null string */
+ yylval.s = (char *) ralloc(yylval.s, 1);
+ }
+ yylval.s[i++] = '\0';
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> STRING \"%s\"\n", yylval.s);
+#endif
+ return STRING;
+ }
+
+ /* scan operator */
+ switch (c) {
+ case ';':
+ case '}':
+ do
+ d = nextc();
+ while (isspace(d));
+ if (d == '.') {
+ while (lin)
+ unwind();
+ }
+ else
+ prevc(d);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> END-OF-RULE\n");
+#endif
+ return EOF;
+
+ case '$':
+ if (!get_ident(nbuf))
+ return EOF;
+ switch (varDeref(nbuf)) {
+ case DEREF_ERROR:
+ lexSync();
+ return EOF;
+ case DEREF_STRING:
+ c = nextc();
+ continue;
+ case DEREF_BOOL:
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> (boolean) macro $%s\n", nbuf);
+#endif
+ return VAR;
+ case DEREF_NUMBER:
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> (numeric) macro $%s\n", nbuf);
+#endif
+ return VAR;
+ }
+ /*NOTREACHED*/
+ break;
+
+ case '/':
+ behind = c;
+ c = nextc();
+ continue;
+ case '-':
+ if ((d = nextc()) == '>') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"->\"\n");
+#endif
+ return ARROW;
+ }
+ prevc(d);
+ break;
+ case '=':
+ if ((d = nextc()) == '=') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"==\"\n");
+#endif
+ return EQ_REL;
+ }
+ prevc(d);
+ break;
+ case '!':
+ if ((d = nextc()) == '=') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"!=\"\n");
+#endif
+ return NEQ_REL;
+ }
+ prevc(d);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"!\"\n");
+#endif
+ return NOT;
+ case '<':
+ if ((d = nextc()) == '=') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"<=\"\n");
+#endif
+ return LEQ_REL;
+ }
+ prevc(d);
+ break;
+ case '>':
+ if ((d = nextc()) == '=') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \">=\"\n");
+#endif
+ return GEQ_REL;
+ }
+ prevc(d);
+ break;
+ case '&':
+ if ((d = nextc()) == '&') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"&&\"\n");
+#endif
+ return AND;
+ }
+ prevc(d);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"&\"\n");
+#endif
+ return SEQ;
+ case '|':
+ if ((d = nextc()) == '|') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"||\"\n");
+#endif
+ return OR;
+ }
+ prevc(d);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"|\"\n");
+#endif
+ return ALT;
+ case '.':
+ if ((d = nextc()) == '.') {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> OPERATOR \"..\"\n");
+#endif
+ return INTERVAL;
+ }
+ prevc(d);
+ break;
+
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ if (c == EOF)
+ fprintf(stderr, "yylex() -> EOF\n");
+ else
+ fprintf(stderr, "yylex() -> TOKEN \'%c\' (0x%x)\n", c, c & 0xff);
+ }
+#endif
+ return c;
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "yylex() -> EOF\n");
+#endif
+ return EOF;
+}
+
+
+
diff --git a/src/pmie/src/lexicon.h b/src/pmie/src/lexicon.h
new file mode 100644
index 0000000..7c2c16a
--- /dev/null
+++ b/src/pmie/src/lexicon.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * lexicon.h - lexical scanner
+ ***********************************************************************/
+
+#ifndef LEXICON_H
+#define LEXICON_H
+
+/***********************************************************************
+ * ONLY FOR USE BY: lexicon.c and syntax.c
+ ***********************************************************************/
+
+#define LEX_MAX 254 /* max length of token */
+
+/* scanner input context stack entry */
+typedef struct lexin {
+ struct lexin *prev; /* calling context on stack */
+ FILE *stream; /* rule input stream */
+ char *macro; /* input from macro definition */
+ char *name; /* file/macro name */
+ int lno; /* current line number */
+ int cno; /* current column number */
+ int lookin; /* lookahead buffer input index */
+ int lookout; /* lookahead buffer output index */
+ signed char look[LEX_MAX + 2]; /* lookahead ring buffer */
+} LexIn;
+
+extern LexIn *lin; /* current input context */
+
+
+/***********************************************************************
+ * public
+ ***********************************************************************/
+
+/* initialize scan of new input file */
+int lexInit(char *);
+
+/* finalize scan of input stream */
+void lexFinal(void);
+
+/* not end of input stream? */
+int lexMore(void);
+
+/* discard input until ';' or EOF */
+void lexSync(void);
+
+/* scanner main function */
+int yylex(void);
+
+/* yacc parser */
+int yyparse(void);
+
+#endif /* LEXICON_H */
+
diff --git a/src/pmie/src/logger.h b/src/pmie/src/logger.h
new file mode 100644
index 0000000..534f4dc
--- /dev/null
+++ b/src/pmie/src/logger.h
@@ -0,0 +1,83 @@
+/*
+ * Provided by Alan Hoyt <ahoyt@moser-inc.com> as part of the Solaris port,
+ * this code came from
+ * http://www.mit.edu/afs/sipb/service/logging/arch/sun4x_55/build/zephyr/clients/syslogd/syslogd.c-test1
+ *
+ * Copyright (c) 1983, 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley. The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#define NOPRI 0x10 /* the "no priority" priority */
+#define LOG_MAKEPRI(f,p) (((f) << 3) + (p))
+#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */
+
+#ifndef LOG_PRIMASK
+#define LOG_PRIMASK 0x07
+#endif
+#ifndef LOG_FACMASK
+#define LOG_FACMASK 0x03f8
+#endif
+
+typedef struct code {
+ char *c_name;
+ int c_val;
+} CODE;
+
+struct code prioritynames[] = {
+ { "panic", LOG_EMERG },
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "error", LOG_ERR },
+ { "warning", LOG_WARNING },
+ { "notice", LOG_NOTICE },
+ { "info", LOG_INFO },
+ { "debug", LOG_DEBUG },
+ { "none", NOPRI },
+ { "emerg", LOG_EMERG },
+ { "err", LOG_ERR },
+ { "warn", LOG_WARNING },
+ { NULL, -1 }
+};
+
+struct code facilitynames[] = {
+ { "daemon", LOG_DAEMON },
+#ifndef IS_MINGW
+ { "kern", LOG_KERN },
+ { "user", LOG_USER },
+ { "mail", LOG_MAIL },
+ { "auth", LOG_AUTH },
+ { "syslog", LOG_SYSLOG },
+ { "lpr", LOG_LPR },
+ { "news", LOG_NEWS },
+ { "uucp", LOG_UUCP },
+ { "cron", LOG_CRON },
+ { "reserved", -1 },
+ { "reserved", -1 },
+ { "reserved", -1 },
+ { "cron", LOG_CRON },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { "security", LOG_AUTH },
+ { "mark", LOG_MARK },
+#endif
+ { NULL, -1 }
+};
+
diff --git a/src/pmie/src/match_inst.c b/src/pmie/src/match_inst.c
new file mode 100644
index 0000000..26a1d1c
--- /dev/null
+++ b/src/pmie/src/match_inst.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 1999 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include "pmapi.h" /* _pmCtime only */
+#include "impl.h" /* _pmCtime only */
+#include "dstruct.h"
+#include "fun.h"
+#include "show.h"
+
+/*
+ * x-arg1 is the bexp, x->arg2 is the regex
+ */
+void
+cndMatch_inst(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ Boolean *ip1;
+ Boolean *op;
+ int n;
+ int i;
+ int sts;
+ int mi;
+ Metric *m;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "match_inst(" PRINTF_P_PFX "%p): regex handle=" PRINTF_P_PFX "%p desire %s\n",
+ x, arg2->ring, x->op == CND_MATCH ? "match" : "nomatch");
+ dumpExpr(x);
+ }
+#endif
+
+ if (arg2->sem != SEM_REGEX) {
+ fprintf(stderr, "cndMatch_inst: internal botch arg2 not SEM_REGEX?\n");
+ dumpExpr(arg2);
+ exit(1);
+ }
+
+ if (arg1->tspan > 0) {
+
+ mi = 0;
+ m = &arg1->metrics[mi++];
+ i = 0;
+ ip1 = (Boolean *)(&arg1->smpls[0])->ptr;
+ op = (Boolean *)(&x->smpls[0])->ptr;
+
+ for (n = 0; n < arg1->tspan; n++) {
+
+ if (!arg2->valid || !arg1->valid) {
+ *op++ = B_UNKNOWN;
+ }
+ else if (x->e_idom <= 0) {
+ *op++ = B_FALSE;
+ }
+ else {
+ while (i >= m->m_idom) {
+ /*
+ * no more values, next metric
+ */
+ m = &arg1->metrics[mi++];
+ i = 0;
+ }
+
+ if (m->inames == NULL) {
+ *op++ = B_FALSE;
+ }
+ else {
+ sts = regexec((regex_t *)arg2->ring, m->inames[i], 0, NULL, 0);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (x->op == CND_MATCH && sts != REG_NOMATCH) {
+ fprintf(stderr, "match_inst: inst=\"%s\" match && %s\n",
+ m->inames[i],
+ *ip1 == B_TRUE ? "true" :
+ (*ip1 == B_FALSE ? "false" :
+ (*ip1 == B_UNKNOWN ? "unknown" : "bogus" )));
+
+ }
+ else if (x->op == CND_NOMATCH && sts == REG_NOMATCH) {
+ fprintf(stderr, "match_inst: inst=\"%s\" nomatch && %s\n",
+ m->inames[i],
+ *ip1 == B_TRUE ? "true" :
+ (*ip1 == B_FALSE ? "false" :
+ (*ip1 == B_UNKNOWN ? "unknown" : "bogus" )));
+ }
+ }
+#endif
+ if ((x->op == CND_MATCH && sts != REG_NOMATCH) ||
+ (x->op == CND_NOMATCH && sts == REG_NOMATCH))
+ *op++ = *ip1 && B_TRUE;
+ else
+ *op++ = *ip1 && B_FALSE;
+ }
+ i++;
+ }
+ ip1++;
+ }
+ x->valid++;
+ }
+ else
+ x->valid = 0;
+
+ x->smpls[0].stamp = arg1->smpls[0].stamp;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "cndMatch_inst(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
diff --git a/src/pmie/src/merge.sk b/src/pmie/src/merge.sk
new file mode 100644
index 0000000..ea3e814
--- /dev/null
+++ b/src/pmie/src/merge.sk
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: merge.sk
+ ***********************************************************************/
+
+/*
+ * operator: @FUN
+ */
+
+void
+@FUN_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg1->smpls[1];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip1;
+ @ITYPE *ip2;
+ @OTYPE *op;
+ RealTime delta;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid >= 2 && x->tspan > 0) {
+ ip1 = (@ITYPE *)is1->ptr;
+ ip2 = (@ITYPE *)is2->ptr;
+ op = (@OTYPE *)os->ptr;
+ n = x->tspan;
+ @DELTA
+ for (i = 0; i < n; i++) {
+ *op = *ip1++ @OP *ip2++;
+ @SCALE
+ op++;
+ }
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is1 = &arg1->smpls[0];
+ Sample *is2 = &arg1->smpls[1];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip1;
+ @ITYPE *ip2;
+ @OTYPE *op;
+ RealTime delta;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid >= 2) {
+ ip1 = (@ITYPE *)is1->ptr;
+ ip2 = (@ITYPE *)is2->ptr;
+ op = (@OTYPE *)os->ptr;
+ @DELTA
+ *op = *ip1 @OP *ip2;
+ @SCALE
+ os->stamp = is1->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+
diff --git a/src/pmie/src/meta b/src/pmie/src/meta
new file mode 100755
index 0000000..00074ef
--- /dev/null
+++ b/src/pmie/src/meta
@@ -0,0 +1,299 @@
+#!/bin/sh
+#
+# Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Generate evaluator functions from skeletons.
+#
+
+##############
+# procedures #
+##############
+
+CULLCOPYRIGHT="/^ \* Copyright.* Silicon Graphics.*$/d"
+
+_fetch()
+{
+fin=fetch.sk
+sed -e "$CULLCOPYRIGHT" $fin >> $fout
+}
+
+_misc()
+{
+fin=misc.sk
+sed -e "$CULLCOPYRIGHT" \
+ -e "s/@ITYPE/$itype/g" \
+ -e "s/@OTYPE/$otype/g" \
+ $fin >> $fout
+}
+
+_aggr()
+{
+fin=aggregate.sk
+sed -e "$CULLCOPYRIGHT" \
+ -e "s/@FUN/$fun/g" \
+ -e "s/@ITYPE/$itype/g" \
+ -e "s/@OTYPE/$otype/g" \
+ -e "s/@TTYPE/$ttype/g" \
+ -e "s/@TOP/$top/g" \
+ -e "s/@LOOP/$loop/g" \
+ -e "s/@BOT/$bot/g" \
+ -e "s/@NOTVALID/$notvalid/g" \
+ $fin >> $fout
+}
+
+_unary()
+{
+fin=unary.sk
+sed -e "$CULLCOPYRIGHT" \
+ -e "s/@FUN/$fun/g" \
+ -e "s/@ITYPE/$itype/g" \
+ -e "s/@OTYPE/$otype/g" \
+ -e "s/@OP/$op/g" \
+ $fin >> $fout
+}
+
+_binary()
+{
+fin=binary.sk
+sed -e "$CULLCOPYRIGHT" \
+ -e "s/@FUN/$fun/g" \
+ -e "s/@ITYPE/$itype/g" \
+ -e "s/@OTYPE/$otype/g" \
+ -e "s/@OP/$op/g" \
+ $fin >> $fout
+}
+
+_merge()
+{
+fin=merge.sk
+if [ -z "$scale" ]
+then
+ sed -e '/RealTime/d' $fin
+else
+ cat $fin
+fi \
+| sed -e "$CULLCOPYRIGHT" \
+ -e "s/@FUN/$fun/g" \
+ -e "s/@ITYPE/$itype/g" \
+ -e "s/@OTYPE/$otype/g" \
+ -e "s/@OP/$op/g" \
+ -e "s/@DELTA/$delta/g" \
+ -e "s/@SCALE/$scale/g" \
+ >> $fout
+}
+
+_act()
+{
+fin=act.sk
+sed -e "$CULLCOPYRIGHT" $fin >> $fout
+}
+
+
+
+########
+# main #
+########
+
+fout=fun.c
+rm -f $fout
+cat hdr.sk > $fout
+
+
+#
+# fetch
+#
+_fetch
+
+#
+# rule and delay
+#
+itype=double
+otype=double
+
+_misc
+
+#
+# aggregation operators
+#
+itype=double
+otype=double
+ttype=double
+notvalid="x->valid = 0;"
+
+fun=cndSum
+top="a = *ip;"
+loop="a += *ip;"
+bot="*op++ = a;"
+_aggr
+
+fun=cndAvg
+top="a = *ip;"
+loop="a += *ip;"
+bot="*op++ = a \/ n;"
+_aggr
+
+fun=cndMax
+top="a = *ip;"
+loop="if (*ip > a) a = *ip;"
+bot="*op++ = a;"
+_aggr
+
+fun=cndMin
+top="a = *ip;"
+loop="if (*ip < a) a = *ip;"
+bot="*op++ = a;"
+_aggr
+
+#
+# arithmetic operators
+#
+itype=double
+otype=double
+ttype=double
+
+fun=cndNeg
+op="OP(x) -(x)"
+_unary
+
+fun=cndAdd
+op="OP(x,y) ((x) + (y))"
+_binary
+
+fun=cndSub
+op="OP(x,y) ((x) - (y))"
+_binary
+
+fun=cndMul
+op="OP(x,y) ((x) * (y))"
+_binary
+
+fun=cndDiv
+op="OP(x,y) ((x) \/ (y))"
+_binary
+
+fun=cndRate
+delta="delta = is1->stamp - is2->stamp;"
+op="-"
+scale="*op = *op \\/ delta;"
+_merge
+
+#
+# relational operators
+#
+itype=double
+otype=Boolean
+ttype=Boolean
+
+fun=cndEq
+op="OP(x,y) ((x) == (y))"
+_binary
+
+fun=cndNeq
+op="OP(x,y) ((x) != (y))"
+_binary
+
+fun=cndLt
+op="OP(x,y) ((x) < (y))"
+_binary
+
+fun=cndLte
+op="OP(x,y) ((x) <= (y))"
+_binary
+
+fun=cndGt
+op="OP(x,y) ((x) > (y))"
+_binary
+
+fun=cndGte
+op="OP(x,y) ((x) >= (y))"
+_binary
+
+#
+# boolean connectives
+#
+itype=Boolean
+otype=Boolean
+ttype=Boolean
+
+fun=cndNot
+op="OP(x) (((x) == B_TRUE || (x) == B_FALSE) ? !(x) : B_UNKNOWN)"
+_unary
+
+fun=cndRise
+delta=""
+op=">"
+scale=""
+_merge
+
+fun=cndFall
+delta=""
+op="<"
+scale=""
+_merge
+
+#
+# quantifiers
+#
+itype=Boolean
+otype=Boolean
+ttype=Boolean
+
+fun=cndAll
+top="a = *ip;"
+loop="if (*ip == B_FALSE) a = B_FALSE;\\
+ else if (*ip == B_UNKNOWN \\&\\& a != B_UNKNOWN) a = B_UNKNOWN;"
+bot="*op++ = a;"
+notvalid="*op++ = B_UNKNOWN; os->stamp = is->stamp; x->valid++;"
+_aggr
+
+fun=cndSome
+top="a = *ip;"
+loop="if (*ip == B_TRUE) a = B_TRUE;\\
+ else if (*ip == B_UNKNOWN \\&\\& a != B_UNKNOWN) a = B_UNKNOWN;"
+bot="*op++ = a;"
+notvalid="*op++ = B_UNKNOWN; os->stamp = is->stamp; x->valid++;"
+_aggr
+
+fun=cndPcnt
+ttype='int '
+top="a = *ip;"
+loop="a += *ip;"
+bot="*op++ = (a >= (int)(0.5 + *(double *)x->arg2->ring * n)) ? B_TRUE : B_FALSE;"
+notvalid="*op++ = B_UNKNOWN; os->stamp = is->stamp; x->valid++;"
+_aggr
+
+#
+# truth counter
+#
+itype=Boolean
+otype=double
+notvalid="x->valid = 0;"
+
+fun=cndCount
+top="a = *ip == B_TRUE ? 1 : 0;"
+loop="if (*ip == B_TRUE) a++;"
+bot="*op++ = a;"
+_aggr
+
+#
+# actions
+#
+_act
+
+# discourage changes to fun.c
+#
+chmod 444 $fout
diff --git a/src/pmie/src/misc.sk b/src/pmie/src/misc.sk
new file mode 100644
index 0000000..30095f4
--- /dev/null
+++ b/src/pmie/src/misc.sk
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: misc.sk
+ ***********************************************************************/
+
+#include <assert.h>
+
+/*
+ * operator RULE
+ */
+
+void
+rule(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Expr *arg2 = x->arg2;
+ int sts;
+
+ EVALARG(arg1)
+ if ((x->valid = arg1->valid) > 0) {
+ sts = (*(Boolean *)x->ring = *(Boolean *)arg1->ring);
+ if (sts == B_FALSE)
+ perf->eval_false++;
+ else if (sts == B_TRUE) {
+ perf->eval_true++;
+ EVALARG(arg2)
+ }
+ else
+ perf->eval_unknown++;
+ }
+ else
+ perf->eval_unknown++;
+}
+
+/*
+ * operator CND_RULESET
+ */
+
+void
+ruleset(Expr *x)
+{
+ Expr *op; /* operator nodes */
+ Expr *rp; /* rule or action nodes */
+ Expr *save_curr; /* save-restore curr when calling rule() */
+ Expr *other; /* UNKNOWN/OTHERWISE clauses */
+
+ x->valid = 0;
+ op = x->arg1;
+ while (op != NULL) {
+ if (op->op == RULE)
+ rp = op;
+ else {
+ assert(op->op == CND_RULESET || op->op == CND_OR);
+ rp = op->arg1;
+ }
+ assert(rp->op == RULE);
+ save_curr = curr;
+ curr = rp;
+ rule(rp);
+ curr = save_curr;
+ if (rp->arg1->valid) {
+ x->valid = rp->arg1->valid;
+ *(Boolean *)x->ring = *(Boolean *)rp->arg1->ring;
+ if (x->valid > 0 && *(Boolean *)x->ring == B_TRUE) {
+ /* predicate is true, so stop evaluation */
+ return;
+ }
+ }
+ if (op->op == RULE)
+ break;
+ op = op->arg2;
+ }
+
+ if (x->arg2 == NULL)
+ /* no OTHERWISE or UNKNOWN clauses */
+ return;
+
+ other = x->arg2;
+ assert(other->op == CND_OTHER);
+
+ if (x->valid == 0) {
+ /*
+ * all predicates are B_UNKNOWN, so do the UNKNOWN action if any
+ */
+ if (other->arg1->op != NOP) {
+ rp = other->arg1;
+ save_curr = curr;
+ curr = rp;
+ rule(rp);
+ curr = save_curr;
+ return;
+ }
+ }
+
+ /*
+ * no predicate is B_TRUE and either some predicate is B_FALSE
+ * or they are all B_UNKNOWN and there is no UNKNOWN action ...
+ * so do the OTHERWISE action, if any
+ */
+ if (other->arg2->op != NOP) {
+ rp = other->arg2;
+ save_curr = curr;
+ curr = rp;
+ rule(rp);
+ save_curr = curr;
+ x->valid = rp->arg1->valid;
+ *(Boolean *)x->ring = *(Boolean *)rp->arg1->ring;
+ }
+}
+
+/*
+ * operator: cndDelay
+ */
+
+void
+cndDelay_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ int n = arg1->tdom;
+ Sample *is = &arg1->smpls[n - 1];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip;
+ @OTYPE *op;
+ int i;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid >= n && x->tspan > 0) {
+ ip = (@ITYPE *)is->ptr;
+ op = (@OTYPE *)os->ptr;
+ for (i = 0; i < x->tspan; i++)
+ *op++ = *ip++;
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+}
+
+void
+cndDelay_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ int n = arg1->tdom;
+ Sample *is = &arg1->smpls[n - 1];
+ Sample *os = &x->smpls[0];
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid >= n) {
+ *(@OTYPE *)os->ptr = *(@ITYPE *)is->ptr;
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+}
+
+
diff --git a/src/pmie/src/pmie.c b/src/pmie/src/pmie.c
new file mode 100644
index 0000000..b5150fa
--- /dev/null
+++ b/src/pmie/src/pmie.c
@@ -0,0 +1,942 @@
+/***********************************************************************
+ * pmie.c - performance inference engine
+ ***********************************************************************
+ *
+ * Copyright (c) 2013-2014 Red Hat, Inc.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * pmie debug flags:
+ * APPL0 - lexical scanning
+ * APPL1 - parse/expression tree construction
+ * APPL2 - expression execution
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+
+#include "dstruct.h"
+#include "stomp.h"
+#include "syntax.h"
+#include "pragmatics.h"
+#include "eval.h"
+#include "show.h"
+
+
+/***********************************************************************
+ * constants
+ ***********************************************************************/
+
+#define LINE_LENGTH 255 /* max length of command token */
+#define PROC_FNAMESIZE 20 /* from proc pmda - proc.h */
+
+static char *prompt = "pmie> ";
+static char *intro = "Performance Co-Pilot Inference Engine (pmie), "
+ "Version %s\n\n%s%s";
+char *clientid;
+
+static FILE *logfp;
+static char logfile[MAXPATHLEN+1];
+static char perffile[MAXPATHLEN+1]; /* /var/tmp/<pid> file name */
+static char *username;
+
+static char menu[] =
+"pmie debugger commands\n\n"
+" f [file-name] - load expressions from given file or stdin\n"
+" l [expr-name] - list named expression or all expressions\n"
+" r [interval] - run for given or default interval\n"
+" S time-spec - set start time for run\n"
+" T time-spec - set default interval for run command\n"
+" v [expr-name] - print subexpression used for %h, %i and\n"
+" %v bindings\n"
+" h or ? - print this menu of commands\n"
+" q - quit\n\n";
+
+/***********************************************************************
+ * command line usage
+ ***********************************************************************/
+
+static int
+override(int opt, pmOptions *opts)
+{
+ if (opt == 'a' || opt == 'h' || opt == 'H' || opt == 'V')
+ return 1;
+ return 0;
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_ALIGN,
+ PMOPT_ARCHIVE,
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ PMOPT_NAMESPACE,
+ PMOPT_ORIGIN,
+ PMOPT_START,
+ PMOPT_FINISH,
+ PMOPT_INTERVAL,
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Runtime options"),
+ { "check", 0, 'C', 0, "parse configuration and exit" },
+ { "config", 1, 'c', "FILE", "configuration file" },
+ { "interact", 0, 'd', 0, "interactive debugging mode" },
+ { "foreground", 0, 'f', 0, "run in the foreground, not as a daemon" },
+ { "", 0, 'H', NULL }, /* was: no DNS lookup on the default hostname */
+ { "", 1, 'j', "FILE", "stomp protocol (JMS) file" },
+ { "logfile", 1, 'l', "FILE", "send status and error messages to FILE" },
+ { "username", 1, 'U', "USER", "run as named USER in daemon mode [default pcp]" },
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "buffer", 0, 'b', 0, "one line buffered output stream, stdout on stderr" },
+ { "timestamp", 0, 'e', 0, "force timestamps to be reported with -V, -v or -W" },
+ { "", 0, 'v', 0, "verbose mode, expression values printed" },
+ { "verbose", 0, 'V', 0, "verbose mode, annotated expression values printed" },
+ { "", 0, 'W', 0, "verbose mode, satisfying expression values printed" },
+ { "secret-applet", 0, 'X', 0, "run in secret applet mode (thin client)" },
+ { "secret-agent", 0, 'x', 0, "run in secret agent mode (summary PMDA)" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_STDOUT_TZ,
+ .short_options = "a:A:bc:CdD:efHh:j:l:n:O:S:t:T:U:vVWXxzZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] [filename ...]",
+ .override = override,
+};
+
+
+/***********************************************************************
+ * interactive commands
+ ***********************************************************************/
+
+/* read command input line */
+static int
+readLine(char *bfr, int max)
+{
+ int c, i;
+
+ /* skip blanks */
+ do
+ c = getchar();
+ while (isspace(c));
+
+ /* scan till end of line */
+ i = 0;
+ while ((c != '\n') && (c != EOF) && (i < max)) {
+ bfr[i++] = c;
+ c = getchar();
+ }
+ bfr[i] = '\0';
+ return (c != EOF);
+}
+
+
+/* scan interactive command token */
+static char *
+scanCmd(char **pp)
+{
+ char *p = *pp;
+
+ /* skip blanks */
+ while (isspace((int)*p))
+ p++;
+
+ /* single char token */
+ if (isgraph((int)*p)) {
+ *pp = p + 1;
+ return p;
+ }
+
+ return NULL;
+}
+
+
+/* scan interactive command argument */
+static char *
+scanArg(char *p)
+{
+ char *q;
+
+ /* strip leading blanks */
+ while (isspace((int)*p))
+ p++;
+ if (*p == '\0')
+ return NULL;
+ q = p;
+
+ /* strip trailing blanks */
+ while (*q != '\0')
+ q++;
+ q--;
+ while (isspace((int)*q))
+ q--;
+ *(q + 1) = '\0';
+
+ /* return result */
+ return p;
+}
+
+
+/* load rules from given file or stdin */
+static void
+load(char *fname)
+{
+ Symbol s;
+ Expr *d;
+ int sts = 0;
+ int sep = __pmPathSeparator();
+ char config[MAXPATHLEN+1];
+
+ /* search for configfile on configuration file path */
+ if (fname && access(fname, F_OK) != 0) {
+ sts = oserror(); /* always report the first error */
+ if (__pmAbsolutePath(fname)) {
+ fprintf(stderr, "%s: cannot access config file %s: %s\n", pmProgname,
+ fname, strerror(sts));
+ exit(1);
+ }
+#if PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "load: cannot access config file %s: %s\n", fname, strerror(sts));
+ }
+#endif
+ snprintf(config, sizeof(config)-1, "%s%c" "pmie" "%c%s",
+ pmGetConfig("PCP_SYSCONF_DIR"), sep, sep, fname);
+ if (access(config, F_OK) != 0) {
+ fprintf(stderr, "%s: cannot access config file as either %s or %s: %s\n",
+ pmProgname, fname, config, strerror(sts));
+ exit(1);
+ }
+#if PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "load: using standard config file %s\n", config);
+ }
+#endif
+ fname = config;
+ }
+#if PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "load: using config file %s\n",
+ fname == NULL? "<stdin>":fname);
+ }
+#endif
+
+ if (perf->config[0] == '\0') { /* keep record of first config */
+ if (fname == NULL)
+ strcpy(perf->config, "<stdin>");
+ else if (realpath(fname, perf->config) == NULL) {
+ fprintf(stderr, "%s: failed to resolve realpath for %s: %s\n",
+ pmProgname, fname, osstrerror());
+ exit(1);
+ }
+ }
+
+ if (synInit(fname)) {
+ while ((s = syntax()) != NULL) {
+ d = (Expr *) symValue(symDelta);
+ pragmatics(s, *(RealTime *)d->smpls[0].ptr);
+ }
+ }
+}
+
+
+/* list given expression or all expressions */
+static void
+list(char *name)
+{
+ Task *t;
+ Symbol *r;
+ Symbol s;
+ int i;
+
+ if (name) { /* single named rule */
+ if ( (s = symLookup(&rules, name)) )
+ showSyntax(stdout, s);
+ else
+ printf("%s: error - rule \"%s\" not defined\n", pmProgname, name);
+ }
+ else { /* all rules */
+ t = taskq;
+ while (t) {
+ r = t->rules;
+ for (i = 0; i < t->nrules; i++) {
+ showSyntax(stdout, *r);
+ r++;
+ }
+ t = t->next;
+ }
+ }
+}
+
+
+/* list binding subexpression of given expression or all expressions */
+static void
+sublist(char *name)
+{
+ Task *t;
+ Symbol *r;
+ Symbol s;
+ int i;
+
+ if (name) { /* single named rule */
+ if ( (s = symLookup(&rules, name)) )
+ showSubsyntax(stdout, s);
+ else
+ printf("%s: error - rule '%s' not defined\n", pmProgname, name);
+ }
+ else { /* all rules */
+ t = taskq;
+ while (t) {
+ r = t->rules;
+ for (i = 0; i < t->nrules; i++) {
+ showSubsyntax(stdout, *r);
+ r++;
+ }
+ t = t->next;
+ }
+ }
+}
+
+
+/***********************************************************************
+ * manipulate the performance instrumentation data structure
+ ***********************************************************************/
+
+static void
+stopmonitor(void)
+{
+ if (*perffile)
+ unlink(perffile);
+}
+
+static void
+startmonitor(void)
+{
+ void *ptr;
+ char *path;
+ int fd;
+ char zero = '\0';
+ char pmie_dir[MAXPATHLEN];
+
+ /* try to create the port file directory. OK if it already exists */
+ snprintf(pmie_dir, sizeof(pmie_dir), "%s%c%s",
+ pmGetConfig("PCP_TMP_DIR"), __pmPathSeparator(), PMIE_SUBDIR);
+ if (mkdir2(pmie_dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
+ if (oserror() != EEXIST) {
+ fprintf(stderr, "%s: warning cannot create stats file dir %s: %s\n",
+ pmProgname, pmie_dir, osstrerror());
+ }
+ }
+ atexit(stopmonitor);
+
+ /* create and initialize memory mapped performance data file */
+ sprintf(perffile, "%s%c%" FMT_PID, pmie_dir, __pmPathSeparator(), getpid());
+ unlink(perffile);
+ if ((fd = open(perffile, O_RDWR | O_CREAT | O_EXCL | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) {
+ /* cannot create stats file; too bad, so sad, continue on without it */
+ perf = &instrument;
+ return;
+ }
+ /* seek to struct size and write one zero */
+ lseek(fd, sizeof(pmiestats_t)-1, SEEK_SET);
+ if (write(fd, &zero, 1) != 1) {
+ fprintf(stderr, "%s: Warning: write failed for stats file %s: %s\n",
+ pmProgname, perffile, osstrerror());
+ }
+
+ /* map perffile & associate the instrumentation struct with it */
+ if ((ptr = __pmMemoryMap(fd, sizeof(pmiestats_t), 1)) == NULL) {
+ fprintf(stderr, "%s: memory map failed for stats file %s: %s\n",
+ pmProgname, perffile, osstrerror());
+ perf = &instrument;
+ } else {
+ perf = (pmiestats_t *)ptr;
+ }
+ close(fd);
+
+ path = (logfile[0] == '\0') ? "<none>" : logfile;
+ strncpy(perf->logfile, path, sizeof(perf->logfile));
+ perf->logfile[sizeof(perf->logfile)-1] = '\0';
+ strncpy(perf->defaultfqdn, dfltHostName, sizeof(perf->defaultfqdn));
+ perf->defaultfqdn[sizeof(perf->defaultfqdn)-1] = '\0';
+ perf->version = 1;
+}
+
+
+/***********************************************************************
+ * signal handling
+ ***********************************************************************/
+
+static void
+sigintproc(int sig)
+{
+ __pmSetSignalHandler(SIGINT, SIG_IGN);
+ __pmSetSignalHandler(SIGTERM, SIG_IGN);
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ __pmNotifyErr(LOG_INFO, "%s caught SIGINT or SIGTERM\n", pmProgname);
+ doexit = sig;
+}
+
+static void
+remap_stdout_stderr(void)
+{
+ int i, j;
+
+ fflush(stderr);
+ fflush(stdout);
+ setlinebuf(stderr);
+ setlinebuf(stdout);
+ i = fileno(stdout);
+ close(i);
+ if ((j = dup(fileno(stderr))) != i)
+ fprintf(stderr, "%s: Warning: failed to link stdout ... "
+ "dup() returns %d, expected %d (stderr=%d)\n",
+ pmProgname, j, i, fileno(stderr));
+}
+
+void
+logRotate(void)
+{
+ FILE *fp;
+ int sts;
+
+ fp = __pmRotateLog(pmProgname, logfile, logfp, &sts);
+ if (sts != 0) {
+ fprintf(stderr, "pmie: PID = %" FMT_PID ", default host = %s via %s\n\n",
+ getpid(), dfltHostName, dfltHostConn);
+ remap_stdout_stderr();
+ logfp = fp;
+ } else {
+ __pmNotifyErr(LOG_ERR, "pmie: log rotation failed\n");
+ }
+}
+
+static void
+sighupproc(int sig)
+{
+ __pmSetSignalHandler(SIGHUP, sighupproc);
+ dorotate = 1;
+}
+
+static void
+sigbadproc(int sig)
+{
+ if (pmDebug & DBG_TRACE_DESPERATE) {
+ __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig);
+ fprintf(stderr, "\nProcedure call traceback ...\n");
+ __pmDumpStack(stderr);
+ fflush(stderr);
+ }
+ stopmonitor();
+ _exit(sig);
+}
+
+
+/***********************************************************************
+ * command line processing - extract command line arguments & initialize
+ ***********************************************************************/
+
+static void
+getargs(int argc, char *argv[])
+{
+ char *configfile = NULL;
+ char *commandlog = NULL;
+ char *subopts;
+ char *subopt;
+ char *msg;
+ int checkFlag = 0;
+ int foreground = 0;
+ int sts;
+ int c;
+ int bflag = 0;
+ int dfltConn = 0; /* default context type */
+ Archive *a;
+ struct timeval tv, tv1, tv2;
+
+ extern int showTimeFlag;
+ extern int errs; /* syntax errors from syntax.c */
+
+ memset(&tv, 0, sizeof(tv));
+ memset(&tv1, 0, sizeof(tv1));
+ memset(&tv2, 0, sizeof(tv2));
+ dstructInit();
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'a': /* archives */
+ if (dfltConn && dfltConn != PM_CONTEXT_ARCHIVE) {
+ /* (technically, multiple -a's are allowed.) */
+ pmprintf("%s: at most one of -a or -h allowed\n", pmProgname);
+ opts.errors++;
+ break;
+ }
+ dfltConn = opts.context = PM_CONTEXT_ARCHIVE;
+ subopts = opts.optarg;
+ for ( ; ; ) {
+ subopt = subopts;
+ subopts = strchr(subopts, ',');
+ if (subopts != NULL) {
+ *subopts++ = '\0';
+ }
+ a = (Archive *)zalloc(sizeof(Archive));
+ a->fname = subopt;
+ if (!initArchive(a)) {
+ exit(1);
+ }
+ if (subopts == NULL) break;
+ }
+ foreground = 1;
+ break;
+
+ case 'b': /* line buffered, stdout on stderr */
+ bflag++;
+ break;
+
+ case 'c': /* configuration file */
+ if (interactive) {
+ pmprintf("%s: at most one of -c and -d allowed\n", pmProgname);
+ opts.errors++;
+ break;
+ }
+ configfile = opts.optarg;
+ break;
+
+ case 'C': /* check config and exit */
+ checkFlag = 1;
+ break;
+
+ case 'd': /* interactive mode */
+ if (configfile) {
+ pmprintf("%s: at most one of -c and -d allowed\n", pmProgname);
+ opts.errors++;
+ break;
+ }
+ interactive = 1;
+ break;
+
+ case 'e': /* force timestamps */
+ showTimeFlag = 1;
+ break;
+
+ case 'f': /* in foreground, not as daemon */
+ foreground = 1;
+ break;
+
+ case 'H': /* deprecated: no DNS lookups */
+ break;
+
+ case 'h': /* default host name */
+ if (dfltConn) {
+ pmprintf("%s: at most one of -a or -h allowed\n", pmProgname);
+ opts.errors++;
+ break;
+ }
+ dfltConn = opts.context = PM_CONTEXT_HOST;
+ dfltHostConn = opts.optarg;
+ dfltHostName = ""; /* unknown until newContext */
+ break;
+
+ case 'j': /* stomp protocol (JMS) config */
+ stompfile = opts.optarg;
+ break;
+
+ case 'l': /* alternate log file */
+ if (commandlog != NULL) {
+ pmprintf("%s: at most one -l option is allowed\n", pmProgname);
+ opts.errors++;
+ break;
+ }
+ commandlog = opts.optarg;
+ isdaemon = 1;
+ break;
+
+ case 'U': /* run as named user */
+ username = opts.optarg;
+ isdaemon = 1;
+ break;
+
+ case 'v': /* print values */
+ verbose = 1;
+ break;
+
+ case 'V': /* print annotated values */
+ verbose = 2;
+ break;
+
+ case 'W': /* print satisfying values */
+ verbose = 3;
+ break;
+
+ case 'X': /* secret applet flag */
+ applet = 1;
+ verbose = 1;
+ setlinebuf(stdout);
+ break;
+
+ case 'x': /* summary PMDA flag */
+ agent = 1;
+ verbose = 1;
+ isdaemon = 1;
+ break;
+ }
+ }
+
+ if (!opts.errors && configfile && opts.optind != argc) {
+ pmprintf("%s: extra filenames cannot be given after using -c\n",
+ pmProgname);
+ opts.errors++;
+ }
+ if (!opts.errors && bflag && agent) {
+ pmprintf("%s: the -b and -x options are incompatible\n",
+ pmProgname);
+ opts.errors++;
+ }
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (foreground)
+ isdaemon = 0;
+
+ hostZone = opts.tzflag;
+ timeZone = opts.timezone;
+ if (opts.interval.tv_sec || opts.interval.tv_usec)
+ dfltDelta = realize(opts.interval);
+
+ if (archives || interactive)
+ perf = &instrument;
+
+ if (isdaemon) { /* daemon mode */
+ /* done before opening log to get permissions right */
+ __pmSetProcessIdentity(username);
+
+#if defined(HAVE_TERMIO_SIGNALS)
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+#endif
+ __pmSetSignalHandler(SIGINT, sigintproc);
+ __pmSetSignalHandler(SIGTERM, sigintproc);
+ __pmSetSignalHandler(SIGBUS, sigbadproc);
+ __pmSetSignalHandler(SIGSEGV, sigbadproc);
+ }
+ else {
+ /* need to catch these so the atexit() processing is done */
+ __pmSetSignalHandler(SIGINT, sigintproc);
+ __pmSetSignalHandler(SIGTERM, sigintproc);
+ }
+
+ if (commandlog != NULL) {
+ logfp = __pmOpenLog(pmProgname, commandlog, stderr, &sts);
+ if (realpath(commandlog, logfile) == NULL) {
+ fprintf(stderr, "%s: cannot find realpath for log %s: %s\n",
+ pmProgname, commandlog, osstrerror());
+ exit(1);
+ }
+ __pmSetSignalHandler(SIGHUP, (isdaemon && !agent) ? sighupproc : SIG_IGN);
+ } else {
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ }
+
+ /*
+ * -b ... force line buffering and stdout onto stderr
+ */
+ if ((bflag || isdaemon) && !agent)
+ remap_stdout_stderr();
+
+ /* default host from leftmost archive on command line, or from
+ discovery after a brief connection */
+ if (archives) {
+ a = archives;
+ while (a->next)
+ a = a->next;
+ dfltHostName = a->hname; /* already filled in during initArchive() */
+ } else if (!dfltConn || dfltConn == PM_CONTEXT_HOST) {
+ if (dfltConn == 0) /* default case, no -a or -h */
+ dfltHostConn = "local:";
+ sts = pmNewContext(PM_CONTEXT_HOST, dfltHostConn);
+ /* pmcd down locally, try to extract hostname manually */
+ if (sts < 0 && (!dfltConn ||
+ !strcmp(dfltHostConn, "localhost") ||
+ !strcmp(dfltHostConn, "local:") ||
+ !strcmp(dfltHostConn, "unix:")))
+ sts = pmNewContext(PM_CONTEXT_LOCAL, NULL);
+ if (sts < 0) {
+ fprintf(stderr, "%s: cannot find host name for %s\n"
+ "pmNewContext failed: %s\n",
+ pmProgname, dfltHostConn, pmErrStr(sts));
+ exit(1);
+ } else {
+ const char *tmp = pmGetContextHostName(sts);
+
+ if (strlen(tmp) == 0) {
+ fprintf(stderr, "%s: pmGetContextHostName(%d) failed\n",
+ pmProgname, sts);
+ exit(1);
+ }
+ if ((dfltHostName = strdup(tmp)) == NULL)
+ __pmNoMem("host name copy", strlen(tmp)+1, PM_FATAL_ERR);
+ pmDestroyContext(sts);
+ }
+ }
+
+ if (!archives && !interactive) {
+ if (commandlog != NULL)
+ fprintf(stderr, "pmie: PID = %" FMT_PID ", default host = %s via %s\n\n",
+ getpid(), dfltHostName, dfltHostConn);
+ startmonitor();
+ }
+
+ /* initialize time */
+ now = archives ? first : getReal() + 1.0;
+ zoneInit();
+ reflectTime(dfltDelta);
+
+ /* parse time window - just to check argument syntax */
+ unrealize(now, &tv1);
+ if (archives) {
+ unrealize(last, &tv2);
+ } else {
+ tv2.tv_sec = INT_MAX; /* sizeof(time_t) == sizeof(int) */
+ tv2.tv_usec = 0;
+ }
+ if (pmParseTimeWindow(opts.start_optarg, opts.finish_optarg,
+ opts.align_optarg, opts.origin_optarg,
+ &tv1, &tv2,
+ &tv, &tv2, &tv1,
+ &msg) < 0) {
+ fputs(msg, stderr);
+ exit(1);
+ }
+ start = realize(tv1);
+ stop = realize(tv2);
+ runTime = stop - start;
+
+ /* when not in secret agent mode, register client id with pmcd */
+ if (!agent)
+ clientid = __pmGetClientId(argc, argv);
+
+ if (!interactive && opts.optind == argc) { /* stdin or config file */
+ load(configfile);
+ }
+ else { /* list of 1/more filenames */
+ while (opts.optind < argc) {
+ load(argv[opts.optind]);
+ opts.optind++;
+ }
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ dumpRules();
+#endif
+
+ if (checkFlag)
+ exit(errs == 0 ? 0 : 1); /* exit 1 for syntax errors ...
+ * suggestion from
+ * Kevin Wang <kjw@rightsock.com>
+ */
+
+ if (isdaemon) { /* daemon mode */
+ /* Note: we can no longer unilaterally close stdin here, as it
+ * can really confuse remap_stdout_stderr() during log rotation!
+ */
+ if (agent)
+ close(fileno(stdin));
+#ifndef IS_MINGW
+ setsid(); /* not process group leader, lose controlling tty */
+#endif
+ }
+
+ if (stomping)
+ stompInit(); /* connect to our message server */
+
+ if (agent)
+ agentInit(); /* initialize secret agent stuff */
+
+ /* really parse time window */
+ if (!archives) {
+ now = getReal() + 1.0;
+ reflectTime(dfltDelta);
+ }
+ unrealize(now, &tv1);
+ if (archives) {
+ unrealize(last, &tv2);
+ } else {
+ tv2.tv_sec = INT_MAX;
+ tv2.tv_usec = 0;
+ }
+ if (pmParseTimeWindow(opts.start_optarg, opts.finish_optarg,
+ opts.align_optarg, opts.origin_optarg,
+ &tv1, &tv2,
+ &tv, &tv2, &tv1,
+ &msg) < 0) {
+ fputs(msg, stderr);
+ exit(1);
+ }
+
+ /* set run timing window */
+ start = realize(tv1);
+ stop = realize(tv2);
+ runTime = stop - start;
+}
+
+/***********************************************************************
+ * interactive (debugging) mode
+ ***********************************************************************/
+
+static void
+interact(void)
+{
+ int quit = 0;
+ char *line = (char *)zalloc(LINE_LENGTH + 2);
+ char *finger;
+ char *token;
+ char *msg;
+ RealTime rt;
+ struct timeval tv1, tv2;
+
+ printf(intro, PCP_VERSION, menu, prompt);
+ fflush(stdout);
+ while (!quit && readLine(line, LINE_LENGTH)) {
+ finger = line;
+
+ if ( (token = scanCmd(&finger)) ) {
+ switch (*token) {
+
+ case 'f':
+ token = scanArg(finger);
+ load(token);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ dumpRules();
+#endif
+ break;
+
+ case 'l':
+ token = scanArg(finger);
+ list(token);
+ break;
+
+ case 'r':
+ token = scanArg(finger);
+ if (token) {
+ if (pmParseInterval(token, &tv1, &msg) == 0)
+ runTime = realize(tv1);
+ else {
+ fputs(msg, stderr);
+ free(msg);
+ break;
+ }
+ }
+ if (!archives) {
+ invalidate();
+ rt = getReal();
+ if (now < rt)
+ now = rt;
+ start = now;
+ }
+ stop = start + runTime;
+ run();
+ break;
+
+ case 'S':
+ token = scanArg(finger);
+ if (token == NULL) {
+ fprintf(stderr, "%s: error - argument required\n", pmProgname);
+ break;
+ }
+ unrealize(start, &tv1);
+ if (archives) {
+ unrealize(last, &tv2);
+ } else {
+ tv2.tv_sec = INT_MAX;
+ tv2.tv_usec = 0;
+ }
+ if (__pmParseTime(token, &tv1, &tv2, &tv1, &msg) < 0) {
+ fputs(msg, stderr);
+ free(msg);
+ break;
+ }
+ start = realize(tv1);
+ if (archives)
+ invalidate();
+ break;
+
+ case 'T':
+ token = scanArg(finger);
+ if (token == NULL) {
+ fprintf(stderr, "%s: error - argument required\n", pmProgname);
+ break;
+ }
+ if (pmParseInterval(token, &tv1, &msg) < 0) {
+ fputs(msg, stderr);
+ free(msg);
+ break;
+ }
+ runTime = realize(tv1);
+ break;
+ case 'q':
+ quit = 1;
+ break;
+
+ case 'v':
+ token = scanArg(finger);
+ sublist(token);
+ break;
+
+ case '?':
+ default:
+ printf("%s", menu);
+ }
+ }
+ if (!quit) {
+ printf("%s", prompt);
+ fflush(stdout);
+ }
+ }
+ free(line);
+}
+
+
+/***********************************************************************
+ * main
+ ***********************************************************************/
+
+int
+main(int argc, char **argv)
+{
+ __pmGetUsername(&username);
+ setlinebuf(stdout);
+
+ /* PCP_COUNTER_WRAP in environment enables "counter wrap" logic */
+ if (getenv("PCP_COUNTER_WRAP") != NULL)
+ dowrap = 1;
+
+ getargs(argc, argv);
+
+ if (interactive)
+ interact();
+ else
+ run();
+ exit(0);
+}
diff --git a/src/pmie/src/pragmatics.c b/src/pmie/src/pragmatics.c
new file mode 100644
index 0000000..23e3d1e
--- /dev/null
+++ b/src/pmie/src/pragmatics.c
@@ -0,0 +1,1226 @@
+/*
+ * pragmatics.c - inference engine pragmatics analysis
+ *
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2013-2014 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * The analysis of how to organize the fetching of metrics (pragmatics),
+ * and any other parts of the inference engine sensitive to details of
+ * the PMAPI access are kept in this source file.
+ */
+
+#include <math.h>
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "dstruct.h"
+#include "eval.h"
+#include "pragmatics.h"
+#if defined(HAVE_IEEEFP_H)
+#include <ieeefp.h>
+#endif
+
+extern char *clientid;
+
+/* for initialization of pmUnits struct */
+pmUnits noUnits;
+pmUnits countUnits = { .dimCount = 1 };
+
+char *
+findsource(char *host)
+{
+ static char buf[MAXPATHLEN+MAXHOSTNAMELEN+30];
+
+ if (archives) {
+ Archive *a = archives;
+ while (a) { /* find archive for host */
+ if (strcmp(host, a->hname) == 0)
+ break;
+ a = a->next;
+ }
+ if (a)
+ snprintf(buf, sizeof(buf), "archive %s (host %s)", a->fname, host);
+ else
+ snprintf(buf, sizeof(buf), "host %s in unknown archive!", host);
+ }
+ else
+ snprintf(buf, sizeof(buf), "host %s", host);
+
+ return buf;
+}
+
+/***********************************************************************
+ * PMAPI context creation & destruction
+ ***********************************************************************/
+
+int /* > 0: context handle, -1: retry later */
+newContext(char *host)
+{
+ Archive *a;
+ int sts = -1;
+
+ if (archives) {
+ a = archives;
+ while (a) { /* find archive for host */
+ if (strcmp(host, a->hname) == 0)
+ break;
+ a = a->next;
+ }
+ if (a) { /* archive found */
+ if ((sts = pmNewContext(PM_CONTEXT_ARCHIVE, a->fname)) < 0) {
+ fprintf(stderr, "%s: cannot open archive %s\n",
+ pmProgname, a->fname);
+ fprintf(stderr, "pmNewContext: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ }
+ else { /* no archive for host */
+ fprintf(stderr, "%s: no archive for host %s\n", pmProgname, host);
+ exit(1);
+ }
+ }
+ else if ((sts = pmNewContext(PM_CONTEXT_HOST, host)) < 0) {
+ if (host_state_changed(host, STATE_FAILINIT) == 1) {
+ if (sts == -ECONNREFUSED)
+ fprintf(stderr, "%s: warning - pmcd "
+ "on host %s does not respond\n",
+ pmProgname, host);
+ else if (sts == PM_ERR_PERMISSION)
+ fprintf(stderr, "%s: warning - host %s does not "
+ "permit delivery of metrics to the local host\n",
+ pmProgname, host);
+ else if (sts == PM_ERR_CONNLIMIT)
+ fprintf(stderr, "%s: warning - pmcd "
+ "on host %s has exceeded its connection limit\n",
+ pmProgname, host);
+ else
+ fprintf(stderr, "%s: warning - host %s is unreachable\n",
+ pmProgname, host);
+ }
+ sts = -1;
+ }
+ else if (clientid != NULL)
+ /* register client id with pmcd */
+ __pmSetClientId(clientid);
+ return sts;
+}
+
+
+/***********************************************************************
+ * instance profile
+ ***********************************************************************/
+
+/* equality (not symmetric) of instance names */
+static int
+eqinst(char *i1, char *i2)
+{
+ int n1 = (int) strlen(i1);
+ int n2 = (int) strlen(i2);
+
+ /* test equality of first word */
+ if ((strncmp(i1, i2, n1) == 0) && ((n1 == n2) || isspace((int)i2[n1])))
+ return 1;
+
+ do { /* skip over first word */
+ i2++;
+ n2--;
+ if (n2 < n1)
+ return 0;
+ } while (! isspace((int)*i2));
+
+ do { /* skip over spaces */
+ i2++;
+ n2--;
+ if (n2 < n1)
+ return 0;
+ } while (isspace((int)*i2));
+
+ /* test equality of second word */
+ if ((strncmp(i1, i2, n1) == 0) && ((n1 == n2) || isspace((int)i2[n1])))
+ return 1;
+ return 0;
+}
+
+
+/***********************************************************************
+ * task queue
+ ***********************************************************************/
+
+/* find Task for new rule */
+static Task *
+findTask(RealTime delta)
+{
+ Task *t, *u;
+ int n = 0;
+
+ t = taskq;
+ if (t) {
+ while (t->next) { /* find last task in queue */
+ t = t->next;
+ n++;
+ }
+
+ /* last task in queue has same delta */
+ if (t->delta == delta)
+ return t;
+ }
+
+ u = newTask(delta, n); /* create new Task */
+ if (t) {
+ t->next = u;
+ u->prev = t;
+ }
+ else
+ taskq = u;
+ return u;
+}
+
+
+/***********************************************************************
+ * wait list
+ ***********************************************************************/
+
+/* put Metric onto wait list */
+void
+waitMetric(Metric *m)
+{
+ Host *h = m->host;
+
+ m->next = h->waits;
+ m->prev = NULL;
+ if (h->waits) h->waits->prev = m;
+ h->waits = m;
+}
+
+/* remove Metric from wait list */
+void
+unwaitMetric(Metric *m)
+{
+ if (m->prev) m->prev->next = m->next;
+ else m->host->waits = m->next;
+ if (m->next) m->next->prev = m->prev;
+}
+
+
+/***********************************************************************
+ * fetch list
+ ***********************************************************************/
+
+/* find Host for Metric */
+static Host *
+findHost(Task *t, Metric *m)
+{
+ Host *h;
+
+ h = t->hosts;
+ while (h) { /* look for existing host */
+ if (h->name == m->hname)
+ return h;
+ h = h->next;
+ }
+
+ h = newHost(t, m->hname); /* add new host */
+ if (t->hosts) {
+ h->next = t->hosts;
+ t->hosts->prev = h;
+ }
+ t->hosts = h;
+ return h;
+}
+
+/* helper function for Extended Time Base */
+static void
+getDoubleAsXTB(double *realtime, int *ival, int *mode)
+{
+#define SECS_IN_24_DAYS 2073600.0
+
+ if (*realtime > SECS_IN_24_DAYS) {
+ *ival = (int)*realtime;
+ *mode = (*mode & 0x0000ffff) | PM_XTB_SET(PM_TIME_SEC);
+ }
+ else {
+ *ival = (int)(*realtime * 1000.0);
+ *mode = (*mode & 0x0000ffff) | PM_XTB_SET(PM_TIME_MSEC);
+ }
+}
+
+
+/* find Fetch bundle for Metric */
+static Fetch *
+findFetch(Host *h, Metric *m)
+{
+ Fetch *f;
+ int sts;
+ int i;
+ int n;
+ pmID pmid = m->desc.pmid;
+ pmID *p;
+ struct timeval tv;
+
+ /* find existing Fetch bundle */
+ f = h->fetches;
+
+ /* create new Fetch bundle */
+ if (! f) {
+ f = newFetch(h);
+ if ((f->handle = newContext(symName(h->name))) < 0) {
+ free(f);
+ h->down = 1;
+ return NULL;
+ }
+ if (archives) {
+ int tmp_ival;
+ int tmp_mode = PM_MODE_INTERP;
+ getDoubleAsXTB(&h->task->delta, &tmp_ival, &tmp_mode);
+
+ tv.tv_sec = (time_t)start;
+ tv.tv_usec = (int)((start - tv.tv_sec) * 1000000.0);
+ if ((sts = pmSetMode(tmp_mode, &tv, tmp_ival)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n", pmProgname,
+ pmErrStr(sts));
+ exit(1);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "findFetch: fetch=0x%p host=0x%p delta=%.6f handle=%d\n", f, h, h->task->delta, f->handle);
+ }
+#endif
+ }
+ f->next = NULL;
+ f->prev = NULL;
+ h->fetches = f;
+ }
+
+ /* look for existing pmid */
+ p = f->pmids;
+ n = f->npmids;
+ for (i = 0; i < n; i++) {
+ if (*p == pmid) break;
+ p++;
+ }
+
+ /* add new pmid */
+ if (i == n) {
+ p = f->pmids;
+ p = ralloc(p, (n+1) * sizeof(pmID));
+ p[n] = pmid;
+ f->npmids = n + 1;
+ f->pmids = p;
+ }
+
+ return f;
+}
+
+
+/* find Profile for Metric */
+static Profile *
+findProfile(Fetch *f, Metric *m)
+{
+ Profile *p;
+ int sts;
+
+ /* find existing Profile */
+ p = f->profiles;
+ while (p) {
+ if (p->indom == m->desc.indom) {
+ m->next = p->metrics;
+ if (p->metrics) p->metrics->prev = m;
+ p->metrics = m;
+ break;
+ }
+ p = p->next;
+ }
+
+ /* create new Profile */
+ if (p == NULL) {
+ m->next = NULL;
+ p = newProfile(f, m->desc.indom);
+ p->next = f->profiles;
+ if (f->profiles) f->profiles->prev = p;
+ f->profiles = p;
+ p->metrics = m;
+ }
+
+ /* add instances required by Metric to Profile */
+ if ((sts = pmUseContext(f->handle)) < 0) {
+ fprintf(stderr, "%s: pmUseContext failed: %s\n", pmProgname,
+ pmErrStr(sts));
+ exit(1);
+ }
+
+ /*
+ * If any rule requires all instances, then ignore restricted
+ * instance lists from all other rules
+ */
+ if (m->specinst == 0 && p->need_all == 0) {
+ sts = pmDelProfile(p->indom, 0, (int *)0);
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmDelProfile failed: %s\n", pmProgname,
+ pmErrStr(sts));
+ exit(1);
+ }
+ sts = pmAddProfile(p->indom, 0, (int *)0);
+ p->need_all = 1;
+ }
+ else if (m->specinst > 0 && p->need_all == 0)
+ sts = pmAddProfile(p->indom, m->m_idom, m->iids);
+ else
+ sts = 0;
+
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmAddProfile failed: %s\n", pmProgname,
+ pmErrStr(sts));
+ exit(1);
+ }
+
+ m->profile = p;
+ return p;
+}
+
+
+/* organize fetch bundling for given expression */
+static void
+bundle(Task *t, Expr *x)
+{
+ Metric *m;
+ Host *h;
+ int i;
+
+ if (x->op == CND_FETCH) {
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+ h = findHost(t, m);
+ m->host = h;
+ if (m->conv) /* initialized Metric */
+ bundleMetric(h, m);
+ else /* uninitialized Metric */
+ waitMetric(m);
+ m++;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "bundle: task " PRINTF_P_PFX "%p nth=%d prev=" PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p delta=%.3f nrules=%d\n",
+ t, t->nth, t->prev, t->next, t->delta, t->nrules+1);
+ __dumpExpr(1, x);
+ m = x->metrics;
+ for (i = 0; i < x->hdom; i++) {
+ __dumpMetric(2, m);
+ m++;
+ }
+ }
+#endif
+ }
+ else {
+ if (x->arg1) {
+ bundle(t, x->arg1);
+ if (x->arg2)
+ bundle(t, x->arg2);
+ }
+ }
+}
+
+
+/***********************************************************************
+ * secret agent mode support
+ ***********************************************************************/
+
+/* send pmDescriptor for the given Expr as a binary PDU */
+static void
+sendDesc(Expr *x, pmValueSet *vset)
+{
+ pmDesc d;
+
+ d.pmid = vset->pmid;
+ d.indom = PM_INDOM_NULL;
+ switch (x->sem) {
+ case PM_SEM_COUNTER:
+ case PM_SEM_INSTANT:
+ case PM_SEM_DISCRETE:
+ /* these map directly to PMAPI semantics */
+ d.type = PM_TYPE_DOUBLE;
+ d.sem = x->sem;
+ d.units = x->units;
+ break;
+
+ case SEM_NUMVAR:
+ case SEM_NUMCONST:
+ case SEM_BOOLEAN:
+ /* map to a numeric value */
+ d.type = PM_TYPE_DOUBLE;
+ d.sem = PM_SEM_INSTANT;
+ d.units = x->units;
+ break;
+
+ default:
+ fprintf(stderr, "sendDesc(%s): botch sem=%d?\n", pmIDStr(d.pmid), x->sem);
+ /* FALLTHROUGH */
+ case SEM_UNKNOWN:
+ case SEM_CHAR:
+ case SEM_REGEX:
+ /* no mapping is possible */
+ d.type = PM_TYPE_NOSUPPORT;
+ d.sem = PM_SEM_INSTANT;
+ d.units = noUnits;
+ break;
+ }
+ __pmSendDesc(STDOUT_FILENO, pmWhichContext(), &d);
+}
+
+
+/***********************************************************************
+ * exported functions
+ ***********************************************************************/
+
+/* initialize access to archive */
+int
+initArchive(Archive *a)
+{
+ pmLogLabel label;
+ struct timeval tv;
+ int sts;
+ int handle;
+ Archive *b;
+ const char *tmp;
+
+ /* setup temorary context for the archive */
+ if ((sts = pmNewContext(PM_CONTEXT_ARCHIVE, a->fname)) < 0) {
+ fprintf(stderr, "%s: cannot open archive %s\n"
+ "pmNewContext failed: %s\n",
+ pmProgname, a->fname, pmErrStr(sts));
+ return 0;
+ }
+ handle = sts;
+
+ tmp = pmGetContextHostName(handle);
+ if (strlen(tmp) == 0) {
+ fprintf(stderr, "%s: pmGetContextHostName(%d) failed\n",
+ pmProgname, handle);
+ return 0;
+ }
+ if ((a->hname = strdup(tmp)) == NULL)
+ __pmNoMem("host name copy", strlen(tmp)+1, PM_FATAL_ERR);
+
+ /* get the goodies from archive label */
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: cannot read label from archive %s\n"
+ "pmGetArchiveLabel failed: %s\n",
+ pmProgname, a->fname, pmErrStr(sts));
+ pmDestroyContext(handle);
+ return 0;
+ }
+ a->first = realize(label.ll_start);
+ if ((sts = pmGetArchiveEnd(&tv)) < 0) {
+ fprintf(stderr, "%s: archive %s is corrupted\n"
+ "pmGetArchiveEnd failed: %s\n",
+ pmProgname, a->fname, pmErrStr(sts));
+ pmDestroyContext(handle);
+ return 0;
+ }
+ a->last = realize(tv);
+
+ /* check for duplicate host */
+ b = archives;
+ while (b) {
+ if (strcmp(a->hname, b->hname) == 0) {
+ fprintf(stderr, "%s: Error: archive %s not legal - archive %s is already open "
+ "for host %s\n", pmProgname, a->fname, b->fname, b->hname);
+ pmDestroyContext(handle);
+ return 0;
+ }
+ b = b->next;
+ }
+
+ /* put archive record on the archives list */
+ a->next = archives;
+ archives = a;
+
+ /* update first and last available data points */
+ if (first == -1 || a->first < first)
+ first = a->first;
+ if (a->last > last)
+ last = a->last;
+
+ pmDestroyContext(handle);
+ return 1;
+}
+
+
+/* initialize timezone */
+void
+zoneInit(void)
+{
+ int sts;
+ int handle = -1;
+ Archive *a;
+
+ if (timeZone) { /* TZ from timezone string */
+ if ((sts = pmNewZone(timeZone)) < 0)
+ fprintf(stderr, "%s: cannot set timezone to %s\n"
+ "pmNewZone failed: %s\n", pmProgname, timeZone,
+ pmErrStr(sts));
+ }
+ else if (! archives && hostZone) { /* TZ from live host */
+ if ((handle = pmNewContext(PM_CONTEXT_HOST, dfltHostConn)) < 0)
+ fprintf(stderr, "%s: cannot set timezone from %s\n"
+ "pmNewContext failed: %s\n", pmProgname,
+ findsource(dfltHostConn), pmErrStr(handle));
+ else if ((sts = pmNewContextZone()) < 0)
+ fprintf(stderr, "%s: cannot set timezone from %s\n"
+ "pmNewContextZone failed: %s\n", pmProgname,
+ findsource(dfltHostConn), pmErrStr(sts));
+ else
+ fprintf(stdout, "%s: timezone set to local timezone of host %s\n",
+ pmProgname, dfltHostConn);
+ if (handle >= 0)
+ pmDestroyContext(handle);
+ }
+ else if (hostZone) { /* TZ from an archive */
+ a = archives;
+ while (a) {
+ if (strcmp(dfltHostName, a->hname) == 0)
+ break;
+ a = a->next;
+ }
+ if (! a)
+ fprintf(stderr, "%s: no archive supplied for host %s\n",
+ pmProgname, dfltHostName);
+ else if ((handle = pmNewContext(PM_CONTEXT_ARCHIVE, a->fname)) < 0)
+ fprintf(stderr, "%s: cannot set timezone from %s\npmNewContext failed: %s\n",
+ pmProgname, findsource(dfltHostName), pmErrStr(handle));
+ else if ((sts = pmNewContextZone()) < 0)
+ fprintf(stderr, "%s: cannot set timezone from %s\n"
+ "pmNewContextZone failed: %s\n",
+ pmProgname, findsource(dfltHostName), pmErrStr(sts));
+ else
+ fprintf(stdout, "%s: timezone set to local timezone of host %s\n",
+ pmProgname, dfltHostName);
+ if (handle >= 0)
+ pmDestroyContext(handle);
+ }
+}
+
+
+/* convert to canonical units */
+pmUnits
+canon(pmUnits in)
+{
+ static pmUnits out;
+
+ out = in;
+ out.scaleSpace = PM_SPACE_BYTE;
+ out.scaleTime = PM_TIME_SEC;
+ out.scaleCount = 0;
+ return out;
+}
+
+/* scale factor to canonical pmUnits */
+double
+scale(pmUnits in)
+{
+ double f;
+
+ /* scale space to Mbyte */
+ f = pow(1024, in.dimSpace * (in.scaleSpace - PM_SPACE_BYTE));
+
+ /* scale time to seconds */
+ if (in.scaleTime > PM_TIME_SEC)
+ f *= pow(60, in.dimTime * (in.scaleTime - PM_TIME_SEC));
+ else
+ f *= pow(1000, in.dimTime * (in.scaleTime - PM_TIME_SEC));
+
+ /* scale events to millions of events */
+ f *= pow(10, in.dimCount * in.scaleCount);
+
+ return f;
+}
+
+
+/* initialize Metric */
+int /* 1: ok, 0: try again later, -1: fail */
+initMetric(Metric *m)
+{
+ char *hname = symName(m->hname);
+ char *mname = symName(m->mname);
+ char **inames;
+ int *iids;
+ int handle;
+ int ret = 1;
+ int sts;
+ int i, j;
+
+ /* set up temporary context */
+ if ((handle = newContext(hname)) < 0)
+ return 0;
+
+ host_state_changed(hname, STATE_RECONN);
+
+ if ((sts = pmLookupName(1, &mname, &m->desc.pmid)) < 0) {
+ fprintf(stderr, "%s: metric %s not in namespace for %s\n"
+ "pmLookupName failed: %s\n",
+ pmProgname, mname, findsource(hname), pmErrStr(sts));
+ ret = 0;
+ goto end;
+ }
+
+ /* fill in performance metric descriptor */
+ if ((sts = pmLookupDesc(m->desc.pmid, &m->desc)) < 0) {
+ fprintf(stderr, "%s: metric %s not currently available from %s\n"
+ "pmLookupDesc failed: %s\n",
+ pmProgname, mname, findsource(hname), pmErrStr(sts));
+ ret = 0;
+ goto end;
+ }
+
+ if (m->desc.type == PM_TYPE_STRING ||
+ m->desc.type == PM_TYPE_AGGREGATE ||
+ m->desc.type == PM_TYPE_AGGREGATE_STATIC ||
+ m->desc.type == PM_TYPE_EVENT ||
+ m->desc.type == PM_TYPE_HIGHRES_EVENT ||
+ m->desc.type == PM_TYPE_UNKNOWN) {
+ fprintf(stderr, "%s: metric %s has non-numeric type\n", pmProgname, mname);
+ ret = -1;
+ }
+ else if (m->desc.indom == PM_INDOM_NULL) {
+ if (m->specinst != 0) {
+ fprintf(stderr, "%s: metric %s has no instances\n", pmProgname, mname);
+ ret = -1;
+ }
+ else
+ m->m_idom = 1;
+ }
+ else {
+ /* metric has instances, get full instance profile */
+ if (archives) {
+ if ((sts = pmGetInDomArchive(m->desc.indom, &iids, &inames)) < 0) {
+ fprintf(stderr, "Metric %s from %s - instance domain not "
+ "available in archive\npmGetInDomArchive failed: %s\n",
+ mname, findsource(hname), pmErrStr(sts));
+ ret = -1;
+ }
+ }
+ else if ((sts = pmGetInDom(m->desc.indom, &iids, &inames)) < 0) {
+ fprintf(stderr, "Instance domain for metric %s from %s not (currently) available\n"
+ "pmGetIndom failed: %s\n", mname, findsource(hname), pmErrStr(sts));
+ ret = 0;
+ }
+
+ if (ret == 1) { /* got instance profile */
+ if (m->specinst == 0) {
+ /* all instances */
+ m->iids = iids;
+ m->m_idom = sts;
+ m->inames = alloc(m->m_idom*sizeof(char *));
+ for (i = 0; i < m->m_idom; i++) {
+ m->inames[i] = sdup(inames[i]);
+ }
+ }
+ else {
+ /* selected instances only */
+ m->m_idom = 0;
+ for (i = 0; i < m->specinst; i++) {
+ /* look for first matching instance name */
+ for (j = 0; j < sts; j++) {
+ if (eqinst(m->inames[i], inames[j])) {
+ m->iids[i] = iids[j];
+ m->m_idom++;
+ break;
+ }
+ }
+ if (j == sts) {
+ __pmNotifyErr(LOG_ERR, "metric %s from %s does not "
+ "(currently) have instance \"%s\"\n",
+ mname, findsource(hname), m->inames[i]);
+ m->iids[i] = PM_IN_NULL;
+ ret = 0;
+ }
+ }
+ if (sts > 0) {
+ /*
+ * pmGetInDom or pmGetInDomArchive returned some
+ * instances above
+ */
+ free(iids);
+ }
+
+ /*
+ * if specinst != m_idom, then some not found ... move these
+ * to the end of the list
+ */
+ for (j = m->specinst-1; j >= 0; j--) {
+ if (m->iids[j] != PM_IN_NULL)
+ break;
+ }
+ for (i = 0; i < j; i++) {
+ if (m->iids[i] == PM_IN_NULL) {
+ /* need to swap */
+ char *tp;
+ tp = m->inames[i];
+ m->inames[i] = m->inames[j];
+ m->iids[i] = m->iids[j];
+ m->inames[j] = tp;
+ m->iids[j] = PM_IN_NULL;
+ j--;
+ }
+ }
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ int numinst;
+ fprintf(stderr, "initMetric: %s from %s: instance domain specinst=%d\n",
+ mname, hname, m->specinst);
+ if (m->m_idom < 1) fprintf(stderr, " %d instances!\n", m->m_idom);
+ numinst = m->specinst == 0 ? m->m_idom : m->specinst;
+ for (i = 0; i < numinst; i++) {
+ fprintf(stderr, " indom[%d]", i);
+ if (m->iids[i] == PM_IN_NULL)
+ fprintf(stderr, " ?missing");
+ else
+ fprintf(stderr, " %d", m->iids[i]);
+ fprintf(stderr, " \"%s\"\n", m->inames[i]);
+ }
+ }
+#endif
+ if (sts > 0) {
+ /*
+ * pmGetInDom or pmGetInDomArchive returned some instances
+ * above
+ */
+ free(inames);
+ }
+ }
+ }
+
+ if (ret == 1) {
+ /* compute conversion factor into canonical units
+ - non-zero conversion factor flags initialized metric */
+ m->conv = scale(m->desc.units);
+
+ /* automatic rate computation */
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ m->vals = (double *) ralloc(m->vals, m->m_idom * sizeof(double));
+ for (j = 0; j < m->m_idom; j++)
+ m->vals[j] = 0;
+ }
+ }
+
+end:
+ /* destroy temporary context */
+ pmDestroyContext(handle);
+
+ /* retry not meaningful for archives */
+ if (archives && (ret == 0))
+ ret = -1;
+
+ return ret;
+}
+
+
+/* reinitialize Metric - only for live host */
+int /* 1: ok, 0: try again later, -1: fail */
+reinitMetric(Metric *m)
+{
+ char *hname = symName(m->hname);
+ char *mname = symName(m->mname);
+ char **inames;
+ int *iids;
+ int handle;
+ int ret = 1;
+ int sts;
+ int i, j;
+
+ /* set up temporary context */
+ if ((handle = newContext(hname)) < 0)
+ return 0;
+
+ host_state_changed(hname, STATE_RECONN);
+
+ if ((sts = pmLookupName(1, &mname, &m->desc.pmid)) < 0) {
+ ret = 0;
+ goto end;
+ }
+
+ /* fill in performance metric descriptor */
+ if ((sts = pmLookupDesc(m->desc.pmid, &m->desc)) < 0) {
+ ret = 0;
+ goto end;
+ }
+
+ if (m->desc.type == PM_TYPE_STRING ||
+ m->desc.type == PM_TYPE_AGGREGATE ||
+ m->desc.type == PM_TYPE_AGGREGATE_STATIC ||
+ m->desc.type == PM_TYPE_EVENT ||
+ m->desc.type == PM_TYPE_HIGHRES_EVENT ||
+ m->desc.type == PM_TYPE_UNKNOWN) {
+ fprintf(stderr, "%s: metric %s has non-numeric type\n", pmProgname, mname);
+ ret = -1;
+ }
+ else if (m->desc.indom == PM_INDOM_NULL) {
+ if (m->specinst != 0) {
+ fprintf(stderr, "%s: metric %s has no instances\n", pmProgname, mname);
+ ret = -1;
+ }
+ else
+ m->m_idom = 1;
+ }
+ else {
+ if ((sts = pmGetInDom(m->desc.indom, &iids, &inames)) < 0) { /* full profile */
+ ret = 0;
+ }
+ else {
+ if (m->specinst == 0) {
+ /* all instances */
+ m->iids = iids;
+ m->m_idom = sts;
+ m->inames = alloc(m->m_idom*sizeof(char *));
+ for (i = 0; i < m->m_idom; i++) {
+ m->inames[i] = sdup(inames[i]);
+ }
+ }
+ else {
+ /* explicit instance profile */
+ m->m_idom = 0;
+ for (i = 0; i < m->specinst; i++) {
+ /* look for first matching instance name */
+ for (j = 0; j < sts; j++) {
+ if (eqinst(m->inames[i], inames[j])) {
+ m->iids[i] = iids[j];
+ m->m_idom++;
+ break;
+ }
+ }
+ if (j == sts) {
+ m->iids[i] = PM_IN_NULL;
+ ret = 0;
+ }
+ }
+ if (sts > 0) {
+ /*
+ * pmGetInDom or pmGetInDomArchive returned some
+ * instances above
+ */
+ free(iids);
+ }
+
+ /*
+ * if specinst != m_idom, then some not found ... move these
+ * to the end of the list
+ */
+ for (j = m->specinst-1; j >= 0; j--) {
+ if (m->iids[j] != PM_IN_NULL)
+ break;
+ }
+ for (i = 0; i < j; i++) {
+ if (m->iids[i] == PM_IN_NULL) {
+ /* need to swap */
+ char *tp;
+ tp = m->inames[i];
+ m->inames[i] = m->inames[j];
+ m->iids[i] = m->iids[j];
+ m->inames[j] = tp;
+ m->iids[j] = PM_IN_NULL;
+ j--;
+ }
+ }
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ int numinst;
+ fprintf(stderr, "reinitMetric: %s from %s: instance domain specinst=%d\n",
+ mname, hname, m->specinst);
+ if (m->m_idom < 1) fprintf(stderr, " %d instances!\n", m->m_idom);
+ if (m->specinst == 0) numinst = m->m_idom;
+ else numinst = m->specinst;
+ for (i = 0; i < numinst; i++) {
+ fprintf(stderr, " indom[%d]", i);
+ if (m->iids[i] == PM_IN_NULL)
+ fprintf(stderr, " ?missing");
+ else
+ fprintf(stderr, " %d", m->iids[i]);
+ fprintf(stderr, " \"%s\"\n", m->inames[i]);
+ }
+ }
+#endif
+ if (sts > 0) {
+ /*
+ * pmGetInDom or pmGetInDomArchive returned some instances
+ * above
+ */
+ free(inames);
+ }
+ }
+ }
+
+ if (ret == 1) {
+ /* compute conversion factor into canonical units
+ - non-zero conversion factor flags initialized metric */
+ m->conv = scale(m->desc.units);
+
+ /* automatic rate computation */
+ if (m->desc.sem == PM_SEM_COUNTER) {
+ m->vals = (double *) ralloc(m->vals, m->m_idom * sizeof(double));
+ for (j = 0; j < m->m_idom; j++)
+ m->vals[j] = 0;
+ }
+ }
+
+ if (ret >= 0) {
+ /*
+ * re-shape, starting here are working up the expression until
+ * we reach the top of the tree or the designated metrics
+ * associated with the node are not the same
+ */
+ Expr *x = m->expr;
+ while (x) {
+ /*
+ * only re-shape expressions that may have set values
+ */
+ if (x->op == CND_FETCH ||
+ x->op == CND_NEG || x->op == CND_ADD || x->op == CND_SUB ||
+ x->op == CND_MUL || x->op == CND_DIV ||
+ x->op == CND_SUM_HOST || x->op == CND_SUM_INST ||
+ x->op == CND_SUM_TIME ||
+ x->op == CND_AVG_HOST || x->op == CND_AVG_INST ||
+ x->op == CND_AVG_TIME ||
+ x->op == CND_MAX_HOST || x->op == CND_MAX_INST ||
+ x->op == CND_MAX_TIME ||
+ x->op == CND_MIN_HOST || x->op == CND_MIN_INST ||
+ x->op == CND_MIN_TIME ||
+ x->op == CND_EQ || x->op == CND_NEQ ||
+ x->op == CND_LT || x->op == CND_LTE ||
+ x->op == CND_GT || x->op == CND_GTE ||
+ x->op == CND_NOT || x->op == CND_AND || x->op == CND_OR ||
+ x->op == CND_RISE || x->op == CND_FALL ||
+ x->op == CND_MATCH || x->op == CND_NOMATCH) {
+ instFetchExpr(x);
+ findEval(x);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "reinitMetric: re-shaped ...\n");
+ dumpExpr(x);
+ }
+#endif
+ }
+ if (x->parent) {
+ x = x->parent;
+ if (x->metrics == m)
+ continue;
+ }
+ break;
+ }
+ }
+
+end:
+ /* destroy temporary context */
+ pmDestroyContext(handle);
+
+ return ret;
+}
+
+
+/* put initialised Metric onto fetch list */
+void
+bundleMetric(Host *h, Metric *m)
+{
+ Fetch *f = findFetch(h, m);
+ if (f == NULL) {
+ /*
+ * creating new fetch bundle and pmNewContext failed ...
+ * not much choice here
+ */
+ waitMetric(m);
+ }
+ else
+ /* usual case */
+ findProfile(findFetch(h, m), m);
+}
+
+
+/* reconnect attempt to host */
+int
+reconnect(Host *h)
+{
+ Fetch *f;
+
+ f = h->fetches;
+ while (f) {
+ if (pmReconnectContext(f->handle) < 0)
+ return 0;
+ if (clientid != NULL)
+ /* re-register client id with pmcd */
+ __pmSetClientId(clientid);
+ f = f->next;
+ }
+ return 1;
+}
+
+
+/* pragmatics analysis */
+void
+pragmatics(Symbol rule, RealTime delta)
+{
+ Expr *x = symValue(rule);
+ Task *t;
+
+ if (x->op != NOP) {
+ t = findTask(delta);
+ bundle(t, x);
+ t->nrules++;
+ t->rules = (Symbol *) ralloc(t->rules, t->nrules * sizeof(Symbol));
+ t->rules[t->nrules-1] = symCopy(rule);
+ perf->eval_expected += (float)1/delta;
+ }
+}
+
+/*
+ * find all expressions for a host that has just been marked "down"
+ * and invalidate them
+ */
+static void
+mark_all(Host *hdown)
+{
+ Task *t;
+ Symbol *s;
+ Metric *m;
+ Expr *x;
+ int i;
+
+ for (t = taskq; t != NULL; t = t->next) {
+ s = t->rules;
+ for (i = 0; i < t->nrules; i++, s++) {
+ x = (Expr *)symValue(*s);
+ for (m = x->metrics; m != NULL; m = m->next) {
+ if (m->host == hdown)
+ clobber(x);
+ }
+ }
+ }
+}
+
+/* execute fetches for given Task */
+void
+taskFetch(Task *t)
+{
+ Host *h;
+ Fetch *f;
+ Profile *p;
+ Metric *m;
+ pmResult *r;
+ pmValueSet **v;
+ int i;
+ int sts;
+
+ /* do all fetches, quick as you can */
+ h = t->hosts;
+ while (h) {
+ f = h->fetches;
+ while (f) {
+ if (f->result) pmFreeResult(f->result);
+ if (! h->down) {
+ pmUseContext(f->handle);
+ if ((sts = pmFetch(f->npmids, f->pmids, &f->result)) < 0) {
+ if (! archives) {
+ __pmNotifyErr(LOG_ERR, "pmFetch from %s failed: %s\n",
+ symName(f->host->name), pmErrStr(sts));
+ host_state_changed(symName(f->host->name), STATE_LOSTCONN);
+ h->down = 1;
+ mark_all(h);
+ }
+ f->result = NULL;
+ }
+ }
+ else
+ f->result = NULL;
+ f = f->next;
+ }
+ h = h->next;
+ }
+
+ /* sort and distribute pmValueSets to requesting Metrics */
+ h = t->hosts;
+ while (h) {
+ if (! h->down) {
+ f = h->fetches;
+ while (f && (r = f->result)) {
+ /* sort all vlists in result r */
+ v = r->vset;
+ for (i = 0; i < r->numpmid; i++) {
+ if ((*v)->numval > 0) {
+ qsort((*v)->vlist, (size_t)(*v)->numval,
+ sizeof(pmValue), compair);
+ }
+ v++;
+ }
+
+ /* distribute pmValueSets to Metrics */
+ p = f->profiles;
+ while (p) {
+ m = p->metrics;
+ while (m) {
+ for (i = 0; i < r->numpmid; i++) {
+ if (m->desc.pmid == r->vset[i]->pmid) {
+ if (r->vset[i]->numval > 0) {
+ m->vset = r->vset[i];
+ m->stamp = realize(r->timestamp);
+ }
+ break;
+ }
+ }
+ m = m->next;
+ }
+ p = p->next;
+ }
+ f = f->next;
+ }
+ }
+ h = h->next;
+ }
+}
+
+
+/* send pmDescriptors for all expressions in given task */
+void
+sendDescs(Task *task)
+{
+ Symbol *s;
+ int i;
+
+ s = task->rules;
+ for (i = 0; i < task->nrules; i++) {
+ sendDesc(symValue(*s), task->rslt->vset[i]);
+ s++;
+ }
+}
+
+
+/* convert Expr value to pmValueSet value */
+void
+fillVSet(Expr *x, pmValueSet *vset)
+{
+ if (x->valid > 0) {
+ if (finite(*((double *)x->ring))) { /* copy value */
+ vset->numval = 1;
+ memcpy(&vset->vlist[0].value.pval->vbuf, x->ring, sizeof(double));
+ }
+ else /* value not representable */
+ vset->numval = 0;
+ }
+ else { /* value not available */
+ vset->numval = PM_ERR_VALUE;
+ }
+}
diff --git a/src/pmie/src/pragmatics.h b/src/pmie/src/pragmatics.h
new file mode 100644
index 0000000..bee5f88
--- /dev/null
+++ b/src/pmie/src/pragmatics.h
@@ -0,0 +1,103 @@
+/***********************************************************************
+ * pragmatics.h - inference engine pragmatics analysis
+ *
+ * The analysis of how to organize the fetching of metrics (pragmatics)
+ * and other parts of the inference engine that are particularly
+ * sensitive to details of the performance metrics API are kept here.
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef PRAG_H
+#define PRAG_H
+
+#include "pmapi.h"
+
+/* report where PCP data is coming from */
+char *findsource(char *);
+
+/* initialize performance metrics API */
+void pmcsInit(void);
+
+/* juggle contexts */
+int newContext(char *);
+
+/* initialize access to archive */
+int initArchive(Archive *);
+
+/* initialize timezone */
+void zoneInit(void);
+
+/* convert to canonical units */
+pmUnits canon(pmUnits);
+
+/* scale factor to canonical pmUnits */
+double scale(pmUnits);
+
+/* initialize Metric */
+int initMetric(Metric *);
+
+/* reinitialize Metric */
+int reinitMetric(Metric *);
+
+/* put initialiaed Metric onto fetch list */
+void bundleMetric(Host *, Metric *);
+
+/* reconnect attempt to host */
+int reconnect(Host *);
+
+/* pragmatics analysis */
+void pragmatics(Symbol, RealTime);
+
+/* execute fetches for given Task */
+void taskFetch(Task *);
+
+/* convert Expr value to pmValueSet value */
+void fillVSet(Expr *, pmValueSet *);
+
+/* send pmDescriptors for all expressions in given task */
+void sendDescs(Task *);
+
+/* put Metric onto wait list */
+void waitMetric(Metric *);
+
+/* remove Metric from wait list */
+void unwaitMetric(Metric *);
+
+/* check that pmUnits dimensions are equal */
+#define dimeq(x, y) (((x).dimSpace == (y).dimSpace) && \
+ ((x).dimTime == (y).dimTime) && \
+ ((x).dimCount == (y).dimCount))
+
+/* check equality of two pmUnits */
+#define unieq(x, y) (((x).dimSpace == (y).dimSpace) && \
+ ((x).dimTime == (y).dimTime) && \
+ ((x).dimCount == (y).dimCount) && \
+ ((x).scaleSpace == (y).scaleSpace) && \
+ ((x).scaleTime == (y).scaleTime) && \
+ ((x).scaleCount == (y).scaleCount))
+
+/* for initialization of pmUnits struct */
+extern pmUnits noUnits;
+extern pmUnits countUnits;
+
+/* flag processes spawned */
+extern int need_wait;
+
+#endif /* PRAG_H */
+
+
diff --git a/src/pmie/src/show.c b/src/pmie/src/show.c
new file mode 100644
index 0000000..f879679
--- /dev/null
+++ b/src/pmie/src/show.c
@@ -0,0 +1,1104 @@
+/***********************************************************************
+ * show.c - display expressions and their values
+ ***********************************************************************
+ *
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <math.h>
+#include <ctype.h>
+#include <assert.h>
+#include "show.h"
+#include "impl.h"
+#include "dstruct.h"
+#include "lexicon.h"
+#include "pragmatics.h"
+#if defined(HAVE_IEEEFP_H)
+#include <ieeefp.h>
+#endif
+
+/***********************************************************************
+ * local declarations
+ ***********************************************************************/
+
+static struct {
+ int op;
+ char *str;
+} opstr[] = {
+ { RULE, "->" },
+ { CND_FETCH, "<fetch node>" },
+ { CND_DELAY, "<delay node>" },
+ { CND_RATE, "rate" },
+ { CND_NEG, "-" },
+ { CND_ADD, "+" },
+ { CND_SUB, "-" },
+ { CND_MUL, "*" },
+ { CND_DIV, "/" },
+/* aggregation */
+ { CND_SUM_HOST, "sum_host" },
+ { CND_SUM_INST, "sum_inst" },
+ { CND_SUM_TIME, "sum_sample" },
+ { CND_AVG_HOST, "avg_host" },
+ { CND_AVG_INST, "avg_inst" },
+ { CND_AVG_TIME, "avg_sample" },
+ { CND_MAX_HOST, "max_host" },
+ { CND_MAX_INST, "max_inst" },
+ { CND_MAX_TIME, "max_sample" },
+ { CND_MIN_HOST, "min_host" },
+ { CND_MIN_INST, "min_inst" },
+ { CND_MIN_TIME, "min_sample" },
+/* relational */
+ { CND_EQ, "==" },
+ { CND_NEQ, "!=" },
+ { CND_LT, "<" },
+ { CND_LTE, "<=" },
+ { CND_GT, ">" },
+ { CND_GTE, ">=" },
+/* boolean */
+ { CND_NOT, "!" },
+ { CND_RISE, "rising" },
+ { CND_FALL, "falling" },
+ { CND_AND, "&&" },
+ { CND_OR, "||" },
+ { CND_MATCH, "match_inst" },
+ { CND_NOMATCH, "nomatch_inst" },
+ { CND_RULESET, "ruleset" },
+ { CND_OTHER, "other" },
+/* quantification */
+ { CND_ALL_HOST, "all_host" },
+ { CND_ALL_INST, "all_inst" },
+ { CND_ALL_TIME, "all_sample" },
+ { CND_SOME_HOST, "some_host" },
+ { CND_SOME_INST, "some_inst" },
+ { CND_SOME_TIME, "some_sample" },
+ { CND_PCNT_HOST, "pcnt_host" },
+ { CND_PCNT_INST, "pcnt_inst" },
+ { CND_PCNT_TIME, "pcnt_sample" },
+ { CND_COUNT_HOST, "count_host" },
+ { CND_COUNT_INST, "count_inst" },
+ { CND_COUNT_TIME, "count_sample" },
+ { ACT_SEQ, "&" },
+ { ACT_ALT, "|" },
+ { ACT_SHELL, "shell" },
+ { ACT_ALARM, "alarm" },
+ { ACT_SYSLOG, "syslog" },
+ { ACT_PRINT, "print" },
+ { ACT_STOMP, "stomp" },
+ { ACT_ARG, "<action arg node>" },
+ { NOP, "<nop node>" },
+ { OP_VAR, "<op_var node>" },
+};
+
+static int numopstr = sizeof(opstr) / sizeof(opstr[0]);
+
+/***********************************************************************
+ * local utility functions
+ ***********************************************************************/
+
+/* Concatenate string1 to existing string2 whose original length is given. */
+static size_t /* new length of *string2 */
+concat(char *string1, size_t pos, char **string2)
+{
+ size_t slen;
+ size_t tlen;
+ char *cat;
+ char *dog;
+
+ if ((slen = strlen(string1)) == 0)
+ return pos;
+ tlen = pos + slen;
+ cat = (char *) ralloc(*string2, tlen + 1);
+ dog = cat + pos;
+ strcpy(dog, string1);
+ dog += slen;
+ *dog = '\0';
+
+ *string2 = cat;
+ return tlen;
+}
+
+
+/***********************************************************************
+ * host and instance names
+ ***********************************************************************/
+
+/* Return host and instance name for nth value in expression *x */
+static int
+lookupHostInst(Expr *x, int nth, char **host, char **inst)
+{
+ Metric *m = NULL;
+ int mi;
+ int sts = 0;
+ int pick = -1;
+ int matchaggr = 0;
+ int aggrop = NOP;
+ double *aggrval = NULL;
+#if PCP_DEBUG
+ static Expr *lastx = NULL;
+ int dbg_dump = 0;
+#endif
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (x != lastx) {
+ fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...)\n", x, nth);
+ lastx = x;
+ dbg_dump = 1;
+ }
+ }
+#endif
+ if (x->op == CND_MIN_HOST || x->op == CND_MAX_HOST ||
+ x->op == CND_MIN_INST || x->op == CND_MAX_INST ||
+ x->op == CND_MIN_TIME || x->op == CND_MAX_TIME) {
+ /*
+ * extrema operators ... value is here, but the host, instance, sample
+ * context is in the child expression ... go one level deeper and try
+ * to match the value
+ */
+ aggrop = x->op;
+ aggrval = (double *)x->smpls[0].ptr;
+ matchaggr = 1;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "lookupHostInst look for extrema val=%f @ " PRINTF_P_PFX "%p\n", *aggrval, x);
+ }
+ x = x->arg1;
+#endif
+ }
+
+ /* check for no host and instance available e.g. constant expression */
+ if ((x->e_idom <= 0 && x->hdom <= 0) || ! x->metrics) {
+ *host = NULL;
+ *inst = NULL;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...) -> %%h and %%i undefined\n", x, nth);
+ }
+#endif
+ return sts;
+ }
+
+ /* find Metric containing the nth instance */
+ if (matchaggr == 0) {
+ pick = nth;
+ mi = 0;
+ for (;;) {
+ m = &x->metrics[mi];
+#if PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL2) && dbg_dump) {
+ fprintf(stderr, "lookupHostInst: metrics[%d]\n", mi);
+ dumpMetric(m);
+ }
+#endif
+ if (pick < m->m_idom)
+ break;
+ if (m->m_idom > 0)
+ pick -= m->m_idom;
+ mi++;
+ }
+ }
+ else {
+ if (aggrop == CND_MIN_HOST || aggrop == CND_MAX_HOST) {
+ int k;
+#if PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL2) && dbg_dump) {
+ fprintf(stderr, "lookupHostInst [extrema_host]:\n");
+ }
+#endif
+ for (k = 0; k < x->tspan; k++) {
+#if DESPERATE
+ fprintf(stderr, "smpls[0][%d]=%g\n", k, *((double *)x->smpls[0].ptr+k));
+#endif
+ if (*aggrval == *((double *)x->smpls[0].ptr+k)) {
+ m = &x->metrics[k];
+ goto done;
+ }
+ }
+ fprintf(stderr, "Internal error: LookupHostInst: %s\n", opStrings(aggrop));
+ }
+ else if (aggrop == CND_MIN_INST || aggrop == CND_MAX_INST) {
+ int k;
+ for (k = 0; k < x->tspan; k++) {
+#if DESPERATE
+ fprintf(stderr, "smpls[0][%d]=%g\n", k, *((double *)x->smpls[0].ptr+k));
+#endif
+ if (*aggrval == *((double *)x->smpls[0].ptr+k)) {
+ pick = k;
+ m = &x->metrics[0];
+#if PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL2) && dbg_dump) {
+ fprintf(stderr, "lookupHostInst [extrema_inst]:\n");
+ dumpMetric(m);
+ }
+#endif
+ goto done;
+ }
+ }
+ fprintf(stderr, "Internal error: LookupHostInst: %s\n", opStrings(aggrop));
+ }
+ else if (aggrop == CND_MIN_TIME || aggrop == CND_MAX_TIME) {
+ int k;
+ for (k = 0; k < x->nsmpls; k++) {
+#if DESPERATE
+ fprintf(stderr, "smpls[%d][0]=%g\n", k, *((double *)x->smpls[k].ptr));
+#endif
+ if (*aggrval == *((double *)x->smpls[k].ptr)) {
+ pick = nth;
+ m = &x->metrics[0];
+#if PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL2) && dbg_dump) {
+ fprintf(stderr, "lookupHostInst [extrema_sample]:\n");
+ dumpMetric(m);
+ }
+#endif
+ goto done;
+ }
+ }
+ fprintf(stderr, "Internal error: LookupHostInst: %s\n", opStrings(aggrop));
+ }
+ }
+
+done:
+ /* host and instance names */
+ if (m == NULL) {
+ *host = NULL;
+ *inst = NULL;
+ }
+ else {
+ *host = symName(m->hname);
+ sts++;
+ if (pick >= 0 && x->e_idom > 0 && m->inames) {
+ *inst = m->inames[pick];
+ sts++;
+ }
+ else
+ *inst = NULL;
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "lookupHostInst(x=" PRINTF_P_PFX "%p, nth=%d, ...) -> sts=%d %%h=%s %%i=%s\n",
+ x, nth, sts, *host, *inst == NULL ? "undefined" : *inst);
+ }
+#endif
+
+ return sts;
+}
+
+
+/***********************************************************************
+ * expression value
+ ***********************************************************************/
+
+#define BOOLEAN_SPACE 8
+
+static size_t
+showBoolean(Expr *x, int nth, size_t length, char **string)
+{
+ int smpl;
+ size_t tlen;
+ char *cat;
+ char *dog;
+ int val;
+
+ tlen = length + (x->nsmpls * BOOLEAN_SPACE);
+ cat = (char *)ralloc(*string, tlen + 1);
+ dog = cat + length;
+ for (smpl = 0; smpl < x->nsmpls; smpl++) {
+ if (smpl > 0) {
+ strcpy(dog, " ");
+ dog += 1;
+ }
+
+ if (x->valid == 0) {
+ strncpy(dog, "unknown", BOOLEAN_SPACE);
+ dog += strlen("unknown");
+ continue;
+ }
+
+ val = *((char *)x->smpls[smpl].ptr + nth);
+ if (val == B_FALSE) {
+ strncpy(dog, "false", BOOLEAN_SPACE);
+ dog += strlen("false");
+ }
+ else if (val == B_TRUE) {
+ strncpy(dog, "true", BOOLEAN_SPACE);
+ dog += strlen("true");
+ }
+ else if (val == B_UNKNOWN) {
+ strncpy(dog, "unknown", BOOLEAN_SPACE);
+ dog += strlen("unknown");
+ }
+ else {
+ sprintf(dog, "0x%02x?", val & 0xff);
+ dog += 5;
+ }
+ }
+ *dog = '\0';
+
+ *string = cat;
+ return dog - cat;
+}
+
+
+static size_t
+showString(Expr *x, size_t length, char **string)
+{
+ size_t slen;
+ size_t tlen;
+ char *cat;
+ char *dog;
+
+ slen = strlen((char *)x->smpls[0].ptr);
+ tlen = length + slen + 2;
+ cat = (char *)ralloc(*string, tlen + 1);
+ dog = cat + length;
+ *dog++ = '"';
+ strcpy(dog, (char *)x->smpls[0].ptr);
+ dog += slen;
+ *dog++ = '"';
+ *dog = '\0';
+
+ *string = cat;
+ return tlen;
+}
+
+#define DBL_SPACE 24
+
+static size_t
+showNum(Expr *x, int nth, size_t length, char **string)
+{
+ int smpl;
+ size_t tlen;
+ char *cat;
+ char *dog;
+ char *fmt;
+ double v;
+ double abs_v;
+ int sts;
+
+ tlen = length + (x->nsmpls * DBL_SPACE);
+ cat = (char *)ralloc(*string, tlen + 1);
+ dog = cat + length;
+ for (smpl = 0; smpl < x->nsmpls; smpl++) {
+ int noval = 0;
+ if (smpl > 0) {
+ strcpy(dog, " ");
+ dog++;
+ }
+ if (x->valid <= smpl)
+ noval = 1;
+ else {
+#ifdef HAVE_FPCLASSIFY
+ noval = fpclassify(*((double *)x->smpls[smpl].ptr + nth)) == FP_NAN;
+#else
+#ifdef HAVE_ISNAN
+ noval = isnan(*((double *)x->smpls[smpl].ptr + nth));
+#endif
+#endif
+ }
+ if (noval) {
+ if (x->sem == SEM_BOOLEAN) {
+ strcpy(dog, "unknown");
+ dog += strlen("unknown");
+ }
+ else {
+ strcpy(dog, "?");
+ dog++;
+ }
+ }
+ else {
+ v = *((double *)x->smpls[smpl].ptr+nth);
+ if (v == (int)v)
+ sts = sprintf(dog, "%d", (int)v);
+ else {
+ abs_v = v < 0 ? -v : v;
+ if (abs_v < 0.5)
+ fmt = "%g";
+ else if (abs_v < 5)
+ fmt = "%.2f";
+ else if (abs_v < 50)
+ fmt = "%.1f";
+ else
+ fmt = "%.0f";
+ sts = sprintf(dog, fmt, v);
+ }
+ if (sts > 0)
+ dog += sts;
+ else {
+ strcpy(dog, "!");
+ dog += 1;
+ }
+ }
+ }
+ *dog = '\0';
+
+ *string = cat;
+ return dog - cat;
+}
+
+static char *
+showConst(Expr *x)
+{
+ char *string = NULL;
+ size_t length = 0;
+ int i;
+ int first = 1;
+
+ /* construct string representation */
+ if (x->nvals > 0) {
+ for (i = 0; i < x->tspan; i++) {
+ if (first)
+ first = 0;
+ else
+ length = concat(" ", length, &string);
+ if (x->sem == SEM_BOOLEAN)
+ length = showBoolean(x, i, length, &string);
+ else if (x->sem == SEM_REGEX) {
+ /* regex is compiled, cannot recover original string */
+ length = concat("/<regex>/", length, &string);
+ }
+ else if (x->sem == SEM_CHAR) {
+ length = showString(x, length, &string);
+ /* tspan is string length, not an iterator in this case */
+ break;
+ }
+ else
+ length = showNum(x, i, length, &string);
+ }
+ }
+ return string;
+}
+
+
+
+/***********************************************************************
+ * expression syntax
+ ***********************************************************************/
+
+static void
+showSyn(FILE *f, Expr *x)
+{
+ char *s;
+ char *c;
+ Metric *m;
+ char **n;
+ int i;
+ int paren;
+
+ if (x->op == NOP) {
+ /* constant */
+ s = showConst(x);
+ if (s) {
+ c = s;
+ while(isspace((int)*c))
+ c++;
+ fputs(c, f);
+ free(s);
+ }
+ }
+ else if ((x->op == CND_FETCH) || (x->op == CND_DELAY)) {
+ /* fetch expression (perhaps with delay) */
+ m = x->metrics;
+ fprintf(f, "%s", symName(m->mname));
+ for (i = 0; i < x->hdom; i++) {
+ fprintf(f, " :%s", symName(m->hname));
+ m++;
+ }
+ m = x->metrics;
+ if (m->inames) {
+ n = m->inames;
+ for (i = 0; i < m->m_idom; i++) {
+ fprintf(f, " #%s", *n);
+ n++;
+ }
+ }
+ if (x->op == CND_FETCH) {
+ if (x->tdom > 1)
+ fprintf(f, " @0..%d", x->tdom - 1);
+ }
+ else {
+ if (x->tdom == x->arg1->tdom - 1) fprintf(f, " @%d", x->tdom);
+ else fprintf(f, " @%d..%d", x->tdom, x->tdom + x->arg1->tdom - 1);
+ }
+ }
+ else if (x->arg1 && x->arg2) {
+ /* binary operator */
+ if (x->op == ACT_SHELL || x->op == ACT_ALARM || x->op == ACT_PRINT ||
+ x->op == ACT_STOMP) {
+ fputs(opStrings(x->op), f);
+ fputc(' ', f);
+ showSyn(f, x->arg2);
+ fputc(' ', f);
+ showSyn(f, x->arg1);
+ }
+ else if (x->op == ACT_ARG && x->parent->op == ACT_SYSLOG) {
+ int *ip;
+ char *cp;
+ ip = x->arg2->ring;
+ cp = (char *)&ip[1];
+ fprintf(f, "[level=%d tag=\"%s\"]", *ip, cp);
+ fputc(' ', f);
+ showSyn(f, x->arg1);
+ }
+ else if (x->op == CND_PCNT_HOST || x->op == CND_PCNT_INST || x->op == CND_PCNT_TIME) {
+ int pcnt;
+ fputs(opStrings(x->op), f);
+ fputc(' ', f);
+ /*
+ * used to showSyn(f, x->arg2) here, but formatting is a little
+ * better if we punt on there being a single double representation
+ * of the % value at the end of arg2
+ */
+ pcnt = (int)(*((double *)x->arg2->smpls[0].ptr)*100+0.5);
+ fprintf(f, "%d%%", pcnt);
+ fputc(' ', f);
+ if (x->arg1->op == NOP || x->arg1->op == CND_DELAY || x->arg1->op == CND_FETCH)
+ showSyn(f, x->arg1);
+ else {
+ fputc('(', f);
+ showSyn(f, x->arg1);
+ fputc(')', f);
+ }
+ }
+ else if (x->op == CND_MATCH || x->op == CND_NOMATCH) {
+ fputs(opStrings(x->op), f);
+ fputc(' ', f);
+ showSyn(f, x->arg2);
+ fputc(' ', f);
+ fputc('(', f);
+ showSyn(f, x->arg1);
+ fputc(')', f);
+ }
+ else {
+ paren = 1 -
+ (x->arg1->op == NOP || x->arg1->op == CND_DELAY ||
+ x->arg1->op == CND_FETCH || x->arg1->op == CND_RATE ||
+ x->arg1->op == CND_SUM_HOST || x->arg1->op == CND_SUM_INST ||
+ x->arg1->op == CND_SUM_TIME || x->arg1->op == CND_AVG_HOST ||
+ x->arg1->op == CND_AVG_INST || x->arg1->op == CND_AVG_TIME ||
+ x->arg1->op == CND_MAX_HOST || x->arg1->op == CND_MAX_INST ||
+ x->arg1->op == CND_MAX_TIME || x->arg1->op == CND_MIN_HOST ||
+ x->arg1->op == CND_MIN_INST || x->arg1->op == CND_MIN_TIME ||
+ x->arg1->op == CND_COUNT_HOST || x->arg1->op == CND_COUNT_INST ||
+ x->arg1->op == CND_COUNT_TIME || x->arg1->op == CND_RATE ||
+ x->op == RULE);
+ if (paren)
+ fputc('(', f);
+ showSyn(f, x->arg1);
+ if (paren)
+ fputc(')', f);
+ fputc(' ', f);
+ fputs(opStrings(x->op), f);
+ fputc(' ', f);
+ paren = 1 -
+ (x->arg2->op == NOP || x->arg2->op == CND_DELAY ||
+ x->arg2->op == CND_FETCH || x->arg2->op == CND_RATE ||
+ x->arg2->op == CND_SUM_HOST || x->arg2->op == CND_SUM_INST ||
+ x->arg2->op == CND_SUM_TIME || x->arg2->op == CND_AVG_HOST ||
+ x->arg2->op == CND_AVG_INST || x->arg2->op == CND_AVG_TIME ||
+ x->arg2->op == CND_MAX_HOST || x->arg2->op == CND_MAX_INST ||
+ x->arg2->op == CND_MAX_TIME || x->arg2->op == CND_MIN_HOST ||
+ x->arg2->op == CND_MIN_INST || x->arg2->op == CND_MIN_TIME ||
+ x->arg2->op == CND_COUNT_HOST || x->arg2->op == CND_COUNT_INST ||
+ x->arg2->op == CND_COUNT_TIME || x->arg2->op == CND_RATE ||
+ x->op == RULE);
+ if (paren)
+ fputc('(', f);
+ showSyn(f, x->arg2);
+ if (paren)
+ fputc(')', f);
+ }
+ }
+ else {
+ /* unary operator */
+ assert(x->arg1 != NULL);
+ if (x->op == ACT_ARG) {
+ /* parameters for an action */
+ Expr *y = x->arg1;
+ while (y != NULL) {
+ if (y != x->arg1)
+ fputc(' ', f);
+ showSyn(f, y);
+ // fprintf(f, "\"%s\"", (char *)y->smpls[0].ptr);
+ y = y->arg1;
+ }
+ }
+ else {
+ fputs(opStrings(x->op), f);
+ fputc(' ', f);
+ paren = 1 -
+ (x->arg1->op == ACT_SEQ || x->arg1->op == ACT_ALT ||
+ x->op == ACT_SHELL || x->op == ACT_ALARM ||
+ x->op == ACT_SYSLOG || x->op == ACT_PRINT ||
+ x->op == ACT_STOMP || x->op == CND_DELAY);
+ if (paren)
+ fputc('(', f);
+ showSyn(f, x->arg1);
+ if (paren)
+ fputc(')', f);
+ }
+ }
+}
+
+/*
+ * recursive descent to find a conjunct from the root of
+ * the expression that has associated metrics (not constants)
+ */
+static Expr *
+findMetrics(Expr *y)
+{
+ Expr *z;
+
+ if (y == NULL) return NULL;
+ if (y->metrics) return y; /* success */
+
+ /* give up if not a conjunct */
+ if (y->op != CND_AND) return NULL;
+
+ /* recurse left and then right */
+ z = findMetrics(y->arg1);
+ if (z != NULL) return z;
+ return findMetrics(y->arg2);
+}
+
+/***********************************************************************
+ * satisfying bindings and values
+ ***********************************************************************/
+
+/* Find sub-expression that reveals host and instance bindings
+ that satisfy the given expression *x. */
+static Expr *
+findBindings(Expr *x)
+{
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "call findBindings(x=" PRINTF_P_PFX "%p)\n", x);
+ }
+#endif
+
+ if (x->metrics == NULL) {
+ /*
+ * this Expr node has no metrics (involves only constants)
+ * ... try and find a conjunct at the top level that has
+ * associated metrics
+ */
+ Expr *y = findMetrics(x->arg1);
+ if (y != NULL) x = y;
+ }
+ while (x->metrics && (x->e_idom <= 0 || x->hdom <= 0)) {
+ if (x->op == CND_SUM_HOST || x->op == CND_SUM_INST || x->op == CND_SUM_TIME ||
+ x->op == CND_AVG_HOST || x->op == CND_AVG_INST || x->op == CND_AVG_TIME ||
+ x->op == CND_MAX_HOST || x->op == CND_MAX_INST || x->op == CND_MAX_TIME ||
+ x->op == CND_MIN_HOST || x->op == CND_MIN_INST || x->op == CND_MIN_TIME ||
+ x->op == CND_COUNT_HOST || x->op == CND_COUNT_INST || x->op == CND_COUNT_TIME) {
+ /*
+ * don't descend below an aggregation operator with a singular
+ * value, ... value you seek is right here
+ */
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findBindings: found %s @ x=" PRINTF_P_PFX "%p\n", opStrings(x->op), x);
+ }
+#endif
+ break;
+ }
+ if (x->arg1 && x->metrics == x->arg1->metrics) {
+ x = x->arg1;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findBindings: try x->arg1=" PRINTF_P_PFX "%p\n", x);
+ }
+#endif
+ }
+ else if (x->arg2) {
+ x = x->arg2;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findBindings: try x->arg2=" PRINTF_P_PFX "%p\n", x);
+ }
+#endif
+ }
+ else
+ break;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findBindings finish @ " PRINTF_P_PFX "%p\n", x);
+ dumpTree(x);
+ }
+#endif
+ return x;
+}
+
+
+/* Find sub-expression that reveals the values that satisfy the
+ given expression *x. */
+static Expr *
+findValues(Expr *x)
+{
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "call findValues(x=" PRINTF_P_PFX "%p)\n", x);
+ }
+#endif
+ while (x->sem == SEM_BOOLEAN && x->metrics) {
+ if (x->metrics == x->arg1->metrics) {
+ x = x->arg1;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findValues: try x->arg1=" PRINTF_P_PFX "%p\n", x);
+ }
+#endif
+ }
+ else {
+ x = x->arg2;
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findValues: try x->arg2=" PRINTF_P_PFX "%p\n", x);
+ }
+#endif
+ }
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "findValues finish @ " PRINTF_P_PFX "%p\n", x);
+ dumpTree(x);
+ }
+#endif
+ return x;
+}
+
+
+/***********************************************************************
+ * format string
+ ***********************************************************************/
+
+/* Locate next %h, %i or %v in format string. */
+static int /* 0 -> not found, 1 -> host, 2 -> inst, 3 -> value */
+findFormat(char *format, char **pos)
+{
+ for (;;) {
+ if (*format == '\0')
+ return 0;
+ if (*format == '%') {
+ switch (*(format + 1)) {
+ case 'h':
+ *pos = format;
+ return 1;
+ case 'i':
+ *pos = format;
+ return 2;
+ case 'v':
+ *pos = format;
+ return 3;
+ }
+ }
+ format++;
+ }
+
+}
+
+
+/***********************************************************************
+ * exported functions
+ ***********************************************************************/
+
+void
+showSyntax(FILE *f, Symbol s)
+{
+ char *name = symName(s);
+ Expr *x = symValue(s);
+
+ fprintf(f, "%s =\n", name);
+ showSyn(f, x);
+ fprintf(f, ";\n\n");
+}
+
+
+void
+showSubsyntax(FILE *f, Symbol s)
+{
+ char *name = symName(s);
+ Expr *x = symValue(s);
+ Expr *x1;
+ Expr *x2;
+
+ fprintf(f, "%s (subexpression for %s, %s and %s bindings) =\n",
+ name, "%h", "%i", "%v");
+ x1 = findBindings(x);
+ x2 = findValues(x1);
+ showSyn(f, x2);
+ fprintf(f, "\n\n");
+}
+
+
+/* Print value of expression */
+void
+showValue(FILE *f, Expr *x)
+{
+ char *string = NULL;
+
+ string = showConst(x);
+ if (string) {
+ fputs(string, f);
+ free(string);
+ }
+ else
+ fputs("?", f);
+}
+
+
+/* Print value of expression together with any host and instance bindings */
+void
+showAnnotatedValue(FILE *f, Expr *x)
+{
+ char *string = NULL;
+ size_t length = 0;
+ char *host;
+ char *inst;
+ int i;
+
+ /* no annotation possible */
+ if ((x->e_idom <= 0 && x->hdom <= 0) ||
+ x->sem == SEM_CHAR ||
+ x->metrics == NULL ||
+ x->valid == 0) {
+ showValue(f, x);
+ return;
+ }
+
+ /* construct string representation */
+ for (i = 0; i < x->tspan; i++) {
+ length = concat("\n ", length, &string);
+ lookupHostInst(x, i, &host, &inst);
+ length = concat(host, length, &string);
+ if (inst) {
+ length = concat(": [", length, &string);
+ length = concat(inst, length, &string);
+ length = concat("] ", length, &string);
+ }
+ else
+ length = concat(": ", length, &string);
+ if (x->sem == SEM_BOOLEAN)
+ length = showBoolean(x, i, length, &string);
+ else /* numeric value */
+ length = showNum(x, i, length, &string);
+ }
+
+ /* print string representation */
+ if (string) {
+ fputs(string, f);
+ free(string);
+ }
+}
+
+
+void
+showTime(FILE *f, RealTime rt)
+{
+ time_t t = (time_t)rt;
+ char bfr[26];
+
+ pmCtime(&t, bfr);
+ bfr[24] = '\0';
+ fprintf(f, "%s", bfr);
+}
+
+
+void
+showFullTime(FILE *f, RealTime rt)
+{
+ time_t t = (time_t)rt;
+ char bfr[26];
+
+ pmCtime(&t, bfr);
+ bfr[24] = '\0';
+ fprintf(f, "%s.%06d", bfr, (int)((rt-t)*1000000));
+}
+
+
+void
+showSatisfyingValue(FILE *f, Expr *x)
+{
+ char *string = NULL;
+ size_t length = 0;
+ char *host;
+ char *inst;
+ int i;
+ Expr *x1;
+ Expr *x2;
+
+ /* no satisfying values possible */
+ if (x->metrics == NULL || x->valid == 0) {
+ showValue(f, x);
+ return;
+ }
+
+ if (x->sem != SEM_BOOLEAN) {
+ showAnnotatedValue(f, x);
+ return;
+ }
+
+ x1 = findBindings(x);
+ x2 = findValues(x1);
+ if (!x1->valid) {
+ /*
+ * subexpression for %h, %i and %v is not valid but rule is
+ * true, return string without substitution ... rare case
+ * for <bad-or-not evaluated expr> || <true expr> rule
+ */
+ concat(" <no bindings available>", length, &string);
+ goto done;
+ }
+
+ /* construct string representation */
+ for (i = 0; i < x1->tspan; i++) {
+ if ((x1->sem == SEM_BOOLEAN && *((char *)x1->smpls[0].ptr + i) == B_TRUE)
+ || (x1->sem != SEM_BOOLEAN && x1->sem != SEM_UNKNOWN)) {
+ length = concat("\n ", length, &string);
+ lookupHostInst(x1, i, &host, &inst);
+ length = concat(host, length, &string);
+ if (inst) {
+ length = concat(": [", length, &string);
+ length = concat(inst, length, &string);
+ length = concat("] ", length, &string);
+ }
+ else
+ length = concat(": ", length, &string);
+ if (x2->sem == SEM_BOOLEAN)
+ length = showBoolean(x2, i, length, &string);
+ else /* numeric value */
+ length = showNum(x2, i, length, &string);
+ }
+ }
+
+done:
+ /* print string representation */
+ if (string) {
+ fputs(string, f);
+ free(string);
+ }
+}
+
+
+/*
+ * Instantiate format string for each satisfying binding and value
+ * of the current rule ... enumerate and insert %h, %v and %v values
+ *
+ * WARNING: This is not thread safe, it dinks with the format string.
+ */
+size_t /* new length of string */
+formatSatisfyingValue(char *format, size_t length, char **string)
+{
+ char *host;
+ char *inst;
+ char *first;
+ char *prev;
+ char *next;
+ int i;
+ Expr *x1;
+ Expr *x2;
+ int sts1;
+ int sts2;
+
+ /* no formatting present? */
+ if ((sts1 = findFormat(format, &first)) == 0)
+ return concat(format, length, string);
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "formatSatisfyingValue: curr=" PRINTF_P_PFX "%p\n", curr);
+ dumpExpr(curr);
+ }
+#endif
+ x1 = findBindings(curr);
+ x2 = findValues(x1);
+ if (!x1->valid)
+ /*
+ * subexpression for %h, %i and %v is not valid but rule is
+ * true, return string without substitution ... rare case
+ * for <bad-or-not evaluated expr> || <true expr> rule
+ */
+ return concat(format, length, string);
+
+ for (i = 0; i < x1->tspan; i++) {
+ if ((x1->sem == SEM_BOOLEAN && *((char *)x1->smpls[0].ptr + i) == B_TRUE)
+ || (x1->sem != SEM_BOOLEAN && x1->sem != SEM_UNKNOWN)) {
+ prev = format;
+ next = first;
+ sts2 = sts1;
+ lookupHostInst(x1, i, &host, &inst);
+ do {
+ *next = '\0';
+ length = concat(prev, length, string);
+ *next = '%';
+
+ switch (sts2) {
+ case 1:
+ if (host)
+ length = concat(host, length, string);
+ else
+ length = concat("<%h undefined>", length, string);
+ break;
+ case 2:
+ if (inst)
+ length = concat(inst, length, string);
+ else
+ length = concat("<%i undefined>", length, string);
+ break;
+ case 3:
+ if (x2->sem == SEM_BOOLEAN)
+ length = showBoolean(x2, i, length, string);
+ else /* numeric value */
+ length = showNum(x2, i, length, string);
+ break;
+ }
+ prev = next + 2;
+ } while ((sts2 = findFormat(prev, &next)));
+ length = concat(prev, length, string);
+ }
+ }
+
+ return length;
+}
+
+char *
+opStrings(int op)
+{
+ int i;
+ /*
+ * sizing of "eh" is a bit tricky ...
+ * XXXXXXXXX is the number of digits in the largest possible value
+ * for "op", to handle the default "<unknown op %d>" case, but also
+ * "eh" must be long enough to accommodate the longest string from
+ * opstr[i].str ... currently "<action arg node>"
+ */
+ static char *eh = "<unknown op XXXXXXXXX>";
+
+ for (i = 0; i < numopstr; i++) {
+ if (opstr[i].op == op)
+ break;
+ }
+
+ if (i < numopstr)
+ return opstr[i].str;
+ else {
+ sprintf(eh, "<unknown op %d>", op);
+ return eh;
+ }
+}
diff --git a/src/pmie/src/show.h b/src/pmie/src/show.h
new file mode 100644
index 0000000..83341ac
--- /dev/null
+++ b/src/pmie/src/show.h
@@ -0,0 +1,32 @@
+/***********************************************************************
+ * show.h - output syntax and values
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include "dstruct.h"
+
+char *opStrings(int);
+
+void showSyntax(FILE *,Symbol);
+void showSubsyntax(FILE *, Symbol);
+void showValue(FILE *, Expr *);
+void showAnnotatedValue(FILE *, Expr *);
+void showSatisfyingValue(FILE *, Expr *);
+void showTime(FILE *, RealTime);
+void showFullTime(FILE *, RealTime);
+size_t formatSatisfyingValue(char *, size_t, char **);
diff --git a/src/pmie/src/stats.h b/src/pmie/src/stats.h
new file mode 100644
index 0000000..d3d819f
--- /dev/null
+++ b/src/pmie/src/stats.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 1999 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef STATS_H
+#define STATS_H
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+/* subdir nested under PCP_TMP_DIR */
+#define PMIE_SUBDIR "pmie"
+
+/* pmie performance instrumentation */
+typedef struct {
+ char config[MAXPATHLEN+1];
+ char logfile[MAXPATHLEN+1];
+ char defaultfqdn[MAXHOSTNAMELEN+1];
+ float eval_expected; /* pmcd.pmie.eval.expected */
+ unsigned int numrules; /* pmcd.pmie.numrules */
+ unsigned int actions; /* pmcd.pmie.actions */
+ unsigned int eval_true; /* pmcd.pmie.eval.true */
+ unsigned int eval_false; /* pmcd.pmie.eval.false */
+ unsigned int eval_unknown; /* pmcd.pmie.eval.unknown */
+ unsigned int eval_actual; /* pmcd.pmie.eval.actual */
+ unsigned int version;
+} pmiestats_t;
+
+#endif /* STATS_H */
diff --git a/src/pmie/src/stomp.c b/src/pmie/src/stomp.c
new file mode 100644
index 0000000..78a21b8
--- /dev/null
+++ b/src/pmie/src/stomp.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2006 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static int stomp_connect(const char *hostname, int port);
+static void stomp_disconnect(void);
+
+int stomping;
+char *stompfile;
+extern int verbose;
+
+static int fd = -1;
+static int port = -1;
+static int timeout = 2; /* default 2 sec to timeout JMS server ACKs */
+static char *hostname;
+static char *username;
+static char *passcode;
+static char *topic; /* JMS "topic" for pmie messages */
+static char pmietopic[] = "PMIE"; /* default JMS "topic" for pmie */
+
+static char buffer[4096];
+
+static int stomp_connect(const char *hostname, int port)
+{
+ __pmSockAddr *myaddr;
+ __pmHostEnt *servinfo;
+ void *enumIx;
+ struct timeval tv;
+ struct timeval *ptv;
+ __pmFdSet wfds;
+ int ret;
+ int flags = 0;
+
+ if ((servinfo = __pmGetAddrInfo(hostname)) == NULL)
+ return -1;
+
+ fd = -1;
+ enumIx = NULL;
+ for (myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx);
+ myaddr != NULL;
+ myaddr = __pmHostEntGetSockAddr(servinfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myaddr))
+ fd = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myaddr))
+ fd = __pmCreateIPv6Socket();
+ else
+ continue;
+ if (fd < 0) {
+ __pmSockAddrFree(myaddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ flags = __pmConnectTo(fd, myaddr, port);
+ __pmSockAddrFree(myaddr);
+
+ if (flags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address. fd has been closed in __pmConnectTo().
+ */
+ fd = -1;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ ptv = (tv.tv_sec || tv.tv_usec) ? &tv : NULL;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(fd, &wfds);
+ ret = __pmSelectWrite(fd+1, &wfds, ptv);
+
+ /* Was the connection successful? */
+ if (ret <= 0) {
+ if (oserror() == EINTR)
+ return -2;
+ continue;
+ }
+ ret = __pmConnectCheckError(fd);
+ if (ret == 0)
+ break;
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(fd);
+ fd = -1;
+ } /* loop over addresses */
+
+ __pmHostEntFree(servinfo);
+
+ if(fd == -1)
+ return -4;
+
+ fd = __pmConnectRestoreFlags(fd, flags);
+ if(fd < 0)
+ return -5;
+
+ return fd;
+}
+
+static int stomp_read_ack(void)
+{
+ struct timeval tv;
+ fd_set fds, readyfds;
+ int nready, sts;
+
+ FD_ZERO(&fds);
+ FD_SET(fd, &fds);
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ nready = select(fd + 1, &readyfds, NULL, NULL, &tv);
+ if (nready <= 0) {
+ if (nready == 0)
+ __pmNotifyErr(LOG_ERR, "Timed out waiting for server %s:%d - %s",
+ hostname, port, netstrerror());
+ else
+ __pmNotifyErr(LOG_ERR, "Error waiting for server %s:%d - %s",
+ hostname, port, netstrerror());
+ stomp_disconnect();
+ return -1;
+ }
+
+ do {
+ sts = recv(fd, buffer, sizeof(buffer), 0);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "Error recving from server %s:%d - %s",
+ hostname, port, netstrerror());
+ stomp_disconnect();
+ return -1;
+ }
+ /* check for anything else we need to read to clear this ACK */
+ memset(&tv, 0, sizeof(tv));
+ memcpy(&readyfds, &fds, sizeof(readyfds));
+ } while (select(fd + 1, &readyfds, NULL, NULL, &tv) > 0);
+
+ return 0;
+}
+
+static int stomp_write(const char *buffer, int length)
+{
+ int sts;
+
+ do {
+ sts = send(fd, buffer, length, 0);
+ if (sts < 0) {
+ __pmNotifyErr(LOG_ERR, "Write error to JMS server %s:%d - %s",
+ hostname, port, netstrerror());
+ stomp_disconnect();
+ return -1;
+ }
+ else if (sts == 0)
+ break;
+ length -= sts;
+ } while (length > 0);
+
+ return 0;
+}
+
+static int stomp_authenticate(void)
+{
+ int len;
+
+ if (fd < 0)
+ return -1;
+ len = snprintf(buffer, sizeof(buffer),
+ "CONNECT\nlogin:%s\npasscode:%s\n\n", username, passcode);
+ if (stomp_write(buffer, len) < 0)
+ return -1;
+ if (stomp_write("\0\n", 2) < 0)
+ return -1;
+ return 0;
+}
+
+static int stomp_destination(void)
+{
+ int len;
+
+ if (fd < 0)
+ return -1;
+ len = snprintf(buffer, sizeof(buffer),
+ "SUB\ndestination:/topic/%s\n\n", topic);
+ if (stomp_write(buffer, len) < 0)
+ return -1;
+ if (stomp_write("\0\n", 2) < 0)
+ return -1;
+ return 0;
+}
+
+static int stomp_hello(void)
+{
+ int len;
+
+ if (fd < 0)
+ return -1;
+ len = snprintf(buffer, sizeof(buffer), "SEND\ndestination:/topic/%s\n\n"
+ "INFO: PMIE: Established initial connection", topic);
+ if (stomp_write(buffer, len) < 0)
+ return -1;
+ if (stomp_write("\0\n", 2) < 0)
+ return -1;
+ return 0;
+}
+
+static void stomp_disconnect(void)
+{
+ if (fd >= 0)
+ close(fd);
+ fd = -1;
+}
+
+static char *isspace_terminate(char *string)
+{
+ int i = 0;
+
+ while (!isspace((int)string[i++])) /* do nothing */ ;
+ if (i)
+ string[i-1] = '\0';
+ return string;
+}
+
+/*
+ * Parse our stomp configuration file, simple format:
+ * host=<hostname> # JMS server machine
+ * port=<port#> # server port number
+ * username=<user> | user=<user>
+ * passcode=<password> | password=<password>
+ * timeout=<seconds> # optional
+ * topic=<JMStopic> # optional
+ */
+static void stomp_parse(void)
+{
+ char config[MAXPATHLEN+1];
+ FILE *f;
+ int sep = __pmPathSeparator();
+
+ if (stompfile)
+ strncat(config, stompfile, sizeof(config)-1);
+ else
+ snprintf(config, sizeof(config),
+ "%s%c" "config" "%c" "pmie" "%c" "stomp",
+ pmGetConfig("PCP_VAR_DIR"), sep, sep, sep);
+ if ((f = fopen(config, "r")) == NULL) {
+ __pmNotifyErr(LOG_ERR, "Cannot open STOMP configuration file %s: %s",
+ config, osstrerror());
+ exit(1);
+ }
+ while (fgets(buffer, sizeof(buffer), f)) {
+ if (strncmp(buffer, "port=", 5) == 0)
+ port = atoi(isspace_terminate(&buffer[5]));
+ else if (strncmp(buffer, "host=", 5) == 0)
+ hostname = strdup(isspace_terminate(&buffer[5]));
+ else if (strncmp(buffer, "hostname=", 9) == 0)
+ hostname = strdup(isspace_terminate(&buffer[9]));
+ else if (strncmp(buffer, "user=", 5) == 0)
+ username = strdup(isspace_terminate(&buffer[5]));
+ else if (strncmp(buffer, "username=", 9) == 0)
+ username = strdup(isspace_terminate(&buffer[9]));
+ else if (strncmp(buffer, "password=", 9) == 0)
+ passcode = strdup(isspace_terminate(&buffer[9]));
+ else if (strncmp(buffer, "passcode=", 9) == 0)
+ passcode = strdup(isspace_terminate(&buffer[9]));
+ else if (strncmp(buffer, "timeout=", 8) == 0) /* optional */
+ timeout = atoi(isspace_terminate(&buffer[8]));
+ else if (strncmp(buffer, "topic=", 6) == 0) /* optional */
+ topic = strdup(isspace_terminate(&buffer[6]));
+ }
+ fclose(f);
+
+ if (!hostname)
+ __pmNotifyErr(LOG_ERR, "No host in STOMP config file %s", config);
+ if (port == -1)
+ __pmNotifyErr(LOG_ERR, "No port in STOMP config file %s", config);
+ if (!username)
+ __pmNotifyErr(LOG_ERR, "No username in STOMP config file %s", config);
+ if (!passcode)
+ __pmNotifyErr(LOG_ERR, "No passcode in STOMP config file %s", config);
+ if (port == -1 || !hostname || !username || !passcode)
+ exit(1);
+}
+
+/*
+ * Setup the connection to the stomp server, and handle initial protocol
+ * negotiations (sending user/passcode over to the server in particular).
+ * Stomp protocol is clear text... (we don't need no stinkin' security!).
+ * Note: this routine is used for both the initial connection and also for
+ * any subsequent reconnect attempts.
+ */
+void stompInit(void)
+{
+ time_t thistime;
+ static time_t lasttime;
+ static int firsttime = 1;
+
+ if (firsttime) { /* initial connection attempt */
+ stomp_parse();
+ if (!topic)
+ topic = pmietopic;
+ atexit(stomp_disconnect);
+ } else { /* reconnect attempt, if not too soon */
+ time(&thistime);
+ if (thistime < lasttime + 60)
+ goto disconnect;
+ }
+
+ if (verbose)
+ __pmNotifyErr(LOG_INFO, "Connecting to %s, port %d", hostname, port);
+ if (stomp_connect(hostname, port) < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not connect to the message server");
+ goto disconnect;
+ }
+
+ if (verbose)
+ __pmNotifyErr(LOG_INFO, "Connected; sending stomp connect message");
+ if (stomp_authenticate() < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not sent STOMP CONNECT frame to server");
+ goto disconnect;
+ }
+
+ if (verbose)
+ __pmNotifyErr(LOG_INFO, "Sent; waiting for server ACK");
+ if (stomp_read_ack() < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not read STOMP ACK frame.");
+ goto disconnect;
+ }
+
+ if (verbose)
+ __pmNotifyErr(LOG_INFO, "ACK; sending initial PMIE topic and hello");
+ if (stomp_destination() < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not read TOPIC frame.");
+ goto disconnect;
+ }
+ if (stomp_hello() < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not send HELLO frame.");
+ goto disconnect;
+ }
+
+ if (verbose)
+ __pmNotifyErr(LOG_INFO, "Sent; waiting for server ACK");
+ if (stomp_read_ack() < 0) {
+ __pmNotifyErr(LOG_ERR, "Could not read STOMP ACK frame");
+ goto disconnect;
+ }
+
+ if (!firsttime)
+ __pmNotifyErr(LOG_INFO, "Reconnected to STOMP protocol server");
+ else if (verbose)
+ __pmNotifyErr(LOG_INFO, "Initial STOMP protocol setup complete");
+ firsttime = 0;
+ goto finished;
+
+disconnect:
+ stomp_disconnect();
+ if (firsttime)
+ exit(1);
+ /* otherwise, we attempt reconnect on next message firing (>1min) */
+finished:
+ lasttime = thistime;
+}
+
+/*
+ * Send a message to the stomp server.
+ */
+int stompSend(const char *msg)
+{
+ int len;
+
+ if (fd < 0) stompInit(); /* reconnect */
+ if (fd < -1) return -1;
+
+ len = snprintf(buffer, sizeof(buffer),
+ "SEND\ndestination:/topic/%s\n\n", topic);
+ if (stomp_write(buffer, len) < 0)
+ return -1;
+ if (stomp_write(msg, strlen(msg)) < 0)
+ return -1;
+ if (stomp_write("\0\n", 2) < 0)
+ return -1;
+ return 0;
+}
diff --git a/src/pmie/src/stomp.h b/src/pmie/src/stomp.h
new file mode 100644
index 0000000..b015efe
--- /dev/null
+++ b/src/pmie/src/stomp.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2006 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * Streaming Text Orientated Messaging Protocol implementation
+ * http://stomp.codehaus.org/
+ */
+extern int stomping; /* true if stomp actions present */
+extern char *stompfile; /* stomp config file */
+extern int stompInit(void); /* connect to stomp server */
+extern int stompSend(const char *); /* send to JMS server, via stomp */
diff --git a/src/pmie/src/symbol.c b/src/pmie/src/symbol.c
new file mode 100644
index 0000000..a27bcc4
--- /dev/null
+++ b/src/pmie/src/symbol.c
@@ -0,0 +1,264 @@
+/***********************************************************************
+ * symbol.c - a symbol is an object with a name, a value and a
+ * reference count
+ ***********************************************************************
+ *
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "symbol.h"
+#include "dstruct.h"
+#include "pmapi.h"
+
+
+/***********************************************************************
+ * constants
+ ***********************************************************************/
+
+/* bucket memory alignment, address mask and size */
+#undef ALIGN
+#define ALIGN 1024
+#undef MASK
+#define MASK (ALIGN - 1)
+#undef BSIZE
+#define BSIZE (ALIGN / sizeof(SymUnion))
+
+
+
+/***********************************************************************
+ * types
+ ***********************************************************************/
+
+typedef SymUnion Bucket[BSIZE];
+
+
+
+/***********************************************************************
+ * local functions
+ ***********************************************************************/
+
+static void
+addBucket(SymUnion *st)
+{
+ SymUnion *bckt = (SymUnion *) aalloc(ALIGN, sizeof(Bucket));
+ SymUnion *scoop = bckt + 1;
+
+ bckt->hdr.prev = st;
+ bckt->hdr.next = st->hdr.next;
+ bckt->hdr.free = scoop;
+ st->hdr.next->hdr.prev = bckt;
+ st->hdr.next = bckt;
+ scoop->entry.refs = 0;
+ scoop->entry.stat.free.ptr = NULL;
+ scoop->entry.stat.free.count = BSIZE - 1;
+}
+
+static void
+remBucket(SymUnion *bckt)
+{
+ SymUnion *prev = bckt->hdr.prev;
+ SymUnion *next = bckt->hdr.next;
+ SymUnion *scan;
+ int i = 1;
+
+ prev->hdr.next = next;
+ next->hdr.prev = prev;
+ while (i < BSIZE) {
+ scan = bckt + i;
+ if (scan->entry.refs == 0)
+ i += scan->entry.stat.free.count;
+ else {
+ free(scan->entry.stat.used.name);
+ i++;
+ }
+ }
+ free(bckt);
+}
+
+
+
+/***********************************************************************
+ * exported functions
+ ***********************************************************************/
+
+/* initialize symbol table */
+void
+symSetTable(SymbolTable *st)
+{
+ /* start with one clean bucket */
+ st->hdr.next = st;
+ st->hdr.prev = st;
+ addBucket(st);
+}
+
+
+/* reset symbol table */
+void
+symClearTable(SymbolTable *st)
+{
+ /* unchain all buckets and free storage */
+ while (st->hdr.next != st)
+ remBucket(st->hdr.next);
+ /* start with one clean bucket */
+ addBucket(st);
+}
+
+
+/* Convert string to symbol. A copy of the name string is made
+ on the heap for use by the symbol. */
+Symbol
+symIntern(SymbolTable *st, char *name)
+{
+ SymUnion *bckt;
+ SymUnion *scoop = NULL;
+ char *copy;
+ int i;
+
+ /* pick up existing symbol */
+ bckt = st->hdr.next;
+ while (bckt != st) {
+ i = 1;
+ while (i < BSIZE) {
+ scoop = bckt + i;
+ if (scoop->entry.refs) {
+ if (strcmp(name, scoop->entry.stat.used.name) == 0) {
+ scoop->entry.refs++;
+ return scoop;
+ }
+ i++;
+ }
+ else
+ i += scoop->entry.stat.free.count;
+ }
+ bckt = bckt->hdr.next;
+ }
+
+ /* pick up free entry */
+ bckt = st->hdr.next;
+ while (bckt != st) {
+ if ((scoop = bckt->hdr.free)) {
+ if (scoop->entry.stat.free.count > 1)
+ scoop += --scoop->entry.stat.free.count;
+ else
+ bckt->hdr.free = scoop->entry.stat.free.ptr;
+ break;
+ }
+ bckt = bckt->hdr.next;
+ }
+
+ /* no free entry - allocate new bucket */
+ if (scoop == NULL) {
+ addBucket(st);
+ scoop = st->hdr.next + 1;
+ scoop += --scoop->entry.stat.free.count;
+ }
+
+ /* initialize symbol */
+ scoop->entry.refs = 1;
+ copy = (char *) alloc(strlen(name) + 1);
+ strcpy(copy, name);
+ scoop->entry.stat.used.name = copy;
+ scoop->entry.stat.used.value = NULL;
+ return scoop;
+}
+
+
+/* lookup symbol by name */
+Symbol
+symLookup(SymbolTable *st, char *name)
+{
+ SymUnion *bckt;
+ SymUnion *scoop;
+ int i;
+
+ bckt = st->hdr.next;
+ while (bckt != st) {
+ i = 1;
+ while (i < BSIZE) {
+ scoop = bckt + i;
+ if (scoop->entry.refs) {
+ if (strcmp(name, scoop->entry.stat.used.name) == 0) {
+ scoop->entry.refs++;
+ return scoop;
+ }
+ i++;
+ }
+ else
+ i += scoop->entry.stat.free.count;
+ }
+ bckt = bckt->hdr.next;
+ }
+ return NULL;
+}
+
+
+/* copy symbol */
+Symbol
+symCopy(Symbol sym)
+{
+ sym->entry.refs++;
+ return sym;
+}
+
+
+/* remove reference to symbol */
+void
+symFree(Symbol sym)
+{
+ SymUnion *bckt;
+ SymUnion *lead, *lag;
+
+ if ((sym != SYM_NULL) && (--sym->entry.refs <= 0)) {
+
+ /* free up name string BUT NOT value */
+ free(sym->entry.stat.used.name);
+
+ /* find correct place in ordered free list */
+ bckt = (SymUnion *) ((char *) sym - ((long) sym & MASK));
+ lead = bckt->hdr.free;
+ lag = NULL;
+ while ((lead != NULL) && (lead < sym)) {
+ lag = lead;
+ lead = lead->entry.stat.free.ptr;
+ }
+
+ if (lag != NULL && (lag + lag->entry.stat.free.count) == sym) {
+ /* coalesce with preceding free block */
+ lag->entry.stat.free.count++;
+ sym = lag;
+ }
+ else {
+ /* link up as single free entry */
+ if (lag)
+ lag->entry.stat.free.ptr = sym;
+ else
+ bckt->hdr.free = sym;
+ sym->entry.stat.free.count = 1;
+ sym->entry.stat.free.ptr = lead;
+ }
+
+ if (sym + sym->entry.stat.free.count == lead) {
+ /* coalesce with following free block */
+ sym->entry.stat.free.count += lead->entry.stat.free.count;
+ sym->entry.stat.free.ptr = lead->entry.stat.free.ptr;
+ }
+ }
+}
+
+
diff --git a/src/pmie/src/symbol.h b/src/pmie/src/symbol.h
new file mode 100644
index 0000000..07e222a
--- /dev/null
+++ b/src/pmie/src/symbol.h
@@ -0,0 +1,93 @@
+/***********************************************************************
+ * symbol.h - a symbol is an object with a name, a value and a
+ * reference count
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef SYMBOL_H
+#define SYMBOL_H
+
+/***********************************************************************
+ * private
+ ***********************************************************************/
+
+union symunion; /* for forward reference */
+
+typedef struct {
+ union symunion *next; /* forward pointer */
+ union symunion *prev; /* backward pointer */
+ union symunion *free; /* free list head */
+} SymHdr;
+
+typedef struct {
+ union symunion *ptr; /* free list pointer */
+ int count; /* number of free entries */
+} SymFree;
+
+typedef struct {
+ char *name; /* name string */
+ void *value; /* arbitrary value */
+} SymUsed;
+
+typedef struct {
+ union {
+ SymFree free; /* free symbol table entry */
+ SymUsed used; /* occupied symbol table entry */
+ } stat;
+ int refs; /* refernce count */
+} SymEntry;
+
+typedef union symunion {
+ SymHdr hdr; /* symbol table or bucket header */
+ SymEntry entry; /* symbol or free slot */
+} SymUnion;
+
+
+/***********************************************************************
+ * public
+ ***********************************************************************/
+
+#define SYM_NULL NULL
+typedef SymUnion *Symbol;
+typedef SymUnion SymbolTable;
+
+/* access to name string, value and reference count */
+#define symName(sym) ((sym)->entry.stat.used.name)
+#define symValue(sym) ((sym)->entry.stat.used.value)
+#define symRefs(sym) ((sym)->entry.refs)
+
+/* initialize symbol table */
+void symSetTable(SymbolTable *);
+
+/* reset symbol table */
+void symClearTable(SymbolTable *);
+
+/* convert string to symbol */
+Symbol symIntern(SymbolTable *, char *);
+
+/* lookup symbol by name */
+Symbol symLookup(SymbolTable *, char *);
+
+/* copy symbol */
+Symbol symCopy(Symbol);
+
+/* remove reference to symbol */
+void symFree(Symbol);
+
+#endif /* SYMBOL_H */
+
diff --git a/src/pmie/src/syntax.c b/src/pmie/src/syntax.c
new file mode 100644
index 0000000..46edc27
--- /dev/null
+++ b/src/pmie/src/syntax.c
@@ -0,0 +1,781 @@
+/***********************************************************************
+ * syntax.c - inference rule language parser
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include "pmapi.h"
+#include "impl.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_REGEX_H
+#include <regex.h>
+#endif
+#include "dstruct.h"
+#include "symbol.h"
+#include "syntax.h"
+#include "lexicon.h"
+#include "grammar.h"
+#include "pragmatics.h"
+#include "eval.h"
+#include "show.h"
+
+
+
+/***********************************************************************
+ * constants
+ ***********************************************************************/
+
+#define NAMEGEN_MAX 12
+
+
+
+/***********************************************************************
+ * variables
+ ***********************************************************************/
+
+Symbol parse; /* result of parse */
+int errs; /* error count */
+
+
+/***********************************************************************
+ * miscellaneous local functions
+ ***********************************************************************/
+
+/* generate unique name, only good till next call */
+static char *
+nameGen(void)
+{
+ static int state = 0;
+ static char bfr[NAMEGEN_MAX];
+
+ state++;
+ snprintf(bfr, sizeof(bfr), "expr_%1d", state);
+
+ return bfr;
+}
+
+
+/* check domains cardinality agreement */
+static int
+checkDoms(Expr *x1, Expr *x2)
+{
+ if ((x1->nvals != 1) && (x2->nvals != 1)) {
+ if (x1->hdom != x2->hdom) {
+ synerr();
+ fprintf(stderr, "host domains have different size (%d and %d)\n",
+ x1->hdom, x2->hdom);
+ return 0;
+ }
+ if (x1->e_idom != x2->e_idom) {
+ synerr();
+ fprintf(stderr, "instance domains have different size (%d and %d)\n",
+ x1->e_idom, x2->e_idom);
+ return 0;
+ }
+ if (x1->tdom != x2->tdom) {
+ synerr();
+ fprintf(stderr, "time domains have different size (%d and %d)\n",
+ x1->tdom, x2->tdom);
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+/* evaluate constant expression */
+static void
+evalConst(Expr *x)
+{
+ if ((x->op < ACT_SHELL) &&
+ (x->arg1->op == NOP) &&
+ ((x->arg2 == NULL) || (x->arg2->op == NOP))) {
+ (x->eval)(x);
+ x->op = NOP;
+ x->eval = NULL;
+ freeExpr(x->arg1); x->arg1 = NULL;
+ freeExpr(x->arg2); x->arg2 = NULL;
+ }
+}
+
+
+
+/***********************************************************************
+ * error reporting
+ ***********************************************************************/
+
+static void
+report(char *msg)
+{
+ LexIn *x = lin;
+
+ fprintf(stderr, "%s: %s - ", pmProgname, msg);
+ if (x) {
+ fprintf(stderr, "near line %d of ", x->lno);
+ if (x->stream) {
+ if (x->name) fprintf(stderr, "file %s", x->name);
+ else fprintf(stderr, "standard input");
+ }
+ else {
+ fprintf(stderr, "macro $%s", x->name);
+ do x = x->prev;
+ while (x && x->stream == NULL);
+ if (x) {
+ fprintf(stderr, " called from ");
+ if (x->name) fprintf(stderr, "file %s", x->name);
+ else fprintf(stderr, "standard input");
+ }
+ }
+ }
+ else
+ fprintf(stderr, "at end of file");
+ putc('\n', stderr);
+}
+
+void
+synwarn(void)
+{
+ report("warning");
+}
+
+void
+synerr(void)
+{
+ report("syntax error");
+ errs++;
+}
+
+void
+yyerror(char *s)
+{
+ synerr();
+}
+
+
+/***********************************************************************
+ * post processing
+ * - propagate instance information from fetch expressions towards
+ * the root of the expression tree, allocating ring buffers etc.
+ * along the way.
+ ***********************************************************************/
+
+static void
+postExpr(Expr *x)
+{
+ if (x->op == CND_FETCH) {
+ instFetchExpr(x);
+ }
+ else {
+ if (x->arg1) {
+ postExpr(x->arg1);
+ if (x->arg2)
+ postExpr(x->arg2);
+ }
+ }
+}
+
+
+
+/***********************************************************************
+ * parser actions
+ ***********************************************************************/
+
+/* statement */
+Symbol
+statement(char *name, Expr *x)
+{
+ Symbol s;
+
+ /* error guard */
+ if (x == NULL) return NULL;
+
+ /* if name not given, make one up */
+ if (name == NULL) name = nameGen();
+
+ /* the parsed object is a rule (expression to evaluate) */
+ if (x->op != NOP) {
+ if (symLookup(&rules, name)) {
+ synerr();
+ fprintf(stderr, "rule \"%s\" multiply defined\n", name);
+ freeExpr(x);
+ return NULL;
+ }
+ else {
+ if (errs == 0) {
+ postExpr(x);
+ s = symIntern(&rules, name);
+ }
+ else return NULL;
+ }
+ }
+
+ /* the parsed object is a non-rule */
+ else {
+ if ( (s = symLookup(&vars, name)) )
+ freeExpr(symValue(s));
+ else
+ s = symIntern(&vars, name);
+ }
+
+ symValue(s) = x;
+ return s;
+}
+
+
+/* construct rule Expr */
+Expr *
+ruleExpr(Expr *cond, Expr *act)
+{
+ Expr *x;
+
+ if (cond == NULL) return act;
+ if (act == NULL) return cond;
+
+ if (cond->nvals != 1) {
+ synerr();
+ fprintf(stderr, "rule condition must have a single boolean value\n");
+ }
+
+ perf->numrules++;
+
+ x = newExpr(RULE, cond, act, -1, -1, -1, 1, SEM_BOOLEAN);
+ newRingBfr(x);
+ findEval(x);
+ return x;
+}
+
+
+/* action expression */
+Expr *
+actExpr(int op, Expr *arg1, Expr *arg2)
+{
+ Expr *x;
+
+ /* error guard */
+ if (arg1 == NULL) return NULL;
+
+ /* construct expression node */
+ x = newExpr(op, arg1, arg2, -1, -1, -1, 1, SEM_BOOLEAN);
+ newRingBfr(x);
+ findEval(x);
+
+ return x;
+}
+
+
+/* action argument expression */
+Expr *
+actArgExpr(Expr *arg1, Expr *arg2)
+{
+ Expr *x;
+
+ /* error guard */
+ if (arg1 == NULL) return NULL;
+
+ /* construct expression node */
+ x = newExpr(ACT_ARG, arg1, arg2, -1, 1, -1, 1, SEM_CHAR);
+ newRingBfr(x);
+ findEval(x);
+
+ return x;
+}
+
+/* action argument */
+Expr *
+actArgList(Expr *arg1, char *str)
+{
+ Expr *x;
+
+ /* construct expression node for an action argument string */
+ x = (Expr *) zalloc(sizeof(Expr));
+ x->op = NOP;
+ x->smpls[0].ptr = x->ring = sdup(str);
+ x->valid = x->nsmpls = x->nvals = 1;
+ x->tspan = strlen(str);
+ x->sem = SEM_CHAR;
+ if (arg1) {
+ x->arg1 = arg1;
+ arg1->parent = x;
+ }
+
+ return x;
+}
+
+/* relational operator expression */
+Expr *
+relExpr(int op, Expr *arg1, Expr *arg2)
+{
+ Expr *x;
+ Expr *arg;
+ int i;
+ int sts;
+
+ /* error guard */
+ if (arg1 == NULL || arg2 == NULL) return NULL;
+
+ /* check domains */
+ sts = checkDoms(arg1, arg2);
+
+ /* decide primary argument for inheritance of Expr attributes */
+ arg = primary(arg1, arg2);
+
+ /* construct expression node */
+ x = newExpr(op, arg1, arg2, arg->hdom, arg->e_idom, arg->tdom, abs(arg->tdom), SEM_BOOLEAN);
+#if PCP_DEBUG
+ if (sts == 0 && (pmDebug & DBG_TRACE_APPL1)) {
+ fprintf(stderr, "relExpr: checkDoms(" PRINTF_P_PFX "%p, " PRINTF_P_PFX "%p) failed ...\n", arg1, arg2);
+ __dumpTree(1, x);
+ }
+#endif
+ newRingBfr(x);
+ if (x->tspan > 0) {
+ for (i = 0; i < x->nsmpls; i++)
+ *((char *)x->ring + i) = B_UNKNOWN;
+ }
+ findEval(x);
+
+ /* evaluate constant expression now */
+ evalConst(x);
+
+ return x;
+}
+
+
+/* binary operator expression */
+Expr *
+binaryExpr(int op, Expr *arg1, Expr *arg2)
+{
+ Expr *x;
+ Expr *arg = arg1;
+ int sts = 0;
+
+ /* error guard */
+ if (arg1 == NULL) return NULL;
+
+ if (arg1 != NULL && arg2 != NULL) {
+ if (op != CND_MATCH && op != CND_NOMATCH) {
+ /* check domains */
+ sts = checkDoms(arg1, arg2);
+
+ /* decide primary argument for inheritance of Expr attributes */
+ arg = primary(arg1, arg2);
+ }
+ else {
+ regex_t *pat;
+
+ pat = alloc(sizeof(*pat));
+ if (regcomp(pat, (char *)arg2->ring, REG_EXTENDED|REG_NOSUB) != 0) {
+ /* bad pattern */
+ fprintf(stderr, "illegal regular expression \"%s\"\n", (char *)arg2->ring);
+ free(pat);
+ return NULL;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "binaryExpr: regex=\"%s\" handle=" PRINTF_P_PFX "%p\n", (char *)arg2->ring, pat);
+ }
+#endif
+ /*
+ * change operand from the string form of the pattern to the
+ * compiled regex
+ */
+ free(arg2->ring);
+ arg2->tspan = 1;
+ arg2->ring = pat;
+ arg2->sem = SEM_REGEX;
+ sts = 1;
+ }
+ }
+
+ /* construct expression node */
+ x = newExpr(op, arg1, arg2, arg->hdom, arg->e_idom, arg->tdom, abs(arg->tdom), arg->sem);
+#if PCP_DEBUG
+ if (sts == 0 && (pmDebug & DBG_TRACE_APPL1)) {
+ fprintf(stderr, "binaryExpr: checkDoms(" PRINTF_P_PFX "%p, " PRINTF_P_PFX "%p) failed ...\n", arg1, arg2);
+ __dumpTree(1, x);
+ }
+#endif
+ newRingBfr(x);
+ findEval(x);
+
+ /* evaluate constant expression now */
+ evalConst(x);
+
+ return x;
+}
+
+
+/* unary operator expression */
+Expr *
+unaryExpr(int op, Expr *arg)
+{
+ Expr *x;
+
+ /* error guard */
+ if (arg == NULL) return NULL;
+
+ /* construct expression node */
+ x = newExpr(op, arg, NULL, arg->hdom, arg->e_idom, arg->tdom, abs(arg->tdom), arg->sem);
+ newRingBfr(x);
+ findEval(x);
+
+ /* evaluate constant expression now */
+ evalConst(x);
+
+ return x;
+}
+
+
+/* aggregation/quantification operator expression */
+Expr *
+domainExpr(int op, int dom, Expr *arg)
+{
+ Expr *x;
+ int hdom;
+ int idom;
+ int tdom;
+
+ /* error guard */
+ if (arg == NULL) return NULL;
+
+ hdom = arg->hdom;
+ idom = arg->e_idom;
+ tdom = arg->tdom;
+
+ switch (dom) {
+ case HOST_DOM:
+ if (hdom == -1) {
+ synerr();
+ fprintf(stderr, "no host domain\n");
+ freeExpr(arg);
+ return NULL;
+ }
+ hdom = -1;
+ idom = -1;
+ dom = 0;
+ break;
+ case INST_DOM:
+#if 0
+ /*
+ * I believe this test is no longer correct ... the instance
+ * domain may be unobtainable at this point, or may change
+ * later so checking at the syntactic level is not helpful
+ */
+ if (idom == -1) {
+ synerr();
+ fprintf(stderr, "no instance domain\n");
+ freeExpr(arg);
+ return NULL;
+ }
+#endif
+ idom = -1;
+ dom = 1;
+ break;
+ case TIME_DOM:
+ if (tdom == -1) {
+ synerr();
+ fprintf(stderr, "no time domain\n");
+ freeExpr(arg);
+ return NULL;
+ }
+ tdom = -1;
+ dom = 2;
+ }
+
+ if (op == CND_COUNT_HOST) {
+ x = newExpr(op + dom, arg, NULL, hdom, idom, tdom, abs(tdom), PM_SEM_INSTANT);
+ newRingBfr(x);
+ x->units = countUnits;
+ }
+ else {
+ x = newExpr(op + dom, arg, NULL, hdom, idom, tdom, abs(tdom), arg->sem);
+ newRingBfr(x);
+ }
+
+ findEval(x);
+ return x;
+}
+
+
+/* percentage quantifier */
+Expr *
+percentExpr(double pcnt, int dom, Expr *arg)
+{
+ Expr *x;
+
+ /* error guard */
+ if (arg == NULL) return NULL;
+
+ x = domainExpr(CND_PCNT_HOST, dom, arg);
+ x->arg2 = newExpr(NOP, NULL, NULL, -1, -1, -1, 1, SEM_NUMCONST);
+ newRingBfr(x->arg2);
+ *(double *)x->arg2->ring = pcnt / 100.0;
+ x->arg2->valid = 1;
+ return x;
+}
+
+
+/* merge expression */
+static Expr *
+mergeExpr(int op, Expr *arg)
+{
+ Expr *x;
+
+ /* force argument to buffer at least two samples */
+ if (arg->nsmpls < 2)
+ changeSmpls(&arg, 2);
+
+ /* construct expression node */
+ x = newExpr(op, arg, NULL, arg->hdom, arg->e_idom, arg->tdom, abs(arg->tdom), arg->sem);
+ newRingBfr(x);
+ findEval(x);
+ return x;
+}
+
+
+/* numeric merge expression */
+Expr *
+numMergeExpr(int op, Expr *arg)
+{
+ /* error guard */
+ if (arg == NULL) return NULL;
+
+ /* check argument semantics */
+ if ((arg->sem == SEM_BOOLEAN) || (arg->sem == SEM_CHAR)) {
+ synerr();
+ fprintf(stderr, "operator \"%s\" requires numeric valued argument\n", opStrings(op));
+ return NULL;
+ }
+
+ return mergeExpr(op, arg);
+}
+
+
+/* boolean merge expression */
+Expr *
+boolMergeExpr(int op, Expr *arg)
+{
+ /* error guard */
+ if (arg == NULL) return NULL;
+
+ /* check argument semantics */
+ if (arg->sem != SEM_BOOLEAN) {
+ synerr();
+ fprintf(stderr, "operator \"%s\" requires boolean valued argument\n", opStrings(op));
+ return NULL;
+ }
+
+ return mergeExpr(op, arg);
+}
+
+
+/* fetch expression */
+Expr *
+fetchExpr(char *mname,
+ StringArray hnames,
+ StringArray inames,
+ Interval times)
+{
+ Expr *x;
+ Metric *marr, *m;
+ int fsz, dsz;
+ int sum;
+ int i;
+
+ /* calculate samplecounts for fetch and delay */
+ if (times.t1 == 0) {
+ fsz = times.t2 - times.t1 + 1;
+ dsz = 0;
+ }
+ else {
+ fsz = times.t1 + 1;
+ dsz = times.t2 - times.t1 + 1;
+ }
+
+ /* default host */
+ if (hnames.n == 0) {
+ hnames.n = 1;
+ }
+
+ /* construct Metrics array */
+ marr = m = (Metric *) zalloc(hnames.n * sizeof(Metric));
+ sum = 0;
+ for (i = 0; i < hnames.n; i++) {
+ m->mname = symIntern(&metrics, mname);
+ if (hnames.ss) m->hname = symIntern(&hosts, hnames.ss[i]);
+ else m->hname = symIntern(&hosts, dfltHostName);
+ m->desc.sem = SEM_UNKNOWN;
+ m->m_idom = -1;
+ if (inames.n > 0) {
+ m->specinst = inames.n;
+ m->iids = alloc(inames.n * sizeof(int));
+ m->inames = inames.ss;
+ }
+ else {
+ m->specinst = 0;
+ m->iids = NULL;
+ m->inames = NULL;
+ }
+ if (errs == 0) {
+ int sts = initMetric(m);
+ if (sts < 0) errs++;
+ if (m->m_idom > 0)
+ sum += m->m_idom;
+ }
+ m++;
+ }
+ if (sum == 0)
+ sum = -1;
+
+ /* error exit */
+ if (errs) {
+ m = marr;
+ for (i = 0; i < hnames.n; i++) {
+ if (m->iids) free(m->iids);
+ m++;
+ }
+ free(marr);
+ return NULL;
+ }
+
+ /* construct fetch node */
+ x = newExpr(CND_FETCH, NULL, NULL, hnames.n, sum, fsz, fsz, SEM_UNKNOWN);
+ newRingBfr(x);
+ x->metrics = marr;
+ findEval(x);
+ instFetchExpr(x);
+
+ /* patch in fetch node reference in each Metric */
+ m = marr;
+ for (i = 0; i < hnames.n; i++) {
+ m->expr = x;
+ m++;
+ }
+
+ /* construct delay node */
+ if (dsz) {
+ x = newExpr(CND_DELAY, x, NULL, x->hdom, x->e_idom, dsz, dsz, SEM_UNKNOWN);
+ newRingBfr(x);
+ findEval(x);
+ }
+ return x;
+}
+
+
+/* numeric constant */
+Expr *
+numConst(double v, pmUnits u)
+{
+ Expr *x;
+
+ x = newExpr(NOP, NULL, NULL, -1, -1, -1, 1, SEM_NUMCONST);
+ newRingBfr(x);
+ x->units = canon(u);
+ x->valid = 1;
+ *(double *) x->ring = scale(u) * v;
+ return x;
+}
+
+
+/* string constant */
+Expr *
+strConst(char *s)
+{
+ Expr *x;
+ int n = (int) strlen(s) + 1;
+
+ x = newExpr(NOP, NULL, NULL, -1, -1, -1, 1, SEM_CHAR);
+ x->valid = 1;
+ x->tspan = n;
+ newRingBfr(x);
+ strcpy((char *)x->ring, s);
+ return x;
+}
+
+
+/* boolean constant */
+Expr *
+boolConst(Boolean v)
+{
+ Expr *x;
+
+ x = newExpr(NOP, NULL, NULL, -1, -1, -1, 1, SEM_BOOLEAN);
+ newRingBfr(x);
+ x->valid = 1;
+ *(Boolean *) x->ring = v;
+ return x;
+}
+
+
+/* numeric valued variable */
+Expr *
+numVar(Expr *x)
+{
+ if (x->sem == SEM_BOOLEAN || x->sem == SEM_CHAR) {
+ synerr();
+ fprintf(stderr, "numeric valued variable expected\n");
+ return NULL;
+ }
+ return x;
+}
+
+
+/* truth valued variable */
+Expr *
+boolVar(Expr *x)
+{
+ if (x->sem == SEM_CHAR) {
+ synerr();
+ fprintf(stderr, "truth valued variable expected\n");
+ return NULL;
+ }
+ return x;
+}
+
+
+/***********************************************************************
+ * parser
+ ***********************************************************************/
+
+/* Initialization to be called at the start of new input file. */
+int
+synInit(char *fname)
+{
+ return lexInit(fname);
+}
+
+
+/* parse single statement */
+Symbol
+syntax(void)
+{
+ while (lexMore()) {
+ errs = 0;
+ parse = NULL;
+ yyparse();
+ if (parse)
+ return parse;
+ }
+ lexFinal();
+ return NULL;
+}
+
+
+
diff --git a/src/pmie/src/syntax.h b/src/pmie/src/syntax.h
new file mode 100644
index 0000000..f01228f
--- /dev/null
+++ b/src/pmie/src/syntax.h
@@ -0,0 +1,97 @@
+/***********************************************************************
+ * syntax.h - inference rule language parser
+ ***********************************************************************
+ *
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef SYNTAX_H
+#define SYNTAX_H
+
+
+/***********************************************************************
+ * ONLY FOR USE BY: lexicon.c grammar.y
+ ***********************************************************************/
+
+typedef struct {
+ int n; /* number of elements */
+ char **ss; /* dynamically allocated array */
+} StringArray;
+
+typedef struct {
+ int t1; /* start of interval */
+ int t2; /* end of interval */
+} Interval;
+
+/* parser stack entry */
+typedef union {
+ int i;
+ char *s;
+ double d;
+ StringArray sa;
+ Interval t;
+ pmUnits u;
+ Metric *m;
+ Expr *x;
+ Symbol *z;
+} YYSTYPE;
+#define YYSTYPE_IS_DECLARED 1
+
+extern YYSTYPE yylval;
+
+/* error reporting */
+extern int errs; /* error count */
+void yyerror(char *);
+void synerr(void);
+void synwarn(void);
+
+/* parser actions */
+Symbol statement(char *, Expr *);
+Expr *ruleExpr(Expr *, Expr *);
+Expr *relExpr(int, Expr *, Expr *);
+Expr *binaryExpr(int, Expr *, Expr *);
+Expr *unaryExpr(int, Expr *);
+Expr *domainExpr(int, int, Expr *);
+Expr *percentExpr(double, int, Expr *);
+Expr *numMergeExpr(int, Expr *);
+Expr *boolMergeExpr(int, Expr *);
+Expr *fetchExpr(char *, StringArray, StringArray, Interval);
+Expr *numConst(double, pmUnits);
+Expr *strConst(char *);
+Expr *boolConst(Boolean);
+Expr *numVar(Expr *);
+Expr *boolVar(Expr *);
+Expr *actExpr(int, Expr *, Expr *);
+Expr *actArgExpr(Expr *, Expr *);
+Expr *actArgList(Expr *, char *);
+
+/* parse tree */
+extern Symbol parse;
+
+
+
+/***********************************************************************
+ * public
+ ***********************************************************************/
+
+/* Initialization to be called at the start of new input file. */
+int synInit(char *);
+
+/* parse single statement */
+Symbol syntax(void);
+
+#endif /* SYNTAX_H */
+
diff --git a/src/pmie/src/systemlog.c b/src/pmie/src/systemlog.c
new file mode 100644
index 0000000..a493841
--- /dev/null
+++ b/src/pmie/src/systemlog.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 1999-2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#define SYSLOG_NAMES
+#include "dstruct.h"
+#include "eval.h"
+#include "syntax.h"
+#include "systemlog.h"
+#include "pmapi.h"
+
+#if defined(IS_SOLARIS) || defined(IS_AIX) || defined(IS_MINGW)
+#include "logger.h"
+#endif
+
+/*
+ * based on source for logger(1)
+ */
+static int
+decode(char *name, CODE *codetab)
+{
+ CODE *c;
+
+ if (isdigit((int)*name))
+ return (atoi(name));
+
+ for (c = codetab; c->c_name; c++)
+ if (!strcasecmp(name, c->c_name))
+ return (c->c_val);
+
+ return (-1);
+}
+
+/*
+ * Decode a symbolic name to a numeric value
+ * ... based on source for logger(1)
+ */
+static int
+pencode(char *s)
+{
+ char *save;
+ int fac, lev;
+
+ save = s;
+ while (*s && *s != '.') ++s;
+ if (*s) {
+ *s = '\0';
+ fac = decode(save, facilitynames);
+ if (fac < 0) {
+ synwarn();
+ fprintf(stderr, "Ignoring unknown facility (%s) for -p in syslog action\n", save);
+ fac = 0;
+ }
+ s++;
+ }
+ else {
+ fac = 0;
+ s = save;
+ }
+ lev = decode(s, prioritynames);
+ if (lev < 0) {
+ synwarn();
+ fprintf(stderr, "Ignoring unknown priority (%s) for -p in syslog action\n", s);
+ lev = LOG_NOTICE;
+ }
+ return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK));
+}
+
+/*
+ * handle splitting of -t tag and -p prio across one or two
+ * arguments to the syslog action in the rule
+ */
+static void
+nextch(char **p, Expr **x)
+{
+ static char end = '\0';
+ (*p)++;
+ while (**p == '\0') {
+ if ((*x)->arg1 == NULL) {
+ *p = &end;
+ return;
+ }
+ *x = (*x)->arg1;
+ *p = (*x)->ring;
+ }
+}
+
+/*
+ * post-process the expression tree for a syslog action to gather
+ * any -t tag or -p pri options (as for logger(1)) and build the
+ * encoded equivalent as a new expression accessed via arg2 from
+ * the head of the arguments list
+ */
+void
+do_syslog_args(Expr *act)
+{
+ int pri = -1;
+ char *tag = NULL;
+ char *p;
+ char *q;
+ Expr *others;
+ Expr *tmp;
+ Expr *new;
+
+ /*
+ * scan for -p pri and -t tag
+ */
+ for (others = act->arg1; others != NULL; ) {
+ if (others->ring == NULL) break;
+ p = others->ring;
+ if (*p != '-') break;
+ nextch(&p, &others);
+ if (*p == 'p' && pri == -1) {
+ nextch(&p, &others);
+ while (*p && isspace((int)*p)) nextch(&p, &others);
+ if (*p == '\0') {
+ synwarn();
+ fprintf(stderr, "Missing [facility.]priority after -p in syslog action\n");
+ }
+ else {
+ q = p+1;
+ while (*q && !isspace((int)*q)) q++;
+ if (*q) {
+ synwarn();
+ fprintf(stderr, "Ignoring extra text (%s) after -p pri in syslog action\n", q);
+ *q = '\0';
+ }
+ pri = pencode(p);
+ }
+ }
+ else if (*p == 't' && tag == NULL) {
+ nextch(&p, &others);
+ while (*p && isspace((int)*p)) nextch(&p, &others);
+ if (*p == '\0') {
+ synwarn();
+ fprintf(stderr, "Missing tag after -t in syslog action\n");
+ }
+ else {
+ q = p+1;
+ while (*q && !isspace((int)*q)) q++;
+ if (*q) {
+ synwarn();
+ fprintf(stderr, "Ignoring extra text (%s) after -t tag in syslog action\n", q);
+ *q = '\0';
+ }
+ tag = p;
+ }
+ }
+ else
+ break;
+ others = others->arg1;
+ }
+
+ /* defaults if -t and/or -p not seen */
+ if (pri < 0) pri = LOG_NOTICE;
+ if (tag == NULL) tag = "pcp-pmie";
+
+ /*
+ * construct new arg2 argument node, with
+ * ring -> pri (int) and tag (char *) concatenated
+ */
+ new = (Expr *) zalloc(sizeof(Expr));
+ new->op = NOP;
+ new->ring = (char *)alloc(sizeof(int)+strlen(tag)+1);
+ *((int *)new->ring) = pri;
+ strcpy(&((char *)new->ring)[sizeof(int)], tag);
+ act->arg2 = new;
+ new->parent = act;
+
+ /* free old argument nodes used for -p and/or -t specifications */
+ for (tmp = act->arg1; tmp != others; ) {
+ if (tmp->ring) {
+ free(tmp->ring);
+ }
+ new = tmp->arg1;
+ free(tmp);
+ tmp = new;
+ }
+
+ /* re-link remaining argument nodes */
+ if (others != act->arg1) {
+ act->arg1 = others;
+ if (others != NULL)
+ others->parent = act;
+ }
+
+}
diff --git a/src/pmie/src/systemlog.h b/src/pmie/src/systemlog.h
new file mode 100644
index 0000000..fba5151
--- /dev/null
+++ b/src/pmie/src/systemlog.h
@@ -0,0 +1 @@
+void do_syslog_args(Expr *);
diff --git a/src/pmie/src/unary.sk b/src/pmie/src/unary.sk
new file mode 100644
index 0000000..cbf3839
--- /dev/null
+++ b/src/pmie/src/unary.sk
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/***********************************************************************
+ * skeleton: unary.sk - unary operator
+ ***********************************************************************/
+
+/*
+ * operator: @FUN
+ */
+
+#define @OP
+
+void
+@FUN_n(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is = &arg1->smpls[0];
+ Sample *os = &x->smpls[0];
+ @ITYPE *ip;
+ @OTYPE *op;
+ int n;
+ int i;
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid && x->tspan > 0) {
+ ip = (@ITYPE *) is->ptr;
+ op = (@OTYPE *) os->ptr;
+ n = x->tspan;
+ for (i = 0; i < n; i++) {
+ *op = OP(*ip);
+ op++;
+ ip++;
+ }
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_n(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+void
+@FUN_1(Expr *x)
+{
+ Expr *arg1 = x->arg1;
+ Sample *is = &arg1->smpls[0];
+ Sample *os = &x->smpls[0];
+
+ EVALARG(arg1)
+ ROTATE(x)
+
+ if (arg1->valid) {
+ *(@OTYPE *)os->ptr = OP(*(@ITYPE *)is->ptr);
+ os->stamp = is->stamp;
+ x->valid++;
+ }
+ else x->valid = 0;
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "@FUN_1(" PRINTF_P_PFX "%p) ...\n", x);
+ dumpExpr(x);
+ }
+#endif
+}
+
+#undef OP
+
diff --git a/src/pmie/stomp b/src/pmie/stomp
new file mode 100644
index 0000000..7c93fec
--- /dev/null
+++ b/src/pmie/stomp
@@ -0,0 +1,11 @@
+#
+# Sample STOMP configuration file, parameters affecting connection
+# between pmie and a JMS server for the "stomp" rule action.
+#
+
+host=foo.bar.com # this is the JMS server (required)
+port=61616 # and its listening here (required)
+timeout=2 # seconds to wait for server (optional)
+topic=PMIE # JMS topic for pmie messages (optional)
+username=joe # required
+password=j03ST0MP # required
diff --git a/src/pmieconf/GNUmakefile b/src/pmieconf/GNUmakefile
new file mode 100644
index 0000000..9f9c639
--- /dev/null
+++ b/src/pmieconf/GNUmakefile
@@ -0,0 +1,81 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+MKFILE_SUBDIRS = cpu filesys memory percpu pernetif global
+
+SUBDIRS = $(MKFILE_SUBDIRS)
+
+CMDTARGET = pmieconf$(EXECSUFFIX)
+CFILES = pmieconf.c rules.c io.c
+HFILES = rules.h
+
+PMLOGCONF_TOOLS = $(PCP_VAR_DIR)/config/pmlogconf/tools
+
+LSRCFILES = GNUmakefile.rules check-rules pmie_email xtractnames $(RFILES)
+
+LLDLIBS = $(PCPLIB)
+LCFLAGS = -I$(TOPDIR)/src/pmie/src
+LDIRT = local $(CMDTARGET) rate-syscalls \
+ pmlogconf.tmp pmlogconf \
+ cpu/GNUmakefile filesys/GNUmakefile memory/GNUmakefile \
+ percpu/GNUmakefile pernetif/GNUmakefile
+LDIRDIRT = rules .pcp
+
+default: $(CMDTARGET) makefiles local pmlogconf
+
+# for src-link-pcp target from buildrules
+$(SUBDIRS): makefiles
+
+$(CMDTARGET): $(OBJECTS)
+
+pmieconf.o rules.o: rules.h
+
+.NOTPARALLEL:
+makefiles:
+ @for d in $(MKFILE_SUBDIRS); do \
+ rm -f $$d/GNUmakefile; \
+ cd $$d; \
+ $(LN_S) ../GNUmakefile.rules GNUmakefile; \
+ cd ..; \
+ done
+
+local: $(SUBDIRS)
+ @rm -fr rules; mkdir rules
+ $(SUBDIRS_MAKERULE)
+ $(RUN_IN_BUILD_ENV) ./$(CMDTARGET) -F -r rules -f local
+
+pmlogconf: $(SUBDIRS)
+ @rm -f pmlogconf
+ @echo "#pmlogconf-setup 2.0" >pmlogconf
+ @echo "ident metrics used by pmie(1) rules from the pmieconf(1) command" >>pmlogconf
+ @echo "force available" >>pmlogconf
+ $(SUBDIRS_MAKERULE) | grep -v '===' >pmlogconf.tmp
+ @$(PCP_SORT_PROG) -u pmlogconf.tmp | sed -e 's/^/ /' >>pmlogconf
+
+install: default $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -m 755 pmie_email $(PCP_BINADM_DIR)/pmie_email
+ $(INSTALL) -m 644 pmlogconf $(PMLOGCONF_TOOLS)/pmieconf
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/pmieconf/GNUmakefile.rules b/src/pmieconf/GNUmakefile.rules
new file mode 100644
index 0000000..6a12eba
--- /dev/null
+++ b/src/pmieconf/GNUmakefile.rules
@@ -0,0 +1,55 @@
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+# localdefs needs to set ALL_RULES (all the rules files to be included
+# in the builds and tar balls) and set LOCAL_RULES (the subset of the
+# rules that will work on the TARGET_OS platform)
+#
+include localdefs
+
+WORKDIR := $(shell pwd)
+GROUP := $(shell basename $(WORKDIR))
+RULESDIR = $(PCP_VAR_DIR)/config/pmieconf
+
+LDIRT = GNUmakefile
+
+LSRCFILES = localdefs $(ALL_RULES)
+
+CONFIGS = $(subst "./","",$(LOCAL_RULES))
+
+default_pcp: $(LOCAL_RULES)
+
+install_pcp: install
+
+install: default_pcp
+ $(INSTALL) -d $(RULESDIR)/$(GROUP)
+ @for f in $(CONFIGS); do \
+ $(INSTALL) -m 644 $$f $(RULESDIR)/$(GROUP)/$$f; \
+ done
+
+local:
+ @test ! -d ../rules/$(GROUP) && mkdir -p ../rules/$(GROUP); exit 0
+ @rm -f ../rules/$(GROUP)/*
+ @for f in IGNORE_DUMMY_RULE $(LOCAL_RULES); do \
+ [ $$f = IGNORE_DUMMY_RULE ] && continue; \
+ cp ../$(GROUP)/$$f ../rules/$(GROUP)/$$f; \
+ sed -e's|/usr/pcp/lib/pmie_email|$(PCP_BINADM_DIR)/pmie_email|' \
+ -e's|/usr/pcp/bin/pmpost|$(PCP_BINADM_DIR)/pmpost|' <$$f \
+ >../rules/$(GROUP)/$$f; \
+ done
+
+pmlogconf:
+ @if [ -n "$(CONFIGS)" ]; then sh ../xtractnames $(CONFIGS); fi
+
+include $(BUILDRULES)
diff --git a/src/pmieconf/check-rules b/src/pmieconf/check-rules
new file mode 100755
index 0000000..b61421d
--- /dev/null
+++ b/src/pmieconf/check-rules
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+# Once the "rules" directory is populated, extract all the PCP metric
+# names and make sure they are available on the current host or the
+# host specified by -h hostname
+#
+
+_usage()
+{
+ echo "Usage: $0 [-h hostname]"
+ exit 1
+}
+
+source=''
+while getopts "h:?" c
+do
+ case $c
+ in
+ h)
+ source="-h $OPTARG"
+ ;;
+ ?)
+ _usage
+ # NOTREACHED
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+if [ $# -ne 0 ]
+then
+ _usage
+ # NOTREACHED
+fi
+
+if [ ! -d rules ]
+then
+ echo "$0: cannot find \"rules\" directory!"
+ exit 1
+fi
+
+find rules -follow -type f -print \
+| LC_COLLATE=POSIX sort \
+| while read rule
+do
+ badrule=false
+ ./xtractnames <$rule \
+ | while read metric
+ do
+
+ probe=`pmprobe $source $metric`
+ numval=`echo $probe | awk '{print $2}'`
+ if [ $numval -lt 0 ]
+ then
+ if $badrule
+ then
+ :
+ else
+ echo
+ echo "$rule"
+ echo "#"
+ echo "# rule: `basename $rule`"
+ badrule=true
+ fi
+ echo "# $probe"
+ fi
+ done
+done
diff --git a/src/pmieconf/cpu/context_switch b/src/pmieconf/cpu/context_switch
new file mode 100644
index 0000000..fc2b8ac
--- /dev/null
+++ b/src/pmieconf/cpu/context_switch
@@ -0,0 +1,59 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.context_switch
+ summary = "$rule$"
+ predicate =
+"some_host (
+ kernel.all.pswitch $hosts$
+ > hinv.ncpu $hosts$ * $threshold$ count/sec
+)"
+ enabled = yes
+ version = 1
+ help =
+"Average number of context switches per CPU per second exceeded
+threshold over the past sample interval.";
+
+string rule
+ default = "High aggregate context switch rate"
+ modify = no
+ display = no;
+
+double threshold
+ default = 4000
+ help =
+"The threshold number of process context switches per second.";
+
+string action_expand
+ default = %vctxsw/s@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h aggregate context switches: %v/sec"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20005C"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.context_switch
+ display = no
+ modify = no;
+string enln_units
+ default = ctxsw/s
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/excess_fpe b/src/pmieconf/cpu/excess_fpe
new file mode 100644
index 0000000..90b62dc
--- /dev/null
+++ b/src/pmieconf/cpu/excess_fpe
@@ -0,0 +1,73 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.excess_fpe
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_host (
+ some_inst (
+ ( 100 * kernel.percpu.cpu.sys $hosts$ > $systime_util$ )
+ && kernel.percpu.syscall $hosts$ < $syscall_rate$
+ )
+)"
+ enabled = no
+ version = 1
+ help =
+"This predicate attempts to detect processes generating very large
+numbers of floating point exceptions (FPEs). Characteristic of
+this situation is heavy system time coupled with low system call
+rates (exceptions are delivered through the kernel to the process,
+taking some system time, but no system call is serviced on the
+application's behalf).";
+
+string rule
+ default = "Possible high floating point exception rate"
+ modify = no
+ display = no;
+
+percent systime_util
+ default = 50
+ help =
+"Threshold percentage for kernel CPU utilization, in the range 0
+(idle) to 100 (completely busy)";
+
+double syscall_rate
+ default = 100
+ help =
+"Threshold system call rate (calls per second) below which something
+is deemed amiss.";
+
+string action_expand
+ default = %v%sys[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h CPU: %i system mode: %v% and low syscall rate"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200041"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.excess_fpe
+ display = no
+ modify = no;
+string enln_units
+ default = %sys[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/load_average b/src/pmieconf/cpu/load_average
new file mode 100644
index 0000000..95b4c5d
--- /dev/null
+++ b/src/pmieconf/cpu/load_average
@@ -0,0 +1,73 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.load_average
+ summary = "$rule$"
+ predicate =
+"some_host (
+ // threshold scales with the number of CPUs (works better for
+ // large systems) and there is an absolute lower bound,
+ // especially for small systems
+ kernel.all.load $hosts$ #'1 minute' > hinv.ncpu $hosts$ * $per_cpu_load$
+ && kernel.all.load $hosts$ #'1 minute' > $min_load$
+)"
+ enabled = yes
+ version = 1
+ help =
+"The current 1-minute load average is higher than the larger of
+min_load and ( per_cpu_load times the number of CPUs ).
+The load average measures the number of processes that are running,
+runnable or soon to be runnable (i.e. in short term sleep).";
+
+string rule
+ default = "High 1-minute load average"
+ modify = no
+ display = no;
+
+double per_cpu_load
+ default = 3
+ help =
+"The multiplier per CPU for the minimum load to make the rule true,
+when expressed as a function of the number of CPUs. Typically in
+the range 1.0 (very light load) to 8.0 (very heavy load ).";
+
+double min_load
+ default = 4
+ help =
+"The minimum load average before the rule is true. Most useful for
+single-processor systems or where the desired threshold is
+absolute, rather than a function of the number of CPUs.";
+
+string action_expand
+ default = %vload@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h load average: %v"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200042"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.load_average
+ display = no
+ modify = no;
+string enln_units
+ default = load
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/localdefs b/src/pmieconf/cpu/localdefs
new file mode 100644
index 0000000..dbe7c39
--- /dev/null
+++ b/src/pmieconf/cpu/localdefs
@@ -0,0 +1,49 @@
+ALL_RULES = util syscall context_switch system load_average \
+ excess_fpe low_util
+
+LOCAL_RULES = $(ALL_RULES)
+
+# Metrics missing from Linux
+#
+# rule: excess_fpe
+# kernel.percpu.syscall -12357 Unknown metric name
+#
+# rule: syscall
+# kernel.all.syscall -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), linux)
+LOCAL_RULES = util context_switch system load_average low_util
+endif
+
+# Metrics missing from Mac OS X
+#
+# rule: util
+# kernel.all.cpu.intr -12357 Unknown metric name
+#
+# rule: syscall
+# kernel.all.syscall -12357 Unknown metric name
+#
+# rule: context_switch
+# kernel.all.pswitch -12357 Unknown metric name
+#
+# rule: excess_fpe
+# kernel.percpu.syscall -12357 Unknown metric name
+#
+# rule: low_util
+# kernel.all.cpu.intr -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), darwin)
+LOCAL_RULES = system load_average
+endif
+
+# Metrics missing from Solaris
+#
+# rule: low_util
+# kernel.all.cpu.intr -12357 Unknown metric name
+#
+# rule: util
+# kernel.all.cpu.intr -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), solaris)
+LOCAL_RULES = syscall context_switch system load_average excess_fpe
+endif
diff --git a/src/pmieconf/cpu/low_util b/src/pmieconf/cpu/low_util
new file mode 100644
index 0000000..de6c0da
--- /dev/null
+++ b/src/pmieconf/cpu/low_util
@@ -0,0 +1,69 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.low_util
+ summary = "$rule$"
+ predicate =
+"some_host (
+ // kernel.all.cpu metrics count up to 1 second of CPU time
+ // per second per CPU
+ 100 * ( kernel.all.cpu.user $hosts$ +
+ kernel.all.cpu.sys $hosts$ +
+ kernel.all.cpu.intr $hosts$ ) / hinv.ncpu $hosts$
+ < $threshold$
+)"
+ enabled = no
+ version = 1
+ help =
+"The average processor utilization over all CPUs was below threshold
+percent during the last sample interval.
+This rule is effectively the opposite of cpu.util and is disabled by
+default - it is only useful in specialized environments where, for
+example, processing is batch oriented and low processor utilization
+is indicative of poor use of system resources. In such a situation
+the cpu.low_util rule should be enabled, and cpu.util disabled.";
+
+string rule
+ default = "Low average processor utilization"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 25
+ help =
+"Lower bound percentage for CPU utilization, in the range 0 (idle)
+to 100 (completely busy), independent of the number of CPUs.";
+
+string action_expand
+ default = %v%util@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h average CPU utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20005E"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.low_util
+ display = no
+ modify = no;
+string enln_units
+ default = %util
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/syscall b/src/pmieconf/cpu/syscall
new file mode 100644
index 0000000..4dcf074
--- /dev/null
+++ b/src/pmieconf/cpu/syscall
@@ -0,0 +1,69 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.syscall
+ summary = "$rule$"
+ predicate =
+"some_host (
+ kernel.all.syscall $hosts$
+ > hinv.ncpu $hosts$ * $threshold$ count/sec
+)"
+ enabled = yes
+ version = 1
+ help =
+"Average number of system calls per CPU per second exceeded
+threshold over the past sample interval.";
+
+string rule
+ default = "High aggregate system call rate"
+ modify = no
+ display = no;
+
+double threshold
+ default = 10000
+ help =
+"The threshold of system calls per second per CPU. The appropriate
+value here is a function of the processor type and the workload, but
+here are some indicative figures of sustained system call rates for a
+single process:
+ getpid() - 380000 syscalls/sec
+ lseek() to start of file - 280000 syscalls/sec
+ gettimeofday() - 200000 syscalls/sec
+ read() at end of file - 83000 syscalls/sec
+ file creat() and close() - 65000 syscalls/sec
+ socket(), connect() and close() - 7000 syscalls/sec
+(generated using an otherwise idle system with 180MHz R10000 processors).";
+
+string action_expand
+ default = %vscall/s@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h aggregate syscalls/sec: %v"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200043"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.syscall
+ display = no
+ modify = no;
+string enln_units
+ default = scall/s
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/system b/src/pmieconf/cpu/system
new file mode 100644
index 0000000..04d61a5
--- /dev/null
+++ b/src/pmieconf/cpu/system
@@ -0,0 +1,73 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.system
+ summary = "$rule$"
+ predicate =
+"some_host (
+ // first term is always true, but provides %v for actions ...
+ ( 100 * kernel.all.cpu.sys $hosts$ / hinv.ncpu $hosts$ ) > 0
+ && 100 * ( kernel.all.cpu.user $hosts$ + kernel.all.cpu.sys $hosts$ )
+ > $busy$ * hinv.ncpu $hosts$
+ && 100 * kernel.all.cpu.sys $hosts$ /
+ ( kernel.all.cpu.user $hosts$ + kernel.all.cpu.sys $hosts$ )
+ > $threshold$
+)"
+ enabled = yes
+ version = 1
+ help =
+"Over the last sample interval, the average utilization per CPU was
+busy percent or more, and the ratio of system time to busy time
+exceeded threshold percent.";
+
+string rule
+ default = "Busy executing in system mode"
+ modify = no
+ display = no;
+
+percent busy
+ default = 70
+ help =
+"Busy percentage for average CPU utilization, in the range 0 (idle)
+to 100 (completely busy), independent of the number of CPUs.";
+
+percent threshold
+ default = 75
+ help =
+"Threshold percentage for system time as a fraction of the non-idle
+CPU time, in the range 0 (no system time) to 100 (all system time),
+independent of the number of CPUs.";
+
+string action_expand
+ default = %v%sys@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h system mode: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200044"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.system
+ display = no
+ modify = no;
+string enln_units
+ default = %sys
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/cpu/util b/src/pmieconf/cpu/util
new file mode 100644
index 0000000..be21b5c
--- /dev/null
+++ b/src/pmieconf/cpu/util
@@ -0,0 +1,62 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule cpu.util
+ summary = "$rule$"
+ predicate =
+"some_host (
+ 100 * ( kernel.all.cpu.user $hosts$ +
+ kernel.all.cpu.sys $hosts$ +
+ kernel.all.cpu.intr $hosts$ ) / hinv.ncpu $hosts$
+ > $threshold$
+)"
+ enabled = yes
+ version = 1
+ help =
+"The average processor utilization over all CPUs exceeded threshold
+percent during the last sample interval.";
+
+string rule
+ default = "High average processor utilization"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 90
+ help =
+"Threshold percentage for CPU saturation, in the range 0 (idle)
+to 100 (completely busy), independent of the number of CPUs.";
+
+string action_expand
+ default = %v%util@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h average CPU utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200045"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = cpu.util
+ display = no
+ modify = no;
+string enln_units
+ default = %util
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/filesys/buffer_cache b/src/pmieconf/filesys/buffer_cache
new file mode 100644
index 0000000..47878b4
--- /dev/null
+++ b/src/pmieconf/filesys/buffer_cache
@@ -0,0 +1,80 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule filesys.buffer_cache
+ summary = "$rule$"
+ predicate =
+"some_host (
+ 100 * ( kernel.all.io.lread $hosts$ -
+ kernel.all.io.bread $hosts$
+ / kernel.all.io.lread $hosts$ ) < $threshold$
+ && kernel.all.io.lread $hosts$ > $min_lread$ Kbytes/sec
+)"
+ enabled = yes
+ version = 1
+ help =
+"Some filesystem read activity (at least min_lread Kbytes per
+second of logical reads), and the read hit ratio in the buffer
+cache is below threshold percent.
+Note: It is possible for the read hit ratio to be negative
+(more phsical reads than logical reads) - this can be as a
+result of:
+ o XLV striped volumes, where blocks span stripe boundaries;
+ o very large files, where the disk controller has to read
+ blocks indirectly (multiple block reads to find a single
+ data block result);
+ o file system read-ahead pre-fetching blocks which are not
+ subsequently read.";
+
+string rule
+ default = "Low buffer cache read hit ratio"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 50
+ help =
+"The minimum acceptable buffer cache read hit ratio, expressed as a
+percentage. Values may be in the range 0 (nothing is read from the
+cache and poor performance is expected) to 100 (all reads come from
+the cache, no disk I/O required and good performance expected).";
+
+double min_lread
+ default = 512
+ help =
+"Unless at least min_lread Kbytes per second are passing across the
+logical filesystem read interface the rule will not be true.";
+
+string action_expand
+ default = %v%rcach@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h buffer cache read hit ratio: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200049"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = filesys.buffer_cache
+ display = no
+ modify = no;
+string enln_units
+ default = %rcach
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/filesys/dnlc_miss b/src/pmieconf/filesys/dnlc_miss
new file mode 100644
index 0000000..59264de
--- /dev/null
+++ b/src/pmieconf/filesys/dnlc_miss
@@ -0,0 +1,72 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule filesys.dnlc_miss
+ default = "$rule$"
+ predicate =
+"some_host (
+ 100 * ( name_cache.misses $hosts$ +
+ name_cache.enters $hosts$ +
+ name_cache.removes $hosts$ )
+ / name_cache.searches $hosts$ > $threshold$
+ && name_cache.searches $hosts$
+ > $min_lookup$ count/sec
+)"
+ enabled = no
+ version = 1
+ help =
+"With at least min_lookup directory name cache (DNLC) lookups per
+second being performed, threshold percent of lookups result in
+cache misses.";
+
+string rule
+ default = "High directory name cache miss rate"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 90
+ help =
+"Threshold percent of total directory name cache lookups are resulting
+in cache misses, in the range 0 (all accesses are satisified in the
+cache) to 100 (no accesses are satisifed in the cache).";
+
+double min_lookup
+ default = 100
+ help =
+"Minimum number of name cache lookups per second before considering
+whether these lookups are stressing the cache or not.";
+
+string action_expand
+ default = "%v%miss@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h name cache misses: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200040"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = filesys.dnlc_miss
+ display = no
+ modify = no;
+string enln_units
+ default = %miss
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/filesys/filling b/src/pmieconf/filesys/filling
new file mode 100644
index 0000000..b54b308
--- /dev/null
+++ b/src/pmieconf/filesys/filling
@@ -0,0 +1,99 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule filesys.filling
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_host (
+ some_inst (
+ ( 100 * filesys.used $hosts$ / filesys.capacity $hosts$ ) > $threshold$
+ && filesys.used $hosts$ +
+ $lead_time$ * ( rate filesys.used $hosts$ ) >
+ filesys.capacity $hosts$
+ )
+)"
+ enabled = yes
+ version = 1
+ help =
+"Filesystem is at least threshold percent full and the used space
+is growing at a rate that would see the filesystem full within
+lead_time.";
+
+string rule
+ default = "File system is filling up"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 95
+ help =
+"The threshold of filesystem fullness expressed as a percentage,
+usually in the range 90 to 99.";
+
+string lead_time
+ default = "20 min"
+ help =
+"The rule is true if the filesystem would be full within this time
+given the recent rate of growth. Normally requires a scale such as
+\"sec\", \"min\" or \"hr\", otherwise the value is interpreted as
+meaning seconds.";
+
+string delta
+ default = "4 mins"
+ help =
+"Sample interval between evaluations of this rule. The calculation
+of the projected rate of growth is sensitive to variations in the
+observed fullness of the filesystem. Adjust this parameter to be
+smaller if the filesystems' fullness are very stable and you want
+earlier warning of impending filling. Else make the parameter
+larger to avoid false warnings if the filesystems are close to full
+in the normal state and subject to bursts of file creation and
+deletion.";
+
+string action_expand
+ default = %v%used[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h filesystem: %i used: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20004A"
+ display = no
+ modify = no;
+
+# for HP OpenView integration:
+string ov_severity
+ display = no
+ default = "Critical";
+
+# for CA/Unicenter TNG integration:
+string tngfw_color
+ display = no
+ default = "Red";
+
+# for EnlightenDSM integration:
+string enln_test
+ default = filesys.filling
+ display = no
+ modify = no;
+string enln_units
+ default = %used[%i]
+ display = no
+ modify = no;
+unsigned enln_severity
+ display = no
+ default = 5;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/filesys/localdefs b/src/pmieconf/filesys/localdefs
new file mode 100644
index 0000000..3945214
--- /dev/null
+++ b/src/pmieconf/filesys/localdefs
@@ -0,0 +1,51 @@
+ALL_RULES = buffer_cache dnlc_miss filling
+
+LOCAL_RULES = $(ALL_RULES)
+
+# Metrics missing from Linux
+#
+# rule: buffer_cache
+# kernel.all.io.bread -12357 Unknown metric name
+# kernel.all.io.lread -12357 Unknown metric name
+#
+# rule: dnlc_miss
+# name_cache.enters -12357 Unknown metric name
+# name_cache.misses -12357 Unknown metric name
+# name_cache.removes -12357 Unknown metric name
+# name_cache.searches -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), linux)
+LOCAL_RULES = filling
+endif
+
+# Metrics missing from Mac OS X
+#
+# rule: buffer_cache
+# kernel.all.io.bread -12357 Unknown metric name
+# kernel.all.io.lread -12357 Unknown metric name
+#
+# rule: dnlc_miss
+# name_cache.enters -12357 Unknown metric name
+# name_cache.misses -12357 Unknown metric name
+# name_cache.removes -12357 Unknown metric name
+# name_cache.searches -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), darwin)
+LOCAL_RULES = filling
+endif
+
+# Metrics missing from Solaris
+#
+# rule: dnlc_miss
+# name_cache.enters -12357 Unknown metric name
+# name_cache.misses -12357 Unknown metric name
+# name_cache.removes -12357 Unknown metric name
+# name_cache.searches -12357 Unknown metric name
+#
+# rule: filling
+# filesys.capacity -12357 Unknown metric name
+# filesys.used -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), solaris)
+LOCAL_RULES = buffer_cache
+endif
diff --git a/src/pmieconf/global/enln_actions b/src/pmieconf/global/enln_actions
new file mode 100644
index 0000000..4d48bd1
--- /dev/null
+++ b/src/pmieconf/global/enln_actions
@@ -0,0 +1,42 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+shell global.enln_action
+ enabled = no
+ default = "$enln_bin$/EventsCli -q -s '$enln_severity$' -S N/A -T N/A -n 'pmie/$enln_test$ - $rule$' -h '%h' -v '%v' -u '$enln_units$'"
+ help =
+"Provides a mechanism for sending local pmie-generated events into
+the local Enlighten DSM management framework.
+This action requires the \"EventsCli\" program which is part of the
+Enlighten DSM framework (refer to the enln_bin variable description
+also).";
+
+string global.enln_bin
+ default = "/opt/enlighten/bin"
+ help =
+"The full path to the Enlighten DSM \"EventsCli\" program, which is used
+to propagate external events into the Enlighten DSM Framework.";
+
+unsigned global.enln_severity
+ default = 2
+ display = no
+ help =
+"The severity is a number between a low of 1 and a high of 5 (1=OK,
+2=Informational, 3=Warning, 4=Error, 5=Severe). The default value is 2.";
+
+string global.enln_test
+ default = "Unknown"
+ display = no
+ help =
+"The brief name of the pmie event being sent to Enlighten DSM, typically
+the name of an individual rule.";
+
+string global.enln_units
+ default = "N/A"
+ display = no
+ help =
+"The units value specifies the unit of measure for the value measured.";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/global/localdefs b/src/pmieconf/global/localdefs
new file mode 100644
index 0000000..cbebc2c
--- /dev/null
+++ b/src/pmieconf/global/localdefs
@@ -0,0 +1,7 @@
+ALL_RULES = enln_actions tngfw_actions parameters ov_actions pcp_actions
+
+LOCAL_RULES = $(ALL_RULES)
+
+ifneq ($(TARGET_OS), irix)
+LOCAL_RULES = parameters pcp_actions
+endif
diff --git a/src/pmieconf/global/ov_actions b/src/pmieconf/global/ov_actions
new file mode 100644
index 0000000..dc0652d
--- /dev/null
+++ b/src/pmieconf/global/ov_actions
@@ -0,0 +1,53 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+shell global.ov_action
+ enabled = no
+ default = "OID=.1.3.6.1.4.1.11.2.17; export OID; $ov_bin$/ovevent -c '$ov_category$' -s '$ov_severity$' '$ov_node$' ^\\\\${OID}.1.0.58916872 \\\\${OID}.2.1.0 Integer 0 \\\\${OID}.2.2.0 OctetString '%h' \\\\${OID}.2.4.0 OctetString '$rule$^ $action_expand$^'"
+ # 4 backquotes gives a shell variable - '$' is a special character
+ # to pmieconf, pmie, and the shell - so need to backquote it up the
+ # whazoo to get this to come out right!
+ #
+ help =
+"The HP OpenView Network Node Manager event subsystem on ov_node will
+receive an OV_Message event when the rule condition is true.
+This action requires the ovevent(1) program which is part of the HP
+OpenView package (refer to the ov_bin variable description also).";
+
+string global.ov_bin
+ default = "/opt/OV/bin"
+ help =
+"The full path to the HP OpenView ovevent(1) program, which is used to
+propagate external events into the OpenView framework.";
+
+string global.ov_node
+ default = ""
+ help =
+"The node on which the HP OpenView pmd(1M) daemon is running, which
+will reliably broadcast the pmie event to all interested HP OpenView
+processes.
+The node can be either an Internet address or host name (see hosts(4)),
+and is usually the local host except when run from a management console
+(client host). An empty string is equivalent to the local host.";
+
+string global.ov_severity
+ default = "Warning"
+ display = no
+ help =
+"Severity with which an event will be reported to the HP OpenView node
+manager on ov_node. Valid values are \"Critical\", \"Major\", \"Minor\",
+\"Warning\" and \"Normal\".";
+
+string global.ov_category
+ default = "Threshold Events"
+ display = no
+ help =
+"Category with which an event will be reported to the HP OpenView node
+manager on ov_node. The category must be one of the existing event
+categories; the default categories defined in trapd.conf(4) are:
+\"IGNORE\", \"LOGONLY\", \"Error Events\", \"Threshold Events\", \"Status
+Events\", \"Configuration Events\", and \"Application Alert Events\".";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/global/parameters b/src/pmieconf/global/parameters
new file mode 100644
index 0000000..38b16c7
--- /dev/null
+++ b/src/pmieconf/global/parameters
@@ -0,0 +1,35 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+# variable definitions applicable to all rules
+# (unless overridden at the level of an individual rule or group)
+#
+
+string global.delta
+ default = "2 min"
+ help =
+"Sample interval between evaluations of this rule. Default units
+are seconds and common units are \"second\", \"sec\", \"minute\",
+\"min\" and \"hour\".";
+
+string global.holdoff
+ default = "10 min"
+ help =
+"Once the predicate is true and the action is executed, this
+variable allows suppression of further action execution until the
+specified interval has elapsed. A value of zero enables execution
+of the action if the rule predicate is true at the next sample.
+Default units are seconds and common units are \"second\", \"sec\",
+\"minute\", \"min\" and \"hour\".";
+
+hostlist global.hosts
+ default = ""
+ help =
+"May be set to a list of host names for which the rules will be
+evaluated. Multiple hostnames should be separated by white space.
+If the list is empty, the host will be the host named in the -h
+option to pmie(1) if specified, else the local host.";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/global/pcp_actions b/src/pmieconf/global/pcp_actions
new file mode 100644
index 0000000..104543c
--- /dev/null
+++ b/src/pmieconf/global/pcp_actions
@@ -0,0 +1,89 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+# action definitions applicable to all rules follow
+#
+# o $rule$ defined locally & contains the "message" to be propagated
+#
+# o $*_expand$ often overridden locally & contains the part of the
+# action string which is rule-semantics specific and will be expanded
+# possibly multiple times on truthful evaluation of the predicate to
+# contain values/instances/hosts which matched,e.g:
+# "%v@%h" might expand to "1.1@moomba 1.7@rattle 4.5@wobbly" if all
+# the rule is true for all of the hosts moomba, rattle and wobbly.
+#
+# some common alternatives:
+# %i@%h (inst@host)
+# %h (host)
+# %v[%i]@%h (value:inst@host)
+# %v%@%h (value%@host)
+#
+
+string global.action_expand
+ display = no
+ modify = no
+ default = "%v@%h"; # (value@host)
+
+string global.email_expand
+ display = no
+ modify = no
+ default = "%v@%h"; # (value@host)
+
+
+shell global.user_action
+ enabled = no
+ default = "$user_command$"
+ help =
+"Execute \"user_command\" when the rule condition is true";
+
+string global.user_command
+ default = "echo $rule$^ $action_expand$"
+ help =
+"Shell (sh(1)) command line to execute when rule condition is true
+and \"user_action\" is enabled.";
+
+
+shell global.email_action
+ enabled = no
+ default = "pmie_email '$email_recipients$|$rule$^|$email_expand$^'"
+ help =
+"A mail message will be sent to \"email_recipients\" when the rule
+condition is true.";
+
+string global.email_recipients
+ default = "root"
+ help =
+"Space separated list of e-mail addresses for notification from the
+\"email_action\" when it is enabled";
+
+
+shell global.pcplog_action
+ enabled = no
+ default = "pmpost pmie: $rule$^ $action_expand$"
+ help =
+"The PCP notices file $PCP_LOG_DIR/NOTICES will be updated when
+the rule condition is true.";
+
+
+syslog global.syslog_action
+ enabled = yes
+ default = "$syslog_prefix$$rule$^ $action_expand$"
+ help =
+"The system log file (usually /var/log/messages) will be updated
+when the rule condition is true.";
+
+string global.syslog_prefix
+ display = no
+ modify = no
+ default = "";
+
+# for SGI Embedded Support Partner integration, use:
+# $ pmieconf modify global syslog_prefix '$esp_prefix$'
+string global.esp_prefix
+ display = no
+ modify = no
+ default = "|\\\\$($esp_type$)";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/global/tngfw_actions b/src/pmieconf/global/tngfw_actions
new file mode 100644
index 0000000..36ade07
--- /dev/null
+++ b/src/pmieconf/global/tngfw_actions
@@ -0,0 +1,45 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+shell global.tngfw_action
+ enabled = no
+ default = "$tngfw_bin$/cawto -n '$tngfw_node$' -c '$tngfw_color$' -g $tngfw_category$ -s `/usr/bsd/hostname` '$rule$^ $action_expand$^'"
+ help =
+"The CA Unicenter TNG console node at tngfw_node will be notified when
+the rule condition is true.
+This action requires the \"cawto\" program which is part of the CA
+Unicenter TNG Framework (refer to the tngfw_bin variable description
+also).";
+
+string global.tngfw_bin
+ default = "/usr/TNGFW/bin"
+ help =
+"The full path to the TNG Framework \"cawto\" program, which is used to
+propagate external events into the Unicenter TNG Framework.";
+
+string global.tngfw_node
+ default = ""
+ help =
+"The node on which the CA Unicenter TNG monitoring software is running.
+The node can be either an Internet address or host name (see hosts(4)),
+and is usually the local host. An empty string is equivalent to the
+local host.";
+
+string global.tngfw_color
+ default = "default"
+ display = no
+ help =
+"The color that the CA Unicenter TNG event console on tngfw_node will
+use to display the event message string. Valid values are \"default\",
+\"Red\", \"Orange\", \"yellow\", \"green\", \"blue\", \"pink\" or \"purple\".";
+
+string global.tngfw_category
+ default = "Performance"
+ display = no
+ help =
+"The category with which the CA Unicenter TNG event console on
+tngfw_node will associate each event.";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/global/web_parameters b/src/pmieconf/global/web_parameters
new file mode 100644
index 0000000..1d4482d
--- /dev/null
+++ b/src/pmieconf/global/web_parameters
@@ -0,0 +1,35 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+instlist global.webservers
+ default = ""
+ help =
+"List of web server names over which the rules will be evaluated.
+Multiple server names should be separated by white space, e.g.
+ \"ncsa-gonzo 'ftpd'\"
+To discover the names of the available web servers, execute the
+following command on the target host:
+ $ pminfo -f web.perserver.requests.total
+the double-quoted instance names are the web server names.
+If the list is empty, all web servers on the target host will be
+checked.";
+
+instlist global.urls
+ default = ""
+ help =
+"May be set to a list of URLs names to limit those which the rules
+will be evaluated, as a subset of those used by the webping agent.
+Each URL should be in the form used by the webping agent, be enclosed
+in single quotes, and multiple entries separated by white space, e.g.
+'GET_http://boing/index.html' 'GET_http://far.away.com/planning.html'
+
+To discover the names of the available webping URLs, execute the
+following command on the target host:
+ $ pminfo -f webping.perurl.kbytes
+the (double) quoted instance names are the required URLs.
+
+If the list is empty, all webping URLs will be checked.";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/io.c b/src/pmieconf/io.c
new file mode 100644
index 0000000..86a8025
--- /dev/null
+++ b/src/pmieconf/io.c
@@ -0,0 +1,229 @@
+/*
+ * Copyright 1998, Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdarg.h>
+#include "pmapi.h"
+#include "impl.h"
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_TERMIO_H
+#include <termio.h>
+#endif
+
+#define MINCOLS 80
+#define MINROWS 24
+
+static int neols = -1;
+static int needresize;
+static int needscroll;
+static int skiprest;
+static int nrows;
+static int ncols;
+static char shortmsg[] = "more? (h=help) ";
+static char longmsg[] = \
+ "[q or n to stop, y or <space> to go on, <enter> to step] more? ";
+#ifdef HAVE_TERMIO_H
+static struct termio otty;
+#endif
+
+void setio(int reset) { neols = 0; skiprest = reset; }
+void setscroll(void) { needscroll = 1; }
+int resized(void) { return needresize; }
+
+/* looks after window resizing for the printing routine */
+void
+onwinch(int dummy)
+{
+#ifdef SIGWINCH
+ __pmSetSignalHandler(SIGWINCH, onwinch);
+#endif
+ needresize = 1;
+}
+
+/* in interactive mode scrolling, if no more wanted skiprest is set */
+static void
+promptformore(void)
+{
+ int i;
+ int ch;
+ int sts = 1;
+ char c;
+ char *prompt;
+#ifdef HAVE_TERMIO_H
+ static int first = 1;
+ struct termio ntty;
+#endif
+
+#ifdef HAVE_TERMIO_H
+ if (first) {
+ if (ioctl(0, TCGETA, &otty) < 0) {
+ fprintf(stderr, "%s: TCGETA ioctl failed: %s\n", pmProgname,
+ osstrerror());
+ exit(1);
+ }
+ first = 0;
+ }
+
+ /* put terminal into raw mode so we can read input immediately */
+ memcpy(&ntty, &otty, sizeof(struct termio));
+ ntty.c_cc[VMIN] = 1;
+ ntty.c_cc[VTIME] = 1;
+ ntty.c_lflag &= ~(ICANON | ECHO);
+ if (ioctl(0, TCSETAW, &ntty) < 0) {
+ fprintf(stderr, "%s: TCSETAW ioctl failed: %s\n", pmProgname,
+ osstrerror());
+ exit(1);
+ }
+#endif
+
+ prompt = shortmsg;
+ while (sts == 1) {
+ putchar('\r');
+ for (i = 0; i < ncols-1; i++)
+ putchar(' ');
+ putchar('\r');
+ printf("%s", prompt);
+ fflush(stdout);
+
+ if (read(0, &c, 1) != 1) {
+ sts = 1;
+ goto reset_tty;
+ }
+ ch = (int)c;
+
+ switch(ch) {
+ case 'n': /* stop */
+ case 'q':
+ setio(1);
+ sts = 0;
+ break;
+ case 'y': /* page down */
+ case ' ':
+ neols = sts = 0;
+ break;
+ case '\n': /* step down */
+ neols = nrows;
+ sts = 0;
+ break;
+ default:
+ prompt = longmsg;
+ }
+ }
+
+reset_tty:
+#ifdef HAVE_TERMIO_H
+ if (ioctl(0, TCSETAW, &otty) < 0) {
+ fprintf(stderr, "%s: reset TCSETAW ioctl failed: %s\n", pmProgname,
+ osstrerror());
+ exit(1);
+ }
+#endif
+
+ putchar('\r');
+ for (i = 0; i < ncols-1; i++)
+ putchar(' ');
+ putchar('\r');
+ fflush(stdout);
+}
+
+/*
+ * generic printing routine which can pause at end of a screenful.
+ * if this returns 1, the user has requested an end to this info,
+ * so the caller must always observe the pprintf return value.
+ */
+void
+pprintf(char *format, ...)
+{
+ char *p;
+ va_list args;
+#ifdef TIOCGWINSZ
+ struct winsize geom;
+#endif
+ static int first = 1;
+
+ if (first == 1) { /* first time thru */
+ first = 0;
+#ifdef TIOCGWINSZ
+ ioctl(0, TIOCGWINSZ, &geom);
+ nrows = (geom.ws_row < MINROWS? MINROWS : geom.ws_row);
+ ncols = (geom.ws_col < MINCOLS? MINCOLS : geom.ws_col);
+#else
+ nrows = MINROWS;
+ ncols = MINCOLS;
+#endif
+ }
+
+ if (skiprest)
+ return;
+
+ va_start(args, format);
+ if (needscroll) {
+ /*
+ * use the fact that i know we never print more than MINROWS at once
+ * to figure out how many lines we've done before doing the vfprintf
+ */
+ if (neols >= nrows-1) {
+ promptformore();
+ if (skiprest) {
+ va_end(args);
+ return;
+ }
+ }
+ for (p = format; *p != '\0'; p++)
+ if (*p == '\n') neols++;
+ vfprintf(stdout, format, args);
+ }
+ else
+ vfprintf(stdout, format, args);
+ va_end(args);
+ if (needresize) {
+#ifdef HAVE_TIOCGWINSZ
+ ioctl(0, TIOCGWINSZ, &geom);
+ nrows = (geom.ws_row < MINROWS? MINROWS : geom.ws_row);
+ ncols = (geom.ws_col < MINCOLS? MINCOLS : geom.ws_col);
+#ifdef PMIECONF_DEBUG
+ printf("debug - reset size: cols=%d rows=%d\n", ncols, nrows);
+#endif
+#endif
+ needresize = 0;
+ }
+}
+
+/* general error printing routine */
+void
+error(char *format, ...)
+{
+ va_list args;
+ FILE *f;
+
+ if (skiprest)
+ return;
+ va_start(args, format);
+ if (needscroll) {
+ f = stdout;
+ fprintf(f, " Error - ");
+ }
+ else {
+ f = stderr;
+ fprintf(f, "%s: error - ", pmProgname);
+ }
+ vfprintf(f, format, args);
+ fprintf(f, "\n");
+ neols++;
+ va_end(args);
+}
diff --git a/src/pmieconf/memory/exhausted b/src/pmieconf/memory/exhausted
new file mode 100644
index 0000000..9e430a6
--- /dev/null
+++ b/src/pmieconf/memory/exhausted
@@ -0,0 +1,81 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule memory.exhausted
+ summary = "$rule$"
+ # first conjunct for %v, second is real condition...
+ predicate =
+"some_host (
+ ( avg_sample ( swap.pagesout $hosts$ @0..9 ) ) > 0 &&
+ $pct$ %_sample swap.pagesout $hosts$ @0..9 >= $threshold$
+)"
+ enabled = yes
+ version = 1
+ help =
+"The system is swapping modified pages out of main memory to the
+swap partitions, and has been doing this at the rate of at least
+threshold pages swapped out per second for at least pct of the last
+10 samples, ie. sustained page out activity.";
+
+double threshold
+ default = 5
+ help =
+"Threshold rate of pages swapped out per second.";
+
+percent pct
+ default = 30
+ help =
+"Percentage of the last 10 observations with at least threshold
+pages swapped out per second required to make the rule true.";
+
+string rule
+ default = "Severe demand for real memory"
+ modify = no
+ display = no;
+
+string action_expand
+ default = "%vpgsout/s@%h"
+ modify = no
+ display = no;
+
+string email_expand
+ default = "host: %h recent average: %v pageouts/sec"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20004B"
+ display = no
+ modify = no;
+
+# for HP OpenView integration:
+string ov_severity
+ display = no
+ default = "Critical";
+
+# for CA/Unicenter TNG integration:
+string tngfw_color
+ display = no
+ default = "Red";
+
+# for EnlightenDSM integration:
+string enln_test
+ default = memory.exhausted
+ display = no
+ modify = no;
+string enln_units
+ default = pgsout/s
+ display = no
+ modify = no;
+unsigned enln_severity
+ display = no
+ default = 5;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/memory/localdefs b/src/pmieconf/memory/localdefs
new file mode 100644
index 0000000..a74d0c6
--- /dev/null
+++ b/src/pmieconf/memory/localdefs
@@ -0,0 +1,29 @@
+ALL_RULES = swap_low exhausted
+
+LOCAL_RULES = $(ALL_RULES)
+
+# Metrics missing from Mac OS X
+#
+# rule: swap_low
+# swap.free -12357 Unknown metric name
+# swap.length -12357 Unknown metric name
+#
+# rule: exhausted
+# swap.pagesout -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), darwin)
+LOCAL_RULES =
+endif
+
+# Metrics missing from Solaris
+#
+# rule: exhausted
+# swap.pagesout -12357 Unknown metric name
+#
+# rule: swap_low
+# swap.free -12357 Unknown metric name
+# swap.length -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), solaris)
+LOCAL_RULES =
+endif
diff --git a/src/pmieconf/memory/swap_low b/src/pmieconf/memory/swap_low
new file mode 100644
index 0000000..d69866d
--- /dev/null
+++ b/src/pmieconf/memory/swap_low
@@ -0,0 +1,78 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+# Based on a rule originally developed by Kevin Wang at Silicon Graphics
+#
+
+rule memory.swap_low
+ summary = "$rule$"
+ predicate =
+"some_host (
+ ( 100 * ( swap.free $hosts$ / swap.length $hosts$ ) )
+ < $threshold$
+ && swap.length $hosts$ > 0 // ensure swap in use
+)"
+ enabled = no
+ version = 1
+ help =
+"There is only threshold percent swap space remaining - the system
+may soon run out of virtual memory. Reduce the number and size of
+the running programs or add more swap(1) space before it completely
+runs out.";
+
+percent threshold
+ default = 10
+ help =
+"Threshold percent of total swap space which is free, in the range
+0 (none free) to 100 (all swap is unused).";
+
+string rule
+ default = "Low free swap space"
+ modify = no
+ display = no;
+
+string action_expand
+ default = "%v%free@%h"
+ modify = no
+ display = no;
+
+string email_expand
+ default = "host: %h free swap space: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20004C"
+ display = no
+ modify = no;
+
+# for HP OpenView integration:
+string ov_severity
+ display = no
+ default = "Critical";
+
+# for CA/Unicenter TNG integration:
+string tngfw_color
+ display = no
+ default = "Red";
+
+# for EnlightenDSM integration:
+string enln_test
+ default = memory.swap_low
+ display = no
+ modify = no;
+string enln_units
+ default = %free
+ display = no
+ modify = no;
+unsigned enln_severity
+ display = no
+ default = 5;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pcp_web b/src/pmieconf/pcp_web
new file mode 100644
index 0000000..9de8479
--- /dev/null
+++ b/src/pmieconf/pcp_web
@@ -0,0 +1,21 @@
+#
+# Performance events generated by pmie rules in pcp_web
+# range: 0x200040 - 0x20008F -> pcp_eoe
+# range: 0x200090 - 0x20009F -> pcp
+# range: 0x2000A0 - 0x2000AF -> pcp_web
+# range: 0x2000B0 - 0x2000B8 -> pcp_hpc
+# range: 0x2000B9 - 0x2000BF -> pcp_fsafe
+# range: 0x2000C0 - 0x2000FF -> (unused)
+#
+
+4001:Performance:0x2000A0:Excessive individual Web server errors:1:1:0:1:1
+4001:Performance:0x2000A1:High rate of Web requests per server:1:1:0:1:1
+4001:Performance:0x2000A2:Web server appears idle:1:1:0:1:1
+4001:Performance:0x2000A3:Excessive aggregate Web server errors:1:1:0:1:1
+4001:Performance:0x2000A4:High aggregate rate of Web requests:1:1:0:1:1
+4001:Performance:0x2000A5:Low aggregate rate of Web requests:1:1:0:1:1
+4001:Performance:0x2000A6:High webping connection error rate:1:1:0:1:1
+4001:Performance:0x2000A7:High webping HTML error rate:1:1:0:1:1
+4001:Performance:0x2000A8:High webping HTTP error rate:1:1:0:1:1
+4001:Performance:0x2000A9:High webping error rate, cause unknown:1:1:0:1:1
+4001:Performance:0x2000AA:Slow response to some webping requests:1:1:0:1:1
diff --git a/src/pmieconf/percpu/context_switch b/src/pmieconf/percpu/context_switch
new file mode 100644
index 0000000..fdee5cd
--- /dev/null
+++ b/src/pmieconf/percpu/context_switch
@@ -0,0 +1,70 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_cpu.context_switch
+ summary = "$rule$"
+ enumerate = "hosts"
+ predicate =
+"some_host (
+ some_inst (
+ kernel.percpu.pswitch $hosts$ > $threshold$ count/sec
+ )
+ && hinv.ncpu $hosts$ > 1
+)"
+ enabled = yes
+ version = 1
+ help =
+"The number of context switches per second for at least one CPU
+exceeded $threshold$ over the past sample interval.
+
+This rule only applies to multi-processor systems, for
+single-processor systems refer to the cpu.context_switch rule.
+
+For Origin 200 and Origin 2000 systems, use the command
+ $ pminfo -f hinv.map.cpu
+to discover the abbreviated PCP names of the installed CPUs and
+their corresponding full names in the /hw file system.";
+
+string rule
+ default = "High per CPU context switch rate"
+ modify = no
+ display = no;
+
+double threshold
+ default = 5000
+ help =
+"The threshold number of context switches per second per CPU.";
+
+string action_expand
+ default = %vctxsw/s[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h CPU: %i context switches: %v/sec"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200056"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_cpu.context_switch
+ display = no
+ modify = no;
+string enln_units
+ default = ctxsw/s[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/percpu/localdefs b/src/pmieconf/percpu/localdefs
new file mode 100644
index 0000000..7bb0c69
--- /dev/null
+++ b/src/pmieconf/percpu/localdefs
@@ -0,0 +1,45 @@
+ALL_RULES = syscall some_util many_util context_switch system
+
+LOCAL_RULES = $(ALL_RULES)
+
+# Metrics missing from Linux
+#
+# rule: context_switch
+# kernel.percpu.pswitch -12357 Unknown metric name
+#
+# rule: syscall
+# kernel.percpu.syscall -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), linux)
+LOCAL_RULES = some_util many_util system
+endif
+
+# Metrics missing from Mac OS X
+#
+# rule: syscall
+# kernel.percpu.syscall -12357 Unknown metric name
+#
+# rule: some_util
+# kernel.percpu.cpu.intr -12357 Unknown metric name
+#
+# rule: many_util
+# kernel.percpu.cpu.intr -12357 Unknown metric name
+#
+# rule: context_switch
+# kernel.percpu.pswitch -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), darwin)
+LOCAL_RULES = system
+endif
+
+# Metrics missing from Solaris
+#
+# rule: many_util
+# kernel.percpu.cpu.intr -12357 Unknown metric name
+#
+# rule: some_util
+# kernel.percpu.cpu.intr -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), solaris)
+LOCAL_RULES = syscall context_switch system
+endif
diff --git a/src/pmieconf/percpu/many_util b/src/pmieconf/percpu/many_util
new file mode 100644
index 0000000..9fe2f08
--- /dev/null
+++ b/src/pmieconf/percpu/many_util
@@ -0,0 +1,85 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_cpu.many_util
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_host (
+ $pct$ %_inst (
+ 100 * ( kernel.percpu.cpu.user $hosts$ +
+ kernel.percpu.cpu.sys $hosts$ +
+ kernel.percpu.cpu.intr $hosts$ )
+ > $threshold$
+ )
+ && hinv.ncpu $hosts$ > $min_cpu_count$
+)"
+ enabled = yes
+ version = 1
+ help =
+"The processor utilization for at least pct percent of the CPUs
+exceeded threshold percent during the last sample interval. Only
+applies to multi-processor systems having more than min_cpu_count
+processors - for single-processor systems refer to the cpu.util
+rule, for multi-processor systems with less than min_cpu_count
+processors refer to the per_cpu.some_util rule.";
+
+string rule
+ default = "High number of saturated processors"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 95
+ help =
+"Threshold percentage for CPU saturation, in the range 0 (idle)
+to 100 (completely busy)";
+
+percent pct
+ default = 80
+ help =
+"Percentage of the processors which must be utilized greater
+than threshold percent, in the range 0 (no processors utilized)
+to 100 (all processors).";
+
+unsigned min_cpu_count
+ default = 4
+ help =
+"Lower limit on number of processors for this rule - this rule will
+apply to configurations of greater than min_cpu_count CPUs.
+For smaller processor counts, the per_cpu.some_util rule may be more
+appropriate.";
+
+string action_expand
+ default = "\\\\>$pct$%cpus@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h more than $pct$% of the processors are saturated"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20005F"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_cpu.many_util
+ display = no
+ modify = no;
+string enln_units
+ default = "busy_CPUs"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/percpu/some_util b/src/pmieconf/percpu/some_util
new file mode 100644
index 0000000..a065226
--- /dev/null
+++ b/src/pmieconf/percpu/some_util
@@ -0,0 +1,83 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_cpu.some_util
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_host (
+ some_inst (
+ ( 100 * ( kernel.percpu.cpu.user $hosts$ +
+ kernel.percpu.cpu.sys $hosts$ +
+ kernel.percpu.cpu.intr $hosts$ ) )
+ > $threshold$
+ )
+ && hinv.ncpu $hosts$ > 1
+ && hinv.ncpu $hosts$ <= $max_cpu_count$
+)"
+ enabled = yes
+ version = 1
+ help =
+"The processor utilization for at least one CPU exceeded threshold
+percent during the last sample interval. Only applies to
+multi-processor systems with less than max_cpu_count processors -
+for single-processor systems refer to the cpu.util rule, and for
+multi-processor systems with more than max_cpu_count processors
+refer to the cpu.many_util rule.
+For Origin 200 and Origin 2000 systems, use the command
+ $ pminfo -f hinv.map.cpu
+to discover the abbreviated PCP names of the installed CPUs and
+their corresponding full names in the /hw file system.";
+
+string rule
+ default = "High per CPU processor utilization"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 95
+ help =
+"Threshold percentage for CPU saturation, in the range 0 (idle)
+to 100 (completely busy)";
+
+unsigned max_cpu_count
+ default = 4
+ help =
+"Upper limit on number of processors for this rule - this rule will
+apply to configurations of between two and max_cpu_count CPUs.
+For larger processor counts, the per_cpu.many_util rule may be more
+appropriate.";
+
+string action_expand
+ default = %v%util[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h CPU: %i utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200059"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_cpu.some_util
+ display = no
+ modify = no;
+string enln_units
+ default = %util[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/percpu/syscall b/src/pmieconf/percpu/syscall
new file mode 100644
index 0000000..fbfbeee
--- /dev/null
+++ b/src/pmieconf/percpu/syscall
@@ -0,0 +1,80 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_cpu.syscall
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_host (
+ some_inst (
+ kernel.percpu.syscall $hosts$ > $threshold$ count/sec
+ )
+ && hinv.ncpu $hosts$ > 1
+)"
+ enabled = yes
+ version = 1
+ help =
+"The number of system calls per second for at least one CPU
+exceeded threshold over the past sample interval.
+
+This rule only applies to multi-processor systems, for
+single-processor systems refer to the cpu.syscall rule.
+
+For Origin 200 and Origin 2000 systems, use the command
+ $ pminfo -f hinv.map.cpu
+to discover the abbreviated PCP names of the installed CPUs and
+their corresponding full names in the /hw file system.";
+
+string rule
+ default = "High per CPU system call rate"
+ modify = no
+ display = no;
+
+double threshold
+ default = 12000
+ help =
+"The threshold of system calls per second per CPU. The appropriate
+value here is a function of the processor type and the workload, but
+here are some indicative figures of sustained system call rates for a
+single process:
+ getpid() - 380000 syscalls/sec
+ lseek() to start of file - 280000 syscalls/sec
+ gettimeofday() - 200000 syscalls/sec
+ read() at end of file - 83000 syscalls/sec
+ file creat() and close() - 65000 syscalls/sec
+ socket(), connect() and close() - 7000 syscalls/sec
+(generated using an otherwise idle system with 180MHz R10000 processors).";
+
+string action_expand
+ default = %vscall/s[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h CPU: %i syscalls/sec: %v"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200057"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_cpu.syscall
+ display = no
+ modify = no;
+string enln_units
+ default = scall/s[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/percpu/system b/src/pmieconf/percpu/system
new file mode 100644
index 0000000..223692c
--- /dev/null
+++ b/src/pmieconf/percpu/system
@@ -0,0 +1,84 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_cpu.system
+ summary = "$rule$"
+ enumerate = hosts
+ # need first conjunct to get %v in actions...
+ predicate =
+"some_host (
+ some_inst (
+ // first term is always true, but provides %v for actions ...
+ ( 100 * kernel.percpu.cpu.sys $hosts$ ) > 0
+ && 100 * ( kernel.percpu.cpu.user $hosts$ +
+ kernel.percpu.cpu.sys $hosts$ ) > $busy$
+ && 100 * kernel.percpu.cpu.sys $hosts$ /
+ ( kernel.percpu.cpu.user $hosts$ + kernel.percpu.cpu.sys $hosts$ )
+ > $threshold$
+ )
+ && hinv.ncpu $hosts$ > 1
+)"
+ enabled = yes
+ version = 1
+ help =
+"Over the last sample interval, at least one CPU was active for
+busy percent or more, and the ratio of system time to busy time
+exceeded threshold percent. Only applies to multi-processor
+systems, for single-processor systems refer to the cpu.system
+rule.
+For Origin 200 and Origin 2000 systems, use the command
+ $ pminfo -f hinv.map.cpu
+to discover the abbreviated PCP names of the installed CPUs and
+their corresponding full names in the /hw file system.";
+
+string rule
+ default = "Some CPU busy executing in system mode"
+ modify = no
+ display = no;
+
+percent busy
+ default = 75
+ help =
+"Busy percentage for average CPU utilization, in the range 0 (idle)
+to 100 (completely busy), independent of the number of CPUs.";
+
+percent threshold
+ default = 80
+ help =
+"Threshold percentage for system time as a fraction of the non-idle
+CPU time, in the range 0 (no system time) to 100 (all system time),
+independent of the number of CPUs.";
+
+string action_expand
+ default = %v%sys[%i]@%h
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h CPU: %i system mode: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200058"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_cpu.system
+ display = no
+ modify = no;
+string enln_units
+ default = %sys[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pernetif/collisions b/src/pmieconf/pernetif/collisions
new file mode 100644
index 0000000..9df73ee
--- /dev/null
+++ b/src/pmieconf/pernetif/collisions
@@ -0,0 +1,76 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_netif.collisions
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 100 * network.interface.collisions $hosts$
+ / ( network.interface.collisions $hosts$
+ + network.interface.out.packets $hosts$ )
+ > $threshold$ &&
+ network.interface.out.packets $hosts$
+ > $packet_rate$ count/second
+)"
+ enabled = yes
+ version = 1
+ help =
+"More than threshold percent of the packets being sent across an
+interface are causing a collision, and packets are being sent
+across the interface at packet_rate packets per second.
+Ethernet interfaces expect a certain number of packet collisions,
+but a high ratio of collisions to packet sends is indicitive of a
+saturated network.";
+
+string rule
+ default = "High collision rate in packet sends"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 40
+ help =
+"Threshold percent of transmitted packets are colliding with other
+packet sending attempts before being successfully transmitted.";
+
+double packet_rate
+ default = 10
+ help =
+"Rate at which packets are being transmitted on an interface (number
+of packets per second) before considering the number of collisions as
+significant.";
+
+string action_expand
+ default = "%v%collisions[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h interface: %i collision rate: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20004E"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_netif.collisions
+ display = no
+ modify = no;
+string enln_units
+ default = %collide[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pernetif/errors b/src/pmieconf/pernetif/errors
new file mode 100644
index 0000000..61c9e38
--- /dev/null
+++ b/src/pmieconf/pernetif/errors
@@ -0,0 +1,72 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_netif.errors
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ network.interface.total.errors $hosts$ $interfaces$
+ > $threshold$
+)"
+ enabled = yes
+ version = 1
+ help =
+"For at least one network interface, the error rate exceeded
+threshold errors per second during the last sample interval.";
+
+string rule
+ default = "High network interface error rate"
+ modify = no
+ display = no;
+
+double threshold
+ default = 15
+ help =
+"Threshold in units of errors per second per interface.";
+
+instlist interfaces
+ default = ""
+ help =
+"May be set to a list of network interfaces for which the rule will
+be evaluated, as a subset of configured network interfaces.
+Network interfaces should be separated by white space and may be
+enclosed in single quotes, eg. \"ec0 'xpi5' ec2\". Use the command:
+ $ pminfo -f network.interface.mtu
+to discover the names of the installed network interfaces.
+Setting this variable is most useful to remove the loopback, SLIP
+and PPP interfaces from the rule evaluations.";
+
+string action_expand
+ default = "%verr/s[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h interface: %i errors per sec: %v"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x20004F"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_netif.errors
+ display = no
+ modify = no;
+string enln_units
+ default = err/s[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pernetif/localdefs b/src/pmieconf/pernetif/localdefs
new file mode 100644
index 0000000..e153d6d
--- /dev/null
+++ b/src/pmieconf/pernetif/localdefs
@@ -0,0 +1,22 @@
+ALL_RULES = collisions errors packets util
+
+LOCAL_RULES = $(ALL_RULES)
+
+# Metrics missing from Solaris
+#
+# rule: collisions
+# network.interface.collisions -12357 Unknown metric name
+#
+# rule: errors
+# network.interface.total.errors -12357 Unknown metric name
+#
+# rule: packets
+# network.interface.total.packets -12357 Unknown metric name
+#
+# rule: util
+# network.interface.baudrate -12357 Unknown metric name
+# network.interface.total.bytes -12357 Unknown metric name
+#
+ifeq ($(TARGET_OS), solaris)
+LOCAL_RULES =
+endif
diff --git a/src/pmieconf/pernetif/packets b/src/pmieconf/pernetif/packets
new file mode 100644
index 0000000..7b68553
--- /dev/null
+++ b/src/pmieconf/pernetif/packets
@@ -0,0 +1,83 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_netif.packets
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ network.interface.total.packets $hosts$ $interfaces$ >
+ $threshold$ count/sec
+)"
+ enabled = no
+ version = 1
+ help =
+"For at least one network interface, the average rate of packet
+transfers (in and/or out) exceeded the threshold during the last
+sample interval.
+This rule is disabled by default because the per_netif.util rule
+is more generally useful as it takes into consideration each
+network interfaces' reported bandwidth. However, there are some
+situations in which this value is zero, in which case an absolute
+threshold-based rule like this one will make more sense (for this
+reason it should typically be applied to some network interfaces,
+but not others - use the \"interfaces\" variable to filter this).";
+
+string rule
+ default = "High network interface packet transfers"
+ modify = no
+ display = no;
+
+double threshold
+ default = 2000
+ help =
+"Threshold in units of packets (in or out) per second per interface.
+A tolerable value depends on the type of network interface and
+the packet size; some experimentation may be required to find
+an accpetable threshold.";
+
+instlist interfaces
+ default =""
+ help =
+"May be set to a list of network interfaces for which the rule will
+be evaluated, as a subset of configured network interfaces.
+Network interfaces should be separated by white space and may be
+enclosed in single quotes, eg. \"ec0 'xpi5' ec2\". Use the command:
+ $ pminfo -f network.interface.mtu
+to discover the names of the installed network interfaces.
+This is most useful to remove the loopback, SLIP and PPP interfaces
+from the rule evaluations.";
+
+string action_expand
+ default = "%vpkt/s[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h interface: %i packets/sec: %v"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200050"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_netif.packets
+ display = no
+ modify = no;
+string enln_units
+ default = pkt/s[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pernetif/util b/src/pmieconf/pernetif/util
new file mode 100644
index 0000000..c096cd3
--- /dev/null
+++ b/src/pmieconf/pernetif/util
@@ -0,0 +1,75 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule per_netif.util
+ summary = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ ( 100 * network.interface.total.bytes $hosts$ $interfaces$ /
+ network.interface.baudrate $hosts$ $interfaces$ )
+ > $threshold$
+ && network.interface.baudrate $hosts$ $interfaces$ > 0
+)"
+ enabled = yes
+ version = 1
+ help =
+"For at least one network interface, the average transfer rate (in
+and/or out) exceeded threshold percent of the peak bandwidth of the
+interface during the last sample interval.";
+
+string rule
+ default = "High network interface utilization"
+ modify = no
+ display = no;
+
+double threshold
+ default = 85
+ help =
+"Threshold in percentage of bandwidth utilization.";
+
+instlist interfaces
+ default = ""
+ help =
+"May be set to a list of network interfaces for which the rule will
+be evaluated, as a subset of configured network interfaces.
+Network interfaces should be separated by white space and may be
+enclosed in single quotes, eg. \"ec0 'xpi5' ec2\". Use the command:
+ $ pminfo -f network.interface.mtu
+to discover the names of the installed network interfaces.
+Setting this variable is most useful to remove the loopback, SLIP
+and PPP interfaces from the rule evaluations.";
+
+string action_expand
+ default = "%v%util[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h interface: %i utilization: %v%"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x200051"
+ display = no
+ modify = no;
+
+# for EnlightenDSM integration:
+string enln_test
+ default = per_netif.util
+ display = no
+ modify = no;
+string enln_units
+ default = %util[%i]
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/pmie_email b/src/pmieconf/pmie_email
new file mode 100755
index 0000000..710fd26
--- /dev/null
+++ b/src/pmieconf/pmie_email
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+# pmie_email is intended for use in pmie actions to send e-mail.
+#
+# the one argument consists of a multi-line message, separated by
+# '|' characters ...
+#
+# "line" 1 - e-mail addressee, as passed to a mail program
+# "line" 2 - mail Subject: will be "pmie alert: " and then this
+# text
+# "line" 2,3,.. - body of the message [optional]
+
+# source the PCP configuration environment variables
+. /etc/pcp.env
+
+prog=`basename $0`
+
+if [ $# -ne 1 ]
+then
+ echo "Usage: $prog long|format|message|as|one|argument"
+ exit 1
+fi
+
+if [ -z "$PCP_MUA" ] ; then
+ for mua in Mail mailx; do
+ if which $mua > /dev/null 2>&1
+ then
+ PCP_MUA=`which $mua`
+ break
+ fi
+ done
+fi
+
+if [ -z "$PCP_MUA" ] ; then
+ echo "Cannot find a mail program"
+ exit 1
+fi
+
+cat <<End-of-File | ${PCP_AWK_PROG} -F\| '
+NF < 2 { print "echo '"'$prog"': need at least \"e-mail addr|subject\" in argument'"'"'"
+ exit 1
+ }
+ { printf "%s -s \"pmie alert: %s\" %s <<End-of-File\n", "'$PCP_MUA'", $2, $1
+ print ""
+ for (i = 3; i <= NF; i++)
+ print $i
+ print "End-of-File"
+ }' | /bin/sh
+$1
+End-of-File
diff --git a/src/pmieconf/pmieconf.c b/src/pmieconf/pmieconf.c
new file mode 100644
index 0000000..0de4ff6
--- /dev/null
+++ b/src/pmieconf/pmieconf.c
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1998-2001, Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <sys/param.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "rules.h"
+
+#define MAXSYMLEN (MAXPATHLEN+1)
+#define MAXVARLEN (512+1)
+#define MAXBUFLEN (1024+1)
+
+static int verbose;
+static int autocreate;
+static int interactive = 1;
+static int pmiefile_modified;
+static char warn[MAXBUFLEN]; /* buffer for any warning messages */
+
+static char help[] = \
+ " 1. help [ { . | all | global | <rule> | <group> } [<variable>] ]\n"
+ " descriptive text on one or more variables or rules\n"
+ " 2. rules [ enabled | disabled ]\n"
+ " list of available, enabled, or disabled rules\n"
+ " 3. groups list of available groups of rules\n"
+ " 4. status print status information\n"
+ " 5. enable { . | all | <rule> | <group> }\n"
+ " switch evaluation on for given rule(s)\n"
+ " 6. disable { . | all | <rule> | <group> }\n"
+ " switch evaluation off for given rule(s)\n"
+ " 7. list { . | all | global | <rule> | <group> } [<variable>]\n"
+ " print values of variables for given rule(s)\n"
+ " 8. modify { . | all | global | <rule> | <group> } <variable> <value>\n"
+ " change the value of a rule variable\n"
+ " 9. undo { . | all | global | <rule> | <group> } [<variable>]\n"
+ " revert to default value of rule variable\n"
+ " 10. verbose [ { on | off } ]\n"
+ " change amount of info displayed ('*' denotes global variables)\n"
+ " 11. quit save changes then exit\n"
+ " 12. abort discard changes then exit";
+
+#define MAXARGS 4
+static char inbuf[MAXARGS][MAXBUFLEN+1]; /* input buffer */
+static char previous[MAXVARLEN+1]; /* buffer for last rule name */
+
+static symbol_t commands[] = {
+#define COMMAND_HELP 1
+ { COMMAND_HELP, "help" }, { COMMAND_HELP, "h" }, { COMMAND_HELP, "?" },
+#define COMMAND_RULES 2
+ { COMMAND_RULES, "rules" }, { COMMAND_RULES, "r" },
+#define COMMAND_GROUPS 3
+ { COMMAND_GROUPS, "groups" }, { COMMAND_GROUPS, "g" },
+#define COMMAND_STATUS 4
+ { COMMAND_STATUS, "status" }, { COMMAND_STATUS, "s" },
+#define COMMAND_ENABLE 5
+ { COMMAND_ENABLE, "enable" }, { COMMAND_ENABLE, "e" },
+#define COMMAND_DISABLE 6
+ { COMMAND_DISABLE, "disable" }, { COMMAND_DISABLE, "d" },
+#define COMMAND_LIST 7
+ { COMMAND_LIST, "list" }, { COMMAND_LIST, "l" },
+#define COMMAND_MODIFY 8
+ { COMMAND_MODIFY, "modify" }, { COMMAND_MODIFY, "m" },
+#define COMMAND_UNDO 9
+ { COMMAND_UNDO, "undo" }, { COMMAND_UNDO, "u" },
+#define COMMAND_VERBOSE 10
+ { COMMAND_VERBOSE, "verbose" }, { COMMAND_VERBOSE, "v" },
+#define COMMAND_QUIT 11
+ { COMMAND_QUIT, "quit" }, { COMMAND_QUIT, "q" },
+#define COMMAND_ABORT 12
+ { COMMAND_ABORT, "abort" }, { COMMAND_ABORT, "a" },
+#define LAST_COMMAND 12
+};
+static int ncommands = (sizeof(commands)/sizeof(commands[0]));
+
+/* io-related stuff */
+extern void setio(int);
+extern void setscroll(void);
+extern void onwinch(int);
+extern int pprintf(char *, ...);
+extern void error(char *, ...);
+
+
+/*
+ * #### simple printing routines ###
+ */
+
+static void
+print_helpstring(char *value)
+{
+ char *s;
+ char *str;
+ int i = 0;
+
+ if (value == NULL) {
+ pprintf(" help: No help available.\n");
+ return;
+ }
+ if ((str = strdup(value)) == NULL) {
+ error("insufficient memory to display help");
+ exit(1);
+ }
+ s = strtok(str, "\n");
+ while (s != NULL) {
+ pprintf("%s%s\n", i++ == 0? " help: ":"\t", s);
+ s = strtok(NULL, "\n");
+ }
+ free(str);
+}
+
+static void
+print_predicatestring(char *value)
+{
+ char *s;
+ char *str;
+
+ if ((str = strdup(value)) == NULL) {
+ error("insufficient memory to display predicate");
+ exit(1);
+ }
+ s = strtok(str, "\n");
+ pprintf("\tpredicate = \n");
+ while (s != NULL) {
+ pprintf("\t %s\n", s);
+ s = strtok(NULL, "\n");
+ }
+ free(str);
+}
+
+/* prints help string for a parameter of a rule */
+void
+print_help(rule_t *rule, char *attrib)
+{
+ atom_t *aptr;
+
+ for (aptr = &rule->self; aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(rule, aptr), attrib) == 0) {
+ if (aptr->help == NULL)
+ goto nohelp;
+ print_helpstring(aptr->help);
+ return;
+ }
+ for (aptr = &globals->self; aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(globals, aptr), attrib) == 0) {
+ if (aptr->help == NULL)
+ goto nohelp;
+ print_helpstring(aptr->help);
+ return;
+ }
+nohelp:
+ error("no help available for variable \"%s\" of rule %s",
+ attrib, rule->self.name);
+}
+
+/* prints a named parameter from a rule, return -1 on failure */
+int
+print_attribute(rule_t *rule, char *attrib, int dohelp)
+{
+ atom_t *aptr;
+ int isattrib = is_attribute(attrib);
+
+ if (isattrib == ATTRIB_PREDICATE && rule != globals)
+ print_predicatestring(rule->predicate);
+ else if (isattrib == ATTRIB_HELP)
+ print_helpstring(rule->self.help);
+ else if (isattrib != -1 && rule != globals) {
+ if (dohelp)
+ pprintf(" var: %s\n help: No help available.\n", attrib);
+ else
+ pprintf("\t%s = %s\n", attrib, get_attribute(attrib, &rule->self));
+ }
+ else {
+ for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(rule, aptr), attrib) == 0) {
+ if (dohelp) {
+ pprintf(" var: %s\n", attrib);
+ print_helpstring(aptr->help);
+ }
+ else
+ pprintf("\t%s = %s\n", attrib, value_string(aptr, 1));
+ return 0;
+ }
+ for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(globals, aptr), attrib) == 0) {
+ if (dohelp) {
+ pprintf(" var: %s\n", attrib);
+ print_helpstring(aptr->help);
+ }
+ else
+ pprintf("\t%s = %s\n", attrib, value_string(aptr, 1));
+ return 0;
+ }
+ error("variable \"%s\" is undefined for rule %s",
+ attrib, rule->self.name);
+ return -1;
+ }
+ return 0;
+}
+
+
+/* one line summary (name and short help) for a given rule */
+void
+print_rule_summary(rule_t *rule, char *prefix)
+{
+ char *str;
+ char fmt[] = "%s%s [%s]\n";
+
+ if ((str = dollar_expand(rule, rule->self.data, 0)) == NULL)
+ return;
+ pprintf(fmt, prefix, rule->self.name, str);
+ free(str);
+}
+
+
+void
+print_rule(rule_t *rule)
+{
+ atom_t *a;
+ int needvars = 1;
+
+ print_rule_summary(rule, " rule: ");
+ if (rule->self.help != NULL)
+ print_helpstring(rule->self.help);
+ if (rule != globals) { /* non-global */
+ print_predicatestring(rule->predicate);
+ pprintf(" vars: enabled = %s\n", rule->self.enabled?"yes":"no");
+ }
+ for (a = rule->self.next; a != NULL; a = a->next) {
+ if (!a->display)
+ continue;
+ pprintf("%s%s = %s\n", (rule == globals && needvars)?
+ " vars: ":"\t", get_aname(rule, a), value_string(a, 1));
+ needvars = 0;
+ }
+ if (verbose && rule != globals) {
+ for (a = globals->self.next; a != NULL; a = a->next) {
+ if (is_overridden(rule, a))
+ continue; /* printed already as part of attribs */
+ pprintf("\t%s = %s (*)\n", get_aname(globals,a), value_string(a,1));
+ }
+ }
+}
+
+
+/* print out the list of unique group names (sort done previously) */
+static int
+print_grouplist(int argcount)
+{
+ int i;
+ char *j;
+ char lastgroup[MAXVARLEN];
+
+ if (argcount != 0) {
+ error("too many arguments for \"groups\" command");
+ return -1;
+ }
+ lastgroup[0] = '\0';
+ for (i = 1; i < rulecount; i++) {
+ if ((j = strchr(rulelist[i].self.name, '.')) != NULL) {
+ *j = '\0'; /* mark end of group name */
+ if (strcmp(rulelist[i].self.name, lastgroup) != 0) {
+ strcpy(lastgroup, rulelist[i].self.name);
+ pprintf(" %s\n", lastgroup);
+ }
+ *j = '.'; /* repair the rule name */
+ }
+ }
+ return 0;
+}
+
+/*
+ * print out the current verbosity setting, running pmies using this
+ * pmie file, total number of rules & number of rules switched on.
+ */
+static int
+print_status(int argcount)
+{
+ int i, count = 0, pcount = 0;
+ char **processes;
+
+ if (argcount != 0) {
+ error("too many arguments for \"status\" command");
+ return -1;
+ }
+
+ for (i = 1; i < rulecount; i++) /* find enabled rules */
+ if (rulelist[i].self.enabled)
+ count++;
+ lookup_processes(&pcount, &processes); /* find running pmies */
+ printf(" verbose: %s\n"
+ " enabled rules: %u of %u\n"
+ " pmie configuration file: %s\n"
+ " pmie %s using this file: ",
+ verbose? "on" : "off", count, rulecount-1, get_pmiefile(),
+ pcount == 1? "process (PID)" : "processes (PIDs)");
+ if (pcount == 0)
+ printf(" (none found)");
+ else {
+ for (i = 0; i < pcount; i++) {
+ printf(" %s", processes[i]);
+ free(processes[i]);
+ }
+ free(processes);
+ }
+ printf("\n");
+ return 0;
+}
+
+int
+write_pmie(void)
+{
+ int i, count;
+ char *msg;
+ dep_t *list;
+
+ if ((msg = write_pmiefile(pmProgname, autocreate)) != NULL) {
+ error(msg);
+ return 1;
+ }
+ if ((count = fetch_deprecated(&list)) > 0) {
+ if (interactive)
+ pprintf(" Warning - some rules have been deprecated:\n");
+ else
+ pprintf("%s: some rules have been deprecated:\n", pmProgname);
+ for (i = 0; i < count; i++) {
+ pprintf(" %s (deprecated, %s)\n", list[i].name, list[i].reason);
+ free(list[i].name);
+ }
+ free(list);
+ pprintf("\n See %s for details\n", get_pmiefile());
+ }
+ return 0;
+}
+
+/* display the list of available, enabled or disabled rules */
+static int
+command_rules(int argcount)
+{
+ int i;
+
+ if (argcount > 1) {
+ error("too many arguments for \"rules\" command");
+ return -1;
+ }
+ if (argcount == 0)
+ for (i = 1; i < rulecount; i++)
+ print_rule_summary(&rulelist[i], " ");
+ else {
+ if (strcmp(inbuf[1], "enabled") == 0) {
+ for (i = 1; i < rulecount; i++)
+ if (rulelist[i].self.enabled)
+ print_rule_summary(&rulelist[i], " ");
+ }
+ else if (strcmp(inbuf[1], "disabled") == 0) {
+ for (i = 1; i < rulecount; i++)
+ if (!rulelist[i].self.enabled)
+ print_rule_summary(&rulelist[i], " ");
+ }
+ else {
+ error("invalid argument for \"rules\" command");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* display or set the verbosity level */
+static int
+command_verbose(int argcount)
+{
+ int sts;
+
+ if (argcount < 1) {
+ printf(" verbose: %s\n", verbose? "on" : "off");
+ return 0;
+ }
+ else if (argcount > 1) {
+ error("too many arguments for \"verbose\" command");
+ return -1;
+ }
+ if (strcmp(inbuf[1], "on") == 0)
+ sts = 1;
+ else if (strcmp(inbuf[1], "off") == 0)
+ sts = 0;
+ else {
+ error("invalid argument, expected \"on\" or \"off\"");
+ return -1;
+ }
+ verbose = sts;
+ return 0;
+}
+
+static int
+command_list(int argcount)
+{
+ unsigned int rcount;
+ rule_t **rptr;
+ char *msg;
+ int all = 0;
+ int sts = 0;
+ int i;
+
+ if (argcount < 1) {
+ error("too few arguments for \"list\" command");
+ return -1;
+ }
+ else if (argcount > 2) {
+ error("too many arguments for \"list\" command");
+ return -1;
+ }
+
+ if (strcmp(".", inbuf[1]) == 0)
+ strcpy(inbuf[1], previous);
+ if (strcmp("all", inbuf[1]) == 0)
+ all = 1;
+ if ((msg = lookup_rules(inbuf[1], &rptr, &rcount, all)) != NULL) {
+ error(msg);
+ return -1;
+ }
+ if (argcount == 1) { /* print out one rule or one group */
+ for (i = 0; i < rcount; i++) {
+ if (i > 0) pprintf("\n");
+ print_rule(rptr[i]);
+ }
+ }
+ else { /* print out one rule/group variable */
+ for (i = 0; i < rcount; i++) {
+ print_rule_summary(rptr[i], " rule: ");
+ if (print_attribute(rptr[i], inbuf[2], 0) == -1)
+ sts = -1; /* failure */
+ }
+ }
+ free(rptr);
+ strcpy(previous, inbuf[1]);
+ return sts;
+}
+
+static int
+command_undo(int argcount)
+{
+ unsigned int rcount;
+ rule_t **rptr;
+ char *msg = NULL;
+ char *var = NULL;
+ int all = 0;
+ int i;
+
+ if (argcount < 1) {
+ error("too few arguments for \"undo\" command");
+ return -1;
+ }
+ else if (argcount > 2) {
+ error("too many arguments for \"undo\" command");
+ return -1;
+ }
+
+ if (strcmp(".", inbuf[1]) == 0)
+ strcpy(inbuf[1], previous);
+ if (strcmp("all", inbuf[1]) == 0)
+ all = 1;
+ if ((msg = lookup_rules(inbuf[1], &rptr, &rcount, all)) != NULL) {
+ error(msg);
+ return -1;
+ }
+ if (argcount == 2)
+ var = inbuf[2];
+
+ for (i = 0; i < rcount; i++)
+ if ((msg = rule_defaults(rptr[i], var)) != NULL)
+ break;
+ free(rptr);
+ if (msg != NULL) {
+ error(msg);
+ return -1;
+ }
+ strcpy(previous, inbuf[1]);
+ pmiefile_modified = 1;
+ return 0;
+}
+
+static int
+command_modify(int command, int argcount)
+{
+ unsigned int rcount;
+ rule_t **rptr;
+ char *msg;
+ int all = 0;
+ int c;
+
+ if (command == COMMAND_MODIFY) {
+ if (argcount != 3) {
+ error("too %s arguments for \"modify\" command",
+ argcount < 3? "few":"many");
+ return -1;
+ }
+ }
+ else if (strcmp(inbuf[1], "global") == 0) {
+ error("invalid argument - \"global\"");
+ return -1;
+ }
+ else if (command == COMMAND_ENABLE) {
+ if (argcount != 1) {
+ error("too %s arguments for \"enable\" command",
+ argcount < 1? "few":"many");
+ return -1;
+ }
+ strcpy(inbuf[2], "enabled");
+ strcpy(inbuf[3], "yes");
+ }
+ else { /* (command == COMMAND_DISABLE) */
+ if (argcount != 1) {
+ error("too %s arguments for \"disable\" command",
+ argcount < 1? "few":"many");
+ return -1;
+ }
+ strcpy(inbuf[2], "enabled");
+ strcpy(inbuf[3], "no");
+ }
+
+ if (strcmp(".", inbuf[1]) == 0)
+ strcpy(inbuf[1], previous);
+ if (strcmp("all", inbuf[1]) == 0)
+ all = 1;
+ if ( ((c = is_attribute(inbuf[2])) != -1) && c != ATTRIB_ENABLED ) {
+ error("no change - variable \"%s\" is always readonly", inbuf[2]);
+ return -1;
+ }
+ if ((msg = lookup_rules(inbuf[1], &rptr, &rcount, all)) != NULL) {
+ error(msg);
+ return -1;
+ }
+
+ for (c = 0; c < rcount; c++) {
+ if ((msg = value_change(rptr[c], inbuf[2], inbuf[3])) != NULL) {
+ error("change aborted - %s", msg);
+ free(rptr);
+ return -1;
+ }
+ }
+ pmiefile_modified = 1;
+ free(rptr);
+ strcpy(previous, inbuf[1]);
+ return 0;
+}
+
+static int
+command_help(int argcount)
+{
+ unsigned int rcount;
+ rule_t **rptr;
+ char *msg;
+ int sts = 0;
+ int all = 0;
+ int i;
+
+ if (argcount < 1) {
+ puts(help);
+ return 0;
+ }
+ else if (argcount > 2) {
+ error("too many arguments for \"help\" command");
+ return -1;
+ }
+
+ if (strcmp(".", inbuf[1]) == 0)
+ strcpy(inbuf[1], previous);
+ if (strcmp("all", inbuf[1]) == 0)
+ all = 1;
+ if ((msg = lookup_rules(inbuf[1], &rptr, &rcount, all)) != NULL) {
+ error(msg);
+ return -1;
+ }
+
+ if (argcount == 1) {
+ for (i = 0; i < rcount; i++) {
+ print_rule_summary(rptr[i], " rule: ");
+ print_helpstring(rptr[i]->self.help);
+ }
+ }
+ else {
+ for (i = 0; i < rcount; i++) {
+ print_rule_summary(rptr[i], " rule: ");
+ if (print_attribute(rptr[i], inbuf[2], 1) == -1)
+ sts = -1; /* failure */
+ }
+ }
+ free(rptr);
+ strcpy(previous, inbuf[1]);
+ return sts;
+}
+
+static void
+command_quit(void)
+{
+ static int done = 0;
+ int i, pcount = 0;
+ char **processes;
+ char *msg;
+
+ /* must only come thru here once, but can be called multiple times */
+ if (done != 0)
+ return;
+ done = 1;
+
+ if (pmiefile_modified && write_pmie() != 0)
+ exit(1);
+
+ /* show any running pmie processes which use this pmie config */
+ if (interactive && pmiefile_modified) {
+ if ((msg = lookup_processes(&pcount, &processes)) != NULL)
+ error(msg);
+ else if (pcount > 0) {
+ pprintf(" %s is in use by %d running pmie process%s:\n\t",
+ get_pmiefile(), pcount, pcount == 1? "":"es");
+ for (i = 0; i < pcount; i++)
+ pprintf("%s ", processes[i]);
+ pprintf("\n Restart %s for the configuration change to take effect.",
+ pcount == 1 ? "this process" : "these processes");
+ pprintf("\n o Use kill(1) to stop; e.g.\tkill -INT ");
+ for (i = 0; i < pcount; i++) {
+ pprintf("%s ", processes[i]);
+ free(processes[i]);
+ }
+ free(processes);
+ pprintf("\n\
+ o Refer to pmie_check(1) for a convenient mechanism for restarting pmie\n\
+ daemons launched under the control of %s/pmie/control;\n\
+ e.g.\t%s/pmie_check -V\n", pmGetConfig("PCP_SYSCONF_DIR"), pmGetConfig("PCP_BINADM_DIR"));
+ }
+ }
+}
+
+/*
+ * workhorse routine - dishes out work depending on user command;
+ * returns 1 on user-quit, 0 on success, -1 on failure
+ */
+static int
+configure(int count)
+{
+ int command = atoi(inbuf[0]);
+
+ if (command <= 0 || command > LAST_COMMAND)
+ command = map_symbol(commands, ncommands, inbuf[0]);
+
+ switch(command) {
+ case COMMAND_HELP:
+ return command_help(count-1);
+ case COMMAND_RULES:
+ return command_rules(count-1);
+ case COMMAND_GROUPS:
+ return print_grouplist(count-1);
+ case COMMAND_STATUS:
+ return print_status(count-1);
+ case COMMAND_VERBOSE:
+ return command_verbose(count-1);
+ case COMMAND_ENABLE: /* shortcut for modify */
+ case COMMAND_DISABLE: /* shortcut for modify */
+ case COMMAND_MODIFY:
+ return command_modify(command, count-1);
+ case COMMAND_LIST:
+ return command_list(count-1);
+ case COMMAND_UNDO:
+ return command_undo(count-1);
+ case COMMAND_QUIT:
+ command_quit();
+ return 1;
+ case COMMAND_ABORT:
+ exit(0);
+ default:
+ error("unrecognised command \"%s\" - try \"help\"", inbuf[0]);
+ return -1;
+ }
+ /*NOTREACHED*/
+}
+
+
+static void
+interact(void)
+{
+ int done = 0;
+ int n, sts;
+
+ if (interactive)
+ printf("Updates will be made to %s\n", get_pmiefile());
+ do {
+ sts = 0;
+ for (n = 0; n < MAXARGS; n++)
+ inbuf[n][0] = '\0';
+ if (interactive) {
+ setio(0);
+ printf("\n%s> ", pmProgname);
+ fflush(stdout);
+ }
+
+ do {
+ if ((n = read_token(stdin, inbuf[sts], MAXBUFLEN, '\n')) == -1) {
+ error("failed to parse argument %d correctly", sts+1);
+ break;
+ }
+ else if (n > 0)
+ sts++;
+ else {
+ if (n == -2) {
+ command_quit();
+ done = 1;
+ }
+ break;
+ }
+ } while (sts <= MAXARGS);
+
+ if (n < 0) /* done (EOF) or error reported above */
+ continue;
+ else if (sts > MAXARGS) {
+ error("too many arguments - try \"help\"");
+ /* consume until '\n' reached... */
+ while ((n = read_token(stdin, inbuf[0], MAXBUFLEN, '\n')) > 0);
+ if (n == -2) {
+ command_quit();
+ done = 1; /* reached EOF, bail out! */
+ }
+ }
+ else if (sts > 0 && !done)
+ done = (configure(sts) == 1);
+ } while (!done);
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ { "", 0, 'c', 0, "an automated pmie configuration by the system" },
+ { "force", 0, 'F', 0, "force creation/update of pmie file, then exit" },
+ { "config", 1, 'f', "FILE", "location of generated pmie configuration file" },
+ { "rules", 1, 'r', "PATH", "path specifying groups of rule files" },
+ { "verbose", 0, 'v', 0, "increase level of diagnostics" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_NOFLUSH,
+ .short_options = "cFf:r:v?",
+ .long_options = longopts,
+ .short_usage = "[options] [ command [args...] ]",
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int force = 0;
+ char *p;
+ char *in_rules = NULL;
+ char *in_pmie = NULL;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'c':
+ autocreate = 1;
+ interactive = 0;
+ break;
+
+ case 'F':
+ force = 1;
+ break;
+
+ case 'f':
+ in_pmie = opts.optarg;
+ break;
+
+ case 'r':
+ in_rules = opts.optarg;
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ }
+ }
+
+ if (force && opts.optind < argc) {
+ pmprintf("%s: cannot use -F option with a command\n", pmProgname);
+ opts.optind = argc;
+ opts.errors++;
+ }
+
+ for (c = 0; opts.optind < argc && c < MAXARGS; c++) {
+ strncpy(inbuf[c], argv[opts.optind++], MAXBUFLEN);
+ inbuf[c][MAXBUFLEN] = '\0';
+ interactive = 0;
+ }
+ if (opts.optind < argc) {
+ pmprintf("%s: too many arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ pmprintf("\nCommands:\n%s\n", help);
+ pmflush();
+ exit(1);
+ }
+
+ if ((p = initialise(in_rules, in_pmie, warn, sizeof(warn))) != NULL) {
+ error(p);
+ exit(1);
+ }
+ sort_rules();
+
+ if (rulecount <= 1) {
+ fprintf(stderr, "%s: no rules were found using rule path: %s\n",
+ pmProgname, get_rules());
+ exit(1);
+ }
+
+ if (force || (*warn && p == NULL)) { /* force/pmie doesn't exist */
+ if (write_pmie() != 0)
+ exit(1);
+ else if (force)
+ exit(0);
+ }
+ else if (*warn) /* some other warning */
+ error(warn);
+
+ if (interactive) {
+ if (!isatty(0)) /* reading commands from a file */
+ interactive = 0;
+ else if (isatty(1)) { /* be $PAGER, handle window-resize */
+ setscroll();
+ onwinch(0);
+ }
+ interact();
+ }
+ else if (configure(c) == -1)
+ exit(1);
+ command_quit();
+ exit(0);
+ /*NOTREACHED*/
+}
diff --git a/src/pmieconf/rate-syscalls.c b/src/pmieconf/rate-syscalls.c
new file mode 100644
index 0000000..eb7643e
--- /dev/null
+++ b/src/pmieconf/rate-syscalls.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2010 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+/*
+ * test program to calibrate system call rates ... just compile
+ * and run:
+ * $ make rate-syscalls
+ * $ ./rate-syscalls
+ *
+ * useful for *cpu/syscall rules
+ */
+
+int
+main()
+{
+ int fd;
+ int i;
+ int n;
+ int c;
+ struct timeval now;
+ struct timeval then;
+ struct timeval eek;
+ double delta;
+ struct hostent *servInfo;
+ int s;
+ struct sockaddr_in myAddr;
+ struct linger noLinger = {1, 0};
+ int scale = 2;
+
+ __pmtimevalNow(&then);
+ n = 600000 * scale;
+ for (i = 0; i < n; i++)
+ getpid();
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("getpid()\t\t\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + n / delta), delta);
+
+ __pmtimevalNow(&then);
+ n = 300000 * scale;
+ for (i = 0; i < n; i++)
+ __pmtimevalNow(&eek);
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("gettimeofday()\t\t\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + n / delta), delta);
+
+ fd = open("/dev/null", 0);
+ n = 150000 * scale;
+ __pmtimevalNow(&then);
+ for (i = 0; i < n; i++) {
+ /* expect EOF */
+ read(fd, &c, 1);
+ }
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("read() at end of file\t\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + n / delta), delta);
+ close(fd);
+
+ fd = open("/dev/null", 0);
+ n = 400000 * scale;
+ __pmtimevalNow(&then);
+ for (i = 0; i < n; i++) {
+ lseek(fd, 0L, 0);
+ }
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("lseek() to start of file\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + n / delta), delta);
+ close(fd);
+
+ unlink("/tmp/creat-clo");
+ n = 20000 * scale;
+ __pmtimevalNow(&then);
+ for (i = 0; i < n; i++) {
+ if ((fd = creat("/tmp/creat-clo", 0644)) < 0) {
+ fprintf(stderr, "creat: %s\n", osstrerror());
+ exit(1);
+ }
+ close(fd);
+ }
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("file creat() and close()\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + 2*n / delta), delta);
+ unlink("/tmp/creat-clo");
+
+ servInfo = gethostbyname("localhost");
+ memset(&myAddr, 0, sizeof(myAddr));
+ myAddr.sin_family = AF_INET;
+ memcpy(&myAddr.sin_addr, servInfo->h_addr, servInfo->h_length);
+ myAddr.sin_port = htons(80);
+ n = 4000 * scale;
+ __pmtimevalNow(&then);
+ for (i = 0; i < n; i++) {
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ fprintf(stderr, "socket: %s\n", netstrerror());
+ exit(1);
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &noLinger, sizeof(noLinger)) < 0) {
+ fprintf(stderr, "setsockopt(SO_LINGER): %s\n", netstrerror());
+ exit(1);
+ }
+
+ if (connect(s, (struct sockaddr*) &myAddr, sizeof(myAddr)) < 0) {
+ fprintf(stderr, "connect: %s\n", netstrerror());
+ exit(1);
+ }
+ close(s);
+ }
+ __pmtimevalNow(&now);
+ delta = now.tv_sec - then.tv_sec +
+ (double)(now.tv_usec - then.tv_usec) / 1000000;
+ printf("socket(), connect() and close()\t- %9d syscalls/sec [%.2f sec]\n",
+ (int)(0.5 + 3*n / delta), delta);
+
+ return 0;
+}
diff --git a/src/pmieconf/rules.c b/src/pmieconf/rules.c
new file mode 100644
index 0000000..cb0c9a0
--- /dev/null
+++ b/src/pmieconf/rules.c
@@ -0,0 +1,2335 @@
+/*
+ * rules.c - rule description parsing routines (rules & pmie config)
+ *
+ * Copyright (c) 1998-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <ctype.h>
+#include <string.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "rules.h"
+#include "stats.h"
+
+#define SEP __pmPathSeparator()
+
+
+#define PMIE_FILE "pmieconf-pmie"
+#define PMIE_VERSION "1" /* local configurations file format version */
+#define RULES_FILE "pmieconf-rules"
+#define RULES_VERSION "1" /* rule description file format version */
+
+#define START_STRING \
+ "// --- START GENERATED SECTION (do not change this section) ---\n"
+#define END_STRING \
+ "// --- END GENERATED SECTION (changes below will be preserved) ---\n"
+#define TOKEN_LENGTH 2048 /* max length of input token, incl string */
+#define LINE_LENGTH 4096
+
+#if !defined(sgi)
+#define PROC_DIR "/proc"
+#else
+#define PROC_DIR "/proc/pinfo"
+#endif
+
+char errmsg[512]; /* error message buffer */
+char rulepath[MAXPATHLEN+1]; /* root of rules files */
+char pmiefile[MAXPATHLEN+1]; /* pmie configuration file */
+char token[TOKEN_LENGTH+1];
+
+rule_t *rulelist; /* global list of rules */
+unsigned int rulecount; /* # rule list elements */
+rule_t *globals; /* list of atoms with global scope */
+
+#define GLOBAL_LEN 7
+static char global_name[] = "global"; /* GLOBAL_LEN chars long */
+static char global_data[] = "generic variables applied to all rules";
+static char global_help[] = \
+ "The global variables are used by all rules, but their values can be\n"
+ "overridden at the level of an individual rule or group of rules.";
+static char yes[] = "yes";
+static char no[] = "no";
+
+static char *filename; /* file currently being parsed */
+static unsigned int linenum; /* input line number */
+
+symbol_t types[] = {
+ { TYPE_STRING, "string" }, /* predicate data types */
+ { TYPE_DOUBLE, "double" },
+ { TYPE_INTEGER, "integer" },
+ { TYPE_UNSIGNED, "unsigned" },
+ { TYPE_PERCENT, "percent" },
+ { TYPE_HOSTLIST, "hostlist" },
+ { TYPE_INSTLIST, "instlist" },
+ { TYPE_PRINT, "print" }, /* action types */
+ { TYPE_SHELL, "shell" },
+ { TYPE_ALARM, "alarm" },
+ { TYPE_SYSLOG, "syslog" },
+ { TYPE_RULE, "rule" }, /* fundamental type */
+};
+int numtypes = (sizeof(types)/sizeof(types[0]));
+
+symbol_t attribs[] = {
+ { ATTRIB_HELP, "help" },
+ { ATTRIB_MODIFY, "modify" },
+ { ATTRIB_ENABLED, "enabled" },
+ { ATTRIB_DISPLAY, "display" },
+ { ATTRIB_DEFAULT, "default" },
+ { ATTRIB_DEFAULT, "summary" }, /* alias for "default" */
+ { ATTRIB_VERSION, "version" }, /* applies to rules only */
+ { ATTRIB_PREDICATE, "predicate" }, /* applies to rules only */
+ { ATTRIB_ENUMERATE, "enumerate" }, /* applies to rules only */
+};
+int numattribs = (sizeof(attribs)/sizeof(attribs[0]));
+
+/* pmiefile variables */
+static int gotpath; /* state flag - has realpath been run */
+static char *save_area; /* holds text to restore on write */
+static int sa_size; /* current size of save area */
+static int sa_mark = 1; /* number used chars in save area, 1 for \0 */
+static dep_t *dlist; /* list of depreciated rules */
+static int dcount; /* number of entries in dlist */
+static char drulestring[] = "rule definition no longer exists";
+static char dverstring[] = "rule version no longer supported";
+
+/* io-related stuff */
+extern int resized(void);
+
+char *get_pmiefile(void) { return &pmiefile[0]; }
+char *get_rules(void) { return &rulepath[0]; }
+
+char *
+get_aname(rule_t *r, atom_t *a)
+{
+ if (r == globals)
+ return &a->name[GLOBAL_LEN]; /* lose "globals." at the start */
+ return a->name;
+}
+
+
+/*
+ * #### error reporting routines ###
+ */
+
+static void
+alloc_error(size_t request)
+{
+ if (linenum == 0) /* parsing user input, not a file */
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for requested operation.\n"
+ " requested: %u bytes", (unsigned int)request);
+ else
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for parsing file.\n"
+ " requested: %u bytes", (unsigned int)request);
+}
+
+static void
+parse_error(char *expected, char *found)
+{
+ if (linenum == 0) /* parsing user input, not a file */
+ snprintf(errmsg, sizeof(errmsg), "input is invalid - expected %.60s, got \"%.60s\"",
+ expected, found);
+ else
+ snprintf(errmsg, sizeof(errmsg), "file parsing error.\n"
+ " line number: %u (\"%s\")\n"
+ " expected: %.60s\n"
+ " found: %.60s", linenum, filename, expected, found);
+}
+
+/* report attribute format error */
+static void
+type_error(char *attrib, char *expected)
+{
+ snprintf(errmsg, sizeof(errmsg), "%s's value is invalid.\n"
+ " It should %s.", attrib, expected);
+}
+
+
+/*
+ * #### search routines ###
+ */
+
+char *
+find_rule(char *name, rule_t **rule)
+{
+ int i;
+
+ for (i = 0; i < rulecount; i++) {
+ if (strcmp(rulelist[i].self.name, name) == 0) {
+ *rule = &rulelist[i];
+ return NULL;
+ }
+ }
+ snprintf(errmsg, sizeof(errmsg), "rule named \"%s\" does not exist", name);
+ return errmsg;
+}
+
+/* is global attribute 'atom' overridden by a local in 'rule's atom list */
+int
+is_overridden(rule_t *rule, atom_t *atom)
+{
+ atom_t *aptr;
+
+ for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(globals, atom), get_aname(rule, aptr)) == 0)
+ return 1;
+ return 0;
+}
+
+/* tests whether a rule is in the fullname group, if so returns 0 */
+int
+rule_match(char *fullname, char *rulename)
+{
+ char *s;
+
+ /* if fullname == rulename, then obvious match */
+ if (strcmp(fullname, rulename) == 0)
+ return 1;
+ /* fullname may be a group, so match against rulename's groups */
+ s = strcpy(token, rulename); /* reuse the token buffer */
+ while ((s = strrchr(s, '.')) != NULL) {
+ s[0] = '\0';
+ if (strcmp(token, fullname) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+/* find rule or set of rules in given rule or group name */
+char *
+lookup_rules(char *name, rule_t ***rlist, unsigned int *count, int all)
+{
+ size_t size;
+ rule_t **rptr = NULL;
+ unsigned int i;
+ unsigned int matches = 0;
+
+ /* search through the rulelist and build up rlist & count */
+ for (i = 0; i < rulecount; i++) {
+ /* don't match globals if we've been asked for "all" */
+ if ((all && i > 0) || rule_match(name, rulelist[i].self.name)) {
+ size = (1 + matches) * sizeof(rule_t *);
+ if ((rptr = (rule_t **)realloc(rptr, size)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for rule search"
+ " (needed %u bytes)\n", (unsigned int)size);
+ return errmsg;
+ }
+ rptr[matches] = &rulelist[i];
+ matches++;
+ }
+ }
+ if (matches == 0) {
+ snprintf(errmsg, sizeof(errmsg), "no group or rule names match \"%s\"", name);
+ return errmsg;
+ }
+ *rlist = rptr; /* rlist must be freed by caller */
+ *count = matches;
+ return NULL;
+}
+
+
+/*
+ * #### memory management routines ###
+ */
+
+static char *
+alloc_string(size_t size)
+{
+ char *p;
+
+ if ((p = (char *)malloc(size)) == NULL)
+ alloc_error(size);
+ return p;
+}
+
+atom_t *
+alloc_atom(rule_t *r, atom_t atom, int global)
+{
+ atom_t *aptr;
+ atom_t *tmp;
+
+ /* create some space and copy in the atom data we have already */
+ if ((aptr = (atom_t *)malloc(sizeof(atom_t))) == NULL) {
+ alloc_error(sizeof(atom_t));
+ return NULL;
+ }
+ *aptr = atom;
+ aptr->next = NULL; /* want contents of this atom, but not rest of list */
+ if (global) { /* applies to all rules */
+ r = rulelist;
+ aptr->global = 1;
+ }
+
+ aptr->next = NULL; /* want contents of this atom, but not rest of list */
+
+ /* stick into the list of atoms associated with this rule */
+ if (r->self.next == NULL)
+ r->self.next = aptr; /* insert at head of list */
+ else {
+ for (tmp = r->self.next; tmp->next != NULL; tmp = tmp->next);
+ tmp->next = aptr; /* append at tail of list */
+ }
+
+ return aptr;
+}
+
+rule_t *
+alloc_rule(rule_t rule)
+{
+ size_t size;
+ rule_t *rptr;
+
+ /* first check that name is unique */
+ if (find_rule(rule.self.name, &rptr) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "rule name \"%s\" has already been used, duplicate name"
+ " found in:\n\t\"%.60s\", line %u.", rule.self.name, filename, linenum);
+ return NULL;
+ }
+ size = (rulecount+1) * sizeof(rule_t);
+ if ((rulelist = globals = (rule_t *)realloc(rulelist, size)) == NULL) {
+ alloc_error(size);
+ return NULL;
+ }
+ rptr = &rulelist[rulecount];
+ *rptr = rule;
+ rulecount++;
+ return rptr;
+}
+
+
+/*
+ * #### misc parsing routines ###
+ */
+
+/* given string contains no isgraph chars? */
+int
+empty_string(char *s)
+{
+ char *str = s;
+ while (*str != '\0') {
+ if (isgraph((int)*str))
+ return 0;
+ str++;
+ }
+ return 1;
+}
+
+
+/* lookup keyword, returns symbol identifier or -1 if not there */
+int
+map_symbol(symbol_t *table, int tsize, char *symbol)
+{
+ int i;
+
+ for (i = 0; i < tsize; i++) {
+ if (strcmp(symbol, table[i].symbol) == 0)
+ return table[i].symbol_id;
+ }
+ return -1;
+}
+
+/* lookup symbol identifier, returns keyword or NULL if not there */
+char *
+map_identifier(symbol_t *table, int tsize, int symbol_id)
+{
+ int i;
+
+ for (i = 0; i < tsize; i++) {
+ if (symbol_id == table[i].symbol_id)
+ return table[i].symbol;
+ }
+ return NULL;
+}
+
+
+/* parse yes/no attribute value; returns 0 no, 1 yes, -1 error */
+int
+map_boolean(char *token)
+{
+ if (token[0] == 'y')
+ return 1;
+ if (token[0] == 'n')
+ return 0;
+ parse_error("yes or no", token);
+ return -1;
+}
+
+
+/* scan token from string, return 1 ok, 0 no more, -1 error */
+int
+string_token(char **scan, char *token)
+{
+ char *s = *scan;
+ char *t = token;
+
+ while (! isgraph((int)*s) || *s == ',') {
+ if (*s == '\0')
+ return 0;
+ s++;
+ }
+
+ if (*s == '\'') { /* quoted token */
+ *t++ = *s++;
+ while (*s != '\'') {
+ if (*s == '\\')
+ s++;
+ if (*s == '\0')
+ return -1;
+ *t++ = *s++;
+ }
+ *t++ = *s++;
+ }
+ else { /* ordinary token */
+ while (isgraph((int)*s) && *s != ',')
+ *t++ = *s++;
+ }
+
+ *t = '\0';
+ *scan = s;
+ return 1;
+}
+
+
+/* check proposed value for type, returns NULL/failure message */
+char *
+validate(int type, char *name, char *value)
+{
+ int x;
+ char *s;
+ double d;
+ /*
+ * Below we don't care about the value from strtol() and strtoul()
+ * we're interested in updating the pointer "s". The messiness is
+ * thanks to gcc and glibc ... strtol() amd strtoul() are marked
+ * __attribute__((warn_unused_result)) ... to avoid warnings on all
+ * platforms, assign to dummy variables that are explicitly marked
+ * unused.
+ */
+ long l __attribute__((unused));
+ unsigned long ul __attribute__((unused));
+
+ switch (type) {
+ case TYPE_RULE:
+ case TYPE_STRING:
+ break;
+ case TYPE_SHELL:
+ case TYPE_PRINT:
+ case TYPE_ALARM:
+ case TYPE_SYSLOG:
+ if (map_boolean(value) < 0)
+ return errmsg;
+ break;
+ case TYPE_DOUBLE:
+ d = strtod(value, &s);
+ if (*s != '\0') {
+ type_error(name, "be a real number");
+ return errmsg;
+ }
+ break;
+ case TYPE_INTEGER:
+ l = strtol(value, &s, 10);
+ if (*s != '\0') {
+ type_error(name, "be an integer number");
+ return errmsg;
+ }
+ break;
+ case TYPE_UNSIGNED:
+ ul = strtoul(value, &s, 10);
+ if (*s != '\0') {
+ type_error(name, "be a positive integer number");
+ return errmsg;
+ }
+ break;
+ case TYPE_PERCENT:
+ if ((s = strrchr(value, '%')) != NULL) /* % as final char is OK */
+ *s = '\0';
+ d = strtod(value, &s);
+ if (*s != '\0' || d < 0.0 || d > 100.0) {
+ type_error(name, "be a percentage between 0.0 and 100.0");
+ return errmsg;
+ }
+ break;
+ case TYPE_HOSTLIST:
+ case TYPE_INSTLIST:
+ if ((s = alloc_string(strlen(value)+1)) == NULL)
+ return errmsg;
+ while ((x = string_token(&value, s)) > 0)
+ ;
+ if (x < 0) {
+ type_error(name, "include a closing single quote");
+ return errmsg;
+ }
+ free(s);
+ break;
+ }
+ return NULL;
+}
+
+
+/*
+ * printable string form of atoms value, returns NULL terminated string
+ * pp (pretty print) argument valued 1 means use format appropriate for
+ * a user interface
+ */
+
+char *
+value_string(atom_t *atom, int pp)
+{
+ int key;
+ int i = 0;
+ int start = 1;
+ int quoted = 0;
+ char *s;
+
+ switch (atom->type) {
+ case TYPE_RULE:
+ case TYPE_STRING:
+ if (pp) {
+ snprintf(token, sizeof(token), "\"%s\"", atom->data);
+ return token;
+ }
+ return atom->data;
+ case TYPE_PRINT:
+ case TYPE_SHELL:
+ case TYPE_ALARM:
+ case TYPE_SYSLOG:
+ return atom->enabled? yes : no;
+ case TYPE_HOSTLIST:
+ case TYPE_INSTLIST:
+ if (pp) token[i++] = '"';
+ if (atom->type == TYPE_HOSTLIST) key = ':';
+ else key = '#';
+ for (s = atom->data; *s != '\0'; s++) {
+ if (!isspace((int)*s)) {
+ if (start && !pp) {
+ token[i++] = key;
+ token[i++] = '\'';
+ if (*s != '\'')
+ quoted = 0;
+ else if (!quoted) {
+ quoted = 1;
+ start = 0;
+ continue;
+ }
+ }
+ else if (*s == '\'' && !start && !pp)
+ quoted = 0;
+ start = 0;
+ }
+ else if (!pp && !quoted) {
+ quoted = 0;
+ if (i > 0 && token[i-1] != '\'')
+ token[i++] = '\'';
+ start = 1;
+ }
+ token[i++] = *s;
+ }
+ if (!pp && i > 0 && token[i-1] != '\'')
+ token[i++] = '\'';
+ else if (pp) token[i++] = '"';
+ token[i++] = '\0';
+ return token;
+ case TYPE_DOUBLE:
+ snprintf(token, sizeof(token), "%g", strtod(atom->data, &s));
+ return token;
+ case TYPE_INTEGER:
+ snprintf(token, sizeof(token), "%ld", strtol(atom->data, &s, 10));
+ return token;
+ case TYPE_UNSIGNED:
+ snprintf(token, sizeof(token), "%lu", strtoul(atom->data, &s, 10));
+ return token;
+ case TYPE_PERCENT:
+ snprintf(token, sizeof(token), "%g%c", strtod(atom->data, &s), pp? '%':'\0');
+ return token;
+ }
+ return NULL;
+}
+
+
+/* #### rules file parsing routines #### */
+
+
+/* returns attrib number or -1 if not an attribute */
+int
+is_attribute(char *aname)
+{
+ return map_symbol(attribs, numattribs, aname);
+}
+
+/* returns attrib value as a string, or NULL on error */
+char *
+get_attribute(char *attrib, atom_t *atom)
+{
+ char *value = NULL;
+
+ switch (map_symbol(attribs, numattribs, attrib)) {
+ case ATTRIB_HELP:
+ value = atom->help;
+ break;
+ case ATTRIB_MODIFY:
+ if (atom->modify) value = yes;
+ else value = no;
+ break;
+ case ATTRIB_ENABLED:
+ if (atom->enabled) value = yes;
+ else value = no;
+ break;
+ case ATTRIB_DISPLAY:
+ if (atom->display) value = yes;
+ else value = no;
+ break;
+ case ATTRIB_DEFAULT:
+ if (IS_ACTION(atom->type)) {
+ if (atom->enabled) value = yes;
+ else value = no;
+ }
+ else
+ value = atom->data;
+ break;
+ }
+ return value;
+}
+
+
+/*
+ * #### sorting routines ###
+ */
+
+static int
+compare_rules(const void *a, const void *b)
+{
+ rule_t *ra = (rule_t *)a;
+ rule_t *rb = (rule_t *)b;
+ return strcmp(ra->self.name, rb->self.name);
+}
+
+void
+sort_rules(void)
+{
+ /* start at second array element so that 'globals' is skipped */
+ qsort(&rulelist[1], rulecount-1, sizeof(rule_t), compare_rules);
+}
+
+
+/* revert to default rules file values for a single atom (enabled/data/both) */
+static char *
+atom_defaults(atom_t *a, atom_t *p, char *param)
+{
+ int sts = map_symbol(attribs, numattribs, param);
+
+ if (sts != -1) { /* an attribute - is it valid? */
+ if (sts == ATTRIB_ENABLED) {
+ if (a->global) { /* this was a global atom promoted to local */
+ if (p) p->next = a->next;
+ free(a->name);
+ free(a);
+ a = NULL;
+ }
+ else {
+ a->enabled = a->denabled; /* reset enabled flag */
+ a->changed = 0;
+ }
+ return NULL;
+ }
+ snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is inappropriate for this "
+ "operation", param);
+ return errmsg;
+ }
+ else {
+ if (a->global) { /* this was a global atom promoted to local */
+ if (p) p->next = a->next;
+ free(a->name);
+ free(a);
+ a = NULL;
+ }
+ else {
+ if (strcmp(a->data, a->ddata) != 0) { /* need to alloc mem? */
+ free(a->data);
+ if ((a->data = strdup(a->ddata)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to set defaults");
+ return errmsg;
+ }
+ }
+ a->enabled = a->denabled;
+ a->changed = 0;
+ }
+ return NULL;
+ }
+}
+
+/* revert to default rules values for a rule or attribute (enabled/data/both) */
+char *
+rule_defaults(rule_t *rule, char *param)
+{
+ atom_t *aptr;
+ atom_t *prev = NULL;
+
+ if (param == NULL) { /* for this rule, reset all attributes */
+ for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) {
+ atom_defaults(aptr, prev, aptr->name);
+ prev = aptr;
+ }
+ }
+ else { /* find the associated atom, and just reset that */
+ if (map_symbol(attribs, numattribs, param) != -1) {
+ rule->self.enabled = rule->self.denabled; /* reset enabled flag */
+ rule->self.changed = 0;
+ return NULL;
+ }
+ for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) {
+ if (strcmp(get_aname(rule, aptr), param) == 0)
+ return atom_defaults(aptr, prev, param);
+ prev = aptr;
+ }
+ }
+ return NULL;
+}
+
+/* set an attribute field in an atom; returns NULL/failure message */
+static char *
+set_attribute(rule_t *r, atom_t *atom, int attrib, char *value, int changed)
+{
+ char *s;
+ int sts;
+
+ switch(attrib) {
+ case ATTRIB_HELP:
+ if (empty_string(value)) {
+ parse_error("non-empty string for help", value);
+ return errmsg;
+ }
+ if ((s = alloc_string(strlen(value)+1)) == NULL)
+ return errmsg;
+ atom->help = strcpy(s, value);
+ break;
+ case ATTRIB_MODIFY:
+ if ((sts = map_boolean(value)) < 0)
+ return errmsg;
+ atom->modify = sts;
+ break;
+ case ATTRIB_ENABLED:
+ if ((sts = map_boolean(value)) < 0)
+ return errmsg;
+ if (!changed) /* initially, set enabled to default */
+ atom->denabled = sts;
+ atom->enabled = sts;
+ break;
+ case ATTRIB_DISPLAY:
+ if ((sts = map_boolean(value)) < 0)
+ return errmsg;
+ atom->display = sts;
+ break;
+ case ATTRIB_DEFAULT:
+ if (IS_ACTION(atom->type) && changed) {
+ if ((sts = map_boolean(value)) < 0)
+ return errmsg;
+ atom->enabled = sts;
+ }
+ else { /* actions from rules file (string) handled here too... */
+ if (!IS_ACTION(atom->type) &&
+ (validate(atom->type, get_aname(r, atom), value) != NULL))
+ return errmsg;
+ sts = strlen(value)+1;
+ if ((s = alloc_string(sts)) == NULL)
+ return errmsg;
+ atom->data = strcpy(s, value);
+ if (!changed) { /* initially, set the default as well */
+ if ((s = alloc_string(sts)) == NULL) {
+ free(atom->data);
+ atom->data = NULL;
+ return errmsg;
+ }
+ atom->ddata = strcpy(s, value);
+ }
+ }
+ break;
+ }
+ if (changed)
+ atom->changed = 1;
+ return NULL;
+}
+
+/* set a parameter field in a rule; returns NULL/failure message */
+char *
+value_change(rule_t *rule, char *param, char *value)
+{
+ int sts;
+ atom_t *aptr;
+
+ if ((sts = map_symbol(attribs, numattribs, param)) != -1)
+ return set_attribute(rule, &rule->self, sts, value, 1);
+ else {
+ for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) {
+ if (strcmp(get_aname(rule, aptr), param) == 0)
+ return set_attribute(rule, aptr, ATTRIB_DEFAULT, value, 1);
+ }
+ /* if found in globals, promote the global to customised local.. */
+ for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next) {
+ if (strcmp(get_aname(globals, aptr), param) == 0) {
+ if ((aptr = alloc_atom(rule, *aptr, 0)) == NULL)
+ return errmsg;
+ if ((aptr->name = strdup(get_aname(globals, aptr))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to change value");
+ return errmsg;
+ }
+ return set_attribute(globals, aptr, ATTRIB_DEFAULT, value, 1);
+ }
+ }
+ }
+ snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is undefined for rule %s",
+ param, rule->self.name);
+ return errmsg;
+}
+
+static char *
+append_string(char *s, char *append, int len)
+{
+ size_t size = (strlen(s) + len + 1);
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - \"%s\" + (%d)\"%s\" = (%d chars)\n",
+ s, len, append, size);
+#endif
+ if ((s = (char *)realloc(s, size)) == NULL)
+ return NULL;
+ strncat(s, append, len);
+ s[size-1] = '\0';
+ return s;
+}
+
+/* fix up value strings by doing variable expansion */
+char *
+dollar_expand(rule_t *rule, char *string, int pp)
+{
+ atom_t *aptr;
+ char *tmp, *r;
+ char *sptr;
+ char *s;
+ char *mark = NULL;
+ char localbuf[TOKEN_LENGTH];
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - dollar_expand %s in %s\n", string, rule->self.name);
+#endif
+ if ((s = (char *)malloc(sizeof(char))) == NULL)
+ return NULL;
+ *s = '\0';
+
+ for (sptr = string; *sptr != '\0'; sptr++) {
+ if (*sptr == '\\' && *(sptr+1) == '$') { /* skip escaped $ */
+ if ((s = append_string(s, sptr+1, 1)) == NULL)
+ return NULL;
+ sptr++; /* move passed the escaped char */
+ continue;
+ }
+ if (*sptr == '$') {
+ if (mark == NULL) /* start of an expansion section */
+ mark = sptr+1;
+ else { /* end of an expansion section */
+ /* look through atom list & if not there search globally */
+ strncpy(localbuf, mark, sptr - mark);
+ localbuf[sptr - mark] = '\0';
+ mark = NULL;
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - expand localbuf: %s\n", localbuf);
+#endif
+ if ((tmp = get_attribute(localbuf, &rule->self)) == NULL) {
+ for (aptr = &rule->self; tmp == NULL && aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(rule, aptr), localbuf) == 0)
+ tmp = value_string(aptr, pp);
+ for (aptr = globals->self.next; tmp == NULL && aptr != NULL; aptr = aptr->next)
+ if (strcmp(get_aname(globals, aptr), localbuf) == 0)
+ tmp = value_string(aptr, pp);
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - expanded localbuf? %s\n", tmp);
+#endif
+ if (tmp == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "variable \"$%s$\" in %s is undefined",
+ localbuf, rule->self.name);
+ free(s);
+ return NULL;
+ }
+ }
+ if (tmp != NULL) {
+ if ((r = dollar_expand(rule, tmp, pp)) == NULL) {
+ free(s);
+ return NULL;
+ }
+ if ((s = append_string(s, r, strlen(r))) == NULL) {
+ free(r);
+ return NULL;
+ }
+ free(r);
+ }
+ }
+ }
+ else if (mark == NULL) { /* need memory to hold this character */
+ if ((s = append_string(s, sptr, 1)) == NULL)
+ return NULL;
+ }
+ }
+ if (mark != NULL) /* no terminating '$' */
+ s = append_string(s, mark, strlen(mark));
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - expanded '%s' to '%s'\n", string, s);
+#endif
+ return s;
+}
+
+
+/*
+ * #### main parsing routines ###
+ */
+
+/* need a SIGWINCH-aware read routine for interactive pmieconf */
+static int
+mygetc(FILE *f)
+{
+ int c;
+
+ for (;;) {
+ setoserror(0);
+ c = getc(f);
+ /* did we get told to resize the window during read? */
+ if (c == -1 && oserror() == EINTR && resized() == 1)
+ continue; /* signal handled, try reading again */
+ break;
+ }
+ return c;
+}
+
+/*
+ * skip leading white space and comments, return first character in next token
+ * or zero on end of file
+ */
+static int
+prime_next_pread(FILE *f, int end)
+{
+ int c;
+
+ do {
+ c = mygetc(f);
+ if (c == '#')
+ do {
+ c = mygetc(f);
+ } while (c != '\n' && c != end && c != EOF);
+ if (c == end)
+ return 0;
+ else if (c == EOF)
+ return -2;
+ if (c == '\n' && end != '\n')
+ linenum++;
+ } while (!isgraph(c));
+ return c;
+}
+
+/*
+ * read next input token; returns 1 ok, 0 end, -1 error, -2 EOF (if end!=EOF)
+ * nb: `end' can be either EOL or EOF, depending on use of this routine
+ */
+int
+read_token(FILE *f, char *token, int token_length, int end)
+{
+ int c;
+ int n = 0;
+
+ switch (c = prime_next_pread(f, end)) {
+ case 0: /* end */
+ case -2: /* EOF */
+ return c;
+ case '"': /* scan string */
+ c = mygetc(f);
+ while (c != '"') {
+ if (c == '\\')
+ c = mygetc(f);
+ if (c == end || c == EOF || n == token_length) {
+ token[n] = '\0';
+ parse_error("end-of-string", token);
+ return -1;
+ }
+ if (c == '\n' && end != '\n')
+ linenum++;
+ token[n++] = c;
+ c = mygetc(f);
+ }
+ break;
+ case ';':
+ case '=':
+ token[n++] = c; /* single char token */
+ break;
+ default: /* some other token */
+ while (isgraph(c)) {
+ if (c == '=' || c == ';') {
+ ungetc(c, f);
+ break;
+ }
+ if (n == token_length) {
+ token[n] = '\0';
+ parse_error("end-of-token", token);
+ return -1;
+ }
+ token[n++] = c;
+ c = mygetc(f);
+ if (c == end || c == EOF)
+ ungetc(c, f);
+ }
+ if (c == '\n' && end != '\n')
+ linenum++;
+ break;
+ }
+
+ token[n] = '\0';
+ return 1;
+}
+
+/*
+ * get attribute list part of an atom; returns -1 on error, 0 on reaching
+ * the end of the attribute list, and 1 at end of each attribute.
+ */
+static int
+read_next_attribute(FILE *f, char **attr, char **value)
+{
+ int sts;
+
+ if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0) {
+ if (sts == 0)
+ parse_error("attribute or ';'", "end-of-file");
+ return -1;
+ }
+ if (token[0] == ';')
+ return 0;
+ if (map_symbol(attribs, numattribs, token) < 0) {
+ parse_error("attribute keyword", token);
+ return -1;
+ }
+ if ((*attr = alloc_string(strlen(token)+1)) == NULL)
+ return -1;
+ strcpy(*attr, token);
+ if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0
+ || token[0] != '=') {
+ if (sts == 0)
+ parse_error("=", "end-of-file");
+ else
+ parse_error("=", token);
+ free(*attr);
+ return -1;
+ }
+ if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0) {
+ if (sts == 0)
+ parse_error("attribute value", "end-of-file");
+ else
+ parse_error("attribute value", token);
+ free(*attr);
+ return -1;
+ }
+ if ((*value = alloc_string(strlen(token)+1)) == NULL) {
+ free(*attr);
+ return -1;
+ }
+ strcpy(*value, token);
+ return 1;
+}
+
+
+/* parse an atom, return NULL/failure message */
+static char *
+read_atom(FILE *f, rule_t *r, char *name, int type, int global)
+{
+ int sts;
+ int attrib;
+ char *attr;
+ char *value;
+ atom_t atom;
+
+ memset(&atom, 0, sizeof(atom_t));
+ atom.name = name;
+ atom.type = type;
+ atom.enabled = atom.display = atom.modify = 1; /* defaults */
+ for (;;) {
+ if ((sts = read_next_attribute(f, &attr, &value)) < 0)
+ return errmsg;
+ else if (sts == 0) { /* end of parameter list */
+ if (alloc_atom(r, atom, global) == NULL)
+ return errmsg;
+ break;
+ }
+ else {
+ if ((attrib = map_symbol(attribs, numattribs, attr)) < 0) {
+ parse_error("attribute keyword", attr);
+ goto fail;
+ }
+ if (set_attribute(r, &atom, attrib, value, 0) != NULL)
+ goto fail;
+ free(attr);
+ free(value);
+ }
+ }
+ return NULL;
+
+fail:
+ free(attr);
+ free(value);
+ return errmsg;
+}
+
+
+/* parse type-identifier pair, return NULL/failure message */
+static char *
+read_type(FILE *f, rule_t *r, int *type, int *global, char **name)
+{
+ int sts;
+
+ /* read type - rule, percent, double, unsigned, string... */
+ if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) < 0)
+ return errmsg;
+ else if (sts == 0)
+ return NULL;
+ if ((*type = map_symbol(types, numtypes, token)) < 0) {
+ parse_error("type keyword", token);
+ return errmsg;
+ }
+
+ /* read name identifying this rule/atom of type '*type' */
+ if ((sts = read_token(f, token, TOKEN_LENGTH, EOF)) <= 0)
+ return errmsg;
+ if ((*name = alloc_string(strlen(token)+1)) == NULL)
+ return errmsg;
+ strcpy(*name, token);
+ *global = (strncmp(*name, "global.", GLOBAL_LEN) == 0)? 1 : 0;
+
+ /* do some simple validity checks */
+ if (IS_RULE(*type) && strncmp(*name, global_name, GLOBAL_LEN-1) == 0) {
+ snprintf(errmsg, sizeof(errmsg), "rule name may not be \"%s\" - this is reserved",
+ global_name);
+ free(*name);
+ return errmsg;
+ }
+ if (r == NULL) { /* any rule defined yet? - simple validity checks */
+ if (*global && IS_RULE(*type)) {
+ snprintf(errmsg, sizeof(errmsg), "rules not allowed in global group: \"%s\"", *name);
+ free(*name);
+ return errmsg;
+ }
+ else if (!*global && !IS_RULE(*type)) { /* not global, and no rule */
+ snprintf(errmsg, sizeof(errmsg), "no rule defined, cannot make sense of %s \"%s\""
+ " without one\n line number: %u (\"%s\")\n",
+ types[*type].symbol, *name, linenum, filename);
+ free(*name);
+ return errmsg;
+ }
+ }
+ return NULL;
+}
+
+/* set an attribute field in an atom; returns NULL/failure message */
+static char *
+set_rule_attribute(rule_t *rule, int attrib, char *value)
+{
+ char *s;
+
+ if (attrib == ATTRIB_PREDICATE) {
+ if ((s = alloc_string(strlen(value)+1)) == NULL)
+ return errmsg;
+ rule->predicate = strcpy(s, value);
+ return NULL;
+ }
+ if (attrib == ATTRIB_ENUMERATE) {
+ if ((s = alloc_string(strlen(value)+1)) == NULL)
+ return errmsg;
+ rule->enumerate = strcpy(s, value);
+ return NULL;
+ }
+ else if (attrib == ATTRIB_VERSION) {
+ rule->version = strtoul(value, &s, 10);
+ if (*s != '\0') {
+ parse_error("version number", "be a positive integer number");
+ return errmsg;
+ }
+ return NULL;
+ }
+ /* else */
+ return set_attribute(rule, &rule->self, attrib, value, 0);
+}
+
+
+/* parse a single "rule" expression, return NULL/failure message */
+static char *
+read_rule(FILE *f, rule_t **r, char *name)
+{
+ int sts;
+ int attrib;
+ char *attr;
+ char *value;
+ rule_t rule;
+
+ memset(&rule, 0, sizeof(rule_t));
+ rule.self.name = name;
+ rule.self.type = TYPE_RULE;
+ rule.version = rule.self.enabled = rule.self.display = 1; /* defaults */
+ for (;;) {
+ if ((sts = read_next_attribute(f, &attr, &value)) < 0)
+ return errmsg;
+ else if (sts == 0) { /* end of attribute list */
+ if ((*r = alloc_rule(rule)) == NULL)
+ return errmsg;
+ break;
+ }
+ else {
+ if ((attrib = map_symbol(attribs, numattribs, attr)) < 0) {
+ parse_error("rule attribute keyword", attr);
+ goto fail;
+ }
+ if (set_rule_attribute(&rule, attrib, value) != NULL)
+ goto fail;
+ free(attr);
+ free(value);
+ }
+ }
+ return NULL;
+
+fail:
+ free(attr);
+ free(value);
+ return errmsg;
+}
+
+
+/* parse rule description file; returns NULL/failure message */
+static char *
+read_all_rules(FILE *f)
+{
+ rule_t *rule = NULL; /* current rule */
+ char *name = NULL;
+ int type = 0;
+ int global = 0;
+
+ /* rule files have quite a simple grammar, along these lines:
+ TYPE identifier [ ATTRIB '=' value ]* ';'
+ */
+ for (;;) {
+ if (read_type(f, rule, &type, &global, &name) != NULL)
+ return errmsg;
+ if (feof(f)) /* end of file reached without error */
+ break;
+ if (type == TYPE_RULE) {
+ if (read_rule(f, &rule, name) != NULL)
+ return errmsg;
+ }
+ else {
+ if (read_atom(f, rule, name, type, global) != NULL)
+ return errmsg;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ * validate header of rule description file, return NULL/failure message
+ */
+static char *
+read_pheader(FILE *f)
+{
+ int c;
+
+ c = getc(f);
+ if (c != '#' || read_token(f, token, TOKEN_LENGTH, EOF) != 1 ||
+ strcmp(token, RULES_FILE) != 0 ||
+ read_token(f, token, TOKEN_LENGTH, EOF) != 1) {
+ snprintf(errmsg, sizeof(errmsg), "%s is not a rule description file (bad header)\n"
+ "found \"%s\", expected \"%s\"", filename,
+ token, RULES_FILE);
+ return errmsg;
+ }
+ else if (strcmp(token, RULES_VERSION) != 0) { /* one version only */
+ snprintf(errmsg, sizeof(errmsg), "unknown version number in %s: \"%s\" (expected %s)",
+ filename, token, RULES_VERSION);
+ return errmsg;
+ }
+ return NULL;
+}
+
+
+/*
+ * builds up rule data structures for all rule files in given directory
+ * and all its subdirectories, returns NULL/failure message
+ */
+char *
+read_rule_subdir(char *subdir)
+{
+ struct stat sbuf;
+ struct dirent *dp;
+ FILE *fp;
+ DIR *dirp;
+ char fullpath[MAXPATHLEN+1];
+
+ if (stat(subdir, &sbuf) < 0) {
+ snprintf(errmsg, sizeof(errmsg), "cannot stat %s: %s",
+ subdir, osstrerror());
+ return errmsg;
+ }
+ if (!S_ISDIR(sbuf.st_mode)) {
+ if ((fp = fopen(subdir, "r")) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "cannot open %s: %s",
+ subdir, osstrerror());
+ return errmsg;
+ }
+ linenum = 1;
+ filename = subdir;
+ if (read_pheader(fp) == NULL) {
+ if (read_all_rules(fp) != NULL) {
+ fclose(fp);
+ return errmsg;
+ }
+ }
+#ifdef PMIECONF_DEBUG
+ else {
+ fprintf(stderr, "debug - %s isn't a pmie rule file: %s\n",
+ filename, errmsg);
+ }
+#endif
+ fclose(fp);
+ }
+ else {
+ /* iterate through the rules directory and for each subdirectory */
+ /* fetch all the rules along with associated parameters & values */
+
+ if ((dirp = opendir(subdir)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "cannot opendir %s: %s", subdir, osstrerror());
+ return errmsg;
+ }
+ while ((dp = readdir(dirp)) != NULL) { /* groups */
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+ snprintf(fullpath, sizeof(fullpath), "%s%c%s", subdir, SEP, dp->d_name);
+ if (read_rule_subdir(fullpath) != NULL) { /* recurse */
+ closedir(dirp);
+ return errmsg;
+ }
+ }
+ closedir(dirp);
+ }
+ return NULL;
+}
+
+
+/* ##### pmiefile parsing routines #### */
+
+
+/* returns NULL on successfully adding rule to list, else failure message */
+char *
+deprecate_rule(char *name, unsigned int version, int type)
+{
+ int index;
+
+ /* first check to see if this rule is deprecated already */
+ for (index = 0; index < dcount; index++) {
+ if (strcmp(dlist[index].name, name) == 0
+ && dlist[index].version == version)
+ return NULL;
+ }
+
+ /* get the memory we need & then keep a copy of deprecated rule info */
+ if ((dlist = (dep_t *)realloc(dlist, (dcount+1)*sizeof(dep_t))) == NULL
+ || (dlist[dcount].name = strdup(name)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to deprecate rule %s", name);
+ return errmsg;
+ }
+ dlist[dcount].type = type;
+ dlist[dcount].version = version;
+ if (type == DEPRECATE_NORULE)
+ dlist[dcount].reason = drulestring;
+ else
+ dlist[dcount].reason = dverstring;
+ dcount++;
+ return NULL;
+}
+
+/*
+ * makes list of deprecated rules available to caller (for warning message)
+ * nb: called following a pmiefile write to see what was deprecated during
+ * that write - subsequent writing of this pmiefile should not deprecate
+ * anything (deprecations done once & not again). caller must free list.
+ */
+int
+fetch_deprecated(dep_t **list)
+{
+ int sts;
+
+ *list = dlist;
+ sts = dcount;
+ dcount = 0;
+ return sts;
+}
+
+/* merges local customisations back into the rules atom list */
+static char *
+merge_local(unsigned int version, char *name, char *attrib, char *value)
+{
+ atom_t *aptr;
+ rule_t *rule;
+ int a;
+
+ /*
+ first find the rule to which this local belongs, then figure
+ out what sort of attribute this really is, and finally merge
+ the customisation back into the values in the rules attribs.
+ */
+
+ if (find_rule(name, &rule) != NULL) /* in pmiefile but not rules */
+ return NULL; /* this will be deprecated later */
+ else if (rule->version != version) /* no rule for this version */
+ return NULL; /* this will be deprecated later */
+
+ if ((a = is_attribute(attrib)) != -1)
+ return set_attribute(rule, &rule->self, a, value, 1);
+ else { /* search through this rules list of atoms */
+ for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next) {
+ if (strcmp(get_aname(rule, aptr), attrib) == 0)
+ return set_attribute(rule, aptr, ATTRIB_DEFAULT, value, 1);
+ }
+ for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next) {
+ if (strcmp(get_aname(globals, aptr), attrib) == 0) {
+ /* promote global to become a local */
+ if ((aptr = alloc_atom(rule, *aptr, 0)) == NULL)
+ return errmsg;
+ if ((aptr->name = strdup(get_aname(globals, aptr))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to change value");
+ return errmsg;
+ }
+ return set_attribute(globals, aptr, ATTRIB_DEFAULT, value, 1);
+ }
+ }
+ }
+ snprintf(errmsg, sizeof(errmsg), "variable \"%s\" is undefined for rule %s",
+ attrib, name);
+ return errmsg;
+}
+
+
+/*
+ * #### produce pmie configuration file from rules ###
+ */
+
+char *
+action_string(int type)
+{
+ switch (type) {
+ case TYPE_PRINT: return "print";
+ case TYPE_SHELL: return "shell";
+ case TYPE_ALARM: return "alarm";
+ case TYPE_SYSLOG: return "syslog";
+ }
+ return NULL;
+}
+
+static int
+expand_action(FILE *f, int count, rule_t *rule, atom_t *atom)
+{
+ char *p;
+ char *str;
+
+ if (IS_ACTION(atom->type) && atom->enabled) {
+ if ((str = dollar_expand(rule, " $holdoff$ ", 0)) == NULL)
+ return count;
+ if (count == 0)
+ fprintf(f, " -> ");
+ else
+ fprintf(f, "\n & ");
+ fprintf(f, "%s", action_string(atom->type));
+ fprintf(f, "%s", str);
+ free(str);
+ if ((str = dollar_expand(rule, atom->data, 0)) == NULL) {
+ fprintf(stderr, "Warning - failed to expand action for rule %s\n"
+ " string: \"%s\"\n", rule->self.name, atom->data);
+ /* keep going - too late to bail out without file corruption */
+ }
+#ifdef PMIECONF_DEBUG
+ else {
+ fprintf(stderr, "expanded action= \"%s\"\n", str);
+ }
+#endif
+ fputc('"', f);
+ for (p = str; p != NULL && *p; p++) { /* expand the '^' character */
+ if (*p == '/' && *(p+1) == '^') {
+ fputc(*p, f); p++;
+ fputc(*p, f); p++;
+ }
+ else if (*p == '^')
+ fputs("\" \"", f);
+ else fputc(*p, f);
+ }
+ fputc('"', f);
+ if (str != NULL)
+ free(str);
+ return count + 1;
+ }
+ return count;
+}
+
+
+/*
+ * this struct and the enumerate function are used only in generate_rules()
+ * and the enumerate() routines, for enumeration of either a hostlist or an
+ * instlist
+ */
+
+typedef struct {
+ atom_t *atom;
+ int nvalues;
+ char **valuelist;
+ char *restore;
+} enumlist_t;
+
+static enumlist_t *list;
+static int nlistitems;
+static int writecount;
+
+/*
+ * expands and writes out a single rule, and optionally the delta
+ * note: in the single rule case (not enumerated), we absolutely
+ * must write out the delta every time
+ */
+static char *
+write_rule(FILE *f, rule_t *rule)
+{
+ atom_t *aptr;
+ char *dgen = NULL; /* holds generated "delta" */
+ char *pgen; /* holds generated "predicate" */
+ int actions = 0;
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - writing rule %s\n", rule->self.name);
+#endif
+
+ if (writecount == 0 && (dgen = dollar_expand(rule, "$delta$", 0)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "\"$delta$\" variable expansion failed for rule %s",
+ rule->self.name);
+ return errmsg;
+ }
+ if ((pgen = dollar_expand(rule, rule->predicate, 0)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "\"$predicate$\" variable expansion failed "
+ "for rule %s", rule->self.name);
+ return errmsg;
+ }
+ if (writecount == 0) {
+ fprintf(f, "// %u %s\ndelta = %s;\n%s = \n", rule->version,
+ rule->self.name, dgen, rule->self.name);
+ free(dgen);
+ }
+ else /* we're enumerating, need to differentiate rule names */
+ fprintf(f, "%s%u = \n", rule->self.name, writecount);
+ fputs(pgen, f);
+ free(pgen);
+ for (aptr = rule->self.next; aptr != NULL; aptr = aptr->next)
+ actions = expand_action(f, actions, rule, aptr);
+ for (aptr = globals->self.next; aptr != NULL; aptr = aptr->next)
+ actions = expand_action(f, actions, rule, aptr);
+ fprintf(f, ";\n\n");
+
+ writecount++;
+ return NULL;
+}
+
+/* parses the "enumerate" value string passed in thru the rules file */
+char *
+parse_enumerate(rule_t *rule)
+{
+ atom_t *ap;
+ char *p = rule->enumerate;
+ int needsave = 0; /* should we save this variable name yet? */
+ int i = 0;
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - parse_enumerate called for %s\n", rule->self.name);
+#endif
+
+ nlistitems = 0;
+ list = NULL;
+ while (*p != '\0') {
+ if (!isspace((int)*p)) {
+ needsave = 1;
+ token[i++] = *p;
+ }
+ p++;
+ if ((isspace((int)*p) && needsave) || *p == '\0') {
+ token[i] = '\0';
+ i = 0;
+ if (map_symbol(attribs, numattribs, token) != -1) {
+ snprintf(errmsg, sizeof(errmsg), "cannot enumerate rule %s using attribute"
+ " \"%s\"", rule->self.name, token);
+ return errmsg;
+ }
+ else {
+ for (ap = rule->self.next; ap != NULL; ap = ap->next)
+ if (strcmp(get_aname(rule, ap), token) == 0)
+ goto foundname;
+ for (ap = globals->self.next; ap != NULL; ap = ap->next)
+ if (strcmp(get_aname(globals, ap), token) == 0)
+ goto foundname;
+ snprintf(errmsg, sizeof(errmsg), "variable \"%s\" undefined for enumerated"
+ " rule %s", token, rule->self.name);
+ return errmsg;
+ }
+foundname:
+ if (ap->type != TYPE_HOSTLIST && ap->type != TYPE_INSTLIST) {
+ snprintf(errmsg, sizeof(errmsg), "rules file error - \"$%s$\" in \"enumerate\" "
+ "clause of rule %s is not of type hostlist or instlist",
+ token, rule->self.name);
+ return errmsg;
+ }
+ /* increase size of list & keep a copy of the variable name */
+ if ((list = realloc(list, (nlistitems+1)*sizeof(enumlist_t))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to write rules");
+ return errmsg;
+ }
+ list[nlistitems].atom = ap;
+ list[nlistitems].nvalues = 0;
+ list[nlistitems].valuelist = NULL;
+ list[nlistitems].restore = NULL;
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - variable %s added to enum list (#%d)\n",
+ list[nlistitems].atom->name, nlistitems);
+#endif
+ nlistitems++;
+ needsave = 0;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * converts a host/inst list into individual elements, overwrites liststr
+ * (turns all spaces to NULLs to mark string ends - reduces mallocing)
+ */
+char **
+get_listitems(char *liststr, int *count)
+{
+ char **result = NULL;
+ char *p = liststr;
+ int keepwhite = 0;
+ int startagain = 0; /* set to signify new list item has started */
+ int ptrcount = 0;
+
+ if ((result = realloc(result, (ptrcount+1) * sizeof(char *))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to get list elements");
+ return NULL;
+ }
+ result[ptrcount++] = p;
+ while (*p != '\0') {
+ if (!isspace((int)*p) || keepwhite) {
+ if (startagain) {
+ result = realloc(result, (ptrcount+1) * sizeof(char *));
+ if (result == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to get list elements");
+ return NULL;
+ }
+ result[ptrcount++] = p;
+ startagain = 0;
+ }
+ if (*p == '\\')
+ p++;
+ else if (*p == '\'')
+ keepwhite = !keepwhite;
+ }
+ else {
+ *p = '\0';
+ startagain = 1;
+ }
+ p++;
+ }
+#ifdef PMIECONF_DEBUG
+ fputs("debug - instances are:", stderr);
+ for (keepwhite = 0; keepwhite < ptrcount; keepwhite++)
+ fprintf(stderr, " %s", result[keepwhite]);
+ fputs("\n", stderr);
+#endif
+ *count = ptrcount;
+ return result;
+}
+
+/* expands variables from the "enumerate" string in the rules file */
+char *
+expand_enumerate(rule_t *rule)
+{
+ int i, j;
+ char *p;
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - expanding enum variables for rule %s\n",
+ rule->self.name);
+#endif
+
+ for (i = 0; i < nlistitems; i++) {
+ if ((p = dollar_expand(rule, list[i].atom->data, 0)) == NULL)
+ return errmsg;
+ if ((list[i].valuelist = realloc(list[i].valuelist,
+ sizeof(char *) * (list[i].nvalues + 1))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for rule enumeration");
+ free(p);
+ return errmsg;
+ }
+ if ((list[i].valuelist = get_listitems(p, &j)) == NULL) {
+ free(p);
+ return errmsg;
+ }
+ list[i].nvalues = j;
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - %s value list:", list[i].atom->name);
+ for (j = 0; j < list[i].nvalues; j++)
+ fprintf(stderr, " %s", list[i].valuelist[j]);
+ fprintf(stderr, "\n");
+#endif
+ }
+ return NULL;
+}
+
+void
+enumerate(FILE *f, rule_t *r, enumlist_t *listoffset, int wssize, char **wset)
+{
+ int i;
+
+ if (wssize < nlistitems) {
+ for (i = 0; i < listoffset->nvalues; i++) {
+ /* add current word to word set, and move down a level */
+ wset[wssize] = listoffset->valuelist[i];
+ enumerate(f, r, &list[wssize+1], wssize+1, wset);
+ }
+ }
+ else { /* have a full set, generate rule */
+#ifdef PMIECONF_DEBUG
+ for (i = 0; i < wssize; i++)
+ printf("%s=%s ", list[i].atom->name, wset[i]);
+ printf("\n");
+#endif
+ for (i = 0; i < wssize; i++) {
+ list[i].restore = list[i].atom->data;
+ list[i].atom->data = wset[i];
+ }
+
+ write_rule(f, r);
+
+ for (i = 0; i < wssize; i++)
+ list[i].atom->data = list[i].restore;
+ }
+}
+
+/* generate pmie rules for rule, returns rule string/NULL */
+static char *
+generate_rules(FILE *f, rule_t *rule)
+{
+ int i;
+
+ if (rule->self.enabled == 0)
+ return NULL;
+ if (rule->enumerate == NULL) {
+ writecount = 0;
+ write_rule(f, rule);
+ }
+ else {
+ char **workingset; /* holds current variable values set */
+
+#ifdef PMIECONF_DEBUG
+ fprintf(stderr, "debug - generating enumerated rule %s\n",
+ rule->self.name);
+#endif
+
+ /* "enumerate" attrib is a space-separated list of variables */
+ /* 1.create a list of variable info structs (name->valuelist) */
+ /* 2.recurse thru lists, when each set built, write out rule */
+
+ if ((parse_enumerate(rule)) != NULL)
+ return errmsg;
+ if ((expand_enumerate(rule)) != NULL)
+ return errmsg;
+ if ((workingset = malloc(nlistitems * sizeof(char*))) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to generate rules");
+ return errmsg;
+ }
+ writecount = 0;
+ enumerate(f, rule, list, 0, workingset);
+ free(workingset);
+ for (i = 0; i < nlistitems; i++)
+ free(list[i].valuelist); /* alloc'd by dollar_expand */
+ free(list);
+ }
+
+ return NULL;
+}
+
+/* generate local configuration changes, returns rule string/NULL */
+static char *
+generate_pmiefile(FILE *f, rule_t *rule)
+{
+ atom_t *aptr;
+
+ for (aptr = &rule->self; aptr != NULL; aptr = aptr->next) {
+ if (aptr->changed == 0)
+ continue;
+ if (IS_RULE(aptr->type)) {
+ fprintf(f, "// %u %s %s = %s\n", rule->version,
+ get_aname(rule, aptr), "enabled",
+ rule->self.enabled? "yes" : "no");
+ }
+ else {
+ fprintf(f, "// %u %s %s = %s\n", rule->version, rule->self.name,
+ get_aname(rule, aptr), value_string(aptr, 1));
+ }
+ }
+ return NULL;
+}
+
+/* generate pmie rules and write to file, returns NULL/failure message */
+char *
+write_pmiefile(char *program, int autocreate)
+{
+ time_t now = time(NULL);
+ char *p, *msg = NULL;
+ char buf[MAXPATHLEN+10];
+ char *fname = get_pmiefile();
+ FILE *fp;
+ int i;
+
+ /* create full path to file if it doesn't exist */
+ if ((p = strrchr(fname, '/')) != NULL) {
+ struct stat sbuf;
+
+ *p = '\0'; /* p is the dirname of fname */
+ if (stat(fname, &sbuf) < 0) {
+ snprintf(buf, sizeof(buf), "/bin/mkdir -p %s", fname);
+ if (system(buf) < 0) {
+ snprintf(errmsg, sizeof(errmsg), "failed to create directory \"%s\"", p);
+ return errmsg;
+ }
+ }
+ else if (!S_ISDIR(sbuf.st_mode)) {
+ snprintf(errmsg, sizeof(errmsg), "\"%s\" exists and is not a directory", p);
+ return errmsg;
+ }
+ fname[strlen(fname)] = '/'; /* stitch together */
+ }
+
+ if ((fp = fopen(fname, "w")) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "cannot write file %s: %s", fname, osstrerror());
+ return errmsg;
+ }
+ else if (!gotpath) {
+ strcpy(token, fname);
+ if (realpath(token, pmiefile) == NULL) {
+ fclose(fp);
+ snprintf(errmsg, sizeof(errmsg), "failed to resolve %s realpath: %s", token, osstrerror());
+ return errmsg;
+ }
+ gotpath = 1;
+ }
+
+ fprintf(fp, "// %s %s %s\n", PMIE_FILE, PMIE_VERSION, get_rules());
+ for (i = 0; i < rulecount; ++i)
+ if ((msg = generate_pmiefile(fp, &rulelist[i])) != NULL)
+ goto imouttahere;
+ fputs("// end\n//\n", fp);
+
+ fprintf(fp, "%s// %sgenerated by %s on: %s//\n\n",
+ START_STRING, autocreate ? "Auto-" : " ", program, ctime(&now));
+ for (i = 1; i < rulecount; ++i) /* 1: start _after_ globals */
+ if ((msg = generate_rules(fp, &rulelist[i])) != NULL)
+ goto imouttahere;
+
+ /* write user-modifications area */
+ fprintf(fp, END_STRING);
+ /* finally any other local changes */
+ if (save_area != NULL)
+ fputs(save_area, fp);
+
+imouttahere:
+ fclose(fp);
+ return msg;
+}
+
+
+/*
+ * #### pmiefile manipulation routines ###
+ */
+
+/*
+ * skip leading white space and comments, return first character in next token
+ * or zero on end of file
+ */
+static int
+prime_next_lread(FILE *f)
+{
+ int c;
+
+ do {
+ c = getc(f);
+ if (c == EOF)
+ return 0;
+ if (c == '\n') {
+ linenum++;
+ if (getc(f) != '/') return 0;
+ if (getc(f) != '/') return 0;
+ }
+ } while (! isgraph(c));
+ return c;
+}
+
+/* read next input token; returns 1 ok, 0 eof, -1 error */
+static int
+read_ltoken(FILE *f)
+{
+ int c;
+ int n = 0;
+
+ switch (c = prime_next_lread(f)) {
+ case 0: /* EOF */
+ return 0;
+ case '"': /* scan string */
+ c = getc(f);
+ while (c != '"') {
+ if (c == '\\')
+ c = getc(f);
+ if (c == EOF || n == TOKEN_LENGTH) {
+ token[n] = '\0';
+ parse_error("end-of-string", token);
+ return -1;
+ }
+ if (c == '\n') {
+ token[n] = '\0';
+ parse_error("end-of-string", "end-of-line");
+ return -1;
+ }
+ token[n++] = c;
+ c = getc(f);
+ }
+ break;
+ case '=':
+ token[n++] = c; /* single char token */
+ break;
+ default: /* some other token */
+ while (isgraph(c)) {
+ if (c == '=') {
+ ungetc(c, f);
+ break;
+ }
+ if (n == TOKEN_LENGTH) {
+ token[n] = '\0';
+ parse_error("end-of-token", token);
+ return -1;
+ }
+ token[n++] = c;
+ c = getc(f);
+ }
+ if (c == '\n') {
+ linenum++;
+ if (strncmp(token, "end", 3) == 0) break;
+ if (getc(f) != '/') return 0;
+ if (getc(f) != '/') return 0;
+ }
+ break;
+ }
+
+ token[n] = '\0';
+ return 1;
+}
+
+
+/* allocates memory & appends a string to the save area */
+char *
+save_area_append(char *str)
+{
+ int size = strlen(str);
+
+ while ( (size+1) >= (sa_size-sa_mark) ) {
+ sa_size += 256; /* increase area by 256 bytes at a time */
+ if ((save_area = (char *)realloc(save_area, sa_size)) == NULL)
+ return NULL;
+ }
+ if (sa_mark == 1)
+ save_area = strcpy(save_area, str);
+ else
+ save_area = strcat(save_area, str);
+ sa_mark += size;
+ return save_area;
+}
+
+/* read and save text which is to be restored on pmiefile write */
+static char *
+read_restore(FILE *f)
+{
+ unsigned int version;
+ rule_t *rule;
+ char buf[LINE_LENGTH];
+ int saverule = 0;
+ int saveall = 0;
+
+ do {
+ if (fgets(buf, LINE_LENGTH, f) == NULL)
+ break;
+ if (!saveall) { /* not yet at start of explicit "save" position */
+ if (strcmp(buf, END_STRING) == 0)
+ saveall = 1;
+ else if (sscanf(buf, "// %u %s\n", &version, token) == 2) {
+ /*
+ * where the rule has disappeared or its version does not match
+ * the one in the pmiefile, add the rule name & version to list
+ * of rules to be deprecated (i.e. moved to the "save area")
+ */
+ /* check that we still have this rule definition */
+ if (find_rule(token, &rule) != NULL) { /* not found! */
+ snprintf(buf, sizeof(buf), "// %u %s (deprecated, %s)\n",
+ version, token, drulestring);
+ deprecate_rule(token, version, DEPRECATE_NORULE);
+ saverule = 1;
+ }
+ else if (rule->version != version) { /* not supported! */
+ snprintf(buf, sizeof(buf), "// %u %s (deprecated, %s)\n",
+ version, token, dverstring);
+ deprecate_rule(token, version, DEPRECATE_VERSION);
+ saverule = 1;
+ }
+ else
+ saverule = 0;
+ }
+ if (!saveall && saverule) {
+ if (save_area_append("// ") == NULL ||
+ save_area_append(buf) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to deprecate a rule");
+ return errmsg;
+ }
+ }
+ }
+ else if (save_area_append(buf) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory to preserve save area");
+ return errmsg;
+ }
+ } while (!feof(f));
+ return NULL;
+}
+
+
+/* read custom values, return NULL/failure message */
+static char *
+read_locals(FILE *f)
+{
+ int sts;
+ char *rule;
+ char *attrib;
+ char *value;
+ unsigned int version;
+
+ /* general pmiefile format: version rulename attribute = value */
+
+ for (;;) {
+ if ((sts = read_ltoken(f)) < 0)
+ return errmsg;
+ else if (sts == 0) {
+ parse_error("rule identifier or \"end\" symbol", "end-of-file");
+ return errmsg;
+ }
+ if (strcmp("end", token) == 0)
+ break;
+
+ /* read the version number for this rule */
+ version = strtoul(token, &value, 10);
+ if (*value != '\0') {
+ parse_error("version number", token);
+ return errmsg;
+ }
+
+ /* read the name of the rule */
+ if ((sts = read_ltoken(f)) != 1 ||
+ (rule = alloc_string(strlen(token)+1)) == NULL) {
+ if (sts == 0)
+ parse_error("rule name", token);
+ return errmsg;
+ }
+ strcpy(rule, token);
+
+ /* read the rule attribute component */
+ if ((sts = read_ltoken(f)) != 1 ||
+ (attrib = alloc_string(strlen(token)+1)) == NULL) {
+ free(rule);
+ if (sts == 0)
+ parse_error("rule attribute", token);
+ return errmsg;
+ }
+ strcpy(attrib, token);
+
+ if ((sts = read_ltoken(f)) != 1 || strcmp("=", token) != 0) {
+ free(rule); free(attrib);
+ if (sts == 0)
+ parse_error("'=' symbol", "end-of-file");
+ return errmsg;
+ }
+
+ /* read the modified value of this attribute */
+ if ((sts = read_ltoken(f)) != 1 ||
+ (value = alloc_string(strlen(token)+1)) == NULL) {
+ free(rule); free(attrib);
+ if (sts == 0)
+ parse_error("rule attribute value", "end-of-file");
+ return errmsg;
+ }
+ strcpy(value, token);
+
+ if (merge_local(version, rule, attrib, value) != NULL) {
+ free(rule); free(attrib); free(value);
+ return errmsg;
+ }
+ free(rule); free(attrib); free(value); /* no longer need these */
+ }
+ return NULL;
+}
+
+/* validate header of rule customizations file, return NULL/failure message */
+static char *
+read_lheader(FILE *f, char **proot)
+{
+ if (read_ltoken(f) != 1 || strcmp(token, "//") || read_ltoken(f) != 1
+ || strcmp(token, PMIE_FILE) || read_ltoken(f) != 1) {
+ snprintf(errmsg, sizeof(errmsg), "%s is not a rule customization file (bad header)",
+ filename);
+ return errmsg;
+ }
+ else if (strcmp(token, PMIE_VERSION) != 0) { /* one version only */
+ snprintf(errmsg, sizeof(errmsg), "unknown version number in %s: \"%s\" (expected %s)",
+ filename, token, PMIE_VERSION);
+ return errmsg;
+ }
+ else if (read_ltoken(f) != 1) {
+ snprintf(errmsg, sizeof(errmsg), "no rules path specified in %s after version number",
+ filename);
+ return errmsg;
+ }
+ *proot = token;
+ return NULL;
+}
+
+
+/*
+ * read the pmiefile format into global data structures
+ */
+char *
+read_pmiefile(char *warning, size_t warnlen)
+{
+ char *tmp = NULL;
+ char *p, *home;
+ FILE *f;
+ char *rule_path_sep;
+
+ if ((f = fopen(get_pmiefile(), "r")) == NULL) {
+ if (oserror() == ENOENT)
+ return NULL;
+ snprintf(errmsg, sizeof(errmsg), "cannot open %s: %s",
+ get_pmiefile(), osstrerror());
+ return errmsg;
+ }
+
+ linenum = 1;
+ filename = get_pmiefile();
+ if (read_lheader(f, &tmp) != NULL) {
+ fclose(f);
+ return errmsg;
+ }
+
+ /* check that we have access to all components of the path */
+ if ((home = strdup(tmp)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for pmie file parsing");
+ return errmsg;
+ }
+#ifdef IS_MINGW
+ rule_path_sep = ";";
+#else
+ rule_path_sep = ":";
+#endif
+ p = strtok(home, rule_path_sep);
+ while (p != NULL) {
+ if (access(p, F_OK) < 0) {
+ free(home);
+ snprintf(errmsg, sizeof(errmsg), "cannot access rules path component: \"%s\"", p);
+ return errmsg;
+ }
+ p = strtok(NULL, rule_path_sep);
+ }
+ free(home);
+
+ if (strcmp(get_rules(), tmp) != 0)
+ snprintf(warning, warnlen, "warning - pmie configuration file \"%s\"\n"
+ " may not have been built using rules path:\n\t\"%s\"\n"
+ " (originally built using \"%s\")", filename, get_rules(), tmp);
+
+ tmp = NULL;
+ if (read_locals(f) != NULL || read_restore(f) != NULL)
+ tmp = errmsg;
+ fclose(f);
+ return tmp;
+}
+
+
+/* #### setup global data structures; return NULL/failure message #### */
+char *
+initialise(char *in_rules, char *in_pmie, char *warning, size_t warnlen)
+{
+ char *p;
+ char *home;
+ rule_t global;
+ char *rule_path_sep;
+
+ /* setup pointers to the configuration files */
+#ifdef IS_MINGW
+ if ((home = getenv("USERPROFILE")) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "USERPROFILE undefined in environment");
+ return errmsg;
+ }
+ if (in_pmie == NULL)
+ snprintf(pmiefile, sizeof(pmiefile), "%s\\%s", home, DEFAULT_USER_PMIE);
+ else
+ strcpy(pmiefile, in_pmie);
+ rule_path_sep = ";";
+#else
+ if (getuid() == 0) {
+ if (in_pmie == NULL)
+ snprintf(pmiefile, sizeof(pmiefile), "%s%c%s", pmGetConfig("PCP_SYSCONF_DIR"), SEP, DEFAULT_ROOT_PMIE);
+ else if (realpath(in_pmie, pmiefile) == NULL && oserror() != ENOENT) {
+ snprintf(errmsg, sizeof(errmsg), "failed to resolve realpath for %s: %s",
+ in_pmie, osstrerror());
+ return errmsg;
+ }
+ else if (oserror() != ENOENT)
+ gotpath = 1;
+ }
+ else {
+ if ((home = getenv("HOME")) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "$HOME undefined in environment");
+ return errmsg;
+ }
+ if (in_pmie == NULL)
+ snprintf(pmiefile, sizeof(pmiefile), "%s%c%s", home, SEP, DEFAULT_USER_PMIE);
+ else
+ strcpy(pmiefile, in_pmie);
+ }
+ rule_path_sep = ":";
+#endif
+
+ if (in_rules == NULL) {
+ if ((p = getenv("PMIECONF_PATH")) == NULL)
+ snprintf(rulepath, sizeof(rulepath), "%s%c%s", pmGetConfig("PCP_VAR_DIR"), SEP, DEFAULT_RULES);
+ else
+ strcpy(rulepath, p);
+ }
+ else
+ snprintf(rulepath, sizeof(rulepath), "%s", in_rules);
+
+ memset(&global, 0, sizeof(rule_t));
+ global.self.name = global_name;
+ global.self.data = global_data;
+ global.self.help = global_help;
+ global.self.global = 1;
+ if (alloc_rule(global) == NULL) { /* 1st rule holds global (fake rule) */
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for global parameters");
+ return errmsg;
+ }
+
+ if ((home = strdup(rulepath)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for rules path parsing");
+ return errmsg;
+ }
+ p = strtok(home, rule_path_sep);
+ while (p != NULL) {
+ if (read_rule_subdir(p) != NULL) {
+ free(home);
+ return errmsg;
+ }
+ p = strtok(NULL, rule_path_sep);
+ }
+ free(home);
+
+ if (read_pmiefile(warning, warnlen) != NULL)
+ return errmsg;
+ linenum = 0; /* finished all parsing */
+ return NULL;
+}
+
+
+/* iterate through the pmie status directory and find running pmies */
+char *
+lookup_processes(int *count, char ***processes)
+{
+ int fd;
+ int running = 0;
+ DIR *dirp;
+ void *ptr;
+ char proc[MAXPATHLEN+1];
+ char **proc_list = NULL;
+ size_t size;
+ pmiestats_t *stats;
+ struct dirent *dp;
+ struct stat statbuf;
+ int sep = __pmPathSeparator();
+
+ snprintf(proc, sizeof(proc), "%s%c%s",
+ pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR);
+ if ((dirp = opendir(proc)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "cannot opendir %s: %s",
+ proc, osstrerror());
+ return NULL;
+ }
+ while ((dp = readdir(dirp)) != NULL) {
+ /* bunch of checks to find valid pmie data files... */
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+ snprintf(proc, sizeof(proc), "%s%c%s",
+ PROC_DIR, sep, dp->d_name); /* check /proc */
+ if (access(proc, F_OK) < 0)
+ continue; /* process has exited */
+ snprintf(proc, sizeof(proc), "%s%c%s%c%s",
+ pmGetConfig("PCP_TMP_DIR"), sep, PMIE_SUBDIR, sep, dp->d_name);
+ if (stat(proc, &statbuf) < 0)
+ continue;
+ if (statbuf.st_size != sizeof(pmiestats_t))
+ continue;
+ if ((fd = open(proc, O_RDONLY)) < 0)
+ continue;
+ ptr = __pmMemoryMap(fd, statbuf.st_size, 0);
+ close(fd);
+ if (ptr == NULL)
+ continue;
+ stats = (pmiestats_t *)ptr;
+ if (strcmp(stats->config, get_pmiefile()) != 0)
+ continue;
+
+ size = (1 + running) * sizeof(char *);
+ if ((proc_list = (char **)realloc(proc_list, size)) == NULL
+ || (proc_list[running] = strdup(dp->d_name)) == NULL) {
+ snprintf(errmsg, sizeof(errmsg), "insufficient memory for process search");
+ if (proc_list) free(proc_list);
+ closedir(dirp);
+ close(fd);
+ return errmsg;
+ }
+ running++;
+ }
+ closedir(dirp);
+ *count = running;
+ *processes = proc_list;
+ return NULL;
+}
diff --git a/src/pmieconf/rules.h b/src/pmieconf/rules.h
new file mode 100644
index 0000000..2ce3378
--- /dev/null
+++ b/src/pmieconf/rules.h
@@ -0,0 +1,154 @@
+/*
+ * rules.h - rule description data structures and parsing
+ *
+ * Copyright 1998, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef RULES_H
+#define RULES_H
+
+/* defaults relative to $PCP_VAR_DIR */
+#ifdef IS_MINGW
+#define DEFAULT_RULES "config\\pmieconf"
+#define DEFAULT_ROOT_PMIE "pmie\\config.default"
+#else
+#define DEFAULT_RULES "config/pmieconf"
+#define DEFAULT_ROOT_PMIE "pmie/config.default"
+#endif
+
+/* default relative to $HOME */
+#ifdef IS_MINGW
+#define DEFAULT_USER_PMIE ".pcp\\pmie\\config.pmie"
+#else
+#define DEFAULT_USER_PMIE ".pcp/pmie/config.pmie"
+#endif
+
+#define TYPE_STRING 0 /* arbitrary string (default) */
+#define TYPE_DOUBLE 1 /* real number */
+#define TYPE_INTEGER 2 /* integer number */
+#define TYPE_UNSIGNED 3 /* cardinal number */
+#define TYPE_PERCENT 4 /* percentage 0..100 */
+#define TYPE_HOSTLIST 5 /* list of host names */
+#define TYPE_INSTLIST 6 /* list of metric instances */
+#define TYPE_PRINT 7 /* print action */
+#define TYPE_SHELL 8 /* shell action */
+#define TYPE_ALARM 9 /* alarm window action */
+#define TYPE_SYSLOG 10 /* syslog action */
+#define TYPE_RULE 11 /* rule definition */
+
+#define ATTRIB_HELP 0 /* help= text */
+#define ATTRIB_MODIFY 1 /* modify= text */
+#define ATTRIB_ENABLED 2 /* enabled= y/n */
+#define ATTRIB_DISPLAY 3 /* display= y/n */
+#define ATTRIB_DEFAULT 4 /* default= value */
+#define ATTRIB_VERSION 5 /* version= int */
+#define ATTRIB_PREDICATE 6 /* predicate= text */
+#define ATTRIB_ENUMERATE 7 /* enumerate= text */
+
+#define IS_ACTION(t) \
+ (t==TYPE_PRINT || t==TYPE_SHELL || t==TYPE_ALARM || t==TYPE_SYSLOG)
+#define IS_RULE(t) (t == TYPE_RULE)
+
+/* generic data block definition */
+struct atom_type {
+ char *name; /* atom identifier */
+ char *data; /* value string */
+ char *ddata; /* default value */
+ char *help; /* help text */
+ unsigned int type : 16; /* data type */
+ unsigned int modify : 1; /* advice for editor */
+ unsigned int display : 1; /* advice for editor */
+ unsigned int enabled : 1; /* switched on/off */
+ unsigned int denabled: 1; /* default on/off */
+ unsigned int changed : 1; /* has value been changed? */
+ unsigned int global : 1; /* global scope */
+ unsigned int padding : 10; /* unused */
+ struct atom_type *next;
+};
+typedef struct atom_type atom_t;
+
+typedef struct {
+ unsigned int version;
+ atom_t self;
+ char *predicate;
+ char *enumerate; /* list of hostlist/instlist params */
+} rule_t;
+
+
+extern rule_t *rulelist;
+extern unsigned int rulecount;
+extern rule_t *globals;
+
+extern char errmsg[]; /* error message buffer */
+
+
+/*
+ * routines below returning char*, on success return NULL else failure message
+ */
+
+char *initialise(char *, char *, char *, size_t); /* setup global data */
+
+char *get_pmiefile(void);
+char *get_rules(void);
+char *get_aname(rule_t *, atom_t *);
+
+void sort_rules(void);
+char *find_rule(char *, rule_t **);
+char *lookup_rules(char *, rule_t ***, unsigned int *, int);
+
+char *value_string(atom_t *, int); /* printable string form of atoms value */
+char *value_change(rule_t *, char *, char *); /* change rule parameter value */
+char *validate(int, char *, char *); /* check proposed value for named type */
+
+char *write_pmiefile(char *, int);
+char *lookup_processes(int *, char ***);
+
+int is_attribute(char *);
+char *get_attribute(char *, atom_t *);
+char *rule_defaults(rule_t *, char *);
+
+int is_overridden(rule_t *, atom_t *);
+int read_token(FILE *, char *, int, int);
+char *dollar_expand(rule_t *, char *, int);
+
+
+/* deprecated rules stuff */
+#define DEPRECATE_NORULE 0
+#define DEPRECATE_VERSION 1
+
+typedef struct {
+ unsigned int version; /* version not matching/in rules */
+ char *name; /* full name of the offending rule */
+ char *reason; /* ptr to deprecation description */
+ int type; /* reason for deprecating this rule */
+} dep_t;
+int fetch_deprecated(dep_t **list);
+
+
+/* generic symbol table definition */
+typedef struct {
+ int symbol_id;
+ char *symbol;
+} symbol_t;
+
+/* lookup keyword, returns symbol identifier or -1 if not there */
+int map_symbol(symbol_t *, int, char *);
+
+/* lookup symbol identifier, returns keyword or NULL if not there */
+char *map_identifier(symbol_t *, int, int);
+
+/* parse yes/no attribute value; returns 0 no, 1 yes, -1 error */
+int map_boolean(char *);
+
+#endif
diff --git a/src/pmieconf/web/errors b/src/pmieconf/web/errors
new file mode 100644
index 0000000..5af87ff
--- /dev/null
+++ b/src/pmieconf/web/errors
@@ -0,0 +1,60 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule web.errors
+ default = "$rule$"
+ predicate =
+"some_host (
+ 100 * web.allservers.errors $hosts$
+ / web.allservers.requests.total $hosts$ > $threshold$
+ &&
+ web.allservers.requests.total > $min_requests$ count/minute
+)"
+ enabled = yes
+ version = 1
+ help =
+"Across all web servers on the target host, there is some web serving
+activity (at least min_requests per minute), but more than threshold
+percent of the requests are resulting in errors being reported in the
+activity logs.";
+
+string rule
+ default = "Excessive aggregate web server errors"
+ modify = no
+ display = no;
+
+percent threshold
+ default = 20
+ help =
+"Maximum acceptable percentage of web requests that may result in
+errors.";
+
+double min_requests
+ default = 2
+ help =
+"Minimum number of requests per minute before considering the error
+rate as significant.";
+
+string action_expand
+ default = "%v%errs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h web server errors: %v% of all requests"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A3"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/web/high_requests b/src/pmieconf/web/high_requests
new file mode 100644
index 0000000..8e6e579
--- /dev/null
+++ b/src/pmieconf/web/high_requests
@@ -0,0 +1,50 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule web.high_requests
+ default = "$rule$"
+ predicate =
+"some_host (
+ web.allservers.requests.total $hosts$
+ > $threshold$ count/second
+)"
+ enabled = no
+ version = 1
+ help =
+"The aggregate request rate for all web servers on the target host is
+above threshold requests per second over the last sample interval.";
+
+string rule
+ default = "High aggregate rate of web requests"
+ display = no
+ modify = no;
+
+double threshold
+ default = 10
+ help =
+"Threshold web requests per second for all web servers on the target
+hosts.";
+
+string action_expand
+ default = "%vreqs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h high web server request rate: %v/sec"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A4"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/web/low_requests b/src/pmieconf/web/low_requests
new file mode 100644
index 0000000..9850e5e
--- /dev/null
+++ b/src/pmieconf/web/low_requests
@@ -0,0 +1,50 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule web.low_requests
+ default = "$rule$"
+ predicate =
+"some_host (
+ 60 * web.allservers.requests.total $hosts$
+ < 60 * $threshold$ count/minute
+)"
+ enabled = no
+ version = 1
+ help =
+"The aggregate request rate for all web servers on the target host is
+below threshold requests per minute over the last sample interval.";
+
+string rule
+ default = "Low aggregate rate of web requests"
+ display = no
+ modify = no;
+
+double threshold
+ default = 1
+ help =
+"Threshold minimum web requests per minute for all web servers on the
+target hosts.";
+
+string action_expand
+ default = "%vreqs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h low web server request rate: %v/min"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A5"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/connect_errors b/src/pmieconf/webping/connect_errors
new file mode 100644
index 0000000..ff523af
--- /dev/null
+++ b/src/pmieconf/webping/connect_errors
@@ -0,0 +1,50 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.connect_errors
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 60 * webping.errors.sockerr $hosts$
+ > 60 * $threshold$ count/minute
+)"
+ enabled = yes
+ version = 1
+ help =
+"The webping agent encountered more than threshold web server connection
+errors per minute in the last sample interval.";
+
+string rule
+ default = "High webping connection error rate"
+ display = no
+ modify = no;
+
+double threshold
+ default = 3
+ help =
+"Threshold webping web server connection errors per minute.";
+
+string action_expand
+ default = "%verrs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h high connection errors: %verrs"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A6"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/html_errors b/src/pmieconf/webping/html_errors
new file mode 100644
index 0000000..58eecbb
--- /dev/null
+++ b/src/pmieconf/webping/html_errors
@@ -0,0 +1,50 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.html_errors
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 60 * webping.errors.htmlerr $hosts$ $urls$
+ > 60 * $threshold$ count/minute
+)"
+ enabled = yes
+ version = 1
+ help =
+"The webping agent encountered more than threshold HTML errors per
+minute communicating with web servers in the last sample interval.";
+
+string rule
+ default = "High webping HTML error rate"
+ display = no
+ modify = no;
+
+double threshold
+ default = 3
+ help =
+"Threshold webping HTML errors per minute.";
+
+string action_expand
+ default = "%verrs[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h high HTML errors for %i: %verrs"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A7"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/http_errors b/src/pmieconf/webping/http_errors
new file mode 100644
index 0000000..6ac0aca
--- /dev/null
+++ b/src/pmieconf/webping/http_errors
@@ -0,0 +1,50 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.http_errors
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 60 * webping.errors.httperr $hosts$
+ > 60 * $threshold$ count/minute
+)"
+ enabled = yes
+ version = 1
+ help =
+"The webping agent encountered more than threshold HTTP errors per
+minute communicating with web servers in the last sample interval.";
+
+string rule
+ default = "High webping HTTP error rate"
+ display = no
+ modify = no;
+
+double threshold
+ default = 3
+ help =
+"Threshold webping HTTP errors per minute.";
+
+string action_expand
+ default = "%verrs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h high HTTP errors: %verrs"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A8"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/no_response b/src/pmieconf/webping/no_response
new file mode 100644
index 0000000..e5624dd
--- /dev/null
+++ b/src/pmieconf/webping/no_response
@@ -0,0 +1,56 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.no_response
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ webping.perurl.kbytes $hosts$ $urls$ @1 -
+ webping.perurl.kbytes $hosts$ $urls$ @0 > 0
+ && webping.perurl.kbytes $hosts$ $urls$ @0 == 0
+)"
+ enabled = yes
+ version = 1
+ help =
+"The retrieval of at least one URL by the webping agent failed on the
+most recent attempt, but succeeded on the previous attempt.";
+
+string rule
+ default = "No response to webping request"
+ display = no
+ modify = no;
+
+string action_expand
+ default = "%i@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h no web server response for %i"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000AB"
+ display = no
+ modify = no;
+
+# for HP OpenView integration:
+string ov_severity
+ display = no
+ default = "Critical";
+
+# for CA/Unicenter TNG integration:
+string tngfw_color
+ display = no
+ default = "Red";
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/other_errors b/src/pmieconf/webping/other_errors
new file mode 100644
index 0000000..29d56b2
--- /dev/null
+++ b/src/pmieconf/webping/other_errors
@@ -0,0 +1,51 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.other_errors
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ 60 * webping.errors.othererr $hosts$
+ > 60 * $threshold$ count/minute
+)"
+ enabled = yes
+ version = 1
+ help =
+"The webping agent encountered more than threshold errors (cause
+unknown) per minute communicating with web servers in the last sample
+interval.";
+
+string rule
+ default = "High webping error rate, cause unknown"
+ display = no
+ modify = no;
+
+double threshold
+ default = 3
+ help =
+"Threshold webping errors of unknown cause, per minute.";
+
+string action_expand
+ default = "%verrs@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h high errors of unknown cause: %verrs"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000A9"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/webping/slow_response b/src/pmieconf/webping/slow_response
new file mode 100644
index 0000000..40766ee
--- /dev/null
+++ b/src/pmieconf/webping/slow_response
@@ -0,0 +1,56 @@
+#pmieconf-rules 1
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
+#
+
+rule webping.slow_response
+ default = "$rule$"
+ enumerate = hosts
+ predicate =
+"some_inst (
+ webping.perurl.time.total $hosts$ $urls$
+ > $threshold$ seconds
+)"
+ enabled = yes
+ version = 1
+ help =
+"The retrieval of at least one URL by the webping agent took longer than
+threshold seconds at the most recent attempt.";
+
+string rule
+ default = "Slow response to webping request"
+ display = no
+ modify = no;
+
+double threshold
+ default = 8
+ help=
+"Threshold on the maximum acceptable web server response time, in
+seconds.
+The possible range of values for the threshold may be very large,
+e.g. 0.5 to 20 seconds or more, and the most appropriate value depends
+on the response time to fetch the URLs (the transmission time is
+included), the network latency between the webping agent and the web
+servers, and the loading of the web servers.";
+
+string action_expand
+ default = "%vsecs[%i]@%h"
+ display = no
+ modify = no;
+
+string email_expand
+ default = "host: %h web server response for %i: %vsecs"
+ display = no
+ modify = no;
+
+
+# Configuration info specific to non-PCP tools follows...
+#
+
+# for SGI Embedded Support Partner integration:
+string esp_type
+ default = "0x2000AA"
+ display = no
+ modify = no;
+
+#
+# --- DO NOT MODIFY THIS FILE --- see pmieconf(4)
diff --git a/src/pmieconf/xtractnames b/src/pmieconf/xtractnames
new file mode 100755
index 0000000..33aab7b
--- /dev/null
+++ b/src/pmieconf/xtractnames
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+# Extract metric names (or likely looking ones) from pmieconf files
+# In comparison to the tools/xtractnames version, this one is a little
+# more context sensitive, and not PMNS-driven
+#
+
+for d in ../include ../../include /etc
+do
+ if [ -f $d/pcp.conf ]
+ then
+ . $d/pcp.conf
+ break
+ fi
+done
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit" 0 1 2 3 15
+
+_usage()
+{
+ echo "Usage: xtractnames [file ...]"
+}
+
+while getopts "?" c
+do
+ case $c
+ in
+ ?)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $# -eq 0 ]
+then
+ # no args, from stdin
+ cat >$tmp/in
+ set -- $tmp/in
+fi
+
+for file
+do
+ awk <$file '
+/^[ ]*predicate[ ]*=/ { want = 1 }
+/^[ ]*enabled[ ]*=/ { exit }
+want == 1 { print }' \
+ | tr -cs 'a-zA-Z0-9_.' '[\012*]' \
+ | grep '[a-zA-Z].*\.[a-zA-Z0-9_]'
+done \
+| $PCP_SORT_PROG -u
+
+exit
diff --git a/src/pmiestatus/GNUmakefile b/src/pmiestatus/GNUmakefile
new file mode 100644
index 0000000..9d67f93
--- /dev/null
+++ b/src/pmiestatus/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2010 Max Matveev. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmiestatus.c
+CMDTARGET = pmiestatus$(EXECSUFFIX)
+
+LLDLIBS = $(PCPLIB)
+LCFLAGS = -I$(TOPDIR)/src/pmie/src
+
+default : $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install : default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmiestatus/pmiestatus.c b/src/pmiestatus/pmiestatus.c
new file mode 100644
index 0000000..d955a61
--- /dev/null
+++ b/src/pmiestatus/pmiestatus.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010 Max Matveev. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "stats.h"
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ __pmSetProgname(argv[0]);
+
+ for (i=1; i < argc; i++) {
+ pmiestats_t ps;
+ struct stat st;
+ int f = open(argv[i], O_RDONLY, 0);
+
+ if (f < 0) {
+ fprintf(stderr, "%s: cannot open %s - %s\n",
+ pmProgname, argv[i], osstrerror());
+ continue;
+ }
+
+ if (fstat(f, &st) < 0) {
+ fprintf(stderr, "%s: cannot get size of %s - %s\n",
+ pmProgname, argv[i], osstrerror());
+ goto closefile;
+ }
+
+ if (st.st_size != sizeof(ps)) {
+ fprintf(stderr, "%s: %s is not a valid pmie stats file\n",
+ pmProgname, argv[i]);
+ goto closefile;
+ }
+ if (read(f, &ps, sizeof(ps)) != sizeof(ps)) {
+ fprintf(stderr, "%s: cannot read %ld bytes from %s\n",
+ pmProgname, (long)sizeof(ps), argv[i]);
+ goto closefile;
+ }
+
+ if (ps.version != 1) {
+ fprintf(stderr, "%s: unsupported version %d in %s\n",
+ pmProgname, ps.version, argv[i]);
+ goto closefile;
+ }
+
+ printf ("%s\n%s\n%s\n", ps.config, ps.logfile, ps.defaultfqdn);
+closefile:
+ close(f);
+ }
+ return 0;
+}
diff --git a/src/pminfo/GNUmakefile b/src/pminfo/GNUmakefile
new file mode 100644
index 0000000..0423dd3
--- /dev/null
+++ b/src/pminfo/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pminfo.c
+CMDTARGET = pminfo$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default : $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install : default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pminfo/pminfo.c b/src/pminfo/pminfo.c
new file mode 100644
index 0000000..014c256
--- /dev/null
+++ b/src/pminfo/pminfo.c
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <limits.h>
+
+static void myeventdump(pmValueSet *, int, int);
+static int myoverrides(int, pmOptions *);
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_ARCHIVE,
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ PMOPT_LOCALPMDA,
+ PMOPT_SPECLOCAL,
+ PMOPT_NAMESPACE,
+ PMOPT_DUPNAMES,
+ PMOPT_ORIGIN,
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Protocol options"),
+ { "batch", 1, 'b', "N", "fetch N metrics at a time for -f and -v [20]" },
+ { "desc", 0, 'd', 0, "get and print metric description" },
+ { "fetch", 0, 'f', 0, "fetch and print values for all instances" },
+ { "fetchall", 0, 'F', 0, "fetch and print values for non-enumerable indoms" },
+ { "pmid", 0, 'm', 0, "print PMID" },
+ { "fullpmid", 0, 'M', 0, "print PMID in verbose format" },
+ { "oneline", 0, 't', 0, "get and display (terse) oneline text" },
+ { "helptext", 0, 'T', 0, "get and display (verbose) help text" },
+ PMAPI_OPTIONS_HEADER("Metrics options"),
+ { "derived", 1, 'c', "FILE", "load derived metric definitions from FILE" },
+ { "events", 0, 'x', 0, "unpack and report on any fetched event records" },
+ { "verify", 0, 'v', 0, "verify mode, be quiet and only report errors" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_STDOUT_TZ,
+ .short_options = "a:b:c:dD:Ffh:K:LMmN:n:O:tTvxzZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] [metricname ...]",
+ .override = myoverrides,
+};
+
+static int p_mid; /* Print metric IDs of leaf nodes */
+static int p_fullmid; /* Print verbose metric IDs of leaf nodes */
+static int p_desc; /* Print descriptions for metrics */
+static int p_oneline; /* fetch oneline text? */
+static int p_help; /* fetch help text? */
+static int p_value; /* pmFetch and print value(s)? */
+static int p_force; /* pmFetch and print value(s)? for non-enumerable indoms */
+
+static int need_context; /* set if need a pmapi context */
+static int need_pmid; /* set if need to lookup names */
+static char **namelist;
+static pmID *pmidlist;
+static int batchsize = 20;
+static int batchidx;
+static int verify; /* Only print error messages */
+static int events; /* Decode event metrics */
+static pmID pmid_flags;
+static pmID pmid_missed;
+
+/*
+ * pminfo has a few options which do not follow the defacto standards
+ */
+static int
+myoverrides(int opt, pmOptions *opts)
+{
+ if (opt == 't' || opt == 'T')
+ return 1; /* we've claimed these, inform pmGetOptions */
+ return 0;
+}
+
+/*
+ * Cache all of the most recently requested pmInDom
+ */
+static char *
+lookup(pmInDom indom, int inst)
+{
+ static pmInDom last = PM_INDOM_NULL;
+ static int numinst = -1;
+ static int *instlist;
+ static char **namelist;
+ int i;
+
+ if (indom != last) {
+ if (numinst > 0) {
+ free(instlist);
+ free(namelist);
+ }
+ numinst = pmGetInDom(indom, &instlist, &namelist);
+ last = indom;
+ }
+
+ for (i = 0; i < numinst; i++) {
+ if (instlist[i] == inst)
+ return namelist[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * we only ever have one metric
+ */
+static void
+mydump(pmDesc *dp, pmValueSet *vsp, char *indent)
+{
+ int j;
+ char *p;
+
+ if (indent != NULL)
+ printf("%s", indent);
+ if (vsp->numval == 0) {
+ printf("No value(s) available!\n");
+ return;
+ }
+ else if (vsp->numval < 0) {
+ printf("Error: %s\n", pmErrStr(vsp->numval));
+ return;
+ }
+
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (dp->indom != PM_INDOM_NULL) {
+ if ((p = lookup(dp->indom, vp->inst)) == NULL) {
+ if (p_force) {
+ /* the instance disappeared; ignore it */
+ printf(" inst [%d \"%s\"]\n", vp->inst, "DISAPPEARED");
+ continue;
+ }
+ else {
+ /* report the error and give up */
+ printf("pmNameIndom: indom=%s inst=%d: %s\n",
+ pmInDomStr(dp->indom), vp->inst, pmErrStr(PM_ERR_INST));
+ printf(" inst [%d]", vp->inst);
+ }
+ }
+ else
+ printf(" inst [%d or \"%s\"]", vp->inst, p);
+ }
+ else
+ printf(" ");
+ printf(" value ");
+ pmPrintValue(stdout, vsp->valfmt, dp->type, vp, 1);
+ putchar('\n');
+ if (!events)
+ continue;
+ if (dp->type == PM_TYPE_HIGHRES_EVENT)
+ myeventdump(vsp, j, 1);
+ else if (dp->type == PM_TYPE_EVENT)
+ myeventdump(vsp, j, 0);
+ }
+}
+
+static void
+setup_event_derived_metrics(void)
+{
+ if (pmid_flags == 0) {
+ /*
+ * get PMID for event.flags and event.missed
+ * note that pmUnpackEventRecords() will have called
+ * __pmRegisterAnon(), so the anonymous metrics
+ * should now be in the PMNS
+ */
+ char *name_flags = "event.flags";
+ char *name_missed = "event.missed";
+ int sts;
+
+ sts = pmLookupName(1, &name_flags, &pmid_flags);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_flags, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_flags)->item = 1;
+ }
+ sts = pmLookupName(1, &name_missed, &pmid_missed);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_missed, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_missed)->item = 1;
+ }
+ }
+}
+
+static int
+dump_nparams(int numpmid)
+{
+ if (numpmid == 0) {
+ printf(" ---\n");
+ printf(" No parameters\n");
+ return -1;
+ }
+ if (numpmid < 0) {
+ printf(" ---\n");
+ printf(" Error: illegal number of parameters (%d)\n", numpmid);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+dump_parameter(pmValueSet *xvsp, int index, int *flagsp)
+{
+ int sts, flags = *flagsp;
+ pmDesc desc;
+ char *name;
+
+ if (pmNameID(xvsp->pmid, &name) >= 0) {
+ if (index == 0) {
+ if (xvsp->pmid == pmid_flags) {
+ flags = *flagsp = xvsp->vlist[0].value.lval;
+ printf(" flags 0x%x", flags);
+ printf(" (%s) ---\n", pmEventFlagsStr(flags));
+ free(name);
+ return;
+ }
+ else
+ printf(" ---\n");
+ }
+ if ((flags & PM_EVENT_FLAG_MISSED) &&
+ (index == 1) &&
+ (xvsp->pmid == pmid_missed)) {
+ printf(" ==> %d missed event records\n",
+ xvsp->vlist[0].value.lval);
+ free(name);
+ return;
+ }
+ printf(" %s (%s)\n", name, pmIDStr(xvsp->pmid));
+ free(name);
+ }
+ else
+ printf(" PMID: %s\n", pmIDStr(xvsp->pmid));
+ if ((sts = pmLookupDesc(xvsp->pmid, &desc)) < 0)
+ printf(" pmLookupDesc: %s\n", pmErrStr(sts));
+ else
+ mydump(&desc, xvsp, " ");
+}
+
+static void
+myeventdump(pmValueSet *vsp, int inst, int highres)
+{
+ int r; /* event records */
+ int p; /* event parameters */
+ int nrecords;
+ int flags;
+
+ if (highres) {
+ pmHighResResult **hr;
+
+ if ((nrecords = pmUnpackHighResEventRecords(vsp, inst, &hr)) < 0) {
+ fprintf(stderr, "pmUnpackHighResEventRecords: %s\n",
+ pmErrStr(nrecords));
+ return;
+ }
+ setup_event_derived_metrics();
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintHighResStamp(stdout, &hr[r]->timestamp);
+ if (dump_nparams(hr[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < hr[r]->numpmid; p++)
+ dump_parameter(hr[r]->vset[p], p, &flags);
+ }
+ pmFreeHighResEventResult(hr);
+ }
+ else {
+ pmResult **res;
+
+ if ((nrecords = pmUnpackEventRecords(vsp, inst, &res)) < 0) {
+ fprintf(stderr, "pmUnpackEventRecords: %s\n", pmErrStr(nrecords));
+ return;
+ }
+ setup_event_derived_metrics();
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintStamp(stdout, &res[r]->timestamp);
+ if (dump_nparams(res[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < res[r]->numpmid; p++)
+ dump_parameter(res[r]->vset[p], p, &flags);
+ }
+ pmFreeEventResult(res);
+ }
+}
+
+static void
+report(void)
+{
+ int i;
+ int sts;
+ pmDesc desc;
+ pmResult *result = NULL;
+ pmResult *xresult = NULL;
+ pmValueSet *vsp = NULL;
+ char *buffer;
+ int all_count;
+ int *all_inst;
+ char **all_names;
+
+ if (batchidx == 0)
+ return;
+
+ /* Lookup names.
+ * Cull out names that were unsuccessfully looked up.
+ * However, it is unlikely to fail because names come from a traverse PMNS.
+ */
+ if (need_pmid) {
+ if ((sts = pmLookupName(batchidx, namelist, pmidlist)) < 0) {
+ int j = 0;
+ for (i = 0; i < batchidx; i++) {
+ if (pmidlist[i] == PM_ID_NULL) {
+ printf("%s: pmLookupName: %s\n", namelist[i], pmErrStr(sts));
+ free(namelist[i]);
+ }
+ else {
+ /* assert(j <= i); */
+ pmidlist[j] = pmidlist[i];
+ namelist[j] = namelist[i];
+ j++;
+ }
+ }
+ batchidx = j;
+ }
+ }
+
+ if (p_value || verify) {
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.origin, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+ if ((sts = pmFetch(batchidx, pmidlist, &result)) < 0) {
+ for (i = 0; i < batchidx; i++)
+ printf("%s: pmFetch: %s\n", namelist[i], pmErrStr(sts));
+ goto done;
+ }
+ }
+
+ for (i = 0; i < batchidx; i++) {
+
+ if (p_desc || p_value || verify) {
+ if ((sts = pmLookupDesc(pmidlist[i], &desc)) < 0) {
+ printf("%s: pmLookupDesc: %s\n", namelist[i], pmErrStr(sts));
+ continue;
+ }
+ }
+
+ if (p_desc || p_help || p_value)
+ /* Not doing verify, output separator */
+ putchar('\n');
+
+
+ if (p_value || verify) {
+ vsp = result->vset[i];
+ if (p_force) {
+ if (result->vset[i]->numval == PM_ERR_PROFILE) {
+ /* indom is non-enumerable; try harder */
+ if ((all_count = pmGetInDom(desc.indom, &all_inst, &all_names)) > 0) {
+ pmDelProfile(desc.indom, 0, NULL);
+ pmAddProfile(desc.indom, all_count, all_inst);
+ if (xresult != NULL) {
+ pmFreeResult(xresult);
+ xresult = NULL;
+ }
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.origin, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+ if ((sts = pmFetch(1, &pmidlist[i], &xresult)) < 0) {
+ printf("%s: pmFetch: %s\n", namelist[i], pmErrStr(sts));
+ continue;
+ }
+ vsp = xresult->vset[0];
+ /* leave the profile in the default state */
+ free(all_inst);
+ free(all_names);
+ pmDelProfile(desc.indom, 0, NULL);
+ pmAddProfile(desc.indom, 0, NULL);
+ }
+ else if (all_count == 0) {
+ printf("%s: pmGetIndom: No instances?\n", namelist[i]);
+ continue;
+ }
+ else {
+ printf("%s: pmGetIndom: %s\n", namelist[i], pmErrStr(all_count));
+ continue;
+ }
+ }
+ }
+ }
+
+ if (verify) {
+ if (desc.type == PM_TYPE_NOSUPPORT)
+ printf("%s: Not Supported\n", namelist[i]);
+ else if (vsp->numval < 0)
+ printf("%s: %s\n", namelist[i], pmErrStr(vsp->numval));
+ else if (vsp->numval == 0)
+ printf("%s: No value(s) available\n", namelist[i]);
+ continue;
+ }
+ else
+ /* not verify */
+ printf("%s", namelist[i]);
+
+ if (p_mid)
+ printf(" PMID: %s", pmIDStr(pmidlist[i]));
+ if (p_fullmid)
+ printf(" = %d = 0x%x", pmidlist[i], pmidlist[i]);
+
+ if (p_oneline) {
+ if ((sts = pmLookupText(pmidlist[i], PM_TEXT_ONELINE, &buffer)) == 0) {
+ if (p_fullmid)
+ printf("\n ");
+ else
+ putchar(' ');
+ printf("[%s]", buffer);
+ free(buffer);
+ }
+ else
+ printf(" One-line Help: Error: %s\n", pmErrStr(sts));
+ }
+ putchar('\n');
+
+ if (p_desc)
+ __pmPrintDesc(stdout, &desc);
+
+ if (p_help) {
+ if ((sts = pmLookupText(pmidlist[i], PM_TEXT_HELP, &buffer)) == 0) {
+ char *p;
+ for (p = buffer; *p; p++)
+ ;
+ while (p > buffer && p[-1] == '\n') {
+ p--;
+ *p = '\0';
+ }
+ if (*buffer != '\0') {
+ printf("Help:\n");
+ printf("%s", buffer);
+ putchar('\n');
+ }
+ else
+ printf("Help: <empty entry>\n");
+ free(buffer);
+ }
+ else
+ printf("Full Help: Error: %s\n", pmErrStr(sts));
+ }
+
+ if (p_value) {
+ mydump(&desc, vsp, NULL);
+ }
+ }
+
+ if (result != NULL) {
+ pmFreeResult(result);
+ result = NULL;
+ }
+ if (xresult != NULL) {
+ pmFreeResult(xresult);
+ xresult = NULL;
+ }
+
+done:
+ for (i = 0; i < batchidx; i++)
+ free(namelist[i]);
+ batchidx = 0;
+}
+
+static void
+dometric(const char *name)
+{
+ if (*name == '\0') {
+ printf("PMNS appears to be empty!\n");
+ return;
+ }
+
+ namelist[batchidx]= strdup(name);
+ if (namelist[batchidx] == NULL) {
+ fprintf(stderr, "%s: namelist string malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+
+ batchidx++;
+ if (batchidx >= batchsize)
+ report();
+}
+
+int
+main(int argc, char **argv)
+{
+ int a, c;
+ int sts;
+ int exitsts = 0;
+ char *source;
+ char *endnum;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'b': /* batchsize */
+ batchsize = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: -b requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'c': /* derived metrics config file */
+ sts = pmLoadDerivedConfig(opts.optarg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: -c error: %s\n", pmProgname, pmErrStr(sts));
+ /* errors are not necessarily fatal ... */
+ }
+ break;
+
+ case 'd':
+ p_desc = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'F':
+ p_force = p_value = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'f':
+ p_value = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'M':
+ p_fullmid = 1;
+ p_mid = 1;
+ need_pmid = 1;
+ break;
+
+ case 'm':
+ p_mid = 1;
+ need_pmid = 1;
+ break;
+
+ case 't':
+ p_oneline = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'T':
+ p_help = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'v':
+ verify = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+
+ case 'x':
+ events = p_value = 1;
+ need_context = 1;
+ need_pmid = 1;
+ break;
+ }
+ }
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.context)
+ need_context = 1;
+
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ /*
+ * for archives, one metric per batch and start at beginning of
+ * archive for each batch so metric will be found if it is in
+ * the archive
+ */
+ batchsize = 1;
+
+ if (verify)
+ p_desc = p_mid = p_fullmid = p_help = p_oneline = p_value = p_force = 0;
+
+
+
+ if ((namelist = (char **)malloc(batchsize * sizeof(char *))) == NULL) {
+ fprintf(stderr, "%s: namelist malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+
+ if ((pmidlist = (pmID *)malloc(batchsize * sizeof(pmID))) == NULL) {
+ fprintf(stderr, "%s: pmidlist malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+
+ if (!opts.nsflag)
+ need_context = 1; /* for distributed PMNS as no PMNS file given */
+
+ if (need_context) {
+ int ctxid;
+
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ source = opts.archives[0];
+ else if (opts.context == PM_CONTEXT_HOST)
+ source = opts.hosts[0];
+ else if (opts.context == PM_CONTEXT_LOCAL)
+ source = NULL;
+ else {
+ opts.context = PM_CONTEXT_HOST;
+ source = "local:";
+ }
+ if ((sts = pmNewContext(opts.context, source)) < 0) {
+ if (opts.context == PM_CONTEXT_HOST)
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ else if (opts.context == PM_CONTEXT_LOCAL)
+ fprintf(stderr, "%s: Cannot make standalone connection on localhost: %s\n",
+ pmProgname, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ exit(1);
+ }
+ ctxid = sts;
+
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ pmTrimNameSpace();
+ /* complete TZ and time window option (origin) setup */
+ if (pmGetContextOptions(ctxid, &opts)) {
+ pmflush();
+ exit(1);
+ }
+ }
+ }
+
+ if (opts.optind >= argc) {
+ sts = pmTraversePMNS("", dometric);
+ if (sts < 0) {
+ fprintf(stderr, "Error: %s\n", pmErrStr(sts));
+ exitsts = 1;
+ }
+ }
+ else {
+ for (a = opts.optind; a < argc; a++) {
+ sts = pmTraversePMNS(argv[a], dometric);
+ if (sts < 0) {
+ fprintf(stderr, "Error: %s: %s\n", argv[a], pmErrStr(sts));
+ exitsts = 1;
+ }
+ }
+ }
+ report();
+
+ exit(exitsts);
+}
diff --git a/src/pmiostat/GNUmakefile b/src/pmiostat/GNUmakefile
new file mode 100644
index 0000000..15b77cc
--- /dev/null
+++ b/src/pmiostat/GNUmakefile
@@ -0,0 +1,29 @@
+# Copyright (c) 2014 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmiostat
+LDIRT = $(CMDTARGET)
+
+default:
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET).py $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmiostat/pmiostat.py b/src/pmiostat/pmiostat.py
new file mode 100755
index 0000000..e4dbaeb
--- /dev/null
+++ b/src/pmiostat/pmiostat.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Iostat Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# pylint: disable=C0103,R0914,R0902
+""" Display disk and device-mapper I/O statistics """
+
+import sys
+from pcp import pmapi, pmcc
+from cpmapi import PM_TYPE_U64, PM_CONTEXT_ARCHIVE, PM_SPACE_KBYTE
+
+IOSTAT_SD_METRICS = [ "disk.dev.read", "disk.dev.read_bytes",
+ "disk.dev.write", "disk.dev.write_bytes",
+ "disk.dev.read_merge", "disk.dev.write_merge",
+ "disk.dev.blkread", "disk.dev.blkwrite",
+ "disk.dev.read_rawactive", "disk.dev.write_rawactive",
+ "disk.dev.avactive"]
+
+IOSTAT_DM_METRICS = [ "disk.dm.read", "disk.dm.read_bytes",
+ "disk.dm.write", "disk.dm.write_bytes",
+ "disk.dm.read_merge", "disk.dm.write_merge",
+ "disk.dm.blkread", "disk.dm.blkwrite",
+ "disk.dm.read_rawactive", "disk.dm.write_rawactive",
+ "disk.dm.avactive"]
+
+class IostatReport(pmcc.MetricGroupPrinter):
+ Hcount = 0
+ def timeStampDelta(self, group):
+ c = 1000000.0 * group.timestamp.tv_sec + group.timestamp.tv_usec
+ p = 1000000.0 * group.prevTimestamp.tv_sec + group.prevTimestamp.tv_usec
+ return (c - p) / 1000000.0
+
+ def instlist(self, group, name):
+ return dict(map(lambda x: (x[1], x[2]), group[name].netValues)).keys()
+
+ def curVals(self, group, name):
+ return dict(map(lambda x: (x[1], x[2]), group[name].netValues))
+
+ def prevVals(self, group, name):
+ return dict(map(lambda x: (x[1], x[2]), group[name].netPrevValues))
+
+ def report(self, manager):
+ if "dm" in IostatOptions.xflag:
+ subtree = "disk.dm"
+ else:
+ subtree = "disk.dev"
+ group = manager["iostat"]
+
+ if group[subtree + ".read_merge"].netPrevValues == None:
+ # need two fetches to report rate converted counter metrics
+ return
+
+ instlist = self.instlist(group, subtree + ".read")
+ dt = self.timeStampDelta(group)
+ timestamp = group.contextCache.pmCtime(long(group.timestamp)).rstrip()
+
+ c_rrqm = self.curVals(group, subtree + ".read_merge")
+ p_rrqm = self.prevVals(group, subtree + ".read_merge")
+
+ c_wrqm = self.curVals(group, subtree + ".write_merge")
+ p_wrqm = self.prevVals(group, subtree + ".write_merge")
+
+ c_r = self.curVals(group, subtree + ".read")
+ p_r = self.prevVals(group, subtree + ".read")
+
+ c_w = self.curVals(group, subtree + ".write")
+ p_w = self.prevVals(group, subtree + ".write")
+
+ c_rkb = self.curVals(group, subtree + ".read_bytes")
+ p_rkb = self.prevVals(group, subtree + ".read_bytes")
+
+ c_wkb = self.curVals(group, subtree + ".write_bytes")
+ p_wkb = self.prevVals(group, subtree + ".write_bytes")
+
+ c_ractive = self.curVals(group, subtree + ".read_rawactive")
+ p_ractive = self.prevVals(group, subtree + ".read_rawactive")
+
+ c_wactive = self.curVals(group, subtree + ".write_rawactive")
+ p_wactive = self.prevVals(group, subtree + ".write_rawactive")
+
+ c_avactive = self.curVals(group, subtree + ".avactive")
+ p_avactive = self.prevVals(group, subtree + ".avactive")
+
+ # check availability
+ if p_rrqm == {} or p_wrqm == {} or p_r == {} or p_w == {} or p_rkb == {} \
+ or p_wkb == {} or p_ractive == {} or p_wactive == {} or p_avactive == {}:
+ # no values (near start of archive?)
+ return
+
+ if "h" not in IostatOptions.xflag:
+ self.Hcount += 1
+ if self.Hcount == 24:
+ self.Hcount = 1
+ if self.Hcount == 1:
+ if "t" in IostatOptions.xflag:
+ heading = ('# Timestamp', 'Device', 'rrqm/s', 'wrqm/s', 'r/s', 'w/s', 'rkB/s', 'wkB/s',
+ 'avgrq-sz', 'avgqu-sz', 'await', 'r_await', 'w_await', '%util')
+ print "%-24s %-12s %7s %7s %6s %6s %8s %8s %8s %8s %7s %7s %7s %5s" % heading
+ else:
+ heading = ('# Device', 'rrqm/s', 'wrqm/s', 'r/s', 'w/s', 'rkB/s', 'wkB/s',
+ 'avgrq-sz', 'avgqu-sz', 'await', 'r_await', 'w_await', '%util')
+ print "%-12s %7s %7s %6s %6s %8s %8s %8s %8s %7s %7s %7s %5s" % heading
+
+ for inst in instlist:
+ # basic stats
+ rrqm = (c_rrqm[inst] - p_rrqm[inst]) / dt
+ wrqm = (c_wrqm[inst] - p_wrqm[inst]) / dt
+ r = (c_r[inst] - p_r[inst]) / dt
+ w = (c_w[inst] - p_w[inst]) / dt
+ rkb = (c_rkb[inst] - p_rkb[inst]) / dt
+ wkb = (c_wkb[inst] - p_wkb[inst]) / dt
+
+ # totals
+ tot_rios = (float)(c_r[inst] - p_r[inst])
+ tot_wios = (float)(c_w[inst] - p_w[inst])
+ tot_ios = (float)(tot_rios + tot_wios)
+
+ # total active time in seconds (same units as dt)
+ tot_active = (float)(c_avactive[inst] - p_avactive[inst]) / 1000.0
+
+ avgrqsz = avgqsz = await = r_await = w_await = util = 0.0
+
+ # average request size units are KB (sysstat reports in units of sectors)
+ if tot_ios:
+ avgrqsz = (float)((c_rkb[inst] - p_rkb[inst]) + (c_wkb[inst] - p_wkb[inst])) / tot_ios
+
+ # average queue length
+ avgqsz = (float)((c_ractive[inst] - p_ractive[inst]) + (c_wactive[inst] - p_wactive[inst])) / dt / 1000.0
+
+ # await, r_await, w_await
+ if tot_ios:
+ await = ((c_ractive[inst] - p_ractive[inst]) + (c_wactive[inst] - p_wactive[inst])) / tot_ios
+
+ if tot_rios:
+ r_await = (c_ractive[inst] - p_ractive[inst]) / tot_rios
+
+ if tot_wios:
+ w_await = (c_wactive[inst] - p_wactive[inst]) / tot_wios
+
+ # device utilization (percentage of active time / interval)
+ if tot_active:
+ util = 100.0 * tot_active / dt
+
+ if "t" in IostatOptions.xflag:
+ print "%-24s %-12s %7.1f %7.1f %6.1f %6.1f %8.1f %8.1f %8.2f %8.2f %7.1f %7.1f %7.1f %5.1f" \
+ % (timestamp, inst, rrqm, wrqm, r, w, rkb, wkb, avgrqsz, avgqsz, await, r_await, w_await, util)
+ else:
+ print "%-12s %7.1f %7.1f %6.1f %6.1f %8.1f %8.1f %8.2f %8.2f %7.1f %7.1f %7.1f %5.1f" \
+ % (inst, rrqm, wrqm, r, w, rkb, wkb, avgrqsz, avgqsz, await, r_await, w_await, util)
+
+
+class IostatOptions(pmapi.pmOptions):
+ # class attributes
+ xflag = []
+
+ def extraOptions(self, opt, optarg, index):
+ if opt == "x":
+ IostatOptions.xflag = optarg.replace(',', ' ').split(' ')
+ else:
+ print "Warning: option '", opt, "' not recognised"
+
+ def __init__(self):
+ pmapi.pmOptions.__init__(self, "A:a:D:h:O:S:s:T:t:VZ:z?x:")
+ self.pmSetLongOptionHeader("General options:")
+ self.pmSetLongOptionAlign()
+ self.pmSetLongOptionArchive()
+ self.pmSetLongOptionDebug()
+ self.pmSetLongOptionHost()
+ self.pmSetLongOptionOrigin()
+ self.pmSetLongOptionStart()
+ self.pmSetLongOptionSamples()
+ self.pmSetLongOptionFinish()
+ self.pmSetLongOptionInterval()
+ self.pmSetLongOptionVersion()
+ self.pmSetLongOptionTimeZone()
+ self.pmSetLongOptionHostZone()
+ self.pmSetLongOptionHelp()
+ self.pmSetOptionCallback(self.extraOptions)
+ self.pmSetLongOptionText(" -x comma separated extended options: [dm][,t][,h]")
+ self.pmSetLongOptionText(" dm show device-mapper statistics (default is sd devices)")
+ self.pmSetLongOptionText(" t precede every line with a timestamp in ctime format");
+ self.pmSetLongOptionText(" h suppress headings");
+
+if __name__ == '__main__':
+ try:
+ manager = pmcc.MetricGroupManager.builder(IostatOptions(), sys.argv)
+ if "dm" in IostatOptions.xflag :
+ manager["iostat"] = IOSTAT_DM_METRICS
+ else:
+ manager["iostat"] = IOSTAT_SD_METRICS
+ manager.printer = IostatReport()
+ sts = manager.run()
+ sys.exit(sts)
+ except pmapi.pmErr, error:
+ print '%s: %s\n' % (error.progname(), error.message())
+ except pmapi.pmUsageErr, usage:
+ print usage.message()
+ sys.exit(1)
+ except KeyboardInterrupt:
+ pass
diff --git a/src/pmlc/GNUmakefile b/src/pmlc/GNUmakefile
new file mode 100644
index 0000000..78b1fab
--- /dev/null
+++ b/src/pmlc/GNUmakefile
@@ -0,0 +1,46 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmlc$(EXECSUFFIX)
+CFILES = pmlc.c util.c actions.c
+HFILES = pmlc.h
+LFILES = lex.l
+YFILES = gram.y
+LDIRT = *.log foo.* $(YFILES:%.y=%.tab.?) $(CMDTARGET) $(LFILES:.l=.c) gram.h
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+$(CMDTARGET): $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+pmlc.o: gram.h
+
+.NOTPARALLEL:
+gram.tab.h gram.tab.c: gram.y
+ $(YACC) -d -b `basename $< .y` $<
+
+gram.h: gram.tab.h
+ rm -f $@ && $(LN_S) $< $@
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlc/actions.c b/src/pmlc/actions.c
new file mode 100644
index 0000000..43ea25c
--- /dev/null
+++ b/src/pmlc/actions.c
@@ -0,0 +1,868 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <inttypes.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmlc.h"
+
+/* for the pmlogger/PMCD we currently have a connection to */
+static int logger_fd = -1; /* file desc pmlogger */
+static char *lasthost; /* host that logger_ctx is for */
+static int src_ctx = -1; /* context for logged host's PMCD*/
+static char *srchost; /* host that logged_ctx is for */
+
+static time_t tmp; /* for pmCtime */
+
+/* PCP 1.x pmlogger returns one of these when a status request is made */
+typedef struct {
+ __pmTimeval ls_start; /* start time for log */
+ __pmTimeval ls_last; /* last time log written */
+ __pmTimeval ls_timenow; /* current time */
+ int ls_state; /* state of log (from __pmLogCtl) */
+ int ls_vol; /* current volume number of log */
+ __int64_t ls_size; /* size of current volume */
+ char ls_hostname[PM_LOG_MAXHOSTLEN];
+ /* name of pmcd host */
+ char ls_tz[40]; /* $TZ at collection host */
+ char ls_tzlogger[40]; /* $TZ at pmlogger */
+} __pmLoggerStatus_v1;
+
+static int
+IsLocal(const char *hostspec)
+{
+ if (strcmp(hostspec, "localhost") == 0 ||
+ strcmp(hostspec, "local:") == 0 ||
+ strcmp(hostspec, "unix:") == 0)
+ return 1;
+ return 0;
+}
+int
+ConnectPMCD(void)
+{
+ int sts;
+ __pmPDU *pb = NULL;
+
+ if (src_ctx >= 0)
+ return src_ctx;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+ __pmLoggerStatus *lsp;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 status request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_STATUS)) < 0) {
+ fprintf(stderr, "Error sending request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return sts;
+ }
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) <= 0) {
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return sts;
+ }
+ if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ sts = 0;
+ goto done;
+ }
+ if (sts != PDU_LOG_STATUS) {
+ fprintf(stderr, "Error PDU response from pmlogger %s", __pmPDUTypeStr(sts));
+ fprintf(stderr, " not %s as expected\n", __pmPDUTypeStr(PDU_LOG_STATUS));
+ sts = 0;
+ goto done;
+ }
+ sts = __pmDecodeLogStatus(pb, &lsp);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error decoding response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ goto done;
+ }
+ if (IsLocal(lsp->ls_fqdn)) {
+ /*
+ * if pmcd host is "localhost"-alike then use hostname that
+ * was used to contact pmlogger, as from here (where pmlc is
+ * running) "localhost" is likely to connect us to the wrong
+ * pmcd or no pmcd at all.
+ */
+ srchost = strdup(lasthost);
+ if (srchost == NULL)
+ __pmNoMem("Error copying host name", strlen(lasthost), PM_FATAL_ERR);
+ }
+ else {
+ srchost = strdup(lsp->ls_fqdn);
+ if (srchost == NULL)
+ __pmNoMem("Error copying host name", strlen(lsp->ls_fqdn), PM_FATAL_ERR);
+ }
+ }
+
+ if ((sts = pmNewContext(PM_CONTEXT_HOST, srchost)) < 0) {
+ /* no PMCD connection, we can't do anything, give up */
+ fprintf(stderr, "Error trying to connect to PMCD on %s: %s\n",
+ srchost, pmErrStr(sts));
+ }
+ else
+ src_ctx = sts;
+
+done:
+ if (pb)
+ __pmUnpinPDUBuf(pb);
+ return sts;
+}
+
+int
+ConnectLogger(char *hostname, int *pid, int *port)
+{
+ int sts;
+
+ if (lasthost != NULL) {
+ free(lasthost);
+ lasthost = NULL;
+ }
+ DisconnectLogger();
+
+ if (src_ctx != -1) {
+ if ((sts = pmDestroyContext(src_ctx)) < 0)
+ fprintf(stderr, "Error deleting PMCD connection to %s: %s\n",
+ srchost, pmErrStr(sts));
+ src_ctx = -1;
+ }
+ if (srchost != NULL) {
+ free(srchost);
+ srchost = NULL;
+ }
+
+ if ((sts = __pmConnectLogger(hostname, pid, port)) < 0) {
+ logger_fd = -1;
+ return sts;
+ }
+ else {
+ logger_fd = sts;
+ if ((lasthost = strdup(hostname)) == NULL) {
+ __pmNoMem("Error copying host name", strlen(hostname), PM_FATAL_ERR);
+ }
+ return 0;
+ }
+}
+
+void
+DisconnectLogger(void)
+{
+ if (logger_fd != -1) {
+ __pmCloseSocket(logger_fd);
+ logger_fd = -1;
+ sleep(1);
+ }
+}
+
+void
+ShowLoggers(char *hostname)
+{
+ int i, n;
+ int ctx;
+ int primary = -1; /* ports[] index for primary logger */
+ int pport = -1; /* port for primary logger */
+ __pmLogPort *ports;
+
+ if ((n = __pmIsLocalhost(hostname)) == 0) {
+ /* remote, need PMCD's help for __pmLogFindPort */
+ if ((ctx = pmNewContext(PM_CONTEXT_HOST, hostname)) < 0) {
+ fprintf(stderr, "Error trying to connect to PMCD on %s: %s\n",
+ hostname, pmErrStr(ctx));
+ return;
+ }
+ }
+ else
+ ctx = -1;
+
+ if ((n = __pmLogFindPort(hostname, PM_LOG_ALL_PIDS, &ports)) < 0) {
+ fprintf(stderr, "Error finding pmloggers on %s: ",
+ hostname);
+ if (still_connected(n))
+ fprintf(stderr, "%s\n", pmErrStr(n));
+ }
+ else if (n == 0)
+ printf("No pmloggers running on %s\n", hostname);
+ else {
+ /* find the position of the primary logger */
+ for (i = 0; i < n; i++) {
+ if (ports[i].pid == PM_LOG_PRIMARY_PID) {
+ pport = ports[i].port;
+ break;
+ }
+ }
+ for (i = 0; i < n; i++) {
+ if (ports[i].port == pport) {
+ primary = i;
+ break;
+ }
+ }
+ printf("The following pmloggers are running on %s:\n ", hostname);
+ /* print any primary logger first, with its pid alias in parentheses) */
+ if (primary != -1) {
+ printf("primary");
+ printf(" (%d)", ports[primary].pid);
+ }
+ /* now print everything except the primary logger */
+ for (i = 0; i < n; i++) {
+ if (i != primary &&
+ ports[i].pid != PM_LOG_PRIMARY_PID) {
+ printf(" %d", ports[i].pid);
+ }
+ }
+ putchar('\n');
+
+ /* Note: Don't free ports, it's storage is managed by __pmLogFindPort() */
+ }
+
+ if (ctx >= 0)
+ pmDestroyContext(ctx);
+}
+
+static void
+PrintState(int state)
+{
+ static char *units[] = {"msec", "sec ", "min ", "hour"};
+ static int factor[] = {1000, 60, 60, 24};
+ int nfactors = sizeof(factor) / sizeof(factor[0]);
+ int i, j, is_on;
+ int delta = PMLC_GET_DELTA(state);
+ float t = delta;
+ int frac;
+
+ fputs(PMLC_GET_MAND(state) ? "mand " : "adv ", stdout);
+ is_on = PMLC_GET_ON(state);
+ fputs(is_on ? "on " : "off ", stdout);
+ if (PMLC_GET_INLOG(state))
+ fputs(PMLC_GET_AVAIL(state) ? " " : "na ", stdout);
+ else
+ fputs("nl ", stdout);
+
+ /* don't display time unless logging on */
+ if (!is_on) {
+ fputs(" ", stdout);
+ return;
+ }
+
+ if (delta == 0) {
+ fputs(" once", stdout);
+ return;
+ }
+
+ for (i = 0; i < nfactors; i++) {
+ if (t < factor[i])
+ break;
+ t /= factor[i];
+ }
+ if (i >= nfactors)
+ i = nfactors - 1;
+
+ frac = (int) ((t - (int)t) * 1000); /* get 3 decimal places */
+ if (frac % 10)
+ j = 3;
+ else
+ if (frac % 100)
+ j = 2;
+ else
+ if (frac % 1000)
+ j = 1;
+ else
+ j = 0;
+ fprintf(stdout, "%*.*f %s", 7 - j, j, t, units[i]);
+ return;
+}
+
+
+/* this pmResult is built during parsing of each pmlc statement.
+ * the metrics and indoms likewise
+ */
+extern pmResult *logreq;
+extern metric_t *metric;
+extern int n_metrics;
+extern indom_t *indom;
+extern int n_indoms;
+
+void
+Query(void)
+{
+ int i, j, k, inst;
+ metric_t *mp;
+ pmValueSet *vsp;
+ pmResult *res;
+
+ if (!connected())
+ return;
+
+ if ((i = __pmControlLog(logger_fd, logreq, PM_LOG_ENQUIRE, 0, 0, &res)) < 0) {
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(i))
+ fprintf(stderr, "%s\n", pmErrStr(i));
+ return;
+ }
+ if (res == NULL) {
+ fprintf(stderr, "Error: NULL result from __pmControlLog\n");
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ vsp = res->vset[i];
+ if (mp->pmid != vsp->pmid) {
+ fprintf(stderr, "GAK! %s not found in returned result\n", mp->name);
+ return;
+ }
+ puts(mp->name);
+ if (vsp->numval < 0) {
+ fputs(" ", stdout);
+ puts(pmErrStr(vsp->numval));
+ }
+ else if (mp->indom == -1) {
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[0].value.lval);
+ putchar('\n');
+ }
+ else if (mp->status.has_insts)
+ for (j = 0; j < mp->n_insts; j++) {
+ inst = mp->inst[j];
+ for (k = 0; k < vsp->numval; k++)
+ if (inst == vsp->vlist[k].inst)
+ break;
+ if (k >= vsp->numval) {
+ printf(" [%d] (not found)\n", inst);
+ continue;
+ }
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[k].value.lval);
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ printf(" [%d or \"%s\"]\n", inst,
+ (k < indom[mp->indom].n_insts) ? indom[mp->indom].name[k] : "???");
+ }
+ else {
+ if (vsp->numval <= 0)
+ puts(" (no instances)");
+ else
+ for (j = 0; j < vsp->numval; j++) {
+ fputs(" ", stdout);
+ PrintState(vsp->vlist[j].value.lval);
+ inst = vsp->vlist[j].inst;
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ printf(" [%d or \"%s\"]\n",
+ inst,
+ (k < indom[mp->indom].n_insts) ? indom[mp->indom].name[k] : "???");
+ }
+ }
+ putchar('\n');
+ }
+ pmFreeResult(res);
+}
+
+void LogCtl(int control, int state, int delta)
+{
+ int i;
+ metric_t *mp;
+ pmValueSet *vsp;
+ pmResult *res;
+ int newstate, newdelta; /* from pmlogger */
+ int expstate = 0; /* expected from pmlogger */
+ int expdelta;
+ int firsterr = 1;
+ unsigned statemask;
+ static char *heading = "Warning: unable to change logging state for:";
+
+ if (!connected())
+ return;
+
+ i = __pmControlLog(logger_fd, logreq, control, state, delta, &res);
+ if (i < 0 && i != PM_ERR_GENERIC) {
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(i))
+ fprintf(stderr, "%s\n", pmErrStr(i));
+ return;
+ }
+ if (res == NULL) {
+ fprintf(stderr, "Error: NULL result from __pmControlLog\n");
+ return;
+ }
+
+ /* Set up the state that we expect pmlogger to return. The encoding for
+ * control and state passed to __pmControlLog differs from that returned
+ * in the result.
+ */
+ statemask = 0;
+ if (state != PM_LOG_MAYBE) {
+ if (control == PM_LOG_MANDATORY)
+ PMLC_SET_MAND(expstate, 1);
+ else
+ PMLC_SET_MAND(expstate, 0);
+ if (state == PM_LOG_ON)
+ PMLC_SET_ON(expstate, 1);
+ else
+ PMLC_SET_ON(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ PMLC_SET_ON(statemask, 1);
+ }
+ else {
+ /* only mandatory+maybe is allowed by parser, which should return
+ * advisory+off OR advisory+on from pmlogger
+ */
+ PMLC_SET_MAND(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ /* don't set ON bit in statemask; ignore returned on/off status */
+ }
+
+ expdelta = PMLC_GET_ON(expstate) ? delta : 0;
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ int j, k, inst;
+ int hadinstmsg;
+ char *name;
+
+ vsp = res->vset[i];
+ if (mp->pmid != vsp->pmid) {
+ fprintf(stderr, "GAK! %s not found in returned result\n", mp->name);
+ return;
+ }
+ if (vsp->numval < 0) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ printf("%s:%s\n", mp->name, pmErrStr(vsp->numval));
+ continue;
+ }
+
+ if (mp->indom == -1) {
+ newstate = PMLC_GET_STATE(vsp->vlist[0].value.lval) & statemask;
+ newdelta = PMLC_GET_DELTA(vsp->vlist[0].value.lval);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ printf("%s\n\n", mp->name);
+ }
+ }
+ else if (mp->status.has_insts) {
+ long val;
+
+ hadinstmsg = 0;
+ for (j = 0; j < mp->n_insts; j++) {
+ inst = mp->inst[j];
+ /* find inst name via mp->indom */
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ if (k < indom[mp->indom].n_insts)
+ name = indom[mp->indom].name[k];
+ else
+ name = "???";
+ /* look for inst in pmValueSet from pmlogger */
+ for (k = 0; k < vsp->numval; k++)
+ if (inst == vsp->vlist[k].inst)
+ break;
+ if (k >= vsp->numval) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s\n", mp->name);
+ }
+ printf(" [%d or \"%s\"] instance not found\n", inst, name);
+ continue;
+ }
+ val = vsp->vlist[k].value.lval;
+ newstate = (int)PMLC_GET_STATE(val) & statemask;
+ newdelta = PMLC_GET_DELTA(val);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s instance(s):\n", mp->name);
+ }
+ printf(" [%d or \"%s\"]\n", inst, name);
+ }
+ }
+ if (hadinstmsg)
+ putchar('\n');
+ }
+ else {
+ hadinstmsg = 0;
+ for (j = 0; j < vsp->numval; j++) {
+ newstate = PMLC_GET_STATE(vsp->vlist[j].value.lval) & statemask;
+ newdelta = PMLC_GET_DELTA(vsp->vlist[j].value.lval);
+ if (expstate != newstate || expdelta != newdelta) {
+ if (firsterr) {
+ firsterr = 0;
+ puts(heading);
+ }
+ if (!hadinstmsg) {
+ hadinstmsg = 1;
+ printf("%s instance(s):\n", mp->name);
+ }
+ inst = vsp->vlist[j].inst;
+ for (k = 0; k < indom[mp->indom].n_insts; k++)
+ if (inst == indom[mp->indom].inst[k])
+ break;
+ if (k < indom[mp->indom].n_insts)
+ name = indom[mp->indom].name[k];
+ else
+ name = "???";
+ printf(" [%d or \"%s\"]\n", inst, name);
+ }
+ }
+ if (hadinstmsg)
+ putchar('\n');
+ }
+ }
+ pmFreeResult(res);
+}
+
+/*
+ * Used to flag timezone changes.
+ * These changes are only relevant to Status() so they are made here.
+ */
+int tzchange = 0;
+
+#define TZBUFSZ 30 /* for pmCtime buffers */
+
+void Status(int pid, int primary)
+{
+ static int localtz = -1;
+ static char *ltzstr = ""; /* pmNewZone doesn't like null pointers */
+ char *str;
+ __pmLoggerStatus *lsp;
+ static char localzone[] = "local";
+ static char *zonename = localzone;
+ char *tzlogger;
+ __pmTimeval *start;
+ __pmTimeval *last;
+ __pmTimeval *timenow;
+ char *hostname;
+ int state;
+ int vol;
+ __int64_t size;
+ char startbuf[TZBUFSZ];
+ char lastbuf[TZBUFSZ];
+ char timenowbuf[TZBUFSZ];
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 status request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_STATUS)) < 0) {
+ fprintf(stderr, "Error sending status request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) <= 0) {
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ if (sts == PDU_ERROR) {
+ __pmDecodeError(pb, &sts);
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ sts = 0;
+ goto done;
+ }
+ if (sts != PDU_LOG_STATUS) {
+ fprintf(stderr, "Error PDU response from pmlogger %s", __pmPDUTypeStr(sts));
+ fprintf(stderr, " not %s as expected\n", __pmPDUTypeStr(PDU_LOG_STATUS));
+ __pmUnpinPDUBuf(pb);
+ return;
+ }
+ sts = __pmDecodeLogStatus(pb, &lsp);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error decoding response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ tzlogger = lsp->ls_tzlogger;
+ start = &lsp->ls_start;
+ last = &lsp->ls_last;
+ timenow = &lsp->ls_timenow;
+ hostname = lsp->ls_hostname;
+ state = lsp->ls_state;
+ vol = lsp->ls_vol;
+ size = lsp->ls_size;
+ }
+ else {
+ fprintf(stderr, "Error: logger IPC version < LOG_PDU_VERSION2, not supported\n");
+ return;
+ }
+
+ if (tzchange) {
+ switch (tztype) {
+ case TZ_LOCAL:
+ if (localtz == -1) {
+ str = __pmTimezone();
+ if (str != NULL)
+ ltzstr = str;
+ localtz = pmNewZone(ltzstr);
+ /* (exits if it fails) */
+ }
+ else
+ pmUseZone(localtz);
+ zonename = localzone;
+ break;
+
+ case TZ_LOGGER:
+ if (tzlogger)
+ pmNewZone(tzlogger); /* but keep me! */
+ zonename = "pmlogger";
+ break;
+
+ case TZ_OTHER:
+ pmNewZone(tz);
+ zonename = tz;
+ break;
+ }
+ }
+ tmp = start->tv_sec;
+ pmCtime(&tmp, startbuf);
+ startbuf[strlen(startbuf)-1] = '\0'; /* zap the '\n' at the end */
+ tmp = last->tv_sec;
+ pmCtime(&tmp, lastbuf);
+ lastbuf[strlen(lastbuf)-1] = '\0';
+ tmp = timenow->tv_sec;
+ pmCtime(&tmp, timenowbuf);
+ timenowbuf[strlen(timenowbuf)-1] = '\0';
+ printf("pmlogger ");
+ if (primary)
+ printf("[primary]");
+ else
+ printf("[%d]", pid);
+ printf(" on host %s is logging metrics from host %s\n",
+ lasthost, hostname);
+ /* NB: FQDN cleanup: note that this is not 'the fqdn' of the
+ pmlogger host or that of its target. */
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2)
+ printf("PMCD host %s\n",
+ IsLocal(lsp->ls_fqdn) ? hostname : lsp->ls_fqdn);
+ if (state == PM_LOG_STATE_NEW) {
+ puts("logging hasn't started yet");
+ goto done;
+ }
+ printf("log started %s (times in %s time)\n"
+ "last log entry %s\n"
+ "current time %s\n"
+ "log volume %d\n"
+ "log size %" PRIi64 "\n",
+ startbuf, zonename, lastbuf, timenowbuf, vol, size);
+
+done:
+ return;
+
+}
+
+void
+Sync(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 sync request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_SYNC)) < 0) {
+ fprintf(stderr, "Error sending sync request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+
+ return;
+}
+
+void
+Qa(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 qa request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, 100+qa_case)) < 0) {
+ fprintf(stderr, "Error sending qa request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+
+ return;
+}
+
+void
+NewVolume(void)
+{
+ int sts;
+ __pmPDU *pb;
+
+ if (!connected())
+ return;
+
+ if (__pmVersionIPC(logger_fd) >= LOG_PDU_VERSION2) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU)
+ fprintf(stderr, "pmlc: sending version 2 newvol request\n");
+#endif
+ if ((sts = __pmSendLogRequest(logger_fd, LOG_REQUEST_NEWVOLUME)) < 0) {
+ fprintf(stderr, "Error sending newvolume request to pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ }
+
+ if ((sts = __pmGetPDU(logger_fd, ANY_SIZE, __pmLoggerTimeout(), &pb)) != PDU_ERROR) {
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ if (sts == 0)
+ /* end of file! */
+ sts = PM_ERR_IPC;
+ fprintf(stderr, "Error receiving response from pmlogger: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ __pmDecodeError(pb, &sts);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "Error: ");
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ return;
+ }
+ else
+ fprintf(stderr, "New log volume %d\n", sts);
+
+ __pmUnpinPDUBuf(pb);
+ return;
+}
+
+int
+connected(void)
+{
+ if (logger_fd == -1) {
+ yyerror("Not connected to any pmlogger instance");
+ return 0;
+ }
+ else
+ return 1;
+}
+
+int
+still_connected(int sts)
+{
+ if (sts == PM_ERR_IPC || sts == -EPIPE) {
+ fprintf(stderr, "Lost connection to the pmlogger instance\n");
+ DisconnectLogger();
+ return 0;
+ }
+ if (sts == PM_ERR_TIMEOUT) {
+ fprintf(stderr, "Timeout, closed connection to the pmlogger instance\n");
+ DisconnectLogger();
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/src/pmlc/gram.y b/src/pmlc/gram.y
new file mode 100644
index 0000000..1455329
--- /dev/null
+++ b/src/pmlc/gram.y
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "pmapi.h"
+#include "impl.h"
+#include "./pmlc.h"
+
+#ifdef YYDEBUG
+int yydebug=1;
+#endif
+
+int mystate = GLOBAL;
+int logfreq;
+int parse_stmt;
+char emess[160];
+char *hostname;
+int state;
+int control;
+int qa_case;
+
+static int sts;
+
+extern int port;
+extern int pid;
+extern int is_local;
+extern int is_unix;
+extern int is_socket_path;
+
+%}
+
+%union {
+ long lval;
+ char * str;
+}
+
+%term LSQB
+ RSQB
+ COMMA
+ LBRACE
+ RBRACE
+ AT
+ EOL
+
+ LOG
+ MANDATORY ADVISORY
+ ON OFF MAYBE
+ EVERY ONCE
+ MSEC SECOND MINUTE HOUR
+
+ QUERY SHOW LOGGER CONNECT PRIMARY QUIT STATUS HELP
+ TIMEZONE LOCAL PORT SOCKET
+ NEW VOLUME
+
+ SYNC
+ QA
+
+%token<str> NAME HOSTNAME STRING URL
+%token<lval> NUMBER
+
+%type<lval> timeunits
+%%
+
+stmt : dowhat
+ {
+ mystate |= INSPEC;
+ if (!connected()) {
+ metric_cnt = -1;
+ return 0;
+ }
+ if (ConnectPMCD()) {
+ yyerror("");
+ metric_cnt = -1;
+ return 0;
+ }
+ beginmetrics();
+ }
+ somemetrics
+ {
+ mystate = GLOBAL;
+ endmetrics();
+ }
+ EOL
+ {
+ parse_stmt = LOG;
+ YYACCEPT;
+ }
+ | SHOW loggersopt hostopt EOL
+ {
+ parse_stmt = SHOW;
+ YYACCEPT;
+ }
+ | CONNECT towhom hostopt EOL
+ {
+ parse_stmt = CONNECT;
+ YYACCEPT;
+ }
+ | HELP EOL
+ {
+ parse_stmt = HELP;
+ YYACCEPT;
+ }
+ | QUIT EOL
+ {
+ parse_stmt = QUIT;
+ YYACCEPT;
+ }
+ | STATUS EOL
+ {
+ parse_stmt = STATUS;
+ YYACCEPT;
+ }
+ | NEW VOLUME EOL
+ {
+ parse_stmt = NEW;
+ YYACCEPT;
+ }
+ | TIMEZONE tzspec EOL
+ {
+ parse_stmt = TIMEZONE;
+ YYACCEPT;
+ }
+ | SYNC EOL
+ {
+ parse_stmt = SYNC;
+ YYACCEPT;
+ }
+ | QA NUMBER EOL
+ {
+ parse_stmt = QA;
+ qa_case = $2;
+ YYACCEPT;
+ }
+ | EOL
+ {
+ parse_stmt = 0;
+ YYACCEPT;
+ }
+ | { YYERROR; }
+ ;
+
+dowhat : action
+ ;
+
+action : QUERY { state = PM_LOG_ENQUIRE; }
+ | logopt cntrl ON frequency { state = PM_LOG_ON; }
+ | logopt cntrl OFF { state = PM_LOG_OFF; }
+ | logopt MANDATORY MAYBE
+ {
+ control = PM_LOG_MANDATORY;
+ state = PM_LOG_MAYBE;
+ }
+ ;
+
+logopt : LOG
+ | /* nothing */
+ ;
+
+cntrl : MANDATORY { control = PM_LOG_MANDATORY; }
+ | ADVISORY { control = PM_LOG_ADVISORY; }
+ ;
+
+frequency : everyopt NUMBER timeunits { logfreq = $2 * $3; }
+ | ONCE { logfreq = -1; }
+ ;
+
+everyopt : EVERY
+ | /* nothing */
+ ;
+
+timeunits : MSEC { $$ = 1; }
+ | SECOND { $$ = 1000; }
+ | MINUTE { $$ = 60000; }
+ | HOUR { $$ = 3600000; }
+ ;
+
+somemetrics : LBRACE { mystate |= INSPECLIST; } metriclist RBRACE
+ | metricspec
+ ;
+
+metriclist : metricspec
+ | metriclist metricspec
+ | metriclist COMMA metricspec
+ ;
+
+metricspec : NAME
+ {
+ beginmetgrp();
+ if ((sts = pmTraversePMNS($1, addmetric)) < 0)
+ /* metric_cnt is set by addmetric but if
+ * traversePMNS fails, set it so that the bad
+ * news is visible to other routines
+ */
+ metric_cnt = sts;
+ else if (metric_cnt < 0) /* addmetric failed */
+ sts = metric_cnt;
+
+ if (sts < 0 || metric_cnt == 0) {
+ sprintf(emess,
+ "Problem with lookup for metric \"%s\" ...",$1);
+ yywarn(emess);
+ if (sts < 0) {
+ fprintf(stderr, "Reason: ");
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ }
+ }
+ optinst { endmetgrp(); }
+ ;
+
+optinst : LSQB instancelist RSQB
+ | /* nothing */
+ ;
+
+instancelist : instance
+ | instance instancelist
+ | instance COMMA instancelist
+ ;
+
+instance : NAME { addinst($1, 0); }
+ | NUMBER { addinst(NULL, $1); }
+ | STRING { addinst($1, 0); }
+ ;
+
+loggersopt : LOGGER
+ | /* nothing */
+ ;
+
+hostopt : AT NAME { hostname = strdup($2); }
+ | AT HOSTNAME { hostname = strdup($2); }
+ | AT URL
+ {
+ char *prefix_end;
+ size_t prefix_len;
+ hostname = strdup($2);
+ prefix_end = strchr(hostname, ':');
+ if (prefix_end != NULL) {
+ prefix_len = prefix_end - hostname + 1;
+ if (prefix_len == 6 && strncmp(hostname, "local:", prefix_len) == 0)
+ is_local = 1;
+ else if (prefix_len == 5 && strncmp(hostname, "unix:", prefix_len) == 0)
+ is_unix = 1;
+ if (is_local || is_unix) {
+ const char *p;
+ /*
+ * Find out is a path was specified.
+ * Skip any initial path separators.
+ */
+ for (p = hostname + prefix_len; *p == __pmPathSeparator(); ++p)
+ ;
+ if (*p != '\0')
+ is_socket_path = 1;
+ }
+ }
+ }
+ | AT NUMBER
+ {
+ /* That MUST be a mistake! */
+ char tb[64];
+ sprintf (tb, "%d", (int)$2);
+ hostname = strdup(tb);
+ }
+ | AT STRING { hostname = strdup($2); }
+ | /* nothing */
+ ;
+
+towhom : PRIMARY { pid = PM_LOG_PRIMARY_PID; port = PM_LOG_NO_PORT; }
+ | NUMBER { pid = $1; port = PM_LOG_NO_PORT; }
+ | PORT NUMBER { pid = PM_LOG_NO_PID; port = $2; }
+ | SOCKET { pid = PM_LOG_NO_PID; port = PM_LOG_NO_PORT; }
+ ;
+
+tzspec : LOCAL { tztype = TZ_LOCAL; }
+ | LOGGER { tztype = TZ_LOGGER; }
+ | STRING
+ {
+ tztype = TZ_OTHER;
+ /* ignore the quotes: skip the leading one and
+ * clobber the trailing one with a null to
+ * terminate the string really required.
+ */
+ if (tz != NULL)
+ free(tz);
+ if ((tz = strdup($1)) == NULL) {
+ __pmNoMem("setting up timezone",
+ strlen($1), PM_FATAL_ERR);
+ }
+ }
+ ;
+
+%%
+
+extern char *configfile;
+extern int lineno;
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n",
+ configfile == NULL ? "<stdin>" : configfile, lineno);
+ if (s != NULL && s[0] != '\0')
+ fprintf(stderr, "%s\n", s);
+}
+
+void
+yyerror(char *s)
+{
+ fprintf(stderr, "Error [%s, line %d]\n",
+ configfile == NULL ? "<stdin>" : configfile, lineno);
+ if (s != NULL && s[0] != '\0')
+ fprintf(stderr, "%s\n", s);
+
+ skipAhead ();
+ yyclearin;
+ mystate = GLOBAL;
+}
diff --git a/src/pmlc/lex.l b/src/pmlc/lex.l
new file mode 100644
index 0000000..c523a36
--- /dev/null
+++ b/src/pmlc/lex.l
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include "pmapi.h"
+#include "./pmlc.h"
+
+extern int eflag;
+
+#ifdef FLEX_SCANNER
+#include "gram.tab.h"
+int pmlcFlexInput (char *, int);
+int lineno = 0;
+#else /* AT&T lex */
+#include "gram.h"
+int lineno = 1;
+#endif
+
+static int
+ctx(int type)
+{
+ if (mystate == GLOBAL)
+ return type;
+ else {
+ yylval.str = yytext;
+ return NAME;
+ }
+}
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#undef YY_INPUT
+#define YY_INPUT(b,r,ms) (r=pmlcFlexInput(b, ms))
+#else /* AT&T Lex */
+#undef input
+#undef unput
+#undef yywrap
+#endif
+%}
+
+%%
+"[" { return LSQB; }
+"]" { return RSQB; }
+"," { return COMMA; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+"@" { return AT; }
+
+milliseconds? { return ctx(MSEC); }
+mandatory { return ctx(MANDATORY); }
+advisory { return ctx(ADVISORY); }
+timezone { return ctx(TIMEZONE); }
+loggers? { return ctx(LOGGER); }
+minutes? { return ctx(MINUTE); }
+seconds? { return ctx(SECOND); }
+connect { return ctx(CONNECT); }
+primary { return ctx(PRIMARY); }
+status { return ctx(STATUS); }
+volume { return ctx(VOLUME); }
+flush { return ctx(SYNC); }
+every { return ctx(EVERY); }
+local { return ctx(LOCAL); }
+maybe { return ctx(MAYBE); }
+query { return ctx(QUERY); }
+hours? { return ctx(HOUR); }
+msecs? { return ctx(MSEC); }
+mins? { return ctx(MINUTE); }
+secs? { return ctx(SECOND); }
+help { return ctx(HELP); }
+once { return ctx(ONCE); }
+port { return ctx(PORT); }
+quit { return ctx(QUIT); }
+socket { return ctx(SOCKET); }
+show { return ctx(SHOW); }
+sync { return ctx(SYNC); }
+log { return ctx(LOG); }
+new { return ctx(NEW); }
+off { return ctx(OFF); }
+on { return ctx(ON); }
+qa { return ctx(QA); }
+at { return ctx(AT); }
+h { return ctx(HELP); }
+q { return ctx(QUIT); }
+\? { return ctx(HELP); }
+
+unix:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+unix:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = yytext; return NAME; }
+
+[A-Za-z][A-Za-z0-9_.-]* { yylval.str = yytext; return HOSTNAME; }
+
+\"[^\"\n][^\"\n]*\" { /* strip quotes before returing */
+ yytext[strlen(yytext)-1] = '\0';
+ yylval.str = yytext+1;
+ return STRING;
+ }
+
+[0-9]+ { yylval.lval = atol(yytext); return NUMBER; }
+
+\#[^\n]* { }
+\n { if (!(mystate & INSPECLIST)) return EOL; }
+
+[ \r\t]+ { }
+
+. {
+ char emess[160];
+ sprintf(emess, "Unexpected character '%c'",
+ yytext[0]);
+ yyerror(emess);
+ }
+%%
+
+#ifdef FLEX_SCANNER
+static int in = '\n';
+
+int yywrap(void)
+{
+ return in == EOF;
+}
+
+int
+pmlcFlexInput (char * buf, int ms)
+{
+ extern int iflag;
+
+ if ( in == '\n' ) {
+ lineno++;
+ if ( iflag ) {
+ if ( mystate == GLOBAL )
+ printf ("pmlc> ");
+ else
+ printf ("? ");
+ fflush (stdout);
+ }
+ }
+
+ if (ms > 0 ) {
+ if ( (in = fgetc (yyin)) != EOF ) {
+ buf[0] = in & 0xFFU;
+ ms = 1;
+ if ( eflag ) {
+ putchar (in);
+ fflush (stdout);
+ }
+ } else {
+ ms = 0;
+ }
+ }
+
+ return (ms);
+}
+
+void
+skipAhead (void)
+{
+ if ( mystate & INSPECLIST ) {
+ while ( in != '}' && in != EOF ) {
+ char c;
+ pmlcFlexInput (&c, 1);
+ }
+ mystate = GLOBAL;
+ }
+
+ while ( (in != '\n') && (in != EOF) ) {
+ char c;
+ pmlcFlexInput (&c, 1);
+ }
+}
+
+#else
+static char lastc;
+static char peekc = '\0';
+
+char input(void)
+{
+ int get;
+ static int first = 1;
+
+ if (peekc) {
+ lastc = peekc;
+ peekc = '\0';
+ return lastc;
+ }
+
+ if (lastc == '\n' || first) {
+ extern int iflag;
+ if (iflag) {
+ if (mystate == GLOBAL)
+ printf("pmlc> ");
+ else
+ printf("? ");
+ fflush(stdout);
+ }
+ if (first)
+ first = 0;
+ else
+ lineno++;
+ }
+ else if (lastc == '\0')
+ return lastc;
+
+ get = getchar();
+ if (get == EOF)
+ lastc = '\0';
+ else {
+ lastc = get;
+ if (eflag) {
+ putchar(lastc);
+ fflush(stdout);
+ }
+ }
+
+ return lastc;
+}
+
+void unput(char c)
+{
+ peekc = c;
+}
+
+void
+skipAhead (void)
+{
+ int c = lastc;
+
+ for ( ; ; ) {
+ if (c == '\0')
+ break;
+ if ((mystate == GLOBAL || (mystate & INSPEC)) && c == '\n')
+ break;
+ if ((mystate & INSPECLIST) && c == '}')
+ break;
+ c = input();
+ }
+}
+
+int yywrap(void)
+{
+ return lastc == '\0';
+}
+#endif
diff --git a/src/pmlc/pmlc.c b/src/pmlc/pmlc.c
new file mode 100644
index 0000000..cb477fb
--- /dev/null
+++ b/src/pmlc/pmlc.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmlc.h"
+#include "gram.h"
+
+char *configfile;
+__pmLogCtl logctl;
+int parse_done;
+int pid = PM_LOG_NO_PID;
+int port = PM_LOG_NO_PORT;
+int is_unix; /* host spec is a unix: url. */
+int is_local; /* host spec is a local: url. */
+int is_socket_path; /* host spec is a url with a path. */
+char *tz; /* for -Z timezone */
+int tztype = TZ_LOCAL; /* timezone for status cmd */
+int eflag;
+int iflag;
+
+extern int parse_stmt;
+extern int tzchange;
+
+static char title[] = "Performance Co-Pilot Logger Control (pmlc), Version %s\n\n%s\n";
+static char menu[] =
+"pmlc commands\n\n"
+" show loggers [@<host>] display <pid>s of running pmloggers\n"
+" connect _logger_id [@<host>] connect to designated pmlogger\n"
+" status information about connected pmlogger\n"
+" query metric-list show logging state of metrics\n"
+" new volume start a new log volume\n"
+"\n"
+" log { mandatory | advisory } on <interval> _metric-list\n"
+" log { mandatory | advisory } off _metric-list\n"
+" log mandatory maybe _metric-list\n"
+"\n"
+" timezone local|logger|'<timezone>' change reporting timezone\n"
+" help print this help message\n"
+" quit exit from pmlc\n"
+"\n"
+" _logger_id is primary | <pid> | port <n>\n"
+" _metric-list is _metric-spec | { _metric-spec ... }\n"
+" _metric-spec is <metric-name> | <metric-name> [ <instance> ... ]\n";
+
+static int overrides(int, pmOptions *);
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "echo", 0, 'e', 0, "echo input" },
+ { "host", 1, 'h', "HOST", "connect to pmlogger using host specification" },
+ { "interactive", 0, 'i', 0, "be interactive and prompt" },
+ PMOPT_NAMESPACE,
+ { "primary", 0, 'P', 0, "connect to primary pmlogger" },
+ { "port", 1, 'p', "N", "connect to pmlogger on this TCP/IP port" },
+ PMOPT_TIMEZONE,
+ { "logzone", 0, 'z', 0, "set reporting timezone to local time for pmlogger" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:eh:in:Pp:zZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] [pid]",
+ .override = overrides,
+};
+
+static int
+overrides(int opt, pmOptions *opts)
+{
+ if (opt == 'h' || opt == 'p')
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts = 0; /* initialize to pander to gcc */
+ char *host = NULL;
+ char *endnum;
+ int primary;
+ size_t prefix_len;
+ char *prefix_end;
+
+ iflag = isatty(0);
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'e': /* echo input */
+ eflag++;
+ break;
+
+ case 'h': /* hostspec */
+ /*
+ * We need to know if a socket path has been specified.
+ */
+ host = opts.optarg;
+ prefix_end = strchr(host, ':');
+ if (prefix_end != NULL) {
+ prefix_len = prefix_end - host + 1;
+ if (prefix_len == 6 && strncmp(host, "local:", prefix_len) == 0)
+ is_local = 1;
+ else if (prefix_len == 5 && strncmp(host, "unix:", prefix_len) == 0)
+ is_unix = 1;
+ if (is_local || is_unix) {
+ const char *p;
+ /*
+ * Find out is a path was specified.
+ * Skip any initial path separators.
+ */
+ for (p = host + prefix_len; *p == __pmPathSeparator(); ++p)
+ ;
+ if (*p != '\0')
+ is_socket_path = 1;
+ }
+ }
+ break;
+
+ case 'i': /* be interactive */
+ iflag++;
+ break;
+
+ case 'P': /* connect to primary logger */
+ if (port != PM_LOG_NO_PORT || (is_unix && is_socket_path)) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ port = PM_LOG_PRIMARY_PORT;
+ }
+ break;
+
+ case 'p': /* connect via port */
+ if (port != PM_LOG_NO_PORT || is_unix) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ } else {
+ port = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || port <= PM_LOG_PRIMARY_PORT) {
+ pmprintf("%s: port must be numeric and greater than %d\n",
+ pmProgname, PM_LOG_PRIMARY_PORT);
+ opts.errors++;
+ }
+ }
+ break;
+ }
+ }
+
+ if (opts.optind < argc - 1)
+ opts.errors++;
+ else if (opts.optind == argc - 1) {
+ /* pid was specified */
+ if (port != PM_LOG_NO_PORT || (is_unix && is_socket_path)) {
+ pmprintf("%s: at most one of -P, -p, unix socket, or PID may be specified\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else {
+ pid = (int)strtol(argv[opts.optind], &endnum, 10);
+ if (*endnum != '\0' || pid <= PM_LOG_PRIMARY_PID) {
+ pmprintf("%s: PID must be a numeric process ID and greater than %d\n",
+ pmProgname, PM_LOG_PRIMARY_PID);
+ opts.errors++;
+ }
+ }
+ }
+
+ if (!opts.errors && host && pid == PM_LOG_NO_PID &&
+ port == PM_LOG_NO_PORT && !is_socket_path) {
+ pmprintf("%s: -h may not be used without -P or -p or a socket path or a PID\n",
+ pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.tzflag) {
+ tztype = TZ_LOGGER;
+ tzchange = 1;
+ } else if (opts.timezone) {
+ tz = opts.timezone;
+ tztype = TZ_OTHER;
+ tzchange = 1;
+ }
+
+ if (host == NULL)
+ host = "local:";
+
+ primary = 0;
+ if (port == PM_LOG_PRIMARY_PORT || pid == PM_LOG_PRIMARY_PID)
+ primary = 1;
+
+ if (pid != PM_LOG_NO_PID || port != PM_LOG_NO_PORT || is_socket_path)
+ sts = ConnectLogger(host, &pid, &port);
+
+ if (iflag)
+ printf(title, PCP_VERSION, menu);
+
+ if (pid != PM_LOG_NO_PID || port != PM_LOG_NO_PORT || is_socket_path) {
+ if (sts < 0) {
+ if (primary) {
+ fprintf(stderr, "Unable to connect to primary pmlogger at %s: ",
+ host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else if (is_socket_path) {
+ fprintf(stderr, "Unable to connect to pmlogger via the local socket at %s: ",
+ host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else if (port != PM_LOG_NO_PORT) {
+ fprintf(stderr, "Unable to connect to pmlogger on port %d at %s: ",
+ port, host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else {
+ fprintf(stderr, "Unable to connect to pmlogger pid %d at %s: ",
+ pid, host);
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ }
+ else {
+ if (primary)
+ printf("Connected to primary pmlogger at %s\n", host);
+ else if (is_socket_path)
+ printf("Connected to pmlogger via local socket at %s\n", host);
+ else if (port != PM_LOG_NO_PORT)
+ printf("Connected to pmlogger on port %d at %s\n", port, host);
+ else
+ printf("Connected to pmlogger pid %d at %s\n", pid, host);
+ }
+ }
+
+ for ( ; ; ) {
+ char *realhost;
+
+ is_local = 0;
+ is_unix = 0;
+ is_socket_path = 0;
+ parse_stmt = -1;
+ metric_cnt = 0;
+ yyparse();
+ if (yywrap()) {
+ if (iflag)
+ putchar('\n');
+ break;
+ }
+ if (metric_cnt < 0)
+ continue;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ printf("stmt=%d, state=%d, control=%d, hostspec=%s, pid=%d, port=%d\n",
+ parse_stmt, state, control, hostname, pid, port);
+#endif
+
+ realhost = (hostname == NULL) ? host : hostname;
+ switch (parse_stmt) {
+
+ case SHOW:
+ ShowLoggers(realhost);
+ break;
+
+ case CONNECT:
+ /* The unix: url requres either 'primary', a pid or a socket path. */
+ if (is_unix && pid == PM_LOG_NO_PID && ! is_socket_path) {
+ fprintf(stderr, "The 'unix:' url requires either 'primary', a pid or a socket path");
+ if (still_connected(sts))
+ fprintf(stderr, "\n");
+ break;
+ }
+ /* The local: url requres either 'primary', a pid a port or a socket path. */
+ if (is_local && pid == PM_LOG_NO_PID && port == PM_LOG_NO_PORT && ! is_socket_path) {
+ fprintf(stderr, "The 'local:' url requires either 'primary', a pid, a port or a socket path");
+ if (still_connected(sts))
+ fprintf(stderr, "\n");
+ break;
+ }
+ primary = 0;
+ if (port == PM_LOG_PRIMARY_PORT || pid == PM_LOG_PRIMARY_PID)
+ primary = 1;
+ if ((sts = ConnectLogger(realhost, &pid, &port)) < 0) {
+ if (primary) {
+ fprintf(stderr, "Unable to connect to primary pmlogger at %s: ",
+ realhost);
+ }
+ else if (is_socket_path) {
+ fprintf(stderr, "Unable to connect to pmlogger via local socket at %s: ",
+ realhost);
+ }
+ else if (port != PM_LOG_NO_PORT) {
+ fprintf(stderr, "Unable to connect to pmlogger on port %d at %s: ",
+ port, realhost);
+ }
+ else {
+ fprintf(stderr, "Unable to connect to pmlogger pid %d at %s: ",
+ pid, realhost);
+ }
+ if (still_connected(sts))
+ fprintf(stderr, "%s\n", pmErrStr(sts));
+ }
+ else
+ /* if the timezone is "logger time", it has changed
+ * because the logger may be in a different zone
+ * (note that tzchange may already be set (e.g. -Z and
+ * this connect is the first).
+ */
+ tzchange |= (tztype == TZ_LOGGER);
+ break;
+
+ case HELP:
+ puts(menu);
+ break;
+
+ case LOG:
+ if (state == PM_LOG_ENQUIRE) {
+ Query();
+ break;
+ }
+ if (logfreq == -1)
+ logfreq = 0;
+ if (state == PM_LOG_ON) {
+ if (logfreq < 0) {
+fprintf(stderr, "Logging delta (%d msec) must be positive\n", logfreq);
+ break;
+ }
+ else if (logfreq > PMLC_MAX_DELTA) {
+fprintf(stderr, "Logging delta (%d msec) cannot be bigger than %d msec\n", logfreq, PMLC_MAX_DELTA);
+ break;
+ }
+ }
+ LogCtl(control, state, logfreq);
+ break;
+
+ case QUIT:
+ printf("Goodbye\n");
+ DisconnectLogger();
+ exit(0);
+ break;
+
+ case STATUS:
+ Status(pid, primary);
+ break;
+
+ case NEW:
+ NewVolume();
+ break;
+
+ case TIMEZONE:
+ tzchange = 1;
+ break;
+
+ case SYNC:
+ Sync();
+ break;
+
+ case QA:
+ if (qa_case == 0)
+ fprintf(stderr, "QA Test Case deactivated\n");
+ else
+ fprintf(stderr, "QA Test Case #%d activated\n", qa_case);
+ Qa();
+ }
+
+ if (hostname != NULL) {
+ free(hostname);
+ hostname = NULL;
+ }
+
+ }
+
+ DisconnectLogger();
+ exit(0);
+}
diff --git a/src/pmlc/pmlc.h b/src/pmlc/pmlc.h
new file mode 100644
index 0000000..83fd38a
--- /dev/null
+++ b/src/pmlc/pmlc.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PMLC_H
+#define _PMLC_H
+
+/* config file parser states (bit field values) */
+#define GLOBAL 0
+#define INSPEC 1
+#define INSPECLIST 2
+
+/* timezone to use when printing status */
+#define TZ_LOCAL 0
+#define TZ_LOGGER 1
+#define TZ_OTHER 2
+
+/* timezone variables */
+extern char *tz; /* for -Z timezone */
+extern int tztype; /* timezone for status cmd */
+
+/* parse summary back from yacc to main */
+extern char *hostname;
+extern int state;
+extern int control;
+extern int mystate;
+extern int qa_case;
+
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern int yywrap(void);
+extern int yylex(void);
+extern void skipAhead (void);
+extern int yyparse(void);
+extern void beginmetrics(void);
+extern void endmetrics(void);
+extern void beginmetgrp(void);
+extern void endmetgrp(void);
+extern void addmetric(const char *);
+extern void addinst(char *, int);
+extern int connected(void);
+extern int still_connected(int);
+extern int metric_cnt;
+#ifdef PCP_DEBUG
+extern void dumpmetrics(FILE *);
+#endif
+
+/* connection routines */
+extern int ConnectLogger(char *, int *, int *);
+extern void DisconnectLogger(void);
+extern int ConnectPMCD(void);
+
+/* command routines */
+extern int logfreq;
+extern void ShowLoggers(char *);
+extern void Query(void);
+extern void LogCtl(int, int, int);
+extern void Status(int, int);
+extern void NewVolume(void);
+extern void Sync(void);
+extern void Qa(void);
+
+/* information about an instance domain */
+typedef struct {
+ pmInDom indom;
+ int n_insts;
+ int *inst;
+ char **name;
+} indom_t;
+
+/* a metric plus an optional list of instances */
+typedef struct {
+ char *name;
+ pmID pmid;
+ int indom; /* index of indom (or -1) */
+ int n_insts; /* number of insts for this metric */
+ int *inst; /* list of insts for this metric */
+ struct {
+ unsigned selected : 1, /* apply instances to metric? */
+ new : 1, /* new in current PMNS subtree */
+ has_insts : 1; /* free inst list? */
+ } status;
+} metric_t;
+
+#endif /* _PMLC_H */
diff --git a/src/pmlc/util.c b/src/pmlc/util.c
new file mode 100644
index 0000000..30e3881
--- /dev/null
+++ b/src/pmlc/util.c
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmlc.h"
+
+/* this pmResult is built after parsing the current statement. The action
+ * routines (Status, LogReq) send it to the logger as a request.
+ */
+pmResult *logreq = NULL;
+int sz_logreq = 0;
+int n_logreq = 0;
+
+int n_metrics = 0;
+int sz_metrics = 0;
+metric_t *metric = NULL;
+
+int n_indoms = 0;
+int sz_indoms = 0;
+indom_t *indom = NULL;
+
+void
+freemetrics(void)
+{
+ int i;
+ metric_t *mp;
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ free(mp->name);
+ if (mp->status.has_insts && mp->inst != NULL)
+ free(mp->inst);
+ }
+ n_metrics = 0;
+ /* keep the array around for re-use */
+}
+
+void
+freeindoms(void)
+{
+ int i;
+ indom_t *ip;
+
+ for (i = 0, ip = indom; i < n_indoms; i++, ip++) {
+ free(ip->inst);
+ free(ip->name);
+ }
+ free(indom);
+ sz_indoms = n_indoms = 0;
+ indom = NULL;
+}
+
+/* Return a pointer to the specified instance domain, adding it to the list if
+ * it is not already present. Returns < 0 on error.
+ */
+int
+addindom(pmInDom newindom, int *resptr)
+{
+ int i;
+ indom_t *ip;
+
+ for (i = 0; i < n_indoms; i++)
+ if (newindom == indom[i].indom) {
+ *resptr = i;
+ return 0;
+ }
+
+ *resptr = -1; /* in case of errors */
+ if (n_indoms >= sz_indoms) {
+ sz_indoms += 4;
+ i = sz_indoms * sizeof(indom_t);
+ if ((indom = (indom_t *)realloc(indom, i)) == NULL) {
+ __pmNoMem("expanding instance domain array", i, PM_FATAL_ERR);
+ }
+ }
+ ip = &indom[n_indoms];
+ ip->inst = NULL;
+ ip->name = NULL;
+
+ if ((i = pmGetInDom(newindom, &ip->inst, &ip->name)) < 0) {
+ *resptr = -1;
+ return i;
+ }
+ ip->indom = newindom;
+ ip->n_insts = i;
+ *resptr = n_indoms;
+ n_indoms++;
+ return 0;
+}
+
+int metric_cnt; /* status of addmetric operation(s) */
+static int had_insts; /* new metric(s) had instances specified */
+static int n_selected; /* number of metrics selected */
+static int first_inst; /* check consistency of new metrics' InDoms */
+
+void
+beginmetrics(void)
+{
+ metric_cnt = 0;
+ fflush(stdout);
+ fflush(stderr);
+ freemetrics();
+}
+
+void
+beginmetgrp(void)
+{
+ int i;
+ metric_t *mp;
+
+ had_insts = 0;
+ n_selected = 0;
+ first_inst = 1;
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ mp->status.selected = 0;
+ mp->status.new = 0;
+ }
+}
+
+/* Perform any instance domain fixups and error checks required for the latest
+ * metrics added.
+ */
+void
+endmetgrp(void)
+{
+ int i;
+ metric_t *mp;
+
+ if (n_metrics <= 0) {
+ if (metric_cnt >= 0)
+ metric_cnt = PM_ERR_PMID;
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* just added metric, no instances => use all instances */
+ if (mp->status.new) {
+ if (mp->indom != -1 && !had_insts) {
+ mp->n_insts = indom[mp->indom].n_insts;
+ mp->inst = indom[mp->indom].inst;
+ mp->status.has_insts = 0;
+ }
+ }
+ /* metric was there already */
+ else
+ if (mp->indom == -1)
+ fprintf(stderr, "Warning: %s has already been specified\n", mp->name);
+ else
+ /* if metric had specific instances */
+ if (mp->status.has_insts) {
+ if (!had_insts) {
+ fprintf(stderr,
+ "Warning: %s had instance(s) specified previously.\n"
+ " Using all instances since none specified this time\n",
+ mp->name);
+ if (mp->inst != NULL)
+ free(mp->inst);
+ mp->n_insts = indom[mp->indom].n_insts;
+ mp->inst = indom[mp->indom].inst;
+ mp->status.has_insts = 0;
+ }
+ }
+ /* metric had "use all instances" */
+ else
+ if (!had_insts)
+ fprintf(stderr,
+ "Warning: already using all instances for %s\n",
+ mp->name);
+ else
+ fprintf(stderr,
+ "Warning: instance(s) specified for %s\n"
+ " (already using all instances)\n",
+ mp->name);
+ }
+}
+
+void
+endmetrics(void)
+{
+ int i, j, need;
+ metric_t *mp;
+ pmValueSet *vsp;
+
+ if (metric_cnt < 0) {
+ if (connected())
+ fputs("Logging statement ignored due to error(s)\n", stderr);
+ goto done;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ dumpmetrics(stdout);
+#endif
+
+ /* free any old values in the reusable pmResult skeleton */
+ if (n_logreq) {
+ for (i = 0; i < n_logreq; i++)
+ free(logreq->vset[i]);
+ n_logreq = 0;
+ }
+
+ /* build a result from the metrics and instances in the metric array */
+ if (n_metrics > sz_logreq) {
+ if (sz_logreq)
+ free(logreq);
+ need = sizeof(pmResult) + (n_metrics - 1) * sizeof(pmValueSet *);
+ /* - 1 because a pmResult already contains one pmValueSet ptr */
+ if ((logreq = (pmResult *)malloc(need)) == NULL) {
+ __pmNoMem("building result to send", need, PM_FATAL_ERR);
+ }
+ sz_logreq = n_metrics;
+ }
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ need = sizeof(pmValueSet);
+ /* pmValueSet contains one pmValue, allow for more if > 1 inst */
+ if (mp->status.has_insts && mp->n_insts > 1)
+ need += (mp->n_insts - 1) * sizeof(pmValue);
+ if ((vsp = (pmValueSet *)malloc(need)) == NULL) {
+ __pmNoMem("building result value set", need , PM_FATAL_ERR);
+ }
+ logreq->vset[i] = vsp;
+ vsp->pmid = mp->pmid;
+ if (mp->indom == -1)
+ vsp->numval = 1;
+ else
+ vsp->numval = mp->status.has_insts ? mp->n_insts : 0;
+ vsp->valfmt = PM_VAL_INSITU;
+ if (mp->status.has_insts)
+ for (j = 0; j < vsp->numval; j++)
+ vsp->vlist[j].inst = mp->inst[j];
+ else
+ vsp->vlist[0].inst = PM_IN_NULL;
+ }
+ logreq->numpmid = n_metrics;
+ n_logreq = n_metrics;
+
+done:
+ fflush(stderr);
+ fflush(stdout);
+}
+
+/* Add a metric to the metric list. Sets metric_cnt to < 0 on fatal error. */
+
+void
+addmetric(const char *name)
+{
+ int i;
+ pmID pmid;
+ int need;
+ metric_t *mp;
+ pmDesc desc;
+ int sts;
+
+ if (metric_cnt < 0)
+ return;
+
+ /* Cast const away as pmLookUpName should not modify name */
+ if ((sts = pmLookupName(1, (char **)&name, &pmid)) < 0) {
+ metric_cnt = sts;
+ return;
+ }
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++)
+ if (pmid == mp->pmid)
+ break;
+
+ if (i >= n_metrics) {
+ /* expand metric array if necessary */
+ if (n_metrics >= sz_metrics) {
+ sz_metrics += 4;
+ need = sz_metrics * sizeof(metric_t);
+ if ((metric = (metric_t *)realloc(metric, need)) == NULL) {
+ __pmNoMem("expanding metric array", need, PM_FATAL_ERR);
+ }
+ }
+ mp = &metric[i];
+ mp->status.new = 1;
+ }
+ mp->status.selected = 1;
+ n_selected++;
+ if (i < n_metrics) /* already have this metric */
+ return;
+
+ if ((sts = pmLookupDesc(pmid, &desc)) == PM_ERR_PMID
+ || desc.type == PM_TYPE_NOSUPPORT) {
+ /*
+ * Name is bad or descriptor is not available ...
+ * this is not fatal, but need to back off a little
+ */
+ n_selected--;
+ return;
+ }
+ if (sts < 0) {
+ /* other errors are more serious */
+ metric_cnt = sts;
+ return;
+ }
+ if (desc.indom == PM_INDOM_NULL)
+ i = -1;
+ else
+ if ((metric_cnt = addindom(desc.indom, &i)) < 0)
+ return;
+
+ mp->name = strdup(name);
+ mp->pmid = pmid;
+ mp->indom = i;
+ mp->n_insts = 0;
+ mp->inst = NULL;
+ mp->status.has_insts = 0;
+ metric_cnt = ++n_metrics;
+ return;
+}
+
+/* Add an instance to the selected metric(s) on the metric list.
+ * If name is NULL, use instance number in inst.
+ * Check that
+ * - the last group of metrics added have the same pmInDom
+ * - the specified instance exists in the metrics' instance domain.
+ */
+void
+addinst(char *name, int instid)
+{
+ metric_t *mp, *first_mp;
+ indom_t *ip;
+ int m, i, j, need;
+ int inst;
+ static int done_inst_msg = 0;
+
+ /* don't try to add instances if one more metrics were erroneous */
+ if (metric_cnt < 0)
+ return;
+
+ had_insts = 1;
+
+ /* Find the first selected metric */
+ for (m = 0, mp = metric; m < n_metrics; m++, mp++)
+ if (mp->status.selected)
+ break;
+#ifdef PCP_DEBUG
+ if (m >= n_metrics) {
+ fprintf(stderr, "Ark! No metrics selected for addinst(%s)\n", name);
+ abort();
+ }
+#endif
+ first_mp = mp;
+
+ if (first_inst) {
+ /* check that the first metric doesn't have PM_INDOM_NULL */
+ if (mp->indom == -1) {
+ metric_cnt = PM_ERR_INDOM;
+ fprintf(stderr, "%s has no instance domain but an instance was specified\n",
+ mp->name);
+ return;
+ }
+
+ /* check that all of the metrics have the same instance domain */
+ if (n_selected > 1) {
+ i = 0;
+ for (i++, mp++; m < n_metrics; m++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* check pointers to indoms; PM_INDOM_NULL has -1 */
+ if (first_mp->indom != mp->indom) {
+ if (i++ == 0) {
+ fprintf(stderr,
+ "The instance domains for the following metrics clash with %s:\n",
+ first_mp->name);
+ metric_cnt = PM_ERR_INDOM;
+ }
+ if (i < 5) { /* elide any domain errors after this */
+ fputs(mp->name, stderr);
+ putc('\n', stderr);
+ }
+ else {
+ fputs("(etc.)\n", stderr);
+ break;
+ }
+ }
+ }
+ if (i)
+ return;
+ }
+ first_inst = 0;
+ }
+
+ mp = first_mp; /* go back to first selected metric */
+ ip = &indom[mp->indom];
+ for (i = 0; i < ip->n_insts; i++)
+ if (name != NULL) {
+ if (strcasecmp(name, ip->name[i]) == 0)
+ break;
+ }
+ else
+ if (instid == ip->inst[i])
+ break;
+
+ for ( ; m < n_metrics; m++, mp++) {
+ if (!mp->status.selected)
+ continue;
+
+ /* check that metric with explicit instances has the specified inst.
+ * For those metrics that specify "all instances" an instance not there yet
+ * is OK (but generates a warning) since we don't need to give pmlogger any
+ * instance ids.
+ * For metrics with specific instances, an unknown instance is an error
+ * since the instance id can't be given to pmlogger.
+ */
+ if (i >= ip->n_insts) {
+ if (mp->status.has_insts || mp->status.new) {
+ metric_cnt = PM_ERR_INST;
+ if (name != NULL)
+ fprintf(stderr, "%s currently has no instance named %s\n", mp->name, name);
+ else
+ fprintf(stderr, "%s currently has no instance number %d\n", mp->name, instid);
+ if (!done_inst_msg) {
+ fputs(" - you may only specify metric instances active now. However if no\n"
+ " instances are specified, pmlogger will use all of the instances\n"
+ " available at the time the metric is logged\n",
+ stderr);
+ done_inst_msg = 1;
+ }
+ }
+ /* for an old metric specifying all instances warn if the specified
+ * instance is not currently in the instance domain.
+ */
+ else {
+ if (name != NULL)
+ fprintf(stderr,
+ "Warning: instance %s not currently in %s's instance domain\n",
+ name, mp->name);
+ else
+ fprintf(stderr,
+ "Warning: instance %d not currently in %s's instance domain\n",
+ instid, mp->name);
+ fputs(" (getting all instances anyway)\n", stderr);
+ }
+ continue;
+ }
+ inst = ip->inst[i];
+
+ /* check that we don't already have the same instance */
+ for (j = 0; j < mp->n_insts; j++)
+ if (inst == mp->inst[j])
+ break;
+
+ if (j < mp->n_insts) { /* already have inst */
+ if (mp->status.has_insts) {
+ if (name != NULL) {
+ fprintf(stderr,
+ "Warning: instance %s already specified for %s\n",
+ name, mp->name);
+ }
+ else {
+ fprintf(stderr,
+ "Warning: instance %d already specified for %s\n",
+ instid, mp->name);
+ }
+ }
+
+ /* if the metric had no insts of its own, (it specifies all insts)
+ * just do nothing. endmetgrp() will print a single message which
+ * is better than having, one printed for each instance specified.
+ */
+ continue;
+ }
+
+ else { /* don't have this inst */
+ /* add inst for new metric or old metric with explicit insts */
+ if (mp->status.new || mp->status.has_insts) {
+ j = mp->n_insts++;
+ if (j == 0)
+ mp->status.has_insts = 1;
+ need = mp->n_insts * sizeof(int);
+ if ((mp->inst = (int *)realloc(mp->inst, need)) == NULL) {
+ if (name != NULL)
+ fprintf(stderr, "%s inst %s: ", mp->name, name);
+ else
+ fprintf(stderr, "%s inst %d: ", mp->name, instid);
+ __pmNoMem("expanding instance array", need, PM_FATAL_ERR);
+ }
+ mp->inst[j] = inst;
+ }
+ }
+ }
+}
+
+#ifdef PCP_DEBUG
+void
+dumpmetrics(FILE *f)
+{
+ int i, j, k;
+ metric_t *mp;
+
+ fprintf(f, " Inst Inst Name\n");
+ fprintf(f, " ====== =========\n");
+
+ for (i = 0, mp = metric; i < n_metrics; i++, mp++) {
+ fprintf(f, "%s\n", mp->name);
+ if (mp->indom == -1)
+ fprintf(f, " singular instance\n");
+ else {
+ indom_t *ip = &indom[mp->indom];
+ int *inst = ip->inst;
+ char **name = ip->name;
+ int n_insts = ip->n_insts;
+
+ /* No instances specified, use them all */
+ if (mp->n_insts == 0)
+ for (j = 0; j < n_insts; j++)
+ fprintf(f, " %6d %s\n", inst[j], name[j]);
+ else
+ for (j = 0; j < mp->n_insts; j++) {
+ int m_inst = mp->inst[j];;
+
+ for (k = 0; k < n_insts; k++)
+ if (m_inst == inst[k])
+ break;
+ if (k < n_insts)
+ fprintf(f, " %6d %s\n", inst[k], name[k]);
+ else
+ fprintf(f, " KAPOWIE! inst %d not found\n", m_inst);
+ }
+ }
+ putc('\n', f);
+ }
+}
+#endif
diff --git a/src/pmlock/GNUmakefile b/src/pmlock/GNUmakefile
new file mode 100644
index 0000000..c397a1f
--- /dev/null
+++ b/src/pmlock/GNUmakefile
@@ -0,0 +1,36 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+LLDLIBS = $(LIB_FOR_BASENAME)
+CFILES = pmlock.c
+CMDTARGET = pmlock$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlock/pmlock.c b/src/pmlock/pmlock.c
new file mode 100644
index 0000000..c47af06
--- /dev/null
+++ b/src/pmlock/pmlock.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+
+int
+main(int argc, char **argv)
+{
+ int verbose = 0;
+
+ if (argc > 1 &&
+ (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--verbose") == 0)) {
+ verbose = 1;
+ argc--;
+ argv++;
+ }
+ if (argc != 2 || (argc == 2 && strcmp(argv[1], "-?") == 0)) {
+ fprintf(stderr, "Usage: pmlock [-v,--verbose] file\n");
+ exit(1);
+ }
+ if (open(argv[1], O_CREAT|O_EXCL|O_RDONLY, 0) < 0) {
+ if (verbose) {
+ if (oserror() == EACCES) {
+ char *p = dirname(argv[1]);
+ if (access(p, W_OK) == -1)
+ printf("%s: Directory not writable\n", p);
+ else
+ printf("%s: %s\n", argv[1], strerror(EACCES));
+ }
+ else
+ printf("%s: %s\n", argv[1], osstrerror());
+ }
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/pmlogcheck/GNUmakefile b/src/pmlogcheck/GNUmakefile
new file mode 100644
index 0000000..8602693
--- /dev/null
+++ b/src/pmlogcheck/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmlogcheck.c
+CMDTARGET = pmlogcheck$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlogcheck/RFC b/src/pmlogcheck/RFC
new file mode 100644
index 0000000..5ff62e4
--- /dev/null
+++ b/src/pmlogcheck/RFC
@@ -0,0 +1,64 @@
+In the beginning all PCP archives were created by pmlogger. So a
+corrupt PCP archive meant pmlogger had a bug or was interrupted in
+some manner.
+
+We fixed the bugs (!) and hardened the checking of archives to ensure
+we could process as much as possible of an interrupted archive.
+
+But things changed and archives could be created in more ways ...
+- pmlogmerge, so more checks to ensure the semantic consistency of
+ the input archives, but again we could assume pmlogmerge would
+ create correct archives
+- pmlogreduce, same as pmlogmerge
+
+With the introduction of libpcp_import and bindings for Perl and
+Python, we now have the possibility of and infinite number of scripts
+creating archives using low-level calls that can be combined to
+produce and infinite variety of corrupted archives. The first example
+of this class is https://bugzilla.redhat.com/show_bug.cgi?id=958745
+but we should expect more of these to be lurking in the future.
+
+When these problems appear, the initial triage effort is directed
+(rightly) towards the replay tool that is failing, and it takes
+considerable time and effort to determine that the root cause is
+a corrupted archive, not an application or libpcp failure.
+
+Some corruption we can (and do) catch in libpcp. We could probably
+do more there, but the most common usage with "interp" mode replay
+makes it almost impossible to check timestamps on the fly, so the
+but above would be most unlikely to be found there.
+
+So, in the spirit of the original Unix filesystem, I'm proposing
+an ncheck/icheck (none of you're whimpy fsck in those days) tool,
+pmlogcheck.
+
+The objective would be to have one anally retentive tool that can
+assert the "goodness" of a PCP archive, as the first step in any
+triage, even before pmdumplog is used.
+
+pmlogcheck would certainly be a multi-pass tool, initially using
+no libpcp services to read blocks of the files, and then graduate
+to the low-level libpcp routines once basic sanity has been
+established.
+
+The sorts of checks it might try would include:
+
+x. process temporal index if any
+ [ ] check label
+ [ ] if missing, warn
+ [ ] else load temporal index
+x. process meta file
+ [ ] check label
+ [ ] check header-trailer len for every record
+ [ ] check timestamps for indoms are monotonic increasing (and >= label
+ record start)
+ [ ] check timestamp and offset against temporal index (if available)
+ [ ] if OK load metadata
+x. process each metric volume file
+ [ ] check label
+ [ ] check header-trailer len for every record
+ [ ] check timestamps are monotonic increasing (and >= label record start)
+ [ ] check timestamp and offset against temporal index (if available)
+ [ ] check pmids are defined in meta data
+ [ ] check instances are defined in metadata
+ [ ] check value encoding matches metadata type
diff --git a/src/pmlogcheck/TODO b/src/pmlogcheck/TODO
new file mode 100644
index 0000000..6fcb31c
--- /dev/null
+++ b/src/pmlogcheck/TODO
@@ -0,0 +1,11 @@
+- incorporate label checking logic from pmloglabel/pmloglabel.c
+
+- see existing src/pmlogsummary/pmlogcheck.c
+
+- conditional checking from pmNewContext with PM_CONTEXT_ARCHIVE,
+ based on an environment variable (or maybe even a type modifier
+ to pmNewContext, in the flavor of PM_CTXFLAG_SECURE)
+
+- consideration for repair
+ (a) automatic
+ (b) interactive
diff --git a/src/pmlogcheck/pmlogcheck.c b/src/pmlogcheck/pmlogcheck.c
new file mode 100644
index 0000000..2899f06
--- /dev/null
+++ b/src/pmlogcheck/pmlogcheck.c
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2013 Ken McDonell.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+
+#define STS_FATAL -2
+#define STS_WARNING -1
+#define STS_OK 0
+
+static struct timeval tv;
+static int numpmid;
+static pmID *pmid;
+static pmID pmid_flags;
+static pmID pmid_missed;
+static char *archpathname; /* from the command line */
+static char *archbasename; /* after basename() */
+static char *archdirname; /* after dirname() */
+#define STATE_OK 1
+#define STATE_MISSING 2
+#define STATE_BAD 3
+static int index_state = STATE_MISSING;
+static int meta_state = STATE_MISSING;
+static int log_state = STATE_MISSING;
+static int sflag;
+static int vflag;
+static int sep;
+
+/*
+ * return -1, 0 or 1 as the struct timeval's compare
+ * a < b, a == b or a > b
+ */
+int
+tvcmp(struct timeval a, struct timeval b)
+{
+ if (a.tv_sec < b.tv_sec)
+ return -1;
+ if (a.tv_sec > b.tv_sec)
+ return 1;
+ if (a.tv_usec < b.tv_usec)
+ return -1;
+ if (a.tv_usec > b.tv_usec)
+ return 1;
+ return 0;
+}
+
+static int
+do_size(pmResult *rp)
+{
+ int nbyte = 0;
+ int i;
+ int j;
+ /*
+ * Externally the log record looks like this ...
+ * :----------:-----------:..........:---------:
+ * | int len | timestamp | pmResult | int len |
+ * :----------:-----------:..........:---------:
+ *
+ * start with sizes of the header len, timestamp, numpmid, and
+ * trailer len
+ */
+ nbyte = sizeof(int) + sizeof(__pmTimeval) + sizeof(int);
+ /* len + timestamp + len */
+ nbyte += sizeof(int);
+ /* numpmid */
+ for (i = 0; i < rp->numpmid; i++) {
+ pmValueSet *vsp = rp->vset[i];
+ nbyte += sizeof(pmID) + sizeof(int); /* + pmid[i], numval */
+ if (vsp->numval > 0) {
+ nbyte += sizeof(int); /* + valfmt */
+ for (j = 0; j < vsp->numval; j++) {
+ nbyte += sizeof(__pmValue_PDU); /* + pmValue[j] */
+ if (vsp->valfmt != PM_VAL_INSITU)
+ /* + pmValueBlock */
+ /* rounded up */
+ nbyte += PM_PDU_SIZE_BYTES(vsp->vlist[j].value.pval->vlen);
+ }
+ }
+ }
+
+ return nbyte;
+}
+
+static void
+setup_event_derived_metrics(void)
+{
+ int sts;
+
+ if (pmid_flags == 0) {
+ /*
+ * get PMID for event.flags and event.missed
+ * note that pmUnpackEventRecords() will have called
+ * __pmRegisterAnon(), so the anon metrics
+ * should now be in the PMNS
+ */
+ char *name_flags = "event.flags";
+ char *name_missed = "event.missed";
+
+ if ((sts = pmLookupName(1, &name_flags, &pmid_flags)) < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_flags, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_flags)->item = 1;
+ }
+ sts = pmLookupName(1, &name_missed, &pmid_missed);
+ if (sts < 0) {
+ /* should not happen! */
+ fprintf(stderr, "Warning: cannot get PMID for %s: %s\n",
+ name_missed, pmErrStr(sts));
+ /* avoid subsequent warnings ... */
+ __pmid_int(&pmid_missed)->item = 1;
+ }
+ }
+}
+
+static int
+dump_nrecords(int nrecords, int nmissed)
+{
+ printf("%d", nrecords);
+ if (nmissed > 0)
+ printf(" (and %d missed)", nmissed);
+ if (nrecords + nmissed == 1)
+ printf(" event record\n");
+ else
+ printf(" event records\n");
+ return 0;
+}
+
+static int
+dump_nparams(int numpmid)
+{
+ if (numpmid == 0) {
+ printf(" ---\n");
+ printf(" No parameters\n");
+ return -1;
+ }
+ if (numpmid < 0) {
+ printf(" ---\n");
+ printf(" Error: illegal number of parameters (%d)\n",
+ numpmid);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+dump_parameter(pmValueSet *xvsp, int index, int *flagsptr)
+{
+ int sts, flags = *flagsptr;
+ pmDesc desc;
+ char *name;
+
+ if (pmNameID(xvsp->pmid, &name) >= 0) {
+ if (index == 0) {
+ if (xvsp->pmid == pmid_flags) {
+ flags = xvsp->vlist[0].value.lval;
+ printf(" flags 0x%x", flags);
+ printf(" (%s) ---\n", pmEventFlagsStr(flags));
+ free(name);
+ *flagsptr = flags;
+ return;
+ }
+ printf(" ---\n");
+ }
+ if ((flags & PM_EVENT_FLAG_MISSED) && index == 1 &&
+ (xvsp->pmid == pmid_missed)) {
+ printf(" ==> %d missed event records\n",
+ xvsp->vlist[0].value.lval);
+ free(name);
+ return;
+ }
+ printf(" %s (%s):", pmIDStr(xvsp->pmid), name);
+ free(name);
+ }
+ else
+ printf(" PMID: %s:", pmIDStr(xvsp->pmid));
+ if ((sts = pmLookupDesc(xvsp->pmid, &desc)) < 0) {
+ printf(" pmLookupDesc: %s\n", pmErrStr(sts));
+ return;
+ }
+ printf(" value ");
+ pmPrintValue(stdout, xvsp->valfmt, desc.type, &xvsp->vlist[0], 1);
+ putchar('\n');
+}
+
+static void
+dump_event(const char *name, pmValueSet *vsp, int index, int indom, int type)
+{
+ int r; /* event records */
+ int p; /* event parameters */
+ int nrecords;
+ int nmissed = 0;
+ int highres = (type == PM_TYPE_HIGHRES_EVENT);
+ int flags;
+ char *iname;
+ pmValue *vp = &vsp->vlist[index];
+
+ setup_event_derived_metrics();
+
+ if (index > 0)
+ printf(" ");
+ printf(" %s (%s", pmIDStr(vsp->pmid), name);
+ if (indom != PM_INDOM_NULL) {
+ putchar('[');
+ if (pmNameInDom(indom, vp->inst, &iname) < 0)
+ printf("%d or ???])", vp->inst);
+ else {
+ printf("%d or \"%s\"])", vp->inst, iname);
+ free(iname);
+ }
+ }
+ else {
+ printf(")");
+ }
+ printf(": ");
+
+ if (highres) {
+ pmHighResResult **hr;
+
+ if ((nrecords = pmUnpackHighResEventRecords(vsp, index, &hr)) < 0)
+ return;
+ if (nrecords == 0) {
+ printf("No event records\n");
+ pmFreeHighResEventResult(hr);
+ return;
+ }
+
+ for (r = 0; r < nrecords; r++) {
+ if (hr[r]->numpmid == 2 && hr[r]->vset[0]->pmid == pmid_flags &&
+ (hr[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
+ hr[r]->vset[1]->pmid == pmid_missed) {
+ nmissed += hr[r]->vset[1]->vlist[0].value.lval;
+ }
+ }
+ dump_nrecords(nrecords, nmissed);
+
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintHighResStamp(stdout, &hr[r]->timestamp);
+ if (dump_nparams(hr[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < hr[r]->numpmid; p++)
+ dump_parameter(hr[r]->vset[p], p, &flags);
+ }
+ pmFreeHighResEventResult(hr);
+ }
+ else {
+ pmResult **res;
+
+ if ((nrecords = pmUnpackEventRecords(vsp, index, &res)) < 0)
+ return;
+ if (nrecords == 0) {
+ printf("No event records\n");
+ pmFreeEventResult(res);
+ return;
+ }
+
+ for (r = 0; r < nrecords; r++) {
+ if (res[r]->numpmid == 2 && res[r]->vset[0]->pmid == pmid_flags &&
+ (res[r]->vset[0]->vlist[0].value.lval & PM_EVENT_FLAG_MISSED) &&
+ res[r]->vset[1]->pmid == pmid_missed) {
+ nmissed += res[r]->vset[1]->vlist[0].value.lval;
+ }
+ }
+ dump_nrecords(nrecords, nmissed);
+
+ for (r = 0; r < nrecords; r++) {
+ printf(" --- event record [%d] timestamp ", r);
+ __pmPrintStamp(stdout, &res[r]->timestamp);
+ if (dump_nparams(res[r]->numpmid) < 0)
+ continue;
+ flags = 0;
+ for (p = 0; p < res[r]->numpmid; p++)
+ dump_parameter(res[r]->vset[p], p, &flags);
+ }
+ pmFreeEventResult(res);
+ }
+}
+
+static void
+dump_metric(const char *mname, pmValueSet *vsp, int index, int indom, int type)
+{
+ pmValue *vp = &vsp->vlist[index];
+ char *iname;
+
+ if (index == 0) {
+ printf(" %s (%s):", pmIDStr(vsp->pmid), mname);
+ if (vsp->numval > 1) {
+ putchar('\n');
+ printf(" ");
+ }
+ }
+ else
+ printf(" ");
+
+ if (indom != PM_INDOM_NULL) {
+ printf(" inst [");
+ if (pmNameInDom(indom, vp->inst, &iname) < 0)
+ printf("%d or ???]", vp->inst);
+ else {
+ printf("%d or \"%s\"]", vp->inst, iname);
+ free(iname);
+ }
+ }
+ printf(" value ");
+ pmPrintValue(stdout, vsp->valfmt, type, vp, 1);
+ putchar('\n');
+}
+
+static void
+dumpResult(pmResult *resp)
+{
+ int i;
+ int j;
+ int n;
+ char *mname;
+ pmDesc desc;
+
+ if (sflag) {
+ int nbyte;
+ nbyte = do_size(resp);
+ printf("[%d bytes]\n", nbyte);
+ }
+
+ __pmPrintStamp(stdout, &resp->timestamp);
+
+ if (resp->numpmid == 0) {
+ printf(" <mark>\n");
+ return;
+ }
+
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+
+ if (i > 0)
+ printf(" ");
+ if ((n = pmNameID(vsp->pmid, &mname)) < 0)
+ mname = strdup("<noname>");
+ if (vsp->numval == 0) {
+ printf(" %s (%s): No values returned!\n", pmIDStr(vsp->pmid), mname);
+ goto next;
+ }
+ else if (vsp->numval < 0) {
+ printf(" %s (%s): %s\n", pmIDStr(vsp->pmid), mname, pmErrStr(vsp->numval));
+ goto next;
+ }
+
+ if (pmLookupDesc(vsp->pmid, &desc) < 0) {
+ /* don't know, so punt on the most common cases */
+ desc.indom = PM_INDOM_NULL;
+ if (vsp->valfmt == PM_VAL_INSITU)
+ desc.type = PM_TYPE_32;
+ else
+ desc.type = PM_TYPE_AGGREGATE;
+ }
+
+ for (j = 0; j < vsp->numval; j++) {
+ if (desc.type == PM_TYPE_EVENT ||
+ desc.type == PM_TYPE_HIGHRES_EVENT)
+ dump_event(mname, vsp, j, desc.indom, desc.type);
+ else
+ dump_metric(mname, vsp, j, desc.indom, desc.type);
+ }
+next:
+ if (mname)
+ free(mname);
+ }
+}
+
+static void
+dumpDesc(__pmContext *ctxp)
+{
+ int i;
+ int sts;
+ char *p;
+ __pmHashNode *hp;
+ pmDesc *dp;
+
+ printf("\nDescriptions for Metrics in the Log ...\n");
+ for (i = 0; i < ctxp->c_archctl->ac_log->l_hashpmid.hsize; i++) {
+ for (hp = ctxp->c_archctl->ac_log->l_hashpmid.hash[i]; hp != NULL; hp = hp->next) {
+ dp = (pmDesc *)hp->data;
+ sts = pmNameID(dp->pmid, &p);
+ if (sts < 0)
+ printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), "<noname>");
+ else {
+ printf("PMID: %s (%s)\n", pmIDStr(dp->pmid), p);
+ free(p);
+ }
+ __pmPrintDesc(stdout, dp);
+ }
+ }
+}
+
+static void
+dumpInDom(__pmContext *ctxp)
+{
+ int i;
+ int j;
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+ __pmLogInDom *ldp;
+
+ printf("\nInstance Domains in the Log ...\n");
+ for (i = 0; i < ctxp->c_archctl->ac_log->l_hashindom.hsize; i++) {
+ for (hp = ctxp->c_archctl->ac_log->l_hashindom.hash[i]; hp != NULL; hp = hp->next) {
+ printf("InDom: %s\n", pmInDomStr((pmInDom)hp->key));
+ /*
+ * in reverse chronological order, so iteration is a bit funny
+ */
+ ldp = NULL;
+ for ( ; ; ) {
+ for (idp = (__pmLogInDom *)hp->data; idp->next != ldp; idp =idp->next)
+ ;
+ tv.tv_sec = idp->stamp.tv_sec;
+ tv.tv_usec = idp->stamp.tv_usec;
+ __pmPrintStamp(stdout, &tv);
+ printf(" %d instances\n", idp->numinst);
+ for (j = 0; j < idp->numinst; j++) {
+ printf(" %d or \"%s\"\n",
+ idp->instlist[j], idp->namelist[j]);
+ }
+ if (idp == (__pmLogInDom *)hp->data)
+ break;
+ ldp = idp;
+ }
+ }
+ }
+}
+
+/*
+ * check the temporal index
+ */
+static void
+pass1(__pmContext *ctxp)
+{
+ int i;
+ char path[MAXPATHLEN];
+ off_t meta_size = -1; /* initialize to pander to gcc */
+ off_t log_size = -1; /* initialize to pander to gcc */
+ struct stat sbuf;
+ __pmLogTI *tip;
+ __pmLogTI *lastp;
+
+ if (vflag) printf("pass1: check temporal index\n");
+ lastp = NULL;
+ for (i = 1; i <= ctxp->c_archctl->ac_log->l_numti; i++) {
+ /*
+ * Integrity Checks
+ *
+ * this(tv_sec) < 0
+ * this(tv_usec) < 0 || this(tv_usec) > 999999
+ * this(timestamp) < last(timestamp)
+ * this(vol) >= 0
+ * this(vol) < last(vol)
+ * this(vol) == last(vol) && this(meta) <= last(meta)
+ * this(vol) == last(vol) && this(log) <= last(log)
+ * file_exists(<base>.meta) && this(meta) > file_size(<base>.meta)
+ * file_exists(<base>.this(vol)) &&
+ * this(log) > file_size(<base>.this(vol))
+ *
+ * Integrity Warnings
+ *
+ * this(vol) != last(vol) && !file_exists(<base>.this(vol))
+ */
+ tip = &ctxp->c_archctl->ac_log->l_ti[i-1];
+ tv.tv_sec = tip->ti_stamp.tv_sec;
+ tv.tv_usec = tip->ti_stamp.tv_usec;
+ if (i == 1) {
+ sprintf(path, "%s%c%s.meta", archdirname, sep, archbasename);
+ if (stat(path, &sbuf) == 0)
+ meta_size = sbuf.st_size;
+ else {
+ /* should not get here ... as detected in after pass0 */
+ fprintf(stderr, "%s: pass1: Botch: cannot open metadata file (%s)\n", pmProgname, path);
+ exit(1);
+ }
+ }
+ if (tip->ti_vol < 0) {
+ printf("%s.index[entry %d]: illegal volume number %d\n",
+ archbasename, i, tip->ti_vol);
+ index_state = STATE_BAD;
+ log_size = -1;
+ }
+ else if (lastp == NULL || tip->ti_vol != lastp->ti_vol) {
+ sprintf(path, "%s%c%s.%d", archdirname, sep, archbasename, tip->ti_vol);
+ if (stat(path, &sbuf) == 0)
+ log_size = sbuf.st_size;
+ else {
+ log_size = -1;
+ printf("%s: File missing or compressed for log volume %d\n", path, tip->ti_vol);
+ }
+ }
+ if (tip->ti_stamp.tv_sec < 0 ||
+ tip->ti_stamp.tv_usec < 0 || tip->ti_stamp.tv_usec > 999999) {
+ printf("%s.index[entry %d]: Illegal timestamp value (%d sec, %d usec)\n",
+ archbasename, i, tip->ti_stamp.tv_sec, tip->ti_stamp.tv_usec);
+ index_state = STATE_BAD;
+ }
+ if (tip->ti_meta < sizeof(__pmLogLabel)+2*sizeof(int)) {
+ printf("%s.index[entry %d]: offset to metadata (%ld) before end of label record (%ld)\n",
+ archbasename, i, (long)tip->ti_meta, (long)(sizeof(__pmLogLabel)+2*sizeof(int)));
+ index_state = STATE_BAD;
+ }
+ if (meta_size != -1 && tip->ti_meta > meta_size) {
+ printf("%s.index[entry %d]: offset to metadata (%ld) past end of file (%ld)\n",
+ archbasename, i, (long)tip->ti_meta, (long)meta_size);
+ index_state = STATE_BAD;
+ }
+ if (tip->ti_log < sizeof(__pmLogLabel)+2*sizeof(int)) {
+ printf("%s.index[entry %d]: offset to log (%ld) before end of label record (%ld)\n",
+ archbasename, i, (long)tip->ti_log, (long)(sizeof(__pmLogLabel)+2*sizeof(int)));
+ index_state = STATE_BAD;
+ }
+ if (log_size != -1 && tip->ti_log > log_size) {
+ printf("%s.index[entry %d]: offset to log (%ld) past end of file (%ld)\n",
+ archbasename, i, (long)tip->ti_log, (long)log_size);
+ index_state = STATE_BAD;
+ }
+ if (lastp != NULL) {
+ if (tip->ti_stamp.tv_sec < lastp->ti_stamp.tv_sec ||
+ (tip->ti_stamp.tv_sec == lastp->ti_stamp.tv_sec &&
+ tip->ti_stamp.tv_usec < lastp->ti_stamp.tv_usec)) {
+ printf("%s.index: timestamp went backwards in time %d.%06d[entry %d]-> %d.%06d[entry %d]\n",
+ archbasename, (int)lastp->ti_stamp.tv_sec, (int)lastp->ti_stamp.tv_usec, i-1,
+ (int)tip->ti_stamp.tv_sec, (int)tip->ti_stamp.tv_usec, i);
+ index_state = STATE_BAD;
+ }
+ if (tip->ti_vol < lastp->ti_vol) {
+ printf("%s.index: volume number decreased %d[entry %d] -> %d[entry %d]\n",
+ archbasename, lastp->ti_vol, i-1, tip->ti_vol, i);
+ index_state = STATE_BAD;
+ }
+ if (tip->ti_vol == lastp->ti_vol && tip->ti_meta < lastp->ti_meta) {
+ printf("%s.index: offset to metadata decreased %ld[entry %d] -> %ld[entry %d]\n",
+ archbasename, (long)lastp->ti_meta, i-1, (long)tip->ti_meta, i);
+ index_state = STATE_BAD;
+ }
+ if (tip->ti_vol == lastp->ti_vol && tip->ti_log < lastp->ti_log) {
+ printf("%s.index: offset to log decreased %ld[entry %d] -> %ld[entry %d]\n",
+ archbasename, (long)lastp->ti_log, i-1, (long)tip->ti_log, i);
+ index_state = STATE_BAD;
+ }
+ }
+ lastp = tip;
+ }
+}
+
+static int
+filter(const_dirent *dp)
+{
+ static int len = -1;
+
+ if (len == -1)
+ len = strlen(archbasename);
+ if (strncmp(dp->d_name, archbasename, len) != 0)
+ return 0;
+ if (strcmp(&dp->d_name[len], ".meta") == 0)
+ return 1;
+ if (strcmp(&dp->d_name[len], ".index") == 0)
+ return 1;
+ if (dp->d_name[len] == '.' && isdigit(dp->d_name[len+1])) {
+ const char *p = &dp->d_name[len+2];
+ for ( ; *p; p++) {
+ if (!isdigit(*p))
+ return 0;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+#define IS_UNKNOWN 0
+#define IS_INDEX 1
+#define IS_META 2
+#define IS_LOG 3
+/*
+ * Pass 1 for all files
+ * - should only come here if fname exists
+ * - check that file contains a number of complete records
+ * - the label record and the records for the data and metadata
+ * have this format:
+ * :----------:----------------------:---------:
+ * | int len | stuff | int len |
+ * | header | stuff | trailer |
+ * :----------:----------------------:---------:
+ * and the len fields are in network byte order.
+ * For these records, check that the header length is equal to
+ * the trailer length
+ * - for index files, following the label record there should be
+ * a number of complete records, each of which is a __pmLogTI
+ * record, with the fields converted network byte order
+ *
+ * TODO - repair
+ * - truncate metadata and data files ... unconditional or interactive confirm?
+ * - mark index as bad and needing rebuild
+ * - move access check into here (if cannot open file for reading we're screwed)
+ */
+static int
+pass0(char *fname)
+{
+ int len;
+ int check;
+ int i;
+ int sts;
+ int nrec = 0;
+ int is = IS_UNKNOWN;
+ char *p;
+ FILE *f;
+
+ if (vflag)
+ printf("pass0: %s:\n", fname);
+ if ((f = fopen(fname, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open \"%s\": %s\n", pmProgname, fname, osstrerror());
+ sts = STS_FATAL;
+ goto done;
+ }
+ p = strrchr(fname, '.');
+ if (p != NULL) {
+ if (strcmp(p, ".index") == 0)
+ is = IS_INDEX;
+ else if (strcmp(p, ".meta") == 0)
+ is = IS_META;
+ else if (isdigit(*++p)) {
+ p++;
+ while (*p && isdigit(*p))
+ p++;
+ if (*p == '\0')
+ is = IS_LOG;
+ }
+ }
+ if (is == IS_UNKNOWN) {
+ /*
+ * should never get here because filter() is supposed to
+ * only include PCP archive file names from scandir()
+ */
+ fprintf(stderr, "%s: pass0: Botch: bad file name? %s\n", pmProgname, fname);
+ exit(1);
+ }
+
+ while ((sts = fread(&len, 1, sizeof(len), f)) == sizeof(len)) {
+ len = ntohl(len);
+ len -= 2 * sizeof(len);
+ /* gobble stuff between header and trailer without looking at it */
+ for (i = 0; i < len; i++) {
+ check = fgetc(f);
+ if (check == EOF) {
+ if (nrec == 0)
+ printf("%s: unexpected EOF in label record body\n", fname);
+ else
+ printf("%s[record %d]: unexpected EOF in record body\n", fname, nrec);
+ sts = STS_FATAL;
+ goto done;
+ }
+ }
+ if ((sts = fread(&check, 1, sizeof(check), f)) != sizeof(check)) {
+ if (nrec == 0)
+ printf("%s: unexpected EOF in label record trailer\n", fname);
+ else
+ printf("%s[record %d]: unexpected EOF in record trailer\n", fname, nrec);
+ sts = STS_FATAL;
+ goto done;
+ }
+ check = ntohl(check);
+ len += 2 * sizeof(len);
+ if (check != len) {
+ if (nrec == 0)
+ printf("%s: label record length mismatch: header %d != trailer %d\n", fname, len, check);
+ else
+ printf("%s[record %d]: length mismatch: header %d != trailer %d\n", fname, nrec, len, check);
+ sts = STS_FATAL;
+ goto done;
+ }
+ nrec++;
+ if (is == IS_INDEX) {
+ /* for index files, done label record, now eat index records */
+ __pmLogTI tirec;
+ while ((sts = fread(&tirec, 1, sizeof(tirec), f)) == sizeof(tirec)) {
+ nrec++;
+ }
+ if (sts != 0) {
+ printf("%s[record %d]: unexpected EOF in index entry\n", fname, nrec);
+ index_state = STATE_BAD;
+ sts = STS_FATAL;
+ goto done;
+ }
+ goto empty_check;
+ }
+ }
+ if (sts != 0) {
+ printf("%s[record %d]: unexpected EOF in record header\n", fname, nrec);
+ sts = STS_FATAL;
+ }
+empty_check:
+ if (sts != STS_FATAL && nrec < 2) {
+ printf("%s: contains no PCP data\n", fname);
+ sts = STS_WARNING;
+ }
+ /*
+ * sts == 0 (from fread) => STS_OK
+ */
+done:
+ if (is == IS_INDEX) {
+ if (sts == STS_OK)
+ index_state = STATE_OK;
+ else
+ index_state = STATE_BAD;
+ }
+ else if (is == IS_META) {
+ if (sts == STS_OK)
+ meta_state = STATE_OK;
+ else
+ meta_state = STATE_BAD;
+ }
+ else {
+ if (log_state == STATE_OK && sts != STS_OK)
+ log_state = STATE_BAD;
+ else if (log_state == STATE_MISSING) {
+ if (sts == STS_OK)
+ log_state = STATE_OK;
+ else
+ log_state = STATE_BAD;
+ }
+ }
+
+ return sts;
+}
+
+static void
+dometric(const char *name)
+{
+ int sts;
+
+ if (*name == '\0') {
+ printf("PMNS appears to be empty!\n");
+ return;
+ }
+ numpmid++;
+ pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
+ if ((sts = pmLookupName(1, (char **)&name, &pmid[numpmid-1])) < 0) {
+ fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, name, pmErrStr(sts));
+ numpmid--;
+ }
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "all", 0, 'a', 0, "dump everything" },
+ { "descs", 0, 'd', 0, "dump metric descriptions" },
+ { "indoms", 0, 'i', 0, "dump instance domain descriptions" },
+ { "", 0, 'L', 0, "more verbose form of label dump" },
+ { "label", 0, 'l', 0, "dump the archive label" },
+ { "", 0, 'm', 0, "dump values of the metrics (default)" },
+ { "reverse", 0, 'r', 0, "process archive in reverse chronological order" },
+ { "size", 0, 's', 0, "report size of data records in archive" },
+ { "", 0, 't', 0, "dump the temporal index" },
+ { "verbose", 0, 'v', 0, "verbose output" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "aD:dilLmst:vZ:z?",
+ .long_options = longopts,
+ .short_usage = "[options] archive [metricname ...]",
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ int i;
+ int n;
+ int dflag = 0;
+ int iflag = 0;
+ int Lflag = 0;
+ int lflag = 0;
+ int mflag = 0;
+ int tflag = 0;
+ int mode = PM_MODE_FORW;
+ char *p;
+ __pmContext *ctxp;
+ pmResult *result;
+ struct timeval endTime = { INT_MAX, 0 };
+ struct timeval appStart;
+ struct timeval appEnd;
+ pmLogLabel label; /* get hostname for archives */
+ char timebuf[32]; /* for pmCtime result + .xxx */
+ struct timeval done;
+ struct dirent **namelist;
+ char *tz = NULL; /* for -Z timezone */
+ int zflag = 0; /* for -z */
+ int nfile;
+
+ sep = __pmPathSeparator();
+ setlinebuf(stdout);
+ setlinebuf(stderr);
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'a': /* dump everything */
+ dflag = iflag = lflag = mflag = sflag = tflag = 1;
+ break;
+
+ case 'd': /* dump pmDesc structures */
+ dflag = 1;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'i': /* dump instance domains */
+ iflag = 1;
+ break;
+
+ case 'L': /* dump label, verbose */
+ Lflag = 1;
+ lflag = 1;
+ break;
+
+ case 'l': /* dump label */
+ lflag = 1;
+ break;
+
+ case 'm': /* dump metrics in log */
+ mflag = 1;
+ break;
+
+ case 's': /* report data size in log */
+ sflag = 1;
+ break;
+
+ case 'v': /* verbose */
+ vflag = 1;
+ break;
+
+ case 'z': /* timezone from host */
+ if (tz != NULL) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n", pmProgname);
+ opts.errors++;
+ }
+ zflag++;
+ break;
+
+ case 'Z': /* $TZ timezone */
+ if (zflag) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n", pmProgname);
+ opts.errors++;
+ }
+ tz = opts.optarg;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind > argc - 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ archpathname = argv[opts.optind];
+ archbasename = strdup(basename(strdup(archpathname)));
+ /*
+ * treat foo, foo.index, foo.meta, foo.NNN as all equivalent
+ * to "foo"
+ */
+ p = strrchr(archbasename, '.');
+ if (p != NULL) {
+ if (strcmp(p, ".index") == 0 || strcmp(p, ".meta") == 0)
+ *p = '\0';
+ else {
+ char *q = ++p;
+ if (isdigit(*q)) {
+ q++;
+ while (*q && isdigit(*q))
+ q++;
+ if (*q == '\0')
+ *p = '\0';
+ }
+ }
+ }
+ archdirname = dirname(strdup(archpathname));
+ if (vflag)
+ printf("Scanning for components of archive \"%s\"\n", archpathname);
+ nfile = scandir(archdirname, &namelist, filter, NULL);
+ if (nfile < 1) {
+ fprintf(stderr, "%s: No PCP archive files match \"%s\"\n", pmProgname, archpathname);
+ exit(1);
+ }
+ /*
+ * Pass 0 for data and metadata files
+ */
+ sts = STS_OK;
+ for (i = 0; i < nfile; i++) {
+ char path[MAXPATHLEN];
+ snprintf(path, sizeof(path), "%s%c%s", archdirname, sep, namelist[i]->d_name);
+ if (pass0(path) == STS_FATAL)
+ /* unrepairable or unrepaired error */
+ sts = STS_FATAL;
+ }
+ if (meta_state == STATE_MISSING) {
+ fprintf(stderr, "%s: Missing metadata file (%s%c%s.meta)\n", pmProgname, archdirname, sep, archbasename);
+ sts = STS_FATAL;
+ }
+ if (log_state == STATE_MISSING) {
+ fprintf(stderr, "%s: Missing log file (%s%c%s.0 or similar)\n", pmProgname, archdirname, sep, archbasename);
+ sts = STS_FATAL;
+ }
+
+ if (sts == STS_FATAL) {
+ if (vflag) printf("Cannot continue ... bye\n");
+ exit(1);
+ }
+
+ if ((sts = pmNewContext(PM_CONTEXT_ARCHIVE, archpathname)) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n", pmProgname, archpathname, pmErrStr(sts));
+ fprintf(stderr, "Checking abandoned.\n");
+ exit(1);
+ }
+ if ((n = pmWhichContext()) >= 0) {
+ if ((ctxp = __pmHandleToPtr(n)) == NULL) {
+ fprintf(stderr, "%s: Botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, n);
+ exit(1);
+ }
+ }
+ else {
+ fprintf(stderr, "%s: Botch: %s!\n", pmProgname, pmErrStr(PM_ERR_NOCONTEXT));
+ exit(1);
+ }
+ /*
+ * Note: ctxp->c_lock remains locked throughout ... __pmHandleToPtr()
+ * is only called once, and a single context is used throughout
+ * ... so there is no PM_UNLOCK(ctxp->c_lock) anywhere in the
+ * pmchecklog code.
+ * This works because ctxp->c_lock is a recursive lock and
+ * pmchecklog is single-threaded.
+ */
+
+ pass1(ctxp);
+ exit(1);
+
+ numpmid = argc - opts.optind;
+ if (numpmid) {
+ numpmid = 0;
+ pmid = NULL;
+ for (i = 0 ; opts.optind < argc; i++, opts.optind++) {
+ numpmid++;
+ pmid = (pmID *)realloc(pmid, numpmid * sizeof(pmID));
+ if ((sts = pmLookupName(1, &argv[opts.optind], &pmid[numpmid-1])) < 0) {
+ if (sts == PM_ERR_NONLEAF) {
+ numpmid--;
+ if ((sts = pmTraversePMNS(argv[opts.optind], dometric)) < 0)
+ fprintf(stderr, "%s: pmTraversePMNS(%s): %s\n", pmProgname, argv[opts.optind], pmErrStr(sts));
+ }
+ else
+ fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, argv[opts.optind], pmErrStr(sts));
+ if (sts < 0)
+ numpmid--;
+ }
+ }
+ if (numpmid == 0) {
+ fprintf(stderr, "No metric names can be translated, dump abandoned\n");
+ exit(1);
+ }
+ }
+
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: Cannot get archive label record: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmGetArchiveEnd(&endTime)) < 0) {
+ endTime.tv_sec = INT_MAX;
+ endTime.tv_usec = 0;
+ fflush(stdout);
+ fprintf(stderr, "%s: Cannot locate end of archive: %s\n",
+ pmProgname, pmErrStr(sts));
+ fprintf(stderr, "\nWARNING: This archive is sufficiently damaged that it may not be possible to\n");
+ fprintf(stderr, " produce complete information. Continuing and hoping for the best.\n\n");
+ fflush(stderr);
+ }
+
+ if (zflag) {
+ if ((sts = pmNewContextZone()) < 0) {
+ fprintf(stderr, "%s: Cannot set context timezone: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ printf("Note: timezone set to local timezone of host \"%s\" from archive\n\n",
+ label.ll_hostname);
+ }
+ else if (tz != NULL) {
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, tz, pmErrStr(sts));
+ exit(1);
+ }
+ printf("Note: timezone set to \"TZ=%s\"\n\n", tz);
+ }
+
+ if (sts < 0) {
+ fprintf(stderr, "%s:\n%s\n", pmProgname, p);
+ exit(1);
+ }
+
+ if (lflag) {
+ char *ddmm;
+ char *yr;
+
+ printf("Log Label (Log Format Version %d)\n", label.ll_magic & 0xff);
+ printf("Performance metrics from host %s\n", label.ll_hostname);
+
+ ddmm = pmCtime(&label.ll_start.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" commencing %s ", ddmm);
+ __pmPrintStamp(stdout, &label.ll_start);
+ printf(" %4.4s\n", yr);
+
+ if (endTime.tv_sec == INT_MAX) {
+ /* pmGetArchiveEnd() failed! */
+ printf(" ending UNKNOWN\n");
+ }
+ else {
+ ddmm = pmCtime(&endTime.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" ending %s ", ddmm);
+ __pmPrintStamp(stdout, &endTime);
+ printf(" %4.4s\n", yr);
+ }
+
+ if (Lflag) {
+ printf("Archive timezone: %s\n", label.ll_tz);
+ printf("PID for pmlogger: %" FMT_PID "\n", label.ll_pid);
+ }
+ }
+
+ if (dflag)
+ dumpDesc(ctxp);
+
+ if (iflag)
+ dumpInDom(ctxp);
+
+
+ if (mflag) {
+ if (mode == PM_MODE_FORW) {
+ sts = pmSetMode(mode, &appStart, 0);
+ done = appEnd;
+ }
+ else {
+ appEnd.tv_sec = INT_MAX;
+ sts = pmSetMode(mode, &appEnd, 0);
+ done = appStart;
+ }
+ if (sts < 0) {
+ fprintf(stderr, "%s: pmSetMode: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ sts = 0;
+ for ( ; ; ) {
+ sts = pmFetchArchive(&result);
+ if (sts < 0)
+ break;
+ if ((mode == PM_MODE_FORW && tvcmp(result->timestamp, done) > 0) ||
+ (mode == PM_MODE_BACK && tvcmp(result->timestamp, done) < 0)) {
+ sts = PM_ERR_EOL;
+ break;
+ }
+ putchar('\n');
+ dumpResult(result);
+ pmFreeResult(result);
+ }
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ exit(0);
+}
diff --git a/src/pmlogconf/GNUmakefile b/src/pmlogconf/GNUmakefile
new file mode 100644
index 0000000..6f8aafd
--- /dev/null
+++ b/src/pmlogconf/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = v1.0 platform disk sgi memory cpu kernel filesystem networking \
+ sqlserver tools
+
+LSRCFILES = pmlogconf.sh pmlogconf-setup.sh GNUmakefile.groups
+
+default: makefiles pmlogconf.sh pmlogconf-setup.sh
+
+include $(BUILDRULES)
+
+install: default $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 pmlogconf.sh $(PCP_BINADM_DIR)/pmlogconf
+ $(INSTALL) -m 755 pmlogconf-setup.sh $(PCP_BINADM_DIR)/pmlogconf-setup
+
+default_pcp : default
+
+install_pcp : install
+
+# for src-link-pcp target from buildrules
+$(SUBDIRS): makefiles
+
+makefiles:
+ @for d in $(SUBDIRS); do \
+ rm -f $$d/GNUmakefile; \
+ cp GNUmakefile.groups $$d/GNUmakefile; \
+ done
+
+groups: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
diff --git a/src/pmlogconf/GNUmakefile.groups b/src/pmlogconf/GNUmakefile.groups
new file mode 100644
index 0000000..5930fae
--- /dev/null
+++ b/src/pmlogconf/GNUmakefile.groups
@@ -0,0 +1,38 @@
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+include localdefs
+
+WORKDIR := $(shell pwd)
+GROUP := $(shell basename $(WORKDIR))
+GROUPDIR = $(PCP_VAR_DIR)/config/pmlogconf
+
+LDIRT = GNUmakefile
+
+DEFAULT = $(FILES)
+
+LSRCFILES = localdefs $(DEFAULT)
+
+CONFIGS = $(subst "./","",$(DEFAULT))
+
+default_pcp: $(DEFAULT)
+
+install_pcp: install
+
+install: default_pcp
+ $(INSTALL) -d $(GROUPDIR)/$(GROUP)
+ @for f in $(CONFIGS); do \
+ $(INSTALL) -m 644 $$f $(GROUPDIR)/$(GROUP)/$$f; \
+ done
+
+include $(BUILDRULES)
diff --git a/src/pmlogconf/cpu/localdefs b/src/pmlogconf/cpu/localdefs
new file mode 100644
index 0000000..1245766
--- /dev/null
+++ b/src/pmlogconf/cpu/localdefs
@@ -0,0 +1 @@
+FILES = summary percpu
diff --git a/src/pmlogconf/cpu/percpu b/src/pmlogconf/cpu/percpu
new file mode 100644
index 0000000..c5ef876
--- /dev/null
+++ b/src/pmlogconf/cpu/percpu
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident utilization per CPU
+force available
+ kernel.percpu.cpu
diff --git a/src/pmlogconf/cpu/summary b/src/pmlogconf/cpu/summary
new file mode 100644
index 0000000..84ff418
--- /dev/null
+++ b/src/pmlogconf/cpu/summary
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident utilization (usr, sys, idle, ...) over all CPUs
+force include
+ kernel.all.cpu
diff --git a/src/pmlogconf/disk/localdefs b/src/pmlogconf/disk/localdefs
new file mode 100644
index 0000000..bfa6c36
--- /dev/null
+++ b/src/pmlogconf/disk/localdefs
@@ -0,0 +1 @@
+FILES = summary percontroller perdisk perpartition
diff --git a/src/pmlogconf/disk/percontroller b/src/pmlogconf/disk/percontroller
new file mode 100644
index 0000000..774d825
--- /dev/null
+++ b/src/pmlogconf/disk/percontroller
@@ -0,0 +1,9 @@
+#pmlogconf-setup 2.0
+ident per controller disk activity
+probe disk.ctl.total
+ disk.ctl.read
+ disk.ctl.write
+ disk.ctl.total
+ disk.ctl.read_bytes
+ disk.ctl.write_bytes
+ disk.ctl.bytes
diff --git a/src/pmlogconf/disk/perdisk b/src/pmlogconf/disk/perdisk
new file mode 100644
index 0000000..b3a73ca
--- /dev/null
+++ b/src/pmlogconf/disk/perdisk
@@ -0,0 +1,10 @@
+#pmlogconf-setup 2.0
+ident per spindle disk activity
+force available
+ disk.dev.read
+ disk.dev.write
+ disk.dev.total
+ disk.dev.read_bytes
+ disk.dev.write_bytes
+ disk.dev.total_bytes
+ disk.dev.avactive
diff --git a/src/pmlogconf/disk/perpartition b/src/pmlogconf/disk/perpartition
new file mode 100644
index 0000000..f65c736
--- /dev/null
+++ b/src/pmlogconf/disk/perpartition
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident per logical block device activity
+force available
+ disk.partitions.read
+ disk.partitions.write
+ disk.partitions.read_bytes
+ disk.partitions.write_bytes
diff --git a/src/pmlogconf/disk/summary b/src/pmlogconf/disk/summary
new file mode 100644
index 0000000..32f08fd
--- /dev/null
+++ b/src/pmlogconf/disk/summary
@@ -0,0 +1,10 @@
+#pmlogconf-setup 2.0
+ident summary disk activity (IOPs and bytes for both reads and writes over all disks).
+force include
+ disk.all.read
+ disk.all.write
+ disk.all.total
+ disk.all.read_bytes
+ disk.all.write_bytes
+ disk.all.total_bytes
+ disk.all.avactive
diff --git a/src/pmlogconf/filesystem/all b/src/pmlogconf/filesystem/all
new file mode 100644
index 0000000..84424e1
--- /dev/null
+++ b/src/pmlogconf/filesystem/all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident generic filesystem size, fullness and mount information
+force available
+ filesys
diff --git a/src/pmlogconf/filesystem/localdefs b/src/pmlogconf/filesystem/localdefs
new file mode 100644
index 0000000..47766a8
--- /dev/null
+++ b/src/pmlogconf/filesystem/localdefs
@@ -0,0 +1 @@
+FILES = all xfs-io-irix xfs-io-linux xfs-all summary
diff --git a/src/pmlogconf/filesystem/summary b/src/pmlogconf/filesystem/summary
new file mode 100644
index 0000000..d08b2d1
--- /dev/null
+++ b/src/pmlogconf/filesystem/summary
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident filesystem size and fullness
+force available
+ filesys.full
+ filesys.used
+ filesys.free
+ filesys.avail # for Linux
+
diff --git a/src/pmlogconf/filesystem/xfs-all b/src/pmlogconf/filesystem/xfs-all
new file mode 100644
index 0000000..22e2136
--- /dev/null
+++ b/src/pmlogconf/filesystem/xfs-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available XFS information
+probe xfs.read_bytes
+ xfs
diff --git a/src/pmlogconf/filesystem/xfs-io-irix b/src/pmlogconf/filesystem/xfs-io-irix
new file mode 100644
index 0000000..ded61ed
--- /dev/null
+++ b/src/pmlogconf/filesystem/xfs-io-irix
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident XFS data and log I/O traffic [Irix]
+probe xfs.log_writes exists ? include : exclude
+ xfs.log_writes
+ xfs.log_blocks
+ xfs.log_noiclogs
+ xfs.read_bytes
+ xfs.write_bytes
diff --git a/src/pmlogconf/filesystem/xfs-io-linux b/src/pmlogconf/filesystem/xfs-io-linux
new file mode 100644
index 0000000..d7c0fe9
--- /dev/null
+++ b/src/pmlogconf/filesystem/xfs-io-linux
@@ -0,0 +1,11 @@
+#pmlogconf-setup 2.0
+ident XFS data and log I/O traffic [Linux]
+probe xfs.log.writes
+ xfs.log.writes
+ xfs.log.blocks
+ xfs.log.noiclogs
+ xfs.read
+ xfs.write
+ xfs.read_bytes
+ xfs.write_bytes
+ xfs.buffer
diff --git a/src/pmlogconf/kernel/bufcache-activity b/src/pmlogconf/kernel/bufcache-activity
new file mode 100644
index 0000000..89e194e
--- /dev/null
+++ b/src/pmlogconf/kernel/bufcache-activity
@@ -0,0 +1,10 @@
+#pmlogconf-setup 2.0
+ident kernel buffer cache reads, writes, hits and misses
+probe kernel.all.io.bread
+ kernel.all.io.bread
+ kernel.all.io.bwrite
+ kernel.all.io.lread
+ kernel.all.io.lwrite
+ kernel.all.io.phread
+ kernel.all.io.phwrite
+ kernel.all.io.wcancel
diff --git a/src/pmlogconf/kernel/bufcache-all b/src/pmlogconf/kernel/bufcache-all
new file mode 100644
index 0000000..07d2c55
--- /dev/null
+++ b/src/pmlogconf/kernel/bufcache-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available kernel buffer cache data
+probe buffer_cache.flush
+ buffer_cache
diff --git a/src/pmlogconf/kernel/inode-cache b/src/pmlogconf/kernel/inode-cache
new file mode 100644
index 0000000..70f0118
--- /dev/null
+++ b/src/pmlogconf/kernel/inode-cache
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident kernel name cache (namei, iget, etc) activity
+probe kernel.all.io.namei
+ kernel.all.io.iget
+ kernel.all.io.namei
+ kernel.all.io.dirblk
+ name_cache
diff --git a/src/pmlogconf/kernel/interrupts-irix b/src/pmlogconf/kernel/interrupts-irix
new file mode 100644
index 0000000..bc10187
--- /dev/null
+++ b/src/pmlogconf/kernel/interrupts-irix
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident interrupts [Irix]
+probe kernel.all.intr.vme
+ kernel.all.intr.vme
+ kernel.all.intr.non_vme
+ kernel.all.tty.recvintr
+ kernel.all.tty.xmitintr
+ kernel.all.tty.mdmintr
diff --git a/src/pmlogconf/kernel/load b/src/pmlogconf/kernel/load
new file mode 100644
index 0000000..c75b97b
--- /dev/null
+++ b/src/pmlogconf/kernel/load
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident load average
+probe kernel.all.load
+ kernel.all.load
diff --git a/src/pmlogconf/kernel/localdefs b/src/pmlogconf/kernel/localdefs
new file mode 100644
index 0000000..31f666d
--- /dev/null
+++ b/src/pmlogconf/kernel/localdefs
@@ -0,0 +1 @@
+FILES = load syscalls-irix syscalls-linux bufcache-activity bufcache-all vnodes memory-irix memory-linux inode-cache syscalls-percpu-irix queues-irix read-write-data interrupts-irix summary-windows summary-linux
diff --git a/src/pmlogconf/kernel/memory-irix b/src/pmlogconf/kernel/memory-irix
new file mode 100644
index 0000000..3694c83
--- /dev/null
+++ b/src/pmlogconf/kernel/memory-irix
@@ -0,0 +1,31 @@
+#pmlogconf-setup 2.0
+ident kernel memory allocation [Irix]
+probe mem.chunkpages
+ mem.system
+ mem.util
+ mem.freemem
+ mem.availsmem
+ mem.availrmem
+ mem.bufmem
+ mem.physmem
+ mem.dchunkpages
+ mem.pmapmem
+ mem.strmem
+ mem.chunkpages
+ mem.dpages
+ mem.emptymem
+ mem.freeswap
+ mem.halloc
+ mem.heapmem
+ mem.hfree
+ mem.hovhd
+ mem.hunused
+ mem.zfree
+ mem.zonemem
+ mem.zreq
+ mem.iclean
+ mem.bsdnet
+ mem.palloc
+ mem.unmodfl
+ mem.unmodsw
+ mem.paging.reclaim
diff --git a/src/pmlogconf/kernel/memory-linux b/src/pmlogconf/kernel/memory-linux
new file mode 100644
index 0000000..c8ec0ac
--- /dev/null
+++ b/src/pmlogconf/kernel/memory-linux
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident kernel memory allocation [Linux]
+probe mem.util.used
+ mem.util
diff --git a/src/pmlogconf/kernel/queues-irix b/src/pmlogconf/kernel/queues-irix
new file mode 100644
index 0000000..10dd4c3
--- /dev/null
+++ b/src/pmlogconf/kernel/queues-irix
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident run and swap queues [Irix]
+probe kernel.all.runque
+ kernel.all.runque
+ kernel.all.runocc
+ kernel.all.swap.swpque
+ kernel.all.swap.swpocc
diff --git a/src/pmlogconf/kernel/read-write-data b/src/pmlogconf/kernel/read-write-data
new file mode 100644
index 0000000..f333787
--- /dev/null
+++ b/src/pmlogconf/kernel/read-write-data
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident bytes across the read() and write() syscall interfaces
+probe kernel.all.readch
+ kernel.all.readch
+ kernel.all.writech
diff --git a/src/pmlogconf/kernel/summary-linux b/src/pmlogconf/kernel/summary-linux
new file mode 100644
index 0000000..215e6fa
--- /dev/null
+++ b/src/pmlogconf/kernel/summary-linux
@@ -0,0 +1,17 @@
+#pmlogconf-setup 2.0
+ident summary kernel performance data [Linux]
+probe kernel.uname.sysname ~ Linux ? include : exclude
+ mem.util
+ swap.pagesin
+ swap.pagesout
+ swap.free
+ swap.used
+ kernel.all.pswitch
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.runnable
+ network.interface.collisions
+ network.interface.in.drops
+ network.interface.out.drops
+ disk.dev.avactive
+ filesys.full
diff --git a/src/pmlogconf/kernel/summary-windows b/src/pmlogconf/kernel/summary-windows
new file mode 100644
index 0000000..6553192
--- /dev/null
+++ b/src/pmlogconf/kernel/summary-windows
@@ -0,0 +1,15 @@
+#pmlogconf-setup 2.0
+ident summary kernel performance data [Windows]
+probe mem.pool.paged_bytes exists ? include : exclude
+ mem.available
+ mem.committed_bytes
+ mem.pool.paged_bytes
+ mem.pool.non_paged_bytes
+ mem.page_faults
+ mem.page_reads
+ mem.page_writes
+ mem.pages_total
+ disk.dev.idle
+ disk.dev.queue_len
+ filesys.full
+
diff --git a/src/pmlogconf/kernel/syscalls-irix b/src/pmlogconf/kernel/syscalls-irix
new file mode 100644
index 0000000..9a04cdf
--- /dev/null
+++ b/src/pmlogconf/kernel/syscalls-irix
@@ -0,0 +1,12 @@
+#pmlogconf-setup 2.0
+ident context switches, total syscalls and counts for selected calls (e.g. read, write, fork, exec, select) over all CPUs [Irix]
+probe kernel.all.syscall values ? available : exclude
+ kernel.all.pswitch
+ kernel.all.syscall
+ kernel.all.sysexec
+ kernel.all.sysfork
+ kernel.all.sysread
+ kernel.all.syswrite
+ kernel.all.kswitch
+ kernel.all.kpreempt
+ kernel.all.sysioctl
diff --git a/src/pmlogconf/kernel/syscalls-linux b/src/pmlogconf/kernel/syscalls-linux
new file mode 100644
index 0000000..8e50db5
--- /dev/null
+++ b/src/pmlogconf/kernel/syscalls-linux
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident context switches and fork system calls over all CPUs [Linux]
+probe kernel.all.sysfork values ? available : exclude
+ kernel.all.pswitch
+ kernel.all.sysfork
diff --git a/src/pmlogconf/kernel/syscalls-percpu-irix b/src/pmlogconf/kernel/syscalls-percpu-irix
new file mode 100644
index 0000000..30a106f
--- /dev/null
+++ b/src/pmlogconf/kernel/syscalls-percpu-irix
@@ -0,0 +1,12 @@
+#pmlogconf-setup 2.0
+ident per CPU context switches, total syscalls and counts for selected calls [Irix]
+probe kernel.percpu.pswitch
+ kernel.percpu.pswitch
+ kernel.percpu.syscall
+ kernel.percpu.sysexec
+ kernel.percpu.sysfork
+ kernel.percpu.sysread
+ kernel.percpu.syswrite
+ kernel.percpu.kswitch
+ kernel.percpu.kpreempt
+ kernel.percpu.sysioctl
diff --git a/src/pmlogconf/kernel/vnodes b/src/pmlogconf/kernel/vnodes
new file mode 100644
index 0000000..ab1a077
--- /dev/null
+++ b/src/pmlogconf/kernel/vnodes
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident vnode activity
+probe vnodes.alloc
+ vnodes
diff --git a/src/pmlogconf/memory/localdefs b/src/pmlogconf/memory/localdefs
new file mode 100644
index 0000000..f2e82f1
--- /dev/null
+++ b/src/pmlogconf/memory/localdefs
@@ -0,0 +1 @@
+FILES = tlb-irix swap-all swap-config swap-activity proc-linux
diff --git a/src/pmlogconf/memory/proc-linux b/src/pmlogconf/memory/proc-linux
new file mode 100644
index 0000000..399ab40
--- /dev/null
+++ b/src/pmlogconf/memory/proc-linux
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident System process memory-usage information
+delta 5 minutes
+#probe proc.memory.size exists ? include : exclude
+force available
+ proc.memory.size
+ proc.memory.rss
+ proc.psinfo.maj_flt
diff --git a/src/pmlogconf/memory/swap-activity b/src/pmlogconf/memory/swap-activity
new file mode 100644
index 0000000..8affe94
--- /dev/null
+++ b/src/pmlogconf/memory/swap-activity
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident pages in and out (severe VM demand)
+force available
+ swap.pagesin
+ swap.pagesout
diff --git a/src/pmlogconf/memory/swap-all b/src/pmlogconf/memory/swap-all
new file mode 100644
index 0000000..68cda81
--- /dev/null
+++ b/src/pmlogconf/memory/swap-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident current swap allocation and all swap activity
+force available
+ swap
diff --git a/src/pmlogconf/memory/swap-config b/src/pmlogconf/memory/swap-config
new file mode 100644
index 0000000..ad7e0da
--- /dev/null
+++ b/src/pmlogconf/memory/swap-config
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident swap configuration
+force available
+ swapdev
diff --git a/src/pmlogconf/memory/tlb-irix b/src/pmlogconf/memory/tlb-irix
new file mode 100644
index 0000000..1688d7d
--- /dev/null
+++ b/src/pmlogconf/memory/tlb-irix
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident address translation (faults and TLB activity) [Irix version]
+probe mem.tlb.flush
+ mem.tlb
diff --git a/src/pmlogconf/networking/interface-all b/src/pmlogconf/networking/interface-all
new file mode 100644
index 0000000..82a6c91
--- /dev/null
+++ b/src/pmlogconf/networking/interface-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available data per network interface
+force available
+ network.interface
diff --git a/src/pmlogconf/networking/interface-summary b/src/pmlogconf/networking/interface-summary
new file mode 100644
index 0000000..41be875
--- /dev/null
+++ b/src/pmlogconf/networking/interface-summary
@@ -0,0 +1,10 @@
+#pmlogconf-setup 2.0
+ident bytes, packets and errors (in and out) per network interface
+force include
+ network.interface.in.bytes
+ network.interface.in.packets
+ network.interface.in.errors
+ network.interface.out.bytes
+ network.interface.out.packets
+ network.interface.out.errors
+ network.interface.collisions
diff --git a/src/pmlogconf/networking/localdefs b/src/pmlogconf/networking/localdefs
new file mode 100644
index 0000000..5b34550
--- /dev/null
+++ b/src/pmlogconf/networking/localdefs
@@ -0,0 +1 @@
+FILES = nfs2-client nfs2-server nfs3-client nfs3-server nfs4-client nfs4-server rpc interface-summary interface-all socket-irix socket-linux mbufs multicast streams tcp-all udp-all tcp-activity-irix tcp-activity-linux udp-packets-irix udp-packets-linux other-protocols
diff --git a/src/pmlogconf/networking/mbufs b/src/pmlogconf/networking/mbufs
new file mode 100644
index 0000000..3fc1743
--- /dev/null
+++ b/src/pmlogconf/networking/mbufs
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident mbuf stats (alloc, failed, waited, etc)
+probe network.mbuf.alloc
+ network.mbuf
diff --git a/src/pmlogconf/networking/multicast b/src/pmlogconf/networking/multicast
new file mode 100644
index 0000000..805bd85
--- /dev/null
+++ b/src/pmlogconf/networking/multicast
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident multicast routing stats
+probe network.mcr.mfc_lookups
+ network.mcr
diff --git a/src/pmlogconf/networking/nfs2-client b/src/pmlogconf/networking/nfs2-client
new file mode 100644
index 0000000..39d2a66
--- /dev/null
+++ b/src/pmlogconf/networking/nfs2-client
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v2 client stats
+probe nfs.client.reqs values ? include : available
+ nfs.client
diff --git a/src/pmlogconf/networking/nfs2-server b/src/pmlogconf/networking/nfs2-server
new file mode 100644
index 0000000..727622b
--- /dev/null
+++ b/src/pmlogconf/networking/nfs2-server
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v2 server stats
+probe nfs.server.reqs values ? include : available
+ nfs.server
diff --git a/src/pmlogconf/networking/nfs3-client b/src/pmlogconf/networking/nfs3-client
new file mode 100644
index 0000000..c077be7
--- /dev/null
+++ b/src/pmlogconf/networking/nfs3-client
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v3 client stats
+probe nfs3.client.reqs values ? include : available
+ nfs3.client
diff --git a/src/pmlogconf/networking/nfs3-server b/src/pmlogconf/networking/nfs3-server
new file mode 100644
index 0000000..87693de
--- /dev/null
+++ b/src/pmlogconf/networking/nfs3-server
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v3 server stats
+probe nfs3.server.reqs values ? include : available
+ nfs3.server
diff --git a/src/pmlogconf/networking/nfs4-client b/src/pmlogconf/networking/nfs4-client
new file mode 100644
index 0000000..6b9601b
--- /dev/null
+++ b/src/pmlogconf/networking/nfs4-client
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v4 client stats
+probe nfs4.client.reqs values ? include : available
+ nfs4.client
diff --git a/src/pmlogconf/networking/nfs4-server b/src/pmlogconf/networking/nfs4-server
new file mode 100644
index 0000000..8e402ed
--- /dev/null
+++ b/src/pmlogconf/networking/nfs4-server
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS v4 server stats
+probe nfs4.server.reqs values ? include : available
+ nfs4.server
diff --git a/src/pmlogconf/networking/other-protocols b/src/pmlogconf/networking/other-protocols
new file mode 100644
index 0000000..301bed4
--- /dev/null
+++ b/src/pmlogconf/networking/other-protocols
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident all available data for other protocols (ip, icmp, igmp, udplite)
+force available
+ network.ip
+ network.icmp
+ network.igmp # for Irix
+ network.udplite # for Linux
diff --git a/src/pmlogconf/networking/rpc b/src/pmlogconf/networking/rpc
new file mode 100644
index 0000000..d8c0d49
--- /dev/null
+++ b/src/pmlogconf/networking/rpc
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident RPC stats
+force available
+ rpc
diff --git a/src/pmlogconf/networking/socket-irix b/src/pmlogconf/networking/socket-irix
new file mode 100644
index 0000000..87a71a6
--- /dev/null
+++ b/src/pmlogconf/networking/socket-irix
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident socket stats (counts by type and state) [Irix]
+probe network.socket.type
+ network.socket
diff --git a/src/pmlogconf/networking/socket-linux b/src/pmlogconf/networking/socket-linux
new file mode 100644
index 0000000..25202c6
--- /dev/null
+++ b/src/pmlogconf/networking/socket-linux
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident socket stats (in use, high-water mark, etc.) [Linux]
+probe network.sockstat.raw.inuse
+ network.sockstat
diff --git a/src/pmlogconf/networking/streams b/src/pmlogconf/networking/streams
new file mode 100644
index 0000000..5f204cf
--- /dev/null
+++ b/src/pmlogconf/networking/streams
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident SVR5 streams activity
+probe resource.nstream_queue
+ resource.nstream_queue
+ resource.nstream_head
diff --git a/src/pmlogconf/networking/tcp-activity-irix b/src/pmlogconf/networking/tcp-activity-irix
new file mode 100644
index 0000000..4027675
--- /dev/null
+++ b/src/pmlogconf/networking/tcp-activity-irix
@@ -0,0 +1,18 @@
+#pmlogconf-setup 2.0
+ident TCP bytes and packets (in and out), connects, accepts, drops and closes [Irix]
+probe network.tcp.accepts
+ network.tcp.accepts
+ network.tcp.connattempt
+ network.tcp.connects
+ network.tcp.drops
+ network.tcp.conndrops
+ network.tcp.timeoutdrop
+ network.tcp.closed
+ network.tcp.sndtotal
+ network.tcp.sndpack
+ network.tcp.sndbyte
+ network.tcp.rcvtotal
+ network.tcp.rcvpack
+ network.tcp.rcvbyte
+ network.tcp.rexmttimeo
+ network.tcp.sndrexmitpack
diff --git a/src/pmlogconf/networking/tcp-activity-linux b/src/pmlogconf/networking/tcp-activity-linux
new file mode 100644
index 0000000..fd7e854
--- /dev/null
+++ b/src/pmlogconf/networking/tcp-activity-linux
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident TCP packets (in and out), errors and retransmits [Linux]
+probe network.tcp.insegs
+ network.tcp.insegs
+ network.tcp.outsegs
+ network.tcp.retranssegs
+ network.tcp.inerrs
diff --git a/src/pmlogconf/networking/tcp-all b/src/pmlogconf/networking/tcp-all
new file mode 100644
index 0000000..15cfbfe
--- /dev/null
+++ b/src/pmlogconf/networking/tcp-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available TCP data
+force available
+ network.tcp
diff --git a/src/pmlogconf/networking/udp-all b/src/pmlogconf/networking/udp-all
new file mode 100644
index 0000000..5265aab
--- /dev/null
+++ b/src/pmlogconf/networking/udp-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available UDP data
+force available
+ network.udp
diff --git a/src/pmlogconf/networking/udp-packets-irix b/src/pmlogconf/networking/udp-packets-irix
new file mode 100644
index 0000000..8b904a5
--- /dev/null
+++ b/src/pmlogconf/networking/udp-packets-irix
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident UDP packets in and out [Irix]
+probe network.udp.ipackets
+ network.udp.ipackets
+ network.udp.opackets
diff --git a/src/pmlogconf/networking/udp-packets-linux b/src/pmlogconf/networking/udp-packets-linux
new file mode 100644
index 0000000..656719b
--- /dev/null
+++ b/src/pmlogconf/networking/udp-packets-linux
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident UDP packets in and out [Linux]
+probe network.udp.indatagrams
+ network.udp.indatagrams
+ network.udp.outdatagrams
diff --git a/src/pmlogconf/platform/hinv b/src/pmlogconf/platform/hinv
new file mode 100644
index 0000000..baee2c5
--- /dev/null
+++ b/src/pmlogconf/platform/hinv
@@ -0,0 +1,9 @@
+#pmlogconf-setup 2.0
+ident platform, filesystem and hardware configuration
+force include
+delta once
+ hinv
+ kernel.uname
+ filesys.mountdir
+ filesys.blocksize
+ filesys.capacity
diff --git a/src/pmlogconf/platform/linux b/src/pmlogconf/platform/linux
new file mode 100644
index 0000000..a2f0308
--- /dev/null
+++ b/src/pmlogconf/platform/linux
@@ -0,0 +1,11 @@
+#pmlogconf-setup 2.0
+ident Linux swap, cache and networking configuration
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ swap.length
+ mem.slabinfo.objects.size
+ network.interface.mtu
+ network.interface.speed
+ network.interface.duplex
+ network.interface.inet_addr
+
diff --git a/src/pmlogconf/platform/localdefs b/src/pmlogconf/platform/localdefs
new file mode 100644
index 0000000..87e49c1
--- /dev/null
+++ b/src/pmlogconf/platform/localdefs
@@ -0,0 +1 @@
+FILES = hinv linux
diff --git a/src/pmlogconf/pmlogconf-setup.sh b/src/pmlogconf/pmlogconf-setup.sh
new file mode 100755
index 0000000..fe9eb68
--- /dev/null
+++ b/src/pmlogconf/pmlogconf-setup.sh
@@ -0,0 +1,404 @@
+#!/bin/sh
+#
+# pmlogconf-setup - parse and process a group file to produce an
+# initial configuration file control line
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+#debug# tmp=`pwd`/tmp-setup
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] file
+
+Options:
+ --host
+ -v,--verbose increase diagnostic verbosity
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit 1
+}
+
+HOST=local:
+verbose=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -h) # host to contact for "probe" tests
+ HOST="$2"
+ shift
+ ;;
+
+ -v) # verbose
+ verbose=true
+ ;;
+
+ --) shift
+ break
+ ;;
+
+ -\?) # eh?
+ _usage
+ # NOTREACHED
+ ;;
+ esac
+ shift
+done
+
+[ $# -eq 1 ] || _usage
+
+# find "probe metric [condition] [state_rule]" line to determine action
+# or
+# find "force state" line
+#
+metric=''
+options=''
+force=''
+eval `sed -n <"$1" \
+ -e '/^#/d' \
+ -e 's/?/"\\\\?"/g' \
+ -e 's/\*/"\\\\*"/g' \
+ -e 's/\[/"\\\\["/g' \
+ -e '/^probe[ ]/{
+s/^probe[ ]*/metric="/
+s/$/ /
+s/[ ]/" options="/
+s/ *$/"/
+p
+q
+}' \
+ -e '/^force[ ]/{
+s/^force[ ]*/force='"'"'/
+s/ *$/'"'"'/
+p
+}'`
+$verbose && $PCP_ECHO_PROG "$1: " >&2
+if [ -n "$force" -a -n "$metric" ]
+then
+ $PCP_ECHO_PROG "$1: Warning: \"probe\" and \"force\" control lines ... ignoring \"force\"" >&2
+ force=''
+fi
+if [ -z "$force" -a -z "$metric" ]
+then
+ $PCP_ECHO_PROG "$1: Warning: neither \"probe\" nor \"force\" control lines ... use \"force available\"" >&2
+ force=available
+fi
+
+$verbose && [ -n "$metric" ] && $PCP_ECHO_PROG $PCP_ECHO_N "probe $metric $options""$PCP_ECHO_C" >&2
+$verbose && [ -n "$force" ] && $PCP_ECHO_PROG $PCP_ECHO_N "force $force""$PCP_ECHO_C" >&2
+rm -f $tmp/err
+if [ -n "$metric" ]
+then
+ # probe
+ echo "$options"
+ echo "data `pmprobe -h $HOST -v $metric`"
+ # need to handle these variants of pmprobe output
+ # sample.string.hullo 1 "hullo world!"
+ # sample.colour 3 101 202 303
+ #
+else
+ # force
+ $PCP_ECHO_PROG "force $force"
+fi \
+| sed \
+ -e '/^data /!{
+s/ //g
+}' \
+ -e '/^data [^"]*$/{
+s/^data //
+s/ //g
+}' \
+ -e '/^data .*"/{
+s/^data //
+s/ "//
+s/" "//g
+s/"$//
+s/ //
+}' \
+| $PCP_AWK_PROG -F '
+BEGIN { # conditions
+ i = 0
+ exists = ++i; condition[exists] = "exists"
+ values = ++i; condition[values] = "values"
+ force = ++i; conditon[force] = "-"
+ regexp = ++i; condition[regexp] = "~"
+ notregexp = ++i; condition[notregexp] = "!~"
+ gt = ++i; condition[gt] = ">";
+ ge = ++i; condition[ge] = ">=";
+ eq = ++i; condition[eq] = "==";
+ neq = ++i; condition[neq] = "!=";
+ le = ++i; condition[le] = "<=";
+ lt = ++i; condition[lt] = "<";
+ # states
+ include = 100; state[include] = "include"
+ exclude = 101; state[exclude] = "exclude"
+ available = 102; state[available] = "available"
+ }
+NR == 1 && $1 == "force" && NF == 2 {
+ # force variant
+ action = -1
+ for (i in state) {
+ if ($2 == state[i]) {
+ action = i
+ break
+ }
+ }
+ if (action == -1) {
+ print "force state \"" $2 "\" not recognized" >"'$tmp/err'"
+ exit
+ }
+ printf "probe=1 action=%d\n",action >>"'$tmp/out'"
+ exit
+ }
+NR == 1 {
+ op = exists # default predicate
+ yes = available # default success action
+ no = exclude # default failure action
+ if (NF > 0) {
+ have_condition = 0
+ for (i in condition) {
+ if ($1 == condition[i]) {
+ have_condition = 1
+ op = i
+ yes = available # default success action
+ no = exclude # default failure action
+ break
+ }
+ }
+ if (have_condition == 0 && $1 != "?") {
+ print "condition operator \"" $1 "\" not recognized" >"'$tmp/err'"
+ exit
+ }
+ if (op == exists || op == values) {
+ if (have_condition)
+ actarg = 2
+ else
+ actarg = 1
+ }
+ else {
+ if (NF < 2) {
+ print "missing condition operand after " condition[op] " operator" >"'$tmp/err'"
+ exit
+ }
+ oprnd = $2
+ actarg = 3
+ }
+ if (NF < actarg) next
+ str = $actarg
+ for (i = actarg+1; i <= NF; i++) {
+ str = str " " $i
+ }
+ if (NF >= actarg && $actarg != "?") {
+ print "expected \"?\" after condition, found \"" str "\"" >"'$tmp/err'"
+ exit
+ }
+ if (NF >= actarg && NF < actarg+3) {
+ print "missing state rule components: \"" str "\"" >"'$tmp/err'"
+ exit
+ }
+ if (NF >= actarg && NF > actarg+3) {
+ print "extra state rule components: \"" str "\"" >"'$tmp/err'"
+ exit
+ }
+ actarg++
+ yes = -1
+ for (i in state) {
+ if ($actarg == state[i]) {
+ yes = i
+ break
+ }
+ }
+ if (yes == -1) {
+ print "sucess state \"" $actarg "\" not recognized" >"'$tmp/err'"
+ exit
+ }
+ actarg++
+ if ($actarg != ":") {
+ print "expected \":\" in state rule, found \"" $actarg "\"" >"'$tmp/err'"
+ exit
+ }
+ actarg++
+ no = -1
+ for (i in state) {
+ if ($actarg == state[i]) {
+ no = i
+ break
+ }
+ }
+ if (no == -1) {
+ print "failure state \"" $actarg "\" not recognized" >"'$tmp/err'"
+ exit
+ }
+ }
+ }
+NR == 2 {
+ #debug# printf "op: %d %s pmprobe: %s",op, condition[op],$0
+ probe = 0
+ if ($2 < 0) {
+ # error from pmprobe
+ ;
+ }
+ else {
+ if (op == exists) {
+ probe = 1
+ }
+ else if (op == values) {
+ if ($2 > 0) probe = 1
+ }
+ else if (op == regexp) {
+ for (i = 3; i <= NF; i++) {
+ if ($i ~ oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == notregexp) {
+ for (i = 3; i <= NF; i++) {
+ if ($i !~ oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == gt) {
+ for (i = 3; i <= NF; i++) {
+ if ($i > oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == ge) {
+ for (i = 3; i <= NF; i++) {
+ if ($i >= oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == eq) {
+ for (i = 3; i <= NF; i++) {
+ if ($i == oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == neq) {
+ for (i = 3; i <= NF; i++) {
+ if ($i != oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == le) {
+ for (i = 3; i <= NF; i++) {
+ if ($i <= oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ else if (op == lt) {
+ for (i = 3; i <= NF; i++) {
+ if ($i < oprnd) {
+ probe = 1
+ break
+ }
+ }
+ }
+ }
+ if (probe == 1)
+ action = yes
+ else
+ action = no
+ printf "probe=%d action=%d\n",probe,action >>"'$tmp/out'"
+ }'
+
+if [ -f $tmp/err ]
+then
+ $verbose && $PCP_ECHO_PROG >&2
+ $PCP_ECHO_PROG "$1: Error: `cat $tmp/err`" >&2
+elif [ -f $tmp/out ]
+then
+ probe=''
+ action=''
+ eval `cat $tmp/out`
+ if $verbose
+ then
+ case $probe
+ in
+ 0)
+ probe_s="failure"
+ ;;
+ 1)
+ probe_s="success"
+ ;;
+ *)
+ probe_s="unknown ($probe)"
+ ;;
+ esac
+ case $action
+ in
+ 100)
+ action_s="include"
+ ;;
+ 101)
+ action_s="exclude"
+ ;;
+ 102)
+ action_s="available"
+ ;;
+ *)
+ action_s="unknown ($action)"
+ ;;
+ esac
+ $PCP_ECHO_PROG " -> probe=$probe_s action=$action_s" >&2
+ fi
+ if [ $action = 100 -o $action = 102 ]
+ then
+ mode='n'
+ [ $action = 100 ] && mode='y'
+ delta=`sed -n <"$1" -e /'^delta[ ]/s/delta[ ]*//p'`
+ [ -z "$delta" ] && delta='default'
+ echo "#+ $1:$mode:$delta:"
+ else
+ echo "#+ $1:x::"
+ fi
+ status=0
+else
+ $verbose && $PCP_ECHO_PROG >&2
+ $PCP_ECHO_PROG "$1: Botch: no errors and no probe results ... try verbose mode" >&2
+fi
+
+exit
diff --git a/src/pmlogconf/pmlogconf.sh b/src/pmlogconf/pmlogconf.sh
new file mode 100755
index 0000000..bdb07f9
--- /dev/null
+++ b/src/pmlogconf/pmlogconf.sh
@@ -0,0 +1,667 @@
+#!/bin/sh
+#
+# pmlogconf - generate/edit a pmlogger configuration file
+#
+# control lines have this format
+# #+ tag:on-off:delta
+# where
+# tag is arbitrary (no embedded :'s) and unique
+# on-off y or n to enable or disable this group, else
+# x for groups excluded by probing from pmlogconf-setup
+# when the group was added to the configuration file
+# delta delta argument for pmlogger "logging ... on delta" clause
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1998,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+#debug# tmp=`pwd`/tmp
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] configfile
+
+Options:
+ -c add message and timestamp (not for interactive use)
+ -d=DIR,--groups=DIR specify path to the pmlogconf groups directory
+ --host
+ -q,--quiet quiet, suppress logging interval dialog
+ -r,--reprobe every group reconsidered for inclusion in configfile
+ -v,--verbose increase diagnostic verbosity
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit
+}
+
+quick=false
+pat=''
+prompt=true
+reprobe=false
+autocreate=false
+BASE=''
+HOST=''
+verbose=false
+setupflags=''
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) # automated, non-interactive file creation
+ autocreate=true
+ prompt=false
+ ;;
+
+ -d) # base directory for the group files
+ BASE="$2"
+ shift
+ ;;
+
+ -h) # host to contact for "probe" tests
+ HOST="$2"
+ shift
+ ;;
+
+ -q) # "quick" mode, don't change logging intervals
+ quick=true
+ ;;
+
+ -r) # reprobe
+ reprobe=true
+ ;;
+
+ -v) # verbose
+ verbose=true
+ setupflags="$setupflags -v"
+ ;;
+
+ --) # end options
+ shift
+ break
+ ;;
+
+ -\?) _usage
+ ;;
+ esac
+ shift
+done
+
+[ $# -eq 1 ] || _usage
+
+if [ -n "$BASE" -a ! -d "$BASE" ]
+then
+ echo "$prog: Error: base directory ($BASE) for group files does not exist"
+ exit
+fi
+
+config="$1"
+
+# split $tmp/ctl at the line containing the unprocessed tag to
+# produce
+# $tmp/head
+# $tmp/tag - one line
+# $tmp/tail
+#
+_split()
+{
+ rm -f $tmp/head $tmp/tag $tmp/tail
+ $PCP_AWK_PROG <$tmp/ctl '
+BEGIN { out = "'"$tmp/head"'" }
+/DO NOT UPDATE THE FILE ABOVE/ { seen = 1 }
+seen == 0 && /^\#\? [^:]*:[ynx]:/ { print >"'"$tmp/tag"'"
+ out = "'"$tmp/tail"'"
+ seen = 1
+ next
+ }
+ { print >out }'
+}
+
+# do all of the real iterative work
+#
+_update()
+{
+ # strip the existing pmlogger config and leave the comments
+ # and the control lines
+ #
+
+ $PCP_AWK_PROG <$tmp/in >$tmp/ctl '
+/DO NOT UPDATE THE FILE ABOVE/ { tail = 1 }
+tail == 1 { print; next }
+/^\#\+ [^:]*:[ynx]:/ { sub(/\+/, "?", $1); print; skip = 1; next }
+skip == 1 && /^\#----/ { skip = 0; next }
+skip == 1 { next }
+ { print }'
+
+ # now need to be a little smarter ... tags may have appeared or
+ # disappeared from the shipped defaults, so need to munge the contents
+ # of $tmp/ctl to reflect this
+ #
+ find $BASE -type f \
+ | sed \
+ -e "s;$BASE/;;" \
+ -e '/^v1.0\//d' \
+ | LC_COLLATE=POSIX sort \
+ | while read tag
+ do
+ if sed 1q <$BASE/"$tag" | grep '^#pmlogconf-setup 2.0' >/dev/null
+ then
+ :
+ else
+ # not one of our group files, skip it ...
+ continue
+ fi
+ if grep "^#? $tag:" $tmp/ctl >/dev/null
+ then
+ :
+ else
+ $verbose && echo "need to add new group tag=$tag"
+ rm -f $tmp/pre $tmp/post
+ $PCP_AWK_PROG <$tmp/ctl '
+BEGIN { out = "'"$tmp/pre"'" }
+/DO NOT UPDATE THE FILE ABOVE/ { out = "'"$tmp/post"'" }
+ { print >out }'
+ mv $tmp/pre $tmp/ctl
+ [ -z "$HOST" ] && HOST=local:
+ if $PCP_BINADM_DIR/pmlogconf-setup -h $HOST $setupflags $BASE/"$tag" 2>$tmp/err >$tmp/out
+ then
+ :
+ else
+ echo "$prog: Warning: $BASE/$tag: pmlogconf-setup failed"
+ sts=1
+ fi
+ sed -e "s;$BASE/;;" <$tmp/out >$tmp/tmp
+ [ -s $tmp/err ] && cat $tmp/err
+ sed -e '/^#+/s/+/?/' <$tmp/tmp >>$tmp/ctl
+ cat $tmp/post >>$tmp/ctl
+ fi
+ done
+
+ while true
+ do
+ _split
+ [ ! -s $tmp/tag ] && break
+ eval `sed <$tmp/tag -e 's/^#? /tag="/' -e 's/:/" onoff="/' -e 's/:/" delta="/' -e 's/:.*/"/'`
+ [ -z "$delta" ] && delta=default
+
+ if $reprobe
+ then
+ [ -z "$HOST" ] && HOST=local:
+ if $PCP_BINADM_DIR/pmlogconf-setup -h $HOST $setupflags $BASE/"$tag" 2>$tmp/err >$tmp/out
+ then
+ :
+ else
+ echo "$prog: Warning: $BASE/$tag: pmlogconf-setup failed"
+ sts=1
+ fi
+ sed -e "s;$BASE/;;" <$tmp/out >$tmp/tmp
+ [ -s $tmp/err ] && cat $tmp/err
+ if [ -s $tmp/tmp ]
+ then
+ eval `sed <$tmp/tmp -e 's/^#+ /tag_r="/' -e 's/:/" onoff_r="/' -e 's/:/" delta_r="/' -e 's/:.*/"/'`
+ [ -z "$delta_r" ] && delta_r=default
+ if [ "$tag" != "$tag_r" ]
+ then
+ echo "Botch: reprobe for $tag found new tag ${tag_r}, no change"
+ cat $tmp/tmp
+ else
+ if [ "$onoff" = y ]
+ then
+ # existing y takes precedence
+ if [ "$onoff_r" = x ]
+ then
+ echo "Warning: reprobe for $tag suggests exclude, keeping current include status"
+ fi
+ else
+ onoff=$onoff_r
+ [ "$delta" != "default" ] && delta=$delta_r
+ fi
+ fi
+ fi
+ fi
+
+ case $onoff
+ in
+ y|n) ;;
+ x) # excluded group from setup
+ cat $tmp/head >$tmp/ctl
+ echo "#+ $tag:x::" >>$tmp/ctl
+ echo "#----" >>$tmp/ctl
+ cat $tmp/tail >>$tmp/ctl
+ continue
+ ;;
+ *) echo "Warning: tag=$tag onoff is illegal ($onoff) ... setting to \"n\""
+ onoff=n
+ ;;
+ esac
+
+ if [ -f $BASE/$tag ]
+ then
+ eval `$PCP_AWK_PROG <$BASE/$tag '
+BEGIN { desc = ""; metrics = "" }
+$1 == "ident" { if (desc != "") desc = desc "\n"
+ for (i = 2; i <= NF; i++) {
+ if (i == 2) desc = desc $2
+ else desc = desc " " $i
+ }
+ next
+ }
+END { printf "desc='"'"'%s'"'"'\n",desc }'`
+
+ sed -n <$BASE/$tag >$tmp/metrics \
+ -e '/^[ ]/s/[ ]*//p'
+ #debug# echo $tag:
+ #debug# echo "desc: $desc"
+ else
+ case "$tag"
+ in
+ v1.0/*)
+ # from migration, silently do nothing
+ ;;
+ *)
+ echo "Warning: cannot find group file ($tag) ... no change is possible"
+ ;;
+ esac
+ $PCP_AWK_PROG <"$config" >>$tmp/head '
+BEGIN { tag="'"$tag"'" }
+$1 == "#+" && $2 ~ tag { want = 1 }
+want == 1 { print }
+want == 1 && /^#----/ { exit }'
+ cat $tmp/head $tmp/tail >$tmp/ctl
+ continue
+ fi
+
+ if [ ! -z "$pat" ]
+ then
+ if echo "$desc" | grep "$pat" >/dev/null
+ then
+ pat=''
+ prompt=true
+ fi
+ if grep "$pat" $tmp/metrics >/dev/null
+ then
+ pat=''
+ prompt=true
+ fi
+ fi
+ if $prompt
+ then
+ # prompt for answers
+ #
+ echo
+ was_onoff=$onoff
+ echo "Group: $desc" | fmt -74 | sed -e '1!s/^/ /'
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Log this group? [$onoff] ""$PCP_ECHO_C"
+ read ans
+ if [ "$ans" = "?" ]
+ then
+ echo 'Valid responses are:
+m report the names of the metrics in this group
+n do not log this group
+q quit; no change for this or any of the following groups
+y log this group
+/pattern no change for this group and search for a group containing pattern
+ in the description or the metrics associated with the group'
+ continue
+ fi
+ if [ "$ans" = m ]
+ then
+ echo "Metrics in this group ($tag):"
+ sed -e 's/^/ /' $tmp/metrics
+ continue
+ fi
+ if [ "$ans" = q ]
+ then
+ # quit ...
+ ans="$onoff"
+ prompt=false
+ fi
+ pat=`echo "$ans" | sed -n 's/^\///p'`
+ if [ ! -z "$pat" ]
+ then
+ echo "Searching for \"$pat\""
+ ans="$onoff"
+ prompt=false
+ fi
+ [ -z "$ans" ] && ans="$onoff"
+ [ "$ans" = y -o "$ans" = n ] && break
+ echo "Error: you must answer \"m\" or \"n\" or \"q\" or \"y\" or \"/pattern\" ... try again"
+ done
+ onoff="$ans"
+ if [ $prompt = true -a "$onoff" = y ]
+ then
+ if $quick
+ then
+ if [ $was_onoff = y ]
+ then
+ # no change, be quiet
+ :
+ else
+ echo "Logging interval: $delta"
+ fi
+ else
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Logging interval? [$delta] ""$PCP_ECHO_C"
+ read ans
+ if [ -z "$ans" ]
+ then
+ # use suggested value, assume this is good
+ #
+ ans="$delta"
+ break
+ else
+ # do some sanity checking ...
+ #
+ ok=`echo "$ans" \
+ | sed -e 's/^every //' \
+ | $PCP_AWK_PROG '
+/^once$/ { print "true"; exit }
+/^default$/ { print "true"; exit }
+/^[0-9][0-9]* *msec$/ { print "true"; exit }
+/^[0-9][0-9]* *msecs$/ { print "true"; exit }
+/^[0-9][0-9]* *millisecond$/ { print "true"; exit }
+/^[0-9][0-9]* *milliseconds$/ { print "true"; exit }
+/^[0-9][0-9]* *sec$/ { print "true"; exit }
+/^[0-9][0-9]* *secs$/ { print "true"; exit }
+/^[0-9][0-9]* *second$/ { print "true"; exit }
+/^[0-9][0-9]* *seconds$/ { print "true"; exit }
+/^[0-9][0-9]* *min$/ { print "true"; exit }
+/^[0-9][0-9]* *mins$/ { print "true"; exit }
+/^[0-9][0-9]* *minute$/ { print "true"; exit }
+/^[0-9][0-9]* *minutes$/ { print "true"; exit }
+/^[0-9][0-9]* *hour$/ { print "true"; exit }
+/^[0-9][0-9]* *hours$/ { print "true"; exit }
+ { print "false"; exit }'`
+ if $ok
+ then
+ delta="$ans"
+ break
+ else
+
+ echo "Error: logging interval must be of the form \"once\" or \"default\" or"
+ echo "\"<integer> <scale>\", where <scale> is one of \"sec\", \"secs\", \"min\","
+ echo "\"mins\", etc ... try again"
+ fi
+ fi
+ done
+ fi
+ fi
+ else
+ $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ fi
+
+ echo "#+ $tag:$onoff:$delta:" >>$tmp/head
+ echo "$desc" | fmt | sed -e 's/^/## /' >>$tmp/head
+ if [ "$onoff" = y ]
+ then
+ if [ -s $tmp/metrics ]
+ then
+ echo "log advisory on $delta {" >>$tmp/head
+ sed -e 's/^/ /' <$tmp/metrics >>$tmp/head
+ echo "}" >>$tmp/head
+ fi
+ fi
+ echo "#----" >>$tmp/head
+ cat $tmp/head $tmp/tail >$tmp/ctl
+
+ done
+}
+
+if [ ! -f "$config" ]
+then
+ # create a new config file
+ #
+ touch "$config"
+ if [ ! -f "$config" ]
+ then
+ echo "$prog: Error: config file \"$config\" does not exist and cannot be created"
+ exit
+ fi
+
+ $PCP_ECHO_PROG "Creating config file \"$config\" using default settings ..."
+ prompt=false
+ new=true
+ [ -z "$HOST" ] && HOST=local:
+ [ -z "$BASE" ] && BASE=$PCP_VAR_DIR/config/pmlogconf
+
+ cat <<End-of-File >$tmp/in
+#pmlogconf 2.0
+#
+# pmlogger(1) config file created and updated by pmlogconf
+End-of-File
+ $autocreate && echo "# Auto-generated by pmlogconf on: "`date` >>$tmp/in
+ cat <<End-of-File >>$tmp/in
+#
+# DO NOT UPDATE THE INITIAL SECTION OF THIS FILE.
+# Any changes may be lost the next time pmlogconf is used
+# on this file.
+#
+#+ groupdir $BASE
+#
+End-of-File
+
+ find $BASE -type f \
+ | sed \
+ -e '/\/v1.0\//d' \
+ | LC_COLLATE=POSIX sort \
+ | while read tag
+ do
+ if sed 1q <"$tag" | grep '^#pmlogconf-setup 2.0' >/dev/null
+ then
+ :
+ else
+ # not one of our group files, skip it ...
+ continue
+ fi
+ if $PCP_BINADM_DIR/pmlogconf-setup -h $HOST $setupflags "$tag" 2>$tmp/err >$tmp/out
+ then
+ :
+ else
+ echo "$prog: Warning: $BASE/$tag: pmlogconf-setup failed"
+ [ -s $tmp/err ] && cat $tmp/err
+ sts=1
+ fi
+ sed -e "s;$BASE/;;" <$tmp/out >>$tmp/in
+ [ -s $tmp/err ] && cat $tmp/err
+ done
+
+ cat <<End-of-File >>$tmp/in
+
+# DO NOT UPDATE THE FILE ABOVE THIS LINE
+# Otherwise any changes may be lost the next time pmlogconf is
+# used on this file.
+#
+# It is safe to make additions from here on ...
+#
+
+[access]
+disallow .* : all;
+disallow :* : all;
+allow local:* : enquire;
+End-of-File
+
+else
+ # updating an existing config file
+ #
+ new=false
+ magic=`sed 1q "$config"`
+ if echo "$magic" | grep "^#pmlogconf" >/dev/null
+ then
+ version=`echo $magic | sed -e "s/^#pmlogconf//" -e 's/^ *//'`
+ if [ "$version" = "1.0" ]
+ then
+ echo "$prog: migrating \"$config\" from version 1.0 to 2.0 ..."
+ [ -z "$BASE" ] && BASE=$PCP_VAR_DIR/config/pmlogconf
+ sed <"$config" >$tmp/in \
+ -e '1s/1\.0/2.0/' \
+ -e "/# on this file./a\\
+#\\
+#+ groupdir $BASE" \
+ -e '/^#\+/{
+s; C0:; cpu/summary:;
+s; C1:; cpu/percpu:;
+s; C2:; v1.0/C2:;
+s; C3:; v1.0/C3:;
+s; D0:; disk/summary:;
+s; D1:; disk/percontroller:;
+s; D2:; disk/perdisk:;
+s; D3:; v1.0/D3:;
+s; F0:; filesystem/all:;
+s; F1:; filesystem/xfs-io-irix:;
+s; F2:; filesystem/xfs-all:;
+s; F3:; sgi/xlv-activity:;
+s; F4:; sgi/xlv-stripe-io:;
+s; F5:; sgi/efs:;
+s; F6:; sgi/xvm-ops:;
+s; F7:; sgi/xvm-stats:;
+s; F8:; sgi/xvm-all:;
+s; H0:; sgi/craylink:;
+s; H1:; sgi/hub:;
+s; H2:; sgi/cpu-evctr:;
+s; H3:; sgi/xbow:;
+s; I0:; platform/hinv:;
+s; K0:; v1.0/K0:;
+s; K1:; kernel/syscalls-irix:;
+s; K2:; kernel/syscalls-percpu-irix:;
+s; K3:; kernel/read-write-data:;
+s; K4:; kernel/interrupts-irix:;
+s; K5:; kernel/bufcache-activity:;
+s; K6:; kernel/bufcache-all:;
+s; K7:; kernel/vnodes:;
+s; K8:; kernel/inode-cache:;
+s; K9:; sgi/kaio:;
+s; Ka:; kernel/queues-irix:;
+s; M0:; memory/swap-activity:;
+s; M1:; memory/tlb-irix:;
+s; M2:; kernel/memory-irix:;
+s; M3:; memory/swap-all:;
+s; M4:; memory/swap-config:;
+s; M5:; sgi/node-memory:;
+s; M6:; sgi/numa:;
+s; M7:; sgi/numa-summary:;
+s; N0:; networking/interface-summary:;
+s; N1:; networking/interface-all:;
+s; N2:; networking/tcp-activity-irix:;
+s; N3:; networking/tcp-all:;
+s; N4:; networking/udp-packets-irix:;
+s; N5:; networking/udp-all:;
+s; N6:; networking/socket-irix:;
+s; N7:; networking/other-protocols:;
+s; N8:; networking/mbufs:;
+s; N9:; networking/multicast:;
+s; Na:; networking/streams:;
+s; S0:; v1.0/S0:;
+s; S1:; v1.0/S1:;
+s; S2:; networking/rpc:;
+}'
+ reprobe=true
+ elif [ "$version" = "2.0" ]
+ then
+ # start with existing config file
+ #
+ cp "$config" $tmp/in
+ else
+ echo "$prog: Error: existing config file \"$config\" is wrong version ($version)"
+ exit
+ fi
+ else
+ echo "$prog: Error: existing \"$config\" is not a $prog control file"
+ exit
+ fi
+ if [ ! -w "$config" ]
+ then
+ echo "$prog: Error: existing config file \"$config\" is not writeable"
+ exit
+ fi
+
+ [ -n "$HOST" ] && echo "$prog: Warning: existing config file, -h $HOST will be ignored"
+
+ CBASE=`sed -n -e '/^#+ groupdir /s///p' <$tmp/in`
+ if [ -z "$BASE" ]
+ then
+ BASE="$CBASE"
+ else
+ if [ "$BASE" != "$CBASE" ]
+ then
+ echo "$prog: Warning: using base directory for group files from command line ($BASE) which is different from that in $config ($CBASE)"
+ fi
+ fi
+fi
+
+while true
+do
+ _update
+
+ [ -z "$pat" ] && break
+
+ echo " not found."
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Continue searching from start of the file? [y] ""$PCP_ECHO_C"
+ read ans
+ [ -z "$ans" ] && ans=y
+ [ "$ans" = y -o "$ans" = n ] && break
+ echo "Error: you must answer \"y\" or \"n\" ... try again"
+ done
+ mv $tmp/ctl $tmp/in
+ if [ "$ans" = n ]
+ then
+ pat=''
+ prompt=true
+ else
+ echo "Searching for \"$pat\""
+ fi
+done
+
+if $new
+then
+ echo
+ cp $tmp/ctl "$config"
+else
+ echo
+ if diff "$config" $tmp/ctl >/dev/null
+ then
+ echo "No changes"
+ else
+ echo "Differences ..."
+ ${DIFF-diff} -c "$config" $tmp/ctl
+ while true
+ do
+ $PCP_ECHO_PROG $PCP_ECHO_N "Keep changes? [y] ""$PCP_ECHO_C"
+ read ans
+ [ -z "$ans" ] && ans=y
+ [ "$ans" = y -o "$ans" = n ] && break
+ echo "Error: you must answer \"y\" or \"n\" ... try again"
+ done
+ [ "$ans" = y ] && cp $tmp/ctl "$config"
+ fi
+fi
+
+status=0
+exit
diff --git a/src/pmlogconf/sgi/cpu-evctr b/src/pmlogconf/sgi/cpu-evctr
new file mode 100644
index 0000000..88e3978
--- /dev/null
+++ b/src/pmlogconf/sgi/cpu-evctr
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident global CPU hardware event counters (enable first with ecadmin(1))
+probe hw.r10kevctr.state
+ hw.r10kevctr
diff --git a/src/pmlogconf/sgi/craylink b/src/pmlogconf/sgi/craylink
new file mode 100644
index 0000000..99ca3e5
--- /dev/null
+++ b/src/pmlogconf/sgi/craylink
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident CrayLink routers
+probe hw.router.rev_id
+ hw.router
diff --git a/src/pmlogconf/sgi/efs b/src/pmlogconf/sgi/efs
new file mode 100644
index 0000000..858fd03
--- /dev/null
+++ b/src/pmlogconf/sgi/efs
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident EFS activity
+probe efs.attempts
+ efs
diff --git a/src/pmlogconf/sgi/hub b/src/pmlogconf/sgi/hub
new file mode 100644
index 0000000..62ddb71
--- /dev/null
+++ b/src/pmlogconf/sgi/hub
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident Origin hubs
+probe hw.hub.nasid
+ hw.hub
diff --git a/src/pmlogconf/sgi/kaio b/src/pmlogconf/sgi/kaio
new file mode 100644
index 0000000..f5ea49b
--- /dev/null
+++ b/src/pmlogconf/sgi/kaio
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident asynchronous I/O activity
+probe kaio.reads
+ kaio
diff --git a/src/pmlogconf/sgi/localdefs b/src/pmlogconf/sgi/localdefs
new file mode 100644
index 0000000..27d97fe
--- /dev/null
+++ b/src/pmlogconf/sgi/localdefs
@@ -0,0 +1 @@
+FILES = cpu-evctr xbow hub craylink node-memory numa numa-summary xvm-ops xvm-stats xvm-all kaio efs xlv-activity xlv-stripe-io
diff --git a/src/pmlogconf/sgi/node-memory b/src/pmlogconf/sgi/node-memory
new file mode 100644
index 0000000..fdbd0f0
--- /dev/null
+++ b/src/pmlogconf/sgi/node-memory
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident "large" page and Origin node-based allocations and activity
+probe mem.lpage.enabled
+ mem.lpage
+ origin.node
diff --git a/src/pmlogconf/sgi/numa b/src/pmlogconf/sgi/numa
new file mode 100644
index 0000000..d4d2163
--- /dev/null
+++ b/src/pmlogconf/sgi/numa
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all NUMA stats
+probe origin.numa.routerload
+ origin.numa
diff --git a/src/pmlogconf/sgi/numa-summary b/src/pmlogconf/sgi/numa-summary
new file mode 100644
index 0000000..0d8d711
--- /dev/null
+++ b/src/pmlogconf/sgi/numa-summary
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NUMA migration stats
+probe origin.numa.migr.intr.total
+ origin.numa.migr.intr.total
diff --git a/src/pmlogconf/sgi/xbow b/src/pmlogconf/sgi/xbow
new file mode 100644
index 0000000..ff1ee3f
--- /dev/null
+++ b/src/pmlogconf/sgi/xbow
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident XBOW activity
+probe xbow.nports
+ xbow
diff --git a/src/pmlogconf/sgi/xlv-activity b/src/pmlogconf/sgi/xlv-activity
new file mode 100644
index 0000000..dda08ec
--- /dev/null
+++ b/src/pmlogconf/sgi/xlv-activity
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident XLV operations and bytes per volume
+probe xlv.read
+ xlv.read
+ xlv.write
+ xlv.read_bytes
+ xlv.write_bytes
diff --git a/src/pmlogconf/sgi/xlv-stripe-io b/src/pmlogconf/sgi/xlv-stripe-io
new file mode 100644
index 0000000..255c369
--- /dev/null
+++ b/src/pmlogconf/sgi/xlv-stripe-io
@@ -0,0 +1,8 @@
+#pmlogconf-setup 2.0
+ident XLV striped volume stats
+probe xlv.stripe_ops
+ xlv.stripe_ops
+ xlv.stripe_units
+ xlv.aligned
+ xlv.unaligned
+ xlv.largest_io
diff --git a/src/pmlogconf/sgi/xvm-all b/src/pmlogconf/sgi/xvm-all
new file mode 100644
index 0000000..e207066
--- /dev/null
+++ b/src/pmlogconf/sgi/xvm-all
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available XVM data
+probe hinv.xvm.nvolumes
+ xvm
diff --git a/src/pmlogconf/sgi/xvm-ops b/src/pmlogconf/sgi/xvm-ops
new file mode 100644
index 0000000..9d001e1
--- /dev/null
+++ b/src/pmlogconf/sgi/xvm-ops
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident XVM operations and bytes per volume
+probe hinv.xvm.nvolumes
+ xvm.ve.read
+ xvm.ve.write
+ xvm.ve.read_bytes
+ xvm.ve.write_bytes
diff --git a/src/pmlogconf/sgi/xvm-stats b/src/pmlogconf/sgi/xvm-stats
new file mode 100644
index 0000000..47468da
--- /dev/null
+++ b/src/pmlogconf/sgi/xvm-stats
@@ -0,0 +1,6 @@
+#pmlogconf-setup 2.0
+ident XVM stripe, mirror and concat volume stats
+probe hinv.xvm.nvolumes
+ xvm.ve.concat
+ xvm.ve.mirror
+ xvm.ve.stripe
diff --git a/src/pmlogconf/sqlserver/localdefs b/src/pmlogconf/sqlserver/localdefs
new file mode 100644
index 0000000..f1f1c80
--- /dev/null
+++ b/src/pmlogconf/sqlserver/localdefs
@@ -0,0 +1 @@
+FILES = summary
diff --git a/src/pmlogconf/sqlserver/summary b/src/pmlogconf/sqlserver/summary
new file mode 100644
index 0000000..1e28cd6
--- /dev/null
+++ b/src/pmlogconf/sqlserver/summary
@@ -0,0 +1,24 @@
+#pmlogconf-setup 2.0
+ident SQLServer summary
+probe sqlserver.locks
+ sqlserver.access
+ sqlserver.buf_mgr
+ sqlserver.mem_mgr
+ sqlserver.locks
+ sqlserver.connections
+ sqlserver.sql.batch_requests
+ sqlserver.sql.compilations
+ sqlserver.sql.re_compilations
+ sqlserver.databases.all.transactions
+ sqlserver.databases.all.active_transactions
+ sqlserver.databases.all.data_file_size
+ sqlserver.databases.all.log_file_size
+ sqlserver.databases.all.log_file_used
+ sqlserver.databases.db.transactions
+ sqlserver.databases.db.active_transactions
+ sqlserver.databases.db.data_file_size
+ sqlserver.databases.db.log_file_size
+ sqlserver.databases.db.log_file_used
+ sqlserver.latches.waits
+ sqlserver.latches.wait_time
+ sqlserver.latches.avg_wait_time
diff --git a/src/pmlogconf/tools/atop b/src/pmlogconf/tools/atop
new file mode 100644
index 0000000..5d7ed8d
--- /dev/null
+++ b/src/pmlogconf/tools/atop
@@ -0,0 +1,67 @@
+#pmlogconf-setup 2.0
+ident metrics used by the atop command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+ disk.dev.avactive
+ disk.dev.read
+ disk.dev.read_bytes
+ disk.dev.total
+ disk.dev.write
+ disk.partitions.blkread
+ disk.partitions.blkwrite
+ disk.partitions.read
+ disk.partitions.write
+ kernel.all.cpu.idle
+ kernel.all.cpu.irq.hard
+ kernel.all.cpu.irq.soft
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.nprocs
+ kernel.all.pswitch
+ kernel.all.uptime
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.irq.hard
+ kernel.percpu.cpu.irq.soft
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ kernel.percpu.interrupts
+ mem.freemem
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.commitLimit
+ mem.util.committed_AS
+ mem.util.shmem
+ mem.util.slab
+ mem.util.swapFree
+ mem.util.swapTotal
+ mem.vmstat.allocstall
+ mem.vmstat.pginodesteal
+ mem.vmstat.pswpin
+ mem.vmstat.pswpout
+ mem.vmstat.slabs_scanned
+ network.icmp.inmsgs
+ network.icmp.outmsgs
+ network.interface.in.bytes
+ network.interface.in.errors
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.errors
+ network.interface.out.packets
+ network.ip.forwdatagrams
+ network.ip.indelivers
+ network.ip.inreceives
+ network.ip.outrequests
+ network.tcp.activeopens
+ network.tcp.insegs
+ network.tcp.outsegs
+ network.tcp.passiveopens
+ network.udp.indatagrams
+ network.udp.outdatagrams
+ proc.nprocs
+ proc.runq.blocked
+ proc.runq.defunct
+ proc.runq.sleeping
diff --git a/src/pmlogconf/tools/atop-proc b/src/pmlogconf/tools/atop-proc
new file mode 100644
index 0000000..c886cde
--- /dev/null
+++ b/src/pmlogconf/tools/atop-proc
@@ -0,0 +1,21 @@
+#pmlogconf-setup 2.0
+ident per-process metrics used by the atop command
+force available
+delta 120 seconds
+
+ proc.id.uid_nm
+ proc.memory.datrss
+ proc.memory.librss
+ proc.memory.textrss
+ proc.memory.vmstack
+ proc.psinfo.cmd
+ proc.psinfo.maj_flt
+ proc.psinfo.minflt
+ proc.psinfo.pid
+ proc.psinfo.rss
+ proc.psinfo.sname
+ proc.psinfo.stime
+ proc.psinfo.utime
+ proc.psinfo.vsize
+ proc.psinfo.nswap
+ proc.psinfo.threads
diff --git a/src/pmlogconf/tools/atop-summary b/src/pmlogconf/tools/atop-summary
new file mode 100644
index 0000000..25e5501
--- /dev/null
+++ b/src/pmlogconf/tools/atop-summary
@@ -0,0 +1,7 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the atop command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ hinv.cpu.clock
+ hinv.ncpu
+ mem.physmem
diff --git a/src/pmlogconf/tools/collectl b/src/pmlogconf/tools/collectl
new file mode 100644
index 0000000..a8a9b3e
--- /dev/null
+++ b/src/pmlogconf/tools/collectl
@@ -0,0 +1,75 @@
+#pmlogconf-setup 2.0
+ident metrics used by the collectl command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+ disk.all.read
+ disk.all.read_bytes
+ disk.all.read_merge
+ disk.all.write
+ disk.all.write_bytes
+ disk.all.write_merge
+ disk.dev.blkread
+ disk.dev.blkwrite
+ disk.dev.read
+ disk.dev.read_bytes
+ disk.dev.read_merge
+ disk.dev.write
+ disk.dev.write_bytes
+ disk.dev.write_merge
+ hinv.ncpu
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.irq.hard
+ kernel.all.cpu.irq.soft
+ kernel.all.cpu.nice
+ kernel.all.cpu.steal
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.nprocs
+ kernel.all.pswitch
+ kernel.all.runnable
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.irq.hard
+ kernel.percpu.cpu.irq.soft
+ kernel.percpu.cpu.nice
+ kernel.percpu.cpu.steal
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ kernel.percpu.interrupts
+ mem.freemem
+ mem.physmem
+ mem.util.anonpages
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.committed_AS
+ mem.util.inactive
+ mem.util.mapped
+ mem.util.mlocked
+ mem.util.slab
+ mem.util.swapTotal
+ mem.util.used
+ mem.vmstat.pgfault
+ mem.vmstat.pgmajfault
+ mem.vmstat.pgpgin
+ mem.vmstat.pgpgout
+ network.interface.in.bytes
+ network.interface.in.compressed
+ network.interface.in.errors
+ network.interface.in.mcasts
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.errors
+ network.interface.out.packets
+ network.interface.total.mcasts
+ proc.runq.blocked
+ proc.runq.runnable
+ swap.free
+ swap.pagesin
+ swap.pagesout
+ swap.used
+
diff --git a/src/pmlogconf/tools/collectl-summary b/src/pmlogconf/tools/collectl-summary
new file mode 100644
index 0000000..e03b9ac
--- /dev/null
+++ b/src/pmlogconf/tools/collectl-summary
@@ -0,0 +1,6 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the collectl command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ hinv.ncpu
+ mem.physmem
diff --git a/src/pmlogconf/tools/dmcache b/src/pmlogconf/tools/dmcache
new file mode 100644
index 0000000..e463bd5
--- /dev/null
+++ b/src/pmlogconf/tools/dmcache
@@ -0,0 +1,13 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pcp-dmcache(1) command
+probe dmcache.cache.used
+ disk.dm.read
+ disk.dm.write
+ dmcache.cache.used
+ dmcache.cache.total
+ dmcache.metadata.used
+ dmcache.metadata.total
+ dmcache.read_hits
+ dmcache.read_misses
+ dmcache.write_hits
+ dmcache.write_misses
diff --git a/src/pmlogconf/tools/free b/src/pmlogconf/tools/free
new file mode 100644
index 0000000..7cd62e2
--- /dev/null
+++ b/src/pmlogconf/tools/free
@@ -0,0 +1,13 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pcp-free(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+ mem.util.free
+ mem.util.shared
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.highFree
+ mem.util.highTotal
+ mem.util.lowFree
+ mem.util.lowTotal
+ mem.util.swapFree
+ mem.util.swapTotal
diff --git a/src/pmlogconf/tools/free-summary b/src/pmlogconf/tools/free-summary
new file mode 100644
index 0000000..e54af98
--- /dev/null
+++ b/src/pmlogconf/tools/free-summary
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the pcp-free(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ mem.physmem
diff --git a/src/pmlogconf/tools/iostat b/src/pmlogconf/tools/iostat
new file mode 100644
index 0000000..ae7f6e1
--- /dev/null
+++ b/src/pmlogconf/tools/iostat
@@ -0,0 +1,68 @@
+#pmlogconf-setup 2.0
+ident metrics used by the iostat(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+# -d --------------------------------------------------
+
+# Device:
+# tps
+# kB_read/s
+# kB_wrtn/s
+ disk.dev.read_bytes # kB_read
+ disk.dm.read_bytes
+ disk.dev.write_bytes # kB_wrtn
+ disk.dm.write_bytes
+
+# -h --------------------------------------------------
+
+ kernel.all.cpu.user # %user
+ kernel.all.cpu.nice # %nice
+ kernel.all.cpu.sys # %system
+ kernel.all.cpu.wait.total # %iowait
+ kernel.all.cpu.steal # %steal
+ kernel.all.cpu.idle # %idle
+
+# -x --------------------------------------------------
+
+ disk.all.read # total: Total reads completed successfully
+ disk.all.read_merge # merged: grouped reads (resulting in one I/O)
+ disk.all.blkread # sectors: Sectors read successfully
+ disk.all.read_rawactive # ms: milliseconds spent reading
+ disk.all.write # total: Total writes completed successfully
+ disk.all.write_merge # merged: grouped writes (resulting in one I/O)
+ disk.all.blkwrite # sectors: Sectors written successfully
+ disk.all.write_rawactive # ms: milliseconds spent writing
+ disk.all.avactive # s: seconds spent for I/O
+
+ disk.dev.read # total: Total reads completed successfully
+ disk.dm.read
+ disk.dev.read_merge # merged: grouped reads (resulting in one I/O)
+ disk.dm.read_merge
+ disk.dev.blkread # sectors: Sectors read successfully
+ disk.dm.blkread
+ disk.dev.read_rawactive # ms: milliseconds spent reading
+ disk.dm.read_rawactive
+ disk.dev.write # total: Total writes completed successfully
+ disk.dm.write
+ disk.dev.write_merge # merged: grouped writes (resulting in one I/O)
+ disk.dm.write_merge
+ disk.dev.blkwrite # sectors: Sectors written successfully
+ disk.dm.blkwrite
+ disk.dev.write_rawactive # ms: milliseconds spent writing
+ disk.dm.write_rawactive
+
+ disk.all.read_merge # rrqm/s
+ disk.all.write_merge # wrqm/s
+ disk.all.read # r/s
+ disk.all.write # w/s
+ disk.all.read_bytes # rkB/s
+ disk.all.write_bytes # wkB/s
+ # avgqu-sz
+ # - avgrq-sz
+ disk.dev.avactive # await
+ disk.dm.avactive
+ disk.all.read_rawactive # r_await
+ disk.all.write_rawactive # w_await
+ # - svctm
+ # - %util (r/s + w/s) * (svctm / 1000)
+
diff --git a/src/pmlogconf/tools/ip b/src/pmlogconf/tools/ip
new file mode 100644
index 0000000..4075db8
--- /dev/null
+++ b/src/pmlogconf/tools/ip
@@ -0,0 +1,21 @@
+#pmlogconf-setup 2.0
+ident metrics used by the ip(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+# -s link -------------------------------------------------
+
+ network.interface.mtu
+ network.interface.out.bytes
+ network.interface.out.packets
+ network.interface.out.errors
+ network.interface.out.drops
+ network.interface.out.fifo
+ network.interface.out.carrier
+ network.interface.collisions
+ network.interface.in.bytes
+ network.interface.in.packets
+ network.interface.in.errors
+ network.interface.in.drops
+ network.interface.in.fifo
+ network.interface.total.mcasts
+
diff --git a/src/pmlogconf/tools/localdefs b/src/pmlogconf/tools/localdefs
new file mode 100644
index 0000000..9d693e0
--- /dev/null
+++ b/src/pmlogconf/tools/localdefs
@@ -0,0 +1,23 @@
+FILES = \
+ atop \
+ atop-proc \
+ atop-summary \
+ collectl \
+ collectl-summary \
+ dmcache \
+ free \
+ free-summary \
+ iostat \
+ ip \
+ mpstat \
+ numastat \
+ pcp-summary \
+ pmclient \
+ pmclient-summary \
+ pmstat \
+ sar \
+ sar-summary \
+ uptime \
+ vmstat \
+ vmstat-summary \
+# END
diff --git a/src/pmlogconf/tools/mpstat b/src/pmlogconf/tools/mpstat
new file mode 100644
index 0000000..0578021
--- /dev/null
+++ b/src/pmlogconf/tools/mpstat
@@ -0,0 +1,29 @@
+#pmlogconf-setup 2.0
+ident metrics used by the mpstat(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+# -I -------------------------------------------------
+
+ kernel.percpu.interrupts
+
+# -u --------------------------------------------------
+
+ kernel.all.cpu.user # %usr
+ kernel.percpu.cpu.user
+ kernel.all.cpu.nice # %nice
+ kernel.percpu.cpu.nice
+ kernel.all.cpu.sys # %sys
+ kernel.percpu.cpu.sys
+ kernel.all.cpu.wait.total # %iowait
+ kernel.percpu.cpu.wait.total
+ kernel.all.cpu.intr # %irq
+ kernel.percpu.cpu.intr
+ kernel.all.cpu.irq.soft # %soft
+ kernel.percpu.cpu.irq.soft
+ kernel.all.cpu.steal # %steal
+ kernel.percpu.cpu.steal
+ kernel.all.cpu.guest # %guest
+ kernel.percpu.cpu.guest
+ kernel.all.cpu.idle # %idle
+ kernel.percpu.cpu.idle
+
diff --git a/src/pmlogconf/tools/numastat b/src/pmlogconf/tools/numastat
new file mode 100644
index 0000000..b8ddde3
--- /dev/null
+++ b/src/pmlogconf/tools/numastat
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pcp-numastat(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+ mem.numa.alloc
diff --git a/src/pmlogconf/tools/pcp-summary b/src/pmlogconf/tools/pcp-summary
new file mode 100644
index 0000000..240d92b
--- /dev/null
+++ b/src/pmlogconf/tools/pcp-summary
@@ -0,0 +1,17 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pcp(1) command
+force include
+delta once
+ hinv.ncpu
+ hinv.ndisk
+ hinv.nnode
+ hinv.physmem
+ pmda.uname
+ pmcd.numagents
+ pmcd.numclients
+ pmcd.services
+ pmcd.version
+ pmcd.build
+ pmcd.agent.status
+ pmcd.pmlogger
+ pmcd.pmie
diff --git a/src/pmlogconf/tools/pmclient b/src/pmlogconf/tools/pmclient
new file mode 100644
index 0000000..742a50b
--- /dev/null
+++ b/src/pmlogconf/tools/pmclient
@@ -0,0 +1,9 @@
+#pmlogconf-setup 2.0
+ident metrics sampled frequently by the pmclient(1) command
+force available
+delta 5 second
+ kernel.all.load [ 1 15 ]
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.sys
+ mem.freemem
+ disk.all.total
diff --git a/src/pmlogconf/tools/pmclient-summary b/src/pmlogconf/tools/pmclient-summary
new file mode 100644
index 0000000..04fd7f4
--- /dev/null
+++ b/src/pmlogconf/tools/pmclient-summary
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the pmclient(1) command
+force available
+delta once
+ hinv.ncpu
diff --git a/src/pmlogconf/tools/pmstat b/src/pmlogconf/tools/pmstat
new file mode 100644
index 0000000..49ff445
--- /dev/null
+++ b/src/pmlogconf/tools/pmstat
@@ -0,0 +1,17 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pmstat(1) command
+force include
+ kernel.all.load
+ swap.used
+ mem.util.free
+ mem.util.bufmem
+ mem.util.cached
+ swap.in
+ swap.pagesin
+ swap.out
+ swap.pagesout
+ disk.all.blkread
+ disk.all.blkwrite
+ kernel.all.intr
+ kernel.all.pswitch
+ kernel.all.cpu
diff --git a/src/pmlogconf/tools/sar b/src/pmlogconf/tools/sar
new file mode 100644
index 0000000..3518139
--- /dev/null
+++ b/src/pmlogconf/tools/sar
@@ -0,0 +1,64 @@
+#pmlogconf-setup 2.0
+ident metrics used by the sar(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+ disk.all.read
+ disk.all.read_bytes
+ disk.all.total
+ disk.all.total_bytes
+ disk.all.write
+ disk.all.write_bytes
+ disk.dev.avactive
+ #disk.dev.avqsz
+ #disk.dev.avrqsz
+ #disk.dev.await
+ disk.dev.read_bytes
+ #disk.dev.svctm
+ disk.dev.total
+ disk.dev.total_bytes
+ disk.dev.write_bytes
+ kernel.all.cpu.guest
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.nice
+ kernel.all.cpu.steal
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.pswitch
+ kernel.percpu.cpu.guest
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.nice
+ kernel.percpu.cpu.steal
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ mem.vmstat.pgfault
+ mem.vmstat.pgfree
+ mem.vmstat.pgmajfault
+ mem.vmstat.pgpgin
+ mem.vmstat.pgpgout
+ mem.util
+ network.interface.collisions
+ network.interface.in.bytes
+ network.interface.in.drops
+ network.interface.in.errors
+ network.interface.in.fifo
+ network.interface.in.frame
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.carrier
+ network.interface.out.drops
+ network.interface.out.errors
+ network.interface.out.fifo
+ network.interface.out.packets
+ proc.nprocs
+ proc.runq.runnable
+ swap.pagesin
+ swap.pagesout
+ vfs.dentry.count
+ vfs.files.count
+ vfs.inodes.count
diff --git a/src/pmlogconf/tools/sar-summary b/src/pmlogconf/tools/sar-summary
new file mode 100644
index 0000000..3a4af57
--- /dev/null
+++ b/src/pmlogconf/tools/sar-summary
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the sar(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ hinv.ncpu
diff --git a/src/pmlogconf/tools/uptime b/src/pmlogconf/tools/uptime
new file mode 100644
index 0000000..4448a17
--- /dev/null
+++ b/src/pmlogconf/tools/uptime
@@ -0,0 +1,6 @@
+#pmlogconf-setup 2.0
+ident metrics used by the pcp-uptime(1) command
+force include
+ kernel.all.load
+ kernel.all.nusers
+ kernel.all.uptime
diff --git a/src/pmlogconf/tools/vmstat b/src/pmlogconf/tools/vmstat
new file mode 100644
index 0000000..eed11fc
--- /dev/null
+++ b/src/pmlogconf/tools/vmstat
@@ -0,0 +1,90 @@
+#pmlogconf-setup 2.0
+ident metrics used by the vmstat(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+
+# -s --------------------------------------------------
+
+ mem.util.active # K active memory
+ mem.util.inactive # K inactive memory
+ mem.freemem # K free memory
+ mem.util.bufmem # K buffer memory
+ mem.util.cached # K swap cache
+ mem.util.swapTotal # K total swap
+ #- K used swap
+ mem.util.swapFree # K free swap
+ kernel.all.cpu.user # non-nice user cpu ticks
+ kernel.all.cpu.nice # nice user cpu ticks
+ kernel.all.cpu.sys # system cpu ticks
+ kernel.all.cpu.idle # idle cpu ticks
+ kernel.all.cpu.wait.total # IO-wait cpu ticks
+ kernel.all.cpu.irq.hard # IRQ cpu ticks
+ kernel.all.cpu.irq.soft # softirq cpu ticks
+ kernel.all.cpu.steal # stolen cpu ticks
+ mem.vmstat.pgpgin # pages paged in
+ mem.vmstat.pgpgout # pages paged out
+ mem.vmstat.pswpin # pages swapped in
+ mem.vmstat.pswpout # pages swapped out
+ kernel.all.intr # interrupts
+ kernel.all.pswitch # CPU context switches
+ # - boot time
+ kernel.all.sysfork # forks
+
+# -a --------------------------------------------------
+ proc.runq.runnable # r: The number of processes waiting for run time.
+ proc.runq.blocked # b: The number of processes in uninterruptible sleep.
+ mem.vmstat.nr_mapped # swpd: the amount of virtual memory used.
+ mem.util.free # free: the amount of idle memory.
+ mem.util.bufmem # buff: the amount of memory used as buffers.
+ mem.util.cached # cache: the amount of memory used as cache.
+ mem.util.inactive # inact: the amount of inactive memory. (-a option)
+ mem.util.active # active: the amount of active memory. (-a option)
+
+ swap.in # si: Amount of memory swapped in from disk (/s).
+ swap.pagesout # so: Amount of memory swapped to disk (/s).
+ # - bi: Blocks received from a block device (blocks/s).
+ # - bo: Blocks sent to a block device (blocks/s).
+ kernel.all.intr # in: The number of interrupts per second, including the clock.
+ kernel.all.pswitch # cs: The number of context switches per second.
+ kernel.all.cpu.user # us: Time spent running non-kernel code. (user time, including nice time)
+ kernel.all.cpu.sys # sy: Time spent running kernel code. (system time)
+ kernel.all.idletime # id: Time spent idle.
+ kernel.all.cpu.wait.total # wa: Time spent waiting for IO.
+ kernel.all.cpu.steal # st: Time stolen from a virtual machine.
+
+# -d --------------------------------------------------
+
+ disk.partitions.read # total: Total reads completed successfully
+ # - merged: grouped reads (resulting in one I/O)
+ disk.partitions.blkread # sectors: Sectors read successfully
+ # - ms: milliseconds spent reading
+ disk.partitions.write # total: Total writes completed successfully
+ # - merged: grouped writes (resulting in one I/O)
+ disk.partitions.blkwrite # sectors: Sectors written successfully
+ # - ms: milliseconds spent writing
+ # cur: I/O in progress
+ # - s: seconds spent for I/O
+
+# -D --------------------------------------------------
+
+ disk.all.read # total: Total reads completed successfully
+ disk.all.read_merge # merged: grouped reads (resulting in one I/O)
+ disk.all.blkread # sectors: Sectors read successfully
+ disk.all.read_rawactive # ms: milliseconds spent reading
+ disk.all.write # total: Total writes completed successfully
+ disk.all.write_merge # merged: grouped writes (resulting in one I/O)
+ disk.all.blkwrite # sectors: Sectors written successfully
+ disk.all.read_rawactive # ms: milliseconds spent writing
+ # cur: I/O in progress
+ disk.all.avactive # s: seconds spent for I/O
+
+ disk.dev.read # total: Total reads completed successfully
+ disk.dev.read_merge # merged: grouped reads (resulting in one I/O)
+ disk.dev.blkread # sectors: Sectors read successfully
+ disk.dev.read_rawactive # ms: milliseconds spent reading
+ disk.dev.write # total: Total writes completed successfully
+ disk.dev.write_merge # merged: grouped writes (resulting in one I/O)
+ disk.dev.blkwrite # sectors: Sectors written successfully
+ disk.dev.read_rawactive # ms: milliseconds spent writing
+ # cur: I/O in progress
+ disk.dev.avactive # s: seconds spent for I/O
+
diff --git a/src/pmlogconf/tools/vmstat-summary b/src/pmlogconf/tools/vmstat-summary
new file mode 100644
index 0000000..d23c5bd
--- /dev/null
+++ b/src/pmlogconf/tools/vmstat-summary
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident metrics sampled once by the vmstat(1) command
+probe kernel.uname.sysname ~ Linux ? include : exclude
+delta once
+ mem.physmem # K total memory
diff --git a/src/pmlogconf/v1.0/C2 b/src/pmlogconf/v1.0/C2
new file mode 100644
index 0000000..14916be
--- /dev/null
+++ b/src/pmlogconf/v1.0/C2
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident contributions to CPU wait time
+force available
+ kernel.all.wait
diff --git a/src/pmlogconf/v1.0/C3 b/src/pmlogconf/v1.0/C3
new file mode 100644
index 0000000..ea791de
--- /dev/null
+++ b/src/pmlogconf/v1.0/C3
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident per CPU contributions to wait time
+force available
+ kernel.percpu.wait
diff --git a/src/pmlogconf/v1.0/D3 b/src/pmlogconf/v1.0/D3
new file mode 100644
index 0000000..7a36c55
--- /dev/null
+++ b/src/pmlogconf/v1.0/D3
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident all available data per disk spindle
+force available
+ disk.dev
diff --git a/src/pmlogconf/v1.0/K0 b/src/pmlogconf/v1.0/K0
new file mode 100644
index 0000000..ad9bcd6
--- /dev/null
+++ b/src/pmlogconf/v1.0/K0
@@ -0,0 +1,5 @@
+#pmlogconf-setup 2.0
+ident load average and number of logins
+force include
+ kernel.all.load
+ kernel.all.users
diff --git a/src/pmlogconf/v1.0/S0 b/src/pmlogconf/v1.0/S0
new file mode 100644
index 0000000..a125cdd
--- /dev/null
+++ b/src/pmlogconf/v1.0/S0
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS2 stats
+force available
+ nfs
diff --git a/src/pmlogconf/v1.0/S1 b/src/pmlogconf/v1.0/S1
new file mode 100644
index 0000000..f8b93a2
--- /dev/null
+++ b/src/pmlogconf/v1.0/S1
@@ -0,0 +1,4 @@
+#pmlogconf-setup 2.0
+ident NFS3 stats
+force available
+ nfs3
diff --git a/src/pmlogconf/v1.0/localdefs b/src/pmlogconf/v1.0/localdefs
new file mode 100644
index 0000000..7af03ff
--- /dev/null
+++ b/src/pmlogconf/v1.0/localdefs
@@ -0,0 +1 @@
+FILES = C2 C3 D3 K0 S0 S1
diff --git a/src/pmlogextract/GNUmakefile b/src/pmlogextract/GNUmakefile
new file mode 100644
index 0000000..74ea997
--- /dev/null
+++ b/src/pmlogextract/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmlogextract.c logio.c error.c metriclist.c
+HFILES = logger.h
+LFILES = lex.l
+YFILES = gram.y
+
+CMDTARGET = pmlogextract$(EXECSUFFIX)
+
+LLDLIBS = $(PCPLIB) $(LIB_FOR_MATH) $(LIB_FOR_PTHREADS)
+LDIRT = $(YFILES:%.y=%.tab.?)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+pmlogextract: $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -S $(PCP_BIN_DIR)/$(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+.NOTPARALLEL:
+gram.tab.h gram.tab.c: gram.y
+
+lex.o gram.tab.o: gram.tab.h
+
+default_pcp: default
+
+install_pcp: install
+
+gram.tab.o: logger.h
+lex.o: logger.h
+metriclist.o: logger.h
+pmlogextract.o: logger.h
diff --git a/src/pmlogextract/error.c b/src/pmlogextract/error.c
new file mode 100644
index 0000000..7ecdb8a
--- /dev/null
+++ b/src/pmlogextract/error.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+extern char *configfile;
+extern int lineno;
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n%s\n", configfile, lineno, s);
+}
+
+void
+yyerror(char *s)
+{
+ fprintf(stderr, "Specification error in configuration file (%s)\n",
+ configfile);
+ fprintf(stderr, "[line %d] %s\n", lineno, s);
+ exit(1);
+}
diff --git a/src/pmlogextract/gram.y b/src/pmlogextract/gram.y
new file mode 100644
index 0000000..66330a9
--- /dev/null
+++ b/src/pmlogextract/gram.y
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+/*
+ * pmlogextract parser
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int i;
+int found;
+int argcount; /* number of arguments in config file */
+char *arglist[24]; /* arguments from config file */
+char emess[240];
+
+static char *name;
+static int sts;
+static int numinst; /* num instances (per metric) in config file */
+static int *intlist; /* instance id's (internal list) */
+static char **extlist; /* instance names (external list) */
+static int warn = 1;
+
+extern int lineno;
+
+static void buildinst(int *, int **, char ***, int , char *);
+static void freeinst(int *, int *, char **);
+
+%}
+%union {
+ long lval;
+ char * str;
+}
+
+%token LSQB
+ RSQB
+ COMMA
+
+%token<str> NAME STRING
+%token<lval> NUMBER
+%%
+
+config : somemetrics
+ ;
+
+somemetrics : metriclist
+ | /* nothing */
+ ;
+
+metriclist : metricspec
+ | metriclist metricspec
+ | metriclist COMMA metricspec
+ ;
+
+metricspec : NAME { name = strdup($1); numinst = 0; } optinst
+ {
+ if (name == NULL) {
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+ }
+ found = 0;
+ for (i=0; i<inarchnum; i++) {
+ if ((sts = pmUseContext(inarch[i].ctx)) < 0) {
+ fprintf(stderr,
+ "%s: Error: cannot use context (%d) "
+ "from archive \"%s\"\n",
+ pmProgname, inarch[i].ctx, inarch[i].name);
+ exit(1);
+ }
+
+ if ((sts = pmTraversePMNS (name, dometric)) >= 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ snprintf(emess, sizeof(emess),
+ "Problem with lookup for metric \"%s\" ... "
+ "metric ignored", name);
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ }
+
+ free(name);
+ freeinst(&numinst, intlist, extlist);
+ }
+ ;
+
+optinst : LSQB instancelist RSQB
+ | /* nothing */
+ ;
+
+instancelist : instance
+ | instance instancelist
+ | instance COMMA instancelist
+ ;
+
+instance : NAME { buildinst(&numinst, &intlist, &extlist, -1, $1); }
+ | NUMBER{ buildinst(&numinst, &intlist, &extlist, $1, NULL);}
+ | STRING{ buildinst(&numinst, &intlist, &extlist, -1, $1);}
+ ;
+
+%%
+
+void
+dometric(const char *name)
+{
+ int i;
+ int j;
+ int inst;
+ int skip;
+ int sts;
+ pmID pmid;
+ pmDesc *dp;
+
+ if ((dp = (pmDesc *)malloc(sizeof(pmDesc))) == NULL) {
+ goto nomem;
+ }
+
+ /* Cast away const, pmLookUpName should not modify name
+ */
+ if ((sts = pmLookupName(1,(char **)&name,&pmid)) < 0 || pmid == PM_ID_NULL){
+ snprintf(emess, sizeof(emess), "Metric \"%s\" is unknown ... not logged", name);
+ goto defer;
+ }
+
+ if ((sts = pmLookupDesc(pmid, dp)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Description unavailable for metric \"%s\" ... not logged", name);
+ goto defer;
+ }
+
+
+ ml_numpmid++;
+ if (ml_size < ml_numpmid) {
+ ml_size = ml_numpmid;
+ ml = (mlist_t *) realloc(ml, ml_size * sizeof(mlist_t));
+ if (ml == NULL) {
+ goto nomem;
+ }
+ }
+
+ ml[ml_numpmid-1].name = NULL;
+ ml[ml_numpmid-1].idesc = NULL;
+ ml[ml_numpmid-1].odesc = NULL;
+ ml[ml_numpmid-1].numinst = 0;
+ ml[ml_numpmid-1].instlist = NULL;
+
+
+ /*
+ * ml_nmpmid-1 == index of latest addition to the list
+ */
+
+ ml[ml_numpmid-1].name = strdup(name);
+ if (ml[ml_numpmid-1].name == NULL) {
+ goto nomem;
+ }
+
+ /* input descriptor (idesc) and output descriptor (odesc) are initially
+ * pointed at the same descriptor
+ */
+ ml[ml_numpmid-1].idesc = dp;
+ ml[ml_numpmid-1].odesc = dp;
+ ml[ml_numpmid-1].numinst = numinst;
+
+ skip = 0;
+ if (numinst == 0) {
+ intlist = NULL;
+ extlist = NULL;
+ /* user hasn't specified any instances
+ * - if there is NO instance domain, then allocate at least one
+ * - if there is an instance domain, then need to get them all
+ */
+ if (dp->indom == PM_INDOM_NULL) {
+ ml[ml_numpmid-1].numinst = 1;
+ }
+ else {
+ if ((sts = pmGetInDomArchive(dp->indom, &intlist, &extlist)) < 0) {
+ if (sts == PM_ERR_INDOM_LOG) {
+ /*
+ * If instance domain is not in archive, then there
+ * are no instances, this is not a fatal error
+ */
+ ml[ml_numpmid-1].numinst = 0;
+ ml[ml_numpmid-1].instlist = NULL;
+ }
+ else {
+ snprintf(emess, sizeof(emess),
+ "Cannot get instance domain for metric %s - %s)\n",
+ name, pmErrStr(sts));
+ yyerror(emess);
+ }
+ }
+ else
+ ml[ml_numpmid-1].numinst = sts;
+ }
+
+ if (ml[ml_numpmid-1].numinst >= 1) {
+ /*
+ * malloc here, and keep ... gets buried
+ */
+ ml[ml_numpmid-1].instlist = (int *)malloc(ml[ml_numpmid-1].numinst * sizeof(int));
+ if (ml[ml_numpmid-1].instlist == NULL) {
+ goto nomem;
+ }
+
+ for (i=0; i<ml[ml_numpmid-1].numinst; i++) {
+ if (intlist == NULL) {
+ /* PM_INDOM_NULL case */
+ ml[ml_numpmid-1].instlist[i] = -1;
+ }
+ else
+ ml[ml_numpmid-1].instlist[i] = intlist[i];
+
+ } /*for(i)*/
+ }
+
+ if (intlist != NULL)
+ free(intlist);
+ if (extlist != NULL)
+ free(extlist);
+
+ intlist = NULL;
+ extlist = NULL;
+ }
+ else if (numinst) {
+ /*
+ * malloc here, and keep ... gets buried
+ */
+ ml[ml_numpmid-1].instlist = (int *)malloc(numinst * sizeof(int));
+ if (ml[ml_numpmid-1].instlist == NULL) {
+ goto nomem;
+ }
+
+ j = 0;
+ for (i=0; i<numinst; i++) {
+ inst = -1;
+ if (extlist[i] != NULL) {
+ if ((sts = pmLookupInDomArchive(dp->indom, extlist[i])) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%s\" is not defined for the metric \"%s\"",
+ extlist[i], name);
+ yywarn(emess);
+ ml[ml_numpmid-1].numinst--;
+ continue;
+ }
+ inst = sts;
+ }
+ else {
+ char *p;
+ if ((sts = pmNameInDomArchive(dp->indom, intlist[i], &p)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%d\" is not defined for the metric \"%s\"",
+ intlist[i], name);
+ yywarn(emess);
+ ml[ml_numpmid-1].numinst--;
+ continue;
+ }
+ else {
+ inst = intlist[i];
+ }
+ free(p);
+ }
+
+ /* if inst is > -1 then this instance exists, and its id is `inst'
+ */
+ if (inst > -1) {
+ ml[ml_numpmid-1].instlist[j] = inst;
+ ++j;
+ }
+ } /* for(i) */
+
+ if (ml[ml_numpmid-1].numinst == 0)
+ skip = 1;
+
+ }
+
+
+ /* if skip has been set, then this metric has no instances
+ * (probably because all instances specified by user are invalid)
+ * then we don't want this metric, so ...
+ * - free dp (the descriptor)
+ * - free the instance list
+ * - adjust ml_numpmid ... do not free the space ... it isn't much
+ * and we may need it (if there is another metric)
+ */
+ if (skip) {
+ free(dp);
+ free(ml[ml_numpmid-1].instlist);
+ --ml_numpmid;
+ }
+ else {
+ /* EXCEPTION PCP 2.1.1 - may want to check instances here
+ */
+ }
+ return;
+
+defer:
+ /* EXCEPTION PCP 2.1.1 - defer this one until sometime later ... */
+ if (warn) {
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ }
+ free(dp);
+ return;
+
+nomem:
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+}
+
+
+static void
+buildinst(int *numinst, int **intlist, char ***extlist, int intid, char *extid)
+{
+ char **el;
+ int *il;
+ int num = *numinst;
+
+ if (num == 0) {
+ il = NULL;
+ el = NULL;
+ }
+ else {
+ il = *intlist;
+ el = *extlist;
+ }
+
+ el = (char **)realloc(el, (num+1)*sizeof(el[0]));
+ il = (int *)realloc(il, (num+1)*sizeof(il[0]));
+
+ il[num] = intid;
+
+ if (extid == NULL)
+ el[num] = NULL;
+ else {
+ if (*extid == '"') {
+ char *p;
+ p = ++extid;
+ while (*p && *p != '"') p++;
+ *p = '\0';
+ }
+ el[num] = strdup(extid);
+ }
+
+ *numinst = ++num;
+ *intlist = il;
+ *extlist = el;
+}
+
+
+static void
+freeinst(int *numinst, int *intlist, char **extlist)
+{
+ int i;
+
+ if (*numinst) {
+ free(intlist);
+ for (i = 0; i < *numinst; i++)
+ free(extlist[i]);
+ free(extlist);
+
+ intlist = NULL;
+ extlist = NULL;
+ *numinst = 0;
+ }
+}
+
diff --git a/src/pmlogextract/lex.l b/src/pmlogextract/lex.l
new file mode 100644
index 0000000..c0de4c7
--- /dev/null
+++ b/src/pmlogextract/lex.l
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+%{
+/*
+ * pmlogextract configfile lexer
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int lineno = 1;
+
+#include "gram.tab.h"
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#else
+#undef input
+#define input() ((yytchar=fgetc(yyin)) == EOF ? 0 : yytchar)
+#undef unput
+#define unput(c) {yytchar=(c); ungetc(yytchar, yyin);}
+#endif
+%}
+
+%%
+"[" { return LSQB; }
+"]" { return RSQB; }
+"," { return COMMA; }
+
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = yytext; return NAME; }
+
+\"[^\"\n][^\"\n]*\" { yylval.str = yytext; return STRING; }
+
+[0-9]+ { yylval.lval = atol(yytext); return NUMBER; }
+
+\#.* { }
+
+[ \t\r]+ { }
+
+\n { lineno++; }
+. {
+ sprintf(emess, "Unexpected character '%c'",
+ yytext[0]);
+ yyerror(emess);
+ }
+%%
+
+int
+yywrap(void)
+{
+ return(1);
+}
diff --git a/src/pmlogextract/logger.h b/src/pmlogextract/logger.h
new file mode 100644
index 0000000..ca5f350
--- /dev/null
+++ b/src/pmlogextract/logger.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * common data structures for pmlogextract
+ */
+
+#ifndef _LOGGER_H
+#define _LOGGER_H
+
+#include "pmapi.h"
+
+/*
+ * list of pdu's to write out at start of time window
+ */
+typedef struct _reclist_t {
+ __pmPDU *pdu; /* PDU ptr */
+ __pmTimeval stamp; /* for indom records */
+ pmDesc desc;
+ int written; /* written status */
+ struct _reclist_t *ptr; /* ptr to record in another reclist */
+ struct _reclist_t *next; /* ptr to next reclist_t record */
+} reclist_t;
+
+/*
+ * Input archive control
+ */
+typedef struct {
+ int ctx;
+ char *name;
+ pmLogLabel label;
+ __pmPDU *pb[2];
+ pmResult *_result;
+ pmResult *_Nresult;
+ int eof[2];
+ int mark; /* need EOL marker */
+} inarch_t;
+
+extern inarch_t *inarch; /* input archive control(s) */
+extern int inarchnum; /* number of input archives */
+
+/*
+ * metric [instance] list
+ */
+typedef struct {
+ char *name; /* metric name */
+ /* normally idesc and odesc will point to the same descriptor ...
+ * however, if the "-t" flag is specified, then in the case of
+ * counters and instantaneous values, odesc will be different
+ */
+ pmDesc *idesc; /* input metric descriptor - pmid, pmindom */
+ pmDesc *odesc; /* output metric descriptor - pmid, pmindom */
+ int numinst; /* number of instances (0 means all) */
+ int *instlist; /* instance ids */
+} mlist_t;
+
+
+/*
+ * pmResult list
+ */
+typedef struct __rlist_t {
+ pmResult *res; /* ptr to pmResult */
+ struct __rlist_t *next; /* ptr to next element in list */
+} rlist_t;
+
+
+extern int ml_numpmid; /* num pmid in ml list */
+extern int ml_size; /* actual size of ml array */
+extern mlist_t *ml; /* list of pmids with indoms */
+extern rlist_t *rl; /* list of pmResults */
+
+extern int ilog;
+
+
+/* config file parser states */
+#define GLOBAL 0
+#define INSPEC 1
+
+/* generic error message buffer */
+extern char emess[];
+
+/* yylex() gets intput from here ... */
+extern FILE *fconfig;
+extern FILE *yyin;
+
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern int yylex(void);
+extern int yyparse(void);
+extern void dometric(const char *);
+
+/* log I/O helper routines */
+extern int _pmLogGet(__pmLogCtl *, int, __pmPDU **);
+extern int _pmLogPut(FILE *, __pmPDU *);
+extern pmUnits ntoh_pmUnits(pmUnits);
+#define ntoh_pmInDom(indom) ntohl(indom)
+#define ntoh_pmID(pmid) ntohl(pmid)
+
+/* internal routines */
+extern void insertresult(rlist_t **, pmResult *);
+extern pmResult *searchmlist(pmResult *);
+
+
+#endif /* _LOGGER_H */
diff --git a/src/pmlogextract/logio.c b/src/pmlogextract/logio.c
new file mode 100644
index 0000000..59461eb
--- /dev/null
+++ b/src/pmlogextract/logio.c
@@ -0,0 +1,202 @@
+/*
+ * utils for pmlogextract
+ *
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+
+/*
+ * raw read of next log record - largely stolen from __pmLogRead in libpcp
+ */
+int
+_pmLogGet(__pmLogCtl *lcp, int vol, __pmPDU **pb)
+{
+ int head;
+ int tail;
+ int sts;
+ long offset;
+ char *p;
+ __pmPDU *lpb;
+ FILE *f;
+
+ if (vol == PM_LOG_VOL_META)
+ f = lcp->l_mdfp;
+ else
+ f = lcp->l_mfp;
+
+ offset = ftell(f);
+ assert(offset >= 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "_pmLogGet: fd=%d vol=%d posn=%ld ",
+ fileno(f), vol, offset);
+ }
+#endif
+
+again:
+ sts = (int)fread(&head, 1, sizeof(head), f);
+ if (sts != sizeof(head)) {
+ if (sts == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "AFTER end\n");
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (vol != PM_LOG_VOL_META) {
+ if (lcp->l_curvol < lcp->l_maxvol) {
+ if (__pmLogChangeVol(lcp, lcp->l_curvol+1) == 0) {
+ f = lcp->l_mfp;
+ goto again;
+ }
+ }
+ }
+ return PM_ERR_EOL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: hdr fread=%d %s\n", sts, osstrerror());
+#endif
+ if (sts > 0)
+ return PM_ERR_LOGREC;
+ else
+ return -oserror();
+ }
+
+ if ((lpb = (__pmPDU *)malloc(ntohl(head))) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: _pmLogGet:(%d) %s\n",
+ (int)ntohl(head), osstrerror());
+#endif
+ fseek(f, offset, SEEK_SET);
+ return -oserror();
+ }
+
+ lpb[0] = head;
+ if ((sts = (int)fread(&lpb[1], 1, ntohl(head) - sizeof(head), f)) != ntohl(head) - sizeof(head)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: data fread=%d %s\n", sts, osstrerror());
+#endif
+ if (sts == 0) {
+ fseek(f, offset, SEEK_SET);
+ free(lpb);
+ return PM_ERR_EOL;
+ }
+ else if (sts > 0) {
+ free(lpb);
+ return PM_ERR_LOGREC;
+ }
+ else {
+ free(lpb);
+ return -oserror();
+ }
+ }
+
+
+ p = (char *)lpb;
+ memcpy(&tail, &p[ntohl(head) - sizeof(head)], sizeof(head));
+ if (head != tail) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: head-tail mismatch (%d-%d)\n",
+ (int)ntohl(head), (int)ntohl(tail));
+#endif
+ free(lpb);
+ return PM_ERR_LOGREC;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, "@");
+ if (sts >= 0) {
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ else
+ fprintf(stderr, "unknown time");
+ }
+ fprintf(stderr, " len=%d (incl head+tail)\n", (int)ntohl(head));
+ }
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int i, j;
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ fprintf(stderr, "_pmLogGet");
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, " timestamp=");
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ fprintf(stderr, " " PRINTF_P_PFX "%p ... " PRINTF_P_PFX "%p", lpb, &lpb[ntohl(head)/sizeof(__pmPDU) - 1]);
+ fputc('\n', stderr);
+ fprintf(stderr, "%03d: ", 0);
+ for (j = 0, i = 0; j < ntohl(head)/sizeof(__pmPDU); j++) {
+ if (i == 8) {
+ fprintf(stderr, "\n%03d: ", j);
+ i = 0;
+ }
+ fprintf(stderr, "0x%x ", lpb[j]);
+ i++;
+ }
+ fputc('\n', stderr);
+ }
+#endif
+
+ *pb = lpb;
+ return 0;
+}
+
+int
+_pmLogPut(FILE *f, __pmPDU *pb)
+{
+ int rlen = ntohl(pb[0]);
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "_pmLogPut: fd=%d rlen=%d\n",
+ fileno(f), rlen);
+ }
+#endif
+
+ if ((sts = (int)fwrite(pb, 1, rlen, f)) != rlen) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "_pmLogPut: fwrite=%d %s\n", sts, osstrerror());
+#endif
+ return -oserror();
+ }
+ return 0;
+}
+
+pmUnits
+ntoh_pmUnits(pmUnits units)
+{
+ unsigned int x;
+
+ x = ntohl(*(unsigned int *)&units);
+ units = *(pmUnits *)&x;
+ return units;
+}
diff --git a/src/pmlogextract/metriclist.c b/src/pmlogextract/metriclist.c
new file mode 100644
index 0000000..e640137
--- /dev/null
+++ b/src/pmlogextract/metriclist.c
@@ -0,0 +1,311 @@
+/*
+ * metriclist.c
+ *
+ * Copyright (c) 1997,2005 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+/*
+ * extract the pmid in vsetp and all its instances, and put it in
+ * a pmResult of its own
+ */
+void
+extractpmid(pmValueSet *vsetp, struct timeval *timestamp, pmResult **resp)
+{
+ int i;
+ int size;
+ pmResult *result;
+ pmValueBlock *vbp; /* value block pointer */
+
+
+ result = (pmResult *)malloc(sizeof(pmResult));
+ if (result == NULL) {
+ fprintf(stderr, "%s: Error: cannot malloc space in \"extractpmid\".\n",
+ pmProgname);
+ exit(1);
+ }
+
+ size = sizeof(pmValueSet) + (vsetp->numval-1) * sizeof(pmValue);
+ result->vset[0] = (pmValueSet *)malloc(size);
+ if (result->vset[0] == NULL) {
+ fprintf(stderr, "%s: Error: cannot malloc space in \"extractpmid\".\n",
+ pmProgname);
+ exit(1);
+ }
+
+
+ result->timestamp.tv_sec = timestamp->tv_sec;
+ result->timestamp.tv_usec = timestamp->tv_usec;
+ result->numpmid = 1;
+ result->vset[0]->pmid = vsetp->pmid;
+ result->vset[0]->numval = vsetp->numval;
+ result->vset[0]->valfmt = vsetp->valfmt;
+
+
+ for(i=0; i<vsetp->numval; i++) {
+ result->vset[0]->vlist[i].inst = vsetp->vlist[i].inst;
+ if (vsetp->valfmt == PM_VAL_INSITU)
+ result->vset[0]->vlist[i].value = vsetp->vlist[i].value;
+ else {
+ vbp = vsetp->vlist[i].value.pval;
+
+ size = (int)vbp->vlen;
+ result->vset[0]->vlist[i].value.pval = (pmValueBlock *)malloc(size);
+ if (result->vset[0]->vlist[i].value.pval == NULL) {
+ fprintf(stderr,
+ "%s: Error: cannot malloc space in \"extractpmid\".\n",
+ pmProgname);
+ exit(1);
+ }
+
+ result->vset[0]->vlist[i].value.pval->vtype = vbp->vtype;
+ result->vset[0]->vlist[i].value.pval->vlen = vbp->vlen;
+
+ /* in a pmValueBlock, the first byte is assigned to vtype,
+ * and the subsequent 3 bytes are assigned to vlen - that's
+ * a total of 4 bytes - the rest is used for vbuf
+ */
+ if (vbp->vlen < 4) {
+ fprintf(stderr, "%s: Warning: pmValueBlock vlen (%u) is too small\n", pmProgname, vbp->vlen);
+ }
+ memcpy(result->vset[0]->vlist[i].value.pval->vbuf,
+ vbp->vbuf, vbp->vlen-4);
+ }
+ } /*for(i)*/
+
+ *resp = result;
+}
+
+rlist_t *
+mk_rlist_t(void)
+{
+ rlist_t *rlist;
+ if ((rlist = (rlist_t *)malloc(sizeof(rlist_t))) == NULL) {
+ fprintf(stderr, "%s: Error: cannot malloc space in \"mk_rlist_t\"\n",
+ pmProgname);
+ exit(1);
+ }
+ rlist->res = NULL;
+ rlist->next = NULL;
+ return(rlist);
+}
+
+
+/*
+ * insert rlist element in rlist list
+ */
+void
+insertrlist(rlist_t **rlist, rlist_t *elm)
+{
+ rlist_t *curr;
+ rlist_t *prev;
+
+ if (elm == NULL)
+ return;
+
+ elm->next = NULL;
+
+ if (*rlist == NULL) {
+ *rlist = elm;
+ return;
+ }
+
+ if (elm->res->timestamp.tv_sec < (*rlist)->res->timestamp.tv_sec ||
+ (elm->res->timestamp.tv_sec == (*rlist)->res->timestamp.tv_sec &&
+ elm->res->timestamp.tv_usec <= (*rlist)->res->timestamp.tv_usec)) {
+ curr = *rlist;
+ *rlist = elm;
+ (*rlist)->next = curr;
+ return;
+ }
+
+ curr = (*rlist)->next;
+ prev = *rlist;
+
+ while (curr != NULL) {
+ if (elm->res->timestamp.tv_sec < curr->res->timestamp.tv_sec ||
+ (elm->res->timestamp.tv_sec == curr->res->timestamp.tv_sec &&
+ elm->res->timestamp.tv_usec <= curr->res->timestamp.tv_usec)) {
+ break;
+ }
+ prev = curr;
+ curr = prev->next;
+ }
+
+ prev->next = elm;
+ elm->next = curr;
+}
+
+
+/*
+ * insert pmResult in rlist list
+ */
+void
+insertresult(rlist_t **rlist, pmResult *result)
+{
+ rlist_t *elm;
+
+ elm = mk_rlist_t();
+ elm->res = result;
+ elm->next = NULL;
+
+ insertrlist (rlist, elm);
+}
+
+/*
+ * Find out whether the metrics in _result are in the metric list ml
+ */
+pmResult *
+searchmlist(pmResult *_Oresult)
+{
+ int i;
+ int j;
+ int k;
+ int q;
+ int r;
+ int found = 0;
+ int maxinst = 0; /* max number of instances */
+ int numpmid = 0;
+ int *ilist;
+ int *jlist;
+ pmResult *_Nresult;
+ pmValue *vlistp = NULL; /* temporary list of instances */
+ pmValueSet *vsetp; /* value set pointer */
+
+ ilist = (int *) malloc(_Oresult->numpmid * sizeof(int));
+ if (ilist == NULL)
+ goto nomem;
+
+ jlist = (int *) malloc(_Oresult->numpmid * sizeof(int));
+ if (jlist == NULL)
+ goto nomem;
+
+ /* find out how many of the pmid's in _Oresult need to be written out
+ * (also, find out the maximum number of instances to write out)
+ */
+ numpmid = 0;
+ maxinst = 0;
+ for (i=0; i<_Oresult->numpmid; i++) {
+ vsetp = _Oresult->vset[i];
+
+ for (j=0; j<ml_numpmid; j++) {
+ if (vsetp->pmid == ml[j].idesc->pmid) {
+ /* pmid has been found in metric list
+ */
+ if (ml[j].numinst > maxinst)
+ maxinst = ml[j].numinst;
+
+ ++numpmid;
+ ilist[numpmid-1] = i; /* _Oresult index */
+ jlist[numpmid-1] = j; /* ml list index */
+ break;
+ }
+ }
+ }
+
+
+ /* if no matches (no pmid's are ready for writing), then return
+ */
+ if (numpmid == 0) {
+ free(ilist);
+ free(jlist);
+ return(NULL);
+ }
+
+
+ /* `numpmid' matches were found (some or all pmid's are ready for writing),
+ * then allocate space for new result
+ */
+ _Nresult = (pmResult *) malloc(sizeof(pmResult) +
+ (numpmid - 1) * sizeof(pmValueSet *));
+ if (_Nresult == NULL)
+ goto nomem;
+
+ _Nresult->timestamp.tv_sec = _Oresult->timestamp.tv_sec;
+ _Nresult->timestamp.tv_usec = _Oresult->timestamp.tv_usec;
+ _Nresult->numpmid = numpmid;
+
+
+ /* make array for indeces into vlist
+ */
+ if (maxinst > 0) {
+ vlistp = (pmValue *) malloc(maxinst * sizeof(pmValue));
+ if (vlistp == NULL)
+ goto nomem;
+ }
+
+
+ /* point _Nresult at the right pmValueSet(s)
+ */
+ for (k=0; k<numpmid; k++) {
+ i = ilist[k];
+ j = jlist[k];
+
+ /* point new result at the wanted pmid
+ */
+ _Nresult->vset[k] = _Oresult->vset[i];
+
+
+ /* allocate the right instances
+ */
+ vsetp = _Nresult->vset[k];
+
+ found = 0;
+ for (q=0; q<ml[j].numinst; q++) {
+ for (r=0; r<vsetp->numval; r++) {
+
+ /* if id in ml is -1, chances are that we haven't seen
+ * it before ... set the instance id
+ */
+ if (ml[j].instlist[q] < 0)
+ ml[j].instlist[q] = vsetp->vlist[r].inst;
+
+ if (ml[j].instlist[q] == vsetp->vlist[r].inst) {
+ /* instance has been found
+ */
+ vlistp[found].inst = vsetp->vlist[r].inst;
+ vlistp[found].value = vsetp->vlist[r].value;
+ ++found;
+ break;
+ } /*if*/
+ } /*for(r)*/
+ } /*for(q)*/
+
+
+ /* note: found may be <= ml[j].numinst
+ * further more, found may be zero ... deal with this later?
+ * - NUMVAL
+ */
+ vsetp->numval = found;
+
+ for (q=0; q<vsetp->numval; q++) {
+ vsetp->vlist[q].inst = vlistp[q].inst;
+ vsetp->vlist[q].value = vlistp[q].value;
+ vlistp[q].inst = 0;
+ vlistp[q].value.lval = 0;
+ } /*for(q)*/
+ } /*for(k)*/
+
+ free(ilist);
+ free(jlist);
+ if (maxinst > 0) free(vlistp); /* free only if space was allocated */
+ return(_Nresult);
+
+nomem:
+ fprintf(stderr, "%s: Error: cannot malloc space in \"searchmlist\".\n",
+ pmProgname);
+ exit(1);
+}
diff --git a/src/pmlogextract/pmlogextract.c b/src/pmlogextract/pmlogextract.c
new file mode 100644
index 0000000..7c20292
--- /dev/null
+++ b/src/pmlogextract/pmlogextract.c
@@ -0,0 +1,2000 @@
+/*
+ * pmlogextract - extract desired metrics from PCP archive logs
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+#ifdef PCP_DEBUG
+long totalmalloc;
+#endif
+static pmUnits nullunits;
+static int desperate;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ { "config", 1, 'c', "FILE", "file to load configuration from" },
+ { "desperate", 0, 'd', 0, "desperate, save output after fatal error" },
+ { "first", 0, 'f', 0, "use timezone from first archive [default is last]" },
+ PMOPT_START,
+ { "samples", 1, 's', "NUM", "terminate after NUM log records have been written" },
+ PMOPT_FINISH,
+ { "", 1, 'v', "SAMPLES", "switch log volumes after this many samples" },
+ { "", 0, 'w', 0, "ignore day/month/year" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "c:D:dfS:s:T:v:wZ:z?",
+ .long_options = longopts,
+ .short_usage = "[options] input-archive output-archive",
+};
+
+const char *
+metricname(pmID pmid)
+{
+ static char *name = NULL;
+ if (name != NULL) {
+ free(name);
+ name = NULL;
+ }
+ if (pmNameID(pmid, &name) == 0)
+ return(name);
+ name = NULL;
+ return pmIDStr(pmid);
+}
+
+/*
+ * global constants
+ */
+#define LOG 0
+#define META 1
+#define LOG_META 2
+#define NUM_SEC_PER_DAY 86400
+
+#define NOT_WRITTEN 0
+#define MARK_FOR_WRITE 1
+#define WRITTEN 2
+
+/*
+ * reclist_t is in logger.h
+ * (list of pdu's to write out at start of time window)
+ */
+
+/*
+ * Input archive control is in logger.h
+ */
+
+
+/*
+ * PDU for pmResult (PDU_RESULT)
+ */
+typedef struct {
+ pmID pmid;
+ int numval; /* no. of vlist els to follow, or err */
+ int valfmt; /* insitu or pointer */
+ __pmValue_PDU vlist[1]; /* zero or more */
+} vlist_t;
+
+/*
+ * This struct is not needed?
+ */
+typedef struct {
+ /* __pmPDUHdr hdr; */
+ __pmPDU hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* no. of PMIDs to follow */
+ __pmPDU data[1]; /* zero or more */
+} result_t;
+
+/*
+ * Mark record
+ */
+typedef struct {
+ __pmPDU len;
+ __pmPDU type;
+ __pmPDU from;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* zero PMIDs to follow */
+} mark_t;
+
+
+/*
+ * Global variables
+ */
+static int exit_status = 0;
+static int inarchvers = PM_LOG_VERS02; /* version of input archive */
+static int outarchvers = PM_LOG_VERS02; /* version of output archive */
+static int first_datarec = 1; /* first record flag */
+static int pre_startwin = 1; /* outside time win flag */
+static int written = 0; /* num log writes so far */
+int ml_numpmid = 0; /* num pmid in ml list */
+int ml_size = 0; /* actual size of ml array */
+mlist_t *ml = NULL; /* list of pmids with indoms */
+rlist_t *rl = NULL; /* list of pmResults */
+
+
+off_t new_log_offset; /* new log offset */
+off_t new_meta_offset; /* new meta offset */
+off_t old_log_offset; /* old log offset */
+off_t old_meta_offset; /* old meta offset */
+static off_t flushsize = 100000; /* bytes before flush */
+
+
+/* archive control stuff */
+char *outarchname = NULL; /* name of output archive */
+static __pmHashCtl mdesc_hash; /* pmids that have been written */
+static __pmHashCtl mindom_hash; /* indoms that have been written */
+static __pmLogCtl logctl; /* output archive control */
+inarch_t *inarch; /* input archive control(s) */
+int inarchnum; /* number of input archives */
+
+int ilog; /* index of earliest log */
+
+static reclist_t *rlog; /* log records to be written */
+static reclist_t *rdesc; /* meta desc records to be written */
+static reclist_t *rindom; /* meta indom records to be written */
+
+static __pmTimeval curlog; /* most recent timestamp in log */
+static __pmTimeval current; /* most recent timestamp overall */
+
+/* time window stuff */
+static struct timeval logstart_tval = {0,0}; /* extracted log start */
+static struct timeval logend_tval = {0,0}; /* extracted log end */
+static struct timeval winstart_tval = {0,0}; /* window start tval*/
+static struct timeval winend_tval = {0,0}; /* window end tval*/
+
+static __pmTimeval winstart = {-1,0}; /* window start time */
+static __pmTimeval winend = {-1,0}; /* window end time */
+static __pmTimeval logend = {-1,0}; /* log end time */
+
+/* command line args */
+char *configfile = NULL; /* -c arg - name of config file */
+int farg = 0; /* -f arg - use first timezone */
+int sarg = -1; /* -s arg - finish after X samples */
+char *Sarg = NULL; /* -S arg - window start */
+char *Targ = NULL; /* -T arg - window end */
+int varg = -1; /* -v arg - switch log vol every X */
+int warg = 0; /* -w arg - ignore day/month/year */
+int zarg = 0; /* -z arg - use archive timezone */
+char *tz = NULL; /* -Z arg - use timezone from user */
+
+/* cmd line args that could exist, but don't (needed for pmParseTimeWin) */
+char *Aarg = NULL; /* -A arg - non-existent */
+char *Oarg = NULL; /* -O arg - non-existent */
+
+/*--- START FUNCTIONS -------------------------------------------------------*/
+
+/*
+ * return -1, 0 or 1 as the __pmTimeval's compare
+ * a < b, a == b or a > b
+ */
+static int
+tvcmp(__pmTimeval a, __pmTimeval b)
+{
+ if (a.tv_sec < b.tv_sec)
+ return -1;
+ if (a.tv_sec > b.tv_sec)
+ return 1;
+ if (a.tv_usec < b.tv_usec)
+ return -1;
+ if (a.tv_usec > b.tv_usec)
+ return 1;
+ return 0;
+}
+
+static void
+abandon()
+{
+ char fname[MAXNAMELEN];
+ if (desperate == 0) {
+ fprintf(stderr, "Archive \"%s\" not created.\n", outarchname);
+ while (logctl.l_curvol >= 0) {
+ snprintf(fname, sizeof(fname), "%s.%d", outarchname, logctl.l_curvol);
+ unlink(fname);
+ logctl.l_curvol--;
+ }
+ snprintf(fname, sizeof(fname), "%s.meta", outarchname);
+ unlink(fname);
+ snprintf(fname, sizeof(fname), "%s.index", outarchname);
+ unlink(fname);
+ }
+ exit(1);
+}
+
+
+/*
+ * report that archive is corrupted
+ */
+static void
+_report(FILE *fp)
+{
+ off_t here;
+ struct stat sbuf;
+
+ here = lseek(fileno(fp), 0L, SEEK_CUR);
+ fprintf(stderr, "%s: Error occurred at byte offset %ld into a file of",
+ pmProgname, (long int)here);
+ if (fstat(fileno(fp), &sbuf) < 0)
+ fprintf(stderr, ": stat: %s\n", osstrerror());
+ else
+ fprintf(stderr, " %ld bytes.\n", (long int)sbuf.st_size);
+ fprintf(stderr, "The last record, and the remainder of this file will not be extracted.\n");
+ abandon();
+}
+
+
+/*
+ * switch output volumes
+ */
+static void
+newvolume(char *base, __pmTimeval *tvp)
+{
+ FILE *newfp;
+ int nextvol = logctl.l_curvol + 1;
+
+ if ((newfp = __pmLogNewFile(base, nextvol)) != NULL) {
+ struct timeval stamp;
+ fclose(logctl.l_mfp);
+ logctl.l_mfp = newfp;
+ logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ fflush(logctl.l_mfp);
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ fprintf(stderr, "%s: New log volume %d, at ", pmProgname, nextvol);
+ __pmPrintStamp(stderr, &stamp);
+ fputc('\n', stderr);
+ }
+ else {
+ fprintf(stderr, "%s: Error: volume %d: %s\n",
+ pmProgname, nextvol, pmErrStr(-oserror()));
+ abandon();
+ }
+ flushsize = 100000;
+}
+
+
+/*
+ * construct new external label, and check label records from
+ * input archives
+ */
+static void
+newlabel(void)
+{
+ int i;
+ inarch_t *iap;
+ __pmLogLabel *lp = &logctl.l_label;
+
+ /* set outarch to inarch[0] to start off with */
+ iap = &inarch[0];
+
+ /* check version number */
+ inarchvers = iap->label.ll_magic & 0xff;
+ outarchvers = inarchvers;
+
+ if (inarchvers != PM_LOG_VERS02) {
+ fprintf(stderr,"%s: Error: illegal version number %d in archive (%s)\n",
+ pmProgname, inarchvers, iap->name);
+ abandon();
+ }
+
+ /* copy magic number, pid, host and timezone */
+ lp->ill_magic = iap->label.ll_magic;
+ lp->ill_pid = (int)getpid();
+ strncpy(lp->ill_hostname, iap->label.ll_hostname, PM_LOG_MAXHOSTLEN);
+ lp->ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0';
+ if (farg) {
+ /*
+ * use timezone from first archive ... this is the OLD default
+ */
+ strcpy(lp->ill_tz, iap->label.ll_tz);
+ }
+ else {
+ /*
+ * use timezone from last archive ... this is the NEW default
+ */
+ strcpy(lp->ill_tz, inarch[inarchnum-1].label.ll_tz);
+ }
+
+ /* reset outarch as appropriate, depending on other input archives */
+ for (i=0; i<inarchnum; i++) {
+ iap = &inarch[i];
+
+ /* Ensure all archives of the same version number */
+ if ((iap->label.ll_magic & 0xff) != inarchvers) {
+ fprintf(stderr,
+ "%s: Error: input archives with different version numbers\n"
+ "archive: %s version: %d\n"
+ "archive: %s version: %d\n",
+ pmProgname, inarch[0].name, inarchvers,
+ iap->name, (iap->label.ll_magic & 0xff));
+ abandon();
+ }
+
+ /* Ensure all archives of the same host */
+ if (strcmp(lp->ill_hostname, iap->label.ll_hostname) != 0) {
+ fprintf(stderr,"%s: Error: host name mismatch for input archives\n",
+ pmProgname);
+ fprintf(stderr, "archive: %s host: %s\n",
+ inarch[0].name, inarch[0].label.ll_hostname);
+ fprintf(stderr, "archive: %s host: %s\n",
+ iap->name, iap->label.ll_hostname);
+ abandon();
+ }
+
+ /* Ensure all archives of the same timezone */
+ if (strcmp(lp->ill_tz, iap->label.ll_tz) != 0) {
+ fprintf(stderr,
+ "%s: Warning: timezone mismatch for input archives\n",
+ pmProgname);
+ if (farg) {
+ fprintf(stderr, "archive: %s timezone: %s [will be used]\n",
+ inarch[0].name, lp->ill_tz);
+ fprintf(stderr, "archive: %s timezone: %s [will be ignored]\n",
+ iap->name, iap->label.ll_tz);
+ }
+ else {
+ fprintf(stderr, "archive: %s timezone: %s [will be used]\n",
+ inarch[inarchnum-1].name, lp->ill_tz);
+ fprintf(stderr, "archive: %s timezone: %s [will be ignored]\n",
+ iap->name, iap->label.ll_tz);
+ }
+ }
+ } /*for(i)*/
+}
+
+
+/*
+ *
+ */
+void
+writelabel_metati(int do_rewind)
+{
+ if (do_rewind) rewind(logctl.l_tifp);
+ logctl.l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
+
+ if (do_rewind) rewind(logctl.l_mdfp);
+ logctl.l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
+}
+
+
+/*
+ *
+ */
+void
+writelabel_data(void)
+{
+ logctl.l_label.ill_vol = 0;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+}
+
+
+/* --- Start of reclist functions --- */
+
+/*
+ * make a reclist_t record
+ */
+static reclist_t *
+mk_reclist_t(void)
+{
+ reclist_t *rec;
+
+ if ((rec = (reclist_t *)malloc(sizeof(reclist_t))) == NULL) {
+ fprintf(stderr, "%s: Error: cannot malloc space for record list.\n",
+ pmProgname);
+ abandon();
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ totalmalloc += sizeof(reclist_t);
+ fprintf(stderr, "mk_reclist_t: allocated %d\n", (int)sizeof(reclist_t));
+ }
+#endif
+ rec->pdu = NULL;
+ rec->desc.pmid = PM_ID_NULL;
+ rec->desc.type = PM_TYPE_NOSUPPORT;
+ rec->desc.indom = PM_IN_NULL;
+ rec->desc.sem = 0;
+ rec->desc.units = nullunits; /* struct assignment */
+ rec->written = NOT_WRITTEN;
+ rec->ptr = NULL;
+ rec->next = NULL;
+ return(rec);
+}
+
+/*
+ * find indom in indomreclist - if it isn't in the list then add it in
+ * with no pdu buffer
+ */
+static reclist_t *
+findnadd_indomreclist(int indom)
+{
+ reclist_t *curr;
+
+ if (rindom == NULL) {
+ rindom = mk_reclist_t();
+ rindom->desc.pmid = PM_ID_NULL;
+ rindom->desc.type = PM_TYPE_NOSUPPORT;
+ rindom->desc.indom = indom;
+ rindom->desc.sem = 0;
+ rindom->desc.units = nullunits; /* struct assignment */
+ return(rindom);
+ }
+ else {
+ curr = rindom;
+
+ /* find matching record or last record */
+ while (curr->next != NULL && curr->desc.indom != indom)
+ curr = curr->next;
+
+ if (curr->desc.indom == indom) {
+ /* we have found a matching record - return the pointer */
+ return(curr);
+ }
+ else {
+ /* we have not found a matching record - append new record */
+ curr->next = mk_reclist_t();
+ curr = curr->next;
+ curr->desc.pmid = PM_ID_NULL;
+ curr->desc.type = PM_TYPE_NOSUPPORT;
+ curr->desc.indom = indom;
+ curr->desc.sem = 0;
+ curr->desc.units = nullunits; /* struct assignment */
+ return(curr);
+ }
+ }
+
+}
+
+/*
+ * append a new record to the log record list
+ */
+void
+append_logreclist(int i)
+{
+ inarch_t *iap;
+ reclist_t *curr;
+
+ iap = &inarch[i];
+
+ if (rlog == NULL) {
+ rlog = mk_reclist_t();
+ rlog->pdu = iap->pb[LOG];
+ }
+ else {
+ curr = rlog;
+
+ /* find matching record or last record */
+ while (curr->next != NULL &&
+ curr->pdu[4] != iap->pb[LOG][4]) curr = curr->next;
+
+ if (curr->pdu[4] == iap->pb[LOG][4]) {
+ /* LOG: discard old record; insert new record */
+ __pmUnpinPDUBuf(curr->pdu);
+ curr->pdu = iap->pb[LOG];
+ }
+ else {
+ curr->next = mk_reclist_t();
+ curr = curr->next;
+ curr->pdu = iap->pb[LOG];
+ }
+ } /*else*/
+
+ iap->pb[LOG] = NULL;
+}
+
+/*
+ * append a new record to the desc meta record list if not seen
+ * before, else check the desc meta record is semantically the
+ * same as the last desc meta record for this pmid from this source
+ */
+void
+update_descreclist(int i)
+{
+ inarch_t *iap;
+ reclist_t *curr;
+ pmUnits pmu;
+ pmUnits *pmup;
+
+ iap = &inarch[i];
+
+ if (rdesc == NULL) {
+ /* first time */
+ curr = rdesc = mk_reclist_t();
+ }
+ else {
+ curr = rdesc;
+ /* find matching record or last record */
+ while (curr->next != NULL && curr->desc.pmid != ntoh_pmID(iap->pb[META][2]))
+ curr = curr->next;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, "update_descreclist: pmid: last/match %s", metricname(curr->desc.pmid));
+ fprintf(stderr, " new %s", metricname(ntoh_pmID(iap->pb[META][2])));
+ fputc('\n', stderr);
+ }
+#endif
+ }
+
+ if (curr->desc.pmid == ntoh_pmID(iap->pb[META][2])) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr, " type: old %s", pmTypeStr(curr->desc.type));
+ fprintf(stderr, " new %s", pmTypeStr(ntohl(iap->pb[META][3])));
+ fprintf(stderr, " indom: old %s", pmInDomStr(curr->desc.indom));
+ fprintf(stderr, " new %s", pmInDomStr(ntoh_pmInDom(iap->pb[META][4])));
+ fprintf(stderr, " sem: old %d", curr->desc.sem);
+ fprintf(stderr, " new %d", (int)ntohl(iap->pb[META][5]));
+ fprintf(stderr, " units: old %s", pmUnitsStr(&curr->desc.units));
+ pmup = (pmUnits *)&iap->pb[META][6];
+ pmu = ntoh_pmUnits(*pmup);
+ fprintf(stderr, " new %s", pmUnitsStr(&pmu));
+ fputc('\n', stderr);
+ }
+#endif
+ if (curr->desc.type != ntohl(iap->pb[META][3])) {
+ fprintf(stderr,
+ "%s: Error: metric %s: type changed from",
+ pmProgname, metricname(curr->desc.pmid));
+ fprintf(stderr, " %s", pmTypeStr(curr->desc.type));
+ fprintf(stderr, " to %s!\n", pmTypeStr(ntohl(iap->pb[META][3])));
+ abandon();
+ }
+ if (curr->desc.indom != ntoh_pmInDom(iap->pb[META][4])) {
+ fprintf(stderr,
+ "%s: Error: metric %s: indom changed from",
+ pmProgname, metricname(curr->desc.pmid));
+ fprintf(stderr, " %s", pmInDomStr(curr->desc.indom));
+ fprintf(stderr, " to %s!\n", pmInDomStr(ntoh_pmInDom(iap->pb[META][4])));
+ abandon();
+ }
+ if (curr->desc.sem != ntohl(iap->pb[META][5])) {
+ fprintf(stderr,
+ "%s: Error: metric %s: semantics changed from",
+ pmProgname, metricname(curr->desc.pmid));
+ fprintf(stderr, " %d", curr->desc.sem);
+ fprintf(stderr, " to %d!\n", (int)ntohl(iap->pb[META][5]));
+ abandon();
+ }
+ pmup = (pmUnits *)&iap->pb[META][6];
+ pmu = ntoh_pmUnits(*pmup);
+ if (curr->desc.units.dimSpace != pmu.dimSpace ||
+ curr->desc.units.dimTime != pmu.dimTime ||
+ curr->desc.units.dimCount != pmu.dimCount ||
+ curr->desc.units.scaleSpace != pmu.scaleSpace ||
+ curr->desc.units.scaleTime != pmu.scaleTime ||
+ curr->desc.units.scaleCount != pmu.scaleCount) {
+ fprintf(stderr,
+ "%s: Error: metric %s: units changed from",
+ pmProgname, metricname(curr->desc.pmid));
+ fprintf(stderr, " %s", pmUnitsStr(&curr->desc.units));
+ fprintf(stderr, " to %s!\n", pmUnitsStr(&pmu));
+ abandon();
+ }
+ /* not adding, so META: discard new record */
+ free(iap->pb[META]);
+ iap->pb[META] = NULL;
+ }
+ else {
+ /* append new record */
+ curr->next = mk_reclist_t();
+ curr = curr->next;
+ curr->pdu = iap->pb[META];
+ curr->desc.pmid = ntoh_pmID(iap->pb[META][2]);
+ curr->desc.type = ntohl(iap->pb[META][3]);
+ curr->desc.indom = ntoh_pmInDom(iap->pb[META][4]);
+ curr->desc.sem = ntohl(iap->pb[META][5]);
+ pmup =(pmUnits *)&iap->pb[META][6];
+ curr->desc.units = ntoh_pmUnits(*pmup);
+ curr->ptr = findnadd_indomreclist(curr->desc.indom);
+ iap->pb[META] = NULL;
+ }
+}
+
+/*
+ * append a new record to the indom meta record list
+ */
+void
+append_indomreclist(int i)
+{
+ inarch_t *iap;
+ reclist_t *curr;
+ reclist_t *rec;
+
+ iap = &inarch[i];
+
+ if (rindom == NULL) {
+ rindom = mk_reclist_t();
+ rindom->pdu = iap->pb[META];
+ rindom->stamp.tv_sec = ntohl(rindom->pdu[2]);
+ rindom->stamp.tv_usec = ntohl(rindom->pdu[3]);
+ rindom->desc.pmid = PM_ID_NULL;
+ rindom->desc.type = PM_TYPE_NOSUPPORT;
+ rindom->desc.indom = ntoh_pmInDom(iap->pb[META][4]);
+ rindom->desc.sem = 0;
+ rindom->desc.units = nullunits; /* struct assignment */
+ }
+ else {
+ curr = rindom;
+
+ /* find matching record or last record */
+ while (curr->next != NULL && curr->desc.indom != ntoh_pmInDom(iap->pb[META][4])) {
+ curr = curr->next;
+ }
+
+ if (curr->desc.indom == ntoh_pmInDom(iap->pb[META][4])) {
+ if (curr->pdu == NULL) {
+ /* insert new record */
+ curr->pdu = iap->pb[META];
+ curr->stamp.tv_sec = ntohl(curr->pdu[2]);
+ curr->stamp.tv_usec = ntohl(curr->pdu[3]);
+ }
+ else {
+ /* do NOT discard old record; insert new record */
+ rec = mk_reclist_t();
+ rec->pdu = iap->pb[META];
+ rec->stamp.tv_sec = ntohl(rec->pdu[2]);
+ rec->stamp.tv_usec = ntohl(rec->pdu[3]);
+ rec->desc.pmid = PM_ID_NULL;
+ rec->desc.type = PM_TYPE_NOSUPPORT;
+ rec->desc.indom = ntoh_pmInDom(iap->pb[META][4]);
+ rec->desc.sem = 0;
+ rec->desc.units = nullunits; /* struct assignment */
+ rec->next = curr->next;
+ curr->next = rec;
+ }
+ }
+ else {
+ /* append new record */
+ curr->next = mk_reclist_t();
+ curr = curr->next;
+ curr->pdu = iap->pb[META];
+ curr->stamp.tv_sec = ntohl(curr->pdu[2]);
+ curr->stamp.tv_usec = ntohl(curr->pdu[3]);
+ curr->desc.pmid = PM_ID_NULL;
+ curr->desc.type = PM_TYPE_NOSUPPORT;
+ curr->desc.indom = ntoh_pmInDom(iap->pb[META][4]);
+ curr->desc.sem = 0;
+ curr->desc.units = nullunits; /* struct assignment */
+ }
+ } /*else*/
+
+ iap->pb[META] = NULL;
+}
+
+/*
+ * write out one desc/indom record
+ */
+void
+write_rec(reclist_t *rec)
+{
+ int sts;
+
+ if (rec->written == MARK_FOR_WRITE) {
+ if (rec->pdu == NULL) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr," record is marked for write, but pdu is NULL\n");
+ abandon();
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA) {
+ __pmLogHdr *h;
+ int len;
+ int type;
+ h = (__pmLogHdr *)rec->pdu;
+ len = ntohl(h->len);
+ type = ntohl(h->type);
+ fprintf(stderr, "write_rec: record len=%d, type=%d @ offset=%d\n",
+ len, type, (int)(ftell(logctl.l_mdfp) - sizeof(__pmLogHdr)));
+ if (type == TYPE_DESC) {
+ pmDesc *dp;
+ pmDesc desc;
+ int *namelen;
+ char *name; /* just first name for diag */
+ dp = (pmDesc *)((void *)rec->pdu + sizeof(__pmLogHdr));
+ desc.type = ntohl(dp->type);
+ desc.sem = ntohl(dp->sem);
+ desc.indom = ntoh_pmInDom(dp->indom);
+ desc.units = ntoh_pmUnits(dp->units);
+ desc.pmid = ntoh_pmID(dp->pmid);
+ namelen = (int *)((void *)rec->pdu + sizeof(__pmLogHdr) + sizeof(pmDesc) + sizeof(int));
+ len = ntohl(*namelen);
+ name = (char *)((void *)rec->pdu + sizeof(__pmLogHdr) + sizeof(pmDesc) + sizeof(int) + sizeof(int));
+ fprintf(stderr, "PMID: %s name: %*.*s\n", pmIDStr(desc.pmid), len, len, name);
+ __pmPrintDesc(stderr, &desc);
+ }
+ else if (type == TYPE_INDOM) {
+ __pmTimeval *tvp;
+ __pmTimeval when;
+ int k = 2;
+ pmInDom indom;
+ int numinst;
+ int *instlist;
+ int inst;
+
+ tvp = (__pmTimeval *)&rec->pdu[k];
+ when.tv_sec = ntohl(tvp->tv_sec);
+ when.tv_usec = ntohl(tvp->tv_usec);
+ k += sizeof(__pmTimeval)/sizeof(rec->pdu[0]);
+ indom = ntoh_pmInDom((unsigned int)rec->pdu[k++]);
+ fprintf(stderr, "INDOM: %s when: ", pmInDomStr(indom));
+ __pmPrintTimeval(stderr, &when);
+ numinst = ntohl(rec->pdu[k++]);
+ fprintf(stderr, " numinst: %d", numinst);
+ if (numinst > 0) {
+ int i;
+ instlist = (int *)&rec->pdu[k];
+ for (i = 0; i < numinst; i++) {
+ inst = ntohl(instlist[i]);
+ fprintf(stderr, " [%d] %d", i, inst);
+ }
+ }
+ fputc('\n', stderr);
+ }
+ else {
+ fprintf(stderr, "Botch: bad type\n");
+ }
+ }
+#endif
+
+ /* write out the pdu ; exit if write failed */
+ if ((sts = _pmLogPut(logctl.l_mdfp, rec->pdu)) < 0) {
+ fprintf(stderr, "%s: Error: _pmLogPut: meta data : %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ }
+ /* META: free PDU buffer */
+ free(rec->pdu);
+ rec->pdu = NULL;
+ rec->written = WRITTEN;
+ }
+ else {
+ fprintf(stderr,
+ "%s : Warning: attempting to write out meta record (%d,%d)\n",
+ pmProgname, rec->desc.pmid, rec->desc.indom);
+ fprintf(stderr, " when it is not marked for writing (%d)\n",
+ rec->written);
+ }
+}
+
+void
+write_metareclist(pmResult *result, int *needti)
+{
+ int i;
+ reclist_t *curr_desc; /* current desc record */
+ reclist_t *curr_indom; /* current indom record */
+ reclist_t *othr_indom; /* other indom record */
+ pmID pmid;
+ pmInDom indom;
+ struct timeval *this; /* ptr to timestamp in result */
+
+ this = &result->timestamp;
+
+ /* if pmid in result matches a pmid in desc then write desc
+ */
+ for (i=0; i<result->numpmid; i++) {
+ pmid = result->vset[i]->pmid;
+ indom = PM_IN_NULL;
+ curr_indom = NULL;
+
+ curr_desc = rdesc;
+ while (curr_desc != NULL && curr_desc->desc.pmid != pmid)
+ curr_desc = curr_desc->next;
+
+ if (curr_desc == NULL) {
+ /* descriptor has not been found - this is bad
+ */
+ fprintf(stderr, "%s: Error: meta data (TYPE_DESC) for pmid %s has not been found.\n", pmProgname, pmIDStr(pmid));
+ abandon();
+ }
+ else {
+ /* descriptor has been found
+ */
+ if (curr_desc->written == WRITTEN) {
+ /* descriptor has been written before (no need to write again)
+ * but still need to check indom
+ */
+ indom = curr_desc->desc.indom;
+ curr_indom = curr_desc->ptr;
+ }
+ else if (curr_desc->pdu == NULL) {
+ /* descriptor is in list, has not been written, but no pdu
+ * - this is bad
+ */
+ fprintf(stderr, "%s: Error: missing pdu for pmid %s\n",
+ pmProgname, pmIDStr(pmid));
+ abandon();
+ }
+ else {
+ /* descriptor is in list, has not been written, and has pdu
+ * write!
+ */
+ curr_desc->written = MARK_FOR_WRITE;
+ write_rec(curr_desc);
+ indom = curr_desc->desc.indom;
+ curr_indom = curr_desc->ptr;
+ }
+ }
+
+ /* descriptor has been found and written,
+ * now go and find & write the indom
+ */
+ if (indom != PM_INDOM_NULL) {
+ /* there may be more than one indom in the list, so we need
+ * to traverse the entire list
+ * - we can safely ignore all indoms after the current timestamp
+ * - we want the latest indom at, or before the current timestamp
+ */
+ othr_indom = NULL;
+ while (curr_indom != NULL && curr_indom->desc.indom == indom) {
+ if (curr_indom->stamp.tv_sec < this->tv_sec ||
+ (curr_indom->stamp.tv_sec == this->tv_sec &&
+ curr_indom->stamp.tv_usec <= this->tv_usec))
+ {
+ /* indom is in list, indom has pdu
+ * and timestamp in pdu suits us
+ */
+ if (othr_indom == NULL) {
+ othr_indom = curr_indom;
+ }
+ else if (othr_indom->stamp.tv_sec < curr_indom->stamp.tv_sec ||
+ (othr_indom->stamp.tv_sec == curr_indom->stamp.tv_sec &&
+ othr_indom->stamp.tv_usec <= curr_indom->stamp.tv_usec))
+ {
+ /* we already have a perfectly good indom,
+ * but curr_indom has a better timestamp
+ */
+ othr_indom = curr_indom;
+ }
+ }
+ curr_indom = curr_indom->next;
+ } /*while()*/
+
+ if (othr_indom != NULL && othr_indom->pdu != NULL && othr_indom->written != WRITTEN) {
+ othr_indom->written = MARK_FOR_WRITE;
+ othr_indom->pdu[2] = htonl(this->tv_sec);
+ othr_indom->pdu[3] = htonl(this->tv_usec);
+
+ /* make sure to set needti, when writing out the indom
+ */
+ *needti = 1;
+ write_rec(othr_indom);
+ }
+ }
+ } /*for(i)*/
+}
+
+/* --- End of reclist functions --- */
+
+/*
+ * create a mark record
+ */
+__pmPDU *
+_createmark(void)
+{
+ mark_t *markp;
+
+ /*
+ * add space for trailer in case __pmLogPutResult2() is called with
+ * this PDU buffer
+ */
+ markp = (mark_t *)malloc(sizeof(mark_t)+sizeof(int));
+ if (markp == NULL) {
+ fprintf(stderr, "%s: Error: mark_t malloc: %s\n",
+ pmProgname, osstrerror());
+ abandon();
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ totalmalloc += sizeof(mark_t);
+ fprintf(stderr, "_createmark : allocated %d\n", (int)sizeof(mark_t));
+ }
+#endif
+
+ markp->len = (int)sizeof(mark_t);
+ markp->type = markp->from = 0;
+ markp->timestamp = current;
+ markp->timestamp.tv_usec += 1000; /* + 1msec */
+ if (markp->timestamp.tv_usec > 1000000) {
+ markp->timestamp.tv_usec -= 1000000;
+ markp->timestamp.tv_sec++;
+ }
+ markp->numpmid = 0;
+ return((__pmPDU *)markp);
+}
+
+void
+checklogtime(__pmTimeval *this, int i)
+{
+ if ((curlog.tv_sec == 0 && curlog.tv_usec == 0) ||
+ (curlog.tv_sec > this->tv_sec ||
+ (curlog.tv_sec == this->tv_sec && curlog.tv_usec > this->tv_usec))) {
+ ilog = i;
+ curlog.tv_sec = this->tv_sec;
+ curlog.tv_usec = this->tv_usec;
+ }
+}
+
+
+/*
+ * pick next meta record - if all meta is at EOF return -1
+ * (normally this function returns 0)
+ */
+static int
+nextmeta(void)
+{
+ int i;
+ int j;
+ int want;
+ int numeof = 0;
+ int sts;
+ pmID pmid; /* pmid for TYPE_DESC */
+ pmInDom indom; /* indom for TYPE_INDOM */
+ __pmLogCtl *lcp;
+ __pmContext *ctxp;
+ inarch_t *iap; /* pointer to input archive control */
+
+ for (i=0; i<inarchnum; i++) {
+ iap = &inarch[i];
+
+ /* if at the end of meta file then skip this archive
+ */
+ if (iap->eof[META]) {
+ ++numeof;
+ continue;
+ }
+
+ /* we should never already have a meta record
+ */
+ if (iap->pb[META] != NULL) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr, " iap->pb[META] is not NULL\n");
+ abandon();
+ }
+ if ((ctxp = __pmHandleToPtr(iap->ctx)) == NULL) {
+ fprintf(stderr, "%s: botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, iap->ctx);
+ abandon();
+ }
+ lcp = ctxp->c_archctl->ac_log;
+
+againmeta:
+ /* get next meta record */
+
+ if ((sts = _pmLogGet(lcp, PM_LOG_VOL_META, &iap->pb[META])) < 0) {
+ iap->eof[META] = 1;
+ ++numeof;
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: Error: _pmLogGet[meta %s]: %s\n",
+ pmProgname, iap->name, pmErrStr(sts));
+ _report(lcp->l_mdfp);
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ continue;
+ }
+
+ /* pmDesc entries, if not seen before & wanted,
+ * then append to desc list
+ */
+ if (ntohl(iap->pb[META][1]) == TYPE_DESC) {
+ pmid = ntoh_pmID(iap->pb[META][2]);
+
+ /* if ml is defined, then look for pmid in the list
+ * if pmid is not in the list then discard it immediately
+ */
+ want = 0;
+ if (ml == NULL)
+ want = 1;
+ else {
+ for (j=0; j<ml_numpmid; j++) {
+ if (pmid == ml[j].idesc->pmid)
+ want = 1;
+ }
+ }
+
+ if (want) {
+ if (__pmHashSearch((int)pmid, &mdesc_hash) == NULL)
+ __pmHashAdd((int)pmid, NULL, &mdesc_hash);
+ /*
+ * update the desc list (add first time, check on subsequent
+ * sightings of desc for this pmid from this source
+ * update_descreclist() sets pb[META] to NULL
+ */
+ update_descreclist(i);
+ }
+ else {
+ /* not wanted */
+ free(iap->pb[META]);
+ iap->pb[META] = NULL;
+ goto againmeta;
+ }
+ }
+ else if (ntohl(iap->pb[META][1]) == TYPE_INDOM) {
+ /* if ml is defined, then look for instance domain in the list
+ * if indom is not in the list then discard it immediately
+ */
+ indom = ntoh_pmInDom(iap->pb[META][4]);
+ want = 0;
+ if (ml == NULL)
+ want = 1;
+ else {
+ for (j=0; j<ml_numpmid; j++) {
+ if (indom == ml[j].idesc->indom)
+ want = 1;
+ }
+ }
+
+ if (want) {
+ if (__pmHashSearch((int)indom, &mindom_hash) == NULL) {
+ /* meta record has never been seen ... add it to the list */
+ __pmHashAdd((int)indom, NULL, &mindom_hash);
+ }
+ /* add to indom list */
+ /* append_indomreclist() sets pb[META] to NULL
+ * append_indomreclist() may unpin the pdu buffer
+ */
+ append_indomreclist(i);
+ }
+ else {
+ /* META: don't want this meta */
+ free(iap->pb[META]);
+ iap->pb[META] = NULL;
+ goto againmeta;
+ }
+ }
+ else {
+ fprintf(stderr, "%s: Error: unrecognised meta data type: %d\n",
+ pmProgname, (int)ntohl(iap->pb[META][1]));
+ abandon();
+ }
+
+ PM_UNLOCK(ctxp->c_lock);
+ }
+
+ if (numeof == inarchnum) return(-1);
+ return(0);
+}
+
+
+/*
+ * read in next log record for every archive
+ */
+static int
+nextlog(void)
+{
+ int i;
+ int eoflog = 0; /* number of log files at eof */
+ int sts;
+ __pmTimeval curtime;
+ __pmLogCtl *lcp;
+ __pmContext *ctxp;
+ inarch_t *iap;
+
+
+ for (i=0; i<inarchnum; i++) {
+ iap = &inarch[i];
+
+ /* if at the end of log file then skip this archive
+ */
+ if (iap->eof[LOG]) {
+ ++eoflog;
+ continue;
+ }
+
+ /* if we already have a log record then skip this archive
+ */
+ if (iap->_Nresult != NULL) {
+ continue;
+ }
+
+ /* if mark has been written out, then log is at EOF
+ */
+ if (iap->mark) {
+ iap->eof[LOG] = 1;
+ ++eoflog;
+ continue;
+ }
+
+ if ((ctxp = __pmHandleToPtr(iap->ctx)) == NULL) {
+ fprintf(stderr, "%s: botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, iap->ctx);
+ abandon();
+ }
+ lcp = ctxp->c_archctl->ac_log;
+
+againlog:
+ if ((sts=__pmLogRead(lcp, PM_MODE_FORW, NULL, &iap->_result, PMLOGREAD_NEXT)) < 0) {
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: Error: __pmLogRead[log %s]: %s\n",
+ pmProgname, iap->name, pmErrStr(sts));
+ _report(lcp->l_mfp);
+ }
+ /* if the first data record has not been written out, then
+ * do not generate a mark record, and you may as well ignore
+ * this archive
+ */
+ if (first_datarec) {
+ iap->mark = 1;
+ iap->eof[LOG] = 1;
+ ++eoflog;
+ }
+ else {
+ iap->mark = 1;
+ iap->pb[LOG] = _createmark();
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ continue;
+ }
+ assert(iap->_result != NULL);
+
+
+ /* set current log time - this is only done so that we can
+ * determine whether to keep or discard the log
+ */
+ curtime.tv_sec = iap->_result->timestamp.tv_sec;
+ curtime.tv_usec = iap->_result->timestamp.tv_usec;
+
+ /* if log time is greater than (or equal to) the current window
+ * start time, then we may want it
+ * (irrespective of the current window end time)
+ */
+ if (tvcmp(curtime, winstart) < 0) {
+ /* log is not in time window - discard result and get next record
+ */
+ pmFreeResult(iap->_result);
+ iap->_result = NULL;
+ goto againlog;
+ }
+ else {
+ /* log is within time window - check whether we want this record
+ */
+ if (iap->_result->numpmid == 0) {
+ /* mark record, process this one as is
+ */
+ iap->_Nresult = iap->_result;
+ }
+ else if (ml == NULL) {
+ /* ml is NOT defined, we want everything
+ */
+ iap->_Nresult = iap->_result;
+ }
+ else {
+ /* ml is defined, need to search metric list for wanted pmid's
+ * (searchmlist may return a NULL pointer - this is fine)
+ */
+ iap->_Nresult = searchmlist(iap->_result);
+ }
+
+ if (iap->_Nresult == NULL) {
+ /* dont want any of the metrics in _result, try again
+ */
+ pmFreeResult(iap->_result);
+ iap->_result = NULL;
+ goto againlog;
+ }
+ }
+
+ PM_UNLOCK(ctxp->c_lock);
+ } /*for(i)*/
+
+ /* if we are here, then each archive control struct should either
+ * be at eof, or it should have a _result, or it should have a mark PDU
+ * (if we have a _result, we may want all/some/none of the pmid's in it)
+ */
+
+ if (eoflog == inarchnum) return(-1);
+ return 0;
+}
+
+/*
+ * parse command line arguments
+ */
+int
+parseargs(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ char *endnum;
+ struct stat sbuf;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'c': /* config file */
+ configfile = opts.optarg;
+ if (stat(configfile, &sbuf) < 0) {
+ pmprintf("%s: %s - invalid file\n", pmProgname, configfile);
+ opts.errors++;
+ }
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'd': /* desperate to save output archive, even after error */
+ desperate = 1;
+ break;
+
+ case 'f': /* use timezone from first archive */
+ farg = 1;
+ break;
+
+ case 's': /* number of samples to write out */
+ sarg = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || sarg < 0) {
+ pmprintf("%s: -s requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'S': /* start time for extracting */
+ Sarg = opts.optarg;
+ break;
+
+ case 'T': /* end time for extracting */
+ Targ = opts.optarg;
+ break;
+
+ case 'v': /* number of samples per volume */
+ varg = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || varg < 0) {
+ pmprintf("%s: -v requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'w': /* ignore day/month/year */
+ warg++;
+ break;
+
+ case 'Z': /* use timezone from command line */
+ if (zarg) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ opts.errors++;
+ }
+ tz = opts.optarg;
+ break;
+
+ case 'z': /* use timezone from archive */
+ if (tz != NULL) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ opts.errors++;
+ }
+ zarg++;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (warg) {
+ if (Sarg == NULL || Targ == NULL) {
+ fprintf(stderr, "%s: Warning: -w flag requires that both -S and -T are specified.\nIgnoring -w flag.\n", pmProgname);
+ warg = 0;
+ }
+ }
+
+
+ if (opts.errors == 0 && opts.optind > argc - 2) {
+ pmprintf("%s: Error: insufficient arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ return -opts.errors;
+}
+
+int
+parseconfig(void)
+{
+ int errflag = 0;
+
+ if ((yyin = fopen(configfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+
+ if (yyparse() != 0)
+ exit(1);
+
+ fclose(yyin);
+ yyin = NULL;
+
+ return(-errflag);
+}
+
+/*
+ * we are within time window ... return 0
+ * we are outside of time window & mk new window ... return 1
+ * we are outside of time window & exit ... return -1
+ */
+static int
+checkwinend(__pmTimeval now)
+{
+ int i;
+ int sts;
+ __pmTimeval tmptime;
+ inarch_t *iap;
+ __pmPDU *markpdu; /* mark b/n time windows */
+
+ if (winend.tv_sec < 0 || tvcmp(now, winend) <= 0)
+ return(0);
+
+ /* we have reached the end of a window
+ * - if warg is not set, then we have finished (break)
+ * - otherwise, calculate start and end of next window,
+ * set pre_startwin, discard logs before winstart,
+ * and write out mark
+ */
+ if (!warg)
+ return(-1);
+
+ winstart.tv_sec += NUM_SEC_PER_DAY;
+ winend.tv_sec += NUM_SEC_PER_DAY;
+ pre_startwin = 1;
+
+ /* if start of next window is later than max termination
+ * then bail out here
+ */
+ if (tvcmp(winstart, logend) > 0)
+ return(-1);
+
+ ilog = -1;
+ for (i=0; i<inarchnum; i++) {
+ iap = &inarch[i];
+ if (iap->_Nresult != NULL) {
+ tmptime.tv_sec = iap->_Nresult->timestamp.tv_sec;
+ tmptime.tv_usec = iap->_Nresult->timestamp.tv_usec;
+ if (tvcmp(tmptime, winstart) < 0) {
+ /* free _result and _Nresult
+ */
+ if (iap->_result != iap->_Nresult) {
+ free(iap->_Nresult);
+ }
+ if (iap->_result != NULL) {
+ pmFreeResult(iap->_result);
+ iap->_result = NULL;
+ }
+ iap->_Nresult = NULL;
+ iap->pb[LOG] = NULL;
+ }
+ }
+ if (iap->pb[LOG] != NULL) {
+ tmptime.tv_sec = ntohl(iap->pb[LOG][3]);
+ tmptime.tv_usec = ntohl(iap->pb[LOG][4]);
+ if (tvcmp(tmptime, winstart) < 0) {
+ /* free PDU buffer ... it is probably a mark
+ * and has not been pinned
+ */
+ free(iap->pb[LOG]);
+ iap->pb[LOG] = NULL;
+ }
+ }
+ } /*for(i)*/
+
+ /* must create "mark" record and write it out */
+ /* (need only one mark record) */
+ markpdu = _createmark();
+ if ((sts = __pmLogPutResult2(&logctl, markpdu)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: log data: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ }
+ written++;
+ free(markpdu);
+ return(1);
+}
+
+
+/*
+ *
+ */
+void
+writerlist(rlist_t **rlready, __pmTimeval mintime)
+{
+ int sts;
+ int needti = 0; /* need to flush/update */
+ __pmTimeval titime = {0,0};/* time of last temporal index write */
+ __pmTimeval restime; /* time of result */
+ rlist_t *elm; /* element of rlready to be written out */
+ __pmPDU *pb; /* pdu buffer */
+ unsigned long peek_offset;
+
+ while (*rlready != NULL) {
+ restime.tv_sec = (*rlready)->res->timestamp.tv_sec;
+ restime.tv_usec = (*rlready)->res->timestamp.tv_usec;
+
+ if (tvcmp(restime, mintime) > 0) {
+#if 0
+fprintf(stderr, "writelist: restime %d.%06d mintime %d.%06d ", restime.tv_sec, restime.tv_usec, mintime.tv_sec, mintime.tv_usec);
+fprintf(stderr, " break!\n");
+#endif
+ break;
+ }
+
+ /* get the first element from the list
+ */
+ elm = *rlready;
+ *rlready = elm->next;
+
+ /* if this is the first record (for output archive) then do some
+ * admin stuff
+ */
+ if (first_datarec) {
+ first_datarec = 0;
+ logctl.l_label.ill_start.tv_sec = elm->res->timestamp.tv_sec;
+ logctl.l_label.ill_start.tv_usec = elm->res->timestamp.tv_usec;
+ logctl.l_state = PM_LOG_STATE_INIT;
+ writelabel_data();
+ }
+
+ /* if we are in a pre_startwin state, and we are writing
+ * something out, then we are not in a pre_startwin state any more
+ * (it also means that there may be some discrete metrics to be
+ * written out)
+ */
+ if (pre_startwin)
+ pre_startwin = 0;
+
+
+ /* convert log record to a pdu
+ */
+ sts = __pmEncodeResult(PDU_OVERRIDE2, elm->res, &pb);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Error: __pmEncodeResult: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ }
+
+ /* switch volumes if required */
+ if (varg > 0) {
+ if (written > 0 && (written % varg) == 0) {
+ newvolume(outarchname, (__pmTimeval *)&pb[3]);
+ }
+ }
+ /*
+ * Even without a -v option, we may need to switch volumes
+ * if the data file exceeds 2^31-1 bytes
+ */
+ peek_offset = ftell(logctl.l_mfp);
+ peek_offset += ((__pmPDUHdr *)pb)->len - sizeof(__pmPDUHdr) + 2*sizeof(int);
+ if (peek_offset > 0x7fffffff) {
+ newvolume(outarchname, (__pmTimeval *)&pb[3]);
+ }
+
+ /* write out the descriptor and instance domain pdu's first
+ */
+ write_metareclist(elm->res, &needti);
+
+ /* write out log record */
+ old_log_offset = ftell(logctl.l_mfp);
+ assert(old_log_offset >= 0);
+ if ((sts = __pmLogPutResult2(&logctl, pb)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: log data: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ }
+ written++;
+
+
+ /* check whether we need to write TI (temporal index) */
+ if (old_log_offset == 0 ||
+ old_log_offset == sizeof(__pmLogLabel)+2*sizeof(int) ||
+ ftell(logctl.l_mfp) > flushsize)
+ needti = 1;
+
+ /* make sure that we do not write out the temporal index more
+ * than once for the same timestamp
+ */
+ if (needti && tvcmp(titime, restime) >= 0)
+ needti = 0;
+
+ /* flush/update */
+ if (needti) {
+ titime = restime;
+
+ fflush(logctl.l_mfp);
+ fflush(logctl.l_mdfp);
+
+ if (old_log_offset == 0)
+ old_log_offset = sizeof(__pmLogLabel)+2*sizeof(int);
+
+ new_log_offset = ftell(logctl.l_mfp);
+ assert(new_log_offset >= 0);
+ new_meta_offset = ftell(logctl.l_mdfp);
+ assert(new_meta_offset >= 0);
+
+ fseek(logctl.l_mfp, (long)old_log_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, (long)old_meta_offset, SEEK_SET);
+
+ __pmLogPutIndex(&logctl, &restime);
+
+ fseek(logctl.l_mfp, (long)new_log_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, (long)new_meta_offset, SEEK_SET);
+
+ old_log_offset = ftell(logctl.l_mfp);
+ assert(old_log_offset >= 0);
+ old_meta_offset = ftell(logctl.l_mdfp);
+ assert(old_meta_offset >= 0);
+
+ flushsize = ftell(logctl.l_mfp) + 100000;
+ }
+
+ /* free PDU buffer */
+ __pmUnpinPDUBuf(pb);
+ pb = NULL;
+
+ elm->res = NULL;
+ elm->next = NULL;
+ free(elm);
+
+ } /*while(*rlready)*/
+}
+
+
+/*
+ * mark record has been created and assigned to iap->pb[LOG]
+ * write it out
+ */
+void
+writemark(inarch_t *iap)
+{
+ int sts;
+ mark_t *p = (mark_t *)iap->pb[LOG];
+
+ if (!iap->mark) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr, " writemark called, but mark not set\n");
+ abandon();
+ }
+
+ if (p == NULL) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr, " writemark called, but no pdu\n");
+ abandon();
+ }
+
+ p->timestamp.tv_sec = htonl(p->timestamp.tv_sec);
+ p->timestamp.tv_usec = htonl(p->timestamp.tv_usec);
+
+ if ((sts = __pmLogPutResult2(&logctl, iap->pb[LOG])) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: log data: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ }
+ written++;
+ free(iap->pb[LOG]);
+ iap->pb[LOG] = NULL;
+}
+
+/*--- END FUNCTIONS ---------------------------------------------------------*/
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ int j;
+ int sts;
+ int stslog; /* sts from nextlog() */
+ int stsmeta; /* sts from nextmeta() */
+
+ char *msg;
+
+ __pmTimeval now = {0,0}; /* the current time */
+ __pmTimeval mintime = {0,0};
+ __pmTimeval tmptime = {0,0};
+
+ __pmTimeval tstamp; /* temporary timestamp */
+ inarch_t *iap; /* ptr to archive control */
+ rlist_t *rlready; /* list of results ready for writing */
+ struct timeval unused;
+
+
+ rlog = NULL; /* list of log records to write */
+ rdesc = NULL; /* list of meta desc records to write */
+ rindom = NULL; /* list of meta indom records to write */
+ rlready = NULL;
+
+
+ /* process cmd line args */
+ if (parseargs(argc, argv) < 0) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+
+ /* input archive names are argv[opts.optind] ... argv[argc-2]) */
+ /* output archive name is argv[argc-1]) */
+
+ /* output archive */
+ outarchname = argv[argc-1];
+
+ /* input archive(s) */
+ inarchnum = argc - 1 - opts.optind;
+ inarch = (inarch_t *) malloc(inarchnum * sizeof(inarch_t));
+ if (inarch == NULL) {
+ fprintf(stderr, "%s: Error: mallco inarch: %s\n",
+ pmProgname, osstrerror());
+ exit(1);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ totalmalloc += (inarchnum * sizeof(inarch_t));
+ fprintf(stderr, "main : allocated %d\n",
+ (int)(inarchnum * sizeof(inarch_t)));
+ }
+#endif
+
+
+ for (i=0; i<inarchnum; i++, opts.optind++) {
+ iap = &inarch[i];
+
+ iap->name = argv[opts.optind];
+
+ iap->pb[LOG] = iap->pb[META] = NULL;
+ iap->eof[LOG] = iap->eof[META] = 0;
+ iap->mark = 0;
+ iap->_result = NULL;
+ iap->_Nresult = NULL;
+
+ if ((iap->ctx = pmNewContext(PM_CONTEXT_ARCHIVE, iap->name)) < 0) {
+ fprintf(stderr, "%s: Error: cannot open archive \"%s\": %s\n",
+ pmProgname, iap->name, pmErrStr(iap->ctx));
+ exit(1);
+ }
+
+ if ((sts = pmUseContext(iap->ctx)) < 0) {
+ fprintf(stderr, "%s: Error: cannot use context (%s): %s\n",
+ pmProgname, iap->name, pmErrStr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmGetArchiveLabel(&iap->label)) < 0) {
+ fprintf(stderr, "%s: Error: cannot get archive label record (%s): %s\n", pmProgname, iap->name, pmErrStr(sts));
+ exit(1);
+ }
+
+ if ((sts = pmGetArchiveEnd(&unused)) < 0) {
+ fprintf(stderr, "%s: Error: cannot get end of archive (%s): %s\n",
+ pmProgname, iap->name, pmErrStr(sts));
+ exit(1);
+ }
+
+ if (i == 0) {
+ /* start time */
+ logstart_tval.tv_sec = iap->label.ll_start.tv_sec;
+ logstart_tval.tv_usec = iap->label.ll_start.tv_usec;
+
+ /* end time */
+ logend_tval.tv_sec = unused.tv_sec;
+ logend_tval.tv_usec = unused.tv_usec;
+ }
+ else {
+ /* get the earlier start time */
+ if (logstart_tval.tv_sec > iap->label.ll_start.tv_sec ||
+ (logstart_tval.tv_sec == iap->label.ll_start.tv_sec &&
+ logstart_tval.tv_usec > iap->label.ll_start.tv_usec)) {
+ logstart_tval.tv_sec = iap->label.ll_start.tv_sec;
+ logstart_tval.tv_usec = iap->label.ll_start.tv_usec;
+ }
+
+ /* get the later end time */
+ if (logend_tval.tv_sec < unused.tv_sec ||
+ (logend_tval.tv_sec == unused.tv_sec &&
+ logend_tval.tv_usec < unused.tv_usec)) {
+ logend_tval.tv_sec = unused.tv_sec;
+ logend_tval.tv_usec = unused.tv_usec;
+ }
+ }
+ } /*for(i)*/
+
+ logctl.l_label.ill_start.tv_sec = logstart_tval.tv_sec;
+ logctl.l_label.ill_start.tv_usec = logstart_tval.tv_usec;
+
+ /* process config file
+ * - this includes a list of metrics and their instances
+ */
+ if (configfile && parseconfig() < 0)
+ exit(1);
+
+ if (zarg) {
+ /* use TZ from metrics source (input-archive) */
+ if ((sts = pmNewZone(inarch[0].label.ll_tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set context timezone: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit_status = 1;
+ goto cleanup;
+ }
+ printf("Note: timezone set to local timezone of host \"%s\" from archive\n\n", inarch[0].label.ll_hostname);
+ }
+ else if (tz != NULL) {
+ /* use TZ as specified by user */
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, tz, pmErrStr(sts));
+ exit_status = 1;
+ goto cleanup;
+ }
+ printf("Note: timezone set to \"TZ=%s\"\n\n", tz);
+ }
+ else {
+ char *tz;
+ tz = __pmTimezone();
+ /* use TZ from local host */
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set local host's timezone: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit_status = 1;
+ goto cleanup;
+ }
+ }
+
+
+ /* create output log - must be done before writing label */
+ if ((sts = __pmLogCreate("", outarchname, outarchvers, &logctl)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogCreate: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* This must be done after log is created:
+ * - checks that archive version, host, and timezone are ok
+ * - set archive version, host, and timezone of output archive
+ */
+ newlabel();
+
+ /* write label record */
+ writelabel_metati(0);
+
+
+ /* set winstart and winend timevals */
+ sts = pmParseTimeWindow(Sarg, Targ, Aarg, Oarg,
+ &logstart_tval, &logend_tval,
+ &winstart_tval, &winend_tval, &unused, &msg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Invalid time window specified: %s\n",
+ pmProgname, msg);
+ abandon();
+ }
+ winstart.tv_sec = winstart_tval.tv_sec;
+ winstart.tv_usec = winstart_tval.tv_usec;
+ winend.tv_sec = winend_tval.tv_sec;
+ winend.tv_usec = winend_tval.tv_usec;
+ logend.tv_sec = logend_tval.tv_sec;
+ logend.tv_usec = logend_tval.tv_usec;
+
+ if (warg) {
+ if (winstart.tv_sec + NUM_SEC_PER_DAY < winend.tv_sec) {
+ fprintf(stderr, "%s: Warning: -S and -T must specify a time window within\nthe same day, for -w to be used. Ignoring -w flag.\n", pmProgname);
+ warg = 0;
+ }
+ }
+
+ ilog = -1;
+ written = 0;
+ curlog.tv_sec = 0;
+ curlog.tv_usec = 0;
+ current.tv_sec = 0;
+ current.tv_usec = 0;
+ first_datarec = 1;
+ pre_startwin = 1;
+
+ /* get all meta data first
+ * nextmeta() should return 0 (will return -1 when all meta is eof)
+ */
+ do {
+ stsmeta = nextmeta();
+ } while (stsmeta >= 0);
+
+
+ /* get log record - choose one with earliest timestamp
+ * write out meta data (required by this log record)
+ * write out log
+ * do ti update if necessary
+ */
+ while (sarg == -1 || written < sarg) {
+ ilog = -1;
+ curlog.tv_sec = 0;
+ curlog.tv_usec = 0;
+ old_meta_offset = ftell(logctl.l_mdfp);
+ assert(old_meta_offset >= 0);
+
+ /* nextlog() resets ilog, and curlog (to the smallest timestamp)
+ */
+ stslog = nextlog();
+
+ if (stslog < 0)
+ break;
+
+ /* find the _Nresult (or mark pdu) with the earliest timestamp;
+ * set ilog
+ * (this is a bit more complex when tflag is specified)
+ */
+ mintime.tv_sec = mintime.tv_usec = 0;
+ for (i=0; i<inarchnum; i++) {
+ if (inarch[i]._Nresult != NULL) {
+ tstamp.tv_sec = inarch[i]._Nresult->timestamp.tv_sec;
+ tstamp.tv_usec = inarch[i]._Nresult->timestamp.tv_usec;
+ checklogtime(&tstamp, i);
+
+ if (ilog == i) {
+ tmptime = curlog;
+ if (mintime.tv_sec <= 0 || tvcmp(mintime, tmptime) > 0)
+ mintime = tmptime;
+ }
+ }
+ else if (inarch[i].pb[LOG] != NULL) {
+ tstamp.tv_sec = inarch[i].pb[LOG][3]; /* no swab needed */
+ tstamp.tv_usec = inarch[i].pb[LOG][4]; /* no swab needed */
+ checklogtime(&tstamp, i);
+
+ if (ilog == i) {
+ tmptime = curlog;
+ if (mintime.tv_sec <= 0 || tvcmp(mintime, tmptime) > 0)
+ mintime = tmptime;
+ }
+ }
+ }
+
+ /* now == the earliest timestamp of the archive(s)
+ * and/or mark records
+ * mintime == now or timestamp of the earliest mark
+ * (whichever is smaller)
+ */
+ now = curlog;
+
+ /* note - mark (after last archive) will be created, but this
+ * break, will prevent it from being written out
+ */
+ if (tvcmp(now, logend) > 0)
+ break;
+
+ sts = checkwinend(now);
+ if (sts < 0)
+ break;
+ if (sts > 0)
+ continue;
+
+ current = curlog;
+
+ /* prepare to write out log record
+ */
+ if (ilog < 0 || ilog >= inarchnum) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr, " log file index = %d\n", ilog);
+ abandon();
+ }
+
+
+ iap = &inarch[ilog];
+ if (iap->mark)
+ writemark(iap);
+ else {
+ /* result is to be written out, but there is no _Nresult
+ */
+ if (iap->_Nresult == NULL) {
+ fprintf(stderr, "%s: Fatal Error!\n", pmProgname);
+ fprintf(stderr, " pick == LOG and _Nresult = NULL\n");
+ abandon();
+ }
+ insertresult(&rlready, iap->_Nresult);
+#if 0
+{
+ rlist_t *rp;
+ int i;
+
+ fprintf(stderr, "rlready");
+ for (i = 0, rp = rlready; rp != NULL; i++, rp = rp->next) {
+ fprintf(stderr, " [%d] t=%d.%06d numpmid=%d", i, (int)rp->res->timestamp.tv_sec, (int)rp->res->timestamp.tv_usec, rp->res->numpmid);
+ }
+ fprintf(stderr, " now=%d.%06d\n", now.tv_sec, now.tv_usec);
+}
+#endif
+
+ writerlist(&rlready, curlog);
+
+ /* writerlist frees elm (elements of rlready) but does not
+ * free _result & _Nresult
+ */
+
+ /* free _result & _Nresult
+ * _Nresult may contain space that was allocated
+ * in __pmStuffValue this space has PM_VAL_SPTR format,
+ * and has to be freed first
+ * (in order to avoid memory leaks)
+ */
+ if (iap->_result != iap->_Nresult && iap->_Nresult != NULL) {
+ pmValueSet *vsetp;
+ for (i=0; i<iap->_Nresult->numpmid; i++) {
+ vsetp = iap->_Nresult->vset[i];
+ if (vsetp->valfmt == PM_VAL_SPTR) {
+ for (j=0; j<vsetp->numval; j++) {
+ free(vsetp->vlist[j].value.pval);
+ }
+ }
+ }
+ free(iap->_Nresult);
+ }
+ if (iap->_result != NULL) {
+ pmFreeResult(iap->_result);
+ iap->_result = NULL;
+ }
+ iap->_Nresult = NULL;
+ }
+ } /*while()*/
+
+ if (first_datarec) {
+ fprintf(stderr, "%s: Warning: no qualifying records found.\n",
+ pmProgname);
+cleanup:
+ abandon();
+ }
+ else {
+ /* write the last time stamp */
+ fflush(logctl.l_mfp);
+ fflush(logctl.l_mdfp);
+
+ if (old_log_offset == 0)
+ old_log_offset = sizeof(__pmLogLabel)+2*sizeof(int);
+
+ new_log_offset = ftell(logctl.l_mfp);
+ assert(new_log_offset >= 0);
+ new_meta_offset = ftell(logctl.l_mdfp);
+ assert(new_meta_offset >= 0);
+
+#if 0
+ fprintf(stderr, "*** last tstamp: \n\tmintime=%d.%06d \n\ttmptime=%d.%06d \n\tlogend=%d.%06d \n\twinend=%d.%06d \n\tcurrent=%d.%06d\n",
+ mintime.tv_sec, mintime.tv_usec, tmptime.tv_sec, tmptime.tv_usec, logend.tv_sec, logend.tv_usec, winend.tv_sec, winend.tv_usec, current.tv_sec, current.tv_usec);
+#endif
+
+ fseek(logctl.l_mfp, old_log_offset, SEEK_SET);
+ __pmLogPutIndex(&logctl, &current);
+
+
+ /* need to fix up label with new start-time */
+ writelabel_metati(1);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "main : total allocated %ld\n", totalmalloc);
+ }
+#endif
+
+ exit(exit_status);
+}
diff --git a/src/pmlogger/GNUmakefile b/src/pmlogger/GNUmakefile
new file mode 100644
index 0000000..07fecfe
--- /dev/null
+++ b/src/pmlogger/GNUmakefile
@@ -0,0 +1,69 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = src
+OTHERS = pmnewlog.sh control rc_pmlogger \
+ pmlogger_daily.sh pmlogger_check.sh pmlogger_merge.sh pmlogmv.sh
+LDIRT = crontab pmlogger.service
+
+ifeq ($(TARGET_OS),linux)
+CRONTAB_USER = $(PCP_USER)
+CRONTAB_PATH = $(PCP_ETC_DIR)/cron.d/pcp-pmlogger
+else
+CRONTAB_USER =
+CRONTAB_PATH = $(PCP_SYSCONF_DIR)/pmlogger/crontab
+endif
+
+default:: crontab pmlogger.service
+
+default:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install:: default
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_SYSCONF_DIR)/pmlogger
+ $(INSTALL) -m 664 -o $(PCP_USER) -g $(PCP_GROUP) control $(PCP_PMLOGGERCONTROL_PATH)
+ $(INSTALL) -m 755 pmnewlog.sh $(PCP_BINADM_DIR)/pmnewlog$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_daily.sh $(PCP_BINADM_DIR)/pmlogger_daily$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_check.sh $(PCP_BINADM_DIR)/pmlogger_check$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogger_merge.sh $(PCP_BINADM_DIR)/pmlogger_merge$(SHELLSUFFIX)
+ $(INSTALL) -m 755 pmlogmv.sh $(PCP_BIN_DIR)/pmlogmv$(SHELLSUFFIX)
+ $(INSTALL) -m 755 rc_pmlogger $(PCP_RC_DIR)/pmlogger
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmlogger.service $(PCP_SYSTEMDUNIT_DIR)/pmlogger.service
+endif
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)/pmlogger
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_TMP_DIR)/pmlogger
+ifeq ($(TARGET_OS),linux)
+ $(INSTALL) -m 755 -d `dirname $(CRONTAB_PATH)`
+endif
+ $(INSTALL) -m 644 crontab $(CRONTAB_PATH)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+pmlogger.service : pmlogger.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+crontab : crontab.in
+ $(SED) -e 's;@user@;'$(CRONTAB_USER)';' -e 's;@path@;'$(PCP_BINADM_DIR)';' $< > $@
diff --git a/src/pmlogger/control b/src/pmlogger/control
new file mode 100644
index 0000000..7e25ef8
--- /dev/null
+++ b/src/pmlogger/control
@@ -0,0 +1,52 @@
+#
+# PCP archive logging configuration/control
+#
+# This file is used by various of the PCP archive logging administrative
+# tools to perform maintenance on the pmlogger instances running on
+# the local host.
+#
+# This file contains one line per host to be logged, fields are
+# Host name of host to be logged
+# P(rimary) is this the primary logger? y or n
+# S(ocks) should this logger be launched with pmsocks? y or n
+# Directory full pathname to directory where archive logs are
+# to be maintained ... note all scripts "cd" to here as
+# a first step
+# Args optional additional arguments to pmlogger and/or pmnewlog
+#
+
+# === VARIABLE ASSIGNMENTS ===
+#
+# DO NOT REMOVE OR EDIT THE FOLLOWING LINE
+$version=1.1
+
+# if pmsocks is being used, edit the IP address for $SOCKS_SERVER
+#$SOCKS_SERVER=123.456.789.123
+
+# for remote loggers running over a WAN with potentially long delays
+$PMCD_CONNECT_TIMEOUT=150
+$PMCD_REQUEST_TIMEOUT=120
+
+# === LOGGER CONTROL SPECIFICATIONS ===
+#
+#Host P? S? directory args
+
+# local primary logger
+#
+# (LOCALHOSTNAME is expanded to local: in the first column,
+# and to `hostname` in the fourth (directory) column.)
+#
+LOCALHOSTNAME y n PCP_LOG_DIR/pmlogger/LOCALHOSTNAME -r -T24h10m -c config.default
+
+# Note: if multiple pmloggers for the same host (e.g. both primary and
+# non-primary loggers are active), then they MUST use different
+# directories
+
+# local non-primary logger
+#LOCALHOSTNAME n n PCP_LOG_DIR/pmlogger/mysummary -r -T24h10m -c config.Summary
+
+# remote host
+#remote n n PCP_LOG_DIR/pmlogger/remote -r -T24h10m -c config.remote
+
+# thru the firewall via socks
+#distant n y PCP_LOG_DIR/pmlogger/distant -r -T24h10m -c config.distant
diff --git a/src/pmlogger/crontab.in b/src/pmlogger/crontab.in
new file mode 100644
index 0000000..1427e07
--- /dev/null
+++ b/src/pmlogger/crontab.in
@@ -0,0 +1,8 @@
+#
+# Performance Co-Pilot crontab entries for a monitored site
+# with one or more pmlogger instances running
+#
+# daily processing of archive logs (with compression enabled)
+10 0 * * * @user@ @path@/pmlogger_daily -X xz -x 3
+# every 30 minutes, check pmlogger instances are running
+25,55 * * * * @user@ @path@/pmlogger_check -C
diff --git a/src/pmlogger/pmlogger.service.in b/src/pmlogger/pmlogger.service.in
new file mode 100644
index 0000000..a0e4fe4
--- /dev/null
+++ b/src/pmlogger/pmlogger.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Performance Metrics Archive Logger
+Documentation=man:pmlogger(1)
+After=local-fs.target network.target
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmlogger start
+ExecStop=@path@/pmlogger stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmlogger/pmlogger_check.sh b/src/pmlogger/pmlogger_check.sh
new file mode 100755
index 0000000..a05fd0f
--- /dev/null
+++ b/src/pmlogger/pmlogger_check.sh
@@ -0,0 +1,867 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Administrative script to check pmlogger processes are alive, and restart
+# them as required.
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMLOGGER=pmlogger
+PMLOGCONF="$PCP_BINADM_DIR/pmlogconf"
+
+# error messages should go to stderr, not the GUI notifiers
+unset PCP_STDERR
+
+# constant setup
+#
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+echo >$tmp/lock
+trap "rm -rf \`[ -f $tmp/lock ] && cat $tmp/lock\` $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+# control file for pmlogger administration ... edit the entries in this
+# file to reflect your local configuration
+#
+CONTROL=$PCP_PMLOGGERCONTROL_PATH
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the fourth column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+[ -z "$PWDCMND" ] && PWDCMND=/bin/pwd
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+# default location
+#
+logfile=pmlogger.log
+
+
+# option parsing
+#
+SHOWME=false
+MV=mv
+CP=cp
+KILL=pmsignal
+TERSE=false
+VERBOSE=false
+VERY_VERBOSE=false
+CHECK_RUNLEVEL=false
+START_PMLOGGER=true
+
+echo > $tmp/usage
+cat >> $tmp/usage << EOF
+Options:
+ -c=FILE,--control=FILE configuration of pmlogger instances to manage
+ -C query system service runlevel information
+ -N,--showme perform a dry run, showing what would be done
+ -s,--stop stop pmlogger processes instead of starting them
+ -T,--terse produce a terser form of output
+ -V,--verbose increase diagnostic verbosity
+ --help
+EOF
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -C) CHECK_RUNLEVEL=true
+ ;;
+ -N) SHOWME=true
+ MV="echo + mv"
+ CP="echo + cp"
+ KILL="echo + kill"
+ ;;
+ -s) START_PMLOGGER=false
+ ;;
+ -T) TERSE=true
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 0 ]
+then
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+fi
+
+QUIETLY=false
+if [ $CHECK_RUNLEVEL = true ]
+then
+ # determine whether to start/stop based on runlevel settings - we
+ # need to do this when running unilaterally from cron, else we'll
+ # always start pmlogger up (even when we shouldn't).
+ #
+ QUIETLY=true
+ if is_chkconfig_on pmlogger
+ then
+ START_PMLOGGER=true
+ else
+ START_PMLOGGER=false
+ fi
+fi
+
+if [ $START_PMLOGGER = false ]
+then
+ # if pmlogger has never been started, there's no work to do to stop it
+ [ ! -d "$PCP_TMP_DIR/pmlogger" ] && exit
+ $QUIETLY || $PCP_BINADM_DIR/pmpost "stop pmlogger from $prog"
+fi
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+_error()
+{
+ echo 2>&1 "$prog: [$CONTROL:$line]"
+ echo 2>&1 "Error: $1"
+ echo 2>&1 "... logging for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_warning()
+{
+ echo 2>&1 "$prog [$CONTROL:$line]"
+ echo 2>&1 "Warning: $1"
+}
+
+_message()
+{
+ case $1
+ in
+ restart)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Restarting$iam pmlogger for host \"$host\" ...""$PCP_ECHO_C"
+ ;;
+ esac
+}
+
+_unlock()
+{
+ rm -f lock
+ echo >$tmp/lock
+}
+
+_get_ino()
+{
+ # get inode number for $1
+ # throw away stderr (and return '') in case $1 has been removed by now
+ #
+ stat "$1" 2>/dev/null \
+ | sed -n '/Device:[ ].*[ ]Inode:/{
+s/Device:[ ].*[ ]Inode:[ ]*//
+s/[ ].*//
+p
+}'
+}
+
+_get_configfile()
+{
+ # extract the pmlogger configuration file (-c) from a list of arguments
+ #
+ echo $@ | sed -n \
+ -e 's/^/ /' \
+ -e 's/[ ][ ]*/ /g' \
+ -e 's/-c /-c/' \
+ -e 's/.* -c\([^ ]*\).*/\1/p'
+}
+
+_configure_pmlogger()
+{
+ # update a pmlogger configuration file if it should be created/modified
+ #
+ configfile="$1"
+ hostname="$2"
+
+ if [ -f "$configfile" ]
+ then
+ # look for "magic" string at start of file, and ensure we created it
+ sed 1q "$configfile" | grep '^#pmlogconf [0-9]' >/dev/null
+ magic=$?
+ grep '^# Auto-generated by pmlogconf' "$configfile" >/dev/null
+ owned=$?
+ if [ $magic -eq 0 -a $owned -eq 0 ]
+ then
+ # pmlogconf file that we own, see if re-generation is needed
+ cp "$configfile" $tmp/pmlogger
+ if $PMLOGCONF -c -q -h $hostname $tmp/pmlogger >$tmp/diag 2>&1
+ then
+ if grep 'No changes' $tmp/diag >/dev/null 2>&1
+ then
+ :
+ elif [ -w $configfile ]
+ then
+ $VERBOSE && echo "Reconfigured: \"$configfile\" (pmlogconf)"
+ eval $MV $tmp/pmlogger "$configfile"
+ else
+ _warning "no write access to pmlogconf file \"$configfile\", skip reconfiguration"
+ ls -l "$configfile"
+ fi
+ else
+ _warning "pmlogconf failed to reconfigure \"$configfile\""
+ cat "s;$tmp/pmlogger;$configfile;g" $tmp/diag
+ echo "=== start pmlogconf file ==="
+ cat $tmp/pmlogger
+ echo "=== end pmlogconf file ==="
+ fi
+ fi
+ elif [ ! -e "$configfile" ]
+ then
+ # file does not exist, generate it, if possible
+ if $SHOWME
+ then
+ echo "+ $PMLOGCONF -c -q -h $hostname $configfile"
+ elif ! $PMLOGCONF -c -q -h $hostname "$configfile" >$tmp/diag 2>&1
+ then
+ _warning "pmlogconf failed to generate \"$configfile\""
+ cat $tmp/diag
+ echo "=== start pmlogconf file ==="
+ cat "$configfile"
+ echo "=== end pmlogconf file ==="
+ else
+ chown $PCP_USER:$PCP_GROUP "$configfile" >/dev/null 2>&1
+ fi
+ fi
+}
+
+_get_logfile()
+{
+ # looking for -lLOGFILE or -l LOGFILE in args
+ #
+ want=false
+ for a in $args
+ do
+ if $want
+ then
+ logfile="$a"
+ want=false
+ break
+ fi
+ case "$a"
+ in
+ -l)
+ want=true
+ ;;
+ -l*)
+ logfile=`echo "$a" | sed -e 's/-l//'`
+ break
+ ;;
+ esac
+ done
+}
+
+_check_archive()
+{
+ if [ ! -e "$logfile" ]
+ then
+ echo "$prog: Error: cannot find pmlogger output file at \"$logfile\""
+ if $TERSE
+ then
+ :
+ else
+ logdir=`dirname "$logfile"`
+ echo "Directory (`cd "$logdir"; $PWDCMND`) contents:"
+ LC_TIME=POSIX ls -la "$logdir"
+ fi
+ elif [ -f "$logfile" ]
+ then
+ echo "Contents of pmlogger output file \"$logfile\" ..."
+ cat "$logfile"
+ fi
+}
+
+_check_logger()
+{
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " [process $1] ""$PCP_ECHO_C"
+
+ # wait until pmlogger process starts, or exits
+ #
+ delay=5
+ [ ! -z "$PMCD_CONNECT_TIMEOUT" ] && delay=$PMCD_CONNECT_TIMEOUT
+ x=5
+ [ ! -z "$PMCD_REQUEST_TIMEOUT" ] && x=$PMCD_REQUEST_TIMEOUT
+
+ # wait for maximum time of a connection and 20 requests
+ #
+ delay=`expr \( $delay + 20 \* $x \) \* 10` # tenths of a second
+ while [ $delay -gt 0 ]
+ do
+ if [ -f $logfile ]
+ then
+ # $logfile was previously removed, if it has appeared again
+ # then we know pmlogger has started ... if not just sleep and
+ # try again
+ #
+ if echo "connect $1" | pmlc 2>&1 | grep "Unable to connect" >/dev/null
+ then
+ :
+ else
+ $VERBOSE && echo " done"
+ return 0
+ fi
+
+ _plist=`_get_pids_by_name pmlogger`
+ _found=false
+ for _p in `echo $_plist`
+ do
+ [ $_p -eq $1 ] && _found=true
+ done
+
+ if $_found
+ then
+ # process still here, just not accepting pmlc connections
+ # yet, try again
+ :
+ else
+ $VERBOSE || _message restart
+ echo " process exited!"
+ if $TERSE
+ then
+ :
+ else
+ echo "$prog: Error: failed to restart pmlogger"
+ echo "Current pmlogger processes:"
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS | tee $tmp/tmp | sed -n -e 1p
+ for _p in `echo $_plist`
+ do
+ sed -n -e "/^[ ]*[^ ]* [ ]*$_p /p" < $tmp/tmp
+ done
+ echo
+ fi
+ _check_archive
+ return 1
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ $VERBOSE && [ `expr $delay % 10` -eq 0 ] && \
+ $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ done
+ $VERBOSE || _message restart
+ echo " timed out waiting!"
+ if $TERSE
+ then
+ :
+ else
+ sed -e 's/^/ /' $tmp/out
+ fi
+ _check_archive
+ return 1
+}
+
+# note on control file format version
+# 1.0 was shipped as part of PCPWEB beta, and did not include the
+# socks field [this is the default for backwards compatibility]
+# 1.1 is the first production release, and the version is set in
+# the control file with a $version=1.1 line (see below)
+#
+version=''
+
+echo >$tmp/dir
+rm -f $tmp/err $tmp/pmloggers
+
+line=0
+cat $CONTROL \
+ | sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+ | while read host primary socks dir args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ dir_hostname=`hostname || echo localhost`
+ dir=`echo $dir | sed -e "s;LOCALHOSTNAME;$dir_hostname;"`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ $VERY_VERBOSE && echo "[control:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $primary $socks $dir $args"
+ cmd=`echo "$host $primary $socks $dir $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$version" -o "$version" = "1.0" ]
+ then
+ if [ -z "$version" ]
+ then
+ echo "$prog: Warning: processing default version 1.0 control format"
+ version=1.0
+ fi
+ args="$dir $args"
+ dir="$socks"
+ socks=n
+ fi
+
+ if [ -z "$primary" -o -z "$socks" -o -z "$dir" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ if $VERY_VERBOSE
+ then
+ pflag=''
+ [ $primary = y ] && pflag=' -P'
+ echo "Check pmlogger$pflag -h $host ... in $dir ..."
+ fi
+
+ # check for directory duplicate entries
+ #
+ if [ "`grep $dir $tmp/dir`" = "$dir" ]
+ then
+ _error "Cannot start more than one pmlogger instance for archive directory \"$dir\""
+ continue
+ else
+ echo "$dir" >>$tmp/dir
+ fi
+
+ # make sure output directory exists
+ #
+ if [ ! -d "$dir" ]
+ then
+ mkdir -p -m 755 "$dir" >$tmp/err 2>&1
+ if [ ! -d "$dir" ]
+ then
+ cat $tmp/err
+ _error "cannot create directory ($dir) for PCP archive files"
+ continue
+ else
+ _warning "creating directory ($dir) for PCP archive files"
+ fi
+ chown $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ fi
+
+ cd "$dir"
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ # ensure pcp user will be able to write there
+ #
+ chown -R $PCP_USER:$PCP_GROUP "$dir" >/dev/null 2>&1
+ if [ ! -w "$dir" ]
+ then
+ echo "$prog: Warning: no write access in $dir, skip lock file processing"
+ else
+ # demand mutual exclusion
+ #
+ rm -f $tmp/stamp $tmp/out
+ delay=200 # tenths of a second
+ while [ $delay -gt 0 ]
+ do
+ if pmlock -v lock >>$tmp/out 2>&1
+ then
+ echo $dir/lock >$tmp/lock
+ break
+ else
+ [ -f $tmp/stamp ] || touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ if [ -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: removing lock file older than 30 minutes"
+ LC_TIME=POSIX ls -l $dir/lock
+ rm -f lock
+ else
+ # there is a small timing window here where pmlock
+ # might fail, but the lock file has been removed by
+ # the time we get here, so just keep trying
+ #
+ :
+ fi
+ fi
+ fi
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ done
+
+ if [ $delay -eq 0 ]
+ then
+ # failed to gain mutex lock
+ #
+ # maybe pmlogger_daily is running ... check and silently
+ # move on if this is the case
+ #
+ # Note: $PCP_RUN_DIR may not exist (see pmlogger_daily note), but
+ # only if pmlogger_daily has not run, so no chance of a
+ # collision
+ #
+ if [ -f "$PCP_RUN_DIR"/pmlogger_daily.pid ]
+ then
+ # maybe, check pid matches a running /bin/sh
+ #
+ pid=`cat "$PCP_RUN_DIR"/pmlogger_daily.pid`
+ if _get_pids_by_name sh | grep "^$pid\$" >/dev/null
+ then
+ # seems to be still running ... nothing for us to see
+ # or do here
+ #
+ continue
+ fi
+ fi
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: is another PCP cron job running concurrently?"
+ LC_TIME=POSIX ls -l $dir/lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($dir/lock) ..."
+ continue
+ fi
+ fi
+
+ pid=''
+ if [ "X$primary" = Xy ]
+ then
+ # NB: FQDN cleanup: previously, we used to quietly accept several
+ # putative-aliases in the first (hostname) slot for a primary logger,
+ # which were all supposed to refer to the local host. So now we
+ # squash them all to the officially pcp-preferred way to access it.
+ # This does not get used by pmlogger in the end (gets -P and not -h
+ # in the primary logger case), but it *does* matter for pmlogconf.
+ host=local:
+
+ if test -f "$PCP_TMP_DIR/pmlogger/primary"
+ then
+ if $VERY_VERBOSE
+ then
+ _host=`sed -n 2p <"$PCP_TMP_DIR/pmlogger/primary"`
+ _arch=`sed -n 3p <"$PCP_TMP_DIR/pmlogger/primary"`
+ $PCP_ECHO_PROG $PCP_ECHO_N "... try $PCP_TMP_DIR/pmlogger/primary: host=$_host arch=$_arch""$PCP_ECHO_C"
+ fi
+ primary_inode=`_get_ino $PCP_TMP_DIR/pmlogger/primary`
+ $VERY_VERBOSE && echo primary_inode=$primary_inode
+ for file in $PCP_TMP_DIR/pmlogger/*
+ do
+ case "$file"
+ in
+ */primary|*\*)
+ ;;
+ */[0-9]*)
+ inode=`_get_ino "$file"`
+ $VERY_VERBOSE && echo $file inode=$inode
+ if [ "$primary_inode" = "$inode" ]
+ then
+ pid="`echo $file | sed -e 's/.*\/\([^/]*\)$/\1/'`"
+ break
+ fi
+ ;;
+ esac
+ done
+ if [ -z "$pid" ]
+ then
+ if $VERY_VERBOSE
+ then
+ echo "primary pmlogger process pid not found"
+ ls -l "$PCP_TMP_DIR/pmlogger"
+ fi
+ else
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "primary pmlogger process $pid identified, OK"
+ else
+ $VERY_VERBOSE && echo "primary pmlogger process $pid not running"
+ pid=''
+ fi
+ fi
+ fi
+ else
+ for log in $PCP_TMP_DIR/pmlogger/[0-9]*
+ do
+ [ "$log" = "$PCP_TMP_DIR/pmlogger/[0-9]*" ] && continue
+ if $VERY_VERBOSE
+ then
+ _host=`sed -n 2p <$log`
+ _arch=`sed -n 3p <$log`
+ $PCP_ECHO_PROG $PCP_ECHO_N "... try $log host=$_host arch=$_arch: ""$PCP_ECHO_C"
+ fi
+ # throw away stderr in case $log has been removed by now
+ match=`sed -e '3s/\/[0-9][0-9][0-9][0-9][0-9.]*$//' $log 2>/dev/null \
+ | $PCP_AWK_PROG '
+BEGIN { m = 0 }
+NR == 3 && $0 == "'$dir'" { m = 2; next }
+END { print m }'`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
+ if [ "$match" = 2 ]
+ then
+ pid=`echo $log | sed -e 's,.*/,,'`
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "pmlogger process $pid identified, OK"
+ break
+ fi
+ $VERY_VERBOSE && echo "pmlogger process $pid not running, skip"
+ pid=''
+ else
+ $VERY_VERBOSE && echo "different directory, skip"
+ fi
+ done
+ fi
+
+ if [ -z "$pid" -a $START_PMLOGGER = true ]
+ then
+ rm -f Latest
+
+ if [ "X$primary" = Xy ]
+ then
+ args="-P $args"
+ iam=" primary"
+ # clean up port-map, just in case
+ #
+ PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
+ rm -f "$PM_LOG_PORT_DIR/primary"
+ else
+ args="-h $host $args"
+ iam=""
+ fi
+
+ # each new log started is named yyyymmdd.hh.mm
+ #
+ LOGNAME=`date "+%Y%m%d.%H.%M"`
+
+ # handle duplicates/aliases (happens when pmlogger is restarted
+ # within a minute and LOGNAME is the same)
+ #
+ suff=''
+ for file in $LOGNAME.*
+ do
+ [ "$file" = "$LOGNAME"'.*' ] && continue
+ # we have a clash! ... find a new -number suffix for the
+ # existing files ... we are going to keep $LOGNAME for the
+ # new pmlogger below
+ #
+ if [ -z "$suff" ]
+ then
+ for xx in 0 1 2 3 4 5 6 7 8 9
+ do
+ for yy in 0 1 2 3 4 5 6 7 8 9
+ do
+ [ "`echo $LOGNAME-${xx}${yy}.*`" != "$LOGNAME-${xx}${yy}.*" ] && continue
+ suff=${xx}${yy}
+ break
+ done
+ [ ! -z "$suff" ] && break
+ done
+ if [ -z "$suff" ]
+ then
+ _error "unable to break duplicate clash for archive basename $LOGNAME"
+ fi
+ $VERBOSE && echo "Duplicate archive basename ... rename $LOGNAME.* files to $LOGNAME-$suff.*"
+ fi
+ eval $MV -f $file `echo $file | sed -e "s/$LOGNAME/&-$suff/"`
+ done
+
+ configfile=`_get_configfile $args`
+ if [ ! -z "$configfile" ]
+ then
+ # if this is a relative path and not relative to cwd,
+ # substitute in the default pmlogger search location.
+ #
+ if [ ! -f "$configfile" -a "`basename $configfile`" = "$configfile" ]
+ then
+ configfile="$PCP_SYSCONF_DIR/pmlogger/$configfile"
+ fi
+
+ # check configuration file exists and is up to date
+ _configure_pmlogger "$configfile" "$host"
+ fi
+
+ $VERBOSE && _message restart
+
+ sock_me=''
+ if [ "$socks" = y ]
+ then
+ # only check for pmsocks if it's specified in the control file
+ have_pmsocks=false
+ if which pmsocks >/dev/null 2>&1
+ then
+ # check if pmsocks has been set up correctly
+ if pmsocks ls >/dev/null 2>&1
+ then
+ have_pmsocks=true
+ fi
+ fi
+
+ if $have_pmsocks
+ then
+ sock_me="pmsocks "
+ else
+ echo "$prog: Warning: no pmsocks available, would run without"
+ sock_me=""
+ fi
+ fi
+
+ _get_logfile
+ if [ -f $logfile ]
+ then
+ $VERBOSE && $SHOWME && echo
+ eval $MV -f $logfile $logfile.prior
+ fi
+
+ args="$args -m pmlogger_check"
+ if $SHOWME
+ then
+ echo
+ echo "+ ${sock_me}$PMLOGGER $args $LOGNAME"
+ _unlock
+ continue
+ else
+ $PCP_BINADM_DIR/pmpost "start pmlogger from $prog for host $host"
+ ${sock_me}$PMLOGGER $args $LOGNAME >$tmp/out 2>&1 &
+ pid=$!
+ fi
+
+ # wait for pmlogger to get started, and check on its health
+ _check_logger $pid
+
+ # the archive folio Latest is for the most recent archive in
+ # this directory
+ #
+ if [ -f $LOGNAME.0 ]
+ then
+ $VERBOSE && echo "Latest folio created for $LOGNAME"
+ mkaf $LOGNAME.0 >Latest
+ chown $PCP_USER:$PCP_GROUP Latest >/dev/null 2>&1
+ else
+ logdir=`dirname $LOGNAME`
+ if $TERSE
+ then
+ echo "$prog: Error: archive file `cd $logdir; $PWDCMND`/$LOGNAME.0 missing"
+ else
+ echo "$prog: Error: archive file $LOGNAME.0 missing"
+ echo "Directory (`cd $logdir; $PWDCMND`) contents:"
+ LC_TIME=POSIX ls -la $logdir
+ fi
+ fi
+
+ elif [ ! -z "$pid" -a $START_PMLOGGER = false ]
+ then
+ # Send pmlogger a SIGTERM, which is noted as a pending shutdown.
+ # Add pid to list of loggers sent SIGTERM - may need SIGKILL later.
+ #
+ $VERY_VERBOSE && echo "+ $KILL -s TERM $pid"
+ eval $KILL -s TERM $pid
+ $PCP_ECHO_PROG $PCP_ECHO_N "$pid ""$PCP_ECHO_C" >> $tmp/pmloggers
+ fi
+
+ _unlock
+done
+
+# check all the SIGTERM'd loggers really died - if not, use a bigger hammer.
+#
+if $SHOWME
+then
+ :
+elif [ $START_PMLOGGER = false -a -s $tmp/pmloggers ]
+then
+ pmloggerlist=`cat $tmp/pmloggers`
+ if ps -p "$pmloggerlist" >/dev/null 2>&1
+ then
+ $VERY_VERBOSE && ( echo; $PCP_ECHO_PROG $PCP_ECHO_N "+ $KILL -KILL `cat $tmp/pmies` ...""$PCP_ECHO_C" )
+ eval $KILL -s KILL $pmloggerlist >/dev/null 2>&1
+ delay=30 # tenths of a second
+ while ps -f -p "$pmloggerlist" >$tmp/alive 2>&1
+ do
+ if [ $delay -gt 0 ]
+ then
+ pmsleep 0.1
+ delay=`expr $delay - 1`
+ continue
+ fi
+ echo "$prog: Error: pmlogger process(es) will not die"
+ cat $tmp/alive
+ status=1
+ break
+ done
+ fi
+fi
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmlogger/pmlogger_daily.sh b/src/pmlogger/pmlogger_daily.sh
new file mode 100755
index 0000000..12bc3d7
--- /dev/null
+++ b/src/pmlogger/pmlogger_daily.sh
@@ -0,0 +1,952 @@
+#! /bin/sh
+#
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Daily administrative script for PCP archive logs
+#
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+# error messages should go to stderr, not the GUI notifiers
+#
+unset PCP_STDERR
+
+# constant setup
+#
+prog=`basename $0`
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+
+_cleanup()
+{
+ lockfile=`cat $tmp/lock 2>/dev/null`
+ rm -f "$PCP_RUN_DIR/pmlogger_daily.pid" "$lockfile"
+ rm -rf $tmp
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+echo >$tmp/lock
+
+if is_chkconfig_on pmlogger
+then
+ PMLOGGER_CTL=on
+else
+ PMLOGGER_CTL=off
+fi
+
+# control file for pmlogger administration ... edit the entries in this
+# file to reflect your local configuration (see also -c option below)
+#
+CONTROL=$PCP_PMLOGGERCONTROL_PATH
+
+# default number of days to keep archive logs
+#
+CULLAFTER=14
+
+# default compression program and days until starting compression
+#
+COMPRESS=xz
+COMPRESSAFTER=""
+COMPRESSREGEX="\.(meta|index|Z|gz|bz2|zip|xz|lzma|lzo|lz4)$"
+
+# threshold size to roll $PCP_LOG_DIR/NOTICES
+#
+NOTICES=$PCP_LOG_DIR/NOTICES
+ROLLNOTICES=20480
+
+# mail addresses to send daily NOTICES summary to
+#
+MAILME=""
+MAILFILE=$PCP_LOG_DIR/NOTICES.daily
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+# NB: FQDN cleanup; don't guess a 'real name for localhost', and
+# definitely don't truncate it a la `hostname -s`. Instead now
+# we use such a string only for the default log subdirectory, ie.
+# for substituting LOCALHOSTNAME in the fourth column of $CONTROL.
+
+# determine path for pwd command to override shell built-in
+# (see BugWorks ID #595416).
+PWDCMND=`which pwd 2>/dev/null | $PCP_AWK_PROG '
+BEGIN { i = 0 }
+/ not in / { i = 1 }
+/ aliased to / { i = 1 }
+ { if ( i == 0 ) print }
+'`
+if [ -z "$PWDCMND" ]
+then
+ # Looks like we have no choice here...
+ # force it to a known IRIX location
+ PWDCMND=/bin/pwd
+fi
+eval $PWDCMND -P >/dev/null 2>&1
+[ $? -eq 0 ] && PWDCMND="$PWDCMND -P"
+here=`$PWDCMND`
+
+echo > $tmp/usage
+cat >> $tmp/usage <<EOF
+Options:
+ -c=FILE,--control=FILE pmlogger control file
+ -k=N,--discard=N remove archives after N days
+ -m=ADDRs,--mail=ADDRs send daily NOTICES entries to email addresses
+ -M do not rewrite, merge or rename archives
+ -N,--showme perform a dry run, showing what would be done
+ -o merge yesterdays logs only (old form, default is all)
+ -r,--norewrite do not process archives with pmlogrewrite(1)
+ -s=SIZE,--rotate=SIZE rotate NOTICES file after reaching SIZE bytes
+ -t=WANT implies -VV, keep verbose output trace for WANT days
+ -V,--verbose verbose output (multiple times for very verbose)
+ -x=N,--compress-after=N compress archive data files after N days
+ -X=PROGRAM,--compressor=PROGRAM use PROGRAM for archive data file compression
+ -Y=REGEX,--regex=REGEX egrep filter when compressing files ["$COMPRESSREGEX"]
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ status=1
+ exit
+}
+
+# option parsing
+#
+SHOWME=false
+VERBOSE=false
+VERY_VERBOSE=false
+MYARGS=""
+OFLAG=false
+TRACE=0
+RFLAG=false
+MFLAG=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -c) CONTROL="$2"
+ shift
+ ;;
+ -k) CULLAFTER="$2"
+ shift
+ check=`echo "$CULLAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" -a X"$check" != Xforever ]
+ then
+ echo "Error: -k option ($CULLAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -m) MAILME="$2"
+ shift
+ ;;
+ -N) SHOWME=true
+ MYARGS="$MYARGS -N"
+ ;;
+ -M) MFLAG=true
+ RFLAG=true
+ ;;
+ -o) OFLAG=true
+ ;;
+ -r) RFLAG=true
+ ;;
+ -s) ROLLNOTICES="$2"
+ shift
+ check=`echo "$ROLLNOTICES" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" ]
+ then
+ echo "Error: -s option ($ROLLNOTICES) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -t) TRACE="$2"
+ shift
+ # from here on, all stdout and stderr output goes to
+ # $PCP_LOG_DIR/pmlogger/daily.<date>.trace
+ #
+ exec 1>$PCP_LOG_DIR/pmlogger/daily.`date "+%Y%m%d.%H.%M"`.trace 2>&1
+ VERBOSE=true
+ VERY_VERBOSE=true
+ MYARGS="$MYARGS -V -V"
+ ;;
+ -V) if $VERBOSE
+ then
+ VERY_VERBOSE=true
+ else
+ VERBOSE=true
+ fi
+ MYARGS="$MYARGS -V"
+ ;;
+ -x) COMPRESSAFTER="$2"
+ shift
+ check=`echo "$COMPRESSAFTER" | sed -e 's/[0-9]//g'`
+ if [ ! -z "$check" ]
+ then
+ echo "Error: -x option ($COMPRESSAFTER) must be numeric"
+ status=1
+ exit
+ fi
+ ;;
+ -X) COMPRESS="$2"
+ shift
+ ;;
+ -Y) COMPRESSREGEX="$2"
+ shift
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ ;;
+ esac
+ shift
+done
+
+[ $# -ne 0 ] && _usage
+
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file ($CONTROL)"
+ status=1
+ exit
+fi
+
+# each new archive log started by pmnewlog or pmlogger_check is named
+# yyyymmdd.hh.mm
+#
+LOGNAME=`date "+%Y%m%d.%H.%M"`
+
+_error()
+{
+ _report Error "$1"
+}
+
+_warning()
+{
+ _report Warning "$1"
+}
+
+_report()
+{
+ echo "$prog: $1: $2"
+ echo "[$CONTROL:$line] ... logging for host \"$host\" unchanged"
+ touch $tmp/err
+}
+
+_unlock()
+{
+ rm -f lock
+ echo >$tmp/lock
+}
+
+# filter file names to leave those that look like PCP archives
+# managed by pmlogger_check and pmlogger_daily, namely they begin
+# with a datestamp
+#
+# need to handle both the year 2000 and the old name formats, and
+# possible ./ prefix (from find .)
+#
+_filter_filename()
+{
+ sed -n \
+ -e 's/^\.\///' \
+ -e '/^[12][0-9][0-9][0-9][0-1][0-9][0-3][0-9][-.]/p' \
+ -e '/^[0-9][0-9][0-1][0-9][0-3][0-9][-.]/p'
+}
+
+_get_ino()
+{
+ # get inode number for $1
+ # throw away stderr (and return '') in case $1 has been removed by now
+ #
+ stat "$1" 2>/dev/null \
+ | sed -n '/Device:[ ].*[ ]Inode:/{
+s/Device:[ ].*[ ]Inode:[ ]*//
+s/[ ].*//
+p
+}'
+}
+
+# mails out any entries for the previous 24hrs from the PCP notices file
+#
+if [ ! -z "$MAILME" ]
+then
+ # get start time of NOTICES entries we want - all earlier are discarded
+ #
+ args=`pmdate -1d '-v yy=%Y -v my=%b -v dy=%d'`
+ args=`pmdate -1d '-v Hy=%H -v My=%M'`" $args"
+ args=`pmdate '-v yt=%Y -v mt=%b -v dt=%d'`" $args"
+
+ #
+ # Basic algorithm:
+ # from NOTICES head, look for a DATE: entry for yesterday or today;
+ # if its yesterday, find all HH:MM timestamps which are in the window,
+ # until the end of yesterday is reached;
+ # copy out the remainder of the file (todays entries).
+ #
+ # initially, entries have one of three forms:
+ # DATE: weekday mon day HH:MM:SS year
+ # Started by pmlogger_daily: weekday mon day HH:MM:SS TZ year
+ # HH:MM message
+ #
+
+ # preprocess to provide a common date separator - if new date stamps are
+ # ever introduced into the NOTICES file, massage them first...
+ #
+ rm -f $tmp/pcp
+ $PCP_AWK_PROG '
+/^Started/ { print "DATE:",$4,$5,$6,$7,$9; next }
+ { print }
+ ' $NOTICES | \
+ $PCP_AWK_PROG -F ':[ \t]*|[ \t]+' $args '
+$1 == "DATE" && $3 == mt && $4 == dt && $8 == yt { tday = 1; print; next }
+$1 == "DATE" && $3 == my && $4 == dy && $8 == yy { yday = 1; print; next }
+ { if ( tday || (yday && $1 > Hy) || (yday && $1 == Hy && $2 >= My) )
+ print
+ }' >$tmp/pcp
+
+ if [ -s $tmp/pcp ]
+ then
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "PCP NOTICES summary for `hostname`" $MAILME <$tmp/pcp
+ else
+ echo "$prog: Warning: cannot find a mail agent to send mail ..."
+ echo "PCP NOTICES summary for `hostname`"
+ cat $tmp/pcp
+ fi
+ [ -w `dirname "$NOTICES"` ] && mv $tmp/pcp "$MAILFILE"
+ fi
+fi
+
+
+# Roll $PCP_LOG_DIR/NOTICES -> $PCP_LOG_DIR/NOTICES.old if larger
+# that 10 Kbytes, and you can write in $PCP_LOG_DIR
+#
+if [ -s "$NOTICES" -a -w `dirname "$NOTICES"` ]
+then
+ if [ "`wc -c <"$NOTICES"`" -ge $ROLLNOTICES ]
+ then
+ if $VERBOSE
+ then
+ echo "Roll $NOTICES -> $NOTICES.old"
+ echo "Start new $NOTICES"
+ fi
+ if $SHOWME
+ then
+ echo "+ mv -f $NOTICES $NOTICES.old"
+ echo "+ touch $NOTICES"
+ else
+ echo >>"$NOTICES"
+ echo "*** rotated by $prog: `date`" >>"$NOTICES"
+ mv -f "$NOTICES" "$NOTICES.old"
+ echo "Started by $prog: `date`" >"$NOTICES"
+ (id "$PCP_USER" && chown $PCP_USER:$PCP_GROUP "$NOTICES") >/dev/null 2>&1
+ fi
+ fi
+fi
+
+# Keep our pid in $PCP_RUN_DIR/pmlogger_daily.pid ... this is checked
+# by pmlogger_check when it fails to obtain the lock should it be run
+# while pmlogger_daily is running
+#
+# For most packages, $PCP_RUN_DIR is included in the package,
+# but for Debian and cases where /var/run is a mounted filesystem
+# it may not exist, so create it here before it is used to create
+# any pid/lock files
+#
+# $PCP_RUN_DIR creation is also done in pmcd startup, but pmcd may
+# not be running on this system
+#
+if [ ! -d "$PCP_RUN_DIR" ]
+then
+ mkdir -p -m 775 "$PCP_RUN_DIR"
+ chown $PCP_USER:$PCP_GROUP "$PCP_RUN_DIR"
+fi
+echo $$ >"$PCP_RUN_DIR"/pmlogger_daily.pid
+
+# note on control file format version
+# 1.0 was shipped as part of PCPWEB beta, and did not include the
+# socks field [this is the default for backwards compatibility]
+# 1.1 is the first production release, and the version is set in
+# the control file with a $version=1.1 line (see below)
+#
+
+rm -f $tmp/err
+line=0
+version=''
+cat $CONTROL \
+| sed -e "s;PCP_LOG_DIR;$PCP_LOG_DIR;g" \
+| while read host primary socks dir args
+do
+ # start in one place for each iteration (beware relative paths)
+ cd "$here"
+ line=`expr $line + 1`
+
+ # NB: FQDN cleanup: substitute the LOCALHOSTNAME marker in the config line
+ # differently for the directory and the pcp -h HOST arguments.
+ dir_hostname=`hostname || echo localhost`
+ dir=`echo $dir | sed -e "s;LOCALHOSTNAME;$dir_hostname;"`
+ [ "x$host" = "xLOCALHOSTNAME" ] && host=local:
+
+ $VERY_VERBOSE && echo "[control:$line] host=\"$host\" primary=\"$primary\" socks=\"$socks\" dir=\"$dir\" args=\"$args\""
+
+ case "$host"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $host $primary $socks $dir $args"
+ cmd=`echo "$host $primary $socks $dir $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$version" -o "$version" = "1.0" ]
+ then
+ if [ -z "$version" ]
+ then
+ echo "$prog: Warning: processing default version 1.0 control format"
+ version=1.0
+ fi
+ args="$dir $args"
+ dir="$socks"
+ socks=n
+ fi
+
+ if [ -z "$primary" -o -z "$socks" -o -z "$dir" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ if $VERY_VERBOSE
+ then
+ pflag=''
+ [ $primary = y ] && pflag=' -P'
+ echo "Check pmlogger$pflag -h $host ... in $dir ..."
+ fi
+
+ if [ ! -d $dir ]
+ then
+ _error "archive directory ($dir) does not exist"
+ continue
+ fi
+
+ cd $dir
+ dir=`$PWDCMND`
+ $SHOWME && echo "+ cd $dir"
+
+ if $VERBOSE
+ then
+ echo
+ echo "=== daily maintenance of PCP archives for host $host ==="
+ echo
+ fi
+
+ if [ ! -w $dir ]
+ then
+ echo "$prog: Warning: no write access in $dir, skip lock file processing"
+ else
+ # demand mutual exclusion
+ #
+ fail=true
+ rm -f $tmp/stamp
+ for try in 1 2 3 4
+ do
+ if pmlock -v lock >$tmp/out
+ then
+ echo $dir/lock >$tmp/lock
+ fail=false
+ break
+ else
+ if [ ! -f $tmp/stamp ]
+ then
+ touch -t `pmdate -30M %Y%m%d%H%M` $tmp/stamp
+ fi
+ if [ ! -z "`find lock -newer $tmp/stamp -print 2>/dev/null`" ]
+ then
+ :
+ else
+ echo "$prog: Warning: removing lock file older than 30 minutes"
+ LC_TIME=POSIX ls -l $dir/lock
+ rm -f lock
+ fi
+ fi
+ sleep 5
+ done
+
+ if $fail
+ then
+ # failed to gain mutex lock
+ #
+ if [ -f lock ]
+ then
+ echo "$prog: Warning: is another PCP cron job running concurrently?"
+ LC_TIME=POSIX ls -l $dir/lock
+ else
+ echo "$prog: `cat $tmp/out`"
+ fi
+ _warning "failed to acquire exclusive lock ($dir/lock) ..."
+ continue
+ fi
+ fi
+
+ pid=''
+ if [ X"$primary" = Xy ]
+ then
+ # NB: FQDN cleanup: previously, we used to quietly accept several
+ # putative-aliases in the first (hostname) slot for a primary logger,
+ # which were all supposed to refer to the local host. So now we
+ # squash them all to the officially pcp-preferred way to access it.
+ host=local:
+
+ if test -f "$PCP_TMP_DIR/pmlogger/primary"
+ then
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "... try $PCP_TMP_DIR/pmlogger/primary: ""$PCP_ECHO_C"
+ primary_inode=`_get_ino $PCP_TMP_DIR/pmlogger/primary`
+ $VERY_VERBOSE && echo primary_inode=$primary_inode
+ for file in $PCP_TMP_DIR/pmlogger/*
+ do
+ case "$file"
+ in
+ */primary|*\*)
+ ;;
+ */[0-9]*)
+ inode=`_get_ino "$file"`
+ $VERY_VERBOSE && echo $file inode=$inode
+ if [ "$primary_inode" = "$inode" ]
+ then
+ pid="`echo $file | sed -e 's/.*\/\([^/]*\)$/\1/'`"
+ break
+ fi
+ ;;
+ esac
+ done
+ if [ -z "$pid" ]
+ then
+ if $VERY_VERBOSE
+ then
+ echo "primary pmlogger process pid not found"
+ ls -l "$PCP_TMP_DIR/pmlogger"
+ fi
+ else
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "primary pmlogger process $pid identified, OK"
+ else
+ $VERY_VERBOSE && echo "primary pmlogger process $pid not running"
+ pid=``
+ fi
+ fi
+ fi
+ else
+ for log in $PCP_TMP_DIR/pmlogger/[0-9]*
+ do
+ [ "$log" = "$PCP_TMP_DIR/pmlogger/[0-9]*" ] && continue
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "... try $log: ""$PCP_ECHO_C"
+ match=`sed -e '3s/\/[0-9][0-9][0-9][0-9][0-9.]*$//' $log \
+ | $PCP_AWK_PROG '
+BEGIN { m = 0 }
+NR == 3 && $0 == "'$dir'" { m = 2; next }
+END { print m }'`
+ $VERY_VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "match=$match ""$PCP_ECHO_C"
+ if [ "$match" = 2 ]
+ then
+ pid=`echo $log | sed -e 's,.*/,,'`
+ if _get_pids_by_name pmlogger | grep "^$pid\$" >/dev/null
+ then
+ $VERY_VERBOSE && echo "pmlogger process $pid identified, OK"
+ break
+ fi
+ $VERY_VERBOSE && echo "pmlogger process $pid not running, skip"
+ pid=''
+ else
+ $VERY_VERBOSE && echo "different directory, skip"
+ fi
+ done
+ fi
+
+ if [ -z "$pid" ]
+ then
+ if [ "$PMLOGGER_CTL" = "on" ]
+ then
+ _error "no pmlogger instance running for host \"$host\""
+ fi
+ else
+ # now execute pmnewlog to "roll the archive logs"
+ #
+ [ X"$primary" != Xy ] && args="-p $pid $args"
+ # else: -P is already the default
+ [ X"$socks" = Xy ] && args="-s $args"
+ args="$args -m pmlogger_daily"
+ $SHOWME && echo "+ pmnewlog$MYARGS $args $LOGNAME"
+ if pmnewlog$MYARGS $args $LOGNAME
+ then
+ :
+ else
+ _error "problems executing pmnewlog for host \"$host\""
+ touch $tmp/err
+ fi
+ fi
+ $VERBOSE && echo
+
+ # Merge archive logs.
+ #
+ # Will work for new style YYYYMMDD.HH.MM[-NN] archives and old style
+ # YYMMDD.HH.MM[-NN] archives.
+ # Note: we need to handle duplicate-breaking forms like
+ # YYYYMMDD.HH.MM-seq# (even though pmlogger_merge already picks most
+ # of these up) in case the base YYYYMMDD.HH.MM archive is for some
+ # reason missing here
+ #
+ # Assume if .meta file is present then other archive components are
+ # also present (if not the case it is a serious process botch, and
+ # pmlogger_merge will fail below)
+ #
+ # Find all candidate input archives, remove any that contain today's
+ # date and group the remainder by date.
+ #
+ TODAY=`date +%Y%m%d`
+
+ find *.meta \
+ \( -name "*.[0-2][0-9].[0-5][0-9].meta" \
+ -o -name "*.[0-2][0-9].[0-5][0-9]-[0-9][0-9].meta" \
+ \) \
+ -print 2>/dev/null \
+ | sed \
+ -e "/^$TODAY\./d" \
+ -e 's/\.meta//' \
+ | sort -n \
+ | $PCP_AWK_PROG '
+ { if (lastdate != "" && match($1, "^" lastdate "\\.") == 1) {
+ # same date as previous one
+ inlist = inlist " " $1
+ next
+ }
+ else {
+ # different date as previous one
+ if (inlist != "") print lastdate,inlist
+ inlist = $1
+ lastdate = $1
+ sub(/\..*/, "", lastdate)
+ }
+ }
+END { if (inlist != "") print lastdate,inlist }' >$tmp/list
+
+ if $OFLAG
+ then
+ # -o option, preserve the old semantics, and only process the
+ # previous day's archives ... aim for a time close to midday
+ # yesterday and report that date
+ #
+ now_hr=`pmdate %H`
+ hr=`expr 12 + $now_hr`
+ grep "^[0-9]*`pmdate -${hr}H %y%m%d` " $tmp/list >$tmp/tmp
+ mv $tmp/tmp $tmp/list
+ fi
+
+ # pmlogrewrite if no -r on command line and
+ # (a) pmlogrewrite exists in the same directory that the input
+ # archives are found, or
+ # (b) if $PCP_VAR_LIB/config/pmlogrewrite exists
+ # "exists" => file, directory or symbolic link
+ #
+ rewrite=''
+ if $RFLAG
+ then
+ :
+ else
+ for type in -f -d -L
+ do
+ if [ $type "$dir/pmlogrewrite" ]
+ then
+ rewrite="$dir/pmlogrewrite"
+ break
+ fi
+ done
+ if [ -z "$rewrite" ]
+ then
+ for type in -f -d -L
+ do
+ if [ $type "$PCP_VAR_DIR/config/pmlogrewrite" ]
+ then
+ rewrite="$PCP_VAR_DIR/config/pmlogrewrite"
+ break
+ fi
+ done
+ fi
+ fi
+
+ rm -f $tmp/skip
+ if $MFLAG
+ then
+ # -M don't rewrite, merge or rename
+ #
+ :
+ else
+ if [ ! -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "$prog: Warning: no archives found to merge"
+ $VERY_VERBOSE && ls -l
+ fi
+ else
+ cat $tmp/list \
+ | while read outfile inlist
+ do
+ if [ -f $outfile.0 -o -f $outfile.index -o -f $outfile.meta ]
+ then
+ echo "$prog: Warning: output archive ($outfile) already exists"
+ echo "[$CONTROL:$line] ... skip log merging, culling and compressing for host \"$host\""
+ touch $tmp/skip
+ break
+ else
+ if [ -n "$rewrite" ]
+ then
+ $VERY_VERBOSE && echo "Rewriting input archives using $rewrite"
+ for arch in $inlist
+ do
+ if pmlogrewrite -iq -c "$rewrite" $arch
+ then
+ :
+ else
+ echo "$prog: Warning: rewrite for $arch using -c $rewrite failed"
+ echo "[$CONTROL:$line] ... skip log merging, culling and compressing for host \"$host\""
+ touch $tmp/skip
+ break
+ fi
+ done
+
+ fi
+ if $VERY_VERBOSE
+ then
+ for arch in $inlist
+ do
+ echo "Input archive $arch ..."
+ pmdumplog -L $arch
+ done
+ fi
+ narch=`echo $inlist | wc -w | sed -e 's/ //g'`
+ if [ "$narch" = 1 ]
+ then
+ # optimization ... rename don't merge for one input
+ # archive case
+ #
+ if $SHOWME
+ then
+ echo "+ pmlogmv$MYARGS $inlist $outfile"
+ else
+ if pmlogmv$MYARGS $inlist $outfile
+ then
+ if $VERY_VERBOSE
+ then
+ echo "Renamed output archive $outfile ..."
+ pmdumplog -L $outfile
+ fi
+ else
+ _error "problems executing pmlogmv for host \"$host\""
+ fi
+ fi
+ else
+ # more than one input archive, merge away
+ #
+ if $SHOWME
+ then
+ echo "+ pmlogger_merge$MYARGS -f $inlist $outfile"
+ else
+ if pmlogger_merge$MYARGS -f $inlist $outfile
+ then
+ if $VERY_VERBOSE
+ then
+ echo "Merged output archive $outfile ..."
+ pmdumplog -L $outfile
+ fi
+ else
+ _error "problems executing pmlogger_merge for host \"$host\""
+ fi
+ fi
+ fi
+ fi
+ done
+ fi
+ fi
+
+ if [ -f $tmp/skip ]
+ then
+ # this is sufficiently serious that we don't want to remove
+ # the lock file, so problems are not compounded the next time
+ # the script is run
+ $VERY_VERBOSE && echo "Skip culling and compression ..."
+ continue
+ fi
+
+ # and cull old archives
+ #
+ if [ X"$CULLAFTER" != X"forever" ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # FreeBSD semantics for find(1) -mtime +N are "rounded up to
+ # the next full 24-hour period", compared to GNU/Linux semantics
+ # "any fractional part is ignored". So, these are almost always
+ # off by one day in terms of the files selected.
+ # For consistency, try to match the GNU/Linux semantics by using
+ # one MORE day.
+ #
+ mtime=`expr $CULLAFTER + 1`
+ else
+ mtime=$CULLAFTER
+ fi
+ find . -type f -mtime +$mtime \
+ | _filter_filename \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Archive files older than $CULLAFTER days being removed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + rm -f
+ else
+ cat $tmp/list | xargs rm -f
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no archive files found to cull"
+ fi
+ fi
+
+ # and compress old archive data files
+ # (after cull - don't compress unnecessarily)
+ #
+ if [ ! -z "$COMPRESSAFTER" ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # See note above re. find(1) on FreeBSD
+ #
+ mtime=`expr $COMPRESSAFTER - 1`
+ else
+ mtime=$COMPRESSAFTER
+ fi
+ find . -type f -mtime +$mtime \
+ | _filter_filename \
+ | egrep -v "$COMPRESSREGEX" \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Archive files older than $COMPRESSAFTER days being compressed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + $COMPRESS
+ else
+ cat $tmp/list | xargs $COMPRESS
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no archive files found to compress"
+ fi
+ fi
+
+ # and cull old trace files (from -t option)
+ #
+ if [ "$TRACE" -gt 0 ]
+ then
+ if [ "$PCP_PLATFORM" = freebsd ]
+ then
+ # See note above re. find(1) on FreeBSD
+ #
+ mtime=`expr $TRACE - 1`
+ else
+ mtime=$TRACE
+ fi
+ find $PCP_LOG_DIR/pmlogger -type f -mtime +$mtime \
+ | sed -n -e '/pmlogger\/daily\..*\.trace/p' \
+ | sort >$tmp/list
+ if [ -s $tmp/list ]
+ then
+ if $VERBOSE
+ then
+ echo "Trace files older than $TRACE days being removed ..."
+ fmt <$tmp/list | sed -e 's/^/ /'
+ fi
+ if $SHOWME
+ then
+ cat $tmp/list | xargs echo + rm -f
+ else
+ cat $tmp/list | xargs rm -f
+ fi
+ else
+ $VERY_VERBOSE && echo "$prog: Warning: no trace files found to cull"
+ fi
+ fi
+
+ _unlock
+
+done
+
+[ -f $tmp/err ] && status=1
+exit
diff --git a/src/pmlogger/pmlogger_merge.sh b/src/pmlogger/pmlogger_merge.sh
new file mode 100755
index 0000000..ba3edd1
--- /dev/null
+++ b/src/pmlogger/pmlogger_merge.sh
@@ -0,0 +1,282 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# merge a group of logfiles, e.g. all those for today
+#
+# default case, w/out arguments uses the default pmlogger filename
+# conventions for today's logs, namely `date +%Y%m%d` for both the
+# input-basename and the output-name
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+
+prog=`basename $0`
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+force=false
+VERBOSE=false
+SHOWME=false
+RM=rm
+
+_abandon()
+{
+ echo "$prog: These error(s) are fatal, no output archive has been created."
+ status=1
+ exit
+}
+
+_warning()
+{
+ echo "$prog: Trying to continue, although output archive may be corrupted."
+ force=false
+}
+
+cat > $tmp/usage << EOF
+Usage: [options] [input-basename ... output-name]
+
+Options:
+ -f, --force remove input files after creating output files
+ -N, --showme perform a dry run, showing what would be done
+ -V, --verbose increase diagnostic verbosity
+ --help
+EOF
+
+# option parsing
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+
+ -f) force=true
+ ;;
+
+ -N) SHOWME=true
+ RM="echo + rm"
+ ;;
+
+ -V) VERBOSE=true
+ ;;
+
+ --) shift
+ break
+ ;;
+
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+ ;;
+ esac
+ shift
+done
+
+if [ $# -eq 0 ]
+then
+ trylist=`date +%Y%m%d`
+ output=$input
+elif [ $# -ge 2 ]
+then
+ trylist=""
+ while [ $# -ge 2 ]
+ do
+ trylist="$trylist $1"
+ shift
+ done
+ output="$1"
+else
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ status=1
+ exit
+fi
+
+fail=false
+mergelist=""
+rmlist=""
+
+# handle dupicate-breaking name form of the base name
+# i.e. YYYYMMDD.HH.MM-seq# and ensure no duplicates
+#
+rm -f $tmp/input
+echo >$tmp/input
+for try in $trylist
+do
+ grep "^$try\$" $tmp/input >/dev/null || echo "$try" >>$tmp/input
+ for xxx in $try-*.index
+ do
+ [ "$xxx" = "$try-*.index" ] && continue
+ tie=`basename $xxx .index`
+ grep "^$tie\$" $tmp/input >/dev/null || echo "$tie" >>$tmp/input
+ done
+done
+
+for input in `cat $tmp/input`
+do
+ for file in $input.index
+ do
+ file=`basename $file .index`
+ rmlist="$rmlist $file"
+ empty=0
+ if [ ! -f "$file.index" ]
+ then
+ echo "$prog: Error: \"index\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.index" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ ! -f "$file.meta" ]
+ then
+ echo "$prog: Error: \"meta\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.meta" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ ! -f "$file.0" ]
+ then
+ echo "$prog: Error: \"volume 0\" file missing for archive \"$file\""
+ fail=true
+ elif [ ! -s "$file.0" ]
+ then
+ empty=`expr $empty + 1`
+ fi
+ if [ $empty -eq 3 ]
+ then
+ echo "$prog: Warning: archive \"$file\" is empty and will be skipped"
+ else
+ mergelist="$mergelist $file"
+ fi
+ done
+done
+
+if [ -f $output.index ]
+then
+ echo "$prog: Error: \"index\" file already exists for output archive \"$output\""
+ fail=true
+fi
+if [ -f $output.meta ]
+then
+ echo "$prog: Error: \"meta\" file already exists for output archive \"$output\""
+ fail=true
+fi
+if [ -f $output.0 ]
+then
+ echo "$prog: Error: \"volume 0\" file already exists for output archive \"$output\""
+ fail=true
+fi
+
+$fail && _abandon
+
+i=0
+list=""
+part=0
+if [ -z "$mergelist" ]
+then
+ $VERBOSE && echo "No archives to be merged."
+else
+ $VERBOSE && echo "Input archives to be merged:"
+ for input in $mergelist
+ do
+ for file in $input.index
+ do
+ if [ $i -ge 35 ]
+ then
+ # this limit requires of the order of 3 x 35 input + 3 x 1
+ # output = 108 file descriptors which should be well below any
+ # shell-imposed or system-imposed limits
+ #
+ $VERBOSE && echo " -> partial merge to $tmp/$part"
+ cmd="pmlogextract $list $tmp/$part"
+ if $SHOWME
+ then
+ echo "+ $cmd"
+ else
+ if $cmd
+ then
+ :
+ else
+ $VERBOSE || echo " -> partial merge to $tmp/$part"
+ echo "$prog: Directory: `pwd`"
+ echo "$prog: Failed: pmlogextract $list $tmp/$part"
+ _warning
+ fi
+ fi
+ list=$tmp/$part
+ part=`expr $part + 1`
+ i=0
+ fi
+ file=`basename $file .index`
+ list="$list $file"
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " $file""$PCP_ECHO_C"
+ numvol=`echo $file.[0-9]* | wc -w | sed -e 's/ *//g'`
+ if [ $numvol -gt 1 ]
+ then
+ $VERBOSE && echo " ($numvol volumes)"
+ else
+ $VERBOSE && echo
+ fi
+ i=`expr $i + 1`
+ done
+ done
+
+ cmd="pmlogextract $list $output"
+ if $SHOWME
+ then
+ echo "+ $cmd"
+ else
+ if $cmd
+ then
+ :
+ else
+ echo "$prog: Directory: `pwd`"
+ echo "$prog: Failed: pmlogextract $list $output"
+ _warning
+ fi
+ $VERBOSE && echo "Output archive files:"
+ for file in $output.meta $output.index $output.0
+ do
+ if [ -f $file ]
+ then
+ $VERBOSE && LC_TIME=POSIX ls -l $file
+ else
+ echo "$prog: Error: file \"$file\" not created"
+ force=false
+ fi
+ done
+ fi
+fi
+
+if $force
+then
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "Removing input archive files ...""$PCP_ECHO_C"
+ for input in $rmlist
+ do
+ for file in $input.index
+ do
+ file=`basename $file .index`
+ [ "$file" = "$output" ] && continue
+ eval $RM -f $file.index $file.meta $file.[0-9]*
+ done
+ done
+ $VERBOSE && echo " done"
+fi
+
+exit
diff --git a/src/pmlogger/pmlogmv.sh b/src/pmlogger/pmlogmv.sh
new file mode 100755
index 0000000..7a07098
--- /dev/null
+++ b/src/pmlogger/pmlogmv.sh
@@ -0,0 +1,261 @@
+#!/bin/sh
+#
+# Copyright (c) 1997,2003 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2013-2014 Red Hat.
+# Copyright (c) 2014 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Move (rename) a PCP archive.
+#
+# Operation is atomic across the multiple files of a PCP archive
+#
+
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$status" 0
+trap "_cleanup; rm -rf $tmp; exit \$status" 1 2 3 15
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] oldname newname
+
+Options:
+ -N, --showme perform a dry run, showing what would be done
+ -V, --verbose increase diagnostic verbosity
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit 1
+}
+
+verbose=false
+showme=false
+LN=ln
+RM=rm
+RMF="rm -f"
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -N) # show me
+ showme=true
+ LN='echo >&2 + ln'
+ RM='echo >&2 + rm'
+ RMF='echo >&2 + rm -f'
+ ;;
+ -V) # verbose
+ verbose=true
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -\?)
+ _usage
+ # NOTREACHED
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 2 ]
+then
+ _usage
+ # NOTREACHED
+fi
+
+if [ -f "$1" ]
+then
+ # oldname is an existing file, strip the expected PCP suffix
+ #
+ case "$1"
+ in
+ *[0-9])
+ old=`echo "$1" | sed -e 's/\.[0-9][0-9]*$//'`
+ ;;
+ *.index|*.meta)
+ old=`echo "$1" | sed -e 's/\.[a-z][a-z]*$//'`
+ ;;
+ *)
+ echo >&2 "$prog: Error: oldname argument ($1) is not a PCP archive"
+ exit
+ ;;
+ esac
+else
+ old="$1"
+fi
+new="$2"
+
+_cleanup()
+{
+ if [ -f $tmp/old ]
+ then
+ for f in `cat $tmp/old`
+ do
+ if [ ! -f "$f" ]
+ then
+ part=`echo "$f" | sed -e 's/.*\.\([^.][^.]*\)$/\1/'`
+ if [ ! -f "$new.$part" ]
+ then
+ echo >&2 "$prog: Fatal: $f and $new.$part lost"
+ ls -l "$old"* "$new"*
+ rm -f $tmp/old
+ return
+ fi
+ $verbose && echo >&2 "cleanup: recover $f from $new.$part"
+ if eval $LN "$new.$part" "$f"
+ then
+ :
+ else
+ echo >&2 "$prog: Fatal: ln $new.$part $f failed!"
+ ls -l "$old"* "$new"*
+ rm -f $tmp/old
+ return
+ fi
+ fi
+ done
+ fi
+ if [ -f $tmp/new ]
+ then
+ for f in `cat $tmp/new`
+ do
+ $verbose && echo >&2 "cleanup: remove $f"
+ eval $RMF "$f"
+ done
+ rm -f $tmp/new
+ fi
+ exit
+}
+
+# get oldnames inventory check required files are present
+#
+ls "$old".* 2>&1 | egrep '\.(index|meta|[0-9][0-9]*)$' >$tmp/old
+if [ -s $tmp/old ]
+then
+ # $old may be an ambiguous suffix, e.g. 20140417.00 (with more than
+ # one .HH archives) ... pick the suffixes and make sure there are
+ # no duplicates
+ #
+ touch $tmp/ok
+ sed <$tmp/old \
+ -e 's/.*\.index$/index/' \
+ -e 's/.*\.meta$/meta/' \
+ -e 's/.*\.\([0-9][0-9]*\)$/\1/' \
+ | sort \
+ | uniq -c \
+ | while read c x
+ do
+ case $c
+ in
+ 1)
+ ;;
+ *)
+ echo >&2 "$prog: Error: oldname argument ($old) is a prefix for multiple PCP archive files:"
+ grep "\\.$x\$" $tmp/old | sed -e 's/^/ /' >&2
+ rm -f $tmp/ok
+ ;;
+ esac
+ done
+ [ -f $tmp/ok ] || exit
+else
+ echo >&2 "$prog: Error: cannot find any files for the input archive ($old)"
+ exit
+fi
+if grep -q '.[0-9][0-9]*$' $tmp/old
+then
+ :
+else
+ echo >&2 "$prog: Error: cannot find any data files for the input archive ($old)"
+ ls -l "$old"*
+ exit
+fi
+if grep -q '.meta$' $tmp/old
+then
+ :
+else
+ echo >&2 "$prog: Error: cannot find .metadata file for the input archive ($old)"
+ ls -l "$old"*
+ exit
+fi
+
+# (hard) link oldnames and newnames
+#
+for f in `cat $tmp/old`
+do
+ if [ ! -f "$f" ]
+ then
+ echo >&2 "$prog: Error: ln-pass: input file vanished: $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ part=`echo "$f" | sed -e 's/.*\.\([^.][^.]*\)$/\1/'`
+ if [ -f "$new.$part" ]
+ then
+ echo >&2 "$prog: Error: ln-pass: output file already exists: $new.$part"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ $verbose && echo >&2 "link $f -> $new.$part"
+ echo "$new.$part" >>$tmp/new
+ if eval $LN "$f" "$new.$part"
+ then
+ :
+ else
+ echo >&2 "$prog: Error: ln $f $new.$part failed!"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+done
+
+# unlink oldnames provided link count is 2
+#
+for f in `cat $tmp/old`
+do
+ if [ ! -f "$f" ]
+ then
+ echo >&2 "$prog: Error: rm-pass: input file vanished: $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ # NOTREACHED
+ fi
+ links=`stat $f | sed -n -e '/Links:/s/.*Links:[ ]*\([0-9][0-9]*\).*/\1/p'`
+ xpect=2
+ $showme && xpect=1
+ if [ -z "$links" -o "$links" != $xpect ]
+ then
+ echo >&2 "$prog: Error: rm-pass: link count "$links" (not $xpect): $f"
+ ls -l "$old"* "$new"*
+ _cleanup
+ fi
+ $verbose && echo >&2 "remove $f"
+ if eval $RM "$f"
+ then
+ :
+ else
+ echo >&2 "$prog: Warning: rm $f failed!"
+ fi
+done
+
+status=0
diff --git a/src/pmlogger/pmnewlog.sh b/src/pmlogger/pmnewlog.sh
new file mode 100755
index 0000000..ff68936
--- /dev/null
+++ b/src/pmlogger/pmnewlog.sh
@@ -0,0 +1,702 @@
+#! /bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# stop and restart a pmlogger instance
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+
+# error messages should go to stderr, not the GUI notifiers
+#
+unset PCP_STDERR
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+status=0
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+VERBOSE=false
+SHOWME=false
+CP=cp
+MV=mv
+RM=rm
+KILL=pmsignal
+primary=true
+myname="primary pmlogger"
+connect=primary
+access=""
+config=""
+saveconfig=""
+logfile="pmlogger.log"
+namespace=""
+args=""
+sock_me=""
+proxyhost="$PMPROXY_HOST"
+proxyport="$PMPROXY_PORT"
+
+cat > $tmp/usage << EOF
+# Usage: [options] archive
+
+pmnewlog options:
+ -a=FILE,--access=FILE specify access controls for the new pmlogger
+ -C=FILE,--save=FILE save the configuration of new pmlogger in FILE
+ -c=FILE,--config=FILE file to load configuration from
+ -N,--showme perform a dry run, showing what would be done
+ --namespace
+ -P,--primary execute as primary logger instance
+ -p=PID,--pid=PID restart non-primary logger with pid
+ -s,--socks use pmsocks
+ -V,--verbose turn on verbose reporting of pmnewlog progress
+ --help
+
+pmlogger options:
+ --debug
+ -c=FILE,--config=FILE file to load configuration from
+ -l=FILE, --log=FILE redirect diagnostics and trace output
+ -L, --linger run even if not primary logger instance and nothing to log
+ -m=MSG, --note=MSG descriptive note to be added to the port map file
+ --namespace
+ -P, --primary execute as primary logger instance
+ -r, --report report record sizes and archive growth rate
+ -t=DELTA, --interval=DELTA default logging interval
+ -T=TIME, --finish=TIME end of the time window
+ -v=SIZE, --volsize=SIZE switch log volumes after size has been accumulated
+ -y set timezone for times to local time rather than from PMCD host
+EOF
+
+_abandon()
+{
+ echo
+ echo "Sorry, but this is fatal. No new pmlogger instance has been started."
+ status=1
+ exit
+}
+
+_check_pid()
+{
+ if $SHOWME
+ then
+ :
+ else
+ _get_pids_by_name pmlogger | grep "^$1\$"
+ fi
+}
+
+_check_logfile()
+{
+ if [ ! -f $logfile ]
+ then
+ echo "Cannot find pmlogger output file at \"$logfile\""
+ else
+ echo "Contents of pmlogger output file \"$logfile\" ..."
+ cat $logfile
+ fi
+}
+
+_check_logger()
+{
+ # wait until pmlogger process starts, or exits
+ #
+ delay=5
+ [ ! -z "$PMCD_CONNECT_TIMEOUT" ] && delay=$PMCD_CONNECT_TIMEOUT
+ x=5
+ [ ! -z "$PMCD_REQUEST_TIMEOUT" ] && x=$PMCD_REQUEST_TIMEOUT
+
+ # wait for maximum time of a connection and 20 requests
+ #
+ delay=`expr $delay + 20 \* $x`
+ i=0
+ while [ $i -lt $delay ]
+ do
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N ".""$PCP_ECHO_C"
+ if $SHOWME
+ then
+ echo "+ echo 'connect $1' | $pmlc_prefix pmlc ..."
+ $VERBOSE && echo " done"
+ return 0
+ elif echo "connect $1" | eval $pmlc_prefix pmlc 2>&1 | grep "Unable to connect" >/dev/null
+ then
+ :
+ else
+ sleep 5
+ $VERBOSE && echo " done"
+ return 0
+ fi
+
+ if _get_pids_by_name pmlogger | grep "^$1\$" >/dev/null
+ then
+ :
+ else
+ $VERBOSE || _message restart
+ echo " process exited!"
+ _check_logfile
+ return 1
+ fi
+ sleep 5
+ i=`expr $i + 5`
+ done
+ $VERBOSE || _message restart
+ echo " timed out waiting!"
+ sed -e 's/^/ /' $tmp/out
+ _check_logfile
+ return 1
+}
+
+_message()
+{
+ case $1
+ in
+ looking)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Looking for $myname ...""$PCP_ECHO_C"
+ ;;
+ get_host)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Getting logged host name from $myname ...""$PCP_ECHO_C"
+ ;;
+ get_state)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Contacting $myname to get logging state ...""$PCP_ECHO_C"
+ ;;
+ restart)
+ $PCP_ECHO_PROG $PCP_ECHO_N "Waiting for new pmlogger to start ..""$PCP_ECHO_C"
+ ;;
+ esac
+}
+
+_do_cmd()
+{
+ if $SHOWME
+ then
+ echo "+ $1"
+ else
+ eval $1
+ fi
+}
+
+# option parsing
+#
+# pmlogger, without -V version which is redefined as -V (verbose)
+# and -s exit_size which is redefined as -s (pmsocks), and ignore
+# [ -h host ] and [ -x fd ] as they make no sense in the argument
+# part of the pmlogger control file for a long-running pmlogger.
+#
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+
+# pmnewlog options and flags
+#
+
+ -a) access="$2"
+ shift
+ if [ ! -f "$access" ]
+ then
+ echo "$prog: Error: cannot find accessfile ($access)"
+ _abandon
+ fi
+ ;;
+
+ -C) saveconfig="$2"
+ shift
+ ;;
+
+ -N) SHOWME=true
+ CP="echo + cp"
+ MV="echo + mv"
+ RM="echo + rm"
+ KILL="echo + kill"
+ ;;
+
+ -p) pid=$2
+ shift
+ primary=false
+ myname="pmlogger (process $pid)"
+ connect=$pid
+ ;;
+
+ -s) if which pmsocks >/dev/null 2>&1
+ then
+ sock_me="pmsocks "
+ else
+ echo "$prog: Warning: no pmsocks available, would run without"
+ sock_me=""
+ fi
+ ;;
+
+ -V) VERBOSE=true
+ ;;
+
+# pmlogger options and flags that need special handling
+#
+
+ -c) config="$2"
+ shift
+ if [ ! -f "$config" ]
+ then
+ if [ -f "$PCP_SYSCONF_DIR/pmlogger/$config" ]
+ then
+ config="$PCP_SYSCONF_DIR/pmlogger/$config"
+ else
+ echo "$prog: Error: cannot find configfile ($config)"
+ _abandon
+ fi
+ fi
+ ;;
+
+ -l) logfile="$2"
+ shift
+ ;;
+
+ -n) namespace="-n $2"
+ args="${args}$1 $2 "
+ shift
+ ;;
+
+ -P) primary=true
+ myname="primary pmlogger"
+ ;;
+
+# pmlogger flags passed through
+#
+
+ -L|-r|-y)
+ args="${args}$1 "
+ ;;
+
+ -D|-m|-t|-T|-v)
+ args="${args}$1 $2 "
+ shift
+ ;;
+
+ --) # end of options, start of arguments
+ shift
+ break
+ ;;
+
+ -\?) pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+ ;;
+ esac
+ shift
+done
+
+if [ $# -ne 1 ]
+then
+ echo "$prog: Insufficient arguments" >&2
+ echo >&2
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ _abandon
+fi
+
+# initial sanity checking for new archive name
+#
+archive="$1"
+
+# check that designated pmlogger is really running
+#
+$VERBOSE && _message looking
+$PCP_PS_PROG $PCP_PS_ALL_FLAGS \
+| if $primary
+then
+ grep 'pmlogger .*-P' | grep -v grep
+else
+ $PCP_AWK_PROG '$2 == '"$pid"' && /pmlogger/ { print }'
+fi >$tmp/out
+
+if [ -s $tmp/out ]
+then
+ $VERBOSE && echo " found"
+ $VERBOSE && cat $tmp/out
+ # expecting something like
+ # pcp 30019 ... pmlogger ...args... -h hostname ...args...
+ # pick pid and hostname (safer to do it here if possible because it
+ # captures the possible -h hostname@proxy construct which is lost
+ # if one gets the hostname from pmlogger via pmlc)
+ #
+ pid=`$PCP_AWK_PROG '{ print $2 }' <$tmp/out`
+ hostname=`sed -n <$tmp/out -e '/ -h /{
+s/.* -h //
+s/ .*//
+p
+}'`
+ # may have @proxyhost or @proxyhost:proxyport appended to hostname
+ #
+ proxyhost=`echo "$hostname" | sed -n -e '/@/{
+s/.*@//
+s/:.*//
+p
+}'`
+ proxyport=`echo "$hostname" | sed -n -e '/@.*:/s/.*;//p'`
+else
+ if $VERBOSE
+ then
+ :
+ else
+ _message looking
+ echo
+ fi
+ echo "$prog: Error: process not found"
+ _abandon
+fi
+
+if [ -n "$proxyhost" ]
+then
+ if [ -n "$proxyport" ]
+ then
+ pmlc_prefix="PMPROXY_HOST=$proxyhost PMPROXY_PORT=$proxyport"
+ else
+ pmlc_prefix="PMPROXY_HOST=$proxyhost"
+ fi
+else
+ pmlc_prefix=''
+fi
+
+# pass primary/not primary down
+#
+$primary && args="$args-P "
+
+# pass logfile option down
+#
+args="$args-l $logfile "
+
+# if not a primary pmlogger, get name of pmcd host pmlogger is connected to
+#
+if $primary
+then
+ hostname=localhost
+elif [ -z "$hostname" ]
+then
+ # did not get pmcd hostname from ps output above, talk to pmlogger
+ # via pmlc
+ #
+ # start critical section ... no interrupts due to pmlogger SIGPIPE
+ # bug in PCP 1.1
+ #
+ trap "echo; echo $prog:' Interrupt! ... I am talking to pmlogger, please wait ...'" 1 2 3 15
+ $VERBOSE && _message get_host
+
+ _do_cmd "( echo 'connect $connect' ; echo status ) | eval $pmlc_prefix pmlc 2>$tmp/err >$tmp/out"
+
+ # end critical section
+ #
+ trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+ if $SHOWME || [ ! -s $tmp/err ]
+ then
+ $VERBOSE && echo " done"
+ else
+ if grep "Unable to connect" $tmp/err >/dev/null
+ then
+ $VERBOSE || _message get_host
+ echo " failed to connect"
+ echo
+ sed -e 's/^/ /' $tmp/err
+ _abandon
+ else
+ $VERBOSE || _message get_host
+ echo
+ echo "$prog: Warning: errors from talking to $myname via pmlc"
+ sed -e 's/^/ /' $tmp/err
+ echo
+ echo "continuing ..."
+ fi
+ fi
+
+ if $SHOWME
+ then
+ hostname=somehost
+ else
+ hostname=`sed -n -e '/^pmlogger/s/.* from host //p' <$tmp/out`
+ if [ -z "$hostname" ]
+ then
+ echo "$prog: Error: failed to get host name from $myname"
+ echo "This is what was collected from $myname."
+ echo
+ sed -e 's/^/ /' $tmp/out
+ _abandon
+ fi
+ args="$args-h $hostname "
+ fi
+else
+ # got hostname from ps output
+ #
+ args="$args-h $hostname "
+fi
+
+# extract/construct config file if required
+#
+if [ -z "$config" ]
+then
+ # start critical section ... no interrupts due to pmlogger SIGPIPE
+ # bug in PCP 1.1
+ #
+ trap "echo; echo $prog:' Interrupt! ... I am talking to pmlogger, please wait ...'" 1 2 3 15
+ $VERBOSE && _message get_state
+
+ # iterate over top-level names in pmns, and query pmlc for
+ # current configuration ... note exclusion of "proc" metrics
+ # ... others may be excluded in a similar fashion
+ #
+ if $SHOWME
+ then
+ echo "+ ( echo 'connect $connect'; echo 'query ...'; ... ) | eval $pmlc_prefix pmlc $namespace | $PCP_AWK_PROG ..."
+ else
+ echo "connect $connect" >$tmp/pmlc.cmd
+ for top in `pminfo -h $hostname $namespace \
+ | sed -e 's/\..*//' -e '/^proc$/d' \
+ | sort -u`
+ do
+ echo "query $top" >>$tmp/pmlc.cmd
+ done
+ eval $pmlc_prefix pmlc $namespace <$tmp/pmlc.cmd 2>$tmp/err \
+ | $PCP_AWK_PROG >$tmp/out '
+/^[^ ]/ { metric = $1; next }
+$1 == "mand" || ( $1 == "adv" && $2 == "on" ) { print $0 " " metric }'
+ fi
+
+ # end critical section
+ #
+ trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+ if $SHOWME || [ ! -s $tmp/err ]
+ then
+ $VERBOSE && echo " done"
+ else
+ if grep "Unable to connect" $tmp/err >/dev/null
+ then
+ $VERBOSE || _message get_state
+ echo " failed to connect"
+ echo
+ sed -e 's/^/ /' $tmp/err
+ _abandon
+ else
+ $VERBOSE || _message get_state
+ echo
+ echo "$prog: Warning: errors from talking to $myname via pmlc"
+ sed -e 's/^/ /' $tmp/err
+ echo
+ echo "continuing ..."
+ fi
+ fi
+
+ if [ ! -s $tmp/out ]
+ then
+ if $SHOWME
+ then
+ :
+ else
+ echo "$prog: Error: failed to collect configuration info from $myname"
+ echo "Most likely this pmlogger instance is inactive."
+ _abandon
+ fi
+ fi
+
+ # convert to a pmlogger config file
+ #
+ if $SHOWME
+ then
+ echo "+ create new pmlogger config file ..."
+ else
+ sed <$tmp/out >$tmp/config \
+ -e 's/ on nl/ on/' \
+ -e 's/ off nl/ off/'\
+ -e 's/ *mand *\(o[nf]*\) /log mandatory \1 /' \
+ -e 's/ *adv *on /log advisory on/' \
+ -e 's/\[[0-9][0-9]* or /[/' \
+ -e 's/\(\[[^]]*]\) \([^ ]*\)/\2 \1/' \
+ -e 's/ */ /g'
+
+ if [ ! -s $tmp/config ]
+ then
+ echo "$prog: Error: failed to generate a pmlogger configuration file for pmlogger"
+ echo "This is what was collected from $myname."
+ echo
+ sed -e 's/^/ /' $tmp/out
+ _abandon
+ fi
+ fi
+ config=$tmp/config
+fi
+
+# optionally append access control specifications
+#
+if [ -n "$access" ]
+then
+ if grep '\[access]' $config >/dev/null
+ then
+ echo "$prog: Error: pmlogger configuration file already contains an"
+ echo " access control section, specifications from \"$access\" cannot"
+ echo " be applied."
+ _abandon
+ fi
+ cat $access >>$config
+fi
+
+# add config file to the args, save config file if -C
+#
+args="$args-c $config "
+if [ -n "$saveconfig" ]
+then
+ if eval $CP $config $saveconfig
+ then
+ echo "New pmlogger configuration file saved as $saveconfig"
+ else
+ echo "$prog: Warning: unable to save configuration file as $saveconfig"
+ fi
+fi
+
+# kill off existing pmlogger
+#
+$VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N "Terminating $myname ...""$PCP_ECHO_C"
+for sig in USR1 TERM KILL
+do
+ $VERBOSE && $PCP_ECHO_PROG $PCP_ECHO_N " SIG$sig ...""$PCP_ECHO_C"
+ eval $KILL -s $sig $pid
+ sleep 5
+ [ "`_check_pid $pid`" = "" ] && break
+done
+
+if [ "`_check_pid $pid`" = "" ]
+then
+ $VERBOSE && echo " done"
+else
+ echo " failed!"
+ _abandon
+fi
+
+# the archive folio Latest is for the most recent archive in this directory
+#
+dir=`dirname $archive`
+eval $RM -f $dir/Latest
+
+# clean up port-map, just in case
+#
+PM_LOG_PORT_DIR="$PCP_TMP_DIR/pmlogger"
+eval $RM -f "$PM_LOG_PORT_DIR"/$pid
+$primary && eval $RM -f $PM_LOG_PORT_DIR/primary
+
+# finally do it, ...
+#
+cd $dir
+$SHOWME && echo "+ cd $dir"
+[ "$dir" = . ] && dir=`pwd`
+archive=`basename $archive`
+
+# handle duplicates/aliases (happens when pmlogger is restarted
+# within a minute and basename is the same)
+#
+suff=''
+for file in $archive.*
+do
+ [ "$file" = "$archive"'.*' ] && continue
+ # we have a clash! ... find a new -number suffix for the
+ # existing files ... we are going to keep $archive for the
+ # new pmlogger below
+ #
+ if [ -z "$suff" ]
+ then
+ for xx in 0 1 2 3 4 5 6 7 8 9
+ do
+ for yy in 0 1 2 3 4 5 6 7 8 9
+ do
+ [ "`echo $archive-${xx}${yy}.*`" != "$archive-${xx}${yy}.*" ] && continue
+ suff=${xx}$yy
+ break
+ done
+ [ ! -z "$suff" ] && break
+ done
+ if [ -z "$suff" ]
+ then
+ echo "$prog: Error: unable to break duplicate clash for archive basename \"$archive\""
+ _abandon
+ fi
+ $VERBOSE && echo "Duplicate archive basename ... rename $archive.* files to $archive-$suff.*"
+ fi
+ eval $MV -f $file `echo $file | sed -e "s/$archive/&-$suff/"`
+done
+
+$VERBOSE && echo "Launching new pmlogger in directory \"$dir\" as ..."
+[ -f $logfile ] && eval $MV -f $logfile $logfile.prior
+$VERBOSE && echo "${sock_me}pmlogger $args$archive"
+
+if $SHOWME
+then
+ echo "+ ${sock_me}pmlogger $args$archive &"
+ echo "+ ... assume pid is 12345"
+ new_pid=12345
+else
+ ${sock_me}pmlogger $args$archive &
+ new_pid=$!
+fi
+
+# stall a bit ...
+#
+STALL_TIME=10
+sleep $STALL_TIME
+
+$VERBOSE && _message restart
+if _check_logger $new_pid || $SHOWME
+then
+ $VERBOSE && echo "New pmlogger status ..."
+ $VERBOSE && _do_cmd "( echo 'connect $new_pid'; echo status ) | $pmlc_prefix pmlc"
+
+ # make the "Latest" archive folio
+ #
+ i=0
+ failed=true
+ WAIT_TIME=10
+ while [ $i -lt $WAIT_TIME ]
+ do
+ if $SHOWME || [ -f $archive.0 -a -f $archive.meta -a $archive.index ]
+ then
+ _do_cmd "mkaf $archive.0 >Latest" 2>$tmp/err
+ if [ -s $tmp/err ]
+ then
+ # errors from mkaf typically result from race conditions
+ # at the start of pmlogger, e.g.
+ # Warning: cannot extract hostname from archive "..." ...
+ #
+ # simply keep trying
+ :
+ else
+ failed=false
+ break
+ fi
+ fi
+ sleep 1
+ i=`expr $i + 1`
+ done
+
+ if $failed
+ then
+ ELAPSED=`expr $STALL_TIME + $WAIT_TIME`
+ echo "Warning: pmlogger [pid=$new_pid host=$hostname] failed to create archive files within $ELAPSED seconds"
+ if [ -f $tmp/err ]
+ then
+ echo "Warnings/errors from mkaf ..."
+ cat $tmp/err
+ fi
+ fi
+else
+ _abandon
+fi
+
+exit
diff --git a/src/pmlogger/rc_pmlogger b/src/pmlogger/rc_pmlogger
new file mode 100644
index 0000000..251b43b
--- /dev/null
+++ b/src/pmlogger/rc_pmlogger
@@ -0,0 +1,301 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Red Hat.
+# Copyright (c) 2000-2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Start or Stop the Performance Co-Pilot pmlogger processes.
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 94 06
+# description: pmlogger is a performance metrics logger for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmlogger
+# Required-Start: $local_fs
+# Should-Start: $network $remote_fs $syslog $time $pmcd
+# Required-Stop: $local_fs
+# Should-Stop: $network $remote_fs $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmlogger (the performance metrics logger for PCP)
+# Description: Configure and control pmlogger (the performance metrics logger for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMLOGCTRL=$PCP_PMLOGGERCONTROL_PATH
+PMLOGGER=$PCP_BINADM_DIR/pmlogger
+prog=$PCP_RC_DIR/`basename $0`
+
+# search for your mail agent of choice ...
+#
+MAIL=''
+for try in Mail mail email
+do
+ if which $try >/dev/null 2>&1
+ then
+ MAIL=$try
+ break
+ fi
+done
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if is_chkconfig_on pmlogger
+then
+ PMLOGGER_CTL=on
+else
+ PMLOGGER_CTL=off
+fi
+
+VERBOSE_CTL=on
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ IAM=0
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+# Note: _start_pmcheck() runs in the background, in parallel with
+# the rest of the script. It might complete well after the caller
+# so tmpfile handling is especially problematic. Goal is to speed
+# bootup by starting potentially slow (remote monitoring) pmlogger
+# processes in the background.
+#
+_start_pmcheck()
+{
+ bgstatus=0
+ bgtmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+ trap "rm -rf $bgtmp; exit \$bgstatus" 0 1 2 3 15
+
+ pmlogger_check $VFLAG >$bgtmp/pmcheck.out 2>$bgtmp/pmcheck
+ bgstatus=$?
+ if [ -s $bgtmp/pmcheck ]
+ then
+ $PCP_BINADM_DIR/pmpost "pmlogger_check failed in $prog, mailing output to root"
+ if [ ! -z "$MAIL" ]
+ then
+ $MAIL -s "pmlogger_check failed in $prog" root <$bgtmp/pmcheck
+ else
+ echo "$prog: pmlogger_check failed ..."
+ cat $bgtmp/pmcheck
+ fi
+ fi
+ exit $bgstatus # co-process is now complete
+}
+
+_start_pmlogger()
+{
+ if which pmlogger_check >/dev/null 2>&1
+ then
+ # pmlogger_check uses $PMLOGCTRL to start everything that is needed
+ #
+ if [ ! -f $PMLOGCTRL ]
+ then
+ echo "$prog:"'
+Error: PCP archive logger control file '$PMLOGCTRL'
+ is missing! Cannot start any Performance Co-Pilot archive logger(s).'
+ # failure
+ false
+ else
+ # really start the pmlogger instances based on the control file.
+ # done in the background to avoid delaying the init script,
+ # failures are notified by mail
+ #
+ $ECHO $PCP_ECHO_N "Starting pmlogger ..." "$PCP_ECHO_C"
+ _start_pmcheck &
+ # success
+ true
+ fi
+ else
+ echo "$prog:"'
+Warning: Performance Co-Pilot installation is incomplete (at least the
+ script "pmlogger_check" is missing) and the PCP archive logger(s)
+ cannot be started.'
+ # failure
+ false
+ fi
+ $RC_STATUS -v
+}
+
+_shutdown()
+{
+ # Is any pmlogger running?
+ #
+ _get_pids_by_name pmlogger >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$prog: pmlogger not running"
+ return 0
+ fi
+
+ [ "$1" = quietly ] || \
+ $ECHO $PCP_ECHO_N "Stopping pmlogger ...""$PCP_ECHO_C"
+
+ # Terminate those pmloggers started by either pmlogger_check or
+ # pmlogger_daily ... relies on the -m option to pmlogger and the
+ # annotation in the (optional) 4th line of the port map files
+ #
+ for pid in `cat $tmp/tmp`
+ do
+ if [ -f "$PCP_TMP_DIR/pmlogger/$pid" ]
+ then
+ note=`sed -n -e 4p <"$PCP_TMP_DIR/pmlogger/$pid"`
+ if [ "$note" = pmlogger_check -o "$note" = pmlogger_daily ]
+ then
+ pmsignal -s TERM $pid
+ fi
+ fi
+ done
+ $RC_STATUS -v
+ $PCP_BINADM_DIR/pmpost "stop pmlogger from $prog"
+}
+
+_usage()
+{
+ echo "Usage: $prog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+ REBUILDOPT=''
+ VFLAG='-V'
+else # For a quiet startup and shutdown
+ ECHO=:
+ REBUILDOPT=-s
+ VFLAG=
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the Performance Co-Pilot loggers.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmlogger
+ then
+ status=0
+ exit
+ fi
+ _shutdown quietly
+
+ # pmlogger messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ if [ -x $PMLOGGER ]
+ then
+ if [ "$PMLOGGER_CTL" = on ]
+ then
+ _start_pmlogger
+ fi
+ fi
+
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown verbose
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmlogger:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMLOGGER
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMLOGGER
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmlogger/src/GNUmakefile b/src/pmlogger/src/GNUmakefile
new file mode 100644
index 0000000..69b6cbb
--- /dev/null
+++ b/src/pmlogger/src/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmlogger$(EXECSUFFIX)
+
+CFILES = pmlogger.c fetch.c util.c error.c callback.c ports.c \
+ dopdu.c check.c preamble.c rewrite.c events.c
+HFILES = logger.h
+LFILES = lex.l
+YFILES = gram.y
+
+LCFLAGS += $(PIECFLAGS)
+LLDFLAGS += $(PIELDFLAGS)
+
+LLDLIBS = $(PCPLIB) $(LIB_FOR_PTHREADS)
+LDIRT = *.log foo.* gram.h lex.c y.tab.? $(YFILES:%.y=%.tab.?) $(CMDTARGET)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+ $(INSTALL) -S $(PCP_BIN_DIR)/$(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+.NOTPARALLEL:
+YFLAGS += -v
+gram.tab.h gram.tab.c: gram.y
+ $(YACC) -d -b `basename $< .y` $<
+
+lex.o gram.tab.o: gram.tab.h
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlogger/src/callback.c b/src/pmlogger/src/callback.c
new file mode 100644
index 0000000..e099ebc
--- /dev/null
+++ b/src/pmlogger/src/callback.c
@@ -0,0 +1,771 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+int last_log_offset;
+
+/*
+ * pro tem, we have a single context with the pmcd providing the
+ * results, hence need to send the profile each time
+ */
+static int one_context = 1;
+
+struct timeval last_stamp;
+__pmHashCtl hist_hash;
+
+/*
+ * These structures allow us to keep track of the _last_ fetch
+ * for each fetch in each AF group ... needed to track changes in
+ * instance availability.
+ */
+typedef struct _lastfetch {
+ struct _lastfetch *lf_next;
+ fetchctl_t *lf_fp;
+ pmResult *lf_resp;
+ __pmPDU *lf_pb;
+} lastfetch_t;
+
+typedef struct _AFctl {
+ struct _AFctl *ac_next;
+ int ac_afid;
+ lastfetch_t *ac_fetch;
+} AFctl_t;
+
+static AFctl_t *achead = (AFctl_t *)0;
+
+/* clear the "metric/instance was available at last fetch" flag for each metric
+ * and instance in the specified fetchgroup.
+ */
+static void
+clearavail(fetchctl_t *fcp)
+{
+ indomctl_t *idp;
+ pmID pmid;
+ pmidctl_t *pmp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ __pmHashNode *hp;
+ int i, inst;
+ int j;
+
+ for (idp = fcp->f_idp; idp != (indomctl_t *)0; idp = idp->i_next) {
+ for (pmp = idp->i_pmp; pmp != (pmidctl_t *)0; pmp = pmp->p_next) {
+ /* find the metric if it's in the history hash table */
+ pmid = pmp->p_pmid;
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+ if (hp == (__pmHashNode *)0)
+ /* not in history, no flags to update */
+ continue;
+ php = (pmidhist_t *)hp->data;
+
+ /* now we have the metric's entry in the history */
+
+ if (idp->i_indom != PM_INDOM_NULL) {
+ /*
+ * for each instance in the profile for this metric, find
+ * the history entry for the instance if it exists and
+ * reset the "was available at last fetch" flag
+ */
+ if (idp->i_numinst)
+ for (i = 0; i < idp->i_numinst; i++) {
+ inst = idp->i_instlist[i];
+ ihp = &php->ph_instlist[0];
+ for (j = 0; j < php->ph_numinst; j++, ihp++)
+ if (ihp->ih_inst == inst) {
+ PMLC_SET_AVAIL(ihp->ih_flags, 0);
+ break;
+ }
+ }
+ else
+ /*
+ * if the profile specifies "all instances" clear EVERY
+ * instance's "available" flag
+ * NOTE: even instances that don't exist any more
+ */
+ for (i = 0; i < php->ph_numinst; i++)
+ PMLC_SET_AVAIL(php->ph_instlist[i].ih_flags, 0);
+ }
+ /* indom is PM_INDOM_NULL */
+ else {
+ /* if the single-valued metric is in the history it will have 1
+ * instance */
+ ihp = &php->ph_instlist[0];
+ PMLC_SET_AVAIL(ihp->ih_flags, 0);
+ }
+ }
+ }
+}
+
+static void
+setavail(pmResult *resp)
+{
+ int i;
+
+ for (i = 0; i < resp->numpmid; i++) {
+ pmID pmid;
+ pmValueSet *vsp;
+ __pmHashNode *hp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ int j;
+
+ vsp = resp->vset[i];
+ pmid = vsp->pmid;
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+
+ if (hp != (__pmHashNode *)0)
+ php = (pmidhist_t *)hp->data;
+ else {
+ /* add new pmid to history if it's pmValueSet is OK */
+ if (vsp->numval <= 0)
+ continue;
+ /*
+ * use the OTHER hash list to find the pmid's desc and thereby its
+ * indom
+ */
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != (__pmHashNode *)0; hp = hp->next)
+ if (pmid == (pmID)hp->key)
+ break;
+ if (hp == (__pmHashNode *)0 ||
+ ((optreq_t *)hp->data)->r_desc == (pmDesc *)0)
+ /* not set up properly yet, not much we can do ... */
+ continue;
+ php = (pmidhist_t *)calloc(1, sizeof(pmidhist_t));
+ if (php == (pmidhist_t *)0) {
+ __pmNoMem("setavail: new pmid hist entry calloc",
+ sizeof(pmidhist_t), PM_FATAL_ERR);
+ }
+ php->ph_pmid = pmid;
+ php->ph_indom = ((optreq_t *)hp->data)->r_desc->indom;
+ /*
+ * now create a new insthist list for all the instances in the
+ * pmResult and we're done
+ */
+ php->ph_numinst = vsp->numval;
+ ihp = (insthist_t *)calloc(vsp->numval, sizeof(insthist_t));
+ if (ihp == (insthist_t *)0) {
+ __pmNoMem("setavail: inst list calloc",
+ vsp->numval * sizeof(insthist_t), PM_FATAL_ERR);
+ }
+ php->ph_instlist = ihp;
+ for (j = 0; j < vsp->numval; j++, ihp++) {
+ ihp->ih_inst = vsp->vlist[j].inst;
+ PMLC_SET_AVAIL(ihp->ih_flags, 1);
+ }
+ if ((j = __pmHashAdd(pmid, (void *)php, &hist_hash)) < 0) {
+ die("setavail: __pmHashAdd(hist_hash)", j);
+ }
+
+ return;
+ }
+
+ /* update an existing pmid history entry, adding any previously unseen
+ * instances
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ int inst = vsp->vlist[j].inst;
+ int k;
+
+ for (k = 0; k < php->ph_numinst; k++)
+ if (inst == php->ph_instlist[k].ih_inst)
+ break;
+
+ if (k < php->ph_numinst)
+ ihp = &php->ph_instlist[k];
+ else {
+ /* allocate new instance if required */
+ int need = (k + 1) * sizeof(insthist_t);
+
+ php->ph_instlist = (insthist_t *)realloc(php->ph_instlist, need);
+ if (php->ph_instlist == (insthist_t *)0) {
+ __pmNoMem("setavail: inst list realloc", need, PM_FATAL_ERR);
+ }
+ ihp = &php->ph_instlist[k];
+ ihp->ih_inst = inst;
+ ihp->ih_flags = 0;
+ php->ph_numinst++;
+ }
+ PMLC_SET_AVAIL(ihp->ih_flags, 1);
+ }
+ }
+}
+
+/*
+ * This has been taken straight from logmeta.c in libpcp. It is required
+ * here to get the timestamp of the indom.
+ * Note that the tp argument is used to return the timestamp of the indom.
+ * It is a merger of __pmLogGetIndom and searchindom.
+ */
+int
+__localLogGetInDom(__pmLogCtl *lcp, pmInDom indom, __pmTimeval *tp, int **instlist, char ***namelist)
+{
+ __pmHashNode *hp;
+ __pmLogInDom *idp;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOGMETA)
+ fprintf(stderr, "__localLogGetInDom( ..., %s)\n",
+ pmInDomStr(indom));
+#endif
+
+ if ((hp = __pmHashSearch((unsigned int)indom, &lcp->l_hashindom)) == NULL)
+ return 0;
+
+ idp = (__pmLogInDom *)hp->data;
+
+ if (idp == NULL)
+ return PM_ERR_INDOM_LOG;
+
+ *instlist = idp->instlist;
+ *namelist = idp->namelist;
+ *tp = idp->stamp;
+
+ return idp->numinst;
+}
+
+
+/*
+ * compare pmResults for a particular metric, and return 1 if
+ * the set of instances has changed.
+ */
+static int
+check_inst(pmValueSet *vsp, int hint, pmResult *lrp)
+{
+ int i;
+ int j;
+ pmValueSet *lvsp;
+
+ /* Make sure vsp->pmid exists in lrp's result */
+ /* and find which value set in lrp it is. */
+ if (hint < lrp->numpmid && lrp->vset[hint]->pmid == vsp->pmid)
+ i = hint;
+ else {
+ for (i = 0; i < lrp->numpmid; i++) {
+ if (lrp->vset[i]->pmid == vsp->pmid)
+ break;
+ }
+ if (i == lrp->numpmid) {
+ fprintf(stderr, "check_inst: cannot find PMID %s in last result ...\n",
+ pmIDStr(vsp->pmid));
+ __pmDumpResult(stderr, lrp);
+ return 0;
+ }
+ }
+
+ lvsp = lrp->vset[i];
+
+ if (lvsp->numval != vsp->numval)
+ return 1;
+
+ /* compare instances */
+ for (i = 0; i < lvsp->numval; i++) {
+ if (lvsp->vlist[i].inst != vsp->vlist[i].inst) {
+ /* the hard way */
+ for (j = 0; j < vsp->numval; j++) {
+ if (lvsp->vlist[j].inst == vsp->vlist[i].inst)
+ break;
+ }
+ if (j == vsp->numval)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Lookup the first cache index associated with a given PMID in a given task.
+ */
+static int
+lookupTaskCacheIndex(task_t *tp, pmID pmid)
+{
+ int i;
+
+ for (i = 0; i < tp->t_numpmid; i++)
+ if (tp->t_pmidlist[i] == pmid)
+ return i;
+ return -1;
+}
+
+/*
+ * Iterate over *all* tasks and return all names for a given PMID.
+ * Returns the number of names found, and nameset allocated in a
+ * single allocation call (which the caller must free).
+ */
+static int
+lookupTaskCacheNames(pmID pmid, char ***namesptr)
+{
+ int i, numnames = 0, len = 0;
+ char *data, **names = NULL;
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ for (i = 0; i < tp->t_numpmid; i++) {
+ if (tp->t_pmidlist[i] != pmid)
+ continue;
+ len += strlen(tp->t_namelist[i]) + 1;
+ numnames++;
+ }
+ }
+
+ names = (char **)malloc(numnames * sizeof(names[0]) + len);
+ data = (char *)names + (numnames * sizeof(names[0]));
+ for (tp = tasklist, len = 0; tp != NULL; tp = tp->t_next) {
+ for (i = 0; i < tp->t_numpmid; i++) {
+ if (tp->t_pmidlist[i] != pmid)
+ continue;
+ names[len++] = data;
+ strcpy(data, tp->t_namelist[i]);
+ data += (strlen(tp->t_namelist[i]) + 1);
+ }
+ }
+
+ *namesptr = names;
+ return numnames;
+}
+
+void
+log_callback(int afid, void *data)
+{
+ int i;
+ int j;
+ int k;
+ int sts;
+ task_t *tp = (task_t *)data;
+ fetchctl_t *fp;
+ indomctl_t *idp;
+ pmResult *resp;
+ __pmPDU *pb;
+ AFctl_t *acp;
+ lastfetch_t *lfp;
+ lastfetch_t *free_lfp;
+ int needindom;
+ int needti;
+ static int flushsize = 100000;
+ long old_meta_offset;
+ long new_offset;
+ long new_meta_offset;
+ int pdu_bytes = 0;
+ int pdu_metrics = 0;
+ int numinst;
+ int *instlist;
+ char **namelist;
+ __pmTimeval tmp;
+ __pmTimeval resp_tval;
+ unsigned long peek_offset;
+
+ if (!parse_done)
+ /* ignore callbacks until all of the config file has been parsed */
+ return;
+
+ /* find AFctl_t for this afid */
+ for (acp = achead; acp != (AFctl_t *)0; acp = acp->ac_next) {
+ if (acp->ac_afid == afid)
+ break;
+ }
+ if (acp == (AFctl_t *)0) {
+ acp = (AFctl_t *)calloc(1, sizeof(AFctl_t));
+ if (acp == (AFctl_t *)0) {
+ __pmNoMem("log_callback: new AFctl_t entry calloc",
+ sizeof(AFctl_t), PM_FATAL_ERR);
+ }
+ acp->ac_afid = afid;
+ acp->ac_next = achead;
+ achead = acp;
+ }
+ else {
+ /* cleanup any fetchgroups that have gone away */
+ for (lfp = acp->ac_fetch; lfp != (lastfetch_t *)0; lfp = lfp->lf_next) {
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+ if (fp == lfp->lf_fp)
+ break;
+ }
+ if (fp == (fetchctl_t *)0) {
+ lfp->lf_fp = (fetchctl_t *)0; /* mark lastfetch_t as free */
+ if (lfp->lf_resp != (pmResult *)0) {
+ pmFreeResult(lfp->lf_resp);
+ lfp->lf_resp =(pmResult *)0;
+ }
+ }
+ }
+ }
+
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+
+ /* find lastfetch_t for this fetch group, else make a new one */
+ free_lfp = (lastfetch_t *)0;
+ for (lfp = acp->ac_fetch; lfp != (lastfetch_t *)0; lfp = lfp->lf_next) {
+ if (lfp->lf_fp == fp)
+ break;
+ if (lfp->lf_fp == (fetchctl_t *)0 && free_lfp == (lastfetch_t *)0)
+ free_lfp = lfp;
+ }
+ if (lfp == (lastfetch_t *)0) {
+ /* need new one */
+ if (free_lfp != (lastfetch_t *)0)
+ lfp = free_lfp; /* lucky */
+ else {
+ lfp = (lastfetch_t *)calloc(1, sizeof(lastfetch_t));
+ if (lfp == (lastfetch_t *)0) {
+ __pmNoMem("log_callback: new lastfetch_t entry calloc",
+ sizeof(lastfetch_t), PM_FATAL_ERR);
+ }
+ lfp->lf_next = acp->ac_fetch;
+ acp->ac_fetch = lfp;
+ }
+ lfp->lf_fp = fp;
+ }
+
+ if (one_context || fp->f_state & OPT_STATE_PROFILE) {
+ /* profile for this fetch group has changed */
+ pmAddProfile(PM_INDOM_NULL, 0, (int *)0);
+ for (idp = fp->f_idp; idp != (indomctl_t *)0; idp = idp->i_next) {
+ if (idp->i_indom != PM_INDOM_NULL && idp->i_numinst != 0)
+ pmAddProfile(idp->i_indom, idp->i_numinst, idp->i_instlist);
+ }
+ fp->f_state &= ~OPT_STATE_PROFILE;
+ }
+
+ clearavail(fp);
+
+ if ((sts = myFetch(fp->f_numpmid, fp->f_pmidlist, &pb)) < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: disconnecting because myFetch failed: %s\n", pmErrStr(sts));
+#endif
+ disconnect(sts);
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: fetch group %p (%d metrics)\n", fp, fp->f_numpmid);
+#endif
+
+ /*
+ * hook to rewrite PDU buffer ...
+ lfp */
+ pb = rewrite_pdu(pb, archive_version);
+
+ if (rflag) {
+ /*
+ * bytes = PDU len - sizeof (header) + 2 * sizeof (int)
+ * see logputresult() in libpcp/logutil.c for details of how
+ * a PDU buffer is reformatted to make len shorter by one int
+ * before the record is written to the external file
+ */
+ pdu_bytes += ((__pmPDUHdr *)pb)->len - sizeof (__pmPDUHdr) +
+ 2*sizeof(int);
+ pdu_metrics += fp->f_numpmid;
+ }
+
+ /*
+ * Even without a -v option, we may need to switch volumes
+ * if the data file exceeds 2^31-1 bytes
+ */
+ peek_offset = ftell(logctl.l_mfp);
+ peek_offset += ((__pmPDUHdr *)pb)->len - sizeof(__pmPDUHdr) + 2*sizeof(int);
+ if (peek_offset > 0x7fffffff) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on max size, currently %ld\n", ftell(logctl.l_mfp));
+#endif
+ (void)newvolume(VOL_SW_MAX);
+ }
+
+ /*
+ * would prefer to save this up until after any meta data and/or
+ * temporal index writes, but __pmDecodeResult changes the pointers
+ * in the pdu buffer for the non INSITU values ... sigh
+ */
+ last_log_offset = ftell(logctl.l_mfp);
+ assert(last_log_offset >= 0);
+ if ((sts = __pmLogPutResult2(&logctl, pb)) < 0) {
+ fprintf(stderr, "__pmLogPutResult2: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+
+ __pmOverrideLastFd(fileno(logctl.l_mfp));
+ if ((sts = __pmDecodeResult(pb, &resp)) < 0) {
+ fprintf(stderr, "__pmDecodeResult: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ setavail(resp);
+ resp_tval.tv_sec = resp->timestamp.tv_sec;
+ resp_tval.tv_usec = resp->timestamp.tv_usec;
+
+ needti = 0;
+ old_meta_offset = ftell(logctl.l_mdfp);
+ assert(old_meta_offset >= 0);
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+ pmDesc desc;
+ char **names = NULL;
+ int numnames = 0;
+
+ sts = __pmLogLookupDesc(&logctl, vsp->pmid, &desc);
+ if (sts < 0) {
+ /* lookup name and descriptor in task cache */
+ int taskindex = lookupTaskCacheIndex(tp, vsp->pmid);
+ if (taskindex == -1) {
+ fprintf(stderr, "lookupTaskCacheIndex cannot find PMID %s\n",
+ pmIDStr(vsp->pmid));
+ exit(1);
+ }
+ desc = tp->t_desclist[taskindex];
+ numnames = lookupTaskCacheNames(vsp->pmid, &names);
+ if ((sts = __pmLogPutDesc(&logctl, &desc, numnames, names)) < 0) {
+ fprintf(stderr, "__pmLogPutDesc: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ if (numnames) {
+ free(names);
+ }
+ }
+ if (desc.type == PM_TYPE_EVENT) {
+ /*
+ * Event records need some special handling ...
+ */
+ if ((sts = do_events(vsp)) < 0) {
+ fprintf(stderr, "Failed to process event records: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ }
+ if (desc.indom != PM_INDOM_NULL && vsp->numval > 0) {
+ /*
+ * __pmLogGetInDom has been replaced by __localLogGetInDom so that
+ * the timestamp of the retrieved indom is also returned. The timestamp
+ * is then used to decide if the indom needs to be refreshed.
+ */
+ __pmTimeval indom_tval;
+ numinst = __localLogGetInDom(&logctl, desc.indom, &indom_tval, &instlist, &namelist);
+ if (numinst < 0)
+ needindom = 1;
+ else {
+ needindom = 0;
+ /* Need to see if result's insts all exist
+ * somewhere in the hashed/cached insts.
+ * Thus a potential numval^2 search.
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ for (k = 0; k < numinst; k++) {
+ if (vsp->vlist[j].inst == instlist[k])
+ break;
+ }
+ if (k == numinst) {
+ needindom = 1;
+ break;
+ }
+ }
+ }
+ /*
+ * Check here that the instance domain has not been changed
+ * by a previous iteration of this loop.
+ * So, the timestamp of resp must be after the update timestamp
+ * of the target instance domain.
+ */
+ if (needindom == 0 && lfp->lf_resp != (pmResult *)0 &&
+ __pmTimevalSub(&resp_tval, &indom_tval) < 0 )
+ needindom = check_inst(vsp, i, lfp->lf_resp);
+
+ if (needindom) {
+ /*
+ * Note. We do NOT free() instlist and namelist allocated
+ * here ... look for magic below log{Put,Get}InDom ...
+ */
+ if ((numinst = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) {
+ fprintf(stderr, "pmGetInDom(%s): %s\n", pmInDomStr(desc.indom), pmErrStr(numinst));
+ exit(1);
+ }
+ tmp.tv_sec = (__int32_t)resp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)resp->timestamp.tv_usec;
+ if ((sts = __pmLogPutInDom(&logctl, desc.indom, &tmp, numinst, instlist, namelist)) < 0) {
+ fprintf(stderr, "__pmLogPutInDom: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: indom (%s) changed\n", pmInDomStr(desc.indom));
+#endif
+ }
+ }
+ }
+
+ if (ftell(logctl.l_mfp) > flushsize) {
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: file size (%d) reached flushsize (%d)\n", (int)ftell(logctl.l_mfp), flushsize);
+#endif
+ }
+
+ if (last_log_offset == 0 || last_log_offset == sizeof(__pmLogLabel)+2*sizeof(int)) {
+ /* first result in this volume */
+ needti = 1;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: first result for this volume\n");
+#endif
+ }
+
+ if (needti) {
+ /*
+ * need to unwind seek pointer to start of most recent
+ * result (but if this is the first one, skip the label
+ * record, what a crock), ... ditto for the meta data
+ */
+ new_offset = ftell(logctl.l_mfp);
+ assert(new_offset >= 0);
+ new_meta_offset = ftell(logctl.l_mdfp);
+ assert(new_meta_offset >= 0);
+ fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, old_meta_offset, SEEK_SET);
+ tmp.tv_sec = (__int32_t)resp->timestamp.tv_sec;
+ tmp.tv_usec = (__int32_t)resp->timestamp.tv_usec;
+ __pmLogPutIndex(&logctl, &tmp);
+ /*
+ * ... and put them back
+ */
+ fseek(logctl.l_mfp, new_offset, SEEK_SET);
+ fseek(logctl.l_mdfp, new_meta_offset, SEEK_SET);
+ flushsize = ftell(logctl.l_mfp) + 100000;
+ }
+
+ last_stamp = resp->timestamp; /* struct assignment */
+
+ if (lfp->lf_resp != (pmResult *)0) {
+ /*
+ * release memory that is allocated and pinned in pmDecodeResult
+ */
+ pmFreeResult(lfp->lf_resp);
+ }
+ lfp->lf_resp = resp;
+ if (lfp->lf_pb != NULL)
+ __pmUnpinPDUBuf(lfp->lf_pb);
+ lfp->lf_pb = pb;
+ }
+
+ if (rflag && tp->t_size == 0 && pdu_metrics > 0) {
+ char *name = NULL;
+ int taskindex;
+ int i;
+
+ tp->t_size = pdu_bytes;
+
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\nGroup [%d metrics] {\n", pdu_metrics);
+ else
+ fprintf(stderr, "\nMetric ");
+
+ for (fp = tp->t_fetch; fp != (fetchctl_t *)0; fp = fp->f_next) {
+ for (i = 0; i < fp->f_numpmid; i++) {
+ name = NULL;
+ taskindex = lookupTaskCacheIndex(tp, fp->f_pmidlist[i]);
+ if (taskindex >= 0)
+ name = tp->t_namelist[taskindex];
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\t");
+ fprintf(stderr, "%s", name ? name : pmIDStr(fp->f_pmidlist[i]));
+ if (pdu_metrics > 1)
+ fprintf(stderr, "\n");
+ }
+ if (pdu_metrics > 1)
+ fprintf(stderr, "}");
+ }
+ fprintf(stderr, " logged ");
+
+ if (tp->t_delta.tv_sec == 0 && tp->t_delta.tv_usec == 0)
+ fprintf(stderr, "once: %d bytes\n", pdu_bytes);
+ else {
+ if (tp->t_delta.tv_usec == 0) {
+ fprintf(stderr, "every %d sec: %d bytes ",
+ (int)tp->t_delta.tv_sec, pdu_bytes);
+ }
+ else
+ fprintf(stderr, "every %d.%03d sec: %d bytes ",
+ (int)tp->t_delta.tv_sec, (int)tp->t_delta.tv_usec / 1000, pdu_bytes);
+ fprintf(stderr, "or %.2f Mbytes/day\n",
+ ((double)pdu_bytes * 24 * 60 * 60) /
+ (1024 * 1024 * (tp->t_delta.tv_sec + (double)tp->t_delta.tv_usec / 1000000)));
+ }
+ }
+
+ if (exit_samples > 0)
+ exit_samples--;
+
+ if (exit_samples == 0)
+ /* run out of samples in sample counter, so stop logging */
+ run_done(0, "Sample limit reached");
+
+ if (exit_bytes != -1 &&
+ (vol_bytes + ftell(logctl.l_mfp) >= exit_bytes))
+ /* reached exit_bytes limit, so stop logging */
+ run_done(0, "Byte limit reached");
+
+ if (vol_switch_samples > 0 &&
+ ++vol_samples_counter == vol_switch_samples) {
+ (void)newvolume(VOL_SW_COUNTER);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on samples (%d)\n", vol_samples_counter);
+#endif
+ }
+
+ if (vol_switch_bytes > 0 &&
+ (ftell(logctl.l_mfp) >= vol_switch_bytes)) {
+ (void)newvolume(VOL_SW_BYTES);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "callback: new volume based on size (%d)\n", (int)ftell(logctl.l_mfp));
+#endif
+ }
+
+}
+
+int
+putmark(void)
+{
+ struct {
+ __pmPDU hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* zero PMIDs to follow */
+ __pmPDU tail;
+ } mark;
+
+ if (last_stamp.tv_sec == 0 && last_stamp.tv_usec == 0)
+ /* no earlier pmResult, no point adding a mark record */
+ return 0;
+
+ mark.hdr = htonl((int)sizeof(mark));
+ mark.tail = mark.hdr;
+ mark.timestamp.tv_sec = last_stamp.tv_sec;
+ mark.timestamp.tv_usec = last_stamp.tv_usec + 1000; /* + 1msec */
+ if (mark.timestamp.tv_usec > 1000000) {
+ mark.timestamp.tv_usec -= 1000000;
+ mark.timestamp.tv_sec++;
+ }
+ mark.timestamp.tv_sec = htonl(mark.timestamp.tv_sec);
+ mark.timestamp.tv_usec = htonl(mark.timestamp.tv_usec);
+ mark.numpmid = htonl(0);
+
+ if (fwrite(&mark, 1, sizeof(mark), logctl.l_mfp) != sizeof(mark))
+ return -oserror();
+ else
+ return 0;
+}
diff --git a/src/pmlogger/src/check.c b/src/pmlogger/src/check.c
new file mode 100644
index 0000000..a6c2def
--- /dev/null
+++ b/src/pmlogger/src/check.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+char *chk_emess[] = {
+ "No error",
+ "Request for (advisory) ON conflicts with current (mandatory) ON state",
+ "Request for (advisory) OFF conflicts with current (mandatory) ON state",
+ "Request for (advisory) ON conflicts with current (mandatory) OFF state",
+ "Request for (advisory) OFF conflicts with current (mandatory) OFF state",
+};
+
+static void
+undo(task_t *tp, optreq_t *rqp, int inst)
+{
+ int j;
+ int k;
+ int sts;
+
+ if (rqp->r_numinst >= 1) {
+ /* remove instance from list of instance */
+ for (k =0, j = 0; j < rqp->r_numinst; j++) {
+ if (rqp->r_instlist[j] != inst)
+ rqp->r_instlist[k++] = rqp->r_instlist[j];
+ }
+ rqp->r_numinst = k;
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("undo: __pmOptFetchDel", sts);
+
+ if (rqp->r_numinst == 0) {
+ /* no more instances, remove specification */
+ if (tp->t_fetch == NULL) {
+ /* no more specifications, remove task */
+ task_t *xtp;
+ task_t *ltp = NULL;
+ for (xtp = tasklist; xtp != NULL; xtp = xtp->t_next) {
+ if (xtp == tp) {
+ if (ltp == NULL)
+ tasklist = tp->t_next;
+ else
+ ltp->t_next = tp->t_next;
+ break;
+ }
+ ltp = xtp;
+ }
+ }
+ __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash);
+ free(rqp);
+ }
+ else
+ /* re-insert modified specification */
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ }
+ else {
+ /*
+ * TODO ... current specification is for all instances,
+ * need to remove this instance from the set ...
+ * this requires some enhancement to optFetch
+ *
+ * pro tem, this metric-instance pair may continue to get
+ * logged, even though the logging state is recorded as
+ * OFF (this is the worst thing that can happen here)
+ */
+ }
+}
+
+int
+chk_one(task_t *tp, pmID pmid, int inst)
+{
+ optreq_t *rqp;
+ task_t *ctp;
+
+ rqp = findoptreq(pmid, inst);
+ if (rqp == NULL)
+ return 0;
+
+ ctp = rqp->r_fetch->f_aux;
+ if (ctp == NULL || ctp == tp)
+ /*
+ * can only happen if same metric+inst appears more than once
+ * in the same group ... this can never be a conflict
+ */
+ return 1;
+
+ if (PMLC_GET_MAND(ctp->t_state)) {
+ if (PMLC_GET_ON(ctp->t_state)) {
+ if (PMLC_GET_MAND(tp->t_state) == 0 && PMLC_GET_MAYBE(tp->t_state) == 0) {
+ if (PMLC_GET_ON(tp->t_state))
+ return -1;
+ else
+ return -2;
+ }
+ }
+ else {
+ if (PMLC_GET_MAND(tp->t_state) == 0 && PMLC_GET_MAYBE(tp->t_state) == 0) {
+ if (PMLC_GET_ON(tp->t_state))
+ return -3;
+ else
+ return -4;
+ }
+ }
+ /*
+ * new mandatory, over-rides the old mandatory
+ */
+ undo(ctp, rqp, inst);
+ }
+ else {
+ /*
+ * new anything, over-rides the old advisory
+ */
+ undo(ctp, rqp, inst);
+ }
+
+ return 0;
+}
+
+int
+chk_all(task_t *tp, pmID pmid)
+{
+ optreq_t *rqp;
+ task_t *ctp;
+
+ rqp = findoptreq(pmid, 0); /*TODO, not right!*/
+ if (rqp == NULL)
+ return 0;
+
+ ctp = rqp->r_fetch->f_aux;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "chk_all: pmid=%s task=" PRINTF_P_PFX "%p state=%s%s%s%s delta=%d.%06d\n",
+ pmIDStr(pmid), tp,
+ PMLC_GET_INLOG(tp->t_state) ? " " : "N",
+ PMLC_GET_AVAIL(tp->t_state) ? " " : "N",
+ PMLC_GET_MAND(tp->t_state) ? "M" : "A",
+ PMLC_GET_ON(tp->t_state) ? "Y" : "N",
+ (int)tp->t_delta.tv_sec, (int)tp->t_delta.tv_usec);
+ if (ctp == NULL)
+ fprintf(stderr, "compared to: NULL\n");
+ else
+ fprintf(stderr, "compared to: optreq task=" PRINTF_P_PFX "%p state=%s%s%s%s delta=%d.%06d\n",
+ ctp,
+ PMLC_GET_INLOG(ctp->t_state) ? " " : "N",
+ PMLC_GET_AVAIL(ctp->t_state) ? " " : "N",
+ PMLC_GET_MAND(ctp->t_state) ? "M" : "A",
+ PMLC_GET_ON(ctp->t_state) ? "Y" : "N",
+ (int)ctp->t_delta.tv_sec, (int)ctp->t_delta.tv_usec);
+ }
+#endif
+ return 0;
+}
diff --git a/src/pmlogger/src/dopdu.c b/src/pmlogger/src/dopdu.c
new file mode 100644
index 0000000..d3c35c4
--- /dev/null
+++ b/src/pmlogger/src/dopdu.c
@@ -0,0 +1,1491 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+
+/* return one of these when a status request is made from a PCP 1.x pmlc */
+typedef struct {
+ __pmTimeval ls_start; /* start time for log */
+ __pmTimeval ls_last; /* last time log written */
+ __pmTimeval ls_timenow; /* current time */
+ int ls_state; /* state of log (from __pmLogCtl) */
+ int ls_vol; /* current volume number of log */
+ __int64_t ls_size; /* size of current volume */
+ char ls_hostname[PM_LOG_MAXHOSTLEN];
+ /* name of pmcd host */
+ char ls_tz[40]; /* $TZ at collection host */
+ char ls_tzlogger[40]; /* $TZ at pmlogger */
+} __pmLoggerStatus_v1;
+
+#ifdef PCP_DEBUG
+/* This crawls over the data structure looking for weirdness */
+void
+reality_check(void)
+{
+ __pmHashNode *hp;
+ task_t *tp;
+ task_t *tp2;
+ fetchctl_t *fp;
+ optreq_t *rqp;
+ pmID pmid;
+ int i = 0, j, k;
+
+ /* check that all fetch_t's f_aux point back to their parent task */
+ for (tp = tasklist; tp != NULL; tp = tp->t_next, i++) {
+ if (tp->t_fetch == NULL)
+ fprintf(stderr, "task[%d] @" PRINTF_P_PFX "%p has no fetch group\n", i, tp);
+ j = 0;
+ for (fp = tp->t_fetch; fp != NULL; fp = fp->f_next) {
+ if (fp->f_aux != (void *)tp)
+ fprintf(stderr, "task[%d] fetch group[%d] has invalid task pointer\n",
+ i, j);
+ j++;
+ }
+
+ /* check that all optreq_t's in hash list have valid r_fetch->f_aux
+ * pointing to a task in the task list.
+ */
+ for (j = 0; j < tp->t_numpmid; j++) {
+ pmid = tp->t_pmidlist[j];
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ for (tp2 = tasklist; tp2 != NULL; tp2 = tp2->t_next)
+ if (rqp->r_fetch->f_aux == (void *)tp2)
+ break;
+ if (tp2 == NULL) {
+ fprintf(stderr, "task[%d] pmid %s optreq " PRINTF_P_PFX "%p for [",
+ i, pmIDStr(pmid), rqp);
+ if (rqp->r_numinst == 0)
+ fputs("`all instances' ", stderr);
+ else
+ for (k = 0; k < rqp->r_numinst; k++)
+ fprintf(stderr, "%d ", rqp->r_instlist[k]);
+ fputs("] bad task pointer\n", stderr);
+ }
+ }
+ }
+ }
+}
+
+void
+dumpit(void)
+{
+ int i;
+ task_t *tp;
+
+ reality_check();
+ for (tp = tasklist, i = 0; tp != NULL; tp = tp->t_next, i++) {
+ fprintf(stderr,
+ "\ntask[%d] @" PRINTF_P_PFX "%p: %s %s \ndelta = %f\n", i, tp,
+ PMLC_GET_MAND(tp->t_state) ? "mandatory " : "advisory ",
+ PMLC_GET_ON(tp->t_state) ? "on " : "off ",
+ tp->t_delta.tv_sec + (float)tp->t_delta.tv_usec / 1.0e6);
+ __pmOptFetchDump(stderr, tp->t_fetch);
+ }
+}
+
+/*
+ * stolen from __pmDumpResult
+ */
+static void
+dumpcontrol(FILE *f, const pmResult *resp, int dovalue)
+{
+ int i;
+ int j;
+
+ fprintf(f,"LogControl dump from " PRINTF_P_PFX "%p", resp);
+ fprintf(f, " numpmid: %d\n", resp->numpmid);
+ for (i = 0; i < resp->numpmid; i++) {
+ pmValueSet *vsp = resp->vset[i];
+ fprintf(f," %s :", pmIDStr(vsp->pmid));
+ if (vsp->numval == 0) {
+ fprintf(f, " No values!\n");
+ continue;
+ }
+ else if (vsp->numval < 0) {
+ fprintf(f, " %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+ fprintf(f, " numval: %d", vsp->numval);
+ fprintf(f, " valfmt: %d", vsp->valfmt);
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (vsp->numval > 1 || vp->inst != PM_INDOM_NULL) {
+ fprintf(f," inst [%d]", vp->inst);
+ }
+ else
+ fprintf(f, " singular");
+ if (dovalue) {
+ fprintf(f, " value ");
+ pmPrintValue(f, vsp->valfmt, PM_TYPE_U32, vp, 1);
+ }
+ fputc('\n', f);
+ }
+ }
+}
+
+#endif
+
+/* Called when optFetch or _pmHash routines fail. This is terminal. */
+void
+die(char *name, int sts)
+{
+ __pmNotifyErr(LOG_ERR, "%s error unrecoverable: %s\n", name, pmErrStr(sts));
+ exit(1);
+}
+
+optreq_t *
+findoptreq(pmID pmid, int inst)
+{
+ __pmHashNode *hp;
+ optreq_t *rqp;
+ optreq_t *all_rqp = NULL;
+ int j;
+
+ /*
+ * Note:
+ * The logic here assumes that for each metric-inst pair, there is
+ * at most one optreq_t structure, corresponding to the logging
+ * state of ON (mandatory or advisory) else OFF (mandatory). Other
+ * requests change the data structures, but do not leave optreq_t
+ * structures lying about, i.e. MAYBE (mandatory) is the default,
+ * and does not have to be explicitly stored, while OFF (advisory)
+ * reverts to MAYBE (mandatory).
+ * There is one exception to the above assumption, namely for
+ * cases where the initial specification includes "all" instances,
+ * then some later concurrent specification may refer to specific
+ * instances ... in this case, the specific optreq_t structure is
+ * the one that applies.
+ */
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if (rqp->r_numinst == 0) {
+ all_rqp = rqp;
+ continue;
+ }
+ for (j = 0; j < rqp->r_numinst; j++)
+ if (inst == rqp->r_instlist[j])
+ return rqp;
+ }
+
+ if (all_rqp != NULL)
+ return all_rqp;
+ else
+ return NULL;
+}
+
+/* Determine whether a metric is currently known. Returns
+ * -1 if metric not known
+ * inclusive OR of the flags below if it is known
+ */
+#define MF_HAS_INDOM 0x1 /* has an instance domain */
+#define MF_HAS_ALL 0x2 /* has an "all instances" */
+#define MF_HAS_INST 0x4 /* has specific instance(s) */
+#define MF_HAS_MAND 0x8 /* has at least one inst with mandatory */
+ /* logging (or is mandatory if no indom) */
+static int
+find_metric(pmID pmid)
+{
+ __pmHashNode *hp;
+ optreq_t *rqp;
+ int result = 0;
+ int found = 0;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if (found++ == 0)
+ if (rqp->r_desc->indom != PM_INDOM_NULL) {
+ result |= MF_HAS_INDOM;
+ if (rqp->r_numinst == 0)
+ result |= MF_HAS_ALL;
+ else
+ result |= MF_HAS_INST;
+ }
+ if (PMLC_GET_MAND(((task_t *)(rqp->r_fetch->f_aux))->t_state))
+ result |= MF_HAS_MAND;
+ }
+ return found ? result : -1;
+}
+
+/* Find an optreq_t suitable for adding a new instance */
+
+/* Add a new metric (given a pmValueSet and a pmDesc) to the specified task.
+ * Allocate and return a new task_t if the specified task pointer is nil.
+ *
+ * Note that this should only be called for metrics not currently in the
+ * logging data structure. All instances in the pmValueSet are added!
+ */
+static int
+add_metric(pmValueSet *vsp, task_t **result)
+{
+ pmID pmid = vsp->pmid;
+ task_t *tp = *result;
+ optreq_t *rqp;
+ pmDesc *dp;
+ char *name;
+ int sts, i, need = 0;
+
+ dp = (pmDesc *)malloc(sizeof(pmDesc));
+ if (dp == NULL) {
+ __pmNoMem("add_metric: new pmDesc malloc", sizeof(pmDesc), PM_FATAL_ERR);
+ }
+ if ((sts = pmLookupDesc(pmid, dp)) < 0)
+ die("add_metric: lookup desc", sts);
+ if ((sts = pmNameID(pmid, &name)) < 0)
+ die("add_metric: lookup name", sts);
+
+ /* allocate a new task if null task pointer passed in */
+ if (tp == NULL) {
+ tp = calloc(1, sizeof(task_t));
+ if (tp == NULL) {
+ __pmNoMem("add_metric: new task calloc", sizeof(task_t), PM_FATAL_ERR);
+ }
+ *result = tp;
+ }
+
+ /* add metric (and any instances specified) to task */
+ i = tp->t_numpmid++;
+ need = tp->t_numpmid * sizeof(pmID);
+ if (!(tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, need)))
+ __pmNoMem("add_metric: new task pmidlist realloc", need, PM_FATAL_ERR);
+ need = tp->t_numpmid * sizeof(char *);
+ if (!(tp->t_namelist = (char **)realloc(tp->t_namelist, need)))
+ __pmNoMem("add_metric: new task namelist realloc", need, PM_FATAL_ERR);
+ need = tp->t_numpmid * sizeof(pmDesc);
+ if (!(tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, need)))
+ __pmNoMem("add_metric: new task desclist realloc", need, PM_FATAL_ERR);
+ tp->t_pmidlist[i] = pmid;
+ tp->t_namelist[i] = name;
+ tp->t_desclist[i] = *dp; /* struct assignment */
+
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("add_metric: new task optreq calloc", need, PM_FATAL_ERR);
+ }
+ rqp->r_desc = dp;
+
+ /* Now copy instances if required. Remember that metrics with singular
+ * values actually have one instance specified to distinguish them from the
+ * "all instances" case (which has no instances). Use the pmDesc to check
+ * for this.
+ */
+ if (dp->indom != PM_INDOM_NULL)
+ need = rqp->r_numinst = vsp->numval;
+ if (need) {
+ need *= sizeof(rqp->r_instlist[0]);
+ rqp->r_instlist = (int *)malloc(need);
+ if (rqp->r_instlist == NULL) {
+ __pmNoMem("add_metric: new task optreq instlist malloc", need,
+ PM_FATAL_ERR);
+ }
+ for (i = 0; i < vsp->numval; i++)
+ rqp->r_instlist[i] = vsp->vlist[i].inst;
+ }
+
+ /* Add new metric to task's fetchgroup(s) and global hash table */
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ linkback(tp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("add_metric: __pmHashAdd", sts);
+ return 0;
+}
+
+/* Return true if a request for a new logging state (newstate) will be honoured
+ * when current state is curstate.
+ */
+static int
+update_ok(int curstate, int newstate)
+{
+ /* If new state is advisory and current is mandatory, reject request.
+ * Any new mandatory state is accepted. If the new state is advisory
+ * and the current state is advisory, it is accepted.
+ * Note that a new state of maybe (mandatory maybe) counts as mandatory
+ */
+ if (PMLC_GET_MAND(newstate) == 0 && PMLC_GET_MAYBE(newstate) == 0 &&
+ PMLC_GET_MAND(curstate))
+ return 0;
+ else
+ return 1;
+}
+
+/* Given a task and a pmID, find an optreq_t associated with the task suitable
+ * for inserting a new instance into.
+ * The one with the smallest number of instances is chosen. We could also
+ * have just used the first, but smallest-first gives a more even distribution.
+ */
+static optreq_t *
+find_instoptreq(task_t *tp, pmID pmid)
+{
+ optreq_t *result = NULL;
+ optreq_t *rqp;
+ int ni = 0;
+ __pmHashNode *hp;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL;
+ hp = hp->next) {
+ if (pmid != (pmID)hp->key)
+ continue;
+ rqp = (optreq_t *)hp->data;
+ if ((task_t *)rqp->r_fetch->f_aux != tp)
+ continue;
+ if (rqp->r_numinst == 0)
+ continue; /* don't want "all instances" cases */
+ if (ni == 0 || rqp->r_numinst < ni) {
+ result = rqp;
+ ni = rqp->r_numinst;
+ }
+ }
+ return result;
+}
+
+/* Delete an optreq_t from its task, free it and remove it from the hash list.
+ */
+static void
+del_optreq(optreq_t *rqp)
+{
+ int sts;
+ task_t *tp = (task_t *)rqp->r_fetch->f_aux;
+
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("del_optreq: __pmOptFetchDel", sts);
+ if ((sts = __pmHashDel(rqp->r_desc->pmid, (void *)rqp, &pm_hash)) < 0)
+ die("del_optreq: __pmHashDel", sts);
+ free(rqp->r_desc);
+ if (rqp->r_numinst)
+ free(rqp->r_instlist);
+ free(rqp);
+ /* TODO: remove pmid from task if that was the last optreq_t for it */
+ /* TODO: remove task if last pmid removed */
+}
+
+/* Delete every instance of a given metric from the data structure.
+ * The pmid is deleted from the pmidlist of every task containing an instance.
+ * Return a pointer to the first pmDesc found (the only thing salvaged from the
+ * smoking ruins), or nil if no instances were found.
+ */
+static pmDesc *
+del_insts(pmID pmid)
+{
+ optreq_t *rqp;
+ __pmHashNode *hp;
+ task_t *tp;
+ pmDesc *dp = NULL;
+ int i, sts, keep;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash); hp != NULL; ) {
+ /* Do that BEFORE we nuke the node */
+ __pmHashNode * nextnode = hp->next;
+
+ if (pmid == (pmID)hp->key) {
+ rqp = (optreq_t *)hp->data;
+ tp = (task_t *)rqp->r_fetch->f_aux;
+ if ((sts = __pmOptFetchDel(&tp->t_fetch, rqp)) < 0)
+ die("del_insts: __pmOptFetchDel", sts);
+ if ((sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("del_insts: __pmHashDel", sts);
+
+ /* save the first pmDesc pointer for return and subsequent
+ * re-use, but free all the others
+ */
+ if (dp != NULL)
+ free(rqp->r_desc);
+ else
+ dp = rqp->r_desc;
+
+ if (rqp->r_numinst)
+ free(rqp->r_instlist);
+ free(rqp);
+
+ /* remove pmid from the task's pmid list */
+ for (i = 0; i < tp->t_numpmid; i++)
+ if (tp->t_pmidlist[i] == pmid)
+ break;
+ keep = (tp->t_numpmid - 1 - i) * sizeof(tp->t_pmidlist[0]);
+ if (keep) {
+ memmove(&tp->t_pmidlist[i], &tp->t_pmidlist[i+1], keep);
+ memmove(&tp->t_desclist[i], &tp->t_desclist[i+1], keep);
+ memmove(&tp->t_namelist[i], &tp->t_namelist[i+1], keep);
+ }
+
+ /* don't bother shrinking the pmidlist */
+ tp->t_numpmid--;
+ if (tp->t_numpmid == 0) {
+ /* TODO: nuke the task if that was the last pmID */
+ }
+ }
+ hp = nextnode;
+ }
+
+ return dp;
+}
+
+/* Update an existing metric (given a pmValueSet) adding it to the specified
+ * task. Allocate and return a new task_t if the specified task pointer is nil.
+ */
+static int
+update_metric(pmValueSet *vsp, int reqstate, int mflags, task_t **result)
+{
+ pmID pmid = vsp->pmid;
+ task_t *ntp = *result; /* pointer to new task */
+ task_t *ctp; /* pointer to current task */
+ optreq_t *rqp;
+ pmDesc *dp;
+ int i, j, inst;
+ int sts, need = 0;
+ int addpmid = 0;
+ int freedp;
+
+ /* allocate a new task if null task pointer passed in */
+ if (ntp == NULL) {
+ ntp = calloc(1, sizeof(task_t));
+ if (ntp == NULL) {
+ __pmNoMem("update_metric: new task calloc", sizeof(task_t),
+ PM_FATAL_ERR);
+ }
+ *result = ntp;
+ }
+
+ if ((mflags & MF_HAS_INDOM) == 0) {
+ rqp = findoptreq(pmid, 0);
+ ctp = (task_t *)(rqp->r_fetch->f_aux);
+ if (!update_ok(ctp->t_state, reqstate))
+ return 1;
+
+ /* if the new state is advisory off, just remove the metric */
+ if ((PMLC_GET_MAYBE(reqstate)) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0))
+ del_optreq(rqp);
+ else {
+ /* update the optreq. For single valued metrics there are no
+ * instances involved so the sole optreq can just be re-used.
+ */
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: 1 metric __pmOptFetchDel", sts);
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ addpmid = 1;
+ }
+ }
+ else {
+ /* metric has an instance domain */
+ if (vsp->numval > 0) {
+ /* tricky: since optFetch can't handle instance profiles of the
+ * form "all except these specific instances", and managing it
+ * manually is just too hard, reject requests for specific
+ * metric instances if "all instances" of the metric are already
+ * being logged.
+ * Note: advisory off "all instances" is excepted since ANY request
+ * overrides and advisory off. E.g. "advisory off all" followed by
+ * "advisory on someinsts" turns on advisory logging for
+ * "someinsts". mflags will be zero for "advisory off" metrics.
+ */
+ if (mflags & MF_HAS_ALL)
+ return 1; /* can't turn "all" into specific insts */
+
+ for (i = 0; i < vsp->numval; i++) {
+ dp = NULL;
+ freedp = 0;
+ inst = vsp->vlist[i].inst;
+ rqp = findoptreq(pmid, inst);
+ if (rqp != NULL) {
+ dp = rqp->r_desc;
+ ctp = (task_t *)(rqp->r_fetch->f_aux);
+ /* no work required if new task and current are the same */
+ if (ntp == ctp)
+ continue;
+ if (!update_ok(ctp->t_state, reqstate))
+ continue;
+
+ /* remove inst's group from current task */
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: instance add __pmOptFetchDel", sts);
+
+ /* put group back if there are any instances left */
+ if (rqp->r_numinst > 1) {
+ /* remove inst from group */
+ for (j = 0; j < rqp->r_numinst; j++)
+ if (inst == rqp->r_instlist[j])
+ break;
+ /* don't call memmove to move zero bytes */
+ if (j < rqp->r_numinst - 1)
+ memmove(&rqp->r_instlist[j], &rqp->r_instlist[j+1],
+ (rqp->r_numinst - 1 - j) *
+ sizeof(rqp->r_instlist[0]));
+ rqp->r_numinst--;
+ /* (don't bother realloc-ing the instlist to a smaller size) */
+
+ __pmOptFetchAdd(&ctp->t_fetch, rqp);
+ linkback(ctp);
+ /* no need to update hash list, rqp already there */
+ }
+ /* if that was the last instance, free the group */
+ else {
+ if (( sts = __pmHashDel(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: instance __pmHashDel", sts);
+ freedp = 1;
+ free(rqp->r_instlist);
+ free(rqp);
+ }
+ }
+
+ /* advisory off (mandatory maybe) metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ if (freedp)
+ free(dp);
+ continue;
+ }
+ addpmid = 1;
+
+ /* try to find an existing optreq_t for the instance */
+ rqp = find_instoptreq(ntp, pmid);
+ if (rqp != NULL) {
+ if ((sts = __pmOptFetchDel(&ntp->t_fetch, rqp)) < 0)
+ die("update_metric: instance add __pmOptFetchDel", sts);
+ }
+ /* no existing optreq_t found, allocate & populate a new one */
+ else {
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("update_metric: optreq calloc",
+ sizeof(optreq_t), PM_FATAL_ERR);
+ }
+ /* if the metric existed but the instance didn't, we don't
+ * have a valid pmDesc (dp), so find one.
+ */
+ if (dp == NULL) {
+ /* find metric and associated pmDesc */
+ __pmHashNode *hp;
+
+ for (hp = __pmHashSearch(pmid, &pm_hash);
+ hp != NULL; hp = hp->next) {
+ if (pmid == (pmID)hp->key)
+ break;
+ }
+ assert(hp != NULL);
+ dp = ((optreq_t *)hp->data)->r_desc;
+ }
+ /* recycle pmDesc from the old group, if possible */
+ if (freedp) {
+ rqp->r_desc = dp;
+ freedp = 0;
+ }
+ /* otherwise allocate & copy a new pmDesc via dp */
+ else {
+ need = sizeof(pmDesc);
+ rqp->r_desc = (pmDesc *)malloc(need);
+ if (rqp->r_desc == NULL) {
+ __pmNoMem("update_metric: new inst pmDesc malloc",
+ need, PM_FATAL_ERR);
+ }
+ memcpy(rqp->r_desc, dp, need);
+ }
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: __pmHashAdd", sts);
+ }
+
+ need = (rqp->r_numinst + 1) * sizeof(rqp->r_instlist[0]);
+ rqp->r_instlist = (int *)realloc(rqp->r_instlist, need);
+ if (rqp->r_instlist == NULL) {
+ __pmNoMem("update_metric: inst list resize", need,
+ PM_FATAL_ERR);
+ }
+ rqp->r_instlist[rqp->r_numinst++] = inst;
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ if (freedp)
+ free(dp);
+ }
+ }
+ /* the vset has numval == 0, a request for "all instances" */
+ else {
+ /* if the metric is a singular instance that has mandatory logging
+ * or has at least one instance with mandatory logging on, a
+ * request for advisory logging cannot be honoured
+ */
+ if ((mflags & MF_HAS_MAND) &&
+ PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_MAYBE(reqstate) == 0)
+ return 1;
+
+ if (mflags & MF_HAS_ALL) {
+ /* if there is an "all instances" for the metric, it will be
+ * the only optreq_t for the metric
+ */
+ rqp = findoptreq(pmid, 0);
+ ctp = (task_t *)rqp->r_fetch->f_aux;
+
+ /* if the metric is "advisory on, all instances" and the
+ * request is for "mandatory maybe, all instances" the current
+ * advisory logging state of the metric is retained
+ */
+ if (PMLC_GET_MAND(ctp->t_state) == 0 && PMLC_GET_MAYBE(reqstate))
+ return 0;
+
+ /* advisory off & mandatory maybe metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ del_optreq(rqp);
+ return 0;
+ }
+
+ addpmid = 1;
+ if ((sts = __pmOptFetchDel(&ctp->t_fetch, rqp)) < 0)
+ die("update_metric: all inst __pmOptFetchDel", sts);
+ /* don't delete from hash list, rqp re-used */
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ }
+ else {
+ /* there are one or more specific instances for the metric.
+ * The metric cannot have an "all instances" at the same time.
+ *
+ * if the request is for "mandatory maybe, all instances" and
+ * the only instances of the metric all have advisory logging
+ * on, retain the current advisory semantics.
+ */
+ if (PMLC_GET_MAYBE(reqstate) &&
+ (mflags & MF_HAS_INST) && !(mflags & MF_HAS_MAND))
+ return 0;
+
+ dp = del_insts(pmid);
+
+ /* advisory off (mandatory maybe) metrics don't get put into
+ * the data structure
+ */
+ if (PMLC_GET_MAYBE(reqstate) ||
+ (PMLC_GET_MAND(reqstate) == 0 && PMLC_GET_ON(reqstate) == 0)) {
+ free(dp);
+ return 0;
+ }
+
+ addpmid = 1;
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL) {
+ __pmNoMem("update_metric: all inst calloc",
+ sizeof(optreq_t), PM_FATAL_ERR);
+ }
+ rqp->r_desc = dp;
+ __pmOptFetchAdd(&ntp->t_fetch, rqp);
+ linkback(ntp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0)
+ die("update_metric: all inst __pmHashAdd", sts);
+ }
+ }
+ }
+
+ if (!addpmid)
+ return 0;
+
+ /* add pmid to new task if not already there */
+ for (i = 0; i < ntp->t_numpmid; i++)
+ if (pmid == ntp->t_pmidlist[i])
+ break;
+ if (i >= ntp->t_numpmid) {
+ pmDesc desc;
+ char *name;
+ int need;
+
+ if ((sts = pmLookupDesc(pmid, &desc)) < 0)
+ die("update_metric: cannot lookup desc", sts);
+ if ((sts = pmNameID(pmid, &name)) < 0)
+ die("update_metric: cannot lookup name", sts);
+
+ need = (ntp->t_numpmid + 1) * sizeof(pmID);
+ if (!(ntp->t_pmidlist = (pmID *)realloc(ntp->t_pmidlist, need)))
+ __pmNoMem("update_metric: grow task pmidlist", need, PM_FATAL_ERR);
+ need = (ntp->t_numpmid + 1) * sizeof(char *);
+ if (!(ntp->t_namelist = (char **)realloc(ntp->t_namelist, need)))
+ __pmNoMem("update_metric: grow task namelist", need, PM_FATAL_ERR);
+ need = (ntp->t_numpmid + 1) * sizeof(pmDesc);
+ if (!(ntp->t_desclist = (pmDesc *)realloc(ntp->t_desclist, need)))
+ __pmNoMem("update_metric: grow task desclist", need, PM_FATAL_ERR);
+ i = ntp->t_numpmid;
+ ntp->t_pmidlist[i] = pmid;
+ ntp->t_namelist[i] = name;
+ ntp->t_desclist[i] = desc;
+ ntp->t_numpmid++;
+ }
+ return 0;
+}
+
+/* Given a state and a delta, return the first matching task.
+ * Return NULL if a matching task was not found.
+ */
+task_t *
+find_task(int state, struct timeval *delta)
+{
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (state == (tp->t_state & 0x3) && /* MAND|ON */
+ delta->tv_sec == tp->t_delta.tv_sec &&
+ delta->tv_usec == tp->t_delta.tv_usec)
+ break;
+ }
+ return tp;
+}
+
+/* Return a mask containing the history flags for a given metric/instance.
+ * the history flags indicate whether the metric/instance is in the log at all
+ * and whether the last fetch of the metric/instance was successful.
+ *
+ * The result is suitable for ORing into the result returned by a control log
+ * request.
+ */
+static int
+gethistflags(pmID pmid, int inst)
+{
+ __pmHashNode *hp;
+ pmidhist_t *php;
+ insthist_t *ihp;
+ int i, found;
+ int val;
+
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
+ if ((pmID)hp->key == pmid)
+ break;
+ if (hp == NULL)
+ return 0;
+ php = (pmidhist_t *)hp->data;
+ ihp = &php->ph_instlist[0];
+ val = 0;
+ if (php->ph_indom != PM_INDOM_NULL) {
+ for (i = 0; i < php->ph_numinst; i++, ihp++)
+ if (ihp->ih_inst == inst)
+ break;
+ found = i < php->ph_numinst;
+ }
+ else
+ found = php->ph_numinst > 0;
+ if (found) {
+ PMLC_SET_INLOG(val, 1);
+ val |= ihp->ih_flags; /* only "available flag" is ever set */
+ }
+ return val;
+}
+
+/* take a pmResult (from a control log request) and half-clone it: return a
+ * pointer to a new pmResult struct which shares the pmValueSets in the
+ * original that have numval > 0, and has null pointers for the pmValueSets
+ * in the original with numval <= 0
+ */
+static pmResult *
+siamise_request(pmResult *request)
+{
+ int i, need;
+ pmValueSet *vsp;
+ pmResult *result;
+
+ need = sizeof(pmResult) + (request->numpmid - 1) * sizeof(pmValueSet *);
+ result = (pmResult *)malloc(need);
+ if (result == NULL) {
+ __pmNoMem("siamise_request: malloc pmResult", need, PM_FATAL_ERR);
+ }
+ for (i = 0; i < request->numpmid; i++) {
+ vsp = request->vset[i];
+ if (vsp->numval > 0)
+ result->vset[i] = request->vset[i];
+ else
+ result->vset[i] = NULL;
+ }
+ result->timestamp = request->timestamp; /* structure assignment */
+ result->numpmid = request->numpmid;
+
+ return result;
+}
+
+/* Temporarily borrow a bit in the metric/instance history to indicate that
+ * the instance currently exists in the instance domain. The macros below
+ * set and get the bit, which is cleared after we are finished with it here.
+ */
+
+#define PMLC_SET_USEINDOM(val, flag) (val = (val & ~0x1000) | (flag << 12 ))
+#define PMLC_GET_USEINDOM(val) ((val & 0x1000) >> 12)
+
+/* create a pmValueSet large enough to contain the union of the current
+ * instance domain of the specified metric and any previous instances from
+ * the history list.
+ */
+static pmValueSet *
+build_vset(pmID pmid, int usehist)
+{
+ __pmHashNode *hp;
+ pmidhist_t *php = NULL;
+ insthist_t *ihp;
+ int need = 0;
+ int i, numindom = 0;
+ pmDesc desc;
+ int have_desc;
+ int *instlist = NULL;
+ char **namelist = NULL;
+ pmValueSet *vsp;
+
+ if (usehist) {
+ /* find the number of instances of the metric in the history (1 if
+ * single-valued metric)
+ */
+ for (hp = __pmHashSearch(pmid, &hist_hash); hp != NULL; hp = hp->next)
+ if ((pmID)hp->key == pmid)
+ break;
+ if (hp != NULL) {
+ php = (pmidhist_t *)hp->data;
+ need = php->ph_numinst;
+ }
+ }
+ /*
+ * get the current instance domain, so that if the metric hasn't been
+ * logged yet a sensible result is returned.
+ */
+ if ((have_desc = pmLookupDesc(pmid, &desc)) < 0)
+ goto no_info;
+ if (desc.indom == PM_INDOM_NULL)
+ need = 1; /* will be same in history */
+ else {
+ int j;
+
+ if ((numindom = pmGetInDom(desc.indom, &instlist, &namelist)) < 0) {
+ have_desc = numindom;
+ goto no_info;
+ }
+ /* php will be null if usehist is false or there is no history yet */
+ if (php == NULL)
+ need = numindom; /* no history => use indom */
+ else
+ for (i = 0; i < numindom; i++) {
+ int inst = instlist[i];
+
+ for (j = 0; j < php->ph_numinst; j++)
+ if (inst == php->ph_instlist[j].ih_inst)
+ break;
+ /*
+ * if instance is in history but not instance domain, leave
+ * extra space for it in vset, otherwise use the USEINDOM
+ * flag to avoid another NxM comparison when building the vset
+ * instances later.
+ */
+ if (j >= php->ph_numinst)
+ need++;
+ else
+ PMLC_SET_USEINDOM(php->ph_instlist[j].ih_flags, 1);
+ }
+ }
+
+no_info:
+
+ need = sizeof(pmValueSet) + (need - 1) * sizeof(pmValue);
+ vsp = (pmValueSet *)malloc(need);
+ if (vsp == NULL) {
+ __pmNoMem("build_vset for control/enquire", need, PM_FATAL_ERR);
+ }
+ vsp->pmid = pmid;
+ if (have_desc < 0) {
+ vsp->numval = have_desc;
+ }
+ else if (desc.indom == PM_INDOM_NULL) {
+ vsp->vlist[0].inst = PM_IN_NULL;
+ vsp->numval = 1;
+ }
+ else {
+ int j;
+
+ i = 0;
+ /* get instances out of instance domain first */
+ if (numindom > 0)
+ for (j = 0; j < numindom; j++)
+ vsp->vlist[i++].inst = instlist[j];
+
+ /* then any not in instance domain from history */
+ if (php != NULL) {
+ ihp = &php->ph_instlist[0];
+ for (j = 0; j < php->ph_numinst; j++, ihp++)
+ if (PMLC_GET_USEINDOM(ihp->ih_flags))
+ /* it's already in the indom */
+ PMLC_SET_USEINDOM(ihp->ih_flags, 0);
+ else
+ vsp->vlist[i++].inst = ihp->ih_inst;
+ }
+ vsp->numval = i;
+ }
+ if (instlist)
+ free(instlist);
+ if (namelist)
+ free(namelist);
+
+ return vsp;
+}
+
+static int
+do_control(__pmPDU *pb)
+{
+ int sts;
+ int control;
+ int state;
+ int delta;
+ pmResult *request;
+ pmResult *result;
+ int siamised = 0; /* the verb from siamese (as in twins) */
+ int i;
+ int j;
+ int val;
+ pmValueSet *vsp;
+ optreq_t *rqp;
+ task_t *tp;
+ time_t now;
+ int reqstate = 0;
+
+ /*
+ * TODO - encoding for logging interval in requests and results?
+ */
+ if ((sts = __pmDecodeLogControl(pb, &request, &control, &state, &delta)) < 0)
+ return sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "do_control: control=%d state=%d delta=%d request ...\n",
+ control, state, delta);
+ dumpcontrol(stderr, request, 0);
+ }
+#endif
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ time(&now);
+ fprintf(stderr, "\n%s", ctime(&now));
+ fprintf(stderr, "pmlc request from %s: %s",
+ pmlc_host, control == PM_LOG_MANDATORY ? "mandatory" : "advisory");
+ if (state == PM_LOG_ON) {
+ if (delta == 0)
+ fprintf(stderr, " on once\n");
+ else
+ fprintf(stderr, " on %.1f sec\n", (float)delta/1000);
+ }
+ else if (state == PM_LOG_OFF)
+ fprintf(stderr, " off\n");
+ else
+ fprintf(stderr, " maybe\n");
+ }
+
+ /*
+ * access control checks
+ */
+ sts = 0;
+ switch (control) {
+ case PM_LOG_MANDATORY:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ case PM_LOG_ADVISORY:
+ if (denyops & PM_OP_LOG_ADV)
+ sts = PM_ERR_PERMISSION;
+ break;
+
+ case PM_LOG_ENQUIRE:
+ /*
+ * Don't need to check [access] as you have to have _some_
+ * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
+ * and PM_OP_LOG_ENQ) to make a connection ... and if you
+ * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
+ * sense to deny PM_OP_LOG_ENQ operations.
+ */
+ break;
+
+ default:
+ fprintf(stderr, "Bad control PDU type %d\n", control);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ if (sts < 0) {
+ fprintf(stderr, "Error: %s\n", pmErrStr(sts));
+ if ((sts = __pmSendError(clientfd, FROM_ANON, sts)) < 0)
+ __pmNotifyErr(LOG_ERR,
+ "do_control: error sending Error PDU to client: %s\n",
+ pmErrStr(sts));
+ pmFreeResult(request);
+ return sts;
+ }
+
+ /* handle everything except PM_LOG_ENQUIRE */
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ /* update the logging status of metrics */
+
+ task_t *newtp = NULL; /* task for metrics/insts in request */
+ struct timeval tdelta = { 0 };
+ int newtask;
+ int mflags;
+
+ /* convert state and control to the bitmask used in pmlogger and values
+ * returned in results. Remember that reqstate starts with nothing on.
+ */
+ if (state == PM_LOG_ON)
+ PMLC_SET_ON(reqstate, 1);
+ else
+ PMLC_SET_ON(reqstate, 0);
+ if (control == PM_LOG_MANDATORY) {
+ if (state == PM_LOG_MAYBE)
+ /* mandatory+maybe => maybe+advisory+off */
+ PMLC_SET_MAYBE(reqstate, 1);
+ else
+ PMLC_SET_MAND(reqstate, 1);
+ }
+
+ /* try to find an existing task for the request
+ * Never return a "once only" task, it may have gone off already and just
+ * be hanging around like a bad smell.
+ */
+ if (delta != 0) {
+ tdelta.tv_sec = delta / 1000;
+ tdelta.tv_usec = (delta % 1000) * 1000;
+ newtp = find_task(reqstate, &tdelta);
+ }
+ newtask = (newtp == NULL);
+
+ for (i = 0; i < request->numpmid; i++) {
+ vsp = request->vset[i];
+ if (vsp->numval < 0)
+ /*
+ * request is malformed, as we cannot control logging
+ * for an undefined instance ... there is no way to
+ * return an error from here, so simply ignore this
+ * metric
+ */
+ continue;
+ mflags = find_metric(vsp->pmid);
+ if (mflags < 0) {
+ /* only add new metrics if they are ON or MANDATORY OFF
+ * Careful: mandatory+maybe is mandatory+maybe+off
+ */
+ if (PMLC_GET_ON(reqstate) ||
+ (PMLC_GET_MAND(reqstate) && !PMLC_GET_MAYBE(reqstate)))
+ add_metric(vsp, &newtp);
+ }
+ else
+ /* already a specification for this metric */
+ update_metric(vsp, reqstate, mflags, &newtp);
+ }
+
+ /* schedule new logging task if new metric(s) specified */
+ if (newtask && newtp != NULL) {
+ if (newtp->t_fetch == NULL) {
+ /* the new task ended up with no fetch groups, throw it away */
+ if (newtp->t_pmidlist != NULL)
+ free(newtp->t_pmidlist);
+ free(newtp);
+ }
+ else {
+ /* link new task into tasklist */
+ newtp->t_next = tasklist;
+ tasklist = newtp;
+
+ /* use only the MAND/ADV and ON/OFF bits of reqstate */
+ newtp->t_state = PMLC_GET_STATE(reqstate);
+ if (PMLC_GET_ON(reqstate)) {
+ newtp->t_delta = tdelta;
+ newtp->t_afid = __pmAFregister(&tdelta, (void *)newtp,
+ log_callback);
+ }
+ else
+ newtp->t_delta.tv_sec = newtp->t_delta.tv_usec = 0;
+ linkback(newtp);
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ dumpit();
+#endif
+
+ /* just ignore advisory+maybe---the returned pmResult will have the metrics
+ * in their original state indicating that the request could not be
+ * satisfied.
+ */
+
+ result = request;
+ result->timestamp.tv_sec = result->timestamp.tv_usec = 0; /* for purify */
+ /* write the current state of affairs into the result _pmResult */
+ for (i = 0; i < request->numpmid; i++) {
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ char *p;
+
+ sts = pmNameID(request->vset[i]->pmid, &p);
+ if (sts < 0)
+ fprintf(stderr, " metric: %s", pmIDStr(request->vset[i]->pmid));
+ else {
+ fprintf(stderr, " metric: %s", p);
+ free(p);
+ }
+ }
+
+ if (request->vset[i]->numval <= 0 && !siamised) {
+ result = siamise_request(request);
+ siamised = 1;
+ }
+ /*
+ * pmids with numval <= 0 in the request have a null vset ptr in the
+ * in the corresponding place in the siamised result.
+ */
+ if (result->vset[i] != NULL)
+ vsp = result->vset[i];
+ else {
+ /* the result should also contain the history for an all instances
+ * enquire request. Control requests just get the current indom
+ * since the user of pmlc really wants to see what's being logged
+ * now rather than in the past.
+ */
+ vsp = build_vset(request->vset[i]->pmid, control == PM_LOG_ENQUIRE);
+ result->vset[i] = vsp;
+ }
+ vsp->valfmt = PM_VAL_INSITU;
+ for (j = 0; j < vsp->numval; j++) {
+ rqp = findoptreq(vsp->pmid, vsp->vlist[j].inst);
+ val = 0;
+ if (rqp == NULL) {
+ PMLC_SET_STATE(val, 0);
+ PMLC_SET_DELTA(val, 0);
+ }
+ else {
+ tp = rqp->r_fetch->f_aux;
+ PMLC_SET_STATE(val, tp->t_state);
+ PMLC_SET_DELTA(val, (tp->t_delta.tv_sec*1000 + tp->t_delta.tv_usec/1000));
+ }
+
+ val |= gethistflags(vsp->pmid, vsp->vlist[j].inst);
+ vsp->vlist[j].value.lval = val;
+
+ if (control == PM_LOG_MANDATORY || control == PM_LOG_ADVISORY) {
+ int expstate = 0;
+ int statemask = 0;
+ int expdelta;
+ if (rqp != NULL && rqp->r_desc->indom != PM_INDOM_NULL) {
+ char *p;
+ if (j == 0)
+ fputc('\n', stderr);
+ if (pmNameInDom(rqp->r_desc->indom, vsp->vlist[j].inst, &p) >= 0) {
+ fprintf(stderr, " instance: %s", p);
+ free(p);
+ }
+ else
+ fprintf(stderr, " instance: #%d", vsp->vlist[j].inst);
+ }
+ else {
+ /* no pmDesc ... punt */
+ if (vsp->numval > 1 || vsp->vlist[j].inst != PM_IN_NULL) {
+ if (j == 0)
+ fputc('\n', stderr);
+ fprintf(stderr, " instance: #%d", vsp->vlist[j].inst);
+ }
+ }
+ if (state != PM_LOG_MAYBE) {
+ if (control == PM_LOG_MANDATORY)
+ PMLC_SET_MAND(expstate, 1);
+ else
+ PMLC_SET_MAND(expstate, 0);
+ if (state == PM_LOG_ON)
+ PMLC_SET_ON(expstate, 1);
+ else
+ PMLC_SET_ON(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ PMLC_SET_ON(statemask, 1);
+ }
+ else {
+ PMLC_SET_MAND(expstate, 0);
+ PMLC_SET_MAND(statemask, 1);
+ }
+ expdelta = PMLC_GET_ON(expstate) ? delta : 0;
+ if ((PMLC_GET_STATE(val) & statemask) != expstate ||
+ PMLC_GET_DELTA(val) != expdelta)
+ fprintf(stderr, " [request failed]");
+ fputc('\n', stderr);
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ __pmDumpResult(stderr, result);
+ }
+#endif
+
+ if ((sts = __pmSendResult(clientfd, FROM_ANON, result)) < 0)
+ __pmNotifyErr(LOG_ERR,
+ "do_control: error sending Error PDU to client: %s\n",
+ pmErrStr(sts));
+
+ if (siamised) {
+ for (i = 0; i < request->numpmid; i++)
+ if (request->vset[i]->numval <= 0)
+ free(result->vset[i]);
+ free(result);
+ }
+ pmFreeResult(request);
+
+ return 0;
+}
+
+/*
+ * sendstatus
+ */
+static int
+sendstatus(void)
+{
+ int rv;
+ int end;
+ int version;
+ static int firsttime = 1;
+ static char *tzlogger;
+ struct timeval now;
+
+ if (firsttime) {
+ tzlogger = __pmTimezone();
+ firsttime = 0;
+ }
+
+ if ((version = __pmVersionIPC(clientfd)) < 0)
+ return version;
+
+ if (version >= LOG_PDU_VERSION2) {
+ __pmLoggerStatus ls;
+
+ if ((ls.ls_state = logctl.l_state) == PM_LOG_STATE_NEW)
+ ls.ls_start.tv_sec = ls.ls_start.tv_usec = 0;
+ else
+ memcpy(&ls.ls_start, &logctl.l_label.ill_start, sizeof(ls.ls_start));
+ memcpy(&ls.ls_last, &last_stamp, sizeof(ls.ls_last));
+ __pmtimevalNow(&now);
+ ls.ls_timenow.tv_sec = (__int32_t)now.tv_sec;
+ ls.ls_timenow.tv_usec = (__int32_t)now.tv_usec;
+ ls.ls_vol = logctl.l_curvol;
+ ls.ls_size = ftell(logctl.l_mfp);
+ assert(ls.ls_size >= 0);
+
+ /* be careful of buffer size mismatches when copying strings */
+ end = sizeof(ls.ls_hostname) - 1;
+ strncpy(ls.ls_hostname, logctl.l_label.ill_hostname, end);
+ ls.ls_hostname[end] = '\0';
+ /* BTW, that string should equal pmcd_host[]. */
+
+ /* NB: FQDN cleanup: there is no such thing as 'the fully
+ qualified domain name' of a server: it may have several or
+ none; the set may have changed since the time the log
+ archive was collected. Now that we store the then-current
+ pmcd.hostname in the ill_hostname (and thus get it reported
+ in ls_hostname), we could pass something else informative
+ in the ls_fqdn slot. Namely, pmcd_host_conn[], which is the
+ access path pmlogger's using to get to the pmcd. */
+ end = sizeof(ls.ls_fqdn) - 1;
+ strncpy(ls.ls_fqdn, pmcd_host_conn, end);
+ ls.ls_fqdn[end] = '\0';
+
+ end = sizeof(ls.ls_tz) - 1;
+ strncpy(ls.ls_tz, logctl.l_label.ill_tz, end);
+ ls.ls_tz[end] = '\0';
+ end = sizeof(ls.ls_tzlogger) - 1;
+ if (tzlogger != NULL)
+ strncpy(ls.ls_tzlogger, tzlogger, end);
+ else
+ end = 0;
+ ls.ls_tzlogger[end] = '\0';
+
+ rv = __pmSendLogStatus(clientfd, &ls);
+ }
+ else
+ rv = PM_ERR_IPC;
+ return rv;
+}
+
+static int
+do_request(__pmPDU *pb)
+{
+ int sts;
+ int type;
+
+ if ((sts = __pmDecodeLogRequest(pb, &type)) < 0) {
+ __pmNotifyErr(LOG_ERR, "do_request: error decoding PDU: %s\n", pmErrStr(sts));
+ return PM_ERR_IPC;
+ }
+
+ switch (type) {
+ case LOG_REQUEST_STATUS:
+ /*
+ * Don't need to check [access] as you have to have _some_
+ * permission (at least one of PM_OP_LOG_ADV or PM_OP_LOG_MAND
+ * and PM_OP_LOG_ENQ) to make a connection ... and if you
+ * have either PM_OP_LOG_ADV or PM_OP_LOG_MAND it makes no
+ * sense to deny LOG_REQUEST_STATUS operations.
+ * Also, this is needed internally by pmlc to discover pmcd's
+ * hostname.
+ */
+ sts = sendstatus();
+ break;
+
+ case LOG_REQUEST_NEWVOLUME:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ sts = newvolume(VOL_SW_PMLC);
+ if (sts >= 0)
+ sts = logctl.l_label.ill_vol;
+ sts = __pmSendError(clientfd, FROM_ANON, sts);
+ }
+ break;
+
+ case LOG_REQUEST_SYNC:
+ /*
+ * Don't need to check access controls, as this is now
+ * a no-op with unbuffered I/O from pmlogger.
+ *
+ * Do nothing, simply send status 0 back to pmlc.
+ */
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ break;
+
+ /*
+ * QA support ... intended for error injection
+ * If the request is > QA_OFF then this is a code to enable
+ * a specific style of error behaviour. If the request
+ * is QA_OFF, this disables the error behaviour.
+ *
+ * Supported behaviours.
+ * QA_SLEEPY
+ * After this exchange with pmlc, sleep for 5 seconds
+ * after each incoming pmlc request ... allows testing
+ * of timeout logic in pmlc
+ */
+
+ case QA_OFF:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ qa_case = 0;
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ }
+ break;
+
+ case QA_SLEEPY:
+ if (denyops & PM_OP_LOG_MAND)
+ sts = __pmSendError(clientfd, FROM_ANON, PM_ERR_PERMISSION);
+ else {
+ qa_case = type;
+ sts = __pmSendError(clientfd, FROM_ANON, 0);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "do_request: bad request type %d\n", type);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ return sts;
+}
+
+static int
+do_creds(__pmPDU *pb)
+{
+ int i;
+ int sts;
+ int version = UNKNOWN_VERSION;
+ int credcount;
+ int sender;
+ __pmCred *credlist = NULL;
+
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) {
+ __pmNotifyErr(LOG_ERR, "do_creds: error decoding PDU: %s\n", pmErrStr(sts));
+ return PM_ERR_IPC;
+ }
+
+ for (i = 0; i < credcount; i++) {
+ if (credlist[i].c_type == CVERSION) {
+ version = credlist[i].c_vala;
+ if ((sts = __pmSetVersionIPC(clientfd, version)) < 0) {
+ free(credlist);
+ return sts;
+ }
+ }
+ }
+
+ if (credlist)
+ free(credlist);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "do_creds: pmlc version=%d\n", version);
+#endif
+
+ return sts;
+}
+
+/*
+ * Service a request from the pmlogger client.
+ * Return non-zero if the client has closed the connection.
+ */
+int
+client_req(void)
+{
+ int sts;
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ int pinpdu;
+
+ if ((pinpdu = sts = __pmGetPDU(clientfd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) <= 0) {
+ if (sts != 0)
+ fprintf(stderr, "client_req: %s\n", pmErrStr(sts));
+ return 1;
+ }
+ if (qa_case == QA_SLEEPY) {
+ /* error injection - delay before processing and responding */
+ sleep(5);
+ }
+ php = (__pmPDUHdr *)pb;
+ sts = 0;
+
+ switch (php->type) {
+ case PDU_CREDS: /* version 2 PDU */
+ sts = do_creds(pb);
+ break;
+ case PDU_LOG_REQUEST: /* version 2 PDU */
+ sts = do_request(pb);
+ break;
+ case PDU_LOG_CONTROL: /* version 2 PDU */
+ sts = do_control(pb);
+ break;
+ default: /* unknown PDU */
+ fprintf(stderr, "client_req: bad PDU type 0x%x\n", php->type);
+ sts = PM_ERR_IPC;
+ break;
+ }
+ if (pinpdu > 0)
+ __pmUnpinPDUBuf(pb);
+
+ if (sts >= 0)
+ return 0;
+ else {
+ /* the client isn't playing by the rules */
+ __pmSendError(clientfd, FROM_ANON, sts);
+ return 1;
+ }
+}
diff --git a/src/pmlogger/src/error.c b/src/pmlogger/src/error.c
new file mode 100644
index 0000000..2eb3b1f
--- /dev/null
+++ b/src/pmlogger/src/error.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "logger.h"
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n%s\n", configfile, lineno, s);
+}
+
+void
+yyerror(char *s)
+{
+
+ fprintf(stderr, "Specification error in configuration file (%s)\n",
+ configfile);
+ fprintf(stderr, "[line %d] %s\n", lineno, s);
+ exit(1);
+}
diff --git a/src/pmlogger/src/events.c b/src/pmlogger/src/events.c
new file mode 100644
index 0000000..a8f4ec6
--- /dev/null
+++ b/src/pmlogger/src/events.c
@@ -0,0 +1,113 @@
+/*
+ * Unpack an array of event records
+ * Free space from unpack
+ *
+ * Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+/*
+ * Handle event records.
+ *
+ * Walk the packed array of events using similar logic to
+ * pmUnpackEventRecords() but we don't need any allocations.
+ *
+ * For each embedded event parameter, make sure the metadata for
+ * the associated metric is added to the archive.
+ */
+int
+do_events(pmValueSet *vsp)
+{
+ pmEventArray *eap;
+ char *base;
+ pmEventRecord *erp;
+ pmEventParameter *epp;
+ int r; /* records */
+ int p; /* parameters in a record ... */
+ int i; /* instances ... */
+ int sts;
+ pmDesc desc;
+
+ for (i = 0; i < vsp->numval; i++) {
+ if ((sts = __pmCheckEventRecords(vsp, i)) < 0) {
+ __pmDumpEventRecords(stderr, vsp, i);
+ return sts;
+ }
+ eap = (pmEventArray *)vsp->vlist[i].value.pval;
+ if (eap->ea_nrecords == 0)
+ return 0;
+ base = (char *)&eap->ea_record[0];
+ for (r = 0; r < eap->ea_nrecords; r++) {
+ erp = (pmEventRecord *)base;
+ base += sizeof(erp->er_timestamp) + sizeof(erp->er_flags) + sizeof(erp->er_nparams);
+ if (erp->er_flags & PM_EVENT_FLAG_MISSED) {
+ /*
+ * no event "parameters" here, just a missed records count
+ * in er_nparams
+ */
+ continue;
+ }
+ for (p = 0; p < erp->er_nparams; p++) {
+ epp = (pmEventParameter *)base;
+ base += sizeof(epp->ep_pmid) + PM_PDU_SIZE_BYTES(epp->ep_len);
+ sts = __pmLogLookupDesc(&logctl, epp->ep_pmid, &desc);
+ if (sts < 0) {
+ int numnames;
+ char **names;
+ numnames = pmNameAll(epp->ep_pmid, &names);
+ if (numnames < 0) {
+ /*
+ * Event parameter metric not defined in the PMNS.
+ * This should not happen, but is probably not fatal, so
+ * issue a warning and make up a name based on the pmid
+ * event_param.<domain>.<cluster>.<item>
+ */
+ char *name;
+ size_t name_size = strlen("event_param")+3+1+4+1+4+1;
+ names = (char **)malloc(sizeof(char*) + name_size);
+ if (names == NULL)
+ return -oserror();
+ name = (char *)&names[1];
+ names[0] = name;
+ snprintf(name, name_size, "event_param.%s", pmIDStr(epp->ep_pmid));
+ fprintf(stderr, "Warning: metric %s has no name, using %s\n", pmIDStr(epp->ep_pmid), name);
+ }
+ sts = pmLookupDesc(epp->ep_pmid, &desc);
+ if (sts < 0) {
+ /* Event parameter metric does not have a pmDesc.
+ * This should not happen, but is probably not entirely
+ * fatal (although more serious than not having a metric
+ * name), issue a warning and construct a minimalist
+ * pmDesc
+ */
+ desc.pmid = epp->ep_pmid;
+ desc.type = PM_TYPE_AGGREGATE;
+ desc.indom = PM_INDOM_NULL;
+ desc.sem = PM_SEM_DISCRETE;
+ memset(&desc.units, '\0', sizeof(desc.units));
+ fprintf(stderr, "Warning: metric %s (%s) has no descriptor, using a default one\n", names[0], pmIDStr(epp->ep_pmid));
+ }
+ if ((sts = __pmLogPutDesc(&logctl, &desc, numnames, names)) < 0) {
+ fprintf(stderr, "__pmLogPutDesc: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ free(names);
+ }
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/pmlogger/src/fetch.c b/src/pmlogger/src/fetch.c
new file mode 100644
index 0000000..05e0378
--- /dev/null
+++ b/src/pmlogger/src/fetch.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * Thread-safe note
+ *
+ * myFetch() returns a PDU buffer that is pinned from _pmGetPDU() or
+ * __pmEncodeResult() and this needs to be unpinned by the myFetch()
+ * caller when safe to do so.
+ */
+
+#include "logger.h"
+
+int
+myFetch(int numpmid, pmID pmidlist[], __pmPDU **pdup)
+{
+ int n = 0;
+ int ctx;
+ __pmPDU *pb;
+ __pmContext *ctxp;
+
+ if (numpmid < 1)
+ return PM_ERR_TOOSMALL;
+
+ if ((ctx = pmWhichContext()) >= 0) {
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctxp == NULL)
+ return PM_ERR_NOCONTEXT;
+ if (ctxp->c_type != PM_CONTEXT_HOST) {
+ PM_UNLOCK(ctxp->c_lock);
+ return PM_ERR_NOTHOST;
+ }
+ }
+ else
+ return PM_ERR_NOCONTEXT;
+
+#if CAN_RECONNECT
+ if (ctxp->c_pmcd->pc_fd == -1) {
+ /* lost connection, try to get it back */
+ n = reconnect();
+ if (n < 0) {
+ PM_UNLOCK(ctxp->c_lock);
+ return n;
+ }
+ }
+#endif
+
+ if (ctxp->c_sent == 0) {
+ /*
+ * current profile is _not_ already cached at other end of
+ * IPC, so send current profile
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PROFILE)
+ fprintf(stderr, "myFetch: calling __pmSendProfile, context: %d\n", ctx);
+#endif
+ if ((n = __pmSendProfile(ctxp->c_pmcd->pc_fd, FROM_ANON, ctx, ctxp->c_instprof)) >= 0)
+ ctxp->c_sent = 1;
+ }
+
+ if (n >= 0) {
+ int newcnt;
+ pmID *newlist = NULL;
+ int have_dm;
+
+ /* for derived metrics, may need to rewrite the pmidlist */
+ have_dm = newcnt = __pmPrepareFetch(ctxp, numpmid, pmidlist, &newlist);
+ if (newcnt > numpmid) {
+ /* replace args passed into myFetch */
+ numpmid = newcnt;
+ pmidlist = newlist;
+ }
+
+ n = __pmSendFetch(ctxp->c_pmcd->pc_fd, FROM_ANON, ctx, &ctxp->c_origin, numpmid, pmidlist);
+ if (n >= 0){
+ int changed = 0;
+ do {
+ n = __pmGetPDU(ctxp->c_pmcd->pc_fd, ANY_SIZE, TIMEOUT_DEFAULT, &pb);
+ /*
+ * expect PDU_RESULT or
+ * PDU_ERROR(changed > 0)+PDU_RESULT or
+ * PDU_ERROR(real error < 0 from PMCD) or
+ * 0 (end of file)
+ * < 0 (local error or IPC problem)
+ * other (bogus PDU)
+ */
+ if (n == PDU_RESULT) {
+ /*
+ * Success with a pmResult in a pdubuf.
+ *
+ * Need to process derived metrics, if any.
+ * This is ugly, we need to decode the pdubuf, rebuild
+ * the pmResult and encode back into a pdubuf ... the
+ * fastpath of not doing all of this needs to be
+ * preserved in the common case where derived metrics
+ * are not being logged.
+ */
+ if (have_dm) {
+ pmResult *result;
+ __pmPDU *npb;
+ int sts;
+ if ((sts = __pmDecodeResult(pb, &result)) < 0) {
+ n = sts;
+ }
+ else {
+ __pmFinishResult(ctxp, sts, &result);
+ if ((sts = __pmEncodeResult(ctxp->c_pmcd->pc_fd, result, &npb)) < 0)
+ n = sts;
+ else {
+ /* using PDU with derived metrics */
+ __pmUnpinPDUBuf(pb);
+ *pdup = npb;
+ }
+ }
+ }
+ else
+ *pdup = pb;
+ }
+ else if (n == PDU_ERROR) {
+ __pmDecodeError(pb, &n);
+ if (n > 0) {
+ /* PMCD state change protocol */
+ changed = n;
+ n = 0;
+ }
+ else {
+ fprintf(stderr, "myFetch: ERROR PDU: %s\n", pmErrStr(n));
+ disconnect(PM_ERR_IPC);
+ }
+ __pmUnpinPDUBuf(pb);
+ }
+ else if (n == 0) {
+ fprintf(stderr, "myFetch: End of File: PMCD exited?\n");
+ disconnect(PM_ERR_IPC);
+ }
+ else if (n < 0) {
+ fprintf(stderr, "myFetch: __pmGetPDU: Error: %s\n", pmErrStr(n));
+ disconnect(PM_ERR_IPC);
+ }
+ else {
+ fprintf(stderr, "myFetch: Unexpected %s PDU from PMCD\n", __pmPDUTypeStr(n));
+ disconnect(PM_ERR_IPC);
+ __pmUnpinPDUBuf(pb);
+ }
+ } while (n == 0);
+
+ if (changed & PMCD_ADD_AGENT) {
+ /*
+ * PMCD_DROP_AGENT does not matter, no values are returned.
+ * Trying to restart (PMCD_RESTART_AGENT) is less interesting
+ * than when we actually start (PMCD_ADD_AGENT) ... the latter
+ * is also set when a successful restart occurs, but more
+ * to the point the sequence Install-Remove-Install does
+ * not involve a restart ... it is the second Install that
+ * generates the second PMCD_ADD_AGENT that we need to be
+ * particularly sensitive to, as this may reset counter
+ * metrics ...
+ */
+ int sts;
+ if ((sts = putmark()) < 0) {
+ fprintf(stderr, "putmark: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ }
+ }
+ if (newlist != NULL)
+ free(newlist);
+ }
+
+ if (n < 0 && ctxp->c_pmcd->pc_fd != -1) {
+ disconnect(n);
+ }
+
+ PM_UNLOCK(ctxp->c_lock);
+ return n;
+}
diff --git a/src/pmlogger/src/gram.y b/src/pmlogger/src/gram.y
new file mode 100644
index 0000000..9a28d8a
--- /dev/null
+++ b/src/pmlogger/src/gram.y
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/*
+ * There is a shift/reduced conflict reported by yacc when it cannot
+ * decide whatever it should take 'optinst' route or 'access' one in
+ * the following sutiation:
+ *
+ * log on once foo [access] all
+ *
+ * This conflict considered to be benign, since yacc takes 'the right' option
+ * if optinst is supplied. To work around the issue of access been treated
+ * as an option, enclose the list of metrics in the curly braces, i.e.
+ *
+ * log on once {foo} [access] all
+ */
+
+%{
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int mystate = GLOBAL; /* config file parser state */
+
+__pmHashCtl pm_hash;
+task_t *tasklist;
+
+static task_t *tp;
+static int numinst;
+static int *intlist;
+static char **extlist;
+static int state; /* logging state, current block */
+static char *metricName; /* current metric, current block */
+
+typedef struct _hl {
+ struct _hl *hl_next;
+ char *hl_name;
+ int hl_line;
+} hostlist_t;
+
+static hostlist_t *hl_root;
+static hostlist_t *hl_last;
+static hostlist_t *hlp;
+static hostlist_t *prevhlp;
+static int opmask; /* operations mask */
+static int specmask; /* specifications mask */
+static int allow; /* host allow/disallow state */
+
+static int lookup_metric_name(const char *);
+static void activate_new_metric(const char *);
+static void activate_cached_metric(const char *, int);
+static task_t *findtask(int, struct timeval *);
+
+%}
+%union {
+ long lval;
+ char * str;
+}
+
+%expect 1
+
+%term LSQB
+ RSQB
+ COMMA
+ LBRACE
+ RBRACE
+ COLON
+ SEMICOLON
+
+ LOG
+ MANDATORY ADVISORY
+ ON OFF MAYBE
+ EVERY ONCE DEFAULT
+ MSEC SECOND MINUTE HOUR
+
+ ACCESS ENQUIRE ALLOW DISALLOW ALL EXCEPT
+
+%token<str> NAME STRING IPSPEC HOSTNAME URL
+%token<lval> NUMBER
+
+%type<lval> frequency timeunits action
+%type<str> hostspec
+%%
+
+config : specopt accessopt
+ ;
+
+specopt : spec
+ | /* nothing */
+ ;
+
+spec : stmt
+ | spec stmt
+ ;
+
+stmt : dowhat somemetrics
+ {
+ mystate = GLOBAL;
+ if (tp->t_numvalid)
+ linkback(tp);
+ state = 0;
+ }
+ ;
+
+dowhat : logopt action
+ {
+ struct timeval delta;
+
+ delta.tv_sec = $2 / 1000;
+ delta.tv_usec = 1000 * ($2 % 1000);
+
+ /*
+ * Search for an existing task for this state/interval;
+ * only allocate and setup a new task if none exists.
+ */
+ if ((tp = findtask(state, &delta)) == NULL) {
+ if ((tp = (task_t *)calloc(1, sizeof(task_t))) == NULL) {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+ } else {
+ tp->t_delta = delta;
+ tp->t_state = state;
+ tp->t_next = tasklist;
+ tasklist = tp;
+ }
+ }
+ state = 0;
+ }
+ ;
+
+logopt : LOG
+ | /* nothing */
+ ;
+
+action : cntrl ON frequency
+ {
+ char emess[256];
+ if ($3 < 0) {
+ snprintf(emess, sizeof(emess),
+ "Logging delta (%ld msec) must be positive",$3);
+ yyerror(emess);
+ }
+ else if ($3 > PMLC_MAX_DELTA) {
+ snprintf(emess, sizeof(emess),
+ "Logging delta (%ld msec) cannot be bigger "
+ "than %d msec", $3, PMLC_MAX_DELTA);
+ yyerror(emess);
+ }
+
+ PMLC_SET_ON(state, 1);
+ $$ = $3;
+ }
+ | cntrl OFF { PMLC_SET_ON(state, 0);$$ = 0;}
+ | MANDATORY MAYBE
+ {
+ PMLC_SET_MAND(state, 0);
+ PMLC_SET_ON(state, 0);
+ PMLC_SET_MAYBE(state, 1);
+ $$ = 0;
+ }
+ ;
+
+cntrl : MANDATORY { PMLC_SET_MAND(state, 1); }
+ | ADVISORY { PMLC_SET_MAND(state, 0); }
+ | /*nothing == advisory*/ { PMLC_SET_MAND(state, 0); }
+ ;
+
+frequency : everyopt NUMBER timeunits { $$ = $2*$3; }
+ | ONCE { $$ = 0; }
+ | DEFAULT
+ {
+ extern struct timeval delta; /* default logging interval */
+ $$ = delta.tv_sec*1000 + delta.tv_usec/1000;
+ }
+ ;
+
+everyopt : EVERY
+ | /* nothing */
+ ;
+
+timeunits : MSEC { $$ = 1; }
+ | SECOND { $$ = 1000; }
+ | MINUTE { $$ = 60000; }
+ | HOUR { $$ = 3600000; }
+ ;
+
+somemetrics : LBRACE { mystate = INSPEC; } metriclist RBRACE
+ | metricspec
+ ;
+
+metriclist : metricspec
+ | metriclist metricspec
+ | metriclist COMMA metricspec
+ ;
+
+metricspec : NAME
+ {
+ if ((metricName = strdup($1)) == NULL) {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+ }
+ }
+ optinst
+ {
+ int index, sts;
+
+ /*
+ * search names for previously seen metrics for this task
+ * (note that name may be non-terminal in the PMNS here);
+ * if already found in this task, skip namespace PDUs.
+ */
+ if ((index = lookup_metric_name(metricName)) < 0) {
+ if ((sts = pmTraversePMNS(metricName, activate_new_metric)) < 0 ) {
+ char emess[256];
+ snprintf(emess, sizeof(emess),
+ "Problem with lookup for metric \"%s\" "
+ "... logging not activated", metricName);
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ }
+ }
+ else { /* name is cached already, handle instances */
+ activate_cached_metric(metricName, index);
+ }
+ freeinst(&numinst, intlist, extlist);
+ free(metricName);
+ }
+ ;
+
+optinst : LSQB instancelist RSQB
+ | /* nothing */
+ ;
+
+instancelist : instance
+ | instance instancelist
+ | instance COMMA instancelist
+ ;
+
+instance : NAME { buildinst(&numinst, &intlist, &extlist, -1, $1); }
+ | NUMBER { buildinst(&numinst, &intlist, &extlist, $1, NULL); }
+ | STRING { buildinst(&numinst, &intlist, &extlist, -1, $1); }
+ ;
+
+accessopt : LSQB ACCESS RSQB ctllist
+ | /* nothing */
+ ;
+
+ctllist : ctl
+ | ctl ctllist
+ ;
+
+ctl : allow hostlist COLON operation SEMICOLON
+ {
+ prevhlp = NULL;
+ for (hlp = hl_root; hlp != NULL; hlp = hlp->hl_next) {
+ int sts;
+
+ if (prevhlp != NULL) {
+ free(prevhlp->hl_name);
+ free(prevhlp);
+ }
+ sts = __pmAccAddHost(hlp->hl_name, specmask,
+ opmask, 0);
+ if (sts < 0) {
+ fprintf(stderr, "error was on line %d\n",
+ hlp->hl_line);
+ YYABORT;
+ }
+ prevhlp = hlp;
+ }
+ if (prevhlp != NULL) {
+ free(prevhlp->hl_name);
+ free(prevhlp);
+ }
+ opmask = 0;
+ specmask = 0;
+ hl_root = hl_last = NULL;
+ }
+ ;
+
+allow : ALLOW { allow = 1; }
+ | DISALLOW { allow = 0; }
+ ;
+
+hostlist : host
+ | host COMMA hostlist
+ ;
+
+host : hostspec
+ {
+ size_t sz = sizeof(hostlist_t);
+
+ hlp = (hostlist_t *)malloc(sz);
+ if (hlp == NULL) {
+ __pmNoMem("adding new host", sz, PM_FATAL_ERR);
+ }
+ if (hl_last != NULL) {
+ hl_last->hl_next = hlp;
+ hl_last = hlp;
+ }
+ else
+ hl_root = hl_last = hlp;
+ hlp->hl_next = NULL;
+ hlp->hl_name = strdup($1);
+ hlp->hl_line = lineno;
+ }
+ ;
+
+hostspec : IPSPEC
+ | URL
+ | HOSTNAME
+ | NAME
+ ;
+
+operation : operlist
+ {
+ specmask = opmask;
+ if (allow)
+ opmask = ~opmask;
+ }
+ | ALL
+ {
+ specmask = PM_OP_ALL;
+ if (allow)
+ opmask = PM_OP_NONE;
+ else
+ opmask = PM_OP_ALL;
+ }
+ | ALL EXCEPT operlist
+ {
+ specmask = PM_OP_ALL;
+ if (!allow)
+ opmask = ~opmask;
+ }
+ ;
+
+operlist : op
+ | op COMMA operlist
+ ;
+
+op : ADVISORY { opmask |= PM_OP_LOG_ADV; }
+ | MANDATORY { opmask |= PM_OP_LOG_MAND; }
+ | ENQUIRE { opmask |= PM_OP_LOG_ENQ; }
+ ;
+
+%%
+
+/*
+ * Search the cache for previously seen metrics for active task.
+ * Returns -1 if not found, else an index into tp->t_namelist.
+ */
+static int
+lookup_metric_name(const char *name)
+{
+ int j;
+
+ for (j = 0; j < tp->t_numpmid; j++)
+ if (strcmp(tp->t_namelist[j], name) == 0)
+ return j;
+ return -1;
+}
+
+/*
+ * Assumed calling context ...
+ * tp the correct task for the requested metric
+ * numinst number of instances associated with this request
+ * extlist[] external instance names if numinst > 0
+ * intlist[] internal instance identifier if numinst > 0 and
+ * corresponding extlist[] entry is NULL
+ */
+static void
+activate_cached_metric(const char *name, int index)
+{
+ int sts = 0;
+ int inst;
+ int i;
+ int j;
+ int skip = 0;
+ pmID pmid;
+ pmDesc *dp;
+ optreq_t *rqp;
+ char emess[1024];
+
+ /*
+ * need new malloc'd pmDesc, even if metric found in cache, as
+ * the fetchctl keeps its own (non-realloc-movable!) pointer.
+ */
+ dp = (pmDesc *)malloc(sizeof(pmDesc));
+ if (dp == NULL)
+ goto nomem;
+
+ if (index < 0) {
+ if ((sts = pmLookupName(1, (char **)&name, &pmid)) < 0 || pmid == PM_ID_NULL) {
+ snprintf(emess, sizeof(emess),
+ "Metric \"%s\" is unknown ... not logged", name);
+ goto snarf;
+ }
+ if ((sts = pmLookupDesc(pmid, dp)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Description unavailable for metric \"%s\" ... not logged",
+ name);
+ goto snarf;
+ }
+ tp->t_numpmid++;
+ tp->t_namelist = (char **)realloc(tp->t_namelist, tp->t_numpmid * sizeof(char *));
+ if (tp->t_namelist == NULL)
+ goto nomem;
+ if ((tp->t_namelist[tp->t_numpmid-1] = strdup(name)) == NULL)
+ goto nomem;
+ tp->t_pmidlist = (pmID *)realloc(tp->t_pmidlist, tp->t_numpmid * sizeof(pmID));
+ if (tp->t_pmidlist == NULL)
+ goto nomem;
+ tp->t_desclist = (pmDesc *)realloc(tp->t_desclist, tp->t_numpmid * sizeof(pmDesc));
+ if (tp->t_desclist == NULL)
+ goto nomem;
+ tp->t_pmidlist[tp->t_numpmid-1] = pmid;
+ tp->t_desclist[tp->t_numpmid-1] = *dp; /* struct assignment */
+ }
+ else {
+ *dp = tp->t_desclist[index];
+ pmid = tp->t_pmidlist[index];
+ }
+
+ rqp = (optreq_t *)calloc(1, sizeof(optreq_t));
+ if (rqp == NULL)
+ goto nomem;
+ rqp->r_desc = dp;
+ rqp->r_numinst = numinst;
+
+ if (numinst) {
+ /*
+ * malloc here, and keep ... gets buried in optFetch data structures
+ */
+ rqp->r_instlist = (int *)malloc(numinst * sizeof(rqp->r_instlist[0]));
+ if (rqp->r_instlist == NULL)
+ goto nomem;
+ j = 0;
+ for (i = 0; i < numinst; i++) {
+ if (extlist[i] != NULL) {
+ sts = pmLookupInDom(dp->indom, extlist[i]);
+ if (sts < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%s\" is not defined for the metric \"%s\"",
+ extlist[i], name);
+ yywarn(emess);
+ rqp->r_numinst--;
+ continue;
+ }
+ inst = sts;
+ }
+ else {
+ char *p;
+ sts = pmNameInDom(dp->indom, intlist[i], &p);
+ if (sts < 0) {
+ snprintf(emess, sizeof(emess),
+ "Instance \"%d\" is not defined for the metric \"%s\"",
+ intlist[i], name);
+ yywarn(emess);
+ rqp->r_numinst--;
+ continue;
+ }
+ free(p);
+ inst = intlist[i];
+ }
+ if ((sts = chk_one(tp, pmid, inst)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Incompatible request for metric \"%s\" "
+ "and instance \"%s\"", name, extlist[i]);
+ yywarn(emess);
+ fprintf(stderr, "%s\n", chk_emess[-sts]);
+ rqp->r_numinst--;
+ }
+ else if (sts == 0)
+ rqp->r_instlist[j++] = inst;
+ else /* already have this instance */
+ skip = 1;
+ }
+ if (rqp->r_numinst == 0)
+ skip = 1;
+ }
+ else {
+ if ((sts = chk_all(tp, pmid)) < 0) {
+ snprintf(emess, sizeof(emess),
+ "Incompatible request for metric \"%s\"", name);
+ yywarn(emess);
+
+ skip = 1;
+ }
+ }
+
+ if (!skip) {
+ __pmOptFetchAdd(&tp->t_fetch, rqp);
+ if ((sts = __pmHashAdd(pmid, (void *)rqp, &pm_hash)) < 0) {
+ snprintf(emess, sizeof(emess), "__pmHashAdd failed "
+ "for metric \"%s\" ... logging not activated", name);
+ goto snarf;
+ }
+ tp->t_numvalid++;
+ }
+ else {
+ free(dp);
+ free(rqp);
+ }
+ return;
+
+nomem:
+ snprintf(emess, sizeof(emess), "malloc failed: %s", osstrerror());
+ yyerror(emess);
+
+snarf:
+ yywarn(emess);
+ fprintf(stderr, "Reason: %s\n", pmErrStr(sts));
+ if (dp != NULL)
+ free(dp);
+ return;
+}
+
+static void
+activate_new_metric(const char *name)
+{
+ activate_cached_metric(name, lookup_metric_name(name));
+}
+
+/*
+ * Given a logging state and an interval, return a matching task
+ * or NULL if none exists for that value pair.
+ */
+task_t *
+findtask(int state, struct timeval *delta)
+{
+ task_t *tp;
+
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (state == tp->t_state &&
+ delta->tv_sec == tp->t_delta.tv_sec &&
+ delta->tv_usec == tp->t_delta.tv_usec)
+ break;
+ }
+ return tp;
+}
+
+/*
+ * Complete the delayed processing of task elements, which can only
+ * be done once all configuration file parsing is complete.
+ */
+void
+yyend(void)
+{
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (tp->t_numvalid == 0)
+ continue;
+ PMLC_SET_MAYBE(tp->t_state, 0); /* clear req */
+ if (PMLC_GET_ON(tp->t_state))
+ tp->t_afid = __pmAFregister(&tp->t_delta, (void *)tp, log_callback);
+ }
+}
diff --git a/src/pmlogger/src/lex.l b/src/pmlogger/src/lex.l
new file mode 100644
index 0000000..f57b3ca
--- /dev/null
+++ b/src/pmlogger/src/lex.l
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+
+int lineno=1;
+
+#include "gram.tab.h"
+
+static int
+ctx(int type)
+{
+ extern int mystate;
+ if (mystate == GLOBAL)
+ return type;
+ else {
+ yylval.str = yytext;
+ return NAME;
+ }
+}
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#else
+#undef input
+#define input() ((yytchar=fgetc(yyin)) == EOF ? 0 : yytchar)
+#undef unput
+#define unput(c) {yytchar=(c); ungetc(yytchar, yyin);}
+#endif /* FLEX_SCANNER */
+%}
+
+%%
+"[" { return LSQB; }
+"]" { return RSQB; }
+"," { return COMMA; }
+"{" { return LBRACE; }
+"}" { return RBRACE; }
+":" { return COLON; }
+";" { return SEMICOLON; }
+
+milliseconds? { return ctx(MSEC); }
+mandatory { return ctx(MANDATORY); }
+advisory { return ctx(ADVISORY); }
+disallow { return ctx(DISALLOW); }
+minutes? { return ctx(MINUTE); }
+seconds? { return ctx(SECOND); }
+default { return ctx(DEFAULT); }
+enquire { return ctx(ENQUIRE); }
+access { return ctx(ACCESS); }
+except { return ctx(EXCEPT); }
+allow { return ctx(ALLOW); }
+every { return ctx(EVERY); }
+maybe { return ctx(MAYBE); }
+hours? { return ctx(HOUR); }
+msecs? { return ctx(MSEC); }
+mins? { return ctx(MINUTE); }
+once { return ctx(ONCE); }
+secs? { return ctx(SECOND); }
+log { return ctx(LOG); }
+all { return ctx(ALL); }
+off { return ctx(OFF); }
+on { return ctx(ON); }
+
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = yytext; return NAME; }
+
+[A-Za-z][A-Za-z0-9_.-]* { yylval.str = yytext; return HOSTNAME; }
+
+\"[^\"\n][^\"\n]*\" { /* strip quotes before returing */
+ yytext[strlen(yytext)-1] = '\0';
+ yylval.str = yytext+1;
+ return STRING;
+ }
+
+[0-9]+ { yylval.lval = atol(yytext); return NUMBER; }
+
+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.[0-9]+\.[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+[0-9]+\.\* { yylval.str = yytext; return IPSPEC; }
+\.\* { yylval.str = yytext; return IPSPEC; }
+
+([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+:: { yylval.str = yytext; return IPSPEC; }
+::([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+:([A-Fa-f0-9]+:)*[A-Fa-f0-9]+ { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+: { yylval.str = yytext; return IPSPEC; }
+
+([A-Fa-f0-9]+:)+\* { yylval.str = yytext; return IPSPEC; }
+::([A-Fa-f0-9]+:)*\* { yylval.str = yytext; return IPSPEC; }
+([A-Fa-f0-9]+:)+:([A-Fa-f0-9]+:)*\* { yylval.str = yytext; return IPSPEC; }
+:\* { yylval.str = yytext; return IPSPEC; }
+
+\* { yylval.str = yytext; return IPSPEC; }
+
+unix:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+unix:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]*\* { yylval.str = yytext; return URL; }
+local:[A-Za-z0-9_.-/]* { yylval.str = yytext; return URL; }
+
+\#.* { }
+
+[ \t\r]+ { }
+
+\n { lineno++; }
+
+. {
+ char emess[256];
+ snprintf(emess, sizeof(emess), "Unexpected character '%c'", yytext[0]);
+ yyerror(emess);
+ }
+%%
+
+int
+yywrap (void)
+{
+ return 1;
+}
diff --git a/src/pmlogger/src/logger.h b/src/pmlogger/src/logger.h
new file mode 100644
index 0000000..f57b18b
--- /dev/null
+++ b/src/pmlogger/src/logger.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LOGGER_H
+#define _LOGGER_H
+
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+
+/*
+ * a task is a bundle of fetches to be done together - it
+ * originally corresponded one-to-one with a configuration
+ * file curly-brace-enclosed block, but no longer does.
+ */
+typedef struct task_s {
+ struct task_s *t_next;
+ struct timeval t_delta;
+ int t_state; /* logging state */
+ int t_numpmid;
+ int t_numvalid;
+ pmID *t_pmidlist;
+ char **t_namelist;
+ pmDesc *t_desclist;
+ fetchctl_t *t_fetch;
+ int t_afid;
+ int t_size;
+} task_t;
+
+extern task_t *tasklist; /* master list of tasks */
+extern __pmLogCtl logctl; /* global log control */
+
+/* config file parser states */
+#define GLOBAL 0
+#define INSPEC 1
+
+/* generic error messages */
+extern char *chk_emess[];
+extern void die(char *, int);
+
+/*
+ * hash control for per-metric (really per-metric per-log specification)
+ * -- used to establish and maintain state for ControlLog operations
+ */
+extern __pmHashCtl pm_hash;
+
+/* another hash list used for maintaining information about all metrics and
+ * instances that have EVER appeared in the log as opposed to just those
+ * currently being logged. It's a history list.
+ */
+extern __pmHashCtl hist_hash;
+
+typedef struct {
+ int ih_inst;
+ int ih_flags;
+} insthist_t;
+
+typedef struct {
+ pmID ph_pmid;
+ pmInDom ph_indom;
+ int ph_numinst;
+ insthist_t *ph_instlist;
+} pmidhist_t;
+
+/* access control goo */
+#define PM_OP_LOG_ADV 0x1
+#define PM_OP_LOG_MAND 0x2
+#define PM_OP_LOG_ENQ 0x4
+
+#define PM_OP_NONE 0x0
+#define PM_OP_ALL 0x7
+
+#define PMLC_SET_MAYBE(val, flag) \
+ val = ((val) & ~0x10) | (((flag) & 0x1) << 4)
+#define PMLC_GET_MAYBE(val) \
+ (((val) & 0x10) >> 4)
+
+/* volume switch types */
+#define VOL_SW_SIGHUP 0
+#define VOL_SW_PMLC 1
+#define VOL_SW_COUNTER 2
+#define VOL_SW_BYTES 3
+#define VOL_SW_TIME 4
+#define VOL_SW_MAX 5
+
+/* initial time of day from remote PMCD */
+extern struct timeval epoch;
+
+/* offset to start of last written pmResult */
+extern int last_log_offset;
+
+/* yylex() gets input from here ... */
+extern FILE *fconfig;
+extern FILE *yyin;
+extern char *configfile;
+extern int lineno;
+
+extern int myFetch(int, pmID *, __pmPDU **);
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern int yylex(void);
+extern int yyparse(void);
+extern void yyend(void);
+extern void buildinst(int *, int **, char ***, int, char *);
+extern void freeinst(int *, int *, char **);
+extern void linkback(task_t *);
+extern optreq_t *findoptreq(pmID, int);
+extern void log_callback(int, void *);
+extern int chk_one(task_t *, pmID, int);
+extern int chk_all(task_t *, pmID);
+extern int newvolume(int);
+extern void disconnect(int);
+#if CAN_RECONNECT
+extern int reconnect(void);
+#endif
+extern int do_preamble(void);
+extern void run_done(int,char *);
+extern __pmPDU *rewrite_pdu(__pmPDU *, int);
+extern int putmark(void);
+extern void dumpit(void);
+
+#include <sys/param.h>
+extern char pmlc_host[];
+
+#define LOG_DELTA_ONCE -1
+#define LOG_DELTA_DEFAULT -2
+
+/* command line parameters */
+extern char *archBase; /* base name for log files */
+extern char *pmcd_host; /* collecting from PMCD on this host */
+extern char *pmcd_host_conn; /* ... and this is how we connected to it */
+extern int primary; /* Non-zero for primary logger */
+extern int rflag;
+extern struct timeval delta; /* default logging interval */
+extern int ctlport; /* pmlogger control port number */
+extern char *note; /* note for port map file */
+
+/* pmlc support */
+extern void init_ports(void);
+extern int control_req(int ctlfd);
+extern int client_req(void);
+extern __pmHashCtl hist_hash;
+extern unsigned int denyops; /* for access control (ops not allowed) */
+extern struct timeval last_stamp;
+extern int clientfd;
+#define CFD_INET 0
+#define CFD_IPV6 1
+#define CFD_UNIX 2
+#define CFD_NUM 3
+extern int ctlfds[CFD_NUM];
+extern int exit_samples;
+extern int vol_switch_samples;
+extern __int64_t vol_switch_bytes;
+extern int vol_switch_flag;
+extern int vol_samples_counter;
+extern int archive_version;
+extern int parse_done;
+extern __int64_t exit_bytes;
+extern __int64_t vol_bytes;
+extern int exit_code;
+
+/* event record handling */
+extern int do_events(pmValueSet *);
+
+/* QA testing and error injection support ... see do_request() */
+extern int qa_case;
+#define QA_OFF 100
+#define QA_SLEEPY 101
+
+#endif /* _LOGGER_H */
diff --git a/src/pmlogger/src/pmlogger.c b/src/pmlogger/src/pmlogger.c
new file mode 100644
index 0000000..2a332e3
--- /dev/null
+++ b/src/pmlogger/src/pmlogger.c
@@ -0,0 +1,1186 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include "logger.h"
+
+char *configfile;
+__pmLogCtl logctl;
+int exit_samples = -1; /* number of samples 'til exit */
+__int64_t exit_bytes = -1; /* number of bytes 'til exit */
+__int64_t vol_bytes; /* total in earlier volumes */
+struct timeval exit_time; /* time interval 'til exit */
+int vol_switch_samples = -1; /* number of samples 'til vol switch */
+__int64_t vol_switch_bytes = -1; /* number of bytes 'til vol switch */
+struct timeval vol_switch_time; /* time interval 'til vol switch */
+int vol_samples_counter; /* Counts samples - reset for new vol*/
+int vol_switch_afid = -1; /* afid of event for vol switch */
+int vol_switch_flag; /* sighup received - switch vol now */
+int parse_done;
+int primary; /* Non-zero for primary pmlogger */
+char *archBase; /* base name for log files */
+char *pmcd_host;
+char *pmcd_host_conn;
+struct timeval epoch;
+int archive_version = PM_LOG_VERS02; /* Type of archive to create */
+int linger; /* linger with no tasks/events */
+int rflag; /* report sizes */
+struct timeval delta = { 60, 0 }; /* default logging interval */
+int exit_code; /* code to pass to exit (zero/signum) */
+int qa_case; /* QA error injection state */
+char *note; /* note for port map file */
+
+static int pmcdfd; /* comms to pmcd */
+static __pmFdSet fds; /* file descriptors mask for select */
+static int numfds; /* number of file descriptors in mask */
+
+static int rsc_fd = -1; /* recording session control see -x */
+static int rsc_replay;
+static time_t rsc_start;
+static char *rsc_prog = "<unknown>";
+static char *folio_name = "<unknown>";
+static char *dialog_title = "PCP Archive Recording Session";
+
+void
+run_done(int sts, char *msg)
+{
+#ifdef PCP_DEBUG
+ if (msg != NULL)
+ fprintf(stderr, "pmlogger: %s, exiting\n", msg);
+ else
+ fprintf(stderr, "pmlogger: End of run time, exiting\n");
+#endif
+
+ /*
+ * write the last last temportal index entry with the time stamp
+ * of the last pmResult and the seek pointer set to the offset
+ * _before_ the last log record
+ */
+ if (last_stamp.tv_sec != 0) {
+ __pmTimeval tmp;
+ tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
+ tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
+ fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
+ __pmLogPutIndex(&logctl, &tmp);
+ }
+
+ exit(sts);
+}
+
+static void
+run_done_callback(int i, void *j)
+{
+ run_done(0, NULL);
+}
+
+static void
+vol_switch_callback(int i, void *j)
+{
+ newvolume(VOL_SW_TIME);
+}
+
+static int
+maxfd(void)
+{
+ int i;
+ int max = 0;
+
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] > max)
+ max = ctlfds[i];
+ }
+ if (clientfd > max)
+ max = clientfd;
+ if (pmcdfd > max)
+ max = pmcdfd;
+ if (rsc_fd > max)
+ max = rsc_fd;
+ return max;
+}
+
+/*
+ * tolower_str - convert a string to all lowercase
+ */
+static void
+tolower_str(char *str)
+{
+ char *s = str;
+
+ while (*s) {
+ *s = tolower((int)*s);
+ s++;
+ }
+}
+
+/*
+ * ParseSize - parse a size argument given in a command option
+ *
+ * The size can be in one of the following forms:
+ * "40" = sample counter of 40
+ * "40b" = byte size of 40
+ * "40Kb" = byte size of 40*1024 bytes = 40 kilobytes
+ * "40Mb" = byte size of 40*1024*1024 bytes = 40 megabytes
+ * time-format = time delta in seconds
+ *
+ */
+static int
+ParseSize(char *size_arg, int *sample_counter, __int64_t *byte_size,
+ struct timeval *time_delta)
+{
+ long x = 0; /* the size number */
+ char *ptr = NULL;
+ char *interval_err;
+
+ *sample_counter = -1;
+ *byte_size = -1;
+ time_delta->tv_sec = -1;
+ time_delta->tv_usec = -1;
+
+ x = strtol(size_arg, &ptr, 10);
+
+ /* must be positive */
+ if (x <= 0)
+ return -1;
+
+ if (*ptr == '\0') {
+ /* we have consumed entire string as a long */
+ /* => we have a sample counter */
+ *sample_counter = x;
+ return 1;
+ }
+
+ /* we have a number followed by something else */
+ if (ptr != size_arg) {
+ int len;
+
+ tolower_str(ptr);
+
+ /* chomp off plurals */
+ len = strlen(ptr);
+ if (ptr[len-1] == 's')
+ ptr[len-1] = '\0';
+
+ /* if bytes */
+ if (strcmp(ptr, "b") == 0 ||
+ strcmp(ptr, "byte") == 0) {
+ *byte_size = x;
+ return 1;
+ }
+
+ /* if kilobytes */
+ if (strcmp(ptr, "k") == 0 || strcmp(ptr, "kb") == 0 ||
+ strcmp(ptr, "kbyte") == 0 || strcmp(ptr, "kilobyte") == 0) {
+ *byte_size = x*1024;
+ return 1;
+ }
+
+ /* if megabytes */
+ if (strcmp(ptr, "m") == 0 ||
+ strcmp(ptr, "mb") == 0 ||
+ strcmp(ptr, "mbyte") == 0 ||
+ strcmp(ptr, "megabyte") == 0) {
+ *byte_size = x*1024*1024;
+ return 1;
+ }
+
+ /* if gigabytes */
+ if (strcmp(ptr, "g") == 0 ||
+ strcmp(ptr, "gb") == 0 ||
+ strcmp(ptr, "gbyte") == 0 ||
+ strcmp(ptr, "gigabyte") == 0) {
+ *byte_size = ((__int64_t)x)*1024*1024*1024;
+ return 1;
+ }
+ }
+
+ /* Doesn't fit pattern above, try a time interval */
+ if (pmParseInterval(size_arg, time_delta, &interval_err) >= 0)
+ return 1;
+ /* error message not used here */
+ free(interval_err);
+
+ /* Doesn't match anything, return an error */
+ return -1;
+}
+
+/* time manipulation */
+static void
+tsub(struct timeval *a, struct timeval *b)
+{
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+ if (a->tv_sec < 0) {
+ /* clip negative values at zero */
+ a->tv_sec = 0;
+ a->tv_usec = 0;
+ }
+}
+
+static char *
+do_size(double d)
+{
+ static char nbuf[100];
+
+ if (d < 10 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%ld bytes", (long)d);
+ else if (d < 10.0 * 1024 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%.1f Kbytes", d/1024);
+ else if (d < 10.0 * 1024 * 1024 * 1024)
+ snprintf(nbuf, sizeof(nbuf), "%.1f Mbytes", d/(1024 * 1024));
+ else
+ snprintf(nbuf, sizeof(nbuf), "%ld Mbytes", (long)d/(1024 * 1024));
+
+ return nbuf;
+}
+
+/*
+ * add text identified by p to the malloc buffer at bp[0] ... bp[nchar -1]
+ * return the length of the result or -1 for an error
+ */
+static int
+add_msg(char **bp, int nchar, char *p)
+{
+ int add_len;
+
+ if (nchar < 0 || p == NULL)
+ return nchar;
+
+ add_len = (int)strlen(p);
+ if (nchar == 0)
+ add_len++;
+ if ((*bp = realloc(*bp, nchar+add_len)) == NULL)
+ return -1;
+ if (nchar == 0)
+ strcpy(*bp, p);
+ else
+ strcat(&(*bp)[nchar-1], p);
+
+ return nchar+add_len;
+}
+
+
+/*
+ * generate dialog/message when launching application wishes to break
+ * its association with pmlogger
+ *
+ * cmd is one of the following:
+ * D detach pmlogger and let it run forever
+ * Q terminate pmlogger
+ * ? display status
+ * X fatal error or application exit ... user must decide
+ * to detach or quit
+ */
+void
+do_dialog(char cmd)
+{
+ FILE *msgf = NULL;
+ time_t now;
+ static char lbuf[100+MAXPATHLEN];
+ double archsize;
+ char *q;
+ char *p = NULL;
+ int nchar;
+ char *msg;
+#if HAVE_MKSTEMP
+ char tmp[MAXPATHLEN];
+#endif
+
+ time(&now);
+ now -= rsc_start;
+ if (now == 0)
+ /* hack is close enough! */
+ now = 1;
+
+ archsize = vol_bytes + ftell(logctl.l_mfp);
+
+ nchar = add_msg(&p, 0, "");
+ p[0] = '\0';
+
+ snprintf(lbuf, sizeof(lbuf), "PCP recording for the archive folio \"%s\" and the host", folio_name);
+ nchar = add_msg(&p, nchar, lbuf);
+ snprintf(lbuf, sizeof(lbuf), " \"%s\" has been in progress for %ld %s",
+ pmcd_host,
+ now < 240 ? now : now/60, now < 240 ? "seconds" : "minutes");
+ nchar = add_msg(&p, nchar, lbuf);
+ nchar = add_msg(&p, nchar, " and in that time the pmlogger process has created an");
+ nchar = add_msg(&p, nchar, " archive of ");
+ q = do_size(archsize);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, ".");
+ if (rsc_replay) {
+ nchar = add_msg(&p, nchar, "\n\nThis archive may be replayed with the following command:\n");
+ snprintf(lbuf, sizeof(lbuf), " $ pmafm %s replay", folio_name);
+ nchar = add_msg(&p, nchar, lbuf);
+ }
+
+ if (cmd == 'D') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has asked pmlogger");
+ nchar = add_msg(&p, nchar, " to continue independently and the PCP archive will grow at");
+ nchar = add_msg(&p, nchar, " the rate of ");
+ q = do_size((archsize * 3600) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per hour or ");
+ q = do_size((archsize * 3600 * 24) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per day.");
+ }
+
+ if (cmd == 'X') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has exited and you");
+ nchar = add_msg(&p, nchar, " must decide if the PCP recording session should be terminated");
+ nchar = add_msg(&p, nchar, " or continued. If recording is continued the PCP archive will");
+ nchar = add_msg(&p, nchar, " grow at the rate of ");
+ q = do_size((archsize * 3600) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per hour or ");
+ q = do_size((archsize * 3600 * 24) / now);
+ nchar = add_msg(&p, nchar, q);
+ nchar = add_msg(&p, nchar, " per day.");
+ }
+
+ if (cmd == 'Q') {
+ nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has terminated");
+ nchar = add_msg(&p, nchar, " this PCP recording session.\n");
+ }
+
+ if (cmd != 'Q') {
+ nchar = add_msg(&p, nchar, "\n\nAt any time this pmlogger process may be terminated with the");
+ nchar = add_msg(&p, nchar, " following command:\n");
+ snprintf(lbuf, sizeof(lbuf), " $ pmsignal -s TERM %" FMT_PID "\n", getpid());
+ nchar = add_msg(&p, nchar, lbuf);
+ }
+
+ if (cmd == 'X')
+ nchar = add_msg(&p, nchar, "\n\nTerminate this PCP recording session now?");
+
+ if (nchar > 0) {
+ char * xconfirm = __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG"));
+ int fd = -1;
+
+#if HAVE_MKSTEMP
+ snprintf(tmp, sizeof(tmp), "%s%cmsgXXXXXX", pmGetConfig("PCP_TMPFILE_DIR"), __pmPathSeparator());
+ msg = tmp;
+ fd = mkstemp(tmp);
+#else
+ if ((msg = tmpnam(NULL)) != NULL)
+ fd = open(msg, O_WRONLY|O_CREAT|O_EXCL, 0600);
+#endif
+ if (fd >= 0)
+ msgf = fdopen(fd, "w");
+ if (msgf == NULL) {
+ fprintf(stderr, "\nError: failed create temporary message file for recording session dialog\n");
+ fprintf(stderr, "Reason? %s\n", osstrerror());
+ if (fd != -1)
+ close(fd);
+ goto failed;
+ }
+ fputs(p, msgf);
+ fclose(msgf);
+ msgf = NULL;
+
+ if (cmd == 'X')
+ snprintf(lbuf, sizeof(lbuf), "%s -c -header \"%s - %s\" -file %s -icon question "
+ "-B Yes -b No 2>/dev/null",
+ xconfirm, dialog_title, rsc_prog, msg);
+ else
+ snprintf(lbuf, sizeof(lbuf), "%s -c -header \"%s - %s\" -file %s -icon info "
+ "-b Close 2>/dev/null",
+ xconfirm, dialog_title, rsc_prog, msg);
+
+ if ((msgf = popen(lbuf, "r")) == NULL) {
+ fprintf(stderr, "\nError: failed to start command for recording session dialog\n");
+ fprintf(stderr, "Command: \"%s\"\n", lbuf);
+ goto failed;
+ }
+
+ if (fgets(lbuf, sizeof(lbuf), msgf) == NULL) {
+ fprintf(stderr, "\n%s: pmconfirm(1) failed for recording session dialog\n",
+ cmd == '?' ? "Warning" : "Error");
+failed:
+ fprintf(stderr, "Dialog:\n");
+ fputs(p, stderr);
+ strcpy(lbuf, "Yes");
+ }
+ else {
+ /* strip at first newline */
+ for (q = lbuf; *q && *q != '\n'; q++)
+ ;
+ *q = '\0';
+ }
+
+ if (msgf != NULL)
+ pclose(msgf);
+ unlink(msg);
+ }
+ else {
+ fprintf(stderr, "Error: failed to create recording session dialog message!\n");
+ fprintf(stderr, "Reason? %s\n", osstrerror());
+ strcpy(lbuf, "Yes");
+ }
+
+ free(p);
+
+ if (cmd == 'Q' || (cmd == 'X' && strcmp(lbuf, "Yes") == 0)) {
+ run_done(0, "Recording session terminated");
+ }
+
+ if (cmd != '?') {
+ /* detach, silently go off to the races ... */
+ close(rsc_fd);
+ __pmFD_CLR(rsc_fd, &fds);
+ rsc_fd = -1;
+ }
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ { "config", 1, 'c', "FILE", "file to load configuration from" },
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ { "log", 1, 'l', "FILE", "redirect diagnostics and trace output" },
+ { "linger", 0, 'L', 0, "run even if not primary logger instance and nothing to log" },
+ { "note", 1, 'm', "MSG", "descriptive note to be added to the port map file" },
+ PMOPT_NAMESPACE,
+ { "primary", 0, 'P', 0, "execute as primary logger instance" },
+ { "report", 0, 'r', 0, "report record sizes and archive growth rate" },
+ { "size", 1, 's', "SIZE", "terminate after endsize has been accumulated" },
+ { "interval", 1, 't', "DELTA", "default logging interval [default 60.0 seconds]" },
+ PMOPT_FINISH,
+ { "", 0, 'u', 0, "output is unbuffered [default now, so -u is a no-op]" },
+ { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" },
+ { "volsize", 1, 'v', "SIZE", "switch log volumes after size has been accumulated" },
+ { "version", 1, 'V', "NUM", "version for archive (default and only version is 2)" },
+ { "", 1, 'x', "FD", "control file descriptor for running from pmRecordControl(3)" },
+ { "", 0, 'y', 0, "set timezone for times to local time rather than from PMCD host" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "c:D:h:l:Lm:n:Prs:T:t:uU:v:V:x:y?",
+ .long_options = longopts,
+ .short_usage = "[options] archive",
+};
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int sts;
+ int sep = __pmPathSeparator();
+ int use_localtime = 0;
+ int isdaemon = 0;
+ char *pmnsfile = PM_NS_DEFAULT;
+ char *username;
+ char *logfile = "pmlogger.log";
+ /* default log (not archive) file name */
+ char *endnum;
+ int i;
+ task_t *tp;
+ optcost_t ocp;
+ __pmFdSet readyfds;
+ char *p;
+ char *runtime = NULL;
+ int ctx; /* handle corresponding to ctxp below */
+ __pmContext *ctxp; /* pmlogger has just this one context */
+
+ __pmGetUsername(&username);
+
+ /*
+ * Warning:
+ * If any of the pmlogger options change, make sure the
+ * corresponding changes are made to pmnewlog when pmlogger
+ * options are passed through from the control file
+ */
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'c': /* config file */
+ if (access(opts.optarg, F_OK) == 0)
+ configfile = opts.optarg;
+ else {
+ /* does not exist as given, try the standard place */
+ char *sysconf = pmGetConfig("PCP_SYSCONF_DIR");
+ int sz = strlen(sysconf)+strlen("/pmlogger/")+strlen(opts.optarg)+1;
+ if ((configfile = (char *)malloc(sz)) == NULL)
+ __pmNoMem("config file name", sz, PM_FATAL_ERR);
+ snprintf(configfile, sz,
+ "%s%c" "pmlogger" "%c%s",
+ sysconf, sep, sep, opts.optarg);
+ if (access(configfile, F_OK) != 0) {
+ /* still no good, error handling happens below */
+ free(configfile);
+ configfile = opts.optarg;
+ }
+ }
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'h': /* hostname for PMCD to contact */
+ pmcd_host_conn = opts.optarg;
+ break;
+
+ case 'l': /* log file name */
+ logfile = opts.optarg;
+ break;
+
+ case 'L': /* linger if not primary logger */
+ linger = 1;
+ break;
+
+ case 'm': /* note for port map file */
+ note = opts.optarg;
+ isdaemon = ((strcmp(note, "pmlogger_check") == 0) ||
+ (strcmp(note, "pmlogger_daily") == 0));
+ break;
+
+ case 'n': /* alternative name space file */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'P': /* this is the primary pmlogger */
+ primary = 1;
+ isdaemon = 1;
+ break;
+
+ case 'r': /* report sizes of pmResult records */
+ rflag = 1;
+ break;
+
+ case 's': /* exit size */
+ sts = ParseSize(opts.optarg, &exit_samples, &exit_bytes, &exit_time);
+ if (sts < 0) {
+ pmprintf("%s: illegal size argument '%s' for exit size\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else if (exit_time.tv_sec > 0) {
+ __pmAFregister(&exit_time, NULL, run_done_callback);
+ }
+ break;
+
+ case 'T': /* end time */
+ runtime = opts.optarg;
+ break;
+
+ case 't': /* change default logging interval */
+ if (pmParseInterval(opts.optarg, &delta, &p) < 0) {
+ pmprintf("%s: illegal -t argument\n%s", pmProgname, p);
+ free(p);
+ opts.errors++;
+ }
+ break;
+
+ case 'U': /* run as named user */
+ username = opts.optarg;
+ isdaemon = 1;
+ break;
+
+ case 'u': /* flush output buffers after each fetch */
+ /*
+ * all archive write I/O is unbuffered now, so maintain -u
+ * for backwards compatibility only
+ */
+ break;
+
+ case 'v': /* volume switch after given size */
+ sts = ParseSize(opts.optarg, &vol_switch_samples, &vol_switch_bytes,
+ &vol_switch_time);
+ if (sts < 0) {
+ pmprintf("%s: illegal size argument '%s' for volume size\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else if (vol_switch_time.tv_sec > 0) {
+ vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
+ vol_switch_callback);
+ }
+ break;
+
+ case 'V':
+ archive_version = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || archive_version != PM_LOG_VERS02) {
+ pmprintf("%s: -V requires a version number of %d\n",
+ pmProgname, PM_LOG_VERS02);
+ opts.errors++;
+ }
+ break;
+
+ case 'x': /* recording session control fd */
+ rsc_fd = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || rsc_fd < 0) {
+ pmprintf("%s: -x requires a non-negative numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ else {
+ time(&rsc_start);
+ }
+ break;
+
+ case 'y':
+ use_localtime = 1;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (primary && pmcd_host != NULL) {
+ pmprintf(
+ "%s: -P and -h are mutually exclusive; use -P only when running\n"
+ "%s on the same (local) host as the PMCD to which it connects.\n",
+ pmProgname, pmProgname);
+ opts.errors++;
+ }
+
+ if (!opts.errors && opts.optind != argc - 1) {
+ pmprintf("%s: insufficient arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (rsc_fd != -1 && note == NULL) {
+ /* add default note to indicate running with -x */
+ static char xnote[10];
+ snprintf(xnote, sizeof(xnote), "-x %d", rsc_fd);
+ note = xnote;
+ }
+
+ /* if we are running as a daemon, change user early */
+ if (isdaemon)
+ __pmSetProcessIdentity(username);
+
+ __pmOpenLog("pmlogger", logfile, stderr, &sts);
+ if (sts != 1) {
+ fprintf(stderr, "%s: Warning: log file (%s) creation failed\n", pmProgname, logfile);
+ /* continue on ... writing to stderr */
+ }
+
+ /* base name for archive is here ... */
+ archBase = argv[opts.optind];
+
+ if (pmcd_host_conn == NULL)
+ pmcd_host_conn = "local:";
+
+ /* initialise access control */
+ if (__pmAccAddOp(PM_OP_LOG_ADV) < 0 ||
+ __pmAccAddOp(PM_OP_LOG_MAND) < 0 ||
+ __pmAccAddOp(PM_OP_LOG_ENQ) < 0) {
+ fprintf(stderr, "%s: access control initialisation failed\n", pmProgname);
+ exit(1);
+ }
+
+ if (pmnsfile != PM_NS_DEFAULT) {
+ if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
+ fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n", pmProgname, pmnsfile, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ if ((ctx = pmNewContext(PM_CONTEXT_HOST, pmcd_host_conn)) < 0) {
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n", pmProgname, pmcd_host_conn, pmErrStr(ctx));
+ exit(1);
+ }
+ pmcd_host = (char *)pmGetContextHostName(ctx);
+ if (strlen(pmcd_host) == 0) {
+ fprintf(stderr, "%s: pmGetContextHostName(%d) failed\n",
+ pmProgname, ctx);
+ exit(1);
+ }
+
+ if (rsc_fd == -1) {
+ /* no -x, so register client id with pmcd */
+ __pmSetClientIdArgv(argc, argv);
+ }
+
+ /*
+ * discover fd for comms channel to PMCD ...
+ */
+ if ((ctxp = __pmHandleToPtr(ctx)) == NULL) {
+ fprintf(stderr, "%s: botch: __pmHandleToPtr(%d) returns NULL!\n", pmProgname, ctx);
+ exit(1);
+ }
+ pmcdfd = ctxp->c_pmcd->pc_fd;
+ PM_UNLOCK(ctxp->c_lock);
+
+ if (configfile != NULL) {
+ if ((yyin = fopen(configfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ }
+ else {
+ /* **ANY** Lex would read from stdin automagically */
+ configfile = "<stdin>";
+ }
+
+ __pmOptFetchGetParams(&ocp);
+ ocp.c_scope = 1;
+ __pmOptFetchPutParams(&ocp);
+
+ /* prevent early timer events ... */
+ __pmAFblock();
+
+ if (yyparse() != 0)
+ exit(1);
+ if (configfile != NULL)
+ fclose(yyin);
+ yyend();
+
+#ifdef PCP_DEBUG
+ fprintf(stderr, "Config parsed\n");
+#endif
+
+ fprintf(stderr, "Starting %slogger for host \"%s\" via \"%s\"\n",
+ primary ? "primary " : "", pmcd_host, pmcd_host_conn);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "optFetch Cost Parameters: pmid=%d indom=%d fetch=%d scope=%d\n",
+ ocp.c_pmid, ocp.c_indom, ocp.c_fetch, ocp.c_scope);
+
+ fprintf(stderr, "\nAfter loading config ...\n");
+ for (tp = tasklist; tp != NULL; tp = tp->t_next) {
+ if (tp->t_numvalid == 0)
+ continue;
+ fprintf(stderr, " state: %sin log, %savail, %s, %s",
+ PMLC_GET_INLOG(tp->t_state) ? "" : "not ",
+ PMLC_GET_AVAIL(tp->t_state) ? "" : "un",
+ PMLC_GET_MAND(tp->t_state) ? "mand" : "adv",
+ PMLC_GET_ON(tp->t_state) ? "on" : "off");
+ fprintf(stderr, " delta: %ld usec",
+ (long)1000 * tp->t_delta.tv_sec + tp->t_delta.tv_usec);
+ fprintf(stderr, " numpmid: %d\n", tp->t_numpmid);
+ for (i = 0; i < tp->t_numpmid; i++) {
+ fprintf(stderr, " %s (%s):\n", pmIDStr(tp->t_pmidlist[i]), tp->t_namelist[i]);
+ }
+ __pmOptFetchDump(stderr, tp->t_fetch);
+ }
+ }
+#endif
+
+ if (!primary && tasklist == NULL && !linger) {
+ fprintf(stderr, "Nothing to log, and not the primary logger instance ... good-bye\n");
+ exit(1);
+ }
+
+ if ((sts = __pmLogCreate(pmcd_host, archBase, archive_version, &logctl)) < 0) {
+ fprintf(stderr, "__pmLogCreate: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+ else {
+ /*
+ * try and establish $TZ from the remote PMCD ...
+ * Note the label record has been set up, but not written yet
+ */
+ char *name = "pmcd.timezone";
+ pmID pmid;
+ pmResult *resp;
+
+ __pmtimevalNow(&epoch);
+ sts = pmUseContext(ctx);
+
+ if (sts >= 0)
+ sts = pmLookupName(1, &name, &pmid);
+ if (sts >= 0)
+ sts = pmFetch(1, &pmid, &resp);
+ if (sts >= 0) {
+ if (resp->vset[0]->numval > 0) { /* pmcd.timezone present */
+ strcpy(logctl.l_label.ill_tz, resp->vset[0]->vlist[0].value.pval->vbuf);
+ /* prefer to use remote time to avoid clock drift problems */
+ epoch = resp->timestamp; /* struct assignment */
+ if (! use_localtime)
+ pmNewZone(logctl.l_label.ill_tz);
+ }
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr,
+ "main: Could not get timezone from host %s\n",
+ pmcd_host);
+ }
+#endif
+ pmFreeResult(resp);
+ }
+ }
+
+ /* do ParseTimeWindow stuff for -T */
+ if (runtime) {
+ struct timeval res_end; /* time window end */
+ struct timeval start;
+ struct timeval end;
+ struct timeval last_delta;
+ char *err_msg; /* parsing error message */
+ time_t now;
+ struct timeval now_tv;
+
+ time(&now);
+ now_tv.tv_sec = now;
+ now_tv.tv_usec = 0;
+
+ start = now_tv;
+ end.tv_sec = INT_MAX;
+ end.tv_usec = INT_MAX;
+ sts = __pmParseTime(runtime, &start, &end, &res_end, &err_msg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: illegal -T argument\n%s", pmProgname, err_msg);
+ exit(1);
+ }
+
+ last_delta = res_end;
+ tsub(&last_delta, &now_tv);
+ __pmAFregister(&last_delta, NULL, run_done_callback);
+
+ last_stamp = res_end;
+ }
+
+ fprintf(stderr, "Archive basename: %s\n", archBase);
+
+#ifndef IS_MINGW
+ /* detach yourself from the launching process */
+ if (isdaemon)
+ setpgid(getpid(), 0);
+#endif
+
+ /* set up control port */
+ init_ports();
+ __pmFD_ZERO(&fds);
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] >= 0)
+ __pmFD_SET(ctlfds[i], &fds);
+ }
+#ifndef IS_MINGW
+ __pmFD_SET(pmcdfd, &fds);
+#endif
+ if (rsc_fd != -1)
+ __pmFD_SET(rsc_fd, &fds);
+ numfds = maxfd() + 1;
+
+ if ((sts = do_preamble()) < 0)
+ fprintf(stderr, "Warning: problem writing archive preamble: %s\n",
+ pmErrStr(sts));
+
+ sts = 0; /* default exit status */
+
+ parse_done = 1; /* enable callback processing */
+ __pmAFunblock();
+
+ for ( ; ; ) {
+ int nready;
+ __pmFD_COPY(&readyfds, &fds);
+ nready = __pmSelectRead(numfds, &readyfds, NULL);
+
+ /* block signals to simplify IO handling */
+ __pmAFblock();
+ if (nready > 0) {
+
+ /* handle request on control port */
+ for (i = 0; i < CFD_NUM; ++i) {
+ if (ctlfds[i] >= 0 && __pmFD_ISSET(ctlfds[i], &readyfds)) {
+ if (control_req(ctlfds[i])) {
+ /* new client has connected */
+ __pmFD_SET(clientfd, &fds);
+ if (clientfd >= numfds)
+ numfds = clientfd + 1;
+ }
+ }
+ }
+ if (clientfd >= 0 && __pmFD_ISSET(clientfd, &readyfds)) {
+ /* process request from client, save clientfd in case client
+ * closes connection, resetting clientfd to -1
+ */
+ int fd = clientfd;
+
+ if (client_req()) {
+ /* client closed connection */
+ __pmFD_CLR(fd, &fds);
+ __pmCloseSocket(clientfd);
+ clientfd = -1;
+ numfds = maxfd() + 1;
+ qa_case = 0;
+ }
+ }
+#ifndef IS_MINGW
+ if (pmcdfd >= 0 && __pmFD_ISSET(pmcdfd, &readyfds)) {
+ /*
+ * do not expect this, given synchronous commumication with the
+ * pmcd ... either pmcd has terminated, or bogus PDU ... or its
+ * Win32 and we are operating under the different conditions of
+ * our AF.c implementation there, which has to deal with a lack
+ * of signal support on Windows - race condition exists between
+ * this check and the async event timer callback.
+ */
+ __pmPDU *pb;
+ __pmPDUHdr *php;
+ sts = __pmGetPDU(pmcdfd, ANY_SIZE, TIMEOUT_NEVER, &pb);
+ if (sts <= 0) {
+ if (sts < 0)
+ fprintf(stderr, "Error: __pmGetPDU: %s\n", pmErrStr(sts));
+ disconnect(sts);
+ }
+ else {
+ php = (__pmPDUHdr *)pb;
+ fprintf(stderr, "Error: Unsolicited %s PDU from PMCD\n",
+ __pmPDUTypeStr(php->type));
+ disconnect(PM_ERR_IPC);
+ }
+ if (sts > 0)
+ __pmUnpinPDUBuf(pb);
+ }
+#endif
+ if (rsc_fd >= 0 && __pmFD_ISSET(rsc_fd, &readyfds)) {
+ /*
+ * some action on the recording session control fd
+ * end-of-file means launcher has quit, otherwise we
+ * expect one of these commands
+ * V<number>\n - version
+ * F<folio>\n - folio name
+ * P<name>\n - launcher's name
+ * R\n - launcher can replay
+ * D\n - detach from launcher
+ * Q\n - quit pmlogger
+ */
+ char rsc_buf[MAXPATHLEN];
+ char *rp = rsc_buf;
+ char myc;
+ int fake_x = 0;
+
+ for (rp = rsc_buf; ; rp++) {
+ if (read(rsc_fd, &myc, 1) <= 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ fprintf(stderr, "recording session control: eof\n");
+#endif
+ if (rp != rsc_buf) {
+ *rp = '\0';
+ fprintf(stderr, "Error: incomplete recording session control message: \"%s\"\n", rsc_buf);
+ }
+ fake_x = 1;
+ break;
+ }
+ if (rp >= &rsc_buf[MAXPATHLEN]) {
+ fprintf(stderr, "Error: absurd recording session control message: \"%100.100s ...\"\n", rsc_buf);
+ fake_x = 1;
+ break;
+ }
+ if (myc == '\n') {
+ *rp = '\0';
+ break;
+ }
+ *rp = myc;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (fake_x == 0)
+ fprintf(stderr, "recording session control: \"%s\"\n", rsc_buf);
+ }
+#endif
+
+ if (fake_x)
+ do_dialog('X');
+ else if (strcmp(rsc_buf, "Q") == 0 ||
+ strcmp(rsc_buf, "D") == 0 ||
+ strcmp(rsc_buf, "?") == 0)
+ do_dialog(rsc_buf[0]);
+ else if (rsc_buf[0] == 'F')
+ folio_name = strdup(&rsc_buf[1]);
+ else if (rsc_buf[0] == 'P')
+ rsc_prog = strdup(&rsc_buf[1]);
+ else if (strcmp(rsc_buf, "R") == 0)
+ rsc_replay = 1;
+ else if (rsc_buf[0] == 'V' && rsc_buf[1] == '0') {
+ /*
+ * version 0 of the recording session control ...
+ * this is all we grok at the moment
+ */
+ ;
+ }
+ else {
+ fprintf(stderr, "Error: illegal recording session control message: \"%s\"\n", rsc_buf);
+ do_dialog('X');
+ }
+ }
+ }
+ else if (vol_switch_flag) {
+ newvolume(VOL_SW_SIGHUP);
+ vol_switch_flag = 0;
+ }
+ else if (nready < 0 && neterror() != EINTR)
+ fprintf(stderr, "Error: select: %s\n", netstrerror());
+
+ __pmAFunblock();
+
+ if (exit_code)
+ break;
+ }
+ exit(exit_code);
+}
+
+int
+newvolume(int vol_switch_type)
+{
+ FILE *newfp;
+ int nextvol = logctl.l_curvol + 1;
+ time_t now;
+ static char *vol_sw_strs[] = {
+ "SIGHUP", "pmlc request", "sample counter",
+ "sample byte size", "sample time", "max data volume size"
+ };
+
+ vol_samples_counter = 0;
+ vol_bytes += ftell(logctl.l_mfp);
+ if (exit_bytes != -1) {
+ if (vol_bytes >= exit_bytes)
+ run_done(0, "Byte limit reached");
+ }
+
+ /*
+ * If we are not part of a callback but instead a
+ * volume switch from "pmlc" or a "SIGHUP" then
+ * get rid of pending volume switch in event queue
+ * as it will now be wrong, and schedule a new
+ * volume switch event.
+ */
+ if (vol_switch_afid >= 0 && vol_switch_type != VOL_SW_TIME) {
+ __pmAFunregister(vol_switch_afid);
+ vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
+ vol_switch_callback);
+ }
+
+ if ((newfp = __pmLogNewFile(archBase, nextvol)) != NULL) {
+ if (logctl.l_state == PM_LOG_STATE_NEW) {
+ /*
+ * nothing has been logged as yet, force out the label records
+ */
+ __pmtimevalNow(&last_stamp);
+ logctl.l_label.ill_start.tv_sec = (__int32_t)last_stamp.tv_sec;
+ logctl.l_label.ill_start.tv_usec = (__int32_t)last_stamp.tv_usec;
+ logctl.l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
+ logctl.l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
+ logctl.l_label.ill_vol = 0;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ logctl.l_state = PM_LOG_STATE_INIT;
+ }
+#if 0
+ if (last_stamp.tv_sec != 0) {
+ __pmTimeval tmp;
+ tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
+ tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
+ __pmLogPutIndex(&logctl, &tmp);
+ }
+#endif
+ fclose(logctl.l_mfp);
+ logctl.l_mfp = newfp;
+ logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ time(&now);
+ fprintf(stderr, "New log volume %d, via %s at %s",
+ nextvol, vol_sw_strs[vol_switch_type], ctime(&now));
+ return nextvol;
+ }
+ else
+ return -oserror();
+}
+
+void
+disconnect(int sts)
+{
+ time_t now;
+#if CAN_RECONNECT
+ int ctx;
+ __pmContext *ctxp;
+#endif
+
+ time(&now);
+ if (sts != 0)
+ fprintf(stderr, "%s: Error: %s\n", pmProgname, pmErrStr(sts));
+ fprintf(stderr, "%s: Lost connection to PMCD on \"%s\" at %s",
+ pmProgname, pmcd_host, ctime(&now));
+#if CAN_RECONNECT
+ if (primary) {
+ fprintf(stderr, "This is fatal for the primary logger.");
+ exit(1);
+ }
+ close(pmcdfd);
+ __pmFD_CLR(pmcdfd, &fds);
+ pmcdfd = -1;
+ numfds = maxfd() + 1;
+ if ((ctx = pmWhichContext()) >= 0)
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctx < 0 || ctxp == NULL) {
+ fprintf(stderr, "%s: disconnect botch: cannot get context: %s\n", pmProgname, pmErrStr(ctx));
+ exit(1);
+ }
+ ctxp->c_pmcd->pc_fd = -1;
+ PM_UNLOCK(ctxp->c_lock);
+#else
+ exit(1);
+#endif
+}
+
+#if CAN_RECONNECT
+int
+reconnect(void)
+{
+ int sts;
+ int ctx;
+ __pmContext *ctxp;
+
+ if ((ctx = pmWhichContext()) >= 0)
+ ctxp = __pmHandleToPtr(ctx);
+ if (ctx < 0 || ctxp == NULL) {
+ fprintf(stderr, "%s: reconnect botch: cannot get context: %s\n", pmProgname, pmErrStr(ctx));
+ exit(1);
+ }
+ sts = pmReconnectContext(ctx);
+ if (sts >= 0) {
+ time_t now;
+ time(&now);
+ fprintf(stderr, "%s: re-established connection to PMCD on \"%s\" at %s\n",
+ pmProgname, pmcd_host, ctime(&now));
+ pmcdfd = ctxp->c_pmcd->pc_fd;
+ __pmFD_SET(pmcdfd, &fds);
+ numfds = maxfd() + 1;
+ }
+ PM_UNLOCK(ctxp->c_lock);
+ return sts;
+}
+#endif
diff --git a/src/pmlogger/src/ports.c b/src/pmlogger/src/ports.c
new file mode 100644
index 0000000..8d5eed3
--- /dev/null
+++ b/src/pmlogger/src/ports.c
@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 1995-2001,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#define _WIN32_WINNT 0x0500 /* for CreateHardLink */
+#include <math.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include "logger.h"
+
+#if !defined(SIGRTMAX)
+#if defined(NSIG)
+#define SIGRTMAX (NSIG)
+#else
+! bozo neither NSIG nor SIGRTMAX are defined
+#endif
+#endif
+
+/* The logger will try to allocate port numbers beginning with the number
+ * defined below. If that is in use it will keep adding one and trying again
+ * until it allocates a port.
+ */
+#define PORT_BASE 4330 /* Base of range for port numbers */
+
+static char *ctlfile; /* Control directory/portmap name */
+static char *linkfile; /* Link name for primary logger */
+static const char *socketPath; /* Path to unix domain sockets. */
+static const char *linkSocketPath;/* Link to socket for primary logger */
+
+int ctlfds[CFD_NUM] = {-1, -1, -1};/* fds for control ports: */
+int ctlport; /* pmlogger control port number */
+
+static void
+cleanup(void)
+{
+ if (linkfile != NULL)
+ unlink(linkfile);
+ if (ctlfile != NULL)
+ unlink(ctlfile);
+ if (linkSocketPath != NULL)
+ unlink(linkSocketPath);
+ if (socketPath != NULL)
+ unlink(socketPath);
+}
+
+static void
+sigexit_handler(int sig)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "pmlogger: Signalled (signal=%d), exiting\n", sig);
+#endif
+ cleanup();
+ _exit(sig);
+}
+
+static void
+sigterm_handler(int sig)
+{
+ /* exit as soon as possible, handler is deferred for log cleanup */
+ exit_code = sig;
+}
+
+static void
+sighup_handler(int sig)
+{
+ __pmSetSignalHandler(SIGHUP, sighup_handler);
+ vol_switch_flag = 1;
+}
+
+#ifndef IS_MINGW
+static void
+sigcore_handler(int sig)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_DESPERATE)
+ fprintf(stderr, "pmlogger: Signalled (signal=%d), exiting (core dumped)\n", sig);
+#endif
+ __pmSetSignalHandler(SIGABRT, SIG_DFL); /* Don't come back here */
+ cleanup();
+ _exit(sig);
+}
+
+static void
+sigpipe_handler(int sig)
+{
+ /*
+ * just ignore the signal, the write() will fail, and the PDU
+ * xmit will return with an error
+ */
+ __pmSetSignalHandler(SIGPIPE, sigpipe_handler);
+}
+
+static void
+sigusr1_handler(int sig)
+{
+ /*
+ * no-op now that all archive write I/O is unbuffered
+ */
+ __pmSetSignalHandler(SIGUSR1, sigusr1_handler);
+}
+#endif
+
+typedef struct {
+ int sig;
+ void (*func)(int);
+} sig_map_t;
+
+/* This is used to set the dispositions for the various signals received.
+ * Try to do the right thing for the various STOP/CONT signals.
+ */
+static sig_map_t sig_handler[] = {
+ { SIGHUP, sighup_handler }, /* Exit Hangup [see termio(7)] */
+ { SIGINT, sigterm_handler }, /* Exit Interrupt [see termio(7)] */
+#ifndef IS_MINGW
+ { SIGQUIT, sigcore_handler }, /* Core Quit [see termio(7)] */
+ { SIGILL, sigcore_handler }, /* Core Illegal Instruction */
+ { SIGTRAP, sigcore_handler }, /* Core Trace/Breakpoint Trap */
+ { SIGABRT, sigcore_handler }, /* Core Abort */
+#ifdef SIGEMT
+ { SIGEMT, sigcore_handler }, /* Core Emulation Trap */
+#endif
+ { SIGFPE, sigcore_handler }, /* Core Arithmetic Exception */
+ { SIGKILL, sigexit_handler }, /* Exit Killed */
+ { SIGBUS, sigcore_handler }, /* Core Bus Error */
+ { SIGSEGV, sigcore_handler }, /* Core Segmentation Fault */
+ { SIGSYS, sigcore_handler }, /* Core Bad System Call */
+ { SIGPIPE, sigpipe_handler }, /* Exit Broken Pipe */
+ { SIGALRM, sigterm_handler }, /* Exit Alarm Clock */
+#endif
+ { SIGTERM, sigterm_handler }, /* Exit Terminated */
+#ifndef IS_MINGW
+ { SIGUSR1, sigusr1_handler }, /* NOP User Signal 1 - [was fflush(3)] */
+ { SIGUSR2, sigexit_handler }, /* Exit User Signal 2 */
+ { SIGCHLD, SIG_IGN }, /* NOP Child stopped or terminated */
+#ifdef SIGPWR
+ { SIGPWR, SIG_DFL }, /* Ignore Power Fail/Restart */
+#endif
+ { SIGWINCH, SIG_DFL }, /* Ignore Window Size Change */
+ { SIGURG, SIG_DFL }, /* Ignore Urgent Socket Condition */
+#ifdef SIGPOLL
+ { SIGPOLL, sigexit_handler }, /* Exit Pollable Event [see streamio(7)] */
+#endif
+ { SIGSTOP, SIG_DFL }, /* Stop Stopped (signal) */
+ { SIGTSTP, SIG_DFL }, /* Stop Stopped (user) */
+ { SIGCONT, SIG_DFL }, /* Ignore Continued */
+ { SIGTTIN, SIG_DFL }, /* Stop Stopped (tty input) */
+ { SIGTTOU, SIG_DFL }, /* Stop Stopped (tty output) */
+ { SIGVTALRM, sigterm_handler }, /* Exit Virtual Timer Expired */
+
+ { SIGPROF, sigterm_handler }, /* Exit Profiling Timer Expired */
+ { SIGXCPU, sigcore_handler }, /* Core CPU time limit exceeded [see getrlimit(2)] */
+ { SIGXFSZ, sigcore_handler} /* Core File size limit exceeded [see getrlimit(2)] */
+#endif
+};
+
+/* Create a network socket for incoming connections and bind to it an address for
+ * clients to use.
+ * If supported, also create a unix domain socket for local clients to use.
+ * Only returns if it succeeds (exits on failure).
+ */
+
+static void
+GetPorts(char *file)
+{
+ int fd;
+ int mapfd = -1;
+ FILE *mapstream = NULL;
+ int sts;
+ int socketsCreated = 0;
+ int ctlix;
+ __pmSockAddr *myAddr;
+ char globalPath[MAXPATHLEN];
+ char localPath[MAXPATHLEN];
+ static int port_base = -1;
+
+ /* Try to create sockets for control connections. */
+ for (ctlix = 0; ctlix < CFD_NUM; ++ctlix) {
+ if (ctlix == CFD_UNIX) {
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ const char *socketError;
+ const char *errorPath;
+ /* Try to create a unix domain socket, if supported. */
+ fd = __pmCreateUnixSocket();
+ if (fd < 0) {
+ fprintf(stderr, "GetPorts: unix domain socket failed: %s\n", netstrerror());
+ continue;
+ }
+ if ((myAddr = __pmSockAddrAlloc()) == NULL) {
+ fprintf(stderr, "GetPorts: __pmSockAddrAlloc out of memory\n");
+ exit(1);
+ }
+ socketPath = __pmLogLocalSocketDefault(getpid(), globalPath, sizeof(globalPath));
+ __pmSockAddrSetFamily(myAddr, AF_UNIX);
+ __pmSockAddrSetPath(myAddr, socketPath);
+ __pmServerSetLocalSocket(socketPath);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+
+ /*
+ * If we cannot bind to the system wide socket path, then try binding
+ * to the user specific one.
+ */
+ if (sts < 0) {
+ char *tmpPath;
+ socketError = netstrerror();
+ errorPath = socketPath;
+ unlink(errorPath);
+ socketPath = __pmLogLocalSocketUser(getpid(), localPath, sizeof(localPath));
+ if (socketPath == NULL) {
+ sts = -ESRCH;
+ }
+ else {
+ /*
+ * Make sure that the directory exists. dirname may modify the
+ * contents of its first argument, so use a copy.
+ */
+ if ((tmpPath = strdup(socketPath)) == NULL) {
+ fprintf(stderr, "GetPorts: strdup out of memory\n");
+ exit(1);
+ }
+ sts = __pmMakePath(dirname(tmpPath),
+ S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ free(tmpPath);
+ if (sts >= 0 || oserror() == EEXIST) {
+ __pmSockAddrSetPath(myAddr, socketPath);
+ __pmServerSetLocalSocket(socketPath);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+ }
+ }
+ }
+ __pmSockAddrFree(myAddr);
+
+ if (sts < 0) {
+ /* Could not bind to either socket path. */
+ fprintf(stderr, "__pmBind(%s): %s\n", errorPath, socketError);
+ if (sts == -ESRCH)
+ fprintf(stderr, "__pmLogLocalSocketUser(): %s\n", osstrerror());
+ else
+ fprintf(stderr, "__pmBind(%s): %s\n", socketPath, netstrerror());
+ }
+ else {
+ /*
+ * For unix domain sockets, grant rw access to the socket for all,
+ * otherwise, on linux platforms, connection will not be possible.
+ * This must be done AFTER binding the address. See Unix(7) for details.
+ */
+ sts = chmod(socketPath, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (sts != 0) {
+ fprintf(stderr, "GetPorts: chmod(%s): %s\n", socketPath, strerror(errno));
+ }
+ }
+ /* On error, don't leave the socket file lying around. */
+ if (sts < 0) {
+ unlink(socketPath);
+ socketPath = NULL;
+ }
+ else if ((socketPath = strdup(socketPath)) == NULL) {
+ fprintf(stderr, "GetPorts: strdup out of memory\n");
+ exit(1);
+ }
+#else
+ /*
+ * Unix domain sockets are not supported.
+ * This is not an error, just don't try to create one.
+ */
+ continue;
+#endif
+ }
+ else {
+ /* Try to create a network socket. */
+ if (ctlix == CFD_INET) {
+ fd = __pmCreateSocket();
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "GetPorts: inet socket creation failed: %s\n",
+ netstrerror());
+#endif
+ continue;
+ }
+ }
+ else {
+ fd = __pmCreateIPv6Socket();
+ if (fd < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "GetPorts: ipv6 socket creation failed: %s\n",
+ netstrerror());
+#endif
+ continue;
+ }
+ }
+ if (port_base == -1) {
+ /*
+ * get optional stuff from environment ...
+ * PMLOGGER_PORT
+ */
+ char *env_str;
+ if ((env_str = getenv("PMLOGGER_PORT")) != NULL) {
+ char *end_ptr;
+
+ port_base = strtol(env_str, &end_ptr, 0);
+ if (*end_ptr != '\0' || port_base < 0) {
+ fprintf(stderr,
+ "GetPorts: ignored bad PMLOGGER_PORT = '%s'\n", env_str);
+ port_base = PORT_BASE;
+ }
+ }
+ else
+ port_base = PORT_BASE;
+ }
+
+ /*
+ * try to allocate ports from port_base. If port already in use, add one
+ * and try again.
+ */
+ if ((myAddr = __pmSockAddrAlloc()) == NULL) {
+ fprintf(stderr, "GetPorts: __pmSockAddrAlloc out of memory\n");
+ exit(1);
+ }
+ for (ctlport = port_base; ; ctlport++) {
+ if (ctlix == CFD_INET)
+ __pmSockAddrInit(myAddr, AF_INET, INADDR_ANY, ctlport);
+ else
+ __pmSockAddrInit(myAddr, AF_INET6, INADDR_ANY, ctlport);
+ sts = __pmBind(fd, (void *)myAddr, __pmSockAddrSize());
+ if (sts < 0) {
+ if (neterror() != EADDRINUSE) {
+ fprintf(stderr, "__pmBind(%d): %s\n", ctlport, netstrerror());
+ break;
+ }
+ }
+ else
+ break;
+ }
+ __pmSockAddrFree(myAddr);
+ }
+
+ /* Now listen on the new socket. */
+ if (sts >= 0) {
+ sts = __pmListen(fd, 5); /* Max. of 5 pending connection requests */
+ if (sts == -1) {
+ __pmCloseSocket(fd);
+ fprintf(stderr, "__pmListen: %s\n", netstrerror());
+ }
+ else {
+ ctlfds[ctlix] = fd;
+ ++socketsCreated;
+ }
+ }
+ }
+
+ if (socketsCreated != 0) {
+ /* create and initialize the port map file */
+ unlink(file);
+ mapfd = open(file, O_WRONLY | O_EXCL | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (mapfd == -1) {
+ /* not a fatal error; continue on without control file */
+#ifdef DESPERATE
+ fprintf(stderr, "%s: error creating port map file %s: %s. Exiting.\n",
+ pmProgname, file, osstrerror());
+#endif
+ return;
+ }
+ /* write the port number to the port map file */
+ if ((mapstream = fdopen(mapfd, "w")) == NULL) {
+ /* not a fatal error; continue on without control file */
+ close(mapfd);
+#ifdef DESPERATE
+ perror("GetPorts: fdopen");
+#endif
+ return;
+ }
+ /* first the port number */
+ fprintf(mapstream, "%d\n", ctlport);
+
+ /* then the PMCD host (but don't bother try DNS-canonicalize) */
+ fprintf(mapstream, "%s\n", pmcd_host);
+
+ /* then the full pathname to the archive base */
+ __pmNativePath(archBase);
+ if (__pmAbsolutePath(archBase))
+ fprintf(mapstream, "%s\n", archBase);
+ else {
+ char path[MAXPATHLEN];
+
+ if (getcwd(path, MAXPATHLEN) == NULL)
+ fprintf(mapstream, "\n");
+ else
+ fprintf(mapstream, "%s%c%s\n", path, __pmPathSeparator(), archBase);
+ }
+
+ /* and finally, the annotation from -m or -x */
+ if (note != NULL)
+ fprintf(mapstream, "%s\n", note);
+ }
+
+ if (mapstream != NULL)
+ fclose(mapstream);
+ if (mapfd >= 0)
+ close(mapfd);
+ if (socketsCreated == 0)
+ exit(1);
+}
+
+/* Create the control port for this pmlogger and the file containing the port
+ * number so that other programs know which port to connect to.
+ * If this is the primary pmlogger, create the special link to the
+ * control file.
+ */
+void
+init_ports(void)
+{
+ int i, j, n, sts;
+ int sep = __pmPathSeparator();
+ int extlen, baselen;
+ char path[MAXPATHLEN];
+ pid_t mypid = getpid();
+
+ /*
+ * make sure control port files are removed when pmlogger terminates
+ * by trapping all the signals we can
+ */
+ for (i = 0; i < sizeof(sig_handler)/sizeof(sig_handler[0]); i++) {
+ __pmSetSignalHandler(sig_handler[i].sig, sig_handler[i].func);
+ }
+ /*
+ * install explicit handler for other signals ... we assume all
+ * of the interesting signals we are likely to receive are smaller
+ * than 32 (this is a hack 'cause there is no portable way of
+ * determining the maximum signal number)
+ */
+ for (j = 1; j < 32; j++) {
+ for (i = 0; i < sizeof(sig_handler)/sizeof(sig_handler[0]); i++) {
+ if (j == sig_handler[i].sig) break;
+ }
+ if (i == sizeof(sig_handler)/sizeof(sig_handler[0]))
+ /* not special cased in seg_handler[] */
+ __pmSetSignalHandler(j, sigexit_handler);
+ }
+
+#if defined(HAVE_ATEXIT)
+ if (atexit(cleanup) != 0) {
+ perror("atexit");
+ fprintf(stderr, "%s: unable to register atexit cleanup function. Exiting\n",
+ pmProgname);
+ cleanup();
+ exit(1);
+ }
+#endif
+
+ /* create the control port file (make the directory if necessary). */
+
+ /* count digits in mypid */
+ for (n = mypid, extlen = 1; n ; extlen++)
+ n /= 10;
+ /* baselen is directory + trailing / */
+ snprintf(path, sizeof(path), "%s%cpmlogger", pmGetConfig("PCP_TMP_DIR"), sep);
+ baselen = strlen(path) + 1;
+ /* likewise for PCP_DIR if it is set */
+ n = baselen + extlen + 1;
+ ctlfile = (char *)malloc(n);
+ if (ctlfile == NULL)
+ __pmNoMem("port file name", n, PM_FATAL_ERR);
+ strcpy(ctlfile, path);
+
+ /* try to create the port file directory. OK if it already exists */
+ sts = mkdir2(ctlfile, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (sts < 0) {
+ if (oserror() != EEXIST) {
+ fprintf(stderr, "%s: error creating port file dir %s: %s\n",
+ pmProgname, ctlfile, osstrerror());
+ exit(1);
+ }
+ } else {
+ chmod(ctlfile, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
+ }
+
+ /* remove any existing port file with my name (it's old) */
+ snprintf(ctlfile + (baselen-1), n, "%c%" FMT_PID, sep, mypid);
+ unlink(ctlfile);
+
+ /* get control port and write port map file */
+ GetPorts(ctlfile);
+
+ /*
+ * If this is the primary logger, make the special link for
+ * clients to connect specifically to it.
+ */
+ if (primary) {
+ baselen = snprintf(path, sizeof(path), "%s%cpmlogger",
+ pmGetConfig("PCP_TMP_DIR"), sep);
+ n = baselen + 9; /* separator + "primary" + null */
+ linkfile = (char *)malloc(n);
+ if (linkfile == NULL)
+ __pmNoMem("primary logger link file name", n, PM_FATAL_ERR);
+ snprintf(linkfile, n, "%s%cprimary", path, sep);
+#ifndef IS_MINGW
+ sts = link(ctlfile, linkfile);
+#else
+ sts = (CreateHardLink(linkfile, ctlfile, NULL) == 0);
+#endif
+ if (sts != 0) {
+ if (oserror() == EEXIST)
+ fprintf(stderr, "%s: there is already a primary pmlogger running\n", pmProgname);
+ else
+ fprintf(stderr, "%s: error creating primary logger link %s: %s\n",
+ pmProgname, linkfile, osstrerror());
+ exit(1);
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /* Create a hard link to the local socket for users wanting the primary logger. */
+ linkSocketPath = __pmLogLocalSocketDefault(PM_LOG_PRIMARY_PID, path, sizeof(path));
+#ifndef IS_MINGW
+ sts = link(socketPath, linkSocketPath);
+#else
+ sts = (CreateHardLink(linkSocketPath, socketPath, NULL) == 0);
+#endif
+ if (sts != 0) {
+ if (oserror() == EEXIST)
+ fprintf(stderr, "%s: there is already a primary pmlogger running\n", pmProgname);
+ else
+ fprintf(stderr, "%s: error creating primary logger socket link %s: %s\n",
+ pmProgname, linkSocketPath, osstrerror());
+ exit(1);
+ }
+ if ((linkSocketPath = strdup(linkSocketPath)) == NULL) {
+ fprintf(stderr, "init_ports: strdup out of memory\n");
+ exit(1);
+ }
+#endif
+ }
+}
+
+/* Service a request on the control port Return non-zero if a new client
+ * connection has been accepted.
+ */
+
+int clientfd = -1;
+unsigned int denyops = 0; /* for access control (ops not allowed) */
+char pmlc_host[MAXHOSTNAMELEN];
+int connect_state = 0;
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+static int
+check_local_creds(__pmHashCtl *attrs)
+{
+ __pmHashNode *node;
+ const char *connectingUser;
+ char *end;
+ __pmUserID connectingUid;
+
+ /* Get the user name of the connecting process. */
+ connectingUser = ((node = __pmHashSearch(PCP_ATTR_USERID, attrs)) ?
+ (const char *)node->data : NULL);
+ if (connectingUser == NULL) {
+ /* We don't know who is connecting. */
+ return PM_ERR_PERMISSION;
+ }
+
+ /* Get the uid of the connecting process. */
+ errno = 0;
+ connectingUid = strtol(connectingUser, &end, 0);
+ if (errno != 0 || *end != '\0') {
+ /* Can't convert the connecting user to a uid cleanly. */
+ return PM_ERR_PERMISSION;
+ }
+
+ /* Allow connections from root (uid == 0). */
+ if (connectingUid == 0)
+ return 0;
+
+ /* Allow connections from the same user as us. */
+ if (connectingUid == getuid() || connectingUid == geteuid())
+ return 0;
+
+ /* Connection is not allowed. */
+ return PM_ERR_PERMISSION;
+}
+#endif /* defined(HAVE_STRUCT_SOCKADDR_UN) */
+
+int
+control_req(int ctlfd)
+{
+ int fd, sts;
+ char *abuf;
+ char *hostName;
+ __pmSockAddr *addr;
+ __pmSockLen addrlen;
+
+ if ((addr = __pmSockAddrAlloc()) == NULL) {
+ fputs("error allocating space for client sockaddr\n", stderr);
+ return 0;
+ }
+ addrlen = __pmSockAddrSize();
+ fd = __pmAccept(ctlfd, addr, &addrlen);
+ if (fd == -1) {
+ fprintf(stderr, "error accepting client: %s\n", netstrerror());
+ __pmSockAddrFree(addr);
+ return 0;
+ }
+ __pmSetSocketIPC(fd);
+ if (clientfd != -1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "control_req: send EADDRINUSE on fd=%d (client already on fd=%d)\n", fd, clientfd);
+#endif
+ sts = __pmSendError(fd, FROM_ANON, -EADDRINUSE);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ sts = __pmSetVersionIPC(fd, UNKNOWN_VERSION);
+ if (sts < 0) {
+ __pmSendError(fd, FROM_ANON, sts);
+ fprintf(stderr, "error connecting to client: %s\n", pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ hostName = __pmGetNameInfo(addr);
+ if (hostName == NULL || strlen(hostName) > MAXHOSTNAMELEN-1) {
+ abuf = __pmSockAddrToString(addr);
+ snprintf(pmlc_host, sizeof(pmlc_host), "%s", abuf);
+ free(abuf);
+ }
+ else {
+ /* this is safe, due to strlen() test above */
+ strcpy(pmlc_host, hostName);
+ }
+ if (hostName != NULL)
+ free(hostName);
+
+ sts = __pmAccAddClient(addr, &denyops);
+ if (sts < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ abuf = __pmSockAddrToString(addr);
+ fprintf(stderr, "client addr: %s\n\n", abuf);
+ free(abuf);
+ __pmAccDumpHosts(stderr);
+ fprintf(stderr, "\ncontrol_req: connection rejected on fd=%d from %s: %s\n", fd, pmlc_host, pmErrStr(sts));
+ }
+#endif
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection access NACK to client: %s\n",
+ pmErrStr(sts));
+ sleep(1); /* QA 083 seems like there is a race w/out this delay */
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+#if defined(HAVE_STRUCT_SOCKADDR_UN)
+ /*
+ * For connections on an AF_UNIX socket, check the user credentials of the
+ * connecting process.
+ */
+ if (__pmSockAddrGetFamily(addr) == AF_UNIX) {
+ __pmHashCtl clientattrs; /* Connection attributes (auth info) */
+ __pmHashInit(&clientattrs);
+
+ /* Get the user credentials. */
+ if ((sts = __pmServerSetLocalCreds(fd, &clientattrs)) < 0) {
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection credentials NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ /* Check the user credentials. */
+ if ((sts = check_local_creds(&clientattrs)) < 0) {
+ sts = __pmSendError(fd, FROM_ANON, sts);
+ if (sts < 0)
+ fprintf(stderr, "error sending connection credentials NACK to client: %s\n",
+ pmErrStr(sts));
+ __pmSockAddrFree(addr);
+ __pmCloseSocket(fd);
+ return 0;
+ }
+
+ /* This information is no longer needed. */
+ __pmFreeAttrsSpec(&clientattrs);
+ __pmHashClear(&clientattrs);
+ }
+#endif
+
+ /* Done with this address. */
+ __pmSockAddrFree(addr);
+
+ /*
+ * encode pdu version in the acknowledgement
+ * also need "from" to be pmlogger's pid as this is checked at
+ * the other end
+ */
+ sts = __pmSendError(fd, (int)getpid(), LOG_PDU_VERSION);
+ if (sts < 0) {
+ fprintf(stderr, "error sending connection ACK to client: %s\n",
+ pmErrStr(sts));
+ __pmCloseSocket(fd);
+ return 0;
+ }
+ clientfd = fd;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "control_req: connection accepted on fd=%d from %s\n", fd, pmlc_host);
+#endif
+
+ return 1;
+}
diff --git a/src/pmlogger/src/preamble.c b/src/pmlogger/src/preamble.c
new file mode 100644
index 0000000..ff37f09
--- /dev/null
+++ b/src/pmlogger/src/preamble.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+#include "pmda.h"
+
+/*
+ * this routine creates the "fake" pmResult to be added to the
+ * start of the archive log to identify information about the
+ * archive beyond what is in the archive label.
+ */
+
+/* encode the domain(x), cluster (y) and item (z) parts of the PMID */
+#define PMID(x,y,z) ((x<<22)|(y<<10)|z)
+
+/* encode the domain(x) and serial (y) parts of the pmInDom */
+#define INDOM(x,y) ((x<<22)|y)
+
+/*
+ * Note: these pmDesc entries MUST correspond to the corrsponding
+ * entries from the real PMDA ...
+ * We fake it out here to accommodate logging from PCP 1.1
+ * PMCD's and to avoid round-trip dependencies in setting up
+ * the preamble
+ */
+static pmDesc desc[] = {
+/* pmcd.pmlogger.host */
+ { PMID(2,3,3), PM_TYPE_STRING, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+/* pmcd.pmlogger.port */
+ { PMID(2,3,0), PM_TYPE_U32, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+/* pmcd.pmlogger.archive */
+ { PMID(2,3,2), PM_TYPE_STRING, INDOM(2,1), PM_SEM_DISCRETE, {0,0,0,0,0,0} },
+};
+/* names added for version 2 archives */
+static char* names[] = {
+"pmcd.pmlogger.host",
+"pmcd.pmlogger.port",
+"pmcd.pmlogger.archive"
+};
+
+static int n_metric = sizeof(desc) / sizeof(desc[0]);
+
+int
+do_preamble(void)
+{
+ int sts;
+ int i;
+ int j;
+ pid_t mypid = getpid();
+ pmResult *res;
+ __pmPDU *pb;
+ pmAtomValue atom;
+ __pmTimeval tmp;
+ char path[MAXPATHLEN];
+ char host[MAXHOSTNAMELEN];
+ int free_cp;
+
+ /* start to build the pmResult */
+ res = (pmResult *)malloc(sizeof(pmResult) + (n_metric - 1) * sizeof(pmValueSet *));
+ if (res == NULL)
+ return -oserror();
+
+ res->numpmid = n_metric;
+ last_stamp = res->timestamp = epoch; /* struct assignment */
+ tmp.tv_sec = (__int32_t)epoch.tv_sec;
+ tmp.tv_usec = (__int32_t)epoch.tv_usec;
+
+ for (i = 0; i < n_metric; i++)
+ res->vset[i] = NULL;
+
+ for (i = 0; i < n_metric; i++) {
+ res->vset[i] = (pmValueSet *)malloc(sizeof(pmValueSet));
+ if (res->vset[i] == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ res->vset[i]->pmid = desc[i].pmid;
+ res->vset[i]->numval = 1;
+ /* special case for each value 0 .. n_metric-1 */
+ free_cp = 0;
+ if (desc[i].pmid == PMID(2,3,3)) {
+ __pmHostEnt *servInfo;
+ /* my fully qualified hostname, cloned from the pmcd PMDA */
+ (void)gethostname(host, MAXHOSTNAMELEN);
+ host[MAXHOSTNAMELEN-1] = '\0';
+ if ((servInfo = __pmGetAddrInfo(host)) == NULL)
+ atom.cp = host;
+ else {
+ atom.cp = __pmHostEntGetName(servInfo);
+ __pmHostEntFree(servInfo);
+ if (atom.cp == NULL)
+ atom.cp = host;
+ else
+ free_cp = 1;
+ }
+ }
+ else if (desc[i].pmid == PMID(2,3,0)) {
+ /* my control port number, from ports.c */
+ atom.l = ctlport;
+ }
+ else if (desc[i].pmid == PMID(2,3,2)) {
+ /*
+ * the full pathname to the base of the archive, cloned
+ * from GetPort() in ports.c
+ */
+ if (__pmAbsolutePath(archBase))
+ atom.cp = archBase;
+ else {
+ if (getcwd(path, MAXPATHLEN) == NULL)
+ atom.cp = archBase;
+ else {
+ strcat(path, "/");
+ strcat(path, archBase);
+ atom.cp = path;
+ }
+ }
+ }
+
+ sts = __pmStuffValue(&atom, &res->vset[i]->vlist[0], desc[i].type);
+ if (free_cp)
+ free(atom.cp);
+ if (sts < 0)
+ goto done;
+ res->vset[i]->vlist[0].inst = (int)mypid;
+ res->vset[i]->valfmt = sts;
+ }
+
+ if ((sts = __pmEncodeResult(fileno(logctl.l_mfp), res, &pb)) < 0)
+ goto done;
+
+ __pmOverrideLastFd(fileno(logctl.l_mfp)); /* force use of log version */
+ /* and start some writing to the archive log files ... */
+ sts = __pmLogPutResult2(&logctl, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0)
+ goto done;
+
+ for (i = 0; i < n_metric; i++) {
+ if ((sts = __pmLogPutDesc(&logctl, &desc[i], 1, &names[i])) < 0)
+ goto done;
+ if (desc[i].indom == PM_INDOM_NULL)
+ continue;
+ for (j = 0; j < i; j++) {
+ if (desc[i].indom == desc[j].indom)
+ break;
+ }
+ if (j == i) {
+ /* need indom ... force one with my PID as the only instance */
+ int *instid;
+ char **instname;
+
+ if ((instid = (int *)malloc(sizeof(*instid))) == NULL) {
+ sts = -oserror();
+ goto done;
+ }
+ *instid = (int)mypid;
+ snprintf(path, sizeof(path), "%" FMT_PID, mypid);
+ if ((instname = (char **)malloc(sizeof(char *)+strlen(path)+1)) == NULL) {
+ free(instid);
+ sts = -oserror();
+ goto done;
+ }
+ /*
+ * this _is_ correct ... instname[] is a one element array
+ * with the string value immediately following
+ */
+ instname[0] = (char *)&instname[1];
+ strcpy(instname[0], path);
+ /*
+ * Note. DO NOT free instid and instname ... they get hidden
+ * away in addindom() below __pmLogPutInDom()
+ */
+ if ((sts = __pmLogPutInDom(&logctl, desc[i].indom, &tmp, 1, instid, instname)) < 0)
+ goto done;
+ }
+ }
+
+ /* fudge the temporal index */
+ fseek(logctl.l_mfp, sizeof(__pmLogLabel)+2*sizeof(int), SEEK_SET);
+ fseek(logctl.l_mdfp, sizeof(__pmLogLabel)+2*sizeof(int), SEEK_SET);
+ __pmLogPutIndex(&logctl, &tmp);
+ fseek(logctl.l_mfp, 0L, SEEK_END);
+ fseek(logctl.l_mdfp, 0L, SEEK_END);
+ sts = 0;
+
+ /*
+ * and now free stuff
+ */
+done:
+ for (i = 0; i < n_metric; i++) {
+ if (res->vset[i] != NULL)
+ free(res->vset[i]);
+ }
+ free(res);
+
+ return sts;
+}
diff --git a/src/pmlogger/src/rewrite.c b/src/pmlogger/src/rewrite.c
new file mode 100644
index 0000000..cd8104e
--- /dev/null
+++ b/src/pmlogger/src/rewrite.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "logger.h"
+
+/*
+ * PDU for pmResult (PDU_RESULT) -- from libpcp/src/p_fetch.c
+ */
+
+typedef struct {
+ pmID pmid;
+ int numval; /* no. of vlist els to follow, or error */
+ int valfmt; /* insitu or pointer */
+ __pmValue_PDU vlist[1]; /* zero or more */
+} vlist_t;
+
+typedef struct {
+ __pmPDUHdr hdr;
+ __pmTimeval timestamp; /* when returned */
+ int numpmid; /* no. of PMIDs to follow */
+ __pmPDU data[1]; /* zero or more */
+} result_t;
+
+__pmPDU *
+rewrite_pdu(__pmPDU *pb, int version)
+{
+ if (version == PM_LOG_VERS02)
+ return pb;
+
+ fprintf(stderr, "Errors: do not know how to re-write the PDU buffer for a version %d archive\n", version);
+ exit(1);
+}
diff --git a/src/pmlogger/src/util.c b/src/pmlogger/src/util.c
new file mode 100644
index 0000000..eb09779
--- /dev/null
+++ b/src/pmlogger/src/util.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "logger.h"
+
+void
+buildinst(int *numinst, int **intlist, char ***extlist, int intid, char *extid)
+{
+ char **el;
+ int *il;
+ int num = *numinst;
+
+ if (num == 0) {
+ il = NULL;
+ el = NULL;
+ }
+ else {
+ il = *intlist;
+ el = *extlist;
+ }
+
+ el = (char **)realloc(el, (num+1)*sizeof(el[0]));
+ il = (int *)realloc(il, (num+1)*sizeof(il[0]));
+
+ il[num] = intid;
+
+ if (extid == NULL)
+ el[num] = NULL;
+ else {
+ if (*extid == '"') {
+ char *p;
+ p = ++extid;
+ while (*p && *p != '"') p++;
+ *p = '\0';
+ }
+ el[num] = strdup(extid);
+ }
+
+ *numinst = ++num;
+ *intlist = il;
+ *extlist = el;
+}
+
+void
+freeinst(int *numinst, int *intlist, char **extlist)
+{
+ int i;
+
+ if (*numinst) {
+ free(intlist);
+ for (i = 0; i < *numinst; i++)
+ free(extlist[i]);
+ free(extlist);
+
+ *numinst = 0;
+ }
+}
+
+/*
+ * Link the new fetch groups back to their task structure
+ */
+void
+linkback(task_t *tp)
+{
+ fetchctl_t *fcp;
+
+ for (fcp = tp->t_fetch; fcp != NULL; fcp = fcp->f_next)
+ fcp->f_aux = (void *)tp;
+}
diff --git a/src/pmloglabel/GNUmakefile b/src/pmloglabel/GNUmakefile
new file mode 100644
index 0000000..c569d2f
--- /dev/null
+++ b/src/pmloglabel/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmloglabel.c
+CMDTARGET = pmloglabel$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmloglabel/pmloglabel.c b/src/pmloglabel/pmloglabel.c
new file mode 100644
index 0000000..ce7ae57
--- /dev/null
+++ b/src/pmloglabel/pmloglabel.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+static int gold; /* boolean flag - do we have a golden label yet? */
+static char *goldfile;
+static __pmLogLabel golden;
+static __pmLogCtl logctl;
+static int status;
+
+/*
+ * Basic log control label sanity testing, with prefix/suffix len
+ * checks too (these are stored as int's around the actual label).
+ */
+int
+verify_label(FILE *f, const char *file)
+{
+ int version, magic;
+ int n, len, xpectlen = sizeof(__pmLogLabel) + 2 * sizeof(len);
+
+ /* check the prefix integer */
+ fseek(f, (long)0, SEEK_SET);
+ n = (int)fread(&len, 1, sizeof(len), f);
+ len = ntohl(len);
+ if (n != sizeof(len)) {
+ if (feof(f)) {
+ fprintf(stderr, "Bad prefix sentinel read for %s: file too short\n",
+ file);
+ status = 2;
+ }
+ else if (ferror(f)) {
+ fprintf(stderr, "Prefix sentinel read error for %s: %s\n",
+ file, osstrerror());
+ status = 2;
+ }
+ else {
+ fprintf(stderr, "Prefix sentinel read error for %s: read only %d\n",
+ file, n);
+ status = 2;
+ }
+ }
+ if (len != xpectlen) {
+ fprintf(stderr, "Bad prefix sentinel value for %s: %d (%d expected)\n",
+ file, len, xpectlen);
+ status = 2;
+ }
+
+ /* check the suffix integer */
+ fseek(f, (long)(xpectlen - sizeof(len)), SEEK_SET);
+ n = (int)fread(&len, 1, sizeof(len), f);
+ len = ntohl(len);
+ if (n != sizeof(len)) {
+ if (feof(f)) {
+ fprintf(stderr, "Bad suffix sentinel read for %s: file too short\n",
+ file);
+ status = 2;
+ }
+ else if (ferror(f)) {
+ fprintf(stderr, "Suffix sentinel read error for %s: %s\n",
+ file, osstrerror());
+ status = 2;
+ }
+ else {
+ fprintf(stderr, "Suffix sentinel read error for %s: read only %d\n",
+ file, n);
+ status = 2;
+ }
+ }
+ if (len != xpectlen) {
+ fprintf(stderr, "Bad suffix sentinel value for %s: %d (%d expected)\n",
+ file, len, xpectlen);
+ status = 2;
+ }
+
+ /* check the label itself */
+ magic = logctl.l_label.ill_magic & 0xffffff00;
+ version = logctl.l_label.ill_magic & 0xff;
+ if (magic != PM_LOG_MAGIC) {
+ fprintf(stderr, "Bad magic (%x) in %s\n", magic, file);
+ status = 2;
+ }
+ if (version != PM_LOG_VERS02) {
+ fprintf(stderr, "Bad version (%x) in %s\n", version, file);
+ status = 2;
+ }
+
+ return version;
+}
+
+/*
+ * Check log control label with the known good "golden" label, if
+ * we have it yet. Passed in status is __pmLogChkLabel result, &
+ * we only use that to determine if this is good as a gold label.
+ */
+void
+compare_golden(FILE *f, const char *file, int sts, int warnings)
+{
+ __pmLogLabel *label = &logctl.l_label;
+
+ if (!gold) {
+ memcpy(&golden, label, sizeof(golden));
+ if ((gold = (sts >= 0)) != 0)
+ goldfile = strdup(file);
+ }
+ else if (warnings) {
+ int version = verify_label(f, file);
+
+ if (version != (golden.ill_magic & 0xff)) {
+ fprintf(stderr, "Mismatched version (%x/%x) between %s and %s\n",
+ version, golden.ill_magic & 0xff, file, goldfile);
+ status = 2;
+ }
+ if (label->ill_pid != golden.ill_pid) {
+ fprintf(stderr, "Mismatched PID (%d/%d) between %s and %s\n",
+ label->ill_pid, golden.ill_pid, file, goldfile);
+ status = 2;
+ }
+ if (strncmp(label->ill_hostname, golden.ill_hostname,
+ PM_LOG_MAXHOSTLEN) != 0) {
+ fprintf(stderr, "Mismatched hostname (%s/%s) between %s and %s\n",
+ label->ill_hostname, golden.ill_hostname, file, goldfile);
+ status = 2;
+ }
+ if (strncmp(label->ill_tz, golden.ill_tz, PM_TZ_MAXLEN) != 0) {
+ fprintf(stderr, "Mismatched timezone (%s/%s) between %s and %s\n",
+ label->ill_tz, golden.ill_tz, file, goldfile);
+ status = 2;
+ }
+ }
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ { "host", 1, 'h', "HOSTNAME", "set the hostname for all files in archive" },
+ { "label", 0, 'l', 0, "dump the archive label" },
+ { "", 0, 'L', 0, "more verbose form of label dump" },
+ { "pid", 1, 'p', "PID", "set the logger process ID field for all files in archive" },
+ { "", 0, 's', 0, "write the label sentinel values for all files in archive" },
+ { "verbose", 0, 'v', 0, "run in verbose mode, reporting on each stage of checking" },
+ { "version", 1, 'V', "NUM", "write magic and version numbers for all files in archive" },
+ { "timezone", 1, 'Z', "TZ", "set the timezone for all files in archive" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "D:h:lLp:svV:Z:?",
+ .long_options = longopts,
+ .short_usage = "[options] archive",
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ int lflag = 0;
+ int Lflag = 0;
+ int verbose = 0;
+ int version = 0;
+ int readonly = 1;
+ int warnings = 1;
+ int pid = 0;
+ char *archive;
+ char *tz = NULL;
+ char *host = NULL;
+ char buffer[MAXPATHLEN];
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'h': /* rewrite hostname */
+ host = opts.optarg;
+ readonly = 0;
+ break;
+
+ case 'l': /* dump label */
+ lflag = 1;
+ break;
+
+ case 'L': /* dump label (verbose) */
+ Lflag = 1;
+ break;
+
+ case 'p': /* rewrite pid */
+ pid = atoi(opts.optarg);
+ readonly = 0;
+ break;
+
+ case 's': /* rewrite sentinels */
+ readonly = 0;
+ break;
+
+ case 'v': /* verbose */
+ verbose = 1;
+ break;
+
+ case 'V': /* reset magic and version numbers */
+ version = atoi(opts.optarg);
+ if (version != PM_LOG_VERS02) {
+ fprintf(stderr, "%s: unknown version number (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ readonly = 0;
+ break;
+
+ case 'Z': /* $TZ timezone */
+ tz = opts.optarg;
+ readonly = 0;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.optind != argc - 1) {
+ pmprintf("%s: insufficient arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ archive = argv[opts.optind];
+ warnings = (readonly || verbose);
+
+ if (verbose)
+ printf("Scanning for components of archive \"%s\"\n", archive);
+ if ((sts = __pmLogLoadLabel(&logctl, archive)) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, archive, pmErrStr(sts));
+ exit(1);
+ }
+
+ logctl.l_curvol = -1;
+ logctl.l_physend = -1;
+
+ /*
+ * Read the label from each data volume, check, and report status
+ */
+ for (c = logctl.l_minvol; c <= logctl.l_maxvol; c++) {
+ if (verbose)
+ printf("Checking label on data volume %d\n", c);
+ if ((sts = __pmLogChangeVol(&logctl, c)) < 0 && warnings) {
+ fprintf(stderr, "Bad data volume %d label: %s\n", c, pmErrStr(sts));
+ status = 2;
+ }
+ snprintf(buffer, sizeof(buffer), "data volume %d", c);
+ compare_golden(logctl.l_mfp, buffer, sts, warnings);
+ }
+
+ if (logctl.l_tifp) {
+ if (verbose)
+ printf("Checking label on temporal index\n");
+ if ((sts = __pmLogChkLabel(&logctl, logctl.l_tifp, &logctl.l_label,
+ PM_LOG_VOL_TI)) < 0 && warnings) {
+ fprintf(stderr, "Bad temporal index label: %s\n", pmErrStr(sts));
+ status = 2;
+ }
+ compare_golden(logctl.l_tifp, "temporal index", sts, warnings);
+ }
+ else if (verbose) {
+ printf("No temporal index found\n");
+ }
+
+ if (verbose)
+ printf("Checking label on metadata volume\n");
+ if ((sts = __pmLogChkLabel(&logctl, logctl.l_mdfp, &logctl.l_label,
+ PM_LOG_VOL_META)) < 0 && warnings) {
+ fprintf(stderr, "Bad metadata volume label: %s\n", pmErrStr(sts));
+ status = 2;
+ }
+ compare_golden(logctl.l_mdfp, "metadata volume", sts, warnings);
+
+ /*
+ * Now, make any modifications requested
+ */
+ if (!readonly) {
+ if (version)
+ golden.ill_magic = PM_LOG_MAGIC | version;
+ if (pid)
+ golden.ill_pid = pid;
+ if (host) {
+ memset(golden.ill_hostname, 0, sizeof(golden.ill_hostname));
+ strncpy(golden.ill_hostname, host, PM_LOG_MAXHOSTLEN-1);
+ golden.ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0';
+ }
+ if (tz) {
+ memset(golden.ill_tz, 0, sizeof(golden.ill_tz));
+ strncpy(golden.ill_tz, tz, PM_TZ_MAXLEN-1);
+ golden.ill_tz[PM_TZ_MAXLEN-1] = '\0';
+ }
+
+ if (logctl.l_mfp)
+ fclose(logctl.l_mfp);
+ for (c = logctl.l_minvol; c <= logctl.l_maxvol; c++) {
+ if (verbose)
+ printf("Writing label on data volume %d\n", c);
+ golden.ill_vol = c;
+ snprintf(buffer, sizeof(buffer), "%s.%d", logctl.l_name, c);
+ if ((logctl.l_mfp = fopen(buffer, "r+")) == NULL) {
+ fprintf(stderr, "Failed data volume %d open: %s\n",
+ c, osstrerror());
+ status = 3;
+ }
+ else if ((sts = __pmLogWriteLabel(logctl.l_mfp, &golden)) < 0) {
+ fprintf(stderr, "Failed data volume %d label write: %s\n",
+ c, pmErrStr(sts));
+ status = 3;
+ }
+ if (logctl.l_mfp)
+ fclose(logctl.l_mfp);
+ }
+ /* Need to reset the data volume, for subsequent label read */
+ logctl.l_mfp = NULL;
+ logctl.l_curvol = -1;
+ __pmLogChangeVol(&logctl, logctl.l_minvol);
+
+ if (logctl.l_tifp) {
+ fclose(logctl.l_tifp);
+ if (verbose)
+ printf("Writing label on temporal index\n");
+ golden.ill_vol = PM_LOG_VOL_TI;
+ snprintf(buffer, sizeof(buffer), "%s.index", logctl.l_name);
+ if ((logctl.l_tifp = fopen(buffer, "r+")) == NULL) {
+ fprintf(stderr, "Failed temporal index open: %s\n",
+ osstrerror());
+ status = 3;
+ }
+ else if ((sts = __pmLogWriteLabel(logctl.l_tifp, &golden)) < 0) {
+ fprintf(stderr, "Failed temporal index label write: %s\n",
+ pmErrStr(sts));
+ status = 3;
+ }
+ }
+
+ fclose(logctl.l_mdfp);
+ if (verbose)
+ printf("Writing label on metadata volume\n");
+ golden.ill_vol = PM_LOG_VOL_META;
+ snprintf(buffer, sizeof(buffer), "%s.meta", logctl.l_name);
+ if ((logctl.l_mdfp = fopen(buffer, "r+")) == NULL) {
+ fprintf(stderr, "Failed metadata volume open: %s\n",
+ osstrerror());
+ status = 3;
+ }
+ else if ((sts = __pmLogWriteLabel(logctl.l_mdfp, &golden)) < 0) {
+ fprintf(stderr, "Failed metadata volume label write: %s\n",
+ pmErrStr(sts));
+ status = 3;
+ }
+ }
+
+ /*
+ * Finally, dump out the label if requested
+ */
+ if (lflag || Lflag) {
+ char *ddmm;
+ char *yr;
+ struct timeval tv;
+ time_t t = golden.ill_start.tv_sec;
+
+ printf("Log Label (Log Format Version %d)\n", golden.ill_magic & 0xff);
+ printf("Performance metrics from host %s\n", golden.ill_hostname);
+
+ ddmm = pmCtime(&t, buffer);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" commencing %s ", ddmm);
+ tv.tv_sec = golden.ill_start.tv_sec;
+ tv.tv_usec = golden.ill_start.tv_usec;
+ __pmPrintStamp(stdout, &tv);
+ printf(" %4.4s\n", yr);
+ if (__pmLogChangeVol(&logctl, 0) < 0)
+ printf(" ending UNKNOWN\n");
+ else if (__pmGetArchiveEnd(&logctl, &tv) < 0)
+ printf(" ending UNKNOWN\n");
+ else {
+ ddmm = pmCtime(&tv.tv_sec, buffer);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" ending %s ", ddmm);
+ __pmPrintStamp(stdout, &tv);
+ printf(" %4.4s\n", yr);
+ }
+ if (Lflag) {
+ printf("Archive timezone: %s\n", golden.ill_tz);
+ printf("PID for pmlogger: %d\n", golden.ill_pid);
+ }
+ }
+
+ exit(status);
+}
diff --git a/src/pmlogreduce/GNUmakefile b/src/pmlogreduce/GNUmakefile
new file mode 100644
index 0000000..4fd88d6
--- /dev/null
+++ b/src/pmlogreduce/GNUmakefile
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmlogreduce.c logio.c dometric.c rewrite.c indom.c scan.c
+HFILES = pmlogreduce.h
+
+CMDTARGET = pmlogreduce$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+pmlogreduce : $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+pmlogreduce.o: pmlogreduce.h
+logio.o: pmlogreduce.h
+dometric.o: pmlogreduce.h
+rewrite.o: pmlogreduce.h
+indom.o: pmlogreduce.h
+wrap.o: pmlogreduce.h
+scan.o: pmlogreduce.h
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmlogreduce/dometric.c b/src/pmlogreduce/dometric.c
new file mode 100644
index 0000000..6ba0fdc
--- /dev/null
+++ b/src/pmlogreduce/dometric.c
@@ -0,0 +1,155 @@
+#include "pmlogreduce.h"
+
+void
+dometric(const char *name)
+{
+ int sts;
+ metric_t *mp;
+
+ if ((namelist = (char **)realloc(namelist, (numpmid+1)*sizeof(namelist[0]))) == NULL) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot realloc space for %d names\n",
+ pmProgname, numpmid+1);
+ exit(1);
+ }
+ namelist[numpmid] = strdup(name);
+ if ((pmidlist = (pmID *)realloc(pmidlist, (numpmid+1)*sizeof(pmidlist[0]))) == NULL) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot realloc space for %d pmIDs\n",
+ pmProgname, numpmid+1);
+ exit(1);
+ }
+ if ((sts = pmLookupName(1, (char **)&name, &pmidlist[numpmid])) < 0) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot lookup pmID for metric \"%s\": %s\n",
+ pmProgname, name, pmErrStr(sts));
+ exit(1);
+ }
+ if ((metriclist = (metric_t *)realloc(metriclist, (numpmid+1)*sizeof(metriclist[0]))) == NULL) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot realloc space for %d metric_t's\n",
+ pmProgname, numpmid+1);
+ exit(1);
+ }
+ mp = &metriclist[numpmid];
+ mp->first = NULL;
+ if ((sts = pmLookupDesc(pmidlist[numpmid], &mp->idesc)) < 0) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot lookup pmDesc for metric \"%s\": %s\n",
+ pmProgname, name, pmErrStr(sts));
+ exit(1);
+ }
+ mp->odesc = mp->idesc; /* struct assignment */
+ mp->mode = MODE_NORMAL;
+ mp->idp = NULL;
+
+ /*
+ * some metrics cannot sensibly be processed ... skip these ones
+ */
+ if (mp->idesc.type == PM_TYPE_AGGREGATE ||
+ mp->idesc.type == PM_TYPE_AGGREGATE_STATIC ||
+ mp->idesc.type == PM_TYPE_EVENT) {
+ fprintf(stderr,
+ "%s: %s: Warning: skipping %s metric\n",
+ pmProgname, name, pmTypeStr(mp->idesc.type));
+ mp->mode = MODE_SKIP;
+ goto done;
+ }
+
+ /*
+ * input -> output descriptor mapping ... has to be the same
+ * logic as we apply to the pmResults later on.
+ */
+ switch (mp->idesc.sem) {
+ case PM_SEM_COUNTER:
+ switch (mp->idesc.type) {
+ case PM_TYPE_32:
+ mp->odesc.type = PM_TYPE_64;
+ mp->mode = MODE_REWRITE;
+ break;
+ case PM_TYPE_U32:
+ mp->odesc.type = PM_TYPE_U64;
+ mp->mode = MODE_REWRITE;
+ break;
+ }
+#if 0
+ mp->odesc.sem = PM_SEM_INSTANT;
+ if (mp->idesc.units.dimTime == 0) {
+ /* rate convert */
+ mp->odesc.units.dimTime = -1;
+ mp->odesc.units.scaleTime = PM_TIME_SEC;
+ }
+ else if (mp->idesc.units.dimTime == 1) {
+ /* becomes (time) utilization */
+ mp->odesc.units.dimTime = 0;
+ mp->odesc.units.scaleTime = 0;
+ }
+ else {
+ fprintf(stderr, "Cannot rate convert \"%s\" yet,", namelist[numpmid]);
+ __pmPrintDesc(stderr, &mp->idesc);
+ exit(1);
+ }
+ break;
+#endif
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "metric: \"%s\" (%s)\n",
+ namelist[numpmid], pmIDStr(pmidlist[numpmid]));
+ fprintf(stderr, "input descriptor:\n");
+ __pmPrintDesc(stderr, &mp->idesc);
+ fprintf(stderr, "output descriptor (added to archive):\n");
+ __pmPrintDesc(stderr, &mp->odesc);
+ }
+#endif
+
+ if ((sts = __pmLogPutDesc(&logctl, &mp->odesc, 1, &namelist[numpmid])) < 0) {
+ fprintf(stderr,
+ "%s: Error: failed to add pmDesc for %s (%s): %s\n",
+ pmProgname, namelist[numpmid], pmIDStr(pmidlist[numpmid]), pmErrStr(sts));
+ exit(1);
+ }
+
+ /*
+ * instance domain initialization
+ */
+ if (mp->idesc.indom != PM_INDOM_NULL) {
+ /*
+ * has an instance domain, check to see if it has already been seen
+ */
+ int j;
+
+ for (j = 0; j <= numpmid; j++) {
+ if (metriclist[j].idp != NULL &&
+ metriclist[j].idp->indom == mp->idesc.indom) {
+ mp->idp = metriclist[j].idp;
+ break;
+ }
+ }
+ if (j > numpmid) {
+ /* first sighting, allocate a new one */
+ if ((mp->idp = (indom_t *)malloc(sizeof(indom_t))) == NULL) {
+ fprintf(stderr,
+ "%s: dometric: Error: cannot malloc indom_t for %s\n",
+ pmProgname, pmInDomStr(mp->idesc.indom));
+ exit(1);
+ }
+ mp->idp->indom = mp->idesc.indom;
+ mp->idp->numinst = 0;
+ mp->idp->inst = NULL;
+ mp->idp->name = NULL;
+ }
+ }
+
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ if (mp->idp != NULL)
+ fprintf(stderr, " indom %s -> (" PRINTF_P_PFX "%p)\n", pmInDomStr(mp->idp->indom), mp->idp);
+ }
+#endif
+
+done:
+
+ numpmid++;
+}
+
diff --git a/src/pmlogreduce/indom.c b/src/pmlogreduce/indom.c
new file mode 100644
index 0000000..57efc4e
--- /dev/null
+++ b/src/pmlogreduce/indom.c
@@ -0,0 +1,103 @@
+#include "pmlogreduce.h"
+
+void
+doindom(pmResult *rp)
+{
+ pmValueSet *vsp;
+ int i;
+ int j;
+ int needti = 0;
+ int need;
+ metric_t *mp = NULL;
+ int *instlist;
+ char **namelist;
+ int sts;
+
+ for (i = 0; i < rp->numpmid; i++) {
+ vsp = rp->vset[i];
+ if (vsp->numval <= 0)
+ continue;
+
+ /*
+ * pmidlist[] and rp->vset[]->pmid may not be in 1:1
+ * correspondence because we come here after rewrite() has
+ * been called ... search for matching pmid
+ */
+ for (j = 0; j < numpmid; j++) {
+ if (pmidlist[j] == vsp->pmid) {
+ mp = &metriclist[j];
+ break;
+ }
+ }
+ if (mp == NULL) {
+ fprintf(stderr,
+ "%s: doindom: Arrgh, unexpected PMID %s @ vset[%d]\n",
+ pmProgname, pmIDStr(vsp->pmid), i);
+ __pmDumpResult(stderr, rp);
+ exit(1);
+ }
+ if (mp->idp == NULL)
+ continue;
+
+ if ((sts = pmGetInDom(mp->idp->indom, &instlist, &namelist)) < 0) {
+ fprintf(stderr,
+ "%s: doindom: pmGetInDom (%s) failed: %s\n",
+ pmProgname, pmInDomStr(mp->idp->indom), pmErrStr(sts));
+ exit(1);
+ }
+
+ need = 1;
+ /*
+ * Need to output the indom if the number of instances changes
+ * or the set of instance ids are not the same from the last
+ * time.
+ */
+ if (sts == mp->idp->numinst) {
+ for (j = 0; j < mp->idp->numinst; j++) {
+ if (mp->idp->inst[j] != instlist[j])
+ break;
+ }
+ if (j == mp->idp->numinst) {
+ /*
+ * Do we need to check the namelist elts as well, e.g.
+ * using strcmp()?
+ * Not at this stage ... if the instance ids are all the
+ * same, then only a very odd (and non-compliant) PMDA
+ * would change the mapping from id to name on the fly
+ */
+ need = 0;
+ }
+ }
+
+ if (need) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Add metadata: indom %s for metric %s\n", pmInDomStr(mp->idp->indom), pmIDStr(vsp->pmid));
+ }
+#endif
+ if (mp->idp->name != NULL) free(mp->idp->name);
+ if (mp->idp->inst != NULL) free(mp->idp->inst);
+ mp->idp->name = namelist;
+ mp->idp->inst = instlist;
+ mp->idp->numinst = sts;
+ if ((sts = __pmLogPutInDom(&logctl, mp->idp->indom, &current, mp->idp->numinst, mp->idp->inst, mp->idp->name)) < 0) {
+ fprintf(stderr,
+ "%s: Error: failed to add pmInDom: indom %s (for pmid %s): %s\n",
+ pmProgname, pmInDomStr(mp->idp->indom), pmIDStr(vsp->pmid), pmErrStr(sts));
+ exit(1);
+ }
+ needti = 1; /* requires a temporal index update */
+ }
+ else {
+ free(instlist);
+ free(namelist);
+ }
+
+ }
+
+ if (needti) {
+ fflush(logctl.l_mdfp);
+ __pmLogPutIndex(&logctl, &current);
+ }
+
+}
diff --git a/src/pmlogreduce/logio.c b/src/pmlogreduce/logio.c
new file mode 100644
index 0000000..09a135b
--- /dev/null
+++ b/src/pmlogreduce/logio.c
@@ -0,0 +1,260 @@
+/*
+ * utils for pmlogextract
+ *
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmlogreduce.h"
+
+/*
+ * raw read of next log record - largely stolen from __pmLogRead in libpcp
+ */
+int
+_pmLogGet(__pmLogCtl *lcp, int vol, __pmPDU **pb)
+{
+ int head;
+ int tail;
+ int sts;
+ long offset;
+ char *p;
+ __pmPDU *lpb;
+ FILE *f;
+
+ if (vol == PM_LOG_VOL_META)
+ f = lcp->l_mdfp;
+ else
+ f = lcp->l_mfp;
+
+ offset = ftell(f);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "_pmLogGet: fd=%d vol=%d posn=%ld ",
+ fileno(f), vol, offset);
+ }
+#endif
+
+again:
+ sts = (int)fread(&head, 1, sizeof(head), f);
+ if (sts != sizeof(head)) {
+ if (sts == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "AFTER end\n");
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (vol != PM_LOG_VOL_META) {
+ if (lcp->l_curvol < lcp->l_maxvol) {
+ if (__pmLogChangeVol(lcp, lcp->l_curvol+1) == 0) {
+ f = lcp->l_mfp;
+ goto again;
+ }
+ }
+ }
+ return PM_ERR_EOL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: hdr fread=%d %s\n", sts, osstrerror());
+#endif
+ if (sts > 0)
+ return PM_ERR_LOGREC;
+ else
+ return -oserror();
+ }
+
+ if ((lpb = (__pmPDU *)malloc(ntohl(head))) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: _pmLogGet:(%d) %s\n",
+ (int)ntohl(head), osstrerror());
+#endif
+ fseek(f, offset, SEEK_SET);
+ return -oserror();
+ }
+
+ lpb[0] = head;
+ if ((sts = (int)fread(&lpb[1], 1, ntohl(head) - sizeof(head), f)) != ntohl(head) - sizeof(head)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: data fread=%d %s\n", sts, osstrerror());
+#endif
+ if (sts == 0) {
+ fseek(f, offset, SEEK_SET);
+ free(lpb);
+ return PM_ERR_EOL;
+ }
+ else if (sts > 0) {
+ free(lpb);
+ return PM_ERR_LOGREC;
+ }
+ else {
+ int e = -oserror();
+ free(lpb);
+ return e;
+ }
+ }
+
+
+ p = (char *)lpb;
+ memcpy(&tail, &p[ntohl(head) - sizeof(head)], sizeof(head));
+ if (head != tail) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: head-tail mismatch (%d-%d)\n",
+ (int)ntohl(head), (int)ntohl(tail));
+#endif
+ return PM_ERR_LOGREC;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, "@");
+ if (sts >= 0) {
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ else
+ fprintf(stderr, "unknown time");
+ }
+ fprintf(stderr, " len=%d (incl head+tail)\n", (int)ntohl(head));
+ }
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int i, j;
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ fprintf(stderr, "_pmLogGet");
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, " timestamp=");
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ fprintf(stderr, " " PRINTF_P_PFX "%p ... " PRINTF_P_PFX "%p", lpb, &lpb[ntohl(head)/sizeof(__pmPDU) - 1]);
+ fputc('\n', stderr);
+ fprintf(stderr, "%03d: ", 0);
+ for (j = 0, i = 0; j < ntohl(head)/sizeof(__pmPDU); j++) {
+ if (i == 8) {
+ fprintf(stderr, "\n%03d: ", j);
+ i = 0;
+ }
+ fprintf(stderr, "0x%x ", lpb[j]);
+ i++;
+ }
+ fputc('\n', stderr);
+ }
+#endif
+
+ *pb = lpb;
+ return 0;
+}
+
+int
+_pmLogPut(FILE *f, __pmPDU *pb)
+{
+ int rlen = ntohl(pb[0]);
+ int sts;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "_pmLogPut: fd=%d rlen=%d\n",
+ fileno(f), rlen);
+ }
+#endif
+
+ if ((sts = (int)fwrite(pb, 1, rlen, f)) != rlen) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "_pmLogPut: fwrite=%d %s\n", sts, osstrerror());
+#endif
+ return -oserror();
+ }
+ return 0;
+}
+
+/*
+ * construct new external label, and check label records from
+ * input archives
+ */
+void
+newlabel(void)
+{
+ __pmLogLabel *lp = &logctl.l_label;
+
+ /* check version number */
+ if ((ilabel.ll_magic & 0xff) != PM_LOG_VERS02) {
+ fprintf(stderr,"%s: Error: version number %d (not %d as expected) in archive (%s)\n",
+ pmProgname, ilabel.ll_magic & 0xff, PM_LOG_VERS02, iname);
+ exit(1);
+ }
+
+ /* copy magic number, host and timezone, use our pid */
+ lp->ill_magic = ilabel.ll_magic;
+ lp->ill_pid = (int)getpid();
+ strncpy(lp->ill_hostname, ilabel.ll_hostname, PM_LOG_MAXHOSTLEN);
+ lp->ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0';
+ strncpy(lp->ill_tz, ilabel.ll_tz, PM_TZ_MAXLEN);
+ lp->ill_tz[PM_TZ_MAXLEN-1] = '\0';
+}
+
+
+/*
+ * write label records into all files of the output archive
+ */
+void
+writelabel(void)
+{
+ logctl.l_label.ill_vol = 0;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ logctl.l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
+ logctl.l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
+}
+
+/*
+ * switch output volumes
+ */
+void
+newvolume(char *base, __pmTimeval *tvp)
+{
+ FILE *newfp;
+ int nextvol = logctl.l_curvol + 1;
+ struct timeval stamp;
+
+ if ((newfp = __pmLogNewFile(base, nextvol)) != NULL) {
+ fclose(logctl.l_mfp);
+ logctl.l_mfp = newfp;
+ logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
+ __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
+ fflush(logctl.l_mfp);
+ stamp.tv_sec = tvp->tv_sec;
+ stamp.tv_usec = tvp->tv_usec;
+ fprintf(stderr, "%s: New log volume %d, at ",
+ pmProgname, nextvol);
+ __pmPrintStamp(stderr, &stamp);
+ fputc('\n', stderr);
+ return;
+ }
+ else {
+ fprintf(stderr, "%s: Error: volume %d: %s\n",
+ pmProgname, nextvol, pmErrStr(-oserror()));
+ exit(1);
+ }
+}
diff --git a/src/pmlogreduce/pmlogreduce.c b/src/pmlogreduce/pmlogreduce.c
new file mode 100644
index 0000000..036953a
--- /dev/null
+++ b/src/pmlogreduce/pmlogreduce.c
@@ -0,0 +1,490 @@
+/*
+ * pmlogreduce - statistical reduction of a PCP archive log
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * TODO (global list)
+ * - check for counter overflow in doscan()
+ * - optimization (maybe) for discrete and instantaneous metrics
+ * to suppress repeated values, provided you get the boundary
+ * conditions correct ... beware of mark records and past last
+ * value ... it may be difficult to distinguish ... in practice
+ * this may not be worth it because discrete is rare and
+ * instantaneous is very likely to change over long enough time
+ * ... counter example is hinv.* that interpolate returns on every
+ * fetch, even only once in the input archive
+ * - performance profiling
+ * - testing with dynamic instance domains
+ * - check comments ahead of call to doscan() and the description
+ * in the head of scan.c
+ *
+ * Debug flags
+ * APPL0
+ * initialization
+ * metadata
+ * APPL1
+ * inst-value scanning in doscan()
+ * APPL2
+ * scan summary
+ * details for records read and records written
+ */
+
+#include <sys/stat.h>
+#include "pmlogreduce.h"
+
+/*
+ * globals defined in pmlogreduce.h
+ */
+__pmTimeval current; /* most recent timestamp overall */
+char *iname; /* name of input archive */
+pmLogLabel ilabel; /* input archive label */
+int numpmid; /* all metrics from the input archive */
+pmID *pmidlist;
+char **namelist;
+metric_t *metriclist;
+__pmLogCtl logctl; /* output archive control */
+/* command line args */
+double targ = 600.0; /* -t arg - interval b/n output samples */
+int sarg = -1; /* -s arg - finish after X samples */
+char *Sarg; /* -S arg - window start */
+char *Targ; /* -T arg - window end */
+char *Aarg; /* -A arg - output time alignment */
+int varg = -1; /* -v arg - switch log vol every X */
+int zarg; /* -z arg - use archive timezone */
+char *tz; /* -Z arg - use timezone from user */
+
+int written; /* num log writes so far */
+int exit_status;
+
+/* archive control stuff */
+int ictx_a;
+char *oname; /* name of output archive */
+pmLogLabel olabel; /* output archive label */
+struct timeval winstart_tval; /* window start tval*/
+
+/* time window stuff */
+static struct timeval logstart_tval; /* reduced log start */
+static struct timeval logend_tval; /* reduced log end */
+static struct timeval winend_tval; /* window end tval */
+
+/* cmd line args that could exist, but don't (needed for pmParseTimeWin) */
+static char *Oarg; /* -O arg - non-existent */
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_ALIGN,
+ PMOPT_DEBUG,
+ PMOPT_START,
+ PMOPT_SAMPLES,
+ PMOPT_FINISH,
+ { "interval", 1, 't', "DELTA", "sample output interval [default 10min]" },
+ { "", 1, 'v', "NUM", "switch log volumes after this many samples" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "A:D:S:s:T:t:v:Z:z?",
+ .long_options = longopts,
+ .short_usage = "[options] input-archive output-archive",
+};
+
+static double
+tv2double(struct timeval *tv)
+{
+ return tv->tv_sec + (double)tv->tv_usec / 1000000.0;
+}
+
+static int
+parseargs(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ char *endnum;
+ char *msg;
+ struct timeval interval;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'A': /* output time alignment */
+ Aarg = opts.optarg;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 's': /* number of samples to write out */
+ sarg = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || sarg < 0) {
+ pmprintf("%s: -s requires numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'S': /* start time for reduction */
+ Sarg = opts.optarg;
+ break;
+
+ case 'T': /* end time for reduction */
+ Targ = opts.optarg;
+ break;
+
+ case 't': /* output sample interval */
+ if (pmParseInterval(opts.optarg, &interval, &msg) < 0) {
+ pmprintf("%s", msg);
+ free(msg);
+ opts.errors++;
+ }
+ else
+ targ = tv2double(&interval);
+ break;
+
+ case 'v': /* number of samples per volume */
+ varg = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || varg < 0) {
+ pmprintf("%s: -v requires numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'Z': /* use timezone from command line */
+ if (zarg) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ opts.errors++;
+
+ }
+ tz = opts.optarg;
+ break;
+
+ case 'z': /* use timezone from archive */
+ if (tz != NULL) {
+ pmprintf("%s: at most one of -Z and/or -z allowed\n",
+ pmProgname);
+ opts.errors++;
+ }
+ zarg++;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors == 0 && opts.optind > argc-2) {
+ pmprintf("%s: Error: insufficient arguments\n", pmProgname);
+ opts.errors++;
+ }
+
+ return -opts.errors;
+}
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ char *msg;
+ pmResult *irp; /* input pmResult */
+ pmResult *orp; /* output pmResult */
+ __pmPDU *pb; /* pdu buffer */
+ struct timeval unused;
+ unsigned long peek_offset;
+
+ /* process cmd line args */
+ if (parseargs(argc, argv) < 0) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ /* input archive name is argv[opts.optind] */
+ /* output archive name is argv[argc-1]) */
+
+ /* output archive */
+ oname = argv[argc-1];
+
+ /* input archive */
+ iname = argv[opts.optind];
+
+ /*
+ * This is the interp mode context
+ */
+ if ((ictx_a = pmNewContext(PM_CONTEXT_ARCHIVE, iname)) < 0) {
+ fprintf(stderr, "%s: Error: cannot open archive \"%s\" (ctx_a): %s\n",
+ pmProgname, iname, pmErrStr(ictx_a));
+ exit(1);
+ }
+
+ if ((sts = pmGetArchiveLabel(&ilabel)) < 0) {
+ fprintf(stderr, "%s: Error: cannot get archive label record (%s): %s\n", pmProgname, iname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* start time */
+ logstart_tval.tv_sec = ilabel.ll_start.tv_sec;
+ logstart_tval.tv_usec = ilabel.ll_start.tv_usec;
+
+ /* end time */
+ if ((sts = pmGetArchiveEnd(&logend_tval)) < 0) {
+ fprintf(stderr, "%s: Error: cannot get end of archive (%s): %s\n",
+ pmProgname, iname, pmErrStr(sts));
+ exit(1);
+ }
+
+ if (zarg) {
+ /* use TZ from metrics source (input-archive) */
+ if ((sts = pmNewZone(ilabel.ll_tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set context timezone: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ printf("Note: timezone set to local timezone of host \"%s\" from archive\n\n", ilabel.ll_hostname);
+ }
+ else if (tz != NULL) {
+ /* use TZ as specified by user */
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
+ pmProgname, tz, pmErrStr(sts));
+ exit(1);
+ }
+ printf("Note: timezone set to \"TZ=%s\"\n\n", tz);
+ }
+ else {
+ char *tz;
+ tz = __pmTimezone();
+ /* use TZ from local host */
+ if ((sts = pmNewZone(tz)) < 0) {
+ fprintf(stderr, "%s: Cannot set local host's timezone: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ /* set winstart and winend timevals */
+ sts = pmParseTimeWindow(Sarg, Targ, Aarg, Oarg,
+ &logstart_tval, &logend_tval,
+ &winstart_tval, &winend_tval, &unused, &msg);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Invalid time window specified: %s\n",
+ pmProgname, msg);
+ exit(1);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char buf[26];
+ pmCtime((const time_t *)&winstart_tval.tv_sec, buf);
+ fprintf(stderr, "Start time: %s", buf);
+ pmCtime((const time_t *)&winend_tval.tv_sec, buf);
+ fprintf(stderr, "End time: %s", buf);
+ }
+#endif
+
+ if ((sts = pmSetMode(PM_MODE_INTERP | PM_XTB_SET(PM_TIME_SEC),
+ &winstart_tval, (int)targ)) < 0) {
+ fprintf(stderr, "%s: pmSetMode(PM_MODE_INTERP ...) failed: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* create output log - must be done before writing label */
+ if ((sts = __pmLogCreate("", oname, PM_LOG_VERS02, &logctl)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogCreate: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* This must be done after log is created:
+ * - checks that archive version, host, and timezone are ok
+ * - set archive version, host, and timezone of output archive
+ * - set start time
+ * - write labels
+ */
+ newlabel();
+ current.tv_sec = logctl.l_label.ill_start.tv_sec = winstart_tval.tv_sec;
+ current.tv_usec = logctl.l_label.ill_start.tv_usec = winstart_tval.tv_usec;
+ /* write label record */
+ writelabel();
+ /*
+ * Supress any automatic label creation in libpcp at the first
+ * pmResult write.
+ */
+ logctl.l_state = PM_LOG_STATE_INIT;
+
+ /*
+ * Traverse the PMNS to get all the metrics and their metadata
+ */
+ if ((sts = pmTraversePMNS ("", dometric)) < 0) {
+ fprintf(stderr, "%s: Error traversing namespace ... %s\n",
+ pmProgname, pmErrStr(sts));
+ goto cleanup;
+ }
+
+ /*
+ * All the initial metadata has been generated, add timestamp
+ */
+ fflush(logctl.l_mdfp);
+ __pmLogPutIndex(&logctl, &current);
+
+ written = 0;
+
+ /*
+ * main loop
+ */
+ while (sarg == -1 || written < sarg) {
+ /*
+ * do stuff
+ */
+ if ((sts = pmUseContext(ictx_a)) < 0) {
+ fprintf(stderr, "%s: Error: cannot use context (%s): %s\n",
+ pmProgname, iname, pmErrStr(sts));
+ goto cleanup;
+ }
+ if ((sts = pmFetch(numpmid, pmidlist, &irp)) < 0) {
+ if (sts == PM_ERR_EOL)
+ break;
+ fprintf(stderr,
+ "%s: Error: pmFetch failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ if (irp->timestamp.tv_sec > winend_tval.tv_sec ||
+ (irp->timestamp.tv_sec == winend_tval.tv_sec &&
+ irp->timestamp.tv_usec > winend_tval.tv_usec)) {
+ /* past end time as per -T */
+ break;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "input record ...\n");
+ __pmDumpResult(stderr, irp);
+ }
+#endif
+
+ /*
+ * traverse the interval, looking at every archive record ...
+ * we are particularly interested in:
+ * - metric-values that are interpolated but not present in
+ * this interval
+ * - counter wraps
+ * - mark records
+ */
+ doscan(&irp->timestamp);
+
+ if ((sts = pmUseContext(ictx_a)) < 0) {
+ fprintf(stderr, "%s: Error: cannot use context (%s): %s\n",
+ pmProgname, iname, pmErrStr(sts));
+ goto cleanup;
+ }
+
+ orp = rewrite(irp);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (orp == NULL)
+ fprintf(stderr, "output record ... none!\n");
+ else {
+ fprintf(stderr, "output record ...\n");
+ __pmDumpResult(stderr, orp);
+ }
+ }
+#endif
+ if (orp == NULL)
+ goto next;
+
+ /*
+ * convert log record to a PDU, and enforce V2 encoding semantics,
+ * then write it out
+ */
+ sts = __pmEncodeResult(PDU_OVERRIDE2, orp, &pb);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Error: __pmEncodeResult: %s\n",
+ pmProgname, pmErrStr(sts));
+ goto cleanup;
+ }
+
+ /* switch volumes if required */
+ if (varg > 0) {
+ if (written > 0 && (written % varg) == 0) {
+ __pmTimeval next_stamp;
+ next_stamp.tv_sec = irp->timestamp.tv_sec;
+ next_stamp.tv_usec = irp->timestamp.tv_usec;
+ newvolume(oname, &next_stamp);
+ }
+ }
+ /*
+ * Even without a -v option, we may need to switch volumes
+ * if the data file exceeds 2^31-1 bytes
+ */
+ peek_offset = ftell(logctl.l_mfp);
+ peek_offset += ((__pmPDUHdr *)pb)->len - sizeof(__pmPDUHdr) + 2*sizeof(int);
+ if (peek_offset > 0x7fffffff) {
+ __pmTimeval next_stamp;
+ next_stamp.tv_sec = irp->timestamp.tv_sec;
+ next_stamp.tv_usec = irp->timestamp.tv_usec;
+ newvolume(oname, &next_stamp);
+ }
+
+ current.tv_sec = orp->timestamp.tv_sec;
+ current.tv_usec = orp->timestamp.tv_usec;
+
+ doindom(orp);
+
+ /* write out log record */
+ sts = __pmLogPutResult2(&logctl, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: log data: %s\n",
+ pmProgname, pmErrStr(sts));
+ goto cleanup;
+ }
+ written++;
+
+ rewrite_free();
+
+next:
+ pmFreeResult(irp);
+ }
+
+ /* write the last time stamp */
+ fflush(logctl.l_mfp);
+ fflush(logctl.l_mdfp);
+ __pmLogPutIndex(&logctl, &current);
+
+ exit(exit_status);
+
+cleanup:
+ {
+ char fname[MAXNAMELEN];
+ fprintf(stderr, "Archive \"%s\" not created.\n", oname);
+ snprintf(fname, sizeof(fname), "%s.0", oname);
+ unlink(fname);
+ snprintf(fname, sizeof(fname), "%s.meta", oname);
+ unlink(fname);
+ snprintf(fname, sizeof(fname), "%s.index", oname);
+ unlink(fname);
+ exit(1);
+ }
+}
diff --git a/src/pmlogreduce/pmlogreduce.h b/src/pmlogreduce/pmlogreduce.h
new file mode 100644
index 0000000..c654518
--- /dev/null
+++ b/src/pmlogreduce/pmlogreduce.h
@@ -0,0 +1,85 @@
+#include "pmapi.h"
+#include "impl.h"
+
+#define NUM_SEC_PER_DAY 86400
+
+/*
+ * Value control for a metric-instance and the last observed input value.
+ * Used for rate conversion and supression of repeating values for
+ * instantaneous and discrete metrics
+ */
+typedef struct value {
+ struct value *next; /* next for this metric */
+ int inst; /* instance id */
+ pmAtomValue value; /* last output value */
+ struct timeval timestamp; /* time of last output value */
+ /*
+ * last interval value interpretation ... set in doscan() and used in
+ * rewrite()
+ */
+ int control;
+ int nobs; /* number of observations */
+ int nwrap; /* number of counter wraps */
+ pmAtomValue pvalue; /* used for counter wrap detection */
+} value_t;
+
+/*
+ * control values ... bit fields
+ */
+#define V_INIT 1
+#define V_SEEN 2
+
+/*
+ * instance domain control
+ */
+typedef struct {
+ int indom;
+ int numinst;
+ int *inst;
+ char **name;
+} indom_t;
+
+/*
+ * Metric control record in metric hash list
+ */
+typedef struct {
+ pmDesc idesc; /* input archive descriptor */
+ pmDesc odesc; /* output archive descriptor */
+ value_t *first; /* list of values, one per instance */
+ indom_t *idp; /* instance domain control, if any */
+ int mode; /* have to skip or rewrite the value format */
+} metric_t;
+#define MODE_NORMAL 0
+#define MODE_REWRITE 1
+#define MODE_SKIP 2
+
+extern __pmTimeval current; /* most recent timestamp overall */
+extern char *iname; /* name of input archive */
+extern pmLogLabel ilabel; /* input archive label */
+extern int numpmid; /* all metrics from the input archive */
+extern pmID *pmidlist; /* ditto */
+extern char **namelist; /* ditto */
+extern metric_t *metriclist; /* ditto */
+extern __pmLogCtl logctl; /* output archive control */
+extern double targ; /* -t arg - interval b/n output samples */
+extern int sarg; /* -s arg - finish after X samples */
+extern char *Sarg; /* -S arg - window start */
+extern char *Targ; /* -T arg - window end */
+extern char *Aarg; /* -A arg - output time alignment */
+extern int varg; /* -v arg - switch log vol every X */
+extern int zarg; /* -z arg - use archive timezone */
+extern char *tz; /* -Z arg - use timezone from user */
+
+
+extern int _pmLogGet(__pmLogCtl *, int, __pmPDU **);
+extern int _pmLogPut(FILE *, __pmPDU *);
+extern void newlabel(void);
+extern void writelabel(void);
+extern void newvolume(char *, __pmTimeval *);
+
+extern pmResult *rewrite(pmResult *);
+extern void rewrite_free(void);
+
+extern void dometric(const char *);
+extern void doindom(pmResult *);
+extern void doscan(struct timeval *);
diff --git a/src/pmlogreduce/rewrite.c b/src/pmlogreduce/rewrite.c
new file mode 100644
index 0000000..eae00e4
--- /dev/null
+++ b/src/pmlogreduce/rewrite.c
@@ -0,0 +1,178 @@
+#include "pmlogreduce.h"
+#include <inttypes.h>
+
+static pmResult *orp;
+
+/*
+ * Must either re-write the pmResult, or return NULL for non-fatal
+ * errors, else report and exit for catastrophic errors ...
+ */
+pmResult *
+rewrite(pmResult *rp)
+{
+ int i;
+ int sts;
+
+ if ((orp = (pmResult *)malloc(sizeof(pmResult) +
+ (rp->numpmid - 1) * sizeof(pmValueSet *))) == NULL) {
+ fprintf(stderr,
+ "%s: rewrite: cannot malloc pmResult for %d metrics\n",
+ pmProgname, rp->numpmid);
+ exit(1);
+ }
+ orp->numpmid = 0;
+ orp->timestamp = rp->timestamp; /* struct assignment */
+
+ for (i = 0; i < rp->numpmid; i++) {
+ metric_t *mp;
+ value_t *vp;
+ pmValueSet *vsp = rp->vset[i];
+ pmValueSet *ovsp;
+ int j;
+ int need;
+
+ if (pmidlist[i] != vsp->pmid) {
+ fprintf(stderr,
+ "%s: rewrite: Arrgh, mismatched PMID %s vs %s\n",
+ pmProgname, pmIDStr(pmidlist[i]), pmIDStr(vsp->pmid));
+ exit(1);
+ }
+
+ if (vsp->numval > 0)
+ need = (vsp->numval - 1) * sizeof(pmValue);
+ else
+ need = 0;
+ ovsp = (pmValueSet *)malloc(sizeof(pmValueSet) +
+ need*sizeof(pmValue));
+ if (ovsp == NULL) {
+ __uint64_t bytes = (sizeof(pmValueSet) + need * sizeof(pmValue));
+ fprintf(stderr,
+ "%s: rewrite: Arrgh, cannot malloc %"PRIi64" bytes for ovsp\n",
+ pmProgname, bytes);
+ exit(1);
+ }
+ ovsp->pmid = vsp->pmid;
+ ovsp->valfmt = vsp->valfmt;
+ if (vsp->numval <= 0) {
+ ovsp->numval = vsp->numval;
+ orp->vset[orp->numpmid] = ovsp;
+ orp->numpmid++;
+ }
+ else {
+ ovsp->numval = 0;
+ mp = &metriclist[i];
+ if (mp->mode != MODE_SKIP) {
+ for (j = 0; j < vsp->numval; j++) {
+ for (vp = mp->first; vp != NULL; vp = vp->next) {
+ if (vp->inst == vsp->vlist[j].inst)
+ break;
+ }
+ if (vp == NULL) {
+ fprintf(stderr,
+ "%s: rewrite: Arrgh: cannot find inst %d in value_t list for %s (%s)\n",
+ pmProgname, vsp->vlist[j].inst, namelist[i], pmIDStr(vsp->pmid));
+ exit(1);
+ }
+ if ((vp->control & (V_SEEN|V_INIT)) == 0)
+ continue;
+ /*
+ * we've seen this metric-instance pair in the last
+ * interval, or it is the first time for this one
+ */
+ if (mp->mode == MODE_REWRITE) {
+ pmAtomValue av;
+ int k;
+ sts = pmExtractValue(vsp->valfmt, &vsp->vlist[j], mp->idesc.type, &av, mp->odesc.type);
+ if (sts < 0) {
+ fprintf(stderr,
+ "%s: rewrite: pmExtractValue failed for pmid %s value %d: %s\n",
+ pmProgname, pmIDStr(vsp->pmid), j, pmErrStr(sts));
+ exit(1);
+ }
+ ovsp->pmid = vsp->pmid;
+ ovsp->vlist[ovsp->numval].inst = vsp->vlist[j].inst;
+ k = __pmStuffValue(&av, &ovsp->vlist[ovsp->numval], mp->odesc.type);
+ if (k < 0) {
+ fprintf(stderr,
+ "%s: rewrite: __pmStuffValue failed for pmid %s value %d: %s\n",
+ pmProgname, pmIDStr(vsp->pmid), j, pmErrStr(sts));
+ exit(1);
+ }
+ if (ovsp->numval == 0)
+ ovsp->valfmt = k;
+ ovsp->numval++;
+ vp->timestamp = rp->timestamp;
+ vp->value = av;
+ }
+ else {
+ ovsp->vlist[ovsp->numval] = vsp->vlist[j];
+ ovsp->numval++;
+ }
+ vp->control &= ~V_INIT;
+ }
+ }
+ if (ovsp->numval > 0) {
+ orp->vset[orp->numpmid] = ovsp;
+ orp->numpmid++;
+ }
+ else
+ free(ovsp);
+ }
+ }
+
+ if (orp->numpmid == 0) {
+ /*
+ * very unlikely that all metrics are either skipped or have
+ * no values, but it might happen ... do not allow this record
+ * to be written because it looks like a "mark" record with
+ * numpmid == 0
+ */
+ free(orp);
+ orp = NULL;
+ }
+
+ return orp;
+}
+
+void
+rewrite_free(void)
+{
+ int i;
+
+ if (orp == NULL)
+ return;
+
+ for (i = 0; i < orp->numpmid; i++) {
+ pmValueSet *vsp = orp->vset[i];
+ int j;
+ metric_t *mp;
+
+ for (j = 0; j < numpmid; j++) {
+ if (vsp->pmid == pmidlist[j])
+ break;
+ }
+ if (j == numpmid) {
+ fprintf(stderr,
+ "%s: rewrite_free: Arrgh, cannot find pmid %s in pmidlist[]\n",
+ pmProgname, pmIDStr(vsp->pmid));
+ exit(1);
+ }
+ mp = &metriclist[j];
+
+ if (vsp->numval > 0 && mp->mode == MODE_REWRITE) {
+ /*
+ * MODE_REWRITE implies the value was promoted to 64-bit
+ * and the pval in the pmResult came from the __pmStuffValue()
+ * call above, so free it here
+ */
+ for (j = 0; j < vsp->numval; j++) {
+ free(vsp->vlist[j].value.pval);
+ }
+ }
+
+ free(vsp);
+ }
+
+ free(orp);
+ orp = NULL;
+}
diff --git a/src/pmlogreduce/scan.c b/src/pmlogreduce/scan.c
new file mode 100644
index 0000000..e1bece7
--- /dev/null
+++ b/src/pmlogreduce/scan.c
@@ -0,0 +1,232 @@
+#include "pmlogreduce.h"
+
+static struct timeval last_tv = { 0, 0 };
+static int ictx_b = -1;
+
+extern struct timeval winstart_tval;
+
+/*
+ * This is the heart of the data reduction algorithm. The term
+ * metric-instance is used here to reflect the fact that this computation
+ * has to be performed for every instance of every metric.
+ *
+ * 1. need to look at every input archive record going forward from the
+ * current point up to the last one <= end (time) ... so no interp
+ * mode here
+ *
+ * 2. for counter metric-instances, look for and count "wraps"
+ *
+ * 3. for instantenous or discrete metric-instances with a numeric type,
+ * compute the arithmetic average of the observations over the
+ * interval
+ *
+ * 4. for _all_ metric-instances if there are no observations in the
+ * interval, then we'd like to supress this metric-instance from the
+ * output archive
+ *
+ * 5. all of the above has to be done in a way that makes sense in the
+ * presence of mark records
+ */
+
+void
+doscan(struct timeval *end)
+{
+ pmResult *rp;
+ value_t *vp;
+ int sts;
+ int i;
+ int ir;
+ int nr;
+
+ if (ictx_b == -1) {
+ /*
+ * first time, create the record at a time mode context for the
+ * input archive
+ */
+ if ((ictx_b = pmNewContext(PM_CONTEXT_ARCHIVE, iname)) < 0) {
+ fprintf(stderr, "%s: Error: cannot open archive \"%s\" (ctx_b): %s\n",
+ pmProgname, iname, pmErrStr(ictx_b));
+ exit(1);
+ }
+
+ if ((sts = pmSetMode(PM_MODE_FORW, NULL, 0)) < 0) {
+ fprintf(stderr,
+ "%s: Error: pmSetMode (ictx_b) failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+
+ if ((sts = pmUseContext(ictx_b)) < 0) {
+ fprintf(stderr, "%s: doscan: Error: cannot use context: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ for (i = 0; i < numpmid; i++) {
+ for (vp = metriclist[i].first; vp != NULL; vp = vp->next) {
+ vp->nobs = vp->nwrap = 0;
+ vp->control &= ~V_SEEN;
+ }
+ }
+
+ for (nr = 0; ; nr++) {
+
+ if ((sts = pmFetchArchive(&rp)) < 0) {
+ if (sts == PM_ERR_EOL)
+ break;
+ fprintf(stderr,
+ "%s: doscan: Error: pmFetch failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ if (nr == 0) {
+ fprintf(stderr, "scan starts at ");
+ __pmPrintStamp(stderr, &rp->timestamp);
+ fprintf(stderr, "\n");
+ }
+ }
+#endif
+
+ if (rp->numpmid == 0) {
+ /*
+ * Mark record ... copy into the output file as we cannot
+ * pretend there is data between the previous data record
+ * and the next data record
+ * Logic copied from pmlogextract.
+ */
+ struct {
+ __pmPDU len;
+ __pmPDU type;
+ __pmPDU from;
+ __pmTimeval timestamp;
+ int numpmid; /* zero PMIDs to follow */
+ __pmPDU trailer;
+ } markrec;
+ /*
+ * add space for, but don't bump length for, trailer so
+ * __pmLogPutResult2() has space for trailer in the buffer
+ */
+ markrec.len = sizeof(markrec) - sizeof(__pmPDU);
+ markrec.type = markrec.from = 0;
+ markrec.timestamp.tv_sec = htonl(rp->timestamp.tv_sec);
+ markrec.timestamp.tv_usec = htonl(rp->timestamp.tv_usec);
+ markrec.numpmid = 0;
+ if ((sts = __pmLogPutResult2(&logctl, (__pmPDU *)&markrec)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: mark record write: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ /*
+ * continue on to check the interval range ... numpmid == 0
+ * makes the rest of the loop safe
+ */
+ }
+
+ if (rp->timestamp.tv_sec > end->tv_sec ||
+ (rp->timestamp.tv_sec == end->tv_sec &&
+ rp->timestamp.tv_usec > end->tv_usec)) {
+ /*
+ * past the end of the interval, remember timestamp so we
+ * can resume here next time
+ */
+ last_tv = rp->timestamp; /* struct assignment */
+ pmFreeResult(rp);
+ break;
+ }
+
+ for (ir = 0; ir < rp->numpmid; ir++) {
+ pmValueSet *vsp;
+ int j;
+ metric_t *mp;
+
+ vsp = rp->vset[ir];
+ if (vsp->numval <= 0)
+ continue;
+
+ for (i = 0; i < numpmid; i++) {
+ if (vsp->pmid == pmidlist[i])
+ break;
+ }
+ if (i == numpmid) {
+ fprintf(stderr,
+ "%s: scan: Arrgh, cannot find pid %s in pidlist[]\n",
+ pmProgname, pmIDStr(vsp->pmid));
+ exit(1);
+ }
+ mp = &metriclist[i];
+ if (mp->mode == MODE_SKIP)
+ continue;
+
+ for (j = 0; j < vsp->numval; j++) {
+ value_t *lvp = NULL;
+ for (vp = mp->first; vp != NULL; vp = vp->next) {
+ if (vp->inst == vsp->vlist[j].inst)
+ break;
+ lvp = vp;
+ }
+ if (vp == NULL) {
+ vp = (value_t *)malloc(sizeof(value_t));
+ if (vp == NULL) {
+ fprintf(stderr,
+ "%s: rewrite: Arrgh, cannot malloc value_t\n", pmProgname);
+ exit(1);
+ }
+ if (lvp == NULL)
+ mp->first = vp;
+ else
+ lvp->next = vp;
+ vp->inst = vsp->vlist[j].inst;
+ vp->nobs = vp->nwrap = 0;
+ vp->control = V_INIT;
+ vp->next = NULL;
+#if PCP_DEBUG
+
+ if (pmDebug & DBG_TRACE_APPL1) {
+ fprintf(stderr,
+ "add value_t for %s (%s) inst %d\n",
+ namelist[i], pmIDStr(pmidlist[i]), vsp->vlist[j].inst);
+ }
+#endif
+ }
+ /* TODO ... hard part goes here 8^) */
+ if (mp->idesc.sem == PM_SEM_COUNTER) {
+ /*
+ * OK, this metric is a counter, scan each instance
+ * looking for potential wraps
+ */
+ ;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ __pmPrintStamp(stderr, &rp->timestamp);
+ fprintf(stderr, ": seen %s (%s) inst %d\n",
+ namelist[i], pmIDStr(pmidlist[i]),
+ vsp->vlist[j].inst);
+ }
+#endif
+ vp->control |= V_SEEN;
+ }
+ }
+
+ pmFreeResult(rp);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "scan ends at ");
+ __pmPrintStamp(stderr, &last_tv);
+ if (sts == PM_ERR_EOL)
+ fprintf(stderr, " [EOL]");
+ fprintf(stderr, " (%d records)\n", nr);
+ }
+#endif
+
+ if ((sts = pmSetMode(PM_MODE_FORW, &last_tv, 0)) < 0) {
+ fprintf(stderr,
+ "%s: doscan: Error: pmSetMode (ictx_b) time=", pmProgname);
+ __pmPrintStamp(stderr, &last_tv);
+ fprintf(stderr,
+ " failed: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+}
diff --git a/src/pmlogrewrite/GNUlocaldefs.coverage b/src/pmlogrewrite/GNUlocaldefs.coverage
new file mode 100644
index 0000000..00f5eb4
--- /dev/null
+++ b/src/pmlogrewrite/GNUlocaldefs.coverage
@@ -0,0 +1,2 @@
+CFLAGS += -fprofile-arcs -ftest-coverage
+LDIRT += *.gcda *.gcno *.gcov
diff --git a/src/pmlogrewrite/GNUmakefile b/src/pmlogrewrite/GNUmakefile
new file mode 100644
index 0000000..9a0c79a
--- /dev/null
+++ b/src/pmlogrewrite/GNUmakefile
@@ -0,0 +1,48 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2012 Red Hat, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+CFILES = pmlogrewrite.c util.c logio.c metric.c indom.c result.c
+HFILES = logger.h
+LFILES = lex.l
+YFILES = gram.y
+
+CMDTARGET = pmlogrewrite$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB) $(LIB_FOR_MATH)
+LDIRT += $(YFILES:%.y=%.tab.?)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+pmlogextract: $(OBJECTS)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogrewrite
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+.NOTPARALLEL:
+gram.tab.h gram.tab.c: gram.y
+
+lex.o gram.tab.o: gram.tab.h logger.h
+indom.o metric.o result.o util.o pmlogrewrite.o: logger.h
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlogrewrite/gram.y b/src/pmlogrewrite/gram.y
new file mode 100644
index 0000000..bdbb795
--- /dev/null
+++ b/src/pmlogrewrite/gram.y
@@ -0,0 +1,986 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+/*
+ * pmlogrewrite parser
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <errno.h>
+#include <assert.h>
+
+static indomspec_t *current_indomspec;
+static int current_star_indom;
+static int do_walk_indom;
+static int star_domain;
+
+static metricspec_t *current_metricspec;
+static int current_star_metric;
+static int star_cluster;
+static int do_walk_metric;
+static int output = OUTPUT_ALL;
+static int one_inst;
+static char *one_name;
+
+indomspec_t *
+walk_indom(int mode)
+{
+ static indomspec_t *ip;
+
+ if (do_walk_indom) {
+ if (mode == W_START)
+ ip = indom_root;
+ else
+ ip = ip->i_next;
+ while (ip != NULL && pmInDom_domain(ip->old_indom) != star_domain)
+ ip = ip->i_next;
+ }
+ else {
+ if (mode == W_START)
+ ip = current_indomspec;
+ else
+ ip = NULL;
+ }
+
+ return ip;
+}
+
+metricspec_t *
+walk_metric(int mode, int flag, char *which)
+{
+ static metricspec_t *mp;
+
+ if (do_walk_metric) {
+ if (mode == W_START)
+ mp = metric_root;
+ else
+ mp = mp->m_next;
+ while (mp != NULL) {
+ if (pmid_domain(mp->old_desc.pmid) == star_domain &&
+ (star_cluster == PM_ID_NULL || star_cluster == pmid_cluster(mp->old_desc.pmid)))
+ break;
+ mp = mp->m_next;
+ }
+ }
+ else {
+ if (mode == W_START)
+ mp = current_metricspec;
+ else
+ mp = NULL;
+ }
+
+ if (mp != NULL) {
+ if (mp->flags & flag) {
+ snprintf(mess, sizeof(mess), "Duplicate %s clause for metric %s", which, mp->old_name);
+ yyerror(mess);
+ }
+ if (flag != METRIC_DELETE) {
+ if (mp->flags & METRIC_DELETE) {
+ snprintf(mess, sizeof(mess), "Conflicting %s clause for deleted metric %s", which, mp->old_name);
+ yyerror(mess);
+ }
+ }
+ else {
+ if (mp->flags & (~METRIC_DELETE)) {
+ snprintf(mess, sizeof(mess), "Conflicting delete and other clauses for metric %s", mp->old_name);
+ yyerror(mess);
+ }
+ }
+ }
+
+ return mp;
+}
+
+%}
+
+%union {
+ char *str;
+ int ival;
+ double dval;
+ pmInDom indom;
+ pmID pmid;
+}
+
+%token TOK_LBRACE
+ TOK_RBRACE
+ TOK_PLUS
+ TOK_MINUS
+ TOK_COLON
+ TOK_COMMA
+ TOK_ASSIGN
+ TOK_GLOBAL
+ TOK_INDOM
+ TOK_DUPLICATE
+ TOK_METRIC
+ TOK_HOSTNAME
+ TOK_TZ
+ TOK_TIME
+ TOK_NAME
+ TOK_INST
+ TOK_INAME
+ TOK_DELETE
+ TOK_PMID
+ TOK_NULL_INT
+ TOK_TYPE
+ TOK_SEM
+ TOK_UNITS
+ TOK_OUTPUT
+ TOK_RESCALE
+
+%token<str> TOK_GNAME TOK_NUMBER TOK_STRING TOK_HNAME TOK_FLOAT
+%token<str> TOK_INDOM_STAR TOK_PMID_INT TOK_PMID_STAR
+%token<ival> TOK_TYPE_NAME TOK_SEM_NAME TOK_SPACE_NAME TOK_TIME_NAME
+%token<ival> TOK_COUNT_NAME TOK_OUTPUT_TYPE
+
+%type<str> hname
+%type<indom> indom_int null_or_indom
+%type<pmid> pmid_int pmid_or_name
+%type<ival> signnumber number rescaleopt duplicateopt
+%type<dval> float
+
+%%
+
+config : speclist
+ ;
+
+speclist : spec
+ | spec speclist
+ ;
+
+spec : globalspec
+ | indomspec
+ | metricspec
+ ;
+
+globalspec : TOK_GLOBAL TOK_LBRACE globaloptlist TOK_RBRACE
+ | TOK_GLOBAL TOK_LBRACE TOK_RBRACE
+ ;
+
+globaloptlist : globalopt
+ | globalopt globaloptlist
+ ;
+
+globalopt : TOK_HOSTNAME TOK_ASSIGN hname
+ {
+ if (global.flags & GLOBAL_CHANGE_HOSTNAME) {
+ snprintf(mess, sizeof(mess), "Duplicate global hostname clause");
+ yyerror(mess);
+ }
+ if (strcmp(inarch.label.ll_hostname, $3) == 0) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Global hostname (%s): No change", inarch.label.ll_hostname);
+ yywarn(mess);
+ }
+ }
+ else {
+ strncpy(global.hostname, $3, sizeof(global.hostname));
+ global.flags |= GLOBAL_CHANGE_HOSTNAME;
+ }
+ free($3);
+ }
+ | TOK_TZ TOK_ASSIGN TOK_STRING
+ {
+ if (global.flags & GLOBAL_CHANGE_TZ) {
+ snprintf(mess, sizeof(mess), "Duplicate global tz clause");
+ yyerror(mess);
+ }
+ if (strcmp(inarch.label.ll_tz, $3) == 0) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Global timezone (%s): No change", inarch.label.ll_tz);
+ yywarn(mess);
+ }
+ }
+ else {
+ strncpy(global.tz, $3, sizeof(global.tz));
+ global.flags |= GLOBAL_CHANGE_TZ;
+ }
+ free($3);
+ }
+ | TOK_TIME TOK_ASSIGN signtime
+ {
+ if (global.flags & GLOBAL_CHANGE_TIME) {
+ snprintf(mess, sizeof(mess), "Duplicate global time clause");
+ yyerror(mess);
+ }
+ if (global.time.tv_sec == 0 && global.time.tv_usec == 0) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Global time: No change");
+ yywarn(mess);
+ }
+ }
+ else
+ global.flags |= GLOBAL_CHANGE_TIME;
+ }
+ | TOK_HOSTNAME TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting hostname in hostname clause");
+ yyerror(mess);
+ }
+ | TOK_HOSTNAME
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in hostname clause");
+ yyerror(mess);
+ }
+ | TOK_TZ TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting timezone string in tz clause");
+ yyerror(mess);
+ }
+ | TOK_TZ
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in tz clause");
+ yyerror(mess);
+ }
+ | TOK_TIME TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting delta of the form [+-][HH:[MM:]]SS[.d...] in time clause");
+ yyerror(mess);
+ }
+ | TOK_TIME
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in time clause");
+ yyerror(mess);
+ }
+ ;
+
+ /*
+ * ambiguity in lexical scanner ... handle here
+ * abc.def - is TOK_HNAME or TOK_GNAME
+ * 123 - is TOK_HNAME or TOK_NUMBER
+ * 123.456 - is TOK_HNAME or TOK_FLOAT
+ */
+hname : TOK_HNAME
+ | TOK_GNAME
+ | TOK_NUMBER
+ | TOK_FLOAT
+ ;
+
+signnumber : TOK_PLUS TOK_NUMBER
+ {
+ $$ = atoi($2);
+ free($2);
+ }
+ | TOK_MINUS TOK_NUMBER
+ {
+ $$ = -atoi($2);
+ free($2);
+ }
+ | TOK_NUMBER
+ {
+ $$ = atoi($1);
+ free($1);
+ }
+ ;
+
+number : TOK_NUMBER
+ {
+ $$ = atoi($1);
+ free($1);
+ }
+ ;
+
+float : TOK_FLOAT
+ {
+ $$ = atof($1);
+ free($1);
+ }
+ ;
+
+signtime : TOK_PLUS time
+ | TOK_MINUS time { global.time.tv_sec = -global.time.tv_sec; }
+ | time
+ ;
+
+time : number TOK_COLON number TOK_COLON float /* HH:MM:SS.d.. format */
+ {
+ if ($3 > 59) {
+ snprintf(mess, sizeof(mess), "Minutes (%d) in time clause more than 59", $3);
+ yywarn(mess);
+ }
+ if ($5 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%.6f) in time clause more than 59", $5);
+ yywarn(mess);
+ }
+ global.time.tv_sec = $1 * 3600 + $3 * 60 + (int)$5;
+ global.time.tv_usec = (int)(1000000*(($5 - (int)$5))+0.5);
+ }
+ | number TOK_COLON number TOK_COLON number /* HH:MM:SS format */
+ {
+ if ($3 > 59) {
+ snprintf(mess, sizeof(mess), "Minutes (%d) in time clause more than 59", $3);
+ yywarn(mess);
+ }
+ if ($5 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%d) in time clause more than 59", $5);
+ yywarn(mess);
+ }
+ global.time.tv_sec = $1 * 3600 + $3 * 60 + $5;
+ }
+ | number TOK_COLON float /* MM:SS.d.. format */
+ {
+ if ($1 > 59) {
+ snprintf(mess, sizeof(mess), "Minutes (%d) in time clause more than 59", $1);
+ yywarn(mess);
+ }
+ if ($3 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%.6f) in time clause more than 59", $3);
+ yywarn(mess);
+ }
+ global.time.tv_sec = $1 * 60 + (int)$3;
+ global.time.tv_usec = (int)(1000000*(($3 - (int)$3))+0.5);
+ }
+ | number TOK_COLON number /* MM:SS format */
+ {
+ if ($1 > 59) {
+ snprintf(mess, sizeof(mess), "Minutes (%d) in time clause more than 59", $1);
+ yywarn(mess);
+ }
+ if ($3 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%d) in time clause more than 59", $3);
+ yywarn(mess);
+ }
+ global.time.tv_sec = $1 * 60 + $3;
+ }
+ | float /* SS.d.. format */
+ {
+ if ($1 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%.6f) in time clause more than 59", $1);
+ yywarn(mess);
+ }
+ global.time.tv_sec = (int)$1;
+ global.time.tv_usec = (int)(1000000*(($1 - (int)$1))+0.5);
+ }
+ | number /* SS format */
+ {
+ if ($1 > 59) {
+ snprintf(mess, sizeof(mess), "Seconds (%d) in time clause more than 59", $1);
+ yywarn(mess);
+ }
+ global.time.tv_sec = $1;
+ global.time.tv_usec = 0;
+ }
+ ;
+
+indomspec : TOK_INDOM indom_int
+ {
+ if (current_star_indom) {
+ __pmContext *ctxp;
+ __pmHashCtl *hcp;
+ __pmHashNode *node;
+
+ ctxp = __pmHandleToPtr(pmWhichContext());
+ assert(ctxp != NULL);
+ hcp = &ctxp->c_archctl->ac_log->l_hashindom;
+ star_domain = pmInDom_domain($2);
+ for (node = __pmHashWalk(hcp, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) {
+ if (pmInDom_domain((pmInDom)(node->key)) == star_domain)
+ current_indomspec = start_indom((pmInDom)(node->key));
+ }
+ do_walk_indom = 1;
+ }
+ else {
+ current_indomspec = start_indom($2);
+ do_walk_indom = 0;
+ }
+ }
+ TOK_LBRACE optindomopt TOK_RBRACE
+ | TOK_INDOM
+ {
+ snprintf(mess, sizeof(mess), "Expecting <domain>.<serial> or <domain>.* in indom rule");
+ yyerror(mess);
+ }
+ ;
+
+indom_int : TOK_FLOAT
+ {
+ int domain;
+ int serial;
+ int sts;
+ sts = sscanf($1, "%d.%d", &domain, &serial);
+ if (sts < 2) {
+ snprintf(mess, sizeof(mess), "Missing serial field for indom");
+ yyerror(mess);
+ }
+ if (domain < 1 || domain >= DYNAMIC_PMID) {
+ snprintf(mess, sizeof(mess), "Illegal domain field (%d) for indom", domain);
+ yyerror(mess);
+ }
+ if (serial < 0 || serial >= 4194304) {
+ snprintf(mess, sizeof(mess), "Illegal serial field (%d) for indom", serial);
+ yyerror(mess);
+ }
+ current_star_indom = 0;
+ free($1);
+ $$ = pmInDom_build(domain, serial);
+ }
+ | TOK_INDOM_STAR
+ {
+ int domain;
+ sscanf($1, "%d.", &domain);
+ if (domain < 1 || domain >= DYNAMIC_PMID) {
+ snprintf(mess, sizeof(mess), "Illegal domain field (%d) for indom", domain);
+ yyerror(mess);
+ }
+ current_star_indom = 1;
+ free($1);
+ $$ = pmInDom_build(domain, 0);
+ }
+ ;
+
+optindomopt : indomoptlist
+ |
+ ;
+
+indomoptlist : indomopt
+ | indomopt indomoptlist
+ ;
+
+indomopt : TOK_INDOM TOK_ASSIGN duplicateopt indom_int
+ {
+ indomspec_t *ip;
+ for (ip = walk_indom(W_START); ip != NULL; ip = walk_indom(W_NEXT)) {
+ pmInDom indom;
+ if (indom_root->new_indom != indom_root->old_indom) {
+ snprintf(mess, sizeof(mess), "Duplicate indom clause for indom %s", pmInDomStr(indom_root->old_indom));
+ yyerror(mess);
+ }
+ if (current_star_indom)
+ indom = pmInDom_build(pmInDom_domain($4), pmInDom_serial(ip->old_indom));
+ else
+ indom = $4;
+ if (indom != ip->old_indom)
+ ip->new_indom = indom;
+ else {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Instance domain %s: indom: No change", pmInDomStr(ip->old_indom));
+ yywarn(mess);
+ }
+ }
+ ip->indom_flags |= $3;
+ }
+ }
+ | TOK_INAME TOK_STRING TOK_ASSIGN TOK_STRING
+ {
+ indomspec_t *ip;
+ for (ip = walk_indom(W_START); ip != NULL; ip = walk_indom(W_NEXT)) {
+ if (change_inst_by_name(ip->old_indom, $2, $4) < 0)
+ yyerror(mess);
+ }
+ free($2);
+ /* Note: $4 referenced from new_iname[] */
+ }
+ | TOK_INAME TOK_STRING TOK_ASSIGN TOK_DELETE
+ {
+ indomspec_t *ip;
+ for (ip = walk_indom(W_START); ip != NULL; ip = walk_indom(W_NEXT)) {
+ if (change_inst_by_name(ip->old_indom, $2, NULL) < 0)
+ yyerror(mess);
+ }
+ free($2);
+ }
+ | TOK_INST number TOK_ASSIGN number
+ {
+ indomspec_t *ip;
+ for (ip = walk_indom(W_START); ip != NULL; ip = walk_indom(W_NEXT)) {
+ if (change_inst_by_inst(ip->old_indom, $2, $4) < 0)
+ yyerror(mess);
+ }
+ }
+ | TOK_INST number TOK_ASSIGN TOK_DELETE
+ {
+ indomspec_t *ip;
+ for (ip = walk_indom(W_START); ip != NULL; ip = walk_indom(W_NEXT)) {
+ if (change_inst_by_inst(ip->old_indom, $2, PM_IN_NULL) < 0)
+ yyerror(mess);
+ }
+ }
+ | TOK_INDOM TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting <domain>.<serial> or <domain>.* in indom clause");
+ yyerror(mess);
+ }
+ | TOK_INDOM
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in indom clause");
+ yyerror(mess);
+ }
+ | TOK_INAME TOK_STRING TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting new external instance name string or DELETE in iname clause");
+ yyerror(mess);
+ }
+ | TOK_INAME TOK_STRING
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in iname clause");
+ yyerror(mess);
+ }
+ | TOK_INAME
+ {
+ snprintf(mess, sizeof(mess), "Expecting old external instance name string in iname clause");
+ yyerror(mess);
+ }
+ | TOK_INST number TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting new internal instance identifier or DELETE in inst clause");
+ yyerror(mess);
+ }
+ | TOK_INST number
+ {
+ snprintf(mess, sizeof(mess), "Expecting -> in inst clause");
+ yyerror(mess);
+ }
+ | TOK_INST
+ {
+ snprintf(mess, sizeof(mess), "Expecting old internal instance identifier in inst clause");
+ yyerror(mess);
+ }
+ ;
+
+duplicateopt : TOK_DUPLICATE { $$ = INDOM_DUPLICATE; }
+ | { $$ = 0; }
+ ;
+
+metricspec : TOK_METRIC pmid_or_name
+ {
+ if (current_star_metric) {
+ __pmContext *ctxp;
+ __pmHashCtl *hcp;
+ __pmHashNode *node;
+
+ ctxp = __pmHandleToPtr(pmWhichContext());
+ assert(ctxp != NULL);
+ hcp = &ctxp->c_archctl->ac_log->l_hashpmid;
+ star_domain = pmid_domain($2);
+ if (current_star_metric == 1)
+ star_cluster = pmid_cluster($2);
+ else
+ star_cluster = PM_ID_NULL;
+ for (node = __pmHashWalk(hcp, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) {
+ if (pmid_domain((pmID)(node->key)) == star_domain &&
+ (star_cluster == PM_ID_NULL ||
+ star_cluster == pmid_cluster((pmID)(node->key))))
+ current_metricspec = start_metric((pmID)(node->key));
+ }
+ do_walk_metric = 1;
+ }
+ else {
+ if ($2 == PM_ID_NULL)
+ /* metric not in archive */
+ current_metricspec = NULL;
+ else
+ current_metricspec = start_metric($2);
+ do_walk_metric = 0;
+ }
+ }
+ TOK_LBRACE optmetricoptlist TOK_RBRACE
+ | TOK_METRIC
+ {
+ snprintf(mess, sizeof(mess), "Expecting metric name or <domain>.<cluster>.<item> or <domain>.<cluster>.* or <domain>.*.* in metric rule");
+ yyerror(mess);
+ }
+ ;
+
+pmid_or_name : pmid_int
+ | TOK_GNAME
+ {
+ int sts;
+ pmID pmid;
+ sts = pmLookupName(1, &$1, &pmid);
+ if (sts < 0) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s: %s", $1, pmErrStr(sts));
+ yywarn(mess);
+ }
+ pmid = PM_ID_NULL;
+ }
+ current_star_metric = 0;
+ free($1);
+ $$ = pmid;
+ }
+ ;
+
+pmid_int : TOK_PMID_INT
+ {
+ int domain;
+ int cluster;
+ int item;
+ int sts;
+ sts = sscanf($1, "%d.%d.%d", &domain, &cluster, &item);
+ assert(sts == 3);
+ if (domain < 1 || domain >= DYNAMIC_PMID) {
+ snprintf(mess, sizeof(mess), "Illegal domain field (%d) for pmid", domain);
+ yyerror(mess);
+ }
+ if (cluster < 0 || cluster >= 4096) {
+ snprintf(mess, sizeof(mess), "Illegal cluster field (%d) for pmid", cluster);
+ yyerror(mess);
+ }
+ if (item < 0 || item >= 1024) {
+ snprintf(mess, sizeof(mess), "Illegal item field (%d) for pmid", item);
+ yyerror(mess);
+ }
+ current_star_metric = 0;
+ free($1);
+ $$ = pmid_build(domain, cluster, item);
+ }
+ | TOK_PMID_STAR
+ {
+ int domain;
+ int cluster;
+ int sts;
+ sts = sscanf($1, "%d.%d.", &domain, &cluster);
+ if (domain < 1 || domain >= DYNAMIC_PMID) {
+ snprintf(mess, sizeof(mess), "Illegal domain field (%d) for pmid", domain);
+ yyerror(mess);
+ }
+ if (sts == 2) {
+ if (cluster >= 4096) {
+ snprintf(mess, sizeof(mess), "Illegal cluster field (%d) for pmid", cluster);
+ yyerror(mess);
+ }
+ current_star_metric = 1;
+ }
+ else {
+ cluster = 0;
+ current_star_metric = 2;
+ }
+ free($1);
+ $$ = pmid_build(domain, cluster, 0);
+ }
+ ;
+
+optmetricoptlist : metricoptlist
+ | /* nothing */
+ ;
+
+metricoptlist : metricopt
+ | metricopt metricoptlist
+ ;
+
+metricopt : TOK_PMID TOK_ASSIGN pmid_int
+ {
+ metricspec_t *mp;
+ pmID pmid;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_PMID, "pmid"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_PMID, "pmid")) {
+ if (current_star_metric == 1)
+ pmid = pmid_build(pmid_domain($3), pmid_cluster($3), pmid_item(mp->old_desc.pmid));
+ else if (current_star_metric == 2)
+ pmid = pmid_build(pmid_domain($3), pmid_cluster(mp->old_desc.pmid), pmid_item(mp->old_desc.pmid));
+ else
+ pmid = $3;
+ if (pmid == mp->old_desc.pmid) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): pmid: No change", mp->old_name, pmIDStr(mp->old_desc.pmid));
+ yywarn(mess);
+ }
+ }
+ else {
+ mp->new_desc.pmid = pmid;
+ mp->flags |= METRIC_CHANGE_PMID;
+ }
+ }
+ }
+ | TOK_NAME TOK_ASSIGN TOK_GNAME
+ {
+ metricspec_t *mp;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_NAME, "name"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_NAME, "name")) {
+ if (strcmp($3, mp->old_name) == 0) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): name: No change", mp->old_name, pmIDStr(mp->old_desc.pmid));
+ yywarn(mess);
+ }
+ }
+ else {
+ int sts;
+ pmID pmid;
+ sts = pmLookupName(1, &$3, &pmid);
+ if (sts >= 0) {
+ snprintf(mess, sizeof(mess), "Metric name %s already assigned for PMID %s", $3, pmIDStr(pmid));
+ yyerror(mess);
+ }
+ mp->new_name = $3;
+ mp->flags |= METRIC_CHANGE_NAME;
+ }
+ }
+ }
+ | TOK_TYPE TOK_ASSIGN TOK_TYPE_NAME
+ {
+ metricspec_t *mp;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_TYPE, "type"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_TYPE, "type")) {
+ if ($3 == mp->old_desc.type) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): type: PM_TYPE_%s: No change", mp->old_name, pmIDStr(mp->old_desc.pmid), pmTypeStr(mp->old_desc.type));
+ yywarn(mess);
+ }
+ }
+ else {
+ if (mp->old_desc.type == PM_TYPE_32 ||
+ mp->old_desc.type == PM_TYPE_U32 ||
+ mp->old_desc.type == PM_TYPE_64 ||
+ mp->old_desc.type == PM_TYPE_U64 ||
+ mp->old_desc.type == PM_TYPE_FLOAT ||
+ mp->old_desc.type == PM_TYPE_DOUBLE) {
+ mp->new_desc.type = $3;
+ mp->flags |= METRIC_CHANGE_TYPE;
+ }
+ else {
+ snprintf(mess, sizeof(mess), "Old type (PM_TYPE_%s) must be numeric", pmTypeStr(mp->old_desc.type));
+ yyerror(mess);
+ }
+ }
+ }
+ }
+ | TOK_INDOM TOK_ASSIGN null_or_indom pick
+ {
+ metricspec_t *mp;
+ pmInDom indom;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_INDOM, "indom"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_INDOM, "indom")) {
+ if (current_star_indom)
+ indom = pmInDom_build(pmInDom_domain($3), pmInDom_serial(mp->old_desc.indom));
+ else
+ indom = $3;
+ if (indom == mp->old_desc.indom) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): indom: %s: No change", mp->old_name, pmIDStr(mp->old_desc.pmid), pmInDomStr(mp->old_desc.indom));
+ yywarn(mess);
+ }
+ }
+ else {
+ if ((output == OUTPUT_MIN ||
+ output == OUTPUT_MAX ||
+ output == OUTPUT_SUM ||
+ output == OUTPUT_AVG) &&
+ mp->old_desc.type != PM_TYPE_32 &&
+ mp->old_desc.type != PM_TYPE_U32 &&
+ mp->old_desc.type != PM_TYPE_64 &&
+ mp->old_desc.type != PM_TYPE_U64 &&
+ mp->old_desc.type != PM_TYPE_FLOAT &&
+ mp->old_desc.type != PM_TYPE_DOUBLE) {
+ snprintf(mess, sizeof(mess), "OUTPUT option MIN, MAX, AVG or SUM requires type to be numeric, not PM_TYPE_%s", pmTypeStr(mp->old_desc.type));
+ yyerror(mess);
+ }
+ mp->new_desc.indom = indom;
+ mp->flags |= METRIC_CHANGE_INDOM;
+ mp->output = output;
+ if (output == OUTPUT_ONE) {
+ mp->one_name = one_name;
+ mp->one_inst = one_inst;
+ if (mp->old_desc.indom == PM_INDOM_NULL)
+ /*
+ * singular input, pick first (only)
+ * value, not one_inst matching ...
+ * one_inst used for output instance
+ * id
+ */
+ mp->output = OUTPUT_FIRST;
+ }
+ if (output == OUTPUT_ALL) {
+ /*
+ * No OUTPUT clause, set up the defaults
+ * based on indom types:
+ * non-NULL -> NULL
+ * OUTPUT_FIRST, inst PM_IN_NULL
+ * NULL -> non-NULL
+ * OUTPUT_FIRST, inst 0
+ * non-NULL -> non-NULL
+ * all instances selected
+ * (nothing to do for defaults)
+ * NULL -> NULL
+ * caught above in no change case
+ */
+ if (mp->old_desc.indom != PM_INDOM_NULL &&
+ mp->new_desc.indom == PM_INDOM_NULL) {
+ mp->output = OUTPUT_FIRST;
+ mp->one_inst = PM_IN_NULL;
+ }
+ else if (mp->old_desc.indom == PM_INDOM_NULL &&
+ mp->new_desc.indom != PM_INDOM_NULL) {
+ mp->output = OUTPUT_FIRST;
+ mp->one_inst = 0;
+ }
+ }
+ }
+ }
+ output = OUTPUT_ALL; /* for next time */
+ }
+ | TOK_SEM TOK_ASSIGN TOK_SEM_NAME
+ {
+ metricspec_t *mp;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_SEM, "sem"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_SEM, "sem")) {
+ if ($3 == mp->old_desc.sem) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): sem: %s: No change", mp->old_name, pmIDStr(mp->old_desc.pmid), SemStr(mp->old_desc.sem));
+ yywarn(mess);
+ }
+ }
+ else {
+ mp->new_desc.sem = $3;
+ mp->flags |= METRIC_CHANGE_SEM;
+ }
+ }
+ }
+ | TOK_UNITS TOK_ASSIGN signnumber TOK_COMMA signnumber TOK_COMMA signnumber TOK_COMMA TOK_SPACE_NAME TOK_COMMA TOK_TIME_NAME TOK_COMMA TOK_COUNT_NAME rescaleopt
+ {
+ metricspec_t *mp;
+ for (mp = walk_metric(W_START, METRIC_CHANGE_UNITS, "units"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_CHANGE_UNITS, "units")) {
+ if ($3 == mp->old_desc.units.dimSpace &&
+ $5 == mp->old_desc.units.dimTime &&
+ $7 == mp->old_desc.units.dimCount &&
+ $9 == mp->old_desc.units.scaleSpace &&
+ $11 == mp->old_desc.units.scaleTime &&
+ $13 == mp->old_desc.units.scaleCount) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): units: %s: No change", mp->old_name, pmIDStr(mp->old_desc.pmid), pmUnitsStr(&mp->old_desc.units));
+ yywarn(mess);
+ }
+ }
+ else {
+ mp->new_desc.units.dimSpace = $3;
+ mp->new_desc.units.dimTime = $5;
+ mp->new_desc.units.dimCount = $7;
+ mp->new_desc.units.scaleSpace = $9;
+ mp->new_desc.units.scaleTime = $11;
+ mp->new_desc.units.scaleCount = $13;
+ mp->flags |= METRIC_CHANGE_UNITS;
+ if ($14 == 1) {
+ if ($3 == mp->old_desc.units.dimSpace &&
+ $5 == mp->old_desc.units.dimTime &&
+ $7 == mp->old_desc.units.dimCount)
+ /* OK, no dim changes */
+ mp->flags |= METRIC_RESCALE;
+ else {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric: %s (%s): Dimension changed, cannot rescale", mp->old_name, pmIDStr(mp->old_desc.pmid));
+ yywarn(mess);
+ }
+ }
+ }
+ else if (sflag) {
+ if ($3 == mp->old_desc.units.dimSpace &&
+ $5 == mp->old_desc.units.dimTime &&
+ $7 == mp->old_desc.units.dimCount)
+ mp->flags |= METRIC_RESCALE;
+ }
+ }
+ }
+ }
+ | TOK_DELETE
+ {
+ metricspec_t *mp;
+ for (mp = walk_metric(W_START, METRIC_DELETE, "delete"); mp != NULL; mp = walk_metric(W_NEXT, METRIC_DELETE, "delete")) {
+ mp->flags |= METRIC_DELETE;
+ }
+ }
+ | TOK_PMID TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting <domain>.<cluster>.<item> or <domain>.<cluster>.* or <domain>.*.* in pmid clause");
+ yyerror(mess);
+ }
+ | TOK_NAME TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting metric name in iname clause");
+ yyerror(mess);
+ }
+ | TOK_TYPE TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting XXX (from PM_TYPE_XXX) in type clause");
+ yyerror(mess);
+ }
+ | TOK_INDOM TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting <domain>.<serial> or NULL in indom clause");
+ yyerror(mess);
+ }
+ | TOK_SEM TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting XXX (from PM_SEM_XXX) in sem clause");
+ yyerror(mess);
+ }
+ | TOK_UNITS TOK_ASSIGN
+ {
+ snprintf(mess, sizeof(mess), "Expecting 3 numeric values for dim* fields of units");
+ yyerror(mess);
+ }
+ | TOK_UNITS TOK_ASSIGN signnumber TOK_COMMA signnumber TOK_COMMA signnumber TOK_COMMA
+ {
+ snprintf(mess, sizeof(mess), "Expecting 0 or XXX (from PM_SPACE_XXX) for scaleSpace field of units");
+ yyerror(mess);
+ }
+ | TOK_UNITS TOK_ASSIGN signnumber TOK_COMMA signnumber TOK_COMMA signnumber TOK_COMMA TOK_SPACE_NAME TOK_COMMA
+ {
+ snprintf(mess, sizeof(mess), "Expecting 0 or XXX (from PM_TIME_XXX) for scaleTime field of units");
+ yyerror(mess);
+ }
+ | TOK_UNITS TOK_ASSIGN signnumber TOK_COMMA signnumber TOK_COMMA signnumber TOK_COMMA TOK_SPACE_NAME TOK_COMMA TOK_TIME_NAME TOK_COMMA
+ {
+ snprintf(mess, sizeof(mess), "Expecting 0 or ONE for scaleCount field of units");
+ yyerror(mess);
+ }
+ ;
+
+null_or_indom : indom_int
+ | TOK_NULL_INT
+ {
+ $$ = PM_INDOM_NULL;
+ }
+ ;
+
+pick : TOK_OUTPUT TOK_INST number
+ {
+ output = OUTPUT_ONE;
+ one_inst = $3;
+ one_name = NULL;
+ }
+ | TOK_OUTPUT TOK_INAME TOK_STRING
+ {
+ output = OUTPUT_ONE;
+ one_inst = PM_IN_NULL;
+ one_name = $3;
+ }
+ | TOK_OUTPUT TOK_OUTPUT_TYPE
+ {
+ output = $2;
+ }
+ | TOK_OUTPUT
+ {
+ snprintf(mess, sizeof(mess), "Expecting FIRST or LAST or INST or INAME or MIN or MAX or AVG for OUTPUT instance option");
+ yyerror(mess);
+ }
+ | /* nothing */
+ ;
+
+rescaleopt : TOK_RESCALE { $$ = 1; }
+ | /* nothing */
+ { $$ = 0; }
+ ;
+
+%%
diff --git a/src/pmlogrewrite/indom.c b/src/pmlogrewrite/indom.c
new file mode 100644
index 0000000..25592cb
--- /dev/null
+++ b/src/pmlogrewrite/indom.c
@@ -0,0 +1,381 @@
+/*
+ * Indom metadata support for pmlogrewrite
+ *
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <assert.h>
+
+/*
+ * Find or create a new indomspec_t
+ */
+indomspec_t *
+start_indom(pmInDom indom)
+{
+ indomspec_t *ip;
+ int i;
+
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (indom == ip->old_indom)
+ break;
+ }
+ if (ip == NULL) {
+ int numinst;
+ int *instlist;
+ char **namelist;
+
+ numinst = pmGetInDomArchive(indom, &instlist, &namelist);
+ if (numinst < 0) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Instance domain %s: %s", pmInDomStr(indom), pmErrStr(numinst));
+ yywarn(mess);
+ }
+ return NULL;
+ }
+
+ ip = (indomspec_t *)malloc(sizeof(indomspec_t));
+ if (ip == NULL) {
+ fprintf(stderr, "indomspec malloc(%d) failed: %s\n", (int)sizeof(indomspec_t), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ ip->i_next = indom_root;
+ indom_root = ip;
+ ip->indom_flags = 0;
+ ip->inst_flags = (int *)malloc(numinst*sizeof(int));
+ if (ip->inst_flags == NULL) {
+ fprintf(stderr, "indomspec flags malloc(%d) failed: %s\n", (int)(numinst*sizeof(int)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ for (i = 0; i < numinst; i++)
+ ip->inst_flags[i] = 0;
+ ip->old_indom = indom;
+ ip->new_indom = indom;
+ ip->numinst = numinst;
+ ip->old_inst = instlist;
+ ip->new_inst = (int *)malloc(numinst*sizeof(int));
+ if (ip->new_inst == NULL) {
+ fprintf(stderr, "new_inst malloc(%d) failed: %s\n", (int)(numinst*sizeof(int)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ ip->old_iname = namelist;
+ ip->new_iname = (char **)malloc(numinst*sizeof(char *));
+ if (ip->new_iname == NULL) {
+ fprintf(stderr, "new_iname malloc(%d) failed: %s\n", (int)(numinst*sizeof(char *)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ }
+
+ return ip;
+}
+
+int
+change_inst_by_name(pmInDom indom, char *old, char *new)
+{
+ int i;
+ indomspec_t *ip;
+
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (indom == ip->old_indom)
+ break;
+ }
+ assert(ip != NULL);
+
+ for (i = 0; i < ip->numinst; i++) {
+ if (inst_name_eq(ip->old_iname[i], old) > 0) {
+ if ((new == NULL && ip->inst_flags[i]) ||
+ (ip->inst_flags[i] & (INST_CHANGE_INAME|INST_DELETE))) {
+ snprintf(mess, sizeof(mess), "Duplicate or conflicting clauses for instance [%d] \"%s\" of indom %s",
+ ip->old_inst[i], ip->old_iname[i], pmInDomStr(indom));
+ return -1;
+ }
+ break;
+ }
+ }
+ if (i == ip->numinst) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Unknown instance \"%s\" in iname clause for indom %s", old, pmInDomStr(indom));
+ yywarn(mess);
+ }
+ return 0;
+ }
+
+ if (new == NULL) {
+ ip->inst_flags[i] |= INST_DELETE;
+ ip->new_iname[i] = NULL;
+ return 0;
+ }
+
+ if (strcmp(ip->old_iname[i], new) == 0) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Instance domain %s: Instance: \"%s\": No change", pmInDomStr(indom), ip->old_iname[i]);
+ yywarn(mess);
+ }
+ }
+ else {
+ ip->inst_flags[i] |= INST_CHANGE_INAME;
+ ip->new_iname[i] = new;
+ }
+
+ return 0;
+}
+
+int
+change_inst_by_inst(pmInDom indom, int old, int new)
+{
+ int i;
+ indomspec_t *ip;
+
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (indom == ip->old_indom)
+ break;
+ }
+ assert(ip != NULL);
+
+ for (i = 0; i < ip->numinst; i++) {
+ if (ip->old_inst[i] == old) {
+ if ((new == PM_IN_NULL && ip->inst_flags[i]) ||
+ (ip->inst_flags[i] & (INST_CHANGE_INST|INST_DELETE))) {
+ snprintf(mess, sizeof(mess), "Duplicate or conflicting clauses for instance [%d] \"%s\" of indom %s",
+ ip->old_inst[i], ip->old_iname[i], pmInDomStr(indom));
+ return -1;
+ }
+ break;
+ }
+ }
+ if (i == ip->numinst) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Unknown instance %d in inst clause for indom %s", old, pmInDomStr(indom));
+ yywarn(mess);
+ }
+ return 0;
+ }
+
+ if (new == PM_IN_NULL) {
+ ip->inst_flags[i] |= INST_DELETE;
+ ip->new_inst[i] = PM_IN_NULL;
+ return 0;
+ }
+
+ if (ip->old_inst[i] == new) {
+ /* no change ... */
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Instance domain %s: Instance: %d: No change", pmInDomStr(indom), ip->old_inst[i]);
+ yywarn(mess);
+ }
+ }
+ else {
+ ip->new_inst[i] = new;
+ ip->inst_flags[i] |= INST_CHANGE_INST;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ __pmLogHdr hdr;
+ __pmTimeval stamp;
+ pmInDom indom;
+ int numinst;
+ char other[1];
+} indom_t;
+
+/*
+ * reverse the logic of __pmLogPutInDom()
+ */
+static void
+_pmUnpackInDom(__pmPDU *pdubuf, pmInDom *indom, __pmTimeval *tp, int *numinst, int **instlist, char ***inamelist)
+{
+ indom_t *idp;
+ int i;
+ int *ip;
+ char *strbuf;
+
+ idp = (indom_t *)pdubuf;
+
+ tp->tv_sec = ntohl(idp->stamp.tv_sec);
+ tp->tv_usec = ntohl(idp->stamp.tv_usec);
+ *indom = ntoh_pmInDom(idp->indom);
+ *numinst = ntohl(idp->numinst);
+ *instlist = (int *)malloc(*numinst * sizeof(int));
+ if (*instlist == NULL) {
+ fprintf(stderr, "_pmUnpackInDom instlist malloc(%d) failed: %s\n", (int)(*numinst * sizeof(int)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ ip = (int *)idp->other;
+ for (i = 0; i < *numinst; i++)
+ (*instlist)[i] = ntohl(*ip++);
+ *inamelist = (char **)malloc(*numinst * sizeof(char *));
+ if (*inamelist == NULL) {
+ fprintf(stderr, "_pmUnpackInDom inamelist malloc(%d) failed: %s\n", (int)(*numinst * sizeof(char *)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ /*
+ * ip[i] is stridx[i], which is offset into strbuf[]
+ */
+ strbuf = (char *)&ip[*numinst];
+ for (i = 0; i < *numinst; i++) {
+ (*inamelist)[i] = &strbuf[ntohl(ip[i])];
+ }
+}
+
+/*
+ * Note:
+ * We unpack the indom metadata record _again_ (was already done when
+ * the input archive was opened), but the data structure behind
+ * __pmLogCtl has differences for 32-bit and 64-bit pointers and
+ * modifying it as part of the rewrite could make badness break
+ * out later. It is safer to do it again, populate local copies
+ * of instlist[] and inamelist[], dink with 'em and then toss them
+ * away.
+ */
+void
+do_indom(void)
+{
+ long out_offset;
+ pmInDom indom;
+ __pmTimeval stamp;
+ int numinst;
+ int *instlist;
+ char **inamelist;
+ indomspec_t *ip;
+ int sts;
+ int i;
+ int j;
+ int need_alloc = 0;
+
+ out_offset = ftell(outarch.logctl.l_mdfp);
+ _pmUnpackInDom(inarch.metarec, &indom, &stamp, &numinst, &instlist, &inamelist);
+
+ /*
+ * global time stamp adjustment (if any has already been done in the
+ * PDU buffer, so this is reflected in the unpacked value of stamp.
+ */
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (ip->old_indom != indom)
+ continue;
+ if (ip->indom_flags & INDOM_DUPLICATE) {
+ /*
+ * save the old indom without changes, then operate on the
+ * duplicate
+ */
+ if ((sts = __pmLogPutInDom(&outarch.logctl, indom, &stamp, numinst, instlist, inamelist)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutInDom: %s: %s\n",
+ pmProgname, pmInDomStr(indom), pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Metadata: write pre-duplicate InDom %s @ offset=%ld\n", pmInDomStr(indom), out_offset);
+ }
+#endif
+ out_offset = ftell(outarch.logctl.l_mdfp);
+ }
+ if (ip->new_indom != ip->old_indom)
+ indom = ip->new_indom;
+ for (i = 0; i < ip->numinst; i++) {
+ for (j = 0; j < numinst; j++) {
+ if (ip->old_inst[i] == instlist[j])
+ break;
+ }
+ if (j == numinst)
+ continue;
+ if (ip->inst_flags[i] & INST_DELETE) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Delete: instance %s (%d) for indom %s\n", ip->old_iname[i], ip->old_inst[i], pmInDomStr(ip->old_indom));
+#endif
+ j++;
+ while (j < numinst) {
+ instlist[j-1] = instlist[j];
+ inamelist[j-1] = inamelist[j];
+ j++;
+ }
+ need_alloc = 1;
+ numinst--;
+ }
+ else {
+ if (ip->inst_flags[i] & INST_CHANGE_INST)
+ instlist[j] = ip->new_inst[i];
+ if (ip->inst_flags[i] & INST_CHANGE_INAME) {
+ inamelist[j] = ip->new_iname[i];
+ need_alloc = 1;
+ }
+#if PCP_DEBUG
+ if ((ip->inst_flags[i] & (INST_CHANGE_INST | INST_CHANGE_INAME)) && (pmDebug & DBG_TRACE_APPL1)) {
+ if ((ip->inst_flags[i] & (INST_CHANGE_INST | INST_CHANGE_INAME)) == (INST_CHANGE_INST | INST_CHANGE_INAME))
+ fprintf(stderr, "Rewrite: instance %s (%d) -> %s (%d) for indom %s\n", ip->old_iname[i], ip->old_inst[i], ip->new_iname[i], ip->new_inst[i], pmInDomStr(ip->old_indom));
+ else if ((ip->inst_flags[i] & (INST_CHANGE_INST | INST_CHANGE_INAME)) == INST_CHANGE_INST)
+ fprintf(stderr, "Rewrite: instance %s (%d) -> %s (%d) for indom %s\n", ip->old_iname[i], ip->old_inst[i], ip->old_iname[i], ip->new_inst[i], pmInDomStr(ip->old_indom));
+ else
+ fprintf(stderr, "Rewrite: instance %s (%d) -> %s (%d) for indom %s\n", ip->old_iname[i], ip->old_inst[i], ip->new_iname[i], ip->old_inst[i], pmInDomStr(ip->old_indom));
+ }
+#endif
+ }
+ }
+ }
+
+ if (need_alloc) {
+ /*
+ * __pmLogPutInDom assumes the elements of inamelist[] point into
+ * of a contiguous allocation starting at inamelist[0] ... if we've
+ * changed an instance name or moved instance names about, then we
+ * need to reallocate the strings for inamelist[]
+ */
+ int need = 0;
+ char *new;
+ char *p;
+
+ for (j = 0; j < numinst; j++)
+ need += strlen(inamelist[j]) + 1;
+ new = (char *)malloc(need);
+ if (new == NULL) {
+ fprintf(stderr, "inamelist[] malloc(%d) failed: %s\n", need, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ p = new;
+ for (j = 0; j < numinst; j++) {
+ strcpy(p, inamelist[j]);
+ inamelist[j] = p;
+ p += strlen(p) + 1;
+ }
+ }
+
+ if ((sts = __pmLogPutInDom(&outarch.logctl, indom, &stamp, numinst, instlist, inamelist)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutInDom: %s: %s\n",
+ pmProgname, pmInDomStr(indom), pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ fprintf(stderr, "Metadata: write InDom %s @ offset=%ld\n", pmInDomStr(indom), out_offset);
+ }
+#endif
+
+ free(instlist);
+ if (need_alloc)
+ free(inamelist[0]);
+ free(inamelist);
+}
diff --git a/src/pmlogrewrite/lex.l b/src/pmlogrewrite/lex.l
new file mode 100644
index 0000000..4f7e6d7
--- /dev/null
+++ b/src/pmlogrewrite/lex.l
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+%{
+/*
+ * pmlogrewrite configfile lexical scanner
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <errno.h>
+
+char mess[256];
+
+#define LEX_NONE 0
+#define LEX_GLOBAL 1
+#define LEX_INDOM 2
+#define LEX_METRIC 3
+
+int mystate = LEX_NONE;
+
+static int comma_count;
+
+#include "gram.tab.h"
+
+static char *
+dupstr(char *s, int strip_quotes)
+{
+ char *p;
+ if (strip_quotes)
+ p = strdup(&s[1]);
+ else
+ p = strdup(s);
+ if (p == NULL) {
+ fprintf(stderr, "Failed strdup(\"%s\") in lexer: %s\n", s, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (strip_quotes) {
+ char *pend = p;
+ while (*pend != '\0')
+ pend++;
+ *--pend = '\0';
+ }
+ return p;
+}
+
+%}
+
+%option noinput
+%option nounput
+
+%{
+#ifdef FLEX_SCANNER
+#ifndef YY_NO_UNPUT
+#define YY_NO_UNPUT
+#endif
+#endif
+%}
+
+%s none glob host indom metric type sem units space time count output
+%option case-insensitive
+
+%%
+
+<INITIAL>"global" { mystate= LEX_GLOBAL; return TOK_GLOBAL; }
+<INITIAL>"metric" { mystate = LEX_METRIC; return TOK_METRIC; }
+<INITIAL>"indom" { mystate = LEX_INDOM; return TOK_INDOM; }
+
+<glob>"hostname" { BEGIN(host); return TOK_HOSTNAME; }
+<glob>"time" { return TOK_TIME; }
+<glob>"tz" { return TOK_TZ; }
+ /* Hostname */
+<host>[A-Za-z0-9][A-Za-z0-9.-]* { yylval.str = dupstr(yytext, 0); BEGIN(glob); return TOK_HNAME; }
+
+<indom,metric>"delete" { return TOK_DELETE; }
+<indom>"indom" { return TOK_INDOM; }
+<indom>"duplicate" { return TOK_DUPLICATE; }
+<indom>"iname" { return TOK_INAME; }
+<indom>"inst" { return TOK_INST; }
+
+<metric>"name" { return TOK_NAME; }
+<metric>"pmid" { return TOK_PMID; }
+<metric>"type" { BEGIN(type); return TOK_TYPE; }
+<metric>"indom" { return TOK_INDOM; }
+<metric>"NULL" { return TOK_NULL_INT; }
+<metric>"output" { BEGIN(output); return TOK_OUTPUT; }
+<metric>"sem" { BEGIN(sem); return TOK_SEM; }
+<metric>"units" { BEGIN(units); comma_count = 0; return TOK_UNITS; }
+
+<type>"32" { yylval.ival = PM_TYPE_32; BEGIN(metric); return TOK_TYPE_NAME; }
+<type>"U32" { yylval.ival = PM_TYPE_U32; BEGIN(metric); return TOK_TYPE_NAME; }
+<type>"64" { yylval.ival = PM_TYPE_64; BEGIN(metric); return TOK_TYPE_NAME; }
+<type>"U64" { yylval.ival = PM_TYPE_U64; BEGIN(metric); return TOK_TYPE_NAME; }
+<type>"FLOAT" { yylval.ival = PM_TYPE_FLOAT; BEGIN(metric); return TOK_TYPE_NAME; }
+<type>"DOUBLE" { yylval.ival = PM_TYPE_DOUBLE; BEGIN(metric); return TOK_TYPE_NAME; }
+
+<output>"first" { yylval.ival = OUTPUT_FIRST; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"last" { yylval.ival = OUTPUT_LAST; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"min" { yylval.ival = OUTPUT_MIN; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"max" { yylval.ival = OUTPUT_MAX; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"sum" { yylval.ival = OUTPUT_SUM; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"avg" { yylval.ival = OUTPUT_AVG; BEGIN(metric); return TOK_OUTPUT_TYPE; }
+<output>"inst" { BEGIN(metric); return TOK_INST; }
+<output>"iname" { BEGIN(metric); return TOK_INAME; }
+
+<sem>"COUNTER" { yylval.ival = PM_SEM_COUNTER; BEGIN(metric); return TOK_SEM_NAME; }
+<sem>"INSTANT" { yylval.ival = PM_SEM_INSTANT; BEGIN(metric); return TOK_SEM_NAME; }
+<sem>"DISCRETE" { yylval.ival = PM_SEM_DISCRETE; BEGIN(metric); return TOK_SEM_NAME; }
+
+<units>"," {
+ ++comma_count;
+ switch (comma_count) {
+ case 1:
+ case 2:
+ break;
+ case 3:
+ BEGIN(space);
+ break;
+ case 4:
+ BEGIN(time);
+ break;
+ case 5:
+ BEGIN(count);
+ break;
+ }
+ return TOK_COMMA;
+ }
+<metric>"rescale" { return TOK_RESCALE; }
+
+<space>"BYTE" { yylval.ival = PM_SPACE_BYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"KBYTE" { yylval.ival = PM_SPACE_KBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"MBYTE" { yylval.ival = PM_SPACE_MBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"GBYTE" { yylval.ival = PM_SPACE_GBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"TBYTE" { yylval.ival = PM_SPACE_TBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"PBYTE" { yylval.ival = PM_SPACE_PBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"EBYTE" { yylval.ival = PM_SPACE_EBYTE; BEGIN(units); return TOK_SPACE_NAME; }
+<space>"0" { yylval.ival = 0; BEGIN(units); return TOK_SPACE_NAME; }
+
+<time>"NSEC" { yylval.ival = PM_TIME_NSEC; BEGIN(units); return TOK_TIME_NAME; }
+<time>"USEC" { yylval.ival = PM_TIME_USEC; BEGIN(units); return TOK_TIME_NAME; }
+<time>"MSEC" { yylval.ival = PM_TIME_MSEC; BEGIN(units); return TOK_TIME_NAME; }
+<time>"SEC" { yylval.ival = PM_TIME_SEC; BEGIN(units); return TOK_TIME_NAME; }
+<time>"MIN" { yylval.ival = PM_TIME_MIN; BEGIN(units); return TOK_TIME_NAME; }
+<time>"HOUR" { yylval.ival = PM_TIME_HOUR; BEGIN(units); return TOK_TIME_NAME; }
+<time>"0" { yylval.ival = 0; BEGIN(units); return TOK_TIME_NAME; }
+
+<count>"ONE" { yylval.ival = PM_COUNT_ONE; BEGIN(metric); return TOK_COUNT_NAME; }
+<count>[0-9]+ { yylval.ival = atoi(yytext); BEGIN(metric); return TOK_COUNT_NAME; }
+
+\"[^\"\n][^\"\n]*\" { yylval.str = dupstr(yytext, 1); return TOK_STRING; }
+
+[0-9]+ { yylval.str = dupstr(yytext, 0); return TOK_NUMBER; }
+
+[0-9]+\.[0-9]* { yylval.str = dupstr(yytext, 0); return TOK_FLOAT; }
+
+[0-9]+\.\* { yylval.str = dupstr(yytext, 0); return TOK_INDOM_STAR; }
+
+[0-9]+\.[0-9]+\.[0-9]+ { yylval.str = dupstr(yytext, 0); return TOK_PMID_INT; }
+
+[0-9]+\.[0-9]+\.\* { yylval.str = dupstr(yytext, 0); return TOK_PMID_STAR; }
+[0-9]+\.\*\.\* { yylval.str = dupstr(yytext, 0); return TOK_PMID_STAR; }
+
+ /* Generic name, e.g. for identifier or metric or hostname */
+[A-Za-z][A-Za-z0-9_.]* { yylval.str = dupstr(yytext, 0); return TOK_GNAME; }
+
+\#.* { }
+
+[ \t\r]+ { }
+
+"->" { return TOK_ASSIGN; }
+"{" {
+ if (mystate == LEX_GLOBAL) BEGIN(glob);
+ if (mystate == LEX_INDOM) BEGIN(indom);
+ if (mystate == LEX_METRIC) BEGIN(metric);
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1)) == (DBG_TRACE_APPL0 | DBG_TRACE_APPL1))
+ fprintf(stderr, "lex: [%d] { begin state=%d\n", lineno, mystate);
+#endif
+ return TOK_LBRACE;
+ }
+"}" {
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1)) == (DBG_TRACE_APPL0 | DBG_TRACE_APPL1))
+ fprintf(stderr, "lex: [%d] } end state=%d\n", lineno, mystate);
+#endif
+ mystate = LEX_NONE;
+ BEGIN(INITIAL); return TOK_RBRACE;
+ }
+"+" { return TOK_PLUS; }
+"-" { return TOK_MINUS; }
+":" { return TOK_COLON; }
+"," { return TOK_COMMA; }
+
+\n { lineno++; }
+. {
+ snprintf(mess, sizeof(mess), "Unexpected character '%c'",
+ yytext[0]);
+ yyerror(mess);
+ }
+%%
+
+int
+yywrap(void)
+{
+ return(1);
+}
diff --git a/src/pmlogrewrite/logger.h b/src/pmlogrewrite/logger.h
new file mode 100644
index 0000000..36c9712
--- /dev/null
+++ b/src/pmlogrewrite/logger.h
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * common data structures for pmlogextract
+ */
+
+#ifndef _LOGGER_H
+#define _LOGGER_H
+
+extern int sflag; /* -s from command line */
+extern int vflag; /* -v from command line */
+extern int wflag; /* -w from command line */
+
+/*
+ * Global rewrite specifications
+ */
+typedef struct {
+ int flags; /* GLOBAL_* flags */
+ struct timeval time; /* timestamp shift */
+ char hostname[PM_LOG_MAXHOSTLEN];
+ char tz[PM_TZ_MAXLEN];
+} global_t;
+
+/* values for global_t flags */
+#define GLOBAL_CHANGE_TIME 1
+#define GLOBAL_CHANGE_HOSTNAME 2
+#define GLOBAL_CHANGE_TZ 4
+
+extern global_t global;
+
+/*
+ * Rewrite specifications for an instance domain
+ */
+typedef struct indomspec {
+ struct indomspec *i_next;
+ int indom_flags; /* INDOM_* flags */
+ int *inst_flags; /* INST_* flags */
+ pmInDom old_indom;
+ pmInDom new_indom; /* old_indom if no change */
+ int numinst;
+ int *old_inst; /* filled from pmGetInDomArchive() */
+ char **old_iname; /* filled from pmGetInDomArchive() */
+ int *new_inst;
+ char **new_iname;
+} indomspec_t;
+
+/* values for indomspec_t indom_flags */
+#define INDOM_ADD 1
+#define INDOM_DELETE 2
+#define INDOM_DUPLICATE 4
+/* values for indomspec_t inst_flags[] */
+#define INST_CHANGE_INST 16
+#define INST_CHANGE_INAME 32
+#define INST_DELETE 64
+
+extern indomspec_t *indom_root;
+
+/*
+ * Rewrite specifications for a metric
+ */
+typedef struct metricspec {
+ struct metricspec *m_next;
+ int flags; /* METRIC_* flags */
+ int output; /* OUTPUT_* values */
+ int one_inst; /* for OUTPUT_ONE INST */
+ char *one_name; /* for OUTPUT_ONE NAME */
+ char *old_name;
+ char *new_name;
+ pmDesc old_desc;
+ pmDesc new_desc;
+ indomspec_t *ip; /* for instance id changes */
+} metricspec_t;
+
+/* values for metricspec_t flags[] */
+#define METRIC_CHANGE_PMID 1
+#define METRIC_CHANGE_NAME 2
+#define METRIC_CHANGE_TYPE 4
+#define METRIC_CHANGE_INDOM 8
+#define METRIC_CHANGE_SEM 16
+#define METRIC_CHANGE_UNITS 32
+#define METRIC_DELETE 64
+#define METRIC_RESCALE 128
+
+/* values for output when indom (numval >= 1) => PM_INDOM_NULL (numval = 1) */
+#define OUTPUT_ALL 0
+#define OUTPUT_FIRST 1
+#define OUTPUT_LAST 2
+#define OUTPUT_ONE 3
+#define OUTPUT_MIN 4
+#define OUTPUT_MAX 5
+#define OUTPUT_SUM 6
+#define OUTPUT_AVG 7
+
+extern metricspec_t *metric_root;
+
+/*
+ * Input archive control
+ */
+typedef struct {
+ int ctx;
+ __pmContext *ctxp;
+ char *name;
+ pmLogLabel label;
+ __pmPDU *metarec;
+ __pmPDU *logrec;
+ pmResult *rp;
+ int mark; /* need EOL marker */
+} inarch_t;
+
+extern inarch_t inarch; /* input archive */
+
+/*
+ * Output archive control
+ */
+typedef struct {
+ char *name; /* base name of output archive */
+ __pmLogCtl logctl; /* libpcp control */
+} outarch_t;
+
+extern outarch_t outarch; /* output archive */
+
+/* size of a string length field */
+#define LENSIZE 4
+
+/* generic error message buffer */
+extern char mess[256];
+
+/* yylex() gets intput from here ... */
+extern char *configfile;
+extern FILE *fconfig;
+extern int lineno;
+extern FILE *yyin;
+
+extern void yyerror(char *);
+extern void yywarn(char *);
+extern void yysemantic(char *);
+extern int yylex(void);
+extern int yyparse(void);
+
+#define W_START 1
+#define W_NEXT 2
+#define W_NONE 3
+
+extern int _pmLogGet(__pmLogCtl *, int, __pmPDU **);
+extern int _pmLogPut(FILE *, __pmPDU *);
+extern int _pmLogRename(const char *, const char *);
+extern int _pmLogRemove(const char *);
+extern pmUnits ntoh_pmUnits(pmUnits);
+#define ntoh_pmInDom(indom) ntohl(indom)
+#define ntoh_pmID(pmid) ntohl(pmid)
+
+extern metricspec_t *start_metric(pmID);
+extern indomspec_t *start_indom(pmInDom);
+extern int change_inst_by_inst(pmInDom, int, int);
+extern int change_inst_by_name(pmInDom, char *, char *);
+extern int inst_name_eq(const char *, const char *);
+
+extern char *SemStr(int);
+extern void newvolume(int);
+
+extern void do_desc(void);
+extern void do_indom(void);
+extern void do_result(void);
+
+extern void abandon(void);
+
+#endif /* _LOGGER_H */
diff --git a/src/pmlogrewrite/logio.c b/src/pmlogrewrite/logio.c
new file mode 100644
index 0000000..010e0e3
--- /dev/null
+++ b/src/pmlogrewrite/logio.c
@@ -0,0 +1,175 @@
+/*
+ * utils for pmlogextract
+ *
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+
+/*
+ * raw read of next log record - largely stolen from __pmLogRead in libpcp
+ */
+int
+_pmLogGet(__pmLogCtl *lcp, int vol, __pmPDU **pb)
+{
+ int head;
+ int tail;
+ int sts;
+ long offset;
+ char *p;
+ __pmPDU *lpb;
+ FILE *f;
+
+ if (vol == PM_LOG_VOL_META)
+ f = lcp->l_mdfp;
+ else
+ f = lcp->l_mfp;
+
+ offset = ftell(f);
+ assert(offset >= 0);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ fprintf(stderr, "_pmLogGet: fd=%d vol=%d posn=%ld ",
+ fileno(f), vol, offset);
+ }
+#endif
+
+again:
+ sts = (int)fread(&head, 1, sizeof(head), f);
+ if (sts != sizeof(head)) {
+ if (sts == 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "AFTER end\n");
+#endif
+ fseek(f, offset, SEEK_SET);
+ if (vol != PM_LOG_VOL_META) {
+ if (lcp->l_curvol < lcp->l_maxvol) {
+ if (__pmLogChangeVol(lcp, lcp->l_curvol+1) == 0) {
+ f = lcp->l_mfp;
+ goto again;
+ }
+ }
+ }
+ return PM_ERR_EOL;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: hdr fread=%d %s\n", sts, osstrerror());
+#endif
+ if (sts > 0)
+ return PM_ERR_LOGREC;
+ else
+ return -oserror();
+ }
+
+ if ((lpb = (__pmPDU *)malloc(ntohl(head))) == NULL) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: __pmFindPDUBuf(%d) %s\n",
+ (int)ntohl(head), osstrerror());
+#endif
+ fseek(f, offset, SEEK_SET);
+ return -oserror();
+ }
+
+ lpb[0] = head;
+ if ((sts = (int)fread(&lpb[1], 1, ntohl(head) - sizeof(head), f)) != ntohl(head) - sizeof(head)) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: data fread=%d %s\n", sts, osstrerror());
+#endif
+ free(lpb);
+ if (sts == 0) {
+ fseek(f, offset, SEEK_SET);
+ return PM_ERR_EOL;
+ }
+ else if (sts > 0)
+ return PM_ERR_LOGREC;
+ else
+ return -oserror();
+ }
+
+
+ p = (char *)lpb;
+ memcpy(&tail, &p[ntohl(head) - sizeof(head)], sizeof(head));
+ if (head != tail) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "Error: head-tail mismatch (%d-%d)\n",
+ (int)ntohl(head), (int)ntohl(tail));
+#endif
+ free(lpb);
+ return PM_ERR_LOGREC;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG) {
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, "@");
+ if (sts >= 0) {
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ else
+ fprintf(stderr, "unknown time");
+ }
+ fprintf(stderr, " len=%d (incl head+tail)\n", (int)ntohl(head));
+ }
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_PDU) {
+ int i, j;
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&lpb[vol == PM_LOG_VOL_META ? 2 : 1];
+ fprintf(stderr, "_pmLogGet");
+ if (vol != PM_LOG_VOL_META || ntohl(lpb[1]) == TYPE_INDOM) {
+ fprintf(stderr, " timestamp=");
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ __pmPrintStamp(stderr, &stamp);
+ }
+ fprintf(stderr, " " PRINTF_P_PFX "%p ... " PRINTF_P_PFX "%p", lpb, &lpb[ntohl(head)/sizeof(__pmPDU) - 1]);
+ fputc('\n', stderr);
+ fprintf(stderr, "%03d: ", 0);
+ for (j = 0, i = 0; j < ntohl(head)/sizeof(__pmPDU); j++) {
+ if (i == 8) {
+ fprintf(stderr, "\n%03d: ", j);
+ i = 0;
+ }
+ fprintf(stderr, "%08x ", lpb[j]);
+ i++;
+ }
+ fputc('\n', stderr);
+ }
+#endif
+
+ *pb = lpb;
+ return 0;
+}
+
+pmUnits
+ntoh_pmUnits(pmUnits units)
+{
+ unsigned int x;
+
+ x = ntohl(*(unsigned int *)&units);
+ units = *(pmUnits *)&x;
+ return units;
+}
diff --git a/src/pmlogrewrite/metric.c b/src/pmlogrewrite/metric.c
new file mode 100644
index 0000000..31da308
--- /dev/null
+++ b/src/pmlogrewrite/metric.c
@@ -0,0 +1,230 @@
+/*
+ * Metric metadata support for pmlogrewrite
+ *
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <assert.h>
+
+/*
+ * Find or create a new metricspec_t
+ */
+metricspec_t *
+start_metric(pmID pmid)
+{
+ metricspec_t *mp;
+ int sts;
+
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1)) == (DBG_TRACE_APPL0 | DBG_TRACE_APPL1))
+ fprintf(stderr, "start_metric(%s)", pmIDStr(pmid));
+#endif
+
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (pmid == mp->old_desc.pmid) {
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1)) == (DBG_TRACE_APPL0 | DBG_TRACE_APPL1))
+ fprintf(stderr, " -> %s\n", mp->old_name);
+#endif
+ break;
+ }
+ }
+ if (mp == NULL) {
+ char *name;
+ pmDesc desc;
+
+ sts = pmNameID(pmid, &name);
+ if (sts < 0) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric %s pmNameID: %s", pmIDStr(pmid), pmErrStr(sts));
+ yywarn(mess);
+ }
+ return NULL;
+ }
+ sts = pmLookupDesc(pmid, &desc);
+ if (sts < 0) {
+ if (wflag) {
+ snprintf(mess, sizeof(mess), "Metric %s: pmLookupDesc: %s", pmIDStr(pmid), pmErrStr(sts));
+ yywarn(mess);
+ }
+ free(name);
+ return NULL;
+ }
+
+ mp = (metricspec_t *)malloc(sizeof(metricspec_t));
+ if (mp == NULL) {
+ fprintf(stderr, "metricspec malloc(%d) failed: %s\n", (int)sizeof(metricspec_t), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ mp->m_next = metric_root;
+ metric_root = mp;
+ mp->output = OUTPUT_ALL;
+ mp->one_inst = 0;
+ mp->one_name = NULL;
+ mp->old_name = name;
+ mp->old_desc = desc;
+ mp->new_desc = mp->old_desc;
+ mp->flags = 0;
+ mp->ip = NULL;
+#if PCP_DEBUG
+ if ((pmDebug & (DBG_TRACE_APPL0 | DBG_TRACE_APPL1)) == (DBG_TRACE_APPL0 | DBG_TRACE_APPL1))
+ fprintf(stderr, " -> %s [new entry]\n", mp->old_name);
+#endif
+ }
+
+ return mp;
+}
+
+typedef struct {
+ __pmLogHdr hdr;
+ pmDesc desc;
+ int numnames;
+ char strbuf[1];
+} desc_t;
+
+/*
+ * reverse the logic of __pmLogPutDesc()
+ */
+static void
+_pmUnpackDesc(__pmPDU *pdubuf, pmDesc *desc, int *numnames, char ***names)
+{
+ desc_t *pp;
+ int i;
+ char *p;
+ int slen;
+
+ pp = (desc_t *)pdubuf;
+ desc->type = ntohl(pp->desc.type);
+ desc->sem = ntohl(pp->desc.sem);
+ desc->indom = ntoh_pmInDom(pp->desc.indom);
+ desc->units = ntoh_pmUnits(pp->desc.units);
+ desc->pmid = ntoh_pmID(pp->desc.pmid);
+ *numnames = ntohl(pp->numnames);
+ *names = (char **)malloc(*numnames * sizeof(*names[1]));
+ if (*names == NULL) {
+ fprintf(stderr, "_pmUnpackDesc malloc(%d) failed: %s\n", (int)(*numnames * sizeof(*names[1])), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+
+ p = pp->strbuf;
+ for (i = 0; i < *numnames; i++) {
+ memcpy(&slen, p, LENSIZE);
+ slen = ntohl(slen);
+ p += LENSIZE;
+ (*names)[i] = malloc(slen+1);
+ if ((*names)[i] == NULL) {
+ fprintf(stderr, "_pmUnpackDesc malloc(%d) failed: %s\n", slen+1, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ strncpy((*names)[i], p, slen);
+ (*names)[i][slen] = '\0';
+ p += slen;
+ }
+
+ return;
+}
+
+/*
+ * rewrite pmDesc from metadata, returns
+ * -1 delete this pmDesc
+ * 0 no change
+ * 1 changed
+ */
+void
+do_desc(void)
+{
+ metricspec_t *mp;
+ pmDesc desc;
+ int i;
+ int sts;
+ int numnames;
+ char **names;
+ long out_offset;
+
+ out_offset = ftell(outarch.logctl.l_mdfp);
+ _pmUnpackDesc(inarch.metarec, &desc, &numnames, &names);
+
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (desc.pmid != mp->old_desc.pmid || mp->flags == 0)
+ continue;
+ if (mp->flags & METRIC_DELETE) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Delete: pmDesc for %s\n", pmIDStr(desc.pmid));
+#endif
+ goto done;
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Rewrite: pmDesc for %s\n", pmIDStr(desc.pmid));
+#endif
+ if (mp->flags & METRIC_CHANGE_PMID)
+ desc.pmid = mp->new_desc.pmid;
+ if (mp->flags & METRIC_CHANGE_NAME) {
+ for (i = 0; i < numnames; i++) {
+ if (strcmp(names[i], mp->old_name) == 0) {
+ free(names[i]);
+ names[i] = strdup(mp->new_name);
+ if (names[i] == NULL) {
+ fprintf(stderr, "do_desc strdup(%s) failed: %s\n", mp->new_name, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ break;
+ }
+ }
+ if (i == numnames) {
+ fprintf(stderr, "%s: Botch: old name %s not found in list of %d names for pmid %s ...",
+ pmProgname, mp->old_name, numnames, pmIDStr(mp->old_desc.pmid));
+ for (i = 0; i < numnames; i++) {
+ if (i > 0) fputc(',', stderr);
+ fprintf(stderr, " %s", names[i]);
+ }
+ fputc('\n', stderr);
+ abandon();
+ /*NOTREACHED*/
+ }
+ }
+ if (mp->flags & METRIC_CHANGE_TYPE)
+ desc.type = mp->new_desc.type;
+ if (mp->flags & METRIC_CHANGE_INDOM)
+ desc.indom = mp->new_desc.indom;
+ if (mp->flags & METRIC_CHANGE_SEM)
+ desc.sem = mp->new_desc.sem;
+ if (mp->flags & METRIC_CHANGE_UNITS)
+ desc.units = mp->new_desc.units; /* struct assignment */
+ break;
+ }
+ if ((sts = __pmLogPutDesc(&outarch.logctl, &desc, numnames, names)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutDesc: %s (%s): %s\n",
+ pmProgname, names[0], pmIDStr(desc.pmid), pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Metadata: write PMID %s @ offset=%ld\n", pmIDStr(desc.pmid), out_offset);
+#endif
+
+done:
+ for (i = 0; i < numnames; i++)
+ free(names[i]);
+ free(names);
+ return;
+}
diff --git a/src/pmlogrewrite/pmlogrewrite.c b/src/pmlogrewrite/pmlogrewrite.c
new file mode 100644
index 0000000..625366d
--- /dev/null
+++ b/src/pmlogrewrite/pmlogrewrite.c
@@ -0,0 +1,1318 @@
+/*
+ * pmlogrewrite - config-driven stream editor for PCP archives
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <assert.h>
+
+global_t global;
+indomspec_t *indom_root;
+metricspec_t *metric_root;
+int lineno;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "config", 1, 'c', "PATH", "file or directory to load rules from" },
+ { "check", 0, 'C', 0, "parse config file(s) and quit (verbose warnings also)" },
+ { "desperate", 0, 'd', 0, "desperate, save output archive even after error" },
+ { "", 0, 'i', 0, "rewrite in place, input-archive will be over-written" },
+ { "quick", 0, 'q', 0, "quick mode, no output if no change" },
+ { "scale", 0, 's', 0, "do scale conversion" },
+ { "verbose", 0, 'v', 0, "increased diagnostic verbosity" },
+ { "warnings", 0, 'w', 0, "emit warnings [default is silence]" },
+ PMAPI_OPTIONS_TEXT(""),
+ PMAPI_OPTIONS_TEXT("output-archive is required unless -i is specified"),
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "c:CdD:iqsvw?",
+ .long_options = longopts,
+ .short_usage = "[options] input-archive [output-archive]",
+};
+
+/*
+ * Global variables
+ */
+static int first_datarec = 1; /* first record flag */
+static char bak_base[MAXPATHLEN+1]; /* basename for backup with -i */
+
+off_t new_log_offset; /* new log offset */
+off_t new_meta_offset; /* new meta offset */
+
+
+/* archive control stuff */
+inarch_t inarch; /* input archive control */
+outarch_t outarch; /* output archive control */
+
+/* command line args */
+int nconf; /* number of config files */
+char **conf; /* list of config files */
+char *configfile; /* current config file */
+int Cflag; /* -C parse config and quit */
+int dflag; /* -d desperate */
+int iflag; /* -i in-place */
+int qflag; /* -q quick or quiet */
+int sflag; /* -s scale values */
+int vflag; /* -v verbosity */
+int wflag; /* -w emit warnings */
+
+/*
+ * report that archive is corrupted
+ */
+static void
+_report(FILE *fp)
+{
+ off_t here;
+ struct stat sbuf;
+
+ here = lseek(fileno(fp), 0L, SEEK_CUR);
+ fprintf(stderr, "%s: Error occurred at byte offset %ld into a file of",
+ pmProgname, (long)here);
+ if (fstat(fileno(fp), &sbuf) < 0)
+ fprintf(stderr, ": stat: %s\n", osstrerror());
+ else
+ fprintf(stderr, " %ld bytes.\n", (long)sbuf.st_size);
+ if (dflag)
+ fprintf(stderr, "The last record, and the remainder of this file will not be processed.\n");
+ abandon();
+ /*NOTREACHED*/
+}
+
+/*
+ * switch output volumes
+ */
+void
+newvolume(int vol)
+{
+ FILE *newfp;
+
+ if ((newfp = __pmLogNewFile(outarch.name, vol)) != NULL) {
+ fclose(outarch.logctl.l_mfp);
+ outarch.logctl.l_mfp = newfp;
+ outarch.logctl.l_label.ill_vol = outarch.logctl.l_curvol = vol;
+ __pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label);
+ fflush(outarch.logctl.l_mfp);
+ }
+ else {
+ fprintf(stderr, "%s: __pmLogNewFile(%s,%d) Error: %s\n",
+ pmProgname, outarch.name, vol, pmErrStr(-oserror()));
+ abandon();
+ /*NOTREACHED*/
+ }
+}
+
+/* construct new archive label */
+static void
+newlabel(void)
+{
+ __pmLogLabel *lp = &outarch.logctl.l_label;
+
+ /* copy magic number, pid, host and timezone */
+ lp->ill_magic = inarch.label.ll_magic;
+ lp->ill_pid = inarch.label.ll_pid;
+ if (global.flags & GLOBAL_CHANGE_HOSTNAME)
+ strncpy(lp->ill_hostname, global.hostname, PM_LOG_MAXHOSTLEN);
+ else
+ strncpy(lp->ill_hostname, inarch.label.ll_hostname, PM_LOG_MAXHOSTLEN);
+ lp->ill_hostname[PM_LOG_MAXHOSTLEN-1] = '\0';
+ if (global.flags & GLOBAL_CHANGE_TZ)
+ strncpy(lp->ill_tz, global.tz, PM_TZ_MAXLEN);
+ else
+ strncpy(lp->ill_tz, inarch.label.ll_tz, PM_TZ_MAXLEN);
+ lp->ill_tz[PM_TZ_MAXLEN-1] = '\0';
+}
+
+/*
+ * write label records at the start of each physical file
+ */
+void
+writelabel(int do_rewind)
+{
+ off_t old_offset;
+
+ if (do_rewind) {
+ old_offset = ftell(outarch.logctl.l_tifp);
+ assert(old_offset >= 0);
+ rewind(outarch.logctl.l_tifp);
+ }
+ outarch.logctl.l_label.ill_vol = PM_LOG_VOL_TI;
+ __pmLogWriteLabel(outarch.logctl.l_tifp, &outarch.logctl.l_label);
+ if (do_rewind)
+ fseek(outarch.logctl.l_tifp, (long)old_offset, SEEK_SET);
+
+ if (do_rewind) {
+ old_offset = ftell(outarch.logctl.l_mdfp);
+ assert(old_offset >= 0);
+ rewind(outarch.logctl.l_mdfp);
+ }
+ outarch.logctl.l_label.ill_vol = PM_LOG_VOL_META;
+ __pmLogWriteLabel(outarch.logctl.l_mdfp, &outarch.logctl.l_label);
+ if (do_rewind)
+ fseek(outarch.logctl.l_mdfp, (long)old_offset, SEEK_SET);
+
+ if (do_rewind) {
+ old_offset = ftell(outarch.logctl.l_mfp);
+ assert(old_offset >= 0);
+ rewind(outarch.logctl.l_mfp);
+ }
+ outarch.logctl.l_label.ill_vol = 0;
+ __pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label);
+ if (do_rewind)
+ fseek(outarch.logctl.l_mfp, (long)old_offset, SEEK_SET);
+}
+
+/*
+ * read next metadata record
+ */
+static int
+nextmeta()
+{
+ int sts;
+ __pmLogCtl *lcp;
+
+ lcp = inarch.ctxp->c_archctl->ac_log;
+ if ((sts = _pmLogGet(lcp, PM_LOG_VOL_META, &inarch.metarec)) < 0) {
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: Error: _pmLogGet[meta %s]: %s\n",
+ pmProgname, inarch.name, pmErrStr(sts));
+ _report(lcp->l_mdfp);
+ }
+ return -1;
+ }
+
+ return ntohl(inarch.metarec[1]);
+}
+
+
+/*
+ * read next log record
+ *
+ * return status is
+ * 0 ok
+ * 1 ok, but volume switched
+ * PM_ERR_EOL end of file
+ * -1 fatal error
+ */
+static int
+nextlog(void)
+{
+ int sts;
+ __pmLogCtl *lcp;
+ int old_vol;
+
+
+ lcp = inarch.ctxp->c_archctl->ac_log;
+ old_vol = inarch.ctxp->c_archctl->ac_log->l_curvol;
+
+ if ((sts = __pmLogRead(lcp, PM_MODE_FORW, NULL, &inarch.rp, PMLOGREAD_NEXT)) < 0) {
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: Error: __pmLogRead[log %s]: %s\n",
+ pmProgname, inarch.name, pmErrStr(sts));
+ _report(lcp->l_mfp);
+ }
+ return -1;
+ }
+
+ return old_vol == inarch.ctxp->c_archctl->ac_log->l_curvol ? 0 : 1;
+}
+
+#ifdef IS_MINGW
+#define S_ISLINK(mode) 0 /* no symlink support */
+#else
+#ifndef S_ISLINK
+#define S_ISLINK(mode) ((mode & S_IFMT) == S_IFLNK)
+#endif
+#endif
+
+/*
+ * parse command line arguments
+ */
+int
+parseargs(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ int sep = __pmPathSeparator();
+ struct stat sbuf;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'c': /* config file */
+ if (stat(opts.optarg, &sbuf) < 0) {
+ pmprintf("%s: stat(%s) failed: %s\n",
+ pmProgname, opts.optarg, osstrerror());
+ opts.errors++;
+ break;
+ }
+ if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) {
+ nconf++;
+ if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) != NULL)
+ conf[nconf-1] = opts.optarg;
+ }
+ else if (S_ISDIR(sbuf.st_mode)) {
+ DIR *dirp;
+ struct dirent *dp;
+ char path[MAXPATHLEN+1];
+
+ if ((dirp = opendir(opts.optarg)) == NULL) {
+ pmprintf("%s: opendir(%s) failed: %s\n", pmProgname, opts.optarg, osstrerror());
+ opts.errors++;
+ }
+ else while ((dp = readdir(dirp)) != NULL) {
+ /* skip ., .. and "hidden" files */
+ if (dp->d_name[0] == '.') continue;
+ snprintf(path, sizeof(path), "%s%c%s", opts.optarg, sep, dp->d_name);
+ if (stat(path, &sbuf) < 0) {
+ pmprintf("%s: %s: %s\n", pmProgname, path, osstrerror());
+ opts.errors++;
+ }
+ else if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) {
+ nconf++;
+ if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) == NULL)
+ break;
+ if ((conf[nconf-1] = strdup(path)) == NULL) {
+ fprintf(stderr, "conf[%d] strdup(%s) failed: %s\n", nconf-1, path, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ }
+ }
+ if (dirp != NULL)
+ closedir(dirp);
+ }
+ else {
+ pmprintf("%s: Error: -c config %s is not a file or directory\n", pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ if (nconf > 0 && conf == NULL) {
+ fprintf(stderr, "%s: Error: conf[%d] realloc(%d) failed: %s\n", pmProgname, nconf, (int)(nconf*sizeof(conf[0])), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ break;
+
+ case 'C': /* parse configs and quit */
+ Cflag = 1;
+ vflag = 1;
+ wflag = 1;
+ break;
+
+ case 'd': /* desperate */
+ dflag = 1;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'i': /* in-place, over-write input archive */
+ iflag = 1;
+ break;
+
+ case 'q': /* quick or quiet */
+ qflag = 1;
+ break;
+
+ case 's': /* do scale conversions */
+ sflag = 1;
+ break;
+
+ case 'v': /* verbosity */
+ vflag++;
+ break;
+
+ case 'w': /* print warnings */
+ wflag = 1;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors == 0) {
+ if ((iflag == 0 && opts.optind != argc-2) ||
+ (iflag == 1 && opts.optind != argc-1))
+ opts.errors++;
+ }
+
+ return -opts.errors;
+}
+
+static void
+parseconfig(char *file)
+{
+ configfile = file;
+ if ((yyin = fopen(configfile, "r")) == NULL) {
+ fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
+ pmProgname, configfile, osstrerror());
+ exit(1);
+ }
+ if (vflag > 1)
+ fprintf(stderr, "Start configfile: %s\n", file);
+ lineno = 1;
+
+ if (yyparse() != 0)
+ exit(1);
+
+ fclose(yyin);
+ yyin = NULL;
+
+ return;
+}
+
+char *
+SemStr(int sem)
+{
+ static char buf[20];
+
+ if (sem == PM_SEM_COUNTER) snprintf(buf, sizeof(buf), "counter");
+ else if (sem == PM_SEM_INSTANT) snprintf(buf, sizeof(buf), "instant");
+ else if (sem == PM_SEM_DISCRETE) snprintf(buf, sizeof(buf), "discrete");
+ else snprintf(buf, sizeof(buf), "bad sem? %d", sem);
+
+ return buf;
+}
+
+static void
+reportconfig(void)
+{
+ indomspec_t *ip;
+ metricspec_t *mp;
+ int i;
+ int change = 0;
+
+ printf("PCP Archive Log Rewrite Specifications Summary\n");
+ change |= (global.flags != 0);
+ if (global.flags & GLOBAL_CHANGE_HOSTNAME)
+ printf("Hostname:\t%s -> %s\n", inarch.label.ll_hostname, global.hostname);
+ if (global.flags & GLOBAL_CHANGE_TZ)
+ printf("Timezone:\t%s -> %s\n", inarch.label.ll_tz, global.tz);
+ if (global.flags & GLOBAL_CHANGE_TIME) {
+ static struct tm *tmp;
+ char *sign = "";
+ time_t time;
+ if (global.time.tv_sec < 0) {
+ time = (time_t)(-global.time.tv_sec);
+ sign = "-";
+ }
+ else
+ time = (time_t)global.time.tv_sec;
+ tmp = gmtime(&time);
+ tmp->tm_hour += 24 * tmp->tm_yday;
+ if (tmp->tm_hour < 10)
+ printf("Delta:\t\t-> %s%02d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec);
+ else
+ printf("Delta:\t\t-> %s%d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec);
+ }
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ int hdr_done = 0;
+ if (ip->new_indom != ip->old_indom) {
+ printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom));
+ hdr_done = 1;
+ printf("pmInDom:\t-> %s\n", pmInDomStr(ip->new_indom));
+ change |= 1;
+ }
+ for (i = 0; i < ip->numinst; i++) {
+ change |= (ip->inst_flags[i] != 0);
+ if (ip->inst_flags[i]) {
+ if (hdr_done == 0) {
+ printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom));
+ hdr_done = 1;
+ }
+ printf("Instance:\t\[%d] \"%s\" -> ", ip->old_inst[i], ip->old_iname[i]);
+ if (ip->inst_flags[i] & INST_DELETE)
+ printf("DELETE\n");
+ else {
+ if (ip->inst_flags[i] & INST_CHANGE_INST)
+ printf("[%d] ", ip->new_inst[i]);
+ else
+ printf("[%d] ", ip->old_inst[i]);
+ if (ip->inst_flags[i] & INST_CHANGE_INAME)
+ printf("\"%s\"\n", ip->new_iname[i]);
+ else
+ printf("\"%s\"\n", ip->old_iname[i]);
+ }
+ }
+ }
+ }
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (mp->flags != 0 || mp->ip != NULL) {
+ change |= 1;
+ printf("\nMetric: %s (%s)\n", mp->old_name, pmIDStr(mp->old_desc.pmid));
+ }
+ if (mp->flags & METRIC_CHANGE_PMID) {
+ printf("pmID:\t\t%s ->", pmIDStr(mp->old_desc.pmid));
+ printf(" %s\n", pmIDStr(mp->new_desc.pmid));
+ }
+ if (mp->flags & METRIC_CHANGE_NAME)
+ printf("Name:\t\t%s -> %s\n", mp->old_name, mp->new_name);
+ if (mp->flags & METRIC_CHANGE_TYPE) {
+ printf("Type:\t\t%s ->", pmTypeStr(mp->old_desc.type));
+ printf(" %s\n", pmTypeStr(mp->new_desc.type));
+ }
+ if (mp->flags & METRIC_CHANGE_INDOM) {
+ printf("InDom:\t\t%s ->", pmInDomStr(mp->old_desc.indom));
+ printf(" %s\n", pmInDomStr(mp->new_desc.indom));
+ if (mp->output != OUTPUT_ALL) {
+ printf("Output:\t\t");
+ switch (mp->output) {
+ case OUTPUT_ONE:
+ if (mp->old_desc.indom != PM_INDOM_NULL) {
+ printf("value for instance");
+ if (mp->one_inst != PM_IN_NULL)
+ printf(" %d", mp->one_inst);
+ if (mp->one_name != NULL)
+ printf(" \"%s\"", mp->one_name);
+ putchar('\n');
+ }
+ else
+ printf("the only value (output instance %d)\n", mp->one_inst);
+ break;
+ case OUTPUT_FIRST:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("first value\n");
+ else {
+ if (mp->one_inst != PM_IN_NULL)
+ printf("first and only value (output instance %d)\n", mp->one_inst);
+ else
+ printf("first and only value (output instance \"%s\")\n", mp->one_name);
+ }
+ break;
+ case OUTPUT_LAST:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("last value\n");
+ else
+ printf("last and only value (output instance %d)\n", mp->one_inst);
+ break;
+ case OUTPUT_MIN:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("smallest value\n");
+ else
+ printf("smallest and only value (output instance %d)\n", mp->one_inst);
+ break;
+ case OUTPUT_MAX:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("largest value\n");
+ else
+ printf("largest and only value (output instance %d)\n", mp->one_inst);
+ break;
+ case OUTPUT_SUM:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("sum value (output instance %d)\n", mp->one_inst);
+ else
+ printf("sum and only value (output instance %d)\n", mp->one_inst);
+ break;
+ case OUTPUT_AVG:
+ if (mp->old_desc.indom != PM_INDOM_NULL)
+ printf("average value (output instance %d)\n", mp->one_inst);
+ else
+ printf("average and only value (output instance %d)\n", mp->one_inst);
+ break;
+ }
+ }
+ }
+ if (mp->ip != NULL)
+ printf("Inst Changes:\t<- InDom %s\n", pmInDomStr(mp->ip->old_indom));
+ if (mp->flags & METRIC_CHANGE_SEM) {
+ printf("Semantics:\t%s ->", SemStr(mp->old_desc.sem));
+ printf(" %s\n", SemStr(mp->new_desc.sem));
+ }
+ if (mp->flags & METRIC_CHANGE_UNITS) {
+ printf("Units:\t\t%s ->", pmUnitsStr(&mp->old_desc.units));
+ printf(" %s", pmUnitsStr(&mp->new_desc.units));
+ if (mp->flags & METRIC_RESCALE)
+ printf(" (rescale)");
+ putchar('\n');
+ }
+ if (mp->flags & METRIC_DELETE)
+ printf("DELETE\n");
+ }
+ if (change == 0)
+ printf("No changes\n");
+}
+
+static int
+anychange(void)
+{
+ indomspec_t *ip;
+ metricspec_t *mp;
+ int i;
+
+ if (global.flags != 0)
+ return 1;
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (ip->new_indom != ip->old_indom)
+ return 1;
+ for (i = 0; i < ip->numinst; i++) {
+ if (ip->inst_flags[i])
+ return 1;
+ }
+ }
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (mp->flags != 0 || mp->ip != NULL)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+fixstamp(struct timeval *tvp)
+{
+ if (global.flags & GLOBAL_CHANGE_TIME) {
+ if (global.time.tv_sec > 0) {
+ tvp->tv_sec += global.time.tv_sec;
+ tvp->tv_usec += global.time.tv_usec;
+ if (tvp->tv_usec > 1000000) {
+ tvp->tv_sec++;
+ tvp->tv_usec -= 1000000;
+ }
+ return 1;
+ }
+ else if (global.time.tv_sec < 0) {
+ /* parser makes tv_sec < 0 and tv_usec >= 0 */
+ tvp->tv_sec += global.time.tv_sec;
+ tvp->tv_usec -= global.time.tv_usec;
+ if (tvp->tv_usec < 0) {
+ tvp->tv_sec--;
+ tvp->tv_usec += 1000000;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Link metricspec_t entries to corresponding indom_t entry if there
+ * are changes to instance identifiers or instance names (includes
+ * instance deletion)
+ */
+static void
+link_entries(void)
+{
+ indomspec_t *ip;
+ metricspec_t *mp;
+ __pmHashCtl *hcp;
+ __pmHashNode *node;
+ int i;
+ int change;
+
+ hcp = &inarch.ctxp->c_archctl->ac_log->l_hashpmid;
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ change = 0;
+ for (i = 0; i < ip->numinst; i++)
+ change |= (ip->inst_flags[i] != 0);
+ if (change == 0 && ip->new_indom == ip->old_indom)
+ continue;
+
+ for (node = __pmHashWalk(hcp, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) {
+ mp = start_metric((pmID)(node->key));
+ if (mp->old_desc.indom == ip->old_indom) {
+ if (change)
+ mp->ip = ip;
+ if (ip->new_indom != ip->old_indom) {
+ if (mp->flags & METRIC_CHANGE_INDOM) {
+ /* indom already changed via metric clause */
+ if (mp->new_desc.indom != ip->new_indom) {
+ char strbuf[80];
+ snprintf(strbuf, sizeof(strbuf), "%s", pmInDomStr(mp->new_desc.indom));
+ snprintf(mess, sizeof(mess), "Conflicting indom change for metric %s (%s from metric clause, %s from indom clause)", mp->old_name, strbuf, pmInDomStr(ip->new_indom));
+ yysemantic(mess);
+ }
+ }
+ else {
+ mp->flags |= METRIC_CHANGE_INDOM;
+ mp->new_desc.indom = ip->new_indom;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void
+check_indoms()
+{
+ /*
+ * For each metric, make sure the output instance domain will be in
+ * the output archive.
+ * Called after link_entries(), so if an input metric is associated
+ * with an instance domain that has any instance rewriting, we're OK.
+ * The case to be checked here is a rewritten metric with an indom
+ * clause and no associated indomspec_t (so no instance domain changes,
+ * but the new indom may not match any indom in the archive.
+ */
+ metricspec_t *mp;
+ indomspec_t *ip;
+ __pmHashCtl *hcp;
+ __pmHashNode *node;
+
+
+ hcp = &inarch.ctxp->c_archctl->ac_log->l_hashindom;
+
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (mp->ip != NULL)
+ /* associated indom has instance changes, we're OK */
+ continue;
+ if ((mp->flags & METRIC_CHANGE_INDOM) && mp->new_desc.indom != PM_INDOM_NULL) {
+ for (node = __pmHashWalk(hcp, PM_HASH_WALK_START);
+ node != NULL;
+ node = __pmHashWalk(hcp, PM_HASH_WALK_NEXT)) {
+ /*
+ * if this indom has an indomspec_t, check that, else
+ * this indom will go to the archive without change
+ */
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ if (ip->old_indom == mp->old_desc.indom)
+ break;
+ }
+ if (ip == NULL) {
+ if ((pmInDom)(node->key) == mp->new_desc.indom)
+ /* we're OK */
+ break;
+ }
+ else {
+ if (ip->new_indom != ip->old_indom &&
+ ip->new_indom == mp->new_desc.indom)
+ /* we're OK */
+ break;
+ }
+ }
+ if (node == NULL) {
+ snprintf(mess, sizeof(mess), "New indom (%s) for metric %s is not in the output archive", pmInDomStr(mp->new_desc.indom), mp->old_name);
+ yysemantic(mess);
+ }
+ }
+ }
+
+ /*
+ * For each modified instance domain, make sure instances are
+ * still unique and instance names are unique to the first
+ * space.
+ */
+ for (ip = indom_root; ip != NULL; ip = ip->i_next) {
+ int i;
+ for (i = 0; i < ip->numinst; i++) {
+ int insti;
+ char *namei;
+ int j;
+ if (ip->inst_flags[i] & INST_CHANGE_INST)
+ insti = ip->new_inst[i];
+ else
+ insti = ip->old_inst[i];
+ if (ip->inst_flags[i] & INST_CHANGE_INAME)
+ namei = ip->new_iname[i];
+ else
+ namei = ip->old_iname[i];
+ for (j = 0; j < ip->numinst; j++) {
+ int instj;
+ char *namej;
+ if (i == j)
+ continue;
+ if (ip->inst_flags[j] & INST_CHANGE_INST)
+ instj = ip->new_inst[j];
+ else
+ instj = ip->old_inst[j];
+ if (ip->inst_flags[j] & INST_CHANGE_INAME)
+ namej = ip->new_iname[j];
+ else
+ namej = ip->old_iname[j];
+ if (insti == instj) {
+ snprintf(mess, sizeof(mess), "Duplicate instance id %d (\"%s\" and \"%s\") for indom %s", insti, namei, namej, pmInDomStr(ip->old_indom));
+ yysemantic(mess);
+ }
+ if (inst_name_eq(namei, namej) > 0) {
+ snprintf(mess, sizeof(mess), "Duplicate instance name \"%s\" (%d) and \"%s\" (%d) for indom %s", namei, insti, namej, instj, pmInDomStr(ip->old_indom));
+ yysemantic(mess);
+ }
+ }
+ }
+ }
+}
+
+static void
+check_output()
+{
+ /*
+ * For each metric, if there is an INDOM clause, perform some
+ * additional semantic checks and perhaps a name -> instance id
+ * mapping.
+ *
+ * Note instance renumbering happens _after_ value selction from
+ * the INDOM -> ,,,, OUTPUT clause, so all references to
+ * instance names and instance ids are relative to the
+ * "old" set.
+ */
+ metricspec_t *mp;
+ indomspec_t *ip;
+
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if ((mp->flags & METRIC_CHANGE_INDOM)) {
+ if (mp->output == OUTPUT_ONE || mp->output == OUTPUT_FIRST) {
+ /*
+ * cases here are
+ * INAME "name"
+ * => one_name == "name" and one_inst == PM_IN_NULL
+ * INST id
+ * => one_name == NULL and one_inst = id
+ */
+ if (mp->old_desc.indom != PM_INDOM_NULL && mp->output == OUTPUT_ONE) {
+ /*
+ * old metric is not singular, so one_name and one_inst
+ * are used to pick the value
+ * also map one_name -> one_inst
+ */
+ int i;
+ ip = start_indom(mp->old_desc.indom);
+ for (i = 0; i < ip->numinst; i++) {
+ if (mp->one_name != NULL) {
+ if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) {
+ mp->one_name = NULL;
+ mp->one_inst = ip->old_inst[i];
+ break;
+ }
+ }
+ else if (ip->old_inst[i] == mp->one_inst)
+ break;
+ }
+ if (i == ip->numinst) {
+ if (wflag) {
+ if (mp->one_name != NULL)
+ snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in old indom %s", mp->one_name, pmInDomStr(mp->old_desc.indom));
+ else
+ snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in old indom %s", mp->one_inst, pmInDomStr(mp->old_desc.indom));
+ yywarn(mess);
+ }
+ }
+ }
+ if (mp->new_desc.indom != PM_INDOM_NULL) {
+ /*
+ * new metric is not singular, so one_inst should be
+ * found in the new instance domain ... ignore one_name
+ * other than to map one_name -> one_inst if one_inst
+ * is not already known
+ */
+ int i;
+ ip = start_indom(mp->new_desc.indom);
+ for (i = 0; i < ip->numinst; i++) {
+ if (mp->one_name != NULL) {
+ if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) {
+ mp->one_name = NULL;
+ mp->one_inst = ip->old_inst[i];
+ break;
+ }
+ }
+ else if (ip->old_inst[i] == mp->one_inst)
+ break;
+ }
+ if (i == ip->numinst) {
+ if (wflag) {
+ if (mp->one_name != NULL)
+ snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in new indom %s", mp->one_name, pmInDomStr(mp->new_desc.indom));
+ else
+ snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in new indom %s", mp->one_inst, pmInDomStr(mp->new_desc.indom));
+ yywarn(mess);
+ }
+ }
+ /*
+ * use default rule (id 0) if INAME not found and
+ * and instance id is needed for output value
+ */
+ if (mp->old_desc.indom == PM_INDOM_NULL && mp->one_inst == PM_IN_NULL)
+ mp->one_inst = 0;
+ }
+ }
+ }
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ int stslog; /* sts from nextlog() */
+ int stsmeta = 0; /* sts from nextmeta() */
+ int i;
+ int ti_idx; /* next slot for input temporal index */
+ int dir_fd = -1; /* poinless initialization to humour gcc */
+ int needti = 0;
+ int doneti = 0;
+ __pmTimeval tstamp = { 0 }; /* for last log record */
+ off_t old_log_offset = 0; /* log offset before last log record */
+ off_t old_meta_offset;
+
+ /* process cmd line args */
+ if (parseargs(argc, argv) < 0) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ /* input archive */
+ if (iflag == 0)
+ inarch.name = argv[argc-2];
+ else
+ inarch.name = argv[argc-1];
+ inarch.logrec = inarch.metarec = NULL;
+ inarch.mark = 0;
+ inarch.rp = NULL;
+
+ if ((inarch.ctx = pmNewContext(PM_CONTEXT_ARCHIVE, inarch.name)) < 0) {
+ fprintf(stderr, "%s: Error: cannot open archive \"%s\": %s\n",
+ pmProgname, inarch.name, pmErrStr(inarch.ctx));
+ exit(1);
+ }
+ inarch.ctxp = __pmHandleToPtr(inarch.ctx);
+ assert(inarch.ctxp != NULL);
+
+ if ((sts = pmGetArchiveLabel(&inarch.label)) < 0) {
+ fprintf(stderr, "%s: Error: cannot get archive label record (%s): %s\n",
+ pmProgname, inarch.name, pmErrStr(sts));
+ exit(1);
+ }
+
+ if ((inarch.label.ll_magic & 0xff) != PM_LOG_VERS02) {
+ fprintf(stderr,"%s: Error: illegal version number %d in archive (%s)\n",
+ pmProgname, inarch.label.ll_magic & 0xff, inarch.name);
+ exit(1);
+ }
+
+ /* output archive */
+ if (iflag && Cflag == 0) {
+ /*
+ * -i (in place) method outline
+ *
+ * + create one temporary base filename in the same directory is
+ * the input archive, keep a copy of this name this accessed
+ * via outarch.name
+ * + create a second (and different) temporary base file name
+ * in the same directory, keep this name in bak_base[]
+ * + close the temporary file descriptors and unlink the basename
+ * files
+ * + create the output as per normal in outarch.name
+ * + fsync() all the output files and the container directory
+ * + rename the _input_ archive files using the _second_ temporary
+ * basename
+ * + rename the output archive files to the basename of the input
+ * archive ... if this step fails for any reason, restore the
+ * original input files
+ * + unlink all the (old) input archive files
+ */
+ char path[MAXPATHLEN+1];
+ char dname[MAXPATHLEN+1];
+ mode_t cur_umask;
+ int tmp_f1; /* fd for first temp basename */
+ int tmp_f2; /* fd for second temp basename */
+
+#if HAVE_MKSTEMP
+ strncpy(path, argv[argc-1], sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ strncpy(dname, dirname(path), sizeof(dname));
+ dname[sizeof(dname)-1] = '\0';
+ if ((dir_fd = open(dname, O_RDONLY)) < 0) {
+ fprintf(stderr, "%s: Error: cannot open directory \"%s\" for reading: %s\n", pmProgname, dname, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ sprintf(path, "%s%cXXXXXX", dname, __pmPathSeparator());
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ tmp_f1 = mkstemp(path);
+ umask(cur_umask);
+ outarch.name = strdup(path);
+ if (outarch.name == NULL) {
+ fprintf(stderr, "%s: Error: temp file strdup(%s) failed: %s\n", pmProgname, path, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ sprintf(bak_base, "%s%cXXXXXX", dname, __pmPathSeparator());
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ tmp_f2 = mkstemp(bak_base);
+ umask(cur_umask);
+#else
+ char fname[MAXPATHLEN+1];
+ char *s;
+
+ strncpy(path, argv[argc-1], sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ strncpy(fname, basename(path), sizeof(fname));
+ fname[sizeof(fname)-1] = '\0';
+ strncpy(dname, dirname(path), sizeof(dname));
+ dname[sizeof(dname)-1] = '\0';
+
+ if ((s = tempnam(dname, fname)) == NULL) {
+ fprintf(stderr, "%s: Error: first tempnam() failed: %s\n", pmProgname, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ else {
+ outarch.name = strdup(s);
+ if (outarch.name == NULL) {
+ fprintf(stderr, "%s: Error: temp file strdup(%s) failed: %s\n", pmProgname, s, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ tmp_f1 = open(outarch.name, O_WRONLY|O_CREAT|O_EXCL, 0600);
+ umask(cur_umask);
+ }
+ if ((s = tempnam(dname, fname)) == NULL) {
+ fprintf(stderr, "%s: Error: second tempnam() failed: %s\n", pmProgname, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ else {
+ strcpy(bak_base, s);
+ cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
+ tmp_f2 = open(bak_base, O_WRONLY|O_CREAT|O_EXCL, 0600);
+ umask(cur_umask);
+ }
+#endif
+ if (tmp_f1 < 0) {
+ fprintf(stderr, "%s: Error: create first temp (%s) failed: %s\n", pmProgname, outarch.name, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (tmp_f2 < 0) {
+ fprintf(stderr, "%s: Error: create second temp (%s) failed: %s\n", pmProgname, bak_base, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ close(tmp_f1);
+ close(tmp_f2);
+ unlink(outarch.name);
+ unlink(bak_base);
+ }
+ else
+ outarch.name = argv[argc-1];
+
+ /*
+ * process config file(s)
+ */
+ for (i = 0; i < nconf; i++) {
+ parseconfig(conf[i]);
+ }
+
+ /*
+ * cross-specification dependencies and semantic checks once all
+ * config files have been processed
+ */
+ link_entries();
+ check_indoms();
+ check_output();
+
+ if (vflag)
+ reportconfig();
+
+ if (Cflag)
+ exit(0);
+
+ if (qflag && anychange() == 0)
+ exit(0);
+
+ /* create output log - must be done before writing label */
+ if ((sts = __pmLogCreate("", outarch.name, PM_LOG_VERS02, &outarch.logctl)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogCreate(%s): %s\n",
+ pmProgname, outarch.name, pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+
+ /* initialize and write label records */
+ newlabel();
+ outarch.logctl.l_state = PM_LOG_STATE_INIT;
+ writelabel(0);
+
+ first_datarec = 1;
+ ti_idx = 0;
+
+ /*
+ * loop
+ * - get next log record
+ * - write out new/changed meta data required by this log record
+ * - write out log
+ * - do ti update if necessary
+ */
+ while (1) {
+ static long in_offset; /* for -Dappl0 */
+
+ fflush(outarch.logctl.l_mdfp);
+ old_meta_offset = ftell(outarch.logctl.l_mdfp);
+ assert(old_meta_offset >= 0);
+
+ in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mfp);
+ stslog = nextlog();
+ if (stslog < 0) {
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Log: read EOF @ offset=%ld\n", in_offset);
+#endif
+ break;
+ }
+ if (stslog == 1) {
+ /* volume change */
+ if (inarch.ctxp->c_archctl->ac_log->l_curvol >= outarch.logctl.l_curvol+1)
+ /* track input volume numbering */
+ newvolume(inarch.ctxp->c_archctl->ac_log->l_curvol);
+ else
+ /*
+ * output archive volume number is ahead, probably because
+ * rewriting has forced an earlier volume change
+ */
+ newvolume(outarch.logctl.l_curvol+1);
+ }
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ struct timeval stamp;
+ fprintf(stderr, "Log: read ");
+ stamp.tv_sec = inarch.rp->timestamp.tv_sec;
+ stamp.tv_usec = inarch.rp->timestamp.tv_usec;
+ __pmPrintStamp(stderr, &stamp);
+ fprintf(stderr, " numpmid=%d @ offset=%ld\n", inarch.rp->numpmid, in_offset);
+ }
+#endif
+
+ if (ti_idx < inarch.ctxp->c_archctl->ac_log->l_numti) {
+ __pmLogTI *tip = &inarch.ctxp->c_archctl->ac_log->l_ti[ti_idx];
+ if (tip->ti_stamp.tv_sec == inarch.rp->timestamp.tv_sec &&
+ tip->ti_stamp.tv_usec == inarch.rp->timestamp.tv_usec) {
+ /*
+ * timestamp on input pmResult matches next temporal index
+ * entry for input archive ... make sure matching temporal
+ * index entry added to output archive
+ */
+ needti = 1;
+ ti_idx++;
+ }
+ }
+
+ /*
+ * optionally rewrite timestamp in pmResult for global time
+ * adjustment ... flows to output pmResult, indom entries in
+ * metadata, temporal index entries and label records
+ * */
+ fixstamp(&inarch.rp->timestamp);
+
+ /*
+ * process metadata until find an indom record with timestamp
+ * after the current log record, or a metric record for a pmid
+ * that is not in the current log record
+ */
+ for ( ; ; ) {
+ pmID pmid; /* pmid for TYPE_DESC */
+ pmInDom indom; /* indom for TYPE_INDOM */
+
+ if (stsmeta == 0) {
+ in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mdfp);
+ stsmeta = nextmeta();
+#if PCP_DEBUG
+ if (stsmeta < 0 && pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Metadata: read EOF @ offset=%ld\n", in_offset);
+#endif
+ }
+ if (stsmeta < 0) {
+ break;
+ }
+ if (stsmeta == TYPE_DESC) {
+ int i;
+ pmid = ntoh_pmID(inarch.metarec[2]);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Metadata: read PMID %s @ offset=%ld\n", pmIDStr(pmid), in_offset);
+#endif
+ /*
+ * if pmid not in next pmResult, we're done ...
+ */
+ for (i = 0; i < inarch.rp->numpmid; i++) {
+ if (pmid == inarch.rp->vset[i]->pmid)
+ break;
+ }
+ if (i == inarch.rp->numpmid)
+ break;
+ /*
+ * rewrite if needed, delete if needed else output
+ */
+ do_desc();
+ }
+ else if (stsmeta == TYPE_INDOM) {
+ struct timeval stamp;
+ __pmTimeval *tvp = (__pmTimeval *)&inarch.metarec[2];
+ indom = ntoh_pmInDom((unsigned int)inarch.metarec[4]);
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "Metadata: read InDom %s @ offset=%ld\n", pmInDomStr(indom), in_offset);
+#endif
+ stamp.tv_sec = ntohl(tvp->tv_sec);
+ stamp.tv_usec = ntohl(tvp->tv_usec);
+ if (fixstamp(&stamp)) {
+ /* global time adjustment specified */
+ tvp->tv_sec = htonl(stamp.tv_sec);
+ tvp->tv_usec = htonl(stamp.tv_usec);
+ }
+ /* if time of indom > next pmResult stop processing metadata */
+ if (stamp.tv_sec > inarch.rp->timestamp.tv_sec)
+ break;
+ if (stamp.tv_sec == inarch.rp->timestamp.tv_sec &&
+ stamp.tv_usec > inarch.rp->timestamp.tv_usec)
+ break;
+ needti = 1;
+ do_indom();
+ }
+ else {
+ fprintf(stderr, "%s: Error: unrecognised meta data type: %d\n",
+ pmProgname, stsmeta);
+ abandon();
+ /*NOTREACHED*/
+ }
+ free(inarch.metarec);
+ stsmeta = 0;
+ }
+
+ if (first_datarec) {
+ first_datarec = 0;
+ /* any global time adjustment done after nextlog() above */
+ outarch.logctl.l_label.ill_start.tv_sec = inarch.rp->timestamp.tv_sec;
+ outarch.logctl.l_label.ill_start.tv_usec = inarch.rp->timestamp.tv_usec;
+ /* need to fix start-time in label records */
+ writelabel(1);
+ needti = 1;
+ }
+
+ tstamp.tv_sec = inarch.rp->timestamp.tv_sec;
+ tstamp.tv_usec = inarch.rp->timestamp.tv_usec;
+
+ if (needti) {
+ fflush(outarch.logctl.l_mdfp);
+ fflush(outarch.logctl.l_mfp);
+ new_meta_offset = ftell(outarch.logctl.l_mdfp);
+ assert(new_meta_offset >= 0);
+ fseek(outarch.logctl.l_mdfp, (long)old_meta_offset, SEEK_SET);
+ __pmLogPutIndex(&outarch.logctl, &tstamp);
+ fseek(outarch.logctl.l_mdfp, (long)new_meta_offset, SEEK_SET);
+ needti = 0;
+ doneti = 1;
+ }
+ else
+ doneti = 0;
+
+ old_log_offset = ftell(outarch.logctl.l_mfp);
+ assert(old_log_offset >= 0);
+
+ if (inarch.rp->numpmid == 0)
+ /* mark record, need index entry @ next log record */
+ needti = 1;
+
+ do_result();
+ }
+
+ if (!doneti) {
+ /* Final temporal index entry */
+ fflush(outarch.logctl.l_mfp);
+ fseek(outarch.logctl.l_mfp, (long)old_log_offset, SEEK_SET);
+ __pmLogPutIndex(&outarch.logctl, &tstamp);
+ }
+
+ if (iflag) {
+ /*
+ * fsync() to make sure new archive is safe before we start
+ * renaming ...
+ */
+ if (fsync(fileno(outarch.logctl.l_mdfp)) < 0) {
+ fprintf(stderr, "%s: Error: fsync(%d) failed for output metadata file: %s\n",
+ pmProgname, fileno(outarch.logctl.l_mdfp), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (fsync(fileno(outarch.logctl.l_mfp)) < 0) {
+ fprintf(stderr, "%s: Error: fsync(%d) failed for output data file: %s\n",
+ pmProgname, fileno(outarch.logctl.l_mfp), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (fsync(fileno(outarch.logctl.l_tifp)) < 0) {
+ fprintf(stderr, "%s: Error: fsync(%d) failed for output index file: %s\n",
+ pmProgname, fileno(outarch.logctl.l_tifp), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (fsync(dir_fd) < 0) {
+ fprintf(stderr, "%s: Error: fsync(%d) failed for output directory: %s\n",
+ pmProgname, dir_fd, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ close(dir_fd);
+ if (_pmLogRename(inarch.name, bak_base) < 0) {
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (_pmLogRename(outarch.name, inarch.name) < 0) {
+ abandon();
+ /*NOTREACHED*/
+ }
+ _pmLogRemove(bak_base);
+ }
+
+ exit(0);
+}
+
+void
+abandon(void)
+{
+ char path[MAXNAMELEN+1];
+ if (dflag == 0) {
+ if (Cflag == 0 && iflag == 0)
+ fprintf(stderr, "Archive \"%s\" not created.\n", outarch.name);
+
+ _pmLogRemove(outarch.name);
+ if (iflag)
+ _pmLogRename(bak_base, inarch.name);
+ while (outarch.logctl.l_curvol >= 0) {
+ snprintf(path, sizeof(path), "%s.%d", outarch.name, outarch.logctl.l_curvol);
+ unlink(path);
+ outarch.logctl.l_curvol--;
+ }
+ snprintf(path, sizeof(path), "%s.meta", outarch.name);
+ unlink(path);
+ snprintf(path, sizeof(path), "%s.index", outarch.name);
+ unlink(path);
+ }
+ else
+ fprintf(stderr, "Archive \"%s\" creation truncated.\n", outarch.name);
+
+ exit(1);
+}
diff --git a/src/pmlogrewrite/result.c b/src/pmlogrewrite/result.c
new file mode 100644
index 0000000..715b9d5
--- /dev/null
+++ b/src/pmlogrewrite/result.c
@@ -0,0 +1,730 @@
+/*
+ * pmResult rewrite methods for pmlogrewrite
+ *
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * pmResult rewriting is complicated because ...
+ *
+ * - deleting a metric involves moving all of the following rp->vset[]
+ * entries "up" one position and decrementing rp->numpmid
+ * - deleting an instance involves moving all of the following
+ * rp->vset[i]->vlist[] enties "up" one position and then decrementing
+ * rp->vset[i]->numval
+ * - rescaling values involves calling __pmStuffValue which modifies
+ * rp->vset[i]->vlist[j].value, and in the case of all types other than
+ * U32 or 32 this will involve an allocation for a new pmValueBlock
+ * ... we need to keep track of the previous pmValueBlock (if any) to
+ * avoid memory leaks
+ * - changing type has the same implications as rescaling
+ * - a single metric within a pmResult may have both rescaling and type
+ * change
+ * - the initial pmResult contains pointers into a PDU buffer so the fast
+ * track case in pmFreeresult() may not release any of the pmValueSet
+ * or pmValue or pmValueBlock allocations if pmFreeResult is given a
+ * rewritten pmResult, so we modify the pmResult in place but use save[]
+ * to remember the original pmValueSet when retyping or rescaling, use
+ * orig_numval[] to remember how many pmValue instances we had had for
+ * each pmValueSet, and use orig_numpmid to remember the original numpmid
+ * value
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <assert.h>
+
+/*
+ * Keep track of pmValueSets that have been moved aside to allow for
+ * new values from __pmStuffValue() during rewriting.
+ */
+static pmValueSet **save = NULL;
+static int len_save = 0;
+
+/*
+ * Save rp->vset[idx] in save[idx], and build a new rp->vset[idx]
+ * for the number of values expected for this metric
+ */
+static int
+save_vset(pmResult *rp, int idx)
+{
+ pmValueSet *vsp;
+ int need;
+ int j;
+
+ if (save[idx] != NULL)
+ /* already done */
+ return 1;
+
+ vsp = save[idx] = rp->vset[idx];
+
+ if (vsp->numval > 0)
+ need = sizeof(pmValueSet) + (vsp->numval-1)*sizeof(pmValue);
+ else
+ need = sizeof(pmValueSet);
+ rp->vset[idx] = (pmValueSet *)malloc(need);
+ if (rp->vset[idx] == NULL) {
+ fprintf(stderr, "save_vset: malloc(%d) failed: %s\n", need, strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ rp->vset[idx]->pmid = vsp->pmid;
+ rp->vset[idx]->numval = vsp->numval;
+ rp->vset[idx]->valfmt = vsp->valfmt;
+ for (j = 0; j < vsp->numval; j++)
+ rp->vset[idx]->vlist[j].inst = vsp->vlist[j].inst;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "save_vset: vset[%d] -> " PRINTF_P_PFX "%p (was " PRINTF_P_PFX "%p) pmid=%s numval=%d\n",
+ idx, rp->vset[idx], save[idx], pmIDStr(rp->vset[idx]->pmid), rp->vset[idx]->numval);
+ }
+#endif
+ return 0;
+}
+
+/*
+ * free the pval for the jth instance of the ith metric
+ */
+static void
+free_pval(pmResult *rp, int i, int j)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "free_pval: free(" PRINTF_P_PFX "%p) pmid=%s inst=%d\n",
+ rp->vset[i]->vlist[j].value.pval, pmIDStr(rp->vset[i]->pmid), rp->vset[i]->vlist[j].inst);
+ }
+#endif
+ free(rp->vset[i]->vlist[j].value.pval);
+}
+
+/*
+ * if a pmValueSet was saved via save_vset(), then free the newly build
+ * pmValueSet and put the old one back in place
+ */
+static void
+clean_vset(pmResult *rp)
+{
+ int i;
+ int j;
+
+ for (i = 0; i < rp->numpmid; i++) {
+ if (save[i] != NULL) {
+ if (rp->vset[i]->valfmt == PM_VAL_DPTR) {
+ /* values hanging off the pval */
+ for (j = 0; j < rp->vset[i]->numval; j++)
+ free_pval(rp, i, j);
+ }
+ /*
+ * we did the vset[i] allocation in save_vset(), so malloc()
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "clean_vset: free(" PRINTF_P_PFX "%p) pmValueSet pmid=%s\n",
+ rp->vset[i], pmIDStr(rp->vset[i]->pmid));
+ }
+#endif
+ free(rp->vset[i]);
+ rp->vset[i] = save[i];
+ save[i] = NULL;
+ }
+ }
+}
+
+/*
+ * pick/calculate one value from multiple values for the ith vset[] ...
+ *
+ * Note: pval->vbuf may not have correct alignment for all data types,
+ * so memcpy() rather than assign.
+ */
+static int
+pick_val(int i, metricspec_t *mp)
+{
+ int j;
+ int pick = -1;
+ pmAtomValue jval;
+ pmAtomValue pickval;
+
+ assert(inarch.rp->vset[i]->numval > 0);
+
+ for (j = 0; j < inarch.rp->vset[i]->numval; j++) {
+ if (mp->output == OUTPUT_ONE) {
+ if (inarch.rp->vset[i]->vlist[j].inst == mp->one_inst) {
+ pick = j;
+ break;
+ }
+ continue;
+ }
+ if (j == 0) {
+ pick = 0;
+ switch (mp->old_desc.type) {
+ case PM_TYPE_64:
+ memcpy(&pickval.ll, &inarch.rp->vset[i]->vlist[0].value.pval->vbuf, sizeof(__int64_t));
+ break;
+ case PM_TYPE_U64:
+ memcpy(&pickval.ull, &inarch.rp->vset[i]->vlist[0].value.pval->vbuf, sizeof(__uint64_t));
+ break;
+ case PM_TYPE_FLOAT:
+ memcpy(&pickval.f, &inarch.rp->vset[i]->vlist[0].value.pval->vbuf, sizeof(float));
+ break;
+ case PM_TYPE_DOUBLE:
+ memcpy(&pickval.d, &inarch.rp->vset[i]->vlist[0].value.pval->vbuf, sizeof(double));
+ break;
+ }
+ if (mp->output == OUTPUT_MIN || mp->output == OUTPUT_MAX ||
+ mp->output == OUTPUT_SUM || mp->output == OUTPUT_AVG)
+ continue;
+ }
+ switch (mp->old_desc.type) {
+ case PM_TYPE_32:
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if (inarch.rp->vset[i]->vlist[j].value.lval < inarch.rp->vset[i]->vlist[pick].value.lval)
+ pick = j;
+ break;
+ case OUTPUT_MAX:
+ if (inarch.rp->vset[i]->vlist[j].value.lval > inarch.rp->vset[i]->vlist[pick].value.lval)
+ pick = j;
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ inarch.rp->vset[i]->vlist[0].value.lval += inarch.rp->vset[i]->vlist[j].value.lval;
+ break;
+ }
+ break;
+ case PM_TYPE_U32:
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if ((__uint32_t)inarch.rp->vset[i]->vlist[j].value.lval < (__uint32_t)inarch.rp->vset[i]->vlist[pick].value.lval)
+ pick = j;
+ break;
+ case OUTPUT_MAX:
+ if ((__uint32_t)inarch.rp->vset[i]->vlist[j].value.lval > (__uint32_t)inarch.rp->vset[i]->vlist[pick].value.lval)
+ pick = j;
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ *(__uint32_t *)&inarch.rp->vset[i]->vlist[0].value.lval += (__uint32_t)inarch.rp->vset[i]->vlist[j].value.lval;
+ break;
+ }
+ break;
+ case PM_TYPE_64:
+ memcpy(&jval.ll, &inarch.rp->vset[i]->vlist[j].value.pval->vbuf, sizeof(__int64_t));
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if (jval.ll < pickval.ll) {
+ pickval.ll = jval.ll;
+ pick = j;
+ }
+ break;
+ case OUTPUT_MAX:
+ if (jval.ll > pickval.ll) {
+ pickval.ll = jval.ll;
+ pick = j;
+ }
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ pickval.ll += jval.ll;
+ break;
+ }
+ break;
+ case PM_TYPE_U64:
+ memcpy(&jval.ull, &inarch.rp->vset[i]->vlist[j].value.pval->vbuf, sizeof(__int64_t));
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if (jval.ull < pickval.ull) {
+ pickval.ull = jval.ull;
+ pick = j;
+ }
+ break;
+ case OUTPUT_MAX:
+ if (jval.ull > pickval.ull) {
+ pickval.ull = jval.ull;
+ pick = j;
+ }
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ pickval.ull += jval.ull;
+ break;
+ }
+ break;
+ case PM_TYPE_FLOAT:
+ memcpy(&jval.f, &inarch.rp->vset[i]->vlist[j].value.pval->vbuf, sizeof(float));
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if (jval.f < pickval.f) {
+ pickval.f = jval.f;
+ pick = j;
+ }
+ break;
+ case OUTPUT_MAX:
+ if (jval.f > pickval.f) {
+ pickval.f = jval.f;
+ pick = j;
+ }
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ pickval.f += jval.f;
+ break;
+ }
+ break;
+ case PM_TYPE_DOUBLE:
+ memcpy(&jval.d, &inarch.rp->vset[i]->vlist[j].value.pval->vbuf, sizeof(double));
+ switch (mp->output) {
+ case OUTPUT_MIN:
+ if (jval.d < pickval.d) {
+ pickval.d = jval.d;
+ pick = j;
+ }
+ break;
+ case OUTPUT_MAX:
+ if (jval.d > pickval.d) {
+ pickval.d = jval.d;
+ pick = j;
+ }
+ break;
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ pickval.d += jval.d;
+ break;
+ }
+ break;
+ }
+ }
+
+ if (mp->output == OUTPUT_AVG) {
+ switch (mp->old_desc.type) {
+ case PM_TYPE_32:
+ inarch.rp->vset[i]->vlist[0].value.lval = (int)(0.5 + inarch.rp->vset[i]->vlist[0].value.lval / (double)inarch.rp->vset[i]->numval);
+ break;
+ case PM_TYPE_U32:
+ *(__uint32_t *)&inarch.rp->vset[i]->vlist[0].value.lval = (__uint32_t)(0.5 + *(__uint32_t *)&inarch.rp->vset[i]->vlist[0].value.lval / (double)inarch.rp->vset[i]->numval);
+ break;
+ case PM_TYPE_64:
+ pickval.ll = 0.5 + pickval.ll / (double)inarch.rp->vset[i]->numval;
+ break;
+ case PM_TYPE_U64:
+ pickval.ull = 0.5 + pickval.ull / (double)inarch.rp->vset[i]->numval;
+ break;
+ case PM_TYPE_FLOAT:
+ pickval.f = pickval.f / (float)inarch.rp->vset[i]->numval;
+ break;
+ case PM_TYPE_DOUBLE:
+ pickval.d = pickval.d / (double)inarch.rp->vset[i]->numval;
+ break;
+ }
+ }
+ if (mp->output == OUTPUT_AVG || mp->output == OUTPUT_SUM) {
+ switch (mp->old_desc.type) {
+ case PM_TYPE_64:
+ memcpy(&inarch.rp->vset[i]->vlist[0].value.pval->vbuf, &pickval.ll, sizeof(__int64_t));
+ break;
+ case PM_TYPE_U64:
+ memcpy(&inarch.rp->vset[i]->vlist[0].value.pval->vbuf, &pickval.ull, sizeof(__uint64_t));
+ break;
+ case PM_TYPE_FLOAT:
+ memcpy(&inarch.rp->vset[i]->vlist[0].value.pval->vbuf, &pickval.f, sizeof(float));
+ break;
+ case PM_TYPE_DOUBLE:
+ memcpy(&inarch.rp->vset[i]->vlist[0].value.pval->vbuf, &pickval.d, sizeof(double));
+ break;
+ }
+ }
+
+ return pick;
+}
+
+/*
+ * rescale values for the ith vset[]
+ */
+static void
+rescale(int i, metricspec_t *mp)
+{
+ int sts;
+ int j;
+ pmAtomValue ival;
+ pmAtomValue oval;
+ int old_valfmt = inarch.rp->vset[i]->valfmt;
+ int already_saved;
+ pmValueSet *vsp;
+
+ sts = old_valfmt;
+ already_saved = save_vset(inarch.rp, i);
+ if (already_saved)
+ vsp = inarch.rp->vset[i];
+ else
+ vsp = save[i];
+ for (j = 0; j < inarch.rp->vset[i]->numval; j++) {
+ sts = pmExtractValue(old_valfmt, &vsp->vlist[j], mp->old_desc.type, &ival, mp->old_desc.type);
+ if (sts < 0) {
+ /*
+ * No type conversion here, so error not expected
+ */
+ fprintf(stderr, "%s: Botch: %s (%s): extracting value: %s\n",
+ pmProgname, mp->old_name, pmIDStr(mp->old_desc.pmid), pmErrStr(sts));
+ inarch.rp->vset[i]->numval = j;
+ __pmDumpResult(stderr, inarch.rp);
+ abandon();
+ /*NOTREACHED*/
+ }
+ sts = pmConvScale(mp->old_desc.type, &ival, &mp->old_desc.units, &oval, &mp->new_desc.units);
+ if (sts < 0) {
+ /*
+ * unless the "units" are bad (and the parser is supposed to
+ * make sure this does not happen) we do not expect errors
+ * from pmConvScale()
+ */
+ fprintf(stderr, "%s: Botch: %s (%s): scale conversion from %s",
+ pmProgname, mp->old_name, pmIDStr(mp->old_desc.pmid), pmUnitsStr(&mp->old_desc.units));
+ fprintf(stderr, " to %s failed: %s\n", pmUnitsStr(&mp->new_desc.units), pmErrStr(sts));
+ inarch.rp->vset[i]->numval = j;
+ __pmDumpResult(stderr, inarch.rp);
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (already_saved && old_valfmt == PM_VAL_DPTR) {
+ /*
+ * current value uses pval that is from a previous call to
+ * __pmStuffValue() during rewriting, not a pointer into a
+ * PDU buffer
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "rescale free(" PRINTF_P_PFX "%p) pval pmid=%s inst=%d\n",
+ inarch.rp->vset[i]->vlist[j].value.pval,
+ pmIDStr(inarch.rp->vset[i]->pmid),
+ inarch.rp->vset[i]->vlist[j].inst);
+ }
+#endif
+ free(inarch.rp->vset[i]->vlist[j].value.pval);
+ }
+ sts = __pmStuffValue(&oval, &inarch.rp->vset[i]->vlist[j], mp->old_desc.type);
+ if (sts < 0) {
+ /*
+ * unless "type" is bad (which the parser is supposed to
+ * prevent) or malloc() failed, we do not expect errors from
+ * __pmStuffValue()
+ */
+ fprintf(stderr, "%s: Botch: %s (%s): stuffing value %s (type=%s) into rewritten pmResult: %s\n",
+ pmProgname, mp->old_name, pmIDStr(mp->old_desc.pmid), pmAtomStr(&oval, mp->old_desc.type), pmTypeStr(mp->old_desc.type), pmErrStr(sts));
+ inarch.rp->vset[i]->numval = j;
+ __pmDumpResult(stderr, inarch.rp);
+ abandon();
+ /*NOTREACHED*/
+ }
+ }
+ inarch.rp->vset[i]->valfmt = sts;
+}
+
+/*
+ * change type of values for the ith vset[] ... real chance that this will
+ * fail, as some failure modes depend on the sign or size of the data
+ * values found in the pmResult
+ */
+static void
+retype(int i, metricspec_t *mp)
+{
+ int sts;
+ int j;
+ pmAtomValue val;
+ int old_valfmt = inarch.rp->vset[i]->valfmt;
+ int already_saved;
+ pmValueSet *vsp;
+
+ sts = old_valfmt;
+ already_saved = save_vset(inarch.rp, i);
+ if (already_saved)
+ vsp = inarch.rp->vset[i];
+ else
+ vsp = save[i];
+ for (j = 0; j < inarch.rp->vset[i]->numval; j++) {
+ sts = pmExtractValue(old_valfmt, &vsp->vlist[j], mp->old_desc.type, &val, mp->new_desc.type);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Error: %s (%s): extracting value from type %s",
+ pmProgname, mp->old_name, pmIDStr(mp->old_desc.pmid), pmTypeStr(mp->old_desc.type));
+ fprintf(stderr, " to %s: %s\n", pmTypeStr(mp->new_desc.type), pmErrStr(sts));
+ inarch.rp->vset[i]->numval = j;
+ __pmDumpResult(stderr, inarch.rp);
+ abandon();
+ /*NOTREACHED*/
+ }
+ if (already_saved && old_valfmt == PM_VAL_DPTR) {
+ /*
+ * current value uses pval that is from a previous call to
+ * __pmStuffValue() during rewriting, not a pointer into a
+ * PDU buffer
+ */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ fprintf(stderr, "retype free(" PRINTF_P_PFX "%p) pval pmid=%s inst=%d\n",
+ inarch.rp->vset[i]->vlist[j].value.pval,
+ pmIDStr(inarch.rp->vset[i]->pmid),
+ inarch.rp->vset[i]->vlist[j].inst);
+ }
+#endif
+ free(inarch.rp->vset[i]->vlist[j].value.pval);
+ }
+ sts = __pmStuffValue(&val, &inarch.rp->vset[i]->vlist[j], mp->new_desc.type);
+ if (sts < 0) {
+ /*
+ * unless "type" is bad (which the parser is supposed to
+ * prevent) or malloc() failed, we do not expect errors from
+ * __pmStuffValue()
+ */
+ fprintf(stderr, "%s: Botch: %s (%s): stuffing value %s (type=%s) into rewritten pmResult: %s\n",
+ pmProgname, mp->old_name, pmIDStr(mp->old_desc.pmid), pmAtomStr(&val, mp->new_desc.type), pmTypeStr(mp->new_desc.type), pmErrStr(sts));
+ inarch.rp->vset[i]->numval = j;
+ __pmDumpResult(stderr, inarch.rp);
+ abandon();
+ /*NOTREACHED*/
+ }
+ }
+ inarch.rp->vset[i]->valfmt = sts;
+}
+
+void
+do_result(void)
+{
+ metricspec_t *mp;
+ int i;
+ int j;
+ int sts;
+ int orig_numpmid;
+ int *orig_numval = NULL;
+
+ orig_numpmid = inarch.rp->numpmid;
+
+ if (inarch.rp->numpmid > len_save) {
+ /* expand save[] */
+ save = (pmValueSet **)realloc(save, inarch.rp->numpmid * sizeof(save[0]));
+ if (save == NULL) {
+ fprintf(stderr, "save_vset: save realloc(...,%d) failed: %s\n", (int)(inarch.rp->numpmid * sizeof(save[0])), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ for (i = len_save; i < inarch.rp->numpmid; i++)
+ save[i] = NULL;
+ len_save = inarch.rp->numpmid;
+ }
+ orig_numval = (int *)malloc(orig_numpmid * sizeof(int));
+ if (orig_numval == NULL) {
+ fprintf(stderr, "orig_numval malloc(%d) failed: %s\n", (int)(orig_numpmid * sizeof(int)), strerror(errno));
+ abandon();
+ /*NOTREACHED*/
+ }
+ for (i = 0; i < orig_numpmid; i++)
+ orig_numval[i] = inarch.rp->vset[i]->numval;
+
+ for (i = 0; i < inarch.rp->numpmid; i++) {
+ for (mp = metric_root; mp != NULL; mp = mp->m_next) {
+ if (inarch.rp->vset[i]->pmid != mp->old_desc.pmid)
+ continue;
+ if (mp->flags == 0 && mp->ip == NULL)
+ break;
+ if (mp->flags & METRIC_DELETE) {
+ /* move vset[i] to end of list, shuffle lower ones up */
+ pmValueSet *vsp = inarch.rp->vset[i];
+ pmValueSet *save_vsp = save[i];
+ int save_numval;
+ save_numval = orig_numval[i];
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Delete: vset[%d] for %s\n", i, pmIDStr(inarch.rp->vset[i]->pmid));
+#endif
+ for (j = i+1; j < inarch.rp->numpmid; j++) {
+ inarch.rp->vset[j-1] = inarch.rp->vset[j];
+ save[j-1] = save[j];
+ orig_numval[j-1] = orig_numval[j];
+ }
+ inarch.rp->vset[j-1] = vsp;
+ save[j-1] = save_vsp;
+ orig_numval[j-1] = save_numval;
+ /* one less metric to write out, process vset[i] again */
+ inarch.rp->numpmid--;
+ i--;
+ break;
+ }
+#if PCP_DEBUG
+ /*
+ * mflags that will not force any pmResult rewrite ...
+ * METRIC_CHANGE_NAME
+ * METRIC_CHANGE_SEM
+ */
+ if (pmDebug & DBG_TRACE_APPL1)
+ fprintf(stderr, "Rewrite: vset[%d] for %s\n", i, pmIDStr(inarch.rp->vset[i]->pmid));
+
+#endif
+ if (mp->flags & METRIC_CHANGE_PMID)
+ inarch.rp->vset[i]->pmid = mp->new_desc.pmid;
+ if ((mp->flags & METRIC_CHANGE_INDOM) && inarch.rp->vset[i]->numval > 0) {
+ if (mp->output != OUTPUT_ALL) {
+ /*
+ * Output only one value ...
+ * Some instance selection to be done for the following
+ * indom cases:
+ * non-NULL -> NULL
+ * pick one input value, singular output
+ * NULL -> non-NULL
+ * only one input value, magic-up instance id for
+ * output value from mp->one_inst
+ * non-NULL -> non-NULL
+ * pick one input value base in mp->one_inst,
+ * copy to output
+ */
+ int pick = 0;
+ switch (mp->output) {
+ case OUTPUT_FIRST:
+ break;
+ case OUTPUT_LAST:
+ pick = inarch.rp->vset[i]->numval-1;
+ break;
+ case OUTPUT_ONE:
+ case OUTPUT_MIN:
+ case OUTPUT_MAX:
+ case OUTPUT_SUM:
+ case OUTPUT_AVG:
+ pick = pick_val(i, mp);
+ break;
+ }
+ if (pick >= 0) {
+ if (pick > 0) {
+ /* swap vlist[0] and vlist[pick] */
+ pmValue save;
+ save = inarch.rp->vset[i]->vlist[0];
+ inarch.rp->vset[i]->vlist[0] = inarch.rp->vset[i]->vlist[pick];
+ inarch.rp->vset[i]->vlist[0].inst = inarch.rp->vset[i]->vlist[pick].inst;
+ inarch.rp->vset[i]->vlist[pick] = save;
+ }
+ if (mp->new_desc.indom == PM_INDOM_NULL)
+ inarch.rp->vset[i]->vlist[0].inst = PM_IN_NULL;
+ else if (mp->old_desc.indom == PM_INDOM_NULL)
+ inarch.rp->vset[i]->vlist[0].inst = mp->one_inst;
+ inarch.rp->vset[i]->numval = 1;
+ }
+ else
+ inarch.rp->vset[i]->numval = 0;
+ }
+ }
+ /*
+ * order below is deliberate ...
+ * - cull/renumber instances if needed
+ * - rescale if needed
+ * - fix type if needed
+ */
+ if (mp->ip != NULL) {
+ /* rewrite/delete instance ids from the indom map */
+ int k;
+ for (k = 0; k < mp->ip->numinst; k++) {
+ if (mp->ip->inst_flags[k] & INST_CHANGE_INST) {
+ for (j = 0; j < inarch.rp->vset[i]->numval; j++) {
+ if (inarch.rp->vset[i]->vlist[j].inst == mp->ip->old_inst[k]) {
+ inarch.rp->vset[i]->vlist[j].inst = mp->ip->new_inst[k];
+ }
+ }
+ }
+ if (mp->ip->inst_flags[k] & INST_DELETE) {
+ for (j = 0; j < inarch.rp->vset[i]->numval; j++) {
+ if (inarch.rp->vset[i]->vlist[j].inst == mp->ip->old_inst[k]) {
+ j++;
+ while (j < inarch.rp->vset[i]->numval) {
+ inarch.rp->vset[i]->vlist[j-1] = inarch.rp->vset[i]->vlist[j];
+ j++;
+ }
+ if (save[i] != NULL &&
+ inarch.rp->vset[i]->valfmt == PM_VAL_DPTR) {
+ /*
+ * messy case ... last instance pval is
+ * from calling __pmStuffValue() in
+ * rewriting not a pointer into the PDU
+ * buffer, so free here because
+ * clean_vset() won't find it
+ */
+ free_pval(inarch.rp, i, j-1);
+ }
+ inarch.rp->vset[i]->numval--;
+ }
+ }
+ }
+ }
+ }
+ if (mp->flags & METRIC_RESCALE) {
+ /*
+ * parser already checked that dimension is unchanged,
+ * scale is different and -s on command line or RESCALE
+ * in UNITS clause of metricspec => rescale values
+ */
+ rescale(i, mp);
+ }
+ if (mp->flags & METRIC_CHANGE_TYPE)
+ retype(i, mp);
+ break;
+ }
+ }
+
+ /*
+ * only output numpmid == 0 case if input was a mark record
+ */
+ if (orig_numpmid == 0 || inarch.rp->numpmid > 0) {
+ unsigned long out_offset;
+ unsigned long peek_offset;
+ peek_offset = ftell(outarch.logctl.l_mfp);
+ sts = __pmEncodeResult(PDU_OVERRIDE2, inarch.rp, &inarch.logrec);
+ if (sts < 0) {
+ fprintf(stderr, "%s: Error: __pmEncodeResult: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+ peek_offset += ((__pmPDUHdr *)inarch.logrec)->len - sizeof(__pmPDUHdr) + 2*sizeof(int);
+ if (peek_offset > 0x7fffffff) {
+ /*
+ * data file size will exceed 2^31-1 bytes, so force
+ * volume switch
+ */
+ newvolume(outarch.logctl.l_curvol+1);
+ }
+ out_offset = ftell(outarch.logctl.l_mfp);
+ if ((sts = __pmLogPutResult2(&outarch.logctl, inarch.logrec)) < 0) {
+ fprintf(stderr, "%s: Error: __pmLogPutResult2: log data: %s\n",
+ pmProgname, pmErrStr(sts));
+ abandon();
+ /*NOTREACHED*/
+ }
+ /* do not free inarch.logrec ... this is a libpcp PDU buffer */
+#if PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ struct timeval stamp;
+ fprintf(stderr, "Log: write ");
+ stamp.tv_sec = inarch.rp->timestamp.tv_sec;
+ stamp.tv_usec = inarch.rp->timestamp.tv_usec;
+ __pmPrintStamp(stderr, &stamp);
+ fprintf(stderr, " numpmid=%d @ offset=%ld\n", inarch.rp->numpmid, out_offset);
+ }
+#endif
+ }
+
+ /* restore numpmid up so all vset[]s are freed */
+ inarch.rp->numpmid = orig_numpmid;
+ /*
+ * put pmResult back the way it was (so pmFreeResult works correctly)
+ * and release any allocated memory used in the rewriting
+ */
+ clean_vset(inarch.rp);
+ /* restore numval up so all vlist[]s are freed */
+ for (i = 0; i < orig_numpmid; i++)
+ inarch.rp->vset[i]->numval = orig_numval[i];
+ free(orig_numval);
+
+ pmFreeResult(inarch.rp);
+}
diff --git a/src/pmlogrewrite/util.c b/src/pmlogrewrite/util.c
new file mode 100644
index 0000000..360ac10
--- /dev/null
+++ b/src/pmlogrewrite/util.c
@@ -0,0 +1,302 @@
+/*
+ * Utiility routines for pmlogrewrite
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2011 Ken McDonell. All Rights Reserved.
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "logger.h"
+#include <assert.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+void
+yywarn(char *s)
+{
+ fprintf(stderr, "Warning [%s, line %d]\n%s\n", configfile, lineno, s);
+}
+
+void
+yyerror(char *s)
+{
+ fprintf(stderr, "Specification error in configuration file (%s)\n",
+ configfile);
+ fprintf(stderr, "[line %d] %s\n", lineno, s);
+ exit(1);
+}
+
+void
+yysemantic(char *s)
+{
+ fprintf(stderr, "Semantic error in configuration file (%s)\n",
+ configfile);
+ fprintf(stderr, "%s\n", s);
+ exit(1);
+}
+
+/*
+ * instance name matching ... return
+ * 0 for no match
+ * 1 for match to first space
+ * 2 for complete match
+ * -1 if either name is empty or NULL
+ */
+int
+inst_name_eq(const char *p, const char *q)
+{
+ if (p == NULL || *p == '\0')
+ return -1;
+ if (q == NULL || *q == '\0')
+ return -1;
+
+ for ( ; ; p++, q++) {
+ if (*p == '\0' && *q == '\0')
+ return 2;
+ if (*p == '\0' || *p == ' ') {
+ if (*q == '\0' || *q == ' ')
+ return 1;
+ break;
+ }
+ if (*q == '\0' || *q == ' ') {
+ if (*p == '\0' || *p == ' ')
+ return 1;
+ break;
+ }
+ if (*p != *q)
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Rename all the physical archive files with basename of old to
+ * a basename of new.
+ *
+ * If _any_ error occurs, don't make any changes.
+ *
+ * Note: does not handle compressed versions of files.
+ *
+ * TODO - need global locking for PCP 3.6 version if this is promoted
+ * to libpcp
+ */
+int
+_pmLogRename(const char *old, const char *new)
+{
+ int sts;
+ int nfound = 0;
+ char **found = NULL;
+ char *dname;
+ char *obase;
+ char path[MAXPATHLEN+1];
+ char opath[MAXPATHLEN+1];
+ char npath[MAXPATHLEN+1];
+ DIR *dirp;
+ struct dirent *dp;
+
+ strncpy(path, old, sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ dname = dirname(path);
+
+ if ((dirp = opendir(dname)) == NULL)
+ return -oserror();
+
+ strncpy(path, old, sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ obase = basename(path);
+
+ for ( ; ; ) {
+ setoserror(0);
+ if ((dp = readdir(dirp)) == NULL)
+ break;
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+ if (strncmp(obase, dp->d_name, strlen(obase)) == 0) {
+ /*
+ * match the base part of the old archive name, now check
+ * for meta or index or a valid volume number
+ */
+ char *p = &dp->d_name[strlen(obase)];
+ int want = 0;
+ if (strcmp(p, ".meta") == 0)
+ want = 1;
+ else if (strcmp(p, ".index") == 0)
+ want = 1;
+ else if (*p == '.' && isdigit((int)p[1])) {
+ char *endp;
+ long vol;
+ vol = strtol(&p[1], &endp, 10);
+ if (vol >= 0 && *endp == '\0')
+ want = 1;
+ }
+ if (want) {
+ struct stat stbuf;
+ snprintf(opath, sizeof(opath), "%s%s", old, p);
+ snprintf(npath, sizeof(npath), "%s%s", new, p);
+ if (stat(npath, &stbuf) == 0) {
+ fprintf(stderr, "__pmLogRename: destination file %s already exists\n", npath);
+ goto revert;
+ }
+ if (rename(opath, npath) == -1) {
+ fprintf(stderr, "__pmLogRename: rename %s -> %s failed: %s\n", opath, npath, pmErrStr(-oserror()));
+ goto revert;
+ }
+ nfound++;
+ found = (char **)realloc(found, nfound*sizeof(found[0]));
+ if (found == NULL) {
+ __pmNoMem("__pmLogRename: realloc", nfound*sizeof(found[0]), PM_RECOV_ERR);
+ abandon();
+ /*NOTREACHED*/
+ }
+ if ((found[nfound-1] = strdup(p)) == NULL) {
+ __pmNoMem("__pmLogRename: strdup", strlen(p)+1, PM_RECOV_ERR);
+ abandon();
+ /*NOTREACHED*/
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogRename: %s -> %s\n", opath, npath);
+#endif
+ }
+ }
+ }
+
+ if ((sts = oserror()) != 0) {
+ fprintf(stderr, "__pmLogRename: readdir for %s failed: %s\n", dname, pmErrStr(-sts));
+ goto revert;
+ }
+
+ sts = 0;
+ goto cleanup;
+
+revert:
+ while (nfound > 0) {
+ snprintf(opath, sizeof(opath), "%s%s", old, found[nfound-1]);
+ snprintf(npath, sizeof(npath), "%s%s", new, found[nfound-1]);
+ if (rename(npath, opath) == -1) {
+ fprintf(stderr, "__pmLogRename: arrgh trying to revert rename %s -> %s failed: %s\n", npath, opath, pmErrStr(-oserror()));
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogRename: revert %s <- %s\n", opath, npath);
+#endif
+ nfound--;
+ }
+ sts = PM_ERR_GENERIC;
+
+cleanup:
+ closedir(dirp);
+ while (nfound > 0) {
+ free(found[nfound-1]);
+ nfound--;
+ }
+ if (found != NULL)
+ free(found);
+
+ return sts;
+}
+
+/*
+ * Remove all the physical archive files with basename of base.
+ *
+ * Note: does not handle compressed versions of files.
+ *
+ * TODO - need global locking for PCP 3.6 version if this is promoted
+ * to libpcp
+ */
+int
+_pmLogRemove(const char *name)
+{
+ int sts;
+ int nfound = 0;
+ char *dname;
+ char *base;
+ char path[MAXPATHLEN+1];
+ DIR *dirp;
+ struct dirent *dp;
+
+ strncpy(path, name, sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ dname = strdup(dirname(path));
+ if (dname == NULL) {
+ __pmNoMem("__pmLogRemove: dirname strdup", strlen(dirname(path))+1, PM_RECOV_ERR);
+ abandon();
+ /*NOTREACHED*/
+ }
+
+ if ((dirp = opendir(dname)) == NULL) {
+ free(dname);
+ return -oserror();
+ }
+
+ strncpy(path, name, sizeof(path));
+ path[sizeof(path)-1] = '\0';
+ base = strdup(basename(path));
+ if (base == NULL) {
+ __pmNoMem("__pmLogRemove: basename strdup", strlen(basename(path))+1, PM_RECOV_ERR);
+ abandon();
+ /*NOTREACHED*/
+ }
+
+ for ( ; ; ) {
+ setoserror(0);
+ if ((dp = readdir(dirp)) == NULL)
+ break;
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+ if (strncmp(base, dp->d_name, strlen(base)) == 0) {
+ /*
+ * match the base part of the old archive name, now check
+ * for meta or index or a valid volume number
+ */
+ char *p = &dp->d_name[strlen(base)];
+ int want = 0;
+ if (strcmp(p, ".meta") == 0)
+ want = 1;
+ else if (strcmp(p, ".index") == 0)
+ want = 1;
+ else if (*p == '.' && isdigit((int)p[1])) {
+ char *endp;
+ long vol;
+ vol = strtol(&p[1], &endp, 10);
+ if (vol >= 0 && *endp == '\0')
+ want = 1;
+ }
+ if (want) {
+ snprintf(path, sizeof(path), "%s%s", name, p);
+ unlink(path);
+ nfound++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LOG)
+ fprintf(stderr, "__pmLogRemove: %s\n", path);
+#endif
+ }
+ }
+ }
+
+ if ((sts = oserror()) != 0) {
+ fprintf(stderr, "__pmLogRemove: readdir for %s failed: %s\n", dname, pmErrStr(-sts));
+ sts = -sts;
+ }
+ else
+ sts = nfound;
+
+ closedir(dirp);
+ free(dname);
+ free(base);
+
+ return sts;
+}
diff --git a/src/pmlogsummary/GNUmakefile b/src/pmlogsummary/GNUmakefile
new file mode 100644
index 0000000..9c27ac4
--- /dev/null
+++ b/src/pmlogsummary/GNUmakefile
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmlogcheck.c pmlogsummary.c
+TARGETS = pmlogcheck$(EXECSUFFIX) pmlogsummary$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB) $(LIB_FOR_MATH)
+LDIRT += $(TARGETS)
+
+default: $(TARGETS)
+
+pmlogcheck$(EXECSUFFIX): pmlogcheck.o
+ $(CCF) -o $@ $(LDFLAGS) pmlogcheck.o $(LDLIBS)
+
+pmlogsummary$(EXECSUFFIX): pmlogsummary.o
+ $(CCF) -o $@ $(LDFLAGS) pmlogsummary.o $(LDLIBS)
+
+include $(BUILDRULES)
+
+install: $(TARGETS)
+ $(INSTALL) -m 755 $(TARGETS) $(PCP_BIN_DIR)
+ $(INSTALL) -m 755 pmdiff.sh $(PCP_BIN_DIR)/pmdiff
+ $(INSTALL) -S $(PCP_BIN_DIR)/pmdiff $(PCP_BINADM_DIR)/pmwtf
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmlogsummary/pmdiff.sh b/src/pmlogsummary/pmdiff.sh
new file mode 100755
index 0000000..a47372b
--- /dev/null
+++ b/src/pmlogsummary/pmdiff.sh
@@ -0,0 +1,316 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Compare two PCP archives and report significant differences
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] archive1 [archive2]
+
+Options:
+ -d,--keep debug, keep intermediate files
+ -p=N,--precision=N number of digits to display after the decimal point
+ -q=N,--threshold=N change interesting threshold to be > N or < 1/N [N=2]
+ --skip-missing do not report metrics missing between the archives
+ --skip-excluded do not report the list of metrics being excluded
+ --start
+ --finish
+ -B=TIME,--begin=TIME start time for second archive (optional)
+ -E=TIME,--end=TIME end time for second archive (optional)
+ -x=REGEX egrep(1) pattern of metric(s) to be excluded
+ -X=FILE file containing egrep(1) patterns to exclude
+ --timezone
+ --hostzone
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ exit $status
+}
+
+_fix()
+{
+ sed -e 's/ *\([0-9][0-9.]*\)\([^"]*\)$/|\1/' \
+ | egrep -v -f $tmp/exclude \
+ | sort -t\| -k1,1
+}
+
+cat <<'End-of-File' >$tmp/exclude
+^pmcd.pmlogger.port
+End-of-File
+
+thres=2
+opts=""
+start1=""
+start2=""
+finish1=""
+finish2=""
+precision=3
+skip_missing=false
+skip_excluded=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -d) trap "exit \$status" 0 1 2 3 15
+ otmp="$tmp"
+ tmp=`pwd`/tmp
+ [ -d "$tmp" ] || mkdir "$tmp" || exit 1
+ mv $otmp/exclude $tmp/exclude
+ rmdir $otmp
+ ;;
+ -p) precision="$2"
+ shift
+ opts="$opts -p $precision"
+ ;;
+ -q) thres="$2"
+ shift
+ ;;
+ -S) start1="$2"
+ shift
+ ;;
+ -T) finish1="$2"
+ shift
+ ;;
+ -B) start2="$2"
+ shift
+ ;;
+ -E) finish2="$2"
+ shift
+ ;;
+ -x) echo "$2" >>$tmp/exclude
+ shift
+ ;;
+ -X) cat "$2" >>$tmp/exclude
+ shift
+ ;;
+ -z) opts="$opts -z"
+ ;;
+ -Z) opts="$opts -Z $2"
+ shift
+ ;;
+ --skip-missing)
+ skip_missing=true
+ ;;
+ --skip-excluded)
+ skip_excluded=true
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ # NOTREACHED
+ ;;
+ esac
+ shift
+done
+
+if [ $# -lt 1 -o $# -gt 2 ]
+then
+ _usage
+ # NOTREACHED
+elif [ $# -eq 2 ]
+then
+ arch1="$1"
+ arch2="$2"
+else
+ arch1="$1"
+ arch2="$1"
+fi
+
+[ $precision -lt 3 -o $precision -gt 15 ] && precision=3
+colwidth=`expr 12 + $precision`
+
+echo "Directory: `pwd`"
+if ! $skip_excluded
+then
+ echo "Excluded metrics:"
+ sed -e 's/^/ /' <$tmp/exclude
+fi
+echo
+
+options="$opts"
+if [ "X$start1" != X ]; then
+ options="$options -S $start1"
+fi
+if [ "X$finish1" != X ]; then
+ options="$options -T $finish1"
+fi
+pmlogsummary -N $options $arch1 2>$tmp/err | _fix >$tmp/1
+if [ -s $tmp/err ]
+then
+ echo "Warnings from pmlogsummary ... $arch1"
+ cat $tmp/err
+ echo
+fi
+
+options="$opts"
+if [ "X$start2" != X ]; then
+ options="$options -S $start2"
+elif [ "X$start1" != X ]; then
+ options="$options -S $start1"
+fi
+if [ "X$finish2" != X ]; then
+ options="$options -T $finish2"
+elif [ "X$finish1" != X ]; then
+ options="$options -T $finish1"
+fi
+pmlogsummary -N $options $arch2 2>$tmp/err | _fix >$tmp/2
+if [ -s $tmp/err ]
+then
+ echo "Warnings from pmlogsummary ... $arch2"
+ cat $tmp/err
+ echo
+fi
+
+if [ -z "$start1" ]
+then
+ window1="start"
+else
+ window1="$start1"
+fi
+if [ -z "$finish1" ]
+then
+ window1="$window1-end"
+else
+ window1="$window1-$finish1"
+fi
+if [ -z "$start2" ]
+then
+ window2="start"
+else
+ window2="$start2"
+fi
+if [ -z "$finish2" ]
+then
+ window2="$window2-end"
+else
+ window2="$window2-$finish2"
+fi
+
+if ! $skip_missing
+then
+ join -t\| -v 2 $tmp/1 $tmp/2 >$tmp/tmp
+ if [ -s $tmp/tmp ]
+ then
+ echo "Missing from $arch1 $window1 (not compared) ..."
+ sed <$tmp/tmp -e 's/|.*//' -e 's/^/ /'
+ echo
+ fi
+
+ join -t\| -v 1 $tmp/1 $tmp/2 >$tmp/tmp
+ if [ -s $tmp/tmp ]
+ then
+ echo "Missing from $arch2 $window2 (not compared) ..."
+ sed <$tmp/tmp -e 's/|.*//' -e 's/^/ /'
+ echo
+ fi
+fi
+
+a1=`basename "$arch1"`
+a2=`basename "$arch2"`
+echo "$thres" | awk '
+ { printf "Ratio Threshold: > %.2f or < %.3f\n",'"$thres"',1/'"$thres"'
+ printf "%*s %*s Ratio Metric-Instance\n",
+ '$colwidth', "'"$a1"'", '$colwidth', "'"$a2"'" }'
+if [ -z "$start1" ]
+then
+ window1="start"
+else
+ window1="$start1"
+fi
+if [ -z "$finish1" ]
+then
+ window1="$window1-end"
+else
+ window1="$window1-$finish1"
+fi
+if [ -z "$start2" ]
+then
+ window2="start"
+else
+ window2="$start2"
+fi
+if [ -z "$finish2" ]
+then
+ window2="$window2-end"
+else
+ window2="$window2-$finish2"
+fi
+printf '%*s %*s\n' $colwidth "$window1" $colwidth "$window2"
+join -t\| $tmp/1 $tmp/2 \
+| awk -F\| '
+function doval(v)
+{
+ precision='"$precision"'
+ extra=precision-3
+ if (v > 99999999)
+ printf "%*.*f%*s",15+extra,0,v,1," "
+ else if (v > 999)
+ printf "%*.*f%*s",11,0,v,2+precision," "
+ else if (v > 99)
+ printf "%*.*f%*s",13+extra,1+extra,v,3," "
+ else if (v > 9)
+ printf "%*.*f%*s",14+extra,2+extra,v,2," "
+ else
+ printf "%*.*f%*s",15+extra,precision,v,1," "
+}
+$3+0 == 0 || $2+0 == 0 {
+ if ($3 == $2)
+ next
+ doval($2)
+ doval($3)
+ printf " "
+ if ($3+0 == 0)
+ printf "|-| %s\n",$1
+ else if ($2+0 == 0)
+ printf "|+| %s\n",$1
+ next
+}
+$2 / $3 > '"$thres"' || $3 / $2 > '"$thres"' {
+ doval($2)
+ doval($3)
+ printf " "
+ r = $3/$2
+ if (r < 0.001)
+ printf " 0.001-"
+ else if (r < 0.01)
+ printf "%6.3f ",r
+ else if (r > 100)
+ printf " 100+ "
+ else
+ printf "%5.2f ",r
+ printf " %s\n",$1
+ }' \
+| sort -nr -k 3,3 \
+| sed -e 's/100+/>100/' -e 's/ 0.001-/<0.001 /'
+
+status=0
+exit
diff --git a/src/pmlogsummary/pmlogcheck.c b/src/pmlogsummary/pmlogcheck.c
new file mode 100644
index 0000000..e1dcb1e
--- /dev/null
+++ b/src/pmlogsummary/pmlogcheck.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include <limits.h>
+#include "pmapi.h"
+#include "impl.h"
+
+typedef struct {
+ int inst;
+ double lastval; /* value from previous sample */
+ struct timeval lasttime; /* time of previous sample */
+} instData;
+
+typedef struct {
+ pmDesc desc;
+ double scale;
+ instData **instlist;
+ unsigned int listsize;
+} checkData;
+
+static __pmHashCtl hashlist; /* hash statistics about each metric */
+static int dayflag;
+static char timebuf[32]; /* for pmCtime result + .xxx */
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "label", 0, 'l', 0, "print the archive label" },
+ PMOPT_NAMESPACE,
+ PMOPT_START,
+ PMOPT_FINISH,
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_DONE | PM_OPTFLAG_BOUNDARIES | PM_OPTFLAG_STDOUT_TZ,
+ .short_options = "D:ln:S:T:zZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] archive",
+};
+
+/* time manipulation */
+static int
+tsub(struct timeval *a, struct timeval *b)
+{
+ if ((a == NULL) || (b == NULL))
+ return -1;
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+ return 0;
+}
+
+static double
+tosec(struct timeval t)
+{
+ return t.tv_sec + (t.tv_usec / 1000000.0);
+}
+
+static char *
+typeStr(int pmtype)
+{
+ switch (pmtype) {
+ case PM_TYPE_32:
+ return "signed 32-bit";
+ case PM_TYPE_U32:
+ return "unsigned 32-bit";
+ case PM_TYPE_64:
+ return "signed 64-bit";
+ case PM_TYPE_U64:
+ return "unsigned 64-bit";
+ }
+ return "unknown type";
+}
+
+static void
+print_metric(FILE *f, pmID pmid)
+{
+ char *name = NULL;
+
+ if (pmNameID(pmid, &name) < 0)
+ fprintf(f, "%s", pmIDStr(pmid));
+ else {
+ fprintf(f, "%s", name);
+ free(name);
+ }
+}
+
+static void
+print_stamp(FILE *f, struct timeval *stamp)
+{
+ if (dayflag) {
+ char *ddmm;
+ char *yr;
+
+ ddmm = pmCtime(&stamp->tv_sec, timebuf);
+ ddmm[10] = ' ';
+ ddmm[11] = '\0';
+ yr = &ddmm[20];
+ fprintf(f, "%s", ddmm);
+ __pmPrintStamp(f, stamp);
+ fprintf(f, " %4.4s", yr);
+ }
+ else
+ __pmPrintStamp(f, stamp);
+}
+
+static double
+unwrap(double current, struct timeval *curtime, checkData *checkdata, int index)
+{
+ double outval = current;
+ int wrapflag = 0;
+ char *str = NULL;
+
+ if ((current - checkdata->instlist[index]->lastval) < 0.0) {
+ switch (checkdata->desc.type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ outval += (double)UINT_MAX+1;
+ wrapflag = 1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ outval += (double)ULONGLONG_MAX+1;
+ wrapflag = 1;
+ break;
+ default:
+ wrapflag = 0;
+ }
+ }
+
+ if (wrapflag) {
+ printf("[");
+ print_stamp(stdout, curtime);
+ printf("]: ");
+ print_metric(stdout, checkdata->desc.pmid);
+ if (pmNameInDom(checkdata->desc.indom, checkdata->instlist[index]->inst, &str) < 0)
+ printf(": %s wrap", typeStr(checkdata->desc.type));
+ else {
+ printf("[%s]: %s wrap", str, typeStr(checkdata->desc.type));
+ free(str);
+ }
+ printf("\n\tvalue %.0f at ", checkdata->instlist[index]->lastval);
+ print_stamp(stdout, &checkdata->instlist[index]->lasttime);
+ printf("\n\tvalue %.0f at ", current);
+ print_stamp(stdout, curtime);
+ putchar('\n');
+ }
+
+ return outval;
+}
+
+static void
+newHashInst(pmValue *vp,
+ checkData *checkdata, /* updated by this function */
+ int valfmt,
+ struct timeval *timestamp, /* timestamp for this sample */
+ int pos) /* position of this inst in instlist */
+{
+ int sts;
+ size_t size;
+ pmAtomValue av;
+
+ if ((sts = pmExtractValue(valfmt, vp, checkdata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
+ printf("[");
+ print_stamp(stdout, timestamp);
+ printf("] ");
+ print_metric(stdout, checkdata->desc.pmid);
+ printf(": pmExtractValue failed: %s\n", pmErrStr(sts));
+ fprintf(stderr, "%s: possibly corrupt archive?\n", pmProgname);
+ exit(1);
+ }
+ size = (pos+1)*sizeof(instData*);
+ checkdata->instlist = (instData**) realloc(checkdata->instlist, size);
+ if (!checkdata->instlist)
+ __pmNoMem("newHashInst.instlist", size, PM_FATAL_ERR);
+ size = sizeof(instData);
+ checkdata->instlist[pos] = (instData*) malloc(size);
+ if (!checkdata->instlist[pos])
+ __pmNoMem("newHashInst.instlist[pos]", size, PM_FATAL_ERR);
+ checkdata->instlist[pos]->inst = vp->inst;
+ checkdata->instlist[pos]->lastval = av.d;
+ checkdata->instlist[pos]->lasttime = *timestamp;
+ checkdata->listsize++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ char *name;
+
+ printf("[");
+ print_stamp(stdout, timestamp);
+ printf("] ");
+ print_metric(stdout, checkdata->desc.pmid);
+ if (vp->inst == PM_INDOM_NULL)
+ printf(": new singular metric\n");
+ else {
+ printf(": new metric-instance pair ");
+ if (pmNameInDom(checkdata->desc.indom, vp->inst, &name) < 0)
+ printf("%d\n", vp->inst);
+ else {
+ printf("\"%s\"\n", name);
+ free(name);
+ }
+ }
+
+ }
+#endif
+}
+
+static void
+newHashItem(pmValueSet *vsp,
+ pmDesc *desc,
+ checkData *checkdata, /* output from this function */
+ struct timeval *timestamp) /* timestamp for this sample */
+{
+ int j;
+
+ checkdata->desc = *desc;
+ checkdata->scale = 0.0;
+
+ /* convert counter metric units to rate units & get time scale */
+ if (checkdata->desc.sem == PM_SEM_COUNTER) {
+ if (checkdata->desc.units.dimTime == 0)
+ checkdata->scale = 1.0;
+ else {
+ if (checkdata->desc.units.scaleTime > PM_TIME_SEC)
+ checkdata->scale = pow(60.0, (double)(PM_TIME_SEC - checkdata->desc.units.scaleTime));
+ else
+ checkdata->scale = pow(1000.0, (double)(PM_TIME_SEC - checkdata->desc.units.scaleTime));
+ }
+ if (checkdata->desc.units.dimTime == 0) checkdata->desc.units.scaleTime = PM_TIME_SEC;
+ checkdata->desc.units.dimTime--;
+ }
+
+ checkdata->listsize = 0;
+ checkdata->instlist = NULL;
+ for (j = 0; j < vsp->numval; j++) {
+ newHashInst(&vsp->vlist[j], checkdata, vsp->valfmt, timestamp, j);
+ }
+}
+
+static void
+docheck(pmResult *result)
+{
+ int i, j, k;
+ int sts;
+ pmDesc desc;
+ pmAtomValue av;
+ pmValue *vp;
+ pmValueSet *vsp;
+ __pmHashNode *hptr = NULL;
+ checkData *checkdata = NULL;
+ double diff;
+ struct timeval timediff;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ if (vsp->numval == 0) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, vsp->pmid);
+ printf(": No values returned\n");
+ continue;
+ }
+ else if (vsp->numval < 0) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, vsp->pmid);
+ printf(": Error from numval: %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+ }
+#endif
+
+ /* check if pmid already in hash list */
+ if ((hptr = __pmHashSearch(vsp->pmid, &hashlist)) == NULL) {
+ if ((sts = pmLookupDesc(vsp->pmid, &desc)) < 0) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, vsp->pmid);
+ printf(": pmLookupDesc failed: %s\n", pmErrStr(sts));
+ continue;
+ }
+
+ if (desc.type != PM_TYPE_32 && desc.type != PM_TYPE_U32 &&
+ desc.type != PM_TYPE_64 && desc.type != PM_TYPE_U64 &&
+ desc.type != PM_TYPE_FLOAT && desc.type != PM_TYPE_DOUBLE) {
+ continue; /* no checks for non-numeric metrics */
+ }
+
+ /* create a new one & add to list */
+ checkdata = (checkData*) malloc(sizeof(checkData));
+ newHashItem(vsp, &desc, checkdata, &result->timestamp);
+ if (__pmHashAdd(checkdata->desc.pmid, (void*)checkdata, &hashlist) < 0) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, vsp->pmid);
+ printf(": __pmHashAdd failed (internal pmlogcheck error)\n");
+ /* free memory allocated above on insert failure */
+ for (j = 0; j < vsp->numval; j++) {
+ if (checkdata->instlist[j] != NULL)
+ free(checkdata->instlist[j]);
+ }
+ if (checkdata->instlist != NULL)
+ free(checkdata->instlist);
+ continue;
+ }
+ }
+ else { /* pmid exists - update statistics */
+ checkdata = (checkData *)hptr->data;
+ for (j = 0; j < vsp->numval; j++) { /* iterate thro result values */
+ vp = &vsp->vlist[j];
+ k = j; /* index into stored inst list, result may differ */
+ if ((vsp->numval > 1) || (checkdata->desc.indom != PM_INDOM_NULL)) {
+ /* must store values using correct inst - probably in correct order already */
+ if ((k < checkdata->listsize) && (checkdata->instlist[k]->inst != vp->inst)) {
+ for (k = 0; k < checkdata->listsize; k++) {
+ if (vp->inst == checkdata->instlist[k]->inst) {
+ break; /* k now correct */
+ }
+ }
+ if (k == checkdata->listsize) { /* no matching inst was found */
+ newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
+ continue;
+ }
+ }
+ else if (k >= checkdata->listsize) {
+ k = checkdata->listsize;
+ newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
+ continue;
+ }
+ }
+ if (k >= checkdata->listsize) { /* only error values observed so far */
+ k = checkdata->listsize;
+ newHashInst(vp, checkdata, vsp->valfmt, &result->timestamp, k);
+ continue;
+ }
+
+ timediff = result->timestamp;
+ tsub(&timediff, &(checkdata->instlist[k]->lasttime));
+ if (timediff.tv_sec < 0) {
+ /* clip negative values at zero */
+ timediff.tv_sec = 0;
+ timediff.tv_usec = 0;
+ }
+ diff = tosec(timediff);
+ if ((sts = pmExtractValue(vsp->valfmt, vp, checkdata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, vsp->pmid);
+ printf(": pmExtractValue failed: %s\n", pmErrStr(sts));
+ continue;
+ }
+ if (checkdata->desc.sem == PM_SEM_COUNTER) {
+ if (diff == 0.0) continue;
+ diff *= checkdata->scale;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("] ");
+ print_metric(stdout, checkdata->desc.pmid);
+ printf(": current counter value is %.0f\n", av.d);
+ }
+#endif
+ unwrap(av.d, &(result->timestamp), checkdata, k);
+ }
+ checkdata->instlist[k]->lastval = av.d;
+ checkdata->instlist[k]->lasttime = result->timestamp;
+ }
+ }
+ }
+}
+
+static void
+dumpLabel(void)
+{
+ pmLogLabel label;
+ char *ddmm;
+ char *yr;
+ int sts;
+
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: Cannot get archive label record: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ printf("Log Label (Log Format Version %d)\n", label.ll_magic & 0xff);
+ printf("Performance metrics from host %s\n", label.ll_hostname);
+
+ ddmm = pmCtime(&label.ll_start.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" commencing %s ", ddmm);
+ __pmPrintStamp(stdout, &label.ll_start);
+ printf(" %4.4s\n", yr);
+
+ if (opts.finish.tv_sec == INT_MAX) {
+ /* pmGetArchiveEnd() failed! */
+ printf(" ending UNKNOWN\n");
+ }
+ else {
+ ddmm = pmCtime(&opts.finish.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" ending %s ", ddmm);
+ __pmPrintStamp(stdout, &opts.finish);
+ printf(" %4.4s\n", yr);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, sts, ctx;
+ int lflag = 0; /* no label by default */
+ pmResult *result;
+ struct timeval timespan;
+ struct timeval last_stamp;
+ struct timeval delta_stamp;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'l': /* display the archive label */
+ lflag = 1;
+ break;
+ }
+ }
+
+ if (!opts.errors && opts.optind >= argc) {
+ pmprintf("Error: no archive specified\n\n");
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ __pmAddOptArchive(&opts, argv[opts.optind]);
+ opts.flags &= ~PM_OPTFLAG_DONE;
+ __pmEndOptions(&opts);
+
+ if ((sts = ctx = pmNewContext(PM_CONTEXT_ARCHIVE, opts.archives[0])) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, opts.archives[0], pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
+ if (pmGetContextOptions(ctx, &opts) < 0) {
+ pmflush(); /* runtime errors only at this stage */
+ exit(EXIT_FAILURE);
+ }
+
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
+ if (lflag)
+ dumpLabel();
+
+ /* check which timestamp print format we should be using */
+ timespan = opts.finish;
+ tsub(&timespan, &opts.start);
+ if (timespan.tv_sec > 86400) /* seconds per day: 60*60*24 */
+ dayflag = 1;
+
+ sts = 0;
+ last_stamp = opts.start;
+ for ( ; ; ) {
+ if ((sts = pmFetchArchive(&result)) < 0)
+ break;
+ delta_stamp = result->timestamp;
+ tsub(&delta_stamp, &last_stamp);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ int i;
+ int sum_val = 0;
+ int cnt_noval = 0;
+ int cnt_err = 0;
+ pmValueSet *vsp;
+
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ if (vsp->numval > 0)
+ sum_val += vsp->numval;
+ else if (vsp->numval == 0)
+ cnt_noval++;
+ else
+ cnt_err++;
+ }
+ printf("] delta(stamp)=%d.%03fsec",
+ (int)delta_stamp.tv_sec, (double)(delta_stamp.tv_usec)/1000000);
+ printf(" numpmid=%d sum(numval)=%d", result->numpmid, sum_val);
+ if (cnt_noval > 0)
+ printf(" count(numval=0)=%d", cnt_noval);
+ if (cnt_err > 0)
+ printf(" count(numval<0)=%d", cnt_err);
+ fputc('\n', stdout);
+ }
+#endif
+ if (delta_stamp.tv_sec < 0) {
+ /* time went backwards! */
+ printf("[");
+ print_stamp(stdout, &result->timestamp);
+ printf("]: timestamp went backwards, prior timestamp: ");
+ print_stamp(stdout, &last_stamp);
+ printf("\n");
+ }
+
+ last_stamp = result->timestamp;
+ if ((opts.finish.tv_sec > result->timestamp.tv_sec) ||
+ ((opts.finish.tv_sec == result->timestamp.tv_sec) &&
+ (opts.finish.tv_usec >= result->timestamp.tv_usec))) {
+ docheck(result);
+ pmFreeResult(result);
+ }
+ else {
+ pmFreeResult(result);
+ sts = PM_ERR_EOL;
+ break;
+ }
+ }
+ if (sts != PM_ERR_EOL) {
+ printf("[after ");
+ print_stamp(stdout, &last_stamp);
+ printf("]: pmFetch: Error: %s\n", pmErrStr(sts));
+ exit(1);
+ }
+
+ return 0;
+}
diff --git a/src/pmlogsummary/pmlogsummary.c b/src/pmlogsummary/pmlogsummary.c
new file mode 100644
index 0000000..9db205e
--- /dev/null
+++ b/src/pmlogsummary/pmlogsummary.c
@@ -0,0 +1,1197 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include <stdarg.h>
+#include <limits.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "all", 0, 'a', 0, "print all information (equivalent to -blmMy)" },
+ { "", 0, 'b', 0, "print both stochastic and time averages for counter metrics" },
+ { "bins", 1, 'B', "N", "print value distribution across a number of bins" },
+ { "", 0, 'f', 0, "print using \"spreadsheet\" format (tab delimited fields)" },
+ { "", 0, 'F', 0, "print using \"spreadsheet\" format (comma separated values)" },
+ { "header", 0, 'H', 0, "print one-line header at start showing each column" },
+ { "mintime", 0, 'i', 0, "also print timestamp for minimum value" },
+ { "maxtime", 0, 'I', 0, "also print timestamp for maximum value" },
+ { "label", 0, 'l', 0, "also print the archive label and time window" },
+ { "minimum", 0, 'm', 0, "also print minimum value" },
+ { "maximum", 0, 'M', 0, "also print maximum value" },
+ PMOPT_NAMESPACE,
+ { "", 0, 'N', 0, "suppress warnings from individual archive fetches (default)" },
+ { "precision", 0, 'p', 0, "number of digits to display after the decimal point" },
+ PMOPT_START,
+ PMOPT_FINISH,
+ { "verbose", 0, 'v', 0, "verbose, enable warnings from individual archive fetches" },
+ { "", 0, 'x', 0, "print only stochastic averages for counter metrics" },
+ { "samples", 0, 'y', "print sample count for each metric" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static int override(int, pmOptions *);
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_DONE | PM_OPTFLAG_BOUNDARIES | PM_OPTFLAG_STDOUT_TZ,
+ .short_options = "abB:D:fFHiIlmMNn:p:rsS:T:vxyzZ:?",
+ .long_options = longopts,
+ .short_usage = "[options] archive [metricname ...]",
+ .override = override,
+};
+
+typedef struct {
+ int inst;
+ unsigned int count;
+ double stocave; /* stochastic average */
+ double timeave; /* time average */
+ double min; /* minimum value */
+ double max; /* maximum value */
+ double sum; /* sum of all values */
+ double lastval; /* value from previous sample */
+ struct timeval firsttime; /* time of first sample */
+ struct timeval lasttime; /* time of previous sample */
+ struct timeval mintime; /* time of minimum sample */
+ struct timeval maxtime; /* time of maximum sample */
+ int markcount; /* num mark records seen */
+ int marked; /* seen since last "mark" record? */
+ unsigned int bintotal; /* copy of count for 2nd pass */
+ unsigned int *bin; /* bins for value distribution */
+} instData;
+
+typedef struct {
+ pmDesc desc;
+ double scale;
+ instData **instlist;
+ unsigned int listsize;
+} aveData;
+
+/*
+ * Hash control for statistics & errors related to each metric
+ */
+static __pmHashCtl hashlist;
+static __pmHashCtl errlist;
+
+/* output format flags */
+static unsigned int stocaveflag; /* no stochastic counter ave */
+static unsigned int timeaveflag = 1;/* use time counter ave */
+static unsigned int maxflag; /* no maximum */
+static unsigned int minflag; /* no minimum */
+static unsigned int sumflag; /* no sum */
+static unsigned int maxtimeflag; /* no timestamp for maximum */
+static unsigned int mintimeflag; /* no timestamp for minimum */
+static unsigned int countflag; /* no count */
+static unsigned int warnflag; /* warnings are off by default */
+static unsigned int delimiter = ' ';/* output field separator */
+static unsigned int nbins; /* number of distribution bins */
+static unsigned int precision = 3; /* number of digits after "." */
+
+/* time window stuff */
+static int dayflag;
+static char timebuf[32]; /* for pmCtime result + .xxx */
+
+/* duration of log */
+static double logspan;
+
+/* optional metric specification, optionally with instances */
+pmMetricSpec *msp;
+
+/* time manipulation */
+static int
+tsub(struct timeval *a, struct timeval *b)
+{
+ if ((a == NULL) || (b == NULL))
+ return -1;
+ a->tv_usec -= b->tv_usec;
+ if (a->tv_usec < 0) {
+ a->tv_usec += 1000000;
+ a->tv_sec--;
+ }
+ a->tv_sec -= b->tv_sec;
+ if (a->tv_sec < 0) {
+ /* clip negative values at zero */
+ a->tv_sec = 0;
+ a->tv_usec = 0;
+ }
+ return 0;
+}
+
+static int
+tadd(struct timeval *a, struct timeval *b)
+{
+ if ((a == NULL) || (b == NULL))
+ return -1;
+ a->tv_usec += b->tv_usec;
+ if (a->tv_usec > 1000000) {
+ a->tv_usec -= 1000000;
+ a->tv_sec++;
+ }
+ a->tv_sec += b->tv_sec;
+ return 0;
+}
+
+static double
+tosec(struct timeval t)
+{
+ return t.tv_sec + (t.tv_usec / 1000000.0);
+}
+
+static void
+pmiderr(pmID pmid, const char *msg, ...)
+{
+ if (warnflag && __pmHashSearch(pmid, &errlist) == NULL) {
+ va_list arg;
+ char *mname = NULL;
+ static char *unknown = "(cannot find metric name) ";
+
+ pmNameID(pmid, &mname);
+ if (!mname) mname = unknown;
+ fprintf(stderr, "%s: %s(%s) - ", pmProgname, mname, pmIDStr(pmid));
+ va_start(arg, msg);
+ vfprintf(stderr, msg, arg);
+ va_end(arg);
+ __pmHashAdd(pmid, NULL, &errlist);
+ if (mname && mname != unknown) free(mname);
+ }
+}
+
+static void
+printstamp(struct timeval *stamp, int delimiter)
+{
+ if (dayflag) {
+ char *ddmm;
+ char *yr;
+
+ ddmm = pmCtime(&stamp->tv_sec, timebuf);
+ ddmm[10] = ' ';
+ ddmm[11] = '\0';
+ yr = &ddmm[20];
+ printf("%c'%s", delimiter, ddmm);
+ __pmPrintStamp(stdout, stamp);
+ printf(" %4.4s\'", yr);
+ }
+ else {
+ printf("%c", delimiter);
+ __pmPrintStamp(stdout, stamp);
+ }
+}
+
+static void
+printlabel(void)
+{
+ pmLogLabel label;
+ char *ddmm;
+ char *yr;
+ int sts;
+
+ if ((sts = pmGetArchiveLabel(&label)) < 0) {
+ fprintf(stderr, "%s: Cannot get archive label record: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ printf("Log Label (Log Format Version %d)\n", label.ll_magic & 0xff);
+ printf("Performance metrics from host %s\n", label.ll_hostname);
+
+ ddmm = pmCtime(&opts.start.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" commencing %s ", ddmm);
+ __pmPrintStamp(stdout, &opts.start);
+ printf(" %4.4s\n", yr);
+
+ if (opts.finish.tv_sec == INT_MAX) {
+ /* pmGetArchiveEnd() failed! */
+ printf(" ending UNKNOWN\n");
+ }
+ else {
+ ddmm = pmCtime(&opts.finish.tv_sec, timebuf);
+ ddmm[10] = '\0';
+ yr = &ddmm[20];
+ printf(" ending %s ", ddmm);
+ __pmPrintStamp(stdout, &opts.finish);
+ printf(" %4.4s\n", yr);
+ }
+}
+
+static void
+printheaders(void)
+{
+ printf("metric");
+ if (stocaveflag)
+ printf("%cstochastic_average", delimiter);
+ if (timeaveflag)
+ printf("%ctime_average", delimiter);
+ if (sumflag)
+ printf("%csum", delimiter);
+ if (minflag)
+ printf("%cminimum", delimiter);
+ if (mintimeflag)
+ printf("%cminimum_time", delimiter);
+ if (maxflag)
+ printf("%cmaximum", delimiter);
+ if (maxtimeflag)
+ printf("%cmaximum_time", delimiter);
+ if (countflag)
+ printf("%ccount", delimiter);
+ if (nbins)
+ printf("%cbins", delimiter);
+ printf("%cunits\n", delimiter);
+}
+
+static void
+printsummary(const char *name)
+{
+ int sts;
+ int i, j;
+ int star;
+ pmID pmid;
+ char *str = NULL;
+ const char *u;
+ __pmHashNode *hptr;
+ aveData *avedata;
+ instData *instdata;
+ double metricspan = 0.0;
+ struct timeval metrictimespan;
+
+ /* cast away const, pmLookupName should never modify name */
+ if ((sts = pmLookupName(1, (char **)&name, &pmid)) < 0) {
+ fprintf(stderr, "%s: failed to lookup metric name (pmid=%s): %s\n",
+ pmProgname, name, pmErrStr(sts));
+ return;
+ }
+
+ /* lookup using pmid, print values according to set flags */
+ if ((hptr = __pmHashSearch(pmid, &hashlist)) != NULL) {
+ avedata = (aveData*)hptr->data;
+ for (i = 0; i < avedata->listsize; i++) {
+ if ((instdata = avedata->instlist[i]) == NULL)
+ break;
+ if (avedata->desc.sem == PM_SEM_DISCRETE) {
+ double val;
+ struct timeval timediff;
+
+ /* extend discrete metrics to the archive end */
+ timediff = opts.finish;
+ tsub(&timediff, &instdata->lasttime);
+ val = instdata->lastval;
+ instdata->stocave += val;
+ instdata->timeave += val*tosec(timediff);
+ instdata->lasttime = opts.finish;
+ instdata->count++;
+ }
+ metrictimespan = instdata->lasttime;
+ tsub(&metrictimespan, &instdata->firsttime);
+ metricspan = tosec(metrictimespan);
+ /* counter metric doesn't cover 90% of log */
+ star = (avedata->desc.sem == PM_SEM_COUNTER && metricspan / logspan <= 0.1);
+
+ if ((sts = pmNameInDom(avedata->desc.indom, instdata->inst, &str)) < 0) {
+ if (msp && msp->ninst > 0 && avedata->desc.indom == PM_INDOM_NULL)
+ break;
+ if (star)
+ putchar('*');
+ if (avedata->desc.indom == PM_INDOM_NULL)
+ printf("%s%c", name, delimiter);
+ else
+ printf("%s%c[%u]", name, delimiter, instdata->inst);
+ }
+ else { /* part of an instance domain */
+ if (msp && msp->ninst > 0) {
+ for (j = 0; j < msp->ninst; j++)
+ if (strcmp(msp->inst[j], str) == 0)
+ break;
+ if (j == msp->ninst)
+ continue;
+ }
+ if (star)
+ putchar('*');
+ printf("%s%c[\"%s\"]", name, delimiter, str);
+ }
+ if (str) free(str);
+ str = NULL;
+
+ /* complete the calculations, count is number of intervals */
+ if (avedata->desc.sem == PM_SEM_COUNTER) {
+ if (metricspan <= 0) {
+ printf("%c- insufficient archive data.\n", delimiter);
+ continue;
+ }
+ instdata->stocave /= (double)instdata->count;
+ instdata->timeave /= metricspan * avedata->scale;
+ }
+ else { /* non-counters, count is number of observations */
+ instdata->stocave /= (double)instdata->count;
+ if (metricspan == 0) /* happens for instantaneous metrics */
+ metricspan = 1; /* only - just report the one value */
+ instdata->timeave /= metricspan;
+ }
+
+ if (stocaveflag)
+ printf("%c%.*f", delimiter, (int)precision, instdata->stocave);
+ if (timeaveflag)
+ printf("%c%.*f", delimiter, (int)precision, instdata->timeave);
+ if (sumflag)
+ printf("%c%.*f", delimiter, (int)precision, instdata->sum);
+ if (minflag)
+ printf("%c%.*f", delimiter, (int)precision, instdata->min);
+ if (mintimeflag)
+ printstamp(&instdata->mintime, delimiter);
+ if (maxflag)
+ printf("%c%.*f", delimiter, (int)precision, instdata->max);
+ if (maxtimeflag)
+ printstamp(&instdata->maxtime, delimiter);
+ if (avedata->desc.sem == PM_SEM_DISCRETE) /* all added marks + added endpoint above */
+ instdata->count = instdata->count - instdata->markcount - 1;
+ if (countflag)
+ printf("%c%u", delimiter, instdata->count);
+ for (j=0; j < nbins; j++) { /* print value distribution summary */
+ if (j > 0 && instdata->min == instdata->max) /* all in 1st bin */
+ printf("%c[]%c%u", delimiter, delimiter, 0);
+ else
+ printf("%c[<=%.*f]%c%u", delimiter, (int)precision,
+ ((instdata->max - instdata->min) / nbins * (j+1)) + instdata->min,
+ delimiter, instdata->bin[j]);
+ }
+ u = pmUnitsStr(&avedata->desc.units);
+ printf("%c%s\n", delimiter, *u == '\0' ? "none" : u);
+ if (instdata) {
+ if (instdata->bin)
+ free(instdata->bin);
+ free(instdata);
+ }
+ }
+ if (avedata->instlist) free(avedata->instlist);
+ __pmHashDel(avedata->desc.pmid, (void*)avedata, &hashlist);
+ free(avedata);
+ }
+}
+
+static double
+unwrap(double current, double previous, int pmtype)
+{
+ double outval = current;
+ static int dowrap = -1;
+
+ if ((current - previous) < 0.0) {
+ if (dowrap == -1) {
+ /* PCP_COUNTER_WRAP in environment enables "counter wrap" logic */
+ if (getenv("PCP_COUNTER_WRAP") == NULL)
+ dowrap = 0;
+ else
+ dowrap = 1;
+ }
+ if (dowrap) {
+ switch (pmtype) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ outval += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ outval += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+ }
+
+ return outval;
+}
+
+static void
+newHashInst(pmValue *vp,
+ aveData *avedata, /* updated by this function */
+ int valfmt,
+ struct timeval *timestamp, /* timestamp for this sample */
+ int pos) /* position of this inst in instlist */
+{
+ int sts;
+ size_t size;
+ instData *instdata;
+ pmAtomValue av;
+
+ if ((sts = pmExtractValue(valfmt, vp, avedata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
+ pmiderr(avedata->desc.pmid, "failed to extract value: %s\n", pmErrStr(sts));
+ fprintf(stderr, "%s: possibly corrupt archive?\n", pmProgname);
+ exit(1);
+ }
+ size = (pos+1) * sizeof(instData *);
+ avedata->instlist = (instData **) realloc(avedata->instlist, size);
+ if (avedata->instlist == NULL)
+ __pmNoMem("newHashInst.instlist", size, PM_FATAL_ERR);
+ size = sizeof(instData);
+ avedata->instlist[pos] = instdata = (instData *) malloc(size);
+ if (instdata == NULL)
+ __pmNoMem("newHashInst.instlist[inst]", size, PM_FATAL_ERR);
+ if (nbins == 0)
+ instdata->bin = NULL;
+ else { /* we are doing binning ... make space for the bins */
+ size = nbins * sizeof(unsigned int);
+ instdata->bin = (unsigned int *)malloc(size);
+ if (instdata->bin == NULL)
+ __pmNoMem("newHashInst.instlist[inst].bin", size, PM_FATAL_ERR);
+ memset(instdata->bin, 0, size);
+ }
+ instdata->inst = vp->inst;
+ if (avedata->desc.sem == PM_SEM_COUNTER) {
+ instdata->min = 0.0;
+ instdata->max = 0.0;
+ instdata->sum = 0.0;
+ instdata->mintime = *timestamp;
+ instdata->maxtime = *timestamp;
+ instdata->stocave = 0.0;
+ instdata->timeave = 0.0;
+ instdata->count = 0;
+ }
+ else { /* for the other semantics */
+ instdata->min = av.d;
+ instdata->max = av.d;
+ instdata->sum = av.d;
+ instdata->mintime = *timestamp;
+ instdata->maxtime = *timestamp;
+ instdata->stocave = av.d;
+ instdata->timeave = 0.0;
+ instdata->count = 1;
+ }
+ instdata->marked = 0;
+ instdata->bintotal = 0;
+ instdata->markcount = 0;
+ instdata->lastval = av.d;
+ instdata->firsttime = *timestamp;
+ instdata->lasttime = *timestamp;
+ avedata->listsize++;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *name = NULL;
+ pmNameID(avedata->desc.pmid, &name);
+ fprintf(stderr, "%s Initially - min/max=%f/%f\n", name,
+ instdata->min, instdata->max);
+ if (name) free(name);
+ }
+#endif
+}
+
+static void
+newHashItem(pmValueSet *vsp,
+ pmDesc *desc,
+ aveData *avedata, /* output from this function */
+ struct timeval *timestamp) /* timestamp for this sample */
+{
+ int j;
+
+ avedata->desc = *desc;
+ avedata->scale = 0.0;
+
+ /* convert counter metric units to rate units & get time scale */
+ if (avedata->desc.sem == PM_SEM_COUNTER && !sumflag) {
+ if (avedata->desc.units.dimTime == 0)
+ avedata->scale = 1.0;
+ else {
+ if (avedata->desc.units.scaleTime > PM_TIME_SEC)
+ avedata->scale = pow(60.0, (double)(PM_TIME_SEC - avedata->desc.units.scaleTime));
+ else
+ avedata->scale = pow(1000.0, (double)(PM_TIME_SEC - avedata->desc.units.scaleTime));
+ }
+ if (avedata->desc.units.dimTime == 0)
+ avedata->desc.units.scaleTime = PM_TIME_SEC;
+ avedata->desc.units.dimTime--;
+ }
+ else if (avedata->desc.sem == PM_SEM_COUNTER && sumflag) {
+ avedata->scale = 1.0;
+ }
+ avedata->listsize = 0;
+ avedata->instlist = NULL;
+ for (j = 0; j < vsp->numval; j++)
+ newHashInst(&vsp->vlist[j], avedata, vsp->valfmt, timestamp, j);
+}
+
+/*
+ * find index to bin array for "val"
+ */
+unsigned int
+findbin(pmID pmid, double val, double min, double max)
+{
+ unsigned int index;
+ double bound, next;
+ double binsize;
+
+ binsize = (max - min) / (double)nbins;
+ bound = min;
+ for (index=0; index < nbins-1; index++) {
+ next = bound + binsize;
+ if (val >= bound && val <= next)
+ break;
+ bound = next;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *mname = NULL;
+ pmNameID(pmid, &mname);
+ fprintf(stderr, "%s selected bin %u/%u (val=%.*f, min=%.*f, max=%.*f)\n",
+ mname, index, nbins, (int)precision, val, (int)precision,
+ min, (int)precision, max);
+ if (mname) free(mname);
+ if (index >= nbins) exit(1);
+ }
+#endif
+ return index;
+}
+
+/*
+ * must keep a note for every instance of every metric whenever a mark
+ * record has been seen between now & the last fetch for that instance
+ */
+static void
+markrecord(pmResult *result)
+{
+ int i, j;
+ __pmHashNode *hptr;
+ aveData *avedata;
+ instData *instdata;
+ double val;
+ struct timeval timediff;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ printstamp(&result->timestamp, '\n');
+ printf(" - mark record\n\n");
+ }
+#endif
+ for (i = 0; i < hashlist.hsize; i++) {
+ for (hptr = hashlist.hash[i]; hptr != NULL; hptr = hptr->next) {
+ avedata = (aveData *)hptr->data;
+ for (j = 0; j < avedata->listsize; j++) {
+ instdata = avedata->instlist[j];
+ if (avedata->desc.sem == PM_SEM_DISCRETE) {
+ /* extend discrete metrics to the mark point */
+ timediff = result->timestamp;
+ tsub(&timediff, &instdata->lasttime);
+ val = instdata->lastval;
+ instdata->stocave += val;
+ instdata->timeave += val*tosec(timediff);
+ instdata->lasttime = result->timestamp;
+ instdata->count++;
+ }
+ instdata->marked = 1;
+ instdata->markcount++;
+ }
+ }
+ }
+}
+
+static void
+calcbinning(pmResult *result)
+{
+ int i, j, k;
+ int sts;
+ int wrap;
+ double val;
+ pmAtomValue av;
+ pmValue *vp;
+ pmValueSet *vsp;
+ __pmHashNode *hptr = NULL;
+ aveData *avedata = NULL;
+ instData *instdata;
+ double diff;
+ struct timeval timediff;
+
+ if (result->numpmid == 0) /* mark record */
+ markrecord(result);
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ if (vsp->numval == 0)
+ continue;
+ else if (vsp->numval < 0) {
+ pmiderr(vsp->pmid, "failed in 2nd pass archive fetch: %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+
+ if ((hptr = __pmHashSearch(vsp->pmid, &hashlist)) != NULL) {
+ avedata = (aveData *)hptr->data;
+ for (j = 0; j < vsp->numval; j++) { /* iterate thro result values */
+ int fp_bad;
+ vp = &vsp->vlist[j];
+ k = j; /* index into stored inst list, result may differ */
+ if ((vsp->numval > 1) || (avedata->desc.indom != PM_INDOM_NULL)) {
+ if ((k < avedata->listsize) && (avedata->instlist[k]->inst != vp->inst)) {
+ for (k = 0; k < avedata->listsize; k++) {
+ if (vp->inst == avedata->instlist[k]->inst)
+ break; /* k now correct */
+ }
+ if (k == avedata->listsize) {
+ pmiderr(vsp->pmid, "ignoring new instance found on second pass\n");
+ continue;
+ }
+ }
+ else if (k >= avedata->listsize) {
+ k = avedata->listsize;
+ pmiderr(vsp->pmid, "ignoring new instance found on second pass\n");
+ continue;
+ }
+ }
+ instdata = avedata->instlist[k];
+
+ if ((sts = pmExtractValue(vsp->valfmt, vp, avedata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
+ pmiderr(avedata->desc.pmid, "failed to extract value: %s\n", pmErrStr(sts));
+ continue;
+ }
+ fp_bad = 0;
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(av.d) == FP_NAN;
+#else
+#ifdef HAVE_ISNAN
+ fp_bad = isnan(av.d);
+#endif
+#endif
+ if (fp_bad)
+ continue;
+
+ /* reset values from first pass needed in this second pass */
+ if (instdata->bintotal == 0) { /* 1st time instance seen on 2nd pass */
+ instdata->bintotal = instdata->count;
+ instdata->lasttime = result->timestamp;
+ instdata->firsttime = result->timestamp;
+ instdata->lastval = av.d;
+ if (avedata->desc.sem == PM_SEM_COUNTER)
+ instdata->count = 0;
+ else {
+ unsigned int sts;
+ instdata->count = 1;
+ sts = findbin(avedata->desc.pmid, av.d, instdata->min, instdata->max);
+ instdata->bin[sts]++;
+ }
+ continue;
+ }
+
+ timediff = result->timestamp;
+ tsub(&timediff, &instdata->lasttime);
+ diff = tosec(timediff);
+ wrap = 0;
+ if (avedata->desc.sem == PM_SEM_COUNTER) {
+ diff *= avedata->scale;
+ if (diff == 0.0) continue;
+ if (instdata->marked)
+ val = av.d;
+ else
+ val = unwrap(av.d, instdata->lastval, avedata->desc.type);
+ if (instdata->marked || val < instdata->lastval) {
+ /* mark or not first one & counter not monotonic increasing */
+ wrap = 1;
+ instdata->marked = 0;
+ tadd(&instdata->firsttime, &result->timestamp);
+ tsub(&instdata->firsttime, &instdata->lasttime);
+ }
+ else {
+ unsigned int sts;
+ val = (val - instdata->lastval) / diff;
+ sts = findbin(avedata->desc.pmid, val, instdata->min, instdata->max);
+ instdata->bin[sts]++;
+ }
+ }
+ else { /* for the other semantics */
+ unsigned int sts;
+ val = av.d;
+ sts = findbin(avedata->desc.pmid, val, instdata->min, instdata->max);
+ instdata->bin[sts]++;
+ }
+ if (!wrap) {
+ instdata->count++;
+ }
+ instdata->lastval = av.d;
+ instdata->lasttime = result->timestamp;
+ }
+ }
+ }
+}
+
+static void
+calcaverage(pmResult *result)
+{
+ int i, j, k;
+ int sts;
+ int wrap;
+ double val;
+ pmDesc desc;
+ pmAtomValue av;
+ pmValue *vp;
+ pmValueSet *vsp;
+ __pmHashNode *hptr = NULL;
+ aveData *avedata = NULL;
+ instData *instdata;
+ double diff;
+ double rate = 0;
+ struct timeval timediff;
+
+ if (result->numpmid == 0) /* mark record */
+ markrecord(result);
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ if (vsp->numval == 0)
+ continue;
+ else if (vsp->numval < 0) {
+ pmiderr(vsp->pmid, "failed in archive value fetch: %s\n", pmErrStr(vsp->numval));
+ continue;
+ }
+
+ /* check if pmid already in hash list */
+ if ((hptr = __pmHashSearch(vsp->pmid, &hashlist)) == NULL) {
+ if ((sts = pmLookupDesc(vsp->pmid, &desc)) < 0) {
+ pmiderr(vsp->pmid, "cannot find descriptor: %s\n", pmErrStr(sts));
+ continue;
+ }
+
+ if (desc.type != PM_TYPE_32 && desc.type != PM_TYPE_U32 &&
+ desc.type != PM_TYPE_64 && desc.type != PM_TYPE_U64 &&
+ desc.type != PM_TYPE_FLOAT && desc.type != PM_TYPE_DOUBLE) {
+ continue; /* cannot average non-numeric metrics */
+ }
+
+ /* create a new one & add to list */
+ avedata = (aveData*) malloc(sizeof(aveData));
+ newHashItem(vsp, &desc, avedata, &result->timestamp);
+ if (__pmHashAdd(avedata->desc.pmid, (void*)avedata, &hashlist) < 0) {
+ pmiderr(avedata->desc.pmid, "failed %s hash table insertion\n", pmProgname);
+ /* free memory allocated above on insert failure */
+ for (j = 0; j < vsp->numval; j++)
+ if (avedata->instlist[j]) free(avedata->instlist[j]);
+ if (avedata->instlist) free(avedata->instlist);
+ continue;
+ }
+ }
+ else { /* pmid exists - update statistics */
+ avedata = (aveData*)hptr->data;
+ for (j = 0; j < vsp->numval; j++) { /* iterate thro result values */
+ int fp_bad;
+ vp = &vsp->vlist[j];
+ k = j; /* index into stored inst list, result may differ */
+ if ((vsp->numval > 1) || (avedata->desc.indom != PM_INDOM_NULL)) {
+ /* must store values using correct inst - probably in correct order already */
+ if ((k < avedata->listsize) && (avedata->instlist[k]->inst != vp->inst)) {
+ for (k = 0; k < avedata->listsize; k++) {
+ if (vp->inst == avedata->instlist[k]->inst) {
+ break; /* k now correct */
+ }
+ }
+ if (k == avedata->listsize) { /* no matching inst was found */
+ newHashInst(vp, avedata, vsp->valfmt, &result->timestamp, k);
+ continue;
+ }
+ }
+ else if (k >= avedata->listsize) {
+ k = avedata->listsize;
+ newHashInst(vp, avedata, vsp->valfmt, &result->timestamp, k);
+ continue;
+ }
+ }
+ instdata = avedata->instlist[k];
+
+ if ((sts = pmExtractValue(vsp->valfmt, vp, avedata->desc.type, &av, PM_TYPE_DOUBLE)) < 0) {
+ pmiderr(avedata->desc.pmid, "failed to extract value: %s\n", pmErrStr(sts));
+ continue;
+ }
+ fp_bad = 0;
+#ifdef HAVE_FPCLASSIFY
+ fp_bad = fpclassify(av.d) == FP_NAN;
+#else
+#ifdef HAVE_ISNAN
+ fp_bad = isnan(av.d);
+#endif
+#endif
+ if (fp_bad)
+ continue;
+ timediff = result->timestamp;
+ tsub(&timediff, &instdata->lasttime);
+ diff = tosec(timediff);
+ wrap = 0;
+ if (avedata->desc.sem == PM_SEM_COUNTER) {
+ diff *= avedata->scale;
+ if (diff == 0.0) continue;
+ if (instdata->marked)
+ val = av.d;
+ else
+ val = unwrap(av.d, instdata->lastval, avedata->desc.type);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char *name = NULL;
+ pmNameID(avedata->desc.pmid, &name);
+ fprintf(stderr, "%s base value is %f, count %d\n",
+ name, val, instdata->count+1);
+ if (name) free(name);
+ }
+#endif
+ if (instdata->marked || val < instdata->lastval) {
+ /* either previous record was a "mark", or this is not */
+ /* the first one, and counter not monotonic increasing */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ char *name = NULL;
+ pmNameID(avedata->desc.pmid, &name);
+ fprintf(stderr, "%s counter wrapped or <mark>\n", name);
+ if (name) free(name);
+ }
+#endif
+ wrap = 1;
+ instdata->marked = 0;
+ tadd(&instdata->firsttime, &result->timestamp);
+ tsub(&instdata->firsttime, &instdata->lasttime);
+ }
+ else {
+ rate = (val - instdata->lastval) / diff;
+ instdata->stocave += rate;
+ if (!instdata->marked)
+ instdata->timeave += (val - instdata->lastval);
+ else {
+ instdata->marked = 0;
+ /* remove the timeslice in question from time-based calc */
+ tadd(&instdata->firsttime, &result->timestamp);
+ tsub(&instdata->firsttime, &instdata->lasttime);
+ }
+ if (instdata->count == 0) { /* 1st time */
+ instdata->min = instdata->max = rate;
+ instdata->sum = (val - instdata->lastval);
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ char *name = NULL;
+ char *istr = NULL;
+
+ pmNameID(avedata->desc.pmid, &name);
+ if (pmNameInDom(avedata->desc.indom,
+ instdata->inst, &istr) < 0)
+ istr = NULL;
+ if (rate < instdata->min) {
+ fprintf(stderr, "new min value for %s "
+ "(inst[%s]: %f) at ",
+ name, (istr == NULL ? "":istr), rate);
+ __pmPrintStamp(stderr, &result->timestamp);
+ fprintf(stderr, "\n");
+ }
+ if (rate > instdata->max) {
+ fprintf(stderr, "new max value for %s "
+ "(inst[%s]: %f) at ",
+ name, (istr == NULL ? "":istr), rate);
+ __pmPrintStamp(stderr, &result->timestamp);
+ fprintf(stderr, "\n");
+ }
+ if (name) free(name);
+ if (istr) free(istr);
+ }
+#endif
+ if (rate < instdata->min) {
+ instdata->min = rate;
+ instdata->mintime = result->timestamp;
+ }
+ if (rate > instdata->max) {
+ instdata->max = rate;
+ instdata->maxtime = result->timestamp;
+ }
+ instdata->sum += (val - instdata->lastval);
+ }
+ }
+ }
+ else { /* for the other semantics - discrete & instantaneous */
+ val = av.d;
+ instdata->sum += val;
+ instdata->stocave += val;
+ if (val < instdata->min) {
+ instdata->min = val;
+ instdata->mintime = result->timestamp;
+ }
+ if (val > instdata->max) {
+ instdata->max = val;
+ instdata->maxtime = result->timestamp;
+ }
+ if (!instdata->marked)
+ instdata->timeave += instdata->lastval*diff;
+ else {
+ instdata->marked = 0;
+ /* remove the timeslice in question from time-based calc */
+ tadd(&instdata->firsttime, &result->timestamp);
+ tsub(&instdata->firsttime, &instdata->lasttime);
+ }
+ }
+ if (!wrap) {
+ instdata->count++;
+#ifdef PCP_DEBUG
+ if ((pmDebug & DBG_TRACE_APPL1) &&
+ (avedata->desc.sem != PM_SEM_COUNTER || instdata->count > 0)) {
+ char *name = NULL;
+ double metricspan = 0.0;
+ struct timeval metrictimespan;
+
+ metrictimespan = result->timestamp;
+ tsub(&metrictimespan, &instdata->firsttime);
+ metricspan = tosec(metrictimespan);
+ pmNameID(avedata->desc.pmid, &name);
+
+ if (avedata->desc.sem == PM_SEM_COUNTER) {
+ fprintf(stderr, "++ %s timedelta=%f count=%d\n"
+ "sum=%f min=%f max=%f stocsum=%f\n"
+ "rate=%f timesum=%f (+%f) timespan=%f\n",
+ name, diff, instdata->count, instdata->sum,
+ instdata->min, instdata->max,
+ instdata->stocave, rate, instdata->timeave,
+ diff * (val - instdata->lastval) / 2,
+ metricspan);
+ }
+ else { /* non-counters */
+ fprintf(stderr, "++ %s timedelta=%f count=%d\n"
+ "sum=%f min=%f max=%f stocsum=%f\n"
+ "lastval=%f timesum=%f (+%f) timespan=%f\n",
+ name, diff, instdata->count, instdata->sum,
+ instdata->min, instdata->max,
+ instdata->stocave, instdata->lastval,
+ instdata->timeave, instdata->lastval*diff,
+ metricspan);
+ }
+ if (name)
+ free(name);
+ }
+#endif
+ }
+ instdata->lastval = av.d;
+ instdata->lasttime = result->timestamp;
+ }
+ }
+ }
+}
+
+static int
+override(int opt, pmOptions *opts)
+{
+ if (opt == 'a' || opt == 'H' || opt == 'N' || opt == 'p' || opt == 's')
+ return 1;
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c, i, sts, trip, exitstatus = 0;
+ int lflag = 0; /* no label by default */
+ int Hflag = 0; /* no header by default */
+ pmResult *result;
+ struct timeval timespan = {0, 0};
+ char *endnum;
+ char *archive;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'a': /* provide all information */
+ stocaveflag = timeaveflag = lflag = countflag = minflag = maxflag = 1;
+ sumflag = 0;
+ break;
+
+ case 'b': /* use both averages */
+ stocaveflag = 1;
+ timeaveflag = 1;
+ sumflag = 0;
+ break;
+
+ case 'B': /* number of distribution bins */
+ sts = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || sts < 0) {
+ pmprintf("%s: -B requires positive numeric argument\n",
+ pmProgname);
+ opts.errors++;
+ }
+ else
+ nbins = (unsigned int)sts;
+ break;
+
+ case 'f': /* spreadsheet format - use tab delimiters */
+ delimiter = '\t';
+ break;
+
+ case 'F': /* spreadsheet format - use comma delimiters */
+ delimiter = ',';
+ break;
+
+ case 'H': /* print columns headings */
+ Hflag = 1;
+ break;
+
+ case 'i': /* print timestamp for minimum */
+ mintimeflag = 1;
+ break;
+
+ case 'I': /* print timestamp for maximum */
+ maxtimeflag = 1;
+ break;
+
+ case 'l': /* display label */
+ lflag = 1;
+ break;
+
+ case 'm': /* print minimums */
+ minflag = 1;
+ break;
+
+ case 'M': /* print maximums */
+ maxflag = 1;
+ break;
+
+ case 'N': /* suppress fetch warnings */
+ warnflag = 0;
+ break;
+
+ case 'p': /* number of digits after decimal point */
+ precision = (unsigned int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: -p requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 's': /* print sums (and only sums) */
+ stocaveflag = timeaveflag = lflag = countflag = minflag = maxflag = 0;
+ sumflag = 1;
+ break;
+
+ case 'v': /* verbose "fetch" warnings reported */
+ warnflag = 1;
+ break;
+
+ case 'x': /* use only stochastic counter averages */
+ stocaveflag = 1;
+ timeaveflag = 0;
+ sumflag = 0;
+ break;
+
+ case 'y': /* print sample count */
+ countflag = 1;
+ break;
+ }
+ }
+
+ if (!opts.errors && opts.optind >= argc) {
+ pmprintf("Error: no archive specified\n\n");
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ archive = argv[opts.optind++];
+ __pmAddOptArchive(&opts, archive);
+ opts.flags &= ~PM_OPTFLAG_DONE;
+ __pmEndOptions(&opts);
+
+ if ((sts = c = pmNewContext(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, archive, pmErrStr(sts));
+ exit(1);
+ }
+
+ if (pmGetContextOptions(c, &opts) < 0) {
+ pmflush(); /* runtime errors only at this stage */
+ exit(EXIT_FAILURE);
+ }
+
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ if (lflag)
+ printlabel();
+
+ logspan = tosec(opts.finish) - tosec(opts.start);
+
+ /* check which timestamp print format we should be using */
+ timespan = opts.finish;
+ tsub(&timespan, &opts.start);
+ if (timespan.tv_sec > 86400) /* seconds per day: 60*60*24 */
+ dayflag = 1;
+
+ for (trip = 0; trip < 2; trip++) { /* two passes if binning */
+ for ( ; ; ) {
+ if ((sts = pmFetchArchive(&result)) < 0)
+ break;
+
+ if (opts.finish.tv_sec > result->timestamp.tv_sec ||
+ (opts.finish.tv_sec == result->timestamp.tv_sec &&
+ opts.finish.tv_usec >= result->timestamp.tv_usec)) {
+ if (trip == 0)
+ calcaverage(result);
+ else
+ calcbinning(result);
+ pmFreeResult(result);
+ }
+ else {
+ pmFreeResult(result);
+ sts = PM_ERR_EOL;
+ break;
+ }
+ }
+
+ if (trip == 0 && nbins > 0) { /* distribute values into bins */
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ fprintf(stderr, "resetting for second iteration\n");
+#endif
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) {
+ fprintf(stderr, "%s: pmSetMode reset failed: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+ else
+ break; /* two passes only when doing binning */
+ }
+
+ if (sts != PM_ERR_EOL) {
+ fprintf(stderr, "%s: fetch failed: %s\n", pmProgname, pmErrStr(sts));
+ exitstatus = 1;
+ }
+
+ if (Hflag)
+ printheaders();
+
+ if (opts.optind >= argc) { /* print all results */
+ if ((sts = pmTraversePMNS("", printsummary)) < 0) {
+ fprintf(stderr, "%s: PMNS traversal failed: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+ }
+ else { /* print only selected results */
+ for (i = opts.optind; i < argc; i++) {
+ char *msg;
+
+ if (pmParseMetricSpec(argv[i], 1, archive, &msp, &msg) < 0) {
+ fputs(msg, stderr);
+ free(msg);
+ continue;
+ }
+ if ((sts = pmTraversePMNS(msp->metric, printsummary)) < 0)
+ fprintf(stderr, "%s: PMNS traversal failed for %s: %s\n",
+ pmProgname, msp->metric, pmErrStr(sts));
+ pmFreeMetricSpec(msp);
+ }
+ }
+
+ exit(exitstatus);
+}
diff --git a/src/pmlogsummary/pmwtf.sh b/src/pmlogsummary/pmwtf.sh
new file mode 100755
index 0000000..a47372b
--- /dev/null
+++ b/src/pmlogsummary/pmwtf.sh
@@ -0,0 +1,316 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2008-2010 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Compare two PCP archives and report significant differences
+#
+
+# Get standard environment
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+
+cat > $tmp/usage << EOF
+# Usage: [options] archive1 [archive2]
+
+Options:
+ -d,--keep debug, keep intermediate files
+ -p=N,--precision=N number of digits to display after the decimal point
+ -q=N,--threshold=N change interesting threshold to be > N or < 1/N [N=2]
+ --skip-missing do not report metrics missing between the archives
+ --skip-excluded do not report the list of metrics being excluded
+ --start
+ --finish
+ -B=TIME,--begin=TIME start time for second archive (optional)
+ -E=TIME,--end=TIME end time for second archive (optional)
+ -x=REGEX egrep(1) pattern of metric(s) to be excluded
+ -X=FILE file containing egrep(1) patterns to exclude
+ --timezone
+ --hostzone
+ --help
+EOF
+
+_usage()
+{
+ pmgetopt --usage --progname=$prog --config=$tmp/usage
+ exit $status
+}
+
+_fix()
+{
+ sed -e 's/ *\([0-9][0-9.]*\)\([^"]*\)$/|\1/' \
+ | egrep -v -f $tmp/exclude \
+ | sort -t\| -k1,1
+}
+
+cat <<'End-of-File' >$tmp/exclude
+^pmcd.pmlogger.port
+End-of-File
+
+thres=2
+opts=""
+start1=""
+start2=""
+finish1=""
+finish2=""
+precision=3
+skip_missing=false
+skip_excluded=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -d) trap "exit \$status" 0 1 2 3 15
+ otmp="$tmp"
+ tmp=`pwd`/tmp
+ [ -d "$tmp" ] || mkdir "$tmp" || exit 1
+ mv $otmp/exclude $tmp/exclude
+ rmdir $otmp
+ ;;
+ -p) precision="$2"
+ shift
+ opts="$opts -p $precision"
+ ;;
+ -q) thres="$2"
+ shift
+ ;;
+ -S) start1="$2"
+ shift
+ ;;
+ -T) finish1="$2"
+ shift
+ ;;
+ -B) start2="$2"
+ shift
+ ;;
+ -E) finish2="$2"
+ shift
+ ;;
+ -x) echo "$2" >>$tmp/exclude
+ shift
+ ;;
+ -X) cat "$2" >>$tmp/exclude
+ shift
+ ;;
+ -z) opts="$opts -z"
+ ;;
+ -Z) opts="$opts -Z $2"
+ shift
+ ;;
+ --skip-missing)
+ skip_missing=true
+ ;;
+ --skip-excluded)
+ skip_excluded=true
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) _usage
+ # NOTREACHED
+ ;;
+ esac
+ shift
+done
+
+if [ $# -lt 1 -o $# -gt 2 ]
+then
+ _usage
+ # NOTREACHED
+elif [ $# -eq 2 ]
+then
+ arch1="$1"
+ arch2="$2"
+else
+ arch1="$1"
+ arch2="$1"
+fi
+
+[ $precision -lt 3 -o $precision -gt 15 ] && precision=3
+colwidth=`expr 12 + $precision`
+
+echo "Directory: `pwd`"
+if ! $skip_excluded
+then
+ echo "Excluded metrics:"
+ sed -e 's/^/ /' <$tmp/exclude
+fi
+echo
+
+options="$opts"
+if [ "X$start1" != X ]; then
+ options="$options -S $start1"
+fi
+if [ "X$finish1" != X ]; then
+ options="$options -T $finish1"
+fi
+pmlogsummary -N $options $arch1 2>$tmp/err | _fix >$tmp/1
+if [ -s $tmp/err ]
+then
+ echo "Warnings from pmlogsummary ... $arch1"
+ cat $tmp/err
+ echo
+fi
+
+options="$opts"
+if [ "X$start2" != X ]; then
+ options="$options -S $start2"
+elif [ "X$start1" != X ]; then
+ options="$options -S $start1"
+fi
+if [ "X$finish2" != X ]; then
+ options="$options -T $finish2"
+elif [ "X$finish1" != X ]; then
+ options="$options -T $finish1"
+fi
+pmlogsummary -N $options $arch2 2>$tmp/err | _fix >$tmp/2
+if [ -s $tmp/err ]
+then
+ echo "Warnings from pmlogsummary ... $arch2"
+ cat $tmp/err
+ echo
+fi
+
+if [ -z "$start1" ]
+then
+ window1="start"
+else
+ window1="$start1"
+fi
+if [ -z "$finish1" ]
+then
+ window1="$window1-end"
+else
+ window1="$window1-$finish1"
+fi
+if [ -z "$start2" ]
+then
+ window2="start"
+else
+ window2="$start2"
+fi
+if [ -z "$finish2" ]
+then
+ window2="$window2-end"
+else
+ window2="$window2-$finish2"
+fi
+
+if ! $skip_missing
+then
+ join -t\| -v 2 $tmp/1 $tmp/2 >$tmp/tmp
+ if [ -s $tmp/tmp ]
+ then
+ echo "Missing from $arch1 $window1 (not compared) ..."
+ sed <$tmp/tmp -e 's/|.*//' -e 's/^/ /'
+ echo
+ fi
+
+ join -t\| -v 1 $tmp/1 $tmp/2 >$tmp/tmp
+ if [ -s $tmp/tmp ]
+ then
+ echo "Missing from $arch2 $window2 (not compared) ..."
+ sed <$tmp/tmp -e 's/|.*//' -e 's/^/ /'
+ echo
+ fi
+fi
+
+a1=`basename "$arch1"`
+a2=`basename "$arch2"`
+echo "$thres" | awk '
+ { printf "Ratio Threshold: > %.2f or < %.3f\n",'"$thres"',1/'"$thres"'
+ printf "%*s %*s Ratio Metric-Instance\n",
+ '$colwidth', "'"$a1"'", '$colwidth', "'"$a2"'" }'
+if [ -z "$start1" ]
+then
+ window1="start"
+else
+ window1="$start1"
+fi
+if [ -z "$finish1" ]
+then
+ window1="$window1-end"
+else
+ window1="$window1-$finish1"
+fi
+if [ -z "$start2" ]
+then
+ window2="start"
+else
+ window2="$start2"
+fi
+if [ -z "$finish2" ]
+then
+ window2="$window2-end"
+else
+ window2="$window2-$finish2"
+fi
+printf '%*s %*s\n' $colwidth "$window1" $colwidth "$window2"
+join -t\| $tmp/1 $tmp/2 \
+| awk -F\| '
+function doval(v)
+{
+ precision='"$precision"'
+ extra=precision-3
+ if (v > 99999999)
+ printf "%*.*f%*s",15+extra,0,v,1," "
+ else if (v > 999)
+ printf "%*.*f%*s",11,0,v,2+precision," "
+ else if (v > 99)
+ printf "%*.*f%*s",13+extra,1+extra,v,3," "
+ else if (v > 9)
+ printf "%*.*f%*s",14+extra,2+extra,v,2," "
+ else
+ printf "%*.*f%*s",15+extra,precision,v,1," "
+}
+$3+0 == 0 || $2+0 == 0 {
+ if ($3 == $2)
+ next
+ doval($2)
+ doval($3)
+ printf " "
+ if ($3+0 == 0)
+ printf "|-| %s\n",$1
+ else if ($2+0 == 0)
+ printf "|+| %s\n",$1
+ next
+}
+$2 / $3 > '"$thres"' || $3 / $2 > '"$thres"' {
+ doval($2)
+ doval($3)
+ printf " "
+ r = $3/$2
+ if (r < 0.001)
+ printf " 0.001-"
+ else if (r < 0.01)
+ printf "%6.3f ",r
+ else if (r > 100)
+ printf " 100+ "
+ else
+ printf "%5.2f ",r
+ printf " %s\n",$1
+ }' \
+| sort -nr -k 3,3 \
+| sed -e 's/100+/>100/' -e 's/ 0.001-/<0.001 /'
+
+status=0
+exit
diff --git a/src/pmmgr/GNUmakefile b/src/pmmgr/GNUmakefile
new file mode 100644
index 0000000..2c173aa
--- /dev/null
+++ b/src/pmmgr/GNUmakefile
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2013-2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = config
+CXXMDTARGET = pmmgr$(EXECSUFFIX)
+HFILES = pmmgr.h
+CXXFILES = pmmgr.cxx
+LLDLIBS = $(PCPLIB) $(LIB_FOR_PTHREADS) $(LIB_FOR_ATOMIC)
+LLDFLAGS += $(RDYNAMIC_FLAG) $(PIELDFLAGS)
+LCFLAGS += $(PIECFLAGS)
+LDIRT = *.log pmmgr.service
+
+default: build-me
+
+ifeq ($(BUILD_PMMGR),yes)
+build-me: $(SUBDIRS) $(CXXMDTARGET) pmmgr.service
+ $(SUBDIRS_MAKERULE)
+
+pmmgr.service: pmmgr.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+install: $(SUBDIRS) $(CXXMDTARGET)
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMMGROPTIONS_PATH)`
+ $(INSTALL) -m 644 pmmgr.options $(PCP_PMMGROPTIONS_PATH)
+ $(INSTALL) -m 755 rc_pmmgr $(PCP_RC_DIR)/pmmgr
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmmgr.service $(PCP_SYSTEMDUNIT_DIR)/pmmgr.service
+endif
+ $(INSTALL) -m 755 $(CXXMDTARGET) $(PCP_BINADM_DIR)/$(CXXMDTARGET)
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)/pmmgr
+else
+build-me:
+ @echo not building pmmgr
+install:
+ @echo not installing pmmgr
+endif
+
+$(OBJECTS): $(HFILES)
+
+include $(BUILDRULES)
+
+default_pcp : default
+
+install_pcp : install
+
+# Hey, does anyone have a spare vowel?
+
+.PHONY: build-me
diff --git a/src/pmmgr/TODO b/src/pmmgr/TODO
new file mode 100644
index 0000000..b551687
--- /dev/null
+++ b/src/pmmgr/TODO
@@ -0,0 +1,12 @@
+- pmmgr.1 EXAMPLE CONFIGURATIONS
+- optionally delay pm*conf
+- pmlogreduce
+- log aging in background while new pmlogger's already running
+- old log compression (until we get libpcp zlib or something)
+- email error reporting?
+- qa
+- port to mingw?
+- port to cygwin?
+- pmlogger/pmie .log rotation
+- pid->pid_t cleanup
+
diff --git a/src/pmmgr/config/GNUmakefile b/src/pmmgr/config/GNUmakefile
new file mode 100644
index 0000000..0ecdf16
--- /dev/null
+++ b/src/pmmgr/config/GNUmakefile
@@ -0,0 +1,40 @@
+#!gmake
+#
+# Copyright (c) 2013-2014 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+LLDIRT =
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = pmie pmieconf pmlogconf pmlogger pmlogmerge pmlogmerge-rewrite pmlogmerge-granular \
+ README target-discovery.example-avahi
+
+PMMGR_SYSCONF_DIR=$(PCP_SYSCONF_DIR)/pmmgr
+
+default:
+
+build-me:
+
+include $(BUILDRULES)
+
+install:
+ $(INSTALL) -m 755 -d $(PMMGR_SYSCONF_DIR)
+ for file in $(LSRCFILES); do \
+ $(INSTALL) -m 644 $$file $(PMMGR_SYSCONF_DIR)/$$file; \
+ done
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmmgr/config/README b/src/pmmgr/config/README
new file mode 100644
index 0000000..d767635
--- /dev/null
+++ b/src/pmmgr/config/README
@@ -0,0 +1,13 @@
+This is the default built-in configuration directory for pmmgr.
+See man pmmgr(1) for details of the individual files that are
+read to extract configuration lines. Other files in this directory
+are for documentation / examples.
+
+The default built-in configuration items for pmmgr are almost sufficient
+to act as a system's primary pmlogger and pmie. The exceptions are the
+actual enablement of pmlogger & pmie, and their auto-generated configuration,
+which are enabled by some empty files here.
+
+Additional possible but non-default configurations are given in .example*
+files here. Rename them to drop the .example* filename extension to
+activate them.
diff --git a/src/pmmgr/config/pmie b/src/pmmgr/config/pmie
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmie
diff --git a/src/pmmgr/config/pmieconf b/src/pmmgr/config/pmieconf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmieconf
diff --git a/src/pmmgr/config/pmlogconf b/src/pmmgr/config/pmlogconf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmlogconf
diff --git a/src/pmmgr/config/pmlogger b/src/pmmgr/config/pmlogger
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmlogger
diff --git a/src/pmmgr/config/pmlogmerge b/src/pmmgr/config/pmlogmerge
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmlogmerge
diff --git a/src/pmmgr/config/pmlogmerge-granular b/src/pmmgr/config/pmlogmerge-granular
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmlogmerge-granular
diff --git a/src/pmmgr/config/pmlogmerge-rewrite b/src/pmmgr/config/pmlogmerge-rewrite
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pmmgr/config/pmlogmerge-rewrite
diff --git a/src/pmmgr/config/target-discovery.example-avahi b/src/pmmgr/config/target-discovery.example-avahi
new file mode 100644
index 0000000..c3d9d94
--- /dev/null
+++ b/src/pmmgr/config/target-discovery.example-avahi
@@ -0,0 +1 @@
+avahi,timeout=2.5
diff --git a/src/pmmgr/pmmgr.cxx b/src/pmmgr/pmmgr.cxx
new file mode 100644
index 0000000..5ff210f
--- /dev/null
+++ b/src/pmmgr/pmmgr.cxx
@@ -0,0 +1,1285 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 600
+#endif
+#include "pmmgr.h"
+#include "impl.h"
+
+#include <sys/stat.h>
+#include <cassert>
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+
+extern "C" {
+#include <fcntl.h>
+#include <unistd.h>
+#include <glob.h>
+#include <sys/wait.h>
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+#ifdef IS_LINUX
+#include <sys/syscall.h>
+#endif
+}
+
+
+using namespace std;
+
+// ------------------------------------------------------------------------
+
+
+int quit;
+int polltime = 60;
+
+
+// ------------------------------------------------------------------------
+
+
+// Create a string that is safe to pass to system(3), i.e., sh -c,
+// by quoting metacharacters. This transform generally should be
+// applied only once.
+string
+sh_quote(const string& input)
+{
+ string output;
+ for (unsigned i=0; i<input.length(); i++)
+ {
+ char c = input[i];
+ if ((ispunct(c) || isspace(c)) && // quite aggressive
+ (c != ':' && c != '.' && c != '_' && c != '/' && c != '-')) // safe & popular punctuation
+ output += '\\';
+ output += c;
+ }
+
+ return output;
+}
+
+
+// Print a string to cout/cerr progress reports, similar to the
+// stuff produced by __pmNotifyErr
+ostream&
+timestamp(ostream &o)
+{
+ time_t now;
+ time (&now);
+ char *now2 = ctime (&now);
+ if (now2)
+ now2[19] = '\0'; // overwrite \n
+
+ return o << "[" << (now2 ? now2 : "") << "] " << pmProgname << "("
+ << getpid()
+#ifdef HAVE_PTHREAD_H
+#ifdef IS_LINUX
+ << "/" << syscall(SYS_gettid)
+#else
+ << "/" << pthread_self()
+#endif
+#endif
+ << "): ";
+}
+
+
+extern "C" void *
+pmmgr_daemon_poll_thread (void* a)
+{
+ pmmgr_daemon* d = (pmmgr_daemon*) a;
+ d->poll();
+ return 0;
+}
+
+
+// A wrapper for something like system(3), but responding quicker to
+// interrupts and standardizing tracing.
+int
+pmmgr_configurable::wrap_system(const std::string& cmd)
+{
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "running " << cmd << endl;
+
+ int pid = fork();
+ if (pid == 0)
+ {
+ // child
+ int rc = execl ("/bin/sh", "sh", "-c", cmd.c_str(), NULL);
+ timestamp(cerr) << "failed to execl sh -c " << cmd << " rc=" << rc << endl;
+ _exit (1);
+ }
+ else if (pid < 0)
+ {
+ // error
+ timestamp(cerr) << "fork for " << cmd << " failed: errno=" << errno << endl;
+ return -1;
+ }
+ else
+ {
+ // parent
+ int status = -1;
+ int rc;
+ //timestamp(cout) << "waiting for pid=" << pid << endl;
+
+ do { rc = waitpid(pid, &status, 0); } while (!quit && rc == -1 && errno == EINTR); // TEMP_FAILURE_RETRY
+ if (quit)
+ {
+ // timestamp(cout) << "killing pid=" << pid << endl;
+ kill (pid, SIGTERM); // just to be on the safe side
+ // it might linger a few seconds in zombie mode
+ }
+
+ //timestamp(cout) << "done status=" << status << endl;
+ if (status != 0)
+ timestamp(cerr) << "system(" << cmd << ") failed: rc=" << status << endl;
+ return status;
+ }
+}
+
+
+
+// ------------------------------------------------------------------------
+
+
+pmmgr_configurable::pmmgr_configurable(const string& dir):
+ config_directory(dir)
+{
+}
+
+
+vector<string>
+pmmgr_configurable::get_config_multi(const string& file) const
+{
+ vector<string> lines;
+
+ string complete_filename = config_directory + (char)__pmPathSeparator() + file;
+ ifstream f (complete_filename.c_str());
+ while (f.good()) {
+ string line;
+ getline(f, line);
+ if (! f.good())
+ break;
+ if (line != "")
+ lines.push_back(line);
+ }
+
+ return lines;
+}
+
+
+bool
+pmmgr_configurable::get_config_exists(const string& file) const
+{
+ string complete_filename = config_directory + (char)__pmPathSeparator() + file;
+ ifstream f (complete_filename.c_str());
+ return (f.good());
+}
+
+
+string
+pmmgr_configurable::get_config_single(const string& file) const
+{
+ vector<string> lines = get_config_multi (file);
+ if (lines.size() == 1)
+ return lines[0];
+ else
+ return "";
+}
+
+ostream&
+pmmgr_configurable::timestamp(ostream& o)
+{
+ return ::timestamp(o) << config_directory << ": ";
+}
+
+
+
+// ------------------------------------------------------------------------
+
+
+pmMetricSpec*
+pmmgr_job_spec::parse_metric_spec (const string& spec)
+{
+ if (parsed_metric_cache.find(spec) != parsed_metric_cache.end())
+ return parsed_metric_cache[spec];
+
+ const char* specstr = spec.c_str();
+ pmMetricSpec* pms = 0;
+ char *errmsg;
+ char dummy_host[] = "";
+ int rc = pmParseMetricSpec (specstr,
+ 0, dummy_host, /* both ignored */
+ & pms, & errmsg);
+ if (rc < 0) {
+ timestamp(cerr) << "hostid-metrics '" << specstr << "' parse error: " << errmsg << endl;
+ free (errmsg);
+ }
+
+ parsed_metric_cache[spec] = pms;
+ return pms;
+}
+
+
+pmmgr_hostid
+pmmgr_job_spec::compute_hostid (const pcp_context_spec& ctx)
+{
+ int pmc = pmNewContext (PM_CONTEXT_HOST, ctx.c_str());
+ if (pmc < 0)
+ return "";
+
+ // parse all the hostid metric specifications
+ vector<string> hostid_specs = get_config_multi("hostid-metrics");
+ if (hostid_specs.size() == 0)
+ hostid_specs.push_back(string("pmcd.hostname"));
+
+ // fetch all hostid metrics in sequence
+ vector<string> hostid_fields;
+ for (unsigned i=0; i<hostid_specs.size(); i++)
+ {
+ pmMetricSpec* pms = parse_metric_spec (hostid_specs[i]);
+
+ pmID pmid;
+ int rc = pmLookupName (1, & pms->metric, &pmid);
+ if (rc < 0)
+ continue;
+
+ pmDesc desc;
+ rc = pmLookupDesc (pmid, & desc);
+ if (rc < 0)
+ continue;
+
+ if (desc.type != PM_TYPE_STRING)
+ continue;
+
+ if ((desc.indom != PM_INDOM_NULL) && pms->ninst > 0)
+ {
+ // reset the indom to include all elements
+ rc = pmDelProfile(desc.indom, 0, (int *)0);
+ if (rc < 0)
+ continue;
+
+ int *inums = (int *) malloc (pms->ninst * sizeof(int));
+ if (inums == NULL)
+ continue;
+ // NB: after this point, 'continue' must also free(inums);
+
+ // map the instance names to instance numbers
+ unsigned numinums_used = 0;
+ for (int j=0; j<pms->ninst; j++)
+ {
+ int inum = pmLookupInDom (desc.indom, pms->inst[j]);
+ if (inum < 0)
+ continue;
+ inums[numinums_used++] = inum;
+ }
+
+ // add the selected instances to the profile
+ rc = pmAddProfile (desc.indom, numinums_used, inums);
+ free (inums);
+ if (rc < 0)
+ continue;
+ }
+
+ // fetch the values
+ pmResult *r;
+ rc = pmFetch (1, &pmid, &r);
+ if (rc < 0)
+ continue;
+ // NB: after this point, 'continue' must also pmFreeResult(r)
+
+ // in-place sort value list by indom number
+ pmSortInstances(r);
+
+ // only vset[0] will be set, for csb->pmid
+ if (r->vset[0]->numval > 0)
+ {
+ for (int j=0; j<r->vset[0]->numval; j++) // iterate over instances
+ {
+ // fetch the string value
+ pmAtomValue av;
+ rc = pmExtractValue(r->vset[0]->valfmt,
+ & r->vset[0]->vlist[j],
+ PM_TYPE_STRING, & av, PM_TYPE_STRING);
+ if (rc < 0)
+ continue;
+
+ // at last! we have a string we can accumulate
+ hostid_fields.push_back (av.cp);
+ free (av.cp);
+ }
+ }
+
+ (void) pmFreeResult (r);
+ }
+
+ (void) pmDestroyContext (pmc);
+
+ // Sanitize the host-id metric values into a single string that is
+ // suitable for posix-portable-filenames, and not too ugly for
+ // someone to look at or type in.
+ //
+ // http://www.opengroup.org/onlinepubs/007904975/basedefs/xbd_chap03.html
+ string sanitized;
+ for (unsigned i=0; i<hostid_fields.size(); i++)
+ {
+ const string& f = hostid_fields[i];
+ if (f == "") continue;
+ if (sanitized != "") sanitized += "-"; // separate fields
+ for (unsigned j=0; j<f.length(); j++)
+ {
+ char c = f[j];
+ if (isalnum(c))
+ sanitized += c;
+ else if (c== '-' || c == '.' || c == '_')
+ sanitized += c;
+ else
+ // drop other non-portable characters NB: this can mean
+ // unintentional duplication in IDs, which a user can work
+ // around by configuring additional hostid metrics.
+ ;
+ }
+ }
+
+ return pmmgr_hostid (sanitized);
+}
+
+
+pmmgr_job_spec::pmmgr_job_spec(const std::string& config_directory):
+ pmmgr_configurable(config_directory)
+{
+ // We don't actually have to do any configuration parsing at this
+ // time. Let's do it during poll(), which makes us more responsive
+ // to run-time changes.
+}
+
+
+pmmgr_job_spec::~pmmgr_job_spec()
+{
+ // free any cached pmMetricSpec's
+ for (map<string,pmMetricSpec*>::iterator it = parsed_metric_cache.begin();
+ it != parsed_metric_cache.end();
+ ++it)
+ free (it->second); // aka pmFreeMetricSpec
+
+ // kill all our daemons created during poll()
+ for (map<pmmgr_hostid,pcp_context_spec>::iterator it = known_targets.begin();
+ it != known_targets.end();
+ ++it)
+ note_dead_hostid (it->first);
+}
+
+
+// ------------------------------------------------------------------------
+
+
+void
+pmmgr_job_spec::poll()
+{
+ if (quit) return;
+
+ // phase 1: run all discovery/probing functions to collect context-spec's
+ set<pcp_context_spec> new_specs;
+
+ vector<string> target_hosts = get_config_multi("target-host");
+ for (unsigned i=0; i<target_hosts.size(); i++)
+ new_specs.insert(target_hosts[i]);
+
+ vector<string> target_discovery = get_config_multi("target-discovery");
+ for (unsigned i=0; i<target_discovery.size() && !quit; i++)
+ {
+ char **urls = NULL;
+ const char *discovery = (target_discovery[i] == "")
+ ? NULL
+ : target_discovery[i].c_str();
+ int numUrls = pmDiscoverServices (PM_SERVER_SERVICE_SPEC, discovery, &urls);
+ if (numUrls <= 0)
+ continue;
+ for (int i=0; i<numUrls; i++)
+ new_specs.insert(string(urls[i]));
+ free ((void*) urls);
+ }
+
+ // fallback to logging the local server, if nothing else is configured/discovered
+ if (target_hosts.size() == 0 &&
+ target_discovery.size() == 0)
+ new_specs.insert("local:");
+
+ // phase 2: move previously-identified targets over, so we can tell who
+ // has come or gone
+ const map<pmmgr_hostid,pcp_context_spec> old_known_targets = known_targets;
+ known_targets.clear();
+
+ // phase 3: map the context-specs to hostids to find new hosts
+ map<pmmgr_hostid,double> known_target_scores;
+ for (set<pcp_context_spec>::iterator it = new_specs.begin();
+ it != new_specs.end() && !quit;
+ ++it)
+ {
+ struct timeval before, after;
+ __pmtimevalNow(& before);
+ pmmgr_hostid hostid = compute_hostid (*it);
+ __pmtimevalNow(& after);
+ double score = __pmtimevalSub(& after, & before); // the smaller, the preferreder
+
+ if (hostid != "") // verified existence/liveness
+ {
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "hostid " << hostid << " via " << *it << " time " << score << endl;
+
+ if (known_target_scores.find(hostid) == known_target_scores.end() ||
+ known_target_scores[hostid] > score) // previous slower than this one
+ {
+ known_targets[hostid] = *it;
+ known_target_scores[hostid] = score;
+ }
+ }
+ }
+
+ // phase 4a: compare old_known_targets vs. known_targets: look for any recently died
+ for (map<pmmgr_hostid,pcp_context_spec>::const_iterator it = old_known_targets.begin();
+ it != old_known_targets.end();
+ ++it)
+ {
+ const pmmgr_hostid& hostid = it->first;
+ if (known_targets.find(hostid) == known_targets.end())
+ note_dead_hostid (hostid);
+ }
+
+ // phase 4b: compare new known_targets & old_known_targets: look for recently born
+ for (map<pmmgr_hostid,pcp_context_spec>::const_iterator it = known_targets.begin();
+ it != known_targets.end();
+ ++it)
+ {
+ const pmmgr_hostid& hostid = it->first;
+ if (old_known_targets.find(hostid) == old_known_targets.end())
+ note_new_hostid (hostid, known_targets[hostid]);
+ }
+
+ // phase 5: poll all the live daemons
+ // NB: there is a parallelism opportunity, as running many pmlogconf/etc.'s in series
+ // is a possible bottleneck.
+#ifdef HAVE_PTHREAD_H
+ vector<pthread_t> threads;
+#endif
+ for (multimap<pmmgr_hostid,pmmgr_daemon*>::iterator it = daemons.begin();
+ it != daemons.end() && !quit;
+ ++it)
+ {
+#ifdef HAVE_PTHREAD_H
+ pthread_t foo;
+ int rc = pthread_create(&foo, NULL, &pmmgr_daemon_poll_thread, it->second);
+ if (rc == 0)
+ threads.push_back (foo);
+#else
+ int rc = -ENOSUPP;
+#endif
+ if (rc) // threading failed or running single-threaded
+ it->second->poll();
+ }
+
+#ifdef HAVE_PTHREAD_H
+ for (unsigned i=0; i<threads.size(); i++)
+ pthread_join (threads[i], NULL);
+#endif
+
+ // phase 6: garbage-collect ancient log-directory subdirs
+ string subdir_gc = get_config_single("log-subdirectory-gc");
+ if (subdir_gc == "")
+ subdir_gc = "90days";
+ struct timeval tv;
+ char *errmsg;
+ int rc = pmParseInterval(subdir_gc.c_str(), & tv, & errmsg);
+ if (rc < 0)
+ {
+ timestamp(cerr) << "log-subdirectory-gc '" << subdir_gc << "' parse error: " << errmsg << endl;
+ free (errmsg);
+ // default to 90days in another way
+ tv.tv_sec = 60 * 60 * 24 * 90;
+ tv.tv_usec = 0;
+ }
+ time_t now;
+ (void) time(& now);
+
+ // NB: check less frequently?
+
+ // XXX: getting a bit duplicative
+ string default_log_dir =
+ string(pmGetConfig("PCP_LOG_DIR")) + (char)__pmPathSeparator() + "pmmgr";
+ string log_dir = get_config_single ("log-directory");
+ if (log_dir == "") log_dir = default_log_dir;
+ else if(log_dir[0] != '/') log_dir = config_directory + (char)__pmPathSeparator() + log_dir;
+
+ glob_t the_blob;
+ string glob_pattern = log_dir + (char)__pmPathSeparator() + "*";
+ rc = glob (glob_pattern.c_str(),
+ GLOB_NOESCAPE
+#ifdef GLOB_ONLYDIR
+ | GLOB_ONLYDIR
+#endif
+ , NULL, & the_blob);
+ if (rc == 0)
+ {
+ for (unsigned i=0; i<the_blob.gl_pathc && !quit; i++)
+ {
+ string item_name = the_blob.gl_pathv[i];
+
+ // Reject if currently live hostid
+ // NB: basename(3) might modify the argument string, so we don't feed
+ // it item_name.c_str().
+ string target_name = basename(the_blob.gl_pathv[i]);
+ if (known_targets.find(target_name) != known_targets.end())
+ continue;
+
+ struct stat foo;
+ rc = stat (item_name.c_str(), & foo);
+ if (rc == 0 &&
+ S_ISDIR(foo.st_mode) &&
+ (foo.st_mtime + tv.tv_sec) < now)
+ {
+ // <Janine Melnitz>We've got one!!!!!</>
+ timestamp(cout) << "gc subdirectory " << item_name << endl;
+ string cleanup_cmd = "/bin/rm -rf " + sh_quote(item_name);
+ (void) wrap_system(cleanup_cmd);
+ }
+ }
+ }
+ globfree (& the_blob);
+}
+
+
+// ------------------------------------------------------------------------
+
+
+void
+pmmgr_job_spec::note_new_hostid(const pmmgr_hostid& hid, const pcp_context_spec& spec)
+{
+ timestamp(cout) << "new hostid " << hid << " at " << string(spec) << endl;
+
+ if (get_config_exists("pmlogger"))
+ daemons.insert(make_pair(hid, new pmmgr_pmlogger_daemon(config_directory, hid, spec)));
+
+ if (get_config_exists("pmie"))
+ daemons.insert(make_pair(hid, new pmmgr_pmie_daemon(config_directory, hid, spec)));
+}
+
+
+void
+pmmgr_job_spec::note_dead_hostid(const pmmgr_hostid& hid)
+{
+ timestamp(cout) << "dead hostid " << hid << endl;
+
+ pair<multimap<pmmgr_hostid,pmmgr_daemon*>::iterator,
+ multimap<pmmgr_hostid,pmmgr_daemon*>::iterator> range =
+ daemons.equal_range(hid);
+
+ for (multimap<pmmgr_hostid,pmmgr_daemon*>::iterator it = range.first;
+ it != range.second;
+ ++it)
+ delete (it->second);
+
+ daemons.erase(range.first, range.second);
+}
+
+
+// ------------------------------------------------------------------------
+
+
+pmmgr_daemon::pmmgr_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid,
+ const pcp_context_spec& spec):
+ pmmgr_configurable(config_directory),
+ hostid(hostid),
+ spec(spec),
+ pid(0),
+ last_restart_attempt(0)
+{
+}
+
+
+pmmgr_pmlogger_daemon::pmmgr_pmlogger_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid,
+ const pcp_context_spec& spec):
+ pmmgr_daemon(config_directory, hostid, spec)
+{
+}
+
+
+pmmgr_pmie_daemon::pmmgr_pmie_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid,
+ const pcp_context_spec& spec):
+ pmmgr_daemon(config_directory, hostid, spec)
+{
+}
+
+
+pmmgr_daemon::~pmmgr_daemon()
+{
+ if (pid != 0)
+ {
+ int ignored;
+ (void) kill ((pid_t) pid, SIGTERM);
+ (void) waitpid ((pid_t) pid, &ignored, 0); // collect zombie
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "daemon pid " << pid << " killed" << endl;
+ }
+}
+
+
+void pmmgr_daemon::poll()
+{
+ if (quit) return;
+
+ if (pid != 0) // test if it's still alive
+ {
+ // reap it if it might have died
+ int ignored;
+ int rc = waitpid ((pid_t) pid, &ignored, WNOHANG);
+
+ rc = kill ((pid_t) pid, 0);
+ if (rc < 0)
+ {
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "daemon pid " << pid << " found dead" << endl;
+ pid = 0;
+ // we will try again immediately
+ }
+ }
+
+ if (pid == 0) // needs a restart
+ {
+ time_t now;
+ time (& now);
+
+ // Prevent an error in the environment or the pmmgr daemon
+ // command lines from generating a tight loop of failure /
+ // retry, wasting time and log file space. Limit retry attempts
+ // to one per poll interval (pmmgr -p N parameter).
+ if (last_restart_attempt && (last_restart_attempt + polltime) >= now)
+ return; // quietly, without attempting to restart
+
+ string commandline = daemon_command_line(); // <--- may take many seconds!
+
+ // NB: Note this time as a restart attempt, even if daemon_command_line()
+ // returned an empty string, so that we don't try to restart it too soon.
+ // We note this time rather than the beginning of daemon_command_line(),
+ // to ensure at least polltime seconds of rest between attempts.
+ last_restart_attempt = now;
+
+ if (quit) return; // without starting the daemon process
+
+ if (commandline == "") // error in some intermediate processing stage
+ {
+ timestamp(cerr) << "failed to prepare daemon command line" << endl;
+ return;
+ }
+
+ // We are going to run the daemon with sh -c, but on some versions of
+ // sh, this doesn't imply an exec, which interferes with signalling.
+ // Enforce exec on even these shells.
+ commandline = string("exec ") + commandline;
+
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "fork/exec sh -c " << commandline << endl;
+ pid = fork();
+ if (pid == 0) // child process
+ {
+ int rc = execl ("/bin/sh", "sh", "-c", commandline.c_str(), NULL);
+ timestamp(cerr) << "failed to execl sh -c " << commandline << " rc=" << rc << endl;
+ _exit (1);
+ // parent will try again at next poll
+ }
+ else if (pid < 0) // failed fork
+ {
+ timestamp(cerr) << "failed to fork for sh -c " << commandline << endl;
+ pid = 0;
+ // we will try again at next poll
+ }
+ else // congratulations! we're apparently a parent
+ {
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "daemon pid " << pid << " started: " << commandline << endl;
+ }
+ }
+}
+
+
+std::string
+pmmgr_pmlogger_daemon::daemon_command_line()
+{
+ string default_log_dir =
+ string(pmGetConfig("PCP_LOG_DIR")) + (char)__pmPathSeparator() + "pmmgr";
+ string log_dir = get_config_single ("log-directory");
+ if (log_dir == "") log_dir = default_log_dir;
+ else if(log_dir[0] != '/') log_dir = config_directory + (char)__pmPathSeparator() + log_dir;
+
+ (void) mkdir2 (log_dir.c_str(), 0777); // implicitly consults umask(2)
+
+ string host_log_dir = log_dir + (char)__pmPathSeparator() + hostid;
+ (void) mkdir2 (host_log_dir.c_str(), 0777);
+ // (errors creating actual files under host_log_dir will be noted shortly)
+
+ string pmlogger_command =
+ string(pmGetConfig("PCP_BIN_DIR")) + (char)__pmPathSeparator() + "pmlogger";
+ string pmlogger_options = sh_quote(pmlogger_command);
+ pmlogger_options += " " + get_config_single ("pmlogger") + " ";
+
+ // run pmlogconf if requested
+ if (get_config_exists("pmlogconf"))
+ {
+ string pmlogconf_output_file = host_log_dir + (char)__pmPathSeparator() + "config.pmlogger";
+ (void) unlink (pmlogconf_output_file.c_str());
+ string pmlogconf_command =
+ string(pmGetConfig("PCP_BINADM_DIR")) + (char)__pmPathSeparator() + "pmlogconf";
+ string pmlogconf_options =
+ sh_quote(pmlogconf_command)
+ + " -c -r -h " + sh_quote(spec)
+ + " " + get_config_single ("pmlogconf")
+ + " " + sh_quote(pmlogconf_output_file)
+ + " >/dev/null"; // pmlogconf is too chatty
+
+ int rc = wrap_system(pmlogconf_options);
+ if (rc) return "";
+
+ pmlogger_options += " -c " + sh_quote(pmlogconf_output_file);
+ }
+
+ // collect -h direction
+ pmlogger_options += " -h " + sh_quote(spec);
+
+ // hard-code -r to report metrics & expected disk usage rate
+ pmlogger_options += " -r";
+
+ // collect subsidiary pmlogger diagnostics
+ pmlogger_options += " -l " + sh_quote(host_log_dir + (char)__pmPathSeparator() + "pmlogger.log");
+
+ // do log merging
+ if (get_config_exists ("pmlogmerge"))
+ {
+ string pmlogextract_command =
+ string(pmGetConfig("PCP_BIN_DIR")) + (char)__pmPathSeparator() + "pmlogextract";
+
+ string pmlogcheck_command =
+ string(pmGetConfig("PCP_BIN_DIR")) + (char)__pmPathSeparator() + "pmlogcheck";
+
+ string pmlogrewrite_command =
+ string(pmGetConfig("PCP_BINADM_DIR")) + (char)__pmPathSeparator() + "pmlogrewrite";
+
+ string pmlogextract_options = sh_quote(pmlogextract_command);
+
+ string retention = get_config_single ("pmlogmerge-retain");
+ if (retention == "") retention = "14days";
+ struct timeval retention_tv;
+ char *errmsg;
+ int rc = pmParseInterval(retention.c_str(), &retention_tv, &errmsg);
+ if (rc)
+ {
+ timestamp(cerr) << "pmlogmerge-retain '" << retention << "' parse error: " << errmsg << endl;
+ free (errmsg);
+ retention = "14days";
+ retention_tv.tv_sec = 14*24*60*60;
+ retention_tv.tv_usec = 0;
+ }
+ pmlogextract_options += " -S -" + sh_quote(retention);
+
+ // Arrange our new pmlogger to kill itself after the given
+ // period, to give us a chance to rerun.
+ string period = get_config_single ("pmlogmerge");
+ if (period == "") period = "24hours";
+ struct timeval period_tv;
+ rc = pmParseInterval(period.c_str(), &period_tv, &errmsg);
+ if (rc)
+ {
+ timestamp(cerr) << "pmlogmerge '" << period << "' parse error: " << errmsg << endl;
+ free (errmsg);
+ period = "24hours";
+ period_tv.tv_sec = 24*60*60;
+ period_tv.tv_usec = 0;
+ }
+ if (get_config_exists ("pmlogmerge-granular"))
+ {
+ // adjust stopping time to the next multiple of period
+ struct timeval now_tv;
+ __pmtimevalNow (&now_tv);
+ time_t period_s = period_tv.tv_sec;
+ if (period_s < 1) period_s = 1; // at least one second
+ time_t period_end = ((now_tv.tv_sec + period_s - 1) / period_s) * period_s;
+ period = string(" @") +
+ string(ctime(& period_end)).substr(0,24); // 24: ctime(3) magic value, sans \n
+ }
+ pmlogger_options += " -y -T " + sh_quote(period); // NB: pmmgr host local time!
+
+ // Find prior archives by globbing for *.index files,
+ // just like pmlogger_merge does.
+ // Er ... but aren't .index files optional?
+ vector<string> mergeable_archives; // those to merge
+ glob_t the_blob;
+ string glob_pattern = host_log_dir + (char)__pmPathSeparator() + "*.index";
+ rc = glob (glob_pattern.c_str(), GLOB_NOESCAPE, NULL, & the_blob);
+ if (rc == 0)
+ {
+ // compute appropriate
+ struct timeval now_tv;
+ __pmtimevalNow (&now_tv);
+ time_t period_s = period_tv.tv_sec;
+ if (period_s < 1) period_s = 1; // at least one second
+ time_t prior_period_start = ((now_tv.tv_sec - period_s) / period_s) * period_s;
+ time_t prior_period_end = prior_period_start + period_s;
+
+ for (unsigned i=0; i<the_blob.gl_pathc; i++)
+ {
+ if (quit) return "";
+
+ string index_name = the_blob.gl_pathv[i];
+ string base_name = index_name.substr(0,index_name.length()-6); // trim .index
+
+ // Manage retention based upon the stat timestamps of the .index file,
+ // because the archives might be so corrupt that even loglabel-based
+ // checks could fail. Non-corrupt archives will have already been merged
+ // into a fresher archive.
+ struct stat foo;
+ rc = stat (the_blob.gl_pathv[i], & foo);
+ if (rc)
+ {
+ // this apprx. can't happen
+ timestamp(cerr) << "stat '" << the_blob.gl_pathv[i] << "' error; skipping cleanup" << endl;
+ continue; // likely nothing can be done to this one
+ }
+ else if ((foo.st_mtime + retention_tv.tv_sec) < now_tv.tv_sec)
+ {
+ string bnq = sh_quote(base_name);
+ string cleanup_cmd = string("/bin/rm -f")
+ + " " + bnq + ".[0-9]*"
+ + " " + bnq + ".index" +
+ + " " + bnq + ".meta";
+
+ (void) wrap_system(cleanup_cmd);
+ continue; // it's gone now; don't try to merge it or anything
+ }
+
+ if (quit) return "";
+
+ // sic pmlogcheck on it; if it is broken, pmlogextract
+ // will give up and make no progress
+ string pmlogcheck_options = sh_quote(pmlogcheck_command);
+ pmlogcheck_options += " " + sh_quote(base_name) + " >/dev/null";
+
+ rc = wrap_system(pmlogcheck_options);
+ if (rc != 0)
+ {
+ timestamp(cerr) << "corrupt archive " << base_name << " preserved." << endl;
+ continue;
+ }
+
+ if (quit) return "";
+
+ // In granular mode, skip if this file is too old or too new. NB: Decide
+ // based upon the log-label, not fstat timestamps, since files postdate
+ // the time region they cover.
+ if (get_config_exists ("pmlogmerge-granular"))
+ {
+ // One could do this the pmloglabel(1) __pmLog* way,
+ // rather than the pmlogsummary(1) PMAPI way.
+
+ int ctx = pmNewContext(PM_CONTEXT_ARCHIVE, base_name.c_str());
+ if (ctx < 0)
+ continue; // skip; gc later
+
+ pmLogLabel label;
+ rc = pmGetArchiveLabel (& label);
+ if (rc < 0)
+ continue; // skip; gc later
+
+ if (label.ll_start.tv_sec >= prior_period_end) // archive too new?
+ {
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "skipping merge of too-new archive " << base_name << endl;
+ pmDestroyContext (ctx);
+ continue;
+ }
+
+ struct timeval archive_end;
+ rc = pmGetArchiveEnd(&archive_end);
+ if (rc < 0)
+ {
+ pmDestroyContext (ctx);
+ continue; // skip; gc later
+ }
+
+ if (archive_end.tv_sec < prior_period_start) // archive too old?
+ {
+ if (pmDebug & DBG_TRACE_APPL0)
+ timestamp(cout) << "skipping merge of too-old archive " << base_name << endl;
+ pmDestroyContext (ctx);
+ continue; // skip; gc later
+ }
+
+ pmDestroyContext (ctx);
+ // fallthrough: the archive intersects the prior_period_{start,end} interval
+
+ // XXX: What happens for archives that span across granular periods?
+ }
+
+ mergeable_archives.push_back (base_name);
+ }
+ globfree (& the_blob);
+ }
+
+ string timestr = "archive";
+ time_t now2 = time(NULL);
+ struct tm *now = gmtime(& now2);
+ if (now != NULL)
+ {
+ char timestr2[100];
+ int rc = strftime(timestr2, sizeof(timestr2), "-%Y%m%d.%H%M%S", now);
+ if (rc > 0)
+ timestr += timestr2;
+ }
+ string merged_archive_name = host_log_dir + (char)__pmPathSeparator() + timestr;
+
+ if (mergeable_archives.size() > 1) // 1 or 0 are not worth merging!
+ {
+ // assemble final bits of pmlogextract command line: the inputs and the output
+ for (unsigned i=0; i<mergeable_archives.size(); i++)
+ {
+ if (quit) return "";
+
+ if (get_config_exists("pmlogmerge-rewrite"))
+ {
+ string pmlogrewrite_options = sh_quote(pmlogrewrite_command);
+ pmlogrewrite_options += " -i " + get_config_single("pmlogmerge-rewrite");
+ pmlogrewrite_options += " " + sh_quote(mergeable_archives[i]);
+
+ (void) wrap_system(pmlogrewrite_options.c_str());
+ // In case of error, don't break; let's try to merge it anyway.
+ // Maybe pmlogrewrite will succeed and will get rid of this file.
+ }
+
+ pmlogextract_options += " " + sh_quote(mergeable_archives[i]);
+ }
+
+ if (quit) return "";
+
+ pmlogextract_options += " " + sh_quote(merged_archive_name);
+
+ rc = wrap_system(pmlogextract_options.c_str());
+ if (rc == 0)
+ {
+ // zap the previous archive files
+ //
+ // Don't skip this upon "if (quit)", since the new merged archive is already complete;
+ // it'd be a waste to keep these files around for a future re-merge.
+ for (unsigned i=0; i<mergeable_archives.size(); i++)
+ {
+ string base_name = sh_quote(mergeable_archives[i]);
+ string cleanup_cmd = string("/bin/rm -f")
+ + " " + base_name + ".[0-9]*"
+ + " " + base_name + ".index" +
+ + " " + base_name + ".meta";
+
+ (void) wrap_system(cleanup_cmd.c_str());
+ }
+ }
+ }
+ }
+
+ // synthesize a logfile name similarly as pmlogger_check, but add %S (seconds)
+ // to reduce likelihood of conflict with a short poll interval
+ string timestr = "archive";
+ time_t now2 = time(NULL);
+ struct tm *now = gmtime(& now2);
+ if (now != NULL)
+ {
+ char timestr2[100];
+ int rc = strftime(timestr2, sizeof(timestr2), "-%Y%m%d.%H%M%S", now);
+ if (rc > 0)
+ timestr += timestr2; // no sh_quote required
+ }
+
+ // last argument
+ pmlogger_options += " " + sh_quote(host_log_dir + (char)__pmPathSeparator() + timestr);
+
+ return pmlogger_options;
+}
+
+
+std::string
+pmmgr_pmie_daemon::daemon_command_line()
+{
+ string default_log_dir =
+ string(pmGetConfig("PCP_LOG_DIR")) + (char)__pmPathSeparator() + "pmmgr";
+ string log_dir = get_config_single ("log-directory");
+ if (log_dir == "") log_dir = default_log_dir;
+ else if(log_dir[0] != '/') log_dir = config_directory + (char)__pmPathSeparator() + log_dir;
+
+ (void) mkdir2 (log_dir.c_str(), 0777); // implicitly consults umask(2)
+
+ string host_log_dir = log_dir + (char)__pmPathSeparator() + hostid;
+ (void) mkdir2 (host_log_dir.c_str(), 0777);
+ // (errors creating actual files under host_log_dir will be noted shortly)
+
+ string pmie_command =
+ string(pmGetConfig("PCP_BIN_DIR")) + (char)__pmPathSeparator() + "pmie";
+ string pmie_options = sh_quote (pmie_command);
+
+ pmie_options += " " + get_config_single ("pmie") + " ";
+
+ // run pmieconf if requested
+ if (get_config_exists ("pmieconf"))
+ {
+ string pmieconf_output_file = host_log_dir + (char)__pmPathSeparator() + "config.pmie";
+ string pmieconf_command =
+ string(pmGetConfig("PCP_BIN_DIR")) + (char)__pmPathSeparator() + "pmieconf";
+
+ // NB: pmieconf doesn't take a host name as an argument, unlike pmlogconf
+ string pmieconf_options =
+ sh_quote(pmieconf_command)
+ + " -F -c " + get_config_single ("pmieconf")
+ + " -f " + sh_quote(pmieconf_output_file);
+
+ int rc = wrap_system(pmieconf_options.c_str());
+ if (rc) return "";
+
+ pmie_options += "-c " + sh_quote(pmieconf_output_file);
+ }
+
+ if (quit) return "";
+
+ // collect -h direction
+ pmie_options += " -h " + sh_quote(spec);
+
+ // collect -f, to get it to run in the foreground, avoid setuid
+ pmie_options += " -f";
+
+ // collect subsidiary pmlogger diagnostics
+ pmie_options += " -l " + sh_quote(host_log_dir + (char)__pmPathSeparator() + "pmie.log");
+
+ return pmie_options;
+}
+
+
+
+// ------------------------------------------------------------------------
+
+
+extern "C"
+void handle_interrupt (int sig)
+{
+ // Propagate signal to inferior processes (just once, to prevent
+ // recursive signals or whatnot, despite sa_mask in
+ // setup_signals()).
+ if (quit == 0)
+ kill(-getpid(), SIGTERM);
+
+ quit ++;
+ if (quit > 3) // ignore 1 from user; 1 from kill(-getpid) above; 1 from same near main() exit
+ {
+ char msg[] = "Too many interrupts received, exiting.\n";
+ int rc = write (2, msg, sizeof(msg)-1);
+ if (rc) {/* Do nothing; we don't care if our last gasp went out. */ ;}
+ // XXX: send a suicide signal to the process group?
+ _exit (1);
+ }
+}
+
+extern "C"
+void ignore_signal (int sig)
+{
+ (void) sig;
+}
+
+
+
+void setup_signals()
+{
+ // NB: we eschew __pmSetSignalHandler, since it uses signal(3),
+ // whose behavior is less predictable than sigaction(2).
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handle_interrupt;
+ sigemptyset (&sa.sa_mask);
+ sigaddset (&sa.sa_mask, SIGHUP);
+ sigaddset (&sa.sa_mask, SIGPIPE);
+ sigaddset (&sa.sa_mask, SIGINT);
+ sigaddset (&sa.sa_mask, SIGTERM);
+ sigaddset (&sa.sa_mask, SIGXFSZ);
+ sigaddset (&sa.sa_mask, SIGXCPU);
+ sa.sa_flags = SA_RESTART;
+ sigaction (SIGHUP, &sa, NULL);
+ sigaction (SIGPIPE, &sa, NULL);
+ sigaction (SIGINT, &sa, NULL);
+ sigaction (SIGTERM, &sa, NULL);
+ sigaction (SIGXFSZ, &sa, NULL);
+ sigaction (SIGXCPU, &sa, NULL);
+}
+
+
+
+// ------------------------------------------------------------------------
+
+static pmOptions opts;
+static pmLongOptions longopts[] =
+ {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "config", 1, 'c', "DIR", "configuration directory [default $PCP_SYSCONF_DIR/pmmgr]" },
+ { "poll", 1, 'p', "NUM", "set pmcd polling interval [default 60]" },
+ { "username", 1, 'U', "USER", "switch to named user account [default pcp]" },
+ { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" },
+ { "verbose", 0, 'v', 0, "verbose diagnostics to stderr" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+ };
+
+int main (int argc, char *argv[])
+{
+ /* Become our own process group, to assist signal passing to children. */
+ setpgid(getpid(), 0);
+ setup_signals();
+
+ string default_config_dir =
+ string(pmGetConfig("PCP_SYSCONF_DIR")) + (char)__pmPathSeparator() + "pmmgr";
+ vector<pmmgr_job_spec*> js;
+
+ int c;
+ char* username_str;
+ __pmGetUsername(& username_str);
+ string username = username_str;
+ char* output_filename = NULL;
+
+ opts.long_options = longopts;
+ opts.short_options = "D:c:vp:U:l:?";
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF)
+ {
+ switch (c)
+ {
+ case 'D': // undocumented
+ if ((c = __pmParseDebug(opts.optarg)) < 0)
+ {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ {
+ pmDebug |= c;
+ }
+ break;
+
+ case 'l':
+ output_filename = opts.optarg;
+ break;
+
+ case 'v':
+ pmDebug |= DBG_TRACE_APPL0;
+ break;
+
+ case 'p':
+ polltime = atoi(opts.optarg);
+ if (polltime <= 0)
+ {
+ pmprintf("%s: poll time too short\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'c':
+ js.push_back (new pmmgr_job_spec(opts.optarg));
+ break;
+
+ case 'U':
+ username = opts.optarg;
+ break;
+
+ default:
+ opts.errors++;
+ }
+ }
+
+ if (opts.errors)
+ {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ // default
+ if (js.size() == 0)
+ js.push_back (new pmmgr_job_spec(default_config_dir));
+
+ // let pmdapmcd know pmmgr is currently running
+ if (__pmServerCreatePIDFile(pmProgname, PM_FATAL_ERR) < 0)
+ exit(1);
+
+ // lose root privileges if we have them
+ __pmSetProcessIdentity(username.c_str());
+
+ // (re)create log file, redirect stdout/stderr
+ // NB: must be done after __pmSetProcessIdentity() for proper file permissions
+ if (output_filename)
+ {
+ int fd;
+ (void) unlink (output_filename); // in case one's left over from a previous other-uid run
+ fd = open (output_filename, O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 0666);
+ if (fd < 0)
+ timestamp(cerr) << "Cannot re-create logfile " << output_filename << endl;
+ else
+ {
+ int rc;
+ // Move the new file descriptors on top of stdout/stderr
+ rc = dup2 (fd, STDOUT_FILENO);
+ if (rc < 0) // rather unlikely
+ timestamp(cerr) << "Cannot redirect logfile to stdout" << endl;
+ rc = dup2 (fd, STDERR_FILENO);
+ if (rc < 0) // rather unlikely
+ timestamp(cerr) << "Cannot redirect logfile to stderr" << endl;
+ rc = close (fd);
+ if (rc < 0) // rather unlikely
+ timestamp(cerr) << "Cannot close logfile fd" << endl;
+ }
+
+ }
+
+ timestamp(cout) << "Log started" << endl;
+ while (! quit)
+ {
+ // In this section, we must not fidget with SIGCHLD, due to use of system(3).
+ for (unsigned i=0; i<js.size() && !quit; i++)
+ js[i]->poll();
+
+ if (quit)
+ break;
+
+ // We want to respond quickly if a child daemon process dies.
+ (void) signal (SIGCHLD, ignore_signal);
+ (void) signal (SIGALRM, ignore_signal);
+ alarm (polltime);
+ pause ();
+ alarm (0);
+ (void) signal (SIGCHLD, SIG_DFL);
+ (void) signal (SIGALRM, SIG_DFL);
+ }
+
+ // NB: don't let this cleanup be interrupted by pending-quit signals;
+ // we want the daemon pid's killed.
+ for (unsigned i=0; i<js.size(); i++)
+ delete js[i];
+
+ timestamp(cout) << "Log finished" << endl;
+
+ // Send a last-gasp signal out, just in case daemons somehow missed
+ kill(-getpid(), SIGTERM);
+
+ return 0;
+}
diff --git a/src/pmmgr/pmmgr.h b/src/pmmgr/pmmgr.h
new file mode 100644
index 0000000..4934ace
--- /dev/null
+++ b/src/pmmgr/pmmgr.h
@@ -0,0 +1,133 @@
+/* -*- C++ -*-
+ * Copyright (c) 2013-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef PMMGR_H
+#define PMMGR_H
+
+extern "C" {
+#include "pmapi.h"
+}
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+#include <stdexcept>
+#include <iostream>
+
+
+typedef std::string pcp_context_spec; // pmNewContext PM_CONTEXT_HOST parameter
+typedef std::string pmmgr_hostid; // a unique id for a pmcd
+
+
+// Instances of pmmgr_configurable represent a configurable object,
+// which reads one or more lines of djb-style directories.
+class pmmgr_configurable
+{
+protected:
+ pmmgr_configurable(const std::string& dir);
+ virtual ~pmmgr_configurable() {}
+
+ std::vector<std::string> get_config_multi(const std::string&) const;
+ std::string get_config_single(const std::string&) const;
+ bool get_config_exists(const std::string&) const;
+
+ // private: maybe?
+ std::string config_directory;
+
+ std::ostream& timestamp(std::ostream&);
+ int wrap_system(const std::string& cmd);
+};
+
+
+
+
+// Instances of pmmgr_daemon represent a possibly-live, restartable daemon.
+class pmmgr_daemon: public pmmgr_configurable
+{
+public:
+ pmmgr_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid, const pcp_context_spec& spec);
+ virtual ~pmmgr_daemon();
+ void poll();
+
+protected:
+ pmmgr_hostid hostid;
+ pcp_context_spec spec;
+ int pid;
+ time_t last_restart_attempt;
+
+ virtual std::string daemon_command_line() = 0;
+};
+
+
+class pmmgr_pmlogger_daemon: public pmmgr_daemon
+{
+public:
+ pmmgr_pmlogger_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid, const pcp_context_spec& spec);
+protected:
+ std::string daemon_command_line();
+};
+
+class pmmgr_pmie_daemon: public pmmgr_daemon
+{
+public:
+ pmmgr_pmie_daemon(const std::string& config_directory,
+ const pmmgr_hostid& hostid, const pcp_context_spec& spec);
+protected:
+ std::string daemon_command_line();
+};
+
+
+
+
+// An instance of a pmmgr_job_spec represents a pmmgr
+// configuration item to monitor some set of pcp target patterns
+// (which collectively map to a varying set of pmcd's), and a
+// corresponding set of daemons to keep running for each of them.
+//
+// The pmcds are identified by a configurable algorithm that collects
+// site-specific metrics into a single string, which is then sanitized
+// to make it typeable, portable, useful as a directory name.
+//
+// It is configured from a djb-style control directory with files containing
+// 100% pure content. Multiple values within the files, where permitted,
+// are newline-separated.
+
+class pmmgr_job_spec: pmmgr_configurable
+{
+public:
+ pmmgr_job_spec(const std::string& config_directory);
+ ~pmmgr_job_spec(); // shut down all daemons
+ void poll(); // check targets, daemons
+
+private:
+ std::map<std::string,pmMetricSpec*> parsed_metric_cache;
+ pmMetricSpec* parse_metric_spec(const std::string&);
+
+ pmmgr_hostid compute_hostid (const pcp_context_spec&);
+ std::map<pmmgr_hostid,pcp_context_spec> known_targets;
+
+ void note_new_hostid(const pmmgr_hostid&, const pcp_context_spec&);
+ void note_dead_hostid(const pmmgr_hostid&);
+ std::multimap<pmmgr_hostid,pmmgr_daemon*> daemons;
+};
+
+
+
+
+
+
+#endif
+
diff --git a/src/pmmgr/pmmgr.options b/src/pmmgr/pmmgr.options
new file mode 100644
index 0000000..86037d7
--- /dev/null
+++ b/src/pmmgr/pmmgr.options
@@ -0,0 +1,27 @@
+# command-line options and environment variables for pmmgr
+# uncomment/edit lines as required
+# note: environment variables are *not* expanded - use full path
+
+# make log more verbose
+# -v
+
+# poll less frequently
+# -p 300
+
+# add more configuration directories
+# -c DIR1
+# -c DIR2
+
+# assume identity of some user other than "pcp"
+# -U foobar
+
+# make log go someplace else
+# -l /some/place/else
+
+# setting of environment variables for pmmgr
+
+# timeouts for interactions with pmcd on behalf of clients
+# PMCD_CONNECT_TIMEOUT=10
+# PMCD_RECONNECT_TIMEOUT=10,20,30
+# PMCD_REQUEST_TIMEOUT=10
+HOME=/var/lib/pcp
diff --git a/src/pmmgr/pmmgr.service.in b/src/pmmgr/pmmgr.service.in
new file mode 100644
index 0000000..2b73366
--- /dev/null
+++ b/src/pmmgr/pmmgr.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=Performance Metrics Daemon Manager
+Documentation=man:pmmgr(8)
+Wants=avahi-daemon.service
+After=network.target avahi-daemon.service
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmmgr start
+ExecStop=@path@/pmmgr stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmmgr/rc_pmmgr b/src/pmmgr/rc_pmmgr
new file mode 100644
index 0000000..cdc3887
--- /dev/null
+++ b/src/pmmgr/rc_pmmgr
@@ -0,0 +1,296 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Start or Stop the Performance Co-Pilot (PCP) daemon manager
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 95 05
+# description: pmmgr is a daemon manager for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmmgr
+# Required-Start: $remote_fs
+# Should-Start: $local_fs $network $syslog $time $pmcd
+# Required-Stop: $remote_fs
+# Should-Stop: $local_fs $network $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmmgr (daemon manager for PCP)
+# Description: Configure and control pmmgr (a daemon manager for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMMGR=$PCP_BINADM_DIR/pmmgr
+PMMGROPTS=$PCP_PMMGROPTIONS_PATH
+RUNDIR=$PCP_LOG_DIR/pmmgr
+pmprog=$PCP_RC_DIR/pmmgr
+prog=$PCP_RC_DIR/`basename $0`
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if [ $pmprog = $prog ]
+then
+ VERBOSE_CTL=on
+else
+ VERBOSE_CTL=off
+fi
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ test -f /usr/xpg4/bin/id && ID=/usr/xpg4/bin/id
+
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+_shutdown()
+{
+ # Is pmmgr running?
+ #
+ _get_pids_by_name pmmgr >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$pmprog: pmmgr not running"
+ return 0
+ fi
+
+ # Send pmmgr a SIGTERM, which is noted as a pending shutdown.
+ # When finished the currently active request, pmmgr will close any
+ # connections and then exit.
+ # Wait for pmmgr to terminate.
+ #
+ pmsignal -a -s TERM pmmgr > /dev/null 2>&1
+ $ECHO $PCP_ECHO_N "Waiting for pmmgr to terminate ...""$PCP_ECHO_C"
+ gone=0
+ for i in 1 2 3 4 5 6
+ do
+ sleep 3
+ _get_pids_by_name pmmgr >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ gone=1
+ break
+ fi
+
+ # If pmmgr doesn't go in 15 seconds, SIGKILL and sleep 1 more time
+ # to allow any clients reading from pmmgr sockets to fail so that
+ # socket doesn't end up in TIME_WAIT or somesuch.
+ #
+ if [ $i = 5 ]
+ then
+ $ECHO
+ echo "Process ..."
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps
+ sed 1q $tmp/ps
+ for pid in `cat $tmp/tmp`
+ do
+ $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }"
+ done
+ echo "$prog: Warning: Forcing pmmgr to terminate!"
+ pmsignal -a -s KILL pmmgr > /dev/null 2>&1
+ else
+ $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C"
+ fi
+ done
+ if [ $gone != 1 ] # It just WON'T DIE, give up.
+ then
+ echo "Process ..."
+ cat $tmp/tmp
+ echo "$prog: Warning: pmmgr won't die!"
+ exit
+ fi
+ $RC_STATUS -v
+ pmpost "stop pmmgr from $pmprog"
+}
+
+_usage()
+{
+ echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+else # For a quiet startup and shutdown
+ ECHO=:
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the PCP pmmgr daemon.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmmgr
+ then
+ status=0
+ exit
+ fi
+
+ _shutdown quietly
+
+ # pmmgr messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ if [ -x $PMMGR ]
+ then
+ if [ ! -f $PMMGROPTS ]
+ then
+ echo "$prog:"'
+Error: pmmgr control file "$PMMGROPTS" is missing, cannot start pmmgr.'
+ exit
+ fi
+ if [ ! -d "$RUNDIR" ]
+ then
+ mkdir -p -m 775 "$RUNDIR"
+ chown $PCP_USER:$PCP_GROUP "$RUNDIR"
+ fi
+ cd $RUNDIR
+
+ # salvage the previous versions of any pmmgr
+ #
+ if [ -f pmmgr.log ]
+ then
+ rm -f pmmgr.log.prev
+ mv pmmgr.log pmmgr.log.prev
+ fi
+
+ $ECHO $PCP_ECHO_N "Starting pmmgr ..." "$PCP_ECHO_C"
+ # options file processing ...
+ # only consider lines which start with a hyphen
+ # get rid of the -f option
+ # ensure multiple lines concat onto 1 line
+ OPTS=`sed <$PMMGROPTS 2>/dev/null \
+ -e '/^[^-]/d' \
+ -e 's/^/ /' \
+ -e 's/$/ /' \
+ -e 's/ -f / /g' \
+ -e 's/^ //' \
+ -e 's/ $//' \
+ | tr '\012' ' ' `
+
+ # environment stuff
+ #
+ eval `sed -e 's/"/\\"/g' $PMMGROPTS \
+ | awk -F= '
+BEGIN { exports="" }
+/^[A-Z]/ && NF == 2 { exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+ }
+END { if (exports != "") print "export", exports }'`
+
+ $PMMGR -l pmmgr.log $OPTS &
+ $RC_STATUS -v
+
+ pmpost "start pmmgr from $pmprog"
+ fi
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmmgr:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMMGR
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMMGR
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmns/GNUmakefile b/src/pmns/GNUmakefile
new file mode 100644
index 0000000..32ebe34
--- /dev/null
+++ b/src/pmns/GNUmakefile
@@ -0,0 +1,79 @@
+#
+# Copyright (c) 2000-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+-include ./GNUlocaldefs
+
+PMNS_VAR_DIR = $(PCP_VAR_DIR)/pmns
+PMNS_BIN_DIR = $(PCP_BINADM_DIR)
+PMNS_LIB_DIR = $(PCP_SHARE_DIR)/lib
+
+# Take control here ... do not need to search in libpcp_pmda directory
+# for libpcp_pmda DSO, and it is not even built yet for a virgin make.
+#
+PCPLIB_LDFLAGS = -L$(TOPDIR)/src/libpcp/src
+
+CFILES = pmnsmerge.c pmnsutil.c pmnsdel.c
+HFILES = pmnsutil.h
+TARGETS = pmnsmerge$(EXECSUFFIX) pmnsdel$(EXECSUFFIX)
+SCRIPTS = pmnsadd
+LOCKERS = lockpmns unlockpmns
+STDPMID = stdpmid.pcp stdpmid.local
+
+LSRCFILES = Make.stdpmid GNUmakefile.install Rebuild ReplacePmnsSubtree \
+ $(STDPMID) $(SCRIPTS) $(LOCKERS)
+
+LLDLIBS = $(PCPLIB)
+LDIRT = *.log *.pmns stdpmid .NeedRebuild build.script $(TARGETS)
+
+default: $(SCRIPTS) $(LOCKERS) $(TARGETS) \
+ GNUmakefile.install .NeedRebuild Rebuild ReplacePmnsSubtree stdpmid
+
+include $(BUILDRULES)
+
+pmnsmerge$(EXECSUFFIX): pmnsmerge.o pmnsutil.o
+ $(CCF) -o $@ $(LDFLAGS) pmnsmerge.o pmnsutil.o $(LDLIBS)
+
+pmnsdel$(EXECSUFFIX): pmnsdel.o pmnsutil.o
+ $(CCF) -o $@ $(LDFLAGS) pmnsdel.o pmnsutil.o $(LDLIBS)
+
+.NeedRebuild:
+ echo "This file flags the rc scripts to rebuild the PMNS" > .NeedRebuild
+
+# All PMNS config stuff goes in $PCP_VAR_DIR/pmns
+# For platforms that want it, the .NeedRebuild hook is added there,
+# else a manual touch(1) here is as close as it gets unfortunately.
+#
+install: default
+ $(INSTALL) -m 755 $(TARGETS) $(SCRIPTS) $(PMNS_BIN_DIR)
+ $(INSTALL) -m 755 $(LOCKERS) ReplacePmnsSubtree $(PMNS_LIB_DIR)
+ $(INSTALL) -m 644 GNUmakefile.install $(PMNS_VAR_DIR)/Makefile
+ $(INSTALL) -m 755 Rebuild $(PMNS_VAR_DIR)/Rebuild
+ $(INSTALL) -m 755 Make.stdpmid $(PMNS_VAR_DIR)/Make.stdpmid
+ $(INSTALL) -m 644 $(STDPMID) $(PMNS_VAR_DIR)
+ifeq (, $(filter redhat debian, $(PACKAGE_DISTRIBUTION)))
+ $(INSTALL) -m 644 .NeedRebuild $(PMNS_VAR_DIR)/.NeedRebuild
+endif
+
+stdpmid: $(STDPMID)
+ rm -f build.script
+ $(AWK) <Make.stdpmid >build.script '\
+/^. \$$PCP_DIR/ { print "PCP_CONF=../include/pcp.conf"; print ". ../include/pcp.env"; next }\
+ { print }'
+ sh ./build.script
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmns/GNUmakefile.install b/src/pmns/GNUmakefile.install
new file mode 100644
index 0000000..772c80a
--- /dev/null
+++ b/src/pmns/GNUmakefile.install
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Makefile to rebuild the Performance Metrics Names Space (PMNS) (root)
+# and the standard Performance Metric Domain numbers (stdpmid)
+#
+
+TARGETS = root
+PMNS != echo root_*
+STDPMID != echo stdpmid.*
+
+default: root stdpmid
+
+root: $(PMNS)
+ ./Rebuild
+
+stdpmid: $(STDPMID)
+ ./Make.stdpmid
+
+clobber:
+ rm -f stdpmid
diff --git a/src/pmns/Make.stdpmid b/src/pmns/Make.stdpmid
new file mode 100755
index 0000000..c50423e
--- /dev/null
+++ b/src/pmns/Make.stdpmid
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 1995,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+if [ -d "$PCP_TMPFILE_DIR" ]
+then
+ tmp=`mktemp -d "$PCP_TMPFILE_DIR/pcp.XXXXXXXXX"` || exit 1
+else
+ # if configure --prefix is used in a the build, then $PCP_TMPFILE_DIR
+ # may not yet exist ... /tmp is a safe bet
+ #
+ tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+fi
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+prog=`basename $0`
+OLD=stdpmid
+NEW=$tmp/new
+SOURCE=""
+for file in `echo stdpmid.*`
+do
+ case $file
+ in
+ stdpmid.'*'|stdpmid.O|stdpmid.O.*|stdpmid.N|stdpmid.N.*|stdpmid.rpmorig|stdpmid.*.rpmorig)
+ ;;
+ *)
+ SOURCE="$SOURCE $file"
+ ;;
+ esac
+done
+
+[ -z "$SOURCE" ] && exit # nothing to do - the norm nowadays
+
+# post-processing with sed ...
+# - removes comments
+# - maps white space to a single space
+# - performs domain re-numbering that occured for LAB and ASH
+# between PCP 2.0 and PCP 2.1 to avoid duplicates in the interim
+#
+# Note on sort. Used to be "sort -n +1 -2", but changed to "sort -n -k2,3"
+# to avoid problems with more recent Linux coreutils versions.
+#
+for file in $SOURCE
+do
+ sed <$file \
+ -e '/^#/d' \
+ -e 's/[ ][ ]*/ /' \
+ -e '/^LAB /s/254/246/' \
+ -e '/^ASH /s/7/11/'
+done \
+| sort -n -k2,3 \
+| uniq >$tmp/tmp
+
+if [ -s "$tmp/tmp" ]
+then
+ error=false
+else
+ echo "$prog: Error: failed to create temporary file"
+ exit
+fi
+
+# scan for duplicate domain name, but different domain number
+#
+$PCP_AWK_PROG '{ print $1 }' <$tmp/tmp \
+| sort \
+| uniq -c \
+| while read cnt domain
+do
+ [ $cnt -eq 1 ] && continue
+ echo "$prog: Error: duplicate for domain name \"$domain\" ..."
+ grep "^$domain[ ]" $SOURCE | sed -e 's/^/ /'
+ error=true
+done
+
+# scan for duplicate domain number, but different domain name
+#
+$PCP_AWK_PROG '{ print $2 }' <$tmp/tmp \
+| sort \
+| uniq -c \
+| while read cnt number
+do
+ [ $cnt -eq 1 ] && continue
+ echo "$prog: Error: duplicate for domain number \"$number\" ..."
+ grep "[ ]$number\$" $SOURCE | sed -e 's/^/ /'
+ error=true
+done
+
+$error && exit
+
+# preamble
+#
+cat <<'End-of-File' >$NEW
+/*
+ * NOTE:
+ * Do not edit this file (it is re-created by Make.stdpmid).
+ * To make changes, edit one of the stdpmid.* files, most probably
+ * stdpmid.local, and as root
+ * # make stdpmid
+ *
+ * The following domain number assignments are assumed to apply
+ *
+ * Domain Number Range Use
+ * 0 reserved -- DO NOT USE
+ * 1-31 production PMDAs from PCP packages (#1)
+ * 32-39 ORACLE DBMS PMDAs
+ * 40-47 Sybase DBMS PMDAs
+ * 48-55 Informix DBMS PMDAs
+ * 56-58 SNMP Gateway PMDA
+ * 59-63 Linux PMDAs
+ * 64-69 ISV PMDAs
+ * 70-128 production PMDAs from PCP packages (#2)
+ * 129-510 End-User PMDAs and demo PMDAs
+ * 511 reserved for dynamic PMNS entries -- DO NOT USE
+ *
+ * A Performance Metrics Identifier (PMID) is internally encoded as
+ * 1 bits - flag reserved for internal use
+ * 9 bits - the Performance Metric Domain Agent (PMDA) domain number
+ * from the list below
+ * 12 bits - cluster within domain
+ * 10 bits - serial within cluster
+ */
+
+#ifndef __STDPMID
+#define __STDPMID
+
+End-of-File
+
+cat $tmp/tmp \
+| while read domain number
+do
+ echo "#define $domain $number" >>$NEW
+done
+
+echo '
+#endif' >>$NEW
+
+# only update if it has changed
+#
+if [ -f $OLD ]
+then
+ if cmp -s $OLD $NEW >/dev/null 2>&1
+ then
+ rm -f $NEW
+ fi
+fi
+
+if [ -f $NEW ]
+then
+ cp $NEW $OLD
+ chmod 444 $NEW
+fi
+
+status=0
+exit
diff --git a/src/pmns/Rebuild b/src/pmns/Rebuild
new file mode 100755
index 0000000..1d69e04
--- /dev/null
+++ b/src/pmns/Rebuild
@@ -0,0 +1,395 @@
+#!/bin/sh
+#
+# Copyright (c) 2000-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Rebuild the PMNS, handling assorted errors
+#
+
+# Note. has to be run from where the PMNS files are installed as local
+# file names (not full paths) are used
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d ./pcp.XXXXXXXXX` || exit 1
+status=1
+
+$PCP_SHARE_DIR/lib/lockpmns root
+trap "$PCP_SHARE_DIR/lib/unlockpmns root; rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+_trace()
+{
+ if $silent
+ then
+ :
+ else
+ if $nochanges
+ then
+ echo "$*"
+ else
+ echo "$*" >>$tmp/trace
+ fi
+ fi
+}
+
+_trace_file()
+{
+ if $silent
+ then
+ :
+ else
+ if $nochanges
+ then
+ cat $1
+ else
+ cat $1 >>$tmp/trace
+ fi
+ fi
+}
+
+_syslog()
+{
+ $PCP_SYSLOG_PROG -p user.alert -t PCP "$*"
+ _trace "$*"
+}
+
+_die()
+{
+ [ -f $tmp/trace ] && cat $tmp/trace
+ rm -f root.new
+ exit
+}
+
+prog=`basename $0`
+debug=false
+nochanges=false
+root=root
+root_updated=false
+update=false
+verbose=""
+dupok=false
+silent=false
+
+_usage()
+{
+ _trace "Usage: Rebuild [-dnsuv]"
+ _trace "Options:"
+ _trace " -d allow duplicate PMIDs in the PMNS"
+ _trace " -n dry run, show me what would be done"
+ _trace " -s silent, exit status says it all"
+ _trace " -u once only upgrade processing for a new PCP version"
+ _trace " -v verbose, for the paranoid"
+}
+
+# Fixup "root" after PCP upgrade
+#
+_upgrade_root()
+{
+ [ ! -f root ] && return
+ _trace "Rebuild: PCP upgrade processing for \"root\" PMNS changes ..."
+ $nochanges && _trace "+ cull root_* names from PMNS ... <root >$tmp/root"
+
+ # Cull root to remove all metrics from root_* files, so only metrics
+ # for optional PMDAs remain
+
+ # If there are deprecated top-level names (below "root") that are
+ # no longer in a root_* file, add them here ...
+ EXCLUDE="pagebuf origin"
+ if [ "$PCP_PLATFORM" = linux ]
+ then
+ # If we're on Linux and the proc PMDA is _not_ included in
+ # the pmcd configuration file, add the top-level metrics
+ # that migrated from the linux PMDA to the proc PMDA
+ #
+ if [ -f $PCP_PMCDCONF_PATH ]
+ then
+ if grep '^proc[ ]' $PCP_PMCDCONF_PATH >/dev/null
+ then
+ # proc PMDA is installed
+ #
+ :
+ else
+ EXCLUDE="$EXCLUDE proc cgroup"
+ fi
+ else
+ EXCLUDE="$EXCLUDE proc cgroup"
+ fi
+ else
+ EXCLUDE="$EXCLUDE proc cgroup"
+ fi
+
+ # now gather top-level names from root_* files
+ #
+ if [ "`echo root_*`" != "root_*" ]
+ then
+ EXCLUDE_TMP=`for file in root_*
+ do
+ $PCP_AWK_PROG <$file '
+$1 == "}" { exit }
+in_root==1 { printf "%s ",$1 }
+$1 == "root" && $2 == "{" { in_root = 1 }'
+done`
+ EXCLUDE="$EXCLUDE $EXCLUDE_TMP"
+ fi
+
+ if [ ! -z "$verbose" -a ! -z "$EXCLUDE" ]
+ then
+ _trace "Exclude these top-level names ..."
+ _trace "`echo $EXCLUDE | fmt | sed -e 's/^/ /'`"
+ fi
+
+ $PCP_AWK_PROG <root >$tmp/root '
+BEGIN { # exclude these top-level names and all their descendents
+ #
+ n = split("'"$EXCLUDE"'", e)
+ for (i=1; i <= n; i++) {
+ not_in_root[e[i]] = 1
+ }
+
+ in_root = 0
+ skip = 0
+ }
+$1 == "root" && $2 == "{" { in_root = 1 }
+$1 == "}" { in_root = 0 }
+in_root { if (!($1 in not_in_root)) print
+ next
+ }
+$2 == "{" { n = split($1, name, ".")
+ if (n > 0 && name[1] in not_in_root)
+ skip = 1
+ }
+skip && $1 == "}" { skip = 0; next }
+skip { next }
+ { print }'
+ if cmp -s root $tmp/root >/dev/null 2>&1
+ then
+ # no changes ... already been here?
+ :
+ else
+ # we will usually end up here
+ root=$tmp/root
+ root_updated=true
+ fi
+}
+
+while getopts dnusv\? c
+do
+ case $c
+ in
+ d) dupok=true
+ ;;
+ n) nochanges=true
+ echo "$prog: Warning: dry run, no changes will be made"
+ ;;
+ u) update=true
+ ;;
+ s) silent=true
+ ;;
+ v) verbose="-v"
+ ;;
+ \?) _usage
+ status=0
+ _die
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# some preliminary checks
+#
+for file in $PCP_BINADM_DIR/pmnsmerge
+do
+ if [ ! -x $file ]
+ then
+ _syslog "$prog: $file is missing. Cannot proceed."
+ _die
+ fi
+done
+
+# remove all trace of old binary pmns (not used in PCP 3.6 or later)
+#
+rm -f root.bin
+
+here=`pwd`
+_trace "Rebuilding the Performance Metrics Name Space (PMNS) in $here ..."
+
+if [ $# -ne 0 ]
+then
+ _usage
+ _die
+fi
+
+if $nochanges
+then
+ CP="_trace + cp"
+ MV="_trace + mv"
+ LN="_trace + ln"
+ RM="_trace + rm"
+ PMNSMERGE="_trace + pmnsmerge ..."
+else
+ CP=cp
+ MV=mv
+ LN=ln
+ RM=rm
+ PMNSMERGE=
+
+ if [ ! -w `pwd` ]
+ then
+ _syslog "$prog: cannot write in directory `pwd`, script should be run as \"root\"?"
+ _die
+ fi
+
+ if [ ! -f root ]
+ then
+ echo "root {" >root
+ echo "}" >>root
+ chmod 644 root
+ fi
+
+ if [ ! -w root ]
+ then
+ _syslog "$prog: cannot write file \"root\" in directory `pwd`, script should be run as \"root\"?"
+ _die
+ fi
+fi
+
+if $update
+then
+ # PCP upgrade fix ups
+ #
+ _upgrade_root
+fi
+
+if [ -f $root ]
+then
+ haveroot=true
+else
+ haveroot=false
+ if $nochanges
+ then
+ _trace "+ create empty root PMNS ..."
+ else
+ root=$tmp/root
+ cat <<EOFEOF >$root
+root {
+}
+EOFEOF
+ fi
+fi
+
+# Merge $root and root_* to produce the new root.
+# Each root_* file should be a complete namespace,
+# i.e. it should include an entry for root.
+#
+mergelist=""
+if [ "`echo root_*`" != "root_*" ]
+then
+ mergelist=`ls -1 root_* | $PCP_AWK_PROG '
+ /root_web/ {next}
+ {print}'`
+fi
+
+_trace "$prog: merging the following PMNS files: "
+_trace $root $mergelist | fmt | sed -e 's/^/ /'
+
+rm -f root.new
+eval $PMNSMERGE
+if $dupok
+then
+ pmnsmerge $verbose -d $root $mergelist root.new >$tmp/out 2>&1
+else
+ pmnsmerge $verbose $root $mergelist root.new >$tmp/out 2>&1
+fi
+
+if [ $? != 0 ]
+then
+ cat $tmp/out
+ _syslog "$prog: pmnsmerge failed"
+ _trace " \"root\" has not been changed."
+ _die
+fi
+
+# Multiple Rebuilds in succession should be a no-op.
+#
+if [ -f root ]
+then
+ if $dupok
+ then
+ pminfo -m -N root 2>/dev/null | sort >$tmp/list.old
+ else
+ pminfo -m -n root 2>/dev/null | sort >$tmp/list.old
+ fi
+fi
+if [ ! -s $tmp/list.old ]
+then
+ if $dupok
+ then
+ pminfo -m -N $root 2>/dev/null | sort >$tmp/list.old
+ else
+ pminfo -m -n $root 2>/dev/null | sort >$tmp/list.old
+ fi
+fi
+if $dupok
+then
+ pminfo -m -N root.new | sort >$tmp/list.new
+else
+ pminfo -m -n root.new | sort >$tmp/list.new
+fi
+if cmp -s $tmp/list.old $tmp/list.new > /dev/null 2>&1
+then
+ [ ! -f root ] && eval $MV root.new root
+ _trace "$prog: PMNS is unchanged."
+else
+ # Install the new root
+ #
+ [ ! -z "$verbose" ] && _trace_file $tmp/out
+ if $haveroot
+ then
+ _trace "$prog: PMNS \"$here/root\" updated."
+ _trace "... previous version saved as \"$here/root.prev\""
+ eval $MV root root.prev
+ else
+ _trace "$prog: new PMNS \"$here/root\" created."
+ fi
+ eval $MV root.new root
+
+ # signal pmcd if it is running
+ #
+ pminfo -v pmcd.version >/dev/null 2>&1 && pmsignal -a -s HUP pmcd
+
+ if [ ! -z "$verbose" ] && $haveroot
+ then
+ _trace "+ PMNS differences ..."
+ diff -c $tmp/list.old $tmp/list.new >$tmp/diff
+ _trace_file $tmp/diff
+ _trace
+ _trace "+ root differences ..."
+ diff -c root.prev root >$tmp/diff
+ _trace_file $tmp/diff
+ fi
+fi
+rm -f root.new
+
+# remake stdpmid
+#
+[ -f Make.stdpmid ] && ./Make.stdpmid
+
+[ X"$verbose" = X-v -a -f $tmp/trace ] && cat $tmp/trace
+
+status=0
+exit
diff --git a/src/pmns/ReplacePmnsSubtree b/src/pmns/ReplacePmnsSubtree
new file mode 100644
index 0000000..8b5a05c
--- /dev/null
+++ b/src/pmns/ReplacePmnsSubtree
@@ -0,0 +1,184 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Replace a subtree in a performance metrics namespace (PMNS) with a new
+# subtree read from a file. The file is moved (note: not copied) into the
+# PMNS and given the same name as the subtree.
+#
+# Usage: ReplacePmnsSubtree [-n namespace] pmns-subtree replacement-file
+#
+# Refer to the NOTE at the bottom of the file for important locking details.
+#
+
+. $PCP_DIR/etc/pcp.env
+
+namespace=$PCP_VAR_DIR/pmns/root
+
+# Print usage message to stderr and exit with status provided (default 1)
+
+usageExit()
+{
+ echo "Usage: $prog [-n namespace] pmns-subtree replacement-file" 1>&2
+ exit ${1:-1}
+}
+
+# Sanity check options, parameters, namespace, etc.
+
+prog=`basename $0`
+while getopts n:\? c
+do
+ case "$c" in
+ n)
+ if [ "x$OPTARG" = x ]
+ then
+ echo "$prog: -n requires a namespace" 1>&2
+ exit 1
+ else
+ namespace="$OPTARG"
+ fi
+ ;;
+ \?)
+ usageExit 0
+ ;;
+ esac
+done
+
+if [ ! -f $namespace ]
+then
+ echo "$prog: namespace doesn't exist: $namespace" 1>&2
+ exit 1
+fi
+
+[ $# != 2 ] && usageExit
+
+subtree=$1
+newSubtreeFile=$2
+namespaceDir=`dirname $namespace`
+
+if [ ! -w $namespaceDir ]
+then
+ echo "$prog: can't write namespace directory $namespaceDir" 1>&2
+ exit 1
+fi
+
+if [ ! -f $newSubtreeFile ]
+then
+ echo "$prog: can't read replacement namespace subtree file for $subtree:" \
+ "$newSubtreeFile" 1>&2
+ exit 1
+fi
+
+# variables for back-up/restore of namespace files affected by this script
+
+backups="root"
+[ -f "$namespaceDir/$subtree" ] && backups="$backups $subtree"
+backSuffix="$prog-$$-backup"
+restore=false # restore backup namespace files in cleanup()
+
+# Signal and exit handler to clean/restore namespace (lock and backups).
+
+haveLock=false
+
+# signals we need to be careful of ... HUP, INT, QUIT, PIPE, ALRM, TERM
+#
+STD_SIGNALS="1 2 3 13 14 15"
+
+cleanup()
+{
+ # Release namespace lock as early as possible. Ignore signals to avoid
+ # releasing a lock already released.
+ trap "" $STD_SIGNALS
+
+ if $restore
+ then
+ for f in $backups
+ do
+ [ -f "$namespaceDir/.$f-$backSuffix" ] && \
+ mv "$namespaceDir/.$f-$backSuffix" "$namespaceDir/$f"
+ done
+ $haveLock && unlockpmns $namespace
+ haveLock=false
+ else
+ $haveLock && unlockpmns $namespace
+ haveLock=false
+ for f in $backups
+ do
+ rm -f "$namespaceDir/.$f-$backSuffix"
+ done
+ fi
+}
+
+trap "cleanup; exit" 0 $STD_SIGNALS
+
+# "haveLock=true" is duplicated in both "if" and "else" to minimise the window
+# for a signal leaving an orphaned lock if the "else" condition fires (we
+# stole an existing lock). See note at bottom of script for details.
+
+if lockpmns $namespace
+then
+ haveLock=true # duplicate: minimise race condition (see note above)
+else
+ haveLock=true # duplicate: minimise race condition (see note above)
+ $PCP_BINADM_DIR/pmpost "PCP: PMNS lock stolen by: $*"
+fi
+
+# Namespace is locked, back up affected files. Once backup completes, enable
+# namespace restore during error handling.
+
+backupErr=false
+for f in $backups
+do
+ dest="$namespaceDir/.$f-$backSuffix"
+ if cp "$namespaceDir/$f" "$dest"
+ then
+ :
+ else
+ echo "error creating namespace backup for $f" 1>&2
+ backupErr=true
+ fi
+done
+$backupErr && exit 1
+restore=true
+
+# pmnsdel leaves any file corresponding to the deleted subtree in place after
+# it runs, regardless of whether it succeeds or fails. Allow pmnsdel to fail
+# (there may be no existing subtree). Return 0 only if the entire replacement
+# succeeds.
+
+sts=0
+pmnsdel -n $namespace $subtree >/dev/null 2>&1
+if pmnsadd -n $namespace $newSubtreeFile >/dev/null 2>&1
+then
+ mv -f $newSubtreeFile $namespaceDir/$subtree
+ sts=$?
+else
+ sts=1
+fi
+[ $sts = 0 ] && restore=false
+exit $sts
+
+##############################################################################
+
+# NOTE
+#
+# If a signal occurs in the very short window between pmnslock returning and
+# setting haveLock, the lock is not released by cleanup(). Any subsequent
+# process calling pmnslock will block until its pmnslock steals the lock
+# (currently after 120 secs). That window is carefully minimised.
+#
+# Past versions of similar scripts (e.g. Rebuild) unconditionally unlocked the
+# namespace when a signal was caught. This could potentially happen while
+# still waiting to acquire the lock, breaking a lock held by another process!
+#
+# Consider locking and backup/restore implications carefully if making changes.
diff --git a/src/pmns/lockpmns b/src/pmns/lockpmns
new file mode 100644
index 0000000..9e670cc
--- /dev/null
+++ b/src/pmns/lockpmns
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Lock the PMNS against concurrent transactional updates
+#
+
+. $PCP_DIR/etc/pcp.env
+
+lock=${1-$PCP_VAR_DIR/pmns/root}.lock
+
+i=0
+while true
+do
+ if pmlock $lock
+ then
+ # lock acquired
+ #
+ #DEBUG# echo "pmnslock: `date; ls -li $lock`"
+ break
+ fi
+ if [ $i -eq 20 ]
+ then
+ echo "lockpmns: Warning: Unable to acquire lock ($lock)"
+ echo " after 120 seconds ... continuing anyway"
+ exit 1
+ fi
+ sleep 5
+ i=`expr $i + 1`
+done
+
+exit 0
diff --git a/src/pmns/pmnsadd b/src/pmns/pmnsadd
new file mode 100755
index 0000000..a14e4ad
--- /dev/null
+++ b/src/pmns/pmnsadd
@@ -0,0 +1,125 @@
+#!/bin/sh
+#
+# Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Add a subtree of new names into the namespace in the current directory
+#
+
+# source the PCP configuration environment variables
+. $PCP_DIR/etc/pcp.env
+
+umask 22 # anything else is pretty silly
+exitsts=1
+tmp=`mktemp -d $PCP_TMPFILE_DIR/pcp.XXXXXXXXX` || exit 1
+prog=`basename $0`
+trap "rm -rf $tmp; exit \$exitsts" 0 1 2 3 15
+
+_usage()
+{
+ echo "Usage: pmnsadd [-d] [-n namespace] file"
+}
+
+namespace=${PMNS_DEFAULT-$PCP_VAR_DIR/pmns/root}
+dupok=""
+
+while getopts dn:\? c
+do
+ case $c
+ in
+ d) dupok="-d"
+ ;;
+ n) namespace=$OPTARG
+ ;;
+ \?) _usage
+ exitsts=0
+ exit
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $# -ne 1 ]
+then
+ _usage
+ exit
+fi
+
+if [ ! -f $namespace ]
+then
+ echo "$prog: cannot find PMNS file \"$root\""
+ exit
+fi
+
+if [ ! -w $namespace ]
+then
+ echo "$prog: cannot open PMNS file \"$root\" for writing"
+ exit
+fi
+
+if [ ! -f "$1" ]
+then
+ echo "$prog: cannot find input file \"$1\""
+ exit
+fi
+
+if grep '^root {' "$1" >/dev/null
+then
+ # Special case ... if the PMDA only supplies metrics at the root of
+ # the PMNS, e.g. the MMV PMDA with all dynamic metrics, the input
+ # PMNS contains 'root {' already, so just use that without any need
+ # to construct the upper levels of the PMNS
+ #
+ cp "$1" $tmp/tmp
+else
+ # Normal case ... find PMNS pathname for base of new subtree
+ # (subroot), construct upper levels of PMNS as required and hand-off
+ # to pmnsmerge
+ #
+ subroot=`$PCP_AWK_PROG <"$1" 'NF >= 2 && $2 == "{" { print $1 ; exit }'`
+ echo 'root {' >$tmp/tmp
+ path=""
+ for name in `echo "$subroot" | tr '.' ' '`
+ do
+ [ ! -z "$path" ] && echo "$path {" >>$tmp/tmp
+ echo " $name" >>$tmp/tmp
+ echo "}" >>$tmp/tmp
+ if [ -z "$path" ]
+ then
+ path="$name"
+ else
+ path="$path.$name"
+ fi
+ done
+ cat "$1" >>$tmp/tmp
+fi
+
+# try to preserve mode, owner and group for the new output files
+#
+rm -f $namespace.new
+[ -f $namespace ] && cp -p $namespace $namespace.new
+
+$PCP_BINADM_DIR/pmnsmerge -f $dupok $namespace $tmp/tmp $namespace.new
+exitsts=$?
+
+# from here on, ignore SIGINT, SIGHUP and SIGTERM to protect
+# the integrity of the new ouput files
+#
+trap "" 1 2 15
+
+if [ $exitsts = 0 ]
+then
+ mv $namespace.new $namespace
+else
+ echo "$prog: No changes have been made to the PMNS file \"$namespace\""
+ rm -f $namespace.new
+fi
diff --git a/src/pmns/pmnsdel.c b/src/pmns/pmnsdel.c
new file mode 100644
index 0000000..01a0150
--- /dev/null
+++ b/src/pmns/pmnsdel.c
@@ -0,0 +1,216 @@
+/*
+ * Cull subtree(s) from a PCP PMNS
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmnsutil.h"
+
+static FILE *outf; /* output */
+static __pmnsNode *root; /* result so far */
+static char *fullname; /* full PMNS pathname for newbie */
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "duplicates", 0, 'd', 0, "duplicate PMIDs are allowed" },
+ PMOPT_NAMESPACE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "dD:n:?",
+ .long_options = longopts,
+ .short_usage = "[options] metricpath [...]",
+};
+
+static void
+delpmns(__pmnsNode *base, char *name)
+{
+ char *tail;
+ ptrdiff_t nch;
+ __pmnsNode *np;
+ __pmnsNode *lastp = NULL;
+
+ for (tail = name; *tail && *tail != '.'; tail++)
+ ;
+ nch = tail - name;
+
+ for (np = base->first; np != NULL; np = np->next) {
+ if (strlen(np->name) == nch && strncmp(name, np->name, (int)nch) == 0)
+ break;
+ lastp = np;
+ }
+
+ if (np == NULL) {
+ /* no match ... */
+ fprintf(stderr, "%s: Error: metricpath \"%s\" not defined in the PMNS\n",
+ pmProgname, fullname);
+ exit(1);
+ }
+ else if (*tail == '\0') {
+ /* complete match */
+ if (np == base->first) {
+ /* deleted node is first at this level */
+ base->first = np->next;
+ /*
+ * remove predecessors that only exist to connect
+ * deleted node to the rest of the PMNS
+ */
+ np = base;
+ while (np->first == NULL && np->parent != NULL) {
+ if (np->parent->first == np) {
+ /* victim is the only one at this level ... */
+ np->parent->first = np->next;
+ np = np->parent;
+ }
+ else {
+ /* victim has at least one sibling at this level */
+ lastp = np->parent->first;
+ while (lastp->next != np)
+ lastp = lastp->next;
+ lastp->next = np->next;
+ break;
+ }
+ }
+ }
+ else
+ /* link around deleted node */
+ lastp->next = np->next;
+ return;
+ }
+
+ /* descend */
+ delpmns(np, tail+1);
+}
+
+int
+main(int argc, char **argv)
+{
+ int sep = __pmPathSeparator();
+ int sts;
+ int c;
+ int dupok = 0;
+ char *p;
+ char pmnsfile[MAXPATHLEN];
+ char outfname[MAXPATHLEN];
+ struct stat sbuf;
+
+ if ((p = getenv("PMNS_DEFAULT")) != NULL)
+ strcpy(pmnsfile, p);
+ else
+ snprintf(pmnsfile, sizeof(pmnsfile), "%s%c" "pmns" "%c" "root",
+ pmGetConfig("PCP_VAR_DIR"), sep, sep);
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'd': /* duplicate PMIDs are OK */
+ dupok = 1;
+ break;
+
+ case 'D': /* debug flag */
+ if ((sts = __pmParseDebug(opts.optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ pmDebug |= sts;
+ }
+ break;
+
+ case 'n': /* alternative name space file */
+ strcpy(pmnsfile, opts.optarg);
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind > argc - 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if ((sts = pmLoadASCIINameSpace(pmnsfile, dupok)) < 0) {
+ fprintf(stderr, "%s: Error: pmLoadNameSpace(%s): %s\n",
+ pmProgname, pmnsfile, pmErrStr(sts));
+ exit(1);
+ }
+
+ {
+ __pmnsTree *t;
+ t = __pmExportPMNS();
+ if (t == NULL) {
+ /* sanity check - shouldn't ever happen */
+ fprintf(stderr, "Exported PMNS is NULL !");
+ exit(1);
+ }
+ root = t->root;
+ }
+
+
+ while (opts.optind < argc) {
+ delpmns(root, fullname = argv[opts.optind]);
+ opts.optind++;
+ }
+
+ /*
+ * from here on, ignore SIGHUP, SIGINT and SIGTERM to protect
+ * the integrity of the new ouput file
+ */
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ __pmSetSignalHandler(SIGINT, SIG_IGN);
+ __pmSetSignalHandler(SIGTERM, SIG_IGN);
+
+ snprintf(outfname, sizeof(outfname), "%s.new", pmnsfile);
+ if ((outf = fopen(outfname, "w")) == NULL) {
+ fprintf(stderr, "%s: Error: cannot open PMNS file \"%s\" for writing: %s\n",
+ pmProgname, outfname, osstrerror());
+ exit(1);
+ }
+ if (stat(pmnsfile, &sbuf) == 0) {
+ /*
+ * preserve the mode and ownership of any existing PMNS file
+ */
+ chmod(outfname, sbuf.st_mode & ~S_IFMT);
+#if defined(HAVE_CHOWN)
+ if (chown(outfname, sbuf.st_uid, sbuf.st_gid) < 0)
+ fprintf(stderr, "%s: chown(%s, ...) failed: %s\n",
+ pmProgname, outfname, osstrerror());
+#endif
+ }
+
+ pmns_output(root, outf);
+ fclose(outf);
+
+ /* rename the PMNS */
+ if (rename2(outfname, pmnsfile) == -1) {
+ fprintf(stderr, "%s: cannot rename \"%s\" to \"%s\": %s\n",
+ pmProgname, outfname, pmnsfile, osstrerror());
+ /* remove the new PMNS */
+ unlink(outfname);
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/pmns/pmnsmerge.c b/src/pmns/pmnsmerge.c
new file mode 100644
index 0000000..39316a1
--- /dev/null
+++ b/src/pmns/pmnsmerge.c
@@ -0,0 +1,322 @@
+/*
+ * pmnsmerge [-adfv] infile [...] outfile
+ *
+ * Merge PCP PMNS files
+ *
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmnsutil.h"
+
+static FILE *outf; /* output */
+static __pmnsNode *root; /* result so far */
+static char *fullname; /* full PMNS pathname for newbie */
+static int verbose;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "", 0, 'a', 0, "process files in order, ignoring embedded _DATESTAMP control lines" },
+ { "duplicates", 0, 'd', 0, "duplicate PMIDs are allowed" },
+ { "force", 0, 'f', 0, "force overwriting of the output file if it exists" },
+ { "verbose", 0, 'v', 0, "verbose, echo input file names as processed" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "aD:dfv?",
+ .long_options = longopts,
+ .short_usage = "[options] infile [...] outfile",
+};
+
+typedef struct {
+ char *fname;
+ char *date;
+} datestamp_t;
+
+#define STAMP "_DATESTAMP"
+
+static int
+sortcmp(const void *a, const void *b)
+{
+ datestamp_t *pa = (datestamp_t *)a;
+ datestamp_t *pb = (datestamp_t *)b;
+
+ if (pa->date == NULL) return -1;
+ if (pb->date == NULL) return 1;
+ return strcmp(pa->date, pb->date);
+}
+
+/*
+ * scan for #define _DATESTAMP and re-order args accordingly
+ */
+static void
+sortargs(char **argv, int argc)
+{
+ FILE *f;
+ datestamp_t *tab;
+ char *p;
+ char *q;
+ int i;
+ char lbuf[40];
+
+ tab = (datestamp_t *)malloc(argc * sizeof(datestamp_t));
+
+ for (i = 0; i <argc; i++) {
+ if ((f = fopen(argv[i], "r")) == NULL) {
+ fprintf(stderr, "%s: Error: cannot open input PMNS file \"%s\"\n",
+ pmProgname, argv[i]);
+ exit(1);
+ }
+ tab[i].fname = strdup(argv[i]);
+ tab[i].date = NULL;
+ while (fgets(lbuf, sizeof(lbuf), f) != NULL) {
+ if (strncmp(lbuf, "#define", 7) != 0)
+ continue;
+ p = &lbuf[7];
+ while (*p && isspace((int)*p))
+ p++;
+ if (*p == '\0' || strncmp(p, STAMP, strlen(STAMP)) != 0)
+ continue;
+ p += strlen(STAMP);
+ while (*p && isspace((int)*p))
+ p++;
+ q = p;
+ while (*p && !isspace((int)*p))
+ p++;
+ *p = '\0';
+ tab[i].date = strdup(q);
+ break;
+ }
+ fclose(f);
+ }
+
+ qsort(tab, argc, sizeof(tab[0]), sortcmp);
+ for (i = 0; i <argc; i++) {
+ argv[i] = tab[i].fname;
+ if (verbose > 1)
+ printf("arg[%d] %s _DATESTAMP=%s\n", i, tab[i].fname, tab[i].date);
+ }
+
+ free(tab);
+}
+
+static void
+addpmns(__pmnsNode *base, char *name, __pmnsNode *p)
+{
+ char *tail;
+ ptrdiff_t nch;
+ __pmnsNode *np;
+ __pmnsNode *lastp = NULL;
+
+ for (tail = name; *tail && *tail != '.'; tail++)
+ ;
+ nch = tail - name;
+
+ for (np = base->first; np != NULL; np = np->next) {
+ if (strlen(np->name) == nch && strncmp(name, np->name, (int)nch) == 0)
+ break;
+ lastp = np;
+ }
+
+ if (np == NULL) {
+ /* no match ... add here */
+ np = (__pmnsNode *)malloc(sizeof(__pmnsNode));
+ if (base->first) {
+ lastp->next = np;
+ np->parent = lastp->parent;
+ }
+ else {
+ base->first = np;
+ np->parent = base;
+ }
+ np->first = np->next = NULL;
+ np->hash = NULL; /* we do not need this here */
+ np->name = (char *)malloc(nch+1);
+ strncpy(np->name, name, nch);
+ np->name[nch] = '\0';
+ if (*tail == '\0') {
+ np->pmid = p->pmid;
+ return;
+ }
+ np->pmid = PM_ID_NULL;
+ }
+ else if (*tail == '\0') {
+ /* complete match */
+ if (np->pmid != p->pmid) {
+ fprintf(stderr, "%s: Warning: performance metric \"%s\" has multiple PMIDs.\n... using PMID %s and ignoring PMID",
+ pmProgname, fullname, pmIDStr(np->pmid));
+ fprintf(stderr, " %s\n",
+ pmIDStr(p->pmid));
+ }
+ return;
+ }
+
+ /* descend */
+ addpmns(np, tail+1, p);
+}
+
+
+/*
+ * merge, adding new nodes if required
+ */
+static void
+merge(__pmnsNode *p, int depth, char *path)
+{
+ char *name;
+
+ if (depth < 1 || p->pmid == PM_ID_NULL || p->first != NULL)
+ return;
+ name = (char *)malloc(strlen(path)+strlen(p->name)+2);
+ if (*path == '\0')
+ strcpy(name, p->name);
+ else {
+ strcpy(name, path);
+ strcat(name, ".");
+ strcat(name, p->name);
+ }
+ fullname = name;
+ addpmns(root, name, p);
+ free(name);
+}
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ int first = 1;
+ int c;
+ int j;
+ int force = 0;
+ int asis = 0;
+ int dupok = 0;
+ __pmnsNode *tmp;
+
+ umask((mode_t)022); /* anything else is pretty silly */
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'a':
+ asis = 1;
+ break;
+
+ case 'd': /* duplicate PMIDs are OK */
+ dupok = 1;
+ break;
+
+ case 'D': /* debug flag */
+ if ((sts = __pmParseDebug(opts.optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ pmDebug |= sts;
+ }
+ break;
+
+ case 'f': /* force ... unlink file first */
+ force = 1;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind > argc - 2) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (force)
+ unlink(argv[argc-1]);
+
+ if (access(argv[argc-1], F_OK) == 0) {
+ fprintf(stderr, "%s: Error: output PMNS file \"%s\" already exists!\nYou must either remove it first, or use -f\n",
+ pmProgname, argv[argc-1]);
+ exit(1);
+ }
+
+ /*
+ * from here on, ignore SIGHUP, SIGINT and SIGTERM to protect
+ * the integrity of the new ouput file
+ */
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ __pmSetSignalHandler(SIGINT, SIG_IGN);
+ __pmSetSignalHandler(SIGTERM, SIG_IGN);
+
+ if ((outf = fopen(argv[argc-1], "w+")) == NULL) {
+ fprintf(stderr, "%s: Error: cannot create output PMNS file \"%s\": %s\n", pmProgname, argv[argc-1], osstrerror());
+ exit(1);
+ }
+
+ if (!asis)
+ sortargs(&argv[opts.optind], argc - opts.optind - 1);
+
+ j = opts.optind;
+ while (j < argc-1) {
+ if (verbose)
+ printf("%s:\n", argv[j]);
+
+ if ((sts = pmLoadASCIINameSpace(argv[j], dupok)) < 0) {
+ fprintf(stderr, "%s: Error: pmLoadNameSpace(%s): %s\n",
+ pmProgname, argv[j], pmErrStr(sts));
+ exit(1);
+ }
+ {
+ __pmnsTree *t;
+ t = __pmExportPMNS();
+ if (t == NULL) {
+ /* sanity check - shouldn't ever happen */
+ fprintf(stderr, "Exported PMNS is NULL !");
+ exit(1);
+ }
+ tmp = t->root;
+ }
+
+ if (first) {
+ root = tmp;
+ first = 0;
+ }
+ else {
+ pmns_traverse(tmp, 0, "", merge);
+ }
+ j++;
+ }
+
+ pmns_output(root, outf);
+ fclose(outf);
+
+ /*
+ * now load the merged PMNS to check for errors ...
+ */
+ if ((sts = pmLoadASCIINameSpace(argv[argc-1], dupok)) < 0) {
+ fprintf(stderr, "%s: Error: pmLoadNameSpace(%s): %s\n",
+ pmProgname, argv[argc-1], pmErrStr(sts));
+ exit(1);
+ }
+
+ exit(0);
+}
diff --git a/src/pmns/pmnsutil.c b/src/pmns/pmnsutil.c
new file mode 100644
index 0000000..1e0acf9
--- /dev/null
+++ b/src/pmns/pmnsutil.c
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmnsutil.h"
+
+static FILE *outf;
+
+/*
+ * breadth-first traversal
+ */
+void
+pmns_traverse(__pmnsNode *p, int depth, char *path, void(*func)(__pmnsNode *, int, char *))
+{
+ char *newpath;
+ __pmnsNode *q;
+
+ if (p != NULL) {
+ /* breadth */
+ for (q = p; q != NULL; q = q->next)
+ (*func)(q, depth, path);
+ if (depth > 0)
+ (*func)(NULL, -1, NULL); /* end of level */
+ /* descend */
+ for (q = p; q != NULL; q = q->next) {
+ if (q->first != NULL) {
+ newpath = (char *)malloc(strlen(path)+strlen(q->name)+2);
+ if (depth == 0)
+ *newpath = '\0';
+ else if (depth == 1)
+ strcpy(newpath, q->name);
+ else {
+ strcpy(newpath, path);
+ strcat(newpath, ".");
+ strcat(newpath, q->name);
+ }
+ pmns_traverse(q->first, depth+1, newpath, func);
+ free(newpath);
+ }
+ }
+ }
+}
+
+/*
+ * generate an ASCII PMNS from the internal format produced by
+ * pmLoadNameSpace and friends
+ */
+static void
+output(__pmnsNode *p, int depth, char *path)
+{
+ static int lastdepth = -1;
+
+ if (depth == 0) {
+ fprintf(outf, "root {\n");
+ lastdepth = 1;
+ return;
+ }
+ else if (depth < 0) {
+ if (lastdepth > 0)
+ fprintf(outf, "}\n");
+ lastdepth = -1;
+ return;
+ }
+ else if (depth != lastdepth)
+ fprintf(outf, "\n%s {\n", path);
+ lastdepth = depth;
+ if (p->first != NULL)
+ fprintf(outf, "\t%s\n", p->name);
+ else {
+ if (pmid_domain(p->pmid) == DYNAMIC_PMID && pmid_item(p->pmid) == 0)
+ fprintf(outf, "\t%s\t%d:*:*\n", p->name, pmid_cluster(p->pmid));
+ else
+ fprintf(outf, "\t%s\t%d:%d:%d\n", p->name, pmid_domain(p->pmid), pmid_cluster(p->pmid), pmid_item(p->pmid));
+ }
+}
+
+void
+pmns_output(__pmnsNode *root, FILE *f)
+{
+ outf = f;
+ pmns_traverse(root, 0, "", output);
+ output(NULL, -2, NULL); /* special hack for null PMNS */
+}
diff --git a/src/pmns/pmnsutil.h b/src/pmns/pmnsutil.h
new file mode 100644
index 0000000..dd6c536
--- /dev/null
+++ b/src/pmns/pmnsutil.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+extern void pmns_traverse(__pmnsNode *, int , char *, void(*)(__pmnsNode *, int, char *));
+extern void pmns_output(__pmnsNode *, FILE *);
+
diff --git a/src/pmns/stdpmid.local b/src/pmns/stdpmid.local
new file mode 100644
index 0000000..6d55daa
--- /dev/null
+++ b/src/pmns/stdpmid.local
@@ -0,0 +1,5 @@
+# Domain numbers for local PMDAs
+#
+# PMDA Name Domain Number
+#
+#EXAMPLE 123
diff --git a/src/pmns/stdpmid.pcp b/src/pmns/stdpmid.pcp
new file mode 100644
index 0000000..1296c81
--- /dev/null
+++ b/src/pmns/stdpmid.pcp
@@ -0,0 +1,128 @@
+# Domain numbers for PMDAs in the base PCP product
+#
+# PMDA Name Domain Number
+#
+IRIX 1
+PMCD 2
+PROC 3
+ENVIRON 4
+CISCO 5
+HIPPI 6
+HOTPROC 7
+NETPROBE 8
+MAILQ 9
+TRACE 10
+XFS 11
+FSAFE 12
+MPI 13
+DMF 14
+SENDMAIL 15
+XVM 16
+BROCADE 17
+ESPPING 18
+SHPING 19
+HPUX 20
+WEBPING 21
+WEBSERVER 22
+ARRAY0 23
+ARRAY1 24
+ARRAY2 25
+ARRAY3 26
+SYSSUMMARY 27
+NEWS 28
+SAMPLE 29
+SAMPLEDSO 30
+KERN_DEV 31
+ORACLE 32
+SYBASE 40
+INFORMIX 48
+SNMP 56
+LOCKSTAT 59
+LINUX 60
+MYRINET 61
+NFSCLIENT 62
+SNIA 63
+CLUSTER 65
+MYSQL 66
+LOGTAIL 67
+APACHE 68
+ROOMTEMP 69
+MMV 70
+PROCESS 71
+MOUNTS 72
+SNIFF 73
+LMSENSORS 74
+SOLARIS 75
+SAMBA 76
+NFS 77
+DARWIN 78
+WINDOWS 79
+AIX 80
+TG3 81
+CXFS 82
+FCSW 83
+CPUSET 84
+FREEBSD 85
+RPCBIND 86
+ETW 87
+SYSTEMTAP 88
+MEMCACHE 89
+VMWARE 90
+IB 91
+ISCSI 92
+LUSTRECOMM 93
+SDR 94
+KVM 95
+BONDING 96
+NETFILTER 97
+ZIMBRA 98
+UV 99
+NAMED 100
+PDNS 101
+DTSRUN 102
+POSTFIX 103
+IPMI 104
+GPSD 105
+LOGGER 106
+RSYSLOG 107
+ELASTICSEARCH 108
+MSSQL 109
+POSTGRESQL 110
+CTDB 111 /* Clustered Trivial Database (samba.org) */
+BASH 112
+FIBRECHANNEL 113
+SYSTEMD 114
+GFS2 115
+NETBSD 116
+NGINX 117
+GLUSTER 118
+PANASAS 119
+NVML 120
+CIFS 121
+JBD2 122
+RPM 123
+QPID 124
+ZSWAP 125
+PAPI 126
+PERFEVENT 127
+PIPE 128
+DMCACHE 129
+### NEXT FREE SLOT ###
+SLOW_PYTHON 242
+SLOW 243
+DBPING 244
+PMI_DOMAIN 245
+LAB 246
+DYNAMIC 247
+TXMON 248
+BROKEN 249
+TRIVIAL 250
+SYBPING 251
+INFMXPING 252
+SIMPLE 253
+ORAPING 254
+### MORE FREE SLOTS ###
+#
+# 511 is REALLY reserved ... see DYNAMIC_PMID in impl.h
+#
+RESERVED_DO_NOT_USE 511
diff --git a/src/pmns/unlockpmns b/src/pmns/unlockpmns
new file mode 100644
index 0000000..69d094c
--- /dev/null
+++ b/src/pmns/unlockpmns
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Unlock the PMNS for concurrent transactional updates
+#
+
+. $PCP_DIR/etc/pcp.env
+
+lock=${1-$PCP_VAR_DIR/pmns/root}.lock
+
+#DEBUG# echo "pmnsunlock: `date; ls -li $lock`"
+
+rm -f $lock
+
+exit 0
diff --git a/src/pmnscomp/GNUmakefile b/src/pmnscomp/GNUmakefile
new file mode 100644
index 0000000..fd19998
--- /dev/null
+++ b/src/pmnscomp/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmnscomp.c
+CMDTARGET = pmnscomp$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmnscomp/pmnscomp.c b/src/pmnscomp/pmnscomp.c
new file mode 100644
index 0000000..ae10fa2
--- /dev/null
+++ b/src/pmnscomp/pmnscomp.c
@@ -0,0 +1,621 @@
+/*
+ * Construct a compiled PMNS suitable for "fast" loading in pmLoadNameSpace
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 1995-2006 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static int nodecnt; /* number of nodes */
+static int leafcnt; /* number of leaf nodes */
+static int symbsize; /* aggregate string table size */
+static __pmnsNode **_htab; /* output htab[] */
+static __pmnsNode *_nodetab; /* output nodes */
+static char *_symbol; /* string table */
+static char *symp; /* pointer into same */
+static __pmnsNode **_map; /* point-to-ordinal map */
+static int i; /* node counter for traversal */
+
+static int version = 2; /* default output format version */
+
+/*
+ * 32-bit pointer version of __pmnsNode
+ */
+typedef struct {
+ __int32_t parent;
+ __int32_t next;
+ __int32_t first;
+ __int32_t hash;
+ __int32_t name;
+ pmID pmid;
+} __pmnsNode32;
+static __int32_t *_htab32;
+static __pmnsNode32 *_nodetab32;
+
+/*
+ * 64-bit pointer version of __pmnsNode
+ */
+typedef struct {
+ __int64_t parent;
+ __int64_t next;
+ __int64_t first;
+ __int64_t hash;
+ __int64_t name;
+ pmID pmid;
+ __int32_t __pad__;
+} __pmnsNode64;
+static __int64_t *_htab64;
+static __pmnsNode64 *_nodetab64;
+
+static void
+dumpmap(void)
+{
+ int n;
+
+ for (n = 0; n < nodecnt; n++) {
+ if (n % 8 == 0) {
+ if (n)
+ putchar('\n');
+ printf("map[%3d]", n);
+ }
+ printf(" " PRINTF_P_PFX "%p", _map[n]);
+ }
+ putchar('\n');
+}
+
+static long
+nodemap(__pmnsNode *p)
+{
+ int n;
+
+ if (p == NULL)
+ return -1;
+
+ for (n = 0; n < nodecnt; n++) {
+ if (_map[n] == p)
+ return n;
+ }
+ printf("%s: fatal error, cannot map node addr " PRINTF_P_PFX "%p\n", pmProgname, p);
+ dumpmap();
+ exit(1);
+}
+
+static void
+traverse(__pmnsNode *p, void(*func)(__pmnsNode *this))
+{
+ if (p != NULL) {
+ (*func)(p);
+ traverse(p->first, func);
+ traverse(p->next, func);
+ }
+}
+
+#ifdef PCP_DEBUG
+static void
+chkascii(char *tag, char *p)
+{
+ int i = 0;
+
+ while (*p) {
+ if (!isascii((int)*p) || !isprint((int)*p)) {
+ printf("chkascii: %s: non-printable char 0x%02x in \"%s\"[%d] @ " PRINTF_P_PFX "%p\n",
+ tag, *p & 0xff, p, i, p);
+ exit(1);
+ }
+ i++;
+ p++;
+ }
+}
+
+static char *chktag;
+
+static void
+chknames(__pmnsNode *p)
+{
+ chkascii(chktag, p->name);
+}
+#endif
+
+static void
+pass1(__pmnsNode *p)
+{
+ nodecnt++;
+ if (p->pmid != PM_ID_NULL && p->first == NULL)
+ leafcnt++;
+ symbsize += strlen(p->name)+1;
+}
+
+static void
+pass2(__pmnsNode *p)
+{
+ ptrdiff_t offset;
+ _map[i] = p;
+ _nodetab[i] = *p; /* struct assignment */
+ strcpy(symp, p->name);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ chkascii("pass2 symtab", symp);
+#endif
+ offset = symp - _symbol;
+ symp += strlen(p->name);
+ *symp++ = '\0';
+ _nodetab[i].name = (char *)offset;
+ i++;
+}
+
+static void
+pass3(__pmnsNode *p)
+{
+ _nodetab[i].parent = (__pmnsNode *)nodemap(_nodetab[i].parent);
+ _nodetab[i].next = (__pmnsNode *)nodemap(_nodetab[i].next);
+ _nodetab[i].first = (__pmnsNode *)nodemap(_nodetab[i].first);
+ _nodetab[i].hash = (__pmnsNode *)nodemap(_nodetab[i].hash);
+ i++;
+}
+
+#ifndef __htonll
+void
+__htonll(char *p)
+{
+ char c;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ c = p[i];
+ p[i] = p[7-i];
+ p[7-i] = c;
+ }
+}
+#endif
+
+#ifdef HAVE_NETWORK_BYTEORDER
+#define __ntohll(a) /* noop */
+#else
+#define __ntohll(v) __htonll(v)
+#endif
+
+/*
+ * Promote a pointer to a 64 bit interger and do the endianess
+ * conversion on a 64 bit integer. Promotion should be sign
+ * extending because we're actually dealing with integers and
+ * not real pointers, so if promoting -1 from 32 to 64 bits, we
+ * want -1 to appear on the other end as well.
+ */
+static __int64_t
+to64_htonll(void *from)
+{
+ __int64_t to = (__psint_t)from; /* compiler sign extends */
+
+ __htonll((char *)&to);
+ return to;
+}
+
+/*
+ * And this is the reverse - take something which purpots to be a pointer
+ * and stuff it into an 32 bit integer. If cannot do it safely, then
+ * complain and exit
+ */
+static __int32_t
+to32_htonl(void *from)
+{
+ __int32_t to = (__int32_t)(__psint_t)from; /* compiler truncates */
+
+ if (to != (__psint_t)from) {
+ fprintf(stderr, "%s: loss of precision during the conversion\n",
+ pmProgname);
+ exit(1);
+ }
+ return htonl(to);
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ { "duplicates", 0, 'd', 0, "duplicate PMIDs are allowed" },
+ { "force", 0, 'f', 0, "force overwriting of the output file if it exists" },
+ PMOPT_NAMESPACE,
+ { "version", 1, 'v', "N", "alternate output format version [default 2]" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "dD:fn:v:?",
+ .long_options = longopts,
+ .short_usage = "[options] outfile",
+};
+
+int
+main(int argc, char **argv)
+{
+ int n;
+ int c;
+ int j;
+ int sts;
+ int force = 0;
+ int dupok = 0;
+ char *pmnsfile = PM_NS_DEFAULT;
+ char *endnum;
+ FILE *outf;
+ __pmnsNode *root;
+ __pmnsNode **htab;
+ int htabcnt; /* count of the number of htab[] entries */
+ __int32_t tmp;
+ __int32_t sum;
+ long startsum = 0; /* initialize to pander to gcc */
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'd': /* duplicate PMIDs are allowed */
+ dupok = 1;
+ break;
+
+ case 'D': /* debug flag */
+ sts = __pmParseDebug(opts.optarg);
+ if (sts < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ else
+ pmDebug |= sts;
+ break;
+
+ case 'f': /* force ... unlink file first */
+ force = 1;
+ break;
+
+ case 'n': /* alternative namespace file */
+ pmnsfile = opts.optarg;
+ break;
+
+ case 'v': /* alternate version */
+ version = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0') {
+ pmprintf("%s: -v requires numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ if (version < 1 || version > 2) {
+ pmprintf("%s: output format version %d not supported\n",
+ pmProgname, version);
+ opts.errors++;
+ }
+ break;
+
+ case '?':
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind != argc-1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (force) {
+ struct stat sbuf;
+
+ if (stat(argv[opts.optind], &sbuf) == -1) {
+ if (oserror() != ENOENT) {
+ fprintf(stderr, "%s: cannot stat \"%s\": %s\n",
+ pmProgname, argv[opts.optind], osstrerror());
+ exit(1);
+ }
+ }
+ else {
+ /* stat is OK, so exists ... must be a regular file */
+ if (!S_ISREG(sbuf.st_mode)) {
+ fprintf(stderr, "%s: \"%s\" is not a regular file\n",
+ pmProgname, argv[opts.optind]);
+ exit(1);
+ }
+ if (unlink(argv[opts.optind]) == -1) {
+ fprintf(stderr, "%s: cannot unlink \"%s\": %s\n",
+ pmProgname, argv[opts.optind], osstrerror());
+ exit(1);
+ }
+ }
+ }
+
+ if (access(argv[opts.optind], F_OK) == 0) {
+ fprintf(stderr, "%s: \"%s\" already exists!\nYou must either remove it first, or use -f\n",
+ pmProgname, argv[opts.optind]);
+ exit(1);
+ }
+
+ if ((n = pmLoadASCIINameSpace(pmnsfile, dupok)) < 0) {
+ fprintf(stderr, "pmLoadNameSpace: %s\n", pmErrStr(n));
+ exit(1);
+ }
+
+ {
+ __pmnsTree *t;
+ t = __pmExportPMNS();
+ if (t == NULL) {
+ /* sanity check - shouldn't ever happen */
+ fprintf(stderr, "%s: Exported PMNS is NULL!\n", pmProgname);
+ exit(1);
+ }
+ root = t->root;
+ htabcnt = t->htabsize;
+ htab = t->htab;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ chktag = "pmLoadASCIINameSpace";
+ traverse(root, chknames);
+ }
+#endif
+
+ traverse(root, pass1);
+
+ _htab = (__pmnsNode **)malloc(htabcnt * sizeof(_htab[0]));
+ _nodetab = (__pmnsNode *)malloc(nodecnt * sizeof(_nodetab[0]));
+ symp = _symbol = (char *)malloc(symbsize);
+ _map = (__pmnsNode **)malloc(nodecnt * sizeof(_map[0]));
+
+ if (_htab == NULL || _nodetab == NULL ||
+ symp == NULL || _map == NULL) {
+ __pmNoMem("pmnscomp", htabcnt * sizeof(_htab[0]) +
+ nodecnt * sizeof(_nodetab[0]) +
+ symbsize +
+ nodecnt * sizeof(_map[0]), PM_FATAL_ERR);
+ }
+
+ i = 0;
+ traverse(root, pass2);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ chktag = "pass2";
+ traverse(root, chknames);
+ }
+#endif
+
+ memcpy(_htab, htab, htabcnt * sizeof(htab[0]));
+ for (j = 0; j < htabcnt; j++)
+ _htab[j] = (__pmnsNode *)nodemap(_htab[j]);
+
+ i = 0;
+ traverse(root, pass3);
+
+ /*
+ * from here on, ignore SIGHUP, SIGINT and SIGTERM to protect
+ * the integrity of the new ouput file
+ */
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ __pmSetSignalHandler(SIGINT, SIG_IGN);
+ __pmSetSignalHandler(SIGTERM, SIG_IGN);
+
+ if ((outf = fopen(argv[opts.optind], "w+")) == NULL) {
+ fprintf(stderr, "%s: cannot create \"%s\": %s\n",
+ pmProgname, argv[opts.optind], osstrerror());
+ exit(1);
+ }
+
+ /*
+ * Format verisons
+ * 0 PmNs - original PCP 1.0, 32-bit format
+ * 1 PmN1 - PCP 1.1 format with both 32 and 64-bit formats
+ * for MIPS
+ * 2 PmN2 - same as PmN1, but with initial checksum
+ *
+ * Note: all of this must be understood by pmLoadNameSpace() as well
+ */
+ if (version == 0)
+ fprintf(outf, "PmNs");
+ else if (version == 1)
+ fprintf(outf, "PmN1");
+ else if (version == 2)
+ fprintf(outf, "PmN2");
+
+ if (version == 2) {
+ /* dummy at this stage, filled in later */
+ fwrite(&sum, sizeof(sum), 1, outf);
+ startsum = ftell(outf);
+ }
+
+ if(version == 1 || version == 2) {
+ /*
+ * Version 1, after label, comes repetitions of, one for each "style"
+ *
+ * <symbsize> | __int32_t
+ * <_symbol> |
+ * <htabcnt><size of _htab[0]> | style 1, __int32_t
+ * <nodecnt><size of _nodetab[0]> | __int32_t
+ * <_htab>
+ * <_nodetab>
+ * <htabcnt><size of _htab[0]> | style 2, __int32_t
+ * <nodecnt><size of _nodetab[0]> | __int32_t
+ * <_htab>
+ * <_nodetab>
+ * .... | style 3, ...
+ *
+ * Version 2 is similar, except the checksum follows the label,
+ * then as for Version 1.
+ */
+
+ tmp = htonl((__int32_t)symbsize);
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+
+ fwrite(_symbol, sizeof(_symbol[0]), symbsize, outf);
+
+ tmp = htonl((__int32_t)htabcnt);
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)sizeof(_htab32[0]));
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)nodecnt);
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)sizeof(_nodetab32[0]));
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+
+ _htab32 = (__int32_t *)malloc(htabcnt * sizeof(__int32_t));
+ for (j = 0; j < htabcnt; j++)
+ _htab32[j] = to32_htonl(_htab[j]);
+ fwrite(_htab32, sizeof(_htab32[0]), htabcnt, outf);
+ _nodetab32 = (__pmnsNode32 *)malloc(nodecnt * sizeof(__pmnsNode32));
+ for (j = 0; j < nodecnt; j++) {
+ _nodetab32[j].parent = to32_htonl(_nodetab[j].parent);
+ _nodetab32[j].next = to32_htonl(_nodetab[j].next);
+ _nodetab32[j].first = to32_htonl(_nodetab[j].first);
+ _nodetab32[j].hash = to32_htonl(_nodetab[j].hash);
+ _nodetab32[j].name = to32_htonl(_nodetab[j].name);
+ _nodetab32[j].pmid = htonl(_nodetab[j].pmid);
+ }
+ fwrite(_nodetab32, sizeof(_nodetab32[0]), nodecnt, outf);
+
+ tmp = htonl((__int32_t)htabcnt);
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)sizeof(_htab64[0]));
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)nodecnt);
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+ tmp = htonl((__int32_t)sizeof(_nodetab64[0]));
+ fwrite(&tmp, sizeof(tmp), 1, outf);
+
+ _htab64 = (__int64_t *)malloc(htabcnt * sizeof(__int64_t));
+ for (j = 0; j < htabcnt; j++) {
+ /*
+ * Danger ahead ... serious cast games here to convert
+ * 32-bit ptrs to 64-bit integers _with_ sign extension
+ */
+ _htab64[j] = to64_htonll(_htab[j]);
+ }
+ fwrite(_htab64, sizeof(_htab64[0]), htabcnt, outf);
+ _nodetab64 = (__pmnsNode64 *)malloc(nodecnt * sizeof(__pmnsNode64));
+ for (j = 0; j < nodecnt; j++) {
+ /*
+ * Danger ahead ... serious cast games here to convert
+ * 32-bit ptrs to 64-bit integers _with_ sign extension
+ */
+ _nodetab64[j].parent = to64_htonll(_nodetab[j].parent);
+ _nodetab64[j].next = to64_htonll(_nodetab[j].next);
+ _nodetab64[j].first = to64_htonll(_nodetab[j].first);
+ _nodetab64[j].hash = to64_htonll(_nodetab[j].hash);
+ _nodetab64[j].name = to64_htonll(_nodetab[j].name);
+
+ _nodetab64[j].pmid = htonl(_nodetab[j].pmid);
+ _nodetab64[j].__pad__ = htonl(0xdeadbeef);
+ }
+ fwrite(_nodetab64, sizeof(_nodetab64[0]), nodecnt, outf);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ printf("32-bit Header: htab[%d] x %d bytes, nodetab[%d] x %d bytes\n",
+ htabcnt, (int)sizeof(_htab32[0]),
+ nodecnt, (int)sizeof(_nodetab32[0]));
+ printf("\n32-bit Hash Table\n");
+ for (j = 0; j < htabcnt; j++) {
+ if (j % 10 == 0) {
+ if (j)
+ putchar('\n');
+ printf("htab32[%3d]", j);
+ }
+ printf(" %5d", (int)ntohl(_htab32[j]));
+ }
+ printf("\n\n32-bit Node Table\n");
+ for (j = 0; j < nodecnt; j++) {
+ if (j % 20 == 0)
+ printf(" Parent Next First Hash Symbol PMID\n");
+ printf("node32[%4d] %6d %6d %6d %6d %-16.16s",
+ j, (int)ntohl(_nodetab32[j].parent),
+ (int)ntohl(_nodetab32[j].next),
+ (int)ntohl(_nodetab32[j].first),
+ (int)ntohl(_nodetab32[j].hash),
+ _symbol+ntohl(_nodetab32[j].name));
+ if (htonl(_nodetab32[j].first) == -1)
+ printf(" %s", pmIDStr(htonl(_nodetab32[j].pmid)));
+ putchar('\n');
+ }
+ printf("\n64-bit Header: htab[%d] x %d bytes, nodetab[%d] x %d bytes\n",
+ htabcnt, (int)sizeof(_htab64[0]),
+ nodecnt, (int)sizeof(_nodetab64[0]));
+ printf("\n64-bit Hash Table\n");
+ for (j = 0; j < htabcnt; j++) {
+ __int64_t k64 = _htab64[j];
+ if (j % 10 == 0) {
+ if (j)
+ putchar('\n');
+ printf("htab64[%3d]", j);
+ }
+ __ntohll((char *)&k64);
+ printf(" %5" PRIi64, k64);
+ }
+ printf("\n\n64-bit Node Table\n");
+ for (j = 0; j < nodecnt; j++) {
+ __pmnsNode64 t = _nodetab64[j]; /* struct copy */
+
+ if (j % 20 == 0)
+ printf(" Parent Next First Hash Symbol PMID\n");
+
+ __ntohll ((char *)&t.name);
+ __ntohll ((char *)&t.parent);
+ __ntohll ((char *)&t.first);
+ __ntohll ((char *)&t.next);
+ __ntohll ((char *)&t.hash);
+
+ printf("node64[%4d] "
+ "%6" PRIi64 " %6" PRIi64 " %6" PRIi64 " %6" PRIi64
+ " %-16.16s",
+ j, t.parent, t.next, t.first, t.hash, _symbol+t.name);
+ if (t.first == -1) {
+ printf(" %s", pmIDStr(htonl(_nodetab64[j].pmid)));
+ }
+ putchar('\n');
+ }
+ }
+#endif
+ }
+
+ if (version == 2) {
+ fseek(outf, startsum, SEEK_SET);
+ sum = __pmCheckSum(outf);
+ fseek(outf, startsum - (long)sizeof(sum), SEEK_SET);
+ tmp = htonl(sum);
+ fwrite(&tmp, sizeof(sum), 1, outf);
+ }
+
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ if (version == 2)
+ printf("\nChecksum 0x%x\n", sum);
+ printf("\nSymbol Table\n");
+ for (j = 0; j < symbsize; j++) {
+ if (j % 50 == 0) {
+ if (j)
+ putchar('\n');
+ printf("symbol[%4d] ", j);
+ }
+ if (_symbol[j])
+ putchar(_symbol[j]);
+ else
+ putchar('*');
+ }
+ putchar('\n');
+ }
+#endif
+
+ printf("Compiled PMNS contains\n\t%5d hash table entries\n\t%5d leaf nodes\n\t%5d non-leaf nodes\n\t%5d bytes of symbol table\n",
+ htabcnt, leafcnt, nodecnt - leafcnt, symbsize);
+
+ exit(0);
+}
diff --git a/src/pmpost/GNUmakefile b/src/pmpost/GNUmakefile
new file mode 100644
index 0000000..c39a96c
--- /dev/null
+++ b/src/pmpost/GNUmakefile
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmpost.c
+CMDTARGET = pmpost$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+
+default: $(CMDTARGET)
+
+install: default
+ $(INSTALL) -m 755 -o root $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+include $(BUILDRULES)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmpost/pmpost.c b/src/pmpost/pmpost.c
new file mode 100644
index 0000000..2691cc9
--- /dev/null
+++ b/src/pmpost/pmpost.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <sys/stat.h>
+#include <sys/file.h>
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
+/*
+ * Attempt to setup the notices file in a way that members of
+ * the (unprivileged) "pcp" group account can write to it.
+ */
+int gid;
+#ifdef HAVE_GETGRNAM
+static void
+setup_group(void)
+{
+ char *name = pmGetConfig("PCP_GROUP");
+ struct group *group;
+
+ if (!name || name[0] == '\0')
+ name = "pcp";
+ group = getgrnam(name);
+ if (group)
+ gid = group->gr_gid;
+}
+#else
+#define setup_group()
+#endif
+
+static int
+mkdir_r(char *path)
+{
+ struct stat sbuf;
+ int sts;
+
+ if (stat(path, &sbuf) < 0) {
+ if (mkdir_r(dirname(strdup(path))) < 0)
+ return -1;
+ sts = mkdir2(path, 0775);
+ if (chown(path, 0, gid) < 0)
+ fprintf(stderr, "pmpost: cannot set dir gid[%d]: %s\n", gid, path);
+ return sts;
+ }
+ else if ((sbuf.st_mode & S_IFDIR) == 0) {
+ fprintf(stderr, "pmpost: \"%s\" is not a directory\n", path);
+ exit(1);
+ }
+ return 0;
+}
+
+#define LAST_UNDEFINED -1
+#define LAST_NEWFILE -2
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ int fd;
+ FILE *np;
+ char *dir;
+ struct stat sbuf;
+ time_t now;
+ int lastday = LAST_UNDEFINED;
+ struct tm *tmp;
+ int sts = 0;
+ char notices[MAXPATHLEN];
+#ifndef IS_MINGW
+ char *ep;
+ struct flock lock;
+ extern char **environ;
+ static char *newenviron[] =
+ { "HOME=/nowhere", "SHELL=/noshell", "PATH=/nowhere", NULL };
+ static char *keepname[] =
+ { "TZ", "PCP_DIR", "PCP_CONF", NULL };
+ char *keepval[] =
+ { NULL, NULL, NULL, NULL };
+
+ /*
+ * Fix for bug #827972, do not trust the environment.
+ * See also below.
+ */
+ for (i = 0; keepname[i] != NULL; i++)
+ keepval[i] = getenv(keepname[i]);
+ environ = newenviron;
+ for (i = 0; keepname[i] != NULL; i++) {
+ if (keepval[i] != NULL) {
+ snprintf(notices, sizeof(notices), "%s=%s", keepname[i], keepval[i]);
+ if ((ep = strdup(notices)) != NULL)
+ putenv(ep);
+ }
+ }
+#endif
+ umask(0002);
+
+ if ((argc == 1) || (argc == 2 && strcmp(argv[1], "-?") == 0)) {
+ fprintf(stderr, "Usage: pmpost message\n");
+ exit(1);
+ }
+
+ snprintf(notices, sizeof(notices), "%s%c" "NOTICES",
+ pmGetConfig("PCP_LOG_DIR"), __pmPathSeparator());
+
+ setup_group();
+ dir = dirname(strdup(notices));
+ if (mkdir_r(dir) < 0) {
+ fprintf(stderr, "pmpost: cannot create directory \"%s\": %s\n",
+ dir, osstrerror());
+ exit(1);
+ }
+
+ if ((fd = open(notices, O_WRONLY|O_APPEND, 0)) < 0) {
+ if ((fd = open(notices, O_WRONLY|O_CREAT|O_APPEND, 0664)) < 0) {
+ fprintf(stderr, "pmpost: cannot create file \"%s\": %s\n",
+ notices, osstrerror());
+ exit(1);
+ } else if ((fchown(fd, 0, gid)) < 0) {
+ fprintf(stderr, "pmpost: cannot set file gid \"%s\": %s\n",
+ notices, osstrerror());
+ }
+ lastday = LAST_NEWFILE;
+ }
+
+#ifndef IS_MINGW
+ /*
+ * drop root privileges for bug #827972
+ */
+ if (setuid(getuid()) < 0)
+ exit(1);
+
+ lock.l_type = F_WRLCK;
+ lock.l_whence = 0;
+ lock.l_start = 0;
+ lock.l_len = 0;
+
+ /*
+ * willing to try for 3 seconds to get the lock ... note fcntl()
+ * does not block, unlike flock()
+ */
+ for (i = 0; i < 3; i++) {
+ if ((sts = fcntl(fd, F_SETLK, &lock)) != -1)
+ break;
+ sleep(1);
+ }
+
+ if (sts == -1) {
+ fprintf(stderr, "pmpost: warning, cannot lock file \"%s\"", notices);
+ if (oserror() != EINTR)
+ fprintf(stderr, ": %s", osstrerror());
+ fputc('\n', stderr);
+ }
+ sts = 0;
+#endif
+
+ /*
+ * have lock, get last modified day unless file just created
+ */
+ if (lastday != LAST_NEWFILE) {
+ if (fstat(fd, &sbuf) < 0)
+ /* should never happen */
+ ;
+ else {
+ tmp = localtime(&sbuf.st_mtime);
+ lastday = tmp->tm_yday;
+ }
+ }
+
+ if ((np = fdopen(fd, "a")) == NULL) {
+ fprintf(stderr, "pmpost: fdopen: %s\n", osstrerror());
+ exit(1);
+ }
+
+ time(&now);
+ tmp = localtime(&now);
+
+ if (lastday != tmp->tm_yday) {
+ if (fprintf(np, "\nDATE: %s", ctime(&now)) < 0)
+ sts = oserror();
+ }
+
+ if (fprintf(np, "%02d:%02d", tmp->tm_hour, tmp->tm_min) < 0)
+ sts = oserror();
+
+ for (i = 1; i < argc; i++) {
+ if (fprintf(np, " %s", argv[i]) < 0)
+ sts = oserror();
+ }
+
+ if (fputc('\n', np) < 0)
+ sts = oserror();
+
+ if (fclose(np) < 0)
+ sts = oserror();
+
+ if (sts < 0) {
+ fprintf(stderr, "pmpost: write failed: %s\n", osstrerror());
+ fprintf(stderr, "Lost message ...");
+ for (i = 1; i < argc; i++) {
+ fprintf(stderr, " %s", argv[i]);
+ }
+ fputc('\n', stderr);
+ }
+
+ exit(0);
+}
diff --git a/src/pmprobe/GNUmakefile b/src/pmprobe/GNUmakefile
new file mode 100644
index 0000000..475a11d
--- /dev/null
+++ b/src/pmprobe/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmprobe.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmprobe$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/pmprobe/pmprobe.c b/src/pmprobe/pmprobe.c
new file mode 100644
index 0000000..c9b2a22
--- /dev/null
+++ b/src/pmprobe/pmprobe.c
@@ -0,0 +1,361 @@
+/*
+ * pmprobe - light-weight pminfo for configuring monitor apps
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <unistd.h>
+#include "pmapi.h"
+#include "impl.h"
+
+static char **namelist;
+static pmID *pmidlist;
+static int listsize;
+static int numpmid;
+static int *instlist;
+static char **instnamelist;
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_ARCHIVE,
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ PMOPT_LOCALPMDA,
+ PMOPT_SPECLOCAL,
+ PMOPT_NAMESPACE,
+ PMOPT_ORIGIN,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "force", 0, 'f', 0, "report all pmGetIndom or pmGetInDomArchive instances" },
+ { "external", 0, 'I', 0, "list external instance names" },
+ { "internal", 0, 'i', 0, "list internal instance numbers" },
+ { "verbose", 0, 'V', 0, "report PDU operations (verbose)" },
+ { "values", 0, 'v', 0, "list metric values" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_STDOUT_TZ,
+ .short_options = "a:D:fh:IiK:Ln:O:VvZ:z?",
+ .long_options = longopts,
+ .short_usage = "[options] [metricname ...]",
+};
+
+static void
+dometric(const char *name)
+{
+ if (numpmid >= listsize) {
+ size_t size;
+
+ listsize = listsize == 0 ? 16 : listsize * 2;
+ size = listsize * sizeof(pmidlist[0]);
+ if ((pmidlist = (pmID *)realloc(pmidlist, size)) == NULL)
+ __pmNoMem("realloc pmidlist", size, PM_FATAL_ERR);
+ size = listsize * sizeof(namelist[0]);
+ if ((namelist = (char **)realloc(namelist, size)) == NULL)
+ __pmNoMem("realloc namelist", size, PM_FATAL_ERR);
+ }
+
+ namelist[numpmid]= strdup(name);
+ if (namelist[numpmid] == NULL)
+ __pmNoMem("strdup name", strlen(name), PM_FATAL_ERR);
+
+ numpmid++;
+}
+
+static int
+lookup(pmInDom indom)
+{
+ static pmInDom last = PM_INDOM_NULL;
+ static int numinst;
+
+ if (indom != last) {
+ if (numinst > 0) {
+ free(instlist);
+ free(instnamelist);
+ }
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ numinst = pmGetInDomArchive(indom, &instlist, &instnamelist);
+ else
+ numinst = pmGetInDom(indom, &instlist, &instnamelist);
+ last = indom;
+ }
+ return numinst;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, i, j, sts;
+ int fetch_sts;
+ int numinst;
+ int fflag = 0; /* -f pmGetIndom or pmGetIndomArchive for instances */
+ int iflag = 0; /* -i for instance numbers */
+ int Iflag = 0; /* -I for instance names */
+ int vflag = 0; /* -v for values */
+ int Vflag = 0; /* -V for verbose */
+ char *source;
+ pmResult *result;
+ pmValueSet *vsp;
+ pmDesc desc;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'f': /* pmGetIndom or pmGetInDomArchive for instances with -i or -I */
+ fflag++;
+ break;
+
+ case 'i': /* report internal instance numbers */
+ if (vflag) {
+ pmprintf("%s: at most one of -i and -v allowed\n", pmProgname);
+ opts.errors++;
+ }
+ iflag++;
+ break;
+
+ case 'I': /* report external instance names */
+ if (vflag) {
+ pmprintf("%s: at most one of -I and -v allowed\n", pmProgname);
+ opts.errors++;
+ }
+ Iflag++;
+ break;
+
+ case 'V': /* verbose */
+ Vflag++;
+ break;
+
+ case 'v': /* cheap values */
+ if (iflag || Iflag) {
+ pmprintf("%s: at most one of -v and (-i or -I) allowed\n", pmProgname);
+ opts.errors++;
+ }
+ vflag++;
+ break;
+
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ source = opts.archives[0];
+ else if (opts.context == PM_CONTEXT_HOST)
+ source = opts.hosts[0];
+ else if (opts.context == PM_CONTEXT_LOCAL)
+ source = NULL;
+ else {
+ opts.context = PM_CONTEXT_HOST;
+ source = "local:";
+ }
+
+ if ((sts = c = pmNewContext(opts.context, source)) < 0) {
+ if (opts.context == PM_CONTEXT_HOST)
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ else if (opts.context == PM_CONTEXT_LOCAL)
+ fprintf(stderr, "%s: Cannot make standalone connection on localhost: %s\n",
+ pmProgname, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ exit(1);
+ }
+
+ /* complete TZ and time window option (origin) setup */
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ if (pmGetContextOptions(c, &opts)) {
+ pmflush();
+ exit(1);
+ }
+ }
+
+ if (opts.optind >= argc)
+ pmTraversePMNS("", dometric);
+ else {
+ for (i = opts.optind; i < argc; i++) {
+ sts = pmTraversePMNS(argv[i], dometric);
+ if (sts < 0)
+ printf("%s %d %s\n", argv[i], sts, pmErrStr(sts));
+ }
+ }
+
+ /* Lookup names.
+ * Cull out names that were unsuccessfully looked up.
+ * However, it is unlikely to fail because names come from a traverse PMNS.
+ */
+ if ((sts = pmLookupName(numpmid, namelist, pmidlist)) < 0) {
+ for (i = j = 0; i < numpmid; i++) {
+ if (pmidlist[i] == PM_ID_NULL) {
+ printf("%s %d %s\n", namelist[i], sts, pmErrStr(sts));
+ free(namelist[i]);
+ }
+ else {
+ /* assert(j <= i); */
+ pmidlist[j] = pmidlist[i];
+ namelist[j] = namelist[i];
+ j++;
+ }
+ }
+ numpmid = j;
+ }
+
+ fetch_sts = 0;
+ for (i = 0; i < numpmid; i++) {
+ printf("%s ", namelist[i]);
+
+ if (iflag || Iflag || vflag) {
+ if ((sts = pmLookupDesc(pmidlist[i], &desc)) < 0) {
+ printf("%d %s (pmLookupDesc)\n", sts, pmErrStr(sts));
+ continue;
+ }
+ }
+
+ if (fflag && (iflag || Iflag)) {
+ /*
+ * must be -i or -I with -f ... don't even fetch a result
+ * with pmFetch, just go straight to the instance domain with
+ * pmGetInDom or pmGetInDomArchive
+ */
+ if (desc.indom == PM_INDOM_NULL) {
+ printf("1 PM_IN_NULL");
+ if ( iflag && Iflag )
+ printf(" PM_IN_NULL");
+ }
+ else {
+ if ((numinst = lookup(desc.indom)) < 0) {
+ printf("%d %s (pmGetInDom)", numinst, pmErrStr(numinst));
+ }
+ else {
+ int j;
+ printf("%d", numinst);
+ for (j = 0; j < numinst; j++) {
+ if (iflag)
+ printf(" ?%d", instlist[j]);
+ if (Iflag)
+ printf(" \"%s\"", instnamelist[j]);
+ }
+ }
+ }
+ putchar('\n');
+ continue;
+ }
+
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ /*
+ * merics from archives are fetched one at a time, otherwise
+ * get them all at once
+ */
+ if ((sts = pmSetMode(PM_MODE_FORW, &opts.start, 0)) < 0) {
+ printf("%d %s (pmSetMode)\n", sts, pmErrStr(sts));
+ continue;
+ }
+ fetch_sts = pmFetch(1, &pmidlist[i], &result);
+ }
+ else {
+ if (i == 0)
+ fetch_sts = pmFetch(numpmid, pmidlist, &result);
+ }
+
+ if (fetch_sts < 0) {
+ printf("%d %s (pmFetch)", fetch_sts, pmErrStr(fetch_sts));
+ }
+ else {
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ vsp = result->vset[0];
+ else
+ vsp = result->vset[i];
+
+ if (vsp->numval < 0) {
+ printf("%d %s", vsp->numval, pmErrStr(vsp->numval));
+ }
+ else if (vsp->numval == 0) {
+ printf("0");
+ ;
+ }
+ else if (vsp->numval > 0) {
+ printf("%d", vsp->numval);
+ if (vflag) {
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ putchar(' ');
+ pmPrintValue(stdout, vsp->valfmt, desc.type, vp, 1);
+ }
+ }
+ if (iflag || Iflag) {
+ /*
+ * must be without -f
+ * get instance domain for reporting to minimize PDU
+ * round trips ... state should be the same as of the
+ * pmResult, so each instance in the pmResult should be
+ * found by pmGetInDom or pmGetInDomArchive
+ */
+ if (desc.indom == PM_INDOM_NULL) {
+ printf(" PM_IN_NULL");
+ if ( iflag && Iflag )
+ printf(" PM_IN_NULL");
+ putchar ('\n');
+ continue;
+ }
+ if ((numinst = lookup(desc.indom)) < 0) {
+ printf("%d %s (pmGetInDom)\n", numinst, pmErrStr(numinst));
+ continue;
+ }
+ for (j = 0; j < vsp->numval; j++) {
+ pmValue *vp = &vsp->vlist[j];
+ if (iflag)
+ printf(" ?%d", vp->inst);
+ if (Iflag) {
+ int k;
+ for (k = 0; k < numinst; k++) {
+ if (instlist[k] == vp->inst)
+ break;
+ }
+ if (k < numinst)
+ printf(" \"%s\"", instnamelist[k]);
+ else
+ printf(" ?%d", vp->inst);
+ }
+ }
+ }
+ }
+ }
+ putchar('\n');
+ }
+
+ if (Vflag) {
+ printf("PDUs send");
+ for (i = j = 0; i < PDU_MAX; i++) {
+ printf(" %3d", __pmPDUCntOut[i]);
+ j += __pmPDUCntOut[i];
+ }
+ printf("\nTotal: %d\n", j);
+
+ printf("PDUs recv");
+ for (i = j = 0; i < PDU_MAX; i++) {
+ printf(" %3d",__pmPDUCntIn[i]);
+ j += __pmPDUCntIn[i];
+ }
+ printf("\nTotal: %d\n", j);
+ }
+
+ exit(0);
+}
diff --git a/src/pmproxy/GNUmakefile b/src/pmproxy/GNUmakefile
new file mode 100644
index 0000000..a519c75
--- /dev/null
+++ b/src/pmproxy/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2000-2002 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmproxy$(EXECSUFFIX)
+HFILES = pmproxy.h
+CFILES = pmproxy.c client.c util.c
+
+LLDLIBS = $(PCPLIB)
+LDIRT = pmproxy.log pmproxy.service
+
+LCFLAGS += $(PIECFLAGS)
+LLDFLAGS += $(PIELDFLAGS)
+
+default: $(CMDTARGET) pmproxy.service
+
+pmproxy.service: pmproxy.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMPROXYOPTIONS_PATH)`
+ $(INSTALL) -m 644 pmproxy.options $(PCP_PMPROXYOPTIONS_PATH)
+ $(INSTALL) -m 755 rc_pmproxy $(PCP_RC_DIR)/pmproxy
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmproxy.service $(PCP_SYSTEMDUNIT_DIR)/pmproxy.service
+endif
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_USER) -d $(PCP_LOG_DIR)/pmproxy
+
+default_pcp : default
+
+install_pcp : install
+
+pmproxy.o client.o: pmproxy.h
diff --git a/src/pmproxy/client.c b/src/pmproxy/client.c
new file mode 100644
index 0000000..f45534b
--- /dev/null
+++ b/src/pmproxy/client.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmproxy.h"
+
+#define MIN_CLIENTS_ALLOC 8
+
+ClientInfo *client;
+int nClients; /* Number in array, (not all in use) */
+int maxReqPortFd; /* highest request port fd */
+int maxSockFd; /* largest fd for a client */
+__pmFdSet sockFds; /* for client select() */
+
+static int
+NewClient(void)
+{
+ int i;
+ static int clientSize;
+
+ for (i = 0; i < nClients; i++)
+ if (!client[i].status.connected)
+ break;
+
+ if (i == clientSize) {
+ int j, sz;
+
+ clientSize = clientSize ? clientSize * 2 : MIN_CLIENTS_ALLOC;
+ sz = sizeof(ClientInfo) * clientSize;
+ client = (ClientInfo *) realloc(client, sz);
+ if (client == NULL) {
+ __pmNoMem("NewClient", sz, PM_RECOV_ERR);
+ Shutdown();
+ exit(1);
+ }
+ for (j = i; j < clientSize; j++) {
+ client[j].addr = NULL;
+ }
+ }
+ client[i].addr = __pmSockAddrAlloc();
+ if (client[i].addr == NULL) {
+ __pmNoMem("NewClient", __pmSockAddrSize(), PM_RECOV_ERR);
+ Shutdown();
+ exit(1);
+ }
+ if (i >= nClients)
+ nClients = i + 1;
+ return i;
+}
+
+/* MY_BUFLEN needs to big enough to hold "hostname port" */
+#define MY_BUFLEN (MAXHOSTNAMELEN+10)
+#define MY_VERSION "pmproxy-server 1\n"
+
+/* Establish a new socket connection to a client */
+ClientInfo *
+AcceptNewClient(int reqfd)
+{
+ int i;
+ int fd;
+ __pmSockLen addrlen;
+ int ok = 0;
+ char buf[MY_BUFLEN];
+ char *bp;
+ char *endp;
+ char *abufp;
+
+ i = NewClient();
+ addrlen = __pmSockAddrSize();
+ fd = __pmAccept(reqfd, client[i].addr, &addrlen);
+ if (fd == -1) {
+ __pmNotifyErr(LOG_ERR, "AcceptNewClient(%d) __pmAccept failed: %s",
+ reqfd, netstrerror());
+ Shutdown();
+ exit(1);
+ }
+ __pmSetSocketIPC(fd);
+ if (fd > maxSockFd)
+ maxSockFd = fd;
+ __pmFD_SET(fd, &sockFds);
+
+ client[i].fd = fd;
+ client[i].pmcd_fd = -1;
+ client[i].status.connected = 1;
+ client[i].pmcd_hostname = NULL;
+
+ /*
+ * version negotiation (converse to negotiate_proxy() logic in
+ * libpcp
+ *
+ * __pmRecv client version message
+ * __pmSend my server version message
+ * __pmRecv pmcd hostname and pmcd port
+ */
+ for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) {
+ if (__pmRecv(fd, bp, 1, 0) != 1) {
+ *bp = '\0'; /* null terminate what we have */
+ bp = &buf[MY_BUFLEN]; /* flag error */
+ break;
+ }
+ /* end of line means no more ... */
+ if (*bp == '\n' || *bp == '\r') {
+ *bp = '\0';
+ break;
+ }
+ }
+ if (bp < &buf[MY_BUFLEN]) {
+ /* looks OK so far ... is this a version we can support? */
+ if (strcmp(buf, "pmproxy-client 1") == 0) {
+ client[i].version = 1;
+ ok = 1;
+ }
+ }
+
+ if (!ok) {
+ abufp = __pmSockAddrToString(client[i].addr);
+ __pmNotifyErr(LOG_WARNING, "Bad version string from client at %s", abufp);
+ free(abufp);
+ fprintf(stderr, "AcceptNewClient: bad version string was \"");
+ for (bp = buf; *bp && bp < &buf[MY_BUFLEN]; bp++)
+ fputc(*bp & 0xff, stderr);
+ fprintf(stderr, "\"\n");
+ DeleteClient(&client[i]);
+ return NULL;
+ }
+
+ if (__pmSend(fd, MY_VERSION, strlen(MY_VERSION), 0) != strlen(MY_VERSION)) {
+ abufp = __pmSockAddrToString(client[i].addr);
+ __pmNotifyErr(LOG_WARNING, "AcceptNewClient: failed to send version "
+ "string (%s) to client at %s\n", MY_VERSION, abufp);
+ free(abufp);
+ DeleteClient(&client[i]);
+ return NULL;
+ }
+
+ for (bp = buf; bp < &buf[MY_BUFLEN]; bp++) {
+ if (__pmRecv(fd, bp, 1, 0) != 1) {
+ *bp = '\0'; /* null terminate what we have */
+ bp = &buf[MY_BUFLEN]; /* flag error */
+ break;
+ }
+ /* end of line means no more ... */
+ if (*bp == '\n' || *bp == '\r') {
+ *bp = '\0';
+ break;
+ }
+ }
+ if (bp < &buf[MY_BUFLEN]) {
+ /* looks OK so far ... get hostname and port */
+ for (bp = buf; *bp && *bp != ' '; bp++)
+ ;
+ if (bp != buf) {
+ *bp = '\0';
+ client[i].pmcd_hostname = strdup(buf);
+ if (client[i].pmcd_hostname == NULL)
+ __pmNoMem("PMCD.hostname", strlen(buf), PM_FATAL_ERR);
+ bp++;
+ client[i].pmcd_port = (int)strtoul(bp, &endp, 10);
+ if (*endp != '\0') {
+ abufp = __pmSockAddrToString(client[i].addr);
+ __pmNotifyErr(LOG_WARNING, "AcceptNewClient: bad pmcd port "
+ "\"%s\" from client at %s", bp, abufp);
+ free(abufp);
+ DeleteClient(&client[i]);
+ return NULL;
+ }
+ }
+ /* error, fall through */
+ }
+
+ if (client[i].pmcd_hostname == NULL) {
+ abufp = __pmSockAddrToString(client[i].addr);
+ __pmNotifyErr(LOG_WARNING, "AcceptNewClient: failed to get PMCD "
+ "hostname (%s) from client at %s", buf, abufp);
+ free(abufp);
+ DeleteClient(&client[i]);
+ return NULL;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT) {
+ /*
+ * note error message gets appended to once pmcd connection is
+ * made in ClientLoop()
+ */
+ abufp = __pmSockAddrToString(client[i].addr);
+ fprintf(stderr, "AcceptNewClient [%d] fd=%d from %s to %s (port %s)",
+ i, fd, abufp, client[i].pmcd_hostname, bp);
+ free(abufp);
+ }
+#endif
+
+ return &client[i];
+}
+
+void
+DeleteClient(ClientInfo *cp)
+{
+ int i;
+
+ for (i = 0; i < nClients; i++)
+ if (cp == &client[i])
+ break;
+
+ if (i == nClients) {
+ fprintf(stderr, "DeleteClient: Botch: tried to delete non-existent client @" PRINTF_P_PFX "%p\n", cp);
+ return;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ fprintf(stderr, "DeleteClient [%d]\n", i);
+#endif
+
+ if (cp->fd >= 0) {
+ __pmFD_CLR(cp->fd, &sockFds);
+ __pmCloseSocket(cp->fd);
+ }
+ if (cp->pmcd_fd >= 0) {
+ __pmFD_CLR(cp->pmcd_fd, &sockFds);
+ __pmCloseSocket(cp->pmcd_fd);
+ }
+ if (i == nClients-1) {
+ i--;
+ while (i >= 0 && !client[i].status.connected)
+ i--;
+ nClients = (i >= 0) ? i + 1 : 0;
+ }
+ if (cp->fd == maxSockFd || cp->pmcd_fd == maxSockFd) {
+ maxSockFd = maxReqPortFd;
+ for (i = 0; i < nClients; i++) {
+ if (cp->status.connected == 0)
+ continue;
+ if (client[i].fd > maxSockFd)
+ maxSockFd = client[i].fd;
+ if (client[i].pmcd_fd > maxSockFd)
+ maxSockFd = client[i].pmcd_fd;
+ }
+ }
+ __pmSockAddrFree(cp->addr);
+ cp->addr = NULL;
+ cp->status.connected = 0;
+ cp->fd = -1;
+ cp->pmcd_fd = -1;
+ if (cp->pmcd_hostname != NULL) {
+ free(cp->pmcd_hostname);
+ cp->pmcd_hostname = NULL;
+ }
+}
diff --git a/src/pmproxy/pmproxy.c b/src/pmproxy/pmproxy.c
new file mode 100644
index 0000000..b359199
--- /dev/null
+++ b/src/pmproxy/pmproxy.c
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2012-2014 Red Hat.
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmproxy.h"
+#include <sys/stat.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#define MAXPENDING 5 /* maximum number of pending connections */
+#define FDNAMELEN 40 /* maximum length of a fd description */
+#define STRINGIFY(s) #s
+#define TO_STRING(s) STRINGIFY(s)
+
+static char *FdToString(int);
+
+static int timeToDie; /* For SIGINT handling */
+static char *logfile = "pmproxy.log"; /* log file name */
+static int run_daemon = 1; /* run as a daemon, see -f */
+static char *fatalfile = "/dev/tty";/* fatal messages at startup go here */
+static char *username;
+static char *certdb; /* certificate DB path (NSS) */
+static char *dbpassfile; /* certificate DB password file */
+static char *hostname;
+
+static void
+DontStart(void)
+{
+ FILE *tty;
+ FILE *log;
+
+ __pmNotifyErr(LOG_ERR, "pmproxy not started due to errors!\n");
+
+ if ((tty = fopen(fatalfile, "w")) != NULL) {
+ fflush(stderr);
+ fprintf(tty, "NOTE: pmproxy not started due to errors! ");
+ if ((log = fopen(logfile, "r")) != NULL) {
+ int c;
+ fprintf(tty, "Log file \"%s\" contains ...\n", logfile);
+ while ((c = fgetc(log)) != EOF)
+ fputc(c, tty);
+ fclose(log);
+ }
+ else
+ fprintf(tty, "Log file \"%s\" has vanished!\n", logfile);
+ fclose(tty);
+ }
+ exit(1);
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Service options"),
+ { "foreground", 0, 'f', 0, "run in the foreground" },
+ { "username", 1, 'U', "USER", "in daemon mode, run as named user [default pcp]" },
+ PMAPI_OPTIONS_HEADER("Configuration options"),
+ { "certdb", 1, 'C', "PATH", "path to NSS certificate database" },
+ { "passfile", 1, 'P', "PATH", "password file for certificate database access" },
+ { "", 1, 'L', "BYTES", "maximum size for PDUs from clients [default 65536]" },
+ PMAPI_OPTIONS_HEADER("Connection options"),
+ { "interface", 1, 'i', "ADDR", "accept connections on this IP address" },
+ { "port", 1, 'p', "N", "accept connections on this port" },
+ PMAPI_OPTIONS_HEADER("Diagnostic options"),
+ { "log", 1, 'l', "PATH", "redirect diagnostics and trace output" },
+ { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "C:D:fi:l:L:p:P:U:x:?",
+ .long_options = longopts,
+};
+
+static void
+ParseOptions(int argc, char *argv[], int *nports)
+{
+ int c;
+ int sts;
+ int usage = 0;
+
+ while ((c = pmgetopt_r(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'C': /* path to NSS certificate database */
+ certdb = opts.optarg;
+ break;
+
+ case 'D': /* debug flag */
+ if ((sts = __pmParseDebug(opts.optarg)) < 0) {
+ pmprintf("%s: unrecognized debug flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ pmDebug |= sts;
+ }
+ break;
+
+ case 'f': /* foreground, i.e. do _not_ run as a daemon */
+ run_daemon = 0;
+ break;
+
+ case 'i':
+ /* one (of possibly several) interfaces for client requests */
+ __pmServerAddInterface(opts.optarg);
+ break;
+
+ case 'l':
+ /* log file name */
+ logfile = opts.optarg;
+ break;
+
+ case 'L': /* Maximum size for PDUs from clients */
+ sts = (int)strtol(opts.optarg, NULL, 0);
+ if (sts <= 0) {
+ pmprintf("%s: -L requires a positive value\n", pmProgname);
+ opts.errors++;
+ } else {
+ __pmSetPDUCeiling(sts);
+ }
+ break;
+
+ case 'p':
+ if (__pmServerAddPorts(opts.optarg) < 0) {
+ pmprintf("%s: -p requires a positive numeric argument (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ *nports += 1;
+ }
+ break;
+
+ case 'P': /* password file for certificate database access */
+ dbpassfile = opts.optarg;
+ break;
+
+ case 'U': /* run as user username */
+ username = opts.optarg;
+ break;
+
+ case 'x':
+ fatalfile = opts.optarg;
+ break;
+
+ case '?':
+ usage = 1;
+ break;
+
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (usage || opts.errors || opts.optind < argc) {
+ pmUsageMessage(&opts);
+ if (usage)
+ exit(0);
+ DontStart();
+ }
+}
+
+static void
+CleanupClient(ClientInfo *cp, int sts)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ int i;
+ for (i = 0; i < nClients; i++) {
+ if (cp == &client[i])
+ break;
+ }
+ fprintf(stderr, "CleanupClient: client[%d] fd=%d %s (%d)\n",
+ i, cp->fd, pmErrStr(sts), sts);
+ }
+#endif
+
+ DeleteClient(cp);
+}
+
+static int
+VerifyClient(ClientInfo *cp, __pmPDU *pb)
+{
+ int i, sts, flags = 0, sender = 0, credcount = 0;
+ __pmPDUHdr *header = (__pmPDUHdr *)pb;
+ __pmHashCtl attrs = { 0 }; /* TODO */
+ __pmCred *credlist;
+
+ /* first check that this is a credentials PDU */
+ if (header->type != PDU_CREDS)
+ return PM_ERR_IPC;
+
+ /* now decode it and if secure connection requested, set it up */
+ if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0)
+ return sts;
+
+ for (i = 0; i < credcount; i++) {
+ if (credlist[i].c_type == CVERSION) {
+ __pmVersionCred *vcp = (__pmVersionCred *)&credlist[i];
+ flags = vcp->c_flags;
+ break;
+ }
+ }
+ if (credlist != NULL)
+ free(credlist);
+
+ /* need to ensure both the pmcd and client channel use flags */
+
+ if (sts >= 0 && flags)
+ sts = __pmSecureServerHandshake(cp->fd, flags, &attrs);
+
+ /* send credentials PDU through to pmcd now (order maintained) */
+ if (sts >= 0)
+ sts = __pmXmitPDU(cp->pmcd_fd, pb);
+
+ /*
+ * finally perform any additional handshaking needed with pmcd.
+ * Do not initialize NSS again.
+ */
+ if (sts >= 0 && flags)
+ sts = __pmSecureClientHandshake(cp->pmcd_fd,
+ flags | PDU_FLAG_NO_NSS_INIT,
+ hostname, &attrs);
+
+ return sts;
+}
+
+/* Determine which clients (if any) have sent data to the server and handle it
+ * as required.
+ */
+void
+HandleInput(__pmFdSet *fdsPtr)
+{
+ int i, sts;
+ __pmPDU *pb;
+ ClientInfo *cp;
+
+ /* input from clients */
+ for (i = 0; i < nClients; i++) {
+ if (!client[i].status.connected || !__pmFD_ISSET(client[i].fd, fdsPtr))
+ continue;
+
+ cp = &client[i];
+
+ sts = __pmGetPDU(cp->fd, LIMIT_SIZE, 0, &pb);
+ if (sts <= 0) {
+ CleanupClient(cp, sts);
+ continue;
+ }
+
+ /* We *must* see a credentials PDU as the first PDU */
+ if (!cp->status.allowed) {
+ sts = VerifyClient(cp, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts < 0) {
+ CleanupClient(cp, sts);
+ continue;
+ }
+ cp->status.allowed = 1;
+ continue;
+ }
+
+ sts = __pmXmitPDU(cp->pmcd_fd, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts <= 0) {
+ CleanupClient(cp, sts);
+ continue;
+ }
+ }
+
+ /* input from pmcds */
+ for (i = 0; i < nClients; i++) {
+ if (!client[i].status.connected ||
+ !__pmFD_ISSET(client[i].pmcd_fd, fdsPtr))
+ continue;
+
+ cp = &client[i];
+
+ sts = __pmGetPDU(cp->pmcd_fd, ANY_SIZE, 0, &pb);
+ if (sts <= 0) {
+ CleanupClient(cp, sts);
+ continue;
+ }
+
+ sts = __pmXmitPDU(cp->fd, pb);
+ __pmUnpinPDUBuf(pb);
+ if (sts <= 0) {
+ CleanupClient(cp, sts);
+ continue;
+ }
+ }
+}
+
+/* Called to shutdown pmproxy in an orderly manner */
+void
+Shutdown(void)
+{
+ int i;
+
+ for (i = 0; i < nClients; i++)
+ if (client[i].status.connected)
+ __pmCloseSocket(client[i].fd);
+ __pmServerCloseRequestPorts();
+ __pmSecureServerShutdown();
+ __pmNotifyErr(LOG_INFO, "pmproxy Shutdown\n");
+ fflush(stderr);
+}
+
+void
+SignalShutdown(void)
+{
+ __pmNotifyErr(LOG_INFO, "pmproxy caught SIGINT or SIGTERM\n");
+ Shutdown();
+ exit(0);
+}
+
+static void
+CheckNewClient(__pmFdSet * fdset, int rfd, int family)
+{
+ ClientInfo *cp;
+
+ if (__pmFD_ISSET(rfd, fdset)) {
+ if ((cp = AcceptNewClient(rfd)) == NULL)
+ /* failed to negotiate, already cleaned up */
+ return;
+
+ /* establish a new connection to pmcd */
+ if ((cp->pmcd_fd = __pmAuxConnectPMCDPort(cp->pmcd_hostname, cp->pmcd_port)) < 0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ /* append to message started in AcceptNewClient() */
+ fprintf(stderr, " oops!\n"
+ "__pmAuxConnectPMCDPort(%s,%d) failed: %s\n",
+ cp->pmcd_hostname, cp->pmcd_port,
+ pmErrStr(-oserror()));
+#endif
+ CleanupClient(cp, -oserror());
+ }
+ else {
+ if (cp->pmcd_fd > maxSockFd)
+ maxSockFd = cp->pmcd_fd;
+ __pmFD_SET(cp->pmcd_fd, &sockFds);
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_CONTEXT)
+ /* append to message started in AcceptNewClient() */
+ fprintf(stderr, " fd=%d\n", cp->pmcd_fd);
+#endif
+ }
+ }
+}
+
+/* Loop, synchronously processing requests from clients. */
+static void
+ClientLoop(void)
+{
+ int i, sts;
+ int maxFd;
+ __pmFdSet readableFds;
+
+ for (;;) {
+ /* Figure out which file descriptors to wait for input on. Keep
+ * track of the highest numbered descriptor for the select call.
+ */
+ readableFds = sockFds;
+ maxFd = maxSockFd + 1;
+
+ sts = __pmSelectRead(maxFd, &readableFds, NULL);
+
+ if (sts > 0) {
+ if (pmDebug & DBG_TRACE_APPL0)
+ for (i = 0; i <= maxSockFd; i++)
+ if (__pmFD_ISSET(i, &readableFds))
+ fprintf(stderr, "__pmSelectRead(): from %s fd=%d\n",
+ FdToString(i), i);
+ __pmServerAddNewClients(&readableFds, CheckNewClient);
+ HandleInput(&readableFds);
+ }
+ else if (sts == -1 && neterror() != EINTR) {
+ __pmNotifyErr(LOG_ERR, "ClientLoop select: %s\n", netstrerror());
+ break;
+ }
+ if (timeToDie) {
+ SignalShutdown();
+ break;
+ }
+ }
+}
+
+#ifdef IS_MINGW
+static void
+SigIntProc(int s)
+{
+ SignalShutdown();
+}
+#else
+static void
+SigIntProc(int s)
+{
+ signal(SIGINT, SigIntProc);
+ signal(SIGTERM, SigIntProc);
+ timeToDie = 1;
+}
+#endif
+
+static void
+SigBad(int sig)
+{
+ if (pmDebug & DBG_TRACE_DESPERATE) {
+ __pmNotifyErr(LOG_ERR, "Unexpected signal %d ...\n", sig);
+ fprintf(stderr, "\nDumping to core ...\n");
+ fflush(stderr);
+ }
+ _exit(sig);
+}
+
+/*
+ * Hostname extracted and cached for later use during protocol negotiations
+ */
+static void
+GetProxyHostname(void)
+{
+ __pmHostEnt *hep;
+ char host[MAXHOSTNAMELEN];
+
+ if (gethostname(host, MAXHOSTNAMELEN) < 0) {
+ __pmNotifyErr(LOG_ERR, "%s: gethostname failure\n", pmProgname);
+ DontStart();
+ }
+ host[MAXHOSTNAMELEN-1] = '\0';
+
+ hep = __pmGetAddrInfo(host);
+ if (hep == NULL) {
+ __pmNotifyErr(LOG_ERR, "%s: __pmGetAddrInfo failure\n", pmProgname);
+ DontStart();
+ } else {
+ hostname = __pmHostEntGetName(hep);
+ if (!hostname) { /* no reverse DNS lookup for local hostname */
+ hostname = strdup(host);
+ if (!hostname) /* out of memory, we're having a bad day!?! */
+ __pmNoMem("PMPROXY.hostname", strlen(host), PM_FATAL_ERR);
+ }
+ __pmHostEntFree(hep);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int sts;
+ int nport = 0;
+ char *envstr;
+
+ umask(022);
+ __pmGetUsername(&username);
+ __pmSetInternalState(PM_STATE_PMCS);
+
+ if ((envstr = getenv("PMPROXY_PORT")) != NULL)
+ nport = __pmServerAddPorts(envstr);
+ ParseOptions(argc, argv, &nport);
+ if (nport == 0)
+ __pmServerAddPorts(TO_STRING(PROXY_PORT));
+ GetProxyHostname();
+
+ if (run_daemon) {
+ fflush(stderr);
+ StartDaemon(argc, argv);
+ __pmServerCreatePIDFile(PM_SERVER_PROXY_SPEC, 0);
+ }
+
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ __pmSetSignalHandler(SIGINT, SigIntProc);
+ __pmSetSignalHandler(SIGTERM, SigIntProc);
+ __pmSetSignalHandler(SIGBUS, SigBad);
+ __pmSetSignalHandler(SIGSEGV, SigBad);
+
+ /* Open request ports for client connections */
+ if ((sts = __pmServerOpenRequestPorts(&sockFds, MAXPENDING)) < 0)
+ DontStart();
+ maxReqPortFd = maxSockFd = sts;
+
+ __pmOpenLog(pmProgname, logfile, stderr, &sts);
+ /* close old stdout, and force stdout into same stream as stderr */
+ fflush(stdout);
+ close(fileno(stdout));
+ if (dup(fileno(stderr)) == -1) {
+ fprintf(stderr, "Warning: dup() failed: %s\n", pmErrStr(-oserror()));
+ }
+
+ fprintf(stderr, "pmproxy: PID = %" FMT_PID, getpid());
+ fprintf(stderr, ", PDU version = %u\n", PDU_VERSION);
+ __pmServerDumpRequestPorts(stderr);
+ fflush(stderr);
+
+ /* lose root privileges if we have them */
+ __pmSetProcessIdentity(username);
+
+ if (__pmSecureServerSetup(certdb, dbpassfile) < 0)
+ DontStart();
+
+ /* all the work is done here */
+ ClientLoop();
+
+ Shutdown();
+ exit(0);
+}
+
+/* Convert a file descriptor to a string describing what it is for. */
+static char *
+FdToString(int fd)
+{
+ static char fdStr[FDNAMELEN];
+ static char *stdFds[4] = {"*UNKNOWN FD*", "stdin", "stdout", "stderr"};
+ int i;
+
+ if (fd >= -1 && fd < 3)
+ return stdFds[fd + 1];
+ if (__pmServerRequestPortString(fd, fdStr, FDNAMELEN) != NULL)
+ return fdStr;
+ for (i = 0; i < nClients; i++) {
+ if (client[i].status.connected && fd == client[i].fd) {
+ sprintf(fdStr, "client[%d] client socket", i);
+ return fdStr;
+ }
+ if (client[i].status.connected && fd == client[i].pmcd_fd) {
+ sprintf(fdStr, "client[%d] pmcd socket", i);
+ return fdStr;
+ }
+ }
+ return stdFds[0];
+}
diff --git a/src/pmproxy/pmproxy.h b/src/pmproxy/pmproxy.h
new file mode 100644
index 0000000..df6005c
--- /dev/null
+++ b/src/pmproxy/pmproxy.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012-2013 Red Hat.
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PROXY_H
+#define _PROXY_H
+
+#include "pmapi.h"
+#include "impl.h"
+
+/* The table of clients, used by pmproxy */
+typedef struct {
+ int fd; /* client socket descriptor */
+ int version; /* proxy-client protocol version */
+ struct { /* Status of connection to client */
+ unsigned int connected : 1; /* Client connected, socket level */
+ unsigned int allowed : 1; /* Creds seen, OK to talk to pmcd */
+ } status;
+ char *pmcd_hostname; /* PMCD hostname */
+ int pmcd_port; /* PMCD port */
+ int pmcd_fd; /* PMCD socket file descriptor */
+ __pmSockAddr *addr; /* address of client */
+} ClientInfo;
+
+extern ClientInfo *client; /* Array of clients */
+extern int nClients; /* Number of entries in array */
+extern int maxReqPortFd; /* highest request port fd */
+extern int maxSockFd; /* largest fd for a clients
+ * and pmcd connections */
+extern __pmFdSet sockFds; /* for select() */
+
+/* prototypes */
+extern ClientInfo *AcceptNewClient(int);
+extern void DeleteClient(ClientInfo *);
+extern void StartDaemon(int, char **);
+extern void Shutdown(void);
+
+#endif /* _PROXY_H */
diff --git a/src/pmproxy/pmproxy.options b/src/pmproxy/pmproxy.options
new file mode 100644
index 0000000..bd83e0c
--- /dev/null
+++ b/src/pmproxy/pmproxy.options
@@ -0,0 +1,31 @@
+# command-line options and environment variables for pmproxy
+# uncomment/edit lines as required
+
+# run in the foreground
+# -f
+
+# restrict PCP monitoring client connections to these interfaces
+# -i gateway-public-0.foo.bar.com
+# -i gateway-public-1.foo.bar.com
+
+# make log go someplace else
+# -l /some/place/else
+
+# maximum incoming PDU size (default 64KB)
+# -L 16384
+
+# assume identity of some user other than "pcp"
+# -U nobody
+
+# emergency messages before logfile created
+# -x /tmp/desperate.log
+
+# setting of environment variables for pmproxy
+
+# port for incomining connections from PCP monitoring clients
+# PMPROXY_PORT=44322
+
+# timeouts for interactions with pmcd on behalf of clients
+# PMCD_CONNECT_TIMEOUT=10
+# PMCD_RECONNECT_TIMEOUT=10,20,30
+# PMCD_REQUEST_TIMEOUT=10
diff --git a/src/pmproxy/pmproxy.service.in b/src/pmproxy/pmproxy.service.in
new file mode 100644
index 0000000..078f126
--- /dev/null
+++ b/src/pmproxy/pmproxy.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=Proxy for Performance Metrics Collector Daemon
+Documentation=man:pmproxy(8)
+Wants=avahi-daemon.service
+After=network.target avahi-daemon.service
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmproxy start
+ExecStop=@path@/pmproxy stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmproxy/rc_pmproxy b/src/pmproxy/rc_pmproxy
new file mode 100644
index 0000000..5886219
--- /dev/null
+++ b/src/pmproxy/rc_pmproxy
@@ -0,0 +1,301 @@
+#! /bin/sh
+#
+# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Start or Stop the Performance Co-Pilot (PCP) proxy daemon for pmcd
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 95 05
+# description: pmproxy is the pmcd proxy daemon for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmproxy
+# Required-Start: $remote_fs
+# Should-Start: $local_fs $network $syslog $time $pmcd
+# Required-Stop: $remote_fs
+# Should-Stop: $local_fs $network $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmproxy (the pmcd proxy daemon for PCP)
+# Description: Configure and control pmproxy (the pmcd proxy daemon for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMPROXY=$PCP_BINADM_DIR/pmproxy
+PMPROXYOPTS=$PCP_PMPROXYOPTIONS_PATH
+RUNDIR=$PCP_LOG_DIR/pmproxy
+pmprog=$PCP_RC_DIR/pmproxy
+prog=$PCP_RC_DIR/`basename $0`
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if [ $pmprog = $prog ]
+then
+ VERBOSE_CTL=on
+else
+ VERBOSE_CTL=off
+fi
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ test -f /usr/xpg4/bin/id && ID=/usr/xpg4/bin/id
+
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+_shutdown()
+{
+ # Is pmproxy running?
+ #
+ _get_pids_by_name pmproxy >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$pmprog: pmproxy not running"
+ return 0
+ fi
+
+ # Send pmproxy a SIGTERM, which is noted as a pending shutdown.
+ # When finished the currently active request, pmproxy will close any
+ # connections and then exit.
+ # Wait for pmproxy to terminate.
+ #
+ pmsignal -a -s TERM pmproxy > /dev/null 2>&1
+ $ECHO $PCP_ECHO_N "Waiting for pmproxy to terminate ...""$PCP_ECHO_C"
+ gone=0
+ for i in 1 2 3 4 5 6
+ do
+ sleep 3
+ _get_pids_by_name pmproxy >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ gone=1
+ break
+ fi
+
+ # If pmproxy doesn't go in 15 seconds, SIGKILL and sleep 1 more time
+ # to allow any clients reading from pmproxy sockets to fail so that
+ # socket doesn't end up in TIME_WAIT or somesuch.
+ #
+ if [ $i = 5 ]
+ then
+ $ECHO
+ echo "Process ..."
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps
+ sed 1q $tmp/ps
+ for pid in `cat $tmp/tmp`
+ do
+ $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }"
+ done
+ echo "$prog: Warning: Forcing pmproxy to terminate!"
+ pmsignal -a -s KILL pmproxy > /dev/null 2>&1
+ else
+ $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C"
+ fi
+ done
+ if [ $gone != 1 ] # It just WON'T DIE, give up.
+ then
+ echo "Process ..."
+ cat $tmp/tmp
+ echo "$prog: Warning: pmproxy won't die!"
+ exit
+ fi
+ $RC_STATUS -v
+ $PCP_BINADM_DIR/pmpost "stop pmproxy from $pmprog"
+}
+
+_usage()
+{
+ echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+else # For a quiet startup and shutdown
+ ECHO=:
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the PCP pmproxy daemon.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmproxy
+ then
+ status=0
+ exit
+ fi
+
+ _shutdown quietly
+
+ # pmproxy messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ if [ -x $PMPROXY ]
+ then
+ if [ ! -f $PCP_PMPROXYCONF_PATH ]
+ then
+ echo "$prog:"'
+Error: pmproxy control file "$PCP_PMPROXYCONF_PATH" is missing, cannot start pmproxy.'
+ exit
+ fi
+ if [ ! -d "$RUNDIR" ]
+ then
+ mkdir -p -m 775 "$RUNDIR"
+ chown $PCP_USER:$PCP_GROUP "$RUNDIR"
+ fi
+ cd $RUNDIR
+
+ # salvage the previous versions of any pmproxy
+ #
+ if [ -f pmproxy.log ]
+ then
+ rm -f pmproxylog.log.prev
+ mv pmproxy.log pmproxy.log.prev
+ fi
+
+ $ECHO $PCP_ECHO_N "Starting pmproxy ..." "$PCP_ECHO_C"
+
+ # options file processing ...
+ # only consider lines which start with a hyphen
+ # get rid of the -f option
+ # ensure multiple lines concat onto 1 line
+ OPTS=`sed <$PMPROXYOPTS 2>/dev/null \
+ -e '/^[^-]/d' \
+ -e 's/^/ /' \
+ -e 's/$/ /' \
+ -e 's/ -f / /g' \
+ -e 's/^ //' \
+ -e 's/ $//' \
+ | tr '\012' ' ' `
+
+ # environment stuff
+ #
+ eval `sed -e 's/"/\\"/g' $PMPROXYOPTS \
+ | awk -F= '
+BEGIN { exports="" }
+/^[A-Z]/ && NF == 2 { exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+ }
+END { if (exports != "") print "export", exports }'`
+
+ $PMPROXY $OPTS
+ $RC_STATUS -v
+
+ $PCP_BINADM_DIR/pmpost "start pmproxy from $pmprog"
+
+ fi
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmproxy:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMPROXY
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMPROXY
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmproxy/util.c b/src/pmproxy/util.c
new file mode 100644
index 0000000..c22ac8a
--- /dev/null
+++ b/src/pmproxy/util.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmproxy.h"
+
+
+#ifdef IS_MINGW
+
+void
+StartDaemon(int argc, char **argv)
+{
+ PROCESS_INFORMATION piProcInfo;
+ STARTUPINFO siStartInfo;
+ LPTSTR cmdline = NULL;
+ int i, sz = 3; /* -f\0 */
+
+ for (i = 0; i < argc; i++)
+ sz += strlen(argv[i]) + 1;
+ if ((cmdline = malloc(sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "StartDaemon: no memory");
+ exit(1);
+ }
+ for (sz = i = 0; i < argc; i++)
+ sz += sprintf(cmdline, "%s ", argv[i]);
+ sprintf(cmdline + sz, "-f");
+
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+ siStartInfo.cb = sizeof(STARTUPINFO);
+
+ if (0 == CreateProcess(
+ NULL, cmdline,
+ NULL, NULL, /* process and thread attributes */
+ FALSE, /* inherit handles */
+ CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS,
+ NULL, /* environment (from parent) */
+ NULL, /* current directory */
+ &siStartInfo, /* STARTUPINFO pointer */
+ &piProcInfo)) { /* receives PROCESS_INFORMATION */
+ __pmNotifyErr(LOG_ERR, "StartDaemon: CreateProcess");
+ /* but keep going */
+ }
+ else {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+}
+
+#else
+
+/* Based on Stevens (Unix Network Programming, p.83) */
+void
+StartDaemon(int argc, char **argv)
+{
+ int childpid;
+
+ (void)argc; (void)argv;
+
+#if defined(HAVE_TERMIO_SIGNALS)
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+#endif
+
+ if ((childpid = fork()) < 0)
+ __pmNotifyErr(LOG_ERR, "StartDaemon: fork");
+ /* but keep going */
+ else if (childpid > 0) {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+
+ /* not a process group leader, lose controlling tty */
+ if (setsid() == -1)
+ __pmNotifyErr(LOG_WARNING, "StartDaemon: setsid");
+ /* but keep going */
+
+ close(0);
+ /* don't close other fd's -- we know that only good ones are open! */
+
+ /* don't chdir("/") -- we still need to open pmcd.log */
+}
+#endif
diff --git a/src/pmquery/GNUmakefile b/src/pmquery/GNUmakefile
new file mode 100644
index 0000000..c7cc628
--- /dev/null
+++ b/src/pmquery/GNUmakefile
@@ -0,0 +1,76 @@
+TOPDIR = ../..
+COMMAND = pmquery
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+WRAPPER = $(COMMAND).sh
+QRCFILE = $(COMMAND).qrc
+ICNFILE = $(COMMAND).icns
+XMLFILE = $(COMMAND).info
+HEADERS = pmquery.h
+SOURCES = $(HEADERS:.h=.cpp) main.cpp
+SCRIPTS = pmconfirm.sh pmmessage.sh
+LSRCFILES = $(PROJECT) $(QRCFILE) $(SCRIPTS) $(HEADERS) $(SOURCES) \
+ $(WRAPPER).in pmconfirm.sh.in pmmessage.sh.in $(XMLFILE).in
+LDIRT = $(COMMAND) $(ICNFILE) $(WRAPPER) $(SCRIPTS) $(XMLFILE) images
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me:: images wrappers
+ $(QTMAKE)
+ $(LNMAKE)
+
+ifeq ($(WINDOW),mac)
+PKG_MAC_DIR = /Library/PCP/$(COMMAND).app/Contents
+PKG_SUB_DIR = $(PKG_MAC_DIR)/MacOS
+wrappers: $(WRAPPER) $(SCRIPTS) $(XMLFILE)
+else
+PKG_SUB_DIR = $(PCP_BIN_DIR)
+wrappers: $(SCRIPTS)
+endif
+
+$(WRAPPER): $(WRAPPER).in
+ @ $(SED) -e '/\# .*/b' -e 's;PCP_BIN_DIR;$(PKG_SUB_DIR);g' < $< > $@
+pmmessage.sh: pmmessage.sh.in
+ @ $(SED) -e '/\# .*/b' -e 's;PCP_BIN_DIR;$(PKG_SUB_DIR);g' < $< > $@
+pmconfirm.sh: pmconfirm.sh.in
+ @ $(SED) -e '/\# .*/b' -e 's;PCP_BIN_DIR;$(PKG_SUB_DIR);g' < $< > $@
+$(XMLFILE): $(XMLFILE).in
+ $(SED) -e 's;PACKAGE_VERSION;$(PACKAGE_VERSION);g' < $< > $@
+
+install: default
+ifneq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+endif
+ $(INSTALL) -m 755 pmconfirm.sh $(PCP_BIN_DIR)/pmconfirm
+ $(INSTALL) -m 755 pmmessage.sh $(PCP_BIN_DIR)/pmmessage
+ifeq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(WRAPPER) $(PCP_BIN_DIR)/$(COMMAND)
+ $(call INSTALL_DIRECTORY_HIERARCHY,$(PKG_MAC_DIR),/Library)
+ $(INSTALL) -m 644 $(XMLFILE) $(PKG_MAC_DIR)/Info.plist
+ $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS
+ $(call INSTALL_QT_FRAMEWORKS,$(BINARY))
+ $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND)
+ rm $(BINARY)
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources
+ $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE)
+ $(call INSTALL_QT_RESOURCES,$(PKG_MAC_DIR)/Resources)
+endif
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+images: $(ICNFILE)
+ $(LN_S) $(TOPDIR)/images images
+
+$(ICNFILE):
+ $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE)
diff --git a/src/pmquery/main.cpp b/src/pmquery/main.cpp
new file mode 100644
index 0000000..303761f
--- /dev/null
+++ b/src/pmquery/main.cpp
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QFile>
+#include <QtCore/QTextStream>
+#include <QtGui/QDesktopWidget>
+#include <QtGui/QCursor>
+#include "pmquery.h"
+
+static char usage[] =
+ "Usage: pmquery [options] [message...]\n\n"
+ "Options:\n"
+ " -c center the window on the display\n"
+ " -center display in the center (alias for -c)\n"
+ " -nearmouse pop up window near the mouse cursor\n"
+ " -b button create a button with the label button\n"
+ " -B button create the default button with the label button\n"
+ " -default button sets named button as the default button\n"
+ " -buttons string comma-separated list of label:exitcode\n"
+ " -h | -? | -help display this usage message\n"
+ " -t string add string to the message displayed\n"
+ " -file filename read message from file, \"-\" for stdin\n"
+ " -icon icontype dialog type: info, error, question, warning, critical,\n"
+ " host, or archive\n"
+ " -header title set window title\n"
+ " -useslider always display a text box slider\n"
+ " -noslider do not display a text box slider\n"
+ " -noframe do not display a frame around the text box\n"
+ " -print print the button label when selected\n"
+ " -noprint do not print the button label when selected\n"
+ " -timeout secs exit with status 0 after \"secs\" seconds\n"
+ " -exclusive do not allow mouse/button presses until dismissed\n";
+
+char *getoption(int argc, char **argv)
+{
+ static int index;
+
+ if (index >= argc)
+ return NULL;
+ return argv[++index];
+}
+
+char *catoption(char *prefix, char *option, int total)
+{
+ prefix = (char *)realloc(prefix, total);
+ if (prefix) {
+ strncat(prefix, " ", 2);
+ strncat(prefix, option, total);
+ }
+ return prefix;
+}
+
+char *getoptions(int argc, char **argv, char *arg)
+{
+ int length = strlen(arg) + 1;
+ char *string = (char *)malloc(length);
+ strncpy(string, arg, length);
+ while (string && (arg = getoption(argc, argv)) != NULL) {
+ length += 1 + strlen(arg) + 1;
+ string = catoption(string, arg, length);
+ }
+ if (!string) {
+ fputs("Insufficient memory for buffering message\n", stderr);
+ exit(1);
+ }
+ return string;
+}
+
+int main(int argc, char ** argv)
+{
+ char *option;
+ char *filename = NULL;
+ char *defaultname = NULL;
+ int errflag = 0;
+ int printflag = 1;
+ int inputflag = 0;
+ int centerflag = 0;
+ int noframeflag = 0;
+ int nosliderflag = 0;
+ int nearmouseflag = 0;
+ int usesliderflag = 0;
+ int exclusiveflag = 0;
+
+ QApplication a(argc, argv);
+
+ while ((option = getoption(argc, argv)) != NULL) {
+ if (strcmp(option, "-c") == 0 || strcmp(option, "-center") == 0) {
+ centerflag = 1;
+ }
+ else if (strcmp(option, "-nearmouse") == 0) {
+ nearmouseflag = 1;
+ }
+ else if (strcmp(option, "-b") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -b option requires an argument\n");
+ errflag++;
+ }
+ PmQuery::addButton(option, FALSE, 0);
+ }
+ else if (strcmp(option, "-B") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -B option requires an argument\n");
+ errflag++;
+ }
+ PmQuery::addButton(option, TRUE, 0);
+ }
+ else if (strcmp(option, "-default") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -default option requires an argument\n");
+ errflag++;
+ }
+ else defaultname = option;
+ }
+ else if (strcmp(option, "-buttons") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -buttons option requires an argument\n");
+ errflag++;
+ }
+ PmQuery::addButtons(option);
+ }
+ else if (strcmp(option, "-t") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -B option requires an argument\n");
+ errflag++;
+ }
+ else if (filename) {
+ fprintf(stderr, "The -file and -t options are incompatible\n");
+ errflag++;
+ }
+ else PmQuery::addMessage(option);
+ }
+ else if (strcmp(option, "-file") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -file option requires an argument\n");
+ errflag++;
+ }
+ else if (PmQuery::messageCount()) {
+ fprintf(stderr, "The -file and -t options are incompatible\n");
+ errflag++;
+ }
+ else filename = option;
+ }
+ else if (strcmp(option, "-icon") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -icon option requires an argument\n");
+ errflag++;
+ }
+ else if (PmQuery::setIcontype(option) < 0) {
+ fprintf(stderr, "Unknown icon type - %s\n", option);
+ errflag++;
+ }
+ }
+ else if (strcmp(option, "-header") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -header option requires an argument\n");
+ errflag++;
+ }
+ else PmQuery::setTitle(option);
+ }
+ else if (strcmp(option, "-input") == 0) {
+ inputflag = 1;
+ }
+ else if (strcmp(option, "-noframe") == 0) {
+ noframeflag = 1;
+ }
+ else if (strcmp(option, "-useslider") == 0) {
+ if (nosliderflag) {
+ fprintf(stderr,
+ "The -useslider and -noslider options are incompatible\n");
+ errflag++;
+ }
+ else usesliderflag = 1;
+ }
+ else if (strcmp(option, "-noslider") == 0) {
+ if (usesliderflag) {
+ fprintf(stderr,
+ "The -useslider and -noslider options are incompatible\n");
+ errflag++;
+ }
+ else nosliderflag = 1;
+ }
+ else if (strcmp(option, "-timeout") == 0) {
+ if ((option = getoption(argc, argv)) == NULL) {
+ fprintf(stderr, "The -timeout option requires an argument\n");
+ errflag++;
+ }
+ else if (PmQuery::setTimeout(option) < 0) {
+ fprintf(stderr, "'%s' is not a positive non-zero timeout\n",
+ option);
+ errflag++;
+ }
+ }
+ else if (strcmp(option, "-print") == 0) {
+ printflag = 1;
+ }
+ else if (strcmp(option, "-noprint") == 0) {
+ printflag = 0;
+ }
+ else if (strcmp(option, "-exclusive") == 0) {
+ exclusiveflag = 1;
+ }
+ else if (strcmp(option, "-?") == 0 || strcmp(option, "-help") == 0 ||
+ strcmp(option, "-h") == 0 || strcmp(option, "--help") == 0) {
+ errflag++;
+ }
+ else {
+ PmQuery::addMessage(getoptions(argc, argv, option));
+ }
+ }
+
+ if (errflag) {
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ }
+
+ if (defaultname)
+ PmQuery::setDefaultButton(defaultname);
+
+ if (filename) {
+ QTextStream *stream;
+ QFile *file = NULL;
+ QString line;
+
+ if (strcmp(filename, "-") == 0)
+ stream = new QTextStream(stdin, QIODevice::ReadOnly);
+ else {
+ file = new QFile(filename);
+ if (!file->open(QIODevice::ReadOnly)) {
+ fprintf(stderr, "Cannot open %s: %s\n", filename,
+ (char *)file->errorString().toAscii().data());
+ exit(1);
+ }
+ stream = new QTextStream(file);
+ }
+ for (;;) {
+ QString line = stream->readLine();
+ if (line.isNull())
+ break;
+ if ((option = strdup(line.toAscii())) == NULL) {
+ fputs("Insufficient memory reading message stream\n", stderr);
+ exit(1);
+ }
+ PmQuery::addMessage(option);
+ }
+ if (file)
+ delete file;
+ delete stream;
+ }
+
+ if (!PmQuery::buttonCount())
+ PmQuery::addButton("Continue", TRUE, 0);
+
+ PmQuery q(inputflag, printflag, noframeflag,
+ nosliderflag, usesliderflag, exclusiveflag);
+
+ if (nearmouseflag)
+ q.move(QCursor::pos());
+ else if (centerflag) {
+ int x = (a.desktop()->screenGeometry().width() / 2) - (q.width() / 2);
+ int y = (a.desktop()->screenGeometry().height() / 2) - (q.height() / 2);
+ q.move(x > 0 ? x : 0, y > 0 ? y : 0);
+ }
+
+ return q.exec();
+}
diff --git a/src/pmquery/pmconfirm.sh.in b/src/pmquery/pmconfirm.sh.in
new file mode 100755
index 0000000..8fdaf49
--- /dev/null
+++ b/src/pmquery/pmconfirm.sh.in
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PCP_BIN_DIR/pmquery -print "$@"
diff --git a/src/pmquery/pmmessage.sh.in b/src/pmquery/pmmessage.sh.in
new file mode 100755
index 0000000..5b52a73
--- /dev/null
+++ b/src/pmquery/pmmessage.sh.in
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PCP_BIN_DIR/pmquery -noprint "$@"
diff --git a/src/pmquery/pmquery.cpp b/src/pmquery/pmquery.cpp
new file mode 100644
index 0000000..8dcbbda
--- /dev/null
+++ b/src/pmquery/pmquery.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmquery.h"
+
+#define max(a,b) ((a)>(b)?(a):(b))
+#define DEFAULT_EDIT_WIDTH 640 /* in units of pixels */
+#define DEFAULT_EDIT_HEIGHT 320 /* only for usesliderflag */
+
+enum icontypes {
+ INFO_ICON,
+ ERROR_ICON,
+ QUESTION_ICON,
+ WARNING_ICON,
+ ARCHIVE_ICON,
+ HOST_ICON,
+} iconic;
+
+static const char *title = "Query";
+static int timeout;
+static int *statusi;
+static const char **buttons;
+static int buttoncount;
+static const char *defaultbutton;
+static char **messages;
+static int messagecount;
+
+static void nomem()
+{
+ fputs("Insufficient memory\n", stderr);
+ exit(1);
+}
+
+int PmQuery::setTimeout(char *string)
+{
+ char *endnum;
+ timeout = (int)strtol(string, &endnum, 10);
+ if (*endnum != '\0' || timeout <= 0)
+ return -1;
+ return 0;
+}
+
+void PmQuery::setTitle(char *heading)
+{
+ title = heading;
+}
+
+int PmQuery::messageCount()
+{
+ return messagecount;
+}
+
+int PmQuery::buttonCount()
+{
+ return buttoncount;
+}
+
+int PmQuery::setIcontype(char *string)
+{
+ if (strcmp(string, "info") == 0)
+ iconic = INFO_ICON;
+ else if (strcmp(string, "error") == 0 ||
+ strcmp(string, "action") == 0 ||
+ strcmp(string, "critical") == 0)
+ iconic = ERROR_ICON;
+ else if (strcmp(string, "question") == 0)
+ iconic = QUESTION_ICON;
+ else if (strcmp(string, "warning") == 0)
+ iconic = WARNING_ICON;
+ else if (strcmp(string, "archive") == 0)
+ iconic = ARCHIVE_ICON;
+ else if (strcmp(string, "host") == 0)
+ iconic = HOST_ICON;
+ else
+ return -1;
+ return 0;
+}
+
+void PmQuery::addMessage(char *string)
+{
+ messages = (char **)realloc(messages, (messagecount+1) * sizeof(char *));
+ if (!messages)
+ nomem();
+ messages[messagecount++] = string;
+}
+
+void PmQuery::addButton(const char *string, bool iamdefault, int status)
+{
+ buttons = (const char **)realloc(buttons, (buttoncount+1) * sizeof(char *));
+ statusi = (int *)realloc(statusi, (buttoncount+1) * sizeof(int));
+ if (!buttons)
+ nomem();
+ if (iamdefault)
+ defaultbutton = string;
+ statusi[buttoncount] = status;
+ buttons[buttoncount++] = string;
+}
+
+void PmQuery::addButtons(char *string) // comma-separated label:exitcode string
+{
+ char *n;
+ QString pairs(string);
+ static int next = 100;
+
+ QStringList list = pairs.split(",");
+ for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) {
+ QString name = (*it).section(":", 0, 0);
+ QString code = (*it).section(":", 1, 1);
+ if (!name.isEmpty()) {
+ int sts = code.isEmpty() ? ++next : code.toInt();
+ if ((n = strdup(name.toAscii().data())) == NULL)
+ nomem();
+ addButton(n, FALSE, sts);
+ }
+ }
+}
+
+void PmQuery::setDefaultButton(char *string)
+{
+ for (int i = 0; i < buttoncount; i++)
+ if (strcmp(buttons[i], string) == 0)
+ defaultbutton = buttons[i];
+}
+
+void PmQuery::buttonClicked()
+{
+ done(my.status);
+}
+
+void PmQuery::timerEvent(QTimerEvent *)
+{
+ done(1);
+}
+
+// Currently we set default edit size to hardcoded values, until
+// better ways are found to interact with any geometry requests.
+// Note: the +4 pixels for height ensure the auto-scroll does not
+// kick in, seems to be required.
+
+PmQuery::PmQuery(bool inputflag, bool printflag, bool noframeflag,
+ bool nosliderflag, bool usesliderflag, bool exclusiveflag)
+ : QDialog()
+{
+ QHBoxLayout *hboxLayout;
+ QVBoxLayout *vboxLayout;
+ QSpacerItem *spacerItem;
+ QSpacerItem *spacerItem1;
+ QVBoxLayout *vboxLayout1;
+ QHBoxLayout *hboxLayout1;
+ QSpacerItem *spacerItem2;
+
+ QString filename;
+ if (iconic == HOST_ICON)
+ filename = tr(":images/dialog-host.png");
+ else if (iconic == ERROR_ICON)
+ filename = tr(":images/dialog-error.png");
+ else if (iconic == WARNING_ICON)
+ filename = tr(":images/dialog-warning.png");
+ else if (iconic == ARCHIVE_ICON)
+ filename = tr(":images/dialog-archive.png");
+ else if (iconic == QUESTION_ICON)
+ filename = tr(":images/dialog-question.png");
+ else // (iconic == INFO_ICON)
+ filename = tr(":images/dialog-information.png");
+
+ QIcon icon(filename);
+ QPixmap pixmap(filename);
+ setWindowIcon(icon);
+ setWindowTitle(tr(title));
+
+ QGridLayout *gridLayout = new QGridLayout(this);
+ gridLayout->setSpacing(6);
+ gridLayout->setMargin(9);
+ hboxLayout = new QHBoxLayout();
+ hboxLayout->setSpacing(6);
+ hboxLayout->setMargin(0);
+ vboxLayout = new QVBoxLayout();
+ vboxLayout->setSpacing(6);
+ vboxLayout->setMargin(0);
+ spacerItem = new QSpacerItem(20, 2, QSizePolicy::Minimum,
+ QSizePolicy::Expanding);
+
+ vboxLayout->addItem(spacerItem);
+
+ QLabel *imageLabel = new QLabel(this);
+ imageLabel->setPixmap(pixmap);
+
+ vboxLayout->addWidget(imageLabel);
+
+ spacerItem1 = new QSpacerItem(20, 20, QSizePolicy::Minimum,
+ QSizePolicy::Expanding);
+
+ vboxLayout->addItem(spacerItem1);
+ hboxLayout->addLayout(vboxLayout);
+ vboxLayout1 = new QVBoxLayout();
+ vboxLayout1->setSpacing(6);
+ vboxLayout1->setMargin(0);
+
+ int height;
+ int width = DEFAULT_EDIT_WIDTH;
+
+ QLineEdit* lineEdit = NULL;
+ QTextEdit* textEdit = NULL;
+ if (inputflag && messagecount <= 1) {
+ lineEdit = new QLineEdit(this);
+ if (messagecount == 1)
+ lineEdit->setText(tr(messages[0]));
+ height = lineEdit->font().pointSize() + 4;
+ if (height < 0)
+ height = lineEdit->font().pixelSize() + 4;
+ if (height < 0)
+ height = lineEdit->heightForWidth(width) + 4;
+ lineEdit->setSizePolicy(QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed);
+ lineEdit->setMinimumSize(QSize(width, height));
+ lineEdit->setGeometry(QRect(0, 0, width, height));
+ vboxLayout1->addWidget(lineEdit);
+ }
+ else {
+ QFont fixed("monospace");
+ fixed.setStyleHint(QFont::TypeWriter);
+
+ textEdit = new QTextEdit(this);
+ textEdit->setFont(fixed);
+ textEdit->setLineWrapMode(QTextEdit::FixedColumnWidth);
+ textEdit->setLineWrapColumnOrWidth(80);
+ textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ if (nosliderflag)
+ textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ else if (usesliderflag)
+ textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ else
+ textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ for (int m = 0; m < messagecount; m++)
+ textEdit->append(tr(messages[m]));
+ if (inputflag)
+ textEdit->setReadOnly(FALSE);
+ else {
+ textEdit->setLineWidth(1);
+ textEdit->setFrameStyle(noframeflag ? QFrame::NoFrame :
+ QFrame::Box | QFrame::Sunken);
+ textEdit->setReadOnly(TRUE);
+ }
+ if (usesliderflag)
+ height = DEFAULT_EDIT_HEIGHT;
+ else {
+ height = textEdit->font().pointSize() + 4;
+ if (height < 0)
+ height = textEdit->font().pixelSize() + 4;
+ if (height < 0)
+ height = textEdit->heightForWidth(width) + 4;
+ height *= messagecount;
+ }
+ textEdit->setMinimumSize(QSize(width, height));
+ textEdit->setSizePolicy(QSizePolicy::MinimumExpanding,
+ QSizePolicy::MinimumExpanding);
+ textEdit->moveCursor(QTextCursor::Start);
+ textEdit->ensureCursorVisible();
+ vboxLayout1->addWidget(textEdit);
+ }
+
+ hboxLayout1 = new QHBoxLayout();
+ hboxLayout1->setSpacing(6);
+ hboxLayout1->setMargin(0);
+ spacerItem2 = new QSpacerItem(40, 20, QSizePolicy::Expanding,
+ QSizePolicy::Minimum);
+ hboxLayout1->addItem(spacerItem2);
+
+ for (int i = 0; i < buttoncount; i++) {
+ QueryButton *button = new QueryButton(printflag, this);
+ button->setMinimumSize(QSize(72, 32));
+ button->setDefault(buttons[i] == defaultbutton);
+ button->setQuery(this);
+ button->setText(tr(buttons[i]));
+ button->setStatus(statusi[i]);
+ if (inputflag && buttons[i] == defaultbutton) {
+ if (textEdit)
+ button->setEditor(textEdit);
+ else if (lineEdit) {
+ button->setEditor(lineEdit);
+ if (buttons[i] == defaultbutton)
+ connect(lineEdit, SIGNAL(returnPressed()),
+ button, SLOT(click()));
+ }
+ }
+ connect(button, SIGNAL(clicked()), this, SLOT(buttonClicked()));
+ hboxLayout1->addWidget(button);
+ }
+
+ vboxLayout1->addLayout(hboxLayout1);
+ hboxLayout->addLayout(vboxLayout1);
+ gridLayout->addLayout(hboxLayout, 0, 0, 1, 1);
+ gridLayout->setSizeConstraint(QLayout::SetFixedSize);
+
+ if (inputflag && messagecount <= 1)
+ resize(QSize(320, 83));
+ else
+ resize(QSize(320, 132));
+
+ if (timeout)
+ startTimer(timeout * 1000);
+
+ if (exclusiveflag)
+ setWindowModality(Qt::WindowModal);
+}
diff --git a/src/pmquery/pmquery.h b/src/pmquery/pmquery.h
new file mode 100644
index 0000000..e85fd14
--- /dev/null
+++ b/src/pmquery/pmquery.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef KMQUERY_H
+#define KMQUERY_H
+
+#include <QtCore/QVariant>
+#include <QtCore/QTimerEvent>
+
+#include <QtGui/QAction>
+#include <QtGui/QApplication>
+#include <QtGui/QButtonGroup>
+#include <QtGui/QDialog>
+#include <QtGui/QGridLayout>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QLabel>
+#include <QtGui/QLineEdit>
+#include <QtGui/QTextEdit>
+#include <QtGui/QPushButton>
+#include <QtGui/QSpacerItem>
+#include <QtGui/QVBoxLayout>
+#include <QtGui/QWidget>
+
+#include <cstdio>
+
+class PmQuery : public QDialog
+{
+ Q_OBJECT
+public:
+ PmQuery(bool inputflag, bool printflag, bool noframeflag,
+ bool nosliderflag, bool usesliderflag, bool exclusiveflag);
+ void setStatus(int status) { my.status = status; }
+
+ static void setTitle(char *string);
+ static int setTimeout(char *string);
+ static int setIcontype(char *string);
+
+ static int messageCount();
+ static void addMessage(char *string);
+
+ static int buttonCount();
+ static void addButton(const char *string, bool iamdefault, int exitstatus);
+ static void addButtons(char *stringlist);
+ static void setDefaultButton(char *string);
+
+public slots:
+ void buttonClicked();
+
+protected:
+ void timerEvent(QTimerEvent *);
+
+private:
+ struct {
+ int status;
+ } my;
+};
+
+class QueryButton : public QPushButton
+{
+ Q_OBJECT
+public:
+ QueryButton(bool out, QWidget *p) : QPushButton(NULL, p)
+ {
+ my.s = 0;
+ my.k = NULL;
+ my.l = NULL;
+ my.t = NULL;
+ if (out)
+ connect(this, SIGNAL(clicked()), this, SLOT(print()));
+ else
+ connect(this, SIGNAL(clicked()), this, SLOT(noprint()));
+ }
+ void setQuery(PmQuery *dialog) { my.k = dialog; }
+ void setStatus(int status) { my.s = status; }
+ void setEditor(QLineEdit *editor) { my.l = editor; }
+ void setEditor(QTextEdit *editor) { my.t = editor; }
+
+public slots:
+ void print()
+ {
+ noprint(); puts(my.l ? my.l->text().toAscii().data() : (my.t?
+ my.t->toPlainText().toAscii().data() : text().toAscii().data()));
+ }
+ void noprint() { my.k->setStatus(my.s); }
+
+private:
+ struct {
+ int s;
+ PmQuery *k;
+ QLineEdit *l;
+ QTextEdit *t;
+ } my;
+};
+
+#endif // KMQUERY_H
diff --git a/src/pmquery/pmquery.info.in b/src/pmquery/pmquery.info.in
new file mode 100644
index 0000000..a3bb88f
--- /dev/null
+++ b/src/pmquery/pmquery.info.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>pmquery.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>PACKAGE_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>pmquery</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.aconex.pmquery</string>
+</dict>
+</plist>
diff --git a/src/pmquery/pmquery.pro b/src/pmquery/pmquery.pro
new file mode 100644
index 0000000..6a4293f
--- /dev/null
+++ b/src/pmquery/pmquery.pro
@@ -0,0 +1,9 @@
+TEMPLATE = app
+LANGUAGE = C++
+HEADERS = pmquery.h
+SOURCES = pmquery.cpp main.cpp
+ICON = pmquery.icns
+RESOURCES = pmquery.qrc
+CONFIG += qt warn_on
+release:DESTDIR = build/debug
+debug:DESTDIR = build/release
diff --git a/src/pmquery/pmquery.qrc b/src/pmquery/pmquery.qrc
new file mode 100644
index 0000000..39164cc
--- /dev/null
+++ b/src/pmquery/pmquery.qrc
@@ -0,0 +1,10 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/dialog-archive.png</file>
+ <file>images/dialog-error.png</file>
+ <file>images/dialog-host.png</file>
+ <file>images/dialog-information.png</file>
+ <file>images/dialog-question.png</file>
+ <file>images/dialog-warning.png</file>
+</qresource>
+</RCC>
diff --git a/src/pmquery/pmquery.sh.in b/src/pmquery/pmquery.sh.in
new file mode 100644
index 0000000..aaf8351
--- /dev/null
+++ b/src/pmquery/pmquery.sh.in
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PCP_BIN_DIR/pmquery "$@"
diff --git a/src/pmsignal/GNUmakefile b/src/pmsignal/GNUmakefile
new file mode 100644
index 0000000..24be489
--- /dev/null
+++ b/src/pmsignal/GNUmakefile
@@ -0,0 +1,30 @@
+#!gmake
+#
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = pmsignal.sh
+
+default:
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 pmsignal.sh $(PCP_BINADM_DIR)/pmsignal$(SHELLSUFFIX)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmsignal/pmsignal.sh b/src/pmsignal/pmsignal.sh
new file mode 100755
index 0000000..5a7e613
--- /dev/null
+++ b/src/pmsignal/pmsignal.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Red Hat.
+# Copyright (c) 2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Cross-platform signal/event sender for Performance Co-Pilot utilities.
+# Supports a minimal set of signals, used by PCP tools on all platforms.
+#
+
+. $PCP_DIR/etc/pcp.env
+
+status=1
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+prog=`basename $0`
+sigs="HUP USR1 TERM KILL"
+
+cat > $tmp/usage << EOF
+# Usage: [options] PID ... | name ...
+
+Options:
+ -a,--all send signal to all named processes (killall mode)
+ -l,--list list available signals
+ -n,--dry-run list processes that would be affected
+ -s=N,--signal=N signal to send ($sigs)"
+ --help
+EOF
+
+usage()
+{
+ [ ! -z "$@" ] && echo $@ 1>&2
+ pmgetopt --progname=$prog --config=$tmp/usage --usage
+ exit 1
+}
+
+check()
+{
+ for sig in $sigs
+ do
+ [ $sig = "$1" ] && echo $sig && return
+ done
+ usage "$prog: invalid signal - $1"
+}
+
+signal=TERM
+aflag=false
+lflag=false
+nflag=false
+
+ARGS=`pmgetopt --progname=$prog --config=$tmp/usage -- "$@"`
+[ $? != 0 ] && exit 1
+
+eval set -- "$ARGS"
+while [ $# -gt 0 ]
+do
+ case "$1"
+ in
+ -a) aflag=true ;;
+ -l) lflag=true ;;
+ -n) nflag=true ;;
+ -s) signal=`check "$2"`
+ shift
+ ;;
+ --) shift
+ break
+ ;;
+ -\?) usage ""
+ ;;
+ esac
+ shift
+done
+
+[ $lflag = true ] && echo "$sigs" && exit 0
+
+[ $# -lt 1 ] && usage "$prog: Insufficient arguments"
+
+if [ $aflag = true ]
+then
+ pids=""
+ for name in "$@"; do
+ program=`basename "$name"`
+ pidlist=`_get_pids_by_name "$program"`
+ pids="$pids $pidlist"
+ done
+else
+ pids="$@"
+fi
+if [ $nflag = true ]
+then
+ echo "$pids"
+ status=0
+ exit
+fi
+
+sts=0
+if [ "$PCP_PLATFORM" = mingw ]
+then
+ for pid in $pids ; do
+ pcp-setevent $signal $pid
+ [ $? -eq 0 ] || sts=$?
+ done
+else
+ for pid in $pids ; do
+ kill -$signal $pid
+ [ $? -eq 0 ] || sts=$?
+ done
+fi
+
+status=$sts
+exit
diff --git a/src/pmsleep/GNUmakefile b/src/pmsleep/GNUmakefile
new file mode 100644
index 0000000..4682093
--- /dev/null
+++ b/src/pmsleep/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2007 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+LLDLIBS = $(PCPLIB)
+CFILES = pmsleep.c
+CMDTARGET = pmsleep$(EXECSUFFIX)
+LDIRT = $(TARGET)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmsleep/pmsleep.c b/src/pmsleep/pmsleep.c
new file mode 100644
index 0000000..a3c5f75
--- /dev/null
+++ b/src/pmsleep/pmsleep.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2007 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "pmapi.h"
+
+int
+main(int argc, char **argv)
+{
+ struct timespec rqt;
+ struct timeval delta;
+ int r = 0;
+ char *msg;
+
+ if (argc == 2) {
+ if (pmParseInterval(argv[1], &delta, &msg) < 0) {
+ fputs(msg, stderr);
+ free(msg);
+ } else {
+ rqt.tv_sec = delta.tv_sec;
+ rqt.tv_nsec = delta.tv_usec * 1000;
+ if (0 != nanosleep(&rqt, NULL))
+ r = oserror();
+
+ exit(r);
+ }
+ }
+ fprintf(stderr, "Usage: pmsleep interval\n");
+ exit(1);
+}
diff --git a/src/pmsnap/GNUmakefile b/src/pmsnap/GNUmakefile
new file mode 100644
index 0000000..df7c270
--- /dev/null
+++ b/src/pmsnap/GNUmakefile
@@ -0,0 +1,29 @@
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPTS = pmsnap.sh
+CONFIGS = crontab.IN Snap control
+HTMLDOC = summary.html
+LSRCFILES = $(SCRIPTS) $(CONFIGS) $(HTMLDOC)
+LDIRT = crontab
+
+default: crontab
+
+crontab : crontab.IN
+ $(SED) -e '/\# .*/b' -e 's;PCP_BINADM_DIR;$(PCP_BINADM_DIR);g' < $< > $@
+
+include $(BUILDRULES)
+
+install: default
+ifeq "$(ENABLE_QT)" "true"
+ $(INSTALL) -m 755 -d $(PCP_BINADM_DIR)
+ $(INSTALL) -m 755 pmsnap.sh $(PCP_BINADM_DIR)/pmsnap
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmsnap
+ $(INSTALL) -m 644 Snap crontab summary.html $(PCP_VAR_DIR)/config/pmsnap
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMSNAPCONTROL_PATH)`
+ $(INSTALL) -m 644 control $(PCP_PMSNAPCONTROL_PATH)
+endif
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmsnap/Snap b/src/pmsnap/Snap
new file mode 100644
index 0000000..8f49a80
--- /dev/null
+++ b/src/pmsnap/Snap
@@ -0,0 +1,30 @@
+#kmchart
+Version 1
+
+Chart Title "CPU Utilization" Style utilization
+ Plot Color #2d2de2 Metric kernel.all.cpu.user
+ Plot Color #e71717 Metric kernel.all.cpu.sys
+ Plot Color rgbi:0.8/0.8/0.0 Metric kernel.all.cpu.intr
+ Optional-Plot Color rgbi:0.4/0.9/0.4 Metric kernel.all.cpu.nice
+ Optional-Plot Color rgbi:0.0/0.8/0.8 Metric kernel.all.cpu.wait.total
+ Plot Color #16e116 Metric kernel.all.cpu.idle
+
+Chart Title "Average Load" Style plot
+ Plot Metric kernel.all.load Instance 1 minute
+ Plot Metric kernel.all.load Instance 5 minute
+ Plot Metric kernel.all.load Instance 15 minute
+ Plot Color black Metric hinv.ncpu
+
+Chart Title "Disk Activity" Style stacking
+ Plot Color yellow Metric disk.all.read
+ Plot Color violet Metric disk.all.write
+
+Chart Title "Network Interface Activity" Style stacking
+ Plot Metric network.interface.in.bytes Not-Matching ^lo|^sl|^ppp|^sit
+ Plot Metric network.interface.out.bytes Not-Matching ^lo|^sl|^ppp|^sit
+
+Chart Title "TCP Network Failures" Style stacking
+ Plot Metric network.tcp.attemptfails Not-Matching ^lo|^sl|^ppp|^sit
+ Plot Metric network.tcp.retranssegs Not-Matching ^lo|^sl|^ppp|^sit
+ Plot Metric network.tcp.inerrs Not-Matching ^lo|^sl|^ppp|^sit
+ Plot Metric network.tcp.outrsts Not-Matching ^lo|^sl|^ppp|^sit
diff --git a/src/pmsnap/control b/src/pmsnap/control
new file mode 100644
index 0000000..54440b5
--- /dev/null
+++ b/src/pmsnap/control
@@ -0,0 +1,71 @@
+#
+# Configuration file for pmsnap(1)
+#
+# This file controls the output of pmsnap.
+# See the manual page for pmsnap(1) for full details.
+#
+# Although not mandatory, it is strongly advised that you follow
+# the instructions described in the manual page for pmlogger_daily(1)
+# before proceeding with the instructions described in pmsnap(1).
+#
+# Each line below (which is not a comment) has 4 columns, with
+# the following meaning ..
+#
+# (1) Name : name of the output file. This is typically
+# in the local Web content directory hierarchy.
+#
+# (2) Folio : use the named PCP archive, OR use the archives
+# in the named PCP archive folio. This is usually
+# in PCP_LOG_DIR/pmlogger/HOSTNAME, but may be the path of an
+# archive on an NFS/CIFS mounted filesystem if logging is done
+# on a remote host. Note that pmlogger_daily(1) automatically
+# creates the folio PCP_LOG_DIR/pmlogger/LOCALHOSTNAME/Latest.
+#
+# (3) Config : the pmchart "view" to use. If not found in the
+# local directory (relative to the -o flag), pmsnap
+# will also search the directories $PCP_VAR_DIR/config/pmsnap,
+# $PCP_VAR_DIR/config/pmchart, and $PCP_VAR_DIR/config/pmchart.
+#
+# (4) Arguments : passed to pmchart, see pmchart(1). Note that $commonargs
+# will be prepended to the argument list (see the assignment to
+# $commonargs below). Do NOT include any of the following
+# arguments: -o -c -a
+#
+# Notes : the string LOCALHOSTNAME in this file will be substituted
+# by the name of the local host by pmsnap at run-time.
+# If pmlogger is logging Snap metrics on the local host,
+# the example below should work virtually out of the box,
+# provided you want the output images in /var/www/htdocs/snapshots.
+# You will of course need to publish an html page that shows
+# the generated gif images - you can use the example provided
+# in the file $PCP_VAR_DIR/config/pmsnap/Snap.html.
+#
+# : lines beginning with $ are assumed to be assignments to
+# environment variables in the style of sh(1), and all text
+# following the $ will be eval'ed by the script reading
+# this control file, and the corresponding variable
+# exported into the environment.
+#
+# Recommended pmchart arguments :
+# -g 650x600 (raw image size, Width x Height)
+# -t 1min (update interval, i.e. time between ticks on the X-Axis)
+# -v 30 (number of visible points)
+# -O -0 (show end of archive, backwards by (-vh) * (-t) seconds)
+# -A 10min (aligned to start at the nearest 10 minutes)
+# -W (generated image has a white background, not transparent)
+#
+
+# === Variable Assignments ===
+#
+# DO NOT REMOVE OR EDIT THE FOLLOWING LINE
+$version=1.0
+
+# Common pmchart Arguments. Edit as required.
+$commonargs=''
+
+#
+# === pmsnap Control Specifications ===
+#
+# Name (Output Image) Folio|Archive Config Arguments
+/var/www/pcp/LOCALHOSTNAME.1hour.summary LOCALHOSTNAME/Latest Snap -O-0 -A 10min -t 2min -v 30
+/var/www/pcp/LOCALHOSTNAME.12hour.summary LOCALHOSTNAME/Latest Snap -O-0 -A 1hour -t 30min -v 30
diff --git a/src/pmsnap/crontab.IN b/src/pmsnap/crontab.IN
new file mode 100644
index 0000000..32427b0
--- /dev/null
+++ b/src/pmsnap/crontab.IN
@@ -0,0 +1,6 @@
+#
+# Standard Performance Co-Pilot pmsnap crontab entry for a PCP site
+# with one or more pmlogger instances running
+#
+# every 30 minutes, generate snapshot images
+30,0 * * * * PCP_BIN_DIR/pmsnap # -d DISPLAY
diff --git a/src/pmsnap/pmsnap.sh b/src/pmsnap/pmsnap.sh
new file mode 100755
index 0000000..87434a5
--- /dev/null
+++ b/src/pmsnap/pmsnap.sh
@@ -0,0 +1,457 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+# Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
+#
+
+. $PCP_DIR/etc/pcp.env
+
+tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1
+trap "rm -rf $tmp; exit 0" 0 1 2 3 15
+prog=`basename $0`
+
+LOCALHOST=`pmhostname`
+CONFIGDIR=$PCP_VAR_DIR/config/pmsnap
+CONTROL=$CONFIGDIR/control
+[ -z "$PCP_PMSNAPCONTROL_PATH" ] || CONTROL="$PCP_PMSNAPCONTROL_PATH"
+
+_usage()
+{
+ cat << EOF
+Usage: $prog [-dNV] [-C dir] [-c regex] [-f format] [-n regex] [-o dir]
+ -C alternate directory for control file(s)
+ -c matches the Config field in the pmsnap control file
+ -f output image format (default png)
+ -N show me, but do not execute commands
+ -n matches the Name field in the pmsnap control file
+ -o default directory for output images (default ".")
+ -V verbose trace of actions (for debugging)
+ -- pass all following options directly to pmchart
+
+If no patterns are given then all lines in the pmsnap control file
+ $CONTROL
+will be processed. If any patterns are given then only lines which
+match all patterns will be processed.
+
+Patterns are full regular expressions, see egrep(1) and may require
+appropriate quotes to avoid shell meta character expansion.
+EOF
+ exit 1
+}
+
+_fixpath()
+{
+ echo $1 $2 |\
+ $PCP_AWK_PROG '{
+ if (substr($2, 1, 1) == "/")
+ print $2
+ else
+ printf "%s/%s\n", $1, $2
+ }'
+}
+
+_warning()
+{
+ echo
+ echo "$prog: $CONTROL[$line]:"
+ echo "$*" | fmt \
+| sed -e '1{
+s/^/Warning: /
+n
+}' -e 's/^/ /'
+}
+
+_error()
+{
+ echo
+ echo "$prog: $CONTROL[$line]:"
+ echo "$*" | fmt \
+| sed -e '1{
+s/^/Error: /
+n
+}' -e 's/^/ /'
+}
+
+_debug()
+{
+ echo
+ echo "$prog: $CONTROL[$line]:"
+ echo "$*" | fmt \
+| sed -e '1{
+s/^/Debug: /
+n
+}' -e 's/^/ /'
+}
+
+serverId()
+{
+ id=42
+ while [ -f /tmp/.X$id-lock ]; do
+ id=`expr $id + 1`
+ done
+ echo $id
+}
+
+startXvfb()
+{
+ [ -x /usr/bin/Xvfb ] || return
+
+ geom="2048x2048"
+ args="-nolisten tcp -screen 0 ${geom}x8"
+ server=`serverId`
+ cookie=`mcookie`
+ XAUTHORITY=$tmp/xauth xauth add ":$server" . "$cookie" >/dev/null 2>&1
+ XAUTHORITY=$tmp/xauth /usr/bin/Xvfb ":$server" $args >/dev/null 2>&1 &
+ pid=$?
+ pmsleep 0.2
+ kill -0 $pid 2>/dev/null || return
+ export XAUTHORITY=$tmp/xauth
+ export DISPLAY=":$server"
+ echo $pid
+}
+
+stopXvfb()
+{
+ pid="$1"
+ [ -z "$pid" -o "$pid" -le 0 ] && return
+ kill $pid
+}
+
+line=0
+archives=""
+configs='.*'
+names='.*'
+dir="."
+format="png"
+verbose=false
+passthru=false
+version="1.0"
+
+# option parsing
+#
+SHOWME=false
+PMLC="echo flush | pmlc >/dev/null 2>&1"
+while getopts a:C:c:f:Nn:o:V-? c
+do
+ case $c
+ in
+ C) CONFIGDIR="$OPTARG"
+ ;;
+ c) configs="$OPTARG"
+ ;;
+ f) format="$OPTARG"
+ ;;
+ o) dir="$OPTARG"
+ ;;
+ N) SHOWME=true
+ PMLC="echo '+ echo flush | pmlc'"
+ ;;
+ n) names="$OPTARG"
+ ;;
+ V) verbose=true
+ ;;
+ -) passthru=true
+ ;;
+ ?) _usage
+ ;;
+ esac
+ [ $passthru = true ] && break
+done
+
+if [ $passthru = false ]
+then
+ shift `expr $OPTIND - 1`
+ [ $# -ne 0 ] && _usage
+fi
+commonargs="$commonargs $@"
+
+CONTROL=$CONFIGDIR/control
+if [ ! -f "$CONTROL" ]
+then
+ echo "$prog: Error: cannot find control file \"$CONTROL\""
+ exit 1
+fi
+
+if [ ! -d "$dir" ]
+then
+ echo "$prog: Error: directory \"$dir\" does not exist"
+ exit 1
+fi
+cd $dir
+dir=`pwd`
+$SHOWME && echo "+ cd $dir"
+
+$SHOWME || xvfb=`startXvfb`
+
+# escape / in patterns so $PCP_AWK_PROG is not confused
+#
+names=`echo "$names" | sed -e 's;/;\\\\/;g'`
+configs=`echo "$configs" | sed -e 's;/;\\\\/;g'`
+
+sed -e 's/LOCALHOSTNAME/'$LOCALHOST'/g' < $CONTROL \
+| $PCP_AWK_PROG '
+/^\#/ || /^\$/ || NF < 4 {
+ print
+ next
+}
+{
+ if ($1 ~ /'$names'/ && $3 ~ /'$configs'/) {
+ printf "%s %s %s '\''", $1, $2, $3
+ for (i=4; i<= NF; i++)
+ printf "%s ", $i
+ printf "'\''\n"
+ }
+}' \
+| while read aname afolio aview args
+do
+ line=`expr $line + 1`
+ case "$aname"
+ in
+ \#*|'') # comment or empty
+ continue
+ ;;
+
+ \$*) # in-line variable assignment
+ $SHOWME && echo "# $aname $afolio $aview $args"
+ cmd=`echo "$aname $afolio $aview $args" \
+ | sed -n \
+ -e "/='/s/\(='[^']*'\).*/\1/" \
+ -e '/="/s/\(="[^"]*"\).*/\1/' \
+ -e '/=[^"'"'"']/s/[;&<>|].*$//' \
+ -e '/^\\$[A-Za-z][A-Za-z0-9_]*=/{
+s/^\\$//
+s/^\([A-Za-z][A-Za-z0-9_]*\)=/export \1; \1=/p
+}'`
+ if [ -z "$cmd" ]
+ then
+ # in-line command, not a variable assignment
+ _warning "in-line command is not a variable assignment, line ignored"
+ else
+ case "$cmd"
+ in
+ 'export PATH;'*)
+ _warning "cannot change \$PATH, line ignored"
+ ;;
+ 'export IFS;'*)
+ _warning "cannot change \$IFS, line ignored"
+ ;;
+ *)
+ $SHOWME && echo "+ $cmd"
+ eval $cmd
+ ;;
+ esac
+ fi
+ continue
+ ;;
+ esac
+
+ if [ -z "$afolio" -o -z "$aview" -o -z "$args" ]
+ then
+ _error "insufficient fields in control file record"
+ continue
+ fi
+
+ # strip first and last single quote added by awk prior to read
+ #
+ args=`echo $args | sed -e "s/^'//" -e "s/'$//"`
+
+ name=`_fixpath $dir $aname`
+ if [ -d $PCP_LOG_DIR/pmlogger ]; then
+ folio=`_fixpath $PCP_LOG_DIR/pmlogger $afolio`
+ fi
+ view=`_fixpath $dir $aview`
+
+ # make sure output directory exists
+ #
+ outdir=`dirname $name`
+ if [ ! -d $outdir ]
+ then
+ if $SHOWME
+ then
+ echo "+ mkdir -p $outdir"
+ else
+ mkdir -p $outdir
+ if [ ! -d $outdir ]
+ then
+ _error "cannot create directory \"$outdir\" for output file"
+ else
+ $verbose && _debug "created image output directory \"$outdir\""
+ fi
+ fi
+ fi
+
+ if $SHOWME
+ then
+ :
+ else
+ if [ ! -w $outdir ]
+ then
+ _error "cannot write in directory \"$outdir\" to create output file"
+ exit 1
+ fi
+ fi
+
+ if $verbose
+ then
+ echo
+ _debug "name=\"$name\""
+ _debug "folio=\"$folio\""
+ _debug "view=\"$view\""
+ _debug "args=\"$args\""
+ _debug "commonargs=\"$commonargs\""
+ fi
+
+ if [ ! -f "$view" ]
+ then
+ if [ ! -f $CONFIGDIR/$aview ]
+ then
+ if [ ! -f $PCP_VAR_DIR/config/pmchart/$aview ]
+ then
+ if [ ! -f $PCP_VAR_DIR/config/kmchart/$aview ]
+ then
+ _warning "could not find \"$view\", \"$CONFIGDIR/$aview\", \"$PCP_VAR_DIR/config/pmchart/$aview, or \"$PCP_VAR_DIR/config/kmchart/$aview\""
+ continue
+ else
+ view=$PCP_VAR_DIR/config/kmchart/$aview
+ fi
+ else
+ view=$PCP_VAR_DIR/config/pmchart/$aview
+ fi
+ else
+ view=$CONFIGDIR/$aview
+ fi
+ fi
+
+ if [ ! -f $folio ]
+ then
+ # oops, no folio
+ _error "cannot find archive folio \"$folio\""
+ echo "Skipping entry from control file ..."
+ continue
+ fi
+
+ f=$folio
+ $SHOWME && echo "+ pmafm $f selection"
+ pmafm "$f" selection >$tmp/out 2>&1
+
+ if [ $? -ne 0 ]
+ then
+ if [ ! -f $folio.meta ]
+ then
+ _warning "\"$folio\" is neither a PCP folio nor a PCP archive"
+ ls -l $folio
+ continue
+ else
+ contents=$folio
+ fi
+ else
+ contents=`sed -n -e '/Archive:/s///gp' <$tmp/out`
+ if [ -z "$contents" ]
+ then
+ _warning "cannot determine archive name from PCP folio \"$folio\""
+ continue
+ fi
+ fi
+
+ arch=""
+
+ for c in $contents
+ do
+ if [ -f $c.meta ]
+ then
+ host=`pminfo -f -a $c pmcd.pmlogger.host \
+ | sed -e 's/"//g' -e 's/\[//g' \
+ | $PCP_AWK_PROG '$1=="inst" {print $6; exit}'`
+
+ port=`pminfo -f -a $c pmcd.pmlogger.port \
+ | sed -e 's/"//g' -e 's/\[//g' \
+ | $PCP_AWK_PROG '$1=="inst" {print $6; exit}'`
+
+ if [ ! -z "$host" -a ! -z "$port" ]
+ then
+ echo "$host $port" >> $tmp/flush
+ else
+ if $verbose
+ then
+ _warning "could not extract host and pmlogger control port from archive \"$c\" ... this archive may not be flushed"
+ fi
+ fi
+
+ if [ -z "$arch" ]
+ then
+ arch=$c
+ else
+ arch=$arch","$c
+ fi
+ else
+ _warning "cannot find or open archive \"$c\""
+ fi
+ done
+
+ if [ -z "$arch" ]
+ then
+ _warning "no archives could be found for \"$contents\""
+ continue
+ fi
+
+ sort -u $tmp/flush | while read host port
+ do
+ eval $PMLC -h $host -p $port
+ done
+
+ if $SHOWME
+ then
+ echo "+ pmchart -c $view -o $name.$format -a $arch $commonargs $args"
+ else
+ eval pmchart -c "$view" -o "$tmp/name.$format" -a "$arch" $commonargs $args > $tmp/err 2>&1
+ sts=$?
+
+ if [ $sts -eq 0 -a -f $tmp/name.$format ]
+ then
+ # success, update the image
+ mv $tmp/name.$format $name.$format
+ $verbose && _debug "created \"$name.$format\""
+ if [ -s "$tmp/err" ]
+ then
+ _warning "the output image \"$name.$format\" was created but there were warning messages from pmchart, as follows:"
+ cat $tmp/err
+ echo "-------------"
+ fi
+ elif grep 'too short to satisfy the requested starting alignment' $tmp/err >/dev/null
+ then
+ # archive's duration is too short for chart configuration ...
+ # silently ignore this one, as we'll probably succeed next time
+ # or soon thereafter
+ if $verbose
+ then
+ _debug "archive \"$arch\" too short for pmchart args \"$args\""
+ fi
+ else
+ # failed, remove the image and report the error
+ _warning "the output image \"$name.$format\" was not created. There were error messages from pmchart, as follows:"
+ cat $tmp/err
+ echo "-------------"
+ rm -f $name.$format
+ fi
+ fi
+
+ rm -f $tmp/err
+done
+
+$SHOWME || stopXvfb "$xvfb"
+
+exit $status
diff --git a/src/pmsnap/summary.html b/src/pmsnap/summary.html
new file mode 100644
index 0000000..f33a72b
--- /dev/null
+++ b/src/pmsnap/summary.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<HTML VERSION="2.0">
+<!
+<! To customize this file, globally change MYHOST with the name of the host
+<! you want to monitor. You may also want to change the auto-refresh rate
+<! to something other than every five minutes (300 seconds, below).
+<!
+<HEAD>
+<META HTTP-EQUIV="Refresh" CONTENT="300">
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+<TITLE>PCP Summary Snapshot for MYHOST</TITLE>
+</HEAD>
+<BODY BGCOLOR="#bfbfbf">
+<META http-equiv=refresh content=300>
+<H2>System Performance Summary</H2>
+<HR>
+<CENTER>
+<H3 ALIGN="CENTER"><FONT SIZE="3">One Hour Activity for MYHOST</FONT></H3>
+<P ALIGN="CENTER"><IMG SRC="MYHOST.1hour.summary.gif"></P>
+</CENTER>
+<HR>
+<CENTER>
+<H3 ALIGN="CENTER"><FONT SIZE="3">Twelve Hour Activity for MYHOST</FONT></H3>
+<P ALIGN="CENTER"><IMG SRC="MYHOST.12hour.summary.gif"></P>
+</CENTER>
+<HR>
+<P>Images produced by <A HREF="http://oss.sgi.com/projects/pcp/">Performance Co-Pilot Charts</A></P>
+</BODY>
+<HEAD>
+<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
+<META HTTP-EQUIV="Expires" CONTENT="-1">
+</HEAD>
+</HTML>
diff --git a/src/pmsocks/GNUmakefile b/src/pmsocks/GNUmakefile
new file mode 100644
index 0000000..507937c
--- /dev/null
+++ b/src/pmsocks/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+LSRCFILES = tsocks.sh
+
+default :
+
+include $(BUILDRULES)
+
+install : default
+ $(INSTALL) -m 755 tsocks.sh $(PCP_BIN_DIR)/pmsocks
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/pmsocks/tsocks.sh b/src/pmsocks/tsocks.sh
new file mode 100755
index 0000000..b162e74
--- /dev/null
+++ b/src/pmsocks/tsocks.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 1995-1999,2008 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Dynamically sockify the argument program using tsocks
+# from http://www.progsoc.uts.edu.au/~delius/
+#
+
+prog=`basename $0`
+
+if [ $# -eq 0 -o "X$1" = "X-?" ]
+then
+ echo "Usage: $prog [path]program [args ...]"
+ exit 1
+
+fi
+
+if [ ! -f /etc/tsocks.conf -o ! -f /usr/lib/libtsocks.so ]
+then
+ echo "$prog: Error \"tsocks\" doesn't seem to be installed."
+ echo "*** Get it from http://www.progsoc.uts.edu.au/~delius/"
+ exit 1
+fi
+
+target=`which "$1" 2>/dev/null | grep -v "^alias "`
+if [ -z "$target" -o ! -x "$target" ]
+then
+ echo "$prog: Error: \"$1\": Command not found."
+ exit 1
+fi
+
+shift
+args=""
+for arg
+do
+ args="$args \"$1\""
+ shift
+done
+
+LD_PRELOAD=/usr/lib/libtsocks.so
+export LD_PRELOAD
+eval exec $target $args
diff --git a/src/pmstat/GNUmakefile b/src/pmstat/GNUmakefile
new file mode 100644
index 0000000..24ed9c0
--- /dev/null
+++ b/src/pmstat/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmstat$(EXECSUFFIX)
+CFILES = pmstat.c
+LDIRT = $(CMDTARGET)
+
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_gui/src
+LLDLIBS = $(PCP_GUILIB)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmstat/pmstat.c b/src/pmstat/pmstat.c
new file mode 100644
index 0000000..b5faebb
--- /dev/null
+++ b/src/pmstat/pmstat.c
@@ -0,0 +1,759 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmtime.h"
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+
+#include <ctype.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_TERMIOS_H
+#include <sys/termios.h>
+#endif
+
+struct statsrc_t {
+ int ctx;
+ int flip;
+ char * sname;
+ pmID * pmids;
+ pmDesc * pmdesc;
+ pmResult * res[2];
+};
+
+static char * metrics[] = {
+#define LOADAVG 0
+ "kernel.all.load",
+#define MEM 1
+ "swap.used",
+ "mem.util.free",
+ "mem.util.bufmem",
+ "mem.util.cached",
+#define SWAP 5
+ "swap.pagesin",
+ "swap.pagesout",
+#define IO 7
+ "disk.all.blkread",
+ "disk.all.blkwrite",
+#define SYSTEM 9
+ "kernel.all.intr",
+ "kernel.all.pswitch",
+#define CPU 11
+ "kernel.all.cpu.nice",
+ "kernel.all.cpu.user",
+ "kernel.all.cpu.intr",
+ "kernel.all.cpu.sys",
+ "kernel.all.cpu.idle",
+ "kernel.all.cpu.wait.total",
+ "kernel.all.cpu.steal",
+};
+
+static char * metricSubst[] = {
+ NULL,
+/*Memory*/
+ NULL,
+ NULL,
+ "mem.bufmem",
+ NULL,
+/*Swap*/
+ "swap.in",
+ "swap.out",
+/*IO*/
+ "disk.all.read",
+ "disk.all.write",
+/*System*/
+ NULL,
+ NULL,
+/*CPU*/
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static const int nummetrics = sizeof(metrics)/sizeof (metrics[0]);
+
+pmLongOptions longopts[] = {
+ PMAPI_GENERAL_OPTIONS,
+ PMAPI_OPTIONS_HEADER("Alternate sources"),
+ PMOPT_HOSTSFILE,
+ PMOPT_LOCALPMDA,
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "suffix", 0, 'l', 0, "print last 7 charcters of the host name(s)" },
+ { "pause", 0, 'P', 0, "pause between updates for archive replay" },
+ { "xcpu", 0, 'x', 0, "extended CPU statistics reporting" },
+ PMAPI_OPTIONS_END
+};
+
+pmOptions opts = {
+ .flags = PM_OPTFLAG_MULTI | PM_OPTFLAG_BOUNDARIES | PM_OPTFLAG_STDOUT_TZ,
+ .short_options = PMAPI_OPTIONS "H:LlPx",
+ .long_options = longopts,
+};
+
+static int extraCpuStats;
+static char swapOp = 'p';
+static int rows = 21;
+static int header;
+static float period;
+static pmTimeControls defaultcontrols;
+
+
+static struct statsrc_t *
+getNewContext(int type, char * host, int quiet)
+{
+ struct statsrc_t *s;
+
+ if ((s = (struct statsrc_t *)malloc(sizeof (struct statsrc_t))) != NULL) {
+ if ((s->ctx = pmNewContext(type, host)) < 0 ) {
+ if (!quiet)
+ fprintf(stderr,
+ "%s: Cannot create context to get data from %s: %s\n",
+ pmProgname, host, pmErrStr(s->ctx));
+ free(s);
+ s = NULL;
+ } else {
+ int sts;
+ int i;
+
+ if ((s->pmids = calloc(nummetrics, sizeof(pmID))) == NULL) {
+ free(s);
+ return NULL;
+ }
+
+ if ((sts = pmLookupName(nummetrics, metrics, s->pmids)) != nummetrics) {
+ if (sts >= 0) {
+ for (i = 0; i < nummetrics; i++) {
+ if (s->pmids[i] != PM_ID_NULL)
+ continue;
+ if (metricSubst[i] == NULL) {
+ /* skip these, as archives may not contain 'em */
+ if (i != CPU+2 && i != CPU+5 && i != CPU+6) {
+ int e2 = pmLookupName(1,metrics+i, s->pmids+i);
+ if (e2 != 1)
+ fprintf(stderr,
+ "%s: %s: no metric \"%s\": %s\n",
+ pmProgname, host, metrics[i],
+ pmErrStr(e2));
+ }
+ } else {
+ int e2 = pmLookupName(1,metricSubst+i, s->pmids+i);
+ if (e2 != 1) {
+ fprintf (stderr,
+ "%s: %s: no metric \"%s\" nor \"%s\": %s\n",
+ pmProgname, host, metrics[i],
+ metricSubst[i], pmErrStr(e2));
+ }
+ else {
+ fprintf (stderr,
+ "%s: %s: Warning: using metric \"%s\" instead of \"%s\"\n",
+ pmProgname, host,
+ metricSubst[i], metrics[i]);
+ if (i == SWAP || i == SWAP+1)
+ swapOp = 's';
+ }
+ }
+ }
+ }
+ else {
+ fprintf(stderr, "%s: pmLookupName: %s\n",
+ pmProgname, pmErrStr(sts));
+ free(s->pmids);
+ free(s);
+ return NULL;
+ }
+ }
+
+ if ((s->pmdesc = calloc(nummetrics, sizeof (pmDesc))) == NULL) {
+ free(s->pmids);
+ free(s);
+ return NULL;
+ }
+
+ for (i = 0; i < nummetrics; i++) {
+ if (s->pmids[i] == PM_ID_NULL) {
+ s->pmdesc[i].indom = PM_INDOM_NULL;
+ s->pmdesc[i].pmid = PM_ID_NULL;
+ } else {
+ if ((sts = pmLookupDesc(s->pmids[i], s->pmdesc+i)) < 0) {
+ fprintf(stderr,
+ "%s: %s: Warning: cannot retrieve description for "
+ "metric \"%s\" (PMID: %s)\nReason: %s\n",
+ pmProgname, host, metrics[i], pmIDStr(s->pmids[i]),
+ pmErrStr(sts));
+ s->pmdesc[i].indom = PM_INDOM_NULL;
+ s->pmdesc[i].pmid = PM_ID_NULL;
+
+ }
+ }
+ }
+
+ s->flip = 0;
+ s->sname = NULL;
+ s->res[0] = s->res[1] = NULL;
+ }
+ }
+
+ return s;
+}
+
+static char *
+saveContextHostName(int ctx)
+{
+ char hostname[MAXHOSTNAMELEN];
+ char *name = pmGetContextHostName_r(ctx, hostname, sizeof(hostname));
+ size_t length;
+
+ if ((length = strlen(name)) == 0)
+ fprintf(stderr, "%s: Warning: pmGetContextHostName(%d) failed\n",
+ pmProgname, ctx);
+ if ((name = strdup(name)) == NULL)
+ __pmNoMem("context name", length + 1, PM_FATAL_ERR);
+ return name;
+}
+
+static void
+destroyContext(struct statsrc_t *s)
+{
+ if (s != NULL && s->ctx >= 0) {
+ int index;
+
+ if (!s->sname)
+ s->sname = saveContextHostName(s->ctx);
+ pmDestroyContext(s->ctx);
+ s->ctx = -1;
+ free(s->pmdesc);
+ s->pmdesc = NULL;
+ free(s->pmids);
+ s->pmids = NULL;
+ index = 1 - s->flip;
+ if (s->res[index] != NULL)
+ pmFreeResult(s->res[index]);
+ s->res[index] = NULL;
+ }
+}
+
+static long long
+countDiff(pmDesc *d, pmValueSet *now, pmValueSet *was)
+{
+ long long diff = 0;
+ pmAtomValue a;
+ pmAtomValue b;
+
+ pmExtractValue(was->valfmt, &was->vlist[0], d->type, &a, d->type);
+ pmExtractValue(now->valfmt, &now->vlist[0], d->type, &b, d->type);
+ switch (d->type) {
+ case PM_TYPE_32:
+ diff = b.l - a.l;
+ break;
+ case PM_TYPE_U32:
+ diff = b.ul - a.ul;
+ break;
+ case PM_TYPE_U64:
+ diff = b.ull - a.ull;
+ break;
+ }
+ return diff;
+}
+
+static void
+scalePrint(long value)
+{
+ if (value < 10000)
+ printf (" %4ld", value);
+ else {
+ value /= 1000; /* '000s */
+ if (value < 1000)
+ printf(" %3ldK", value);
+ else {
+ value /= 1000; /* '000,000s */
+ printf(" %3ldM", value);
+ }
+ }
+}
+
+static void
+timeinterval(struct timeval delta)
+{
+ defaultcontrols.interval(delta);
+ opts.interval = delta;
+ period = (delta.tv_sec * 1.0e6 + delta.tv_usec) / 1e6;
+ header = 1;
+}
+
+static void
+timeresumed(void)
+{
+ defaultcontrols.resume();
+ header = 1;
+}
+
+static void
+timerewind(void)
+{
+ defaultcontrols.rewind();
+ header = 1;
+}
+
+static void
+timenewzone(char *tz, char *label)
+{
+ defaultcontrols.newzone(tz, label);
+ header = 1;
+}
+
+static void
+timeposition(struct timeval position)
+{
+ defaultcontrols.position(position);
+ header = 1;
+}
+
+#if defined(TIOCGWINSZ)
+static void
+resize(int sig)
+{
+ struct winsize win;
+
+ if (ioctl(1, TIOCGWINSZ, &win) != -1 && win.ws_row > 0)
+ rows = win.ws_row - 3;
+}
+#endif
+
+static int
+setupTimeOptions(int ctx, pmOptions *opts, char **tzlabel)
+{
+ char *label = (char *)pmGetContextHostName(ctx);
+ char *zone;
+
+ if (pmGetContextOptions(ctx, opts)) {
+ pmflush();
+ exit(1);
+ }
+ if (opts->timezone)
+ *tzlabel = opts->timezone;
+ else
+ *tzlabel = label;
+ return pmWhichZone(&zone);
+}
+
+int
+main(int argc, char *argv[])
+{
+ time_t now;
+ pmTime *pmtime = NULL;
+ pmTimeControls controls;
+ struct statsrc_t *pd;
+ struct statsrc_t **ctxList = &pd;
+ int ctxCount = 0;
+ int sts, j;
+ int iteration;
+ int pauseFlag = 0;
+ int printTail = 0;
+ int tzh = -1;
+ char *tzlabel = NULL;
+ char **nameList;
+ int nameCount;
+
+ setlinebuf(stdout);
+
+ while ((sts = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (sts) {
+ case 'l': /* print last 7 characters of hostname(s) */
+ printTail++;
+ break;
+ case 'P': /* pause between updates when replaying an archive */
+ pauseFlag++;
+ break;
+ case 'x': /* extended CPU reporting */
+ extraCpuStats = 1;
+ break;
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (argc != opts.optind) {
+ pmprintf("%s: too many options\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (pauseFlag && (opts.context != PM_CONTEXT_ARCHIVE)) {
+ pmprintf("%s: -P can only be used with archives\n", pmProgname);
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0)
+ opts.interval.tv_sec = 5; /* 5 sec default sampling */
+
+ if (opts.context == PM_CONTEXT_ARCHIVE) {
+ nameCount = opts.narchives;
+ nameList = opts.archives;
+ } else {
+ if (opts.context == 0)
+ opts.context = PM_CONTEXT_HOST;
+ nameCount = opts.nhosts;
+ nameList = opts.hosts;
+ }
+
+ if (nameCount) {
+ if ((ctxList = calloc(nameCount, sizeof(struct statsrc_t *))) != NULL) {
+ int ct;
+
+ for (ct = 0; ct < nameCount; ct++) {
+ if ((pd = getNewContext(opts.context, nameList[ct], 0)) != NULL) {
+ ctxList[ctxCount++] = pd;
+ if (tzh < 0)
+ tzh = setupTimeOptions(pd->ctx, &opts, &tzlabel);
+ pmUseZone(tzh);
+ }
+ }
+ } else {
+ __pmNoMem("contexts", nameCount * sizeof(struct statsrc_t *), PM_FATAL_ERR);
+ }
+ } else {
+ /*
+ * Read metrics from the local host. Note that context can be
+ * either LOCAL or HOST, but not ARCHIVE here. If we fail to
+ * talk to pmcd we fallback to local context mode automagically.
+ */
+ if ((pd = getNewContext(opts.context, "local:", 1)) == NULL) {
+ opts.context = PM_CONTEXT_LOCAL;
+ pd = getNewContext(opts.context, NULL, 0);
+ }
+ if (pd) {
+ tzh = setupTimeOptions(pd->ctx, &opts, &tzlabel);
+ ctxCount = 1;
+ }
+ }
+
+ if (!ctxCount) {
+ fprintf(stderr, "%s: No place to get data from!\n", pmProgname);
+ exit(1);
+ }
+
+#if defined(TIOCGWINSZ)
+# if defined(SIGWINCH)
+ __pmSetSignalHandler(SIGWINCH, resize);
+# endif
+ resize(0);
+#endif
+
+ /* calculate the number of samples needed, if given an end time */
+ period = (opts.interval.tv_sec * 1.0e6 + opts.interval.tv_usec) / 1e6;
+ now = (time_t)(opts.start.tv_sec + 0.5 + opts.start.tv_usec / 1.0e6);
+ if (opts.finish_optarg) {
+ double win = opts.finish.tv_sec - opts.origin.tv_sec +
+ (opts.finish.tv_usec - opts.origin.tv_usec) / 1e6;
+ win /= period;
+ if (win > opts.samples)
+ opts.samples = (int)win;
+ }
+
+ if (opts.guiflag != 0 || opts.guiport != 0) {
+ char *timezone;
+
+ pmWhichZone(&timezone);
+ if (!opts.guiport)
+ opts.guiport = -1;
+ pmtime = pmTimeStateSetup(&controls, opts.context,
+ opts.guiport, opts.interval, opts.origin,
+ opts.start, opts.finish, timezone, tzlabel);
+
+ /* keep pointers to some default time control functions */
+ defaultcontrols = controls;
+
+ /* custom time control routines */
+ controls.rewind = timerewind;
+ controls.resume = timeresumed;
+ controls.newzone = timenewzone;
+ controls.interval = timeinterval;
+ controls.position = timeposition;
+ opts.guiflag = 1;
+ }
+
+ /* Do first fetch */
+ for (j = 0; j < ctxCount; j++) {
+ struct statsrc_t *pd = ctxList[j];
+
+ pmUseContext(pd->ctx);
+
+ if (!opts.guiflag && opts.context == PM_CONTEXT_ARCHIVE)
+ pmTimeStateMode(PM_MODE_INTERP, opts.interval, &opts.origin);
+
+ if (pd->ctx >= 0) {
+ if ((sts = pmFetch(nummetrics, pd->pmids, pd->res + pd->flip)) < 0)
+ pd->res[pd->flip] = NULL;
+ else
+ pd->flip = 1 - pd->flip;
+ }
+ }
+
+ for (iteration = 0; !opts.samples || iteration < opts.samples; iteration++) {
+ if ((iteration * ctxCount) % rows < ctxCount)
+ header = 1;
+
+ if (header) {
+ pmResult *r = ctxList[0]->res[1 - ctxList[0]->flip];
+ char tbuf[26];
+
+ if (r != NULL)
+ now = (time_t)(r->timestamp.tv_sec + 0.5 +
+ r->timestamp.tv_usec/ 1.0e6);
+ printf("@ %s", pmCtime(&now, tbuf));
+
+ if (ctxCount > 1) {
+ printf("%-7s%8s%21s%10s%10s%10s%*s\n",
+ "node", "loadavg","memory","swap","io","system",
+ extraCpuStats ? 20 : 12, "cpu");
+ if (extraCpuStats)
+ printf("%8s%7s %6s %6s %6s %c%1s %c%1s %4s %4s %4s %4s %3s %3s %3s %3s %3s\n",
+ "", "1 min","swpd","buff","cache", swapOp,"i",swapOp,"o","bi","bo",
+ "in","cs","us","sy","id","wa","st");
+ else
+ printf("%8s%7s %6s %6s %6s %c%1s %c%1s %4s %4s %4s %4s %3s %3s %3s\n",
+ "", "1 min","swpd","buff","cache", swapOp,"i",swapOp,"o","bi","bo",
+ "in","cs","us","sy","id");
+
+ } else {
+ printf("%8s%28s%10s%10s%10s%*s\n",
+ "loadavg","memory","swap","io","system",
+ extraCpuStats ? 20 : 12, "cpu");
+ if (extraCpuStats)
+ printf(" %7s %6s %6s %6s %6s %c%1s %c%1s %4s %4s %4s %4s %3s %3s %3s %3s %3s\n",
+ "1 min","swpd","free","buff","cache", swapOp,"i",swapOp,"o","bi","bo",
+ "in","cs","us","sy","id","wa","st");
+ else
+ printf(" %7s %6s %6s %6s %6s %c%1s %c%1s %4s %4s %4s %4s %3s %3s %3s\n",
+ "1 min","swpd","free","buff","cache", swapOp,"i",swapOp,"o","bi","bo",
+ "in","cs","us","sy","id");
+ }
+ header = 0;
+ }
+
+ if (opts.guiflag)
+ pmTimeStateVector(&controls, pmtime);
+ else if (opts.context != PM_CONTEXT_ARCHIVE || pauseFlag)
+ __pmtimevalSleep(opts.interval);
+ if (header)
+ goto next;
+
+ for (j = 0; j < ctxCount; j++) {
+ int i;
+ unsigned long long dtot = 0;
+ unsigned long long diffs[7];
+ pmAtomValue la;
+ struct statsrc_t *s = ctxList[j];
+
+ if (ctxCount > 1) {
+ const char *fn;
+
+ if (!s->sname)
+ s->sname = saveContextHostName(s->ctx);
+ fn = s->sname;
+
+ if (printTail)
+ printf("%-7s", strlen(fn) <= 7 ? fn : fn + strlen(fn) - 7);
+ else
+ printf("%-7.7s", fn);
+
+ if (s->ctx < 0) {
+ putchar('\n');
+ continue;
+ }
+
+ pmUseContext(s->ctx);
+ }
+
+ if ((sts = pmFetch(nummetrics, s->pmids, s->res + s->flip)) < 0) {
+ if (opts.context == PM_CONTEXT_HOST &&
+ (sts == PM_ERR_IPC || sts == PM_ERR_TIMEOUT)) {
+ puts(" Fetch failed. Reconnecting ...");
+ i = 1 - s->flip;
+ if (s->res[i] != NULL) {
+ pmFreeResult(s->res[i]);
+ s->res[i] = NULL;
+ }
+ pmReconnectContext(s->ctx);
+ } else if ((opts.context == PM_CONTEXT_ARCHIVE) &&
+ (sts == PM_ERR_EOL) && opts.guiflag) {
+ pmTimeStateBounds(&controls, pmtime);
+ } else if ((opts.context == PM_CONTEXT_ARCHIVE) &&
+ (sts == PM_ERR_EOL) &&
+ (s->res[0] == NULL) && (s->res[1] == NULL)) {
+ /* I'm yet to see something from this archive - don't
+ * discard it just yet */
+ puts(" No data in the archive");
+ } else {
+ int k;
+ int valid = 0;
+
+ printf(" pmFetch: %s\n", pmErrStr(sts));
+
+ destroyContext(s);
+ for (k = 0; k < ctxCount; k++)
+ valid += (ctxList[k]->ctx >= 0);
+ if (!valid)
+ exit(1);
+ }
+ } else {
+ pmResult *cur = s->res[s->flip];
+ pmResult *prev = s->res[1 - s->flip];
+
+
+ /* LoadAvg - Assume that 1min is the first one */
+ if (s->pmdesc[LOADAVG].pmid == PM_ID_NULL ||
+ cur->vset[LOADAVG]->numval < 1)
+ printf(" %7.7s", "?");
+ else {
+ pmExtractValue(cur->vset[LOADAVG]->valfmt,
+ &cur->vset[LOADAVG]->vlist[0],
+ s->pmdesc[LOADAVG].type,
+ &la, PM_TYPE_FLOAT);
+
+ printf(" %7.2f", la.f);
+ }
+
+ /* Memory state */
+ for (i = 0; i < 4; i++) {
+ if (i == 2 && ctxCount > 1)
+ continue; /* Don't report free mem for multiple hosts */
+
+ if (cur->vset[MEM+i]->numval == 1) {
+ pmUnits kb = PMDA_PMUNITS(1, 0, 0,
+ PM_SPACE_KBYTE, 0, 0);
+
+ pmExtractValue(cur->vset[MEM+i]->valfmt,
+ &cur->vset[MEM+i]->vlist[0],
+ s->pmdesc[MEM+i].type,
+ &la, PM_TYPE_U32);
+ pmConvScale(s->pmdesc[MEM+i].type, & la,
+ & s->pmdesc[MEM+i].units, &la, &kb);
+
+ if (la.ul < 1000000)
+ printf(" %6u", la.ul);
+ else {
+ la.ul /= 1024; /* PM_SPACE_MBYTE now */
+ if (la.ul < 100000)
+ printf(" %5um", la.ul);
+ else {
+ la.ul /= 1024; /* PM_SPACE_GBYTE now */
+ printf(" %5ug", la.ul);
+ }
+ }
+ } else
+ printf(" %6.6s", "?");
+ }
+
+ /* Swap in/out */
+ for (i = 0; i < 2; i++) {
+ if (s->pmdesc[SWAP+i].pmid == PM_ID_NULL || prev == NULL ||
+ prev->vset[SWAP+i]->numval != 1 ||
+ cur->vset[SWAP+i]->numval != 1)
+ printf(" %4.4s", "?");
+ else
+ scalePrint(countDiff(s->pmdesc+SWAP+i, cur->vset[SWAP+i], prev->vset[SWAP+i])/period);
+ }
+
+ /* io in/out */
+ for (i = 0; i < 2; i++) {
+ if (s->pmdesc[IO+i].pmid == PM_ID_NULL || prev == NULL ||
+ prev->vset[IO+i]->numval != 1 ||
+ cur->vset[IO+i]->numval != 1)
+ printf(" %4.4s", "?");
+ else
+ scalePrint(countDiff(s->pmdesc+IO+i, cur->vset[IO+i], prev->vset[IO+i])/period);
+ }
+
+ /* system interrupts */
+ for (i = 0; i < 2; i++) {
+ if (s->pmdesc[SYSTEM+i].pmid == PM_ID_NULL ||
+ prev == NULL ||
+ prev->vset[SYSTEM+i]->numval != 1 ||
+ cur->vset[SYSTEM+i]->numval != 1)
+ printf(" %4.4s", "?");
+ else
+ scalePrint(countDiff(s->pmdesc+SYSTEM+i, cur->vset[SYSTEM+i], prev->vset[SYSTEM+i])/period);
+ }
+
+ /* CPU utilization - report percentage */
+ for (i = 0; i < 7; i++) {
+ if (s->pmdesc[CPU+i].pmid == PM_ID_NULL || prev == NULL ||
+ cur->vset[CPU+i]->numval != 1 ||
+ prev->vset[CPU+i]->numval != 1) {
+ if (i > 0 && i < 4 && i != 2) {
+ break;
+ } else { /* Nice, intr, iowait, steal are optional */
+ diffs[i] = 0;
+ }
+ } else {
+ diffs[i] = countDiff(s->pmdesc + CPU+i,
+ cur->vset[CPU+i], prev->vset[CPU+i]);
+ dtot += diffs[i];
+ }
+ }
+
+ if (extraCpuStats) {
+ if (i != 7 || dtot == 0) {
+ printf(" %3.3s %3.3s %3.3s %3.3s %3.3s",
+ "?", "?", "?", "?", "?");
+ } else {
+ unsigned long long fill = dtot/2;
+ printf(" %3u %3u %3u %3u %3u",
+ (unsigned int)((100*(diffs[0]+diffs[1])+fill)/dtot),
+ (unsigned int)((100*(diffs[2]+diffs[3])+fill)/dtot),
+ (unsigned int)((100*diffs[4]+fill)/dtot),
+ (unsigned int)((100*diffs[5]+fill)/dtot),
+ (unsigned int)((100*diffs[6]+fill)/dtot));
+ }
+ } else if (i != 7 || dtot == 0) {
+ printf(" %3.3s %3.3s %3.3s", "?", "?", "?");
+ } else {
+ unsigned long long fill = dtot/2;
+ printf(" %3u %3u %3u",
+ (unsigned int)((100*(diffs[0]+diffs[1])+fill)/dtot),
+ (unsigned int)((100*(diffs[2]+diffs[3])+fill)/dtot),
+ (unsigned int)((100*diffs[4]+fill)/dtot));
+ }
+
+ if (prev != NULL)
+ pmFreeResult(prev);
+ s->flip = 1 - s->flip;
+ s->res[s->flip] = NULL;
+
+ putchar('\n');
+ }
+ }
+
+next:
+ if (opts.guiflag)
+ pmTimeStateAck(&controls, pmtime);
+
+ now += (time_t)period;
+ }
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/pmstore/GNUmakefile b/src/pmstore/GNUmakefile
new file mode 100644
index 0000000..e04cdd9
--- /dev/null
+++ b/src/pmstore/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmstore.c
+CMDTARGET = pmstore$(EXECSUFFIX)
+LLDLIBS = $(PCPLIB)
+LDIRT = $(TARGET)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmstore/pmstore.c b/src/pmstore/pmstore.c
new file mode 100644
index 0000000..5dcacb0
--- /dev/null
+++ b/src/pmstore/pmstore.c
@@ -0,0 +1,385 @@
+/*
+ * pmstore [-h hostname ] [-i inst[,inst...]] [-n pmnsfile ] metric value
+ *
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1995,2004-2008 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmda.h"
+#include <ctype.h>
+#include <limits.h>
+#ifdef HAVE_VALUES_H
+#include <values.h>
+#endif
+#include <float.h>
+
+#define IS_UNKNOWN 15
+#define IS_STRING 1
+#define IS_INTEGER 2
+#define IS_FLOAT 4
+#define IS_HEX 8
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General options"),
+ PMOPT_DEBUG,
+ PMOPT_HOST,
+ PMOPT_LOCALPMDA,
+ PMOPT_SPECLOCAL,
+ PMOPT_NAMESPACE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_HEADER("Value options"),
+ { "force", 0, 'f', 0, "store the value even if there is no current value set" },
+ { "insts", 1, 'i', "INSTS", "restrict store to comma-separated list of instances" },
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_POSIX,
+ .short_options = "D:fh:K:Li:n:?",
+ .long_options = longopts,
+ .short_usage = "[options] metricname value",
+};
+
+#ifndef HAVE_STRTOLL
+/*
+ * cheap hack ...won't work for large values!
+ */
+static __int64_t
+strtoll(char *p, char **endp, int base)
+{
+ return (__int64_t)strtol(p, endp, base);
+}
+#endif
+
+#ifndef HAVE_STRTOULL
+/*
+ * cheap hack ...won't work for large values!
+ */
+static __uint64_t
+strtoull(char *p, char **endp, int base)
+{
+ return (__uint64_t)strtoul(p, endp, base);
+}
+#endif
+
+static void
+mkAtom(pmAtomValue *avp, int type, char *buf)
+{
+ char *p = buf;
+ char *endbuf;
+ int vtype = IS_UNKNOWN;
+ int seendot = 0;
+ int base;
+ double d;
+ __int64_t temp_l;
+ __uint64_t temp_ul;
+
+ /*
+ * for strtol() et al, start with optional white space, then
+ * optional sign, then optional hex prefix, then stuff ...
+ */
+ p = buf;
+ while (*p && isspace((int)*p)) p++;
+ if (*p && *p == '-') p++;
+
+ if (*p && *p == '0' && p[1] && tolower((int)p[1]) == 'x') {
+ p += 2;
+ }
+ else {
+ vtype &= ~IS_HEX; /* hex MUST start with 0x or 0X */
+ }
+
+ /*
+ * does it smell like a hex number or a floating point number?
+ */
+ while (*p) {
+ if (!isdigit((int)*p)) {
+ vtype &= ~IS_INTEGER;
+ if (!isxdigit((int)*p) ) {
+ vtype &= ~IS_HEX;
+ if (*p == '.')
+ seendot++;
+ }
+ }
+ p++;
+ }
+
+ if (seendot != 1)
+ /* more or less than one '.' and it is not a floating point number */
+ vtype &= ~IS_FLOAT;
+
+ endbuf = buf;
+ base = (vtype & IS_HEX) ? 16:10;
+
+ switch (type) {
+ case PM_TYPE_32:
+ temp_l = strtol(buf, &endbuf, base);
+ if (oserror() != ERANGE) {
+ /*
+ * ugliness here is for cases where pmstore is compiled
+ * 64-bit (e.g. on ia64) and then strtol() may return
+ * values larger than 32-bits with no error indication
+ * ... if this is being compiled 32-bit, then the
+ * condition will be universally false, and a smart
+ * compiler may notice and warn.
+ */
+#ifdef HAVE_64BIT_LONG
+ if (temp_l > 0x7fffffffLL || temp_l < (-0x7fffffffLL - 1))
+ setoserror(ERANGE);
+ else
+#endif
+ {
+ avp->l = (__int32_t)temp_l;
+ }
+ }
+ break;
+
+ case PM_TYPE_U32:
+ temp_ul = strtoul(buf, &endbuf, base);
+ if (oserror() != ERANGE) {
+#ifdef HAVE_64BIT_LONG
+ if (temp_ul > 0xffffffffLL)
+ setoserror(ERANGE);
+ else
+#endif
+ {
+ avp->ul = (__uint32_t)temp_ul;
+ }
+ }
+ break;
+
+ case PM_TYPE_64:
+ avp->ll = strtoll(buf, &endbuf, base);
+ /* trust library to set error code to ERANGE as appropriate */
+ break;
+
+ case PM_TYPE_U64:
+ /* trust library to set error code to ERANGE as appropriate */
+ avp->ull = strtoull(buf, &endbuf, base);
+ break;
+
+ case PM_TYPE_FLOAT:
+ if (vtype & IS_HEX) {
+ /* strtod from GNU libc would try to convert it using some
+ * strange algo - we don't want it */
+ endbuf = buf;
+ }
+ else {
+ d = strtod(buf, &endbuf);
+ if (d < FLT_MIN || d > FLT_MAX)
+ setoserror(ERANGE);
+ else {
+ avp->f = (float)d;
+ }
+ }
+ break;
+
+ case PM_TYPE_DOUBLE:
+ if (vtype & IS_HEX) {
+ /* strtod from GNU libc would try to convert it using some
+ * strange algo - we don't want it */
+ endbuf = buf;
+ }
+ else {
+ avp->d = strtod(buf, &endbuf);
+ }
+ break;
+
+ case PM_TYPE_STRING:
+ if ((avp->cp = strdup(buf)) == NULL) {
+ __pmNoMem("pmstore", strlen(buf)+1, PM_FATAL_ERR);
+ }
+ endbuf = "";
+ break;
+
+ }
+ if (*endbuf != '\0') {
+ fprintf(stderr,
+ "The value \"%s\" is incompatible with the data "
+ "type (PM_TYPE_%s)\n",
+ buf, pmTypeStr(type));
+ exit(1);
+ }
+ if (oserror() == ERANGE) {
+ fprintf(stderr,
+ "The value \"%s\" is out of range for the data "
+ "type (PM_TYPE_%s)\n",
+ buf, pmTypeStr(type));
+ exit(1);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ int n;
+ int c;
+ int i;
+ char *p;
+ char *source;
+ char *namelist[1];
+ pmID pmidlist[1];
+ pmResult *result;
+ char **instnames = NULL;
+ int numinst = 0;
+ int force = 0;
+ pmDesc desc;
+ pmAtomValue nav;
+ pmValueSet *vsp;
+ char *subopt;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'f':
+ force++;
+ break;
+
+ case 'i': /* list of instances */
+#define WHITESPACE ", \t\n"
+ subopt = strtok(opts.optarg, WHITESPACE);
+ while (subopt != NULL) {
+ numinst++;
+ n = numinst * sizeof(char *);
+ instnames = (char **)realloc(instnames, n);
+ if (instnames == NULL)
+ __pmNoMem("pmstore.instnames", n, PM_FATAL_ERR);
+ instnames[numinst-1] = subopt;
+ subopt = strtok(NULL, WHITESPACE);
+ }
+#undef WHITESPACE
+ break;
+
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (opts.errors || opts.optind != argc - 2) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (opts.context == PM_CONTEXT_HOST)
+ source = opts.hosts[0];
+ else if (opts.context == PM_CONTEXT_LOCAL)
+ source = NULL;
+ else {
+ opts.context = PM_CONTEXT_HOST;
+ source = "local:";
+ }
+ if ((sts = pmNewContext(opts.context, source)) < 0) {
+ if (opts.context == PM_CONTEXT_LOCAL)
+ fprintf(stderr, "%s: Cannot make standalone local connection: %s\n",
+ pmProgname, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ exit(1);
+ }
+
+ namelist[0] = argv[opts.optind++];
+ if ((n = pmLookupName(1, namelist, pmidlist)) < 0) {
+ printf("%s: pmLookupName: %s\n", namelist[0], pmErrStr(n));
+ exit(1);
+ }
+ if (pmidlist[0] == PM_ID_NULL) {
+ printf("%s: unknown metric\n", namelist[0]);
+ exit(1);
+ }
+ if ((n = pmLookupDesc(pmidlist[0], &desc)) < 0) {
+ printf("%s: pmLookupDesc: %s\n", namelist[0], pmErrStr(n));
+ exit(1);
+ }
+ if (desc.type == PM_TYPE_AGGREGATE || desc.type == PM_TYPE_AGGREGATE_STATIC) {
+ fprintf(stderr, "%s: Cannot modify values for PM_TYPE_AGGREGATE metrics\n",
+ pmProgname);
+ exit(1);
+ }
+ if (desc.type == PM_TYPE_EVENT || desc.type == PM_TYPE_HIGHRES_EVENT) {
+ fprintf(stderr, "%s: Cannot modify values for event type metrics\n",
+ pmProgname);
+ exit(1);
+ }
+ if (instnames != NULL) {
+ pmDelProfile(desc.indom, 0, NULL);
+ for (i = 0; i < numinst; i++) {
+ if ((n = pmLookupInDom(desc.indom, instnames[i])) < 0) {
+ printf("pmLookupInDom %s[%s]: %s\n",
+ namelist[0], instnames[i], pmErrStr(n));
+ exit(1);
+ }
+ if ((sts = pmAddProfile(desc.indom, 1, &n)) < 0) {
+ printf("pmAddProfile %s[%s]: %s\n",
+ namelist[0], instnames[i], pmErrStr(sts));
+ exit(1);
+ }
+ }
+ }
+ if ((n = pmFetch(1, pmidlist, &result)) < 0) {
+ printf("%s: pmFetch: %s\n", namelist[0], pmErrStr(n));
+ exit(1);
+ }
+
+ /* value is argv[opts.optind] */
+ mkAtom(&nav, desc.type, argv[opts.optind]);
+
+ vsp = result->vset[0];
+ if (vsp->numval < 0) {
+ printf("%s: Error: %s\n", namelist[0], pmErrStr(vsp->numval));
+ exit(1);
+ }
+
+ if (vsp->numval == 0) {
+ if (!force) {
+ printf("%s: No value(s) available!\n", namelist[0]);
+ exit(1);
+ }
+ else {
+ pmAtomValue tmpav;
+
+ mkAtom(&tmpav, PM_TYPE_STRING, "(none)");
+
+ vsp->numval = 1;
+ vsp->valfmt = __pmStuffValue(&tmpav, &vsp->vlist[0], PM_TYPE_STRING);
+ }
+ }
+
+ for (i = 0; i < vsp->numval; i++) {
+ pmValue *vp = &vsp->vlist[i];
+ printf("%s", namelist[0]);
+ if (desc.indom != PM_INDOM_NULL) {
+ if ((n = pmNameInDom(desc.indom, vp->inst, &p)) < 0)
+ printf(" inst [%d]", vp->inst);
+ else {
+ printf(" inst [%d or \"%s\"]", vp->inst, p);
+ free(p);
+ }
+ }
+ printf(" old value=");
+ pmPrintValue(stdout, vsp->valfmt, desc.type, vp, 1);
+ vsp->valfmt = __pmStuffValue(&nav, &vsp->vlist[i], desc.type);
+ printf(" new value=");
+ pmPrintValue(stdout, vsp->valfmt, desc.type, vp, 1);
+ putchar('\n');
+ }
+ if ((n = pmStore(result)) < 0) {
+ printf("%s: pmStore: %s\n", namelist[0], pmErrStr(n));
+ exit(1);
+ }
+ pmFreeResult(result);
+ exit(0);
+}
diff --git a/src/pmtime/GNUmakefile b/src/pmtime/GNUmakefile
new file mode 100644
index 0000000..e35c1a6
--- /dev/null
+++ b/src/pmtime/GNUmakefile
@@ -0,0 +1,71 @@
+TOPDIR = ../..
+COMMAND = pmtime
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+WRAPPER = $(COMMAND).sh
+QRCFILE = $(COMMAND).qrc
+RCFILE = $(COMMAND).rc
+ICOFILE = $(COMMAND).ico
+ICNFILE = $(COMMAND).icns
+XMLFILE = $(COMMAND).info
+UIFILES = $(shell echo *.ui)
+HEADERS = aboutdialog.h console.h pmtime.h pmtimearch.h pmtimelive.h \
+ seealsodialog.h showboundsdialog.h timelord.h timezone.h
+SOURCES = aboutdialog.cpp console.cpp pmtime.cpp pmtimearch.cpp pmtimelive.cpp \
+ seealsodialog.cpp showboundsdialog.cpp timelord.cpp main.cpp
+LSRCFILES = $(PROJECT) $(QRCFILE) $(RCFILE) $(UIFILES) $(HEADERS) \
+ $(SOURCES) $(WRAPPER).in $(XMLFILE).in
+LDIRT = $(COMMAND) $(WRAPPER) $(XMLFILE) images
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me: images wrappers
+ $(QTMAKE)
+ $(LNMAKE)
+
+ifeq ($(WINDOW),mac)
+PKG_MAC_DIR = /Library/PCP/$(COMMAND).app/Contents
+wrappers: $(WRAPPER) $(XMLFILE)
+else
+wrappers:
+endif
+
+$(WRAPPER): $(WRAPPER).in
+ $(SED) -e '/\# .*/b' -e 's;PKG_MAC_DIR;$(PKG_MAC_DIR);g' < $< > $@
+$(XMLFILE): $(XMLFILE).in
+ $(SED) -e 's;PACKAGE_VERSION;$(PACKAGE_VERSION);g' < $< > $@
+
+install: default
+ifneq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(BINARY) $(PCP_BIN_DIR)/$(COMMAND)
+else
+ $(INSTALL) -m 755 $(WRAPPER) $(PCP_BIN_DIR)/$(COMMAND)
+ $(call INSTALL_DIRECTORY_HIERARCHY,$(PKG_MAC_DIR),/Library)
+ $(INSTALL) -m 644 $(XMLFILE) $(PKG_MAC_DIR)/Info.plist
+ $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS
+ $(call INSTALL_QT_FRAMEWORKS,$(BINARY))
+ $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND)
+ rm $(BINARY)
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources
+ $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE)
+ $(call INSTALL_QT_RESOURCES,$(PKG_MAC_DIR)/Resources)
+endif
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+images: $(ICNFILE)
+ $(LN_S) $(TOPDIR)/images images
+
+$(ICNFILE):
+ $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE)
diff --git a/src/pmtime/aboutdialog.cpp b/src/pmtime/aboutdialog.cpp
new file mode 100644
index 0000000..37de391
--- /dev/null
+++ b/src/pmtime/aboutdialog.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "aboutdialog.h"
+#include <pcp/pmapi.h>
+
+AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+ QRegExp rx("\\b(VERSION)\\b");
+ QString version = versionTextLabel->text();
+ version.replace(rx, pmGetConfig("PCP_VERSION"));
+ versionTextLabel->setText(version);
+}
+
+void AboutDialog::aboutOKButton_clicked()
+{
+ done(0);
+}
diff --git a/src/pmtime/aboutdialog.h b/src/pmtime/aboutdialog.h
new file mode 100644
index 0000000..17ca543
--- /dev/null
+++ b/src/pmtime/aboutdialog.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef ABOUTDIALOG_H
+#define ABOUTDIALOG_H
+
+#include "ui_aboutdialog.h"
+
+class AboutDialog : public QDialog, public Ui::AboutDialog
+{
+ Q_OBJECT
+
+public:
+ AboutDialog(QWidget* parent);
+
+public slots:
+ virtual void aboutOKButton_clicked();
+};
+
+#endif // ABOUTDIALOG_H
diff --git a/src/pmtime/aboutdialog.ui b/src/pmtime/aboutdialog.ui
new file mode 100644
index 0000000..9b10993
--- /dev/null
+++ b/src/pmtime/aboutdialog.ui
@@ -0,0 +1,229 @@
+<ui version="4.0" >
+ <class>AboutDialog</class>
+ <widget class="QDialog" name="AboutDialog" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>330</width>
+ <height>240</height>
+ </rect>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>330</width>
+ <height>240</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>330</width>
+ <height>260</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>About</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="modal" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="aboutPixmapLabel" >
+ <property name="pixmap" >
+ <pixmap resource="pmtime.qrc" >:/images/pmtime.png</pixmap>
+ </property>
+ <property name="scaledContents" >
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="versionTextLabel" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" >
+ <string>&lt;b>&lt;i>pmtime&lt;/i>&lt;/b>&lt;br>
+&lt;b>&lt;i>Version VERSION&lt;/i>&lt;/b></string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="authorsTextLabel" >
+ <property name="midLineWidth" >
+ <number>0</number>
+ </property>
+ <property name="text" >
+ <string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
+p, li { white-space: pre-wrap; }
+&lt;/style>&lt;/head>&lt;body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
+&lt;p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Copyright 2012, Red Hat.&lt;br />Copyright 2012, Nathan Scott.&lt;br />Copyright 2006, Ken McDonell.&lt;br />Copyright 2006-2010, Aconex.&lt;br />All rights reserved.&lt;br />&lt;span style=" font-style:italic;">&lt;br />This program is licensed under the&lt;br />GNU General Public License.&lt;br />&lt;/span>&lt;/p>&lt;/body>&lt;/html></string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="aboutOKButton" >
+ <property name="focusPolicy" >
+ <enum>Qt::TabFocus</enum>
+ </property>
+ <property name="text" >
+ <string>OK</string>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmtime.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>aboutOKButton</sender>
+ <signal>clicked()</signal>
+ <receiver>AboutDialog</receiver>
+ <slot>aboutOKButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/console.cpp b/src/pmtime/console.cpp
new file mode 100644
index 0000000..cb346b9
--- /dev/null
+++ b/src/pmtime/console.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "console.h"
+#include "pmtime.h"
+#include <stdarg.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+Console *console;
+
+Console::Console() : QDialog()
+{
+ my.level = 0;
+ if (pmDebug & DBG_TRACE_APPL0)
+ my.level |= PmTime::DebugApp; // pmtime apps internals
+ if (pmDebug & DBG_TRACE_APPL1)
+ my.level |= PmTime::DebugProtocol; // trace pmtime protocol
+ setupUi(this);
+}
+
+void Console::post(const char *fmt, ...)
+{
+ static char buffer[4096];
+ va_list ap;
+
+ if (!(my.level & PmTime::DebugApp))
+ return;
+
+ va_start(ap, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ fputs(buffer, stderr);
+ fputc('\n', stderr);
+ text->append(QString(buffer));
+}
+
+void Console::post(int level, const char *fmt, ...)
+{
+ static char buffer[4096];
+ va_list ap;
+
+ if (!(my.level & level))
+ return;
+
+ va_start(ap, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, ap);
+ va_end(ap);
+
+ fputs(buffer, stderr);
+ fputc('\n', stderr);
+ text->append(QString(buffer));
+}
diff --git a/src/pmtime/console.h b/src/pmtime/console.h
new file mode 100644
index 0000000..7776616
--- /dev/null
+++ b/src/pmtime/console.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+#include "ui_console.h"
+
+class Console : public QDialog, public Ui::Console
+{
+ Q_OBJECT
+
+public:
+ Console();
+ virtual void post(const char * p, ...);
+ virtual void post(int level, const char * p, ...);
+
+private:
+ struct {
+ int level;
+ } my;
+};
+
+extern Console *console;
+
+#endif // CONSOLE_H
diff --git a/src/pmtime/console.ui b/src/pmtime/console.ui
new file mode 100644
index 0000000..a3ea80f
--- /dev/null
+++ b/src/pmtime/console.ui
@@ -0,0 +1,139 @@
+<ui version="4.0" >
+ <class>Console</class>
+ <widget class="QDialog" name="Console" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>493</width>
+ <height>326</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>pmtime Console</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QTextEdit" name="text" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="autoFormatting" >
+ <set>QTextEdit::AutoNone</set>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonHide" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>85</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>85</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="text" >
+ <string>&amp;Hide</string>
+ </property>
+ <property name="shortcut" >
+ <string>Alt+H</string>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11" />
+ <resources>
+ <include location="pmtime.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonHide</sender>
+ <signal>clicked()</signal>
+ <receiver>Console</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/main.cpp b/src/pmtime/main.cpp
new file mode 100644
index 0000000..3e85e63
--- /dev/null
+++ b/src/pmtime/main.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtGui/QApplication>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "timelord.h"
+#include "pmtime.h"
+
+static pmOptions opts;
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_GUIPORT,
+ PMOPT_VERSION,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static void setupEnvironment(void)
+{
+ char *value;
+ QString confirm = pmGetConfig("PCP_BIN_DIR");
+ confirm.prepend("PCP_XCONFIRM_PROG=");
+ confirm.append(QChar(__pmPathSeparator()));
+ confirm.append("pmquery");
+ if ((value = strdup((const char *)confirm.toAscii())) != NULL)
+ putenv(value);
+ if (getenv("PCP_STDERR") == NULL && // do not overwrite, for QA
+ ((value = strdup("PCP_STDERR=DISPLAY")) != NULL))
+ putenv(value);
+
+ QCoreApplication::setOrganizationName("PCP");
+ QCoreApplication::setApplicationName("pmtime");
+}
+
+int main(int argc, char **argv)
+{
+ int autoport = 0;
+
+ QApplication a(argc, argv);
+ setupEnvironment();
+
+ opts.short_options = "D:p:V?";
+ opts.long_options = longopts;
+ pmGetOptions(argc, argv, &opts);
+ if (opts.errors || opts.optind != argc) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if (!opts.guiport) {
+ char *endnum, *envstr;
+
+ autoport = 1;
+ if ((envstr = getenv("PMTIME_PORT")) == NULL) {
+ opts.guiport = PmTime::BasePort;
+ } else {
+ opts.guiport = strtol(envstr, &endnum, 0);
+ if (*endnum != '\0' || opts.guiport < 0) {
+ pmprintf(
+ "%s: PMTIME_PORT must be a numeric port number (not %s)\n",
+ pmProgname, envstr);
+ pmflush();
+ exit(1);
+ }
+ }
+ }
+
+ console = new Console;
+ TimeLord tl(&a);
+ do {
+ if (tl.listen(QHostAddress::LocalHost, opts.guiport))
+ break;
+ opts.guiport++;
+ } while (autoport && (opts.guiport >= 0));
+
+ if (!opts.guiport || tl.isListening() == false) {
+ if (!autoport)
+ pmprintf("%s: cannot find an available port\n", pmProgname);
+ else
+ pmprintf("%s: cannot connect to requested port (%d)\n",
+ pmProgname, opts.guiport);
+ pmflush();
+ exit(1);
+ } else if (autoport) { /* write to stdout for client */
+ char name[32];
+ int c = snprintf(name, sizeof(name), "port=%u\n", opts.guiport);
+ if (write(fileno(stdout), name, c + 1) < 0) {
+ if (errno != EPIPE) {
+ pmprintf("%s: cannot write port for client: %s\n",
+ pmProgname, strerror(errno));
+ pmflush();
+ }
+ exit(1);
+ }
+ }
+
+ PmTimeLive hc;
+ PmTimeArch ac;
+ tl.setContext(&hc, &ac);
+
+ hc.init();
+ if (!pmDebug) hc.disableConsole();
+ else hc.popup(1);
+
+ ac.init();
+ if (!pmDebug) ac.disableConsole();
+ else ac.popup(1);
+
+ a.exec();
+ return 0;
+}
diff --git a/src/pmtime/pmtime.cpp b/src/pmtime/pmtime.cpp
new file mode 100644
index 0000000..3c586f4
--- /dev/null
+++ b/src/pmtime/pmtime.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2014, Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmtime.h"
+#include <QtCore/QUrl>
+#include <QtCore/QLibraryInfo>
+#include <QtGui/QDesktopServices>
+#include <QtGui/QWhatsThis>
+#include <QtGui/QMessageBox>
+#include <QtGui/QCloseEvent>
+#include "console.h"
+#include "aboutdialog.h"
+#include "seealsodialog.h"
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+PmTime::PmTime() : QMainWindow(NULL)
+{
+}
+
+void PmTime::helpAbout()
+{
+ AboutDialog about(this);
+ about.exec();
+}
+
+void PmTime::helpAboutQt()
+{
+ QApplication::aboutQt();
+}
+
+void PmTime::helpSeeAlso()
+{
+ SeeAlsoDialog about(this);
+ about.exec();
+}
+
+void PmTime::whatsThis()
+{
+ QWhatsThis::enterWhatsThisMode();
+}
+
+void PmTime::helpManual()
+{
+ bool ok;
+ QString documents("file://");
+ QString separator = QString(__pmPathSeparator());
+ documents.append(pmGetConfig("PCP_HTML_DIR"));
+ documents.append(separator).append("timecontrol.html");
+ ok = QDesktopServices::openUrl(QUrl(documents, QUrl::TolerantMode));
+ if (!ok) {
+ documents.prepend("Failed to open:\n");
+ QMessageBox::warning(this, pmProgname, documents);
+ }
+}
+
+void PmTime::hideWindow()
+{
+ if (isVisible())
+ hide();
+ else {
+ show();
+ raise();
+ }
+}
+
+void PmTime::popup(bool hello_popetts)
+{
+ if (!hello_popetts)
+ hide();
+ else {
+ show();
+ raise();
+ }
+}
+
+void PmTime::closeEvent(QCloseEvent *ce)
+{
+ hide();
+ ce->ignore();
+}
+
+void PmTime::showConsole()
+{
+ console->show();
+}
diff --git a/src/pmtime/pmtime.h b/src/pmtime/pmtime.h
new file mode 100644
index 0000000..01faee7
--- /dev/null
+++ b/src/pmtime/pmtime.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2006-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMTIME_H
+#define PMTIME_H
+
+#include <QtGui/QMainWindow>
+#include <qmc_time.h>
+
+class PmTime : public QMainWindow, public QmcTime
+{
+ Q_OBJECT
+
+public:
+ typedef enum {
+ DebugApp = 0x1,
+ DebugProtocol = 0x2,
+ } DebugOptions;
+
+public:
+ PmTime();
+ virtual void popup(bool hello_popetts);
+
+public slots:
+ virtual void helpManual();
+ virtual void helpAbout();
+ virtual void helpAboutQt();
+ virtual void helpSeeAlso();
+ virtual void whatsThis();
+ virtual void hideWindow();
+ virtual void showConsole();
+
+protected:
+ virtual void closeEvent(QCloseEvent * ce);
+};
+
+#endif // PMTIME_H
diff --git a/src/pmtime/pmtime.info.in b/src/pmtime/pmtime.info.in
new file mode 100644
index 0000000..7a0842f
--- /dev/null
+++ b/src/pmtime/pmtime.info.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>pmtime.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>PACKAGE_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>pmtime</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.aconex.pmtime</string>
+</dict>
+</plist>
diff --git a/src/pmtime/pmtime.pro b/src/pmtime/pmtime.pro
new file mode 100644
index 0000000..ad3e193
--- /dev/null
+++ b/src/pmtime/pmtime.pro
@@ -0,0 +1,24 @@
+TEMPLATE = app
+LANGUAGE = C++
+HEADERS = console.h pmtime.h pmtimearch.h pmtimelive.h \
+ aboutdialog.h seealsodialog.h showboundsdialog.h \
+ timelord.h timezone.h
+SOURCES = console.cpp pmtime.cpp pmtimearch.cpp pmtimelive.cpp \
+ aboutdialog.cpp seealsodialog.cpp showboundsdialog.cpp \
+ timelord.cpp main.cpp
+FORMS = aboutdialog.ui console.ui pmtimelive.ui pmtimearch.ui \
+ seealsodialog.ui showboundsdialog.ui
+ICON = pmtime.icns
+RC_FILE = pmtime.rc
+RESOURCES = pmtime.qrc
+CONFIG += qt warn_on
+INCLUDEPATH += ../include ../libpcp_qwt/src ../libpcp_qmc/src
+release:DESTDIR = build/debug
+debug:DESTDIR = build/release
+LIBS += -L../libpcp/src
+LIBS += -L../libpcp_qwt/src -L../libpcp_qwt/src/$$DESTDIR
+LIBS += -L../libpcp_qmc/src -L../libpcp_qmc/src/$$DESTDIR
+LIBS += -lpcp_qwt -lpcp_qmc -lpcp
+win32:LIBS += -lwsock32
+QT += network
+QMAKE_INFO_PLIST = pmtime.info
diff --git a/src/pmtime/pmtime.qrc b/src/pmtime/pmtime.qrc
new file mode 100644
index 0000000..be262b0
--- /dev/null
+++ b/src/pmtime/pmtime.qrc
@@ -0,0 +1,24 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/play_off.png</file>
+ <file>images/play_on.png</file>
+ <file>images/fastfwd_off.png</file>
+ <file>images/fastfwd_on.png</file>
+ <file>images/fastback_off.png</file>
+ <file>images/fastback_on.png</file>
+ <file>images/back_off.png</file>
+ <file>images/back_on.png</file>
+ <file>images/stepfwd_off.png</file>
+ <file>images/stepfwd_on.png</file>
+ <file>images/stepback_off.png</file>
+ <file>images/stepback_on.png</file>
+ <file>images/stop_off.png</file>
+ <file>images/stop_on.png</file>
+ <file>images/whatsthis.png</file>
+ <file>images/edit-clear.png</file>
+ <file>images/internet-web-browser.png</file>
+ <file>images/pmtime.png</file>
+ <file>images/aboutpmtime.png</file>
+ <file>images/aboutpcp.png</file>
+</qresource>
+</RCC>
diff --git a/src/pmtime/pmtime.rc b/src/pmtime/pmtime.rc
new file mode 100644
index 0000000..61dd59a
--- /dev/null
+++ b/src/pmtime/pmtime.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON DISCARDABLE "images/pmtime.ico"
diff --git a/src/pmtime/pmtime.sh.in b/src/pmtime/pmtime.sh.in
new file mode 100644
index 0000000..350504a
--- /dev/null
+++ b/src/pmtime/pmtime.sh.in
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec PKG_MAC_DIR/MacOS/pmtime "$@"
diff --git a/src/pmtime/pmtimearch.cpp b/src/pmtime/pmtimearch.cpp
new file mode 100644
index 0000000..cb14f03
--- /dev/null
+++ b/src/pmtime/pmtimearch.cpp
@@ -0,0 +1,704 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmtimearch.h"
+
+#include <QtCore/QTimer>
+#include <QtGui/QValidator>
+#include <QtGui/QMessageBox>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "aboutdialog.h"
+#include "seealsodialog.h"
+
+PmTimeArch::PmTimeArch() : PmTime()
+{
+ setupUi(this);
+#ifdef Q_OS_MAC // fixup after relocation of the MenuBar by Qt
+ QSize size = QSize(width(), height() - MenuBar->height());
+ setMinimumSize(size);
+ setMaximumSize(size);
+#endif
+}
+
+typedef struct {
+ PmTime::State state;
+ PmTime::Mode mode;
+ QIcon *back;
+ QIcon *stop;
+ QIcon *play;
+} IconStateMap;
+
+static void setup(IconStateMap *map, PmTime::State state, PmTime::Mode mode,
+ QIcon *back, QIcon *stop, QIcon *play)
+{
+ map->state = state;
+ map->mode = mode;
+ map->back = back;
+ map->stop = stop;
+ map->play = play;
+}
+
+void PmTimeArch::setControl(PmTime::State state, PmTime::Mode mode)
+{
+ static IconStateMap maps[3 * 3];
+ static int nmaps;
+
+ if (!nmaps) {
+ nmaps = sizeof(maps) / sizeof(maps[0]);
+ setup(&maps[0], PmTime::StoppedState, PmTime::NormalMode,
+ PmTime::icon(PmTime::BackwardOff),
+ PmTime::icon(PmTime::StoppedOn),
+ PmTime::icon(PmTime::ForwardOff));
+ setup(&maps[1], PmTime::ForwardState, PmTime::NormalMode,
+ PmTime::icon(PmTime::BackwardOff),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::ForwardOn));
+ setup(&maps[2], PmTime::BackwardState, PmTime::NormalMode,
+ PmTime::icon(PmTime::BackwardOn),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::ForwardOff));
+ setup(&maps[3], PmTime::StoppedState, PmTime::FastMode,
+ PmTime::icon(PmTime::FastBackwardOff),
+ PmTime::icon(PmTime::StoppedOn),
+ PmTime::icon(PmTime::FastForwardOff));
+ setup(&maps[4], PmTime::ForwardState, PmTime::FastMode,
+ PmTime::icon(PmTime::FastBackwardOff),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::FastForwardOn));
+ setup(&maps[5], PmTime::BackwardState, PmTime::FastMode,
+ PmTime::icon(PmTime::FastBackwardOn),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::FastForwardOff));
+ setup(&maps[6], PmTime::StoppedState, PmTime::StepMode,
+ PmTime::icon(PmTime::StepBackwardOff),
+ PmTime::icon(PmTime::StoppedOn),
+ PmTime::icon(PmTime::StepForwardOff));
+ setup(&maps[7], PmTime::ForwardState, PmTime::StepMode,
+ PmTime::icon(PmTime::StepBackwardOff),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::StepForwardOn));
+ setup(&maps[8], PmTime::BackwardState, PmTime::StepMode,
+ PmTime::icon(PmTime::StepBackwardOn),
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::StepForwardOff));
+ }
+
+ if (my.pmtime.state != state || my.pmtime.mode != mode) {
+ for (int i = 0; i < nmaps; i++) {
+ if (maps[i].state == state && maps[i].mode == mode) {
+ buttonBack->setIcon(*maps[i].back);
+ buttonStop->setIcon(*maps[i].stop);
+ buttonPlay->setIcon(*maps[i].play);
+ break;
+ }
+ }
+ my.pmtime.state = state;
+ my.pmtime.mode = mode;
+ if (my.pmtime.mode == PmTime::NormalMode) {
+ buttonSpeed->setEnabled(true);
+ textLabelSpeed->setEnabled(true);
+ lineEditSpeed->setEnabled(true);
+ wheelSpeed->setEnabled(true);
+ }
+ else {
+ buttonSpeed->setEnabled(false);
+ textLabelSpeed->setEnabled(false);
+ lineEditSpeed->setEnabled(false);
+ wheelSpeed->setEnabled(false);
+ }
+ }
+}
+
+void PmTimeArch::init()
+{
+ static char UTC[] = "UTC\0Universal Coordinated Time";
+
+ console->post(PmTime::DebugApp, "Starting Archive Time Control...");
+
+ my.units = PmTime::Seconds;
+ my.first = true;
+ my.tzActions = NULL;
+
+ memset(&my.absoluteStart, 0, sizeof(struct timeval));
+ memset(&my.absoluteEnd, 0, sizeof(struct timeval));
+ memset(&my.pmtime, 0, sizeof(my.pmtime));
+ my.pmtime.source = PmTime::ArchiveSource;
+ my.pmtime.delta.tv_sec = PmTime::DefaultDelta;
+
+ my.showMilliseconds = false;
+ optionsDetailShow_MillisecondsAction->setChecked(my.showMilliseconds);
+ my.showYear = false;
+ optionsDetailShow_YearAction->setChecked(my.showYear);
+
+ my.speed = 1.0;
+ my.timer = new QTimer(this);
+ my.timer->setInterval(timerInterval());
+ my.timer->stop();
+ connect(my.timer, SIGNAL(timeout()), SLOT(timerTick()));
+
+ addTimezone(UTC);
+ displayDeltaText();
+ setControl(PmTime::StoppedState, PmTime::NormalMode);
+
+ double delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+ changeSpeed(PmTime::defaultSpeed(delta));
+ wheelSpeed->setRange(
+ PmTime::minimumSpeed(delta), PmTime::maximumSpeed(delta), 0.1);
+ wheelSpeed->setValue(PmTime::defaultSpeed(delta));
+ lineEditDelta->setAlignment(Qt::AlignRight);
+ lineEditDelta->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 3, lineEditDelta));
+ lineEditSpeed->setAlignment(Qt::AlignRight);
+ lineEditSpeed->setValidator(
+ new QDoubleValidator(0.001, INT_MAX, 1, lineEditSpeed));
+
+ my.bounds = new ShowBounds(this);
+ my.bounds->init(&my.absoluteStart, &my.pmtime.start,
+ &my.absoluteEnd, &my.pmtime.end);
+ connect(my.bounds, SIGNAL(boundsChanged()), this, SLOT(doneBounds()));
+
+ console->post("PmTimeArch::init absS=%p S=%p absE=%p E=%p\n",
+ &my.absoluteStart, &my.pmtime.start,
+ &my.absoluteEnd, &my.pmtime.end);
+}
+
+void PmTimeArch::quit()
+{
+ console->post("arch quit!\n");
+}
+
+int PmTimeArch::timerInterval()
+{
+ return (int)(((my.pmtime.delta.tv_sec * 1000) +
+ (my.pmtime.delta.tv_usec / 1000)) / my.speed);
+}
+
+void PmTimeArch::play_clicked()
+{
+ if (lineEditCtime->isModified())
+ lineEditCtime_validate();
+ if (lineEditDelta->isModified())
+ lineEditDelta_validate();
+ if (my.pmtime.state != PmTime::ForwardState ||
+ my.pmtime.mode == PmTime::StepMode)
+ play();
+}
+
+void PmTimeArch::play()
+{
+ if (addDelta()) {
+ setControl(PmTime::ForwardState, my.pmtime.mode);
+ updateTime();
+ if (my.pmtime.mode == PmTime::NormalMode)
+ my.timer->start(timerInterval());
+ else if (my.pmtime.mode == PmTime::FastMode)
+ my.timer->start(PmTime::FastModeDelay);
+ console->post(PmTime::DebugApp, "PmTimeArch::play moved time forward");
+ } else {
+ console->post(PmTime::DebugApp, "PmTimeArch::play reached archive end");
+ emit boundsPulse(&my.pmtime);
+ stop();
+ }
+}
+
+void PmTimeArch::back_clicked()
+{
+ if (lineEditCtime->isModified())
+ lineEditCtime_validate();
+ if (lineEditDelta->isModified())
+ lineEditDelta_validate();
+ if (my.pmtime.state != PmTime::BackwardState ||
+ my.pmtime.mode == PmTime::StepMode)
+ back();
+}
+
+void PmTimeArch::back()
+{
+ if (subDelta()) {
+ setControl(PmTime::BackwardState, my.pmtime.mode);
+ updateTime();
+ if (my.pmtime.mode == PmTime::NormalMode)
+ my.timer->start(timerInterval());
+ else if (my.pmtime.mode == PmTime::FastMode)
+ my.timer->start(PmTime::FastModeDelay);
+ console->post(PmTime::DebugApp, "PmTimeArch::back moved time backward");
+ } else {
+ console->post(PmTime::DebugApp, "PmTimeArch::back reached archive end");
+ emit boundsPulse(&my.pmtime);
+ stop();
+ }
+}
+
+void PmTimeArch::stop_clicked()
+{
+ if (my.pmtime.state != PmTime::StoppedState)
+ stop();
+}
+
+void PmTimeArch::stop()
+{
+ setControl(PmTime::StoppedState, my.pmtime.mode);
+ my.timer->stop();
+ emit vcrModePulse(&my.pmtime, 0);
+ console->post(PmTime::DebugApp, "PmTimeArch::stop stopped time");
+}
+
+void PmTimeArch::timerTick()
+{
+ if (my.pmtime.state == PmTime::ForwardState)
+ play();
+ else if (my.pmtime.state == PmTime::BackwardState)
+ back();
+ else
+ console->post(PmTime::DebugApp, "PmTimeArch::timerTick: dodgey state?");
+}
+
+int PmTimeArch::addDelta()
+{
+ struct timeval current = my.pmtime.position;
+
+#if DESPERATE
+ console->post(PmTime::DebugProtocol,
+ "now=%u.%u end=%u.%u start=%u.%u delta=%u.%u speed=%.3e",
+ my.pmtime.position.tv_sec, my.pmtime.position.tv_usec,
+ my.pmtime.end.tv_sec, my.pmtime.end.tv_usec, my.pmtime.start.tv_sec,
+ my.pmtime.start.tv_usec, my.pmtime.delta.tv_sec,
+ my.pmtime.delta.tv_usec, speed);
+#endif
+
+ PmTime::timevalAdd(&current, &my.pmtime.delta);
+ if (PmTime::timevalCompare(&current, &my.pmtime.end) > 0 ||
+ PmTime::timevalCompare(&current, &my.pmtime.start) < 0)
+ return 0;
+ my.pmtime.position = current;
+ return 1;
+}
+
+int PmTimeArch::subDelta()
+{
+ struct timeval current = my.pmtime.position;
+
+ PmTime::timevalSub(&current, &my.pmtime.delta);
+ if (PmTime::timevalCompare(&current, &my.pmtime.end) > 0 ||
+ PmTime::timevalCompare(&current, &my.pmtime.start) < 0)
+ return 0;
+ my.pmtime.position = current;
+ return 1;
+}
+
+void PmTimeArch::changeDelta(int value)
+{
+ my.units = (PmTime::DeltaUnits)value;
+ displayDeltaText();
+}
+
+void PmTimeArch::changeControl(int value)
+{
+ setControl(PmTime::StoppedState, (PmTime::Mode)value);
+}
+
+void PmTimeArch::updateTime()
+{
+ emit timePulse(&my.pmtime);
+ displayPositionText();
+ displayPositionSlide();
+}
+
+void PmTimeArch::displayDeltaText()
+{
+ QString text;
+ double delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+
+ delta = PmTime::secondsToUnits(delta, my.units);
+ if ((double)(int)delta == delta)
+ text.sprintf("%.2f", delta);
+ else
+ text.sprintf("%.6f", delta);
+ lineEditDelta->setText(text);
+}
+
+void PmTimeArch::displayPositionText()
+{
+ QString text;
+ char ctimebuf[32], msecbuf[5];
+
+ pmCtime(&my.pmtime.position.tv_sec, ctimebuf);
+ text = tr(ctimebuf);
+ if (my.showYear == false)
+ text.remove(19, 5);
+ if (my.showMilliseconds == true) {
+ sprintf(msecbuf, ".%03u", (uint)my.pmtime.position.tv_usec / 1000);
+ text.insert(19, msecbuf);
+ }
+ lineEditCtime->setText(text.simplified());
+}
+
+void PmTimeArch::displayPositionSlide(void)
+{
+ sliderPosition->setValue(PmTime::secondsFromTimeval(&my.pmtime.position));
+}
+
+void PmTimeArch::setPositionSlideRange(void)
+{
+ sliderPosition->setRange(PmTime::secondsFromTimeval(&my.pmtime.start),
+ PmTime::secondsFromTimeval(&my.pmtime.end));
+}
+
+void PmTimeArch::setPositionSlideDelta(void)
+{
+ sliderPosition->setStep(PmTime::secondsFromTimeval(&my.pmtime.delta));
+}
+
+void PmTimeArch::pressedPosition()
+{
+ emit vcrModePulse(&my.pmtime, 1);
+}
+
+void PmTimeArch::releasedPosition()
+{
+ emit vcrModePulse(&my.pmtime, 0);
+}
+
+void PmTimeArch::changedPosition(double value)
+{
+#if DESPERATE
+ console->post("PmTimeArch::changedPosition changing pos from %d.%d",
+ my.pmtime.position.tv_sec, my.pmtime.position.tv_usec);
+#endif
+
+ PmTime::secondsToTimeval(value, &my.pmtime.position);
+ displayPositionText();
+
+#if DESPERATE
+ console->post("PmTimeArch::changedPosition changed pos to %d.%d",
+ my.pmtime.position.tv_sec, my.pmtime.position.tv_usec);
+#endif
+}
+
+void PmTimeArch::clickShowMsec()
+{
+ if (my.showMilliseconds == true)
+ my.showMilliseconds = false;
+ else
+ my.showMilliseconds = true;
+ optionsDetailShow_MillisecondsAction->setChecked(my.showMilliseconds);
+ displayPositionText();
+}
+
+void PmTimeArch::clickShowYear()
+{
+ if (my.showYear == true)
+ my.showYear = false;
+ else
+ my.showYear = true;
+ optionsDetailShow_YearAction->setChecked(my.showYear);
+ displayPositionText();
+}
+
+void PmTimeArch::resetSpeed()
+{
+ double delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+ changeSpeed(PmTime::defaultSpeed(delta));
+}
+
+void PmTimeArch::changeSpeed(double value)
+{
+ QString text;
+ int reset = my.timer->isActive();
+ double upper, lower, delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+
+ my.timer->stop();
+
+ upper = PmTime::maximumSpeed(delta);
+ lower = PmTime::minimumSpeed(delta);
+ if (value > upper)
+ value = upper;
+ else if (value < lower)
+ value = lower;
+ text.sprintf("%.1f", value);
+ lineEditSpeed->setText(text);
+ if (wheelSpeed->value() != value)
+ wheelSpeed->setValue(value);
+
+ my.speed = value;
+ if (reset)
+ my.timer->start(timerInterval());
+
+ console->post("PmTimeArch::changeSpeed changed delta to %d.%d (%.2fs)",
+ my.pmtime.delta.tv_sec, my.pmtime.delta.tv_usec, value);
+}
+
+void PmTimeArch::showBounds()
+{
+ my.bounds->reset();
+ console->post("PmTimeArch::showBounds: absS=%p S=%p absE=%p E=%p\n",
+ &my.absoluteStart, &my.pmtime.start, &my.absoluteEnd, &my.pmtime.end);
+ my.bounds->show();
+}
+
+void PmTimeArch::doneBounds(void)
+{
+ int tellclients = 0;
+
+ console->post("PmTimeArch::doneBounds signal received\n");
+
+ my.bounds->flush();
+ if (PmTime::timevalCompare(&my.pmtime.position, &my.pmtime.start) < 0) {
+ my.pmtime.position = my.pmtime.start;
+ tellclients = 1;
+ }
+ if (PmTime::timevalCompare(&my.pmtime.position, &my.pmtime.end) > 0) {
+ my.pmtime.position = my.pmtime.end;
+ tellclients = 1;
+ }
+ sliderPosition->blockSignals(true);
+ setPositionSlideRange();
+ sliderPosition->blockSignals(false);
+ if (tellclients)
+ emit vcrModePulse(&my.pmtime, 0);
+}
+
+void PmTimeArch::disableConsole()
+{
+ optionsShowConsoleAction->setVisible(false);
+}
+
+void PmTimeArch::lineEditDelta_changed(const QString &)
+{
+ if (lineEditDelta->isModified())
+ stop_clicked();
+}
+
+void PmTimeArch::lineEditCtime_changed(const QString &)
+{
+ if (lineEditCtime->isModified())
+ stop_clicked();
+}
+
+void PmTimeArch::lineEditDelta_validate()
+{
+ double delta;
+ bool ok, reset = my.timer->isActive();
+
+ delta = lineEditDelta->text().toDouble(&ok);
+ if (!ok || delta <= 0) {
+ displayDeltaText(); // reset to previous, known-good delta
+ } else {
+ my.timer->stop();
+ delta = PmTime::unitsToSeconds(delta, my.units);
+ PmTime::secondsToTimeval(delta, &my.pmtime.delta);
+ emit vcrModePulse(&my.pmtime, 0);
+ if (reset)
+ my.timer->start(timerInterval());
+ }
+}
+
+void PmTimeArch::lineEditCtime_validate()
+{
+ struct timeval current;
+ QString input, error;
+ char *msg;
+
+ input = lineEditCtime->text().simplified();
+ if (input.length() == 0) {
+ error.sprintf("Position time has not been set.\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+ if (input[0] != '@')
+ input.prepend("@");
+ if (__pmParseTime(input.toAscii(),
+ &my.pmtime.start, &my.pmtime.end, &current, &msg) < 0) {
+ error.sprintf("Invalid position date/time:\n\n%s\n", msg);
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ displayPositionText(); // reset to previous, known-good position
+ free(msg);
+ } else {
+ my.pmtime.position = current;
+ displayPositionText();
+ displayPositionSlide();
+ emit vcrModePulse(&my.pmtime, 0);
+ }
+}
+
+void PmTimeArch::lineEditSpeed_validate()
+{
+ double value, delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+ bool ok, reset = my.timer->isActive();
+
+ value = lineEditSpeed->text().toDouble(&ok);
+ if (!ok ||
+ value < PmTime::minimumSpeed(delta) ||
+ value > PmTime::maximumSpeed(delta)) {
+ wheelSpeed->setValue(my.speed); // reset to previous, known-good speed
+ } else {
+ my.speed = value;
+ wheelSpeed->setValue(my.speed);
+ if (reset) {
+ my.timer->stop();
+ my.timer->start(timerInterval());
+ }
+ }
+}
+
+void PmTimeArch::setTimezone(QAction *action)
+{
+ for (int i = 0; i < my.tzlist.size(); i++) {
+ TimeZone *tz = my.tzlist.at(i);
+ if (tz->action() == action) {
+ my.first = true; // resetting time completely
+ pmUseZone(tz->handle());
+ emit tzPulse(&my.pmtime, tz->tz(), strlen(tz->tz()) + 1,
+ tz->tzlabel(), strlen(tz->tzlabel()) + 1);
+ console->post("PmTimeArch::setTimezone sent TZ %s (%s) to clients",
+ tz->tz(), tz->tzlabel());
+ setTime(&my.pmtime, NULL); // re-display the time, no messages
+ break;
+ }
+ }
+}
+
+void PmTimeArch::addTimezone(const char *string)
+{
+ TimeZone *tmp, *tzp;
+ QAction *tzAction;
+ char *label, *tz;
+ int handle;
+
+ if ((handle = pmNewZone(string)) < 0)
+ return;
+
+ if ((tz = strdup(string)) == NULL)
+ return;
+
+ if ((label = strdup(string + strlen(string) + 1)) == NULL) {
+ free(tz);
+ return;
+ }
+
+ for (int i = 0; i < my.tzlist.size(); i++) {
+ tmp = my.tzlist.at(i);
+ if (strcmp(tmp->tzlabel(), label) == 0) {
+ free(label);
+ free(tz);
+ return;
+ }
+ }
+
+ tzAction = new QAction(this);
+ tzAction->setCheckable(true);
+ tzAction->setToolTip(tz);
+ tzAction->setText(label);
+
+ tzp = new TimeZone(tz, label, tzAction, handle);
+ my.tzlist.append(tzp);
+
+ if (!my.tzActions) {
+ my.tzActions = new QActionGroup(this);
+ connect(my.tzActions, SIGNAL(selected(QAction *)),
+ this, SLOT(setTimezone(QAction *)));
+ }
+ my.tzActions->addAction(tzAction);
+ optionsTimezoneAction->addActions(my.tzActions->actions());
+ console->post("PmTimeArch::addTimezone added tz=%s label=%s", tz, label);
+}
+
+void PmTimeArch::setTime(PmTime::Packet *k, char *tzdata)
+{
+#if DESPERATE
+ console->post(PmTime::DebugProtocol, "PmTimeArch::setTime START: "
+ "1st=%d now=%u.%u end=%u.%u start=%u.%u delta=%u.%u",
+ my.first, my.pmtime.position.tv_sec, my.pmtime.position.tv_usec,
+ my.pmtime.end.tv_sec, my.pmtime.end.tv_usec, my.pmtime.start.tv_sec,
+ my.pmtime.start.tv_usec, my.pmtime.delta.tv_sec,
+ my.pmtime.delta.tv_usec);
+#endif
+
+ if (my.first == true) {
+ my.first = false;
+ if (tzdata != NULL)
+ addTimezone(tzdata);
+ my.absoluteStart = my.pmtime.start = k->start;
+ my.absoluteEnd = my.pmtime.end = k->end;
+ my.pmtime.position = k->position;
+ my.pmtime.delta = k->delta;
+ sliderPosition->blockSignals(true);
+ setPositionSlideRange();
+ setPositionSlideDelta();
+ sliderPosition->blockSignals(false);
+ displayDeltaText();
+ displayPositionText();
+ displayPositionSlide();
+ my.bounds->reset();
+ double delta = PmTime::secondsFromTimeval(&k->delta);
+ changeSpeed(PmTime::defaultSpeed(delta));
+ } else {
+ addBound(k, tzdata);
+ }
+
+#if DESPERATE
+ console->post(PmTime::DebugProtocol, "PmTimeArch::setTime ENDED: "
+ "1st=%d now=%u.%u end=%u.%u start=%u.%u delta=%u.%u",
+ my.first, my.pmtime.position.tv_sec, my.pmtime.position.tv_usec,
+ my.pmtime.end.tv_sec, my.pmtime.end.tv_usec, my.pmtime.start.tv_sec,
+ my.pmtime.start.tv_usec, my.pmtime.delta.tv_sec,
+ my.pmtime.delta.tv_usec);
+#endif
+}
+
+void PmTimeArch::addBound(PmTime::Packet *k, char *tzdata)
+{
+ // Note: pmchart can start pmtime up without an archive
+ // so, we need to explicitly initialise some fields now
+ // that one might otherwise have expected to be setup.
+
+ bool needPulse = PmTime::timevalNonZero(&my.pmtime.position);
+
+ console->post(PmTime::DebugProtocol, "PmTimeArch::addBound START: "
+ "p?=%d now=%u.%u end=%u.%u start=%u.%u", needPulse,
+ my.pmtime.position.tv_sec, my.pmtime.position.tv_usec,
+ my.pmtime.end.tv_sec, my.pmtime.end.tv_usec,
+ my.pmtime.start.tv_sec, my.pmtime.start.tv_usec);
+
+ if (tzdata != NULL)
+ addTimezone(tzdata);
+
+ if (PmTime::timevalCompare(&k->start, &my.absoluteStart) < 0)
+ my.absoluteStart = my.pmtime.start = k->start;
+ if (PmTime::timevalCompare(&k->end, &my.absoluteEnd) > 0)
+ my.absoluteEnd = my.pmtime.end = k->end;
+ if (!needPulse) { // first-time archive initialisation
+ my.pmtime.position = k->position;
+ my.pmtime.start = k->start;
+ my.pmtime.end = k->end;
+ }
+
+ sliderPosition->blockSignals(true);
+ setPositionSlideRange();
+ sliderPosition->blockSignals(false);
+ displayPositionText();
+ displayPositionSlide();
+ my.bounds->reset();
+
+ if (needPulse)
+ emit vcrModePulse(&my.pmtime, 0);
+
+ console->post(PmTime::DebugProtocol, "PmTimeArch::addBound ENDED: "
+ "p?=%d now=%u.%u end=%u.%u start=%u.%u", needPulse,
+ my.pmtime.position.tv_sec, my.pmtime.position.tv_usec,
+ my.pmtime.end.tv_sec, my.pmtime.end.tv_usec,
+ my.pmtime.start.tv_sec, my.pmtime.start.tv_usec);
+}
diff --git a/src/pmtime/pmtimearch.h b/src/pmtime/pmtimearch.h
new file mode 100644
index 0000000..3c06043
--- /dev/null
+++ b/src/pmtime/pmtimearch.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMTIMEARCH_H
+#define PMTIMEARCH_H
+
+#include "ui_pmtimearch.h"
+#include "pmtime.h"
+#include "console.h"
+#include "showboundsdialog.h"
+#include "timezone.h"
+
+class PmTimeArch : public PmTime, public Ui::PmTimeArch
+{
+ Q_OBJECT
+
+public:
+ PmTimeArch();
+
+ virtual void play();
+ virtual void back();
+ virtual void stop();
+ virtual int addDelta();
+ virtual int subDelta();
+ virtual int timerInterval();
+ virtual void displayDeltaText();
+ virtual void displayPositionText();
+ virtual void displayPositionSlide();
+ virtual void setPositionSlideRange();
+ virtual void setPositionSlideDelta();
+ virtual void addTimezone(const char *string);
+ virtual void setTime(PmTime::Packet *k, char *tzdata);
+ virtual void addBound(PmTime::Packet *k, char *tzdata);
+
+public slots:
+ virtual void setControl(PmTime::State newstate, PmTime::Mode newmode);
+ virtual void init();
+ virtual void quit();
+ virtual void play_clicked();
+ virtual void back_clicked();
+ virtual void stop_clicked();
+ virtual void timerTick();
+ virtual void changeDelta(int value);
+ virtual void disableConsole();
+ virtual void changeControl(int value);
+ virtual void updateTime();
+ virtual void pressedPosition();
+ virtual void releasedPosition();
+ virtual void changedPosition(double value);
+ virtual void clickShowMsec();
+ virtual void clickShowYear();
+ virtual void resetSpeed();
+ virtual void changeSpeed(double value);
+ virtual void showBounds();
+ virtual void doneBounds();
+ virtual void lineEditDelta_changed(const QString & s);
+ virtual void lineEditCtime_changed(const QString & s);
+ virtual void lineEditDelta_validate();
+ virtual void lineEditCtime_validate();
+ virtual void lineEditSpeed_validate();
+ virtual void setTimezone(QAction *action);
+
+signals:
+ void timePulse(PmTime::Packet *);
+ void boundsPulse(PmTime::Packet *);
+ void vcrModePulse(PmTime::Packet *, int);
+ void tzPulse(PmTime::Packet *, char *, int, char *, int);
+
+private:
+ struct {
+ PmTime::Packet pmtime;
+ PmTime::DeltaUnits units;
+ struct timeval absoluteStart;
+ struct timeval absoluteEnd;
+ double speed;
+
+ bool first;
+ bool showMilliseconds;
+ bool showYear;
+
+ QTimer *timer;
+ Console *console;
+ ShowBounds *bounds;
+ QActionGroup *tzActions;
+ QList<TimeZone*> tzlist;
+ } my;
+};
+
+#endif // PMTIMEARCH_H
diff --git a/src/pmtime/pmtimearch.ui b/src/pmtime/pmtimearch.ui
new file mode 100644
index 0000000..cddc526
--- /dev/null
+++ b/src/pmtime/pmtimearch.ui
@@ -0,0 +1,1262 @@
+<ui version="4.0" >
+ <class>PmTimeArch</class>
+ <widget class="QMainWindow" name="PmTimeArch" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>220</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>320</width>
+ <height>220</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>320</width>
+ <height>220</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>PCP Archive Time Control</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="windowIconText" >
+ <string/>
+ </property>
+ <property name="toolTip" >
+ <string/>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Archive Time Control&lt;/b>&lt;br>
+A time server, sending time related information (current time,
+metric value fetch interval, timezone) to one or more client
+programs (e.g. pmchart, pmval, etc).</string>
+ </property>
+ <widget class="QWidget" name="widget" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="intervalLabel" >
+ <property name="toolTip" >
+ <string comment="Time interval between performance metric value samples" >Metric sampling interval</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Metric sampling interval&lt;/b>&lt;br>
+Numeric values entered into these fields will form the number of
+hours, minutes and/or seconds between PCP metric value
+samples in Time Control client programs (e.g. pmchart, pmval, etc).</string>
+ </property>
+ <property name="text" >
+ <string>Interval</string>
+ </property>
+ <property name="scaledContents" >
+ <bool>false</bool>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="positionLabel" >
+ <property name="whatsThis" >
+ <string>&lt;b>Time Position&lt;/b>&lt;br>
+This text field displays the current time position. This value is
+sent to clients (e.g. pmchart, pmval) of this Time Control
+program to synchronise them in terms of the progression of time
+and also with respect to any "play mode" changes requested.</string>
+ </property>
+ <property name="text" >
+ <string>Position</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="lineEditDelta" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>95</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>95</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string comment="Time interval between performance metric value samples" >Metric sampling interval</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Metric sampling interval&lt;/b>&lt;br>
+Numeric values entered into these fields will form the number of
+hours, minutes and/or seconds between PCP metric value
+samples in Time Control client programs (e.g. pmchart, pmval, etc).</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxDelta" >
+ <property name="whatsThis" >
+ <string>&lt;b>Sampling interval units&lt;/b>&lt;br>
+Displays the units in which the sampling interval is entered.
+Metric values can be sampled in days, hours, minutes, seconds
+or milliseconds in Time Control client programs (e.g. pmchart,
+pmval, etc).</string>
+ </property>
+ <property name="currentIndex" >
+ <number>1</number>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditCtime" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>226</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>226</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Time Position&lt;/b>&lt;br>
+This text field displays the current time position. This value is
+sent to clients (e.g. pmchart, pmval) of this Time Control
+program to synchronise them in terms of the progression of time
+and also with respect to any "play mode" changes requested.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QwtSlider" name="sliderPosition" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>290</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>290</width>
+ <height>14</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Time Position&lt;/b>&lt;br>
+This slider modifies the current time position. This value is
+sent to clients (e.g. pmchart, pmval) of this Time Control
+program to synchronise them in terms of the progression of time
+and also with respect to any "play mode" changes requested.</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="backgroundStyle" >
+ <enum>QwtSlider::Groove</enum>
+ </property>
+ <property name="handleSize" >
+ <size>
+ <width>12</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="borderWidth" >
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>8</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>4</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel3" >
+ <property name="text" >
+ <string>Archive Control</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxSpeed" >
+ <property name="currentIndex" >
+ <number>1</number>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Step</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Normal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Fast</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QToolButton" name="buttonBack" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>54</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Back/Step/Rewind&lt;/b>&lt;br>
+Pressing this button will cause time to decrease, as seen by the
+Time Control program and all of its clients (e.g. pmchart, pmval,
+etc). In Backward mode, time will decrease in decrements of the
+current Interval (subject to Speed setting). In Step mode, time
+will decrease one Interval and then wait. In Rewind mode, time
+will move backward at a very rapid rate in decrements of the current
+time Interval.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>52</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="buttonStop" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>54</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Stop&lt;/b>&lt;br>
+Pressing this button will stop the movement of time, as seen by
+the Time Control program and all of its clients (e.g. pmchart,
+pmval, etc). Unless stopped, time either advances/retreats in
+increments of the current Interval.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>52</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="buttonPlay" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>54</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Play/Step/FastFwd&lt;/b>&lt;br>
+Pressing this button will begin the advancement of time, as seen
+by the Time Control program and all of its clients (e.g. pmchart,
+pmval, etc). In Play mode, time will advance in increments of the
+current Interval (subject to Speed setting). In Step mode, time
+will advance one Interval and then wait. In Fast Forward mode, time
+will move forward at a very rapid rate in increments of the current
+time Interval.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>52</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabelSpeed" >
+ <property name="whatsThis" >
+ <string>&lt;b>Time Speed&lt;/b>&lt;br>
+The Speed at which Time clients are updated with respect to the current
+time Position can be modified here. This only affects normal play/back
+modes.</string>
+ </property>
+ <property name="text" >
+ <string>Speed</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditSpeed" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>45</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>45</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Time Speed&lt;/b>&lt;br>
+The Speed at which Time clients are updated with respect to the current
+time Position can be modified here. This only affects normal play/back
+modes.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="buttonSpeed" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>16</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Speed Reset&lt;/b>&lt;br>
+The Speed at which Time clients are updated with respect to the current
+time Position can be reset (to one) here. This affects normal play/back
+modes only.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QwtWheel" name="wheelSpeed" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>155</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>155</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Speed Wheel&lt;/b>&lt;br>
+The Speed at which Time clients are updated with respect to the current
+time Position can be modified by rotating the wheel left (slower) and
+right (faster). This only affects normal play/back modes.</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="wheelBorderWidth" >
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="MenuBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="File" >
+ <property name="title" >
+ <string>&amp;File</string>
+ </property>
+ <addaction name="fileHideAction" />
+ </widget>
+ <widget class="QMenu" name="Help" >
+ <property name="title" >
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="helpManualAction" />
+ <addaction name="separator" />
+ <addaction name="helpAboutAction" />
+ <addaction name="helpAboutQtAction" />
+ <addaction name="helpSeeAlsoAction" />
+ <addaction name="separator" />
+ <addaction name="helpWhats_ThisAction" />
+ </widget>
+ <widget class="QMenu" name="Options" >
+ <property name="title" >
+ <string>&amp;Options</string>
+ </property>
+ <widget class="QMenu" name="optionsDetailAction" >
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail&lt;/b>&lt;br>
+Click this menu item to allow extended precision in time
+reporting to be enabled. Optionally, milliseconds and year can
+be displayed in the current time position, and days and milliseconds
+can be displayed in the current time interval.</string>
+ </property>
+ <property name="title" >
+ <string>&amp;Detail</string>
+ </property>
+ <addaction name="optionsDetailShow_MillisecondsAction" />
+ <addaction name="optionsDetailShow_YearAction" />
+ </widget>
+ <widget class="QMenu" name="optionsTimezoneAction" >
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Timezone&lt;/b>&lt;br>
+Click on this menu item to display a list of available timezones,
+from which to choose. The timezone selected will be made the
+new reporting timezone for all client programs (e.g. pmchart,
+pmval, etc) and affects the display of timestamps in the clients
+and also in the Time Control program itself.</string>
+ </property>
+ <property name="title" >
+ <string>&amp;Timezone</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/internet-web-browser.png</iconset>
+ </property>
+ </widget>
+ <addaction name="optionsTimezoneAction" />
+ <addaction name="optionsDetailAction" />
+ <addaction name="optionsShowBoundsAction" />
+ <addaction name="optionsShowConsoleAction" />
+ </widget>
+ <addaction name="File" />
+ <addaction name="Options" />
+ <addaction name="separator" />
+ <addaction name="Help" />
+ </widget>
+ <action name="helpAboutAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;About</string>
+ </property>
+ <property name="iconText" >
+ <string>About pmtime</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|About&lt;/b>&lt;br>
+Click this menu option to bring up the About &lt;i>pmtime&lt;/i> dialog with version, author, and licensing information.</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </action>
+ <action name="fileHideAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/edit-clear.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Hide</string>
+ </property>
+ <property name="iconText" >
+ <string>Hide Archive Time Control window</string>
+ </property>
+ <property name="toolTip" >
+ <string>Hide Archive Time Control window</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>File|Hide&lt;/b>&lt;br>
+Click on this menu option to dismiss the Time Control window,
+for the time being. As the Time Control serves one or
+more client programs (which are completely reliant on the Time
+Controls), this action does not end the Time Control program.
+Rather, it simply removes the window from the visible set.&lt;br>
+The client programs will typically provide a mechanism for making
+the Time Controls visible.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+H</string>
+ </property>
+ </action>
+ <action name="optionsDetailShow_MillisecondsAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Show &amp;Milliseconds</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Milliseconds in Time</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail|Milliseconds&lt;/b>&lt;br>
+Display millisecond precision (or not) in the current time interval and
+position.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+M</string>
+ </property>
+ </action>
+ <action name="optionsDetailShow_YearAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Show &amp;Year</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Year in Position</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail|Year&lt;/b>&lt;br>
+Display the year (or not) in the current time position.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Y</string>
+ </property>
+ </action>
+ <action name="optionsShowBoundsAction" >
+ <property name="text" >
+ <string>&amp;Boundaries</string>
+ </property>
+ <property name="iconText" >
+ <string>Archive Time Boundaries</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Option|Show Console&lt;/b>&lt;br>
+Click here to display the Time Boundaries dialog, which allows the
+archive time boundaries (as seen by pmtime clients, like pmval and
+pmchart) to be set.
+Initially these are set to the start and end time of the archive
+(or earliest start and latest end, if multiple archives present),
+but it can be further restricted as needed here.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+B</string>
+ </property>
+ </action>
+ <action name="optionsShowConsoleAction" >
+ <property name="text" >
+ <string>&amp;Show Console</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Diagnostic Console</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Option|Show Console&lt;/b>&lt;br>
+Click here to display the diagnostic console with whatever debug
+information has been sent there. This menu option is available
+only when the -D command line option is in use, which allows a
+level of runtime verbosity to be configured (default is "none").</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+C</string>
+ </property>
+ </action>
+ <action name="helpAboutQtAction" >
+ <property name="text" >
+ <string>About Qt</string>
+ </property>
+ <property name="iconText" >
+ <string>About Qt</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|About Qt&lt;/b>&lt;br>
+Click on this menu option to bring up the See Also dialog, with author and website information about Qt - which provides software infrastructure on which this tool was built.</string>
+ </property>
+ </action>
+ <action name="helpSeeAlsoAction" >
+ <property name="text" >
+ <string>See Also</string>
+ </property>
+ <property name="iconText" >
+ <string>See Also</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|See Also&lt;/b>&lt;br>
+Click on this menu option to bring up the See Also dialog, with author and website information about PCP, Qwt, and Qt - all of which provide software infrastructure on which this tool was built.</string>
+ </property>
+ </action>
+ <action name="helpWhats_ThisAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/whatsthis.png</iconset>
+ </property>
+ <property name="text" >
+ <string>What's This?</string>
+ </property>
+ <property name="iconText" >
+ <string>"What's This?" context sensitive help</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|What's This&lt;/b>&lt;br>
+Click this menu option to invoke a small question mark that is attached to the mouse pointer. Click on a feature which you would like more information about. A popup box appears with more information about the feature.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Shift+F1</string>
+ </property>
+ </action>
+ <action name="helpManualAction" >
+ <property name="text" >
+ <string>&amp;Manual</string>
+ </property>
+ <property name="iconText" >
+ <string>Manual</string>
+ </property>
+ <property name="shortcut" >
+ <string>F1</string>
+ </property>
+ </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11" />
+ <customwidgets>
+ <customwidget>
+ <class>QwtSlider</class>
+ <extends>QWidget</extends>
+ <header>qwt_slider.h</header>
+ </customwidget>
+ <customwidget>
+ <class>QwtWheel</class>
+ <extends>QWidget</extends>
+ <header>qwt_wheel.h</header>
+ </customwidget>
+ </customwidgets>
+ <includes>
+ <include location="local" >pmtime.h</include>
+ <include location="local" >console.h</include>
+ <include location="local" >timezone.h</include>
+ <include location="local" >showboundsdialog.h</include>
+ </includes>
+ <resources>
+ <include location="pmtime.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>fileHideAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>hideWindow()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpAboutAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>helpAbout()</slot>
+ </connection>
+ <connection>
+ <sender>helpAboutQtAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>helpAboutQt()</slot>
+ </connection>
+ <connection>
+ <sender>helpSeeAlsoAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>helpSeeAlso()</slot>
+ </connection>
+ <connection>
+ <sender>buttonBack</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>back_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonStop</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>stop_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonPlay</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>play_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsDetailShow_MillisecondsAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>clickShowMsec()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsDetailShow_YearAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>clickShowYear()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>comboBoxSpeed</sender>
+ <signal>activated(int)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>changeControl(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sliderPosition</sender>
+ <signal>sliderPressed()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>pressedPosition()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sliderPosition</sender>
+ <signal>sliderReleased()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>releasedPosition()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sliderPosition</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>changedPosition(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsShowBoundsAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>showBounds()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsShowConsoleAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>showConsole()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditCtime</sender>
+ <signal>returnPressed()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditCtime_validate()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditCtime</sender>
+ <signal>textChanged(QString)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditCtime_changed(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditSpeed</sender>
+ <signal>returnPressed()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditSpeed_validate()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditSpeed</sender>
+ <signal>editingFinished()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditSpeed_validate()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpWhats_ThisAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>whatsThis()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>wheelSpeed</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>changeSpeed(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonSpeed</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>resetSpeed()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>comboBoxDelta</sender>
+ <signal>activated(int)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>changeDelta(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditDelta</sender>
+ <signal>returnPressed()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditDelta_validate()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditDelta</sender>
+ <signal>textChanged(QString)</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>lineEditDelta_changed(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpManualAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeArch</receiver>
+ <slot>helpManual()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/pmtimelive.cpp b/src/pmtime/pmtimelive.cpp
new file mode 100644
index 0000000..cc480c3
--- /dev/null
+++ b/src/pmtime/pmtimelive.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmtimelive.h"
+
+#include <QtCore/QTimer>
+#include <QtGui/QValidator>
+#include <QtGui/QActionGroup>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "pmtime.h"
+#include "aboutdialog.h"
+#include "seealsodialog.h"
+
+PmTimeLive::PmTimeLive() : PmTime()
+{
+ setupUi(this);
+#ifdef Q_OS_MAC // fixup after relocation of the MenuBar by Qt
+ QSize size = QSize(width(), height() - MenuBar->height());
+ setMinimumSize(size);
+ setMaximumSize(size);
+#endif
+}
+
+typedef struct {
+ PmTime::State state;
+ QIcon *stop;
+ QIcon *play;
+} IconStateMap;
+
+static void setup(IconStateMap *map, PmTime::State state,
+ QIcon *stop, QIcon *play)
+{
+ map->state = state;
+ map->stop = stop;
+ map->play = play;
+}
+
+void PmTimeLive::setControl(PmTime::State state)
+{
+ static IconStateMap maps[2];
+ static int nmaps;
+
+ if (!nmaps) {
+ nmaps = sizeof(maps) / sizeof(maps[0]);
+ setup(&maps[0], PmTime::StoppedState,
+ PmTime::icon(PmTime::StoppedOn),
+ PmTime::icon(PmTime::ForwardOff));
+ setup(&maps[1], PmTime::ForwardState,
+ PmTime::icon(PmTime::StoppedOff),
+ PmTime::icon(PmTime::ForwardOn));
+ }
+
+ if (my.pmtime.state != state) {
+ for (int i = 0; i < nmaps; i++) {
+ if (maps[i].state == state) {
+ buttonStop->setIcon(*maps[i].stop);
+ buttonPlay->setIcon(*maps[i].play);
+ break;
+ }
+ }
+ my.pmtime.state = state;
+ }
+}
+
+void PmTimeLive::init()
+{
+ static const char *UTC = "UTC\0Universal Coordinated Time";
+
+ console->post("Starting Live Time Control...");
+
+ my.units = PmTime::Seconds;
+ my.first = true;
+ my.tzActions = NULL;
+
+ memset(&my.pmtime, 0, sizeof(my.pmtime));
+ my.pmtime.source = PmTime::HostSource;
+ my.pmtime.delta.tv_sec = PmTime::DefaultDelta;
+
+ my.showMilliseconds = false;
+ optionsDetailShow_MillisecondsAction->setChecked(my.showMilliseconds);
+ my.showYear = false;
+ optionsDetailShow_YearAction->setChecked(my.showYear);
+
+ addTimezone(UTC);
+ displayPosition();
+ displayDeltaText();
+ setControl(PmTime::ForwardState);
+
+ my.timer = new QTimer(this);
+ connect(my.timer, SIGNAL(timeout()), SLOT(updateTime()));
+ my.timer->start(timerInterval());
+ lineEditDelta->setAlignment(Qt::AlignRight);
+ lineEditDelta->setValidator(new QDoubleValidator
+ (0.001, INT_MAX, 3, lineEditDelta));
+}
+
+void PmTimeLive::quit()
+{
+ console->post("live quit!\n");
+}
+
+int PmTimeLive::timerInterval()
+{
+ return (int)((my.pmtime.delta.tv_sec * 1000) +
+ (my.pmtime.delta.tv_usec / 1000));
+}
+
+void PmTimeLive::play_clicked()
+{
+ if (lineEditDelta->isModified())
+ lineEditDelta_validate();
+ if (my.pmtime.state != PmTime::ForwardState)
+ play();
+}
+
+void PmTimeLive::play()
+{
+ console->post("PmTimeLive::play");
+ setControl(PmTime::ForwardState);
+ __pmtimevalNow(&my.pmtime.position);
+ displayPosition();
+ emit vcrModePulse(&my.pmtime, 0);
+ if (!my.timer->isActive())
+ my.timer->start(timerInterval());
+}
+
+void PmTimeLive::stop_clicked()
+{
+ if (my.pmtime.state != PmTime::StoppedState)
+ stop();
+}
+
+void PmTimeLive::stop()
+{
+ console->post("PmTimeLive::stop stopped time");
+ setControl(PmTime::StoppedState);
+ my.timer->stop();
+ __pmtimevalNow(&my.pmtime.position);
+ emit vcrModePulse(&my.pmtime, 0);
+}
+
+void PmTimeLive::updateTime()
+{
+ __pmtimevalNow(&my.pmtime.position);
+ displayPosition();
+ emit timePulse(&my.pmtime);
+}
+
+void PmTimeLive::displayPosition()
+{
+ QString text;
+ char ctimebuf[32], msecbuf[5];
+
+ pmCtime(&my.pmtime.position.tv_sec, ctimebuf);
+ text = tr(ctimebuf);
+ if (my.showYear == false)
+ text.remove(19, 5);
+ if (my.showMilliseconds == true) {
+ sprintf(msecbuf, ".%03u", (uint)my.pmtime.position.tv_usec / 1000);
+ text.insert(19, msecbuf);
+ }
+ lineEditCtime->setText(text.simplified());
+}
+
+void PmTimeLive::clickShowMsec()
+{
+ if (my.showMilliseconds == true)
+ my.showMilliseconds = false;
+ else
+ my.showMilliseconds = true;
+ optionsDetailShow_MillisecondsAction->setChecked(my.showMilliseconds);
+ displayPosition();
+}
+
+void PmTimeLive::clickShowYear()
+{
+ if (my.showYear == true)
+ my.showYear = false;
+ else
+ my.showYear = true;
+ optionsDetailShow_YearAction->setChecked(my.showYear);
+ displayPosition();
+}
+
+void PmTimeLive::changeDelta(int value)
+{
+ my.units = (PmTime::DeltaUnits)value;
+ displayDeltaText();
+}
+
+void PmTimeLive::displayDeltaText()
+{
+ QString text;
+ double delta = PmTime::secondsFromTimeval(&my.pmtime.delta);
+
+ delta = PmTime::secondsToUnits(delta, my.units);
+ if ((double)(int)delta == delta)
+ text.sprintf("%.2f", delta);
+ else
+ text.sprintf("%.6f", delta);
+ lineEditDelta->setText(text);
+}
+
+void PmTimeLive::disableConsole()
+{
+ optionsShowConsoleAction->setVisible(false);
+}
+
+void PmTimeLive::lineEditDelta_changed(const QString &)
+{
+ if (lineEditDelta->isModified())
+ stop_clicked();
+}
+
+void PmTimeLive::lineEditDelta_validate()
+{
+ double delta;
+ bool ok, reset = my.timer->isActive();
+
+ delta = lineEditDelta->text().toDouble(&ok);
+ if (!ok || delta <= 0) {
+ displayDeltaText(); // reset to previous, known-good delta
+ } else {
+ my.timer->stop();
+ delta = PmTime::unitsToSeconds(delta, my.units);
+ PmTime::secondsToTimeval(delta, &my.pmtime.delta);
+ emit vcrModePulse(&my.pmtime, 0);
+ if (reset)
+ my.timer->start(timerInterval());
+ }
+}
+
+void PmTimeLive::setTimezone(QAction *action)
+{
+ for (int i = 0; i < my.tzlist.size(); i++) {
+ TimeZone *tz = my.tzlist.at(i);
+ if (tz->action() == action) {
+ pmUseZone(tz->handle());
+ emit tzPulse(&my.pmtime, tz->tz(), strlen(tz->tz()) + 1,
+ tz->tzlabel(), strlen(tz->tzlabel()) + 1);
+ setTime(&my.pmtime, NULL); // re-display the time, no messages
+ console->post("PmTimeLive::setTimezone sent TZ %s(%s) to clients\n",
+ tz->tz(), tz->tzlabel());
+ break;
+ }
+ }
+}
+
+void PmTimeLive::addTimezone(const char *string)
+{
+ TimeZone *tmp, *tzp;
+ QAction *tzAction;
+ char *label, *tz;
+ int handle;
+
+ if ((handle = pmNewZone(string)) < 0)
+ return;
+
+ if ((tz = strdup(string)) == NULL)
+ return;
+
+ if ((label = strdup(string + strlen(string) + 1)) == NULL) {
+ free(tz);
+ return;
+ }
+
+ for (int i = 0; i < my.tzlist.size(); i++) {
+ tmp = my.tzlist.at(i);
+ if (strcmp(tmp->tzlabel(), label) == 0) {
+ free(label);
+ free(tz);
+ return;
+ }
+ }
+
+ tzAction = new QAction(this);
+ tzAction->setCheckable(true);
+ tzAction->setToolTip(tz);
+ tzAction->setText(label);
+
+ tzp = new TimeZone(tz, label, tzAction, handle);
+ my.tzlist.append(tzp);
+
+ if (!my.tzActions) {
+ my.tzActions = new QActionGroup(this);
+ connect(my.tzActions, SIGNAL(selected(QAction *)) , this,
+ SLOT(setTimezone(QAction *)));
+ }
+ my.tzActions->addAction(tzAction);
+ optionsTimezoneAction->addActions(my.tzActions->actions());
+ console->post("PmTimeLive::addTimezone added TZ=%s label=%s", tz, label);
+}
+
+void PmTimeLive::setTime(PmTime::Packet *k, char *tzdata)
+{
+ if (tzdata != NULL)
+ addTimezone(tzdata);
+
+ if (my.first == true) {
+ bool reset = my.timer->isActive();
+
+ my.first = false;
+ my.pmtime.position = k->position;
+ my.pmtime.delta = k->delta;
+ my.timer->stop();
+ if (reset)
+ my.timer->start(timerInterval());
+ displayDeltaText();
+ displayPosition();
+ }
+}
diff --git a/src/pmtime/pmtimelive.h b/src/pmtime/pmtimelive.h
new file mode 100644
index 0000000..2a4835b
--- /dev/null
+++ b/src/pmtime/pmtimelive.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2012, Red Hat.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ * Copyright (c) 2006-2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMTIMELIVE_H
+#define PMTIMELIVE_H
+
+#include "ui_pmtimelive.h"
+#include "pmtime.h"
+#include "console.h"
+#include "timezone.h"
+
+class PmTimeLive : public PmTime, public Ui::PmTimeLive
+{
+ Q_OBJECT
+
+public:
+ PmTimeLive();
+
+ virtual void play();
+ virtual void stop();
+ virtual int timerInterval();
+ virtual void displayPosition();
+ virtual void displayDeltaText();
+ virtual void addTimezone(const char *string);
+ virtual void setTime(PmTime::Packet *k, char *tzdata);
+
+public slots:
+ virtual void setControl(PmTime::State newstate);
+ virtual void init();
+ virtual void quit();
+ virtual void play_clicked();
+ virtual void stop_clicked();
+ virtual void updateTime();
+ virtual void clickShowMsec();
+ virtual void clickShowYear();
+ virtual void changeDelta(int value);
+ virtual void disableConsole();
+ virtual void lineEditDelta_changed(const QString &s);
+ virtual void lineEditDelta_validate();
+ virtual void setTimezone(QAction * action);
+
+signals:
+ void timePulse(PmTime::Packet *);
+ void vcrModePulse(PmTime::Packet *, int);
+ void tzPulse(PmTime::Packet *, char *, int, char *, int);
+
+private:
+ struct {
+ PmTime::Packet pmtime;
+ PmTime::DeltaUnits units;
+ bool first;
+ bool showMilliseconds;
+ bool showYear;
+ QTimer *timer;
+ QActionGroup *tzActions;
+ QList<TimeZone *> tzlist;
+ } my;
+};
+
+#endif // PMTIMELIVE_H
diff --git a/src/pmtime/pmtimelive.ui b/src/pmtime/pmtimelive.ui
new file mode 100644
index 0000000..7a3b882
--- /dev/null
+++ b/src/pmtime/pmtimelive.ui
@@ -0,0 +1,824 @@
+<ui version="4.0" >
+ <class>PmTimeLive</class>
+ <widget class="QMainWindow" name="PmTimeLive" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>310</width>
+ <height>155</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>310</width>
+ <height>155</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>310</width>
+ <height>155</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>PCP Live Time Control</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="windowIconText" >
+ <string/>
+ </property>
+ <property name="toolTip" >
+ <string/>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Live Time Control&lt;/b>&lt;br>
+A time server, sending time related information (current time,
+metric value fetch interval, timezone) to one or more client
+programs (e.g. pmchart, pmval, etc).</string>
+ </property>
+ <widget class="QWidget" name="widget" >
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>9</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="textLabel1" >
+ <property name="whatsThis" >
+ <string>&lt;b>Sampling interval&lt;/b>&lt;br>
+Numeric values entered into these fields will form the number of
+days, hours, minutes, seconds, and/or milliseconds between PCP
+metric value samples in Time Control client programs
+(e.g. pmchart, pmval, etc).</string>
+ </property>
+ <property name="text" >
+ <string>Interval</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel2" >
+ <property name="whatsThis" >
+ <string>&lt;b>Time Position&lt;/b>&lt;br>
+This text field displays the current time position. This value is
+sent to clients (e.g. pmchart, pmval) of this Time Control
+program to synchronise them in terms of the progression of time
+and also with respect to any "play mode" changes requested.</string>
+ </property>
+ <property name="text" >
+ <string>Time</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="textLabel3" >
+ <property name="whatsThis" >
+ <string>&lt;b>Live Control&lt;/b>&lt;br>
+Pressing the Stop/Play controls will begin/block the advancement
+of time, as seen by the Time Control program and all of its clients
+(e.g. pmchart, pmval, etc). Time will advance in increments of thecurrent Interval.</string>
+ </property>
+ <property name="text" >
+ <string>Control</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="lineEditDelta" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>98</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>98</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="toolTip" >
+ <string comment="Time interval between performance metric value samples" >Sampling interval</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Sampling interval&lt;/b>&lt;br>
+Numeric values entered into these fields will form the number of
+hours, minutes and/or seconds between PCP metric value
+samples in Time Control client programs (e.g. pmchart, pmval, etc).</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxDelta" >
+ <property name="whatsThis" >
+ <string>&lt;b>Sampling interval units&lt;/b>&lt;br>
+Displays the units in which the sampling interval is entered.
+Metric values can be sampled in days, hours, minutes, seconds
+or milliseconds in Time Control client programs (e.g. pmchart,
+pmval, etc).</string>
+ </property>
+ <property name="currentIndex" >
+ <number>1</number>
+ </property>
+ <item>
+ <property name="text" >
+ <string>Milliseconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Seconds</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Hours</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Days</string>
+ </property>
+ </item>
+ <item>
+ <property name="text" >
+ <string>Weeks</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="lineEditCtime" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>1</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>200</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize" >
+ <size>
+ <width>32767</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="acceptDrops" >
+ <bool>false</bool>
+ </property>
+ <property name="toolTip" >
+ <string comment="This is the current time position as seen by all pmtime clients (e.g. pmchart, pmval, etc)" >Current date and time display</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Time Position&lt;/b>&lt;br>
+This text field displays the current time position. This value is
+sent to clients (e.g. pmchart, pmval) of this Time Control
+program to synchronise them in terms of the progression of time
+and also with respect to any "play mode" changes requested.</string>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="buttonStop" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>54</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Stop&lt;/b>&lt;br>
+Pressing this button will stop the advancement of time, as seen
+by the Time Control program and all of its clients (e.g. pmchart,
+pmval, etc). Unless stopped, time advances in increments of the
+current Interval.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>52</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="buttonPlay" >
+ <property name="sizePolicy" >
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>54</width>
+ <height>36</height>
+ </size>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Play&lt;/b>&lt;br>
+Pressing this button will begin the advancement of time, as seen
+by the Time Control program and all of its clients (e.g. pmchart,
+pmval, etc). Time will advance in increments of the current
+Interval.</string>
+ </property>
+ <property name="text" >
+ <string/>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>52</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>29</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0" >
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="MenuBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>310</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="Help" >
+ <property name="title" >
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="helpManualAction" />
+ <addaction name="separator" />
+ <addaction name="helpAboutAction" />
+ <addaction name="helpAboutQtAction" />
+ <addaction name="helpSeeAlsoAction" />
+ <addaction name="separator" />
+ <addaction name="helpWhats_ThisAction" />
+ </widget>
+ <widget class="QMenu" name="File" >
+ <property name="title" >
+ <string>&amp;File</string>
+ </property>
+ <addaction name="fileHideAction" />
+ </widget>
+ <widget class="QMenu" name="Options" >
+ <property name="title" >
+ <string>&amp;Options</string>
+ </property>
+ <widget class="QMenu" name="optionsDetailAction" >
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail&lt;/b>&lt;br>
+Click this menu item to allow extended precision in time
+reporting to be enabled. Optionally, milliseconds and year can
+be displayed in the current time position, and days and milliseconds
+can be displayed in the current time interval.</string>
+ </property>
+ <property name="title" >
+ <string>&amp;Detail</string>
+ </property>
+ <addaction name="optionsDetailShow_MillisecondsAction" />
+ <addaction name="optionsDetailShow_YearAction" />
+ </widget>
+ <widget class="QMenu" name="optionsTimezoneAction" >
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/internet-web-browser.png</iconset>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Timezone&lt;/b>&lt;br>
+Click on this menu item to display a list of available timezones,
+from which to choose. The timezone selected will be made the
+new reporting timezone for all client programs (e.g. pmchart,
+pmval, etc) and affects the display of timestamps in the clients
+and also in the Time Control program itself.</string>
+ </property>
+ <property name="title" >
+ <string>&amp;Timezone</string>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/internet-web-browser.png</iconset>
+ </property>
+ </widget>
+ <addaction name="optionsTimezoneAction" />
+ <addaction name="optionsDetailAction" />
+ <addaction name="optionsShowConsoleAction" />
+ </widget>
+ <addaction name="File" />
+ <addaction name="Options" />
+ <addaction name="separator" />
+ <addaction name="Help" />
+ </widget>
+ <action name="helpAboutAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;About</string>
+ </property>
+ <property name="iconText" >
+ <string>About pmtime</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|About&lt;/b>&lt;br>
+Click this menu option to bring up the About &lt;i>pmtime&lt;/i> dialog with version, author, and licensing information.</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </action>
+ <action name="fileHideAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/edit-clear.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Hide</string>
+ </property>
+ <property name="iconText" >
+ <string>Hide Live Time Control window</string>
+ </property>
+ <property name="toolTip" >
+ <string>Hide Live Time Control window</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>File|Hide&lt;/b>&lt;br>
+Click on this menu option to dismiss the Time Control window,
+for the time being. As the Time Control serves one or
+more client programs (which are completely reliant on the Time
+Controls), this action does not end the Time Control program.
+Rather, it simply removes the window from the visible set.&lt;br>
+The client programs will typically provide a mechanism for making
+the Time Controls visible.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+H</string>
+ </property>
+ </action>
+ <action name="optionsDetailShow_MillisecondsAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Show &amp;Milliseconds</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Milliseconds in Time</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail|Milliseconds&lt;/b>&lt;br>
+Display millisecond precision (or not) in the current time interval and
+position.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+M</string>
+ </property>
+ </action>
+ <action name="optionsDetailShow_YearAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Show &amp;Year</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Year in Time</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Options|Detail|Year&lt;/b>&lt;br>
+Display the year (or not) in the current time position.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Y</string>
+ </property>
+ </action>
+ <action name="optionsShowConsoleAction" >
+ <property name="text" >
+ <string>&amp;Show Console</string>
+ </property>
+ <property name="iconText" >
+ <string>Show Diagnostic Console</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Option|Show Console&lt;/b>&lt;br>
+Click here to display the diagnostic console with whatever debug
+information has been sent there. This menu option is available
+only when the -D command line option is in use, which allows a
+level of runtime verbosity to be configured (default is "none").</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+C</string>
+ </property>
+ </action>
+ <action name="helpAboutQtAction" >
+ <property name="text" >
+ <string>About Qt</string>
+ </property>
+ <property name="iconText" >
+ <string>About Qt</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|About Qt&lt;/b>&lt;br>
+Click on this menu option to bring up the See Also dialog, with author and website information about Qt - which provides software infrastructure on which this tool was built.</string>
+ </property>
+ </action>
+ <action name="helpSeeAlsoAction" >
+ <property name="text" >
+ <string>See Also</string>
+ </property>
+ <property name="iconText" >
+ <string>See Also</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|See Also&lt;/b>&lt;br>
+Click on this menu option to bring up the See Also dialog, with author and website information about PCP, Qwt, and Qt - all of which provide software infrastructure on which this tool was built.</string>
+ </property>
+ </action>
+ <action name="helpWhats_ThisAction" >
+ <property name="icon" >
+ <iconset resource="pmtime.qrc" >:/images/whatsthis.png</iconset>
+ </property>
+ <property name="text" >
+ <string>What's This?</string>
+ </property>
+ <property name="iconText" >
+ <string>"What's This?" context sensitive help</string>
+ </property>
+ <property name="whatsThis" >
+ <string>&lt;b>Help|What's This&lt;/b>&lt;br>
+Click this menu option to invoke a small question mark that is attached to the mouse pointer. Click on a feature which you would like more information about. A popup box appears with more information about the feature.</string>
+ </property>
+ <property name="shortcut" >
+ <string>Shift+F1</string>
+ </property>
+ </action>
+ <action name="helpManualAction" >
+ <property name="text" >
+ <string>&amp;Manual</string>
+ </property>
+ <property name="iconText" >
+ <string>Manual</string>
+ </property>
+ <property name="shortcut" >
+ <string>F1</string>
+ </property>
+ </action>
+ </widget>
+ <layoutdefault spacing="6" margin="11" />
+ <includes>
+ <include location="local" >pmtime.h</include>
+ <include location="local" >console.h</include>
+ <include location="local" >timezone.h</include>
+ </includes>
+ <resources>
+ <include location="pmtime.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>fileHideAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>hideWindow()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpAboutAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>helpAbout()</slot>
+ </connection>
+ <connection>
+ <sender>helpAboutQtAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>helpAboutQt()</slot>
+ </connection>
+ <connection>
+ <sender>helpSeeAlsoAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>helpSeeAlso()</slot>
+ </connection>
+ <connection>
+ <sender>buttonPlay</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>play_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonStop</sender>
+ <signal>clicked()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>stop_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsDetailShow_MillisecondsAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>clickShowMsec()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsDetailShow_YearAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>clickShowYear()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>optionsShowConsoleAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>showConsole()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>comboBoxDelta</sender>
+ <signal>activated(int)</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>changeDelta(int)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpWhats_ThisAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>whatsThis()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditDelta</sender>
+ <signal>returnPressed()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>lineEditDelta_validate()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>lineEditDelta</sender>
+ <signal>textChanged(QString)</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>lineEditDelta_changed(QString)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>helpManualAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmTimeLive</receiver>
+ <slot>helpManual()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>-1</x>
+ <y>-1</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/seealsodialog.cpp b/src/pmtime/seealsodialog.cpp
new file mode 100644
index 0000000..ae663e8
--- /dev/null
+++ b/src/pmtime/seealsodialog.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "seealsodialog.h"
+
+SeeAlsoDialog::SeeAlsoDialog(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+}
+
+void SeeAlsoDialog::seeAlsoOKButton_clicked()
+{
+ done(0);
+}
diff --git a/src/pmtime/seealsodialog.h b/src/pmtime/seealsodialog.h
new file mode 100644
index 0000000..b5465b6
--- /dev/null
+++ b/src/pmtime/seealsodialog.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SEEALSODIALOG_H
+#define SEEALSODIALOG_H
+
+#include "ui_seealsodialog.h"
+
+class SeeAlsoDialog : public QDialog, public Ui::SeeAlsoDialog
+{
+ Q_OBJECT
+
+public:
+ SeeAlsoDialog(QWidget* parent);
+
+public slots:
+ virtual void seeAlsoOKButton_clicked();
+};
+
+#endif // SEEALSODIALOG_H
diff --git a/src/pmtime/seealsodialog.ui b/src/pmtime/seealsodialog.ui
new file mode 100644
index 0000000..9b81af8
--- /dev/null
+++ b/src/pmtime/seealsodialog.ui
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SeeAlsoDialog</class>
+ <widget class="QDialog" name="SeeAlsoDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>410</width>
+ <height>250</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>410</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="windowTitle">
+ <string>Acknowledgements</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="pmtime.qrc">
+ <normaloff>:/images/pmtime.png</normaloff>:/images/pmtime.png</iconset>
+ </property>
+ <layout class="QGridLayout">
+ <property name="margin">
+ <number>9</number>
+ </property>
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="seeAlsoPCPPixmapLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>76</width>
+ <height>82</height>
+ </size>
+ </property>
+ <property name="pixmap">
+ <pixmap resource="pmtime.qrc">:/images/aboutpcp.png</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ <property name="wordWrap">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>51</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="versionPCPTextLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;&lt;i&gt;Performance Co-Pilot (PCP)&lt;/i&gt;&lt;/b&gt;&lt;br&gt;
+Copyright (c) 2010-2012 Red Hat&lt;br&gt;
+Copyright (c) 2006-2012 Aconex&lt;br&gt;
+Copyright (c) 1995-2007 SGI&lt;br&gt;
+... and many, many others!&lt;br&gt;
+&lt;i&gt;http://oss.sgi.com/projects/pcp&lt;/i&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="versionQwtTextLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&lt;b&gt;&lt;i&gt;Qt Widgets for Technical Apps (Qwt)&lt;/i&gt;&lt;/b&gt;&lt;br&gt;
+Copyright (c) 1997 Josef Wilgen&lt;br&gt;
+Copyright (c) 2002 Uwe Rathmann&lt;br&gt;
+&lt;i&gt;http://qwt.sourceforge.net/&lt;/i&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <property name="spacing">
+ <number>6</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="seeAlsoOKButton">
+ <property name="focusPolicy">
+ <enum>Qt::TabFocus</enum>
+ </property>
+ <property name="text">
+ <string>OK</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources>
+ <include location="pmtime.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>seeAlsoOKButton</sender>
+ <signal>clicked()</signal>
+ <receiver>SeeAlsoDialog</receiver>
+ <slot>seeAlsoOKButton_clicked()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/showboundsdialog.cpp b/src/pmtime/showboundsdialog.cpp
new file mode 100644
index 0000000..6d982b3
--- /dev/null
+++ b/src/pmtime/showboundsdialog.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "showboundsdialog.h"
+#include <QtGui/QMessageBox>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+#include "pmtime.h"
+
+ShowBounds::ShowBounds(QWidget* parent) : QDialog(parent)
+{
+ setupUi(this);
+}
+
+void ShowBounds::init(
+ struct timeval *absoluteStart, struct timeval *currentStart,
+ struct timeval *absoluteEnd, struct timeval *currentEnd)
+{
+ my.absoluteStart = absoluteStart;
+ my.currentStart = currentStart;
+ my.absoluteEnd = absoluteEnd;
+ my.currentEnd = currentEnd;
+ reset();
+}
+
+void ShowBounds::reset()
+{
+ my.localAbsoluteStart = PmTime::secondsFromTimeval(my.absoluteStart);
+ my.localCurrentStart = PmTime::secondsFromTimeval(my.currentStart);
+ my.localAbsoluteEnd = PmTime::secondsFromTimeval(my.absoluteEnd);
+ my.localCurrentEnd = PmTime::secondsFromTimeval(my.currentEnd);
+
+#ifdef DESPERATE
+ console->post(PmTime::DebugProtocol, "ShowBounds::reset START: "
+ "end=%u.%u(%.3f) start=%u.%u(%.3f) lend=%u.%u(%.3f) lstart=%u.%u(%.3f)",
+ my.absoluteEnd->tv_sec, my.absoluteEnd->tv_usec, my.localAbsoluteEnd,
+ my.absoluteStart->tv_sec, my.absoluteStart->tv_usec,
+ my.localAbsoluteStart,
+ my.currentEnd->tv_sec, my.currentEnd->tv_usec, my.localCurrentEnd,
+ my.currentStart->tv_sec, my.currentStart->tv_usec,
+ my.localCurrentStart);
+#endif
+
+ displayStartSlider();
+ displayEndSlider();
+ displayStartText();
+ displayEndText();
+
+#ifdef DESPERATE
+ console->post(PmTime::DebugProtocol, "ShowBounds::reset ENDED: "
+ "end=%u.%u(%.3f) start=%u.%u(%.3f) lend=%u.%u(%.3f) lstart=%u.%u(%.3f)",
+ my.absoluteEnd->tv_sec, my.absoluteEnd->tv_usec, my.localAbsoluteEnd,
+ my.absoluteStart->tv_sec, my.absoluteStart->tv_usec,
+ my.localAbsoluteStart,
+ my.currentEnd->tv_sec, my.currentEnd->tv_usec, my.localCurrentEnd,
+ my.currentStart->tv_sec, my.currentStart->tv_usec,
+ my.localCurrentStart);
+#endif
+}
+
+void ShowBounds::displayStartSlider()
+{
+ sliderStart->blockSignals(true);
+ sliderStart->setRange(my.localAbsoluteStart, my.localAbsoluteEnd);
+ sliderStart->setValue(my.localCurrentStart);
+ sliderStart->blockSignals(false);
+}
+
+void ShowBounds::displayEndSlider()
+{
+ sliderEnd->blockSignals(true);
+ sliderEnd->setRange(my.localAbsoluteStart, my.localAbsoluteEnd);
+ sliderEnd->setValue(my.localCurrentEnd);
+ sliderEnd->blockSignals(false);
+}
+
+void ShowBounds::displayStartText()
+{
+ time_t clock = (time_t)my.localCurrentStart;
+ char ctimebuf[32];
+
+ pmCtime(&clock, ctimebuf);
+ lineEditStart->setText(tr(ctimebuf).simplified());
+
+ console->post("ShowBounds::displayStartText clock=%.3f - %s",
+ my.localCurrentStart,
+ (const char *)lineEditStart->text().toAscii());
+}
+
+void ShowBounds::displayEndText()
+{
+ time_t clock = (time_t)my.localCurrentEnd;
+ char ctimebuf[32];
+
+ pmCtime(&clock, ctimebuf);
+ lineEditEnd->setText(tr(ctimebuf).simplified());
+}
+
+void ShowBounds::changedStart(double value)
+{
+ if (value != my.localCurrentStart) {
+ console->post("ShowBounds::changedStart: %.3f -> %.3f",
+ my.localCurrentStart, value);
+ my.localCurrentStart = value;
+ displayStartSlider();
+ displayStartText();
+ if (my.localCurrentStart > my.localCurrentEnd)
+ sliderEnd->setValue(value);
+ }
+}
+
+void ShowBounds::changedEnd(double value)
+{
+ if (value != my.localCurrentEnd) {
+ console->post("ShowBounds::changedEnd %.3f -> %.3f", __func__,
+ my.localCurrentEnd, value);
+ my.localCurrentEnd = value;
+ displayEndSlider();
+ displayEndText();
+ if (my.localCurrentStart > my.localCurrentEnd)
+ sliderStart->setValue(value);
+ }
+}
+
+//
+// This routine just verifies that input from the user is correct,
+// before allowing the window to be closed. If theres invalid date
+// strings in start/end text boxes, we must deny the "OK" press.
+// Actual work of updating pmtime is done in flush().
+//
+void ShowBounds::accept()
+{
+ struct timeval current, start, end;
+ QString error, input;
+ char *msg;
+
+ console->post("ShowBounds::accept: OK pressed");
+
+ PmTime::secondsToTimeval(my.localAbsoluteStart, &start);
+ PmTime::secondsToTimeval(my.localAbsoluteEnd, &end);
+
+ if (lineEditStart->isModified()) {
+ input = lineEditStart->text().simplified();
+ if (input.length() == 0) {
+ error.sprintf("Start time has not been set.\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+ if (input[0] != '@')
+ input.prepend("@");
+ if (__pmParseTime(input.toAscii(), &start, &end, &current, &msg) < 0) {
+ error.sprintf("Invalid start date/time:\n\n%s\n", msg);
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ free(msg);
+ return;
+ } else if (PmTime::timevalCompare(&current, &start) < 0 ||
+ PmTime::timevalCompare(&current, &end) > 0) {
+ error.sprintf("Start time is outside archive boundaries\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+ my.localCurrentStart = PmTime::secondsFromTimeval(&current);
+ console->post("ShowBounds::accept start=%.2f (abs=%.2f-%.2f)",
+ my.localCurrentStart, my.localAbsoluteStart,
+ my.localAbsoluteEnd);
+ }
+
+ if (lineEditEnd->isModified()) {
+ input = lineEditEnd->text().simplified();
+ if (input.length() == 0) {
+ error.sprintf("End time has not been set.\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+ if (input[0] != '@')
+ input.prepend("@");
+ if (__pmParseTime(input.toAscii(), &start, &end, &current, &msg) < 0) {
+ error.sprintf("Invalid end date/time:\n%s\n\n", msg);
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ free(msg);
+ return;
+ } else if (PmTime::timevalCompare(&current, &start) < 0 ||
+ PmTime::timevalCompare(&current, &end) > 0) {
+ error.sprintf("End time is outside the archive boundaries\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+ my.localCurrentEnd = PmTime::secondsFromTimeval(&current);
+ console->post("ShowBounds::accept end=%.2f (abs=%.2f-%.2f)",
+ my.localCurrentEnd, my.localAbsoluteStart, my.localAbsoluteEnd);
+ }
+
+ if (my.localCurrentStart > my.localCurrentEnd) {
+ error.sprintf("Start time must be less than end time.\n");
+ QMessageBox::warning(0, tr("Warning"), error, tr("Quit"));
+ return;
+ }
+
+ emit boundsChanged();
+ done(0);
+}
+
+void ShowBounds::reject()
+{
+ done(1);
+}
+
+//
+// Inform parent pmtime window of accepted changes (start/end),
+// via the pointers-to-struct-timevals we were initially given.
+//
+void ShowBounds::flush()
+{
+ struct timeval start, end;
+
+ PmTime::secondsToTimeval(my.localCurrentStart, &start);
+ PmTime::secondsToTimeval(my.localCurrentEnd, &end);
+
+ console->post("ShowBounds::flush updating bounds to %.2f->%.2f",
+ PmTime::secondsFromTimeval(&start),
+ PmTime::secondsFromTimeval(&end));
+
+ if (PmTime::timevalCompare(&start, my.currentStart) != 0)
+ *my.currentStart = start;
+ if (PmTime::timevalCompare(&end, my.currentEnd) != 0)
+ *my.currentEnd = end;
+}
diff --git a/src/pmtime/showboundsdialog.h b/src/pmtime/showboundsdialog.h
new file mode 100644
index 0000000..da080c8
--- /dev/null
+++ b/src/pmtime/showboundsdialog.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SHOWBOUNDS_H
+#define SHOWBOUNDS_H
+
+#include "ui_showboundsdialog.h"
+#include "console.h"
+
+class ShowBounds : public QDialog, public Ui::ShowBounds
+{
+ Q_OBJECT
+
+public:
+ ShowBounds(QWidget* parent);
+
+ virtual void init(struct timeval *, struct timeval *,
+ struct timeval *, struct timeval *);
+ virtual void reset();
+ virtual void flush();
+ virtual void displayStartText();
+ virtual void displayEndText();
+ virtual void displayStartSlider();
+ virtual void displayEndSlider();
+
+public slots:
+ virtual void changedStart(double value);
+ virtual void changedEnd(double value);
+ virtual void accept();
+ virtual void reject();
+
+signals:
+ void boundsChanged();
+
+private:
+ struct {
+ double localCurrentStart;
+ double localCurrentEnd;
+ double localAbsoluteStart;
+ double localAbsoluteEnd;
+ struct timeval *currentStart;
+ struct timeval *currentEnd;
+ struct timeval *absoluteStart;
+ struct timeval *absoluteEnd;
+ } my;
+};
+
+#endif // SHOWBOUNDS_H
diff --git a/src/pmtime/showboundsdialog.ui b/src/pmtime/showboundsdialog.ui
new file mode 100644
index 0000000..8459921
--- /dev/null
+++ b/src/pmtime/showboundsdialog.ui
@@ -0,0 +1,303 @@
+<ui version="4.0" >
+ <class>ShowBounds</class>
+ <widget class="QDialog" name="ShowBounds" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>324</width>
+ <height>181</height>
+ </rect>
+ </property>
+ <property name="acceptDrops" >
+ <bool>false</bool>
+ </property>
+ <property name="windowTitle" >
+ <string>Archive Time Bounds</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmtime.qrc" >:/images/pmtime.png</iconset>
+ </property>
+ <property name="whatsThis" >
+ <string>The Archive Bounds dialog displays and allows modifications
+to the start and end time of a (set of) PCP data archive(s).
+This effectively restricts all pmtime clients (e.g. pmval, pmchart)
+to a subset of the available performance data.</string>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <property name="modal" >
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>7</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="0" column="0" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>4</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="3" column="0" colspan="2" >
+ <widget class="QwtSlider" name="sliderEnd" >
+ <property name="whatsThis" >
+ <string>Modify the archive end time as seen by all&lt;br>
+pmtime clients (e.g. pmval, pmchart).&lt;br>&lt;br>
+This cannot be set before the start time of the&lt;br>
+archive with the earliest start time, nor beyond&lt;br>
+of the archive with the latest end time.&lt;br>
+It also cannot be less than the current start&lt;br>
+time.</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="backgroundStyle" >
+ <enum>QwtSlider::Groove</enum>
+ </property>
+ <property name="handleSize" >
+ <size>
+ <width>12</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="endLabel" >
+ <property name="text" >
+ <string>End</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="startLabel" >
+ <property name="text" >
+ <string>Start</string>
+ </property>
+ <property name="wordWrap" >
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" >
+ <widget class="QLineEdit" name="lineEditStart" >
+ <property name="whatsThis" >
+ <string>Modify the archive start time as seen by all&lt;br>
+pmtime clients (e.g. pmval, pmchart).&lt;br>&lt;br>
+This cannot be set before the start time of the&lt;br>
+archive with the earliest start time, nor beyond&lt;br>
+of the archive with the latest end time.&lt;br>
+It also cannot be greater than the current end&lt;br>
+time.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2" >
+ <widget class="QwtSlider" name="sliderStart" >
+ <property name="whatsThis" >
+ <string>Modify the archive start time as seen by all&lt;br>
+pmtime clients (e.g. pmval, pmchart).&lt;br>&lt;br>
+This cannot be set before the start time of the&lt;br>
+archive with the earliest start time, nor beyond&lt;br>
+of the archive with the latest end time.&lt;br>
+It also cannot be greater than the current end&lt;br>
+time.</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="backgroundStyle" >
+ <enum>QwtSlider::Groove</enum>
+ </property>
+ <property name="handleSize" >
+ <size>
+ <width>12</width>
+ <height>16</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" >
+ <widget class="QLineEdit" name="lineEditEnd" >
+ <property name="whatsThis" >
+ <string>Modify the archive end time as seen by all&lt;br>
+pmtime clients (e.g. pmval, pmchart).&lt;br>&lt;br>
+This cannot be set before the start time of the&lt;br>
+archive with the earliest start time, nor beyond&lt;br>
+of the archive with the latest end time.&lt;br>
+It also cannot be less than the current start&lt;br>
+time.</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType" >
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonOK" >
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="buttonCancel" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ <property name="autoDefault" >
+ <bool>true</bool>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11" />
+ <customwidgets>
+ <customwidget>
+ <class>QwtSlider</class>
+ <extends>QWidget</extends>
+ <header>qwt_slider.h</header>
+ </customwidget>
+ </customwidgets>
+ <includes>
+ <include location="local" >console.h</include>
+ </includes>
+ <resources>
+ <include location="pmtime.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonOK</sender>
+ <signal>clicked()</signal>
+ <receiver>ShowBounds</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>ShowBounds</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sliderStart</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>ShowBounds</receiver>
+ <slot>changedStart(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>sliderEnd</sender>
+ <signal>valueChanged(double)</signal>
+ <receiver>ShowBounds</receiver>
+ <slot>changedEnd(double)</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>20</x>
+ <y>20</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmtime/timelord.cpp b/src/pmtime/timelord.cpp
new file mode 100644
index 0000000..64ebcf0
--- /dev/null
+++ b/src/pmtime/timelord.cpp
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "timelord.h"
+#include <stdlib.h>
+
+TimeClient::TimeClient(QTcpSocket *s, QObject *p) : QObject(p)
+{
+ my.ac = NULL;
+ my.hc = NULL;
+ my.socket = s;
+ my.state = TimeClient::Disconnected;
+ my.source = PmTime::NoSource;
+ memset(&my.acktime, 0, sizeof(my.acktime));
+ console->post(PmTime::DebugProtocol, "TimeClient initialised");
+ connect(my.socket, SIGNAL(readyRead()), SLOT(readClient()));
+ connect(my.socket, SIGNAL(disconnected()), SLOT(disconnectClient()));
+}
+
+TimeClient::~TimeClient()
+{
+ console->post(PmTime::DebugProtocol, "Destroying client %p", this);
+ my.state = TimeClient::Disconnected;
+ my.source = PmTime::NoSource;
+ emit endConnect(this);
+}
+
+static char *stateString(int state)
+{
+ static char buffer[32];
+
+ if (state == TimeClient::Disconnected)
+ strcpy(buffer, "Disconnected State");
+ else if (state == TimeClient::ClientConnectSET)
+ strcpy(buffer, "ClientConnectSET State");
+ else if (state == TimeClient::ServerConnectACK)
+ strcpy(buffer, "ServerConnectACK State");
+ else if (state == TimeClient::ServerNeedACK)
+ strcpy(buffer, "ServerNeedACK State");
+ else if (state == TimeClient::ClientReady)
+ strcpy(buffer, "ClientReady State");
+ else
+ strcpy(buffer, "Unknown State");
+ return buffer;
+}
+
+void TimeClient::disconnectClient()
+{
+ console->post(PmTime::DebugProtocol, "TimeClient::disconnectClient");
+ delete this;
+}
+
+bool TimeClient::writeClient(PmTime::Packet *packet,
+ char *tz, int tzlen, char *label, int llen)
+{
+ if (packet->source != my.source)
+ return true;
+
+ switch (my.state) {
+ case TimeClient::Disconnected:
+ return true;
+ case TimeClient::ClientConnectSET:
+ if (packet->command == PmTime::ACK) {
+ my.state = TimeClient::ServerConnectACK;
+ break;
+ }
+ return true;
+ case TimeClient::ServerConnectACK:
+ break;
+ case TimeClient::ClientReady:
+ if (packet->command == PmTime::Step) {
+ my.state = TimeClient::ServerNeedACK;
+ my.acktime = packet->position;
+ }
+ else {
+ my.state = TimeClient::ClientReady;
+ memset(&my.acktime, 0, sizeof(my.acktime));
+ }
+ break;
+ case TimeClient::ServerNeedACK:
+ if (packet->command != PmTime::Step) {
+ my.state = TimeClient::ClientReady; // clear NEED_ACK
+ memset(&my.acktime, 0, sizeof(my.acktime));
+ break;
+ }
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "SKIP STEP to pos=%u.%u when client %p in NEED_ACK",
+ packet->position.tv_sec,packet->position.tv_usec, this);
+ return false;
+ }
+
+ int len = my.socket->write((const char *)packet, sizeof(PmTime::Packet));
+ if (len != sizeof(PmTime::Packet)) {
+ console->post(PmTime::DebugProtocol, "TimeCient::writeClient "
+ "wrote %d bytes not %d (%x command)",
+ len, packet->length, packet->command);
+ my.state = TimeClient::Disconnected;
+ endConnect(this);
+ } else {
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "wrote %d bytes command=%x state=%u",
+ len, packet->command, my.state);
+ }
+ if (tzlen > 0 && len > 0 &&
+ (len = my.socket->write(tz, tzlen)) != tzlen) {
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "wrote %d bytes not %d (timezone)", len, tzlen);
+ my.state = TimeClient::Disconnected;
+ endConnect(this);
+ } else if (tzlen) {
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "wrote %d bytes of timezone successfully", len);
+ }
+ if (llen > 0 && len > 0 &&
+ (len = my.socket->write(label, llen)) != llen) {
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "wrote %d bytes not %d (tz label)", len, llen);
+ my.state = TimeClient::Disconnected;
+ endConnect(this);
+ } else if (llen) {
+ console->post(PmTime::DebugProtocol, "TimeClient::writeClient "
+ "wrote %d bytes of tz label successfully", len);
+ }
+ return true;
+}
+
+void TimeClient::readClient(void)
+{
+ PmTime::Packet packet;
+ char *payload = NULL;
+ int bad = 0, len, sz;
+
+ console->post(PmTime::DebugProtocol, "Reading data from client %p", this);
+
+ len = my.socket->read((char *)&packet, sizeof(PmTime::Packet));
+ if (len < 0) {
+ console->post(PmTime::DebugProtocol, "Read error on client %p", this);
+ bad = 1;
+ } else if (packet.magic != PmTime::Magic) {
+ console->post(PmTime::DebugProtocol, "Bad magic (%x) from client %p",
+ packet.magic, this);
+ bad = 1;
+ } else if (len != sizeof(PmTime::Packet)) {
+ console->post(PmTime::DebugProtocol,
+ "Bad 1st read (want %d, got %d) on client %p",
+ len, sizeof(PmTime::Packet), this);
+ bad = 1;
+ } else if (packet.length > sizeof(PmTime::Packet)) {
+ sz = packet.length - sizeof(PmTime::Packet);
+ payload = (char *)malloc(sz);
+ if (payload == NULL) {
+ console->post(PmTime::DebugProtocol,
+ "No memory (%d) for second read on client %p",
+ sz, len, this);
+ bad = 1;
+ } else if ((len = my.socket->read(payload, sz)) != sz) {
+ console->post(PmTime::DebugProtocol,
+ "Bad 2nd read (want %d, got %d) on client %p",
+ sz, len, this);
+ bad = 1;
+ }
+ console->post(PmTime::DebugProtocol, "+%d message from client %p",
+ sz, this);
+ } else {
+ console->post(PmTime::DebugProtocol, "good message from client %p",
+ this);
+ }
+
+ if (!bad) {
+ console->post(PmTime::DebugProtocol, "state %s message %d",
+ stateString(my.state), packet.command);
+ switch(my.state) {
+ case TimeClient::Disconnected:
+ if (packet.command == PmTime::Set)
+ console->post(PmTime::DebugProtocol,
+ "%s got new SET from client %p",
+ __func__, this);
+ if (packet.source == PmTime::HostSource) {
+ my.source = PmTime::HostSource;
+ my.hc->setTime(&packet, payload);
+ } else {
+ my.source = PmTime::ArchiveSource;
+ my.ac->setTime(&packet, payload);
+ }
+ my.state = TimeClient::ClientConnectSET;
+ packet.command = PmTime::ACK;
+ packet.length = sizeof(PmTime::Packet);
+ writeClient(&packet);
+ return;
+
+ case TimeClient::ClientConnectSET:
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "bad client %p command %d in ConnectSET state",
+ this, packet.command);
+ break;
+
+ case TimeClient::ServerConnectACK:
+ if (packet.command == PmTime::ACK)
+ my.state = TimeClient::ClientReady;
+ break;
+
+ case TimeClient::ServerNeedACK:
+ if (packet.command != PmTime::ACK)
+ break;
+ if (packet.position.tv_sec == my.acktime.tv_sec &&
+ packet.position.tv_usec == my.acktime.tv_usec) {
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "good ACK client=%p (%u.%u)", this,
+ my.acktime.tv_sec, my.acktime.tv_usec);
+ my.state = TimeClient::ClientReady;
+ break;
+ }
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "BAD ACK client=%p (got %u.%u vs %u.%u)", this,
+ packet.position.tv_sec, packet.position.tv_usec,
+ my.acktime.tv_sec, my.acktime.tv_usec);
+ bad = 1;
+ break;
+
+ case TimeClient::ClientReady:
+ if (packet.command == PmTime::ACK) {
+ console->post(PmTime::DebugProtocol, "TimeClient:: readClient "
+ "unexpected client %p ACK in Ready state", this);
+ }
+ break;
+ }
+
+ switch(packet.command) {
+ case PmTime::GUIHide:
+ case PmTime::GUIShow:
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "HIDE/SHOW from client %p", this);
+ if (my.source == PmTime::HostSource)
+ my.hc->popup(packet.command == PmTime::GUIShow);
+ if (my.source == PmTime::ArchiveSource)
+ my.ac->popup(packet.command == PmTime::GUIShow);
+ break;
+ case PmTime::Bounds:
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "BOUNDS from client %p", this);
+ my.ac->addBound(&packet, payload);
+ break;
+ case PmTime::ACK:
+ break;
+ default:
+ console->post(PmTime::DebugProtocol, "TimeClient::readClient "
+ "unknown command %d from client %p",
+ packet.command, this);
+ bad = 1;
+ }
+ }
+
+ if (bad)
+ reset();
+}
+
+void TimeClient::reset()
+{
+ console->post(PmTime::DebugProtocol, "TimeClient::reset");
+#if 0
+ if (my.source == PmTime::HostSource)
+ my.hc->stop();
+ else
+ my.ac->stop();
+#endif
+}
+
+TimeLord::TimeLord(QApplication *app)
+{
+ my.ac = NULL;
+ my.hc = NULL;
+ connect(this, SIGNAL(lastClientExit()), app, SLOT(quit()));
+ connect(this, SIGNAL(lastClientExit()), this, SLOT(quit()));
+ connect(this, SIGNAL(newConnection()), SLOT(newConnection()));
+ console->post(PmTime::DebugProtocol, "TimeLord initialised");
+}
+
+void TimeLord::quit()
+{
+ if (my.ac)
+ my.ac->quit();
+ if (my.hc)
+ my.hc->quit();
+}
+
+void TimeLord::setContext(PmTimeLive *live, PmTimeArch *arch)
+{
+ my.hc = live;
+ connect(live, SIGNAL(timePulse(PmTime::Packet *)),
+ SLOT(timePulse(PmTime::Packet *)));
+ connect(live, SIGNAL(vcrModePulse(PmTime::Packet *, int)),
+ SLOT(vcrModePulse(PmTime::Packet *, int)));
+ connect(live, SIGNAL(tzPulse(PmTime::Packet *, char *, int, char *, int)),
+ SLOT(tzPulse(PmTime::Packet *, char *, int, char *, int)));
+ my.ac = arch;
+ connect(arch, SIGNAL(timePulse(PmTime::Packet *)),
+ SLOT(timePulse(PmTime::Packet *)));
+ connect(arch, SIGNAL(boundsPulse(PmTime::Packet *)),
+ SLOT(boundsPulse(PmTime::Packet *)));
+ connect(arch, SIGNAL(vcrModePulse(PmTime::Packet *, int)),
+ SLOT(vcrModePulse(PmTime::Packet *, int)));
+ connect(arch, SIGNAL(tzPulse(PmTime::Packet *, char *, int, char *, int)),
+ SLOT(tzPulse(PmTime::Packet *, char *, int, char *, int)));
+}
+
+void TimeLord::newConnection(void)
+{
+ TimeClient *c = new TimeClient(nextPendingConnection(), this);
+
+ console->post(PmTime::DebugProtocol, "Adding new client %p", c);
+ c->setContext(my.ac, my.hc);
+ connect(c, SIGNAL(endConnect(TimeClient *)),
+ SLOT(endConnect(TimeClient *)));
+ my.clientlist.append(c);
+}
+
+void TimeLord::endConnect(TimeClient *client)
+{
+ console->post(PmTime::DebugProtocol, "Removing client %p", client);
+ my.clientlist.removeAll(client);
+ if (my.clientlist.isEmpty()) {
+ console->post(PmTime::DebugProtocol, "No clients remain, exiting");
+ emit lastClientExit();
+ }
+}
+
+void TimeLord::timePulse(PmTime::Packet *packet)
+{
+ QList<TimeClient*> overrunClients;
+
+#if DESPERATE
+ static int sequence;
+ int localSequence = sequence++;
+ console->post(PmTime::DebugProtocol, "TimeLord::timePulse %d (%d clients)",
+ localSequence, my.clientlist.count());
+#endif
+
+ packet->magic = PmTime::Magic;
+ packet->length = sizeof(PmTime::Packet);
+ packet->command = PmTime::Step;
+ for (int i = 0; i < my.clientlist.size(); i++)
+ if (my.clientlist.at(i)->writeClient(packet) == false)
+ overrunClients.append(my.clientlist.at(i));
+ for (int i = 0; i < overrunClients.size(); i++)
+ overrunClients.at(i)->reset();
+
+#if DESPERATE
+ console->post(PmTime::DebugProtocol, "TimeLord::timePulse ended %d (%d)",
+ localSequence, overrunClients.size());
+#endif
+}
+
+void TimeLord::boundsPulse(PmTime::Packet *packet)
+{
+ console->post(PmTime::DebugProtocol, "TimeLord::boundsPulse (%d clients)",
+ my.clientlist.count());
+ packet->magic = PmTime::Magic;
+ packet->length = sizeof(PmTime::Packet);
+ packet->command = PmTime::Bounds;
+ for (int i = 0; i < my.clientlist.size(); i++)
+ my.clientlist.at(i)->writeClient(packet);
+}
+
+void TimeLord::vcrModePulse(PmTime::Packet *packet, int drag)
+{
+ console->post(PmTime::DebugProtocol, "TimeLord::vcrModePulse (%d clients)"
+ " %d", my.clientlist.count(), drag);
+ packet->magic = PmTime::Magic;
+ packet->length = sizeof(PmTime::Packet);
+ packet->command = drag ? PmTime::VCRModeDrag : PmTime::VCRMode;
+ for (int i = 0; i < my.clientlist.size(); i++)
+ my.clientlist.at(i)->writeClient(packet);
+}
+
+void TimeLord::tzPulse(PmTime::Packet *packet,
+ char *tz, int tzlen, char *l, int llen)
+{
+ console->post(PmTime::DebugProtocol, "TimeLord::tzPulse (%d clients)"
+ " - %s/%d/%d", my.clientlist.count(), tz, tzlen, llen);
+ packet->magic = PmTime::Magic;
+ packet->length = sizeof(PmTime::Packet) + tzlen + llen;
+ packet->command = PmTime::TZ;
+ for (int i = 0; i < my.clientlist.size(); i++)
+ my.clientlist.at(i)->writeClient(packet, tz, tzlen, l, llen);
+}
diff --git a/src/pmtime/timelord.h b/src/pmtime/timelord.h
new file mode 100644
index 0000000..31fedf9
--- /dev/null
+++ b/src/pmtime/timelord.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TIMELORD_H
+#define TIMELORD_H
+
+#include <QtCore/QVariant>
+#include <QtCore/QTimer>
+#include <QtCore/QList>
+#include <QtCore/QTextStream>
+#include <QtNetwork/QTcpSocket>
+#include <QtNetwork/QTcpServer>
+#include <QtGui/QApplication>
+
+#include "console.h"
+#include "pmtimelive.h"
+#include "pmtimearch.h"
+
+// The TimeClient class provides a socket that is connected with a client.
+// For every client that connects to the server, the server creates a new
+// instance of this class.
+//
+class TimeClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ // State transitions for a pmtime client. Basic SET/ACK protocol is:
+ // - client connects and sends initial (global) SET
+ // - server responds with ACK (beware live mode, with timers SETs here)
+ // - server now sends SETs with optional ACKs, until first ACK recv'd
+ // - after first ACK recv'd by server, all subsequent SETs must be ACK'd.
+ // The other messages can be sent/recv'd any time after the server has
+ // ACK'd the initial connection.
+ //
+ typedef enum {
+ Disconnected = 1,
+ ClientConnectSET,
+ ServerConnectACK,
+ ServerNeedACK,
+ ClientReady,
+ } State;
+
+public:
+ TimeClient(QTcpSocket *socket, QObject *parent);
+ ~TimeClient();
+ void reset();
+
+ void setContext(PmTimeArch *ac, PmTimeLive *hc) { my.ac = ac; my.hc = hc; }
+ bool writeClient(PmTime::Packet *k, char *tz = NULL, int tzlen = 0,
+ char *label = NULL, int llen = 0);
+
+signals:
+ void endConnect(TimeClient *);
+
+public slots:
+ void readClient();
+ void disconnectClient();
+
+private:
+ struct {
+ QTcpSocket *socket;
+ TimeClient::State state;
+ PmTime::Source source;
+ struct timeval acktime; // time position @ last STEP
+ PmTimeLive *hc;
+ PmTimeArch *ac;
+ } my;
+};
+
+// The TimeLord class is a QTcpServer which randomly travels the space-
+// time continuim, servicing and connecting up clients with the server;
+// it also hooks up logging of the action to the wide-screen console in
+// the Tardis.
+//
+// For each client that connects it creates a new TimeClient (maintained
+// in a QList) - the new instance is responsible for communication with
+// that TCP client.
+//
+class TimeLord : public QTcpServer
+{
+ Q_OBJECT
+
+public:
+ TimeLord(QApplication *app);
+ void setContext(PmTimeLive *live, PmTimeArch *archive);
+
+signals:
+ void lastClientExit();
+
+public slots:
+ void quit();
+ void newConnection();
+ void endConnect(TimeClient *client);
+ void timePulse(PmTime::Packet *k);
+ void boundsPulse(PmTime::Packet *k);
+ void vcrModePulse(PmTime::Packet *k, int drag);
+ void tzPulse(PmTime::Packet *k, char *t, int tlen, char *l, int llen);
+
+private:
+ struct {
+ PmTimeLive *hc;
+ PmTimeArch *ac;
+ QList<TimeClient*> clientlist;
+ } my;
+};
+
+#endif // TIMELORD_H
diff --git a/src/pmtime/timezone.h b/src/pmtime/timezone.h
new file mode 100644
index 0000000..2b8ea32
--- /dev/null
+++ b/src/pmtime/timezone.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2007, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef TIMEZONE_H
+#define TIMEZONE_H
+
+#include <string.h>
+#include <QtGui/QAction>
+
+class TimeZone
+{
+public:
+ TimeZone(char *name, char *label, QAction *action, int handle)
+ {
+ my.name = name;
+ my.label = label;
+ my.action = action;
+ my.handle = handle;
+ }
+
+ ~TimeZone()
+ {
+ if (my.name) free(my.name);
+ if (my.label) free(my.label);
+ if (my.action) delete my.action;
+ }
+
+ char *tz(void) { return my.name; }
+ char *tzlabel(void) { return my.label; }
+ int handle(void) { return my.handle; }
+ QAction *action(void) { return my.action; }
+
+private:
+ struct {
+ char *name;
+ char *label;
+ int handle;
+ QAction *action;
+ } my;
+};
+
+#endif // TIMEZONE_H
diff --git a/src/pmtop/GNUmakefile b/src/pmtop/GNUmakefile
new file mode 100644
index 0000000..e64aeaf
--- /dev/null
+++ b/src/pmtop/GNUmakefile
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2007 Silicon Graphics, Inc. All Rights Reserved.
+# Copyright (c) 2008 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmtop.c
+LLDLIBS = $(PCPLIB)
+CMDTARGET = pmtop$(EXECSUFFIX)
+
+default : $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install : default
+ #$(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp : default
+
+install_pcp : install
+
diff --git a/src/pmtop/pmtop.c b/src/pmtop/pmtop.c
new file mode 100644
index 0000000..dc10fc0
--- /dev/null
+++ b/src/pmtop/pmtop.c
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (c) 2013-2014 Red Hat.
+ * Copyright (c) 1999 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+/*
+ * Outputs psinfo.psargs and top value.
+ *
+ * Want to periodically write to stdout
+ * with the top processes for the following categories:
+ *
+ * cpuburn - rate converted proc.psusage.utime + stime
+ * syscalls - rate converted proc.psusage.pu_sysc
+ * reads - rate converted proc.psusage.bread + gbread
+ * writes - rate converted proc.psusage.bwrit + gbwrit
+ * ctxswitch - rate converted proc.psusage.pu_vctx + proc.psusage.pu_ictx
+ * residentsize - proc.psusage.rss
+ *
+ */
+
+#define MAX_PMID 15 /* gives enough space for all possible metrics in fetch */
+char *namelist[MAX_PMID];
+pmID pmidlist[MAX_PMID];
+int type_tab[MAX_PMID]; /* the types of the metrics */
+int valfmt_tab[MAX_PMID]; /* the types of the metrics */
+int num_pmid;
+int num_proc_pmid;
+
+/* indexes into [MAX_PMID] arrays */
+int kernel_utime;
+int kernel_stime;
+int kernel_sysc;
+int kernel_ctx;
+int pu_utime;
+int pu_stime;
+int pu_sysc;
+int pu_bread;
+int pu_gbread;
+int pu_bwrit;
+int pu_gbwrit;
+int pu_rss;
+int pu_vctx;
+int pu_ictx;
+
+char *hostname;
+int top = 5;
+long num_samples = -1;
+int num_inst;
+int *instances;
+char **instance_names;
+char *runtime;
+char pid_label_fmt[8];
+char pid_fmt[8];
+char *line_fmt = "%.79s";
+int pid_len;
+pmInDom proc_indom;
+
+/* values of interest */
+#define CPU_VAL 0
+#define SYSCALLS_VAL 1
+#define CTX_VAL 2
+#define WRITES_VAL 3
+#define READS_VAL 4
+#define RSS_VAL 5
+#define USR_VAL 6
+#define SYS_VAL 7
+#define NUM_VALUES 8
+
+/* which values to calculate and print */
+#define SHOW_CPU (1<<CPU_VAL)
+#define SHOW_SYSCALLS (1<<SYSCALLS_VAL)
+#define SHOW_CTX (1<<CTX_VAL)
+#define SHOW_WRITES (1<<WRITES_VAL)
+#define SHOW_READS (1<<READS_VAL)
+#define SHOW_RSS (1<<RSS_VAL)
+#define SHOW_USR (1<<USR_VAL)
+#define SHOW_SYS (1<<SYS_VAL)
+
+int show_spec = SHOW_CPU|SHOW_SYSCALLS|SHOW_CTX|SHOW_WRITES|SHOW_READS|SHOW_RSS; /* default to show all */
+
+typedef struct {
+ char *name;
+ int bit;
+} show_map_t;
+
+static show_map_t show_map[] = {
+ { "CPU", SHOW_CPU },
+ { "SYSC", SHOW_SYSCALLS },
+ { "CTX", SHOW_CTX },
+ { "WRITE", SHOW_WRITES },
+ { "READ", SHOW_READS },
+ { "RSS", SHOW_RSS },
+ { "USR", SHOW_USR },
+ { "SYS", SHOW_SYS },
+};
+static int num_show = sizeof(show_map) / sizeof(show_map[0]);
+
+typedef struct {
+ int inst; /* index into instances and names */
+ double values[NUM_VALUES];
+} rate_entry_t;
+
+static rate_entry_t *rate_tab;
+
+double global_rates[NUM_VALUES];
+double sum_rates[NUM_VALUES];
+
+typedef struct {
+ char *val_fmt;
+ char *label_fmt;
+ char *label;
+ char *units;
+ char *fullname;
+} format_entry_t;
+
+/*
+ * Warning - must match order of *_VAL macros
+ */
+format_entry_t format_tab[] =
+{
+ {"%7.2f", "%7s", "CPU%", "", "CPU Utilization"},
+ {"%9.0f", "%9s", "SYSCALLS", "sys/sec", "System Calls"},
+ {"%9.0f", "%9s", "CTX", "ctx/sec", "Context Switches"},
+ {"%8.0f", "%8s", "WRITES", "Kb/sec", "Writes"},
+ {"%8.0f", "%8s", "READS", "Kb/sec", "Reads"},
+ {"%9.0f", "%9s", "RSS", "Kb", "Resident Size"},
+ {"%7.2f", "%7s", "USR%", "", "User CPU Utilization"},
+ {"%7.2f", "%7s", "SYS%", "", "System CPU Utilization"},
+};
+
+/* value table */
+typedef struct {
+ int num;
+ pmValue values[MAX_PMID][2];
+} val_entry_t;
+
+val_entry_t *val_tab;
+
+/* index will use high end for globals */
+/* parallels index with other MAX_PMID arrays */
+pmValue global_val[MAX_PMID][2];
+
+#ifndef min
+#define min(x,y) (((x)<(y))?(x):(y))
+#endif
+
+static void get_indom(void);
+static int overrides(int, pmOptions *);
+
+pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("General Options"),
+ PMOPT_HOST,
+ PMOPT_SAMPLES,
+ PMOPT_INTERVAL,
+ PMAPI_OPTIONS_HEADER("Reporting Options"),
+ { "", 1, 'm', "N", "report the top N values only" },
+ { "", 1, 'p', "SPEC", "report certain values e.g. cpu,sysc,rss" },
+ { "wide", 0, 'w', 0, "wide output for command name" },
+ PMOPT_TIMEZONE,
+ PMOPT_HOSTZONE,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+pmOptions opts = {
+ .short_options = "D:h:m:p:s:t:wzZ:?",
+ .long_options = longopts,
+ .override = overrides,
+};
+
+static int
+parse_show_spec(const char *spec)
+{
+ int val = 0;
+ int tmp;
+ const char *p;
+ char *pend;
+ int i;
+
+ for (p = spec; *p; ) {
+ tmp = (int)strtol(p, &pend, 10);
+ if (tmp == -1)
+ /* special case ... -1 really means set all the bits! */
+ tmp = INT_MAX;
+ if (*pend == '\0') {
+ val |= tmp;
+ break;
+ }
+ else if (*pend == ',') {
+ val |= tmp;
+ p = pend + 1;
+ }
+ else {
+ pend = strchr(p, ',');
+ if (pend != NULL)
+ *pend = '\0';
+
+ for (i = 0; i < num_show; i++) {
+ if (strcasecmp(p, show_map[i].name) == 0) {
+ val |= show_map[i].bit;
+ if (pend != NULL) {
+ *pend = ',';
+ p = pend + 1;
+ }
+ else
+ p = ""; /* force termination of outer loop */
+ break;
+ }
+ }
+
+ if (i == num_show) {
+ if (pend != NULL)
+ *pend = ',';
+ return PM_ERR_CONV;
+ }
+ }
+ }
+
+ return val;
+}
+
+static char *
+get_time(void)
+{
+ static char str[80];
+ time_t now = time(NULL);
+ struct tm *tm = localtime(&now);
+ strftime(str, sizeof(str), "%c %Z", tm);
+ return str;
+}
+
+static __uint64_t
+get_value(int metric, pmValue *value)
+{
+ int sts;
+ pmAtomValue val;
+
+ if ((sts = pmExtractValue(valfmt_tab[metric], value,
+ type_tab[metric], &val, PM_TYPE_U64)) < 0) {
+ fprintf(stderr, "%s: Failed to extract metric value: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+
+ }
+ return val.ull;
+}
+
+static int
+cpuburn_cmp(const void *x, const void *y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[CPU_VAL] < c2->values[CPU_VAL])
+ return -1;
+ if (c2->values[CPU_VAL] > c1->values[CPU_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+sys_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[SYS_VAL] < c2->values[SYS_VAL])
+ return -1;
+ if (c2->values[SYS_VAL] > c1->values[SYS_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+usr_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[USR_VAL] < c2->values[USR_VAL])
+ return -1;
+ if (c2->values[USR_VAL] > c1->values[USR_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+syscall_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[SYSCALLS_VAL] < c2->values[SYSCALLS_VAL])
+ return -1;
+ if (c2->values[SYSCALLS_VAL] > c1->values[SYSCALLS_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+ctx_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[CTX_VAL] < c2->values[CTX_VAL])
+ return -1;
+ if (c2->values[CTX_VAL] > c1->values[CTX_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+write_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[WRITES_VAL] < c2->values[WRITES_VAL])
+ return -1;
+ if (c2->values[WRITES_VAL] > c1->values[WRITES_VAL])
+ return 1;
+ return 0;
+}
+
+static int
+read_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[READS_VAL] < c2->values[READS_VAL])
+ return -1;
+ if (c2->values[READS_VAL] > c1->values[READS_VAL])
+ return 1;
+ return 0;
+}
+
+int
+rss_cmp(const void* x, const void* y)
+{
+ rate_entry_t *c1 = (rate_entry_t *)x;
+ rate_entry_t *c2 = (rate_entry_t *)y;
+
+ if (c1->values[RSS_VAL] < c2->values[RSS_VAL])
+ return -1;
+ if (c2->values[RSS_VAL] > c1->values[RSS_VAL])
+ return 1;
+ return 0;
+}
+
+/*
+ * sum the rate values for the top entries
+ */
+void
+calc_sum_rates(int num_entries, int val_type)
+{
+ int i;
+ double sum = 0;
+
+ for (i = 0; i < min(top, num_entries); i++) {
+ rate_entry_t *entry = &rate_tab[i];
+ sum += entry->values[val_type];
+ }
+
+ sum_rates[val_type] = sum;
+}
+
+void
+print_top(rate_entry_t *rate_tab, int num_entries, int val_type)
+{
+ static char line[2048];
+ char *ptr;
+ int i,j;
+ double x;
+
+ /* --- section heading --- */
+ if (global_rates[val_type] <= 0) {
+ printf("%s\n", format_tab[val_type].label);
+ }
+ else {
+ x = 100*sum_rates[val_type]/global_rates[val_type];
+ printf("%s - top %d processes account for %.0f%% of %s\n",
+ format_tab[val_type].label,
+ top, x>100?100:x,
+ format_tab[val_type].fullname);
+ }
+
+ /* --- fields heading --- */
+ printf(pid_label_fmt, "PID");
+ for(j=0; j < NUM_VALUES; j++) {
+ if (show_spec & (1<<j)) {
+ format_entry_t *fmt = &format_tab[j];
+ printf(fmt->label_fmt, fmt->label);
+ }
+ }
+ printf(" CMD\n");
+
+ /* units */
+ printf(pid_label_fmt, " ");
+ for(j=0; j < NUM_VALUES; j++) {
+ if (show_spec & (1<<j)) {
+ format_entry_t *fmt = &format_tab[j];
+ printf(fmt->label_fmt, fmt->units);
+ }
+ }
+ printf("\n");
+
+
+ /* --- values --- */
+ for(i=0; i < min(top, num_entries); i++) {
+ rate_entry_t *entry = &rate_tab[i];
+
+ *line = '\0';
+ ptr = line;
+
+ /* pid */
+ sprintf(ptr, pid_fmt, instances[entry->inst]);
+ ptr += strlen(ptr);
+
+ /* other values */
+ for(j=0; j < NUM_VALUES; j++) {
+ if (show_spec & (1<<j)) {
+ format_entry_t *fmt = &format_tab[j];
+ sprintf(ptr, fmt->val_fmt, entry->values[j]);
+ ptr += strlen(ptr);
+ }
+ }
+
+ /* cmd */
+ /* skip over leading pid-name in instance name */
+ sprintf(ptr, " %s", &instance_names[entry->inst][pid_len+1]);
+
+ /* ensure line fits in certain width */
+ printf(line_fmt, line);
+ printf("\n");
+ }
+
+ printf("\n");
+}
+
+void
+doit(void)
+{
+ int i, r, m, v, sts;
+ static double now = 0, then = 0;
+ double delta_d;
+ static unsigned long num_times = 0;
+ static pmResult *result[2];
+ int num_entries;
+
+ num_times++;
+
+ if (num_times > 2) {
+ pmFreeResult(result[0]);
+
+ /* if forever, ensure no wrapping */
+ if (num_samples < 0)
+ num_times = 3;
+ }
+
+ if (num_times > 1) {
+ result[0] = result[1];
+ }
+
+ get_indom();
+ if ((sts = pmFetch(num_pmid, pmidlist, &result[1])) < 0) {
+ for (i = 0; i < num_pmid; i++)
+ fprintf(stderr, "%s: pmFetch: %s\n", namelist[i], pmErrStr(sts));
+ exit(1);
+ }
+
+
+ if (num_times > 1) {
+ then = result[0]->timestamp.tv_sec + result[0]->timestamp.tv_usec/1000000;
+ now = result[1]->timestamp.tv_sec + result[1]->timestamp.tv_usec/1000000;
+ delta_d = now - then;
+
+ if (result[1]->numpmid != num_pmid) {
+ fprintf(stderr, "%s: Failed to fetch all metrics (%d out of %d)\n",
+ pmProgname, result[1]->numpmid, num_pmid);
+ exit(1);
+ }
+
+ /* --- build value table --- */
+
+ for(i=0; i<num_inst; i++) {
+ val_tab[i].num = 0;
+ }
+
+ /* go thru each result */
+ for(r=0; r<2; r++) {
+ /* go thru each metric */
+ for(m=0; m<num_pmid; m++) {
+ pmValueSet *vset = result[r]->vset[m];
+ if (vset->numval < 0) {
+ fprintf(stderr, "%s: Error when fetching metric %s: %s\n",
+ pmProgname, namelist[m], pmErrStr(vset->numval));
+ continue;
+ }
+ if (vset->numval == 0)
+ continue;
+ valfmt_tab[m] = vset->valfmt;
+
+ /* handle global metrics */
+ if (((show_spec & (SHOW_CPU | SHOW_SYS)) && m == kernel_stime) ||
+ ((show_spec & (SHOW_CPU | SHOW_USR)) && m == kernel_utime) ||
+ ((show_spec & SHOW_SYSCALLS) && m == kernel_sysc) ||
+ ((show_spec & SHOW_CTX) && m == kernel_ctx)) {
+ if (vset->numval != 1) {
+ fprintf(stderr, "%s: Wrong num of values for kernel metric: %d\n",
+ pmProgname, vset->numval);
+ exit(1);
+ }
+ global_val[m][r] = vset->vlist[0];
+ continue;
+ }
+
+ /* handle proc-instance metrics */
+
+ /* Find result inst
+ * Would be faster to start from where left off
+ * last time. Need to remember this for a metric/result.
+ */
+ for(v=0; v<vset->numval; v++) {
+ pmValue val = vset->vlist[v];
+ /* go thru each possible inst */
+ for(i=0; i<num_inst; i++) {
+ if (instances[i] == val.inst) {
+ val_tab[i].num++; /* needs to be zeroed at start */
+ val_tab[i].values[m][r] = val;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* --- calculate rates --- */
+
+ /* handle global metrics */
+ if (show_spec & SHOW_CPU) {
+ __uint64_t utime[2], stime[2];
+ double cpuburn_val;
+
+ utime[0] = get_value(kernel_utime, &global_val[kernel_utime][0]);
+ utime[1] = get_value(kernel_utime, &global_val[kernel_utime][1]);
+ stime[0] = get_value(kernel_stime, &global_val[kernel_stime][0]);
+ stime[1] = get_value(kernel_stime, &global_val[kernel_stime][1]);
+
+ cpuburn_val = (utime[1] - utime[0] + stime[1] - stime[0]) / delta_d;
+ cpuburn_val /= 10;
+ cpuburn_val = cpuburn_val > 100 ? 100 : cpuburn_val;
+ global_rates[CPU_VAL] = cpuburn_val;
+ }
+
+
+ if (show_spec & SHOW_SYSCALLS) {
+ __uint64_t sysc[2];
+
+ sysc[0] = get_value(kernel_sysc, &global_val[kernel_sysc][0]);
+ sysc[1] = get_value(kernel_sysc, &global_val[kernel_sysc][1]);
+
+ global_rates[SYSCALLS_VAL] = (sysc[1] - sysc[0]) / delta_d;
+ }
+
+ if (show_spec & SHOW_CTX) {
+ __uint64_t ctx[2];
+
+ ctx[0] = get_value(kernel_ctx, &global_val[kernel_ctx][0]);
+ ctx[1] = get_value(kernel_ctx, &global_val[kernel_ctx][1]);
+
+ global_rates[CTX_VAL] = (ctx[1] - ctx[0]) / delta_d;
+ }
+
+ if (show_spec & SHOW_SYS) {
+ __uint64_t stime[2];
+
+ stime[0] = get_value(kernel_stime, &global_val[kernel_stime][0]);
+ stime[1] = get_value(kernel_stime, &global_val[kernel_stime][1]);
+
+ global_rates[SYS_VAL] = ((stime[1] - stime[0]) / delta_d) / 10;
+ }
+
+ if (show_spec & SHOW_USR) {
+ __uint64_t utime[2];
+
+ utime[0] = get_value(kernel_utime, &global_val[kernel_utime][0]);
+ utime[1] = get_value(kernel_utime, &global_val[kernel_utime][1]);
+
+ global_rates[USR_VAL] = ((utime[1] - utime[0]) / delta_d) / 10;
+ }
+
+
+ /* handle proc-instance metrics */
+ num_entries = 0;
+ for(i=0; i<num_inst; i++) {
+ val_entry_t *vt = &val_tab[i];
+ rate_entry_t *rt;
+ if (vt->num < (num_proc_pmid*2)) {
+ /* couldn't get instances for all proc metrics for both(*2) results */
+ continue;
+ }
+
+ rt = &rate_tab[num_entries];
+ rt->inst = i;
+
+ /* create cpuburn values */
+ if (show_spec & SHOW_CPU) {
+ __uint64_t utime[2], stime[2];
+ double cpuburn_val;
+
+ utime[0] = get_value(pu_utime, &vt->values[pu_utime][0]);
+ utime[1] = get_value(pu_utime, &vt->values[pu_utime][1]);
+ stime[0] = get_value(pu_stime, &vt->values[pu_stime][0]);
+ stime[1] = get_value(pu_stime, &vt->values[pu_stime][1]);
+
+ cpuburn_val = (utime[1] - utime[0] + stime[1] - stime[0]) / delta_d;
+ cpuburn_val /= 10;
+ cpuburn_val = cpuburn_val > 100 ? 100 : cpuburn_val;
+ rt->values[CPU_VAL] = cpuburn_val;
+ }
+
+ /* create syscalls values */
+ if (show_spec & SHOW_SYSCALLS) {
+ __uint64_t sysc[2];
+
+ sysc[0] = get_value(pu_sysc, &vt->values[pu_sysc][0]);
+ sysc[1] = get_value(pu_sysc, &vt->values[pu_sysc][1]);
+
+ rt->values[SYSCALLS_VAL] = (sysc[1] - sysc[0]) / delta_d;
+ }
+
+ /* create ctx values */
+ if (show_spec & SHOW_CTX) {
+ double ictx, vctx;
+
+ vctx = get_value(pu_vctx, &vt->values[pu_vctx][1]) -
+ get_value(pu_vctx, &vt->values[pu_vctx][0]);
+ ictx = get_value(pu_ictx, &vt->values[pu_ictx][1]) -
+ get_value(pu_ictx, &vt->values[pu_ictx][0]);
+
+ rt->values[CTX_VAL] = (vctx + ictx) / delta_d;
+ }
+
+ /* create reads values */
+ if (show_spec & SHOW_READS) {
+ double br, gbr;
+
+ br = get_value(pu_bread, &vt->values[pu_bread][1]) -
+ get_value(pu_bread, &vt->values[pu_bread][0]);
+ gbr = get_value(pu_gbread, &vt->values[pu_gbread][1]) -
+ get_value(pu_gbread, &vt->values[pu_gbread][0]);
+
+ rt->values[READS_VAL] = (gbr * 1024 * 1024 + br / 1024) / delta_d;
+ }
+
+ /* create writes values */
+ if (show_spec & SHOW_WRITES) {
+ double bw, gbw;
+
+ bw = get_value(pu_bwrit, &vt->values[pu_bwrit][1]) -
+ get_value(pu_bwrit, &vt->values[pu_bwrit][0]);
+ gbw = get_value(pu_gbwrit, &vt->values[pu_gbwrit][1]) -
+ get_value(pu_gbwrit, &vt->values[pu_gbwrit][0]);
+
+ rt->values[WRITES_VAL] = (gbw * 1024 * 1024 + bw / 1024) / delta_d;
+ }
+
+ /* create rss values */
+ if (show_spec & SHOW_RSS) {
+ rt->values[RSS_VAL] = get_value(pu_rss, &vt->values[pu_rss][1]);
+ }
+
+ /* create sys cpu time values */
+ if (show_spec & SHOW_SYS) {
+ __uint64_t stime[2];
+
+ stime[0] = get_value(pu_stime, &vt->values[pu_stime][0]);
+ stime[1] = get_value(pu_stime, &vt->values[pu_stime][1]);
+
+ rt->values[SYS_VAL] = ((stime[1] - stime[0]) / delta_d) / 10;
+ rt->values[SYS_VAL] = rt->values[SYS_VAL] > 100 ? 100 : rt->values[SYS_VAL];
+ }
+
+ /* create user cpu time values */
+ if (show_spec & SHOW_USR) {
+ __uint64_t utime[2];
+
+ utime[0] = get_value(pu_utime, &vt->values[pu_utime][0]);
+ utime[1] = get_value(pu_utime, &vt->values[pu_utime][1]);
+
+ rt->values[USR_VAL] = ((utime[1] - utime[0]) / delta_d) / 10;
+ rt->values[USR_VAL] = rt->values[USR_VAL] > 100 ? 100 : rt->values[USR_VAL];
+ }
+
+
+ num_entries++;
+ }
+
+ /* --- report values --- */
+ printf("HOST: %s\n", hostname);
+ printf("DATE: %s\n\n", get_time());
+
+ if (show_spec & SHOW_CPU) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), cpuburn_cmp);
+ calc_sum_rates(num_entries, CPU_VAL);
+ print_top(rate_tab, num_entries, CPU_VAL);
+ }
+
+ if (show_spec & SHOW_SYSCALLS) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), syscall_cmp);
+ calc_sum_rates(num_entries, SYSCALLS_VAL);
+ print_top(rate_tab, num_entries, SYSCALLS_VAL);
+ }
+
+ if (show_spec & SHOW_CTX) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), ctx_cmp);
+ calc_sum_rates(num_entries, CTX_VAL);
+ print_top(rate_tab, num_entries, CTX_VAL);
+ }
+
+ if (show_spec & SHOW_WRITES) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), write_cmp);
+ print_top(rate_tab, num_entries, WRITES_VAL);
+ }
+
+ if (show_spec & SHOW_READS) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), read_cmp);
+ print_top(rate_tab, num_entries, READS_VAL);
+ }
+
+ if (show_spec & SHOW_RSS) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), rss_cmp);
+ print_top(rate_tab, num_entries, RSS_VAL);
+ }
+
+ if (show_spec & SHOW_SYS) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), sys_cmp);
+ calc_sum_rates(num_entries, SYS_VAL);
+ print_top(rate_tab, num_entries, SYS_VAL);
+ }
+
+ if (show_spec & SHOW_USR) {
+ qsort(rate_tab, num_entries, sizeof(rate_entry_t), usr_cmp);
+ calc_sum_rates(num_entries, USR_VAL);
+ print_top(rate_tab, num_entries, USR_VAL);
+ }
+ }
+
+ if (num_times-1 == num_samples) {
+ exit(0);
+ }
+}
+
+/*
+ * go thru show_spec and build up the namelist and
+ * index variables into the array
+ */
+void
+create_namelist(void)
+{
+ int i = 0; /* for each pmid */
+ int j = 0; /* for global pmids */
+
+ if (show_spec & SHOW_CPU || show_spec & SHOW_USR) {
+ namelist[i] = "proc.psusage.utime";
+ pu_utime = i;
+ i++;
+ namelist[i] = "kernel.all.cpu.user";
+ kernel_utime = i;
+ i++; j++;
+ }
+ if (show_spec & SHOW_CPU || show_spec & SHOW_SYS) {
+ namelist[i] = "proc.psusage.stime";
+ pu_stime = i;
+ i++;
+ namelist[i] = "kernel.all.cpu.sys";
+ kernel_stime = i;
+ i++; j++;
+ }
+ if (show_spec & SHOW_SYSCALLS) {
+ namelist[i] = "proc.psusage.sysc";
+ pu_sysc = i;
+ i++;
+ namelist[i] = "kernel.all.syscall";
+ kernel_sysc = i;
+ i++; j++;
+ }
+ if (show_spec & SHOW_CTX) {
+ namelist[i] = "proc.psusage.ictx";
+ pu_ictx = i;
+ i++;
+ namelist[i] = "proc.psusage.vctx";
+ pu_vctx = i;
+ i++;
+ namelist[i] = "kernel.all.pswitch";
+ kernel_ctx = i;
+ i++; j++;
+ }
+ if (show_spec & SHOW_READS) {
+ namelist[i] = "proc.psusage.bread";
+ pu_bread = i;
+ i++;
+ namelist[i] = "proc.psusage.gbread";
+ pu_gbread = i;
+ i++;
+ }
+ if (show_spec & SHOW_WRITES) {
+ namelist[i] = "proc.psusage.bwrit";
+ pu_bwrit = i;
+ i++;
+ namelist[i] = "proc.psusage.gbwrit";
+ pu_gbwrit = i;
+ i++;
+ }
+ if (show_spec & SHOW_RSS) {
+ namelist[i] = "proc.psusage.rss";
+ pu_rss = i;
+ i++;
+ }
+
+ num_pmid = i;
+ num_proc_pmid = i - j;
+}
+
+static void
+get_indom(void)
+{
+ static int onetrip = 1;
+
+ if (instances != NULL)
+ free(instances);
+ if (instance_names != NULL)
+ free(instance_names);
+
+ if ((num_inst = pmGetInDom(proc_indom, &instances, &instance_names)) <= 0) {
+ fprintf(stderr, "%s: Failed to find any proc instances : %s\n",
+ pmProgname, pmErrStr(num_inst));
+ exit(1);
+ }
+
+
+ /* process the instance names
+ * - find how long a pid is
+ * - create pid label fmt based on pid length
+ */
+ if (onetrip) {
+ char *str = instance_names[0];
+
+ onetrip=0;
+
+ /* get pid str up to first space */
+ pid_len = 0;
+ while (*str != '\0' && *str != ' ') {
+ pid_len++;
+ str++;
+ }
+ if (*str == '\0') {
+ /* shouldn't happen */
+ fprintf(stderr, "%s: Bad proc instance : %s\n",
+ pmProgname, instance_names[0]);
+ exit(1);
+ }
+
+ sprintf(pid_label_fmt, "%%%ds", pid_len);
+ sprintf(pid_fmt, "%%%dd", pid_len);
+ }
+
+
+ rate_tab = realloc(rate_tab, sizeof(rate_entry_t)*num_inst);
+ if (rate_tab == NULL) {
+ fprintf(stderr, "%s: Failed to allocate rate table\n",
+ pmProgname);
+ exit(1);
+ }
+
+ val_tab = realloc(val_tab, sizeof(val_entry_t)*num_inst);
+ if (val_tab == NULL) {
+ fprintf(stderr, "%s: Failed to allocate value table\n",
+ pmProgname);
+ exit(1);
+ }
+
+ pmDelProfile(proc_indom, 0, NULL);
+ pmAddProfile(proc_indom, num_inst, instances);
+}
+
+static int
+overrides(int opt, pmOptions *opts)
+{
+ if (opt == 'p')
+ return 1;
+
+ if (opt == 's')
+ num_samples = atoi(opts->optarg); /* continue processing 's' */
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ char *endnum;
+ pmDesc desc;
+ int one_trip = 1;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'w': /* wide flag */
+ line_fmt = "%.1024s";
+ break;
+
+ case 'p': /* show flag */
+ if (one_trip) {
+ show_spec = 0;
+ one_trip = 0;
+ }
+ if ((sts = parse_show_spec(opts.optarg)) < 0) {
+ pmprintf("%s: unrecognized print flag specification (%s)\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ } else {
+ show_spec |= sts;
+ }
+ break;
+
+ case 'm': /* top N */
+ top = (int)strtol(opts.optarg, &endnum, 10);
+ if (top <= 0) {
+ pmprintf("%s: -m requires a positive integer\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+ }
+ }
+
+ if (opts.optind < argc)
+ opts.errors++;
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ create_namelist();
+
+ if (opts.interval.tv_sec == 0)
+ opts.interval.tv_sec = 2;
+
+ if (opts.nhosts > 0)
+ hostname = opts.hosts[0];
+ else
+ hostname = "local:";
+
+ if ((sts = c = pmNewContext(PM_CONTEXT_HOST, hostname)) < 0) {
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, hostname, pmErrStr(sts));
+ exit(1);
+ }
+ hostname = (char *)pmGetContextHostName(c);
+
+ if (pmGetContextOptions(c, &opts)) {
+ pmflush();
+ exit(1);
+ }
+
+ if ((sts = pmLookupName(num_pmid, namelist, pmidlist)) < 0) {
+ fprintf(stderr, "%s: Failed to lookup metrics : %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ for (c = 0; c < num_pmid; c++) {
+ if ((sts = pmLookupDesc(pmidlist[c], &desc)) < 0) {
+ fprintf(stderr, "%s: Failed to lookup descriptor for metric \"%s\": %s\n",
+ pmProgname, namelist[c], pmErrStr(sts));
+ exit(1);
+ }
+ type_tab[c] = desc.type;
+ /* ASSUMES that the first metric will always be a proc metric */
+ if (c == 0) {
+ proc_indom = desc.indom;
+ }
+ }
+
+ for (;;) {
+ doit();
+ __pmtimevalSleep(opts.interval);
+ }
+
+ return 0;
+}
diff --git a/src/pmtrace/GNUmakefile b/src/pmtrace/GNUmakefile
new file mode 100644
index 0000000..b83d970
--- /dev/null
+++ b/src/pmtrace/GNUmakefile
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmtrace.c
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_trace/src
+LLDLIBS = $(PCP_TRACELIB)
+LCFLAGS = -I.
+
+CMDTARGET = pmtrace$(EXECSUFFIX)
+
+LDIRT = $(CMDTARGET)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmtrace/pmtrace.c b/src/pmtrace/pmtrace.c
new file mode 100644
index 0000000..745da3e
--- /dev/null
+++ b/src/pmtrace/pmtrace.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 1997-2000 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "trace.h"
+
+typedef enum { UNKNOWN, COUNTER, OBSERVE, TRANSACT } trace_t;
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ char *p;
+ char *me;
+ int err = 0;
+ int verbose = 1;
+ /* anything directly influencing the trace API is in this struct */
+ struct {
+ int state;
+ char *tag;
+ char *host;
+ trace_t type; /* COUNTER, OBSERVE, or TRANSACT */
+ union {
+ double value; /* OBSERVE/COUNTER types */
+ char *command; /* TRANSACT type */
+ } arg;
+ } api;
+
+ __pmSetProgname(argv[0]);
+ me = pmProgname;
+
+ memset(&api, 0, sizeof(api));
+
+ while ((c = getopt(argc, argv, "S:c:e:h:qv:?")) != EOF) {
+ switch (c) {
+ case 'S':
+ /* allow base 8 or 16 or 10 based on supplied value */
+ c = (int)strtol(optarg, &p, 0);
+ if (p == optarg || *p != '\0') {
+ fprintf(stderr, "%s: -S requires a numeric argument\n", me);
+ err++;
+ }
+ else
+ api.state |= c;
+ break;
+
+ case 'h':
+ api.host = optarg;
+ break;
+
+ case 'q':
+ verbose = 0;
+ break;
+
+ case 'c':
+ if (api.type != UNKNOWN) {
+ fprintf(stderr, "%s: only one of -c, -v or -e allowed\n", me);
+ err++;
+ }
+ api.type = COUNTER;
+ api.arg.value = strtod(optarg, &p);
+ if (*p != '\0') {
+ fprintf(stderr, "%s: -v requires a numeric argument\n", me);
+ err++;
+ }
+ break;
+
+ case 'e':
+ if (api.type != UNKNOWN) {
+ fprintf(stderr, "%s: only one of -c, -v or -e allowed\n", me);
+ err++;
+ }
+ api.type = TRANSACT;
+ api.arg.command = optarg;
+ break;
+
+ case 'v':
+ if (api.type != UNKNOWN) {
+ fprintf(stderr, "%s: only one of -c, -v or -e allowed\n", me);
+ err++;
+ }
+ api.type = OBSERVE;
+ api.arg.value = strtod(optarg, &p);
+ if (*p != '\0') {
+ fprintf(stderr, "%s: -v requires a numeric argument\n", me);
+ err++;
+ }
+ break;
+
+ default:
+ err++;
+ }
+ }
+
+ if (optind == argc-1) {
+ api.tag = argv[optind];
+ optind++;
+ }
+
+ if (err || optind != argc || api.tag == NULL) {
+ fprintf(stderr,
+"Usage: %s [-q] [-c value|-e command|-v value] [-h host] [-S state] tag\n\n\
+Options:\n\
+ -c value export a counter value through the trace PMDA\n\
+ -e command run command and export transaction data\n\
+ -h host send trace data to trace PMDA on given host\n\
+ -q quiet mode - suppress message from successful trace\n\
+ -S state set debug state using pmtracestate(3) as bit-wise\n\
+ combination of these flags:\n\
+ 1 Shows processing just below the API\n\
+ 2 Shows network-related activity\n\
+ 4 Shows app<->PMDA IPC traffic\n\
+ 8 Shows internal IPC buffer management\n\
+ 16 No PMDA communications at all\n\
+ -v value export an observation value through the trace PMDA\n\
+\n\
+%s always uses the asynchronous PDU protocol mode.\n", me, me);
+ exit(0);
+ }
+
+ pmtracestate(api.state | PMTRACE_STATE_ASYNC);
+ c = 0; /* reuse as the exit status */
+
+ if (api.host != NULL) {
+ if ((p = (char *)malloc(strlen(api.host) + 20)) == NULL) {
+ fprintf(stderr, "%s: malloc failed: %s\n",
+ me, pmtraceerrstr(-oserror()));
+ exit(0);
+ }
+ sprintf(p, "PCP_TRACE_HOST=%s", api.host);
+ if (putenv(p) < 0) {
+ fprintf(stderr, "%s: putenv failed: %s\n",
+ me, pmtraceerrstr(-oserror()));
+ exit(0);
+ }
+ }
+
+ switch (api.type) {
+ case COUNTER:
+ if ((err = pmtracecounter(api.tag, api.arg.value)) < 0) {
+ fprintf(stderr, "%s: counter error: %s\n",
+ me, pmtraceerrstr(err));
+ exit(0);
+ }
+ else if (verbose) {
+ printf("%s: counter complete (tag=\"%s\", value=%f)\n",
+ me, api.tag, api.arg.value);
+ }
+ break;
+
+ case OBSERVE:
+ if ((err = pmtraceobs(api.tag, api.arg.value)) < 0) {
+ fprintf(stderr, "%s: observation error: %s\n",
+ me, pmtraceerrstr(err));
+ exit(0);
+ }
+ else if (verbose) {
+ printf("%s: observation complete (tag=\"%s\", value=%f)\n",
+ me, api.tag, api.arg.value);
+ }
+ break;
+
+ case TRANSACT:
+ if ((err = pmtracebegin(api.tag)) < 0) {
+ fprintf(stderr, "%s: transaction begin error: %s\n",
+ me, pmtraceerrstr(err));
+ /* don't exit yet - execute cmd anyway, just no tracing */
+ }
+
+ if ((c = system(api.arg.command)) < 0) {
+ fprintf(stderr, "%s: system error running '%s': %s\n",
+ me, api.arg.command, pmtraceerrstr(-oserror()));
+ exit(0);
+ }
+ if (err < 0)
+ exit(c);
+
+ if ((err = pmtraceend(api.tag)) < 0) {
+ fprintf(stderr, "%s: transaction end error: %s\n",
+ me, pmtraceerrstr(err));
+ exit(c);
+ }
+ else if (verbose) {
+ printf("%s: transaction complete (tag=\"%s\")\n", me, api.tag);
+ }
+ break;
+
+ default:
+ if ((err = pmtracepoint(api.tag)) < 0) {
+ fprintf(stderr, "%s: point error: %s\n",
+ me, pmtraceerrstr(err));
+ exit(0);
+ }
+ else if (verbose) {
+ printf("%s: point complete (tag=\"%s\")\n", me, api.tag);
+ }
+ }
+ exit(c);
+}
diff --git a/src/pmval/GNUmakefile b/src/pmval/GNUmakefile
new file mode 100644
index 0000000..94a0351
--- /dev/null
+++ b/src/pmval/GNUmakefile
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pmval.c
+LLDFLAGS = -L$(TOPDIR)/src/libpcp_gui/src
+LLDLIBS = $(PCP_GUILIB) $(LIB_FOR_MATH)
+
+CMDTARGET = pmval$(EXECSUFFIX)
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+
+default_pcp: default
+
+install_pcp: install
+
diff --git a/src/pmval/pmval.c b/src/pmval/pmval.c
new file mode 100644
index 0000000..44dd8ba
--- /dev/null
+++ b/src/pmval/pmval.c
@@ -0,0 +1,1141 @@
+/*
+ * pmval - simple performance metrics value dumper
+ *
+ * Copyright (c) 2014 Red Hat.
+ * Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <math.h>
+#include "pmapi.h"
+#include "impl.h"
+#include "pmtime.h"
+
+/* instance id - instance name association */
+typedef struct {
+ int id;
+ char *name;
+} InstPair;
+
+/* full description of a performance metric */
+typedef struct {
+ /* external (printable) description */
+ const char *hostname;
+ char *metric; /* name of metric */
+ int iall; /* all instances */
+ int inum; /* number of instances */
+ char **inames; /* list of instance names */
+ /* internal description */
+ int handle; /* context handle */
+ pmID pmid; /* metric identifier */
+ pmDesc desc; /* metric description */
+ float scale; /* conversion factor for rate */
+ int *iids; /* list of instance ids */
+ /* internal-external association */
+ InstPair *ipairs; /* sorted array of id-name */
+} Context;
+
+static pmLongOptions longopts[] = {
+ PMAPI_GENERAL_OPTIONS,
+ PMOPT_SPECLOCAL,
+ PMOPT_LOCALPMDA,
+ PMAPI_OPTIONS_HEADER("Reporting options"),
+ { "delay", 0, 'd', 0, "delay, pause between updates for archive replay" },
+ { "precision", 1, 'f', "N", "fixed output format with N digits precision" },
+ { "instances", 1, 'i', "INST", "comma-separated metrics instance list" },
+ { "raw", 0, 'r', 0, "output raw counter values (no rate conversion)" },
+ { "nointerp", 1, 'U', "FILE", "non-interpolated fetching; ignores interval" },
+ { "width", 1, 'w', "N", "set the width of each column of output" },
+ PMAPI_OPTIONS_END
+};
+
+static int override(int, pmOptions *);
+static pmOptions opts = {
+ .flags = PM_OPTFLAG_DONE | PM_OPTFLAG_BOUNDARIES | PM_OPTFLAG_STDOUT_TZ,
+ .short_options = PMAPI_OPTIONS "df:i:K:LrU:w:",
+ .long_options = longopts,
+ .short_usage = "[options] metricname",
+ .override = override,
+};
+
+static int pauseFlag;
+static int rawCounter;
+static int rawArchive;
+static int fixed = -1;
+static int nosamples;
+static int cols; /* width of output column(s) */
+static int havePrev; /* have one sample, can compute rate */
+static int amode = PM_MODE_INTERP; /* archive scan mode */
+static char * source;
+static char * tzlabel;
+static pmTime * pmtime;
+static pmTimeControls controls;
+static Context context;
+
+/*
+ * Processing fetched values
+ */
+
+/* Compare two InstPair's on their id fields.
+ - This function is passed as an argument to qsort,
+ hence the ugly casts. */
+static int /* -1 less, 0 equal, 1 greater */
+compare(const void *pair1, const void *pair2)
+{
+ if (((InstPair *)pair1)->id < ((InstPair *)pair2)->id) return -1;
+ if (((InstPair *)pair1)->id > ((InstPair *)pair2)->id) return 1;
+ return 0;
+}
+
+/* Does the Context have names for all instances in the pmValueSet? */
+static int /* 1 yes, 0 no */
+chkinsts(Context *x, pmValueSet *vs)
+{
+ int i, j;
+
+ if (x->desc.indom == PM_INDOM_NULL)
+ return 1;
+
+ for (i = 0; i < vs->numval; i++) {
+ for (j = 0; j < x->inum; j++) {
+ if (vs->vlist[i].inst == x->ipairs[j].id)
+ break;
+ }
+ if (j == x->inum)
+ return 0;
+ }
+ return 1;
+}
+
+
+/* Fill in current instances into given Context.
+ Instances sorted by instance identifier. */
+static void
+initinsts(Context *x)
+{
+ int *ip;
+ char **np;
+ InstPair *pp;
+ int n;
+ int e;
+ int i;
+
+ if (x->desc.indom == PM_INDOM_NULL)
+ x->inum = 0;
+ else {
+
+ /* fill in instance ids for given profile */
+ if (! x->iall) {
+ n = x->inum;
+ np = x->inames;
+ ip = (int *)malloc(n * sizeof(int));
+ if (ip == NULL) {
+ __pmNoMem("pmval.ip", n * sizeof(int), PM_FATAL_ERR);
+ }
+ x->iids = ip;
+ for (i = 0; i < n; i++) {
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ e = pmLookupInDomArchive(x->desc.indom, *np);
+ else
+ e = pmLookupInDom(x->desc.indom, *np);
+ if (e < 0) {
+ printf("%s: instance %s not available\n", pmProgname, *np);
+ exit(EXIT_FAILURE);
+ }
+ *ip = e;
+ np++; ip++;
+ }
+ ip = x->iids;
+ np = x->inames;
+ if ((e = pmAddProfile(x->desc.indom, x->inum, x->iids)) < 0) {
+ fprintf(stderr, "%s: pmAddProfile: %s\n", pmProgname, pmErrStr(e));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* find all available instances */
+ else {
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ n = pmGetInDomArchive(x->desc.indom, &ip, &np);
+ else
+ n = pmGetInDom(x->desc.indom, &ip, &np);
+ if (n < 0) {
+ fprintf(stderr, "%s: pmGetInDom(%s): %s\n", pmProgname, pmInDomStr(x->desc.indom), pmErrStr(n));
+ exit(EXIT_FAILURE);
+ }
+ x->inum = n;
+ x->iids = ip;
+ x->inames = np;
+ }
+
+ /* build InstPair list and sort */
+ pp = (InstPair *)malloc(n * sizeof(InstPair));
+ if (pp == NULL) {
+ __pmNoMem("pmval.pp", n * sizeof(InstPair), PM_FATAL_ERR);
+ }
+ x->ipairs = pp;
+ for (i = 0; i < n; i++) {
+ pp->id = *ip;
+ pp->name = *np;
+ ip++; np++; pp++;
+ }
+ qsort(x->ipairs, (size_t)n, sizeof(InstPair), compare);
+ }
+}
+
+/* Initialize API and fill in internal description for given Context. */
+static void
+initapi(Context *x)
+{
+ int e;
+
+ x->handle = pmWhichContext();
+
+ if ((e = pmLookupName(1, &(x->metric), &(x->pmid))) < 0) {
+ fprintf(stderr, "%s: pmLookupName(%s): %s\n", pmProgname, x->metric, pmErrStr(e));
+ exit(EXIT_FAILURE);
+ }
+
+ if ((e = pmLookupDesc(x->pmid, &(x->desc))) < 0) {
+ fprintf(stderr, "%s: pmLookupDesc: %s\n", pmProgname, pmErrStr(e));
+ exit(EXIT_FAILURE);
+ }
+
+ if (x->desc.sem == PM_SEM_COUNTER) {
+ if (x->desc.units.dimTime == 0)
+ x->scale = 1.0;
+ else {
+ if (x->desc.units.scaleTime > PM_TIME_SEC)
+ x->scale = pow(60, (PM_TIME_SEC - x->desc.units.scaleTime));
+ else
+ x->scale = pow(1000, (PM_TIME_SEC - x->desc.units.scaleTime));
+ }
+ }
+}
+
+/* Fetch metric values. */
+static int
+getvals(Context *x, /* in - full pm description */
+ pmResult **vs) /* alloc - pm values */
+{
+ pmResult *r;
+ int e;
+ int i;
+
+ if (rawArchive) {
+ /*
+ * for -U mode, read until we find either a pmResult with the
+ * pmid we are after, or a mark record
+ */
+ for ( ; ; ) {
+ e = pmFetchArchive(&r);
+ if (e < 0)
+ break;
+
+ if (r->numpmid == 0) {
+ if (opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE)
+ __pmPrintStamp(stdout, &r->timestamp);
+ printf(" Archive logging suspended\n");
+ pmFreeResult(r);
+ return -1;
+ }
+
+ for (i = 0; i < r->numpmid; i++) {
+ if (r->vset[i]->pmid == x->pmid)
+ break;
+ }
+ if (i != r->numpmid)
+ break;
+ pmFreeResult(r);
+ }
+ }
+ else {
+ e = pmFetch(1, &(x->pmid), &r);
+ i = 0;
+ }
+
+ if (e < 0) {
+ if (e == PM_ERR_EOL && opts.guiflag) {
+ pmTimeStateBounds(&controls, pmtime);
+ return -1;
+ }
+ if (rawArchive)
+ fprintf(stderr, "\n%s: pmFetchArchive: %s\n", pmProgname, pmErrStr(e));
+ else
+ fprintf(stderr, "\n%s: pmFetch: %s\n", pmProgname, pmErrStr(e));
+ exit(EXIT_FAILURE);
+ }
+
+ if (opts.guiflag)
+ pmTimeStateAck(&controls, pmtime);
+
+ if ((double)r->timestamp.tv_sec + (double)r->timestamp.tv_usec/1000000 >
+ (double)opts.finish.tv_sec + (double)opts.finish.tv_usec/1000000) {
+ pmFreeResult(r);
+ return -2;
+ }
+
+ if (r->vset[i]->numval == 0) {
+ if (opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE) {
+ __pmPrintStamp(stdout, &r->timestamp);
+ printf(" ");
+ }
+ printf("No values available\n");
+ pmFreeResult(r);
+ return -1;
+ }
+ else if (r->vset[i]->numval < 0) {
+ if (rawArchive)
+ fprintf(stderr, "\n%s: pmFetchArchive: %s\n", pmProgname, pmErrStr(r->vset[i]->numval));
+ else
+ fprintf(stderr, "\n%s: pmFetch: %s\n", pmProgname, pmErrStr(r->vset[i]->numval));
+ pmFreeResult(r);
+ return -1;
+ }
+
+ *vs = r;
+ qsort(r->vset[i]->vlist,
+ (size_t)r->vset[i]->numval,
+ sizeof(pmValue),
+ compare);
+
+ return i;
+}
+
+static void
+timestep(struct timeval delta)
+{
+ /* time moved, may need to wait for previous value again */
+ havePrev = 0;
+}
+
+
+/* How many print positions required for value of given type? */
+static int
+howide(int type)
+{
+ switch (type) {
+ case PM_TYPE_32: return 11;
+ case PM_TYPE_U32: return 11;
+ case PM_TYPE_64: return 21;
+ case PM_TYPE_U64: return 21;
+ case PM_TYPE_FLOAT: return 13;
+ case PM_TYPE_DOUBLE: return 21;
+ case PM_TYPE_STRING: return 21;
+ case PM_TYPE_AGGREGATE: return 21;
+ default:
+ fprintf(stderr, "pmval: unknown performance metric value type %s\n", pmTypeStr(type));
+ exit(EXIT_FAILURE);
+ }
+}
+
+/* Print parameter values as output header. */
+static void
+printhdr(Context *x)
+{
+ pmUnits units;
+ char tbfr[26];
+ const char *u;
+
+ printf("metric: %s\n", x->metric);
+
+ if (opts.context != PM_CONTEXT_ARCHIVE)
+ printf("host: %s\n", x->hostname);
+ else {
+ printf("archive: %s\n", source);
+ printf("host: %s\n", x->hostname);
+ printf("start: %s", pmCtime(&opts.origin.tv_sec, tbfr));
+ if (opts.finish.tv_sec != INT_MAX)
+ printf("end: %s", pmCtime(&opts.finish.tv_sec, tbfr));
+ }
+
+ printf("semantics: ");
+ switch (x->desc.sem) {
+ case PM_SEM_COUNTER:
+ printf("cumulative counter");
+ if (! rawCounter) printf(" (converting to rate)");
+ break;
+ case PM_SEM_INSTANT:
+ printf("instantaneous value");
+ break;
+ case PM_SEM_DISCRETE:
+ printf("discrete instantaneous value");
+ break;
+ default:
+ printf("unknown");
+ }
+ putchar('\n');
+
+ units = x->desc.units;
+ u = pmUnitsStr(&units);
+ printf("units: %s", *u == '\0' ? "none" : u);
+ if ((! rawCounter) && (x->desc.sem == PM_SEM_COUNTER)) {
+ printf(" (converting to ");
+ if (units.dimTime == 0) units.scaleTime = PM_TIME_SEC;
+ units.dimTime--;
+ if ((units.dimSpace == 0) && (units.dimTime == 0) && (units.dimCount == 0))
+ printf("time utilization)");
+ else {
+ u = pmUnitsStr(&units);
+ printf("%s)", *u == '\0' ? "none" : u);
+ }
+ }
+ putchar('\n');
+
+ /* sample count and interval */
+ if (opts.samples < 0) printf("samples: all\n");
+ else printf("samples: %d\n", opts.samples);
+ if ((opts.samples > 1) &&
+ (opts.context != PM_CONTEXT_ARCHIVE || amode == PM_MODE_INTERP))
+ printf("interval: %1.2f sec\n", __pmtimevalToReal(&opts.interval));
+}
+
+/* Print instance identifier names as column labels. */
+static void
+printlabels(Context *x)
+{
+ int n = x->inum;
+ InstPair *pairs = x->ipairs;
+ int i;
+ static int style = -1;
+
+ if (style == -1) {
+ InstPair *ip = pairs;
+ style = 0;
+ for (i = 0; i < n; i++) {
+ if (strlen(ip->name) > cols) {
+ style = 2; /* too wide */
+ break;
+ }
+ if (strlen(ip->name) > cols-3)
+ style = 1; /* wide enough to change shift */
+ ip++;
+ }
+ if (style == 2) {
+ ip = pairs;
+ for (i = 0; i < n; i++) {
+ printf("full label for instance[%d]: %s\n", i, ip->name);
+ ip++;
+ }
+ }
+ }
+
+ putchar('\n');
+ for (i = 0; i < n; i++) {
+ if ((opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE) && i == 0)
+ printf(" ");
+ if (rawCounter || (x->desc.sem != PM_SEM_COUNTER) || style != 0)
+ printf("%*.*s ", cols, cols, pairs->name);
+ else {
+ if (fixed == -1) {
+ /* shift left by 3 places for decimal points in rate */
+ printf("%*.*s ", cols-3, cols-3, pairs->name);
+ }
+ else {
+ /* no shift for fixed format */
+ printf("%*.*s ", cols, cols, pairs->name);
+ }
+ }
+ pairs++;
+ }
+ if (n > 0) putchar('\n');
+}
+
+void
+printreal(double v, int minwidth)
+{
+ char *fmt;
+
+ /*
+ * <-- minwidth -->
+ * xxxxxxxxxxxxxxxxx
+ * ! no value
+ * x.xxxE-xx < 0.1
+ * 0.0___ 0
+ * x.xxxx 0.1 ... 0.9999
+ * x.xxx_ 1 ... 9.999
+ * xx.xx__ 10 ... 99.99
+ * xxx.x___ 100 ... 999.9
+ * xxxx.____ 1000 ... 9999
+ * x.xxxE+xx > 9999
+ */
+
+ if (fixed != -1) {
+ printf("%*.*f", minwidth, fixed, v);
+ }
+ else {
+ if (v < 0.0)
+ printf("%*s", minwidth, "!");
+ else {
+ if (v == 0) {
+ fmt = "%*.0f.0 ";
+ minwidth -= 5;
+ }
+ else if (v < 0.1 || v > 9999)
+ fmt = "%*.3E";
+ else if (v <= 0.9999)
+ fmt = "%*.4f";
+ else if (v <= 9.999) {
+ fmt = "%*.3f ";
+ minwidth -= 1;
+ }
+ else if (v <= 99.99) {
+ fmt = "%*.2f ";
+ minwidth -= 2;
+ }
+ else if (v <= 999.9) {
+ fmt = "%*.1f ";
+ minwidth -= 3;
+ }
+ else {
+ fmt = "%*.0f. ";
+ minwidth -= 5;
+ }
+ printf(fmt, minwidth, v);
+ }
+ }
+}
+
+/* Print performance metric values */
+static void
+printvals(Context *x, pmValueSet *vset, int cols)
+{
+ int i, j;
+ pmAtomValue av;
+ int doreal = 0;
+
+ if (x->desc.type == PM_TYPE_FLOAT || x->desc.type == PM_TYPE_DOUBLE)
+ doreal = 1;
+
+ /* null instance domain */
+ if (x->desc.indom == PM_INDOM_NULL) {
+ if (vset->numval == 1) {
+ if (doreal) {
+ pmExtractValue(vset->valfmt, &vset->vlist[0], x->desc.type, &av, PM_TYPE_DOUBLE);
+ printreal(av.d, cols);
+ }
+ else
+ pmPrintValue(stdout, vset->valfmt, x->desc.type, &vset->vlist[0], cols);
+
+ }
+ else
+ printf("%*s", cols, "?");
+ putchar('\n');
+ }
+
+ /* non-null instance domain */
+ else {
+ for (i = 0; i < x->inum; i++) {
+ for (j = 0; j < vset->numval; j++) {
+ if (vset->vlist[j].inst == x->ipairs[i].id)
+ break;
+ }
+ if (j < vset->numval) {
+ if (doreal) {
+ pmExtractValue(vset->valfmt, &vset->vlist[j], x->desc.type, &av, PM_TYPE_DOUBLE);
+ printreal(av.d, cols);
+ }
+ else
+ pmPrintValue(stdout, vset->valfmt, x->desc.type, &vset->vlist[j], cols);
+ }
+ else
+ printf("%*s", cols, "?");
+ putchar(' ');
+ }
+ putchar('\n');
+
+ for (j = 0; j < vset->numval; j++) {
+ for (i = 0; i < x->inum; i++) {
+ if (vset->vlist[j].inst == x->ipairs[i].id)
+ break;
+ }
+ if (x->iall == 1 && i == x->inum) {
+ printf("Warning: value=");
+ if (doreal) {
+ pmExtractValue(vset->valfmt, &vset->vlist[j], x->desc.type, &av, PM_TYPE_DOUBLE);
+ printreal(av.d, 1);
+ }
+ else
+ pmPrintValue(stdout, vset->valfmt, x->desc.type, &vset->vlist[j], 1);
+ printf(", but instance=%d is unknown\n", vset->vlist[j].inst);
+ }
+ }
+ }
+}
+
+/* Print single performance metric rate value */
+static void
+printrate(int valfmt, /* from pmValueSet */
+ int type, /* from pmDesc */
+ pmValue *val1, /* current value */
+ pmValue *val2, /* previous value */
+ double delta, /* time difference between samples */
+ int minwidth) /* output is at least this wide */
+{
+ pmAtomValue a, b;
+ double v;
+ static int dowrap = -1;
+
+ pmExtractValue(valfmt, val1, type, &a, PM_TYPE_DOUBLE);
+ pmExtractValue(valfmt, val2, type, &b, PM_TYPE_DOUBLE);
+ v = a.d - b.d;
+ if (v < 0.0) {
+ if (dowrap == -1) {
+ /* PCP_COUNTER_WRAP in environment enables "counter wrap" logic */
+ if (getenv("PCP_COUNTER_WRAP") == NULL)
+ dowrap = 0;
+ else
+ dowrap = 1;
+ }
+ if (dowrap) {
+ switch (type) {
+ case PM_TYPE_32:
+ case PM_TYPE_U32:
+ v += (double)UINT_MAX+1;
+ break;
+ case PM_TYPE_64:
+ case PM_TYPE_U64:
+ v += (double)ULONGLONG_MAX+1;
+ break;
+ }
+ }
+ }
+ v /= delta;
+ printreal(v, minwidth);
+}
+
+/* Print performance metric rates */
+static void
+printrates(Context *x,
+ pmValueSet *vset1, struct timeval stamp1, /* current values */
+ pmValueSet *vset2, struct timeval stamp2, /* previous values */
+ int cols)
+{
+ int i, j;
+ double delta;
+
+ /* compute delta from timestamps and convert units */
+ delta = x->scale *
+ (__pmtimevalToReal(&stamp1) - __pmtimevalToReal(&stamp2));
+
+ /* null instance domain */
+ if (x->desc.indom == PM_INDOM_NULL) {
+ if ((vset1->numval == 1) && (vset2->numval == 1))
+ printrate(vset1->valfmt, x->desc.type, &vset1->vlist[0], &vset2->vlist[0], delta, cols);
+ else
+ printf("%*s", cols, "?");
+ putchar('\n');
+ }
+
+ /* non-null instance domain */
+ else {
+ for (i = 0; i < x->inum; i++) {
+ for (j = 0; j < vset1->numval; j++) {
+ if (vset1->vlist[j].inst == x->ipairs[i].id)
+ break;
+ }
+ if ((j < vset1->numval) && (j < vset2->numval) &&
+ (vset1->vlist[j].inst == vset2->vlist[j].inst))
+ printrate(vset1->valfmt, x->desc.type, &vset1->vlist[j], &vset2->vlist[j], delta, cols);
+ else
+ printf("%*s", cols, "?");
+ putchar(' ');
+ }
+ putchar('\n');
+
+ for (j = 0; j < vset1->numval; j++) {
+ for (i = 0; i < x->inum; i++) {
+ if (vset1->vlist[j].inst == x->ipairs[i].id)
+ break;
+ }
+ if (x->iall == 1 && i == x->inum && j < vset2->numval &&
+ vset1->vlist[j].inst == vset2->vlist[j].inst) {
+ printf("Warning: value=");
+ printrate(vset1->valfmt, x->desc.type, &vset1->vlist[j], &vset2->vlist[j], delta, 1);
+ printf(", but instance=%d is unknown\n", vset1->vlist[j].inst);
+ }
+ }
+ }
+}
+
+
+/*
+ * like isspace, but for a string
+ */
+static int
+hasspace(char *p)
+{
+ static const char *whitespace = ", \t\n";
+ char *set = (char *)whitespace;
+
+ if (p != NULL && *p) {
+ while (*set) {
+ if (*p == *set)
+ return 1;
+ set++;
+ }
+ }
+ return 0;
+}
+
+/*
+ * like strtok, but smarter
+ */
+static char *
+getinstance(char *p)
+{
+ static char *save;
+ char quot;
+ char *q;
+ char *start;
+
+ if (p == NULL)
+ q = save;
+ else
+ q = p;
+
+ while (hasspace(q))
+ q++;
+
+ if (*q == '\0')
+ return NULL;
+ else if (*q == '"' || *q == '\'') {
+ quot = *q;
+ start = ++q;
+
+ while (*q && *q != quot)
+ q++;
+ if (*q == quot)
+ *q++ = '\0';
+ }
+ else {
+ start = q;
+ while (*q && !hasspace(q))
+ q++;
+ }
+ if (*q)
+ *q++ = '\0';
+ save = q;
+
+ return start;
+}
+
+static int
+override(int opt, pmOptions *opts)
+{
+ /* need to distinguish between zero argument or not requested */
+ if (opt == 's') {
+ if (atoi(opts->optarg) == 0)
+ nosamples = 1;
+ }
+ return 0; /* continue on with using the common code, always */
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int i;
+ int ctx;
+ int sts;
+ int idx1;
+ int idx2 = 0; /* initialize to pander to gcc */
+ int forever;
+ int no_values = 0;
+ char *subopt;
+ char *endnum;
+ char *errmsg;
+ pmMetricSpec *msp;
+ pmResult *rslt1; /* current values */
+ pmResult *rslt2; /* previous values */
+
+ setlinebuf(stdout);
+ context.iall = 1;
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+
+ case 'd':
+ pauseFlag = 1;
+ break;
+
+ case 'f': /* fixed format count */
+ fixed = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || fixed < 0) {
+ pmprintf("%s: -f requires +ve numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ case 'i': /* instance names */
+ context.iall = 0;
+ i = context.inum;
+ subopt = getinstance(opts.optarg);
+ while (subopt != NULL) {
+ i++;
+ context.inames =
+ (char **)realloc(context.inames, i * (sizeof (char *)));
+ if (context.inames == NULL)
+ __pmNoMem("pmval.ip", i * sizeof(char *), PM_FATAL_ERR);
+ *(context.inames + i - 1) = subopt;
+ subopt = getinstance(NULL);
+ }
+ context.inum = i;
+ break;
+
+ case 'r':
+ rawCounter = 1;
+ break;
+
+ case 'U': /* non-interpolated archive (undocumented) */
+ __pmAddOptArchive(&opts, opts.optarg);
+ amode = PM_MODE_FORW;
+ rawArchive = 1;
+ break;
+
+ case 'w': /* output column width */
+ cols = (int)strtol(opts.optarg, &endnum, 10);
+ if (*endnum != '\0' || cols < 1) {
+ pmprintf("%s: -w requires +ve numeric argument\n", pmProgname);
+ opts.errors++;
+ }
+ break;
+
+ default:
+ opts.errors++;
+ break;
+ }
+ }
+
+ if (!opts.errors && opts.optind >= argc) {
+ pmprintf("Error: no metricname specified\n\n");
+ opts.errors++;
+ }
+ else if (!opts.errors && opts.optind < argc - 1) {
+ pmprintf("Error: pmval can only process one metricname at a time\n\n");
+ opts.errors++;
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(EXIT_FAILURE);
+ }
+
+ /* parse uniform metric spec */
+ if (opts.nhosts > 0) {
+ source = opts.hosts[0];
+ i = 0;
+ }
+ else if (opts.narchives > 0) {
+ source = opts.archives[0];
+ i = 1;
+ }
+ else if (opts.Lflag) {
+ source = NULL;
+ i = 2;
+ }
+ else {
+ source = "local:";
+ i = 0;
+ }
+ if (pmParseMetricSpec(argv[opts.optind], i, source, &msp, &errmsg) < 0) {
+ pmprintf("%s", errmsg);
+ free(errmsg);
+ opts.errors++;
+ }
+ else if (msp->isarch == 1) {
+ if (opts.narchives == 0)
+ __pmAddOptArchive(&opts, msp->source);
+ source = msp->source;
+ }
+ else if (msp->isarch == 2) {
+ opts.Lflag = 1;
+ source = NULL;
+ }
+ else {
+ if (opts.nhosts == 0)
+ __pmAddOptHost(&opts, msp->source);
+ source = msp->source;
+ }
+
+ /*
+ * As a result of allowing either getopts or the metricspec to specify
+ * the source of the metric, we delay option end processing until now.
+ */
+ opts.flags &= ~PM_OPTFLAG_DONE;
+ __pmEndOptions(&opts);
+
+ if (opts.context != PM_CONTEXT_ARCHIVE) {
+ if (rawArchive) {
+ pmprintf("%s: uninterpolated mode can only be used with archives",
+ pmProgname);
+ opts.errors++;
+ }
+ if (pauseFlag) {
+ pmprintf("%s: delay can only be used with archives\n", pmProgname);
+ opts.errors++;
+ }
+ }
+ else {
+ if (opts.guiflag && pauseFlag) {
+ pmprintf("%s: guiflag cannot be used with delay\n", pmProgname);
+ opts.errors++;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(EXIT_FAILURE);
+ }
+
+ context.metric = msp->metric;
+ if (msp->ninst > 0) {
+ context.inum = msp->ninst;
+ context.iall = (context.inum == 0);
+ context.inames = &msp->inst[0];
+ }
+
+ if ((sts = ctx = pmNewContext(opts.context, source)) < 0) {
+ if (opts.context == PM_CONTEXT_ARCHIVE)
+ fprintf(stderr, "%s: Cannot open archive \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ else if (opts.context == PM_CONTEXT_HOST)
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
+ pmProgname, source, pmErrStr(sts));
+ else
+ fprintf(stderr, "%s: Cannot establish local context: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
+ context.hostname = pmGetContextHostName(ctx);
+ if (strlen(context.hostname) == 0) {
+ fprintf(stderr, "%s: Cannot evaluate context host name: %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(EXIT_FAILURE);
+ }
+
+ if (pmGetContextOptions(ctx, &opts) < 0) {
+ pmflush(); /* runtime errors only at this stage */
+ exit(EXIT_FAILURE);
+ }
+ if (opts.timezone)
+ tzlabel = opts.timezone;
+ else {
+ if (!opts.tzflag)
+ printf("\n");
+ tzlabel = (char *)context.hostname;
+ }
+
+ if (!opts.samples && !nosamples)
+ opts.samples = -1;
+ if (!opts.guiport)
+ opts.guiport = -1;
+ if (!opts.finish.tv_sec)
+ opts.finish.tv_sec = INT_MAX;
+ if (opts.interval.tv_sec == 0 && opts.interval.tv_usec == 0)
+ opts.interval.tv_sec = 1;
+
+ initapi(&context);
+ initinsts(&context);
+
+ if (!(opts.guiflag || opts.guiport != -1) &&
+ opts.samples < 0 &&
+ opts.finish.tv_sec != INT_MAX &&
+ amode != PM_MODE_FORW) {
+ double start, finish, origin, delta;
+
+ start = __pmtimevalToReal(&opts.start);
+ finish = __pmtimevalToReal(&opts.finish);
+ origin = __pmtimevalToReal(&opts.origin);
+ delta = __pmtimevalToReal(&opts.interval);
+
+ opts.samples = (int) ((finish - origin) / delta);
+ if (opts.samples < 0)
+ opts.samples = 0; /* if end is before start, no samples thanks */
+ else {
+ /*
+ * p stands for posn
+ * + p + p+delta + p+2*delta + p+3*delta + last
+ * | | | | | |
+ * +-----------+-----------+-----------+-- ...... ----+---+---> time
+ * 1 2 3 smpls
+ *
+ * So we will perform smpls+1 fetches ... the number of reported
+ * values cannot be determined as it is usually (but not always
+ * thanks to interpolation mode in archives) one less for
+ * PM_SEM_COUNTER metrics.
+ *
+ * samples: as reported in the header output is the number
+ * of fetches to be attempted.
+ */
+ opts.samples++;
+ }
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ char tbfr[26];
+ char *tp;
+ fprintf(stderr, "getargs: first=%.6f", start);
+ tp = pmCtime(&opts.start.tv_sec, tbfr);
+ /*
+ * tp -> Ddd Mmm DD HH:MM:SS YYYY\n
+ * 0 4 8 1 1 2 2 2
+ * 1 8 0 3 4
+ */
+ fprintf(stderr, "[%8.8s]\n", &tp[11]);
+ fprintf(stderr, "getargs: posn=%.6f", origin);
+ tp = pmCtime(&opts.origin.tv_sec, tbfr);
+ fprintf(stderr, "[%8.8s]\n", &tp[11]);
+ fprintf(stderr, "getargs: last=%.6f", finish);
+ tp = pmCtime(&opts.finish.tv_sec, tbfr);
+ fprintf(stderr, "[%8.8s]\n", &tp[11]);
+ fprintf(stderr, "getargs: delta=%.6f samples=%d\n",
+ delta, opts.samples);
+ }
+#endif
+ }
+
+ if (opts.guiflag || opts.guiport != -1) {
+ /* set up pmtime control */
+ pmWhichZone(&opts.timezone);
+ pmtime = pmTimeStateSetup(&controls, opts.context, opts.guiport,
+ opts.interval, opts.origin, opts.start,
+ opts.finish, opts.timezone, tzlabel);
+ controls.stepped = timestep;
+ opts.guiflag = 1; /* we're using pmtime control from here on */
+ }
+ else if (opts.context == PM_CONTEXT_ARCHIVE) /* no time control, go it alone */
+ pmTimeStateMode(amode, opts.interval, &opts.origin);
+
+ /* TODO: bring logic from pmevent, reunify the two binaries */
+ if (context.desc.type == PM_TYPE_EVENT ||
+ context.desc.type == PM_TYPE_HIGHRES_EVENT) {
+ fprintf(stderr, "%s: Cannot display values for event type metrics\n",
+ pmProgname);
+ exit(EXIT_FAILURE);
+ }
+
+ forever = (opts.samples < 0 || opts.guiflag);
+
+ if (cols <= 0)
+ cols = howide(context.desc.type);
+
+ if ((fixed == 0 && fixed > cols) || (fixed > 0 && fixed > cols - 2)) {
+ fprintf(stderr, "%s: -f %d too large for column width %d\n",
+ pmProgname, fixed, cols);
+ exit(EXIT_FAILURE);
+ }
+
+ printhdr(&context);
+
+ /* wait till time for first sample */
+ if (opts.context != PM_CONTEXT_ARCHIVE)
+ __pmtimevalPause(opts.start);
+
+ /* main loop fetching and printing sample values */
+ while (forever || (opts.samples-- > 0)) {
+ if (opts.guiflag)
+ pmTimeStateVector(&controls, pmtime);
+ if (havePrev == 0) {
+ /*
+ * We don't yet have a value at the previous time point ...
+ * save this value so we can use it to compute the rate if
+ * the metric has counter semantics and we're doing rate
+ * conversion.
+ */
+ if ((idx2 = getvals(&context, &rslt2)) >= 0) {
+ /* previous value success */
+ havePrev = 1;
+ if (context.desc.indom != PM_INDOM_NULL)
+ printlabels(&context);
+ if (rawCounter || (context.desc.sem != PM_SEM_COUNTER)) {
+ /* not doing rate conversion, report this value immediately */
+ if (opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE)
+ __pmPrintStamp(stdout, &rslt2->timestamp);
+ printvals(&context, rslt2->vset[idx2], cols);
+ continue;
+ }
+ else if (no_values) {
+ if (opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE) {
+ __pmPrintStamp(stdout, &rslt2->timestamp);
+ printf(" ");
+ }
+ printf("No values available\n");
+ }
+ no_values = 0;
+ if (opts.guiflag)
+ /* pmtime controls timing */
+ continue;
+ }
+ else if (idx2 == -2)
+ /* out the end of the window */
+ break;
+ else
+ no_values = 1;
+ }
+
+ /* wait till time for sample */
+ if (!opts.guiflag && (pauseFlag || opts.context != PM_CONTEXT_ARCHIVE))
+ __pmtimevalSleep(opts.interval);
+
+ if (havePrev == 0)
+ continue; /* keep trying to get the previous sample */
+
+ /* next sample */
+ if ((idx1 = getvals(&context, &rslt1)) == -2)
+ /* out the end of the window */
+ break;
+ else if (idx1 < 0) {
+ /*
+ * Fall back to trying to get an initial sample because
+ * although we got the previous sample, we failed to get the
+ * next sample.
+ */
+ havePrev = 0;
+ continue;
+ }
+
+ /* refresh instance names */
+ if (context.iall && ! chkinsts(&context, rslt1->vset[idx1])) {
+ free(context.iids);
+ if (context.iall)
+ free(context.inames);
+ free(context.ipairs);
+ initinsts(&context);
+ printlabels(&context);
+ }
+
+ /* print values */
+ if (opts.guiflag || opts.context == PM_CONTEXT_ARCHIVE)
+ __pmPrintStamp(stdout, &rslt1->timestamp);
+ if (rawCounter || (context.desc.sem != PM_SEM_COUNTER))
+ printvals(&context, rslt1->vset[idx1], cols);
+ else
+ printrates(&context, rslt1->vset[idx1], rslt1->timestamp,
+ rslt2->vset[idx2], rslt2->timestamp, cols);
+
+ /*
+ * discard previous and save current result, so this value
+ * becomes the previous value at the next iteration
+ */
+ pmFreeResult(rslt2);
+ rslt2 = rslt1;
+ idx2 = idx1;
+ }
+
+ /*
+ * All serious error conditions have explicit exit() calls, so
+ * if we get this far, all has gone well.
+ */
+ return 0;
+}
diff --git a/src/pmview/GNUmakefile b/src/pmview/GNUmakefile
new file mode 100644
index 0000000..c6e039f
--- /dev/null
+++ b/src/pmview/GNUmakefile
@@ -0,0 +1,102 @@
+TOPDIR = ../..
+COMMAND = pmview
+PROJECT = $(COMMAND).pro
+include $(TOPDIR)/src/include/builddefs
+
+WRAPPER = $(COMMAND).sh
+QRCFILE = $(COMMAND).qrc
+ICNFILE = $(COMMAND).icns
+ICOFILE = $(COMMAND).ico
+XMLFILE = $(COMMAND).info
+DESKTOP = $(COMMAND).desktop
+UIFILES = $(shell echo *.ui)
+CLASSES = main.h pmview.h colorlist.h \
+ barmod.h barobj.h baseobj.h \
+ defaultobj.h gridobj.h labelobj.h stackobj.h \
+ text.h viewobj.h pipeobj.h link.h xing.h \
+ scenefileobj.h scenegroup.h \
+ colorscalemod.h colormod.h colorscale.h \
+ metriclist.h modlist.h modulate.h \
+ scalemod.h stackmod.h togglemod.h \
+ yscalemod.h pcpcolor.h launch.h
+SOURCES = $(CLASSES:.h=.cpp) error.cpp
+HEADERS = $(CLASSES) modobj.h
+GENERATED = gram.cpp lex.cpp
+LFILES = lex.l
+YFILES = gram.y
+LDIRT = $(COMMAND) $(WRAPPER) $(XMLFILE) $(GENERATED) gram.h y.tab.? images
+
+SUBDIRS = front-ends
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(ENABLE_QT)" "true"
+build-me:: images wrappers $(GENERATED)
+ $(QTMAKE)
+ $(LNMAKE)
+
+build-me:: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+lex.cpp: lex.l
+ $(LEX) -t lex.l > $@
+
+gram.h y.tab.c: gram.y
+ $(YACC) -d gram.y && cp y.tab.h gram.h
+
+gram.cpp: y.tab.c
+ cp y.tab.c $@
+
+lex.o: gram.h
+
+ifeq ($(WINDOW),mac)
+MACBUILD = $(COMMAND).app/Contents
+PKG_MAC_DIR = /Applications/$(COMMAND).app/Contents
+wrappers: $(WRAPPER)
+else
+wrappers:
+endif
+
+$(WRAPPER): $(WRAPPER).IN
+ $(SED) -e '/\# .*/b' -e 's;PKG_MAC_DIR;$(PKG_MAC_DIR);g' < $< > $@
+
+install: default
+ $(SUBDIRS_MAKERULE)
+ $(INSTALL) -m 755 -d $(PCP_BIN_DIR)
+ifeq ($(WINDOW),win)
+ $(INSTALL) -m 755 $(BINARY) $(PKG_BIN_DIR)/$(COMMAND)
+endif
+ifeq ($(WINDOW),x11)
+ $(INSTALL) -m 755 $(BINARY) $(PKG_BIN_DIR)/$(COMMAND)
+ $(INSTALL) -m 755 -d $(PKG_DESKTOP_DIR)
+ $(INSTALL) -m 644 $(DESKTOP) $(PKG_DESKTOP_DIR)/$(DESKTOP)
+endif
+ifeq ($(WINDOW),mac)
+ $(INSTALL) -m 755 $(WRAPPER) $(PKG_BIN_DIR)/$(COMMAND)
+ $(INSTALL) -m 755 -d /Applications
+ $(INSTALL) -m 755 -d /Applications/$(COMMAND).app
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)
+ $(INSTALL) -m 644 $(MACBUILD)/Info.plist $(PKG_MAC_DIR)/Info.plist
+ $(INSTALL) -m 644 $(MACBUILD)/PkgInfo $(PKG_MAC_DIR)/PkgInfo
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/MacOS
+ $(INSTALL) -m 755 $(BINARY) $(PKG_MAC_DIR)/MacOS/$(COMMAND)
+ $(INSTALL) -m 755 -d $(PKG_MAC_DIR)/Resources
+ $(INSTALL) -m 644 $(ICNFILE) $(PKG_MAC_DIR)/Resources/$(ICNFILE)
+endif
+
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
+
+images: $(ICNFILE)
+ $(LN_S) $(TOPDIR)/images images
+
+$(ICNFILE):
+ $(LN_S) $(TOPDIR)/images/$(ICNFILE) $(ICNFILE)
diff --git a/src/pmview/app-defaults b/src/pmview/app-defaults
new file mode 100644
index 0000000..94cb915
--- /dev/null
+++ b/src/pmview/app-defaults
@@ -0,0 +1,207 @@
+! Copyright (c) 1995-2002 Silicon Graphics, Inc. All Rights Reserved.
+!
+! This program is free software; you can redistribute it and/or modify it
+! under the terms of the GNU General Public License as published by the
+! Free Software Foundation; either version 2 of the License, or (at your
+! option) any later version.
+!
+! This program is distributed in the hope that it will be useful, but
+! WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+! for more details.
+!
+! You should have received a copy of the GNU General Public License along
+! with this program; if not, write to the Free Software Foundation, Inc.,
+! 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+!
+
+
+!
+!Activate schemes and sgi mode by default
+!
+PmView+*useSchemes: all
+PmView+*sgiMode: true
+
+#ifdef __linux
+!
+! default fonts
+!
+PmView+*defaultFontList: -*-helvetica-bold-o-*-*-*-140-75-75-*-*-iso8859-1
+PmView+*form*fontList: -*-helvetica-medium-r-*-*-*-120-75-75-*-*-iso8859-1
+PmView+*form.scaleText.fontList: -*-helvetica-medium-r-*-*-*-140-75-75-*-*-iso8859-1
+PmView+*form.timeLabel.fontList: -*-helvetica-medium-r-*-*-*-140-75-75-*-*-iso8859-1
+PmView+*form.metricLabel.fontList: -*-helvetica-medium-r-*-*-*-140-75-75-*-*-iso8859-1
+#endif
+!
+!SGI Style guide specifies explicit focus within PmView+s
+!
+PmView+*keyboardFocusPolicy: explicit
+
+!
+! Menus
+!
+PmView+*title: Performance Metrics Viewer
+PmView+*fileMenu.labelString: File
+PmView+*fileMenu.mnemonic: F
+PmView+*recordButton.labelString: Record
+PmView+*recordButton.mnemonic: R
+PmView+*recordButton.accelerator: Ctrl<Key>R
+PmView+*recordButton.acceleratorText: Ctrl+R
+PmView+*saveButton.labelString: Save
+PmView+*saveButton.mnemonic: S
+PmView+*saveButton.accelerator: Ctrl<Key>S
+PmView+*saveButton.acceleratorText: Ctrl+S
+PmView+*printButton.labelString: Print
+PmView+*printButton.mnemonic: P
+PmView+*printButton.accelerator: Ctrl<Key>P
+PmView+*printButton.acceleratorText: Ctrl+P
+PmView+*exitButton.labelString: Quit
+PmView+*exitButton.mnemonic: Q
+PmView+*exitButton.accelerator: Ctrl<Key>Q
+PmView+*exitButton.acceleratorText: Ctrl+Q
+PmView+*optionsMenu.labelString: Options
+PmView+*optionsMenu.mnemonic: O
+PmView+*showVCRButton.labelString: Show Time Control
+PmView+*showVCRButton.mnemonic: T
+PmView+*showVCRButton.accelerator: Ctrl<Key>T
+PmView+*showVCRButton.acceleratorText: Ctrl+T
+PmView+*newVCRButton.mnemonic: N
+PmView+*newVCRButton.labelString: New Time Control
+PmView+*newVCRButton.accelerator: Ctrl<Key>N
+PmView+*newVCRButton.acceleratorText: Ctrl+N
+
+PmView+*launchMenu.labelString: Launch
+PmView+*launchMenu.mnemonic: L
+
+PmView+*helpPane.labelString: Help
+PmView+*helpPane.mnemonic: H
+PmView+*help_click_for_help.labelString: Click For Help
+PmView+*help_click_for_help.mnemonic: C
+PmView+*help_click_for_help.accelerator: Shift<Key>F1
+PmView+*help_click_for_help.acceleratorText: Shift+F1
+PmView+*help_overview.labelString: Overview
+PmView+*help_overview.mnemonic: O
+PmView+*help_index.labelString: Index
+PmView+*help_index.mnemonic: I
+PmView+*help_keys_and_short.labelString: Keys and Shortcuts
+PmView+*help_keys_and_short.mnemonic: K
+PmView+*help_prod_info.labelString: Product Information
+PmView+*help_prod_info.mnemonic: P
+PmView+*overviewButton.labelString: Overview
+PmView+*overviewButton.mnemonic: O
+PmView+*indexButton.labelString: Index
+PmView+*indexButton.mnemonic: I
+
+!
+! Scale Thumb Wheel (see SgThumbWheel(3))
+! These resources control the coarseness of the scale wheel (which is
+! logarithmic). These settings force the wheel to have no maximum or
+! minimum value and is reasonably fine near the center (scale of 1.0):
+!
+! PmView+*scaleWheel.homePosition: 0
+! PmView+*scaleWheel.maximum: 0
+! PmView+*scaleWheel.minimum: 0
+! PmView+*scaleWheel.unitsPerRotation: 100
+!
+! These settings force a minimum and maximum value which is not as fine
+! for values near the center:
+!
+! PmView+*scaleWheel.homePosition: 0
+! PmView+*scaleWheel.maximum: 80
+! PmView+*scaleWheel.minimum: -80
+! PmView+*scaleWheel.angleRange: 240
+!
+PmView+*scaleText.value: 1.0000
+PmView+*scaleLabel.labelString: Scale
+PmView+*scaleWheel.homePosition: 0
+PmView+*scaleWheel.maximum: 0
+PmView+*scaleWheel.minimum: 0
+PmView+*scaleWheel.unitsPerRotation: 100
+PmView+*scaleWheel.angleRange: 240
+
+!
+! Default label text
+!
+PmView+*metricLabel.labelString: \n
+PMView*timeLabel.labelString:
+
+!
+! Background color of read-only labels
+!
+!PmView+*readOnlyBackground: Black
+
+!
+! Maximum value before saturation
+! The default of 1.05 allows for 5% error in the time delta when
+! determining rates, before values are deemed saturated.
+!
+PmView+*saturation: 1.05
+
+!
+! Use fast anti-aliasing
+! See SoXtRenderArea(3)
+!
+PmView+*antiAliasSmooth: tree
+
+!
+! Number of anti-aliasing passes: 1 to 255. Only 1 pass disables antialiasing.
+PmView+*antiAliasPasses: 1
+
+!
+! Title, geometry etc.
+!
+PmView+*vkwindow.title: Performance Metrics Viewer
+PmView+*vkwindow.geometry: 512x512
+
+!
+! Better handling of limited colors with pixmaps
+!
+PmView+*silenceWarnings: true
+PmView+*xpmColorCloseness: 113512
+
+!
+! Dialogs
+!
+PmView+*fileSelectionDialog.width: 358
+PmView+*fileSelectionDialog.height: 417
+
+!
+! Help
+!
+*helpSubSys: pcp_eoe.books.PmViewHelp
+*helpTitle: pmview Help
+
+!
+! Layout - can be overridden in configuration files
+!
+
+! Grid, Bar and Stack object base borders
+PmView+*baseBorderWidth: 8
+PmView+*baseBorderDepth: 8
+
+! Height of Grid, Bar and Stack bases
+PmView+*baseHeight: 2
+
+! Color of base plane
+PmView+*baseColor: rgbi:0.15/0.15/0.15
+
+! Spacing between Bar blocks
+PmView+*barSpaceWidth: 8
+PmView+*barSpaceDepth: 8
+
+! Spacing between Bar base and labels
+PmView+*barSpaceLabel: 6
+
+! Width and depth of Bar blocks
+PmView+*barLength: 28
+PmView+*barHeight: 80
+
+! Margin around a Label
+PmView+*labelMargin: 5
+
+! Color of labels
+PmView+*labelColor: rgbi:1.0/1.0/1.0
+
+! Width and depth of Grid columns and rows
+PmView+*gridMinWidth: 20
+PmView+*gridMinDepth: 20
diff --git a/src/pmview/barmod.cpp b/src/pmview/barmod.cpp
new file mode 100644
index 0000000..206f32c
--- /dev/null
+++ b/src/pmview/barmod.cpp
@@ -0,0 +1,603 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/SoPath.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoSelection.h>
+#include "barmod.h"
+#include "modlist.h"
+#include "launch.h"
+
+#include <iostream>
+using namespace std;
+
+//
+// Use debug flag LIBPMDA to trace Bar refreshes
+//
+
+const char BarMod::theBarId = 'b';
+
+BarMod::~BarMod()
+{
+}
+
+BarMod::BarMod(MetricList *metrics,
+ SoNode *obj,
+ BarMod::Direction dir,
+ BarMod::Grouping group,
+ float xScale, float yScale, float zScale,
+ float xSpace, float zSpace)
+: Modulate(metrics),
+ _blocks(),
+ _dir(dir),
+ _mod(BarMod::yScale),
+ _group(group),
+ _colScale(0.0, 0.0, 0.0),
+ _selectCount(0),
+ _infoValue(0),
+ _infoMetric(0),
+ _infoInst(0),
+ _xScale(xScale),
+ _yScale(yScale),
+ _zScale(zScale)
+{
+ generate(obj, xSpace, zSpace);
+}
+
+BarMod::BarMod(MetricList *metrics,
+ const ColorScale &colScale,
+ SoNode *obj,
+ BarMod::Direction dir,
+ BarMod::Modulation mod,
+ BarMod::Grouping group,
+ float xScale, float yScale, float zScale,
+ float xSpace, float zSpace)
+: Modulate(metrics),
+ _blocks(),
+ _dir(dir),
+ _mod(mod),
+ _group(group),
+ _colScale(colScale),
+ _selectCount(0),
+ _infoValue(0),
+ _infoMetric(0),
+ _infoInst(0),
+ _xScale(xScale),
+ _yScale(yScale),
+ _zScale(zScale)
+{
+ generate(obj, xSpace, zSpace);
+}
+
+void
+BarMod::generate(SoNode *obj, float xSpace, float zSpace)
+{
+ int numMetrics = _metrics->numMetrics();
+ int numValues = _metrics->numValues();
+ int maxInst = 0;
+ char buf[32];
+ int m, i, v;
+
+ _root = new SoSeparator;
+
+ if (numValues > 0) {
+
+ for (m = 0; m < numMetrics; m++)
+ if (_metrics->metric(m).numValues() > maxInst)
+ maxInst = _metrics->metric(m).numValues();
+
+ if (_dir == instPerCol) {
+ _cols = maxInst;
+ _rows = numMetrics;
+ }
+ else {
+ _cols = numMetrics;
+ _rows = maxInst;
+ }
+
+ _blocks.resize(numValues);
+
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ const QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ BarBlock &block = _blocks[v];
+ sprintf(buf, "%c%d", theBarId, v);
+ block._sep = new SoSeparator;
+ block._sep->setName((SbName)buf);
+ _root->addChild(block._sep);
+
+ block._tran = new SoTranslation;
+ block._sep->addChild(block._tran);
+
+ block._color = new SoBaseColor;
+ block._sep->addChild(block._color);
+
+ block._scale = new SoScale;
+ block._sep->addChild(block._scale);
+
+ block._sep->addChild(obj);
+ }
+ }
+
+ regenerate(_xScale, _zScale, xSpace, zSpace);
+ _infoValue = numValues;
+
+ add();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::generate: Added " << numValues << " in " << _cols
+ << " cols and " << _rows << " rows." << endl;
+#endif
+
+ }
+
+ // Invalid object
+ else {
+ _sts = -1;
+ }
+}
+
+void
+BarMod::refresh(bool fetchFlag)
+{
+ int m, i, v;
+
+ if (status() < 0)
+ return;
+
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+
+ if (fetchFlag)
+ metric.update();
+
+ for (i = 0; i < metric.numValues(); i++, v++) {
+
+ BarBlock &block = _blocks[v];
+
+ if (metric.error(i) <= 0) {
+
+ if (block._state != Modulate::error) {
+ block._color->rgb.setValue(_errorColor.getValue());
+ if (_mod != color)
+ block._scale->scaleFactor.setValue(_xScale,
+ theMinScale,
+ _zScale);
+ block._state = Modulate::error;
+ }
+ }
+ else {
+ double unscaled = metric.value(i);
+ double value = unscaled * theScale;
+
+ if (value > theNormError) {
+ if (block._state != Modulate::saturated) {
+ block._color->rgb.setValue(Modulate::_saturatedColor);
+ if (_mod != color)
+ block._scale->scaleFactor.setValue(_xScale,
+ _yScale,
+ _zScale);
+ block._state = Modulate::saturated;
+ }
+ }
+ else {
+ if (block._state != Modulate::normal) {
+ block._state = Modulate::normal;
+ if (_mod == yScale)
+ block._color->rgb.setValue(_metrics->color(m).getValue());
+ }
+ else if (_mod != yScale)
+ block._color->rgb.setValue(_colScale.step(unscaled).color().getValue());
+ if (_mod != color) {
+ if (value < Modulate::theMinScale)
+ value = Modulate::theMinScale;
+ else if (value > 1.0)
+ value = 1.0;
+ block._scale->scaleFactor.setValue(_xScale,
+ _yScale * value,
+ _zScale);
+ }
+
+ }
+ }
+ }
+ }
+}
+
+void
+BarMod::selectAll()
+{
+ int i;
+
+ if (_selectCount == _blocks.size())
+ return;
+
+ theModList->selectAllId(_root, _blocks.size());
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::selectAll" << endl;
+#endif
+
+ for (i = 0; i < _blocks.size(); i++) {
+ if (_blocks[i]._selected == false) {
+ _selectCount++;
+ theModList->selectSingle(_blocks[i]._sep);
+ _blocks[i]._selected = true;
+ }
+ }
+}
+
+int
+BarMod::select(SoPath *path)
+{
+ int metric, inst, value;
+
+ findBlock(path, metric, inst, value, false);
+ if (value < _blocks.size() && _blocks[value]._selected == false) {
+ _blocks[value]._selected = true;
+ _selectCount++;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::select: value = " << value
+ << ", count = " << _selectCount << endl;
+#endif
+ }
+ return _selectCount;
+}
+
+int
+BarMod::remove(SoPath *path)
+{
+ int metric, inst, value;
+
+ findBlock(path, metric, inst, value, false);
+ if (value < _blocks.size() && _blocks[value]._selected == true) {
+ _blocks[value]._selected = false;
+ _selectCount--;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::remove: value = " << value
+ << ", count = " << _selectCount << endl;
+#endif
+
+ }
+
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::remove: did not remove " << value
+ << ", count = " << _selectCount << endl;
+#endif
+
+ return _selectCount;
+}
+
+void BarMod::infoText(QString &str, bool selected) const
+{
+ int m = _infoMetric;
+ int i = _infoInst;
+ int v = _infoValue;
+ bool found = false;
+
+ if (selected && _selectCount == 1) {
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ const QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++)
+ if (_blocks[v]._selected) {
+ found = true;
+ break;
+ }
+ if (found)
+ break;
+ }
+ }
+
+ if (v >= _blocks.size()) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::infoText: infoText requested but nothing selected"
+ << endl;
+#endif
+ str = "";
+ }
+ else {
+ const QmcMetric &metric = _metrics->metric(m);
+ str = metric.spec(true, true, i);
+ str.append(QChar('\n'));
+
+ if (_blocks[v]._state == Modulate::error)
+ str.append(theErrorText);
+ else if (_blocks[v]._state == Modulate::start)
+ str.append(theStartText);
+ else {
+ QString value;
+ str.append(value.setNum(metric.realValue(i), 'g', 4));
+ str.append(QChar(' '));
+ if (metric.desc().units().size() > 0)
+ str.append(metric.desc().units());
+ str.append(" [");
+ str.append(value.setNum(metric.value(i) * 100.0, 'g', 4));
+ str.append("% of expected max]");
+ }
+ }
+}
+
+void BarMod::launch(Launch &launch, bool all) const
+{
+ int m, i, v;
+ bool needClose;
+ bool always = all;
+ bool keepGoing = true;
+
+ if (status() < 0)
+ return;
+
+ if (_selectCount == _blocks.size())
+ always = true;
+
+ // Group by metric
+ if (_group == groupByMetric ||
+ (_group == groupByRow && _dir == instPerCol) ||
+ (_group == groupByCol && _dir == instPerRow)) {
+
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+
+ // Do we have to check that an instance of this metric has
+ // been selected?
+ if (!always) {
+ needClose = false;
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ if (_blocks[v]._selected) {
+ if (needClose == false) {
+ launch.startGroup("point");
+ needClose = true;
+ }
+ if (_mod == yScale)
+ launch.addMetric(metric, _metrics->color(m), i);
+ else
+ launch.addMetric(metric, _colScale, i);
+ }
+ }
+ if (needClose)
+ launch.endGroup();
+ }
+ else {
+ launch.startGroup("point");
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ if (_mod == yScale)
+ launch.addMetric(metric, _metrics->color(m), i);
+ else
+ launch.addMetric(metric, _colScale, i);
+ }
+ launch.endGroup();
+ }
+ }
+ }
+
+ // Group by instance, this gets a little tricky
+ else {
+ for (i = 0; keepGoing ; i++) {
+ needClose = false;
+ keepGoing = false;
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ if (metric.numValues() > i) {
+ if (always || _blocks[v+i]._selected) {
+ if (needClose == false) {
+ launch.startGroup("point");
+ needClose = true;
+ }
+ if (_mod == yScale)
+ launch.addMetric(metric, _metrics->color(m), i);
+ else
+ launch.addMetric(metric, _colScale, i);
+ }
+ keepGoing = true;
+ }
+ v += metric.numValues();
+ }
+ if (needClose)
+ launch.endGroup();
+ }
+ }
+}
+
+void
+BarMod::selectInfo(SoPath *path)
+{
+ findBlock(path, _infoMetric, _infoInst, _infoValue);
+}
+
+void
+BarMod::removeInfo(SoPath *)
+{
+ _infoValue = _blocks.size();
+ _infoMetric = _infoInst = 0;
+}
+
+void
+BarMod::dump(QTextStream &os) const
+{
+ int m, i, v;
+
+ os << "BarMod: ";
+
+ if (_dir == instPerCol)
+ os << "inst per col";
+ else
+ os << "inst per row";
+
+ if (_mod == yScale)
+ os << ", Y-Scale: ";
+ else if (_mod == color)
+ os << ", Color Only: ";
+ else
+ os << ", Color & Y-Scale: ";
+
+ if (status() < 0)
+ os << "Invalid metrics: " << pmErrStr(status()) << endl;
+ else {
+ os << endl;
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ os << " [" << v << "]: ";
+ if (_blocks[v]._selected == true)
+ os << '*';
+ else
+ os << ' ';
+ dumpState(os, _blocks[v]._state);
+ os << ": ";
+ metric.dump(os, true, i);
+ }
+ }
+ }
+}
+
+void
+BarMod::findBlock(SoPath *path, int &metric, int &inst,
+ int &value, bool idMetric)
+{
+ SoNode *node;
+ char *str;
+ int m, i, v;
+ char c;
+
+ for (i = path->getLength() - 1; i >= 0; --i) {
+ node = path->getNode(i);
+ str = (char *)(node->getName().getString());
+ if (strlen(str) && str[0] == theBarId)
+ break;
+ }
+
+ if (i >= 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "BarMod::findBlock: Bar id = " << str << endl;
+#endif
+
+ sscanf(str, "%c%d", &c, &value);
+
+ if (value == 0 || idMetric == false) {
+ metric = 0;
+ inst = 0;
+ }
+ else {
+ m = 0;
+ v = value;
+ while (m < _metrics->numMetrics()) {
+ i = _metrics->metric(m).numValues();
+ if (v < i) {
+ metric = m;
+ inst = v;
+ break;
+ }
+ else {
+ v -= i;
+ m++;
+ }
+ }
+ }
+ }
+ else {
+ value = _blocks.size();
+ metric = inst = 0;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ cerr << "BarMod::findBlock: metric = " << metric
+ << ", inst = " << inst << ", value = " << value << endl;
+ }
+#endif
+
+ return;
+}
+
+void
+BarMod::regenerate(float xScale, float zScale, float xSpace, float zSpace)
+{
+ int m, i, v;
+ float halfX = xScale / 2.0;
+ float halfZ = zScale / 2.0;
+
+ if (status() < 0)
+ return;
+
+ _xScale = xScale;
+ _zScale = zScale;
+
+ _width = (unsigned int)((_cols * (_xScale + xSpace)) - xSpace);
+ _depth = (unsigned int)((_rows * (_zScale + zSpace)) - zSpace);
+
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ const QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ BarBlock &block = _blocks[v];
+
+ if (_dir == instPerCol)
+ block._tran->translation.setValue(i * (_xScale+xSpace) + halfX,
+ 0,
+ m * (_zScale+zSpace) + halfZ);
+ else
+ block._tran->translation.setValue(m * (_xScale+xSpace) + halfX,
+ 0,
+ i * (_zScale+zSpace) + halfZ);
+
+ block._color->rgb.setValue(_errorColor.getValue());
+ block._scale->scaleFactor.setValue(_xScale, _yScale, _zScale);
+ block._state = Modulate::start;
+ block._selected = false;
+ }
+ }
+}
+
+const char *
+BarMod::dirStr() const
+{
+ const char *str = NULL;
+
+ if (_dir == instPerCol)
+ str = "instances in columns";
+ else
+ str = "instances in rows";
+
+ return str;
+}
+
+const char *
+BarMod::modStr() const
+{
+ const char *str = NULL;
+
+ switch (_mod) {
+ case yScale:
+ str = "Y-Scale";
+ break;
+ case color:
+ str = "Colored";
+ break;
+ case colYScale:
+ str = "Colored Y-Scale";
+ break;
+ }
+ return str;
+}
diff --git a/src/pmview/barmod.h b/src/pmview/barmod.h
new file mode 100644
index 0000000..073f7ce
--- /dev/null
+++ b/src/pmview/barmod.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _BARMOD_H_
+#define _BARMOD_H_
+
+#include "colorscale.h"
+#include "modulate.h"
+#include <QtCore/QVector>
+
+class SoBaseColor;
+class SoScale;
+class SoNode;
+class SoTranslation;
+class Launch;
+
+struct BarBlock {
+ SoSeparator *_sep;
+ SoBaseColor *_color;
+ SoScale *_scale;
+ SoTranslation *_tran;
+ Modulate::State _state;
+ bool _selected;
+};
+
+typedef QVector<BarBlock> BarBlockList;
+
+class BarMod : public Modulate
+{
+public:
+
+ enum Direction { instPerCol, instPerRow };
+ enum Modulation { yScale, color, colYScale };
+ enum Grouping { groupByRow, groupByCol, groupByMetric, groupByInst };
+
+private:
+
+ static const char theBarId;
+
+ BarBlockList _blocks;
+ Direction _dir;
+ Modulation _mod;
+ Grouping _group;
+ ColorScale _colScale;
+ int _selectCount;
+ int _infoValue;
+ int _infoMetric;
+ int _infoInst;
+ float _xScale;
+ float _yScale;
+ float _zScale;
+ int _width;
+ int _depth;
+ int _cols;
+ int _rows;
+
+public:
+
+ virtual ~BarMod();
+
+ BarMod(MetricList *list,
+ SoNode *obj,
+ BarMod::Direction dir,
+ BarMod::Grouping group,
+ float xScale, float yScale, float zScale,
+ float xSpace, float zSpace);
+
+ BarMod(MetricList *list,
+ const ColorScale &colScale,
+ SoNode *obj,
+ BarMod::Direction dir,
+ BarMod::Modulation mod,
+ BarMod::Grouping group,
+ float xScale, float yScale, float zScale,
+ float xSpace, float zSpace);
+
+ Direction dir() const
+ { return _dir; }
+ int width() const
+ { return _width; }
+ int depth() const
+ { return _depth; }
+ int numBars() const
+ { return _blocks.size(); }
+ int rows() const
+ { return _rows; }
+ int cols() const
+ { return _cols; }
+
+ virtual void refresh(bool fetchFlag);
+
+ virtual void selectAll();
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void selectInfo(SoPath *);
+ virtual void removeInfo(SoPath *);
+
+ virtual void infoText(QString &str, bool) const;
+
+ virtual void launch(Launch &launch, bool) const;
+
+ virtual void dump(QTextStream &) const;
+
+ void regenerate(float xScale, float zScale, float xSpace, float zSpace);
+
+ const char *dirStr() const;
+ const char *modStr() const;
+
+private:
+
+ BarMod();
+ BarMod(const BarMod &);
+ const BarMod &operator=(const BarMod &);
+ // Never defined
+
+ void generate(SoNode *obj, float xSpace, float zSpace);
+ void findBlock(SoPath *path, int &metric, int &inst,
+ int &value, bool idMetric = true);
+};
+
+#endif /* _BARMOD_H_ */
diff --git a/src/pmview/barobj.cpp b/src/pmview/barobj.cpp
new file mode 100644
index 0000000..da3e621
--- /dev/null
+++ b/src/pmview/barobj.cpp
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoTransform.h>
+
+#include "barobj.h"
+#include "colorlist.h"
+#include "yscalemod.h"
+#include "colormod.h"
+#include "colorscalemod.h"
+#include "text.h"
+#include "defaultobj.h"
+
+#include <iostream>
+using namespace std;
+
+BarObj::~BarObj()
+{
+}
+
+BarObj::BarObj(ViewObj::Shape shape,
+ BarMod::Direction dir,
+ BarMod::Modulation mod,
+ BarMod::Grouping group,
+ bool baseFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ BaseObj::Alignment align)
+: ModObj(baseFlag, defaults, x, y, cols, rows, align),
+ _shape(shape),
+ _dir(dir),
+ _mod(mod),
+ _group(group),
+ _width(0),
+ _depth(0),
+ _xSpace(defaults.barSpaceX()),
+ _zSpace(defaults.barSpaceZ()),
+ _labelSpace(defaults.barSpaceLabel()),
+ _bars(0),
+ _metDir(towards),
+ _metLabels(new QStringList),
+ _instDir(away),
+ _instLabels(new QStringList)
+{
+ _objtype |= BAROBJ;
+
+ int i;
+ for (i = 0; i < numSides; i++)
+ _margins[i] = 0.0;
+ _labelColor[0] = defaults.labelColor(0);
+ _labelColor[1] = defaults.labelColor(1);
+ _labelColor[2] = defaults.labelColor(2);
+}
+
+void
+BarObj::finishedAdd()
+{
+ const ColorSpec *colSpec = NULL;
+ SoNode *object = ViewObj::object(_shape);
+ SoSeparator *labelSep = NULL;
+ SoSeparator *metricSep = NULL;
+ SoSeparator *instSep = NULL;
+ SoSeparator *barSep = new SoSeparator;
+ SoSeparator *baseSep = new SoSeparator;
+ SoTranslation *objTran = new SoTranslation;
+ SoTranslation *barTran = new SoTranslation;
+ SoTranslation *baseTran = new SoTranslation;
+ SoTranslation *modTran = new SoTranslation;
+ ColorScale *colScale = NULL;
+ LabelSide metSide = left;
+ LabelSide instSide = left;
+ Text **metText = NULL;
+ Text **instText = NULL;
+ int i;
+ int max = 0;
+ int numMetLabels = 0;
+ int numInstLabels = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "BarObj::finishedAdd:" << endl;
+#endif
+
+ if (_metrics.numMetrics() == 0) {
+ BaseObj::addBase(_root);
+ pmprintf("%s: Error: Bar object has no metrics\n",
+ pmProgname);
+ _length = 0;
+ _width = baseWidth();
+ _depth = baseDepth();
+ return;
+ }
+
+ _root->addChild(objTran);
+
+ if (_metLabels->size() || _instLabels->size()) {
+ labelSep = new SoSeparator;
+ _root->addChild(labelSep);
+ SoBaseColor *base = new SoBaseColor;
+ base->rgb.setValue(_labelColor[0], _labelColor[1], _labelColor[2]);
+ labelSep->addChild(base);
+ }
+ _root->addChild(barSep);
+ barSep->addChild(barTran);
+ barSep->addChild(baseSep);
+ baseSep->addChild(baseTran);
+ barSep->addChild(modTran);
+ BaseObj::addBase(baseSep);
+
+ // Determine color mapping
+
+ if (_colors.size())
+ colSpec = theColorLists.list((const char *)_colors.toAscii());
+
+ if (colSpec != NULL) {
+ if (colSpec->_scale) {
+ if (_mod == BarMod::yScale) {
+ pmprintf("%s: Warning: Color scale ignored for Y-Scale Bar object.\n",
+ pmProgname);
+ }
+ else {
+ if (colSpec->_list.size() == 0)
+ colScale = new ColorScale(0.0, 0.0, 1.0);
+ else {
+ colScale = new ColorScale(*(colSpec->_list[0]));
+ for (i = 1; i < colSpec->_list.size(); i++)
+ colScale->add(new ColorStep(*(colSpec->_list[i]),
+ colSpec->_max[i]));
+ }
+ }
+ }
+ else if (_mod == BarMod::color || _mod == BarMod::colYScale) {
+ pmprintf("%s: Warning: Expected color scale for color modulated Bar object.\n",
+ pmProgname);
+
+ if (colSpec->_list.size() == 0)
+ colScale = new ColorScale(0.0, 0.0, 1.0);
+ else
+ colScale = new ColorScale(*(colSpec->_list[0]));
+ }
+ }
+ else {
+ pmprintf("%s: Warning: No colours specified for Bar objects, defaulting to blue.\n",
+ pmProgname);
+
+ if (_mod == BarMod::color || _mod == BarMod::colYScale)
+ colScale = new ColorScale(0.0, 0.0, 1.0);
+ }
+
+ if (_mod == BarMod::yScale) {
+ if (colSpec != NULL)
+ for (i = 0; i < colSpec->_list.size(); i++)
+ _metrics.add(*(colSpec->_list)[i]);
+ _metrics.resolveColors(MetricList::perMetric);
+ }
+
+ // Generate Bar Modulate Object
+ if (_mod == BarMod::yScale)
+ _bars = new BarMod(&_metrics, object, _dir, _group,
+ (float)_length, (float)_maxHeight, (float)_length,
+ (float)_xSpace, (float)_zSpace);
+ else {
+ _bars = new BarMod(&_metrics, *colScale, object, _dir, _mod, _group,
+ (float)_length, (float)_maxHeight, (float)_length,
+ (float)_xSpace, (float)_zSpace);
+ }
+
+ barSep->addChild(_bars->root());
+ BaseObj::add(_bars);
+
+ // Generate Labels
+
+ if (_metLabels->size()) {
+ if (_dir == BarMod::instPerRow)
+ if (_metDir == away)
+ metSide = below;
+ else
+ metSide = above;
+ else
+ if (_metDir == away)
+ metSide = right;
+ else
+ metSide = left;
+
+ metricSep = new SoSeparator;
+ labelSep->addChild(metricSep);
+
+ if (_metLabels->size() < _metrics.numMetrics())
+ numMetLabels = _metLabels->size();
+ else
+ numMetLabels = _metrics.numMetrics();
+
+ metText = calcLabels(*_metLabels, metSide, numMetLabels);
+ }
+
+ if (_instLabels->size()) {
+ if (_dir == BarMod::instPerCol) {
+ max = _bars->cols();
+ if (_instDir == away)
+ instSide = below;
+ else
+ instSide = above;
+ }
+ else {
+ max = _bars->rows();
+ if (_instDir == away)
+ instSide = right;
+ else
+ instSide = left;
+ }
+
+ instSep = new SoSeparator;
+ labelSep->addChild(instSep);
+
+ if (_instLabels->size() < max)
+ numInstLabels = _instLabels->size();
+ else
+ numInstLabels = max;
+
+ instText = calcLabels(*_instLabels, instSide, numInstLabels);
+ }
+
+ // Width and depth of bars only, effects of labels added later
+
+ _width = _bars->width();
+ _depth = _bars->depth();
+
+ // Insert the labels
+
+ if (numMetLabels)
+ metricSep->addChild(doLabels(metText, metSide, numMetLabels));
+
+ if (numInstLabels)
+ instSep->addChild(doLabels(instText, instSide, numInstLabels));
+
+ // Work out where the bars live
+
+ _bars->regenerate(_length, _length, _xSpace, _zSpace);
+ _width = _bars->width();
+ _depth = _bars->depth();
+
+ baseTran->translation.setValue(_width / 2.0, 0.0, _depth / 2.0);
+
+ _width += (u_int32_t)(baseWidth() + _margins[left] + _margins[right]+0.5);
+ _depth += (u_int32_t)(baseDepth() + _margins[above] + _margins[below]+0.5);
+
+ objTran->translation.setValue((_width / -2.0), 0.0, (_depth / -2.0));
+
+ barTran->translation.setValue(_margins[left] + borderX(), 0.0,
+ _margins[above] + borderZ());
+
+
+ modTran->translation.setValue(0.0,
+ (BaseObj::state() ? baseHeight() : 0.0),
+ 0.0);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "BarObj::finishedAdd: metric list = " << endl
+ << _metrics << endl;
+#endif
+
+ if (_metrics.numMetrics())
+ ViewObj::theNumModObjects++;
+
+ // Cleanup
+
+ if (colScale)
+ delete colScale;
+ delete _metLabels;
+ delete _instLabels;
+}
+
+void
+BarObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ BaseObj::setBaseSize(width() - _margins[left] - _margins[right],
+ depth() - _margins[above] - _margins[below]);
+ BaseObj::setTran(xTran + (width() / 2.0),
+ zTran + (depth() / 2.0),
+ setWidth, setDepth);
+}
+
+QTextStream&
+operator<<(QTextStream& os, BarObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+BarObj::display(QTextStream& os) const
+{
+ BaseObj::display(os);
+
+ if (_bars == NULL) {
+ os << "No valid metrics" << endl;
+ return;
+ }
+
+ os << ", dir = "
+ << (_dir == BarMod::instPerCol ? "instPerCol" : "instPerRow")
+ << ", length = " << _length << ", xSpace = " << _xSpace << ", zSpace = "
+ << _zSpace << ", labelSpace = " << _labelSpace << ", rows = " << _rows
+ << ", cols = " << _cols << ", num bars = " << _bars->numBars()
+ << ", shape = ";
+ ViewObj::dumpShape(os, _shape);
+ os << ", margins: left = " << _margins[left] << ", right = "
+ << _margins[right] << ", above = " << _margins[above]
+ << ", below = " << _margins[below];
+}
+
+const char*
+BarObj::name() const
+{
+ static QString myName;
+
+ if (myName.size() == 0) {
+ if (_bars == NULL)
+ myName = "Invalid bar object";
+ else {
+ myName = _bars->modStr();
+ myName.append(" Bar Object (");
+ myName.append(_bars->dirStr());
+ myName.append(QChar(')'));
+ }
+ }
+
+ return (const char *)myName.toAscii();
+}
+
+Text **
+BarObj::calcLabels(const QStringList &labels, LabelSide side, int numLabels)
+{
+ Text **text = NULL;
+ int i;
+ int maxWidth = 0;
+ int maxDepth = 0;
+
+ text= new Text*[numLabels];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "BarObj::calcLabels: " << numLabels << " labels on the ";
+ switch(side) {
+ case left:
+ cerr << "left";
+ break;
+ case right:
+ cerr << "right";
+ break;
+ case above:
+ cerr << "above";
+ break;
+ case below:
+ cerr << "below";
+ break;
+ }
+ cerr << " side" << endl;
+ }
+#endif
+
+ // Create the text objects so that we know how big they are
+
+ for (i = 0; i < numLabels; i++) {
+ if (side == above || side == below)
+ text[i] = new Text(labels[i], Text::down, Text::medium);
+ else
+ text[i] = new Text(labels[i], Text::right, Text::medium);
+
+ if (text[i]->width() > maxWidth)
+ maxWidth = text[i]->width();
+ if (text[i]->depth() > maxDepth)
+ maxDepth = text[i]->depth();
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "BarObj::calcLabels: maxWidth = " << maxWidth
+ << ", maxDepth = " << maxDepth << endl;
+#endif
+
+ // Determine if the size of the bars will need to be increased
+
+ if (side == above || side == below) {
+ _margins[side] = maxDepth + _labelSpace;
+ if (maxWidth > _length) {
+ _length = maxWidth;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "BarObj::calcLabels: length (width) increased to "
+ << _length << endl;
+#endif
+
+ }
+ }
+ else {
+ _margins[side] = maxWidth + _labelSpace;
+ if (maxDepth > _length) {
+ _length = maxDepth;
+
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "BarObj::calcLabels: length (depth) increased to "
+ << _length << endl;
+#endif
+ }
+ }
+ return text;
+}
+
+SoNode *
+BarObj::doLabels(Text **text, LabelSide side, int numLabels)
+{
+ SoSeparator *sep = new SoSeparator;
+ SoTranslation *tran = new SoTranslation;
+ int i;
+ int maxWidth = 0;
+ int maxDepth = 0;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "BarObj::doLabels: " << numLabels << " labels on the ";
+ switch(side) {
+ case left:
+ cerr << "left";
+ break;
+ case right:
+ cerr << "right";
+ break;
+ case above:
+ cerr << "above";
+ break;
+ case below:
+ cerr << "below";
+ break;
+ }
+ cerr << " side" << endl;
+ }
+#endif
+
+ sep->addChild(tran);
+
+ // Determine the translation to the first label, subsequent labels
+ // are translated from the first
+
+ maxWidth = _length + _xSpace;
+ maxDepth = _length + _zSpace;
+
+ switch (side) {
+ case left:
+ tran->translation.setValue(_margins[left] - _labelSpace, 0.0,
+ _margins[above] + borderZ());
+ break;
+ case right:
+ tran->translation.setValue(
+ _margins[left] + _width + baseWidth() + _labelSpace,
+ 0.0, _margins[above] + borderZ());
+ break;
+ case above:
+ tran->translation.setValue(_margins[left] + borderX(), 0.0,
+ _margins[above] - _labelSpace);
+ break;
+ case below:
+ tran->translation.setValue(_margins[left] + borderX(), 0.0,
+ _margins[above] + _depth + baseDepth() + _labelSpace);
+ break;
+ default:
+ break;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ float x, y, z;
+ tran->translation.getValue().getValue(x, y, z);
+ cerr << "BarObj::doLabels: translation set to " << x << ',' << y
+ << ',' << z << endl;
+ }
+#endif
+
+ // Add each label to the scene graph
+
+ for (i = 0; i < numLabels; i++) {
+ SoSeparator *labelSep = new SoSeparator;
+ sep->addChild(labelSep);
+
+ SoTranslation *labelTran = new SoTranslation;
+ labelSep->addChild(labelTran);
+
+ switch (side) {
+ case left:
+ labelTran->translation.setValue(0.0,
+ 0.0,
+ (maxDepth * i) + ((_length - (float)text[i]->depth())/ 2.0));
+ break;
+ case right:
+ labelTran->translation.setValue(text[i]->width(), 0.0,
+ (maxDepth * i) + ((_length - (float)text[i]->depth())/ 2.0));
+ break;
+ case above:
+ labelTran->translation.setValue(
+ (maxWidth * i) + ((_length - (float)text[i]->width())/ 2.0),
+ 0.0, 0.0);
+ break;
+ case below:
+ labelTran->translation.setValue(
+ (maxWidth * i) + ((_length - (float)text[i]->width())/ 2.0),
+ 0.0, text[i]->depth());
+ break;
+ default:
+ break;
+ }
+
+ labelSep->addChild(text[i]->root());
+ }
+
+ // Do not delete contents, just the array pointer
+ delete [] text;
+
+ return sep;
+}
diff --git a/src/pmview/barobj.h b/src/pmview/barobj.h
new file mode 100644
index 0000000..c9566a7
--- /dev/null
+++ b/src/pmview/barobj.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _BAROBJ_H_
+#define _BAROBJ_H_
+
+#include "barmod.h"
+#include "modobj.h"
+#include "metriclist.h"
+#include <QtCore/QStringList>
+
+class SoNode;
+class SoTranslation;
+class Text;
+
+class BarObj : public ModObj
+{
+public:
+
+ enum LabelDir { away, towards };
+ enum LabelSide { left, right, above, below, numSides };
+
+protected:
+
+ ViewObj::Shape _shape;
+ BarMod::Direction _dir;
+ BarMod::Modulation _mod;
+ BarMod::Grouping _group;
+ int _width;
+ int _depth;
+ int _xSpace;
+ int _zSpace;
+ int _labelSpace;
+ BarMod *_bars;
+ LabelDir _metDir;
+ QStringList *_metLabels;
+ LabelDir _instDir;
+ QStringList *_instLabels;
+ float _margins[numSides];
+ float _labelColor[3];
+
+public:
+
+ virtual ~BarObj();
+
+ BarObj(ViewObj::Shape shape,
+ BarMod::Direction dir,
+ BarMod::Modulation mod,
+ BarMod::Grouping group,
+ bool baseFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols = 1, int rows = 1,
+ BaseObj::Alignment align = BaseObj::center);
+
+ virtual int width() const
+ { return _width; }
+ virtual int depth() const
+ { return _depth; }
+ Shape shape() const
+ { return _shape; }
+ BarMod::Direction dir() const
+ { return _dir; }
+ BarMod::Modulation mod() const
+ { return _mod; }
+ int numMetricLabels() const
+ { return _metLabels->size(); }
+ LabelDir metricLabelDir() const
+ { return _metDir; }
+ int numInstLabels() const
+ { return _instLabels->size(); }
+ LabelDir instLabelDir() const
+ { return _instDir; }
+
+ void addMetric(const char *metric, double scale, const char *label)
+ { if (_metrics.add(metric, scale) >= 0) _metLabels->append(label); }
+
+ void addMetricLabel(const char *label)
+ { _metLabels->append(label); }
+ void addInstLabel(const char *label)
+ { _instLabels->append(label); }
+
+ virtual void finishedAdd();
+
+ // Local change
+ int &xSpace()
+ { return _xSpace; }
+ int &zSpace()
+ { return _zSpace; }
+ LabelDir &metricLabelDir()
+ { return _metDir; }
+ LabelDir &instLabelDir()
+ { return _instDir; }
+
+ virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ virtual const char* name() const;
+
+ virtual void display(QTextStream& os) const;
+
+ friend QTextStream& operator<<(QTextStream& os, BarObj const& rhs);
+
+private:
+
+ Text ** calcLabels(const QStringList &labels, LabelSide side,
+ int numLabels);
+ SoNode *doLabels(Text **text, LabelSide side, int numLabels);
+
+ BarObj();
+ BarObj(BarObj const&);
+ BarObj const& operator=(BarObj const &);
+};
+
+#endif /* _BAROBJ_H_ */
diff --git a/src/pmview/baseobj.cpp b/src/pmview/baseobj.cpp
new file mode 100644
index 0000000..8646381
--- /dev/null
+++ b/src/pmview/baseobj.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoCube.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include "baseobj.h"
+#include "defaultobj.h"
+
+BaseObj::~BaseObj()
+{
+ delete(_mod);
+}
+
+BaseObj::BaseObj(bool onFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ BaseObj::Alignment align)
+: ViewObj(x, y, cols, rows, align),
+ _on(onFlag),
+ _borderX(defaults.baseBorderX()),
+ _borderZ(defaults.baseBorderZ()),
+ _baseHeight(defaults.baseHeight()),
+ _length(defaults.barLength()),
+ _maxHeight(defaults.barHeight()),
+ _mod(0),
+ _cube(0),
+ _label("\n")
+{
+ _objtype |= BASEOBJ;
+
+ _baseColor[0] = defaults.baseColor(0);
+ _baseColor[1] = defaults.baseColor(1);
+ _baseColor[2] = defaults.baseColor(2);
+}
+
+void
+BaseObj::addBase(SoSeparator *sep)
+{
+ SoSeparator *cubeSep = new SoSeparator;
+
+ if (_on) {
+ SoTranslation *tran = new SoTranslation;
+ tran->translation.setValue(0.0, _baseHeight / 2, 0.0);
+ sep->addChild(tran);
+ SoBaseColor *col = new SoBaseColor;
+ col->rgb.setValue(_baseColor[0], _baseColor[1], _baseColor[2]);
+ cubeSep->addChild(col);
+ _cube = new SoCube;
+ _cube->width = baseWidth();
+ _cube->depth = baseDepth();
+ _cube->height = _baseHeight;
+ cubeSep->addChild(_cube);
+ }
+
+ _mod = new ToggleMod(cubeSep, (const char *)_label.toAscii());
+ sep->addChild(_mod->root());
+
+ if (_on) {
+ SoTranslation *tran2 = new SoTranslation;
+ tran2->translation.setValue(0.0, _baseHeight / 2.0, 0.0);
+ sep->addChild(tran2);
+ }
+}
+
+void
+BaseObj::setBaseSize(int width, int depth)
+{
+ if (_on) {
+ _cube->width = width;
+ _cube->depth = depth;
+ }
+}
+
+#if 0
+void
+BaseObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ ViewObj::setTran(xTran, zTran, setWidth, setDepth);
+}
+
+#endif
+
+QTextStream&
+operator<<(QTextStream& os, BaseObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+BaseObj::display(QTextStream& os) const
+{
+ ViewObj::display(os);
+ os << ", border: X = " << _borderX << ", Z = " << _borderZ
+ << ", height = " << _baseHeight << ", length = " << _length
+ << ", maxHeight = " << _maxHeight << ", color = " << _baseColor[0]
+ << ',' << _baseColor[1] << ',' << _baseColor[2] << ", on = "
+ << (_on == true ? "true" : "false") << ", label = "
+ << (_label == "\n" ? "\\n" : _label);
+}
+
+void
+BaseObj::add(Modulate *mod)
+{
+ _mod->addMod(mod);
+}
+
+void
+BaseObj::finishedAdd()
+{
+}
diff --git a/src/pmview/baseobj.h b/src/pmview/baseobj.h
new file mode 100644
index 0000000..e2736c0
--- /dev/null
+++ b/src/pmview/baseobj.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _BASEOBJ_H_
+#define _BASEOBJ_H_
+
+#include "viewobj.h"
+#include "togglemod.h"
+
+class SoCube;
+
+class BaseObj : public ViewObj
+{
+protected:
+
+ bool _on;
+ int _borderX;
+ int _borderZ;
+ int _baseHeight;
+ int _length;
+ int _maxHeight;
+ float _baseColor[3];
+ ToggleMod *_mod;
+ SoCube *_cube;
+ QString _label;
+
+public:
+
+ virtual ~BaseObj();
+
+ BaseObj(bool onFlag,
+ const DefaultObj &defaults,
+ int, int,
+ int cols = 1, int rows = 1,
+ BaseObj::Alignment align = BaseObj::center);
+
+ int borderX() const
+ { return _borderX; }
+ int borderZ() const
+ { return _borderZ; }
+ int baseWidth() const
+ { return _borderX * 2; }
+ int baseDepth() const
+ { return _borderZ * 2; }
+ int baseHeight() const
+ { return _baseHeight; }
+ float baseColor(int i) const
+ { return _baseColor[i]; }
+ const QString &label() const
+ { return _label; }
+ int length() const
+ { return _length; }
+ int maxHeight() const
+ { return _maxHeight; }
+
+ // Local changes
+ int &borderX()
+ { return _borderX; }
+ int &borderZ()
+ { return _borderZ; }
+ int &baseHeight()
+ { return _baseHeight; }
+ void baseColor(float r, float g, float b)
+ { _baseColor[0] = r; _baseColor[1] = g; _baseColor[2] = b; }
+ QString &label()
+ { return _label; }
+ int &length()
+ { return _length; }
+ int& maxHeight()
+ { return _maxHeight; }
+
+ bool state() const
+ { return _on; }
+ void state(bool flag);
+
+ virtual int width() const = 0;
+ virtual int depth() const = 0;
+
+ void addBase(SoSeparator *sep);
+
+ void setBaseSize (int width, int depth);
+
+// virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ virtual Modulate *modObj()
+ { return _mod; }
+
+ virtual void finishedAdd();
+
+ // Output
+ virtual void display(QTextStream& os) const;
+
+ virtual const char* name() const = 0;
+
+ friend QTextStream& operator<<(QTextStream& os, BaseObj const& rhs);
+
+protected:
+
+ void add(Modulate *mod);
+
+private:
+
+ BaseObj();
+ BaseObj(BaseObj const &);
+ BaseObj const& operator=(BaseObj const &);
+};
+
+#endif /* _BASEOBJ_H_ */
diff --git a/src/pmview/colorlist.cpp b/src/pmview/colorlist.cpp
new file mode 100644
index 0000000..5709a89
--- /dev/null
+++ b/src/pmview/colorlist.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "colorlist.h"
+#include <QtGui/QColor>
+
+ColorList theColorLists;
+
+ColorList::~ColorList()
+{
+ int i, j;
+
+ for (i = 0; i < _colors.size(); i++) {
+ ColorSpec &spec = *_colors[i];
+ for (j = 0; j < spec._list.size(); j++)
+ delete spec._list[j];
+ delete _colors[i];
+ }
+}
+
+ColorList::ColorList()
+: _names(),
+ _colors()
+{
+}
+
+const ColorSpec *
+ColorList::list(const char *name)
+{
+ int i;
+
+ for (i = 0; i < _names.size(); i++)
+ if (_names[i] == name)
+ return _colors[i];
+ return NULL;
+}
+
+bool
+ColorList::add(const char *name, const char *scaleColor)
+{
+ if (list(name) != NULL)
+ return false;
+
+ _names.append(name);
+ if (scaleColor != NULL) {
+ _colors.append(new ColorSpec(true));
+ if (addColor(scaleColor, 0.0) == false) {
+ _colors.last()->_list.append(new SbColor(0.0, 0.0, 1.0));
+ _colors.last()->_max.append(0.0);
+ }
+ }
+ else
+ _colors.append(new ColorSpec(false));
+
+ return true;
+}
+
+bool
+ColorList::add(const char *name, float red, float green, float blue)
+{
+ if (list(name) != NULL)
+ return false;
+
+ _names.append(name);
+ _colors.append(new ColorSpec(true));
+ _colors.last()->_list.append(new SbColor(red, green, blue));
+ _colors.last()->_max.append(0.0);
+
+ return true;
+}
+
+bool
+ColorList::findColor(const char *color, float &red, float &green, float &blue)
+{
+ QColor col;
+
+ col.setAllowX11ColorNames(true);
+ col.setNamedColor(color);
+ if (!col.isValid())
+ return false;
+
+ red = col.redF();
+ green = col.greenF();
+ blue = col.blueF();
+ return true;
+}
+
+bool
+ColorList::findColor(const char *color)
+{
+ QColor col;
+
+ col.setAllowX11ColorNames(true);
+ col.setNamedColor(color);
+ if (!col.isValid())
+ return false;
+
+ _colors.last()->_list.append(
+ new SbColor(col.redF(), col.greenF(), col.blueF()));
+ return true;
+}
+
+bool
+ColorList::addColor(const char *color)
+{
+ assert(_colors.size() > 0);
+ assert(_colors.last()->_scale == false);
+ return findColor(color);
+}
+
+bool
+ColorList::addColor(const char *color, double max)
+{
+ bool result;
+
+ assert(_colors.size() > 0);
+ assert(_colors.last()->_scale == true);
+ result = findColor(color);
+ if (result)
+ _colors.last()->_max.append(max);
+ return result;
+}
+
+bool
+ColorList::addColor(float red, float green, float blue)
+{
+ if (red < 0.0 || red > 1.0 || green < 0.0 || green > 1.0 ||
+ blue < 0.0 || blue > 1.0)
+ return false;
+
+ assert(_colors.size() > 0);
+ assert(_colors.last()->_scale == false);
+ _colors.last()->_list.append(new SbColor(red, green, blue));
+ return true;
+}
+
+bool
+ColorList::addColor(float red, float green, float blue, double max)
+{
+ if (red < 0.0 || red > 1.0 || green < 0.0 || green > 1.0 ||
+ blue < 0.0 || blue > 1.0)
+ return false;
+
+ assert(_colors.size() > 0);
+ assert(_colors.last()->_scale == true);
+ _colors.last()->_list.append(new SbColor(red, green, blue));
+ _colors.last()->_max.append(max);
+ return true;
+}
+
+QTextStream&
+operator<<(QTextStream& os, ColorList const& rhs)
+{
+ int i, j;
+ float r, g, b;
+
+ for (i = 0; i < rhs.numLists(); i++) {
+ const ColorSpec &list = *(rhs._colors[i]);
+ os << '[' << i << "] = " << rhs._names[i] << ", scale = "
+ << (list._scale == true ? "true" : "false") << ": ";
+ if (list._list.size()) {
+ for (j = 0; j < list._list.size(); j++) {
+ list._list[j]->getValue(r, g, b);
+ os << r << ',' << g << ',' << b;
+ if (list._scale)
+ os << "<=" << list._max[j];
+ os << ' ';
+ }
+ }
+ os << endl;
+ }
+ return os;
+}
+
diff --git a/src/pmview/colorlist.h b/src/pmview/colorlist.h
new file mode 100644
index 0000000..8f8a431
--- /dev/null
+++ b/src/pmview/colorlist.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _COLORLIST_H_
+#define _COLORLIST_H_
+
+#include "metriclist.h"
+#include <QStringList>
+
+struct ColorSpec;
+
+typedef QList<ColorSpec *> ColorsList;
+
+class ColorList
+{
+private:
+
+ QStringList _names;
+ ColorsList _colors;
+
+public:
+
+ virtual ~ColorList();
+
+ ColorList();
+
+ int numLists() const
+ { return _colors.size(); }
+
+ const ColorSpec *list(const char *name);
+
+ bool add(const char *name, const char *color = NULL);
+ bool add(const char *name, float red, float green, float blue);
+
+ // Add colors
+ bool addColor(const char *color);
+ bool addColor(float red, float blue, float green);
+
+ // Add scaled colors
+ bool addColor(const char *color, double max);
+ bool addColor(float red, float green, float blue, double max);
+
+ static bool findColor(const char *color, float &r, float &g, float &b);
+
+ friend QTextStream& operator<<(QTextStream& os, ColorList const& rhs);
+
+private:
+ bool findColor(const char *color);
+
+ ColorList(ColorList const &);
+ ColorList const& operator=(ColorList const &);
+ // Never defined
+};
+
+struct ColorSpec
+{
+ bool _scale;
+ SbColorList _list;
+ QList<double> _max;
+
+ ColorSpec(bool scale) : _scale(scale) {}
+};
+
+extern ColorList theColorLists;
+
+#endif /* _COLORLIST_H_ */
diff --git a/src/pmview/colormod.cpp b/src/pmview/colormod.cpp
new file mode 100644
index 0000000..b2cefd9
--- /dev/null
+++ b/src/pmview/colormod.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include "main.h"
+#include "colormod.h"
+#include "modlist.h"
+#include "launch.h"
+
+#include <iostream>
+using namespace std;
+
+ColorMod::~ColorMod()
+{
+}
+
+ColorMod::ColorMod(const char *metric, double scale,
+ const ColorScale &colors, SoNode *obj)
+: Modulate(metric, scale),
+ _state(Modulate::start),
+ _scale(colors),
+ _color(0)
+{
+ _root = new SoSeparator;
+ _color = new SoBaseColor;
+
+ _color->rgb.setValue(_errorColor.getValue());
+ _root->addChild(_color);
+ _root->addChild(obj);
+
+ if (_metrics->numValues() == 1 && _scale.numSteps() && status() >= 0) {
+ add();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorMod: Added " << metric << " (Id = "
+ << _root->getName().getString() << ")" << endl;
+#endif
+
+ }
+ else if (_metrics->numValues() > 1) {
+ warningMsg(_POS_,
+ "Color modulated metric (%s) has more than one value (%d)",
+ metric, _metrics->numValues());
+ }
+ else if (_scale.numSteps() == 0) {
+ warningMsg(_POS_,
+ "No color steps for color modulated metric (%s)",
+ metric);
+ }
+}
+
+void
+ColorMod::refresh(bool fetchFlag)
+{
+ QmcMetric &metric = _metrics->metric(0);
+
+ if (status() < 0)
+ return;
+
+ if (fetchFlag)
+ metric.update();
+
+ if (metric.error(0) <= 0) {
+ if (_state != Modulate::error) {
+ _color->rgb.setValue(_errorColor.getValue());
+ _state = Modulate::error;
+ }
+ }
+ else {
+ double value = metric.value(0) * theScale;
+ if (value > theNormError) {
+ if (_state != Modulate::saturated) {
+ _color->rgb.setValue(Modulate::_saturatedColor);
+ _state = Modulate::saturated;
+ }
+ }
+ else {
+ if (_state != Modulate::normal)
+ _state = Modulate::normal;
+ _color->rgb.setValue(_scale.step(value).color().getValue());
+ }
+ }
+}
+
+void
+ColorMod::dump(QTextStream &os) const
+{
+ os << "ColorMod: ";
+ if (status() < 0)
+ os << "Invalid Metric: " << pmErrStr(status()) << endl;
+ else {
+ os << "state = ";
+ dumpState(os, _state);
+ os << ", scale = " << _scale << ": ";
+ _metrics->metric(0).dump(os, true);
+ }
+}
+
+void
+ColorMod::infoText(QString &str, bool) const
+{
+ const QmcMetric &metric = _metrics->metric(0);
+ str = metric.spec(true, true, 0);
+ str.append(QChar('\n'));
+ if (_state == Modulate::error)
+ str.append(theErrorText);
+ else if (_state == Modulate::start)
+ str.append(theStartText);
+ else {
+ QString value;
+ str.append(value.setNum(metric.realValue(0), 'g', 4));
+ str.append(QChar(' '));
+ if (metric.desc().units().length() > 0)
+ str.append(metric.desc().units());
+ str.append(" [");
+ str.append(value.setNum(metric.value(0) * 100.0 * theScale, 'g', 4));
+ str.append("% of color scale]");
+ }
+}
+
+void
+ColorMod::launch(Launch &launch, bool) const
+{
+ if (status() < 0)
+ return;
+ launch.startGroup("point");
+ launch.addMetric(_metrics->metric(0), _scale, 0);
+ launch.endGroup();
+}
+
+int
+ColorMod::select(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorMod::select: " << _metrics->metric(0) << endl;
+#endif
+ return 1;
+}
+
+int
+ColorMod::remove(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorMod::remove: " << _metrics->metric(0) << endl;
+#endif
+ return 0;
+}
diff --git a/src/pmview/colormod.h b/src/pmview/colormod.h
new file mode 100644
index 0000000..c41f957
--- /dev/null
+++ b/src/pmview/colormod.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _COLORMOD_H
+#define _COLORMOD_H
+
+#include "colorscale.h"
+#include "modulate.h"
+
+class SoBaseColor;
+class SoNode;
+class Launch;
+
+class ColorMod : public Modulate
+{
+private:
+
+ State _state;
+ ColorScale _scale;
+ SoBaseColor *_color;
+
+public:
+
+ virtual ~ColorMod();
+
+ ColorMod(const char *metric, double scale,
+ const ColorScale &colors, SoNode *obj);
+
+ virtual void refresh(bool fetchFlag);
+
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void infoText(QString &str, bool) const;
+
+ virtual void launch(Launch &launch, bool) const;
+
+ virtual void dump(QTextStream &) const;
+
+private:
+
+ ColorMod();
+ ColorMod(const ColorMod &);
+ const ColorMod &operator=(const ColorMod &);
+ // Never defined
+};
+
+#endif /* _COLORMOD_H */
diff --git a/src/pmview/colorscale.cpp b/src/pmview/colorscale.cpp
new file mode 100644
index 0000000..c90658d
--- /dev/null
+++ b/src/pmview/colorscale.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "colorscale.h"
+
+ColorStep::~ColorStep()
+{
+}
+
+ColorStep::ColorStep(SbColor col, float val)
+: _color(),
+ _min(val)
+{
+ _color.setValue(col.getValue());
+}
+
+ColorStep::ColorStep(float r, float g, float b, float val)
+: _color(),
+ _min(val)
+{
+ SbColor tmp(r, g, b);
+ _color.setValue(tmp.getValue());
+}
+
+ColorStep::ColorStep(uint32_t col, float val)
+: _color(),
+ _min(val)
+{
+ float dummy = 0.0;
+ _color.setPackedValue(col, dummy);
+}
+
+ColorStep::ColorStep(const ColorStep &rhs)
+: _color(),
+ _min(rhs._min)
+{
+ _color.setValue(rhs._color.getValue());
+}
+
+const ColorStep &
+ColorStep::operator=(const ColorStep &rhs)
+{
+ if (this != &rhs) {
+ _color.setValue(rhs._color.getValue());
+ _min = rhs._min;
+ }
+ return *this;
+}
+
+ColorScale::~ColorScale()
+{
+ int i;
+
+ for (i = 0; i < _colors.size(); i++)
+ delete _colors.takeAt(i);
+}
+
+ColorScale::ColorScale(const SbColor &col)
+: _colors()
+{
+ add(new ColorStep(col));
+}
+
+ColorScale::ColorScale(float r, float g, float b)
+: _colors()
+{
+ add(new ColorStep(r, g, b));
+}
+
+ColorScale::ColorScale(uint32_t col)
+: _colors()
+{
+ add(new ColorStep(col));
+}
+
+ColorScale::ColorScale(const ColorScale &rhs)
+: _colors()
+{
+ int i;
+
+ for (i = 0; i < rhs._colors.size(); i++)
+ add(new ColorStep(rhs[i]));
+}
+
+const ColorScale &
+ColorScale::operator=(const ColorScale &rhs)
+{
+ int i;
+
+ if (this != &rhs) {
+ for (i = 0; i < _colors.size(); i++)
+ delete _colors.takeAt(i);
+ for (i = 0; i < rhs._colors.size(); i++)
+ add(new ColorStep(rhs[i]));
+ }
+ return *this;
+}
+
+int
+ColorScale::add(ColorStep *ptr)
+{
+ if (_colors.size()) {
+ float prev = _colors.last()->min();
+ if (prev >= ptr->min()) {
+ warningMsg(_POS_,
+ "Color step (%f) was less than previous step (%f), skipping.",
+ ptr->min(), prev);
+ return -1;
+ }
+ }
+ _colors.append(ptr);
+
+ return 0;
+}
+
+const ColorStep &
+ColorScale::step(float value)
+{
+ int i = _colors.size();
+
+ while (i > 0 && _colors[i-1]->min() > value)
+ i--;
+
+ if (i == 0)
+ return *(_colors[0]);
+ return *(_colors[i-1]);
+}
+
+QTextStream&
+operator<<(QTextStream &os, const ColorScale &rhs)
+{
+ int i;
+
+ if (rhs._colors.size() > 0) {
+ os << '[' << rhs[0].min();
+ for (i = 1; i < rhs.numSteps(); i++)
+ os << ", " << rhs[i].min();
+ os << ']';
+ }
+ else {
+ os << "empty";
+ }
+
+ return os;
+}
+
diff --git a/src/pmview/colorscale.h b/src/pmview/colorscale.h
new file mode 100644
index 0000000..5578092
--- /dev/null
+++ b/src/pmview/colorscale.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _COLORSCALE_H
+#define _COLORSCALE_H
+
+#include <Inventor/SbColor.h>
+#include <QtCore/QTextStream>
+#include <QtCore/QList>
+
+class ColorStep
+{
+private:
+
+ SbColor _color;
+ float _min;
+
+public:
+
+ ~ColorStep();
+
+ ColorStep(SbColor col, float val = 0.0);
+ ColorStep(float r, float g, float b, float val = 0.0);
+ ColorStep(uint32_t col, float val = 0.0);
+ ColorStep(const ColorStep &rhs);
+
+ const ColorStep &operator=(const ColorStep &);
+
+ const SbColor &color() const
+ { return _color; }
+ SbColor &color()
+ { return _color; }
+
+ const float &min() const
+ { return _min; }
+ float &min()
+ { return _min; }
+};
+
+typedef QList<ColorStep *> ColorStepList;
+
+class ColorScale
+{
+private:
+
+ ColorStepList _colors;
+
+public:
+
+ ~ColorScale();
+
+ ColorScale(const SbColor &col);
+ ColorScale(float r, float g, float b);
+ ColorScale(uint32_t col);
+ ColorScale(const ColorScale &);
+ const ColorScale &operator=(const ColorScale &);
+
+ int numSteps() const
+ { return _colors.size(); }
+
+ int add(ColorStep *ptr);
+
+ const ColorStep &operator[](int i) const
+ { return *(_colors[i]); }
+ ColorStep &operator[](int i)
+ { return *(_colors[i]); }
+
+ const ColorStep &step(float);
+
+ friend QTextStream &operator<<(QTextStream &os, const ColorScale &rhs);
+
+private:
+
+ ColorScale();
+ // Not defined
+};
+
+#endif /* _COLORSCALE_H */
diff --git a/src/pmview/colorscalemod.cpp b/src/pmview/colorscalemod.cpp
new file mode 100644
index 0000000..b162b49
--- /dev/null
+++ b/src/pmview/colorscalemod.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoScale.h>
+#include "main.h"
+#include "colorscalemod.h"
+#include "modlist.h"
+#include "launch.h"
+
+#include <iostream>
+using namespace std;
+
+ColorScaleMod::~ColorScaleMod()
+{
+}
+
+ColorScaleMod::ColorScaleMod(const char *metric, double scale,
+ const ColorScale &colors, SoNode *obj,
+ float xScale, float yScale, float zScale)
+: Modulate(metric, scale),
+ _state(Modulate::start),
+ _colScale(colors),
+ _scale(0),
+ _xScale(xScale),
+ _yScale(yScale),
+ _zScale(zScale),
+ _color(0)
+{
+ _root = new SoSeparator;
+ _color = new SoBaseColor;
+
+ _color->rgb.setValue(_errorColor.getValue());
+ _root->addChild(_color);
+
+ if (_metrics->numValues() == 1 && _colScale.numSteps() && status() >= 0) {
+
+ _scale = new SoScale;
+ _scale->scaleFactor.setValue(1.0, 1.0, 1.0);
+ _root->addChild(_scale);
+ _root->addChild(obj);
+
+ add();
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorScaleMod: Added " << metric << " (Id = "
+ << _root->getName().getString() << ")" << endl;
+#endif
+
+ }
+ else if (_metrics->numValues() > 1) {
+ warningMsg(_POS_,
+ "Color modulated metric (%s) has more than one value (%d)",
+ metric, _metrics->numValues());
+ _root->addChild(obj);
+ }
+ else if (status() < 0)
+ _root->addChild(obj);
+ else {
+ warningMsg(_POS_,
+ "No color steps for color modulated metric (%s)",
+ metric);
+ _root->addChild(obj);
+ }
+}
+
+void
+ColorScaleMod::refresh(bool fetchFlag)
+{
+ QmcMetric &metric = _metrics->metric(0);
+
+ if (status() < 0)
+ return;
+
+ if (fetchFlag)
+ metric.update();
+
+ if (metric.error(0) <= 0) {
+ if (_state != Modulate::error) {
+ _color->rgb.setValue(_errorColor.getValue());
+ _scale->scaleFactor.setValue((_xScale==0.0f ? 1.0 : theMinScale),
+ (_yScale==0.0f ? 1.0 : theMinScale),
+ (_zScale==0.0f ? 1.0 : theMinScale));
+ _state = Modulate::error;
+ }
+ }
+ else {
+ double value = metric.value(0) * theScale;
+ if (value > theNormError) {
+ if (_state != Modulate::saturated) {
+ _color->rgb.setValue(Modulate::_saturatedColor);
+ _scale->scaleFactor.setValue(1.0, 1.0, 1.0);
+ _state = Modulate::saturated;
+ }
+ }
+ else {
+ if (_state != Modulate::normal)
+ _state = Modulate::normal;
+ _color->rgb.setValue(_colScale.step(value).color().getValue());
+ if (value < Modulate::theMinScale)
+ value = Modulate::theMinScale;
+ else if (value > 1.0)
+ value = 1.0;
+ _scale->scaleFactor.setValue((_xScale==0.0f ? 1.0 : _xScale*value),
+ (_yScale==0.0f ? 1.0 : _yScale*value),
+ (_zScale==0.0f ? 1.0 : _zScale*value));
+ }
+ }
+}
+
+void
+ColorScaleMod::dump(QTextStream &os) const
+{
+ os << "ColorScaleMod: ";
+ if (status() < 0)
+ os << "Invalid Metric: " << pmErrStr(status()) << endl;
+ else {
+ os << "state = ";
+ dumpState(os, _state);
+ os << ", col scale = " << _colScale << ", y scale = "
+ << _scale->scaleFactor.getValue()[1]
+ << ": ";
+ _metrics->metric(0).dump(os, true);
+ }
+}
+
+void
+ColorScaleMod::infoText(QString &str, bool) const
+{
+ const QmcMetric &metric = _metrics->metric(0);
+
+ str = metric.spec(true, true, 0);
+ str.append(QChar('\n'));
+ if (_state == Modulate::error)
+ str.append(theErrorText);
+ else if (_state == Modulate::start)
+ str.append(theStartText);
+ else {
+ QString value;
+ str.append(value.setNum(metric.realValue(0), 'g', 4));
+ str.append(QChar(' '));
+ if (metric.desc().units().length() > 0)
+ str.append(metric.desc().units());
+ str.append(" [");
+ str.append(value.setNum(metric.realValue(0) * 100.0 * theScale, 'g', 4));
+ str.append("% of color scale]");
+ }
+}
+
+void
+ColorScaleMod::launch(Launch &launch, bool) const
+{
+ if (status() < 0)
+ return;
+ launch.startGroup("point");
+ launch.addMetric(_metrics->metric(0), _colScale, 0);
+ launch.endGroup();
+}
+
+int
+ColorScaleMod::select(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorScaleMod::select: " << _metrics->metric(0) << endl;
+#endif
+ return 1;
+}
+
+int
+ColorScaleMod::remove(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ColorScaleMod::remove: " << _metrics->metric(0) << endl;
+#endif
+ return 0;
+}
diff --git a/src/pmview/colorscalemod.h b/src/pmview/colorscalemod.h
new file mode 100644
index 0000000..66c670f
--- /dev/null
+++ b/src/pmview/colorscalemod.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _COLORSCALEMOD_H
+#define _COLORSCALEMOD_H
+
+#include "colorscale.h"
+#include "modulate.h"
+
+class SoBaseColor;
+class SoScale;
+class SoNode;
+class Launch;
+
+class ColorScaleMod : public Modulate
+{
+private:
+
+ State _state;
+ ColorScale _colScale;
+ SoScale *_scale;
+ float _xScale;
+ float _yScale;
+ float _zScale;
+ SoBaseColor *_color;
+
+public:
+
+ virtual ~ColorScaleMod();
+
+ ColorScaleMod(const char *metric, double scale,
+ const ColorScale &colors, SoNode *obj,
+ float xScale, float yScale, float zScale);
+
+ virtual void refresh(bool fetchFlag);
+
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void infoText(QString &str, bool) const;
+
+ virtual void launch(Launch &launch, bool) const;
+
+ virtual void dump(QTextStream &) const;
+
+private:
+
+ ColorScaleMod();
+ ColorScaleMod(const ColorScaleMod &);
+ const ColorScaleMod &operator=(const ColorScaleMod &);
+ // Never defined
+};
+
+#endif /* _COLORSCALEMOD_H */
diff --git a/src/pmview/defaultobj.cpp b/src/pmview/defaultobj.cpp
new file mode 100644
index 0000000..977186e
--- /dev/null
+++ b/src/pmview/defaultobj.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "defaultobj.h"
+#include "colorlist.h"
+#include <QtCore/QSettings>
+
+#include <iostream>
+using namespace std;
+
+DefaultObj *DefaultObj::theDefaultObj;
+
+DefaultObj::DefaultObj()
+: _baseBorderX(8),
+ _baseBorderZ(8),
+ _baseHeight(6),
+ _barSpaceX(8),
+ _barSpaceZ(8),
+ _barSpaceLabel(6),
+ _barLength(28),
+ _barHeight(80),
+ _labelMargin(5),
+ _gridMinWidth(20),
+ _gridMinDepth(20)
+{
+ _baseColor[0] = _baseColor[1] = _baseColor[2] = 0.15;
+ _labelColor[0] = _labelColor[1] = _labelColor[2] = 1.0;
+ _pipeLength = _barHeight;
+}
+
+const DefaultObj &
+DefaultObj::defObj()
+{
+ if (!theDefaultObj) {
+ theDefaultObj = new DefaultObj;
+ theDefaultObj->getResources();
+ }
+ return *theDefaultObj;
+}
+
+DefaultObj &
+DefaultObj::changeDefObj()
+{
+ if (!theDefaultObj) {
+ theDefaultObj = new DefaultObj;
+ theDefaultObj->getResources();
+ }
+ return *theDefaultObj;
+}
+
+DefaultObj::DefaultObj(const DefaultObj &rhs)
+: _baseBorderX(rhs._baseBorderX),
+ _baseBorderZ(rhs._baseBorderZ),
+ _baseHeight(rhs._baseHeight),
+ _barSpaceX(rhs._barSpaceX),
+ _barSpaceZ(rhs._barSpaceZ),
+ _barSpaceLabel(rhs._barSpaceLabel),
+ _barLength(rhs._barLength),
+ _barHeight(rhs._barHeight),
+ _labelMargin(rhs._labelMargin),
+ _gridMinWidth(rhs._gridMinWidth),
+ _gridMinDepth(rhs._gridMinDepth),
+ _pipeLength(rhs._pipeLength)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ _baseColor[i] = rhs._baseColor[i];
+ _labelColor[i] = rhs._labelColor[i];
+ }
+}
+
+const DefaultObj &
+DefaultObj::operator=(const DefaultObj &rhs)
+{
+ int i;
+
+ if (this != &rhs) {
+ _baseBorderX = rhs._baseBorderX;
+ _baseBorderZ = rhs._baseBorderZ;
+ _baseHeight = rhs._baseHeight;
+ _barSpaceX = rhs._barSpaceX;
+ _barSpaceZ = rhs._barSpaceZ;
+ _barSpaceLabel = rhs._barSpaceLabel;
+ _barLength = rhs._barLength;
+ _barHeight = rhs._barHeight;
+ _labelMargin = rhs._labelMargin;
+ _gridMinWidth = rhs._gridMinWidth;
+ _gridMinDepth = rhs._gridMinDepth;
+
+ for (i = 0; i < 3; i++) {
+ _baseColor[i] = rhs._baseColor[i];
+ _labelColor[i] = rhs._labelColor[i];
+ }
+ }
+ return *this;
+}
+
+QTextStream&
+operator<<(QTextStream &os, const DefaultObj &rhs)
+{
+ os << "baseBorderX=" << rhs._baseBorderX;
+ os << ", baseBorderZ=" << rhs._baseBorderZ;
+ os << ", baseHeight=" << rhs._baseHeight;
+ os << ", baseColor=" << rhs._baseColor[0] << ',' << rhs._baseColor[1]
+ << ',' << rhs._baseColor[2] << endl;
+ os << ", barSpaceX=" << rhs._barSpaceX;
+ os << ", barSpaceZ=" << rhs._barSpaceZ;
+ os << ", barSpaceLabel=" << rhs._barSpaceLabel;
+ os << ", barLength=" << rhs._barLength;
+ os << ", barHeight=" << rhs._barHeight;
+ os << ", labelMargin=" << rhs._labelMargin;
+ os << ", labelColor=" << rhs._labelColor[0] << ',' << rhs._labelColor[1]
+ << ',' << rhs._labelColor[2] << endl;
+ os << ", gridMinWidth=" << rhs._gridMinWidth;
+ os << ", gridMinDepth=" << rhs._gridMinDepth;
+ return os;
+}
+
+static void
+getColorResource(const char *name, QString label, float &r, float &g, float &b)
+{
+ if (label != QString::null && label.compare("default") != 0) {
+ const char *str = (const char *)label.toAscii();
+ if (ColorList::findColor(str, r, g, b) == false) {
+ pmprintf("%s: Unable to map color resource \"%s\" to \"%s\", "
+ "using default color rgbi:%f/%f/%f\n",
+ pmProgname, name, str, r, g, b);
+ }
+ }
+}
+
+void
+DefaultObj::getResources()
+{
+ QString color;
+ QSettings resources;
+ resources.beginGroup(pmProgname);
+
+ _baseBorderX = resources.value("baseBorderWidth", 8).toInt();
+ _baseBorderZ = resources.value("baseBorderDepth", 8).toInt();
+ _baseHeight = resources.value("baseHeight", 2).toInt();
+ color = resources.value("baseColor", QString("default")).toString();
+ getColorResource("baseColor", color,
+ _baseColor[0], _baseColor[1], _baseColor[2]);
+ _barSpaceX = resources.value("barSpaceWidth", 8).toInt();
+ _barSpaceZ = resources.value("barSpaceDepth", 8).toInt();
+ _barSpaceLabel = resources.value("barSpaceLabel", 6).toInt();
+ _barLength = resources.value("barLength", 28).toInt();
+ _barHeight = resources.value("barHeight", 80).toInt();
+ _labelMargin = resources.value("labelMargin", 5).toInt();
+ color = resources.value("labelColor", QString("default")).toString();
+ getColorResource("labelColor", color,
+ _labelColor[0], _labelColor[1], _labelColor[2]);
+ _gridMinWidth = resources.value("gridMinWidth", 20).toInt();
+ _gridMinDepth = resources.value("gridMinDepth", 20).toInt();
+
+ resources.endGroup();
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "DefaultObj::getResources: " << *this << endl;
+#endif
+}
diff --git a/src/pmview/defaultobj.h b/src/pmview/defaultobj.h
new file mode 100644
index 0000000..f600790
--- /dev/null
+++ b/src/pmview/defaultobj.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _DEFAULTOBJ_H_
+#define _DEFAULTOBJ_H_
+
+#include "main.h"
+#include <QtCore/QTextStream>
+
+class DefaultObj
+{
+private:
+
+ static DefaultObj *theDefaultObj;
+
+ int _baseBorderX;
+ int _baseBorderZ;
+ int _baseHeight;
+ float _baseColor[3];
+
+ int _barSpaceX;
+ int _barSpaceZ;
+ int _barSpaceLabel;
+ int _barLength;
+ int _barHeight;
+
+ int _labelMargin;
+ float _labelColor[3];
+
+ int _gridMinWidth;
+ int _gridMinDepth;
+ int _pipeLength;
+
+public:
+
+ ~DefaultObj()
+ {}
+
+ DefaultObj();
+ DefaultObj(const DefaultObj &);
+ const DefaultObj &operator=(const DefaultObj &rhs);
+
+ static const DefaultObj &defObj();
+
+ // Query
+ int baseBorderX() const
+ { return _baseBorderX; }
+ int baseBorderZ() const
+ { return _baseBorderZ; }
+ int baseHeight() const
+ { return _baseHeight; }
+ float baseColor(int i) const
+ { return _baseColor[i]; }
+ int barSpaceX() const
+ { return _barSpaceX; }
+ int barSpaceZ() const
+ { return _barSpaceZ; }
+ int barSpaceLabel() const
+ { return _barSpaceLabel; }
+ int barLength() const
+ { return _barLength; }
+ int barHeight() const
+ { return _barHeight; }
+ int labelMargin() const
+ { return _labelMargin; }
+ float labelColor(int i) const
+ { return _labelColor[i]; }
+ int gridMinWidth() const
+ { return _gridMinWidth; }
+ int gridMinDepth() const
+ { return _gridMinDepth; }
+ int pipeLength () const
+ { return _pipeLength; }
+
+ // Local Changes
+ int &baseBorderX()
+ { return _baseBorderX; }
+ int & pipeLength ()
+ { return _pipeLength; }
+ int &baseBorderZ()
+ { return _baseBorderZ; }
+ int &baseHeight()
+ { return _baseHeight; }
+ void baseColor(float r, float g, float b)
+ { _baseColor[0] = r; _baseColor[1] = g; _baseColor[2] = b; }
+ int &barSpaceX()
+ { return _barSpaceX; }
+ int &barSpaceZ()
+ { return _barSpaceZ; }
+ int &barSpaceLabel()
+ { return _barSpaceLabel; }
+ int &barLength()
+ { return _barLength; }
+ int &barHeight()
+ { return _barHeight; }
+ int &labelMargin()
+ { return _labelMargin; }
+ void labelColor(float r, float g, float b)
+ { _labelColor[0] = r; _labelColor[1] = g; _labelColor[2] = b; }
+ int &gridMinWidth()
+ { return _gridMinWidth; }
+ int &gridMinDepth()
+ { return _gridMinDepth; }
+
+ // Global
+ static void baseBorderX(int val)
+ { changeDefObj()._baseBorderX = val; }
+ static void baseBorderZ(int val)
+ { changeDefObj()._baseBorderZ = val; }
+ static void baseHeight(int val)
+ { changeDefObj()._baseHeight = val; }
+ static void baseColors(float r, float g, float b)
+ { changeDefObj().baseColor(r, g, b); }
+ static void barSpaceX(int val)
+ { changeDefObj()._barSpaceX = val; }
+ static void barSpaceZ(int val)
+ { changeDefObj()._barSpaceZ = val; }
+ static void barSpaceLabel(int val)
+ { changeDefObj()._barSpaceLabel = val; }
+ static void barLength(int val)
+ { changeDefObj()._barLength = val; }
+ static void barHeight(int val)
+ { changeDefObj()._barHeight = val; }
+ static void labelMargin(int val)
+ { changeDefObj()._labelMargin = val; }
+ static void labelColors(float r, float g, float b)
+ { changeDefObj().labelColor(r, g, b); }
+ static void gridMinWidth(int val)
+ { changeDefObj()._gridMinWidth = val; }
+ static void gridMinDepth(int val)
+ { changeDefObj()._gridMinDepth = val; }
+
+ friend QTextStream& operator<<(QTextStream &os, const DefaultObj &rhs);
+
+private:
+
+ static DefaultObj &changeDefObj();
+ void getResources();
+};
+
+#endif /* _DEFAULTOBJ_H_ */
diff --git a/src/pmview/error.cpp b/src/pmview/error.cpp
new file mode 100644
index 0000000..c5981d3
--- /dev/null
+++ b/src/pmview/error.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+
+int errorCount;
+int lineNum;
+
+void
+yywarn(const char *s)
+{
+ const char * fmt = theConfigName.length() ? "%s: warning - %s(%d): %s\n":
+ "%s: warning - line %3$d: %4$s\n";
+ const char * config = (const char *)theConfigName.toAscii();
+
+ pmprintf(fmt, pmProgname, config, lineNum+1, s);
+ pmflush();
+}
+
+void
+yyerror(const char *s)
+{
+ const char * fmt = theConfigName.length() ? "%s: error - %s(%d): %s\n":
+ "%s: error - line %3$d: %4$s\n";
+ const char * config = (const char *)theConfigName.toAscii();
+ const char badeof[] = "unexpected end of file";
+
+ markpos();
+ if (!locateError())
+ s = (char *)badeof;
+
+ pmprintf(fmt, pmProgname, config, lineNum+1, s);
+ pmflush();
+ errorCount++; /* It's used in pmview.cpp to abort the execution */
+}
+
diff --git a/src/pmview/front-ends/GNUmakefile b/src/pmview/front-ends/GNUmakefile
new file mode 100644
index 0000000..c67f02b
--- /dev/null
+++ b/src/pmview/front-ends/GNUmakefile
@@ -0,0 +1,25 @@
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+#VIEWDIR = $(PCP_VAR_DIR)/config/pmview
+VIEWDIR = $(PCP_BIN_DIR)
+LOGDIR = $(PCP_VAR_DIR)/config/pmlogger
+ARGSDIR = $(PCP_SHARE_DIR)/lib
+
+VIEWS = clustervis dkvis mpvis nfsvis osvis weblogvis webpingvis webvis
+LOGGERS = config.dkvis config.mpvis config.nfsvis config.osvis \
+ config.weblogvis config.webpingvis config.webvis
+
+LSRCFILES = $(VIEWS) $(LOGGERS) pmview-args
+
+default build-me:
+
+include $(BUILDRULES)
+
+install: default
+ #$(INSTALL) -m 755 -d $(VIEWDIR)
+ $(INSTALL) -m 755 $(VIEWS) $(VIEWDIR)
+ $(INSTALL) -m 755 -d $(LOGDIR)
+ $(INSTALL) -m 444 $(LOGGERS) $(LOGDIR)
+ $(INSTALL) -m 755 -d $(ARGSDIR)
+ $(INSTALL) -m 755 pmview-args $(ARGSDIR)/pmview-args
diff --git a/src/pmview/front-ends/clustervis b/src/pmview/front-ends/clustervis
new file mode 100755
index 0000000..6cab7aa
--- /dev/null
+++ b/src/pmview/front-ends/clustervis
@@ -0,0 +1,331 @@
+#!/bin/sh
+#
+# Copyright (c) 2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+debug=false
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+# --- scaling parameters ---
+#
+
+# maximum packets per second
+max=750
+
+# maximum req rate (default: 5% of packet rate)
+maxreq=`expr $max / 20`
+
+# milliseconds per CPU
+maxcpu=1000
+
+#
+# clustervis tolerates multiple hosts or archives
+_pmview_multiple_sources_are_ok
+
+# --- define usage message ---
+#
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options]
+
+Default hosts are specified in /etc/nodes (or /etc/ace/nodes)
+or specify the file specifying cluster hosts with the -H flag.
+The -h flag may be used to specify multiple hosts or the -a flag
+may be used to specify multiple archives. The -h, -a and -H flags
+are all mutually exclusive.
+
+options:
+ -H nodes file specifying hosts in cluster
+ [default $PCP_CLUSTER_CONFIG or /etc/nodes or /etc/ace/nodes]
+ -m max maximum expected packets sent or received per sec [default 750]
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+
+ _pmview_usage >> $tmp.msg
+ echo >> $tmp.msg
+ echo >> $tmp.msg 'Default title is: Web Server Activity'
+ echo >> $tmp.msg '
+ By default all network interfaces are shown, with a maximum packet rate
+ of '$max' packets per second. The maximum request rate is 5% (of the
+ maximum packet rate) and the maximum error rate is 20% of the maximum
+ request rate.
+
+ If given, the [interface ...] regular expressions restrict
+ the network statistics displayed to matching network interface names only.'
+
+ _pmview_info -f $tmp.msg
+}
+
+# --- build WM_COMMAND X(1) property for restart after login/logout ---
+#
+echo -n "pmview Version 2.1 \"$prog\"" > $tmp.pmview
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.pmview
+done
+echo >> $tmp.pmview
+
+# --- parse command line arguments ---
+#
+verbose=false
+argInterfaces=""
+hosts=""
+interfaces=""
+numsource=1
+if [ -z "$PCP_CLUSTER_CONFIG" ]
+then
+ nodesfile=/etc/nodes
+ [ ! -f "$nodesfile" ] && nodesfile=/etc/ace/nodes
+else
+ nodesfile="$PCP_CLUSTER_CONFIG"
+ if [ ! -f "$nodesfile" ]
+ then
+ echo "Error: \"$nodesfile\" specified in \$PCP_CLUSTER_CONFIG: file not found"
+ _usage
+ exit 1
+ fi
+fi
+
+livemode=false
+for f in $*
+do
+ [ $f = "-h" ] && livemode=true
+done
+
+_pmview_args "$@"
+$debug && echo "DEBUG msource=\"$msource\""
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?H:m:v:V" c $otherArgs
+ do
+ case $c
+ in
+ H)
+ nodesfile=$OPTARG
+ if [ ! -f "$nodesfile" ]
+ then
+ _pmview_error "$prog: \"$nodesfile\" for -H file not found"
+ #NOTREACHED
+ fi
+ ;;
+ m)
+ max=$OPTARG
+ if [ "X-$max" != "X`expr 0 + -$max 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -m must have a positive integral argument"
+ #NOTREACHED
+ fi
+ maxreq=`expr $max / 20`
+ ;;
+
+ V)
+ verbose=true
+ ;;
+
+ '?')
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+fi
+
+if [ $numsource -eq 0 ]
+then
+ if [ -f $nodesfile ]
+ then
+ livemode=true
+ sourcelist=`sed -e '/^#/d' $nodesfile`
+ numsource=`echo $sourcelist | wc -w`
+ else
+ _pmview_error "$prog: -H, -a or -h must be given"
+ fi
+fi
+
+$debug && echo DEBUG sourcelist=\"$sourcelist\"
+hostcols=`echo "sqrt($numsource)" | bc`
+$debug && echo hostcols=$hostcols
+
+
+# --- set the window title ---
+#
+if [ -z "$titleArg" ]
+then
+ titleArg="SGI PCP : Cluster Node Activity"
+fi
+
+
+# ---- the real config starts here ---
+# pmview Version 2.1
+#
+cat << end-of-file >> $tmp.pmview
+
+_stackLength 26
+_marginWidth 4
+_marginDepth 4
+
+_colorList net_colors (
+ rgbi:0.8/0.0/0.0
+ rgbi:0.0/0.8/0.8
+ rgbi:1.0/0.5/0.0
+)
+
+_colorList cpu_colors ( red2 blue2 blue2 )
+
+# main grid
+_grid _show (
+
+end-of-file
+
+hostrow=0
+hostcol=0
+for source in $sourcelist
+do
+ if $livemode
+ then
+ host=$source
+ msource="-h $source"
+ else
+ host=`pmdumplog -l $source | $PCP_AWK_PROG '/^Performance/ {print $NF}'`
+ msource="-a $source"
+ fi
+
+ nice=`pmprobe $msource -i kernel.all.cpu.nice | $PCP_AWK_PROG '{print $2; exit}'`
+ if [ "$nice" = 1 ]
+ then
+ nicemetric=kernel.percpu.cpu.nice
+ else
+ nicemetric=""
+ fi
+
+ _pmview_cache_fetch -v \
+ kernel.percpu.cpu.user $nicemetric \
+ network.interface.in.packets \
+ network.interface.out.packets
+
+ # --- how many CPUs on this system? ---
+ #
+ if _pmview_fetch -I kernel.percpu.cpu.user
+ then
+ ncpu=`wc -l <$tmp.pmview_result`
+ else
+ _pmview_fetch_fail "get the number of CPUs"
+ fi
+
+ _pmview_cache_fetch -I network.interface.total.bytes
+
+ # --- how many network interfaces on this system? ---
+ #
+ if _pmview_fetch -I network.interface.in.packets
+ then
+ nnet=`grep -v '^lo' $tmp.pmview_result | wc -l`
+ else
+ _pmview_fetch_fail "get the number of network interfaces"
+ fi
+
+ rows=`echo "sqrt($ncpu + $ncpu + $nnet + $nnet)" | bc`
+ [ $rows -lt 4 ] && rows=4
+ row=0
+ col=0
+
+ echo "# grid for host $host" >>$tmp.pmview
+ echo "_baseHeight 8" >>$tmp.pmview
+ echo "_grid $hostcol $hostrow _hide (" >>$tmp.pmview
+
+ echo " _baseColor rgbi:0.4/0.4/0.6" >>$tmp.pmview
+ echo " _label 0 0 _down _large \"$host\"" >>$tmp.pmview
+ echo " _grid 1 0 _show (" >>$tmp.pmview
+
+ _pmview_fetch -I kernel.percpu.cpu.user
+ for cpu in `cat $tmp.pmview_result`
+ do
+ echo " _label $row $col _west \"$cpu\"" >>$tmp.pmview
+ echo " _stack `expr $row + 1` $col _hide (" >>$tmp.pmview
+ echo " _metrics (" >>$tmp.pmview
+ echo " $host:kernel.percpu.cpu.sys[$cpu] $maxcpu" >>$tmp.pmview
+ if [ ! -z "$nicemetric" ]
+ then
+ echo " $host:kernel.percpu.cpu.nice[$cpu] $maxcpu" >>$tmp.pmview
+ fi
+ echo " $host:kernel.percpu.cpu.user[$cpu] $maxcpu" >>$tmp.pmview
+ echo " )" >>$tmp.pmview
+ echo " _colorlist cpu_colors" >>$tmp.pmview
+ echo " )" >>$tmp.pmview
+
+ $debug && echo stack at row=`expr $row + 1` col=$col \"$cpu\"
+ $debug && echo label at row=$row col=$col
+
+ col=`expr $col + 1`
+ if [ $col -ge $rows ]
+ then
+ col=0
+ row=`expr $row + 2`
+ fi
+ done
+
+ _pmview_fetch -I network.interface.in.packets
+ grep -v '^lo' $tmp.pmview_result >$tmp.net
+ for net in `cat $tmp.net`
+ do
+ echo " _label $row $col _west \"$net\"" >>$tmp.pmview
+ echo " _stack `expr $row + 1` $col _hide (" >>$tmp.pmview
+ echo " _metrics (" >>$tmp.pmview
+ echo " $host:network.interface.total.errors[$net] $max" >>$tmp.pmview
+ echo " $host:network.interface.in.packets[$net] $max" >>$tmp.pmview
+ echo " $host:network.interface.out.packets[$net] $max" >>$tmp.pmview
+ echo " )" >>$tmp.pmview
+ echo " _colorlist net_colors" >>$tmp.pmview
+ echo " )" >>$tmp.pmview
+
+ $debug && echo stack at row=`expr $row + 1` col=$col \"$net\"
+ $debug && echo label at row=$row col=$col
+
+ col=`expr $col + 1`
+ if [ $col -ge $rows ]
+ then
+ col=0
+ row=`expr $row + 2`
+ fi
+
+ done
+ echo " )" >>$tmp.pmview
+ row=`expr $row + 1`
+ echo "# end of host grid" >>$tmp.pmview
+ echo ")" >>$tmp.pmview
+
+ hostcol=`expr $hostcol + 1`
+ if [ $hostcol -ge $hostcols ]
+ then
+ hostcol=0
+ hostrow=`expr $hostrow + 1`
+ fi
+
+done
+
+echo "" >>$tmp.pmview
+echo "# end of main grid" >>$tmp.pmview
+echo ")" >>$tmp.pmview
+
+$verbose && cat $tmp.pmview
+
+eval $PMVIEW <$tmp.pmview $args -title "'$titleArg'" -xrm "'*iconName: $prog'"
+
+exit
+
diff --git a/src/pmview/front-ends/config.clustervis b/src/pmview/front-ends/config.clustervis
new file mode 100644
index 0000000..118cb34
--- /dev/null
+++ b/src/pmview/front-ends/config.clustervis
@@ -0,0 +1,13 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with clustervis(1)
+#
+
+log advisory on default {
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ network.interface.in.packets
+ network.interface.out.packets
+ network.interface.total.bytes
+ network.interface.total.errors
+}
diff --git a/src/pmview/front-ends/config.dkvis b/src/pmview/front-ends/config.dkvis
new file mode 100755
index 0000000..03e0e31
--- /dev/null
+++ b/src/pmview/front-ends/config.dkvis
@@ -0,0 +1,14 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with dkvis(1)
+#
+
+log advisory on default {
+ disk.dev.total
+ disk.dev.read
+ disk.dev.write
+ disk.dev.bytes
+ disk.dev.read_bytes
+ disk.dev.write_bytes
+ disk.dev.total_bytes
+}
diff --git a/src/pmview/front-ends/config.mpvis b/src/pmview/front-ends/config.mpvis
new file mode 100755
index 0000000..f28ce84
--- /dev/null
+++ b/src/pmview/front-ends/config.mpvis
@@ -0,0 +1,13 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with mpvis(1)
+#
+
+log advisory on default {
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.sxbrk
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+}
diff --git a/src/pmview/front-ends/config.nfsvis b/src/pmview/front-ends/config.nfsvis
new file mode 100755
index 0000000..f8ab0fb
--- /dev/null
+++ b/src/pmview/front-ends/config.nfsvis
@@ -0,0 +1,12 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with nfsvis(1)
+#
+
+log advisory on default {
+ nfs
+}
+
+log advisory on default {
+ nfs3
+}
diff --git a/src/pmview/front-ends/config.osvis b/src/pmview/front-ends/config.osvis
new file mode 100755
index 0000000..629a3a4
--- /dev/null
+++ b/src/pmview/front-ends/config.osvis
@@ -0,0 +1,31 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with osvis(1)
+#
+log mandatory on once {
+ hinv.ncpu
+ hinv.ndisk
+ hinv.physmem
+ mem.physmem
+}
+
+log advisory on default {
+ disk.all.avg_disk.active
+ disk.all.read
+ disk.all.write
+ disk.ctl.avg_disk.active
+ kernel.all.load
+ kernel.all.cpu.intr
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ mem.freemem
+ mem.util
+ network.interface.in.bytes
+ network.interface.in.packets
+ network.interface.in.errors
+ network.interface.out.bytes
+ network.interface.out.packets
+ network.interface.out.errors
+}
+
diff --git a/src/pmview/front-ends/config.weblogvis b/src/pmview/front-ends/config.weblogvis
new file mode 100755
index 0000000..1d3453f
--- /dev/null
+++ b/src/pmview/front-ends/config.weblogvis
@@ -0,0 +1,20 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with weblogvis(1)
+#
+
+log advisory on default {
+ web.perserver.logidletime
+ web.perserver.requests.total
+ web.perserver.bytes.total
+ web.perserver.requests.size.unknown
+ web.perserver.requests.size.gt3m
+ web.perserver.requests.size.le3m
+ web.perserver.requests.size.le1m
+ web.perserver.requests.size.le300k
+ web.perserver.requests.size.le100k
+ web.perserver.requests.size.le30k
+ web.perserver.requests.size.le10k
+ web.perserver.requests.size.le3k
+ web.perserver.requests.size.zero
+}
diff --git a/src/pmview/front-ends/config.webpingvis b/src/pmview/front-ends/config.webpingvis
new file mode 100755
index 0000000..078ab3c
--- /dev/null
+++ b/src/pmview/front-ends/config.webpingvis
@@ -0,0 +1,8 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with webpingvis(1)
+#
+
+log advisory on default {
+ webpingvis
+}
diff --git a/src/pmview/front-ends/config.webvis b/src/pmview/front-ends/config.webvis
new file mode 100644
index 0000000..08394fc
--- /dev/null
+++ b/src/pmview/front-ends/config.webvis
@@ -0,0 +1,51 @@
+#
+# pmlogger(1) configuration file suitable creating an archive to be
+# used with webvis(1)
+#
+
+# file system and hinv stats
+# (log these once so they _definitely_ exist at start of archive)
+log advisory on once {
+ hinv.ncpu
+ hinv.physmem
+ hinv.ndisk
+ mem.physmem
+ disk.dev.read
+}
+
+log advisory on 60 seconds {
+ # Kernel, disk and swap and paging metrics
+ disk.all.read
+ disk.all.write
+ disk.all.avg_disk.active
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ mem.freemem
+ mem.util.kernel
+ mem.util.fs_ctl
+ mem.util.fs_dirty
+ mem.util.fs_clean
+ mem.util.user
+ swap.pagesout
+ network.interface.out.drops
+ network.interface.out.errors
+ network.interface.out.packets
+ network.interface.in.drops
+ network.interface.in.errors
+ network.interface.in.packets
+ network.interface.total.bytes
+ # Network data rates and error conditions
+ network.tcp.drops
+ network.tcp.conndrops
+ network.tcp.timeoutdrop
+ network.tcp.sndrexmitpack
+ network.tcp.rcvbadsum
+ network.tcp.rexmttimeo
+ network.mbuf.failed
+ network.mbuf.waited
+ # Web logs - both frequent and infrequent samples
+ web.allservers
+}
diff --git a/src/pmview/front-ends/dkvis b/src/pmview/front-ends/dkvis
new file mode 100755
index 0000000..5cf7267
--- /dev/null
+++ b/src/pmview/front-ends/dkvis
@@ -0,0 +1,572 @@
+#! /bin/sh
+#
+# Copyright (c) 1997-2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options] [diskid ...]
+
+options:
+ -b display data throughput (Kbytes/sec) rather than IOPs
+ -m maxrate maximum activity expected (integer)
+ [default 150 IOPs (without -b) or 2048 Kbytes/sec (with -b)]
+ -r display only the read activity
+ -V verbose/diagnostic output
+ -w display only the write activity
+ -X ndisk limit layout to at most this many disks per row, set
+ to 0 for no limit [default 12]
+ -Y nctl limit layout to at most this many controllers per column
+ before starting a new group of controllers, set to 0
+ for no limit [default 16]
+
+pmview(1) options:'
+
+ _pmview_usage >> $tmp.msg
+ echo >> $tmp.msg
+ echo 'Default title is: Total Disk Activity (IOPS) for Host' >> $tmp.msg
+ _pmview_info -f $tmp.msg
+}
+
+verbose=false
+type=total
+Type=Total
+Thru=IOPS
+max=''
+force=false
+maxnctl=16
+maxndisk=12
+diskargs=
+debug=false
+
+# build WM_COMMAND X(1) property for restart after login/logout
+#
+echo -n "pmview Version 2.1 \"dkvis\"" >$tmp.conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.conf
+done
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?bdm:R:rv:VX:Y:w" c $otherArgs
+ do
+ case $c
+ in
+ b)
+ Thru="Kbytes/sec"
+ ;;
+ d)
+ debug=true
+ ;;
+ m)
+ _pmview_unsigned $c
+ max=$OPTARG
+ ;;
+ r)
+ if [ $type != total ]
+ then
+ _pmview_error "$prog: only one -r or -w option may be specified"
+ # NOTREACHED
+ fi
+ type=read
+ Type=Read
+ ;;
+
+ v)
+ if [ $OPTARG = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ elif [ $OPTARG != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+
+ V)
+ verbose=true
+ ;;
+ w)
+ if [ $type != total ]
+ then
+ _pmview_error "$prog: only one -r or -w option may be specified"
+ # NOTREACHED
+ fi
+ type=write
+ Type=Write
+ ;;
+ X)
+ _pmview_unsigned $c
+ maxndisk=$OPTARG
+ ;;
+ [YR])
+ # used to be -R, so support both for backwards compatibility
+ _pmview_unsigned $c
+ maxnctl=$OPTARG
+ force=true
+ ;;
+ ?)
+ _usage
+ exit 1
+ ;;
+
+ esac
+ done
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+
+ [ $# -gt 0 ] && diskargs=$@
+fi
+
+if [ "$Thru" != "IOPS" ]
+then
+ case $type
+ in
+ read)
+ type=read_bytes
+ ;;
+ write)
+ type=write_bytes
+ ;;
+ *)
+ if pminfo $msource disk.dev.total_bytes >/dev/null 2>&1
+ then
+ type=total_bytes
+ else
+ type=bytes
+ fi
+ ;;
+ esac
+fi
+
+# default max depends on -b or not
+#
+if [ "$Thru" != "IOPS" ]
+then
+ [ -z "$max" ] && max=2048
+else
+ [ -z "$max" ] && max=150
+fi
+
+pminfo -f $msource $namespace disk.dev.$type >$tmp.info 2>$tmp.err
+if [ -s $tmp.err ]
+then
+ cat $tmp.err | _pmview_fetch_mesg >> $tmp.msg
+ echo "$prog: Failed to get disk inventory from host \"$host\"" | fmt >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+fi
+
+$PCP_AWK_PROG < $tmp.info -F'"' '/inst/ { print $2 }' > $tmp.disks
+
+if [ ! -s $tmp.disks ]
+then
+ cat $tmp.info | _pmview_fetch_mesg >> $tmp.msg
+ echo "$prog: Failed to get disk inventory from host \"$host\"" | fmt >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+fi
+
+if [ ! -z "$diskargs" ]
+then
+ rm -f $tmp.tmp $tmp.msg
+ for dk in $diskargs
+ do
+ if echo "$dk" | grep '[.[^]' >/dev/null
+ then
+ # assume egrep(1) regular expression
+ #
+ if egrep "$dk" $tmp.disks >>$tmp.tmp
+ then
+ # found some matches
+ #
+ :
+ else
+ echo "$prog: pattern \"$dk\" does not match any disks ..." >$tmp.msg
+ fi
+ elif grep "^$dk\$" $tmp.disks >/dev/null
+ then
+ echo $dk >>$tmp.tmp
+ else
+ echo "$prog: disk \"$dk\" not in the disk inventory ..." >$tmp.msg
+ fi
+ done
+ if [ -s $tmp.msg ]
+ then
+ echo "Disks on host \"$host\" are:" >> $tmp.msg
+ fmt $tmp.disks | sed -e 's/^/ /' >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+ fi
+ mv $tmp.tmp $tmp.disks
+fi
+
+if $debug
+then
+ echo "Disks ..."
+ cat $tmp.disks
+fi
+
+ndisk=`wc -l <$tmp.disks | sed -e 's/ //g'`
+
+if [ "$ndisk" -lt 1 ]
+then
+ echo "$prog: Cannot get disk inventory for host \"$host\"\n" > $tmp.msg
+ cat $tmp.info >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+fi
+
+cat << end-of-file >> $tmp.conf
+
+#
+# dkvis
+#
+end-of-file
+
+if $verbose
+then
+ echo "# Disk Inventory:" >> $tmp.conf
+ fmt <$tmp.disks | sed -e 's/^/# /' >> $tmp.conf
+fi
+cp $tmp.disks $tmp.in
+
+# get controller-based order
+#
+# the mapping is controlled by the here-is document below, where each
+# (non-comment) line contains three fields separated by tabs:
+# 1. pattern to match disk names (must match the start of a disk indom name)
+# - don't bother escaping / (fixed up later)
+# - <n> is expanded to \([0-9][0-9]*\) later
+# - <h> is expanded to \([0-9a-f][0-9a-f]*\) later
+# 2. controller-tag ... construction from literals and substrings (in sed
+# syntax, so \1, \2, etc) from the pattern
+# 3. sort field order ... constructed from the ordinal labels of the
+# substrings in the pattern (comma separated) ... "n" is appended
+# for numerical sorting
+#
+# so the control line
+# dks<n>d<n>l*\([0-9]*\) dks\1 1n,2n,3n
+#
+# will first turn disk indom names like dks14d3, dks14d3l2, dks14d3l10,
+# dks14d2, dks10d7, dks10d10 and dks10d1 into records like
+#
+# dks14 14 3 dks14d3
+# dks14 14 3 2 dks14d3l2
+# dks14 14 3 10 dks14d3l10
+# dks14 14 2 dks14d2
+# dks10 10 7 dks10d7
+# dks10 10 1 10 dks10d110
+# dks10 10 1 dks10d1
+# --+-- -+ -+ -+ ----+----
+# | | | | |
+# | | | | +-- full disk name
+# | | | +-- 3rd sort key (lun, may be missing)
+# | | +-- 2nd sort key (target)
+# | +-- 1st sort key (controller)
+# +-- controller-tag for dkvis scene and used for grouping "related"
+# disks
+#
+# and generate a sort(1) key of the form "+1n -2 +2n -3 +3n -4"
+#
+# after sorting this list becomes
+#
+# dks10 10 1 dks10d1
+# dks10 10 1 10 dks10d110
+# dks10 10 7 dks10d7
+# dks14 14 2 dks14d2
+# dks14 14 3 dks14d3
+# dks14 14 3 2 dks14d3l2
+# dks14 14 3 10 dks14d3l10
+#
+# and after grouping based on the common controller-tag and stripping
+# the sort fields this becomes
+#
+# dks10 dks10d1 dks10d110 dks10d7
+# dks14 dks14d2 dks14d3 dks14d3l2 dks14d3l10
+#
+sed >$tmp.ctl <<'End-of-File' \
+ -e '/^#/d' \
+ -e 's/ */ /g' \
+ -e 's/\//\\\//g' \
+ -e 's/<n>/\\([0-9][0-9]*\\)/g' \
+ -e 's/<h>/\\([0-9a-f][0-9a-f]*\\)/g' \
+ -e 's/^/^/'
+# Linux 2.2, no sard, IDE and SCSI
+sd\([a-z]\)+hd\([abcd]\) sd+hd 1
+# Linux IDE, no devfs
+hd\([abcd]\) hd 1
+# Linux IDE, with devfs
+ide/host<n>/bus<n>/target<n>/lun<n> ide\1b\2 1n,2n,3n,4n
+# Linux SCSI, no devfs
+sd\([a-z]\) sd 1
+# Linux XSCSI, dual port HBA, with devfs
+# e.g. xscsi/pci00.01.0-1/target2/lun0/disc (and similar for fabric device)
+xscsi/pci<h>\.<h>\.<n>-<n>/target<n>/lun<n> xscsi\1.\2 1,2,3n,4n,5n,6n
+xscsi/pci<h>\.<h>\.<n>-<n>/node<h>/lun<n> xscsi\1.\2 1,2,3n,4n,5,6n
+# Linux XSCSI, single port HBA, with devfs
+# e.g. xscsi/pci05.01.0/target2/lun0/disc (and similar for fabric device)
+xscsi/pci<h>\.<h>\.<n>/target<n>/lun<n> xscsi\1.\2 1,2,3n,4n,5n
+xscsi/pci<h>\.<h>\.<n>/node<h>/lun<n> xscsi\1.\2 1,2,3n,4,5n
+# Linux SCSI, with devfs
+scsi/host<n>/bus<n>/target<n>/lun<n> scsi\1b\2 1n,2n,3n,4n
+# Linux Mylex RAID, no devfs
+rd/c<n>d<n> dac 1n,2n
+# Linux Mylex RAID, with devfs (old style)
+dac960/c<n>d<n> dac 1n,2n
+# Linux Mylex RAID, with devfs (new style)
+dac960/host<n>/disc<n> dac 1n,2n
+# IRIX Firewire
+# two name formats: first is IRIX before 6.5.11, second is 6.5.11 or later
+/hw/rdisk/1394/\([^/]*\)/lun<n>vol/c<n>p<n> 1394c\3 3n,2n,1,2n
+1394/\([^/]*\)/lun<n>vol/c<n>p<n> 1394c\3 3n,2n,1,2n
+# IRIX SCSI, note l<n> is missing for LUN 0, so pattern is a little different
+# at the end
+dks<n>d<n>l*\([0-9]*\) dks\1 1n,2n,3n
+# IRIX FC
+\(................\)/lun<n>/c<n>p<n> fc\3p\4 3n,4n,1,2n
+# Linux direct attach xscsi
+# e.g. xscsi/pci15.01.0-2/target4/lun0/disc
+xscsi/pci<n>\.\(..\)\.\(.*\)/target<n>/lun<n> xscsi\1b\2 1n,2,3,4n,5n
+# Linux fabric attach xscsi
+# e.g. xscsi/pci04.01.0/node50060946700083e9/port1/lun0/disc
+xscsi/pci<n>\.\(..\)\.\(.*\)/node\(................\)/port<n>/lun<n> xfc\1b\2 1n,2,3,4,5n,6n
+# Linux IDA, with devfs
+ida/disc<n> ida 1n
+# Old style IRIX Jaguar drives
+jag<n>d<n> jag\1 1n,2n
+# Old style IRIX RAID drives
+rad<n>d<n> rad\1 1n,2n
+End-of-File
+
+if $debug
+then
+ echo "Control lines ..."
+ cat $tmp.ctl
+fi
+
+nctl=`wc -l <$tmp.ctl | sed -e 's/ //g'`
+i=1
+
+# loop once per contol line in $tmp.ctl ...
+# - select matching disks
+# - generate the sort keys with the disk names
+# - sort and group by the controller-tag
+# - remove the matching lists from consideration
+#
+while [ $i -le $nctl ]
+do
+ $PCP_AWK_PROG -F' ' <$tmp.ctl '
+NR == '$i' { print "/" $1 "/p" >"'$tmp.sed-in'"
+ print "/" $1 "/!p" >"'$tmp.sed-out'"
+ nfld = split($3,fld,/,/)
+ maxfld = 1
+ for (i = 1; i <= nfld; i++) {
+ if (fld[i] > maxfld) maxfld = fld[i]
+ printf "+%s -%d ",fld[i],fld[i]+1 >"'$tmp.sort-arg'"
+ }
+ print "" >"'$tmp.sort-arg'"
+ printf "%s","s/" $1 "/" $2 >"'$tmp.sed-key'"
+ for (i = 1; i <= maxfld; i++) {
+ printf " \\%d",i >"'$tmp.sed-key'"
+ }
+ print " &/" >"'$tmp.sed-key'"
+ exit
+ }'
+
+ sed -n -f $tmp.sed-in <$tmp.in \
+ | sed -f $tmp.sed-key >$tmp.key
+ sort `cat $tmp.sort-arg` $tmp.key \
+ | $PCP_AWK_PROG >>$tmp.order '
+BEGIN { ctl = "" }
+$1 != ctl { if (NR > 1) print ""
+ ctl = $1
+ ndisk = 0
+ printf "%s",ctl
+ }
+ { ndisk++
+ if ('"$maxndisk"' > 0 && ndisk > '"$maxndisk"') {
+ print ""
+ printf "%s",ctl
+ ndisk = 1
+ }
+ printf " %s",$NF
+ }
+END { if (NR > 0) print "" }'
+ sed -n -f $tmp.sed-out <$tmp.in >$tmp.out
+ mv $tmp.out $tmp.in
+
+ i=`expr $i + 1`
+done
+
+# any leftovers do not match any control pattern
+#
+if [ -s $tmp.in ]
+then
+ echo "$prog: The following disk names do not match a known controller pattern," >$tmp.warn
+ echo "and they will be ungrouped in the scene:" >>$tmp.warn
+ fmt <$tmp.in | sed -e 's/^/ /' >>$tmp.warn
+ _pmview_warning -f $tmp.warn
+ sed -e 's/.*/& &/' <$tmp.in >>$tmp.order
+fi
+
+nctl=`wc -l <$tmp.order | sed -e 's/ //g'`
+
+# shape the base geometry for the scene ... there are groups, each with
+# $maxnctl controller rows, and the groups are arranged in a grid that
+# is $nrow groups deep and $ncol groups across
+#
+if [ $nctl -le $maxnctl ]
+then
+ # less than $maxnctl controller rows, so just one group
+ #
+ ncol=1
+ nrow=1
+else
+ # have some shaping to do ... assume each group is $maxnctl controller
+ # rows deep and as wide as the maximum number of disks per controller
+ # row, then square up the scene and maybe adjust $maxnctl
+ #
+ eval `$PCP_AWK_PROG <$tmp.order '
+START { maxdk = 0 }
+ { if (NF - 1 > maxdk) maxdk = NF - 1 }
+END {
+ if ('"$nctl"' % "'$maxnctl'" == 0)
+ ngrp = '"$nctl"' / "'$maxnctl'"
+ else
+ ngrp = int('"$nctl"' / "'$maxnctl'") + 1
+ # add 2 to maxdk as an estimate of the label length in units of
+ # displayed blocks, i.e. the label is about as long as the width
+ # of 2 blocks
+ ncol = int(0.5 + sqrt(ngrp)*'"$maxnctl"'/(maxdk + 2))
+ if (ncol < 1) ncol = 1
+ if (ngrp % ncol == 0)
+ nrow = ngrp / ncol
+ else
+ nrow = int(ngrp / ncol) + 1
+ # use all of the space in the base layout
+ ngrp = nrow * ncol
+ # make all the groups have the same number of controller rows
+ if ('"$nctl"' % ngrp == 0)
+ maxnctl = '"$nctl"' / ngrp
+ else
+ maxnctl = int('"$nctl"' / ngrp) + 1
+ print "ncol=" ncol " nrow=" nrow " maxnctl=" maxnctl
+ }'`
+fi
+
+#DEBUG echo "nctl=$nctl maxnctl=$maxnctl ncol=$ncol nrow=$nrow"
+
+# heuristic hack for pmlaunch
+#
+if [ "$nctl" -gt 6 ]
+then
+ group="_groupByInst"
+else
+ group="_groupByMetric"
+fi
+
+if [ -z "$titleArg" ]
+then
+ titleArg="$Type Disk Activity ($Thru) for Host $host"
+fi
+
+#
+# pmview 2.0
+#
+
+echo '
+_grid (' >> $tmp.conf
+
+$PCP_AWK_PROG -v io=$Type -v max=$max -v group=$group -v thru="$Thru" <$tmp.order '
+BEGIN { row = 0; col = 0; cnt = 0; type = ""; ctl = ""
+ start_ctl = ""; label = ""; last_label = ""; last_ctl = ""
+ color[0] = "green"
+ color[1] = "blue"
+ color[2] = "red"
+ color[3] = "cyan"
+ color[4] = "violet"
+ color[5] = "yellow"
+ ncol = 5
+ colorlist = ""
+ }
+
+function dumpLabel(start, last)
+{
+ printf(" _baseLabel \"%s Activity for Disks on ", io)
+ if (start != last)
+ printf("Controllers %s to %s", start, last)
+ else
+ printf("Controller %s", start)
+ printf("\\nNormalized to %s %s\"\n", max, thru)
+ printf(" _colorlist (%s )\n", colorlist )
+ colorlist = ""
+}
+
+ { if (cnt % '"$maxnctl"' == 0) {
+ if (cnt > 0) {
+ print " )"
+ dumpLabel(start_ctl, last_ctl)
+ print " )"
+ }
+ printf("\n _bar %d %d north %s (\n", 2*col, 2*row, group)
+ print " _metrics ("
+ start_ctl = $1
+ row++
+ if (row >= '"$nrow"') {
+ col++
+ row = 0
+ }
+ }
+ printf(" disk.dev.'$type'[")
+
+ for (d = 2; d <= NF; d++) {
+ if (d == 2)
+ printf("%s", $d)
+ else
+ printf(",%s", $d)
+ }
+ printf("] %s \"%s\"\n", max, $1)
+ if ($1 != last_ctl) {
+ # new controller, change colors
+ ncol++
+ if (ncol > 5)
+ ncol = 0
+ }
+ colorlist = colorlist " " color[ncol]
+ last_ctl = $1
+ cnt++
+ }
+END { if (cnt > 0) {
+ print " )"
+ dumpLabel(start_ctl, last_ctl)
+ print " )"
+ }
+ print ")"
+ }' >> $tmp.conf
+
+$verbose && cat $tmp.conf
+
+eval $PMVIEW <$tmp.conf $args -title "'$titleArg'" # -xrm "'*iconName:dkvis'"
+
+exit
diff --git a/src/pmview/front-ends/mpvis b/src/pmview/front-ends/mpvis
new file mode 100755
index 0000000..dd7db78
--- /dev/null
+++ b/src/pmview/front-ends/mpvis
@@ -0,0 +1,452 @@
+#!/bin/sh
+# Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+gridspace=120
+cpuargs=
+
+algorithm="k"
+default_layout=true
+force=false
+maxrowlen=16
+verbose=false
+showinst=false
+version=2
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: mpvis [options] [cpuid...]
+
+options:
+ -b use row:column ratio of 1:8 (soft limit)
+ [default, if 64 or more CPUs]
+ -i show CPU numbers
+ -r rowlen maximum number of CPUs per row (soft limit)
+ [default 16, if less than 64 CPUs]
+ -R rowlen force this number of CPUs per row
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+
+ _pmview_usage >>$tmp.msg
+ echo >>$tmp.msg
+ echo 'Default title is: CPU Utilization for Host' >>$tmp.msg
+ _pmview_info -f $tmp.msg
+}
+
+# generate one row of the scene
+#
+_do_row()
+{
+if [ "$1" = "$2" ]
+then
+ msg="$titleArg\n$1 only"
+else
+ msg="$titleArg\n$1 to $2"
+fi
+
+cat <<End-of-File >>$tmp.conf
+ _grid 0 $3 show (
+ _baseLabel "$msg"
+ _bar $group (
+ _metrics (
+End-of-File
+$have_idle && echo " kernel.percpu.cpu.idle[$rowlist] $max_util \"idle\"" >>$tmp.conf
+$have_wait && echo " kernel.percpu.cpu.wait.total[$rowlist] $max_util \"wait\"" >>$tmp.conf
+$have_intr && echo " kernel.percpu.cpu.intr[$rowlist] $max_util \"intr\"" >>$tmp.conf
+$have_nice && echo " kernel.percpu.cpu.nice[$rowlist] $max_util \"nice\"" >>$tmp.conf
+$have_sys && echo " kernel.percpu.cpu.sys[$rowlist] $max_util \"sys\"" >>$tmp.conf
+$have_user && echo " kernel.percpu.cpu.user[$rowlist] $max_util \"user\"" >>$tmp.conf
+cat <<End-of-File >>$tmp.conf
+ )
+End-of-File
+$showinst && echo " _instlabels away ( $labels )" >>$tmp.conf
+cat <<End-of-File >>$tmp.conf
+ _colorlist cpu
+ _baseLabel "$msg"
+ )
+ )
+End-of-File
+}
+
+# build WM_COMMAND X(1) property for restart after login/logout
+#
+echo -n "pmview Version 2.1 \"mpvis\"" >$tmp.conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.conf
+done
+
+_pmview_args "$@"
+
+if [ "X$otherArgs" != X ]
+then
+ while getopts "bir:R:v:V?" c $otherArgs
+ do
+ case $c
+ in
+ b)
+ algorithm="b"
+ default_layout=false
+ ;;
+ i)
+ showinst=true
+ gridspace=60
+ ;;
+ r)
+ _pmview_unsigned $c
+ maxrowlen=$OPTARG
+ default_layout=false
+ ;;
+ R)
+ _pmview_unsigned $c
+ maxrowlen=$OPTARG
+ default_layout=false
+ force=true
+ ;;
+
+ v)
+ version=$OPTARG
+ if [ $version = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ version=2
+ elif [ $version != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+
+ V)
+ verbose=true
+ ;;
+ ?)
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+
+ [ $# -gt 0 ] && cpuargs=$@
+fi
+
+[ -z "$titleArg" ] && titleArg="SGI PCP : CPU Utilization for Host $host"
+
+_pmview_cache_fetch -I kernel.percpu.cpu.user
+_pmview_cache_fetch -v kernel.percpu.cpu.idle \
+ kernel.percpu.cpu.wait.total \
+ kernel.percpu.cpu.intr \
+ kernel.percpu.cpu.nice \
+ kernel.percpu.cpu.sys \
+ kernel.percpu.cpu.user
+
+
+# Check that we can get the metrics
+#
+if _pmview_fetch_indom kernel.percpu.cpu.user
+then
+ if [ ! -s "$tmp.pmview_result" -o "$number" -lt 1 ]
+ then
+ _pmview_fetch_fail "get CPU inventory"
+ fi
+else
+ _pmview_fetch_fail "get CPU inventory"
+ # NOTREACHED
+fi
+
+if [ ! -z "$cpuargs" ]
+then
+ # restrict based on command line args
+ #
+ rm -f $tmp.tmp $tmp.msg
+ ncpu=0
+ for cpu in $cpuargs
+ do
+ if echo "$cpu" | grep '[.[^]' >/dev/null
+ then
+ # assume egrep(1) regular expression
+ #
+ if egrep "$cpu" $tmp.pmview_result >>$tmp.tmp
+ then
+ # found some matches
+ #
+ :
+ else
+ echo "$prog: pattern \"$cpu\" does not match any CPUs ..." >$tmp.msg
+ fi
+ elif grep "^$cpu\$" $tmp.pmview_result >/dev/null
+ then
+ echo $cpu >>$tmp.tmp
+ else
+ echo "$prog: CPU \"$cpu\" not in the CPU inventory" >$tmp.msg
+ fi
+ done
+ if [ -s $tmp.msg ]
+ then
+ echo "CPUs on host \"$host\" are:" >> $tmp.msg
+ tr < $tmp.pmview_result '\012' ' ' | fmt | sed -e "s/^/ /" >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+ fi
+ sort $tmp.tmp | uniq > $tmp.pmview_result
+ ncpu=`wc -l $tmp.pmview_result | $PCP_AWK_PROG '{print $1}'`
+else
+ ncpu=$number
+fi
+
+if [ "$ncpu" -ge 64 -a "$default_layout" = true ]
+then
+ # >= 64p, no -b, -r or -R options ... make -b the default
+ #
+ algorithm="b"
+fi
+
+# sort list
+#
+if grep cpu: $tmp.pmview_result >/dev/null
+then
+ # Origin series name style
+ sed -e 's/:/./' < $tmp.pmview_result \
+ | sort -t. +1n -2 +2n -3 +3 -4 \
+ | sed -e 's/\./:/' \
+ > $tmp.cpulist
+else
+ # CPU names for older systems
+ sed -e 's/cpu/cpu./' < $tmp.pmview_result \
+ | sort -t. +1n -2 \
+ | sed -e 's/\.//' \
+ > $tmp.cpulist
+fi
+
+scale=''
+have_idle=false
+if _pmview_fetch_values kernel.percpu.cpu.idle
+then
+ have_idle=true
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.idle| sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+have_wait=false
+if _pmview_fetch_values kernel.percpu.cpu.wait.total
+then
+ have_wait=true
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.wait.total | sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+have_intr=false
+if _pmview_fetch_values kernel.percpu.cpu.intr
+then
+# linux 2.6 has wait and intr, but 2.4 does not
+ have_intr=`$PCP_AWK_PROG -v found=false '
+ $1 > 0 { found="true" }
+ END { print found }' $tmp.pmview_result`
+ have_wait=$have_intr
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.intr | sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+have_nice=false
+if _pmview_fetch_values kernel.percpu.cpu.nice
+then
+ have_nice=true
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.nice | sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+have_sys=false
+if _pmview_fetch_values kernel.percpu.cpu.sys
+then
+ have_sys=true
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.sys | sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+have_user=false
+if _pmview_fetch_values kernel.percpu.cpu.user
+then
+ have_user=true
+ [ -z "$scale" ] && scale=`pminfo $namespace $msource -d kernel.percpu.cpu.user | sed -n '/Semantics:/s/.*Units: //p'`
+fi
+
+case $scale
+in
+ microsec)
+ max_util=1000000
+ ;;
+ millisec)
+ max_util=1000
+ ;;
+ *)
+ _pmview_warning "$prog: cannot determine CPU time units, assuming milliseconds"
+ max_util=1000
+ ;;
+esac
+
+# shape the base geometry for the scene
+#
+if [ $ncpu -le "$maxrowlen" ]
+then
+ nrows=1
+ ncols=$ncpu
+elif $force
+then
+ nrows=`echo $ncpu $maxrowlen | $PCP_AWK_PROG ' \
+ { x = $1 / $2; y = $1 % $2; if (y > 0) ++x; printf "%d\n", x; }'`
+ ncols=$maxrowlen
+else
+ case $algorithm in
+ a) # this algorithm doesn't work at the moment
+ # (the exit condition is not robust enough)
+ nrows=1
+ ncols=1
+ bound=1
+ num=0
+ while [ $num -gt $ncols -o $num -lt $bound ]
+ do
+ nrows=`expr $nrows \* 2`
+ ncols=`expr $ncpu / $nrows`
+ bound=`expr $ncols / 2`
+ num=`expr $nrows \* 4`
+ done
+ ncols=`echo $ncpu $nrows | $PCP_AWK_PROG ' { x = $1 / $2; y = $1 % $2; \
+ if (y > 0) ++x; printf "%d\n", x }'`
+ ;;
+ b)
+ # use a ratio for rows:columns of 1:8
+ #
+ nrows=`echo $ncpu | $PCP_AWK_PROG ' { x = sqrt ($1 / 8.0);
+ y = int (x); if (y < x) ++y; print y }'`
+ ncols=`expr $ncpu + $nrows - 1`
+ ncols=`expr $ncols / $nrows`
+ ;;
+ k)
+ nrows=`expr $ncpu + $maxrowlen - 1`
+ nrows=`expr $nrows / $maxrowlen`
+ ncols=`expr $ncpu + $nrows - 1`
+ ncols=`expr $ncols / $nrows`
+ esac
+fi
+
+if [ "$ncols" -gt 6 ]
+then
+ group="_groupByMetric"
+else
+ group="_groupByInst"
+fi
+
+cat <<End-of-File >>$tmp.conf
+
+#
+# mpvis
+#
+# ncpus = $ncpu
+# nrows = $nrows
+# ncols = $ncols
+#
+# List:
+End-of-File
+
+col=0
+rowlist=""
+cat $tmp.cpulist | while read cpu
+do
+ if [ $col -eq 0 ]
+ then
+ echo -n "$cpu " > $tmp.rowlist
+ else
+ echo -n "$cpu " >> $tmp.rowlist
+ fi
+ col=`expr $col + 1`
+ if [ "$col" -eq $ncols ]
+ then
+ echo "# "`cat $tmp.rowlist` >>$tmp.conf
+ col=0
+ rm -f $tmp.rowlist
+ fi
+done
+
+[ -s $tmp.rowlist ] && echo "# "`cat $tmp.rowlist` >>$tmp.conf
+
+echo "_gridSpace $gridspace" >>$tmp.conf
+echo >>$tmp.conf
+
+colorlist="_colorlist cpu ("
+$have_idle && colorlist="$colorlist green2"
+$have_wait && colorlist="$colorlist cyan2"
+$have_intr && colorlist="$colorlist yellow2"
+$have_nice && colorlist="$colorlist rgbi:0.6/1.0/0.7"
+$have_sys && colorlist="$colorlist red2"
+$have_user && colorlist="$colorlist blue2"
+colorlist="$colorlist )"
+
+echo "$colorlist" >>$tmp.conf
+echo "_grid 0 0 _hide ( # outer grid" >>$tmp.conf
+
+# build rows from front-to-back of scene
+# fill rows with CPUs from left-to-right
+#
+y=`expr $nrows \* 2 - 2`
+col=0
+rowlist=""
+labels=""
+cat $tmp.cpulist | while read cpu
+do
+ if [ $col -eq 0 ]
+ then
+ rowlist=$cpu
+ start=$cpu
+ else
+ rowlist="$rowlist,$cpu"
+ fi
+ $showinst && labels="$labels \"`echo $cpu | sed -e 's/cpu:*//'`\""
+ col=`expr $col + 1`
+ echo "$start $cpu $y $rowlist $labels" > $tmp.rowlist
+ if [ $col -eq $ncols ]
+ then
+ _do_row $start $cpu $y
+ col=0
+ echo -n "" > $tmp.rowlist
+ labels=""
+ y=`expr $y - 2`
+ fi
+done
+
+if [ -s $tmp.rowlist ]
+then
+ read start cpu y rowlist labels < $tmp.rowlist
+ # cat $tmp.rowlist
+ _do_row $start $cpu $y
+fi
+
+echo ")" >>$tmp.conf
+
+if [ $nrows -eq 1 ]
+then
+ # remove unnecessary _grid for a single row
+ #
+ sed -e '/^ _grid/d' -e '/^ )/d' <$tmp.conf >$tmp.tmp
+ mv $tmp.tmp $tmp.conf
+fi
+
+$verbose && cat $tmp.conf
+
+eval $PMVIEW <$tmp.conf $args -title "'$titleArg'" -xrm "'*iconName:mpvis'"
+
+exit
diff --git a/src/pmview/front-ends/nfsvis b/src/pmview/front-ends/nfsvis
new file mode 100755
index 0000000..26b87ae
--- /dev/null
+++ b/src/pmview/front-ends/nfsvis
@@ -0,0 +1,244 @@
+#! /bin/sh
+# Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options]
+
+options:
+ -c version Monitor NFS version (integer) client metrics [default 2]
+ -m maxrate maximum request rate expected (integer) [default 120]
+ -s version Monitor NFS version (integer) server metrics [default 2]
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+
+ _pmview_usage >>$tmp.msg
+ echo >>$tmp.msg
+ echo 'Default title is: NFS Version 2 Request Traffic for host' >>$tmp.msg
+ _pmview_info -f $tmp.msg
+}
+
+max=120
+client=2
+server=2
+type=total
+Type=Total
+verbose=false
+version=2
+
+# build WM_COMMAND X(1) property for restart after login/logout
+#
+echo -n "pmview Version 2.1 \"nfsvis\"" >$tmp.conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.conf
+done
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?c:m:s:v:V" c $otherArgs
+ do
+ case $c
+ in
+ c)
+ client=$OPTARG
+ if [ "$client" != 2 -a "$client" != 3 ]
+ then
+ _pmview_error "$prog: only NFS 2 and NFS 3 client metrics supported"
+ # NOTREACHED
+ fi
+ ;;
+ m)
+ _pmview_unsigned $c
+ max=$OPTARG
+ ;;
+ s)
+ server=$OPTARG
+ if [ "$server" != 2 -a "$server" != 3 ]
+ then
+ _pmview_error "$prog: only NFS 2 and NFS 3 server metrics supported"
+ # NOTREACHED
+ fi
+ ;;
+
+ v)
+ version=$OPTARG
+ if [ $version = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ version=2
+ elif [ $version != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+
+ V)
+ verbose=true
+ ;;
+ ?)
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+ set - $otherArgs
+ shift `expr $OPTIND - 1`
+ if [ $# -gt 0 ]
+ then
+ _usage
+ exit 1
+ fi
+fi
+
+if [ "$client" = "2" ]
+then
+ # NFS V2 client stats
+ #
+ if _pmview_fetch_indom nfs.client.reqs
+ then
+ :
+ else
+ _pmview_fetch_fail "get NFS Version 2 client metrics"
+ # NOTREACHED
+ fi
+else
+ # NFS V3 client stats
+ #
+ if _pmview_fetch_indom nfs3.client.reqs
+ then
+ :
+ else
+ _pmview_fetch_fail "get NFS Version 3 client metrics"
+ # NOTREACHED
+ fi
+fi
+
+# handle fsstat alias statfs
+#
+c_statfs=statfs
+grep fsstat $tmp.pmview_result >/dev/null && c_statfs=fsstat
+
+if [ "$server" = "2" ]
+then
+ # NFS V2 server stats
+ #
+ if _pmview_fetch_indom nfs.server.reqs
+ then
+ :
+ else
+ _pmview_fetch_fail "get NFS Version 2 server metrics"
+ # NOTREACHED
+ fi
+else
+ # NFS V3 server stats
+ #
+ if _pmview_fetch_indom nfs3.server.reqs
+ then
+ :
+ else
+ _pmview_fetch_fail "get NFS Verion 3 server metrics"
+ # NOTREACHED
+ fi
+fi
+
+# handle fsstat alias statfs
+#
+s_statfs=statfs
+grep fsstat $tmp.pmview_result >/dev/null && s_statfs=fsstat
+
+if [ -z "$titleArg" ]
+then
+ titleArg="SGI PCP : NFS Client V$client & Server V$server Request Traffic for host $host"
+fi
+
+
+cat << End-of-File >> $tmp.conf
+
+#
+# nfsvis
+#
+_colorlist colors (red1 green1 blue1)
+_grid hide (
+ _label 2 0 _down _large "Client"
+ _bar 0 0 _east _groupByMetric (
+ _metrics (
+End-of-File
+
+if [ "$client" = "2" ]
+then
+ cat << End-of-File >> $tmp.conf
+ nfs.client.reqs[create,remove,rename,link,symlink,mkdir,rmdir] $max "dir"
+ nfs.client.reqs[getattr,setattr,lookup,readdir,$c_statfs,root] $max "attr"
+ nfs.client.reqs[readlink,read,write,wrcache] $max "data"
+ )
+ _baseLabel "Requests by NFS2 Client\nNormalized to $max requests / second"
+End-of-File
+else
+ cat << End-of-File >> $tmp.conf
+ nfs3.client.reqs[create,remove,rename,link,symlink,mkdir,rmdir,mknod] $max "dir"
+ nfs3.client.reqs[getattr,setattr,lookup,readdir,$c_statfs,access,readdir+,fsinfo,pathconf] $max "attr"
+ nfs3.client.reqs[readlink,read,write,commit] $max "data"
+ )
+ _baseLabel "Requests by NFS3 Client\nNormalized to $max requests / second"
+End-of-File
+fi
+cat << End-of-File >> $tmp.conf
+ _colorlist colors
+ )
+ _label 2 2 _down _large "Server"
+ _bar 0 2 _east (
+ _metrics (
+End-of-File
+if [ "$server" = "2" ]
+then
+ cat << End-of-File >> $tmp.conf
+ nfs.server.reqs[create,remove,rename,link,symlink,mkdir,rmdir] $max "dir"
+ nfs.server.reqs[getattr,setattr,lookup,readdir,$s_statfs,root] $max "attr"
+ nfs.server.reqs[readlink,read,write,wrcache] $max "data"
+ )
+ _baseLabel "Requests to NFS2 Server\nNormalized to $max requests / second"
+End-of-File
+else
+ cat << End-of-File >> $tmp.conf
+ nfs3.server.reqs[create,remove,rename,link,symlink,mkdir,rmdir,mknod] $max "dir"
+ nfs3.server.reqs[getattr,setattr,lookup,readdir,$s_statfs,access,readdir+,fsinfo,pathconf] $max "attr"
+ nfs3.server.reqs[readlink,read,write,commit] $max "data"
+ )
+ _baseLabel "Requests to NFS3 Server\nNormalized to $max requests / second"
+End-of-File
+fi
+
+cat << End-of-File >> $tmp.conf
+ _colorlist colors
+ )
+)
+End-of-File
+
+$verbose && cat $tmp.conf
+
+eval $PMVIEW <$tmp.conf $args -title "'$titleArg'" -xrm "'*iconName:nfsvis'"
+
+exit
diff --git a/src/pmview/front-ends/osvis b/src/pmview/front-ends/osvis
new file mode 100644
index 0000000..a704446
--- /dev/null
+++ b/src/pmview/front-ends/osvis
@@ -0,0 +1,651 @@
+#! /bin/sh
+# Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+#
+# scaling parameters
+#
+
+# maximum network packets per second
+maxpack=750
+
+# maximum error packets per second
+maxerr=`expr $maxpack / 100`
+
+# maximum network bytes per second
+maxbyte=65536
+
+# maximum disk io rate (I/O operations per second)
+maxio=100
+
+# milliseconds per CPU
+maxcpu=1000
+
+# maximum disk activity
+maxdisk=30
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options] [interface [interface ...]]
+
+options:
+ -b bytes maximum expected network throughput [default 65536 bytes]
+ -d activity maximum expected disk activity as a percentage [default 30]
+ -i ops maximum expected I/O operations per seconds [default 100]
+ -m packets maximum expected packets sent or received per sec [default 750]
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+ _pmview_usage >>$tmp.msg
+ echo >> $tmp.msg
+ echo 'Default title is: High-Level Activity for Host' >> $tmp.msg
+
+ echo >>$tmp.msg '
+ By default all network interfaces are shown, with a maximum packet rate
+ of '$maxpack' packets per second and error rate of '$maxerr' packets per second.
+
+ If given, the [interface [interface ...]] regular expressions restrict
+ the network statistics displayed to matching network interface names only.'
+
+ _pmview_info -f $tmp.msg
+}
+
+argInterfaces=""
+verbose=false
+interfaces=""
+
+# build WM_COMMAND X(1) property for restart after login/logout
+#
+echo -n "pmview Version 2.1 \"osvis\"" >$tmp.conf
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.conf
+done
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?b:d:i:m:r:v:V" c $otherArgs
+ do
+ case $c
+ in
+
+ b)
+ _pmview_unsigned $c
+ maxbyte=$OPTARG
+ ;;
+
+ d)
+ _pmview_unsigned $c
+ maxdisk=$OPTARG
+ ;;
+
+ i)
+ _pmview_unsigned $c
+ maxio=$OPTARG
+ ;;
+
+ m)
+ _pmview_unsigned $c
+ maxpack=$OPTARG
+ ;;
+
+ V)
+ verbose=true
+ ;;
+
+ '?')
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+
+ set -- $otherArgs
+ shift `expr $OPTIND - 1`
+ if [ $# -gt 0 ]
+ then
+ argInterfaces="$*"
+ fi
+fi
+
+cat << end-of-file >> $tmp.conf
+
+#
+# osvis
+#
+end-of-file
+
+# maximum req error rate (default: 1% of packet rate)
+maxerr=`expr $maxpack / 100`
+[ "$maxerr" -eq 0 ] && maxerr=1
+
+_pmview_cache_fetch -v hinv.ncpu \
+ hinv.ndisk \
+ disk.all.avg_disk.active \
+ mem.util \
+ hinv.physmem \
+ mem.physmem \
+ kernel.all.cpu.wait.total \
+ kernel.all.cpu.intr \
+ kernel.all.cpu.nice \
+ kernel.all.cpu.sys \
+ kernel.all.cpu.user
+
+_pmview_cache_fetch -I network.interface.in.bytes \
+ disk.ctl.avg_disk.active
+
+if _pmview_fetch_values hinv.ncpu
+then
+ ncpu=`cat $tmp.pmview_result`
+ maxcpu=`expr $maxcpu \* $ncpu`
+ maxload=`expr $ncpu \* 2`
+else
+ _pmview_fetch_fail "get the number of CPUs"
+fi
+
+if [ $ncpu -eq 1 ]
+then
+ cpudesc="1 CPU"
+else
+ cpudesc="$ncpu CPUs"
+fi
+
+$verbose && echo "# $cpudesc detected" >> $tmp.conf
+
+if _pmview_fetch_indom network.interface.in.bytes
+then
+ ninterfaces=$number
+else
+ _pmview_fetch_warn "get the number of network interfaces"
+ ninterfaces=0
+fi
+
+[ $ninterfaces -gt 0 ] && sed < $tmp.pmview_result -e 's/lo[0-9]*//g' -e 's/sl[0-9]*//g' -e 's/ppp[0-9]*//g' > $tmp.list
+
+[ ! -s $tmp.list ] && ninterfaces=0
+
+if $verbose
+then
+ echo "# Available Network Interfaces: "`cat $tmp.list | tr '\012' ' '` >> $tmp.conf
+fi
+
+if [ $ninterfaces -gt 0 ]
+then
+ if [ -z "$argInterfaces" ]
+ then
+ cp $tmp.list $tmp.chosen
+ else
+ touch $tmp.chosen
+ for i in $argInterfaces
+ do
+ egrep $i $tmp.list >> $tmp.chosen
+ done
+ fi
+
+ interfaces_sp=`cat $tmp.chosen | sort | uniq`
+ interfaces=`echo $interfaces_sp | sed -e 's/ /,/g'`
+ ninterfaces=`echo $interfaces | wc -w`
+
+ if $verbose
+ then
+ echo "# Network interfaces Matching \"$argInterfaces\": $interfaces" >> $tmp.conf
+ fi
+
+ if [ $ninterfaces -eq 0 ]
+ then
+ echo "$prog: no matching network interfaces for \"$argInterfaces\"" > $tmp.msg
+ echo "Available network interfaces on host \"$host\" are: " >> $tmp.msg
+ tr < $tmp.list '\012' ' ' | fmt | sed -e "s/^/ /" >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ #NOTREACHED
+ fi
+fi
+
+if _pmview_fetch_values hinv.ndisk
+then
+ ndisk=`cat $tmp.pmview_result`
+ maxdiskscale=`expr $maxdisk \* 10`
+else
+ ndisk=0
+ maxdiskscale=0
+fi
+
+if [ $ndisk -eq 1 ]
+then
+ diskdesc="1 Disk"
+else
+ diskdesc="$ndisk Disks"
+fi
+
+$verbose && echo "# $diskdesc detected" >> $tmp.conf
+
+if [ "$ndisk" -gt 0 ]
+then
+
+ if _pmview_fetch_values disk.all.avg_disk.active
+ then
+ allAvg=true
+ else
+ allAvg=false
+ fi
+
+ if _pmview_fetch_indom disk.ctl.avg_disk.active
+ then
+ nctrl=$number
+ cp $tmp.pmview_result $tmp.ctrls
+ else
+ nctrl=0
+ fi
+
+ if [ $nctrl -eq 1 ]
+ then
+ ctrldesc="1 Disk Controller"
+ else
+ ctrldesc="$nctrl Disk Controllers"
+ fi
+
+ if [ "$nctrl" -gt 0 ]
+ then
+
+ collen=`expr $ninterfaces + 3`
+ collensq=`expr $collen \* $collen`
+
+ if [ $nctrl -le $collen ]
+ then
+ ctrlcols=$nctrl
+ elif [ $nctrl -le $collensq ]
+ then
+ ctrlcols=$collen
+ else
+ ctrlcols=`echo $nctrl \
+ | $PCP_AWK_PROG '{x = sqrt($1); y = int(x); if (y < x) y++; print y }'`
+ fi
+ fi
+
+ if $verbose
+ then
+ echo "# $ctrldesc detected: "`tr < $tmp.ctrls ' ' '\012'` >> $tmp.conf
+ echo "#" >> $tmp.conf
+ fi
+fi
+
+linuxutilmem=false
+utilmem=false
+if _pmview_fetch_values mem.util.used
+then
+ linuxutilmem=true
+ if $verbose
+ then
+ echo "# Linux memory utilization metrics supported" >> $tmp.conf
+ echo "#" >> $tmp.conf
+ fi
+else
+ linuxutilmem=false
+ if _pmview_fetch_values mem.util
+ then
+ utilmem=true
+ if $verbose
+ then
+ echo "# Memory utilization metrics supported" >> $tmp.conf
+ echo "#" >> $tmp.conf
+ fi
+ else
+ utilmem=false
+ if $verbose
+ then
+ _pmview_warning "$prog: Memory utilization metrics not supported, showing free memory only"
+ echo "# Memory utilization metrics not supported, showing free memory only" >> $tmp.conf
+ echo "#" >> $tmp.conf
+ fi
+ fi
+fi
+
+# Use mem.physmem if available, otherwise hinv.physmem will do.
+if _pmview_fetch_values mem.physmem
+then
+ realmem=`cat $tmp.pmview_result`
+elif _pmview_fetch_values hinv.physmem
+then
+ realmem=`cat $tmp.pmview_result`
+ realmem=`expr $realmem \* 1024`
+else
+ realmem=0
+
+ if $utilmem
+ then
+ if $verbose
+ then
+ echo "# Unable to determine total real memory" >> $tmp.conf
+ echo "# Showing memory utilisation with free memory" >> $tmp.conf
+ fi
+ else
+ _pmview_warning "Unable to determine size of real memory on host \"$host\""
+ fi
+fi
+
+have_wait=false
+if _pmview_fetch_values kernel.all.cpu.wait.total
+then
+ have_wait=true
+fi
+
+have_intr=false
+if _pmview_fetch_values kernel.all.cpu.intr
+then
+ have_intr=true
+fi
+
+have_nice=false
+if _pmview_fetch_values kernel.all.cpu.nice
+then
+ have_nice=true
+# linux hack !!
+# these metrics are not actually supported in linux
+ have_intr=false
+ have_wait=false
+fi
+
+have_sys=false
+if _pmview_fetch_values kernel.all.cpu.sys
+then
+ have_sys=true
+fi
+
+have_user=false
+if _pmview_fetch_values kernel.all.cpu.user
+then
+ have_user=true
+fi
+
+cpucolors="("
+$have_user && cpucolors="$cpucolors blue2"
+$have_sys && cpucolors="$cpucolors red2"
+$have_nice && cpucolors="$cpucolors rgbi:0.6/1.0/0.7"
+$have_intr && cpucolors="$cpucolors yellow2"
+$have_wait && cpucolors="$cpucolors cyan2"
+cpucolors="$cpucolors )"
+
+if [ -z "$titleArg" ]
+then
+ titleArg="SGI PCP : High-Level Activity for Host $host"
+fi
+
+cat << end-of-file >> $tmp.conf
+
+_colorlist cpu_colors $cpucolors
+_colorlist disk_colors ( violet yellow )
+_colorlist ctrl_colors ( green2 )
+_colorlist network_colors ( green2 blue2 red2 )
+
+_colorlist memory_colors (
+ rgbi:1.0/1.0/0.0
+ rgbi:0.0/1.0/1.0
+ rgbi:1.0/0.0/0.0
+ rgbi:1.0/0.0/1.0
+ rgbi:0.0/0.0/1.0
+ rgbi:0.0/1.0/0.0
+)
+
+_grid hide (
+
+#
+# System level stuff
+#
+end-of-file
+
+if [ "$ndisk" -gt 0 ]
+then
+ cat << end-of-file >> $tmp.conf
+
+ _label 0 1 _down _large "Disk"
+
+ _stack 1 1 _west _cylinder (
+ _metrics (
+ disk.all.write $maxio
+ disk.all.read $maxio
+ )
+ _colorlist disk_colors
+ _baseLabel "Disk Operations\nNormalized to $maxio I/Os per second"
+ )
+end-of-file
+
+ if $allAvg
+ then
+ cat << end-of-file >> $tmp.conf
+
+ _bar 2 1 _west _cylinder (
+ _metrics (
+ disk.all.avg_disk.active $maxdiskscale
+ )
+ _colorlist ctrl_colors
+ _baseLabel "Disk Activity\nNormalized to ${maxdisk}% for $ndisk disks"
+ )
+end-of-file
+ fi
+
+ if [ "$nctrl" -gt 0 ]
+ then
+ cat << end-of-file >> $tmp.conf
+
+ _label 4 0 _west "Disk Controllers"
+
+ _bar 4 1 _west _cylinder (
+ _metrics (
+end-of-file
+
+ cat $tmp.ctrls | tr '\012' ' ' \
+ | $PCP_AWK_PROG -v cols=$ctrlcols -v scale=$maxdiskscale >> $tmp.conf '
+BEGIN { str = ""; j = 0 }
+ { for (i = 1; i <= NF; i++) {
+ if (str == "")
+ str = $i;
+ else
+ str = str "," $i
+ if (j == cols) {
+ printf(" disk.ctl.avg_disk.active[%s] %d\n", str, scale);
+ str = "";
+ j = 0;
+ }
+ else
+ j++;
+ }
+ }
+END { if (str != "")
+ printf(" disk.ctl.avg_disk.active[%s] %d\n", str, scale);
+ }'
+
+ cat << end-of-file >> $tmp.conf
+ )
+ _colorlist ctrl_colors
+ _baseLabel "Disk Controller Activity\nNormalized to ${maxdisk}% for the disks on each controller"
+ )
+end-of-file
+
+ fi
+fi
+
+cat << end-of-file >> $tmp.conf
+
+ _label 0 3 _west _down _large "Load"
+
+ _bar 1 3 2 1 _west (
+ _metrics (
+ kernel.all.load[15] $maxload
+ kernel.all.load[5] $maxload
+ kernel.all.load[1] $maxload
+ )
+ _metriclabels _away ( "15" "5" "1" )
+ _colorlist ( blue2 blue2 blue2 )
+ _baseLabel "Average System Load over the last 1, 5, and 15 minutes\nNormalized to $maxload"
+ )
+
+ _label 0 5 _west _down _large "Mem"
+
+end-of-file
+
+if [ $realmem -ne 0 ]
+then
+ if $utilmem
+ then
+ cat << end-of-file >> $tmp.conf
+ _stack 1 5 _west (
+ _metrics (
+ mem.util.kernel $realmem
+ mem.util.fs_ctl $realmem
+ mem.util.fs_dirty $realmem
+ mem.util.fs_clean $realmem
+ mem.util.user $realmem
+ )
+ _colorlist memory_colors
+ _baseLabel "Physical Memory Utilization\nNormalized to $realmem Kbytes"
+ )
+end-of-file
+ elif $linuxutilmem
+ then
+ cat << end-of-file >> $tmp.conf
+ _stack 1 5 _west (
+ _metrics (
+ mem.util.shared $realmem
+ mem.util.cached $realmem
+ mem.util.bufmem $realmem
+ mem.util.other $realmem
+ mem.util.free $realmem
+ )
+ _colorlist memory_colors
+ _baseLabel "Physical Memory Utilization\nNormalized to `expr $realmem / 1024` Kbytes"
+ )
+end-of-file
+ else
+ cat << end-of-file >> $tmp.conf
+ _stack 1 5 _west _fillmod (
+ _metrics (
+ mem.freemem $realmem
+ )
+ _colorlist ( blue2 )
+ _baseLabel "Unallocated Physical Memory"
+ _stackLabel "Used Physical Memory\nNormalized to $realmem Kbytes"
+ )
+end-of-file
+fi
+elif $utilmem
+then
+ cat << end-of-file >> $tmp.conf
+ _stack 1 5 _west _utilmod (
+ _metrics (
+ mem.util.kernel 0
+ mem.util.fs_ctl 0
+ mem.util.fs_dirty 0
+ mem.util.fs_clean 0
+ mem.util.user 0
+ mem.util.free 0
+ )
+ _colorlist memory_colors
+ _baseLabel "Physical Memory Utilization"
+ )
+end-of-file
+elif $linuxutilmem
+then
+ cat << end-of-file >> $tmp.conf
+ _stack 1 5 _west _utilmod (
+ _metrics (
+ mem.util.shared 0
+ mem.util.cached 0
+ mem.util.bufmem 0
+ mem.util.other 0
+ mem.util.free 0
+ )
+ _colorlist memory_colors
+ _baseLabel "Physical Memory Utilization"
+ )
+end-of-file
+fi
+
+cat << end-of-file >> $tmp.conf
+
+ _label 0 7 _west _down _large "CPU"
+
+ _stack 1 7 _west (
+ _metrics (
+end-of-file
+$have_user && echo " kernel.all.cpu.user $maxcpu" >> $tmp.conf
+$have_sys && echo " kernel.all.cpu.sys $maxcpu" >> $tmp.conf
+$have_nice && echo " kernel.all.cpu.nice $maxcpu" >> $tmp.conf
+$have_intr && echo " kernel.all.cpu.intr $maxcpu" >> $tmp.conf
+$have_wait && echo " kernel.all.cpu.wait.total $maxcpu" >> $tmp.conf
+cat << end-of-file >> $tmp.conf
+ )
+ _colorlist cpu_colors
+ _baseLabel "CPU Utilization\nSummed over $cpudesc"
+ )
+end-of-file
+
+if [ "$ninterfaces" -gt 0 ]
+then
+ cat << end-of-file >> $tmp.conf
+
+#
+# Network Stuff and Alarms
+#
+
+ _marginWidth 1
+
+ _grid 4 2 1 7 _nw (
+
+ _marginWidth 8
+
+ _label 0 0 _sw "Network Input"
+
+ _bar 0 1 _nw _groupByMetric (
+ _metrics (
+ network.interface.in.bytes[$interfaces] $maxbyte
+ network.interface.in.packets[$interfaces] $maxpack
+ network.interface.in.errors[$interfaces] $maxerr
+ )
+ _colorlist network_colors
+ _metricLabels _away ( "Bytes" "Packets" "Errors" )
+ _baseLabel "Input on all Network Interfaces\nNormalized to $maxbyte bytes per second and $maxpack packets per second"
+ )
+
+ _label 0 2 _sw "Network Output"
+
+ _bar 0 3 _nw (
+ _metrics (
+ network.interface.out.bytes[$interfaces] $maxbyte
+ network.interface.out.packets[$interfaces] $maxpack
+ network.interface.out.errors[$interfaces] $maxerr
+ )
+ _instlabels _away ( $interfaces_sp )
+ _metricLabels _away ( "Bytes" "Packets" "Errors" )
+ _colorlist network_colors
+ _baseLabel "Output on all Network Interfaces\nNormalized to $maxbyte bytes per second and $maxpack packets per second"
+ )
+ )
+end-of-file
+fi
+
+echo ")" >> $tmp.conf
+
+$verbose && cat $tmp.conf
+
+eval $PMVIEW <$tmp.conf $args -title "'$titleArg'" -xrm "'*iconName:osvis'" -R $PCP_VAR_DIR/config/pmlogger/config.osvis
+
+exit
diff --git a/src/pmview/front-ends/pmview-args b/src/pmview/front-ends/pmview-args
new file mode 100644
index 0000000..31a7008
--- /dev/null
+++ b/src/pmview/front-ends/pmview-args
@@ -0,0 +1,625 @@
+# Copyright (c) 1995-2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+prog=`basename $0`
+
+PMVIEW=pmview
+PMPROBE=pmprobe
+CONFIRM="$PCP_XCONFIRM_PROG"
+ECHONL="echo -n"
+_multiple_sources=false
+
+#
+# Standard usage and command-line argument parsing for pmview front ends.
+# This file should be included by pmview front end scripts to present a
+# consistent interface. See pmview(1), dkvis(1), mpvis(1) and nfsvis(1)
+# for more information on their respective interfaces.
+#
+
+#
+# The front end scripts should call _pmview_usage after their own usage
+# information in a subroutine called _usage. The _usage subroutine may be
+# called by either _pmview_note or _pmview_args.
+#
+_pmview_usage()
+{
+ if $_multiple_sources
+ then
+ echo -n '
+ -A align align sample times on natural boundaries
+ -a archive[,archive...]
+ metrics source is one or more PCP archive logs
+ -C check configuration file and exit
+ -h host[,host...] metrics source is PMCD on one or more hosts'
+
+ else
+
+ echo -n '
+ -A align align sample times on natural boundaries
+ -a archive metrics source is a PCP archive log
+ -C check configuration file and exit
+ -h host metrics source is PMCD on host'
+
+ fi
+
+ echo '
+ -n pmnsfile use an alternative PMNS
+ -O offset initial offset into the time window
+ -p port port name for connection to existing time control
+ -S starttime start of the time window
+ -T endtime end of the time window
+ -t interval sample interval [default 2.0 seconds]
+ -x version use pmlaunch(5) version [default version 2.0]
+ -z set reporting timezone to local time of metrics source
+ -Z timezone set reporting timezone
+
+ -display display-string
+ -geometry geometry-string
+ -name name-string
+ -title title-string
+ -xrm resource'
+}
+
+# Most front-end scripts can handle input from only one source, and
+# hence only one -h or -a option.
+#
+# For those than can handle multiple sources, they should call
+# _pmview_multiple_sources_are_ok before calling _pmview_args to
+# enable the following extensions to the -a and -h options:
+# -a a1 -a a2 ... multiple -a options
+# -h h1 -h h2 ... multiple -h options
+# -a a1,a2,... multiple archives with the -a option
+# -h h1,h2,... multiple hosts with the -h option
+#
+_pmview_multiple_sources_are_ok()
+{
+ _multiple_sources=true
+}
+
+# check for magic numbers in a file that indicate it is a PCP archive
+#
+# if file(1) was reliable, this would be much easier, ... sigh
+#
+# if you need to change this, make consistent changes in all these
+# places:
+# _check_file() in src/pmafm/mkaf
+# _is_archive() in src/pmafm/pmafm
+# _is_archive() in src/pmview/front-ends/pmview-args
+#
+_is_archive()
+{
+ case "$PCP_PLATFORM" in
+ irix)
+ dd ibs=1 count=7 if="$1" 2>/dev/null | od -X | $PCP_AWK_PROG '
+NR == 1 && $2 == "00000084" && $3 == "50052600" { exit 0 }
+ { exit 1 }'
+ ;;
+ linux)
+ dd ibs=1 count=7 if="$1" 2>/dev/null | od -x | $PCP_AWK_PROG '
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "0084" && $4 == "5005" && $5 == "2600" { exit 0 }
+NR == 1 && NF == 5 && $2 == "0000" && $3 == "8400" && $4 == "0550" && $5 == "0026" { exit 0 }
+
+ { exit 1 }'
+ ;;
+ esac
+ return $?
+}
+
+# One of the first actions of a front end script should be to call
+# _pmview_args. It sets the following variables:
+#
+# host the first host specified with -h (if any) in the format
+# hostname
+# else the host from the first archive in the format
+# hostname (Archive archivename)
+# arch the first archive specified with -a (if any).
+# All archives are passed on to pmview with one -a
+# argument per archive via $args
+# numsource Number of metrics sources (hosts or archives)
+# args The list of args that pmview will comprehend and use.
+# otherArgs The arguments pmview will not understand and should be
+# handled by the front end script.
+# titleArg The title the user prefers. If empty, the title should be
+# provided by the front end script.
+# prog The name of the program.
+# namespace The namespace (including the flag) if specified, else empty
+# eg "-n foo"
+# msource The default metrics source, whether live or an archive,
+# including the flag. e.g. "-h blah" or "-a first_archive".
+# Taken from the first encountered -a or -h option.
+#
+# sourcelist space separated list of hosts or archives
+#
+_pmview_args()
+{
+
+_seen_host=false
+_seen_arch=false
+host=""
+arch=""
+numsource=0
+args=""
+otherArgs=""
+titleArg=""
+namespace=""
+msource=""
+
+if [ $# -eq 1 -a '$1' = '-\?' ]
+then
+ _usage
+ exit 0
+fi
+
+while [ $# -gt 0 ]
+do
+ case $1
+ in
+ -g*|-di*|-name|-xrm)
+ # assume an X11 argument
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: X-11 option $1 requires one argument"
+ #NOTREACHED
+ fi
+ args="$args $1 '$2'"
+ shift
+ ;;
+
+ -title)
+ # assume an X11 argument
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ exit 1
+ fi
+ titleArg="$2"
+ shift
+ ;;
+
+ -A|-D|-O|-p|-S|-T|-t|-x|-Z)
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ args="$args $1 '$2'"
+ shift
+ ;;
+
+ -A*|-D*|-O*|-p*|-S*|-T*|-t*|-Z*|-C|-z)
+ args="$args $1"
+ ;;
+
+ -a)
+ if $_seen_host
+ then
+ _pmview_note Usage-Error error "$prog: Only one of the -h or -a options may be specified"
+ #NOTREACHED
+ fi
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ for _archbase in `echo "$2" | sed -e 's/,/ /g'`
+ do
+ if [ $numsource -gt 0 -a $_multiple_sources = false ]
+ then
+ _pmview_note Usage-Error error "$prog: Only one -a option may be specified"
+ # NOTREACHED
+ fi
+ if _is_archive $_archbase 2>&1
+ then
+ _archbase=`echo $_archbase | sed -e 's/\.[^.]*$//'`
+ fi
+ # at least $_archbase.0 and $_archbase.meta have to be here
+ #
+ if _is_archive $_archbase.0 && _is_archive $_archbase.meta
+ then
+ :
+ else
+ _pmview_note Error error "$prog: \"$_archbase\" is not the basename of a valid PCP archive"
+ #NOTREACHED
+ fi
+ if [ -z "$arch" ]
+ then
+ # first archive seen
+ #
+ arch=$_archbase
+ msource="-a $_archbase"
+ host=`pmdumplog -l $arch \
+ | $PCP_AWK_PROG '/^Performance/ {print $5}' \
+ | sed -e 's/,//g'`
+ [ "X$host" = X ] && host="unknown host"
+ host="$host (Archive $arch)"
+ fi
+
+ # pmview(1) can handle multiple -a options, so
+ # pass _all_ archive names back to the caller both as
+ # -a options via $args and in $sourcelist (counted by
+ # $numsource).
+ #
+ # Note: multiple -h options are handled slightly
+ # differently, see also the comments for -h below.
+ #
+
+ args="$args -a $_archbase"
+ if [ -z "$sourcelist" ]; then
+ sourcelist=$_archbase
+ else
+ sourcelist="$sourcelist $_archbase"
+ fi
+ numsource=`expr $numsource + 1`
+ done
+ _seen_arch=true
+ shift
+ ;;
+
+ -h)
+ if $_seen_arch
+ then
+ _pmview_note Usage-Error error "$prog: Only one of the -h or -a options may be specified"
+ #NOTREACHED
+ fi
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ for _host in `echo "$2" | sed -e 's/,/ /g'`
+ do
+ if [ $numsource -gt 0 -a $_multiple_sources = false ]
+ then
+ _pmview_note Usage-Error error "$prog: Only one -h option may be specified"
+ # NOTREACHED
+ fi
+ if [ -z "$host" ]
+ then
+ host=$_host
+ msource="-h $_host"
+
+ # pmview(1) can handle only one -h options, so
+ # pass the _first_ host name back to the caller as a
+ # -h option via $args and _all_ hostnames via
+ # $sourcelist (counted by $numsource).
+ #
+ # Note: multiple -a options are handled slightly
+ # differently, see also the comments for -a above.
+ #
+
+ args="$args -h $_host"
+ fi
+ if [ -z "$sourcelist" ]; then
+ sourcelist=$_host
+ else
+ sourcelist="$sourcelist $_host"
+ fi
+ numsource=`expr $numsource + 1`
+ done
+ _seen_host=true
+ shift
+ ;;
+
+ -n)
+ if [ $# -lt 2 ]
+ then
+ _pmview_note Usage-Error error "$prog: Option $1 requires one argument"
+ #NOTREACHED
+ fi
+ namespace="-n $2"
+ args="$args -n $2"
+ shift
+ ;;
+
+ *)
+ otherArgs="$otherArgs $1"
+ ;;
+
+ esac
+ shift
+
+done
+
+if [ -z "$host" ]
+then
+ host=`pmhostname`
+ msource="-h $host"
+fi
+}
+
+# standard fatal error reporting
+# Usage: _pmview_error message goes in here
+# _pmview_error -f file
+#
+_pmview_error()
+{
+ _pmview_note Error error $*
+}
+
+# standard warning
+# Usage: _pmview_warning message goes in here
+# _pmview_warning -f file
+#
+_pmview_warning()
+{
+ _pmview_note Warning warning $*
+}
+
+# standard info
+# Usage: _pmview_info message goes in here
+# _pmview_info -f file
+#
+_pmview_info()
+{
+ _pmview_note Info info $*
+}
+
+# generic notifier
+# Usage: _pmview_note tag icon args ...
+#
+_pmview_note()
+{
+ tag=$1; shift
+ icon=$1; shift
+ button=""
+ [ $tag = Error ] && button="-B Quit"
+ [ $tag = Usage-Error ] && button="-B Quit -b Usage"
+
+ [ X"$PCP_STDERR" = XDISPLAY -a -z "$DISPLAY" ] && unset PCP_STDERR
+
+ if [ $# -eq 2 -a "X$1" = X-f ]
+ then
+ case "$PCP_STDERR"
+ in
+ DISPLAY)
+ ans=`$CONFIRM -icon $icon -file $2 -useslider -header "$tag $prog" $button 2>&1`
+ ;;
+ '')
+ echo "$tag:" >&2
+ cat $2 >&2
+ ans=Quit
+ ;;
+ *)
+ echo "$tag:" >>$PCP_STDERR
+ cat $2 >>$PCP_STDERR
+ ans=Quit
+ ;;
+ esac
+ else
+ case "$PCP_STDERR"
+ in
+ DISPLAY)
+ ans=`$CONFIRM -icon $icon -t "$*" -noframe -header "$tag $prog" $button 2>&1`
+ ;;
+ '')
+ echo "$tag: $*" >&2
+ ans=Quit
+ ;;
+ *)
+ echo "$tag: $*" >>$PCP_STDERR
+ ans=Quit
+ ;;
+ esac
+ fi
+
+ if [ $tag = Usage-Error ]
+ then
+ [ $ans = Usage ] && _usage
+ tag=Error
+ fi
+
+ [ $tag = Error ] && exit 1
+}
+
+# used internally by _pmview_cache_fetch() and _pmview_fetch()
+#
+_pmview_probe()
+{
+ flag=$1
+ shift
+ ( echo $* \
+ ; echo "-----" \
+ ; ( $PMPROBE $namespace $msource $flag $* 2>$tmp.pmview_err \
+ | tee -a $tmp.pmview_fetch \
+ ) \
+ ) \
+ | tr ' ' '\012' \
+ | sed -e 's/"//g' \
+ | $PCP_AWK_PROG '
+BEGIN { last = 0 }
+$1 == "-----" { state = 1; next }
+state == 0 { metric[last] = pat[last] = $1
+ # deal with arguments that are non-terminals in the PMNS
+ # so pmprobe a.b => a.b.c a.b.c.x a.b.d etc
+ gsub("\\.", "\\.", pat[last])
+ pat[last] = "^" pat[last] "\\."
+ last++
+ next
+ }
+state > 0 { for (i = 0; i < last; i++) {
+ if (metric[i] == $1 || match($1, pat[i]) > 0) {
+ # new matching metric name
+ name=$1
+ state=2
+ next
+ }
+ }
+ }
+state == 2 { if ($1 > 0) state = 3
+ else state = 1
+ next
+ }
+state == 3 { printf("%s%s|%s\n", "'"$flag|$msource|"'",name,$1) }'
+}
+
+# Fetch metrics and cache result
+# input
+# $1 - pmprobe flag
+# $* - 1 or more metrics
+# output
+# $tmp.pmview_cache - cached values, with this format
+# pmprobe flag|metric source|metric name|pmprobe result
+#
+_pmview_cache_fetch()
+{
+ flag=$1
+ shift
+ _pmview_probe $flag $* >>$tmp.pmview_cache
+
+ return 0
+}
+
+# Fetch metrics
+#
+# input
+# $1 - pmprobe flag
+# $2 - metric name
+# output
+# $number - number of values
+# $tmp.pmview_result - values
+#
+_pmview_fetch()
+{
+ flag=$1
+ metric=$2
+ rm -f $tmp.pmview_fetch $tmp.pmview_result
+ if [ -s $tmp.pmview_cache ]
+ then
+ $PCP_AWK_PROG -F\| \
+ <$tmp.pmview_cache >$tmp.pmview_result \
+'
+$1 == "'"$flag"'" && $2 == "'"$msource"'" && $3 == "'"$metric"'" { print $4 }'
+ fi
+
+ if [ ! -s $tmp.pmview_result ]
+ then
+ # cache miss, forced to probe
+ #
+ _pmview_probe $flag $metric \
+ | $PCP_AWK_PROG -F\| >$tmp.pmview_result '{print $4}'
+ fi
+
+ if [ -s $tmp.pmview_result ]
+ then
+ number=`wc -l <$tmp.pmview_result | sed -e 's/ //g'`
+ else
+ if [ -s $tmp.pmview_fetch ]
+ then
+ check=`cut -d ' ' -f 2 $tmp.pmview_fetch`
+ if [ "$check" = "$metric" ]
+ then
+ # *.pmview_fetch looks valid, extract numval from 2nd field
+ #
+ number=`cut -d ' ' -f 2 $tmp.pmview_fetch`
+ else
+ # *.pmview_fetch exists, but does not contain
+ # pmprobe output, more than likely this is some
+ # sort of fatal error message ... but _real_ message
+ # is likely to be in *.pmview_err
+ #
+ [ -s $tmp.pmview_err ] && mv $tmp.pmview_err $tmp.pmview_fetch
+ number=-1
+ fi
+ else
+ number=-1
+ mv $tmp.pmview_err $tmp.pmview_fetch
+ fi
+ fi
+ if [ $number -le 0 ]
+ then
+ rm -f $tmp.pmview_result
+ return 1
+ fi
+
+ return 0
+}
+
+# Fetch the metric values
+#
+# input
+# $1 - metric name
+# output
+# $number - number of values
+# $tmp.pmview_result - values
+#
+_pmview_fetch_values()
+{
+ _pmview_fetch -v $1
+ return $?
+}
+
+# Fetch the metric instance list
+#
+# input
+# $1 - metric name
+# output
+# $number - number of instances
+# $tmp.pmview_result - instances
+#
+_pmview_fetch_indom()
+{
+ _pmview_fetch -I $1
+ return $?
+}
+
+# Convert pmprobe/pminfo error message into something useful and
+# consistent
+#
+_pmview_fetch_mesg()
+{
+ $PCP_AWK_PROG '
+$1 == "pmprobe:" { $1 = "'$prog':"; print; exit }
+$1 == "pminfo:" { $1 = "'$prog':"; print; exit }
+$1 == "Error:" { $1 = ":";
+ printf("%s: %s%s\n", "'$prog'", metric, $0); exit }
+$1 == "inst" { exit }
+NF == 1 { metric = $1; next }
+NF == 0 { next }
+NF == 2 && $2 == "0" { printf("%s: %s: No values available\n", "'$prog'", $1); exit}
+ { $2 = ":"; print "'$prog': " $0; exit}' \
+ | sed "s/ : /: /" \
+ | fmt
+}
+
+# Generate error metric for failed fetch
+#
+_pmview_fetch_fail()
+{
+ cat $tmp.pmview_fetch | _pmview_fetch_mesg >> $tmp.msg
+ echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp.msg
+ _pmview_error -f $tmp.msg
+ # NOTREACHED
+}
+
+# Generate warning message for failed fetch
+#
+_pmview_fetch_warn()
+{
+ cat $tmp.pmview_fetch | _pmview_fetch_mesg >> $tmp.msg
+ echo "$prog: Failed to $1 from host \"$host\"" | fmt >> $tmp.msg
+ _pmview_warning -f $tmp.msg
+ rm -f $tmp.msg
+}
+
+# Check that $OPTARG for option $1 is a positive integer
+# ...note the creative use of unary - to prevent leading signs
+#
+_pmview_unsigned()
+{
+ if [ "X-$OPTARG" != "X`expr 0 + -$OPTARG 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -$1 option must have a positive integral argument"
+ # NOTREACHED
+ fi
+}
diff --git a/src/pmview/front-ends/weblogvis b/src/pmview/front-ends/weblogvis
new file mode 100755
index 0000000..5b570bd
--- /dev/null
+++ b/src/pmview/front-ends/weblogvis
@@ -0,0 +1,451 @@
+#!/bin/sh
+# Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+# default idle time
+#
+defidle=3600
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options] [server ...]
+
+Options
+ -b display byte rate, rather than request rate
+ -f display activity by function, rather than activity by
+ result size
+ -i show server names
+ -I time maximum expected idle time in seconds [default $defidle]
+ -m max maximum expected request rate (or maximum byte rate)
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+ _pmview_usage >> $tmp.msg
+ echo '
+Default title is: Web Log Activity (request rate by result size) for Host' >> $tmp.msg
+ echo '
+ The default is to display the request rate, grouped by result size, for all
+ Web servers on the target host.' | fmt >> $tmp.msg
+ _pmview_info -f $tmp.msg
+}
+
+# --- build WM_COMMAND X(1) property for restart after login/logout ---
+#
+echo -n "pmview Version 2.1 \"$prog\"" >$tmp.pmview
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.pmview
+done
+echo >> $tmp.pmview
+
+# --- parse command line arguments ---
+#
+verbose=false
+serverList=""
+max=0
+idle=$defidle
+class=size
+show=request
+showInst=false
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?bfiI:m:v:V" c $otherArgs
+ do
+ case $c
+ in
+ b) # show bytes not requests
+ show=byte
+ ;;
+
+ f) # classify by function (method)
+ class=method
+ ;;
+ i)
+ showInst=true
+ ;;
+ I)
+ idle=$OPTARG
+ # and now the obscure +ve integer checking bit
+ # ...note the creative use of unary - to prevent leading signs
+ if [ "X-$idle" != "X`expr 0 + -$idle 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -I must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+ m)
+ max=$OPTARG
+ # and now the obscure +ve integer checking bit
+ # ...note the creative use of unary - to prevent leading signs
+ if [ "X-$max" != "X`expr 0 + -$max 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -m must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+ v)
+ if [ $OPTARG = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ elif [ $OPTARG != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+ V)
+ verbose=true
+ ;;
+ '?')
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+
+ set - $otherArgs
+ shift `expr $OPTIND - 1`
+ [ $# -gt 0 ] && serverList_sp=$* && serverList=`echo $* | sed -e 's/ /,/g'`
+fi
+[ -z "$titleArg" ] && titleArg="SGI PCP : Web Log Activity"
+
+
+if [ $max = 0 ]
+then
+ if [ $show = request ]
+ then
+ # requests per second
+ max=50
+ else
+ # bytes per second
+ max=500000
+ fi
+fi
+
+
+# maximum req error rate (default: 5% of request rate)
+#
+maxerr=`expr $max / 20`
+
+
+# if metrics source is an archive, and find out name of host
+#
+if [ "X$arch" != X ]
+then
+ host=`pmdumplog -l $arch | $PCP_AWK_PROG '/^Performance/ {print $5}' \
+ | sed -e 's/,//g'`
+ [ "X$host" = X ] && host="unknown host"
+ host="$host (Archive $arch)"
+fi
+
+
+# get perserver instances
+# in the default case use web.perserver.requests.total
+# in the -b flag case use web.perserver.bytes.total
+#
+rm -f $tmp.int
+if [ $show = request ]
+then
+ pminfo -f $msource $namespace web.perserver.requests.total \
+ > $tmp.int 2>/dev/null
+else
+ pminfo -f $msource $namespace web.perserver.bytes.total \
+ > $tmp.int 2>/dev/null
+fi
+if [ ! -s $tmp.int ]
+then
+ if [ "X$arch" != "X" ]
+ then
+ _pmview_error "$prog: weblog metrics not included in the archive \"$arch\""
+ #NOTREACHED
+ else
+ _pmview_error "$prog: weblog metrics not available for host \"$host\""
+ #NOTREACHED
+ fi
+fi
+
+sed -e 's/\]//' -e 's/"//g' $tmp.int \
+ | $PCP_AWK_PROG '$1 == "inst" {print $4}' > $tmp.list
+
+if [ ! -s $tmp.list ]
+then
+ if [ "X$arch" != X ]
+ then
+ _pmview_error "$prog: failed to get web servers from archive \"$arch\""
+ #NOTREACHED
+ else
+ _pmview_error "$prog: failed to get web servers from host \"$host\""
+ #NOTREACHED
+ fi
+fi
+
+rm -f $tmp.chosen
+if [ -z "$serverList" ]
+then
+ cp $tmp.list $tmp.chosen
+else
+ touch $tmp.chosen
+ for i in $serverList_sp
+ do
+ egrep $i $tmp.list >> $tmp.chosen
+ done
+fi
+
+servers_sp=`cat $tmp.chosen | sort | uniq`
+servers=`echo $servers_sp | sed -e 's/ /,/g'`
+nservers=`echo $servers | wc -w`
+
+if [ $nservers = 0 ]
+then
+ _usage
+ echo
+ echo "Error: $prog: no matching web servers"
+ echo " Available web servers are: " `cat $tmp.list`
+ exit 1
+fi
+
+
+if [ $show = request ]
+then
+ totalLabel="Total_Req_Rate"
+ label_sp="\"Request Rate\""
+ met="requests"
+ titleArg="$titleArg (request rate"
+else
+ totalLabel="Total_Data_Rate"
+ label_sp="\"Data Rate\""
+ met="bytes"
+ titleArg="$titleArg (data rate"
+fi
+
+#
+# strings for base plane labels
+#
+basestr_idle="Elapsed time since the last request\nNormalized to $idle seconds"
+if [ $show = request ]
+then
+ basestr_total="Total number of HTTP requests processed by server\nNormalized to $max requests per second"
+ basestr_req="HTTP request rate by response size in bytes\nNormalized to $max hits per second"
+ basestr_type="HTTP request rate by HTTP method\nNormalized to $max hits per second"
+else
+ basestr_total="Total number of bytes processed by server\nNormalized to $max bytes per second"
+ basestr_req="HTTP size rate by response size in bytes\nNormalized to $max bytes per second"
+ basestr_type="HTTP size rate by HTTP method\nNormalized to $max hits per second"
+fi
+
+
+#--- config file has already been created; continue writing to it ---
+#
+
+cat << end-of-file >>$tmp.pmview
+#
+# $prog
+#
+# Servers: $servers
+#
+end-of-file
+
+if $verbose
+then
+ if [ "X$serverList" != X ]
+ then
+ echo "# Matching servers for \"$serverList\": $servers"
+ fi
+fi
+
+if [ $class = size ]
+then
+ titleArg="$titleArg by result size)"
+else
+ titleArg="$titleArg by request type)"
+fi
+
+
+#
+# the real config starts here
+#
+cat << end-of-file >>$tmp.pmview
+
+_scale 1.2
+
+_grid _hide (
+
+ _label 2 0 1 5 _down _large $label_sp
+
+ _bar 0 0 east _col (
+ _metrics (
+ web.perserver.logidletime[$servers] $idle
+ )
+ _metriclabels ( Idle )
+ _colorList ( orange )
+ _baseLabel "$basestr_idle"
+ )
+
+end-of-file
+
+if [ $class = size ]
+then
+ cat << end-of-file >>$tmp.pmview
+
+ _bar 0 2 east _col (
+ _metrics (
+ web.perserver.$met.total[$servers] $max
+ )
+ _metriclabels ( Total )
+ _colorList ( rgbi:0.0/0.88/0.88 )
+ _baseLabel "$basestr_total"
+ )
+
+ _bar 0 4 east _col _groupbyinst (
+ _metrics (
+end-of-file
+
+ if [ $show = request ]
+ then
+ cat <<end-of-file >>$tmp.pmview
+ web.perserver.$met.size.unknown[$servers] $max
+end-of-file
+ fi
+
+ cat << end-of-file >>$tmp.pmview
+ web.perserver.$met.size.gt3m[$servers] $max
+ web.perserver.$met.size.le3m[$servers] $max
+ web.perserver.$met.size.le1m[$servers] $max
+ web.perserver.$met.size.le300k[$servers] $max
+ web.perserver.$met.size.le100k[$servers] $max
+ web.perserver.$met.size.le30k[$servers] $max
+ web.perserver.$met.size.le10k[$servers] $max
+ web.perserver.$met.size.le3k[$servers] $max
+end-of-file
+
+ if [ $show = request ]
+ then
+ cat <<end-of-file >>$tmp.pmview
+ web.perserver.$met.size.zero[$servers] $max
+ )
+ _metriclabels (
+ "?" "> 3M" "1M < 3M" "300K < 1M" "100K < 300K"
+ "30K < 100K" "10K < 30K" "3K < 10K" "0 < 3K" "0K"
+ )
+end-of-file
+ else
+ cat <<end-of-file >>$tmp.pmview
+ )
+ _metriclabels (
+ "> 3M" "1M < 3M" "300K < 1M" "100K < 300K"
+ "30K < 100K" "10K < 30K" "3K < 10K" "0 < 3K"
+ )
+end-of-file
+ fi
+
+ if $showInst
+ then
+ cat <<end-of-file >>$tmp.pmview
+ _instlabels _away ( $servers_sp )
+end-of-file
+ fi
+
+ cat << end-of-file >>$tmp.pmview
+ _colorList (
+ rgbi:1.0/0.35/0.0
+ rgbi:0.6/0.0/0.9
+ rgbi:0.0/1.0/0.0
+ rgbi:1.0/0.5/0.0
+ rgbi:0.65/0.3/1.0
+ rgbi:0.3/1.0/0.3
+ rgbi:1.0/0.65/0.3
+ rgbi:0.8/0.6/1.0
+ rgbi:0.6/1.0/0.6
+ rgbi:1.0/0.8/0.6
+
+ )
+ _baseLabel "$basestr_req"
+ )
+
+end-of-file
+
+else
+ cat << end-of-file >>$tmp.pmview
+
+ _bar 0 2 east _col (
+ _metrics (
+ web.perserver.$met.total[$servers] $max
+ )
+ _metriclabels ( Total )
+ _colorList ( rgbi:1.0/0.5/0.0 )
+ _baseLabel "$basestr_total"
+ )
+
+ _bar 0 4 east _col (
+ _metrics (
+ web.perserver.$met.get[$servers] $max
+ web.perserver.$met.head[$servers] $max
+ web.perserver.$met.post[$servers] $max
+ web.perserver.$met.other[$servers] $max
+end-of-file
+
+ if [ $show = request ]
+ then
+ cat << end-of-file >>$tmp.pmview
+ web.perserver.errors[$servers] $maxerr
+end-of-file
+ fi
+
+ cat << end-of-file >>$tmp.pmview
+ )
+ _metriclabels ( Get Head Post Other Error )
+end-of-file
+
+ if $showInst
+ then
+ cat <<end-of-file >>$tmp.pmview
+ _instlabels _away ( $servers_sp )
+end-of-file
+ fi
+
+ cat << end-of-file >>$tmp.pmview
+ _colorList (
+ rgbi:1.0/1.0/0.0
+ rgbi:0.0/1.0/1.0
+ rgbi:1.0/0.0/1.0
+ rgbi:1.0/1.0/0.6
+ rgbi:0.8/0.0/0.0
+ )
+ _baseLabel "$basestr_type"
+ )
+end-of-file
+
+fi
+
+cat << end-of-file >>$tmp.pmview
+)
+end-of-file
+
+titleArg="$titleArg for Host $host"
+
+$verbose && cat $tmp.pmview
+
+eval $PMVIEW <$tmp.pmview $args -title "'$titleArg'" -xrm "'*iconName: $prog'"
+
+exit
+
diff --git a/src/pmview/front-ends/weblogvis.load b/src/pmview/front-ends/weblogvis.load
new file mode 100755
index 0000000..a676124
--- /dev/null
+++ b/src/pmview/front-ends/weblogvis.load
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Copyright (c) 2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Load up a Web server to show off/QA weblogvis
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; killall -9 load; exit" 0 1 2 3 15
+
+i=0
+
+# specular
+#
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.108/file8k.html
+GET http://155.11.225.108/file15k.html
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.108/file15k.html
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+# specular IP alias
+#
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.118/file45k.html
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.118/file15k.html
+GET http://155.11.225.118/file45k.html
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.118/file45k.html
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+# specular proxy
+#
+cat >$tmp.$i <<End-of-File
+GET http://boing/file15k.html
+End-of-File
+webping -c $tmp.$i -I0 -q -P specular:8080 &
+i=`expr $i + 1`
+
+cat >$tmp.$i <<End-of-File
+GET http://boing/file0k.html
+GET http://boing/file0k.html
+GET http://boing/file115k.html
+End-of-File
+webping -c $tmp.$i -I0 -q -P specular:8080 &
+i=`expr $i + 1`
+
+# specular socks
+#
+cat >$tmp.$i <<End-of-File
+GET http://155.11.225.108/file500k.html
+GET http://boing/file500k.html
+End-of-File
+webping -c $tmp.$i -I0 -q -S specular:8080 &
+i=`expr $i + 1`
+
+# specular ftp-socks
+#
+cat >$tmp.$i <<End-of-File
+GET ftp://155.11.225.108/pub/README
+End-of-File
+webping -c $tmp.$i -I0 -q -S specular:8080 &
+i=`expr $i + 1`
+
+# specular ftp
+#
+cat >$tmp.$i <<End-of-File
+GET ftp://155.11.225.108/pub/README
+End-of-File
+webping -c $tmp.$i -I0 -q &
+i=`expr $i + 1`
+
+wait
diff --git a/src/pmview/front-ends/weblogvis.rgb b/src/pmview/front-ends/weblogvis.rgb
new file mode 100644
index 0000000..11c4712
--- /dev/null
+++ b/src/pmview/front-ends/weblogvis.rgb
Binary files differ
diff --git a/src/pmview/front-ends/webpingvis b/src/pmview/front-ends/webpingvis
new file mode 100644
index 0000000..a08aa81
--- /dev/null
+++ b/src/pmview/front-ends/webpingvis
@@ -0,0 +1,372 @@
+#! /bin/sh
+# Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options]
+
+options:
+ -i show URL names
+ -m max maximum expected total ping response time (milliseconds)
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+
+ _pmview_usage >> $tmp.msg
+ echo >> $tmp.msg
+ echo 'Default title is: SGI PCP: Web PING Performance for Host' >> $tmp.msg
+ echo '
+ The "max" parameter (used with -m) should be set to the maximum
+ expected ping response time for all URLs being monitored. The default
+ is 1000 milliseconds. This governs the height of the bar showing total
+ response time (at the far right of the scene). All bars showing
+ response times use "max" for height normalization.' >> $tmp.msg
+ _pmview_info -f $tmp.msg
+}
+
+#--- build WM_COMMAND X(1) property for restart after login/logout ---
+#
+echo -n "pmview Version 2.1 \"$prog\"" > $tmp.pmview
+for arg
+do
+ echo -n " \"$arg\"" >> $tmp.pmview
+done
+echo >> $tmp.pmview
+
+# --- LOCAL FUNCTIONS : START ------------------------------------------------
+_sort_host_indom()
+{
+ sed <$1 \
+ -e 's/instance\[\([0-9][0-9]*\)]:/\1/' \
+ | $PCP_AWK_PROG '
+BEGIN { state = 0 }
+/^samples:/ { state = 1; next }
+state == 0 { next }
+/^full label/ { label[$4] = $NF; next }
+NF == 0 { state = 2; next }
+state == 2 { for (i=1; i<=NF; i++) {
+ if (label[i-1] == "")
+ label[i-1] = $i
+ }
+ state = 3
+ next
+ }
+state == 3 { for (i=1; i<=NF; i++)
+ size[i-1] = $i
+ }
+END { for (i in label) {
+ print label[i],size[i]
+ }
+ }' \
+ | sort +1n -2 +0 -1 >$tmp.tmp
+ mv $tmp.tmp $1
+}
+
+_sort_arch_indom()
+{
+metric=webping.perurl.kbytes
+indom=`pminfo -d -a $arch $metric \
+| sed -n -e '/InDom:/{
+s/.*InDom: //
+s/ .*//p
+}'`
+
+if [ "X$indom" = XPM_INDOM_NULL ]
+then
+ _pmview_error "$prog: $metric is singular"
+ #NOTREACHED
+elif [ -z "$indom" ]
+then
+ _pmview_error "$prog: cannot determine InDom for $metric"
+ #NOTREACHED
+fi
+
+pmdumplog -z -i $arch 2>&1 \
+| $PCP_AWK_PROG '
+/^InDom: '$indom'$/ { state=1; next }
+/^InDom: / { state=0; next }
+state == 1 && /^[^ ]/ { print }' \
+| while read stamp junk
+do
+ pminfo -f -a $arch -z -O "@$stamp" -f $metric
+done \
+| sed -n \
+ -e '/ value /{
+s/"] value//
+s/.*"//p
+}' \
+| sort -u >$tmp.known
+
+pmdumplog -z -i $arch 2>&1 \
+| $PCP_AWK_PROG '
+/^InDom: '$indom'$/ { state=1; next }
+/^InDom: / { state=0; next }
+state == 1 && /^[ ]/ { print }' \
+| sed -n \
+ -e '/ or /{
+s/.* or "//
+s/".*//p
+}' \
+| sort -u \
+| join -a1 - $tmp.known \
+| sort +2n -3 +1 -2 \
+| $PCP_AWK_PROG '
+NF==1 { printf("%s ?\n",$0); next }
+ { print }' > $1
+}
+# --- LOCAL FUNCTIONS : END --------------------------------------------------
+
+verbose=false
+max=1000
+showInst=false
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?im:v:V" c $otherArgs
+ do
+ case $c
+ in
+ i)
+ showInst=true
+ ;;
+ m)
+ max=$OPTARG
+ # and now the obscure +ve integer checking bit
+ # ...note the creative use of unary - to prevent leading signs
+ if [ "X-$max" != "X`expr 0 + -$max 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -m must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+ v)
+ if [ $OPTARG = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ elif [ $OPTARG != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+ V)
+ verbose=true
+ ;;
+ '?')
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+fi
+[ -z "$titleArg" ] && titleArg="SGI PCP: Web PING Performance for Host $host"
+
+
+# check that webping metrics are available from metrics source
+#
+if pminfo $msource $namespace webping 2>&1 \
+ | grep 'webping: Unknown metric name' >/dev/null
+then
+ _pmview_error "$prog: webping metrics not defined in the name space"
+ #NOTREACHED
+fi
+#
+# do a second check to make sure that we have webping.perurl metrics
+#
+if pminfo -f $msource $namespace webping.perurl.kbytes 2>&1 \
+ | grep 'inst .* value' >/dev/null
+then
+ :
+else
+ if [ "X$arch" != "X" ]
+ then
+ _pmview_error "$prog: webping.perurl metrics not available from $arch"
+ #NOTREACHED
+ else
+ _pmview_error "$prog: webping.perurl metrics not available from $host"
+ #NOTREACHED
+ fi
+fi
+
+
+# if metrics source is an archive, and find out name of host
+# get the instance domain and sort it
+#
+if [ "X$arch" != X ]
+then
+ _sort_arch_indom $tmp.int
+ host=`pmdumplog -l $arch | $PCP_AWK_PROG '/^Performance/ {print $5}' | sed -e 's/,//g'`
+ [ "X$host" = X ] && host="unknown host"
+ host="$host (Archive $arch)"
+else
+ pmval -h $host -s 1 webping.perurl.kbytes >$tmp.int
+ _sort_host_indom $tmp.int
+fi
+
+
+urlcount=`wc -l <$tmp.int | sed -e 's/ *//g'`
+bytesmax=`tail -1 $tmp.int | $PCP_AWK_PROG '{print $2}'`
+
+if [ $urlcount -eq 0 ]
+then
+ if [ "X$arch" != "X" ]
+ then
+ _pmview_error "$prog: there were no URLs monitored in archive \"$arch\""
+ #NOTREACHED
+ else
+ _pmview_error "$prog: there are no URLs monitored on host \"$host\""
+ #NOTREACHED
+ fi
+fi
+
+
+url_list=`$PCP_AWK_PROG <$tmp.int '
+NR>1 { printf "," }
+ { printf $1 }
+END { print "" }'`
+
+url_list_sp=`$PCP_AWK_PROG <$tmp.int '
+NR>1 { printf " " }
+/^GET_.*/ { printf "\"GET %s\"",substr($1,5) }
+/^HEAD_.*/ { printf "\"HEAD %s\"",substr($1,6) }
+/^POST_.*/ { printf "\"POST %s\"",substr($1,6) }
+END { print "" }'`
+
+
+#
+# strings for base plane labels
+#
+basestr_size="URL size in kbytes\nNormalized to $bytesmax kbytes"
+basestr_urltime="Time to fetch the URL\nNormalized to $max seconds"
+basestr_tottime="Time to fetch all URLs\nNormalized to $max seconds"
+basestr_err="Webing errors by error type\nNormalized to 1"
+
+
+#--- config file has already been created ; continue writing to it ---
+#
+
+cat << end-of-file >> $tmp.pmview
+#
+# $prog
+#
+# Largest URL = $bytesmax Kbytes
+# Maximum fetch time = $max seconds
+#
+# URLs = $url_list_sp
+#
+end-of-file
+
+
+#
+# the real config starts here
+# pmview Version 2.1
+#
+cat <<end-of-file >> $tmp.pmview
+
+_scale 1.2
+
+_colorList url_colors (
+ rgbi:1.0/0.5/0.0
+ rgbi:0.9/0.9/0.0
+ rgbi:0.0/0.9/0.9
+ rgbi:0.9/0.0/0.9
+)
+
+_grid _hide (
+
+ _bar 0 0 east _col (
+ _metrics (
+ webping.perurl.kbytes[$url_list] $bytesmax
+ )
+ _metriclabels ( Size )
+ _baseLabel "$basestr_size"
+end-of-file
+
+if $showInst
+then
+ cat <<end-of-file >>$tmp.pmview
+ _instlabels _towards ( $url_list_sp )
+end-of-file
+fi
+
+cat <<end-of-file >>$tmp.pmview
+ _colorList ( rgbi:0.0/0.9/0.0 )
+ )
+
+ _bar 0 2 ne _col _groupbyinst (
+ _metrics (
+ webping.perurl.time.total[$url_list] $max
+ webping.perurl.time.body[$url_list] $max
+ webping.perurl.time.head[$url_list] $max
+ webping.perurl.time.connect[$url_list] $max
+ )
+ _metriclabels ( Total Body Head Connect )
+ _baseLabel "$basestr_urltime"
+ _colorList url_colors
+ )
+
+ _bar 0 4 east _row _groupbyrow (
+ _metrics (
+ webping.errors.sockerr 1
+ webping.errors.httperr 1
+ webping.errors.htmlerr 1
+ webping.errors.othererr 1
+ )
+ _instlabels ( Errors )
+ _baseLabel "$basestr_err"
+ _colorList ( red1 red1 red1 red1 )
+ )
+
+end-of-file
+
+#
+# Only show row totals if there is more than one URL being monitored
+#
+if [ $urlcount -gt 1 ]
+then
+ totoff=`expr $urlcount + 1`
+ cat <<end-of-file >> $tmp.pmview
+ _bar 2 2 north _col _groupbycol (
+ _metrics (
+ webping.time.total $max
+ webping.time.body $max
+ webping.time.head $max
+ webping.time.connect $max
+ )
+ _colorList url_colors
+ _baseLabel "$basestr_tottime"
+ )
+end-of-file
+
+fi
+
+cat <<end-of-file >> $tmp.pmview
+)
+end-of-file
+
+$verbose && cat $tmp.pmview
+
+#eval pmview <$tmp.pmview $args -title "'$titleArg'" -xrm "'*iconName: $prog'" -geometry 560x515
+eval pmview <$tmp.pmview $args -title "'$titleArg'" -xrm "'*iconName: $prog'"
+
+exit
diff --git a/src/pmview/front-ends/webpingvis.rgb b/src/pmview/front-ends/webpingvis.rgb
new file mode 100644
index 0000000..a444bec
--- /dev/null
+++ b/src/pmview/front-ends/webpingvis.rgb
Binary files differ
diff --git a/src/pmview/front-ends/webvis b/src/pmview/front-ends/webvis
new file mode 100755
index 0000000..7969aec
--- /dev/null
+++ b/src/pmview/front-ends/webvis
@@ -0,0 +1,707 @@
+#!/bin/sh
+# Copyright (c) 1995-2001 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+tmp=/tmp/$$
+trap "rm -f $tmp.*; exit" 0 1 2 3 15
+rm -f $tmp.*
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/pmview-args
+
+# --- scaling parameters ---
+#
+
+# maximum packets per second
+max=750
+
+# maximum req rate (default: 5% of packet rate)
+maxreq=`expr $max / 20`
+
+# maximum disk io rate (I/O operations per second)
+maxio=100
+
+# maximum average disk business (percent)
+maxbusy=30
+
+# milliseconds per CPU
+maxcpu=1000
+
+# maximum TCP output queue length
+maxq=5
+
+# --- define usage message ---
+#
+_usage()
+{
+ echo >$tmp.msg 'Usage: '$prog' [options] [interface ...]
+
+options:
+ -b maxbusy maximum average disk active (percent) [default 30]
+ -i maxio maximum total I/Os per second [default 100]
+ -m max maximum expected packets sent or received per sec [default 750]
+ -r maxreq maximum expected Web requests per second [default 35]
+ -V verbose/diagnostic output
+
+pmview(1) options:'
+
+ _pmview_usage >> $tmp.msg
+ echo >> $tmp.msg
+ echo >> $tmp.msg 'Default title is: Web Server Activity for Host'
+ echo >> $tmp.msg '
+ By default all network interfaces are shown, with a maximum packet rate
+ of '$max' packets per second. The maximum request rate is 5% (of the
+ maximum packet rate) and the maximum error rate is 20% of the maximum
+ request rate.
+
+ If given, the [interface ...] regular expressions restrict
+ the network statistics displayed to matching network interface names only.'
+
+ _pmview_info -f $tmp.msg
+}
+
+# --- build WM_COMMAND X(1) property for restart after login/logout ---
+#
+echo -n "pmview Version 2.1 \"$prog\"" > $tmp.pmview
+for arg
+do
+ echo -n " \"$arg\"" >>$tmp.pmview
+done
+echo >> $tmp.pmview
+
+# --- parse command line arguments ---
+#
+verbose=false
+argInterfaces=""
+interfaces=""
+
+_pmview_args "$@"
+
+if [ -n "$otherArgs" ]
+then
+ while getopts "?b:i:m:r:v:V" c $otherArgs
+ do
+ case $c
+ in
+ b)
+ # and now the obscure +ve integer checking bit
+ # ...note the creative use of unary - to prevent leading signs
+ maxbusy=$OPTARG
+ if [ "X-$maxbusy" != "X`expr 0 + -$maxbusy 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -b must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+
+ i)
+ maxio=$OPTARG
+ if [ "X-$maxio" != "X`expr 0 + -$maxio 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -i must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+
+ m)
+ max=$OPTARG
+ if [ "X-$max" != "X`expr 0 + -$max 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -m must have a positive integral argument"
+ #NOTREACHED
+ fi
+ maxreq=`expr $max / 20`
+ ;;
+
+ r)
+ maxreq=$OPTARG
+ if [ "X-$maxreq" != "X`expr 0 + -$maxreq 2>/dev/null`" ]
+ then
+ _pmview_error "$prog: -r must have a positive integral argument"
+ #NOTREACHED
+ fi
+ ;;
+
+ v)
+ if [ $OPTARG = "1" ]
+ then
+ _pmview_warning "$prog: pmview version 1 no longer supported, using version 2"
+ elif [ $OPTARG != "2" ]
+ then
+ _pmview_error "$prog: only version 2 supported for -v"
+ # NOTREACHED
+ fi
+ ;;
+
+ V)
+ verbose=true
+ ;;
+
+ '?')
+ _usage
+ exit 1
+ ;;
+ esac
+ done
+
+ set - $otherArgs
+ shift `expr $OPTIND - 1`
+ [ $# -gt 0 ] && argInterfaces="$*"
+fi
+
+# maximum average active for disks, percent -> msec/sec
+maxactive=`expr $maxbusy \* 1000 / 100`
+
+
+# maximum req error rate (default: 20% of packet rate)
+maxerr=`expr $maxreq / 5`
+[ "$maxerr" -eq 0 ] && maxerr=1
+
+
+# --- check that web metrics are available from metrics source ---
+#
+if _pmview_fetch web.allservers.errors
+then
+ :
+else
+ _pmview_error "$prog: weblog metrics not defined in the name space"
+ #NOTREACHED
+fi
+
+
+# --- if metrics source is an archive, and find out name of host ---
+#
+if [ "X$arch" != X ]
+then
+ host=`pmdumplog -l $arch | $PCP_AWK_PROG '/^Performance/ {print $5}' | sed -e 's/,//g'`
+ [ "X$host" = X ] && host="unknown host"
+ host="$host (Archive $arch)"
+fi
+
+
+# --- get network interfaces ---
+#
+pminfo -f $msource network.interface.total.bytes >$tmp.int
+sed -e 's/\]//' -e 's/"//g' $tmp.int \
+ | $PCP_AWK_PROG '$1 == "inst" {print $4}' > $tmp.list
+if [ ! -s $tmp.list ]
+then
+ if [ "X$arch" != X ]
+ then
+ _pmview_error "$prog: failed to get network interface list from archive \"$arch\""
+ #NOTREACHED
+ else
+ _pmview_error "$prog: failed to get network interface list from host \"$host\""
+ #NOTREACHED
+ fi
+fi
+
+
+# --- pmview config file has already been created; keep writing to it ---
+#
+
+cat << end-of-file >> $tmp.pmview
+#
+# $prog
+#
+end-of-file
+
+if $verbose
+then
+ echo "# Available Network Interfaces: " `cat $tmp.list` >> $tmp.pmview
+fi
+
+if [ -z "$argInterfaces" ]
+then
+ cp $tmp.list $tmp.chosen
+else
+ touch $tmp.chosen
+ for i in $argInterfaces
+ do
+ egrep $i $tmp.list >> $tmp.chosen
+ done
+fi
+
+interfaces_sp=`cat $tmp.chosen | sort | uniq`
+loflag=false
+for iface in $interfaces_sp
+do
+ if [ "X$iface" = "Xlo0" ]
+ then
+ loflag=true
+ else
+ interfaces="$interfaces $iface"
+ fi
+done
+$loflag && interfaces="$interfaces lo0"
+ninterfaces=`echo $interfaces | wc -w`
+
+if $verbose
+then
+ echo "# Network interfaces Matching \"$argInterfaces\": $interfaces" >> $tmp.pmview
+fi
+
+if [ $ninterfaces = 0 ]
+then
+ echo "$prog: no matching network interfaces"
+ echo "Available interfaces on host \"$host\" are: " `cat $tmp.list`
+ echo ""
+ _usage
+ exit 1
+fi
+
+net_grid1=`expr $ninterfaces + 2`
+
+# --- how many CPUs on this system? ---
+#
+numcpu=`pminfo $msource -f $namespace hinv.ncpu 2>&1 | $PCP_AWK_PROG '
+BEGIN { num = 0 }
+$1 == "value" { if (NF == 2) num = $2 }
+END { print num }'`
+
+if [ $numcpu -lt 1 ]
+then
+ _pmview_fetch_indom kernel.percpu.cpu.user && numcpu=$number
+ if [ $numcpu -lt 1 ]
+ then
+ _pmview_error "$prog: Unable to determine the number of CPUs on host $host"
+ #NOTREACHED
+ fi
+fi
+
+maxcpu=`expr $maxcpu \* $numcpu`
+
+if $verbose
+then
+ if [ $numcpu = 1 ]
+ then
+ echo "# 1 CPU detected" >> $tmp.pmview
+ echo "#" >> $tmp.pmview
+ else
+ echo "# $numcpu CPUs detected" >> $tmp.pmview
+ echo "#" >> $tmp.pmview
+ fi
+fi
+
+# --- how much memory on this system? ---
+#
+realmem=0
+if pminfo -v $msource $namespace hinv.physmem > /dev/null 2>&1
+then
+ realmem=`pminfo -f $msource $namespace hinv.physmem | $PCP_AWK_PROG '/value/ { print $2 }'`
+ if [ -z "$realmem" ]
+ then
+ realmem=0
+ else
+ realmem=`expr $realmem \* 1024`
+ fi
+fi
+[ $realmem = 0 ] && echo "$prog: Warning: Unable to determine size of real memory for $host"
+
+
+# --- how many disks on this system? ---
+#
+numdisk=`pminfo $msource -f $namespace hinv.ndisk 2>&1 | $PCP_AWK_PROG '
+BEGIN { num = 0 }
+$1 == "value" { if (NF == 2) num = $2 }
+END { print num }'`
+
+if [ $numdisk -lt 0 ]
+then
+ _pmview_fetch_indom disk.dev.read && numdisk=$number
+ [ $numdisk -lt 0 ] && _pmview_warning "$prog: Unable to determine the number of disks for $host"
+fi
+
+if $verbose
+then
+ if [ $numdisk = 1 ]
+ then
+ echo "# 1 disk detected" >> $tmp.pmview
+ echo "#" >> $tmp.pmview
+ else
+ echo "# $numdisk disks detected" >> $tmp.pmview
+ echo "#" >> $tmp.pmview
+ fi
+fi
+
+
+# --- set the window title ---
+#
+if [ -z "$titleArg" ]
+then
+ titleArg="SGI PCP : Web Server Activity for Host $host"
+fi
+
+
+# --- set base strings for base plane objects ---
+#
+if [ $numcpu = 1 ]
+then
+ basestr_cpu="CPU Utilization\nSummed over 1 CPU"
+else
+ basestr_cpu="CPU Utilization\nSummed over $numcpu CPUs"
+fi
+
+basestr_mem="Physical Memory Utilization\nNormalized to `pminfo -f $msource mem.physmem | grep value | sed -e 's/ *value //'` Kbytes"
+
+basestr_disk="Read and Write activity for all Disks\nNormalized to $maxio I/Os per second"
+basestr_diskact="Average Disk Utilization\nNormalized to $maxbusy% across $numdisk disks"
+
+basestr_net="Input and Output on Network Interfaces\nPackets are normalized to $max packets per second"
+
+basestr_amem="Memory metrics which may indicate a problem\nNormalized to $maxerr events per second"
+basestr_atcp="TCP metrics which may indicate a problem\nNormalized to $maxerr events per second"
+
+basestr_size="HTTP request rate by response size in bytes\nNormalized to $maxreq hits per second"
+basestr_type="HTTP request rate by HTTP method\nNormalized to $maxreq hits per second"
+
+
+# ---- the real config starts here ---
+# pmview Version 2.1
+#
+cat << end-of-file >> $tmp.pmview
+
+_stackLength 26
+_marginWidth 8
+_marginDepth 8
+
+_colorList cpu_colors ( blue2 red2 yellow2 cyan2 )
+_colorList disk_colors ( violet yellow )
+_colorList memory_colors (
+ rgbi:1.0/1.0/0.0
+ rgbi:0.0/1.0/1.0
+ rgbi:1.0/0.0/0.0
+ rgbi:1.0/0.0/1.0
+ rgbi:0.0/0.0/1.0
+ rgbi:0.0/1.0/0.0
+)
+_colorList network_colors (
+ rgbi:0.8/0.0/0.0
+ rgbi:1.0/0.5/0.0
+ rgbi:0.0/0.8/0.0
+)
+_colorList type_colors (
+ rgbi:0.8/0.0/0.0
+ rgbi:1.0/1.0/0.6
+ rgbi:1.0/0.0/1.0
+ rgbi:0.0/1.0/1.0
+ rgbi:1.0/1.0/0.0
+)
+
+_colorList size_colors (
+ rgbi:1.0/0.35/0.0
+ rgbi:0.6/0.0/0.9
+ rgbi:0.0/1.0/0.0
+ rgbi:1.0/0.5/0.0
+ rgbi:0.65/0.3/1.0
+ rgbi:0.3/1.0/0.3
+ rgbi:1.0/0.65/0.3
+ rgbi:0.8/0.6/1.0
+ rgbi:0.6/1.0/0.6
+ rgbi:1.0/0.8/0.6
+)
+
+_grid _hide (
+
+#
+# Alarms
+#
+ _grid 1 0 6 2 south _hide (
+ _bar 0 1 6 1 west _row _groupbyrow (
+ _metrics (
+end-of-file
+
+for m in drops conndrops timeoutdrop rcvbadsum rexmttime sndrexmitpack attemptfails inerrs retranssegs
+do
+ if _pmview_fetch network.tcp.$m
+ then
+ echo " network.tcp.$m $maxerr" >> $tmp.pmview
+ fi
+done
+
+cat << end-of-file >> $tmp.pmview
+ )
+ _colorList (
+ red1 red1 red1 red1 red1 red1
+ )
+ _baseLabel "$basestr_atcp"
+ )
+ _bar 0 0 3 1 west _row _groupbyrow (
+ _metrics (
+end-of-file
+
+for m in swap.pagesout network.mbuf.failed network.mbuf.waited
+do
+ if _pmview_fetch $m
+ then
+ echo " $m $maxerr" >> $tmp.pmview
+ fi
+done
+
+cat << end-of-file >> $tmp.pmview
+ )
+ _colorList (
+ yellow rgbi:1.0/0.5/0.0 rgbi:1.0/0.5/0.0
+ )
+ _baseLabel "$basestr_amem"
+ )
+ _label 3 0 west _right _medium "Alarms"
+ )
+
+#
+# Size
+#
+ _bar 0 1 1 10 south _groupbycol (
+ _metrics (
+ web.allservers.requests.size.unknown $maxreq
+ web.allservers.requests.size.gt3m $maxreq
+ web.allservers.requests.size.le3m $maxreq
+ web.allservers.requests.size.le1m $maxreq
+ web.allservers.requests.size.le300k $maxreq
+ web.allservers.requests.size.le100k $maxreq
+ web.allservers.requests.size.le30k $maxreq
+ web.allservers.requests.size.le10k $maxreq
+ web.allservers.requests.size.le3k $maxreq
+ web.allservers.requests.size.zero $maxreq
+ )
+ _metriclabels _towards (
+ "?" ">3M" "3M" "1M" "300k"
+ "100k" "30k" "10k" "3k" "0k"
+ )
+ _colorList size_colors
+ _baseLabel "$basestr_size"
+ )
+
+ _label 0 11 northeast _right _medium "Size"
+
+#
+# Type
+#
+ _bar 1 6 1 5 south _groupbycol (
+ _metrics (
+ web.allservers.errors $maxerr
+ web.allservers.requests.other $maxreq
+ web.allservers.requests.post $maxreq
+ web.allservers.requests.head $maxreq
+ web.allservers.requests.get $maxreq
+ )
+ _colorList type_colors
+ _baseLabel "$basestr_type"
+ )
+
+ _label 1 11 north _right _medium "Type"
+
+#
+# System level stuff
+#
+ _grid 1 3 5 3 southwest (
+ _stack 0 0 (
+ _metrics (
+ kernel.all.cpu.user $maxcpu
+ kernel.all.cpu.sys $maxcpu
+ kernel.all.cpu.intr $maxcpu
+ kernel.all.cpu.wait.total $maxcpu
+ )
+ _colorList cpu_colors
+ _baseLabel "$basestr_cpu"
+ )
+ _label 0 1 north _right _medium "CPU"
+
+# 2 levels of base plane, halve the margin size and increase the height
+# for the inner one
+#
+_marginWidth 4
+_marginDepth 4
+_baseHeight 4
+
+ _grid 1 0 2 1 _show (
+ _baseColor rgbi:0.30/0.30/0.30
+ _stack 0 0 south _cylinder (
+ _metrics (
+ disk.all.write $maxio
+ disk.all.read $maxio
+ )
+ _colorList disk_colors
+ _baseLabel "$basestr_disk"
+ )
+end-of-file
+
+ # disk.all.avg_disk.active metric is not available from pcp 1.x
+ #
+ if _pmview_fetch disk.all.avg_disk.active
+ then
+ cat << end-of-file >> $tmp.pmview
+ _bar 1 0 _cylinder (
+ _metrics (
+ disk.all.avg_disk.active $maxactive
+ )
+ _colorList ( green2 )
+ _baseLabel "$basestr_diskact"
+ )
+end-of-file
+ fi
+
+ cat << end-of-file >> $tmp.pmview
+ _baseColor rgbi:0.15/0.15/0.15
+ )
+ _label 1 1 2 1 north _medium "Disk"
+
+end-of-file
+
+ if [ "X$realmem" != X0 -a "X$realmem" != X ]
+ then
+ xcoord=0
+
+ cat << end-of-file >> $tmp.pmview
+ _grid 3 0 2 1 _show (
+ _baseColor rgbi:0.30/0.30/0.30
+end-of-file
+
+ if _pmview_fetch mem.freemem
+ then
+ cat << end-of-file >> $tmp.pmview
+ _stack $xcoord 0 (
+ _metrics (
+ mem.freemem $realmem
+ )
+ _colorList ( rgbi:0.0/0.8/0.0 )
+ _baseLabel "Free memory"
+ )
+end-of-file
+ xcoord=`expr $xcoord + 1`
+ fi
+
+ if _pmview_fetch mem.util.kernel
+ then
+ cat << end-of-file >> $tmp.pmview
+
+ _stack $xcoord 0 (
+ _metrics (
+end-of-file
+ # Use all the metrics we have
+ for m in kernel fs_ctl fs_dirty fs_clean user ; do
+ if _pmview_fetch mem.util.$m
+ then
+ echo " mem.util.$m $realmem" >> $tmp.pmview
+ fi
+ done
+
+ cat << end-of-file >> $tmp.pmview
+ )
+ _colorList memory_colors
+ _baseLabel "$basestr_mem"
+ )
+end-of-file
+ xcoord=`expr $xcoord + 1`
+ fi
+
+ cat << end-of-file >> $tmp.pmview
+ _baseColor rgbi:0.15/0.15/0.15
+ )
+ _label 3 1 2 1 north _medium "Mem"
+end-of-file
+ fi
+
+ cat << end-of-file >> $tmp.pmview
+
+# restore defaults
+#
+ _marginWidth 8
+ _marginDepth 8
+ _baseHeight 2
+ )
+
+#
+# Network Stuff
+#
+ _grid 2 6 $net_grid1 5 south _hide (
+ _grid 0 0 1 2 west _hide (
+ _label 0 0 east _right _medium "In"
+ _label 0 1 east _right _medium "Out"
+ )
+# 2 levels of base plane, halve the margin size and increase the height
+# for the inner one
+#
+_marginWidth 4
+_marginDepth 4
+_baseHeight 4
+
+ _grid 1 0 $ninterfaces 2 _show (
+ _baseColor rgbi:0.30/0.30/0.30
+
+end-of-file
+
+ xcoord=0
+ yin=0
+ yout=1
+ for iface in $interfaces
+ do
+ cat << end-of-file >> $tmp.pmview
+
+ _stack $xcoord $yin (
+ _metrics (
+ network.interface.in.errors[$iface] $maxerr
+ network.interface.in.drops[$iface] $maxerr
+ network.interface.in.packets[$iface] $max
+ )
+ _colorList network_colors
+ _baseLabel "$basestr_net"
+ )
+ _stack $xcoord $yout (
+ _metrics (
+ network.interface.out.errors[$iface] $maxerr
+ network.interface.out.drops[$iface] $maxerr
+ network.interface.out.packets[$iface] $max
+ )
+ _colorList network_colors
+ _baseLabel "$basestr_net"
+ )
+end-of-file
+ xcoord=`expr $xcoord + 1`
+ done
+
+ cat << end-of-file >> $tmp.pmview
+ _baseLabel "$basestr_net"
+_baseColor rgbi:0.15/0.15/0.15
+ )
+# restore defaults
+#
+_marginWidth 8
+_marginDepth 8
+_baseHeight 2
+ _grid 1 2 $ninterfaces 2 _hide (
+end-of-file
+
+ xcoord=0
+ ylabel=0
+ for iface in $interfaces
+ do
+ cat << end-of-file >> $tmp.pmview
+ _label $xcoord $ylabel north _down _medium "$iface"
+end-of-file
+ xcoord=`expr $xcoord + 1`
+ done
+
+ cat << end-of-file >> $tmp.pmview
+ )
+ )
+ _label 3 11 northwest _right _medium "Network"
+)
+end-of-file
+
+$verbose && cat $tmp.pmview
+
+eval $PMVIEW <$tmp.pmview $args -title "'$titleArg'" -xrm "'*iconName: $prog'"
+
+exit
+
diff --git a/src/pmview/front-ends/webvis.rgb b/src/pmview/front-ends/webvis.rgb
new file mode 100644
index 0000000..5bd1482
--- /dev/null
+++ b/src/pmview/front-ends/webvis.rgb
Binary files differ
diff --git a/src/pmview/gram.y b/src/pmview/gram.y
new file mode 100644
index 0000000..5859f94
--- /dev/null
+++ b/src/pmview/gram.y
@@ -0,0 +1,1391 @@
+/*
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+
+#include <QtCore/QStack>
+
+#include "main.h"
+#include "gridobj.h"
+#include "stackobj.h"
+#include "barobj.h"
+#include "labelobj.h"
+#include "pipeobj.h"
+#include "scenefileobj.h"
+#include "link.h"
+#include "xing.h"
+
+extern void yywarn(const char *s);
+extern void yyerror(const char *s);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+extern int yylex(void);
+#ifdef __cplusplus
+}
+#endif
+
+static DefaultObj dobj;
+static QStack<ViewObj *> objstack;
+
+ViewObj * rootObj = 0;
+
+bool theColorScaleFlag;
+int theCol;
+int theRow;
+int theNumCols;
+int theNumRows;
+int theHistory = 0;
+ViewObj::Alignment theAlignment;
+int theColorListCount = 0;
+
+%}
+
+%union {
+ char *y_str;
+ int y_int;
+ double y_real;
+ bool y_bool;
+ ViewObj::Alignment y_align;
+ ViewObj::Shape y_shape;
+ Text::Direction y_textDir;
+ Text::FontSize y_fontSize;
+ BarMod::Direction y_instDir;
+ BarObj::LabelDir y_labelDir;
+ BarMod::Modulation y_barMod;
+ StackMod::Height y_stackMod;
+ BarMod::Grouping y_barGroup;
+ ViewObj * y_object;
+}
+
+%token <y_str>
+ NAME STRING PMVIEW METRIC
+
+%token <y_int>
+ INT
+
+%token <y_real>
+ REAL
+
+%token <y_bool>
+ BOOL
+
+%token <y_align>
+ ALIGN
+
+%token <y_textDir>
+ DIRVAL
+
+%token <y_fontSize>
+ SIZ
+
+%token <y_instDir>
+ INST_DIR
+
+%token <y_barMod>
+ BAR_TYPE
+
+%token <y_shape>
+ SHAPE
+
+%token <y_stackMod>
+ STACK_TYPE
+
+%token <y_labelDir>
+ LABEL_DIR
+
+%token <y_barGroup>
+ GROUP
+
+%token SCALE BAR_HEIGHT BAR_LENGTH MARGIN_WIDTH MARGIN_DEPTH BASE_HEIGHT
+ BASE_COLOR BASE_LABEL GRID_WIDTH GRID_DEPTH GAP_LABEL GRID_SPACE
+ GAP_WIDTH GAP_DEPTH COLORLIST OPENB CLOSEB COLORSCALE GRID SHOW HIDE
+ LABEL LABEL_MARGIN LABEL_COLOR DIRECTION SIZE TEXT BAR METRICLIST
+ METRICLABEL INSTLABEL STACK STACK_LABEL UTIL AWAY TOWARDS
+ BOX SOCKET PIPE PIPE_LENGTH PIPETAG GR_LINK GR_XING HISTORY SCENE_FILE
+
+%type <y_real>
+ real
+
+%type <y_bool>
+ show_or_hide
+ hide_or_show
+
+%type <y_textDir>
+ direction_cmd
+
+%type <y_fontSize>
+ size_cmd
+
+%type <y_instDir>
+ col_or_row
+
+%type <y_barMod>
+ bar_type
+
+%type <y_shape>
+ shape_type
+
+%type <y_stackMod>
+ stack_type
+
+%type <y_labelDir>
+ away_or_towards
+
+%type <y_barGroup>
+ bar_group
+
+%type <y_object>
+ scene object grid bar stack label pipe link xing scenefile
+
+%type <y_str>
+ symname nameval colordecl linktag metricname
+%%
+
+config : header scene;
+
+header : pmview arglist
+ | pmview
+ ;
+
+pmview : PMVIEW
+ {
+ if (strcmp($1, "1.2") < 0) {
+ pmprintf("Version %s is no longer supported\n",
+ $1);
+ pmflush();
+ exit(1);
+ }
+ }
+ ;
+
+arglist : arg
+ | arglist arg
+ ;
+
+arg : STRING
+ {
+ extern char **frontend_argv;
+ extern int frontend_argc;
+
+ frontend_argv = (char **)realloc(frontend_argv,
+ (frontend_argc+1)*sizeof(char *));
+ if (frontend_argv == NULL) {
+ frontend_argc = 0;
+ } else {
+ frontend_argv[frontend_argc] =$1;
+ if (frontend_argv[frontend_argc] == NULL) {
+ frontend_argc = 0;
+ } else {
+ frontend_argc++;
+ }
+ }
+ }
+ ;
+
+scene : object { rootObj = $1; }
+ | sceneattrlist object { rootObj = $2; }
+ ;
+
+object : grid
+ | label
+ | bar
+ | pipe
+ | link
+ | xing
+ | stack
+ | scenefile
+ ;
+
+sceneattrlist : sceneattr
+ | sceneattrlist sceneattr
+ ;
+
+sceneattr : scale
+ | named_color_list
+ | named_color_scale
+ | most_opts
+ ;
+
+most_opts : mod_opts
+ | base_opts
+ | grid_opts
+ | bar_opts
+ | label_opts
+ | pipe_opts
+ ;
+
+scale : SCALE REAL
+ {
+ if ($2 <= 0.0)
+ yyerror("_scale requires a positive real number");
+ else
+ theGlobalScale = $2;
+ }
+ ;
+
+pipe_opts : PIPE_LENGTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_pipeLength requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.pipeLength() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->pipeLength() = $2;
+ }
+ else
+ yyerror ("cannot change pipe length - not a grid");
+ }
+ ;
+
+mod_opts : BAR_HEIGHT INT
+ {
+ if ($2 <= 0)
+ yyerror("_barHeight requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.barHeight() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->barHeight() = $2;
+ }
+ else
+ yyerror ("cannot change bar height - not a grid");
+ }
+ | BAR_LENGTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_barLength requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.barLength() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->barLength() = $2;
+ }
+ else
+ yyerror ("cannot change bar length - not a grid");
+ }
+ ;
+
+base_opts : MARGIN_WIDTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_marginWidth requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.baseBorderX() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->baseBorderX() = $2;
+ }
+ else
+ yyerror("cannot change width margin - not a grid");
+ }
+ | MARGIN_DEPTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_marginDepth requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.baseBorderZ() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->baseBorderZ() = $2;
+ }
+ else
+ yyerror ("cannot change depth margin - not a grid");
+ }
+ | BASE_HEIGHT INT
+ {
+ if ($2 <= 0)
+ yyerror("_baseHeight requires a positive integer");
+ else if ( objstack.empty () )
+ dobj.baseHeight() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->baseHeight() = $2;
+ }
+ else
+ yyerror ("cannot change base height - not a grid");
+ }
+ | BASE_COLOR symname
+ {
+ float r, g, b;
+
+ if (ColorList::findColor($2, r, g, b) == true ) {
+ if ( objstack.empty () )
+ dobj.baseColor (r,g,b);
+ else if(objstack.top()->objbits()&ViewObj::GRIDOBJ){
+ GridObj * go =
+ static_cast<GridObj*>(objstack.top());
+
+ go->defs()->baseColor(r, g, b);
+ }
+ else
+ yyerror ("Cannot change base color - not a grid");
+
+ } else {
+ sprintf(theBuffer,
+ "unable to map _baseColor color \"%s\"",
+ $2);
+ yyerror(theBuffer);
+ }
+ }
+ | BASE_COLOR REAL REAL REAL
+ {
+ if ( $2 < 0.0 || $2 > 1.0 ||
+ $3 < 0.0 || $3 > 1.0 ||
+ $4 < 0.0 || $4 > 1.0) {
+ sprintf(theBuffer,
+ "_baseColor colors %f,%f,%f must be "
+ "between 0.0 and 1.0",
+ $2, $3, $4);
+ yyerror(theBuffer);
+ } else if ( objstack.empty () ) {
+ dobj.baseColor($2, $3, $4);
+ } else if(objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->baseColor($2, $3, $4);
+ } else {
+ yyerror("Cannot change base color - not a grid");
+ }
+ }
+ ;
+
+grid_opts : GRID_WIDTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_gridWidth requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.gridMinWidth() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->gridMinWidth() = $2;
+ }
+ else
+ yyerror("Cannot change min width - not a grid");
+ }
+ | GRID_DEPTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_gridDepth requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.gridMinDepth() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->gridMinDepth() = $2;
+ }
+ else
+ yyerror("Cannot change min depth - not a grid");
+ }
+ | GRID_SPACE INT
+ {
+ if ($2 <= 0) {
+ yyerror("_gridSpace requires a positive integer");
+ } else if ( objstack.empty() ) {
+ dobj.gridMinWidth() = $2;
+ dobj.gridMinDepth() = $2;
+ } else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->gridMinWidth() = $2;
+ go->defs()->gridMinDepth() = $2;
+ } else {
+ yyerror("Cannot change grid size - not a grid");
+ }
+ }
+ ;
+
+bar_opts : GAP_WIDTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_gapWidth requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.barSpaceX() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->barSpaceX() = $2;
+ }
+ else
+ yyerror ("Cannot change bar width - not a grid");
+ }
+ | GAP_DEPTH INT
+ {
+ if ($2 <= 0)
+ yyerror("_gapDepth requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.barSpaceZ() = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->barSpaceZ() = $2;
+ }
+ else
+ yyerror ("Cannot change bar depth - not a grid");
+ }
+ | GAP_LABEL INT
+ {
+ if ($2 <= 0)
+ yyerror("_gapLabel requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.barSpaceLabel () = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->barSpaceLabel() = $2;
+ }
+ else
+ yyerror ("Cannot change label space - not a grid");
+ }
+ ;
+
+label_opts : LABEL_MARGIN INT
+ {
+ if ($2 <= 0)
+ yyerror("_labelMargin requires a positive integer");
+ else if ( objstack.empty() )
+ dobj.labelMargin () = $2;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj*>(objstack.top());
+ go->defs()->labelMargin() = $2;
+ }
+ else
+ yyerror ("Cannot change label margin - not a grid");
+ }
+ | LABEL_COLOR symname
+ {
+ float r, g, b;
+
+ if (ColorList::findColor($2, r, g, b) == true) {
+ if ( objstack.empty() )
+ dobj.labelColor(r, g, b);
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * go =
+ static_cast<GridObj*>(objstack.top());
+ go->defs()->labelColor(r, g, b);
+ }
+ else
+ yyerror ("Cannot change label color - not a grid");
+ } else {
+ sprintf(theBuffer, "unable to map color \"%s\"", $2);
+ yyerror(theBuffer);
+ }
+ }
+ | LABEL_COLOR REAL REAL REAL
+ {
+ if ($2 < 0.0 || $2 > 1.0 || $3 < 0.0 || $3 > 1.0 ||
+ $4 < 0.0 || $4 > 1.0) {
+ sprintf(theBuffer, "unable to map color %f,%f,%f",
+ $2, $3, $4);
+ yyerror(theBuffer);
+ } else {
+ if ( objstack.empty () )
+ dobj.labelColor ($2, $3, $4);
+ else if(objstack.top()->objbits()&ViewObj::GRIDOBJ){
+ GridObj * go =
+ static_cast<GridObj*>(objstack.top());
+ go->defs()->labelColor($2, $3, $4);
+ }
+ else
+ yyerror ("Cannot change label color - not a grid");
+ }
+ }
+ ;
+
+symname : NAME | STRING;
+
+named_color_list: COLORLIST NAME OPENB
+ {
+ if (theColorLists.add($2) == false) {
+ sprintf(theBuffer,
+ "Color list \"%s\" is already defined", $2);
+ yywarn(theBuffer);
+ }
+ } colors CLOSEB
+ ;
+
+colors : color
+ | colors color
+ ;
+
+color : symname
+ {
+ if (theColorLists.addColor($1) == false) {
+ sprintf(theBuffer, "Unable to map color \"%s\"", $1);
+ yywarn(theBuffer);
+ }
+ }
+ | REAL REAL REAL
+ {
+ if (theColorLists.addColor($1, $2, $3) == false) {
+ sprintf(theBuffer,
+ "Unable to map color %f,%f,%f, "
+ "values may be out of range",
+ $1, $2, $3);
+ yywarn(theBuffer);
+ }
+ }
+ ;
+
+named_color_scale: COLORSCALE NAME symname OPENB
+ {
+ if (theColorLists.list($2) == NULL) {
+ if (theColorLists.add($2, $3) == false) {
+ sprintf(theBuffer,
+ "Unable to map color \"%s\", "
+ "defaulting to blue",
+ $3);
+ }
+ theColorScaleFlag = true;
+ } else {
+ sprintf(theBuffer,
+ "Color scale \"%s\" is already defined",
+ $2);
+ yywarn(theBuffer);
+ theColorScaleFlag = false;
+ }
+ } scaled_color_list CLOSEB
+ | COLORSCALE NAME REAL REAL REAL OPENB
+ {
+ if (theColorLists.list($2) == NULL) {
+ if (theColorLists.add($2, $3, $4, $5) == false) {
+ sprintf(theBuffer,
+ "Unable to map color %f,%f,%f, "
+ "defaulting to blue",
+ $3, $4, $5);
+ }
+ theColorScaleFlag = true;
+ } else {
+ sprintf(theBuffer,
+ "Color scale \"%s\" is already defined", $2);
+ yywarn(theBuffer);
+ theColorScaleFlag = false;
+ }
+ } scaled_color_list CLOSEB
+ ;
+
+scaled_color_list : scaled_color
+ | scaled_color_list scaled_color
+ ;
+
+scaled_color : symname REAL
+ {
+ if (theColorLists.addColor($1, $2) == false) {
+ sprintf(theBuffer, "Unable to map color \"%s\"", $1);
+ yywarn(theBuffer);
+ }
+ }
+ | REAL REAL REAL REAL
+ {
+ if (theColorLists.addColor($1, $2, $3, $4) == false) {
+ sprintf(theBuffer,
+ "Unable to map color %f,%f,%f, "
+ "values may be out of range",
+ $1, $2, $3);
+ yywarn(theBuffer);
+ }
+ }
+ ;
+
+pos : grid_pos {
+ theNumCols = 1;
+ theNumRows = 1;
+ theAlignment = ViewObj::center;
+ }
+ | grid_pos grid_size {
+ theAlignment = ViewObj::center;
+ }
+ | grid_pos alignment {
+ theNumCols = 1;
+ theNumRows = 1;
+ }
+ | grid_pos grid_size alignment
+ | /* Empty position */ {
+ theCol = 0;
+ theRow = 0;
+ theNumCols = 1;
+ theNumRows = 1;
+ theAlignment = ViewObj::center;
+ }
+ ;
+
+grid_pos : INT INT
+ {
+ if ($1 < 0) {
+ sprintf(theBuffer,
+ "Column index must be positive, was %d",
+ $1);
+ yyerror(theBuffer);
+ theCol = 0;
+ } else
+ theCol = $1;
+
+ if ($2 < 0) {
+ sprintf(theBuffer,
+ "Row index must be positive, was %d",
+ $2);
+ yyerror(theBuffer);
+ theRow = 0;
+ } else
+ theRow = $2;
+ }
+ ;
+
+grid_size : INT INT
+ {
+ if ($1 < 0) {
+ sprintf(theBuffer,
+ "Number of columns must be positive, was %d",
+ $1);
+ yyerror(theBuffer);
+ theNumCols = 1;
+ } else
+ theNumCols = $1;
+ if ($2 < 0) {
+ sprintf(theBuffer,
+ "Number of rows must be positive, was %d",
+ $2);
+ yyerror(theBuffer);
+ theNumRows = 1;
+ } else
+ theNumRows = $2;
+ }
+ ;
+
+alignment : ALIGN { theAlignment = $1; };
+
+baselabelspec : BASE_LABEL symname
+ {
+ if ( objstack.empty () ) {
+ yyerror ("Syntax error - no object to label");
+ } else if(objstack.top()->objbits() & ViewObj::BASEOBJ){
+ BaseObj * bo = static_cast<BaseObj *>(objstack.top());
+ int i;
+ QString str = $2;
+
+ for (i = 0; i < str.size(); i++) {
+ if (str[i] == '\n')
+ break;
+ if (str[i] == '\\' &&
+ i + 1 < str.size() &&
+ str[i + 1] == 'n') {
+ str[i] = '\n';
+ str.remove(i+1, 1);
+ break;
+ }
+ }
+
+ if (i == str.length())
+ str.append(QChar('\n'));
+
+ bo->label() = str;
+ } else {
+ yyerror ("Syntax error - wrong object");
+ }
+ }
+ ;
+
+scenefile : scenefile_decl
+ {
+ if ( ($$ = objstack.top()) ) {
+ $$->finishedAdd();
+ }
+ objstack.pop();
+ }
+ ;
+
+scenefile_decl : SCENE_FILE pos STRING
+ {
+ const DefaultObj * dob = 0;
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+
+ if (dob) {
+ if ( SceneFileObj * so = new SceneFileObj (*dob,
+ theCol, theRow,
+ theNumCols,
+ theNumRows,
+ theAlignment) ) {
+ so->setSceneFileName($3);
+ objstack.push (so);
+ }
+ else
+ yyerror (
+ "Cannot create a scene file object - out of memory");
+ }
+ else
+ yyerror (
+ "Syntax error - Scene File inside simple object!");
+ }
+ ;
+
+pipe : pipedecl OPENB pipespec CLOSEB
+ {
+ if ( ($$ = objstack.top()) ) {
+ $$->finishedAdd();
+ }
+ objstack.pop();
+ }
+ ;
+
+pipedecl : PIPE pos
+ {
+ if ( theNumRows > 1 && theNumCols > 1 ) {
+ yyerror ("Diagonal pipes are not supported");
+ objstack.push (0); // So that we would pop grid up
+ } else {
+ const DefaultObj * dob = 0;
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+
+ if ( dob )
+ if ( PipeObj * po = new PipeObj (*dob,
+ theCol, theRow,
+ theNumCols,
+ theNumRows,
+ theAlignment) )
+ objstack.push (po);
+ else
+ yyerror (
+ "Cannot create a pipe - out of memory");
+ else
+ yyerror (
+ "Syntax error - Pipe inside simple object");
+ }
+ }
+ ;
+
+pipespec : pipeattr
+ | pipespec pipeattr
+ ;
+
+pipeattr : metric_list
+ | named_color
+ | color_list
+ | pipetag
+ ;
+
+pipetag : PIPETAG symname
+ {
+ if (objstack.empty () ||
+ ((objstack.top()->objbits() & ViewObj::PIPEOBJ) == 0)) {
+ yyerror ("No pipe to attach tag to");
+ } else {
+ PipeObj * p = static_cast<PipeObj*>(objstack.top());
+ p->setTag($2);
+ }
+ }
+ ;
+
+link : GR_LINK pos linktag
+ {
+ const DefaultObj * dob = 0;
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+
+ if ( dob )
+ if ( Link * l = new Link (*dob, theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment) ) {
+ if ( $3 != NULL ) {
+ l->setTag ($3);
+ free ($3);
+ }
+
+ l->finishedAdd ();
+ $$ = l;
+ } else {
+ yyerror ("Cannot create a link - out of memory");
+ }
+ else
+ yyerror ("Syntax error - link inside simple object");
+ }
+ ;
+
+linktag : symname { $$ = strdup ($1); }
+ | /* nothing */ { $$ = NULL; }
+ ;
+
+grid_object : object
+ {
+ if ( $1 ) {
+ if ( (! objstack.empty()) &&
+ (objstack.top()->objbits() & ViewObj::GRIDOBJ) ) {
+ GridObj * go =
+ static_cast<GridObj *>(objstack.top());
+ go->addObj ($1 , $1->col(), $1->row());
+ } else {
+ yyerror ("Syntax error - no gird to add to");
+ }
+ }
+ }
+ | most_opts
+ | baselabelspec
+ ;
+
+grid_object_list : grid_object
+ | grid_object_list grid_object
+ ;
+
+grid : griddecl grid_object_list CLOSEB
+ {
+ $$ = objstack.top();
+ $$->finishedAdd();
+ objstack.pop();
+ }
+ ;
+
+griddecl : GRID pos hide_or_show OPENB
+ {
+ const DefaultObj * dob = 0;
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+
+ if ( dob )
+ if (GridObj * go = new GridObj($3, *dob,
+ theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment)) {
+ objstack.push (go);
+ } else {
+ yyerror ("Cannot create new grid - out of memory");
+ }
+ else
+ yyerror ("Syntax error - grid inside a simple object");
+ }
+ ;
+
+hide_or_show : BOOL { $$ = $1; }
+ | { $$ = false; }
+ ;
+
+label : labeldecl OPENB label_stuff CLOSEB
+ {
+ $$ = objstack.top();
+ $$->finishedAdd();
+ objstack.pop();
+ }
+ | labeldecl direction_cmd size_cmd STRING
+ {
+ if ( ($$ = objstack.top()) ) {
+ if ( $$->objbits() & ViewObj::LABELOBJ ) {
+ LabelObj * lo = static_cast<LabelObj*>($$);
+ lo->dir() = $2;
+ lo->size() = $3;
+ lo->str() = $4;
+ lo->finishedAdd ();
+ }
+ }
+ objstack.pop();
+ }
+ ;
+
+labeldecl : LABEL pos
+ {
+ const DefaultObj * dob = 0;
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+ else
+ yyerror ("Syntax error - label inside simple object");
+
+
+ if ( dob ) {
+ if (LabelObj * lo = new LabelObj(*dob,
+ theCol, theRow,
+ theNumCols,
+ theNumRows,
+ theAlignment))
+ objstack.push (lo);
+ else {
+ yyerror ("Cannot create label - out of memory");
+ objstack.push (NULL);
+ }
+ }
+ }
+ ;
+
+
+direction_cmd : DIRVAL { $$ = $1; }
+ | { $$ = Text::right; }
+ ;
+
+size_cmd : SIZ { $$ = $1; }
+ | { $$ = Text::medium; }
+ ;
+
+label_stuff : label_item
+ | label_stuff label_item
+ ;
+
+label_item : DIRECTION DIRVAL
+ {
+ if ( objstack.empty () )
+ yyerror ("cannot change direction - no label");
+ else if (objstack.top()->objbits() & ViewObj::LABELOBJ) {
+ LabelObj * lo =
+ static_cast<LabelObj*>(objstack.top());
+ lo->dir() = $2;
+ }
+ else
+ yyerror ("Syntax error - not a label");
+ }
+ | SIZE SIZ
+ {
+ if ( objstack.empty () )
+ yyerror ("cannot set label size");
+ else if (objstack.top()->objbits() & ViewObj::LABELOBJ) {
+ LabelObj * lo =
+ static_cast<LabelObj*>(objstack.top());
+ lo->size() = $2;
+ }
+ else
+ yyerror ("Syntax error - not a label");
+ }
+ | TEXT symname
+ {
+ if ( objstack.empty () )
+ yyerror ("cannot set label text");
+ else if (objstack.top()->objbits() & ViewObj::LABELOBJ) {
+ LabelObj * lo =
+ static_cast<LabelObj*>(objstack.top());
+ lo->str() = $2;
+ }
+ else
+ yyerror ("Syntax error - not a label");
+ }
+ ;
+
+bar : bardecl bar_stuff CLOSEB
+ {
+ $$ = objstack.top();
+ $$->finishedAdd();
+ objstack.pop();
+ }
+ ;
+
+history : HISTORY INT
+ {
+ theHistory = $2;
+ }
+ |
+ ;
+
+bardecl : BAR pos col_or_row show_or_hide bar_type shape_type bar_group history OPENB
+ {
+ BarObj * bo = 0;
+
+ if ( objstack.empty () )
+ bo = new BarObj($6, $3, $5, $7, $4, dobj,
+ theCol, theRow,
+ theNumCols, theNumRows, theAlignment);
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ){
+ GridObj * go =
+ static_cast<GridObj *>(objstack.top());
+ bo = new BarObj($6, $3, $5, $7, $4, *go->defs(),
+ theCol, theRow,
+ theNumCols, theNumRows, theAlignment);
+ }
+ else
+ yyerror ("Syntax error - bar inside simple object");
+
+ if ( bo ) {
+ bo->setHistory(theHistory);
+ objstack.push (bo);
+ }
+ }
+ ;
+
+show_or_hide : BOOL { $$ = $1; }
+ | { $$ = true; }
+ ;
+
+col_or_row : INST_DIR { $$ = $1; }
+ | { $$ = BarMod::instPerCol; }
+ ;
+
+bar_type : BAR_TYPE { $$ = $1; }
+ | { $$ = BarMod::yScale; }
+ ;
+shape_type : SHAPE { $$ = $1 ; }
+ | { $$ = ViewObj::cube; }
+ ;
+
+bar_group : GROUP { $$ = $1; }
+ | { $$ = BarMod::groupByMetric; }
+ ;
+
+bar_stuff : bar_item
+ | bar_stuff bar_item
+ ;
+
+bar_item : labelled_metric_list
+ | named_color
+ | color_list
+ | metric_labels
+ | inst_labels
+ | baselabelspec
+ ;
+
+labelled_metric_list: METRICLIST OPENB labelled_metrics CLOSEB;
+
+labelled_metrics: labelled_metric
+ | labelled_metrics labelled_metric
+ ;
+
+metricname : METRIC | NAME ;
+
+labelled_metric : metric
+ | metricname real STRING
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add metrics to");
+ else if (objstack.top()->objbits() & ViewObj::BAROBJ) {
+ BarObj * bo =
+ static_cast<BarObj *>(objstack.top());
+ bo->addMetric($1, $2, $3);
+ } else {
+ yyerror ("Syntax error - not a bar object");
+ }
+ }
+ ;
+
+real : REAL { $$ = $1; }
+ | INT { $$ = (double)$1; }
+ ;
+
+colordecl : COLORLIST NAME { $$ = $2; }
+ | COLORSCALE NAME { $$ = $2; }
+ ;
+
+named_color : colordecl
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add colors to");
+ else if (objstack.top()->objbits() & ViewObj::MODOBJ) {
+ ModObj * mo =
+ static_cast<ModObj *>(objstack.top());
+ mo->setColorList ($1);
+ } else {
+ sprintf (theBuffer,
+ "Syntax error - %s cannot have colors",
+ objstack.top()->name());
+ yyerror (theBuffer);
+ }
+ }
+ ;
+
+color_list : COLORLIST OPENB
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add colors to");
+ else if (objstack.top()->objbits() & ViewObj::MODOBJ) {
+ ModObj * mo = static_cast<ModObj *>(objstack.top());
+ sprintf(theBuffer, "@tmp%d", theColorListCount++);
+ theColorLists.add(theBuffer);
+ mo->setColorList (theBuffer);
+ } else {
+ sprintf (theBuffer,
+ "Syntax error - %s cannot have colors",
+ objstack.top()->name());
+ yyerror (theBuffer);
+ }
+ } colors CLOSEB
+ | COLORSCALE symname OPENB
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add colors to");
+ else if (objstack.top()->objbits() & ViewObj::MODOBJ) {
+ ModObj * mo = static_cast<ModObj *>(objstack.top());
+ sprintf(theBuffer, "@tmp%d", theColorListCount++);
+ theColorLists.add(theBuffer, $2);
+ mo->setColorList (theBuffer);
+ } else {
+ sprintf (theBuffer,
+ "Syntax error - %s cannot have colors",
+ objstack.top()->name());
+ yyerror (theBuffer);
+ }
+ } scaled_color_list CLOSEB
+ | COLORSCALE REAL REAL REAL OPENB
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add colors to");
+ else if (objstack.top()->objbits() & ViewObj::MODOBJ) {
+ ModObj * mo = static_cast<ModObj *>(objstack.top());
+
+ sprintf(theBuffer, "@tmp%d", theColorListCount++);
+ theColorLists.add(theBuffer, $2, $3, $4);
+ mo->setColorList (theBuffer);
+ } else {
+ sprintf (theBuffer,
+ "Syntax error - %s cannot have colors",
+ objstack.top()->name());
+ yyerror (theBuffer);
+ }
+ } scaled_color_list CLOSEB
+ ;
+
+away_or_towards : LABEL_DIR { $$ = $1; }
+ | { $$ = BarObj::towards; }
+ ;
+
+metric_labels : metriclabeldecl
+ | metriclabeldecl OPENB metric_name_list CLOSEB
+ ;
+
+metriclabeldecl : METRICLABEL away_or_towards
+ {
+ if ( objstack.empty () )
+ yyerror ("No object to add metric labels to");
+ else if (objstack.top()->objbits() & ViewObj::BAROBJ) {
+ BarObj * bo = static_cast<BarObj *>(objstack.top());
+ bo->metricLabelDir() = $2;
+ }
+ else
+ yyerror ("Syntax error - not a bar object");
+ }
+ ;
+
+metric_name_list: metric_name
+ | metric_name_list metric_name
+ ;
+
+metric_name : nameval
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add metric names to");
+ else if (objstack.top()->objbits() & ViewObj::BAROBJ) {
+ BarObj * bo = static_cast<BarObj *>(objstack.top());
+ bo->addMetricLabel($1);
+ }
+ else
+ yyerror ("Syntax error - not a bar object");
+ free ($1);
+ }
+ ;
+
+nameval : symname { $$ = strdup ($1); }
+ | INT { sprintf(theBuffer,"%d",$1); $$ = strdup (theBuffer); }
+ | REAL { sprintf(theBuffer,"%f",$1); $$ = strdup (theBuffer); }
+ ;
+
+inst_labels : INSTLABEL away_or_towards OPENB inst_name_list CLOSEB
+ {
+ if ( objstack.empty () )
+ yyerror ("No object to add instance labels to");
+ else if (objstack.top()->objbits() & ViewObj::BAROBJ) {
+ BarObj * bo = static_cast<BarObj *>(objstack.top());
+ bo->instLabelDir() = $2;
+ }
+ else
+ yyerror ("Syntax error - not a bar object");
+
+ }
+ ;
+
+inst_name_list : inst_name
+ | inst_name_list inst_name
+ ;
+
+inst_name : nameval
+ {
+ if ( objstack.empty() )
+ yyerror ("No object to add instance labels to");
+ else if (objstack.top()->objbits() & ViewObj::BAROBJ) {
+ BarObj * bo = static_cast<BarObj *>(objstack.top());
+ bo->addInstLabel($1);
+ }
+ else
+ yyerror ("Syntax error - not a bar object");
+ free ($1);
+ }
+ ;
+
+stack : stackdecl stack_stuff CLOSEB
+ {
+ $$ = objstack.top();
+ $$->finishedAdd();
+ objstack.pop();
+ }
+ ;
+
+stackdecl : STACK pos show_or_hide stack_type shape_type history OPENB
+ {
+ StackObj * so = 0;
+
+ if ( objstack.empty () )
+ so = new StackObj($4, $5, $3, dobj,
+ theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment);
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj *>(objstack.top());
+ so = new StackObj($4, $5, $3, *go->defs(),
+ theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment);
+ }
+ else
+ yyerror ("Syntax error - stack inside simple object");
+
+ if ( so ) {
+ so->setHistory(theHistory);
+ objstack.push (so);
+ }
+ }
+ | UTIL pos OPENB
+ {
+ StackObj * so = 0;
+
+ if ( objstack.empty () )
+ so = new StackObj(StackMod::unfixed, ViewObj::cube,
+ false, dobj,
+ theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment);
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * go = static_cast<GridObj *>(objstack.top());
+
+ so = new StackObj(StackMod::unfixed, ViewObj::cube,
+ false, *go->defs(),
+ theCol, theRow,
+ theNumCols, theNumRows,
+ theAlignment);
+ }
+ else
+ yyerror ("Syntax error - stack inside simple object");
+
+ if ( so ) {
+ objstack.push (so);
+ }
+ }
+ ;
+
+stack_type : STACK_TYPE { $$ = $1; }
+ | { $$ = StackMod::unfixed; }
+ ;
+
+stack_stuff : stack_item
+ | stack_stuff stack_item
+ ;
+
+stack_item : metric_list
+ | named_color
+ | color_list
+ | baselabelspec
+ | STACK_LABEL symname
+ {
+ if ( objstack.empty() ) {
+ yyerror ("Syntax error - no stack to label");
+ } else if (objstack.top()->objbits() & ViewObj::STACKOBJ) {
+ StackObj * so = static_cast<StackObj*>(objstack.top());
+
+ if (so->height() == StackMod::fixed) {
+ int i;
+
+ QString str = $2;
+ for (i = 0; i < str.size(); i++) {
+ if (str[i] == '\n')
+ break;
+ if (str[i] == '\\' &&
+ i + 1 < str.size() &&
+ str[i + 1] == 'n') {
+ str[i] = '\n';
+ str.remove(i+1, 1);
+ break;
+ }
+ }
+ if (i == str.length())
+ str.append(QChar('\n'));
+ so->setFillText((const char *)str.toAscii());
+ } else {
+ yyerror("_stackLabel may only be applied to "
+ "filled stacks");
+ }
+ } else {
+ yyerror ("Syntax error - not a stack");
+ }
+ }
+ ;
+
+metric_list : METRICLIST OPENB metrics CLOSEB;
+
+metrics : metric
+ | metrics metric
+ ;
+
+metric : metricname real
+ {
+ if ( objstack.empty () )
+ yyerror ("Syntax error - no object");
+ else if (objstack.top()->objbits() & ViewObj::MODOBJ) {
+ ModObj * mo = static_cast<ModObj *>(objstack.top());
+ mo->addMetric($1, $2);
+ }
+ else
+ yyerror ("The object has no metrics");
+ }
+ ;
+
+xing : GR_XING INT INT INT INT ALIGN ALIGN ALIGN ALIGN
+ {
+ const DefaultObj * dob = 0;
+ ViewObj::Alignment c[4] = { $6, $7, $8, $9};
+
+ if ( objstack.empty() )
+ dob = & dobj;
+ else if (objstack.top()->objbits() & ViewObj::GRIDOBJ) {
+ GridObj * parent =
+ static_cast<GridObj *>(objstack.top());
+ dob = parent->defs();
+ }
+ else
+ yyerror ("Syntax error - label inside simple object");
+
+ if ( Xing * xo = new Xing (*dob, $2, $3, $4, $5, c) ) {
+ xo->finishedAdd ();
+ $$ = xo;
+ } else {
+ $$ = 0;
+ }
+ }
+ ;
+%%
diff --git a/src/pmview/gridobj.cpp b/src/pmview/gridobj.cpp
new file mode 100644
index 0000000..cd0f762
--- /dev/null
+++ b/src/pmview/gridobj.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoCube.h>
+#include "gridobj.h"
+#include "defaultobj.h"
+
+#include <iostream>
+using namespace std;
+
+GridObj::~GridObj()
+{
+}
+
+GridObj::GridObj(bool onFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ GridObj::Alignment align)
+ : BaseObj(onFlag, defaults, x, y, cols, rows, align),
+ _minDepth(defaults.gridMinDepth()),
+ _minWidth(defaults.gridMinWidth()),
+ _width(_minWidth + baseWidth()),
+ _depth(_minDepth + baseDepth()),
+ _list(),
+ _rowDepth(1, _minDepth),
+ _colWidth(1, _minWidth),
+ _finished(false)
+{
+ _objtype |= GRIDOBJ;
+ _defs = defaults;
+}
+
+void
+GridObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ int i, j;
+ int totalWidth;
+ int totalDepth;
+ float xShift = width() / 2.0;
+ float zShift = depth() / 2.0;
+ QVector<int> rowPos(rows());
+ QVector<int> colPos(cols());
+
+ assert(_finished == true);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "\nGridObj(" << width() << "x" << depth() << ")@"
+ << col() << "," << row()
+ << "::setTran ("
+ << xTran << ", " << zTran << ", "
+ << setWidth << ", " << setDepth << ")" << endl;
+#endif
+
+ BaseObj::setBaseSize(width(), depth());
+ BaseObj::setTran(xTran + xShift, zTran + zShift, setWidth, setDepth);
+
+ colPos[0] = 0;
+ for (i = 1; i < cols(); i++) {
+ colPos[i] = colPos[i-1] + _colWidth[i-1];
+ }
+ rowPos[0] = 0;
+ for (i = 1; i < rows(); i++) {
+ rowPos[i] = rowPos[i-1] + _rowDepth[i-1];
+ }
+
+ for (i = 0; i < _list.size(); i++) {
+ GridItem &item = _list[i];
+ totalWidth = totalDepth = 0;
+ for (j = item._col; j < item._col + item._item->cols(); j++)
+ totalWidth += _colWidth[j];
+ for (j = item._row; j < item._row + item._item->rows(); j++)
+ totalDepth += _rowDepth[j];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "GridObj::setTran: [" << i << "] at " << item._col
+ << ',' << item._row << ": ";
+#endif
+
+ item._item->setTran(colPos[item._col] - xShift + borderX(),
+ rowPos[item._row] - zShift + borderZ(),
+ totalWidth, totalDepth);
+ }
+}
+
+int
+addRowCols(QVector<int> & ivec, int nsize, int min)
+{
+ int extra = 0;
+
+ if (nsize > ivec.size()) {
+ extra = min * (nsize - ivec.size());
+
+ ivec.resize(nsize);
+ }
+
+ return extra;
+}
+
+void
+GridObj::addObj(ViewObj *obj, int col, int row)
+{
+ int i;
+ GridItem newItem;
+
+ for (i = 0; i < _list.size(); i++) {
+ const GridItem &item = _list[i];
+ if ((row >= item._row && row < (item._row + item._item->rows())) &&
+ (col >= item._col && col < (item._col + item._item->cols()))) {
+ pmprintf("%s: %s at %d,%d (%dx%d) collides with %s at %d,%d (%dx%d)\nThe later object will be ignored\n",
+ pmProgname, item._item->name(), item._col,
+ item._row, item._item->cols(), item._item->rows(),
+ obj->name(), col, row, obj->cols(), obj->rows());
+ return;
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::addObj: Adding item " << _list.length() << ": "
+ << obj->name() << ", size = " << obj->width() << 'x'
+ << obj->depth() << endl;
+#endif
+
+ newItem._item = obj;
+ newItem._row = row;
+ newItem._col = col;
+ _list.append(newItem);
+
+ // Add extra cols & rows as necessary
+ _width += addRowCols (_colWidth, col + obj->cols(), _minWidth);
+ _depth += addRowCols (_rowDepth, row + obj->rows(), _minDepth);
+
+
+ // Fasttrack size adjustments for simple objects
+ if (obj->cols() == 1 && _colWidth[col] < obj->width()) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::addObj: increasing col[" << col << "] from "
+ << _colWidth[col] << " to " << obj->width() << endl;
+#endif
+ _width += obj->width() - _colWidth[col];
+ _colWidth[col] = obj->width();
+ }
+
+ if (obj->rows() == 1 && _rowDepth[row] < obj->depth()) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::addObj: increasing row[" << row << "] from "
+ << _rowDepth[row] << " to " << obj->depth() << endl;
+#endif
+ _depth += obj->depth() - _rowDepth[row];
+ _rowDepth[row] = obj->depth();
+ }
+}
+
+void
+GridObj::finishedAdd()
+{
+ int i, j;
+ int size;
+ int current;
+ int adjust;
+
+ BaseObj::addBase(_root);
+
+ for (i= 0; i < _list.size(); i++) {
+ const GridItem &item = _list[i];
+ _root->addChild(item._item->root());
+ if (item._item->modObj() != NULL) {
+ BaseObj::add(item._item->modObj());
+ }
+
+ if (item._item->cols() > 1) {
+ size = item._item->width();
+ for (j = item._col, current = 0;
+ j < item._col + item._item->cols();
+ j++) {
+ current += _colWidth[j];
+ }
+
+ if (current < size) {
+ size -= current;
+ adjust = (int)((size / (float)item._item->cols()) + 0.5);
+ for (j = item._col;
+ j < item._col + item._item->cols() && adjust > 0; j++) {
+ if (adjust > size) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::finishedAdd: increasing col["
+ << j << "] from " << _colWidth[j] << " to "
+ << _colWidth[j] + size << endl;
+#endif
+ _colWidth[j] += size;
+ _width+= size;
+ adjust = 0;
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::finishedAdd: increasing col["
+ << j << "] from " << _colWidth[j] << " to "
+ << _colWidth[j] + adjust << endl;
+#endif
+ _colWidth[j] += adjust;
+ _width += adjust;
+ size -= adjust;
+ }
+ }
+ }
+ }
+
+ if (item._item->rows() > 1) {
+ size = item._item->depth();
+ for (j = item._row, current = 0;
+ j < item._row + item._item->rows();
+ j++) {
+ current += _rowDepth[j];
+ }
+
+ if (current < size) {
+ size -= current;
+ adjust = (int)((size / (float)item._item->rows()) + 0.5);
+ for (j = item._row;
+ j < item._row + item._item->rows() && adjust > 0; j++) {
+ if (adjust > size) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::finishedAdd: increasing row["
+ << j << "] from " << _rowDepth[j] << " to "
+ << _rowDepth[j] + size << endl;
+#endif
+ _rowDepth[j] += size;
+ _depth+= size;
+ adjust = 0;
+ }
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::finishedAdd: increasing row["
+ << j << "] from " << _rowDepth[j] << " to "
+ << _rowDepth[j] + adjust << endl;
+#endif
+ _rowDepth[j] += adjust;
+ _depth += adjust;
+ size -= adjust;
+ }
+ }
+ }
+ }
+ }
+
+ _finished = true;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "GridObj::finishedAdd: " << *this << endl;
+#endif
+}
+
+QTextStream&
+operator<<(QTextStream& os, GridObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+GridObj::display(QTextStream& os) const
+{
+ int i;
+ int sum;
+
+ BaseObj::display(os);
+ os << ", minRowDepth = " << _minDepth << ", minColWidth = "
+ << _minWidth << ", rows = " << rows() << ", cols = " << cols()
+ << ", finishedAdd = " << (_finished == true ? "true" : "false")
+ << endl;
+
+ os << "Column widths: ";
+ for (i = 0, sum = 0; i < _colWidth.size(); i++) {
+ os << _colWidth[i] << ' ';
+ sum += _colWidth[i];
+ }
+ os << endl;
+ assert(_width == sum + baseWidth());
+
+ os << "Row depths: ";
+ for (i = 0, sum = 0; i < _rowDepth.size(); i++) {
+ os << _rowDepth[i] << ' ';
+ sum += _rowDepth[i];
+ }
+ os << endl;
+ assert(_depth == sum + baseDepth());
+
+ for (i = 0; i < _list.size(); i++)
+ os << '[' << i << "] at " << _list[i]._col << ',' << _list[i]._row
+ << ": " << *(_list[i]._item) << endl;
+}
diff --git a/src/pmview/gridobj.h b/src/pmview/gridobj.h
new file mode 100644
index 0000000..f19dbd4
--- /dev/null
+++ b/src/pmview/gridobj.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _GRIDOBJ_H_
+#define _GRIDOBJ_H_
+
+#include "baseobj.h"
+#include "defaultobj.h"
+#include <QtCore/QVector>
+
+struct GridItem
+{
+ ViewObj *_item;
+ int _row;
+ int _col;
+};
+
+typedef QList<GridItem> GridList;
+
+class GridObj : public BaseObj
+{
+protected:
+
+ int _minDepth;
+ int _minWidth;
+ int _width;
+ int _depth;
+ GridList _list;
+ QVector<int> _rowDepth;
+ QVector<int> _colWidth;
+ bool _finished;
+ DefaultObj _defs;
+
+ static int theDefRowDepth;
+ static int theDefColWidth;
+
+public:
+
+ virtual ~GridObj();
+
+ GridObj(bool onFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols = 1, int rows = 1,
+ GridObj::Alignment align = GridObj::center);
+
+ DefaultObj * defs() { return & _defs; }
+
+ int numObj() const
+ { return _list.size(); }
+ int minDepth() const
+ { return _minDepth; }
+ int minWidth() const
+ { return _minWidth; }
+ int rows() const
+ { return _rowDepth.size(); }
+ int cols() const
+ { return _colWidth.size(); }
+
+ // Local changes
+ int &minDepth()
+ { return _minDepth; }
+ int &minWidth()
+ { return _minWidth; }
+
+ virtual int width() const
+ { return _width; }
+ virtual int depth() const
+ { return _depth; }
+
+ virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ void addObj(ViewObj *obj, int col, int row);
+
+ virtual void finishedAdd();
+
+ // Output
+ virtual void display(QTextStream& os) const;
+
+ virtual const char* name() const
+ { return "Grid"; }
+
+ friend QTextStream& operator<<(QTextStream& os, GridObj const& rhs);
+
+protected:
+
+ void add(Modulate *mod);
+
+private:
+
+ GridObj();
+ GridObj(GridObj const &);
+ GridObj const& operator=(GridObj const &);
+};
+
+#endif /* _GRIDOBJ_H_ */
diff --git a/src/pmview/icon b/src/pmview/icon
new file mode 100644
index 0000000..e1650fb
--- /dev/null
+++ b/src/pmview/icon
Binary files differ
diff --git a/src/pmview/labelobj.cpp b/src/pmview/labelobj.cpp
new file mode 100644
index 0000000..c8ec594
--- /dev/null
+++ b/src/pmview/labelobj.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+
+#include "pmview.h"
+#include "labelobj.h"
+#include "defaultobj.h"
+
+LabelObj::~LabelObj()
+{
+ if (_text)
+ delete _text;
+}
+
+LabelObj::LabelObj(const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ ViewObj::Alignment align)
+: ViewObj(x, y, cols, rows, align),
+ _str(),
+ _text(0),
+ _dir(Text::right),
+ _fontSize(Text::medium),
+ _margin(defaults.labelMargin())
+{
+ _objtype |= LABELOBJ;
+
+ _color[0] = defaults.labelColor(0);
+ _color[1] = defaults.labelColor(1);
+ _color[2] = defaults.labelColor(2);
+}
+
+LabelObj::LabelObj(Text::Direction dir,
+ Text::FontSize fontSize,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ ViewObj::Alignment align)
+: ViewObj(x, y, cols, rows, align),
+ _str(),
+ _text(0),
+ _dir(dir),
+ _fontSize(fontSize),
+ _margin(defaults.labelMargin())
+{
+ _color[0] = defaults.labelColor(0);
+ _color[1] = defaults.labelColor(1);
+ _color[2] = defaults.labelColor(2);
+}
+
+void
+LabelObj::finishedAdd()
+{
+ _text = new Text(_str, _dir, _fontSize);
+ SoBaseColor *base = new SoBaseColor;
+ base->rgb.setValue(_color[0], _color[1], _color[2]);
+ _root->addChild(base);
+ _root->addChild(_text->root());
+}
+
+void
+LabelObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ switch(_text->dir()) {
+ case Text::right:
+ ViewObj::setTran(xTran + width() - _margin,
+ zTran + _margin,
+ setWidth, setDepth);
+ break;
+ case Text::down:
+ ViewObj::setTran(xTran + _margin,
+ zTran + depth() - _margin,
+ setWidth, setDepth);
+ break;
+ default:
+ ViewObj::setTran(xTran + _margin,
+ zTran + _margin,
+ setWidth, setDepth);
+ break;
+ }
+}
+
+QTextStream&
+operator<<(QTextStream& os, LabelObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+LabelObj::display(QTextStream& os) const
+{
+ ViewObj::display(os);
+ os << ", label = \"" << _str << "\", margin = " << _margin << ", text = ";
+ if (_text == NULL)
+ os << "undefined";
+ else
+ os << *_text;
+}
+
diff --git a/src/pmview/labelobj.h b/src/pmview/labelobj.h
new file mode 100644
index 0000000..5a0f31c
--- /dev/null
+++ b/src/pmview/labelobj.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LABELOBJ_H_
+#define _LABELOBJ_H_
+
+#include "text.h"
+#include "viewobj.h"
+
+class SoNode;
+class SoSeparator;
+class SoTranslation;
+
+class LabelObj : public ViewObj
+{
+protected:
+
+ QString _str;
+ Text *_text;
+ Text::Direction _dir;
+ Text::FontSize _fontSize;
+ int _margin;
+ float _color[3];
+
+public:
+
+ virtual ~LabelObj();
+
+ LabelObj(Text::Direction dir,
+ Text::FontSize fontSize,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols = 1, int rows = 1,
+ Alignment align = center);
+
+ LabelObj(const DefaultObj &defaults,
+ int x, int y,
+ int cols = 1, int rows = 1,
+ Alignment align = center);
+
+ const QString &str() const
+ { return _str; }
+ Text::Direction dir() const
+ { return _dir; }
+ Text::FontSize size() const
+ { return _fontSize; }
+ int margin() const
+ { return _margin; }
+ float color(int i) const
+ { return _color[i]; }
+
+ // Local Changes
+ QString &str()
+ { return _str; }
+ Text::Direction &dir()
+ { return _dir; }
+ Text::FontSize &size()
+ { return _fontSize; }
+ int &margin()
+ { return _margin; }
+ void color(float r, float g, float b)
+ { _color[0] = r; _color[1] = g; _color[2] = b; }
+
+ virtual int width() const
+ { return _text->width() + (_margin * 2); }
+ virtual int depth() const
+ { return _text->depth() + (_margin * 2); }
+
+ virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ virtual void finishedAdd();
+
+ // Output
+ virtual void display(QTextStream& os) const;
+
+ virtual const char* name() const
+ { return "Label"; }
+
+ friend QTextStream& operator<<(QTextStream& os, LabelObj const& rhs);
+
+private:
+
+ LabelObj();
+ LabelObj(LabelObj const &);
+ LabelObj const& operator=(LabelObj const &);
+};
+
+#endif /* _LABELOBJ_H_ */
diff --git a/src/pmview/launch.cpp b/src/pmview/launch.cpp
new file mode 100644
index 0000000..3e2d2f6
--- /dev/null
+++ b/src/pmview/launch.cpp
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 1997-2002 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "launch.h"
+#include "colorscale.h"
+#include "metriclist.h"
+
+#include <iostream>
+using namespace std;
+
+#define PM_LAUNCH_VERSION1 1
+#define PM_LAUNCH_VERSION2 2
+
+const QString Launch::theVersion1Str = "pmlaunch Version 1.0\n";
+const QString Launch::theVersion2Str = "pmlaunch Version 2.0\n";
+
+Launch::~Launch()
+{
+}
+
+Launch::Launch(const QString &version)
+: _strings(),
+ _groupMetric(-1),
+ _groupCount(0),
+ _groupHint(),
+ _metricCount(0)
+{
+ if (version == "1.0")
+ _version = PM_LAUNCH_VERSION1;
+ else
+ _version = PM_LAUNCH_VERSION2;
+}
+
+Launch::Launch(const Launch &rhs)
+: _strings(rhs._strings),
+ _groupMetric(rhs._groupMetric),
+ _groupCount(rhs._groupCount),
+ _groupHint(rhs._groupHint),
+ _metricCount(rhs._metricCount),
+ _version(rhs._version)
+{
+}
+
+const Launch &
+Launch::operator=(const Launch &rhs)
+{
+ if (this != &rhs) {
+ _strings = rhs._strings;
+ _groupMetric = rhs._groupMetric;
+ _groupCount = rhs._groupCount;
+ _groupHint = rhs._groupHint;
+ _metricCount = rhs._metricCount;
+ }
+ return *this;
+}
+
+void
+Launch::setDefaultOptions(int interval,
+ int debug,
+ const char *timeport,
+ const char *starttime,
+ const char *endtime,
+ const char *offset,
+ const char *timezone,
+ const char *defsourcetype,
+ const char *defsourcename,
+ bool selected)
+{
+ addOption("interval", (interval < 0 ? -interval : interval));
+ addOption("debug", debug);
+
+ if (timeport != NULL)
+ addOption("timeport", timeport);
+ if (starttime != NULL)
+ addOption("starttime", starttime);
+ if (endtime != NULL)
+ addOption("endtime", endtime);
+ if (offset != NULL)
+ addOption("offset", offset);
+ if (timezone != NULL)
+ addOption("timezone", timezone);
+ if (defsourcetype != NULL)
+ addOption("defsourcetype", defsourcetype);
+ if (defsourcename != NULL)
+ addOption("defsourcename", defsourcename);
+ if (pmProgname != NULL)
+ addOption("progname", pmProgname);
+ addOption("pid", (int)getpid());
+
+ if (selected)
+ addOption("selected", "true");
+ else
+ addOption("selected", "false");
+}
+
+void
+Launch::addOption(const char *name, const char *value)
+{
+ QString str = "option ";
+
+ if (name == NULL)
+ return;
+
+ if (value == NULL) {
+ str.append(name).append("=\n");
+ }
+ else {
+ str.append(name).append('=').append(value).append('\n');
+ str.append('\n');
+ }
+ _strings.append(str);
+}
+
+void
+Launch::addOption(const char *name, int value)
+{
+ QString str = "option ";
+
+ if (name == NULL)
+ return;
+
+ str.append(name).append('=').setNum(value).append('\n');
+ _strings.append(str);
+}
+
+void
+Launch::addMetric(const QmcMetric &metric,
+ const SbColor &color,
+ int instance,
+ bool useSocks)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ cerr << "Launch::addMetric(1): Adding ";
+ metric.dump(cerr, true, instance);
+ cerr << " (" << _metricCount << ')' << endl;
+ }
+#endif
+
+ QmcSource source = metric.context()->source();
+ QByteArray ba;
+ ba = metric.instName(instance).toLocal8Bit();
+
+ addMetric(metric.context()->handle(), source.source(), source.host(), metric.name(),
+ metric.hasInstances() == 0 ? NULL : ba.data(),
+ metric.desc(), color, metric.scale(), useSocks);
+}
+
+void
+Launch::addMetric(const QmcMetric &metric,
+ const ColorScale &scale,
+ int instance,
+ bool useSocks)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ cerr << "Launch::addMetric(2): Adding ";
+ metric.dump(cerr, true, instance);
+ cerr << " (" << _metricCount << ')' << endl;
+ }
+#endif
+
+ QmcSource source = metric.context()->source();
+ QByteArray ba;
+ ba = metric.instName(instance).toLocal8Bit();
+
+ addMetric(metric.context()->handle(), source.source(), source.host(), metric.name(),
+ metric.hasInstances() == 0 ? NULL : ba.data(),
+ metric.desc(), scale, useSocks);
+}
+
+void
+Launch::addMetric(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ const char *instance,
+ const QmcDesc &desc,
+ const SbColor &color,
+ double scale,
+ bool useSocks)
+{
+ QString str(256);
+ QString col;
+
+ if (_groupMetric == -1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Launch::addMetric: Called before startGroup."
+ << " Adding a group." << endl;
+#endif
+ startGroup();
+ }
+
+ preColor(context, source, host, metric, useSocks, str);
+
+ str.append(" S ");
+ MetricList::toString(color, col);
+ str.append(col).append(',').setNum(scale).append(' ');
+
+ postColor(desc, instance, str);
+ _strings.append(str);
+}
+
+void
+Launch::addMetric(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ const char *instance,
+ const QmcDesc &desc,
+ const ColorScale &scale,
+ bool useSocks)
+{
+ int i;
+ QString str(128);
+ QString col;
+
+ if (_groupMetric == -1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Launch::addMetric: Called before startGroup."
+ << " Adding a group." << endl;
+#endif
+ startGroup();
+ }
+
+ preColor(context, source, host, metric, useSocks, str);
+ str.append(" D ");
+
+ MetricList::toString(scale[0].color(), col);
+ str.append(col).append(',').setNum(scale[0].min());
+ for (i = 1; i < scale.numSteps(); i++) {
+ MetricList::toString(scale[i].color(), col);
+ str.append(',').append(col).append(',');
+ str.setNum(scale[i].min());
+ }
+ str.append(' ');
+
+ postColor(desc, instance, str);
+ _strings.append(str);
+}
+
+void
+Launch::preColor(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ bool useSocks,
+ QString &str)
+{
+ str.append("metric ").setNum(_metricCount++).append(' ');
+ str.setNum(_groupCount).append(' ').append(_groupHint);
+
+ switch(context) {
+ case PM_CONTEXT_LOCAL:
+ str.append(" l ");
+ break;
+ case PM_CONTEXT_HOST:
+ str.append(" h ");
+ break;
+ case PM_CONTEXT_ARCHIVE:
+ str.append(" a ");
+ break;
+ }
+
+ str.append(source).append(' ');
+
+ if (context == PM_CONTEXT_ARCHIVE)
+ str.append(host);
+ else if (useSocks)
+ str.append("true");
+ else
+ str.append("false");
+
+ str.append(' ').append(metric);
+}
+
+void
+Launch::postColor(const QmcDesc &desc,
+ const char *instance,
+ QString &str)
+{
+ const pmDesc d = desc.desc();
+
+ if (_version == PM_LAUNCH_VERSION2) {
+ str.setNum(d.type).append(' ');
+ str.setNum(d.sem).append(' ');
+ str.setNum(d.units.scaleSpace).append(' ');
+ str.setNum(d.units.scaleTime).append(' ');
+ str.setNum(d.units.scaleCount).append(' ');
+ }
+
+ str.setNum(d.units.dimSpace).append(' ');
+ str.setNum(d.units.dimTime).append(' ');
+ str.setNum(d.units.dimCount).append(' ');
+ str.setNum((int)(d.indom)).append(" [");
+ if (instance != NULL)
+ str.append(instance);
+ str.append("]\n");
+}
+
+void
+Launch::startGroup(const char *hint)
+{
+
+ if (_groupMetric != -1)
+ cerr << "Launch::startGroup: Two groups started at once!" << endl;
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Launch::startGroup: Starting group " << _groupCount
+ << endl;
+#endif
+
+ _groupMetric = _metricCount;
+ _groupHint = hint;
+ }
+}
+
+void
+Launch::endGroup()
+{
+ if (_groupMetric == -1)
+ cerr << "Launch::endGroup: No group to end!" << endl;
+ else if (_groupMetric == _metricCount) {
+ cerr << "Launch::endGroup: No metrics added to group "
+ << _groupCount << endl;
+ _groupMetric = -1;
+ } else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Launch::endGroup: ending group " << _groupCount
+ << endl;
+#endif
+
+ _groupMetric = -1;
+ _groupCount++;
+ }
+}
+
+void
+Launch::append(Launch const &rhs)
+{
+ if (rhs._groupMetric != -1) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Launch::append: Group not finished in appended object."
+ << " Completing group" << endl;
+#endif
+
+ // Cast away const, yuk.
+ ((Launch *)(&rhs))->endGroup();
+ }
+ _strings.append(rhs._strings);
+}
+
+QTextStream&
+operator<<(QTextStream& os, Launch const& rhs)
+{
+ int i;
+ for (i = 0; i < rhs._strings.size(); i++)
+ os << rhs._strings[i];
+ return os;
+}
+
+const char *
+Launch::launchPath()
+{
+ char *env;
+ static char launch_path[MAXPATHLEN];
+
+ if ((env = getenv("PM_LAUNCH_PATH")) != NULL)
+ strncpy(launch_path, env, sizeof(launch_path));
+ else
+ snprintf(launch_path, sizeof(launch_path), "%s/config/pmlaunch", pmGetConfig("PCP_VAR_DIR"));
+ return launch_path;
+}
+
+#include <iostream>
+
+void
+Launch::output(int fd) const
+{
+ QByteArray ba;
+ int sts;
+ const char *str;
+
+ if (_version == PM_LAUNCH_VERSION2)
+ ba = theVersion2Str.toLocal8Bit();
+ else
+ ba = theVersion1Str.toLocal8Bit();
+
+ str = ba.data();
+ if ((sts = write(fd, str, strlen(str))) != (int)strlen(str)) {
+ cerr << "Launch::output: version write->" << sts
+ << " not " << strlen(str) << endl;
+ }
+
+ for (int i = 0; i < _strings.length(); i++) {
+ ba = _strings[i].toLocal8Bit();
+ str = ba.data();
+ if ((sts = write(fd, str, strlen(str))) != (int)strlen(str)) {
+ cerr << "Launch::output: string write->" << sts
+ << " not " << strlen(str)
+ << " for " << str << endl;
+ }
+ }
+}
diff --git a/src/pmview/launch.h b/src/pmview/launch.h
new file mode 100644
index 0000000..2f048b1
--- /dev/null
+++ b/src/pmview/launch.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LAUNCH_H_
+#define _LAUNCH_H_
+
+#include "main.h"
+#include <QtCore/QStringList>
+
+class SbColor;
+class ColorScale;
+
+class Launch
+{
+private:
+
+ static const QString theVersion1Str;
+ static const QString theVersion2Str;
+
+ QStringList _strings;
+ int _groupMetric;
+ int _groupCount;
+ QString _groupHint;
+ int _metricCount;
+ int _version;
+
+public:
+
+ ~Launch();
+
+ Launch(const QString &version = ""); // defaults to version 2.0
+ Launch(const Launch &rhs);
+ const Launch &operator=(const Launch &rhs);
+
+ static const char *launchPath();
+
+ void setDefaultOptions(int interval = 5,
+ int debug = 0,
+ const char *timeport = NULL,
+ const char *starttime = NULL,
+ const char *endtime = NULL,
+ const char *offset = NULL,
+ const char *timezone = NULL,
+ const char *defsourcetype = NULL,
+ const char *defsourcename = NULL,
+ bool selected = false);
+
+ void addOption(const char *name, const char *value);
+ void addOption(const char *name, int value);
+
+ void addMetric(const QmcMetric &metric,
+ const SbColor &color,
+ int instance,
+ bool useSocks = false);
+
+ void addMetric(const QmcMetric &metric,
+ const ColorScale &scale,
+ int instance,
+ bool useSocks = false);
+
+ void addMetric(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ const char *instance,
+ const QmcDesc &desc,
+ const SbColor &color,
+ double scale,
+ bool useSocks = false);
+ // Add metric with static color and scale
+
+ void addMetric(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ const char *instance,
+ const QmcDesc &desc,
+ const ColorScale &scale,
+ bool useSocks = false);
+ // Add metric with dynamic color range
+
+ void startGroup(const char *hint = "none");
+ void endGroup();
+
+ void append(Launch const& rhs);
+
+ void output(int fd) const;
+
+ friend QTextStream& operator<<(QTextStream& os, Launch const& rhs);
+
+private:
+
+ void preColor(int context,
+ const QString &source,
+ const QString &host,
+ const QString &metric,
+ bool useSocks,
+ QString &str);
+ void postColor(const QmcDesc &desc, const char *instance, QString &str);
+};
+
+#endif /* _LAUNCH_H_ */
diff --git a/src/pmview/lex.l b/src/pmview/lex.l
new file mode 100644
index 0000000..40b9285
--- /dev/null
+++ b/src/pmview/lex.l
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 1997-2001 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+%{
+
+#ifdef FLEX_SCANNER
+
+int pmviewFlexInput (char * buf, int ms);
+
+#undef YY_INPUT
+#define YY_INPUT(b,r,ms) (r=pmviewFlexInput(b, ms))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int yylex (void);
+#ifdef __cplusplus
+}
+#endif
+
+#else /* AT&T Lex */
+
+#undef input
+#undef unput
+#undef yywrap
+
+char input(void);
+void unput(char);
+
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "viewobj.h"
+#include "barobj.h"
+#include "text.h"
+#include "stackobj.h"
+#include "gridobj.h"
+#include "gram.h"
+
+extern void yywarn(const char *);
+extern void yyerror(const char *);
+extern FILE *theAltConfig;
+
+#ifdef YYLMAX
+#undef YYLMAX
+#endif
+
+#define YYLMAX 2048
+
+#ifdef LEX_DEBUG
+#define RETURN(c) if (pmDebug & DBG_TRACE_APPL2) { pmprintf("Lex: %d\n", c); pmflush (); } return c
+#else
+#define RETURN(c) if (theAltConfig) fprintf(theAltConfig, "%s", yytext); return c
+#endif
+
+static void lexerror (const char * msg, const char * yytext);
+%}
+
+
+%%
+
+[-]?[0-9]+[\.][0-9]+ {
+ yylval.y_real = strtod(yytext, (char **)0);
+ RETURN(REAL);
+ }
+
+0[xX][0-9]+ {
+ yylval.y_int = (int)strtol(&yytext[2], (char **)0, 16);
+ RETURN(INT);
+ }
+
+[-]?[0-9]+ {
+ yylval.y_int = atoi(yytext);
+ RETURN(INT);
+ }
+
+[Pp][Mm][Vv][Ii][Ee][Ww][\t ]+[vV]ersion[\t ]+[0-9]+\.[0-9]+ {
+ yylval.y_str = strdup (yytext+15);
+ RETURN(PMVIEW);
+ }
+
+_grid { RETURN(GRID); }
+_bar { RETURN(BAR); }
+_labeledbar { RETURN(BAR); }
+_stack { RETURN(STACK); }
+_util { RETURN(UTIL); }
+_metrics { RETURN(METRICLIST); }
+_color[lL]ist { RETURN(COLORLIST); }
+_color[sS]cale { RETURN(COLORSCALE); }
+_label { RETURN(LABEL); }
+_metric[lL]abels { RETURN(METRICLABEL); }
+_inst[lL]abels { RETURN(INSTLABEL); }
+_direction { RETURN(DIRECTION); }
+_size { RETURN(SIZE); }
+_text { RETURN(TEXT); }
+_scale { RETURN(SCALE); }
+_history { RETURN(HISTORY); }
+_bar[lL]ength { RETURN(BAR_LENGTH); }
+_bar[xX][mM]argin { RETURN(MARGIN_WIDTH); }
+_bar[yY][mM]argin { RETURN(MARGIN_DEPTH); }
+_margin[wW]idth { RETURN(MARGIN_WIDTH); }
+_margin[dD]epth { RETURN(MARGIN_DEPTH); }
+_bar[hH]eight { RETURN(BAR_HEIGHT); }
+_bar[bB]ase[hH]eight { RETURN(BASE_HEIGHT); }
+_base[hH]eight { RETURN(BASE_HEIGHT); }
+_stack[lL]ength { RETURN(BAR_LENGTH); }
+_stack[xX][mM]argin { RETURN(MARGIN_WIDTH); }
+_stack[yY][mM]argin { RETURN(MARGIN_DEPTH); }
+_stack[hH]eight { RETURN(BAR_HEIGHT); }
+_stack[bB]ase[hH]eight { RETURN(BASE_HEIGHT); }
+_grid[sS]pace { RETURN(GRID_SPACE); }
+_grid[wW]idth { RETURN(GRID_WIDTH); }
+_grid[dD]epth { RETURN(GRID_DEPTH); }
+_grid[hH]eight { RETURN(BASE_HEIGHT); }
+_grid[cC]olor { RETURN(BASE_COLOR); }
+_base[cC]olor { RETURN(BASE_COLOR); }
+_gap[wW]idth { RETURN(GAP_WIDTH); }
+_gap[dD]epth { RETURN(GAP_DEPTH); }
+_gap[lL]abel { RETURN(GAP_LABEL); }
+_label[mM]argin { RETURN(LABEL_MARGIN); }
+_label[cC]olor { RETURN(LABEL_COLOR); }
+_stack[lL]abel { RETURN(STACK_LABEL); }
+_base[lL]abel { RETURN(BASE_LABEL); }
+_box { RETURN(BOX); }
+_pipe[Ll]ength { RETURN(PIPE_LENGTH); }
+_pipe { RETURN(PIPE); }
+_pipe[Tt]ag { RETURN(PIPETAG); }
+_link { RETURN(GR_LINK); }
+_xing { RETURN(GR_XING); }
+_sceneFile { RETURN(SCENE_FILE); }
+
+[_]?[nN] { yylval.y_align = ViewObj::north; RETURN(ALIGN); }
+[_]?north { yylval.y_align = ViewObj::north; RETURN(ALIGN); }
+[_]?[sS] { yylval.y_align = ViewObj::south; RETURN(ALIGN); }
+[_]?south { yylval.y_align = ViewObj::south; RETURN(ALIGN); }
+[_]?[eE] { yylval.y_align = ViewObj::east; RETURN(ALIGN); }
+[_]?east { yylval.y_align = ViewObj::east; RETURN(ALIGN); }
+[_]?[wW] { yylval.y_align = ViewObj::west; RETURN(ALIGN); }
+[_]?west { yylval.y_align = ViewObj::west; RETURN(ALIGN); }
+[_]?[nN][wW] { yylval.y_align = ViewObj::northWest; RETURN(ALIGN); }
+[_]?northwest { yylval.y_align = ViewObj::northWest; RETURN(ALIGN); }
+[_]?[nN][eE] { yylval.y_align = ViewObj::northEast; RETURN(ALIGN); }
+[_]?northeast { yylval.y_align = ViewObj::northEast; RETURN(ALIGN); }
+[_]?[sS][wW] { yylval.y_align = ViewObj::southWest; RETURN(ALIGN); }
+[_]?southwest { yylval.y_align = ViewObj::southWest; RETURN(ALIGN); }
+[_]?[sS][eE] { yylval.y_align = ViewObj::southEast; RETURN(ALIGN); }
+[_]?southeast { yylval.y_align = ViewObj::southEast; RETURN(ALIGN); }
+[_]?center { yylval.y_align = ViewObj::center; RETURN(ALIGN); }
+
+[_]?up { yylval.y_textDir = Text::up; RETURN(DIRVAL); }
+[_]?down { yylval.y_textDir = Text::down; RETURN(DIRVAL); }
+[_]?left { yylval.y_textDir = Text::left; RETURN(DIRVAL); }
+[_]?right { yylval.y_textDir = Text::right; RETURN(DIRVAL); }
+
+[_]?small { yylval.y_fontSize = Text::small; RETURN(SIZ); }
+[_]?medium { yylval.y_fontSize = Text::medium; RETURN(SIZ); }
+[_]?large { yylval.y_fontSize = Text::large; RETURN(SIZ); }
+
+[_]?show { yylval.y_bool = true; RETURN(BOOL); }
+[_]?hide { yylval.y_bool = false; RETURN(BOOL); }
+
+[_]?towards { yylval.y_labelDir = BarObj::towards; RETURN(LABEL_DIR); }
+[_]?away { yylval.y_labelDir = BarObj::away; RETURN(LABEL_DIR); }
+
+[_]?row { yylval.y_instDir = BarMod::instPerRow; RETURN(INST_DIR); }
+[_]?col { yylval.y_instDir = BarMod::instPerCol; RETURN(INST_DIR); }
+
+_cube { yylval.y_shape = ViewObj::cube; RETURN(SHAPE); }
+_cylinder { yylval.y_shape = ViewObj::cylinder; RETURN(SHAPE); }
+
+_[yY]mod { yylval.y_barMod = BarMod::yScale; RETURN(BAR_TYPE); }
+_color[mM]od { yylval.y_barMod = BarMod::color; RETURN(BAR_TYPE); }
+_color[yY][mM]od { yylval.y_barMod = BarMod::colYScale; RETURN(BAR_TYPE); }
+
+_util[mM]od { yylval.y_stackMod = StackMod::util; RETURN(STACK_TYPE); }
+_fill[mM]od { yylval.y_stackMod = StackMod::fixed; RETURN(STACK_TYPE); }
+
+_group[bB]y[rR]ow { yylval.y_barGroup = BarMod::groupByRow; RETURN(GROUP); }
+_group[bB]y[cC]ol { yylval.y_barGroup = BarMod::groupByCol; RETURN(GROUP); }
+_group[bB]y[mM]etric { yylval.y_barGroup = BarMod::groupByMetric; RETURN(GROUP); }
+_group[bB]y[iI]nst { yylval.y_barGroup = BarMod::groupByInst; RETURN(GROUP); }
+
+[A-Za-z0-9_./\-,:?=+&]+(\[[ \t]*((\"[^\"\n]*\"|[^\"\n\]]*)[ \t]*\,?[ \t]*)+[ \t]*\]) {
+ /*
+ * Metric could be in the form: name + quoted instance or
+ * name + non-quoted instance (i,e no " between the [ ] ) or
+ * just name with no instance spec. Name with no instance spec
+ * is delegated to the NAME token, the rest is handled here.
+ */
+ yylval.y_str = strdup (yytext);
+ RETURN(METRIC);
+ }
+
+[A-Za-z0-9_\.\/\-\,:\?\=\+\&]+ {
+ /*
+ * While parsing, NAME token can be used both in general context
+ * or as name for METRIC. I don't want to do context-specific
+ * lexical analisys, so NAME is restricted to the list of chars
+ * above and it cannot have opening or closing square brackets
+ */
+ yylval.y_str = strdup (yytext);
+ RETURN(NAME);
+ }
+
+\"[^\"\n][^\"\n]*\" {
+ yylval.y_str = (char *)malloc(yyleng-1);
+ strncpy(yylval.y_str, &yytext[1], yyleng-2);
+ yylval.y_str[yyleng-2] = '\0';
+ RETURN(STRING);
+ }
+
+\"[^\"\n][^\"\n]*\n {
+ lexerror("Expected closing \"", yytext);
+ /*NOTREACHED*/
+ }
+
+\( { RETURN(OPENB); }
+\) { RETURN(CLOSEB); }
+
+\#.*\n {}
+\n { if (theAltConfig) fputc('\n', theAltConfig); }
+
+[\t ]+ { if (theAltConfig) fputc(' ', theAltConfig); }
+
+. {
+ sprintf(theBuffer, "Illegal character '%c'", yytext[0]);
+ lexerror(theBuffer, yytext);
+ /*NOTREACHED*/
+ }
+%%
+
+static char *line;
+static int linepos;
+static int linelen;
+static int mark = -1;
+
+extern int lineNum;
+
+#ifdef FLEX_SCANNER
+static int in = '\0';
+
+int yywrap(void)
+{
+ return in == EOF;
+}
+
+int
+pmviewFlexInput(char *buf, int ms)
+{
+ if (in == '\n') {
+ lineNum++;
+ linepos=0;
+ }
+
+ if (linepos >= linelen - 1) {
+ if (linelen == 0)
+ linelen = 128;
+ else
+ linelen *= 2;
+ line = (char *)realloc(line, linelen * sizeof(char));
+ }
+
+ if (ms > 0) {
+ if ((in = fgetc(yyin)) != EOF) {
+ line[linepos++] = buf[0] = in & 0xFFU;
+ ms = 1;
+ } else {
+ ms = 0;
+ }
+ }
+
+ return ms;
+}
+
+void
+skipAhead(void)
+{
+ while ((in != '\n') && (in != EOF)) {
+ char c;
+ pmviewFlexInput(&c, 1);
+ }
+}
+
+char
+input(void)
+{
+ char c;
+ if (pmviewFlexInput(&c, 1))
+ return c;
+ return 0;
+}
+
+char
+lastinput(void)
+{
+ return (in & 0xFFU);
+}
+
+#else
+
+static char lastc = 'A';
+static char peekc = '\0';
+
+extern int lineNum;
+
+extern FILE *yyin;
+
+int
+yywrap(void)
+{
+ return lastc == '\0';
+}
+
+char
+input(void)
+{
+ int get;
+
+ if (peekc != '\0') {
+ lastc = peekc;
+ peekc = '\0';
+ return lastc;
+ }
+
+ if (lastc == '\n') {
+ lineNum++;
+ linepos = 0;
+ }
+ else if (lastc == '\0') {
+ linepos = 0;
+ return lastc;
+ }
+
+ if (linepos >= linelen - 1) {
+ if (linelen == 0)
+ linelen = 128;
+ else
+ linelen *= 2;
+ line = (char *)realloc(line, linelen * sizeof(char));
+ }
+
+ get = fgetc(yyin);
+
+ if (get == EOF)
+ lastc = '\0';
+ else
+ lastc = get;
+
+ line[linepos++] = (char)get;
+
+ return lastc;
+}
+
+void
+unput(char c)
+{
+ peekc = c;
+}
+
+char
+lastinput(void)
+{
+ return lastc;
+}
+
+#endif
+
+int
+markpos(void)
+{
+ mark = linepos;
+ return mark;
+}
+
+int
+locateError(void)
+{
+ int i;
+
+ if (mark < 0) {
+ pmprintf("%s: Unrecoverable internal error in locateError()\n",
+ pmProgname);
+ pmflush();
+ exit(1);
+ }
+
+ if (feof(yyin)) {
+ return 0;
+ }
+
+ for (i = 0; i < mark; i++)
+ if (line[i] == '\n' || line[i] == '\0')
+ break;
+ else
+ pmprintf("%c", line[i]);
+ pmprintf("\n");
+
+ for (i = 0; i < mark - 1; i++)
+ if (line[i] == '\n' || line[i] == '\0')
+ break;
+ else if (line[i] == '\t')
+ pmprintf("\t");
+ else
+ pmprintf(" ");
+
+ pmprintf("^ at or near here\n");
+
+ return 1;
+}
+
+/* This is 'die now' version of yyerror - static and only used for errors
+ * in lexer, i.e those, where we've got no reasonable chance to do any
+ * recovery. */
+static void
+lexerror(const char * msg, const char * yytext)
+{
+ char *pos;
+
+ if ((pos = strstr(line, yytext)) != NULL) {
+ int i;
+
+ for (i = 0; (line[i] != '\n') && (line[i] != '\0'); i++)
+ pmprintf("%c", line[i]);
+ pmprintf("\n");
+
+ for (i = 0; line+i != pos; i++) {
+ if (line[i] == '\n' || line[i] == '\0')
+ break;
+ else if (line[i] == '\t')
+ pmprintf("\t");
+ else
+ pmprintf(" ");
+ }
+ pmprintf("^ at or near here\n");
+ }
+
+ pmprintf("%s: Fatal error in configuration file at line %d:\n\t%s\n",
+ pmProgname, lineNum + 1, msg);
+
+ pmflush();
+ exit(1);
+}
diff --git a/src/pmview/link.cpp b/src/pmview/link.cpp
new file mode 100644
index 0000000..bddfb0c
--- /dev/null
+++ b/src/pmview/link.cpp
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoRotationXYZ.h>
+#include <Inventor/nodes/SoCylinder.h>
+#include <Inventor/nodes/SoSphere.h>
+#include <Inventor/nodes/SoBaseColor.h>
+
+#include "togglemod.h"
+#include "link.h"
+#include "defaultobj.h"
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ * Constructor for the Link.
+ * Note that we use different alignment for the base than the one
+ * for the scene. Base is always centered and we get the visual
+ * effect by addjusting the height of the cylinder(s).
+ \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+Link::Link (const DefaultObj & defs,
+ int c, int r,
+ int colSpan, int rowSpan,
+ Alignment a)
+ : BaseObj (false, defs, c, r, colSpan, rowSpan, center)
+ , _tag ("\n")
+{
+ _objtype |= LINK;
+
+ for ( int i=0; i < 3; i++) {
+ _color[i] = defs.baseColor(i);
+ }
+
+ align = a;
+ c1 = c2 = 0;
+ cellHeight = defs.baseHeight();
+ cellDepth = defs.baseHeight();
+ cellWidth = defs.baseHeight();
+
+ _width = colSpan * cellWidth;
+ _depth = rowSpan * cellDepth;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ *
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void
+Link::finishedAdd ()
+{
+ BaseObj::addBase (_root);
+
+ SoBaseColor * color = new SoBaseColor;
+ color->rgb.setValue (_color);
+ _root->addChild (color);
+
+ float radius = cellHeight * 0.4;
+
+ SoSeparator * mainsep = new SoSeparator;
+
+ SoTranslation * toCenter = new SoTranslation;
+ toCenter->translation.setValue (_width/2.0, cellHeight/2.0, _depth/2.0);
+ mainsep->addChild (toCenter);
+
+ if ( align == northEast || align == northWest ||
+ align == southEast || align == southWest ) {
+ SoRotationXYZ * mrot = new SoRotationXYZ;
+ mrot->axis = SoRotationXYZ::Y;
+ switch (align) {
+ case northEast:
+ mrot->angle = 0;
+ break;
+
+ case northWest:
+ mrot->angle = M_PI/2;
+ break;
+
+ case southWest:
+ mrot->angle = M_PI;
+ break;
+
+ case southEast:
+ mrot->angle = -(M_PI/2);
+ break;
+
+ default:
+ break;
+ }
+ mainsep->addChild (mrot);
+
+
+ // L-shape link - could leave without a separator, but
+ // geometry calculation becomes messy, so just add one.
+ SoSeparator * sp = new SoSeparator;
+
+ SoSphere * s = new SoSphere;
+ s->radius.setValue (radius);
+ sp->addChild (s);
+
+ t2 = new SoTranslation;
+ t3 = new SoTranslation;
+
+ c1 = new SoCylinder;
+ c1->radius.setValue (radius);
+
+ c2 = new SoCylinder;
+ c2->radius.setValue (radius);
+
+ if ( align == northEast || align == southWest ) {
+ c1->height.setValue (_depth/2.0);
+ c2->height.setValue (_width/2.0);
+
+ t2->translation.setValue (0, 0, _depth/-4.0);
+ t3->translation.setValue (_width/4.0, _depth/4.0, 0);
+ } else {
+ c1->height.setValue (_width/2.0);
+ c2->height.setValue (_depth/2.0);
+
+ t2->translation.setValue (0, 0, _width/-4.0);
+ t3->translation.setValue (_depth/4.0, _width/4.0, 0);
+ }
+
+ sp->addChild (t2);
+
+ SoRotationXYZ * r1 = new SoRotationXYZ;
+ r1->axis = SoRotationXYZ::X;
+ r1->angle = M_PI/2;
+
+ sp->addChild (r1);
+ sp->addChild (c1);
+ sp->addChild (t3);
+
+ SoRotationXYZ * r2 = new SoRotationXYZ;
+ r2->axis = SoRotationXYZ::Z;
+ r2->angle = M_PI/2;
+
+ sp->addChild (r2);
+ sp->addChild (c2);
+
+ mainsep->addChild (sp);
+ } else {
+ SoRotationXYZ * r = new SoRotationXYZ;
+
+ int h;
+
+ if ( align == north || align == south ) { // Vertical pipe
+ h = rows() * cellDepth;
+
+ r->axis = SoRotationXYZ::X;
+ r->angle = M_PI/2;
+ } else { // Horizontal pipe
+ h = cols() * cellWidth;
+
+ r->axis = SoRotationXYZ::Z;
+ r->angle = M_PI/2;
+ }
+
+ mainsep->addChild (r);
+
+ c1 = new SoCylinder;
+ c1->radius.setValue (radius);
+ c1->height.setValue (h);
+ mainsep->addChild (c1);
+ }
+
+
+ ToggleMod * m = new ToggleMod (mainsep, (const char *)_tag.toAscii());
+ _root->addChild (m->root());
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ * Adjust the size of the link. Note, that we don't need to change the
+ * reported width and depth of an object (Grid does not cope with such
+ * changes very well) just adjust the length of cylinder(s).
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void
+Link::setTran (float x, float z, int w, int d)
+{
+ if ( c1 && c2 ) {
+ if ( align == northEast || align == southWest ) {
+ c1->height.setValue (d/2.0);
+ c2->height.setValue (w/2.0);
+
+ t2->translation.setValue (0, 0, d/-4.0);
+ t3->translation.setValue (w/4.0, d/4.0, 0);
+ } else {
+ c1->height.setValue (w/2.0);
+ c2->height.setValue (d/2.0);
+
+ t2->translation.setValue (0, 0, w/-4.0);
+ t3->translation.setValue (d/4.0, w/4.0, 0);
+ }
+ } else {
+ c1->height.setValue ((align == north || align == south ) ? d : w );
+ }
+
+ BaseObj::setTran (x, z, w, d);
+}
+
+void
+Link::setTag (const char * s)
+{
+ _tag = s;
+
+ if ( strchr (s, '\n') == NULL ) {
+ _tag.append ("\n");
+ }
+}
diff --git a/src/pmview/link.h b/src/pmview/link.h
new file mode 100644
index 0000000..dc4270e
--- /dev/null
+++ b/src/pmview/link.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _LINK_H
+#define _LINK_H
+
+#include "baseobj.h"
+
+class SoCylinder;
+
+class Link : public BaseObj
+{
+public:
+ Link (const DefaultObj &, int, int, int, int, Alignment);
+
+ void finishedAdd ();
+ void setTran (float, float, int, int);
+ int width () const { return _width; }
+ int depth () const { return _depth; }
+ const char * name () const { return "Link"; }
+ void setTag (const char *);
+
+ virtual Modulate *modObj() { return 0; }
+
+private:
+ Link();
+ Link (Link const &);
+ Link const& operator=(Link const &);
+
+ int cellWidth, cellHeight, cellDepth;
+ int _width, _depth;
+ Alignment align;
+
+ SoCylinder * c1, * c2;
+ SoTranslation * t2, * t3;
+ QString _tag;
+
+ float _color[3];
+};
+
+#endif
diff --git a/src/pmview/main.cpp b/src/pmview/main.cpp
new file mode 100644
index 0000000..115a90b
--- /dev/null
+++ b/src/pmview/main.cpp
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QSettings>
+#include <QtCore/QTextStream>
+#include <QtGui/QMessageBox>
+#include <Inventor/nodes/SoCube.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include "qed_fileiconprovider.h"
+#include "qed_timecontrol.h"
+#include "qed_console.h"
+#include "scenegroup.h"
+#include "pcpcolor.h"
+#include "modlist.h"
+#include "viewobj.h"
+#include "main.h"
+
+#include <iostream>
+using namespace std;
+
+int Cflag;
+int Lflag;
+int Wflag;
+char *outgeometry;
+Settings globalSettings;
+
+float theScale = 1.0;
+const int theBufferLen = 2048;
+char theBuffer[theBufferLen];
+
+// Globals used to provide single instances of classes used across pmview
+SceneGroup *liveGroup; // one metrics class group for all hosts
+SceneGroup *archiveGroup; // one metrics class group for all archives
+SceneGroup *activeGroup; // currently active metric fetchgroup
+QedTimeControl *pmtime; // one timecontrol class for pmtime
+PmView *pmview;
+
+static const char *options = "A:a:Cc:D:g:h:Ln:O:p:S:T:t:VzZ:?";
+
+static void usage(void)
+{
+ pmprintf("Usage: %s [options] [sources]\n\n"
+"Options:\n"
+" -A align align sample times on natural boundaries\n"
+" -a archive add PCP log archive to metrics source list\n"
+" -c configfile initial view to load\n"
+" -C with -c, parse config, report any errors and exit\n"
+" -CC like -C, but also connect to pmcd to check semantics\n"
+" -g geometry image geometry Width x Height (WxH)\n"
+" -h host add host to list of live metrics sources\n"
+" -L directly fetch metrics from localhost, PMCD is not used\n"
+" -O offset initial offset into the time window\n"
+" -p port port name for connection to existing time control\n"
+" -S starttime start of the time window\n"
+" -T endtime end of the time window\n"
+" -t interval sample interval [default: %d seconds]\n"
+" -V display pmview version number and exit\n"
+" -Z timezone set reporting timezone\n"
+" -z set reporting timezone to local time of metrics source\n",
+ pmProgname, (int)PmView::defaultViewDelta());
+ pmflush();
+ exit(1);
+}
+
+int warningMsg(const char *file, int line, const char *msg, ...)
+{
+ int sts = QMessageBox::Ok;
+ va_list arg;
+ va_start(arg, msg);
+
+ int pos = sprintf(theBuffer, "%s: Warning: ", pmProgname);
+ pos += vsprintf(theBuffer + pos, msg, arg);
+ sprintf(theBuffer+pos, "\n");
+
+ if (pmDebug) {
+ QTextStream cerr(stderr);
+ cerr << file << ":" << line << ": " << theBuffer << endl;
+ }
+
+ sts = QMessageBox::warning(pmview, "Warning", theBuffer, sts, sts);
+ va_end(arg);
+
+ return sts;
+}
+
+double QmcTime::secondsToUnits(double value, QmcTime::DeltaUnits units)
+{
+ switch (units) {
+ case Milliseconds:
+ value = value * 1000.0;
+ break;
+ case Minutes:
+ value = value / 60.0;
+ break;
+ case Hours:
+ value = value / (60.0 * 60.0);
+ break;
+ case Days:
+ value = value / (60.0 * 60.0 * 24.0);
+ break;
+ case Weeks:
+ value = value / (60.0 * 60.0 * 24.0 * 7.0);
+ break;
+ case Seconds:
+ default:
+ break;
+ }
+ return value;
+}
+
+double QmcTime::deltaValue(QString delta, QmcTime::DeltaUnits units)
+{
+ return QmcTime::secondsToUnits(delta.trimmed().toDouble(), units);
+}
+
+QString QmcTime::deltaString(double value, QmcTime::DeltaUnits units)
+{
+ QString delta;
+
+ value = QmcTime::secondsToUnits(value, units);
+ if ((double)(int)value == value)
+ delta.sprintf("%.2f", value);
+ else
+ delta.sprintf("%.6f", value);
+ return delta;
+}
+
+void writeSettings(void)
+{
+ QSettings userSettings;
+
+ userSettings.beginGroup(pmProgname);
+ if (globalSettings.viewDeltaModified) {
+ globalSettings.viewDeltaModified = false;
+ userSettings.setValue("viewDelta", globalSettings.viewDelta);
+ }
+ if (globalSettings.loggerDeltaModified) {
+ globalSettings.loggerDeltaModified = false;
+ userSettings.setValue("loggerDelta", globalSettings.loggerDelta);
+ }
+ if (globalSettings.viewBackgroundModified) {
+ globalSettings.viewBackgroundModified = false;
+ userSettings.setValue("viewBackgroundColor",
+ globalSettings.viewBackgroundName);
+ }
+ if (globalSettings.viewHighlightModified) {
+ globalSettings.viewHighlightModified = false;
+ userSettings.setValue("viewHighlightColor",
+ globalSettings.viewHighlightName);
+ }
+ if (globalSettings.gridBackgroundModified) {
+ globalSettings.gridBackgroundModified = false;
+ userSettings.setValue("gridBackgroundColor",
+ globalSettings.gridBackgroundName);
+ }
+ if (globalSettings.initialToolbarModified) {
+ globalSettings.initialToolbarModified = false;
+ userSettings.setValue("initialToolbar", globalSettings.initialToolbar);
+ }
+ if (globalSettings.nativeToolbarModified) {
+ globalSettings.nativeToolbarModified = false;
+ userSettings.setValue("nativeToolbar", globalSettings.nativeToolbar);
+ }
+ if (globalSettings.toolbarLocationModified) {
+ globalSettings.toolbarLocationModified = false;
+ userSettings.setValue("toolbarLocation",
+ globalSettings.toolbarLocation);
+ }
+ if (globalSettings.toolbarActionsModified) {
+ globalSettings.toolbarActionsModified = false;
+ userSettings.setValue("toolbarActions", globalSettings.toolbarActions);
+ }
+ userSettings.endGroup();
+}
+
+void readSettings(void)
+{
+ QSettings userSettings;
+ userSettings.beginGroup(pmProgname);
+
+ //
+ // Parameters related to sampling
+ //
+ globalSettings.viewDeltaModified = false;
+ globalSettings.viewDelta = userSettings.value("viewDelta",
+ PmView::defaultViewDelta()).toDouble();
+ globalSettings.loggerDeltaModified = false;
+ globalSettings.loggerDelta = userSettings.value("loggerDelta",
+ PmView::defaultLoggerDelta()).toDouble();
+
+ //
+ // Everything colour related
+ //
+ globalSettings.viewBackgroundModified = false;
+ globalSettings.viewBackgroundName = userSettings.value(
+ "viewBackgroundColor", "black").toString();
+ globalSettings.viewBackground = QColor(globalSettings.viewBackgroundName);
+
+ globalSettings.viewHighlightModified = false;
+ globalSettings.viewHighlightName = userSettings.value(
+ "viewHighlightColor", "white").toString();
+ globalSettings.viewHighlight = QColor(globalSettings.viewHighlightName);
+
+ globalSettings.gridBackgroundModified = false;
+ globalSettings.gridBackgroundName = userSettings.value(
+ "gridBackgroundColor", "rgbi:0.15/0.15/0.15").toString();
+ globalSettings.gridBackground = QColor(globalSettings.gridBackgroundName);
+
+ //
+ // Toolbar user preferences
+ //
+ globalSettings.initialToolbarModified = false;
+ globalSettings.initialToolbar = userSettings.value(
+ "initialToolbar", 1).toInt();
+ globalSettings.nativeToolbarModified = false;
+ globalSettings.nativeToolbar = userSettings.value(
+ "nativeToolbar", 1).toInt();
+ globalSettings.toolbarLocationModified = false;
+ globalSettings.toolbarLocation = userSettings.value(
+ "toolbarLocation", 0).toInt();
+ QStringList actionList;
+ globalSettings.toolbarActionsModified = false;
+ if (userSettings.contains("toolbarActions") == true)
+ globalSettings.toolbarActions =
+ userSettings.value("toolbarActions").toStringList();
+ // else: (defaults come from the pmview.ui interface specification)
+
+ userSettings.endGroup();
+}
+
+int
+genInventor(void)
+{
+ int sts = 0;
+ const char *configfile;
+ QTextStream cerr(stderr);
+
+ if (theConfigName.length()) {
+ configfile = strdup((const char *)theConfigName.toAscii());
+ if (!(yyin = fopen(configfile, "r"))) {
+ pmprintf(
+ "%s: Error: Unable to open configuration file \"%s\": %s\n",
+ pmProgname, configfile, strerror(errno));
+ return -1;
+ }
+ theAltConfigName = theConfigName;
+ } else {
+ configfile = mktemp(strdup("/tmp/pmview.XXXXXX"));
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "genInventor: Copy of configuration saved to "
+ << configfile << endl;
+ if (!(theAltConfig = fopen(configfile, "w"))) {
+ pmprintf("%s: Warning: Unable to save configuration for "
+ "recording to \"%s\": %s\n",
+ pmProgname, configfile, strerror(errno));
+ }
+ theAltConfigName = configfile;
+ }
+
+ yyparse();
+
+ if (theAltConfig)
+ fclose(theAltConfig);
+
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << pmProgname << ": " << errorCount << " errors detected in "
+ << theConfigName << endl;
+ }
+
+ sts = -errorCount;
+
+ if (rootObj != NULL) {
+ rootObj->setTran(0, 0, rootObj->width(), rootObj->depth());
+
+ SoSeparator *sep = new SoSeparator;
+ SoTranslation *tran = new SoTranslation();
+ tran->translation.setValue(rootObj->width() / -2.0, 0.0,
+ rootObj->depth() / -2.0);
+ sep->addChild(tran);
+
+ if (pmDebug & DBG_TRACE_APPL0 ||
+ pmDebug & DBG_TRACE_APPL1 ||
+ pmDebug & DBG_TRACE_APPL2) {
+ SoBaseColor *col = new SoBaseColor;
+ col->rgb.setValue(1.0, 0.0, 0.0);
+ sep->addChild(col);
+ SoCube * cube = new SoCube;
+ cube->width = 10;
+ cube->depth = 5.0;
+ cube->height = 25;
+ sep->addChild(cube);
+ }
+
+ sep->addChild(rootObj->root());
+ theModList->setRoot(sep);
+ }
+
+ if ((ViewObj::numModObjects() == 0 || theModList->size() == 0) &&
+ elementalNodeList.getLength() == 0) {
+ pmprintf("%s: No valid modulated objects in the scene\n",
+ pmProgname);
+ sts--;
+ }
+ else if (sts < 0) {
+ pmprintf("%s: Unrecoverable errors in the configuration file %s\n",
+ pmProgname, (const char *)theConfigName.toAscii());
+ }
+
+ return sts;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, sts;
+ int errflg = 0;
+ char *msg;
+
+ QedApp a(argc, argv);
+ readSettings();
+
+ liveGroup = new SceneGroup();
+ archiveGroup = new SceneGroup();
+
+ while ((c = a.getopts(options)) != EOF) {
+ switch (c) {
+
+ case 'C':
+ Cflag++;
+ break;
+
+ case 'g':
+ outgeometry = optarg;
+ break;
+
+ case 'c':
+ theConfigName = optarg;
+ break;
+
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ a.startconsole();
+
+ if (a.my.archives.size() > 0)
+ while (optind < argc)
+ a.my.archives.append(argv[optind++]);
+ else
+ while (optind < argc)
+ a.my.hosts.append(argv[optind++]);
+
+ if (optind != argc)
+ errflg++;
+ if (errflg)
+ usage();
+
+ if (a.my.pmnsfile && (sts = pmLoadNameSpace(a.my.pmnsfile)) < 0) {
+ pmprintf("%s: %s\n", pmProgname, pmErrStr(sts));
+ pmflush();
+ exit(1);
+ }
+
+ // Create all of the sources
+ if (a.my.Lflag) {
+ liveGroup->use(PM_CONTEXT_LOCAL, QmcSource::localHost);
+ Lflag = 1;
+ }
+ for (c = 0; c < a.my.hosts.size(); c++) {
+ if (liveGroup->use(PM_CONTEXT_HOST, a.my.hosts[c]) < 0)
+ a.my.hosts.removeAt(c);
+ }
+ for (c = 0; c < a.my.archives.size(); c++) {
+ if (archiveGroup->use(PM_CONTEXT_ARCHIVE, a.my.archives[c]) < 0)
+ a.my.archives.removeAt(c);
+ }
+ if (!a.my.Lflag && a.my.hosts.size() == 0 && a.my.archives.size() == 0)
+ liveGroup->createLocalContext();
+ pmflush();
+ console->post("Metric group setup complete (%d hosts, %d archives)",
+ a.my.hosts.size(), a.my.archives.size());
+
+ if (a.my.zflag) {
+ if (a.my.archives.size() > 0)
+ archiveGroup->useTZ();
+ if (a.my.hosts.size() > 0)
+ liveGroup->useTZ();
+ }
+ else if (a.my.tz != NULL) {
+ if (a.my.archives.size() > 0)
+ archiveGroup->useTZ(QString(a.my.tz));
+ if (a.my.hosts.size() > 0)
+ liveGroup->useTZ(QString(a.my.tz));
+ if ((sts = pmNewZone(a.my.tz)) < 0) {
+ pmprintf("%s: cannot set timezone to \"%s\": %s\n",
+ pmProgname, (char *)a.my.tz, pmErrStr(sts));
+ pmflush();
+ exit(1);
+ }
+ }
+
+ //
+ // Choose which View will be displayed initially - archive/live.
+ // If any archives given on command line, we go Archive mode;
+ // otherwise Live mode wins. Our initial pmtime connection is
+ // set in that mode too. Later we'll make a second connection
+ // in the other mode (and only "on-demand").
+ //
+ if (a.my.archives.size() > 0) {
+ activeGroup = archiveGroup;
+ archiveGroup->defaultTZ(a.my.tzLabel, a.my.tzString);
+ archiveGroup->updateBounds();
+ a.my.logStartTime = archiveGroup->logStart();
+ a.my.logEndTime = archiveGroup->logEnd();
+ if ((sts = pmParseTimeWindow(a.my.Sflag, a.my.Tflag,
+ a.my.Aflag, a.my.Oflag,
+ &a.my.logStartTime, &a.my.logEndTime,
+ &a.my.realStartTime, &a.my.realEndTime,
+ &a.my.position, &msg)) < 0) {
+ pmprintf("Cannot parse archive time window\n%s\n", msg);
+ free(msg);
+ usage();
+ }
+ }
+ else {
+ activeGroup = liveGroup;
+ liveGroup->defaultTZ(a.my.tzLabel, a.my.tzString);
+ gettimeofday(&a.my.logStartTime, NULL);
+ a.my.logEndTime.tv_sec = a.my.logEndTime.tv_usec = INT_MAX;
+ if ((sts = pmParseTimeWindow(a.my.Sflag, a.my.Tflag,
+ a.my.Aflag, a.my.Oflag,
+ &a.my.logStartTime, &a.my.logEndTime,
+ &a.my.realStartTime, &a.my.realEndTime,
+ &a.my.position, &msg)) < 0) {
+ pmprintf("Cannot parse live time window\n%s\n", msg);
+ free(msg);
+ usage();
+ }
+ }
+ console->post("Timezones and time window setup complete");
+
+ pmview = new PmView;
+ pmtime = new QedTimeControl;
+ fileIconProvider = new QedFileIconProvider();
+ console->post("Phase1 user interface constructors complete");
+
+ // Start pmtime process for time management
+ pmtime->init(a.my.port, a.my.archives.size() == 0, &a.my.delta,
+ &a.my.position, &a.my.realStartTime, &a.my.realEndTime,
+ a.my.tzString, a.my.tzLabel);
+
+ pmview->init();
+ liveGroup->init(pmtime->liveInterval(), pmtime->livePosition());
+ archiveGroup->init(pmtime->archiveInterval(), pmtime->archivePosition());
+ console->post("Phase2 user interface setup complete");
+
+ PCPColor::initClass();
+ theModList = new ModList(pmview->viewer(), &PmView::selectionCB, NULL, NULL);
+ if (genInventor() < 0) {
+ pmflush();
+ exit(1);
+ }
+
+ if (Cflag) // done with -c config, quit
+ return 0;
+
+ if (pmview->view(false, 0, 1, 0, M_PI / 4.0, theGlobalScale) == false) {
+ pmflush();
+ exit(1);
+ }
+
+ pmview->viewer()->viewAll();
+ pmview->enableUi();
+ pmview->show();
+ console->post("Top level window shown");
+
+ a.connect(&a, SIGNAL(lastWindowClosed()), pmview, SLOT(quit()));
+ return a.exec();
+}
diff --git a/src/pmview/main.h b/src/pmview/main.h
new file mode 100644
index 0000000..ccd66b0
--- /dev/null
+++ b/src/pmview/main.h
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef MAIN_H
+#define MAIN_H
+
+#include "pcp/pmapi.h"
+#include "pcp/impl.h"
+
+#include "qed_app.h"
+#include "main.h"
+#include "viewobj.h"
+#include "colorlist.h"
+#include "pmview.h"
+
+class QedApp;
+class View;
+class ModList;
+class SoQtExaminerViewer;
+
+typedef void (*TermCB)(int);
+
+typedef struct {
+ // Sampling
+ double viewDelta;
+ bool viewDeltaModified;
+ double loggerDelta;
+ bool loggerDeltaModified;
+
+ // Colors
+ QColor viewBackground;
+ QString viewBackgroundName;
+ bool viewBackgroundModified;
+ QColor viewHighlight;
+ QString viewHighlightName;
+ bool viewHighlightModified;
+ QColor gridBackground;
+ QString gridBackgroundName;
+ bool gridBackgroundModified;
+
+ // Toolbar
+ int initialToolbar;
+ bool initialToolbarModified;
+ int nativeToolbar;
+ bool nativeToolbarModified;
+ int toolbarLocation;
+ int toolbarLocationModified;
+ QStringList toolbarActions;
+ bool toolbarActionsModified;
+} Settings;
+
+// TODO: old X-resources...
+// ! Background color of read-only labels
+// !PmView+*readOnlyBackground: Black
+// ! Maximum value before saturation
+// ! The default of 1.05 allows for 5% error in the time delta when
+// ! determining rates, before values are deemed saturated.
+// PmView+*saturation: 1.05
+// ! Use fast anti-aliasing
+// PmView+*antiAliasSmooth: tree
+// ! Number of anti-aliasing passes: 1-255. Only 1 pass disables antialiasing.
+// PmView+*antiAliasPasses: 1
+// ! Grid, Bar and Stack object base borders
+// PmView+*baseBorderWidth: 8
+// PmView+*baseBorderDepth: 8
+// ! Height of Grid, Bar and Stack bases
+// PmView+*baseHeight: 2
+// ! Color of base plane
+// PmView+*baseColor: rgbi:0.15/0.15/0.15
+// ! Spacing between Bar blocks
+// PmView+*barSpaceWidth: 8
+// PmView+*barSpaceDepth: 8
+// ! Spacing between Bar base and labels
+// PmView+*barSpaceLabel: 6
+// ! Width and depth of Bar blocks
+// PmView+*barLength: 28
+// PmView+*barHeight: 80
+// ! Margin around a Label
+// PmView+*labelMargin: 5
+// ! Color of labels
+// PmView+*labelColor: rgbi:1.0/1.0/1.0
+// ! Width and depth of Grid columns and rows
+// PmView+*gridMinWidth: 20
+// PmView+*gridMinDepth: 20
+
+extern Settings globalSettings;
+extern void readSettings();
+extern void writeSettings();
+extern QColor nextColor(const QString &, int *);
+
+extern int Cflag;
+extern int Lflag;
+extern char *outgeometry;
+
+extern QString theConfigName; // Configuration file name
+extern FILE *theConfigFile; // Configuration file
+extern ColorList theColorLists; // ColorLists generated while parsing config
+extern float theGlobalScale; // Scale applied to entire scene
+extern FILE *theAltConfig; // Save the config file here
+extern bool theAltConfigFlag; // True when config is saved to temporary file
+extern QString theAltConfigName; // Name of the saved configuration file
+
+class SceneGroup;
+extern SceneGroup *liveGroup;
+extern SceneGroup *archiveGroup;
+extern SceneGroup *activeGroup;
+
+class PmView;
+extern PmView *pmview;
+
+class QedTimeControl;
+extern QedTimeControl *pmtime;
+
+extern int genInventor();
+extern char lastinput();
+extern char input();
+extern int markpos();
+extern int locateError();
+
+extern ViewObj *rootObj;
+extern int errorCount;
+extern int yyparse(void);
+extern FILE *yyin;
+
+extern float theScale; // The scale controls multiplier
+extern ModList *theModList; // List of modulated objects
+extern View *theView; // Viewer coordinator
+extern QedApp *theApp; // Our application object
+extern const int theBufferLen; // Length of theBuffer
+extern char theBuffer[]; // String buffer for anything
+extern const QString theDefaultFlags;
+
+int setup(const char *appname, int *argc, char **argv,
+ void *cmdopts, int numOpts, TermCB termCB);
+
+#define _POS_ __FILE__, __LINE__
+
+int warningMsg(const char *fileName, int line, const char *msg, ...);
+int errorMsg(const char *fileName, int line, const char *msg, ...);
+int fatalMsg(const char *fileName, int line, const char *msg, ...);
+
+#endif // MAIN_H
diff --git a/src/pmview/metriclist.cpp b/src/pmview/metriclist.cpp
new file mode 100644
index 0000000..fed2e40
--- /dev/null
+++ b/src/pmview/metriclist.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "metriclist.h"
+#include "scenegroup.h"
+#include "main.h"
+
+static bool doMetricFlag = true;
+static MetricList *currentList;
+
+MetricList::~MetricList()
+{
+ int i;
+
+ for (i = 0; i < _metrics.size(); i++)
+ delete _metrics[i];
+ for (i = 0; i < _colors.size(); i++)
+ delete _colors[i];
+}
+
+MetricList::MetricList()
+: _metrics(), _colors(), _values(0)
+{
+}
+
+void
+MetricList::toString(const SbColor &color, QString &str)
+{
+ char buf[48];
+
+ const float *values = color.getValue();
+ sprintf(buf, "rgbi:%f/%f/%f", values[0], values[1], values[2]);
+ str = buf;
+}
+
+int
+MetricList::add(char const* metric, double scale)
+{
+ QmcMetric *ptr = new QmcMetric(activeGroup, metric, scale);
+
+ if (ptr->status() >= 0) {
+ _metrics.append(ptr);
+ _values += ptr->numValues();
+ }
+
+ return ptr->status();
+}
+
+int
+MetricList::add(char const* metric, double scale, int history)
+{
+ QmcMetric *ptr = new QmcMetric(activeGroup, metric, scale, history);
+
+ if (ptr->status() >= 0) {
+ _metrics.append(ptr);
+ _values += ptr->numValues();
+ }
+
+ return ptr->status();
+}
+
+int
+MetricList::add(pmMetricSpec *metric, double scale)
+{
+ QmcMetric *ptr = new QmcMetric(activeGroup, metric, scale);
+
+ if (ptr->status() >= 0) {
+ _metrics.append(ptr);
+ _values += ptr->numValues();
+ }
+
+ return ptr->status();
+}
+
+int
+MetricList::add(pmMetricSpec *metric, double scale, int history)
+{
+ QmcMetric *ptr = new QmcMetric(activeGroup, metric, scale, history);
+
+ if (ptr->status() >= 0) {
+ _metrics.append(ptr);
+ _values += ptr->numValues();
+ }
+
+ return ptr->status();
+}
+
+void
+MetricList::add(SbColor const& color)
+{
+ SbColor *ptr = new SbColor;
+
+ ptr->setValue(color.getValue());
+ _colors.append(ptr);
+}
+
+void
+MetricList::add(int packedcol)
+{
+ float tran = 0.0;
+ SbColor *ptr = new SbColor;
+
+ ptr->setPackedValue(packedcol, tran);
+ _colors.append(ptr);
+}
+
+void
+MetricList::resolveColors(AlignColor align)
+{
+ int need = 0;
+
+ switch(align) {
+ case perMetric:
+ need = _metrics.size();
+ break;
+ case perValue:
+ need = _values;
+ break;
+ case noColors:
+ default:
+ need = 0;
+ break;
+ }
+
+ if (_metrics.size() == 0)
+ return;
+
+ if (_colors.size() == 0 && need > 0)
+ add(SbColor(0.0, 0.0, 1.0));
+
+ if (_colors.size() < need) {
+ int o = 0;
+ int n = 0;
+
+ while (n < need) {
+ for (o = 0; o < _colors.size() && n < need; o++, n++) {
+ SbColor *ptr = new SbColor;
+ ptr->setValue(((SbColor *)_colors[o])->getValue());
+ _colors.append(ptr);
+ }
+ }
+ }
+}
+
+QTextStream&
+operator<<(QTextStream &os, MetricList const &list)
+{
+ int i;
+ float r, g, b;
+
+ for (i = 0; i < list._metrics.size(); i++) {
+ os << '[' << i << "]: ";
+ if (i < list._colors.size()) {
+ list._colors[i]->getValue(r, g, b);
+ os << r << ',' << g << ',' << b << ": ";
+ }
+ os << *(list._metrics[i]) << endl;
+ }
+ return os;
+}
+
+static void
+dometric(const char *name)
+{
+ if (currentList->add(name, 0.0) < 0)
+ doMetricFlag = false;
+}
+
+int
+MetricList::traverse(const char *str)
+{
+ pmMetricSpec *theMetric;
+ QString source;
+ char *msg;
+ int type = PM_CONTEXT_HOST;
+ SceneGroup *group = liveGroup;
+ int sts = 0;
+
+ sts = pmParseMetricSpec((char *)str, 0, (char *)0, &theMetric, &msg);
+ if (sts < 0) {
+ pmprintf("%s: Error: Unable to parse metric spec:\n%s\n",
+ pmProgname, msg);
+ free(msg);
+ return sts;
+ /*NOTREACHED*/
+ }
+
+ // If the metric has instances, then it cannot be traversed
+ if (theMetric->ninst) {
+ sts = add(theMetric, 0.0);
+ }
+ else {
+ if (theMetric->isarch == 2) {
+ type = PM_CONTEXT_LOCAL;
+ }
+ else if (theMetric->source && strlen(theMetric->source) > 0) {
+ if (theMetric->isarch == 1) {
+ type = PM_CONTEXT_ARCHIVE;
+ group = archiveGroup;
+ }
+ }
+
+ currentList = this;
+ source = theMetric->source;
+ sts = group->use(type, source);
+ if (sts >= 0) {
+ sts = pmTraversePMNS(theMetric->metric, dometric);
+ if (sts >= 0 && doMetricFlag == false)
+ sts = -1;
+ else if (sts < 0)
+ pmprintf("%s: Error: %s%c%s: %s\n",
+ pmProgname,
+ group->context()->source().sourceAscii(),
+ type == PM_CONTEXT_ARCHIVE ? '/' : ':',
+ theMetric->metric,
+ pmErrStr(sts));
+ }
+ }
+
+ free(theMetric);
+
+ return sts;
+}
diff --git a/src/pmview/metriclist.h b/src/pmview/metriclist.h
new file mode 100644
index 0000000..8ef1eee
--- /dev/null
+++ b/src/pmview/metriclist.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _METRICLIST_H_
+#define _METRICLIST_H_
+
+#include <sys/types.h>
+#include <Inventor/SbColor.h>
+#include "qmc_metric.h"
+#include "qmc_source.h"
+
+typedef QList<QmcMetric *> MetricsList;
+typedef QList<SbColor *> SbColorList;
+
+class MetricList
+{
+public:
+
+ enum AlignColor { noColors, perMetric, perValue };
+
+private:
+
+ MetricsList _metrics;
+ SbColorList _colors;
+ int _values;
+
+public:
+
+ ~MetricList();
+
+ MetricList();
+
+ int numMetrics() const
+ { return _metrics.size(); }
+ int numValues() const
+ { return _values; }
+ int numColors() const
+ { return _colors.size(); }
+
+ const QmcMetric &metric(int i) const
+ { return *(_metrics[i]); }
+ QmcMetric &metric(int i)
+ { return *(_metrics[i]); }
+
+ const SbColor &color(int i) const
+ { return *(_colors[i]); }
+ SbColor &color(int i)
+ { return *(_colors[i]); }
+ void color(int i, QString &str) const;
+
+ int add(char const* metric, double scale);
+ int add(char const* metric, double scale, int history);
+ int add(pmMetricSpec *metric, double scale);
+ int add(pmMetricSpec *metric, double scale, int history);
+ int traverse(const char *metric);
+
+ void add(SbColor const& color);
+ void add(int packedcol);
+
+ void resolveColors(AlignColor align = perMetric);
+
+ static void toString(const SbColor &color, QString &str);
+
+ friend QTextStream& operator<<(QTextStream&, MetricList const &);
+
+private:
+
+ MetricList(const MetricList &);
+ const MetricList &operator=(const MetricList &);
+ // Never defined
+};
+
+#endif /* _METRICLIST_H_ */
diff --git a/src/pmview/modlist.cpp b/src/pmview/modlist.cpp
new file mode 100644
index 0000000..05a9c3d
--- /dev/null
+++ b/src/pmview/modlist.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QSettings>
+#include <Inventor/SoPath.h>
+#include <Inventor/SoPickedPoint.h>
+#include <Inventor/events/SoLocation2Event.h>
+#include <Inventor/nodes/SoEventCallback.h>
+#include <Inventor/nodes/SoSelection.h>
+#include <Inventor/nodes/SoIndexedFaceSet.h>
+#include <Inventor/actions/SoBoxHighlightRenderAction.h>
+#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
+#include "modlist.h"
+
+#include <iostream>
+using namespace std;
+
+const QString ModList::theBogusId = "TOP";
+const char ModList::theModListId = 'i';
+
+ModList *theModList;
+SoNodeList elementalNodeList;
+
+ModList::~ModList()
+{
+}
+
+ModList::ModList(SoQtViewer *viewer,
+ SelCallBack selCB,
+ SelInvCallBack selInvCB,
+ SelInvCallBack deselInvCB)
+: _viewer(viewer),
+ _selCB(selCB),
+ _selInvCB(selInvCB),
+ _deselInvCB(deselInvCB),
+ _root(0),
+ _selection(0),
+ _motion(0),
+ _rendAct(0),
+ _list(),
+ _selList(),
+ _current(1),
+ _numSel(0),
+ _oneSel(0),
+ _allFlag(false),
+ _allId(0)
+{
+ QSettings modSettings;
+ modSettings.beginGroup(pmProgname);
+ QString sval = modSettings.value("saturation", QString("")).toString();
+ modSettings.endGroup();
+
+ bool ok;
+ double err = sval.toFloat(&ok);
+ if (!ok || err > 0.0)
+ theNormError = err;
+
+ _root = new SoSeparator;
+ _root->ref();
+}
+
+void
+ModList::setRoot(SoSeparator *root)
+{
+ _selection = new SoSelection;
+ _root->addChild(_selection);
+
+ _selection->policy = SoSelection::SHIFT;
+ _selection->addSelectionCallback(&ModList::selCB, this);
+ _selection->addDeselectionCallback(&ModList::deselectCB, this);
+
+ _motion = new SoEventCallback;
+ _motion->addEventCallback(SoLocation2Event::getClassTypeId(),
+ &ModList::motionCB, this);
+ _selection->addChild(_motion);
+
+ root->setName((SbName)(const char *)theBogusId.toAscii());
+ _selection->addChild(root);
+
+ SoBoxHighlightRenderAction *rendAct = new SoBoxHighlightRenderAction;
+ _viewer->setGLRenderAction(rendAct);
+ _viewer->redrawOnSelectionChange(_selection);
+}
+
+const char *
+ModList::add(Modulate *obj)
+{
+ static char buf[32];
+ int len = _list.size();
+ int zero = 0;
+
+ _list.append(obj);
+ _selList.append(zero);
+
+ if (_current >= len)
+ _current = _list.size();
+
+ sprintf(buf, "%c%d", theModListId, _list.size() - 1);
+ return buf;
+}
+
+void
+ModList::refresh(bool fetchFlag)
+{
+ for (int i = 0; i < _list.size(); i++)
+ _list[i]->refresh(fetchFlag);
+ for (int n=elementalNodeList.getLength()-1; n >= 0; n--) {
+ elementalNodeList[n]->doAction(_viewer->getGLRenderAction());
+ }
+}
+
+void
+ModList::dumpSelections(QTextStream &os) const
+{
+ int i;
+ int count = 0;
+
+ os << _numSel << " selections (SoSelections.numSelections = "
+ << _selection->getNumSelected() << "), allFlag = "
+ << (_allFlag == true ? "true" : "false") << endl;
+ for (i = 0; i < _selList.size(); i++)
+ if (_selList[i] > 0) {
+ count += _selList[i];
+ os << '[' << i << "]: ";
+ if (_numSel == 1 && _oneSel == i)
+ os << '*';
+ os << *(_list[i]) << endl;
+ }
+
+ assert(count == _numSel);
+}
+
+QTextStream &
+operator<<(QTextStream &os, const ModList &rhs)
+{
+ int i;
+
+ for (i = 0; i < rhs._list.size(); i++)
+ os << '[' << i << "]: " << rhs[i] << endl;
+ return os;
+}
+
+int
+ModList::findToken(const SoPath *path)
+{
+ SoNode *node = NULL;
+ char *str = NULL;
+ int id = -1;
+ int i;
+ char c;
+
+ for (i = path->getLength() - 1; i >= 0; --i) {
+ node = path->getNode(i);
+ str = (char *)(node->getName().getString());
+ if (strlen(str) && str[0] == theModListId) {
+ sscanf(str, "%c%d", &c, &id);
+ break;
+ }
+ }
+
+ return id;
+}
+
+void
+ModList::selCB(void *ptrToThis, SoPath *path)
+{
+ ModList *me = (ModList *)ptrToThis;
+ Modulate *obj;
+ int oldCount;
+ int id;
+
+ if (!me->_allFlag)
+ id = ModList::findToken(path);
+ else
+ id = me->_allId;
+
+ if (id < 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::selCB: Nothing selected" << endl;
+#endif
+
+ return;
+ /*NOTREACHED*/
+ }
+ else if (!me->_allFlag) {
+
+ obj = me->_list[id];
+ oldCount = me->_selList[id];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::selCB: Before Selected [" << id << "] = "
+ << *obj << endl
+ << "oldCount = " << oldCount << ", _numSel = "
+ << me->_numSel << ", _allFlag = false" << endl;
+#endif
+
+ me->_selList[id] = obj->select(path);
+
+ me->_numSel += me->_selList[id] - oldCount;
+ if (me->_numSel == 1)
+ me->_oneSel = id;
+ }
+
+ if (!me->_allFlag)
+ (*(me->_selCB))(me, true);
+
+ if (me->_selInvCB != NULL)
+ (*(me->_selInvCB))(me, path);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ cerr << "ModList::selCB: After Selected [" << id << "] " << endl
+ << "oldCount = " << oldCount << ", _numSel = "
+ << me->_numSel << ", _allFlag = "
+ << (me->_allFlag == true ? "true" : "false") << ", _allId = "
+ << me->_allId << endl;
+ cerr << "ModList::selCB: selection state:" << endl;
+ me->dumpSelections(cerr);
+ }
+#endif
+}
+
+void
+ModList::deselectCB(void *ptrToThis, SoPath *path)
+{
+ ModList *me = (ModList *)ptrToThis;
+ Modulate *obj;
+ int oldCount;
+ int i;
+ int id;
+
+ id = findToken(path);
+
+ if (id < 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::deselectCB: Nothing deselected" << endl;
+#endif
+
+ return;
+ /*NOTREACHED*/
+ }
+
+ obj = me->_list[id];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::deselectCB: Deselected [" << id << "] = "
+ << *obj << endl;
+#endif
+
+ oldCount = me->_selList[id];
+ me->_selList[id] = obj->remove(path);
+ me->_numSel -= oldCount - me->_selList[id];
+ if (me->_numSel == 1) {
+ for (i = 0; i < me->_selList.size(); i++)
+ if (me->_selList[i] == 1)
+ me->_oneSel = i;
+ }
+
+ if (me->_numSel == 0)
+ me->_current = me->_list.size();
+
+ if (me->_numSel < 2)
+ (*(me->_selCB))(me, true);
+
+ if (me->_deselInvCB != NULL)
+ (*(me->_deselInvCB))(me, path);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ cerr << "ModList::deselectCB: selection state:" << endl;
+ me->dumpSelections(cerr);
+ }
+#endif
+}
+
+void
+ModList::motionCB(void *ptrToThis, SoEventCallback *theEvent)
+{
+ ModList *me = (ModList *)ptrToThis;
+ const SoPickedPoint *pick = NULL;
+ SoPath *path = NULL;
+ int id = -1;
+
+ // If one item is selected, return as we aren't interested
+ if (me->_numSel == 1)
+ return;
+
+ pick = theEvent->getPickedPoint();
+ if (pick != NULL) {
+ path = pick->getPath();
+ if (path != NULL)
+ id = ModList::findToken(path);
+ }
+
+ // Nothing selected that we are interested in
+ if (id < 0) {
+ // Deselect anything selected
+ if (me->_current < me->size()) {
+ (*me)[me->_current].removeInfo(path);
+ me->_current = me->size();
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::motionCB: remove object " << id << endl;
+#endif
+ }
+ }
+ else if (me->_current != id) {
+ if (me->_current < me->size())
+ (*me)[me->_current].removeInfo(path);
+ me->_current = id;
+ (*me)[me->_current].selectInfo(path);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::motionCB: new object " << id << endl;
+#endif
+ }
+ else {
+ (*me)[me->_current].selectInfo(path);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::motionCB: same object " << id << endl;
+#endif
+ }
+
+ // Note: the call to _selCB below used to only be done if the guard
+ // if (old != me->_current)
+ // is true. But this does not work for stacked bars because
+ // the object is the same even though the mouse has moved over
+ // a different block in the same stack. Hence the metric info
+ // text window was not being updated for the mouse motion CB.
+ // Since the render method for the metricLabel only updates the
+ // text widget if the text has actually changed, it seems to me
+ // that it is safe to call the selCB unconditionally and there
+ // wont be any "flicker" problems.
+ // -- markgw 15 oct 1997
+ //
+ (*(me->_selCB))(me, false);
+}
+
+void
+ModList::infoText(QString &str) const
+{
+ if (_current >= _list.size() && _numSel != 1)
+ str = "";
+ else if (_numSel == 1)
+ _list[_oneSel]->infoText(str, true);
+ else
+ _list[_current]->infoText(str, false);
+}
+
+void
+ModList::launch(Launch &launch, bool all) const
+{
+ int i;
+
+ if (all == false && _numSel > 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::launch: launching for " << _numSel
+ << " objects" << endl;
+#endif
+
+ for (i = 0; i < _selList.size(); i++) {
+ if (_selList[i] > 0)
+ _list[i]->launch(launch, false);
+ }
+ }
+ else {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::launch: launching for all objects" << endl;
+#endif
+
+ for (i = 0; i < _list.size(); i++)
+ _list[i]->launch(launch, true);
+ }
+}
+
+void
+ModList::record(Record &rec) const
+{
+ int i;
+
+ for (i = 0; i < _list.size(); i++)
+ _list[i]->record(rec);
+}
+
+bool
+ModList::selections() const
+{
+ if (_numSel)
+ return true;
+
+ return false;
+}
+
+const SoPath *
+ModList::oneSelPath() const
+{
+ return (_numSel == 1 ? _selection->getPath(0) : NULL);
+}
+
+void
+ModList::deselectPath(SoPath *path)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::deselectPath:" << endl;
+#endif
+
+ _selection->deselect(path);
+ deselectCB(this, path);
+}
+
+void
+ModList::selectAllId(SoNode *node, int count)
+{
+ SoPath *path = new SoPath(node);
+
+ _allId = findToken(path);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "ModList::selectAllId: Select All on " << _allId << endl;
+#endif
+
+ if (_allId > 0) {
+ _numSel += count - _selList[_allId];
+ _selList[_allId] = count;
+ if (_numSel == 1)
+ _oneSel = _allId;
+ }
+
+ path->unref();
+}
+
+void
+ModList::selectSingle(SoNode *node)
+{
+ _selection->select(node);
+}
+
+void
+ModList::selectAllOff()
+{
+ _allFlag = false;
+ _allId = _list.size();
+ (*_selCB)(this, true);
+}
diff --git a/src/pmview/modlist.h b/src/pmview/modlist.h
new file mode 100644
index 0000000..72d2319
--- /dev/null
+++ b/src/pmview/modlist.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _MODLIST_H_
+#define _MODLIST_H_
+
+#include <Inventor/SoLists.h>
+#include "modulate.h"
+
+class SoQtViewer;
+class SoSelection;
+class SoEventCallback;
+class SoBoxHighlightRenderAction;
+class SoPath;
+class SoPickedPoint;
+class SoSeparator;
+class ModList;
+class Launch;
+class Record;
+
+typedef void (*SelCallBack)(ModList *, bool);
+typedef void (*SelInvCallBack)(ModList *, SoPath *);
+
+typedef QList<Modulate *> ModulateList;
+extern SoNodeList elementalNodeList;
+
+class ModList
+{
+private:
+
+ static const QString theBogusId;
+ static const char theModListId;
+
+ SoQtViewer *_viewer;
+ SelCallBack _selCB;
+ SelInvCallBack _selInvCB;
+ SelInvCallBack _deselInvCB;
+ SoSeparator *_root;
+ SoSelection *_selection;
+ SoEventCallback *_motion;
+ SoBoxHighlightRenderAction *_rendAct;
+
+ ModulateList _list;
+ QList<int> _selList;
+ int _current;
+ int _numSel;
+ int _oneSel;
+
+ bool _allFlag;
+ int _allId;
+
+public:
+
+ ~ModList();
+
+ ModList(SoQtViewer *viewer, SelCallBack selCB,
+ SelInvCallBack selInvCB = NULL, SelInvCallBack deselInvCB = NULL);
+
+ int size() const
+ { return _list.size(); }
+ int numSelected() const
+ { return _numSel; }
+
+ SoSeparator *root()
+ { return _root; }
+ void setRoot(SoSeparator *root);
+
+ SoSelection *selector() const
+ { return _selection; }
+
+ const char *add(Modulate *obj);
+
+ const Modulate &operator[](int i) const
+ { return *(_list[i]); }
+ Modulate &operator[](int i)
+ { return *(_list[i]); }
+
+ const Modulate &current() const
+ { return *(_list[_current]); }
+ Modulate &current()
+ { return *(_list[_current]); }
+
+ void refresh(bool fetchFlag);
+
+ void infoText(QString &str) const;
+
+ void launch(Launch &launch, bool all = false) const;
+ void record(Record &rec) const;
+
+ void dumpSelections(QTextStream &os) const;
+ bool selections() const;
+
+ void selectAllOn()
+ { _allFlag = true; _allId = _list.size(); }
+ void selectAllId(SoNode *node, int count);
+ void selectSingle(SoNode *node);
+ void selectAllOff();
+
+ friend QTextStream &operator<<(QTextStream &os, const ModList &rhs);
+
+ static void deselectCB(void *me, SoPath *path);
+
+ // Sprouting support
+
+ // Get path to single selection
+ const SoPath *oneSelPath() const;
+
+ void deselectPath(SoPath *path);
+
+private:
+
+ static void selCB(void *me, SoPath *path);
+ static void motionCB(void *me, SoEventCallback *event);
+ static int findToken(const SoPath *path);
+
+ ModList(const ModList &);
+ const ModList &operator=(const ModList &);
+ // Never defined
+};
+
+extern ModList *theModList;
+
+#endif /* _MODLIST_H_ */
diff --git a/src/pmview/modobj.h b/src/pmview/modobj.h
new file mode 100644
index 0000000..0d65f4b
--- /dev/null
+++ b/src/pmview/modobj.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2000-2005 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _MODOBJ_H_
+#define _MODOBJ_H_
+
+#include "baseobj.h"
+
+class ModObj : public BaseObj
+{
+public:
+ ModObj(bool onFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols = 1, int rows = 1,
+ BaseObj::Alignment align = BaseObj::center)
+ : BaseObj (onFlag, defaults, x, y, cols, rows, align)
+ , _history(0)
+ , _colors()
+ , _metrics ()
+ { _objtype |= MODOBJ; }
+
+ void setColorList(const char *list) { _colors = list; }
+ void addMetric(const char * m, double s) { _metrics.add(m, s, _history); }
+ void setHistory(int history) { _history = history; }
+
+protected:
+ int _history;
+ QString _colors;
+ MetricList _metrics;
+};
+#endif
diff --git a/src/pmview/modulate.cpp b/src/pmview/modulate.cpp
new file mode 100644
index 0000000..8e3fb98
--- /dev/null
+++ b/src/pmview/modulate.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoSelection.h>
+#include "modulate.h"
+#include "modlist.h"
+
+#include <iostream>
+using namespace std;
+
+double theNormError = 1.05;
+
+const QString Modulate::theErrorText = "Metric Unavailable";
+const QString Modulate::theStartText = "Metric has not been fetched from source";
+const float Modulate::theDefErrorColor[] = {0.2, 0.2, 0.2};
+const float Modulate::theDefSaturatedColor[] = {1.0, 1.0, 1.0};
+const double Modulate::theMinScale = 0.01;
+
+Modulate::~Modulate()
+{
+}
+
+Modulate::Modulate(const char *metric, double scale,
+ MetricList::AlignColor align)
+: _sts(0), _metrics(0), _root(0)
+{
+ _metrics = new MetricList();
+ _sts = _metrics->add(metric, scale);
+ if (_sts >= 0) {
+ _metrics->resolveColors(align);
+ _saturatedColor.setValue(theDefSaturatedColor);
+ }
+ _errorColor.setValue(theDefErrorColor);
+}
+
+Modulate::Modulate(const char *metric, double scale,
+ const SbColor &color,
+ MetricList::AlignColor align)
+: _sts(0), _metrics(0), _root(0)
+{
+ _metrics = new MetricList();
+ _sts = _metrics->add(metric, scale);
+ if (_sts >= 0) {
+ _metrics->add(color),
+ _metrics->resolveColors(align);
+ }
+ _saturatedColor.setValue(theDefSaturatedColor);
+ _errorColor.setValue(theDefErrorColor);
+}
+
+Modulate::Modulate(MetricList *list)
+: _sts(0), _metrics(list), _root(0)
+{
+ _saturatedColor.setValue(theDefSaturatedColor);
+ _errorColor.setValue(theDefErrorColor);
+}
+
+const char *
+Modulate::add()
+{
+ const char *str = theModList->add(this);
+ _root->setName((SbName)str);
+ return str;
+}
+
+QTextStream &
+operator<<(QTextStream & os, const Modulate &rhs)
+{
+ rhs.dump(os);
+ return os;
+}
+
+void
+Modulate::dumpState(QTextStream &os, Modulate::State state) const
+{
+ switch(state) {
+ case Modulate::start:
+ os << "Start";
+ break;
+ case Modulate::error:
+ os << "Error";
+ break;
+ case Modulate::saturated:
+ os << "Saturated";
+ break;
+ case Modulate::normal:
+ os << "Normal";
+ break;
+ default:
+ os << "Unknown";
+ break;
+ }
+}
+
+void
+Modulate::record(Record &rec) const
+{
+#if 1 // TODO
+ (void)rec;
+#else // TODO
+ int i;
+
+ if (_metrics != NULL)
+ for (i = 0; i < _metrics->numMetrics(); i++) {
+ const QmcMetric &metric = _metrics->metric(i);
+ rec.add(metric.context()->source().sourceAscii(),
+ (const char *)metric.spec(false, true).toAscii());
+ }
+#endif
+}
+
+void
+Modulate::selectAll()
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "Modulate::selectAll: selectAll for " << *this << endl;
+#endif
+
+ theModList->selectAllId(_root, 1);
+ theModList->selectSingle(_root);
+}
diff --git a/src/pmview/modulate.h b/src/pmview/modulate.h
new file mode 100644
index 0000000..b8e6b7d
--- /dev/null
+++ b/src/pmview/modulate.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _MODULATE_H_
+#define _MODULATE_H_
+
+#include <Inventor/SbString.h>
+#include "metriclist.h"
+
+class SoSeparator;
+class SoPath;
+class Launch;
+class Record;
+
+extern double theNormError;
+extern float theScale;
+
+class Modulate
+{
+public:
+
+ enum State { start, error, saturated, normal };
+
+protected:
+
+ static const QString theErrorText;
+ static const QString theStartText;
+ static const float theDefErrorColor[];
+ static const float theDefSaturatedColor[];
+ static const double theMinScale;
+
+ int _sts;
+ MetricList *_metrics;
+ SoSeparator *_root;
+ SbColor _errorColor;
+ SbColor _saturatedColor;
+
+public:
+
+ virtual ~Modulate();
+
+ Modulate(const char *metric, double scale,
+ MetricList::AlignColor align = MetricList::perMetric);
+
+ Modulate(const char *metric, double scale, const SbColor &color,
+ MetricList::AlignColor align = MetricList::perMetric);
+
+ Modulate(MetricList *list);
+
+ int status() const
+ { return _sts; }
+ const SoSeparator *root() const
+ { return _root; }
+ SoSeparator *root()
+ { return _root; }
+
+ int numValues() const
+ { return _metrics->numValues(); }
+
+ const char *add();
+
+ void setErrorColor(const SbColor &color)
+ { _errorColor.setValue(color.getValue()); }
+ void setSaturatedColor(const SbColor &color)
+ { _saturatedColor.setValue(color.getValue()); }
+
+ virtual void refresh(bool fetchFlag) = 0;
+
+ // Return the number of objects still selected
+ virtual void selectAll();
+ virtual int select(SoPath *)
+ { return 0; }
+ virtual int remove(SoPath *)
+ { return 0; }
+
+ // Should expect selectInfo calls to different paths without
+ // previous removeInfo calls
+ virtual void selectInfo(SoPath *)
+ {}
+ virtual void removeInfo(SoPath *)
+ {}
+
+ virtual void infoText(QString &str, bool selected) const = 0;
+
+ virtual void launch(Launch &launch, bool all) const = 0;
+ virtual void record(Record &rec) const;
+
+ virtual void dump(QTextStream &) const
+ {}
+ void dumpState(QTextStream &os, State state) const;
+
+ friend QTextStream &operator<<(QTextStream &os, const Modulate &rhs);
+
+protected:
+
+ static void add(Modulate *obj);
+
+private:
+
+ Modulate();
+ Modulate(const Modulate &);
+ const Modulate &operator=(const Modulate &);
+ // Never defined
+};
+
+#endif /* _MODULATE_H_ */
diff --git a/src/pmview/pcpcolor.cpp b/src/pmview/pcpcolor.cpp
new file mode 100644
index 0000000..85a313e
--- /dev/null
+++ b/src/pmview/pcpcolor.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/actions/SoCallbackAction.h>
+#include <Inventor/actions/SoGLRenderAction.h>
+#include <Inventor/bundles/SoMaterialBundle.h>
+#include <Inventor/elements/SoEmissiveColorElement.h>
+
+#include "pcpcolor.h"
+#include "scenegroup.h"
+#include "main.h"
+
+SO_NODE_SOURCE(PCPColor);
+// Initializes the PCPColor class. This is a one-time thing that is
+// done after database initialization and before any instance of
+// this class is constructed.
+void
+PCPColor::initClass()
+{
+ // Initialize type id variables. The arguments to the macro
+ // are: the name of the node class, the class this is derived
+ // from, and the name registered with the type of the parent
+ // class.
+ SO_NODE_INIT_CLASS(PCPColor, SoNode, "Node");
+}
+// Constructor
+PCPColor::PCPColor()
+{
+ // Do standard constructor tasks
+ SO_NODE_CONSTRUCTOR(PCPColor);
+
+ SO_NODE_ADD_FIELD(maxValue, (1.0));
+ SO_NODE_ADD_FIELD(color, (1.0, 1.0, 1.0));
+ SO_NODE_ADD_FIELD(metric, (""));
+
+ // SbString s = metric.getValue();
+ SbString *s = new SbString("kernel.all.cpu.user");
+
+ double scale = (double)maxValue.getValue();
+ theMetric = new QmcMetric(activeGroup, s->getString(), scale);
+ elementalNodeList.append(this);
+}
+// Destructor
+PCPColor::~PCPColor()
+{
+}
+// Implements GL render action.
+void
+PCPColor::GLRender(SoGLRenderAction *action)
+{
+ // Set the elements in the state correctly. Note that we
+ // prefix the call to doAction() with the class name. This
+ // avoids problems if someone derives a new class from the
+ // PCPColor node and inherits the GLRender() method; PCPColor's
+ // doAction() will still be called in that case.
+
+ PCPColor::doAction(action);
+
+ // For efficiency, Inventor nodes make sure that the first
+ // defined material is always in GL, so shapes do not have to
+ // send the first material each time. (This keeps caches from
+ // being dependent on material values in many cases.) The
+ // SoMaterialBundle class allows us to do this easily.
+ SoMaterialBundle mb(action);
+ mb.forceSend(0);
+}
+// Implements callback action.
+void
+PCPColor::callback(SoCallbackAction *action)
+{
+ // Set the elements in the state correctly.
+ PCPColor::doAction(action);
+}
+
+// Typical action implementation - it sets the correct element
+// in the action's traversal state. We assume that the element
+// has been enabled.
+void
+PCPColor::doAction(SoAction *action)
+{
+ theMetric->update();
+ float f = theMetric->realValue(0);
+ f /= maxValue.getValue() + 0.001;
+ emissiveColor = color.getValue() * f;
+ if (action->getState())
+ SoEmissiveColorElement::set(action->getState(), this, 1, &emissiveColor);
+
+ touch();
+}
diff --git a/src/pmview/pcpcolor.h b/src/pmview/pcpcolor.h
new file mode 100644
index 0000000..85d47df
--- /dev/null
+++ b/src/pmview/pcpcolor.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PCPCOLOR_H_
+#define _PCPCOLOR_H_
+
+#include <Inventor/SbColor.h>
+#include <Inventor/fields/SoSFString.h>
+#include <Inventor/fields/SoSFColor.h>
+#include <Inventor/fields/SoSFFloat.h>
+#include <Inventor/nodes/SoSubNode.h>
+
+#include "main.h"
+#include "modlist.h"
+
+class PCPColor : public SoNode {
+ SO_NODE_HEADER(PCPColor);
+ public:
+ // Fields:
+ SoSFString metric; // PCP metric spec
+ SoSFFloat maxValue;
+ SoSFColor color; // Color of glow
+
+ // Initializes this class for use in scene graphs. This
+ // should be called after database initialization and before
+ // any instance of this node is constructed.
+ static void initClass();
+ // Constructor
+ PCPColor();
+ protected:
+ QmcMetric *theMetric;
+
+ // These implement supported actions. The only actions that
+ // deal with materials are the callback and GL render
+ // actions. We will inherit all other action methods from
+ // SoNode.
+ virtual void GLRender(SoGLRenderAction *action);
+ virtual void callback(SoCallbackAction *action);
+ // This implements generic traversal of PCPColor node, used in
+ // both of the above methods.
+ virtual void doAction(SoAction *action);
+
+ private:
+ // Destructor. Private to keep people from trying to delete
+ // nodes, rather than using the reference count mechanism.
+ virtual ~PCPColor();
+ SbColor emissiveColor;
+};
+
+#endif /* _PCPCOLOR_H_ */
diff --git a/src/pmview/pipeobj.cpp b/src/pmview/pipeobj.cpp
new file mode 100644
index 0000000..e6e8886
--- /dev/null
+++ b/src/pmview/pipeobj.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoRotationXYZ.h>
+#include <Inventor/nodes/SoCylinder.h>
+#include <Inventor/nodes/SoCube.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+
+#include "stackmod.h"
+#include "togglemod.h"
+#include "pipeobj.h"
+#include "defaultobj.h"
+#include "colorlist.h"
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ * Constructor for Pipe.
+ * Note that similar to Link, pipes are always centered
+ * relative to the grid. The aligment is used to decide
+ * which end of pipe is decorated and in what direction
+ * pipe is oriented.
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+PipeObj::PipeObj (const DefaultObj & defs,
+ int c, int r,
+ int colSpan, int rowSpan,
+ BaseObj::Alignment align)
+ : ModObj (false, defs, c, r, colSpan, rowSpan, center)
+ , _align(align)
+ , _tag ("\n")
+{
+ _objtype |= PIPEOBJ;
+
+ for ( int i=0; i < 3; i++) {
+ _color[i] = defs.baseColor(i);
+ }
+
+ _cylTrans = 0;
+ _origt = 0;
+ _cyl = 0;
+ _stackHeight = defs.pipeLength();
+ cellHeight = defs.baseHeight();
+
+ if ( _align == north || _align == south ) {
+ cellWidth = defs.baseHeight();
+ cellDepth = (defs.pipeLength() < cellHeight) ?
+ cellHeight : defs.pipeLength();
+ } else {
+ cellWidth = (defs.pipeLength() < cellHeight) ?
+ cellHeight : defs.pipeLength();
+ cellDepth = defs.baseHeight();
+ }
+
+ _width = colSpan * cellWidth;
+ _depth = rowSpan * cellDepth;
+
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ *
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void
+PipeObj::finishedAdd ()
+{
+ BaseObj::addBase (_root);
+
+ int h;
+ SoSeparator * sp = new SoSeparator;
+ _root->addChild (sp);
+
+ SoTranslation * st = new SoTranslation;
+ st->translation.setValue (_width/2.0, cellHeight/2.0, _depth/2.0);
+ sp->addChild (st);
+
+ SoRotationXYZ * r = new SoRotationXYZ;
+ if ( _align == north || _align == south ) { // Vertical Pipe
+ h = _depth;
+
+ r->axis = SoRotationXYZ::X;
+ r->angle = M_PI/((_align == north) ? 2 : -2);
+ } else { // Horizontal pipe
+ h = _width;
+
+ r->axis = SoRotationXYZ::Z;
+ r->angle = M_PI/((_align == east) ? 2 : -2);
+ }
+ sp->addChild (r);
+
+ SoSeparator * sceneSp = new SoSeparator;
+ sp->addChild (sceneSp);
+
+ _origt = new SoTranslation;
+ _origt->translation.setValue (0, -h/2.0, 0);
+ sceneSp->addChild (_origt);
+
+ if (_metrics.numMetrics() ) {
+ SoSeparator * stacksp = new SoSeparator;
+ const ColorSpec * colSpec;
+ SoScale *scale = new SoScale();
+ scale->scaleFactor.setValue(cellHeight*0.8, _stackHeight,
+ cellHeight*0.8);
+ stacksp->addChild(scale);
+
+ if ((colSpec = theColorLists.list((const char *)_colors.toAscii()))) {
+ if (colSpec->_scale)
+ pmprintf(
+ "%s: Warning: Color scale cannot be applied to pipe\n",
+ pmProgname);
+ else {
+ for (int i = 0; i < colSpec->_list.size(); i++)
+ _metrics.add(*(colSpec->_list)[i]);
+ }
+ } else {
+ pmprintf("%s: Warning: No colours specified for pipe"
+ "defaulting to blue.\n", pmProgname);
+ }
+
+ _metrics.resolveColors(MetricList::perValue);
+
+ StackMod * _stack = new StackMod(&_metrics, ViewObj::object (cylinder),
+ StackMod::fixed);
+ _stack->setFillColor(_color);
+ _stack->setFillText((const char *)_tag.toAscii());
+
+ stacksp->addChild(_stack->root());
+ sceneSp->addChild(stacksp);
+
+ BaseObj::add(_stack);
+ ViewObj::theNumModObjects++;
+ } else {
+ pmprintf("%s: Error: no metrics for pipe\n", pmProgname);
+ }
+
+ SoBaseColor * color = new SoBaseColor;
+ color->rgb.setValue (_color);
+ sceneSp->addChild (color);
+
+ _cylTrans = new SoTranslation;
+ _cylTrans->translation.setValue (0, (h+_stackHeight)/2.0, 0);
+ sceneSp->addChild (_cylTrans);
+
+
+ _cyl = new SoCylinder;
+ _cyl->radius.setValue (cellHeight*0.4);
+ _cyl->height.setValue (h - _stackHeight);
+
+ // In theory, something like "StaticMod", i.e no metrics, no
+ // nothing should be provided, but togglemod seems to be
+ // working Ok, so....
+ ToggleMod * m = new ToggleMod (_cyl, (const char *)_tag.toAscii());
+ sceneSp->addChild (m->root());
+}
+
+void
+PipeObj::setTran (float x, float z, int w, int d)
+{
+ if ( _cyl ) {
+ float h = (_align == north || _align == south) ? d : w;
+
+ _cylTrans->translation.setValue (0, (h + _stackHeight)/2.0, 0);
+ _origt->translation.setValue (0, -h/2.0, 0);
+ _cyl->height.setValue (h - _stackHeight);
+ }
+
+ ModObj::setTran (x, z, w, d);
+}
+
+void
+PipeObj::setTag (const char * s)
+{
+ _tag = s;
+ if ( strchr (s, '\n' ) == NULL ) {
+ _tag.append ("\n");
+ }
+}
diff --git a/src/pmview/pipeobj.h b/src/pmview/pipeobj.h
new file mode 100644
index 0000000..1630813
--- /dev/null
+++ b/src/pmview/pipeobj.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _PIPEOBJ_H_
+#define _PIPEOBJ_H_
+
+#include "modobj.h"
+
+class SoCylinder;
+class SoTranslation;
+
+class PipeObj : public ModObj {
+public:
+ virtual ~PipeObj () {}
+ PipeObj (const DefaultObj &, int, int, int, int, Alignment);
+
+ int width () const { return _width; }
+ int depth () const { return _depth; }
+
+ const char * name() const { return "Pipe"; }
+
+ void finishedAdd ();
+ void setTran (float, float, int, int);
+ void setTag (const char *);
+
+private:
+ PipeObj ();
+ PipeObj (PipeObj const &);
+ PipeObj const& operator=(PipeObj const &);
+
+ int _width, _depth;
+ int cellWidth, cellDepth, cellHeight;
+ Alignment _align;
+ float _stackHeight;
+ QString _tag;
+
+ SoCylinder * _cyl;
+ SoTranslation * _cylTrans, * _origt;
+
+ float _color[3];
+};
+
+#endif /* _PIPEOBJ_H_ */
diff --git a/src/pmview/pmview.cpp b/src/pmview/pmview.cpp
new file mode 100644
index 0000000..2d4d1ff
--- /dev/null
+++ b/src/pmview/pmview.cpp
@@ -0,0 +1,708 @@
+/*
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ * Copyright (c) 2006, Ken McDonell. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <QtCore/QUrl>
+#include <QtCore/QTimer>
+#include <QtCore/QLibraryInfo>
+#include <QtGui/QDesktopServices>
+#include <QtGui/QApplication>
+#include <QtGui/QPrintDialog>
+#include <QtGui/QMessageBox>
+#include <QtGui/QWhatsThis>
+
+#include <Inventor/nodes/SoPerspectiveCamera.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTransform.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoCube.h>
+
+#include <iostream>
+using namespace std;
+
+#include "barobj.h"
+#include "gridobj.h"
+#include "stackobj.h"
+#include "labelobj.h"
+#include "modlist.h"
+#include "defaultobj.h"
+#include "pcpcolor.h"
+
+#include "main.h"
+#include "pmview.h"
+#include "scenegroup.h"
+#include "qed_console.h"
+#include "qed_statusbar.h"
+#include "qed_timecontrol.h"
+#include "qed_recorddialog.h"
+
+QString theConfigName;
+QString theAltConfigName;
+FILE *theConfigFile;
+FILE *theAltConfig;
+float theGlobalScale = 1.2;
+char **frontend_argv;
+int frontend_argc;
+
+PmView::PmView() : QMainWindow(NULL)
+{
+ my.dialogsSetup = false;
+
+ setIconSize(QSize(22, 22));
+ setupUi(this);
+
+ SoQt::init(widget);
+ my.viewer = new SoQtExaminerViewer(widget);
+
+ my.statusBar = new QedStatusBar;
+ setStatusBar(my.statusBar);
+
+ toolBar->setAllowedAreas(Qt::RightToolBarArea | Qt::TopToolBarArea);
+ connect(toolBar, SIGNAL(orientationChanged(Qt::Orientation)),
+ this, SLOT(updateToolbarOrientation(Qt::Orientation)));
+ updateToolbarLocation();
+ setupEnabledActionsList();
+ if (!globalSettings.initialToolbar)
+ toolBar->hide();
+
+ my.liveHidden = true;
+ my.archiveHidden = true;
+ timeControlAction->setChecked(false);
+ my.menubarHidden = false;
+ my.toolbarHidden = !globalSettings.initialToolbar;
+ toolbarAction->setChecked(globalSettings.initialToolbar);
+ my.consoleHidden = true;
+ if (!pmDebug)
+ consoleAction->setVisible(false);
+ consoleAction->setChecked(false);
+
+ // Build Scene Graph
+ my.root = new SoSeparator;
+ my.root->ref();
+
+ SoPerspectiveCamera *camera = new SoPerspectiveCamera;
+ camera->orientation.setValue(SbVec3f(1, 0, 0), -M_PI/6.0);
+ my.root->addChild(camera);
+
+ my.drawStyle = new SoDrawStyle;
+ my.drawStyle->style.setValue(SoDrawStyle::FILLED);
+ my.root->addChild(my.drawStyle);
+
+#if 0
+ // TODO is this needed?
+ if (outfile)
+ QTimer::singleShot(0, this, SLOT(exportFile()));
+ else
+#endif
+ QTimer::singleShot(PmView::defaultTimeout(), this, SLOT(timeout()));
+
+}
+
+void PmView::languageChange()
+{
+ retranslateUi(this);
+}
+
+void PmView::init(void)
+{
+ my.statusBar->init();
+}
+
+void
+PmView::selectionCB(ModList *, bool redraw)
+{
+ RenderOptions options = PmView::metricLabel;
+
+ if (redraw)
+ options = (RenderOptions)(options | PmView::inventor);
+ pmview->render(options, 0);
+}
+
+bool PmView::view(bool showAxis,
+ float xAxis, float yAxis, float zAxis, float angle, float scale)
+{
+ if (theModList->size() == 0) {
+ warningMsg(_POS_, "No modulated objects in scene");
+ }
+
+ // Setup remainder of the scene graph
+ my.root->addChild(theModList->root());
+
+ viewer()->setSceneGraph(my.root);
+ viewer()->setAutoRedraw(true);
+ viewer()->setTitle(pmProgname);
+ if (showAxis)
+ viewer()->setFeedbackVisibility(true);
+
+ SbBool smooth = TRUE;
+ int passes = 1;
+ char *sval = NULL;
+
+#if 0 // TODO: QSettings API
+ sval = VkGetResource("antiAliasSmooth", XmRString);
+ if (sval && strcmp(sval, "default") != 0 && strcasecmp(sval, "false") == 0)
+ smooth = FALSE;
+ sval = VkGetResource("antiAliasPasses", XmRString);
+ if (sval != NULL && strcmp(sval, "default"))
+ passes = atoi(sval);
+#endif
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "PmView::view: antialiasing set to smooth = "
+ << (smooth == TRUE ? "true" : "false")
+ << ", passes = " << passes << endl;
+#endif
+
+ if (passes > 1)
+ viewer()->setAntialiasing(smooth, atoi(sval));
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "PmView::view: displaying window" << endl;
+#endif
+
+ viewer()->viewAll();
+
+ if (angle != 0.0 || scale != 0.0) {
+ SoTransform *tran = new SoTransform();
+ if (angle != 0.0)
+ tran->rotation.setValue(SbVec3f(xAxis, yAxis, zAxis), angle);
+ if (scale != 0.0)
+ tran->scaleFactor.setValue(scale, scale, scale);
+ theModList->root()->insertChild(tran, 0);
+ }
+
+ PmView::render((RenderOptions)(PmView::inventor | PmView::metricLabel), 0);
+ viewer()->saveHomePosition();
+
+ return true;
+}
+
+void PmView::render(RenderOptions options, time_t theTime)
+{
+ viewer()->setAutoRedraw(false);
+
+ if (options & PmView::metrics)
+ theModList->refresh(true);
+
+ if (options & PmView::inventor)
+ viewer()->render();
+
+ if (options & PmView::metricLabel) {
+ theModList->infoText(my.text);
+ if (my.text != my.prevText) {
+ my.prevText = my.text;
+ if (my.text.length() == 0)
+ // TODO: clear label string
+ ;
+ else {
+ // TODO: set label string to my.text
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "PmView::render: metricLabel text \"" <<
+ my.text() << "\"" << endl;
+#endif
+ }
+ }
+ }
+
+ if (options & PmView::timeLabel)
+ setDateLabel(theTime, QString::null); // TODO
+
+ viewer()->setAutoRedraw(true);
+}
+
+QMenu *PmView::createPopupMenu(void)
+{
+ QMenu *menu = QMainWindow::createPopupMenu();
+ menu->addAction(menubarAction);
+ return menu;
+}
+
+void PmView::quit()
+{
+ // End any processes we may have started and close any open dialogs
+ if (pmtime)
+ pmtime->quit();
+}
+
+void PmView::closeEvent(QCloseEvent *)
+{
+ quit();
+}
+
+void PmView::enableUi(void)
+{
+#if 0
+ recordStartAction->setEnabled(haveGadgets && haveLiveHosts && !haveLoggers);
+ recordQueryAction->setEnabled(haveLoggers);
+ recordStopAction->setEnabled(haveLoggers);
+ recordDetachAction->setEnabled(haveLoggers);
+#endif
+}
+
+void PmView::updateToolbarLocation()
+{
+#if QT_VERSION >= 0x040300
+ setUnifiedTitleAndToolBarOnMac(globalSettings.nativeToolbar);
+#endif
+ if (globalSettings.toolbarLocation)
+ addToolBar(Qt::RightToolBarArea, toolBar);
+ else
+ addToolBar(Qt::TopToolBarArea, toolBar);
+}
+
+void PmView::updateToolbarOrientation(Qt::Orientation orientation)
+{
+ (void)orientation;
+ // TODO
+}
+
+void PmView::setButtonState(QedTimeButton::State state)
+{
+ my.statusBar->timeButton()->setButtonState(state);
+}
+
+void PmView::step(bool live, QmcTime::Packet *packet)
+{
+ if (live)
+ liveGroup->step(packet);
+ else
+ archiveGroup->step(packet);
+}
+
+void PmView::VCRMode(bool live, QmcTime::Packet *packet, bool drag)
+{
+ if (live)
+ liveGroup->VCRMode(packet, drag);
+ else
+ archiveGroup->VCRMode(packet, drag);
+}
+
+void PmView::timeZone(bool live, QmcTime::Packet *packet, char *tzdata)
+{
+ if (live)
+ liveGroup->setTimezone(packet, tzdata);
+ else
+ archiveGroup->setTimezone(packet, tzdata);
+}
+
+void PmView::filePrint()
+{
+ QMessageBox::information(this, pmProgname, "Print, print, print... whirrr");
+}
+
+void PmView::fileQuit()
+{
+ QApplication::exit(0);
+}
+
+void PmView::helpManual()
+{
+ bool ok;
+ QString documents("file://");
+ QString separator = QString(__pmPathSeparator());
+ documents.append(pmGetConfig("PCP_HTML_DIR"));
+ documents.append(separator).append("index.html");
+ ok = QDesktopServices::openUrl(QUrl(documents, QUrl::TolerantMode));
+ if (!ok) {
+ documents.prepend("Failed to open:\n");
+ QMessageBox::warning(this, pmProgname, documents);
+ }
+}
+
+void PmView::helpTutorial()
+{
+ bool ok;
+ QString documents("file://");
+ QString separator = QString(__pmPathSeparator());
+ documents.append(pmGetConfig("PCP_HTML_DIR"));
+ documents.append(separator).append("tutorial.html");
+ ok = QDesktopServices::openUrl(QUrl(documents, QUrl::TolerantMode));
+ if (!ok) {
+ documents.prepend("Failed to open:\n");
+ QMessageBox::warning(this, pmProgname, documents);
+ }
+}
+
+void PmView::helpAbout()
+{
+#if 0
+ AboutDialog about(this);
+ about.exec();
+#endif
+}
+
+void PmView::helpSeeAlso()
+{
+#if 0
+ SeeAlsoDialog seealso(this);
+ seealso.exec();
+#endif
+}
+
+void PmView::whatsThis()
+{
+ QWhatsThis::enterWhatsThisMode();
+}
+
+void PmView::optionsTimeControl()
+{
+ if (activeView()->isArchiveSource()) {
+ if (my.archiveHidden)
+ pmtime->showArchiveTimeControl();
+ else
+ pmtime->hideArchiveTimeControl();
+ my.archiveHidden = !my.archiveHidden;
+ timeControlAction->setChecked(!my.archiveHidden);
+ }
+ else {
+ if (my.liveHidden)
+ pmtime->showLiveTimeControl();
+ else
+ pmtime->hideLiveTimeControl();
+ my.liveHidden = !my.liveHidden;
+ timeControlAction->setChecked(!my.liveHidden);
+ }
+}
+
+void PmView::optionsToolbar()
+{
+ if (my.toolbarHidden)
+ toolBar->show();
+ else
+ toolBar->hide();
+ my.toolbarHidden = !my.toolbarHidden;
+}
+
+void PmView::optionsMenubar()
+{
+ if (my.menubarHidden)
+ MenuBar->show();
+ else
+ MenuBar->hide();
+ my.menubarHidden = !my.menubarHidden;
+}
+
+void PmView::optionsConsole()
+{
+#if 0
+ if (pmDebug) {
+ if (my.consoleHidden)
+ console->show();
+ else
+ console->hide();
+ my.consoleHidden = !my.consoleHidden;
+ }
+#endif
+}
+
+void PmView::optionsNewPmchart()
+{
+ QProcess *buddy = new QProcess(this);
+ QStringList arguments;
+ QString port;
+
+ port.setNum(pmtime->port());
+ arguments << "-p" << port;
+ for (unsigned int i = 0; i < archiveGroup->numContexts(); i++) {
+ QmcSource source = archiveGroup->context(i)->source();
+ arguments << "-a" << source.source();
+ }
+ for (unsigned int i = 0; i < liveGroup->numContexts(); i++) {
+ QmcSource source = liveGroup->context(i)->source();
+ arguments << "-h" << source.source();
+ }
+ if (Lflag)
+ arguments << "-L";
+ buddy->start("pmview", arguments);
+}
+
+bool PmView::isViewRecording()
+{
+ return activeView()->isRecording();
+}
+
+bool PmView::isArchiveView()
+{
+ return activeView()->isArchiveSource();
+}
+
+void PmView::setDateLabel(time_t seconds, QString tz)
+{
+ char datestring[32];
+ QString label;
+
+ if (seconds) {
+ pmCtime(&seconds, datestring);
+ label = tr(datestring);
+ label.remove(10, 9);
+ label.replace(15, 1, " ");
+ label.append(tz);
+ }
+ else {
+ label = tr("");
+ }
+ my.statusBar->setDateText(label);
+}
+
+void PmView::setDateLabel(QString label)
+{
+ my.statusBar->setDateText(label);
+}
+
+void PmView::setRecordState(bool record)
+{
+ liveGroup->newButtonState(liveGroup->pmtimeState(),
+ QmcTime::NormalMode, record);
+ setButtonState(liveGroup->buttonState());
+ enableUi();
+}
+
+void PmView::recordStart()
+{
+ if (activeView()->startRecording())
+ setRecordState(true);
+}
+
+void PmView::recordStop()
+{
+ activeView()->stopRecording();
+}
+
+void PmView::recordQuery()
+{
+ activeView()->queryRecording();
+}
+
+void PmView::recordDetach()
+{
+ activeView()->detachLoggers();
+}
+
+QList<QAction*> PmView::toolbarActionsList()
+{
+ return my.toolbarActionsList;
+}
+
+QList<QAction*> PmView::enabledActionsList()
+{
+ return my.enabledActionsList;
+}
+
+void PmView::setupEnabledActionsList()
+{
+ // ToolbarActionsList is a list of all Actions available.
+ // The SeparatorsList contains Actions that are group "breaks", and
+ // which must be followed by a separator (if they are not the final
+ // action in the toolbar, of course).
+ // Finally the enabledActionsList lists the default enabled Actions.
+
+ my.toolbarActionsList << filePrintAction;
+ addSeparatorAction(); // end exported formats
+ my.toolbarActionsList << recordStartAction << recordStopAction;
+ addSeparatorAction(); // end recording group
+ //my.toolbarActionsList << editSettingsAction;
+ //addSeparatorAction(); // end settings group
+ my.toolbarActionsList << timeControlAction;
+ addSeparatorAction(); // end other processes
+ my.toolbarActionsList << helpManualAction << helpWhatsThisAction;
+
+ // needs to match pmview.ui
+ my.enabledActionsList << filePrintAction;
+
+ if (globalSettings.toolbarActions.size() > 0) {
+ setEnabledActionsList(globalSettings.toolbarActions, false);
+ updateToolbarContents();
+ }
+}
+
+void PmView::addSeparatorAction()
+{
+ int index = my.toolbarActionsList.size() - 1;
+ my.separatorsList << my.toolbarActionsList.at(index);
+}
+
+void PmView::updateToolbarContents()
+{
+ bool needSeparator = false;
+
+ toolBar->clear();
+ for (int i = 0; i < my.toolbarActionsList.size(); i++) {
+ QAction *action = my.toolbarActionsList.at(i);
+ if (my.enabledActionsList.contains(action)) {
+ toolBar->addAction(action);
+ if (needSeparator) {
+ toolBar->insertSeparator(action);
+ needSeparator = false;
+ }
+ }
+ if (my.separatorsList.contains(action))
+ needSeparator = true;
+ }
+}
+
+void PmView::setEnabledActionsList(QStringList tools, bool redisplay)
+{
+ my.enabledActionsList.clear();
+ for (int i = 0; i < my.toolbarActionsList.size(); i++) {
+ QAction *action = my.toolbarActionsList.at(i);
+ if (tools.contains(action->iconText()))
+ my.enabledActionsList.append(action);
+ }
+
+ if (redisplay) {
+ my.toolbarHidden = (my.enabledActionsList.size() == 0);
+ toolbarAction->setChecked(my.toolbarHidden);
+ if (my.toolbarHidden)
+ toolBar->hide();
+ else
+ toolBar->show();
+ }
+}
+
+void View::init(SceneGroup *group, QMenu *menu, QString title)
+{
+ my.group = group;
+ QedViewControl::init(group, menu, title, globalSettings.loggerDelta);
+}
+
+QStringList View::hostList(bool)
+{
+ // TODO
+ return QStringList();
+}
+
+QString View::pmloggerSyntax(bool)
+{
+// TODO
+#if 0
+ View *view = pmview->activeView();
+ QString configdata;
+
+ if (selectedOnly)
+ configdata.append(pmview->activeGadget()->pmloggerSyntax());
+ else
+ for (int c = 0; c < view->gadgetCount(); c++)
+ configdata.append(gadget(c)->pmloggerSyntax());
+ return configdata;
+#else
+ return NULL;
+#endif
+}
+
+bool View::saveConfig(QString filename, bool hostDynamic,
+ bool sizeDynamic, bool allViews, bool allCharts)
+{
+// TODO
+#if 0
+ return SaveViewDialog::saveView(filename,
+ hostDynamic, sizeDynamic, allViews, allCharts);
+#else
+ return false;
+#endif
+}
+
+bool View::stopRecording()
+{
+// TODO
+#if 0
+ QString errmsg;
+ bool error = ViewControl::stopRecording(errmsg);
+ QStringList archiveList = ViewControl::archiveList();
+
+ for (int i = 0; i < archiveList.size(); i++) {
+ QString archive = archiveList.at(i);
+ int sts;
+
+ console->post("View::stopRecording opening archive %s",
+ (const char *)archive.toAscii());
+ if ((sts = archiveGroup->use(PM_CONTEXT_ARCHIVE, archive)) < 0) {
+ errmsg.append(QApplication::tr("Cannot open PCP archive: "));
+ errmsg.append(archive);
+ errmsg.append("\n");
+ errmsg.append(pmErrStr(sts));
+ errmsg.append("\n");
+ error = true;
+ }
+ else {
+ archiveGroup->updateBounds();
+ QmcSource source = archiveGroup->context()->source();
+ pmtime->addArchive(source.start(), source.end(),
+ source.timezone(), source.host(), true);
+ }
+ }
+
+ // If all is well, we can now create the new "Record" View.
+ // Order of cleanup and changing Record mode state is different
+ // in the error case to non-error case, this is important for
+ // getting the window state correct (i.e. pmview->enableUi()).
+
+ if (error) {
+ cleanupRecording();
+ pmview->setRecordState(false);
+ QMessageBox::warning(NULL, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ else {
+ // Make the current View stop recording before changing Views
+ pmview->setRecordState(false);
+
+ View *view = new View;
+ console->post("View::stopRecording creating view: delta=%.2f pos=%.2f",
+ App::timevalToSeconds(*pmtime->archiveInterval()),
+ App::timevalToSeconds(*pmtime->archivePosition()));
+ // TODO: may need to update archive samples/visible?
+ view->init(archiveGroup, pmview->viewMenu(), "Record");
+ pmview->addActiveView(view);
+ OpenViewDialog::openView((const char *)ViewControl::view().toAscii());
+ cleanupRecording();
+ }
+ return error;
+#else
+ return false;
+#endif
+}
+
+bool View::queryRecording(void)
+{
+ QString errmsg;
+ bool error = QedViewControl::queryRecording(errmsg);
+
+ if (error) {
+ pmview->setRecordState(false);
+ QMessageBox::warning(NULL, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ return error;
+}
+
+bool View::detachLoggers(void)
+{
+ QString errmsg;
+ bool error = QedViewControl::detachLoggers(errmsg);
+
+ if (error) {
+ pmview->setRecordState(false);
+ QMessageBox::warning(NULL, pmProgname, errmsg,
+ QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
+ QMessageBox::NoButton, QMessageBox::NoButton);
+ }
+ else {
+ pmview->setRecordState(false);
+ cleanupRecording();
+ }
+ return error;
+}
diff --git a/src/pmview/pmview.desktop b/src/pmview/pmview.desktop
new file mode 100644
index 0000000..4129bcd
--- /dev/null
+++ b/src/pmview/pmview.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Name=PCP View
+Comment=3D tool for displaying Performance Co-Pilot metrics
+Exec=pmview
+Icon=pmview
+Terminal=false
+Type=Application
+Categories=System;Utility;Network;Qt;
diff --git a/src/pmview/pmview.h b/src/pmview/pmview.h
new file mode 100644
index 0000000..bd402ad
--- /dev/null
+++ b/src/pmview/pmview.h
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2007-2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef PMVIEW_H
+#define PMVIEW_H
+
+#include <Inventor/Qt/SoQt.h>
+#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
+#include <Inventor/nodes/SoDrawStyle.h>
+
+#include "ui_pmview.h"
+
+#include "qmc_time.h"
+#include "qed_statusbar.h"
+#include "qed_viewcontrol.h"
+
+class ModList;
+class SceneGroup;
+
+class View : public QedViewControl
+{
+public:
+ View() : QedViewControl() { };
+ virtual ~View() { };
+
+ void init(SceneGroup *, QMenu *, QString);
+ SceneGroup *group() const { return my.group; }
+
+ bool saveConfig(QString, bool, bool, bool, bool);
+ QStringList hostList(bool);
+ QString pmloggerSyntax(bool);
+
+ bool stopRecording();
+ bool queryRecording();
+ bool detachLoggers();
+
+private:
+ struct {
+ SceneGroup *group;
+ } my;
+};
+
+class PmView : public QMainWindow, public Ui::PmView
+{
+ Q_OBJECT
+
+public:
+ PmView();
+
+ typedef enum {
+ nothing = 0,
+ // fetch = 0x1, -- TODO
+ metrics = 0x2,
+ inventor = 0x4,
+ metricLabel = 0x8,
+ timeLabel = 0x10,
+ all = 0xffffffff,
+ } RenderOptions;
+
+ static int defaultFontSize();
+ static double defaultViewDelta() { return 1.0; } // seconds
+ static double defaultLoggerDelta() { return 1.0; }
+ static int defaultTimeout() { return 3000; } // milliseconds
+ static int minimumPoints() { return 2; }
+ static int maximumPoints() { return 360; }
+ static int maximumLegendLength() { return 120; } // chars
+ static int minimumViewHeight() { return 80; } // pixels
+
+ bool view(bool, float, float, float, float, float);
+ void render(RenderOptions options, time_t);
+ View *activeView() { return my.viewList.at(my.activeView); }
+ bool isViewRecording();
+ bool isArchiveView();
+
+ virtual void step(bool livemode, QmcTime::Packet *pmtime);
+ virtual void VCRMode(bool livemode, QmcTime::Packet *pmtime, bool drag);
+ virtual void timeZone(bool livemode, QmcTime::Packet *pmtime, char *tzdata);
+ virtual void setDateLabel(QString label);
+ virtual void setDateLabel(time_t seconds, QString tz);
+ virtual void setButtonState(QedTimeButton::State state);
+ virtual void setRecordState(bool recording);
+
+ virtual QMenu *createPopupMenu();
+ virtual void updateToolbarContents();
+ virtual void updateToolbarLocation();
+ virtual QList<QAction*> toolbarActionsList();
+ virtual QList<QAction*> enabledActionsList();
+ virtual void setupEnabledActionsList();
+ virtual void addSeparatorAction();
+ virtual void setEnabledActionsList(QStringList tools, bool redisplay);
+
+ // Adjusted height for exporting images (without UI elements)
+ int exportHeight()
+ { return height() - menuBar()->height() - toolBar->height(); }
+
+ SoQtExaminerViewer *viewer() { return my.viewer; }
+ static void selectionCB(ModList *, bool);
+
+public slots:
+ virtual void init();
+ virtual void quit();
+ virtual void enableUi();
+ virtual void filePrint();
+ virtual void fileQuit();
+ virtual void helpManual();
+ virtual void helpTutorial();
+ virtual void helpAbout();
+ virtual void helpSeeAlso();
+ virtual void whatsThis();
+ virtual void optionsNewPmchart();
+ virtual void optionsTimeControl();
+ virtual void optionsMenubar();
+ virtual void optionsToolbar();
+ virtual void optionsConsole();
+ virtual void recordStart();
+ virtual void recordQuery();
+ virtual void recordStop();
+ virtual void recordDetach();
+ virtual void updateToolbarOrientation(Qt::Orientation);
+
+protected slots:
+ virtual void languageChange();
+ virtual void closeEvent(QCloseEvent *);
+
+private:
+ struct {
+ bool dialogsSetup;
+ bool liveHidden;
+ bool archiveHidden;
+ bool menubarHidden;
+ bool toolbarHidden;
+ bool consoleHidden;
+
+ QMenu *viewMenu;
+ QList<QAction*> separatorsList; // separator follow these
+ QList<QAction*> toolbarActionsList; // all toolbar actions
+ QList<QAction*> enabledActionsList; // currently visible actions
+
+ QList<View *>viewList;
+ int activeView;
+
+ SoSeparator *root;
+ SoDrawStyle *drawStyle;
+ SoQtExaminerViewer *viewer; // The examiner window
+
+ QString text;
+ QString prevText;
+ QedStatusBar *statusBar;
+ } my;
+};
+
+#endif // PMVIEW_H
diff --git a/src/pmview/pmview.info.in b/src/pmview/pmview.info.in
new file mode 100644
index 0000000..bfa34df
--- /dev/null
+++ b/src/pmview/pmview.info.in
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">
+<plist version="0.9">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string>pmview.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>@pkg_version@</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>pmview</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.aconex.pmview</string>
+</dict>
+</plist>
diff --git a/src/pmview/pmview.pro b/src/pmview/pmview.pro
new file mode 100644
index 0000000..e546eba
--- /dev/null
+++ b/src/pmview/pmview.pro
@@ -0,0 +1,34 @@
+TEMPLATE = app
+LANGUAGE = C++
+HEADERS = main.h pmview.h colorlist.h \
+ barmod.h barobj.h baseobj.h \
+ defaultobj.h gridobj.h labelobj.h stackobj.h \
+ launch.h viewobj.h pipeobj.h link.h xing.h \
+ scenefileobj.h scenegroup.h \
+ colorscalemod.h colormod.h colorscale.h \
+ metriclist.h modlist.h modulate.h \
+ scalemod.h stackmod.h togglemod.h \
+ text.h yscalemod.h pcpcolor.h
+SOURCES = main.cpp colorlist.cpp barmod.cpp barobj.cpp baseobj.cpp \
+ defaultobj.cpp gridobj.cpp labelobj.cpp stackobj.cpp \
+ launch.cpp viewobj.cpp pipeobj.cpp link.cpp xing.cpp \
+ scenefileobj.cpp scenegroup.cpp \
+ colorscalemod.cpp colormod.cpp colorscale.cpp \
+ scalemod.cpp stackmod.cpp togglemod.cpp yscalemod.cpp \
+ metricList.cpp modlist.cpp modulate.cpp pcpcolor.cpp \
+ text.cpp error.cpp gram.cpp lex.cpp pmview.cpp
+FORMS = pmview.ui
+ICON = pmview.icns
+RC_FILE = pmview.rc
+RESOURCES = pmview.qrc
+INCLUDEPATH += /usr/include/Coin2
+INCLUDEPATH += ../include ../libpcp_qmc/src ../libpcp_qed/src
+CONFIG += qt warn_on
+LIBS += -L../libpcp/src
+LIBS += -L../libpcp_qmc/src -L../libpcp_qmc/src/$$DESTDIR
+LIBS += -L../libpcp_qed/src -L../libpcp_qed/src/$$DESTDIR
+LIBS += -lpcp_qed -lpcp_qmc -lpcp -lCoin -lSoQt
+win32:LIBS += -lwsock32
+QT += network
+QMAKE_INFO_PLIST = pmview.info
+QMAKE_CXXFLAGS += $$(PCP_CFLAGS)
diff --git a/src/pmview/pmview.qrc b/src/pmview/pmview.qrc
new file mode 100644
index 0000000..9694fc1
--- /dev/null
+++ b/src/pmview/pmview.qrc
@@ -0,0 +1,23 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+ <file>images/aboutpcp.png</file>
+ <file>images/aboutqt.png</file>
+ <file>images/pmview.png</file>
+ <file>images/pmtime.png</file>
+ <file>images/whatsthis.png</file>
+ <file>images/document-print.png</file>
+ <file>images/help-browser.png</file>
+ <file>images/help-contents.png</file>
+ <file>images/play_live.png</file>
+ <file>images/stop_live.png</file>
+ <file>images/play_record.png</file>
+ <file>images/stop_record.png</file>
+ <file>images/play_archive.png</file>
+ <file>images/stop_archive.png</file>
+ <file>images/back_archive.png</file>
+ <file>images/stepfwd_archive.png</file>
+ <file>images/stepback_archive.png</file>
+ <file>images/fastfwd_archive.png</file>
+ <file>images/fastback_archive.png</file>
+</qresource>
+</RCC>
diff --git a/src/pmview/pmview.ui b/src/pmview/pmview.ui
new file mode 100644
index 0000000..ed4fafa
--- /dev/null
+++ b/src/pmview/pmview.ui
@@ -0,0 +1,386 @@
+<ui version="4.0" >
+ <class>PmView</class>
+ <widget class="QMainWindow" name="PmView" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>470</width>
+ <height>470</height>
+ </rect>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>182</width>
+ <height>196</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>PCP Viewer</string>
+ </property>
+ <property name="windowIcon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/pmview.png</normaloff>:/images/pmview.png</iconset>
+ </property>
+ <property name="iconSize" >
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <widget class="QWidget" name="widget" >
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="MenuBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>370</width>
+ <height>24</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="Help" >
+ <property name="title" >
+ <string>&amp;Help</string>
+ </property>
+ <addaction name="helpManualAction" />
+ <addaction name="helpTutorialAction" />
+ <addaction name="separator" />
+ <addaction name="helpAboutAction" />
+ <addaction name="helpSeeAlsoAction" />
+ <addaction name="separator" />
+ <addaction name="helpWhatsThisAction" />
+ </widget>
+ <widget class="QMenu" name="Options" >
+ <property name="title" >
+ <string>&amp;Options</string>
+ </property>
+ <addaction name="timeControlAction" />
+ <addaction name="menubarAction" />
+ <addaction name="toolbarAction" />
+ <addaction name="consoleAction" />
+ </widget>
+ <widget class="QMenu" name="Record" >
+ <property name="title" >
+ <string>&amp;Record</string>
+ </property>
+ <addaction name="recordStartAction" />
+ <addaction name="recordQueryAction" />
+ <addaction name="recordStopAction" />
+ <addaction name="separator" />
+ <addaction name="recordDetachAction" />
+ </widget>
+ <widget class="QMenu" name="File" >
+ <property name="title" >
+ <string>&amp;File</string>
+ </property>
+ <addaction name="fileQuitAction" />
+ </widget>
+ <addaction name="File" />
+ <addaction name="Record" />
+ <addaction name="Options" />
+ <addaction name="separator" />
+ <addaction name="Help" />
+ </widget>
+ <widget class="QToolBar" name="toolBar" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>24</y>
+ <width>370</width>
+ <height>36</height>
+ </rect>
+ </property>
+ <property name="sizePolicy" >
+ <sizepolicy vsizetype="Fixed" hsizetype="MinimumExpanding" >
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize" >
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="windowTitle" >
+ <string>Toolbar</string>
+ </property>
+ <property name="whatsThis" >
+ <string>Configurable toolbar, use the Preferences dialog to change its contents</string>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <attribute name="toolBarArea" >
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak" >
+ <bool>false</bool>
+ </attribute>
+ <addaction name="filePrintAction" />
+ </widget>
+ <action name="filePrintAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/document-print.png</normaloff>:/images/document-print.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Print...</string>
+ </property>
+ <property name="iconText" >
+ <string>Print</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+P</string>
+ </property>
+ </action>
+ <action name="fileQuitAction" >
+ <property name="text" >
+ <string>&amp;Quit</string>
+ </property>
+ <property name="iconText" >
+ <string>Quit</string>
+ </property>
+ <property name="shortcut" >
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="timeControlAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/pmtime.png</normaloff>:/images/pmtime.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Time Controls</string>
+ </property>
+ </action>
+ <action name="menubarAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="checked" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Menubar</string>
+ </property>
+ </action>
+ <action name="toolbarAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Toolbar</string>
+ </property>
+ </action>
+ <action name="consoleAction" >
+ <property name="checkable" >
+ <bool>true</bool>
+ </property>
+ <property name="text" >
+ <string>Console</string>
+ </property>
+ </action>
+ <action name="helpManualAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/help-contents.png</normaloff>:/images/help-contents.png</iconset>
+ </property>
+ <property name="text" >
+ <string>&amp;Manual</string>
+ </property>
+ <property name="iconText" >
+ <string>Manual</string>
+ </property>
+ <property name="shortcut" >
+ <string>F1</string>
+ </property>
+ </action>
+ <action name="helpTutorialAction" >
+ <property name="text" >
+ <string>Tutorial</string>
+ </property>
+ <property name="iconText" >
+ <string>Tutorial</string>
+ </property>
+ </action>
+ <action name="helpAboutAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/pmview.png</normaloff>:/images/pmview.png</iconset>
+ </property>
+ <property name="text" >
+ <string>About</string>
+ </property>
+ <property name="iconText" >
+ <string>About</string>
+ </property>
+ <property name="shortcut" >
+ <string/>
+ </property>
+ </action>
+ <action name="helpSeeAlsoAction" >
+ <property name="text" >
+ <string>See Also</string>
+ </property>
+ <property name="iconText" >
+ <string>See Also</string>
+ </property>
+ </action>
+ <action name="helpWhatsThisAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/whatsthis.png</normaloff>:/images/whatsthis.png</iconset>
+ </property>
+ <property name="text" >
+ <string>What's This</string>
+ </property>
+ <property name="iconText" >
+ <string>What's This</string>
+ </property>
+ <property name="shortcut" >
+ <string>Shift+F1</string>
+ </property>
+ </action>
+ <action name="recordStartAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/camera-video.png</normaloff>:/images/camera-video.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Start...</string>
+ </property>
+ </action>
+ <action name="recordQueryAction" >
+ <property name="text" >
+ <string>Query</string>
+ </property>
+ </action>
+ <action name="recordStopAction" >
+ <property name="icon" >
+ <iconset resource="pmview.qrc" >
+ <normaloff>:/images/camera-video-close.png</normaloff>:/images/camera-video-close.png</iconset>
+ </property>
+ <property name="text" >
+ <string>Stop</string>
+ </property>
+ </action>
+ <action name="recordDetachAction" >
+ <property name="text" >
+ <string>Detach</string>
+ </property>
+ </action>
+ </widget>
+ <includes>
+ <include location="local" >qmc_time.h</include>
+ <include location="local" >qprinter.h</include>
+ <include location="local" >qed_timebutton.h</include>
+ </includes>
+ <resources>
+ <include location="pmview.qrc" />
+ </resources>
+ <connections>
+ <connection>
+ <sender>filePrintAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>filePrint()</slot>
+ </connection>
+ <connection>
+ <sender>fileQuitAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>fileQuit()</slot>
+ </connection>
+ <connection>
+ <sender>helpTutorialAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>helpTutorial()</slot>
+ </connection>
+ <connection>
+ <sender>helpAboutAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>helpAbout()</slot>
+ </connection>
+ <connection>
+ <sender>helpSeeAlsoAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>helpSeeAlso()</slot>
+ </connection>
+ <connection>
+ <sender>helpWhatsThisAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>whatsThis()</slot>
+ </connection>
+ <connection>
+ <sender>timeControlAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>optionsTimeControl()</slot>
+ </connection>
+ <connection>
+ <sender>menubarAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>optionsMenubar()</slot>
+ </connection>
+ <connection>
+ <sender>toolbarAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>optionsToolbar()</slot>
+ </connection>
+ <connection>
+ <sender>consoleAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>optionsConsole()</slot>
+ </connection>
+ <connection>
+ <sender>helpManualAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>helpManual()</slot>
+ </connection>
+ <connection>
+ <sender>recordStartAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>recordStart()</slot>
+ </connection>
+ <connection>
+ <sender>recordQueryAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>recordQuery()</slot>
+ </connection>
+ <connection>
+ <sender>recordStopAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>recordStop()</slot>
+ </connection>
+ <connection>
+ <sender>recordDetachAction</sender>
+ <signal>triggered()</signal>
+ <receiver>PmView</receiver>
+ <slot>recordDetach()</slot>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/pmview/scalemod.cpp b/src/pmview/scalemod.cpp
new file mode 100644
index 0000000..49c7a36
--- /dev/null
+++ b/src/pmview/scalemod.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include "main.h"
+#include "scalemod.h"
+#include "modlist.h"
+#include "launch.h"
+
+#include <iostream>
+using namespace std;
+
+ScaleMod::~ScaleMod()
+{
+}
+
+ScaleMod::ScaleMod(const char *str,
+ double scale,
+ const SbColor &color,
+ SoNode *obj,
+ float xScale,
+ float yScale,
+ float zScale)
+: Modulate(str, scale, color),
+ _state(Modulate::start),
+ _color(0),
+ _scale(0),
+ _xScale(xScale),
+ _yScale(yScale),
+ _zScale(zScale)
+{
+ _root = new SoSeparator;
+
+ _color = new SoBaseColor;
+ _color->rgb.setValue(_errorColor.getValue());
+ _root->addChild(_color);
+
+ if (_metrics->numValues() == 1 && status() >= 0) {
+
+ _scale = new SoScale;
+ _scale->scaleFactor.setValue(1.0, 1.0, 1.0);
+ _root->addChild(_scale);
+ _root->addChild(obj);
+
+ add();
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ScaleMod: Added " << str << " (Id = "
+ << _root->getName().getString() << ")" << endl;
+#endif
+ }
+
+ // Invalid metric
+ else
+ _root->addChild(obj);
+}
+
+void
+ScaleMod::refresh(bool fetchFlag)
+{
+ QmcMetric &metric = _metrics->metric(0);
+
+ if (status() < 0)
+ return;
+
+ if (fetchFlag)
+ metric.update();
+
+ if (metric.error(0) <= 0) {
+ if (_state != Modulate::error) {
+ _color->rgb.setValue(_errorColor.getValue());
+ _scale->scaleFactor.setValue((_xScale==0.0f ? 1.0 : theMinScale),
+ (_yScale==0.0f ? 1.0 : theMinScale),
+ (_zScale==0.0f ? 1.0 : theMinScale));
+ _state = Modulate::error;
+ }
+ }
+ else {
+ double value = metric.value(0) * theScale;
+ if (value > theNormError) {
+ if (_state != Modulate::saturated) {
+ _color->rgb.setValue(_saturatedColor.getValue());
+ _scale->scaleFactor.setValue(1.0, 1.0, 1.0);
+ _state = Modulate::saturated;
+ }
+ }
+ else {
+ if (_state != Modulate::normal) {
+ _color->rgb.setValue(_metrics->color(0).getValue());
+ _state = Modulate::normal;
+ }
+ if (value < Modulate::theMinScale)
+ value = Modulate::theMinScale;
+ else if (value > 1.0)
+ value = 1.0;
+ _scale->scaleFactor.setValue((_xScale==0.0f ? 1.0 : _xScale*value),
+ (_yScale==0.0f ? 1.0 : _yScale*value),
+ (_zScale==0.0f ? 1.0 : _zScale*value));
+ }
+ }
+}
+
+void
+ScaleMod::dump(QTextStream &os) const
+{
+ os << "ScaleMod: ";
+
+ if (status() < 0)
+ os << "Invalid metric";
+ else {
+ os << "state = ";
+ dumpState(os, _state);
+ os << ", scale = " << _xScale << ',' << _yScale << ',' << _zScale
+ << ": ";
+ _metrics->metric(0).dump(os, true);
+ }
+}
+
+void
+ScaleMod::infoText(QString &str, bool) const
+{
+ const QmcMetric &metric = _metrics->metric(0);
+ str = metric.spec(true, true, 0);
+ str.append(QChar('\n'));
+ if (_state == Modulate::error)
+ str.append(theErrorText);
+ else if (_state == Modulate::start)
+ str.append(theStartText);
+ else {
+ QString value;
+ str.append(value.setNum(metric.realValue(0), 'g', 4));
+ str.append(QChar(' '));
+ if (metric.desc().units().size() > 0)
+ str.append(metric.desc().units());
+ str.append(" [");
+ str.append(value.setNum(metric.value(0) * 100.0, 'g', 4));
+ str.append("% of expected max]");
+ }
+}
+
+void
+ScaleMod::launch(Launch &launch, bool) const
+{
+ if (status() < 0)
+ return;
+ launch.startGroup("point");
+ launch.addMetric(_metrics->metric(0), _metrics->color(0), 0);
+ launch.endGroup();
+}
+
+int
+ScaleMod::select(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ScaleMod::select: " << _metrics->metric(0) << endl;
+#endif
+ return 1;
+}
+
+int
+ScaleMod::remove(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ScaleMod::remove: " << _metrics->metric(0) << endl;
+#endif
+ return 0;
+}
diff --git a/src/pmview/scalemod.h b/src/pmview/scalemod.h
new file mode 100644
index 0000000..2a28adc
--- /dev/null
+++ b/src/pmview/scalemod.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _SCALEMOD_H_
+#define _SCALEMOD_H_
+
+#include "modulate.h"
+
+class SoBaseColor;
+class SoScale;
+class SoNode;
+class Launch;
+
+class ScaleMod : public Modulate
+{
+protected:
+
+ State _state;
+ SoBaseColor *_color;
+ SoScale *_scale;
+ float _xScale;
+ float _yScale;
+ float _zScale;
+
+public:
+
+ virtual ~ScaleMod();
+
+ ScaleMod(const char *metric, double scale, const SbColor &color,
+ SoNode *obj, float xScale, float yScale, float zScale);
+
+ virtual void refresh(bool fetchFlag);
+
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void infoText(QString &str, bool) const;
+
+ virtual void launch(Launch &launch, bool) const;
+
+ virtual void dump(QTextStream &) const;
+
+private:
+
+ ScaleMod();
+ ScaleMod(const ScaleMod &);
+ const ScaleMod &operator=(const ScaleMod &);
+ // Never defined
+};
+
+#endif /* _SCALEMOD_H_ */
diff --git a/src/pmview/scenefileobj.cpp b/src/pmview/scenefileobj.cpp
new file mode 100644
index 0000000..76d7446
--- /dev/null
+++ b/src/pmview/scenefileobj.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoRotationXYZ.h>
+#include <Inventor/nodes/SoCylinder.h>
+#include <Inventor/nodes/SoCube.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+
+#include <ctype.h>
+#include "stackmod.h"
+#include "togglemod.h"
+#include "scenefileobj.h"
+#include "defaultobj.h"
+#include "colorlist.h"
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ * Constructor for Generic Inventor Scene Object.
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+SceneFileObj::SceneFileObj (const DefaultObj & defs,
+ int c, int r,
+ int colSpan, int rowSpan,
+ BaseObj::Alignment align)
+ : ModObj (false, defs, c, r, colSpan, rowSpan, center)
+ , _align(align)
+ , _tag ("\n")
+{
+ _objtype |= SCENEFILEOBJ;
+
+ for ( int i=0; i < 3; i++) {
+ _color[i] = defs.baseColor(i);
+ }
+
+ cellHeight = defs.baseHeight();
+ _stackHeight = defs.pipeLength();
+
+ if ( _align == north || _align == south ) {
+ cellWidth = defs.baseHeight();
+ cellDepth = (defs.pipeLength() < cellHeight) ?
+ cellHeight : defs.pipeLength();
+ } else {
+ cellWidth = (defs.pipeLength() < cellHeight) ?
+ cellHeight : defs.pipeLength();
+ cellDepth = defs.baseHeight();
+ }
+
+ _width = colSpan * cellWidth;
+ _depth = rowSpan * cellDepth;
+ _sceneFileName[0] = '\0';
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
+ *
+\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void
+SceneFileObj::finishedAdd ()
+{
+ SoSeparator *sp;
+
+ BaseObj::addBase (_root);
+ if ((sp = readSceneFile())) {
+ _root->addChild (sp);
+ // To make sure the viewer will display a scene with a single
+ // IV scenegraph treat all IV scenegraphs as "modulated" objects.
+ ViewObj::theNumModObjects++;
+ } else {
+ pmprintf("Warning: Failed to read scene file \"%s\"\n", _sceneFileName);
+ }
+}
+
+void
+SceneFileObj::setTag (const char * s)
+{
+ _tag = s;
+ if ( strchr (s, '\n' ) == NULL ) {
+ _tag.append ("\n");
+ }
+}
+
+SoSeparator *
+SceneFileObj::readSceneFile(void)
+{
+ SoInput input;
+ SoSeparator *s;
+ FILE *f = NULL;
+
+ if (_sceneFileName[0] == '<') {
+ char *p = _sceneFileName+1;
+
+ while (*p) {
+ if (! isdigit(*p))
+ break;
+ p++;
+ }
+
+ if (*p == '\0') {
+ int fd = atoi(_sceneFileName+1);
+
+ if ((f = fdopen(fd, "r")) == NULL) {
+ return (NULL);
+ }
+ input.setFilePointer(f);
+ }
+ }
+
+ if (f == NULL) {
+ if (!input.openFile(_sceneFileName))
+ return NULL;
+ }
+ s = SoDB::readAll(&input);
+ input.closeFile();
+
+ return s;
+}
diff --git a/src/pmview/scenefileobj.h b/src/pmview/scenefileobj.h
new file mode 100644
index 0000000..0ddb1f5
--- /dev/null
+++ b/src/pmview/scenefileobj.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _SCENEFILEOBJ_H_
+#define _SCENEFILEOBJ_H_
+
+#include <Inventor/SoLists.h>
+#include "modobj.h"
+#include "modlist.h"
+
+class SceneFileObj : public ModObj
+{
+public:
+ virtual ~SceneFileObj () {}
+ SceneFileObj(const DefaultObj &, int, int, int, int, Alignment);
+
+ int width () const { return _width; }
+ int depth () const { return _depth; }
+
+ const char * name() const { return "SceneFile"; }
+ SoSeparator *readSceneFile(void);
+ void setSceneFileName(char *fname) { strcpy(_sceneFileName, fname); };
+
+ void finishedAdd ();
+ void setTag (const char *);
+
+private:
+ SceneFileObj ();
+ SceneFileObj (SceneFileObj const &);
+ SceneFileObj const& operator=(SceneFileObj const &);
+
+ int _width, _depth;
+ int cellWidth, cellDepth, cellHeight;
+ Alignment _align;
+ float _stackHeight;
+ QString _tag;
+
+ float _color[3];
+ char _sceneFileName[MAXPATHLEN];
+};
+
+#endif /* _SCENEFILEOBJ_H_ */
diff --git a/src/pmview/scenegroup.cpp b/src/pmview/scenegroup.cpp
new file mode 100644
index 0000000..810c089
--- /dev/null
+++ b/src/pmview/scenegroup.cpp
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "main.h"
+#include "pmview.h"
+#include "scenegroup.h"
+#include "qed_console.h"
+#include "qed_timecontrol.h"
+
+SceneGroup::SceneGroup() : QedGroupControl()
+{
+}
+
+SceneGroup::~SceneGroup()
+{
+}
+
+void SceneGroup::init(struct timeval *interval, struct timeval *position)
+{
+ QedGroupControl::init(interval, position);
+}
+
+bool SceneGroup::isArchiveSource(void)
+{
+ // Note: We purposefully are not using QmcGroup::mode() here, as we
+ // may not have initialised any contexts yet. In such a case, live
+ // mode is always returned (default, from the QmcGroup constructor).
+
+ return this == archiveGroup;
+}
+
+bool SceneGroup::isActive(QmcTime::Packet *packet)
+{
+ return (((activeGroup == archiveGroup) &&
+ (packet->source == QmcTime::ArchiveSource)) ||
+ ((activeGroup == liveGroup) &&
+ (packet->source == QmcTime::HostSource)));
+}
+
+bool SceneGroup::isRecording(QmcTime::Packet *packet)
+{
+ (void)packet;
+ return pmview->isViewRecording();
+}
+
+void SceneGroup::setButtonState(QedTimeButton::State state)
+{
+ pmview->setButtonState(state);
+}
+
+void SceneGroup::updateTimeAxis(void)
+{
+ QString tz, otz, unused;
+
+ if (numContexts() > 0 || isArchiveSource() == false) {
+ if (numContexts() > 0)
+ defaultTZ(unused, otz);
+ else
+ otz = QmcSource::localHost;
+ tz = otz;
+ pmview->setDateLabel((int)timePosition(), tz);
+ } else {
+ pmview->setDateLabel(tr("[No open archives]"));
+ }
+
+ if (console->logLevel(QedApp::DebugProtocol)) {
+ console->post(QedApp::DebugProtocol,
+ "SceneGroup::updateTimeAxis: tz=%s",
+ (const char *)tz.toAscii());
+ }
+}
+
+void SceneGroup::updateTimeButton(void)
+{
+ pmview->setButtonState(buttonState());
+}
+
+void SceneGroup::setupWorldView()
+{
+ activeGroup->QedGroupControl::setupWorldView(
+ pmtime->archiveInterval(), pmtime->archivePosition(),
+ pmtime->archiveStart(), pmtime->archiveEnd());
+}
+
+void SceneGroup::adjustLiveWorldViewForward(QmcTime::Packet *packet)
+{
+ double position = timePosition();
+
+ console->post("Fetching data at %s", QedApp::timeString(position));
+ fetch();
+
+ setTimeState(packet->state == QmcTime::StoppedState ?
+ StandbyState : ForwardState);
+
+ QedGroupControl::adjustLiveWorldViewForward(packet);
+ pmview->render(PmView::inventor, 0);
+}
+
+void SceneGroup::adjustArchiveWorldViewForward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("SceneGroup::adjustArchiveWorldViewForward");
+ setTimeState(ForwardState);
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ struct timeval timeval;
+ double position = timePosition();
+ QedApp::timevalFromSeconds(timePosition(), &timeval);
+ setArchiveMode(setmode, &timeval, delta);
+ console->post("Fetching data at %s", QedApp::timeString(position));
+ fetch();
+
+ QedGroupControl::adjustArchiveWorldViewForward(packet, setup);
+ pmview->render(PmView::inventor, 0);
+}
+
+void SceneGroup::adjustArchiveWorldViewBackward(QmcTime::Packet *packet, bool setup)
+{
+ console->post("SceneGroup::adjustArchiveWorldViewBackward");
+ setTimeState(BackwardState);
+
+ int setmode = PM_MODE_INTERP;
+ int delta = packet->delta.tv_sec;
+ if (packet->delta.tv_usec == 0) {
+ setmode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ delta = delta * 1000 + packet->delta.tv_usec / 1000;
+ setmode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+
+ struct timeval timeval;
+ double position = timePosition();
+ QedApp::timevalFromSeconds(timePosition(), &timeval);
+ setArchiveMode(setmode, &timeval, delta);
+ console->post("Fetching data at %s", QedApp::timeString(position));
+ fetch();
+
+ QedGroupControl::adjustArchiveWorldViewBackward(packet, setup);
+ pmview->render(PmView::inventor, 0);
+}
+
+//
+// Fetch all metric values across all scenes, and update the status bar.
+//
+void SceneGroup::adjustStep(QmcTime::Packet *packet)
+{
+ (void)packet; // no-op in pmview
+}
+
+void SceneGroup::step(QmcTime::Packet *packet)
+{
+ QedGroupControl::step(packet);
+ pmview->render(PmView::inventor, 0);
+}
+
+void SceneGroup::setTimezone(QmcTime::Packet *packet, char *tz)
+{
+ QedGroupControl::setTimezone(packet, tz);
+ if (isActive(packet))
+ updateTimeAxis();
+}
diff --git a/src/pmview/scenegroup.h b/src/pmview/scenegroup.h
new file mode 100644
index 0000000..0e40697
--- /dev/null
+++ b/src/pmview/scenegroup.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009, Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef SCENEGROUP_H
+#define SCENEGROUP_H
+
+#include <QtCore/QList>
+#include <QtGui/QColor>
+#include <QtGui/QPainter>
+#include <QtGui/QDockWidget>
+#include <QtGui/QAbstractButton>
+#include "qed_groupcontrol.h"
+#include "qmc_metric.h"
+#include "qmc_group.h"
+#include "qmc_time.h"
+
+class SceneGroup : public QedGroupControl
+{
+ Q_OBJECT
+
+public:
+ SceneGroup();
+ virtual ~SceneGroup();
+ void init(struct timeval *, struct timeval *);
+
+ bool isArchiveSource();
+ bool isActive(QmcTime::Packet *);
+ bool isRecording(QmcTime::Packet *);
+
+ void updateTimeAxis();
+ void updateTimeButton();
+
+ void setupWorldView();
+ void step(QmcTime::Packet *);
+ void setTimezone(QmcTime::Packet *, char *);
+
+protected:
+ void adjustLiveWorldViewForward(QmcTime::Packet *);
+ void adjustArchiveWorldViewForward(QmcTime::Packet *, bool);
+ void adjustArchiveWorldViewBackward(QmcTime::Packet *, bool);
+
+ void adjustStep(QmcTime::Packet *);
+ void setButtonState(QedTimeButton::State);
+
+private:
+ void refreshScenes(bool);
+
+// struct {
+// } my;
+};
+
+#endif // SCENEGROUP_H
diff --git a/src/pmview/stackmod.cpp b/src/pmview/stackmod.cpp
new file mode 100644
index 0000000..448b9bb
--- /dev/null
+++ b/src/pmview/stackmod.cpp
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/SoPath.h>
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoSelection.h>
+#include <Inventor/nodes/SoSwitch.h>
+#include "stackmod.h"
+#include "modlist.h"
+#include "launch.h"
+
+#include <iostream>
+using namespace std;
+
+//
+// Use debug flag LIBPMDA to trace stack refreshes
+//
+
+const float StackMod::theDefFillColor[] = { 0.35, 0.35, 0.35 };
+const char StackMod::theStackId = 's';
+
+StackMod::~StackMod()
+{
+}
+
+StackMod::StackMod(MetricList *metrics, SoNode *obj, StackMod::Height height)
+: Modulate(metrics),
+ _blocks(),
+ _switch(0),
+ _height(height),
+ _text(),
+ _selectCount(0),
+ _infoValue(0),
+ _infoMetric(0),
+ _infoInst(0)
+{
+ int numValues = _metrics->numValues();
+ int numMetrics = _metrics->numMetrics();
+ char buf[32];
+ float initScale = 0.0;
+ int m, i, v;
+
+ _root = new SoSeparator;
+
+ if (numValues > 0) {
+ m = numValues;
+ if (_height == fixed) {
+ m++;
+ _text.append(QChar('\n'));
+ }
+
+ _blocks.resize(m);
+ _infoValue = m+1;
+
+ initScale = 1.0 / (float)numValues;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::StackMod: numValues = "
+ << numValues << ", num of blocks = " << m << endl
+ << *_metrics;
+#endif
+
+ for (m = 0, v = 0; m < numMetrics; m++) {
+ const QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ StackBlock block;
+
+ block._sep = new SoSeparator;
+ sprintf(buf, "%c%d", theStackId, v);
+ block._sep->setName((SbName)buf);
+ _root->addChild(block._sep);
+
+ block._color = new SoBaseColor;
+ block._color->rgb.setValue(_errorColor.getValue());
+ block._sep->addChild(block._color);
+
+ block._scale = new SoScale;
+ block._scale->scaleFactor.setValue(1.0, initScale, 1.0);
+ block._sep->addChild(block._scale);
+
+ block._sep->addChild(obj);
+
+ block._state = Modulate::start;
+ block._selected = false;
+
+ if (_height == fixed || v < numValues - 1) {
+ block._tran = new SoTranslation();
+ block._tran->translation.setValue(0.0, initScale, 0.0);
+ _root->addChild(block._tran);
+ }
+ else {
+ block._tran = NULL;
+ }
+ _blocks[v] = block;
+ }
+ }
+
+ if (_height == fixed) {
+ StackBlock block;
+ block._sep = new SoSeparator;
+ _root->addChild(block._sep);
+ sprintf(buf, "%c%d", theStackId, v);
+ block._sep->setName((SbName)buf);
+
+ _switch = new SoSwitch();
+ _switch->whichChild.setValue(SO_SWITCH_ALL);
+ block._sep->addChild(_switch);
+
+ block._color = new SoBaseColor;
+ block._color->rgb.setValue(theDefFillColor);
+ _switch->addChild(block._color);
+
+ block._tran = NULL;
+ block._scale = new SoScale;
+ block._scale->scaleFactor.setValue(1.0, 0.0, 1.0);
+ block._state = Modulate::start;
+ block._selected = false;
+ _switch->addChild(block._scale);
+
+ _switch->addChild(obj);
+ _blocks[v] = block;
+ }
+
+ add();
+ }
+
+ // Invalid object
+ else {
+
+ _sts = -1;
+
+ SoBaseColor *tmpColor = new SoBaseColor();
+ tmpColor->rgb.setValue(_errorColor.getValue());
+ _root->addChild(tmpColor);
+
+ _root->addChild(obj);
+ }
+}
+
+void
+StackMod::refresh(bool fetchFlag)
+{
+ int numValues = _metrics->numValues();
+ int numMetrics = _metrics->numMetrics();
+ int m, i, v;
+ double sum = 0.0;
+
+ static QVector<double> values;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << endl << "StackMod::refresh" << endl;
+#endif
+
+ if (status() < 0)
+ return;
+
+ if (numValues > values.size())
+ values.resize(numValues);
+
+ for (m = 0, v = 0; m < numMetrics; m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ if (fetchFlag)
+ metric.update();
+ for (i = 0; i < metric.numValues(); i++, v++) {
+
+ StackBlock &block = _blocks[v];
+ double &value = values[v];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << '[' << v << "] ";
+#endif
+
+ if (metric.error(i) <= 0) {
+ if (block._state != Modulate::error) {
+ block._color->rgb.setValue(_errorColor.getValue());
+ block._state = Modulate::error;
+ }
+ value = Modulate::theMinScale;
+ sum += value;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << "Error, value set to " << value << endl;
+#endif
+
+ }
+ else if (block._state == Modulate::error ||
+ block._state == Modulate::start) {
+ block._state = Modulate::normal;
+ if (numMetrics == 1)
+ block._color->rgb.setValue(_metrics->color(v).getValue());
+ else
+ block._color->rgb.setValue(_metrics->color(m).getValue());
+ value = metric.value(i) * theScale;
+ if (value < theMinScale)
+ value = theMinScale;
+ sum += value;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << "Error->Normal, value = " << value << endl;
+#endif
+
+ }
+ else {
+ value = metric.value(i) * theScale;
+ if (value < theMinScale)
+ value = theMinScale;
+ sum += value;
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << "Normal, value = " << value << endl;
+#endif
+
+ }
+ }
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << "sum = " << sum << endl;
+#endif
+
+ if (sum > theNormError && _height != util) {
+ if (_blocks[0]._state != Modulate::saturated) {
+ for (v = 0; v < numValues; v++) {
+ StackBlock &block = _blocks[v];
+ if (block._state != Modulate::error) {
+ block._color->rgb.setValue(Modulate::_saturatedColor);
+ block._state = Modulate::saturated;
+ }
+ }
+ }
+ }
+ else {
+ for (m = 0, v = 0; m < numMetrics; m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ StackBlock &block = _blocks[v];
+ if (block._state == Modulate::saturated) {
+ block._state = Modulate::normal;
+ if (numMetrics == 1)
+ block._color->rgb.setValue(_metrics->color(v).getValue());
+ else
+ block._color->rgb.setValue(_metrics->color(m).getValue());
+ }
+ }
+ }
+ }
+
+ // Scale values to the range [0,1].
+ // Ensure that each block always has the minimum height to
+ // avoid planes clashing
+
+ if (sum > 1.0 || _height == util) {
+ double oldSum = sum;
+ double max = 1.0 - (theMinScale * (numValues - 1));
+ sum = 0.0;
+ for (v = 0; v < numValues; v++) {
+ double &value = values[v];
+ value /= oldSum;
+ sum += value;
+ if (sum > max) {
+ value -= sum - max;
+ sum -= sum - max;
+ }
+ if (value < theMinScale)
+ value = theMinScale;
+ max += theMinScale;
+ }
+ }
+
+ for (v = 0; v < numValues; v++) {
+
+ StackBlock &block = _blocks[v];
+ double &value = values[v];
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ cerr << '[' << v << "] scale = " << value << endl;
+#endif
+
+ block._scale->scaleFactor.setValue(1.0, value, 1.0);
+
+ if (v < numValues-1 || _height == fixed)
+ block._tran->translation.setValue(0.0, value, 0.0);
+ }
+
+ if (_height == fixed) {
+ sum = 1.0 - sum;
+ if (sum >= theMinScale) {
+ _switch->whichChild.setValue(SO_SWITCH_ALL);
+ _blocks[v]._scale->scaleFactor.setValue(1.0, sum, 1.0);
+ }
+ else {
+ _switch->whichChild.setValue(SO_SWITCH_NONE);
+ _blocks[v]._scale->scaleFactor.setValue(1.0, theMinScale, 1.0);
+ }
+ }
+}
+
+void
+StackMod::dump(QTextStream &os) const
+{
+ int m, i, v;
+
+ os << "StackMod: ";
+
+ if (status() < 0)
+ os << "Invalid metrics: " << pmErrStr(status()) << endl;
+ else {
+ os << endl;
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ os << " [" << v << "]: ";
+ if (_blocks[v]._selected == true)
+ os << '*';
+ else
+ os << ' ';
+ dumpState(os, _blocks[v]._state);
+ os << ": ";
+ metric.dump(os, true, i);
+ }
+ }
+ }
+}
+
+void
+StackMod::infoText(QString &str, bool selected) const
+{
+ int m = _infoMetric;
+ int i = _infoInst;
+ int v = _infoValue;
+ bool found = false;
+
+ if (selected && _selectCount == 1) {
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ const QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++)
+ if (_blocks[v]._selected) {
+ found = true;
+ break;
+ }
+ if (found)
+ break;
+ }
+ }
+
+ if (v >= _blocks.size()) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::infoText: infoText requested but nothing selected"
+ << endl;
+#endif
+ str = "";
+ }
+ else if (_height == fixed && v == _blocks.size() - 1) {
+ str = _text;
+ }
+ else {
+ const QmcMetric &metric = _metrics->metric(m);
+ str = metric.spec(true, true, i);
+ str.append(QChar('\n'));
+
+ if (_blocks[v]._state == Modulate::error)
+ str.append(theErrorText);
+ else if (_blocks[v]._state == Modulate::start)
+ str.append(theStartText);
+ else {
+ QString value;
+ str.append(value.setNum(metric.realValue(i), 'g', 4));
+ str.append(QChar(' '));
+ if (metric.desc().units().length() > 0)
+ str.append(metric.desc().units());
+ str.append(" [");
+ str.append(value.setNum(metric.value(i) * 100.0, 'g', 4));
+ str.append("% of expected max]");
+ }
+ }
+}
+
+void
+StackMod::launch(Launch &launch, bool all) const
+{
+ int m, i, v;
+ bool launchAll = all;
+
+ if (status() < 0)
+ return;
+
+ // If the filler block is selected, launch all metrics
+ if (!launchAll && _height == fixed &&
+ _blocks.last()._selected == true) {
+ launchAll = true;
+ }
+
+ if (_height == StackMod::util)
+ launch.startGroup("util");
+ else
+ launch.startGroup("stack");
+
+ for (m = 0, v = 0; m < _metrics->numMetrics(); m++) {
+ QmcMetric &metric = _metrics->metric(m);
+ for (i = 0; i < metric.numValues(); i++, v++) {
+ if ((_selectCount > 0 && _blocks[v]._selected == true) ||
+ _selectCount == 0 || launchAll == true) {
+
+ launch.addMetric(_metrics->metric(m),
+ _metrics->color(m),
+ i);
+ }
+ }
+ }
+
+ launch.endGroup();
+}
+
+void
+StackMod::selectAll()
+{
+ int i;
+
+ if (_selectCount == _blocks.size())
+ return;
+
+ theModList->selectAllId(_root, _blocks.size());
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::selectAll" << endl;
+#endif
+
+ for (i = 0; i < _blocks.size(); i++) {
+ if (_blocks[i]._selected == false) {
+ _selectCount++;
+ theModList->selectSingle(_blocks[i]._sep);
+ _blocks[i]._selected = true;
+ }
+ }
+}
+
+int
+StackMod::select(SoPath *path)
+{
+ int metric, inst, value;
+
+ findBlock(path, metric, inst, value, false);
+ if (value < _blocks.size() && _blocks[value]._selected == false) {
+ _blocks[value]._selected = true;
+ _selectCount++;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::select: value = " << value
+ << ", count = " << _selectCount << endl;
+#endif
+ }
+ return _selectCount;
+}
+
+int
+StackMod::remove(SoPath *path)
+{
+ int metric, inst, value;
+
+ findBlock(path, metric, inst, value, false);
+ if (value < _blocks.size() && _blocks[value]._selected == true) {
+ _blocks[value]._selected = false;
+ _selectCount--;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::remove: value = " << value
+ << ", count = " << _selectCount << endl;
+#endif
+
+ }
+
+#ifdef PCP_DEBUG
+ else if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::remove: did not remove " << value
+ << ", count = " << _selectCount << endl;
+#endif
+
+ return _selectCount;
+}
+
+void
+StackMod::selectInfo(SoPath *path)
+{
+ findBlock(path, _infoMetric, _infoInst, _infoValue);
+}
+
+void
+StackMod::removeInfo(SoPath *)
+{
+ _infoValue = _blocks.size();
+ _infoMetric = _infoInst = 0;
+}
+
+void
+StackMod::findBlock(SoPath *path, int &metric, int &inst,
+ int &value, bool idMetric)
+{
+ SoNode *node;
+ char *str;
+ int m, i, v;
+ char c;
+
+ for (i = path->getLength() - 1; i >= 0; --i) {
+ node = path->getNode(i);
+ str = (char *)(node->getName().getString());
+ if (strlen(str) && str[0] == theStackId)
+ break;
+ }
+
+ if (i >= 0) {
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "StackMod::findBlock: stack id = " << str << endl;
+#endif
+
+ sscanf(str, "%c%d", &c, &value);
+
+ if (value == 0 || idMetric == false) {
+ metric = 0;
+ inst = 0;
+ }
+ else {
+ m = 0;
+ v = value;
+ while (m < _metrics->numMetrics()) {
+ i = _metrics->metric(m).numValues();
+ if (v < i) {
+ metric = m;
+ inst = v;
+ break;
+ }
+ else {
+ v -= i;
+ m++;
+ }
+ }
+ }
+ }
+ else {
+ value = _blocks.size();
+ metric = inst = 0;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ cerr << "StackMod::findBlock: metric = " << metric
+ << ", inst = " << inst << ", value = " << value << endl;
+ }
+#endif
+
+ return;
+}
+
+void
+StackMod::setFillColor(const SbColor &col)
+{
+ if (_sts >= 0 && _height == fixed)
+ _blocks.last()._color->rgb.setValue(col.getValue());
+}
+
+void
+StackMod::setFillColor(int packedcol)
+{
+ SbColor col;
+ float dummy = 0;
+
+ col.setPackedValue(packedcol, dummy);
+ setFillColor(col);
+}
diff --git a/src/pmview/stackmod.h b/src/pmview/stackmod.h
new file mode 100644
index 0000000..8c0fbc5
--- /dev/null
+++ b/src/pmview/stackmod.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _STACKMOD_H_
+#define _STACKMOD_H_
+
+#include <QtCore/QVector>
+#include "modulate.h"
+
+class SoBaseColor;
+class SoTranslation;
+class SoScale;
+class SoNode;
+class SoSwitch;
+class Launch;
+
+struct StackBlock {
+ SoSeparator *_sep;
+ SoBaseColor *_color;
+ SoScale *_scale;
+ SoTranslation *_tran;
+ Modulate::State _state;
+ bool _selected;
+};
+
+typedef QVector<StackBlock> StackBlockList;
+
+class StackMod : public Modulate
+{
+public:
+
+ enum Height { unfixed, fixed, util };
+
+private:
+
+ static const float theDefFillColor[];
+ static const char theStackId;
+
+ StackBlockList _blocks;
+ SoSwitch *_switch;
+ Height _height;
+ QString _text;
+ int _selectCount;
+ int _infoValue;
+ int _infoMetric;
+ int _infoInst;
+
+public:
+
+ virtual ~StackMod();
+
+ StackMod(MetricList *metrics,
+ SoNode *obj,
+ Height height = unfixed);
+
+ void setFillColor(const SbColor &col);
+ void setFillColor(int packedcol);
+ void setFillText(const char *str)
+ { _text = str; }
+
+ virtual void refresh(bool fetchFlag);
+
+ virtual void selectAll();
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void selectInfo(SoPath *);
+ virtual void removeInfo(SoPath *);
+
+ virtual void infoText(QString &str, bool) const;
+
+ virtual void launch(Launch &launch, bool all) const;
+
+ virtual void dump(QTextStream &) const;
+
+private:
+
+ StackMod();
+ StackMod(const StackMod &);
+ const StackMod &operator=(const StackMod &);
+ // Never defined
+
+ void findBlock(SoPath *path, int &metric, int &inst,
+ int &value, bool idMetric = true);
+};
+
+#endif /* _STACKMOD_H_ */
diff --git a/src/pmview/stackobj.cpp b/src/pmview/stackobj.cpp
new file mode 100644
index 0000000..faa8d42
--- /dev/null
+++ b/src/pmview/stackobj.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoTransform.h>
+#include "stackobj.h"
+#include "colorlist.h"
+#include "defaultobj.h"
+
+#include <iostream>
+using namespace std;
+
+StackObj::~StackObj()
+{
+ delete _stack;
+}
+
+StackObj::StackObj(StackMod::Height height,
+ ViewObj::Shape shape,
+ bool baseFlag,
+ const DefaultObj &defaults,
+ int x, int y,
+ int cols, int rows,
+ BaseObj::Alignment align)
+: ModObj(baseFlag, defaults, x, y, cols, rows, align),
+ _width(0),
+ _depth(0),
+ _height(height),
+ _stack(0),
+ _text()
+{
+ _objtype |= STACKOBJ;
+ _shape = shape;
+}
+
+void
+StackObj::finishedAdd()
+{
+ int i;
+
+ BaseObj::addBase(_root);
+
+ if (_metrics.numMetrics() == 0) {
+ pmprintf("%s: Error: Stack object has no metrics\n",
+ pmProgname);
+ _length = 0;
+ }
+ else {
+ SoScale *blockScale = new SoScale();
+ blockScale->scaleFactor.setValue(_length, _maxHeight, _length);
+ _root->addChild(blockScale);
+
+ if (_metrics.numMetrics()) {
+ const char *colName = (const char *)_colors.toAscii();
+ const ColorSpec *colSpec = theColorLists.list(colName);
+ if (colSpec != NULL) {
+ if (colSpec->_scale)
+ pmprintf("%s: Warning: Color scale ignored for stack object.\n",
+ pmProgname);
+ else {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "StackObj::finishedAdd: Adding "
+ << colSpec->_list.length()
+ << " colors for " << _metrics.numMetrics()
+ << " metrics" << endl;
+#endif
+
+ for (i = 0; i < colSpec->_list.size(); i++)
+ _metrics.add(*(colSpec->_list)[i]);
+ }
+ }
+ else
+ pmprintf("%s: Warning: No colours specified for stack object, "
+ "defaulting to blue.\n", pmProgname);
+
+ _metrics.resolveColors(MetricList::perValue);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << "StackObj::finishedAdd: metrics: " << endl
+ << _metrics << endl;
+#endif
+
+ _stack = new StackMod(&_metrics, ViewObj::object(_shape), _height);
+ _root->addChild(_stack->root());
+
+ if (_text.length())
+ _stack->setFillText((const char *)_text.toAscii());
+
+ BaseObj::add(_stack);
+ ViewObj::theNumModObjects++;
+ }
+ else
+ _length = 0;
+ }
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0)
+ cerr << name() << "has length " << _length << endl;
+#endif
+
+ _width = baseWidth() + _length;
+ _depth = baseDepth() + _length;
+}
+
+void
+StackObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ BaseObj::setBaseSize(width(), depth());
+ BaseObj::setTran(xTran + (width() / 2.0),
+ zTran + (depth() / 2.0),
+ setWidth, setDepth);
+}
+
+QTextStream&
+operator<<(QTextStream& os, StackObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+StackObj::display(QTextStream& os) const
+{
+ BaseObj::display(os);
+ os << ", length = " << _length << ": ";
+ if (_stack)
+ os << *_stack << endl;
+ else
+ os << "stack undefined!" << endl;
+}
diff --git a/src/pmview/stackobj.h b/src/pmview/stackobj.h
new file mode 100644
index 0000000..76fed4a
--- /dev/null
+++ b/src/pmview/stackobj.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _STACKOBJ_H_
+#define _STACKOBJ_H_
+
+#include "modobj.h"
+#include "stackmod.h"
+
+class SoSeparator;
+
+class StackObj : public ModObj
+{
+protected:
+
+ int _width;
+ int _depth;
+ StackMod::Height _height;
+ ViewObj::Shape _shape;
+ StackMod *_stack;
+ QString _text;
+
+public:
+
+ virtual ~StackObj();
+
+ StackObj(StackMod::Height height,
+ ViewObj::Shape shape,
+ bool baseFlag,
+ const DefaultObj &defaults,
+ int x, int z,
+ int cols = 1, int rows = 1,
+ BaseObj::Alignment align = BaseObj::center);
+
+ virtual int width() const
+ { return _width; }
+ virtual int depth() const
+ { return _depth; }
+ StackMod::Height height() const
+ { return _height; }
+
+ void setFillText(const char *str)
+ { _text = str; }
+
+ virtual void finishedAdd();
+
+ virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ virtual const char* name() const
+ { return "Stack"; }
+
+ virtual void display(QTextStream& os) const;
+
+ friend QTextStream& operator<<(QTextStream& os, StackObj const& rhs);
+
+ virtual void setBarHeight (int h) { _maxHeight = h; }
+
+private:
+
+ StackObj();
+ StackObj(StackObj const&);
+ StackObj const& operator=(StackObj const &);
+};
+
+#endif /* _STACKOBJ_H_ */
diff --git a/src/pmview/text.cpp b/src/pmview/text.cpp
new file mode 100644
index 0000000..f4547b4
--- /dev/null
+++ b/src/pmview/text.cpp
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/Qt/viewers/SoQtExaminerViewer.h>
+#include <Inventor/actions/SoGetBoundingBoxAction.h>
+#include <Inventor/nodes/SoPickStyle.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoRotation.h>
+#include <Inventor/nodes/SoCube.h>
+#include "main.h"
+#include "text.h"
+
+#include <iostream>
+using namespace std;
+
+const char *Text::theHeightStr = "gjpqy|_";
+
+SoFont *Text::theSmallFont = (SoFont *)0;
+SoFont *Text::theMediumFont = (SoFont *)0;
+SoFont *Text::theLargeFont = (SoFont *)0;
+
+SbVec3f Text::theColor(1.0, 1.0, 1.0);
+SoGetBoundingBoxAction *Text::theBoxAction = (SoGetBoundingBoxAction *)0;
+
+Text::~Text()
+{
+}
+
+Text::Text(const QString &theString,
+ Direction theDir,
+ FontSize theFontSize,
+ bool rightJust)
+: _width(0),
+ _depth(0),
+ _dir(theDir),
+ _fontSize(theFontSize),
+ _rightJustFlag(rightJust),
+ _root(0),
+ _translation(0)
+{
+ float x = 0.0;
+ float y = 0.0;
+ float z = 0.0;
+ int width = 0;
+ int height = 0;
+ SoRotation *rot1;
+ SoRotation *rot2;
+ SoText3 *theText;
+
+ _root = new SoSeparator;
+ _translation = new SoTranslation;
+ theText = new SoText3;
+
+ SoPickStyle *style = new SoPickStyle;
+ style->style = SoPickStyle::UNPICKABLE;
+ _root->addChild(style);
+
+ if (theSmallFont == (SoFont *)0) {
+ char * font = getenv ("PMVIEW_FONT");
+
+ theSmallFont = new SoFont;
+ theMediumFont = new SoFont;
+ theLargeFont = new SoFont;
+
+ if ( font != NULL ) {
+ theSmallFont->name.setValue(font);
+ theMediumFont->name.setValue(font);
+ theLargeFont->name.setValue(font);
+#ifdef __sgi
+ } else {
+ // On Irix we know that Helvetica-Narrow is shipped as part
+ // of x_eoe.sw.Xfonts, so we're going to use it since it's
+ // an easier font to render compared to Times.
+ theSmallFont->name.setValue("Helvetica-Narrow");
+ theMediumFont->name.setValue("Helvetica-Narrow");
+ theLargeFont->name.setValue("Helvetica-Narrow");
+#endif
+ }
+
+ theSmallFont->size.setValue(14);
+ theMediumFont->size.setValue(24);
+ theLargeFont->size.setValue(32);
+ theSmallFont->ref();
+ theMediumFont->ref();
+ theLargeFont->ref();
+ theBoxAction = new SoGetBoundingBoxAction(pmview->viewer()->getViewportRegion());
+ }
+
+ switch(_fontSize) {
+ case small:
+ _root->addChild(theSmallFont->copy());
+ break;
+ case medium:
+ _root->addChild(theMediumFont->copy());
+ break;
+ case large:
+ _root->addChild(theLargeFont->copy());
+ break;
+ default:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Text::Text: Illegal size specified" << endl;
+#endif
+ _fontSize = medium;
+ _root->addChild(theMediumFont->copy());
+ }
+
+ _translation->translation.setValue(0.0, 0.0, 0.0);
+ _root->addChild(_translation);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ SoSeparator *sep = new SoSeparator;
+ _root->addChild(sep);
+ SoBaseColor *col = new SoBaseColor;
+ col->rgb.setValue(1.0, 0.0, 0.0);
+ sep->addChild(col);
+ SoCube *cube = new SoCube;
+ cube->width.setValue(3);
+ cube->depth.setValue(3);
+ cube->height.setValue(20);
+ sep->addChild(cube);
+ }
+#endif
+
+ if (_dir != vertical) {
+
+ rot1 = new SoRotation;
+ rot1->rotation.setValue(SbVec3f(1,0,0), -M_PI/2);
+ _root->addChild(rot1);
+
+ switch(_dir) {
+ case left:
+ rot2 = new SoRotation;
+ rot2->rotation.setValue(SbVec3f(0,0,1), M_PI);
+ _root->addChild(rot2);
+ break;
+ case down:
+ rot2 = new SoRotation;
+ rot2->rotation.setValue(SbVec3f(0,0,1), -M_PI/2);
+ _root->addChild(rot2);
+ break;
+ case up:
+ rot2 = new SoRotation;
+ rot2->rotation.setValue(SbVec3f(0,0,1), M_PI/2);
+ _root->addChild(rot2);
+ break;
+ default:
+ break;
+ }
+
+ if (((_dir == left || _dir == up) && !_rightJustFlag) ||
+ ((_dir == right || _dir == down) && !_rightJustFlag)) {
+ theText->justification = SoText3::RIGHT;
+ }
+
+ theText->parts = SoText3::FRONT;
+ theText->string.setValue((const char *)theString.toAscii());
+ _root->addChild(theText);
+
+ _root->ref();
+ theBoxAction->apply(_root);
+ SbXfBox3f box = theBoxAction->getBoundingBox();
+ box.getSize(x, y, z);
+
+ if (x < 0.0 || y < 0.0 || z < 0.0) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2) {
+ cerr << "Text::Text: Bogus bounding box returned for \""
+ << theString << "\": x = " << x << ", y = " << y
+ << ", z = " << z << endl;
+ }
+#endif
+ x = 0.0;
+ y = 0.0;
+ z = 0.0;
+ }
+
+ _width = (int)ceilf(x);
+ _depth = (int)ceilf(z);
+
+ const char *hasLow = strpbrk((const char *)theString.toAscii(), theHeightStr);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1) {
+ cerr << "Text::Text: " << theString << ": width = "
+ << _width << " height = " << _depth << " low = "
+ << ((hasLow != (char *)0) ? 1 : 0) << endl;
+ }
+#endif
+
+ switch(_dir) {
+ case left:
+ if (hasLow != (char *)0) {
+ _translation->translation.setValue(1, 1, _depth * 0.3);
+ _depth = (int)(_depth * 1.2);
+ }
+ else
+ _translation->translation.setValue(1, 1, 0);
+ _width += 2;
+ break;
+ case right:
+ if (hasLow != (char *)0) {
+ _translation->translation.setValue(1, 1, _depth * 0.85);
+ _depth = (int)(_depth * 1.2);
+ }
+ else
+ _translation->translation.setValue(1, 1, _depth);
+ _width += 2;
+ break;
+ case down:
+ if (hasLow != (char *)0) {
+ _translation->translation.setValue(_width * 0.3, 1, 1);
+ _width = (int)(_width * 1.2);
+ }
+ else
+ _translation->translation.setValue(0, 1, 1);
+ _depth += 2;
+ break;
+ case up:
+ if (hasLow != (char *)0) {
+ _translation->translation.setValue(_width * 0.85, 1, 1);
+ _width = (int)(_width * 1.2);
+ }
+ else
+ _translation->translation.setValue(_width, 1, 1);
+ _depth += 2;
+ break;
+ default:
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL1)
+ cerr << "Text::Text: Illegal direction specified ("
+ << (int)_dir << ")" << endl;
+#endif
+ break;
+ }
+ }
+ else {
+ char c[2] = { ' ', '\0' };
+ SoSeparator *sep;
+ SoTranslation *tran;
+ SoRotation *rot;
+ width = 0;
+ height = 0;
+
+ for (int i = 0; i < theString.length(); i++) {
+ c[0] = ((const char *)theString.toAscii())[i];
+ sep = new SoSeparator;
+ tran = new SoTranslation;
+ rot = new SoRotation;
+ rot->rotation.setValue(SbVec3f(1,0,0), -M_PI/2);
+ theText->string.setValue(c);
+ theText->parts = SoText3::FRONT;
+
+ _root->addChild(sep);
+ sep->addChild(rot);
+ sep->addChild(tran);
+ sep->addChild(theText);
+
+ theBoxAction->apply(sep);
+ SbXfBox3f box = theBoxAction->getBoundingBox();
+ box.getSize(x, y, z);
+
+ width = (int)ceilf(x);
+ height = (int)ceilf(z);
+
+// TODO: This bounding box is wrong.
+
+ if (width*2 > _width)
+ _width = width*2;
+
+ _depth += (height + 2) * 2;
+
+ tran->translation.setValue(0, -_depth, 1);
+
+ if (i < theString.length() - 1) {
+ theText = new SoText3;
+ }
+ }
+ }
+}
+
+
+QTextStream&
+operator<<(QTextStream& os, Text const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+Text::display(QTextStream& os) const
+{
+ os << "Text: dir = ";
+ switch(_dir) {
+ case left:
+ os << "left";
+ break;
+ case right:
+ os << "right";
+ break;
+ case up:
+ os << "up";
+ break;
+ case down:
+ os << "down";
+ break;
+ default:
+ break;
+ }
+ os << ", font size = ";
+ switch(_fontSize) {
+ case small:
+ os << "small";
+ break;
+ case medium:
+ os << "medium";
+ break;
+ case large:
+ os << "large";
+ break;
+ }
+}
diff --git a/src/pmview/text.h b/src/pmview/text.h
new file mode 100644
index 0000000..f9a6854
--- /dev/null
+++ b/src/pmview/text.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1995 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _TEXT_H_
+#define _TEXT_H_
+
+#include <QtCore/QTextStream>
+#include <Inventor/nodes/SoFont.h>
+#include <Inventor/nodes/SoText3.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoBaseColor.h>
+
+class Text
+{
+ public:
+
+ enum Direction { left, right, up, down, vertical };
+ enum FontSize { small, medium, large };
+
+ private:
+
+ int _width;
+ int _depth;
+ Direction _dir;
+ FontSize _fontSize;
+ bool _rightJustFlag;
+
+ SoSeparator *_root;
+ SoTranslation *_translation;
+
+ static const char *theHeightStr;
+ static SoFont *theSmallFont;
+ static SoFont *theMediumFont;
+ static SoFont *theLargeFont;
+
+ static SbVec3f theColor;
+ static SoGetBoundingBoxAction *theBoxAction;
+
+ public:
+
+ ~Text();
+
+ Text(const QString &theText,
+ Direction theDir,
+ FontSize theFontSize,
+ bool rightJust = false);
+
+ int width() const
+ { return _width; }
+ int depth() const
+ { return _depth; }
+ Direction dir() const
+ { return _dir; }
+ FontSize size() const
+ { return _fontSize; }
+
+ SoSeparator* root() const
+ { return _root; }
+
+ friend QTextStream& operator<<(QTextStream& os, Text const& rhs);
+
+ void display(QTextStream& os) const;
+
+ private:
+
+ Text();
+ Text(Text const &);
+ Text const& operator=(Text const&);
+};
+
+#endif /* _TEXT_H_ */
diff --git a/src/pmview/togglemod.cpp b/src/pmview/togglemod.cpp
new file mode 100644
index 0000000..282bbe8
--- /dev/null
+++ b/src/pmview/togglemod.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoSelection.h>
+#include <Inventor/SoPath.h>
+#include "main.h"
+#include "togglemod.h"
+
+#include <iostream>
+using namespace std;
+
+ToggleMod::~ToggleMod()
+{
+}
+
+ToggleMod::ToggleMod(SoNode *obj, const char *label)
+: Modulate(NULL),
+ _label(label)
+{
+ _root = new SoSeparator;
+ _root->addChild(obj);
+
+ if (_label.length() == 0)
+ _label = "\n";
+
+ add();
+}
+
+void
+ToggleMod::dump(QTextStream &os) const
+{
+ os << "ToggleMod: \"" << _label << "\" has " << _list.size()
+ << " objects" << endl;
+}
+
+void
+ToggleMod::selectAll()
+{
+ int i;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ToggleMod::selectAll: \"" << _label << '"' << endl;
+#endif
+
+ for (i = 0; i < _list.size(); i++) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ToggleMod::selectAll: Selecting [" << i << ']'
+ << endl;
+#endif
+
+ _list[i]->selectAll();
+ }
+}
+
+int
+ToggleMod::select(SoPath *path)
+{
+ int i;
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ToggleMod::select: \"" << _label << '"' << endl;
+#endif
+
+ theModList->selectAllOn();
+
+ for (i = 0; i < _list.size(); i++) {
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ToggleMod::select: Selecting [" << i << ']' << endl;
+#endif
+
+ _list[i]->selectAll();
+ }
+
+ theModList->selectAllOff();
+
+ theModList->selector()->deselect(path->getNodeFromTail(0));
+
+ return 0;
+}
+
+int
+ToggleMod::remove(SoPath *)
+{
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL2)
+ cerr << "ToggleMod::remove: " << _label << endl;
+#endif
+ return 0;
+}
+
diff --git a/src/pmview/togglemod.h b/src/pmview/togglemod.h
new file mode 100644
index 0000000..dafd65e
--- /dev/null
+++ b/src/pmview/togglemod.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _TOGGLEMOD_H_
+#define _TOGGLEMOD_H_
+
+#include <Inventor/SbString.h>
+#include "modulate.h"
+#include "modlist.h"
+
+class SoSeparator;
+class SoPath;
+class Launch;
+class Record;
+
+class ToggleMod : public Modulate
+{
+private:
+
+ ModulateList _list;
+ QString _label;
+
+public:
+
+ virtual ~ToggleMod();
+
+ ToggleMod(SoNode *obj, const char *label);
+
+ void addMod(Modulate *mod)
+ { _list.append(mod); }
+
+ virtual void selectAll();
+ virtual int select(SoPath *);
+ virtual int remove(SoPath *);
+
+ virtual void selectInfo(SoPath *)
+ {}
+ virtual void removeInfo(SoPath *)
+ {}
+
+ virtual void infoText(QString &str, bool) const
+ { str = _label; }
+
+ virtual void refresh(bool)
+ {}
+ virtual void launch(Launch &, bool) const
+ {}
+ virtual void record(Record &) const
+ {}
+
+ virtual void dump(QTextStream &) const;
+ void dumpState(QTextStream &os, Modulate::State state) const;
+
+ friend QTextStream &operator<<(QTextStream &os, const ToggleMod &rhs);
+
+private:
+
+ ToggleMod();
+ ToggleMod(const ToggleMod &);
+ const ToggleMod &operator=(const ToggleMod &);
+ // Never defined
+};
+
+#endif /* _TOGGLEMOD_H_ */
diff --git a/src/pmview/viewobj.cpp b/src/pmview/viewobj.cpp
new file mode 100644
index 0000000..f0e9b30
--- /dev/null
+++ b/src/pmview/viewobj.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoCube.h>
+#include <Inventor/nodes/SoCylinder.h>
+#include "pmview.h"
+#include "viewobj.h"
+
+#include <iostream>
+using namespace std;
+
+int ViewObj::theNumModObjects = 0;
+
+ViewObj::~ViewObj()
+{
+}
+
+ViewObj::ViewObj(int x, int z, int cols, int rows, Alignment align)
+ : _root(new SoSeparator)
+ , _tran(new SoTranslation)
+ , _objtype(VIEWOBJ)
+ , _col(x)
+ , _row(z)
+ , _cols(cols)
+ , _rows(rows)
+{
+ _tran->translation.setValue(0.0, 0.0, 0.0);
+ _root->addChild(_tran);
+
+ switch(align) {
+ case north:
+ _xAlign = 0.5;
+ _zAlign = 0.0;
+ break;
+ case south:
+ _xAlign = 0.5;
+ _zAlign = 1.0;
+ break;
+ case east:
+ _xAlign = 1.0;
+ _zAlign = 0.5;
+ break;
+ case west:
+ _xAlign = 0.0;
+ _zAlign = 0.5;
+ break;
+ case northEast:
+ _xAlign = 1.0;
+ _zAlign = 0.0;
+ break;
+ case northWest:
+ _xAlign = 0.0;
+ _zAlign = 0.0;
+ break;
+ case southEast:
+ _xAlign = 1.0;
+ _zAlign = 1.0;
+ break;
+ case southWest:
+ _xAlign = 0.0;
+ _zAlign = 1.0;
+ break;
+ case center:
+ default:
+ _xAlign = 0.5;
+ _zAlign = 0.5;
+ break;
+ }
+}
+
+SoNode *
+ViewObj::object(Shape shape)
+{
+ static SoSeparator *cubeSep = NULL;
+ static SoSeparator *cylSep = NULL;
+ SoNode *obj;
+
+ switch(shape) {
+ case cylinder:
+ if (cylSep == NULL) {
+ cylSep = new SoSeparator();
+ SoTranslation *cylTran = new SoTranslation;
+ cylTran->translation.setValue(0.0, 0.5, 0.0);
+ cylSep->addChild(cylTran);
+ SoCylinder *cyl = new SoCylinder;
+ cyl->radius.setValue(0.5);
+ cyl->height.setValue(1.0);
+ cylSep->addChild(cyl);
+ }
+ obj = cylSep;
+ break;
+
+ case cube:
+ default:
+ if (cubeSep == NULL) {
+ cubeSep = new SoSeparator();
+ SoTranslation *cubeTran = new SoTranslation;
+ cubeTran->translation.setValue(0.0, 0.5, 0.0);
+ cubeSep->addChild(cubeTran);
+ SoCube *cube = new SoCube;
+ cube->height = cube->width = cube->depth = 1.0;
+ cubeSep->addChild(cube);
+ }
+ obj = cubeSep;
+ break;
+ }
+
+ return obj;
+}
+
+void
+ViewObj::setTran(float xTran, float zTran, int setWidth, int setDepth)
+{
+ float x = xTran + ((setWidth - (float)width()) * _xAlign);
+ float z = zTran + ((setDepth - (float)depth()) * _zAlign);
+
+#ifdef PCP_DEBUG
+ if (pmDebug & DBG_TRACE_APPL0) {
+ cerr << "ViewObj::setTran: " << name() << ":" << endl;
+ cerr << "x=" << x << ", xTran =" << xTran << ", setWidth="
+ << setWidth << ", width=" << width() << ", xAlign=" << _xAlign
+ << endl;
+ cerr << "z=" << z << ", zTran =" << zTran << ", setDepth="
+ << setDepth << ", depth=" << depth() << ", zAlign=" << _zAlign
+ << endl << endl;
+ }
+#endif
+
+ _tran->translation.setValue(x, 0.0, z);
+}
+
+QTextStream&
+operator<<(QTextStream& os, ViewObj const& rhs)
+{
+ rhs.display(os);
+ return os;
+}
+
+void
+ViewObj::display(QTextStream& os) const
+{
+ os << name() << ": size = " << width() << "x" << depth() << " ("
+ << _tran->translation.getValue()[0] << ','
+ << _tran->translation.getValue()[1] << ','
+ << _tran->translation.getValue()[2] << "), cols = " << _cols
+ << ", rows = " << _rows << ", alignment = " << _xAlign << ','
+ << _zAlign;
+}
+
+void
+ViewObj::dumpShape(QTextStream& os, ViewObj::Shape shape) const
+{
+ if (shape == ViewObj::cube)
+ os << "cube";
+ else
+ os << "cylinder";
+}
diff --git a/src/pmview/viewobj.h b/src/pmview/viewobj.h
new file mode 100644
index 0000000..f713c71
--- /dev/null
+++ b/src/pmview/viewobj.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _VIEWOBJ_H_
+#define _VIEWOBJ_H_
+
+#include <QtCore/QTextStream>
+
+class SoNode;
+class SoSeparator;
+class SoTranslation;
+class Modulate;
+class DefaultObj;
+
+class ViewObj
+{
+public:
+
+ enum Alignment { north, south, east, west,
+ northEast, northWest, southEast, southWest,
+ center };
+
+ enum Shape { cube, cylinder };
+
+ // Poor man substitution for RTTI until O32 compiler will either
+ // die or start supporting that stuff
+ enum ObjType {VIEWOBJ = 1,
+ BASEOBJ = 2,
+ LABELOBJ = 4,
+ MODOBJ = 8,
+ GRIDOBJ = 16,
+ PIPEOBJ = 32,
+ BAROBJ = 64,
+ STACKOBJ = 128,
+ LINK = 256,
+ XING = 512,
+ SCENEFILEOBJ = 1024
+ };
+
+protected:
+
+ SoSeparator *_root;
+ SoTranslation *_tran;
+
+ int _objtype;
+ int _col;
+ int _row;
+ int _cols;
+ int _rows;
+ int _maxHeight;
+ float _xAlign;
+ float _zAlign;
+
+ static int theNumModObjects;
+
+public:
+
+ virtual ~ViewObj();
+
+ ViewObj(int, int, int cols = 1, int rows = 1,
+ Alignment align = center);
+
+ // The Scene Graph Root for this object
+ SoSeparator* root()
+ { return _root; }
+
+ int objbits() const { return _objtype; }
+ int row() const { return _row; }
+ int col() const { return _col; }
+
+ int cols() const
+ { return _cols; }
+ int rows() const
+ { return _rows; }
+ int height() const
+ { return _maxHeight; }
+ int &height()
+ { return _maxHeight; }
+ float xAlign() const
+ { return _xAlign; }
+ float zAlign() const
+ { return _zAlign; }
+
+ // Set the coordinates (and the allocated size) from parent
+ virtual void setTran(float xTran, float zTran, int width, int depth);
+
+ // Size (in object coordinates). Must be the correct value before
+ // the object is added to the parent.
+ virtual int width() const = 0;
+ virtual int depth() const = 0;
+
+ static int numModObjects()
+ { return theNumModObjects; }
+
+ // Return default object
+ static SoNode *object(Shape shape);
+
+ virtual Modulate *modObj()
+ { return (Modulate *)0; }
+
+ // Inform object parsing stuff is done
+
+ virtual void finishedAdd() = 0;
+
+ // Output
+ virtual void display(QTextStream& os) const;
+
+ virtual const char* name() const = 0;
+
+ friend QTextStream& operator<<(QTextStream& os, ViewObj const& rhs);
+
+protected:
+
+ void dumpShape(QTextStream& os, ViewObj::Shape shape) const;
+
+private:
+
+ ViewObj();
+ ViewObj(ViewObj const &);
+ ViewObj const& operator=(ViewObj const &);
+ // Never defined
+};
+
+#endif /* _VIEWOBJ_H_ */
diff --git a/src/pmview/xing.cpp b/src/pmview/xing.cpp
new file mode 100644
index 0000000..c7637e0
--- /dev/null
+++ b/src/pmview/xing.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoSphere.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include <Inventor/nodes/SoTranslation.h>
+#include <Inventor/nodes/SoTransform.h>
+#include <Inventor/nodes/SoRotation.h>
+#include <Inventor/nodes/SoCylinder.h>
+#include <Inventor/nodes/SoClipPlane.h>
+#include <Inventor/nodes/SoBaseColor.h>
+
+#include "xing.h"
+#include "defaultobj.h"
+
+Xing::Xing (const DefaultObj & dobj, int c, int r,
+ int colSpan, int rowSpan,
+ Alignment corners[4])
+ : BaseObj (false, dobj, c, r, colSpan, rowSpan, center)
+{
+ int i;
+ _objtype |= XING;
+
+ for (i = 0; i < 3; i++) {
+ _color[i] = dobj.baseColor(i);
+ }
+
+ _width = colSpan * _cellWidth;
+ _depth = rowSpan * _cellDepth;
+ _cellHeight = dobj.baseHeight();
+ _cellWidth = dobj.baseHeight();
+ _cellDepth = dobj.baseHeight();
+
+ for ( i=0; i < 4; i++ ) {
+ _corner[i] = corners[i];
+ }
+}
+
+void
+Xing::finishedAdd ()
+{
+ int i;
+ float ts[12] = {
+ (float)(_cellWidth/2.0), (float)(_cellHeight/2.0), (float)(_cellDepth/2.0),
+ (float)(_width - _cellWidth), 0, 0,
+ 0, 0, (float)(_depth - _cellDepth),
+ (float)(_cellWidth - _width), 0, 0
+ };
+
+ SoBaseColor * cl = new SoBaseColor;
+ cl->rgb.setValue (_color);
+ _root->addChild (cl);
+
+ SoSeparator * scene = new SoSeparator;
+
+ for (i=0; i < 4; i++ ) {
+ SoTranslation * t = new SoTranslation;
+ t->translation.setValue (ts+i*3);
+ scene->addChild(t);
+
+ SoSphere * s = new SoSphere;
+ s->radius.setValue (_cellHeight*0.4);
+ scene->addChild (s);
+
+ SoSeparator * csep = new SoSeparator;
+
+ float ch;
+
+ ctrans[i] = new SoTransform;
+ switch ( _corner[i] ) {
+ case south:
+ ch = _cellDepth/2.0;
+ ctrans[i]->translation.setValue (0, 0, _cellDepth/4.0);
+ ctrans[i]->rotation.setValue (SbVec3f(1, 0, 0), M_PI/2);
+ break;
+
+ case east:
+ ch = _cellWidth/2.0;
+ ctrans[i]->translation.setValue (_cellWidth/4.0, 0, 0);
+ ctrans[i]->rotation.setValue (SbVec3f(0, 0, 1), M_PI/2);
+ break;
+
+ case west:
+ ch = _cellWidth/2.0;
+ ctrans[i]->translation.setValue (_cellWidth/-4.0, 0, 0);
+ ctrans[i]->rotation.setValue (SbVec3f(0, 0, 1), M_PI/2);
+ break;
+
+ case north:
+ ch = _cellDepth/2.0;
+ ctrans[i]->translation.setValue (0, 0, _cellDepth/-4.0);
+ ctrans[i]->rotation.setValue (SbVec3f(1, 0, 0), M_PI/2);
+ break;
+
+ default:
+ ch = 0.0;
+ break;
+ }
+ csep->addChild (ctrans[i]);
+
+ cyls[i] = new SoCylinder;
+ cyls[i]->radius.setValue (_cellHeight*0.4);
+ cyls[i]->height.setValue (ch);
+ csep->addChild (cyls[i]);
+
+ scene->addChild (csep);
+ }
+
+ float cilh = sqrt (_cellWidth*_cellWidth*(cols()-1)*(cols()-1) +
+ _cellDepth*_cellDepth*(rows()-1)*(rows()-1));
+
+ // We're in the bottom left corner now
+ SbVec3f axis(rows()-1, 0, cols()-1);
+ SoTransform * tr = new SoTransform;
+ tr->rotation.setValue (axis, M_PI/2);
+ tr->translation.setValue (((cols()-1)*_cellWidth)/2.0, 0,
+ ((rows()-1)*_cellDepth)/-2.0);
+
+ scene->addChild (tr);
+
+ SoCylinder * c1 = new SoCylinder;
+ c1->radius.setValue (_cellHeight*0.4);
+ c1->height.setValue (cilh);
+ scene->addChild (c1);
+
+ axis[0] = cols() - 1;
+ axis[2] = -((float)(rows()-1));
+
+ // To draw a "broken" cylinder, define two clipping planes along the
+ // "main" cylinder and draw the crossing one twice, each time enabling
+ // a different plane.
+ for (i=-1; i < 2; i += 2 ) {
+ SoSeparator * sp = new SoSeparator;
+
+ SbVec3f normal(i*((int)(rows()-1)), 0, i*((int)(cols()-1)));
+ SbPlane p (normal, _cellHeight/2.0);
+
+ SoClipPlane * c = new SoClipPlane;
+ c->on.setValue (1);
+ c->plane = p;
+
+ sp->addChild (c);
+
+ SoRotation * r = new SoRotation;
+ r->rotation.setValue (axis, atanf (axis[0]/(rows()-1))*2.0);
+ sp->addChild (r);
+ sp->addChild (c1);
+
+ scene->addChild (sp);
+ }
+
+ _root->addChild (scene);
+}
+
+void
+Xing::setTran(float x, float z, int w, int d)
+{
+ float ch;
+
+ for ( int i=0; i < 4; i++) {
+ switch ( _corner[i] ) {
+ case south:
+ ch = (d + _cellDepth - _depth)/2.0;
+ ctrans[i]->translation.setValue (0, 0, ch/2.0);
+ break;
+
+ case east:
+ ch = (w + _cellWidth - _width)/2.0;
+ ctrans[i]->translation.setValue (ch/2.0, 0, 0);
+ break;
+
+ case west:
+ ch = (w +_cellWidth - _width)/2.0;
+ ctrans[i]->translation.setValue (-ch/2.0, 0, 0);
+ break;
+
+ case north:
+ ch = (d + _cellDepth - _depth)/2.0;
+ ctrans[i]->translation.setValue (0, 0, -ch/2.0);
+ break;
+
+ default:
+ ch = 0.0;
+ break;
+ }
+
+ cyls[i]->height.setValue (ch);
+ }
+
+ BaseObj::setTran (x, z, w, d);
+}
diff --git a/src/pmview/xing.h b/src/pmview/xing.h
new file mode 100644
index 0000000..b5920bd
--- /dev/null
+++ b/src/pmview/xing.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _XING_H_
+#define _XING_H_
+
+#include "baseobj.h"
+
+class SoTransform;
+class SoCylinder;
+
+class Xing : public BaseObj
+{
+public:
+ Xing (const DefaultObj &, int, int, int, int, Alignment[4]);
+
+ void finishedAdd ();
+ void setTran (float, float, int, int);
+ int width () const { return _width; }
+ int depth () const { return _depth; }
+ const char * name () const { return "Xing"; }
+
+private:
+ Xing();
+ Xing(Xing const &);
+ Xing const & operator= (Xing const &);
+
+ int _width;
+ int _depth;
+ int _cellHeight, _cellDepth, _cellWidth;
+ float _color[3];
+ Alignment _corner[4];
+ SoTransform * ctrans[4];
+ SoCylinder * cyls[4];
+};
+
+#endif /* _XING_H_ */
diff --git a/src/pmview/yscalemod.cpp b/src/pmview/yscalemod.cpp
new file mode 100644
index 0000000..63359f5
--- /dev/null
+++ b/src/pmview/yscalemod.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <Inventor/nodes/SoBaseColor.h>
+#include <Inventor/nodes/SoScale.h>
+#include <Inventor/nodes/SoSeparator.h>
+#include "main.h"
+#include "yscalemod.h"
+#include "modlist.h"
+#include "launch.h"
+
+YScaleMod::~YScaleMod()
+{
+}
+
+YScaleMod::YScaleMod(const char *str, double scale,
+ const SbColor &color, SoNode *obj)
+: ScaleMod(str, scale, color, obj, 0.0, 1.0, 0.0)
+{
+}
+
+void
+YScaleMod::dump(QTextStream &os) const
+{
+ os << "YScaleMod: ";
+
+ if (status() < 0)
+ os << "Invalid metric";
+ else {
+ os << "state = ";
+ dumpState(os, _state);
+ os << ", scale = " << _scale->scaleFactor.getValue()[1] << ": ";
+ _metrics->metric(0).dump(os, true);
+ }
+}
diff --git a/src/pmview/yscalemod.h b/src/pmview/yscalemod.h
new file mode 100644
index 0000000..a0458f6
--- /dev/null
+++ b/src/pmview/yscalemod.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 1997 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef _YSCALEMOD_H_
+#define _YSCALEMOD_H_
+
+#include "scalemod.h"
+
+class SoBaseColor;
+class SoScale;
+class SoNode;
+class Launch;
+
+class YScaleMod : public ScaleMod
+{
+public:
+
+ virtual ~YScaleMod();
+
+ YScaleMod(const char *metric, double scale, const SbColor &color,
+ SoNode *obj);
+
+ virtual void dump(QTextStream &) const;
+
+private:
+
+ YScaleMod();
+ YScaleMod(const YScaleMod &);
+ const YScaleMod &operator=(const YScaleMod &);
+ // Never defined
+};
+
+#endif /* _YSCALEMOD_H_ */
diff --git a/src/pmwebapi/GNUmakefile b/src/pmwebapi/GNUmakefile
new file mode 100644
index 0000000..c88df98
--- /dev/null
+++ b/src/pmwebapi/GNUmakefile
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2011-2014 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CMDTARGET = pmwebd$(EXECSUFFIX)
+HFILES = pmwebapi.h
+CFILES = main.c util.c pmwebapi.c pmresapi.c
+
+LLDLIBS = $(PCPLIB) -lmicrohttpd
+LDIRT = pmwebd.log pmwebd.service
+
+LCFLAGS += -Wextra
+LCFLAGS += $(PIECFLAGS)
+LLDFLAGS += $(PIELDFLAGS)
+
+SUBDIRS = jsdemos
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(HAVE_LIBMICROHTTPD)" "1"
+build-me: $(CMDTARGET) pmwebd.service
+
+pmwebd.service: pmwebd.service.in
+ $(SED) -e 's;@path@;'$(PCP_RC_DIR)';' $< > $@
+
+install: default
+ $(INSTALL) -m 755 -d `dirname $(PCP_PMWEBDOPTIONS_PATH)`
+ $(INSTALL) -m 644 pmwebd.options $(PCP_PMWEBDOPTIONS_PATH)
+ $(INSTALL) -m 755 rc_pmwebd $(PCP_RC_DIR)/pmwebd
+ifeq ($(ENABLE_SYSTEMD),true)
+ $(INSTALL) -m 644 pmwebd.service $(PCP_SYSTEMDUNIT_DIR)/pmwebd.service
+endif
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+ $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)/pmwebd
+else
+build-me:
+install:
+endif
+
+default_pcp :: default
+
+default_pcp :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install_pcp :: install
+
+install_pcp :: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
diff --git a/src/pmwebapi/TODO b/src/pmwebapi/TODO
new file mode 100644
index 0000000..e0f23ca
--- /dev/null
+++ b/src/pmwebapi/TODO
@@ -0,0 +1,18 @@
+- incoming & outgoing ACL
+- ssl
+- pmStore ?
+- missing APIs
+ - time control
+ - or _metric&intervals=STARTTIME&count=COUNT
+- consider having blinkenlights call /pmapi/NNNNN/destroy ... but DoS unless authenticated
+<script type="text/javascript">
+ window.onbeforeunload = function(){
+ return "Did you save your stuff?"
+ }
+</script>
+
+- load control / attack detection (tarpot invalid webapi# brute-forcers)
+- pmReconnectContext() if pmUseContext etc. return PM_ERR_IPC || PM_ERR_TIMEOUT
+- serialize-to-disk webapi connection state & profile data,
+ for possible restoring if pmwebapi is killed/restarted
+
diff --git a/src/pmwebapi/jsdemos/GNUmakefile b/src/pmwebapi/jsdemos/GNUmakefile
new file mode 100644
index 0000000..756d1af
--- /dev/null
+++ b/src/pmwebapi/jsdemos/GNUmakefile
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2013 Red Hat. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+JSFILES = jquery-1.7.2.js jquery-ui-1.10.2.js jquery-ui-themes-1.10.2.tar.gz
+SUBDIRS = blinkenlights jsbrowser
+LSRCFILES = $(JSFILES) favicon.ico
+LDIRDIRT = jquery-ui-themes-1.10.2
+LDIRT = .unpacked
+
+default: $(SUBDIRS) jquery-ui-themes
+ $(SUBDIRS_MAKERULE)
+
+include $(BUILDRULES)
+
+jquery-ui-themes: .unpacked
+.unpacked: jquery-ui-themes-1.10.2.tar.gz
+ $(ZIP) -d < $^ | $(TAR) -xf -
+ touch .unpacked
+
+install: $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmwebapi/jsdemos/blinkenlights/GNUmakefile b/src/pmwebapi/jsdemos/blinkenlights/GNUmakefile
new file mode 100644
index 0000000..a2ba409
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/GNUmakefile
@@ -0,0 +1,33 @@
+#
+# Copyright (c) 2013 Red Hat. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+IMAGES = blinken_error.png blinken_off.png blinken_on.png
+JSFILES = blinkenlights.js
+CSSFILES = blinkenlights.css
+HTMLFILES = index.html
+
+LSRCFILES = $(IMAGES) $(JSFILES) $(CSSFILES) $(HTMLFILES)
+
+default:
+
+include $(BUILDRULES)
+
+install:
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/pmwebapi/jsdemos/blinkenlights/blinken_error.png b/src/pmwebapi/jsdemos/blinkenlights/blinken_error.png
new file mode 100644
index 0000000..556bd3a
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/blinken_error.png
Binary files differ
diff --git a/src/pmwebapi/jsdemos/blinkenlights/blinken_off.png b/src/pmwebapi/jsdemos/blinkenlights/blinken_off.png
new file mode 100644
index 0000000..abb5c1d
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/blinken_off.png
Binary files differ
diff --git a/src/pmwebapi/jsdemos/blinkenlights/blinken_on.png b/src/pmwebapi/jsdemos/blinkenlights/blinken_on.png
new file mode 100644
index 0000000..8d85f61
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/blinken_on.png
Binary files differ
diff --git a/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.css b/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.css
new file mode 100644
index 0000000..c99ceff
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.css
@@ -0,0 +1,19 @@
+#interval-slider-label { margin: 0px; }
+#interval-slider { width: 300px; margin: 0px; display: inline-block; }
+
+#blinkenlights { font-family: monospace; font-size: 130%; }
+#blinkenlights li {
+ list-style: none; line-height: 25px;
+ background-repeat: no-repeat;
+ background-size: 25px 25px;
+ padding-left: 32px; margin-top: 5px;
+}
+
+/* public domain images courtesy of
+ - http://www.clker.com/clipart-led-off.html
+ - http://www.clker.com/clipart-6514.html
+ XXX and related images from the same site
+ */
+#blinkenlights li.on { background-image: url(blinken_on.png); }
+#blinkenlights li.off { background-image: url(blinken_off.png); }
+#blinkenlights li.error { background-image: url(blinken_error.png); }
diff --git a/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.js b/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.js
new file mode 100644
index 0000000..03bb315
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/blinkenlights.js
@@ -0,0 +1,170 @@
+var pm_root = "http://" + location.hostname + ":" + location.port + "/pmapi";
+var pm_context = -1;
+
+// ----------------------------------------------------------------------
+
+function Predicate(name,index,operator,threshold) {
+ this.name = name;
+ this.index = index;
+ this.operator = operator;
+ this.threshold = threshold;
+ this.inames = {};
+}
+
+Predicate.prototype.to_string = function() {
+ return this.name + "[" + this.index + "] "
+ + this.operator + " " + this.threshold;
+};
+
+Predicate.prototype.get_iname = function(iid) {
+ if (!(iid in this.inames)) {
+ var pm_url = pm_root + "/" + pm_context + "/_indom?name=" + this.name + "&instance=" + iid;
+ var predicate = this;
+ $.getJSON(pm_url, function(data, status) {
+ // TODOXXX error check: should return 1 instance
+ predicate.inames[iid] = data.instances[0].name;
+ });
+
+ return "..."; // will be reloaded next cycle
+ }
+
+ return this.inames[iid];
+}
+
+Predicate.prototype.test = function(elt,data_dict,index) {
+ if (this.index == "*" && typeof(index) == "undefined") {
+ var predicate = this;
+ $.each(data_dict[this.name].instances, function(i,_instance) {
+ predicate.test(elt,data_dict,i);
+ });
+ return;
+ }
+
+ if (typeof(index) == "undefined") index = this.index;
+
+ var metric = data_dict[this.name].instances[index].value;
+ var iid = data_dict[this.name].instances[index].instance;
+ var result = 0, error = "";
+ if (this.operator == "<") result = metric < this.threshold;
+ else if (this.operator == ">") result = metric > this.threshold;
+ else if (this.operator == "<=") result = metric <= this.threshold;
+ else if (this.operator == ">=") result = metric >= this.threshold;
+ else if (this.operator == "==") result = metric == this.threshold;
+ else { error = "unknown operator '" + this.operator + "'"; result = -1; }
+
+ // TODOXXX avoid $("#blinkenlights").empty() by using existing li's??
+ var bclass = result < 0 ? "error" : result ? "on" : "off";
+
+ var source = "<strong>" + metric + "</strong> -- "
+ + this.name + " ( " + this.get_iname(iid) + " : " + iid + " ) "
+ + this.operator + " " + this.threshold;
+ var content = "<span>" + source + "</span>"
+ + (result < 0 ? " -- error: " + error : "");
+
+ elt.append("<li class=\"" + bclass + "\">" + content + "</li>");
+};
+
+var predicates = [];
+
+function parsePredicate(src) {
+ var matches = /^([^[ ]+)\s*(\[\d+\]|\[\*\]|\[\]|)\s*(<=|>=|==|<|>)\s*(\S*)$/.exec(src);
+ if (matches == null) return null;
+ var name = matches[1];
+ var index = matches[2]; index = index == "" ? "*" : index.substring(1,index.length-1);
+ var operator = matches[3];
+ var threshold = parseFloat(matches[4]); // TODOXXX what about other types?; accepts 40foobar
+ if (isNaN(threshold)) return null;
+
+ console.log ("create predicate " + name + " : " + index + " : " + operator + " : " + threshold)
+
+ return new Predicate(name,index,operator,threshold);
+}
+
+// ----------------------------------------------------------------------
+
+var updateInterval = 10000; // milliseconds
+var updateIntervalID = 1;
+
+function setUpdateInterval(i) {
+ if (updateIntervalID >= 0) { clearInterval(updateIntervalID); }
+ if (i > updateInterval) { pm_context = -1; } // will likely need a new context
+ updateInterval = i;
+ updateIntervalID = setInterval(updateBlinkenlights, updateInterval);
+}
+
+// default mode
+var pm_context_type = "hostspec";
+var pm_context_spec = "localhost";
+
+function setPMContextType(k) {
+ pm_context_type = k;
+ pm_context = -1;
+ updateBlinkenlights();
+}
+function setPMContextSpec(i) {
+ pm_context_spec = i;
+ pm_context = -1;
+ updateBlinkenlights();
+}
+
+
+function updateBlinkenlights() {
+ var pm_url;
+
+ if (pm_context < 0) {
+ pm_url = pm_root
+ + "/context?"
+ + pm_context_type + "=" + encodeURIComponent(pm_context_spec)
+ + "&polltimeout=" + Math.floor(5*updateInterval/1000);
+ $.getJSON(pm_url, function(data, status) {
+ pm_context = data.context;
+ setTimeout(updateBlinkenlights, 100); // retry soon
+ }).error(function() { pm_context = -1; });
+ return; // will retry one cycle later
+ }
+
+ if(predicates.length == 0) {
+ $("#blinkenlights").html("<b>No predicates requested...</b>");
+ return;
+ }
+
+ // ajax request for JSON data
+ pm_url = pm_root + "/" + pm_context + "/_fetch?names=";
+ $.each(predicates, function(i, predicate) {
+ if (i > 0) pm_url += ",";
+ pm_url += predicate.name;
+ });
+ $.getJSON(pm_url, function(data, status) {
+ // update data_dict
+ var data_dict = {};
+ $.each(data.values, function(i, value) {
+ data_dict[value.name] = value;
+ });
+ // update status field
+ theDate = new Date(0);
+ theDate.setUTCSeconds(data.timestamp.s);
+ theDate.setUTCMilliseconds(data.timestamp.us/1000);
+ $("#status").html("Timestamp: " + theDate.toString());
+ // update the view
+ $("#blinkenlights").empty();
+ $.each(predicates, function(i, predicate) {
+ predicate.test($("#blinkenlights"), data_dict);
+ });
+ }).error(function() {
+ $("#blinkenlights").html("<b>error accessing server, retrying...</b>");
+ pm_context = -1; });
+}
+
+function loadBlinkenlights() {
+ $("#header").html("pcp blinkenlights demo");
+ $("#content").html("<ul id=\"blinkenlights\"><li>loading...</li></ul>");
+
+ // start timer for updateBlinkenlights
+ updateBlinkenlights();
+ setUpdateInterval(updateInterval);
+}
+
+$(document).ready(function() {
+ loadBlinkenlights();
+ // TODOXXX add support for editing mode
+});
diff --git a/src/pmwebapi/jsdemos/blinkenlights/index.html b/src/pmwebapi/jsdemos/blinkenlights/index.html
new file mode 100644
index 0000000..cf2e4fd
--- /dev/null
+++ b/src/pmwebapi/jsdemos/blinkenlights/index.html
@@ -0,0 +1,129 @@
+<html>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ <head>
+ <link rel="stylesheet" href="../jquery-ui-themes-1.10.2/themes/humanity/jquery-ui.css">
+ <script type="text/javascript" src="../jquery-1.7.2.js"></script>
+ <script type="text/javascript" src="../jquery-ui-1.10.2.js"></script>
+ <script type="text/javascript" src="blinkenlights.js"></script>
+ <script type="text/javascript">
+ /* Query string handling */
+ var qs = (function(a) {
+ if (a == "") return {};
+ var b = {};
+ for (var i = 0; i < a.length; ++i)
+ {
+ var p=a[i].split('=');
+ if (p.length != 2) continue;
+ b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
+ }
+ return b;
+ })(window.location.search.substr(1).split('&'));
+
+ if ("predicates" in qs) {
+ raw_predicates = qs["predicates"].split('@');
+ for (var i = 0; i < raw_predicates.length; i++) {
+ if (raw_predicates[i] == "") continue;
+ p = parsePredicate(raw_predicates[i]);
+ if (p == null) { alert("cannot parse " + predicates[i]); }
+ else { predicates.push(p); }
+ }
+ } else {
+ p = parsePredicate("kernel.all.load[*] > 0.2");
+ if (p == null) { alert("cannot parse " + predicates[i]); }
+ else { predicates.push(p); }
+ }
+
+ if ("contexttype" in qs) {
+ pm_context_type=qs["contexttype"];
+ }
+
+ if ("contextspec" in qs) {
+ pm_context_spec=qs["contextspec"];
+ }
+
+ if ("updateInterval" in qs) {
+ updateInterval = qs["updateInterval"];
+ }
+
+ function saveQueryString() {
+ raw_predicates = [];
+ for (var i = 0; i < predicates.length; i++)
+ raw_predicates.push(predicates[i].to_string());
+ str = $.param({
+ predicates: raw_predicates.join('@'),
+ updateInterval: updateInterval,
+ contexttype: pm_context_type,
+ contextspec: pm_context_spec
+ });
+ str = location.protocol + "//" + location.hostname + ":" + location.port + location.pathname + "?" + str; // TODOXXX
+ $("#permalink_uri").html(str);
+ $("#permalink_uri").attr("href", str);
+ }
+
+ /* Set up document */
+ $(document).ready(function() {
+ $("#predicate_form").submit(function() {
+ pstr = $("#predicate_input").val();
+ p = parsePredicate(pstr);
+ if (p == null) { alert("cannot parse " + pstr); } else { predicates.push(p); }
+ saveQueryString();
+ return false; // stay on this page
+ });
+ $("#predicate_clear_form").submit(function() {
+ predicates = [];
+ saveQueryString();
+ return false; // stay on this page
+ });
+ $("#interval").val(updateInterval);
+ $("#interval-slider").slider({
+ value: updateInterval, min: 1000, max: 10000,
+ slide: function (event, ui) {
+ setUpdateInterval(ui.value);
+ saveQueryString();
+ $("#interval").val(updateInterval);
+ }
+ });
+ $("#contexttype").val(pm_context_type);
+ $("#contextspec").val(pm_context_spec);
+ $("#contextspec_form").submit(function() {
+ setPMContextType($("#contexttype").val());
+ setPMContextSpec($("#contextspec").val());
+ saveQueryString();
+ return false; // stay on this page
+ });
+ $("#predicate_input").val("kernel.all.load[*] > 0.2");
+ saveQueryString(); // set up initial permalink
+ });
+ </script>
+ <link rel="stylesheet" type="text/css" href="blinkenlights.css" />
+ <title>pcp blinkenlights demo</title>
+ </head>
+ <body>
+ <h1 id="header"></h1>
+ <form id="contextspec_form">
+ <select id="contexttype">
+ <option>hostspec</option>
+ <option>archivefile</option>
+ <option>local</option>
+ </select>
+ <input type="text" size="50" id="contextspec" />
+ <input value="Connect" type="submit" />
+ </form>
+ <form id="predicate_form">
+ <input id="predicate_input" size="50" type="text" />
+ <input value="Add Predicate" type="submit" />
+ </form>
+ <form id="predicate_clear_form">
+ <input value="Clear Predicates" type="submit" />
+ </form>
+ <span id="interval-slider-label"><label for="interval">Refresh interval (ms):</label><input type="text" size="5" id="interval" readonly /></span>
+ <div id="interval-slider"></div>
+ <p id="status"></p>
+ <div id="content"></div>
+ <p id="permalink">permalink: <a href="#" id="permalink_uri"></a></p>
+ <!--<footer>
+ <p id="permalink">permalink: <span></span> (<a>copy</a>)</p>
+ <p id="editlink"></p>
+ </footer>-->
+ </body>
+</html>
diff --git a/src/pmwebapi/jsdemos/favicon.ico b/src/pmwebapi/jsdemos/favicon.ico
new file mode 100644
index 0000000..168721e
--- /dev/null
+++ b/src/pmwebapi/jsdemos/favicon.ico
Binary files differ
diff --git a/src/pmwebapi/jsdemos/jquery-1.7.2.js b/src/pmwebapi/jsdemos/jquery-1.7.2.js
new file mode 100644
index 0000000..3774ff9
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jquery-1.7.2.js
@@ -0,0 +1,9404 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Wed Mar 21 12:46:34 2012 -0700
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ fired = true;
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ pixelMargin: true
+ };
+
+ // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
+ jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
+ paddingMarginBorderVisibility, paddingMarginBorder,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ paddingMarginBorder = "padding:0;margin:0;border:";
+ positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
+ paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
+ style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
+ html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
+ "<table " + style + "' cellpadding='0' cellspacing='0'>" +
+ "<tr><td></td></tr></table>";
+
+ container = document.createElement("div");
+ container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ div.innerHTML = "";
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.width = div.style.padding = "1px";
+ div.style.border = 0;
+ div.style.overflow = "hidden";
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div style='width:5px;'></div>";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+ }
+
+ div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ if ( window.getComputedStyle ) {
+ div.style.marginTop = "1%";
+ support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
+ }
+
+ if ( typeof container.style.zoom !== "undefined" ) {
+ container.style.zoom = 1;
+ }
+
+ body.removeChild( container );
+ marginDiv = div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( object );
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: selector && quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process events on disabled elements (#6911, #8165)
+ if ( cur.disabled !== true ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.globalPOS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery.clean(arguments) );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ null;
+ }
+
+
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, fragment, parent,
+ value = args[0],
+ scripts = [];
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = jQuery.buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ // Make sure that we do not leak memory by inadvertently discarding
+ // the original fragment (which might have attached data) instead of
+ // using it; in addition, use the original fragment object for the last
+ // item instead of first because it can end up being emptied incorrectly
+ // in certain situations (Bug #8070).
+ // Fragments from the fragment cache must always be cloned and never used
+ // in place.
+ results.cacheable || ( l > 1 && i < lastIndex ) ?
+ jQuery.clone( fragment, true, true ) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ type: "GET",
+ global: false,
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 fail to clone children inside object elements that use
+ // the proprietary classid attribute value (rather than the type
+ // attribute) to identify the type of content to display
+ if ( nodeName === "object" ) {
+ dest.outerHTML = src.outerHTML;
+
+ } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+ if ( src.checked ) {
+ dest.defaultChecked = dest.checked = src.checked;
+ }
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+
+ // Clear flags for bubbling special change/submit events, they must
+ // be reattached when the newly cloned events are first activated
+ dest.removeAttribute( "_submit_attached" );
+ dest.removeAttribute( "_change_attached" );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults, doc,
+ first = args[ 0 ];
+
+ // nodes may contain either an explicit document object,
+ // a jQuery collection or context object.
+ // If nodes[0] contains a valid object to assign to doc
+ if ( nodes && nodes[0] ) {
+ doc = nodes[0].ownerDocument || nodes[0];
+ }
+
+ // Ensure that an attr object doesn't incorrectly stand in as a document object
+ // Chrome and Firefox seem to allow this to occur and will throw exception
+ // Fixes #8950
+ if ( !doc.createDocumentFragment ) {
+ doc = document;
+ }
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ cacheable = true;
+
+ cacheresults = jQuery.fragments[ first ];
+ if ( cacheresults && cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [],
+ insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( elem.type === "checkbox" || elem.type === "radio" ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+ var nodeName = ( elem.nodeName || "" ).toLowerCase();
+ if ( nodeName === "input" ) {
+ fixDefaultChecked( elem );
+ // Skip scripts, get other children
+ } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+ var div = document.createElement( "div" );
+ safeFragment.appendChild( div );
+
+ div.innerHTML = elem.outerHTML;
+ return div.firstChild;
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ // IE<=8 does not properly clone detached, unknown element nodes
+ clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ?
+ elem.cloneNode( true ) :
+ shimCloneNode( elem );
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var checkScriptType, script, j,
+ ret = [];
+
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div"),
+ safeChildNodes = safeFragment.childNodes,
+ remove;
+
+ // Append wrapper element to unknown element safe doc fragment
+ if ( context === document ) {
+ // Use the fragment we've already created for this document
+ safeFragment.appendChild( div );
+ } else {
+ // Use a fragment created with the owner document
+ createSafeFragment( context ).appendChild( div );
+ }
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Clear elements from DocumentFragment (safeFragment or otherwise)
+ // to avoid hoarding elements. Fixes #11356
+ if ( div ) {
+ div.parentNode.removeChild( div );
+
+ // Guard against -1 index exceptions in FF3.6
+ if ( safeChildNodes.length > 0 ) {
+ remove = safeChildNodes[ safeChildNodes.length - 1 ];
+
+ if ( remove && remove.parentNode ) {
+ remove.parentNode.removeChild( remove );
+ }
+ }
+ }
+ }
+ }
+
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
+ for ( j = 0; j < len; j++ ) {
+ findInputs( elem[j] );
+ }
+ } else {
+ findInputs( elem );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ checkScriptType = function( elem ) {
+ return !elem.type || rscriptType.test( elem.type );
+ };
+ for ( i = 0; ret[i]; i++ ) {
+ script = ret[i];
+ if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
+ scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
+
+ } else {
+ if ( script.nodeType === 1 ) {
+ var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ }
+ fragment.appendChild( script );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id,
+ cache = jQuery.cache,
+ special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ continue;
+ }
+
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ];
+
+ if ( data && data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+
+ // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+ if ( data.handle ) {
+ data.handle.elem = null;
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+ rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
+ rrelNum = /^([\-+])=([\-+.\de]+)/,
+ rmargin = /^margin/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+
+ // order is important!
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+
+ curCSS,
+
+ getComputedStyle,
+ currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } else {
+ return elem.style.opacity;
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, origName = jQuery.camelCase( name ),
+ style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra ) {
+ var ret, hooks;
+
+ // Make sure that we're working with the right name
+ name = jQuery.camelCase( name );
+ hooks = jQuery.cssHooks[ name ];
+ name = jQuery.cssProps[ name ] || name;
+
+ // cssFloat needs a special treatment
+ if ( name === "cssFloat" ) {
+ name = "float";
+ }
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+ return ret;
+
+ // Otherwise, if a way to get the computed value exists, use that
+ } else if ( curCSS ) {
+ return curCSS( elem, name );
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {},
+ ret, name;
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// DEPRECATED in 1.3, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, name ) {
+ var ret, defaultView, computedStyle, width,
+ style = elem.style;
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ if ( (defaultView = elem.ownerDocument.defaultView) &&
+ (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+
+ ret = computedStyle.getPropertyValue( name );
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
+ // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
+ width = style.width;
+ style.width = ret;
+ ret = computedStyle.width;
+ style.width = width;
+ }
+
+ return ret;
+ };
+}
+
+if ( document.documentElement.currentStyle ) {
+ currentStyle = function( elem, name ) {
+ var left, rsLeft, uncomputed,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && (uncomputed = style[ name ]) ) {
+ ret = uncomputed;
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( rnumnonpx.test( ret ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ i = name === "width" ? 1 : 0,
+ len = 4;
+
+ if ( val > 0 ) {
+ if ( extra !== "border" ) {
+ for ( ; i < len; i += 2 ) {
+ if ( !extra ) {
+ val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
+ } else {
+ val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+ }
+
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+
+ // Add padding, border, margin
+ if ( extra ) {
+ for ( ; i < len; i += 2 ) {
+ val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ if ( extra !== "padding" ) {
+ val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ return getWidthOrHeight( elem, name, extra );
+ } else {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ }
+ }
+ },
+
+ set: function( elem, value ) {
+ return rnum.test( value ) ?
+ value + "px" :
+ value;
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+jQuery(function() {
+ // This hook cannot be added until DOM ready because the support test
+ // for it is not run until after DOM ready
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "margin-right" );
+ } else {
+ return elem.style.marginRight;
+ }
+ });
+ }
+ };
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth,
+ height = elem.offsetHeight;
+
+ return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+});
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rselectTextarea = /^(?:select|textarea)/i,
+ rspacesAjax = /\s+/,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Document location
+ ajaxLocation,
+
+ // Document location segments
+ ajaxLocParts,
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ if ( jQuery.isFunction( func ) ) {
+ var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+ i = 0,
+ length = dataTypes.length,
+ dataType,
+ list,
+ placeBefore;
+
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters ),
+ selection;
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf( " " );
+ if ( off >= 0 ) {
+ var selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ // Complete callback (responseText is used internally)
+ complete: function( jqXHR, status, responseText ) {
+ // Store the response as specified by the jqXHR object
+ responseText = jqXHR.responseText;
+ // If successful, inject the HTML into all the matched elements
+ if ( jqXHR.isResolved() ) {
+ // #4825: Get the actual response in case
+ // a dataFilter is present in ajaxSettings
+ jqXHR.done(function( r ) {
+ responseText = r;
+ });
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [ responseText, status, jqXHR ] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // ifModified key
+ ifModifiedKey,
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // The jqXHR state
+ state = 0,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || "abort";
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ var isSuccess,
+ success,
+ error,
+ statusText = nativeStatusText,
+ response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+ lastModified,
+ etag;
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+ jQuery.lastModified[ ifModifiedKey ] = lastModified;
+ }
+ if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+ jQuery.etag[ ifModifiedKey ] = etag;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ try {
+ success = ajaxConvert( s, response );
+ statusText = "success";
+ isSuccess = true;
+ } catch(e) {
+ // We have a parsererror
+ statusText = "parsererror";
+ error = e;
+ }
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.then( tmp, tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return false;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already
+ jqXHR.abort();
+ return false;
+
+ }
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : value;
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( var name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields,
+ ct,
+ type,
+ finalDataType,
+ firstDataType;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ var dataTypes = s.dataTypes,
+ converters = {},
+ i,
+ key,
+ length = dataTypes.length,
+ tmp,
+ // Current and previous dataTypes
+ current = dataTypes[ 0 ],
+ prev,
+ // Conversion expression
+ conversion,
+ // Conversion function
+ conv,
+ // Conversion functions (transitive conversion)
+ conv1,
+ conv2;
+
+ // For each dataType in the chain
+ for ( i = 1; i < length; i++ ) {
+
+ // Create converters map
+ // with lowercased keys
+ if ( i === 1 ) {
+ for ( key in s.converters ) {
+ if ( typeof key === "string" ) {
+ converters[ key.toLowerCase() ] = s.converters[ key ];
+ }
+ }
+ }
+
+ // Get the dataTypes
+ prev = current;
+ current = dataTypes[ i ];
+
+ // If current is auto dataType, update it to prev
+ if ( current === "*" ) {
+ current = prev;
+ // If no auto and dataTypes are actually different
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Get the converter
+ conversion = prev + " " + current;
+ conv = converters[ conversion ] || converters[ "* " + current ];
+
+ // If there is no direct converter, search transitively
+ if ( !conv ) {
+ conv2 = undefined;
+ for ( conv1 in converters ) {
+ tmp = conv1.split( " " );
+ if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+ conv2 = converters[ tmp[1] + " " + current ];
+ if ( conv2 ) {
+ conv1 = converters[ conv1 ];
+ if ( conv1 === true ) {
+ conv = conv2;
+ } else if ( conv2 === true ) {
+ conv = conv1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // If we found no converter, dispatch an error
+ if ( !( conv || conv2 ) ) {
+ jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+ }
+ // If found converter is not an equivalence
+ if ( conv !== true ) {
+ // Convert with 1 or 2 converters accordingly
+ response = conv ? conv( response ) : conv2( conv1(response) );
+ }
+ }
+ }
+ return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+ jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ return jQuery.expando + "_" + ( jsc++ );
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
+
+ if ( s.dataTypes[ 0 ] === "jsonp" ||
+ s.jsonp !== false && ( jsre.test( s.url ) ||
+ inspectData && jsre.test( s.data ) ) ) {
+
+ var responseContainer,
+ jsonpCallback = s.jsonpCallback =
+ jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+ previous = window[ jsonpCallback ],
+ url = s.url,
+ data = s.data,
+ replace = "$1" + jsonpCallback + "$2";
+
+ if ( s.jsonp !== false ) {
+ url = url.replace( jsre, replace );
+ if ( s.url === url ) {
+ if ( inspectData ) {
+ data = data.replace( jsre, replace );
+ }
+ if ( s.data === data ) {
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+ }
+ }
+ }
+
+ s.url = url;
+ s.data = data;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ responseContainer = [ response ];
+ };
+
+ // Clean-up function
+ jqXHR.always(function() {
+ // Set callback back to previous value
+ window[ jsonpCallback ] = previous;
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( previous ) ) {
+ window[ jsonpCallback ]( responseContainer[ 0 ] );
+ }
+ });
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( jsonpCallback + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Delegate to script
+ return "script";
+ }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0,
+ xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var xhr = s.xhr(),
+ handle,
+ i;
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occured
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( _ ) {
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ // if we're in sync mode or it's in cache
+ // and has been retrieved directly (IE6 & IE7)
+ // we need to manually fire the callback
+ if ( !s.async || xhr.readyState === 4 ) {
+ callback();
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+
+
+
+
+var elemdisplay = {},
+ iframe, iframeDoc,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ],
+ fxNow;
+
+jQuery.fn.extend({
+ show: function( speed, easing, callback ) {
+ var elem, display;
+
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("show", 3), speed, easing, callback );
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+ display = elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( (display === "" && jQuery.css(elem, "display") === "none") ||
+ !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ if ( display === "" || display === "none" ) {
+ elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+ }
+ }
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, easing, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, easing, callback);
+
+ } else {
+ var elem, display,
+ i = 0,
+ j = this.length;
+
+ for ( ; i < j; i++ ) {
+ elem = this[i];
+ if ( elem.style ) {
+ display = jQuery.css( elem, "display" );
+
+ if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ if ( this[i].style ) {
+ this[i].style.display = "none";
+ }
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2, callback ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2, callback);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, easing, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, easing, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed( speed, easing, callback );
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete, [ false ] );
+ }
+
+ // Do not change referenced properties as per-property easing will be lost
+ prop = jQuery.extend( {}, prop );
+
+ function doAnimation() {
+ // XXX 'this' does not always have a nodeName when running the
+ // test suite
+
+ if ( optall.queue === false ) {
+ jQuery._mark( this );
+ }
+
+ var opt = jQuery.extend( {}, optall ),
+ isElement = this.nodeType === 1,
+ hidden = isElement && jQuery(this).is(":hidden"),
+ name, val, p, e, hooks, replace,
+ parts, start, end, unit,
+ method;
+
+ // will store per property easing and be used to determine when an animation is complete
+ opt.animatedProperties = {};
+
+ // first pass over propertys to expand / normalize
+ for ( p in prop ) {
+ name = jQuery.camelCase( p );
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ }
+
+ if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
+ replace = hooks.expand( prop[ name ] );
+ delete prop[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'p' from above because we have the correct "name"
+ for ( p in replace ) {
+ if ( ! ( p in prop ) ) {
+ prop[ p ] = replace[ p ];
+ }
+ }
+ }
+ }
+
+ for ( name in prop ) {
+ val = prop[ name ];
+ // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+ if ( jQuery.isArray( val ) ) {
+ opt.animatedProperties[ name ] = val[ 1 ];
+ val = prop[ name ] = val[ 0 ];
+ } else {
+ opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+ }
+
+ if ( val === "hide" && hidden || val === "show" && !hidden ) {
+ return opt.complete.call( this );
+ }
+
+ if ( isElement && ( name === "height" || name === "width" ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( this, "display" ) === "inline" &&
+ jQuery.css( this, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+ this.style.display = "inline-block";
+
+ } else {
+ this.style.zoom = 1;
+ }
+ }
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ for ( p in prop ) {
+ e = new jQuery.fx( this, opt, p );
+ val = prop[ p ];
+
+ if ( rfxtypes.test( val ) ) {
+
+ // Tracks whether to show or hide based on private
+ // data attached to the element
+ method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+ if ( method ) {
+ jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+ e[ method ]();
+ } else {
+ e[ val ]();
+ }
+
+ } else {
+ parts = rfxnum.exec( val );
+ start = e.cur();
+
+ if ( parts ) {
+ end = parseFloat( parts[2] );
+ unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ jQuery.style( this, p, (end || 1) + unit);
+ start = ( (end || 1) / e.cur() ) * start;
+ jQuery.style( this, p, start + unit);
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ }
+
+ // For JS strict compliance
+ return true;
+ }
+
+ return optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+
+ stop: function( type, clearQueue, gotoEnd ) {
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var index,
+ hadTimers = false,
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ // clear marker counters if we know they won't be
+ if ( !gotoEnd ) {
+ jQuery._unmark( true, this );
+ }
+
+ function stopQueue( elem, data, index ) {
+ var hooks = data[ index ];
+ jQuery.removeData( elem, index, true );
+ hooks.stop( gotoEnd );
+ }
+
+ if ( type == null ) {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+ stopQueue( this, data, index );
+ }
+ }
+ } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+ stopQueue( this, data, index );
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ if ( gotoEnd ) {
+
+ // force the next step to be the last
+ timers[ index ]( true );
+ } else {
+ timers[ index ].saveState();
+ }
+ hadTimers = true;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( !( gotoEnd && hadTimers ) ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout( clearFxNow, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+ fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx( "show", 1 ),
+ slideUp: genFx( "hide", 1 ),
+ slideToggle: genFx( "toggle", 1 ),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function( noUnmark ) {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ } else if ( noUnmark !== false ) {
+ jQuery._unmark( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ options.orig = options.orig || {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+ },
+
+ // Get the current size
+ cur: function() {
+ if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var parsed,
+ r = jQuery.css( this.elem, this.prop );
+ // Empty strings, null, undefined and "auto" are converted to 0,
+ // complex values such as "rotate(1rad)" are returned as is,
+ // simple values such as "10px" are parsed to Float.
+ return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ var self = this,
+ fx = jQuery.fx;
+
+ this.startTime = fxNow || createFxNow();
+ this.end = to;
+ this.now = this.start = from;
+ this.pos = this.state = 0;
+ this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+ function t( gotoEnd ) {
+ return self.step( gotoEnd );
+ }
+
+ t.queue = this.options.queue;
+ t.elem = this.elem;
+ t.saveState = function() {
+ if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+ if ( self.options.hide ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+ } else if ( self.options.show ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+ }
+ }
+ };
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval( fx.tick, fx.interval );
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any flash of content
+ if ( dataShow !== undefined ) {
+ // This show is picking up where a previous hide or show left off
+ this.custom( this.cur(), dataShow );
+ } else {
+ this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+ }
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom( this.cur(), 0 );
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var p, n, complete,
+ t = fxNow || createFxNow(),
+ done = true,
+ elem = this.elem,
+ options = this.options;
+
+ if ( gotoEnd || t >= options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ options.animatedProperties[ this.prop ] = true;
+
+ for ( p in options.animatedProperties ) {
+ if ( options.animatedProperties[ p ] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ // Reset the overflow
+ if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+ jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+ elem.style[ "overflow" + value ] = options.overflow[ index ];
+ });
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( options.hide ) {
+ jQuery( elem ).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( options.hide || options.show ) {
+ for ( p in options.animatedProperties ) {
+ jQuery.style( elem, p, options.orig[ p ] );
+ jQuery.removeData( elem, "fxshow" + p, true );
+ // Toggle data is no longer needed
+ jQuery.removeData( elem, "toggle" + p, true );
+ }
+ }
+
+ // Execute the complete function
+ // in the event that the complete function throws an exception
+ // we must ensure it won't be called twice. #5684
+
+ complete = options.complete;
+ if ( complete ) {
+
+ options.complete = false;
+ complete.call( elem );
+ }
+ }
+
+ return false;
+
+ } else {
+ // classical easing cannot be used with an Infinity duration
+ if ( options.duration == Infinity ) {
+ this.now = t;
+ } else {
+ n = t - this.startTime;
+ this.state = n / options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+ this.now = this.start + ( (this.end - this.start) * this.pos );
+ }
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ interval: 13,
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style( fx.elem, "opacity", fx.now );
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+// Ensure props that can't be negative don't go there on undershoot easing
+jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
+ // exclude marginTop, marginLeft, marginBottom and marginRight from this list
+ if ( prop.indexOf( "margin" ) ) {
+ jQuery.fx.step[ prop ] = function( fx ) {
+ jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+ };
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+ if ( !elemdisplay[ nodeName ] ) {
+
+ var body = document.body,
+ elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+ display = elem.css( "display" );
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // No iframe to use yet, so create it
+ if ( !iframe ) {
+ iframe = document.createElement( "iframe" );
+ iframe.frameBorder = iframe.width = iframe.height = 0;
+ }
+
+ body.appendChild( iframe );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.createElement( nodeName );
+
+ iframeDoc.body.appendChild( elem );
+
+ display = jQuery.css( elem, "display" );
+ body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return elemdisplay[ nodeName ];
+}
+
+
+
+
+var getOffset,
+ rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ getOffset = function( elem, doc, docElem, box ) {
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !box || !jQuery.contains( docElem, elem ) ) {
+ return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ }
+
+ var body = doc.body,
+ win = getWindow( doc ),
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+ top = box.top + scrollTop - clientTop,
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ getOffset = function( elem, doc, docElem ) {
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ body = doc.body,
+ defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop,
+ left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent;
+ offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var elem = this[0],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return null;
+ }
+
+ if ( elem === doc.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ return getOffset( elem, doc, doc.documentElement );
+};
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ var clientProp = "client" + name,
+ scrollProp = "scroll" + name,
+ offsetProp = "offset" + name;
+
+ // innerHeight and innerWidth
+ jQuery.fn[ "inner" + name ] = function() {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, "padding" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn[ "outer" + name ] = function( margin ) {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( value ) {
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc, docElemProp, orig, ret;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ doc = elem.document;
+ docElemProp = doc.documentElement[ clientProp ];
+ return jQuery.support.boxModel && docElemProp ||
+ doc.body && doc.body[ clientProp ] || docElemProp;
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ doc = elem.documentElement;
+
+ // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
+ // so we can't use max, as it'll choose the incorrect offset[Width/Height]
+ // instead we use the correct client[Width/Height]
+ // support:IE6
+ if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
+ return doc[ clientProp ];
+ }
+
+ return Math.max(
+ elem.body[ scrollProp ], doc[ scrollProp ],
+ elem.body[ offsetProp ], doc[ offsetProp ]
+ );
+ }
+
+ // Get width or height on the element
+ if ( value === undefined ) {
+ orig = jQuery.css( elem, type );
+ ret = parseFloat( orig );
+ return jQuery.isNumeric( ret ) ? ret : orig;
+ }
+
+ // Set the width or height on the element
+ jQuery( elem ).css( type, value );
+ }, type, value, arguments.length, null );
+ };
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
diff --git a/src/pmwebapi/jsdemos/jquery-ui-1.10.2.js b/src/pmwebapi/jsdemos/jquery-ui-1.10.2.js
new file mode 100644
index 0000000..6eccbfe
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jquery-ui-1.10.2.js
@@ -0,0 +1,14987 @@
+/*! jQuery UI - v1.10.2 - 2013-03-14
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.sortable.js, jquery.ui.effect.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.position.js, jquery.ui.progressbar.js, jquery.ui.slider.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js
+* Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */
+(function( $, undefined ) {
+
+var uuid = 0,
+ runiqueId = /^ui-id-\d+$/;
+
+// $.ui might exist from components with no dependencies, e.g., $.ui.position
+$.ui = $.ui || {};
+
+$.extend( $.ui, {
+ version: "1.10.2",
+
+ keyCode: {
+ BACKSPACE: 8,
+ COMMA: 188,
+ DELETE: 46,
+ DOWN: 40,
+ END: 35,
+ ENTER: 13,
+ ESCAPE: 27,
+ HOME: 36,
+ LEFT: 37,
+ NUMPAD_ADD: 107,
+ NUMPAD_DECIMAL: 110,
+ NUMPAD_DIVIDE: 111,
+ NUMPAD_ENTER: 108,
+ NUMPAD_MULTIPLY: 106,
+ NUMPAD_SUBTRACT: 109,
+ PAGE_DOWN: 34,
+ PAGE_UP: 33,
+ PERIOD: 190,
+ RIGHT: 39,
+ SPACE: 32,
+ TAB: 9,
+ UP: 38
+ }
+});
+
+// plugins
+$.fn.extend({
+ focus: (function( orig ) {
+ return function( delay, fn ) {
+ return typeof delay === "number" ?
+ this.each(function() {
+ var elem = this;
+ setTimeout(function() {
+ $( elem ).focus();
+ if ( fn ) {
+ fn.call( elem );
+ }
+ }, delay );
+ }) :
+ orig.apply( this, arguments );
+ };
+ })( $.fn.focus ),
+
+ scrollParent: function() {
+ var scrollParent;
+ if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
+ scrollParent = this.parents().filter(function() {
+ return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
+ }).eq(0);
+ } else {
+ scrollParent = this.parents().filter(function() {
+ return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
+ }).eq(0);
+ }
+
+ return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
+ },
+
+ zIndex: function( zIndex ) {
+ if ( zIndex !== undefined ) {
+ return this.css( "zIndex", zIndex );
+ }
+
+ if ( this.length ) {
+ var elem = $( this[ 0 ] ), position, value;
+ while ( elem.length && elem[ 0 ] !== document ) {
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
+ // This makes behavior of this function consistent across browsers
+ // WebKit always returns auto if the element is positioned
+ position = elem.css( "position" );
+ if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+ // IE returns 0 when zIndex is not specified
+ // other browsers return a string
+ // we ignore the case of nested elements with an explicit value of 0
+ // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+ value = parseInt( elem.css( "zIndex" ), 10 );
+ if ( !isNaN( value ) && value !== 0 ) {
+ return value;
+ }
+ }
+ elem = elem.parent();
+ }
+ }
+
+ return 0;
+ },
+
+ uniqueId: function() {
+ return this.each(function() {
+ if ( !this.id ) {
+ this.id = "ui-id-" + (++uuid);
+ }
+ });
+ },
+
+ removeUniqueId: function() {
+ return this.each(function() {
+ if ( runiqueId.test( this.id ) ) {
+ $( this ).removeAttr( "id" );
+ }
+ });
+ }
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+ var map, mapName, img,
+ nodeName = element.nodeName.toLowerCase();
+ if ( "area" === nodeName ) {
+ map = element.parentNode;
+ mapName = map.name;
+ if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+ return false;
+ }
+ img = $( "img[usemap=#" + mapName + "]" )[0];
+ return !!img && visible( img );
+ }
+ return ( /input|select|textarea|button|object/.test( nodeName ) ?
+ !element.disabled :
+ "a" === nodeName ?
+ element.href || isTabIndexNotNaN :
+ isTabIndexNotNaN) &&
+ // the element and all of its ancestors must be visible
+ visible( element );
+}
+
+function visible( element ) {
+ return $.expr.filters.visible( element ) &&
+ !$( element ).parents().addBack().filter(function() {
+ return $.css( this, "visibility" ) === "hidden";
+ }).length;
+}
+
+$.extend( $.expr[ ":" ], {
+ data: $.expr.createPseudo ?
+ $.expr.createPseudo(function( dataName ) {
+ return function( elem ) {
+ return !!$.data( elem, dataName );
+ };
+ }) :
+ // support: jQuery <1.8
+ function( elem, i, match ) {
+ return !!$.data( elem, match[ 3 ] );
+ },
+
+ focusable: function( element ) {
+ return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+ },
+
+ tabbable: function( element ) {
+ var tabIndex = $.attr( element, "tabindex" ),
+ isTabIndexNaN = isNaN( tabIndex );
+ return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+ }
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+ $.each( [ "Width", "Height" ], function( i, name ) {
+ var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+ type = name.toLowerCase(),
+ orig = {
+ innerWidth: $.fn.innerWidth,
+ innerHeight: $.fn.innerHeight,
+ outerWidth: $.fn.outerWidth,
+ outerHeight: $.fn.outerHeight
+ };
+
+ function reduce( elem, size, border, margin ) {
+ $.each( side, function() {
+ size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+ if ( border ) {
+ size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+ }
+ if ( margin ) {
+ size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+ }
+ });
+ return size;
+ }
+
+ $.fn[ "inner" + name ] = function( size ) {
+ if ( size === undefined ) {
+ return orig[ "inner" + name ].call( this );
+ }
+
+ return this.each(function() {
+ $( this ).css( type, reduce( this, size ) + "px" );
+ });
+ };
+
+ $.fn[ "outer" + name] = function( size, margin ) {
+ if ( typeof size !== "number" ) {
+ return orig[ "outer" + name ].call( this, size );
+ }
+
+ return this.each(function() {
+ $( this).css( type, reduce( this, size, true, margin ) + "px" );
+ });
+ };
+ });
+}
+
+// support: jQuery <1.8
+if ( !$.fn.addBack ) {
+ $.fn.addBack = function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ };
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+ $.fn.removeData = (function( removeData ) {
+ return function( key ) {
+ if ( arguments.length ) {
+ return removeData.call( this, $.camelCase( key ) );
+ } else {
+ return removeData.call( this );
+ }
+ };
+ })( $.fn.removeData );
+}
+
+
+
+
+
+// deprecated
+$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
+
+$.support.selectstart = "onselectstart" in document.createElement( "div" );
+$.fn.extend({
+ disableSelection: function() {
+ return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+ ".ui-disableSelection", function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ enableSelection: function() {
+ return this.unbind( ".ui-disableSelection" );
+ }
+});
+
+$.extend( $.ui, {
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
+ plugin: {
+ add: function( module, option, set ) {
+ var i,
+ proto = $.ui[ module ].prototype;
+ for ( i in set ) {
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
+ }
+ },
+ call: function( instance, name, args ) {
+ var i,
+ set = instance.plugins[ name ];
+ if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
+ return;
+ }
+
+ for ( i = 0; i < set.length; i++ ) {
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
+ set[ i ][ 1 ].apply( instance.element, args );
+ }
+ }
+ }
+ },
+
+ // only used by resizable
+ hasScroll: function( el, a ) {
+
+ //If overflow is hidden, the element might have extra content, but the user wants to hide it
+ if ( $( el ).css( "overflow" ) === "hidden") {
+ return false;
+ }
+
+ var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+ has = false;
+
+ if ( el[ scroll ] > 0 ) {
+ return true;
+ }
+
+ // TODO: determine which cases actually cause this to happen
+ // if the element doesn't have the scroll set, see if it's possible to
+ // set the scroll
+ el[ scroll ] = 1;
+ has = ( el[ scroll ] > 0 );
+ el[ scroll ] = 0;
+ return has;
+ }
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+var uuid = 0,
+ slice = Array.prototype.slice,
+ _cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ try {
+ $( elem ).triggerHandler( "remove" );
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ }
+ _cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ // proxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ proxiedPrototype = {},
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( !$.isFunction( value ) ) {
+ proxiedPrototype[ prop ] = value;
+ return;
+ }
+ proxiedPrototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
+ }, proxiedPrototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+ var input = slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+};
+
+$.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.widget.extend.apply( null, [ options ].concat(args) ) :
+ options;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} )._init();
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "<div>",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ .removeData( this.widgetName )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( value === undefined ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( value === undefined ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+ .attr( "aria-disabled", value );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ // accept selectors, DOM elements
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^(\w+)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function() {
+ mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+ version: "1.10.2",
+ options: {
+ cancel: "input,textarea,button,select,option",
+ distance: 1,
+ delay: 0
+ },
+ _mouseInit: function() {
+ var that = this;
+
+ this.element
+ .bind("mousedown."+this.widgetName, function(event) {
+ return that._mouseDown(event);
+ })
+ .bind("click."+this.widgetName, function(event) {
+ if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
+ $.removeData(event.target, that.widgetName + ".preventClickEvent");
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+
+ this.started = false;
+ },
+
+ // TODO: make sure destroying one instance of mouse doesn't mess with
+ // other instances of mouse
+ _mouseDestroy: function() {
+ this.element.unbind("."+this.widgetName);
+ if ( this._mouseMoveDelegate ) {
+ $(document)
+ .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+ .unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
+ }
+ },
+
+ _mouseDown: function(event) {
+ // don't let more than one widget handle mouseStart
+ if( mouseHandled ) { return; }
+
+ // we may have missed mouseup (out of window)
+ (this._mouseStarted && this._mouseUp(event));
+
+ this._mouseDownEvent = event;
+
+ var that = this,
+ btnIsLeft = (event.which === 1),
+ // event.target.nodeName works around a bug in IE 8 with
+ // disabled inputs (#7620)
+ elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+ if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+ return true;
+ }
+
+ this.mouseDelayMet = !this.options.delay;
+ if (!this.mouseDelayMet) {
+ this._mouseDelayTimer = setTimeout(function() {
+ that.mouseDelayMet = true;
+ }, this.options.delay);
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted = (this._mouseStart(event) !== false);
+ if (!this._mouseStarted) {
+ event.preventDefault();
+ return true;
+ }
+ }
+
+ // Click event may never have fired (Gecko & Opera)
+ if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
+ $.removeData(event.target, this.widgetName + ".preventClickEvent");
+ }
+
+ // these delegates are required to keep context
+ this._mouseMoveDelegate = function(event) {
+ return that._mouseMove(event);
+ };
+ this._mouseUpDelegate = function(event) {
+ return that._mouseUp(event);
+ };
+ $(document)
+ .bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+ .bind("mouseup."+this.widgetName, this._mouseUpDelegate);
+
+ event.preventDefault();
+
+ mouseHandled = true;
+ return true;
+ },
+
+ _mouseMove: function(event) {
+ // IE mouseup check - mouseup happened when mouse was out of window
+ if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
+ return this._mouseUp(event);
+ }
+
+ if (this._mouseStarted) {
+ this._mouseDrag(event);
+ return event.preventDefault();
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted =
+ (this._mouseStart(this._mouseDownEvent, event) !== false);
+ (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+ }
+
+ return !this._mouseStarted;
+ },
+
+ _mouseUp: function(event) {
+ $(document)
+ .unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
+ .unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
+
+ if (this._mouseStarted) {
+ this._mouseStarted = false;
+
+ if (event.target === this._mouseDownEvent.target) {
+ $.data(event.target, this.widgetName + ".preventClickEvent", true);
+ }
+
+ this._mouseStop(event);
+ }
+
+ return false;
+ },
+
+ _mouseDistanceMet: function(event) {
+ return (Math.max(
+ Math.abs(this._mouseDownEvent.pageX - event.pageX),
+ Math.abs(this._mouseDownEvent.pageY - event.pageY)
+ ) >= this.options.distance
+ );
+ },
+
+ _mouseDelayMet: function(/* event */) {
+ return this.mouseDelayMet;
+ },
+
+ // These are placeholder methods, to be overriden by extending plugin
+ _mouseStart: function(/* event */) {},
+ _mouseDrag: function(/* event */) {},
+ _mouseStop: function(/* event */) {},
+ _mouseCapture: function(/* event */) { return true; }
+});
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.widget("ui.draggable", $.ui.mouse, {
+ version: "1.10.2",
+ widgetEventPrefix: "drag",
+ options: {
+ addClasses: true,
+ appendTo: "parent",
+ axis: false,
+ connectToSortable: false,
+ containment: false,
+ cursor: "auto",
+ cursorAt: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ iframeFix: false,
+ opacity: false,
+ refreshPositions: false,
+ revert: false,
+ revertDuration: 500,
+ scope: "default",
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ snap: false,
+ snapMode: "both",
+ snapTolerance: 20,
+ stack: false,
+ zIndex: false,
+
+ // callbacks
+ drag: null,
+ start: null,
+ stop: null
+ },
+ _create: function() {
+
+ if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
+ this.element[0].style.position = "relative";
+ }
+ if (this.options.addClasses){
+ this.element.addClass("ui-draggable");
+ }
+ if (this.options.disabled){
+ this.element.addClass("ui-draggable-disabled");
+ }
+
+ this._mouseInit();
+
+ },
+
+ _destroy: function() {
+ this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function(event) {
+
+ var o = this.options;
+
+ // among others, prevent a drag on a resizable-handle
+ if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
+ return false;
+ }
+
+ //Quit if we're not on a valid handle
+ this.handle = this._getHandle(event);
+ if (!this.handle) {
+ return false;
+ }
+
+ $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
+ $("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
+ .css({
+ width: this.offsetWidth+"px", height: this.offsetHeight+"px",
+ position: "absolute", opacity: "0.001", zIndex: 1000
+ })
+ .css($(this).offset())
+ .appendTo("body");
+ });
+
+ return true;
+
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options;
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ this.helper.addClass("ui-draggable-dragging");
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ //If ddmanager is used for droppables, set the global draggable
+ if($.ui.ddmanager) {
+ $.ui.ddmanager.current = this;
+ }
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Store the helper's css position
+ this.cssPosition = this.helper.css("position");
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.positionAbs = this.element.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ //Generate the original position
+ this.originalPosition = this.position = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Set a containment if given in the options
+ if(o.containment) {
+ this._setContainment();
+ }
+
+ //Trigger event + callbacks
+ if(this._trigger("start", event) === false) {
+ this._clear();
+ return false;
+ }
+
+ //Recache the helper size
+ this._cacheHelperProportions();
+
+ //Prepare the droppable offsets
+ if ($.ui.ddmanager && !o.dropBehaviour) {
+ $.ui.ddmanager.prepareOffsets(this, event);
+ }
+
+
+ this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
+ if ( $.ui.ddmanager ) {
+ $.ui.ddmanager.dragStart(this, event);
+ }
+
+ return true;
+ },
+
+ _mouseDrag: function(event, noPropagation) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Call plugins and callbacks and use the resulting position if something is returned
+ if (!noPropagation) {
+ var ui = this._uiHash();
+ if(this._trigger("drag", event, ui) === false) {
+ this._mouseUp({});
+ return false;
+ }
+ this.position = ui.position;
+ }
+
+ if(!this.options.axis || this.options.axis !== "y") {
+ this.helper[0].style.left = this.position.left+"px";
+ }
+ if(!this.options.axis || this.options.axis !== "x") {
+ this.helper[0].style.top = this.position.top+"px";
+ }
+ if($.ui.ddmanager) {
+ $.ui.ddmanager.drag(this, event);
+ }
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ //If we are using droppables, inform the manager about the drop
+ var element,
+ that = this,
+ elementInDom = false,
+ dropped = false;
+ if ($.ui.ddmanager && !this.options.dropBehaviour) {
+ dropped = $.ui.ddmanager.drop(this, event);
+ }
+
+ //if a drop comes from outside (a sortable)
+ if(this.dropped) {
+ dropped = this.dropped;
+ this.dropped = false;
+ }
+
+ //if the original element is no longer in the DOM don't bother to continue (see #8269)
+ element = this.element[0];
+ while ( element && (element = element.parentNode) ) {
+ if (element === document ) {
+ elementInDom = true;
+ }
+ }
+ if ( !elementInDom && this.options.helper === "original" ) {
+ return false;
+ }
+
+ if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+ $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+ if(that._trigger("stop", event) !== false) {
+ that._clear();
+ }
+ });
+ } else {
+ if(this._trigger("stop", event) !== false) {
+ this._clear();
+ }
+ }
+
+ return false;
+ },
+
+ _mouseUp: function(event) {
+ //Remove frame helpers
+ $("div.ui-draggable-iframeFix").each(function() {
+ this.parentNode.removeChild(this);
+ });
+
+ //If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
+ if( $.ui.ddmanager ) {
+ $.ui.ddmanager.dragStop(this, event);
+ }
+
+ return $.ui.mouse.prototype._mouseUp.call(this, event);
+ },
+
+ cancel: function() {
+
+ if(this.helper.is(".ui-draggable-dragging")) {
+ this._mouseUp({});
+ } else {
+ this._clear();
+ }
+
+ return this;
+
+ },
+
+ _getHandle: function(event) {
+ return this.options.handle ?
+ !!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
+ true;
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options,
+ helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
+
+ if(!helper.parents("body").length) {
+ helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
+ }
+
+ if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
+ helper.css("position", "absolute");
+ }
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj === "string") {
+ obj = obj.split(" ");
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ("left" in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ("right" in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ("top" in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ("bottom" in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ //Ugly IE fix
+ if((this.offsetParent[0] === document.body) ||
+ (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
+ po = { top: 0, left: 0 };
+ }
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition === "relative") {
+ var p = this.element.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.element.css("marginLeft"),10) || 0),
+ top: (parseInt(this.element.css("marginTop"),10) || 0),
+ right: (parseInt(this.element.css("marginRight"),10) || 0),
+ bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var over, c, ce,
+ o = this.options;
+
+ if(o.containment === "parent") {
+ o.containment = this.helper[0].parentNode;
+ }
+ if(o.containment === "document" || o.containment === "window") {
+ this.containment = [
+ o.containment === "document" ? 0 : $(window).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+ o.containment === "document" ? 0 : $(window).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+ (o.containment === "document" ? 0 : $(window).scrollLeft()) + $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
+ (o.containment === "document" ? 0 : $(window).scrollTop()) + ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+ }
+
+ if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor !== Array) {
+ c = $(o.containment);
+ ce = c[0];
+
+ if(!ce) {
+ return;
+ }
+
+ over = ($(ce).css("overflow") !== "hidden");
+
+ this.containment = [
+ (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0),
+ (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0),
+ (over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderRightWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left - this.margins.right,
+ (over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderBottomWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - this.margins.bottom
+ ];
+ this.relative_container = c;
+
+ } else if(o.containment.constructor === Array) {
+ this.containment = o.containment;
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) {
+ pos = this.position;
+ }
+
+ var mod = d === "absolute" ? 1 : -1,
+ scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top + // The absolute mouse position
+ this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left + // The absolute mouse position
+ this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var containment, co, top, left,
+ o = this.options,
+ scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
+ scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName),
+ pageX = event.pageX,
+ pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+ if(this.containment) {
+ if (this.relative_container){
+ co = this.relative_container.offset();
+ containment = [ this.containment[0] + co.left,
+ this.containment[1] + co.top,
+ this.containment[2] + co.left,
+ this.containment[3] + co.top ];
+ }
+ else {
+ containment = this.containment;
+ }
+
+ if(event.pageX - this.offset.click.left < containment[0]) {
+ pageX = containment[0] + this.offset.click.left;
+ }
+ if(event.pageY - this.offset.click.top < containment[1]) {
+ pageY = containment[1] + this.offset.click.top;
+ }
+ if(event.pageX - this.offset.click.left > containment[2]) {
+ pageX = containment[2] + this.offset.click.left;
+ }
+ if(event.pageY - this.offset.click.top > containment[3]) {
+ pageY = containment[3] + this.offset.click.top;
+ }
+ }
+
+ if(o.grid) {
+ //Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
+ top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
+ pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
+ pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY - // The absolute mouse position
+ this.offset.click.top - // Click offset (relative to the element)
+ this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX - // The absolute mouse position
+ this.offset.click.left - // Click offset (relative to the element)
+ this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _clear: function() {
+ this.helper.removeClass("ui-draggable-dragging");
+ if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
+ this.helper.remove();
+ }
+ this.helper = null;
+ this.cancelHelperRemoval = false;
+ },
+
+ // From now on bulk stuff - mainly helpers
+
+ _trigger: function(type, event, ui) {
+ ui = ui || this._uiHash();
+ $.ui.plugin.call(this, type, [event, ui]);
+ //The absolute position has to be recalculated after plugins
+ if(type === "drag") {
+ this.positionAbs = this._convertPositionTo("absolute");
+ }
+ return $.Widget.prototype._trigger.call(this, type, event, ui);
+ },
+
+ plugins: {},
+
+ _uiHash: function() {
+ return {
+ helper: this.helper,
+ position: this.position,
+ originalPosition: this.originalPosition,
+ offset: this.positionAbs
+ };
+ }
+
+});
+
+$.ui.plugin.add("draggable", "connectToSortable", {
+ start: function(event, ui) {
+
+ var inst = $(this).data("ui-draggable"), o = inst.options,
+ uiSortable = $.extend({}, ui, { item: inst.element });
+ inst.sortables = [];
+ $(o.connectToSortable).each(function() {
+ var sortable = $.data(this, "ui-sortable");
+ if (sortable && !sortable.options.disabled) {
+ inst.sortables.push({
+ instance: sortable,
+ shouldRevert: sortable.options.revert
+ });
+ sortable.refreshPositions(); // Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
+ sortable._trigger("activate", event, uiSortable);
+ }
+ });
+
+ },
+ stop: function(event, ui) {
+
+ //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
+ var inst = $(this).data("ui-draggable"),
+ uiSortable = $.extend({}, ui, { item: inst.element });
+
+ $.each(inst.sortables, function() {
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+
+ inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
+ this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
+
+ //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
+ if(this.shouldRevert) {
+ this.instance.options.revert = this.shouldRevert;
+ }
+
+ //Trigger the stop of the sortable
+ this.instance._mouseStop(event);
+
+ this.instance.options.helper = this.instance.options._helper;
+
+ //If the helper has been the original item, restore properties in the sortable
+ if(inst.options.helper === "original") {
+ this.instance.currentItem.css({ top: "auto", left: "auto" });
+ }
+
+ } else {
+ this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
+ this.instance._trigger("deactivate", event, uiSortable);
+ }
+
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("ui-draggable"), that = this;
+
+ $.each(inst.sortables, function() {
+
+ var innermostIntersecting = false,
+ thisSortable = this;
+
+ //Copy over some variables to allow calling the sortable's native _intersectsWith
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+
+ if(this.instance._intersectsWith(this.instance.containerCache)) {
+ innermostIntersecting = true;
+ $.each(inst.sortables, function () {
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+ if (this !== thisSortable &&
+ this.instance._intersectsWith(this.instance.containerCache) &&
+ $.contains(thisSortable.instance.element[0], this.instance.element[0])
+ ) {
+ innermostIntersecting = false;
+ }
+ return innermostIntersecting;
+ });
+ }
+
+
+ if(innermostIntersecting) {
+ //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
+ if(!this.instance.isOver) {
+
+ this.instance.isOver = 1;
+ //Now we fake the start of dragging for the sortable instance,
+ //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
+ //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
+ this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
+ this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
+ this.instance.options.helper = function() { return ui.helper[0]; };
+
+ event.target = this.instance.currentItem[0];
+ this.instance._mouseCapture(event, true);
+ this.instance._mouseStart(event, true, true);
+
+ //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
+ this.instance.offset.click.top = inst.offset.click.top;
+ this.instance.offset.click.left = inst.offset.click.left;
+ this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
+ this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
+
+ inst._trigger("toSortable", event);
+ inst.dropped = this.instance.element; //draggable revert needs that
+ //hack so receive/update callbacks work (mostly)
+ inst.currentItem = inst.element;
+ this.instance.fromOutside = inst;
+
+ }
+
+ //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
+ if(this.instance.currentItem) {
+ this.instance._mouseDrag(event);
+ }
+
+ } else {
+
+ //If it doesn't intersect with the sortable, and it intersected before,
+ //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+ this.instance.cancelHelperRemoval = true;
+
+ //Prevent reverting on this forced stop
+ this.instance.options.revert = false;
+
+ // The out event needs to be triggered independently
+ this.instance._trigger("out", event, this.instance._uiHash(this.instance));
+
+ this.instance._mouseStop(event, true);
+ this.instance.options.helper = this.instance.options._helper;
+
+ //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
+ this.instance.currentItem.remove();
+ if(this.instance.placeholder) {
+ this.instance.placeholder.remove();
+ }
+
+ inst._trigger("fromSortable", event);
+ inst.dropped = false; //draggable revert needs that
+ }
+
+ }
+
+ });
+
+ }
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+ start: function() {
+ var t = $("body"), o = $(this).data("ui-draggable").options;
+ if (t.css("cursor")) {
+ o._cursor = t.css("cursor");
+ }
+ t.css("cursor", o.cursor);
+ },
+ stop: function() {
+ var o = $(this).data("ui-draggable").options;
+ if (o._cursor) {
+ $("body").css("cursor", o._cursor);
+ }
+ }
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data("ui-draggable").options;
+ if(t.css("opacity")) {
+ o._opacity = t.css("opacity");
+ }
+ t.css("opacity", o.opacity);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data("ui-draggable").options;
+ if(o._opacity) {
+ $(ui.helper).css("opacity", o._opacity);
+ }
+ }
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+ start: function() {
+ var i = $(this).data("ui-draggable");
+ if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
+ i.overflowOffset = i.scrollParent.offset();
+ }
+ },
+ drag: function( event ) {
+
+ var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;
+
+ if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
+
+ if(!o.axis || o.axis !== "x") {
+ if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
+ } else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
+ }
+ }
+
+ if(!o.axis || o.axis !== "y") {
+ if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
+ } else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
+ }
+ }
+
+ } else {
+
+ if(!o.axis || o.axis !== "x") {
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+ }
+ }
+
+ if(!o.axis || o.axis !== "y") {
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+ }
+ }
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+ $.ui.ddmanager.prepareOffsets(i, event);
+ }
+
+ }
+});
+
+$.ui.plugin.add("draggable", "snap", {
+ start: function() {
+
+ var i = $(this).data("ui-draggable"),
+ o = i.options;
+
+ i.snapElements = [];
+
+ $(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
+ var $t = $(this),
+ $o = $t.offset();
+ if(this !== i.element[0]) {
+ i.snapElements.push({
+ item: this,
+ width: $t.outerWidth(), height: $t.outerHeight(),
+ top: $o.top, left: $o.left
+ });
+ }
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var ts, bs, ls, rs, l, r, t, b, i, first,
+ inst = $(this).data("ui-draggable"),
+ o = inst.options,
+ d = o.snapTolerance,
+ x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+ y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+ for (i = inst.snapElements.length - 1; i >= 0; i--){
+
+ l = inst.snapElements[i].left;
+ r = l + inst.snapElements[i].width;
+ t = inst.snapElements[i].top;
+ b = t + inst.snapElements[i].height;
+
+ //Yes, I know, this is insane ;)
+ if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
+ if(inst.snapElements[i].snapping) {
+ (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ }
+ inst.snapElements[i].snapping = false;
+ continue;
+ }
+
+ if(o.snapMode !== "inner") {
+ ts = Math.abs(t - y2) <= d;
+ bs = Math.abs(b - y1) <= d;
+ ls = Math.abs(l - x2) <= d;
+ rs = Math.abs(r - x1) <= d;
+ if(ts) {
+ ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ }
+ if(bs) {
+ ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
+ }
+ if(ls) {
+ ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
+ }
+ if(rs) {
+ ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
+ }
+ }
+
+ first = (ts || bs || ls || rs);
+
+ if(o.snapMode !== "outer") {
+ ts = Math.abs(t - y1) <= d;
+ bs = Math.abs(b - y2) <= d;
+ ls = Math.abs(l - x1) <= d;
+ rs = Math.abs(r - x2) <= d;
+ if(ts) {
+ ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
+ }
+ if(bs) {
+ ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ }
+ if(ls) {
+ ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
+ }
+ if(rs) {
+ ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
+ }
+ }
+
+ if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
+ (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ }
+ inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+ }
+
+ }
+});
+
+$.ui.plugin.add("draggable", "stack", {
+ start: function() {
+ var min,
+ o = this.data("ui-draggable").options,
+ group = $.makeArray($(o.stack)).sort(function(a,b) {
+ return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
+ });
+
+ if (!group.length) { return; }
+
+ min = parseInt($(group[0]).css("zIndex"), 10) || 0;
+ $(group).each(function(i) {
+ $(this).css("zIndex", min + i);
+ });
+ this.css("zIndex", (min + group.length));
+ }
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data("ui-draggable").options;
+ if(t.css("zIndex")) {
+ o._zIndex = t.css("zIndex");
+ }
+ t.css("zIndex", o.zIndex);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data("ui-draggable").options;
+ if(o._zIndex) {
+ $(ui.helper).css("zIndex", o._zIndex);
+ }
+ }
+});
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+function isOverAxis( x, reference, size ) {
+ return ( x > reference ) && ( x < ( reference + size ) );
+}
+
+$.widget("ui.droppable", {
+ version: "1.10.2",
+ widgetEventPrefix: "drop",
+ options: {
+ accept: "*",
+ activeClass: false,
+ addClasses: true,
+ greedy: false,
+ hoverClass: false,
+ scope: "default",
+ tolerance: "intersect",
+
+ // callbacks
+ activate: null,
+ deactivate: null,
+ drop: null,
+ out: null,
+ over: null
+ },
+ _create: function() {
+
+ var o = this.options,
+ accept = o.accept;
+
+ this.isover = false;
+ this.isout = true;
+
+ this.accept = $.isFunction(accept) ? accept : function(d) {
+ return d.is(accept);
+ };
+
+ //Store the droppable's proportions
+ this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
+
+ // Add the reference and positions to the manager
+ $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
+ $.ui.ddmanager.droppables[o.scope].push(this);
+
+ (o.addClasses && this.element.addClass("ui-droppable"));
+
+ },
+
+ _destroy: function() {
+ var i = 0,
+ drop = $.ui.ddmanager.droppables[this.options.scope];
+
+ for ( ; i < drop.length; i++ ) {
+ if ( drop[i] === this ) {
+ drop.splice(i, 1);
+ }
+ }
+
+ this.element.removeClass("ui-droppable ui-droppable-disabled");
+ },
+
+ _setOption: function(key, value) {
+
+ if(key === "accept") {
+ this.accept = $.isFunction(value) ? value : function(d) {
+ return d.is(value);
+ };
+ }
+ $.Widget.prototype._setOption.apply(this, arguments);
+ },
+
+ _activate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) {
+ this.element.addClass(this.options.activeClass);
+ }
+ if(draggable){
+ this._trigger("activate", event, this.ui(draggable));
+ }
+ },
+
+ _deactivate: function(event) {
+ var draggable = $.ui.ddmanager.current;
+ if(this.options.activeClass) {
+ this.element.removeClass(this.options.activeClass);
+ }
+ if(draggable){
+ this._trigger("deactivate", event, this.ui(draggable));
+ }
+ },
+
+ _over: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+
+ // Bail if draggable and droppable are same element
+ if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+ return;
+ }
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) {
+ this.element.addClass(this.options.hoverClass);
+ }
+ this._trigger("over", event, this.ui(draggable));
+ }
+
+ },
+
+ _out: function(event) {
+
+ var draggable = $.ui.ddmanager.current;
+
+ // Bail if draggable and droppable are same element
+ if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+ return;
+ }
+
+ if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.hoverClass) {
+ this.element.removeClass(this.options.hoverClass);
+ }
+ this._trigger("out", event, this.ui(draggable));
+ }
+
+ },
+
+ _drop: function(event,custom) {
+
+ var draggable = custom || $.ui.ddmanager.current,
+ childrenIntersection = false;
+
+ // Bail if draggable and droppable are same element
+ if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
+ return false;
+ }
+
+ this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
+ var inst = $.data(this, "ui-droppable");
+ if(
+ inst.options.greedy &&
+ !inst.options.disabled &&
+ inst.options.scope === draggable.options.scope &&
+ inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
+ $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
+ ) { childrenIntersection = true; return false; }
+ });
+ if(childrenIntersection) {
+ return false;
+ }
+
+ if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ if(this.options.activeClass) {
+ this.element.removeClass(this.options.activeClass);
+ }
+ if(this.options.hoverClass) {
+ this.element.removeClass(this.options.hoverClass);
+ }
+ this._trigger("drop", event, this.ui(draggable));
+ return this.element;
+ }
+
+ return false;
+
+ },
+
+ ui: function(c) {
+ return {
+ draggable: (c.currentItem || c.element),
+ helper: c.helper,
+ position: c.position,
+ offset: c.positionAbs
+ };
+ }
+
+});
+
+$.ui.intersect = function(draggable, droppable, toleranceMode) {
+
+ if (!droppable.offset) {
+ return false;
+ }
+
+ var draggableLeft, draggableTop,
+ x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
+ y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height,
+ l = droppable.offset.left, r = l + droppable.proportions.width,
+ t = droppable.offset.top, b = t + droppable.proportions.height;
+
+ switch (toleranceMode) {
+ case "fit":
+ return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
+ case "intersect":
+ return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
+ x2 - (draggable.helperProportions.width / 2) < r && // Left Half
+ t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
+ y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
+ case "pointer":
+ draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
+ draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
+ return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width );
+ case "touch":
+ return (
+ (y1 >= t && y1 <= b) || // Top edge touching
+ (y2 >= t && y2 <= b) || // Bottom edge touching
+ (y1 < t && y2 > b) // Surrounded vertically
+ ) && (
+ (x1 >= l && x1 <= r) || // Left edge touching
+ (x2 >= l && x2 <= r) || // Right edge touching
+ (x1 < l && x2 > r) // Surrounded horizontally
+ );
+ default:
+ return false;
+ }
+
+};
+
+/*
+ This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+ current: null,
+ droppables: { "default": [] },
+ prepareOffsets: function(t, event) {
+
+ var i, j,
+ m = $.ui.ddmanager.droppables[t.options.scope] || [],
+ type = event ? event.type : null, // workaround for #2317
+ list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
+
+ droppablesLoop: for (i = 0; i < m.length; i++) {
+
+ //No disabled and non-accepted
+ if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
+ continue;
+ }
+
+ // Filter out elements in the current dragged item
+ for (j=0; j < list.length; j++) {
+ if(list[j] === m[i].element[0]) {
+ m[i].proportions.height = 0;
+ continue droppablesLoop;
+ }
+ }
+
+ m[i].visible = m[i].element.css("display") !== "none";
+ if(!m[i].visible) {
+ continue;
+ }
+
+ //Activate the droppable if used directly from draggables
+ if(type === "mousedown") {
+ m[i]._activate.call(m[i], event);
+ }
+
+ m[i].offset = m[i].element.offset();
+ m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
+
+ }
+
+ },
+ drop: function(draggable, event) {
+
+ var dropped = false;
+ // Create a copy of the droppables in case the list changes during the drop (#9116)
+ $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {
+
+ if(!this.options) {
+ return;
+ }
+ if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
+ dropped = this._drop.call(this, event) || dropped;
+ }
+
+ if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
+ this.isout = true;
+ this.isover = false;
+ this._deactivate.call(this, event);
+ }
+
+ });
+ return dropped;
+
+ },
+ dragStart: function( draggable, event ) {
+ //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
+ draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
+ if( !draggable.options.refreshPositions ) {
+ $.ui.ddmanager.prepareOffsets( draggable, event );
+ }
+ });
+ },
+ drag: function(draggable, event) {
+
+ //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
+ if(draggable.options.refreshPositions) {
+ $.ui.ddmanager.prepareOffsets(draggable, event);
+ }
+
+ //Run through all droppables and check their positions based on specific tolerance options
+ $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
+
+ if(this.options.disabled || this.greedyChild || !this.visible) {
+ return;
+ }
+
+ var parentInstance, scope, parent,
+ intersects = $.ui.intersect(draggable, this, this.options.tolerance),
+ c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
+ if(!c) {
+ return;
+ }
+
+ if (this.options.greedy) {
+ // find droppable parents with same scope
+ scope = this.options.scope;
+ parent = this.element.parents(":data(ui-droppable)").filter(function () {
+ return $.data(this, "ui-droppable").options.scope === scope;
+ });
+
+ if (parent.length) {
+ parentInstance = $.data(parent[0], "ui-droppable");
+ parentInstance.greedyChild = (c === "isover");
+ }
+ }
+
+ // we just moved into a greedy child
+ if (parentInstance && c === "isover") {
+ parentInstance.isover = false;
+ parentInstance.isout = true;
+ parentInstance._out.call(parentInstance, event);
+ }
+
+ this[c] = true;
+ this[c === "isout" ? "isover" : "isout"] = false;
+ this[c === "isover" ? "_over" : "_out"].call(this, event);
+
+ // we just moved out of a greedy child
+ if (parentInstance && c === "isout") {
+ parentInstance.isout = false;
+ parentInstance.isover = true;
+ parentInstance._over.call(parentInstance, event);
+ }
+ });
+
+ },
+ dragStop: function( draggable, event ) {
+ draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
+ //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
+ if( !draggable.options.refreshPositions ) {
+ $.ui.ddmanager.prepareOffsets( draggable, event );
+ }
+ }
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+function num(v) {
+ return parseInt(v, 10) || 0;
+}
+
+function isNumber(value) {
+ return !isNaN(parseInt(value, 10));
+}
+
+$.widget("ui.resizable", $.ui.mouse, {
+ version: "1.10.2",
+ widgetEventPrefix: "resize",
+ options: {
+ alsoResize: false,
+ animate: false,
+ animateDuration: "slow",
+ animateEasing: "swing",
+ aspectRatio: false,
+ autoHide: false,
+ containment: false,
+ ghost: false,
+ grid: false,
+ handles: "e,s,se",
+ helper: false,
+ maxHeight: null,
+ maxWidth: null,
+ minHeight: 10,
+ minWidth: 10,
+ // See #7960
+ zIndex: 90,
+
+ // callbacks
+ resize: null,
+ start: null,
+ stop: null
+ },
+ _create: function() {
+
+ var n, i, handle, axis, hname,
+ that = this,
+ o = this.options;
+ this.element.addClass("ui-resizable");
+
+ $.extend(this, {
+ _aspectRatio: !!(o.aspectRatio),
+ aspectRatio: o.aspectRatio,
+ originalElement: this.element,
+ _proportionallyResizeElements: [],
+ _helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
+ });
+
+ //Wrap the element if it cannot hold child nodes
+ if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
+
+ //Create a wrapper element and set the wrapper to the new current internal element
+ this.element.wrap(
+ $("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
+ position: this.element.css("position"),
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight(),
+ top: this.element.css("top"),
+ left: this.element.css("left")
+ })
+ );
+
+ //Overwrite the original this.element
+ this.element = this.element.parent().data(
+ "ui-resizable", this.element.data("ui-resizable")
+ );
+
+ this.elementIsWrapper = true;
+
+ //Move margins to the wrapper
+ this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
+ this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
+
+ //Prevent Safari textarea resize
+ this.originalResizeStyle = this.originalElement.css("resize");
+ this.originalElement.css("resize", "none");
+
+ //Push the actual element to our proportionallyResize internal array
+ this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" }));
+
+ // avoid IE jump (hard set the margin)
+ this.originalElement.css({ margin: this.originalElement.css("margin") });
+
+ // fix handlers offset
+ this._proportionallyResize();
+
+ }
+
+ this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" });
+ if(this.handles.constructor === String) {
+
+ if ( this.handles === "all") {
+ this.handles = "n,e,s,w,se,sw,ne,nw";
+ }
+
+ n = this.handles.split(",");
+ this.handles = {};
+
+ for(i = 0; i < n.length; i++) {
+
+ handle = $.trim(n[i]);
+ hname = "ui-resizable-"+handle;
+ axis = $("<div class='ui-resizable-handle " + hname + "'></div>");
+
+ // Apply zIndex to all handles - see #7960
+ axis.css({ zIndex: o.zIndex });
+
+ //TODO : What's going on here?
+ if ("se" === handle) {
+ axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
+ }
+
+ //Insert into internal handles object and append to element
+ this.handles[handle] = ".ui-resizable-"+handle;
+ this.element.append(axis);
+ }
+
+ }
+
+ this._renderAxis = function(target) {
+
+ var i, axis, padPos, padWrapper;
+
+ target = target || this.element;
+
+ for(i in this.handles) {
+
+ if(this.handles[i].constructor === String) {
+ this.handles[i] = $(this.handles[i], this.element).show();
+ }
+
+ //Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
+ if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
+
+ axis = $(this.handles[i], this.element);
+
+ //Checking the correct pad and border
+ padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
+
+ //The padding type i have to apply...
+ padPos = [ "padding",
+ /ne|nw|n/.test(i) ? "Top" :
+ /se|sw|s/.test(i) ? "Bottom" :
+ /^e$/.test(i) ? "Right" : "Left" ].join("");
+
+ target.css(padPos, padWrapper);
+
+ this._proportionallyResize();
+
+ }
+
+ //TODO: What's that good for? There's not anything to be executed left
+ if(!$(this.handles[i]).length) {
+ continue;
+ }
+ }
+ };
+
+ //TODO: make renderAxis a prototype function
+ this._renderAxis(this.element);
+
+ this._handles = $(".ui-resizable-handle", this.element)
+ .disableSelection();
+
+ //Matching axis name
+ this._handles.mouseover(function() {
+ if (!that.resizing) {
+ if (this.className) {
+ axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
+ }
+ //Axis, default = se
+ that.axis = axis && axis[1] ? axis[1] : "se";
+ }
+ });
+
+ //If we want to auto hide the elements
+ if (o.autoHide) {
+ this._handles.hide();
+ $(this.element)
+ .addClass("ui-resizable-autohide")
+ .mouseenter(function() {
+ if (o.disabled) {
+ return;
+ }
+ $(this).removeClass("ui-resizable-autohide");
+ that._handles.show();
+ })
+ .mouseleave(function(){
+ if (o.disabled) {
+ return;
+ }
+ if (!that.resizing) {
+ $(this).addClass("ui-resizable-autohide");
+ that._handles.hide();
+ }
+ });
+ }
+
+ //Initialize the mouse interaction
+ this._mouseInit();
+
+ },
+
+ _destroy: function() {
+
+ this._mouseDestroy();
+
+ var wrapper,
+ _destroy = function(exp) {
+ $(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
+ .removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove();
+ };
+
+ //TODO: Unwrap at same DOM position
+ if (this.elementIsWrapper) {
+ _destroy(this.element);
+ wrapper = this.element;
+ this.originalElement.css({
+ position: wrapper.css("position"),
+ width: wrapper.outerWidth(),
+ height: wrapper.outerHeight(),
+ top: wrapper.css("top"),
+ left: wrapper.css("left")
+ }).insertAfter( wrapper );
+ wrapper.remove();
+ }
+
+ this.originalElement.css("resize", this.originalResizeStyle);
+ _destroy(this.originalElement);
+
+ return this;
+ },
+
+ _mouseCapture: function(event) {
+ var i, handle,
+ capture = false;
+
+ for (i in this.handles) {
+ handle = $(this.handles[i])[0];
+ if (handle === event.target || $.contains(handle, event.target)) {
+ capture = true;
+ }
+ }
+
+ return !this.options.disabled && capture;
+ },
+
+ _mouseStart: function(event) {
+
+ var curleft, curtop, cursor,
+ o = this.options,
+ iniPos = this.element.position(),
+ el = this.element;
+
+ this.resizing = true;
+
+ // bugfix for http://dev.jquery.com/ticket/1749
+ if ( (/absolute/).test( el.css("position") ) ) {
+ el.css({ position: "absolute", top: el.css("top"), left: el.css("left") });
+ } else if (el.is(".ui-draggable")) {
+ el.css({ position: "absolute", top: iniPos.top, left: iniPos.left });
+ }
+
+ this._renderProxy();
+
+ curleft = num(this.helper.css("left"));
+ curtop = num(this.helper.css("top"));
+
+ if (o.containment) {
+ curleft += $(o.containment).scrollLeft() || 0;
+ curtop += $(o.containment).scrollTop() || 0;
+ }
+
+ //Store needed variables
+ this.offset = this.helper.offset();
+ this.position = { left: curleft, top: curtop };
+ this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
+ this.originalPosition = { left: curleft, top: curtop };
+ this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
+ this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+ //Aspect Ratio
+ this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
+
+ cursor = $(".ui-resizable-" + this.axis).css("cursor");
+ $("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);
+
+ el.addClass("ui-resizable-resizing");
+ this._propagate("start", event);
+ return true;
+ },
+
+ _mouseDrag: function(event) {
+
+ //Increase performance, avoid regex
+ var data,
+ el = this.helper, props = {},
+ smp = this.originalMousePosition,
+ a = this.axis,
+ prevTop = this.position.top,
+ prevLeft = this.position.left,
+ prevWidth = this.size.width,
+ prevHeight = this.size.height,
+ dx = (event.pageX-smp.left)||0,
+ dy = (event.pageY-smp.top)||0,
+ trigger = this._change[a];
+
+ if (!trigger) {
+ return false;
+ }
+
+ // Calculate the attrs that will be change
+ data = trigger.apply(this, [event, dx, dy]);
+
+ // Put this in the mouseDrag handler since the user can start pressing shift while resizing
+ this._updateVirtualBoundaries(event.shiftKey);
+ if (this._aspectRatio || event.shiftKey) {
+ data = this._updateRatio(data, event);
+ }
+
+ data = this._respectSize(data, event);
+
+ this._updateCache(data);
+
+ // plugins callbacks need to be called first
+ this._propagate("resize", event);
+
+ if (this.position.top !== prevTop) {
+ props.top = this.position.top + "px";
+ }
+ if (this.position.left !== prevLeft) {
+ props.left = this.position.left + "px";
+ }
+ if (this.size.width !== prevWidth) {
+ props.width = this.size.width + "px";
+ }
+ if (this.size.height !== prevHeight) {
+ props.height = this.size.height + "px";
+ }
+ el.css(props);
+
+ if (!this._helper && this._proportionallyResizeElements.length) {
+ this._proportionallyResize();
+ }
+
+ // Call the user callback if the element was resized
+ if ( ! $.isEmptyObject(props) ) {
+ this._trigger("resize", event, this.ui());
+ }
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ this.resizing = false;
+ var pr, ista, soffseth, soffsetw, s, left, top,
+ o = this.options, that = this;
+
+ if(this._helper) {
+
+ pr = this._proportionallyResizeElements;
+ ista = pr.length && (/textarea/i).test(pr[0].nodeName);
+ soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height;
+ soffsetw = ista ? 0 : that.sizeDiff.width;
+
+ s = { width: (that.helper.width() - soffsetw), height: (that.helper.height() - soffseth) };
+ left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null;
+ top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
+
+ if (!o.animate) {
+ this.element.css($.extend(s, { top: top, left: left }));
+ }
+
+ that.helper.height(that.size.height);
+ that.helper.width(that.size.width);
+
+ if (this._helper && !o.animate) {
+ this._proportionallyResize();
+ }
+ }
+
+ $("body").css("cursor", "auto");
+
+ this.element.removeClass("ui-resizable-resizing");
+
+ this._propagate("stop", event);
+
+ if (this._helper) {
+ this.helper.remove();
+ }
+
+ return false;
+
+ },
+
+ _updateVirtualBoundaries: function(forceAspectRatio) {
+ var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
+ o = this.options;
+
+ b = {
+ minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
+ maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
+ minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
+ maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
+ };
+
+ if(this._aspectRatio || forceAspectRatio) {
+ // We want to create an enclosing box whose aspect ration is the requested one
+ // First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
+ pMinWidth = b.minHeight * this.aspectRatio;
+ pMinHeight = b.minWidth / this.aspectRatio;
+ pMaxWidth = b.maxHeight * this.aspectRatio;
+ pMaxHeight = b.maxWidth / this.aspectRatio;
+
+ if(pMinWidth > b.minWidth) {
+ b.minWidth = pMinWidth;
+ }
+ if(pMinHeight > b.minHeight) {
+ b.minHeight = pMinHeight;
+ }
+ if(pMaxWidth < b.maxWidth) {
+ b.maxWidth = pMaxWidth;
+ }
+ if(pMaxHeight < b.maxHeight) {
+ b.maxHeight = pMaxHeight;
+ }
+ }
+ this._vBoundaries = b;
+ },
+
+ _updateCache: function(data) {
+ this.offset = this.helper.offset();
+ if (isNumber(data.left)) {
+ this.position.left = data.left;
+ }
+ if (isNumber(data.top)) {
+ this.position.top = data.top;
+ }
+ if (isNumber(data.height)) {
+ this.size.height = data.height;
+ }
+ if (isNumber(data.width)) {
+ this.size.width = data.width;
+ }
+ },
+
+ _updateRatio: function( data ) {
+
+ var cpos = this.position,
+ csize = this.size,
+ a = this.axis;
+
+ if (isNumber(data.height)) {
+ data.width = (data.height * this.aspectRatio);
+ } else if (isNumber(data.width)) {
+ data.height = (data.width / this.aspectRatio);
+ }
+
+ if (a === "sw") {
+ data.left = cpos.left + (csize.width - data.width);
+ data.top = null;
+ }
+ if (a === "nw") {
+ data.top = cpos.top + (csize.height - data.height);
+ data.left = cpos.left + (csize.width - data.width);
+ }
+
+ return data;
+ },
+
+ _respectSize: function( data ) {
+
+ var o = this._vBoundaries,
+ a = this.axis,
+ ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
+ isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
+ dw = this.originalPosition.left + this.originalSize.width,
+ dh = this.position.top + this.size.height,
+ cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
+ if (isminw) {
+ data.width = o.minWidth;
+ }
+ if (isminh) {
+ data.height = o.minHeight;
+ }
+ if (ismaxw) {
+ data.width = o.maxWidth;
+ }
+ if (ismaxh) {
+ data.height = o.maxHeight;
+ }
+
+ if (isminw && cw) {
+ data.left = dw - o.minWidth;
+ }
+ if (ismaxw && cw) {
+ data.left = dw - o.maxWidth;
+ }
+ if (isminh && ch) {
+ data.top = dh - o.minHeight;
+ }
+ if (ismaxh && ch) {
+ data.top = dh - o.maxHeight;
+ }
+
+ // fixing jump error on top/left - bug #2330
+ if (!data.width && !data.height && !data.left && data.top) {
+ data.top = null;
+ } else if (!data.width && !data.height && !data.top && data.left) {
+ data.left = null;
+ }
+
+ return data;
+ },
+
+ _proportionallyResize: function() {
+
+ if (!this._proportionallyResizeElements.length) {
+ return;
+ }
+
+ var i, j, borders, paddings, prel,
+ element = this.helper || this.element;
+
+ for ( i=0; i < this._proportionallyResizeElements.length; i++) {
+
+ prel = this._proportionallyResizeElements[i];
+
+ if (!this.borderDif) {
+ this.borderDif = [];
+ borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")];
+ paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")];
+
+ for ( j = 0; j < borders.length; j++ ) {
+ this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 );
+ }
+ }
+
+ prel.css({
+ height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
+ width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
+ });
+
+ }
+
+ },
+
+ _renderProxy: function() {
+
+ var el = this.element, o = this.options;
+ this.elementOffset = el.offset();
+
+ if(this._helper) {
+
+ this.helper = this.helper || $("<div style='overflow:hidden;'></div>");
+
+ this.helper.addClass(this._helper).css({
+ width: this.element.outerWidth() - 1,
+ height: this.element.outerHeight() - 1,
+ position: "absolute",
+ left: this.elementOffset.left +"px",
+ top: this.elementOffset.top +"px",
+ zIndex: ++o.zIndex //TODO: Don't modify option
+ });
+
+ this.helper
+ .appendTo("body")
+ .disableSelection();
+
+ } else {
+ this.helper = this.element;
+ }
+
+ },
+
+ _change: {
+ e: function(event, dx) {
+ return { width: this.originalSize.width + dx };
+ },
+ w: function(event, dx) {
+ var cs = this.originalSize, sp = this.originalPosition;
+ return { left: sp.left + dx, width: cs.width - dx };
+ },
+ n: function(event, dx, dy) {
+ var cs = this.originalSize, sp = this.originalPosition;
+ return { top: sp.top + dy, height: cs.height - dy };
+ },
+ s: function(event, dx, dy) {
+ return { height: this.originalSize.height + dy };
+ },
+ se: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ sw: function(event, dx, dy) {
+ return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ },
+ ne: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
+ },
+ nw: function(event, dx, dy) {
+ return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
+ }
+ },
+
+ _propagate: function(n, event) {
+ $.ui.plugin.call(this, n, [event, this.ui()]);
+ (n !== "resize" && this._trigger(n, event, this.ui()));
+ },
+
+ plugins: {},
+
+ ui: function() {
+ return {
+ originalElement: this.originalElement,
+ element: this.element,
+ helper: this.helper,
+ position: this.position,
+ size: this.size,
+ originalSize: this.originalSize,
+ originalPosition: this.originalPosition
+ };
+ }
+
+});
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add("resizable", "animate", {
+
+ stop: function( event ) {
+ var that = $(this).data("ui-resizable"),
+ o = that.options,
+ pr = that._proportionallyResizeElements,
+ ista = pr.length && (/textarea/i).test(pr[0].nodeName),
+ soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height,
+ soffsetw = ista ? 0 : that.sizeDiff.width,
+ style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
+ left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null,
+ top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
+
+ that.element.animate(
+ $.extend(style, top && left ? { top: top, left: left } : {}), {
+ duration: o.animateDuration,
+ easing: o.animateEasing,
+ step: function() {
+
+ var data = {
+ width: parseInt(that.element.css("width"), 10),
+ height: parseInt(that.element.css("height"), 10),
+ top: parseInt(that.element.css("top"), 10),
+ left: parseInt(that.element.css("left"), 10)
+ };
+
+ if (pr && pr.length) {
+ $(pr[0]).css({ width: data.width, height: data.height });
+ }
+
+ // propagating resize, and updating values for each animation step
+ that._updateCache(data);
+ that._propagate("resize", event);
+
+ }
+ }
+ );
+ }
+
+});
+
+$.ui.plugin.add("resizable", "containment", {
+
+ start: function() {
+ var element, p, co, ch, cw, width, height,
+ that = $(this).data("ui-resizable"),
+ o = that.options,
+ el = that.element,
+ oc = o.containment,
+ ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
+
+ if (!ce) {
+ return;
+ }
+
+ that.containerElement = $(ce);
+
+ if (/document/.test(oc) || oc === document) {
+ that.containerOffset = { left: 0, top: 0 };
+ that.containerPosition = { left: 0, top: 0 };
+
+ that.parentData = {
+ element: $(document), left: 0, top: 0,
+ width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
+ };
+ }
+
+ // i'm a node, so compute top, left, right, bottom
+ else {
+ element = $(ce);
+ p = [];
+ $([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
+
+ that.containerOffset = element.offset();
+ that.containerPosition = element.position();
+ that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
+
+ co = that.containerOffset;
+ ch = that.containerSize.height;
+ cw = that.containerSize.width;
+ width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw );
+ height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
+
+ that.parentData = {
+ element: ce, left: co.left, top: co.top, width: width, height: height
+ };
+ }
+ },
+
+ resize: function( event ) {
+ var woset, hoset, isParent, isOffsetRelative,
+ that = $(this).data("ui-resizable"),
+ o = that.options,
+ co = that.containerOffset, cp = that.position,
+ pRatio = that._aspectRatio || event.shiftKey,
+ cop = { top:0, left:0 }, ce = that.containerElement;
+
+ if (ce[0] !== document && (/static/).test(ce.css("position"))) {
+ cop = co;
+ }
+
+ if (cp.left < (that._helper ? co.left : 0)) {
+ that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
+ if (pRatio) {
+ that.size.height = that.size.width / that.aspectRatio;
+ }
+ that.position.left = o.helper ? co.left : 0;
+ }
+
+ if (cp.top < (that._helper ? co.top : 0)) {
+ that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
+ if (pRatio) {
+ that.size.width = that.size.height * that.aspectRatio;
+ }
+ that.position.top = that._helper ? co.top : 0;
+ }
+
+ that.offset.left = that.parentData.left+that.position.left;
+ that.offset.top = that.parentData.top+that.position.top;
+
+ woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width );
+ hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );
+
+ isParent = that.containerElement.get(0) === that.element.parent().get(0);
+ isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position"));
+
+ if(isParent && isOffsetRelative) {
+ woset -= that.parentData.left;
+ }
+
+ if (woset + that.size.width >= that.parentData.width) {
+ that.size.width = that.parentData.width - woset;
+ if (pRatio) {
+ that.size.height = that.size.width / that.aspectRatio;
+ }
+ }
+
+ if (hoset + that.size.height >= that.parentData.height) {
+ that.size.height = that.parentData.height - hoset;
+ if (pRatio) {
+ that.size.width = that.size.height * that.aspectRatio;
+ }
+ }
+ },
+
+ stop: function(){
+ var that = $(this).data("ui-resizable"),
+ o = that.options,
+ co = that.containerOffset,
+ cop = that.containerPosition,
+ ce = that.containerElement,
+ helper = $(that.helper),
+ ho = helper.offset(),
+ w = helper.outerWidth() - that.sizeDiff.width,
+ h = helper.outerHeight() - that.sizeDiff.height;
+
+ if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) {
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+ }
+
+ if (that._helper && !o.animate && (/static/).test(ce.css("position"))) {
+ $(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
+ }
+
+ }
+});
+
+$.ui.plugin.add("resizable", "alsoResize", {
+
+ start: function () {
+ var that = $(this).data("ui-resizable"),
+ o = that.options,
+ _store = function (exp) {
+ $(exp).each(function() {
+ var el = $(this);
+ el.data("ui-resizable-alsoresize", {
+ width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
+ left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
+ });
+ });
+ };
+
+ if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
+ if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
+ else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
+ }else{
+ _store(o.alsoResize);
+ }
+ },
+
+ resize: function (event, ui) {
+ var that = $(this).data("ui-resizable"),
+ o = that.options,
+ os = that.originalSize,
+ op = that.originalPosition,
+ delta = {
+ height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
+ top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
+ },
+
+ _alsoResize = function (exp, c) {
+ $(exp).each(function() {
+ var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
+ css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"];
+
+ $.each(css, function (i, prop) {
+ var sum = (start[prop]||0) + (delta[prop]||0);
+ if (sum && sum >= 0) {
+ style[prop] = sum || null;
+ }
+ });
+
+ el.css(style);
+ });
+ };
+
+ if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
+ $.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
+ }else{
+ _alsoResize(o.alsoResize);
+ }
+ },
+
+ stop: function () {
+ $(this).removeData("resizable-alsoresize");
+ }
+});
+
+$.ui.plugin.add("resizable", "ghost", {
+
+ start: function() {
+
+ var that = $(this).data("ui-resizable"), o = that.options, cs = that.size;
+
+ that.ghost = that.originalElement.clone();
+ that.ghost
+ .css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
+ .addClass("ui-resizable-ghost")
+ .addClass(typeof o.ghost === "string" ? o.ghost : "");
+
+ that.ghost.appendTo(that.helper);
+
+ },
+
+ resize: function(){
+ var that = $(this).data("ui-resizable");
+ if (that.ghost) {
+ that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width });
+ }
+ },
+
+ stop: function() {
+ var that = $(this).data("ui-resizable");
+ if (that.ghost && that.helper) {
+ that.helper.get(0).removeChild(that.ghost.get(0));
+ }
+ }
+
+});
+
+$.ui.plugin.add("resizable", "grid", {
+
+ resize: function() {
+ var that = $(this).data("ui-resizable"),
+ o = that.options,
+ cs = that.size,
+ os = that.originalSize,
+ op = that.originalPosition,
+ a = that.axis,
+ grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid,
+ gridX = (grid[0]||1),
+ gridY = (grid[1]||1),
+ ox = Math.round((cs.width - os.width) / gridX) * gridX,
+ oy = Math.round((cs.height - os.height) / gridY) * gridY,
+ newWidth = os.width + ox,
+ newHeight = os.height + oy,
+ isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
+ isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
+ isMinWidth = o.minWidth && (o.minWidth > newWidth),
+ isMinHeight = o.minHeight && (o.minHeight > newHeight);
+
+ o.grid = grid;
+
+ if (isMinWidth) {
+ newWidth = newWidth + gridX;
+ }
+ if (isMinHeight) {
+ newHeight = newHeight + gridY;
+ }
+ if (isMaxWidth) {
+ newWidth = newWidth - gridX;
+ }
+ if (isMaxHeight) {
+ newHeight = newHeight - gridY;
+ }
+
+ if (/^(se|s|e)$/.test(a)) {
+ that.size.width = newWidth;
+ that.size.height = newHeight;
+ } else if (/^(ne)$/.test(a)) {
+ that.size.width = newWidth;
+ that.size.height = newHeight;
+ that.position.top = op.top - oy;
+ } else if (/^(sw)$/.test(a)) {
+ that.size.width = newWidth;
+ that.size.height = newHeight;
+ that.position.left = op.left - ox;
+ } else {
+ that.size.width = newWidth;
+ that.size.height = newHeight;
+ that.position.top = op.top - oy;
+ that.position.left = op.left - ox;
+ }
+ }
+
+});
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.widget("ui.selectable", $.ui.mouse, {
+ version: "1.10.2",
+ options: {
+ appendTo: "body",
+ autoRefresh: true,
+ distance: 0,
+ filter: "*",
+ tolerance: "touch",
+
+ // callbacks
+ selected: null,
+ selecting: null,
+ start: null,
+ stop: null,
+ unselected: null,
+ unselecting: null
+ },
+ _create: function() {
+ var selectees,
+ that = this;
+
+ this.element.addClass("ui-selectable");
+
+ this.dragged = false;
+
+ // cache selectee children based on filter
+ this.refresh = function() {
+ selectees = $(that.options.filter, that.element[0]);
+ selectees.addClass("ui-selectee");
+ selectees.each(function() {
+ var $this = $(this),
+ pos = $this.offset();
+ $.data(this, "selectable-item", {
+ element: this,
+ $element: $this,
+ left: pos.left,
+ top: pos.top,
+ right: pos.left + $this.outerWidth(),
+ bottom: pos.top + $this.outerHeight(),
+ startselected: false,
+ selected: $this.hasClass("ui-selected"),
+ selecting: $this.hasClass("ui-selecting"),
+ unselecting: $this.hasClass("ui-unselecting")
+ });
+ });
+ };
+ this.refresh();
+
+ this.selectees = selectees.addClass("ui-selectee");
+
+ this._mouseInit();
+
+ this.helper = $("<div class='ui-selectable-helper'></div>");
+ },
+
+ _destroy: function() {
+ this.selectees
+ .removeClass("ui-selectee")
+ .removeData("selectable-item");
+ this.element
+ .removeClass("ui-selectable ui-selectable-disabled");
+ this._mouseDestroy();
+ },
+
+ _mouseStart: function(event) {
+ var that = this,
+ options = this.options;
+
+ this.opos = [event.pageX, event.pageY];
+
+ if (this.options.disabled) {
+ return;
+ }
+
+ this.selectees = $(options.filter, this.element[0]);
+
+ this._trigger("start", event);
+
+ $(options.appendTo).append(this.helper);
+ // position helper (lasso)
+ this.helper.css({
+ "left": event.pageX,
+ "top": event.pageY,
+ "width": 0,
+ "height": 0
+ });
+
+ if (options.autoRefresh) {
+ this.refresh();
+ }
+
+ this.selectees.filter(".ui-selected").each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.startselected = true;
+ if (!event.metaKey && !event.ctrlKey) {
+ selectee.$element.removeClass("ui-selected");
+ selectee.selected = false;
+ selectee.$element.addClass("ui-unselecting");
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ });
+
+ $(event.target).parents().addBack().each(function() {
+ var doSelect,
+ selectee = $.data(this, "selectable-item");
+ if (selectee) {
+ doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected");
+ selectee.$element
+ .removeClass(doSelect ? "ui-unselecting" : "ui-selected")
+ .addClass(doSelect ? "ui-selecting" : "ui-unselecting");
+ selectee.unselecting = !doSelect;
+ selectee.selecting = doSelect;
+ selectee.selected = doSelect;
+ // selectable (UN)SELECTING callback
+ if (doSelect) {
+ that._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ } else {
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ return false;
+ }
+ });
+
+ },
+
+ _mouseDrag: function(event) {
+
+ this.dragged = true;
+
+ if (this.options.disabled) {
+ return;
+ }
+
+ var tmp,
+ that = this,
+ options = this.options,
+ x1 = this.opos[0],
+ y1 = this.opos[1],
+ x2 = event.pageX,
+ y2 = event.pageY;
+
+ if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; }
+ if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; }
+ this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
+
+ this.selectees.each(function() {
+ var selectee = $.data(this, "selectable-item"),
+ hit = false;
+
+ //prevent helper from being selected if appendTo: selectable
+ if (!selectee || selectee.element === that.element[0]) {
+ return;
+ }
+
+ if (options.tolerance === "touch") {
+ hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
+ } else if (options.tolerance === "fit") {
+ hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
+ }
+
+ if (hit) {
+ // SELECT
+ if (selectee.selected) {
+ selectee.$element.removeClass("ui-selected");
+ selectee.selected = false;
+ }
+ if (selectee.unselecting) {
+ selectee.$element.removeClass("ui-unselecting");
+ selectee.unselecting = false;
+ }
+ if (!selectee.selecting) {
+ selectee.$element.addClass("ui-selecting");
+ selectee.selecting = true;
+ // selectable SELECTING callback
+ that._trigger("selecting", event, {
+ selecting: selectee.element
+ });
+ }
+ } else {
+ // UNSELECT
+ if (selectee.selecting) {
+ if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
+ selectee.$element.removeClass("ui-selecting");
+ selectee.selecting = false;
+ selectee.$element.addClass("ui-selected");
+ selectee.selected = true;
+ } else {
+ selectee.$element.removeClass("ui-selecting");
+ selectee.selecting = false;
+ if (selectee.startselected) {
+ selectee.$element.addClass("ui-unselecting");
+ selectee.unselecting = true;
+ }
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ if (selectee.selected) {
+ if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
+ selectee.$element.removeClass("ui-selected");
+ selectee.selected = false;
+
+ selectee.$element.addClass("ui-unselecting");
+ selectee.unselecting = true;
+ // selectable UNSELECTING callback
+ that._trigger("unselecting", event, {
+ unselecting: selectee.element
+ });
+ }
+ }
+ }
+ });
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+ var that = this;
+
+ this.dragged = false;
+
+ $(".ui-unselecting", this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass("ui-unselecting");
+ selectee.unselecting = false;
+ selectee.startselected = false;
+ that._trigger("unselected", event, {
+ unselected: selectee.element
+ });
+ });
+ $(".ui-selecting", this.element[0]).each(function() {
+ var selectee = $.data(this, "selectable-item");
+ selectee.$element.removeClass("ui-selecting").addClass("ui-selected");
+ selectee.selecting = false;
+ selectee.selected = true;
+ selectee.startselected = true;
+ that._trigger("selected", event, {
+ selected: selectee.element
+ });
+ });
+ this._trigger("stop", event);
+
+ this.helper.remove();
+
+ return false;
+ }
+
+});
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+/*jshint loopfunc: true */
+
+function isOverAxis( x, reference, size ) {
+ return ( x > reference ) && ( x < ( reference + size ) );
+}
+
+function isFloating(item) {
+ return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
+}
+
+$.widget("ui.sortable", $.ui.mouse, {
+ version: "1.10.2",
+ widgetEventPrefix: "sort",
+ ready: false,
+ options: {
+ appendTo: "parent",
+ axis: false,
+ connectWith: false,
+ containment: false,
+ cursor: "auto",
+ cursorAt: false,
+ dropOnEmpty: true,
+ forcePlaceholderSize: false,
+ forceHelperSize: false,
+ grid: false,
+ handle: false,
+ helper: "original",
+ items: "> *",
+ opacity: false,
+ placeholder: false,
+ revert: false,
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ scope: "default",
+ tolerance: "intersect",
+ zIndex: 1000,
+
+ // callbacks
+ activate: null,
+ beforeStop: null,
+ change: null,
+ deactivate: null,
+ out: null,
+ over: null,
+ receive: null,
+ remove: null,
+ sort: null,
+ start: null,
+ stop: null,
+ update: null
+ },
+ _create: function() {
+
+ var o = this.options;
+ this.containerCache = {};
+ this.element.addClass("ui-sortable");
+
+ //Get the items
+ this.refresh();
+
+ //Let's determine if the items are being displayed horizontally
+ this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;
+
+ //Let's determine the parent's offset
+ this.offset = this.element.offset();
+
+ //Initialize mouse events for interaction
+ this._mouseInit();
+
+ //We're ready to go
+ this.ready = true;
+
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass("ui-sortable ui-sortable-disabled");
+ this._mouseDestroy();
+
+ for ( var i = this.items.length - 1; i >= 0; i-- ) {
+ this.items[i].item.removeData(this.widgetName + "-item");
+ }
+
+ return this;
+ },
+
+ _setOption: function(key, value){
+ if ( key === "disabled" ) {
+ this.options[ key ] = value;
+
+ this.widget().toggleClass( "ui-sortable-disabled", !!value );
+ } else {
+ // Don't call widget base _setOption for disable as it adds ui-state-disabled class
+ $.Widget.prototype._setOption.apply(this, arguments);
+ }
+ },
+
+ _mouseCapture: function(event, overrideHandle) {
+ var currentItem = null,
+ validHandle = false,
+ that = this;
+
+ if (this.reverting) {
+ return false;
+ }
+
+ if(this.options.disabled || this.options.type === "static") {
+ return false;
+ }
+
+ //We have to refresh the items data once first
+ this._refreshItems(event);
+
+ //Find out if the clicked node (or one of its parents) is a actual item in this.items
+ $(event.target).parents().each(function() {
+ if($.data(this, that.widgetName + "-item") === that) {
+ currentItem = $(this);
+ return false;
+ }
+ });
+ if($.data(event.target, that.widgetName + "-item") === that) {
+ currentItem = $(event.target);
+ }
+
+ if(!currentItem) {
+ return false;
+ }
+ if(this.options.handle && !overrideHandle) {
+ $(this.options.handle, currentItem).find("*").addBack().each(function() {
+ if(this === event.target) {
+ validHandle = true;
+ }
+ });
+ if(!validHandle) {
+ return false;
+ }
+ }
+
+ this.currentItem = currentItem;
+ this._removeCurrentsFromItems();
+ return true;
+
+ },
+
+ _mouseStart: function(event, overrideHandle, noActivation) {
+
+ var i, body,
+ o = this.options;
+
+ this.currentContainer = this;
+
+ //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
+ this.refreshPositions();
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Get the next scrolling parent
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.currentItem.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ // Only after we got the offset, we can change the helper's position to absolute
+ // TODO: Still need to figure out a way to make relative sorting possible
+ this.helper.css("position", "absolute");
+ this.cssPosition = this.helper.css("position");
+
+ //Generate the original position
+ this.originalPosition = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+ (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
+
+ //Cache the former DOM position
+ this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
+
+ //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
+ if(this.helper[0] !== this.currentItem[0]) {
+ this.currentItem.hide();
+ }
+
+ //Create the placeholder
+ this._createPlaceholder();
+
+ //Set a containment if given in the options
+ if(o.containment) {
+ this._setContainment();
+ }
+
+ if( o.cursor && o.cursor !== "auto" ) { // cursor option
+ body = this.document.find( "body" );
+
+ // support: IE
+ this.storedCursor = body.css( "cursor" );
+ body.css( "cursor", o.cursor );
+
+ this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
+ }
+
+ if(o.opacity) { // opacity option
+ if (this.helper.css("opacity")) {
+ this._storedOpacity = this.helper.css("opacity");
+ }
+ this.helper.css("opacity", o.opacity);
+ }
+
+ if(o.zIndex) { // zIndex option
+ if (this.helper.css("zIndex")) {
+ this._storedZIndex = this.helper.css("zIndex");
+ }
+ this.helper.css("zIndex", o.zIndex);
+ }
+
+ //Prepare scrolling
+ if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
+ this.overflowOffset = this.scrollParent.offset();
+ }
+
+ //Call callbacks
+ this._trigger("start", event, this._uiHash());
+
+ //Recache the helper size
+ if(!this._preserveHelperProportions) {
+ this._cacheHelperProportions();
+ }
+
+
+ //Post "activate" events to possible containers
+ if( !noActivation ) {
+ for ( i = this.containers.length - 1; i >= 0; i-- ) {
+ this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
+ }
+ }
+
+ //Prepare possible droppables
+ if($.ui.ddmanager) {
+ $.ui.ddmanager.current = this;
+ }
+
+ if ($.ui.ddmanager && !o.dropBehaviour) {
+ $.ui.ddmanager.prepareOffsets(this, event);
+ }
+
+ this.dragging = true;
+
+ this.helper.addClass("ui-sortable-helper");
+ this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+ return true;
+
+ },
+
+ _mouseDrag: function(event) {
+ var i, item, itemElement, intersection,
+ o = this.options,
+ scrolled = false;
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ if (!this.lastPositionAbs) {
+ this.lastPositionAbs = this.positionAbs;
+ }
+
+ //Do scrolling
+ if(this.options.scroll) {
+ if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
+
+ if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
+ } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
+ this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
+ }
+
+ if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
+ } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
+ this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
+ }
+
+ } else {
+
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ } else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+ }
+
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ } else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+ }
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
+ $.ui.ddmanager.prepareOffsets(this, event);
+ }
+ }
+
+ //Regenerate the absolute position used for position checks
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Set the helper position
+ if(!this.options.axis || this.options.axis !== "y") {
+ this.helper[0].style.left = this.position.left+"px";
+ }
+ if(!this.options.axis || this.options.axis !== "x") {
+ this.helper[0].style.top = this.position.top+"px";
+ }
+
+ //Rearrange
+ for (i = this.items.length - 1; i >= 0; i--) {
+
+ //Cache variables and intersection, continue if no intersection
+ item = this.items[i];
+ itemElement = item.item[0];
+ intersection = this._intersectsWithPointer(item);
+ if (!intersection) {
+ continue;
+ }
+
+ // Only put the placeholder inside the current Container, skip all
+ // items form other containers. This works because when moving
+ // an item from one container to another the
+ // currentContainer is switched before the placeholder is moved.
+ //
+ // Without this moving items in "sub-sortables" can cause the placeholder to jitter
+ // beetween the outer and inner container.
+ if (item.instance !== this.currentContainer) {
+ continue;
+ }
+
+ // cannot intersect with itself
+ // no useless actions that have been done before
+ // no action if the item moved is the parent of the item checked
+ if (itemElement !== this.currentItem[0] &&
+ this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
+ !$.contains(this.placeholder[0], itemElement) &&
+ (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
+ ) {
+
+ this.direction = intersection === 1 ? "down" : "up";
+
+ if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
+ this._rearrange(event, item);
+ } else {
+ break;
+ }
+
+ this._trigger("change", event, this._uiHash());
+ break;
+ }
+ }
+
+ //Post events to containers
+ this._contactContainers(event);
+
+ //Interconnect with droppables
+ if($.ui.ddmanager) {
+ $.ui.ddmanager.drag(this, event);
+ }
+
+ //Call callbacks
+ this._trigger("sort", event, this._uiHash());
+
+ this.lastPositionAbs = this.positionAbs;
+ return false;
+
+ },
+
+ _mouseStop: function(event, noPropagation) {
+
+ if(!event) {
+ return;
+ }
+
+ //If we are using droppables, inform the manager about the drop
+ if ($.ui.ddmanager && !this.options.dropBehaviour) {
+ $.ui.ddmanager.drop(this, event);
+ }
+
+ if(this.options.revert) {
+ var that = this,
+ cur = this.placeholder.offset(),
+ axis = this.options.axis,
+ animation = {};
+
+ if ( !axis || axis === "x" ) {
+ animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
+ }
+ if ( !axis || axis === "y" ) {
+ animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
+ }
+ this.reverting = true;
+ $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
+ that._clear(event);
+ });
+ } else {
+ this._clear(event, noPropagation);
+ }
+
+ return false;
+
+ },
+
+ cancel: function() {
+
+ if(this.dragging) {
+
+ this._mouseUp({ target: null });
+
+ if(this.options.helper === "original") {
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ } else {
+ this.currentItem.show();
+ }
+
+ //Post deactivating events to containers
+ for (var i = this.containers.length - 1; i >= 0; i--){
+ this.containers[i]._trigger("deactivate", null, this._uiHash(this));
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", null, this._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ if (this.placeholder) {
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ if(this.placeholder[0].parentNode) {
+ this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+ }
+ if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
+ this.helper.remove();
+ }
+
+ $.extend(this, {
+ helper: null,
+ dragging: false,
+ reverting: false,
+ _noFinalSort: null
+ });
+
+ if(this.domPosition.prev) {
+ $(this.domPosition.prev).after(this.currentItem);
+ } else {
+ $(this.domPosition.parent).prepend(this.currentItem);
+ }
+ }
+
+ return this;
+
+ },
+
+ serialize: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected),
+ str = [];
+ o = o || {};
+
+ $(items).each(function() {
+ var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
+ if (res) {
+ str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
+ }
+ });
+
+ if(!str.length && o.key) {
+ str.push(o.key + "=");
+ }
+
+ return str.join("&");
+
+ },
+
+ toArray: function(o) {
+
+ var items = this._getItemsAsjQuery(o && o.connected),
+ ret = [];
+
+ o = o || {};
+
+ items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
+ return ret;
+
+ },
+
+ /* Be careful with the following core functions */
+ _intersectsWith: function(item) {
+
+ var x1 = this.positionAbs.left,
+ x2 = x1 + this.helperProportions.width,
+ y1 = this.positionAbs.top,
+ y2 = y1 + this.helperProportions.height,
+ l = item.left,
+ r = l + item.width,
+ t = item.top,
+ b = t + item.height,
+ dyClick = this.offset.click.top,
+ dxClick = this.offset.click.left,
+ isOverElement = (y1 + dyClick) > t && (y1 + dyClick) < b && (x1 + dxClick) > l && (x1 + dxClick) < r;
+
+ if ( this.options.tolerance === "pointer" ||
+ this.options.forcePointerForContainers ||
+ (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
+ ) {
+ return isOverElement;
+ } else {
+
+ return (l < x1 + (this.helperProportions.width / 2) && // Right Half
+ x2 - (this.helperProportions.width / 2) < r && // Left Half
+ t < y1 + (this.helperProportions.height / 2) && // Bottom Half
+ y2 - (this.helperProportions.height / 2) < b ); // Top Half
+
+ }
+ },
+
+ _intersectsWithPointer: function(item) {
+
+ var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
+ isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
+ isOverElement = isOverElementHeight && isOverElementWidth,
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (!isOverElement) {
+ return false;
+ }
+
+ return this.floating ?
+ ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
+ : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
+
+ },
+
+ _intersectsWithSides: function(item) {
+
+ var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
+ isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
+ verticalDirection = this._getDragVerticalDirection(),
+ horizontalDirection = this._getDragHorizontalDirection();
+
+ if (this.floating && horizontalDirection) {
+ return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
+ } else {
+ return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
+ }
+
+ },
+
+ _getDragVerticalDirection: function() {
+ var delta = this.positionAbs.top - this.lastPositionAbs.top;
+ return delta !== 0 && (delta > 0 ? "down" : "up");
+ },
+
+ _getDragHorizontalDirection: function() {
+ var delta = this.positionAbs.left - this.lastPositionAbs.left;
+ return delta !== 0 && (delta > 0 ? "right" : "left");
+ },
+
+ refresh: function(event) {
+ this._refreshItems(event);
+ this.refreshPositions();
+ return this;
+ },
+
+ _connectWith: function() {
+ var options = this.options;
+ return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
+ },
+
+ _getItemsAsjQuery: function(connected) {
+
+ var i, j, cur, inst,
+ items = [],
+ queries = [],
+ connectWith = this._connectWith();
+
+ if(connectWith && connected) {
+ for (i = connectWith.length - 1; i >= 0; i--){
+ cur = $(connectWith[i]);
+ for ( j = cur.length - 1; j >= 0; j--){
+ inst = $.data(cur[j], this.widgetFullName);
+ if(inst && inst !== this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
+ }
+ }
+ }
+ }
+
+ queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
+
+ for (i = queries.length - 1; i >= 0; i--){
+ queries[i][0].each(function() {
+ items.push(this);
+ });
+ }
+
+ return $(items);
+
+ },
+
+ _removeCurrentsFromItems: function() {
+
+ var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
+
+ this.items = $.grep(this.items, function (item) {
+ for (var j=0; j < list.length; j++) {
+ if(list[j] === item.item[0]) {
+ return false;
+ }
+ }
+ return true;
+ });
+
+ },
+
+ _refreshItems: function(event) {
+
+ this.items = [];
+ this.containers = [this];
+
+ var i, j, cur, inst, targetData, _queries, item, queriesLength,
+ items = this.items,
+ queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
+ connectWith = this._connectWith();
+
+ if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
+ for (i = connectWith.length - 1; i >= 0; i--){
+ cur = $(connectWith[i]);
+ for (j = cur.length - 1; j >= 0; j--){
+ inst = $.data(cur[j], this.widgetFullName);
+ if(inst && inst !== this && !inst.options.disabled) {
+ queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
+ this.containers.push(inst);
+ }
+ }
+ }
+ }
+
+ for (i = queries.length - 1; i >= 0; i--) {
+ targetData = queries[i][1];
+ _queries = queries[i][0];
+
+ for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
+ item = $(_queries[j]);
+
+ item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
+
+ items.push({
+ item: item,
+ instance: targetData,
+ width: 0, height: 0,
+ left: 0, top: 0
+ });
+ }
+ }
+
+ },
+
+ refreshPositions: function(fast) {
+
+ //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
+ if(this.offsetParent && this.helper) {
+ this.offset.parent = this._getParentOffset();
+ }
+
+ var i, item, t, p;
+
+ for (i = this.items.length - 1; i >= 0; i--){
+ item = this.items[i];
+
+ //We ignore calculating positions of all connected containers when we're not over them
+ if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
+ continue;
+ }
+
+ t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
+
+ if (!fast) {
+ item.width = t.outerWidth();
+ item.height = t.outerHeight();
+ }
+
+ p = t.offset();
+ item.left = p.left;
+ item.top = p.top;
+ }
+
+ if(this.options.custom && this.options.custom.refreshContainers) {
+ this.options.custom.refreshContainers.call(this);
+ } else {
+ for (i = this.containers.length - 1; i >= 0; i--){
+ p = this.containers[i].element.offset();
+ this.containers[i].containerCache.left = p.left;
+ this.containers[i].containerCache.top = p.top;
+ this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
+ this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
+ }
+ }
+
+ return this;
+ },
+
+ _createPlaceholder: function(that) {
+ that = that || this;
+ var className,
+ o = that.options;
+
+ if(!o.placeholder || o.placeholder.constructor === String) {
+ className = o.placeholder;
+ o.placeholder = {
+ element: function() {
+
+ var nodeName = that.currentItem[0].nodeName.toLowerCase(),
+ element = $( that.document[0].createElement( nodeName ) )
+ .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
+ .removeClass("ui-sortable-helper");
+
+ if ( nodeName === "tr" ) {
+ // Use a high colspan to force the td to expand the full
+ // width of the table (browsers are smart enough to
+ // handle this properly)
+ element.append( "<td colspan='99'>&#160;</td>" );
+ } else if ( nodeName === "img" ) {
+ element.attr( "src", that.currentItem.attr( "src" ) );
+ }
+
+ if ( !className ) {
+ element.css( "visibility", "hidden" );
+ }
+
+ return element;
+ },
+ update: function(container, p) {
+
+ // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
+ // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
+ if(className && !o.forcePlaceholderSize) {
+ return;
+ }
+
+ //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
+ if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
+ if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
+ }
+ };
+ }
+
+ //Create the placeholder
+ that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
+
+ //Append it after the actual current item
+ that.currentItem.after(that.placeholder);
+
+ //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+ o.placeholder.update(that, that.placeholder);
+
+ },
+
+ _contactContainers: function(event) {
+ var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
+ innermostContainer = null,
+ innermostIndex = null;
+
+ // get innermost container that intersects with item
+ for (i = this.containers.length - 1; i >= 0; i--) {
+
+ // never consider a container that's located within the item itself
+ if($.contains(this.currentItem[0], this.containers[i].element[0])) {
+ continue;
+ }
+
+ if(this._intersectsWith(this.containers[i].containerCache)) {
+
+ // if we've already found a container and it's more "inner" than this, then continue
+ if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
+ continue;
+ }
+
+ innermostContainer = this.containers[i];
+ innermostIndex = i;
+
+ } else {
+ // container doesn't intersect. trigger "out" event if necessary
+ if(this.containers[i].containerCache.over) {
+ this.containers[i]._trigger("out", event, this._uiHash(this));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ }
+
+ // if no intersecting containers found, return
+ if(!innermostContainer) {
+ return;
+ }
+
+ // move the item into the container if it's not there already
+ if(this.containers.length === 1) {
+ if (!this.containers[innermostIndex].containerCache.over) {
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ }
+ } else {
+
+ //When entering a new container, we will find the item with the least distance and append our item near it
+ dist = 10000;
+ itemWithLeastDistance = null;
+ floating = innermostContainer.floating || isFloating(this.currentItem);
+ posProperty = floating ? "left" : "top";
+ sizeProperty = floating ? "width" : "height";
+ base = this.positionAbs[posProperty] + this.offset.click[posProperty];
+ for (j = this.items.length - 1; j >= 0; j--) {
+ if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
+ continue;
+ }
+ if(this.items[j].item[0] === this.currentItem[0]) {
+ continue;
+ }
+ if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
+ continue;
+ }
+ cur = this.items[j].item.offset()[posProperty];
+ nearBottom = false;
+ if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
+ nearBottom = true;
+ cur += this.items[j][sizeProperty];
+ }
+
+ if(Math.abs(cur - base) < dist) {
+ dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j];
+ this.direction = nearBottom ? "up": "down";
+ }
+ }
+
+ //Check if dropOnEmpty is enabled
+ if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
+ return;
+ }
+
+ if(this.currentContainer === this.containers[innermostIndex]) {
+ return;
+ }
+
+ itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
+ this._trigger("change", event, this._uiHash());
+ this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
+ this.currentContainer = this.containers[innermostIndex];
+
+ //Update the placeholder
+ this.options.placeholder.update(this.currentContainer, this.placeholder);
+
+ this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
+ this.containers[innermostIndex].containerCache.over = 1;
+ }
+
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options,
+ helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
+
+ //Add the helper to the DOM if that didn't happen already
+ if(!helper.parents("body").length) {
+ $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
+ }
+
+ if(helper[0] === this.currentItem[0]) {
+ this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
+ }
+
+ if(!helper[0].style.width || o.forceHelperSize) {
+ helper.width(this.currentItem.width());
+ }
+ if(!helper[0].style.height || o.forceHelperSize) {
+ helper.height(this.currentItem.height());
+ }
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if (typeof obj === "string") {
+ obj = obj.split(" ");
+ }
+ if ($.isArray(obj)) {
+ obj = {left: +obj[0], top: +obj[1] || 0};
+ }
+ if ("left" in obj) {
+ this.offset.click.left = obj.left + this.margins.left;
+ }
+ if ("right" in obj) {
+ this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ }
+ if ("top" in obj) {
+ this.offset.click.top = obj.top + this.margins.top;
+ }
+ if ("bottom" in obj) {
+ this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ }
+ },
+
+ _getParentOffset: function() {
+
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ // This needs to be actually done for all browsers, since pageX/pageY includes this information
+ // with an ugly IE fix
+ if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
+ po = { top: 0, left: 0 };
+ }
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition === "relative") {
+ var p = this.currentItem.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
+ top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var ce, co, over,
+ o = this.options;
+ if(o.containment === "parent") {
+ o.containment = this.helper[0].parentNode;
+ }
+ if(o.containment === "document" || o.containment === "window") {
+ this.containment = [
+ 0 - this.offset.relative.left - this.offset.parent.left,
+ 0 - this.offset.relative.top - this.offset.parent.top,
+ $(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
+ ($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+ }
+
+ if(!(/^(document|window|parent)$/).test(o.containment)) {
+ ce = $(o.containment)[0];
+ co = $(o.containment).offset();
+ over = ($(ce).css("overflow") !== "hidden");
+
+ this.containment = [
+ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+ co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+ co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+ co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+ ];
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) {
+ pos = this.position;
+ }
+ var mod = d === "absolute" ? 1 : -1,
+ scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
+ scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top + // The absolute mouse position
+ this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left + // The absolute mouse position
+ this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var top, left,
+ o = this.options,
+ pageX = event.pageX,
+ pageY = event.pageY,
+ scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ // This is another very weird special case that only happens for relative elements:
+ // 1. If the css position is relative
+ // 2. and the scroll parent is the document or similar to the offset parent
+ // we have to refresh the relative offset during the scroll so there are no jumps
+ if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
+ this.offset.relative = this._getRelativeOffset();
+ }
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+ if(this.containment) {
+ if(event.pageX - this.offset.click.left < this.containment[0]) {
+ pageX = this.containment[0] + this.offset.click.left;
+ }
+ if(event.pageY - this.offset.click.top < this.containment[1]) {
+ pageY = this.containment[1] + this.offset.click.top;
+ }
+ if(event.pageX - this.offset.click.left > this.containment[2]) {
+ pageX = this.containment[2] + this.offset.click.left;
+ }
+ if(event.pageY - this.offset.click.top > this.containment[3]) {
+ pageY = this.containment[3] + this.offset.click.top;
+ }
+ }
+
+ if(o.grid) {
+ top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+ pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+ pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY - // The absolute mouse position
+ this.offset.click.top - // Click offset (relative to the element)
+ this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX - // The absolute mouse position
+ this.offset.click.left - // Click offset (relative to the element)
+ this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
+ this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
+ ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _rearrange: function(event, i, a, hardRefresh) {
+
+ a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
+
+ //Various things done here to improve the performance:
+ // 1. we create a setTimeout, that calls refreshPositions
+ // 2. on the instance, we have a counter variable, that get's higher after every append
+ // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
+ // 4. this lets only the last addition to the timeout stack through
+ this.counter = this.counter ? ++this.counter : 1;
+ var counter = this.counter;
+
+ this._delay(function() {
+ if(counter === this.counter) {
+ this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
+ }
+ });
+
+ },
+
+ _clear: function(event, noPropagation) {
+
+ this.reverting = false;
+ // We delay all events that have to be triggered to after the point where the placeholder has been removed and
+ // everything else normalized again
+ var i,
+ delayedTriggers = [];
+
+ // We first have to update the dom position of the actual currentItem
+ // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
+ if(!this._noFinalSort && this.currentItem.parent().length) {
+ this.placeholder.before(this.currentItem);
+ }
+ this._noFinalSort = null;
+
+ if(this.helper[0] === this.currentItem[0]) {
+ for(i in this._storedCSS) {
+ if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
+ this._storedCSS[i] = "";
+ }
+ }
+ this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
+ } else {
+ this.currentItem.show();
+ }
+
+ if(this.fromOutside && !noPropagation) {
+ delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
+ }
+ if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
+ delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
+ }
+
+ // Check if the items Container has Changed and trigger appropriate
+ // events.
+ if (this !== this.currentContainer) {
+ if(!noPropagation) {
+ delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
+ }
+ }
+
+
+ //Post events to containers
+ for (i = this.containers.length - 1; i >= 0; i--){
+ if(!noPropagation) {
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ }
+ if(this.containers[i].containerCache.over) {
+ delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
+ this.containers[i].containerCache.over = 0;
+ }
+ }
+
+ //Do what was originally in plugins
+ if ( this.storedCursor ) {
+ this.document.find( "body" ).css( "cursor", this.storedCursor );
+ this.storedStylesheet.remove();
+ }
+ if(this._storedOpacity) {
+ this.helper.css("opacity", this._storedOpacity);
+ }
+ if(this._storedZIndex) {
+ this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
+ }
+
+ this.dragging = false;
+ if(this.cancelHelperRemoval) {
+ if(!noPropagation) {
+ this._trigger("beforeStop", event, this._uiHash());
+ for (i=0; i < delayedTriggers.length; i++) {
+ delayedTriggers[i].call(this, event);
+ } //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return false;
+ }
+
+ if(!noPropagation) {
+ this._trigger("beforeStop", event, this._uiHash());
+ }
+
+ //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
+ this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
+
+ if(this.helper[0] !== this.currentItem[0]) {
+ this.helper.remove();
+ }
+ this.helper = null;
+
+ if(!noPropagation) {
+ for (i=0; i < delayedTriggers.length; i++) {
+ delayedTriggers[i].call(this, event);
+ } //Trigger all delayed events
+ this._trigger("stop", event, this._uiHash());
+ }
+
+ this.fromOutside = false;
+ return true;
+
+ },
+
+ _trigger: function() {
+ if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
+ this.cancel();
+ }
+ },
+
+ _uiHash: function(_inst) {
+ var inst = _inst || this;
+ return {
+ helper: inst.helper,
+ placeholder: inst.placeholder || $([]),
+ position: inst.position,
+ originalPosition: inst.originalPosition,
+ offset: inst.positionAbs,
+ item: inst.currentItem,
+ sender: _inst ? _inst.element : null
+ };
+ }
+
+});
+
+})(jQuery);
+
+(function($, undefined) {
+
+var dataSpace = "ui-effects-";
+
+$.effects = {
+ effect: {}
+};
+
+/*!
+ * jQuery Color Animations v2.1.2
+ * https://github.com/jquery/jquery-color
+ *
+ * Copyright 2013 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * Date: Wed Jan 16 08:47:09 2013 -0600
+ */
+(function( jQuery, undefined ) {
+
+ var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",
+
+ // plusequals test for += 100 -= 100
+ rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
+ // a set of RE's that can match strings and generate color tuples.
+ stringParsers = [{
+ re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ],
+ execResult[ 2 ],
+ execResult[ 3 ],
+ execResult[ 4 ]
+ ];
+ }
+ }, {
+ re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ] * 2.55,
+ execResult[ 2 ] * 2.55,
+ execResult[ 3 ] * 2.55,
+ execResult[ 4 ]
+ ];
+ }
+ }, {
+ // this regex ignores A-F because it's compared against an already lowercased string
+ re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
+ parse: function( execResult ) {
+ return [
+ parseInt( execResult[ 1 ], 16 ),
+ parseInt( execResult[ 2 ], 16 ),
+ parseInt( execResult[ 3 ], 16 )
+ ];
+ }
+ }, {
+ // this regex ignores A-F because it's compared against an already lowercased string
+ re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
+ parse: function( execResult ) {
+ return [
+ parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
+ parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
+ parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
+ ];
+ }
+ }, {
+ re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+ space: "hsla",
+ parse: function( execResult ) {
+ return [
+ execResult[ 1 ],
+ execResult[ 2 ] / 100,
+ execResult[ 3 ] / 100,
+ execResult[ 4 ]
+ ];
+ }
+ }],
+
+ // jQuery.Color( )
+ color = jQuery.Color = function( color, green, blue, alpha ) {
+ return new jQuery.Color.fn.parse( color, green, blue, alpha );
+ },
+ spaces = {
+ rgba: {
+ props: {
+ red: {
+ idx: 0,
+ type: "byte"
+ },
+ green: {
+ idx: 1,
+ type: "byte"
+ },
+ blue: {
+ idx: 2,
+ type: "byte"
+ }
+ }
+ },
+
+ hsla: {
+ props: {
+ hue: {
+ idx: 0,
+ type: "degrees"
+ },
+ saturation: {
+ idx: 1,
+ type: "percent"
+ },
+ lightness: {
+ idx: 2,
+ type: "percent"
+ }
+ }
+ }
+ },
+ propTypes = {
+ "byte": {
+ floor: true,
+ max: 255
+ },
+ "percent": {
+ max: 1
+ },
+ "degrees": {
+ mod: 360,
+ floor: true
+ }
+ },
+ support = color.support = {},
+
+ // element for support tests
+ supportElem = jQuery( "<p>" )[ 0 ],
+
+ // colors = jQuery.Color.names
+ colors,
+
+ // local aliases of functions called often
+ each = jQuery.each;
+
+// determine rgba support immediately
+supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
+support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
+
+// define cache name and alpha properties
+// for rgba and hsla spaces
+each( spaces, function( spaceName, space ) {
+ space.cache = "_" + spaceName;
+ space.props.alpha = {
+ idx: 3,
+ type: "percent",
+ def: 1
+ };
+});
+
+function clamp( value, prop, allowEmpty ) {
+ var type = propTypes[ prop.type ] || {};
+
+ if ( value == null ) {
+ return (allowEmpty || !prop.def) ? null : prop.def;
+ }
+
+ // ~~ is an short way of doing floor for positive numbers
+ value = type.floor ? ~~value : parseFloat( value );
+
+ // IE will pass in empty strings as value for alpha,
+ // which will hit this case
+ if ( isNaN( value ) ) {
+ return prop.def;
+ }
+
+ if ( type.mod ) {
+ // we add mod before modding to make sure that negatives values
+ // get converted properly: -10 -> 350
+ return (value + type.mod) % type.mod;
+ }
+
+ // for now all property types without mod have min and max
+ return 0 > value ? 0 : type.max < value ? type.max : value;
+}
+
+function stringParse( string ) {
+ var inst = color(),
+ rgba = inst._rgba = [];
+
+ string = string.toLowerCase();
+
+ each( stringParsers, function( i, parser ) {
+ var parsed,
+ match = parser.re.exec( string ),
+ values = match && parser.parse( match ),
+ spaceName = parser.space || "rgba";
+
+ if ( values ) {
+ parsed = inst[ spaceName ]( values );
+
+ // if this was an rgba parse the assignment might happen twice
+ // oh well....
+ inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
+ rgba = inst._rgba = parsed._rgba;
+
+ // exit each( stringParsers ) here because we matched
+ return false;
+ }
+ });
+
+ // Found a stringParser that handled it
+ if ( rgba.length ) {
+
+ // if this came from a parsed string, force "transparent" when alpha is 0
+ // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
+ if ( rgba.join() === "0,0,0,0" ) {
+ jQuery.extend( rgba, colors.transparent );
+ }
+ return inst;
+ }
+
+ // named colors
+ return colors[ string ];
+}
+
+color.fn = jQuery.extend( color.prototype, {
+ parse: function( red, green, blue, alpha ) {
+ if ( red === undefined ) {
+ this._rgba = [ null, null, null, null ];
+ return this;
+ }
+ if ( red.jquery || red.nodeType ) {
+ red = jQuery( red ).css( green );
+ green = undefined;
+ }
+
+ var inst = this,
+ type = jQuery.type( red ),
+ rgba = this._rgba = [];
+
+ // more than 1 argument specified - assume ( red, green, blue, alpha )
+ if ( green !== undefined ) {
+ red = [ red, green, blue, alpha ];
+ type = "array";
+ }
+
+ if ( type === "string" ) {
+ return this.parse( stringParse( red ) || colors._default );
+ }
+
+ if ( type === "array" ) {
+ each( spaces.rgba.props, function( key, prop ) {
+ rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
+ });
+ return this;
+ }
+
+ if ( type === "object" ) {
+ if ( red instanceof color ) {
+ each( spaces, function( spaceName, space ) {
+ if ( red[ space.cache ] ) {
+ inst[ space.cache ] = red[ space.cache ].slice();
+ }
+ });
+ } else {
+ each( spaces, function( spaceName, space ) {
+ var cache = space.cache;
+ each( space.props, function( key, prop ) {
+
+ // if the cache doesn't exist, and we know how to convert
+ if ( !inst[ cache ] && space.to ) {
+
+ // if the value was null, we don't need to copy it
+ // if the key was alpha, we don't need to copy it either
+ if ( key === "alpha" || red[ key ] == null ) {
+ return;
+ }
+ inst[ cache ] = space.to( inst._rgba );
+ }
+
+ // this is the only case where we allow nulls for ALL properties.
+ // call clamp with alwaysAllowEmpty
+ inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
+ });
+
+ // everything defined but alpha?
+ if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
+ // use the default of 1
+ inst[ cache ][ 3 ] = 1;
+ if ( space.from ) {
+ inst._rgba = space.from( inst[ cache ] );
+ }
+ }
+ });
+ }
+ return this;
+ }
+ },
+ is: function( compare ) {
+ var is = color( compare ),
+ same = true,
+ inst = this;
+
+ each( spaces, function( _, space ) {
+ var localCache,
+ isCache = is[ space.cache ];
+ if (isCache) {
+ localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
+ each( space.props, function( _, prop ) {
+ if ( isCache[ prop.idx ] != null ) {
+ same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
+ return same;
+ }
+ });
+ }
+ return same;
+ });
+ return same;
+ },
+ _space: function() {
+ var used = [],
+ inst = this;
+ each( spaces, function( spaceName, space ) {
+ if ( inst[ space.cache ] ) {
+ used.push( spaceName );
+ }
+ });
+ return used.pop();
+ },
+ transition: function( other, distance ) {
+ var end = color( other ),
+ spaceName = end._space(),
+ space = spaces[ spaceName ],
+ startColor = this.alpha() === 0 ? color( "transparent" ) : this,
+ start = startColor[ space.cache ] || space.to( startColor._rgba ),
+ result = start.slice();
+
+ end = end[ space.cache ];
+ each( space.props, function( key, prop ) {
+ var index = prop.idx,
+ startValue = start[ index ],
+ endValue = end[ index ],
+ type = propTypes[ prop.type ] || {};
+
+ // if null, don't override start value
+ if ( endValue === null ) {
+ return;
+ }
+ // if null - use end
+ if ( startValue === null ) {
+ result[ index ] = endValue;
+ } else {
+ if ( type.mod ) {
+ if ( endValue - startValue > type.mod / 2 ) {
+ startValue += type.mod;
+ } else if ( startValue - endValue > type.mod / 2 ) {
+ startValue -= type.mod;
+ }
+ }
+ result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
+ }
+ });
+ return this[ spaceName ]( result );
+ },
+ blend: function( opaque ) {
+ // if we are already opaque - return ourself
+ if ( this._rgba[ 3 ] === 1 ) {
+ return this;
+ }
+
+ var rgb = this._rgba.slice(),
+ a = rgb.pop(),
+ blend = color( opaque )._rgba;
+
+ return color( jQuery.map( rgb, function( v, i ) {
+ return ( 1 - a ) * blend[ i ] + a * v;
+ }));
+ },
+ toRgbaString: function() {
+ var prefix = "rgba(",
+ rgba = jQuery.map( this._rgba, function( v, i ) {
+ return v == null ? ( i > 2 ? 1 : 0 ) : v;
+ });
+
+ if ( rgba[ 3 ] === 1 ) {
+ rgba.pop();
+ prefix = "rgb(";
+ }
+
+ return prefix + rgba.join() + ")";
+ },
+ toHslaString: function() {
+ var prefix = "hsla(",
+ hsla = jQuery.map( this.hsla(), function( v, i ) {
+ if ( v == null ) {
+ v = i > 2 ? 1 : 0;
+ }
+
+ // catch 1 and 2
+ if ( i && i < 3 ) {
+ v = Math.round( v * 100 ) + "%";
+ }
+ return v;
+ });
+
+ if ( hsla[ 3 ] === 1 ) {
+ hsla.pop();
+ prefix = "hsl(";
+ }
+ return prefix + hsla.join() + ")";
+ },
+ toHexString: function( includeAlpha ) {
+ var rgba = this._rgba.slice(),
+ alpha = rgba.pop();
+
+ if ( includeAlpha ) {
+ rgba.push( ~~( alpha * 255 ) );
+ }
+
+ return "#" + jQuery.map( rgba, function( v ) {
+
+ // default to 0 when nulls exist
+ v = ( v || 0 ).toString( 16 );
+ return v.length === 1 ? "0" + v : v;
+ }).join("");
+ },
+ toString: function() {
+ return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
+ }
+});
+color.fn.parse.prototype = color.fn;
+
+// hsla conversions adapted from:
+// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
+
+function hue2rgb( p, q, h ) {
+ h = ( h + 1 ) % 1;
+ if ( h * 6 < 1 ) {
+ return p + (q - p) * h * 6;
+ }
+ if ( h * 2 < 1) {
+ return q;
+ }
+ if ( h * 3 < 2 ) {
+ return p + (q - p) * ((2/3) - h) * 6;
+ }
+ return p;
+}
+
+spaces.hsla.to = function ( rgba ) {
+ if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
+ return [ null, null, null, rgba[ 3 ] ];
+ }
+ var r = rgba[ 0 ] / 255,
+ g = rgba[ 1 ] / 255,
+ b = rgba[ 2 ] / 255,
+ a = rgba[ 3 ],
+ max = Math.max( r, g, b ),
+ min = Math.min( r, g, b ),
+ diff = max - min,
+ add = max + min,
+ l = add * 0.5,
+ h, s;
+
+ if ( min === max ) {
+ h = 0;
+ } else if ( r === max ) {
+ h = ( 60 * ( g - b ) / diff ) + 360;
+ } else if ( g === max ) {
+ h = ( 60 * ( b - r ) / diff ) + 120;
+ } else {
+ h = ( 60 * ( r - g ) / diff ) + 240;
+ }
+
+ // chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
+ // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
+ if ( diff === 0 ) {
+ s = 0;
+ } else if ( l <= 0.5 ) {
+ s = diff / add;
+ } else {
+ s = diff / ( 2 - add );
+ }
+ return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
+};
+
+spaces.hsla.from = function ( hsla ) {
+ if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
+ return [ null, null, null, hsla[ 3 ] ];
+ }
+ var h = hsla[ 0 ] / 360,
+ s = hsla[ 1 ],
+ l = hsla[ 2 ],
+ a = hsla[ 3 ],
+ q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
+ p = 2 * l - q;
+
+ return [
+ Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
+ Math.round( hue2rgb( p, q, h ) * 255 ),
+ Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
+ a
+ ];
+};
+
+
+each( spaces, function( spaceName, space ) {
+ var props = space.props,
+ cache = space.cache,
+ to = space.to,
+ from = space.from;
+
+ // makes rgba() and hsla()
+ color.fn[ spaceName ] = function( value ) {
+
+ // generate a cache for this space if it doesn't exist
+ if ( to && !this[ cache ] ) {
+ this[ cache ] = to( this._rgba );
+ }
+ if ( value === undefined ) {
+ return this[ cache ].slice();
+ }
+
+ var ret,
+ type = jQuery.type( value ),
+ arr = ( type === "array" || type === "object" ) ? value : arguments,
+ local = this[ cache ].slice();
+
+ each( props, function( key, prop ) {
+ var val = arr[ type === "object" ? key : prop.idx ];
+ if ( val == null ) {
+ val = local[ prop.idx ];
+ }
+ local[ prop.idx ] = clamp( val, prop );
+ });
+
+ if ( from ) {
+ ret = color( from( local ) );
+ ret[ cache ] = local;
+ return ret;
+ } else {
+ return color( local );
+ }
+ };
+
+ // makes red() green() blue() alpha() hue() saturation() lightness()
+ each( props, function( key, prop ) {
+ // alpha is included in more than one space
+ if ( color.fn[ key ] ) {
+ return;
+ }
+ color.fn[ key ] = function( value ) {
+ var vtype = jQuery.type( value ),
+ fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
+ local = this[ fn ](),
+ cur = local[ prop.idx ],
+ match;
+
+ if ( vtype === "undefined" ) {
+ return cur;
+ }
+
+ if ( vtype === "function" ) {
+ value = value.call( this, cur );
+ vtype = jQuery.type( value );
+ }
+ if ( value == null && prop.empty ) {
+ return this;
+ }
+ if ( vtype === "string" ) {
+ match = rplusequals.exec( value );
+ if ( match ) {
+ value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
+ }
+ }
+ local[ prop.idx ] = value;
+ return this[ fn ]( local );
+ };
+ });
+});
+
+// add cssHook and .fx.step function for each named hook.
+// accept a space separated string of properties
+color.hook = function( hook ) {
+ var hooks = hook.split( " " );
+ each( hooks, function( i, hook ) {
+ jQuery.cssHooks[ hook ] = {
+ set: function( elem, value ) {
+ var parsed, curElem,
+ backgroundColor = "";
+
+ if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) {
+ value = color( parsed || value );
+ if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
+ curElem = hook === "backgroundColor" ? elem.parentNode : elem;
+ while (
+ (backgroundColor === "" || backgroundColor === "transparent") &&
+ curElem && curElem.style
+ ) {
+ try {
+ backgroundColor = jQuery.css( curElem, "backgroundColor" );
+ curElem = curElem.parentNode;
+ } catch ( e ) {
+ }
+ }
+
+ value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
+ backgroundColor :
+ "_default" );
+ }
+
+ value = value.toRgbaString();
+ }
+ try {
+ elem.style[ hook ] = value;
+ } catch( e ) {
+ // wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
+ }
+ }
+ };
+ jQuery.fx.step[ hook ] = function( fx ) {
+ if ( !fx.colorInit ) {
+ fx.start = color( fx.elem, hook );
+ fx.end = color( fx.end );
+ fx.colorInit = true;
+ }
+ jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
+ };
+ });
+
+};
+
+color.hook( stepHooks );
+
+jQuery.cssHooks.borderColor = {
+ expand: function( value ) {
+ var expanded = {};
+
+ each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
+ expanded[ "border" + part + "Color" ] = value;
+ });
+ return expanded;
+ }
+};
+
+// Basic color names only.
+// Usage of any of the other color names requires adding yourself or including
+// jquery.color.svg-names.js.
+colors = jQuery.Color.names = {
+ // 4.1. Basic color keywords
+ aqua: "#00ffff",
+ black: "#000000",
+ blue: "#0000ff",
+ fuchsia: "#ff00ff",
+ gray: "#808080",
+ green: "#008000",
+ lime: "#00ff00",
+ maroon: "#800000",
+ navy: "#000080",
+ olive: "#808000",
+ purple: "#800080",
+ red: "#ff0000",
+ silver: "#c0c0c0",
+ teal: "#008080",
+ white: "#ffffff",
+ yellow: "#ffff00",
+
+ // 4.2.3. "transparent" color keyword
+ transparent: [ null, null, null, 0 ],
+
+ _default: "#ffffff"
+};
+
+})( jQuery );
+
+
+/******************************************************************************/
+/****************************** CLASS ANIMATIONS ******************************/
+/******************************************************************************/
+(function() {
+
+var classAnimationActions = [ "add", "remove", "toggle" ],
+ shorthandStyles = {
+ border: 1,
+ borderBottom: 1,
+ borderColor: 1,
+ borderLeft: 1,
+ borderRight: 1,
+ borderTop: 1,
+ borderWidth: 1,
+ margin: 1,
+ padding: 1
+ };
+
+$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
+ $.fx.step[ prop ] = function( fx ) {
+ if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
+ jQuery.style( fx.elem, prop, fx.end );
+ fx.setAttr = true;
+ }
+ };
+});
+
+function getElementStyles( elem ) {
+ var key, len,
+ style = elem.ownerDocument.defaultView ?
+ elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
+ elem.currentStyle,
+ styles = {};
+
+ if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
+ len = style.length;
+ while ( len-- ) {
+ key = style[ len ];
+ if ( typeof style[ key ] === "string" ) {
+ styles[ $.camelCase( key ) ] = style[ key ];
+ }
+ }
+ // support: Opera, IE <9
+ } else {
+ for ( key in style ) {
+ if ( typeof style[ key ] === "string" ) {
+ styles[ key ] = style[ key ];
+ }
+ }
+ }
+
+ return styles;
+}
+
+
+function styleDifference( oldStyle, newStyle ) {
+ var diff = {},
+ name, value;
+
+ for ( name in newStyle ) {
+ value = newStyle[ name ];
+ if ( oldStyle[ name ] !== value ) {
+ if ( !shorthandStyles[ name ] ) {
+ if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
+ diff[ name ] = value;
+ }
+ }
+ }
+ }
+
+ return diff;
+}
+
+// support: jQuery <1.8
+if ( !$.fn.addBack ) {
+ $.fn.addBack = function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter( selector )
+ );
+ };
+}
+
+$.effects.animateClass = function( value, duration, easing, callback ) {
+ var o = $.speed( duration, easing, callback );
+
+ return this.queue( function() {
+ var animated = $( this ),
+ baseClass = animated.attr( "class" ) || "",
+ applyClassChange,
+ allAnimations = o.children ? animated.find( "*" ).addBack() : animated;
+
+ // map the animated objects to store the original styles.
+ allAnimations = allAnimations.map(function() {
+ var el = $( this );
+ return {
+ el: el,
+ start: getElementStyles( this )
+ };
+ });
+
+ // apply class change
+ applyClassChange = function() {
+ $.each( classAnimationActions, function(i, action) {
+ if ( value[ action ] ) {
+ animated[ action + "Class" ]( value[ action ] );
+ }
+ });
+ };
+ applyClassChange();
+
+ // map all animated objects again - calculate new styles and diff
+ allAnimations = allAnimations.map(function() {
+ this.end = getElementStyles( this.el[ 0 ] );
+ this.diff = styleDifference( this.start, this.end );
+ return this;
+ });
+
+ // apply original class
+ animated.attr( "class", baseClass );
+
+ // map all animated objects again - this time collecting a promise
+ allAnimations = allAnimations.map(function() {
+ var styleInfo = this,
+ dfd = $.Deferred(),
+ opts = $.extend({}, o, {
+ queue: false,
+ complete: function() {
+ dfd.resolve( styleInfo );
+ }
+ });
+
+ this.el.animate( this.diff, opts );
+ return dfd.promise();
+ });
+
+ // once all animations have completed:
+ $.when.apply( $, allAnimations.get() ).done(function() {
+
+ // set the final class
+ applyClassChange();
+
+ // for each animated element,
+ // clear all css properties that were animated
+ $.each( arguments, function() {
+ var el = this.el;
+ $.each( this.diff, function(key) {
+ el.css( key, "" );
+ });
+ });
+
+ // this is guarnteed to be there if you use jQuery.speed()
+ // it also handles dequeuing the next anim...
+ o.complete.call( animated[ 0 ] );
+ });
+ });
+};
+
+$.fn.extend({
+ addClass: (function( orig ) {
+ return function( classNames, speed, easing, callback ) {
+ return speed ?
+ $.effects.animateClass.call( this,
+ { add: classNames }, speed, easing, callback ) :
+ orig.apply( this, arguments );
+ };
+ })( $.fn.addClass ),
+
+ removeClass: (function( orig ) {
+ return function( classNames, speed, easing, callback ) {
+ return arguments.length > 1 ?
+ $.effects.animateClass.call( this,
+ { remove: classNames }, speed, easing, callback ) :
+ orig.apply( this, arguments );
+ };
+ })( $.fn.removeClass ),
+
+ toggleClass: (function( orig ) {
+ return function( classNames, force, speed, easing, callback ) {
+ if ( typeof force === "boolean" || force === undefined ) {
+ if ( !speed ) {
+ // without speed parameter
+ return orig.apply( this, arguments );
+ } else {
+ return $.effects.animateClass.call( this,
+ (force ? { add: classNames } : { remove: classNames }),
+ speed, easing, callback );
+ }
+ } else {
+ // without force parameter
+ return $.effects.animateClass.call( this,
+ { toggle: classNames }, force, speed, easing );
+ }
+ };
+ })( $.fn.toggleClass ),
+
+ switchClass: function( remove, add, speed, easing, callback) {
+ return $.effects.animateClass.call( this, {
+ add: add,
+ remove: remove
+ }, speed, easing, callback );
+ }
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EFFECTS **********************************/
+/******************************************************************************/
+
+(function() {
+
+$.extend( $.effects, {
+ version: "1.10.2",
+
+ // Saves a set of properties in a data storage
+ save: function( element, set ) {
+ for( var i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+ }
+ }
+ },
+
+ // Restores a set of previously saved properties from a data storage
+ restore: function( element, set ) {
+ var val, i;
+ for( i=0; i < set.length; i++ ) {
+ if ( set[ i ] !== null ) {
+ val = element.data( dataSpace + set[ i ] );
+ // support: jQuery 1.6.2
+ // http://bugs.jquery.com/ticket/9917
+ // jQuery 1.6.2 incorrectly returns undefined for any falsy value.
+ // We can't differentiate between "" and 0 here, so we just assume
+ // empty string since it's likely to be a more common value...
+ if ( val === undefined ) {
+ val = "";
+ }
+ element.css( set[ i ], val );
+ }
+ }
+ },
+
+ setMode: function( el, mode ) {
+ if (mode === "toggle") {
+ mode = el.is( ":hidden" ) ? "show" : "hide";
+ }
+ return mode;
+ },
+
+ // Translates a [top,left] array into a baseline value
+ // this should be a little more flexible in the future to handle a string & hash
+ getBaseline: function( origin, original ) {
+ var y, x;
+ switch ( origin[ 0 ] ) {
+ case "top": y = 0; break;
+ case "middle": y = 0.5; break;
+ case "bottom": y = 1; break;
+ default: y = origin[ 0 ] / original.height;
+ }
+ switch ( origin[ 1 ] ) {
+ case "left": x = 0; break;
+ case "center": x = 0.5; break;
+ case "right": x = 1; break;
+ default: x = origin[ 1 ] / original.width;
+ }
+ return {
+ x: x,
+ y: y
+ };
+ },
+
+ // Wraps the element around a wrapper that copies position properties
+ createWrapper: function( element ) {
+
+ // if the element is already wrapped, return it
+ if ( element.parent().is( ".ui-effects-wrapper" )) {
+ return element.parent();
+ }
+
+ // wrap the element
+ var props = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ "float": element.css( "float" )
+ },
+ wrapper = $( "<div></div>" )
+ .addClass( "ui-effects-wrapper" )
+ .css({
+ fontSize: "100%",
+ background: "transparent",
+ border: "none",
+ margin: 0,
+ padding: 0
+ }),
+ // Store the size in case width/height are defined in % - Fixes #5245
+ size = {
+ width: element.width(),
+ height: element.height()
+ },
+ active = document.activeElement;
+
+ // support: Firefox
+ // Firefox incorrectly exposes anonymous content
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=561664
+ try {
+ active.id;
+ } catch( e ) {
+ active = document.body;
+ }
+
+ element.wrap( wrapper );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+
+ wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element
+
+ // transfer positioning properties to the wrapper
+ if ( element.css( "position" ) === "static" ) {
+ wrapper.css({ position: "relative" });
+ element.css({ position: "relative" });
+ } else {
+ $.extend( props, {
+ position: element.css( "position" ),
+ zIndex: element.css( "z-index" )
+ });
+ $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
+ props[ pos ] = element.css( pos );
+ if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+ props[ pos ] = "auto";
+ }
+ });
+ element.css({
+ position: "relative",
+ top: 0,
+ left: 0,
+ right: "auto",
+ bottom: "auto"
+ });
+ }
+ element.css(size);
+
+ return wrapper.css( props ).show();
+ },
+
+ removeWrapper: function( element ) {
+ var active = document.activeElement;
+
+ if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+ element.parent().replaceWith( element );
+
+ // Fixes #7595 - Elements lose focus when wrapped.
+ if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+ $( active ).focus();
+ }
+ }
+
+
+ return element;
+ },
+
+ setTransition: function( element, list, factor, value ) {
+ value = value || {};
+ $.each( list, function( i, x ) {
+ var unit = element.cssUnit( x );
+ if ( unit[ 0 ] > 0 ) {
+ value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
+ }
+ });
+ return value;
+ }
+});
+
+// return an effect options object for the given parameters:
+function _normalizeArguments( effect, options, speed, callback ) {
+
+ // allow passing all options as the first parameter
+ if ( $.isPlainObject( effect ) ) {
+ options = effect;
+ effect = effect.effect;
+ }
+
+ // convert to an object
+ effect = { effect: effect };
+
+ // catch (effect, null, ...)
+ if ( options == null ) {
+ options = {};
+ }
+
+ // catch (effect, callback)
+ if ( $.isFunction( options ) ) {
+ callback = options;
+ speed = null;
+ options = {};
+ }
+
+ // catch (effect, speed, ?)
+ if ( typeof options === "number" || $.fx.speeds[ options ] ) {
+ callback = speed;
+ speed = options;
+ options = {};
+ }
+
+ // catch (effect, options, callback)
+ if ( $.isFunction( speed ) ) {
+ callback = speed;
+ speed = null;
+ }
+
+ // add options to effect
+ if ( options ) {
+ $.extend( effect, options );
+ }
+
+ speed = speed || options.duration;
+ effect.duration = $.fx.off ? 0 :
+ typeof speed === "number" ? speed :
+ speed in $.fx.speeds ? $.fx.speeds[ speed ] :
+ $.fx.speeds._default;
+
+ effect.complete = callback || options.complete;
+
+ return effect;
+}
+
+function standardAnimationOption( option ) {
+ // Valid standard speeds (nothing, number, named speed)
+ if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
+ return true;
+ }
+
+ // Invalid strings - treat as "normal" speed
+ if ( typeof option === "string" && !$.effects.effect[ option ] ) {
+ return true;
+ }
+
+ // Complete callback
+ if ( $.isFunction( option ) ) {
+ return true;
+ }
+
+ // Options hash (but not naming an effect)
+ if ( typeof option === "object" && !option.effect ) {
+ return true;
+ }
+
+ // Didn't match any standard API
+ return false;
+}
+
+$.fn.extend({
+ effect: function( /* effect, options, speed, callback */ ) {
+ var args = _normalizeArguments.apply( this, arguments ),
+ mode = args.mode,
+ queue = args.queue,
+ effectMethod = $.effects.effect[ args.effect ];
+
+ if ( $.fx.off || !effectMethod ) {
+ // delegate to the original method (e.g., .show()) if possible
+ if ( mode ) {
+ return this[ mode ]( args.duration, args.complete );
+ } else {
+ return this.each( function() {
+ if ( args.complete ) {
+ args.complete.call( this );
+ }
+ });
+ }
+ }
+
+ function run( next ) {
+ var elem = $( this ),
+ complete = args.complete,
+ mode = args.mode;
+
+ function done() {
+ if ( $.isFunction( complete ) ) {
+ complete.call( elem[0] );
+ }
+ if ( $.isFunction( next ) ) {
+ next();
+ }
+ }
+
+ // If the element already has the correct final state, delegate to
+ // the core methods so the internal tracking of "olddisplay" works.
+ if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+ elem[ mode ]();
+ done();
+ } else {
+ effectMethod.call( elem[0], args, done );
+ }
+ }
+
+ return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
+ },
+
+ show: (function( orig ) {
+ return function( option ) {
+ if ( standardAnimationOption( option ) ) {
+ return orig.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "show";
+ return this.effect.call( this, args );
+ }
+ };
+ })( $.fn.show ),
+
+ hide: (function( orig ) {
+ return function( option ) {
+ if ( standardAnimationOption( option ) ) {
+ return orig.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "hide";
+ return this.effect.call( this, args );
+ }
+ };
+ })( $.fn.hide ),
+
+ toggle: (function( orig ) {
+ return function( option ) {
+ if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
+ return orig.apply( this, arguments );
+ } else {
+ var args = _normalizeArguments.apply( this, arguments );
+ args.mode = "toggle";
+ return this.effect.call( this, args );
+ }
+ };
+ })( $.fn.toggle ),
+
+ // helper functions
+ cssUnit: function(key) {
+ var style = this.css( key ),
+ val = [];
+
+ $.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
+ if ( style.indexOf( unit ) > 0 ) {
+ val = [ parseFloat( style ), unit ];
+ }
+ });
+ return val;
+ }
+});
+
+})();
+
+/******************************************************************************/
+/*********************************** EASING ***********************************/
+/******************************************************************************/
+
+(function() {
+
+// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
+
+var baseEasings = {};
+
+$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
+ baseEasings[ name ] = function( p ) {
+ return Math.pow( p, i + 2 );
+ };
+});
+
+$.extend( baseEasings, {
+ Sine: function ( p ) {
+ return 1 - Math.cos( p * Math.PI / 2 );
+ },
+ Circ: function ( p ) {
+ return 1 - Math.sqrt( 1 - p * p );
+ },
+ Elastic: function( p ) {
+ return p === 0 || p === 1 ? p :
+ -Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
+ },
+ Back: function( p ) {
+ return p * p * ( 3 * p - 2 );
+ },
+ Bounce: function ( p ) {
+ var pow2,
+ bounce = 4;
+
+ while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
+ return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
+ }
+});
+
+$.each( baseEasings, function( name, easeIn ) {
+ $.easing[ "easeIn" + name ] = easeIn;
+ $.easing[ "easeOut" + name ] = function( p ) {
+ return 1 - easeIn( 1 - p );
+ };
+ $.easing[ "easeInOut" + name ] = function( p ) {
+ return p < 0.5 ?
+ easeIn( p * 2 ) / 2 :
+ 1 - easeIn( p * -2 + 2 ) / 2;
+ };
+});
+
+})();
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+var uid = 0,
+ hideProps = {},
+ showProps = {};
+
+hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
+ hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
+showProps.height = showProps.paddingTop = showProps.paddingBottom =
+ showProps.borderTopWidth = showProps.borderBottomWidth = "show";
+
+$.widget( "ui.accordion", {
+ version: "1.10.2",
+ options: {
+ active: 0,
+ animate: {},
+ collapsible: false,
+ event: "click",
+ header: "> li > :first-child,> :not(li):even",
+ heightStyle: "auto",
+ icons: {
+ activeHeader: "ui-icon-triangle-1-s",
+ header: "ui-icon-triangle-1-e"
+ },
+
+ // callbacks
+ activate: null,
+ beforeActivate: null
+ },
+
+ _create: function() {
+ var options = this.options;
+ this.prevShow = this.prevHide = $();
+ this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
+ // ARIA
+ .attr( "role", "tablist" );
+
+ // don't allow collapsible: false and active: false / null
+ if ( !options.collapsible && (options.active === false || options.active == null) ) {
+ options.active = 0;
+ }
+
+ this._processPanels();
+ // handle negative values
+ if ( options.active < 0 ) {
+ options.active += this.headers.length;
+ }
+ this._refresh();
+ },
+
+ _getCreateEventData: function() {
+ return {
+ header: this.active,
+ panel: !this.active.length ? $() : this.active.next(),
+ content: !this.active.length ? $() : this.active.next()
+ };
+ },
+
+ _createIcons: function() {
+ var icons = this.options.icons;
+ if ( icons ) {
+ $( "<span>" )
+ .addClass( "ui-accordion-header-icon ui-icon " + icons.header )
+ .prependTo( this.headers );
+ this.active.children( ".ui-accordion-header-icon" )
+ .removeClass( icons.header )
+ .addClass( icons.activeHeader );
+ this.headers.addClass( "ui-accordion-icons" );
+ }
+ },
+
+ _destroyIcons: function() {
+ this.headers
+ .removeClass( "ui-accordion-icons" )
+ .children( ".ui-accordion-header-icon" )
+ .remove();
+ },
+
+ _destroy: function() {
+ var contents;
+
+ // clean up main element
+ this.element
+ .removeClass( "ui-accordion ui-widget ui-helper-reset" )
+ .removeAttr( "role" );
+
+ // clean up headers
+ this.headers
+ .removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-selected" )
+ .removeAttr( "aria-controls" )
+ .removeAttr( "tabIndex" )
+ .each(function() {
+ if ( /^ui-accordion/.test( this.id ) ) {
+ this.removeAttribute( "id" );
+ }
+ });
+ this._destroyIcons();
+
+ // clean up content panels
+ contents = this.headers.next()
+ .css( "display", "" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-labelledby" )
+ .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
+ .each(function() {
+ if ( /^ui-accordion/.test( this.id ) ) {
+ this.removeAttribute( "id" );
+ }
+ });
+ if ( this.options.heightStyle !== "content" ) {
+ contents.css( "height", "" );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "active" ) {
+ // _activate() will handle invalid values and update this.options
+ this._activate( value );
+ return;
+ }
+
+ if ( key === "event" ) {
+ if ( this.options.event ) {
+ this._off( this.headers, this.options.event );
+ }
+ this._setupEvents( value );
+ }
+
+ this._super( key, value );
+
+ // setting collapsible: false while collapsed; open first panel
+ if ( key === "collapsible" && !value && this.options.active === false ) {
+ this._activate( 0 );
+ }
+
+ if ( key === "icons" ) {
+ this._destroyIcons();
+ if ( value ) {
+ this._createIcons();
+ }
+ }
+
+ // #5332 - opacity doesn't cascade to positioned elements in IE
+ // so we need to add the disabled class to the headers and panels
+ if ( key === "disabled" ) {
+ this.headers.add( this.headers.next() )
+ .toggleClass( "ui-state-disabled", !!value );
+ }
+ },
+
+ _keydown: function( event ) {
+ /*jshint maxcomplexity:15*/
+ if ( event.altKey || event.ctrlKey ) {
+ return;
+ }
+
+ var keyCode = $.ui.keyCode,
+ length = this.headers.length,
+ currentIndex = this.headers.index( event.target ),
+ toFocus = false;
+
+ switch ( event.keyCode ) {
+ case keyCode.RIGHT:
+ case keyCode.DOWN:
+ toFocus = this.headers[ ( currentIndex + 1 ) % length ];
+ break;
+ case keyCode.LEFT:
+ case keyCode.UP:
+ toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
+ break;
+ case keyCode.SPACE:
+ case keyCode.ENTER:
+ this._eventHandler( event );
+ break;
+ case keyCode.HOME:
+ toFocus = this.headers[ 0 ];
+ break;
+ case keyCode.END:
+ toFocus = this.headers[ length - 1 ];
+ break;
+ }
+
+ if ( toFocus ) {
+ $( event.target ).attr( "tabIndex", -1 );
+ $( toFocus ).attr( "tabIndex", 0 );
+ toFocus.focus();
+ event.preventDefault();
+ }
+ },
+
+ _panelKeyDown : function( event ) {
+ if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
+ $( event.currentTarget ).prev().focus();
+ }
+ },
+
+ refresh: function() {
+ var options = this.options;
+ this._processPanels();
+
+ // was collapsed or no panel
+ if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
+ options.active = false;
+ this.active = $();
+ // active false only when collapsible is true
+ } if ( options.active === false ) {
+ this._activate( 0 );
+ // was active, but active panel is gone
+ } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+ // all remaining panel are disabled
+ if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
+ options.active = false;
+ this.active = $();
+ // activate previous panel
+ } else {
+ this._activate( Math.max( 0, options.active - 1 ) );
+ }
+ // was active, active panel still exists
+ } else {
+ // make sure active index is correct
+ options.active = this.headers.index( this.active );
+ }
+
+ this._destroyIcons();
+
+ this._refresh();
+ },
+
+ _processPanels: function() {
+ this.headers = this.element.find( this.options.header )
+ .addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
+
+ this.headers.next()
+ .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
+ .filter(":not(.ui-accordion-content-active)")
+ .hide();
+ },
+
+ _refresh: function() {
+ var maxHeight,
+ options = this.options,
+ heightStyle = options.heightStyle,
+ parent = this.element.parent(),
+ accordionId = this.accordionId = "ui-accordion-" +
+ (this.element.attr( "id" ) || ++uid);
+
+ this.active = this._findActive( options.active )
+ .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
+ .removeClass( "ui-corner-all" );
+ this.active.next()
+ .addClass( "ui-accordion-content-active" )
+ .show();
+
+ this.headers
+ .attr( "role", "tab" )
+ .each(function( i ) {
+ var header = $( this ),
+ headerId = header.attr( "id" ),
+ panel = header.next(),
+ panelId = panel.attr( "id" );
+ if ( !headerId ) {
+ headerId = accordionId + "-header-" + i;
+ header.attr( "id", headerId );
+ }
+ if ( !panelId ) {
+ panelId = accordionId + "-panel-" + i;
+ panel.attr( "id", panelId );
+ }
+ header.attr( "aria-controls", panelId );
+ panel.attr( "aria-labelledby", headerId );
+ })
+ .next()
+ .attr( "role", "tabpanel" );
+
+ this.headers
+ .not( this.active )
+ .attr({
+ "aria-selected": "false",
+ tabIndex: -1
+ })
+ .next()
+ .attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ })
+ .hide();
+
+ // make sure at least one header is in the tab order
+ if ( !this.active.length ) {
+ this.headers.eq( 0 ).attr( "tabIndex", 0 );
+ } else {
+ this.active.attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ })
+ .next()
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ }
+
+ this._createIcons();
+
+ this._setupEvents( options.event );
+
+ if ( heightStyle === "fill" ) {
+ maxHeight = parent.height();
+ this.element.siblings( ":visible" ).each(function() {
+ var elem = $( this ),
+ position = elem.css( "position" );
+
+ if ( position === "absolute" || position === "fixed" ) {
+ return;
+ }
+ maxHeight -= elem.outerHeight( true );
+ });
+
+ this.headers.each(function() {
+ maxHeight -= $( this ).outerHeight( true );
+ });
+
+ this.headers.next()
+ .each(function() {
+ $( this ).height( Math.max( 0, maxHeight -
+ $( this ).innerHeight() + $( this ).height() ) );
+ })
+ .css( "overflow", "auto" );
+ } else if ( heightStyle === "auto" ) {
+ maxHeight = 0;
+ this.headers.next()
+ .each(function() {
+ maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
+ })
+ .height( maxHeight );
+ }
+ },
+
+ _activate: function( index ) {
+ var active = this._findActive( index )[ 0 ];
+
+ // trying to activate the already active panel
+ if ( active === this.active[ 0 ] ) {
+ return;
+ }
+
+ // trying to collapse, simulate a click on the currently active header
+ active = active || this.active[ 0 ];
+
+ this._eventHandler({
+ target: active,
+ currentTarget: active,
+ preventDefault: $.noop
+ });
+ },
+
+ _findActive: function( selector ) {
+ return typeof selector === "number" ? this.headers.eq( selector ) : $();
+ },
+
+ _setupEvents: function( event ) {
+ var events = {
+ keydown: "_keydown"
+ };
+ if ( event ) {
+ $.each( event.split(" "), function( index, eventName ) {
+ events[ eventName ] = "_eventHandler";
+ });
+ }
+
+ this._off( this.headers.add( this.headers.next() ) );
+ this._on( this.headers, events );
+ this._on( this.headers.next(), { keydown: "_panelKeyDown" });
+ this._hoverable( this.headers );
+ this._focusable( this.headers );
+ },
+
+ _eventHandler: function( event ) {
+ var options = this.options,
+ active = this.active,
+ clicked = $( event.currentTarget ),
+ clickedIsActive = clicked[ 0 ] === active[ 0 ],
+ collapsing = clickedIsActive && options.collapsible,
+ toShow = collapsing ? $() : clicked.next(),
+ toHide = active.next(),
+ eventData = {
+ oldHeader: active,
+ oldPanel: toHide,
+ newHeader: collapsing ? $() : clicked,
+ newPanel: toShow
+ };
+
+ event.preventDefault();
+
+ if (
+ // click on active header, but not collapsible
+ ( clickedIsActive && !options.collapsible ) ||
+ // allow canceling activation
+ ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+ return;
+ }
+
+ options.active = collapsing ? false : this.headers.index( clicked );
+
+ // when the call to ._toggle() comes after the class changes
+ // it causes a very odd bug in IE 8 (see #6720)
+ this.active = clickedIsActive ? $() : clicked;
+ this._toggle( eventData );
+
+ // switch classes
+ // corner classes on the previously active header stay after the animation
+ active.removeClass( "ui-accordion-header-active ui-state-active" );
+ if ( options.icons ) {
+ active.children( ".ui-accordion-header-icon" )
+ .removeClass( options.icons.activeHeader )
+ .addClass( options.icons.header );
+ }
+
+ if ( !clickedIsActive ) {
+ clicked
+ .removeClass( "ui-corner-all" )
+ .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
+ if ( options.icons ) {
+ clicked.children( ".ui-accordion-header-icon" )
+ .removeClass( options.icons.header )
+ .addClass( options.icons.activeHeader );
+ }
+
+ clicked
+ .next()
+ .addClass( "ui-accordion-content-active" );
+ }
+ },
+
+ _toggle: function( data ) {
+ var toShow = data.newPanel,
+ toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
+
+ // handle activating a panel during the animation for another activation
+ this.prevShow.add( this.prevHide ).stop( true, true );
+ this.prevShow = toShow;
+ this.prevHide = toHide;
+
+ if ( this.options.animate ) {
+ this._animate( toShow, toHide, data );
+ } else {
+ toHide.hide();
+ toShow.show();
+ this._toggleComplete( data );
+ }
+
+ toHide.attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+ toHide.prev().attr( "aria-selected", "false" );
+ // if we're switching panels, remove the old header from the tab order
+ // if we're opening from collapsed state, remove the previous header from the tab order
+ // if we're collapsing, then keep the collapsing header in the tab order
+ if ( toShow.length && toHide.length ) {
+ toHide.prev().attr( "tabIndex", -1 );
+ } else if ( toShow.length ) {
+ this.headers.filter(function() {
+ return $( this ).attr( "tabIndex" ) === 0;
+ })
+ .attr( "tabIndex", -1 );
+ }
+
+ toShow
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ })
+ .prev()
+ .attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ },
+
+ _animate: function( toShow, toHide, data ) {
+ var total, easing, duration,
+ that = this,
+ adjust = 0,
+ down = toShow.length &&
+ ( !toHide.length || ( toShow.index() < toHide.index() ) ),
+ animate = this.options.animate || {},
+ options = down && animate.down || animate,
+ complete = function() {
+ that._toggleComplete( data );
+ };
+
+ if ( typeof options === "number" ) {
+ duration = options;
+ }
+ if ( typeof options === "string" ) {
+ easing = options;
+ }
+ // fall back from options to animation in case of partial down settings
+ easing = easing || options.easing || animate.easing;
+ duration = duration || options.duration || animate.duration;
+
+ if ( !toHide.length ) {
+ return toShow.animate( showProps, duration, easing, complete );
+ }
+ if ( !toShow.length ) {
+ return toHide.animate( hideProps, duration, easing, complete );
+ }
+
+ total = toShow.show().outerHeight();
+ toHide.animate( hideProps, {
+ duration: duration,
+ easing: easing,
+ step: function( now, fx ) {
+ fx.now = Math.round( now );
+ }
+ });
+ toShow
+ .hide()
+ .animate( showProps, {
+ duration: duration,
+ easing: easing,
+ complete: complete,
+ step: function( now, fx ) {
+ fx.now = Math.round( now );
+ if ( fx.prop !== "height" ) {
+ adjust += fx.now;
+ } else if ( that.options.heightStyle !== "content" ) {
+ fx.now = Math.round( total - toHide.outerHeight() - adjust );
+ adjust = 0;
+ }
+ }
+ });
+ },
+
+ _toggleComplete: function( data ) {
+ var toHide = data.oldPanel;
+
+ toHide
+ .removeClass( "ui-accordion-content-active" )
+ .prev()
+ .removeClass( "ui-corner-top" )
+ .addClass( "ui-corner-all" );
+
+ // Work around for rendering bug in IE (#5421)
+ if ( toHide.length ) {
+ toHide.parent()[0].className = toHide.parent()[0].className;
+ }
+
+ this._trigger( "activate", null, data );
+ }
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+// used to prevent race conditions with remote data sources
+var requestIndex = 0;
+
+$.widget( "ui.autocomplete", {
+ version: "1.10.2",
+ defaultElement: "<input>",
+ options: {
+ appendTo: null,
+ autoFocus: false,
+ delay: 300,
+ minLength: 1,
+ position: {
+ my: "left top",
+ at: "left bottom",
+ collision: "none"
+ },
+ source: null,
+
+ // callbacks
+ change: null,
+ close: null,
+ focus: null,
+ open: null,
+ response: null,
+ search: null,
+ select: null
+ },
+
+ pending: 0,
+
+ _create: function() {
+ // Some browsers only repeat keydown events, not keypress events,
+ // so we use the suppressKeyPress flag to determine if we've already
+ // handled the keydown event. #7269
+ // Unfortunately the code for & in keypress is the same as the up arrow,
+ // so we use the suppressKeyPressRepeat flag to avoid handling keypress
+ // events when we know the keydown event was used to modify the
+ // search term. #7799
+ var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
+ nodeName = this.element[0].nodeName.toLowerCase(),
+ isTextarea = nodeName === "textarea",
+ isInput = nodeName === "input";
+
+ this.isMultiLine =
+ // Textareas are always multi-line
+ isTextarea ? true :
+ // Inputs are always single-line, even if inside a contentEditable element
+ // IE also treats inputs as contentEditable
+ isInput ? false :
+ // All other element types are determined by whether or not they're contentEditable
+ this.element.prop( "isContentEditable" );
+
+ this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
+ this.isNewMenu = true;
+
+ this.element
+ .addClass( "ui-autocomplete-input" )
+ .attr( "autocomplete", "off" );
+
+ this._on( this.element, {
+ keydown: function( event ) {
+ /*jshint maxcomplexity:15*/
+ if ( this.element.prop( "readOnly" ) ) {
+ suppressKeyPress = true;
+ suppressInput = true;
+ suppressKeyPressRepeat = true;
+ return;
+ }
+
+ suppressKeyPress = false;
+ suppressInput = false;
+ suppressKeyPressRepeat = false;
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ suppressKeyPress = true;
+ this._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ suppressKeyPress = true;
+ this._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ suppressKeyPress = true;
+ this._keyEvent( "previous", event );
+ break;
+ case keyCode.DOWN:
+ suppressKeyPress = true;
+ this._keyEvent( "next", event );
+ break;
+ case keyCode.ENTER:
+ case keyCode.NUMPAD_ENTER:
+ // when menu is open and has focus
+ if ( this.menu.active ) {
+ // #6055 - Opera still allows the keypress to occur
+ // which causes forms to submit
+ suppressKeyPress = true;
+ event.preventDefault();
+ this.menu.select( event );
+ }
+ break;
+ case keyCode.TAB:
+ if ( this.menu.active ) {
+ this.menu.select( event );
+ }
+ break;
+ case keyCode.ESCAPE:
+ if ( this.menu.element.is( ":visible" ) ) {
+ this._value( this.term );
+ this.close( event );
+ // Different browsers have different default behavior for escape
+ // Single press can mean undo or clear
+ // Double press in IE means clear the whole form
+ event.preventDefault();
+ }
+ break;
+ default:
+ suppressKeyPressRepeat = true;
+ // search timeout should be triggered before the input value is changed
+ this._searchTimeout( event );
+ break;
+ }
+ },
+ keypress: function( event ) {
+ if ( suppressKeyPress ) {
+ suppressKeyPress = false;
+ event.preventDefault();
+ return;
+ }
+ if ( suppressKeyPressRepeat ) {
+ return;
+ }
+
+ // replicate some key handlers to allow them to repeat in Firefox and Opera
+ var keyCode = $.ui.keyCode;
+ switch( event.keyCode ) {
+ case keyCode.PAGE_UP:
+ this._move( "previousPage", event );
+ break;
+ case keyCode.PAGE_DOWN:
+ this._move( "nextPage", event );
+ break;
+ case keyCode.UP:
+ this._keyEvent( "previous", event );
+ break;
+ case keyCode.DOWN:
+ this._keyEvent( "next", event );
+ break;
+ }
+ },
+ input: function( event ) {
+ if ( suppressInput ) {
+ suppressInput = false;
+ event.preventDefault();
+ return;
+ }
+ this._searchTimeout( event );
+ },
+ focus: function() {
+ this.selectedItem = null;
+ this.previous = this._value();
+ },
+ blur: function( event ) {
+ if ( this.cancelBlur ) {
+ delete this.cancelBlur;
+ return;
+ }
+
+ clearTimeout( this.searching );
+ this.close( event );
+ this._change( event );
+ }
+ });
+
+ this._initSource();
+ this.menu = $( "<ul>" )
+ .addClass( "ui-autocomplete ui-front" )
+ .appendTo( this._appendTo() )
+ .menu({
+ // custom key handling for now
+ input: $(),
+ // disable ARIA support, the live region takes care of that
+ role: null
+ })
+ .hide()
+ .data( "ui-menu" );
+
+ this._on( this.menu.element, {
+ mousedown: function( event ) {
+ // prevent moving focus out of the text field
+ event.preventDefault();
+
+ // IE doesn't prevent moving focus even with event.preventDefault()
+ // so we set a flag to know when we should ignore the blur event
+ this.cancelBlur = true;
+ this._delay(function() {
+ delete this.cancelBlur;
+ });
+
+ // clicking on the scrollbar causes focus to shift to the body
+ // but we can't detect a mouseup or a click immediately afterward
+ // so we have to track the next mousedown and close the menu if
+ // the user clicks somewhere outside of the autocomplete
+ var menuElement = this.menu.element[ 0 ];
+ if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
+ this._delay(function() {
+ var that = this;
+ this.document.one( "mousedown", function( event ) {
+ if ( event.target !== that.element[ 0 ] &&
+ event.target !== menuElement &&
+ !$.contains( menuElement, event.target ) ) {
+ that.close();
+ }
+ });
+ });
+ }
+ },
+ menufocus: function( event, ui ) {
+ // support: Firefox
+ // Prevent accidental activation of menu items in Firefox (#7024 #9118)
+ if ( this.isNewMenu ) {
+ this.isNewMenu = false;
+ if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
+ this.menu.blur();
+
+ this.document.one( "mousemove", function() {
+ $( event.target ).trigger( event.originalEvent );
+ });
+
+ return;
+ }
+ }
+
+ var item = ui.item.data( "ui-autocomplete-item" );
+ if ( false !== this._trigger( "focus", event, { item: item } ) ) {
+ // use value to match what will end up in the input, if it was a key event
+ if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
+ this._value( item.value );
+ }
+ } else {
+ // Normally the input is populated with the item's value as the
+ // menu is navigated, causing screen readers to notice a change and
+ // announce the item. Since the focus event was canceled, this doesn't
+ // happen, so we update the live region so that screen readers can
+ // still notice the change and announce it.
+ this.liveRegion.text( item.value );
+ }
+ },
+ menuselect: function( event, ui ) {
+ var item = ui.item.data( "ui-autocomplete-item" ),
+ previous = this.previous;
+
+ // only trigger when focus was lost (click on menu)
+ if ( this.element[0] !== this.document[0].activeElement ) {
+ this.element.focus();
+ this.previous = previous;
+ // #6109 - IE triggers two focus events and the second
+ // is asynchronous, so we need to reset the previous
+ // term synchronously and asynchronously :-(
+ this._delay(function() {
+ this.previous = previous;
+ this.selectedItem = item;
+ });
+ }
+
+ if ( false !== this._trigger( "select", event, { item: item } ) ) {
+ this._value( item.value );
+ }
+ // reset the term after the select event
+ // this allows custom select handling to work properly
+ this.term = this._value();
+
+ this.close( event );
+ this.selectedItem = item;
+ }
+ });
+
+ this.liveRegion = $( "<span>", {
+ role: "status",
+ "aria-live": "polite"
+ })
+ .addClass( "ui-helper-hidden-accessible" )
+ .insertAfter( this.element );
+
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ this._on( this.window, {
+ beforeunload: function() {
+ this.element.removeAttr( "autocomplete" );
+ }
+ });
+ },
+
+ _destroy: function() {
+ clearTimeout( this.searching );
+ this.element
+ .removeClass( "ui-autocomplete-input" )
+ .removeAttr( "autocomplete" );
+ this.menu.element.remove();
+ this.liveRegion.remove();
+ },
+
+ _setOption: function( key, value ) {
+ this._super( key, value );
+ if ( key === "source" ) {
+ this._initSource();
+ }
+ if ( key === "appendTo" ) {
+ this.menu.element.appendTo( this._appendTo() );
+ }
+ if ( key === "disabled" && value && this.xhr ) {
+ this.xhr.abort();
+ }
+ },
+
+ _appendTo: function() {
+ var element = this.options.appendTo;
+
+ if ( element ) {
+ element = element.jquery || element.nodeType ?
+ $( element ) :
+ this.document.find( element ).eq( 0 );
+ }
+
+ if ( !element ) {
+ element = this.element.closest( ".ui-front" );
+ }
+
+ if ( !element.length ) {
+ element = this.document[0].body;
+ }
+
+ return element;
+ },
+
+ _initSource: function() {
+ var array, url,
+ that = this;
+ if ( $.isArray(this.options.source) ) {
+ array = this.options.source;
+ this.source = function( request, response ) {
+ response( $.ui.autocomplete.filter( array, request.term ) );
+ };
+ } else if ( typeof this.options.source === "string" ) {
+ url = this.options.source;
+ this.source = function( request, response ) {
+ if ( that.xhr ) {
+ that.xhr.abort();
+ }
+ that.xhr = $.ajax({
+ url: url,
+ data: request,
+ dataType: "json",
+ success: function( data ) {
+ response( data );
+ },
+ error: function() {
+ response( [] );
+ }
+ });
+ };
+ } else {
+ this.source = this.options.source;
+ }
+ },
+
+ _searchTimeout: function( event ) {
+ clearTimeout( this.searching );
+ this.searching = this._delay(function() {
+ // only search if the value has changed
+ if ( this.term !== this._value() ) {
+ this.selectedItem = null;
+ this.search( null, event );
+ }
+ }, this.options.delay );
+ },
+
+ search: function( value, event ) {
+ value = value != null ? value : this._value();
+
+ // always save the actual value, not the one passed as an argument
+ this.term = this._value();
+
+ if ( value.length < this.options.minLength ) {
+ return this.close( event );
+ }
+
+ if ( this._trigger( "search", event ) === false ) {
+ return;
+ }
+
+ return this._search( value );
+ },
+
+ _search: function( value ) {
+ this.pending++;
+ this.element.addClass( "ui-autocomplete-loading" );
+ this.cancelSearch = false;
+
+ this.source( { term: value }, this._response() );
+ },
+
+ _response: function() {
+ var that = this,
+ index = ++requestIndex;
+
+ return function( content ) {
+ if ( index === requestIndex ) {
+ that.__response( content );
+ }
+
+ that.pending--;
+ if ( !that.pending ) {
+ that.element.removeClass( "ui-autocomplete-loading" );
+ }
+ };
+ },
+
+ __response: function( content ) {
+ if ( content ) {
+ content = this._normalize( content );
+ }
+ this._trigger( "response", null, { content: content } );
+ if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
+ this._suggest( content );
+ this._trigger( "open" );
+ } else {
+ // use ._close() instead of .close() so we don't cancel future searches
+ this._close();
+ }
+ },
+
+ close: function( event ) {
+ this.cancelSearch = true;
+ this._close( event );
+ },
+
+ _close: function( event ) {
+ if ( this.menu.element.is( ":visible" ) ) {
+ this.menu.element.hide();
+ this.menu.blur();
+ this.isNewMenu = true;
+ this._trigger( "close", event );
+ }
+ },
+
+ _change: function( event ) {
+ if ( this.previous !== this._value() ) {
+ this._trigger( "change", event, { item: this.selectedItem } );
+ }
+ },
+
+ _normalize: function( items ) {
+ // assume all items have the right format when the first item is complete
+ if ( items.length && items[0].label && items[0].value ) {
+ return items;
+ }
+ return $.map( items, function( item ) {
+ if ( typeof item === "string" ) {
+ return {
+ label: item,
+ value: item
+ };
+ }
+ return $.extend({
+ label: item.label || item.value,
+ value: item.value || item.label
+ }, item );
+ });
+ },
+
+ _suggest: function( items ) {
+ var ul = this.menu.element.empty();
+ this._renderMenu( ul, items );
+ this.isNewMenu = true;
+ this.menu.refresh();
+
+ // size and position menu
+ ul.show();
+ this._resizeMenu();
+ ul.position( $.extend({
+ of: this.element
+ }, this.options.position ));
+
+ if ( this.options.autoFocus ) {
+ this.menu.next();
+ }
+ },
+
+ _resizeMenu: function() {
+ var ul = this.menu.element;
+ ul.outerWidth( Math.max(
+ // Firefox wraps long text (possibly a rounding bug)
+ // so we add 1px to avoid the wrapping (#7513)
+ ul.width( "" ).outerWidth() + 1,
+ this.element.outerWidth()
+ ) );
+ },
+
+ _renderMenu: function( ul, items ) {
+ var that = this;
+ $.each( items, function( index, item ) {
+ that._renderItemData( ul, item );
+ });
+ },
+
+ _renderItemData: function( ul, item ) {
+ return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
+ },
+
+ _renderItem: function( ul, item ) {
+ return $( "<li>" )
+ .append( $( "<a>" ).text( item.label ) )
+ .appendTo( ul );
+ },
+
+ _move: function( direction, event ) {
+ if ( !this.menu.element.is( ":visible" ) ) {
+ this.search( null, event );
+ return;
+ }
+ if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
+ this.menu.isLastItem() && /^next/.test( direction ) ) {
+ this._value( this.term );
+ this.menu.blur();
+ return;
+ }
+ this.menu[ direction ]( event );
+ },
+
+ widget: function() {
+ return this.menu.element;
+ },
+
+ _value: function() {
+ return this.valueMethod.apply( this.element, arguments );
+ },
+
+ _keyEvent: function( keyEvent, event ) {
+ if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+ this._move( keyEvent, event );
+
+ // prevents moving cursor to beginning/end of the text field in some browsers
+ event.preventDefault();
+ }
+ }
+});
+
+$.extend( $.ui.autocomplete, {
+ escapeRegex: function( value ) {
+ return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
+ },
+ filter: function(array, term) {
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
+ return $.grep( array, function(value) {
+ return matcher.test( value.label || value.value || value );
+ });
+ }
+});
+
+
+// live region extension, adding a `messages` option
+// NOTE: This is an experimental API. We are still investigating
+// a full solution for string manipulation and internationalization.
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+ options: {
+ messages: {
+ noResults: "No search results.",
+ results: function( amount ) {
+ return amount + ( amount > 1 ? " results are" : " result is" ) +
+ " available, use up and down arrow keys to navigate.";
+ }
+ }
+ },
+
+ __response: function( content ) {
+ var message;
+ this._superApply( arguments );
+ if ( this.options.disabled || this.cancelSearch ) {
+ return;
+ }
+ if ( content && content.length ) {
+ message = this.options.messages.results( content.length );
+ } else {
+ message = this.options.messages.noResults;
+ }
+ this.liveRegion.text( message );
+ }
+});
+
+}( jQuery ));
+
+(function( $, undefined ) {
+
+var lastActive, startXPos, startYPos, clickDragged,
+ baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
+ stateClasses = "ui-state-hover ui-state-active ",
+ typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
+ formResetHandler = function() {
+ var buttons = $( this ).find( ":ui-button" );
+ setTimeout(function() {
+ buttons.button( "refresh" );
+ }, 1 );
+ },
+ radioGroup = function( radio ) {
+ var name = radio.name,
+ form = radio.form,
+ radios = $( [] );
+ if ( name ) {
+ name = name.replace( /'/g, "\\'" );
+ if ( form ) {
+ radios = $( form ).find( "[name='" + name + "']" );
+ } else {
+ radios = $( "[name='" + name + "']", radio.ownerDocument )
+ .filter(function() {
+ return !this.form;
+ });
+ }
+ }
+ return radios;
+ };
+
+$.widget( "ui.button", {
+ version: "1.10.2",
+ defaultElement: "<button>",
+ options: {
+ disabled: null,
+ text: true,
+ label: null,
+ icons: {
+ primary: null,
+ secondary: null
+ }
+ },
+ _create: function() {
+ this.element.closest( "form" )
+ .unbind( "reset" + this.eventNamespace )
+ .bind( "reset" + this.eventNamespace, formResetHandler );
+
+ if ( typeof this.options.disabled !== "boolean" ) {
+ this.options.disabled = !!this.element.prop( "disabled" );
+ } else {
+ this.element.prop( "disabled", this.options.disabled );
+ }
+
+ this._determineButtonType();
+ this.hasTitle = !!this.buttonElement.attr( "title" );
+
+ var that = this,
+ options = this.options,
+ toggleButton = this.type === "checkbox" || this.type === "radio",
+ activeClass = !toggleButton ? "ui-state-active" : "",
+ focusClass = "ui-state-focus";
+
+ if ( options.label === null ) {
+ options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
+ }
+
+ this._hoverable( this.buttonElement );
+
+ this.buttonElement
+ .addClass( baseClasses )
+ .attr( "role", "button" )
+ .bind( "mouseenter" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return;
+ }
+ if ( this === lastActive ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ .bind( "mouseleave" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return;
+ }
+ $( this ).removeClass( activeClass );
+ })
+ .bind( "click" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+ });
+
+ this.element
+ .bind( "focus" + this.eventNamespace, function() {
+ // no need to check disabled, focus won't be triggered anyway
+ that.buttonElement.addClass( focusClass );
+ })
+ .bind( "blur" + this.eventNamespace, function() {
+ that.buttonElement.removeClass( focusClass );
+ });
+
+ if ( toggleButton ) {
+ this.element.bind( "change" + this.eventNamespace, function() {
+ if ( clickDragged ) {
+ return;
+ }
+ that.refresh();
+ });
+ // if mouse moves between mousedown and mouseup (drag) set clickDragged flag
+ // prevents issue where button state changes but checkbox/radio checked state
+ // does not in Firefox (see ticket #6970)
+ this.buttonElement
+ .bind( "mousedown" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ clickDragged = false;
+ startXPos = event.pageX;
+ startYPos = event.pageY;
+ })
+ .bind( "mouseup" + this.eventNamespace, function( event ) {
+ if ( options.disabled ) {
+ return;
+ }
+ if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
+ clickDragged = true;
+ }
+ });
+ }
+
+ if ( this.type === "checkbox" ) {
+ this.buttonElement.bind( "click" + this.eventNamespace, function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ });
+ } else if ( this.type === "radio" ) {
+ this.buttonElement.bind( "click" + this.eventNamespace, function() {
+ if ( options.disabled || clickDragged ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ that.buttonElement.attr( "aria-pressed", "true" );
+
+ var radio = that.element[ 0 ];
+ radioGroup( radio )
+ .not( radio )
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ });
+ } else {
+ this.buttonElement
+ .bind( "mousedown" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).addClass( "ui-state-active" );
+ lastActive = this;
+ that.document.one( "mouseup", function() {
+ lastActive = null;
+ });
+ })
+ .bind( "mouseup" + this.eventNamespace, function() {
+ if ( options.disabled ) {
+ return false;
+ }
+ $( this ).removeClass( "ui-state-active" );
+ })
+ .bind( "keydown" + this.eventNamespace, function(event) {
+ if ( options.disabled ) {
+ return false;
+ }
+ if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
+ $( this ).addClass( "ui-state-active" );
+ }
+ })
+ // see #8559, we bind to blur here in case the button element loses
+ // focus between keydown and keyup, it would be left in an "active" state
+ .bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() {
+ $( this ).removeClass( "ui-state-active" );
+ });
+
+ if ( this.buttonElement.is("a") ) {
+ this.buttonElement.keyup(function(event) {
+ if ( event.keyCode === $.ui.keyCode.SPACE ) {
+ // TODO pass through original event correctly (just as 2nd argument doesn't work)
+ $( this ).click();
+ }
+ });
+ }
+ }
+
+ // TODO: pull out $.Widget's handling for the disabled option into
+ // $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
+ // be overridden by individual plugins
+ this._setOption( "disabled", options.disabled );
+ this._resetButton();
+ },
+
+ _determineButtonType: function() {
+ var ancestor, labelSelector, checked;
+
+ if ( this.element.is("[type=checkbox]") ) {
+ this.type = "checkbox";
+ } else if ( this.element.is("[type=radio]") ) {
+ this.type = "radio";
+ } else if ( this.element.is("input") ) {
+ this.type = "input";
+ } else {
+ this.type = "button";
+ }
+
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ // we don't search against the document in case the element
+ // is disconnected from the DOM
+ ancestor = this.element.parents().last();
+ labelSelector = "label[for='" + this.element.attr("id") + "']";
+ this.buttonElement = ancestor.find( labelSelector );
+ if ( !this.buttonElement.length ) {
+ ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
+ this.buttonElement = ancestor.filter( labelSelector );
+ if ( !this.buttonElement.length ) {
+ this.buttonElement = ancestor.find( labelSelector );
+ }
+ }
+ this.element.addClass( "ui-helper-hidden-accessible" );
+
+ checked = this.element.is( ":checked" );
+ if ( checked ) {
+ this.buttonElement.addClass( "ui-state-active" );
+ }
+ this.buttonElement.prop( "aria-pressed", checked );
+ } else {
+ this.buttonElement = this.element;
+ }
+ },
+
+ widget: function() {
+ return this.buttonElement;
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-helper-hidden-accessible" );
+ this.buttonElement
+ .removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
+ .removeAttr( "role" )
+ .removeAttr( "aria-pressed" )
+ .html( this.buttonElement.find(".ui-button-text").html() );
+
+ if ( !this.hasTitle ) {
+ this.buttonElement.removeAttr( "title" );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ this._super( key, value );
+ if ( key === "disabled" ) {
+ if ( value ) {
+ this.element.prop( "disabled", true );
+ } else {
+ this.element.prop( "disabled", false );
+ }
+ return;
+ }
+ this._resetButton();
+ },
+
+ refresh: function() {
+ //See #8237 & #8828
+ var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );
+
+ if ( isDisabled !== this.options.disabled ) {
+ this._setOption( "disabled", isDisabled );
+ }
+ if ( this.type === "radio" ) {
+ radioGroup( this.element[0] ).each(function() {
+ if ( $( this ).is( ":checked" ) ) {
+ $( this ).button( "widget" )
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ $( this ).button( "widget" )
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ });
+ } else if ( this.type === "checkbox" ) {
+ if ( this.element.is( ":checked" ) ) {
+ this.buttonElement
+ .addClass( "ui-state-active" )
+ .attr( "aria-pressed", "true" );
+ } else {
+ this.buttonElement
+ .removeClass( "ui-state-active" )
+ .attr( "aria-pressed", "false" );
+ }
+ }
+ },
+
+ _resetButton: function() {
+ if ( this.type === "input" ) {
+ if ( this.options.label ) {
+ this.element.val( this.options.label );
+ }
+ return;
+ }
+ var buttonElement = this.buttonElement.removeClass( typeClasses ),
+ buttonText = $( "<span></span>", this.document[0] )
+ .addClass( "ui-button-text" )
+ .html( this.options.label )
+ .appendTo( buttonElement.empty() )
+ .text(),
+ icons = this.options.icons,
+ multipleIcons = icons.primary && icons.secondary,
+ buttonClasses = [];
+
+ if ( icons.primary || icons.secondary ) {
+ if ( this.options.text ) {
+ buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
+ }
+
+ if ( icons.primary ) {
+ buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
+ }
+
+ if ( icons.secondary ) {
+ buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
+ }
+
+ if ( !this.options.text ) {
+ buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
+
+ if ( !this.hasTitle ) {
+ buttonElement.attr( "title", $.trim( buttonText ) );
+ }
+ }
+ } else {
+ buttonClasses.push( "ui-button-text-only" );
+ }
+ buttonElement.addClass( buttonClasses.join( " " ) );
+ }
+});
+
+$.widget( "ui.buttonset", {
+ version: "1.10.2",
+ options: {
+ items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"
+ },
+
+ _create: function() {
+ this.element.addClass( "ui-buttonset" );
+ },
+
+ _init: function() {
+ this.refresh();
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "disabled" ) {
+ this.buttons.button( "option", key, value );
+ }
+
+ this._super( key, value );
+ },
+
+ refresh: function() {
+ var rtl = this.element.css( "direction" ) === "rtl";
+
+ this.buttons = this.element.find( this.options.items )
+ .filter( ":ui-button" )
+ .button( "refresh" )
+ .end()
+ .not( ":ui-button" )
+ .button()
+ .end()
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
+ .filter( ":first" )
+ .addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
+ .end()
+ .filter( ":last" )
+ .addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
+ .end()
+ .end();
+ },
+
+ _destroy: function() {
+ this.element.removeClass( "ui-buttonset" );
+ this.buttons
+ .map(function() {
+ return $( this ).button( "widget" )[ 0 ];
+ })
+ .removeClass( "ui-corner-left ui-corner-right" )
+ .end()
+ .button( "destroy" );
+ }
+});
+
+}( jQuery ) );
+
+(function( $, undefined ) {
+
+$.extend($.ui, { datepicker: { version: "1.10.2" } });
+
+var PROP_NAME = "datepicker",
+ dpuuid = new Date().getTime(),
+ instActive;
+
+/* Date picker manager.
+ Use the singleton instance of this class, $.datepicker, to interact with the date picker.
+ Settings for (groups of) date pickers are maintained in an instance object,
+ allowing multiple different settings on the same page. */
+
+function Datepicker() {
+ this._curInst = null; // The current instance in use
+ this._keyEvent = false; // If the last event was a key event
+ this._disabledInputs = []; // List of date picker inputs that have been disabled
+ this._datepickerShowing = false; // True if the popup picker is showing , false if not
+ this._inDialog = false; // True if showing within a "dialog", false if not
+ this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
+ this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
+ this._appendClass = "ui-datepicker-append"; // The name of the append marker class
+ this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
+ this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
+ this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
+ this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
+ this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
+ this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
+ this.regional = []; // Available regional settings, indexed by language code
+ this.regional[""] = { // Default regional settings
+ closeText: "Done", // Display text for close link
+ prevText: "Prev", // Display text for previous month link
+ nextText: "Next", // Display text for next month link
+ currentText: "Today", // Display text for current month link
+ monthNames: ["January","February","March","April","May","June",
+ "July","August","September","October","November","December"], // Names of months for drop-down and formatting
+ monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting
+ dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting
+ dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting
+ dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday
+ weekHeader: "Wk", // Column header for week of the year
+ dateFormat: "mm/dd/yy", // See format options on parseDate
+ firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
+ isRTL: false, // True if right-to-left language, false if left-to-right
+ showMonthAfterYear: false, // True if the year select precedes month, false for month then year
+ yearSuffix: "" // Additional text to append to the year in the month headers
+ };
+ this._defaults = { // Global defaults for all the date picker instances
+ showOn: "focus", // "focus" for popup on focus,
+ // "button" for trigger button, or "both" for either
+ showAnim: "fadeIn", // Name of jQuery animation for popup
+ showOptions: {}, // Options for enhanced animations
+ defaultDate: null, // Used when field is blank: actual date,
+ // +/-number for offset from today, null for today
+ appendText: "", // Display text following the input box, e.g. showing the format
+ buttonText: "...", // Text for trigger button
+ buttonImage: "", // URL for trigger button image
+ buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
+ hideIfNoPrevNext: false, // True to hide next/previous month links
+ // if not applicable, false to just disable them
+ navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
+ gotoCurrent: false, // True if today link goes back to current selection instead
+ changeMonth: false, // True if month can be selected directly, false if only prev/next
+ changeYear: false, // True if year can be selected directly, false if only prev/next
+ yearRange: "c-10:c+10", // Range of years to display in drop-down,
+ // either relative to today's year (-nn:+nn), relative to currently displayed year
+ // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
+ showOtherMonths: false, // True to show dates in other months, false to leave blank
+ selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
+ showWeek: false, // True to show week of the year, false to not show it
+ calculateWeek: this.iso8601Week, // How to calculate the week of the year,
+ // takes a Date and returns the number of the week for it
+ shortYearCutoff: "+10", // Short year values < this are in the current century,
+ // > this are in the previous century,
+ // string value starting with "+" for current year + value
+ minDate: null, // The earliest selectable date, or null for no limit
+ maxDate: null, // The latest selectable date, or null for no limit
+ duration: "fast", // Duration of display/closure
+ beforeShowDay: null, // Function that takes a date and returns an array with
+ // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
+ // [2] = cell title (optional), e.g. $.datepicker.noWeekends
+ beforeShow: null, // Function that takes an input field and
+ // returns a set of custom settings for the date picker
+ onSelect: null, // Define a callback function when a date is selected
+ onChangeMonthYear: null, // Define a callback function when the month or year is changed
+ onClose: null, // Define a callback function when the datepicker is closed
+ numberOfMonths: 1, // Number of months to show at a time
+ showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
+ stepMonths: 1, // Number of months to step back/forward
+ stepBigMonths: 12, // Number of months to step back/forward for the big links
+ altField: "", // Selector for an alternate field to store selected dates into
+ altFormat: "", // The date format to use for the alternate field
+ constrainInput: true, // The input is constrained by the current date format
+ showButtonPanel: false, // True to show button panel, false to not show it
+ autoSize: false, // True to size the input for the date format, false to leave as is
+ disabled: false // The initial disabled state
+ };
+ $.extend(this._defaults, this.regional[""]);
+ this.dpDiv = bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"));
+}
+
+$.extend(Datepicker.prototype, {
+ /* Class name added to elements to indicate already configured with a date picker. */
+ markerClassName: "hasDatepicker",
+
+ //Keep track of the maximum number of rows displayed (see #7043)
+ maxRows: 4,
+
+ // TODO rename to "widget" when switching to widget factory
+ _widgetDatepicker: function() {
+ return this.dpDiv;
+ },
+
+ /* Override the default settings for all instances of the date picker.
+ * @param settings object - the new settings to use as defaults (anonymous object)
+ * @return the manager object
+ */
+ setDefaults: function(settings) {
+ extendRemove(this._defaults, settings || {});
+ return this;
+ },
+
+ /* Attach the date picker to a jQuery selection.
+ * @param target element - the target input field or division or span
+ * @param settings object - the new settings to use for this date picker instance (anonymous)
+ */
+ _attachDatepicker: function(target, settings) {
+ var nodeName, inline, inst;
+ nodeName = target.nodeName.toLowerCase();
+ inline = (nodeName === "div" || nodeName === "span");
+ if (!target.id) {
+ this.uuid += 1;
+ target.id = "dp" + this.uuid;
+ }
+ inst = this._newInst($(target), inline);
+ inst.settings = $.extend({}, settings || {});
+ if (nodeName === "input") {
+ this._connectDatepicker(target, inst);
+ } else if (inline) {
+ this._inlineDatepicker(target, inst);
+ }
+ },
+
+ /* Create a new instance object. */
+ _newInst: function(target, inline) {
+ var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars
+ return {id: id, input: target, // associated target
+ selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
+ drawMonth: 0, drawYear: 0, // month being drawn
+ inline: inline, // is datepicker inline or not
+ dpDiv: (!inline ? this.dpDiv : // presentation div
+ bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))};
+ },
+
+ /* Attach the date picker to an input field. */
+ _connectDatepicker: function(target, inst) {
+ var input = $(target);
+ inst.append = $([]);
+ inst.trigger = $([]);
+ if (input.hasClass(this.markerClassName)) {
+ return;
+ }
+ this._attachments(input, inst);
+ input.addClass(this.markerClassName).keydown(this._doKeyDown).
+ keypress(this._doKeyPress).keyup(this._doKeyUp);
+ this._autoSize(inst);
+ $.data(target, PROP_NAME, inst);
+ //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ },
+
+ /* Make attachments based on settings. */
+ _attachments: function(input, inst) {
+ var showOn, buttonText, buttonImage,
+ appendText = this._get(inst, "appendText"),
+ isRTL = this._get(inst, "isRTL");
+
+ if (inst.append) {
+ inst.append.remove();
+ }
+ if (appendText) {
+ inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
+ input[isRTL ? "before" : "after"](inst.append);
+ }
+
+ input.unbind("focus", this._showDatepicker);
+
+ if (inst.trigger) {
+ inst.trigger.remove();
+ }
+
+ showOn = this._get(inst, "showOn");
+ if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field
+ input.focus(this._showDatepicker);
+ }
+ if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked
+ buttonText = this._get(inst, "buttonText");
+ buttonImage = this._get(inst, "buttonImage");
+ inst.trigger = $(this._get(inst, "buttonImageOnly") ?
+ $("<img/>").addClass(this._triggerClass).
+ attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
+ $("<button type='button'></button>").addClass(this._triggerClass).
+ html(!buttonImage ? buttonText : $("<img/>").attr(
+ { src:buttonImage, alt:buttonText, title:buttonText })));
+ input[isRTL ? "before" : "after"](inst.trigger);
+ inst.trigger.click(function() {
+ if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) {
+ $.datepicker._hideDatepicker();
+ } else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) {
+ $.datepicker._hideDatepicker();
+ $.datepicker._showDatepicker(input[0]);
+ } else {
+ $.datepicker._showDatepicker(input[0]);
+ }
+ return false;
+ });
+ }
+ },
+
+ /* Apply the maximum length for the date format. */
+ _autoSize: function(inst) {
+ if (this._get(inst, "autoSize") && !inst.inline) {
+ var findMax, max, maxI, i,
+ date = new Date(2009, 12 - 1, 20), // Ensure double digits
+ dateFormat = this._get(inst, "dateFormat");
+
+ if (dateFormat.match(/[DM]/)) {
+ findMax = function(names) {
+ max = 0;
+ maxI = 0;
+ for (i = 0; i < names.length; i++) {
+ if (names[i].length > max) {
+ max = names[i].length;
+ maxI = i;
+ }
+ }
+ return maxI;
+ };
+ date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
+ "monthNames" : "monthNamesShort"))));
+ date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
+ "dayNames" : "dayNamesShort"))) + 20 - date.getDay());
+ }
+ inst.input.attr("size", this._formatDate(inst, date).length);
+ }
+ },
+
+ /* Attach an inline date picker to a div. */
+ _inlineDatepicker: function(target, inst) {
+ var divSpan = $(target);
+ if (divSpan.hasClass(this.markerClassName)) {
+ return;
+ }
+ divSpan.addClass(this.markerClassName).append(inst.dpDiv);
+ $.data(target, PROP_NAME, inst);
+ this._setDate(inst, this._getDefaultDate(inst), true);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ //If disabled option is true, disable the datepicker before showing it (see ticket #5665)
+ if( inst.settings.disabled ) {
+ this._disableDatepicker( target );
+ }
+ // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
+ // http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
+ inst.dpDiv.css( "display", "block" );
+ },
+
+ /* Pop-up the date picker in a "dialog" box.
+ * @param input element - ignored
+ * @param date string or Date - the initial date to display
+ * @param onSelect function - the function to call when a date is selected
+ * @param settings object - update the dialog date picker instance's settings (anonymous object)
+ * @param pos int[2] - coordinates for the dialog's position within the screen or
+ * event - with x/y coordinates or
+ * leave empty for default (screen centre)
+ * @return the manager object
+ */
+ _dialogDatepicker: function(input, date, onSelect, settings, pos) {
+ var id, browserWidth, browserHeight, scrollX, scrollY,
+ inst = this._dialogInst; // internal instance
+
+ if (!inst) {
+ this.uuid += 1;
+ id = "dp" + this.uuid;
+ this._dialogInput = $("<input type='text' id='" + id +
+ "' style='position: absolute; top: -100px; width: 0px;'/>");
+ this._dialogInput.keydown(this._doKeyDown);
+ $("body").append(this._dialogInput);
+ inst = this._dialogInst = this._newInst(this._dialogInput, false);
+ inst.settings = {};
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ }
+ extendRemove(inst.settings, settings || {});
+ date = (date && date.constructor === Date ? this._formatDate(inst, date) : date);
+ this._dialogInput.val(date);
+
+ this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
+ if (!this._pos) {
+ browserWidth = document.documentElement.clientWidth;
+ browserHeight = document.documentElement.clientHeight;
+ scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+ scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+ this._pos = // should use actual width/height below
+ [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
+ }
+
+ // move input on screen for focus, but hidden behind dialog
+ this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px");
+ inst.settings.onSelect = onSelect;
+ this._inDialog = true;
+ this.dpDiv.addClass(this._dialogClass);
+ this._showDatepicker(this._dialogInput[0]);
+ if ($.blockUI) {
+ $.blockUI(this.dpDiv);
+ }
+ $.data(this._dialogInput[0], PROP_NAME, inst);
+ return this;
+ },
+
+ /* Detach a datepicker from its control.
+ * @param target element - the target input field or division or span
+ */
+ _destroyDatepicker: function(target) {
+ var nodeName,
+ $target = $(target),
+ inst = $.data(target, PROP_NAME);
+
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+
+ nodeName = target.nodeName.toLowerCase();
+ $.removeData(target, PROP_NAME);
+ if (nodeName === "input") {
+ inst.append.remove();
+ inst.trigger.remove();
+ $target.removeClass(this.markerClassName).
+ unbind("focus", this._showDatepicker).
+ unbind("keydown", this._doKeyDown).
+ unbind("keypress", this._doKeyPress).
+ unbind("keyup", this._doKeyUp);
+ } else if (nodeName === "div" || nodeName === "span") {
+ $target.removeClass(this.markerClassName).empty();
+ }
+ },
+
+ /* Enable the date picker to a jQuery selection.
+ * @param target element - the target input field or division or span
+ */
+ _enableDatepicker: function(target) {
+ var nodeName, inline,
+ $target = $(target),
+ inst = $.data(target, PROP_NAME);
+
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+
+ nodeName = target.nodeName.toLowerCase();
+ if (nodeName === "input") {
+ target.disabled = false;
+ inst.trigger.filter("button").
+ each(function() { this.disabled = false; }).end().
+ filter("img").css({opacity: "1.0", cursor: ""});
+ } else if (nodeName === "div" || nodeName === "span") {
+ inline = $target.children("." + this._inlineClass);
+ inline.children().removeClass("ui-state-disabled");
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ prop("disabled", false);
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value === target ? null : value); }); // delete entry
+ },
+
+ /* Disable the date picker to a jQuery selection.
+ * @param target element - the target input field or division or span
+ */
+ _disableDatepicker: function(target) {
+ var nodeName, inline,
+ $target = $(target),
+ inst = $.data(target, PROP_NAME);
+
+ if (!$target.hasClass(this.markerClassName)) {
+ return;
+ }
+
+ nodeName = target.nodeName.toLowerCase();
+ if (nodeName === "input") {
+ target.disabled = true;
+ inst.trigger.filter("button").
+ each(function() { this.disabled = true; }).end().
+ filter("img").css({opacity: "0.5", cursor: "default"});
+ } else if (nodeName === "div" || nodeName === "span") {
+ inline = $target.children("." + this._inlineClass);
+ inline.children().addClass("ui-state-disabled");
+ inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
+ prop("disabled", true);
+ }
+ this._disabledInputs = $.map(this._disabledInputs,
+ function(value) { return (value === target ? null : value); }); // delete entry
+ this._disabledInputs[this._disabledInputs.length] = target;
+ },
+
+ /* Is the first field in a jQuery collection disabled as a datepicker?
+ * @param target element - the target input field or division or span
+ * @return boolean - true if disabled, false if enabled
+ */
+ _isDisabledDatepicker: function(target) {
+ if (!target) {
+ return false;
+ }
+ for (var i = 0; i < this._disabledInputs.length; i++) {
+ if (this._disabledInputs[i] === target) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /* Retrieve the instance data for the target control.
+ * @param target element - the target input field or division or span
+ * @return object - the associated instance data
+ * @throws error if a jQuery problem getting data
+ */
+ _getInst: function(target) {
+ try {
+ return $.data(target, PROP_NAME);
+ }
+ catch (err) {
+ throw "Missing instance data for this datepicker";
+ }
+ },
+
+ /* Update or retrieve the settings for a date picker attached to an input field or division.
+ * @param target element - the target input field or division or span
+ * @param name object - the new settings to update or
+ * string - the name of the setting to change or retrieve,
+ * when retrieving also "all" for all instance settings or
+ * "defaults" for all global defaults
+ * @param value any - the new value for the setting
+ * (omit if above is an object or to retrieve a value)
+ */
+ _optionDatepicker: function(target, name, value) {
+ var settings, date, minDate, maxDate,
+ inst = this._getInst(target);
+
+ if (arguments.length === 2 && typeof name === "string") {
+ return (name === "defaults" ? $.extend({}, $.datepicker._defaults) :
+ (inst ? (name === "all" ? $.extend({}, inst.settings) :
+ this._get(inst, name)) : null));
+ }
+
+ settings = name || {};
+ if (typeof name === "string") {
+ settings = {};
+ settings[name] = value;
+ }
+
+ if (inst) {
+ if (this._curInst === inst) {
+ this._hideDatepicker();
+ }
+
+ date = this._getDateDatepicker(target, true);
+ minDate = this._getMinMaxDate(inst, "min");
+ maxDate = this._getMinMaxDate(inst, "max");
+ extendRemove(inst.settings, settings);
+ // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
+ if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
+ inst.settings.minDate = this._formatDate(inst, minDate);
+ }
+ if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
+ inst.settings.maxDate = this._formatDate(inst, maxDate);
+ }
+ if ( "disabled" in settings ) {
+ if ( settings.disabled ) {
+ this._disableDatepicker(target);
+ } else {
+ this._enableDatepicker(target);
+ }
+ }
+ this._attachments($(target), inst);
+ this._autoSize(inst);
+ this._setDate(inst, date);
+ this._updateAlternate(inst);
+ this._updateDatepicker(inst);
+ }
+ },
+
+ // change method deprecated
+ _changeDatepicker: function(target, name, value) {
+ this._optionDatepicker(target, name, value);
+ },
+
+ /* Redraw the date picker attached to an input field or division.
+ * @param target element - the target input field or division or span
+ */
+ _refreshDatepicker: function(target) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._updateDatepicker(inst);
+ }
+ },
+
+ /* Set the dates for a jQuery selection.
+ * @param target element - the target input field or division or span
+ * @param date Date - the new date
+ */
+ _setDateDatepicker: function(target, date) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._setDate(inst, date);
+ this._updateDatepicker(inst);
+ this._updateAlternate(inst);
+ }
+ },
+
+ /* Get the date(s) for the first entry in a jQuery selection.
+ * @param target element - the target input field or division or span
+ * @param noDefault boolean - true if no default date is to be used
+ * @return Date - the current date
+ */
+ _getDateDatepicker: function(target, noDefault) {
+ var inst = this._getInst(target);
+ if (inst && !inst.inline) {
+ this._setDateFromField(inst, noDefault);
+ }
+ return (inst ? this._getDate(inst) : null);
+ },
+
+ /* Handle keystrokes. */
+ _doKeyDown: function(event) {
+ var onSelect, dateStr, sel,
+ inst = $.datepicker._getInst(event.target),
+ handled = true,
+ isRTL = inst.dpDiv.is(".ui-datepicker-rtl");
+
+ inst._keyEvent = true;
+ if ($.datepicker._datepickerShowing) {
+ switch (event.keyCode) {
+ case 9: $.datepicker._hideDatepicker();
+ handled = false;
+ break; // hide on tab out
+ case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
+ $.datepicker._currentClass + ")", inst.dpDiv);
+ if (sel[0]) {
+ $.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
+ }
+
+ onSelect = $.datepicker._get(inst, "onSelect");
+ if (onSelect) {
+ dateStr = $.datepicker._formatDate(inst);
+
+ // trigger custom callback
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
+ } else {
+ $.datepicker._hideDatepicker();
+ }
+
+ return false; // don't submit the form
+ case 27: $.datepicker._hideDatepicker();
+ break; // hide on escape
+ case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, "stepBigMonths") :
+ -$.datepicker._get(inst, "stepMonths")), "M");
+ break; // previous month/year on page up/+ ctrl
+ case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, "stepBigMonths") :
+ +$.datepicker._get(inst, "stepMonths")), "M");
+ break; // next month/year on page down/+ ctrl
+ case 35: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._clearDate(event.target);
+ }
+ handled = event.ctrlKey || event.metaKey;
+ break; // clear on ctrl or command +end
+ case 36: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._gotoToday(event.target);
+ }
+ handled = event.ctrlKey || event.metaKey;
+ break; // current on ctrl or command +home
+ case 37: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
+ }
+ handled = event.ctrlKey || event.metaKey;
+ // -1 day on ctrl or command +left
+ if (event.originalEvent.altKey) {
+ $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ -$.datepicker._get(inst, "stepBigMonths") :
+ -$.datepicker._get(inst, "stepMonths")), "M");
+ }
+ // next month/year on alt +left on Mac
+ break;
+ case 38: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._adjustDate(event.target, -7, "D");
+ }
+ handled = event.ctrlKey || event.metaKey;
+ break; // -1 week on ctrl or command +up
+ case 39: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
+ }
+ handled = event.ctrlKey || event.metaKey;
+ // +1 day on ctrl or command +right
+ if (event.originalEvent.altKey) {
+ $.datepicker._adjustDate(event.target, (event.ctrlKey ?
+ +$.datepicker._get(inst, "stepBigMonths") :
+ +$.datepicker._get(inst, "stepMonths")), "M");
+ }
+ // next month/year on alt +right
+ break;
+ case 40: if (event.ctrlKey || event.metaKey) {
+ $.datepicker._adjustDate(event.target, +7, "D");
+ }
+ handled = event.ctrlKey || event.metaKey;
+ break; // +1 week on ctrl or command +down
+ default: handled = false;
+ }
+ } else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
+ $.datepicker._showDatepicker(this);
+ } else {
+ handled = false;
+ }
+
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ },
+
+ /* Filter entered characters - based on date format. */
+ _doKeyPress: function(event) {
+ var chars, chr,
+ inst = $.datepicker._getInst(event.target);
+
+ if ($.datepicker._get(inst, "constrainInput")) {
+ chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat"));
+ chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode);
+ return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1);
+ }
+ },
+
+ /* Synchronise manual entry and field/alternate field. */
+ _doKeyUp: function(event) {
+ var date,
+ inst = $.datepicker._getInst(event.target);
+
+ if (inst.input.val() !== inst.lastVal) {
+ try {
+ date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
+ (inst.input ? inst.input.val() : null),
+ $.datepicker._getFormatConfig(inst));
+
+ if (date) { // only if valid
+ $.datepicker._setDateFromField(inst);
+ $.datepicker._updateAlternate(inst);
+ $.datepicker._updateDatepicker(inst);
+ }
+ }
+ catch (err) {
+ }
+ }
+ return true;
+ },
+
+ /* Pop-up the date picker for a given input field.
+ * If false returned from beforeShow event handler do not show.
+ * @param input element - the input field attached to the date picker or
+ * event - if triggered by focus
+ */
+ _showDatepicker: function(input) {
+ input = input.target || input;
+ if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger
+ input = $("input", input.parentNode)[0];
+ }
+
+ if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here
+ return;
+ }
+
+ var inst, beforeShow, beforeShowSettings, isFixed,
+ offset, showAnim, duration;
+
+ inst = $.datepicker._getInst(input);
+ if ($.datepicker._curInst && $.datepicker._curInst !== inst) {
+ $.datepicker._curInst.dpDiv.stop(true, true);
+ if ( inst && $.datepicker._datepickerShowing ) {
+ $.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
+ }
+ }
+
+ beforeShow = $.datepicker._get(inst, "beforeShow");
+ beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
+ if(beforeShowSettings === false){
+ return;
+ }
+ extendRemove(inst.settings, beforeShowSettings);
+
+ inst.lastVal = null;
+ $.datepicker._lastInput = input;
+ $.datepicker._setDateFromField(inst);
+
+ if ($.datepicker._inDialog) { // hide cursor
+ input.value = "";
+ }
+ if (!$.datepicker._pos) { // position below input
+ $.datepicker._pos = $.datepicker._findPos(input);
+ $.datepicker._pos[1] += input.offsetHeight; // add the height
+ }
+
+ isFixed = false;
+ $(input).parents().each(function() {
+ isFixed |= $(this).css("position") === "fixed";
+ return !isFixed;
+ });
+
+ offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
+ $.datepicker._pos = null;
+ //to avoid flashes on Firefox
+ inst.dpDiv.empty();
+ // determine sizing offscreen
+ inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"});
+ $.datepicker._updateDatepicker(inst);
+ // fix width for dynamic number of date pickers
+ // and adjust position before showing
+ offset = $.datepicker._checkOffset(inst, offset, isFixed);
+ inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
+ "static" : (isFixed ? "fixed" : "absolute")), display: "none",
+ left: offset.left + "px", top: offset.top + "px"});
+
+ if (!inst.inline) {
+ showAnim = $.datepicker._get(inst, "showAnim");
+ duration = $.datepicker._get(inst, "duration");
+ inst.dpDiv.zIndex($(input).zIndex()+1);
+ $.datepicker._datepickerShowing = true;
+
+ if ( $.effects && $.effects.effect[ showAnim ] ) {
+ inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration);
+ } else {
+ inst.dpDiv[showAnim || "show"](showAnim ? duration : null);
+ }
+
+ if (inst.input.is(":visible") && !inst.input.is(":disabled")) {
+ inst.input.focus();
+ }
+ $.datepicker._curInst = inst;
+ }
+ },
+
+ /* Generate the date picker content. */
+ _updateDatepicker: function(inst) {
+ this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
+ instActive = inst; // for delegate hover events
+ inst.dpDiv.empty().append(this._generateHTML(inst));
+ this._attachHandlers(inst);
+ inst.dpDiv.find("." + this._dayOverClass + " a").mouseover();
+
+ var origyearshtml,
+ numMonths = this._getNumberOfMonths(inst),
+ cols = numMonths[1],
+ width = 17;
+
+ inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");
+ if (cols > 1) {
+ inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em");
+ }
+ inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") +
+ "Class"]("ui-datepicker-multi");
+ inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") +
+ "Class"]("ui-datepicker-rtl");
+
+ // #6694 - don't focus the input if it's already focused
+ // this breaks the change event in IE
+ if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && inst.input &&
+ inst.input.is(":visible") && !inst.input.is(":disabled") && inst.input[0] !== document.activeElement) {
+ inst.input.focus();
+ }
+
+ // deffered render of the years select (to avoid flashes on Firefox)
+ if( inst.yearshtml ){
+ origyearshtml = inst.yearshtml;
+ setTimeout(function(){
+ //assure that inst.yearshtml didn't change.
+ if( origyearshtml === inst.yearshtml && inst.yearshtml ){
+ inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml);
+ }
+ origyearshtml = inst.yearshtml = null;
+ }, 0);
+ }
+ },
+
+ /* Retrieve the size of left and top borders for an element.
+ * @param elem (jQuery object) the element of interest
+ * @return (number[2]) the left and top borders
+ */
+ _getBorders: function(elem) {
+ var convert = function(value) {
+ return {thin: 1, medium: 2, thick: 3}[value] || value;
+ };
+ return [parseFloat(convert(elem.css("border-left-width"))),
+ parseFloat(convert(elem.css("border-top-width")))];
+ },
+
+ /* Check positioning to remain on screen. */
+ _checkOffset: function(inst, offset, isFixed) {
+ var dpWidth = inst.dpDiv.outerWidth(),
+ dpHeight = inst.dpDiv.outerHeight(),
+ inputWidth = inst.input ? inst.input.outerWidth() : 0,
+ inputHeight = inst.input ? inst.input.outerHeight() : 0,
+ viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()),
+ viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());
+
+ offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0);
+ offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0;
+ offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
+
+ // now check if datepicker is showing outside window viewport - move to a better place if so.
+ offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+ Math.abs(offset.left + dpWidth - viewWidth) : 0);
+ offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+ Math.abs(dpHeight + inputHeight) : 0);
+
+ return offset;
+ },
+
+ /* Find an object's position on the screen. */
+ _findPos: function(obj) {
+ var position,
+ inst = this._getInst(obj),
+ isRTL = this._get(inst, "isRTL");
+
+ while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) {
+ obj = obj[isRTL ? "previousSibling" : "nextSibling"];
+ }
+
+ position = $(obj).offset();
+ return [position.left, position.top];
+ },
+
+ /* Hide the date picker from view.
+ * @param input element - the input field attached to the date picker
+ */
+ _hideDatepicker: function(input) {
+ var showAnim, duration, postProcess, onClose,
+ inst = this._curInst;
+
+ if (!inst || (input && inst !== $.data(input, PROP_NAME))) {
+ return;
+ }
+
+ if (this._datepickerShowing) {
+ showAnim = this._get(inst, "showAnim");
+ duration = this._get(inst, "duration");
+ postProcess = function() {
+ $.datepicker._tidyDialog(inst);
+ };
+
+ // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
+ if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
+ inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess);
+ } else {
+ inst.dpDiv[(showAnim === "slideDown" ? "slideUp" :
+ (showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess);
+ }
+
+ if (!showAnim) {
+ postProcess();
+ }
+ this._datepickerShowing = false;
+
+ onClose = this._get(inst, "onClose");
+ if (onClose) {
+ onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]);
+ }
+
+ this._lastInput = null;
+ if (this._inDialog) {
+ this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" });
+ if ($.blockUI) {
+ $.unblockUI();
+ $("body").append(this.dpDiv);
+ }
+ }
+ this._inDialog = false;
+ }
+ },
+
+ /* Tidy up after a dialog display. */
+ _tidyDialog: function(inst) {
+ inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar");
+ },
+
+ /* Close date picker if clicked elsewhere. */
+ _checkExternalClick: function(event) {
+ if (!$.datepicker._curInst) {
+ return;
+ }
+
+ var $target = $(event.target),
+ inst = $.datepicker._getInst($target[0]);
+
+ if ( ( ( $target[0].id !== $.datepicker._mainDivId &&
+ $target.parents("#" + $.datepicker._mainDivId).length === 0 &&
+ !$target.hasClass($.datepicker.markerClassName) &&
+ !$target.closest("." + $.datepicker._triggerClass).length &&
+ $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
+ ( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) {
+ $.datepicker._hideDatepicker();
+ }
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustDate: function(id, offset, period) {
+ var target = $(id),
+ inst = this._getInst(target[0]);
+
+ if (this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+ this._adjustInstDate(inst, offset +
+ (period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning
+ period);
+ this._updateDatepicker(inst);
+ },
+
+ /* Action for current link. */
+ _gotoToday: function(id) {
+ var date,
+ target = $(id),
+ inst = this._getInst(target[0]);
+
+ if (this._get(inst, "gotoCurrent") && inst.currentDay) {
+ inst.selectedDay = inst.currentDay;
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth;
+ inst.drawYear = inst.selectedYear = inst.currentYear;
+ } else {
+ date = new Date();
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ }
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a new month/year. */
+ _selectMonthYear: function(id, select, period) {
+ var target = $(id),
+ inst = this._getInst(target[0]);
+
+ inst["selected" + (period === "M" ? "Month" : "Year")] =
+ inst["draw" + (period === "M" ? "Month" : "Year")] =
+ parseInt(select.options[select.selectedIndex].value,10);
+
+ this._notifyChange(inst);
+ this._adjustDate(target);
+ },
+
+ /* Action for selecting a day. */
+ _selectDay: function(id, month, year, td) {
+ var inst,
+ target = $(id);
+
+ if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
+ return;
+ }
+
+ inst = this._getInst(target[0]);
+ inst.selectedDay = inst.currentDay = $("a", td).html();
+ inst.selectedMonth = inst.currentMonth = month;
+ inst.selectedYear = inst.currentYear = year;
+ this._selectDate(id, this._formatDate(inst,
+ inst.currentDay, inst.currentMonth, inst.currentYear));
+ },
+
+ /* Erase the input field and hide the date picker. */
+ _clearDate: function(id) {
+ var target = $(id);
+ this._selectDate(target, "");
+ },
+
+ /* Update the input field with the selected date. */
+ _selectDate: function(id, dateStr) {
+ var onSelect,
+ target = $(id),
+ inst = this._getInst(target[0]);
+
+ dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
+ if (inst.input) {
+ inst.input.val(dateStr);
+ }
+ this._updateAlternate(inst);
+
+ onSelect = this._get(inst, "onSelect");
+ if (onSelect) {
+ onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback
+ } else if (inst.input) {
+ inst.input.trigger("change"); // fire the change event
+ }
+
+ if (inst.inline){
+ this._updateDatepicker(inst);
+ } else {
+ this._hideDatepicker();
+ this._lastInput = inst.input[0];
+ if (typeof(inst.input[0]) !== "object") {
+ inst.input.focus(); // restore focus
+ }
+ this._lastInput = null;
+ }
+ },
+
+ /* Update any alternate field to synchronise with the main field. */
+ _updateAlternate: function(inst) {
+ var altFormat, date, dateStr,
+ altField = this._get(inst, "altField");
+
+ if (altField) { // update alternate field too
+ altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat");
+ date = this._getDate(inst);
+ dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
+ $(altField).each(function() { $(this).val(dateStr); });
+ }
+ },
+
+ /* Set as beforeShowDay function to prevent selection of weekends.
+ * @param date Date - the date to customise
+ * @return [boolean, string] - is this date selectable?, what is its CSS class?
+ */
+ noWeekends: function(date) {
+ var day = date.getDay();
+ return [(day > 0 && day < 6), ""];
+ },
+
+ /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+ * @param date Date - the date to get the week for
+ * @return number - the number of the week within the year that contains this date
+ */
+ iso8601Week: function(date) {
+ var time,
+ checkDate = new Date(date.getTime());
+
+ // Find Thursday of this week starting on Monday
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+
+ time = checkDate.getTime();
+ checkDate.setMonth(0); // Compare with Jan 1
+ checkDate.setDate(1);
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+ },
+
+ /* Parse a string value into a date object.
+ * See formatDate below for the possible formats.
+ *
+ * @param format string - the expected format of the date
+ * @param value string - the date in the above format
+ * @param settings Object - attributes include:
+ * shortYearCutoff number - the cutoff year for determining the century (optional)
+ * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ * dayNames string[7] - names of the days from Sunday (optional)
+ * monthNamesShort string[12] - abbreviated names of the months (optional)
+ * monthNames string[12] - names of the months (optional)
+ * @return Date - the extracted date value or null if value is blank
+ */
+ parseDate: function (format, value, settings) {
+ if (format == null || value == null) {
+ throw "Invalid arguments";
+ }
+
+ value = (typeof value === "object" ? value.toString() : value + "");
+ if (value === "") {
+ return null;
+ }
+
+ var iFormat, dim, extra,
+ iValue = 0,
+ shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff,
+ shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)),
+ dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
+ dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
+ monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
+ monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
+ year = -1,
+ month = -1,
+ day = -1,
+ doy = -1,
+ literal = false,
+ date,
+ // Check whether a format character is doubled
+ lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+ if (matches) {
+ iFormat++;
+ }
+ return matches;
+ },
+ // Extract a number from the string value
+ getNumber = function(match) {
+ var isDoubled = lookAhead(match),
+ size = (match === "@" ? 14 : (match === "!" ? 20 :
+ (match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
+ digits = new RegExp("^\\d{1," + size + "}"),
+ num = value.substring(iValue).match(digits);
+ if (!num) {
+ throw "Missing number at position " + iValue;
+ }
+ iValue += num[0].length;
+ return parseInt(num[0], 10);
+ },
+ // Extract a name from the string value and convert to an index
+ getName = function(match, shortNames, longNames) {
+ var index = -1,
+ names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
+ return [ [k, v] ];
+ }).sort(function (a, b) {
+ return -(a[1].length - b[1].length);
+ });
+
+ $.each(names, function (i, pair) {
+ var name = pair[1];
+ if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
+ index = pair[0];
+ iValue += name.length;
+ return false;
+ }
+ });
+ if (index !== -1) {
+ return index + 1;
+ } else {
+ throw "Unknown name at position " + iValue;
+ }
+ },
+ // Confirm that a literal character matches the string value
+ checkLiteral = function() {
+ if (value.charAt(iValue) !== format.charAt(iFormat)) {
+ throw "Unexpected literal at position " + iValue;
+ }
+ iValue++;
+ };
+
+ for (iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal) {
+ if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+ literal = false;
+ } else {
+ checkLiteral();
+ }
+ } else {
+ switch (format.charAt(iFormat)) {
+ case "d":
+ day = getNumber("d");
+ break;
+ case "D":
+ getName("D", dayNamesShort, dayNames);
+ break;
+ case "o":
+ doy = getNumber("o");
+ break;
+ case "m":
+ month = getNumber("m");
+ break;
+ case "M":
+ month = getName("M", monthNamesShort, monthNames);
+ break;
+ case "y":
+ year = getNumber("y");
+ break;
+ case "@":
+ date = new Date(getNumber("@"));
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "!":
+ date = new Date((getNumber("!") - this._ticksTo1970) / 10000);
+ year = date.getFullYear();
+ month = date.getMonth() + 1;
+ day = date.getDate();
+ break;
+ case "'":
+ if (lookAhead("'")){
+ checkLiteral();
+ } else {
+ literal = true;
+ }
+ break;
+ default:
+ checkLiteral();
+ }
+ }
+ }
+
+ if (iValue < value.length){
+ extra = value.substr(iValue);
+ if (!/^\s+/.test(extra)) {
+ throw "Extra/unparsed characters found in date: " + extra;
+ }
+ }
+
+ if (year === -1) {
+ year = new Date().getFullYear();
+ } else if (year < 100) {
+ year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+ (year <= shortYearCutoff ? 0 : -100);
+ }
+
+ if (doy > -1) {
+ month = 1;
+ day = doy;
+ do {
+ dim = this._getDaysInMonth(year, month - 1);
+ if (day <= dim) {
+ break;
+ }
+ month++;
+ day -= dim;
+ } while (true);
+ }
+
+ date = this._daylightSavingAdjust(new Date(year, month - 1, day));
+ if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
+ throw "Invalid date"; // E.g. 31/02/00
+ }
+ return date;
+ },
+
+ /* Standard date formats. */
+ ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
+ COOKIE: "D, dd M yy",
+ ISO_8601: "yy-mm-dd",
+ RFC_822: "D, d M y",
+ RFC_850: "DD, dd-M-y",
+ RFC_1036: "D, d M y",
+ RFC_1123: "D, d M yy",
+ RFC_2822: "D, d M yy",
+ RSS: "D, d M y", // RFC 822
+ TICKS: "!",
+ TIMESTAMP: "@",
+ W3C: "yy-mm-dd", // ISO 8601
+
+ _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
+ Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
+
+ /* Format a date object into a string value.
+ * The format can be combinations of the following:
+ * d - day of month (no leading zero)
+ * dd - day of month (two digit)
+ * o - day of year (no leading zeros)
+ * oo - day of year (three digit)
+ * D - day name short
+ * DD - day name long
+ * m - month of year (no leading zero)
+ * mm - month of year (two digit)
+ * M - month name short
+ * MM - month name long
+ * y - year (two digit)
+ * yy - year (four digit)
+ * @ - Unix timestamp (ms since 01/01/1970)
+ * ! - Windows ticks (100ns since 01/01/0001)
+ * "..." - literal text
+ * '' - single quote
+ *
+ * @param format string - the desired format of the date
+ * @param date Date - the date value to format
+ * @param settings Object - attributes include:
+ * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
+ * dayNames string[7] - names of the days from Sunday (optional)
+ * monthNamesShort string[12] - abbreviated names of the months (optional)
+ * monthNames string[12] - names of the months (optional)
+ * @return string - the date in the above format
+ */
+ formatDate: function (format, date, settings) {
+ if (!date) {
+ return "";
+ }
+
+ var iFormat,
+ dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
+ dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
+ monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
+ monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
+ // Check whether a format character is doubled
+ lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+ if (matches) {
+ iFormat++;
+ }
+ return matches;
+ },
+ // Format a number, with leading zero if necessary
+ formatNumber = function(match, value, len) {
+ var num = "" + value;
+ if (lookAhead(match)) {
+ while (num.length < len) {
+ num = "0" + num;
+ }
+ }
+ return num;
+ },
+ // Format a name, short or long as requested
+ formatName = function(match, value, shortNames, longNames) {
+ return (lookAhead(match) ? longNames[value] : shortNames[value]);
+ },
+ output = "",
+ literal = false;
+
+ if (date) {
+ for (iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal) {
+ if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+ literal = false;
+ } else {
+ output += format.charAt(iFormat);
+ }
+ } else {
+ switch (format.charAt(iFormat)) {
+ case "d":
+ output += formatNumber("d", date.getDate(), 2);
+ break;
+ case "D":
+ output += formatName("D", date.getDay(), dayNamesShort, dayNames);
+ break;
+ case "o":
+ output += formatNumber("o",
+ Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
+ break;
+ case "m":
+ output += formatNumber("m", date.getMonth() + 1, 2);
+ break;
+ case "M":
+ output += formatName("M", date.getMonth(), monthNamesShort, monthNames);
+ break;
+ case "y":
+ output += (lookAhead("y") ? date.getFullYear() :
+ (date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100);
+ break;
+ case "@":
+ output += date.getTime();
+ break;
+ case "!":
+ output += date.getTime() * 10000 + this._ticksTo1970;
+ break;
+ case "'":
+ if (lookAhead("'")) {
+ output += "'";
+ } else {
+ literal = true;
+ }
+ break;
+ default:
+ output += format.charAt(iFormat);
+ }
+ }
+ }
+ }
+ return output;
+ },
+
+ /* Extract all possible characters from the date format. */
+ _possibleChars: function (format) {
+ var iFormat,
+ chars = "",
+ literal = false,
+ // Check whether a format character is doubled
+ lookAhead = function(match) {
+ var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
+ if (matches) {
+ iFormat++;
+ }
+ return matches;
+ };
+
+ for (iFormat = 0; iFormat < format.length; iFormat++) {
+ if (literal) {
+ if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
+ literal = false;
+ } else {
+ chars += format.charAt(iFormat);
+ }
+ } else {
+ switch (format.charAt(iFormat)) {
+ case "d": case "m": case "y": case "@":
+ chars += "0123456789";
+ break;
+ case "D": case "M":
+ return null; // Accept anything
+ case "'":
+ if (lookAhead("'")) {
+ chars += "'";
+ } else {
+ literal = true;
+ }
+ break;
+ default:
+ chars += format.charAt(iFormat);
+ }
+ }
+ }
+ return chars;
+ },
+
+ /* Get a setting value, defaulting if necessary. */
+ _get: function(inst, name) {
+ return inst.settings[name] !== undefined ?
+ inst.settings[name] : this._defaults[name];
+ },
+
+ /* Parse existing date and initialise date picker. */
+ _setDateFromField: function(inst, noDefault) {
+ if (inst.input.val() === inst.lastVal) {
+ return;
+ }
+
+ var dateFormat = this._get(inst, "dateFormat"),
+ dates = inst.lastVal = inst.input ? inst.input.val() : null,
+ defaultDate = this._getDefaultDate(inst),
+ date = defaultDate,
+ settings = this._getFormatConfig(inst);
+
+ try {
+ date = this.parseDate(dateFormat, dates, settings) || defaultDate;
+ } catch (event) {
+ dates = (noDefault ? "" : dates);
+ }
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ inst.currentDay = (dates ? date.getDate() : 0);
+ inst.currentMonth = (dates ? date.getMonth() : 0);
+ inst.currentYear = (dates ? date.getFullYear() : 0);
+ this._adjustInstDate(inst);
+ },
+
+ /* Retrieve the default date shown on opening. */
+ _getDefaultDate: function(inst) {
+ return this._restrictMinMax(inst,
+ this._determineDate(inst, this._get(inst, "defaultDate"), new Date()));
+ },
+
+ /* A date may be specified as an exact value or a relative one. */
+ _determineDate: function(inst, date, defaultDate) {
+ var offsetNumeric = function(offset) {
+ var date = new Date();
+ date.setDate(date.getDate() + offset);
+ return date;
+ },
+ offsetString = function(offset) {
+ try {
+ return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
+ offset, $.datepicker._getFormatConfig(inst));
+ }
+ catch (e) {
+ // Ignore
+ }
+
+ var date = (offset.toLowerCase().match(/^c/) ?
+ $.datepicker._getDate(inst) : null) || new Date(),
+ year = date.getFullYear(),
+ month = date.getMonth(),
+ day = date.getDate(),
+ pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
+ matches = pattern.exec(offset);
+
+ while (matches) {
+ switch (matches[2] || "d") {
+ case "d" : case "D" :
+ day += parseInt(matches[1],10); break;
+ case "w" : case "W" :
+ day += parseInt(matches[1],10) * 7; break;
+ case "m" : case "M" :
+ month += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ case "y": case "Y" :
+ year += parseInt(matches[1],10);
+ day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
+ break;
+ }
+ matches = pattern.exec(offset);
+ }
+ return new Date(year, month, day);
+ },
+ newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) :
+ (typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
+
+ newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate);
+ if (newDate) {
+ newDate.setHours(0);
+ newDate.setMinutes(0);
+ newDate.setSeconds(0);
+ newDate.setMilliseconds(0);
+ }
+ return this._daylightSavingAdjust(newDate);
+ },
+
+ /* Handle switch to/from daylight saving.
+ * Hours may be non-zero on daylight saving cut-over:
+ * > 12 when midnight changeover, but then cannot generate
+ * midnight datetime, so jump to 1AM, otherwise reset.
+ * @param date (Date) the date to check
+ * @return (Date) the corrected date
+ */
+ _daylightSavingAdjust: function(date) {
+ if (!date) {
+ return null;
+ }
+ date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
+ return date;
+ },
+
+ /* Set the date(s) directly. */
+ _setDate: function(inst, date, noChange) {
+ var clear = !date,
+ origMonth = inst.selectedMonth,
+ origYear = inst.selectedYear,
+ newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
+
+ inst.selectedDay = inst.currentDay = newDate.getDate();
+ inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
+ inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
+ if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) {
+ this._notifyChange(inst);
+ }
+ this._adjustInstDate(inst);
+ if (inst.input) {
+ inst.input.val(clear ? "" : this._formatDate(inst));
+ }
+ },
+
+ /* Retrieve the date(s) directly. */
+ _getDate: function(inst) {
+ var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
+ this._daylightSavingAdjust(new Date(
+ inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return startDate;
+ },
+
+ /* Attach the onxxx handlers. These are declared statically so
+ * they work with static code transformers like Caja.
+ */
+ _attachHandlers: function(inst) {
+ var stepMonths = this._get(inst, "stepMonths"),
+ id = "#" + inst.id.replace( /\\\\/g, "\\" );
+ inst.dpDiv.find("[data-handler]").map(function () {
+ var handler = {
+ prev: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._adjustDate(id, -stepMonths, "M");
+ },
+ next: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._adjustDate(id, +stepMonths, "M");
+ },
+ hide: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._hideDatepicker();
+ },
+ today: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._gotoToday(id);
+ },
+ selectDay: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this);
+ return false;
+ },
+ selectMonth: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._selectMonthYear(id, this, "M");
+ return false;
+ },
+ selectYear: function () {
+ window["DP_jQuery_" + dpuuid].datepicker._selectMonthYear(id, this, "Y");
+ return false;
+ }
+ };
+ $(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
+ });
+ },
+
+ /* Generate the HTML for the current state of the date picker. */
+ _generateHTML: function(inst) {
+ var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
+ controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
+ monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
+ selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
+ cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
+ printDate, dRow, tbody, daySettings, otherMonth, unselectable,
+ tempDate = new Date(),
+ today = this._daylightSavingAdjust(
+ new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time
+ isRTL = this._get(inst, "isRTL"),
+ showButtonPanel = this._get(inst, "showButtonPanel"),
+ hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
+ navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
+ numMonths = this._getNumberOfMonths(inst),
+ showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
+ stepMonths = this._get(inst, "stepMonths"),
+ isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
+ currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
+ new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
+ minDate = this._getMinMaxDate(inst, "min"),
+ maxDate = this._getMinMaxDate(inst, "max"),
+ drawMonth = inst.drawMonth - showCurrentAtPos,
+ drawYear = inst.drawYear;
+
+ if (drawMonth < 0) {
+ drawMonth += 12;
+ drawYear--;
+ }
+ if (maxDate) {
+ maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
+ maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
+ maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
+ while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
+ drawMonth--;
+ if (drawMonth < 0) {
+ drawMonth = 11;
+ drawYear--;
+ }
+ }
+ }
+ inst.drawMonth = drawMonth;
+ inst.drawYear = drawYear;
+
+ prevText = this._get(inst, "prevText");
+ prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
+ this._getFormatConfig(inst)));
+
+ prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
+ "<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
+ " title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
+ (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));
+
+ nextText = this._get(inst, "nextText");
+ nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
+ this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
+ this._getFormatConfig(inst)));
+
+ next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
+ "<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
+ " title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
+ (hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));
+
+ currentText = this._get(inst, "currentText");
+ gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today);
+ currentText = (!navigationAsDateFormat ? currentText :
+ this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
+
+ controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
+ this._get(inst, "closeText") + "</button>" : "");
+
+ buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
+ (this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
+ ">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : "";
+
+ firstDay = parseInt(this._get(inst, "firstDay"),10);
+ firstDay = (isNaN(firstDay) ? 0 : firstDay);
+
+ showWeek = this._get(inst, "showWeek");
+ dayNames = this._get(inst, "dayNames");
+ dayNamesMin = this._get(inst, "dayNamesMin");
+ monthNames = this._get(inst, "monthNames");
+ monthNamesShort = this._get(inst, "monthNamesShort");
+ beforeShowDay = this._get(inst, "beforeShowDay");
+ showOtherMonths = this._get(inst, "showOtherMonths");
+ selectOtherMonths = this._get(inst, "selectOtherMonths");
+ defaultDate = this._getDefaultDate(inst);
+ html = "";
+ dow;
+ for (row = 0; row < numMonths[0]; row++) {
+ group = "";
+ this.maxRows = 4;
+ for (col = 0; col < numMonths[1]; col++) {
+ selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
+ cornerClass = " ui-corner-all";
+ calender = "";
+ if (isMultiMonth) {
+ calender += "<div class='ui-datepicker-group";
+ if (numMonths[1] > 1) {
+ switch (col) {
+ case 0: calender += " ui-datepicker-group-first";
+ cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break;
+ case numMonths[1]-1: calender += " ui-datepicker-group-last";
+ cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break;
+ default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
+ }
+ }
+ calender += "'>";
+ }
+ calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
+ (/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") +
+ (/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") +
+ this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
+ row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
+ "</div><table class='ui-datepicker-calendar'><thead>" +
+ "<tr>";
+ thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : "");
+ for (dow = 0; dow < 7; dow++) { // days of the week
+ day = (dow + firstDay) % 7;
+ thead += "<th" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" +
+ "<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>";
+ }
+ calender += thead + "</tr></thead><tbody>";
+ daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
+ if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
+ inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
+ }
+ leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
+ curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
+ numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
+ this.maxRows = numRows;
+ printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
+ for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
+ calender += "<tr>";
+ tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
+ this._get(inst, "calculateWeek")(printDate) + "</td>");
+ for (dow = 0; dow < 7; dow++) { // create date picker days
+ daySettings = (beforeShowDay ?
+ beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
+ otherMonth = (printDate.getMonth() !== drawMonth);
+ unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
+ (minDate && printDate < minDate) || (maxDate && printDate > maxDate);
+ tbody += "<td class='" +
+ ((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
+ (otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
+ ((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
+ (defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
+ // or defaultDate is current printedDate and defaultDate is selectedDate
+ " " + this._dayOverClass : "") + // highlight selected day
+ (unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") + // highlight unselectable days
+ (otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
+ (printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
+ (printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
+ ((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
+ (unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
+ (otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
+ (unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
+ (printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
+ (printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day
+ (otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
+ "' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
+ printDate.setDate(printDate.getDate() + 1);
+ printDate = this._daylightSavingAdjust(printDate);
+ }
+ calender += tbody + "</tr>";
+ }
+ drawMonth++;
+ if (drawMonth > 11) {
+ drawMonth = 0;
+ drawYear++;
+ }
+ calender += "</tbody></table>" + (isMultiMonth ? "</div>" +
+ ((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : "");
+ group += calender;
+ }
+ html += group;
+ }
+ html += buttonPanel;
+ inst._keyEvent = false;
+ return html;
+ },
+
+ /* Generate the month and year header. */
+ _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
+ secondary, monthNames, monthNamesShort) {
+
+ var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
+ changeMonth = this._get(inst, "changeMonth"),
+ changeYear = this._get(inst, "changeYear"),
+ showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
+ html = "<div class='ui-datepicker-title'>",
+ monthHtml = "";
+
+ // month selection
+ if (secondary || !changeMonth) {
+ monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>";
+ } else {
+ inMinYear = (minDate && minDate.getFullYear() === drawYear);
+ inMaxYear = (maxDate && maxDate.getFullYear() === drawYear);
+ monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
+ for ( month = 0; month < 12; month++) {
+ if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) {
+ monthHtml += "<option value='" + month + "'" +
+ (month === drawMonth ? " selected='selected'" : "") +
+ ">" + monthNamesShort[month] + "</option>";
+ }
+ }
+ monthHtml += "</select>";
+ }
+
+ if (!showMonthAfterYear) {
+ html += monthHtml + (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "");
+ }
+
+ // year selection
+ if ( !inst.yearshtml ) {
+ inst.yearshtml = "";
+ if (secondary || !changeYear) {
+ html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
+ } else {
+ // determine range of years to display
+ years = this._get(inst, "yearRange").split(":");
+ thisYear = new Date().getFullYear();
+ determineYear = function(value) {
+ var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
+ (value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
+ parseInt(value, 10)));
+ return (isNaN(year) ? thisYear : year);
+ };
+ year = determineYear(years[0]);
+ endYear = Math.max(year, determineYear(years[1] || ""));
+ year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
+ endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
+ inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
+ for (; year <= endYear; year++) {
+ inst.yearshtml += "<option value='" + year + "'" +
+ (year === drawYear ? " selected='selected'" : "") +
+ ">" + year + "</option>";
+ }
+ inst.yearshtml += "</select>";
+
+ html += inst.yearshtml;
+ inst.yearshtml = null;
+ }
+ }
+
+ html += this._get(inst, "yearSuffix");
+ if (showMonthAfterYear) {
+ html += (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "") + monthHtml;
+ }
+ html += "</div>"; // Close datepicker_header
+ return html;
+ },
+
+ /* Adjust one of the date sub-fields. */
+ _adjustInstDate: function(inst, offset, period) {
+ var year = inst.drawYear + (period === "Y" ? offset : 0),
+ month = inst.drawMonth + (period === "M" ? offset : 0),
+ day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0),
+ date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day)));
+
+ inst.selectedDay = date.getDate();
+ inst.drawMonth = inst.selectedMonth = date.getMonth();
+ inst.drawYear = inst.selectedYear = date.getFullYear();
+ if (period === "M" || period === "Y") {
+ this._notifyChange(inst);
+ }
+ },
+
+ /* Ensure a date is within any min/max bounds. */
+ _restrictMinMax: function(inst, date) {
+ var minDate = this._getMinMaxDate(inst, "min"),
+ maxDate = this._getMinMaxDate(inst, "max"),
+ newDate = (minDate && date < minDate ? minDate : date);
+ return (maxDate && newDate > maxDate ? maxDate : newDate);
+ },
+
+ /* Notify change of month/year. */
+ _notifyChange: function(inst) {
+ var onChange = this._get(inst, "onChangeMonthYear");
+ if (onChange) {
+ onChange.apply((inst.input ? inst.input[0] : null),
+ [inst.selectedYear, inst.selectedMonth + 1, inst]);
+ }
+ },
+
+ /* Determine the number of months to show. */
+ _getNumberOfMonths: function(inst) {
+ var numMonths = this._get(inst, "numberOfMonths");
+ return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths));
+ },
+
+ /* Determine the current maximum date - ensure no time components are set. */
+ _getMinMaxDate: function(inst, minMax) {
+ return this._determineDate(inst, this._get(inst, minMax + "Date"), null);
+ },
+
+ /* Find the number of days in a given month. */
+ _getDaysInMonth: function(year, month) {
+ return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
+ },
+
+ /* Find the day of the week of the first of a month. */
+ _getFirstDayOfMonth: function(year, month) {
+ return new Date(year, month, 1).getDay();
+ },
+
+ /* Determines if we should allow a "next/prev" month display change. */
+ _canAdjustMonth: function(inst, offset, curYear, curMonth) {
+ var numMonths = this._getNumberOfMonths(inst),
+ date = this._daylightSavingAdjust(new Date(curYear,
+ curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
+
+ if (offset < 0) {
+ date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
+ }
+ return this._isInRange(inst, date);
+ },
+
+ /* Is the given date in the accepted range? */
+ _isInRange: function(inst, date) {
+ var yearSplit, currentYear,
+ minDate = this._getMinMaxDate(inst, "min"),
+ maxDate = this._getMinMaxDate(inst, "max"),
+ minYear = null,
+ maxYear = null,
+ years = this._get(inst, "yearRange");
+ if (years){
+ yearSplit = years.split(":");
+ currentYear = new Date().getFullYear();
+ minYear = parseInt(yearSplit[0], 10);
+ maxYear = parseInt(yearSplit[1], 10);
+ if ( yearSplit[0].match(/[+\-].*/) ) {
+ minYear += currentYear;
+ }
+ if ( yearSplit[1].match(/[+\-].*/) ) {
+ maxYear += currentYear;
+ }
+ }
+
+ return ((!minDate || date.getTime() >= minDate.getTime()) &&
+ (!maxDate || date.getTime() <= maxDate.getTime()) &&
+ (!minYear || date.getFullYear() >= minYear) &&
+ (!maxYear || date.getFullYear() <= maxYear));
+ },
+
+ /* Provide the configuration settings for formatting/parsing. */
+ _getFormatConfig: function(inst) {
+ var shortYearCutoff = this._get(inst, "shortYearCutoff");
+ shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff :
+ new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
+ return {shortYearCutoff: shortYearCutoff,
+ dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"),
+ monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")};
+ },
+
+ /* Format the given date for display. */
+ _formatDate: function(inst, day, month, year) {
+ if (!day) {
+ inst.currentDay = inst.selectedDay;
+ inst.currentMonth = inst.selectedMonth;
+ inst.currentYear = inst.selectedYear;
+ }
+ var date = (day ? (typeof day === "object" ? day :
+ this._daylightSavingAdjust(new Date(year, month, day))) :
+ this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
+ return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst));
+ }
+});
+
+/*
+ * Bind hover events for datepicker elements.
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
+ * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
+ */
+function bindHover(dpDiv) {
+ var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
+ return dpDiv.delegate(selector, "mouseout", function() {
+ $(this).removeClass("ui-state-hover");
+ if (this.className.indexOf("ui-datepicker-prev") !== -1) {
+ $(this).removeClass("ui-datepicker-prev-hover");
+ }
+ if (this.className.indexOf("ui-datepicker-next") !== -1) {
+ $(this).removeClass("ui-datepicker-next-hover");
+ }
+ })
+ .delegate(selector, "mouseover", function(){
+ if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) {
+ $(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
+ $(this).addClass("ui-state-hover");
+ if (this.className.indexOf("ui-datepicker-prev") !== -1) {
+ $(this).addClass("ui-datepicker-prev-hover");
+ }
+ if (this.className.indexOf("ui-datepicker-next") !== -1) {
+ $(this).addClass("ui-datepicker-next-hover");
+ }
+ }
+ });
+}
+
+/* jQuery extend now ignores nulls! */
+function extendRemove(target, props) {
+ $.extend(target, props);
+ for (var name in props) {
+ if (props[name] == null) {
+ target[name] = props[name];
+ }
+ }
+ return target;
+}
+
+/* Invoke the datepicker functionality.
+ @param options string - a command, optionally followed by additional parameters or
+ Object - settings for attaching new datepicker functionality
+ @return jQuery object */
+$.fn.datepicker = function(options){
+
+ /* Verify an empty collection wasn't passed - Fixes #6976 */
+ if ( !this.length ) {
+ return this;
+ }
+
+ /* Initialise the date picker. */
+ if (!$.datepicker.initialized) {
+ $(document).mousedown($.datepicker._checkExternalClick);
+ $.datepicker.initialized = true;
+ }
+
+ /* Append datepicker main container to body if not exist. */
+ if ($("#"+$.datepicker._mainDivId).length === 0) {
+ $("body").append($.datepicker.dpDiv);
+ }
+
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
+ if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
+ return $.datepicker["_" + options + "Datepicker"].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ }
+ if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
+ return $.datepicker["_" + options + "Datepicker"].
+ apply($.datepicker, [this[0]].concat(otherArgs));
+ }
+ return this.each(function() {
+ typeof options === "string" ?
+ $.datepicker["_" + options + "Datepicker"].
+ apply($.datepicker, [this].concat(otherArgs)) :
+ $.datepicker._attachDatepicker(this, options);
+ });
+};
+
+$.datepicker = new Datepicker(); // singleton instance
+$.datepicker.initialized = false;
+$.datepicker.uuid = new Date().getTime();
+$.datepicker.version = "1.10.2";
+
+// Workaround for #4055
+// Add another global to avoid noConflict issues with inline event handlers
+window["DP_jQuery_" + dpuuid] = $;
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+var sizeRelatedOptions = {
+ buttons: true,
+ height: true,
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true,
+ width: true
+ },
+ resizableRelatedOptions = {
+ maxHeight: true,
+ maxWidth: true,
+ minHeight: true,
+ minWidth: true
+ };
+
+$.widget( "ui.dialog", {
+ version: "1.10.2",
+ options: {
+ appendTo: "body",
+ autoOpen: true,
+ buttons: [],
+ closeOnEscape: true,
+ closeText: "close",
+ dialogClass: "",
+ draggable: true,
+ hide: null,
+ height: "auto",
+ maxHeight: null,
+ maxWidth: null,
+ minHeight: 150,
+ minWidth: 150,
+ modal: false,
+ position: {
+ my: "center",
+ at: "center",
+ of: window,
+ collision: "fit",
+ // Ensure the titlebar is always visible
+ using: function( pos ) {
+ var topOffset = $( this ).css( pos ).offset().top;
+ if ( topOffset < 0 ) {
+ $( this ).css( "top", pos.top - topOffset );
+ }
+ }
+ },
+ resizable: true,
+ show: null,
+ title: null,
+ width: 300,
+
+ // callbacks
+ beforeClose: null,
+ close: null,
+ drag: null,
+ dragStart: null,
+ dragStop: null,
+ focus: null,
+ open: null,
+ resize: null,
+ resizeStart: null,
+ resizeStop: null
+ },
+
+ _create: function() {
+ this.originalCss = {
+ display: this.element[0].style.display,
+ width: this.element[0].style.width,
+ minHeight: this.element[0].style.minHeight,
+ maxHeight: this.element[0].style.maxHeight,
+ height: this.element[0].style.height
+ };
+ this.originalPosition = {
+ parent: this.element.parent(),
+ index: this.element.parent().children().index( this.element )
+ };
+ this.originalTitle = this.element.attr("title");
+ this.options.title = this.options.title || this.originalTitle;
+
+ this._createWrapper();
+
+ this.element
+ .show()
+ .removeAttr("title")
+ .addClass("ui-dialog-content ui-widget-content")
+ .appendTo( this.uiDialog );
+
+ this._createTitlebar();
+ this._createButtonPane();
+
+ if ( this.options.draggable && $.fn.draggable ) {
+ this._makeDraggable();
+ }
+ if ( this.options.resizable && $.fn.resizable ) {
+ this._makeResizable();
+ }
+
+ this._isOpen = false;
+ },
+
+ _init: function() {
+ if ( this.options.autoOpen ) {
+ this.open();
+ }
+ },
+
+ _appendTo: function() {
+ var element = this.options.appendTo;
+ if ( element && (element.jquery || element.nodeType) ) {
+ return $( element );
+ }
+ return this.document.find( element || "body" ).eq( 0 );
+ },
+
+ _destroy: function() {
+ var next,
+ originalPosition = this.originalPosition;
+
+ this._destroyOverlay();
+
+ this.element
+ .removeUniqueId()
+ .removeClass("ui-dialog-content ui-widget-content")
+ .css( this.originalCss )
+ // Without detaching first, the following becomes really slow
+ .detach();
+
+ this.uiDialog.stop( true, true ).remove();
+
+ if ( this.originalTitle ) {
+ this.element.attr( "title", this.originalTitle );
+ }
+
+ next = originalPosition.parent.children().eq( originalPosition.index );
+ // Don't try to place the dialog next to itself (#8613)
+ if ( next.length && next[0] !== this.element[0] ) {
+ next.before( this.element );
+ } else {
+ originalPosition.parent.append( this.element );
+ }
+ },
+
+ widget: function() {
+ return this.uiDialog;
+ },
+
+ disable: $.noop,
+ enable: $.noop,
+
+ close: function( event ) {
+ var that = this;
+
+ if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
+ return;
+ }
+
+ this._isOpen = false;
+ this._destroyOverlay();
+
+ if ( !this.opener.filter(":focusable").focus().length ) {
+ // Hiding a focused element doesn't trigger blur in WebKit
+ // so in case we have nothing to focus on, explicitly blur the active element
+ // https://bugs.webkit.org/show_bug.cgi?id=47182
+ $( this.document[0].activeElement ).blur();
+ }
+
+ this._hide( this.uiDialog, this.options.hide, function() {
+ that._trigger( "close", event );
+ });
+ },
+
+ isOpen: function() {
+ return this._isOpen;
+ },
+
+ moveToTop: function() {
+ this._moveToTop();
+ },
+
+ _moveToTop: function( event, silent ) {
+ var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length;
+ if ( moved && !silent ) {
+ this._trigger( "focus", event );
+ }
+ return moved;
+ },
+
+ open: function() {
+ var that = this;
+ if ( this._isOpen ) {
+ if ( this._moveToTop() ) {
+ this._focusTabbable();
+ }
+ return;
+ }
+
+ this._isOpen = true;
+ this.opener = $( this.document[0].activeElement );
+
+ this._size();
+ this._position();
+ this._createOverlay();
+ this._moveToTop( null, true );
+ this._show( this.uiDialog, this.options.show, function() {
+ that._focusTabbable();
+ that._trigger("focus");
+ });
+
+ this._trigger("open");
+ },
+
+ _focusTabbable: function() {
+ // Set focus to the first match:
+ // 1. First element inside the dialog matching [autofocus]
+ // 2. Tabbable element inside the content element
+ // 3. Tabbable element inside the buttonpane
+ // 4. The close button
+ // 5. The dialog itself
+ var hasFocus = this.element.find("[autofocus]");
+ if ( !hasFocus.length ) {
+ hasFocus = this.element.find(":tabbable");
+ }
+ if ( !hasFocus.length ) {
+ hasFocus = this.uiDialogButtonPane.find(":tabbable");
+ }
+ if ( !hasFocus.length ) {
+ hasFocus = this.uiDialogTitlebarClose.filter(":tabbable");
+ }
+ if ( !hasFocus.length ) {
+ hasFocus = this.uiDialog;
+ }
+ hasFocus.eq( 0 ).focus();
+ },
+
+ _keepFocus: function( event ) {
+ function checkFocus() {
+ var activeElement = this.document[0].activeElement,
+ isActive = this.uiDialog[0] === activeElement ||
+ $.contains( this.uiDialog[0], activeElement );
+ if ( !isActive ) {
+ this._focusTabbable();
+ }
+ }
+ event.preventDefault();
+ checkFocus.call( this );
+ // support: IE
+ // IE <= 8 doesn't prevent moving focus even with event.preventDefault()
+ // so we check again later
+ this._delay( checkFocus );
+ },
+
+ _createWrapper: function() {
+ this.uiDialog = $("<div>")
+ .addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " +
+ this.options.dialogClass )
+ .hide()
+ .attr({
+ // Setting tabIndex makes the div focusable
+ tabIndex: -1,
+ role: "dialog"
+ })
+ .appendTo( this._appendTo() );
+
+ this._on( this.uiDialog, {
+ keydown: function( event ) {
+ if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+ event.keyCode === $.ui.keyCode.ESCAPE ) {
+ event.preventDefault();
+ this.close( event );
+ return;
+ }
+
+ // prevent tabbing out of dialogs
+ if ( event.keyCode !== $.ui.keyCode.TAB ) {
+ return;
+ }
+ var tabbables = this.uiDialog.find(":tabbable"),
+ first = tabbables.filter(":first"),
+ last = tabbables.filter(":last");
+
+ if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
+ first.focus( 1 );
+ event.preventDefault();
+ } else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
+ last.focus( 1 );
+ event.preventDefault();
+ }
+ },
+ mousedown: function( event ) {
+ if ( this._moveToTop( event ) ) {
+ this._focusTabbable();
+ }
+ }
+ });
+
+ // We assume that any existing aria-describedby attribute means
+ // that the dialog content is marked up properly
+ // otherwise we brute force the content as the description
+ if ( !this.element.find("[aria-describedby]").length ) {
+ this.uiDialog.attr({
+ "aria-describedby": this.element.uniqueId().attr("id")
+ });
+ }
+ },
+
+ _createTitlebar: function() {
+ var uiDialogTitle;
+
+ this.uiDialogTitlebar = $("<div>")
+ .addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix")
+ .prependTo( this.uiDialog );
+ this._on( this.uiDialogTitlebar, {
+ mousedown: function( event ) {
+ // Don't prevent click on close button (#8838)
+ // Focusing a dialog that is partially scrolled out of view
+ // causes the browser to scroll it into view, preventing the click event
+ if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) {
+ // Dialog isn't getting focus when dragging (#8063)
+ this.uiDialog.focus();
+ }
+ }
+ });
+
+ this.uiDialogTitlebarClose = $("<button></button>")
+ .button({
+ label: this.options.closeText,
+ icons: {
+ primary: "ui-icon-closethick"
+ },
+ text: false
+ })
+ .addClass("ui-dialog-titlebar-close")
+ .appendTo( this.uiDialogTitlebar );
+ this._on( this.uiDialogTitlebarClose, {
+ click: function( event ) {
+ event.preventDefault();
+ this.close( event );
+ }
+ });
+
+ uiDialogTitle = $("<span>")
+ .uniqueId()
+ .addClass("ui-dialog-title")
+ .prependTo( this.uiDialogTitlebar );
+ this._title( uiDialogTitle );
+
+ this.uiDialog.attr({
+ "aria-labelledby": uiDialogTitle.attr("id")
+ });
+ },
+
+ _title: function( title ) {
+ if ( !this.options.title ) {
+ title.html("&#160;");
+ }
+ title.text( this.options.title );
+ },
+
+ _createButtonPane: function() {
+ this.uiDialogButtonPane = $("<div>")
+ .addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");
+
+ this.uiButtonSet = $("<div>")
+ .addClass("ui-dialog-buttonset")
+ .appendTo( this.uiDialogButtonPane );
+
+ this._createButtons();
+ },
+
+ _createButtons: function() {
+ var that = this,
+ buttons = this.options.buttons;
+
+ // if we already have a button pane, remove it
+ this.uiDialogButtonPane.remove();
+ this.uiButtonSet.empty();
+
+ if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) {
+ this.uiDialog.removeClass("ui-dialog-buttons");
+ return;
+ }
+
+ $.each( buttons, function( name, props ) {
+ var click, buttonOptions;
+ props = $.isFunction( props ) ?
+ { click: props, text: name } :
+ props;
+ // Default to a non-submitting button
+ props = $.extend( { type: "button" }, props );
+ // Change the context for the click callback to be the main element
+ click = props.click;
+ props.click = function() {
+ click.apply( that.element[0], arguments );
+ };
+ buttonOptions = {
+ icons: props.icons,
+ text: props.showText
+ };
+ delete props.icons;
+ delete props.showText;
+ $( "<button></button>", props )
+ .button( buttonOptions )
+ .appendTo( that.uiButtonSet );
+ });
+ this.uiDialog.addClass("ui-dialog-buttons");
+ this.uiDialogButtonPane.appendTo( this.uiDialog );
+ },
+
+ _makeDraggable: function() {
+ var that = this,
+ options = this.options;
+
+ function filteredUi( ui ) {
+ return {
+ position: ui.position,
+ offset: ui.offset
+ };
+ }
+
+ this.uiDialog.draggable({
+ cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
+ handle: ".ui-dialog-titlebar",
+ containment: "document",
+ start: function( event, ui ) {
+ $( this ).addClass("ui-dialog-dragging");
+ that._blockFrames();
+ that._trigger( "dragStart", event, filteredUi( ui ) );
+ },
+ drag: function( event, ui ) {
+ that._trigger( "drag", event, filteredUi( ui ) );
+ },
+ stop: function( event, ui ) {
+ options.position = [
+ ui.position.left - that.document.scrollLeft(),
+ ui.position.top - that.document.scrollTop()
+ ];
+ $( this ).removeClass("ui-dialog-dragging");
+ that._unblockFrames();
+ that._trigger( "dragStop", event, filteredUi( ui ) );
+ }
+ });
+ },
+
+ _makeResizable: function() {
+ var that = this,
+ options = this.options,
+ handles = options.resizable,
+ // .ui-resizable has position: relative defined in the stylesheet
+ // but dialogs have to use absolute or fixed positioning
+ position = this.uiDialog.css("position"),
+ resizeHandles = typeof handles === "string" ?
+ handles :
+ "n,e,s,w,se,sw,ne,nw";
+
+ function filteredUi( ui ) {
+ return {
+ originalPosition: ui.originalPosition,
+ originalSize: ui.originalSize,
+ position: ui.position,
+ size: ui.size
+ };
+ }
+
+ this.uiDialog.resizable({
+ cancel: ".ui-dialog-content",
+ containment: "document",
+ alsoResize: this.element,
+ maxWidth: options.maxWidth,
+ maxHeight: options.maxHeight,
+ minWidth: options.minWidth,
+ minHeight: this._minHeight(),
+ handles: resizeHandles,
+ start: function( event, ui ) {
+ $( this ).addClass("ui-dialog-resizing");
+ that._blockFrames();
+ that._trigger( "resizeStart", event, filteredUi( ui ) );
+ },
+ resize: function( event, ui ) {
+ that._trigger( "resize", event, filteredUi( ui ) );
+ },
+ stop: function( event, ui ) {
+ options.height = $( this ).height();
+ options.width = $( this ).width();
+ $( this ).removeClass("ui-dialog-resizing");
+ that._unblockFrames();
+ that._trigger( "resizeStop", event, filteredUi( ui ) );
+ }
+ })
+ .css( "position", position );
+ },
+
+ _minHeight: function() {
+ var options = this.options;
+
+ return options.height === "auto" ?
+ options.minHeight :
+ Math.min( options.minHeight, options.height );
+ },
+
+ _position: function() {
+ // Need to show the dialog to get the actual offset in the position plugin
+ var isVisible = this.uiDialog.is(":visible");
+ if ( !isVisible ) {
+ this.uiDialog.show();
+ }
+ this.uiDialog.position( this.options.position );
+ if ( !isVisible ) {
+ this.uiDialog.hide();
+ }
+ },
+
+ _setOptions: function( options ) {
+ var that = this,
+ resize = false,
+ resizableOptions = {};
+
+ $.each( options, function( key, value ) {
+ that._setOption( key, value );
+
+ if ( key in sizeRelatedOptions ) {
+ resize = true;
+ }
+ if ( key in resizableRelatedOptions ) {
+ resizableOptions[ key ] = value;
+ }
+ });
+
+ if ( resize ) {
+ this._size();
+ this._position();
+ }
+ if ( this.uiDialog.is(":data(ui-resizable)") ) {
+ this.uiDialog.resizable( "option", resizableOptions );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ /*jshint maxcomplexity:15*/
+ var isDraggable, isResizable,
+ uiDialog = this.uiDialog;
+
+ if ( key === "dialogClass" ) {
+ uiDialog
+ .removeClass( this.options.dialogClass )
+ .addClass( value );
+ }
+
+ if ( key === "disabled" ) {
+ return;
+ }
+
+ this._super( key, value );
+
+ if ( key === "appendTo" ) {
+ this.uiDialog.appendTo( this._appendTo() );
+ }
+
+ if ( key === "buttons" ) {
+ this._createButtons();
+ }
+
+ if ( key === "closeText" ) {
+ this.uiDialogTitlebarClose.button({
+ // Ensure that we always pass a string
+ label: "" + value
+ });
+ }
+
+ if ( key === "draggable" ) {
+ isDraggable = uiDialog.is(":data(ui-draggable)");
+ if ( isDraggable && !value ) {
+ uiDialog.draggable("destroy");
+ }
+
+ if ( !isDraggable && value ) {
+ this._makeDraggable();
+ }
+ }
+
+ if ( key === "position" ) {
+ this._position();
+ }
+
+ if ( key === "resizable" ) {
+ // currently resizable, becoming non-resizable
+ isResizable = uiDialog.is(":data(ui-resizable)");
+ if ( isResizable && !value ) {
+ uiDialog.resizable("destroy");
+ }
+
+ // currently resizable, changing handles
+ if ( isResizable && typeof value === "string" ) {
+ uiDialog.resizable( "option", "handles", value );
+ }
+
+ // currently non-resizable, becoming resizable
+ if ( !isResizable && value !== false ) {
+ this._makeResizable();
+ }
+ }
+
+ if ( key === "title" ) {
+ this._title( this.uiDialogTitlebar.find(".ui-dialog-title") );
+ }
+ },
+
+ _size: function() {
+ // If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+ // divs will both have width and height set, so we need to reset them
+ var nonContentHeight, minContentHeight, maxContentHeight,
+ options = this.options;
+
+ // Reset content sizing
+ this.element.show().css({
+ width: "auto",
+ minHeight: 0,
+ maxHeight: "none",
+ height: 0
+ });
+
+ if ( options.minWidth > options.width ) {
+ options.width = options.minWidth;
+ }
+
+ // reset wrapper sizing
+ // determine the height of all the non-content elements
+ nonContentHeight = this.uiDialog.css({
+ height: "auto",
+ width: options.width
+ })
+ .outerHeight();
+ minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
+ maxContentHeight = typeof options.maxHeight === "number" ?
+ Math.max( 0, options.maxHeight - nonContentHeight ) :
+ "none";
+
+ if ( options.height === "auto" ) {
+ this.element.css({
+ minHeight: minContentHeight,
+ maxHeight: maxContentHeight,
+ height: "auto"
+ });
+ } else {
+ this.element.height( Math.max( 0, options.height - nonContentHeight ) );
+ }
+
+ if (this.uiDialog.is(":data(ui-resizable)") ) {
+ this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
+ }
+ },
+
+ _blockFrames: function() {
+ this.iframeBlocks = this.document.find( "iframe" ).map(function() {
+ var iframe = $( this );
+
+ return $( "<div>" )
+ .css({
+ position: "absolute",
+ width: iframe.outerWidth(),
+ height: iframe.outerHeight()
+ })
+ .appendTo( iframe.parent() )
+ .offset( iframe.offset() )[0];
+ });
+ },
+
+ _unblockFrames: function() {
+ if ( this.iframeBlocks ) {
+ this.iframeBlocks.remove();
+ delete this.iframeBlocks;
+ }
+ },
+
+ _allowInteraction: function( event ) {
+ if ( $( event.target ).closest(".ui-dialog").length ) {
+ return true;
+ }
+
+ // TODO: Remove hack when datepicker implements
+ // the .ui-front logic (#8989)
+ return !!$( event.target ).closest(".ui-datepicker").length;
+ },
+
+ _createOverlay: function() {
+ if ( !this.options.modal ) {
+ return;
+ }
+
+ var that = this,
+ widgetFullName = this.widgetFullName;
+ if ( !$.ui.dialog.overlayInstances ) {
+ // Prevent use of anchors and inputs.
+ // We use a delay in case the overlay is created from an
+ // event that we're going to be cancelling. (#2804)
+ this._delay(function() {
+ // Handle .dialog().dialog("close") (#4065)
+ if ( $.ui.dialog.overlayInstances ) {
+ this.document.bind( "focusin.dialog", function( event ) {
+ if ( !that._allowInteraction( event ) ) {
+ event.preventDefault();
+ $(".ui-dialog:visible:last .ui-dialog-content")
+ .data( widgetFullName )._focusTabbable();
+ }
+ });
+ }
+ });
+ }
+
+ this.overlay = $("<div>")
+ .addClass("ui-widget-overlay ui-front")
+ .appendTo( this._appendTo() );
+ this._on( this.overlay, {
+ mousedown: "_keepFocus"
+ });
+ $.ui.dialog.overlayInstances++;
+ },
+
+ _destroyOverlay: function() {
+ if ( !this.options.modal ) {
+ return;
+ }
+
+ if ( this.overlay ) {
+ $.ui.dialog.overlayInstances--;
+
+ if ( !$.ui.dialog.overlayInstances ) {
+ this.document.unbind( "focusin.dialog" );
+ }
+ this.overlay.remove();
+ this.overlay = null;
+ }
+ }
+});
+
+$.ui.dialog.overlayInstances = 0;
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ // position option with array notation
+ // just override with old implementation
+ $.widget( "ui.dialog", $.ui.dialog, {
+ _position: function() {
+ var position = this.options.position,
+ myAt = [],
+ offset = [ 0, 0 ],
+ isVisible;
+
+ if ( position ) {
+ if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) {
+ myAt = position.split ? position.split(" ") : [ position[0], position[1] ];
+ if ( myAt.length === 1 ) {
+ myAt[1] = myAt[0];
+ }
+
+ $.each( [ "left", "top" ], function( i, offsetPosition ) {
+ if ( +myAt[ i ] === myAt[ i ] ) {
+ offset[ i ] = myAt[ i ];
+ myAt[ i ] = offsetPosition;
+ }
+ });
+
+ position = {
+ my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " +
+ myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]),
+ at: myAt.join(" ")
+ };
+ }
+
+ position = $.extend( {}, $.ui.dialog.prototype.options.position, position );
+ } else {
+ position = $.ui.dialog.prototype.options.position;
+ }
+
+ // need to show the dialog to get the actual offset in the position plugin
+ isVisible = this.uiDialog.is(":visible");
+ if ( !isVisible ) {
+ this.uiDialog.show();
+ }
+ this.uiDialog.position( position );
+ if ( !isVisible ) {
+ this.uiDialog.hide();
+ }
+ }
+ });
+}
+
+}( jQuery ) );
+
+(function( $, undefined ) {
+
+var rvertical = /up|down|vertical/,
+ rpositivemotion = /up|left|vertical|horizontal/;
+
+$.effects.effect.blind = function( o, done ) {
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ direction = o.direction || "up",
+ vertical = rvertical.test( direction ),
+ ref = vertical ? "height" : "width",
+ ref2 = vertical ? "top" : "left",
+ motion = rpositivemotion.test( direction ),
+ animation = {},
+ show = mode === "show",
+ wrapper, distance, margin;
+
+ // if already wrapped, the wrapper's properties are my property. #6245
+ if ( el.parent().is( ".ui-effects-wrapper" ) ) {
+ $.effects.save( el.parent(), props );
+ } else {
+ $.effects.save( el, props );
+ }
+ el.show();
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+
+ distance = wrapper[ ref ]();
+ margin = parseFloat( wrapper.css( ref2 ) ) || 0;
+
+ animation[ ref ] = show ? distance : 0;
+ if ( !motion ) {
+ el
+ .css( vertical ? "bottom" : "right", 0 )
+ .css( vertical ? "top" : "left", "auto" )
+ .css({ position: "absolute" });
+
+ animation[ ref2 ] = show ? margin : distance + margin;
+ }
+
+ // start at 0 if we are showing
+ if ( show ) {
+ wrapper.css( ref, 0 );
+ if ( ! motion ) {
+ wrapper.css( ref2, margin + distance );
+ }
+ }
+
+ // Animate
+ wrapper.animate( animation, {
+ duration: o.duration,
+ easing: o.easing,
+ queue: false,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.bounce = function( o, done ) {
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+
+ // defaults:
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ hide = mode === "hide",
+ show = mode === "show",
+ direction = o.direction || "up",
+ distance = o.distance,
+ times = o.times || 5,
+
+ // number of internal animations
+ anims = times * 2 + ( show || hide ? 1 : 0 ),
+ speed = o.duration / anims,
+ easing = o.easing,
+
+ // utility:
+ ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+ motion = ( direction === "up" || direction === "left" ),
+ i,
+ upAnim,
+ downAnim,
+
+ // we will need to re-assemble the queue to stack our animations in place
+ queue = el.queue(),
+ queuelen = queue.length;
+
+ // Avoid touching opacity to prevent clearType and PNG issues in IE
+ if ( show || hide ) {
+ props.push( "opacity" );
+ }
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el ); // Create Wrapper
+
+ // default distance for the BIGGEST bounce is the outer Distance / 3
+ if ( !distance ) {
+ distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
+ }
+
+ if ( show ) {
+ downAnim = { opacity: 1 };
+ downAnim[ ref ] = 0;
+
+ // if we are showing, force opacity 0 and set the initial position
+ // then do the "first" animation
+ el.css( "opacity", 0 )
+ .css( ref, motion ? -distance * 2 : distance * 2 )
+ .animate( downAnim, speed, easing );
+ }
+
+ // start at the smallest distance if we are hiding
+ if ( hide ) {
+ distance = distance / Math.pow( 2, times - 1 );
+ }
+
+ downAnim = {};
+ downAnim[ ref ] = 0;
+ // Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
+ for ( i = 0; i < times; i++ ) {
+ upAnim = {};
+ upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+ el.animate( upAnim, speed, easing )
+ .animate( downAnim, speed, easing );
+
+ distance = hide ? distance * 2 : distance / 2;
+ }
+
+ // Last Bounce when Hiding
+ if ( hide ) {
+ upAnim = { opacity: 0 };
+ upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+ el.animate( upAnim, speed, easing );
+ }
+
+ el.queue(function() {
+ if ( hide ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+ // inject all the animations we just queued to be first in line (after "inprogress")
+ if ( queuelen > 1) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ el.dequeue();
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.clip = function( o, done ) {
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ direction = o.direction || "vertical",
+ vert = direction === "vertical",
+ size = vert ? "height" : "width",
+ position = vert ? "top" : "left",
+ animation = {},
+ wrapper, animate, distance;
+
+ // Save & Show
+ $.effects.save( el, props );
+ el.show();
+
+ // Create Wrapper
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+ animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
+ distance = animate[ size ]();
+
+ // Shift
+ if ( show ) {
+ animate.css( size, 0 );
+ animate.css( position, distance / 2 );
+ }
+
+ // Create Animation Object:
+ animation[ size ] = show ? distance : 0;
+ animation[ position ] = show ? 0 : distance / 2;
+
+ // Animate
+ animate.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( !show ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.drop = function( o, done ) {
+
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ direction = o.direction || "left",
+ ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+ motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
+ animation = {
+ opacity: show ? 1 : 0
+ },
+ distance;
+
+ // Adjust
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+
+ distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;
+
+ if ( show ) {
+ el
+ .css( "opacity", 0 )
+ .css( ref, motion === "pos" ? -distance : distance );
+ }
+
+ // Animation
+ animation[ ref ] = ( show ?
+ ( motion === "pos" ? "+=" : "-=" ) :
+ ( motion === "pos" ? "-=" : "+=" ) ) +
+ distance;
+
+ // Animate
+ el.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.explode = function( o, done ) {
+
+ var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
+ cells = rows,
+ el = $( this ),
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+
+ // show and then visibility:hidden the element before calculating offset
+ offset = el.show().css( "visibility", "hidden" ).offset(),
+
+ // width and height of a piece
+ width = Math.ceil( el.outerWidth() / cells ),
+ height = Math.ceil( el.outerHeight() / rows ),
+ pieces = [],
+
+ // loop
+ i, j, left, top, mx, my;
+
+ // children animate complete:
+ function childComplete() {
+ pieces.push( this );
+ if ( pieces.length === rows * cells ) {
+ animComplete();
+ }
+ }
+
+ // clone the element for each row and cell.
+ for( i = 0; i < rows ; i++ ) { // ===>
+ top = offset.top + i * height;
+ my = i - ( rows - 1 ) / 2 ;
+
+ for( j = 0; j < cells ; j++ ) { // |||
+ left = offset.left + j * width;
+ mx = j - ( cells - 1 ) / 2 ;
+
+ // Create a clone of the now hidden main element that will be absolute positioned
+ // within a wrapper div off the -left and -top equal to size of our pieces
+ el
+ .clone()
+ .appendTo( "body" )
+ .wrap( "<div></div>" )
+ .css({
+ position: "absolute",
+ visibility: "visible",
+ left: -j * width,
+ top: -i * height
+ })
+
+ // select the wrapper - make it overflow: hidden and absolute positioned based on
+ // where the original was located +left and +top equal to the size of pieces
+ .parent()
+ .addClass( "ui-effects-explode" )
+ .css({
+ position: "absolute",
+ overflow: "hidden",
+ width: width,
+ height: height,
+ left: left + ( show ? mx * width : 0 ),
+ top: top + ( show ? my * height : 0 ),
+ opacity: show ? 0 : 1
+ }).animate({
+ left: left + ( show ? 0 : mx * width ),
+ top: top + ( show ? 0 : my * height ),
+ opacity: show ? 1 : 0
+ }, o.duration || 500, o.easing, childComplete );
+ }
+ }
+
+ function animComplete() {
+ el.css({
+ visibility: "visible"
+ });
+ $( pieces ).remove();
+ if ( !show ) {
+ el.hide();
+ }
+ done();
+ }
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.fade = function( o, done ) {
+ var el = $( this ),
+ mode = $.effects.setMode( el, o.mode || "toggle" );
+
+ el.animate({
+ opacity: mode
+ }, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: done
+ });
+};
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+$.effects.effect.fold = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "hide" ),
+ show = mode === "show",
+ hide = mode === "hide",
+ size = o.size || 15,
+ percent = /([0-9]+)%/.exec( size ),
+ horizFirst = !!o.horizFirst,
+ widthFirst = show !== horizFirst,
+ ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
+ duration = o.duration / 2,
+ wrapper, distance,
+ animation1 = {},
+ animation2 = {};
+
+ $.effects.save( el, props );
+ el.show();
+
+ // Create Wrapper
+ wrapper = $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+ distance = widthFirst ?
+ [ wrapper.width(), wrapper.height() ] :
+ [ wrapper.height(), wrapper.width() ];
+
+ if ( percent ) {
+ size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
+ }
+ if ( show ) {
+ wrapper.css( horizFirst ? {
+ height: 0,
+ width: size
+ } : {
+ height: size,
+ width: 0
+ });
+ }
+
+ // Animation
+ animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
+ animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;
+
+ // Animate
+ wrapper
+ .animate( animation1, duration, o.easing )
+ .animate( animation2, duration, o.easing, function() {
+ if ( hide ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.highlight = function( o, done ) {
+ var elem = $( this ),
+ props = [ "backgroundImage", "backgroundColor", "opacity" ],
+ mode = $.effects.setMode( elem, o.mode || "show" ),
+ animation = {
+ backgroundColor: elem.css( "backgroundColor" )
+ };
+
+ if (mode === "hide") {
+ animation.opacity = 0;
+ }
+
+ $.effects.save( elem, props );
+
+ elem
+ .show()
+ .css({
+ backgroundImage: "none",
+ backgroundColor: o.color || "#ffff99"
+ })
+ .animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ elem.hide();
+ }
+ $.effects.restore( elem, props );
+ done();
+ }
+ });
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.pulsate = function( o, done ) {
+ var elem = $( this ),
+ mode = $.effects.setMode( elem, o.mode || "show" ),
+ show = mode === "show",
+ hide = mode === "hide",
+ showhide = ( show || mode === "hide" ),
+
+ // showing or hiding leaves of the "last" animation
+ anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
+ duration = o.duration / anims,
+ animateTo = 0,
+ queue = elem.queue(),
+ queuelen = queue.length,
+ i;
+
+ if ( show || !elem.is(":visible")) {
+ elem.css( "opacity", 0 ).show();
+ animateTo = 1;
+ }
+
+ // anims - 1 opacity "toggles"
+ for ( i = 1; i < anims; i++ ) {
+ elem.animate({
+ opacity: animateTo
+ }, duration, o.easing );
+ animateTo = 1 - animateTo;
+ }
+
+ elem.animate({
+ opacity: animateTo
+ }, duration, o.easing);
+
+ elem.queue(function() {
+ if ( hide ) {
+ elem.hide();
+ }
+ done();
+ });
+
+ // We just queued up "anims" animations, we need to put them next in the queue
+ if ( queuelen > 1 ) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ elem.dequeue();
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.puff = function( o, done ) {
+ var elem = $( this ),
+ mode = $.effects.setMode( elem, o.mode || "hide" ),
+ hide = mode === "hide",
+ percent = parseInt( o.percent, 10 ) || 150,
+ factor = percent / 100,
+ original = {
+ height: elem.height(),
+ width: elem.width(),
+ outerHeight: elem.outerHeight(),
+ outerWidth: elem.outerWidth()
+ };
+
+ $.extend( o, {
+ effect: "scale",
+ queue: false,
+ fade: true,
+ mode: mode,
+ complete: done,
+ percent: hide ? percent : 100,
+ from: hide ?
+ original :
+ {
+ height: original.height * factor,
+ width: original.width * factor,
+ outerHeight: original.outerHeight * factor,
+ outerWidth: original.outerWidth * factor
+ }
+ });
+
+ elem.effect( o );
+};
+
+$.effects.effect.scale = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ options = $.extend( true, {}, o ),
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ percent = parseInt( o.percent, 10 ) ||
+ ( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
+ direction = o.direction || "both",
+ origin = o.origin,
+ original = {
+ height: el.height(),
+ width: el.width(),
+ outerHeight: el.outerHeight(),
+ outerWidth: el.outerWidth()
+ },
+ factor = {
+ y: direction !== "horizontal" ? (percent / 100) : 1,
+ x: direction !== "vertical" ? (percent / 100) : 1
+ };
+
+ // We are going to pass this effect to the size effect:
+ options.effect = "size";
+ options.queue = false;
+ options.complete = done;
+
+ // Set default origin and restore for show/hide
+ if ( mode !== "effect" ) {
+ options.origin = origin || ["middle","center"];
+ options.restore = true;
+ }
+
+ options.from = o.from || ( mode === "show" ? {
+ height: 0,
+ width: 0,
+ outerHeight: 0,
+ outerWidth: 0
+ } : original );
+ options.to = {
+ height: original.height * factor.y,
+ width: original.width * factor.x,
+ outerHeight: original.outerHeight * factor.y,
+ outerWidth: original.outerWidth * factor.x
+ };
+
+ // Fade option to support puff
+ if ( options.fade ) {
+ if ( mode === "show" ) {
+ options.from.opacity = 0;
+ options.to.opacity = 1;
+ }
+ if ( mode === "hide" ) {
+ options.from.opacity = 1;
+ options.to.opacity = 0;
+ }
+ }
+
+ // Animate
+ el.effect( options );
+
+};
+
+$.effects.effect.size = function( o, done ) {
+
+ // Create element
+ var original, baseline, factor,
+ el = $( this ),
+ props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],
+
+ // Always restore
+ props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],
+
+ // Copy for children
+ props2 = [ "width", "height", "overflow" ],
+ cProps = [ "fontSize" ],
+ vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
+ hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
+
+ // Set options
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ restore = o.restore || mode !== "effect",
+ scale = o.scale || "both",
+ origin = o.origin || [ "middle", "center" ],
+ position = el.css( "position" ),
+ props = restore ? props0 : props1,
+ zero = {
+ height: 0,
+ width: 0,
+ outerHeight: 0,
+ outerWidth: 0
+ };
+
+ if ( mode === "show" ) {
+ el.show();
+ }
+ original = {
+ height: el.height(),
+ width: el.width(),
+ outerHeight: el.outerHeight(),
+ outerWidth: el.outerWidth()
+ };
+
+ if ( o.mode === "toggle" && mode === "show" ) {
+ el.from = o.to || zero;
+ el.to = o.from || original;
+ } else {
+ el.from = o.from || ( mode === "show" ? zero : original );
+ el.to = o.to || ( mode === "hide" ? zero : original );
+ }
+
+ // Set scaling factor
+ factor = {
+ from: {
+ y: el.from.height / original.height,
+ x: el.from.width / original.width
+ },
+ to: {
+ y: el.to.height / original.height,
+ x: el.to.width / original.width
+ }
+ };
+
+ // Scale the css box
+ if ( scale === "box" || scale === "both" ) {
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ props = props.concat( vProps );
+ el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
+ el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
+ }
+
+ // Horizontal props scaling
+ if ( factor.from.x !== factor.to.x ) {
+ props = props.concat( hProps );
+ el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
+ el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
+ }
+ }
+
+ // Scale the content
+ if ( scale === "content" || scale === "both" ) {
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ props = props.concat( cProps ).concat( props2 );
+ el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
+ el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
+ }
+ }
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+ el.css( "overflow", "hidden" ).css( el.from );
+
+ // Adjust
+ if (origin) { // Calculate baseline shifts
+ baseline = $.effects.getBaseline( origin, original );
+ el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
+ el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
+ el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
+ el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
+ }
+ el.css( el.from ); // set top & left
+
+ // Animate
+ if ( scale === "content" || scale === "both" ) { // Scale the children
+
+ // Add margins/font-size
+ vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
+ hProps = hProps.concat([ "marginLeft", "marginRight" ]);
+ props2 = props0.concat(vProps).concat(hProps);
+
+ el.find( "*[width]" ).each( function(){
+ var child = $( this ),
+ c_original = {
+ height: child.height(),
+ width: child.width(),
+ outerHeight: child.outerHeight(),
+ outerWidth: child.outerWidth()
+ };
+ if (restore) {
+ $.effects.save(child, props2);
+ }
+
+ child.from = {
+ height: c_original.height * factor.from.y,
+ width: c_original.width * factor.from.x,
+ outerHeight: c_original.outerHeight * factor.from.y,
+ outerWidth: c_original.outerWidth * factor.from.x
+ };
+ child.to = {
+ height: c_original.height * factor.to.y,
+ width: c_original.width * factor.to.x,
+ outerHeight: c_original.height * factor.to.y,
+ outerWidth: c_original.width * factor.to.x
+ };
+
+ // Vertical props scaling
+ if ( factor.from.y !== factor.to.y ) {
+ child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
+ child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
+ }
+
+ // Horizontal props scaling
+ if ( factor.from.x !== factor.to.x ) {
+ child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
+ child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
+ }
+
+ // Animate children
+ child.css( child.from );
+ child.animate( child.to, o.duration, o.easing, function() {
+
+ // Restore children
+ if ( restore ) {
+ $.effects.restore( child, props2 );
+ }
+ });
+ });
+ }
+
+ // Animate
+ el.animate( el.to, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( el.to.opacity === 0 ) {
+ el.css( "opacity", el.from.opacity );
+ }
+ if( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ if ( !restore ) {
+
+ // we need to calculate our new positioning based on the scaling
+ if ( position === "static" ) {
+ el.css({
+ position: "relative",
+ top: el.to.top,
+ left: el.to.left
+ });
+ } else {
+ $.each([ "top", "left" ], function( idx, pos ) {
+ el.css( pos, function( _, str ) {
+ var val = parseInt( str, 10 ),
+ toRef = idx ? el.to.left : el.to.top;
+
+ // if original was "auto", recalculate the new value from wrapper
+ if ( str === "auto" ) {
+ return toRef + "px";
+ }
+
+ return val + toRef + "px";
+ });
+ });
+ }
+ }
+
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.shake = function( o, done ) {
+
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
+ mode = $.effects.setMode( el, o.mode || "effect" ),
+ direction = o.direction || "left",
+ distance = o.distance || 20,
+ times = o.times || 3,
+ anims = times * 2 + 1,
+ speed = Math.round(o.duration/anims),
+ ref = (direction === "up" || direction === "down") ? "top" : "left",
+ positiveMotion = (direction === "up" || direction === "left"),
+ animation = {},
+ animation1 = {},
+ animation2 = {},
+ i,
+
+ // we will need to re-assemble the queue to stack our animations in place
+ queue = el.queue(),
+ queuelen = queue.length;
+
+ $.effects.save( el, props );
+ el.show();
+ $.effects.createWrapper( el );
+
+ // Animation
+ animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
+ animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
+ animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
+
+ // Animate
+ el.animate( animation, speed, o.easing );
+
+ // Shakes
+ for ( i = 1; i < times; i++ ) {
+ el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
+ }
+ el
+ .animate( animation1, speed, o.easing )
+ .animate( animation, speed / 2, o.easing )
+ .queue(function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ });
+
+ // inject all the animations we just queued to be first in line (after "inprogress")
+ if ( queuelen > 1) {
+ queue.splice.apply( queue,
+ [ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
+ }
+ el.dequeue();
+
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.slide = function( o, done ) {
+
+ // Create element
+ var el = $( this ),
+ props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
+ mode = $.effects.setMode( el, o.mode || "show" ),
+ show = mode === "show",
+ direction = o.direction || "left",
+ ref = (direction === "up" || direction === "down") ? "top" : "left",
+ positiveMotion = (direction === "up" || direction === "left"),
+ distance,
+ animation = {};
+
+ // Adjust
+ $.effects.save( el, props );
+ el.show();
+ distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );
+
+ $.effects.createWrapper( el ).css({
+ overflow: "hidden"
+ });
+
+ if ( show ) {
+ el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
+ }
+
+ // Animation
+ animation[ ref ] = ( show ?
+ ( positiveMotion ? "+=" : "-=") :
+ ( positiveMotion ? "-=" : "+=")) +
+ distance;
+
+ // Animate
+ el.animate( animation, {
+ queue: false,
+ duration: o.duration,
+ easing: o.easing,
+ complete: function() {
+ if ( mode === "hide" ) {
+ el.hide();
+ }
+ $.effects.restore( el, props );
+ $.effects.removeWrapper( el );
+ done();
+ }
+ });
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.effects.effect.transfer = function( o, done ) {
+ var elem = $( this ),
+ target = $( o.to ),
+ targetFixed = target.css( "position" ) === "fixed",
+ body = $("body"),
+ fixTop = targetFixed ? body.scrollTop() : 0,
+ fixLeft = targetFixed ? body.scrollLeft() : 0,
+ endPosition = target.offset(),
+ animation = {
+ top: endPosition.top - fixTop ,
+ left: endPosition.left - fixLeft ,
+ height: target.innerHeight(),
+ width: target.innerWidth()
+ },
+ startPosition = elem.offset(),
+ transfer = $( "<div class='ui-effects-transfer'></div>" )
+ .appendTo( document.body )
+ .addClass( o.className )
+ .css({
+ top: startPosition.top - fixTop ,
+ left: startPosition.left - fixLeft ,
+ height: elem.innerHeight(),
+ width: elem.innerWidth(),
+ position: targetFixed ? "fixed" : "absolute"
+ })
+ .animate( animation, o.duration, o.easing, function() {
+ transfer.remove();
+ done();
+ });
+};
+
+})(jQuery);
+
+(function( $, undefined ) {
+
+$.widget( "ui.menu", {
+ version: "1.10.2",
+ defaultElement: "<ul>",
+ delay: 300,
+ options: {
+ icons: {
+ submenu: "ui-icon-carat-1-e"
+ },
+ menus: "ul",
+ position: {
+ my: "left top",
+ at: "right top"
+ },
+ role: "menu",
+
+ // callbacks
+ blur: null,
+ focus: null,
+ select: null
+ },
+
+ _create: function() {
+ this.activeMenu = this.element;
+ // flag used to prevent firing of the click handler
+ // as the event bubbles up through nested menus
+ this.mouseHandled = false;
+ this.element
+ .uniqueId()
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
+ .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
+ .attr({
+ role: this.options.role,
+ tabIndex: 0
+ })
+ // need to catch all clicks on disabled menu
+ // not possible through _on
+ .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
+ if ( this.options.disabled ) {
+ event.preventDefault();
+ }
+ }, this ));
+
+ if ( this.options.disabled ) {
+ this.element
+ .addClass( "ui-state-disabled" )
+ .attr( "aria-disabled", "true" );
+ }
+
+ this._on({
+ // Prevent focus from sticking to links inside menu after clicking
+ // them (focus should always stay on UL during navigation).
+ "mousedown .ui-menu-item > a": function( event ) {
+ event.preventDefault();
+ },
+ "click .ui-state-disabled > a": function( event ) {
+ event.preventDefault();
+ },
+ "click .ui-menu-item:has(a)": function( event ) {
+ var target = $( event.target ).closest( ".ui-menu-item" );
+ if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
+ this.mouseHandled = true;
+
+ this.select( event );
+ // Open submenu on click
+ if ( target.has( ".ui-menu" ).length ) {
+ this.expand( event );
+ } else if ( !this.element.is( ":focus" ) ) {
+ // Redirect focus to the menu
+ this.element.trigger( "focus", [ true ] );
+
+ // If the active item is on the top level, let it stay active.
+ // Otherwise, blur the active item since it is no longer visible.
+ if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
+ clearTimeout( this.timer );
+ }
+ }
+ }
+ },
+ "mouseenter .ui-menu-item": function( event ) {
+ var target = $( event.currentTarget );
+ // Remove ui-state-active class from siblings of the newly focused menu item
+ // to avoid a jump caused by adjacent elements both having a class with a border
+ target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
+ this.focus( event, target );
+ },
+ mouseleave: "collapseAll",
+ "mouseleave .ui-menu": "collapseAll",
+ focus: function( event, keepActiveItem ) {
+ // If there's already an active item, keep it active
+ // If not, activate the first item
+ var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
+
+ if ( !keepActiveItem ) {
+ this.focus( event, item );
+ }
+ },
+ blur: function( event ) {
+ this._delay(function() {
+ if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
+ this.collapseAll( event );
+ }
+ });
+ },
+ keydown: "_keydown"
+ });
+
+ this.refresh();
+
+ // Clicks outside of a menu collapse any open menus
+ this._on( this.document, {
+ click: function( event ) {
+ if ( !$( event.target ).closest( ".ui-menu" ).length ) {
+ this.collapseAll( event );
+ }
+
+ // Reset the mouseHandled flag
+ this.mouseHandled = false;
+ }
+ });
+ },
+
+ _destroy: function() {
+ // Destroy (sub)menus
+ this.element
+ .removeAttr( "aria-activedescendant" )
+ .find( ".ui-menu" ).addBack()
+ .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
+ .removeAttr( "role" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "aria-labelledby" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-disabled" )
+ .removeUniqueId()
+ .show();
+
+ // Destroy menu items
+ this.element.find( ".ui-menu-item" )
+ .removeClass( "ui-menu-item" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-disabled" )
+ .children( "a" )
+ .removeUniqueId()
+ .removeClass( "ui-corner-all ui-state-hover" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-haspopup" )
+ .children().each( function() {
+ var elem = $( this );
+ if ( elem.data( "ui-menu-submenu-carat" ) ) {
+ elem.remove();
+ }
+ });
+
+ // Destroy menu dividers
+ this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
+ },
+
+ _keydown: function( event ) {
+ /*jshint maxcomplexity:20*/
+ var match, prev, character, skip, regex,
+ preventDefault = true;
+
+ function escape( value ) {
+ return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.PAGE_UP:
+ this.previousPage( event );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ this.nextPage( event );
+ break;
+ case $.ui.keyCode.HOME:
+ this._move( "first", "first", event );
+ break;
+ case $.ui.keyCode.END:
+ this._move( "last", "last", event );
+ break;
+ case $.ui.keyCode.UP:
+ this.previous( event );
+ break;
+ case $.ui.keyCode.DOWN:
+ this.next( event );
+ break;
+ case $.ui.keyCode.LEFT:
+ this.collapse( event );
+ break;
+ case $.ui.keyCode.RIGHT:
+ if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
+ this.expand( event );
+ }
+ break;
+ case $.ui.keyCode.ENTER:
+ case $.ui.keyCode.SPACE:
+ this._activate( event );
+ break;
+ case $.ui.keyCode.ESCAPE:
+ this.collapse( event );
+ break;
+ default:
+ preventDefault = false;
+ prev = this.previousFilter || "";
+ character = String.fromCharCode( event.keyCode );
+ skip = false;
+
+ clearTimeout( this.filterTimer );
+
+ if ( character === prev ) {
+ skip = true;
+ } else {
+ character = prev + character;
+ }
+
+ regex = new RegExp( "^" + escape( character ), "i" );
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
+ return regex.test( $( this ).children( "a" ).text() );
+ });
+ match = skip && match.index( this.active.next() ) !== -1 ?
+ this.active.nextAll( ".ui-menu-item" ) :
+ match;
+
+ // If no matches on the current filter, reset to the last character pressed
+ // to move down the menu to the first item that starts with that character
+ if ( !match.length ) {
+ character = String.fromCharCode( event.keyCode );
+ regex = new RegExp( "^" + escape( character ), "i" );
+ match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
+ return regex.test( $( this ).children( "a" ).text() );
+ });
+ }
+
+ if ( match.length ) {
+ this.focus( event, match );
+ if ( match.length > 1 ) {
+ this.previousFilter = character;
+ this.filterTimer = this._delay(function() {
+ delete this.previousFilter;
+ }, 1000 );
+ } else {
+ delete this.previousFilter;
+ }
+ } else {
+ delete this.previousFilter;
+ }
+ }
+
+ if ( preventDefault ) {
+ event.preventDefault();
+ }
+ },
+
+ _activate: function( event ) {
+ if ( !this.active.is( ".ui-state-disabled" ) ) {
+ if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
+ this.expand( event );
+ } else {
+ this.select( event );
+ }
+ }
+ },
+
+ refresh: function() {
+ var menus,
+ icon = this.options.icons.submenu,
+ submenus = this.element.find( this.options.menus );
+
+ // Initialize nested menus
+ submenus.filter( ":not(.ui-menu)" )
+ .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
+ .hide()
+ .attr({
+ role: this.options.role,
+ "aria-hidden": "true",
+ "aria-expanded": "false"
+ })
+ .each(function() {
+ var menu = $( this ),
+ item = menu.prev( "a" ),
+ submenuCarat = $( "<span>" )
+ .addClass( "ui-menu-icon ui-icon " + icon )
+ .data( "ui-menu-submenu-carat", true );
+
+ item
+ .attr( "aria-haspopup", "true" )
+ .prepend( submenuCarat );
+ menu.attr( "aria-labelledby", item.attr( "id" ) );
+ });
+
+ menus = submenus.add( this.element );
+
+ // Don't refresh list items that are already adapted
+ menus.children( ":not(.ui-menu-item):has(a)" )
+ .addClass( "ui-menu-item" )
+ .attr( "role", "presentation" )
+ .children( "a" )
+ .uniqueId()
+ .addClass( "ui-corner-all" )
+ .attr({
+ tabIndex: -1,
+ role: this._itemRole()
+ });
+
+ // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
+ menus.children( ":not(.ui-menu-item)" ).each(function() {
+ var item = $( this );
+ // hyphen, em dash, en dash
+ if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) {
+ item.addClass( "ui-widget-content ui-menu-divider" );
+ }
+ });
+
+ // Add aria-disabled attribute to any disabled menu item
+ menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
+
+ // If the active item has been removed, blur the menu
+ if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+ this.blur();
+ }
+ },
+
+ _itemRole: function() {
+ return {
+ menu: "menuitem",
+ listbox: "option"
+ }[ this.options.role ];
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "icons" ) {
+ this.element.find( ".ui-menu-icon" )
+ .removeClass( this.options.icons.submenu )
+ .addClass( value.submenu );
+ }
+ this._super( key, value );
+ },
+
+ focus: function( event, item ) {
+ var nested, focused;
+ this.blur( event, event && event.type === "focus" );
+
+ this._scrollIntoView( item );
+
+ this.active = item.first();
+ focused = this.active.children( "a" ).addClass( "ui-state-focus" );
+ // Only update aria-activedescendant if there's a role
+ // otherwise we assume focus is managed elsewhere
+ if ( this.options.role ) {
+ this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
+ }
+
+ // Highlight active parent menu item, if any
+ this.active
+ .parent()
+ .closest( ".ui-menu-item" )
+ .children( "a:first" )
+ .addClass( "ui-state-active" );
+
+ if ( event && event.type === "keydown" ) {
+ this._close();
+ } else {
+ this.timer = this._delay(function() {
+ this._close();
+ }, this.delay );
+ }
+
+ nested = item.children( ".ui-menu" );
+ if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
+ this._startOpening(nested);
+ }
+ this.activeMenu = item.parent();
+
+ this._trigger( "focus", event, { item: item } );
+ },
+
+ _scrollIntoView: function( item ) {
+ var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+ if ( this._hasScroll() ) {
+ borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
+ paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
+ offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+ scroll = this.activeMenu.scrollTop();
+ elementHeight = this.activeMenu.height();
+ itemHeight = item.height();
+
+ if ( offset < 0 ) {
+ this.activeMenu.scrollTop( scroll + offset );
+ } else if ( offset + itemHeight > elementHeight ) {
+ this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+ }
+ }
+ },
+
+ blur: function( event, fromFocus ) {
+ if ( !fromFocus ) {
+ clearTimeout( this.timer );
+ }
+
+ if ( !this.active ) {
+ return;
+ }
+
+ this.active.children( "a" ).removeClass( "ui-state-focus" );
+ this.active = null;
+
+ this._trigger( "blur", event, { item: this.active } );
+ },
+
+ _startOpening: function( submenu ) {
+ clearTimeout( this.timer );
+
+ // Don't open if already open fixes a Firefox bug that caused a .5 pixel
+ // shift in the submenu position when mousing over the carat icon
+ if ( submenu.attr( "aria-hidden" ) !== "true" ) {
+ return;
+ }
+
+ this.timer = this._delay(function() {
+ this._close();
+ this._open( submenu );
+ }, this.delay );
+ },
+
+ _open: function( submenu ) {
+ var position = $.extend({
+ of: this.active
+ }, this.options.position );
+
+ clearTimeout( this.timer );
+ this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
+ .hide()
+ .attr( "aria-hidden", "true" );
+
+ submenu
+ .show()
+ .removeAttr( "aria-hidden" )
+ .attr( "aria-expanded", "true" )
+ .position( position );
+ },
+
+ collapseAll: function( event, all ) {
+ clearTimeout( this.timer );
+ this.timer = this._delay(function() {
+ // If we were passed an event, look for the submenu that contains the event
+ var currentMenu = all ? this.element :
+ $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
+
+ // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
+ if ( !currentMenu.length ) {
+ currentMenu = this.element;
+ }
+
+ this._close( currentMenu );
+
+ this.blur( event );
+ this.activeMenu = currentMenu;
+ }, this.delay );
+ },
+
+ // With no arguments, closes the currently active menu - if nothing is active
+ // it closes all menus. If passed an argument, it will search for menus BELOW
+ _close: function( startMenu ) {
+ if ( !startMenu ) {
+ startMenu = this.active ? this.active.parent() : this.element;
+ }
+
+ startMenu
+ .find( ".ui-menu" )
+ .hide()
+ .attr( "aria-hidden", "true" )
+ .attr( "aria-expanded", "false" )
+ .end()
+ .find( "a.ui-state-active" )
+ .removeClass( "ui-state-active" );
+ },
+
+ collapse: function( event ) {
+ var newItem = this.active &&
+ this.active.parent().closest( ".ui-menu-item", this.element );
+ if ( newItem && newItem.length ) {
+ this._close();
+ this.focus( event, newItem );
+ }
+ },
+
+ expand: function( event ) {
+ var newItem = this.active &&
+ this.active
+ .children( ".ui-menu " )
+ .children( ".ui-menu-item" )
+ .first();
+
+ if ( newItem && newItem.length ) {
+ this._open( newItem.parent() );
+
+ // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
+ this._delay(function() {
+ this.focus( event, newItem );
+ });
+ }
+ },
+
+ next: function( event ) {
+ this._move( "next", "first", event );
+ },
+
+ previous: function( event ) {
+ this._move( "prev", "last", event );
+ },
+
+ isFirstItem: function() {
+ return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
+ },
+
+ isLastItem: function() {
+ return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
+ },
+
+ _move: function( direction, filter, event ) {
+ var next;
+ if ( this.active ) {
+ if ( direction === "first" || direction === "last" ) {
+ next = this.active
+ [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
+ .eq( -1 );
+ } else {
+ next = this.active
+ [ direction + "All" ]( ".ui-menu-item" )
+ .eq( 0 );
+ }
+ }
+ if ( !next || !next.length || !this.active ) {
+ next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
+ }
+
+ this.focus( event, next );
+ },
+
+ nextPage: function( event ) {
+ var item, base, height;
+
+ if ( !this.active ) {
+ this.next( event );
+ return;
+ }
+ if ( this.isLastItem() ) {
+ return;
+ }
+ if ( this._hasScroll() ) {
+ base = this.active.offset().top;
+ height = this.element.height();
+ this.active.nextAll( ".ui-menu-item" ).each(function() {
+ item = $( this );
+ return item.offset().top - base - height < 0;
+ });
+
+ this.focus( event, item );
+ } else {
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" )
+ [ !this.active ? "first" : "last" ]() );
+ }
+ },
+
+ previousPage: function( event ) {
+ var item, base, height;
+ if ( !this.active ) {
+ this.next( event );
+ return;
+ }
+ if ( this.isFirstItem() ) {
+ return;
+ }
+ if ( this._hasScroll() ) {
+ base = this.active.offset().top;
+ height = this.element.height();
+ this.active.prevAll( ".ui-menu-item" ).each(function() {
+ item = $( this );
+ return item.offset().top - base + height > 0;
+ });
+
+ this.focus( event, item );
+ } else {
+ this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
+ }
+ },
+
+ _hasScroll: function() {
+ return this.element.outerHeight() < this.element.prop( "scrollHeight" );
+ },
+
+ select: function( event ) {
+ // TODO: It should never be possible to not have an active item at this
+ // point, but the tests don't trigger mouseenter before click.
+ this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
+ var ui = { item: this.active };
+ if ( !this.active.has( ".ui-menu" ).length ) {
+ this.collapseAll( event, true );
+ }
+ this._trigger( "select", event, ui );
+ }
+});
+
+}( jQuery ));
+
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth,
+ max = Math.max,
+ abs = Math.abs,
+ round = Math.round,
+ rhorizontal = /left|center|right/,
+ rvertical = /top|center|bottom/,
+ roffset = /[\+\-]\d+(\.[\d]+)?%?/,
+ rposition = /^\w+/,
+ rpercent = /%$/,
+ _position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+ return [
+ parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+ parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+ ];
+}
+
+function parseCss( element, property ) {
+ return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+function getDimensions( elem ) {
+ var raw = elem[0];
+ if ( raw.nodeType === 9 ) {
+ return {
+ width: elem.width(),
+ height: elem.height(),
+ offset: { top: 0, left: 0 }
+ };
+ }
+ if ( $.isWindow( raw ) ) {
+ return {
+ width: elem.width(),
+ height: elem.height(),
+ offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
+ };
+ }
+ if ( raw.preventDefault ) {
+ return {
+ width: 0,
+ height: 0,
+ offset: { top: raw.pageY, left: raw.pageX }
+ };
+ }
+ return {
+ width: elem.outerWidth(),
+ height: elem.outerHeight(),
+ offset: elem.offset()
+ };
+}
+
+$.position = {
+ scrollbarWidth: function() {
+ if ( cachedScrollbarWidth !== undefined ) {
+ return cachedScrollbarWidth;
+ }
+ var w1, w2,
+ div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+ innerDiv = div.children()[0];
+
+ $( "body" ).append( div );
+ w1 = innerDiv.offsetWidth;
+ div.css( "overflow", "scroll" );
+
+ w2 = innerDiv.offsetWidth;
+
+ if ( w1 === w2 ) {
+ w2 = div[0].clientWidth;
+ }
+
+ div.remove();
+
+ return (cachedScrollbarWidth = w1 - w2);
+ },
+ getScrollInfo: function( within ) {
+ var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
+ overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
+ hasOverflowX = overflowX === "scroll" ||
+ ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+ hasOverflowY = overflowY === "scroll" ||
+ ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+ return {
+ width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+ height: hasOverflowX ? $.position.scrollbarWidth() : 0
+ };
+ },
+ getWithinInfo: function( element ) {
+ var withinElement = $( element || window ),
+ isWindow = $.isWindow( withinElement[0] );
+ return {
+ element: withinElement,
+ isWindow: isWindow,
+ offset: withinElement.offset() || { left: 0, top: 0 },
+ scrollLeft: withinElement.scrollLeft(),
+ scrollTop: withinElement.scrollTop(),
+ width: isWindow ? withinElement.width() : withinElement.outerWidth(),
+ height: isWindow ? withinElement.height() : withinElement.outerHeight()
+ };
+ }
+};
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
+ target = $( options.of ),
+ within = $.position.getWithinInfo( options.within ),
+ scrollInfo = $.position.getScrollInfo( within ),
+ collision = ( options.collision || "flip" ).split( " " ),
+ offsets = {};
+
+ dimensions = getDimensions( target );
+ if ( target[0].preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ }
+ targetWidth = dimensions.width;
+ targetHeight = dimensions.height;
+ targetOffset = dimensions.offset;
+ // clone to reuse original targetOffset later
+ basePosition = $.extend( {}, targetOffset );
+
+ // force my and at to have valid horizontal and vertical positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[ this ] || "" ).split( " " ),
+ horizontalOffset,
+ verticalOffset;
+
+ if ( pos.length === 1) {
+ pos = rhorizontal.test( pos[ 0 ] ) ?
+ pos.concat( [ "center" ] ) :
+ rvertical.test( pos[ 0 ] ) ?
+ [ "center" ].concat( pos ) :
+ [ "center", "center" ];
+ }
+ pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+ pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+ // calculate offsets
+ horizontalOffset = roffset.exec( pos[ 0 ] );
+ verticalOffset = roffset.exec( pos[ 1 ] );
+ offsets[ this ] = [
+ horizontalOffset ? horizontalOffset[ 0 ] : 0,
+ verticalOffset ? verticalOffset[ 0 ] : 0
+ ];
+
+ // reduce to just the positions without the offsets
+ options[ this ] = [
+ rposition.exec( pos[ 0 ] )[ 0 ],
+ rposition.exec( pos[ 1 ] )[ 0 ]
+ ];
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ if ( options.at[ 0 ] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[ 0 ] === "center" ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[ 1 ] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[ 1 ] === "center" ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+ basePosition.left += atOffset[ 0 ];
+ basePosition.top += atOffset[ 1 ];
+
+ return this.each(function() {
+ var collisionPosition, using,
+ elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseCss( this, "marginLeft" ),
+ marginTop = parseCss( this, "marginTop" ),
+ collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+ collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+ position = $.extend( {}, basePosition ),
+ myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+ if ( options.my[ 0 ] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[ 0 ] === "center" ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[ 1 ] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[ 1 ] === "center" ) {
+ position.top -= elemHeight / 2;
+ }
+
+ position.left += myOffset[ 0 ];
+ position.top += myOffset[ 1 ];
+
+ // if the browser doesn't support fractions, then round for consistent results
+ if ( !$.support.offsetFractions ) {
+ position.left = round( position.left );
+ position.top = round( position.top );
+ }
+
+ collisionPosition = {
+ marginLeft: marginLeft,
+ marginTop: marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[ i ] ] ) {
+ $.ui.position[ collision[ i ] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+ my: options.my,
+ at: options.at,
+ within: within,
+ elem : elem
+ });
+ }
+ });
+
+ if ( options.using ) {
+ // adds feedback as second argument to using callback, if present
+ using = function( props ) {
+ var left = targetOffset.left - position.left,
+ right = left + targetWidth - elemWidth,
+ top = targetOffset.top - position.top,
+ bottom = top + targetHeight - elemHeight,
+ feedback = {
+ target: {
+ element: target,
+ left: targetOffset.left,
+ top: targetOffset.top,
+ width: targetWidth,
+ height: targetHeight
+ },
+ element: {
+ element: elem,
+ left: position.left,
+ top: position.top,
+ width: elemWidth,
+ height: elemHeight
+ },
+ horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+ vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+ };
+ if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+ feedback.horizontal = "center";
+ }
+ if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+ feedback.vertical = "middle";
+ }
+ if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+ feedback.important = "horizontal";
+ } else {
+ feedback.important = "vertical";
+ }
+ options.using.call( this, props, feedback );
+ };
+ }
+
+ elem.offset( $.extend( position, { using: using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+ outerWidth = within.width,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = withinOffset - collisionPosLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+ newOverRight;
+
+ // element is wider than within
+ if ( data.collisionWidth > outerWidth ) {
+ // element is initially over the left side of within
+ if ( overLeft > 0 && overRight <= 0 ) {
+ newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+ position.left += overLeft - newOverRight;
+ // element is initially over right side of within
+ } else if ( overRight > 0 && overLeft <= 0 ) {
+ position.left = withinOffset;
+ // element is initially over both left and right sides of within
+ } else {
+ if ( overLeft > overRight ) {
+ position.left = withinOffset + outerWidth - data.collisionWidth;
+ } else {
+ position.left = withinOffset;
+ }
+ }
+ // too far left -> align with left edge
+ } else if ( overLeft > 0 ) {
+ position.left += overLeft;
+ // too far right -> align with right edge
+ } else if ( overRight > 0 ) {
+ position.left -= overRight;
+ // adjust based on position and margin
+ } else {
+ position.left = max( position.left - collisionPosLeft, position.left );
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+ outerHeight = data.within.height,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = withinOffset - collisionPosTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+ newOverBottom;
+
+ // element is taller than within
+ if ( data.collisionHeight > outerHeight ) {
+ // element is initially over the top of within
+ if ( overTop > 0 && overBottom <= 0 ) {
+ newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+ position.top += overTop - newOverBottom;
+ // element is initially over bottom of within
+ } else if ( overBottom > 0 && overTop <= 0 ) {
+ position.top = withinOffset;
+ // element is initially over both top and bottom of within
+ } else {
+ if ( overTop > overBottom ) {
+ position.top = withinOffset + outerHeight - data.collisionHeight;
+ } else {
+ position.top = withinOffset;
+ }
+ }
+ // too far up -> align with top
+ } else if ( overTop > 0 ) {
+ position.top += overTop;
+ // too far down -> align with bottom edge
+ } else if ( overBottom > 0 ) {
+ position.top -= overBottom;
+ // adjust based on position and margin
+ } else {
+ position.top = max( position.top - collisionPosTop, position.top );
+ }
+ }
+ },
+ flip: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.left + within.scrollLeft,
+ outerWidth = within.width,
+ offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = collisionPosLeft - offsetLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ data.at[ 0 ] === "right" ?
+ -data.targetWidth :
+ 0,
+ offset = -2 * data.offset[ 0 ],
+ newOverRight,
+ newOverLeft;
+
+ if ( overLeft < 0 ) {
+ newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+ if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overRight > 0 ) {
+ newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+ if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.top + within.scrollTop,
+ outerHeight = within.height,
+ offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = collisionPosTop - offsetTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+ top = data.my[ 1 ] === "top",
+ myOffset = top ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ data.at[ 1 ] === "bottom" ?
+ -data.targetHeight :
+ 0,
+ offset = -2 * data.offset[ 1 ],
+ newOverTop,
+ newOverBottom;
+ if ( overTop < 0 ) {
+ newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+ if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overBottom > 0 ) {
+ newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+ if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ }
+ },
+ flipfit: {
+ left: function() {
+ $.ui.position.flip.left.apply( this, arguments );
+ $.ui.position.fit.left.apply( this, arguments );
+ },
+ top: function() {
+ $.ui.position.flip.top.apply( this, arguments );
+ $.ui.position.fit.top.apply( this, arguments );
+ }
+ }
+};
+
+// fraction support test
+(function () {
+ var testElement, testElementParent, testElementStyle, offsetLeft, i,
+ body = document.getElementsByTagName( "body" )[ 0 ],
+ div = document.createElement( "div" );
+
+ //Create a "fake body" for testing based on method used in jQuery.support
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ $.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || document.documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+ offsetLeft = $( div ).offset().left;
+ $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+})();
+
+}( jQuery ) );
+
+(function( $, undefined ) {
+
+$.widget( "ui.progressbar", {
+ version: "1.10.2",
+ options: {
+ max: 100,
+ value: 0,
+
+ change: null,
+ complete: null
+ },
+
+ min: 0,
+
+ _create: function() {
+ // Constrain initial value
+ this.oldValue = this.options.value = this._constrainedValue();
+
+ this.element
+ .addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .attr({
+ // Only set static values, aria-valuenow and aria-valuemax are
+ // set inside _refreshValue()
+ role: "progressbar",
+ "aria-valuemin": this.min
+ });
+
+ this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
+ .appendTo( this.element );
+
+ this._refreshValue();
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-valuemin" )
+ .removeAttr( "aria-valuemax" )
+ .removeAttr( "aria-valuenow" );
+
+ this.valueDiv.remove();
+ },
+
+ value: function( newValue ) {
+ if ( newValue === undefined ) {
+ return this.options.value;
+ }
+
+ this.options.value = this._constrainedValue( newValue );
+ this._refreshValue();
+ },
+
+ _constrainedValue: function( newValue ) {
+ if ( newValue === undefined ) {
+ newValue = this.options.value;
+ }
+
+ this.indeterminate = newValue === false;
+
+ // sanitize value
+ if ( typeof newValue !== "number" ) {
+ newValue = 0;
+ }
+
+ return this.indeterminate ? false :
+ Math.min( this.options.max, Math.max( this.min, newValue ) );
+ },
+
+ _setOptions: function( options ) {
+ // Ensure "value" option is set after other values (like max)
+ var value = options.value;
+ delete options.value;
+
+ this._super( options );
+
+ this.options.value = this._constrainedValue( value );
+ this._refreshValue();
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "max" ) {
+ // Don't allow a max less than min
+ value = Math.max( this.min, value );
+ }
+
+ this._super( key, value );
+ },
+
+ _percentage: function() {
+ return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
+ },
+
+ _refreshValue: function() {
+ var value = this.options.value,
+ percentage = this._percentage();
+
+ this.valueDiv
+ .toggle( this.indeterminate || value > this.min )
+ .toggleClass( "ui-corner-right", value === this.options.max )
+ .width( percentage.toFixed(0) + "%" );
+
+ this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate );
+
+ if ( this.indeterminate ) {
+ this.element.removeAttr( "aria-valuenow" );
+ if ( !this.overlayDiv ) {
+ this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv );
+ }
+ } else {
+ this.element.attr({
+ "aria-valuemax": this.options.max,
+ "aria-valuenow": value
+ });
+ if ( this.overlayDiv ) {
+ this.overlayDiv.remove();
+ this.overlayDiv = null;
+ }
+ }
+
+ if ( this.oldValue !== value ) {
+ this.oldValue = value;
+ this._trigger( "change" );
+ }
+ if ( value === this.options.max ) {
+ this._trigger( "complete" );
+ }
+ }
+});
+
+})( jQuery );
+
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+ version: "1.10.2",
+ widgetEventPrefix: "slide",
+
+ options: {
+ animate: false,
+ distance: 0,
+ max: 100,
+ min: 0,
+ orientation: "horizontal",
+ range: false,
+ step: 1,
+ value: 0,
+ values: null,
+
+ // callbacks
+ change: null,
+ slide: null,
+ start: null,
+ stop: null
+ },
+
+ _create: function() {
+ this._keySliding = false;
+ this._mouseSliding = false;
+ this._animateOff = true;
+ this._handleIndex = null;
+ this._detectOrientation();
+ this._mouseInit();
+
+ this.element
+ .addClass( "ui-slider" +
+ " ui-slider-" + this.orientation +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all");
+
+ this._refresh();
+ this._setOption( "disabled", this.options.disabled );
+
+ this._animateOff = false;
+ },
+
+ _refresh: function() {
+ this._createRange();
+ this._createHandles();
+ this._setupEvents();
+ this._refreshValue();
+ },
+
+ _createHandles: function() {
+ var i, handleCount,
+ options = this.options,
+ existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+ handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+ handles = [];
+
+ handleCount = ( options.values && options.values.length ) || 1;
+
+ if ( existingHandles.length > handleCount ) {
+ existingHandles.slice( handleCount ).remove();
+ existingHandles = existingHandles.slice( 0, handleCount );
+ }
+
+ for ( i = existingHandles.length; i < handleCount; i++ ) {
+ handles.push( handle );
+ }
+
+ this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+ this.handle = this.handles.eq( 0 );
+
+ this.handles.each(function( i ) {
+ $( this ).data( "ui-slider-handle-index", i );
+ });
+ },
+
+ _createRange: function() {
+ var options = this.options,
+ classes = "";
+
+ if ( options.range ) {
+ if ( options.range === true ) {
+ if ( !options.values ) {
+ options.values = [ this._valueMin(), this._valueMin() ];
+ } else if ( options.values.length && options.values.length !== 2 ) {
+ options.values = [ options.values[0], options.values[0] ];
+ } else if ( $.isArray( options.values ) ) {
+ options.values = options.values.slice(0);
+ }
+ }
+
+ if ( !this.range || !this.range.length ) {
+ this.range = $( "<div></div>" )
+ .appendTo( this.element );
+
+ classes = "ui-slider-range" +
+ // note: this isn't the most fittingly semantic framework class for this element,
+ // but worked best visually with a variety of themes
+ " ui-widget-header ui-corner-all";
+ } else {
+ this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
+ // Handle range switching from true to min/max
+ .css({
+ "left": "",
+ "bottom": ""
+ });
+ }
+
+ this.range.addClass( classes +
+ ( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
+ } else {
+ this.range = $([]);
+ }
+ },
+
+ _setupEvents: function() {
+ var elements = this.handles.add( this.range ).filter( "a" );
+ this._off( elements );
+ this._on( elements, this._handleEvents );
+ this._hoverable( elements );
+ this._focusable( elements );
+ },
+
+ _destroy: function() {
+ this.handles.remove();
+ this.range.remove();
+
+ this.element
+ .removeClass( "ui-slider" +
+ " ui-slider-horizontal" +
+ " ui-slider-vertical" +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" );
+
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function( event ) {
+ var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+ that = this,
+ o = this.options;
+
+ if ( o.disabled ) {
+ return false;
+ }
+
+ this.elementSize = {
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight()
+ };
+ this.elementOffset = this.element.offset();
+
+ position = { x: event.pageX, y: event.pageY };
+ normValue = this._normValueFromMouse( position );
+ distance = this._valueMax() - this._valueMin() + 1;
+ this.handles.each(function( i ) {
+ var thisDistance = Math.abs( normValue - that.values(i) );
+ if (( distance > thisDistance ) ||
+ ( distance === thisDistance &&
+ (i === that._lastChangedValue || that.values(i) === o.min ))) {
+ distance = thisDistance;
+ closestHandle = $( this );
+ index = i;
+ }
+ });
+
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return false;
+ }
+ this._mouseSliding = true;
+
+ this._handleIndex = index;
+
+ closestHandle
+ .addClass( "ui-state-active" )
+ .focus();
+
+ offset = closestHandle.offset();
+ mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
+ this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+ left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+ top: event.pageY - offset.top -
+ ( closestHandle.height() / 2 ) -
+ ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+ ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+ ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+ };
+
+ if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+ this._slide( event, index, normValue );
+ }
+ this._animateOff = true;
+ return true;
+ },
+
+ _mouseStart: function() {
+ return true;
+ },
+
+ _mouseDrag: function( event ) {
+ var position = { x: event.pageX, y: event.pageY },
+ normValue = this._normValueFromMouse( position );
+
+ this._slide( event, this._handleIndex, normValue );
+
+ return false;
+ },
+
+ _mouseStop: function( event ) {
+ this.handles.removeClass( "ui-state-active" );
+ this._mouseSliding = false;
+
+ this._stop( event, this._handleIndex );
+ this._change( event, this._handleIndex );
+
+ this._handleIndex = null;
+ this._clickOffset = null;
+ this._animateOff = false;
+
+ return false;
+ },
+
+ _detectOrientation: function() {
+ this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+ },
+
+ _normValueFromMouse: function( position ) {
+ var pixelTotal,
+ pixelMouse,
+ percentMouse,
+ valueTotal,
+ valueMouse;
+
+ if ( this.orientation === "horizontal" ) {
+ pixelTotal = this.elementSize.width;
+ pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+ } else {
+ pixelTotal = this.elementSize.height;
+ pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+ }
+
+ percentMouse = ( pixelMouse / pixelTotal );
+ if ( percentMouse > 1 ) {
+ percentMouse = 1;
+ }
+ if ( percentMouse < 0 ) {
+ percentMouse = 0;
+ }
+ if ( this.orientation === "vertical" ) {
+ percentMouse = 1 - percentMouse;
+ }
+
+ valueTotal = this._valueMax() - this._valueMin();
+ valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+ return this._trimAlignValue( valueMouse );
+ },
+
+ _start: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+ return this._trigger( "start", event, uiHash );
+ },
+
+ _slide: function( event, index, newVal ) {
+ var otherVal,
+ newValues,
+ allowed;
+
+ if ( this.options.values && this.options.values.length ) {
+ otherVal = this.values( index ? 0 : 1 );
+
+ if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+ ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+ ) {
+ newVal = otherVal;
+ }
+
+ if ( newVal !== this.values( index ) ) {
+ newValues = this.values();
+ newValues[ index ] = newVal;
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal,
+ values: newValues
+ } );
+ otherVal = this.values( index ? 0 : 1 );
+ if ( allowed !== false ) {
+ this.values( index, newVal, true );
+ }
+ }
+ } else {
+ if ( newVal !== this.value() ) {
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal
+ } );
+ if ( allowed !== false ) {
+ this.value( newVal );
+ }
+ }
+ }
+ },
+
+ _stop: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "stop", event, uiHash );
+ },
+
+ _change: function( event, index ) {
+ if ( !this._keySliding && !this._mouseSliding ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ //store the last changed value index for reference when handles overlap
+ this._lastChangedValue = index;
+
+ this._trigger( "change", event, uiHash );
+ }
+ },
+
+ value: function( newValue ) {
+ if ( arguments.length ) {
+ this.options.value = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, 0 );
+ return;
+ }
+
+ return this._value();
+ },
+
+ values: function( index, newValue ) {
+ var vals,
+ newValues,
+ i;
+
+ if ( arguments.length > 1 ) {
+ this.options.values[ index ] = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, index );
+ return;
+ }
+
+ if ( arguments.length ) {
+ if ( $.isArray( arguments[ 0 ] ) ) {
+ vals = this.options.values;
+ newValues = arguments[ 0 ];
+ for ( i = 0; i < vals.length; i += 1 ) {
+ vals[ i ] = this._trimAlignValue( newValues[ i ] );
+ this._change( null, i );
+ }
+ this._refreshValue();
+ } else {
+ if ( this.options.values && this.options.values.length ) {
+ return this._values( index );
+ } else {
+ return this.value();
+ }
+ }
+ } else {
+ return this._values();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var i,
+ valsLength = 0;
+
+ if ( key === "range" && this.options.range === true ) {
+ if ( value === "min" ) {
+ this.options.value = this._values( 0 );
+ this.options.values = null;
+ } else if ( value === "max" ) {
+ this.options.value = this._values( this.options.values.length-1 );
+ this.options.values = null;
+ }
+ }
+
+ if ( $.isArray( this.options.values ) ) {
+ valsLength = this.options.values.length;
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+
+ switch ( key ) {
+ case "orientation":
+ this._detectOrientation();
+ this.element
+ .removeClass( "ui-slider-horizontal ui-slider-vertical" )
+ .addClass( "ui-slider-" + this.orientation );
+ this._refreshValue();
+ break;
+ case "value":
+ this._animateOff = true;
+ this._refreshValue();
+ this._change( null, 0 );
+ this._animateOff = false;
+ break;
+ case "values":
+ this._animateOff = true;
+ this._refreshValue();
+ for ( i = 0; i < valsLength; i += 1 ) {
+ this._change( null, i );
+ }
+ this._animateOff = false;
+ break;
+ case "min":
+ case "max":
+ this._animateOff = true;
+ this._refreshValue();
+ this._animateOff = false;
+ break;
+ case "range":
+ this._animateOff = true;
+ this._refresh();
+ this._animateOff = false;
+ break;
+ }
+ },
+
+ //internal value getter
+ // _value() returns value trimmed by min and max, aligned by step
+ _value: function() {
+ var val = this.options.value;
+ val = this._trimAlignValue( val );
+
+ return val;
+ },
+
+ //internal values getter
+ // _values() returns array of values trimmed by min and max, aligned by step
+ // _values( index ) returns single value trimmed by min and max, aligned by step
+ _values: function( index ) {
+ var val,
+ vals,
+ i;
+
+ if ( arguments.length ) {
+ val = this.options.values[ index ];
+ val = this._trimAlignValue( val );
+
+ return val;
+ } else if ( this.options.values && this.options.values.length ) {
+ // .slice() creates a copy of the array
+ // this copy gets trimmed by min and max and then returned
+ vals = this.options.values.slice();
+ for ( i = 0; i < vals.length; i+= 1) {
+ vals[ i ] = this._trimAlignValue( vals[ i ] );
+ }
+
+ return vals;
+ } else {
+ return [];
+ }
+ },
+
+ // returns the step-aligned value that val is closest to, between (inclusive) min and max
+ _trimAlignValue: function( val ) {
+ if ( val <= this._valueMin() ) {
+ return this._valueMin();
+ }
+ if ( val >= this._valueMax() ) {
+ return this._valueMax();
+ }
+ var step = ( this.options.step > 0 ) ? this.options.step : 1,
+ valModStep = (val - this._valueMin()) % step,
+ alignValue = val - valModStep;
+
+ if ( Math.abs(valModStep) * 2 >= step ) {
+ alignValue += ( valModStep > 0 ) ? step : ( -step );
+ }
+
+ // Since JavaScript has problems with large floats, round
+ // the final value to 5 digits after the decimal point (see #4124)
+ return parseFloat( alignValue.toFixed(5) );
+ },
+
+ _valueMin: function() {
+ return this.options.min;
+ },
+
+ _valueMax: function() {
+ return this.options.max;
+ },
+
+ _refreshValue: function() {
+ var lastValPercent, valPercent, value, valueMin, valueMax,
+ oRange = this.options.range,
+ o = this.options,
+ that = this,
+ animate = ( !this._animateOff ) ? o.animate : false,
+ _set = {};
+
+ if ( this.options.values && this.options.values.length ) {
+ this.handles.each(function( i ) {
+ valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+ _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+ if ( that.options.range === true ) {
+ if ( that.orientation === "horizontal" ) {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ } else {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+ lastValPercent = valPercent;
+ });
+ } else {
+ value = this.value();
+ valueMin = this._valueMin();
+ valueMax = this._valueMax();
+ valPercent = ( valueMax !== valueMin ) ?
+ ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+ 0;
+ _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+ if ( oRange === "min" && this.orientation === "horizontal" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "horizontal" ) {
+ this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ if ( oRange === "min" && this.orientation === "vertical" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "vertical" ) {
+ this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ },
+
+ _handleEvents: {
+ keydown: function( event ) {
+ /*jshint maxcomplexity:25*/
+ var allowed, curVal, newVal, step,
+ index = $( event.target ).data( "ui-slider-handle-index" );
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ case $.ui.keyCode.END:
+ case $.ui.keyCode.PAGE_UP:
+ case $.ui.keyCode.PAGE_DOWN:
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ event.preventDefault();
+ if ( !this._keySliding ) {
+ this._keySliding = true;
+ $( event.target ).addClass( "ui-state-active" );
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return;
+ }
+ }
+ break;
+ }
+
+ step = this.options.step;
+ if ( this.options.values && this.options.values.length ) {
+ curVal = newVal = this.values( index );
+ } else {
+ curVal = newVal = this.value();
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ newVal = this._valueMin();
+ break;
+ case $.ui.keyCode.END:
+ newVal = this._valueMax();
+ break;
+ case $.ui.keyCode.PAGE_UP:
+ newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ if ( curVal === this._valueMax() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal + step );
+ break;
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ if ( curVal === this._valueMin() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal - step );
+ break;
+ }
+
+ this._slide( event, index, newVal );
+ },
+ click: function( event ) {
+ event.preventDefault();
+ },
+ keyup: function( event ) {
+ var index = $( event.target ).data( "ui-slider-handle-index" );
+
+ if ( this._keySliding ) {
+ this._keySliding = false;
+ this._stop( event, index );
+ this._change( event, index );
+ $( event.target ).removeClass( "ui-state-active" );
+ }
+ }
+ }
+
+});
+
+}(jQuery));
+
+(function( $ ) {
+
+function modifier( fn ) {
+ return function() {
+ var previous = this.element.val();
+ fn.apply( this, arguments );
+ this._refresh();
+ if ( previous !== this.element.val() ) {
+ this._trigger( "change" );
+ }
+ };
+}
+
+$.widget( "ui.spinner", {
+ version: "1.10.2",
+ defaultElement: "<input>",
+ widgetEventPrefix: "spin",
+ options: {
+ culture: null,
+ icons: {
+ down: "ui-icon-triangle-1-s",
+ up: "ui-icon-triangle-1-n"
+ },
+ incremental: true,
+ max: null,
+ min: null,
+ numberFormat: null,
+ page: 10,
+ step: 1,
+
+ change: null,
+ spin: null,
+ start: null,
+ stop: null
+ },
+
+ _create: function() {
+ // handle string values that need to be parsed
+ this._setOption( "max", this.options.max );
+ this._setOption( "min", this.options.min );
+ this._setOption( "step", this.options.step );
+
+ // format the value, but don't constrain
+ this._value( this.element.val(), true );
+
+ this._draw();
+ this._on( this._events );
+ this._refresh();
+
+ // turning off autocomplete prevents the browser from remembering the
+ // value when navigating through history, so we re-enable autocomplete
+ // if the page is unloaded before the widget is destroyed. #7790
+ this._on( this.window, {
+ beforeunload: function() {
+ this.element.removeAttr( "autocomplete" );
+ }
+ });
+ },
+
+ _getCreateOptions: function() {
+ var options = {},
+ element = this.element;
+
+ $.each( [ "min", "max", "step" ], function( i, option ) {
+ var value = element.attr( option );
+ if ( value !== undefined && value.length ) {
+ options[ option ] = value;
+ }
+ });
+
+ return options;
+ },
+
+ _events: {
+ keydown: function( event ) {
+ if ( this._start( event ) && this._keydown( event ) ) {
+ event.preventDefault();
+ }
+ },
+ keyup: "_stop",
+ focus: function() {
+ this.previous = this.element.val();
+ },
+ blur: function( event ) {
+ if ( this.cancelBlur ) {
+ delete this.cancelBlur;
+ return;
+ }
+
+ this._stop();
+ this._refresh();
+ if ( this.previous !== this.element.val() ) {
+ this._trigger( "change", event );
+ }
+ },
+ mousewheel: function( event, delta ) {
+ if ( !delta ) {
+ return;
+ }
+ if ( !this.spinning && !this._start( event ) ) {
+ return false;
+ }
+
+ this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
+ clearTimeout( this.mousewheelTimer );
+ this.mousewheelTimer = this._delay(function() {
+ if ( this.spinning ) {
+ this._stop( event );
+ }
+ }, 100 );
+ event.preventDefault();
+ },
+ "mousedown .ui-spinner-button": function( event ) {
+ var previous;
+
+ // We never want the buttons to have focus; whenever the user is
+ // interacting with the spinner, the focus should be on the input.
+ // If the input is focused then this.previous is properly set from
+ // when the input first received focus. If the input is not focused
+ // then we need to set this.previous based on the value before spinning.
+ previous = this.element[0] === this.document[0].activeElement ?
+ this.previous : this.element.val();
+ function checkFocus() {
+ var isActive = this.element[0] === this.document[0].activeElement;
+ if ( !isActive ) {
+ this.element.focus();
+ this.previous = previous;
+ // support: IE
+ // IE sets focus asynchronously, so we need to check if focus
+ // moved off of the input because the user clicked on the button.
+ this._delay(function() {
+ this.previous = previous;
+ });
+ }
+ }
+
+ // ensure focus is on (or stays on) the text field
+ event.preventDefault();
+ checkFocus.call( this );
+
+ // support: IE
+ // IE doesn't prevent moving focus even with event.preventDefault()
+ // so we set a flag to know when we should ignore the blur event
+ // and check (again) if focus moved off of the input.
+ this.cancelBlur = true;
+ this._delay(function() {
+ delete this.cancelBlur;
+ checkFocus.call( this );
+ });
+
+ if ( this._start( event ) === false ) {
+ return;
+ }
+
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ "mouseup .ui-spinner-button": "_stop",
+ "mouseenter .ui-spinner-button": function( event ) {
+ // button will add ui-state-active if mouse was down while mouseleave and kept down
+ if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
+ return;
+ }
+
+ if ( this._start( event ) === false ) {
+ return false;
+ }
+ this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+ },
+ // TODO: do we really want to consider this a stop?
+ // shouldn't we just stop the repeater and wait until mouseup before
+ // we trigger the stop event?
+ "mouseleave .ui-spinner-button": "_stop"
+ },
+
+ _draw: function() {
+ var uiSpinner = this.uiSpinner = this.element
+ .addClass( "ui-spinner-input" )
+ .attr( "autocomplete", "off" )
+ .wrap( this._uiSpinnerHtml() )
+ .parent()
+ // add buttons
+ .append( this._buttonHtml() );
+
+ this.element.attr( "role", "spinbutton" );
+
+ // button bindings
+ this.buttons = uiSpinner.find( ".ui-spinner-button" )
+ .attr( "tabIndex", -1 )
+ .button()
+ .removeClass( "ui-corner-all" );
+
+ // IE 6 doesn't understand height: 50% for the buttons
+ // unless the wrapper has an explicit height
+ if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
+ uiSpinner.height() > 0 ) {
+ uiSpinner.height( uiSpinner.height() );
+ }
+
+ // disable spinner if element was already disabled
+ if ( this.options.disabled ) {
+ this.disable();
+ }
+ },
+
+ _keydown: function( event ) {
+ var options = this.options,
+ keyCode = $.ui.keyCode;
+
+ switch ( event.keyCode ) {
+ case keyCode.UP:
+ this._repeat( null, 1, event );
+ return true;
+ case keyCode.DOWN:
+ this._repeat( null, -1, event );
+ return true;
+ case keyCode.PAGE_UP:
+ this._repeat( null, options.page, event );
+ return true;
+ case keyCode.PAGE_DOWN:
+ this._repeat( null, -options.page, event );
+ return true;
+ }
+
+ return false;
+ },
+
+ _uiSpinnerHtml: function() {
+ return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
+ },
+
+ _buttonHtml: function() {
+ return "" +
+ "<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
+ "<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
+ "</a>" +
+ "<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
+ "<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
+ "</a>";
+ },
+
+ _start: function( event ) {
+ if ( !this.spinning && this._trigger( "start", event ) === false ) {
+ return false;
+ }
+
+ if ( !this.counter ) {
+ this.counter = 1;
+ }
+ this.spinning = true;
+ return true;
+ },
+
+ _repeat: function( i, steps, event ) {
+ i = i || 500;
+
+ clearTimeout( this.timer );
+ this.timer = this._delay(function() {
+ this._repeat( 40, steps, event );
+ }, i );
+
+ this._spin( steps * this.options.step, event );
+ },
+
+ _spin: function( step, event ) {
+ var value = this.value() || 0;
+
+ if ( !this.counter ) {
+ this.counter = 1;
+ }
+
+ value = this._adjustValue( value + step * this._increment( this.counter ) );
+
+ if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
+ this._value( value );
+ this.counter++;
+ }
+ },
+
+ _increment: function( i ) {
+ var incremental = this.options.incremental;
+
+ if ( incremental ) {
+ return $.isFunction( incremental ) ?
+ incremental( i ) :
+ Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
+ }
+
+ return 1;
+ },
+
+ _precision: function() {
+ var precision = this._precisionOf( this.options.step );
+ if ( this.options.min !== null ) {
+ precision = Math.max( precision, this._precisionOf( this.options.min ) );
+ }
+ return precision;
+ },
+
+ _precisionOf: function( num ) {
+ var str = num.toString(),
+ decimal = str.indexOf( "." );
+ return decimal === -1 ? 0 : str.length - decimal - 1;
+ },
+
+ _adjustValue: function( value ) {
+ var base, aboveMin,
+ options = this.options;
+
+ // make sure we're at a valid step
+ // - find out where we are relative to the base (min or 0)
+ base = options.min !== null ? options.min : 0;
+ aboveMin = value - base;
+ // - round to the nearest step
+ aboveMin = Math.round(aboveMin / options.step) * options.step;
+ // - rounding is based on 0, so adjust back to our base
+ value = base + aboveMin;
+
+ // fix precision from bad JS floating point math
+ value = parseFloat( value.toFixed( this._precision() ) );
+
+ // clamp the value
+ if ( options.max !== null && value > options.max) {
+ return options.max;
+ }
+ if ( options.min !== null && value < options.min ) {
+ return options.min;
+ }
+
+ return value;
+ },
+
+ _stop: function( event ) {
+ if ( !this.spinning ) {
+ return;
+ }
+
+ clearTimeout( this.timer );
+ clearTimeout( this.mousewheelTimer );
+ this.counter = 0;
+ this.spinning = false;
+ this._trigger( "stop", event );
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "culture" || key === "numberFormat" ) {
+ var prevValue = this._parse( this.element.val() );
+ this.options[ key ] = value;
+ this.element.val( this._format( prevValue ) );
+ return;
+ }
+
+ if ( key === "max" || key === "min" || key === "step" ) {
+ if ( typeof value === "string" ) {
+ value = this._parse( value );
+ }
+ }
+ if ( key === "icons" ) {
+ this.buttons.first().find( ".ui-icon" )
+ .removeClass( this.options.icons.up )
+ .addClass( value.up );
+ this.buttons.last().find( ".ui-icon" )
+ .removeClass( this.options.icons.down )
+ .addClass( value.down );
+ }
+
+ this._super( key, value );
+
+ if ( key === "disabled" ) {
+ if ( value ) {
+ this.element.prop( "disabled", true );
+ this.buttons.button( "disable" );
+ } else {
+ this.element.prop( "disabled", false );
+ this.buttons.button( "enable" );
+ }
+ }
+ },
+
+ _setOptions: modifier(function( options ) {
+ this._super( options );
+ this._value( this.element.val() );
+ }),
+
+ _parse: function( val ) {
+ if ( typeof val === "string" && val !== "" ) {
+ val = window.Globalize && this.options.numberFormat ?
+ Globalize.parseFloat( val, 10, this.options.culture ) : +val;
+ }
+ return val === "" || isNaN( val ) ? null : val;
+ },
+
+ _format: function( value ) {
+ if ( value === "" ) {
+ return "";
+ }
+ return window.Globalize && this.options.numberFormat ?
+ Globalize.format( value, this.options.numberFormat, this.options.culture ) :
+ value;
+ },
+
+ _refresh: function() {
+ this.element.attr({
+ "aria-valuemin": this.options.min,
+ "aria-valuemax": this.options.max,
+ // TODO: what should we do with values that can't be parsed?
+ "aria-valuenow": this._parse( this.element.val() )
+ });
+ },
+
+ // update the value without triggering change
+ _value: function( value, allowAny ) {
+ var parsed;
+ if ( value !== "" ) {
+ parsed = this._parse( value );
+ if ( parsed !== null ) {
+ if ( !allowAny ) {
+ parsed = this._adjustValue( parsed );
+ }
+ value = this._format( parsed );
+ }
+ }
+ this.element.val( value );
+ this._refresh();
+ },
+
+ _destroy: function() {
+ this.element
+ .removeClass( "ui-spinner-input" )
+ .prop( "disabled", false )
+ .removeAttr( "autocomplete" )
+ .removeAttr( "role" )
+ .removeAttr( "aria-valuemin" )
+ .removeAttr( "aria-valuemax" )
+ .removeAttr( "aria-valuenow" );
+ this.uiSpinner.replaceWith( this.element );
+ },
+
+ stepUp: modifier(function( steps ) {
+ this._stepUp( steps );
+ }),
+ _stepUp: function( steps ) {
+ if ( this._start() ) {
+ this._spin( (steps || 1) * this.options.step );
+ this._stop();
+ }
+ },
+
+ stepDown: modifier(function( steps ) {
+ this._stepDown( steps );
+ }),
+ _stepDown: function( steps ) {
+ if ( this._start() ) {
+ this._spin( (steps || 1) * -this.options.step );
+ this._stop();
+ }
+ },
+
+ pageUp: modifier(function( pages ) {
+ this._stepUp( (pages || 1) * this.options.page );
+ }),
+
+ pageDown: modifier(function( pages ) {
+ this._stepDown( (pages || 1) * this.options.page );
+ }),
+
+ value: function( newVal ) {
+ if ( !arguments.length ) {
+ return this._parse( this.element.val() );
+ }
+ modifier( this._value ).call( this, newVal );
+ },
+
+ widget: function() {
+ return this.uiSpinner;
+ }
+});
+
+}( jQuery ) );
+
+(function( $, undefined ) {
+
+var tabId = 0,
+ rhash = /#.*$/;
+
+function getNextTabId() {
+ return ++tabId;
+}
+
+function isLocal( anchor ) {
+ return anchor.hash.length > 1 &&
+ decodeURIComponent( anchor.href.replace( rhash, "" ) ) ===
+ decodeURIComponent( location.href.replace( rhash, "" ) );
+}
+
+$.widget( "ui.tabs", {
+ version: "1.10.2",
+ delay: 300,
+ options: {
+ active: null,
+ collapsible: false,
+ event: "click",
+ heightStyle: "content",
+ hide: null,
+ show: null,
+
+ // callbacks
+ activate: null,
+ beforeActivate: null,
+ beforeLoad: null,
+ load: null
+ },
+
+ _create: function() {
+ var that = this,
+ options = this.options;
+
+ this.running = false;
+
+ this.element
+ .addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
+ .toggleClass( "ui-tabs-collapsible", options.collapsible )
+ // Prevent users from focusing disabled tabs via click
+ .delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
+ if ( $( this ).is( ".ui-state-disabled" ) ) {
+ event.preventDefault();
+ }
+ })
+ // support: IE <9
+ // Preventing the default action in mousedown doesn't prevent IE
+ // from focusing the element, so if the anchor gets focused, blur.
+ // We don't have to worry about focusing the previously focused
+ // element since clicking on a non-focusable element should focus
+ // the body anyway.
+ .delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
+ if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
+ this.blur();
+ }
+ });
+
+ this._processTabs();
+ options.active = this._initialActive();
+
+ // Take disabling tabs via class attribute from HTML
+ // into account and update option properly.
+ if ( $.isArray( options.disabled ) ) {
+ options.disabled = $.unique( options.disabled.concat(
+ $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
+ return that.tabs.index( li );
+ })
+ ) ).sort();
+ }
+
+ // check for length avoids error when initializing empty list
+ if ( this.options.active !== false && this.anchors.length ) {
+ this.active = this._findActive( options.active );
+ } else {
+ this.active = $();
+ }
+
+ this._refresh();
+
+ if ( this.active.length ) {
+ this.load( options.active );
+ }
+ },
+
+ _initialActive: function() {
+ var active = this.options.active,
+ collapsible = this.options.collapsible,
+ locationHash = location.hash.substring( 1 );
+
+ if ( active === null ) {
+ // check the fragment identifier in the URL
+ if ( locationHash ) {
+ this.tabs.each(function( i, tab ) {
+ if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
+ active = i;
+ return false;
+ }
+ });
+ }
+
+ // check for a tab marked active via a class
+ if ( active === null ) {
+ active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
+ }
+
+ // no active tab, set to false
+ if ( active === null || active === -1 ) {
+ active = this.tabs.length ? 0 : false;
+ }
+ }
+
+ // handle numbers: negative, out of range
+ if ( active !== false ) {
+ active = this.tabs.index( this.tabs.eq( active ) );
+ if ( active === -1 ) {
+ active = collapsible ? false : 0;
+ }
+ }
+
+ // don't allow collapsible: false and active: false
+ if ( !collapsible && active === false && this.anchors.length ) {
+ active = 0;
+ }
+
+ return active;
+ },
+
+ _getCreateEventData: function() {
+ return {
+ tab: this.active,
+ panel: !this.active.length ? $() : this._getPanelForTab( this.active )
+ };
+ },
+
+ _tabKeydown: function( event ) {
+ /*jshint maxcomplexity:15*/
+ var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
+ selectedIndex = this.tabs.index( focusedTab ),
+ goingForward = true;
+
+ if ( this._handlePageNav( event ) ) {
+ return;
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ selectedIndex++;
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.LEFT:
+ goingForward = false;
+ selectedIndex--;
+ break;
+ case $.ui.keyCode.END:
+ selectedIndex = this.anchors.length - 1;
+ break;
+ case $.ui.keyCode.HOME:
+ selectedIndex = 0;
+ break;
+ case $.ui.keyCode.SPACE:
+ // Activate only, no collapsing
+ event.preventDefault();
+ clearTimeout( this.activating );
+ this._activate( selectedIndex );
+ return;
+ case $.ui.keyCode.ENTER:
+ // Toggle (cancel delayed activation, allow collapsing)
+ event.preventDefault();
+ clearTimeout( this.activating );
+ // Determine if we should collapse or activate
+ this._activate( selectedIndex === this.options.active ? false : selectedIndex );
+ return;
+ default:
+ return;
+ }
+
+ // Focus the appropriate tab, based on which key was pressed
+ event.preventDefault();
+ clearTimeout( this.activating );
+ selectedIndex = this._focusNextTab( selectedIndex, goingForward );
+
+ // Navigating with control key will prevent automatic activation
+ if ( !event.ctrlKey ) {
+ // Update aria-selected immediately so that AT think the tab is already selected.
+ // Otherwise AT may confuse the user by stating that they need to activate the tab,
+ // but the tab will already be activated by the time the announcement finishes.
+ focusedTab.attr( "aria-selected", "false" );
+ this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
+
+ this.activating = this._delay(function() {
+ this.option( "active", selectedIndex );
+ }, this.delay );
+ }
+ },
+
+ _panelKeydown: function( event ) {
+ if ( this._handlePageNav( event ) ) {
+ return;
+ }
+
+ // Ctrl+up moves focus to the current tab
+ if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
+ event.preventDefault();
+ this.active.focus();
+ }
+ },
+
+ // Alt+page up/down moves focus to the previous/next tab (and activates)
+ _handlePageNav: function( event ) {
+ if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
+ this._activate( this._focusNextTab( this.options.active - 1, false ) );
+ return true;
+ }
+ if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
+ this._activate( this._focusNextTab( this.options.active + 1, true ) );
+ return true;
+ }
+ },
+
+ _findNextTab: function( index, goingForward ) {
+ var lastTabIndex = this.tabs.length - 1;
+
+ function constrain() {
+ if ( index > lastTabIndex ) {
+ index = 0;
+ }
+ if ( index < 0 ) {
+ index = lastTabIndex;
+ }
+ return index;
+ }
+
+ while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
+ index = goingForward ? index + 1 : index - 1;
+ }
+
+ return index;
+ },
+
+ _focusNextTab: function( index, goingForward ) {
+ index = this._findNextTab( index, goingForward );
+ this.tabs.eq( index ).focus();
+ return index;
+ },
+
+ _setOption: function( key, value ) {
+ if ( key === "active" ) {
+ // _activate() will handle invalid values and update this.options
+ this._activate( value );
+ return;
+ }
+
+ if ( key === "disabled" ) {
+ // don't use the widget factory's disabled handling
+ this._setupDisabled( value );
+ return;
+ }
+
+ this._super( key, value);
+
+ if ( key === "collapsible" ) {
+ this.element.toggleClass( "ui-tabs-collapsible", value );
+ // Setting collapsible: false while collapsed; open first panel
+ if ( !value && this.options.active === false ) {
+ this._activate( 0 );
+ }
+ }
+
+ if ( key === "event" ) {
+ this._setupEvents( value );
+ }
+
+ if ( key === "heightStyle" ) {
+ this._setupHeightStyle( value );
+ }
+ },
+
+ _tabId: function( tab ) {
+ return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
+ },
+
+ _sanitizeSelector: function( hash ) {
+ return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
+ },
+
+ refresh: function() {
+ var options = this.options,
+ lis = this.tablist.children( ":has(a[href])" );
+
+ // get disabled tabs from class attribute from HTML
+ // this will get converted to a boolean if needed in _refresh()
+ options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
+ return lis.index( tab );
+ });
+
+ this._processTabs();
+
+ // was collapsed or no tabs
+ if ( options.active === false || !this.anchors.length ) {
+ options.active = false;
+ this.active = $();
+ // was active, but active tab is gone
+ } else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
+ // all remaining tabs are disabled
+ if ( this.tabs.length === options.disabled.length ) {
+ options.active = false;
+ this.active = $();
+ // activate previous tab
+ } else {
+ this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
+ }
+ // was active, active tab still exists
+ } else {
+ // make sure active index is correct
+ options.active = this.tabs.index( this.active );
+ }
+
+ this._refresh();
+ },
+
+ _refresh: function() {
+ this._setupDisabled( this.options.disabled );
+ this._setupEvents( this.options.event );
+ this._setupHeightStyle( this.options.heightStyle );
+
+ this.tabs.not( this.active ).attr({
+ "aria-selected": "false",
+ tabIndex: -1
+ });
+ this.panels.not( this._getPanelForTab( this.active ) )
+ .hide()
+ .attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+
+ // Make sure one tab is in the tab order
+ if ( !this.active.length ) {
+ this.tabs.eq( 0 ).attr( "tabIndex", 0 );
+ } else {
+ this.active
+ .addClass( "ui-tabs-active ui-state-active" )
+ .attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ this._getPanelForTab( this.active )
+ .show()
+ .attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ }
+ },
+
+ _processTabs: function() {
+ var that = this;
+
+ this.tablist = this._getList()
+ .addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+ .attr( "role", "tablist" );
+
+ this.tabs = this.tablist.find( "> li:has(a[href])" )
+ .addClass( "ui-state-default ui-corner-top" )
+ .attr({
+ role: "tab",
+ tabIndex: -1
+ });
+
+ this.anchors = this.tabs.map(function() {
+ return $( "a", this )[ 0 ];
+ })
+ .addClass( "ui-tabs-anchor" )
+ .attr({
+ role: "presentation",
+ tabIndex: -1
+ });
+
+ this.panels = $();
+
+ this.anchors.each(function( i, anchor ) {
+ var selector, panel, panelId,
+ anchorId = $( anchor ).uniqueId().attr( "id" ),
+ tab = $( anchor ).closest( "li" ),
+ originalAriaControls = tab.attr( "aria-controls" );
+
+ // inline tab
+ if ( isLocal( anchor ) ) {
+ selector = anchor.hash;
+ panel = that.element.find( that._sanitizeSelector( selector ) );
+ // remote tab
+ } else {
+ panelId = that._tabId( tab );
+ selector = "#" + panelId;
+ panel = that.element.find( selector );
+ if ( !panel.length ) {
+ panel = that._createPanel( panelId );
+ panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
+ }
+ panel.attr( "aria-live", "polite" );
+ }
+
+ if ( panel.length) {
+ that.panels = that.panels.add( panel );
+ }
+ if ( originalAriaControls ) {
+ tab.data( "ui-tabs-aria-controls", originalAriaControls );
+ }
+ tab.attr({
+ "aria-controls": selector.substring( 1 ),
+ "aria-labelledby": anchorId
+ });
+ panel.attr( "aria-labelledby", anchorId );
+ });
+
+ this.panels
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .attr( "role", "tabpanel" );
+ },
+
+ // allow overriding how to find the list for rare usage scenarios (#7715)
+ _getList: function() {
+ return this.element.find( "ol,ul" ).eq( 0 );
+ },
+
+ _createPanel: function( id ) {
+ return $( "<div>" )
+ .attr( "id", id )
+ .addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
+ .data( "ui-tabs-destroy", true );
+ },
+
+ _setupDisabled: function( disabled ) {
+ if ( $.isArray( disabled ) ) {
+ if ( !disabled.length ) {
+ disabled = false;
+ } else if ( disabled.length === this.anchors.length ) {
+ disabled = true;
+ }
+ }
+
+ // disable tabs
+ for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
+ if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
+ $( li )
+ .addClass( "ui-state-disabled" )
+ .attr( "aria-disabled", "true" );
+ } else {
+ $( li )
+ .removeClass( "ui-state-disabled" )
+ .removeAttr( "aria-disabled" );
+ }
+ }
+
+ this.options.disabled = disabled;
+ },
+
+ _setupEvents: function( event ) {
+ var events = {
+ click: function( event ) {
+ event.preventDefault();
+ }
+ };
+ if ( event ) {
+ $.each( event.split(" "), function( index, eventName ) {
+ events[ eventName ] = "_eventHandler";
+ });
+ }
+
+ this._off( this.anchors.add( this.tabs ).add( this.panels ) );
+ this._on( this.anchors, events );
+ this._on( this.tabs, { keydown: "_tabKeydown" } );
+ this._on( this.panels, { keydown: "_panelKeydown" } );
+
+ this._focusable( this.tabs );
+ this._hoverable( this.tabs );
+ },
+
+ _setupHeightStyle: function( heightStyle ) {
+ var maxHeight,
+ parent = this.element.parent();
+
+ if ( heightStyle === "fill" ) {
+ maxHeight = parent.height();
+ maxHeight -= this.element.outerHeight() - this.element.height();
+
+ this.element.siblings( ":visible" ).each(function() {
+ var elem = $( this ),
+ position = elem.css( "position" );
+
+ if ( position === "absolute" || position === "fixed" ) {
+ return;
+ }
+ maxHeight -= elem.outerHeight( true );
+ });
+
+ this.element.children().not( this.panels ).each(function() {
+ maxHeight -= $( this ).outerHeight( true );
+ });
+
+ this.panels.each(function() {
+ $( this ).height( Math.max( 0, maxHeight -
+ $( this ).innerHeight() + $( this ).height() ) );
+ })
+ .css( "overflow", "auto" );
+ } else if ( heightStyle === "auto" ) {
+ maxHeight = 0;
+ this.panels.each(function() {
+ maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
+ }).height( maxHeight );
+ }
+ },
+
+ _eventHandler: function( event ) {
+ var options = this.options,
+ active = this.active,
+ anchor = $( event.currentTarget ),
+ tab = anchor.closest( "li" ),
+ clickedIsActive = tab[ 0 ] === active[ 0 ],
+ collapsing = clickedIsActive && options.collapsible,
+ toShow = collapsing ? $() : this._getPanelForTab( tab ),
+ toHide = !active.length ? $() : this._getPanelForTab( active ),
+ eventData = {
+ oldTab: active,
+ oldPanel: toHide,
+ newTab: collapsing ? $() : tab,
+ newPanel: toShow
+ };
+
+ event.preventDefault();
+
+ if ( tab.hasClass( "ui-state-disabled" ) ||
+ // tab is already loading
+ tab.hasClass( "ui-tabs-loading" ) ||
+ // can't switch durning an animation
+ this.running ||
+ // click on active header, but not collapsible
+ ( clickedIsActive && !options.collapsible ) ||
+ // allow canceling activation
+ ( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+ return;
+ }
+
+ options.active = collapsing ? false : this.tabs.index( tab );
+
+ this.active = clickedIsActive ? $() : tab;
+ if ( this.xhr ) {
+ this.xhr.abort();
+ }
+
+ if ( !toHide.length && !toShow.length ) {
+ $.error( "jQuery UI Tabs: Mismatching fragment identifier." );
+ }
+
+ if ( toShow.length ) {
+ this.load( this.tabs.index( tab ), event );
+ }
+ this._toggle( event, eventData );
+ },
+
+ // handles show/hide for selecting tabs
+ _toggle: function( event, eventData ) {
+ var that = this,
+ toShow = eventData.newPanel,
+ toHide = eventData.oldPanel;
+
+ this.running = true;
+
+ function complete() {
+ that.running = false;
+ that._trigger( "activate", event, eventData );
+ }
+
+ function show() {
+ eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
+
+ if ( toShow.length && that.options.show ) {
+ that._show( toShow, that.options.show, complete );
+ } else {
+ toShow.show();
+ complete();
+ }
+ }
+
+ // start out by hiding, then showing, then completing
+ if ( toHide.length && this.options.hide ) {
+ this._hide( toHide, this.options.hide, function() {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ show();
+ });
+ } else {
+ eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
+ toHide.hide();
+ show();
+ }
+
+ toHide.attr({
+ "aria-expanded": "false",
+ "aria-hidden": "true"
+ });
+ eventData.oldTab.attr( "aria-selected", "false" );
+ // If we're switching tabs, remove the old tab from the tab order.
+ // If we're opening from collapsed state, remove the previous tab from the tab order.
+ // If we're collapsing, then keep the collapsing tab in the tab order.
+ if ( toShow.length && toHide.length ) {
+ eventData.oldTab.attr( "tabIndex", -1 );
+ } else if ( toShow.length ) {
+ this.tabs.filter(function() {
+ return $( this ).attr( "tabIndex" ) === 0;
+ })
+ .attr( "tabIndex", -1 );
+ }
+
+ toShow.attr({
+ "aria-expanded": "true",
+ "aria-hidden": "false"
+ });
+ eventData.newTab.attr({
+ "aria-selected": "true",
+ tabIndex: 0
+ });
+ },
+
+ _activate: function( index ) {
+ var anchor,
+ active = this._findActive( index );
+
+ // trying to activate the already active panel
+ if ( active[ 0 ] === this.active[ 0 ] ) {
+ return;
+ }
+
+ // trying to collapse, simulate a click on the current active header
+ if ( !active.length ) {
+ active = this.active;
+ }
+
+ anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
+ this._eventHandler({
+ target: anchor,
+ currentTarget: anchor,
+ preventDefault: $.noop
+ });
+ },
+
+ _findActive: function( index ) {
+ return index === false ? $() : this.tabs.eq( index );
+ },
+
+ _getIndex: function( index ) {
+ // meta-function to give users option to provide a href string instead of a numerical index.
+ if ( typeof index === "string" ) {
+ index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
+ }
+
+ return index;
+ },
+
+ _destroy: function() {
+ if ( this.xhr ) {
+ this.xhr.abort();
+ }
+
+ this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
+
+ this.tablist
+ .removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
+ .removeAttr( "role" );
+
+ this.anchors
+ .removeClass( "ui-tabs-anchor" )
+ .removeAttr( "role" )
+ .removeAttr( "tabIndex" )
+ .removeUniqueId();
+
+ this.tabs.add( this.panels ).each(function() {
+ if ( $.data( this, "ui-tabs-destroy" ) ) {
+ $( this ).remove();
+ } else {
+ $( this )
+ .removeClass( "ui-state-default ui-state-active ui-state-disabled " +
+ "ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
+ .removeAttr( "tabIndex" )
+ .removeAttr( "aria-live" )
+ .removeAttr( "aria-busy" )
+ .removeAttr( "aria-selected" )
+ .removeAttr( "aria-labelledby" )
+ .removeAttr( "aria-hidden" )
+ .removeAttr( "aria-expanded" )
+ .removeAttr( "role" );
+ }
+ });
+
+ this.tabs.each(function() {
+ var li = $( this ),
+ prev = li.data( "ui-tabs-aria-controls" );
+ if ( prev ) {
+ li
+ .attr( "aria-controls", prev )
+ .removeData( "ui-tabs-aria-controls" );
+ } else {
+ li.removeAttr( "aria-controls" );
+ }
+ });
+
+ this.panels.show();
+
+ if ( this.options.heightStyle !== "content" ) {
+ this.panels.css( "height", "" );
+ }
+ },
+
+ enable: function( index ) {
+ var disabled = this.options.disabled;
+ if ( disabled === false ) {
+ return;
+ }
+
+ if ( index === undefined ) {
+ disabled = false;
+ } else {
+ index = this._getIndex( index );
+ if ( $.isArray( disabled ) ) {
+ disabled = $.map( disabled, function( num ) {
+ return num !== index ? num : null;
+ });
+ } else {
+ disabled = $.map( this.tabs, function( li, num ) {
+ return num !== index ? num : null;
+ });
+ }
+ }
+ this._setupDisabled( disabled );
+ },
+
+ disable: function( index ) {
+ var disabled = this.options.disabled;
+ if ( disabled === true ) {
+ return;
+ }
+
+ if ( index === undefined ) {
+ disabled = true;
+ } else {
+ index = this._getIndex( index );
+ if ( $.inArray( index, disabled ) !== -1 ) {
+ return;
+ }
+ if ( $.isArray( disabled ) ) {
+ disabled = $.merge( [ index ], disabled ).sort();
+ } else {
+ disabled = [ index ];
+ }
+ }
+ this._setupDisabled( disabled );
+ },
+
+ load: function( index, event ) {
+ index = this._getIndex( index );
+ var that = this,
+ tab = this.tabs.eq( index ),
+ anchor = tab.find( ".ui-tabs-anchor" ),
+ panel = this._getPanelForTab( tab ),
+ eventData = {
+ tab: tab,
+ panel: panel
+ };
+
+ // not remote
+ if ( isLocal( anchor[ 0 ] ) ) {
+ return;
+ }
+
+ this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
+
+ // support: jQuery <1.8
+ // jQuery <1.8 returns false if the request is canceled in beforeSend,
+ // but as of 1.8, $.ajax() always returns a jqXHR object.
+ if ( this.xhr && this.xhr.statusText !== "canceled" ) {
+ tab.addClass( "ui-tabs-loading" );
+ panel.attr( "aria-busy", "true" );
+
+ this.xhr
+ .success(function( response ) {
+ // support: jQuery <1.8
+ // http://bugs.jquery.com/ticket/11778
+ setTimeout(function() {
+ panel.html( response );
+ that._trigger( "load", event, eventData );
+ }, 1 );
+ })
+ .complete(function( jqXHR, status ) {
+ // support: jQuery <1.8
+ // http://bugs.jquery.com/ticket/11778
+ setTimeout(function() {
+ if ( status === "abort" ) {
+ that.panels.stop( false, true );
+ }
+
+ tab.removeClass( "ui-tabs-loading" );
+ panel.removeAttr( "aria-busy" );
+
+ if ( jqXHR === that.xhr ) {
+ delete that.xhr;
+ }
+ }, 1 );
+ });
+ }
+ },
+
+ _ajaxSettings: function( anchor, event, eventData ) {
+ var that = this;
+ return {
+ url: anchor.attr( "href" ),
+ beforeSend: function( jqXHR, settings ) {
+ return that._trigger( "beforeLoad", event,
+ $.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
+ }
+ };
+ },
+
+ _getPanelForTab: function( tab ) {
+ var id = $( tab ).attr( "aria-controls" );
+ return this.element.find( this._sanitizeSelector( "#" + id ) );
+ }
+});
+
+})( jQuery );
+
+(function( $ ) {
+
+var increments = 0;
+
+function addDescribedBy( elem, id ) {
+ var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
+ describedby.push( id );
+ elem
+ .data( "ui-tooltip-id", id )
+ .attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+}
+
+function removeDescribedBy( elem ) {
+ var id = elem.data( "ui-tooltip-id" ),
+ describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
+ index = $.inArray( id, describedby );
+ if ( index !== -1 ) {
+ describedby.splice( index, 1 );
+ }
+
+ elem.removeData( "ui-tooltip-id" );
+ describedby = $.trim( describedby.join( " " ) );
+ if ( describedby ) {
+ elem.attr( "aria-describedby", describedby );
+ } else {
+ elem.removeAttr( "aria-describedby" );
+ }
+}
+
+$.widget( "ui.tooltip", {
+ version: "1.10.2",
+ options: {
+ content: function() {
+ // support: IE<9, Opera in jQuery <1.7
+ // .text() can't accept undefined, so coerce to a string
+ var title = $( this ).attr( "title" ) || "";
+ // Escape title, since we're going from an attribute to raw HTML
+ return $( "<a>" ).text( title ).html();
+ },
+ hide: true,
+ // Disabled elements have inconsistent behavior across browsers (#8661)
+ items: "[title]:not([disabled])",
+ position: {
+ my: "left top+15",
+ at: "left bottom",
+ collision: "flipfit flip"
+ },
+ show: true,
+ tooltipClass: null,
+ track: false,
+
+ // callbacks
+ close: null,
+ open: null
+ },
+
+ _create: function() {
+ this._on({
+ mouseover: "open",
+ focusin: "open"
+ });
+
+ // IDs of generated tooltips, needed for destroy
+ this.tooltips = {};
+ // IDs of parent tooltips where we removed the title attribute
+ this.parents = {};
+
+ if ( this.options.disabled ) {
+ this._disable();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var that = this;
+
+ if ( key === "disabled" ) {
+ this[ value ? "_disable" : "_enable" ]();
+ this.options[ key ] = value;
+ // disable element style changes
+ return;
+ }
+
+ this._super( key, value );
+
+ if ( key === "content" ) {
+ $.each( this.tooltips, function( id, element ) {
+ that._updateContent( element );
+ });
+ }
+ },
+
+ _disable: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+ });
+
+ // remove title attributes to prevent native tooltips
+ this.element.find( this.options.items ).addBack().each(function() {
+ var element = $( this );
+ if ( element.is( "[title]" ) ) {
+ element
+ .data( "ui-tooltip-title", element.attr( "title" ) )
+ .attr( "title", "" );
+ }
+ });
+ },
+
+ _enable: function() {
+ // restore title attributes
+ this.element.find( this.options.items ).addBack().each(function() {
+ var element = $( this );
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ }
+ });
+ },
+
+ open: function( event ) {
+ var that = this,
+ target = $( event ? event.target : this.element )
+ // we need closest here due to mouseover bubbling,
+ // but always pointing at the same event target
+ .closest( this.options.items );
+
+ // No element to show a tooltip for or the tooltip is already open
+ if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+ return;
+ }
+
+ if ( target.attr( "title" ) ) {
+ target.data( "ui-tooltip-title", target.attr( "title" ) );
+ }
+
+ target.data( "ui-tooltip-open", true );
+
+ // kill parent tooltips, custom or native, for hover
+ if ( event && event.type === "mouseover" ) {
+ target.parents().each(function() {
+ var parent = $( this ),
+ blurEvent;
+ if ( parent.data( "ui-tooltip-open" ) ) {
+ blurEvent = $.Event( "blur" );
+ blurEvent.target = blurEvent.currentTarget = this;
+ that.close( blurEvent, true );
+ }
+ if ( parent.attr( "title" ) ) {
+ parent.uniqueId();
+ that.parents[ this.id ] = {
+ element: this,
+ title: parent.attr( "title" )
+ };
+ parent.attr( "title", "" );
+ }
+ });
+ }
+
+ this._updateContent( target, event );
+ },
+
+ _updateContent: function( target, event ) {
+ var content,
+ contentOption = this.options.content,
+ that = this,
+ eventType = event ? event.type : null;
+
+ if ( typeof contentOption === "string" ) {
+ return this._open( event, target, contentOption );
+ }
+
+ content = contentOption.call( target[0], function( response ) {
+ // ignore async response if tooltip was closed already
+ if ( !target.data( "ui-tooltip-open" ) ) {
+ return;
+ }
+ // IE may instantly serve a cached response for ajax requests
+ // delay this call to _open so the other call to _open runs first
+ that._delay(function() {
+ // jQuery creates a special event for focusin when it doesn't
+ // exist natively. To improve performance, the native event
+ // object is reused and the type is changed. Therefore, we can't
+ // rely on the type being correct after the event finished
+ // bubbling, so we set it back to the previous value. (#8740)
+ if ( event ) {
+ event.type = eventType;
+ }
+ this._open( event, target, response );
+ });
+ });
+ if ( content ) {
+ this._open( event, target, content );
+ }
+ },
+
+ _open: function( event, target, content ) {
+ var tooltip, events, delayedShow,
+ positionOption = $.extend( {}, this.options.position );
+
+ if ( !content ) {
+ return;
+ }
+
+ // Content can be updated multiple times. If the tooltip already
+ // exists, then just update the content and bail.
+ tooltip = this._find( target );
+ if ( tooltip.length ) {
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+ return;
+ }
+
+ // if we have a title, clear it to prevent the native tooltip
+ // we have to check first to avoid defining a title if none exists
+ // (we don't want to cause an element to start matching [title])
+ //
+ // We use removeAttr only for key events, to allow IE to export the correct
+ // accessible attributes. For mouse events, set to empty string to avoid
+ // native tooltip showing up (happens only when removing inside mouseover).
+ if ( target.is( "[title]" ) ) {
+ if ( event && event.type === "mouseover" ) {
+ target.attr( "title", "" );
+ } else {
+ target.removeAttr( "title" );
+ }
+ }
+
+ tooltip = this._tooltip( target );
+ addDescribedBy( target, tooltip.attr( "id" ) );
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+
+ function position( event ) {
+ positionOption.of = event;
+ if ( tooltip.is( ":hidden" ) ) {
+ return;
+ }
+ tooltip.position( positionOption );
+ }
+ if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+ this._on( this.document, {
+ mousemove: position
+ });
+ // trigger once to override element-relative positioning
+ position( event );
+ } else {
+ tooltip.position( $.extend({
+ of: target
+ }, this.options.position ) );
+ }
+
+ tooltip.hide();
+
+ this._show( tooltip, this.options.show );
+ // Handle tracking tooltips that are shown with a delay (#8644). As soon
+ // as the tooltip is visible, position the tooltip using the most recent
+ // event.
+ if ( this.options.show && this.options.show.delay ) {
+ delayedShow = this.delayedShow = setInterval(function() {
+ if ( tooltip.is( ":visible" ) ) {
+ position( positionOption.of );
+ clearInterval( delayedShow );
+ }
+ }, $.fx.interval );
+ }
+
+ this._trigger( "open", event, { tooltip: tooltip } );
+
+ events = {
+ keyup: function( event ) {
+ if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+ var fakeEvent = $.Event(event);
+ fakeEvent.currentTarget = target[0];
+ this.close( fakeEvent, true );
+ }
+ },
+ remove: function() {
+ this._removeTooltip( tooltip );
+ }
+ };
+ if ( !event || event.type === "mouseover" ) {
+ events.mouseleave = "close";
+ }
+ if ( !event || event.type === "focusin" ) {
+ events.focusout = "close";
+ }
+ this._on( true, target, events );
+ },
+
+ close: function( event ) {
+ var that = this,
+ target = $( event ? event.currentTarget : this.element ),
+ tooltip = this._find( target );
+
+ // disabling closes the tooltip, so we need to track when we're closing
+ // to avoid an infinite loop in case the tooltip becomes disabled on close
+ if ( this.closing ) {
+ return;
+ }
+
+ // Clear the interval for delayed tracking tooltips
+ clearInterval( this.delayedShow );
+
+ // only set title if we had one before (see comment in _open())
+ if ( target.data( "ui-tooltip-title" ) ) {
+ target.attr( "title", target.data( "ui-tooltip-title" ) );
+ }
+
+ removeDescribedBy( target );
+
+ tooltip.stop( true );
+ this._hide( tooltip, this.options.hide, function() {
+ that._removeTooltip( $( this ) );
+ });
+
+ target.removeData( "ui-tooltip-open" );
+ this._off( target, "mouseleave focusout keyup" );
+ // Remove 'remove' binding only on delegated targets
+ if ( target[0] !== this.element[0] ) {
+ this._off( target, "remove" );
+ }
+ this._off( this.document, "mousemove" );
+
+ if ( event && event.type === "mouseleave" ) {
+ $.each( this.parents, function( id, parent ) {
+ $( parent.element ).attr( "title", parent.title );
+ delete that.parents[ id ];
+ });
+ }
+
+ this.closing = true;
+ this._trigger( "close", event, { tooltip: tooltip } );
+ this.closing = false;
+ },
+
+ _tooltip: function( element ) {
+ var id = "ui-tooltip-" + increments++,
+ tooltip = $( "<div>" )
+ .attr({
+ id: id,
+ role: "tooltip"
+ })
+ .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+ ( this.options.tooltipClass || "" ) );
+ $( "<div>" )
+ .addClass( "ui-tooltip-content" )
+ .appendTo( tooltip );
+ tooltip.appendTo( this.document[0].body );
+ this.tooltips[ id ] = element;
+ return tooltip;
+ },
+
+ _find: function( target ) {
+ var id = target.data( "ui-tooltip-id" );
+ return id ? $( "#" + id ) : $();
+ },
+
+ _removeTooltip: function( tooltip ) {
+ tooltip.remove();
+ delete this.tooltips[ tooltip.attr( "id" ) ];
+ },
+
+ _destroy: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ // Delegate to close method to handle common cleanup
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+
+ // Remove immediately; destroying an open tooltip doesn't use the
+ // hide animation
+ $( "#" + id ).remove();
+
+ // Restore the title
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ element.removeData( "ui-tooltip-title" );
+ }
+ });
+ }
+});
+
+}( jQuery ) );
diff --git a/src/pmwebapi/jsdemos/jquery-ui-themes-1.10.2.tar.gz b/src/pmwebapi/jsdemos/jquery-ui-themes-1.10.2.tar.gz
new file mode 100644
index 0000000..3bcbdbe
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jquery-ui-themes-1.10.2.tar.gz
Binary files differ
diff --git a/src/pmwebapi/jsdemos/jsbrowser/GNUmakefile b/src/pmwebapi/jsdemos/jsbrowser/GNUmakefile
new file mode 100644
index 0000000..70b65a1
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jsbrowser/GNUmakefile
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2013 Red Hat. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../../..
+include $(TOPDIR)/src/include/builddefs
+
+JSFILES = jsbrowser.js
+HTMLFILES = index.html
+
+LSRCFILES = $(JSFILES) $(HTMLFILES)
+
+default default_pcp:
+
+include $(BUILDRULES)
+
+install install_pcp:
diff --git a/src/pmwebapi/jsdemos/jsbrowser/index.html b/src/pmwebapi/jsdemos/jsbrowser/index.html
new file mode 100644
index 0000000..bf79522
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jsbrowser/index.html
@@ -0,0 +1,34 @@
+<html>
+ <head>
+ <script type="text/javascript" src="../jquery-1.7.2.js"></script>
+ <script type="text/javascript" src="jsbrowser.js"></script>
+ <title>pcp metric browser</title>
+ </head>
+ <body>
+
+ <!-- metadata about current context -->
+
+ <div id="context">
+ </div>
+
+ <!-- forms associated with connecting to new context -->
+
+ <div id="new_context">
+ <h1>pcp metric browser: host-local connection</h1>
+ <input type="submit" value="submit"></input>
+ <h1>pcp metric browser: remote connection</h1>
+ <input type="text" name="hostname"></input>
+ <h1>pcp metric browser: archive file</h1>
+ <input type="text" name="archivefile"></input>
+ </div>
+
+ <!-- checkboxed list of metrics -->
+
+ <div id="metrics">
+ </div>
+
+ <!-- metric values -->
+ <div id="values">
+ </div>
+ </body>
+</html>
diff --git a/src/pmwebapi/jsdemos/jsbrowser/jsbrowser.js b/src/pmwebapi/jsdemos/jsbrowser/jsbrowser.js
new file mode 100644
index 0000000..91d9a01
--- /dev/null
+++ b/src/pmwebapi/jsdemos/jsbrowser/jsbrowser.js
@@ -0,0 +1,12 @@
+/*!
+ */
+
+var context = -1; /* pmwebapi context, if connected */
+
+/* Initial load. */
+$(document).ready(function() {
+
+});
+
+
+
diff --git a/src/pmwebapi/main.c b/src/pmwebapi/main.c
new file mode 100644
index 0000000..a8a7920
--- /dev/null
+++ b/src/pmwebapi/main.c
@@ -0,0 +1,561 @@
+/*
+ * JSON web bridge for PMAPI.
+ *
+ * Copyright (c) 2011-2014 Red Hat.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmwebapi.h"
+
+const char uriprefix[] = "/pmapi";
+char *resourcedir; /* set by -R option */
+char *archivesdir = "."; /* set by -A option */
+unsigned verbosity; /* set by -v option */
+unsigned maxtimeout = 300; /* set by -t option */
+unsigned perm_context = 1; /* set by -c option, changed by -h/-a/-L */
+unsigned new_contexts_p = 1; /* set by -N option */
+unsigned exit_p; /* counted by SIG* handler */
+static char *logfile = "pmwebd.log";
+static char *fatalfile = "/dev/tty"; /* fatal messages at startup go here */
+static char *username;
+static __pmServerPresence *presence;
+
+static int
+mhd_log_args(void *connection, enum MHD_ValueKind kind,
+ const char *key, const char *value)
+{
+ (void)kind;
+ pmweb_notify(LOG_DEBUG, connection, "%s%s%s",
+ key, value ? "=" : "", value ? value : "");
+ return MHD_YES;
+}
+
+/*
+ * Respond to a new incoming HTTP request. It may be
+ * one of three general categories:
+ * (a) creation of a new PMAPI context: do it
+ * (b) operation on an existing context: do it
+ * (c) access to some non-API URI: serve it from $resourcedir/ if configured.
+ */
+static int
+mhd_respond(void *cls, struct MHD_Connection *connection,
+ const char *url, const char *method, const char *version,
+ const char *upload_data, size_t *upload_data_size, void **con_cls)
+{
+ /* "error_page" could also be an actual error page... */
+ static const char error_page[] = "PMWEBD error";
+ static int dummy;
+
+ struct MHD_Response *resp = NULL;
+ int sts;
+
+ /*
+ * MHD calls us at least twice per request. Skip the first one,
+ * since it only gives us headers, and not any POST content.
+ */
+ if (& dummy != *con_cls) {
+ *con_cls = &dummy;
+ return MHD_YES;
+ }
+ *con_cls = NULL;
+
+ if (verbosity > 1)
+ pmweb_notify(LOG_INFO, connection, "%s %s %s", version, method, url);
+ if (verbosity > 2) /* Print arguments too. */
+ (void) MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND,
+ &mhd_log_args, connection);
+
+ /* pmwebapi? */
+ if (0 == strncmp(url, uriprefix, strlen(uriprefix)))
+ return pmwebapi_respond(cls, connection,
+ &url[strlen(uriprefix)+1], /* strip prefix */
+ method, upload_data, upload_data_size);
+ /* pmresapi? */
+ else if (0 == strcmp(method, "GET") && resourcedir != NULL)
+ return pmwebres_respond(cls, connection, url);
+
+ /* junk? */
+ MHD_add_response_header(resp, "Content-Type", "text/plain");
+ resp = MHD_create_response_from_buffer(strlen(error_page),
+ (char*)error_page,
+ MHD_RESPMEM_PERSISTENT);
+ if (resp == NULL) {
+ pmweb_notify(LOG_ERR, connection,
+ "MHD_create_response_from_callback failed\n");
+ return MHD_NO;
+ }
+
+ sts = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, resp);
+ MHD_destroy_response(resp);
+ if (sts != MHD_YES) {
+ pmweb_notify(LOG_ERR, connection, "MHD_queue_response failed\n");
+ return MHD_NO;
+ }
+ return MHD_YES;
+}
+
+static void
+handle_signals(int sig)
+{
+ (void)sig;
+ exit_p++;
+}
+
+static void
+pmweb_dont_start(void)
+{
+ FILE *tty;
+ FILE *log;
+
+ __pmNotifyErr(LOG_ERR, "pmwebd not started due to errors!\n");
+
+ if ((tty = fopen(fatalfile, "w")) != NULL) {
+ fflush(stderr);
+ fprintf(tty, "NOTE: pmwebd not started due to errors! ");
+ if ((log = fopen(logfile, "r")) != NULL) {
+ int c;
+
+ fprintf(tty, "Log file \"%s\" contains ...\n", logfile);
+ while ((c = fgetc(log)) != EOF)
+ fputc(c, tty);
+ fclose(log);
+ }
+ else
+ fprintf(tty, "Log file \"%s\" has vanished!\n", logfile);
+ fclose(tty);
+ }
+ exit(1);
+}
+
+/*
+ * Local variant of __pmServerDumpRequestPorts - remove this
+ * when an NSS-based pmweb daemon is built.
+ */
+static void
+server_dump_request_ports(FILE *f, int ipv4, int ipv6, int port)
+{
+ if (ipv4)
+ fprintf(f, "Started daemon on IPv4 TCP port %d\n", port);
+ if (ipv6)
+ fprintf(f, "Started daemon on IPv6 TCP port %d\n", port);
+}
+
+static void
+server_dump_configuration(FILE *f)
+{
+ char *cwd;
+ char path[MAXPATHLEN];
+ char cwdpath[MAXPATHLEN];
+ int sep = __pmPathSeparator();
+ int len;
+
+ cwd = getcwd(cwdpath, sizeof(cwdpath));
+ if (resourcedir) {
+ len = (__pmAbsolutePath(resourcedir) || !cwd) ?
+ snprintf(path, sizeof(path), "%s", resourcedir) :
+ snprintf(path, sizeof(path), "%s%c%s", cwd, sep, resourcedir);
+ while (len-- > 1) {
+ if (path[len] != '.' && path[len] != sep) break;
+ path[len] = '\0';
+ }
+ fprintf (f, "Serving non-pmwebapi URLs under directory %s\n", path);
+ }
+ if (new_contexts_p) {
+ len = (__pmAbsolutePath(archivesdir) || !cwd) ?
+ snprintf(path, sizeof(path), "%s", archivesdir) :
+ snprintf(path, sizeof(path), "%s%c%s", cwd, sep, archivesdir);
+ while (len-- > 1) {
+ if (path[len] != '.' && path[len] != sep) break;
+ path[len] = '\0';
+ }
+ /* XXX: network outbound ACL */
+ fprintf(f, "Serving PCP archives under directory %s\n", path);
+ } else {
+ fprintf(f, "Remote context creation requests disabled\n");
+ }
+}
+
+static void
+pmweb_init_random_seed(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ srandom((unsigned int) getpid() ^
+ (unsigned int) tv.tv_sec ^
+ (unsigned int) tv.tv_usec);
+}
+
+static void
+pmweb_shutdown(struct MHD_Daemon *d4, struct MHD_Daemon *d6)
+{
+ /* Shut down cleanly, out of a misplaced sense of propriety. */
+ if (d4)
+ MHD_stop_daemon(d4);
+ if (d6)
+ MHD_stop_daemon(d6);
+
+ /* No longer advertise pmwebd presence on the network. */
+ __pmServerUnadvertisePresence(presence);
+
+ /*
+ * Let's politely clean up all the active contexts.
+ * The OS will do all that for us anyway, but let's make valgrind happy.
+ */
+ pmwebapi_deallocate_all();
+
+ __pmNotifyErr(LOG_INFO, "pmwebd Shutdown\n");
+ fflush(stderr);
+}
+
+static int
+option_overrides(int opt, pmOptions *opts)
+{
+ (void)opts;
+
+ switch (opt) {
+ case 'A': case 'a': case 'h': case 'L': case 'N': case 'p': case 't':
+ return 1;
+ }
+ return 0;
+}
+
+static pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Network options"),
+ { "port", 1, 'p', "NUM", "listen on given TCP port [default 44323]" },
+ { "ipv4", 0, '4', 0, "listen on IPv4 only" },
+ { "ipv6", 0, '6', 0, "listen on IPv6 only" },
+ { "timeout", 1, 't', "SEC", "max time (seconds) for PMAPI polling [default 300]" },
+ { "resources", 1, 'R', "DIR", "serve non-API files from given directory" },
+ PMAPI_OPTIONS_HEADER("Context options"),
+ { "context", 1, 'c', "NUM", "set next permanent-binding context number" },
+ { "host", 1, 'h', "HOST", "permanent-bind next context to PMCD on host" },
+ { "archive", 1, 'a', "FILE", "permanent-bind next context to archive" },
+ { "local-PMDA", 0, 'L', 0, "permanent-bind next context to local PMDAs" },
+ PMOPT_SPECLOCAL,
+ PMOPT_LOCALPMDA,
+ { "", 0, 'N', 0, "disable remote new-context requests" },
+ { "", 1, 'A', "DIR", "permit remote new-archive-context under dir [default CWD]" },
+ PMAPI_OPTIONS_HEADER("Other"),
+ PMOPT_DEBUG,
+ { "foreground", 0, 'f', 0, "run in the foreground" },
+ { "log", 1, 'l', "FILE", "redirect diagnostics and trace output" },
+ { "verbose", 0, 'v', 0, "increase verbosity" },
+ { "username", 1, 'U', "USER", "assume identity of username [default pcp]" },
+ { "", 1, 'x', "PATH", "fatal messages at startup sent to file [default /dev/tty]" },
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+static pmOptions opts = {
+ .short_options = "A:a:c:D:fh:K:Ll:Np:R:t:U:vx:46?",
+ .long_options = longopts,
+ .override = option_overrides,
+};
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int sts;
+ int ctx;
+ int mhd_ipv4 = 1;
+ int mhd_ipv6 = 1;
+ int run_daemon = 1;
+ int port = PMWEBD_PORT;
+ char *endptr;
+ struct MHD_Daemon *d4 = NULL;
+ struct MHD_Daemon *d6 = NULL;
+
+ umask(022);
+ __pmGetUsername(&username);
+
+ while ((c = pmGetOptions(argc, argv, &opts)) != EOF) {
+ switch (c) {
+ case 'p':
+ port = (int)strtol(opts.optarg, &endptr, 0);
+ if (*endptr != '\0' || port < 0 || port > 65535) {
+ pmprintf("%s: invalid port number %s\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ break;
+
+ case 't':
+ maxtimeout = strtoul(opts.optarg, &endptr, 0);
+ if (*endptr != '\0') {
+ pmprintf("%s: invalid timeout %s\n", pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ break;
+
+ case 'R':
+ resourcedir = opts.optarg;
+ break;
+
+ case 'A':
+ archivesdir = opts.optarg;
+ break;
+
+ case '6':
+ mhd_ipv6 = 1;
+ mhd_ipv4 = 0;
+ break;
+
+ case '4':
+ mhd_ipv4 = 1;
+ mhd_ipv6 = 0;
+ break;
+
+ case 'v':
+ verbosity++;
+ break;
+
+ case 'c':
+ perm_context = strtoul(opts.optarg, &endptr, 0);
+ if (*endptr != '\0' || perm_context >= INT_MAX) {
+ pmprintf("%s: invalid context number %s\n",
+ pmProgname, opts.optarg);
+ opts.errors++;
+ }
+ break;
+
+ case 'h':
+ if ((ctx = pmNewContext(PM_CONTEXT_HOST, opts.optarg)) < 0) {
+ __pmNotifyErr(LOG_ERR, "new context failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((sts = pmwebapi_bind_permanent(perm_context++, ctx)) < 0) {
+ __pmNotifyErr(LOG_ERR, "permanent bind failed\n");
+ exit(EXIT_FAILURE);
+ }
+ __pmNotifyErr(LOG_INFO,
+ "context (web%d=pm%d) created, host %s, permanent\n",
+ perm_context - 1, ctx, opts.optarg);
+ break;
+
+ case 'a':
+ if ((ctx = pmNewContext(PM_CONTEXT_ARCHIVE, opts.optarg)) < 0) {
+ __pmNotifyErr(LOG_ERR, "new context failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((sts = pmwebapi_bind_permanent(perm_context++, ctx)) < 0) {
+ __pmNotifyErr(LOG_ERR, "permanent bind failed\n");
+ exit(EXIT_FAILURE);
+ }
+ __pmNotifyErr(LOG_INFO,
+ "context (web%d=pm%d) created, archive %s, permanent\n",
+ perm_context - 1, ctx, opts.optarg);
+ break;
+
+ case 'L':
+ if ((ctx = pmNewContext(PM_CONTEXT_LOCAL, NULL)) < 0) {
+ __pmNotifyErr(LOG_ERR, "new context failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if ((sts = pmwebapi_bind_permanent(perm_context++, ctx)) < 0) {
+ __pmNotifyErr(LOG_ERR, "permanent bind failed\n");
+ exit(EXIT_FAILURE);
+ }
+ __pmNotifyErr(LOG_INFO,
+ "context (web%d=pm%d) created, local, permanent\n",
+ perm_context - 1, ctx);
+ break;
+
+ case 'N':
+ new_contexts_p = 0;
+ break;
+
+ case 'f':
+ /* foreground, i.e. do _not_ run as a daemon */
+ run_daemon = 0;
+ break;
+
+ case 'l':
+ /* log file name */
+ logfile = opts.optarg;
+ break;
+
+ case 'U':
+ /* run as user username */
+ username = opts.optarg;
+ break;
+
+ case 'x':
+ fatalfile = opts.optarg;
+ break;
+ }
+ }
+
+ if (opts.errors) {
+ pmUsageMessage(&opts);
+ exit(EXIT_FAILURE);
+ }
+
+ if (run_daemon) {
+ fflush(stderr);
+ pmweb_start_daemon(argc, argv);
+ }
+
+ /*
+ * Start microhttp daemon. Use the application-driven threading
+ * model, so we don't complicate things with threads. In the
+ * future, if this daemon becomes safe to run multithreaded,
+ * we could make use of MHD_USE_THREAD_PER_CONNECTION; we'd need
+ * to add ample locking over pmwebd context structures etc.
+ */
+ if (mhd_ipv4)
+ d4 = MHD_start_daemon(0,
+ port,
+ NULL, NULL, /* default accept policy */
+ &mhd_respond, NULL, /* handler callback */
+ MHD_OPTION_CONNECTION_TIMEOUT, maxtimeout,
+ MHD_OPTION_END);
+ if (mhd_ipv6)
+ d6 = MHD_start_daemon(MHD_USE_IPv6,
+ port,
+ NULL, NULL, /* default accept policy */
+ &mhd_respond, NULL, /* handler callback */
+ MHD_OPTION_CONNECTION_TIMEOUT, maxtimeout,
+ MHD_OPTION_END);
+ if (d4 == NULL && d6 == NULL) {
+ __pmNotifyErr(LOG_ERR, "error starting microhttpd daemons\n");
+ pmweb_dont_start();
+ }
+
+ /* tell the world we have arrived */
+ __pmServerCreatePIDFile(PM_SERVER_WEBD_SPEC, 0);
+ presence = __pmServerAdvertisePresence(PM_SERVER_WEBD_SPEC, port);
+
+ __pmOpenLog(pmProgname, logfile, stderr, &sts);
+ /* close old stdout, and force stdout into same stream as stderr */
+ fflush(stdout);
+ close(fileno(stdout));
+ if (dup(fileno(stderr)) == -1)
+ fprintf(stderr, "Warning: dup() failed: %s\n", pmErrStr(-oserror()));
+ fprintf(stderr, "%s: PID = %" FMT_PID ", PMAPI URL = %s\n",
+ pmProgname, getpid(), uriprefix);
+ server_dump_request_ports(stderr, d4 != NULL, d6 != NULL, port);
+ server_dump_configuration(stderr);
+ fflush(stderr);
+
+ /* Set up signal handlers. */
+ __pmSetSignalHandler(SIGHUP, SIG_IGN);
+ __pmSetSignalHandler(SIGINT, handle_signals);
+ __pmSetSignalHandler(SIGTERM, handle_signals);
+ __pmSetSignalHandler(SIGQUIT, handle_signals);
+ /* Not this one; might get it from pmcd momentary disconnection. */
+ /* __pmSetSignalHandler(SIGPIPE, handle_signals); */
+
+ /* lose root privileges if we have them */
+ __pmSetProcessIdentity(username);
+
+ /* Setup randomness for calls to random() */
+ pmweb_init_random_seed();
+
+ /* Block indefinitely. */
+ while (! exit_p) {
+ struct timeval tv;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ int max = 0;
+
+ /* Based upon MHD fileserver_example_external_select.c */
+ FD_ZERO(&rs);
+ FD_ZERO(&ws);
+ FD_ZERO(&es);
+ if (d4 && MHD_YES != MHD_get_fdset(d4, &rs, &ws, &es, &max))
+ break; /* fatal internal error */
+ if (d6 && MHD_YES != MHD_get_fdset(d6, &rs, &ws, &es, &max))
+ break; /* fatal internal error */
+
+ /*
+ * Find the next expiry. We don't need to bound it by
+ * MHD_get_timeout, since we don't use a small
+ * MHD_OPTION_CONNECTION_TIMEOUT.
+ */
+ tv.tv_sec = pmwebapi_gc();
+ tv.tv_usec = 0;
+
+ select(max+1, &rs, &ws, &es, &tv);
+
+ if (d4)
+ MHD_run(d4);
+ if (d6)
+ MHD_run(d6);
+ }
+
+ pmweb_shutdown(d4, d6);
+ return 0;
+}
+
+/*
+ * Generate a __pmNotifyErr with the given arguments,
+ * but also adding some per-connection metadata info.
+ */
+void
+pmweb_notify(int priority, struct MHD_Connection *conn, const char *fmt, ...)
+{
+ struct sockaddr *so;
+ va_list arg;
+ char message_buf[2048]; /* size similar to __pmNotifyErr */
+ char *message_tail;
+ size_t message_len;
+ char hostname[128];
+ char servname[128];
+ int sts = -1;
+
+ /* Look up client address data. */
+ so = (struct sockaddr *) MHD_get_connection_info(conn,
+ MHD_CONNECTION_INFO_CLIENT_ADDRESS)->client_addr;
+
+ if (so && so->sa_family == AF_INET)
+ sts = getnameinfo(so, sizeof(struct sockaddr_in),
+ hostname, sizeof(hostname),
+ servname, sizeof(servname),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ else if (so && so->sa_family == AF_INET6)
+ sts = getnameinfo(so, sizeof(struct sockaddr_in6),
+ hostname, sizeof(hostname),
+ servname, sizeof(servname),
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ if (sts != 0)
+ hostname[0] = servname[0] = '\0';
+
+ /* Add the [hostname:port] as a prefix */
+ sts = snprintf(message_buf, sizeof(message_buf), "[%s:%s] ",
+ hostname, servname);
+
+ if (sts > 0 && sts < (int)sizeof(message_buf)) {
+ message_tail = message_buf + sts; /* Keep it only if successful. */
+ message_len = sizeof(message_buf) - sts;
+ } else {
+ message_tail = message_buf;
+ message_len = sizeof(message_buf);
+ }
+
+ /* Add the remaining incoming text. */
+ va_start(arg, fmt);
+ sts = vsnprintf(message_tail, message_len, fmt, arg);
+ va_end(arg);
+
+ /*
+ * Delegate, but avoid format-string vulnerabilities. Drop the
+ * trailing \n, if there is one, since __pmNotifyErr will add one
+ * for us (since it is missing from the %s format string).
+ */
+ if (sts >= 0 && sts < (int)message_len)
+ if (message_tail[sts-1] == '\n')
+ message_tail[sts-1] = '\0';
+ __pmNotifyErr(priority, "%s", message_buf);
+}
diff --git a/src/pmwebapi/pmresapi.c b/src/pmwebapi/pmresapi.c
new file mode 100644
index 0000000..0585646
--- /dev/null
+++ b/src/pmwebapi/pmresapi.c
@@ -0,0 +1,162 @@
+/*
+ * JSON web bridge for PMAPI.
+ *
+ * Copyright (c) 2011-2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "pmwebapi.h"
+#include "config.h"
+
+
+/* ------------------------------------------------------------------------ */
+
+static const char *guess_content_type (const char* filename)
+{
+ const char *extension = rindex (filename, '.');
+ if (extension == NULL) return NULL;
+
+ /* One could go all out and parse /etc/mime.types, or one can do this ... */
+ if (0 == strcasecmp (extension, "html")) return "text/html";
+ if (0 == strcasecmp (extension, "js")) return "text/javascript";
+ if (0 == strcasecmp (extension, "json")) return "application/json";
+ if (0 == strcasecmp (extension, "txt")) return "text/plain";
+ if (0 == strcasecmp (extension, "xml")) return "text/xml";
+ if (0 == strcasecmp (extension, "svg")) return "image/svg+xml";
+ if (0 == strcasecmp (extension, "png")) return "image/png";
+ if (0 == strcasecmp (extension, "jpg")) return "image/jpg";
+
+ return NULL;
+}
+
+
+static const char *create_rfc822_date (time_t t)
+{
+ static char datebuf[80]; /* if-threaded: unstaticify */
+ struct tm *now = gmtime (& t);
+ size_t rc = strftime (datebuf, sizeof(datebuf), "%a, %d %b %Y %T %z", now);
+ if (rc <= 0 || rc >= sizeof(datebuf)) return NULL;
+ return datebuf;
+}
+
+
+
+/* Respond to a GET request, not under the pmwebapi URL prefix. This
+ is a mini fileserver, just for small standalone installations of
+ pmwebapi-based web front-ends. */
+int pmwebres_respond (void *cls, struct MHD_Connection *connection,
+ const char* url)
+{
+ int fd = -1;
+ int rc;
+ char filename [PATH_MAX];
+ struct stat fds;
+ unsigned int resp_code = MHD_HTTP_OK;
+ struct MHD_Response *resp;
+ const char *ctype = NULL;
+ static const char error_page[] =
+ "PMRESAPI error"; /* could also be an actual error page... */
+
+ (void) cls;
+ assert (resourcedir != NULL); /* facility is enabled at all */
+
+ assert (url[0] == '/');
+ rc = snprintf (filename, sizeof(filename), "%s%s", resourcedir, url);
+ if (rc < 0 || rc >= (int)sizeof(filename))
+ goto error_response;
+
+ /* Reject some obvious ways of escaping resourcedir. */
+ if (NULL != strstr (filename, "/../")) {
+ pmweb_notify (LOG_ERR, connection, "pmwebres suspicious url %s\n", url);
+ goto error_response;
+ }
+
+ fd = open (filename, O_RDONLY);
+ if (fd < 0) {
+ pmweb_notify (LOG_ERR, connection, "pmwebres open %s failed (%d)\n", filename, fd);
+ resp_code = MHD_HTTP_NOT_FOUND;
+ goto error_response;
+ }
+
+ rc = fstat (fd, &fds);
+ if (rc < 0) {
+ pmweb_notify (LOG_ERR, connection, "pmwebres stat %s failed (%d)\n", filename, rc);
+ close (fd);
+ goto error_response;
+ }
+
+ /* XXX: handle if-modified-since */
+
+ if (! S_ISREG (fds.st_mode)) {
+ pmweb_notify (LOG_ERR, connection, "pmwebres non-file %s attempted\n", filename);
+ close (fd);
+
+ /* XXX: list directory, or redirect to index.html instead? */
+ resp_code = MHD_HTTP_FORBIDDEN;
+ goto error_response;
+ }
+
+ if (verbosity > 2)
+ pmweb_notify (LOG_INFO, connection, "pmwebres serving file %s.\n", filename);
+
+ resp = MHD_create_response_from_fd_at_offset (fds.st_size, fd, 0); /* auto-closes fd */
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_callback failed\n");
+ goto error_response;
+ }
+
+ /* Guess at a suitable MIME content-type. */
+ ctype = guess_content_type (filename);
+ if (ctype)
+ (void) MHD_add_response_header (resp, "Content-Type", ctype);
+
+ /* And since we're generous to a fault, supply a timestamp field to
+ assist caching. */
+ ctype = create_rfc822_date (fds.st_mtime);
+ if (ctype)
+ (void) MHD_add_response_header (resp, "Last-Modified", ctype);
+
+ /* Add a 5-minute expiry. */
+ ctype = create_rfc822_date (time(0) + 300); /* XXX: configure */
+ if (ctype)
+ (void) MHD_add_response_header (resp, "Expires", ctype);
+
+ (void) MHD_add_response_header (resp, "Cache-Control", "public");
+
+ rc = MHD_queue_response (connection, resp_code, resp);
+ MHD_destroy_response (resp);
+ return rc;
+
+ error_response:
+ resp = MHD_create_response_from_buffer (strlen(error_page),
+ (char*)error_page,
+ MHD_RESPMEM_PERSISTENT);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_callback failed\n");
+ return MHD_NO;
+ }
+
+ (void) MHD_add_response_header (resp, "Content-Type", "text/plain");
+
+ rc = MHD_queue_response (connection, resp_code, resp);
+ MHD_destroy_response (resp);
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ return MHD_NO;
+ }
+
+ return rc;
+}
diff --git a/src/pmwebapi/pmwebapi.c b/src/pmwebapi/pmwebapi.c
new file mode 100644
index 0000000..5134215
--- /dev/null
+++ b/src/pmwebapi/pmwebapi.c
@@ -0,0 +1,1343 @@
+/*
+ * JSON web bridge for PMAPI.
+ *
+ * Copyright (c) 2011-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "pmwebapi.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <ctype.h>
+
+
+/* ------------------------------------------------------------------------ */
+
+struct webcontext {
+ /* __pmHashNode key is unique randomized web context key, [1,INT_MAX] */
+ /* XXX: optionally bind session to a particular IP address? */
+ char *userid; /* NULL, or strdup'd username */
+ char *password; /* NULL, or strdup'd password */
+ unsigned mypolltimeout;
+ time_t expires; /* poll timeout, 0 if never expires */
+ int context; /* PMAPI context handle, 0 if deleted/free */
+};
+
+/* if-threaded: pthread_mutex_t context_lock; */
+static __pmHashCtl contexts;
+
+
+
+struct web_gc_iteration {
+ time_t now;
+ time_t soonest; /* 0 => no context pending gc */
+};
+
+
+static __pmHashWalkState
+pmwebapi_gc_fn (const __pmHashNode *kv, void *cdata)
+{
+ const struct webcontext *value = kv->data;
+ struct web_gc_iteration *t = (struct web_gc_iteration *) cdata;
+
+ if (! value->expires)
+ return PM_HASH_WALK_NEXT;
+
+ /* Expired. */
+ if (value->expires < t->now)
+ {
+ int rc;
+ if (verbosity)
+ __pmNotifyErr (LOG_INFO, "context (web%d=pm%d) expired.\n", kv->key, value->context);
+ rc = pmDestroyContext (value->context);
+ if (rc)
+ __pmNotifyErr (LOG_ERR, "pmDestroyContext (%d) failed: %d\n",
+ value->context, rc);
+
+ free (value->userid);
+ free (value->password);
+
+ return PM_HASH_WALK_DELETE_NEXT;
+ }
+
+ /* Expiring soon? */
+ if (t->soonest == 0)
+ t->soonest = value->expires;
+ else if (t->soonest > value->expires)
+ t->soonest = value->expires;
+
+ return PM_HASH_WALK_NEXT;
+}
+
+
+/* Check whether any contexts have been unpolled so long that they
+ should be considered abandoned. If so, close 'em, free 'em, yak
+ 'em, smack 'em. Return the number of seconds to the next good time
+ to check for garbage. */
+unsigned pmwebapi_gc ()
+{
+ struct web_gc_iteration t;
+ (void) time (& t.now);
+ t.soonest = 0;
+
+ /* if-multithread: Lock contexts. */
+ __pmHashWalkCB (pmwebapi_gc_fn, & t, & contexts);
+ /* if-multithread: Unlock contexts. */
+
+ return t.soonest ? (unsigned)(t.soonest - t.now) : maxtimeout;
+}
+
+
+
+static __pmHashWalkState
+pmwebapi_deallocate_all_fn (const __pmHashNode *kv, void *cdata)
+{
+ struct webcontext *value = kv->data;
+ int rc;
+ (void) cdata;
+
+ if (verbosity)
+ __pmNotifyErr (LOG_INFO, "context (web%d=pm%d) deleted.\n", kv->key, value->context);
+ rc = pmDestroyContext (value->context);
+ if (rc)
+ __pmNotifyErr (LOG_ERR, "pmDestroyContext (%d) failed: %d\n",
+ value->context, rc);
+ free (value->userid);
+ free (value->password);
+ free (value);
+ return PM_HASH_WALK_DELETE_NEXT;
+}
+
+
+void pmwebapi_deallocate_all ()
+{
+ /* if-multithread: Lock contexts. */
+ __pmHashWalkCB (pmwebapi_deallocate_all_fn, NULL, & contexts);
+ /* if-multithread: Unlock contexts. */
+}
+
+
+/* Allocate an zeroed webcontext structure, and enroll it in the
+ hash table with the given context#. */
+static int webcontext_allocate (int webapi_ctx, struct webcontext** wc)
+{
+ if (__pmHashSearch (webapi_ctx, & contexts) != NULL)
+ return -EEXIST;
+
+ /* Allocate & clear our webapi context. */
+ assert (wc);
+ *wc = calloc(1, sizeof(struct webcontext));
+ if (! *wc)
+ return -ENOMEM;
+
+ int rc = __pmHashAdd(webapi_ctx, *wc, & contexts);
+ if (rc < 0) {
+ free (*wc);
+ return rc;
+ }
+
+ return 0;
+}
+
+
+int pmwebapi_bind_permanent (int webapi_ctx, int pcp_context)
+{
+ struct webcontext* c;
+ int rc = webcontext_allocate (webapi_ctx, &c);
+
+ if (rc < 0)
+ return rc;
+
+ assert (c);
+ assert (pcp_context >= 0);
+ c->context = pcp_context;
+ c->mypolltimeout = ~0;
+ c->expires = 0;
+
+ return 0;
+}
+
+
+/* Print a best-effort message as a plain-text http response; anything
+ to avoid a dreaded MHD_NO, which results in a 500 return code.
+ That in turn could be interpreted by a web client as an invitation
+ to try, try again. */
+static int pmwebapi_notify_error (struct MHD_Connection *connection, int rc)
+{
+ char error_message [1000];
+ char pmmsg [PM_MAXERRMSGLEN];
+ struct MHD_Response *resp;
+
+ (void) pmErrStr_r (rc, pmmsg, sizeof(pmmsg));
+ (void) snprintf (error_message, sizeof(error_message),
+ "PMWEBAPI error, code %d: %s", rc, pmmsg);
+ resp = MHD_create_response_from_buffer (strlen(error_message), error_message,
+ MHD_RESPMEM_MUST_COPY);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_buffer failed\n");
+ return MHD_NO;
+ }
+
+ (void) MHD_add_response_header (resp, "Content-Type", "text/plain");
+
+ rc = MHD_queue_response (connection, MHD_HTTP_BAD_REQUEST, resp);
+ MHD_destroy_response (resp);
+
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ return MHD_NO;
+ }
+
+ return MHD_YES;
+}
+
+
+static int pmwebapi_respond_new_context (struct MHD_Connection *connection)
+{
+ /* Create a context. */
+ const char *val;
+ int rc = 0;
+ int context = -EINVAL;
+ char http_response [30];
+ char context_description [512] = "<none>"; /* for logging */
+ unsigned polltimeout;
+ struct MHD_Response *resp;
+ int webapi_ctx;
+ int iterations = 0;
+ char *userid = NULL;
+ char *password = NULL;
+
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "hostspec");
+ if (val == NULL) /* backward compatibility alias */
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "hostname");
+ if (val) {
+ pmHostSpec *hostSpec;
+ int hostSpecCount;
+ __pmHashCtl hostAttrs;
+ char *hostAttrsError;
+
+ __pmHashInit (& hostAttrs);
+ rc = __pmParseHostAttrsSpec (val, & hostSpec, & hostSpecCount,
+ & hostAttrs, & hostAttrsError);
+ if (rc == 0) {
+ __pmHashNode *node;
+ node = __pmHashSearch (PCP_ATTR_USERNAME, & hostAttrs); /* XXX: PCP_ATTR_AUTHNAME? */
+ if (node) userid = strdup (node->data);
+ node = __pmHashSearch (PCP_ATTR_PASSWORD, & hostAttrs);
+ if (node) password = strdup (node->data);
+ __pmFreeHostAttrsSpec (hostSpec, hostSpecCount, & hostAttrs);
+ __pmHashClear (& hostAttrs);
+ } else {
+ /* Ignore the parse error at this stage; pmNewContext will give it to us. */
+ free (hostAttrsError);
+ }
+
+ context = pmNewContext (PM_CONTEXT_HOST, val); /* XXX: limit access */
+ snprintf (context_description, sizeof(context_description), "PM_CONTEXT_HOST %s", val);
+ } else {
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "archivefile");
+ if (val) {
+ char archive_fullpath[PATH_MAX];
+
+ snprintf(archive_fullpath, sizeof(archive_fullpath),
+ "%s%c%s", archivesdir, __pmPathSeparator(), val);
+
+ /* Block some basic ways of escaping archive_dir */
+ if (NULL != strstr (archive_fullpath, "/../")) {
+ pmweb_notify (LOG_ERR, connection, "pmwebapi suspicious archive path %s\n",
+ archive_fullpath);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ context = pmNewContext (PM_CONTEXT_ARCHIVE, archive_fullpath);
+ snprintf (context_description, sizeof(context_description), "PM_CONTEXT_ARCHIVE %s", val);
+ } else if (MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "local")) {
+ /* Note we need to use a dummy parameter to local=FOO,
+ since the MHD_lookup* API does not differentiate
+ between an absent argument vs. an argument given
+ without a parameter value. */
+ context = pmNewContext (PM_CONTEXT_LOCAL, NULL);
+ snprintf (context_description, sizeof(context_description), "PM_CONTEXT_LOCAL");
+ } else {
+ /* context remains -something */
+ }
+ }
+
+ if (context < 0) {
+ pmweb_notify (LOG_ERR, connection, "new context failed (%s)\n", pmErrStr (context));
+ rc = context;
+ goto out;
+ }
+
+ /* Process optional ?polltimeout=SECONDS field. If broken/missing, assume maxtimeout. */
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "polltimeout");
+ if (val) {
+ long pt;
+ char *endptr;
+ errno = 0;
+ pt = strtol(val, &endptr, 0);
+ if (errno != 0 || *endptr != '\0' || pt <= 0 || pt > (long)maxtimeout) {
+ polltimeout = maxtimeout;
+ }
+ else
+ polltimeout = (unsigned) pt;
+ } else {
+ polltimeout = maxtimeout;
+ }
+
+
+ struct webcontext* c = NULL;
+ /* Create a new context key for the webapi. We just use a random integer within
+ a reasonable range: 1..INT_MAX */
+ while (1) {
+ /* Preclude infinite looping here, for example due to a badly behaving
+ random(3) implementation. */
+ iterations ++;
+ if (iterations > 100)
+ {
+ pmweb_notify (LOG_ERR, connection, "webapi_ctx allocation failed\n");
+ pmDestroyContext (context);
+ rc = -EMFILE;
+ goto out;
+ }
+
+ webapi_ctx = random(); /* we hope RAND_MAX is large enough */
+ if (webapi_ctx <= 0)
+ continue;
+
+ rc = webcontext_allocate (webapi_ctx, & c);
+ if (rc == 0)
+ break;
+ /* This may already exist. We loop in case the key id already exists. */
+ }
+
+ assert (c);
+ c->context = context;
+ time (& c->expires);
+ c->mypolltimeout = polltimeout;
+ c->expires += c->mypolltimeout;
+ c->userid = userid; /* may be NULL; otherwise, will be freed at context release. */
+ c->password = password; /* ditto */
+
+ /* Errors beyond this point don't require instant cleanup; the
+ periodic context GC will do it all. */
+
+ if (verbosity) {
+ const char* context_hostname = pmGetContextHostName (context);
+ pmweb_notify (LOG_INFO, connection, "context %s%s%s%s (web%d=pm%d) created%s%s, expires in %us.\n",
+ context_description,
+ context_hostname ? " (" : "",
+ context_hostname ? context_hostname : "",
+ context_hostname ? ")" : "",
+ webapi_ctx, context,
+ userid ? ", user=" : "",
+ userid ? userid : "",
+ polltimeout);
+ }
+
+ rc = snprintf (http_response, sizeof(http_response), "{ \"context\": %d }", webapi_ctx);
+ assert (rc >= 0 && rc < (int)sizeof(http_response));
+ resp = MHD_create_response_from_buffer (strlen(http_response), http_response,
+ MHD_RESPMEM_MUST_COPY);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "MHD_create_response_from_buffer failed\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+ rc = MHD_add_response_header (resp, "Content-Type", "application/json");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_add_response_header (resp, "Access-Control-Allow-Origin", "*");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header ACAO failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_queue_response (connection, MHD_HTTP_OK, resp);
+ MHD_destroy_response (resp);
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ return MHD_YES;
+
+ out1:
+ MHD_destroy_response (resp);
+ out:
+ return pmwebapi_notify_error (connection, rc);
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* Buffer for building a MHD_Response incrementally. libmicrohttpd does not
+ provide such a facility natively. */
+struct mhdb {
+ size_t buf_size;
+ size_t buf_used;
+ char *buf; /* malloc/realloc()'d. If NULL, mhdbuf is a loser:
+ Attempt no landings there. */
+};
+
+
+/* Create a MHD response buffer of given initial size. Upon failure,
+ leave the buffer unallocated, which will block any mhdb_printfs,
+ and cause mhdb_fini_response to fail. */
+static void mhdb_init (struct mhdb *md, size_t size)
+{
+ md->buf_size = size;
+ md->buf_used = 0;
+ md->buf = malloc (md->buf_size); /* may be NULL => loser */
+}
+
+
+/* Add a given formatted string to the end of the MHD response buffer.
+ Extend/realloc the buffer if needed. If unable, free the buffer,
+ which will block any further mhdb_vsprintfs, and cause the
+ mhdb_fini_response to fail. */
+static void mhdb_printf(struct mhdb *md, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+static void mhdb_printf(struct mhdb *md, const char *fmt, ...)
+{
+ va_list vl;
+ int n;
+ int buf_remaining = md->buf_size - md->buf_used;
+
+ if (md->buf == NULL) return; /* reject loser */
+
+ va_start (vl, fmt);
+ n = vsnprintf (md->buf + md->buf_used, buf_remaining, fmt, vl);
+ va_end (vl);
+
+ if (n >= buf_remaining) { /* complex case: buffer overflow */
+ void *nbuf;
+
+ md->buf_size += n*128; /* pad it to discourage reoffense */
+ buf_remaining += n*128;
+ nbuf = realloc (md->buf, md->buf_size);
+
+ if (nbuf == NULL) { /* ENOMEM */
+ free (md->buf); /* realloc left it alone */
+ md->buf = NULL; /* tag loser */
+ } else { /* success */
+ md->buf = nbuf;
+ /* Try vsnprintf again into the new buffer. */
+ va_start (vl, fmt);
+ n = vsnprintf (md->buf + md->buf_used, buf_remaining, fmt, vl);
+ va_end (vl);
+ assert (n >= 0 && n < buf_remaining); /* should not fail */
+ md->buf_used += n;
+ }
+ } else if (n < 0) { /* simple case: other vsprintf error */
+ free (md->buf);
+ md->buf = NULL; /* tag loser */
+ } else { /* normal case: vsprintf success */
+ md->buf_used += n;
+ }
+
+}
+
+
+/* Print a string with JSON quoting. Replace non-ASCII characters
+ with \uFFFD "REPLACEMENT CHARACTER". */
+static void mhdb_print_qstring(struct mhdb *md, const char *value)
+{
+ const char *c;
+
+ mhdb_printf(md, "\"");
+ for (c = value; *c; c++) {
+ if (! isascii(*c))
+ mhdb_printf(md, "\\uFFFD");
+ else if (isalnum(*c))
+ mhdb_printf(md, "%c", *c);
+ else if (ispunct(*c) && !iscntrl(*c) && (*c != '\\' && *c != '\"'))
+ mhdb_printf(md, "%c", *c);
+ else if (*c == ' ')
+ mhdb_printf(md, "%c", *c);
+ else
+ mhdb_printf(md, "\\u00%02x", *c);
+ }
+ mhdb_printf(md, "\"");
+}
+
+
+/* A convenience function to print a vanilla-ascii key and an
+ unknown ancestry value as a JSON pair. Add given suffix,
+ which is likely to be a comma or a \n. */
+static void mhdb_print_key_value(struct mhdb *md, const char *key, const char *value,
+ const char *suffix)
+{
+ mhdb_printf(md, "\"%s\":", key);
+ mhdb_print_qstring(md, value);
+ mhdb_printf(md, "%s", suffix);
+}
+
+
+#if 0 /* unused */
+/* Ensure that any recent mhdb_printfs are canceled, and that the
+ final mhdb_fini_response will fail. */
+static void mhdb_unprintf(struct mhdb *md)
+{
+ if (md->buf) {
+ free (md->buf);
+ md->buf = NULL;
+ }
+}
+#endif
+
+
+/* Create a MHD_Response from the mhdb, now that we're done with it.
+ If the buffer overflowed earlier (was unable to be extended), or if
+ the MHD_create_response* function fails, return NULL. Ensure that
+ the buffer will be freed either by us here, or later by a
+ successful MHD_create_response* function. */
+static struct MHD_Response* mhdb_fini_response(struct mhdb *md)
+{
+ struct MHD_Response *r;
+
+ if (md->buf == NULL) return NULL; /* reject loser */
+ r = MHD_create_response_from_buffer (md->buf_used, md->buf, MHD_RESPMEM_MUST_FREE);
+ if (r == NULL) {
+ free (md->buf); /* we need to free it ourselves */
+ /* fall through */
+ }
+ return r;
+}
+
+
+
+
+/* ------------------------------------------------------------------------ */
+
+struct metric_list_traverse_closure {
+ struct MHD_Connection *connection;
+ struct webcontext *c;
+ struct mhdb mhdb;
+ unsigned num_metrics;
+};
+
+
+
+static void metric_list_traverse (const char* metric, void *closure)
+{
+ struct metric_list_traverse_closure *mltc = closure;
+ pmID metric_id;
+ pmDesc metric_desc;
+ int rc;
+ char *metric_text;
+ char *metrics[1] = { (char*) metric };
+
+ assert (mltc != NULL);
+
+ rc = pmLookupName (1, metrics, & metric_id);
+ if (rc != 1) {
+ /* Quietly skip this metric. */
+ return;
+ }
+ assert (metric_id != PM_ID_NULL);
+
+ rc = pmLookupDesc (metric_id, & metric_desc);
+ if (rc != 0) {
+ /* Quietly skip this metric. */
+ return;
+ }
+
+ if (mltc->num_metrics > 0)
+ mhdb_printf (& mltc->mhdb, ",\n");
+
+ mhdb_printf (& mltc->mhdb, "{ ");
+ mhdb_print_key_value (& mltc->mhdb, "name", metric, ",");
+ rc = pmLookupText (metric_id, PM_TEXT_ONELINE, & metric_text);
+ if (rc == 0) {
+ mhdb_print_key_value (& mltc->mhdb, "text-oneline", metric_text, ",");
+ free (metric_text);
+ }
+ rc = pmLookupText (metric_id, PM_TEXT_HELP, & metric_text);
+ if (rc == 0) {
+ mhdb_print_key_value (& mltc->mhdb, "text-help", metric_text, ",");
+ free (metric_text);
+ }
+ mhdb_printf (& mltc->mhdb, "\"pmid\":%lu, ", (unsigned long) metric_id);
+ if (metric_desc.indom != PM_INDOM_NULL)
+ mhdb_printf (& mltc->mhdb, "\"indom\":%lu, ", (unsigned long) metric_desc.indom);
+ mhdb_print_key_value (& mltc->mhdb, "sem",
+ (metric_desc.sem == PM_SEM_COUNTER ? "counter" :
+ metric_desc.sem == PM_SEM_INSTANT ? "instant" :
+ metric_desc.sem == PM_SEM_DISCRETE ? "discrete" :
+ "unknown"),
+ ",");
+ mhdb_print_key_value (& mltc->mhdb, "units",
+ pmUnitsStr (& metric_desc.units), ",");
+ mhdb_print_key_value (& mltc->mhdb, "type",
+ pmTypeStr (metric_desc.type), "");
+ mhdb_printf (& mltc->mhdb, "}");
+
+ mltc->num_metrics ++;
+}
+
+
+static int pmwebapi_respond_metric_list (struct MHD_Connection *connection,
+ struct webcontext *c)
+{
+ struct metric_list_traverse_closure mltc;
+ const char* val;
+ struct MHD_Response* resp;
+ int rc;
+
+ mltc.connection = connection;
+ mltc.c = c;
+ mltc.num_metrics = 0;
+
+ /* We need to construct a copy of the entire JSON metric metadata string,
+ in one long malloc()'d buffer. We size it generously to avoid having
+ to realloc the bad boy and cause copies. */
+ mhdb_init (& mltc.mhdb, 300000); /* 1000 pmns entries * 300 bytes each */
+ mhdb_printf(& mltc.mhdb, "{ \"metrics\":[\n");
+
+ val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "prefix");
+ if (val == NULL) val = "";
+ (void) pmTraversePMNS_r (val, & metric_list_traverse, & mltc); /* cannot fail */
+ /* XXX: also handle pmids=... */
+ /* XXX: also handle names=... */
+
+ mhdb_printf(&mltc.mhdb, "] }");
+ resp = mhdb_fini_response (& mltc.mhdb);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "mhdb_response failed\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+ rc = MHD_add_response_header (resp, "Content-Type", "application/json");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_add_response_header (resp, "Access-Control-Allow-Origin", "*");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header ACAO failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_queue_response (connection, MHD_HTTP_OK, resp);
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ MHD_destroy_response (resp);
+ return MHD_YES;
+
+ out1:
+ MHD_destroy_response (resp);
+ out:
+ return pmwebapi_notify_error (connection, rc);
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/* Print "value":<RENDERING>, possibly nested for PM_TYPE_EVENT.
+ Upon failure to decode, print less and return non-0. */
+
+static int pmwebapi_format_value (struct mhdb* output,
+ pmDesc* desc,
+ pmValueSet* pvs,
+ int vsidx)
+{
+ pmValue* value = & pvs->vlist[vsidx];
+ pmAtomValue a;
+ int rc;
+
+ /* unpack, as per pmevent.c:myeventdump */
+ if (desc->type == PM_TYPE_EVENT) {
+ pmResult **res;
+ int numres, i;
+
+ numres = pmUnpackEventRecords (pvs, vsidx, &res);
+ if (numres < 0)
+ return numres;
+
+ mhdb_printf (output, "\"events\":[");
+ for (i=0; i<numres; i++) {
+ int j;
+ if (i>0)
+ mhdb_printf (output, ",");
+ mhdb_printf (output, "{ \"timestamp\": { \"s\":%lu, \"us\":%lu } ",
+ res[i]->timestamp.tv_sec,
+ res[i]->timestamp.tv_usec);
+
+ mhdb_printf (output, ", \"fields\":[");
+ for (j=0; j<res[i]->numpmid; j++) {
+ pmValueSet *fieldvsp = res[i]->vset[j];
+ pmDesc fielddesc;
+ char *fieldname;
+
+ if (j>0)
+ mhdb_printf (output, ",");
+
+ mhdb_printf (output, "{");
+
+ /* recurse */
+ rc = pmLookupDesc (fieldvsp->pmid, &fielddesc);
+ if (rc == 0)
+ rc = pmwebapi_format_value (output, &fielddesc, fieldvsp, 0);
+
+ if (rc == 0) /* printer value: ... etc. ? */
+ mhdb_printf (output, ", ");
+ /* XXX: handle value: for event.flags / event.missed */
+
+ rc = pmNameID (fieldvsp->pmid, & fieldname);
+ if (rc == 0) {
+ mhdb_printf (output, "\"name\":\"%s\"", fieldname);
+ free (fieldname);
+ } else {
+ mhdb_printf (output, "\"name\":\"%s\"", pmIDStr (fieldvsp->pmid));
+ }
+
+ mhdb_printf (output, "}");
+ }
+ mhdb_printf (output, "]");
+
+ mhdb_printf (output, "}\n");
+ }
+ mhdb_printf (output, "]");
+ pmFreeEventResult(res);
+ return 0;
+ }
+
+ rc = pmExtractValue (pvs->valfmt, value, desc->type, &a, desc->type);
+ if (rc != 0)
+ return rc;
+
+ switch (desc->type) {
+ case PM_TYPE_32:
+ mhdb_printf (output, "\"value\":%i", a.l);
+ break;
+ case PM_TYPE_U32:
+ mhdb_printf (output, "\"value\":%u", a.ul);
+ break;
+ case PM_TYPE_64:
+ mhdb_printf (output, "\"value\":%" PRIi64, a.ll);
+ break;
+ case PM_TYPE_U64:
+ mhdb_printf (output, "\"value\":%" PRIu64, a.ull);
+ break;
+ case PM_TYPE_FLOAT:
+ mhdb_printf (output, "\"value\":%g", (double)a.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ mhdb_printf (output, "\"value\":%g", a.d);
+ break;
+ case PM_TYPE_STRING:
+ mhdb_print_key_value (output, "value", a.cp, "");
+ free (a.cp);
+ break;
+ case PM_TYPE_AGGREGATE:
+ case PM_TYPE_AGGREGATE_STATIC:
+ {
+ /* base64-encode binary data */
+ const char* p_bytes = & value->value.pval->vbuf[0]; /* from pmevent.c:mydump() */
+ unsigned p_size = value->value.pval->vlen - PM_VAL_HDR_SIZE;
+ unsigned i;
+ const char base64_encoding_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ if (value->value.pval->vlen < PM_VAL_HDR_SIZE) /* less than zero size? */
+ return -EINVAL;
+
+ mhdb_printf (output, "\"value\":\"");
+ for (i=0; i<p_size; /* */) {
+ unsigned char byte_0 = i < p_size ? p_bytes[i++] : 0;
+ unsigned char byte_1 = i < p_size ? p_bytes[i++] : 0;
+ unsigned char byte_2 = i < p_size ? p_bytes[i++] : 0;
+ unsigned int triple = (byte_0 << 16) | (byte_1 << 8) | byte_2;
+ mhdb_printf (output, "%c", base64_encoding_table[(triple >> 3*6) & 63]);
+ mhdb_printf (output, "%c", base64_encoding_table[(triple >> 2*6) & 63]);
+ mhdb_printf (output, "%c", base64_encoding_table[(triple >> 1*6) & 63]);
+ mhdb_printf (output, "%c", base64_encoding_table[(triple >> 0*6) & 63]);
+ }
+ switch (p_size % 3) {
+ case 0: /*mhdb_printf (output, "");*/ break;
+ case 1: mhdb_printf (output, "=="); break;
+ case 2: mhdb_printf (output, "="); break;
+ }
+ mhdb_printf (output, "\"");
+
+ if (desc->type != PM_TYPE_AGGREGATE_STATIC) /* XXX: correct? */
+ free (a.vbp);
+ break;
+ }
+ default:
+ /* ... just a complete unknown ... */
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+
+static int pmwebapi_respond_metric_fetch (struct MHD_Connection *connection,
+ struct webcontext *c)
+{
+ const char* val_pmids;
+ const char* val_names;
+ struct MHD_Response* resp;
+ int rc = 0;
+ int max_num_metrics;
+ int num_metrics;
+ int printed_metrics; /* exclude skipped ones */
+ pmID *metrics;
+ struct mhdb output;
+ pmResult *results;
+ int i;
+
+ (void) c;
+
+ val_pmids = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "pmids");
+ if (val_pmids == NULL) val_pmids = "";
+ val_names = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "names");
+ if (val_names == NULL) val_names = "";
+
+ /* Pessimistically overestimate maximum number of pmID elements
+ we'll need, to allocate the metrics[] array just once, and not have
+ to range-check. */
+ max_num_metrics = strlen(val_pmids)+strlen(val_names);
+ /* The real minimum is actually closer to strlen()/2, to account
+ for commas. */
+
+ num_metrics = 0;
+ metrics = calloc ((size_t) max_num_metrics, sizeof(pmID));
+ if (metrics == NULL) {
+ pmweb_notify (LOG_ERR, connection, "calloc pmIDs[%d] oom\n", max_num_metrics);
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* Loop over names= names in val_names, collect them in metrics[]. */
+ while (*val_names != '\0') {
+ char *name;
+ char *name_end = strchr (val_names, ',');
+ char *names[1];
+ pmID found_pmid;
+ int num;
+
+ /* Ignore plain "," XXX: elsewhere too? */
+ if (*val_names == ',') {
+ val_names ++;
+ continue;
+ }
+
+ /* Copy just this name piece. */
+ if (name_end) {
+ name = strndup (val_names, (name_end - val_names));
+ val_names = name_end + 1; /* skip past , */
+ } else {
+ name = strdup (val_names);
+ val_names += strlen (val_names); /* skip onto \0 */
+ }
+ names[0] = name;
+ num = pmLookupName (1, names, & found_pmid);
+ free(name);
+
+ if (num == 1) {
+ assert (num_metrics < max_num_metrics);
+ metrics[num_metrics++] = found_pmid;
+ }
+ }
+
+
+ /* Loop over pmids= numbers in val_pmids, append them to metrics[]. */
+ while (*val_pmids) {
+ char *numend;
+ unsigned long pmid = strtoul (val_pmids, & numend, 10); /* matches pmid printing above */
+ if (numend == val_pmids) break; /* invalid contents */
+
+ assert (num_metrics < max_num_metrics);
+ metrics[num_metrics++] = pmid;
+
+ if (*numend == '\0') break; /* end of string */
+ if (*numend == ',')
+ val_pmids = numend+1; /* advance to next string */
+ }
+
+ /* Time to fetch the metric values. */
+ /* num_metrics=0 ==> PM_ERR_TOOSMALL */
+ rc = pmFetch (num_metrics, metrics, & results);
+ free (metrics); /* don't need any more */
+ if (rc < 0) {
+ pmweb_notify (LOG_ERR, connection, "pmFetch failed\n");
+ goto out;
+ }
+ /* NB: we don't care about the possibility of PMCD_*_AGENT bits
+ being set, so rc > 0. */
+
+ /* We need to construct a copy of the entire JSON metric value
+ string, in one long malloc()'d buffer. We size it generously to
+ avoid having to realloc the bad boy and cause copies. */
+ mhdb_init (& output, num_metrics * 200); /* WAG: per-metric size */
+ mhdb_printf(& output, "{ \"timestamp\": { \"s\":%lu, \"us\":%lu },\n",
+ results->timestamp.tv_sec,
+ results->timestamp.tv_usec);
+
+ mhdb_printf(& output, "\"values\": [\n");
+
+ assert (results->numpmid == num_metrics);
+ printed_metrics = 0;
+ for (i=0; i<results->numpmid; i++) {
+ int j;
+ pmValueSet *pvs = results->vset[i];
+ char *metric_name;
+ pmDesc desc;
+
+ if (pvs->numval <= 0) continue; /* error code; skip metric */
+
+ rc = pmLookupDesc (pvs->pmid, &desc); /* need to find desc.type only */
+ if (rc < 0) continue; /* quietly skip it */
+
+ if (printed_metrics >= 1)
+ mhdb_printf (& output, ",\n");
+ mhdb_printf (& output, "{ ");
+
+ mhdb_printf (& output, "\"pmid\":%lu, ", (unsigned long) pvs->pmid);
+ rc = pmNameID (pvs->pmid, &metric_name);
+ if (rc == 0) {
+ mhdb_print_key_value (& output, "name", metric_name, ",");
+ free (metric_name);
+ }
+ mhdb_printf (& output, "\"instances\": [\n");
+ for (j=0; j<pvs->numval; j++) {
+ pmValue* val = & pvs->vlist[j];
+ int printed_value;
+
+ mhdb_printf (& output, "{");
+
+ printed_value = ! pmwebapi_format_value (& output, & desc, pvs, j);
+
+ if (desc.indom != PM_INDOM_NULL)
+ mhdb_printf (& output, "%s \"instance\":%d",
+ printed_value ? ", " : "", /* comma separation */
+ val->inst);
+
+ mhdb_printf (& output, "}");
+ if (j+1 < pvs->numval)
+ mhdb_printf (& output, ","); /* comma separation */
+ }
+ mhdb_printf(& output, "] }"); /* iteration over instances */
+ printed_metrics ++; /* comma separation at beginning of loop */
+ }
+ mhdb_printf(& output, "] }"); /* iteration over metrics */
+
+ pmFreeResult (results); /* not needed any more */
+
+ resp = mhdb_fini_response (& output);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "mhdb_response failed\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+ rc = MHD_add_response_header (resp, "Content-Type", "application/json");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_add_response_header (resp, "Access-Control-Allow-Origin", "*");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header ACAO failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_queue_response (connection, MHD_HTTP_OK, resp);
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ MHD_destroy_response (resp);
+ return MHD_YES;
+
+ out1:
+ MHD_destroy_response (resp);
+ out:
+ return pmwebapi_notify_error (connection, rc);
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+
+static int pmwebapi_respond_instance_list (struct MHD_Connection *connection,
+ struct webcontext *c)
+{
+ const char* val_indom;
+ const char* val_name;
+ const char* val_instance;
+ const char* val_iname;
+ struct MHD_Response* resp;
+ int rc = 0;
+ int max_num_instances;
+ int num_instances;
+ int printed_instances;
+ int *instances;
+ struct mhdb output;
+ pmID metric_id;
+ pmDesc metric_desc;
+ pmInDom inDom;
+ int i;
+
+ (void) c;
+ val_indom = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "indom");
+ if (val_indom == NULL) val_indom = "";
+ val_name = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "name");
+ if (val_name == NULL) val_name = "";
+ val_instance = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "instance");
+ if (val_instance == NULL) val_instance = "";
+ val_iname = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "iname");
+ if (val_iname == NULL) val_iname = "";
+
+ /* Obtain the instance domain. */
+ if (0 == strcmp(val_indom, "")) {
+ rc = pmLookupName (1, (char **)& val_name, & metric_id);
+ if (rc != 1) {
+ pmweb_notify (LOG_ERR, connection, "failed to lookup metric '%s'\n", val_name);
+ goto out;
+ }
+ assert (metric_id != PM_ID_NULL);
+
+ rc = pmLookupDesc (metric_id, & metric_desc);
+ if (rc != 0) {
+ pmweb_notify (LOG_ERR, connection, "failed to lookup metric '%s'\n", val_name);
+ goto out;
+ }
+
+ inDom = metric_desc.indom;
+ } else {
+ char *numend;
+ inDom = strtoul (val_indom, & numend, 10);
+ if (numend == val_indom) {
+ pmweb_notify (LOG_ERR, connection, "failed to parse indom '%s'\n", val_indom);
+ rc = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* Pessimistically overestimate maximum number of instance IDs needed. */
+ max_num_instances = strlen(val_indom) + strlen(val_name);
+ num_instances = 0;
+ instances = calloc ((size_t) max_num_instances, sizeof(int));
+ if (instances == NULL) {
+ pmweb_notify (LOG_ERR, connection, "calloc instances[%d] oom\n", max_num_instances);
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* In the case where neither val_instance nor val_iname are
+ specified, pmGetInDom will allocate different arrays on our
+ behalf, so we don't have to worry about accounting for how many
+ instances are returned in that case. */
+
+ /* Loop over instance= numbers in val_instance, collect them in instances[]. */
+ while (*val_instance != '\0') {
+ char *numend;
+ int iid = (int) strtoul (val_instance, & numend, 10);
+ if (numend == val_instance) break; /* invalid contents */
+ assert (num_instances < max_num_instances);
+ instances[num_instances++] = iid;
+
+ if (*numend == '\0') break; /* end of string */
+ if (*numend == ',')
+ val_instance = numend+1; /* advance to next string */
+ }
+
+ /* Loop over iname= names in val_iname, collect them in instances[]. */
+ while (*val_iname != '\0') {
+ char *iname;
+ char *iname_end = strchr (val_iname, ',');
+ int iid;
+
+ /* Ignore plain "," XXX: elsewhere too? */
+ if (*val_iname == ',') {
+ val_iname ++;
+ continue;
+ }
+
+ if (iname_end) {
+ iname = strndup (val_iname, (iname_end - val_iname));
+ val_iname = iname_end + 1; /* skip past , */
+ } else {
+ iname = strdup (val_iname);
+ val_iname += strlen (val_iname); /* skip onto \0 */
+ }
+
+ iid = pmLookupInDom(inDom, iname);
+
+ if (iid > 0) {
+ assert (num_instances < max_num_instances);
+ instances[num_instances++] = iid;
+ }
+ }
+
+ /* Time to fetch the instance info. */
+ int *instlist;
+ char **namelist = NULL;
+ if (num_instances == 0) {
+ free (instances);
+
+ num_instances = pmGetInDom(inDom, & instlist, & namelist);
+ if (num_instances < 1) {
+ pmweb_notify (LOG_ERR, connection, "pmGetInDom failed\n");
+ rc = num_instances;
+ goto out;
+ }
+ } else {
+ instlist = instances;
+ }
+
+ /* Build the response string all in one giant buffer: */
+ mhdb_init (& output, num_instances * 200);
+ mhdb_printf (& output, "{ \"indom\": %lu,\n", (unsigned long) inDom);
+ mhdb_printf (& output, "\"instances\": [\n");
+
+ printed_instances = 0;
+ for (i=0; i<num_instances; i++) {
+ char *instance_name;
+
+ if (namelist != NULL) {
+ instance_name = namelist[i];
+ } else {
+ rc = pmNameInDom (inDom, instlist[i], & instance_name);
+ if (rc != 0) continue; /* skip this instance quietly */
+ }
+
+ if (printed_instances >= 1)
+ mhdb_printf (& output, ",\n");
+ mhdb_printf (& output, "{ \"instance\":%d,\n", instlist[i]);
+ mhdb_print_key_value (& output, "name", instance_name, "\n");
+ mhdb_printf (& output, "}");
+
+ if (namelist == NULL) free (instance_name);
+
+ printed_instances ++; /* comma separation at beginning of loop */
+ }
+ mhdb_printf(& output, "] }"); /* iteration over instances */
+
+ /* Free no-longer-needed things: */
+ free (instlist);
+ if (namelist != NULL) free (namelist);
+
+ resp = mhdb_fini_response (& output);
+ if (resp == NULL) {
+ pmweb_notify (LOG_ERR, connection, "mhdb_response failed\n");
+ rc = -ENOMEM;
+ goto out;
+ }
+ rc = MHD_add_response_header (resp, "Content-Type", "application/json");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_add_response_header (resp, "Access-Control-Allow-Origin", "*");
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_add_response_header ACAO failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ rc = MHD_queue_response (connection, MHD_HTTP_OK, resp);
+ if (rc != MHD_YES) {
+ pmweb_notify (LOG_ERR, connection, "MHD_queue_response failed\n");
+ rc = -ENOMEM;
+ goto out1;
+ }
+ MHD_destroy_response (resp);
+ return MHD_YES;
+
+ out1:
+ MHD_destroy_response (resp);
+ out:
+ return pmwebapi_notify_error (connection, rc);
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+
+int pmwebapi_respond (void *cls, struct MHD_Connection *connection,
+ const char* url,
+ const char* method, const char* upload_data, size_t *upload_data_size)
+{
+ /* We emit CORS header for all successful json replies, namely:
+ Access-Control-Access-Origin: *
+ https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS */
+
+ /* NB: url is already edited to remove the /pmapi/ prefix. */
+ long webapi_ctx;
+ __pmHashNode *chn;
+ struct webcontext *c;
+ char *context_command;
+ int rc = 0;
+
+ (void) cls;
+ (void) upload_data;
+ (void) upload_data_size;
+
+ /* Decode the calls to the web API. */
+
+ /* -------------------------------------------------------------------- */
+ /* context creation */
+ /* if-multithreaded: write-lock contexts */
+ if (0 == strcmp (url, "context") &&
+ new_contexts_p && /* permitted */
+ (0 == strcmp (method, "POST") || 0 == strcmp (method, "GET")))
+ return pmwebapi_respond_new_context (connection);
+
+ /* -------------------------------------------------------------------- */
+ /* All other calls use $CTX/command, so we parse $CTX
+ generally and map it to the webcontext* */
+ if (! (0 == strcmp (method, "POST") || 0 == strcmp (method, "GET"))) {
+ pmweb_notify (LOG_WARNING, connection, "unknown method %s\n", method);
+ rc = -EINVAL;
+ goto out;
+ }
+ errno = 0;
+ webapi_ctx = strtol (url, & context_command, 10); /* matches %d above */
+ if (errno != 0
+ || webapi_ctx <= 0 /* range check, plus string-nonemptyness check */
+ || webapi_ctx > INT_MAX /* matches random() loop above */
+ || *context_command != '/') { /* parsed up to the next slash */
+ pmweb_notify (LOG_WARNING, connection, "unrecognized %s url %s \n", method, url);
+ rc = -EINVAL;
+ goto out;
+ }
+ context_command ++; /* skip the / */
+
+ /* if-multithreaded: read-lock contexts */
+ chn = __pmHashSearch ((int)webapi_ctx, & contexts);
+ if (chn == NULL) {
+ pmweb_notify (LOG_WARNING, connection, "unknown web context #%ld\n", webapi_ctx);
+ rc = PM_ERR_NOCONTEXT;
+ goto out;
+ }
+ c = (struct webcontext *) chn->data;
+ assert (c != NULL);
+
+ /* Process HTTP Basic userid/password, if supplied. Both returned strings
+ need to be free(3)'d later. */
+ if (c->userid != NULL) { /* Did this context requires userid/password auth? */
+ char *userid = NULL;
+ char *password = NULL;
+ userid = MHD_basic_auth_get_username_password (connection, &password);
+
+ /* 401 */
+ if (userid == NULL || password == NULL) {
+ static char auth_req_msg[] = "authentication required";
+ struct MHD_Response *resp;
+ char auth_realm[40];
+
+ free (userid);
+ free (password);
+ resp = MHD_create_response_from_buffer (strlen(auth_req_msg),
+ auth_req_msg,
+ MHD_RESPMEM_PERSISTENT);
+ if (! resp) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* We need the user to resubmit this with http
+ authentication info, with a custom HTTP authentication
+ realm for this context. */
+ snprintf (auth_realm, sizeof(auth_realm),
+ "%s/%ld", uriprefix, webapi_ctx);
+ rc = MHD_queue_basic_auth_fail_response (connection, auth_realm, resp);
+ MHD_destroy_response (resp);
+ if (rc != MHD_YES) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ return MHD_YES;
+ }
+ /* 403 */
+ if (strcmp (userid, c->userid) ||
+ (c->password && strcmp (password, c->password))) {
+ static char auth_failed_msg[] = "authentication failed";
+ struct MHD_Response *resp;
+
+ free (userid);
+ free (password);
+ resp = MHD_create_response_from_buffer (strlen(auth_failed_msg),
+ auth_failed_msg,
+ MHD_RESPMEM_PERSISTENT);
+ if (! resp) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ /* We need the user to resubmit this with http authentication info. */
+ rc = MHD_queue_response (connection, MHD_HTTP_FORBIDDEN, resp);
+ MHD_destroy_response (resp);
+ if (rc != MHD_YES) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ return MHD_YES;
+ }
+ /* FALLTHROUGH: authentication required & succeeded. */
+ free (userid);
+ free (password);
+ }
+
+ /* Update last-use of this connection. */
+ if (c->expires != 0) {
+ time (& c->expires);
+ c->expires += c->mypolltimeout;
+ }
+
+ /* Switch to this context for subsequent operations. */
+ /* if-multithreaded: watch out. */
+ rc = pmUseContext (c->context);
+ if (rc)
+ goto out;
+
+ /* -------------------------------------------------------------------- */
+ /* metric enumeration: /context/$ID/_metric */
+ if (0 == strcmp (context_command, "_metric") &&
+ (0 == strcmp (method, "POST") || 0 == strcmp (method, "GET")))
+ return pmwebapi_respond_metric_list (connection, c);
+
+ /* -------------------------------------------------------------------- */
+ /* metric instance metadata: /context/$ID/_indom */
+ if (0 == strcmp (context_command, "_indom") &&
+ (0 == strcmp (method, "POST") || 0 == strcmp (method, "GET")))
+ return pmwebapi_respond_instance_list (connection, c);
+
+ /* -------------------------------------------------------------------- */
+ /* metric fetch: /context/$ID/_fetch */
+ if (0 == strcmp (context_command, "_fetch") &&
+ (0 == strcmp (method, "POST") || 0 == strcmp (method, "GET")))
+ return pmwebapi_respond_metric_fetch (connection, c);
+
+ pmweb_notify (LOG_WARNING, connection, "unrecognized %s context command %s \n", method, context_command);
+
+ out:
+ return pmwebapi_notify_error (connection, rc);
+}
diff --git a/src/pmwebapi/pmwebapi.h b/src/pmwebapi/pmwebapi.h
new file mode 100644
index 0000000..ff2e71f
--- /dev/null
+++ b/src/pmwebapi/pmwebapi.h
@@ -0,0 +1,52 @@
+/*
+ * JSON web bridge for PMAPI.
+ *
+ * Copyright (c) 2011-2013 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include <assert.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <stdarg.h>
+#include <microhttpd.h>
+
+/* ------------------------------------------------------------------------ */
+
+extern const char uriprefix[];
+extern char *archivesdir; /* set by -A option */
+extern char *resourcedir; /* set by -R option */
+extern unsigned verbosity; /* set by -v option */
+extern unsigned maxtimeout; /* set by -t option */
+extern unsigned new_contexts_p; /* set by -N option */
+extern unsigned exit_p; /* counted by SIG* handler */
+
+/* ------------------------------------------------------------------------ */
+
+extern int pmwebapi_bind_permanent (int webapi_ctx, int pcp_context);
+extern int pmwebapi_respond (void *cls, struct MHD_Connection *connection,
+ const char* url, const char* method,
+ const char* upload_data, size_t *upload_data_size);
+extern unsigned pmwebapi_gc (void);
+extern void pmwebapi_deallocate_all (void);
+extern int pmwebres_respond (void *cls, struct MHD_Connection *connection,
+ const char* url);
+
+extern void pmweb_notify (int, struct MHD_Connection*, const char *, ...) __PM_PRINTFLIKE(3,4);
+
+extern void pmweb_start_daemon (int, char **);
diff --git a/src/pmwebapi/pmwebd.options b/src/pmwebapi/pmwebd.options
new file mode 100644
index 0000000..27566fb
--- /dev/null
+++ b/src/pmwebapi/pmwebd.options
@@ -0,0 +1,32 @@
+# command-line options and environment variables for pmwebd
+# uncomment/edit lines as required
+# note: environment variables are *not* expanded - use full path
+
+# expose resources for non PMAPI requests, e.g.
+# -R $(PCP_VAR_DIR)/pmwebd/resources
+
+# expose archives from local pmloggers, e.g.
+# -A $(PCP_LOG_DIR)/pmlogger
+# default is pmwebd's private log directory: $(PCP_LOG_DIR)/pmwebd
+
+# run in the foreground
+# -f
+
+# make log go someplace else
+# -l /some/place/else
+
+# assume identity of some user other than "pcp"
+# -U nobody
+
+# emergency messages before logfile created
+# -x /tmp/desperate.log
+
+# port for incoming connections from PCP monitoring clients
+# -p 44323
+
+# setting of environment variables for pmwebd
+
+# timeouts for interactions with pmcd on behalf of clients
+# PMCD_CONNECT_TIMEOUT=10
+# PMCD_RECONNECT_TIMEOUT=10,20,30
+# PMCD_REQUEST_TIMEOUT=10
diff --git a/src/pmwebapi/pmwebd.service.in b/src/pmwebapi/pmwebd.service.in
new file mode 100644
index 0000000..d1b0ba5
--- /dev/null
+++ b/src/pmwebapi/pmwebd.service.in
@@ -0,0 +1,14 @@
+[Unit]
+Description=Performance Metrics Web Daemon
+Documentation=man:pmwebd(8)
+Wants=avahi-daemon.service
+After=network.target avahi-daemon.service
+
+[Service]
+Type=oneshot
+ExecStart=@path@/pmwebd start
+ExecStop=@path@/pmwebd stop
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/pmwebapi/rc_pmwebd b/src/pmwebapi/rc_pmwebd
new file mode 100644
index 0000000..e1a0e4b
--- /dev/null
+++ b/src/pmwebapi/rc_pmwebd
@@ -0,0 +1,294 @@
+#! /bin/sh
+#
+# Copyright (c) 2013 Red Hat.
+# Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# Start or Stop the Performance Co-Pilot (PCP) web daemon for pmcd
+#
+# The following is for chkconfig on RedHat based systems
+# chkconfig: 2345 95 05
+# description: pmwebd is the web frontend for the Performance Co-Pilot (PCP)
+#
+# The following is for insserv(1) based systems,
+# e.g. SuSE, where chkconfig is a perl script.
+### BEGIN INIT INFO
+# Provides: pmwebd
+# Required-Start: $remote_fs
+# Should-Start: $local_fs $network $syslog $time $pmcd
+# Required-Stop: $remote_fs
+# Should-Stop: $local_fs $network $syslog $pmcd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Control pmwebd (the web frontend for PCP)
+# Description: Configure and control pmwebd (the web frontend daemon for the Performance Co-Pilot)
+### END INIT INFO
+
+. $PCP_DIR/etc/pcp.env
+. $PCP_SHARE_DIR/lib/rc-proc.sh
+
+PMWEBD=$PCP_BINADM_DIR/pmwebd
+PMWEBDOPTS=$PCP_PMWEBDOPTIONS_PATH
+RUNDIR=$PCP_LOG_DIR/pmwebd
+pmprog=$PCP_RC_DIR/pmwebd
+prog=$PCP_RC_DIR/`basename $0`
+
+tmp=`mktemp -d /var/tmp/pcp.XXXXXXXXX` || exit 1
+status=1
+trap "rm -rf $tmp; exit \$status" 0 1 2 3 15
+
+if [ $pmprog = $prog ]
+then
+ VERBOSE_CTL=on
+else
+ VERBOSE_CTL=off
+fi
+
+case "$PCP_PLATFORM"
+in
+ mingw)
+ # nothing we can usefully do here, skip the test
+ #
+ ;;
+
+ *)
+ # standard Unix/Linux style test
+ #
+ ID=id
+ test -f /usr/xpg4/bin/id && ID=/usr/xpg4/bin/id
+
+ IAM=`$ID -u 2>/dev/null`
+ if [ -z "$IAM" ]
+ then
+ # do it the hardway
+ #
+ IAM=`$ID | sed -e 's/.*uid=//' -e 's/(.*//'`
+ fi
+ ;;
+esac
+
+_shutdown()
+{
+ # Is pmwebd running?
+ #
+ _get_pids_by_name pmwebd >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ [ "$1" = verbose ] && echo "$pmprog: pmwebd not running"
+ return 0
+ fi
+
+ # Send pmwebd a SIGTERM, which is noted as a pending shutdown.
+ # When finished the currently active request, pmwebd will close any
+ # connections and then exit.
+ # Wait for pmwebd to terminate.
+ #
+ pmsignal -a -s TERM pmwebd > /dev/null 2>&1
+ $ECHO $PCP_ECHO_N "Waiting for pmwebd to terminate ...""$PCP_ECHO_C"
+ gone=0
+ for i in 1 2 3 4 5 6
+ do
+ sleep 3
+ _get_pids_by_name pmwebd >$tmp/tmp
+ if [ ! -s $tmp/tmp ]
+ then
+ gone=1
+ break
+ fi
+
+ # If pmwebd doesn't go in 15 seconds, SIGKILL and sleep 1 more time
+ # to allow any clients reading from pmwebd sockets to fail so that
+ # socket doesn't end up in TIME_WAIT or somesuch.
+ #
+ if [ $i = 5 ]
+ then
+ $ECHO
+ echo "Process ..."
+ $PCP_PS_PROG $PCP_PS_ALL_FLAGS >$tmp/ps
+ sed 1q $tmp/ps
+ for pid in `cat $tmp/tmp`
+ do
+ $PCP_AWK_PROG <$tmp/ps "\$2 == $pid { print }"
+ done
+ echo "$prog: Warning: Forcing pmwebd to terminate!"
+ pmsignal -a -s KILL pmwebd > /dev/null 2>&1
+ else
+ $ECHO $PCP_ECHO_N ".""$PCP_ECHO_C"
+ fi
+ done
+ if [ $gone != 1 ] # It just WON'T DIE, give up.
+ then
+ echo "Process ..."
+ cat $tmp/tmp
+ echo "$prog: Warning: pmwebd won't die!"
+ exit
+ fi
+ $RC_STATUS -v
+ $PCP_BINADM_DIR/pmpost "stop pmwebd from $pmprog"
+}
+
+_usage()
+{
+ echo "Usage: $pmprog [-v] {start|restart|condrestart|stop|status|reload|force-reload}"
+}
+
+while getopts v c
+do
+ case $c
+ in
+ v) # force verbose
+ VERBOSE_CTL=on
+ ;;
+
+ *)
+ _usage
+ exit 1
+ ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+if [ $VERBOSE_CTL = on ]
+then # For a verbose startup and shutdown
+ ECHO=$PCP_ECHO_PROG
+else # For a quiet startup and shutdown
+ ECHO=:
+fi
+
+if [ "$IAM" != 0 -a "$1" != "status" ]
+then
+ if [ -n "$PCP_DIR" ]
+ then
+ : running in a non-default installation, do not need to be root
+ else
+ echo "$prog:"'
+Error: You must be root (uid 0) to start or stop the PCP pmwebd daemon.'
+ exit
+ fi
+fi
+
+# First reset status of this service
+$RC_RESET
+
+# Return values acc. to LSB for all commands but status:
+# 0 - success
+# 1 - misc error
+# 2 - invalid or excess args
+# 3 - unimplemented feature (e.g. reload)
+# 4 - insufficient privilege
+# 5 - program not installed
+# 6 - program not configured
+#
+# Note that starting an already running service, stopping
+# or restarting a not-running service as well as the restart
+# with force-reload (in case signalling is not supported) are
+# considered a success.
+case "$1" in
+
+ 'start'|'restart'|'condrestart'|'reload'|'force-reload')
+ if [ "$1" = "condrestart" ] && ! is_chkconfig_on pmwebd
+ then
+ status=0
+ exit
+ fi
+
+ _shutdown quietly
+
+ # pmwebd messages should go to stderr, not the GUI notifiers
+ #
+ unset PCP_STDERR
+
+ if [ -x $PMWEBD ]
+ then
+ if [ ! -f $PCP_PMWEBDCONF_PATH ]
+ then
+ echo "$prog:"'
+Error: pmwebd control file "$PCP_PMWEBDCONF_PATH" is missing, cannot start pmwebd.'
+ exit
+ fi
+ [ ! -d $RUNDIR ] && mkdir -p $RUNDIR
+ cd $RUNDIR
+
+ # salvage the previous versions of any pmwebd
+ #
+ if [ -f pmwebd.log ]
+ then
+ rm -f pmwebd.log.prev
+ mv pmwebd.log pmwebd.log.prev
+ fi
+
+ $ECHO $PCP_ECHO_N "Starting pmwebd ..." "$PCP_ECHO_C"
+
+ # options file processing ...
+ # only consider lines which start with a hyphen
+ # get rid of the -f option
+ # ensure multiple lines concat onto 1 line
+ OPTS=`sed <$PMWEBDOPTS 2>/dev/null \
+ -e '/^[^-]/d' \
+ -e 's/^/ /' \
+ -e 's/$/ /' \
+ -e 's/ -f / /g' \
+ -e 's/^ //' \
+ -e 's/ $//' \
+ | tr '\012' ' ' `
+
+ # environment stuff
+ #
+ eval `sed -e 's/"/\\"/g' $PMWEBDOPTS \
+ | awk -F= '
+BEGIN { exports="" }
+/^[A-Z]/ && NF == 2 { exports=exports" "$1
+ printf "%s=${%s:-\"%s\"}\n", $1, $1, $2
+ }
+END { if (exports != "") print "export", exports }'`
+
+ $PMWEBD $OPTS
+ $RC_STATUS -v
+
+ $PCP_BINADM_DIR/pmpost "start pmwebd from $pmprog"
+
+ fi
+ status=0
+ ;;
+
+ 'stop')
+ _shutdown
+ status=0
+ ;;
+
+ 'status')
+ # NOTE: $RC_CHECKPROC returns LSB compliant status values.
+ $ECHO $PCP_ECHO_N "Checking for pmwebd:" "$PCP_ECHO_C"
+ if [ -r /etc/rc.status ]
+ then
+ # SuSE
+ $RC_CHECKPROC $PMWEBD
+ $RC_STATUS -v
+ status=$?
+ else
+ # not SuSE
+ $RC_CHECKPROC $PMWEBD
+ status=$?
+ if [ $status -eq 0 ]
+ then
+ $ECHO running
+ else
+ $ECHO stopped
+ fi
+ fi
+ ;;
+
+ *)
+ _usage
+ ;;
+esac
+
diff --git a/src/pmwebapi/util.c b/src/pmwebapi/util.c
new file mode 100644
index 0000000..7c039ad
--- /dev/null
+++ b/src/pmwebapi/util.c
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ * Copyright (c) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmwebapi.h"
+
+#ifdef IS_MINGW
+
+void
+pmweb_start_daemon(int argc, char **argv)
+{
+ PROCESS_INFORMATION piProcInfo;
+ STARTUPINFO siStartInfo;
+ LPTSTR cmdline = NULL;
+ int i, sz = 3; /* -f\0 */
+
+ for (i = 0; i < argc; i++)
+ sz += strlen(argv[i]) + 1;
+ if ((cmdline = malloc(sz)) == NULL) {
+ __pmNotifyErr(LOG_ERR, "pmweb_start_daemon: no memory");
+ exit(1);
+ }
+ for (sz = i = 0; i < argc; i++)
+ sz += sprintf(cmdline, "%s ", argv[i]);
+ sprintf(cmdline + sz, "-f");
+
+ ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+ siStartInfo.cb = sizeof(STARTUPINFO);
+
+ if (0 == CreateProcess(
+ NULL, cmdline,
+ NULL, NULL, /* process and thread attributes */
+ FALSE, /* inherit handles */
+ CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | DETACHED_PROCESS,
+ NULL, /* environment (from parent) */
+ NULL, /* current directory */
+ &siStartInfo, /* STARTUPINFO pointer */
+ &piProcInfo)) { /* receives PROCESS_INFORMATION */
+ __pmNotifyErr(LOG_ERR, "pmweb_start_daemon: CreateProcess");
+ /* but keep going */
+ }
+ else {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+}
+
+#else
+
+/* Based on Stevens (Unix Network Programming, p.83) */
+void
+pmweb_start_daemon(int argc, char **argv)
+{
+ int childpid;
+
+ (void)argc; (void)argv;
+
+#if defined(HAVE_TERMIO_SIGNALS)
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+#endif
+
+ if ((childpid = fork()) < 0)
+ __pmNotifyErr(LOG_ERR, "pmweb_start_daemon: fork");
+ /* but keep going */
+ else if (childpid > 0) {
+ /* parent, let her exit, but avoid ugly "Log finished" messages */
+ fclose(stderr);
+ exit(0);
+ }
+
+ /* not a process group leader, lose controlling tty */
+ if (setsid() == -1)
+ __pmNotifyErr(LOG_WARNING, "pmweb_start_daemon: setsid");
+ /* but keep going */
+
+ close(0);
+ /* don't close other fd's -- we know that only good ones are open! */
+
+ /* don't chdir("/") -- we still need to open pmcd.log */
+}
+#endif
diff --git a/src/procmemstat/GNUmakefile b/src/procmemstat/GNUmakefile
new file mode 100644
index 0000000..e4911c7
--- /dev/null
+++ b/src/procmemstat/GNUmakefile
@@ -0,0 +1,50 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = procmemstat.c
+TARGETS =
+LLDLIBS = $(PCPLIB)
+LDIRT = pmnsmap.h mylog.* runme.sh $(TARGETS) pmnsmap.i GNUmakefile.install.xxx
+LSRCFILES = README GNUmakefile.install pmnsmap.spec
+
+DEMODIR=$(PCP_DEMOS_DIR)/procmemstat
+
+default: $(TARGETS) GNUmakefile.install.xxx
+
+include $(BUILDRULES)
+
+install: $(TARGETS)
+ $(INSTALL) -m 755 -d $(DEMODIR)
+ $(INSTALL) -m 644 GNUmakefile.install.xxx $(DEMODIR)/Makefile
+ $(INSTALL) -m 644 procmemstat.c README pmnsmap.spec $(DEMODIR)
+
+procmemstat.o : pmnsmap.h
+
+pmnsmap.h: pmnsmap.spec
+ PCP_CONF=$(TOPDIR)/src/include/pcp.conf; export PCP_CONF; \
+ sed -e "s;^\. \$PCP_DIR/etc/pcp.env;. $(TOPDIR)/src/include/pcp.env;" \
+ ../pmgenmap/pmgenmap.sh >runme.sh; \
+ $(RUN_IN_BUILD_ENV) $(TOPDIR)/src/pmcpp/pmcpp -D$(TARGET_OS) pmnsmap.spec \
+ | sed -e '/^#/d' -e '/^$$/d' >pmnsmap.i
+ sh ./runme.sh pmnsmap.i >pmnsmap.h
+
+GNUmakefile.install.xxx: GNUmakefile.install
+ sed -e "s;<CPP>;$(PCP_BINADM_DIR)/pmcpp -D$(TARGET_OS);" <GNUmakefile.install >GNUmakefile.install.xxx
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/procmemstat/GNUmakefile.install b/src/procmemstat/GNUmakefile.install
new file mode 100644
index 0000000..9b2a459
--- /dev/null
+++ b/src/procmemstat/GNUmakefile.install
@@ -0,0 +1,39 @@
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+CFILES = procmemstat.c
+CFLAGS = -I/usr/include/pcp -DPCP_DEBUG=1
+TARGETS = procmemstat
+LDOPTS =
+LDLIBS = -lpcp
+
+default: $(TARGETS)
+
+install:
+
+clobber:
+ rm -f $(TARGETS) *.o core a.out pmnsmap.h mylog.* pmnsmap.i
+
+procmemstat: procmemstat.c pmnsmap.h
+ rm -f $@
+ $(CC) $(CFLAGS) procmemstat.c -o $@ $(LDOPTS) $(LDLIBS)
+
+pmnsmap.h: pmnsmap.spec
+ <CPP> <pmnsmap.spec \
+ | sed -e '/^#/d' -e '/^$$/d' >pmnsmap.i
+ pmgenmap pmnsmap.i >pmnsmap.h
diff --git a/src/procmemstat/README b/src/procmemstat/README
new file mode 100644
index 0000000..b413fe5
--- /dev/null
+++ b/src/procmemstat/README
@@ -0,0 +1,19 @@
+procmemstat - a sample client using the PMAPI
+=============================================
+
+procmemstat is a sample client that uses the Performance Metrics
+Application Programming Interface (PMAPI) to report its own memory
+use.
+
+The source is shipped as part of pcp.sw.demo and is installed in
+/var/pcp/demos/procmemstat. If you have the C compiler installed, the
+source and Makefile in this directory may be used to create a
+functionally equivalent binary, simply by entering the command
+
+ % make
+
+The source in procmemstat.c demonstrates most of the PMAPI services, and
+may be used as a template and style guide when creating your own PMAPI
+clients. Note in particular, the use of ./pmnsmap.spec and the shipped
+tool pmgenmap to assist in the creation of arguments to the PMAPI
+routines, and the manipulation of PMAPI data structures.
diff --git a/src/procmemstat/pmnsmap.spec b/src/procmemstat/pmnsmap.spec
new file mode 100644
index 0000000..8df7190
--- /dev/null
+++ b/src/procmemstat/pmnsmap.spec
@@ -0,0 +1,21 @@
+metrics {
+#ifdef linux
+ proc.memory.rss RSS_TOTAL
+ proc.memory.textrss RSS_TEXT
+ proc.memory.librss RSS_LIB
+ proc.memory.datrss RSS_DATA
+#endif
+#ifdef sgi
+ /* IRIX */
+ proc.memory.virtual.txt V_TXT
+ proc.memory.virtual.dat V_DAT
+ proc.memory.virtual.bss V_BSS
+ proc.memory.virtual.stack V_STK
+ proc.memory.virtual.shm V_SHM
+ proc.memory.physical.txt P_TXT
+ proc.memory.physical.dat P_DAT
+ proc.memory.physical.bss P_BSS
+ proc.memory.physical.stack P_STK
+ proc.memory.physical.shm P_SHM
+#endif
+}
diff --git a/src/procmemstat/procmemstat.c b/src/procmemstat/procmemstat.c
new file mode 100644
index 0000000..f542455
--- /dev/null
+++ b/src/procmemstat/procmemstat.c
@@ -0,0 +1,138 @@
+/*
+ * procmemstat - sample, simple PMAPI client to report your own memory
+ * usage
+ *
+ * Copyright (c) 2013 Red Hat.
+ * Copyright (c) 2002 Silicon Graphics, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+#include "pmnsmap.h"
+
+static void
+get_sample(void)
+{
+ int first = 1;
+ static pmResult *rp;
+ static int numpmid;
+ static pmID *pmidlist;
+ static pmDesc *desclist;
+ pmUnits kbyte_scale;
+ int pid;
+ pmAtomValue tmp;
+ pmAtomValue atom;
+ int sts;
+ int i;
+
+ if (first) {
+ memset(&kbyte_scale, 0, sizeof(kbyte_scale));
+ kbyte_scale.dimSpace = 1;
+ kbyte_scale.scaleSpace = PM_SPACE_KBYTE;
+
+ numpmid = sizeof(metrics) / sizeof(char *);
+ if ((pmidlist = (pmID *)malloc(numpmid * sizeof(pmidlist[0]))) == NULL) {
+ fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+ if ((desclist = (pmDesc *)malloc(numpmid * sizeof(desclist[0]))) == NULL) {
+ fprintf(stderr, "%s: get_sample: malloc: %s\n", pmProgname, osstrerror());
+ exit(1);
+ }
+ if ((sts = pmLookupName(numpmid, metrics, pmidlist)) < 0) {
+ printf("%s: pmLookupName: %s\n", pmProgname, pmErrStr(sts));
+ for (i = 0; i < numpmid; i++) {
+ if (pmidlist[i] == PM_ID_NULL)
+ fprintf(stderr, "%s: metric \"%s\" not in name space\n", pmProgname, metrics[i]);
+ }
+ exit(1);
+ }
+ for (i = 0; i < numpmid; i++) {
+ if ((sts = pmLookupDesc(pmidlist[i], &desclist[i])) < 0) {
+ fprintf(stderr, "%s: cannot retrieve description for metric \"%s\" (PMID: %s)\nReason: %s\n",
+ pmProgname, metrics[i], pmIDStr(pmidlist[i]), pmErrStr(sts));
+ exit(1);
+ }
+ }
+ /*
+ * All metrics we care about share the same instance domain,
+ * and the instance of interest is _my_ PID
+ */
+ pmDelProfile(desclist[0].indom, 0, NULL); /* all off */
+ pid = (int)getpid();
+ pmAddProfile(desclist[0].indom, 1, &pid);
+
+ first = 0;
+ }
+
+ /* fetch the current metrics */
+ if ((sts = pmFetch(numpmid, pmidlist, &rp)) < 0) {
+ fprintf(stderr, "%s: pmFetch: %s\n", pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ printf("memory metrics for pid %" FMT_PID " (sizes in Kbytes)\n", pid);
+ for (i = 0; i < numpmid; i++) {
+ /* process metrics in turn */
+ pmExtractValue(rp->vset[i]->valfmt, rp->vset[i]->vlist,
+ desclist[i].type, &tmp, PM_TYPE_U32);
+ pmConvScale(PM_TYPE_U32, &tmp, &desclist[i].units,
+ &atom, &kbyte_scale);
+ printf("%8d %s\n", atom.l, metrics[i]);
+ }
+}
+
+pmLongOptions longopts[] = {
+ PMAPI_OPTIONS_HEADER("Options"),
+ PMOPT_DEBUG,
+ PMOPT_HELP,
+ PMAPI_OPTIONS_END
+};
+
+pmOptions opts = {
+ .short_options = "D:?",
+ .long_options = longopts,
+};
+
+int
+main(int argc, char **argv)
+{
+ int sts;
+ char *p;
+ char *q;
+
+ setlinebuf(stdout);
+ pmGetOptions(argc, argv, &opts);
+
+ if (opts.errors || opts.optind < argc - 1) {
+ pmUsageMessage(&opts);
+ exit(1);
+ }
+
+ if ((sts = pmNewContext(PM_CONTEXT_HOST, "local:")) < 0) {
+ fprintf(stderr, "%s: Cannot connect to PMCD on host \"local:\": %s\n",
+ pmProgname, pmErrStr(sts));
+ exit(1);
+ }
+
+ get_sample();
+
+#define ARRAY 1*1024*1024
+ p = (char *)malloc(ARRAY);
+ for (q = p; q < &p[ARRAY]; q += 1024)
+ *q = '\0';
+ printf("\nAfter malloc ...\n");
+ get_sample();
+
+ exit(0);
+}
diff --git a/src/python/GNUmakefile b/src/python/GNUmakefile
new file mode 100644
index 0000000..3c2df66
--- /dev/null
+++ b/src/python/GNUmakefile
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = pcp
+CFILES = pmapi.c pmda.c pmgui.c pmi.c mmv.c
+LSRCFILES = setup.py
+LDIRDIRT = build dist
+LDIRT = build_python
+ifneq ($(CFLAGS_ABI),)
+ENV = CFLAGS=$(CFLAGS_ABI)
+else
+ENV =
+endif
+
+default default_pcp: build_python
+
+ifneq "$(PYTHON)" ""
+build_python: setup.py $(CFILES)
+ $(ENV) $(PYTHON) setup.py build_ext $(SETUP_PY_BUILD_OPTIONS)
+ $(ENV) $(PYTHON) setup.py build
+ touch build_python
+
+install_python: default
+ $(PYTHON) setup.py install $(SETUP_PY_INSTALL_OPTIONS)
+ $(PYTHON_INSTALL)
+else
+build_python:
+install_python:
+endif
+
+include $(BUILDRULES)
+
+install_pcp install: install_python
diff --git a/src/python/mmv.c b/src/python/mmv.c
new file mode 100644
index 0000000..220e7e8
--- /dev/null
+++ b/src/python/mmv.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2013-2014 Red Hat.
+ *
+ * This file is part of the "pcp" module, the python interfaces for the
+ * Performance Co-Pilot toolkit.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**************************************************************************\
+** **
+** This C extension module mainly serves the purpose of loading constants **
+** from PCP headers into the module dictionary. The MMV functions and **
+** data structures are wrapped in mmv.py using ctypes. **
+** **
+\**************************************************************************/
+
+#include <Python.h>
+#include <pcp/pmapi.h>
+#include <pcp/mmv_stats.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define MOD_ERROR_VAL NULL
+#define MOD_SUCCESS_VAL(val) val
+#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+ ob = PyModule_Create(&moduledef);
+#else
+#define MOD_ERROR_VAL
+#define MOD_SUCCESS_VAL(val)
+#define MOD_INIT(name) void init##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static void
+dict_add(PyObject *dict, char *symbol, long value)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyvalue = PyLong_FromLong(value);
+#else
+ PyObject *pyvalue = PyInt_FromLong(value);
+#endif
+ PyDict_SetItemString(dict, symbol, pyvalue);
+ Py_XDECREF(pyvalue);
+}
+
+static PyMethodDef methods[] = { { NULL } };
+
+/* called when the module is initialized. */
+MOD_INIT(cmmv)
+{
+ PyObject *module, *dict;
+
+ MOD_DEF(module, "cmmv", NULL, methods);
+ if (module == NULL)
+ return MOD_ERROR_VAL;
+
+ dict = PyModule_GetDict(module);
+
+ dict_add(dict, "MMV_NAMEMAX", MMV_NAMEMAX);
+ dict_add(dict, "MMV_STRINGMAX", MMV_STRINGMAX);
+
+ dict_add(dict, "MMV_TYPE_NOSUPPORT", MMV_TYPE_NOSUPPORT);
+ dict_add(dict, "MMV_TYPE_I32", MMV_TYPE_I32);
+ dict_add(dict, "MMV_TYPE_U32", MMV_TYPE_U32);
+ dict_add(dict, "MMV_TYPE_I64", MMV_TYPE_I64);
+ dict_add(dict, "MMV_TYPE_U64", MMV_TYPE_U64);
+ dict_add(dict, "MMV_TYPE_FLOAT", MMV_TYPE_FLOAT);
+ dict_add(dict, "MMV_TYPE_DOUBLE", MMV_TYPE_DOUBLE);
+ dict_add(dict, "MMV_TYPE_STRING", MMV_TYPE_STRING);
+ dict_add(dict, "MMV_TYPE_ELAPSED", MMV_TYPE_ELAPSED);
+
+ dict_add(dict, "MMV_SEM_COUNTER", MMV_SEM_COUNTER);
+ dict_add(dict, "MMV_SEM_INSTANT", MMV_SEM_INSTANT);
+ dict_add(dict, "MMV_SEM_DISCRETE", MMV_SEM_DISCRETE);
+
+ dict_add(dict, "MMV_FLAG_NOPREFIX", MMV_FLAG_NOPREFIX);
+ dict_add(dict, "MMV_FLAG_PROCESS", MMV_FLAG_PROCESS);
+
+ return MOD_SUCCESS_VAL(module);
+}
diff --git a/src/python/pcp/GNUmakefile b/src/python/pcp/GNUmakefile
new file mode 100644
index 0000000..492d6dd
--- /dev/null
+++ b/src/python/pcp/GNUmakefile
@@ -0,0 +1,25 @@
+#
+# Copyright (c) 2012-2013 Red Hat.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+LSRCFILES = __init__.py pmapi.py pmcc.py pmda.py pmgui.py pmi.py mmv.py \
+ pmsubsys.py
+
+default default_pcp:
+
+include $(BUILDRULES)
+
+install_pcp install:
diff --git a/src/python/pcp/__init__.py b/src/python/pcp/__init__.py
new file mode 100644
index 0000000..708e08a
--- /dev/null
+++ b/src/python/pcp/__init__.py
@@ -0,0 +1 @@
+__all__ = ["pmapi", "pmcc", "pmda", "pmgui", "pmi", "pmsubsys", "mmv", "cpmapi", "cpmda", "cpmgui", "cpmi", "cmmv"]
diff --git a/src/python/pcp/mmv.py b/src/python/pcp/mmv.py
new file mode 100644
index 0000000..426c2d8
--- /dev/null
+++ b/src/python/pcp/mmv.py
@@ -0,0 +1,332 @@
+# pylint: disable=C0103
+"""Wrapper module for libpcp_mmv - PCP Memory Mapped Values library
+#
+# Copyright (C) 2013 Red Hat.
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Example use of this module for instrumenting a python application:
+
+ from pcp import mmv, pmapi
+ from cpmapi import PM_COUNT_ONE, PM_TIME_USEC
+
+ instances = [mmv.mmv_instance(0, "zero"), mmv.mmv_instance(1, "hero")]
+ indoms = [mmv.mmv_indom(serial = 1,
+ shorttext = "We can be heroes",
+ helptext = "Set of instances from zero to hero"),
+ indoms[0].set_instances(instances)
+ metrics = [mmv.mmv_metric(name = "counter",
+ item = 1,
+ typeof = mmv.MMV_TYPE_U32,
+ semantics = mmv.MMV_SEM_COUNTER,
+ dimension = pmapi.pmUnits(0,0,1,0,0,PM_COUNT_ONE),
+ shorttext = "Example counter metric",
+ helptext = "Yep, a test counter metric"),
+ mmv.mmv_metric(name = "instant",
+ item = 2,
+ typeof = mmv.MMV_TYPE_I32,
+ semantics = mmv.MMV_SEM_INSTANT,
+ dimension = pmapi.pmUnits(0,0,0,0,0,0),
+ shorttext = "Example instant metric",
+ helptext = "Yep, a test instantaneous metric"),
+ mmv.mmv_metric(name = "indom",
+ item = 3,
+ typeof = mmv.MMV_TYPE_U32,
+ semantics = mmv.MMV_SEM_DISCRETE,
+ dimension = pmapi.pmUnits(0,0,0,0,0,0),
+ indom = 1)]
+
+ values = mmv.MemoryMappedValues("demo")
+ values.add_indoms(indoms)
+ values.add_metrics(metrics)
+
+ values.start()
+ instant = values.lookup_mapping("instant", None)
+ values.set(instant, 41)
+ values.inc(instant)
+ values.stop()
+"""
+
+from pcp.pmapi import pmUnits, pmAtomValue
+from cmmv import MMV_NAMEMAX
+
+import ctypes
+from ctypes import Structure, POINTER
+from ctypes import c_int, c_uint, c_long, c_char, c_char_p, c_double, c_void_p
+
+# Performance Co-Pilot MMV library (C)
+LIBPCP_MMV = ctypes.CDLL(ctypes.util.find_library("pcp_mmv"))
+
+##############################################################################
+#
+# definition of structures used by libpcp, derived from <pcp/pmapi.h>
+#
+# This section defines the data structures for accessing and manuiplating
+# metric information and values. Detailed information about these data
+# structures can be found in the MMV(4) manual page.
+#
+
+class mmv_instance(Structure):
+ """ Maps internal to external instance identifiers, within an
+ instance domain.
+ """
+ _fields_ = [("internal", c_int),
+ ("external", c_char * MMV_NAMEMAX)]
+
+class mmv_indom(Structure):
+ """ Represents an instance domain (for set valued metrics)
+ Instance domains have associated instances - integer/string pairs.
+ Defines complete indom metadata (instances, count, text and so on)
+ """
+ _fields_ = [("serial", c_uint),
+ ("count", c_uint),
+ ("instances", POINTER(mmv_instance)),
+ ("shorttext", c_char_p),
+ ("helptext", c_char_p)]
+
+ def set_instances(self, instances):
+ """ Update the instances and counts fields for this indom """
+ self.count = len(instances)
+ instance_array = (mmv_instance * self.count)()
+ for i in xrange(self.count):
+ instance_array[i].internal = instances[i].internal
+ instance_array[i].external = instances[i].external
+ self.instances = instance_array
+
+class mmv_metric(Structure):
+ """ Represents an individual metric to be exported by pmdammv
+ Defines complete metric metadata (type, semantics, units and so on)
+ """
+ _fields_ = [("name", c_char * MMV_NAMEMAX),
+ ("item", c_int),
+ ("typeof", c_int),
+ ("semantics", c_int),
+ ("dimension", pmUnits),
+ ("indom", c_uint),
+ ("shorttext", c_char_p),
+ ("helptext", c_char_p)]
+
+##
+# PCP Memory Mapped Value Services
+
+LIBPCP_MMV.mmv_stats_init.restype = c_void_p
+LIBPCP_MMV.mmv_stats_init.argtypes = [
+ c_char_p, c_int, c_int,
+ POINTER(mmv_metric), c_int, POINTER(mmv_indom), c_int]
+
+LIBPCP_MMV.mmv_stats_stop.restype = None
+LIBPCP_MMV.mmv_stats_stop.argtypes = [c_char_p, c_void_p]
+
+LIBPCP_MMV.mmv_lookup_value_desc.restype = pmAtomValue
+LIBPCP_MMV.mmv_lookup_value_desc.argtypes = [c_void_p, c_char_p, c_char_p]
+
+LIBPCP_MMV.mmv_inc_value.restype = None
+LIBPCP_MMV.mmv_inc_value.argtypes = [c_void_p, POINTER(pmAtomValue), c_double]
+
+LIBPCP_MMV.mmv_set_value.restype = None
+LIBPCP_MMV.mmv_set_value.argtypes = [c_void_p, POINTER(pmAtomValue), c_double]
+
+LIBPCP_MMV.mmv_set_string.restype = None
+LIBPCP_MMV.mmv_set_string.argtypes = [
+ c_void_p, POINTER(pmAtomValue), c_char_p, c_int]
+
+LIBPCP_MMV.mmv_stats_add.restype = None
+LIBPCP_MMV.mmv_stats_add.argtypes = [c_void_p, c_char_p, c_char_p, c_double]
+
+LIBPCP_MMV.mmv_stats_inc.restype = None
+LIBPCP_MMV.mmv_stats_inc.argtypes = [c_void_p, c_char_p, c_char_p]
+
+LIBPCP_MMV.mmv_stats_set.restype = None
+LIBPCP_MMV.mmv_stats_set.argtypes = [c_void_p, c_char_p, c_char_p, c_double]
+
+LIBPCP_MMV.mmv_stats_add_fallback.restype = None
+LIBPCP_MMV.mmv_stats_add_fallback.argtypes = [
+ c_void_p, c_char_p, c_char_p, c_char_p, c_double]
+
+LIBPCP_MMV.mmv_stats_inc_fallback.restype = None
+LIBPCP_MMV.mmv_stats_inc_fallback.argtypes = [
+ c_void_p, c_char_p, c_char_p, c_char_p]
+
+LIBPCP_MMV.mmv_stats_interval_start.restype = POINTER(pmAtomValue)
+LIBPCP_MMV.mmv_stats_interval_start.argtypes = [
+ c_void_p, POINTER(pmAtomValue), c_char_p, c_char_p]
+
+LIBPCP_MMV.mmv_stats_interval_end.restype = None
+LIBPCP_MMV.mmv_stats_interval_end.argtypes = [c_void_p, POINTER(pmAtomValue)]
+
+LIBPCP_MMV.mmv_stats_set_strlen.restype = None
+LIBPCP_MMV.mmv_stats_set_strlen.argtypes = [
+ c_void_p, c_char_p, c_char_p, c_char_p, c_long]
+
+
+#
+# class MemoryMappedValues
+#
+# This class wraps the MMV (Memory Mapped Values) library functions
+#
+
+class MemoryMappedValues(object):
+ """ Defines a set of PCP Memory Mapped Value (MMV) metrics
+
+ Creates PCP metrics from an instrumented python script
+ via pmdammv (Performance Metrics Domain Agent for MMV)
+ """
+
+ def __init__(self, name, flags = 0, cluster = 42):
+ self._name = name
+ self._cluster = cluster # PMID cluster number (domain is MMV)
+ self._flags = flags # MMV_FLAGS_* flags
+ self._metrics = []
+ self._indoms = []
+ self._handle = None # pointer to the memory mapped area
+
+ def start(self):
+ """ Initialise the underlying library with metrics/instances.
+ On completion of this call, we're all visible to pmdammv.
+ """
+ count_metrics = len(self._metrics)
+ metrics = (mmv_metric * count_metrics)()
+ for i in xrange(count_metrics):
+ metrics[i] = self._metrics[i]
+ count_indoms = len(self._indoms)
+ indoms = (mmv_indom * count_indoms)()
+ for i in xrange(count_indoms):
+ indoms[i] = self._indoms[i]
+ self._handle = LIBPCP_MMV.mmv_stats_init(
+ self._name,
+ self._cluster,
+ self._flags,
+ metrics, count_metrics,
+ indoms, count_indoms)
+
+ def stop(self):
+ """ Shut down the underlying library with metrics/instances.
+ This closes the mmap file preventing any further updates.
+ """
+ if (self._handle != None):
+ LIBPCP_MMV.mmv_stats_stop(self._name, self._handle)
+ self._handle = None
+
+ def restart(self):
+ """ Cleanly stop-if-running and restart MMV export services. """
+ self.stop()
+ self.start()
+
+ def started(self):
+ """ Property flagging an active memory mapping """
+ if (self._handle == None):
+ return 0
+ return 1
+
+ def add_indoms(self, indoms):
+ """ Make a list of instance domains visible to the MMV export """
+ self._indoms = indoms
+ if (self.started()):
+ self.restart()
+
+ def add_indom(self, indom):
+ """ Make an additional instance domain visible to the MMV export """
+ self._indoms.append(indom)
+ self.add_indoms(self._indoms)
+
+ def add_metrics(self, metrics):
+ """ Make a list of metrics visible to the MMV export """
+ self._metrics = metrics
+ if (self.started()):
+ self.restart()
+
+ def add_metric(self, metric):
+ """ Make an additional metric visible to the MMV export """
+ self._metrics.append(metric)
+ self.add_metrics(self._metrics)
+
+
+ def lookup_mapping(self, name, inst):
+ """ Find the memory mapping for a given metric name and instance
+
+ This handle can be used to directly manipulate metric values
+ by other interfaces in this module. This is the *preferred*
+ technique for manipulating MMV values. It is more efficient
+ and the alternative (name/inst lookups) is made available as
+ a convenience only for situations where performance will not
+ be affected by repeated (linear) name/inst lookups.
+ """
+ return LIBPCP_MMV.mmv_lookup_value_desc(self._handle, name, inst)
+
+ def add(self, mapping, value):
+ """ Increment the mapped metric by a given value """
+ LIBPCP_MMV.mmv_inc_value(self._handle, mapping, value)
+
+ def inc(self, mapping):
+ """ Increment the mapped metric by one """
+ LIBPCP_MMV.mmv_inc_value(self._handle, mapping, 1)
+
+ def set(self, mapping, value):
+ """ Set the mapped metric to a given value """
+ LIBPCP_MMV.mmv_set_value(self._handle, mapping, value)
+
+ def set_string(self, mapping, value):
+ """ Set the string mapped metric to a given value """
+ LIBPCP_MMV.mmv_set_string(self._handle, mapping, value, len(value))
+
+ def interval_start(self, mapping):
+ """ Start a timed interval for the mapped metric
+ The opaque handle (mapping) returned is passed to interval_end().
+ """
+ return LIBPCP_MMV.mmv_stats_interval_start(self._handle, mapping, 0, 0)
+
+ def interval_end(self, mapping):
+ """ End a timed interval, the metrics time is increased by interval """
+ return LIBPCP_MMV.mmv_stats_interval_end(self._handle, mapping)
+
+
+ def lookup_add(self, name, inst, value):
+ """ Lookup the named metric[instance] and add a value to it """
+ LIBPCP_MMV.mmv_stats_add(self._handle, name, inst, value)
+
+ def lookup_inc(self, name, inst):
+ """ Lookup the named metric[instance] and add one to it """
+ LIBPCP_MMV.mmv_stats_inc(self._handle, name, inst)
+
+ def lookup_set(self, name, inst, value):
+ """ Lookup the named metric[instance] and set its value """
+ LIBPCP_MMV.mmv_stats_set(self._handle, name, inst, value)
+
+ def lookup_interval_start(self, name, inst):
+ """ Lookup the named metric[instance] and start an interval
+ The opaque handle returned is passed to interval_end().
+ """
+ return LIBPCP_MMV.mmv_stats_interval_start(self._handle,
+ None, name, inst)
+
+ def lookup_set_string(self, name, inst, s):
+ """ Lookup the named metric[instance] and set its string value """
+ LIBPCP_MMV.mmv_stats_set_strlen(self._handle, name, inst, s, len(s))
+
+ def lookup_add_fallback(self, name, inst, fall, value):
+ """ Lookup the named metric[instance] and set its value if found
+ If instance is not found, fallback to using a second instance
+ One example use is: add value to bucketN else use a catch-all
+ bucket such as "other"
+ """
+ LIBPCP_MMV.mmv_stats_add_fallback(self._handle, name, inst, fall, value)
+
+ def lookup_inc_fallback(self, name, inst, fallback):
+ """ Lookup the named metric[instance] and increment its value if found
+ If instance is not found, fallback to using a second instance
+ One sample use is: inc value of BucketA, else inc a catch-all
+ """
+ LIBPCP_MMV.mmv_stats_inc_fallback(self._handle, name, inst, fallback)
+
diff --git a/src/python/pcp/pmapi.py b/src/python/pcp/pmapi.py
new file mode 100644
index 0000000..b558f9d
--- /dev/null
+++ b/src/python/pcp/pmapi.py
@@ -0,0 +1,1866 @@
+# pylint: disable=C0103
+""" Wrapper module for LIBPCP - the core Performace Co-Pilot API """
+#
+# Copyright (C) 2012-2014 Red Hat
+# Copyright (C) 2009-2012 Michael T. Werner
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Additional Information:
+#
+# Performance Co-Pilot Web Site
+# http://www.performancecopilot.org
+#
+# Performance Co-Pilot Programmer's Guide
+# cf. Chapter 3. PMAPI - The Performance Metrics API
+#
+# EXAMPLE
+"""
+ from pcp import pmapi
+ import cpmapi as c_api
+
+ # Create a pcp class
+ context = pmapi.pmContext(c_api.PM_CONTEXT_HOST, "local:")
+
+ # Get ids for number cpus and load metrics
+ metric_ids = context.pmLookupName(("hinv.ncpu","kernel.all.load"))
+ # Get the description of the metrics
+ descs = context.pmLookupDescs(metric_ids)
+ # Fetch the current value for number cpus
+ results = context.pmFetch(metric_ids)
+ # Extract the value into a scalar value
+ atom = context.pmExtractValue(results.contents.get_valfmt(0),
+ results.contents.get_vlist(0, 0),
+ descs[0].contents.type,
+ c_api.PM_TYPE_U32)
+ print "#cpus=", atom.ul
+
+ # Get the instance ids for kernel.all.load
+ inst1 = context.pmLookupInDom(descs[1], "1 minute")
+ inst5 = context.pmLookupInDom(descs[1], "5 minute")
+
+ # Loop through the metric ids
+ for i in xrange(results.contents.numpmid):
+ # Is this the kernel.all.load id?
+ if (results.contents.get_pmid(i) != metric_ids[1]):
+ continue
+ # Extract the kernel.all.load instance
+ for j in xrange(results.contents.get_numval(i) - 1):
+ atom = context.pmExtractValue(results.contents.get_valfmt(i),
+ results.contents.get_vlist(i, j),
+ descs[i].contents.type,
+ c_api.PM_TYPE_FLOAT)
+ value = atom.f
+ if results.contents.get_inst(i, j) == inst1:
+ print "load average 1=",atom.f
+ elif results.contents.get_inst(i, j) == inst5:
+ print "load average 5=",atom.f
+"""
+
+# for reporting on times from pmLocaltime function
+from time import mktime
+
+# constants adapted from C header file <pcp/pmapi.h>
+import cpmapi as c_api
+
+# for interfacing with LIBPCP - the client-side C API
+import ctypes
+from ctypes import c_char, c_int, c_uint, c_long, c_char_p, c_void_p
+from ctypes import c_longlong, c_ulonglong, c_float, c_double
+from ctypes import CDLL, POINTER, CFUNCTYPE, Structure, Union
+from ctypes import addressof, pointer, sizeof, cast, byref
+from ctypes import create_string_buffer, memmove
+from ctypes.util import find_library
+
+##############################################################################
+#
+# dynamic library loads
+#
+
+LIBPCP = CDLL(find_library("pcp"))
+LIBC = CDLL(find_library("c"))
+
+
+##############################################################################
+#
+# definition of exception classes
+#
+
+class pmErr(Exception):
+ def __str__(self):
+ errSym = None
+ try:
+ errSym = c_api.pmErrSymDict[self.args[0]]
+ except KeyError:
+ pass
+ if errSym == None:
+ return self.message()
+ return "%s %s" % (errSym, self.message())
+
+ def message(self):
+ errStr = create_string_buffer(c_api.PM_MAXERRMSGLEN)
+ errStr = LIBPCP.pmErrStr_r(self.args[0], errStr, c_api.PM_MAXERRMSGLEN)
+ for index in range(1, len(self.args)):
+ errStr += " " + str(self.args[index])
+ return errStr
+
+ def progname(self):
+ return c_char_p.in_dll(LIBPCP, "pmProgname").value
+
+class pmUsageErr(Exception):
+ def message(self):
+ return c_api.pmUsageMessage()
+
+
+##############################################################################
+#
+# definition of structures used by libpcp, derived from <pcp/pmapi.h>
+#
+# This section defines the data structures for accessing and manuiplating
+# metric information and values. Detailed information about these data
+# structures can be found in:
+#
+# Performance Co-Pilot Programmer's Guide
+# Section 3.4 - Performance Metric Descriptions
+# Section 3.5 - Performance Metric Values
+#
+
+# these hardcoded decls should be derived from <sys/time.h>
+class timeval(Structure):
+ _fields_ = [("tv_sec", c_long),
+ ("tv_usec", c_long)]
+
+ def __init__(self, sec = 0, usec = 0):
+ Structure.__init__(self)
+ self.tv_sec = sec
+ self.tv_usec = usec
+
+ @classmethod
+ def fromInterval(builder, interval):
+ """ Construct timeval from a string using pmParseInterval """
+ tvp = builder()
+ errmsg = c_char_p()
+ status = LIBPCP.pmParseInterval(interval, byref(tvp), byref(errmsg))
+ if status < 0:
+ raise pmErr(status, errmsg)
+ return tvp
+
+ def __str__(self):
+ return "%.3f" % c_api.pmtimevalToReal(self.tv_sec, self.tv_usec)
+
+ def __float__(self):
+ return float(c_api.pmtimevalToReal(self.tv_sec, self.tv_usec))
+
+ def __long__(self):
+ return long(self.tv_sec)
+
+ def __int__(self):
+ return int(self.tv_sec)
+
+ def sleep(self):
+ """ Delay for the amount of time specified by this timeval. """
+ c_api.pmtimevalSleep(self.tv_sec, self.tv_usec)
+ return None
+
+class tm(Structure):
+ _fields_ = [("tm_sec", c_int),
+ ("tm_min", c_int),
+ ("tm_hour", c_int),
+ ("tm_mday", c_int),
+ ("tm_mon", c_int),
+ ("tm_year", c_int),
+ ("tm_wday", c_int),
+ ("tm_yday", c_int),
+ ("tm_isdst", c_int),
+ ("tm_gmtoff", c_long), # glibc/bsd extension
+ ("tm_zone", c_char_p)] # glibc/bsd extension
+
+ def __str__(self):
+ timetuple = (self.tm_year+1900, self.tm_mon, self.tm_mday,
+ self.tm_hour, self.tm_min, self.tm_sec,
+ self.tm_wday, self.tm_yday, self.tm_isdst)
+ inseconds = 0.0
+ try:
+ inseconds = mktime(timetuple)
+ except:
+ pass
+ return "%s %s" % (inseconds.__str__(), timetuple)
+
+class pmAtomValue(Union):
+ """Union used for unpacking metric values according to type
+
+ Constants for specifying metric types are defined in module pmapi
+ """
+ _fields_ = [("l", c_int),
+ ("ul", c_uint),
+ ("ll", c_longlong),
+ ("ull", c_ulonglong),
+ ("f", c_float),
+ ("d", c_double),
+ ("cp", c_char_p),
+ ("vp", c_void_p)]
+
+ _atomDrefD = {c_api.PM_TYPE_32 : lambda x: x.l,
+ c_api.PM_TYPE_U32 : lambda x: x.ul,
+ c_api.PM_TYPE_64 : lambda x: x.ll,
+ c_api.PM_TYPE_U64 : lambda x: x.ull,
+ c_api.PM_TYPE_FLOAT : lambda x: x.f,
+ c_api.PM_TYPE_DOUBLE : lambda x: x.d,
+ c_api.PM_TYPE_STRING : lambda x: x.cp,
+ c_api.PM_TYPE_AGGREGATE : lambda x: None,
+ c_api.PM_TYPE_AGGREGATE_STATIC : lambda x: None,
+ c_api.PM_TYPE_NOSUPPORT : lambda x: None,
+ c_api.PM_TYPE_UNKNOWN : lambda x: None
+ }
+
+ def dref(self, typed):
+ return self._atomDrefD[typed](self)
+
+class pmUnits(Structure):
+ """
+ Compiler-specific bitfields specifying scale and dimension of metric values
+ Constants for specifying metric units are defined in module pmapi
+ IRIX => HAVE_BITFIELDS_LTOR, gcc => not so much
+ """
+ if c_api.HAVE_BITFIELDS_LTOR:
+ _fields_ = [("dimSpace", c_int, 4),
+ ("dimTime", c_int, 4),
+ ("dimCount", c_int, 4),
+ ("scaleSpace", c_int, 4),
+ ("scaleTime", c_int, 4),
+ ("scaleCount", c_int, 4),
+ ("pad", c_int, 8)]
+ else:
+ _fields_ = [("pad", c_int, 8),
+ ("scaleCount", c_int, 4),
+ ("scaleTime", c_int, 4),
+ ("scaleSpace", c_int, 4),
+ ("dimCount", c_int, 4),
+ ("dimTime", c_int, 4),
+ ("dimSpace", c_int, 4)]
+
+ def __init__(self, dimS=0, dimT=0, dimC=0, scaleS=0, scaleT=0, scaleC=0):
+ Structure.__init__(self)
+ self.dimSpace = dimS
+ self.dimTime = dimT
+ self.dimCount = dimC
+ self.scaleSpace = scaleS
+ self.scaleTime = scaleT
+ self.scaleCount = scaleC
+ self.pad = 0
+
+ def __str__(self):
+ unitstr = ctypes.create_string_buffer(64)
+ return str(LIBPCP.pmUnitsStr_r(self, unitstr, 64))
+
+
+class pmValueBlock(Structure):
+ """Value block bitfields for different compilers
+ A value block holds the value of an instance of a metric
+ pointed to by the pmValue structure, when that value is
+ too large (> 32 bits) to fit in the pmValue structure
+ """
+ if c_api.HAVE_BITFIELDS_LTOR: # IRIX
+ _fields_ = [("vtype", c_uint, 8),
+ ("vlen", c_uint, 24),
+ ("vbuf", c_char * 1)]
+ else: # Linux (gcc)
+ _fields_ = [("vlen", c_uint, 24),
+ ("vtype", c_uint, 8),
+ ("vbuf", c_char * 1)]
+
+class valueDref(Union):
+ """Union in pmValue for dereferencing the value of an instance of a metric
+
+ For small items, e.g. a 32-bit number, the union contains the actual value
+ For large items, e.g. a text string, the union points to a pmValueBlock
+ """
+ _fields_ = [("pval", POINTER(pmValueBlock)),
+ ("lval", c_int)]
+ def __str__(self):
+ return "value=%#lx" % (self.lval)
+
+class pmValue(Structure):
+ """Structure holding the value of a metric instance """
+ _fields_ = [("inst", c_int),
+ ("value", valueDref)]
+ def __str__(self):
+ vstr = str(self.value)
+ return "pmValue@%#lx inst=%d " % (addressof(self), self.inst) + vstr
+
+class pmValueSet(Structure):
+ """Structure holding a metric's list of instance values
+
+ A performance metric may contain one or more instance values, one for each
+ item that the metric concerns. For example, a metric measuring filesystem
+ free space would contain one instance value for each filesystem that exists
+ on the target machine. Whereas, a metric measuring free memory would have
+ only one instance value, representing the total amount of free memory on
+ the target system.
+ """
+ _fields_ = [("pmid", c_uint),
+ ("numval", c_int),
+ ("valfmt", c_int),
+ ("vlist", (pmValue * 1))]
+
+ def __str__(self):
+ if self.valfmt == 0:
+ vals = xrange(self.numval)
+ vstr = str([" %s" % str(self.vlist[i]) for i in vals])
+ vset = (addressof(self), self.pmid, self.numval, self.valfmt)
+ return "pmValueSet@%#lx id=%#lx numval=%d valfmt=%d" % vset + vstr
+ else:
+ return ""
+
+ def vlist_read(self):
+ return pointer(self._vlist[0])
+
+ vlist = property(vlist_read, None, None, None)
+
+
+pmValueSetPtr = POINTER(pmValueSet)
+pmValueSetPtr.pmid = property(lambda x: x.contents.pmid, None, None, None)
+pmValueSetPtr.numval = property(lambda x: x.contents.numval, None, None, None)
+pmValueSetPtr.valfmt = property(lambda x: x.contents.valfmt, None, None, None)
+pmValueSetPtr.vlist = property(lambda x: x.contents.vlist, None, None, None)
+
+
+class pmResult(Structure):
+ """Structure returned by pmFetch, with a value set for each metric queried
+
+ The vset is defined with a "fake" array bounds of 1, which can give runtime
+ array bounds complaints. The getter methods are array bounds agnostic.
+ """
+ _fields_ = [ ("timestamp", timeval),
+ ("numpmid", c_int),
+ # array N of pointer to pmValueSet
+ ("vset", (POINTER(pmValueSet)) * 1) ]
+ def __init__(self):
+ Structure.__init__(self)
+ self.numpmid = 0
+
+ def __str__(self):
+ vals = xrange(self.numpmid)
+ vstr = str([" %s" % str(self.vset[i].contents) for i in vals])
+ return "pmResult@%#lx id#=%d " % (addressof(self), self.numpmid) + vstr
+
+ def get_pmid(self, vset_idx):
+ """ Return the pmid of vset[vset_idx] """
+ vsetptr = cast(self.vset, POINTER(pmValueSetPtr))
+ return vsetptr[vset_idx].contents.pmid
+
+ def get_valfmt(self, vset_idx):
+ """ Return the valfmt of vset[vset_idx] """
+ vsetptr = cast(self.vset, POINTER(POINTER(pmValueSet)))
+ return vsetptr[vset_idx].contents.valfmt
+
+ def get_numval(self, vset_idx):
+ """ Return the numval of vset[vset_idx] """
+ vsetptr = cast(self.vset, POINTER(POINTER(pmValueSet)))
+ return vsetptr[vset_idx].contents.numval
+
+ def get_vset(self, vset_idx):
+ """ Return the vset[vset_idx] """
+ vsetptr = cast(self.vset, POINTER(POINTER(pmValueSet)))
+ return vsetptr[vset_idx]
+
+ def get_vlist(self, vset_idx, vlist_idx):
+ """ Return the vlist[vlist_idx] of vset[vset_idx] """
+ listptr = cast(self.get_vset(vset_idx).contents.vlist, POINTER(pmValue))
+ return listptr[vlist_idx]
+
+ def get_inst(self, vset_idx, vlist_idx):
+ """ Return the inst for vlist[vlist_idx] of vset[vset_idx] """
+ return self.get_vlist(vset_idx, vlist_idx).inst
+
+pmID = c_uint
+pmInDom = c_uint
+
+class pmDesc(Structure):
+ """Structure describing a metric
+ """
+ _fields_ = [("pmid", c_uint),
+ ("type", c_int),
+ ("indom", c_uint),
+ ("sem", c_int),
+ ("units", pmUnits) ]
+ def __str__(self):
+ fields = (addressof(self), self.pmid, self.type)
+ return "pmDesc@%#lx id=%#lx type=%d" % fields
+
+pmDescPtr = POINTER(pmDesc)
+pmDescPtr.sem = property(lambda x: x.contents.sem, None, None, None)
+pmDescPtr.type = property(lambda x: x.contents.type, None, None, None)
+
+
+def get_indom(pmdesc):
+ """Internal function to extract an indom from a pmdesc
+
+ Allow functions requiring an indom to be passed a pmDesc* instead
+ """
+ class Value(Union):
+ _fields_ = [ ("pval", POINTER(pmDesc)),
+ ("lval", c_uint) ]
+ if type(pmdesc) == POINTER(pmDesc):
+ return pmdesc.contents.indom
+ else: # raw indom
+ # Goodness, there must be a simpler way to do this
+ value = Value()
+ value.pval = pmdesc
+ return value.lval
+
+class pmMetricSpec(Structure):
+ """Structure describing a metric's specification
+ """
+ _fields_ = [ ("isarch", c_int),
+ ("source", c_char_p),
+ ("metric", c_char_p),
+ ("ninst", c_int),
+ ("inst", POINTER(c_char_p)) ]
+ def __str__(self):
+ insts = map(lambda x: str(self.inst[x]), range(self.ninst))
+ fields = (addressof(self), self.isarch, self.source, insts)
+ return "pmMetricSpec@%#lx src=%s metric=%s insts=" % fields
+
+ @classmethod
+ def fromString(builder, string, isarch = 0, source = ''):
+ result = POINTER(builder)()
+ errmsg = c_char_p()
+ status = LIBPCP.pmParseMetricSpec(string, isarch, source,
+ byref(result), byref(errmsg))
+ if status < 0:
+ raise pmErr(status, errmsg)
+ return result
+
+class pmLogLabel(Structure):
+ """Label record at the start of every log file
+ """
+ _fields_ = [ ("magic", c_int),
+ ("pid_t", c_int),
+ ("start", timeval),
+ ("hostname", c_char * c_api.PM_LOG_MAXHOSTLEN),
+ ("tz", c_char * c_api.PM_TZ_MAXLEN) ]
+
+
+##############################################################################
+#
+# PMAPI function prototypes
+#
+
+##
+# PMAPI Name Space Services
+
+LIBPCP.pmGetChildren.restype = c_int
+LIBPCP.pmGetChildren.argtypes = [c_char_p, POINTER(POINTER(c_char_p))]
+
+LIBPCP.pmGetChildrenStatus.restype = c_int
+LIBPCP.pmGetChildrenStatus.argtypes = [
+ c_char_p, POINTER(POINTER(c_char_p)), POINTER(POINTER(c_int))]
+
+LIBPCP.pmGetPMNSLocation.restype = c_int
+LIBPCP.pmGetPMNSLocation.argtypes = []
+
+LIBPCP.pmLoadNameSpace.restype = c_int
+LIBPCP.pmLoadNameSpace.argtypes = [c_char_p]
+
+LIBPCP.pmLookupName.restype = c_int
+LIBPCP.pmLookupName.argtypes = [c_int, (c_char_p * 1), POINTER(c_uint)]
+
+LIBPCP.pmNameAll.restype = c_int
+LIBPCP.pmNameAll.argtypes = [c_int, POINTER(POINTER(c_char_p))]
+
+LIBPCP.pmNameID.restype = c_int
+LIBPCP.pmNameID.argtypes = [c_int, POINTER(c_char_p)]
+
+traverseCB_type = CFUNCTYPE(None, c_char_p)
+LIBPCP.pmTraversePMNS.restype = c_int
+LIBPCP.pmTraversePMNS.argtypes = [c_char_p, traverseCB_type]
+
+LIBPCP.pmUnloadNameSpace.restype = c_int
+LIBPCP.pmUnloadNameSpace.argtypes = []
+
+LIBPCP.pmRegisterDerived.restype = c_int
+LIBPCP.pmRegisterDerived.argtypes = [c_char_p, c_char_p]
+
+LIBPCP.pmLoadDerivedConfig.restype = c_int
+LIBPCP.pmLoadDerivedConfig.argtypes = [c_char_p]
+
+LIBPCP.pmDerivedErrStr.restype = c_char_p
+LIBPCP.pmDerivedErrStr.argtypes = []
+
+##
+# PMAPI Metrics Description Services
+
+LIBPCP.pmLookupDesc.restype = c_int
+LIBPCP.pmLookupDesc.argtypes = [c_uint, POINTER(pmDesc)]
+
+LIBPCP.pmLookupInDomText.restype = c_int
+LIBPCP.pmLookupInDomText.argtypes = [c_uint, c_int, POINTER(c_char_p)]
+
+LIBPCP.pmLookupText.restype = c_int
+LIBPCP.pmLookupText.argtypes = [c_uint, c_int, POINTER(c_char_p)]
+
+
+##
+# PMAPI Instance Domain Services
+
+LIBPCP.pmGetInDom.restype = c_int
+LIBPCP.pmGetInDom.argtypes = [
+ c_uint, POINTER(POINTER(c_int)), POINTER(POINTER(c_char_p))]
+
+LIBPCP.pmLookupInDom.restype = c_int
+LIBPCP.pmLookupInDom.argtypes = [c_uint, c_char_p]
+
+LIBPCP.pmNameInDom.restype = c_int
+LIBPCP.pmNameInDom.argtypes = [c_uint, c_uint, POINTER(c_char_p)]
+
+
+##
+# PMAPI Context Services
+
+LIBPCP.pmNewContext.restype = c_int
+LIBPCP.pmNewContext.argtypes = [c_int, c_char_p]
+
+LIBPCP.pmDestroyContext.restype = c_int
+LIBPCP.pmDestroyContext.argtypes = [c_int]
+
+LIBPCP.pmDupContext.restype = c_int
+LIBPCP.pmDupContext.argtypes = []
+
+LIBPCP.pmUseContext.restype = c_int
+LIBPCP.pmUseContext.argtypes = [c_int]
+
+LIBPCP.pmWhichContext.restype = c_int
+LIBPCP.pmWhichContext.argtypes = []
+
+LIBPCP.pmAddProfile.restype = c_int
+LIBPCP.pmAddProfile.argtypes = [c_uint, c_int, POINTER(c_int)]
+
+LIBPCP.pmDelProfile.restype = c_int
+LIBPCP.pmDelProfile.argtypes = [c_uint, c_int, POINTER(c_int)]
+
+LIBPCP.pmSetMode.restype = c_int
+LIBPCP.pmSetMode.argtypes = [c_int, POINTER(timeval), c_int]
+
+LIBPCP.pmReconnectContext.restype = c_int
+LIBPCP.pmReconnectContext.argtypes = [c_int]
+
+LIBPCP.pmGetContextHostName_r.restype = c_char_p
+LIBPCP.pmGetContextHostName_r.argtypes = [c_int, c_char_p, c_int]
+
+
+##
+# PMAPI Timezone Services
+
+LIBPCP.pmNewContextZone.restype = c_int
+LIBPCP.pmNewContextZone.argtypes = []
+
+LIBPCP.pmNewZone.restype = c_int
+LIBPCP.pmNewZone.argtypes = [c_char_p]
+
+LIBPCP.pmUseZone.restype = c_int
+LIBPCP.pmUseZone.argtypes = [c_int]
+
+LIBPCP.pmWhichZone.restype = c_int
+LIBPCP.pmWhichZone.argtypes = [POINTER(c_char_p)]
+
+LIBPCP.pmLocaltime.restype = POINTER(tm)
+LIBPCP.pmLocaltime.argtypes = [POINTER(c_long), POINTER(tm)]
+
+LIBPCP.pmCtime.restype = c_char_p
+LIBPCP.pmCtime.argtypes = [POINTER(c_long), c_char_p]
+
+
+##
+# PMAPI Metrics Services
+
+LIBPCP.pmFetch.restype = c_int
+LIBPCP.pmFetch.argtypes = [c_int, POINTER(c_uint), POINTER(POINTER(pmResult))]
+
+LIBPCP.pmFreeResult.restype = None
+LIBPCP.pmFreeResult.argtypes = [POINTER(pmResult)]
+
+LIBPCP.pmStore.restype = c_int
+LIBPCP.pmStore.argtypes = [POINTER(pmResult)]
+
+
+##
+# PMAPI Archive-Specific Services
+
+LIBPCP.pmGetArchiveLabel.restype = c_int
+LIBPCP.pmGetArchiveLabel.argtypes = [POINTER(pmLogLabel)]
+
+LIBPCP.pmGetArchiveEnd.restype = c_int
+LIBPCP.pmGetArchiveEnd.argtypes = [POINTER(timeval)]
+
+LIBPCP.pmGetInDomArchive.restype = c_int
+LIBPCP.pmGetInDomArchive.argtypes = [
+ c_uint, POINTER(POINTER(c_int)), POINTER(POINTER(c_char_p)) ]
+
+LIBPCP.pmLookupInDomArchive.restype = c_int
+LIBPCP.pmLookupInDom.argtypes = [c_uint, c_char_p]
+LIBPCP.pmLookupInDomArchive.argtypes = [pmInDom, c_char_p]
+
+LIBPCP.pmNameInDomArchive.restype = c_int
+LIBPCP.pmNameInDomArchive.argtypes = [pmInDom, c_int]
+
+LIBPCP.pmFetchArchive.restype = c_int
+LIBPCP.pmFetchArchive.argtypes = [POINTER(POINTER(pmResult))]
+
+
+##
+# PMAPI Ancilliary Support Services
+
+
+LIBPCP.pmGetConfig.restype = c_char_p
+LIBPCP.pmGetConfig.argtypes = [c_char_p]
+
+LIBPCP.pmErrStr_r.restype = c_char_p
+LIBPCP.pmErrStr_r.argtypes = [c_int, c_char_p, c_int]
+
+LIBPCP.pmExtractValue.restype = c_int
+LIBPCP.pmExtractValue.argtypes = [
+ c_int, POINTER(pmValue), c_int, POINTER(pmAtomValue), c_int ]
+
+LIBPCP.pmConvScale.restype = c_int
+LIBPCP.pmConvScale.argtypes = [
+ c_int, POINTER(pmAtomValue), POINTER(pmUnits), POINTER(pmAtomValue),
+ POINTER(pmUnits)]
+
+LIBPCP.pmUnitsStr_r.restype = c_char_p
+LIBPCP.pmUnitsStr_r.argtypes = [POINTER(pmUnits), c_char_p, c_int]
+
+LIBPCP.pmNumberStr_r.restype = c_char_p
+LIBPCP.pmNumberStr_r.argtypes = [c_double, c_char_p, c_int]
+
+LIBPCP.pmIDStr_r.restype = c_char_p
+LIBPCP.pmIDStr_r.argtypes = [c_uint, c_char_p, c_int]
+
+LIBPCP.pmInDomStr_r.restype = c_char_p
+LIBPCP.pmInDomStr_r.argtypes = [c_uint, c_char_p, c_int]
+
+LIBPCP.pmTypeStr_r.restype = c_char_p
+LIBPCP.pmTypeStr_r.argtypes = [c_int, c_char_p, c_int]
+
+LIBPCP.pmAtomStr_r.restype = c_char_p
+LIBPCP.pmAtomStr_r.argtypes = [POINTER(pmAtomValue), c_int, c_char_p, c_int]
+
+LIBPCP.pmPrintValue.restype = None
+LIBPCP.pmPrintValue.argtypes = [c_void_p, c_int, c_int, POINTER(pmValue), c_int]
+
+LIBPCP.pmParseInterval.restype = c_int
+LIBPCP.pmParseInterval.argtypes = [c_char_p, POINTER(timeval),
+ POINTER(c_char_p)]
+
+LIBPCP.pmParseMetricSpec.restype = c_int
+LIBPCP.pmParseMetricSpec.argtypes = [c_char_p, c_int, c_char_p,
+ POINTER(POINTER(pmMetricSpec)), POINTER(c_char_p)]
+
+LIBPCP.pmflush.restype = c_int
+LIBPCP.pmflush.argtypes = []
+
+LIBPCP.pmprintf.restype = c_int
+LIBPCP.pmprintf.argtypes = [c_char_p]
+
+LIBPCP.pmSortInstances.restype = None
+LIBPCP.pmSortInstances.argtypes = [POINTER(pmResult)]
+
+ctypes.pythonapi.PyFile_AsFile.restype = ctypes.c_void_p
+ctypes.pythonapi.PyFile_AsFile.argtypes = [ctypes.py_object]
+
+
+##############################################################################
+#
+# class pmOptions
+#
+# This class wraps the PMAPI pmGetOptions functionality and can be used
+# to assist with automatic construction of a PMAPI context based on the
+# command line options used.
+#
+
+class pmOptions(object):
+ """ Command line option parsing for short and long form arguments
+ Passed into pmGetOptions, pmGetContextOptions, pmUsageMessage.
+ """
+ ##
+ # property read methods
+
+ def _R_mode(self):
+ return self._mode
+ def _R_delta(self):
+ return self._delta
+
+ ##
+ # property definitions
+
+ mode = property(_R_mode, None, None, None)
+ delta = property(_R_delta, None, None, None)
+
+ ##
+ # creation and destruction
+
+ def __init__(self, short_options = None, short_usage = None, flags = 0):
+ c_api.pmResetAllOptions()
+ if (short_options != None):
+ c_api.pmSetShortOptions(short_options)
+ if short_usage != None:
+ c_api.pmSetShortUsage(short_usage)
+ if flags != 0:
+ c_api.pmSetOptionFlags(flags)
+ else: # good default for scripts - always evaluating log bounds
+ c_api.pmSetOptionFlags(c_api.PM_OPTFLAG_BOUNDARIES)
+ self._delta = 1 # default archive pmSetMode delta
+ self._mode = c_api.PM_MODE_INTERP # default pmSetMode access mode
+
+ def __del__(self):
+ c_api.pmResetAllOptions()
+
+ ##
+ # general command line option access and manipulation
+
+ def pmGetOptionFlags(self):
+ return c_api.pmGetOptionFlags()
+
+ def pmSetOptionFlags(self, flags):
+ return c_api.pmSetOptionFlags(flags)
+
+ def pmGetOptionErrors(self):
+ return c_api.pmGetOptionErrors()
+
+ def pmSetOptionErrors(self):
+ errors = c_api.pmGetOptionErrors() + 1
+ return c_api.pmSetOptionErrors(errors)
+
+ def pmSetShortUsage(self, short_usage):
+ return c_api.pmSetShortUsage(short_usage)
+
+ def pmSetShortOptions(self, short_options):
+ return c_api.pmSetShortOptions(short_options)
+
+ def pmSetOptionSamples(self, count):
+ """ Set sample count (converts string to integer) """
+ return c_api.pmSetOptionSamples(count)
+
+ def pmSetOptionInterval(self, interval):
+ """ Set sampling interval (pmParseInterval string) """
+ return c_api.pmSetOptionInterval(interval)
+
+ def pmNonOptionsFromList(self, argv):
+ return c_api.pmGetNonOptionsFromList(argv)
+
+ def pmSetCallbackObject(self, them):
+ """ When options are being parsed from within an object, the
+ caller will want the "self" of the other object ("them")
+ passed as the first parameter to the callback function.
+ """
+ return c_api.pmSetCallbackObject(them)
+
+ def pmSetOptionCallback(self, func):
+ """ Handle individual command line options, outside of the PCP
+ "standard" set (or overridden).
+
+ For every non-standard or overridden option, this callback
+ will be called with the short option character (as an int)
+ or zero for long-option only, and the usual getopts global
+ state (optind, opterr, optopt, optarg, and index - all int
+ except optarg which is a str).
+ """
+ return c_api.pmSetOptionCallback(func)
+
+ def pmSetOverrideCallback(self, func):
+ """ Allow a "standard" PCP option to be overridden.
+
+ For every option parsed, this callback is called and it may
+ return zero, meaning continue with processing the option in
+ the standard way, or non-zero, meaning the caller wishes to
+ override and interpret the option differently.
+ Callback input: int, output: int
+ """
+ return c_api.pmSetOverrideCallback(func)
+
+ def pmSetLongOption(self, long_opt, has_arg, short_opt, argname, message):
+ """ Add long option into the set of supported long options
+
+ Pass in the option name (str), whether it takes an argument (int),
+ its short option form (int), and two usage message hints (argname
+ (str) and message (str) - see pmGetOptions(3) for details).
+ """
+ return c_api.pmSetLongOption(long_opt, has_arg, short_opt, argname, message)
+
+ def pmSetLongOptionHeader(self, heading):
+ """ Add a new section heading into the long option usage message """
+ return c_api.pmSetLongOptionHeader(heading)
+
+ def pmSetLongOptionText(self, text):
+ """ Add some descriptive text into the long option usage message """
+ return c_api.pmSetLongOptionText(text)
+
+ def pmSetLongOptionAlign(self):
+ """ Add support for -A/--align into PMAPI monitor tool """
+ return c_api.pmSetLongOptionAlign()
+
+ def pmSetLongOptionArchive(self):
+ """ Add support for -a/--archive into PMAPI monitor tool """
+ return c_api.pmSetLongOptionArchive()
+
+ def pmSetLongOptionDebug(self):
+ """ Add support for -D/--debug into PMAPI monitor tool """
+ return c_api.pmSetLongOptionDebug()
+
+ def pmSetLongOptionGuiMode(self):
+ """ Add support for -g/--guimode into PMAPI monitor tool """
+ return c_api.pmSetLongOptionGuiMode()
+
+ def pmSetLongOptionHost(self):
+ """ Add support for -h/--host into PMAPI monitor tool """
+ return c_api.pmSetLongOptionHost()
+
+ def pmSetLongOptionHostsFile(self):
+ """ Add support for -H/--hostsfile into PMAPI monitor tool """
+ return c_api.pmSetLongOptionHostsFile()
+
+ def pmSetLongOptionSpecLocal(self):
+ """ Add support for -K/--spec-local into PMAPI monitor tool """
+ return c_api.pmSetLongOptionSpecLocal()
+
+ def pmSetLongOptionLocalPMDA(self):
+ """ Add support for -L/--local-PMDA into PMAPI monitor tool """
+ return c_api.pmSetLongOptionLocalPMDA()
+
+ def pmSetLongOptionOrigin(self):
+ """ Add support for -O/--origin into PMAPI monitor tool """
+ return c_api.pmSetLongOptionOrigin()
+
+ def pmSetLongOptionGuiPort(self):
+ """ Add support for -p/--guiport into PMAPI monitor tool """
+ return c_api.pmSetLongOptionGuiPort()
+
+ def pmSetLongOptionStart(self):
+ """ Add support for -S/--start into PMAPI monitor tool """
+ return c_api.pmSetLongOptionStart()
+
+ def pmSetLongOptionSamples(self):
+ """ Add support for -s/--samples into PMAPI monitor tool """
+ return c_api.pmSetLongOptionSamples()
+
+ def pmSetLongOptionFinish(self):
+ """ Add support for -T/--finish into PMAPI monitor tool """
+ return c_api.pmSetLongOptionFinish()
+
+ def pmSetLongOptionInterval(self):
+ """ Add support for -t/--interval into PMAPI monitor tool """
+ return c_api.pmSetLongOptionInterval()
+
+ def pmSetLongOptionVersion(self):
+ """ Add support for -V/--version into PMAPI monitor tool """
+ return c_api.pmSetLongOptionVersion()
+
+ def pmSetLongOptionTimeZone(self):
+ """ Add support for -Z/--timezone into PMAPI monitor tool """
+ return c_api.pmSetLongOptionTimeZone()
+
+ def pmSetLongOptionHostZone(self):
+ """ Add support for -z/--hostzone into PMAPI monitor tool """
+ return c_api.pmSetLongOptionHostZone()
+
+ def pmSetLongOptionHelp(self):
+ """ Add support for -?/--help into PMAPI monitor tool """
+ return c_api.pmSetLongOptionHelp()
+
+ def pmSetLongOptionArchiveList(self):
+ """ Add support for --archive-list into PMAPI monitor tool """
+ return c_api.pmSetLongOptionArchiveList()
+
+ def pmSetLongOptionArchiveFolio(self):
+ """ Add support for --archive-folio into PMAPI monitor tool """
+ return c_api.pmSetLongOptionArchiveFolio()
+
+ def pmSetLongOptionHostList(self):
+ """ Add support for --host-list into PMAPI monitor tool """
+ return c_api.pmSetLongOptionHostList()
+
+ def pmGetOptionContext(self): # int (typed)
+ return c_api.pmGetOptionContext()
+
+ def pmGetOptionHosts(self): # str list
+ return c_api.pmGetOptionHosts()
+
+ def pmGetOptionArchives(self): # str list
+ return c_api.pmGetOptionArchives()
+
+ def pmGetOptionAlignment(self): # timeval
+ sec = c_api.pmGetOptionAlignment_sec()
+ if sec == None:
+ return None
+ return timeval(sec, c_api.pmGetOptionAlignment_sec())
+
+ def pmGetOptionStart(self): # timeval
+ sec = c_api.pmGetOptionStart_sec()
+ if sec == None:
+ return None
+ return timeval(sec, c_api.pmGetOptionStart_usec())
+
+ def pmGetOptionFinishOptarg(self): # string
+ return c_api.pmGetOptionFinish_optarg()
+
+ def pmGetOptionFinish(self): # timeval
+ sec = c_api.pmGetOptionFinish_sec()
+ if sec == None:
+ return None
+ return timeval(sec, c_api.pmGetOptionFinish_usec())
+
+ def pmGetOptionOrigin(self): # timeval
+ sec = c_api.pmGetOptionOrigin_sec()
+ if sec == None:
+ return None
+ return timeval(sec, c_api.pmGetOptionOrigin_usec())
+
+ def pmGetOptionInterval(self): # timeval
+ sec = c_api.pmGetOptionInterval_sec()
+ if sec == None:
+ return None
+ return timeval(sec, c_api.pmGetOptionInterval_usec())
+
+ def pmGetOptionSamples(self): # int
+ return c_api.pmGetOptionSamples()
+
+ def pmGetOptionTimezone(self): # str
+ return c_api.pmGetOptionTimezone()
+
+ def pmSetOptionArchiveList(self, archives): # str
+ return c_api.pmSetOptionArchiveList(archives)
+
+ def pmSetOptionArchiveFolio(self, folio): # str
+ return c_api.pmSetOptionArchiveFolio(folio)
+
+ def pmSetOptionHostList(self, hosts): # str
+ return c_api.pmSetOptionHostList(hosts)
+
+
+##############################################################################
+#
+# class pmContext
+#
+# This class wraps the PMAPI library functions
+#
+
+class pmContext(object):
+ """Defines a metrics source context (e.g. host, archive, etc) to operate on
+
+ pmContext(c_api.PM_CONTEXT_HOST,"local:")
+ pmContext(c_api.PM_CONTEXT_ARCHIVE,"FILENAME")
+
+ This object defines a PMAPI context, and its methods wrap calls to PMAPI
+ library functions. Detailled information about those C library functions
+ can be found in the following document.
+
+ SGI Document: 007-3434-005
+ Performance Co-Pilot Programmer's Guide
+ Section 3.7 - PMAPI Procedural Interface, pp. 67
+
+ Detailed information about the underlying data structures can be found
+ in the same document.
+
+ Section 3.4 - Performance Metric Descriptions, pp. 59
+ Section 3.5 - Performance Metric Values, pp. 62
+ """
+
+ ##
+ # class attributes
+
+ ##
+ # property read methods
+
+ def _R_type(self):
+ return self._type
+ def _R_target(self):
+ return self._target
+ def _R_ctx(self):
+ return self._ctx
+
+ ##
+ # property definitions
+
+ type = property(_R_type, None, None, None)
+ target = property(_R_target, None, None, None)
+ ctx = property(_R_ctx, None, None, None)
+
+ ##
+ # creation and destruction
+
+ def __init__(self, typed = c_api.PM_CONTEXT_HOST, target = "local:"):
+ self._type = typed # the context type
+ self._target = target # the context target
+ self._ctx = c_api.PM_ERR_NOCONTEXT # init'd pre-connect
+ self._ctx = LIBPCP.pmNewContext(typed, target) # the context handle
+ if self._ctx < 0:
+ raise pmErr(self._ctx, [target])
+
+ def __del__(self):
+ if LIBPCP and self._ctx != c_api.PM_ERR_NOCONTEXT:
+ LIBPCP.pmDestroyContext(self._ctx)
+
+ @classmethod
+ def fromOptions(builder, options, argv, typed = 0, index = 0):
+ """ Helper interface, simple PCP monitor argument parsing.
+
+ Take argv list, create a context using pmGetOptions(3)
+ and standard options default values like local: etc
+ based on the contents of the list.
+
+ Caller should have already registered any options of
+ interest using the option family of interfaces, i.e.
+ pmSetShortOptions, pmSetLongOption*, pmSetOptionFlags,
+ pmSetOptionCallback, and pmSetOptionOverrideCallback.
+
+ When the MULTI/MIXED pmGetOptions flags are being used,
+ the typed/index parameters can be used to setup several
+ contexts based on the given command line parameters.
+ """
+ if (typed <= 0):
+ if c_api.pmGetOptionsFromList(argv):
+ raise pmUsageErr
+ typed = options.pmGetOptionContext()
+
+ if typed == c_api.PM_CONTEXT_ARCHIVE:
+ archives = options.pmGetOptionArchives()
+ source = archives[index]
+ elif typed == c_api.PM_CONTEXT_HOST:
+ hosts = options.pmGetOptionHosts()
+ source = hosts[index]
+ elif typed == c_api.PM_CONTEXT_LOCAL:
+ source = None
+ else:
+ typed = c_api.PM_CONTEXT_HOST
+ source = "local:"
+
+ # core work done here - constructs the new pmContext
+ context = builder(typed, source)
+
+ # finish time windows, timezones, archive access mode
+ if c_api.pmSetContextOptions(context.ctx, options.mode, options.delta):
+ raise pmUsageErr
+
+ return context
+
+ ##
+ # PMAPI Name Space Services
+ #
+
+ def pmGetChildren(self, name):
+ """PMAPI - Return names of children of the given PMNS node NAME
+ tuple names = pmGetChildren("kernel")
+ """
+ offspring = POINTER(c_char_p)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetChildren(name, byref(offspring))
+ if status < 0:
+ raise pmErr(status)
+ if status > 0:
+ childL = map(lambda x: str(offspring[x]), range(status))
+ LIBC.free(offspring)
+ else:
+ return None
+ return childL
+
+ def pmGetChildrenStatus(self, name):
+ """PMAPI - Return names and status of children of the given metric NAME
+ (tuple names,tuple status) = pmGetChildrenStatus("kernel")
+ """
+ offspring = POINTER(c_char_p)()
+ childstat = POINTER(c_int)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetChildrenStatus(name,
+ byref(offspring), byref(childstat))
+ if status < 0:
+ raise pmErr(status)
+ if status > 0:
+ childL = map(lambda x: str(offspring[x]), range(status))
+ statL = map(lambda x: int(childstat[x]), range(status))
+ LIBC.free(offspring)
+ LIBC.free(childstat)
+ else:
+ return None, None
+ return childL, statL
+
+ def pmGetPMNSLocation(self):
+ """PMAPI - Return the namespace location type
+ loc = pmGetPMNSLocation()
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetPMNSLocation()
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmLoadNameSpace(self, filename):
+ """PMAPI - Load a local namespace
+ status = pmLoadNameSpace("filename")
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmLoadNameSpace(filename)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmLookupName(self, nameA, relaxed = 0):
+ """PMAPI - Lookup pmIDs from a list of metric names nameA
+
+ c_uint pmid [] = pmLookupName("MetricName")
+ c_uint pmid [] = pmLookupName(("MetricName1", "MetricName2", ...))
+ """
+ if type(nameA) == type(""):
+ n = 1
+ else:
+ n = len(nameA)
+ names = (c_char_p * n)()
+ if type(nameA) == type(""):
+ names[0] = c_char_p(nameA)
+ else:
+ for i in xrange(len(nameA)):
+ names[i] = c_char_p(nameA[i])
+
+ pmidA = (c_uint * n)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ LIBPCP.pmLookupName.argtypes = [c_int, (c_char_p * n), POINTER(c_uint)]
+ status = LIBPCP.pmLookupName(n, names, pmidA)
+ if status < 0:
+ raise pmErr(status, pmidA)
+ elif relaxed == 0 and status != n:
+ badL = [name for (name, pmid) in zip(nameA, pmidA) \
+ if pmid == c_api.PM_ID_NULL]
+ raise pmErr(c_api.PM_ERR_NAME, badL)
+ return pmidA
+
+ def pmNameAll(self, pmid):
+ """PMAPI - Return list of all metric names having this identical PMID
+ tuple names = pmNameAll(metric_id)
+ """
+ nameA_p = POINTER(c_char_p)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmNameAll(pmid, byref(nameA_p))
+ if status < 0:
+ raise pmErr(status)
+ nameL = map(lambda x: str(nameA_p[x]), range(status))
+ LIBC.free( nameA_p )
+ return nameL
+
+ def pmNameID(self, pmid):
+ """PMAPI - Return a metric name from a PMID
+ name = pmNameID(self.metric_id)
+ """
+ k = c_char_p()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmNameID(pmid, byref(k))
+ if status < 0:
+ raise pmErr(status)
+ name = k.value
+ LIBC.free( k )
+ return name
+
+ def pmTraversePMNS(self, name, callback):
+ """PMAPI - Scan namespace, depth first, run CALLBACK at each node
+ status = pmTraversePMNS("kernel", traverse_callback)
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ cb = traverseCB_type(callback)
+ status = LIBPCP.pmTraversePMNS(name, cb)
+ if status < 0:
+ raise pmErr(status)
+
+ def pmUnLoadNameSpace(self):
+ """PMAPI - Unloads a local PMNS, if one was previously loaded
+ pm.pmUnLoadNameSpace("NameSpace")
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmUnloadNameSpace()
+ if status < 0:
+ raise pmErr(status)
+
+ def pmRegisterDerived(self, name, expr):
+ """PMAPI - Register a derived metric name and definition
+ pm.pmRegisterDerived("MetricName", "MetricName Expression")
+ """
+ status = LIBPCP.pmRegisterDerived(name, expr)
+ if status != 0:
+ raise pmErr(status)
+ status = LIBPCP.pmReconnectContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+
+ def pmLoadDerivedConfig(self, f):
+ """PMAPI - Register derived metric names and definitions from a file
+ pm.pmLoadDerivedConfig("FileName")
+ """
+ status = LIBPCP.pmLoadDerivedConfig(f)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmReconnectContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+
+ @staticmethod
+ def pmDerivedErrStr():
+ """PMAPI - Return an error message if the pmRegisterDerived metric
+ definition cannot be parsed
+ pm.pmRegisterDerived()
+ """
+ return str(LIBPCP.pmDerivedErrStr())
+
+ ##
+ # PMAPI Metrics Description Services
+
+ def pmLookupDesc(self, pmid_p):
+
+ """PMAPI - Lookup a metric description structure from a pmID
+
+ pmDesc* pmdesc = pmLookupDesc(c_uint pmid)
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+
+ descbuf = create_string_buffer(sizeof(pmDesc))
+ desc = cast(descbuf, POINTER(pmDesc))
+ pmid = c_uint(pmid_p)
+ status = LIBPCP.pmLookupDesc(pmid, desc)
+ if status < 0:
+ raise pmErr(status)
+ return desc
+
+ def pmLookupDescs(self, pmids_p):
+
+ """PMAPI - Lookup metric description structures from pmIDs
+
+ (pmDesc* pmdesc)[] = pmLookupDesc(c_uint pmid[N])
+ (pmDesc* pmdesc)[] = pmLookupDesc(c_uint pmid)
+ """
+ if type(pmids_p) == type(int(0)) or type(pmids_p) == type(long(0)):
+ n = 1
+ else:
+ n = len(pmids_p)
+
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+
+ desc = (POINTER(pmDesc) * n)()
+
+ for i in xrange(n):
+ descbuf = create_string_buffer(sizeof(pmDesc))
+ desc[i] = cast(descbuf, POINTER(pmDesc))
+ if type(pmids_p) == type(int()) or type(pmids_p) == type(long()):
+ pmids = c_uint(pmids_p)
+ else:
+ pmids = c_uint(pmids_p[i])
+
+ status = LIBPCP.pmLookupDesc(pmids, desc[i])
+ if status < 0:
+ raise pmErr(status)
+ return desc
+
+ def pmLookupInDomText(self, pmdesc, kind = c_api.PM_TEXT_ONELINE):
+ """PMAPI - Lookup the description of a metric's instance domain
+
+ "instance" = pmLookupInDomText(pmDesc pmdesc)
+ """
+ buf = c_char_p()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+
+ status = LIBPCP.pmLookupInDomText(get_indom(pmdesc), kind, byref(buf))
+ if status < 0:
+ raise pmErr(status)
+ text = str(buf.value)
+ LIBC.free(buf)
+ return text
+
+ def pmLookupText(self, pmid, kind = c_api.PM_TEXT_ONELINE):
+ """PMAPI - Lookup the description of a metric from its pmID
+ "desc" = pmLookupText(pmid)
+ """
+ buf = c_char_p()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmLookupText(pmid, kind, byref(buf))
+ if status < 0:
+ raise pmErr(status)
+ text = buf.value
+ LIBC.free(buf)
+ return text
+
+ ##
+ # PMAPI Instance Domain Services
+
+ def pmGetInDom(self, pmdescp):
+ """PMAPI - Lookup the list of instances from an instance domain PMDESCP
+
+ ([instance1, instance2...] [name1, name2...]) pmGetInDom(pmDesc pmdesc)
+ """
+ instA_p = POINTER(c_int)()
+ nameA_p = POINTER(c_char_p)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetInDom(get_indom(pmdescp),
+ byref(instA_p), byref(nameA_p))
+ if status < 0:
+ raise pmErr(status)
+ if status > 0:
+ nameL = map(lambda x: str(nameA_p[x]), range(status))
+ instL = map(lambda x: int(instA_p[x]), range(status))
+ LIBC.free(instA_p)
+ LIBC.free(nameA_p)
+ else:
+ instL = None
+ nameL = None
+ return instL, nameL
+
+ def pmLookupInDom(self, pmdesc, name):
+ """PMAPI - Lookup the instance id with the given NAME in the indom
+
+ c_uint instid = pmLookupInDom(pmDesc pmdesc, "Instance")
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmLookupInDom(get_indom(pmdesc), name)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmNameInDom(self, pmdesc, instval):
+ """PMAPI - Lookup the text name of an instance in an instance domain
+
+ "string" = pmNameInDom(pmDesc pmdesc, c_uint instid)
+ """
+ if instval == c_api.PM_IN_NULL:
+ return "PM_IN_NULL"
+ name_p = c_char_p()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmNameInDom(get_indom(pmdesc), instval, byref(name_p))
+ if status < 0:
+ raise pmErr(status)
+ outName = str(name_p.value)
+ LIBC.free(name_p)
+ return outName
+
+ ##
+ # PMAPI Context Services
+
+ def pmNewContext(self, typed, name):
+ """PMAPI - NOOP - Establish a new PMAPI context (done in constructor)
+
+ This is unimplemented. A new context is established when a pmContext
+ object is created.
+ """
+ pass
+
+ def pmDestroyContext(self, handle):
+ """PMAPI - NOOP - Destroy a PMAPI context (done in destructor)
+
+ This is unimplemented. The context is destroyed when the pmContext
+ object is destroyed.
+ """
+ pass
+
+ def pmDupContext(self):
+ """PMAPI - Duplicate the current PMAPI Context
+
+ This supports copying a pmContext object
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmDupContext()
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmUseContext(self, handle):
+ """PMAPI - NOOP - Set the PMAPI context to that identified by handle
+
+ This is unimplemented. Context changes are handled by the individual
+ methods in a pmContext class instance.
+ """
+ pass
+
+ @staticmethod
+ def pmWhichContext():
+ """PMAPI - Returns the handle of the current PMAPI context
+ context = pmWhichContext()
+ """
+ status = LIBPCP.pmWhichContext()
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmAddProfile(self, pmdesc, instL):
+ """PMAPI - add instances to list that will be collected from indom
+
+ status = pmAddProfile(pmDesc pmdesc, c_uint instid)
+ """
+ if type(instL) == type(0):
+ numinst = 1
+ instA = (c_int * numinst)()
+ instA[0] = instL
+ elif instL == None or len(instL) == 0:
+ numinst = 0
+ instA = POINTER(c_int)()
+ else:
+ numinst = len(instL)
+ instA = (c_int * numinst)()
+ for index, value in enumerate(instL):
+ instA[index] = value
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmAddProfile(get_indom(pmdesc), numinst, instA)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmDelProfile(self, pmdesc, instL):
+ """PMAPI - delete instances from list to be collected from indom
+
+ status = pmDelProfile(pmDesc pmdesc, c_uint inst)
+ status = pmDelProfile(pmDesc pmdesc, [c_uint inst])
+ """
+ if instL == None or len(instL) == 0:
+ numinst = 0
+ instA = POINTER(c_int)()
+ else:
+ numinst = len(instL)
+ instA = (c_int * numinst)()
+ for index, value in enumerate(instL):
+ instA[index] = value
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmDelProfile(get_indom(pmdesc), numinst, instA)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmSetMode(self, mode, timeVal, delta):
+ """PMAPI - set interpolation mode for reading archive files
+ code = pmSetMode(c_api.PM_MODE_INTERP, timeval, 0)
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmSetMode(mode, pointer(timeVal), delta)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmReconnectContext(self):
+ """PMAPI - Reestablish the context connection
+
+ Unlike the underlying PMAPI function, this method takes no parameter.
+ This method simply attempts to reestablish the the context belonging
+ to its pmContext instance object.
+ """
+ status = LIBPCP.pmReconnectContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmGetContextHostName(self):
+ """PMAPI - Lookup the hostname for the given context
+
+ This method simply returns the hostname for the context belonging to
+ its pmContext instance object.
+
+ "hostname" = pmGetContextHostName()
+ """
+ buflen = c_api.PM_LOG_MAXHOSTLEN
+ buffer = ctypes.create_string_buffer(buflen)
+ return str(LIBPCP.pmGetContextHostName_r(self.ctx, buffer, buflen))
+
+ ##
+ # PMAPI Timezone Services
+
+ def pmNewContextZone(self):
+ """PMAPI - Query and set the current reporting timezone """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmNewContextZone()
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ @staticmethod
+ def pmNewZone(tz):
+ """PMAPI - Create new zone handle and set reporting timezone """
+ status = LIBPCP.pmNewZone(tz)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ @staticmethod
+ def pmUseZone(tz_handle):
+ """PMAPI - Sets the current reporting timezone """
+ status = LIBPCP.pmUseZone(tz_handle)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ @staticmethod
+ def pmWhichZone():
+ """PMAPI - Query the current reporting timezone """
+ tz_p = c_char_p()
+ status = LIBPCP.pmWhichZone(byref(tz_p))
+ if status < 0:
+ raise pmErr(status)
+ tz = str(tz_p.value)
+ LIBC.free(tz_p)
+ return tz
+
+ def pmLocaltime(self, seconds):
+ """PMAPI - convert the date and time for a reporting timezone """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ result = (tm)()
+ timetp = c_long(long(seconds))
+ LIBPCP.pmLocaltime(byref(timetp), byref(result))
+ return result
+
+ def pmCtime(self, seconds):
+ """PMAPI - format the date and time for a reporting timezone """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ result = ctypes.create_string_buffer(32)
+ timetp = c_long(long(seconds))
+ LIBPCP.pmCtime(byref(timetp), result)
+ return str(result.value)
+
+ ##
+ # PMAPI Metrics Services
+
+ def pmFetch(self, pmidA):
+ """PMAPI - Fetch pmResult from the target source
+
+ pmResult* pmresult = pmFetch(c_uint pmid[])
+ """
+ result_p = POINTER(pmResult)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmFetch(len(pmidA), pmidA, byref(result_p))
+ if status < 0:
+ raise pmErr(status)
+ return result_p
+
+ @staticmethod
+ def pmFreeResult(result_p):
+ """PMAPI - Free a result previously allocated by pmFetch
+ pmFreeResult(pmResult* pmresult)
+ """
+ LIBPCP.pmFreeResult(result_p)
+
+ def pmStore(self, result):
+ """PMAPI - Set values on target source, inverse of pmFetch
+ pmresult = pmStore(pmResult* pmresult)
+ """
+ LIBPCP.pmStore.argtypes = [(type(result))]
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmStore(result)
+ if status < 0:
+ raise pmErr(status)
+ return result
+
+
+ ##
+ # PMAPI Archive-Specific Services
+
+ def pmGetArchiveLabel(self):
+ """PMAPI - Get the label record from the archive
+ loglabel = pmGetArchiveLabel()
+ """
+ loglabel = pmLogLabel()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetArchiveLabel(byref(loglabel))
+ if status < 0:
+ raise pmErr(status)
+ return loglabel
+
+ def pmGetArchiveEnd(self):
+ """PMAPI - Get the last recorded timestamp from the archive
+ """
+ tvp = timeval()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmGetArchiveEnd(byref(tvp))
+ if status < 0:
+ raise pmErr(status)
+ return tvp
+
+ def pmGetInDomArchive(self, pmdescp):
+ """PMAPI - Get the instance IDs and names for an instance domain
+
+ ((instance1, instance2...) (name1, name2...)) pmGetInDom(pmDesc pmdesc)
+ """
+ instA_p = POINTER(c_int)()
+ nameA_p = POINTER(c_char_p)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ indom = get_indom(pmdescp)
+ status = LIBPCP.pmGetInDomArchive(indom, byref(instA_p), byref(nameA_p))
+ if status < 0:
+ raise pmErr(status)
+ if status > 0:
+ nameL = map(lambda x: str(nameA_p[x]), range(status))
+ instL = map(lambda x: int(instA_p[x]), range(status))
+ LIBC.free(instA_p)
+ LIBC.free(nameA_p)
+ else:
+ instL = None
+ nameL = None
+ return instL, nameL
+
+ def pmLookupInDomArchive(self, pmdesc, name):
+ """PMAPI - Lookup the instance id with the given name in the indom
+
+ c_uint instid = pmLookupInDomArchive(pmDesc pmdesc, "Instance")
+ """
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmLookupInDomArchive(get_indom(pmdesc), name)
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ def pmNameInDomArchive(self, pmdesc, inst):
+ """PMAPI - Lookup the text name of an instance in an instance domain
+
+ "string" = pmNameInDomArchive(pmDesc pmdesc, c_uint instid)
+ """
+ name_p = c_char_p()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ indom = get_indom(pmdesc)
+ status = LIBPCP.pmNameInDomArchive(indom, inst, byref(name_p))
+ if status < 0:
+ raise pmErr(status)
+ outName = str(name_p.value)
+ LIBC.free(name_p)
+ return outName
+
+ def pmFetchArchive(self):
+ """PMAPI - Fetch measurements from the target source
+
+ pmResult* pmresult = pmFetch()
+ """
+ result_p = POINTER(pmResult)()
+ status = LIBPCP.pmUseContext(self.ctx)
+ if status < 0:
+ raise pmErr(status)
+ status = LIBPCP.pmFetchArchive(byref(result_p))
+ if status < 0:
+ raise pmErr(status)
+ return result_p
+
+
+ ##
+ # PMAPI Ancilliary Support Services
+
+ @staticmethod
+ def pmGetConfig(variable):
+ """PMAPI - Return value from environment or pcp config file """
+ return str(LIBPCP.pmGetConfig(variable))
+
+ @staticmethod
+ def pmErrStr(code):
+ """PMAPI - Return value from environment or pcp config file """
+ errstr = ctypes.create_string_buffer(c_api.PM_MAXERRMSGLEN)
+ return str(LIBPCP.pmErrStr_r(code, errstr, c_api.PM_MAXERRMSGLEN))
+
+ @staticmethod
+ def pmExtractValue(valfmt, vlist, intype, outtype):
+ """PMAPI - Extract a value from a pmValue struct and convert its type
+
+ pmAtomValue = pmExtractValue(results.contents.get_valfmt(i),
+ results.contents.get_vlist(i, 0),
+ descs[i].contents.type,
+ c_api.PM_TYPE_FLOAT)
+ """
+ outAtom = pmAtomValue()
+ status = LIBPCP.pmExtractValue(valfmt, vlist, intype,
+ byref(outAtom), outtype)
+ if status < 0:
+ raise pmErr(status)
+
+ if outtype == c_api.PM_TYPE_STRING:
+ # Get pointer to C string
+ c_str = c_char_p()
+ memmove(byref(c_str), addressof(outAtom) + pmAtomValue.cp.offset,
+ sizeof(c_char_p))
+ # Convert to a python string and have result point to it
+ outAtom.cp = outAtom.cp
+ # Free the C string
+ LIBC.free(c_str)
+ return outAtom
+
+ @staticmethod
+ def pmConvScale(inType, inAtom, desc, metric_idx, outUnits):
+ """PMAPI - Convert a value to a different scale
+
+ pmAtomValue = pmConvScale(c_api.PM_TYPE_FLOAT, pmAtomValue,
+ pmDesc*, 3, c_api.PM_SPACE_MBYTE)
+ """
+ if type(outUnits) == type(int()):
+ pmunits = pmUnits()
+ pmunits.dimSpace = 1
+ pmunits.scaleSpace = outUnits
+ else:
+ pmunits = outUnits
+ outAtom = pmAtomValue()
+ status = LIBPCP.pmConvScale(inType, byref(inAtom),
+ byref(desc[metric_idx].contents.units),
+ byref(outAtom), byref(pmunits))
+ if status < 0:
+ raise pmErr(status)
+ return outAtom
+
+ @staticmethod
+ def pmUnitsStr(units):
+ """PMAPI - Convert units struct to a readable string """
+ unitstr = ctypes.create_string_buffer(64)
+ return str(LIBPCP.pmUnitsStr_r(units, unitstr, 64))
+
+ @staticmethod
+ def pmNumberStr(value):
+ """PMAPI - Convert double value to fixed-width string """
+ numstr = ctypes.create_string_buffer(8)
+ return str(LIBPCP.pmNumberStr_r(value, numstr, 8))
+
+ @staticmethod
+ def pmIDStr(pmid):
+ """PMAPI - Convert a pmID to a readable string """
+ pmidstr = ctypes.create_string_buffer(32)
+ return str(LIBPCP.pmIDStr_r(pmid, pmidstr, 32))
+
+ @staticmethod
+ def pmInDomStr(pmdescp):
+ """PMAPI - Convert an instance domain ID to a readable string
+ "indom" = pmGetInDom(pmDesc pmdesc)
+ """
+ indomstr = ctypes.create_string_buffer(32)
+ return str(LIBPCP.pmInDomStr_r(get_indom(pmdescp), indomstr, 32))
+
+ @staticmethod
+ def pmTypeStr(typed):
+ """PMAPI - Convert a performance metric type to a readable string
+ "type" = pmTypeStr(c_api.PM_TYPE_FLOAT)
+ """
+ typestr = ctypes.create_string_buffer(32)
+ return str(LIBPCP.pmTypeStr_r(typed, typestr, 32))
+
+ @staticmethod
+ def pmAtomStr(atom, typed):
+ """PMAPI - Convert a value atom to a readable string
+ "value" = pmAtomStr(atom, c_api.PM_TYPE_U32)
+ """
+ atomstr = ctypes.create_string_buffer(96)
+ return str(LIBPCP.pmAtomStr(byref(atom), typed, atomstr, 96))
+
+ @staticmethod
+ def pmPrintValue(fileObj, result, ptype, vset_idx, vlist_idx, min_width):
+ """PMAPI - Print the value of a metric
+ pmPrintValue(file, value, pmdesc, vset_index, vlist_index, min_width)
+ """
+ LIBPCP.pmPrintValue(ctypes.pythonapi.PyFile_AsFile(fileObj),
+ c_int(result.contents.vset[vset_idx].contents.valfmt),
+ c_int(ptype.contents.type),
+ byref(result.contents.vset[vset_idx].contents.vlist[vlist_idx]),
+ min_width)
+
+ @staticmethod
+ def pmflush():
+ """PMAPI - flush the internal buffer shared with pmprintf """
+ status = LIBPCP.pmflush()
+ if status < 0:
+ raise pmErr(status)
+ return status
+
+ @staticmethod
+ def pmprintf(fmt, *args):
+ """PMAPI - append message to internal buffer for later printing """
+ status = LIBPCP.pmprintf(fmt, *args)
+ if status < 0:
+ raise pmErr(status)
+
+ @staticmethod
+ def pmSortInstances(result_p):
+ """PMAPI - sort all metric instances in result returned by pmFetch """
+ LIBPCP.pmSortInstances(result_p)
+ return None
+
+ @staticmethod
+ def pmParseInterval(interval):
+ """PMAPI - parse a textual time interval into a timeval struct
+ (timeval_ctype, '') = pmParseInterval("time string")
+ """
+ return (timeval.fromInterval(interval), '')
+
+ @staticmethod
+ def pmParseMetricSpec(string, isarch = 0, source = ''):
+ """PMAPI - parse a textual metric specification into a struct
+ (result, '') = pmParseMetricSpec("hinv.ncpu", 0, "localhost")
+ """
+ return (pmMetricSpec.fromString(string, isarch, source), '')
+
+ @staticmethod
+ def pmtimevalSleep(tvp):
+ """ Delay for a specified amount of time (timeval).
+ Useful for implementing tools that do metric sampling.
+ Single arg is timeval in tuple returned from pmParseInterval().
+ """
+ return tvp.sleep()
+
diff --git a/src/python/pcp/pmcc.py b/src/python/pcp/pmcc.py
new file mode 100644
index 0000000..8189767
--- /dev/null
+++ b/src/python/pcp/pmcc.py
@@ -0,0 +1,620 @@
+""" Convenience Classes building on the base PMAPI extension module """
+#
+# pmcc.py
+#
+# Copyright (C) 2013-2014 Red Hat
+# Copyright (C) 2009-2012 Michael T. Werner
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+from sys import stderr
+from ctypes import c_int, c_uint, c_char_p, cast, POINTER
+from pcp.pmapi import (pmContext, pmResult, pmValueSet, pmValue, pmDesc,
+ pmErr, pmOptions, timeval)
+from cpmapi import (PM_CONTEXT_HOST, PM_CONTEXT_ARCHIVE, PM_INDOM_NULL,
+ PM_IN_NULL, PM_ID_NULL, PM_SEM_COUNTER, PM_ERR_EOL, PM_TYPE_DOUBLE)
+
+
+class MetricCore(object):
+ """
+ Core metric information that can be queried from the PMAPI
+ PMAPI metrics are unique by name, and MetricCores should be also
+ rarely, some PMAPI metrics with different names might have identical PMIDs
+ PMAPI metrics are unique by (name) and by (name,pmid) - _usually_ by (pmid)
+ too.
+ """
+
+ def __init__(self, ctx, name, pmid):
+ self.ctx = ctx
+ self.name = name
+ self.pmid = pmid
+ self.desc = None
+ self.text = None
+ self.help = None
+
+
+class Metric(object):
+ """
+ Additional metric information, such as conversion factors and values
+ several instances of Metric may share a MetricCore instance
+ """
+
+ ##
+ # constructor
+
+ def __init__(self, core):
+ self._core = core # MetricCore
+ self._vset = None # pmValueSet member
+ self._values = None
+ self._prevvset = None
+ self._prevValues = None
+ self._convType = core.desc.contents.type
+ self._convUnits = None
+ self._errorStatus = None
+ self._netValues = None # (instance, name, value)
+ self._netPrevValues = None # (instance, name, value)
+ self._netConvertedValues = None # (instance, name, value)
+
+ ##
+ # core property read methods
+
+ def _R_ctx(self):
+ return self._core.ctx
+ def _R_name(self):
+ return self._core.name
+ def _R_pmid(self):
+ return self._core.pmid
+ def _R_desc(self):
+ return self._core.desc
+ def _R_text(self):
+ return self._core.text
+ def _R_help(self):
+ return self._core.help
+
+ def get_vlist(self, vset, vlist_idx):
+ """ Return the vlist[vlist_idx] of vset[vset_idx] """
+ listptr = cast(vset.contents.vlist, POINTER(pmValue))
+ return listptr[vlist_idx]
+
+ def get_inst(self, vset, vlist_idx):
+ """ Return the inst for vlist[vlist_idx] of vset[vset_idx] """
+ return self.get_vlist(vset, vset_idx, vlist_idx).inst
+
+ def computeValues(self, inValues):
+ """ Extract the value for a singleton or list of instances
+ as a triple (inst, name, val)
+ """
+ vset = inValues
+ ctx = self.ctx
+ instD = ctx.mcGetInstD(self.desc.contents.indom)
+ valL = []
+ for i in range(vset.numval):
+ instval = self.get_vlist(vset, i)
+ try:
+ name = instD[instval.inst]
+ except KeyError:
+ name = ""
+ outAtom = self.ctx.pmExtractValue(
+ vset.valfmt, instval, self.desc.type, self._convType)
+ if self._convUnits:
+ desc = (POINTER(pmDesc) * 1)()
+ desc[0] = self.desc
+ outAtom = self.ctx.pmConvScale(
+ self._convType, outAtom, desc, 0, self._convUnits)
+ value = outAtom.dref(self._convType)
+ valL.append((instval, name, value))
+ return valL
+
+ def _find_previous_instval(self, index, inst, pvset):
+ """ Find a metric instance in the previous resultset """
+ if index <= pvset.numval:
+ pinstval = self.get_vlist(pvset, index)
+ if inst == pinstval.inst:
+ return pinstval
+ for pi in range(pvset.numval):
+ pinstval = self.get_vlist(pvset, pi)
+ if inst == pinstval.inst:
+ return pinstval
+ return None
+
+ def convertValues(self, values, prevValues, delta):
+ """ Extract the value for a singleton or list of instances as a
+ triple (inst, name, val) for COUNTER metrics with the value
+ delta calculation applied (for rate conversion).
+ """
+ if self.desc.sem != PM_SEM_COUNTER:
+ return self.computeValues(values)
+ if prevValues == None:
+ return None
+ pvset = prevValues
+ vset = values
+ ctx = self.ctx
+ instD = ctx.mcGetInstD(self.desc.contents.indom)
+ valL = []
+ for i in range(vset.numval):
+ instval = self.get_vlist(vset, i)
+ pinstval = self._find_previous_instval(i, instval.inst, pvset)
+ if pinstval == None:
+ continue
+ try:
+ name = instD[instval.inst]
+ except KeyError:
+ name = ""
+ outAtom = self.ctx.pmExtractValue(vset.valfmt,
+ instval, self.desc.type, PM_TYPE_DOUBLE)
+ poutAtom = self.ctx.pmExtractValue(pvset.valfmt,
+ pinstval, self.desc.type, PM_TYPE_DOUBLE)
+ if self._convUnits:
+ desc = (POINTER(pmDesc) * 1)()
+ desc[0] = self.desc
+ outAtom = self.ctx.pmConvScale(
+ PM_TYPE_DOUBLE, outAtom, desc, 0, self._convUnits)
+ poutAtom = self.ctx.pmConvScale(
+ PM_TYPE_DOUBLE, poutAtom, desc, 0, self._convUnits)
+ value = outAtom.dref(PM_TYPE_DOUBLE)
+ pvalue = poutAtom.dref(PM_TYPE_DOUBLE)
+ if (value >= pvalue):
+ valL.append((instval, name, (value - pvalue) / delta))
+ return valL
+
+ def _R_values(self):
+ return self._values
+ def _R_prevValues(self):
+ return self._prevValues
+ def _R_convType(self):
+ return self._convType
+ def _R_convUnits(self):
+ return self._convUnits
+
+ def _R_errorStatus(self):
+ return self._errorStatus
+
+ def _R_netConvValues(self):
+ return self._netConvValues
+
+ def _R_netPrevValues(self):
+ if not self._prevvset:
+ return None
+ self._netPrevValues = self.computeValues(self._prevvset)
+ return self._netPrevValues
+
+ def _R_netValues(self):
+ if not self._vset:
+ return None
+ self._netValues = self.computeValues(self._vset)
+ return self._netValues
+
+ def _W_values(self, values):
+ self._prev = self._values
+ self._values = values
+ self._netPrev = self._netValue
+ self._netValue = None
+
+ def _W_convType(self, value):
+ self._convType = value
+ def _W_convUnits(self, value):
+ self._convUnits = value
+
+ # interface to properties in MetricCore
+ ctx = property(_R_ctx, None, None, None)
+ name = property(_R_name, None, None, None)
+ pmid = property(_R_pmid, None, None, None)
+ desc = property(_R_desc, None, None, None)
+ text = property(_R_text, None, None, None)
+ help = property(_R_help, None, None, None)
+
+ # properties specific to this instance
+ values = property(_R_values, _W_values, None, None)
+ prevValues = property(_R_prevValues, None, None, None)
+ convType = property(_R_convType, _W_convType, None, None)
+ convUnits = property(_R_convUnits, _W_convUnits, None, None)
+ errorStatus = property(_R_errorStatus, None, None, None)
+ netValues = property(_R_netValues, None, None, None)
+ netPrevValues = property(_R_netPrevValues, None, None, None)
+ netConvValues = property(_R_netConvValues, None, None, None)
+
+ def metricPrint(self):
+ indomstr = self.ctx.pmInDomStr(self.desc.indom)
+ print(" ", "indom:", indomstr)
+ instD = self.ctx.mcGetInstD(self.desc.indom)
+ for inst, name, val in self.netValues:
+ print(" ", name, val)
+
+ def metricConvert(self, delta):
+ convertedList = self.convertValues(self._vset, self._prevvset, delta)
+ self._netConvValues = convertedList
+ return self._netConvValues
+
+
+class MetricCache(pmContext):
+ """
+ A cache of MetricCores is kept to reduce calls into the PMAPI library
+ this also slightly reduces the memory footprint of Metric instances
+ that share a common MetricCore
+ a cache of instance domain information is also kept, which further
+ reduces calls into the PMAPI and reduces the memory footprint of
+ Metric objects that share a common instance domain
+ """
+
+ ##
+ # overloads
+
+ def __init__(self, typed = PM_CONTEXT_HOST, target = "local:"):
+ pmContext.__init__(self, typed, target)
+ self._mcIndomD = {}
+ self._mcByNameD = {}
+ self._mcByPmidD = {}
+
+ ##
+ # methods
+
+ def mcGetInstD(self, indom):
+ """ Query the instance : instance_list dictionary """
+ return self._mcIndomD[indom]
+
+ def _mcAdd(self, core):
+ """ Update the dictionary """
+ indom = core.desc.contents.indom
+ if not self._mcIndomD.has_key(indom):
+ if c_int(indom).value == c_int(PM_INDOM_NULL).value:
+ instmap = { PM_IN_NULL : "PM_IN_NULL" }
+ else:
+ if self._type == PM_CONTEXT_ARCHIVE:
+ instL, nameL = self.pmGetInDomArchive(core.desc)
+ else:
+ instL, nameL = self.pmGetInDom(core.desc)
+ if instL != None and nameL != None:
+ instmap = dict(zip(instL, nameL))
+ else:
+ instmap = {}
+ self._mcIndomD.update({indom: instmap})
+
+ self._mcByNameD.update({core.name: core})
+ self._mcByPmidD.update({core.pmid: core})
+
+ def mcGetCoresByName(self, nameL):
+ """ Update the core (metric id, description,...) list """
+ coreL = []
+ missD = None
+ errL = None
+ # lookup names in cache
+ for index, name in enumerate(nameL):
+ # lookup metric core in cache
+ core = self._mcByNameD.get(name)
+ if not core:
+ # cache miss
+ if not missD:
+ missD = {}
+ missD.update({name: index})
+ coreL.append(core)
+
+ # some cache lookups missed, fetch pmids and build missing MetricCores
+ if missD:
+ idL, errL = self.mcFetchPmids(missD.keys())
+ for name, pmid in idL:
+ if pmid == PM_ID_NULL:
+ # fetch failed for the given metric name
+ if not errL:
+ errL = []
+ errL.append(name)
+ else:
+ # create core pmDesc
+ newcore = self._mcCreateCore(name, pmid)
+ # update core ref in return list
+ coreL[missD[name]] = newcore
+
+ return coreL, errL
+
+ def _mcCreateCore(self, name, pmid):
+ """ Update the core description """
+ newcore = MetricCore(self, name, pmid)
+ try:
+ newcore.desc = self.pmLookupDesc(pmid)
+ except pmErr as error:
+ fail = "%s: pmLookupDesc: %s" % (error.progname(), error.message())
+ print >> stderr, fail
+ raise SystemExit(1)
+
+ # insert core into cache
+ self._mcAdd(newcore)
+ return newcore
+
+ def mcFetchPmids(self, nameL):
+ """ Update the core metric ids. note: some names have identical pmids """
+ errL = None
+ nameA = (c_char_p * len(nameL))()
+ for index, name in enumerate(nameL):
+ nameA[index] = c_char_p(name)
+ try:
+ pmidArray = self.pmLookupName(nameA)
+ if len(pmidArray) < len(nameA):
+ missing = "%d of %d metric names" % (len(pmidArray), len(nameA))
+ print >> stderr, "Cannot resolve", missing
+ raise SystemExit(1)
+ except pmErr as error:
+ fail = "%s: pmLookupName: %s" % (error.progname(), error.message())
+ print >> stderr, fail
+ raise SystemExit(1)
+
+ return zip(nameA, pmidArray), errL
+
+
+class MetricGroup(dict):
+ """
+ Manages a group of metrics for fetching the values of
+ a MetricGroup is a dictionary of Metric objects, for which data can
+ be fetched from a target system using a single call to pmFetch
+ the Metric objects are indexed by the metric name
+ pmFetch fetches data for a list of pmIDs, so there is also a shadow
+ dictionary keyed by pmID, along with a shadow list of pmIDs
+ """
+
+ ##
+ # property read methods
+
+ def _R_contextCache(self):
+ return self._ctx
+ def _R_pmidArray(self):
+ return self._pmidArray
+ def _R_timestamp(self):
+ return self._result.contents.timestamp
+ def _R_result(self):
+ return self._result
+ def _R_prevTimestamp(self):
+ return self._prev.contents.timestamp
+ def _R_prev(self):
+ return self._prev
+
+ ##
+ # property write methods
+
+ def _W_result(self, pmresult):
+ self._prev = self._result
+ self._result = pmresult
+
+ ##
+ # property definitions
+
+ contextCache = property(_R_contextCache, None, None, None)
+ pmidArray = property(_R_pmidArray, None, None, None)
+ result = property(_R_result, _W_result, None, None)
+ timestamp = property(_R_timestamp, None, None, None)
+ prev = property(_R_prev, None, None, None)
+ prevTimestamp = property(_R_prevTimestamp, None, None, None)
+
+ ##
+ # overloads
+
+ def __init__(self, contextCache, inL = []):
+ dict.__init__(self)
+ self._ctx = contextCache
+ self._pmidArray = None
+ self._result = None
+ self._prev = None
+ self._altD = {}
+ self.mgAdd(inL)
+
+ ##
+ # methods
+
+ def mgAdd(self, nameL):
+ """ Create the list of Metric(s) """
+ coreL, errL = self._ctx.mcGetCoresByName(nameL)
+ for core in coreL:
+ metric = Metric(core)
+ self.update({metric.name: metric})
+ self._altD.update({metric.pmid: metric})
+ n = len(self)
+ self._pmidArray = (c_uint * n)()
+ for x, key in enumerate(self.keys()):
+ self._pmidArray[x] = c_uint(self[key].pmid)
+
+ def mgFetch(self):
+ """ Fetch the list of Metric values. Save the old value. """
+ try:
+ self.result = self._ctx.pmFetch(self._pmidArray)
+ # update the result entries in each metric
+ result = self.result.contents
+ for i in range(self.result.contents.numpmid):
+ pmid = self.result.contents.get_pmid(i)
+ vset = self.result.contents.get_vset(i)
+ self._altD[pmid]._prevvset = self._altD[pmid]._vset
+ self._altD[pmid]._vset = vset
+ except pmErr as error:
+ if error.args[0] == PM_ERR_EOL:
+ raise SystemExit(0)
+ fail = "%s: pmFetch: %s" % (error.progname(), error.message())
+ print >> stderr, fail
+ raise SystemExit(1)
+
+ def mgDelta(self):
+ """
+ Sample delta - used for rate conversion calculations, which
+ requires timestamps from successive samples.
+ """
+ if self._prev != None:
+ prevTimestamp = float(self.prevTimestamp)
+ else:
+ prevTimestamp = 0.0
+ return float(self.timestamp) - prevTimestamp
+
+
+class MetricGroupPrinter(object):
+ """
+ Handles reporting of MetricGroups within a GroupManager.
+ This object is called upon at the end of each fetch when
+ new values are available. It is also responsible for
+ producing any initial (or on-going) header information
+ that the tool may wish to report.
+ """
+ def report(self, manager):
+ """ Base implementation, all tools should override """
+ for group_name in manager.keys():
+ group = manager[group_name]
+ for metric_name in group.keys():
+ group[metric_name].metricPrint()
+
+ def convert(self, manager):
+ """ Do conversion for all metrics across all groups """
+ for group_name in manager.keys():
+ group = manager[group_name]
+ delta = group.mgDelta()
+ for metric_name in group.keys():
+ group[metric_name].metricConvert(delta)
+
+
+class MetricGroupManager(dict, MetricCache):
+ """
+ Manages a dictionary of MetricGroups which can be pmFetch'ed
+ inherits from MetricCache, which inherits from pmContext
+ """
+
+ ##
+ # property access methods
+
+ def _R_options(self): # command line option object
+ return self._options
+
+ def _W_options(self, options):
+ self._options = options
+
+ def _R_default_delta(self): # default interval unless command line set
+ return self._default_delta
+ def _W_default_delta(self, delta):
+ self._default_delta = delta
+ def _R_default_pause(self): # default reporting delay (archives only)
+ return self._default_pause
+ def _W_default_pause(self, pause):
+ self._default_pause = pause
+
+ def _W_printer(self, printer): # helper class for reporting
+ self._printer = printer
+ def _R_counter(self): # fetch iteration count, useful for printer
+ return self._counter
+
+ ##
+ # property definitions
+
+ options = property(_R_options, _W_options, None, None)
+ default_delta = property(_R_default_delta, _W_default_delta, None, None)
+ default_pause = property(_R_default_pause, _W_default_pause, None, None)
+
+ printer = property(None, _W_printer, None, None)
+ counter = property(_R_counter, None, None, None)
+
+ ##
+ # overloads
+
+ def __init__(self, typed = PM_CONTEXT_HOST, target = "local:"):
+ dict.__init__(self)
+ MetricCache.__init__(self, typed, target)
+ self._options = None
+ self._default_delta = timeval(1, 0)
+ self._default_pause = None
+ self._printer = None
+ self._counter = 0
+
+ def __setitem__(self, attr, value = []):
+ if self.has_key(attr):
+ raise KeyError("metric group with that key already exists")
+ else:
+ dict.__setitem__(self, attr, MetricGroup(self, inL = value))
+
+ @classmethod
+ def builder(build, options, argv):
+ """ Helper interface, simple PCP monitor argument parsing. """
+ manager = build.fromOptions(options, argv)
+ manager._default_delta = timeval(options.delta, 0)
+ manager._options = options
+ return manager
+
+ ##
+ # methods
+
+ def _computeSamples(self):
+ """ Calculate the number of samples we are to take.
+ This is based on command line options --samples but also
+ must consider --start, --finish and --interval. If none
+ of these were presented, a zero return means "infinite".
+ """
+ if self._options == None:
+ return 0 # loop until interrupted or PM_ERR_EOL
+ samples = self._options.pmGetOptionSamples()
+ if samples != None:
+ return samples
+ if self._options.pmGetOptionFinishOptarg() == None:
+ return 0 # loop until interrupted or PM_ERR_EOL
+ origin = self._options.pmGetOptionOrigin()
+ finish = self._options.pmGetOptionFinish()
+ delta = self._options.pmGetOptionInterval()
+ if delta == None:
+ delta = self._default_delta
+ period = (delta.tv_sec * 1.0e6 + delta.tv_usec) / 1e6
+ window = float(finish.tv_sec - origin.tv_sec)
+ window += float((finish.tv_usec - origin.tv_usec) / 1e6)
+ window /= period
+ return int(window + 0.5) # roundup to positive number
+
+ def _computePauseTime(self):
+ """ Figure out how long to sleep between samples.
+ This needs to take into account whether we were explicitly
+ asked for a delay (independent of context type, --pause),
+ whether this is an archive or live context, and the sampling
+ --interval (including the default value, if none requested).
+ """
+ if self._default_pause != None:
+ return self._default_pause
+ if self.type == PM_CONTEXT_ARCHIVE:
+ self._default_pause = timeval(0, 0)
+ elif self._options != None:
+ pause = self._options.pmGetOptionInterval()
+ if pause != None:
+ self._default_pause = pause
+ else:
+ self._default_pause = self._default_delta
+ else:
+ self._default_pause = self._default_delta
+ return self._default_pause
+
+ def fetch(self):
+ """ Perform fetch operation on all of the groups. """
+ for group in self.keys():
+ self[group].mgFetch()
+
+ def run(self):
+ """ Using options specification, loop fetching and reporting,
+ pausing for the requested time interval between updates.
+ Transparently handles archive/live mode differences.
+ Note that this can be different to the sampling interval
+ in archive mode, but is usually the same as the sampling
+ interval in live mode.
+ """
+ samples = self._computeSamples()
+ timer = self._computePauseTime()
+ try:
+ self.fetch()
+ while True:
+ self._counter += 1
+ if samples == 0 or self._counter <= samples:
+ self._printer.report(self)
+ if self._counter == samples:
+ break
+ timer.sleep()
+ self.fetch()
+ except SystemExit as code:
+ return code
+ except KeyboardInterrupt:
+ pass
+ return 0
diff --git a/src/python/pcp/pmda.py b/src/python/pcp/pmda.py
new file mode 100644
index 0000000..03d9739
--- /dev/null
+++ b/src/python/pcp/pmda.py
@@ -0,0 +1,407 @@
+# pylint: disable=C0103
+"""Wrapper module for libpcp_pmda - Performace Co-Pilot Domain Agent API
+#
+# Copyright (C) 2013-2014 Red Hat.
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# See pmdasimple.py for an example use of this module.
+"""
+
+import os
+
+import cpmapi
+import cpmda
+from pcp.pmapi import pmContext as PCP
+from pcp.pmapi import pmInDom, pmDesc, pmUnits
+
+from ctypes.util import find_library
+from ctypes import CDLL, c_int, c_long, c_char_p, c_void_p, cast, byref
+from ctypes import addressof, sizeof, POINTER, Structure, create_string_buffer
+
+## Performance Co-Pilot PMDA library (C)
+LIBPCP_PMDA = CDLL(find_library("pcp_pmda"))
+
+
+###
+## PMDA Indom Cache Services
+#
+LIBPCP_PMDA.pmdaCacheStore.restype = c_int
+LIBPCP_PMDA.pmdaCacheStore.argtypes = [pmInDom, c_int, c_char_p, c_void_p]
+LIBPCP_PMDA.pmdaCacheStoreKey.restype = c_int
+LIBPCP_PMDA.pmdaCacheStoreKey.argtypes = [
+ pmInDom, c_int, c_char_p, c_int, c_void_p]
+LIBPCP_PMDA.pmdaCacheLookup.restype = c_int
+LIBPCP_PMDA.pmdaCacheLookup.argtypes = [
+ pmInDom, c_int, POINTER(c_char_p), POINTER(c_void_p)]
+LIBPCP_PMDA.pmdaCacheLookupName.restype = c_int
+LIBPCP_PMDA.pmdaCacheLookupName.argtypes = [
+ pmInDom, c_char_p, POINTER(c_int), POINTER(c_void_p)]
+LIBPCP_PMDA.pmdaCacheLookupKey.restype = c_int
+LIBPCP_PMDA.pmdaCacheLookupKey.argtypes = [
+ pmInDom, c_char_p, c_int, c_void_p, POINTER(c_char_p),
+ POINTER(c_int), POINTER(c_void_p)]
+LIBPCP_PMDA.pmdaCacheOp.restype = c_int
+LIBPCP_PMDA.pmdaCacheOp.argtypes = [pmInDom, c_long]
+
+
+##
+# Definition of structures used by C library libpcp_pmda, derived from <pcp/pmda.h>
+#
+
+class pmdaMetric(Structure):
+ """ Structure describing a metric definition for a PMDA """
+ _fields_ = [("m_user", c_void_p),
+ ("m_desc", pmDesc)]
+
+ def __init__(self, pmid, typeof, indom, sem, units):
+ Structure.__init__(self)
+ self.m_user = None
+ self.m_desc.pmid = pmid
+ self.m_desc.type = typeof
+ self.m_desc.indom = indom
+ self.m_desc.sem = sem
+ self.m_desc.units = units
+
+ def __str__(self):
+ return "pmdaMetric@%#lx pmid=%#lx type=%d" % (addressof(self), self.m_desc.pmid, self.m_desc.type)
+
+class pmdaInstid(Structure):
+ """ Structure describing an instance (index/name) a PMDA """
+ _fields_ = [("i_inst", c_int),
+ ("i_name", c_char_p)]
+
+ def __init__(self, instid, name):
+ Structure.__init__(self)
+ self.i_inst = instid
+ self.i_name = name
+
+ def __str__(self):
+ return "pmdaInstid@%#lx index=%d name=%s" % (addressof(self), self.i_inst, self.i_name)
+
+class pmdaIndom(Structure):
+ """ Structure describing an instance domain within a PMDA """
+ _fields_ = [("it_indom", pmInDom),
+ ("it_numinst", c_int),
+ ("it_set", POINTER(pmdaInstid))]
+
+ def __init__(self, indom, insts):
+ Structure.__init__(self)
+ self.it_numinst = 0
+ self.it_set = None
+ self.it_indom = indom
+ self.set_instances(indom, insts)
+
+ def set_list_instances(self, insts):
+ instance_count = len(insts)
+ if (instance_count == 0):
+ return
+ instance_array = (pmdaInstid * instance_count)()
+ for i in xrange(instance_count):
+ instance_array[i].i_inst = insts[i].i_inst
+ instance_array[i].i_name = insts[i].i_name
+ self.it_set = instance_array
+
+ def set_dict_instances(self, indom, insts):
+ LIBPCP_PMDA.pmdaCacheOp(indom, cpmda.PMDA_CACHE_INACTIVE)
+ for key in insts.keys():
+ LIBPCP_PMDA.pmdaCacheStore(indom, cpmda.PMDA_CACHE_ADD, key, byref(insts[key]))
+ LIBPCP_PMDA.pmdaCacheOp(indom, cpmda.PMDA_CACHE_SAVE)
+
+ def set_instances(self, indom, insts):
+ if (insts == None):
+ self.it_numinst = 0 # not yet known if cache indom or not
+ elif (isinstance(insts, dict)):
+ self.it_numinst = -1 # signifies cache indom (no it_set)
+ self.set_dict_instances(indom, insts)
+ else:
+ self.it_numinst = len(insts) # signifies an old-school array indom
+ self.set_list_instances(insts)
+
+ def __str__(self):
+ return "pmdaIndom@%#lx indom=%#lx num=%d" % (addressof(self), self.it_indom, self.it_numinst)
+
+class pmdaUnits(pmUnits):
+ """ Wrapper class for PMDAs defining their metrics (avoids pmapi import) """
+ def __init__(self, dimS, dimT, dimC, scaleS, scaleT, scaleC):
+ pmUnits.__init__(self, dimS, dimT, dimC, scaleS, scaleT, scaleC)
+
+
+###
+## Convenience Classes for PMDAs
+#
+
+class MetricDispatch(object):
+ """ Helper for PMDA class which manages metric dispatch
+ table, metric namespace, descriptors and help text.
+
+ Overall strategy is to interface to the C code in
+ python/pmda.c here, using a void* handle to the PMDA
+ dispatch structure (allocated and managed in C code).
+
+ In addition, several dictionaries for metric related
+ strings are managed here (names, help text) - cached
+ for quick lookups in callback routines.
+ """
+
+ ##
+ # overloads
+
+ def __init__(self, domain, name, logfile, helpfile):
+ self.clear_indoms()
+ self.clear_metrics()
+ cpmda.init_dispatch(domain, name, logfile, helpfile)
+
+ def clear_indoms(self):
+ self._indomtable = []
+ self._indoms = {}
+ self._indom_oneline = {}
+ self._indom_helptext = {}
+
+ def clear_metrics(self):
+ self._metrictable = []
+ self._metrics = {}
+ self._metric_names = {}
+ self._metric_oneline = {}
+ self._metric_helptext = {}
+
+ def reset_metrics(self):
+ self.clear_metrics()
+ cpmda.set_need_refresh()
+
+ ##
+ # general PMDA class methods
+
+ def pmns_refresh(self):
+ cpmda.pmns_refresh(self._metric_names)
+
+ def connect_pmcd(self):
+ cpmda.connect_pmcd()
+
+ def add_metric(self, name, metric, oneline = '', text = ''):
+ pmid = metric.m_desc.pmid
+ if (pmid in self._metric_names):
+ raise KeyError('attempt to add_metric with an existing name')
+ if (pmid in self._metrics):
+ raise KeyError('attempt to add_metric with an existing PMID')
+
+ self._metrictable.append(metric)
+ self._metrics[pmid] = metric
+ self._metric_names[pmid] = name
+ self._metric_oneline[pmid] = oneline
+ self._metric_helptext[pmid] = text
+ cpmda.set_need_refresh()
+
+ def add_indom(self, indom, oneline = '', text = ''):
+ indomid = indom.it_indom
+ for entry in self._indomtable:
+ if (entry.it_indom == indomid):
+ raise KeyError('attempt to add_indom with an existing ID')
+ self._indomtable.append(indom)
+ self._indoms[indomid] = indom
+ self._indom_oneline[indomid] = oneline
+ self._indom_helptext[indomid] = text
+
+ def replace_indom(self, indom, insts):
+ replacement = pmdaIndom(indom, insts)
+ # list indoms need to keep the table up-to-date for libpcp_pmda
+ if (isinstance(insts, list)):
+ for entry in self._indomtable:
+ if (entry.it_indom == indom):
+ entry = replacement
+ self._indoms[indom] = replacement
+
+ def inst_lookup(self, indom, instance):
+ """
+ Lookup the value associated with an (internal) instance ID
+ within a specific instance domain (only valid with indoms
+ of cache type - array indom will always return None).
+ """
+ entry = self._indoms[indom]
+ if (entry.it_numinst < 0):
+ value = (c_void_p)()
+ sts = LIBPCP_PMDA.pmdaCacheLookup(indom, instance, None, byref(value))
+ if (sts == cpmda.PMDA_CACHE_ACTIVE):
+ return value
+ return None
+
+ def inst_name_lookup(self, indom, instance):
+ """
+ Lookup the name associated with an (internal) instance ID within
+ a specific instance domain.
+ """
+ entry = self._indoms[indom]
+ if (entry.it_numinst < 0):
+ name = (c_char_p)()
+ sts = LIBPCP_PMDA.pmdaCacheLookup(indom, instance, byref(name), None)
+ if (sts == cpmda.PMDA_CACHE_ACTIVE):
+ return name.value
+ elif (entry.it_numinst > 0 and entry.it_indom == indom):
+ for inst in entry.it_set:
+ if (inst.i_inst == instance):
+ return inst.i_name
+ return None
+
+
+class PMDA(MetricDispatch):
+ """ Defines a PCP performance metrics domain agent
+ Used to add new metrics into the PCP toolkit.
+ """
+
+ ##
+ # property read methods
+
+ def read_name(self):
+ """ Property for name of this PMDA """
+ return self._name
+
+ def read_domain(self):
+ """ Property for unique domain number of this PMDA """
+ return self._domain
+
+ ##
+ # property definitions
+
+ name = property(read_name, None, None, None)
+ domain = property(read_domain, None, None, None)
+
+ ##
+ # overloads
+
+ def __init__(self, name, domain):
+ self._name = name
+ self._domain = domain
+ logfile = name + '.log'
+ pmdaname = 'pmda' + name
+ helpfile = '%s/%s/help' % (PCP.pmGetConfig('PCP_PMDAS_DIR'), name)
+ MetricDispatch.__init__(self, domain, pmdaname, logfile, helpfile)
+
+
+ ##
+ # general PMDA class methods
+
+ def domain_write(self):
+ """
+ Write out the domain.h file (used during installation)
+ """
+ print('#define %s %d' % (self._name.upper(), self._domain))
+
+ def pmns_write(self, root):
+ """
+ Write out the namespace file (used during installation)
+ """
+ pmns = self._metric_names
+ prefixes = set([pmns[key].split('.')[0] for key in pmns])
+ indent = (root == 'root')
+ lead = ''
+ if indent:
+ lead = '\t'
+ print('root {')
+ for prefix in prefixes:
+ print('%s%s\t%d:*:*' % (lead, prefix, self._domain))
+ if indent:
+ print('}')
+
+ def run(self):
+ """
+ All the real work happens herein; we can be called in one of three
+ situations, determined by environment variables. First couple are
+ during the agent Install process, where the domain.h and namespace
+ files need to be created. The third case is the real mccoy, where
+ an agent is actually being started by pmcd/dbpmda and makes use of
+ libpcp_pmda to talk PCP protocol.
+ """
+ if ('PCP_PYTHON_DOMAIN' in os.environ):
+ self.domain_write()
+ elif ('PCP_PYTHON_PMNS' in os.environ):
+ self.pmns_write(os.environ['PCP_PYTHON_PMNS'])
+ else:
+ self.pmns_refresh()
+ cpmda.pmid_oneline_refresh(self._metric_oneline)
+ cpmda.pmid_longtext_refresh(self._metric_helptext)
+ cpmda.indom_oneline_refresh(self._indom_oneline)
+ cpmda.indom_longtext_refresh(self._indom_helptext)
+ numindoms = len(self._indomtable)
+ ibuf = create_string_buffer(numindoms * sizeof(pmdaIndom))
+ indoms = cast(ibuf, POINTER(pmdaIndom))
+ for i in xrange(numindoms):
+ indoms[i] = self._indomtable[i]
+ nummetrics = len(self._metrictable)
+ mbuf = create_string_buffer(nummetrics * sizeof(pmdaMetric))
+ metrics = cast(mbuf, POINTER(pmdaMetric))
+ for i in xrange(nummetrics):
+ metrics[i] = self._metrictable[i]
+ cpmda.pmda_dispatch(ibuf.raw, numindoms, mbuf.raw, nummetrics)
+
+ @staticmethod
+ def set_fetch(fetch):
+ return cpmda.set_fetch(fetch)
+
+ @staticmethod
+ def set_refresh(refresh):
+ return cpmda.set_refresh(refresh)
+
+ @staticmethod
+ def set_instance(instance):
+ return cpmda.set_instance(instance)
+
+ @staticmethod
+ def set_fetch_callback(fetch_callback):
+ return cpmda.set_fetch_callback(fetch_callback)
+
+ @staticmethod
+ def set_store_callback(store_callback):
+ return cpmda.set_store_callback(store_callback)
+
+ @staticmethod
+ def set_user(username):
+ return cpmapi.pmSetProcessIdentity(username)
+
+ @staticmethod
+ def pmid(cluster, item):
+ return cpmda.pmda_pmid(cluster, item)
+
+ @staticmethod
+ def indom(serial):
+ return cpmda.pmda_indom(serial)
+
+ @staticmethod
+ def units(dim_space, dim_time, dim_count, scale_space, scale_time, scale_count):
+ return cpmda.pmda_units(dim_space, dim_time, dim_count, scale_space, scale_time, scale_count)
+
+ @staticmethod
+ def uptime(now):
+ return cpmda.pmda_uptime(now)
+
+ @staticmethod
+ def log(message):
+ return cpmda.pmda_log(message)
+
+ @staticmethod
+ def err(message):
+ return cpmda.pmda_err(message)
+
+# Other methods perl API provides:
+# add_timer()
+# add_pipe()
+# add_tail()
+# add_sock()
+# put_sock()
+# set_inet_socket
+# set_ipv6_socket
+# set_unix_socket
+# pmda_pmid_name(cluster,item)
+# pmda_pmid_text(cluster,item)
+#
diff --git a/src/python/pcp/pmgui.py b/src/python/pcp/pmgui.py
new file mode 100644
index 0000000..a9b35b0
--- /dev/null
+++ b/src/python/pcp/pmgui.py
@@ -0,0 +1,174 @@
+# pylint: disable=C0103
+""" Wrapper module for libpcp_gui - PCP Graphical User Interface clients """
+#
+# Copyright (C) 2012-2013 Red Hat Inc.
+# Copyright (C) 2009-2012 Michael T. Werner
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+
+##############################################################################
+#
+# imports
+#
+
+# constants adapted from C header file <pcp/pmapi.h>
+from pmapi import pmErr
+from cpmapi import PM_ERR_IPC
+
+# for interfacing with libpcp - the client-side C API
+from ctypes import CDLL, Structure, POINTER, cast, byref
+from ctypes import c_void_p, c_char_p, c_int, c_long
+from ctypes.util import find_library
+
+
+##############################################################################
+#
+# dynamic library loads
+#
+
+LIBPCP_GUI = CDLL(find_library("pcp_gui"))
+LIBC = CDLL(find_library("c"))
+
+
+##############################################################################
+#
+# definition of structures used by C library libpcp, derived from <pcp/pmafm.h>
+#
+
+class pmRecordHost(Structure):
+ """state information between the recording session and the pmlogger """
+ _fields_ = [("f_config", c_void_p),
+ ("fd_ipc", c_int),
+ ("logfile", c_char_p),
+ ("pid", c_int),
+ ("status", c_int)]
+
+
+##############################################################################
+#
+# GUI API function prototypes
+#
+
+##
+# PMAPI Record-Mode Services
+
+LIBPCP_GUI.pmRecordSetup.restype = c_long
+LIBPCP_GUI.pmRecordSetup.argtypes = [c_char_p, c_char_p, c_int]
+
+LIBPCP_GUI.pmRecordAddHost.restype = c_int
+LIBPCP_GUI.pmRecordAddHost.argtypes = [
+ c_char_p, c_int, POINTER(POINTER(pmRecordHost))]
+
+LIBPCP_GUI.pmRecordControl.restype = c_int
+LIBPCP_GUI.pmRecordControl.argtypes = [POINTER(pmRecordHost), c_int, c_char_p]
+
+
+
+#LIBPCP_GUI.pmTimeConnect.restype = c_int
+#LIBPCP_GUI.pmTimeConnect.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeDisconnect.restype = c_int
+#LIBPCP_GUI.pmTimeDisconnect.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeGetPort.restype = c_int
+#LIBPCP_GUI.pmTimeGetPort.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeRecv.restype = c_int
+#LIBPCP_GUI.pmTimeRecv.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeSendAck.restype = c_int
+#LIBPCP_GUI.pmTimeSendAck.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeSendBounds.restype = c_int
+#LIBPCP_GUI.pmTimeSendBounds.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeSendMode.restype = c_int
+#LIBPCP_GUI.pmTimeSendMode.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeSendPosition.restype = c_int
+#LIBPCP_GUI.pmTimeSendPosition.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeSendTimezone.restype = c_int
+#LIBPCP_GUI.pmTimeSendTimezone.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeShowDialog.restype = c_int
+#LIBPCP_GUI.pmTimeShowDialog.argtypes = [ ]
+
+#LIBPCP_GUI.pmTimeGetStatePixmap.restype = c_int
+#LIBPCP_GUI.pmTimeGetStatePixmap.argtypes = [ ]
+
+
+
+##############################################################################
+#
+# class GuiClient
+#
+# This class wraps the GUI API library functions
+#
+
+class GuiClient(object):
+ """ Provides metric recording and time control interfaces
+ """
+
+ ##
+ # Record-Mode Services
+
+ @staticmethod
+ def pmRecordSetup(folio, creator, replay):
+ """ GUI API - Setup an archive recording session
+ File* file = pmRecordSetup("folio", "creator", 0)
+ """
+ file_result = LIBPCP_GUI.pmRecordSetup(
+ c_char_p(folio), c_char_p(creator), replay)
+ if (file_result == 0):
+ raise pmErr(file_result)
+ return file_result
+
+ @staticmethod
+ def pmRecordAddHost(host, isdefault, config):
+ """ GUI API - Adds host to an archive recording session
+ (status, recordhost) = pmRecordAddHost("host", 1, "configuration")
+ """
+ rhp = POINTER(pmRecordHost)()
+ status = LIBPCP_GUI.pmRecordAddHost(
+ c_char_p(host), isdefault, byref(rhp))
+ if status < 0:
+ raise pmErr(status)
+ status = LIBC.fputs(c_char_p(config), c_long(rhp.contents.f_config))
+ if (status < 0):
+ LIBC.perror(c_char_p(""))
+ raise pmErr(status)
+ return status, rhp
+
+ @staticmethod
+ def pmRecordControl(rhp, request, options):
+ """PMAPI - Control an archive recording session
+ status = pmRecordControl(0, cpmgui.PM_RCSETARG, "args")
+ status = pmRecordControl(0, cpmgui.PM_REC_ON)
+ status = pmRecordControl(0, cpmgui.PM_REC_OFF)
+ """
+ status = LIBPCP_GUI.pmRecordControl(
+ cast(rhp, POINTER(pmRecordHost)),
+ request, c_char_p(options))
+ if status < 0 and status != PM_ERR_IPC:
+ raise pmErr(status)
+ return status
+
+ ##
+ # GUI API Time Control Services
+ # (Not Yet Implemented)
+
+
diff --git a/src/python/pcp/pmi.py b/src/python/pcp/pmi.py
new file mode 100644
index 0000000..178bff9
--- /dev/null
+++ b/src/python/pcp/pmi.py
@@ -0,0 +1,312 @@
+# pylint: disable=C0103
+"""Wrapper module for libpcp_import - Performace Co-Pilot Log Import API
+#
+# Copyright (C) 2012-2013 Red Hat.
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+# Example use of this module for creating a PCP archive:
+
+ import math
+ import time
+ import pmapi
+ from pcp import pmi
+
+ # Create a new archive
+ log = pmi.pmiLogImport("loadtest")
+ log.pmiSetHostname("www.abc.com")
+ log.pmiSetTimezone("EST-10")
+
+ # Add a metric with an instance domain
+ domain = 60 # Linux kernel
+ pmid = log.pmiID(domain, 2, 0)
+ indom = log.pmiInDom(domain, 2)
+ units = log.pmiUnits(0, 0, 0, 0, 0, 0)
+ log.pmiAddMetric("kernel.all.load", pmid, pmapi.PM_TYPE_FLOAT,
+ indom, pmapi.PM_SEM_INSTANT, units)
+ log.pmiAddInstance(indom, "1 minute", 1)
+ log.pmiAddInstance(indom, "5 minute", 5)
+ log.pmiAddInstance(indom, "15 minute", 15)
+
+ # Create a record with a timestamp
+ log.pmiPutValue("kernel.all.load", "1 minute", "%f" % 0.01)
+ log.pmiPutValue("kernel.all.load", "5 minute", "%f" % 0.05)
+ log.pmiPutValue("kernel.all.load", "15 minute", "%f" % 0.15)
+ timetuple = math.modf(time.time())
+ useconds = int(timetuple[0] * 1000000)
+ seconds = int(timetuple[1])
+ log.pmiWrite(seconds, useconds)
+ del log
+"""
+
+from pcp.pmapi import pmID, pmInDom, pmUnits, pmResult
+from cpmi import pmiErrSymDict, PMI_MAXERRMSGLEN
+
+import ctypes
+from ctypes import cast, c_int, c_char_p, POINTER
+
+# Performance Co-Pilot PMI library (C)
+LIBPCP_IMPORT = ctypes.CDLL(ctypes.util.find_library("pcp_import"))
+
+##
+# PMI Log Import Services
+
+LIBPCP_IMPORT.pmiDump.restype = None
+LIBPCP_IMPORT.pmiDump.argtypes = None
+
+LIBPCP_IMPORT.pmiID.restype = pmID
+LIBPCP_IMPORT.pmiID.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int]
+
+LIBPCP_IMPORT.pmiInDom.restype = pmInDom
+LIBPCP_IMPORT.pmiInDom.argtypes = [ctypes.c_int, ctypes.c_int]
+
+LIBPCP_IMPORT.pmiUnits.restype = pmUnits
+LIBPCP_IMPORT.pmiUnits.argtypes = [
+ ctypes.c_int, ctypes.c_int, ctypes.c_int,
+ ctypes.c_int, ctypes.c_int, ctypes.c_int]
+
+LIBPCP_IMPORT.pmiErrStr_r.restype = c_char_p
+LIBPCP_IMPORT.pmiErrStr_r.argtypes = [c_int, c_char_p, c_int]
+
+LIBPCP_IMPORT.pmiStart.restype = c_int
+LIBPCP_IMPORT.pmiStart.argtypes = [c_char_p, c_int]
+
+LIBPCP_IMPORT.pmiUseContext.restype = c_int
+LIBPCP_IMPORT.pmiUseContext.argtypes = [c_int]
+
+LIBPCP_IMPORT.pmiEnd.restype = c_int
+LIBPCP_IMPORT.pmiEnd.argtypes = None
+
+LIBPCP_IMPORT.pmiSetHostname.restype = c_int
+LIBPCP_IMPORT.pmiSetHostname.argtypes = [c_char_p]
+
+LIBPCP_IMPORT.pmiSetTimezone.restype = c_int
+LIBPCP_IMPORT.pmiSetTimezone.argtypes = [c_char_p]
+
+LIBPCP_IMPORT.pmiAddMetric.restype = c_int
+LIBPCP_IMPORT.pmiAddMetric.argtypes = [
+ c_char_p, pmID, c_int, pmInDom, c_int, pmUnits]
+
+LIBPCP_IMPORT.pmiAddInstance.restype = c_int
+LIBPCP_IMPORT.pmiAddInstance.argtypes = [pmInDom, c_char_p, c_int]
+
+LIBPCP_IMPORT.pmiPutValue.restype = c_int
+LIBPCP_IMPORT.pmiPutValue.argtypes = [c_char_p, c_char_p, c_char_p]
+
+LIBPCP_IMPORT.pmiGetHandle.restype = c_int
+LIBPCP_IMPORT.pmiGetHandle.argtypes = [c_char_p, c_char_p]
+
+LIBPCP_IMPORT.pmiPutValueHandle.restype = c_int
+LIBPCP_IMPORT.pmiPutValueHandle.argtypes = [c_int, c_char_p]
+
+LIBPCP_IMPORT.pmiWrite.restype = c_int
+LIBPCP_IMPORT.pmiWrite.argtypes = [c_int, c_int]
+
+LIBPCP_IMPORT.pmiPutResult.restype = c_int
+LIBPCP_IMPORT.pmiPutResult.argtypes = [POINTER(pmResult)]
+
+#
+# definition of exception classes
+#
+
+class pmiErr(Exception):
+ '''
+ Encapsulation for PMI interface error code
+ '''
+ def __str__(self):
+ error_code = self.args[0]
+ try:
+ error_symbol = pmiErrSymDict[error_code]
+ error_string = ctypes.create_string_buffer(PMI_MAXERRMSGLEN)
+ error_string = LIBPCP_IMPORT.pmiErrStr_r(error_code,
+ error_string, PMI_MAXERRMSGLEN)
+ except KeyError:
+ error_symbol = error_string = ""
+ return "%s %s" % (error_symbol, error_string)
+
+
+#
+# class LogImport
+#
+# This class wraps the PMI (Log Import) library functions
+#
+
+class pmiLogImport(object):
+ """Defines a PCP Log Import archive context
+ This is used to create a PCP archive from an external source
+ """
+
+ ##
+ # property read methods
+
+ def read_path(self):
+ """ Property for archive path """
+ return self._path
+ def read_ctx(self):
+ """ Property for log import context """
+ return self._ctx
+
+ ##
+ # property definitions
+
+ path = property(read_path, None, None, None)
+ ctx = property(read_ctx, None, None, None)
+
+ ##
+ # overloads
+
+ def __init__(self, path, inherit = 0):
+ self._path = path # the archive path (file name)
+ self._ctx = LIBPCP_IMPORT.pmiStart(c_char_p(path), inherit)
+ if self._ctx < 0:
+ raise pmiErr(self._ctx)
+
+ def __del__(self):
+ if LIBPCP_IMPORT:
+ LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ LIBPCP_IMPORT.pmiEnd()
+ self._ctx = -1
+
+ ##
+ # PMI Log Import Services
+
+ def pmiSetHostname(self, hostname):
+ """PMI - set the source host name for a Log Import archive """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiSetHostname(c_char_p(hostname))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiSetTimezone(self, timezone):
+ """PMI - set the source timezone for a Log Import archive
+ """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiSetTimezone(c_char_p(timezone))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ @staticmethod
+ def pmiID(domain, cluster, item):
+ """PMI - construct a pmID data structure (helper routine) """
+ return LIBPCP_IMPORT.pmiID(domain, cluster, item)
+
+ @staticmethod
+ def pmiInDom(domain, serial):
+ """PMI - construct a pmInDom data structure (helper routine) """
+ return LIBPCP_IMPORT.pmiInDom(domain, serial)
+
+ @staticmethod
+ def pmiUnits(dim_space, dim_time, dim_count,
+ scale_space, scale_time, scale_count):
+ # pylint: disable=R0913
+ """PMI - construct a pmiUnits data structure (helper routine) """
+ return LIBPCP_IMPORT.pmiUnits(dim_space, dim_time, dim_count,
+ scale_space, scale_time, scale_count)
+
+ def pmiAddMetric(self, name, pmid, typed, indom, sem, units):
+ # pylint: disable=R0913
+ """PMI - add a new metric definition to a Log Import context """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiAddMetric(c_char_p(name),
+ pmid, typed, indom, sem, units)
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiAddInstance(self, indom, instance, instid):
+ """PMI - add element to an instance domain in a Log Import context """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiAddInstance(indom, c_char_p(instance), instid)
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiPutValue(self, name, inst, value):
+ """PMI - add a value for a metric-instance pair """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiPutValue(c_char_p(name),
+ c_char_p(inst), c_char_p(value))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiGetHandle(self, name, inst):
+ """PMI - define a handle for a metric-instance pair """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiGetHandle(c_char_p(name), c_char_p(inst))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiPutValueHandle(self, handle, value):
+ """PMI - add a value for a metric-instance pair via a handle """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiPutValueHandle(handle, c_char_p(value))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def pmiWrite(self, sec, usec):
+ """PMI - flush data to a Log Import archive """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiWrite(sec, usec)
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ def put_result(self, result):
+ """PMI - add a data record to a Log Import archive """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiPutResult(cast(result, POINTER(pmResult)))
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
+ @staticmethod
+ def pmiDump():
+ """PMI - dump the current Log Import contexts (diagnostic) """
+ LIBPCP_IMPORT.pmiDump()
+
+ def pmiEnd(self):
+ """PMI - close current context and finish a Log Import archive """
+ status = LIBPCP_IMPORT.pmiUseContext(self._ctx)
+ if status < 0:
+ raise pmiErr(status)
+ status = LIBPCP_IMPORT.pmiEnd()
+ self._ctx = -1
+ if status < 0:
+ raise pmiErr(status)
+ return status
+
diff --git a/src/python/pcp/pmsubsys.py b/src/python/pcp/pmsubsys.py
new file mode 100644
index 0000000..0bab017
--- /dev/null
+++ b/src/python/pcp/pmsubsys.py
@@ -0,0 +1,355 @@
+#
+# Performance Co-Pilot subsystem classes
+#
+# Copyright (C) 2013 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+"""Advanced System & Process Monitor using the libpcp Wrapper module
+
+Additional Information:
+
+Performance Co-Pilot Web Site
+http://www.performancecopilot.org
+"""
+
+# ignore line too long, missing docstring, method could be a function,
+# too many public methods
+# pylint: disable=C0301
+# pylint: disable=C0111
+# pylint: disable=R0201
+# pylint: disable=R0904
+
+import copy
+import cpmapi as c_api
+from pcp.pmapi import pmErr
+from ctypes import c_char_p
+
+
+# Subsystem ---------------------------------------------------------------
+
+
+class Subsystem(object):
+ def __init__(self):
+ self.metrics = []
+ self.diff_metrics = []
+ self.metric_pmids = []
+ self.metric_descs = []
+ self.metric_values = []
+ self.metrics_dict = {}
+ self.old_metric_values = []
+
+ def setup_metrics(self, pcp):
+ # remove any unsupported metrics
+ name_pattern = self.metrics[0].split(".")[0] + ".*"
+ for j in range(len(self.metrics)-1, -1, -1):
+ try:
+ self.metric_pmids = pcp.pmLookupName(self.metrics[j])
+ except pmErr as e:
+ self.metrics.remove(self.metrics[j])
+
+ if (len(self.metrics) == 0):
+ raise pmErr(c_api.PM_ERR_NAME, "", name_pattern)
+ self.metrics_dict = dict((i, self.metrics.index(i)) for i in self.metrics)
+ self.metric_pmids = pcp.pmLookupName(self.metrics)
+ self.metric_descs = pcp.pmLookupDescs(self.metric_pmids)
+ self.metric_values = [0 for i in range(len(self.metrics))]
+ self.old_metric_values = [0 for i in range(len(self.metrics))]
+
+ def dump_metrics(self):
+ metrics_string = ""
+ for i in xrange(len(self.metrics)):
+ metrics_string += self.metrics[i]
+ metrics_string += " "
+ return metrics_string
+
+ def get_scalar_value(self, var, idx):
+ if type(var) == type(str()):
+ value = self.get_metric_value(var)
+ else:
+ value = self.metric_values[var]
+ if type(value) != type(int()) and type(value) != type(long()):
+ return value[idx]
+ else:
+ return value
+
+ def get_metric_value(self, idx):
+ if idx in self.metrics:
+ return self.metric_values[self.metrics_dict[idx]]
+ else:
+ return 0
+
+ def get_old_scalar_value(self, var, idx):
+ aidx = 0
+ if var in self.metrics:
+ aidx = self.metrics_dict[var]
+ aval = self.old_metric_values[aidx]
+ else:
+ return 0
+ val = self.get_atom_value(aval[idx], None, self.metric_descs[aidx], False)
+ if type(val) == type(int()) or type(val) == type(long()):
+ return val
+ else:
+ return val[idx]
+
+ def get_len(self, var):
+ if type(var) != type(int()) and type(var) != type(long()):
+ return len(var)
+ else:
+ return 1
+
+ def get_atom_value(self, atom1, atom2, desc, want_diff): # pylint: disable-msg=R0913
+ # value conversion and diff, if required
+ atom_type = desc.type
+ if atom2 == None:
+ want_diff = False
+ if atom_type == c_api.PM_TYPE_32:
+ if want_diff:
+ return atom1.l - atom2.l
+ else:
+ return atom1.l
+ elif atom_type == c_api.PM_TYPE_U32:
+ if want_diff:
+ return atom1.ul - atom2.ul
+ else:
+ return atom1.ul
+ elif atom_type == c_api.PM_TYPE_64:
+ if want_diff:
+ return atom1.ll - atom2.ll
+ else:
+ return atom1.ll
+ elif atom_type == c_api.PM_TYPE_U64:
+ if want_diff:
+ return atom1.ull - atom2.ull
+ else:
+ return atom1.ull
+ elif atom_type == c_api.PM_TYPE_FLOAT:
+ if want_diff:
+ return atom1.f - atom2.f
+ else:
+ return atom1.f
+ elif atom_type == c_api.PM_TYPE_DOUBLE:
+ if want_diff:
+ return atom1.d - atom2.d
+ else:
+ return atom1.d
+ elif atom_type == c_api.PM_TYPE_STRING:
+ atom_str = c_char_p(atom1.cp)
+ return str(atom_str.value)
+ else:
+ return 0
+
+ def get_stats(self, pcp):
+ if len(self.metrics) <= 0:
+ raise pmErr
+
+ list_type = type([])
+
+ metric_result = pcp.pmFetch(self.metric_pmids)
+
+ if max(self.old_metric_values) == 0:
+ first = True
+ else:
+ first = False
+
+ # list of metric names
+ for i in xrange(len(self.metrics)):
+ # list of metric results, one per metric name
+ for j in xrange(metric_result.contents.numpmid):
+ if (metric_result.contents.get_pmid(j) != self.metric_pmids[i]):
+ continue
+ atomlist = []
+ # list of instances, one or more per metric. e.g. there are many
+ # instances for network metrics, one per network interface
+ for k in xrange(metric_result.contents.get_numval(j)):
+ atom = pcp.pmExtractValue(metric_result.contents.get_valfmt(j), metric_result.contents.get_vlist(j, k), self.metric_descs[j].type, self.metric_descs[j].type)
+ atomlist.append(atom)
+
+ value = []
+ # metric may require a diff to get a per interval value
+ for k in xrange(metric_result.contents.get_numval(j)):
+ if type(self.old_metric_values[j]) == list_type:
+ try:
+ old_val = self.old_metric_values[j][k]
+ except IndexError:
+ old_val = None
+ else:
+ old_val = self.old_metric_values[j]
+ if first:
+ want_diff = False
+ elif self.metrics[j] in self.diff_metrics:
+ want_diff = True
+ elif (self.metric_descs[j].sem == c_api.PM_SEM_DISCRETE
+ or self.metric_descs[j].sem == c_api.PM_SEM_INSTANT) :
+ want_diff = False
+ else:
+ want_diff = True
+ value.append(self.get_atom_value(atomlist[k], old_val, self.metric_descs[j], want_diff))
+
+ self.old_metric_values[j] = copy.copy(atomlist)
+ if metric_result.contents.get_numval(j) == 1:
+ if len(value) == 1:
+ self.metric_values[j] = copy.copy(value[0])
+ else:
+ self.metric_values[j] = 0
+ elif metric_result.contents.get_numval(j) > 1:
+ self.metric_values[j] = copy.copy(value)
+
+
+# Processor --------------------------------------------------------------
+
+ def init_processor_metrics(self):
+ self.cpu_total = 0
+ self.metrics += ['hinv.ncpu', 'hinv.cpu.clock',
+ 'kernel.all.cpu.idle', 'kernel.all.cpu.intr',
+ 'kernel.all.cpu.irq.hard', 'kernel.all.cpu.irq.soft',
+ 'kernel.all.cpu.nice', 'kernel.all.cpu.steal',
+ 'kernel.all.cpu.sys', 'kernel.all.cpu.user',
+ 'kernel.all.cpu.wait.total', 'kernel.all.intr',
+ 'kernel.all.load', 'kernel.all.pswitch',
+ 'kernel.all.uptime', 'kernel.percpu.cpu.nice',
+ 'kernel.percpu.cpu.user', 'kernel.percpu.cpu.intr',
+ 'kernel.percpu.cpu.sys', 'kernel.percpu.cpu.steal',
+ 'kernel.percpu.cpu.irq.hard',
+ 'kernel.percpu.cpu.irq.soft',
+ 'kernel.percpu.cpu.wait.total',
+ 'kernel.percpu.cpu.idle', 'kernel.all.nprocs',
+ 'kernel.all.runnable',
+ # multiple inheritance?
+ 'proc.runq.blocked', 'proc.runq.defunct',
+ 'proc.runq.runnable', 'proc.runq.sleeping']
+ self.diff_metrics = ['kernel.all.uptime']
+
+
+ def get_total(self):
+ self.cpu_total = (self.get_metric_value('kernel.all.cpu.nice') +
+ self.get_metric_value('kernel.all.cpu.user') +
+ self.get_metric_value('kernel.all.cpu.intr') +
+ self.get_metric_value('kernel.all.cpu.sys') +
+ self.get_metric_value('kernel.all.cpu.idle') +
+ self.get_metric_value('kernel.all.cpu.steal') +
+ self.get_metric_value('kernel.all.cpu.irq.hard') +
+ self.get_metric_value('kernel.all.cpu.irq.soft') )
+
+# Disk -----------------------------------------------------------------
+
+ def init_disk_metrics(self):
+ self.metrics += ['disk.all.read', 'disk.all.write',
+ 'disk.all.read_bytes', 'disk.all.write_bytes',
+ 'disk.all.read_merge', 'disk.all.write_merge',
+ 'disk.dev.avactive',
+ 'disk.dev.blkread', 'disk.dev.blkwrite',
+ 'disk.dev.read', 'disk.dev.read_bytes',
+ 'disk.dev.read_merge', 'disk.dev.total',
+ 'disk.dev.write','disk.dev.write_bytes',
+ 'disk.dev.write_merge',
+ 'disk.partitions.blkread', 'disk.partitions.blkwrite',
+ 'disk.partitions.read', 'disk.partitions.write',
+ 'hinv.map.lvname'
+ ]
+
+# Memory -----------------------------------------------------------------
+
+ def init_memory_metrics(self):
+ self.metrics += ['mem.freemem', 'mem.physmem', 'mem.util.anonpages',
+ 'mem.util.bufmem',
+ 'mem.util.cached', 'mem.util.commitLimit',
+ 'mem.util.committed_AS',
+ 'mem.util.inactive',
+ 'mem.util.inactive', 'mem.util.mapped',
+ 'mem.util.mlocked',
+ 'mem.util.shmem', 'mem.util.slab',
+ 'mem.util.swapFree',
+ 'mem.util.swapTotal', 'mem.util.used',
+ 'mem.vmstat.allocstall', 'mem.vmstat.pgfault',
+ 'mem.vmstat.pginodesteal',
+ 'mem.vmstat.pgmajfault', 'mem.vmstat.pgpgin',
+ 'mem.vmstat.pgpgout', 'mem.vmstat.pswpin',
+ 'mem.vmstat.pswpout', 'mem.vmstat.slabs_scanned',
+ 'swap.free', 'swap.pagesin',
+ 'swap.pagesout', 'swap.used' ]
+
+
+# Network -----------------------------------------------------------------
+
+ def init_network_metrics(self):
+ self.metrics += ['network.interface.in.bytes',
+ 'network.interface.in.packets',
+ 'network.interface.out.bytes',
+ 'network.interface.out.packets',
+ 'network.interface.in.mcasts',
+ 'network.interface.total.mcasts',
+ 'network.interface.in.compressed',
+ 'network.interface.out.compressed',
+ 'network.interface.in.errors',
+ 'network.interface.out.errors',
+ 'network.icmp.inmsgs',
+ 'network.icmp.outmsgs',
+ 'network.ip.forwdatagrams', 'network.ip.indelivers',
+ 'network.ip.inreceives', 'network.ip.outrequests',
+ 'network.tcp.activeopens',
+ 'network.tcp.insegs',
+ 'network.tcp.outsegs',
+ 'network.tcp.passiveopens',
+ 'network.udp.indatagrams',
+ 'network.udp.outdatagrams' ]
+
+# Process -----------------------------------------------------------------
+
+ def init_process_metrics(self):
+ self.metrics += ['proc.id.uid', 'proc.id.uid_nm',
+ 'proc.memory.datrss', 'proc.memory.librss',
+ 'proc.memory.textrss', 'proc.memory.vmstack',
+ 'proc.nprocs', 'proc.psinfo.cmd',
+ 'proc.psinfo.maj_flt', 'proc.psinfo.minflt',
+ 'proc.psinfo.pid',
+ 'proc.psinfo.rss', 'proc.psinfo.sname',
+ 'proc.psinfo.stime', 'proc.psinfo.threads',
+ 'proc.psinfo.utime', 'proc.psinfo.vsize',
+ 'proc.runq.runnable', 'proc.runq.sleeping',
+ 'proc.runq.blocked', 'proc.runq.defunct',
+ ]
+ self.diff_metrics += ['proc.psinfo.rss', 'proc.psinfo.vsize']
+
+# Interrupt --------------------------------------------------------------
+
+ def init_interrupt_metrics(self):
+ self.metrics += ['kernel.percpu.interrupts.MCP',
+ 'kernel.percpu.interrupts.MCE',
+ 'kernel.percpu.interrupts.THR',
+ 'kernel.percpu.interrupts.TRM',
+ 'kernel.percpu.interrupts.TLB',
+ 'kernel.percpu.interrupts.CAL',
+ 'kernel.percpu.interrupts.RES',
+ 'kernel.percpu.interrupts.RTR',
+ 'kernel.percpu.interrupts.IWI',
+ 'kernel.percpu.interrupts.PMI',
+ 'kernel.percpu.interrupts.SPU',
+ 'kernel.percpu.interrupts.LOC',
+ 'kernel.percpu.interrupts.line46',
+ 'kernel.percpu.interrupts.line45',
+ 'kernel.percpu.interrupts.line44',
+ 'kernel.percpu.interrupts.line43',
+ 'kernel.percpu.interrupts.line42',
+ 'kernel.percpu.interrupts.line41',
+ 'kernel.percpu.interrupts.line40',
+ 'kernel.percpu.interrupts.line23',
+ 'kernel.percpu.interrupts.line19',
+ 'kernel.percpu.interrupts.line18',
+ 'kernel.percpu.interrupts.line16',
+ 'kernel.percpu.interrupts.line12',
+ 'kernel.percpu.interrupts.line9',
+ 'kernel.percpu.interrupts.line8',
+ 'kernel.percpu.interrupts.line1',
+ 'kernel.percpu.interrupts.line0',
+ ]
+
diff --git a/src/python/pmapi.c b/src/python/pmapi.c
new file mode 100644
index 0000000..6886214
--- /dev/null
+++ b/src/python/pmapi.c
@@ -0,0 +1,1364 @@
+/*
+ * Copyright (C) 2012-2014 Red Hat.
+ * Copyright (C) 2009-2012 Michael T. Werner
+ *
+ * This file is part of the "pcp" module, the python interfaces for the
+ * Performance Co-Pilot toolkit.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**************************************************************************\
+** **
+** This C extension module mainly serves the purpose of loading constants **
+** from PCP headers into the module dictionary. The PMAPI functions and **
+** data structures are wrapped in pmapi.py and friends, using ctypes. **
+** **
+\**************************************************************************/
+
+#include <Python.h>
+#include <pcp/pmapi.h>
+#include <pcp/impl.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define MOD_ERROR_VAL NULL
+#define MOD_SUCCESS_VAL(val) val
+#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+ ob = PyModule_Create(&moduledef);
+#else
+#define MOD_ERROR_VAL
+#define MOD_SUCCESS_VAL(val)
+#define MOD_INIT(name) void init##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static pmOptions options;
+static int longOptionsCount;
+static PyObject *optionCallback;
+static PyObject *overridesCallback;
+
+static void
+dict_add_unsigned(PyObject *dict, char *symbol, unsigned long value)
+{
+ PyObject *pyvalue = PyLong_FromUnsignedLong(value);
+ PyDict_SetItemString(dict, symbol, pyvalue);
+ Py_XDECREF(pyvalue);
+}
+
+static void
+dict_add(PyObject *dict, char *symbol, long value)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyvalue = PyLong_FromLong(value);
+#else
+ PyObject *pyvalue = PyInt_FromLong(value);
+#endif
+ PyDict_SetItemString(dict, symbol, pyvalue);
+ Py_XDECREF(pyvalue);
+}
+
+static void
+edict_add(PyObject *dict, PyObject *edict, char *symbol, long value)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyvalue = PyLong_FromLong(value);
+ PyObject *pysymbol = PyUnicode_FromString(symbol);
+#else
+ PyObject *pyvalue = PyInt_FromLong(value);
+ PyObject *pysymbol = PyString_FromString(symbol);
+#endif
+
+ PyDict_SetItemString(dict, symbol, pyvalue);
+ PyDict_SetItem(edict, pyvalue, pysymbol);
+ Py_XDECREF(pysymbol);
+ Py_XDECREF(pyvalue);
+}
+
+static PyObject *
+setExtendedTimeBase(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int type;
+ char *keyword_list[] = {"type", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:PM_XTB_SET", keyword_list, &type))
+ return NULL;
+ return Py_BuildValue("i", PM_XTB_SET(type));
+}
+
+static PyObject *
+getExtendedTimeBase(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int mode;
+ char *keyword_list[] = {"mode", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:PM_XTB_GET", keyword_list, &mode))
+ return NULL;
+ return Py_BuildValue("i", PM_XTB_GET(mode));
+}
+
+static PyObject *
+timevalSleep(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ struct timeval ctv;
+ long seconds, useconds;
+ char *keyword_list[] = {"seconds", "useconds", NULL};
+ extern void __pmtimevalSleep(struct timeval);
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "ll:pmtimevalSleep", keyword_list, &seconds, &useconds))
+ return NULL;
+ ctv.tv_sec = seconds;
+ ctv.tv_usec = useconds;
+ __pmtimevalSleep(ctv);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+timevalToReal(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ struct timeval ctv;
+ long seconds, useconds;
+ char *keyword_list[] = {"seconds", "useconds", NULL};
+ extern double __pmtimevalToReal(const struct timeval *);
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "ll:pmtimevalToReal", keyword_list, &seconds, &useconds))
+ return NULL;
+ ctv.tv_sec = seconds;
+ ctv.tv_usec = useconds;
+ return Py_BuildValue("d", __pmtimevalToReal(&ctv));
+}
+
+static PyObject *
+setIdentity(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *name;
+ char *keyword_list[] = {"name", NULL};
+ extern int __pmSetProcessIdentity(const char *);
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetProcessIdentity", keyword_list, &name))
+ return NULL;
+ return Py_BuildValue("i", __pmSetProcessIdentity(name));
+}
+
+/*
+ * Common command line option handling code - wrapping pmOptions
+ */
+
+static int
+addLongOption(pmLongOptions *opt, int duplicate)
+{
+ size_t bytes;
+ pmLongOptions *lp;
+ int index = longOptionsCount;
+
+ if (!opt->long_opt)
+ return -EINVAL;
+
+ bytes = (index + 2) * sizeof(pmLongOptions); /* +2 for PMAPI_OPTIONS_END */
+ if ((lp = realloc(options.long_options, bytes)) == NULL)
+ return -ENOMEM;
+ options.long_options = lp;
+
+ if (!duplicate)
+ goto update;
+
+ if ((opt->long_opt = strdup(opt->long_opt)) == NULL)
+ return -ENOMEM;
+ if (opt->argname &&
+ (opt->argname = strdup(opt->argname)) == NULL) {
+ free((char *)opt->long_opt);
+ return -ENOMEM;
+ }
+ if (opt->message &&
+ (opt->message = strdup(opt->message)) == NULL) {
+ free((char *)opt->long_opt);
+ free((char *)opt->argname);
+ return -ENOMEM;
+ }
+
+update:
+ lp[index].long_opt = opt->long_opt;
+ lp[index].has_arg = opt->has_arg;
+ lp[index].short_opt = opt->short_opt;
+ lp[index].argname = opt->argname;
+ lp[index].message = opt->message;
+ memset(&lp[index+1], 0, sizeof(pmLongOptions)); /* PMAPI_OPTIONS_END */
+ longOptionsCount++;
+ return 0;
+}
+
+static PyObject *
+setLongOptionHeader(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ pmLongOptions header = PMAPI_OPTIONS_HEADER("");
+ char *keyword_list[] = {"heading", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetLongOptionHeader", keyword_list,
+ &header.message))
+ return NULL;
+ if ((header.message = strdup(header.message)) == NULL)
+ return PyErr_NoMemory();
+
+ if (addLongOption(&header, 0) < 0) {
+ free((char *)header.message);
+ return PyErr_NoMemory();
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setLongOptionText(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ pmLongOptions text = PMAPI_OPTIONS_TEXT("");
+ char *keyword_list[] = {"text", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetLongOptionText", keyword_list,
+ &text.message))
+ return NULL;
+ if ((text.message = strdup(text.message)) == NULL)
+ return PyErr_NoMemory();
+
+ if (addLongOption(&text, 0) < 0) {
+ free((char *)text.message);
+ return PyErr_NoMemory();
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+addLongOptionObject(pmLongOptions *option)
+{
+ if (addLongOption(option, 1) < 0)
+ return PyErr_NoMemory();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setLongOption(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *short_opt = NULL;
+ pmLongOptions option = { 0 };
+ char *keyword_list[] = {"long_opt", "has_arg", "short_opt",
+ "argname", "message", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "sisss:pmSetLongOption", keyword_list,
+ &option.long_opt, &option.has_arg, &short_opt,
+ &option.argname, &option.message))
+ return NULL;
+ if (short_opt)
+ option.short_opt = (int)short_opt[0];
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionAlign(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_ALIGN;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionArchive(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_ARCHIVE;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionHostList(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_HOST_LIST;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionArchiveList(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_ARCHIVE_LIST;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionArchiveFolio(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_ARCHIVE_FOLIO;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionDebug(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_DEBUG;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionGuiMode(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_GUIMODE;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionHost(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_HOST;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionHostsFile(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_HOSTSFILE;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionSpecLocal(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_SPECLOCAL;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionLocalPMDA(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_LOCALPMDA;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionOrigin(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_ORIGIN;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionGuiPort(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_GUIPORT;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionStart(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_START;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionSamples(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_SAMPLES;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionFinish(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_FINISH;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionInterval(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_INTERVAL;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionVersion(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_VERSION;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionTimeZone(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_TIMEZONE;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionHostZone(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_HOSTZONE;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+setLongOptionHelp(PyObject *self, PyObject *args)
+{
+ pmLongOptions option = PMOPT_HELP;
+ return addLongOptionObject(&option);
+}
+
+static PyObject *
+resetAllOptions(PyObject *self, PyObject *args)
+{
+ pmFreeOptions(&options);
+ memset(&options, 0, sizeof(options));
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setShortOptions(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *short_opts;
+ char *keyword_list[] = {"short_options", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetShortOptions", keyword_list, &short_opts))
+ return NULL;
+
+ if ((short_opts = strdup(short_opts ? short_opts : "")) == NULL)
+ return PyErr_NoMemory();
+ if (options.short_options)
+ free((void *)options.short_options);
+ options.short_options = short_opts;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setShortUsage(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *short_usage;
+ char *keyword_list[] = {"short_usage", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetShortUsage", keyword_list, &short_usage))
+ return NULL;
+
+ if ((short_usage = strdup(short_usage ? short_usage : "")) == NULL)
+ return PyErr_NoMemory();
+ if (options.short_usage)
+ free((void *)options.short_usage);
+ options.short_usage = short_usage;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionFlags(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int flags;
+ char *keyword_list[] = {"flags", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:pmSetOptionFlags", keyword_list, &flags))
+ return NULL;
+
+ options.flags |= flags;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionArchiveFolio(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *folio;
+ char *keyword_list[] = {PMLONGOPT_ARCHIVE_FOLIO, NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetOptionArchiveFolio", keyword_list, &folio))
+ return NULL;
+
+ __pmAddOptArchiveFolio(&options, folio);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionArchiveList(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *archives;
+ char *keyword_list[] = {PMLONGOPT_ARCHIVE_LIST, NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetOptionArchiveList", keyword_list, &archives))
+ return NULL;
+
+ __pmAddOptArchiveList(&options, archives);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionHostList(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *hosts;
+ char *keyword_list[] = {PMLONGOPT_HOST_LIST, NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetOptionHostList", keyword_list, &hosts))
+ return NULL;
+
+ __pmAddOptHostList(&options, hosts);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionSamples(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *count, *endnum;
+ char *keyword_list[] = {"count", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetOptionSamples", keyword_list, &count))
+ return NULL;
+
+ if (options.finish_optarg) {
+ pmprintf("%s: at most one of finish time and sample count allowed\n",
+ pmProgname);
+ options.errors++;
+ } else {
+ options.samples = (int)strtol(count, &endnum, 10);
+ if (*endnum != '\0' || options.samples < 0) {
+ pmprintf("%s: sample count must be a positive numeric argument\n",
+ pmProgname);
+ options.errors++;
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionInterval(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *delta, *errmsg;
+ char *keyword_list[] = {"delta", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmSetOptionInterval", keyword_list, &delta))
+ return NULL;
+
+ if (pmParseInterval(delta, &options.interval, &errmsg) < 0) {
+ pmprintf("%s: interval argument not in pmParseInterval(3) format:\n",
+ pmProgname);
+ pmprintf("%s\n", errmsg);
+ options.errors++;
+ free(errmsg);
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionErrors(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int errors;
+ char *keyword_list[] = {"errors", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:pmSetOptionErrors", keyword_list, &errors))
+ return NULL;
+
+ options.errors = errors;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static int
+override_callback(int opt, pmOptions *opts)
+{
+ PyObject *arglist, *result;
+ char argstring[2] = { (char)opt, '\0' };
+ int sts;
+
+ arglist = Py_BuildValue("(s)", argstring);
+ if (!arglist) {
+ PyErr_Print();
+ return -ENOMEM;
+ }
+ result = PyEval_CallObject(overridesCallback, arglist);
+ Py_DECREF(arglist);
+ if (!result) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ }
+ sts = PyLong_AsLong(result);
+ Py_DECREF(result);
+ return sts;
+}
+
+static void
+options_callback(int opt, pmOptions *opts)
+{
+ PyObject *arglist, *result;
+ char argstring[2] = { (char)opt, '\0' };
+
+ arglist = Py_BuildValue("(ssi)", argstring, options.optarg, options.index);
+ if (!arglist) {
+ PyErr_Print();
+ } else {
+ result = PyEval_CallObject(optionCallback, arglist);
+ Py_DECREF(arglist);
+ if (!result) {
+ PyErr_Print();
+ return;
+ }
+ Py_DECREF(result);
+ }
+}
+
+static PyObject *
+getNonOptionsFromList(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int argc, length, i;
+ PyObject *result;
+ PyObject *pyargv = NULL;
+ char *keyword_list[] = {"argv", NULL};
+
+ /* Caller must perform pmGetOptions before running this, check */
+ if (!(options.flags & PM_OPTFLAG_DONE))
+ return NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:pmGetNonOptionsFromList", keyword_list, &pyargv))
+ if (pyargv == NULL)
+ return NULL;
+
+ if (!PyList_Check(pyargv)) {
+ PyErr_SetString(PyExc_TypeError, "pmGetNonOptionsFromList uses a list");
+ return NULL;
+ }
+
+ length = 0;
+ if ((argc = PyList_GET_SIZE(pyargv)) > 0)
+ length = argc - options.optind;
+
+ if (length <= 0) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ if ((result = PyList_New(length)) == NULL)
+ return PyErr_NoMemory();
+
+ for (i = 0; i < length; i++) {
+ PyObject *pyarg = PyList_GET_ITEM(pyargv, options.optind + i);
+ PyList_SET_ITEM(result, i, pyarg);
+ }
+ Py_INCREF(result);
+ return result;
+}
+
+static PyObject *
+getOptionsFromList(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int i, argc;
+ char **argv;
+ PyObject *pyargv = NULL;
+ char *keyword_list[] = {"argv", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:pmGetOptionsFromList", keyword_list, &pyargv))
+ return NULL;
+
+ if (pyargv == NULL)
+ return Py_BuildValue("i", 0);
+
+ if (!PyList_Check(pyargv)) {
+ PyErr_SetString(PyExc_TypeError, "pmGetOptionsFromList uses a list");
+ Py_DECREF(pyargv);
+ return NULL;
+ }
+
+ if ((argc = PyList_GET_SIZE(pyargv)) <= 0) {
+ Py_DECREF(pyargv);
+ return Py_BuildValue("i", 0);
+ }
+
+ if ((argv = malloc(argc * sizeof(char *))) == NULL) {
+ Py_DECREF(pyargv);
+ return PyErr_NoMemory();
+ }
+
+ for (i = 0; i < argc; i++) {
+ PyObject *pyarg = PyList_GET_ITEM(pyargv, i);
+#if PY_MAJOR_VERSION >= 3
+ char *string = PyUnicode_AsUTF8(pyarg);
+#else
+ char *string = PyString_AsString(pyarg);
+#endif
+
+ /* argv[0] parameter will be used for pmProgname, so need to
+ * ensure the memory that backs it will be with us forever.
+ */
+ if (i == 0 && (string = strdup(string)) == NULL) {
+ free(argv);
+ Py_DECREF(pyargv);
+ return PyErr_NoMemory();
+ }
+ argv[i] = string;
+ }
+
+ if (overridesCallback)
+ options.override = override_callback;
+ while ((i = pmGetOptions(argc, argv, &options)) != -1)
+ options_callback(i, &options);
+ free(argv);
+
+ if (options.flags & PM_OPTFLAG_EXIT)
+ return Py_BuildValue("i", PM_ERR_APPVERSION);
+
+ return Py_BuildValue("i", options.errors);
+}
+
+static PyObject *
+setContextOptions(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int sts, ctx, step, mode, delta;
+ char *keyword_list[] = {"context", "mode", "delta", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "iii:pmSetContextOptions", keyword_list, &ctx, &mode, &delta))
+ return NULL;
+
+ /* complete time window and timezone setup */
+ if ((sts = pmGetContextOptions(ctx, &options)) < 0)
+ return Py_BuildValue("i", sts);
+
+ /* initial archive mode, position and delta */
+ if (options.context == PM_CONTEXT_ARCHIVE &&
+ (options.flags & PM_OPTFLAG_BOUNDARIES)) {
+ const int SECONDS_IN_24_DAYS = 2073600;
+ struct timeval interval = options.interval;
+ struct timeval position = options.origin;
+
+ if (interval.tv_sec > SECONDS_IN_24_DAYS) {
+ step = interval.tv_sec;
+ mode |= PM_XTB_SET(PM_TIME_SEC);
+ } else {
+ if (interval.tv_sec == 0 && interval.tv_usec == 0)
+ interval.tv_sec = delta;
+ step = interval.tv_sec * 1e3 + interval.tv_usec / 1e3;
+ mode |= PM_XTB_SET(PM_TIME_MSEC);
+ }
+ if ((sts = pmSetMode(mode, &position, step)) < 0) {
+ pmprintf("%s: pmSetMode: %s\n", pmProgname, pmErrStr(sts));
+ options.flags |= PM_OPTFLAG_RUNTIME_ERR;
+ options.errors++;
+ }
+ }
+ return Py_BuildValue("i", sts);
+}
+
+static PyObject *
+usageMessage(PyObject *self, PyObject *args)
+{
+ pmUsageMessage(&options);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOverrideCallback(PyObject *self, PyObject *args)
+{
+ PyObject *func;
+
+ if (!PyArg_ParseTuple(args, "O:pmSetOverrideCallback", &func))
+ return NULL;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError,
+ "pmSetOverrideCallback parameter not callable");
+ return NULL;
+ }
+ Py_XINCREF(func);
+ Py_XDECREF(overridesCallback);
+ overridesCallback = func;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+setOptionCallback(PyObject *self, PyObject *args)
+{
+ PyObject *func;
+
+ if (!PyArg_ParseTuple(args, "O:pmSetOptionCallback", &func))
+ return NULL;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError,
+ "pmSetOptionCallback parameter not callable");
+ return NULL;
+ }
+ Py_XINCREF(func);
+ Py_XDECREF(optionCallback);
+ optionCallback = func;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionErrors(PyObject *self, PyObject *args)
+{
+ return Py_BuildValue("i", options.errors);
+}
+
+static PyObject *
+getOptionFlags(PyObject *self, PyObject *args)
+{
+ return Py_BuildValue("i", options.flags);
+}
+
+static PyObject *
+getOptionContext(PyObject *self, PyObject *args)
+{
+ if (options.context > 0)
+ return Py_BuildValue("i", options.context);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionHosts(PyObject *self, PyObject *args)
+{
+ PyObject *result;
+ int i;
+
+ if (options.nhosts > 0) {
+ if ((result = PyList_New(options.nhosts)) == NULL)
+ return PyErr_NoMemory();
+ for (i = 0; i < options.nhosts; i++) {
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyent = PyUnicode_FromString(options.hosts[i]);
+#else
+ PyObject *pyent = PyString_FromString(options.hosts[i]);
+#endif
+ PyList_SET_ITEM(result, i, pyent);
+ }
+ Py_INCREF(result);
+ return result;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionArchives(PyObject *self, PyObject *args)
+{
+ PyObject *result;
+ int i;
+
+ if (options.narchives > 0) {
+ if ((result = PyList_New(options.narchives)) == NULL)
+ return PyErr_NoMemory();
+ for (i = 0; i < options.narchives; i++) {
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyent = PyUnicode_FromString(options.archives[i]);
+#else
+ PyObject *pyent = PyString_FromString(options.archives[i]);
+#endif
+ PyList_SET_ITEM(result, i, pyent);
+ }
+ Py_INCREF(result);
+ return result;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionStart_sec(PyObject *self, PyObject *args)
+{
+ if (options.start.tv_sec || options.start.tv_usec)
+ return Py_BuildValue("l", options.start.tv_sec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionStart_usec(PyObject *self, PyObject *args)
+{
+ if (options.start.tv_sec || options.start.tv_usec)
+ return Py_BuildValue("l", options.start.tv_usec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionFinish_optarg(PyObject *self, PyObject *args)
+{
+ if (options.finish_optarg)
+ return Py_BuildValue("s", options.finish_optarg);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionFinish_sec(PyObject *self, PyObject *args)
+{
+ if (options.finish.tv_sec || options.finish.tv_usec)
+ return Py_BuildValue("l", options.finish.tv_sec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionFinish_usec(PyObject *self, PyObject *args)
+{
+ if (options.finish.tv_sec || options.finish.tv_usec)
+ return Py_BuildValue("l", options.finish.tv_usec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionOrigin_sec(PyObject *self, PyObject *args)
+{
+ if (options.origin.tv_sec || options.origin.tv_usec)
+ return Py_BuildValue("l", options.origin.tv_sec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionOrigin_usec(PyObject *self, PyObject *args)
+{
+ if (options.origin.tv_sec || options.origin.tv_usec)
+ return Py_BuildValue("l", options.origin.tv_usec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionInterval_sec(PyObject *self, PyObject *args)
+{
+ if (options.interval.tv_sec || options.interval.tv_usec)
+ return Py_BuildValue("l", options.interval.tv_sec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionInterval_usec(PyObject *self, PyObject *args)
+{
+ if (options.interval.tv_sec || options.interval.tv_usec)
+ return Py_BuildValue("l", options.interval.tv_usec);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionSamples(PyObject *self, PyObject *args)
+{
+ if (options.samples)
+ return Py_BuildValue("i", options.samples);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+getOptionTimezone(PyObject *self, PyObject *args)
+{
+ if (options.timezone)
+ return Py_BuildValue("s", options.timezone);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+static PyMethodDef methods[] = {
+ { .ml_name = "PM_XTB_SET",
+ .ml_meth = (PyCFunction) setExtendedTimeBase,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "PM_XTB_GET",
+ .ml_meth = (PyCFunction) getExtendedTimeBase,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmtimevalSleep",
+ .ml_meth = (PyCFunction) timevalSleep,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmtimevalToReal",
+ .ml_meth = (PyCFunction) timevalToReal,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetProcessIdentity",
+ .ml_meth = (PyCFunction) setIdentity,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmResetAllOptions",
+ .ml_meth = (PyCFunction) resetAllOptions,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHeader",
+ .ml_meth = (PyCFunction) setLongOptionHeader,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetLongOptionText",
+ .ml_meth = (PyCFunction) setLongOptionText,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetLongOption",
+ .ml_meth = (PyCFunction) setLongOption,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetLongOptionAlign",
+ .ml_meth = (PyCFunction) setLongOptionAlign,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionArchive",
+ .ml_meth = (PyCFunction) setLongOptionArchive,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionArchiveList",
+ .ml_meth = (PyCFunction) setLongOptionArchiveList,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionArchiveFolio",
+ .ml_meth = (PyCFunction) setLongOptionArchiveFolio,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionDebug",
+ .ml_meth = (PyCFunction) setLongOptionDebug,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionGuiMode",
+ .ml_meth = (PyCFunction) setLongOptionGuiMode,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHost",
+ .ml_meth = (PyCFunction) setLongOptionHost,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHostList",
+ .ml_meth = (PyCFunction) setLongOptionHostList,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHostsFile",
+ .ml_meth = (PyCFunction) setLongOptionHostsFile,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionSpecLocal",
+ .ml_meth = (PyCFunction) setLongOptionSpecLocal,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionLocalPMDA",
+ .ml_meth = (PyCFunction) setLongOptionLocalPMDA,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionOrigin",
+ .ml_meth = (PyCFunction) setLongOptionOrigin,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionGuiPort",
+ .ml_meth = (PyCFunction) setLongOptionGuiPort,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionStart",
+ .ml_meth = (PyCFunction) setLongOptionStart,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionSamples",
+ .ml_meth = (PyCFunction) setLongOptionSamples,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionFinish",
+ .ml_meth = (PyCFunction) setLongOptionFinish,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionInterval",
+ .ml_meth = (PyCFunction) setLongOptionInterval,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionVersion",
+ .ml_meth = (PyCFunction) setLongOptionVersion,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionTimeZone",
+ .ml_meth = (PyCFunction) setLongOptionTimeZone,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHostZone",
+ .ml_meth = (PyCFunction) setLongOptionHostZone,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetLongOptionHelp",
+ .ml_meth = (PyCFunction) setLongOptionHelp,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetShortOptions",
+ .ml_meth = (PyCFunction) setShortOptions,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetShortUsage",
+ .ml_meth = (PyCFunction) setShortUsage,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetOptionFlags",
+ .ml_meth = (PyCFunction) setOptionFlags,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetOptionErrors",
+ .ml_meth = (PyCFunction) setOptionErrors,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmGetOptionErrors",
+ .ml_meth = (PyCFunction) getOptionErrors,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionFlags",
+ .ml_meth = (PyCFunction) getOptionFlags,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionsFromList",
+ .ml_meth = (PyCFunction) getOptionsFromList,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmGetNonOptionsFromList",
+ .ml_meth = (PyCFunction) getNonOptionsFromList,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetContextOptions",
+ .ml_meth = (PyCFunction) setContextOptions,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS},
+ { .ml_name = "pmUsageMessage",
+ .ml_meth = (PyCFunction) usageMessage,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetOptionCallback",
+ .ml_meth = (PyCFunction) setOptionCallback,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetOverrideCallback",
+ .ml_meth = (PyCFunction) setOverrideCallback,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmGetOptionContext",
+ .ml_meth = (PyCFunction) getOptionContext,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionHosts",
+ .ml_meth = (PyCFunction) getOptionHosts,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionArchives",
+ .ml_meth = (PyCFunction) getOptionArchives,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionStart_sec",
+ .ml_meth = (PyCFunction) getOptionStart_sec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionStart_usec",
+ .ml_meth = (PyCFunction) getOptionStart_usec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionFinish_optarg",
+ .ml_meth = (PyCFunction) getOptionFinish_optarg,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionFinish_sec",
+ .ml_meth = (PyCFunction) getOptionFinish_sec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionFinish_usec",
+ .ml_meth = (PyCFunction) getOptionFinish_usec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionOrigin_sec",
+ .ml_meth = (PyCFunction) getOptionOrigin_sec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionOrigin_usec",
+ .ml_meth = (PyCFunction) getOptionOrigin_usec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionInterval_sec",
+ .ml_meth = (PyCFunction) getOptionInterval_sec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmGetOptionInterval_usec",
+ .ml_meth = (PyCFunction) getOptionInterval_usec,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetOptionInterval",
+ .ml_meth = (PyCFunction) setOptionInterval,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmGetOptionSamples",
+ .ml_meth = (PyCFunction) getOptionSamples,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetOptionSamples",
+ .ml_meth = (PyCFunction) setOptionSamples,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmGetOptionTimezone",
+ .ml_meth = (PyCFunction) getOptionTimezone,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmSetOptionArchiveList",
+ .ml_meth = (PyCFunction) setOptionArchiveList,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetOptionArchiveFolio",
+ .ml_meth = (PyCFunction) setOptionArchiveFolio,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { .ml_name = "pmSetOptionHostList",
+ .ml_meth = (PyCFunction) setOptionHostList,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS },
+ { NULL }
+};
+
+/* called when the module is initialized. */
+MOD_INIT(cpmapi)
+{
+ PyObject *module, *dict, *edict;
+
+ MOD_DEF(module, "cpmapi", NULL, methods);
+ if (module == NULL)
+ return MOD_ERROR_VAL;
+
+ dict = PyModule_GetDict(module);
+ edict = PyDict_New();
+ Py_INCREF(edict);
+ PyModule_AddObject(module, "pmErrSymDict", edict);
+
+ dict_add(dict, "PMAPI_VERSION_2", PMAPI_VERSION_2);
+ dict_add(dict, "PMAPI_VERSION", PMAPI_VERSION);
+
+ dict_add_unsigned(dict, "PM_ID_NULL", PM_ID_NULL);
+ dict_add_unsigned(dict, "PM_INDOM_NULL", PM_INDOM_NULL);
+ dict_add_unsigned(dict, "PM_IN_NULL", PM_IN_NULL);
+
+ dict_add_unsigned(dict, "PM_NS_DEFAULT", 0);
+
+#ifdef HAVE_BITFIELDS_LTOR
+ dict_add(dict, "HAVE_BITFIELDS_LTOR", 1);
+ dict_add(dict, "HAVE_BITFIELDS_RTOL", 0);
+#else
+ dict_add(dict, "HAVE_BITFIELDS_LTOR", 0);
+ dict_add(dict, "HAVE_BITFIELDS_RTOL", 1);
+#endif
+
+ dict_add(dict, "PM_SPACE_BYTE", PM_SPACE_BYTE);
+ dict_add(dict, "PM_SPACE_KBYTE", PM_SPACE_KBYTE);
+ dict_add(dict, "PM_SPACE_MBYTE", PM_SPACE_MBYTE);
+ dict_add(dict, "PM_SPACE_GBYTE", PM_SPACE_GBYTE);
+ dict_add(dict, "PM_SPACE_TBYTE", PM_SPACE_TBYTE);
+ dict_add(dict, "PM_SPACE_PBYTE", PM_SPACE_PBYTE);
+ dict_add(dict, "PM_SPACE_EBYTE", PM_SPACE_EBYTE);
+
+ dict_add(dict, "PM_TIME_NSEC", PM_TIME_NSEC);
+ dict_add(dict, "PM_TIME_USEC", PM_TIME_USEC);
+ dict_add(dict, "PM_TIME_MSEC", PM_TIME_MSEC);
+ dict_add(dict, "PM_TIME_SEC", PM_TIME_SEC);
+ dict_add(dict, "PM_TIME_MIN", PM_TIME_MIN);
+ dict_add(dict, "PM_TIME_HOUR", PM_TIME_HOUR);
+ dict_add(dict, "PM_COUNT_ONE", PM_COUNT_ONE);
+
+ dict_add(dict, "PM_TYPE_NOSUPPORT", PM_TYPE_NOSUPPORT);
+ dict_add(dict, "PM_TYPE_32", PM_TYPE_32);
+ dict_add(dict, "PM_TYPE_U32", PM_TYPE_U32);
+ dict_add(dict, "PM_TYPE_64", PM_TYPE_64);
+ dict_add(dict, "PM_TYPE_U64", PM_TYPE_U64);
+ dict_add(dict, "PM_TYPE_FLOAT", PM_TYPE_FLOAT);
+ dict_add(dict, "PM_TYPE_DOUBLE", PM_TYPE_DOUBLE);
+ dict_add(dict, "PM_TYPE_STRING", PM_TYPE_STRING);
+ dict_add(dict, "PM_TYPE_AGGREGATE", PM_TYPE_AGGREGATE);
+ dict_add(dict, "PM_TYPE_AGGREGATE_STATIC", PM_TYPE_AGGREGATE_STATIC);
+ dict_add(dict, "PM_TYPE_EVENT", PM_TYPE_EVENT);
+ dict_add(dict, "PM_TYPE_HIGHRES_EVENT", PM_TYPE_HIGHRES_EVENT);
+ dict_add(dict, "PM_TYPE_UNKNOWN", PM_TYPE_UNKNOWN);
+
+ dict_add(dict, "PM_SEM_COUNTER", PM_SEM_COUNTER);
+ dict_add(dict, "PM_SEM_INSTANT", PM_SEM_INSTANT);
+ dict_add(dict, "PM_SEM_DISCRETE", PM_SEM_DISCRETE);
+
+ dict_add(dict, "PMNS_LOCAL", PMNS_LOCAL);
+ dict_add(dict, "PMNS_REMOTE", PMNS_REMOTE);
+ dict_add(dict, "PMNS_ARCHIVE", PMNS_ARCHIVE);
+ dict_add(dict, "PMNS_LEAF_STATUS", PMNS_LEAF_STATUS);
+ dict_add(dict, "PMNS_NONLEAF_STATUS", PMNS_NONLEAF_STATUS);
+
+ dict_add(dict, "PM_CONTEXT_UNDEF", PM_CONTEXT_UNDEF);
+ dict_add(dict, "PM_CONTEXT_HOST", PM_CONTEXT_HOST);
+ dict_add(dict, "PM_CONTEXT_ARCHIVE", PM_CONTEXT_ARCHIVE);
+ dict_add(dict, "PM_CONTEXT_LOCAL", PM_CONTEXT_LOCAL);
+ dict_add(dict, "PM_CONTEXT_TYPEMASK", PM_CONTEXT_TYPEMASK);
+ dict_add(dict, "PM_CTXFLAG_SECURE", PM_CTXFLAG_SECURE);
+ dict_add(dict, "PM_CTXFLAG_COMPRESS", PM_CTXFLAG_COMPRESS);
+ dict_add(dict, "PM_CTXFLAG_RELAXED", PM_CTXFLAG_RELAXED);
+
+ dict_add(dict, "PM_VAL_HDR_SIZE", PM_VAL_HDR_SIZE);
+ dict_add(dict, "PM_VAL_VLEN_MAX", PM_VAL_VLEN_MAX);
+ dict_add(dict, "PM_VAL_INSITU", PM_VAL_INSITU);
+ dict_add(dict, "PM_VAL_DPTR", PM_VAL_DPTR);
+ dict_add(dict, "PM_VAL_SPTR", PM_VAL_SPTR);
+
+ dict_add(dict, "PMCD_NO_CHANGE", PMCD_NO_CHANGE);
+ dict_add(dict, "PMCD_ADD_AGENT", PMCD_ADD_AGENT);
+ dict_add(dict, "PMCD_RESTART_AGENT", PMCD_RESTART_AGENT);
+ dict_add(dict, "PMCD_DROP_AGENT", PMCD_DROP_AGENT);
+
+ dict_add(dict, "PM_MAXERRMSGLEN", PM_MAXERRMSGLEN);
+ dict_add(dict, "PM_TZ_MAXLEN", PM_TZ_MAXLEN);
+
+ dict_add(dict, "PM_LOG_MAXHOSTLEN", PM_LOG_MAXHOSTLEN);
+ dict_add(dict, "PM_LOG_MAGIC", PM_LOG_MAGIC);
+ dict_add(dict, "PM_LOG_VERS02", PM_LOG_VERS02);
+ dict_add(dict, "PM_LOG_VOL_TI", PM_LOG_VOL_TI);
+ dict_add(dict, "PM_LOG_VOL_META", PM_LOG_VOL_META);
+
+ dict_add(dict, "PM_MODE_LIVE", PM_MODE_LIVE);
+ dict_add(dict, "PM_MODE_INTERP", PM_MODE_INTERP);
+ dict_add(dict, "PM_MODE_FORW", PM_MODE_FORW);
+ dict_add(dict, "PM_MODE_BACK", PM_MODE_BACK);
+
+ dict_add(dict, "PM_TEXT_ONELINE", PM_TEXT_ONELINE);
+ dict_add(dict, "PM_TEXT_HELP", PM_TEXT_HELP);
+
+ dict_add(dict, "PM_XTB_FLAG", PM_XTB_FLAG);
+
+ dict_add(dict, "PM_OPTFLAG_INIT", PM_OPTFLAG_INIT);
+ dict_add(dict, "PM_OPTFLAG_DONE", PM_OPTFLAG_DONE);
+ dict_add(dict, "PM_OPTFLAG_MULTI", PM_OPTFLAG_MULTI);
+ dict_add(dict, "PM_OPTFLAG_USAGE_ERR", PM_OPTFLAG_USAGE_ERR);
+ dict_add(dict, "PM_OPTFLAG_RUNTIME_ERR", PM_OPTFLAG_RUNTIME_ERR);
+ dict_add(dict, "PM_OPTFLAG_EXIT", PM_OPTFLAG_EXIT);
+ dict_add(dict, "PM_OPTFLAG_POSIX", PM_OPTFLAG_POSIX);
+ dict_add(dict, "PM_OPTFLAG_MIXED", PM_OPTFLAG_MIXED);
+ dict_add(dict, "PM_OPTFLAG_ENV_ONLY", PM_OPTFLAG_ENV_ONLY);
+ dict_add(dict, "PM_OPTFLAG_LONG_ONLY", PM_OPTFLAG_LONG_ONLY);
+ dict_add(dict, "PM_OPTFLAG_BOUNDARIES", PM_OPTFLAG_BOUNDARIES);
+ dict_add(dict, "PM_OPTFLAG_STDOUT_TZ", PM_OPTFLAG_STDOUT_TZ);
+ dict_add(dict, "PM_OPTFLAG_NOFLUSH", PM_OPTFLAG_NOFLUSH);
+ dict_add(dict, "PM_OPTFLAG_QUIET", PM_OPTFLAG_QUIET);
+
+ dict_add(dict, "PM_EVENT_FLAG_POINT", PM_EVENT_FLAG_POINT);
+ dict_add(dict, "PM_EVENT_FLAG_START", PM_EVENT_FLAG_START);
+ dict_add(dict, "PM_EVENT_FLAG_END", PM_EVENT_FLAG_END);
+ dict_add(dict, "PM_EVENT_FLAG_ID", PM_EVENT_FLAG_ID);
+ dict_add(dict, "PM_EVENT_FLAG_PARENT", PM_EVENT_FLAG_PARENT);
+ dict_add(dict, "PM_EVENT_FLAG_MISSED", PM_EVENT_FLAG_MISSED);
+
+ edict_add(dict, edict, "PM_ERR_GENERIC", PM_ERR_GENERIC);
+ edict_add(dict, edict, "PM_ERR_PMNS", PM_ERR_PMNS);
+ edict_add(dict, edict, "PM_ERR_NOPMNS", PM_ERR_NOPMNS);
+ edict_add(dict, edict, "PM_ERR_DUPPMNS", PM_ERR_DUPPMNS);
+ edict_add(dict, edict, "PM_ERR_TEXT", PM_ERR_TEXT);
+ edict_add(dict, edict, "PM_ERR_APPVERSION", PM_ERR_APPVERSION);
+ edict_add(dict, edict, "PM_ERR_VALUE", PM_ERR_VALUE);
+ edict_add(dict, edict, "PM_ERR_TIMEOUT", PM_ERR_TIMEOUT);
+ edict_add(dict, edict, "PM_ERR_NODATA", PM_ERR_NODATA);
+ edict_add(dict, edict, "PM_ERR_RESET", PM_ERR_RESET);
+ edict_add(dict, edict, "PM_ERR_NAME", PM_ERR_NAME);
+ edict_add(dict, edict, "PM_ERR_PMID", PM_ERR_PMID);
+ edict_add(dict, edict, "PM_ERR_INDOM", PM_ERR_INDOM);
+ edict_add(dict, edict, "PM_ERR_INST", PM_ERR_INST);
+ edict_add(dict, edict, "PM_ERR_UNIT", PM_ERR_UNIT);
+ edict_add(dict, edict, "PM_ERR_CONV", PM_ERR_CONV);
+ edict_add(dict, edict, "PM_ERR_TRUNC", PM_ERR_TRUNC);
+ edict_add(dict, edict, "PM_ERR_SIGN", PM_ERR_SIGN);
+ edict_add(dict, edict, "PM_ERR_PROFILE", PM_ERR_PROFILE);
+ edict_add(dict, edict, "PM_ERR_IPC", PM_ERR_IPC);
+ edict_add(dict, edict, "PM_ERR_EOF", PM_ERR_EOF);
+ edict_add(dict, edict, "PM_ERR_NOTHOST", PM_ERR_NOTHOST);
+ edict_add(dict, edict, "PM_ERR_EOL", PM_ERR_EOL);
+ edict_add(dict, edict, "PM_ERR_MODE", PM_ERR_MODE);
+ edict_add(dict, edict, "PM_ERR_LABEL", PM_ERR_LABEL);
+ edict_add(dict, edict, "PM_ERR_LOGREC", PM_ERR_LOGREC);
+ edict_add(dict, edict, "PM_ERR_NOTARCHIVE", PM_ERR_NOTARCHIVE);
+ edict_add(dict, edict, "PM_ERR_LOGFILE", PM_ERR_LOGFILE);
+ edict_add(dict, edict, "PM_ERR_NOCONTEXT", PM_ERR_NOCONTEXT);
+ edict_add(dict, edict, "PM_ERR_PROFILESPEC", PM_ERR_PROFILESPEC);
+ edict_add(dict, edict, "PM_ERR_PMID_LOG", PM_ERR_PMID_LOG);
+ edict_add(dict, edict, "PM_ERR_INDOM_LOG", PM_ERR_INDOM_LOG);
+ edict_add(dict, edict, "PM_ERR_INST_LOG", PM_ERR_INST_LOG);
+ edict_add(dict, edict, "PM_ERR_NOPROFILE", PM_ERR_NOPROFILE);
+ edict_add(dict, edict, "PM_ERR_NOAGENT", PM_ERR_NOAGENT);
+ edict_add(dict, edict, "PM_ERR_PERMISSION", PM_ERR_PERMISSION);
+ edict_add(dict, edict, "PM_ERR_CONNLIMIT", PM_ERR_CONNLIMIT);
+ edict_add(dict, edict, "PM_ERR_AGAIN", PM_ERR_AGAIN);
+ edict_add(dict, edict, "PM_ERR_ISCONN", PM_ERR_ISCONN);
+ edict_add(dict, edict, "PM_ERR_NOTCONN", PM_ERR_NOTCONN);
+ edict_add(dict, edict, "PM_ERR_NEEDPORT", PM_ERR_NEEDPORT);
+ edict_add(dict, edict, "PM_ERR_NONLEAF", PM_ERR_NONLEAF);
+ edict_add(dict, edict, "PM_ERR_TYPE", PM_ERR_TYPE);
+ edict_add(dict, edict, "PM_ERR_THREAD", PM_ERR_THREAD);
+ edict_add(dict, edict, "PM_ERR_TOOSMALL", PM_ERR_TOOSMALL);
+ edict_add(dict, edict, "PM_ERR_TOOBIG", PM_ERR_TOOBIG);
+ edict_add(dict, edict, "PM_ERR_FAULT", PM_ERR_FAULT);
+ edict_add(dict, edict, "PM_ERR_PMDAREADY", PM_ERR_PMDAREADY);
+ edict_add(dict, edict, "PM_ERR_PMDANOTREADY", PM_ERR_PMDANOTREADY);
+ edict_add(dict, edict, "PM_ERR_NYI", PM_ERR_NYI);
+
+ return MOD_SUCCESS_VAL(module);
+}
diff --git a/src/python/pmda.c b/src/python/pmda.c
new file mode 100644
index 0000000..9ff1201
--- /dev/null
+++ b/src/python/pmda.c
@@ -0,0 +1,1098 @@
+/*
+ * Copyright (C) 2013-2014 Red Hat.
+ *
+ * This file is part of the "pcp" module, the python interfaces for the
+ * Performance Co-Pilot toolkit.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**************************************************************************\
+** **
+** This C extension module mainly serves the purpose of loading functions **
+** and macros needed to implement PMDAs in python. These are exported to **
+** python PMDAs via the pmda.py module, using ctypes. **
+** **
+\**************************************************************************/
+
+#include <Python.h>
+#include <pcp/pmapi.h>
+#include <pcp/pmda.h>
+#include <pcp/impl.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define MOD_ERROR_VAL NULL
+#define MOD_SUCCESS_VAL(val) val
+#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+ ob = PyModule_Create(&moduledef);
+#else
+#define MOD_ERROR_VAL
+#define MOD_SUCCESS_VAL(val)
+#define MOD_INIT(name) void init##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static pmdaInterface dispatch;
+static __pmnsTree *pmns;
+static int need_refresh;
+static PyObject *pmns_dict; /* metric pmid:names dictionary */
+static PyObject *pmid_oneline_dict; /* metric pmid:short text */
+static PyObject *pmid_longtext_dict; /* metric pmid:long help */
+static PyObject *indom_oneline_dict; /* indom pmid:short text */
+static PyObject *indom_longtext_dict; /* indom pmid:long help */
+
+static PyObject *fetch_func;
+static PyObject *refresh_func;
+static PyObject *instance_func;
+static PyObject *store_cb_func;
+static PyObject *fetch_cb_func;
+
+#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION <= 5
+typedef int Py_ssize_t;
+#endif
+
+static void
+pmns_refresh(void)
+{
+ int sts, count = 0;
+ Py_ssize_t pos = 0;
+ PyObject *key, *value;
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmns_refresh: rebuilding namespace\n");
+
+ if (pmns)
+ __pmFreePMNS(pmns);
+
+ if ((sts = __pmNewPMNS(&pmns)) < 0) {
+ __pmNotifyErr(LOG_ERR, "failed to create namespace root: %s",
+ pmErrStr(sts));
+ return;
+ }
+
+ while (PyDict_Next(pmns_dict, &pos, &key, &value)) {
+ const char *name;
+ long pmid;
+
+ pmid = PyLong_AsLong(key);
+#if PY_MAJOR_VERSION >= 3
+ name = PyUnicode_AsUTF8(value);
+#else
+ name = PyString_AsString(value);
+#endif
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmns_refresh: adding metric %s(%s)\n",
+ name, pmIDStr(pmid));
+ if ((sts = __pmAddPMNSNode(pmns, pmid, name)) < 0) {
+ __pmNotifyErr(LOG_ERR,
+ "failed to add metric %s(%s) to namespace: %s",
+ name, pmIDStr(pmid), pmErrStr(sts));
+ } else {
+ count++;
+ }
+ }
+
+ pmdaTreeRebuildHash(pmns, count); /* for reverse (pmid->name) lookups */
+ Py_DECREF(pmns_dict);
+ need_refresh = 0;
+ pmns_dict = NULL;
+}
+
+static PyObject *
+namespace_refresh(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *keyword_list[] = {"metrics", NULL};
+
+ if (pmns_dict) {
+ Py_DECREF(pmns_dict);
+ pmns_dict = NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:namespace_refresh", keyword_list, &pmns_dict))
+ return NULL;
+ if (pmns_dict) {
+ if (!PyDict_Check(pmns_dict)) {
+ __pmNotifyErr(LOG_ERR,
+ "attempted to refresh namespace with non-dict type");
+ Py_DECREF(pmns_dict);
+ pmns_dict = NULL;
+ } else if (need_refresh) {
+ pmns_refresh();
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+pmid_oneline_refresh(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *keyword_list[] = {"oneline", NULL};
+
+ if (pmid_oneline_dict) {
+ Py_DECREF(pmid_oneline_dict);
+ pmid_oneline_dict = NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:pmid_oneline_refresh",
+ keyword_list, &pmid_oneline_dict))
+ return NULL;
+
+ if (pmid_oneline_dict) {
+ if (!PyDict_Check(pmid_oneline_dict)) {
+ __pmNotifyErr(LOG_ERR,
+ "attempted to refresh pmid oneline help with non-dict type");
+ Py_DECREF(pmid_oneline_dict);
+ pmid_oneline_dict = NULL;
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+pmid_longtext_refresh(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *keyword_list[] = {"longtext", NULL};
+
+ if (pmid_longtext_dict) {
+ Py_DECREF(pmid_longtext_dict);
+ pmid_longtext_dict = NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:pmid_longtext_refresh",
+ keyword_list, &pmid_longtext_dict))
+ return NULL;
+
+ if (pmid_longtext_dict) {
+ if (!PyDict_Check(pmid_longtext_dict)) {
+ __pmNotifyErr(LOG_ERR,
+ "attempted to refresh pmid long help with non-dict type");
+ Py_DECREF(pmid_longtext_dict);
+ pmid_longtext_dict = NULL;
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+indom_oneline_refresh(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *keyword_list[] = {"oneline", NULL};
+
+ if (indom_oneline_dict) {
+ Py_DECREF(indom_oneline_dict);
+ indom_oneline_dict = NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:indom_oneline_refresh",
+ keyword_list, &indom_oneline_dict))
+ return NULL;
+
+ if (indom_oneline_dict) {
+ if (!PyDict_Check(indom_oneline_dict)) {
+ __pmNotifyErr(LOG_ERR,
+ "attempted to refresh indom oneline help with non-dict type");
+ Py_DECREF(indom_oneline_dict);
+ indom_oneline_dict = NULL;
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+indom_longtext_refresh(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *keyword_list[] = {"longtext", NULL};
+
+ if (indom_longtext_dict) {
+ Py_DECREF(indom_longtext_dict);
+ indom_longtext_dict = NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "O:indom_longtext_refresh",
+ keyword_list, &indom_longtext_dict))
+ return NULL;
+
+ if (indom_longtext_dict) {
+ if (!PyDict_Check(indom_longtext_dict)) {
+ __pmNotifyErr(LOG_ERR,
+ "attempted to refresh indom long help with non-dict type");
+ Py_DECREF(indom_longtext_dict);
+ indom_longtext_dict = NULL;
+ }
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+int
+pmns_desc(pmID pmid, pmDesc *desc, pmdaExt *ep)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaDesc(pmid, desc, ep);
+}
+
+int
+pmns_pmid(const char *name, pmID *pmid, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreePMID(pmns, name, pmid);
+}
+
+int
+pmns_name(pmID pmid, char ***nameset, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeName(pmns, pmid, nameset);
+}
+
+int
+pmns_children(const char *name, int traverse, char ***kids, int **sts, pmdaExt *pmda)
+{
+ if (need_refresh)
+ pmns_refresh();
+ return pmdaTreeChildren(pmns, name, traverse, kids, sts);
+}
+
+static int
+prefetch(void)
+{
+ PyObject *arglist, *result;
+
+ arglist = Py_BuildValue("()");
+ if (arglist == NULL)
+ return -ENOMEM;
+ result = PyEval_CallObject(fetch_func, arglist);
+ Py_DECREF(arglist);
+ if (!result) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ }
+ Py_DECREF(result);
+ return 0;
+}
+
+static int
+refresh_cluster(int cluster)
+{
+ PyObject *arglist, *result;
+
+ arglist = Py_BuildValue("(i)", cluster);
+ if (arglist == NULL)
+ return -ENOMEM;
+ result = PyEval_CallObject(refresh_func, arglist);
+ Py_DECREF(arglist);
+ if (result == NULL) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ }
+ Py_DECREF(result);
+ return 0;
+}
+
+static int
+refresh(int numpmid, pmID *pmidlist)
+{
+ size_t need;
+ int *clusters = NULL;
+ int i, j, count = 0;
+ int sts = 0;
+
+ /*
+ * Invoke a callback once for each affected PMID cluster (and not for the
+ * unaffected clusters). This allows specific subsets of metric values to
+ * be refreshed, rather than just blindly fetching everything at the start
+ * of a fetch request. Accomplish this by building an array of the unique
+ * cluster numbers from the given PMID list.
+ */
+ need = sizeof(int) * numpmid; /* max cluster count */
+ if ((clusters = malloc(need)) == NULL)
+ return -ENOMEM;
+ for (i = 0; i < numpmid; i++) {
+ int cluster = pmid_cluster(pmidlist[i]);
+ for (j = 0; j < count; j++)
+ if (clusters[j] == cluster)
+ break;
+ if (j == count)
+ clusters[count++] = cluster;
+ }
+ for (j = 0; j < count; j++)
+ sts |= refresh_cluster(clusters[j]);
+ free(clusters);
+ return sts;
+}
+
+static int
+fetch(int numpmid, pmID *pmidlist, pmResult **rp, pmdaExt *pmda)
+{
+ int sts;
+
+ if (need_refresh)
+ pmns_refresh();
+ if (fetch_func && (sts = prefetch()) < 0)
+ return sts;
+ if (refresh_func && (sts = refresh(numpmid, pmidlist)) < 0)
+ return sts;
+ return pmdaFetch(numpmid, pmidlist, rp, pmda);
+}
+
+static int
+preinstance(pmInDom indom)
+{
+ PyObject *arglist, *result;
+
+ arglist = Py_BuildValue("(i)", pmInDom_serial(indom));
+ if (arglist == NULL)
+ return -ENOMEM;
+ result = PyEval_CallObject(instance_func, arglist);
+ Py_DECREF(arglist);
+ if (result == NULL) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ }
+ Py_DECREF(result);
+ return 0;
+}
+
+int
+instance(pmInDom indom, int a, char *b, __pmInResult **rp, pmdaExt *pmda)
+{
+ int sts;
+
+ if (need_refresh)
+ pmns_refresh();
+ if (instance_func && (sts = preinstance(indom)) < 0)
+ return sts;
+ return pmdaInstance(indom, a, b, rp, pmda);
+}
+
+int
+fetch_callback(pmdaMetric *metric, unsigned int inst, pmAtomValue *atom)
+{
+ char *s;
+ int rc, sts, code;
+ PyObject *arglist, *result;
+ __pmID_int *pmid = (__pmID_int *)&metric->m_desc.pmid;
+
+ if (fetch_cb_func == NULL)
+ return PM_ERR_VALUE;
+
+ arglist = Py_BuildValue("(iiI)", pmid->cluster, pmid->item, inst);
+ if (arglist == NULL) {
+ __pmNotifyErr(LOG_ERR, "fetch callback cannot alloc parameters");
+ return -EINVAL;
+ }
+ result = PyEval_CallObject(fetch_cb_func, arglist);
+ Py_DECREF(arglist);
+ if (result == NULL) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ } else if (PyTuple_Check(result)) {
+ __pmNotifyErr(LOG_ERR, "non-tuple returned from fetch callback");
+ Py_DECREF(result);
+ return -EINVAL;
+ }
+ rc = code = 0;
+ sts = PMDA_FETCH_STATIC;
+ switch (metric->m_desc.type) {
+ case PM_TYPE_32:
+ rc = PyArg_Parse(result, "(ii):fetch_cb_s32", &atom->l, &code);
+ break;
+ case PM_TYPE_U32:
+ rc = PyArg_Parse(result, "(Ii):fetch_cb_u32", &atom->ul, &code);
+ break;
+ case PM_TYPE_64:
+ rc = PyArg_Parse(result, "(Li):fetch_cb_s64", &atom->ll, &code);
+ break;
+ case PM_TYPE_U64:
+ rc = PyArg_Parse(result, "(Ki):fetch_cb_u64", &atom->ull, &code);
+ break;
+ case PM_TYPE_FLOAT:
+ rc = PyArg_Parse(result, "(fi):fetch_cb_float", &atom->f, &code);
+ break;
+ case PM_TYPE_DOUBLE:
+ rc = PyArg_Parse(result, "(di):fetch_cb_double", &atom->d, &code);
+ break;
+ case PM_TYPE_STRING:
+ s = NULL;
+ rc = PyArg_Parse(result, "(si):fetch_cb_string", &s, &code);
+ if (rc == 0)
+ break;
+ if (s == NULL)
+ sts = PM_ERR_VALUE;
+ else if ((atom->cp = strdup(s)) == NULL)
+ sts = -ENOMEM;
+ else
+ sts = PMDA_FETCH_DYNAMIC;
+ break;
+ default:
+ __pmNotifyErr(LOG_ERR, "unsupported metric type in fetch callback");
+ sts = -ENOTSUP;
+ }
+
+ if (!rc || !code) { /* tuple not parsed or atom contains bad value */
+ if (!PyArg_Parse(result, "(ii):fetch_cb_error", &sts, &code)) {
+ __pmNotifyErr(LOG_ERR, "extracting error code in fetch callback");
+ sts = -EINVAL;
+ }
+ }
+ Py_DECREF(result);
+ return sts;
+}
+
+int
+store_callback(__pmID_int *pmid, unsigned int inst, pmAtomValue av, int type)
+{
+ int rc, code;
+ int item = pmid->item;
+ int cluster = pmid->cluster;
+ PyObject *arglist, *result;
+
+ switch (type) {
+ case PM_TYPE_32:
+ arglist = Py_BuildValue("(iiIi)", cluster, item, inst, av.l);
+ break;
+ case PM_TYPE_U32:
+ arglist = Py_BuildValue("(iiII)", cluster, item, inst, av.ul);
+ break;
+ case PM_TYPE_64:
+ arglist = Py_BuildValue("(iiIL)", cluster, item, inst, av.ll);
+ break;
+ case PM_TYPE_U64:
+ arglist = Py_BuildValue("(iiIK)", cluster, item, inst, av.ull);
+ break;
+ case PM_TYPE_FLOAT:
+ arglist = Py_BuildValue("(iiIf)", cluster, item, inst, av.f);
+ break;
+ case PM_TYPE_DOUBLE:
+ arglist = Py_BuildValue("(iiId)", cluster, item, inst, av.d);
+ break;
+ case PM_TYPE_STRING:
+ arglist = Py_BuildValue("(iiIs)", cluster, item, inst, av.cp);
+ break;
+ default:
+ __pmNotifyErr(LOG_ERR, "unsupported type in store callback");
+ return -EINVAL;
+ }
+ result = PyEval_CallObject(store_cb_func, arglist);
+ Py_DECREF(arglist);
+ if (!result) {
+ PyErr_Print();
+ return -EAGAIN; /* exception thrown */
+ }
+ rc = PyArg_Parse(result, "i:store_callback", &code);
+ Py_DECREF(result);
+ if (rc == 0) {
+ __pmNotifyErr(LOG_ERR, "store callback gave bad status (int expected)");
+ return -EINVAL;
+ }
+ return code;
+}
+
+static pmdaMetric *
+lookup_metric(__pmID_int *pmid, pmdaExt *pmda)
+{
+ int i;
+ pmdaMetric *mp;
+
+ for (i = 0; i < pmda->e_nmetrics; i++) {
+ mp = &pmda->e_metrics[i];
+ if (pmid->item != pmid_item(mp->m_desc.pmid))
+ continue;
+ if (pmid->cluster != pmid_cluster(mp->m_desc.pmid))
+ continue;
+ return mp;
+ }
+ return NULL;
+}
+
+int
+store(pmResult *result, pmdaExt *pmda)
+{
+ int i, j;
+ int type;
+ int sts;
+ pmAtomValue av;
+ pmdaMetric *mp;
+ pmValueSet *vsp;
+ __pmID_int *pmid;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ if (store_cb_func == NULL)
+ return PM_ERR_PERMISSION;
+
+ for (i = 0; i < result->numpmid; i++) {
+ vsp = result->vset[i];
+ pmid = (__pmID_int *)&vsp->pmid;
+
+ /* find the type associated with this PMID */
+ if ((mp = lookup_metric(pmid, pmda)) == NULL)
+ return PM_ERR_PMID;
+ type = mp->m_desc.type;
+
+ for (j = 0; j < vsp->numval; j++) {
+ sts = pmExtractValue(vsp->valfmt, &vsp->vlist[j],type, &av, type);
+ if (sts < 0)
+ return sts;
+ sts = store_callback(pmid, vsp->vlist[j].inst, av, type);
+ if (sts < 0)
+ return sts;
+ }
+ }
+ return 0;
+}
+
+int
+text(int ident, int type, char **buffer, pmdaExt *pmda)
+{
+ PyObject *dict, *value, *key;
+
+ if (need_refresh)
+ pmns_refresh();
+
+ if ((type & PM_TEXT_PMID) != 0) {
+ if ((type & PM_TEXT_ONELINE) != 0)
+ dict = pmid_oneline_dict;
+ else
+ dict = pmid_longtext_dict;
+ } else {
+ if ((type & PM_TEXT_ONELINE) != 0)
+ dict = indom_oneline_dict;
+ else
+ dict = indom_longtext_dict;
+ }
+
+ key = PyLong_FromLong((long)ident);
+ if (!key)
+ return PM_ERR_TEXT;
+ value = PyDict_GetItem(dict, key);
+ Py_DECREF(key);
+ if (value == NULL)
+ return PM_ERR_TEXT;
+#if PY_MAJOR_VERSION >= 3
+ *buffer = PyUnicode_AsUTF8(value);
+#else
+ *buffer = PyString_AsString(value);
+#endif
+ /* "value" is a borrowed reference, do not decrement */
+ return 0;
+}
+
+int
+attribute(int ctx, int attr, const char *value, int length, pmdaExt *pmda)
+{
+ if (pmDebug & DBG_TRACE_AUTH) {
+ char buffer[256];
+
+ if (!__pmAttrStr_r(attr, value, buffer, sizeof(buffer))) {
+ __pmNotifyErr(LOG_ERR, "Bad Attribute: ctx=%d, attr=%d\n", ctx, attr);
+ } else {
+ buffer[sizeof(buffer)-1] = '\0';
+ __pmNotifyErr(LOG_INFO, "Attribute: ctx=%d %s", ctx, buffer);
+ }
+ }
+ /* handle connection attributes - need per-connection state code */
+ return 0;
+}
+
+/*
+ * Allocate a new PMDA dispatch structure and fill it
+ * in for the agent we have been asked to instantiate.
+ */
+
+static inline int
+pmda_generating_pmns(void) { return getenv("PCP_PYTHON_PMNS") != NULL; }
+
+static inline int
+pmda_generating_domain(void) { return getenv("PCP_PYTHON_DOMAIN") != NULL; }
+
+static PyObject *
+init_dispatch(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int domain;
+ char *p, *name, *help, *logfile, *pmdaname;
+ char *keyword_list[] = {"domain", "name", "log", "help", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "isss:init_dispatch", keyword_list,
+ &domain, &pmdaname, &logfile, &help))
+ return NULL;
+
+ name = strdup(pmdaname);
+ __pmSetProgname(name);
+ if ((p = getenv("PCP_PYTHON_DEBUG")) != NULL)
+ if ((pmDebug = __pmParseDebug(p)) < 0)
+ pmDebug = 0;
+
+ if (access(help, R_OK) != 0) {
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_6, name, domain, logfile, NULL);
+ dispatch.version.four.text = text;
+ } else {
+ p = strdup(help);
+ pmdaDaemon(&dispatch, PMDA_INTERFACE_6, name, domain, logfile, p);
+ }
+ dispatch.version.six.fetch = fetch;
+ dispatch.version.six.store = store;
+ dispatch.version.six.instance = instance;
+ dispatch.version.six.desc = pmns_desc;
+ dispatch.version.six.pmid = pmns_pmid;
+ dispatch.version.six.name = pmns_name;
+ dispatch.version.six.children = pmns_children;
+ dispatch.version.six.attribute = attribute;
+ pmdaSetFetchCallBack(&dispatch, fetch_callback);
+
+ if (!pmda_generating_pmns() && !pmda_generating_domain())
+ pmdaOpenLog(&dispatch);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+connect_pmcd(void)
+{
+ /*
+ * Need to mimic the same special cases handled by run() in
+ * pcp/pmda.py that explicitly do NOT connect to pmcd and treat
+ * these as no-ops here.
+ *
+ * Otherwise call pmdaConnect() to complete the PMDA's IPC
+ * channel setup and complete the connection handshake with
+ * pmcd.
+ */
+ if (!pmda_generating_pmns() && !pmda_generating_domain()) {
+ /*
+ * On success pmdaConnect sets PMDA_EXT_CONNECTED in e_flags ...
+ * this used in the guard below to stop pmda_dispatch() calling
+ * pmdaConnect() again.
+ */
+ pmdaConnect(&dispatch);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#ifdef PyBUF_SIMPLE
+static PyObject *
+pmda_dispatch(PyObject *self, PyObject *args)
+{
+ int nindoms, nmetrics;
+ PyObject *ibuf, *mbuf;
+ pmdaMetric *metrics;
+ pmdaIndom *indoms;
+ Py_buffer mv, iv;
+
+ if (!PyArg_ParseTuple(args, "OiOi", &ibuf, &nindoms, &mbuf, &nmetrics))
+ return NULL;
+
+ if (!PyObject_CheckBuffer(ibuf)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch expected buffer 1st arg");
+ return NULL;
+ }
+ if (!PyObject_CheckBuffer(mbuf)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch expected buffer 3rd arg");
+ return NULL;
+ }
+
+ if (PyObject_GetBuffer(ibuf, &iv, PyBUF_SIMPLE)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch failed to get indoms");
+ return NULL;
+ }
+ if (PyObject_GetBuffer(mbuf, &mv, PyBUF_SIMPLE)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch failed to get metrics");
+ PyBuffer_Release(&iv);
+ return NULL;
+ }
+
+ if (iv.len != nindoms * sizeof(pmdaIndom)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch: invalid indom array");
+ PyBuffer_Release(&iv);
+ PyBuffer_Release(&mv);
+ return NULL;
+ }
+ if (mv.len != nmetrics * sizeof(pmdaMetric)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch: invalid metric array");
+ PyBuffer_Release(&iv);
+ PyBuffer_Release(&mv);
+ return NULL;
+ }
+
+ indoms = nindoms ? (pmdaIndom *)iv.buf : NULL;
+ metrics = nmetrics ? (pmdaMetric *)mv.buf : NULL;
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch pmdaInit for metrics/indoms\n");
+ pmdaInit(&dispatch, indoms, nindoms, metrics, nmetrics);
+ if ((dispatch.version.any.ext->e_flags & PMDA_EXT_CONNECTED) != PMDA_EXT_CONNECTED) {
+ /*
+ * connect_pmcd() not called before, so need pmdaConnect()
+ * here before falling into the PDU-driven pmdaMain() loop.
+ */
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch connect to pmcd\n");
+ pmdaConnect(&dispatch);
+ }
+
+ PyBuffer_Release(&iv);
+ PyBuffer_Release(&mv);
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch entering PDU loop\n");
+
+ pmdaMain(&dispatch);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+#else /* old-school python */
+static PyObject *
+pmda_dispatch(PyObject *self, PyObject *args)
+{
+ int nindoms, nmetrics, size;
+ PyObject *ibuf, *mbuf, *iv, *mv;
+ pmdaMetric *metrics = NULL;
+ pmdaIndom *indoms = NULL;
+
+ if (!PyArg_ParseTuple(args, "OiOi", &ibuf, &nindoms, &mbuf, &nmetrics))
+ return NULL;
+
+ size = nindoms * sizeof(pmdaIndom);
+ if ((iv = PyBuffer_FromObject(ibuf, 0, size)) == NULL) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch indom extraction");
+ return NULL;
+ }
+ if (!PyBuffer_Check(iv)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch wants buffer 1st arg");
+ return NULL;
+ }
+ size = nmetrics * sizeof(pmdaMetric);
+ if ((mv = PyBuffer_FromObject(mbuf, 0, size)) == NULL) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch metric extraction");
+ return NULL;
+ }
+ if (!PyBuffer_Check(mv)) {
+ PyErr_SetString(PyExc_TypeError, "pmda_dispatch wants buffer 3rd arg");
+ return NULL;
+ }
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch pmdaInit for metrics/indoms\n");
+
+ PyBuffer_Type.tp_as_buffer->bf_getreadbuffer(iv, 0, (void *)&indoms);
+ PyBuffer_Type.tp_as_buffer->bf_getreadbuffer(mv, 0, (void *)&metrics);
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch pmdaInit for metrics/indoms\n");
+ pmdaInit(&dispatch, indoms, nindoms, metrics, nmetrics);
+ if ((dispatch.version.any.ext->e_flags & PMDA_EXT_CONNECTED) != PMDA_EXT_CONNECTED) {
+ /*
+ * connect_pmcd() not called before, so need pmdaConnect()
+ * here before falling into the PDU-driven pmdaMain() loop.
+ */
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch connect to pmcd\n");
+ pmdaConnect(&dispatch);
+ }
+
+ Py_DECREF(ibuf);
+ Py_DECREF(mbuf);
+
+ if (pmDebug & DBG_TRACE_LIBPMDA)
+ fprintf(stderr, "pmda_dispatch entering PDU loop\n");
+
+ pmdaMain(&dispatch);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+#endif
+
+static PyObject *
+pmda_log(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *message;
+ char *keyword_list[] = {"message", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmda_log", keyword_list, &message))
+ return NULL;
+ __pmNotifyErr(LOG_INFO, "%s", message);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+pmda_err(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ char *message;
+ char *keyword_list[] = {"message", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "s:pmda_err", keyword_list, &message))
+ return NULL;
+ __pmNotifyErr(LOG_ERR, "%s", message);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+pmda_pmid(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int result;
+ int cluster, item;
+ char *keyword_list[] = {"item", "cluster", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "ii:pmda_pmid", keyword_list,
+ &item, &cluster))
+ return NULL;
+ result = pmid_build(dispatch.domain, item, cluster);
+ return Py_BuildValue("i", result);
+}
+
+static PyObject *
+pmda_indom(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int result;
+ int serial;
+ char *keyword_list[] = {"serial", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:pmda_indom", keyword_list, &serial))
+ return NULL;
+ result = pmInDom_build(dispatch.domain, serial);
+ return Py_BuildValue("i", result);
+}
+
+static PyObject *
+pmda_units(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ int result;
+ int dim_time, dim_space, dim_count;
+ int scale_space, scale_time, scale_count;
+ char *keyword_list[] = {"dim_time", "dim_space", "dim_count",
+ "scale_space", "scale_time", "scale_count", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "iiiiii:pmda_units", keyword_list,
+ &dim_time, &dim_space, &dim_count,
+ &scale_space, &scale_time, &scale_count))
+ return NULL;
+ {
+ pmUnits units = PMDA_PMUNITS(dim_time, dim_space, dim_count,
+ scale_space, scale_time, scale_count);
+ memcpy(&result, &units, sizeof(result));
+ }
+ return Py_BuildValue("i", result);
+}
+
+static PyObject *
+pmda_uptime(PyObject *self, PyObject *args, PyObject *keywords)
+{
+ static char s[32];
+ size_t sz = sizeof(s);
+ int now, days, hours, mins, secs;
+ char *keyword_list[] = {"seconds", NULL};
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords,
+ "i:pmda_uptime", keyword_list, &now))
+ return NULL;
+
+ days = now / (60 * 60 * 24);
+ now %= (60 * 60 * 24);
+ hours = now / (60 * 60);
+ now %= (60 * 60);
+ mins = now / 60;
+ now %= 60;
+ secs = now;
+
+ if (days > 1)
+ snprintf(s, sz, "%ddays %02d:%02d:%02d", days, hours, mins, secs);
+ else if (days == 1)
+ snprintf(s, sz, "%dday %02d:%02d:%02d", days, hours, mins, secs);
+ else
+ snprintf(s, sz, "%02d:%02d:%02d", hours, mins, secs);
+
+ return Py_BuildValue("s", s);
+}
+
+static PyObject *
+set_need_refresh(PyObject *self, PyObject *args)
+{
+ need_refresh = 1;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+set_callback(PyObject *self, PyObject *args, char *params, PyObject **callback)
+{
+ PyObject *func;
+
+ if (!PyArg_ParseTuple(args, params, &func))
+ return NULL;
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError, "parameter must be callable");
+ return NULL;
+ }
+ Py_XINCREF(func);
+ Py_XDECREF(*callback);
+ *callback = func;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *
+set_fetch(PyObject *self, PyObject *args)
+{
+ return set_callback(self, args, "O:set_fetch", &fetch_func);
+}
+
+static PyObject *
+set_refresh(PyObject *self, PyObject *args)
+{
+ return set_callback(self, args, "O:set_refresh", &refresh_func);
+}
+
+static PyObject *
+set_instance(PyObject *self, PyObject *args)
+{
+ return set_callback(self, args, "O:set_instance", &instance_func);
+}
+
+static PyObject *
+set_store_callback(PyObject *self, PyObject *args)
+{
+ return set_callback(self, args, "O:set_store_callback", &store_cb_func);
+}
+
+static PyObject *
+set_fetch_callback(PyObject *self, PyObject *args)
+{
+ return set_callback(self, args, "O:set_fetch_callback", &fetch_cb_func);
+}
+
+
+static PyMethodDef methods[] = {
+ { .ml_name = "pmda_pmid", .ml_meth = (PyCFunction)pmda_pmid,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_indom", .ml_meth = (PyCFunction)pmda_indom,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_units", .ml_meth = (PyCFunction)pmda_units,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_uptime", .ml_meth = (PyCFunction)pmda_uptime,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "init_dispatch", .ml_meth = (PyCFunction)init_dispatch,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_dispatch", .ml_meth = (PyCFunction)pmda_dispatch,
+ .ml_flags = METH_VARARGS },
+ { .ml_name = "connect_pmcd", .ml_meth = (PyCFunction)connect_pmcd,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "pmns_refresh", .ml_meth = (PyCFunction)namespace_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmid_oneline_refresh",
+ .ml_meth = (PyCFunction)pmid_oneline_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmid_longtext_refresh",
+ .ml_meth = (PyCFunction)pmid_longtext_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "indom_oneline_refresh",
+ .ml_meth = (PyCFunction)indom_oneline_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "indom_longtext_refresh",
+ .ml_meth = (PyCFunction)indom_longtext_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "set_need_refresh", .ml_meth = (PyCFunction)set_need_refresh,
+ .ml_flags = METH_NOARGS },
+ { .ml_name = "set_fetch", .ml_meth = (PyCFunction)set_fetch,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "set_refresh", .ml_meth = (PyCFunction)set_refresh,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "set_instance", .ml_meth = (PyCFunction)set_instance,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "set_store_callback", .ml_meth = (PyCFunction)set_store_callback,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "set_fetch_callback", .ml_meth = (PyCFunction)set_fetch_callback,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_log", .ml_meth = (PyCFunction)pmda_log,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { .ml_name = "pmda_err", .ml_meth = (PyCFunction)pmda_err,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS },
+ { NULL },
+};
+
+static void
+pmda_dict_add(PyObject *dict, char *sym, long val)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyVal = PyLong_FromLong(val);
+#else
+ PyObject *pyVal = PyInt_FromLong(val);
+#endif
+
+ PyDict_SetItemString(dict, sym, pyVal);
+ Py_XDECREF(pyVal);
+}
+
+/* called when the module is initialized. */
+MOD_INIT(cpmda)
+{
+ PyObject *module, *dict;
+
+ MOD_DEF(module, "cpmda", NULL, methods);
+ if (module == NULL)
+ return MOD_ERROR_VAL;
+
+ dict = PyModule_GetDict(module);
+
+ /* pmda.h - fetch callback return codes */
+ pmda_dict_add(dict, "PMDA_FETCH_NOVALUES", PMDA_FETCH_NOVALUES);
+ pmda_dict_add(dict, "PMDA_FETCH_STATIC", PMDA_FETCH_STATIC);
+ pmda_dict_add(dict, "PMDA_FETCH_DYNAMIC", PMDA_FETCH_DYNAMIC);
+
+ /* pmda.h - indom cache operation codes */
+ pmda_dict_add(dict, "PMDA_CACHE_LOAD", PMDA_CACHE_LOAD);
+ pmda_dict_add(dict, "PMDA_CACHE_ADD", PMDA_CACHE_ADD);
+ pmda_dict_add(dict, "PMDA_CACHE_HIDE", PMDA_CACHE_HIDE);
+ pmda_dict_add(dict, "PMDA_CACHE_CULL", PMDA_CACHE_CULL);
+ pmda_dict_add(dict, "PMDA_CACHE_EMPTY", PMDA_CACHE_EMPTY);
+ pmda_dict_add(dict, "PMDA_CACHE_SAVE", PMDA_CACHE_SAVE);
+ pmda_dict_add(dict, "PMDA_CACHE_ACTIVE", PMDA_CACHE_ACTIVE);
+ pmda_dict_add(dict, "PMDA_CACHE_INACTIVE", PMDA_CACHE_INACTIVE);
+ pmda_dict_add(dict, "PMDA_CACHE_SIZE", PMDA_CACHE_SIZE);
+ pmda_dict_add(dict, "PMDA_CACHE_SIZE_ACTIVE", PMDA_CACHE_SIZE_ACTIVE);
+ pmda_dict_add(dict, "PMDA_CACHE_SIZE_INACTIVE", PMDA_CACHE_SIZE_INACTIVE);
+ pmda_dict_add(dict, "PMDA_CACHE_REUSE", PMDA_CACHE_REUSE);
+ pmda_dict_add(dict, "PMDA_CACHE_WALK_REWIND", PMDA_CACHE_WALK_REWIND);
+ pmda_dict_add(dict, "PMDA_CACHE_WALK_NEXT", PMDA_CACHE_WALK_NEXT);
+ pmda_dict_add(dict, "PMDA_CACHE_CHECK", PMDA_CACHE_CHECK);
+ pmda_dict_add(dict, "PMDA_CACHE_REORG", PMDA_CACHE_REORG);
+ pmda_dict_add(dict, "PMDA_CACHE_SYNC", PMDA_CACHE_SYNC);
+ pmda_dict_add(dict, "PMDA_CACHE_DUMP", PMDA_CACHE_DUMP);
+ pmda_dict_add(dict, "PMDA_CACHE_DUMP_ALL", PMDA_CACHE_DUMP_ALL);
+
+ return MOD_SUCCESS_VAL(module);
+}
diff --git a/src/python/pmgui.c b/src/python/pmgui.c
new file mode 100644
index 0000000..60256c4
--- /dev/null
+++ b/src/python/pmgui.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012-2014 Red Hat.
+ *
+ * This file is part of the "pcp" module, the python interfaces for the
+ * Performance Co-Pilot toolkit.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**************************************************************************\
+** **
+** This C extension module mainly serves the purpose of loading constants **
+** from PCP headers into the module dictionary. The GUI API functions **
+** and data structures are wrapped in pmgui.py and friends, using ctypes. **
+** **
+\**************************************************************************/
+
+#include <Python.h>
+#include <pcp/pmafm.h>
+#include <pcp/pmtime.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define MOD_ERROR_VAL NULL
+#define MOD_SUCCESS_VAL(val) val
+#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+ ob = PyModule_Create(&moduledef);
+#else
+#define MOD_ERROR_VAL
+#define MOD_SUCCESS_VAL(val)
+#define MOD_INIT(name) void init##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static void
+pmgui_dict_add(PyObject *dict, char *sym, long val)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyVal = PyLong_FromLong(val);
+#else
+ PyObject *pyVal = PyInt_FromLong(val);
+#endif
+ PyDict_SetItemString(dict, sym, pyVal);
+ Py_XDECREF(pyVal);
+}
+
+static PyMethodDef methods[] = { { NULL } };
+
+/* called when the module is initialized. */
+MOD_INIT(cpmgui)
+{
+ PyObject *module, *dict;
+
+ MOD_DEF(module, "cpmgui", NULL, methods);
+ if (module == NULL)
+ return MOD_ERROR_VAL;
+
+ dict = PyModule_GetDict(module);
+
+ /* pmafm.h */
+ pmgui_dict_add(dict, "PM_REC_ON", PM_REC_ON);
+ pmgui_dict_add(dict, "PM_REC_OFF", PM_REC_OFF);
+ pmgui_dict_add(dict, "PM_REC_DETACH", PM_REC_DETACH);
+ pmgui_dict_add(dict, "PM_REC_STATUS", PM_REC_STATUS);
+ pmgui_dict_add(dict, "PM_REC_SETARG", PM_REC_SETARG);
+
+ /* TODO: pmtime.h */
+
+ return MOD_SUCCESS_VAL(module);
+}
diff --git a/src/python/pmi.c b/src/python/pmi.c
new file mode 100644
index 0000000..b253eff
--- /dev/null
+++ b/src/python/pmi.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013-2014 Red Hat.
+ *
+ * This file is part of the "pcp" module, the python interfaces for the
+ * Performance Co-Pilot toolkit.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/**************************************************************************\
+** **
+** This C extension module mainly serves the purpose of loading constants **
+** from the PCP log import header into the module dictionary. PMI data **
+** structures and interfaces are wrapped in pmi.py, using ctypes. **
+** **
+\**************************************************************************/
+
+#include <Python.h>
+#include <pcp/pmapi.h>
+#include <pcp/import.h>
+
+#if PY_MAJOR_VERSION >= 3
+#define MOD_ERROR_VAL NULL
+#define MOD_SUCCESS_VAL(val) val
+#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ static struct PyModuleDef moduledef = { \
+ PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
+ ob = PyModule_Create(&moduledef);
+#else
+#define MOD_ERROR_VAL
+#define MOD_SUCCESS_VAL(val)
+#define MOD_INIT(name) void init##name(void)
+#define MOD_DEF(ob, name, doc, methods) \
+ ob = Py_InitModule3(name, methods, doc);
+#endif
+
+static PyMethodDef methods[] = { { NULL } };
+
+static void
+pmi_dict_add(PyObject *dict, char *sym, long val)
+{
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyVal = PyLong_FromLong(val);
+#else
+ PyObject *pyVal = PyInt_FromLong(val);
+#endif
+
+ PyDict_SetItemString(dict, sym, pyVal);
+ Py_XDECREF(pyVal);
+}
+
+static void
+pmi_edict_add(PyObject *dict, PyObject *edict, char *sym, long val)
+{
+ PyObject *pySym;
+#if PY_MAJOR_VERSION >= 3
+ PyObject *pyVal = PyLong_FromLong(val);
+#else
+ PyObject *pyVal = PyInt_FromLong(val);
+#endif
+
+ PyDict_SetItemString(dict, sym, pyVal);
+#if PY_MAJOR_VERSION >= 3
+ pySym = PyUnicode_FromString(sym);
+#else
+ pySym = PyString_FromString(sym);
+#endif
+ PyDict_SetItem(edict, pyVal, pySym);
+ Py_XDECREF(pySym);
+ Py_XDECREF(pyVal);
+}
+
+/* This function is called when the module is initialized. */
+MOD_INIT(cpmi)
+{
+ PyObject *module, *dict, *edict;
+
+ MOD_DEF(module, "cpmi", NULL, methods);
+ if (module == NULL)
+ return MOD_ERROR_VAL;
+
+ dict = PyModule_GetDict(module);
+ edict = PyDict_New();
+ Py_INCREF(edict);
+ PyModule_AddObject(module, "pmiErrSymDict", edict);
+
+ /* import.h */
+ pmi_dict_add(dict, "PMI_MAXERRMSGLEN", PMI_MAXERRMSGLEN);
+ pmi_dict_add(dict, "PMI_ERR_BASE", PMI_ERR_BASE);
+
+ pmi_edict_add(dict, edict, "PMI_ERR_DUPMETRICNAME", PMI_ERR_DUPMETRICNAME);
+ pmi_edict_add(dict, edict, "PMI_ERR_DUPMETRICID", PMI_ERR_DUPMETRICID);
+ pmi_edict_add(dict, edict, "PMI_ERR_DUPINSTNAME", PMI_ERR_DUPINSTNAME);
+ pmi_edict_add(dict, edict, "PMI_ERR_DUPINSTID", PMI_ERR_DUPINSTID);
+ pmi_edict_add(dict, edict, "PMI_ERR_INSTNOTNULL", PMI_ERR_INSTNOTNULL);
+ pmi_edict_add(dict, edict, "PMI_ERR_INSTNULL", PMI_ERR_INSTNULL);
+ pmi_edict_add(dict, edict, "PMI_ERR_BADHANDLE", PMI_ERR_BADHANDLE);
+ pmi_edict_add(dict, edict, "PMI_ERR_DUPVALUE", PMI_ERR_DUPVALUE);
+ pmi_edict_add(dict, edict, "PMI_ERR_BADTYPE", PMI_ERR_BADTYPE);
+ pmi_edict_add(dict, edict, "PMI_ERR_BADSEM", PMI_ERR_BADSEM);
+ pmi_edict_add(dict, edict, "PMI_ERR_NODATA", PMI_ERR_NODATA);
+ pmi_edict_add(dict, edict, "PMI_ERR_BADMETRICNAME", PMI_ERR_BADMETRICNAME);
+ pmi_edict_add(dict, edict, "PMI_ERR_BADTIMESTAMP", PMI_ERR_BADTIMESTAMP);
+
+ return MOD_SUCCESS_VAL(module);
+}
diff --git a/src/python/setup.py b/src/python/setup.py
new file mode 100644
index 0000000..c235b08
--- /dev/null
+++ b/src/python/setup.py
@@ -0,0 +1,61 @@
+""" Build script for the PCP python package """
+#
+# Copyright (C) 2012-2014 Red Hat.
+# Copyright (C) 2009-2012 Michael T. Werner
+#
+# This file is part of the "pcp" module, the python interfaces for the
+# Performance Co-Pilot toolkit.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+from distutils.core import setup, Extension
+
+setup(name = 'pcp',
+ version = '1.0',
+ description = 'Python package for Performance Co-Pilot',
+ license = 'GPLv2+',
+ author = 'Performance Co-Pilot Development Team',
+ author_email = 'pcp@oss.sgi.com',
+ url = 'http://www.performancecopilot.org',
+ packages = ['pcp'],
+ ext_modules = [
+ Extension('cpmapi', ['pmapi.c'], libraries = ['pcp']),
+ Extension('cpmda', ['pmda.c'], libraries = ['pcp_pmda', 'pcp']),
+ Extension('cpmgui', ['pmgui.c'], libraries = ['pcp_gui']),
+ Extension('cpmi', ['pmi.c'], libraries = ['pcp_import']),
+ Extension('cmmv', ['mmv.c'], libraries = ['pcp_mmv']),
+ ],
+ platforms = [ 'Windows', 'Linux', 'FreeBSD', 'Solaris', 'Mac OS X', 'AIX' ],
+ long_description =
+ 'PCP provides services to support system-level performance monitoring',
+ classifiers = [
+ 'Development Status :: 5 - Production/Stable'
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'Intended Audience :: Information Technology',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Natural Language :: English',
+ 'Operating System :: MacOS :: MacOS X',
+ 'Operating System :: Microsoft :: Windows',
+ 'Operating System :: POSIX',
+ 'Operating System :: POSIX :: AIX',
+ 'Operating System :: POSIX :: Linux',
+ 'Operating System :: POSIX :: BSD :: NetBSD',
+ 'Operating System :: POSIX :: BSD :: FreeBSD',
+ 'Operating System :: POSIX :: SunOS/Solaris',
+ 'Operating System :: Unix',
+ 'Topic :: System :: Logging',
+ 'Topic :: System :: Monitoring',
+ 'Topic :: System :: Networking :: Monitoring',
+ 'Topic :: Software Development :: Libraries',
+ ],
+)
diff --git a/src/sar2pcp/GNUmakefile b/src/sar2pcp/GNUmakefile
new file mode 100644
index 0000000..be49a5c
--- /dev/null
+++ b/src/sar2pcp/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPT = sar2pcp
+LDIRT = $(MAN_PAGES) $(MAN_PAGES).tmp
+LSRCFILES = $(SCRIPT) README
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = $(SCRIPT).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: $(MAN_PAGES)
+
+$(SCRIPT).$(MAN_SECTION): $(SCRIPT)
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BIN_DIR)/$(SCRIPT)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/sar2pcp/README b/src/sar2pcp/README
new file mode 100644
index 0000000..276c25b
--- /dev/null
+++ b/src/sar2pcp/README
@@ -0,0 +1,81 @@
+Converting a sadc data file to a PCP archive
+
+This example uses the PCP::LogImport Perl wrapper around the libpcp_import
+library to convert a sadc datafile into a PCP archive.
+
+The version of sar that is supported here is the one from
+http://freshmeat.net/projects/sysstat/ which includes the sadf utility
+to translate the sadc datafile into an XML file. The Perl script
+sar2pcp runs sadf and translates the XML into a PCP archive.
+
+Usage: sar2pcp sadcfile archive
+
+The translation currently supports the following PCP metrics:
+ disk.all.read
+ disk.all.read_bytes
+ disk.all.total
+ disk.all.total_bytes
+ disk.all.write
+ disk.all.write_bytes
+ disk.dev.avactive
+ disk.dev.read_bytes
+ disk.dev.total
+ disk.dev.total_bytes
+ disk.dev.write_bytes
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.nice
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.pswitch
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.nice
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.free
+ mem.util.swapCached
+ mem.util.swapFree
+ mem.util.used
+ mem.vmstat.pgfault
+ mem.vmstat.pgfree
+ mem.vmstat.pgmajfault
+ mem.vmstat.pgpgin
+ mem.vmstat.pgpgout
+ network.interface.collisions
+ network.interface.in.bytes
+ network.interface.in.drops
+ network.interface.in.errors
+ network.interface.in.fifo
+ network.interface.in.frame
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.carrier
+ network.interface.out.drops
+ network.interface.out.errors
+ network.interface.out.fifo
+ network.interface.out.packets
+ proc.runq.runnable
+ swap.pagesin
+ swap.pagesout
+ vfs.dentry.count
+ vfs.files.count
+ vfs.inodes.count
+
+This is sufficient to support the following standard pmchart views:
+ CPU
+ Disk
+ Diskbytes
+ Loadavg
+ Memory
+ Netbytes
+ Netpackets
+ Overview (except for a lesser memory stats)
+ Paging
+
diff --git a/src/sar2pcp/sar2pcp b/src/sar2pcp/sar2pcp
new file mode 100755
index 0000000..15dc332
--- /dev/null
+++ b/src/sar2pcp/sar2pcp
@@ -0,0 +1,756 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2012-2013 Red Hat.
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+
+use XML::TokeParser;
+use Date::Parse;
+use Date::Format;
+use PCP::LogImport;
+
+my $sample = 0;
+my $stamp;
+my $zone = 'UTC'; # timestamps from sadf are in UTC by default
+my $parser;
+my %handlemap; # pmi* handles, one per metric-instance pair
+my %instmap; # ensures we don't add duplicate indom/instance pairs
+my $putsts = 0; # pmiPutValue() errors are only checked @ end of loop
+my $in_cpu_load = 0;
+my $snarf_text = 0;
+my $snarf_name;
+my $save_text;
+my $interface;
+my $disk_all_total;
+my $disk_all_total_bytes;
+
+sub dodate($)
+{
+ # convert datetime format YYYY-MM-DD from sadf into the format
+ # DD-Mmm-YYYY that Date::Parse seems to be able to parse correctly
+ #
+ my ($datetime) = @_;
+ my @fields = split(/-/, $datetime);
+ my $mm;
+ my %mm_map = (
+ '01' => 'Jan', '02' => 'Feb', '03' => 'Mar', '04' => 'Apr',
+ '05' => 'May', '06' => 'Jun', '07' => 'Jul', '08' => 'Aug',
+ '09' => 'Sep', '10' => 'Oct', '11' => 'Nov', '12' => 'Dec',
+ );
+ $#fields == 2 or die "dodate: bad datetime format: \"$datetime\"\n";
+ $mm = $fields[1];
+ if ($mm < 10 && $mm !~ /^0/) { $mm = "0" . $mm }; # add leading zero
+ defined($mm_map{$mm}) or die "dodate: bad month in datetime: \"$datetime\"\n";
+ return $fields[2] . '-' . $mm_map{$mm} . '-' . $fields[0];
+}
+
+sub dotime($)
+{
+ # convert time format HH-MM-SS from a few 10.x sysstat versions
+ # into the format HH:MM:SS that Date::Parse (reasonably) expects
+ #
+ my ($daytime) = @_;
+ $daytime =~ s/-/:/g;
+ return $daytime;
+}
+
+sub dovalue($)
+{
+ # convert string to floating point value: "0,00" / "0.00" / undef
+ # (unusual LC_NUMERIC settings put comma instead of decimal point)
+ # fail to convert (or attempt arithmetic on unconverted values) =>
+ # e.g. 'Argument "\x{31}\x{2c}..." isn't numeric in addition (+)'
+ #
+ my ($value) = @_;
+ return $value unless defined($value);
+ $value =~ s/,/./g;
+ return $value;
+}
+
+my %warning_tags; # XML tags we do not know and will issue warnings about
+my %skipped_tags = ( # XML tags we know about, but have no metric to map yet
+ 'sysstat' => 1, 'sysdata-version' => 1,
+ 'sysname' => 1, 'release' => 1,
+ 'machine' => 1, 'comments' => 1,
+ 'restarts' => 1, 'boot' => 1,
+ 'file-date' => 1, 'statistics' => 1,
+ 'interrupts' => 1, 'int-global' => 1,
+ 'int-proc' => 1, 'irqcpu' => 1,
+ 'serial' => 1, 'pty-nr' => 1,
+ 'tty' => 1,
+ 'super-sz' => 1, 'super-sz-percent' => 1,
+ 'dquot-sz' => 1, 'dquot-sz-percent' => 1,
+ 'rtsig-sz' => 1, 'rtsig-sz-percent' => 1,
+ 'memory' => 1, 'memused-percent' => 1,
+ 'commit' => 1, 'commit-percent' => 1,
+ 'swpused' => 1, 'swpused-percent' => 1,
+ 'swpcad-percent' => 1,
+ 'frmpg' => 1, 'bufpg' => 1, 'campg' => 1,
+ 'usb-devices' => 1,
+ 'disk' => 1, 'network' => 1,
+ 'power-management' => 1,
+ 'cpu-frequency' => 1, 'cpu-weighted-frequency' => 1,
+ 'cpufreq' => 1, 'cpuwfreq' => 1,
+ 'cpu' => 1,
+ 'hugepages' => 1, 'hugfree' => 1,
+ 'hugused' => 1, 'hugused-percent' => 1,
+ 'active' => 1, 'inactive' => 1,
+ 'temperature' => 1, 'temp' => 1,
+ 'usb' => 1,
+ 'filesystems' => 1,
+);
+
+# Register metrics with a singular value.
+#
+sub register_single($)
+{
+ my ($name) = @_;
+ my $units = pmiUnits(0,0,0,0,0,0);
+ my $type = PM_TYPE_FLOAT;
+ my $sts;
+
+ if ($name eq 'kernel.all.pswitch' || $name eq 'kernel.all.intr' ||
+ $name eq 'swap.pagesin' || $name eq 'swap.pagesout' ||
+ $name eq 'mem.vmstat.pgpgin' || $name eq 'mem.vmstat.pgpgout' ||
+ $name eq 'mem.vmstat.pgfault' || $name eq 'mem.vmstat.pgmajfault' ||
+ $name eq 'mem.vmstat.pgfree' || $name eq 'disk.all.total' ||
+ $name eq 'disk.all.read' || $name eq 'disk.all.write') {
+ $units = pmiUnits(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE);
+ }
+ elsif ($name =~ /^mem\.util\./) {
+ $units = pmiUnits(1,0,0,PM_SPACE_KBYTE,0,0);
+ $type = PM_TYPE_U64;
+ }
+ elsif ($name =~ /disk\.all\..*bytes/) {
+ $units = pmiUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0);
+ }
+ elsif ($name =~ /^vfs\./) {
+ $type = PM_TYPE_U32;
+ }
+ $sts = pmiAddMetric($name, PM_ID_NULL, $type, PM_INDOM_NULL, PM_SEM_INSTANT, $units);
+ die "pmiAddMetric($name, ...): " . pmiErrStr($sts) . "\n" unless ($sts == 0);
+}
+
+# Register metrics with multiple values.
+#
+sub register_multi($$)
+{
+ my ($name,$indom) = @_;
+ my $units = pmiUnits(0,0,0,0,0,0);
+ my $type = PM_TYPE_FLOAT;
+ my $sts;
+
+ if ($name eq 'proc.runq.runnable' || $name eq 'proc.nprocs') {
+ $units = pmiUnits(0,0,1,0,0,PM_COUNT_ONE);
+ $type = PM_TYPE_U32;
+ }
+ elsif ($name =~ /disk\.dev\..*bytes/ ||
+ $name =~ /network\.interface\..*\.bytes/) {
+ $units = pmiUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0);
+ }
+ elsif ($name eq 'disk.dev.total' || $name eq 'disk.dev.read' ||
+ $name eq 'disk.dev.write' || $name =~ /network\.interface\./) {
+ $units = pmiUnits(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE);
+ }
+ elsif ($name =~ /filesys\..*files/) {
+ $units = pmiUnits(0,0,1,0,0,PM_COUNT_ONE);
+ $type = PM_TYPE_U64;
+ }
+ elsif ($name =~ /filesys\./) {
+ $units = pmiUnits(1,0,0,PM_SPACE_MBYTE,0,0);
+ $type = PM_TYPE_U32;
+ }
+ $sts = pmiAddMetric($name, PM_ID_NULL, $type, $indom, PM_SEM_INSTANT, $units);
+ die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n" unless ($sts == 0);
+}
+
+# Wrapper for pmiPutValueHandle() - used for non-indom-null metrics.
+# If first time this instance has been seen for this indom, add it to
+# the instance domain, and get a handle and add it to the handlemap.
+# Finally add the given value into the current result for the archive.
+#
+sub putinst($$$$)
+{
+ my ($name,$indom,$instance,$value) = @_;
+ my $handlekey = $name . '/' . $instance;
+ my $instkey = $indom . '-' . $instance;
+ my ($handle,$sts);
+
+ return unless defined($value);
+
+ # instmap{} holds the last allocated inst number with $indom as the
+ # key, and marks the instance as known with $indom . $instance as the
+ # key
+ if (!defined($instmap{$instkey})) {
+ my $inst;
+ if (defined($instmap{$indom})) {
+ $instmap{$indom}++;
+ $inst = $instmap{$indom};
+ }
+ else {
+ $instmap{$indom} = 0;
+ $inst = 0;
+ }
+ $sts = pmiAddInstance($indom, $instance, $inst);
+ die "pmiAddInstance([$name], $instance, $inst): " . pmiErrStr($sts) . "\n" unless ($sts >= 0);
+ $instmap{$instkey} = $inst;
+ }
+ $handle = $handlemap{$handlekey};
+ if (!defined($handle)) {
+ if (!defined($handlemap{$name})) {
+ register_multi($name, $indom);
+ $handlemap{$name} = -1; # invalid handle for no-instance lookup
+ }
+ $sts = pmiGetHandle($name, $instance);
+ die "pmiGetHandle($name, $instance): " . pmiErrStr($sts) . "\n" unless ($sts >= 0);
+ $handlemap{$handlekey} = $handle = $sts;
+ }
+ $sts = pmiPutValueHandle($handle, dovalue($value));
+ if ($sts < 0 && $putsts == 0) { $putsts = $sts };
+}
+
+# Wrapper for pmiPutValueHandle() - used for indom-null metrics only.
+#
+sub put($$)
+{
+ my ($name,$value) = @_;
+ my ($handle,$sts);
+
+ return unless defined($value);
+
+ $handle = $handlemap{$name};
+ if (!defined($handle)) {
+ register_single($name);
+ $sts = pmiGetHandle($name, '');
+ $sts >= 0 or die "pmiGetHandle($name, ...): " . pmiErrStr($sts) . "\n";
+ $handlemap{$name} = $handle = $sts;
+ }
+ $sts = pmiPutValueHandle($handle, dovalue($value));
+ if ($sts < 0 && $putsts == 0) { $putsts = $sts };
+}
+
+if ($#ARGV != 1) {
+ print STDERR "Usage: sar2pcp infile outfile\n";
+ exit(1);
+}
+
+if ($ARGV[0] =~ /\.xml$/i) {
+ my $sts = open(XMLDATA, '<', $ARGV[0]);
+ if (!defined($sts)) {
+ print STDERR "sar2pcp: " .
+ "Failed to open sadf XML file \"$ARGV[0]\": $!\n";
+ exit(1);
+ }
+} else {
+ my $sadf = 'sadf';
+ $sadf = $ENV{SADF} if defined($ENV{SADF});
+ my $pid = open(XMLDATA, '-|', "$sadf -x $ARGV[0] -- -A");
+ if (!defined($pid)) {
+ print STDERR "sar2pcp: " .
+ "Failed to launch $sadf to convert \"$ARGV[0]\" to XML\n";
+ exit(1);
+ }
+}
+if (eof(XMLDATA)) {
+ print STDERR "sar2pcp: XML document from $ARGV[0] contains no data\n";
+ exit(1);
+}
+
+$parser = XML::TokeParser->new(\*XMLDATA, Noempty => 1);
+if (!defined($parser)) {
+ print STDERR "sar2pcp: Failed to open input stream for \"$ARGV[0]\"\n";
+ exit(1);
+}
+
+pmiStart($ARGV[1], 0);
+
+while (defined(my $token = $parser->get_token())) {
+ if ($token->is_start_tag) {
+ if ($token->tag eq 'timestamp') {
+ $stamp = dodate($token->attr->{'date'}) . ' ' . dotime($token->attr->{'time'});
+ $sample++;
+ $putsts = 0;
+ }
+ elsif ($token->tag eq 'cpu-load' || $token->tag eq 'cpu-load-all') {
+ $in_cpu_load = 1;
+ }
+ elsif ($token->tag eq 'cpu' && $in_cpu_load) {
+ # <cpu number="all" usr="2.00" nice="0.00" sys="1.20"
+ # iowait="0.00" steal="0.00" irq="0.00" soft="0.00"
+ # guest="0.00" idle="96.79"/>
+ # <cpu number="0" usr="2.00" .../>
+ # Take care: some are missing, some attribute names change!
+ #
+ my ($usr, $sys, $nice, $wait, $irq, $soft, $intr, $guest, $steal, $idle);
+
+ $usr = $token->attr->{'usr'};
+ $usr = $token->attr->{'user'} unless defined($usr); # name change
+ $usr = dovalue($usr) / 100 if defined($usr);
+ $sys = $token->attr->{'sys'};
+ $sys = $token->attr->{'system'} unless defined($sys); # name change
+ $sys = dovalue($sys) / 100 if defined($sys);
+ $idle = $token->attr->{'idle'};
+ $idle = dovalue($idle) / 100 if defined($idle);
+ $nice = $token->attr->{'nice'};
+ $nice = dovalue($nice) / 100 if defined($nice);
+ $wait = $token->attr->{'iowait'};
+ $wait = dovalue($wait) / 100 if defined($wait);
+
+ $irq = dovalue($token->attr->{'irq'});
+ $soft = dovalue($token->attr->{'soft'});
+ $intr = ($irq + $soft) / 100 unless (!defined($irq) || !defined($soft));
+
+ $guest = $token->attr->{'guest'};
+ $guest = dovalue($guest) / 100 if defined($guest);
+ $steal = $token->attr->{'steal'};
+ $steal = dovalue($steal) / 100 if defined($steal);
+
+ if ($token->attr->{'number'} eq 'all') {
+ put('kernel.all.cpu.user', $usr);
+ put('kernel.all.cpu.nice', $nice);
+ put('kernel.all.cpu.sys', $sys);
+ put('kernel.all.cpu.wait.total', $wait);
+ put('kernel.all.cpu.steal', $steal);
+ put('kernel.all.cpu.intr', $intr);
+ put('kernel.all.cpu.guest', $guest);
+ put('kernel.all.cpu.idle', $idle);
+ }
+ else {
+ my $instance = 'cpu' . $token->attr->{'number'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 0);
+
+ putinst('kernel.percpu.cpu.user', $indom, $instance, $usr);
+ putinst('kernel.percpu.cpu.nice', $indom, $instance, $nice);
+ putinst('kernel.percpu.cpu.sys', $indom, $instance, $sys);
+ putinst('kernel.percpu.cpu.wait.total', $indom, $instance, $wait);
+ putinst('kernel.percpu.cpu.steal', $indom, $instance, $steal);
+ putinst('kernel.percpu.cpu.intr', $indom, $instance, $intr);
+ putinst('kernel.percpu.cpu.guest', $indom, $instance, $guest);
+ putinst('kernel.percpu.cpu.idle', $indom, $instance, $idle);
+ }
+ }
+ elsif ($token->tag eq 'process-and-context-switch' ||
+ $token->tag eq 'processes' || $token->tag eq 'context-switch') {
+ # <process-and-context-switch per="second" proc="0.00"
+ # cswch="652.10"/>
+ put('kernel.all.pswitch', $token->attr->{'cswch'});
+ # pro tem skip proc
+ }
+ elsif ($token->tag eq 'irq') {
+ # <irq intr="sum" value="230.46"/>
+ if ($token->attr->{'intr'} eq 'sum') {
+ put('kernel.all.intr', $token->attr->{'value'});
+ }
+ # skip all other intr counts for now
+ }
+ elsif ($token->tag eq 'swap-pages') {
+ # <swap-pages per="second" pswpin="0.00" pswpout="0.00"/>
+ put('swap.pagesin', $token->attr->{'pswpin'});
+ put('swap.pagesout', $token->attr->{'pswpout'});
+ }
+ elsif ($token->tag eq 'paging') {
+ # <paging per="second" pgpgin="0.00" pgpgout="8.82" fault="49.50"
+ # majflt="0.00" pgfree="94.19" pgscank="0.00" pgscand="0.00"
+ # pgsteal="0.00" vmeff-percent="0.00"/>
+ put('mem.vmstat.pgpgin', $token->attr->{'pgpgin'});
+ put('mem.vmstat.pgpgout', $token->attr->{'pgpgout'});
+ put('mem.vmstat.pgfault', $token->attr->{'fault'});
+ put('mem.vmstat.pgmajfault', $token->attr->{'majflt'});
+ put('mem.vmstat.pgfree', $token->attr->{'pgfree'});
+ # pro tem skip pgscank, pgscand, pgsteal and vmeff-percent
+ }
+ elsif ($token->tag eq 'io') {
+ # <io per="second">
+ # no metric values from attribute data, all tags
+ }
+ elsif ($token->tag eq 'tps') {
+ # <tps>0.80</tps>
+ # no metric values from attribute data, all tags
+ }
+ elsif ($token->tag eq 'io-reads') {
+ # <io-reads rtps="0.00" bread="0.00"/>
+ # assume blocks are 512 bytes
+ my $iops = dovalue($token->attr->{'rtps'});
+ if (defined($iops)) {
+ put('disk.all.read', $iops);
+ $disk_all_total = $iops;
+ }
+ my $bytes = dovalue($token->attr->{'bread'});
+ if (defined($bytes)) {
+ put('disk.all.read_bytes', $bytes / 2);
+ $disk_all_total_bytes = $bytes;
+ }
+ }
+ elsif ($token->tag eq 'io-writes') {
+ # <io-writes wtps="0.80" bwrtn="17.64"/>
+ # assume blocks are 512 bytes
+ my $iops = dovalue($token->attr->{'wtps'});
+ if (defined($iops)) {
+ $disk_all_total += $iops;
+ put('disk.all.write', $iops);
+ put('disk.all.total', $disk_all_total);
+ }
+ my $bytes = dovalue($token->attr->{'bwrtn'});
+ if (defined($bytes)) {
+ put('disk.all.write_bytes', $bytes / 2);
+ $disk_all_total_bytes += $bytes;
+ put('disk.all.total_bytes', $disk_all_total_bytes / 2);
+ }
+ }
+ elsif ($token->tag eq 'memfree') {
+ # <memfree>78896</memfree>
+ $snarf_name = 'mem.util.free';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'memused') {
+ # <memused>947232</memused>
+ $snarf_name = 'mem.util.used';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'buffers') {
+ # <buffers>165296</buffers>
+ $snarf_name = 'mem.util.bufmem';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'cached') {
+ # <cached>368644</cached>
+ $snarf_name = 'mem.util.cached';
+ $snarf_text = 1;
+ }
+ # pro tem skip <commit>
+ # pro tem skip <commit-percent>
+ # pro tem skip <active>
+ # pro tem skip <inactive>
+ elsif ($token->tag eq 'dirty') {
+ # <dirty>208</dirty>
+ $snarf_name = 'mem.util.dirty';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'swpfree') {
+ # <swpfree>1920808</swpfree>
+ $snarf_name = 'mem.util.swapFree';
+ $snarf_text = 1;
+ }
+ # pro tem skip <swpused>
+ elsif ($token->tag eq 'swpcad') {
+ # <swpcad>19208</swpcad>
+ $snarf_name = 'mem.util.swapCached';
+ $snarf_text = 1;
+ }
+ # pro tem skip <frmpg>, <bufpg> and <campg>
+ elsif ($token->tag eq 'kernel') {
+ # <kernel dentunusd="86251" file-nr="9696" inode-nr="86841"
+ # pty-nr="123"/>
+ # depending on sadc version, these may be attributes
+ # or separate tags (below).
+ put('vfs.dentry.count', $token->attr->{'dentunusd'});
+ put('vfs.files.count', $token->attr->{'file-nr'});
+ put('vfs.inodes.count', $token->attr->{'inode-nr'});
+ # pro tem skip pty-nr
+ }
+ elsif ($token->tag eq 'dentunusd') {
+ # <dentunusd>75415</dentunusd>
+ $snarf_name = 'vfs.dentry.count';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'file-nr' || $token->tag eq 'file-sz') {
+ # <file-nr>4608</file-nr>
+ # metric defined already (above - different XML output versions!)
+ $snarf_name = 'vfs.files.count';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'inode-nr' || $token->tag eq 'inode-sz') {
+ # <inode-nr>72802</inode-nr>
+ # metric defined already (above - different XML output versions!)
+ $snarf_name = 'vfs.inodes.count';
+ $snarf_text = 1;
+ }
+ # pro tem skip pty-nr
+ elsif ($token->tag eq 'queue') {
+ my $indom = pmInDom_build(PMI_DOMAIN, 1);
+ put('proc.runq.runnable', $token->attr->{'runq-sz'});
+ put('proc.nprocs', $token->attr->{'plist-sz'});
+ putinst('kernel.all.load', $indom, '1 minute', $token->attr->{'ldavg-1'});
+ putinst('kernel.all.load', $indom, '5 minute', $token->attr->{'ldavg-5'});
+ putinst('kernel.all.load', $indom, '15 minute', $token->attr->{'ldavg-15'});
+ }
+ elsif ($token->tag eq 'disk-device') {
+ # <disk-device dev="dev8-0" tps="0.00" rd_sec="0.00" wr_sec="0.00"
+ # avgrq-sz="0.00" avgqu-sz="0.00" await="0.00" svctm="0.00"
+ # util-percent="0.00"/>
+ my $instance = 'disk' . $token->attr->{'dev'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 2);
+ my ($read_bytes, $write_bytes, $percent);
+
+ putinst('disk.dev.total', $indom, $instance, $token->attr->{'tps'});
+ # 512-byte sectors/sec
+ $percent = dovalue($token->attr->{'util-percent'});
+ $read_bytes = dovalue($token->attr->{'rd_sec'});
+ $write_bytes = dovalue($token->attr->{'wr_sec'});
+ if (defined($read_bytes)) {
+ putinst('disk.dev.read_bytes', $indom, $instance, $read_bytes / 2);
+ }
+ if (defined($write_bytes)) {
+ putinst('disk.dev.write_bytes', $indom, $instance, $write_bytes / 2);
+ }
+ if (defined($read_bytes) && defined($write_bytes)) {
+ putinst('disk.dev.total_bytes', $indom, $instance,
+ ($read_bytes + $write_bytes) / 2);
+ }
+ if (defined($percent)) {
+ putinst('disk.dev.avactive', $indom, $instance, $percent / 100);
+ }
+ putinst('disk.dev.avrqsz', $indom, $instance, dovalue($token->attr->{'avgrq-sz'}));
+ putinst('disk.dev.avqsz', $indom, $instance, dovalue($token->attr->{'avgqu-sz'}));
+ putinst('disk.dev.await', $indom, $instance, dovalue($token->attr->{'await'}));
+ putinst('disk.dev.svctm', $indom, $instance, dovalue($token->attr->{'svctm'}));
+ # TODO disk.all aggregation?
+ }
+ elsif ($token->tag eq 'net-device') {
+ # <net-device iface="eth0">
+ $interface = $token->attr->{'iface'};
+ }
+ elsif ($token->tag eq 'net-dev') {
+ # <net-dev iface="eth0" rxpck="0.00" txpck="0.00" rxkB="0.00"
+ # txkB="0.00" rxcmp="0.00" txcmp="0.00" rxmcst="0.00"/>
+ my $indom = pmInDom_build(PMI_DOMAIN, 4);
+ my $iface = $token->attr->{'iface'};
+ $interface = $iface if (defined($iface)); # else it comes from net-device
+
+ putinst('network.interface.in.packets', $indom, $interface, $token->attr->{'rxpck'});
+ putinst('network.interface.out.packets', $indom, $interface, $token->attr->{'txpck'});
+ putinst('network.interface.in.bytes', $indom, $interface, $token->attr->{'rxkB'});
+ putinst('network.interface.out.bytes', $indom, $interface, $token->attr->{'txkB'});
+ # pro tem skip rxcmp, txcmp, rxmcst
+ }
+ elsif ($token->tag eq 'net-edev') {
+ # <net-edev iface="eth0" rxerr="0.00" txerr="0.00" coll="0.00"
+ # rxdrop="0.00" txdrop="0.00" txcarr="0.00" rxfram="0.00"
+ # rxfifo="0.00" txfifo="0.00"/>
+ my $indom = pmInDom_build(PMI_DOMAIN, 4);
+ my $iface = $token->attr->{'iface'};
+ $interface = $iface if (defined($iface)); # else it comes from net-device
+
+ putinst('network.interface.in.errors', $indom, $interface, $token->attr->{'rxerr'});
+ putinst('network.interface.out.errors', $indom, $interface, $token->attr->{'txerr'});
+ putinst('network.interface.collisions', $indom, $interface, $token->attr->{'coll'});
+ putinst('network.interface.in.drops', $indom, $interface, $token->attr->{'rxdrop'});
+ putinst('network.interface.out.drops', $indom, $interface, $token->attr->{'txdrop'});
+ putinst('network.interface.out.carrier', $indom, $interface, $token->attr->{'txcarr'});
+ putinst('network.interface.in.frame', $indom, $interface, $token->attr->{'rxfram'});
+ putinst('network.interface.in.fifo', $indom, $interface, $token->attr->{'rxfifo'});
+ putinst('network.interface.out.fifo', $indom, $interface, $token->attr->{'txfifo'});
+ }
+ elsif ($token->tag eq 'net-nfs' || $token->tag eq 'net-nfsd' ||
+ $token->tag eq 'net-sock' || $token->tag eq 'net-sock6' ||
+ $token->tag eq 'net-ip' || $token->tag eq 'net-eip' ||
+ $token->tag eq 'net-ip6' || $token->tag eq 'net-eip6' ||
+ $token->tag eq 'net-icmp' || $token->tag eq 'net-eicmp' ||
+ $token->tag eq 'net-icmp6' || $token->tag eq 'net-eicmp6' ||
+ $token->tag eq 'net-tcp' || $token->tag eq 'net-etcp' ||
+ $token->tag eq 'net-udp' || $token->tag eq 'net-udp6') {
+ # skip all the network protocol stats for now
+ next;
+ }
+ elsif ($token->tag eq 'host') {
+ # <host nodename="bozo">
+ pmiSetHostname($token->attr->{'nodename'}) >= 0
+ or die "pmiSetHostname($token->attr->{'nodename'}): " . pmiErrStr(-1) . "\n";
+ pmiSetTimezone($zone) >= 0
+ or die "pmiSetTimezone($zone): " . pmiErrStr(-1) . "\n";
+ }
+ elsif ($token->tag eq 'number-of-cpus') {
+ # <number-of-cpus>1</number-of-cpus>
+ $snarf_name = 'hinv.ncpu';
+ $snarf_text = 2;
+ }
+ elsif ($token->tag eq 'filesystem') {
+ # <filesystem fsname="/dev/sda8" MBfsfree="429418" MBfsused="39806" fsused-percent="8.48" ufsused-percent="13.57" Ifree="30318480" Iused="204912" Iused-percent="0.67"/>
+ my $instance = $token->attr->{'fsname'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 5);
+ my ($free, $used);
+ $free = dovalue($token->attr->{'MBfsfree'});
+ $used = dovalue($token->attr->{'MBfsused'});
+ if (defined($free) && defined($used)) {
+ putinst('filesys.capacity', $indom, $instance, $used+$free);
+ }
+ putinst('filesys.free', $indom, $instance, $free);
+ putinst('filesys.used', $indom, $instance, $used);
+ $free = dovalue($token->attr->{'Ifree'});
+ $used = dovalue($token->attr->{'Iused'});
+ if (defined($free) && defined($used)) {
+ putinst('filesys.maxfiles', $indom, $instance, $used+$free);
+ }
+ putinst('filesys.freefiles', $indom, $instance, $free);
+ putinst('filesys.usedfiles', $indom, $instance, $used);
+ }
+ elsif (!defined($skipped_tags{$token->tag})) {
+ $warning_tags{$token->tag} = 1;
+ }
+ if ($putsts != 0) {
+ my $tag = $token->tag;
+ die "pmiPutValue: Failed @ $stamp on $tag: " . pmiErrStr($putsts) . "\n";
+ }
+ next;
+ }
+ elsif ($token->is_end_tag) {
+ #debug# print "end: " . $token->tag . "\n";
+ if ($token->tag eq 'timestamp') {
+ #debug# my ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($stamp, "+1000");
+ #debug# print "stamp: $stamp time: " . str2time($stamp) . " pieces: ss=$ss mm=$mm hh=$hh dd=$day month=$month year=$year";
+ #debug# if (defined($zone)) { print " zone=$zone"; }
+ #debug# print "\n";
+ pmiWrite(str2time($stamp, $zone), 0) >= 0
+ or die "pmiWrite: @ $stamp: " . pmiErrStr(-1) . "\n";
+ }
+ elsif ($token->tag eq 'cpu-load' || $token->tag eq 'cpu-load-all') {
+ $in_cpu_load = 0;
+ }
+ elsif ($token->tag eq 'number-of-cpus') {
+ # log metric once ... don't create or use handle
+ # value snarfed in $save_text
+ pmiAddMetric($snarf_name, PM_ID_NULL, PM_TYPE_U32, PM_INDOM_NULL,
+ PM_SEM_DISCRETE, pmiUnits(0,0,0,0,0,0)) == 0
+ or die "pmiAddMetric(hinv.ncpu, ...): " . pmiErrStr(-1) . "\n";
+ pmiPutValue($snarf_name, '', $save_text) >= 0
+ or die "pmiPutValue(hinv.ncpu,,$save_text): " . pmiErrStr(-1) . "\n";
+ }
+ }
+ elsif ($token->is_text) {
+ if ($snarf_text == 1) {
+ put($snarf_name, $token->text);
+ $snarf_text = 0;
+ }
+ elsif ($snarf_text == 2) {
+ $save_text = $token->text;
+ $snarf_text = 0;
+ }
+ else {
+ #debug# print "text: " . $token->text . "\n";
+ ;
+ }
+ next;
+ }
+ elsif ($token->is_comment) {
+ #debug# print "comment: " . $token->text . "\n";
+ next;
+ }
+ elsif ($token->is_pi) {
+ #debug# print "process instruction: " . $token->target . "\n";
+ next;
+ }
+}
+
+if (%warning_tags) {
+ my @warned = keys %warning_tags;
+ my $nwarns = $#warned + 1;
+ print STDERR
+ "PCP archive produced, but $nwarns unrecognised tag(s) detected in ".
+ "$sample samples:\n\t";
+ print STDERR '"', pop(@warned), '"';
+ while (@warned) { print STDERR ', "', pop(@warned), '"'; }
+ print STDERR "\n";
+}
+
+pmiEnd();
+
+=pod
+
+=head1 NAME
+
+sar2pcp - Import sar data and create a PCP archive
+
+=head1 SYNOPSIS
+
+B<sar2pcp> I<infile> I<outfile>
+
+=head1 DESCRIPTION
+
+B<sar2pcp> is intended to read a binary System Activity Reporting
+(sar) data file
+as created by B<sadc>(1) (I<infile>) and translate this into a Performance
+Co-Pilot (PCP) archive with the basename I<outfile>.
+
+However, if I<infile> has the suffix ".xml", then it will be considered
+already in XML format and B<sar2pcp> will operate directly on it.
+
+The resultant PCP achive may be used with all the PCP client tools
+to graph subsets of the data using B<pmchart>(1),
+perform data reduction and reporting, filter with
+the PCP inference engine B<pmie>(1), etc.
+
+A series of physical files will be created with the prefix I<outfile>.
+These are I<outfile>B<.0> (the performance data),
+I<outfile>B<.meta> (the metadata that describes the performance data) and
+I<outfile>B<.index> (a temporal index to improve efficiency of replay
+operations for the archive). If any of these files exists already,
+then B<sar2pcp> will B<not> overwrite them and will exit with an error
+message of the form
+
+__pmLogNewFile: "blah.0" already exists, not over-written
+
+B<sar2pcp> is a Perl script that uses the PCP::LogImport Perl wrapper
+around the PCP I<libpcp_import>
+library, and as such could be used as an example to develop new
+tools to import other types of performance data and create PCP archives.
+A Python wrapper module is also available.
+
+=head1 CAVEATS
+
+When not using the XML input option, B<sar2pcp> requires I<infile> to
+have been created by a version of B<sadc>(1) from
+L<http://sebastien.godard.pagesperso-orange.fr/>
+which includes the B<sadf>(1) utility
+to translate I<infile> into an XML stream (any since version 6);
+B<sar2pcp> will automatically run B<sadf>(1) and translate the resultant
+XML into a PCP archive.
+
+When using binary B<sadc> files
+it is important to ensure the installed B<sadf> is compatible with the
+version of B<sadc> that originally generated the binary files. Simply
+assuming a newer installed version will work is unfortunately far too
+optimistic, and nor should one assume that binary data from different
+platforms (e.g. different endianness) will work - these issues are due
+to limitations in B<sadc> and B<sadf>, and not in B<sar2pcp> itself.
+
+Fortunately, the B<sadf> message indicating that an incompatibility has
+been detected is consistent across versions, and is always prefixed
+
+Invalid system activity file
+
+Using an XML I<infile> has the advantage that the installed version
+of B<sadf> is completely bypassed. B<sar2pcp> undertakes to transform
+any valid XML produced by any of the different variations of B<sadf>
+into a valid PCP archive. Any version of PCP will be able to interpret
+the archive files produced by any version of B<sar2pcp>, and you are
+also free to move the binary PCP archive between different platforms,
+different hardware, even different operating systems - it Just Works (TM).
+
+=head1 SEE ALSO
+
+B<pmie>(1),
+B<pmchart>(1),
+B<pmlogger>(1),
+B<pmlogextract>(1),
+B<pmlogsummary>(1),
+B<sadc>(1),
+B<sadf>(1),
+B<sar>(1),
+B<Date::Parse>(3pm),
+B<Date::Format>(3pm),
+B<PCP::LogImport>(3pm),
+B<XML::TokeParser>(3pm) and
+B<LOGIMPORT>(3).
diff --git a/src/sheet2pcp/GNUmakefile b/src/sheet2pcp/GNUmakefile
new file mode 100644
index 0000000..7455d0f
--- /dev/null
+++ b/src/sheet2pcp/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPT = sheet2pcp
+LDIRT = $(MAN_PAGES) $(MAN_PAGES).tmp
+LSRCFILES = $(SCRIPT)
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = $(SCRIPT).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: $(MAN_PAGES)
+
+$(SCRIPT).$(MAN_SECTION): $(SCRIPT)
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BIN_DIR)/$(SCRIPT)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/sheet2pcp/sheet2pcp b/src/sheet2pcp/sheet2pcp
new file mode 100755
index 0000000..554f647
--- /dev/null
+++ b/src/sheet2pcp/sheet2pcp
@@ -0,0 +1,843 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+use strict;
+use warnings;
+
+use XML::TokeParser;
+use Getopt::Std;
+use Date::Parse;
+use Date::Format;
+use PCP::LogImport;
+
+# Spreadsheet::Read is not include in some distros, so we give some
+# hints here, rather than dying with
+# Can't locate Spreadsheet/Read in @INC ...
+#
+eval
+{
+ require Spreadsheet::Read;
+ Spreadsheet::Read->import();
+};
+
+if ($@) {
+ # failed
+ die "Error: Perl module Spreadsheet::Read needs to be installed either from\nyour distro or downloaded and installed from CPAN\n";
+}
+
+my $skip_rows = 0; # N from <sheet heading="N">
+my $in_metric = 0; # XML parser context state ... in <metric> element
+my $in_data = 0; # XML parser context state ... in <data> element
+my $idx = -1; # index into columns of spreadsheet
+my $stamp_idx = -1; # column containing the datetime info
+my %indom_map = (); # key=metricname value=indom
+my %inst_map = (); # key=indom value=last_inst_assigned, and
+ # key=indom.instance value=inst
+my @handle = (); # pmi* handles, one per metric-instance pair
+my $sheet;
+
+my $zone = "UTC"; # default timezone
+my $label_zone;
+my $datefmt = "DMY"; # default order of date fields
+my %options; # for command line arguments
+
+sub dodate($)
+{
+ # convert datetime format DD[/-]MM[/-]YY[YY] HH:MM[:SS] from spreadsheet
+ # parsers into the ISO-8601 dates that Date::Parse
+ # seems to be able to parse correctly ... this would appear to
+ # have to be YYYY-MM-DDTHH:MM:SS.000000 and then pass the timezone
+ # as the second parameter to str2time()
+ #
+ my ($datetime) = @_;
+ # date separators may be - or /, space separates date from time, time
+ # separators are :
+ my @field = split(/[ :\/-]/, $datetime);
+ my $mm;
+ my $yy;
+ my $dd;
+
+ if ($#field == 4) {
+ # assume :SS is missing, set seconds to 00
+ push(@field, "00");
+ }
+
+ if ($#field != 5) {
+ print "dodate: bad datetime format: \"$datetime\" => ";
+ foreach (@field) { print " $_"; }
+ print "\n";
+ exit(1);
+ }
+
+ if ($datefmt eq "DMY") {
+ $dd = $field[0]; $mm = $field[1]; $yy = $field[2];
+ }
+ elsif ($datefmt eq "MDY") {
+ $mm = $field[0]; $dd = $field[1]; $yy = $field[2];
+ }
+ elsif ($datefmt eq "YMD") {
+ $yy = $field[0]; $mm = $field[1]; $dd = $field[2];
+ }
+
+ # get into cannonical DD, MM and YYYY format
+ if ($dd < 10 && $dd !~ /^0/) { $dd .= "0" }; # add leading zero
+ if ($mm < 10 && $mm !~ /^0/) { $mm .= "0" }; # add leading zero
+ if ($yy < 100) {
+ # terrible Y2K hack ... will stop working in 2080
+ if ($yy <= 80) { $yy += 2000; }
+ else { $yy += 1900; }
+ }
+
+ return $yy . "-" . $mm . "-" . $dd . "T" . $field[3] . ":" . $field[4] . ":" . $field[5];
+}
+
+# process the mapfile and set up the metadata and handles needed
+# by the main loop
+#
+sub domap($)
+{
+ my ($mapfile) = @_;
+ my $sts = 0;
+ my $pmid;
+ my $indom;
+ my $units;
+ my $type;
+ my $sem;
+ my $name;
+ my $instance;
+ my $lsts;
+
+ my $parser = XML::TokeParser->new($mapfile, Noempty => 1);
+ if (!defined($parser)) {
+ print "sheet2pcp: Failed to open mapfile \"$mapfile\"\n";
+ exit(1);
+ }
+
+ while (defined(my $token = $parser->get_token())) {
+ if ($token->is_start_tag) {
+ if ($token->tag eq "sheet") {
+ foreach my $a (keys %{$token->attr}) {
+ if ($a eq "heading") {
+ $skip_rows = $token->attr->{heading};
+ }
+ elsif ($a eq "hostname") {
+ if (pmiSetHostname($token->attr->{hostname}) < 0) {
+ print "Mapfile Warning: failed to set hostname " . $token->attr->{hostname} . ": " . pmiErrStr(-1) . "\n";
+ }
+ }
+ elsif ($a eq "timezone") {
+ $zone = $token->attr->{timezone};
+ }
+ elsif ($a eq "datefmt") {
+ if ($token->attr->{datefmt} eq "DMY" ||
+ $token->attr->{datefmt} eq "MDY" ||
+ $token->attr->{datefmt} eq "YMD") {
+ $datefmt = $token->attr->{datefmt};
+ }
+ else {
+ print "Mapfile Error: bad format for attribute datefmt=\"" . $token->attr->{datefmt} . "\"\n";
+ $sts++;
+ }
+ }
+ else {
+ print "Mapfile Error: attribute $a=\"" . $token->attr->{$a} . "\" not expected for <sheet> tag\n";
+ $sts++;
+ }
+ }
+ }
+ elsif ($token->tag eq "metric") {
+ $in_metric = 1;
+ # defaults ...
+ $pmid = PM_ID_NULL;
+ $indom = PM_INDOM_NULL;
+ $units = pmiUnits(0,0,0,0,0,0);
+ $type = PM_TYPE_FLOAT;
+ $sem = PM_SEM_INSTANT;
+ $name = undef;
+ $lsts = $sts;
+ foreach my $a (keys %{$token->attr}) {
+ if ($a eq "pmid") {
+ my $value = $token->attr->{pmid};
+ if ($value =~ /^[0-9]+\.[0-9]+\.[0-9]+$/ ||
+ $value =~ /^PMI_DOMAIN\.[0-9]+\.[0-9]+$/) {
+ # expected N.N.N or PMI_DOMAIN.N.N format
+ my @args = split(/\./, $value);
+ $args[0] = PMI_DOMAIN if $args[0] eq "PMI_DOMAIN";
+ $pmid = pmid_build($args[0],$args[1],$args[2]);
+ }
+ elsif ($value eq "PM_ID_NULL") {
+ # accept literal default value
+ $pmid = PM_ID_NULL;
+ }
+ else {
+ print "Mapfile Error: bad format for attribute pmid=\"$value\"\n";
+ $sts++;
+ }
+ }
+ elsif ($a eq "indom") {
+ my $value = $token->attr->{indom};
+ if ($value =~ /^[0-9]+\.[0-9]+$/ ||
+ $value =~ /^PMI_DOMAIN\.[0-9]+$/) {
+ # expected N.N or PMI_DOMAIN.N format
+ my @args = split(/\./, $value);
+ $args[0] = PMI_DOMAIN if $args[0] eq "PMI_DOMAIN";
+ $indom = pmInDom_build($args[0],$args[1]);
+ }
+ elsif ($value eq "PM_INDOM_NULL") {
+ # accept literal default value
+ $indom = PM_INDOM_NULL;
+ }
+ else {
+ print "Mapfile Error: bad format for attribute indom=\"$value\"\n";
+ $sts++;
+ }
+ }
+ elsif ($a eq "units") {
+ my $value = $token->attr->{units};
+ my @args = split(/,/, $value);
+ if ($#args != 5) {
+ print "Mapfile Error: bad format for attribute units=\"$value\"\n";
+ $sts++;
+ }
+ else {
+ for (my $i = 0; $i < 6; $i++) {
+ $_ = eval($args[$i]);
+ if (!defined($_)) {
+ print "Mapfile Error: bad component ($args[$i]) for attribute units=\"$value\"\n";
+ $sts++;
+ }
+ else {
+ $args[$i] = $_;
+ }
+ }
+ if ($sts == 0) {
+ $units = pmiUnits($args[0], $args[1], $args[2],
+ $args[3], $args[4], $args[5]);
+ }
+ }
+ }
+ elsif ($a eq "type") {
+ my $value = $token->attr->{type};
+ $_ = eval($value);
+ if (!defined($_)) {
+ print "Mapfile Error: bad value for attribute type=\"$value\"\n";
+ $sts++;
+ }
+ else {
+ $type = $_;
+ }
+ }
+ elsif ($a eq "sem") {
+ my $value = $token->attr->{sem};
+ $_ = eval($value);
+ if (!defined($_)) {
+ print "Mapfile Error: bad value for attribute sem=\"$value\"\n";
+ $sts++;
+ }
+ else {
+ $sem = $_;
+ }
+ }
+ else {
+ print "Mapfile Error: attribute $a=\"" . $token->attr->{$a} . "\" not expected for <metric> tag\n";
+ $sts++;
+ }
+ }
+ }
+ elsif ($token->tag eq "data") {
+ $in_data = 1;
+ $idx++;
+ $name = undef;
+ $instance = undef;
+ $lsts = $sts;
+ foreach my $a (keys %{$token->attr}) {
+ print "Mapfile Error: attribute $a=\"" . $token->attr->{$a} . "\" not expected for <data> tag\n";
+ $sts++;
+ }
+ }
+ elsif ($token->tag eq "datetime") {
+ $idx++;
+ $stamp_idx = $idx;
+ foreach my $a (keys %{$token->attr}) {
+ print "Mapfile Error: attribute $a=\"" . $token->attr->{$a} . "\" not expected for <datetime> tag\n";
+ $sts++;
+ }
+ }
+ elsif ($token->tag eq "skip") {
+ $idx++;
+ foreach my $a (keys %{$token->attr}) {
+ print "Mapfile Error: attribute $a=\"" . $token->attr->{$a} . "\" not expected for <skip> tag\n";
+ $sts++;
+ }
+ }
+ else {
+ print "Mapfile Error: unexpected start tag " . $token->raw . "\n";
+ $sts++;
+ }
+ }
+ elsif ($token->is_end_tag) {
+ if ($token->tag eq "sheet") {
+ if ($stamp_idx == -1) {
+ print "Mapfile Error: missing <datetime> element\n";
+ $sts++;
+ }
+ elsif ($idx < 1) {
+ print "Mapfile Error: no <data> element\n";
+ $sts++;
+ }
+ # all finished, nothing more to be done
+ }
+ elsif ($token->tag eq "metric") {
+ if (defined($name)) {
+ if ($lsts == $sts) {
+ # no errors in this <metric ...>name</metric> element
+ if (pmiAddMetric($name, $pmid, $type, $indom, $sem, $units) < 0) {
+ print "Mapfile Error: failed to define metric $name: " . pmiErrStr(-1) . "\n";
+ $sts++;
+ }
+ else {
+ # need metric name => indom for <data> later
+ #
+ $indom_map{$name} = $indom;
+ }
+ }
+ }
+ else {
+ print "Mapfile Error: missing metric name in <metric> element\n";
+ $sts++;
+ }
+ $in_metric = 0;
+ }
+ elsif ($token->tag eq "data") {
+ if (defined($name)) {
+ if ($lsts == $sts) {
+ # no errors in this <data ...>metricspec</data> element
+ $indom = $indom_map{$name};
+ if (defined($indom)) {
+ if (defined($instance)) {
+ if (!defined($inst_map{$indom . $instance})) {
+ # first time for this instance and indom
+ my $inst;
+ if (defined($inst_map{$indom})) {
+ $inst_map{$indom}++;
+ $inst = $inst_map{$indom};
+ }
+ else {
+ # first time for this indom
+ $inst_map{$indom} = 0;
+ $inst = 0;
+ }
+ if (pmiAddInstance($indom, $instance, $inst) < 0) {
+ print "Mapfile Error: failed to define instance \"$instance\" ($inst) for metric $name: " . pmiErrStr(-1) . "\n";
+ $sts++;
+ }
+ else {
+ # add new instance for indom
+ $inst_map{$indom . $instance} = $inst;
+ }
+ }
+ my $h = pmiGetHandle($name, $instance);
+ if ($h < 0) {
+ die "pmiGetHandle: failed to create handle for metricspec $name" . "[" . $instance . "]: " . pmiErrStr($sts) . "\n";
+ $sts++;
+ }
+ else {
+ push(@handle, $h);
+ }
+ }
+ else {
+ my $h = pmiGetHandle($name, "");
+ if ($h < 0) {
+ die "pmiGetHandle: failed to create handle for metricspec $name: " . pmiErrStr($sts) . "\n";
+ }
+ else {
+ push(@handle, $h);
+ }
+ }
+ }
+ else {
+ print "Mapfile Error: metric name ($name) in <data> element not defined in earlier <metric> element\n";
+ $sts++;
+ }
+ }
+ }
+ else {
+ print "Mapfile Error: missing metricspec in <data> element\n";
+ $sts++;
+ }
+ $in_data = 0;
+ }
+ elsif ($token->tag eq "datetime" || $token->tag eq "skip") {
+ # -1 flags this a column to be skipped
+ push(@handle, -1);
+ }
+ else {
+ print "Mapfile Error: unexpected end tag " . $token->raw . "\n";
+ $sts++;
+ }
+ }
+ elsif ($token->is_text) {
+ if ($in_metric || $in_data) {
+ $name = $token->text;
+ $name =~ s/^\s+//;
+ $name =~ s/\s+$//;
+ if ($in_data) {
+ my @field = split(/\[/, $name);
+ if ($#field > 1) {
+ print "Mapfile Error: extra [ in metricspec \"" . $token->text . "\" in <data> element\n";
+ $sts++;
+ }
+ else {
+ if (defined($field[1])) {
+ $name = $field[0];
+ $instance = $field[1];
+ if ($instance =~ /]$/) {
+ $instance =~ s/]$//;
+ }
+ else {
+ print "Mapfile Error: missing ] in metricspec \"" . $token->text . "\" in <data> element\n";
+ $sts++;
+ }
+ }
+ }
+ }
+ }
+ else {
+ print "Mapfile Error: unexpected text \"" . $token->text . "\"\n";
+ $sts++;
+ }
+ }
+ elsif ($token->is_comment) {
+ # <!--pmiDump--> is special, silently ignore other comments
+ pmiDump() if $token->text eq "pmiDump";
+
+ }
+ elsif ($token->is_pi) {
+ print "Mapfile Warning: unexpected process instruction (ignored) " . $token->raw . "\n";
+ }
+ }
+ if ($sts != 0) {
+ print "Abandoned after $sts mapfile error";
+ print "s" if $sts > 1;
+ print "\n";
+ exit(0);
+ }
+}
+
+my $sts = getopts('h:Z:', \%options);
+
+if (!defined($sts) || $#ARGV != 2) {
+ print "Usage: sheet2pcp [-h host] [-Z timezone] infile mapfile outfile\n";
+ exit(1);
+}
+
+if (exists($options{Z})) {
+ $zone = $options{Z};
+ if ($zone !~ /^[-+][0-9][0-9][0-9][0-9]$/) {
+ print "sheet2pcp: Illegal -Z value, must be +NNNN or -NNNN\n";
+ exit(1);
+ }
+}
+
+# initialize the output archive so we can add the metadata from the mapfile
+#
+pmiStart($ARGV[2], 0);
+
+if (exists($options{h})) {
+ if (pmiSetHostname($options{h}) < 0) {
+ print "sheet2pcp: Warning: failed to set hostname from -h " . $options{h} . ": " . pmiErrStr(-1) . "\n";
+ }
+}
+
+# all the hard work is done here ...
+#
+domap($ARGV[1]);
+
+# Serious strangeness here ...
+# the Perl Date::Parse and Date::Format routines appear to
+# only work with timezones of the format +NNNN or -NNNN
+# ("UTC" is an exception)
+#
+# PCP expects a $TZ style timezone in the archive label, so
+# we have to make up a PCP-xx:xx timezone ... note this
+# invloves a sign reversal!
+#
+$label_zone = $zone;
+if ($zone =~ /^[-+][0-9][0-9][0-9][0-9]/) {
+ $label_zone =~ s/^\+/PCP-/;
+ $label_zone =~ s/^-/PCP+/;
+ $label_zone =~ s/(..)$/:$1/;
+}
+elsif ($zone ne "UTC") {
+ print "sheet2pcp: Warning: unexpected timezone ($zone), reverting to UTC\n";
+ $zone = "UTC";
+ $label_zone = "UTC";
+}
+if (pmiSetTimezone($label_zone) < 0) {
+ print "Mapfile Warning: failed to set timezone \"" . $label_zone . "\": " . pmiErrStr(-1) . "\n";
+}
+
+if ($ARGV[0] =~ /\.csv$/) {
+ system("perl -MText::CSV_XS -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Text::CSV_XS module not installed";
+ $sheet = ReadData($ARGV[0]);
+}
+elsif ($ARGV[0] =~ /\.ods$/ || $ARGV[0] =~ /\.sxc$/) {
+ system("perl -MArchive::Zip -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Archive::Zip module not installed";
+ system("perl -MSpreadsheet::ReadSXC -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Spreadsheet::ReadSXC module not installed";
+ $sheet = ReadData($ARGV[0], dtfmt => "yyyy-mm-dd");
+}
+elsif ($ARGV[0] =~ /\.xls$/) {
+ system("perl -MOLE::Storage_Lite -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl OLE::Storage_Lite module not installed";
+ system("perl -MSpreadsheet::ParseExcel -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Spreadsheet::ParseExcel module not installed";
+ $sheet = ReadData($ARGV[0], dtfmt => "yyyy-mm-dd");
+}
+elsif ($ARGV[0] =~ /\.xlsx$/) {
+ system("perl -MOLE::Storage_Lite -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl OLE::Storage_Lite module not installed";
+ system("perl -MSpreadsheet::ParseExcel -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Spreadsheet::ParseExcel module not installed";
+ system("perl -MSpreadsheet::XLSX -e 1 >/dev/null 2>&1") == 0 or
+ die "Perl Spreadsheet::XLSX module not installed";
+ $sheet = ReadData($ARGV[0], dtfmt => "yyyy-mm-dd");
+}
+else {
+ print "sheet2pcp: Error: No clue how to deduce format of spreadsheet $ARGV[0]\n";
+ exit(1);
+}
+
+if (!defined($sheet)) {
+ print "sheet2pcp: Failed to open spreadsheet \"$ARGV[0]\"\n";
+ exit(1);
+}
+
+#debug# print "maxrow=$sheet->[1]{maxrow} maxcol=$sheet->[1]{maxcol}\n";
+#debug# print "mapfile " . ($idx+1) . " columns, timestamp @ column " . $stamp_idx . "\n";
+
+if ($sheet->[1]{maxcol} != $idx+1) {
+ print "sheet2pcp: Warning: columns from mapfile (" . ($idx+1) . ") not equal to columns from spreadsheet (" . $sheet->[1]{maxcol} . "), some data will not be exported\n";
+}
+
+for (my $row = 1; $row <= $sheet->[1]{maxrow}; $row++) {
+ my $stamp = undef;
+ if ($skip_rows) {
+ $skip_rows--;
+ next;
+ }
+ for (my $col = 1; $col <= $sheet->[1]{maxcol}; $col++) {
+ my $cell = cr2cell($col, $row);
+ # ignore columns after last one in mapfile
+ next if ($col > $idx+1);
+ #debug# print "$cell = $sheet->[1]{$cell}\n"
+ if (!defined($handle[$col-1])) {
+ pmiDump();
+ die "No handle[] entry for cell[" . $cell . "]. Check Handles in dump above.\n";
+ }
+ if (!defined($sheet->[1]{$cell})) {
+ print "Warning: cell[" . $cell . "] empty, but data expected\n";
+ }
+ else {
+ if ($col == $stamp_idx+1) {
+ $stamp = dodate($sheet->[1]{$cell});
+ #debug# print $sheet->[1]{$cell} . " -> " . $stamp . "\n";
+ }
+ elsif ($handle[$col-1] != -1) {
+ pmiPutValueHandle($handle[$col-1], $sheet->[1]{$cell}) >= 0
+ or die "pmiPutValueHandle: failed at cell[" . $cell . "]: " . pmiErrStr(-1) . "\n";
+ }
+ }
+ }
+ if (!defined($stamp)) {
+ die "Error: no datetime at row[ " . $row . "]";
+ }
+ pmiWrite(str2time($stamp, $zone), 0) >= 0
+ or die "pmiWrite: at row[ ". $row . "] ($stamp): " . pmiErrStr(-1) . "\n";
+}
+
+pmiEnd();
+
+exit(0);
+
+=pod
+
+=head1 NAME
+
+sheet2pcp - Import spreadsheet data and create a PCP archive
+
+=head1 SYNOPSIS
+
+B<sheet2pcp> [B<-h> I<host>] [B<-Z> I<timezone>] I<infile> I<mapfile> I<outfile>
+
+=head1 DESCRIPTION
+
+B<sheet2pcp> is intended to read a data spreadsheet (I<infile>)
+translate this into a Performance
+Co-Pilot (PCP) archive with the basename I<outfile>.
+
+The input spreadsheet can be in any of the common formats, provided
+the appropriate Perl modules have been installed (see the B<CAVEATS>
+section below). The spreadsheet must be E<quot>normalizedE<quot>
+so that each row contains data for the same time interval, and one
+of the columns contains the date and time for the data in each
+row.
+
+The resultant PCP archive may be used with all the PCP client tools
+to graph subsets of the data using B<pmchart>(1),
+perform data reduction and reporting, filter with
+the PCP inference engine B<pmie>(1), etc.
+
+The I<mapfile> controls the import process and defines the data
+mapping from the spreadsheet columns onto the PCP data model. The file
+is written in XML and conforms to the syntax defined in the
+B<MAPPING CONFIGURATION> section below.
+
+A series of physical files will be created with the prefix I<outfile>.
+These are I<outfile>B<.0> (the performance data),
+I<outfile>B<.meta> (the metadata that describes the performance data) and
+I<outfile>B<.index> (a temporal index to improve efficiency of replay
+operations for the archive). If any of these files exists already,
+then B<sheet2pcp> will B<not> overwrite them and will exit with an error
+message.
+
+The B<-h> option is an alternate to the
+B<hostname> attribute of the B<E<lt>sheetE<gt>> element in I<mapfile>
+described below. If both are specified, the value from I<mapfile> is
+used.
+
+The B<-Z> option is an alternate to the
+B<timezone> attribute of the B<E<lt>sheetE<gt>> element in I<mapfile>
+described below. If both are specified, the value from I<mapfile> is
+used.
+
+B<sheet2pcp> is a Perl script that uses the PCP::LogImport Perl wrapper
+around the PCP I<libpcp_import>
+library, and as such could be used as an example to develop new
+tools to import other types of performance data and create PCP archives.
+
+=head1 MAPPING CONFIGURATION
+
+The I<mapfile> contains specifications in standard XML format.
+
+The whole specification is wrapped in a B<E<lt>sheetE<gt>> ... B<E<lt>/sheetE<gt>>
+element.
+The B<sheet> tag supports the following optional attributes:
+
+=over 10
+
+=item B<heading>
+
+Specifies the number of
+heading rows to skip at the start of the spreadsheet before processing data.
+Example: heading="1".
+
+=item B<hostname>
+
+Set the source hostname in the PCP archive (the
+default is to use the hostname of the local host).
+Example: hostname="some.where.com".
+
+=item B<timezone>
+
+Set the source timezone in the PCP archive (the
+default is to use UTC). The timezone must have the
+format +HHMM (for hours and minutes East of UTC) or -HHMM (for hours
+and minutes West of UTC). Note in particular that B<neither> the B<zoneinfo>
+(aka Olson) format, e.g. Europe/Paris, nor the Posix B<TZ> format, e.g.
+EST+5 is allowed.
+Example: timezone="+1100".
+
+=item B<datefmt>
+
+The format of the date imported from the spreadsheet may be specified
+as a concatenation of
+values that specify the
+order of the year (B<Y>), month (B<M>) and day (B<D>) fields in a date.
+The supported variants are B<DMY> (the default),
+B<MDY> and B<YMD>.
+Example: datefmt="YMD".
+
+=back
+
+A B<E<lt>sheetE<gt>> element contains
+one or more metric specifications of
+the form B<E<lt>metricE<gt>>I<metricname>B<E<lt>/metricE<gt>>. The B<metric>
+tag supports the following optional attributes:
+
+=over 10
+
+=item B<pmid>
+
+The Performance Metrics Identifier (PMID), specified as 3 numbers
+separated by a periods (.) to
+set the B<domain>, B<cluster> and B<item> fields of the PMID, see B<PMNS>(4)
+for more details of PMIDs. If omitted, the PMID will be automatically
+assigned by B<pmiAddMetric>(3).
+The value B<PM_ID_NULL> may be used to explicitly nominate
+the default behaviour.
+Examples: pmid="60.0.2", pmid="PM_ID_NULL".
+
+=item B<indom>
+
+Each metric may have one or more values. If a metric B<always>
+has one value, it is singular and the Instance Domain should be set to
+B<PM_INDOM_NULL>.
+Otherwise B<indom> should be specified as 2 numbers separated by a period (.)
+to set the B<domain> and B<ordinal> fields of the Instance Domain, see
+the B<__pmInDom_int> typedef in I<E<lt>pcp/impl.hE<gt>>.
+Examples: indom="PM_INDOM_NULL", indom="60.3", indom="PMI_DOMAIN.4".
+
+More than
+one metric can share the same Instance Domain when the metrics have defined
+values over similar sets of instances, e.g. all the metrics for each network
+interface. It is standard practice for the B<domain> field to be the
+same for the B<pmid> and the B<indom>; if the B<pmid> attribute is missing,
+then the B<domain> field for the B<indom> should be the reserved domain
+B<PMI_DOMAIN>.
+
+If the B<indom> attribute is omitted then the default Instance Domain for
+the metric is B<PM_INDOM_NULL>.
+
+=item B<units>
+
+The scale and dimension of the metric values along the axes of space, time
+and count (events, messages, packets, etc.) is specified with a 6-tuple.
+These values are passed to the B<pmiUnits>(3) function to generate a
+I<pmUnits> structure. Refer to B<pmLookupDesc>(3) for a full description
+of all the fields of this structure.
+The default is to assign no scale or dimension to the metric, i.e. units="0,0,0,0,0,0".
+Examples: units="0,1,0,0,PM_TIME_MSEC,0" (milliseconds),
+units="1,-1,0,PM_SPACE_MBYTE,PM_TIME_SEC,0" (Mbytes/sec),
+units="0,1,-1,0,PM_TIME_USEC,PM_COUNT_ONE" (microseconds/event).
+
+=item B<type>
+
+Defines the data type for the metric.
+Refer to B<pmLookupDesc>(3) for a full description
+of the possible type values; the default is B<PM_TYPE_FLOAT>.
+Examples: type="PM_TYPE_32", type="PM_TYPE_U64", type="PM_TYPE_STRING".
+
+=item B<sem>
+
+Defines the semantics of the metric.
+Refer to B<pmLookupDesc>(3) for a full description
+of the possible values; the default is B<PM_SEM_INSTANT>.
+Examples: sem="PM_SEM_COUNTER", type="PM_SEM_DISCRETE".
+
+=back
+
+The remaining specifications define the data columns B<in order> using
+B<exactly> one B<E<lt>datetimeE<gt>>B<E<lt>/datetimeE<gt>> element,
+one or more B<E<lt>dataE<gt>>I<metricspec>B<E<lt>/dataE<gt>> elements
+and
+one or more B<E<lt>skipE<gt>>B<E<lt>/skipE<gt>> elements.
+
+The B<E<lt>datetimeE<gt>> element defines the column in which a date and time will
+be found to form the timestamp in the PCP archive for all the data in
+each row of the PCP archive.
+
+For the B<E<lt>dataE<gt>> element,
+a I<metricspec>
+consists of a metric name (as defined in an earlier B<E<lt>metricE<gt>>
+element), optionally followed by an instance name that is enclosed by square brackets,
+e.g. <data>hinv.ncpu</data>, <data>kernel.all.load[1 minute]</data>.
+
+The B<skip> tag defines the column that should be skipped when preparing
+data for the PCP archive.
+
+The order of the B<E<lt>datetimeE<gt>>, B<E<lt>dataE<gt>> and
+B<E<lt>skipE<gt>> elements matches the order of columns in the
+spreadsheet. If the number of elements is not the same as the number
+of columns a warning is issued, and the extra elements or columns
+generate no metric values in the output archive.
+
+=head2 EXAMPLE
+
+The I<mapfile> ...
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <sheet heading="1">
+ <!-- simple example -->
+ <metric pmid="60.0.2" indom="60.0" units="0,1,0,0,PM_TIME_MSEC,0"
+ type="PM_TYPE_U64" sem="PM_SEM_COUNTER">
+ kernel.percpu.cpu.sys</metric>
+ <datetime></datetime>
+ <skip></skip>
+ <data>kernel.percpu.cpu.sys[cpu0]</data>
+ <data>kernel.percpu.cpu.sys[cpu1]</data>
+ </sheet>
+
+could be used for a spreadsheet in which the first few rows are ...
+
+ Date;"Status";"SysTime - 0";"SysTime - 1";
+ 26/01/2001 14:05:22;"Some Busy";0.750;0.133
+ 26/01/2001 14:05:37;"OK";0.150;0.273
+ 26/01/2001 14:05:52;"All Busy";0.733;0.653
+
+=head1 CAVEATS
+
+Only the first sheet from I<infile> will be processed.
+
+Additional Perl modules must be installed for the various spreadsheet formats,
+although these are checked for ar run-time so only the modules required for
+the specific types of spreadsheets you wish to process need be installed:
+
+=over 6
+
+=item B<*.csv>
+
+Spreadsheets in the Comma Separated Values (CSV) format require B<Text::CSV_XS>(3pm).
+
+=item B<*.sxc> or B<*.ods>
+
+OpenOffice documents require B<Spreadsheet::ReadSXC>(3pm), which in turn
+requires B<Archive::Zip>(3pm).
+
+=item B<*.xls>
+
+Classical Microsoft Office documents require B<Spreadsheet::ParseExcel>(3pm),
+which in turn requires B<OLE::Storage_Lite>(3pm).
+
+=item B<*.xlsx>
+
+Microsoft OpenXML documents require B<Spreadsheet::XLSX>(3pm). B<sheet2pcp>
+does not appear to work with OpenXML documents saved from OpenOffice.
+
+=back
+
+=head1 SEE ALSO
+
+B<Archive::Zip>(3pm),
+B<Date::Format>(3pm),
+B<Date::Parse>(3pm),
+B<LOGIMPORT>(3),
+B<OLE::Storage_Lite>(3pm),
+B<PCP::LogImport>(3pm),
+B<pmchart>(1),
+B<pmiAddMetric>(3),
+B<pmie>(1),
+B<pmiUnits>(3),
+B<pmlogger>(1),
+B<pmLookupDesc>(3),
+B<PMNS>(4),
+B<Spreadsheet::ParseExcel>(3pm),
+B<Spreadsheet::ReadSXC>(3pm),
+B<Spreadsheet::XLSX>(3pm),
+B<Text::CSV_XS>(3pm)
+and
+B<XML::TokeParser>(3pm).
diff --git a/src/telnet-probe/GNUmakefile b/src/telnet-probe/GNUmakefile
new file mode 100644
index 0000000..05ab71d
--- /dev/null
+++ b/src/telnet-probe/GNUmakefile
@@ -0,0 +1,36 @@
+#!gmake
+#
+# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = telnet-probe.c
+CMDTARGET = telnet-probe
+LLDLIBS = -lpcp
+
+default: $(CMDTARGET)
+
+include $(BUILDRULES)
+
+install: $(CMDTARGET)
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BINADM_DIR)/$(CMDTARGET)
+
+install_pcp : install
+
+default_pcp : default
diff --git a/src/telnet-probe/telnet-probe.c b/src/telnet-probe/telnet-probe.c
new file mode 100644
index 0000000..3754a2d
--- /dev/null
+++ b/src/telnet-probe/telnet-probe.c
@@ -0,0 +1,184 @@
+/*
+ * Lightweight telnet clone for shping and espping PMDAs.
+ *
+ * Usage: telnet-probe [-v] host port
+ *
+ * Once telnet connection is established:
+ * read stdin until EOF, writing to telnet connection
+ * read telnet until EOF, discarding data
+ * -c (connect only) flag skips the send-receive processing
+ *
+ * Exit status is 1 in the case of any errors, else 0.
+ */
+
+#include "pmapi.h"
+#include "impl.h"
+
+int
+main(int argc, char *argv[])
+{
+ __pmFdSet wfds;
+ __pmSockAddr *myAddr;
+ __pmHostEnt *servInfo;
+ void *enumIx;
+ int flags = 0;
+ char *endnum;
+ int port = 0;
+ int s;
+ int errflag = 0;
+ int cflag = 0;
+ int vflag = 0;
+ int sts = 1;
+ ssize_t bytes;
+ int ret;
+ struct timeval canwait = { 5, 000000 };
+ struct timeval stv;
+ struct timeval *pstv;
+ int c;
+
+ while ((c = getopt(argc, argv, "cv?")) != EOF) {
+ switch (c) {
+ case 'c':
+ cflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case '?':
+ default:
+ errflag++;
+ break;
+ }
+ }
+
+ if (optind+2 != argc) {
+ fprintf(stderr, "%s: requires two arguments\n", argv[0]);
+ errflag++;
+ }
+ else {
+ port = (int)strtol(argv[optind+1], &endnum, 10);
+ if (*endnum != '\0' || port < 0) {
+ fprintf(stderr, "%s: port must be a positive number\n", argv[0]);
+ errflag++;
+ }
+ }
+ if (errflag) {
+ fprintf(stderr, "Usage: %s [-c] [-v] host port\n", argv[0]);
+ goto done;
+ }
+
+ if ((servInfo = __pmGetAddrInfo(argv[optind])) == NULL) {
+ if (vflag)
+ fprintf(stderr, "__pmGetAddrInfo: %s\n", hoststrerror());
+ goto done;
+ }
+
+ s = -1;
+ enumIx = NULL;
+ for (myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx);
+ myAddr != NULL;
+ myAddr = __pmHostEntGetSockAddr(servInfo, &enumIx)) {
+ /* Create a socket */
+ if (__pmSockAddrIsInet(myAddr))
+ s = __pmCreateSocket();
+ else if (__pmSockAddrIsIPv6(myAddr))
+ s = __pmCreateIPv6Socket();
+ else
+ continue;
+ if (s < 0) {
+ __pmSockAddrFree(myAddr);
+ continue; /* Try the next address */
+ }
+
+ /* Attempt to connect */
+ flags = __pmConnectTo(s, myAddr, port);
+ __pmSockAddrFree(myAddr);
+
+ if (flags < 0) {
+ /*
+ * Mark failure in case we fall out the end of the loop
+ * and try next address. s has been closed in __pmConnectTo().
+ */
+ setoserror(ECONNREFUSED);
+ s = -1;
+ continue;
+ }
+
+ /* FNDELAY and we're in progress - wait on select */
+ stv = canwait;
+ pstv = (stv.tv_sec || stv.tv_usec) ? &stv : NULL;
+ __pmFD_ZERO(&wfds);
+ __pmFD_SET(s, &wfds);
+ ret = __pmSelectWrite(s+1, &wfds, pstv);
+
+ /* Was the connection successful? */
+ if (ret == 0)
+ setoserror(ETIMEDOUT);
+ else if (ret > 0) {
+ ret = __pmConnectCheckError(s);
+ if (ret == 0)
+ break;
+ setoserror(ret);
+ }
+
+ /* Unsuccessful connection. */
+ __pmCloseSocket(s);
+ s = -1;
+ } /* loop over addresses */
+
+ __pmHostEntFree(servInfo);
+
+ if (s != -1)
+ s = __pmConnectRestoreFlags(s, flags);
+ if (s < 0) {
+ if (vflag)
+ fprintf(stderr, "connect: %s\n", netstrerror());
+ goto done;
+ }
+
+ if (cflag) {
+ /* skip send-recv exercise */
+ sts = 0;
+ goto done;
+ }
+
+ if (vflag)
+ fprintf(stderr, "send ...\n");
+ while ((c = getc(stdin)) != EOF) {
+ if (vflag) {
+ fputc(c, stderr);
+ fflush(stderr);
+ }
+ if (__pmWrite(s, &c, sizeof(c)) != sizeof(c)) {
+ if (vflag)
+ fprintf(stderr, "telnet write: %s\n", osstrerror());
+ goto done;
+ }
+ }
+
+ if (vflag)
+ fprintf(stderr, "recv ...\n");
+ while ((bytes = __pmRead(s, &c, sizeof(c))) == sizeof(c)) {
+ if (vflag) {
+ fputc(c, stderr);
+ fflush(stderr);
+ }
+ }
+ if (bytes < 0) {
+ /*
+ * If __pmSocketClosed(), then treat it as EOF.
+ */
+ if (! __pmSocketClosed()) {
+ if (vflag)
+ fprintf(stderr, "telnet read: %s\n", osstrerror());
+ goto done;
+ }
+ }
+
+ sts = 0;
+
+done:
+ if (vflag)
+ fprintf(stderr, "exit: %d\n", sts);
+ exit(sts);
+}
diff --git a/src/win32ctl/GNUmakefile b/src/win32ctl/GNUmakefile
new file mode 100644
index 0000000..6ae7223
--- /dev/null
+++ b/src/win32ctl/GNUmakefile
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SUBDIRS = include lib \
+ eventlog services setevent
+BATCHES = pcp.bat pmafm.bat mkaf.bat pmsignal.bat
+LSRCFILES = $(BATCHES)
+
+default :: default_pcp
+
+default_pcp : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+
+install :: default_pcp install_pcp
+
+install_pcp : $(SUBDIRS)
+ $(SUBDIRS_MAKERULE)
+ifeq "$(TARGET_OS)" "mingw"
+ $(INSTALL) -m 755 $(BATCHES) $(PCP_BIN_DIR)
+endif
+
+include $(BUILDRULES)
diff --git a/src/win32ctl/eventlog/GNUmakefile b/src/win32ctl/eventlog/GNUmakefile
new file mode 100644
index 0000000..cab11e1
--- /dev/null
+++ b/src/win32ctl/eventlog/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pcp-eventlog.c
+CMDTARGET = pcp-eventlog.exe
+LLDLIBS = $(PCPLIB)
+LSRCFILES = $(WRAPPERS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGET)
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/win32ctl/eventlog/pcp-eventlog.c b/src/win32ctl/eventlog/pcp-eventlog.c
new file mode 100644
index 0000000..e8f8fe3
--- /dev/null
+++ b/src/win32ctl/eventlog/pcp-eventlog.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2008 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include <string.h>
+#include <winbase.h>
+
+int
+posix2win32(char *pri)
+{
+ if (strcmp(pri, "alert") == 0 || strcmp(pri, "warning") == 0 ||
+ strcmp(pri, "warn") == 0)
+ return EVENTLOG_WARNING_TYPE;
+ if (strcmp(pri, "crit") == 0 || strcmp(pri, "emerg") == 0 ||
+ strcmp(pri, "err") == 0 || strcmp(pri, "error") == 0 ||
+ strcmp(pri, "panic") == 0)
+ return EVENTLOG_ERROR_TYPE;
+ if (strcmp(pri, "info") == 0 || strcmp(pri, "notice") == 0 ||
+ strcmp(pri, "debug") == 0)
+ return EVENTLOG_INFORMATION_TYPE;
+ return -1;
+}
+
+void
+append(char *buffer, int bsize, char *string)
+{
+ static int spaced; /* first argument needs no whitespace-prefix */
+ static int offset;
+
+ if (spaced)
+ offset += snprintf(buffer + offset, bsize - offset, " %s", string);
+ else {
+ offset += snprintf(buffer + offset, bsize - offset, "%s", string);
+ spaced = 1; /* remainder will all be space-prefixed */
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ HANDLE sink;
+ LPCSTR msgptr;
+ char buffer[256];
+ char msg[32*1024];
+ char *pri = NULL;
+ char *tag = NULL;
+ int error = 0;
+ int iflag = 0;
+ int sflag = 0;
+ int priority;
+ int c;
+
+ __pmSetProgname(argv[0]);
+
+ while ((c = getopt(argc, argv, "ip:st:?")) != EOF) {
+ switch (c) {
+ case 'i': /* process ID */
+ iflag = 1;
+ break;
+ case 'p': /* pri (facility.level) */
+ pri = optarg;
+ break;
+ case 's': /* stderr too */
+ sflag = 1;
+ break;
+ case 't': /* tag (prefix) */
+ tag = optarg;
+ break;
+ default:
+ error++;
+ }
+ }
+
+ if (error) {
+ fprintf(stderr, "Usage: %s [ options ] message\n\n"
+ "Options:\n"
+ " -i log process identifier with each line\n"
+ " -s log message to standard error as well\n"
+ " -p pri enter message with specified priority\n"
+ " -t tag mark the line with the specified tag\n",
+ pmProgname);
+ return 2;
+ }
+
+ /*
+ * Parse priority. Discard facility, pick out valid level names.
+ */
+ if (!pri)
+ priority = EVENTLOG_INFORMATION_TYPE; /* default event type */
+ else {
+ char *p = strrchr(pri, '.');
+ if (p)
+ pri = p;
+ priority = posix2win32(pri);
+ if (!priority)
+ priority = EVENTLOG_INFORMATION_TYPE; /* default event type */
+ }
+
+ /*
+ * Construct the message from all contributing components.
+ */
+ if (iflag) {
+ snprintf(buffer, sizeof(buffer), "[%" FMT_PID "]", getpid());
+ append(msg, sizeof(msg), buffer);
+ }
+ if (tag) {
+ snprintf(buffer, sizeof(buffer), "%s:", tag);
+ append(msg, sizeof(msg), buffer);
+ }
+ for (c = optind; c < argc; c++) /* insert the remaining text */
+ append(msg, sizeof(msg), argv[c]);
+
+ /*
+ * Optionally write to the standard error stream (as well).
+ */
+ if (sflag) {
+ fputs(msg, stderr);
+ fputc('\n', stderr);
+ }
+
+ sink = RegisterEventSource(NULL, "Application");
+ if (!sink) {
+ fprintf(stderr, "%s: RegisterEventSource failed (%ld)\n",
+ pmProgname, GetLastError());
+ return 1;
+ }
+ msgptr = msg;
+ if (!ReportEvent(sink, priority, 0, 0, NULL, 1, 0, &msgptr, NULL))
+ fprintf(stderr, "%s: ReportEvent failed (%ld)\n",
+ pmProgname, GetLastError());
+ DeregisterEventSource(sink);
+ return 0;
+}
diff --git a/src/win32ctl/include/GNUmakefile b/src/win32ctl/include/GNUmakefile
new file mode 100644
index 0000000..ce72f8e
--- /dev/null
+++ b/src/win32ctl/include/GNUmakefile
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+HFILES = _mingw_unicode.h pshpack8.h \
+ evntcons.h evntprov.h evntrace.h \
+ pdh.h pdhmsg.h \
+ tdh.h tdhmsg.h \
+ winevt.h winmeta.h winperf.h wmistr.h
+
+default :: default_pcp
+
+default_pcp :
+
+include $(BUILDRULES)
+
+install :: default_pcp install_pcp
+
+install_pcp : default_pcp
diff --git a/src/win32ctl/include/_mingw_unicode.h b/src/win32ctl/include/_mingw_unicode.h
new file mode 100644
index 0000000..38334bc
--- /dev/null
+++ b/src/win32ctl/include/_mingw_unicode.h
@@ -0,0 +1,33 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+
+#if !defined(_INC_CRT_UNICODE_MACROS)
+/* _INC_CRT_UNICODE_MACROS defined based on UNICODE flag */
+
+#if defined(UNICODE)
+# define _INC_CRT_UNICODE_MACROS 1
+# define __MINGW_NAME_AW(func) func##W
+# define __MINGW_NAME_AW_EXT(func,ext) func##W##ext
+# define __MINGW_NAME_UAW(func) func##_W
+# define __MINGW_NAME_UAW_EXT(func,ext) func##_W_##ext
+# define __MINGW_STRING_AW(str) L##str /* same as TEXT() from winnt.h */
+# define __MINGW_PROCNAMEEXT_AW "W"
+#else
+# define _INC_CRT_UNICODE_MACROS 2
+# define __MINGW_NAME_AW(func) func##A
+# define __MINGW_NAME_AW_EXT(func,ext) func##A##ext
+# define __MINGW_NAME_UAW(func) func##_A
+# define __MINGW_NAME_UAW_EXT(func,ext) func##_A_##ext
+# define __MINGW_STRING_AW(str) str /* same as TEXT() from winnt.h */
+# define __MINGW_PROCNAMEEXT_AW "A"
+#endif
+
+#define __MINGW_TYPEDEF_AW(type) \
+ typedef __MINGW_NAME_AW(type) type;
+#define __MINGW_TYPEDEF_UAW(type) \
+ typedef __MINGW_NAME_UAW(type) type;
+
+#endif /* !defined(_INC_CRT_UNICODE_MACROS) */
diff --git a/src/win32ctl/include/evntcons.h b/src/win32ctl/include/evntcons.h
new file mode 100644
index 0000000..8c15a20
--- /dev/null
+++ b/src/win32ctl/include/evntcons.h
@@ -0,0 +1,158 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _EVNTCONS_H_
+#define _EVNTCONS_H_
+
+/* --- start added by kenj */
+#undef __MINGW_EXTENSION
+#if defined(__GNUC__) || defined(__GNUG__)
+#define __MINGW_EXTENSION __extension__
+#else
+#define __MINGW_EXTENSION
+#endif
+/* --- end added by kenj */
+
+#include <wmistr.h>
+#include <evntrace.h>
+#include <evntprov.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum EVENTSECURITYOPERATION {
+ EventSecuritySetDACL,
+ EventSecuritySetSACL,
+ EventSecurityAddDACL,
+ EventSecurityAddSACL,
+ EventSecurityMax
+} EVENTSECURITYOPERATION;
+
+typedef struct _EVENT_EXTENDED_ITEM_INSTANCE {
+ ULONG InstanceId;
+ ULONG ParentInstanceId;
+ GUID ParentGuid;
+} EVENT_EXTENDED_ITEM_INSTANCE, *PEVENT_EXTENDED_ITEM_INSTANCE;
+
+typedef struct _EVENT_EXTENDED_ITEM_TS_ID {
+ ULONG SessionId;
+} EVENT_EXTENDED_ITEM_TS_ID, *PEVENT_EXTENDED_ITEM_TS_ID;
+
+typedef struct _EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID {
+ GUID RelatedActivityId;
+} EVENT_EXTENDED_ITEM_RELATED_ACTIVITYID, *PEVENT_EXTENDED_ITEM_RELATED_ACTIVITYID;
+
+typedef struct _EVENT_HEADER_EXTENDED_DATA_ITEM {
+ USHORT Reserved1;
+ USHORT ExtType;
+ __MINGW_EXTENSION struct {
+ USHORT Linkage : 1;
+ USHORT Reserved2 :15;
+ } DUMMYSTRUCTNAME;
+ USHORT DataSize;
+ ULONGLONG DataPtr;
+} EVENT_HEADER_EXTENDED_DATA_ITEM, *PEVENT_HEADER_EXTENDED_DATA_ITEM;
+
+typedef struct _EVENT_HEADER {
+ USHORT Size;
+ USHORT HeaderType;
+ USHORT Flags;
+ USHORT EventProperty;
+ ULONG ThreadId;
+ ULONG ProcessId;
+ LARGE_INTEGER TimeStamp;
+ GUID ProviderId;
+ EVENT_DESCRIPTOR EventDescriptor;
+ __MINGW_EXTENSION union {
+ __MINGW_EXTENSION struct {
+ ULONG KernelTime;
+ ULONG UserTime;
+ } DUMMYSTRUCTNAME;
+ ULONG64 ProcessorTime;
+ } DUMMYUNIONNAME;
+ GUID ActivityId;
+} EVENT_HEADER, *PEVENT_HEADER;
+
+#define EVENT_HEADER_PROPERTY_XML 0x0001
+#define EVENT_HEADER_PROPERTY_FORWARDED_XML 0x0002
+#define EVENT_HEADER_PROPERTY_LEGACY_EVENTLOG 0x0004
+
+#define EVENT_HEADER_FLAG_EXTENDED_INFO 0x0001
+#define EVENT_HEADER_FLAG_PRIVATE_SESSION 0x0002
+#define EVENT_HEADER_FLAG_STRING_ONLY 0x0004
+#define EVENT_HEADER_FLAG_TRACE_MESSAGE 0x0008
+#define EVENT_HEADER_FLAG_NO_CPUTIME 0x0010
+#define EVENT_HEADER_FLAG_32_BIT_HEADER 0x0020
+#define EVENT_HEADER_FLAG_64_BIT_HEADER 0x0040
+#define EVENT_HEADER_FLAG_CLASSIC_HEADER 0x0100
+
+#define EVENT_HEADER_EXT_TYPE_RELATED_ACTIVITYID 0x0001
+#define EVENT_HEADER_EXT_TYPE_SID 0x0002
+#define EVENT_HEADER_EXT_TYPE_TS_ID 0x0003
+#define EVENT_HEADER_EXT_TYPE_INSTANCE_INFO 0x0004
+#define EVENT_HEADER_EXT_TYPE_STACK_TRACE32 0x0005
+#define EVENT_HEADER_EXT_TYPE_STACK_TRACE64 0x0006
+
+struct _EVENT_RECORD {
+ EVENT_HEADER EventHeader;
+ ETW_BUFFER_CONTEXT BufferContext;
+ USHORT ExtendedDataCount;
+ USHORT UserDataLength;
+ PEVENT_HEADER_EXTENDED_DATA_ITEM ExtendedData;
+ PVOID UserData;
+ PVOID UserContext;
+};
+#ifndef DEFINED_PEVENT_RECORD
+typedef struct _EVENT_RECORD EVENT_RECORD, *PEVENT_RECORD;
+#define DEFINED_PEVENT_RECORD 1
+#endif /* for evntrace.h */
+
+#if (_WIN32_WINNT >= 0x0601)
+typedef struct _EVENT_EXTENDED_ITEM_STACK_TRACE32 {
+ ULONG64 MatchId;
+ ULONG Address[ANYSIZE_ARRAY];
+} EVENT_EXTENDED_ITEM_STACK_TRACE32, *PEVENT_EXTENDED_ITEM_STACK_TRACE32;
+
+typedef struct _EVENT_EXTENDED_ITEM_STACK_TRACE64 {
+ ULONG64 MatchId;
+ ULONG64 Address[ANYSIZE_ARRAY];
+} EVENT_EXTENDED_ITEM_STACK_TRACE64, *PEVENT_EXTENDED_ITEM_STACK_TRACE64;
+#endif /*(_WIN32_WINNT >= 0x0601)*/
+
+#define EVENT_ENABLE_PROPERTY_SID 0x00000001
+#define EVENT_ENABLE_PROPERTY_TS_ID 0x00000002
+#define EVENT_ENABLE_PROPERTY_STACK_TRACE 0x00000004
+
+#define PROCESS_TRACE_MODE_REAL_TIME 0x00000100
+#define PROCESS_TRACE_MODE_RAW_TIMESTAMP 0x00001000
+#define PROCESS_TRACE_MODE_EVENT_RECORD 0x10000000
+
+#if (_WIN32_WINNT >= 0x0600)
+ULONG EVNTAPI EventAccessControl(
+ LPGUID Guid,
+ ULONG Operation,
+ PSID Sid,
+ ULONG Rights,
+ BOOLEAN AllowOrDeny
+);
+
+ULONG EVNTAPI EventAccessQuery(
+ LPGUID Guid,
+ PSECURITY_DESCRIPTOR Buffer,
+ PULONG BufferSize
+);
+
+ULONG EVNTAPI EventAccessRemove(
+ LPGUID Guid
+);
+#endif /*(_WIN32_WINNT >= 0x0600)*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _EVNTCONS_H_ */
+
diff --git a/src/win32ctl/include/evntprov.h b/src/win32ctl/include/evntprov.h
new file mode 100644
index 0000000..c0dc243
--- /dev/null
+++ b/src/win32ctl/include/evntprov.h
@@ -0,0 +1,354 @@
+/*
+ * evntprov.h
+ *
+ * This file is part of the ReactOS PSDK package.
+ *
+ * Contributors:
+ * Created by Amine Khaldi.
+ *
+ * THIS SOFTWARE IS NOT COPYRIGHTED
+ *
+ * This source code is offered for use in the public domain. You may
+ * use, modify or distribute it freely.
+ *
+ * This code is distributed in the hope that it will be useful but
+ * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
+ * DISCLAIMED. This includes but is not limited to warranties of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef _EVNTPROV_H_
+#define _EVNTPROV_H_
+
+#ifndef EVNTAPI
+#ifndef MIDL_PASS
+#ifdef _EVNT_SOURCE_
+#define EVNTAPI __stdcall
+#else
+#define EVNTAPI DECLSPEC_IMPORT __stdcall
+#endif /* _EVNT_SOURCE_ */
+#endif /* MIDL_PASS */
+#endif /* EVNTAPI */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EVENT_MIN_LEVEL 0
+#define EVENT_MAX_LEVEL 0xff
+
+#define EVENT_ACTIVITY_CTRL_GET_ID 1
+#define EVENT_ACTIVITY_CTRL_SET_ID 2
+#define EVENT_ACTIVITY_CTRL_CREATE_ID 3
+#define EVENT_ACTIVITY_CTRL_GET_SET_ID 4
+#define EVENT_ACTIVITY_CTRL_CREATE_SET_ID 5
+
+typedef ULONGLONG REGHANDLE, *PREGHANDLE;
+
+#define MAX_EVENT_DATA_DESCRIPTORS 128
+#define MAX_EVENT_FILTER_DATA_SIZE 1024
+
+#define EVENT_FILTER_TYPE_SCHEMATIZED 0x80000000
+
+typedef struct _EVENT_DESCRIPTOR {
+ USHORT Id;
+ UCHAR Version;
+ UCHAR Channel;
+ UCHAR Level;
+ UCHAR Opcode;
+ USHORT Task;
+ ULONGLONG Keyword;
+} EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR;
+typedef const EVENT_DESCRIPTOR *PCEVENT_DESCRIPTOR;
+
+typedef struct _EVENT_DATA_DESCRIPTOR {
+ ULONGLONG Ptr;
+ ULONG Size;
+ ULONG Reserved;
+} EVENT_DATA_DESCRIPTOR, *PEVENT_DATA_DESCRIPTOR;
+
+struct _EVENT_FILTER_DESCRIPTOR {
+ ULONGLONG Ptr;
+ ULONG Size;
+ ULONG Type;
+};
+#ifndef DEFINED_PEVENT_FILTER_DESC
+typedef struct _EVENT_FILTER_DESCRIPTOR EVENT_FILTER_DESCRIPTOR, *PEVENT_FILTER_DESCRIPTOR;
+#define DEFINED_PEVENT_FILTER_DESC 1
+#endif /* for evntrace.h */
+
+typedef struct _EVENT_FILTER_HEADER {
+ USHORT Id;
+ UCHAR Version;
+ UCHAR Reserved[5];
+ ULONGLONG InstanceId;
+ ULONG Size;
+ ULONG NextOffset;
+} EVENT_FILTER_HEADER, *PEVENT_FILTER_HEADER;
+
+
+#ifndef _ETW_KM_ /* for wdm.h */
+
+typedef VOID
+(NTAPI *PENABLECALLBACK)(
+ LPCGUID SourceId,
+ ULONG IsEnabled,
+ UCHAR Level,
+ ULONGLONG MatchAnyKeyword,
+ ULONGLONG MatchAllKeyword,
+ PEVENT_FILTER_DESCRIPTOR FilterData,
+ PVOID CallbackContext);
+
+#if (_WIN32_WINNT >= 0x0600)
+ULONG EVNTAPI EventRegister(
+ LPCGUID ProviderId,
+ PENABLECALLBACK EnableCallback,
+ PVOID CallbackContext,
+ PREGHANDLE RegHandle
+);
+
+ULONG EVNTAPI EventUnregister(
+ REGHANDLE RegHandle
+);
+
+BOOLEAN EVNTAPI EventEnabled(
+ REGHANDLE RegHandle,
+ PCEVENT_DESCRIPTOR EventDescriptor
+);
+
+BOOLEAN EVNTAPI EventProviderEnabled(
+ REGHANDLE RegHandle,
+ UCHAR Level,
+ ULONGLONG Keyword
+);
+
+ULONG EVNTAPI EventWrite(
+ REGHANDLE RegHandle,
+ PCEVENT_DESCRIPTOR EventDescriptor,
+ ULONG UserDataCount,
+ PEVENT_DATA_DESCRIPTOR UserData
+);
+
+ULONG EVNTAPI EventWriteTransfer(
+ REGHANDLE RegHandle,
+ PCEVENT_DESCRIPTOR EventDescriptor,
+ LPCGUID ActivityId,
+ LPCGUID RelatedActivityId,
+ ULONG UserDataCount,
+ PEVENT_DATA_DESCRIPTOR UserData
+);
+
+ULONG EVNTAPI EventWriteString(
+ REGHANDLE RegHandle,
+ UCHAR Level,
+ ULONGLONG Keyword,
+ PCWSTR String
+);
+
+ULONG EVNTAPI EventActivityIdControl(
+ ULONG ControlCode,
+ LPGUID ActivityId
+);
+
+#endif /*(_WIN32_WINNT >= 0x0600)*/
+
+#if (_WIN32_WINNT >= 0x0601)
+ULONG EVNTAPI EventWriteEx(
+ REGHANDLE RegHandle,
+ PCEVENT_DESCRIPTOR EventDescriptor,
+ ULONG64 Filter,
+ ULONG Flags,
+ LPCGUID ActivityId,
+ LPCGUID RelatedActivityId,
+ ULONG UserDataCount,
+ PEVENT_DATA_DESCRIPTOR UserData
+);
+#endif /*(_WIN32_WINNT >= 0x0601)*/
+
+#endif /* _ETW_KM_ */
+
+FORCEINLINE
+VOID
+EventDataDescCreate(
+ PEVENT_DATA_DESCRIPTOR EventDataDescriptor,
+ const VOID* DataPtr,
+ ULONG DataSize)
+{
+ EventDataDescriptor->Ptr = (ULONGLONG)(ULONG_PTR)DataPtr;
+ EventDataDescriptor->Size = DataSize;
+ EventDataDescriptor->Reserved = 0;
+}
+
+FORCEINLINE
+VOID
+EventDescCreate(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ USHORT Id,
+ UCHAR Version,
+ UCHAR Channel,
+ UCHAR Level,
+ USHORT Task,
+ UCHAR Opcode,
+ ULONGLONG Keyword)
+{
+ EventDescriptor->Id = Id;
+ EventDescriptor->Version = Version;
+ EventDescriptor->Channel = Channel;
+ EventDescriptor->Level = Level;
+ EventDescriptor->Task = Task;
+ EventDescriptor->Opcode = Opcode;
+ EventDescriptor->Keyword = Keyword;
+}
+
+FORCEINLINE
+VOID
+EventDescZero(
+ PEVENT_DESCRIPTOR EventDescriptor)
+{
+ memset(EventDescriptor, 0, sizeof(EVENT_DESCRIPTOR));
+}
+
+FORCEINLINE
+USHORT
+EventDescGetId(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Id);
+}
+
+FORCEINLINE
+UCHAR
+EventDescGetVersion(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Version);
+}
+
+FORCEINLINE
+USHORT
+EventDescGetTask(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Task);
+}
+
+FORCEINLINE
+UCHAR
+EventDescGetOpcode(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Opcode);
+}
+
+FORCEINLINE
+UCHAR
+EventDescGetChannel(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Channel);
+}
+
+FORCEINLINE
+UCHAR
+EventDescGetLevel(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Level);
+}
+
+FORCEINLINE
+ULONGLONG
+EventDescGetKeyword(
+ PCEVENT_DESCRIPTOR EventDescriptor)
+{
+ return (EventDescriptor->Keyword);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetId(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ USHORT Id)
+{
+ EventDescriptor->Id = Id;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetVersion(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ UCHAR Version)
+{
+ EventDescriptor->Version = Version;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetTask(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ USHORT Task)
+{
+ EventDescriptor->Task = Task;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetOpcode(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ UCHAR Opcode)
+{
+ EventDescriptor->Opcode = Opcode;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetLevel(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ UCHAR Level)
+{
+ EventDescriptor->Level = Level;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetChannel(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ UCHAR Channel)
+{
+ EventDescriptor->Channel = Channel;
+ return (EventDescriptor);
+}
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescSetKeyword(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ ULONGLONG Keyword)
+{
+ EventDescriptor->Keyword = Keyword;
+ return (EventDescriptor);
+}
+
+
+FORCEINLINE
+PEVENT_DESCRIPTOR
+EventDescOrKeyword(
+ PEVENT_DESCRIPTOR EventDescriptor,
+ ULONGLONG Keyword)
+{
+ EventDescriptor->Keyword |= Keyword;
+ return (EventDescriptor);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _EVNTPROV_H_ */
+
diff --git a/src/win32ctl/include/evntrace.h b/src/win32ctl/include/evntrace.h
new file mode 100644
index 0000000..6e9e2a0
--- /dev/null
+++ b/src/win32ctl/include/evntrace.h
@@ -0,0 +1,841 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _EVNTRACE_
+#define _EVNTRACE_
+
+/* --- start added by kenj */
+#undef __MINGW_EXTENSION
+#if defined(__GNUC__) || defined(__GNUG__)
+#define __MINGW_EXTENSION __extension__
+#else
+#define __MINGW_EXTENSION
+#endif
+/* --- end added by kenj */
+
+#if defined(_WINNT_) || defined(WINNT)
+
+#ifndef WMIAPI
+#ifndef MIDL_PASS
+#ifdef _WMI_SOURCE_
+#define WMIAPI __stdcall
+#else
+#define WMIAPI DECLSPEC_IMPORT __stdcall
+#endif
+#endif /* MIDL_PASS */
+#endif /* WMIAPI */
+
+DEFINE_GUID (EventTraceGuid,0x68fdd900,0x4a3e,0x11d1,0x84,0xf4,0x00,0x00,0xf8,0x04,0x64,0xe3);
+DEFINE_GUID (SystemTraceControlGuid,0x9e814aad,0x3204,0x11d2,0x9a,0x82,0x00,0x60,0x08,0xa8,0x69,0x39);
+DEFINE_GUID (EventTraceConfigGuid,0x01853a65,0x418f,0x4f36,0xae,0xfc,0xdc,0x0f,0x1d,0x2f,0xd2,0x35);
+DEFINE_GUID (DefaultTraceSecurityGuid,0x0811c1af,0x7a07,0x4a06,0x82,0xed,0x86,0x94,0x55,0xcd,0xf7,0x13);
+
+#define KERNEL_LOGGER_NAMEW L"NT Kernel Logger"
+#define GLOBAL_LOGGER_NAMEW L"GlobalLogger"
+#define EVENT_LOGGER_NAMEW L"Event Log"
+#define DIAG_LOGGER_NAMEW L"DiagLog"
+
+#define KERNEL_LOGGER_NAMEA "NT Kernel Logger"
+#define GLOBAL_LOGGER_NAMEA "GlobalLogger"
+#define EVENT_LOGGER_NAMEA "Event Log"
+#define DIAG_LOGGER_NAMEA "DiagLog"
+
+#define MAX_MOF_FIELDS 16
+
+#ifndef _TRACEHANDLE_DEFINED
+#define _TRACEHANDLE_DEFINED
+typedef ULONG64 TRACEHANDLE,*PTRACEHANDLE;
+#endif
+
+#define SYSTEM_EVENT_TYPE 1
+
+#define EVENT_TRACE_TYPE_INFO 0x00
+#define EVENT_TRACE_TYPE_START 0x01
+#define EVENT_TRACE_TYPE_END 0x02
+#define EVENT_TRACE_TYPE_STOP 0x02
+#define EVENT_TRACE_TYPE_DC_START 0x03
+#define EVENT_TRACE_TYPE_DC_END 0x04
+#define EVENT_TRACE_TYPE_EXTENSION 0x05
+#define EVENT_TRACE_TYPE_REPLY 0x06
+#define EVENT_TRACE_TYPE_DEQUEUE 0x07
+#define EVENT_TRACE_TYPE_RESUME 0x07
+#define EVENT_TRACE_TYPE_CHECKPOINT 0x08
+#define EVENT_TRACE_TYPE_SUSPEND 0x08
+#define EVENT_TRACE_TYPE_WINEVT_SEND 0x09
+#define EVENT_TRACE_TYPE_WINEVT_RECEIVE 0XF0
+
+#define TRACE_LEVEL_NONE 0
+#define TRACE_LEVEL_CRITICAL 1
+#define TRACE_LEVEL_FATAL 1
+#define TRACE_LEVEL_ERROR 2
+#define TRACE_LEVEL_WARNING 3
+#define TRACE_LEVEL_INFORMATION 4
+#define TRACE_LEVEL_VERBOSE 5
+#define TRACE_LEVEL_RESERVED6 6
+#define TRACE_LEVEL_RESERVED7 7
+#define TRACE_LEVEL_RESERVED8 8
+#define TRACE_LEVEL_RESERVED9 9
+
+#define EVENT_TRACE_TYPE_LOAD 0x0A
+
+#define EVENT_TRACE_TYPE_IO_READ 0x0A
+#define EVENT_TRACE_TYPE_IO_WRITE 0x0B
+#define EVENT_TRACE_TYPE_IO_READ_INIT 0x0C
+#define EVENT_TRACE_TYPE_IO_WRITE_INIT 0x0D
+#define EVENT_TRACE_TYPE_IO_FLUSH 0x0E
+#define EVENT_TRACE_TYPE_IO_FLUSH_INIT 0x0F
+
+#define EVENT_TRACE_TYPE_MM_TF 0x0A
+#define EVENT_TRACE_TYPE_MM_DZF 0x0B
+#define EVENT_TRACE_TYPE_MM_COW 0x0C
+#define EVENT_TRACE_TYPE_MM_GPF 0x0D
+#define EVENT_TRACE_TYPE_MM_HPF 0x0E
+#define EVENT_TRACE_TYPE_MM_AV 0x0F
+
+#define EVENT_TRACE_TYPE_SEND 0x0A
+#define EVENT_TRACE_TYPE_RECEIVE 0x0B
+#define EVENT_TRACE_TYPE_CONNECT 0x0C
+#define EVENT_TRACE_TYPE_DISCONNECT 0x0D
+#define EVENT_TRACE_TYPE_RETRANSMIT 0x0E
+#define EVENT_TRACE_TYPE_ACCEPT 0x0F
+#define EVENT_TRACE_TYPE_RECONNECT 0x10
+#define EVENT_TRACE_TYPE_CONNFAIL 0x11
+#define EVENT_TRACE_TYPE_COPY_TCP 0x12
+#define EVENT_TRACE_TYPE_COPY_ARP 0x13
+#define EVENT_TRACE_TYPE_ACKFULL 0x14
+#define EVENT_TRACE_TYPE_ACKPART 0x15
+#define EVENT_TRACE_TYPE_ACKDUP 0x16
+
+#define EVENT_TRACE_TYPE_GUIDMAP 0x0A
+#define EVENT_TRACE_TYPE_CONFIG 0x0B
+#define EVENT_TRACE_TYPE_SIDINFO 0x0C
+#define EVENT_TRACE_TYPE_SECURITY 0x0D
+
+#define EVENT_TRACE_TYPE_REGCREATE 0x0A
+#define EVENT_TRACE_TYPE_REGOPEN 0x0B
+#define EVENT_TRACE_TYPE_REGDELETE 0x0C
+#define EVENT_TRACE_TYPE_REGQUERY 0x0D
+#define EVENT_TRACE_TYPE_REGSETVALUE 0x0E
+#define EVENT_TRACE_TYPE_REGDELETEVALUE 0x0F
+#define EVENT_TRACE_TYPE_REGQUERYVALUE 0x10
+#define EVENT_TRACE_TYPE_REGENUMERATEKEY 0x11
+#define EVENT_TRACE_TYPE_REGENUMERATEVALUEKEY 0x12
+#define EVENT_TRACE_TYPE_REGQUERYMULTIPLEVALUE 0x13
+#define EVENT_TRACE_TYPE_REGSETINFORMATION 0x14
+#define EVENT_TRACE_TYPE_REGFLUSH 0x15
+#define EVENT_TRACE_TYPE_REGKCBCREATE 0x16
+#define EVENT_TRACE_TYPE_REGKCBDELETE 0x17
+#define EVENT_TRACE_TYPE_REGKCBRUNDOWNBEGIN 0x18
+#define EVENT_TRACE_TYPE_REGKCBRUNDOWNEND 0x19
+#define EVENT_TRACE_TYPE_REGVIRTUALIZE 0x1A
+#define EVENT_TRACE_TYPE_REGCLOSE 0x1B
+#define EVENT_TRACE_TYPE_REGSETSECURITY 0x1C
+#define EVENT_TRACE_TYPE_REGQUERYSECURITY 0x1D
+#define EVENT_TRACE_TYPE_REGCOMMIT 0x1E
+#define EVENT_TRACE_TYPE_REGPREPARE 0x1F
+#define EVENT_TRACE_TYPE_REGROLLBACK 0x20
+#define EVENT_TRACE_TYPE_REGMOUNTHIVE 0x21
+
+#define EVENT_TRACE_TYPE_CONFIG_CPU 0x0A
+#define EVENT_TRACE_TYPE_CONFIG_PHYSICALDISK 0x0B
+#define EVENT_TRACE_TYPE_CONFIG_LOGICALDISK 0x0C
+#define EVENT_TRACE_TYPE_CONFIG_NIC 0x0D
+#define EVENT_TRACE_TYPE_CONFIG_VIDEO 0x0E
+#define EVENT_TRACE_TYPE_CONFIG_SERVICES 0x0F
+#define EVENT_TRACE_TYPE_CONFIG_POWER 0x10
+#define EVENT_TRACE_TYPE_CONFIG_NETINFO 0x11
+
+#define EVENT_TRACE_TYPE_CONFIG_IRQ 0x15
+#define EVENT_TRACE_TYPE_CONFIG_PNP 0x16
+#define EVENT_TRACE_TYPE_CONFIG_IDECHANNEL 0x17
+#define EVENT_TRACE_TYPE_CONFIG_PLATFORM 0x19
+
+#define EVENT_TRACE_FLAG_PROCESS 0x00000001
+#define EVENT_TRACE_FLAG_THREAD 0x00000002
+#define EVENT_TRACE_FLAG_IMAGE_LOAD 0x00000004
+
+#define EVENT_TRACE_FLAG_DISK_IO 0x00000100
+#define EVENT_TRACE_FLAG_DISK_FILE_IO 0x00000200
+
+#define EVENT_TRACE_FLAG_MEMORY_PAGE_FAULTS 0x00001000
+#define EVENT_TRACE_FLAG_MEMORY_HARD_FAULTS 0x00002000
+
+#define EVENT_TRACE_FLAG_NETWORK_TCPIP 0x00010000
+
+#define EVENT_TRACE_FLAG_REGISTRY 0x00020000
+#define EVENT_TRACE_FLAG_DBGPRINT 0x00040000
+
+#define EVENT_TRACE_FLAG_PROCESS_COUNTERS 0x00000008
+#define EVENT_TRACE_FLAG_CSWITCH 0x00000010
+#define EVENT_TRACE_FLAG_DPC 0x00000020
+#define EVENT_TRACE_FLAG_INTERRUPT 0x00000040
+#define EVENT_TRACE_FLAG_SYSTEMCALL 0x00000080
+
+#define EVENT_TRACE_FLAG_DISK_IO_INIT 0x00000400
+
+#define EVENT_TRACE_FLAG_ALPC 0x00100000
+#define EVENT_TRACE_FLAG_SPLIT_IO 0x00200000
+
+#define EVENT_TRACE_FLAG_DRIVER 0x00800000
+#define EVENT_TRACE_FLAG_PROFILE 0x01000000
+#define EVENT_TRACE_FLAG_FILE_IO 0x02000000
+#define EVENT_TRACE_FLAG_FILE_IO_INIT 0x04000000
+
+#define EVENT_TRACE_FLAG_DISPATCHER 0x00000800
+#define EVENT_TRACE_FLAG_VIRTUAL_ALLOC 0x00004000
+
+#define EVENT_TRACE_FLAG_EXTENSION 0x80000000
+#define EVENT_TRACE_FLAG_FORWARD_WMI 0x40000000
+#define EVENT_TRACE_FLAG_ENABLE_RESERVE 0x20000000
+
+#define EVENT_TRACE_FILE_MODE_NONE 0x00000000
+#define EVENT_TRACE_FILE_MODE_SEQUENTIAL 0x00000001
+#define EVENT_TRACE_FILE_MODE_CIRCULAR 0x00000002
+#define EVENT_TRACE_FILE_MODE_APPEND 0x00000004
+#define EVENT_TRACE_FILE_MODE_NEWFILE 0x00000008
+#define EVENT_TRACE_FILE_MODE_PREALLOCATE 0x00000020
+
+#define EVENT_TRACE_NONSTOPPABLE_MODE 0x00000040
+#define EVENT_TRACE_SECURE_MODE 0x00000080
+#define EVENT_TRACE_USE_KBYTES_FOR_SIZE 0x00002000
+#define EVENT_TRACE_PRIVATE_IN_PROC 0x00020000
+#define EVENT_TRACE_MODE_RESERVED 0x00100000
+
+#define EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING 0x10000000
+
+#define EVENT_TRACE_REAL_TIME_MODE 0x00000100
+#define EVENT_TRACE_DELAY_OPEN_FILE_MODE 0x00000200
+#define EVENT_TRACE_BUFFERING_MODE 0x00000400
+#define EVENT_TRACE_PRIVATE_LOGGER_MODE 0x00000800
+#define EVENT_TRACE_ADD_HEADER_MODE 0x00001000
+
+#define EVENT_TRACE_USE_GLOBAL_SEQUENCE 0x00004000
+#define EVENT_TRACE_USE_LOCAL_SEQUENCE 0x00008000
+
+#define EVENT_TRACE_RELOG_MODE 0x00010000
+
+#define EVENT_TRACE_USE_PAGED_MEMORY 0x01000000
+
+#define EVENT_TRACE_CONTROL_QUERY 0
+#define EVENT_TRACE_CONTROL_STOP 1
+#define EVENT_TRACE_CONTROL_UPDATE 2
+#define EVENT_TRACE_CONTROL_FLUSH 3
+
+#define TRACE_MESSAGE_SEQUENCE 1
+#define TRACE_MESSAGE_GUID 2
+#define TRACE_MESSAGE_COMPONENTID 4
+#define TRACE_MESSAGE_TIMESTAMP 8
+#define TRACE_MESSAGE_PERFORMANCE_TIMESTAMP 16
+#define TRACE_MESSAGE_SYSTEMINFO 32
+
+#define TRACE_MESSAGE_POINTER32 0x0040
+#define TRACE_MESSAGE_POINTER64 0x0080
+
+#define TRACE_MESSAGE_FLAG_MASK 0xFFFF
+
+#define TRACE_HEADER_FLAG_USE_TIMESTAMP 0x00000200
+#define TRACE_HEADER_FLAG_TRACED_GUID 0x00020000
+#define TRACE_HEADER_FLAG_LOG_WNODE 0x00040000
+#define TRACE_HEADER_FLAG_USE_GUID_PTR 0x00080000
+#define TRACE_HEADER_FLAG_USE_MOF_PTR 0x00100000
+
+#define TRACE_MESSAGE_MAXIMUM_SIZE 8*1024
+
+#define ETW_NULL_TYPE_VALUE 0
+#define ETW_OBJECT_TYPE_VALUE 1
+#define ETW_STRING_TYPE_VALUE 2
+#define ETW_SBYTE_TYPE_VALUE 3
+#define ETW_BYTE_TYPE_VALUE 4
+#define ETW_INT16_TYPE_VALUE 5
+#define ETW_UINT16_TYPE_VALUE 6
+#define ETW_INT32_TYPE_VALUE 7
+#define ETW_UINT32_TYPE_VALUE 8
+#define ETW_INT64_TYPE_VALUE 9
+#define ETW_UINT64_TYPE_VALUE 10
+#define ETW_CHAR_TYPE_VALUE 11
+#define ETW_SINGLE_TYPE_VALUE 12
+#define ETW_DOUBLE_TYPE_VALUE 13
+#define ETW_BOOLEAN_TYPE_VALUE 14
+#define ETW_DECIMAL_TYPE_VALUE 15
+
+#define ETW_GUID_TYPE_VALUE 101
+#define ETW_ASCIICHAR_TYPE_VALUE 102
+#define ETW_ASCIISTRING_TYPE_VALUE 103
+#define ETW_COUNTED_STRING_TYPE_VALUE 104
+#define ETW_POINTER_TYPE_VALUE 105
+#define ETW_SIZET_TYPE_VALUE 106
+#define ETW_HIDDEN_TYPE_VALUE 107
+#define ETW_BOOL_TYPE_VALUE 108
+#define ETW_COUNTED_ANSISTRING_TYPE_VALUE 109
+#define ETW_REVERSED_COUNTED_STRING_TYPE_VALUE 110
+#define ETW_REVERSED_COUNTED_ANSISTRING_TYPE_VALUE 111
+#define ETW_NON_NULL_TERMINATED_STRING_TYPE_VALUE 112
+#define ETW_REDUCED_ANSISTRING_TYPE_VALUE 113
+#define ETW_REDUCED_STRING_TYPE_VALUE 114
+#define ETW_SID_TYPE_VALUE 115
+#define ETW_VARIANT_TYPE_VALUE 116
+#define ETW_PTVECTOR_TYPE_VALUE 117
+#define ETW_WMITIME_TYPE_VALUE 118
+#define ETW_DATETIME_TYPE_VALUE 119
+#define ETW_REFRENCE_TYPE_VALUE 120
+
+#define TRACE_PROVIDER_FLAG_LEGACY 0x00000001
+#define TRACE_PROVIDER_FLAG_PRE_ENABLE 0x00000002
+
+#define EVENT_CONTROL_CODE_DISABLE_PROVIDER 0
+#define EVENT_CONTROL_CODE_ENABLE_PROVIDER 1
+#define EVENT_CONTROL_CODE_CAPTURE_STATE 2
+
+#define EVENT_TRACE_USE_PROCTIME 0x0001
+#define EVENT_TRACE_USE_NOCPUTIME 0x0002
+
+typedef struct _EVENT_TRACE_HEADER {
+ USHORT Size;
+ __MINGW_EXTENSION union {
+ USHORT FieldTypeFlags;
+ __MINGW_EXTENSION struct {
+ UCHAR HeaderType;
+ UCHAR MarkerFlags;
+ } DUMMYSTRUCTNAME;
+ } DUMMYUNIONNAME;
+ __MINGW_EXTENSION union {
+ ULONG Version;
+ struct {
+ UCHAR Type;
+ UCHAR Level;
+ USHORT Version;
+ } Class;
+ } DUMMYUNIONNAME2;
+ ULONG ThreadId;
+ ULONG ProcessId;
+ LARGE_INTEGER TimeStamp;
+ __MINGW_EXTENSION union {
+ GUID Guid;
+ ULONGLONG GuidPtr;
+ } DUMMYUNIONNAME3;
+ __MINGW_EXTENSION union {
+ __MINGW_EXTENSION struct {
+ ULONG KernelTime;
+ ULONG UserTime;
+ } DUMMYSTRUCTNAME;
+ ULONG64 ProcessorTime;
+ __MINGW_EXTENSION struct {
+ ULONG ClientContext;
+ ULONG Flags;
+ } DUMMYSTRUCTNAME2;
+ } DUMMYUNIONNAME4;
+} EVENT_TRACE_HEADER,*PEVENT_TRACE_HEADER;
+
+typedef struct _EVENT_INSTANCE_HEADER {
+ USHORT Size;
+ __MINGW_EXTENSION union {
+ USHORT FieldTypeFlags;
+ __MINGW_EXTENSION struct {
+ UCHAR HeaderType;
+ UCHAR MarkerFlags;
+ } DUMMYSTRUCTNAME;
+ } DUMMYUNIONNAME;
+ __MINGW_EXTENSION union {
+ ULONG Version;
+ struct {
+ UCHAR Type;
+ UCHAR Level;
+ USHORT Version;
+ } Class;
+ } DUMMYUNIONNAME2;
+ ULONG ThreadId;
+ ULONG ProcessId;
+ LARGE_INTEGER TimeStamp;
+ ULONGLONG RegHandle;
+ ULONG InstanceId;
+ ULONG ParentInstanceId;
+ __MINGW_EXTENSION union {
+ __MINGW_EXTENSION struct {
+ ULONG KernelTime;
+ ULONG UserTime;
+ } DUMMYSTRUCTNAME;
+ ULONG64 ProcessorTime;
+ __MINGW_EXTENSION struct {
+ ULONG EventId;
+ ULONG Flags;
+ } DUMMYSTRUCTNAME2;
+ } DUMMYUNIONNAME3;
+ ULONGLONG ParentRegHandle;
+} EVENT_INSTANCE_HEADER,*PEVENT_INSTANCE_HEADER;
+
+#define DEFINE_TRACE_MOF_FIELD(MOF,ptr,length,type) \
+ (MOF)->DataPtr = (ULONG64) (ULONG_PTR) ptr; \
+ (MOF)->Length = (ULONG) length; \
+ (MOF)->DataType = (ULONG) type;
+
+typedef struct _MOF_FIELD {
+ ULONG64 DataPtr;
+ ULONG Length;
+ ULONG DataType;
+} MOF_FIELD,*PMOF_FIELD;
+
+#if !(defined(_NTDDK_) || defined(_NTIFS_)) || defined(_WMIKM_)
+
+typedef struct _TRACE_LOGFILE_HEADER {
+ ULONG BufferSize;
+ __MINGW_EXTENSION union {
+ ULONG Version;
+ struct {
+ UCHAR MajorVersion;
+ UCHAR MinorVersion;
+ UCHAR SubVersion;
+ UCHAR SubMinorVersion;
+ } VersionDetail;
+ } DUMMYUNIONNAME;
+ ULONG ProviderVersion;
+ ULONG NumberOfProcessors;
+ LARGE_INTEGER EndTime;
+ ULONG TimerResolution;
+ ULONG MaximumFileSize;
+ ULONG LogFileMode;
+ ULONG BuffersWritten;
+ __MINGW_EXTENSION union {
+ GUID LogInstanceGuid;
+ __MINGW_EXTENSION struct {
+ ULONG StartBuffers;
+ ULONG PointerSize;
+ ULONG EventsLost;
+ ULONG CpuSpeedInMHz;
+ } DUMMYSTRUCTNAME;
+ } DUMMYUNIONNAME2;
+#if defined(_WMIKM_)
+ PWCHAR LoggerName;
+ PWCHAR LogFileName;
+ RTL_TIME_ZONE_INFORMATION TimeZone;
+#else
+ LPWSTR LoggerName;
+ LPWSTR LogFileName;
+ TIME_ZONE_INFORMATION TimeZone;
+#endif
+ LARGE_INTEGER BootTime;
+ LARGE_INTEGER PerfFreq;
+ LARGE_INTEGER StartTime;
+ ULONG ReservedFlags;
+ ULONG BuffersLost;
+} TRACE_LOGFILE_HEADER,*PTRACE_LOGFILE_HEADER;
+
+typedef struct _TRACE_LOGFILE_HEADER32 {
+ ULONG BufferSize;
+ __MINGW_EXTENSION union {
+ ULONG Version;
+ struct {
+ UCHAR MajorVersion;
+ UCHAR MinorVersion;
+ UCHAR SubVersion;
+ UCHAR SubMinorVersion;
+ } VersionDetail;
+ };
+ ULONG ProviderVersion;
+ ULONG NumberOfProcessors;
+ LARGE_INTEGER EndTime;
+ ULONG TimerResolution;
+ ULONG MaximumFileSize;
+ ULONG LogFileMode;
+ ULONG BuffersWritten;
+ __MINGW_EXTENSION union {
+ GUID LogInstanceGuid;
+ __MINGW_EXTENSION struct {
+ ULONG StartBuffers;
+ ULONG PointerSize;
+ ULONG EventsLost;
+ ULONG CpuSpeedInMHz;
+ };
+ };
+#if defined(_WMIKM_)
+ ULONG32 LoggerName;
+ ULONG32 LogFileName;
+ RTL_TIME_ZONE_INFORMATION TimeZone;
+#else
+ ULONG32 LoggerName;
+ ULONG32 LogFileName;
+ TIME_ZONE_INFORMATION TimeZone;
+#endif
+ LARGE_INTEGER BootTime;
+ LARGE_INTEGER PerfFreq;
+ LARGE_INTEGER StartTime;
+ ULONG ReservedFlags;
+ ULONG BuffersLost;
+} TRACE_LOGFILE_HEADER32, *PTRACE_LOGFILE_HEADER32;
+
+typedef struct _TRACE_LOGFILE_HEADER64 {
+ ULONG BufferSize;
+ __MINGW_EXTENSION union {
+ ULONG Version;
+ struct {
+ UCHAR MajorVersion;
+ UCHAR MinorVersion;
+ UCHAR SubVersion;
+ UCHAR SubMinorVersion;
+ } VersionDetail;
+ };
+ ULONG ProviderVersion;
+ ULONG NumberOfProcessors;
+ LARGE_INTEGER EndTime;
+ ULONG TimerResolution;
+ ULONG MaximumFileSize;
+ ULONG LogFileMode;
+ ULONG BuffersWritten;
+ __MINGW_EXTENSION union {
+ GUID LogInstanceGuid;
+ __MINGW_EXTENSION struct {
+ ULONG StartBuffers;
+ ULONG PointerSize;
+ ULONG EventsLost;
+ ULONG CpuSpeedInMHz;
+ };
+ };
+#if defined(_WMIKM_)
+ ULONG64 LoggerName;
+ ULONG64 LogFileName;
+ RTL_TIME_ZONE_INFORMATION TimeZone;
+#else
+ ULONG64 LoggerName;
+ ULONG64 LogFileName;
+ TIME_ZONE_INFORMATION TimeZone;
+#endif
+ LARGE_INTEGER BootTime;
+ LARGE_INTEGER PerfFreq;
+ LARGE_INTEGER StartTime;
+ ULONG ReservedFlags;
+ ULONG BuffersLost;
+} TRACE_LOGFILE_HEADER64, *PTRACE_LOGFILE_HEADER64;
+
+#endif /* !_NTDDK_ || _WMIKM_ */
+
+typedef struct _EVENT_INSTANCE_INFO {
+ HANDLE RegHandle;
+ ULONG InstanceId;
+} EVENT_INSTANCE_INFO,*PEVENT_INSTANCE_INFO;
+
+#if !defined(_WMIKM_) && !defined(_NTDDK_) && !defined(_NTIFS_)
+
+typedef struct _EVENT_TRACE_PROPERTIES {
+ WNODE_HEADER Wnode;
+ ULONG BufferSize;
+ ULONG MinimumBuffers;
+ ULONG MaximumBuffers;
+ ULONG MaximumFileSize;
+ ULONG LogFileMode;
+ ULONG FlushTimer;
+ ULONG EnableFlags;
+ LONG AgeLimit;
+
+ ULONG NumberOfBuffers;
+ ULONG FreeBuffers;
+ ULONG EventsLost;
+ ULONG BuffersWritten;
+ ULONG LogBuffersLost;
+ ULONG RealTimeBuffersLost;
+ HANDLE LoggerThreadId;
+ ULONG LogFileNameOffset;
+ ULONG LoggerNameOffset;
+} EVENT_TRACE_PROPERTIES,*PEVENT_TRACE_PROPERTIES;
+
+typedef struct _TRACE_GUID_REGISTRATION {
+ LPCGUID Guid;
+ HANDLE RegHandle;
+} TRACE_GUID_REGISTRATION,*PTRACE_GUID_REGISTRATION;
+
+#endif /* !_NTDDK_ || _WMIKM_ */
+
+typedef struct _TRACE_GUID_PROPERTIES {
+ GUID Guid;
+ ULONG GuidType;
+ ULONG LoggerId;
+ ULONG EnableLevel;
+ ULONG EnableFlags;
+ BOOLEAN IsEnable;
+} TRACE_GUID_PROPERTIES,*PTRACE_GUID_PROPERTIES;
+
+typedef struct _ETW_BUFFER_CONTEXT {
+ UCHAR ProcessorNumber;
+ UCHAR Alignment;
+ USHORT LoggerId;
+} ETW_BUFFER_CONTEXT, *PETW_BUFFER_CONTEXT;
+
+typedef struct _TRACE_ENABLE_INFO {
+ ULONG IsEnabled;
+ UCHAR Level;
+ UCHAR Reserved1;
+ USHORT LoggerId;
+ ULONG EnableProperty;
+ ULONG Reserved2;
+ ULONGLONG MatchAnyKeyword;
+ ULONGLONG MatchAllKeyword;
+} TRACE_ENABLE_INFO, *PTRACE_ENABLE_INFO;
+
+typedef struct _TRACE_PROVIDER_INSTANCE_INFO {
+ ULONG NextOffset;
+ ULONG EnableCount;
+ ULONG Pid;
+ ULONG Flags;
+} TRACE_PROVIDER_INSTANCE_INFO, *PTRACE_PROVIDER_INSTANCE_INFO;
+
+typedef struct _TRACE_GUID_INFO {
+ ULONG InstanceCount;
+ ULONG Reserved;
+} TRACE_GUID_INFO, *PTRACE_GUID_INFO;
+
+typedef struct _EVENT_TRACE {
+ EVENT_TRACE_HEADER Header;
+ ULONG InstanceId;
+ ULONG ParentInstanceId;
+ GUID ParentGuid;
+ PVOID MofData;
+ ULONG MofLength;
+ __MINGW_EXTENSION union {
+ ULONG ClientContext;
+ ETW_BUFFER_CONTEXT BufferContext; /* MSDN says ULONG, for XP and older? */
+ } DUMMYUNIONNAME;
+} EVENT_TRACE,*PEVENT_TRACE;
+
+#if !defined(_WMIKM_) && !defined(_NTDDK_) && !defined(_NTIFS_)
+
+#ifndef DEFINED_PEVENT_RECORD
+typedef struct _EVENT_RECORD EVENT_RECORD, *PEVENT_RECORD;
+#define DEFINED_PEVENT_RECORD 1
+#endif /* for evntcons.h */
+#ifndef DEFINED_PEVENT_FILTER_DESC
+typedef struct _EVENT_FILTER_DESCRIPTOR EVENT_FILTER_DESCRIPTOR, *PEVENT_FILTER_DESCRIPTOR;
+#define DEFINED_PEVENT_FILTER_DESC 1
+#endif /* for evntprov.h */
+typedef struct _EVENT_TRACE_LOGFILEW EVENT_TRACE_LOGFILEW,*PEVENT_TRACE_LOGFILEW;
+typedef struct _EVENT_TRACE_LOGFILEA EVENT_TRACE_LOGFILEA,*PEVENT_TRACE_LOGFILEA;
+typedef ULONG (WINAPI *PEVENT_TRACE_BUFFER_CALLBACKW)(PEVENT_TRACE_LOGFILEW Logfile);
+typedef ULONG (WINAPI *PEVENT_TRACE_BUFFER_CALLBACKA)(PEVENT_TRACE_LOGFILEA Logfile);
+typedef VOID (WINAPI *PEVENT_CALLBACK)(PEVENT_TRACE pEvent);
+typedef VOID (WINAPI *PEVENT_RECORD_CALLBACK)(PEVENT_RECORD EventRecord);
+typedef ULONG (WINAPI *WMIDPREQUEST)(WMIDPREQUESTCODE RequestCode,PVOID RequestContext,ULONG *BufferSize,PVOID Buffer);
+
+struct _EVENT_TRACE_LOGFILEW {
+ LPWSTR LogFileName;
+ LPWSTR LoggerName;
+ LONGLONG CurrentTime;
+ ULONG BuffersRead;
+ __MINGW_EXTENSION union {
+ ULONG LogFileMode;
+ ULONG ProcessTraceMode;
+ } DUMMYUNIONNAME;
+ EVENT_TRACE CurrentEvent;
+ TRACE_LOGFILE_HEADER LogfileHeader;
+ PEVENT_TRACE_BUFFER_CALLBACKW BufferCallback;
+ ULONG BufferSize;
+ ULONG Filled;
+ ULONG EventsLost;
+ __MINGW_EXTENSION union {
+ PEVENT_CALLBACK EventCallback;
+ PEVENT_RECORD_CALLBACK EventRecordCallback;
+ } DUMMYUNIONNAME2;
+ ULONG IsKernelTrace;
+ PVOID Context;
+};
+
+struct _EVENT_TRACE_LOGFILEA {
+ LPSTR LogFileName;
+ LPSTR LoggerName;
+ LONGLONG CurrentTime;
+ ULONG BuffersRead;
+ __MINGW_EXTENSION union {
+ ULONG LogFileMode;
+ ULONG ProcessTraceMode;
+ } DUMMYUNIONNAME;
+ EVENT_TRACE CurrentEvent;
+ TRACE_LOGFILE_HEADER LogfileHeader;
+ PEVENT_TRACE_BUFFER_CALLBACKA BufferCallback;
+ ULONG BufferSize;
+ ULONG Filled;
+ ULONG EventsLost;
+ __MINGW_EXTENSION union {
+ PEVENT_CALLBACK EventCallback;
+ PEVENT_RECORD_CALLBACK EventRecordCallback;
+ } DUMMYUNIONNAME2;
+ ULONG IsKernelTrace;
+ PVOID Context;
+};
+
+#if defined(_UNICODE) || defined(UNICODE)
+#define PEVENT_TRACE_BUFFER_CALLBACK PEVENT_TRACE_BUFFER_CALLBACKW
+#define EVENT_TRACE_LOGFILE EVENT_TRACE_LOGFILEW
+#define PEVENT_TRACE_LOGFILE PEVENT_TRACE_LOGFILEW
+#define KERNEL_LOGGER_NAME KERNEL_LOGGER_NAMEW
+#define GLOBAL_LOGGER_NAME GLOBAL_LOGGER_NAMEW
+#define EVENT_LOGGER_NAME EVENT_LOGGER_NAMEW
+#else
+#define PEVENT_TRACE_BUFFER_CALLBACK PEVENT_TRACE_BUFFER_CALLBACKA
+#define EVENT_TRACE_LOGFILE EVENT_TRACE_LOGFILEA
+#define PEVENT_TRACE_LOGFILE PEVENT_TRACE_LOGFILEA
+#define KERNEL_LOGGER_NAME KERNEL_LOGGER_NAMEA
+#define GLOBAL_LOGGER_NAME GLOBAL_LOGGER_NAMEA
+#define EVENT_LOGGER_NAME EVENT_LOGGER_NAMEA
+#endif /* defined(_UNICODE) || defined(UNICODE) */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+EXTERN_C ULONG WMIAPI StartTraceW(PTRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI StartTraceA(PTRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI StopTraceW(TRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI StopTraceA(TRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI QueryTraceW(TRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI QueryTraceA(TRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI UpdateTraceW(TRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI UpdateTraceA(TRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI FlushTraceW(TRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI FlushTraceA(TRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties);
+EXTERN_C ULONG WMIAPI ControlTraceW(TRACEHANDLE TraceHandle,LPCWSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties,ULONG ControlCode);
+EXTERN_C ULONG WMIAPI ControlTraceA(TRACEHANDLE TraceHandle,LPCSTR InstanceName,PEVENT_TRACE_PROPERTIES Properties,ULONG ControlCode);
+EXTERN_C ULONG WMIAPI QueryAllTracesW(PEVENT_TRACE_PROPERTIES *PropertyArray,ULONG PropertyArrayCount,PULONG LoggerCount);
+EXTERN_C ULONG WMIAPI QueryAllTracesA(PEVENT_TRACE_PROPERTIES *PropertyArray,ULONG PropertyArrayCount,PULONG LoggerCount);
+EXTERN_C ULONG WMIAPI EnableTrace(ULONG Enable,ULONG EnableFlag,ULONG EnableLevel,LPCGUID ControlGuid,TRACEHANDLE TraceHandle);
+
+#if (_WIN32_WINNT >= 0x0600)
+EXTERN_C ULONG WMIAPI EnableTraceEx(
+ LPCGUID ProviderId,
+ LPCGUID SourceId,
+ TRACEHANDLE TraceHandle,
+ ULONG IsEnabled,
+ UCHAR Level,
+ ULONGLONG MatchAnyKeyword,
+ ULONGLONG MatchAllKeyword,
+ ULONG EnableProperty,
+ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc
+);
+#endif /* _WIN32_WINNT >= 0x0600 */
+
+#define ENABLE_TRACE_PARAMETERS_VERSION 1
+
+typedef struct _ENABLE_TRACE_PARAMETERS {
+ ULONG Version;
+ ULONG EnableProperty;
+ ULONG ControlFlags;
+ GUID SourceId;
+ PEVENT_FILTER_DESCRIPTOR EnableFilterDesc;
+} ENABLE_TRACE_PARAMETERS, *PENABLE_TRACE_PARAMETERS;
+
+#if (_WIN32_WINNT >= 0x0601)
+EXTERN_C ULONG WMIAPI EnableTraceEx2(
+ TRACEHANDLE TraceHandle,
+ LPCGUID ProviderId,
+ ULONG ControlCode,
+ UCHAR Level,
+ ULONGLONG MatchAnyKeyword,
+ ULONGLONG MatchAllKeyword,
+ ULONG Timeout,
+ PENABLE_TRACE_PARAMETERS EnableParameters
+);
+#endif /* _WIN32_WINNT >= 0x0601 */
+
+typedef enum _TRACE_QUERY_INFO_CLASS {
+ TraceGuidQueryList,
+ TraceGuidQueryInfo,
+ TraceGuidQueryProcess,
+ TraceStackTracingInfo,
+ MaxTraceSetInfoClass
+} TRACE_QUERY_INFO_CLASS, TRACE_INFO_CLASS;
+
+#if (_WIN32_WINNT >= 0x0600)
+EXTERN_C ULONG WMIAPI EnumerateTraceGuidsEx(
+ TRACE_QUERY_INFO_CLASS TraceQueryInfoClass,
+ PVOID InBuffer,
+ ULONG InBufferSize,
+ PVOID OutBuffer,
+ ULONG OutBufferSize,
+ PULONG ReturnLength
+);
+#endif /* _WIN32_WINNT >= 0x0600 */
+
+/*To enable the read event type for disk IO events, set GUID to 3d6fa8d4-fe05-11d0-9dda-00c04fd7ba7c and Type to 10.*/
+typedef struct _CLASSIC_EVENT_ID {
+ GUID EventGuid;
+ UCHAR Type;
+ UCHAR Reserved[7];
+} CLASSIC_EVENT_ID, *PCLASSIC_EVENT_ID;
+
+#if (_WIN32_WINNT >= 0x0601)
+EXTERN_C ULONG WMIAPI TraceSetInformation(
+ TRACEHANDLE SessionHandle,
+ TRACE_INFO_CLASS InformationClass,
+ PVOID TraceInformation,
+ ULONG InformationLength
+);
+#endif /* _WIN32_WINNT >= 0x0601 */
+
+EXTERN_C ULONG WMIAPI CreateTraceInstanceId(HANDLE RegHandle,PEVENT_INSTANCE_INFO pInstInfo);
+EXTERN_C ULONG WMIAPI TraceEvent(TRACEHANDLE TraceHandle,PEVENT_TRACE_HEADER EventTrace);
+EXTERN_C ULONG WMIAPI TraceEventInstance(TRACEHANDLE TraceHandle,PEVENT_INSTANCE_HEADER EventTrace,PEVENT_INSTANCE_INFO pInstInfo,PEVENT_INSTANCE_INFO pParentInstInfo);
+EXTERN_C ULONG WMIAPI RegisterTraceGuidsW(WMIDPREQUEST RequestAddress,PVOID RequestContext,LPCGUID ControlGuid,ULONG GuidCount,PTRACE_GUID_REGISTRATION TraceGuidReg,LPCWSTR MofImagePath,LPCWSTR MofResourceName,PTRACEHANDLE RegistrationHandle);
+EXTERN_C ULONG WMIAPI RegisterTraceGuidsA(WMIDPREQUEST RequestAddress,PVOID RequestContext,LPCGUID ControlGuid,ULONG GuidCount,PTRACE_GUID_REGISTRATION TraceGuidReg,LPCSTR MofImagePath,LPCSTR MofResourceName,PTRACEHANDLE RegistrationHandle);
+EXTERN_C ULONG WMIAPI EnumerateTraceGuids(PTRACE_GUID_PROPERTIES *GuidPropertiesArray,ULONG PropertyArrayCount,PULONG GuidCount);
+EXTERN_C ULONG WMIAPI UnregisterTraceGuids(TRACEHANDLE RegistrationHandle);
+EXTERN_C TRACEHANDLE WMIAPI GetTraceLoggerHandle(PVOID Buffer);
+EXTERN_C UCHAR WMIAPI GetTraceEnableLevel(TRACEHANDLE TraceHandle);
+EXTERN_C ULONG WMIAPI GetTraceEnableFlags(TRACEHANDLE TraceHandle);
+EXTERN_C TRACEHANDLE WMIAPI OpenTraceA(PEVENT_TRACE_LOGFILEA Logfile);
+EXTERN_C TRACEHANDLE WMIAPI OpenTraceW(PEVENT_TRACE_LOGFILEW Logfile);
+EXTERN_C ULONG WMIAPI ProcessTrace(PTRACEHANDLE HandleArray,ULONG HandleCount,LPFILETIME StartTime,LPFILETIME EndTime);
+EXTERN_C ULONG WMIAPI CloseTrace(TRACEHANDLE TraceHandle);
+EXTERN_C ULONG WMIAPI SetTraceCallback(LPCGUID pGuid,PEVENT_CALLBACK EventCallback);
+EXTERN_C ULONG WMIAPI RemoveTraceCallback (LPCGUID pGuid);
+EXTERN_C ULONG __cdecl TraceMessage(TRACEHANDLE LoggerHandle,ULONG MessageFlags,LPCGUID MessageGuid,USHORT MessageNumber,...);
+EXTERN_C ULONG WMIAPI TraceMessageVa(TRACEHANDLE LoggerHandle,ULONG MessageFlags,LPCGUID MessageGuid,USHORT MessageNumber,va_list MessageArgList);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define INVALID_PROCESSTRACE_HANDLE ((TRACEHANDLE)INVALID_HANDLE_VALUE)
+
+#if defined(UNICODE) || defined(_UNICODE)
+#define RegisterTraceGuids RegisterTraceGuidsW
+#define StartTrace StartTraceW
+#define ControlTrace ControlTraceW
+
+#if defined(__TRACE_W2K_COMPATIBLE)
+#define StopTrace(a,b,c) ControlTraceW((a),(b),(c),EVENT_TRACE_CONTROL_STOP)
+#define QueryTrace(a,b,c) ControlTraceW((a),(b),(c),EVENT_TRACE_CONTROL_QUERY)
+#define UpdateTrace(a,b,c) ControlTraceW((a),(b),(c),EVENT_TRACE_CONTROL_UPDATE)
+#else
+#define StopTrace StopTraceW
+#define QueryTrace QueryTraceW
+#define UpdateTrace UpdateTraceW
+#endif /* defined(__TRACE_W2K_COMPATIBLE) */
+
+#define FlushTrace FlushTraceW
+#define QueryAllTraces QueryAllTracesW
+#define OpenTrace OpenTraceW
+
+#else /* defined(UNICODE) || defined(_UNICODE) */
+
+#define RegisterTraceGuids RegisterTraceGuidsA
+#define StartTrace StartTraceA
+#define ControlTrace ControlTraceA
+
+#if defined(__TRACE_W2K_COMPATIBLE)
+#define StopTrace(a,b,c) ControlTraceA((a),(b),(c),EVENT_TRACE_CONTROL_STOP)
+#define QueryTrace(a,b,c) ControlTraceA((a),(b),(c),EVENT_TRACE_CONTROL_QUERY)
+#define UpdateTrace(a,b,c) ControlTraceA((a),(b),(c),EVENT_TRACE_CONTROL_UPDATE)
+#else
+#define StopTrace StopTraceA
+#define QueryTrace QueryTraceA
+#define UpdateTrace UpdateTraceA
+#endif /* defined(__TRACE_W2K_COMPATIBLE) */
+
+#define FlushTrace FlushTraceA
+#define QueryAllTraces QueryAllTracesA
+#define OpenTrace OpenTraceA
+#endif /* defined(UNICODE) || defined(_UNICODE) */
+
+#endif /* !defined(_WMIKM_) && !defined(_NTDDK_) && !defined(_NTIFS_) */
+
+#endif /* defined(_WINNT_) || defined(WINNT) */
+
+#endif /* _EVNTRACE_ */
+
diff --git a/src/win32ctl/include/pdh.h b/src/win32ctl/include/pdh.h
new file mode 100644
index 0000000..d7828a4
--- /dev/null
+++ b/src/win32ctl/include/pdh.h
@@ -0,0 +1,605 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _PDH_H_
+#define _PDH_H_
+
+/* --- start added by kenj */
+#undef __MINGW_EXTENSION
+#if defined(__GNUC__) || defined(__GNUG__)
+#define __MINGW_EXTENSION __extension__
+#else
+#define __MINGW_EXTENSION
+#endif
+/* --- end added by kenj */
+
+#include <_mingw_unicode.h>
+#include <windows.h>
+#include <winperf.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef LONG PDH_STATUS;
+
+#define PDH_FUNCTION PDH_STATUS WINAPI
+
+#define PDH_CVERSION_WIN40 ((DWORD)(0x0400))
+#define PDH_CVERSION_WIN50 ((DWORD)(0x0500))
+
+#define PDH_VERSION ((DWORD)((PDH_CVERSION_WIN50) + 0x0003))
+
+#define IsSuccessSeverity(ErrorCode) ((((DWORD)(ErrorCode) & (0xC0000000L))==0x00000000L) ? TRUE : FALSE)
+#define IsInformationalSeverity(ErrorCode) ((((DWORD)(ErrorCode) & (0xC0000000L))==0x40000000L) ? TRUE : FALSE)
+#define IsWarningSeverity(ErrorCode) ((((DWORD)(ErrorCode) & (0xC0000000L))==0x80000000L) ? TRUE : FALSE)
+#define IsErrorSeverity(ErrorCode) ((((DWORD)(ErrorCode) & (0xC0000000L))==0xC0000000L) ? TRUE : FALSE)
+
+#define MAX_COUNTER_PATH 256
+
+#define PDH_MAX_COUNTER_NAME 1024
+#define PDH_MAX_INSTANCE_NAME 1024
+#define PDH_MAX_COUNTER_PATH 2048
+#define PDH_MAX_DATASOURCE_PATH 1024
+
+ typedef HANDLE PDH_HCOUNTER;
+ typedef HANDLE PDH_HQUERY;
+ typedef HANDLE PDH_HLOG;
+
+ typedef PDH_HCOUNTER HCOUNTER;
+ typedef PDH_HQUERY HQUERY;
+#ifndef _LMHLOGDEFINED_
+ typedef PDH_HLOG HLOG;
+#endif
+
+#ifdef INVALID_HANDLE_VALUE
+#undef INVALID_HANDLE_VALUE
+#define INVALID_HANDLE_VALUE ((HANDLE)((LONG_PTR)-1))
+#endif
+
+#define H_REALTIME_DATASOURCE NULL
+#define H_WBEM_DATASOURCE INVALID_HANDLE_VALUE
+
+ typedef struct _PDH_RAW_COUNTER {
+ DWORD CStatus;
+ FILETIME TimeStamp;
+ LONGLONG FirstValue;
+ LONGLONG SecondValue;
+ DWORD MultiCount;
+ } PDH_RAW_COUNTER,*PPDH_RAW_COUNTER;
+
+ typedef struct _PDH_RAW_COUNTER_ITEM_A {
+ LPSTR szName;
+ PDH_RAW_COUNTER RawValue;
+ } PDH_RAW_COUNTER_ITEM_A,*PPDH_RAW_COUNTER_ITEM_A;
+
+ typedef struct _PDH_RAW_COUNTER_ITEM_W {
+ LPWSTR szName;
+ PDH_RAW_COUNTER RawValue;
+ } PDH_RAW_COUNTER_ITEM_W,*PPDH_RAW_COUNTER_ITEM_W;
+
+ typedef struct _PDH_FMT_COUNTERVALUE {
+ DWORD CStatus;
+ __MINGW_EXTENSION union {
+ LONG longValue;
+ double doubleValue;
+ LONGLONG largeValue;
+ LPCSTR AnsiStringValue;
+ LPCWSTR WideStringValue;
+ };
+ } PDH_FMT_COUNTERVALUE,*PPDH_FMT_COUNTERVALUE;
+
+ typedef struct _PDH_FMT_COUNTERVALUE_ITEM_A {
+ LPSTR szName;
+ PDH_FMT_COUNTERVALUE FmtValue;
+ } PDH_FMT_COUNTERVALUE_ITEM_A,*PPDH_FMT_COUNTERVALUE_ITEM_A;
+
+ typedef struct _PDH_FMT_COUNTERVALUE_ITEM_W {
+ LPWSTR szName;
+ PDH_FMT_COUNTERVALUE FmtValue;
+ } PDH_FMT_COUNTERVALUE_ITEM_W,*PPDH_FMT_COUNTERVALUE_ITEM_W;
+
+ typedef struct _PDH_STATISTICS {
+ DWORD dwFormat;
+ DWORD count;
+ PDH_FMT_COUNTERVALUE min;
+ PDH_FMT_COUNTERVALUE max;
+ PDH_FMT_COUNTERVALUE mean;
+ } PDH_STATISTICS,*PPDH_STATISTICS;
+
+ typedef struct _PDH_COUNTER_PATH_ELEMENTS_A {
+ LPSTR szMachineName;
+ LPSTR szObjectName;
+ LPSTR szInstanceName;
+ LPSTR szParentInstance;
+ DWORD dwInstanceIndex;
+ LPSTR szCounterName;
+ } PDH_COUNTER_PATH_ELEMENTS_A,*PPDH_COUNTER_PATH_ELEMENTS_A;
+
+ typedef struct _PDH_COUNTER_PATH_ELEMENTS_W {
+ LPWSTR szMachineName;
+ LPWSTR szObjectName;
+ LPWSTR szInstanceName;
+ LPWSTR szParentInstance;
+ DWORD dwInstanceIndex;
+ LPWSTR szCounterName;
+ } PDH_COUNTER_PATH_ELEMENTS_W,*PPDH_COUNTER_PATH_ELEMENTS_W;
+
+ typedef struct _PDH_DATA_ITEM_PATH_ELEMENTS_A {
+ LPSTR szMachineName;
+ GUID ObjectGUID;
+ DWORD dwItemId;
+ LPSTR szInstanceName;
+ } PDH_DATA_ITEM_PATH_ELEMENTS_A,*PPDH_DATA_ITEM_PATH_ELEMENTS_A;
+
+ typedef struct _PDH_DATA_ITEM_PATH_ELEMENTS_W {
+ LPWSTR szMachineName;
+ GUID ObjectGUID;
+ DWORD dwItemId;
+ LPWSTR szInstanceName;
+ } PDH_DATA_ITEM_PATH_ELEMENTS_W,*PPDH_DATA_ITEM_PATH_ELEMENTS_W;
+
+ typedef struct _PDH_COUNTER_INFO_A {
+ DWORD dwLength;
+ DWORD dwType;
+ DWORD CVersion;
+ DWORD CStatus;
+ LONG lScale;
+ LONG lDefaultScale;
+ DWORD_PTR dwUserData;
+ DWORD_PTR dwQueryUserData;
+ LPSTR szFullPath;
+ __MINGW_EXTENSION union {
+ PDH_DATA_ITEM_PATH_ELEMENTS_A DataItemPath;
+ PDH_COUNTER_PATH_ELEMENTS_A CounterPath;
+ __MINGW_EXTENSION struct {
+ LPSTR szMachineName;
+ LPSTR szObjectName;
+ LPSTR szInstanceName;
+ LPSTR szParentInstance;
+ DWORD dwInstanceIndex;
+ LPSTR szCounterName;
+ };
+ };
+ LPSTR szExplainText;
+ DWORD DataBuffer[1];
+ } PDH_COUNTER_INFO_A,*PPDH_COUNTER_INFO_A;
+
+ typedef struct _PDH_COUNTER_INFO_W {
+ DWORD dwLength;
+ DWORD dwType;
+ DWORD CVersion;
+ DWORD CStatus;
+ LONG lScale;
+ LONG lDefaultScale;
+ DWORD_PTR dwUserData;
+ DWORD_PTR dwQueryUserData;
+ LPWSTR szFullPath;
+ __MINGW_EXTENSION union {
+ PDH_DATA_ITEM_PATH_ELEMENTS_W DataItemPath;
+ PDH_COUNTER_PATH_ELEMENTS_W CounterPath;
+ __MINGW_EXTENSION struct {
+ LPWSTR szMachineName;
+ LPWSTR szObjectName;
+ LPWSTR szInstanceName;
+ LPWSTR szParentInstance;
+ DWORD dwInstanceIndex;
+ LPWSTR szCounterName;
+ };
+ };
+ LPWSTR szExplainText;
+ DWORD DataBuffer[1];
+ } PDH_COUNTER_INFO_W,*PPDH_COUNTER_INFO_W;
+
+ typedef struct _PDH_TIME_INFO {
+ LONGLONG StartTime;
+ LONGLONG EndTime;
+ DWORD SampleCount;
+ } PDH_TIME_INFO,*PPDH_TIME_INFO;
+
+ typedef struct _PDH_RAW_LOG_RECORD {
+ DWORD dwStructureSize;
+ DWORD dwRecordType;
+ DWORD dwItems;
+ UCHAR RawBytes[1];
+ } PDH_RAW_LOG_RECORD,*PPDH_RAW_LOG_RECORD;
+
+ typedef struct _PDH_LOG_SERVICE_QUERY_INFO_A {
+ DWORD dwSize;
+ DWORD dwFlags;
+ DWORD dwLogQuota;
+ LPSTR szLogFileCaption;
+ LPSTR szDefaultDir;
+ LPSTR szBaseFileName;
+ DWORD dwFileType;
+ DWORD dwReserved;
+ __MINGW_EXTENSION union {
+ __MINGW_EXTENSION struct {
+ DWORD PdlAutoNameInterval;
+ DWORD PdlAutoNameUnits;
+ LPSTR PdlCommandFilename;
+ LPSTR PdlCounterList;
+ DWORD PdlAutoNameFormat;
+ DWORD PdlSampleInterval;
+ FILETIME PdlLogStartTime;
+ FILETIME PdlLogEndTime;
+ };
+ __MINGW_EXTENSION struct {
+ DWORD TlNumberOfBuffers;
+ DWORD TlMinimumBuffers;
+ DWORD TlMaximumBuffers;
+ DWORD TlFreeBuffers;
+ DWORD TlBufferSize;
+ DWORD TlEventsLost;
+ DWORD TlLoggerThreadId;
+ DWORD TlBuffersWritten;
+ DWORD TlLogHandle;
+ LPSTR TlLogFileName;
+ };
+ };
+ } PDH_LOG_SERVICE_QUERY_INFO_A,*PPDH_LOG_SERVICE_QUERY_INFO_A;
+
+ typedef struct _PDH_LOG_SERVICE_QUERY_INFO_W {
+ DWORD dwSize;
+ DWORD dwFlags;
+ DWORD dwLogQuota;
+ LPWSTR szLogFileCaption;
+ LPWSTR szDefaultDir;
+ LPWSTR szBaseFileName;
+ DWORD dwFileType;
+ DWORD dwReserved;
+ __MINGW_EXTENSION union {
+ __MINGW_EXTENSION struct {
+ DWORD PdlAutoNameInterval;
+ DWORD PdlAutoNameUnits;
+ LPWSTR PdlCommandFilename;
+ LPWSTR PdlCounterList;
+ DWORD PdlAutoNameFormat;
+ DWORD PdlSampleInterval;
+ FILETIME PdlLogStartTime;
+ FILETIME PdlLogEndTime;
+ };
+ __MINGW_EXTENSION struct {
+ DWORD TlNumberOfBuffers;
+ DWORD TlMinimumBuffers;
+ DWORD TlMaximumBuffers;
+ DWORD TlFreeBuffers;
+ DWORD TlBufferSize;
+ DWORD TlEventsLost;
+ DWORD TlLoggerThreadId;
+ DWORD TlBuffersWritten;
+ DWORD TlLogHandle;
+ LPWSTR TlLogFileName;
+ };
+ };
+ } PDH_LOG_SERVICE_QUERY_INFO_W,*PPDH_LOG_SERVICE_QUERY_INFO_W;
+
+#define MAX_TIME_VALUE ((LONGLONG) 0x7FFFFFFFFFFFFFFF)
+#define MIN_TIME_VALUE ((LONGLONG) 0)
+
+ PDH_FUNCTION PdhGetDllVersion(LPDWORD lpdwVersion);
+ PDH_FUNCTION PdhOpenQueryW(LPCWSTR szDataSource,DWORD_PTR dwUserData,PDH_HQUERY *phQuery);
+ PDH_FUNCTION PdhOpenQueryA(LPCSTR szDataSource,DWORD_PTR dwUserData,PDH_HQUERY *phQuery);
+ PDH_FUNCTION PdhAddCounterW(PDH_HQUERY hQuery,LPCWSTR szFullCounterPath,DWORD_PTR dwUserData,PDH_HCOUNTER *phCounter);
+ PDH_FUNCTION PdhAddCounterA(PDH_HQUERY hQuery,LPCSTR szFullCounterPath,DWORD_PTR dwUserData,PDH_HCOUNTER *phCounter);
+ PDH_FUNCTION PdhRemoveCounter(PDH_HCOUNTER hCounter);
+ PDH_FUNCTION PdhCollectQueryData(PDH_HQUERY hQuery);
+ PDH_FUNCTION PdhCloseQuery(PDH_HQUERY hQuery);
+ PDH_FUNCTION PdhGetFormattedCounterValue(PDH_HCOUNTER hCounter,DWORD dwFormat,LPDWORD lpdwType,PPDH_FMT_COUNTERVALUE pValue);
+ PDH_FUNCTION PdhGetFormattedCounterArrayA(PDH_HCOUNTER hCounter,DWORD dwFormat,LPDWORD lpdwBufferSize,LPDWORD lpdwItemCount,PPDH_FMT_COUNTERVALUE_ITEM_A ItemBuffer);
+ PDH_FUNCTION PdhGetFormattedCounterArrayW(PDH_HCOUNTER hCounter,DWORD dwFormat,LPDWORD lpdwBufferSize,LPDWORD lpdwItemCount,PPDH_FMT_COUNTERVALUE_ITEM_W ItemBuffer);
+
+#define PDH_FMT_RAW ((DWORD) 0x00000010)
+#define PDH_FMT_ANSI ((DWORD) 0x00000020)
+#define PDH_FMT_UNICODE ((DWORD) 0x00000040)
+#define PDH_FMT_LONG ((DWORD) 0x00000100)
+#define PDH_FMT_DOUBLE ((DWORD) 0x00000200)
+#define PDH_FMT_LARGE ((DWORD) 0x00000400)
+#define PDH_FMT_NOSCALE ((DWORD) 0x00001000)
+#define PDH_FMT_1000 ((DWORD) 0x00002000)
+#define PDH_FMT_NODATA ((DWORD) 0x00004000)
+#define PDH_FMT_NOCAP100 ((DWORD) 0x00008000)
+#define PERF_DETAIL_COSTLY ((DWORD) 0x00010000)
+#define PERF_DETAIL_STANDARD ((DWORD) 0x0000FFFF)
+
+ PDH_FUNCTION PdhGetRawCounterValue(PDH_HCOUNTER hCounter,LPDWORD lpdwType,PPDH_RAW_COUNTER pValue);
+ PDH_FUNCTION PdhGetRawCounterArrayA(PDH_HCOUNTER hCounter,LPDWORD lpdwBufferSize,LPDWORD lpdwItemCount,PPDH_RAW_COUNTER_ITEM_A ItemBuffer);
+ PDH_FUNCTION PdhGetRawCounterArrayW(PDH_HCOUNTER hCounter,LPDWORD lpdwBufferSize,LPDWORD lpdwItemCount,PPDH_RAW_COUNTER_ITEM_W ItemBuffer);
+ PDH_FUNCTION PdhCalculateCounterFromRawValue(PDH_HCOUNTER hCounter,DWORD dwFormat,PPDH_RAW_COUNTER rawValue1,PPDH_RAW_COUNTER rawValue2,PPDH_FMT_COUNTERVALUE fmtValue);
+ PDH_FUNCTION PdhComputeCounterStatistics(PDH_HCOUNTER hCounter,DWORD dwFormat,DWORD dwFirstEntry,DWORD dwNumEntries,PPDH_RAW_COUNTER lpRawValueArray,PPDH_STATISTICS data);
+ PDH_FUNCTION PdhGetCounterInfoW(PDH_HCOUNTER hCounter,BOOLEAN bRetrieveExplainText,LPDWORD pdwBufferSize,PPDH_COUNTER_INFO_W lpBuffer);
+ PDH_FUNCTION PdhGetCounterInfoA(PDH_HCOUNTER hCounter,BOOLEAN bRetrieveExplainText,LPDWORD pdwBufferSize,PPDH_COUNTER_INFO_A lpBuffer);
+
+#define PDH_MAX_SCALE (7L)
+#define PDH_MIN_SCALE (-7L)
+
+ PDH_FUNCTION PdhSetCounterScaleFactor(PDH_HCOUNTER hCounter,LONG lFactor);
+ PDH_FUNCTION PdhConnectMachineW(LPCWSTR szMachineName);
+ PDH_FUNCTION PdhConnectMachineA(LPCSTR szMachineName);
+ PDH_FUNCTION PdhEnumMachinesW(LPCWSTR szDataSource,LPWSTR mszMachineList,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhEnumMachinesA(LPCSTR szDataSource,LPSTR mszMachineList,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhEnumObjectsW(LPCWSTR szDataSource,LPCWSTR szMachineName,LPWSTR mszObjectList,LPDWORD pcchBufferSize,DWORD dwDetailLevel,WINBOOL bRefresh);
+ PDH_FUNCTION PdhEnumObjectsA(LPCSTR szDataSource,LPCSTR szMachineName,LPSTR mszObjectList,LPDWORD pcchBufferSize,DWORD dwDetailLevel,WINBOOL bRefresh);
+ PDH_FUNCTION PdhEnumObjectItemsW(LPCWSTR szDataSource,LPCWSTR szMachineName,LPCWSTR szObjectName,LPWSTR mszCounterList,LPDWORD pcchCounterListLength,LPWSTR mszInstanceList,LPDWORD pcchInstanceListLength,DWORD dwDetailLevel,DWORD dwFlags);
+ PDH_FUNCTION PdhEnumObjectItemsA(LPCSTR szDataSource,LPCSTR szMachineName,LPCSTR szObjectName,LPSTR mszCounterList,LPDWORD pcchCounterListLength,LPSTR mszInstanceList,LPDWORD pcchInstanceListLength,DWORD dwDetailLevel,DWORD dwFlags);
+
+#define PDH_OBJECT_HAS_INSTANCES ((DWORD) 0x00000001)
+
+ PDH_FUNCTION PdhMakeCounterPathW(PPDH_COUNTER_PATH_ELEMENTS_W pCounterPathElements,LPWSTR szFullPathBuffer,LPDWORD pcchBufferSize,DWORD dwFlags);
+ PDH_FUNCTION PdhMakeCounterPathA(PPDH_COUNTER_PATH_ELEMENTS_A pCounterPathElements,LPSTR szFullPathBuffer,LPDWORD pcchBufferSize,DWORD dwFlags);
+ PDH_FUNCTION PdhParseCounterPathW(LPCWSTR szFullPathBuffer,PPDH_COUNTER_PATH_ELEMENTS_W pCounterPathElements,LPDWORD pdwBufferSize,DWORD dwFlags);
+ PDH_FUNCTION PdhParseCounterPathA(LPCSTR szFullPathBuffer,PPDH_COUNTER_PATH_ELEMENTS_A pCounterPathElements,LPDWORD pdwBufferSize,DWORD dwFlags);
+
+#define PDH_PATH_WBEM_RESULT ((DWORD) 0x00000001)
+#define PDH_PATH_WBEM_INPUT ((DWORD) 0x00000002)
+
+#define PDH_PATH_LANG_FLAGS(LangId,Flags) ((DWORD)(((LangId & 0x0000FFFF) << 16) | (Flags & 0x0000FFFF)))
+
+ PDH_FUNCTION PdhParseInstanceNameW(LPCWSTR szInstanceString,LPWSTR szInstanceName,LPDWORD pcchInstanceNameLength,LPWSTR szParentName,LPDWORD pcchParentNameLength,LPDWORD lpIndex);
+ PDH_FUNCTION PdhParseInstanceNameA(LPCSTR szInstanceString,LPSTR szInstanceName,LPDWORD pcchInstanceNameLength,LPSTR szParentName,LPDWORD pcchParentNameLength,LPDWORD lpIndex);
+ PDH_FUNCTION PdhValidatePathW(LPCWSTR szFullPathBuffer);
+ PDH_FUNCTION PdhValidatePathA(LPCSTR szFullPathBuffer);
+ PDH_FUNCTION PdhGetDefaultPerfObjectW(LPCWSTR szDataSource,LPCWSTR szMachineName,LPWSTR szDefaultObjectName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfObjectA(LPCSTR szDataSource,LPCSTR szMachineName,LPSTR szDefaultObjectName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfCounterW(LPCWSTR szDataSource,LPCWSTR szMachineName,LPCWSTR szObjectName,LPWSTR szDefaultCounterName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfCounterA(LPCSTR szDataSource,LPCSTR szMachineName,LPCSTR szObjectName,LPSTR szDefaultCounterName,LPDWORD pcchBufferSize);
+
+ typedef PDH_STATUS (WINAPI *CounterPathCallBack)(DWORD_PTR);
+
+ typedef struct _BrowseDlgConfig_HW {
+ DWORD bIncludeInstanceIndex : 1,bSingleCounterPerAdd : 1,bSingleCounterPerDialog : 1,bLocalCountersOnly : 1,bWildCardInstances : 1,bHideDetailBox : 1,bInitializePath : 1,bDisableMachineSelection : 1,bIncludeCostlyObjects : 1,bShowObjectBrowser : 1,bReserved : 22;
+ HWND hWndOwner;
+ PDH_HLOG hDataSource;
+ LPWSTR szReturnPathBuffer;
+ DWORD cchReturnPathLength;
+ CounterPathCallBack pCallBack;
+ DWORD_PTR dwCallBackArg;
+ PDH_STATUS CallBackStatus;
+ DWORD dwDefaultDetailLevel;
+ LPWSTR szDialogBoxCaption;
+ } PDH_BROWSE_DLG_CONFIG_HW,*PPDH_BROWSE_DLG_CONFIG_HW;
+
+ typedef struct _BrowseDlgConfig_HA {
+ DWORD bIncludeInstanceIndex : 1,bSingleCounterPerAdd : 1,bSingleCounterPerDialog : 1,bLocalCountersOnly : 1,bWildCardInstances : 1,bHideDetailBox : 1,bInitializePath : 1,bDisableMachineSelection : 1,bIncludeCostlyObjects : 1,bShowObjectBrowser : 1,bReserved:22;
+ HWND hWndOwner;
+ PDH_HLOG hDataSource;
+ LPSTR szReturnPathBuffer;
+ DWORD cchReturnPathLength;
+ CounterPathCallBack pCallBack;
+ DWORD_PTR dwCallBackArg;
+ PDH_STATUS CallBackStatus;
+ DWORD dwDefaultDetailLevel;
+ LPSTR szDialogBoxCaption;
+ } PDH_BROWSE_DLG_CONFIG_HA,*PPDH_BROWSE_DLG_CONFIG_HA;
+
+ typedef struct _BrowseDlgConfig_W {
+ DWORD bIncludeInstanceIndex : 1,bSingleCounterPerAdd : 1,bSingleCounterPerDialog : 1,bLocalCountersOnly : 1,bWildCardInstances : 1,bHideDetailBox : 1,bInitializePath : 1,bDisableMachineSelection : 1,bIncludeCostlyObjects : 1,bShowObjectBrowser : 1,bReserved:22;
+ HWND hWndOwner;
+ LPWSTR szDataSource;
+ LPWSTR szReturnPathBuffer;
+ DWORD cchReturnPathLength;
+ CounterPathCallBack pCallBack;
+ DWORD_PTR dwCallBackArg;
+ PDH_STATUS CallBackStatus;
+ DWORD dwDefaultDetailLevel;
+ LPWSTR szDialogBoxCaption;
+ } PDH_BROWSE_DLG_CONFIG_W,*PPDH_BROWSE_DLG_CONFIG_W;
+
+ typedef struct _BrowseDlgConfig_A {
+ DWORD bIncludeInstanceIndex : 1,bSingleCounterPerAdd : 1,bSingleCounterPerDialog : 1,bLocalCountersOnly : 1,bWildCardInstances : 1,bHideDetailBox : 1,bInitializePath : 1,bDisableMachineSelection : 1,bIncludeCostlyObjects : 1,bShowObjectBrowser : 1,bReserved:22;
+ HWND hWndOwner;
+ LPSTR szDataSource;
+ LPSTR szReturnPathBuffer;
+ DWORD cchReturnPathLength;
+ CounterPathCallBack pCallBack;
+ DWORD_PTR dwCallBackArg;
+ PDH_STATUS CallBackStatus;
+ DWORD dwDefaultDetailLevel;
+ LPSTR szDialogBoxCaption;
+ } PDH_BROWSE_DLG_CONFIG_A,*PPDH_BROWSE_DLG_CONFIG_A;
+
+ PDH_FUNCTION PdhBrowseCountersW(PPDH_BROWSE_DLG_CONFIG_W pBrowseDlgData);
+ PDH_FUNCTION PdhBrowseCountersA(PPDH_BROWSE_DLG_CONFIG_A pBrowseDlgData);
+ PDH_FUNCTION PdhExpandCounterPathW(LPCWSTR szWildCardPath,LPWSTR mszExpandedPathList,LPDWORD pcchPathListLength);
+ PDH_FUNCTION PdhExpandCounterPathA(LPCSTR szWildCardPath,LPSTR mszExpandedPathList,LPDWORD pcchPathListLength);
+ PDH_FUNCTION PdhLookupPerfNameByIndexW(LPCWSTR szMachineName,DWORD dwNameIndex,LPWSTR szNameBuffer,LPDWORD pcchNameBufferSize);
+ PDH_FUNCTION PdhLookupPerfNameByIndexA(LPCSTR szMachineName,DWORD dwNameIndex,LPSTR szNameBuffer,LPDWORD pcchNameBufferSize);
+ PDH_FUNCTION PdhLookupPerfIndexByNameW(LPCWSTR szMachineName,LPCWSTR szNameBuffer,LPDWORD pdwIndex);
+ PDH_FUNCTION PdhLookupPerfIndexByNameA(LPCSTR szMachineName,LPCSTR szNameBuffer,LPDWORD pdwIndex);
+
+#define PDH_NOEXPANDCOUNTERS 1
+#define PDH_NOEXPANDINSTANCES 2
+#define PDH_REFRESHCOUNTERS 4
+
+ PDH_FUNCTION PdhExpandWildCardPathA(LPCSTR szDataSource,LPCSTR szWildCardPath,LPSTR mszExpandedPathList,LPDWORD pcchPathListLength,DWORD dwFlags);
+ PDH_FUNCTION PdhExpandWildCardPathW(LPCWSTR szDataSource,LPCWSTR szWildCardPath,LPWSTR mszExpandedPathList,LPDWORD pcchPathListLength,DWORD dwFlags);
+
+#define PDH_LOG_READ_ACCESS ((DWORD) 0x00010000)
+#define PDH_LOG_WRITE_ACCESS ((DWORD) 0x00020000)
+#define PDH_LOG_UPDATE_ACCESS ((DWORD) 0x00040000)
+#define PDH_LOG_ACCESS_MASK ((DWORD) 0x000F0000)
+
+#define PDH_LOG_CREATE_NEW ((DWORD) 0x00000001)
+#define PDH_LOG_CREATE_ALWAYS ((DWORD) 0x00000002)
+#define PDH_LOG_OPEN_ALWAYS ((DWORD) 0x00000003)
+#define PDH_LOG_OPEN_EXISTING ((DWORD) 0x00000004)
+#define PDH_LOG_CREATE_MASK ((DWORD) 0x0000000F)
+
+#define PDH_LOG_OPT_USER_STRING ((DWORD) 0x01000000)
+#define PDH_LOG_OPT_CIRCULAR ((DWORD) 0x02000000)
+#define PDH_LOG_OPT_MAX_IS_BYTES ((DWORD) 0x04000000)
+#define PDH_LOG_OPT_APPEND ((DWORD) 0x08000000)
+#define PDH_LOG_OPT_MASK ((DWORD) 0x0F000000)
+
+#define PDH_LOG_TYPE_UNDEFINED 0
+#define PDH_LOG_TYPE_CSV 1
+#define PDH_LOG_TYPE_TSV 2
+
+#define PDH_LOG_TYPE_TRACE_KERNEL 4
+#define PDH_LOG_TYPE_TRACE_GENERIC 5
+#define PDH_LOG_TYPE_PERFMON 6
+#define PDH_LOG_TYPE_SQL 7
+#define PDH_LOG_TYPE_BINARY 8
+
+ PDH_FUNCTION PdhOpenLogW(LPCWSTR szLogFileName,DWORD dwAccessFlags,LPDWORD lpdwLogType,PDH_HQUERY hQuery,DWORD dwMaxSize,LPCWSTR szUserCaption,PDH_HLOG *phLog);
+ PDH_FUNCTION PdhOpenLogA(LPCSTR szLogFileName,DWORD dwAccessFlags,LPDWORD lpdwLogType,PDH_HQUERY hQuery,DWORD dwMaxSize,LPCSTR szUserCaption,PDH_HLOG *phLog);
+ PDH_FUNCTION PdhUpdateLogW(PDH_HLOG hLog,LPCWSTR szUserString);
+ PDH_FUNCTION PdhUpdateLogA(PDH_HLOG hLog,LPCSTR szUserString);
+ PDH_FUNCTION PdhUpdateLogFileCatalog(PDH_HLOG hLog);
+ PDH_FUNCTION PdhGetLogFileSize(PDH_HLOG hLog,LONGLONG *llSize);
+ PDH_FUNCTION PdhCloseLog(PDH_HLOG hLog,DWORD dwFlags);
+
+#define PDH_FLAGS_CLOSE_QUERY ((DWORD) 0x00000001)
+#define PDH_FLAGS_FILE_BROWSER_ONLY ((DWORD) 0x00000001)
+
+ PDH_FUNCTION PdhSelectDataSourceW(HWND hWndOwner,DWORD dwFlags,LPWSTR szDataSource,LPDWORD pcchBufferLength);
+ PDH_FUNCTION PdhSelectDataSourceA(HWND hWndOwner,DWORD dwFlags,LPSTR szDataSource,LPDWORD pcchBufferLength);
+ WINBOOL PdhIsRealTimeQuery(PDH_HQUERY hQuery);
+ PDH_FUNCTION PdhSetQueryTimeRange(PDH_HQUERY hQuery,PPDH_TIME_INFO pInfo);
+ PDH_FUNCTION PdhGetDataSourceTimeRangeW(LPCWSTR szDataSource,LPDWORD pdwNumEntries,PPDH_TIME_INFO pInfo,LPDWORD pdwBufferSize);
+ PDH_FUNCTION PdhGetDataSourceTimeRangeA(LPCSTR szDataSource,LPDWORD pdwNumEntries,PPDH_TIME_INFO pInfo,LPDWORD dwBufferSize);
+ PDH_FUNCTION PdhCollectQueryDataEx(PDH_HQUERY hQuery,DWORD dwIntervalTime,HANDLE hNewDataEvent);
+ PDH_FUNCTION PdhFormatFromRawValue(DWORD dwCounterType,DWORD dwFormat,LONGLONG *pTimeBase,PPDH_RAW_COUNTER pRawValue1,PPDH_RAW_COUNTER pRawValue2,PPDH_FMT_COUNTERVALUE pFmtValue);
+ PDH_FUNCTION PdhGetCounterTimeBase(PDH_HCOUNTER hCounter,LONGLONG *pTimeBase);
+ PDH_FUNCTION PdhReadRawLogRecord(PDH_HLOG hLog,FILETIME ftRecord,PPDH_RAW_LOG_RECORD pRawLogRecord,LPDWORD pdwBufferLength);
+
+#define DATA_SOURCE_REGISTRY ((DWORD) 0x00000001)
+#define DATA_SOURCE_LOGFILE ((DWORD) 0x00000002)
+#define DATA_SOURCE_WBEM ((DWORD) 0x00000004)
+
+ PDH_FUNCTION PdhSetDefaultRealTimeDataSource(DWORD dwDataSourceId);
+ PDH_FUNCTION PdhBindInputDataSourceW(PDH_HLOG *phDataSource,LPCWSTR LogFileNameList);
+ PDH_FUNCTION PdhBindInputDataSourceA(PDH_HLOG *phDataSource,LPCSTR LogFileNameList);
+ PDH_FUNCTION PdhOpenQueryH(PDH_HLOG hDataSource,DWORD_PTR dwUserData,PDH_HQUERY *phQuery);
+ PDH_FUNCTION PdhEnumMachinesHW(PDH_HLOG hDataSource,LPWSTR mszMachineList,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhEnumMachinesHA(PDH_HLOG hDataSource,LPSTR mszMachineList,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhEnumObjectsHW(PDH_HLOG hDataSource,LPCWSTR szMachineName,LPWSTR mszObjectList,LPDWORD pcchBufferSize,DWORD dwDetailLevel,WINBOOL bRefresh);
+ PDH_FUNCTION PdhEnumObjectsHA(PDH_HLOG hDataSource,LPCSTR szMachineName,LPSTR mszObjectList,LPDWORD pcchBufferSize,DWORD dwDetailLevel,WINBOOL bRefresh);
+ PDH_FUNCTION PdhEnumObjectItemsHW(PDH_HLOG hDataSource,LPCWSTR szMachineName,LPCWSTR szObjectName,LPWSTR mszCounterList,LPDWORD pcchCounterListLength,LPWSTR mszInstanceList,LPDWORD pcchInstanceListLength,DWORD dwDetailLevel,DWORD dwFlags);
+ PDH_FUNCTION PdhEnumObjectItemsHA(PDH_HLOG hDataSource,LPCSTR szMachineName,LPCSTR szObjectName,LPSTR mszCounterList,LPDWORD pcchCounterListLength,LPSTR mszInstanceList,LPDWORD pcchInstanceListLength,DWORD dwDetailLevel,DWORD dwFlags);
+ PDH_FUNCTION PdhExpandWildCardPathHW(PDH_HLOG hDataSource,LPCWSTR szWildCardPath,LPWSTR mszExpandedPathList,LPDWORD pcchPathListLength,DWORD dwFlags);
+ PDH_FUNCTION PdhExpandWildCardPathHA(PDH_HLOG hDataSource,LPCSTR szWildCardPath,LPSTR mszExpandedPathList,LPDWORD pcchPathListLength,DWORD dwFlags);
+ PDH_FUNCTION PdhGetDataSourceTimeRangeH(PDH_HLOG hDataSource,LPDWORD pdwNumEntries,PPDH_TIME_INFO pInfo,LPDWORD pdwBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfObjectHW(PDH_HLOG hDataSource,LPCWSTR szMachineName,LPWSTR szDefaultObjectName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfObjectHA(PDH_HLOG hDataSource,LPCSTR szMachineName,LPSTR szDefaultObjectName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfCounterHW(PDH_HLOG hDataSource,LPCWSTR szMachineName,LPCWSTR szObjectName,LPWSTR szDefaultCounterName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhGetDefaultPerfCounterHA(PDH_HLOG hDataSource,LPCSTR szMachineName,LPCSTR szObjectName,LPSTR szDefaultCounterName,LPDWORD pcchBufferSize);
+ PDH_FUNCTION PdhBrowseCountersHW(PPDH_BROWSE_DLG_CONFIG_HW pBrowseDlgData);
+ PDH_FUNCTION PdhBrowseCountersHA(PPDH_BROWSE_DLG_CONFIG_HA pBrowseDlgData);
+ PDH_FUNCTION PdhVerifySQLDBW(LPCWSTR szDataSource);
+ PDH_FUNCTION PdhVerifySQLDBA(LPCSTR szDataSource);
+ PDH_FUNCTION PdhCreateSQLTablesW(LPCWSTR szDataSource);
+ PDH_FUNCTION PdhCreateSQLTablesA(LPCSTR szDataSource);
+ PDH_FUNCTION PdhEnumLogSetNamesW(LPCWSTR szDataSource,LPWSTR mszDataSetNameList,LPDWORD pcchBufferLength);
+ PDH_FUNCTION PdhEnumLogSetNamesA(LPCSTR szDataSource,LPSTR mszDataSetNameList,LPDWORD pcchBufferLength);
+ PDH_FUNCTION PdhGetLogSetGUID(PDH_HLOG hLog,GUID *pGuid,int *pRunId);
+ PDH_FUNCTION PdhSetLogSetRunID(PDH_HLOG hLog,int RunId);
+
+#if defined(UNICODE)
+#ifndef _UNICODE
+#define _UNICODE
+#endif
+#endif
+
+#if defined(_UNICODE)
+#if !defined(UNICODE)
+#define UNICODE
+#endif
+#endif
+
+#define PDH_COUNTER_INFO __MINGW_NAME_UAW(PDH_COUNTER_INFO)
+#define PPDH_COUNTER_INFO __MINGW_NAME_UAW(PPDH_COUNTER_INFO)
+#define PDH_COUNTER_PATH_ELEMENTS __MINGW_NAME_UAW(PDH_COUNTER_PATH_ELEMENTS)
+#define PPDH_COUNTER_PATH_ELEMENTS __MINGW_NAME_UAW(PPDH_COUNTER_PATH_ELEMENTS)
+#define PDH_BROWSE_DLG_CONFIG __MINGW_NAME_UAW(PDH_BROWSE_DLG_CONFIG)
+#define PPDH_BROWSE_DLG_CONFIG __MINGW_NAME_UAW(PPDH_BROWSE_DLG_CONFIG)
+#define PDH_FMT_COUNTERVALUE_ITEM __MINGW_NAME_UAW(PDH_FMT_COUNTERVALUE_ITEM)
+#define PPDH_FMT_COUNTERVALUE_ITEM __MINGW_NAME_UAW(PPDH_FMT_COUNTERVALUE_ITEM)
+#define PDH_RAW_COUNTER_ITEM __MINGW_NAME_UAW(PDH_RAW_COUNTER_ITEM)
+#define PPDH_RAW_COUNTER_ITEM __MINGW_NAME_UAW(PPDH_RAW_COUNTER_ITEM)
+#define PDH_LOG_SERVICE_QUERY_INFO __MINGW_NAME_UAW(PDH_LOG_SERVICE_QUERY_INFO)
+#define PPDH_LOG_SERVICE_QUERY_INFO __MINGW_NAME_UAW(PPDH_LOG_SERVICE_QUERY_INFO)
+
+#define PDH_BROWSE_DLG_CONFIG_H __MINGW_NAME_AW(PDH_BROWSE_DLG_CONFIG_H)
+#define PPDH_BROWSE_DLG_CONFIG_H __MINGW_NAME_AW(PPDH_BROWSE_DLG_CONFIG_H)
+
+#define PdhOpenQuery __MINGW_NAME_AW(PdhOpenQuery)
+#define PdhAddCounter __MINGW_NAME_AW(PdhAddCounter)
+#define PdhGetCounterInfo __MINGW_NAME_AW(PdhGetCounterInfo)
+#define PdhConnectMachine __MINGW_NAME_AW(PdhConnectMachine)
+#define PdhEnumMachines __MINGW_NAME_AW(PdhEnumMachines)
+#define PdhEnumObjects __MINGW_NAME_AW(PdhEnumObjects)
+#define PdhEnumObjectItems __MINGW_NAME_AW(PdhEnumObjectItems)
+#define PdhMakeCounterPath __MINGW_NAME_AW(PdhMakeCounterPath)
+#define PdhParseCounterPath __MINGW_NAME_AW(PdhParseCounterPath)
+#define PdhParseInstanceName __MINGW_NAME_AW(PdhParseInstanceName)
+#define PdhValidatePath __MINGW_NAME_AW(PdhValidatePath)
+#define PdhGetDefaultPerfObject __MINGW_NAME_AW(PdhGetDefaultPerfObject)
+#define PdhGetDefaultPerfCounter __MINGW_NAME_AW(PdhGetDefaultPerfCounter)
+#define PdhBrowseCounters __MINGW_NAME_AW(PdhBrowseCounters)
+#define PdhBrowseCountersH __MINGW_NAME_AW(PdhBrowseCountersH)
+#define PdhExpandCounterPath __MINGW_NAME_AW(PdhExpandCounterPath)
+#define PdhGetFormattedCounterArray __MINGW_NAME_AW(PdhGetFormattedCounterArray)
+#define PdhGetRawCounterArray __MINGW_NAME_AW(PdhGetRawCounterArray)
+#define PdhLookupPerfNameByIndex __MINGW_NAME_AW(PdhLookupPerfNameByIndex)
+#define PdhLookupPerfIndexByName __MINGW_NAME_AW(PdhLookupPerfIndexByName)
+#define PdhOpenLog __MINGW_NAME_AW(PdhOpenLog)
+#define PdhUpdateLog __MINGW_NAME_AW(PdhUpdateLog)
+#define PdhSelectDataSource __MINGW_NAME_AW(PdhSelectDataSource)
+#define PdhGetDataSourceTimeRange __MINGW_NAME_AW(PdhGetDataSourceTimeRange)
+#define PdhLogServiceControl __MINGW_NAME_AW(PdhLogServiceControl)
+#define PdhLogServiceQuery __MINGW_NAME_AW(PdhLogServiceQuery)
+#define PdhExpandWildCardPath __MINGW_NAME_AW(PdhExpandWildCardPath)
+#define PdhBindInputDataSource __MINGW_NAME_AW(PdhBindInputDataSource)
+#define PdhEnumMachinesH __MINGW_NAME_AW(PdhEnumMachinesH)
+#define PdhEnumObjectsH __MINGW_NAME_AW(PdhEnumObjectsH)
+#define PdhEnumObjectItemsH __MINGW_NAME_AW(PdhEnumObjectItemsH)
+#define PdhExpandWildCardPathH __MINGW_NAME_AW(PdhExpandWildCardPathH)
+#define PdhGetDefaultPerfObjectH __MINGW_NAME_AW(PdhGetDefaultPerfObjectH)
+#define PdhGetDefaultPerfCounterH __MINGW_NAME_AW(PdhGetDefaultPerfCounterH)
+#define PdhEnumLogSetNames __MINGW_NAME_AW(PdhEnumLogSetNames)
+#define PdhCreateSQLTables __MINGW_NAME_AW(PdhCreateSQLTables)
+#define PdhVerifySQLDB __MINGW_NAME_AW(PdhVerifySQLDB)
+
+#if (_WIN32_WINNT >= 0x0600)
+PDH_STATUS PdhAddEnglishCounterA(
+ PDH_HQUERY hQuery,
+ LPCSTR szFullCounterPath,
+ DWORD_PTR dwUserData,
+ PDH_HCOUNTER *phCounter
+);
+
+PDH_STATUS PdhAddEnglishCounterW(
+ PDH_HQUERY hQuery,
+ LPCWSTR szFullCounterPath,
+ DWORD_PTR dwUserData,
+ PDH_HCOUNTER *phCounter
+);
+
+#define PdhAddEnglishCounter __MINGW_NAME_AW(PdhAddEnglishCounter)
+
+PDH_STATUS PdhCollectQueryDataWithTime(
+ PDH_HQUERY hQuery,
+ LONGLONG *pllTimeStamp
+);
+
+PDH_STATUS PdhValidatePathExA(
+ PDH_HLOG hDataSource,
+ LPCSTR szFullPathBuffer
+);
+
+PDH_STATUS PdhValidatePathExA(
+ PDH_HLOG hDataSource,
+ LPCWSTR szFullPathBuffer
+);
+
+#define PdhValidatePathEx __MINGW_NAME_AW(PdhValidatePathEx)
+
+#endif /*(_WIN32_WINNT >= 0x0600)*/
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/win32ctl/include/pdhmsg.h b/src/win32ctl/include/pdhmsg.h
new file mode 100644
index 0000000..fc3b42a
--- /dev/null
+++ b/src/win32ctl/include/pdhmsg.h
@@ -0,0 +1,101 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _PDH_MSG_H_
+#define _PDH_MSG_H_
+
+#define STATUS_SEVERITY_WARNING 0x2
+#define STATUS_SEVERITY_SUCCESS 0x0
+#define STATUS_SEVERITY_INFORMATIONAL 0x1
+#define STATUS_SEVERITY_ERROR 0x3
+
+#define PDH_CSTATUS_VALID_DATA ((DWORD)0x00000000L)
+#define PDH_CSTATUS_NEW_DATA ((DWORD)0x00000001L)
+#define PDH_CSTATUS_NO_MACHINE ((DWORD)0x800007D0L)
+#define PDH_CSTATUS_NO_INSTANCE ((DWORD)0x800007D1L)
+#define PDH_MORE_DATA ((DWORD)0x800007D2L)
+#define PDH_CSTATUS_ITEM_NOT_VALIDATED ((DWORD)0x800007D3L)
+#define PDH_RETRY ((DWORD)0x800007D4L)
+#define PDH_NO_DATA ((DWORD)0x800007D5L)
+#define PDH_CALC_NEGATIVE_DENOMINATOR ((DWORD)0x800007D6L)
+#define PDH_CALC_NEGATIVE_TIMEBASE ((DWORD)0x800007D7L)
+#define PDH_CALC_NEGATIVE_VALUE ((DWORD)0x800007D8L)
+#define PDH_DIALOG_CANCELLED ((DWORD)0x800007D9L)
+#define PDH_END_OF_LOG_FILE ((DWORD)0x800007DAL)
+#define PDH_ASYNC_QUERY_TIMEOUT ((DWORD)0x800007DBL)
+#define PDH_CANNOT_SET_DEFAULT_REALTIME_DATASOURCE ((DWORD)0x800007DCL)
+#define PDH_CSTATUS_NO_OBJECT ((DWORD)0xC0000BB8L)
+#define PDH_CSTATUS_NO_COUNTER ((DWORD)0xC0000BB9L)
+#define PDH_CSTATUS_INVALID_DATA ((DWORD)0xC0000BBAL)
+#define PDH_MEMORY_ALLOCATION_FAILURE ((DWORD)0xC0000BBBL)
+#define PDH_INVALID_HANDLE ((DWORD)0xC0000BBCL)
+#define PDH_INVALID_ARGUMENT ((DWORD)0xC0000BBDL)
+#define PDH_FUNCTION_NOT_FOUND ((DWORD)0xC0000BBEL)
+#define PDH_CSTATUS_NO_COUNTERNAME ((DWORD)0xC0000BBFL)
+#define PDH_CSTATUS_BAD_COUNTERNAME ((DWORD)0xC0000BC0L)
+#define PDH_INVALID_BUFFER ((DWORD)0xC0000BC1L)
+#define PDH_INSUFFICIENT_BUFFER ((DWORD)0xC0000BC2L)
+#define PDH_CANNOT_CONNECT_MACHINE ((DWORD)0xC0000BC3L)
+#define PDH_INVALID_PATH ((DWORD)0xC0000BC4L)
+#define PDH_INVALID_INSTANCE ((DWORD)0xC0000BC5L)
+#define PDH_INVALID_DATA ((DWORD)0xC0000BC6L)
+#define PDH_NO_DIALOG_DATA ((DWORD)0xC0000BC7L)
+#define PDH_CANNOT_READ_NAME_STRINGS ((DWORD)0xC0000BC8L)
+#define PDH_LOG_FILE_CREATE_ERROR ((DWORD)0xC0000BC9L)
+#define PDH_LOG_FILE_OPEN_ERROR ((DWORD)0xC0000BCAL)
+#define PDH_LOG_TYPE_NOT_FOUND ((DWORD)0xC0000BCBL)
+#define PDH_NO_MORE_DATA ((DWORD)0xC0000BCCL)
+#define PDH_ENTRY_NOT_IN_LOG_FILE ((DWORD)0xC0000BCDL)
+#define PDH_DATA_SOURCE_IS_LOG_FILE ((DWORD)0xC0000BCEL)
+#define PDH_DATA_SOURCE_IS_REAL_TIME ((DWORD)0xC0000BCFL)
+#define PDH_UNABLE_READ_LOG_HEADER ((DWORD)0xC0000BD0L)
+#define PDH_FILE_NOT_FOUND ((DWORD)0xC0000BD1L)
+#define PDH_FILE_ALREADY_EXISTS ((DWORD)0xC0000BD2L)
+#define PDH_NOT_IMPLEMENTED ((DWORD)0xC0000BD3L)
+#define PDH_STRING_NOT_FOUND ((DWORD)0xC0000BD4L)
+#define PDH_UNABLE_MAP_NAME_FILES ((DWORD)0x80000BD5L)
+#define PDH_UNKNOWN_LOG_FORMAT ((DWORD)0xC0000BD6L)
+#define PDH_UNKNOWN_LOGSVC_COMMAND ((DWORD)0xC0000BD7L)
+#define PDH_LOGSVC_QUERY_NOT_FOUND ((DWORD)0xC0000BD8L)
+#define PDH_LOGSVC_NOT_OPENED ((DWORD)0xC0000BD9L)
+#define PDH_WBEM_ERROR ((DWORD)0xC0000BDAL)
+#define PDH_ACCESS_DENIED ((DWORD)0xC0000BDBL)
+#define PDH_LOG_FILE_TOO_SMALL ((DWORD)0xC0000BDCL)
+#define PDH_INVALID_DATASOURCE ((DWORD)0xC0000BDDL)
+#define PDH_INVALID_SQLDB ((DWORD)0xC0000BDEL)
+#define PDH_NO_COUNTERS ((DWORD)0xC0000BDFL)
+#define PDH_SQL_ALLOC_FAILED ((DWORD)0xC0000BE0L)
+#define PDH_SQL_ALLOCCON_FAILED ((DWORD)0xC0000BE1L)
+#define PDH_SQL_EXEC_DIRECT_FAILED ((DWORD)0xC0000BE2L)
+#define PDH_SQL_FETCH_FAILED ((DWORD)0xC0000BE3L)
+#define PDH_SQL_ROWCOUNT_FAILED ((DWORD)0xC0000BE4L)
+#define PDH_SQL_MORE_RESULTS_FAILED ((DWORD)0xC0000BE5L)
+#define PDH_SQL_CONNECT_FAILED ((DWORD)0xC0000BE6L)
+#define PDH_SQL_BIND_FAILED ((DWORD)0xC0000BE7L)
+#define PDH_CANNOT_CONNECT_WMI_SERVER ((DWORD)0xC0000BE8L)
+#define PDH_PLA_COLLECTION_ALREADY_RUNNING ((DWORD)0xC0000BE9L)
+#define PDH_PLA_ERROR_SCHEDULE_OVERLAP ((DWORD)0xC0000BEAL)
+#define PDH_PLA_COLLECTION_NOT_FOUND ((DWORD)0xC0000BEBL)
+#define PDH_PLA_ERROR_SCHEDULE_ELAPSED ((DWORD)0xC0000BECL)
+#define PDH_PLA_ERROR_NOSTART ((DWORD)0xC0000BEDL)
+#define PDH_PLA_ERROR_ALREADY_EXISTS ((DWORD)0xC0000BEEL)
+#define PDH_PLA_ERROR_TYPE_MISMATCH ((DWORD)0xC0000BEFL)
+#define PDH_PLA_ERROR_FILEPATH ((DWORD)0xC0000BF0L)
+#define PDH_PLA_SERVICE_ERROR ((DWORD)0xC0000BF1L)
+#define PDH_PLA_VALIDATION_ERROR ((DWORD)0xC0000BF2L)
+#define PDH_PLA_VALIDATION_WARNING ((DWORD)0x80000BF3L)
+#define PDH_PLA_ERROR_NAME_TOO_LONG ((DWORD)0xC0000BF4L)
+#define PDH_INVALID_SQL_LOG_FORMAT ((DWORD)0xC0000BF5L)
+#define PDH_COUNTER_ALREADY_IN_QUERY ((DWORD)0xC0000BF6L)
+#define PDH_BINARY_LOG_CORRUPT ((DWORD)0xC0000BF7L)
+#define PDH_LOG_SAMPLE_TOO_SMALL ((DWORD)0xC0000BF8L)
+#define PDH_OS_LATER_VERSION ((DWORD)0xC0000BF9L)
+#define PDH_OS_EARLIER_VERSION ((DWORD)0xC0000BFAL)
+#define PDH_INCORRECT_APPEND_TIME ((DWORD)0xC0000BFBL)
+#define PDH_UNMATCHED_APPEND_COUNTER ((DWORD)0xC0000BFCL)
+#define PDH_SQL_ALTER_DETAIL_FAILED ((DWORD)0xC0000BFDL)
+#define PDH_QUERY_PERF_DATA_TIMEOUT ((DWORD)0xC0000BFEL)
+
+#endif
diff --git a/src/win32ctl/include/pshpack8.h b/src/win32ctl/include/pshpack8.h
new file mode 100644
index 0000000..61f803f
--- /dev/null
+++ b/src/win32ctl/include/pshpack8.h
@@ -0,0 +1,8 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#if !(defined(lint) || defined(RC_INVOKED))
+#pragma pack(push,8)
+#endif
diff --git a/src/win32ctl/include/tdh.h b/src/win32ctl/include/tdh.h
new file mode 100644
index 0000000..745c248
--- /dev/null
+++ b/src/win32ctl/include/tdh.h
@@ -0,0 +1,329 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _INC_TDH
+#define _INC_TDH
+
+/* --- start added by kenj */
+#undef __MINGW_EXTENSION
+#if defined(__GNUC__) || defined(__GNUG__)
+#define __MINGW_EXTENSION __extension__
+#else
+#define __MINGW_EXTENSION
+#endif
+/* --- end added by kenj */
+
+#include <evntprov.h>
+#include <evntcons.h>
+#if (_WIN32_WINNT >= 0x0600)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define __TDHCACHE_ALIGN __attribute__ ((aligned (8)))
+
+typedef enum _EVENT_FIELD_TYPE {
+ EventKeywordInformation = 0,
+ EventLevelInformation = 1,
+ EventChannelInformation = 2,
+ EventTaskInformation = 3,
+ EventOpcodeInformation = 4,
+ EventInformationMax = 5
+} EVENT_FIELD_TYPE;
+
+typedef struct _EVENT_MAP_ENTRY {
+ ULONG OutputOffset;
+ __MINGW_EXTENSION union {
+ ULONG Value;
+ ULONG InputOffset;
+ };
+} __TDHCACHE_ALIGN EVENT_MAP_ENTRY, *PEVENT_MAP_ENTRY;
+
+typedef enum _MAP_VALUETYPE
+{
+ EVENTMAP_ENTRY_VALUETYPE_ULONG = 0,
+ EVENTMAP_ENTRY_VALUETYPE_STRING = 1
+} MAP_VALUETYPE;
+
+typedef enum _MAP_FLAGS {
+ EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP = 1,
+ EVENTMAP_INFO_FLAG_MANIFEST_BITMAP = 2,
+ EVENTMAP_INFO_FLAG_MANIFEST_PATTERNMAP = 4,
+ EVENTMAP_INFO_FLAG_WBEM_VALUEMAP = 8,
+ EVENTMAP_INFO_FLAG_WBEM_BITMAP = 16,
+ EVENTMAP_INFO_FLAG_WBEM_FLAG = 32,
+ EVENTMAP_INFO_FLAG_WBEM_NO_MAP = 64
+} MAP_FLAGS;
+
+typedef struct _EVENT_MAP_INFO {
+ ULONG NameOffset;
+ MAP_FLAGS Flag;
+ ULONG EntryCount;
+ __MINGW_EXTENSION union {
+ MAP_VALUETYPE MapEntryValueType;
+ ULONG FormatStringOffset;
+ };
+ EVENT_MAP_ENTRY MapEntryArray[ANYSIZE_ARRAY];
+} __TDHCACHE_ALIGN EVENT_MAP_INFO, *PEVENT_MAP_INFO;
+
+typedef enum _TDH_IN_TYPE {
+ TDH_INTYPE_NULL,
+ TDH_INTYPE_UNICODESTRING,
+ TDH_INTYPE_ANSISTRING,
+ TDH_INTYPE_INT8,
+ TDH_INTYPE_UINT8,
+ TDH_INTYPE_INT16,
+ TDH_INTYPE_UINT16,
+ TDH_INTYPE_INT32,
+ TDH_INTYPE_UINT32,
+ TDH_INTYPE_INT64,
+ TDH_INTYPE_UINT64,
+ TDH_INTYPE_FLOAT,
+ TDH_INTYPE_DOUBLE,
+ TDH_INTYPE_BOOLEAN,
+ TDH_INTYPE_BINARY,
+ TDH_INTYPE_GUID,
+ TDH_INTYPE_POINTER,
+ TDH_INTYPE_FILETIME,
+ TDH_INTYPE_SYSTEMTIME,
+ TDH_INTYPE_SID,
+ TDH_INTYPE_HEXINT32,
+ TDH_INTYPE_HEXINT64,
+ TDH_INTYPE_COUNTEDSTRING = 300,
+ TDH_INTYPE_COUNTEDANSISTRING,
+ TDH_INTYPE_REVERSEDCOUNTEDSTRING,
+ TDH_INTYPE_REVERSEDCOUNTEDANSISTRING,
+ TDH_INTYPE_NONNULLTERMINATEDSTRING,
+ TDH_INTYPE_NONNULLTERMINATEDANSISTRING,
+ TDH_INTYPE_UNICODECHAR,
+ TDH_INTYPE_ANSICHAR,
+ TDH_INTYPE_SIZET,
+ TDH_INTYPE_HEXDUMP,
+ TDH_INTYPE_WBEMSID
+} TDH_IN_TYPE;
+
+typedef enum _TDH_OUT_TYPE {
+ TDH_OUTTYPE_NULL,
+ TDH_OUTTYPE_STRING,
+ TDH_OUTTYPE_DATETIME,
+ TDH_OUTTYPE_BYTE,
+ TDH_OUTTYPE_UNSIGNEDBYTE,
+ TDH_OUTTYPE_SHORT,
+ TDH_OUTTYPE_UNSIGNEDSHORT,
+ TDH_OUTTYPE_INT,
+ TDH_OUTTYPE_UNSIGNEDINT,
+ TDH_OUTTYPE_LONG,
+ TDH_OUTTYPE_UNSIGNEDLONG,
+ TDH_OUTTYPE_FLOAT,
+ TDH_OUTTYPE_DOUBLE,
+ TDH_OUTTYPE_BOOLEAN,
+ TDH_OUTTYPE_GUID,
+ TDH_OUTTYPE_HEXBINARY,
+ TDH_OUTTYPE_HEXINT8,
+ TDH_OUTTYPE_HEXINT16,
+ TDH_OUTTYPE_HEXINT32,
+ TDH_OUTTYPE_HEXINT64,
+ TDH_OUTTYPE_PID,
+ TDH_OUTTYPE_TID,
+ TDH_OUTTYPE_PORT,
+ TDH_OUTTYPE_IPV4,
+ TDH_OUTTYPE_IPV6,
+ TDH_OUTTYPE_SOCKETADDRESS,
+ TDH_OUTTYPE_CIMDATETIME,
+ TDH_OUTTYPE_ETWTIME,
+ TDH_OUTTYPE_XML,
+ TDH_OUTTYPE_ERRORCODE,
+ TDH_OUTTYPE_WIN32ERROR,
+ TDH_OUTTYPE_NTSTATUS,
+ TDH_OUTTYPE_HRESULT,
+ TDH_OUTTYPE_CULTURE_INSENSITIVE_DATETIME,
+ TDH_OUTTYPE_REDUCEDSTRING = 300,
+ TDH_OUTTYPE_NOPRINT
+} TDH_OUT_TYPE;
+
+typedef enum _PROPERTY_FLAGS {
+ PropertyStruct = 0x1,
+ PropertyParamLength = 0x2,
+ PropertyParamCount = 0x4,
+ PropertyWBEMXmlFragment = 0x8,
+ PropertyParamFixedLength = 0x10
+} PROPERTY_FLAGS;
+
+typedef struct _EVENT_PROPERTY_INFO {
+ PROPERTY_FLAGS Flags;
+ ULONG NameOffset;
+ __MINGW_EXTENSION union {
+ struct {
+ USHORT InType;
+ USHORT OutType;
+ ULONG MapNameOffset;
+ } nonStructType;
+ struct {
+ USHORT StructStartIndex;
+ USHORT NumOfStructMembers;
+ ULONG padding;
+ } structType;
+ };
+ __MINGW_EXTENSION union {
+ USHORT count;
+ USHORT countPropertyIndex;
+ };
+ __MINGW_EXTENSION union {
+ USHORT length;
+ USHORT lengthPropertyIndex;
+ };
+ ULONG Reserved;
+} __TDHCACHE_ALIGN EVENT_PROPERTY_INFO, *PEVENT_PROPERTY_INFO;
+
+typedef enum _DECODING_SOURCE {
+ DecodingSourceXMLFile = 0,
+ DecodingSourceWbem = 1,
+ DecodingSourceWPP = 2
+} DECODING_SOURCE;
+
+typedef enum _TDH_CONTEXT_TYPE {
+ TDH_CONTEXT_WPP_TMFFILE = 0,
+ TDH_CONTEXT_WPP_TMFSEARCHPATH = 1,
+ TDH_CONTEXT_WPP_GMT = 2,
+ TDH_CONTEXT_POINTERSIZE = 3,
+ TDH_CONTEXT_MAXIMUM = 4
+} TDH_CONTEXT_TYPE;
+
+typedef enum _TEMPLATE_FLAGS {
+ TEMPLATE_EVENT_DATA = 1,
+ TEMPLATE_USER_DATA = 2
+} TEMPLATE_FLAGS;
+
+typedef struct _TRACE_EVENT_INFO {
+ GUID ProviderGuid;
+ GUID EventGuid;
+ EVENT_DESCRIPTOR EventDescriptor;
+ DECODING_SOURCE DecodingSource;
+ ULONG ProviderNameOffset;
+ ULONG LevelNameOffset;
+ ULONG ChannelNameOffset;
+ ULONG KeywordsNameOffset;
+ ULONG TaskNameOffset;
+ ULONG OpcodeNameOffset;
+ ULONG EventMessageOffset;
+ ULONG ProviderMessageOffset;
+ ULONG BinaryXMLOffset;
+ ULONG BinaryXMLSize;
+ ULONG ActivityIDNameOffset;
+ ULONG RelatedActivityIDNameOffset;
+ ULONG PropertyCount;
+ ULONG TopLevelPropertyCount;
+ TEMPLATE_FLAGS Flags;
+ EVENT_PROPERTY_INFO EventPropertyInfoArray[ANYSIZE_ARRAY];
+} __TDHCACHE_ALIGN TRACE_EVENT_INFO, *PTRACE_EVENT_INFO;
+
+typedef struct _PROPERTY_DATA_DESCRIPTOR {
+ ULONGLONG PropertyName;
+ ULONG ArrayIndex;
+ ULONG Reserved;
+} __TDHCACHE_ALIGN PROPERTY_DATA_DESCRIPTOR, *PPROPERTY_DATA_DESCRIPTOR;
+
+typedef struct _TRACE_PROVIDER_INFO {
+ GUID ProviderGuid;
+ ULONG SchemaSource;
+ ULONG ProviderNameOffset;
+} __TDHCACHE_ALIGN TRACE_PROVIDER_INFO, *PTRACE_PROVIDER_INFO;
+
+typedef struct _PROVIDER_ENUMERATION_INFO {
+ ULONG NumberOfProviders;
+ ULONG Padding;
+ TRACE_PROVIDER_INFO TraceProviderInfoArray[ANYSIZE_ARRAY];
+} __TDHCACHE_ALIGN PROVIDER_ENUMERATION_INFO, *PPROVIDER_ENUMERATION_INFO;
+
+typedef struct _PROVIDER_FIELD_INFO {
+ ULONG NameOffset;
+ ULONG DescriptionOffset;
+ ULONGLONG Value;
+} __TDHCACHE_ALIGN PROVIDER_FIELD_INFO, *PPROVIDER_FIELD_INFO;
+
+typedef struct _PROVIDER_FIELD_INFOARRAY {
+ ULONG NumberOfElements;
+ EVENT_FIELD_TYPE FieldType;
+ PROVIDER_FIELD_INFO FieldInfoArray[ANYSIZE_ARRAY];
+} __TDHCACHE_ALIGN PROVIDER_FIELD_INFOARRAY, *PPROVIDER_FIELD_INFOARRAY;
+
+typedef struct _TDH_CONTEXT {
+ ULONGLONG ParameterValue;
+ TDH_CONTEXT_TYPE ParameterType;
+ ULONG ParameterSize;
+} __TDHCACHE_ALIGN TDH_CONTEXT, *PTDH_CONTEXT;
+
+ULONG __stdcall TdhEnumerateProviderFieldInformation(
+ LPGUID pGuid,
+ EVENT_FIELD_TYPE EventFieldType,
+ PPROVIDER_FIELD_INFOARRAY pBuffer,
+ ULONG *pBufferSize
+);
+
+ULONG __stdcall TdhEnumerateProviders(
+ PPROVIDER_ENUMERATION_INFO pBuffer,
+ ULONG *pBufferSize
+);
+
+ULONG __stdcall TdhGetEventInformation(
+ PEVENT_RECORD pEvent,
+ ULONG TdhContextCount,
+ PTDH_CONTEXT pTdhContext,
+ PTRACE_EVENT_INFO pBuffer,
+ ULONG *pBufferSize
+);
+
+ULONG __stdcall TdhGetEventMapInformation(
+ PEVENT_RECORD pEvent,
+ LPWSTR pMapName,
+ PEVENT_MAP_INFO pBuffer,
+ ULONG *pBufferSize
+);
+
+ULONG __stdcall TdhGetProperty(
+ PEVENT_RECORD pEvent,
+ ULONG TdhContextCount,
+ PTDH_CONTEXT pTdhContext,
+ ULONG PropertyDataCount,
+ PPROPERTY_DATA_DESCRIPTOR pPropertyData,
+ ULONG BufferSize,
+ PBYTE pBuffer
+);
+
+ULONG __stdcall TdhGetPropertySize(
+ PEVENT_RECORD pEvent,
+ ULONG TdhContextCount,
+ PTDH_CONTEXT pTdhContext,
+ ULONG PropertyDataCount,
+ PPROPERTY_DATA_DESCRIPTOR pPropertyData,
+ ULONG *pPropertySize
+);
+
+ULONG __stdcall TdhQueryProviderFieldInformation(
+ LPGUID pGuid,
+ ULONGLONG EventFieldValue,
+ EVENT_FIELD_TYPE EventFieldType,
+ PPROVIDER_FIELD_INFOARRAY pBuffer,
+ ULONG *pBufferSize
+);
+
+#if (_WIN32_WINNT >= 0x0601)
+typedef struct _PROVIDER_FILTER_INFO {
+ UCHAR Id;
+ UCHAR Version;
+ ULONG MessageOffset;
+ ULONG Reserved;
+ ULONG PropertyCount;
+ EVENT_PROPERTY_INFO EventPropertyInfoArray[ANYSIZE_ARRAY];
+} PROVIDER_FILTER_INFO, *PPROVIDER_FILTER_INFO;
+#endif /*(_WIN32_WINNT >= 0x0601)*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /*(_WIN32_WINNT >= 0x0600)*/
+#endif /*_INC_TDH*/
diff --git a/src/win32ctl/include/tdhmsg.h b/src/win32ctl/include/tdhmsg.h
new file mode 100644
index 0000000..813f4d3
--- /dev/null
+++ b/src/win32ctl/include/tdhmsg.h
@@ -0,0 +1,39 @@
+#define ERROR_EVT_INVALID_CHANNEL_PATH 15000L
+#define ERROR_EVT_INVALID_QUERY 15001L
+#define ERROR_EVT_PUBLISHER_METADATA_NOT_FOUND 15002L
+#define ERROR_EVT_EVENT_TEMPLATE_NOT_FOUND 15003L
+#define ERROR_EVT_INVALID_PUBLISHER_NAME 15004L
+#define ERROR_EVT_INVALID_EVENT_DATA 15005L
+#define ERROR_EVT_CHANNEL_NOT_FOUND 15007L
+#define ERROR_EVT_MALFORMED_XML_TEXT 15008L
+#define ERROR_EVT_SUBSCRIPTION_TO_DIRECT_CHANNEL 15009L
+#define ERROR_EVT_CONFIGURATION_ERROR 15010L
+#define ERROR_EVT_QUERY_RESULT_STALE 15011L
+#define ERROR_EVT_QUERY_RESULT_INVALID_POSITION 15012L
+#define ERROR_EVT_NON_VALIDATING_MSXML 15013L
+#define ERROR_EVT_FILTER_ALREADYSCOPED 15014L
+#define ERROR_EVT_FILTER_NOTELTSET 15015L
+#define ERROR_EVT_FILTER_INVARG 15016L
+#define ERROR_EVT_FILTER_INVTEST 15017L
+#define ERROR_EVT_FILTER_INVTYPE 15018L
+#define ERROR_EVT_FILTER_PARSEERR 15019L
+#define ERROR_EVT_FILTER_UNSUPPORTEDOP 15020L
+#define ERROR_EVT_FILTER_UNEXPECTEDTOKEN 15021L
+#define ERROR_EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL 15022L
+#define ERROR_EVT_INVALID_CHANNEL_PROPERTY_VALUE 15023L
+#define ERROR_EVT_INVALID_PUBLISHER_PROPERTY_VALUE 15024L
+#define ERROR_EVT_CHANNEL_CANNOT_ACTIVATE 15025L
+#define ERROR_EVT_FILTER_TOO_COMPLEX 15026L
+#define ERROR_EVT_MESSAGE_NOT_FOUND 15027L
+#define ERROR_EVT_MESSAGE_ID_NOT_FOUND 15028L
+#define ERROR_EVT_UNRESOLVED_VALUE_INSERT 15029L
+#define ERROR_EVT_UNRESOLVED_PARAMETER_INSERT 15030L
+#define ERROR_EVT_MAX_INSERTS_REACHED 15031L
+#define ERROR_EVT_EVENT_DEFINITION_NOT_FOUND 15032L
+#define ERROR_EVT_MESSAGE_LOCALE_NOT_FOUND 15033L
+#define ERROR_EVT_VERSION_TOO_OLD 15034L
+#define ERROR_EVT_VERSION_TOO_NEW 15035L
+#define ERROR_EVT_CANNOT_OPEN_CHANNEL_OF_QUERY 15036L
+#define ERROR_EVT_PUBLISHER_DISABLED 15037L
+#define ERROR_EVT_FILTER_OUT_OF_RANGE 15038L
+
diff --git a/src/win32ctl/include/winevt.h b/src/win32ctl/include/winevt.h
new file mode 100644
index 0000000..0b5fdd5
--- /dev/null
+++ b/src/win32ctl/include/winevt.h
@@ -0,0 +1,758 @@
+#ifndef WINEVT_H
+#define WINEVT_H
+
+typedef HANDLE EVT_HANDLE, *PEVT_HANDLE;
+
+typedef enum _EVT_VARIANT_TYPE
+{
+ EvtVarTypeNull = 0,
+ EvtVarTypeString = 1,
+ EvtVarTypeAnsiString = 2,
+ EvtVarTypeSByte = 3,
+ EvtVarTypeByte = 4,
+ EvtVarTypeInt16 = 5,
+ EvtVarTypeUInt16 = 6,
+ EvtVarTypeInt32 = 7,
+ EvtVarTypeUInt32 = 8,
+ EvtVarTypeInt64 = 9,
+ EvtVarTypeUInt64 = 10,
+ EvtVarTypeSingle = 11,
+ EvtVarTypeDouble = 12,
+ EvtVarTypeBoolean = 13,
+ EvtVarTypeBinary = 14,
+ EvtVarTypeGuid = 15,
+ EvtVarTypeSizeT = 16,
+ EvtVarTypeFileTime = 17,
+ EvtVarTypeSysTime = 18,
+ EvtVarTypeSid = 19,
+ EvtVarTypeHexInt32 = 20,
+ EvtVarTypeHexInt64 = 21,
+
+ // these types used internally
+ EvtVarTypeEvtHandle = 32,
+ EvtVarTypeEvtXml = 35
+
+} EVT_VARIANT_TYPE;
+
+
+#define EVT_VARIANT_TYPE_MASK 0x7f
+#define EVT_VARIANT_TYPE_ARRAY 128
+
+
+typedef struct _EVT_VARIANT
+{
+ union
+ {
+ BOOL BooleanVal;
+ INT8 SByteVal;
+ INT16 Int16Val;
+ INT32 Int32Val;
+ INT64 Int64Val;
+ UINT8 ByteVal;
+ UINT16 UInt16Val;
+ UINT32 UInt32Val;
+ UINT64 UInt64Val;
+ float SingleVal;
+ double DoubleVal;
+ ULONGLONG FileTimeVal;
+ SYSTEMTIME* SysTimeVal;
+ GUID* GuidVal;
+ LPCWSTR StringVal;
+ LPCSTR AnsiStringVal;
+ PBYTE BinaryVal;
+ PSID SidVal;
+ size_t SizeTVal;
+
+ // array fields
+ BOOL* BooleanArr;
+ INT8* SByteArr;
+ INT16* Int16Arr;
+ INT32* Int32Arr;
+ INT64* Int64Arr;
+ UINT8* ByteArr;
+ UINT16* UInt16Arr;
+ UINT32* UInt32Arr;
+ UINT64* UInt64Arr;
+ float* SingleArr;
+ double* DoubleArr;
+ FILETIME* FileTimeArr;
+ SYSTEMTIME* SysTimeArr;
+ GUID* GuidArr;
+ LPWSTR* StringArr;
+ LPSTR* AnsiStringArr;
+ PSID* SidArr;
+ size_t* SizeTArr;
+
+ // internal fields
+ EVT_HANDLE EvtHandleVal;
+ LPCWSTR XmlVal;
+ LPCWSTR* XmlValArr;
+ };
+
+ DWORD Count; // number of elements (not length) in bytes.
+ DWORD Type;
+
+} EVT_VARIANT, *PEVT_VARIANT;
+
+#if 0
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Sessions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_LOGIN_CLASS
+{
+ EvtRpcLogin = 1
+
+} EVT_LOGIN_CLASS;
+
+typedef enum _EVT_RPC_LOGIN_FLAGS
+{
+ EvtRpcLoginAuthDefault = 0,
+ EvtRpcLoginAuthNegotiate,
+ EvtRpcLoginAuthKerberos,
+ EvtRpcLoginAuthNTLM
+
+} EVT_RPC_LOGIN_FLAGS;
+
+typedef struct _EVT_RPC_LOGIN
+{
+ // all str params are optional
+ LPWSTR Server;
+ LPWSTR User;
+ LPWSTR Domain;
+ LPWSTR Password;
+ DWORD Flags; // EVT_RPC_LOGIN_FLAGS
+
+} EVT_RPC_LOGIN;
+
+EVT_HANDLE WINAPI EvtOpenSession(
+ EVT_LOGIN_CLASS LoginClass,
+ PVOID Login,
+ __reserved DWORD Timeout, // currently must be 0
+ __reserved DWORD Flags // currently must be 0
+ );
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// General Purpose Functions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+BOOL WINAPI EvtClose(
+ EVT_HANDLE Object
+ );
+
+#if 0
+
+BOOL WINAPI EvtCancel(
+ EVT_HANDLE Object
+ );
+
+DWORD WINAPI EvtGetExtendedStatus(
+ DWORD BufferSize,
+ __out_ecount_part_opt(BufferSize, *BufferUsed) LPWSTR Buffer,
+ __out PDWORD BufferUsed
+ );
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Queries
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+typedef enum _EVT_QUERY_FLAGS
+{
+ EvtQueryChannelPath = 0x1,
+ EvtQueryFilePath = 0x2,
+
+ EvtQueryForwardDirection = 0x100,
+ EvtQueryReverseDirection = 0x200,
+
+ EvtQueryTolerateQueryErrors = 0x1000
+
+} EVT_QUERY_FLAGS;
+
+EVT_HANDLE WINAPI EvtQuery(
+ EVT_HANDLE Session,
+ LPCWSTR Path,
+ LPCWSTR Query,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtNext(
+ EVT_HANDLE ResultSet,
+ DWORD EventsSize,
+ PEVT_HANDLE Events,
+ DWORD Timeout,
+ DWORD Flags,
+ PDWORD Returned
+ );
+
+#if 0
+
+typedef enum _EVT_SEEK_FLAGS
+{
+ EvtSeekRelativeToFirst = 1,
+ EvtSeekRelativeToLast = 2,
+ EvtSeekRelativeToCurrent = 3,
+ EvtSeekRelativeToBookmark = 4,
+ EvtSeekOriginMask = 7,
+
+ EvtSeekStrict = 0x10000,
+
+} EVT_SEEK_FLAGS;
+
+
+
+BOOL WINAPI EvtSeek(
+ EVT_HANDLE ResultSet,
+ LONGLONG Position,
+ EVT_HANDLE Bookmark,
+ __reserved DWORD Timeout, // currently must be 0
+ DWORD Flags
+ );
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Subscriptions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_SUBSCRIBE_FLAGS
+{
+ EvtSubscribeToFutureEvents = 1,
+ EvtSubscribeStartAtOldestRecord = 2,
+ EvtSubscribeStartAfterBookmark = 3,
+ EvtSubscribeOriginMask = 3,
+
+ EvtSubscribeTolerateQueryErrors = 0x1000,
+
+ EvtSubscribeStrict = 0x10000,
+
+} EVT_SUBSCRIBE_FLAGS;
+
+typedef enum _EVT_SUBSCRIBE_NOTIFY_ACTION
+{
+ EvtSubscribeActionError = 0,
+ EvtSubscribeActionDeliver
+
+} EVT_SUBSCRIBE_NOTIFY_ACTION;
+
+typedef DWORD (WINAPI *EVT_SUBSCRIBE_CALLBACK)(
+ EVT_SUBSCRIBE_NOTIFY_ACTION Action,
+ PVOID UserContext,
+ EVT_HANDLE Event );
+
+EVT_HANDLE WINAPI EvtSubscribe(
+ EVT_HANDLE Session,
+ HANDLE SignalEvent,
+ LPCWSTR ChannelPath,
+ LPCWSTR Query,
+ EVT_HANDLE Bookmark,
+ PVOID context,
+ EVT_SUBSCRIBE_CALLBACK Callback,
+ DWORD Flags
+ );
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Rendering
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_SYSTEM_PROPERTY_ID
+{
+ EvtSystemProviderName = 0, // EvtVarTypeString
+ EvtSystemProviderGuid, // EvtVarTypeGuid
+ EvtSystemEventID, // EvtVarTypeUInt16
+ EvtSystemQualifiers, // EvtVarTypeUInt16
+ EvtSystemLevel, // EvtVarTypeUInt8
+ EvtSystemTask, // EvtVarTypeUInt16
+ EvtSystemOpcode, // EvtVarTypeUInt8
+ EvtSystemKeywords, // EvtVarTypeHexInt64
+ EvtSystemTimeCreated, // EvtVarTypeFileTime
+ EvtSystemEventRecordId, // EvtVarTypeUInt64
+ EvtSystemActivityID, // EvtVarTypeGuid
+ EvtSystemRelatedActivityID, // EvtVarTypeGuid
+ EvtSystemProcessID, // EvtVarTypeUInt32
+ EvtSystemThreadID, // EvtVarTypeUInt32
+ EvtSystemChannel, // EvtVarTypeString
+ EvtSystemComputer, // EvtVarTypeString
+ EvtSystemUserID, // EvtVarTypeSid
+ EvtSystemVersion, // EvtVarTypeUInt8
+ EvtSystemPropertyIdEND
+
+} EVT_SYSTEM_PROPERTY_ID;
+
+typedef enum _EVT_RENDER_CONTEXT_FLAGS
+{
+ EvtRenderContextValues = 0, // Render specific properties
+ EvtRenderContextSystem, // Render all system properties (System)
+ EvtRenderContextUser // Render all user properties (User/EventData)
+} EVT_RENDER_CONTEXT_FLAGS;
+
+typedef enum _EVT_RENDER_FLAGS
+{
+ EvtRenderEventValues = 0, // Variants
+ EvtRenderEventXml, // XML
+ EvtRenderBookmark // Bookmark
+} EVT_RENDER_FLAGS;
+
+EVT_HANDLE WINAPI EvtCreateRenderContext(
+ DWORD ValuePathsCount,
+ LPCWSTR* ValuePaths,
+ DWORD Flags // EVT_RENDER_CONTEXT_FLAGS
+ );
+
+BOOL WINAPI EvtRender(
+ EVT_HANDLE Context,
+ EVT_HANDLE Fragment,
+ DWORD Flags, // EVT_RENDER_FLAGS
+ DWORD BufferSize,
+ PVOID Buffer,
+ PDWORD BufferUsed,
+ PDWORD PropertyCount
+ );
+
+typedef enum _EVT_FORMAT_MESSAGE_FLAGS
+{
+ EvtFormatMessageEvent = 1,
+ EvtFormatMessageLevel,
+ EvtFormatMessageTask,
+ EvtFormatMessageOpcode,
+ EvtFormatMessageKeyword,
+ EvtFormatMessageChannel,
+ EvtFormatMessageProvider,
+ EvtFormatMessageId,
+ EvtFormatMessageXml,
+
+} EVT_FORMAT_MESSAGE_FLAGS;
+
+BOOL WINAPI EvtFormatMessage(
+ EVT_HANDLE PublisherMetadata, // Except for forwarded events
+ EVT_HANDLE Event,
+ DWORD MessageId,
+ DWORD ValueCount,
+ PEVT_VARIANT Values,
+ DWORD Flags,
+ DWORD BufferSize,
+ LPWSTR Buffer,
+ PDWORD BufferUsed
+ );
+
+
+#if 0
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Log Maintenace and Information
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_OPEN_LOG_FLAGS
+{
+ EvtOpenChannelPath = 0x1,
+ EvtOpenFilePath = 0x2
+
+} EVT_OPEN_LOG_FLAGS;
+
+typedef enum _EVT_LOG_PROPERTY_ID
+{
+ EvtLogCreationTime = 0, // EvtVarTypeFileTime
+ EvtLogLastAccessTime, // EvtVarTypeFileTime
+ EvtLogLastWriteTime, // EvtVarTypeFileTime
+ EvtLogFileSize, // EvtVarTypeUInt64
+ EvtLogAttributes, // EvtVarTypeUInt32
+ EvtLogNumberOfLogRecords, // EvtVarTypeUInt64
+ EvtLogOldestRecordNumber, // EvtVarTypeUInt64
+ EvtLogFull, // EvtVarTypeBoolean
+
+} EVT_LOG_PROPERTY_ID;
+
+EVT_HANDLE WINAPI EvtOpenLog(
+ EVT_HANDLE Session,
+ LPCWSTR Path,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtGetLogInfo(
+ EVT_HANDLE Log,
+ EVT_LOG_PROPERTY_ID PropertyId,
+ DWORD PropertyValueBufferSize,
+ PEVT_VARIANT PropertyValueBuffer,
+ __out PDWORD PropertyValueBufferUsed
+ );
+
+BOOL WINAPI EvtClearLog(
+ EVT_HANDLE Session,
+ LPCWSTR ChannelPath,
+ LPCWSTR TargetFilePath,
+ DWORD Flags
+ );
+
+typedef enum _EVT_EXPORTLOG_FLAGS
+{
+ EvtExportLogChannelPath = 0x1,
+ EvtExportLogFilePath = 0x2,
+ EvtExportLogTolerateQueryErrors = 0x1000
+
+} EVT_EXPORTLOG_FLAGS;
+
+BOOL WINAPI EvtExportLog(
+ EVT_HANDLE Session,
+ LPCWSTR Path,
+ LPCWSTR Query,
+ LPCWSTR TargetFilePath,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtArchiveExportedLog(
+ EVT_HANDLE Session,
+ LPCWSTR LogFilePath,
+ LCID Locale,
+ DWORD Flags
+ );
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Channel Configuration
+//
+////////////////////////////////////////////////////////////////////////////////
+
+
+typedef enum _EVT_CHANNEL_CONFIG_PROPERTY_ID
+{
+ EvtChannelConfigEnabled = 0, // EvtVarTypeBoolean
+ EvtChannelConfigIsolation, // EvtVarTypeUInt32, EVT_CHANNEL_ISOLATION_TYPE
+ EvtChannelConfigType, // EvtVarTypeUInt32, EVT_CHANNEL_TYPE
+ EvtChannelConfigOwningPublisher, // EvtVarTypeString
+ EvtChannelConfigClassicEventlog, // EvtVarTypeBoolean
+ EvtChannelConfigAccess, // EvtVarTypeString
+ EvtChannelLoggingConfigRetention, // EvtVarTypeBoolean
+ EvtChannelLoggingConfigAutoBackup, // EvtVarTypeBoolean
+ EvtChannelLoggingConfigMaxSize, // EvtVarTypeUInt64
+ EvtChannelLoggingConfigLogFilePath, // EvtVarTypeString
+ EvtChannelPublishingConfigLevel, // EvtVarTypeUInt32
+ EvtChannelPublishingConfigKeywords, // EvtVarTypeUInt64
+ EvtChannelPublishingConfigControlGuid, // EvtVarTypeGuid
+ EvtChannelPublishingConfigBufferSize, // EvtVarTypeUInt32
+ EvtChannelPublishingConfigMinBuffers, // EvtVarTypeUInt32
+ EvtChannelPublishingConfigMaxBuffers, // EvtVarTypeUInt32
+ EvtChannelPublishingConfigLatency, // EvtVarTypeUInt32
+ EvtChannelPublishingConfigClockType, // EvtVarTypeUInt32, EVT_CHANNEL_CLOCK_TYPE
+ EvtChannelPublishingConfigSidType, // EvtVarTypeUInt32, EVT_CHANNEL_SID_TYPE
+ EvtChannelPublisherList, // EvtVarTypeString | EVT_VARIANT_TYPE_ARRAY
+ EvtChannelConfigPropertyIdEND
+
+} EVT_CHANNEL_CONFIG_PROPERTY_ID;
+
+typedef enum _EVT_CHANNEL_TYPE
+{
+ EvtChannelTypeAdmin = 0,
+ EvtChannelTypeOperational,
+ EvtChannelTypeAnalytic,
+ EvtChannelTypeDebug
+
+} EVT_CHANNEL_TYPE;
+
+typedef enum _EVT_CHANNEL_ISOLATION_TYPE
+{
+ EvtChannelIsolationTypeApplication = 0,
+ EvtChannelIsolationTypeSystem,
+ EvtChannelIsolationTypeCustom
+
+} EVT_CHANNEL_ISOLATION_TYPE;
+
+typedef enum _EVT_CHANNEL_CLOCK_TYPE
+{
+ EvtChannelClockTypeSystemTime = 0, // System time
+ EvtChannelClockTypeQPC // Query performance counter
+
+} EVT_CHANNEL_CLOCK_TYPE;
+
+typedef enum _EVT_CHANNEL_SID_TYPE
+{
+ EvtChannelSidTypeNone = 0,
+ EvtChannelSidTypePublishing
+
+} EVT_CHANNEL_SID_TYPE;
+
+EVT_HANDLE WINAPI EvtOpenChannelEnum(
+ EVT_HANDLE Session,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtNextChannelPath(
+ EVT_HANDLE ChannelEnum,
+ DWORD ChannelPathBufferSize,
+ __out_ecount_part_opt(ChannelPathBufferSize, *ChannelPathBufferUsed)
+ LPWSTR ChannelPathBuffer,
+ __out PDWORD ChannelPathBufferUsed
+ );
+
+EVT_HANDLE WINAPI EvtOpenChannelConfig(
+ EVT_HANDLE Session,
+ LPCWSTR ChannelPath,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtSaveChannelConfig(
+ EVT_HANDLE ChannelConfig,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtSetChannelConfigProperty(
+ EVT_HANDLE ChannelConfig,
+ EVT_CHANNEL_CONFIG_PROPERTY_ID PropertyId,
+ DWORD Flags,
+ PEVT_VARIANT PropertyValue
+ );
+
+BOOL WINAPI EvtGetChannelConfigProperty(
+ EVT_HANDLE ChannelConfig,
+ EVT_CHANNEL_CONFIG_PROPERTY_ID PropertyId,
+ DWORD Flags,
+ DWORD PropertyValueBufferSize,
+ PEVT_VARIANT PropertyValueBuffer,
+ __out PDWORD PropertyValueBufferUsed
+ );
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Publisher Metadata
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_CHANNEL_REFERENCE_FLAGS
+{
+ EvtChannelReferenceImported = 0x1,
+
+} EVT_CHANNEL_REFERENCE_FLAGS;
+
+typedef enum _EVT_PUBLISHER_METADATA_PROPERTY_ID
+{
+ EvtPublisherMetadataPublisherGuid = 0, // EvtVarTypeGuid
+ EvtPublisherMetadataResourceFilePath, // EvtVarTypeString
+ EvtPublisherMetadataParameterFilePath, // EvtVarTypeString
+ EvtPublisherMetadataMessageFilePath, // EvtVarTypeString
+ EvtPublisherMetadataHelpLink, // EvtVarTypeString
+ EvtPublisherMetadataPublisherMessageID, // EvtVarTypeUInt32
+
+ EvtPublisherMetadataChannelReferences, // EvtVarTypeEvtHandle, ObjectArray
+ EvtPublisherMetadataChannelReferencePath, // EvtVarTypeString
+ EvtPublisherMetadataChannelReferenceIndex, // EvtVarTypeUInt32
+ EvtPublisherMetadataChannelReferenceID, // EvtVarTypeUInt32
+ EvtPublisherMetadataChannelReferenceFlags, // EvtVarTypeUInt32
+ EvtPublisherMetadataChannelReferenceMessageID, // EvtVarTypeUInt32
+
+ EvtPublisherMetadataLevels, // EvtVarTypeEvtHandle, ObjectArray
+ EvtPublisherMetadataLevelName, // EvtVarTypeString
+ EvtPublisherMetadataLevelValue, // EvtVarTypeUInt32
+ EvtPublisherMetadataLevelMessageID, // EvtVarTypeUInt32
+
+ EvtPublisherMetadataTasks, // EvtVarTypeEvtHandle, ObjectArray
+ EvtPublisherMetadataTaskName, // EvtVarTypeString
+ EvtPublisherMetadataTaskEventGuid, // EvtVarTypeGuid
+ EvtPublisherMetadataTaskValue, // EvtVarTypeUInt32
+ EvtPublisherMetadataTaskMessageID, // EvtVarTypeUInt32
+
+ EvtPublisherMetadataOpcodes, // EvtVarTypeEvtHandle, ObjectArray
+ EvtPublisherMetadataOpcodeName, // EvtVarTypeString
+ EvtPublisherMetadataOpcodeValue, // EvtVarTypeUInt32
+ EvtPublisherMetadataOpcodeMessageID, // EvtVarTypeUInt32
+
+ EvtPublisherMetadataKeywords, // EvtVarTypeEvtHandle, ObjectArray
+ EvtPublisherMetadataKeywordName, // EvtVarTypeString
+ EvtPublisherMetadataKeywordValue, // EvtVarTypeUInt64
+ EvtPublisherMetadataKeywordMessageID, // EvtVarTypeUInt32
+
+
+ EvtPublisherMetadataPropertyIdEND
+
+} EVT_PUBLISHER_METADATA_PROPERTY_ID;
+
+EVT_HANDLE WINAPI EvtOpenPublisherEnum(
+ EVT_HANDLE Session,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtNextPublisherId(
+ EVT_HANDLE PublisherEnum,
+ DWORD PublisherIdBufferSize,
+ __out_ecount_part_opt(PublisherIdBufferSize, *PublisherIdBufferUsed)
+ LPWSTR PublisherIdBuffer,
+ __out PDWORD PublisherIdBufferUsed
+ );
+
+#endif
+
+EVT_HANDLE WINAPI EvtOpenPublisherMetadata(
+ EVT_HANDLE Session,
+ LPCWSTR PublisherId,
+ LPCWSTR LogFilePath,
+ LCID Locale,
+ DWORD Flags
+ );
+
+#if 0
+
+BOOL WINAPI EvtGetPublisherMetadataProperty(
+ EVT_HANDLE PublisherMetadata,
+ EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId,
+ DWORD Flags,
+ DWORD PublisherMetadataPropertyBufferSize,
+ PEVT_VARIANT PublisherMetadataPropertyBuffer,
+ __out PDWORD PublisherMetadataPropertyBufferUsed
+ );
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Event Metadata Configuratin
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_EVENT_METADATA_PROPERTY_ID
+{
+ EventMetadataEventID, // EvtVarTypeUInt32
+ EventMetadataEventVersion, // EvtVarTypeUInt32
+ EventMetadataEventChannel, // EvtVarTypeUInt32
+ EventMetadataEventLevel, // EvtVarTypeUInt32
+ EventMetadataEventOpcode, // EvtVarTypeUInt32
+ EventMetadataEventTask, // EvtVarTypeUInt32
+ EventMetadataEventKeyword, // EvtVarTypeUInt64
+ EventMetadataEventMessageID,// EvtVarTypeUInt32
+ EventMetadataEventTemplate, // EvtVarTypeString
+ EvtEventMetadataPropertyIdEND
+
+} EVT_EVENT_METADATA_PROPERTY_ID;
+
+EVT_HANDLE WINAPI EvtOpenEventMetadataEnum(
+ EVT_HANDLE PublisherMetadata,
+ DWORD Flags
+ );
+
+EVT_HANDLE WINAPI EvtNextEventMetadata(
+ EVT_HANDLE EventMetadataEnum,
+ DWORD Flags
+ );
+
+BOOL WINAPI EvtGetEventMetadataProperty(
+ EVT_HANDLE EventMetadata,
+ EVT_EVENT_METADATA_PROPERTY_ID PropertyId,
+ DWORD Flags,
+ DWORD EventMetadataPropertyBufferSize,
+ PEVT_VARIANT EventMetadataPropertyBuffer,
+ __out PDWORD EventMetadataPropertyBufferUsed
+ );
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Array Access
+//
+////////////////////////////////////////////////////////////////////////////////
+
+typedef HANDLE EVT_OBJECT_ARRAY_PROPERTY_HANDLE;
+
+BOOL WINAPI EvtGetObjectArraySize(
+ EVT_OBJECT_ARRAY_PROPERTY_HANDLE ObjectArray,
+ __out PDWORD ObjectArraySize
+ );
+
+BOOL WINAPI EvtGetObjectArrayProperty(
+ EVT_OBJECT_ARRAY_PROPERTY_HANDLE ObjectArray,
+ DWORD PropertyId,
+ DWORD ArrayIndex,
+ DWORD Flags,
+ DWORD PropertyValueBufferSize,
+ PEVT_VARIANT PropertyValueBuffer,
+ __out PDWORD PropertyValueBufferUsed
+ );
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Misc Event Consumer Functions
+//
+////////////////////////////////////////////////////////////////////////////
+
+typedef enum _EVT_QUERY_PROPERTY_ID
+{
+ //
+ // list of channels or logfiles indentified in the query. Variant will be
+ // array of EvtVarTypeString.
+ //
+ EvtQueryNames,
+
+ //
+ // Array of EvtVarTypeUInt32, indicating creation status ( Win32 error
+ // code ) for the list of names returned by the EvtQueryNames
+ // property.
+ //
+ EvtQueryStatuses,
+
+ EvtQueryPropertyIdEND
+
+} EVT_QUERY_PROPERTY_ID;
+
+typedef enum _EVT_EVENT_PROPERTY_ID
+{
+ EvtEventQueryIDs = 0,
+ EvtEventPath,
+ EvtEventPropertyIdEND
+
+} EVT_EVENT_PROPERTY_ID;
+
+
+BOOL WINAPI EvtGetQueryInfo(
+ EVT_HANDLE QueryOrSubscription,
+ EVT_QUERY_PROPERTY_ID PropertyId,
+ DWORD PropertyValueBufferSize,
+ PEVT_VARIANT PropertyValueBuffer,
+ __out PDWORD PropertyValueBufferUsed
+ );
+
+EVT_HANDLE WINAPI EvtCreateBookmark(
+ __in_opt LPCWSTR BookmarkXml
+ );
+
+BOOL WINAPI EvtUpdateBookmark(
+ EVT_HANDLE Bookmark,
+ EVT_HANDLE Event
+ );
+
+BOOL WINAPI EvtGetEventInfo(
+ EVT_HANDLE Event,
+ EVT_EVENT_PROPERTY_ID PropertyId,
+ DWORD PropertyValueBufferSize,
+ PEVT_VARIANT PropertyValueBuffer,
+ __out PDWORD PropertyValueBufferUsed
+ );
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Access Control Permissions
+//
+////////////////////////////////////////////////////////////////////////////////
+
+#define EVT_READ_ACCESS 0x1
+#define EVT_WRITE_ACCESS 0x2
+#define EVT_CLEAR_ACCESS 0x4
+#define EVT_ALL_ACCESS 0x7
+
+#endif
+
+#endif // __WINEVT_H__
diff --git a/src/win32ctl/include/winmeta.h b/src/win32ctl/include/winmeta.h
new file mode 100644
index 0000000..dce52e0
--- /dev/null
+++ b/src/win32ctl/include/winmeta.h
@@ -0,0 +1,3 @@
+
+#define WINEVENT_KEYWORD_AUDIT_FAILURE 0x10000000000000LL
+
diff --git a/src/win32ctl/include/winperf.h b/src/win32ctl/include/winperf.h
new file mode 100644
index 0000000..e5dd86d
--- /dev/null
+++ b/src/win32ctl/include/winperf.h
@@ -0,0 +1,193 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _WINPERF_
+#define _WINPERF_
+
+#include <pshpack8.h>
+
+#define PERF_DATA_VERSION 1
+#define PERF_DATA_REVISION 1
+
+typedef struct _PERF_DATA_BLOCK {
+ WCHAR Signature[4];
+ DWORD LittleEndian;
+ DWORD Version;
+ DWORD Revision;
+ DWORD TotalByteLength;
+ DWORD HeaderLength;
+ DWORD NumObjectTypes;
+ LONG DefaultObject;
+ SYSTEMTIME SystemTime;
+ LARGE_INTEGER PerfTime;
+ LARGE_INTEGER PerfFreq;
+ LARGE_INTEGER PerfTime100nSec;
+ DWORD SystemNameLength;
+ DWORD SystemNameOffset;
+} PERF_DATA_BLOCK,*PPERF_DATA_BLOCK;
+
+typedef struct _PERF_OBJECT_TYPE {
+ DWORD TotalByteLength;
+ DWORD DefinitionLength;
+ DWORD HeaderLength;
+ DWORD ObjectNameTitleIndex;
+#ifdef _WIN64
+ DWORD ObjectNameTitle;
+#else
+ LPWSTR ObjectNameTitle;
+#endif
+ DWORD ObjectHelpTitleIndex;
+#ifdef _WIN64
+ DWORD ObjectHelpTitle;
+#else
+ LPWSTR ObjectHelpTitle;
+#endif
+ DWORD DetailLevel;
+ DWORD NumCounters;
+ LONG DefaultCounter;
+ LONG NumInstances;
+ DWORD CodePage;
+ LARGE_INTEGER PerfTime;
+ LARGE_INTEGER PerfFreq;
+} PERF_OBJECT_TYPE,*PPERF_OBJECT_TYPE;
+
+#define PERF_NO_INSTANCES -1
+#define PERF_SIZE_DWORD 0x00000000
+#define PERF_SIZE_LARGE 0x00000100
+#define PERF_SIZE_ZERO 0x00000200
+#define PERF_SIZE_VARIABLE_LEN 0x00000300
+#define PERF_TYPE_NUMBER 0x00000000
+#define PERF_TYPE_COUNTER 0x00000400
+#define PERF_TYPE_TEXT 0x00000800
+#define PERF_TYPE_ZERO 0x00000C00
+#define PERF_NUMBER_HEX 0x00000000
+#define PERF_NUMBER_DECIMAL 0x00010000
+#define PERF_NUMBER_DEC_1000 0x00020000
+#define PERF_COUNTER_VALUE 0x00000000
+#define PERF_COUNTER_RATE 0x00010000
+#define PERF_COUNTER_FRACTION 0x00020000
+#define PERF_COUNTER_BASE 0x00030000
+#define PERF_COUNTER_ELAPSED 0x00040000
+#define PERF_COUNTER_QUEUELEN 0x00050000
+#define PERF_COUNTER_HISTOGRAM 0x00060000
+#define PERF_COUNTER_PRECISION 0x00070000
+#define PERF_TEXT_UNICODE 0x00000000
+#define PERF_TEXT_ASCII 0x00010000
+#define PERF_TIMER_TICK 0x00000000
+#define PERF_TIMER_100NS 0x00100000
+#define PERF_OBJECT_TIMER 0x00200000
+#define PERF_DELTA_COUNTER 0x00400000
+#define PERF_DELTA_BASE 0x00800000
+#define PERF_INVERSE_COUNTER 0x01000000
+#define PERF_MULTI_COUNTER 0x02000000
+
+#define PERF_DISPLAY_NO_SUFFIX 0x00000000
+#define PERF_DISPLAY_PER_SEC 0x10000000
+#define PERF_DISPLAY_PERCENT 0x20000000
+#define PERF_DISPLAY_SECONDS 0x30000000
+#define PERF_DISPLAY_NOSHOW 0x40000000
+
+#define PERF_COUNTER_COUNTER (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_PER_SEC)
+#define PERF_COUNTER_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_COUNTER_QUEUELEN_TYPE (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_QUEUELEN | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_LARGE_QUEUELEN_TYPE (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_QUEUELEN | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_100NS_QUEUELEN_TYPE (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_QUEUELEN | PERF_TIMER_100NS | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_QUEUELEN | PERF_OBJECT_TIMER | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_BULK_COUNT (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_PER_SEC)
+#define PERF_COUNTER_TEXT (PERF_SIZE_VARIABLE_LEN | PERF_TYPE_TEXT | PERF_TEXT_UNICODE | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_RAWCOUNT (PERF_SIZE_DWORD | PERF_TYPE_NUMBER | PERF_NUMBER_DECIMAL | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_LARGE_RAWCOUNT (PERF_SIZE_LARGE | PERF_TYPE_NUMBER | PERF_NUMBER_DECIMAL | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_RAWCOUNT_HEX (PERF_SIZE_DWORD | PERF_TYPE_NUMBER | PERF_NUMBER_HEX | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_LARGE_RAWCOUNT_HEX (PERF_SIZE_LARGE | PERF_TYPE_NUMBER | PERF_NUMBER_HEX | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_SAMPLE_FRACTION (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION | PERF_DELTA_COUNTER | PERF_DELTA_BASE | PERF_DISPLAY_PERCENT)
+#define PERF_SAMPLE_COUNTER (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_NODATA (PERF_SIZE_ZERO | PERF_DISPLAY_NOSHOW)
+#define PERF_COUNTER_TIMER_INV (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_INVERSE_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_SAMPLE_BASE (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_BASE | PERF_DISPLAY_NOSHOW | 0x00000001)
+#define PERF_AVERAGE_TIMER (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION | PERF_DISPLAY_SECONDS)
+#define PERF_AVERAGE_BASE (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_BASE | PERF_DISPLAY_NOSHOW | 0x00000002)
+#define PERF_AVERAGE_BULK (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION | PERF_DISPLAY_NOSHOW)
+#define PERF_OBJ_TIME_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_OBJECT_TIMER | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_100NSEC_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_100NS | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_100NSEC_TIMER_INV (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_100NS | PERF_DELTA_COUNTER | PERF_INVERSE_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_COUNTER_MULTI_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_DELTA_COUNTER | PERF_TIMER_TICK | PERF_MULTI_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_COUNTER_MULTI_TIMER_INV (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_RATE | PERF_DELTA_COUNTER | PERF_MULTI_COUNTER | PERF_TIMER_TICK | PERF_INVERSE_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_COUNTER_MULTI_BASE (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_BASE | PERF_MULTI_COUNTER | PERF_DISPLAY_NOSHOW)
+#define PERF_100NSEC_MULTI_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_DELTA_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_100NS | PERF_MULTI_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_100NSEC_MULTI_TIMER_INV (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_DELTA_COUNTER | PERF_COUNTER_RATE | PERF_TIMER_100NS | PERF_MULTI_COUNTER | PERF_INVERSE_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_RAW_FRACTION (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION | PERF_DISPLAY_PERCENT)
+#define PERF_LARGE_RAW_FRACTION (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_FRACTION | PERF_DISPLAY_PERCENT)
+#define PERF_RAW_BASE (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_BASE | PERF_DISPLAY_NOSHOW | 0x00000003)
+#define PERF_LARGE_RAW_BASE (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_BASE | PERF_DISPLAY_NOSHOW)
+#define PERF_ELAPSED_TIME (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_ELAPSED | PERF_OBJECT_TIMER | PERF_DISPLAY_SECONDS)
+#define PERF_COUNTER_HISTOGRAM_TYPE 0x80000000
+#define PERF_COUNTER_DELTA (PERF_SIZE_DWORD | PERF_TYPE_COUNTER | PERF_COUNTER_VALUE | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_COUNTER_LARGE_DELTA (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_VALUE | PERF_DELTA_COUNTER | PERF_DISPLAY_NO_SUFFIX)
+#define PERF_PRECISION_SYSTEM_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_PRECISION | PERF_TIMER_TICK | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_PRECISION_100NS_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_PRECISION | PERF_TIMER_100NS | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+#define PERF_PRECISION_OBJECT_TIMER (PERF_SIZE_LARGE | PERF_TYPE_COUNTER | PERF_COUNTER_PRECISION | PERF_OBJECT_TIMER | PERF_DELTA_COUNTER | PERF_DISPLAY_PERCENT)
+
+#define PERF_PRECISION_TIMESTAMP PERF_LARGE_RAW_BASE
+
+#define PERF_DETAIL_NOVICE 100
+#define PERF_DETAIL_ADVANCED 200
+#define PERF_DETAIL_EXPERT 300
+#define PERF_DETAIL_WIZARD 400
+
+typedef struct _PERF_COUNTER_DEFINITION {
+ DWORD ByteLength;
+ DWORD CounterNameTitleIndex;
+#ifdef _WIN64
+ DWORD CounterNameTitle;
+#else
+ LPWSTR CounterNameTitle;
+#endif
+ DWORD CounterHelpTitleIndex;
+#ifdef _WIN64
+ DWORD CounterHelpTitle;
+#else
+ LPWSTR CounterHelpTitle;
+#endif
+ LONG DefaultScale;
+ DWORD DetailLevel;
+ DWORD CounterType;
+ DWORD CounterSize;
+ DWORD CounterOffset;
+} PERF_COUNTER_DEFINITION,*PPERF_COUNTER_DEFINITION;
+
+#define PERF_NO_UNIQUE_ID -1
+
+typedef struct _PERF_INSTANCE_DEFINITION {
+ DWORD ByteLength;
+ DWORD ParentObjectTitleIndex;
+ DWORD ParentObjectInstance;
+ LONG UniqueID;
+ DWORD NameOffset;
+ DWORD NameLength;
+} PERF_INSTANCE_DEFINITION,*PPERF_INSTANCE_DEFINITION;
+
+typedef struct _PERF_COUNTER_BLOCK {
+ DWORD ByteLength;
+
+} PERF_COUNTER_BLOCK,*PPERF_COUNTER_BLOCK;
+
+#define PERF_QUERY_OBJECTS ((LONG)0x80000000)
+#define PERF_QUERY_GLOBAL ((LONG)0x80000001)
+#define PERF_QUERY_COSTLY ((LONG)0x80000002)
+
+typedef DWORD (WINAPI PM_OPEN_PROC)(LPWSTR);
+typedef DWORD (WINAPI PM_COLLECT_PROC)(LPWSTR,LPVOID *,LPDWORD,LPDWORD);
+typedef DWORD (WINAPI PM_CLOSE_PROC)(void);
+typedef DWORD (WINAPI PM_QUERY_PROC)(LPDWORD,LPVOID *,LPDWORD,LPDWORD);
+
+#define MAX_PERF_OBJECTS_IN_QUERY_FUNCTION (64L)
+
+#define WINPERF_LOG_NONE 0
+#define WINPERF_LOG_USER 1
+#define WINPERF_LOG_DEBUG 2
+#define WINPERF_LOG_VERBOSE 3
+
+#include <poppack.h>
+#endif
diff --git a/src/win32ctl/include/wmistr.h b/src/win32ctl/include/wmistr.h
new file mode 100644
index 0000000..5fceb2f
--- /dev/null
+++ b/src/win32ctl/include/wmistr.h
@@ -0,0 +1,198 @@
+/**
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the w64 mingw-runtime package.
+ * No warranty is given; refer to the file DISCLAIMER.PD within this package.
+ */
+#ifndef _WMISTR_
+#define _WMISTR_
+
+/* --- start added by kenj */
+#undef __MINGW_EXTENSION
+#if defined(__GNUC__) || defined(__GNUG__)
+#define __MINGW_EXTENSION __extension__
+#else
+#define __MINGW_EXTENSION
+#endif
+
+#ifndef __C89_NAMELESS
+#define __C89_NAMELESS __MINGW_EXTENSION
+
+#define __C89_NAMELESSSTRUCTNAME
+#define __C89_NAMELESSUNIONNAME
+#endif
+/* --- end added by kenj */
+
+typedef struct _WNODE_HEADER {
+ ULONG BufferSize;
+ ULONG ProviderId;
+ __C89_NAMELESS union {
+ ULONG64 HistoricalContext;
+ __C89_NAMELESS struct {
+ ULONG Version;
+ ULONG Linkage;
+ };
+ };
+ __C89_NAMELESS union {
+ ULONG CountLost;
+ HANDLE KernelHandle;
+ LARGE_INTEGER TimeStamp;
+ };
+ GUID Guid;
+ ULONG ClientContext;
+ ULONG Flags;
+} WNODE_HEADER,*PWNODE_HEADER;
+
+#define WNODE_FLAG_ALL_DATA 0x00000001
+#define WNODE_FLAG_SINGLE_INSTANCE 0x00000002
+#define WNODE_FLAG_SINGLE_ITEM 0x00000004
+#define WNODE_FLAG_EVENT_ITEM 0x00000008
+#define WNODE_FLAG_FIXED_INSTANCE_SIZE 0x00000010
+#define WNODE_FLAG_TOO_SMALL 0x00000020
+#define WNODE_FLAG_INSTANCES_SAME 0x00000040
+#define WNODE_FLAG_STATIC_INSTANCE_NAMES 0x00000080
+#define WNODE_FLAG_INTERNAL 0x00000100
+#define WNODE_FLAG_USE_TIMESTAMP 0x00000200
+#define WNODE_FLAG_PERSIST_EVENT 0x00000400
+#define WNODE_FLAG_EVENT_REFERENCE 0x00002000
+#define WNODE_FLAG_ANSI_INSTANCENAMES 0x00004000
+#define WNODE_FLAG_METHOD_ITEM 0x00008000
+#define WNODE_FLAG_PDO_INSTANCE_NAMES 0x00010000
+#define WNODE_FLAG_TRACED_GUID 0x00020000
+#define WNODE_FLAG_LOG_WNODE 0x00040000
+#define WNODE_FLAG_USE_GUID_PTR 0x00080000
+#define WNODE_FLAG_USE_MOF_PTR 0x00100000
+#define WNODE_FLAG_NO_HEADER 0x00200000
+#define WNODE_FLAG_SEVERITY_MASK 0xff000000
+
+typedef struct {
+ ULONG OffsetInstanceData;
+ ULONG LengthInstanceData;
+} OFFSETINSTANCEDATAANDLENGTH,*POFFSETINSTANCEDATAANDLENGTH;
+
+typedef struct tagWNODE_ALL_DATA {
+ struct _WNODE_HEADER WnodeHeader;
+ ULONG DataBlockOffset;
+ ULONG InstanceCount;
+ ULONG OffsetInstanceNameOffsets;
+ __C89_NAMELESS union {
+ ULONG FixedInstanceSize;
+ OFFSETINSTANCEDATAANDLENGTH OffsetInstanceDataAndLength[1];
+ };
+} WNODE_ALL_DATA,*PWNODE_ALL_DATA;
+
+typedef struct tagWNODE_SINGLE_INSTANCE {
+ struct _WNODE_HEADER WnodeHeader;
+ ULONG OffsetInstanceName;
+ ULONG InstanceIndex;
+ ULONG DataBlockOffset;
+ ULONG SizeDataBlock;
+ UCHAR VariableData[];
+} WNODE_SINGLE_INSTANCE,*PWNODE_SINGLE_INSTANCE;
+
+typedef struct tagWNODE_SINGLE_ITEM {
+ struct _WNODE_HEADER WnodeHeader;
+ ULONG OffsetInstanceName;
+ ULONG InstanceIndex;
+ ULONG ItemId;
+ ULONG DataBlockOffset;
+ ULONG SizeDataItem;
+ UCHAR VariableData[];
+} WNODE_SINGLE_ITEM,*PWNODE_SINGLE_ITEM;
+
+typedef struct tagWNODE_METHOD_ITEM {
+ struct _WNODE_HEADER WnodeHeader;
+ ULONG OffsetInstanceName;
+ ULONG InstanceIndex;
+ ULONG MethodId;
+ ULONG DataBlockOffset;
+ ULONG SizeDataBlock;
+ UCHAR VariableData[];
+} WNODE_METHOD_ITEM,*PWNODE_METHOD_ITEM;
+
+typedef struct tagWNODE_EVENT_ITEM {
+ struct _WNODE_HEADER WnodeHeader;
+} WNODE_EVENT_ITEM,*PWNODE_EVENT_ITEM;
+
+typedef struct tagWNODE_EVENT_REFERENCE {
+ struct _WNODE_HEADER WnodeHeader;
+ GUID TargetGuid;
+ ULONG TargetDataBlockSize;
+ __C89_NAMELESS union {
+ ULONG TargetInstanceIndex;
+ WCHAR TargetInstanceName[1];
+ };
+} WNODE_EVENT_REFERENCE,*PWNODE_EVENT_REFERENCE;
+
+typedef struct tagWNODE_TOO_SMALL {
+ struct _WNODE_HEADER WnodeHeader;
+ ULONG SizeNeeded;
+} WNODE_TOO_SMALL,*PWNODE_TOO_SMALL;
+
+typedef struct {
+ GUID Guid;
+ ULONG Flags;
+ ULONG InstanceCount;
+ __C89_NAMELESS union {
+ ULONG InstanceNameList;
+ ULONG BaseNameOffset;
+ ULONG_PTR Pdo;
+ ULONG_PTR InstanceInfo;
+ };
+} WMIREGGUIDW,*PWMIREGGUIDW;
+
+typedef WMIREGGUIDW WMIREGGUID;
+typedef PWMIREGGUIDW PWMIREGGUID;
+
+#define WMIREG_FLAG_EXPENSIVE 0x00000001
+#define WMIREG_FLAG_INSTANCE_LIST 0x00000004
+#define WMIREG_FLAG_INSTANCE_BASENAME 0x00000008
+#define WMIREG_FLAG_INSTANCE_PDO 0x00000020
+#define WMIREG_FLAG_REMOVE_GUID 0x00010000
+#define WMIREG_FLAG_RESERVED1 0x00020000
+#define WMIREG_FLAG_RESERVED2 0x00040000
+#define WMIREG_FLAG_TRACED_GUID 0x00080000
+#define WMIREG_FLAG_TRACE_CONTROL_GUID 0x00001000
+#define WMIREG_FLAG_EVENT_ONLY_GUID 0x00000040
+
+typedef struct {
+ ULONG BufferSize;
+ ULONG NextWmiRegInfo;
+ ULONG RegistryPath;
+ ULONG MofResourceName;
+ ULONG GuidCount;
+ WMIREGGUIDW WmiRegGuid[];
+} WMIREGINFOW,*PWMIREGINFOW;
+
+typedef WMIREGINFOW WMIREGINFO;
+typedef PWMIREGINFOW PWMIREGINFO;
+
+typedef enum {
+ WMI_GET_ALL_DATA = 0,WMI_GET_SINGLE_INSTANCE = 1,WMI_SET_SINGLE_INSTANCE = 2,WMI_SET_SINGLE_ITEM = 3,WMI_ENABLE_EVENTS = 4,WMI_DISABLE_EVENTS = 5,
+ WMI_ENABLE_COLLECTION = 6,WMI_DISABLE_COLLECTION = 7,WMI_REGINFO = 8,WMI_EXECUTE_METHOD = 9
+} WMIDPREQUESTCODE;
+
+#if defined(_WINNT_) || defined(WINNT)
+
+#define WMI_GUIDTYPE_TRACECONTROL 0
+#define WMI_GUIDTYPE_TRACE 1
+#define WMI_GUIDTYPE_DATA 2
+#define WMI_GUIDTYPE_EVENT 3
+
+#define WMIGUID_QUERY 0x0001
+#define WMIGUID_SET 0x0002
+#define WMIGUID_NOTIFICATION 0x0004
+#define WMIGUID_READ_DESCRIPTION 0x0008
+#define WMIGUID_EXECUTE 0x0010
+#define TRACELOG_CREATE_REALTIME 0x0020
+#define TRACELOG_CREATE_ONDISK 0x0040
+#define TRACELOG_GUID_ENABLE 0x0080
+#define TRACELOG_ACCESS_KERNEL_LOGGER 0x0100
+#define TRACELOG_CREATE_INPROC 0x0200
+#define TRACELOG_ACCESS_REALTIME 0x0400
+#define TRACELOG_REGISTER_GUIDS 0x0800
+
+#define WMIGUID_ALL_ACCESS (STANDARD_RIGHTS_READ | SYNCHRONIZE | WMIGUID_QUERY | WMIGUID_SET | WMIGUID_NOTIFICATION | WMIGUID_READ_DESCRIPTION | WMIGUID_EXECUTE | TRACELOG_CREATE_REALTIME | TRACELOG_CREATE_ONDISK | TRACELOG_GUID_ENABLE | TRACELOG_ACCESS_KERNEL_LOGGER | TRACELOG_CREATE_INPROC | TRACELOG_ACCESS_REALTIME | TRACELOG_REGISTER_GUIDS)
+
+#define WMI_GLOBAL_LOGGER_ID 0x0001
+#endif
+#endif
diff --git a/src/win32ctl/lib/GNUmakefile b/src/win32ctl/lib/GNUmakefile
new file mode 100755
index 0000000..7782434
--- /dev/null
+++ b/src/win32ctl/lib/GNUmakefile
@@ -0,0 +1,42 @@
+#!gmake
+#
+# Copyright (c) 2010-2011 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+ifeq "$(TARGET_OS)" "mingw"
+STATICLIBS = libpcp_pdh.a libpcp_tdh.a
+endif
+STATICDEFS = libpcp_pdh.def libpcp_tdh.def
+MKWIN32LIB = sh -c "$(DLLTOOL) -d $< -k -l $@"
+
+LSRCFILES = $(STATICDEFS)
+LDIRT = $(STATICLIBS)
+
+default: $(STATICLIBS)
+
+include $(BUILDRULES)
+
+libpcp_pdh.a: libpcp_pdh.def
+ $(MKWIN32LIB)
+
+libpcp_tdh.a: libpcp_tdh.def
+ $(MKWIN32LIB)
+
+default_pcp: default
+
+install: default
+
+install_pcp: install
diff --git a/src/win32ctl/lib/libpcp_pdh.def b/src/win32ctl/lib/libpcp_pdh.def
new file mode 100644
index 0000000..4155414
--- /dev/null
+++ b/src/win32ctl/lib/libpcp_pdh.def
@@ -0,0 +1,12 @@
+LIBRARY pdh.dll
+EXPORTS
+DllInstall
+PdhAddCounterA@16
+PdhRemoveCounter@4
+PdhCollectQueryData@4
+PdhExpandCounterPathA@12
+PdhGetCounterInfoA@16
+PdhGetRawCounterValue@12
+PdhGetFormattedCounterValue@16
+PdhOpenQueryA@12
+PdhCloseQuery@4
diff --git a/src/win32ctl/lib/libpcp_tdh.def b/src/win32ctl/lib/libpcp_tdh.def
new file mode 100644
index 0000000..7563764
--- /dev/null
+++ b/src/win32ctl/lib/libpcp_tdh.def
@@ -0,0 +1,10 @@
+LIBRARY tdh.dll
+EXPORTS
+DllInstall
+TdhGetProperty@28
+TdhGetPropertySize@24
+TdhGetEventMapInformation@16
+TdhQueryProviderFieldInformation@24
+TdhGetEventInformation@20
+TdhEnumerateProviderFieldInformation@16
+TdhEnumerateProviders@8
diff --git a/src/win32ctl/mkaf.bat b/src/win32ctl/mkaf.bat
new file mode 100755
index 0000000..7c78626
--- /dev/null
+++ b/src/win32ctl/mkaf.bat
@@ -0,0 +1,10 @@
+@echo off
+if "%OS%" == "Windows_NT" goto WinNT
+%PCP_DIR%\bin\sh.exe mkaf.sh %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto endofbash
+:WinNT
+%PCP_DIR%\bin\sh.exe mkaf.sh %*
+if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofbash
+if %errorlevel% == 9009 echo You do not have sh.exe in your PCP_DIR.
+if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
+:endofbash
diff --git a/src/win32ctl/pcp.bat b/src/win32ctl/pcp.bat
new file mode 100755
index 0000000..81f72be
--- /dev/null
+++ b/src/win32ctl/pcp.bat
@@ -0,0 +1,10 @@
+@echo off
+if "%OS%" == "Windows_NT" goto WinNT
+%PCP_DIR%\bin\sh.exe pcp.sh %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto endofbash
+:WinNT
+%PCP_DIR%\bin\sh.exe pcp.sh %*
+if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofbash
+if %errorlevel% == 9009 echo You do not have sh.exe in your PCP_DIR.
+if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
+:endofbash
diff --git a/src/win32ctl/pmafm.bat b/src/win32ctl/pmafm.bat
new file mode 100755
index 0000000..91e96b7
--- /dev/null
+++ b/src/win32ctl/pmafm.bat
@@ -0,0 +1,10 @@
+@echo off
+if "%OS%" == "Windows_NT" goto WinNT
+%PCP_DIR%\bin\sh.exe pmafm.sh %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto endofbash
+:WinNT
+%PCP_DIR%\bin\sh.exe pmafm.sh %*
+if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofbash
+if %errorlevel% == 9009 echo You do not have sh.exe in your PCP_DIR.
+if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
+:endofbash
diff --git a/src/win32ctl/pmsignal.bat b/src/win32ctl/pmsignal.bat
new file mode 100755
index 0000000..6bbcef2
--- /dev/null
+++ b/src/win32ctl/pmsignal.bat
@@ -0,0 +1,10 @@
+@echo off
+if "%OS%" == "Windows_NT" goto WinNT
+%PCP_DIR%\bin\sh.exe pmsignal.sh %1 %2 %3 %4 %5 %6 %7 %8 %9
+goto endofbash
+:WinNT
+%PCP_DIR%\bin\sh.exe pmsignal.sh %*
+if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto endofbash
+if %errorlevel% == 9009 echo You do not have sh.exe in your PCP_DIR.
+if errorlevel 1 goto script_failed_so_exit_with_non_zero_val 2>nul
+:endofbash
diff --git a/src/win32ctl/services/GNUmakefile b/src/win32ctl/services/GNUmakefile
new file mode 100644
index 0000000..b7eaa33
--- /dev/null
+++ b/src/win32ctl/services/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pcp-services.c
+CMDTARGET = pcp-services.exe
+LLDLIBS = $(PCPLIB)
+LSRCFILES = $(WRAPPERS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGET)
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/win32ctl/services/pcp-services.c b/src/win32ctl/services/pcp-services.c
new file mode 100644
index 0000000..7164fde
--- /dev/null
+++ b/src/win32ctl/services/pcp-services.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2008-2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+#include <wtypes.h>
+#include <winnt.h>
+#include <winsvc.h>
+#include <winuser.h>
+
+typedef enum {
+ PCP_SERVICE_COLLECTORS = 0,
+ PCP_SERVICE_INFERENCE = 1,
+ PCP_SERVICE_PROXY = 2,
+ NUM_SERVICES
+} PCPSERVICE;
+
+VOID WINAPI pcpCollectorsSetup(DWORD, LPTSTR *);
+VOID WINAPI pcpInferenceSetup(DWORD, LPTSTR *);
+VOID WINAPI pcpProxySetup(DWORD, LPTSTR *);
+DWORD WINAPI pcpCollectorsDispatch(DWORD, DWORD, LPVOID, LPVOID);
+DWORD WINAPI pcpInferenceDispatch(DWORD, DWORD, LPVOID, LPVOID);
+DWORD WINAPI pcpProxyDispatch(DWORD, DWORD, LPVOID, LPVOID);
+
+typedef VOID WINAPI (*SETUPFUNC)(DWORD, LPTSTR *);
+typedef DWORD WINAPI (*DISPATCHFUNC)(DWORD, DWORD, LPVOID, LPVOID);
+
+struct {
+ TCHAR * name;
+ TCHAR * script;
+ HANDLE stopEvent;
+ SERVICE_STATUS status;
+ SERVICE_STATUS_HANDLE statusHandle;
+ SETUPFUNC setup;
+ DISPATCHFUNC dispatch;
+} services[3] = {
+ { .name = "PCP Collector Processes",
+ .script = "pcp",
+ .setup = pcpCollectorsSetup,
+ .dispatch = pcpCollectorsDispatch,
+ },
+ { .name = "PCP Inference Engines",
+ .script = "pmie",
+ .setup = pcpInferenceSetup,
+ .dispatch = pcpInferenceDispatch,
+ },
+ { .name = "PCP Collector Proxy",
+ .script = "pmproxy",
+ .setup = pcpProxySetup,
+ .dispatch = pcpProxyDispatch,
+ },
+};
+
+static char pcpdir[MAXPATHLEN+16]; /* PCP_DIR environment variable */
+static char pcpconf[MAXPATHLEN+16]; /* PCP_CONF string for putenv */
+static char pcpdirenv[MAXPATHLEN+16]; /* PCP_DIR string for putenv */
+
+int
+pcpScript(const char *name, const char *action)
+{
+ char s[MAXPATHLEN];
+ snprintf(s, sizeof(s), "%s\\bin\\sh.exe /etc/%s %s", pcpdir, name, action);
+ return system(s);
+}
+
+VOID
+pcpSetServiceState(PCPSERVICE s, DWORD state, DWORD code, DWORD waitHint)
+{
+ services[s].status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
+ services[s].status.dwCurrentState = state;
+ services[s].status.dwWin32ExitCode = code;
+ services[s].status.dwWaitHint = waitHint;
+
+ if (state == SERVICE_START_PENDING)
+ services[s].status.dwControlsAccepted = 0;
+ else
+ services[s].status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+
+ if ((state == SERVICE_RUNNING) || (state == SERVICE_STOPPED))
+ services[s].status.dwCheckPoint = 0;
+ else
+ services[s].status.dwCheckPoint++;
+
+ /* Report the status of the service to the SCM. */
+ SetServiceStatus(services[s].statusHandle, &services[s].status);
+}
+
+VOID
+pcpServiceMain(DWORD argc, LPTSTR *argv, PCPSERVICE s)
+{
+ char *default_basedirs[] = { "C:\\Glider", "C:\\MSYS" };
+ char *default_service = "pcp";
+ char *service = NULL, *basedir = NULL;
+ int i;
+
+ if (argc > 1) { /* first argument is service name */
+ service = argv[1];
+ for (i = 0; i < NUM_SERVICES; i++)
+ if (strcmp(service, services[i].name) == 0)
+ break;
+ if (i == NUM_SERVICES)
+ return; /* unknown service requested - bail out */
+ }
+ if (service == NULL)
+ service = default_service;
+
+ if (argc > 2) /* second argument is PCP_DIR */
+ basedir = argv[2];
+ else if ((basedir = getenv("PCP_DIR")) == NULL) {
+ for (i = 0; i < 3; i++)
+ if (access(default_basedirs[i], R_OK) == 0) {
+ basedir = default_basedirs[i];
+ break;
+ }
+ }
+ if (!basedir || access(basedir, R_OK) != 0)
+ return; /* stuffed up if we have no PCP_DIR - bail out */
+
+ snprintf(pcpdir, sizeof(pcpdir), "%s", basedir);
+ snprintf(pcpconf, sizeof(pcpconf), "PCP_CONF=%s\\etc\\pcp.conf", pcpdir);
+ snprintf(pcpdirenv, sizeof(pcpdirenv), "PCP_DIR=%s", pcpdir);
+ putenv(pcpconf);
+ putenv(pcpdirenv);
+
+ services[s].statusHandle = RegisterServiceCtrlHandlerEx(
+ services[s].name, services[s].dispatch, NULL);
+ if (!services[s].statusHandle)
+ return;
+
+ pcpSetServiceState(s, SERVICE_START_PENDING, NO_ERROR, 0);
+
+ /*
+ * Create an event. The control handler function signals
+ * this event when it receives the stop control code.
+ */
+ services[s].stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!services[s].stopEvent) {
+ pcpSetServiceState(s, SERVICE_STOPPED, NO_ERROR, 0);
+ return;
+ }
+
+ /*
+ * Go, go, go... run the start script for this service.
+ */
+ if (pcpScript(services[s].script, "start") != 0) {
+ pcpSetServiceState(s, SERVICE_STOPPED, NO_ERROR, 0);
+ return;
+ }
+
+ pcpSetServiceState(s, SERVICE_RUNNING, NO_ERROR, 0);
+
+ /* Check whether to stop the service. */
+ WaitForSingleObject(services[s].stopEvent, INFINITE);
+
+ /* ServiceState already set to SERVICE_STOP_PENDING */
+ pcpScript(services[s].script, "stop");
+
+ pcpSetServiceState(s, SERVICE_STOPPED, NO_ERROR, 0);
+}
+
+VOID WINAPI
+pcpCollectorsSetup(DWORD argc, LPTSTR *argv)
+{
+ pcpServiceMain(argc, argv, PCP_SERVICE_COLLECTORS);
+}
+
+VOID WINAPI
+pcpInferenceSetup(DWORD argc, LPTSTR *argv)
+{
+ pcpServiceMain(argc, argv, PCP_SERVICE_INFERENCE);
+}
+
+VOID WINAPI
+pcpProxySetup(DWORD argc, LPTSTR *argv)
+{
+ pcpServiceMain(argc, argv, PCP_SERVICE_PROXY);
+}
+
+DWORD
+pcpServiceHandler(DWORD dwControl, DWORD dwEventType,
+ LPVOID lpEventData, LPVOID lpContext, PCPSERVICE s)
+{
+ switch(dwControl) {
+ case SERVICE_CONTROL_STOP:
+ services[s].status.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus(services[s].statusHandle, &services[s].status);
+ SetEvent(services[s].stopEvent);
+ return NO_ERROR;
+
+ default:
+ break;
+ }
+
+ /* Send current status (done for most request types) */
+ SetServiceStatus(services[s].statusHandle, &services[s].status);
+ return NO_ERROR;
+}
+
+DWORD WINAPI
+pcpCollectorsDispatch(DWORD ctrl, DWORD type, LPVOID data, LPVOID ctxt)
+{
+ return pcpServiceHandler(ctrl, type, data, ctxt, PCP_SERVICE_COLLECTORS);
+}
+
+DWORD WINAPI
+pcpInferenceDispatch(DWORD ctrl, DWORD type, LPVOID data, LPVOID ctxt)
+{
+ return pcpServiceHandler(ctrl, type, data, ctxt, PCP_SERVICE_INFERENCE);
+}
+
+DWORD WINAPI
+pcpProxyDispatch(DWORD ctrl, DWORD type, LPVOID data, LPVOID ctxt)
+{
+ return pcpServiceHandler(ctrl, type, data, ctxt, PCP_SERVICE_PROXY);
+}
+
+int
+main(int argc, char **argv)
+{
+ SERVICE_TABLE_ENTRY dispatchTable[2];
+
+ __pmSetProgname(argv[0]);
+
+ /* setup dispatch table and sentinel */
+ dispatchTable[0].lpServiceName = services[0].name;
+ dispatchTable[0].lpServiceProc = services[0].setup;
+ dispatchTable[1].lpServiceName = NULL;
+ dispatchTable[1].lpServiceProc = NULL;
+
+ if (!StartServiceCtrlDispatcher(dispatchTable)) {
+ DWORD c = GetLastError();
+ fprintf(stderr, "%s: cannot dispatch services (%ld)\n", pmProgname, c);
+ if (c == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
+ fprintf(stderr, "%s: run as service, not on console\n", pmProgname);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/win32ctl/setevent/GNUmakefile b/src/win32ctl/setevent/GNUmakefile
new file mode 100644
index 0000000..d00af4f
--- /dev/null
+++ b/src/win32ctl/setevent/GNUmakefile
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2008-2009 Aconex. All Rights Reserved.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; either version 2 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+
+TOPDIR = ../../..
+include $(TOPDIR)/src/include/builddefs
+
+CFILES = pcp-setevent.c
+CMDTARGET = pcp-setevent.exe
+LLDLIBS = $(PCPLIB)
+LSRCFILES = $(WRAPPERS)
+
+default: build-me
+
+include $(BUILDRULES)
+
+ifeq "$(TARGET_OS)" "mingw"
+build-me: $(CMDTARGET)
+install: default
+ $(INSTALL) -m 755 $(CMDTARGET) $(PCP_BIN_DIR)/$(CMDTARGET)
+else
+build-me:
+install:
+endif
+
+default_pcp: default
+
+install_pcp: install
diff --git a/src/win32ctl/setevent/pcp-setevent.c b/src/win32ctl/setevent/pcp-setevent.c
new file mode 100644
index 0000000..dc6ad68
--- /dev/null
+++ b/src/win32ctl/setevent/pcp-setevent.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2009 Aconex. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include "pmapi.h"
+#include "impl.h"
+
+enum {
+ PCP_SIGHUP = 1,
+ PCP_SIGUSR1 = 2,
+ PCP_SIGTERM = 3,
+ PCP_SIGKILL = 4,
+};
+
+int
+atosig(const char *sig)
+{
+ if (strcmp(sig, "HUP") == 0)
+ return PCP_SIGHUP;
+ if (strcmp(sig, "USR1") == 0)
+ return PCP_SIGUSR1;
+ if (strcmp(sig, "TERM") == 0)
+ return PCP_SIGTERM;
+ if (strcmp(sig, "KILL") == 0)
+ return PCP_SIGKILL;
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ pid_t pid;
+ char name[64];
+ int sig, error = 0;
+
+ __pmSetProgname(argv[0]);
+
+ if (argc != 3)
+ error++;
+ else if ((sig = atosig(argv[1])) < 1)
+ error++;
+ else if ((pid = (pid_t)atoi(argv[2])) < 1)
+ error++;
+
+ if (error) {
+ fprintf(stderr, "Usage: %s <HUP|USR1|TERM|KILL> <PID>\n", pmProgname);
+ return 2;
+ }
+
+ if (sig == PCP_SIGKILL) {
+ __pmProcessTerminate(pid, 1);
+ return 0;
+ }
+
+ if (!__pmProcessExists(pid)) {
+ fprintf(stderr, "%s: OpenEvent(%s) failed on PID %" FMT_PID " (%ld)\n",
+ pmProgname, name, pid, GetLastError());
+ return 1;
+ }
+
+ snprintf(name, sizeof(name), "PCP/%" FMT_PID "/SIG%s", pid, argv[1]);
+ HANDLE h = OpenEvent(EVENT_MODIFY_STATE, FALSE, TEXT(name));
+ if (!h) {
+ fprintf(stderr, "%s: OpenEvent(%s) failed on PID %" FMT_PID " (%ld)\n",
+ pmProgname, name, pid, GetLastError());
+ return 1;
+ }
+ if (!SetEvent(h)) {
+ fprintf(stderr, "%s: SetEvent(%s) failed on PID %" FMT_PID " (%ld)\n",
+ pmProgname, name, pid, GetLastError());
+ return 1;
+ }
+
+ return 0;
+}